commit e4d53119061cb64a7943af703040571a0270563c Author: Jonny_Bro (Nikita) Date: Thu Nov 16 15:01:19 2023 +0500 first commit diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..a15581d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +custom: + - https://octothorp.team/shop/chips diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..478024d --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +/docker/runtime-data +config.ts + +# ignore assets +*.jpg +*.png +*.xcf +*.mdl +*.phy +*.vtx +*.vvd +*.vmt +*.vtf +*.wav +*.mp3 +*.ogg +*.eot +*.svg +*.ttf +*.woff +*.otf diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..8d23d73 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +image: 'octoteam/gmod-test:latest' + +services: + - mariadb:latest + +variables: + GIT_SUBMODULE_STRATEGY: recursive + MYSQL_DATABASE: gmod_dbg_dev + MYSQL_ROOT_PASSWORD: octoteam ci/cd + +stages: + - test + +test: + stage: test + script: + # copy repo into gmod server + - cp -R * /opt/server + # move ci configs + - cd /opt/server + - cp config.ci.ts config.ts + - cp server.ci.cfg garrysmod/cfg/server.cfg + # run octolib scripts + - cd octolib && npm i + - npm run setup + - npm run test server diff --git a/.gitlab/issue_templates/Отчет об ошибке.md b/.gitlab/issue_templates/Отчет об ошибке.md new file mode 100644 index 0000000..01b4d4a --- /dev/null +++ b/.gitlab/issue_templates/Отчет об ошибке.md @@ -0,0 +1,32 @@ +### Что наблюдается? + +Опиши подробно, что именно происходит на момент написания репорта +Пример: Кнопка не нажимается, ничего не происходит... + +### Как это должно работать? + +Как, по твоему мнению, это должно работать, что нужно изменить +Пример: После нажатия кнопки должно открываться меню! + +### Как это повторить? + +Пошагово опиши, как повторить эту ошибку +Пример: +1. Открыть меню на клавишу X +2. Нажать на кнопку ABC +3. ??? + +### Ошибки в консоли + +Если при этом в консоли появлялись какие-то ошибки, скопируй их сюда +Пример: +``` +[dobrograd] addons/octoteam/devteam:42: developers not found (work expected, got nil) + 1. Work - [C]:-1 + 2. unknown - addons/octoteam/devteam:42 +``` + +### Обратная связь + +Если нам понадобится что-то уточнить, как с тобой связаться? +Пример: Discord - username#1234 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..49b694a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "octolib"] + path = octolib + url = ../../gmod/addons/octolib diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b7184e --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Сочные сливы Доброград 13.06.2022 (repack by chelog) сенсация 2023 + +Поскольку за последнее время я устал от разного рода сообщений в ЛС по поводу слитой сборки сервера, я решил залить ее самостоятельно. И так известно, что ресурсам на сайтах и в Telegram-каналах со сливами не стоит доверять, так как легко словить какой-нибудь бэкдур или вирус, поэтому вот вам чистая сборка ровно той же версии + +Пользуйтесь на здоровье и развивайтесь, я буду очень рад, если это кому-то поможет научиться разработке, как на наших успехах, так и ошибках. Этот код никак не зарегистрирован юридически и скорей всего я никого не смогу остановить, но хочу чисто по-человечески попросить вас не использовать целые системы, аддоны или сборку в коммерческих целях + +Честно говоря, я сам в шоке, насколько сильно изменилась структура и статус проекта всего за один год (к примеру, у нас уже нет TS в проекте, октолиба и развертка очень повзрослели и многое другое), но выкладывать самую новую версию по понятным причинам не буду :^) Этот проект был почти самым первым, что я начал делать в Garry's Mod, и понятное дело, здесь есть код из далекого 2017, баги и недоработки, которые на сегодняшный день мы уже исправили (а может и нет), но это было очень крутое путешествие и старт моей карьеры в геймдеве! В любом случае я буду очень благодарен вашим сообщениям о любых находках, в идеале открывайте MR или issue + +— **Dmitry Antonov** aka **[chelog](https://t.me/chelog)** + +*Далее следует оригинальный, старый Readme:* + +--- + +# [-> Справка по разработке <-](https://wiki.octothorp.team/ru/code) + +# Установка + +```sh +# если работаем под Windows, все запускаем внутри WSL + +# скопируем репозиторий к себе на устройство +git clone --recurse-submodules https://gitlab.octo.gg/public-projects/dobrograd-13-06-2022 dobrograd +cd dobrograd + +# скопируем и настроим конфиг сервера +cp config.example.ts config.ts + +# исправим разрешения у папок, иначе контейнеры не будут иметь доступа +# если команда setfacl не найдена, надо установить "acl" через apt +chmod 777 $(find . -type d) +setfacl -dm u::rwx,g::rwx,o::rwx $(find . -type d) +``` + +# Работа +В скриптах проект называется "gmod" для того, чтобы сервер использовал общий кэш загрузки аддонов, таким образом несколько серверов переиспользуют скачанный контент, чтобы не использовать лишнее место на диске + +```sh +# все скрипты выполняются в WSL в корневой папке проекта + +# запустить проект и все связанные с ним сервисы +docker compose -p gmod up + +# прикрепить терминал к консоли сервера, чтобы в него можно было слать команды +docker attach dbg_game + +# остановить контейнеры +docker compose -p gmod stop + +# остановить и удалить контейнеры (для обновления конфигов, данные при этом останутся) +docker compose -p gmod down + +# полный сброс кэша docker если что-то сломалось, перед этим надо прописать скрипт с down +docker system prune -a +``` + +Подключиться к серверу можно по адресу, полученному из `wsl hostname -I`, или же можно поставить `wsl2host`, про который говорится в [настройке окружения](https://wiki.octothorp.team/ru/code) +Для некоторых скриптов есть поддержка прямо в расширении VS Code, но сначала надо хотя бы раз вручную запустить `up`-скрипт через терминал: + +![](https://i.imgur.com/0318kXb.png) ![](https://i.imgur.com/WpVzAEc.png) diff --git a/config.ci.ts b/config.ci.ts new file mode 100644 index 0000000..61ab819 --- /dev/null +++ b/config.ci.ts @@ -0,0 +1,22 @@ +import base from './config.example' + +const config = Object.assign({}, base) + +config.port = 28015 +config.hibernateThink = true + +config.octoservicesURL = 'https://octothorp.team/api' +config.keys.services = '' + +config.db = { + host: 'mariadb', + user: 'root', + pass: 'octoteam ci/cd', + port: 3306, + + main: 'gmod_dbg_dev', + admin: 'gmod_dbg_dev', + shop: 'gmod_dbg_dev', +} + +export default config diff --git a/config.example.ts b/config.example.ts new file mode 100644 index 0000000..1318646 --- /dev/null +++ b/config.example.ts @@ -0,0 +1,58 @@ +import Config from './octolib/core/config' + +export interface DobrogradConfig extends Config { + defaultHostName?: string + adminMention?: string + testEnabled?: boolean + disableGCrash?: boolean + requireLauncher?: boolean +} + +const config: DobrogradConfig = { + dev: true, + serverGroupID: 'dbg_dev', + serverID: 'dbg_dev', + + tickrate: 16, + port: 27015, + language: 'ru', + workshopCollection: 570795184, + gamemode: 'darkrp', + map: 'rp_evocity_dbg_230226', + maxPlayers: 16, + hibernateThink: true, + + octoservicesURL: 'https://octothorp.team/api', + + serverAccount: '', + steamKey: '', + imgurKey: '', + + keys: { + services: '', + cats: '', + logs: '', + test: '', + }, + + webhooks: {}, + + db: { + host: 'mariadb', + user: 'root', + pass: '', + port: 3306, + + main: 'gmod_dobrograd', + admin: 'gmod_dobrograd', + shop: 'gmod_dobrograd', + }, + + disableGCrash: true, + testEnabled: false, + defaultHostName: 'DBG DEV', + adminMention: '', + requireLauncher: false, +} + +export default config diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..99291a3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +networks: + default: + labels: &labels + - project=dobrograd + +volumes: + steam-cache: + game-cache: + +services: + + mariadb: + labels: *labels + container_name: dbg_db + image: mariadb:10.7 + environment: + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: true + MARIADB_DATABASE: gmod_dobrograd + volumes: + - ./docker/runtime-data/mysql:/var/lib/mysql + ports: + - '3306:3306' + healthcheck: + test: ['CMD', 'mysqladmin', 'ping', '--silent'] + interval: 2s + retries: 15 + + gameserver: + labels: *labels + container_name: dbg_game + stdin_open: true + tty: true + build: + context: . + dockerfile: ./docker/Dockerfile.local + labels: *labels + target: setup-gameserver + environment: + STARTUP: ./gameserver-entrypoint.sh + ports: + - '8888:8888' # web interface for code runner + - '27099:27099' # luadev + - '27015:27015/tcp' + - '27015:27015/udp' + volumes: + - steam-cache:/home/container/gameserver/steam_cache + - game-cache:/home/container/gameserver/garrysmod/cache + - ./gameserver-entrypoint.sh:/home/container/gameserver-entrypoint.sh + - ./docker/runtime-data/garrysmod-data:/home/container/gameserver/garrysmod/data + - ./garrysmod/addons:/home/container/gameserver/garrysmod/addons + - ./garrysmod/gamemodes/darkrp:/home/container/gameserver/garrysmod/gamemodes/darkrp + - ./octolib:/home/container/gameserver/octolib + - ./config.example.ts:/home/container/gameserver/config.example.ts + - ./config.ts:/home/container/gameserver/config.ts + - ./server.cfg:/home/container/gameserver/garrysmod/cfg/server.cfg + depends_on: + mariadb: + condition: service_healthy + restart: unless-stopped diff --git a/docker/Dockerfile.local b/docker/Dockerfile.local new file mode 100644 index 0000000..9e26444 --- /dev/null +++ b/docker/Dockerfile.local @@ -0,0 +1,72 @@ +FROM ubuntu:18.04 AS base +LABEL author='Dmitry Antonov' maintainer='chelog@octothorp.team' + +RUN dpkg --add-architecture i386 \ + && apt update \ + && apt upgrade -y \ + && apt install -y \ + ca-certificates \ + curl \ + gcc \ + gdb \ + git \ + iproute2 \ + lib32gcc1 \ + lib32stdc++6 \ + lib32tinfo5 \ + lib32z1 \ + libstdc++6 \ + lua5.1 \ + net-tools \ + unzip \ + zlib1g \ + zlibc \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash - \ + && apt install -y nodejs \ + && groupadd -g 999 container \ + && useradd -m -d /home/container -u 999 -g container container \ + && mkdir -p /home/container/gameserver/steam_cache \ + && mkdir -p /home/container/gameserver/garrysmod/cache \ + && chown -R container:container /home/container/gameserver \ + && chmod 777 -R /home/container/gameserver + +# GAME SETUP +FROM base AS install-gameserver +WORKDIR /home/container +USER container +ENV USER=container HOME=/home/container +ENV LD_LIBRARY_PATH=/home/container/gameserver/bin + +RUN curl -o ./steamcmd_linux.tar.gz https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz \ + && mkdir ./steamcmd \ + && tar -xvzf steamcmd_linux.tar.gz -C ./steamcmd \ + && rm steamcmd_linux.tar.gz \ + && ./steamcmd/steamcmd.sh +quit \ + && mkdir -p ./.steam/sdk32 \ + && cp -v ./steamcmd/linux32/steamclient.so ./.steam/sdk32/steamclient.so \ + && mkdir -p ./.steam/sdk64 \ + && cp -v ./steamcmd/linux64/steamclient.so ./.steam/sdk64/steamclient.so + +RUN ./steamcmd/steamcmd.sh \ + +force_install_dir /home/container/gameserver \ + +login anonymous +app_update 4020 \ + -validate \ + -beta NONE \ + +quit + +FROM install-gameserver AS setup-gameserver +RUN curl -o ./luabin.zip https://cloud.octo.gg/s/oKHm9xXjcLDXwpf/download \ + && mkdir -p ./gameserver/garrysmod/lua/bin \ + && unzip -j ./luabin.zip -d ./gameserver/garrysmod/lua/bin \ + && rm ./luabin.zip + +RUN curl -o ./content.zip -L https://www.dropbox.com/s/al7ruvnda4m8qgf/content.zip?dl=1 \ + && mkdir -p ./content/cstrike \ + && unzip ./content.zip -d ./content/cstrike \ + && rm ./content.zip \ + && echo '"mountcfg" {"cstrike" "/home/contrainer/cstrike"}' > /home/container/gameserver/garrysmod/cfg/mount.cfg + +EXPOSE 27015/tcp 27015/udp + +COPY ./docker/entrypoint.sh /entrypoint.sh +CMD ["/bin/bash", "/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..d48741c --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash +cd /home/container +sleep 1 + +# Make internal Docker IP address available to processes. +export INTERNAL_IP=`ip route get 1 | awk '{print $NF;exit}'` + +# Replace Startup Variables +MODIFIED_STARTUP=`eval echo $(echo ${STARTUP} | sed -e 's/{{/${/g' -e 's/}}/}/g')` +echo ":/home/container$ ${MODIFIED_STARTUP}" + +# Run the Server +eval ${MODIFIED_STARTUP} diff --git a/gameserver-entrypoint.sh b/gameserver-entrypoint.sh new file mode 100644 index 0000000..a7ca6c5 --- /dev/null +++ b/gameserver-entrypoint.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +cd /home/container/gameserver/octolib +npm i +npm run setup +npm run dev diff --git a/garrysmod/addons/.gitignore b/garrysmod/addons/.gitignore new file mode 100644 index 0000000..8f62002 --- /dev/null +++ b/garrysmod/addons/.gitignore @@ -0,0 +1,2 @@ +# ignore local addons +local-* diff --git a/garrysmod/addons/_config/lua/cmenu/categories/other.lua b/garrysmod/addons/_config/lua/cmenu/categories/other.lua new file mode 100644 index 0000000..0370f13 --- /dev/null +++ b/garrysmod/addons/_config/lua/cmenu/categories/other.lua @@ -0,0 +1,3 @@ +octogui.cmenu.registerCategory('other', { + order = math.huge, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/cmenu/categories/rp.lua b/garrysmod/addons/_config/lua/cmenu/categories/rp.lua new file mode 100644 index 0000000..34cff64 --- /dev/null +++ b/garrysmod/addons/_config/lua/cmenu/categories/rp.lua @@ -0,0 +1 @@ +octogui.cmenu.registerCategory('rp') \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/cmenu/items/other.lua b/garrysmod/addons/_config/lua/cmenu/items/other.lua new file mode 100644 index 0000000..28b7b6f --- /dev/null +++ b/garrysmod/addons/_config/lua/cmenu/items/other.lua @@ -0,0 +1,46 @@ +octogui.cmenu.registerItem('other', 'idea', { + text = L.idea, + icon = octolib.icons.silk16('lightbulb'), + say = '/dbg_getidea', +}) + +octogui.cmenu.registerItem('other', 'nrp', { + text = L.nonrp_actions, + icon = octolib.icons.silk16('world'), + options = { + { + text = L.applications_and_ticket, + icon = octolib.icons.silk16('page_error'), + url = 'https://forum.octothorp.team/c/5', + }, { + text = L.send_pm, + icon = octolib.icons.silk16('email'), + build = function(sm) + local ply = LocalPlayer() + local _players = player.GetAll() + table.sort(_players, function(a, b) return a:GetName() < b:GetName() end) + + for _, v in ipairs(_players) do + if v ~= ply then + local name = v:Name() + sm:AddOption(v:SteamName() .. ' (' .. name .. ')', octolib.fStringRequest(L.send_pm_hint .. v:SteamName() .. ' (' .. name .. ')', L.send_text, '', function(s) + octochat.say('/pm', ('"%s"'):format(name), s) + end, nil, L.ok, L.cancel)):SetColor(v:getJobTable().color) + end + end + end, + }, { + text = L.links, + icon = octolib.icons.silk16('link'), + options = { + {text = L.start, url = 'https://wiki.octothorp.team/dobrograd/start' }, + {text = L.site, url = 'https://octothorp.team' }, + {text = L.wiki, url = 'https://wiki.octothorp.team' }, + {text = L.forum, url = 'http://forum.octothorp.team' }, + {text = L.group_vk, url = 'https://vk.com/octoteam' }, + {text = L.group_steam, url = 'https://steamcommunity.com/groups/octothorp-team' }, + {text = L.rules, url = 'https://wiki.octothorp.team/dobrograd/rules' }, + }, + } + }, +}) diff --git a/garrysmod/addons/_config/lua/cmenu/items/rp.lua b/garrysmod/addons/_config/lua/cmenu/items/rp.lua new file mode 100644 index 0000000..c6e8b5c --- /dev/null +++ b/garrysmod/addons/_config/lua/cmenu/items/rp.lua @@ -0,0 +1,11 @@ +octogui.cmenu.registerItem('rp', 'chance', { + text = L.chance, + icon = octolib.icons.silk16('dice'), + options = { + {text = L.pull_card, icon = octolib.icons.silk16('bullet_green'), say = '/card'}, + {text = L.roll_the_dice, icon = octolib.icons.silk16('bullet_yellow'), say = '/dice'}, + {text = L.get_chance, icon = octolib.icons.silk16('bullet_blue'), say = '/roll'}, + {text = L.rock_paper_scissors, icon = octolib.icons.silk16('bullet_red'), say = '/rockpaperscissors'}, + {text = 'Подбросить монетку', icon = octolib.icons.silk16('bullet_purple'), say = '/coin'}, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/apg.lua b/garrysmod/addons/_config/lua/config/apg.lua new file mode 100644 index 0000000..da5e9e0 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/apg.lua @@ -0,0 +1,154 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + + ==================================================================================== + /!\ READ ME /!\ /!\ READ ME /!\ /!\ READ ME /!\ + ==================================================================================== + + This file is the default config file. + If you want to configure APG to fit your server needs, you can either modify this file + or edit the config ingame ( using the chat command : !apg ). + + You can now also use "apg" in console! + +]]-------------------------------------------- +APG.cfg = APG.cfg or {} +APG.modules = APG.modules or {} + +--[[---------- + Your very own custom function + This function will run whenever lag is detected on your server! +]]------------ +function APG.customFunc( notify ) + -- Do something +end + +--[[---------- + Avalaible premade functions - THIS IS INFORMATIVE PURPOSE ONLY ! +]]------------ +if CLIENT then + APG_lagFuncs = { -- THIS IS INFORMATIVE PURPOSE ONLY ! + "cleanup_all", -- Cleanup every props/ents protected by APG (not worldprops nor vehicles) + "cleanup_unfrozen", -- Cleanup only unfrozen stuff + "ghost_unfrozen", -- Ghost unfrozen stuff + "freeze_unfrozen", -- Freeze unfrozen stuff + "smart_cleanup", -- Cleanup unfrozen fading doors, freeze unfrozens, remove large stacks + "custom_function" -- Your custom function (see APG.customFunc) + } -- THIS IS INFORMATIVE PURPOSE ONLY ! +end + +--[[------------------------------------------ + DEFAULT SETTINGS -- You CAN edit this part, but you SHOULDN'T +]]-------------------------------------------- + +local defaultSettings = {} +defaultSettings.modules = { -- Set to true of false to enable/disable module + ["ghosting"] = true, + ["stack_detection"] = true, + ["lag_detection"] = true, + ["misc"] = true, + ["misc2"] = true, +} + +defaultSettings.cfg = { + --[[---------- + Ghosting module + ]]------------ + ghost_color = { value = Color(34, 34, 34, 220) ,desc = "Color set on ghosted props" }, + + bad_ents = { + value = { + ["prop_physics"] = true, + ["wire_"] = false, + ["keypad"] = false, + }, + desc = "Entities to ghost/control/secure (true if exact name, false if it is a pattern"}, + + alwaysFrozen = { value = false , desc = "Set to true to auto freeze props on physgun drop (aka APA_FreezeOnDrop)" }, + + --[[---------- + Stack detection module + ]]------------ + stackMax = { value = 20, desc = "Max amount of entities stacked in a small area"}, + stackArea = { value = 15, desc = "Sphere radius for stack detection (gmod units)"}, + + --[[---------- + Lag detection module + ]]------------ + lagTrigger = { value = 75, desc = "[Default: 75%] Differential threshold between current lag and average lag."}, + lagsCount = { value = 8, desc = "Number of consectuives laggy frames in order to run a cleanup."}, + bigLag = { value = 2, desc = "Maximum time (seconds) between 2 frames to trigger a cleanup"}, + lagFunc = { value = "ghost_unfrozen", desc = "Function ran on lag detected, see APG_lagFuncs." }, + lagFuncTime = { value = 20, desc = "Time (seconds) between 2 anti lag function (avoid spam)"}, + lagFuncNotify = { value = 2, desc = "Notify : 0 - Disabled, 1 - Everyone, 2 - Admins only"}, -- Available soon + + --[[---------- + MISC + ]]------------ + --[[ Vehicles ]]-- + vehDamage = { value = false, desc = "True to disable vehicles damages, false to enable." }, + vehNoCollide = { value = false, desc = "True to disable collisions between vehicles and players"}, + vehIncludeWAC = { value = true, desc = "Check for WAC vehicles."}, + + --[[ Props related ]]-- + blockPhysgunReload = { value = false, desc = "Block players from using physgun reload"}, + autoFreeze = { value = false, desc = "Freeze every unfrozen prop each X seconds" }, + autoFreezeTime = { value = 120, desc = "Auto freeze timer (seconds)"}, + + thFadingDoors = { value = true, desc = "Inject custom hooks into Fading Doors" }, + FadingDoorGhosting = { value = true, desc = "Activate fading door ghosting" }, + frzr9k = { value = false, desc = "Activate FRZR9K (Sleepy Physics)" }, + AllowPK = { value = false, desc = "Allow prop killing (Won't work well with ghosting)" } +} + +--[[------------------------------------------ + LOADING SAVED SETTINGS -- DO NOT EDIT THIS PART +]]-------------------------------------------- +if SERVER and file.Exists( "apg/settings.txt", "DATA" ) then + table.Merge( APG, defaultSettings ) -- Load the default settings first! + + local settings = file.Read( "apg/settings.txt", "DATA" ) + settings = util.JSONToTable( settings ) + + if not settings.modules or not settings.cfg then + ErrorNoHalt("Your custom settings have not been loaded because you have a misconfigured settings file! The default settings were used instead!") + return + end + + local removedSetting = {} + + for k, v in next, settings.modules do + if defaultSettings.modules[k] == nil then + settings.modules[k] = nil + table.insert(removedSetting, k) + end + end + + for k, v in next, settings.cfg do + if defaultSettings.cfg[k] == nil then + settings.cfg[k] = nil + table.insert(removedSetting, k) + end + end + + if next(removedSetting) then + print("[APG] Settings File Updated. (Conflicts Resolved)") + print("[APG] The Following Settings Have Been Removed: ") + for _,v in next, removedSetting do + print("\t> \""..tostring(v).."\" has been removed.") + end + + removedSetting = nil + file.Write("apg/settings.txt", util.TableToJSON(settings)) + end + + table.Merge( APG, settings ) +else + table.Merge( APG, defaultSettings ) +end diff --git a/garrysmod/addons/_config/lua/config/car-dealer.lua b/garrysmod/addons/_config/lua/config/car-dealer.lua new file mode 100644 index 0000000..da35074 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/car-dealer.lua @@ -0,0 +1,202 @@ +carDealer.sellPrice = 0.75 +carDealer.defaultDeposit = 25000 + +carDealer.plateSymbols = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' +carDealer.plateLength = 7 +carDealer.maxCars = { + rp_riverden_dbg_220313 = 12, + default = 10, +} + +carDealer.despawnDistance = 300 + +carDealer.notify = octolib.notify.send + +function carDealer.formatMoney(amount) + return DarkRP.formatMoney(amount) +end + +function carDealer.addMoney(ply, amount) + ply:BankAdd(amount) +end + +function carDealer.hasMoney(ply, amount) + return ply:BankHas(amount) +end + +carDealer.civilSpawns = { + rp_evocity_dbg_220222 = { + { Vector(-4656, -7535, 225), Angle(0,-90,0) }, -- douglas + { Vector(-4656, -7210, 225), Angle(0,-90,0) }, + { Vector(-4656, -6885, 225), Angle(0,-90,0) }, + { Vector(-4656, -6560, 225), Angle(0,-90,0) }, + { Vector(-4656, -6235, 225), Angle(0,-90,0) }, + { Vector(-6649, -3711, 105), Angle(0,90,0) }, + { Vector(-6848, -3712, 105), Angle(0,90,0) }, + { Vector(-7038, -3709, 105), Angle(0,90,0) }, + { Vector(-7229, -3704, 105), Angle(0,90,0) }, + { Vector(-10196, -12573, 125), Angle(0,90,0) }, -- roosevelt + { Vector(-10196, -13121, 125), Angle(0,90,0) }, + { Vector(-8614, 9807, 125), Angle(0,180,0) }, -- hospital + { Vector(-8614, 9659, 125), Angle(0,180,0) }, + { Vector(-8617, 9515, 125), Angle(0,180,0) }, + { Vector(-8620, 9368, 125), Angle(0,180,0) }, + { Vector(-8623, 9224, 125), Angle(0,180,0) }, + { Vector(-8629, 8934, 125), Angle(0,180,0) }, + }, + rp_eastcoast_v4c = { + { Vector(-3610, 2935, 50), Angle(0,-90,0) }, -- spawn + { Vector(-3610, 2710, 50), Angle(0,-90,0) }, + { Vector(-3610, 2491, 50), Angle(0,-90,0) }, + { Vector(3410, -1615, 15), Angle(0,0,0) }, -- church + { Vector(3410, -1742, 15), Angle(0,0,0) }, + { Vector(3410, -1874, 15), Angle(0,0,0) }, + { Vector(3410, -2004, 15), Angle(0,0,0) }, + { Vector(3410, -2134, 15), Angle(0,0,0) }, + { Vector(3410, -2394, 15), Angle(0,0,0) }, + }, + rp_truenorth_v1a = { + { Vector(10800, 2825, -225), Angle(0, 180, 0) }, + { Vector(10800, 2625, -225), Angle(0, 180, 0) }, + { Vector(10800, 2425, -225), Angle(0, 180, 0) }, + { Vector(10800, 2225, -225), Angle(0, 180, 0) }, + { Vector(10800, 2025, -225), Angle(0, 180, 0) }, + { Vector(12000, 2825, -225), Angle(0, 180, 0) }, + { Vector(12000, 2625, -225), Angle(0, 180, 0) }, + { Vector(12000, 2425, -225), Angle(0, 180, 0) }, + { Vector(12000, 2225, -225), Angle(0, 180, 0) }, + { Vector(12000, 2025, -225), Angle(0, 180, 0) }, + }, + rp_riverden_dbg_220313 = { + { Vector(-13222.8, 12256.7, 32.0593), Angle(0, 0, 0) }, + { Vector(-13223.4, 12641.8, 33.3913), Angle(0, 0, 0) }, + { Vector(-11782.2, 12956.9, 29.947), Angle(-0, 90, 0) }, + { Vector(-8721.59, 14412.8, 162.303), Angle(-0, 90, 0) }, + { Vector(-9608.99, 14898.1, 162.87), Angle(-0, -90, 0) }, + { Vector(-10335.6, 14413.3, 292), Angle(-0, 90, 0) }, + { Vector(-8905.67, 14893.9, 290.42), Angle(-0, -90, 0) }, + { Vector(-10077, 8046.36, -227.332), Angle(-0, -90, 0) }, + { Vector(-11037.4, 4706.57, -232.694), Angle(0, 0, 0) }, + { Vector(-11034.7, 5272.99, -231.204), Angle(0, 0, 0) }, + { Vector(-9210.54, -13671, -231.101), Angle(-0, 90, 0) }, + { Vector(12482.3, -8619.35, 804.961), Angle(-0, 90, 0) }, + { Vector(-6446.69, 13275.5, 24.4091), Angle(0, 0, 0) }, + }, +} + +carDealer.policeSpawns = { + rp_evocity_dbg_220222 = { + { Vector(-8050, -8715, 125), Angle(0,180,0) }, -- pd + { Vector(-8050, -8880, 125), Angle(0,180,0) }, + { Vector(-8050, -9045, 125), Angle(0,180,0) }, + { Vector(-8050, -9210, 125), Angle(0,180,0) }, + { Vector(-8050, -9375, 125), Angle(0,180,0) }, + { Vector(-8050, -9540, 125), Angle(0,180,0) }, + { Vector(-8050, -9705, 125), Angle(0,180,0) }, + }, + rp_eastcoast_v4c = { + { Vector(1590, 1215, 20), Angle(0,180,0) }, -- pd + { Vector(1590, 1338, 20), Angle(0,180,0) }, + }, + rp_truenorth_v1a = { + { Vector(2273, 3900, 40), Angle(0, 90, 0) }, + { Vector(2467, 3900, 40), Angle(0, 90, 0) }, + { Vector(2661, 3900, 40), Angle(0, 90, 0) }, + { Vector(2853, 3900, 40), Angle(0, 90, 0) }, + { Vector(3044, 3900, 40), Angle(0, 90, 0) }, + { Vector(3234, 3900, 40), Angle(0, 90, 0) }, + }, + rp_riverden_dbg_220313 = { + { Vector(-8318.34, 8205.45, -214.671), Angle(0, 90, 0) }, + { Vector(-8126.12, 8205.15, -215.018), Angle(0, 90, 0) }, + { Vector(-7940.62, 8203.81, -215.73), Angle(0, 90, 0) }, + { Vector(-8511.8, 8205.09, -215.258), Angle(0, 90, 0) }, + { Vector(-8705.17, 8203.01, -216.215), Angle(0, 90, 0) }, + }, +} + +carDealer.checks = { + civil = function(ply) + local job = ply:getJobTable() + if (ply:isCP() and not ply:isMayor()) or job.noCivilCars then return false, 'Доступно только гражданским' end + if job.hobo then return false, L.hobo_car end + end, + dobro = function(ply) + if not ply:IsSuperAdmin() and not ply:GetNetVar('os_dobro') then + return false, 'Этот автомобиль доступен только Добродеям' + end + end, + superadmin = function(ply) + if not ply:IsSuperAdmin() then return false, 'Доступно только старшей администрации' end + end, + no = function() return false end, +} + +carDealer.tags = { + dobro = {octolib.icons.silk16('heart'), 'Только для Добродеев', true}, + halloween = {octolib.icons.silk16('emotion_pumpkin'), 'Эксклюзивное авто с Хэллоуина', true}, + truck = {octolib.icons.silk16('lorry'), 'Подойдет для перевозки грузов', true}, + timed = {octolib.icons.silk16('time'), 'Ограниченное предложение'}, + new = {octolib.icons.silk16('fire'), 'Новая модель'}, +} + +carDealer.limits = { + police = carDealer.limitedSpawn(game.GetMap():find('riverden') and 6 or 4, 'police', 'В городе уже много полицейских автомобилей, найди напарника или попробуй чуть позже'), + medic = carDealer.limitedSpawn(2, 'medic', 'В городе уже много медицинских автомобилей, найди напарника или попробуй чуть позже'), + fire = carDealer.limitedSpawn(2, 'fire', 'В городе уже много пожарных автомобилей, найди напарника или попробуй чуть позже'), + coroner = carDealer.limitedSpawn(2, 'coroner', 'В городе уже много автомобилей коронеров, найди напарника или попробуй чуть позже'), + mech = carDealer.limitedSpawn(1, 'mech', 'В городе уже есть автомобиль механика, найди напарника или попробуй чуть позже'), +} + +carDealer.policeRadioStations = { + { + id = '-ENq6uQd', + title = 'Police Channel 1', + }, { + id = '16z4iZFY', + title = 'Police Channel 2', + }, { + id = '1BUEnRoy', + title = 'Police Channel 3', + }, { + id = 'dRTMFCjv', + title = 'Police & Fire Channel 4', + }, { + id = 'QDBlRQl4', + title = 'Police & Fire Channel 5', + }, { + id = 'pLwbQSYC', + title = 'Police & Fire Channel 6', + }, { + id = 'N4UujOAb', + title = 'Police & Fire Channel 7', + }, { + id = 'eBZbJcRO', + title = 'Police, Sheriff, Fire & EMS Channel 8', + }, +} + +carDealer.emsRadioStations = { + { + id = 'N4UujOAb', + title = 'Police & Fire Channel 1', + }, { + id = 'pLwbQSYC', + title = 'Police & Fire Channel 2', + }, { + id = 'dRTMFCjv', + title = 'Police & Fire Channel 3', + }, { + id = 'eBZbJcRO', + title = 'Police, Sheriff, Fire & EMS Channel 4', + }, +} + +octolib.shared('cars/pickups') +octolib.shared('cars/4doors') +octolib.shared('cars/2doors') +octolib.shared('cars/large') +octolib.shared('cars/sports') + +octolib.shared('cars/police') +octolib.shared('cars/mech') diff --git a/garrysmod/addons/_config/lua/config/cars/2doors.lua b/garrysmod/addons/_config/lua/config/cars/2doors.lua new file mode 100644 index 0000000..b7ecf3d --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/2doors.lua @@ -0,0 +1,192 @@ +carDealer.addCategory('2doors', { + name = 'Двухдверные', + icon = 'octoteam/icons-16/user.png', + queue = true, + canUse = carDealer.checks.civil, + spawns = carDealer.civilSpawns, +}) + +-- carDealer.addVeh('faction2', { +-- name = 'Faction', +-- simfphysID = 'sim_fphys_gta4_faction', +-- price = 0, +-- bodygroups = { +-- [1] = { +-- name = 'Крыша', +-- variants = { +-- { 'Заводская', 1000 }, +-- { 'Стекло', 35000 }, +-- }, +-- }, +-- [2] = { +-- name = 'Капот', +-- variants = { +-- { 'Заводской', 1000 }, +-- { 'С выступом', 50000 }, +-- }, +-- }, +-- }, +-- }) + +carDealer.addVeh('fortune2', { + name = 'Fortune', + simfphysID = 'sim_fphys_gta4_fortune', + price = 950000, +}) + +carDealer.addVeh('vigero2', { + name = 'Vigero', + simfphysID = 'sim_fphys_gta4_vigero', + price = 1250000, +}) + +carDealer.addVeh('rhapsody2', { + name = 'Rhapsody', + simfphysID = 'sim_fphys_tlad_rhapsody', + price = 2150000, + tags = { carDealer.tags.new }, +}) + +carDealer.addVeh('sabre2', { + name = 'Sabre', + simfphysID = 'sim_fphys_gta4_sabre', + price = 2350000, +}) + +carDealer.addVeh('manana2', { + name = 'Manana', + simfphysID = 'sim_fphys_gta4_manana', + price = 2500000, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Закрытая', 190000 }, + }, + }, + }, +}) + +carDealer.addVeh('ruiner2', { + name = 'Ruiner', + simfphysID = 'sim_fphys_gta4_ruiner', + price = 2600000, + tags = { carDealer.tags.new }, + default = { + bg = { [2] = 1, [3] = 1 }, + }, + bodygroups = { + [1] = { + name = 'Капот', + variants = { + { 'Заводской', 1000 }, + { 'С вентиляцией', 75000 }, + }, + }, + [2] = { + name = 'Крыша', + variants = { + { 'Панорамная', 175000 }, + { 'Заводская', 1000 }, + }, + }, + [3] = { + name = 'Багажник', + variants = { + { 'Открытый', 80000 }, + { 'Заводской', 1000 }, + }, + }, + }, +}) + +carDealer.addVeh('sabregt2', { + name = 'Sabre GT', + simfphysID = 'sim_fphys_gta4_sabregt', + price = 2850000, + tags = { carDealer.tags.new }, + bodygroups = { + [1] = { + name = 'Капот', + variants = { + { 'Заводской', 1000 }, + { 'С воздухозаборником', 125000 }, + { 'С выступом', 45000 }, + }, + }, + }, +}) + +carDealer.addVeh('futo2', { + name = 'Futo', + simfphysID = 'sim_fphys_gta4_futo', + price = 3100000, + bodygroups = { + [1] = { + name = 'Бампер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 75000 }, + }, + }, + [2] = { + name = 'Тюнинг', + variants = { + { 'Заводской', 1000 }, + { 'Рама, пороги, труба и сидение', 235000 }, + }, + }, + [3] = { + name = 'Спойлер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 95000 }, + }, + }, + [4] = { + name = 'Капот', + variants = { + { 'Заводской', 1000 }, + { 'Карбон', 160000 }, + }, + }, + }, +}) + +-- +-- EVENT +-- + +carDealer.addVeh('halloween_sedan', { + name = 'Peyote', + simfphysID = 'sim_fphys_gta4_peyote', + price = 0, + tags = { carDealer.tags.halloween }, + canSee = carDealer.checks.no, + canUse = carDealer.checks.civil, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Закрытая', 320000 }, + }, + }, + [2] = { + name = 'Заднее крепление', + variants = { + { 'Ничего', 1000 }, + { 'Запаска', 185000 }, + }, + }, + [3] = { + name = 'Сидения', + variants = { + { 'Заводские', 1000 }, + { 'Зебра', 210000 }, + { 'Черные', 75000 }, + }, + }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/cars/4doors.lua b/garrysmod/addons/_config/lua/config/cars/4doors.lua new file mode 100644 index 0000000..c33044e --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/4doors.lua @@ -0,0 +1,198 @@ +carDealer.addCategory('4doors', { + name = 'Четырехдверные', + icon = 'octoteam/icons-16/user.png', + queue = true, + canUse = carDealer.checks.civil, + spawns = carDealer.civilSpawns, +}) + +carDealer.addVeh('premier2', { + name = 'Premier', + simfphysID = 'sim_fphys_gta4_premier', + price = 450000, +}) + +carDealer.addVeh('esperanto2', { + name = 'Esperanto', + simfphysID = 'sim_fphys_gta4_esperanto', + price = 600000, +}) + +carDealer.addVeh('dilettante2', { + name = 'Dilettante', + simfphysID = 'sim_fphys_gta4_dilettante', + price = 700000, +}) + +carDealer.addVeh('merit2', { + name = 'Merit', + simfphysID = 'sim_fphys_gta4_merit', + price = 850000, +}) + +carDealer.addVeh('df2', { + name = 'DF8-90', + simfphysID = 'sim_fphys_gta4_df8', + price = 1350000, + bodygroups = { + [1] = { + name = 'Спойлер', + variants = { + { 'Заводской', 1000 }, + { 'Высокий', 180000 }, + { 'Низкий', 120000 }, + }, + }, + }, +}) + +carDealer.addVeh('emperor2', { + name = 'Emperor', + simfphysID = 'sim_fphys_gta4_emperor', + price = 1450000, +}) + +carDealer.addVeh('schafter2', { + name = 'Schafter', + simfphysID = 'sim_fphys_gta4_schafter', + price = 2000000, + default = { + bg = { [1] = 1 }, + }, + bodygroups = { + [1] = { + name = 'Решетка', + variants = { + { 'Частая', 95000 }, + { 'Заводская', 1000 }, + }, + }, + [2] = { + name = 'Передний бампер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 120000 }, + }, + }, + [3] = { + name = 'Пороги', + variants = { + { 'Заводские', 1000 }, + { 'Тюнер', 90000 }, + }, + }, + [4] = { + name = 'Задний бампер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 135000 }, + }, + }, + }, +}) + +carDealer.addVeh('vincent2', { + name = 'Vincent', + simfphysID = 'sim_fphys_gta4_vincent', + price = 2200000, + tags = { carDealer.tags.new }, + bodygroups = { + [1] = { + name = 'Спойлер', + variants = { + { 'Заводской', 1000 }, + { 'Спортивный', 115000 }, + }, + }, + }, +}) + +carDealer.addVeh('admiral2', { + name = 'Admiral', + simfphysID = 'sim_fphys_gta4_admiral', + price = 3200000, +}) + +carDealer.addVeh('cognoscenti2', { + name = 'Cognoscenti', + simfphysID = 'sim_fphys_gta4_cognoscenti', + price = 5250000, +}) + +carDealer.addVeh('superdiamond2', { + name = 'Super Diamond', + simfphysID = 'sim_fphys_tbogt_superd', + price = 5800000, + tags = { carDealer.tags.new }, +}) + +carDealer.addVeh('superdiamond-coupe2', { + name = 'Super Diamond Coupe', + simfphysID = 'sim_fphys_tbogt_superd2', + price = 6000000, + tags = { carDealer.tags.new }, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Мягкая', 450000 }, + }, + }, + }, +}) + +-- +-- DOBRO +-- + +carDealer.addVeh('hakumai2', { + name = 'Hakumai', + simfphysID = 'sim_fphys_gta4_hakumai', + price = 2250000, + bodygroups = { + [1] = { + name = 'Пороги', + variants = { + { 'Заводские', 1000 }, + { 'Тюнер', 140000 }, + }, + }, + [2] = { + name = 'Спойлер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 180000 }, + }, + }, + [3] = { + name = 'Передний бампер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 145000 }, + }, + }, + [4] = { + name = 'Огни', + variants = { + { 'Заводские', 1000 }, + { 'Противотуманки', 95000 }, + }, + }, + }, + canUse = carDealer.checks.dobro, + tags = { carDealer.tags.dobro }, +}) + +-- +-- EVENT +-- + +carDealer.addVeh('halloween_pickup', { + name = 'Regina', + simfphysID = 'sim_fphys_tlad_regina', + price = 0, + tags = { carDealer.tags.halloween }, + canSee = carDealer.checks.no, + canUse = carDealer.checks.civil, +}) diff --git a/garrysmod/addons/_config/lua/config/cars/event.lua b/garrysmod/addons/_config/lua/config/cars/event.lua new file mode 100644 index 0000000..a4e5e63 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/event.lua @@ -0,0 +1,41 @@ +carDealer.addVeh('halloween_sedan', { + name = 'Peyote', + simfphysID = 'sim_fphys_gta4_peyote', + price = 0, + tags = { carDealer.tags.halloween }, + canSee = carDealer.checks.no, + canUse = carDealer.checks.civil, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Закрытая', 320000 }, + }, + }, + [2] = { + name = 'Заднее крепление', + variants = { + { 'Ничего', 1000 }, + { 'Запаска', 185000 }, + }, + }, + [3] = { + name = 'Сидения', + variants = { + { 'Заводские', 1000 }, + { 'Зебра', 210000 }, + { 'Черные', 75000 }, + }, + }, + }, +}) + +carDealer.addVeh('halloween_pickup', { + name = 'Regina', + simfphysID = 'sim_fphys_tlad_regina', + price = 0, + tags = { carDealer.tags.halloween }, + canSee = carDealer.checks.no, + canUse = carDealer.checks.civil, +}) diff --git a/garrysmod/addons/_config/lua/config/cars/large.lua b/garrysmod/addons/_config/lua/config/cars/large.lua new file mode 100644 index 0000000..6a35ae2 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/large.lua @@ -0,0 +1,198 @@ +carDealer.addCategory('large', { + name = 'Большие', + icon = 'octoteam/icons-16/user.png', + queue = true, + canUse = carDealer.checks.civil, + spawns = carDealer.civilSpawns, +}) + +carDealer.addVeh('ingot2', { + name = 'Ingot', + simfphysID = 'sim_fphys_gta4_ingot', + price = 1000000, + tags = { carDealer.tags.new }, + bodygroups = { + [1] = { + name = 'Передний бампер', + variants = { + { 'Заводской', 1000 }, + { 'С решеткой', 45000 }, + }, + }, + [2] = { + name = 'Пороги', + variants = { + { 'Заводские', 1000 }, + { 'Увеличенные', 35000 }, + }, + }, + [3] = { + name = 'Решетка радиатора', + variants = { + { 'Заводская', 1000 }, + { 'Без эмблемы', 25000 }, + }, + }, + [4] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'С рейлингами', 40000 }, + }, + }, + }, +}) + +carDealer.addVeh('rebla2', { + name = 'Rebla', + simfphysID = 'sim_fphys_gta4_rebla', + price = 1750000, + bodygroups = { + [1] = { + name = 'Пороги', + variants = { + { 'Заводские', 1000 }, + { 'Тюнер', 85000 }, + }, + }, + [2] = { + name = 'Передний бампер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 115000 }, + }, + }, + [3] = { + name = 'Задний бампер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 130000 }, + }, + }, + [4] = { + name = 'Спойлер', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 75000 }, + }, + }, + [5] = { + name = 'Выхлоп', + variants = { + { 'Заводской', 1000 }, + { 'Тюнер', 120000 }, + }, + }, + }, +}) + +carDealer.addVeh('habanero2', { + name = 'Habanero', + simfphysID = 'sim_fphys_gta4_habanero', + price = 2600000, +}) + +carDealer.addVeh('pony2', { + name = 'Pony', + simfphysID = 'sim_fphys_gta4_pony', + price = 3500000, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Рейлинги', 120000 }, + }, + }, + }, +}) + +carDealer.addVeh('moonbeam2', { + name = 'Moonbeam', + simfphysID = 'sim_fphys_gta4_moonbeam', + price = 3600000, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Рейлинги', 95000 }, + { 'Багажник', 650000 }, + }, + }, + [2] = { + name = 'Пороги', + variants = { + { 'Заводские', 1000 }, + { 'Спортивные', 135000 }, + { 'Металлические', 110000 }, + { 'Со вставками', 140000 }, + }, + }, + [3] = { + name = 'Окна', + variants = { + { 'Заводские', 1000 }, + { 'Закрытые', 60000 }, + }, + }, + [4] = { + name = 'Аксессуары', + variants = { + { 'Заводские', 1000 }, + { 'Запаска + лестница', 120000 }, + }, + }, + [5] = { + name = 'Решетка', + variants = { + { 'Заводская', 1000 }, + { 'Во всю ширину', 200000 }, + }, + }, + }, +}) + +carDealer.addVeh('huntley2', { + name = 'Huntley', + simfphysID = 'sim_fphys_gta4_huntley', + price = 4000000, + bodygroups = { + [1] = { + name = 'Тюнинг', + variants = { + { 'Заводской', 1000 }, + { 'Выхлоп + решетка', 250000 }, + }, + }, + }, +}) + +carDealer.addVeh('mule2', { + name = 'Mule', + simfphysID = 'sim_fphys_gta4_mule', + price = 4400000, + customFOV = 38, +}) + +carDealer.addVeh('patriot2', { + name = 'Patriot', + simfphysID = 'sim_fphys_gta4_patriot', + price = 4850000, + bodygroups = { + [1] = { + name = 'Тюнинг', + variants = { + { 'Заводской', 1000 }, + { 'Выхлоп + огни', 320000 }, + }, + }, + [2] = { + name = 'Бампер', + variants = { + { 'Заводской', 1000 }, + { 'Кунгурятник', 180000 }, + }, + }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/cars/mech.lua b/garrysmod/addons/_config/lua/config/cars/mech.lua new file mode 100644 index 0000000..b853957 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/mech.lua @@ -0,0 +1,16 @@ +carDealer.addCategory('mech', { + name = 'Механики', + icon = octolib.icons.silk16('wrench'), + canUse = function(ply) return ply:Team() == TEAM_MECH, 'Доступно только механикам' end, + spawns = carDealer.civilSpawns, + spawnCheck = carDealer.limits.mech, + limitGroup = 'mech', +}) + +carDealer.addVeh('towtruck', { + category = 'mech', + name = 'Tow Truck', + simfphysID = 'sim_fphys_gta4_bobcat_towtruck', + price = 30000, + deposit = true, +}) diff --git a/garrysmod/addons/_config/lua/config/cars/pickups.lua b/garrysmod/addons/_config/lua/config/cars/pickups.lua new file mode 100644 index 0000000..186369d --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/pickups.lua @@ -0,0 +1,86 @@ +carDealer.addCategory('pickups', { + name = 'Пикапы', + icon = 'octoteam/icons-16/user.png', + queue = true, + canUse = carDealer.checks.civil, + spawns = carDealer.civilSpawns, +}) + +carDealer.addVeh('bobcat2', { + name = 'Bobcat', + simfphysID = 'sim_fphys_gta4_bobcat', + price = 550000, + bodygroups = { + [1] = { + name = 'Стойка', + variants = { + { 'Заводская', 1000 }, + [5] = { 'Укрепленная', 145000 }, + }, + }, + [2] = { + name = 'Багажник', + variants = { + { 'Заводской', 1000 }, + { 'Маленький', 90000 }, + { 'Полный', 265000 }, + }, + }, + [3] = { + name = 'Бампер', + variants = { + { 'Заводской', 1000 }, + { 'Кенгурятник', 80000 }, + }, + }, + }, +}) + +carDealer.addVeh('rancher2', { + name = 'Rancher', + simfphysID = 'sim_fphys_gta4_rancher', + price = 1200000, + bodygroups = { + [1] = { + name = 'Кузов', + variants = { + { 'Заводской', 1000 }, + { 'Короб', 550000 }, + { 'Стойка', 180000 }, + }, + }, + [2] = { + name = 'Бампер', + variants = { + { 'Заводской', 1000 }, + { 'Кенгурятник', 90000 }, + { 'Фонари', 95000 }, + }, + }, + }, +}) + +carDealer.addVeh('contender2', { + name = 'Contender', + simfphysID = 'sim_fphys_gta4_e109', + price = 3500000, + bodygroups = { + [1] = { + name = 'Кузов', + variants = { + { 'Заводской', 1000 }, + { 'Козырек + покрытие', 325000 }, + { 'Жеское покрытие', 280000 }, + { 'Козырек', 65000 }, + { 'Стойка + покрытие', 450000 }, + }, + }, + [2] = { + name = 'Бампер', + variants = { + { 'Заводской', 1000 }, + { 'Кенгурятник', 145000 }, + }, + }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/cars/police.lua b/garrysmod/addons/_config/lua/config/cars/police.lua new file mode 100644 index 0000000..e39abb5 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/police.lua @@ -0,0 +1,71 @@ +carDealer.addCategory('police', { + name = 'Полицейские', + ems = true, + icon = 'octoteam/icons-16/user_policeman_white.png', + canUse = function(ply) return ply:isCP() and ply:Team() ~= TEAM_FBI and ply:Team() ~= TEAM_WCSO and ply:Team() ~= TEAM_MAYOR, 'Доступно только полиции' end, + spawns = carDealer.policeSpawns, + spawnCheck = carDealer.limits.police, + limitGroup = 'police', +}) + +carDealer.addVeh('police-vapid', { + category = 'police', + name = 'Police Vapid', + simfphysID = 'sim_fphys_gta4_police', + price = 30000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, +}) + +carDealer.addVeh('police-merit', { + category = 'police', + name = 'Police Merit', + simfphysID = 'sim_fphys_gta4_police2', + price = 40000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, +}) + +-- carDealer.addVeh('police-patriot', { +-- category = 'police', +-- name = 'Police Patriot', +-- simfphysID = 'sim_fphys_gta4_polpatriot', +-- price = 50000, +-- deposit = true, +-- police = true, +-- canUse = carDealer.checks.dobro, +-- tags = { carDealer.tags.dobro }, +-- radioWhitelist = carDealer.policeRadioStations, +-- }) + +carDealer.addVeh('police-buffalo', { + category = 'police', + name = 'Police Buffalo', + simfphysID = 'sim_fphys_tbogt_police3', + price = 75000, + deposit = true, + police = true, + canUse = function(ply) + if ply:Team() == TEAM_DPD then return true end + if ply:isCP() and ply:Team() ~= TEAM_FBI and ply:Team() ~= TEAM_MAYOR then return carDealer.checks.dobro(ply) end + return false + end, + tags = { carDealer.tags.dobro }, + radioWhitelist = carDealer.policeRadioStations, +}) + +-- carDealer.addVeh('police-enforcer', { +-- category = 'police', +-- name = 'Enforcer', +-- simfphysID = 'sim_fphys_gta4_nstockade', +-- price = 55000, +-- customFOV = 38, +-- deposit = true, +-- police = true, +-- bulletproof = true, +-- canUse = carDealer.checks.dobro, +-- tags = { carDealer.tags.dobro }, +-- radioWhitelist = carDealer.policeRadioStations, +-- }) diff --git a/garrysmod/addons/_config/lua/config/cars/sports.lua b/garrysmod/addons/_config/lua/config/cars/sports.lua new file mode 100644 index 0000000..27ee0b1 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/cars/sports.lua @@ -0,0 +1,76 @@ +carDealer.addCategory('sports', { + name = 'Спортивные', + icon = 'octoteam/icons-16/user.png', + queue = true, + canUse = carDealer.checks.civil, + spawns = carDealer.civilSpawns, +}) + +carDealer.addVeh('feltzer2', { + name = 'Feltzer', + simfphysID = 'sim_fphys_gta4_feltzer', + price = 3850000, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Мягкая', 275000 }, + { 'Жесткая', 240000 }, + }, + }, + }, +}) + +carDealer.addVeh('comet2', { + name = 'Comet', + simfphysID = 'sim_fphys_gta4_comet', + price = 5000000, +}) + +carDealer.addVeh('banshee2', { + name = 'Banshee', + simfphysID = 'sim_fphys_gta4_banshee', + price = 5800000, + default = { + bg = { [2] = 1 }, + }, + bodygroups = { + [1] = { + name = 'Крыша', + variants = { + { 'Заводская', 1000 }, + { 'Рама', 85000 }, + { 'Закрытая', 325000 }, + }, + }, + [2] = { + name = 'Капот', + variants = { + { 'С вентиляцией', 145000 }, + { 'Заводской', 1000 }, + }, + }, + }, +}) + +-- +-- DOBRO +-- + +carDealer.addVeh('sultanrs2', { + name = 'Sultan RS', + simfphysID = 'sim_fphys_gta4_sultanrs', + price = 3000000, + bodygroups = { + [1] = { + name = 'Капот', + variants = { + { 'Заводской', 1000 }, + { 'Воздухозаборник', 235000 }, + }, + }, + }, + canUse = carDealer.checks.dobro, + tags = { carDealer.tags.dobro }, +}) diff --git a/garrysmod/addons/_config/lua/config/certificates.lua b/garrysmod/addons/_config/lua/config/certificates.lua new file mode 100644 index 0000000..08a5f6b --- /dev/null +++ b/garrysmod/addons/_config/lua/config/certificates.lua @@ -0,0 +1,5 @@ +dbgCerts.certTitles = { + npd = 'Удостоверение члена партии НПД', + investigator = 'Удостоверение сотрудника отдела расследований', + iad = 'Удостоверение инспектора отдела внутренних расследований', +} diff --git a/garrysmod/addons/_config/lua/config/content.lua b/garrysmod/addons/_config/lua/config/content.lua new file mode 100644 index 0000000..c024dae --- /dev/null +++ b/garrysmod/addons/_config/lua/config/content.lua @@ -0,0 +1,107 @@ +-- +-- dobrograd +-- + +resource.AddWorkshop '1407179012' -- rp_eastcoast_v4c +resource.AddWorkshop '2778554124' -- rp_evocity_dbg_220222 + +if game.GetMap():find('truenorth') then + resource.AddWorkshop '1601428630' -- rp_truenorth_v1a + resource.AddWorkshop '1601425051' -- rp_truenorth_v1a content 1 + resource.AddWorkshop '1601404123' -- rp_truenorth_v1a content 2 +end +if game.GetMap():find('riverden') then + resource.AddWorkshop '2778572819' -- rp_riverden_dbg +end + +resource.AddWorkshop '1975435190' -- dbg content +resource.AddWorkshop '2507121628' -- transport content +resource.AddWorkshop '2558543740' -- characters content +resource.AddWorkshop '2532822854' -- props content 1 +resource.AddWorkshop '2532517900' -- props content 2 +resource.AddWorkshop '2534082994' -- props content 3 + +resource.AddWorkshop '1548302260' -- halloween stuff +resource.AddWorkshop '1238652515' -- new year stuff +resource.AddWorkshop '242776816' -- adv part contr + +resource.AddFile 'resource/fonts/Roboto-Black.ttf' +resource.AddFile 'resource/fonts/Roboto-BlackItalic.ttf' +resource.AddFile 'resource/fonts/Roboto-Bold.ttf' +resource.AddFile 'resource/fonts/Roboto-BoldCondensed.ttf' +resource.AddFile 'resource/fonts/Roboto-BoldCondensedItalic.ttf' +resource.AddFile 'resource/fonts/Roboto-BoldItalic.ttf' +resource.AddFile 'resource/fonts/Roboto-Condensed.ttf' +resource.AddFile 'resource/fonts/Roboto-CondensedItalic.ttf' +resource.AddFile 'resource/fonts/Roboto-Italic.ttf' +resource.AddFile 'resource/fonts/Roboto-Light.ttf' +resource.AddFile 'resource/fonts/Roboto-LightItalic.ttf' +resource.AddFile 'resource/fonts/Roboto-Medium.ttf' +resource.AddFile 'resource/fonts/Roboto-MediumItalic.ttf' +resource.AddFile 'resource/fonts/Roboto-Regular.ttf' +resource.AddFile 'resource/fonts/Roboto-Thin.ttf' +resource.AddFile 'resource/fonts/Roboto-ThinItalic.ttf' + +-- +-- octolib +-- +resource.AddWorkshop '1394544550' -- icons +resource.AddFile 'resource/fonts/arial.ttf' +resource.AddFile 'resource/fonts/arialbd.ttf' +resource.AddFile 'resource/fonts/arialbi.ttf' +resource.AddFile 'resource/fonts/arialblk.ttf' +resource.AddFile 'resource/fonts/ariali.ttf' +resource.AddFile 'resource/fonts/blogger.ttf' +resource.AddFile 'resource/fonts/bloggerb.ttf' +resource.AddFile 'resource/fonts/bloggerbi.ttf' +resource.AddFile 'resource/fonts/bloggeri.ttf' +resource.AddFile 'resource/fonts/bloggerl.ttf' +resource.AddFile 'resource/fonts/bloggerli.ttf' +resource.AddFile 'resource/fonts/bloggerm.ttf' +resource.AddFile 'resource/fonts/bloggermi.ttf' +resource.AddFile 'resource/fonts/fontawesome-webfont.ttf' + +-- +-- serverguard +-- +resource.AddWorkshop '685130934' + +-- +-- simfphys +-- +resource.AddWorkshop '771487490' + +-- +-- stormfox +-- +resource.AddWorkshop '2447979470' + +-- +-- atm +-- +resource.AddFile 'materials/newcity/atm.png' + +resource.AddFile 'models/props_unique/atm01.mdl' +resource.AddSingleFile 'models/props_unique/atm01.dx80.vtx' +resource.AddSingleFile 'models/props_unique/atm01.dx90.vtx' +resource.AddSingleFile 'models/props_unique/atm01.mdl' +resource.AddSingleFile 'models/props_unique/atm01.phy' +resource.AddSingleFile 'models/props_unique/atm01.vtx' +resource.AddSingleFile 'models/props_unique/atm01.vvt' + +resource.AddFile 'materials/models/props_unique/atm.vmt' +resource.AddSingleFile 'materials/models/props_unique/atm.vmt' +resource.AddSingleFile 'materials/models/props_unique/atm.vtf' +resource.AddSingleFile 'materials/models/props_unique/atm_ref.vtf' + +-- +-- fadmin (darkrp) +-- +local function AddDir(dir) + local files, folders = file.Find(dir .. "/*", "GAME") + for _, fdir in pairs(folders) do + if fdir ~= ".svn" then AddDir(dir .. "/" .. fdir) end + end + for k, v in pairs(files) do resource.AddFile(dir .. "/" .. v) end +end +AddDir("materials/fadmin") diff --git a/garrysmod/addons/_config/lua/config/groups.lua b/garrysmod/addons/_config/lua/config/groups.lua new file mode 100644 index 0000000..8224afa --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups.lua @@ -0,0 +1,50 @@ +simpleOrgs.orgs = simpleOrgs.orgs or {} + +local reloadUpstream = octolib.func.debounce(function() + hook.Run('octolib.event:reloadOrgs') + hook.Run('dbg-orgs.listLoaded') +end, 1) + +function simpleOrgs.addOrg(id, data) + + simpleOrgs.orgs[id] = data + data.members = {} + data.owners = {} + data.flyer = data.flyer or '' + data.url = '' + + dbgDoorGroups.registerGroup(id, data.name or utf8.upper(id)) + if data.rankOrder then + local dict = {} + for i, v in ipairs(data.rankOrder) do + dict[v] = i + end + data._rankOrder = dict + end + + reloadUpstream() +end + +local files, folders = file.Find('config/groups/*', 'LUA') +for _, v in ipairs(files) do + octolib.shared('config/groups/' .. string.StripExtension(v)) +end +for _, v in ipairs(folders) do + local cats = file.Find('config/groups/' .. v .. '/cmenu/categories/*.lua', 'LUA') + for _, cat in ipairs(cats) do + octolib.client('config/groups/' .. v .. '/cmenu/categories/' .. cat:StripExtension()) + end + local items = file.Find('config/groups/' .. v .. '/cmenu/items/*.lua', 'LUA') + for _, item in ipairs(items) do + octolib.client('config/groups/' .. v .. '/cmenu/items/' .. item:StripExtension()) + end + octolib.shared('config/groups/' .. v .. '/init') +end +hook.Run('simple-orgs.load') + +for _, org in pairs(simpleOrgs.orgs) do + org.members = {} + org.flyer = org.flyer or '' + org.url = '' +end +reloadUpstream() diff --git a/garrysmod/addons/_config/lua/config/groups/alpha.lua b/garrysmod/addons/_config/lua/config/groups/alpha.lua new file mode 100644 index 0000000..15e70be --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/alpha.lua @@ -0,0 +1,234 @@ +local clothesData = { + icon = 'lock', + ['models/player/octo_alpha/'] = {{ + sm = 'Головной убор', + icon = 'hat', + bodygroup = 1, + vals = { + [0] = { 'Без головного убора', 'cross', '/me снимает головной убор' }, + [2] = { 'Кепка', 'cap', '/me надевает кепку' }, + }, + },{ + sm = 'Перчатки', + icon = 'hand', + bodygroup = 3, + vals = { + [0] = { 'Снять', 'cross', '/me снимает перчатки' }, + [1] = { 'Надеть', 'bullet_black', '/me надевает перчатки' }, + }, + },{ + sm = 'Кобура', + icon = 'gun', + bodygroup = 6, + vals = { + [0] = { 'Надеть на пояс', 'arrow_up', '/me надевает кобуру на пояс' }, + [1] = { 'Надеть на ногу', 'arrow_down', '/me надевает кобуру на ногу' }, + [2] = { 'Снять', 'cross', '/me снимает кобуру' }, + }, + },{ + sm = 'Кобура тазера', + icon = 'lightning', + bodygroup = 7, + vals = { + [0] = { 'Надеть', 'bullet_blue', '/me вешает кобуру с тазером обратно на пояс' }, + [1] = { 'Снять', 'cross', '/me снимает кобуру с тазером с пояса' }, + }, + },{ + sm = 'Дубинка', + icon = 'baton', + bodygroup = 8, + vals = { + [0] = { 'Надеть', 'bullet_blue', '/me вешает дубинку обратно на пояс' }, + [1] = { 'Снять', 'cross', '/me снимает дубинку с пояса' }, + }, + },{ + sm = 'Наручники', + icon = 'radio_button', + bodygroup = 9, + vals = { + [0] = { 'Надеть одну пару', 'bullet_blue', '/me надевает одну пару наручников обратно на пояс' }, + [1] = { 'Надеть две пары', 'bullet_black', '/me надевает две пары наручников обратно на пояс' }, + [2] = { 'Снять', 'cross', '/me снимает наручники с пояса' }, + }, + },{ + sm = 'Рация', + icon = 'radio_modern', + bodygroup = 10, + vals = { + [0] = { 'Надеть', 'bullet_blue', '/me надевает рацию обратно на пояс' }, + [1] = { 'Снять', 'cross', '/me снимает рацию с пояса' }, + }, + }, + }, +} + +local modelNums = {1, 2, 3, 4, 5, 6, 8, 9} + +local bgsData = { + [1] = { + name = 'Головной убор', + vals = { + {'Без головного убора', 0, true}, + {'Кепка', 2}, + }, + }, + [2] = { + name = 'Гарнитура', + vals = { + {'Проводная', 0, true}, + {'Снять', 2}, + }, + }, + [4] = { + name = 'Верх', + vals = { + {'Строгая рубашка', 0, true}, + {'Строгая рубашка и куртка', 1}, + {'Поло', 2}, + {'Поло и куртка', 3}, + {'Поло и куртка с бронежилетом', 4}, + }, + }, + [5] = { + name = 'Бронежилет', + vals = { + {'Без бронежилета', 0, true}, + {'Без магазинов', 1}, + }, + }, + [11] = { + name = 'Нашивка', + vals = { + {'Без нашивки', 0, true}, + {'S.S.U.', 1}, + {'King', 2}, + {'Zero', 3}, + {'Siesta', 4}, + {'Yankee', 5}, + {'Alpha', 6}, + {'Ricardo', 7}, + {'Union', 8}, + }, + }, + [12] = { + name = 'Патч', + vals = { + {'Без патча', 0, true}, + {'DMC', 1}, + {'Золотой орел', 2}, + {'Череп', 3}, + {'Орел со звездой', 4}, + {'Sabaton', 5}, + {'Снежинка', 6}, + {'Вялоухий', 7}, + {'Легионер', 8}, + {'Нептун', 9}, + {'ДУХ АХАХАХ', 10}, + }, + } +} + +local skinData = { + name = 'Низ', + vals = { + {'Темные брюки', 0, true}, + {'Светлые брюки', 4}, + }, +} + +local models = {} +for i, v in ipairs(modelNums) do + models[#models + 1] = { + name = 'Охранник ' .. i, + model = ('models/player/octo_alpha/male_%02d.mdl'):format(v), + bgs = bgsData, + skin = skinData, + } +end + +simpleOrgs.addOrg('alpha', { + name = 'Alpha', + title = 'Alpha Corporation | Делаем вашу жизнь безопаснее', + shortTitle = 'Alpha', + clothes = clothesData, + team = 'alpha', + mdls = models, + talkieFreq = 'alpha', +}) + +local plateCol = { + bg = Color(123, 0, 28), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + txt = Color(179, 179, 179), +} + +carDealer.addCategory('alpha', { + name = 'Alpha', + icon = 'icon16/lightning.png', + queue = true, + ems = true, + bulletproof = true, + doNotEvacuate = true, + spawns = carDealer.civilSpawns, + canUse = function(ply) return ply:Team() == TEAM_ALPHA, 'Доступно только Alpha' end, +}) + +carDealer.addVeh('alpha_merit', { + name = 'Merit', + simfphysID = 'sim_fphys_gta4_merit_alpha', + plateCol = plateCol, + price = 12000, + deposit = true, + police = true, + default = { + mats = { + [14] = 'models/alpha_cars/alpha_car_04', + }, + }, +}) + +local function canUse(ply) + return DarkRP.isTaxist(ply) or ply:Team() == TEAM_ALPHA +end + +if SERVER then + local leaveMeAloneID = 0 + + + octochat.registerCommand('/alphabutton', { + cooldown = 60, + log = true, + execute = function(ply) + local job, jobname = ply:getJobTable() + if job then jobname = job.name end + local customJob = ply:GetNetVar('customJob') + if customJob then jobname = unpack(customJob) end + + ply:DoEmote('{name} нажимает кнопку паники') + + local msg = ('%s %s передает свое местоположение, используя тревожную кнопку!'):format(jobname, ply:Name()) + local marker = { + id = 'cpPanicBtn' .. leaveMeAloneID, + group = 'cpPanicBtn', + txt = 'Кнопка паники', + pos = ply:GetPos() + Vector(0,0,40), + col = Color(102,170,170), + des = {'timedist', { 600, 300 }}, + icon = 'octoteam/icons-32/exclamation.png', + size = 32, + } + for _,v in ipairs(player.GetAll()) do + if v:Team() == TEAM_ALPHA then + v:Notify('warning', msg) + v:EmitSound('npc/attack_helicopter/aheli_damaged_alarm1.wav', 45, 100, 0.5) + v:AddMarker(marker) + end + end + leaveMeAloneID = leaveMeAloneID + 1 + end, + check = canUse, + }) +else + octochat.defineCommand('/alphabutton', canUse) +end \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/bank.lua b/garrysmod/addons/_config/lua/config/groups/bank.lua new file mode 100644 index 0000000..9e4c8ab --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/bank.lua @@ -0,0 +1,164 @@ +local bankSubMats = { + [27] = { + name = 'Форма', + vals = { + {'Костюм "D\'Anglere"', 'models/humans/modern/octo/suit1_sheet'}, + {'Черный деловой костюм', 'models/humans/modern/octo/suit2_sheet'}, + {'Белый деловой костюм', 'models/humans/modern/octo/suit3_sheet'}, + {'Серый деловой костюм', 'models/humans/modern/octo/suit4_sheet'}, + {'Зеленый деловой костюм', 'models/humans/modern/octo/suit5_sheet'}, + {'Голубой деловой костюм', 'models/humans/modern/octo/suit6_sheet'}, + {'Черный пиджак', 'models/humans/modern/octo/suit7_sheet'}, + {'Синий пиджак', 'models/humans/modern/octo/suit8_sheet'}, + }, + }, +} + +local models = octolib.table.mapSequential({ + 'models/humans/octo/male_01_01.mdl', + 'models/humans/octo/male_01_02.mdl', + 'models/humans/octo/male_01_03.mdl', + 'models/humans/octo/male_02_01.mdl', + 'models/humans/octo/male_02_02.mdl', + 'models/humans/octo/male_02_03.mdl', + 'models/humans/octo/male_03_01.mdl', + 'models/humans/octo/male_03_02.mdl', + 'models/humans/octo/male_03_03.mdl', + 'models/humans/octo/male_03_04.mdl', + 'models/humans/octo/male_03_05.mdl', + 'models/humans/octo/male_03_06.mdl', + 'models/humans/octo/male_03_07.mdl', + 'models/humans/octo/male_04_01.mdl', + 'models/humans/octo/male_04_02.mdl', + 'models/humans/octo/male_04_03.mdl', + 'models/humans/octo/male_04_04.mdl', + 'models/humans/octo/male_05_01.mdl', + 'models/humans/octo/male_05_02.mdl', + 'models/humans/octo/male_05_03.mdl', + 'models/humans/octo/male_05_04.mdl', + 'models/humans/octo/male_05_05.mdl', + 'models/humans/octo/male_06_01.mdl', + 'models/humans/octo/male_06_02.mdl', + 'models/humans/octo/male_06_03.mdl', + 'models/humans/octo/male_06_04.mdl', + 'models/humans/octo/male_06_05.mdl', + 'models/humans/octo/male_07_01.mdl', + 'models/humans/octo/male_07_02.mdl', + 'models/humans/octo/male_07_03.mdl', + 'models/humans/octo/male_07_04.mdl', + 'models/humans/octo/male_07_05.mdl', + 'models/humans/octo/male_07_06.mdl', + 'models/humans/octo/male_08_01.mdl', + 'models/humans/octo/male_08_02.mdl', + 'models/humans/octo/male_08_03.mdl', + 'models/humans/octo/male_08_04.mdl', + 'models/humans/octo/male_09_01.mdl', + 'models/humans/octo/male_09_02.mdl', + 'models/humans/octo/male_09_03.mdl', + 'models/humans/octo/male_09_04.mdl', +}, function(v, i) + return { + name = 'Внешность ' .. i, + male = true, + model = v, + subMaterials = bankSubMats, + requiredSkin = 23, + } +end) + +models[#models + 1] = { + name = 'Сотрудник охраны', + model = 'models/player/camo_09.mdl', + male = true, + bgs = { + [1] = { + name = 'Базовый бронежилет', + }, + [2] = { + name = 'Кепка', + }, + [3] = { + name = 'Пояс', + }, + [5] = { + name = 'Полный бронежилет', + }, + [6] = { + name = 'Шлем', + } + }, + requiredBgs = {[4] = 1} +} + +simpleOrgs.addOrg('bank', { + name = 'Sevila’s Bank', + title = 'Sevila’s Bank | Лучший банк штата Мичиган', + shortTitle = 'Sevila’s Bank', + team = 'bank', + mdls = models, + talkieFreq = 'bank', +}) + +carDealer.addCategory('bank', { + name = 'Банк', + icon = 'icon16/money.png', + queue = true, + bulletproof = true, + doNotEvacuate = true, + spawns = carDealer.civilSpawns, + canUse = function(ply) return ply:Team() == TEAM_BANK, 'Доступно только сотрудникам банка' end, +}) + +local plateCol = { + bg = Color(30, 30, 30), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + txt = Color(255, 255, 255), +} + +carDealer.addVeh('bank_stockade', { + name = 'Stockade', + simfphysID = 'sim_fphys_gta4_stockade', + price = 0, + bulletproof = true, + deposit = true, + police = true, + plateCol = plateCol, +}) + +carDealer.addVeh('bank_dilettante', { + name = 'Dilettante', + simfphysID = 'sim_fphys_gta4_dilettante', + price = 0, + deposit = true, + plateCol = plateCol, + default = { + col = { Color(30, 30, 30), Color(30, 30, 30), Color(0,0,0), Color(30, 30, 30) }, + }, +}) + +carDealer.addVeh('bank_habanero', { + name = 'Habanero', + simfphysID = 'sim_fphys_gta4_habanero', + price = 0, + deposit = true, + plateCol = plateCol, + default = { + col = { Color(30, 30, 30), Color(30, 30, 30), Color(0,0,0), Color(30, 30, 30) }, + }, +}) + +local toStrip = {'weapon_octo_p228', 'weapon_octo_m4a1', 'weapon_octo_mp5', 'weapon_cuff_police', 'stunstick', 'med_kit', 'stungun'} +hook.Add('OnPlayerChangedTeam', 'dbg-orgs.bank', function(ply, old, new) + if not SERVER then return end + if new == TEAM_BANK then + timer.Simple(0, function() + if ply:GetModel() ~= 'models/player/camo_09.mdl' then + for _, v in ipairs(toStrip) do + ply:SetNetVar('HasGunlicense') + ply:StripWeapon(v) + end + end + end) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/convard.lua b/garrysmod/addons/_config/lua/config/groups/convard.lua new file mode 100644 index 0000000..0bf1e31 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/convard.lua @@ -0,0 +1,28 @@ +-- +-- CONVARD +-- +simpleOrgs.addOrg('convard', { + name = 'ConVard HotDogs', + title = '', + shortTitle = '', + secret = true, + owners = {}, +}) + +-- carDealer.addCategory('convard', { +-- name = 'ConVard', +-- icon = 'octoteam/icons-16/hamburger.png', +-- doNotEvacuate = true, +-- canUse = function(ply) +-- if not ply:IsOrgMember('convard') then +-- return false, 'Доступно только для ConVard HotDogs' +-- end +-- end, +-- }) + +-- carDealer.addVeh('convard_hotdog', { +-- name = 'HotDog', +-- simfphysID = 'simfphys_gta_sa_hotdog', +-- price = 0, +-- deposit = true, +-- }) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/coroners.lua b/garrysmod/addons/_config/lua/config/groups/coroners.lua new file mode 100644 index 0000000..08848ff --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/coroners.lua @@ -0,0 +1,136 @@ +local clothesData = { + icon = 'user_medical', + ['models/kerry/plats_medical_'] = { + { + bodygroup = 2, + vals = { + [0] = { 'Снять перчатки', 'cross', '/me снимает перчатки с рук' }, + [1] = { 'Надеть перчатки', 'bullet_blue', '/me надевает перчатки на руки' }, + }, + }, + }, +} + +local uniform = { + [4] = { + name = 'Форма', + vals = { + {'Обычная форма', 'models/medic/coroner_jbib'}, + {'Специальная форма', 'models/medic/coroner_SU'}, + }, + }, +} + +local coroners = {} +for i = 1, 11 do + coroners[#coroners + 1] = { + model = ('models/kerry/plats_medical_%02i.mdl'):format(i), + male = true, + name = 'Коронер ' .. i, + subMaterials = uniform, + requiredMats = { + [6] = 'null', + [7] = 'null', + [8] = 'null', + [11] = 'models/merriweather/lowr_diff_000_a_uni', + [16] = 'models/medic/uppr_diff_024_a_whi', + }, + } + coroners[#coroners + 1] = { + model = ('models/kerry/plats_medical_%02i.mdl'):format(i), + male = true, + name = 'Теплая форма ' .. i, + requiredBgs = {[1] = 1}, + requiredMats = { + [6] = 'null', + [7] = 'null', + [8] = 'models/coroner/cid', + [11] = 'models/merriweather/lowr_diff_000_a_uni', + [12] = 'models/coroner/cwinter', + [16] = 'models/medic/uppr_diff_024_a_whi', + }, + } +end +for i = 10, 11 do + coroners[#coroners + 1] = { + model = ('models/kerry/plats_medical_%02i.mdl'):format(i), + male = false, + name = 'Коронер ' .. (i-9), + subMaterials = uniform, + requiredMats = { + [8] = 'null', + [9] = 'null', + [10] = 'null', + [15] = 'models/merriweather/lowr_diff_000_a_uni', + [16] = 'models/medic/uppr_diff_024_a_whi', + }, + } +end + +if SERVER then + netstream.Hook('coroners.gloves', function(ply, value) + if ply:getJobTable().command == 'coroner' and octolib.math.inRange(value, 0, 1) then + ply:SetBodygroup(2, value) + end + end) +end + +simpleOrgs.addOrg('coroners', { + name = 'Коронеры', + title = 'Коронеры', + shortTitle = 'Коронеры', + team = 'coroner', + mdls = coroners, + clothes = clothesData, + talkieFreq = 'medic', +}) + +carDealer.addCategory('coroners', { + name = 'Коронеры', + ems = true, + doNotEvacuate = true, + icon = 'octoteam/icons-16/hospital.png', + canUse = function(ply) return ply:Team() == TEAM_CORONER, 'Доступно только коронерам' end, + spawns = { + rp_evocity_dbg_220222 = { + { Vector(-11295, 9344, 125), Angle(0,-90,0) }, -- hospital + { Vector(-11111, 9344, 125), Angle(0,-90,0) }, + }, + rp_eastcoast_v4c = carDealer.civilSpawns.rp_eastcoast_v4c, + rp_truenorth_v1a = { + { Vector(11667, 12990, 128), Angle(0, -90, 0) }, + { Vector(11876, 12990, 128), Angle(0, -90, 0) }, + }, + rp_riverden_dbg_220313 = { + { Vector(-5453.52, 1579.42, -220.133), Angle(-0, -90, 0) }, + { Vector(-5453.98, 2185.43, -219.886), Angle(-0, -90, 0) }, + }, + }, + spawnCheck = carDealer.limits.coroner, + limitGroup = 'coroner', +}) +carDealer.addVeh('coroners_pony', { + category = 'coroners', + name = 'Pony', + simfphysID = 'sim_fphys_gta4_pony_coroner', + price = 0, + deposit = true, + default = { + mats = { + [1] = 'octoteam/models/vehicles/moonbeam/livery1', + }, + }, +}) +carDealer.addVeh('coroners_moonbeam', { + category = 'coroners', + name = 'Moonbeam', + simfphysID = 'sim_fphys_gta4_moonbeam', + price = 0, + deposit = true, + default = { + mats = { + [4] = 'octoteam/models/vehicles/moonbeam/coroner_livery2', + }, + col = { Color(36,36,36), Color(36,36,36), Color(0,0,0), Color(36,36,36) }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/csd.lua b/garrysmod/addons/_config/lua/config/groups/csd.lua new file mode 100644 index 0000000..02f61b4 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/csd.lua @@ -0,0 +1,157 @@ +local models = {} + +-- ELECTRICIANS +local electricianBgs = { + [2] = { + name = 'Головной убор', + vals = { + { 'Нет', 0 }, + { 'Шлем', 2 }, + { 'Шлем + Наушники', 1 }, + }, + }, + [3] = { + name = 'Жилет', + }, + [4] = { + name = 'Очки', + }, +} +for i = 1, 9 do + models[#models + 1] = { + name = 'Электрик ' .. i, + male = true, + model = ('models/player/supernek/workers/electricians/electrician_male%02d.mdl'):format(i), + bgs = electricianBgs, + } +end + +-- HANDYMEN +local handymanBgs = { + [2] = { + name = 'Жилет', + }, +} +for i = 1, 9 do + models[#models + 1] = { + name = 'Мастер ' .. i, + male = true, + model = ('models/player/supernek/workers/handymen/handyman_male%02d.mdl'):format(i), + bgs = handymanBgs, + } +end + +-- PLUMBERS +local plumberBgs = { + [2] = { + name = 'Кепка', + }, + [3] = { + name = 'Жилет', + }, +} +for i = 1, 9 do + models[#models + 1] = { + name = 'Сантехник ' .. i, + male = true, + model = ('models/player/supernek/workers/plumbers/plumber_male%02d.mdl'):format(i), + bgs = plumberBgs, + } +end + +-- SCAVENGERS +local scavengerBgs = { + [2] = { + name = 'Жилет', + }, +} +for i = 1, 9 do + models[#models + 1] = { + name = 'Мусорщик ' .. i, + male = true, + model = ('models/player/supernek/workers/scavengers/scavenger_male%02d.mdl'):format(i), + bgs = scavengerBgs, + } +end + +-- INSPECTORS +for i = 1, 7 do + models[#models + 1] = { + model = ('models/player/kerry/medic/medic_%02i.mdl'):format(i), + male = true, + name = 'Инспектор ' .. i, + requiredMats = { + [4] = 'models/debug/debugwhite', + [7] = 'null', + } + } + if i == 7 then continue end + models[#models + 1] = { + model = ('models/player/kerry/medic/medic_%02i_f.mdl'):format(i), + male = false, + name = 'Инспектор ' .. i, + requiredMats = { + [4] = 'models/debug/debugwhite', + [5] = 'dev/dev_tvmonitor1a', + } + } +end + +simpleOrgs.addOrg('csd', { + name = 'Городская служба', + title = 'Городская служба', + shortTitle = 'Городская служба', + team = 'csd', + mdls = models, + talkieFreq = 'csd', +}) + +carDealer.addCategory('csd', { + name = 'Городская служба', + icon = 'octoteam/icons-16/wrench.png', + ems = true, + queue = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_CSD, 'Доступно только городской службе' end, + spawns = { + rp_riverden_dbg_220313 = { + { Vector(27.1625, 11166.7, 29.3211), Angle(0, 0, 0) }, + { Vector(28.6012, 10978.4, 30.8232), Angle(0, 0, 0) }, + { Vector(26.1124, 11999.1, 28.3349), Angle(0, 0, 0) }, + }, + }, +}) +carDealer.addVeh('csd_burrito', { + category = 'csd', + name = 'Burrito', + simfphysID = 'sim_fphys_gta4_burrito_csd', + price = 0, + deposit = true, + default = { + bg = { [1] = 4 }, + mats = { + [1] = 'octoteam/models/vehicles/burrito/burritocsd_livery_1', + }, + }, +}) +carDealer.addVeh('csd_rancher', { + category = 'csd', + name = 'Rancher', + simfphysID = 'sim_fphys_gta4_rancher', + price = 0, + deposit = true, + default = { + col = { Color(0, 88, 139), Color(0, 88, 139) }, + bg = { [1] = 1 }, + }, +}) +carDealer.addVeh('csd_moonbeam', { + category = 'csd', + name = 'Moonbeam', + simfphysID = 'sim_fphys_gta4_moonbeam', + price = 0, + deposit = true, + default = { + col = { Color(0,88,139), Color(0,88,139), Color(0,0,0), Color(0,88,139) }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/dpd/cars.lua b/garrysmod/addons/_config/lua/config/groups/dpd/cars.lua new file mode 100644 index 0000000..ade7bd0 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/dpd/cars.lua @@ -0,0 +1,249 @@ +hook.Add('car-dealer.priceOverride', 'dbg-police.dpd', function(ply, class) + local cdData = carDealer.vehicles[class] + if ply:Team() == TEAM_DPD and cdData.police and cdData.deposit then return 0 end +end) + +-- +-- STANDARD +-- + +carDealer.addCategory('dpd', { + name = 'DPD', + ems = true, + icon = 'octoteam/icons-16/user_policeman_white.png', + canUse = function(ply) return ply:Team() == TEAM_DPD, 'Доступно только DPD' end, + spawns = carDealer.policeSpawns, + spawnCheck = carDealer.limitedSpawn(3, 'dpd', 'В городе уже много автомобилей DPD, найди напарника или попробуй чуть позже'), + limitGroup = 'dpd', +}) + +carDealer.addVeh('dpd-vapid', { + category = 'dpd', + name = 'DPD Vapid', + simfphysID = 'sim_fphys_gta4_police', + price = 30000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, +}) + +carDealer.addVeh('dpd-merit', { + category = 'dpd', + name = 'DPD Merit', + simfphysID = 'sim_fphys_gta4_police2', + price = 40000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, +}) + +carDealer.addVeh('dpd-buffalo', { + category = 'dpd', + name = 'DPD Buffalo', + simfphysID = 'sim_fphys_tbogt_police3', + price = 75000, + deposit = true, + police = true, + canUse = function(ply) + if ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO then return true end + if ply:isCP() and ply:Team() ~= TEAM_FBI and ply:Team() ~= TEAM_MAYOR then return carDealer.checks.dobro(ply) end + return false + end, + tags = { carDealer.tags.dobro }, + radioWhitelist = carDealer.policeRadioStations, +}) + +-- +-- NOMARK +-- + +local ranks = octolib.array.toKeys {'swat', 'dec1', 'se1', 'se2', 'dec2', 'dec3', 'lie', 'cap', 'cmd', 'asch', 'asc', 'chi'} +carDealer.addCategory('nomark', { + name = 'Немаркированные', + icon = 'icon16/user_suit.png', + ems = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_DPD and ranks[ply:GetActiveRank('dpd') or ''] or false, 'Доступно только детективам, либо сержанту и выше' end, + spawns = { + rp_evocity_dbg_220222 = { + { Vector(-4656, -7516, 225), Angle(0,-90,0) }, -- Duglas + { Vector(-4656, -7211, 225), Angle(0,-90,0) }, + { Vector(-4656, -6906, 225), Angle(0,-90,0) }, + { Vector(-4656, -6601, 225), Angle(0,-90,0) }, + { Vector(-4656, -6296, 225), Angle(0,-90,0) }, + }, + rp_eastcoast_v4c = carDealer.policeSpawns.rp_eastcoast_v4c, + rp_truenorth_v1a = carDealer.policeSpawns.rp_truenorth_v1a, + }, + spawnCheck = carDealer.limits.police, + limitGroup = 'police', +}) + +local randomColorTags = { {'icon16/color_wheel.png', 'Случайный цвет'} } +local function randomColor() + local colData = table.Random(carDealer.defaultCarColors) + local _, r, g, b = unpack(colData) + local col = Color(r, g, b) + return { col, col, Color(0,0,0), col } +end + +local plateCol = { + bg = Color(255, 255, 255), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + text = Color(0, 0, 0), +} + +carDealer.addVeh('nomark_premier', { + name = 'Premier', + simfphysID = 'sim_fphys_gta4_premier', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('nomark_dilettante', { + name = 'Dilettante', + simfphysID = 'sim_fphys_gta4_dilettante', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('nomark_buffalo', { + name = 'Buffalo', + simfphysID = 'sim_fphys_gta4_fbi', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('nomark_admiral', { + name = 'Admiral', + simfphysID = 'sim_fphys_gta4_admiral', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('nomark_taxi', { + name = 'Taxi', + simfphysID = 'sim_fphys_gta4_taxi2', + price = 8000, + police = true, + deposit = true, + default = { + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('nomark_cabbie', { + name = 'Cabby', + simfphysID = 'sim_fphys_gta4_cabby', + price = 15000, + police = true, + deposit = true, + default = { + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + }, + plateCol = plateCol, +}) + +-- +-- SWAT +-- + +carDealer.addCategory('swat', { + name = 'S.W.A.T', + icon = 'icon16/lightning.png', + ems = true, + bulletproof = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_DPD and ply:GetActiveRank('dpd') == 'swat', 'Доступно только S.W.A.T' end, + spawnCheck = carDealer.limitedSpawn(2, 'swat', 'В городе уже много автомобилей SWAT, найди напарника или попробуй чуть позже'), + limitGroup = 'swat', + spawns = carDealer.policeSpawns, +}) + +carDealer.addVeh('swat_moonbeam', { + name = 'Moonbeam', + simfphysID = 'sim_fphys_gta4_moonbeam', + price = 0, + deposit = true, + default = { + col = { Color(15,15,15), Color(15,15,15), Color(0,0,0), Color(15,15,15)}, + }, +}) + +carDealer.addVeh('swat_huntley', { + name = 'Huntley', + simfphysID = 'sim_fphys_gta4_huntley', + price = 0, + deposit = true, + police = true, + glauncher = true, + default = { + col = { Color(15,15,15), Color(15,15,15), Color(0,0,0), Color(15,15,15)}, + }, +}) + +carDealer.addVeh('swat_enforcer', { + name = 'Enforcer', + simfphysID = 'sim_fphys_gta4_nstockade', + price = 0, + bulletproof = true, + deposit = true, + police = true, + glauncher = true, + radioWhitelist = carDealer.policeRadioStations, +}) + +carDealer.addVeh('swat_bearcat', { + name = 'BearCat', + simfphysID = 'sim_fphys_gta4_bearcat', + previewOffset = Vector(0, 10, -30), + SpawnAngleOffset = Angle(0,90,0), + price = 0, + bulletproof = true, + deposit = true, + police = true, + glauncher = true, + radioWhitelist = carDealer.policeRadioStations, +}) + +carDealer.addVeh('swat_bearcat_rescue', { + name = 'BearCat Medevac', + simfphysID = 'sim_fphys_gta4_bearcat', + previewOffset = Vector(0, 10, -30), + SpawnAngleOffset = Angle(0,90,0), + price = 0, + default = { + skin = 2, + }, + bulletproof = true, + deposit = true, + police = true, + glauncher = true, + radioWhitelist = carDealer.policeRadioStations, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/dpd/init.lua b/garrysmod/addons/_config/lua/config/groups/dpd/init.lua new file mode 100644 index 0000000..364b4c8 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/dpd/init.lua @@ -0,0 +1,3 @@ +octolib.shared('models') +octolib.shared('cars') +octolib.server('octoinv/items') diff --git a/garrysmod/addons/_config/lua/config/groups/dpd/models.lua b/garrysmod/addons/_config/lua/config/groups/dpd/models.lua new file mode 100644 index 0000000..2da1f87 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/dpd/models.lua @@ -0,0 +1,1066 @@ +--[[ +РАНГИ: +-- all +cad. Кадет +ppo. Стажер +po. Офицер +spo. Старший офицер +swat. Оперативник S.W.A.T. +dec1. Детектив I квалификации +dec2. Детектив II квалификации +dec3. Детектив III квалификации +lie. Лейтенант +cap. Капитан +cmd. Командующий +asch. Помощник заместителя шефа +asc. Заместитель шефа +chi. Шеф департамента +]] + +local function addAll(...) + local result = {} + for _, tbl in ipairs({...}) do + for _, v in ipairs(tbl) do + result[#result + 1] = table.Copy(v) + end + end + return result +end + +local function addBgs(source, bgs) + local result = table.Copy(source) + for _, mdl in ipairs(result) do + mdl.bgs = mdl.bgs or {} + for k, v in pairs(bgs) do + mdl.bgs[k] = v + end + end + return result +end + +local function addRequiredBgs(source, bgs) + local result = table.Copy(source) + for _, mdl in ipairs(result) do + mdl.requiredBgs = mdl.requiredBgs or {} + for k, v in pairs(bgs) do + mdl.requiredBgs[k] = v + end + end + return result +end + +local function removeBgs(source, ...) -- in honor of kilo-7 + local result = table.Copy(source) + for _, mdl in ipairs(result) do + if not mdl.bgs then continue end + for _, bg in ipairs({...}) do + mdl.bgs[bg] = nil + end + if not next(mdl.bgs) then mdl.bgs = nil end + end + return result +end + +local function modify(source, modify) + local result = table.Copy(source) + for _, mdl in ipairs(result) do + for k, v in pairs(modify) do + mdl[k] = v + end + end + return result +end + +local patrolModels, utilityModels, everydayModels, swatModels, decModels = {}, {}, {}, {}, {} + +local patrolClothes = { + { + bodygroup = 3, + vals = { + [0] = { 'Снять бронежилет', 'cross', '/me снимает бронежилет с себя' }, + [1] = { 'Надеть бронежилет', 'bullet_blue', '/me надевает бронежилет на свое тело' }, + }, + },{ + bodygroup = 4, + vals = { + [0] = { 'Снять головной убор', 'cross', '/me снимает головной убор' }, + [1] = { 'Надеть кепку на голову', 'bullet_blue', '/me надевает кепку на свою голову' }, + [2] = { 'Надеть фуражку на голову', 'bullet_black', '/me надевает фуражку на свою голову' }, + }, + }, +} + +local malePatrolClothes = { + { + sm = 'Головной убор', + icon = 'hat', + bodygroup = 2, + vals = { + [0] = { 'Снять убор', 'cross', '/me снимает головной убор' }, + [1] = { 'Надеть фуражку', 'bullet_white', '/me надевает фуражку' }, + [2] = { 'Надеть кепку', 'bullet_blue', '/me надевает кепку' }, + [3] = { 'Надеть шапку', 'bullet_black', '/me надевает шапку' }, + } + }, { + sm = 'Перчатки', + icon = 'hand', + bodygroup = 6, + vals = { + [0] = { 'Снять', 'cross', '/me снимает перчатки' }, + [1] = { 'Надеть черные перчатки', 'bullet_black', '/me надевает черные перчатки' }, + [2] = { 'Надеть белые перчатки', 'bullet_white', '/me надевает белые перчатки' }, + [3] = { 'Надеть голубые нитриловые перчатки', 'bullet_blue', '/me надевает голубые нитриловые перчатки' }, + }, + }, { + bodygroup = 8, + vals = { + [0] = { 'Снять жилет', 'cross', '/me снимает жилет с себя' }, + [1] = { 'Надеть бронежилет', 'shield', '/me надевает бронежилет на свое тело' }, + [2] = { 'Надеть светоотражающий жилет', 'life_vest', '/me надевает светоотражающий жилет на свое тело' }, + }, + } +} + +local clothesData = { + icon = 'user_policeman_white', + ['models/player/octo_dpd/male'] = malePatrolClothes, + ['models/humans/dpdfem/female'] = patrolClothes, + ['models/dpdac/male_'] = { + { + sm = 'Тактический пояс', + icon = 'gun', + bodygroup = 3, + vals = { + [0] = { 'Снять тактический пояс', 'cross', '/me снимает головной убор' }, + [1] = { 'Надеть пустой тактический пояс', 'bullet_black', '/me надевает пустой тактический пояс' }, + [2] = { 'Надеть пустой тактический пояс со значком', 'bullet_blue', '/me надевает пустой тактический пояс, вешая на него значок' }, + [3] = { 'Надеть тактический пояс с пистолетом', 'bullet_red', '/me надевает снаряженный тактический пояс' }, + [4] = { 'Надеть тактический пояс с пистолетом и значком', 'bullet_green', '/me надевает снаряженный тактический пояс, вешая на него значок' }, + }, + },{ + bodygroup = 4, + vals = { + [0] = { 'Снять бронежилет', 'cross', '/me снимает бронежилет' }, + [1] = { 'Надеть бронежилет', 'bullet_blue', '/me надевает бронежилет на свое тело' }, + }, + },{ + bodygroup = 5, + vals = { + [0] = { 'Снять кепку', 'bullet_blue', '/me снимает кепку с головы' }, + [1] = { 'Надеть кепку', 'cross', '/me надевает кепку на голову' }, + }, + },{ + bodygroup = 6, + vals = { + [0] = { 'Снять очки', 'bullet_blue', '/me снимает защитные очки с глаз' }, + [1] = { 'Надеть очки', 'cross', '/me надевает защитные очки' }, + }, + } + }, + ['models/humans/dpdfeminv/female_'] = { + { + bodygroup = 4, + vals = { + [0] = { 'Снять кепку', 'cross', '/me снимает кепку с головы' }, + [1] = { 'Надеть кепку', 'bullet_blue', '/me надевает кепку на голову, поправляя её' }, + }, + },{ + bodygroup = 5, + vals = { + [0] = { 'Снять очки', 'cross', '/me снимает защитные очки с глаз' }, + [1] = { 'Надеть очки', 'bullet_blue', '/me надевает защитные очки' }, + }, + }, + }, + ['models/kerry/detective_dbg/male_'] = { + { + bodygroup = 1, + vals = { + [0] = { 'Снарядить полностью пояс', 'cross', '/me полностью снаряжает тактический пояс' }, + [1] = { 'Оставить на поясе только фонарик и табель', 'bullet_blue', '/me снимает с тактического пояса всё, кроме фонарика и табельного оружия' }, + [2] = { 'Оставить на поясе только фонарик', 'bullet_green', '/me снимает с тактического пояса всё, кроме фонарика' }, + }, + } + }, + ['models/dizcordum/citizens/playermodels/'] = { + { + bodygroup = 3, + vals = { + [0] = { 'Снять перчатки', 'cross', '/me снимает перчатки с рук' }, + [1] = { 'Надеть перчатки', 'bullet_blue', '/me надевает перчатки на руки' }, + }, + }, + }, + ['models/player/octo_swat_team/'] = { + { + bodygroup = 1, + vals = { + [0] = { 'Надеть бронежилет с разгрузками', 'bullet_blue', '/me надевает бронежилет на тело' }, + [1] = { 'Снять бронежилет с разгрузками', 'cross', '/me снимает бронежилет' }, + }, + },{ + bodygroup = 2, + vals = { + [0] = { 'Снарядить низ', 'bullet_blue', '/me нацепляет снаряжения на ноги' }, + [1] = { 'Снять всё с ног', 'cross', '/me снимает все снаряжение с ног' }, + }, + },{ + bodygroup = 3, + vals = { + [0] = { 'Распрямить рукава', 'arrow_down', '/me распрямляет рукава' }, + [1] = { 'Засучить рукава', 'arrow_up', '/me засучивает рукава' }, + }, + },{ + bodygroup = 4, + vals = { + [0] = { 'Снять перчатки', 'cross', '/me снимает тактические перчатки с рук' }, + [1] = { 'Надеть перчатки', 'bullet_blue', '/me надевает перчатки на руки' }, + }, + },{ + bodygroup = 5, + vals = { + [0] = { 'Снять часы', 'cross', '/me снимает часы с рук' }, + [1] = { 'Надеть часы', 'bullet_blue', '/me застегивает часы на руке' }, + }, + },{ + bodygroup = 9, + vals = { + [0] = { 'Надеть шлем', 'bullet_blue', '/me надевает шлем на голову' }, + [1] = { 'Снять шлем', 'cross', '/me снимает шлем с головы' }, + }, + },{ + bodygroup = 10, + vals = { + [0] = { 'Надеть наушники', 'bullet_blue', '/me надевает наушники' }, + [1] = { 'Снять наушники', 'cross', '/me снимает наушники' }, + }, + },{ + bodygroup = 11, + vals = { + [0] = { 'Надеть прозрачные очки', 'bullet_white', '/me надевает прозрачные тактические очки на глаза' }, + [1] = { 'Надеть затемненные очки', 'bullet_black', '/me надевает затемненные тактические очки на глаза' }, + [2] = { 'Снять очки', 'cross', '/me снимает тактические очки с глаз' }, + }, + },{ + bodygroup = 12, + vals = { + [0] = { 'Надеть камеру на шлем', 'bullet_blue', '/me надевает камеру на шлем' }, + [1] = { 'Снять камеру со шлема', 'cross', '/me снимает камеру со шлема' }, + }, + }, + }, +} + +local everyday_maleBgs = { + [3] = { + name = 'Тактический пояс', + vals = { + {'Нет', 0}, + {'Пустой', 1}, + {'Со значком без пистолета', 2}, + {'Без значка и с пистолетом', 3}, + {'Со значком и с пистолетом', 4}, + }, + }, + [4] = { + name = 'Бронежилет', + }, + [5] = { + name = 'Головной убор', + }, + [6] = { + name = 'Очки', + }, +} + +local everyday_femaleBgs = { + [4] = { + name = 'Головной убор', + }, + [5] = { + name = 'Очки', + }, +} + +local patrolBgs_hatAll = { + [3] = { + name = 'Бронежилет', + }, + [4] = { + name = 'Головной убор', + vals = { + {'Нет', 0}, + {'Кепка', 1}, + {'Фуражка', 2}, + }, + } +} + +local utilityBgs = { + [1] = { + name = 'Верхняя одежда', + vals = { + {'Утилитарная с длинным рукавом', 5}, + {'Утилитарная с коротким рукавом', 7}, + }, + }, + [2] = { + name = 'Головной убор', + vals = { + {'Нет', 0}, + {'Фуражка', 1}, + {'Кепка', 2}, + {'Шапка', 3}, + }, + }, + [5] = { + name = 'Снять бодикамеру', + }, + [6] = { + name = 'Перчатки', + vals = { + {'Нет', 0}, + {'Черные', 1}, + {'Белые', 2}, + {'Голубые нитриловые', 3}, + }, + }, + [8] = { + name = 'Жилет', + vals = { + {'Нет', 0}, + {'Бронежилет', 1}, + {'Светоотражающий жилет', 2}, + }, + }, +} +local utilityBgs_req = {[3] = 1, [7] = 1} + +local patrolBgs_hatAll_male = { + [1] = { + name = 'Верхняя одежда', + vals = { + {'Рубашка с галстуком', 0}, + {'Рубашка с длинным рукавом', 1}, + {'Рубашка с коротким рукавом', 2}, + {'Легкая куртка', 3}, + {'Зимняя куртка', 4}, + }, + }, + [2] = { + name = 'Головной убор', + vals = { + {'Нет', 0}, + {'Фуражка', 1}, + {'Кепка', 2}, + {'Шапка', 3}, + }, + }, + [3] = { + name = 'Тактический пояс', + }, + [5] = { + name = 'Снять бодикамеру', + }, + [6] = { + name = 'Перчатки', + vals = { + {'Нет', 0}, + {'Черные', 1}, + {'Белые', 2}, + {'Голубые нитриловые', 3}, + }, + }, + [8] = { + name = 'Жилет', + vals = { + {'Нет', 0}, + {'Бронежилет', 1}, + {'Светоотражающий жилет', 2}, + }, + }, +} + +local swatNames = { + {'Westbrook', 0}, + {'Sandstorm', 1}, + {'Moore', 2}, + {'Miller', 3}, + {'Bartels', 4}, + {'Marler', 5}, + {'Mckenney', 6}, + {'Rain', 7}, + {'Thompson', 8}, + {'Rose', 9}, + {'Cramble', 10}, + {'Phillips', 11}, + {'Nelson', 12}, + {'Brown', 13}, + {'Jenkins', 14}, + {'Grace', 15}, + {'Campbell', 16}, + {'Hicks', 17}, + {'Ryan', 18}, + {'Hardy', 19}, + {'Krawler', 20}, + {'Shearman', 22}, + {'Stewart', 23}, + {'Campo', 24}, + {'Без имени', 26}, +} + +local swatBgs = { + [7] = { + name = 'Позывной', + vals = swatNames, + }, + [13] = { + name = 'Патч на шлеме', + vals = { + {'Без патча', 0}, + {'Патч "Punisher Thin Blue Line"', 1}, + {'Патч 715 Team', 2}, + {'Патч "Bang one, bang em\' all"', 3}, + {'Патч "My idea of help"', 4}, + {'Патч "Respect all, fear none', 5}, + }, + }, + [11] = { + name = 'Очки', + vals = { + {'Прозрачные очки', 0}, + {'Затемненные очки', 1}, + {'Без очков', 2}, + }, + }, + [1] = { + name = 'Стандартный вверх', + }, + [2] = { + name = 'Стандартный низ', + }, + [3] = { + name = 'Засученные рукава', + }, + [4] = { + name = 'Тактические перчатки', + }, + [5] = { + name = 'Часы', + }, + [6] = { + name = 'Медицинский патч на разгрузку', + }, + [9] = { + name = 'Снять шлем', + }, + [10] = { + name = 'Снять наушники', + }, + [12] = { + name = 'Снять камеру на шлеме', + }, +} + +local swatSubMats = { + [4] = { + name = 'Цвет формы', + vals = { + {'Обычный', 'models/octo_swat_team/body_01'}, + {'Черный', 'models/octo_swat_team/body_01_black'}, + }, + }, + [5] = { + name = 'Цвет бронежилета', + vals = { + {'Обычный', 'models/octo_swat_team/armor'}, + {'Черный', 'models/octo_swat_team/armor_black'}, + }, + }, + [6] = { + name = 'Цвет нашивок', + vals = { + {'Обычный', 'models/octo_swat_team/patch'}, + {'Черный', 'models/octo_swat_team/patch_black'}, + }, + }, + [7] = { + name = 'Цвет штанов', + vals = { + {'Обычный', 'models/octo_swat_team/lowr_01'}, + {'Черный', 'models/octo_swat_team/lowr_01_black'}, + } + } +} + +local decBgs = { + [1] = { + name = 'Пояс', + vals = { + {'Полное вооружение', 0}, + {'Только фонарик и табель', 1}, + {'Только фонарик', 2}, + }, + }, + + [2] = { + name = 'Защита', + vals = { + {'Бронежилет детектива', 0}, + {'Бронежилет инспектора', 1}, + {'Бейдж инспектора', 2}, + {'Бейдж детектива', 3}, + } + }, + + [3] = { + name = 'Значок', + vals = { + {'Значок детектива', 0}, + {'Значок инспектора', 1}, + {'Нет', 2}, + } + }, +} +local decSkin = { + name = 'Одежда', + vals = { + {'Серая рубашка, черные брюки', 0}, + {'Черная рубашка, зеленые брюки', 1}, + {'Серая рубашка, синие брюки', 2}, + {'Серая рубашка, желтые брюки', 3}, + {'Голубая рубашка, черные брюки', 4}, + } +} +local civilSkins = { + name = 'Одежда', + vals = { + {'Коричневый пиджачок', 0}, + {'Синяя клетчатая рубашка с белыми рукавами', 2}, + {'Темно-синяя кофта с белыми рукавами', 3}, + {'Серый мешок, измазан дерьмом, джинсы тоже', 4}, + {'Красная рубашка с белыми рукавами, коричневые джинсы', 5}, + {'Бежевая кофта с логотипом, черные брюки', 6}, + {'Черная куртка с логотипом скелета, бежевые брюки', 7}, + {'Оранжевая рубашка с узором "Такси" и белыми рукавами, темные джинсы', 8}, + {'Черная кофта с белыми рукавами и перчатками', 9}, + {'Черная кофта с логотипом, перчатки', 10}, + {'Серая кофта в зеленую полоску и белыми рукавами', 11}, + {'Красная кофта, бежевые джинсы', 12}, + {'Черная куртка, белая майка, серые джинсы', 13}, + {'Зеленая кофта, светлые брюки', 14}, + {'Черная куртка, серая майка, светлые брюки', 15}, + {'Красная клетчатая рубашка с черными рукавами и перчатками', 16}, + {'Синяя спортивка', 17}, + {'Черная куртка, черная рубашка', 18}, + {'Салатовая куртка, белая майка, светлые брюки', 19}, + {'Синяя куртка, салатовая майка, рваные джинсы', 20}, + {'Черный пиджак, бежевая кофта, салатовая майка, светлые брюки', 21}, + {'Зеленая куртка, бордовая кофта, светлые брюки', 22}, + {'Синяя кофта в белую полоску и белыми рукавами', 23}, + } +} + +local dizcordumMale = { + [1] = { + name = 'Верх', + vals = { + {'Куртка болотного цвета', 0}, + {'Серая куртка, клетчатая рубашка', 1}, + {'Серая куртка, серая кофта с капюшоном', 2}, + {'Серо-синяя куртка, клетчатая рубашка, синий шарф', 3}, + {'Черно-бирюзовая куртка, рубашка в полоску, красный шарф', 4}, + {'Черно-красная куртка, серый свитер, синий шарф', 5}, + {'Серо-синяя куртка с воротником', 6}, + {'Черно-бирюзовая куртка с воротником', 7}, + {'Черно-красная куртка с воротником', 8}, + }, + }, + + [2] = { + name = 'Низ', + vals = { + {'Синие джинсы', 0}, + {'Темно-синие джинсы', 1}, + {'Серые джинсы', 2}, + {'Синие свободные джинсы', 3}, + {'Темные свободные джинсы', 4}, + {'Серые болоневые штаны', 5}, + {'Синие болоневые штаны', 6}, + {'Серые рабочие брюки', 7}, + {'Камуфляжные рабочие брюки', 8}, + }, + }, + + [3] = { + name = 'Перчатки', + }, +} + +local dizcordumFemale = { + [1] = { + name = 'Верх', + vals = { + {'Куртка болотного цвета', 0}, + {'Бирюзовая куртка, расстегнута', 1}, + {'Бирюзовая куртка, застегнута', 5}, + {'Синяя куртка, расстегнута', 2}, + {'Синяя куртка, застегнута', 4}, + {'Красная куртка, расстегнута', 3}, + {'Красная куртка, застегнута', 6}, + }, + }, + + [2] = { + name = 'Низ', + vals = { + {'Синие джинсы + Ботинки', 0}, + {'Брюки + Сапоги', 1}, + {'Штаны с полоской + Ботинки', 2}, + {'Синие испачканные штаны + Ботинки', 3}, + {'Серые испачканные штаны + Ботинки', 4}, + {'Серые болоневые штаны', 5}, + {'Светлые болоневые штаны', 6}, + {'Серые рабочие брюки', 7}, + }, + }, + + [3] = { + name = 'Перчатки' + }, +} + +for i = 1, 9 do + patrolModels[#patrolModels + 1] = { + name = '%s ' .. i, + male = true, + model = ('models/player/octo_dpd/male_%02i.mdl'):format(i), + bgs = patrolBgs_hatAll_male, + } +end +for i = 1, 9 do + utilityModels[#utilityModels + 1] = { + name = '%s (утилитарная) ' .. i, + male = true, + model = ('models/player/octo_dpd/male_%02i.mdl'):format(i), + bgs = utilityBgs, + requiredBgs = utilityBgs_req, + } +end + +local skins = {[1] = 2, [2] = 2, [3] = 6, [4] = 3, [5] = 4, [6] = 4, [7] = 5, [8] = 4, [9] = 3} +for i = 1, 9 do + + everydayModels[#everydayModels + 1] = { + name = 'Повседневная форма ' .. i, + model = ('models/dpdac/male_%02i_01.mdl'):format(i), + male = true, + bgs = everyday_maleBgs, + everyday = true, + } + decModels[#decModels + 1] = { + name = 'Детектив ' .. i, + model = ('models/kerry/detective_dbg/male_%02i.mdl'):format(i), + male = true, + bgs = decBgs, + skin = decSkin, + } + swatModels[#swatModels + 1] = { + name = 'Форма ' .. i, + male = true, + model = ('models/player/octo_swat_team/male_%02i.mdl'):format(i), + bgs = swatBgs, + skin = { + name = 'Внешность', + vals = {}, + }, + subMaterials = swatSubMats, + } + + for n = 0, skins[i] do + swatModels[i].skin.vals[#swatModels[i].skin.vals + 1] = { 'Внешность ' .. (n + 1), n } + end + +end + +for num, i in ipairs({ 1, 2, 3, 4, 6, 7 }) do + patrolModels[#patrolModels + 1] = { + name = '%s ' .. num, + male = false, + model = ('models/humans/dpdfem/female_%02i.mdl'):format(i), + bgs = patrolBgs_hatAll, + } + everydayModels[#everydayModels + 1] = { + name = 'Повседневная форма ' .. num, + model = ('models/humans/dpdfeminv/female_%02i.mdl'):format(i), + male = false, + bgs = everyday_femaleBgs, + everyday = true, + } + decModels[#decModels + 1] = { + name = 'Детектив ' .. num, + model = ('models/humans/dpdfeminv/female_%02i.mdl'):format(i), + male = false, + bgs = { + [4] = { + name = 'Головной убор', + }, + [5] = { + name = 'Очки', + }, + }, + requiredSkin = 4, + } +end + +for num, i in ipairs({1, 2, 4, 6}) do + decModels[#decModels + 1] = { + name = 'Офисный работник ' .. num, + model = ('models/humans/dpdfemsuits/female_%02i.mdl'):format(i), + male = false, + skin = { + name = 'Цвет пиджака', + vals = { + {'Темный', 0}, + {'Синий', 1}, + }, + }, + } +end + +for i = 1, 8 do + decModels[#decModels + 1] = { + name = 'Теплая одежда ' .. i, + male = true, + model = ('models/dizcordum/citizens/playermodels/pm_male_%02i.mdl'):format(i), + bgs = dizcordumMale, + } +end + +for i, v in ipairs({'p_female_01', 'p_female_02', 'p_female_03', 'p_female_04', 'p_female_06', 'p_female_05'}) do + decModels[#decModels + 1] = { + name = 'Теплая одежда ' .. i, + male = false, + model = 'models/dizcordum/citizens/playermodels/' .. v .. '.mdl', + bgs = dizcordumFemale, + } +end + +for i, v in ipairs({'07_01', '07_02', '07_06', '09_03', '06_04', '01_02', '08_01', '08_02', '08_03', '08_04'}) do + decModels[#decModels + 1] = { + name = 'Гражданский ' .. i, + male = true, + model = 'models/humans/octo/male_' .. v .. '.mdl', + skin = civilSkins, + } +end + +decModels[#decModels + 1] = { + name = 'Гражданский 1', + male = false, + model = 'models/humans/octo/female_01.mdl', + skin = { + name = 'Верх', + vals = { + {'Коричневое пальто и темные джинсы', 0}, + {'Темно-синяя куртка на молнии', 1}, + {'Зеленая клетчатая рубашка', 2}, + {'Светло-зеленая куртка на пуговицах', 3}, + {'Синяя кофта', 4}, + {'Коричневый худи с белыми шнурками', 5}, + {'Голубая спортивка', 6}, + {'Зеленая кофта с белым поясом', 7}, + {'Белая кофта с черным рисунком зайца', 8}, + {'Кофта "Hello Kitty"', 9}, + {'Красная полосатая куртка', 10}, + {'Сиреневая куртка', 11}, + {'Бордовое пальто', 12}, + {'Коричневая кожаная куртка', 13}, + {'Жилетка цвета хаки', 14}, + }, + }, +} + +decModels[#decModels + 1] = { + name = 'Гражданский 2', + male = false, + model = 'models/humans/octo/female_02.mdl', + skin = { + name = 'Верх', + vals = { + {'Белая клетчатая рубашка', 0}, + {'Куртка армейской расцветки', 1}, + {'Серая куртка на молнии', 2}, + {'Куртка кирпичного цвета', 3}, + {'Красная кофта', 4}, + {'Джинсовка', 5}, + {'Голубая спортивка', 7}, + {'Зеленая кофта с белым поясом', 8}, + {'Белая кофта с черным рисунком зайца', 9}, + {'Кофта "Hello Kitty"', 10}, + {'Красная полосатая куртка', 11}, + {'Сиреневая куртка', 12}, + {'Серое пальто', 13}, + {'Черная кожаная куртка', 14}, + }, + }, +} + +local function getBadgeBg(base) + return { + [10] = { + name = 'Жетон', + vals = { {'Без ленты', base}, {'С лентой', base+9}, {'Снять', 18} }, + } + } +end +local function cadetBgs() -- IN HONOR OF KILO-7!!!!!!!!!!!!!!!!!!! + return { + [10] = { + name = 'Жетон', + vals = { {'Без ленты', 0}, {'С лентой', 9}, {'Снять', 18} }, + }, + [1] = { + name = 'Верхняя одежда', + vals = { + {'Рубашка с галстуком', 0}, + {'Легкая куртка', 3}, + {'Зимняя куртка', 4}, + }, + }, + } +end + +local serviceStrip = { + [12] = { + name = 'Полоски выслуги лет', + vals = { + {'Нет', 0}, + {'5 лет', 1}, + {'10 лет', 2}, + {'15 лет', 3}, + {'20 лет', 4}, + {'25 лет', 5}, + {'30 лет', 6}, + {'35 лет', 7}, + {'40 лет', 8}, + } + } +} +local function patrolBgs(rank, customBadges) + return table.Merge(customBadges and table.Copy(customBadges) or getBadgeBg(rank), serviceStrip) +end + +local swatBadges = { + [10] = { + name = 'Жетон', + vals = { + {'Жетон офицера', 0}, + {'Жетон сержанта', 2}, + {'Жетон лейтенанта', 3}, + }, + } +} +local swatUtilityRanks = { + [11] = { + name = 'Шеврон', + vals = { + {'Нет', 0}, + {'Офицер полиции', 1}, + {'Старший офицер полиции', 2}, + {'Сержант I квалификации', 3}, + {'Сержант II квалификации', 4}, + } + } +} +local swatBadgesVals = swatBadges[10].vals +for i = 1, 3 do + swatBadgesVals[#swatBadgesVals + 1] = {swatBadgesVals[i][1] .. ' с лентой', swatBadgesVals[i][2]+9} +end +swatBadgesVals[#swatBadgesVals + 1] = {'Снять', 18} + +local swatUtilityTop = { + [1] = { + name = 'Верхняя одежда', + vals = { + {'Утилитарная с длинным рукавом', 6}, + {'Утилитарная с коротким рукавом', 8}, + }, + }, +} +local function getUtilityModels(val, customBadges) + local result = addBgs(addRequiredBgs(utilityModels, {[13] = val}), customBadges and table.Copy(customBadges) or getBadgeBg(0)) + if val == 2 then + result = addBgs(addRequiredBgs(result, {[7] = 2}), table.Merge(table.Copy(swatUtilityTop), table.Copy(swatUtilityRanks))) + end + return result +end + +local function highRankBg(val) + return { + [11] = { + name = 'Воротник застегнут?', + vals = { + {'Да', val}, + {'Нет', val+6}, + }, + } + } +end + +simpleOrgs.addOrg('dpd', { + name = 'Полицейский департамент', + title = 'Работа в Полицейском департаменте', + shortTitle = 'Работа в DPD', + team = 'dpd', + police = true, + clothes = clothesData, + talkieFreq = 'ems', + rankOrder = { 'cad', 'ppo', 'po', 'spo', 'swat', 'dec1', 'se1', 'se2', 'dec2', 'dec3', 'lie', 'cap', 'cmd', 'asch', 'asc', 'chi' }, + multirank = true, + ranks = { + cad = { -- Кадет + shortName = 'Кадет', + name = 'Кадет полиции', + mdls = everydayModels, + icon = octolib.icons.silk16('shield_delete'), + skin = 0, + weps = {'weapon_octo_air_glock17', 'weapon_octo_air_m4a1'}, + excludeWeps = {'weapon_octo_glock17'}, + }, + ppo = { -- Стажер + shortName = 'Стажер', + name = 'Стажер полиции', + mdls = addAll(addBgs(patrolModels, cadetBgs()), everydayModels), + icon = octolib.icons.silk16('shield'), + everydaySkin = 2, + }, + po = { -- Офицер + shortName = 'Офицер', + name = 'Офицер полиции', + mdls = addAll(addBgs(addRequiredBgs(patrolModels, {[11]=1}), patrolBgs(0)), addRequiredBgs(getUtilityModels(1), {[11]=1}), everydayModels), + icon = octolib.icons.silk16('shield'), + everydaySkin = 2, + }, + spo = { -- Старший офицер + shortName = 'Старший офицер', + name = 'Старший офицер полиции', + mdls = addAll(addBgs(addRequiredBgs(patrolModels, {[11]=2}), patrolBgs(0)), addRequiredBgs(getUtilityModels(1), {[11]=2}), everydayModels), + icon = octolib.icons.silk16('shield'), + everydaySkin = 2, + }, + swat = { -- Офицер спецназа + shortName = 'Офицер спецназа', + name = 'Офицер спецназа', + icon = octolib.icons.silk16('lightning'), + mdls = addAll(swatModels, addBgs(patrolModels, patrolBgs(nil, swatBadges)), getUtilityModels(2, swatBadges), everydayModels), + armor = function(ply) + if string.StartWith(ply:GetModel(), 'models/player/octo_swat_team/') then + return 100 + end + end, + weps = {'weapon_octo_m4a1', 'weapon_octo_xm1014', 'weapon_octo_g3sg1', 'weapon_octo_awp', 'weapon_octo_beanbag', 'weapon_octo_p90', 'door_ram', 'dbg_shield'}, + excludeWeps = {'dbg_speedometer'}, + everydaySkin = 3, + }, + dec1 = { -- Детектив I + shortName = 'Детектив I квалификации', + name = 'Детектив полиции I квалификации', + mdls = addAll(decModels, addBgs(addRequiredBgs(patrolModels, {[11]=5}), patrolBgs(1)), addRequiredBgs(getUtilityModels(1), {[11]=5}), everydayModels), + icon = octolib.icons.silk16('shield'), + weps = {'weapon_flashlight_uv'}, + everydaySkin = 2, + }, + se1 = { -- Сержант I + shortName = 'Сержант I квалификации', + name = 'Сержант полиции I квалификации', + mdls = addAll(addBgs(addRequiredBgs(patrolModels, {[11]=3}), patrolBgs(2)), addRequiredBgs(getUtilityModels(1), {[11]=3}), everydayModels), + icon = octolib.icons.silk16('shield'), + everydaySkin = 2, + }, + se2 = { -- Сержант II + shortName = 'Сержант II квалификации', + name = 'Сержант полиции II квалификации', + mdls = addAll(addBgs(addRequiredBgs(patrolModels, {[11]=4}), patrolBgs(2)), addRequiredBgs(getUtilityModels(1), {[11]=4}), everydayModels), + icon = octolib.icons.silk16('shield'), + everydaySkin = 2, + }, + dec2 = { -- Детектив II + shortName = 'Детектив II квалификации', + name = 'Детектив полиции II квалификации', + mdls = addAll(decModels, addBgs(addRequiredBgs(patrolModels, {[11]=6}), patrolBgs(1)), addRequiredBgs(getUtilityModels(1), {[11]=6}), everydayModels), + icon = octolib.icons.silk16('shield'), + weps = {'weapon_flashlight_uv'}, + everydaySkin = 2, + }, + dec3 = { -- Детектив III + shortName = 'Детектив III квалификации', + name = 'Детектив полиции III квалификации', + mdls = addAll(decModels, addBgs(addRequiredBgs(patrolModels, {[11]=7}), patrolBgs(1)), addRequiredBgs(getUtilityModels(1), {[11]=7}), everydayModels), + icon = octolib.icons.silk16('shield'), + weps = {'weapon_flashlight_uv'}, + everydaySkin = 2, + }, + lie = { -- Лейтенант + shortName = 'Лейтенант', + name = 'Лейтенант полиции', + mdls = addAll(addBgs(patrolModels, table.Merge(highRankBg(8), patrolBgs(3))), everydayModels), + icon = octolib.icons.silk16('shield_add'), + everydaySkin = 2, + }, + cap = { -- Капитан + shortName = 'Капитан', + name = 'Капитан полиции', + mdls = addAll(addBgs(patrolModels, table.Merge(highRankBg(9), patrolBgs(4))), everydayModels), + icon = octolib.icons.silk16('shield_add'), + everydaySkin = 2, + }, + cmd = { -- Командующий + shortName = 'Командующий', + name = 'Командующий полиции', + mdls = addAll(addBgs(patrolModels, table.Merge(highRankBg(10), patrolBgs(5))), everydayModels), + icon = octolib.icons.silk16('shield_add'), + everydaySkin = 2, + }, + asch = { -- Помощник заместителя шефа + shortName = 'Помощник заместителя шефа', + name = 'Помощник заместителя шефа полиции', + mdls = addAll(addBgs(patrolModels, table.Merge(highRankBg(11), patrolBgs(6))), everydayModels), + icon = octolib.icons.silk16('shield_add'), + everydaySkin = 2, + }, + asc = { -- Заместитель шефа + shortName = 'Заместитель шефа', + name = 'Заместитель шефа полиции', + mdls = addAll(addBgs(patrolModels, table.Merge(highRankBg(12), patrolBgs(7))), everydayModels), + icon = octolib.icons.silk16('shield_add'), + everydaySkin = 2, + }, + chi = { -- Шеф департамента + shortName = 'Шеф департамента', + name = 'Шеф департамента полиции', + mdls = addAll(addBgs(patrolModels, table.Merge(highRankBg(13), patrolBgs(8))), everydayModels), + icon = octolib.icons.silk16('shield_add'), + everydaySkin = 2, + }, + } +}) + +hook.Add('EntityBodygroupChange', 'dbg-orgs.swat', function(ent, bgID, val) + if not ent:GetModel():find('octo_swat_team') then return end + timer.Simple(0, function() + if bgID == 9 and val == 1 then + ent:SetBodygroup(12, 1) + ent:SetBodygroup(13, 0) + ent:SetBodygroup(8, 26) + end + + if bgID == 1 and val == 1 then + ent:SetBodygroup(6, 0) + end + + if bgID == 7 then + ent:SetBodygroup(8, val) + end + end) +end) diff --git a/garrysmod/addons/_config/lua/config/groups/dpd/octoinv/items.lua b/garrysmod/addons/_config/lua/config/groups/dpd/octoinv/items.lua new file mode 100644 index 0000000..2403215 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/dpd/octoinv/items.lua @@ -0,0 +1,52 @@ +local function throw(ply, ent, expire) + local force = 300 + local pos, ang, vel = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_L_Hand') or 16) + local tr = util.TraceLine { start = ply:GetShootPos(), endpos = pos, filter = ply } + if tr.Hit then + pos = tr.HitPos + tr.HitNormal * 5 + vel = tr.HitNormal * force * 0.4 + else + vel = ply:GetAimVector() + vel = (vel + VectorRand() * math.random() * 0.1) * force + end + + ent:SetPos(pos) + ent:SetAngles(ang) + + ent:Spawn() + ent:Activate() + ent.owner = ply + ent.expire = expire + ply:LinkEntity(ent) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(vel) + end +end + + +octoinv.registerItem('spike_strips', { + name = 'Полицейские шипы', + icon = 'octoteam/icons/spike_strips.png', + mass = 1.5, + volume = 1.5, + model = 'models/props_junk/cardboard_box001a.mdl', + desc = 'Шипы, применяемые при погонях для быстрой остановки подозреваемого', + nostack = true, + use = { + function(ply, item) + if not (ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO) then return end + return 'Бросить', 'octoteam/icons/hand.png', function(ply, item) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_THROW) + timer.Simple(0.88, function() + local ent = ents.Create('ent_dbg_spike_strips') + if not IsValid(ent) then return end + throw(ply, ent, item:GetData('expire')) + end) + return 1 + end + end, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/elegant-security.lua b/garrysmod/addons/_config/lua/config/groups/elegant-security.lua new file mode 100644 index 0000000..9f79f13 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/elegant-security.lua @@ -0,0 +1,71 @@ +simpleOrgs.addOrg('elsec', { + name = 'Elegant Security', + title = 'Elegant Security', + shortTitle = 'Elegant Security', + team = 'elsec', + mdls = { + { + name = 'Сотрудник', + model = 'models/player/camo_09.mdl', + bgs = { + [1] = { + name = 'Базовый бронежилет', + }, + [2] = { + name = 'Кепка', + }, + [3] = { + name = 'Пояс', + }, + [5] = { + name = 'Полный бронежилет', + }, + [6] = { + name = 'Шлем', + } + }, + requiredBgs = {[4] = 1} + } + }, + talkieFreq = 'elsec', +}) + +local plateCol = { + bg = Color(30, 30, 30), + border = Color(255, 191, 0), + title = Color(255, 191, 0), + txt = Color(255, 191, 0), +} + +carDealer.addCategory('elsec', { + name = 'Elegant Security', + icon = 'icon16/lightning.png', + queue = true, + ems = true, + bulletproof = true, + doNotEvacuate = true, + spawns = carDealer.civilSpawns, + canUse = function(ply) return ply:Team() == TEAM_ELSEC, 'Доступно только Elegant Security' end, +}) + +carDealer.addVeh('elsec_moonbeam', { + name = 'Moonbeam', + simfphysID = 'sim_fphys_gta4_moonbeam', + plateCol = plateCol, + price = 12000, + deposit = true, + default = { + col = { Color(36,36,36), Color(36,36,36), Color(0,0,0), Color(36,36,36) }, + }, +}) + +carDealer.addVeh('elsec_premier', { + name = 'Premier', + simfphysID = 'sim_fphys_gta4_premier', + plateCol = plateCol, + price = 25000, + deposit = true, + default = { + col = { Color(36,36,36), Color(0,0,0), Color(0,0,0), Color(36,36,36) }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/fbi.lua b/garrysmod/addons/_config/lua/config/groups/fbi.lua new file mode 100644 index 0000000..7867419 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fbi.lua @@ -0,0 +1,131 @@ +local fbiSubMats = { + [27] = 'models/humans/male/group01/fbi', +} + +local models = octolib.table.mapSequential({ + 'models/humans/octo/male_01_01.mdl', + 'models/humans/octo/male_01_02.mdl', + 'models/humans/octo/male_01_03.mdl', + 'models/humans/octo/male_02_01.mdl', + 'models/humans/octo/male_02_02.mdl', + 'models/humans/octo/male_02_03.mdl', + 'models/humans/octo/male_03_01.mdl', + 'models/humans/octo/male_03_02.mdl', + 'models/humans/octo/male_03_03.mdl', + 'models/humans/octo/male_03_04.mdl', + 'models/humans/octo/male_03_05.mdl', + 'models/humans/octo/male_03_06.mdl', + 'models/humans/octo/male_03_07.mdl', + 'models/humans/octo/male_04_01.mdl', + 'models/humans/octo/male_04_02.mdl', + 'models/humans/octo/male_04_03.mdl', + 'models/humans/octo/male_04_04.mdl', + 'models/humans/octo/male_05_01.mdl', + 'models/humans/octo/male_05_02.mdl', + 'models/humans/octo/male_05_03.mdl', + 'models/humans/octo/male_05_04.mdl', + 'models/humans/octo/male_05_05.mdl', + 'models/humans/octo/male_06_01.mdl', + 'models/humans/octo/male_06_02.mdl', + 'models/humans/octo/male_06_03.mdl', + 'models/humans/octo/male_06_04.mdl', + 'models/humans/octo/male_06_05.mdl', + 'models/humans/octo/male_07_01.mdl', + 'models/humans/octo/male_07_02.mdl', + 'models/humans/octo/male_07_03.mdl', + 'models/humans/octo/male_07_04.mdl', + 'models/humans/octo/male_07_05.mdl', + 'models/humans/octo/male_07_06.mdl', + 'models/humans/octo/male_08_01.mdl', + 'models/humans/octo/male_08_02.mdl', + 'models/humans/octo/male_08_03.mdl', + 'models/humans/octo/male_08_04.mdl', + 'models/humans/octo/male_09_01.mdl', + 'models/humans/octo/male_09_02.mdl', + 'models/humans/octo/male_09_03.mdl', + 'models/humans/octo/male_09_04.mdl', +}, function(v, i) + return { + name = 'Внешность ' .. i, + male = true, + model = v, + requiredMats = fbiSubMats, + requiredSkin = 23, + } +end) + +simpleOrgs.addOrg('fbi', { + name = 'ФБР', + title = '', + shortTitle = '', + team = 'fbi', + mdls = models, + talkieFreq = 'fbi', +}) + +carDealer.addCategory('fbi', { + name = 'FBI', + icon = 'octoteam/icons-16/emotion_daddy_cool.png', + ems = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_FBI, 'Доступно только FBI' end, + spawns = carDealer.civilSpawns, + spawnCheck = carDealer.limitedSpawn(1, 'fbi', 'В городе уже есть автомобиль FBI, найди напарника или попробуй чуть позже'), + limitGroup = 'fbi', +}) + +local fbiPlateCol = { + bg = Color(75, 86, 208), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + txt = Color(255, 255, 255), +} + +carDealer.addVeh('fbi_admiral', { + name = 'Admiral', + simfphysID = 'sim_fphys_gta4_admiral', + price = 0, + police = true, + deposit = true, + default = { + col = { Color(30,30,30), Color(30,30,30), Color(0,0,0), Color(30,30,30) }, + }, + plateCol = {}, -- reset plates +}) + +carDealer.addVeh('fbi_emperor', { + name = 'Emperor', + simfphysID = 'sim_fphys_gta4_emperor', + price = 0, + police = true, + deposit = true, + default = { + col = { Color(30,30,30), Color(30,30,30), Color(0,0,0), Color(30,30,30) }, + }, + plateCol = {}, -- reset plates +}) + +carDealer.addVeh('fbi_manana', { + name = 'Manana', + simfphysID = 'sim_fphys_gta4_manana', + price = 0, + police = true, + deposit = true, + default = { + col = { Color(30,30,30), Color(30,30,30), Color(0,0,0), Color(30,30,30) }, + bg = { [1] = 1 }, + }, + plateCol = {}, -- reset plates +}) + +carDealer.addVeh('fbi_buffalo', { + name = 'Buffalo', + simfphysID = 'sim_fphys_gta4_fbi', + price = 0, + police = true, + deposit = true, + default = { + col = { Color(30,30,30), Color(30,30,30), Color(0,0,0), Color(30,30,30) }, + }, + plateCol = fbiPlateCol, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/fire/cars.lua b/garrysmod/addons/_config/lua/config/groups/fire/cars.lua new file mode 100644 index 0000000..9b6596a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fire/cars.lua @@ -0,0 +1,123 @@ +carDealer.addCategory('fire', { + name = 'Пожарные', + icon = octolib.icons.silk16('fire'), + ems = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_FIREFIGHTER and ply:GetActiveRank('fire') ~= 'cad' or false, 'Доступно только пожарным' end, + spawns = { + rp_evocity_dbg_220222 = { + { Vector(-3650, -7671, 265), Angle(0,180,0) }, + { Vector(-3650, -7953, 265), Angle(0,180,0) }, + { Vector(-3650, -8245, 265), Angle(0,180,0) }, + }, + rp_eastcoast_v4c = carDealer.civilSpawns.rp_eastcoast_v4c, + rp_truenorth_v1a = { + { Vector(13090, 12190, 75), Angle(0, 180, 0) }, + { Vector(13090, 11926, 75), Angle(0, 180, 0) }, + { Vector(13090, 11680, 75), Angle(0, 180, 0) }, + { Vector(13090, 11421, 75), Angle(0, 180, 0) }, + }, + rp_riverden_dbg_220313 = { + { Vector(-12195.1, 1425.92, -202.995), Angle(-0, 90, 0) }, + { Vector(-12446.4, 1424.97, -203.212), Angle(-0, 90, 0) }, + }, + }, + spawnCheck = carDealer.limits.fire, + limitGroup = 'fire', +}) + +local seniorsStaff = octolib.array.toKeys {'lie', 'cap', 'bc', 'rchi', 'ass', 'dep', 'chi'} +local highStaff = octolib.array.toKeys {'bc', 'rchi', 'ass', 'dep', 'chi'} + +local function isSeniors(ply) + return seniorsStaff[ply:GetActiveRank('fire') or ''] or false, 'Доступно только старшему пожарному составу' +end + +local function isHigh(ply) + return highStaff[ply:GetActiveRank('fire') or ''] or false, 'Доступно только высшему пожарному составу' +end + +local plateCol = { + bg = Color(255, 54, 54), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + txt = Color(255, 255, 255), +} + +carDealer.addVeh('fireambulance', { + category = 'fire', + name = 'Ambulance', + simfphysID = 'sim_fphys_gta4_ambulance', + price = 0, + deposit = true, + customFOV = 42, + default = { + col = { Color(255,255,255), Color(255,0,0), Color(0,0,0), Color(255,255,255) }, + bg = { [1] = 1 }, + }, + radioWhitelist = carDealer.emsRadioStations, +}) + +carDealer.addVeh('firetruck', { + category = 'fire', + name = 'Fire Truck', + simfphysID = 'sim_fphys_gta4_firetruk', + price = 0, + deposit = true, + customFOV = 45, + plateCol = plateCol, + radioWhitelist = carDealer.emsRadioStations, +}) + +carDealer.addVeh('ladder', { + name = 'Fire Truck Ladder', + simfphysID = 'sim_fphys_l4d_fire_truck', + price = 0, + deposit = true, + customFOV = 52, + plateCol = plateCol, + radioWhitelist = carDealer.emsRadioStations, +}) + +carDealer.addVeh('battalion', { + name = 'Battalion', + simfphysID = 'sim_fphys_gta4_battalion', + price = 0, + deposit = true, + customFOV = 35, + plateCol = plateCol, + radioWhitelist = carDealer.emsRadioStations, + canUse = isHigh, + canSee = function(ply) + return highStaff[ply:GetActiveRank('fire') or ''] or false + end, +}) + +carDealer.addVeh('battalion_inv', { + name = 'Battalion Investigator', + simfphysID = 'sim_fphys_gta4_battalion', + price = 0, + deposit = true, + customFOV = 35, + plateCol = plateCol, + radioWhitelist = carDealer.emsRadioStations, + canUse = isSeniors, + canSee = function(ply) + return seniorsStaff[ply:GetActiveRank('fire') or ''] or false + end, + default = { + mats = { + [4] = 'octoteam/models/vehicles/landstalker/landstalkerdfd_exterior_invest', + }, + }, +}) + +-- carDealer.addVeh('fireranch', { +-- category = 'fire', +-- name = 'Fire Rancher', +-- simfphysID = 'simfphys_gta_sa_fdranch', +-- price = 0, +-- deposit = true, +-- plateCol = plateCol, +-- radioWhitelist = carDealer.emsRadioStations, +-- }) diff --git a/garrysmod/addons/_config/lua/config/groups/fire/cmenu/categories/fire.lua b/garrysmod/addons/_config/lua/config/groups/fire/cmenu/categories/fire.lua new file mode 100644 index 0000000..bd357fc --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fire/cmenu/categories/fire.lua @@ -0,0 +1 @@ +octogui.cmenu.registerCategory('fire') \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/fire/cmenu/items/fire.lua b/garrysmod/addons/_config/lua/config/groups/fire/cmenu/items/fire.lua new file mode 100644 index 0000000..a265e70 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fire/cmenu/items/fire.lua @@ -0,0 +1,37 @@ +local function checkUniform(ply) + if not ply:GetModel():find('models/gta5/fire') then return end + local bgs = {} + for i, v in ipairs(ply:GetBodyGroups()) do + local id = v.id + bgs[id] = ply:GetBodygroup(id) + end + return bgs[2] == 1 +end + +octogui.cmenu.registerItem('fire', 'lightbulb', { + icon = octolib.icons.silk16('lightbulb'), + text = function(ply) + local mask = ply:GetNetVar('fire.flashlight') + return ('%s фонарик'):format(mask and 'Выключить' or 'Включить') + end, + check = function(ply) + return ply:Team() == TEAM_FIREFIGHTER and checkUniform(ply) + end, + action = function() + netstream.Start('fire.flashlight', LocalPlayer()) + end, +}) + +octogui.cmenu.registerItem('fire', 'scba', { + icon = octolib.icons.silk16('cancel'), + text = function(ply) + local mask = ply:GetNetVar('fire.scba') + return ('%s кнопку тревоги'):format(mask and 'Выключить' or 'Включить') + end, + check = function(ply) + return ply:Team() == TEAM_FIREFIGHTER and checkUniform(ply) + end, + action = function() + netstream.Start('fire.scba', LocalPlayer()) + end, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/fire/cmenu/server.lua b/garrysmod/addons/_config/lua/config/groups/fire/cmenu/server.lua new file mode 100644 index 0000000..5cc3f91 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fire/cmenu/server.lua @@ -0,0 +1,77 @@ +local SwitchSound = Sound('HL2Player.FlashLightOn') + +local function killLight(ply) + if IsValid(ply.projectedlight) then + SafeRemoveEntity(ply.projectedlight) + end +end + +local function createFlashlight(ply) + if IsValid(ply.projectedlight) then return end + local light = ents.Create('env_projectedtexture') + light:SetParent(ply) + light:SetPos(ply:GetShootPos()) + light:SetAngles(ply:GetAngles()) + light:SetKeyValue('enableshadows', 1) + light:SetKeyValue('nearz', 7) + light:SetKeyValue('farz', 750.0) + light:SetKeyValue('lightcolor','255 255 255 255') + light:SetKeyValue('lightfov', 70) + light:Spawn() + light:Input('SpotlightTexture', NULL, NULL, 'effects/flashlight001') + light:Fire('setparentattachment','chest', 0.01) + ply.projectedlight = light +end + +local function checkUniform(ply) + if not ply:GetModel():find('models/gta5/fire') then return end + local bgs = {} + for i, v in ipairs(ply:GetBodyGroups()) do + local id = v.id + bgs[id] = ply:GetBodygroup(id) + end + return bgs[2] == 1 +end + +netstream.Hook('fire.scba', function(ply) + if not (ply:Team() == TEAM_FIREFIGHTER and checkUniform(ply)) then return end + if ply:GetNetVar('fire.scba') then + ply:SetNetVar('fire.scba') + if IsValid(ply.scba) then + ply.scba:Stop() + ply.scba = nil + end + return + end + ply:SetNetVar('fire.scba', true) + ply.scba = CreateSound(ply, 'scba.wav') + ply.scba:Play() +end) + +local function removeSCBA(ply) + if IsValid(ply) and ply:GetNetVar('fire.scba') and IsValid(ply.scba) then + ply.scba:Stop() + ply.scba = nil + ply:SetNetVar('fire.scba') + end +end + +hook.Add('PlayerDeath', 'fire.scba', removeSCBA) +hook.Add('PlayerSpawn', 'fire.scba', removeSCBA) + +netstream.Hook('fire.flashlight', function(ply) + if not (ply:Team() == TEAM_FIREFIGHTER and checkUniform(ply)) then return end + ply:EmitSound(SwitchSound) + if ply:GetNetVar('fire.flashlight') then + killLight(ply) + ply:SetNetVar('fire.flashlight') + else + createFlashlight(ply) + ply:SetNetVar('fire.flashlight', true) + end +end) + +hook.Add('PlayerDeath', 'fire.flashlight', killLight) +hook.Add('PlayerDisconnected', 'fire.flashlight', killLight) +hook.Add('PlayerSpawn', 'fire.flashlight', killLight) +hook.Add('simple-orgs.handOver', 'fire.flashlight', killLight) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/fire/init.lua b/garrysmod/addons/_config/lua/config/groups/fire/init.lua new file mode 100644 index 0000000..8a3d8bc --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fire/init.lua @@ -0,0 +1,3 @@ +octolib.shared('models') +octolib.shared('cars') +octolib.server('config/groups/fire/cmenu/server') \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/fire/models.lua b/garrysmod/addons/_config/lua/config/groups/fire/models.lua new file mode 100644 index 0000000..b9989d3 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/fire/models.lua @@ -0,0 +1,213 @@ +--[[ +РАНГИ: +cad. Пожарный кадет +tra. Пожарный стажер +ff1. Пожарный I +emt. EMT пожарных +ff2. Пожарный II +prm. Парамедик пожарных +lie. Пожарный лейтенант +cap. Пожарный капитан +bc. Пожарный командир батальона +rchi. Пожарный районный шеф +ass. Помощник заместителя шефа +dep. Заместитель пожарного шефа +chi. Шеф пожарного департамента +]] + +local clothesData = { + icon = 'user_firefighter', + ['models/gta5/fire'] = { + { + bodygroup = 1, + vals = { + [0] = { 'Надеть шлем', 'bullet_blue', '/me надевает шлем на голову' }, + [1] = { 'Снять шлем', 'cross', '/me снимает шлем с головы' }, + }, + },{ + bodygroup = 2, + vals = { + [0] = { 'Снять кислородный баллон', 'cross', '/me снимает кислородный баллон с плеч' }, + [1] = { 'Надеть кислородный баллон', 'bullet_blue', '/me надевает кислородный баллон на плечи' }, + }, + },{ + bodygroup = 3, + vals = { + [0] = { 'Снять кислородную маску', 'cross', '/me снимает кислородную маску с лица' }, + [1] = { 'Надеть кислородную маску', 'bullet_blue', '/me надевает кислородную маску на лицо' }, + }, + }, + }, + ['models/taggart/police02/'] = { + { + bodygroup = 1, + vals = { + [0] = { 'Надеть кепку', 'bullet_blue', '/me надевает кепку на голову' }, + [1] = { 'Снять кепку', 'cross', '/me снимает кепку с головы' }, + }, + }, + }, +} + +local everydayMats = { + [4] = 'models/taggart/police02/securityguard1', +} + +local everydayMats_hq = { + [4] = 'models/taggart/police02/securityguard2', +} + +local firemenBgs = { + [1] = { + name = 'Снять шлем', + }, + [2] = { + name = 'Газовый баллон', + }, + [3] = { + name = 'Маска', + }, +} + +local models = {} +for i = 1, 9 do + models[#models + 1] = { + name = 'Офисная форма ' .. i, + model = ('models/taggart/police02/male_%02i.mdl'):format(i), + unisex = true, + everyday = true, + bgs = { + [1] = { + name = 'Снять кепку', + }, + }, + } + + if i > 7 then continue end + models[#models + 1] = { + name = 'Пожарный ' .. i, + model = ('models/gta5/fire%s.mdl'):format(i), + unisex = true, + bgs = firemenBgs, + } +end + +simpleOrgs.addOrg('fire', { + name = 'Пожарный Департамент', + title = 'Работа в Пожарном Департаменте', + shortTitle = 'Работа в Департаменте', + team = 'firefighter', + clothes = clothesData, + talkieFreq = 'ems', + rankOrder = {'cad', 'tra', 'ff1', 'emt', 'ff2', 'prm', 'lie', 'cap', 'bc', 'rchi', 'ass', 'dep', 'chi'}, + multirank = true, + ranks = { + cad = { -- Cadet + shortName = 'Кадет', + name = 'Пожарный кадет', + mdls = models, + everydayMats = everydayMats, + icon = octolib.icons.silk16('user_firefighter'), + skin = 2, + }, + tra = { -- Trainee + shortName = 'Стажер', + name = 'Пожарный стажер', + mdls = models, + everydayMats = everydayMats, + icon = octolib.icons.silk16('user_firefighter'), + skin = 2, + }, + ff1 = { -- FireFighter I + shortName = 'Пожарный I', + name = 'Пожарный I', + mdls = models, + everydayMats = everydayMats, + icon = octolib.icons.silk16('user_firefighter'), + skin = 0, + }, + emt = { -- Fire EMT + shortName = 'EMT', + name = 'EMT пожарных', + mdls = models, + everydayMats = everydayMats, + icon = octolib.icons.silk16('user_medical'), + skin = 0, + }, + ff2 = { -- FireFighter II + shortName = 'Пожарный II', + name = 'Пожарный II', + mdls = models, + everydayMats = everydayMats, + icon = octolib.icons.silk16('user_firefighter'), + skin = 0, + }, + prm = { -- Paramedic + shortName = 'Парамедик', + name = 'Парамедик пожарных', + mdls = models, + everydayMats = everydayMats, + icon = octolib.icons.silk16('user_medical'), + skin = 0, + }, + lie = { -- Lieutenant + shortName = 'Лейтенант', + name = 'Пожарный лейтенант', + mdls = models, + everydayMats = everydayMats_hq, + icon = octolib.icons.silk16('user_firefighter'), + skin = 1, + }, + cap = { -- Captain + shortName = 'Капитан', + name = 'Пожарный капитан', + mdls = models, + everydayMats = everydayMats_hq, + icon = octolib.icons.silk16('user_firefighter'), + skin = 1, + }, + bc = { -- Battalion Commander + shortName = 'Шеф батальона', + name = 'Пожарный шеф батальона', + mdls = models, + icon = octolib.icons.silk16('user_firefighter'), + skin = 3, + }, + rchi = { -- District Chief + shortName = 'Районный шеф', + name = 'Пожарный районный шеф', + mdls = models, + icon = octolib.icons.silk16('user_firefighter'), + skin = 3, + }, + ass = { -- Assistant Chief + shortName = 'Помощник заместителя шефа', + name = 'Помощник заместителя шефа', + mdls = models, + icon = octolib.icons.silk16('lightning'), + skin = 3, + }, + dep = { -- Deputy Chief + shortName = 'Заместитель пожарного шефа', + name = 'Заместитель пожарного шефа', + mdls = models, + icon = octolib.icons.silk16('user_firefighter'), + skin = 3, + }, + chi = { -- Chief + shortName = 'Шеф', + name = 'Шеф пожарного департамента', + mdls = models, + icon = octolib.icons.silk16('user_firefighter'), + skin = 3, + }, + }, +}) + +-- fuck that shit +local toChange = octolib.array.toKeys({'male_02.mdl','male_05.mdl','male_06.mdl','male_07.mdl','male_08.mdl'}) +hook.Add('EntitySubMaterialChange', 'dbg-orgs.fire', function(ent, index, mat) + local mdl = ent:GetModel() + if not mdl:find('taggart/police02') or not toChange[mdl:gsub('.+%/', '')] then return end + if index == 4 then ent:SetSubMaterial(5, mat) return true end +end) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/gov.lua b/garrysmod/addons/_config/lua/config/groups/gov.lua new file mode 100644 index 0000000..056fd9a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/gov.lua @@ -0,0 +1,76 @@ +local suit = 'models/humans/modern/octo/suit1_sheet' + +local models = octolib.table.mapSequential({ + { 'models/humans/octo/male_01_01.mdl', 3}, + { 'models/humans/octo/male_01_02.mdl', 3}, + { 'models/humans/octo/male_01_03.mdl', 3}, + { 'models/humans/octo/male_02_01.mdl', 2}, + { 'models/humans/octo/male_02_02.mdl', 2}, + { 'models/humans/octo/male_02_03.mdl', 2}, + { 'models/humans/octo/male_03_01.mdl', 4}, + { 'models/humans/octo/male_03_02.mdl', 4}, + { 'models/humans/octo/male_03_03.mdl', 4}, + { 'models/humans/octo/male_03_04.mdl', 4}, + { 'models/humans/octo/male_03_05.mdl', 4}, + { 'models/humans/octo/male_03_06.mdl', 4}, + { 'models/humans/octo/male_03_07.mdl', 4}, + { 'models/humans/octo/male_04_01.mdl', 4}, + { 'models/humans/octo/male_04_02.mdl', 4}, + { 'models/humans/octo/male_04_03.mdl', 4}, + { 'models/humans/octo/male_04_04.mdl', 4}, + { 'models/humans/octo/male_05_01.mdl', 4}, + { 'models/humans/octo/male_05_02.mdl', 4}, + { 'models/humans/octo/male_05_03.mdl', 4}, + { 'models/humans/octo/male_05_04.mdl', 4}, + { 'models/humans/octo/male_05_05.mdl', 4}, + { 'models/humans/octo/male_06_01.mdl', 4}, + { 'models/humans/octo/male_06_02.mdl', 4}, + { 'models/humans/octo/male_06_03.mdl', 4}, + { 'models/humans/octo/male_06_04.mdl', 4}, + { 'models/humans/octo/male_06_05.mdl', 4}, + { 'models/humans/octo/male_07_01.mdl', 4}, + { 'models/humans/octo/male_07_02.mdl', 4}, + { 'models/humans/octo/male_07_03.mdl', 4}, + { 'models/humans/octo/male_07_04.mdl', 4}, + { 'models/humans/octo/male_07_05.mdl', 4}, + { 'models/humans/octo/male_07_06.mdl', 4}, + { 'models/humans/octo/male_08_01.mdl', 0}, + { 'models/humans/octo/male_08_02.mdl', 0}, + { 'models/humans/octo/male_08_03.mdl', 0}, + { 'models/humans/octo/male_08_04.mdl', 0}, + { 'models/humans/octo/male_09_01.mdl', 2}, + { 'models/humans/octo/male_09_02.mdl', 2}, + { 'models/humans/octo/male_09_03.mdl', 2}, + { 'models/humans/octo/male_09_04.mdl', 2}, +}, function(v,i) + return { + name = 'Внешность ' .. i, + male = true, + model = v[1], + requiredMats = { [v[2]] = suit }, + } +end) + +simpleOrgs.addOrg('gov', { + name = 'Мэрия', + title = 'Работа в Мэрии', + shortTitle = 'Работа в Мэрии', + team = 'mayor', + talkieFreq = 'gov', + rankOrder = { 'worker', 'mayor' }, + multirank = true, + ranks = { + worker = { + shortName = 'Сотрудник', + name = 'Сотрудник Мэрии', + mdls = models, + icon = octolib.icons.silk16('draw_star'), + }, + mayor = { + shortName = 'Мэр', + name = 'Мэр', + mdls = models, + icon = octolib.icons.silk16('star'), + }, + } +}) diff --git a/garrysmod/addons/_config/lua/config/groups/medic.lua b/garrysmod/addons/_config/lua/config/groups/medic.lua new file mode 100644 index 0000000..4406307 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/medic.lua @@ -0,0 +1,115 @@ +local clothesData = { + icon = 'user_medical', + ['models/kerry/plats_medical_'] = { + { + bodygroup = 2, + vals = { + [0] = { 'Снять перчатки', 'cross', '/me снимает перчатки с рук' }, + [1] = { 'Надеть перчатки', 'bullet_blue', '/me надевает перчатки на руки' }, + }, + }, + }, +} + +local medicBgs = { + [1] = { + name = 'Форма', + vals = { + {'Парамедик', 0}, + {'Врач', 1}, + {'Хирург', 2}, + } + }, + + [2] = { + name = 'Перчатки', + }, +} + +local medics = {} +for i = 1, 9 do + medics[#medics + 1] = { + model = ('models/kerry/plats_medical_%02i.mdl'):format(i), + male = true, + name = 'Медик ' .. i, + bgs = medicBgs, + } + if i == 6 then continue end + medics[#medics + 1] = { + model = ('models/player/kerry/plats_medic/male_%02i_medic.mdl'):format(i), + male = true, + name = 'Тёплая форма ' .. i, + } +end +for i = 10, 11 do + medics[#medics + 1] = { + model = ('models/kerry/plats_medical_%02i.mdl'):format(i), + male = false, + name = 'Медик ' .. i, + bgs = medicBgs, + } +end +for i = 1, 6 do + medics[#medics + 1] = { + model = ('models/player/kerry/plats_medic/female_%02i_medic.mdl'):format(i), + male = false, + name = 'Тёплая форма ' .. i, + } +end + +simpleOrgs.addOrg('medic', { + name = 'Медицинский центр', + title = 'Работа в медицинском центре', + shortTitle = 'Работа в мед. центре', + team = 'paramedic', + mdls = medics, + clothes = clothesData, + talkieFreq = 'ems', +}) + +carDealer.addCategory('medic', { + name = 'Медики', + icon = octolib.icons.silk16('user_medical'), + ems = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_DOCTOR, 'Доступно только медикам' end, + spawns = { + rp_evocity_dbg_220222 = { + { Vector(-11295, 9344, 125), Angle(0,-90,0) }, -- hospital + { Vector(-11111, 9344, 125), Angle(0,-90,0) }, + }, + rp_eastcoast_v4c = carDealer.civilSpawns.rp_eastcoast_v4c, + rp_truenorth_v1a = { + { Vector(11667, 12990, 128), Angle(0, -90, 0) }, + { Vector(11876, 12990, 128), Angle(0, -90, 0) }, + }, + rp_riverden_dbg_220313 = { + { Vector(-5453.52, 1579.42, -220.133), Angle(-0, -90, 0) }, + { Vector(-5453.98, 2185.43, -219.886), Angle(-0, -90, 0) }, + }, + }, + spawnCheck = carDealer.limits.medic, + limitGroup = 'medic', +}) + +carDealer.addVeh('medicambulance', { + category = 'medic', + name = 'Ambulance', + simfphysID = 'sim_fphys_gta4_ambulance', + price = 0, + deposit = true, + customFOV = 42, + default = { + col = { Color(255,255,255), Color(23,11,192), Color(0,0,0), Color(255,255,255) }, + bg = { [1] = 1 }, + }, + radioWhitelist = carDealer.emsRadioStations, +}) + +hook.Add('OnPlayerChangedTeam', 'orgs.medic', function(ply, old, new) + local job = RPExtraTeams[old] + local mask = ply:GetNetVar('hMask') + if job and job.medic and mask and mask[1] == 'medical_mask' then + ply:SetNetVar('hMask', nil) + end +end) diff --git a/garrysmod/addons/_config/lua/config/groups/prison.lua b/garrysmod/addons/_config/lua/config/groups/prison.lua new file mode 100644 index 0000000..08a6be2 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/prison.lua @@ -0,0 +1,147 @@ +local clothesData = { + icon = 'user_police_england', + ['models/player/octo_guard/'] = { + { + bodygroup = 2, + vals = { + [0] = { 'Снять бронежилет', 'cross', '/me снимает бронежилет' }, + [1] = { 'Надеть бронежилет', 'bullet_blue', '/me надевает бронежилет на тело' }, + }, + },{ + bodygroup = 3, + vals = { + [0] = { 'Снять шлем', 'cross', '/me снимает шлем с головы' }, + [1] = { 'Надеть шлем', 'bullet_blue', '/me надевает шлем на голову' }, + }, + }, + }, +} + +local maleBgs = { + [1] = { + name = 'Звание', + vals = { + {'Нет', 4}, + {'Офицер I', 0}, + {'Офицер II', 1}, + {'Офицер III', 2}, + {'Сержант', 3}, + }, + }, + [2] = { + name = 'Бронежилет', + }, + [3] = { + name = 'Шлем', + }, + [4] = { + name = 'Короткие рукава', + }, +} + +local femaleBgs = { + [1] = { + name = 'Звание', + vals = { + {'Нет', 4}, + {'Офицер I', 0}, + {'Офицер II', 1}, + {'Офицер III', 2}, + {'Сержант', 3}, + }, + }, + [2] = { + name = 'Бронежилет', + }, + [3] = { + name = 'Шлем', + }, +} + +local models = {} +for i = 1, 9 do + models[#models + 1] = { + name = 'Форма ' .. i, + male = true, + model = ('models/player/octo_guard/male_%02i.mdl'):format(i), + bgs = maleBgs, + } + if i >= 7 then continue end + models[#models + 1] = { + name = 'Форма ' .. i, + male = false, + model = ('models/player/octo_guard/female_%02i.mdl'):format(i), + bgs = femaleBgs, + } +end + +simpleOrgs.addOrg('prison', { + name = 'Тюрьма', + title = 'Тюрьма', + shortTitle = 'Тюрьма', + team = 'prison', + mdls = models, + clothes = clothesData, + talkieFreq = 'prison', +}) + +local ply = FindMetaTable 'Player' +function ply:SetPrisonClothes(val) + if self:IsMale() then + if val then + local mat = 'models/humans/modern/octo/prisoner1_sheet' + for i, original in ipairs(self:GetMaterials()) do + if string.match(original, '.+/sheet_%d+') then + self:SetSubMaterial(i - 1, mat) + end + end + else + local clothes = self:GetDBVar('customClothes') or nil + self:SetClothes(clothes) + end + end +end + +local spawns = table.Copy(carDealer.policeSpawns) +spawns['rp_riverden_dbg_220313'] = { + { Vector(11254, -10915, 820), Angle(0, 0, 0) }, + { Vector(11254, -11105, 820), Angle(0, 0, 0) }, + { Vector(11254, -11295, 820), Angle(0, 0, 0) }, + { Vector(11254, -11485, 820), Angle(0, 0, 0) }, + { Vector(11254, -12010, 820), Angle(0, 0, 0) }, + { Vector(11254, -12220, 820), Angle(0, 0, 0) }, + { Vector(11254, -12430, 820), Angle(0, 0, 0) }, + { Vector(11254, -12640, 820), Angle(0, 0, 0) }, +} + + +carDealer.addCategory('prison', { + name = 'Тюрьма', + icon = octolib.icons.silk16('user_police_england'), + canUse = function(ply) return ply:Team() == TEAM_PRISON, 'Доступно только сотрудникам тюрьмы' end, + spawns = spawns, + spawnCheck = carDealer.limitedSpawn(1, 'prison', 'В городе уже есть тюремный транспорт'), + limitGroup = 'prison', +}) + +carDealer.addVeh('prison-bus', { + name = 'Автобус', + simfphysID = 'sim_fphys_tlad_pbus', + price = 0, + deposit = true, + customFOV = 42, + default = { + col = { Color(63,79,127), Color(255,255,255), Color(0,0,0), Color(255,255,255) }, + }, + radioWhitelist = carDealer.emsRadioStations, +}) + +carDealer.addVeh('prison-bobcat', { + name = 'Bobcat', + simfphysID = 'sim_fphys_gta4_bobcat', + price = 0, + deposit = true, + default = { + col = { Color(71,75,78), Color(255,255,255), Color(0,0,0), Color(255,255,255) }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/ra.lua b/garrysmod/addons/_config/lua/config/groups/ra.lua new file mode 100644 index 0000000..8ec8372 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/ra.lua @@ -0,0 +1,138 @@ +local uniform = { + [5] = { + name = 'Форма', + vals = { + {'Работник склада', 'models/humans/modern/octo/ra2_sheet'}, + {'Работник RA', 'models/humans/modern/octo/ra1_sheet'}, + }, + }, +} + +local models = octolib.table.mapSequential({ + 'models/humans/octo/male_01_01.mdl', + 'models/humans/octo/male_01_02.mdl', + 'models/humans/octo/male_01_03.mdl', + 'models/humans/octo/male_02_01.mdl', + 'models/humans/octo/male_02_02.mdl', + 'models/humans/octo/male_02_03.mdl', + 'models/humans/octo/male_03_01.mdl', + 'models/humans/octo/male_03_02.mdl', + 'models/humans/octo/male_03_03.mdl', + 'models/humans/octo/male_03_04.mdl', + 'models/humans/octo/male_03_05.mdl', + 'models/humans/octo/male_03_06.mdl', + 'models/humans/octo/male_03_07.mdl', + 'models/humans/octo/male_04_01.mdl', + 'models/humans/octo/male_04_02.mdl', + 'models/humans/octo/male_04_03.mdl', + 'models/humans/octo/male_04_04.mdl', + 'models/humans/octo/male_05_01.mdl', + 'models/humans/octo/male_05_02.mdl', + 'models/humans/octo/male_05_03.mdl', + 'models/humans/octo/male_05_04.mdl', + 'models/humans/octo/male_05_05.mdl', + 'models/humans/octo/male_06_01.mdl', + 'models/humans/octo/male_06_02.mdl', + 'models/humans/octo/male_06_03.mdl', + 'models/humans/octo/male_06_04.mdl', + 'models/humans/octo/male_06_05.mdl', + 'models/humans/octo/male_07_01.mdl', + 'models/humans/octo/male_07_02.mdl', + 'models/humans/octo/male_07_03.mdl', + 'models/humans/octo/male_07_04.mdl', + 'models/humans/octo/male_07_05.mdl', + 'models/humans/octo/male_07_06.mdl', + 'models/humans/octo/male_08_01.mdl', + 'models/humans/octo/male_08_02.mdl', + 'models/humans/octo/male_08_03.mdl', + 'models/humans/octo/male_08_04.mdl', + 'models/humans/octo/male_09_01.mdl', + 'models/humans/octo/male_09_02.mdl', + 'models/humans/octo/male_09_03.mdl', + 'models/humans/octo/male_09_04.mdl', +}, function(v, i) + return { + name = 'Внешность ' .. i, + male = true, + model = v, + requiredSkin = 1, + subMaterials = uniform, + } +end) + +simpleOrgs.addOrg('ra', { + name = 'Richardson Atlantics', + title = 'Работа в Richardson Atlantics', + shortTitle = 'Richardson Atlantics', + team = 'ra', + mdls = models, +}) + +local carsColor = { Color(81,117,56), Color(81,117,56), Color(0,0,0), Color(81,117,56) } +carDealer.addCategory('ra', { + name = 'RA', + icon = 'octoteam/icons-16/lorry.png', + queue = true, + doNotEvacuate = true, + spawns = carDealer.civilSpawns, + canUse = function(ply) + if not ply:IsOrgMember('ra') then + return false, 'Доступно только для Richardson Atlantics' + end + end, +}) + +carDealer.addVeh('ra_pony', { + name = 'Pony', + simfphysID = 'sim_fphys_gta4_pony', + price = 5000, + deposit = true, + default = { + col = carsColor, + }, +}) + +carDealer.addVeh('ra_packer', { + name = 'Packer', + simfphysID = 'sim_fphys_gta4_packer', + price = 15000, + deposit = true, + customFOV = 42, + default = { + col = carsColor, + }, +}) + +carDealer.addVeh('ra_boxville', { + name = 'Boxville', + simfphysID = 'sim_fphys_gta4_boxville', + price = 20000, + deposit = true, + customFOV = 38, + default = { + col = carsColor, + }, +}) + +carDealer.addVeh('ra_yankee', { + name = 'Yankee', + simfphysID = 'sim_fphys_gta4_yankee', + price = 25000, + deposit = true, + customFOV = 42, + default = { + col = carsColor, + skin = 3, + }, +}) + +carDealer.addVeh('ra_flatbed', { + name = 'Flatbed', + simfphysID = 'sim_fphys_gta4_flatbed', + price = 30000, + deposit = true, + customFOV = 45, + default = { + col = carsColor, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/taxi.lua b/garrysmod/addons/_config/lua/config/groups/taxi.lua new file mode 100644 index 0000000..4cf476f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/taxi.lua @@ -0,0 +1,135 @@ +local maleSubMats = { + [27] = { + name = 'Форма', + vals = { + {'Бежевый костюм', 'models/blairs/bs_suit_beige'}, + {'Черный костюм', 'models/blairs/bs_suit_blackf'}, + {'Светло-черный костюм', 'models/blairs/bs_suit_blacks'}, + {'Синий костюм', 'models/blairs/bs_suit_blue'}, + {'Серый костюм', 'models/blairs/bs_suit_gray'}, + {'Зеленый костюм', 'models/blairs/bs_suit_green'}, + {'Белый костюм', 'models/blairs/bs_suit_white'}, + }, + }, +} + +local femaleSubMats = { + [18] = 'models/blairs/bs_suite_femwhite', +} + + +local models = octolib.table.mapSequential({ + 'models/humans/octo/male_01_01.mdl', + 'models/humans/octo/male_01_02.mdl', + 'models/humans/octo/male_01_03.mdl', + 'models/humans/octo/male_02_01.mdl', + 'models/humans/octo/male_02_02.mdl', + 'models/humans/octo/male_02_03.mdl', + 'models/humans/octo/male_03_01.mdl', + 'models/humans/octo/male_03_02.mdl', + 'models/humans/octo/male_03_03.mdl', + 'models/humans/octo/male_03_04.mdl', + 'models/humans/octo/male_03_05.mdl', + 'models/humans/octo/male_03_06.mdl', + 'models/humans/octo/male_03_07.mdl', + 'models/humans/octo/male_04_01.mdl', + 'models/humans/octo/male_04_02.mdl', + 'models/humans/octo/male_04_03.mdl', + 'models/humans/octo/male_04_04.mdl', + 'models/humans/octo/male_05_01.mdl', + 'models/humans/octo/male_05_02.mdl', + 'models/humans/octo/male_05_03.mdl', + 'models/humans/octo/male_05_04.mdl', + 'models/humans/octo/male_05_05.mdl', + 'models/humans/octo/male_06_01.mdl', + 'models/humans/octo/male_06_02.mdl', + 'models/humans/octo/male_06_03.mdl', + 'models/humans/octo/male_06_04.mdl', + 'models/humans/octo/male_06_05.mdl', + 'models/humans/octo/male_07_01.mdl', + 'models/humans/octo/male_07_02.mdl', + 'models/humans/octo/male_07_03.mdl', + 'models/humans/octo/male_07_04.mdl', + 'models/humans/octo/male_07_05.mdl', + 'models/humans/octo/male_07_06.mdl', + 'models/humans/octo/male_08_01.mdl', + 'models/humans/octo/male_08_02.mdl', + 'models/humans/octo/male_08_03.mdl', + 'models/humans/octo/male_08_04.mdl', + 'models/humans/octo/male_09_01.mdl', + 'models/humans/octo/male_09_02.mdl', + 'models/humans/octo/male_09_03.mdl', + 'models/humans/octo/male_09_04.mdl', +}, function(v, i) + return { + name = 'Внешность ' .. i, + male = true, + model = v, + subMaterials = maleSubMats, + requiredSkin = 23, + } +end) + +for num, i in ipairs({ 1, 2, 3, 4, 6, 7 }) do + models[#models + 1] = { + name = 'Внешность ' .. num, + male = false, + model = ('models/humans/octo/female_%02i.mdl'):format(i), + requiredMats = femaleSubMats, + requiredSkin = 29, + } +end + +simpleOrgs.addOrg('taxi', { + name = 'Такси', + title = 'Работа в такси', + shortTitle = 'Работа в такси', + team = 'taxi', + mdls = models, + talkieFreq = 'taxi', +}) + +carDealer.addCategory('taxi', { + name = 'Таксисты', + icon = octolib.icons.silk16('car_taxi'), + queue = true, + canUse = function(ply) return ply:Team() == TEAM_TAXI, 'Доступно только таксистам' end, + spawns = carDealer.civilSpawns, + -- spawnCheck = carDealer.limitedSpawn(2, 'taxi', 'В городе уже достаточно машин такси'), + -- limitGroup = 'taxi', +}) + +carDealer.addVeh('taxi_taxi', { + name = 'Merit', + simfphysID = 'sim_fphys_gta4_taxi2', + price = 0, + deposit = true, + default = { + bg = { [1] = 2 }, + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + skin = 1, + }, +}) + +carDealer.addVeh('taxi_cabbie', { + name = 'Cabby', + simfphysID = 'sim_fphys_gta4_cabby', + price = 0, + deposit = true, + default = { + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + skin = 1, + }, +}) + +carDealer.addVeh('taxi_vapid', { + name = 'Vapid', + simfphysID = 'sim_fphys_gta4_taxi', + price = 0, + deposit = true, + default = { + bg = { [1] = 2 }, + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + skin = 1, + }, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/groups/wcso/cars.lua b/garrysmod/addons/_config/lua/config/groups/wcso/cars.lua new file mode 100644 index 0000000..981b160 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/wcso/cars.lua @@ -0,0 +1,257 @@ +hook.Add('car-dealer.priceOverride', 'dbg-police.wcso', function(ply, class) + local cdData = carDealer.vehicles[class] + if ply:Team() == TEAM_WCSO and cdData.police and cdData.deposit then return 0 end +end) + +carDealer.addCategory('wcso', { + name = 'Офис Шерифа', + icon = octolib.icons.silk16('sheriff'), + ems = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_WCSO, 'Доступно только сотрудникам Офиса Шерифа на службе' end, + spawns = carDealer.policeSpawns, + spawnCheck = carDealer.limits.police, + limitGroup = 'police', +}) + +carDealer.addVeh('wcso-vapid', { + category = 'wcso', + name = 'Police Vapid', + simfphysID = 'sim_fphys_gta4_police', + price = 30000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, + default = { + mats = { + [1] = 'octoteam/models/vehicles/stainer/sheriff_livery_clr_1', + }, + }, +}) + +carDealer.addVeh('wcso-merit', { + category = 'wcso', + name = 'Police Merit', + simfphysID = 'sim_fphys_gta4_police2', + price = 40000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, + default = { + mats = { + [14] = 'octoteam/models/vehicles/merit/sheriff2_livery_clr_1', + }, + }, +}) + +carDealer.addVeh('wcso-buffalo', { + category = 'wcso', + name = 'Police Buffalo', + simfphysID = 'sim_fphys_tbogt_police3', + price = 75000, + deposit = true, + police = true, + radioWhitelist = carDealer.policeRadioStations, + default = { + mats = { + [6] = 'octoteam/models/vehicles/buffalo/sheriff3_livery_clr_1', + }, + }, +}) + +local ranks = octolib.array.toKeys {'srg', 'lie', 'cap', 'ass', 'she', 'seb'} +carDealer.addCategory('wcso_nomark', { + name = 'Немаркированные', + icon = 'icon16/user_suit.png', + ems = true, + doNotEvacuate = true, + canUse = function(ply) return ply:Team() == TEAM_WCSO and ranks[ply:GetActiveRank('wcso') or ''] or false, 'Доступно только сержанту и старше' end, + spawns = { + rp_evocity_dbg_220222 = { + { Vector(-4656, -7516, 225), Angle(0,-90,0) }, -- Duglas + { Vector(-4656, -7211, 225), Angle(0,-90,0) }, + { Vector(-4656, -6906, 225), Angle(0,-90,0) }, + { Vector(-4656, -6601, 225), Angle(0,-90,0) }, + { Vector(-4656, -6296, 225), Angle(0,-90,0) }, + }, + rp_eastcoast_v4c = carDealer.policeSpawns.rp_eastcoast_v4c, + rp_truenorth_v1a = carDealer.policeSpawns.rp_truenorth_v1a, + }, + spawnCheck = carDealer.limits.police, + limitGroup = 'police', +}) + +local randomColorTags = { {'icon16/color_wheel.png', 'Случайный цвет'} } +local function randomColor() + local colData = table.Random(carDealer.defaultCarColors) + local _, r, g, b = unpack(colData) + local col = Color(r, g, b) + return { col, col, Color(0,0,0), col } +end + +local plateCol = { + bg = Color(255, 255, 255), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + text = Color(0, 0, 0), +} + +carDealer.addVeh('wcso_nomark_premier', { + name = 'Premier', + simfphysID = 'sim_fphys_gta4_premier', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('wcso_nomark_dilettante', { + name = 'Dilettante', + simfphysID = 'sim_fphys_gta4_dilettante', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('wcso_nomark_buffalo', { + name = 'Buffalo', + simfphysID = 'sim_fphys_gta4_fbi', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('wcso_nomark_admiral', { + name = 'Admiral', + simfphysID = 'sim_fphys_gta4_admiral', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + +carDealer.addVeh('wcso_nomark_huntley', { + name = 'Huntley', + simfphysID = 'sim_fphys_gta4_huntley', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, +}) + + +local function isSeb(ply) + return ply:GetActiveRank('wcso') == 'seb', 'Доступно только S.E.B.' +end + +carDealer.addVeh('wcso_nomark_mule', { + name = 'Mule', + simfphysID = 'sim_fphys_gta4_mule', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, + canUse = isSeb, +}) + +carDealer.addVeh('wcso_nomark_burrito', { + name = 'Burrito', + simfphysID = 'sim_fphys_gta4_burrito', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, + canUse = isSeb, +}) + +carDealer.addVeh('wcso_nomark_moonbeam', { + name = 'Moonbeam', + simfphysID = 'sim_fphys_gta4_moonbeam', + price = 0, + police = true, + deposit = true, + tags = randomColorTags, + default = { + col = randomColor, + }, + plateCol = plateCol, + canUse = isSeb, +}) + +carDealer.addVeh('wcso_nomark_taxi', { + name = 'Taxi', + simfphysID = 'sim_fphys_gta4_taxi2', + price = 8000, + police = true, + deposit = true, + default = { + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + }, + plateCol = plateCol, + canUse = isSeb, +}) + +carDealer.addVeh('wcso_nomark_cabbie', { + name = 'Cabby', + simfphysID = 'sim_fphys_gta4_cabby', + price = 15000, + police = true, + deposit = true, + default = { + col = { Color(215,142,16), Color(215,142,16), Color(0,0,0), Color(215,142,16) }, + }, + plateCol = plateCol, + canUse = isSeb, +}) + +-- SEB + +carDealer.addVeh('seb_enforcer', { + name = 'Enforcer', + category = 'wcso', + simfphysID = 'sim_fphys_gta4_nstockade', + customFOV = 38, + price = 0, + bulletproof = true, + deposit = true, + police = true, + canUse = isSeb, + glauncher = true, + canSee = function(ply) + return ply:GetActiveRank('wcso') == 'seb' + end, + default = { + skin = 1, + }, + radioWhitelist = carDealer.policeRadioStations, +}) diff --git a/garrysmod/addons/_config/lua/config/groups/wcso/init.lua b/garrysmod/addons/_config/lua/config/groups/wcso/init.lua new file mode 100644 index 0000000..9a28667 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/wcso/init.lua @@ -0,0 +1,2 @@ +octolib.shared('models') +octolib.shared('cars') diff --git a/garrysmod/addons/_config/lua/config/groups/wcso/models.lua b/garrysmod/addons/_config/lua/config/groups/wcso/models.lua new file mode 100644 index 0000000..7ed4713 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/groups/wcso/models.lua @@ -0,0 +1,373 @@ +local clothesData = { + icon = 'sheriff', + ['models/player/octo_sheriff/'] = { + { + sm = 'Головной убор', + icon = 'hat', + bodygroup = 3, + vals = { + [0] = { 'Надеть шляпу', 'bullet_blue', '/me надевает шляпу на голову' }, + [1] = { 'Надеть шлем', 'bullet_black', '/me надевает шлем на голову' }, + [2] = { 'Снять убор', 'cross', '/me снимает головной убор' }, + }, + },{ + sm = 'Перчатки', + icon = 'hand', + bodygroup = 6, + vals = { + [0] = { 'Снять', 'cross', '/me снимает перчатки' }, + [1] = { 'Надеть черные перчатки', 'bullet_black', '/me надевает черные перчатки' }, + [2] = { 'Надеть белые перчатки', 'bullet_white', '/me надевает белые перчатки' }, + }, + },{ + sm = 'Кобура тазера', + icon = 'gun', + bodygroup = 2, + vals = { + [0] = { 'Надеть кобуру тазера слева', 'arrow_left', '/me надевает кобуру с тазером слева' }, + [1] = { 'Надеть кобуру тазера справа', 'arrow_right', '/me надевает кобуру с тазером справа' }, + [2] = { 'Снять кобуру с тазером', 'cross', '/me снимает шлем с головы' }, + }, + },{ + bodygroup = 7, + vals = { + [0] = { 'Надеть рацию', 'bullet_blue', '/me надевает рацию себе на грудь' }, + [1] = { 'Снять рацию', 'cross', '/me снимает рацию с груди' }, + }, + },{ + bodygroup = 5, + vals = { + [0] = { 'Надеть дубинку', 'bullet_blue', '/me вешает дубинку обратно на пояс' }, + [1] = { 'Снять дубинку', 'cross', '/me снимает дубинку с пояса' }, + }, + },{ + bodygroup = 4, + vals = { + [0] = { 'Надеть галстук', 'bullet_blue', '/me надевает галстук себе на шею, плотно его затягивая' }, + [1] = { 'Снять галстук', 'cross', '/me снимает галстук, расслабяя воротник' }, + }, + }, + }, + ['models/player/octo_swat_team/'] = { + { + bodygroup = 1, + vals = { + [0] = { 'Надеть бронежилет с разгрузками', 'bullet_blue', '/me надевает бронежилет на тело' }, + [1] = { 'Снять бронежилет с разгрузками', 'cross', '/me снимает бронежилет с груди' }, + }, + },{ + bodygroup = 2, + vals = { + [0] = { 'Снарядить низ', 'bullet_blue', '/me нацепляет снаряжения на ноги' }, + [1] = { 'Снять всё с ног', 'cross', '/me снимает все снаряжение с ног' }, + }, + },{ + bodygroup = 3, + vals = { + [0] = { 'Распрямить рукава', 'arrow_down', '/me распрямляет рукава' }, + [1] = { 'Засучить рукава', 'arrow_up', '/me засучивает рукава' }, + }, + },{ + bodygroup = 4, + vals = { + [0] = { 'Снять перчатки', 'cross', '/me снимает тактические перчатки с рук' }, + [1] = { 'Надеть перчатки', 'bullet_blue', '/me надевает перчатки на руки' }, + }, + },{ + bodygroup = 5, + vals = { + [0] = { 'Снять часы', 'cross', '/me снимает часы с рук' }, + [1] = { 'Надеть часы', 'bullet_blue', '/me застегивает часы на руке' }, + }, + },{ + bodygroup = 9, + vals = { + [0] = { 'Надеть шлем', 'bullet_blue', '/me надевает шлем на голову' }, + [1] = { 'Снять шлем', 'cross', '/me снимает шлем с головы' }, + }, + },{ + bodygroup = 10, + vals = { + [0] = { 'Надеть наушники', 'bullet_blue', '/me надевает наушники' }, + [1] = { 'Снять наушники', 'cross', '/me снимает наушники' }, + }, + },{ + bodygroup = 11, + vals = { + [0] = { 'Надеть прозрачные очки', 'bullet_white', '/me надевает прозрачные тактические очки на глаза' }, + [1] = { 'Надеть затемненные очки', 'bullet_black', '/me надевает затемненные тактические очки на глаза' }, + [2] = { 'Снять очки', 'cross', '/me снимает тактические очки с глаз' }, + }, + },{ + bodygroup = 12, + vals = { + [0] = { 'Надеть камеру на шлем', 'bullet_blue', '/me надевает камеру на шлем' }, + [1] = { 'Снять камеру со шлема', 'cross', '/me снимает камеру со шлема' }, + }, + }, + }, +} + +local wcsoBgs = { + [1] = { + name = 'Внешний бронежилет', + }, + [2] = { + name = 'Тазер', + vals = { + { 'Слева', 0, true }, + { 'Справа', 1 }, + { 'Снять', 2 }, + }, + }, + [3] = { + name = 'Головной убор', + vals = { + { 'Шляпа', 0, true }, + { 'Шлем', 1 }, + { 'Снять', 2 }, + }, + }, + [4] = { + name = 'Форма', + vals = { + { 'Строгая', 0, true }, + { 'Повседневная', 1 }, + { 'Легкая', 2 }, + }, + }, + [5] = { + name = 'Снять дубинку', + }, + [6] = { + name = 'Перчатки', + vals = { + { 'Без перчаток', 0, true }, + { 'Черные', 1 }, + { 'Белые', 2 }, + }, + }, + [7] = { + name = 'Снять рацию', + }, +} + +local mdls = {} +for i, v in ipairs({1, 2, 3, 4, 5, 6, 8, 9}) do + mdls[#mdls + 1] = { + name = '%s ' .. i, + model = ('models/player/octo_sheriff/male_%02i.mdl'):format(v), + unisex = true, + bgs = wcsoBgs, + } +end + +local sebNames = { + {'Westbrook', 0}, + {'Sandstorm', 1}, + {'Moore', 2}, + {'Miller', 3}, + {'Bartels', 4}, + {'Marler', 5}, + {'Mckenney', 6}, + {'Rain', 7}, + {'Thompson', 8}, + {'Rose', 9}, + {'Cramble', 10}, + {'Phillips', 11}, + {'Nelson', 12}, + {'Anderson', 13}, + {'Kertis', 14}, + {'Campbell', 15}, + {'Bradley', 16}, + {'Archuleta', 17}, + {'Murphy', 18}, + {'Rumberger', 19}, + {'Ter Stegen', 20}, + {'Gvidichi', 21}, + {'Coleman', 22}, + {'Без имени', 26}, +} + +local sebBgs = { + [7] = { + name = 'Позывной', + vals = sebNames, + }, + [13] = { + name = 'Патч на шлеме', + vals = { + {'Без патча', 0}, + {'Патч "Punisher Thin Blue Line"', 1}, + {'Патч 715 Team', 2}, + {'Патч "Bang one, bang em\' all"', 3}, + {'Патч "My idea of help"', 4}, + {'Патч "Respect all, fear none', 5}, + }, + }, + [11] = { + name = 'Очки', + vals = { + {'Прозрачные очки', 0}, + {'Затемненные очки', 1}, + {'Без очков', 2}, + }, + }, + [1] = { + name = 'Стандартный вверх', + }, + [2] = { + name = 'Стандартный низ', + }, + [3] = { + name = 'Засученные рукава', + }, + [4] = { + name = 'Тактические перчатки', + }, + [5] = { + name = 'Часы', + }, + [6] = { + name = 'Медицинский патч на разгрузку', + }, + [9] = { + name = 'Снять шлем', + }, + [10] = { + name = 'Снять наушники', + }, + [12] = { + name = 'Снять камеру на шлеме', + }, +} + +local sebMdls = {} +local skins = {[1] = 2, [2] = 2, [3] = 6, [4] = 3, [5] = 4, [6] = 4, [7] = 5, [8] = 4, [9] = 3} +for i = 1, 9 do + sebMdls[#sebMdls + 1] = { + name = 'Форма ' .. i, + male = true, + model = ('models/player/octo_swat_team/male_%02i.mdl'):format(i), + bgs = sebBgs, + requiredMats = { + [4] = 'models/octo_swat_team/body_01_seb', + [5] = 'models/octo_swat_team/armor_seb', + [6] = 'models/octo_swat_team/patch_seb', + [7] = 'models/octo_swat_team/lowr_01_seb', + [11] = 'models/octo_swat_team/hlem_seb', + }, + skin = { + name = 'Внешность', + vals = {}, + }, + } + + for n = 0, skins[i] do + sebMdls[i].skin.vals[#sebMdls[i].skin.vals + 1] = { 'Внешность ' .. (n + 1), n } + end + +end + +table.Add(sebMdls, mdls) + +simpleOrgs.addOrg('wcso', { + name = 'Офис Шерифа', + title = 'Работа в Офисе Шерифа', + shortTitle = 'Работа в WCSO', + team = 'wcso', + police = true, + talkieFreq = 'ems', + clothes = clothesData, + rankOrder = { 'cad', 'ins', 'ds1', 'ds2', 'crp', 'srg', 'lie', 'cap', 'ass', 'she', 'seb' }, + multirank = true, + ranks = { + cad = { -- Cadet + shortName = 'Кадет', + name = 'Кадет офиса Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + weps = { 'weapon_octo_air_glock17', 'weapon_octo_air_m4a1', 'weapon_octo_air_m3' }, + skin = 0, + }, + ins = { -- Instructor + shortName = 'Инструктор', + name = 'Инструктор офиса Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + weps = { 'weapon_octo_air_glock17', 'weapon_octo_air_m4a1', 'weapon_octo_air_m3' }, + skin = 3, + }, + ds1 = { -- Deputy Sheriff I + shortName = 'Помощник Шерифа I', + name = 'Помощник Шерифа I', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 0, + }, + ds2 = { -- Deputy Sheriff II + shortName = 'Помощник Шерифа II', + name = 'Помощник Шерифа II', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 1, + }, + crp = { -- Corporal + shortName = 'Капрал', + name = 'Капрал офиса Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 2, + }, + srg = { -- Sergeant + shortName = 'Сержант', + name = 'Сержант офиса Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 3, + }, + lie = { -- Lieutenant + shortName = 'Лейтенант', + name = 'Лейтенант офиса Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 4, + }, + cap = { -- Captain + shortName = 'Капитан', + name = 'Капитан офиса Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 5, + }, + ass = { -- Assistant Sheriff + shortName = 'Ассистент Шерифа', + name = 'Ассистент Шерифа', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 6, + }, + she = { -- Sheriff + shortName = 'Шериф', + name = 'Шериф', + mdls = mdls, + icon = octolib.icons.silk16('sheriff'), + skin = 7, + }, + + seb = { -- S. E. B. + shortName = 'Оператор S.E.B.', + name = 'Оператор S.E.B.', + armor = 100, + mdls = sebMdls, + icon = octolib.icons.silk16('lightning'), + weps = {'weapon_octo_m4a1', 'weapon_octo_usps', 'weapon_octo_xm1014', 'weapon_octo_sg550', 'weapon_octo_beanbag', 'weapon_octo_p90', 'weapon_octo_tmp', 'door_ram', 'dbg_shield'}, + excludeWeps = {'weapon_octo_glock17', 'dbg_speedometer'}, + }, + } +}) + +netstream.Hook('wcso.gloves', function(ply, val) + if not (ply:GetActiveRank('wcso') and octolib.math.inRange(val, 0, 2)) then return end + ply:SetBodygroup(6, val) +end) diff --git a/garrysmod/addons/_config/lua/config/map.lua b/garrysmod/addons/_config/lua/config/map.lua new file mode 100644 index 0000000..504c0d9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/map.lua @@ -0,0 +1,88 @@ +if game.GetMap() == 'rp_truenorth_v1a' then + local function createSSEnt(ent, class) + local pos, ang = ent:GetPos(), ent:GetAngles() + ent:Remove() + ent = ents.Create(class) + ent:SetPos(pos) + ent:SetAngles(ang) + ent:Spawn() + ent:GetPhysicsObject():EnableMotion(false) + end + + local toRem = {2699, 2700, 3838} + + local function makeDobrograd() + for _,v in ipairs(ents.GetAll()) do + if v:GetName() == 'speed50' then + v:SetSkin(5) + elseif v:GetName() == 'speed80' then + v:SetSkin(6) + elseif v:GetName() == 'speed100' then + v:SetSkin(7) + elseif v:GetName() == 'flagpole' then + v:SetSkin(1) + elseif v:GetName() == 'hwyca' or v:GetName() == 'pumps' then + v:Remove() + elseif v:GetName() == 'hwyus' then + v:Fire('TurnOn', 1, 0) + if v:GetModel() == 'models/props_street/mail_dropbox.mdl' then + createSSEnt(v, 'octoinv_mailbox') + end + elseif v:GetModel() == 'models/props_unique/atm01.mdl' then + createSSEnt(v, 'brax_atm') + end + end + for _,v in ipairs(toRem) do + ents.GetMapCreatedEntity(v):Remove() + end + end + + hook.Add('InitPostEntity', 'dbg-cleanthemap', makeDobrograd) + hook.Add('PostCleanupMap', 'dbg-cleanthemap', makeDobrograd) +end + +local function setupWeather() + + if game.GetMap():find('evocity') then + local lightspots, sprites, spots, lights, nlights1, nlights3 = {}, {}, {}, {}, {}, {} + for _, ent in ipairs(ents.GetAll()) do + local name = ent:GetName() + if name == 'street_lightspot' then lightspots[#lightspots + 1] = ent + elseif name == 'street_sprite' then sprites[#sprites + 1] = ent + elseif name == 'street_spot' then spots[#spots + 1] = ent + elseif name == 'street_light' then lights[#lights + 1] = ent + elseif name == 'nightlight1' then nlights1[#nlights1 + 1] = ent + elseif name == 'nightlight3' then nlights3[#nlights3 + 1] = ent end + end + + local lastLight = -1 + timer.Create('dbg-weather', 1, 0, function() + local curLight = StormFox2.Weather.GetLuminance() + local turnOn = curLight < 125 + if lastLight >= 0 and turnOn == (lastLight < 125) then return end + lastLight = curLight + for _, ent in ipairs(lightspots) do ent:Fire(turnOn and 'LightOn' or 'LightOff') end + for _, ent in ipairs(sprites) do ent:Fire(turnOn and 'ShowSprite' or 'HideSprite') end + for _, ent in ipairs(spots) do ent:Fire(turnOn and 'TurnOn' or 'TurnOff') end + for _, ent in ipairs(lights) do ent:Fire(turnOn and 'TurnOn' or 'TurnOff') end + for _, ent in ipairs(nlights1) do ent:Fire(turnOn and 'TurnOn' or 'TurnOff') end + for _, ent in ipairs(nlights3) do ent:Fire(turnOn and 'TurnOn' or 'TurnOff') end + end) + end + +end +hook.Add('InitPostEntity', 'dbg-weather', setupWeather) +hook.Add('PostCleanupMap', 'dbg-weather', setupWeather) +setupWeather() + +hook.Add('InitPostEntity', 'dbg-doors', function() + for _,v in ipairs(ents.GetAll()) do + if v:IsDoor() then + v.defaultSkin = v:GetSkin() + v.defaultBGs = {} + for _,bg in ipairs(v:GetBodyGroups()) do + v.defaultBGs[bg.id] = v:GetBodygroup(bg.id) + end + end + end +end) diff --git a/garrysmod/addons/_config/lua/config/masks.lua b/garrysmod/addons/_config/lua/config/masks.lua new file mode 100644 index 0000000..eafa308 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/masks.lua @@ -0,0 +1,558 @@ +CFG.masks = { + cleaver = { + name = L.mask_cleaver, + mdl = 'models/props_lab/Cleaver.mdl', + pos = Vector(-3.1, 6.7, 5.2), + ang = Angle(0, 85, 0), + scale = 0.6, + }, + monkey = { + name = L.mask_monkey, + mdl = 'models/sal/halloween/monkey.mdl', + pos = Vector(-4.1, 0, -2.2), + hideName = true, + }, + zombie = { + name = L.mask_zombie, + mdl = 'models/sal/halloween/zombie.mdl', + pos = Vector(-3.7, 0, -2), + hideName = true, + }, + hockey = { + name = L.mask_hockey, + mdl = 'models/sal/acc/fix/mask_2.mdl', + pos = Vector(-4, 0, -1.9), + hideName = true, + }, + gingerbread = { + name = L.mask_gingerbread, + mdl = 'models/sal/gingerbread.mdl', + pos = Vector(-4.2, 0, -2), + ang = Angle(-2.8, 0, 0), + hideName = true, + }, + pumpkin = { + name = L.mask_pumpkin, + mdl = 'models/halloween2015/pumbkin_n_f01.mdl', + pos = Vector(-2.9, 0, 0.7), + ang = Angle(11, 0, 0), + scale = 0.4, + hideName = true, + }, + top_hat = { + name = L.mask_top_hat, + mdl = 'models/player/items/humans/top_hat.mdl', + pos = Vector(-3.1, 0, -3.6), + }, + doctor = { + name = L.mask_doctor, + mdl = 'models/sal/halloween/doctor.mdl', + pos = Vector(-5, 0, -2), + hideName = true, + }, + pig = { + name = L.mask_pig, + mdl = 'models/sal/pig.mdl', + pos = Vector(-4.2, 0, -2.4), + hideName = true, + }, + bear = { + name = L.mask_bear, + mdl = 'models/sal/bear.mdl', + pos = Vector(-4.3, 0, -1.7), + ang = Angle(-5.6, 0, 0), + hideName = true, + }, + turtle = { + name = L.mask_turtle, + mdl = 'models/props/de_tides/vending_turtle.mdl', + pos = Vector(-3.2, 0.1, 3.8), + ang = Angle(-1.3, -95.9, -3.8), + scale = 0.61, + }, + deer = { + name = L.mask_deer, + mdl = 'models/monstermash/gibs/head_deerhaunter.mdl', + pos = Vector(-5.1, 0, -6.6), + ang = Angle(-3.4, 1.5, -2.9), + scale = 1.3, + skin = 1, + hideName = true, + }, + mummy = { + name = L.mask_mummy, + mdl = 'models/monstermash/gibs/head_mummy.mdl', + pos = Vector(0, 0, -7), + ang = Angle(0, 95.1, -18.6), + scale = 1.02, + hideName = true, + }, + santa = { + name = 'Новогодний колпак', + icon = 'octoteam/icons/xmas_hat.png', + mdl = 'models/cloud/kn_santahat.mdl', + pos = Vector(-4.6, 0, -3.8), + ang = Angle(0.8, -85.2, 81.1), + scale = 1.02, + }, + gasmask = { + name = L.mask_gasmask, + icon = 'octoteam/icons/gasmask.png', + desc = L.desc_gasmask, + mdl = 'models/splinks/kf2/cosmetics/gas_mask.mdl', + pos = Vector(-4.4, 0, -2.7), + ang = Angle(0, 0, 0), + hideName = true, + }, + + cap_dobrograd = { + name = L.cap_dobrograd, + icon = 'octoteam/icons/clothes_cap.png', + desc = L.cap_dobrograd_desc, + noDrop = true, + mdl = 'models/modified/hat08.mdl', + pos = Vector(-3.7, 0, 1.1), + skin = 1, + }, + orangecap = { + name = 'Оранжевая кепка', + icon = 'octoteam/icons/clothes_cap.png', + desc = 'Обычная оранжевая кепка с каким-то логотипом', + mdl = 'models/modified/hat08.mdl', + pos = Vector(-3.7, 0, 1.1), + skin = 0, + price = 12000, + }, + redcap = { + name = 'Красная кепка', + icon = 'octoteam/icons/clothes_cap.png', + desc = 'Классическая красная кепка с каким-то логотипом', + mdl = 'models/modified/hat08.mdl', + pos = Vector(-3.7, 0, 1.1), + skin = 4, + price = 14000, + }, + cap_meteorite = { + name = 'Кепка "Meteorite"', + icon = 'octoteam/icons/clothes_cap.png', + desc = 'Классическая кепка черного цвета с логотипом "Meteorite"', + mdl = 'models/modified/hat08.mdl', + pos = Vector(-3.7, 0, 1.1), + skin = 8, + price = 12000, + }, + cap_monkey = { + name = 'Кепка "Orang-O-Tang"', + icon = 'octoteam/icons/clothes_cap.png', + desc = 'Классическая кепка коричневого цвета с логотипом "Orang-O-Tang" и обезьянкой', + mdl = 'models/modified/hat08.mdl', + pos = Vector(-3.7, 0, 1.1), + skin = 11, + price = 14000, + }, + cap_quests = { + name = 'Кепка "Лига Квестов"', + icon = 'octoteam/icons/clothes_cap.png', + desc = 'Фирменная кепка Лиги Квестов', + mdl = 'models/leeetov/quest_cap.mdl', + pos = Vector(-3.6, -0.19, 1.8), + }, + + mask_hoffman = { + name = 'Маска с эмблемой короны', + desc = L.mask_hoffman, + noDrop = true, + mdl = 'models/sal/acc/fix/mask_2.mdl', + pos = Vector(-3.8, 0, -1.9), + ang = Angle(0, 0, 0), + skin = 5, + hideName = true, + }, + hoff_widesmile = { + name = 'Пакет на голову', + desc = '"Звериный скал"', + noDrop = true, + mdl = 'models/sal/halloween/bag.mdl', + pos = Vector(-3.3, 0, -1.5), + ang = Angle(0, 0, 0), + skin = 1, + hideName = true, + }, + hoff_tears = { + name = 'Пакет на голову', + desc = '"Плак-плак"', + noDrop = true, + mdl = 'models/sal/halloween/bag.mdl', + pos = Vector(-3.3, 0, -1.5), + ang = Angle(0, 0, 0), + skin = 2, + hideName = true, + }, + hoff_tinysmile = { + name = 'Пакет на голову', + desc = '"Легкий скал"', + noDrop = true, + mdl = 'models/sal/halloween/bag.mdl', + pos = Vector(-3.3, 0, -1.5), + ang = Angle(0, 0, 0), + skin = 3, + hideName = true, + }, + hoff_dude = { + name = 'Пакет на голову', + desc = '"Чувак"', + noDrop = true, + mdl = 'models/sal/halloween/bag.mdl', + pos = Vector(-3.3, 0, -1.5), + ang = Angle(0, 0, 0), + skin = 5, + hideName = true, + }, + hoff_killme = { + name = 'Пакет на голову', + desc = '"Пожалуйста, убейте"', + noDrop = true, + mdl = 'models/sal/halloween/bag.mdl', + pos = Vector(-3.3, 0, -1.5), + ang = Angle(0, 0, 0), + skin = 8, + hideName = true, + }, + hoff_helpme = { + name = 'Пакет на голову', + desc = '"Помогите"', + noDrop = true, + mdl = 'models/sal/halloween/bag.mdl', + pos = Vector(-3.3, 0, -1.5), + ang = Angle(0, 0, 0), + skin = 18, + hideName = true, + }, + hardhat = { + name = 'Строительная каска', + noDrop = true, + mdl = 'models/props_junk/hardhat.mdl', + pos = Vector(-3.5, 0, 1.2), + ang = Angle(-5, 0, 0), + scale = 0.73, + }, + peakless = { + name = 'Бескозырка', + desc = 'Удобная бескозырка, только без острия', + icon = 'octoteam/icons/peaky_hat.png', + mdl = 'models/modified/hat06.mdl', + pos = Vector(-4.5, -0.2, 2.2), + ang = Angle(0, 4.1, 0), + scale = 1.01, + price = 34000, + }, + ninja = { + name = 'Балаклава', + desc = 'Маска, закрывающая не только лицо, но и часть головы и шею', + icon = 'octoteam/icons/robber.png', + mdl = 'models/sal/halloween/ninja.mdl', + pos = Vector(-4.2, 0.21, -2), + scale = 1.042, + hideName = true, + }, + balaclava = { + name = 'Черная балаклава', + desc = 'Маска, закрывающая не только лицо, но и часть головы и шею', + icon = 'octoteam/icons/robber.png', + mdl = 'models/modified/balaclava.mdl', + pos = Vector(-3.8, 0.0, 1), + price = 10000, + hideName = true, + }, + balaclava_g = { + name = 'Зеленая балаклава', + desc = 'Маска, закрывающая не только лицо, но и часть головы и шею', + icon = 'octoteam/icons/robber.png', + mdl = 'models/modified/balaclava_g.mdl', + pos = Vector(-2.3, 0.0, 2.2), + price = 10000, + hideName = true, + }, + + baseball = { + name = 'Классическая бейсболка', + desc = 'Бейсболка с логотипом лиги бейсбола', + icon = 'octoteam/icons/clothes_cap.png', + mdl = 'models/modified/hat07.mdl', + pos = Vector(-4, -0.19, 1.8), + ang = Angle(-7.2, 0, 0), + skin = 10, + price = 19000, + }, + baseball_jordan = { + name = 'Бейсболка Jordan', + desc = 'Черная бейсболка с характерной баскетбольной символикой', + icon = 'octoteam/icons/clothes_cap.png', + mdl = 'models/modified/hat07.mdl', + pos = Vector(-4, -0.19, 1.8), + ang = Angle(-7.2, 0, 0), + skin = 0, + price = 20000, + }, + yellowbaseball = { + name = 'Желтая бейсболка', + desc = 'Бейсболка желтого цвета с вышитой надписью', + icon = 'octoteam/icons/clothes_cap.png', + mdl = 'models/modified/hat07.mdl', + pos = Vector(-4, -0.19, 1.8), + ang = Angle(-7.2, 0, 0), + skin = 1, + price = 15000, + }, + baseball_stetson = { + name = 'Бейсболка Stetson', + desc = 'Бейсболка в классическом американском стиле', + icon = 'octoteam/icons/clothes_cap.png', + mdl = 'models/modified/hat07.mdl', + pos = Vector(-4, -0.19, 1.8), + ang = Angle(-7.2, 0, 0), + skin = 3, + price = 24000, + }, + baseball_detroit = { + name = 'Бейсболка Detroit', + desc = 'Классическая бейсболка темно-синего цвета с вышитым на передней части логотипом города', + icon = 'octoteam/icons/clothes_cap.png', + mdl = 'models/modified/hat07.mdl', + pos = Vector(-4, -0.19, 1.8), + ang = Angle(-7.2, 0, 0), + skin = 6, + price = 18000, + }, + + bandana = { + name = 'Черная бандана', + desc = 'Небольшой кусок ткани, служащий для сокрытия лица', + icon = 'octoteam/icons/bandana.png', + mdl = 'models/octoteam/accessory/bandana.mdl', + pos = Vector(-3.95, 0.1, -4), + ang = Angle(0.2, 0, 0), + scale = 1.03, + skin = 5, + price = 5000, + hideName = true, + }, + redbandana = { + name = 'Красная бандана', + desc = 'Небольшой кусок ткани, служащий для сокрытия лица', + icon = 'octoteam/icons/bandana.png', + mdl = 'models/octoteam/accessory/bandana.mdl', + pos = Vector(-3.95, 0.1, -4), + ang = Angle(0.2, 0, 0), + scale = 1.03, + skin = 1, + price = 5000, + hideName = true, + }, + yellowbandana = { + name = 'Желтая бандана', + desc = 'Небольшой кусок ткани, служащий для сокрытия лица', + icon = 'octoteam/icons/bandana.png', + mdl = 'models/octoteam/accessory/bandana.mdl', + pos = Vector(-3.95, 0.1, -4), + ang = Angle(0.2, 0, 0), + scale = 1.03, + skin = 2, + price = 5000, + hideName = true, + }, + greenbandana = { + name = 'Зеленая бандана', + desc = 'Небольшой кусок ткани, служащий для сокрытия лица', + icon = 'octoteam/icons/bandana.png', + mdl = 'models/octoteam/accessory/bandana.mdl', + pos = Vector(-3.95, 0.1, -4), + ang = Angle(0.2, 0, 0), + scale = 1.03, + skin = 3, + price = 5000, + hideName = true, + }, + bluebandana = { + name = 'Синяя бандана', + desc = 'Небольшой кусок ткани, служащий для сокрытия лица', + icon = 'octoteam/icons/bandana.png', + mdl = 'models/octoteam/accessory/bandana.mdl', + pos = Vector(-3.95, 0.1, -4), + ang = Angle(0.2, 0, 0), + scale = 1.03, + skin = 4, + price = 5000, + hideName = true, + }, + medical_mask = { + name = 'Медицинская маска', + desc = 'Сейчас это актуально как никогда', + icon = 'octoteam/icons/medical_mask.png', + mdl = 'models/dean/gtaiv/mask.mdl', + pos = Vector(-3.5, 0, -3.1), + ang = Angle(11.2, 0, 0), + scale = 1.01, + hideName = true, + }, + + blackglasses = { + name = 'Черные очки', + desc = 'Самые стандартные очки с дешевой черной оправой', + icon = 'octoteam/icons/glasses_classic.png', + mdl = 'models/modified/glasses02.mdl', + pos = Vector(-3.9, 0, -0.13), + skin = 3, + price = 7000, + }, + glasses = { + name = 'Классические очки', + desc = 'Самые стандартные очки с дешевой оправой', + icon = 'octoteam/icons/glasses_classic.png', + mdl = 'models/modified/glasses02.mdl', + pos = Vector(-3.9, 0, -0.13), + skin = 0, + price = 7000, + }, + redglasses = { + name = 'Красные очки', + desc = 'Самые стандартные очки с дешевой красной оправой', + icon = 'octoteam/icons/glasses_classic.png', + mdl = 'models/modified/glasses02.mdl', + pos = Vector(-3.9, 0, -0.13), + skin = 1, + price = 7000, + }, + whiteglasses = { + name = 'Белые очки', + desc = 'Самые стандартные очки с дешевой белой оправой', + icon = 'octoteam/icons/glasses_classic.png', + mdl = 'models/modified/glasses02.mdl', + pos = Vector(-3.9, 0, -0.13), + skin = 2, + price = 7000, + }, + brownglasses = { + name = 'Коричневые очки', + desc = 'Самые стандартные очки с дешевой коричневой оправой', + icon = 'octoteam/icons/glasses_classic.png', + mdl = 'models/modified/glasses02.mdl', + pos = Vector(-3.9, 0, -0.13), + skin = 4, + price = 7000, + }, + + modelli = { + name = 'Очки LQB "Modelli"', + desc = 'Фирменные очки LQB, позолоченные дужки которых выполнены в виде специального узора', + icon = 'octoteam/icons/sun_glasses.png', + mdl = 'models/modified/lqb_glass2.mdl', + pos = Vector(-3.9, 0, -0.13), + }, + rettelinee = { + name = 'Очки Gucci "Rette Linee"', + desc = 'Очки Gucci с фирменным узором на дужках', + icon = 'octoteam/icons/glasses_aviator.png', + mdl = 'models/modified/lqb_glass1.mdl', + pos = Vector(-3.9, 0, -0.13), + }, + + aviator = { + name = 'Черные авиаторы', + desc = 'Стильные очки "капельки" черного цвета, имеющие достаточно удобную форму оправы и линз', + icon = 'octoteam/icons/glasses_aviator.png', + mdl = 'models/modified/glasses01.mdl', + pos = Vector(-4.35, 0, -0.1), + skin = 0, + price = 12000, + }, + greenaviator = { + name = 'Зеленые авиаторы', + desc = 'Стильные очки "капельки" зеленого цвета, имеющие достаточно удобную форму оправы и линз', + icon = 'octoteam/icons/glasses_aviator.png', + mdl = 'models/modified/glasses01.mdl', + pos = Vector(-4.35, 0, -0.1), + skin = 2, + price = 12000, + }, + brownaviator = { + name = 'Коричневые авиаторы', + desc = 'Стильные очки "капельки" коричневого цвета, имеющие достаточно удобную форму оправы и линз', + icon = 'octoteam/icons/glasses_aviator.png', + mdl = 'models/modified/glasses01.mdl', + pos = Vector(-4.35, 0, -0.1), + skin = 3, + price = 12000, + }, + orangeaviator = { + name = 'Оранжевые авиаторы', + desc = 'Стильные очки "капельки" оранжевого цвета, имеющие достаточно удобную форму оправы и линз', + icon = 'octoteam/icons/glasses_aviator.png', + mdl = 'models/modified/glasses01.mdl', + pos = Vector(-4.35, 0, -0.1), + skin = 4, + price = 12000, + }, + greyaviator = { + name = 'Серые авиаторы', + desc = 'Стильные очки "капельки" серого цвета, имеющие достаточно удобную форму оправы и линз', + icon = 'octoteam/icons/glasses_aviator.png', + mdl = 'models/modified/glasses01.mdl', + pos = Vector(-4.35, 0, -0.1), + skin = 5, + price = 12000, + }, + greyfedora = { + name = 'Серая федора', + desc = 'Легкая шляпка серого цвета, обвитая лентой', + icon = 'octoteam/icons/detective_hat.png', + mdl = 'models/modified/hat01_fix.mdl', + pos = Vector(-4.1, 0, 1.8), + ang = Angle(0, 0, 0), + scale = 0.96, + skin = 0, + price = 46000, + }, + fedora = { + name = 'Черная федора', + desc = 'Легкая шляпка черного цвета, обвитая лентой', + icon = 'octoteam/icons/detective_hat.png', + mdl = 'models/modified/hat01_fix.mdl', + pos = Vector(-4.1, 0, 1.8), + ang = Angle(0, 0, 0), + scale = 0.96, + skin = 1, + price = 46000, + }, + whitefedora = { + name = 'Белая федора', + desc = 'Легкая шляпка белого цвета, обвитая лентой', + icon = 'octoteam/icons/detective_hat.png', + mdl = 'models/modified/hat01_fix.mdl', + pos = Vector(-4.1, 0, 1.8), + ang = Angle(0, 0, 0), + scale = 0.96, + skin = 2, + price = 46000, + }, + beigefedora = { + name = 'Бежевая федора', + desc = 'Легкая шляпка бежевого цвета, обвитая лентой', + icon = 'octoteam/icons/detective_hat.png', + mdl = 'models/modified/hat01_fix.mdl', + pos = Vector(-4.1, 0, 1.8), + ang = Angle(0, 0, 0), + scale = 0.96, + skin = 3, + price = 46000, + }, + smartglasses = { + name = 'R&C SmartGlasses', + desc = 'Умные очки от компании R&C, выполненные из алюминия черного цвета. На боковой панельке очков есть сенсорная панель управления. На правой линзе выведено изображение с доступом к приложениям', + icon = octolib.icons.color('rc_glasses'), + mdl = 'models/rnc/acessories/rnc_smart_glasses01.mdl', + pos = Vector(-2.6, 0.1, -0.2), + ang = Angle(0, 0, 0), + scale = 0.92, + }, +} diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/admin/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/admin/client.lua new file mode 100644 index 0000000..b275013 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/admin/client.lua @@ -0,0 +1,33 @@ +octochat.defineCommand('/spectate', { + permission = 'FSpectate', + aliases = {'!spectate', '~spectate'}, +}) +octochat.defineCommand('/spawn', { + check = DarkRP.isAdmin, + aliases = {'/respawn'}, +}) +octochat.defineCommand('!invisible', { + aliases = {'~invisible', '/invisible', '!cloak', '~cloak', '/cloak'}, + permission = 'Invisible', +}) + +local adminData = {check = DarkRP.isAdmin} +octochat.defineCommand('/resetname', adminData) +octochat.defineCommand('/resetdesc', adminData) +octochat.defineCommand('/forceunlock', adminData) +octochat.defineCommand('/forcelock', adminData) +octochat.defineCommand('/forceown', adminData) +octochat.defineCommand('/forceunown', adminData) +octochat.defineCommand('/admintell', adminData) +octochat.defineCommand('/admintellall', adminData) +octochat.defineCommand('/teamban', adminData) +octochat.defineCommand('/teamunban', adminData) + + +local superAdminData = {check = DarkRP.isSuperAdmin} +octochat.defineCommand('/addjailpos', superAdminData) +octochat.defineCommand('/clearjailpos', superAdminData) +octochat.defineCommand('/groupown', superAdminData) +octochat.defineCommand('/groupunown', superAdminData) +octochat.defineCommand('/jobown', superAdminData) +octochat.defineCommand('/jobunown', superAdminData) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/admin/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/admin/server.lua new file mode 100644 index 0000000..d0e5f63 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/admin/server.lua @@ -0,0 +1,279 @@ +octochat.registerCommand('/spectate', { + cooldown = 1.5, + execute = function(ply, txt) ply:ConCommand('FSpectate ' .. txt) end, + aliases = {'!spectate', '~spectate'}, + permission = 'FSpectate', +}) + +octochat.registerCommand('/spawn', { + cooldown = 1.5, + log = true, + consoleFriendly = true, + execute = function(ply, txt) + local target = txt == '' and ply or util.FindPlayer(txt) + if not IsValid(target) then return L.player_not_found end + + if target:Alive() then target:KillSilent() end + target:SetNetVar('_SpawnTime', CurTime()) + + target:Notify(L.you_rescued:format(octochat.safePlayerName(ply))) + octochat.safeNotify(ply, L.you_rescued_by:format(target:Nick())) + end, + aliases = {'/respawn'}, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/addjailpos', { + execute = function(ply) + dbgPolice.addJailPos(ply:GetPos()) + ply:Notify('Позиция тюрьмы добавлена') + end, + check = DarkRP.isSuperAdmin, +}) + +octochat.registerCommand('/clearjailpos', { + execute = function(ply) + dbgPolice.clearJailPos() + ply:Notify('Позиции тюрьмы очищены') + end, + check = DarkRP.isSuperAdmin, +}) +octochat.registerCommand('/resetname', { + log = true, + consoleFriendly = true, + execute = function(ply, txt) + local target = txt == '' and ply or util.FindPlayer(txt) + if not IsValid(target) then return L.player_not_found end + + local name = L.names[1][math.random(#L.names[1])] .. ' ' .. L.names[2][math.random(#L.names[2])] + target:SetName(name) + target:Notify('ooc', 'Администрация посчитала твое ролевое имя нарушающим правила и сбросила его. Ты можешь поставить новое и сменить персонажа') + target:ConCommand('dbg_name "' .. name .. '"') + octochat.safeNotify(ply, 'hint', 'Ролевое имя игрока сменено на ' .. name) + end, + check = DarkRP.isAdmin, +}) +octochat.registerCommand('/resetdesc', { + log = true, + consoleFriendly = true, + execute = function(ply, txt) + local target = txt == '' and ply or util.FindPlayer(txt) + if not IsValid(target) then return L.player_not_found end + + target:SetNetVar('dbgDesc') + target:Notify('ooc', 'Администрация посчитала твое описание внешности нарушающим правила и сбросила его. Ты можешь поставить новое и сменить персонажа') + octochat.safeNotify(ply, 'hint', 'Описание внешности игрока "' .. target:Name() .. '" сброшено') + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/forceunlock', { + execute = function(ply) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + door:DoUnlock() + ply:Notify('Открыто') + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/forcelock', { + execute = function(ply) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + door:DoLock() + ply:Notify('Закрыто') + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/forceown', { + execute = function(ply, _, args) + if not args[1] or args[1] == '' then return 'Формат: /forceown "Ник игрока"' end + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + local tgt = octochat.findPlayer(args[1]) + if not tgt then return 'Укажи ник игрока' end + door:SetPlayerOwner(tgt) + ply:Notify('Теперь ' .. tgt:Name() .. ' владеет помещением') + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/forceunown', { + execute = function(ply) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + local cur = door:GetPlayerOwner() + if not cur then return 'Этой дверью не владеет игрок' end + door:RemoveOwner(cur) + local tgt = player.GetBySteamID(cur) + ply:Notify('Теперь ' .. (IsValid(tgt) and tgt:Name() or cur) .. ' не владеет помещением') + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/groupown', { + execute = function(ply, id) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + if not dbgDoorGroups.groups[id] then return 'Такой группы не существует' end + if door:AddGroupOwner(id) then + ply:Notify('Теперь ' .. dbgDoorGroups.groups[id] .. ' владеет помещением') + end + end, + check = DarkRP.isSuperAdmin, +}) + +octochat.registerCommand('/groupunown', { + execute = function(ply, id) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + if not dbgDoorGroups.groups[id] then return 'Такой группы не существует' end + if door:RemoveOwner('g:' .. id) then + ply:Notify('Теперь ' .. dbgDoorGroups.groups[id] .. ' не владеет помещением') + end + end, + check = DarkRP.isSuperAdmin, +}) + +octochat.registerCommand('/jobown', { + execute = function(ply, cmd) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + local job = DarkRP.getJobByCommand(cmd) + if not job then return 'Такой профессии не существует' end + if door:AddJobOwner(cmd) then + ply:Notify('Теперь ' .. job.name .. ' владеет помещением') + end + end, + check = DarkRP.isSuperAdmin, +}) + +octochat.registerCommand('/jobunown', { + execute = function(ply, cmd) + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + local job = DarkRP.getJobByCommand(cmd) + if not job then return 'Такой професии не существует' end + if door:RemoveOwner('j:' .. cmd) then + ply:Notify('Теперь ' .. job.name .. ' не владеет помещением') + end + end, + check = DarkRP.isSuperAdmin, +}) + + +-- +-- INVISIBILITY +-- +octochat.registerCommand('!invisible', { + cooldown = 1.5, + execute = function(ply) + local val = not ply:IsInvisible() + ply.manualInvisibility = val or nil + ply:MakeInvisible(val) + end, + aliases = {'~invisible', '/invisible', '!cloak', '~cloak', '/cloak'}, + permission = 'Invisible', +}) + + +-- +-- ADMINTELL +-- +local function fillInForm(ply, target, time, title, msg) + local isPlayer = target ~= nil + + octolib.request.send(ply, { + {name = L.request_player, desc = 'Оставь заголовок и текст уведомления пустыми, если хочешь скрыть текущее уведомление у игрока'}, + {type = 'numSlider', min = 3, max = 120, dec = 0, txt = L.time, default = time or 10}, + {type = 'strShort', ph = L.title2, default = title}, + {type = 'strLong', ph = L.trigger_text, default = msg}, + }, function(data) + + if not istable(data) then return end + if not (isnumber(data[2]) and isstring(data[3]) and isstring(data[4])) then return end + + if not IsValid(target) and isPlayer then + if IsValid(ply) then ply:Notify('warning', 'Игрок вышел') end + return + end + + hook.Run('dbg-admin.tell', ply, data[2], data[3], data[4], target) + octolib.notify.send(target, 'admin', data[2], data[3], data[4]) + + end) + +end + +octochat.registerCommand('/admintell', { + consoleFriendly = true, + execute = function(ply, _, args) + local target, txt = octochat.pickOutTarget(args) + if not IsValid(target) then return txt or 'Не удалось найти такого игрока' end + + args = octochat.explodeArg(txt) + local time, title, msg = tonumber(args[2] or ''), args[3] or '', table.concat(args, ' ', 4) or '' + if not time or (title == '' and msg == '') then + if not IsValid(ply) then return 'Формат: /admintell "Ник игрока" Время "Заголовок" "Сообщение"' end + fillInForm(ply, target, time, title, msg) + else + hook.Run('dbg-admin.tell', ply, time, title, msg, target) + octolib.notify.send(target, 'admin', time, title, msg) + end + + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/admintellall', { + consoleFriendly = true, + execute = function(ply, _, args) + + local time, title, msg = tonumber(args[1] or ''), args[2] or '', table.concat(args, ' ', 3) or '' + if not time or (title == '' and msg == '') then + if not IsValid(ply) then return 'Формат: /admintellall Время "Заголовок" "Сообщение"' end + fillInForm(ply, nil, time, title, msg) + else + hook.Run('dbg-admin.tell', ply, time, title, msg) + octolib.notify.sendAll('admin', time, title, msg) + end + + end, + check = DarkRP.isAdmin, +}) + + +-- +-- TEAM BAN/UNBAN +-- +octochat.registerCommand('/teamban', { + execute = function() + return 'Команда временно не работает' + end, + check = DarkRP.isAdmin, +}) + +octochat.registerCommand('/teamunban', { + execute = function() + return 'Команда временно не работает' + end, + check = DarkRP.isAdmin, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/goverment/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/goverment/client.lua new file mode 100644 index 0000000..e11c2cc --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/goverment/client.lua @@ -0,0 +1,3 @@ +local emsData = {check = DarkRP.isGov} +octochat.defineCommand('/gr', emsData) +octochat.defineCommand('/panicbutton', emsData) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/goverment/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/goverment/server.lua new file mode 100644 index 0000000..79661c3 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/goverment/server.lua @@ -0,0 +1,57 @@ +octochat.registerCommand('/gr', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + + if txt == '' then return 'Формат: /gr Текст сообщения' end + + local veh + for _, ent in ipairs(ents.FindInSphere(ply:GetPos(), 100)) do + if ent.IsSimfphyscar and not ent.preventEms then veh = ent break end + end + if not IsValid(veh) then + return 'Рядом должен находиться служебный автомобиль' + end + + octochat.talkToRange(ply, 1500, Color(13, 134, 255), L.megaphone, color_white, txt) + veh:EmitSound('ambient/chatter/cb_radio_chatter_' .. math.random(1,3) .. '.wav', 130, 100, 1) + + end, + check = DarkRP.isGov, +}) + +local leaveMeAloneID = 1 +octochat.registerCommand('/panicbutton', { + cooldown = 60, + log = true, + execute = function(ply) + local job, jobname = ply:getJobTable() + if job then jobname = job.name end + local customJob = ply:GetNetVar('customJob') + if customJob then jobname = unpack(customJob) end + + ply:DoEmote('{name} нажимает кнопку паники') + + local msg = ('%s %s передает свое местоположение, используя тревожную кнопку!'):format(jobname, ply:Name()) + local marker = { + id = 'cpPanicBtn' .. leaveMeAloneID, + group = 'cpPanicBtn', + txt = 'Кнопка паники', + pos = ply:GetPos() + Vector(0,0,40), + col = Color(102,170,170), + des = {'timedist', { 600, 300 }}, + icon = 'octoteam/icons-32/exclamation.png', + size = 32, + } + for _,v in ipairs(player.GetAll()) do + if v:isCP() then + v:Notify('warning', msg) + v:EmitSound('npc/attack_helicopter/aheli_damaged_alarm1.wav', 45, 100, 0.5) + v:AddMarker(marker) + end + end + leaveMeAloneID = leaveMeAloneID + 1 + + end, + check = DarkRP.isGov, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/ic/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/ic/client.lua new file mode 100644 index 0000000..46b9b48 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/ic/client.lua @@ -0,0 +1,38 @@ +octochat.defineCommand('/whisper', { + aliases = {'/w'}, +}) +octochat.defineCommand('/yell', { + aliases = {'/y'}, +}) + +octochat.defineCommand('/me', true) +octochat.defineCommand('/it', true) +octochat.defineCommand('/pit', true) +octochat.defineCommand('/toit', true) +octochat.defineCommand('/whispertoit', { + aliases = {'/wtoit'}, +}) +octochat.defineCommand('/yelltoit', { + aliases = {'/ytoit'}, +}) +octochat.defineCommand('//it', { + permission = 'DBG: Глобальный IT', +}) + +octochat.defineCommand('/d', true) +octochat.defineCommand('/sms', true) + +octochat.defineCommand('/roll', true) +octochat.defineCommand('/dice', true) +octochat.defineCommand('/coin', true) +octochat.defineCommand('/card', true) +octochat.defineCommand('/rockpaperscissors', true) +octochat.defineCommand('/dbg_getidea', true) + +octochat.defineCommand('/give', true) +octochat.defineCommand('/dropmoney', { + aliases = {'/moneydrop'}, +}) +octochat.defineCommand('/putmoney', { + aliases = {'/moneyput'}, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/ic/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/ic/server.lua new file mode 100644 index 0000000..8a61000 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/ic/server.lua @@ -0,0 +1,407 @@ +octochat.registerCommand('/whisper', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) octochat.genericSayFunc(ply, txt, 90, ' шепчет: ') end, + aliases = {'/w'}, +}) + +octochat.registerCommand('/yell', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + if txt == '' then return end + if txt:sub(-1) ~= '!' then txt = txt .. '!' end + octochat.genericSayFunc(ply, txt, 550, ' кричит: ') + end, + aliases = {'/y'}, +}) + +octochat.registerCommand('/me', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + if txt == '' then return 'Формат: /me действие от третьего лица' end + ply:DoEmote('{name} ' .. utf8.lower(utf8.sub(txt, 1, 1)) .. utf8.sub(txt, 2)) + end, +}) + +octochat.registerCommand('/it', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + if txt == '' then return 'Формат: /it Описание обстановки от третьего лица' end + octochat.talkToRange(ply, 250, octochat.textColors.rp, utf8.upper(utf8.sub(txt, 1, 1)) .. utf8.sub(txt, 2) .. ' ({name})' % {name = ply:Name()}) + end, +}) + +octochat.registerCommand('/pit', { + cooldown = 1.5, + log = true, + execute = function(ply, _, args) + local target, txt = octochat.pickOutTarget(args) + if not target then return txt or 'Формат: /pit "Имя игрока" Текст обстановки' end + if txt == '' then return 'Формат: /pit "Имя игрока" Текст обстановки' end + octochat.talkTo(target, octochat.textColors.rp, '[Приватный IT, ', ply:Name(), '] ', txt) + octochat.talkTo(ply, octochat.textColors.rp, '[Приватный IT, ', target:Name(), '] ', txt) + end, +}) + + +local function toit(ply, txt, radius, action) + local args = string.Explode(' * ', txt) + if not args[1] or args[1] == '' or not args[2] or args[2] == '' then return 'Формат: /toit действие * Фраза' end + action = action or txt:sub(-1) == '?' and 'спрашивает:' or 'говорит:' + octochat.genericSayFunc(ply, args[2], radius, (' %s, %s'):format(args[1], action)) + if ply:GetNetVar('os_govorilka') and not ply:IsGovorilkaMuted() then + ply:DoVoice(args[2], ply:GetVoice(), heard) + end +end + +octochat.registerCommand('/toit', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + toit(ply, txt, 250) + end, +}) + +octochat.registerCommand('/whispertoit', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + toit(ply, txt, 90, 'шепчет: ') + end, + aliases = {'/wtoit'}, +}) + +octochat.registerCommand('/yelltoit', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + toit(ply, txt, 550, 'кричит: ') + end, + aliases = {'/ytoit'}, +}) + +octochat.registerCommand('//it', { + cooldown = 1.5, + log = true, + execute = function(_, txt) + octochat.talkTo(nil, octochat.textColors.rp, txt) + end, + permission = 'DBG: Глобальный IT', +}) + +-- LOTTERY + +local function lotterySay(ply, msg, all) + if all then + local receivers = octolib.array.filter(player.GetAll(), function(ply) return ply:HasPhone() end) + for i, receiver in ipairs(receivers) do + receiver:SendSMS(octochat.textColors.rp, 'Лотерейное бюро', L.owner_sms, Color(250,250,200), msg) + end + return + else + if not ply:HasPhone() then return end + ply:SendSMS(octochat.textColors.rp, 'Лотерейное бюро', L.owner_sms, Color(250,250,200), msg) + end +end + +local function lotteryFilterValid(sid) + local ply = player.GetBySteamID(sid) + return IsValid(ply) and ply or nil +end + +local entries = {} +local lotteryStarted, lotteryAmount = false, 0 +local function startLottery() + + if player.GetCount() <= 5 then return end + + lotteryAmount = math.Round(math.random(GAMEMODE.Config.minlotterycost, GAMEMODE.Config.maxlotterycost)) + + hook.Run('lotteryStarted', lotteryAmount) + + lotterySay(nil, L.lottery_started:format(DarkRP.formatMoney(lotteryAmount)), true) + + lotteryStarted = true + + timer.Simple(300, function() + local online = octolib.array.map(entries, lotteryFilterValid) + if not online[1] then + lotterySay(nil, L.lottery_noone_entered, true) + return hook.Run('lotteryEnded', entries) + end + + local chosen = octolib.array.random(online) + local sum = math.Round((#entries * lotteryAmount) * 0.95) + hook.Run('lotteryEnded', entries, chosen, sum) + chosen:BankAdd(sum) + lotterySay(chosen, L.lottery_won:format(DarkRP.formatMoney(sum))) + entries, lotteryAmount, lotteryStarted = {}, 0, false + end) + +end +timer.Create('dbg-lottery', octolib.time.toSeconds(1, 'hour'), 0, startLottery) + +local function joinLottery(ply, args) + if not lotteryStarted then return lotterySay(ply, 'Лотерея сейчас не идёт!') end + + table.remove(args, 1) + local txt = string.Trim(string.Implode(' ', args)) + + if not ply:BankHas(lotteryAmount) then + return false, L.cant_afford:format('участия в лотерее'), 'warning' + end + + for _, v in ipairs(entries) do + if ply:SteamID() == v then + return lotterySay(ply, 'Вы уже участвуете в лотерее!') + end + end + + ply:BankAdd(-lotteryAmount) + entries[#entries + 1] = ply:SteamID() + lotterySay(ply, L.lottery_entered) + + hook.Run('playerEnteredLottery', ply) +end + +local groups = { 'dpd', 'medic', 'fire', 'coroners', 'prison', 'wcso', 'csd', 'fbi', 'gov' } +local function isInDepartment(ply) + if ply:Team() == TEAM_ADMIN then return true end + for _, groupID in ipairs(groups) do + if ply.currentOrg == groupID then + return true + end + end + return false +end +octochat.registerCommand('/d', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + octochat.talkTo(octolib.array.filter(player.GetAll(), isInDepartment), octochat.textColors.rp, ply:Name(), ' передает на рацию департаментов: ', color_white, txt) + end, + check = isInDepartment, +}) + +octochat.registerCommand('/sms', { + cooldown = 1.5, + log = true, + phone = true, + execute = function(ply, _, args) + + if not ply:Alive() or ply:IsGhost() then + return L.death_cannot_do_this + end + if ply:isArrested() then + return L.you_arrested + end + if ply:IsHandcuffed() then + return L.handcuffed_cannot_do_this + end + + if txt == '' then return 'Формат: /sms "Имя игрока" Текст сообщения' end + if args[1] == 'lottery' then + joinLottery(ply, args) + return + end + local target, txt = octochat.pickOutTarget(args) + if not target then return txt or 'Формат: /sms "Имя игрока" Текст сообщения' end + if not target:HasPhone() then return L.abonent_unavailable end + + ply:DoEmote('{name} отправляет SMS') + if not target:IsHandcuffed() then + target:SendSMS(octochat.textColors.rp, ply:Name(), L.owner_sms, Color(250,250,200), txt) + else + target:Notify('Тебе пришло SMS от ' .. ply:Name() .. ', но ты не можешь его прочитать, так как руки связаны') + end + ply:SendSMS(octochat.textColors.rp, target:Name(), L.target_sms, Color(250,250,200), txt) + + end, +}) + +octochat.registerCommand('/roll', { + cooldown = 1.5, + log = true, + execute = function(ply, _, args) + local roll = ply:IsSuperAdmin() and tonumber(args[1] or '') or math.random(100) + ply:TalkToRange(350, octochat.textColors.rp, L.have_chance:format(ply:Name()), color_white, L.out_of_100:format(roll)) + end, +}) + +octochat.registerCommand('/dice', { + cooldown = 1.5, + log = true, + execute = function(ply, _, args) + local roll1 = ply:IsSuperAdmin() and tonumber(args[1] or '') or math.random(6) + local roll2 = ply:IsSuperAdmin() and tonumber(args[2] or '') or math.random(6) + ply:TalkToRange(350, octochat.textColors.rp, L.threw_the_dice:format(ply:Name()), color_white, L.dice_and:format(roll1, roll2)) + end, +}) + +octochat.registerCommand('/coin', { + cooldown = 1.5, + log = true, + execute = function(ply, _, args) + local roll = ply:IsSuperAdmin() and tonumber(args[1] or '') or math.random(2) + ply:TalkToRange(350, octochat.textColors.rp, ply:Name(), ' подбрасывает монетку: ', color_white, roll == 1 and 'орел' or 'решка') + end, +}) + +local cards = {} +for _, w1 in ipairs(L.card_parts1) do + for _, w2 in ipairs(L.card_parts2) do + cards[#cards + 1] = ('%s %s'):format(w1, w2) + end +end + +octochat.registerCommand('/card', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + local card = ply:IsSuperAdmin() and txt ~= '' and string.lower(txt) or table.Random(cards) + local found = false + for _,v in ipairs(cards) do + if v == card then + found = true + break + end + end + if not found then card = table.Random(cards) end + + ply:TalkToRange(350, octochat.textColors.rp, L.randomcard:format(ply:Name()), color_white, card) + + end, +}) + +octochat.registerCommand('/rockpaperscissors', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + + local gest = ply:IsSuperAdmin() and txt ~= '' and string.lower(txt) or table.Random(L.rps) + local found = false + for _,v in ipairs(L.rps) do + if v == gest then + found = true + break + end + end + if not found then gest = table.Random(L.rps) end + + ply:TalkToRange(350, octochat.textColors.rp, L.gesture_showed:format(ply:Name()), color_white, gest) + end, +}) + +octochat.registerCommand('/dbg_getidea', { + cooldown = 1.5, + execute = function(ply) ply:Notify(L.ideas[math.random(#L.ideas)]) end, +}) + +octochat.registerCommand('/give', { + execute = function(ply, amount) + + amount = amount:gsub('[^0-9]', '') + if amount == '' then return 'Формат: /give количество' end + amount = tonumber(amount) + if not amount then return 'Формат: /give количество' end + amount = math.floor(amount) + if amount <= 0 then return 'Укажи положительное число' end + if not ply:canAfford(amount) then return 'У тебя нет столько денег' end + + local target = octolib.use.getTrace(ply).Entity + if not IsValid(target) or not target:IsPlayer() then return L.must_be_looking_at:format('игрока') end + if target:getJobTable().notHuman then return L.must_be_looking_at:format('живого человека') end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_GIVE) + ply:addMoney(-amount) + ply:Notify(L.you_gave:format(target:Name(), DarkRP.formatMoney(amount))) + + timer.Simple(1.2, function() + if not IsValid(ply) or not IsValid(target) then return end + + target:addMoney(amount) + target:Notify(L.has_given:format(ply:Name(), DarkRP.formatMoney(amount))) + end) + + hook.Run('DarkRP.payPlayer', ply, target, amount or 1) + + end, +}) + +octochat.registerCommand('/dropmoney', { + cooldown = 3, + execute = function(ply, amount) + + amount = amount:gsub('[^0-9]', '') + if amount == '' then return 'Формат: /dropmoney количество' end + amount = tonumber(amount) + if not amount then return 'Формат: /dropmoney количество' end + amount = math.floor(amount) + if amount <= 0 then return 'Укажи положительное число' end + if amount >= 10000000 then return 'Ты не можешь выбросить больше ' .. DarkRP.formatMoney(10000000) end + if not ply:canAfford(amount) then return 'У тебя нет столько денег' end + + local taken = ply:addMoney(-amount) + if (taken or 0) < 1 then return end + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_GIVE) + + timer.Simple(0.88, function() + + if not IsValid(ply) then return end + + local bonePos, boneAng = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_L_Hand') or 16) + local spawnPos, spawnAng = LocalToWorld(Vector(0,0,0), Angle(0,60,-15), bonePos, boneAng) + local drop = DarkRP.createMoneyBag(spawnPos, taken, ply) + drop:SetAngles(spawnAng) + drop.droppedBy = ply + + local phys = drop:GetPhysicsObject() + if phys then + local dir = ply:GetAimVector() + dir.z = 0 + phys:SetVelocity(dir * 200) + end + + end) + + end, + aliases = {'/moneydrop'}, +}) + +octochat.registerCommand('/putmoney', { + cooldown = 3, + execute = function(ply, amount) + + amount = amount:gsub('[^0-9]', '') + if amount == '' then return 'Формат: /putmoney количество' end + amount = tonumber(amount) + if not amount then return 'Формат: /putmoney количество' end + amount = math.floor(amount) + if amount <= 0 then return 'Укажи положительное число' end + if amount >= 10000000 then return 'Ты не можешь выбросить больше ' .. DarkRP.formatMoney(10000000) end + if not ply:canAfford(amount) then return 'У тебя нет столько денег' end + + local taken = ply:addMoney(-amount) + if (taken or 0) < 1 then return end + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_GIVE) + + timer.Simple(0.88, function() + + if not IsValid(ply) then return end + + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * 85, + filter = ply, + }) + local drop = DarkRP.createMoneyBag(tr.HitPos, taken, ply) + drop.droppedBy = ply + + end) + + end, + aliases = {'/moneyput'}, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/mayor/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/mayor/client.lua new file mode 100644 index 0000000..4abd211 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/mayor/client.lua @@ -0,0 +1,20 @@ +local mayorData = {check = DarkRP.isMayor} +octochat.defineCommand('/broadcast', mayorData) +octochat.defineCommand('/addlaw', mayorData) +octochat.defineCommand('/removelaw', mayorData) +octochat.defineCommand('/resetlaws', mayorData) +octochat.defineCommand('/lockdown', mayorData) +octochat.defineCommand('/unlockdown', mayorData) +octochat.defineCommand('/renamecity', mayorData) +octochat.defineCommand('/resetcity', true) + +local function updateGMFuncs() + if not DarkRP then return end + + function DarkRP.getLaws() + return netvars.GetNetVar('laws') + end + +end +hook.Add('darkrp.loadModules', 'dbg-commands.mayor', updateGMFuncs) +updateGMFuncs() diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/mayor/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/mayor/server.lua new file mode 100644 index 0000000..87b30c6 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/mayor/server.lua @@ -0,0 +1,249 @@ +octochat.registerCommand('/broadcast', { + cooldown = 1.5, + log = true, + execute = function(_, txt) + if txt == '' then return 'Формат: /broadcast Текст объявления' end + octochat.talkTo(nil, Color(214,74,65), L.broadcast_text, Color(250,250,200), unpack(octolib.string.splitByUrl(txt))) + end, + check = DarkRP.isMayor, +}) + +-- LAWS +octochat.registerCommand('/addlaw', { + log = true, + execute = function(ply, txt) + + local can, why = hook.Run('canEditLaws', ply, 'addLaw', txt) + if can == false then + return why or 'Ты не можешь изменять законы' + end + + if txt == '' then return 'Формат: /addlaw Текст закона' end + + if utf8.len(txt) < 10 then + return L.law_too_short + end + + local laws = netvars.GetNetVar('laws') + + if #laws >= 20 then + return L.laws_full + end + + laws[#laws + 1] = txt + netvars.SetNetVar('laws', laws) + + hook.Run('addLaw', #laws, txt, ply) + ply:Notify(L.law_added) + + end, + check = DarkRP.isMayor, +}) + +octochat.registerCommand('/removelaw', { + log = true, + execute = function(ply, _, args) + + local can, why = hook.Run('canEditLaws', ply, 'removeLaw', txt) + if can == false then + return why or 'Ты не можешь изменять законы' + end + + local laws = netvars.GetNetVar('laws') + + local i = tonumber(args[1]) + if not i or not laws[i] then + return 'Неверный номер закона' + end + + if GAMEMODE.Config.DefaultLaws[i] then + return L.default_law_change_denied + end + + local law = laws[i] + table.remove(laws, i) + netvars.SetNetVar('laws', laws) + + hook.Run('removeLaw', i, law, ply) + ply:Notify(L.law_removed) + + end, + check = DarkRP.isMayor, +}) + +octochat.registerCommand('/resetlaws', { + consoleFriendly = true, + log = true, + execute = function(ply) + + if IsValid(ply) then + local can, why = hook.Run('canEditLaws', ply, 'resetLaws') + if can == false then + return why or 'Ты не можешь изменять законы' + end + end + + hook.Run('resetLaws', ply) + DarkRP.resetLaws() + octochat.safeNotify(ply, L.law_reset) + + end, + check = DarkRP.isMayor, +}) + +local function updateGMFuncs() + if not DarkRP then return end + + function DarkRP.resetLaws() + netvars.SetNetVar('laws', table.Copy(GAMEMODE and GAMEMODE.Config.DefaultLaws or {})) + end + DarkRP.resetLaws() + + function DarkRP.getLaws() + return netvars.GetNetVar('laws') + end + +end +hook.Add('darkrp.loadModules', 'dbg-commands.laws', updateGMFuncs) +updateGMFuncs() + +-- LOCKDOWN +local lastLockdown = -math.huge +local function updateGMFuncs() + if not DarkRP then return end + + function DarkRP.lockdown() + for _,v in ipairs(player.GetAll()) do + v:ConCommand('play ' .. GAMEMODE.Config.lockdownsound .. '\n') + end + netvars.SetNetVar('lockdown', true) + octolib.notify.sendAll('warning', L.lockdown_started) + end + + function DarkRP.unLockdown() + netvars.SetNetVar('lockdown', nil) + lastLockdown = CurTime() + octolib.notify.sendAll('hint', L.lockdown_ended) + end + +end +hook.Add('darkrp.loadModules', 'dbg-commands.lockdown', updateGMFuncs) +updateGMFuncs() + + +octochat.registerCommand('/lockdown', { + execute = function(_, txt) + + if netvars.GetNetVar('lockdown') then + return 'Комендантский час уже введен. Выполни /unlockdown, чтобы отменить его' + end + + if not GAMEMODE.Config.lockdown then + return 'Комендантский час отключен' + end + + if lastLockdown > CurTime() - GAMEMODE.Config.lockdowndelay then + return 'Комендантский час уже недавно вводился' + end + + txt = string.Trim(txt) + if txt ~= '' then + octochat.talkTo(nil, Color(214,74,65), L.broadcast_text, Color(250,250,200), unpack(octolib.string.splitByUrl(txt))) + end + DarkRP.lockdown() + + end, + check = DarkRP.isMayor, +}) + +octochat.registerCommand('/unlockdown', { + execute = function() + if not netvars.GetNetVar('lockdown') then + return 'Сейчас не объявлен комендантский час. Выполни /lockdown, чтобы ввести его' + end + if not GAMEMODE.Config.lockdown then + return 'Комендантский час отключен' + end + DarkRP.unLockdown() + end, + check = DarkRP.isMayor, +}) + +-- +-- DOBROGRAD NAME +-- + +local names = L.town_names + +local function updateServerName() + if CFG.dev then + RunConsoleCommand('hostname', CFG.defaultHostName or (L.build_town .. netvars.GetNetVar('cityName', L.dobrograd))) + else + local name = netvars.GetNetVar('cityName', L.dobrograd) + if name == L.dobrograd then + RunConsoleCommand('hostname', CFG.defaultHostName or names[game.GetMap()] or names.rp_eastcoast_v4c) + else + RunConsoleCommand('hostname', L.history_town .. name) + end + end +end +timer.Simple(5, updateServerName) + +local function renameCity(name) + name = name or L.dobrograd + netvars.SetNetVar('cityName', name) + octolib.notify.sendAll('ooc', L.name_town_change:format(name)) + updateServerName() +end + +local function cleanUpAfterMayorLeft() + if netvars.GetNetVar('cityName', L.dobrograd) ~= L.dobrograd then + renameCity() + end + if netvars.GetNetVar('lockdown') then + DarkRP.unLockdown() + end +end + +hook.Add('OnPlayerChangedTeam', 'dbg-mayor.resetCity', function(ply, before) + if before ~= TEAM_MAYOR then return end + if not ply:Alive() or ply:IsGhost() then return end + cleanUpAfterMayorLeft() +end) +hook.Add('PlayerDeath', 'dbg-mayor.resetCity', function(ply) + if not ply:isMayor() then + ply:changeTeam(1, true, true) + cleanUpAfterMayorLeft() + end +end) +hook.Add('PlayerDisconnected', 'dbg-mayor.recetCity', function(ply) + if ply:isMayor() then cleanUpAfterMayorLeft() end +end) + +octochat.registerCommand('/renamecity', { + cooldown = 1.5, + log = true, + execute = function(ply, name) + + if not ply:GetNetVar('os_dobro') then return L.this_feature_only_dobro end + if not ply:canAfford(5000) then return L.not_enough_money2 end + + if name == '' then return 'Формат: /renamecity Название' end + if utf8.len(name) > 31 then return L.title_too_long end + + renameCity(name) + ply:addMoney(-5000) + + end, + check = DarkRP.isMayor, +}) + +octochat.registerCommand('/resetcity', { + cooldown = 1.5, + log = true, + consoleFriendly = true, + execute = function(ply) renameCity() end, + check = function(ply) + return DarkRP.isMayor(ply) or ply:IsSuperAdmin(), L.can_do_only_mayor + end, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/misc/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/misc/client.lua new file mode 100644 index 0000000..a879656 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/misc/client.lua @@ -0,0 +1,121 @@ +octochat.defineCommand('/advert', { + aliases = {'/ad'}, +}) + +octochat.defineCommand('/radio', { + aliases = {'/r'}, +}) + +octochat.defineCommand('/wradio', { + aliases = {'/wr'}, +}) + +octochat.defineCommand('/yradio', { + aliases = {'/yr'}, +}) + +octochat.defineCommand('/lradio', { + aliases = {'/lr'}, +}) + +octochat.defineCommand('/callmed', true) +octochat.defineCommand('/callmech', true) +octochat.defineCommand('/callfire', true) +octochat.defineCommand('/callworker', true) + +octochat.defineCommand('/givecert', true) +octochat.defineCommand('/delcert', true) + +octochat.defineCommand('/ammo', true) +octochat.defineCommand('/bank', true) +octochat.defineCommand('/getbank', true) +octochat.defineCommand('/time', true) + +-- rewards +octochat.defineCommand('/rewards', true) +octochat.defineCommand('/forum', true) + +octochat.defineCommand('/title', true) + +octochat.defineCommand('/write', true) +octochat.defineCommand('/removewrite', true) +octochat.defineCommand('/removewrites', true) + +octochat.defineCommand('/dropweapon', { + aliases = {'/drop'}, +}) +octochat.defineCommand('/holsterweapon', { + aliases = {'/holster'}, +}) + +local function niceTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format("%02i:%02i:%02i", h, m, s) + +end + +local textRules = [[Правила использования команды /advert +Этот чат используется исключительно для рекламы и объявлений, стоимость публикации - 250Р + +Реклама или объявления, поданные через этот чат, не могут быть анонимными, являться "слухами", содержать в себе нелегальные новости или услуги, а также любую неролевую информацию + +За несоблюдение правил пользования чатом тебе может быть выдано наказание]] + +netstream.Hook('octochat.advert', function(text, timeLeft) + local f = vgui.Create 'DFrame' + f:SetSize(400, 355) + f:SetTitle('Отправить рекламу') + f:Center() + f:MakePopup() + + local e = octolib.textEntry(f, 'Текст сообщения') + e:SetTall(100) + e:SetDrawLanguageID(false) + e:SetContentAlignment(7) + e:SetText(text) + e:SetMultiline(true) + e:DockMargin(5,5,5,5) + e.PaintOffset = 5 + + local cont = f:Add 'DScrollPanel' + cont:Dock(TOP) + cont:DockMargin(0, 5, 0, 5) + cont:SetTall(150) + cont:SetPaintBackground(true) + + local rules = cont:Add 'DMarkup' + rules:DockMargin(5, 5, 5, 5) + rules:Dock(TOP) + rules:SetText(textRules) + + timeLeft = timeLeft or 15 + local b = octolib.button(f, '...', function() + netstream.Start('octochat.advert', e:GetValue()) + f:Remove() + end) + b:SetTall(27) + b:SetEnabled(false) + + local function updateName() + b:SetText('Сообщение можно будет отправить через ' .. niceTime(timeLeft)) + end + updateName() + + timer.Create('octochat.advert.confirm', 1, 0, function() + if not IsValid(f) then return timer.Remove('octochat.advert.confirm') end + timeLeft = timeLeft - 1 + + if timeLeft <= 0 then + timer.Remove('octochat.advert.confirm') + b:SetEnabled(true) + b:SetText('Отправить сообщение') + else + updateName() + end + end) +end) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/misc/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/misc/server.lua new file mode 100644 index 0000000..5e00e18 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/misc/server.lua @@ -0,0 +1,534 @@ +local advertPrice = 250 +octochat.registerCommand('/advert', { + log = true, + phone = true, + execute = function(ply, txt) + if not ply:Alive() or ply:IsGhost() then + return L.death_cannot_do_this + end + if ply:isArrested() then + return L.you_arrested + end + if ply:IsHandcuffed() then + return L.handcuffed_cannot_do_this + end + + if not ply:BankHas(advertPrice) then + return L.advert_money:format(DarkRP.formatMoney(advertPrice)) + end + + netstream.Start(ply, 'octochat.advert', txt, ply:GetCooldown('advert') and math.ceil(ply:GetCooldown('advert') - CurTime()) or 0) + + end, + aliases = {'/ad'}, +}) + +netstream.Hook('octochat.advert', function(ply, txt) + local nextAdvert = ply:GetCooldown('advert') + if nextAdvert then return ply:Notify('warning', 'Следующую рекламу можно будет отправить через ' .. niceTime(nextAdvert - CurTime())) end + if txt == '' then return ply:Notify('warning', 'Формат: /advert Текст сообщения') end + txt = txt:gsub('\n', ' ') + ply:BankAdd(-advertPrice) + for _, v in ipairs(player.GetAll()) do + if v:HasPhone() then + octochat.talkTo(v, octochat.textColors.rp, L.advert_hint, ply:Name(), ': ', Color(250,250,200), unpack(octolib.string.splitByUrl(txt))) + end + end + ply:TriggerCooldown('advert', 5 * 60) +end) + +local function sayThroughRadio(ply, txt, distance, action, color, noTalk) + + if txt == '' then return end + + if not ply:Alive() or ply:IsGhost() then + return L.death_cannot_do_this + end + if ply:isArrested() then + return L.you_arrested + end + if ply:IsHandcuffed() then + return L.handcuffed_cannot_do_this + end + if not ply:HasTalkie() then + return L.you_dont_have_radio + end + if ply:IsTalkieDisabled() then + return 'Твоя рация отключена' + end + local freq = ply:GetFrequency() + if not ply:CanSpeakToChannel(freq, true) then + return 'Микрофон твоей рации сломан' + end + + local sources = {ply} + for _, v in ipairs(player.GetAll()) do + if v == ply then continue end + if not v:HasTalkie() or v:IsTalkieDisabled() then continue end + local vFreq = v:GetFrequency() + if vFreq ~= freq then + if v:GetNetVar('NoTalkieParenting') then continue end + local chan = talkie.channels[vFreq] + if not chan or not chan.parent or chan.parent ~= freq then continue end + end + if v:CanListenToChannel(vFreq, true) then sources[#sources + 1] = v end + end + + local heard = octolib.array.toKeys(sources) + local shouldTalk, voice, hideTalkieMessage = not noTalk and ply:GetNetVar('os_govorilka') and not ply:IsGovorilkaMuted(), ply:GetVoice(), hook.Run('dbg-talkie.hideTalkieMessage', ply) + local hearsFunc = function(v) + if v:IsPlayer() and not heard[v] then + heard[v] = true + return true + else return false end + end + for _, source in ipairs(sources) do + if source ~= ply then + source:EmitSound('npc/combine_soldier/vo/off' .. math.random(1,2) .. '.wav', 45) + end + + local hears = octolib.array.filter(ents.FindInSphere(source:GetShootPos(), distance), hearsFunc) + if source ~= ply then + if not hideTalkieMessage then + octochat.talkTo(hears, color or octochat.textColors.rp, 'Кто-то', action .. ' из рации: ', color_white, txt) + end + octochat.talkTo(source, color or octochat.textColors.rp, ply:Name(), action .. ' из рации: ', color_white, txt) + else + octochat.talkTo(hears, color or octochat.textColors.rp, ply:Name(), action .. ' в рацию: ', color_white, txt) + octochat.talkTo(source, color or octochat.textColors.rp, ply:Name(), action .. ' в рацию: ', color_white, txt) + end + + if shouldTalk then + hears[#hears + 1] = source + source:DoVoice(txt, voice, hears) + end + + end + +end +octochat.registerCommand('/radio', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + sayThroughRadio(ply, txt, CFG.radioChatDistance, ' говорит') + end, + aliases = {'/r'}, +}) +octochat.registerCommand('/wradio', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + sayThroughRadio(ply, txt, CFG.radioChatDistance * 0.36, ' шепчет') + end, + aliases = {'/wr'}, +}) +octochat.registerCommand('/yradio', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + if txt == '' then return end + if txt:sub(-1) ~= '!' then txt = txt .. '!' end + sayThroughRadio(ply, txt, CFG.radioChatDistance * 2.2, ' кричит') + end, + aliases = {'/yr'}, +}) +octochat.registerCommand('/lradio', { + cooldown = 1.5, + log = true, + execute = function(ply, txt) + if txt == '' then return end + sayThroughRadio(ply, txt, CFG.radioChatDistance, ' говорит', octochat.textColors.ooc, true) + end, + aliases = {'/lr'}, +}) + +local leaveMeAloneID = 1 +local function callTeam(ply, who, emote, txt, group, icon, filterFunc) + + local people = octolib.array.filter(player.GetAll(), filterFunc) + if not people[1] then ply:Notify('warning', ('В городе сейчас нет %s, поэтому реагировать на вызов некому'):format(who)) return end + + local msg = L.prefix_request:format(DarkRP.nextEMSRequest, ply:Name()) + local marker = { + id = group .. leaveMeAloneID, + group = group, + txt = L.police_call .. DarkRP.nextEMSRequest, + pos = ply:GetPos() + Vector(0,0,40), + col = Color(235,120,120), + des = {'timedist', {600, 300}}, + icon = octolib.icons.silk16(icon), + + } + + octolib.request.send(ply, {{ + type = 'check', + name = 'Вызов ' .. who, + desc = msg .. txt, + txt = 'Отправить координаты вызова', + }}, function(data) + local sendPos = tobool(data and data[1]) + + ply:DoEmote(emote) + + octochat.talkTo(ply, octochat.textColors.rp, msg, color_red, txt) + for _, v in ipairs(people) do + octochat.talkTo(v, octochat.textColors.rp, msg, color_red, txt) + if sendPos then v:AddMarker(marker) end + v:EmitSound('ambient/chatter/cb_radio_chatter_' .. math.random(1,3) .. '.wav', 45, 100, 0.5) + end + + ply.nextEMSRequest = CurTime() + 10 + leaveMeAloneID = leaveMeAloneID + 1 + end) + + return true + +end + +octochat.registerCommand('/callmed', { + cooldown = 60, + log = true, + phone = true, + execute = function(ply, txt) + callTeam(ply, 'медицинских работников', '{name} вызывает врача', txt, 'med', 'asterisk_orange', DarkRP.isMedic) + end, +}) + +octochat.registerCommand('/callmech', { + cooldown = 60, + log = true, + phone = true, + execute = function(ply, txt) + callTeam(ply, 'автомехаников', L.call_mech_hint, txt, 'mech', 'wrench', DarkRP.isMech) + end, +}) + +octochat.registerCommand('/callfire', { + cooldown = 0, + log = true, + phone = true, + execute = function(ply, txt) + callTeam(ply, 'спасателей', '{name} вызывает пожарных', txt, 'fire', 'fire', DarkRP.isFirefighter) + end, +}) + +octochat.registerCommand('/callworker', { + cooldown = 60, + log = true, + phone = true, + execute = function(ply, txt) + callTeam(ply, 'городских рабочих', '{name} вызывает городского рабочего', txt, 'worker', 'wrench', DarkRP.isWorker) + end, +}) + +octochat.registerCommand('/calltaxi', { + cooldown = 60, + log = true, + phone = true, + execute = function(ply, txt) + callTeam(ply, 'таксистов', '{name} вызывает такси', txt, 'taxi', 'car_taxi', DarkRP.isTaxist) + end, +}) + +octochat.registerCommand('/givecert', { + cooldown = 15, + log = true, + execute = function(ply) + + local tgt = octolib.use.getTrace(ply).Entity + if not IsValid(tgt) or not tgt:IsPlayer() then tgt = ply end + + local curCert, allowed = tgt:GetDBVar('cert'), ply:GetDBVar('allowedCert') + if not allowed then + return 'Ты не можешь выдавать удостоверения' + end + if curCert and curCert.id ~= allowed.id then + return 'Этот игрок уже имеет удостоверение другой организации' + end + if not allowed.title then + allowed.title = dbgCerts.certTitles[allowed.id] or 'Удостоверение' + ply:SetDBVar('allowedCert', allowed) + end + + local issue, rem = os.time(), os.time() + allowed.period * 24 * 60 * 60 + octolib.request.send(ply, { + { + name = 'Доп. поле', + type = 'strShort', + ph = 'Отсутствует', + default = curCert and curCert.add or 'Код ' .. math.random(100000, 999999), + }, { + name = 'Кому выдано', + type = 'strShort', + desc = '(' .. tgt:Name() .. ' в дательном падеже)', + ph = 'Не показывать', + default = curCert and curCert.em or '', + }, { + name = 'Должность', + type = 'strShort', + ph = 'Отсутствует', + default = curCert and curCert.pos or '', + }, { + name = 'Дата выдачи', + desc = os.date('%d.%m.%Y', issue), + }, { + name = 'Действ. до', + desc = os.date('%d.%m.%Y', rem), + }, { + desc = 'Пожалуйста, внимательно пересмотри ВСЕ пункты!', + }, + }, function(data) + if not IsValid(tgt) then + return ply:Notify('Игрок вышел :(') + end + for i = 1, #data do + data[i] = string.Trim(data[i]) + data[i] = data[i] ~= '' and data[i] or nil + end + if dbgCerts.give(tgt, allowed.id, allowed.title, data[1], data[2], data[3], issue, rem) then + ply:Notify('Выдано удостоверение:') + dbgCerts.show(tgt, ply, true, true) + dbgCerts.show(tgt, tgt, true, true) + else ply:Notify('warning', 'Не получилось выдать удостоверение') end + end) + + end, +}) + +octochat.registerCommand('/delcert', { + cooldown = 15, + log = true, + execute = function(ply) + + local tgt = octolib.use.getTrace(ply).Entity + if not IsValid(tgt) then + return L.must_be_looking_at:format('игрока') + end + + local cert, allowed = tgt:GetDBVar('cert'), ply:GetDBVar('allowedCert') + if not allowed and ply:Team() ~= TEAM_ADMIN then + return 'Ты не можешь выдавать удостоверения' + end + if not cert then + return 'У этого игрока нет удостоверения' + end + if cert.vthru < os.time() or not dbgCerts.certTitles[cert.id] then + tgt:SetDBVar('cert', nil) + return 'У этого игрока нет удостоверения' + end + if cert.id ~= allowed.id and ply:Team() ~= TEAM_ADMIN then + return 'Этот игрок имеет удостоверение другой организации' + end + + local sid = tgt:SteamID() + octolib.request.send(ply, { + { + name = 'Вид', + desc = dbgCerts.certTitles[cert.id], + }, { + name = 'Доп. поле', + desc = cert.add or 'Отсутствует' + }, { + name = 'Кому выдано', + desc = cert.em or 'Не указано', + }, { + name = 'Должность', + desc = cert.pos or 'Не указана', + }, { + name = 'Дата выдачи', + desc = os.date('%d.%m.%Y', cert.iss), + }, { + name = 'Действ. до', + desc = os.date('%d.%m.%Y', cert.vthru), + }, { + desc = 'Удалить удостоверение игрока?', + }, + }, function() + octolib.setDBVar(sid, 'cert', nil) + ply:Notify('Удостоверение удалено') + end) + + end, +}) + +octochat.registerCommand('/ammo', { + cooldown = 1.5, + execute = function(ply) + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:Clip1() == -1 then + return 'Для этого нужно держать в руках оружие' + end + + ply:DoEmote('{name} проверяет магазин') + timer.Simple(1, function() + + if not IsValid(wep) then return 'Для этого нужно держать в руках оружие' end + + ply:Notify('Патронов в магазине: ' .. wep:Clip1()) + if wep:HasAmmo() then + ply:Notify('Патронов в запасе: ' .. wep:Ammo1()) + end + + end) + + end, +}) + +octochat.registerCommand('/getbank', { + cooldown = 1.5, + phone = true, + execute = function(ply) + ply:DoEmote('{name} проверяет счет в банке') + timer.Simple(1, function() + ply:Notify('Твой баланс в банке: ' .. DarkRP.formatMoney(BraxBank.PlayerMoney(ply))) + end) + end, + aliases = {'/bank'}, +}) + +octochat.registerCommand('/time', { + cooldown = 1.5, + execute = function(ply) + ply:DoEmote(L.see_time_hint) + timer.Simple(1, function() + ply:Notify(L.the_clock_shows:format(CWI.TimeToString())) + end) + end, +}) + +octochat.registerCommand('/title', { + log = true, + execute = function(ply, title) + + local door = ply:GetEyeTrace().Entity + if not IsValid(door) or not door:IsDoor() then + return L.must_be_looking_at:format(L.door) + end + + if door:NearestPoint(ply:GetShootPos()):DistToSqr(ply:GetShootPos()) > CFG.useDistSqr then + return L.must_be_looking_at:format(L.door) + end + + if door:GetPlayerOwner() ~= ply:SteamID() and not ply:IsSuperAdmin() then + return L.this_is_not_your_door + end + + if title == '' then title = nil end + door:SetTitle(title) + + end, +}) + +octochat.registerCommand('/write', { + cooldown = 10, + execute = function(ply) + + if ply:GetLetterCount() >= 3 then return L.too_much_letter end + + local letter = ents.Create 'letter' + letter:SetNetVar('Owner', ply) + letter:SetPos(octolib.use.getTrace(ply).HitPos) + letter.nodupe = true + letter:Spawn() + letter.SID = ply.SID + + end, +}) + +octochat.registerCommand('/removewrite', { + cooldown = 10, + execute = function(ply) + local ent = octolib.use.getTrace(ply).Entity + if IsValid(ent) and ent:GetClass() == 'letter' and (ent:GetNetVar('Owner') == ply or ply:IsAdmin()) then + ent:Remove() + end + end, +}) + +octochat.registerCommand('/removewrites', { + cooldown = 10, + execute = function(ply) + for _,v in ipairs(ents.FindByClass('letter')) do + if v:GetNetVar('Owner') == ply then + v:Remove() + end + end + end, +}) + +octochat.registerCommand('/dropweapon', { + execute = function(ply) + + local ent = ply:GetActiveWeapon() + if not IsValid(ent) or not ent:GetModel() or ent:GetModel() == '' then + return L.cannot_drop_weapon + end + + if not hook.Run('canDropWeapon', ply, ent) then return L.cannot_drop_weapon end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP) + timer.Simple(0.88, function() + + if not IsValid(ply) or not IsValid(ent) or not ent:GetModel() or ent:GetModel() == '' then return end + + local pos, ang = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_L_Hand') or 16) + local vel = ply:GetAimVector() + vel.z = 0 + ply:dropDRPWeapon(ent, pos, ang, vel * 200) + + end) + + end, + aliases = {'/drop'}, + cooldown = 1, +}) + +octochat.registerCommand('/holsterweapon', { + execute = function(ply) + + local ent = ply:GetActiveWeapon() + if not IsValid(ent) or not ent:GetModel() or ent:GetModel() == '' then + return L.cannot_drop_weapon + end + + if not hook.Run('canDropWeapon', ply, ent) then return L.cannot_drop_weapon end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP) + timer.Simple(0.88, function() + if IsValid(ply) and IsValid(ent) and ent:GetModel() and ent:GetModel() ~= '' and ent:GetOwner() == ply then + ply:HolsterWeapon(ent) + end + end) + + end, + aliases = {'/holster'}, + cooldown = 1, +}) + +local function wearMask(ply, class) + local mask = ply:GetNetVar('hMask') + if mask and mask[1] ~= class then return 'Нужно снять аксессуар' end + if mask then ply:SetNetVar('hMask') return end + ply:SetNetVar('hMask', {class, unequip = true}) +end + +octochat.registerCommand('/gasmask', { + cooldown = 1.5, + execute = function(ply) + wearMask(ply, 'gasmask') + end, + check = function(ply) + return ply:GetActiveRank('dpd') == 'swat' or ply:GetActiveRank('wcso') == 'seb' + end, +}) + +octochat.registerCommand('/medmask', { + cooldown = 1.5, + execute = function(ply) + wearMask(ply, 'medical_mask') + end, + check = function(ply) + return ply:isMedic() + end, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/octolib/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/octolib/client.lua new file mode 100644 index 0000000..66cfa0f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/octolib/client.lua @@ -0,0 +1,2 @@ +octochat.defineCommand('/forum', true) +octochat.defineCommand('/rewards', true) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/octolib/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/octolib/server.lua new file mode 100644 index 0000000..993c550 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/octolib/server.lua @@ -0,0 +1,9 @@ +octochat.registerCommand('/forum', { + cooldown = 5, + execute = octolib.rewardCommands.forum, +}) + +octochat.registerCommand('/rewards', { + cooldown = 60, + execute = octolib.rewardCommands.rewards, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/ooc/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/ooc/client.lua new file mode 100644 index 0000000..5ac71f2 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/ooc/client.lua @@ -0,0 +1,84 @@ +octochat.defineCommand('/ooc', { + aliases = {'//', '/a'}, + cooldownBypass = 'DBG: Нет ограничения на OOC', +}) +octochat.defineCommand('/looc', { + aliases = {'/'} +}) +octochat.defineCommand('/pm', true) +octochat.defineCommand('/demote', true) + +local function niceTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format("%02i:%02i:%02i", h, m, s) + +end + +local textRules = [[Правила использования OOC-чата +В глобальный ООС-чат можно писать не чаще одного раза в 30 минут + +Его можно использовать только для того, чтобы узнать, закончилась ли с игроком ролевая ситуация, а также статус ролевой ситуации, в которой погиб твой персонаж + +Если у тебя имеется какой-либо вопрос, то попробуй обратиться к администрации через @ +Когда администраторов нет в игре, ты можешь попробовать найти ответ на свой вопрос с помощью справочного материала по адресу wiki.octothorp.team; чтобы было проще найти что-то конкретное, можно использовать поиск + +Нарушение правил использования ООС-чата приведет к длительному муту]] + +netstream.Hook('octochat.ooc', function(text, timeLeft) + local f = vgui.Create 'DFrame' + f:SetSize(400, 355) + f:SetTitle('Отправить глобальное ООС-сообщение') + f:Center() + f:MakePopup() + + local e = octolib.textEntry(f, 'Текст сообщения') + e:SetTall(100) + e:SetDrawLanguageID(false) + e:SetContentAlignment(7) + e:SetText(text) + e:SetMultiline(true) + e:DockMargin(5,5,5,5) + e.PaintOffset = 5 + + local cont = f:Add 'DScrollPanel' + cont:Dock(TOP) + cont:DockMargin(0, 5, 0, 5) + cont:SetTall(150) + cont:SetPaintBackground(true) + + local rules = cont:Add 'DMarkup' + rules:DockMargin(5, 5, 5, 5) + rules:Dock(TOP) + rules:SetText(textRules) + + timeLeft = timeLeft or 15 + local b = octolib.button(f, '...', function() + netstream.Start('octochat.ooc', e:GetValue()) + f:Remove() + end) + b:SetTall(27) + b:SetEnabled(false) + + local function updateName() + b:SetText('Сообщение можно будет отправить через ' .. niceTime(timeLeft)) + end + updateName() + + timer.Create('octochat.ooc.confirm', 1, 0, function() + if not IsValid(f) then return timer.Remove('octochat.ooc.confirm') end + timeLeft = timeLeft - 1 + + if timeLeft <= 0 then + timer.Remove('octochat.ooc.confirm') + b:SetEnabled(true) + b:SetText('Отправить сообщение') + else + updateName() + end + end) +end) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/ooc/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/ooc/server.lua new file mode 100644 index 0000000..96f1ac4 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/ooc/server.lua @@ -0,0 +1,129 @@ +local function niceTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format("%02i:%02i:%02i", h, m, s) + +end + +local function sendOOC(ply, txt) + txt = txt:gsub('\n', ' ') + if ply:IsAdmin() then + octochat.talkTo(nil, octochat.textColors.ooc, ('[OOC] %s (%s): '):format(ply:SteamName(), ply:Name()), color_white, unpack(octolib.string.splitByUrl(txt))) + else + octochat.talkTo(nil, octochat.textColors.ooc, ('[OOC] %s (%s): '):format(ply:SteamName(), ply:Name()), color_white, txt) + end +end + +octochat.registerCommand('/ooc', { + log = true, + execute = function(ply, txt, _, cmd) + local can, why = hook.Run('PlayerCanOOC', ply, txt) + if can == false then + return why or false + end + + netstream.Start(ply, 'octochat.ooc', txt, ply:GetCooldown('ooc') and math.ceil(ply:GetCooldown('ooc') - CurTime()) or 0) + end, + aliases = {'//', '/a'}, +}) + +netstream.Hook('octochat.ooc', function(ply, txt) + local nextOOC = ply:GetCooldown('ooc') + if nextOOC then return ply:Notify('warning', 'Следующее сообщение в ООС-чат можно будет отправить через ' .. niceTime(nextOOC - CurTime())) end + if txt == '' then return ply:Notify('warning', 'Формат: ' .. cmd .. ' Текст сообщения') end + + sendOOC(ply, txt) + if not ply:query('DBG: Нет ограничения на OOC') then + ply:TriggerCooldown('ooc', 30 * 60) + end +end) + +octochat.registerCommand('/looc', { + cooldown = 1.5, + log = true, + cooldownBypass = 'DBG: Нет ограничения на OOC', + execute = function(ply, txt, _, cmd) + if txt == '' then return 'Формат: ' .. cmd .. ' Текст сообщения' end + + if ply:IsAdmin() then + octochat.talkToRange(ply, 250, octochat.textColors.ooc, ('[LOOC] %s (%s): '):format(ply:SteamName(), ply:Name()), color_white, unpack(octolib.string.splitByUrl(txt))) + else + octochat.talkToRange(ply, 250, octochat.textColors.ooc, ('[LOOC] %s (%s): '):format(ply:SteamName(), ply:Name()), color_white, txt) + end + end, + aliases = {'/'}, +}) + +octochat.registerCommand('/pm', { + cooldown = 3, + log = true, + cooldownBypass = 'DBG: Нет ограничения на OOC', + execute = function(ply, _, args) + local target, txt = octochat.pickOutTarget(args) + if not target then return txt or 'Формат: /pm "Имя игрока" Текст сообщения' end + if txt == '' then return 'Формат: /pm "Имя игрока" Текст сообщения' end + + if ply:IsAdmin() then + octochat.talkTo(target, octochat.textColors.ooc, ('[PM] от %s (%s): '):format(ply:SteamName(), ply:Name()), Color(250,250,200), unpack(octolib.string.splitByUrl(txt))) + octochat.talkTo(ply, octochat.textColors.ooc, ('[PM] для %s (%s): '):format(target:SteamName(), target:Name()), Color(250,250,200), unpack(octolib.string.splitByUrl(txt))) + else + octochat.talkTo(target, octochat.textColors.ooc, ('[PM] от %s (%s): '):format(ply:SteamName(), ply:Name()), Color(250,250,200), txt) + octochat.talkTo(ply, octochat.textColors.ooc, ('[PM] для %s (%s): '):format(target:SteamName(), target:Name()), Color(250,250,200), txt) + end + end, +}) + +octochat.registerCommand('/demote', { + cooldown = 80, + execute = function(ply, _, args) + + if not args[1] or args[1] == '' or not args[2] or args[2] == '' then + return 'Формат: /demote "Ник игрока" Причина увольнения' + end + + local target, reason = octochat.pickOutTarget(args) + if not IsValid(target) then return reason or 'Такой игрок не найден' end + if target == ply then return L.cant_demote_self end + + if utf8.len(reason) > 100 then return L.unable:format('указать такую причину', 'Она должна быть покороче') end + + local canDemote, message = hook.Run('canDemote', ply, target, reason) + if canDemote == false then return message or L.unable:format('уволить этого игрока', '') end + + if target:getJobTable().candemote == false then return L.unable:format('уволить этого игрока', '') end + + local recipients = octolib.array.filter(player.GetAll(), function(v) return v ~= p and v ~= ply end) + target.IsBeingDemoted = octolib.questions.start({ + text = L.demote_vote_text:format(ply:Name(), target:Name(), reason), + recipients = recipients, + spectators = {ply, target}, + time = 20, + onFinish = function(result) + if not IsValid(target) then return end + target.IsBeingDemoted = nil + + if result > 0 then + if target:Alive() then + target:changeTeam(GAMEMODE.DefaultTeam, true) + if target:isArrested() then + target:arrest() + end + else + target.demotedWhileDead = true + end + + hook.Run('onPlayerDemoted', ply, target, reason) + octolib.notify.sendAll(L.demoted:format(target:Nick())) + else + octolib.notify.sendAll('warning', L.demoted_not:format(target:Nick())) + end + end, + }) + octolib.notify.sendAll(L.demote_vote_started:format(ply:Nick(), target:Nick())) + + end, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/police/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/police/client.lua new file mode 100644 index 0000000..377d2dc --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/police/client.lua @@ -0,0 +1,11 @@ +local copData = {check = DarkRP.isCop} +octochat.defineCommand('/callhelp', copData) +octochat.defineCommand('/panicbutton', copData) +octochat.defineCommand('/warrant', copData) +octochat.defineCommand('/wanted', copData) +octochat.defineCommand('/unwanted', copData) +octochat.defineCommand('/givelicense', copData) +octochat.defineCommand('/takelicense', copData) +octochat.defineCommand('/carcheck', copData) + +octochat.defineCommand('/cr', true) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/police/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/police/server.lua new file mode 100644 index 0000000..9159f69 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/police/server.lua @@ -0,0 +1,238 @@ +local leaveMeAloneID = 0 +octochat.registerCommand('/callhelp', { + cooldown = 60, + log = true, + execute = function(ply, txt) + if txt == '' then return 'Формат: /callhelp Текст сообщения' end + + ply:DoEmote('{name} запрашивает подкрепление') + + local marker = { + id = 'cpSupport' .. leaveMeAloneID, + group = 'cpSupport', + txt = 'Подкрепление', + pos = ply:GetPos() + Vector(0,0,40), + col = Color(102,170,170), + des = {'timedist', { 600, 300 }}, + icon = 'octoteam/icons-16/exclamation.png', + } + for _,v in ipairs(player.GetAll()) do + if v:isEMS() then + octochat.talkTo(v, octochat.textColors.rp, '[Подкрепление] ', ply:Name(), ': ', color_red, txt) + v:EmitSound('ambient/chatter/cb_radio_chatter_' .. math.random(1,3) .. '.wav', 45, 100, 0.5) + v:AddMarker(marker) + end + end + leaveMeAloneID = leaveMeAloneID + 1 + + end, + check = DarkRP.isCop, +}) + +octochat.registerCommand('/cr', { + cooldown = 30, + phone = true, + execute = function(ply, txt) + if txt == '' then return 'Формат: /cr Текст вызова' end + local ph = ply:AtStationaryPhone() + DarkRP.callEMS(ply, ph and ph:GetNick() or ply:Name(), txt) + end, +}) + +octochat.registerCommand('/warrant', { + execute = function(ply, _, args) + + local target, txt = octochat.pickOutTarget(args) + if not IsValid(target) then return txt or 'Такой игрок не найден' end + if utf8.len(txt) < 10 then return 'Укажи причину обыска' end + if target.warranted then return L.already_a_warrant end + + local can, message = hook.Run('canRequestWarrant', target, ply, txt) + if can == false then return message or 'Ты не можешь получить ордер на обыск этого игрока' end + + if not ply:getJobTable().mayor then -- No need to search through all the teams if the player is a mayor + local mayors = {} + for _,v in ipairs(player.GetAll()) do + if v:getJobTable().mayor then + mayors[#mayors + 1] = v + end + end + if mayors[1] then + octolib.questions.start({ + text = L.warrant_request:format(ply:Nick(), target:Nick(), txt), + recipients = mayors, + left = 'Одобрить', + right = 'Отклонить', + sound = 'Town.d1_town_02_elevbell1', + time = 40, + onFinish = function(res) + if res <= 0 then + ply:Notify('warning', L.warrant_denied) + elseif IsValid(target) then + target:warrant(txt) + end + end, + }) + end + else target:warrant(reason) end + + end, + check = DarkRP.isCop, +}) + +octochat.registerCommand('/wanted', { + execute = function(ply, _, args) + + local target, txt = octochat.pickOutTarget(args) + if not IsValid(target) then return txt or 'Такой игрок не найден' end + if utf8.len(txt) < 10 then return 'Укажи причину подачи в розыск' end + if target:isWanted() then return L.already_wanted end + if not target:Alive() or target:IsGhost() then return L.suspect_must_be_alive_to_do_x:format(L.make_someone_wanted) end + if target:isArrested() then return L.suspect_already_arrested end + + local can, message = hook.Run('canWanted', target, ply, txt) + if can == false then return message or 'Ты не можешь подать в розыск на этого игрока' end + + target:wanted(ply, txt) + + end, + check = DarkRP.isCop, +}) + +octochat.registerCommand('/unwanted', { + execute = function(ply, _, args) + if not args[1] or args[1] == '' then return 'Ты не указал имя игрока!\nФормат: /unwanted "Ник игрока" "Причина"' end + if not args[2] or args[2] == '' then return 'Ты не указал причину!\nФормат: /unwanted "Ник игрока" "Причина"' end + + local target, txt = octochat.pickOutTarget(args) + if not IsValid(target) then return txt or 'Такой игрок не найден' end + if not target:isWanted() then return L.not_wanted end + + local can, message = hook.Run('canUnwanted', target, ply) + if can == false then return message or 'Ты не можешь отозвать розыск на этого игрока' end + + target:unWanted(ply, table.concat(args, ' ', 2)) + + end, + check = DarkRP.isCop, +}) + +local function isInCharge(msg) + return function(ply) + + local mayor, chief, serg, cop + for _,v in ipairs(player.GetAll()) do + if v:isMayor() or v:GetActiveRank('gov') == 'worker' then + mayor = true + break + elseif v:isChief() then + chief = true + elseif v:getJobTable().command == 'cop2' then + serg = true + elseif v:isCP() then cop = true end + end + + if mayor then + if not (ply:isMayor() or ply:GetActiveRank('gov') == 'worker') then return false, 'Сейчас ' .. msg .. ' может мэр' end + elseif chief then + if not ply:isChief() then return false, 'Сейчас ' .. msg .. ' может лейтенант полиции' end + elseif serg then + if ply:getJobTable().command ~= 'cop2' then return false, 'Сейчас ' .. msg .. ' может сержант полиции' end + elseif cop then + if not ply:isCP() then return false, 'Сейчас ' .. msg .. ' может сотрудник полиции' end + elseif not ply:IsAdmin() then + return false, 'Сейчас ' .. msg .. ' может только администратор' + end + + return true + + end +end + +octochat.registerCommand('/givelicense', { + execute = function(ply, txt) + + local target = octolib.use.getTrace(ply).Entity + if not IsValid(target) or not target:IsPlayer() then + return L.must_be_looking_at:format('игрока') + end + + target:Notify(L.gunlicense_granted:format(ply:Name(), target:Name())) + ply:Notify(L.gunlicense_granted:format(ply:Name(), target:Name())) + target:SetNetVar('HasGunlicense', txt or '') + hook.Run('DarkRPGiveLicence', ply, target, txt or '') + + end, + check = isInCharge('выдавать лицензии'), +}) + +octochat.registerCommand('/takelicense', { + cooldown = 1.5, + execute = function(ply) + + local target = octolib.use.getTrace(ply).Entity + if not IsValid(target) or not target:IsPlayer() then + return L.must_be_looking_at:format('игрока') + end + + target:Notify(L.gunlicense_taken:format(ply:Name(), target:Name())) + ply:Notify(L.gunlicense_taken:format(ply:Name(), target:Name())) + target:SetNetVar('HasGunlicense') + hook.Run('DarkRPTakeLicence', ply, target) + + end, + check = isInCharge('отзывать лицензии'), +}) + +octochat.registerCommand('/carcheck', { + cooldown = 1.5, + execute = function(ply) + + octolib.request.send(ply, {{ + type = 'strShort', + name = 'Номер автомобиля', + desc = '6 или 7 символов. Только латинские буквы и цифры, без пробелов и дефисов', + ph = carDealer.randomPlate(carDealer.plateLength), + required = true, + }}, function(data) + + local plate = data[1] + if not isstring(plate) then return end + if string.gsub(plate, '^[0-9a-zA-Z]+$', '') ~= '' then + ply:Notify('warning', 'Только латинские буквы и цифры') + return + end + if #plate < 6 or #plate > 7 then + ply:Notify('warning', '6 или 7 символов') + return + end + + ply:DoEmote(L.emote_check) + ply:DelayedAction('car_check', L.check_action, { + time = 10, + check = function() return IsValid(ply) and ply:isCP() end, + succ = function() + carDealer.getVehByPlate(plate, function(veh) + if not IsValid(ply) then return end + if not veh then + return ply:Notify('Совпадений в базе не обнаружено') + end + + if not octolib.string.isSteamID(veh.garage) then + return ply:Notify('ooc', 'Эта машина не принадлежит игроку (скажи разработчикам)') + end + + local msg = 'Владелец автомобиля с номером ' .. string.upper(plate) .. ': ' + octolib.getDBVar(veh.garage, 'lastRPName'):Then(function(var) + if not IsValid(ply) then return end + ply:Notify(msg .. var) -- to be tested + end) + end) + + end, + }) + end) + + end, + check = DarkRP.isCop, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/radio/client.lua b/garrysmod/addons/_config/lua/config/octochat-commands/radio/client.lua new file mode 100644 index 0000000..e36784b --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/radio/client.lua @@ -0,0 +1,4 @@ +octochat.defineCommand('!radio', { + aliases = {'~radio', '/radio'}, + check = DarkRP.isSuperAdmin, +}) diff --git a/garrysmod/addons/_config/lua/config/octochat-commands/radio/server.lua b/garrysmod/addons/_config/lua/config/octochat-commands/radio/server.lua new file mode 100644 index 0000000..3f5d258 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octochat-commands/radio/server.lua @@ -0,0 +1,164 @@ +local stationIdSymbols = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + +local function id(station) + return station.id +end +local function escape(str) + return octolib.db:escape(str) +end + +octochat.registerCommand('!radio', { + aliases = {'~radio', '/radio'}, + check = DarkRP.isSuperAdmin, + log = true, + execute = function(ply) + if ply.radioCollecting then return 'Команда еще в работе' end + + ply.radioCollecting = true + + octolib.func.chain({ + + function(nxt) + local response, offset, first = {}, 0, true + octolib.func.loop(function(again, stations) + if not IsValid(ply) then return end + + if istable(stations) then + for _, v in ipairs(stations) do + response[#response + 1] = v + end + offset = offset + #stations + if #stations < 25 then return nxt(response) end + elseif not first then + return nxt(response) + end + first = false + + octoradio.getByPlace('Dobrograd MI', 'United States', offset, again) + end) + end, + + function(nxt, stations) + local response, page = {}, 1 + octolib.func.loop(function(again, ans) + if not IsValid(ply) then return end + + if ans then + if not istable(ans) then return nxt(response) end + for _, v in ipairs(ans) do + response[#response + 1] = v + end + if #ans < 25 then return nxt(response) end + end + + octoradio.getById(octolib.table.mapSequential(octolib.array.page(stations, 25, page), id), again) + end) + end, + + function(nxt, stations) + ply.radioCollecting = nil + for _, v in ipairs(stations) do + v.placeId, v.placeName, v.country = nil + v.id = v.id:sub(3) + end + octolib.entries.gui(ply, 'Радиостанции', { + fields = { + id = { + name = 'Идентификатор', + desc = '6 латинских символов и цифр. Должен быть уникальным', + type = 'strShort', + len = 6, + required = true, + }, + title = { + name = 'Название', + type = 'strShort', + len = 50, + required = true, + }, + playbackUrl = { + name = 'Ссылка', + desc = 'Прямая ссылка на онлайн-стрим. Если сомневаешься, обратись к разработчикам', + type = 'strShort', + len = 255, + required = true, + }, + }, + labelIndex = 'title', + entries = stations, + maxEntries = 1000, + }, function(res) + + if not istable(res) then return end + local stations = {} + local ids = {} + + for i, v in ipairs(res) do + + local title = v.title + if not (title and octolib.math.inRange(utf8.len(title), 4, 50)) then + ply:Notify('warning', ('Запись #%i: неправильное название (%s)'):format(i, tostring(title))) + return + end + + local id = v.id + if not (id and utf8.len(id) == 6) then + ply:Notify('warning', ('Запись #%i (%s): длина идентификатора не равна 6'):format(i, tostring(title))) + return + end + if id:find('[^' .. stationIdSymbols .. ']') then + ply:Notify('warning', ('Запись #%i: (%s): в идентификаторе есть неподходящие символы'):format(i, tostring(title))) + return + end + if ids[id] then + ply:Notify('warning', ('Запись #%i: (%s): идентификатор уже использовался в записи #%i (%s)') + :format(i, tostring(title), ids[i][1], tostring(ids[i][2])) + ) + return + end + ids[id] = { #stations, name } + + local playbackUrl = v.playbackUrl + if not (playbackUrl and playbackUrl:len() <= 255 and octolib.string.isUrl(playbackUrl)) then + ply:Notify('warning', ('Запись #%i: (%s): неверная ссылка'):format(i, tostring(title))) + end + + stations[#stations + 1] = ('(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\')'):format(unpack(octolib.table.mapSequential({ + '##' .. id, title, '########', 'Dobrograd MI', 'United States', playbackUrl + }, escape))) + end + + nxt(stations) + end) + end, + + function(nxt, q) + octolib.db:RunQuery([[DELETE FROM `dbg_radio` WHERE `id` LIKE '##%']], function() + nxt(q) + end) + end, + + function(nxt, q) + local qSize = #q + local iMax = math.ceil(qSize / 10) + for i = 1, iMax do + local values = table.concat(q, ',', (i-1) * 10 + 1, math.min(i * 10, qSize)) + local query = [[REPLACE INTO `dbg_radio` (`id`, `title`, `placeId`, `placeName`, `country`, `playbackUrl`) VALUES ]] .. values + if i == iMax then + octolib.db:RunQuery(query, nxt) + else + octolib.db:RunQuery(query) + end + end + end, + + function() + hook.Run('octoradio.updated') + if IsValid(ply) then + ply:Notify('hint', 'Радиостанции сохранены') + end + end, + }) + + end, +}) diff --git a/garrysmod/addons/_config/lua/config/octogui/f4.lua b/garrysmod/addons/_config/lua/config/octogui/f4.lua new file mode 100644 index 0000000..37980b1 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octogui/f4.lua @@ -0,0 +1,1165 @@ +local cvName = CreateClientConVar('dbg_name', L.name_and_surname, true, true) +local cvModel = CreateClientConVar('dbg_model', 'models/humans/octo/male_01_01.mdl', true, true) +local cvSkin = CreateClientConVar('dbg_skin', '0', true, true) +local cvJob = CreateClientConVar('dbg_job', 'citizen', false, true) +local cvDesc = CreateClientConVar('dbg_desc', '', true, true) +octolib.vars.init('dbg_charset', {}) + +local plyModels = { + {'models/humans/octo/female_01.mdl', L.woman .. ' 1', {20}}, + {'models/humans/octo/female_02.mdl', L.woman .. ' 2', {6,21}}, + {'models/humans/octo/female_03.mdl', L.woman .. ' 3', {6,21}}, + {'models/humans/octo/female_04.mdl', L.woman .. ' 4', {6,21}}, + {'models/humans/octo/female_06.mdl', L.woman .. ' 6', {6,21}}, + {'models/humans/octo/female_07.mdl', L.woman .. ' 7', {6,21}}, + {'models/humans/octo/male_01_01.mdl', L.male .. ' 1' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_01_02.mdl', L.male .. ' 1' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_01_03.mdl', L.male .. ' 1' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_02_01.mdl', L.male .. ' 2' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_02_02.mdl', L.male .. ' 2' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_02_03.mdl', L.male .. ' 2' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_03_01.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_03_02.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_03_03.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_03_04.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 4' .. ')'}, + {'models/humans/octo/male_03_05.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 5' .. ')'}, + {'models/humans/octo/male_03_06.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 6' .. ')'}, + {'models/humans/octo/male_03_07.mdl', L.male .. ' 3' .. ' (' .. L.variant .. ' 7' .. ')'}, + {'models/humans/octo/male_04_01.mdl', L.male .. ' 4' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_04_02.mdl', L.male .. ' 4' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_04_03.mdl', L.male .. ' 4' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_04_04.mdl', L.male .. ' 4' .. ' (' .. L.variant .. ' 4' .. ')'}, + {'models/humans/octo/male_05_01.mdl', L.male .. ' 5' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_05_02.mdl', L.male .. ' 5' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_05_03.mdl', L.male .. ' 5' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_05_04.mdl', L.male .. ' 5' .. ' (' .. L.variant .. ' 4' .. ')'}, + {'models/humans/octo/male_05_05.mdl', L.male .. ' 5' .. ' (' .. L.variant .. ' 5' .. ')'}, + {'models/humans/octo/male_06_01.mdl', L.male .. ' 6' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_06_02.mdl', L.male .. ' 6' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_06_03.mdl', L.male .. ' 6' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_06_04.mdl', L.male .. ' 6' .. ' (' .. L.variant .. ' 4' .. ')'}, + {'models/humans/octo/male_06_05.mdl', L.male .. ' 6' .. ' (' .. L.variant .. ' 5' .. ')'}, + {'models/humans/octo/male_07_01.mdl', L.male .. ' 7' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_07_02.mdl', L.male .. ' 7' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_07_03.mdl', L.male .. ' 7' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_07_04.mdl', L.male .. ' 7' .. ' (' .. L.variant .. ' 4' .. ')'}, + {'models/humans/octo/male_07_05.mdl', L.male .. ' 7' .. ' (' .. L.variant .. ' 5' .. ')'}, + {'models/humans/octo/male_07_06.mdl', L.male .. ' 7' .. ' (' .. L.variant .. ' 6' .. ')'}, + {'models/humans/octo/male_08_01.mdl', L.male .. ' 8' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_08_02.mdl', L.male .. ' 8' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_08_03.mdl', L.male .. ' 8' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_08_04.mdl', L.male .. ' 8' .. ' (' .. L.variant .. ' 4' .. ')'}, + {'models/humans/octo/male_09_01.mdl', L.male .. ' 9' .. ' (' .. L.variant .. ' 1' .. ')'}, + {'models/humans/octo/male_09_02.mdl', L.male .. ' 9' .. ' (' .. L.variant .. ' 2' .. ')'}, + {'models/humans/octo/male_09_03.mdl', L.male .. ' 9' .. ' (' .. L.variant .. ' 3' .. ')'}, + {'models/humans/octo/male_09_04.mdl', L.male .. ' 9' .. ' (' .. L.variant .. ' 4' .. ')'}, +} + +-- defkey, cvar, userinfo, name +local k_list = { + {L.character}, + {KEY_G, 'cl_dbg_key_look', false, L.look}, + {KEY_LALT, 'cl_dbg_key_freeview', false, L.freeview}, + {MOUSE_MIDDLE, 'cl_dbg_key_sights', false, 'Смотреть в прицел'}, + {KEY_C, 'cl_dbg_key_bend', false, 'Целиться из-за угла'}, + {KEY_V, 'radio_bind_key', false, L.speak_in_radio}, + {KEY_H, 'dbg_emotions_key', false, 'Меню эмоций'}, + + {L.car}, + {KEY_W, 'cl_simfphys_keyforward', true, L.key_w}, + {KEY_S, 'cl_simfphys_keyreverse', true, L.key_s}, + {KEY_A, 'cl_simfphys_keyleft', true, L.key_a}, + {KEY_D, 'cl_simfphys_keyright', true, L.key_d}, + {KEY_I, 'cl_simfphys_keyengine', true, L.key_i}, + {KEY_LALT, 'cl_simfphys_keyclutch', true, L.key_lalt}, + {KEY_LSHIFT, 'cl_simfphys_keywot', true, L.key_lshift}, + {MOUSE_LEFT, 'cl_simfphys_keygearup', true, L.key_mouse_left}, + {MOUSE_RIGHT, 'cl_simfphys_keygeardown', true, L.key_mouse_right}, + {KEY_SPACE, 'cl_simfphys_keyhandbrake', true, L.key_space}, + {KEY_R, 'cl_simfphys_keyhandbrake_toggle', true, L.key_r}, + {MOUSE_MIDDLE, 'cl_simfphys_keymousesteer', true, L.key_mouse_middle}, + {KEY_F, 'cl_simfphys_lights', true, L.key_f}, + {KEY_H, 'cl_simfphys_keyhorn', true, L.key_h}, + {KEY_M, 'cl_simfphys_emssiren', true, L.key_m}, + {KEY_N, 'cl_simfphys_emslights', true, L.key_n}, + {KEY_COMMA, 'cl_simfphys_key_turnmenu', true, L.key_comma}, + {KEY_J, 'cl_simfphys_key_lock', true, L.key_j}, + {KEY_LCONTROL, 'cl_simfphys_key_mirror', false, L.key_lcontrol}, + {KEY_B, 'cl_simfphys_key_belt', true, L.key_b}, + {KEY_V, 'cl_simfphys_special', true, 'Лебедка тягача'}, +} +for _, v in ipairs(k_list) do + if isnumber(v[1]) then + CreateClientConVar(v[2], v[1], true, v[3]) + end +end + +CreateClientConVar('cl_octolib_sticky_handbrake', '1', true, true) +CreateClientConVar('cl_dbg_turnsignaloff', '1', true, true) +CreateClientConVar('dbg_emotions_key', KEY_H, true) +CreateClientConVar('cl_dbg_voiceicon', '1', true) +CreateClientConVar('cl_dbg_enter', '1', true, true) +CreateClientConVar('cl_dbg_alcohol_effect', '1', true, true) + +hook.Add('PlayerButtonDown', 'dbg-emotions.bind', function(ply, key) + if key ~= GetConVar('dbg_emotions_key'):GetInt() then return end + + local opts = {{ 'Нейтральность', nil, function() netstream.Start('player-flex', {}) end }} + for _, row in ipairs(octolib.vars.get('faceposes') or {}) do + table.insert(opts, { row.name, nil, function() + netstream.Start('player-flex', row.flexes) + end }) + end + octogui.circularMenu(opts) +end) + +local function buildBinder(parent, data) + + local cv = GetConVar(data[2]) + + local p = parent:Add 'DPanel' + p:SetSize(400, 40) + + local b = p:Add 'DBinder' + b:Dock(RIGHT) + b:SetWide(80) + b:SetValue(cv:GetInt() or data[1]) + function b:SetSelectedNumber(num) + self.m_iSelectedNumber = num + self:ConVarChanged(num) + self:UpdateText() + self:OnChange(num) + cv:SetInt(num) + end + + local l = p:Add 'DLabel' + l:Dock(FILL) + l:DockMargin(8, 0, 0, 0) + l:SetContentAlignment(4) + l:SetText(data[4]) + + function p:Reset() + b:SetValue(data[1]) + end + + return p + +end + +local function paintIconInCenter(self, w, h) + local mat = self:GetMaterial() + local matW, matH = mat:Width(), mat:Height() + surface.SetMaterial(mat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect((w-matW) / 2, (h-matH) / 2, matW, matH) +end + +-- FANCY CHECKBOX WITH DESCRIPTION +local function cbLayout(self) + local x = self.m_iIndent or 0 + self.Button:SetSize(15, 15) + self.Button:SetPos(x, math.floor((self:GetTall() - self.Button:GetTall()) / 2)) + self.Label:SizeToContents() + self.Label:SetPos(x + self.Button:GetWide() + 9, math.floor((self:GetTall() - self.Label:GetTall()) / 2) - 1) +end +local function sizeToContents(self) + self:SizeToChildren(false, true) +end +local function fancifyCheckbox(p, cb, desc) + local cont = p:Add 'DPanel' + cont:Dock(TOP) + cont:SetPaintBackground(false) + cont:DockMargin(0, 8, 0, 8) + cont.PerformLayout = sizeToContents + + cb:SetParent(cont) + cb.PerformLayout = cbLayout + cb:InvalidateLayout(true) + if not desc then return cb end + + local lbl = octolib.label(cont, desc) + lbl:DockMargin(28, -5, 0, 7) + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetAutoStretchVertical(true) + lbl:SetAlpha(100) + + return cb +end +local function fancyCheckbox(p, title, desc) + return fancifyCheckbox(p, octolib.checkBox(p, title), desc) +end +local function fancyVarCheckbox(p, var, title, desc) + return fancifyCheckbox(p, octolib.vars.checkBox(p, var, title), desc) +end + +-- FANCY NUMSLIDER WITH DESCRIPTION +local function slider_pl(self) + self.Label:SetWide(15) +end +local function fancyNumSlider(p, title, desc, min, max, prc) + local cont = p:Add 'DPanel' + cont:Dock(TOP) + cont:SetPaintBackground(false) + cont:DockMargin(0, 8, 0, 8) + cont.PerformLayout = sizeToContents + + local t = octolib.label(cont, title) + t:SetAutoStretchVertical(true) + if desc then + local d = octolib.label(cont, desc) + d:SetMultiline(true) + d:SetWrap(true) + d:SetAlpha(100) + d:DockMargin(5, 3, 0, 5) + d:SetAutoStretchVertical(true) + end + + local sl = octolib.slider(cont, '↔', min, max, prc) + sl:DockMargin(0, -7, 0, 0) + sl.PerformLayout = slider_pl + + return sl +end + +octogui.reloadGovorilka = octogui.reloadGovorilka or octolib.func.zero + +local options = {{ + id = 'character', + name = L.character, + icon = Material('octoteam/icons/man_m.png'), + build = function(f) + f:SetSize(800, 600) + local ply = LocalPlayer() + local skinCount = 1 + + local l = vgui.Create 'DLabel' + l:SetParent(f) + l:SetPos(13, 33) + l:SetSize(250, 30) + l:SetText(L.view) + l:SetFont('f4.normal') + + local modelPnl = vgui.Create 'DPanel' + modelPnl:SetParent(f) + modelPnl:Dock(LEFT) + modelPnl:DockMargin(5,35,5,5) + modelPnl:SetWide(250) + + local mdl = vgui.Create 'DModelPanel' + mdl:SetParent(modelPnl) + mdl:Dock(FILL) + mdl:SetCamPos(Vector(90,0,55)) + mdl:SetLookAng(Angle(10,180,0)) + mdl:SetFOV(25) + mdl:SetCursor('none') + function mdl:LayoutEntity(mdl) + mdl:SetAngles(Angle(0, 5, 0)) + mdl.GetPlayerColor = function() return ply:GetPlayerColor() end + return + end + function mdl:UpdateDBGData() + self:SetModel(cvModel:GetString()) + self.Entity:SetSkin(cvSkin:GetInt()) + skinCount = self.Entity:SkinCount() + end + mdl:UpdateDBGData() + + local pnl = vgui.Create 'DPanel' + pnl:SetParent(f) + pnl:Dock(LEFT) + pnl:DockPadding(5, 5, 5, 5) + pnl:SetWide(250) + pnl:SetPaintBackground(false) + + do -- name + local l = vgui.Create 'DLabel' + l:SetParent(pnl) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.name_character) + l:SetFont('f4.normal') + + local e = vgui.Create 'DTextEntry' + e:SetParent(pnl) + e:Dock(TOP) + e:DockMargin(5,5,5,20) + e:SetTall(20) + e:SetDrawLanguageID(false) + e:SetConVar('dbg_name') + e:SetUpdateOnType(true) + e.OnValueChange = function(self, text) + local oldC = self:GetCaretPos() + local oldLen = utf8.len(self:GetText()) + text = utf8.sub(text, 1, 35) + self:SetText(octolib.string.camel(octolib.string.stripNonWord(text)) .. (text:endsWith(' ') and ' ' or '')) + self:SetCaretPos(oldC - (utf8.len(self:GetText()) ~= oldLen and 1 or 0)) + cvName:SetString(self:GetText()) + end + e.PaintOffset = 5 + + f.e_name = e + end + + do -- short description + if (cvDesc:GetString() or '') ~= '' then + octolib.vars.set('dbg_desc', cvDesc:GetString()) + cvDesc:SetString('') + end + + local e, l = octolib.textEntry(pnl, L.desc_appereance) + e:SetDrawLanguageID(false) + e:SetText(octolib.vars.get('dbg_desc') or '') + l:SetTall(30) + l:SetFont('f4.normal') + l:DockMargin(5,0,5,0) + e:DockMargin(5,5,5,20) + e:SetUpdateOnType(true) + function e:OnValueChange(text) + local oldC = self:GetCaretPos() + local oldLen = utf8.len(self:GetText()) + text = utf8.sub(text, 1, 350) + text = octolib.string.stripNonWord(text, ',:%.0-9-;%(%)%/%"%\'a-zA-Z') + self:SetText(text) + self:SetCaretPos(oldC - (utf8.len(self:GetText()) ~= oldLen and 1 or 0)) + octolib.vars.set('dbg_desc', text) + end + e.PaintOffset = 5 + + f.e_desc = e + end + + do -- model + local l = vgui.Create 'DLabel' + l:SetParent(pnl) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.appearance) + l:SetFont('f4.normal') + + local ep = pnl:Add 'DPanel' + ep:SetPaintBackground(false) + ep:Dock(TOP) + ep:DockMargin(0,0,0,15) + ep:SetTall(30) + + local epb = ep:Add 'DPanel' + epb:SetPaintBackground(false) + epb:Dock(RIGHT) + epb:DockMargin(5,0,0,0) + epb:SetWide(20) + + local e = ep:Add 'DComboBox' + e:Dock(FILL) + e:SetSortItems(false) + for _, v in ipairs(plyModels) do + e:AddChoice(v[2], {v[1], v[3]}, v[1] == cvModel:GetString()) + end + function e:OnSelect(i, v, data) + cvModel:SetString(data[1]) + if cvSkin:GetInt() > skinCount - 1 or (istable(data[2]) and table.HasValue(data[2], cvSkin:GetInt())) then + cvSkin:SetInt(0) + end + mdl:UpdateDBGData() + f.e_skin:UpdateDBGData(data[2]) + end + + local b1 = epb:Add 'DButton' + b1:Dock(TOP) + b1:SetTall(14) + b1:SetFont('dbg-icons2') + b1:SetText(utf8.char(0xf077)) + b1:SetPaintBackground(false) + function b1:DoClick() + local newID = e:GetSelectedID() - 1 + if newID < 1 then newID = #plyModels end + e:ChooseOptionID(newID) + end + + local b2 = epb:Add 'DButton' + b2:Dock(BOTTOM) + b2:SetTall(14) + b2:SetFont('dbg-icons2') + b2:SetText(utf8.char(0xf078)) + b2:SetPaintBackground(false) + function b2:DoClick() + local newID = e:GetSelectedID() + 1 + if newID > #plyModels then newID = 1 end + e:ChooseOptionID(newID) + end + + f.e_model = e + end + + do -- skin + local l = vgui.Create 'DLabel' + l:SetParent(pnl) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.skin) + l:SetFont('f4.normal') + + local ep = pnl:Add 'DPanel' + ep:SetPaintBackground(false) + ep:Dock(TOP) + ep:DockMargin(0,0,0,15) + ep:SetTall(30) + + local epb = ep:Add 'DPanel' + epb:SetPaintBackground(false) + epb:Dock(RIGHT) + epb:DockMargin(5,0,0,0) + epb:SetWide(20) + + local e = ep:Add 'DComboBox' + e:Dock(FILL) + e:SetSortItems(false) + function e:OnSelect(i, v, data) + cvSkin:SetInt(data) + mdl:UpdateDBGData() + end + function e:UpdateDBGData(excluded) + self:Clear() + for i = 0, skinCount - 1 do + if not istable(excluded) or not table.HasValue(excluded, i) then + self:AddChoice(L.skin2 .. i + 1, i, i == cvSkin:GetInt()) + end + end + end + e:UpdateDBGData() + + local b1 = epb:Add 'DButton' + b1:Dock(TOP) + b1:SetTall(14) + b1:SetFont('dbg-icons2') + b1:SetText(utf8.char(0xf077)) + b1:SetPaintBackground(false) + function b1:DoClick() + local newID = e:GetSelectedID() - 1 + if newID < 1 then newID = skinCount end + e:ChooseOptionID(newID) + end + + local b2 = epb:Add 'DButton' + b2:Dock(BOTTOM) + b2:SetTall(14) + b2:SetFont('dbg-icons2') + b2:SetText(utf8.char(0xf078)) + b2:SetPaintBackground(false) + function b2:DoClick() + local newID = e:GetSelectedID() + 1 + if newID > skinCount then newID = 1 end + e:ChooseOptionID(newID) + end + + f.e_skin = e + end + + do -- job + local l = vgui.Create 'DLabel' + l:SetParent(pnl) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.citizen_work) + l:SetFont('f4.normal') + + local e = vgui.Create 'DComboBox' + e:SetParent(pnl) + e:Dock(TOP) + e:DockMargin(0,0,0,15) + e:SetTall(30) + e:SetSortItems(false) + e.jobs = {} + for _, job in ipairs(RPExtraTeams) do + if not job.noPreference and (not job.hidden or isfunction(job.customCheck) and select(1, job.customCheck(ply))) then + e:AddChoice(job.name, job.command, job.command == cvJob:GetString()) + table.insert(e.jobs, job) + end + end + function e:OnSelect(i, v, data) + cvJob:SetString(data) + end + + cvars.RemoveChangeCallback('dbg_job', 'dbg_job.change.listener') + cvars.AddChangeCallback('dbg_job', function(name, old, new) + local newj = DarkRP.getJobByCommand(new) + if not newj then cvJob:SetString(old) return end + if not newj.customCheck then return end + local a, b = newj.customCheck(ply) + if not a then cvJob:SetString(old) return end + e:SetValue(newj.name) + end, 'dbg_job.change.listener') + + local oldOpen = e.OpenMenu + function e:OpenMenu() + oldOpen(self) + for k, but in pairs(self.Menu:GetCanvas():GetChildren()) do + local job = self.jobs[k] + local limit = job.max == 0 or team.NumPlayers(job.team) < math.ceil(player.GetCount() * job.max) + if not limit then + but:SetEnabled(false) + but:SetColor(Color(160,160,160)) + but:SetText(but:GetText() .. L.limit_hint) + elseif isfunction(job.customCheck) then + local can, reason = job.customCheck(ply) + if not can then + if not reason then reason = L.reason_unavailable end + if reason:find(L.reason_find_dobro) then reason = L.reason_dobro end + if reason:find(L.reason_play) then reason = L.need_play .. reason:gsub('[^%d]', '') .. 'ч' end + but:SetEnabled(false) + but:SetColor(Color(160,160,160)) + but:SetText(but:GetText() .. ' (' .. reason .. ')') + end + end + but:SetIcon(octolib.icons.silk16(job.icon or 'error')) + end + end + + f.e_job = e + end + + do -- voice + local l = vgui.Create 'DLabel' + l:SetParent(pnl) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.voice) + l:SetFont('f4.normal') + + local e = vgui.Create 'DComboBox' + e:SetParent(pnl) + e:Dock(TOP) + e:DockMargin(0,0,0,15) + e:SetTall(30) + e:SetSortItems(false) + function octogui.reloadGovorilka() + if not IsValid(e) then return end + if ply:GetNetVar('os_govorilka') then + e:SetEnabled(true) + e:SetValue(ply:GetVoiceName()) + for _, add in ipairs(govorilka.voices) do + e:AddChoice(add.ru_name, add.en_name) + end + function e:OnSelect(_, _, name) + netstream.Start('govorilka.changeVoice', name) + RunConsoleCommand('cl_govorilka_voice', name) + end + else + e:SetValue(L.buy_govorilka) + e:SetEnabled(false) + end + end + octogui.reloadGovorilka() + end + + do -- respawn + local cont = vgui.Create 'DPanel' + cont:SetParent(pnl) + cont:Dock(BOTTOM) + cont:DockPadding(5,5,5,5) + cont:SetTall(85) + + local l = vgui.Create 'DLabel' + l:SetParent(cont) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(40) + l:SetWrap(true) + + local e = vgui.Create 'DButton' + e:SetParent(cont) + e:Dock(TOP) + e:DockMargin(0,5,0,0) + e:SetTall(30) + e:SetEnabled(true) + function e:DoClick() + netstream.Start('dbg-char.respawn', not cont.state) + end + + cont.state = false -- is respawn mode active + cont.UpdateDBGData = function(self) + l:SetText(self.state and L.run_in_discreet_place or L.settings_in_respawn ) + e:SetText(self.state and L.cancel_changes or L.change_character) + end + netstream.Hook('dbg-char.updateState', function(state) + cont.state = state + cont:UpdateDBGData() + end) + cont:UpdateDBGData() + + end + + local pnl2 = vgui.Create 'DPanel' + pnl2:SetParent(f) + pnl2:Dock(FILL) + pnl2:DockPadding(5, 5, 5, 5) + pnl2:SetPaintBackground(false) + + do -- charPresets + local l = vgui.Create 'DLabel' + l:SetParent(pnl2) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText("Персонажи") + l:SetFont('f4.normal') + + local chs = vgui.Create 'DScrollPanel' + chs:SetParent(pnl2) + chs:Dock(FILL) + chs:DockMargin(5,5,5,5) + + local function deleteCharPreset(key) + local deltable = octolib.vars.get('dbg_charset') + table.remove(deltable, key) + octolib.vars.set('dbg_charset', deltable) + end + + local function saveCharPreset(s) + local chardelete = octolib.vars.get('dbg_charset') + local charVal = 1 + if next(chardelete) ~= nil then + charVal = #chardelete+1 + end + chardelete[charVal] = { + ['dbg_name'] = f.e_name:GetValue(), + ['dbg_model'] = cvModel:GetString(), + ['dbg_skin'] = cvSkin:GetString(), + ['dbg_job'] = cvJob:GetString(), + ['dbg_desc'] = octolib.vars.get('dbg_desc'), + ['dbg_userlabel'] = s + } + octolib.vars.set('dbg_charset', chardelete) + end + + local function loadCharPreset(value) + f.e_name:SetValue(value['dbg_name']) + f.e_desc:SetValue(value['dbg_desc']) + for i = 1, #f.e_job.Choices do + if f.e_job:GetOptionData(i) == value['dbg_job'] then + f.e_job:ChooseOptionID(i) + break + end + end + for i = 1, #f.e_model.Choices do + if f.e_model:GetOptionData(i)[1] == value['dbg_model'] then + f.e_model:ChooseOptionID(i) + break + end + end + for i = 1, #f.e_skin.Choices do + if f.e_skin:GetOptionData(i) == tonumber(value['dbg_skin']) then + f.e_skin:ChooseOptionID(i) + break + end + end + end + + local function getRusJobName(value) + for _, job in ipairs(RPExtraTeams) do + if not job.noPreference and (not job.hidden or isfunction(job.customCheck) and select(1, job.customCheck(ply))) then + if value['dbg_job'] == job.command then + return job.name + end + end + end + return nil + end + + local function updateCharSet() + chs:GetCanvas():Clear() + local chartable = octolib.vars.get('dbg_charset') + for key, value in pairs(chartable) do + local charPanel = vgui.Create 'DPanel' --BackPanel + charPanel:SetParent(chs) + charPanel:Dock(TOP) + charPanel:DockMargin(0,0,0,10) + charPanel:SetTall(85) + + local ul = vgui.Create 'DLabel' --UserLabel + ul:SetParent(charPanel) + ul:Dock(TOP) + ul:DockMargin(10,3,10,0) + ul:SetFont('f4.charset-label') + ul:SetContentAlignment(5) + ul:SetTall(25) + ul:SetText(value['dbg_userlabel']) + + local chname = vgui.Create 'DPanel' --CharacterName + chname:SetParent(charPanel) + chname:Dock(TOP) + chname:SetTall(18) + chname:DockMargin(10,5,10,10) + chname:SetPaintBackground(false) + + local icoName = chname:Add 'DImage' + icoName:SetSize(18, 18) + icoName:SetImage('octoteam/icons/man_m.png') + icoName:SetPos(0,0) + + local labName = chname:Add 'DLabel' + labName:SetFont('f4.charset-text') + labName:SetContentAlignment(4) + labName:SetTall(25) + labName:SetWide(300) + labName:SetText(value['dbg_name']) + labName:DockMargin(21,0,10,0) + labName:Dock(LEFT) + + local chjob = vgui.Create 'DPanel' --CharacterJob + chjob:SetParent(charPanel) + chjob:Dock(TOP) + chname:SetTall(18) + chjob:DockMargin(10,0,10,10) + chjob:SetPaintBackground(false) + + local icoJob = chjob:Add 'DImage' + icoJob:SetSize(18, 18) + icoJob:SetImage('octoteam/icons/clipboard.png') + icoJob:SetPos(0,0) + + local labJob = chjob:Add 'DLabel' + labJob:SetFont('f4.charset-text') + labJob:SetContentAlignment(4) + labJob:SetTall(25) + labJob:SetWide(300) + labJob:SetText(getRusJobName(value)) + labJob:DockMargin(21,0,10,5) + labJob:Dock(LEFT) + + local chb = vgui.Create 'DButton' --Load + chb:SetParent(charPanel) + chb:SetTall(25) + chb:SetEnabled(true) + chb:SetText("Загрузить") + function chb:PerformLayout() + self:SizeToContentsX(20) + self:AlignBottom(5) + self:AlignRight(5) + end + function chb:DoClick() + loadCharPreset(value) + end + + local chbClear = vgui.Create 'DButton' --Delete + chbClear:SetParent(charPanel) + chbClear:SetText('') + chbClear:SetSize(24,24) + chbClear.icon=Material(octolib.icons.silk16('cancel')) + chbClear.Paint = function(self, w, h) + surface.SetMaterial(self.icon) + if self:IsHovered() then + surface.SetDrawColor(255, 255, 255, 255) + else + surface.SetDrawColor(255,255,255, 20) + + end + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(4, 4, 16, 16) + end + chbClear:AddHint("Удалить") + function chbClear:DoClick() + Derma_Query('Вы уверены, что хотите удалить этого персонажа?', 'Удаление персонажа', L.yes, function() + deleteCharPreset(key) + updateCharSet() + end, L.no) + end + function chbClear:PerformLayout() + self:SizeToContentsX(20) + self:AlignTop(2) + self:AlignRight(4) + end + end + end + + local chb = vgui.Create 'DButton' --Save + chb:SetParent(pnl2) + chb:Dock(BOTTOM) + chb:SetTall(30) + chb:DockMargin(10,0,10,5) + chb:SetEnabled(true) + chb:SetText(L.save) + chb.DoClick = octolib.fStringRequest("Сохранение", "Напиши название для твоего пресета","",function(s) + saveCharPreset(s) + updateCharSet() + end) + updateCharSet() + end + end, +},{ + id = 'shop', + name = L.shop, + icon = Material('octoteam/icons/shop.png'), + build = function(f) + octoinv.pnlShop:SetVisible(true) + octoinv.pnlShop:SetPos(0, 19) + octoinv.pnlShop.btnClose:SetVisible(false) + f:DockPadding(0, 0, 0, 0) + f:Add(octoinv.pnlShop) + f:SizeToChildren(true, true) + function f:OnRemove() + octoinv.createShop() + end + end, + show = function(f, st) + if st then octoinv.updateShop() end + octoinv.pnlShop:SetVisible(st) + end, +},{ + id = 'craft', + name = L.directory, + icon = Material('octoteam/icons/blueprint.png'), + build = function(f) + if not IsValid(octoinv.helpPnl) then octoinv.initHelp() end + + octoinv.helpPnl:SetVisible(true) + octoinv.helpPnl:SetPos(3, 22) + f:DockPadding(3, 22, 3, 3) + f:Add(octoinv.helpPnl) + f:SizeToChildren(true, true) + function f:OnRemove() + octoinv.helpPnl:SetParent(vgui.GetWorldPanel()) + octoinv.helpPnl:SetVisible(false) + end + end, +},{ + id = 'donate', + name = L.donate, + icon = Material('octoteam/icons/coin_stacks.png'), + build = function(f) + octoshop.pnl:SetVisible(true) + octoshop.pnl:SetPos(0, 19) + octoshop.pnl.cl:SetVisible(false) + f:DockPadding(0, 0, 0, 0) + f:Add(octoshop.pnl) + f:SizeToChildren(true, true) + function f:OnRemove() + octoshop.pnl:SetParent(vgui.GetWorldPanel()) + octoshop.pnl.cl:SetVisible(true) + end + end, + show = function(f, st) + if st then + net.Start('octoshop.rInventory') + net.SendToServer() + end + end, +},{ + id = 'settings', + name = L.settings, + icon = Material('octoteam/icons/cog.png'), + build = function(f) + f:SetSize(650, 500) + f:DockPadding(0, 21, 0, 0) + + local function pl(self) self:SizeToChildren(false, true) end + local function title(p, txt) + local l = p:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(0,5,0,15) + l:SetTall(32) + l:SetText(txt) + l:SetContentAlignment(5) + l:SetFont('f4.medium') + return l + end + local function childPanel(p) + local cp = p:Add 'DPanel' + cp:Dock(TOP) + cp:DockMargin(0, 0, 0, 5) + cp:DockPadding(5, 5, 5, 10) + cp.PerformLayout = pl + return cp + end + + local menuList = f:Add 'DListView' + menuList:Dock(LEFT) + menuList:SetWide(150) + menuList:AddColumn('Icon'):SetFixedWidth(48) + menuList:AddColumn('Name') + menuList:SetHideHeaders(true) + menuList:SetDataHeight(42) + menuList:DockMargin(2, 7, 0, 3) + menuList:SetMultiSelect(false) + + local scr + function menuList:OnRowSelected(_, row) + if IsValid(scr) then scr:Remove() end + scr = f:Add 'DScrollPanel' + scr:Dock(FILL) + scr:DockMargin(5, 3, 5, 0) + scr.pnlCanvas:DockPadding(20, 5, 25, 20) + title(scr, row:GetColumnText(2)) + + local container = row.build(scr) + if IsValid(container) then + scr:Remove() + container:SetParent(f) + container:Dock(FILL) + container:DockMargin(15, 5, 5, 5) + scr = container + end + end + + local function addCategory(name, icon, build) + local iconPnl = vgui.Create 'DImage' + iconPnl:SetImage(octolib.icons.silk32(icon)) + iconPnl.Paint = paintIconInCenter + menuList:AddLine(iconPnl, name).build = build + end + + addCategory('Графика', 'picture', function(p) + local cp = childPanel(p) + cp:DockPadding(10, 0, 10, 10) + + local e = octolib.comboBox(cp, 'Разрешение прицела', octolib.array.map({ 128, 256, 512, 1024 }, function(side) + return { side .. 'x' .. side, side, GetConVar('octoweapons_sight_resolution'):GetInt() == side } + end)) + e:SetSortItems(false) + function e:OnSelect(_, _, side) + GetConVar('octoweapons_sight_resolution'):SetInt(side) + end + + fancyCheckbox(cp, 'Счетчик FPS', 'Показатель количества кадров в секунду в верхнем правом углу экрана'):SetConVar('cl_showfps') + fancyCheckbox(cp, 'Размытие фона', 'Эффект размытия некоторых прозрачных частей интерфейса. Красиво, но довольно требовательно к ресурсам компьютера'):SetConVar('octolib_blur') + fancyCheckbox(cp, '3D-скайбокс', 'Отображение неигровой зоны карты за ее пределами. Отключение может незначительно повысить производительность'):SetConVar('r_3dsky') + fancyCheckbox(cp, 'Моя тень', 'Тень под твоим персонажем при использовании физгана, тулгкана или камеры'):SetConVar('cl_drawownshadow') + fancyCheckbox(cp, 'Тени высокого качества', 'Отображение полных силуэтов объектов в тенях'):SetConVar('r_shadowrendertotexture') + fancyCheckbox(cp, 'Свет высокого качества', 'Динамическое освещение, создаваемое некоторыми объектами'):SetConVar('r_dynamic') + fancyCheckbox(cp, 'Мелкие частицы', 'Например, осколки от поверхности при попадании в нее пули'):SetConVar('r_drawflecks') + fancyCheckbox(cp, 'Декали на объектах', 'Например, следы крови на персонажах и машинах'):SetConVar('r_drawmodeldecals') + fancyCheckbox(cp, 'Отражение в воде', 'Отображение игрового мира в воде. Если выключить, может понадобиться перезайти на сервер'):SetConVar('r_WaterDrawReflection') + fancyCheckbox(cp, 'Тени от фар автомобилей', 'Высокое качество освещения от фар автомобилей. Сильно влияет на производительность. Настройка применяется после включения фар'):SetConVar('cl_simfphys_shadows') + + title(p, 'Погода') + + local cp = childPanel(p) + cp:DockPadding(10, 10, 10, 10) + + local cont = childPanel(cp) + cont:SetPaintBackground(false) + cont:DockMargin(0, 0, 0, 15) + + local fps = StormFox2.Menu.FPSRing() + cont:Add(fps) + fps:SetY(0) + + local qu = StormFox2.Menu.QualityRing() + cont:Add(qu) + qu:SetY(0) + + local sup = StormFox2.Menu.SupportRing() + cont:Add(sup) + sup:SetY(0) + + local mth = StormFox2.Menu.MThreadRing() + cont:Add(mth) + mth:SetY(0) + + function cont:PerformLayout(w, h) + local maxH, sumW = 0, 0 + for _, v in ipairs(self:GetChildren()) do + maxH = math.max(maxH, v:GetTall()) + sumW = sumW + v:GetWide() + end + self:SetTall(maxH) + local x = (w-sumW) / (#self:GetChildren()+1) + fps:SetX(x) + qu:SetX(fps:GetX()+fps:GetWide() + x) + sup:SetX(qu:GetX()+qu:GetWide() + x) + mth:SetX(sup:GetX()+sup:GetWide() + x) + end + + octolib.label(cp, 'Желаемый FPS') + local fpsTarget = StormFox2.Menu.FPSTarget() + cp:Add(fpsTarget) + fpsTarget:Dock(TOP) + fpsTarget:DockMargin(0, -15, 0, 0) + fpsTarget.label_name:Remove() + fpsTarget.description:Remove() + + local function shittyCheckbox(name, desc, setting) + local cb = fancyCheckbox(cp, name, desc) + local obj = StormFox2.Setting.GetObject(setting) + obj:AddCallback(function(val) + cb:SetChecked(val) + end, cb) + function cb:OnChange(val) + obj:SetValue(val) + end + end + shittyCheckbox('Ультра-качество', 'Добавляет ещё больше эффектов', 'quality_ultra') + + local function shittyNumSlider(name, desc, min, max, prec, setting) + local ns = fancyNumSlider(cp, name, desc, min, max, prec) + local obj = StormFox2.Setting.GetObject(setting) + obj:AddCallback(function(val) + ns:SetValue(val) + end, cb) + function ns:OnValueChanged(val) + obj:SetValue(val) + end + end + shittyNumSlider('Множитель темноты', 'Чем больше значение, тем темнее ночи', 0, 1, 2, 'extra_darkness_amount') + end) + + addCategory('Геймплей', 'bricks', function(p) + local cp = childPanel(p) + cp:DockPadding(10, 0, 10, 10) + + fancyCheckbox(cp, L.dbg_help_login, 'Автоматически открывать F1-справку при входе на сервер'):SetConVar('dbg_help_login') + fancyCheckbox(cp, L.voicemods, 'Возможность шептать или кричать в голосовой чат при удерживании Alt или Shift вместе с кнопкой разговора'):SetConVar('dbg_voicemods') + + if LocalPlayer():GetNetVar('halloweenTheme') then + local cb = fancyCheckbox(cp, 'Хэллоуинская тема', 'Цвета интерфейса меняются на черно-оранжевый, как на Хэллоуин') + function cb:OnChange(val) + if val then + octolib.changeSkinColor(Color(52,49,52), Color(222,132,38), 0.5) + else + octolib.changeSkinColor(Color(85,68,85), Color(102,170,170), 0.5) + end + end + end + + fancyCheckbox(cp, 'Иконка голосового чата над головой', 'Пригодится для записи видео, но можно запутаться, кто говорит'):SetConVar('cl_dbg_voiceicon') + fancyCheckbox(cp, 'Текст подключения игроков в чате', 'Пригодится для записи видео, но можно пропустить вход друга'):SetConVar('cl_dbg_enter') + fancyCheckbox(cp, 'Эффект опьянения в чате', 'Если ты под воздействием алкоголя, твои сообщения в ролевом чате будут измеЕеенееЕЕны вооООт таааак'):SetConVar('cl_dbg_alcohol_effect') + fancyCheckbox(cp, 'Эффект рыбьего глаза на глазках', 'Чем ближе человек за дверью к глазку, тем шире ты его видишь в дверном глазке. Прямо как в реальной жизни! Но может мешать'):SetConVar('cl_dbg_fisheyeonpeepholes') + fancyCheckbox(cp, 'Отображать ники говорящих в рацию', 'Когда кто-то начнет говорить в рацию на твоей волне, слева снизу ты увидишь его ролевое имя, как в обычном Sandbox'):SetConVar('cl_dbg_talkienotifies') + end) + + addCategory('Управление', 'keyboard', function(p) + local l = octolib.label(p, 'В этом меню ты можешь назначить горячие клавиши. Чтобы убрать назначение, кликни правой кнопкой мыши') + l:SetTall(35) + l:SetContentAlignment(5) + l:SetWrap(true) + + for _, v in ipairs(k_list) do + if isstring(v[1]) then + local l = p:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(0,5,0,5) + l:SetTall(30) + l:SetText(v[1]) + l:SetContentAlignment(5) + l:SetFont('f4.normal') + else + local b = buildBinder(p, v) + b:Dock(TOP) + b:SetTall(25) + b:DockMargin(0, 0, 0, 5) + end + end + end) + + addCategory('Бинды', 'key', function(p) + local cont = vgui.Create('octolib_binds') + local t = title(cont, 'Бинды') + t:SetParent() + function cont:RebuildList() + t:SetParent() + self:Clear() + self:Add(t) + + for i = 1, #octolib.bindCache do + local row = self:Add 'octolib_binds_row' + row:SetBind(i) + end + + octolib.button(self, 'Создать', function() + octolib.setBind(nil, KEY_SPACE, 'chat', 'Привет!') + end) + + end + + return cont + end) + + addCategory('Автомобиль', 'small_car', function(p) + local cp = childPanel(p) + cp:DockPadding(10, 0, 10, 5) + + fancyCheckbox(cp, 'Выключать поворотники автоматически', 'При повороте в противоположную сторону выключать поворотники'):SetConVar('cl_dbg_turnsignaloff') + fancyNumSlider(cp, 'Скорость поворота руля', 'Скорость, с которой движение мышью изменяет положение руля', 0.1, 5, 2):SetConVar('dbg_cars_ms_sensitivity') + fancyNumSlider(cp, 'Мертвая зона руля', 'Размер области, в которой поворот считается нулевым (машина едет прямо)', 0, 0.3, 2):SetConVar('dbg_cars_ms_deadzone') + fancyNumSlider(cp, 'Возврат руля', 'Скорость, с которой руль возвращается в положение прямо', 0, 2, 2):SetConVar('dbg_cars_ms_return') + end) + + addCategory('Физган', 'transform_move', function(p) + local cp = childPanel(p) + cp:DockPadding(10, 10, 10, 10) + + fancyVarCheckbox(cp, 'hideMyBeam', 'Не показывать мне луч моего физгана', 'Другие игроки всё равно будут видеть луч твоего физгана, если тоже возьмут физган/тулган в руки') + fancyVarCheckbox(cp, 'hidePhysgunHalos', 'Скрыть обводку предметов, взятых физганом', 'Подойдет, если ты хочешь уменьшить количество мистики в этом городе. Иногда может увеличить производительность') + + local t = octolib.label(cp, 'Цвет физгана') + t:SetFont('f4.normal') + t.PerformLayout = sizeToContents + local d = octolib.label(cp, 'Доступно только обладателям плюшки "Строитель"') + d:SetMultiline(true) + d:SetWrap(true) + d:DockMargin(5, 5, 0, 5) + d.PerformLayout = sizeToContents + local cb = octolib.vars.colorPicker(cp, 'physgunColor', nil, false, true) + cb:DockMargin(5,5,5,5) + cb:SetTall(200) + end) + + addCategory('Другое', 'cog', function(p) + p:GetCanvas():Clear() + + title(p, 'Залипание') + local cp = childPanel(p) + cp:DockPadding(10, 10, 10, 10) + + local lbl = octolib.label(cp, 'Двойное нажатие на клавиши заменяет их удерживание') + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetAutoStretchVertical(true) + lbl:SetAlpha(100) + lbl:DockMargin(0, 0, 0, 5) + + octolib.checkBox(cp, 'Приседание'):SetConVar('cl_octolib_sticky_duck') + octolib.checkBox(cp, 'Прицел'):SetConVar('cl_octolib_sticky_attack2') + octolib.checkBox(cp, 'Ручной тормоз'):SetConVar('cl_octolib_sticky_handbrake') + + title(p, 'Цвет прицела') + local cp = childPanel(p) + local cb = octolib.vars.colorPicker(cp, 'dbg-crosshair.color', nil, false, true) + cb:DockMargin(5,5,5,5) + cb:SetTall(200) + + title(p, 'Фиксы') + local cp = childPanel(p) + cp:DockPadding(5, 5, 5, 5) + local function fix(title, click) + local b = cp:Add 'DButton' + b:Dock(TOP) + b:DockMargin(0,0,0,5) + b:SetTall(30) + b:SetText(title) + b.DoClick = click + end + fix('Отпустить все кнопки залипания', function() RunConsoleCommand('-duck') RunConsoleCommand('-attack2') end) + fix('Остановить все звуки', function() RunConsoleCommand('stopsound') end) + fix('Перезагрузить F4-меню', function() RunConsoleCommand('octogui_reloadf4') end) + end) + + menuList:SelectFirstItem() + + end, +}} + +hook.Add('octogui.f4-tabs', 'f4', function() + + for i, opt in ipairs(options) do + opt.order = i + 10 + octogui.addToF4(opt) + end + +end) diff --git a/garrysmod/addons/_config/lua/config/octogui/score.lua b/garrysmod/addons/_config/lua/config/octogui/score.lua new file mode 100644 index 0000000..6b3c5ce --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octogui/score.lua @@ -0,0 +1,559 @@ +local function run() +if not GAMEMODE then return end +hook.Add('Think', 'dbg-score', function() +if not IsValid(LocalPlayer()) then return end + +if IsValid(dbgScore) then dbgScore:Remove() end + +local maxH = ScrH() - 80 +local p = vgui.Create 'DFrame' +dbgScore = p + +local icons = {} +local function updateIcons() + for rank, data in pairs(serverguard.ranks.stored) do + icons[rank] = Material(data.texture) + end +end +hook.Add('PlayerIsLoaded', 'dbg-score', updateIcons) + +local function convert_rank_to_img(ply) + + local icon = icons[ply:GetUserGroup()] + if not icon then + updateIcons() + return convert_rank_to_img(ply) + end + + return icon + +end + +local function niceMinutes(num) + + local word = L.minutes_hint + if num < 5 or (num % 100) > 20 then + local lastDigit = num % 10 + if lastDigit == 1 then + word = word .. 'у' + elseif lastDigit > 1 and lastDigit < 5 then + word = word .. 'ы' + end + end + + return num .. word + +end + +local function canSeeHisJob(ply) + local lp = LocalPlayer() + if ply == lp then return true end + local job = ply:getJobTable() + local myJob = lp:getJobTable() + + if myJob.admin ~= 0 then return true end + if job.command == 'fbi' and myJob.command ~= 'fbi' then return false end + if myJob.police ~= job.police then return false end + if not myJob.police then return false end + + return true +end + +local function canSeeHisLicense(ply) + local lp, license = LocalPlayer(), ply:GetNetVar('HasGunlicense') + if not license then return false end + if ply == lp then return true end + + local myJob = lp:getJobTable() + + if myJob.admin ~= 0 then return true end + if myJob.seesLicense then return true end + if myJob.police then return true end + + return false +end + +surface.CreateFont('dbg-score.large', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +surface.CreateFont('dbg-score.normal', { + font = 'Calibri', + extended = true, + size = 22, + weight = 350, +}) + +surface.CreateFont('dbg-score.small', { + font = 'Calibri', + extended = true, + size = 18, + weight = 350, +}) + +p:SetSize(800, 600) +p:Center() +p:MakePopup() +p:SetMouseInputEnabled(true) +p:SetKeyboardInputEnabled(false) +p:SetTitle('') +p:DockPadding(0,0,0,0) +p:ShowCloseButton(false) + +p.canvas = vgui.Create 'DScrollPanel' +p.canvas:SetParent(p) +p.canvas:Dock(FILL) +p.canvas:SetPaintBackground(false) +p.canvas.VBar.btnUp.Paint = function() end +p.canvas.VBar.btnDown.Paint = function() end +p.canvas.VBar.Paint = function(self, w, h) + draw.RoundedBox(4, w/2 - 3, 18, 6, h - 36, Color(43,34,43, 180)) +end +p.canvas.VBar.btnGrip.Paint = function(self, w, h) + local col = (self.Hovered or self.Depressed) and Color(238,238,238, 255) or Color(238,238,238, 100) + draw.RoundedBox(4, w/2 - 4, 0, 8, h, col) +end + +p.al = 0 +p.playerNum = 0 + +local function ease( t, b, c, d ) + + t = t / d; + return -c * t * (t - 2) + b + +end + +local function paintTag(self, w, h) + + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(4, 4, 16, 16) + +end + +local vipMat = Material(octolib.icons.silk16('heart')) +local function paintButton(self, w, h) + + local al = self.anim + local ply = self.ply + + local col = Color(43,34,43, 180 + al * 70) + draw.RoundedBox(4, 0, 0, w, h, col) + + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(convert_rank_to_img(ply)) + surface.DrawTexturedRect(12, 12, 16, 16) + + -- draw.SimpleText(team.GetName(ply:Team()), 'dbg-score.normal', w - 10, h/2 - 1, Color(238,238,238), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + draw.SimpleText(ply:Name(), 'dbg-score.large', 38, h/2 - 1, Color(238,238,238), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + if al > 0 then + al = ease(al, 0, 1, 1) + local tw, th = surface.GetTextSize(ply:Name()) + local x, y = math.floor(28 + tw + al * 20), h/2 + if ply:GetNetVar('os_dobro') then + surface.SetDrawColor(255,255,255, al*150) + surface.SetMaterial(vipMat) + surface.DrawTexturedRect(x, y-7, 16, 16) + x = x + 20 + end + draw.SimpleText(ply:SteamName(), 'dbg-score.small', x, y, Color(238,238,238, al * 150), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + +end + +local function thinkButton(self) + + if not IsValid(self.ply) then return self:Remove() end + local steamID = self.ply:SteamID() + + if self.Hovered or self:IsChildHovered(true) then + self.anim = math.Approach( self.anim, 1, FrameTime() / 0.1 ) + else + self.anim = math.Approach( self.anim, 0, FrameTime() / 0.2 ) + end + + -- job + local name, icon, overridden + local job = self.ply:getJobTable() + if job.displayAs and not canSeeHisJob(self.ply) then + job = DarkRP.getJobByCommand(job.displayAs) + overridden = true + end + + if job then name, icon = job.name, octolib.icons.silk16(job.icon or 'error') end + if not overridden then + local customJob = self.ply:GetNetVar('customJob') + if customJob then name, icon = unpack(customJob) end + end + + if self.j and (self.j.hint ~= name or self.j.iconPath ~= icon) then + self.j.hint = name + self.j.icon = Material(icon) + self.j.iconPath = icon + end + + -- friend + if self.fs then + if FPP.Buddies[steamID] == nil then + self.fs[2]:Remove() + self.fs = nil + end + elseif FPP.Buddies[steamID] ~= nil then + local fs = vgui.Create 'DPanel' + fs:SetParent(self) + fs:Dock(RIGHT) + fs:SetWide(24) + fs:SetCursor('hand') + fs.icon = Material(octolib.icons.silk16('personals')) + fs.Paint = paintTag + fs:AddHint(L.your_friend) + fs.DoClick = function(fs) + self:DoClick() + end + self.fs = {true, fs} + end + + -- afk + local isAFK = self.ply:IsAFK() + if self.afk then + if not isAFK then + self.afk[2]:Remove() + self.afk = nil + end + elseif isAFK then + local afk = vgui.Create 'DPanel' + afk:SetParent(self) + afk:Dock(RIGHT) + afk:SetWide(24) + afk:SetCursor('hand') + afk.icon = Material(octolib.icons.silk16('time')) + afk.Paint = paintTag + afk:AddHint(function() return 'AFK: ' .. niceMinutes(math.floor(self.ply:GetAFKTime() / 60)) end) + afk.DoClick = function(afk) + self:DoClick() + end + self.afk = {true, afk} + end + + -- gun license + local license = self.ply:GetNetVar('HasGunlicense') + if self.gl then + if not license or not canSeeHisLicense(self.ply) then + self.gl[2]:Remove() + self.gl = nil + end + elseif canSeeHisLicense(self.ply) then + local gl = vgui.Create 'DPanel' + gl:SetParent(self) + gl:Dock(RIGHT) + gl:SetWide(24) + gl:SetCursor('hand') + gl.icon = Material(octolib.icons.silk16('page_white_text')) + gl.Paint = paintTag + gl:AddHint(function() local l = self.ply:GetNetVar('HasGunlicense') return (isstring(l) and string.Trim(l) ~= '') and L.license_hint2 .. l or L.have_license end) + gl.DoClick = function(gl) + self:DoClick() + end + self.gl = {true, gl} + end + + -- police + local isPolice = self.ply:GetNetVar('dbg-police.job', '') ~= '' and canSeeHisJob(self.ply) + if self.pj then + if not isPolice then + self.pj[2]:Remove() + self.pj = nil + end + elseif isPolice then + local pj = vgui.Create 'DPanel' + pj:SetParent(self) + pj:Dock(RIGHT) + pj:SetWide(24) + pj:SetCursor('hand') + pj.icon = Material(octolib.icons.silk16('baton')) + pj.Paint = paintTag + pj:AddHint(L.working_in_police) + pj.DoClick = function(pj) + self:DoClick() + end + self.pj = {true, pj} + end + + -- wanted + if self.w then + if not self.ply:GetNetVar('wanted') then + self.w[2]:Remove() + self.w = nil + end + elseif self.ply:GetNetVar('wanted') then + local w = vgui.Create 'DPanel' + w:SetParent(self) + w:Dock(RIGHT) + w:SetWide(24) + w:SetCursor('hand') + w.icon = Material(octolib.icons.silk16('exclamation')) + w.Paint = paintTag + local reason = self.ply:GetNetVar('wanted') + w:AddHint(L.wanted_hint:format(reason)) + w.DoClick = function(w) + self:DoClick() + end + self.w = {true, w} + end + +end + +p.Paint = function(self, w, h) + + if h == maxH then + local drawUpper = self.canvas.VBar:GetScroll() ~= 0 + local drawLower = self.canvas.VBar:GetScroll() ~= self.canvas.VBar.CanvasSize + + surface.DisableClipping(true) + surface.SetDrawColor(0,0,0, 100) + if drawUpper then surface.DrawLine(-5, -1, w - 11, -1) end + if drawLower then surface.DrawLine(-5, h, w - 11, h) end + surface.DisableClipping(false) + end + +end + +p.UpdatePlayerList = function(self) + + self.canvas:Clear() + + local plys = player.GetAll() + table.sort(plys, function(a, b) + return a:Name() < b:Name() + end) + + for i, ply in ipairs(plys) do + if hook.Run('dbg-score.hidePlayer', ply) == true then continue end + + local b = vgui.Create 'DButton' + b:SetParent(self.canvas) + b:Dock(TOP) + b:DockMargin(0,0,0,5) + b:DockPadding(8,8,8,8) + b:SetText('') + b:SetTall(40) + b.ply = ply + b.anim = 0 + b.Paint = paintButton + b.Think = thinkButton + b.DoClick = function(self) + local steamID = ply:SteamID() + local menu = DermaMenu() + + if ply ~= LocalPlayer() then + local types = {'physgun','gravgun','toolgun','playeruse','entitydamage'} + if FPP.Buddies[steamID] ~= nil then + menu:AddOption(L.remove_friend, function() + for k, acessType in pairs( types ) do + FPP.SaveBuddy(steamID, ply:Name(), acessType, 0) + end + end):SetIcon(octolib.icons.silk16('heart_delete')) + else + menu:AddOption(L.add_friend, function() + for k, acessType in pairs( types ) do + FPP.SaveBuddy(steamID, ply:Name(), acessType, 1) + end + end):SetIcon(octolib.icons.silk16('heart')) + end + end + + menu:AddSpacer() + + hook.Run('dbg-admin.getActions', menu, ply, 'score') + + menu:Open() + end + + local r = vgui.Create 'DPanel' + r:SetParent(b) + r:Dock(LEFT) + r:SetWide(24) + r:SetCursor('hand') + r.icon = convert_rank_to_img(ply) + r.Paint = paintTag + r:AddHint(serverguard.ranks.stored[ply:GetUserGroup()].name) + r.DoClick = function() + b:DoClick() + end + + local job = ply:getJobTable() + if job.displayAs and not canSeeHisJob(ply) then + job = DarkRP.getJobByCommand(job.displayAs) + end + + local j = vgui.Create 'DPanel' + j:SetParent(b) + j:Dock(RIGHT) + j:SetWide(24) + j:SetCursor('hand') + j.icon = Material(octolib.icons.silk16(job and job.icon or 'error')) + j.Paint = paintTag + j:AddHint(function() + return j.hint + end) + j.DoClick = function() + b:DoClick() + end + b.j = j + end + + self:SetTall(math.min(player.GetCount() * 45 - 5, maxH)) + self:Center() + +end + +p.PerformLayout = function() end +p.Think = function( self ) + + if self.playerNum ~= player.GetCount() then + self:UpdatePlayerList() + self.playerNum = player.GetCount() + end + + self.al = math.Approach( self.al, self.active and 1 or 0, FrameTime() / 0.3 ) + self:SetAlpha(self.al * 255) + + if self:IsVisible() and self.al == 0 then + self:SetVisible(false) + end + + if LocalPlayer():IsAdmin() and not IsValid(self.adminBut1) then + local b1 = vgui.Create 'DButton' + b1:SetText('') + b1:SetSize(24,24) + b1:SetPos(ScrW() / 2 - 40, ScrH() - 32) + b1.icon = Material(octolib.icons.silk16('shield')) + b1.Paint = paintTag + b1.Think = function(self2) + self2:SetAlpha(self.al * 255) + end + b1:AddHint(L.admin_mode) + function b1:DoClick() + RunConsoleCommand('dbg_admin') + end + + local b2 = vgui.Create 'DButton' + b2:SetText('') + b2:SetSize(24,24) + b2:SetPos(ScrW() / 2 - 12, ScrH() - 32) + b2.icon = Material(octolib.icons.silk16('gear_in')) + b2.Paint = paintTag + b2.Think = function(self2) + self2:SetAlpha(self.al * 255) + end + b2:AddHint(L.admin_menu) + function b2:DoClick() + RunConsoleCommand('serverguard_menu_toggle') + end + + local b3 = vgui.Create 'DButton' + b3:SetText('') + b3:SetSize(24,24) + b3:SetPos(ScrW() / 2 + 16, ScrH() - 32) + b3.icon = Material(octolib.icons.silk16('page_white_magnify')) + b3.Paint = paintTag + b3.Think = function(self2) + self2:SetAlpha(self.al * 255) + end + b3:AddHint(L.hud_logs) + function b3:DoClick() + octoesc.OpenURL('https://octothorp.team/logs') + end + + self.adminBut1 = b1 + self.adminBut2 = b2 + self.adminBut3 = b3 + + self.OnRemove = function() + b1:Remove() + b2:Remove() + b3:Remove() + end + end + +end + +netstream.Hook('dbg-score.update', function() + p.playerNum = -1 +end) + +function GAMEMODE:ScoreboardShow() + + p.active = true + p.holding = true + p:SetMouseInputEnabled(true) + p:SetVisible(true) + +end + +function GAMEMODE:ScoreboardHide() + + if not p.active then return end + p.active = false + p.holding = false + p:SetMouseInputEnabled(false) + +end + +local blur = Material( 'pp/blurscreen' ) +local colors = CFG.skinColors +hook.Add( 'RenderScreenspaceEffects', 'dbg-scoreboard', function() + + local state = p.al + local a = 1 - math.pow( 1 - state, 2 ) + + if a > 0 then + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -a * 0.2, + ['$pp_colour_contrast'] = 1 + 0.5 * a, + ['$pp_colour_colour'] = 1 - a, + } + + if GetConVar('octolib_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor( 255, 255, 255, a * 255 ) + surface.SetMaterial( blur ) + + for i = 1, 3 do + blur:SetFloat( '$blur', a * i * 2 ) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect( -1, -1, ScrW() + 2, ScrH() + 2 ) + end + else + colMod['$pp_colour_brightness'] = -0.4 * a + colMod['$pp_colour_contrast'] = 1 + 0.2 * a + DrawColorModify(colMod) + end + + draw.NoTexture() + local col = colors.bg + surface.SetDrawColor(col.r, col.g, col.b, a * 100) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + end + +end) + +hook.Remove('Think', 'dbg-score') +end) +end +hook.Add('darkrp.loadModules', 'dbg-score', run) +run() diff --git a/garrysmod/addons/_config/lua/config/octoinv/collect/base.lua b/garrysmod/addons/_config/lua/config/octoinv/collect/base.lua new file mode 100644 index 0000000..95a297f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/collect/base.lua @@ -0,0 +1,87 @@ +octoinv.registerCollector('pickaxe', { + name = 'Кирка', + icon = octolib.icons.color('crowbar'), + hold = 'melee2', + interval = 1.5, + delay = 0.15, + power = 10, + health = 500, + sounds = { + {'physics/concrete/concrete_impact_hard1.wav', 75}, + {'physics/concrete/concrete_impact_hard2.wav', 75}, + {'physics/concrete/concrete_impact_hard3.wav', 75}, + }, + worldModel = { + model = 'models/weapons/hl2meleepack/w_pickaxe.mdl', + pos = Vector(-1.9, -0.6, -0.7), + ang = Angle(168.6, -100.5, -4), + scale = 0.8 + }, +}) + +octoinv.registerCollectable('stone', { + name = 'Камень', + collectors = {'pickaxe'}, + period = {10, 90}, + max = 25, + health = 50, + effects = { + hit = 'StunstickImpact', + hitSounds = {'Rock.ImpactSoft'}, + collect = 'GlassImpact', + collectSounds = {'Boulder.ImpactHard'}, + }, + models = { + 'models/props_lab/bigrock.mdl', + 'models/props_wasteland/rockgranite02a.mdl', + 'models/props_wasteland/rockgranite02b.mdl', + 'models/props_wasteland/rockgranite02c.mdl', + 'models/props_wasteland/rockgranite03b.mdl', + 'models/props_wasteland/rockgranite03c.mdl', + 'models/props/cs_militia/militiarock05.mdl', + }, + dropsAmount = 3, + drops = { + {1, {'stone', 1}}, + {0.1, {'craft_coal', 1}}, + {0.14, {'ore_iron', 1}}, + {0.14, {'ore_steel', 1}}, + -- {0.05, {'ore_copper', 1}}, + -- {0.04, {'ore_bronze', 1}}, + {0.03, {'ore_gold', 1}}, + {0.01, {'ore_silver', 1}}, + {0.0015, {'jewelry_diamond', 1}}, + {0.0015, {'jewelry_ruby', 1}}, + {0.0015, {'jewelry_sapphire', 1}}, + {0.0015, {'jewelry_topaz', 1}}, + {0.0015, {'jewelry_emerald', 1}}, + }, +}) + +octoinv.registerCraft('collector_pickaxe', { + name = 'Кирка', + icon = octolib.icons.color('crowbar'), + conts = {'workbench'}, + soundTime = 2, + time = 30, + tools = { + {'tool_saw', 1}, + {'tool_hammer', 1}, + {'tool_screwer', 1}, + }, + ings = { + {'craft_stick', 2}, + {'ingot_iron', 5}, + {'craft_nail', 2}, + {'craft_screw', 4}, + }, + finish = { + {'collector', { + name = 'Кирка', + icon = octolib.icons.color('crowbar'), + collector = 'pickaxe', + maxhealth = 500, + health = 500, + }}, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/craft/_howto.txt b/garrysmod/addons/_config/lua/config/octoinv/craft/_howto.txt new file mode 100644 index 0000000..06159e1 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/craft/_howto.txt @@ -0,0 +1,28 @@ +octoinv.registerCraft('id', { -- Уникальный ID крафта. + name = '', -- Не уникальное имя крафта. + desc = '', -- Описание крафта ( Опционально ). + icon = '', -- Установка иконки. ( Опционально. ) + time = '', -- Время для проведения крафта. ( Опционально. ) + sound = '', -- string звука при крафтинге. + soundTime = 1, -- через сколько секнуд проигрывать звуки + conts = {'',''}, -- Контейнер, в котором крафтится предмет. + jobs = {'job','job'}, -- Работы, которым доступен крафт + tools = { -- Проверка чего-либо у игрока. ( Опционально, от предметов в инвентаре до функции с результатом. ) + {'item', amount} or + + function(ply) + return result, 'error message' + end + }, + ings = { -- Рецепт создания чего-либо. + {'item', amount}, + {'item', amount} + }, + finish = { -- Что будет по выполнению крафта ( Опционально, предметы в инвентарь, либо функция.) + {'item', amount} or + + function(ply) + return result + end + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/craft/benches.lua b/garrysmod/addons/_config/lua/config/octoinv/craft/benches.lua new file mode 100644 index 0000000..9d1f24a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/craft/benches.lua @@ -0,0 +1,330 @@ +------------------------------------------------ +-- +-- BENCHES +-- +------------------------------------------------ + +local drillSounds = { + 'ambient/machines/pneumatic_drill_1.wav', + 'ambient/machines/pneumatic_drill_2.wav', + 'ambient/machines/pneumatic_drill_4.wav', +} + +octoinv.registerCraft('bpd_stove', { + name = L.stove, + desc = L.desc_bpd_stove, + icon = 'octoteam/icons/box3.png', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_screwer', 1}, + {'tool_wrench', 1}, + {'tool_solder', 1}, + {'tool_drill', 1}, + }, + ings = { + {'craft_cable', 4}, + {'craft_bulb', 4}, + {'craft_sheet', 2}, + {'craft_plug', 1}, + {'craft_gauge', 1}, + {'craft_relay', 2}, + {'craft_transistor', 4}, + {'craft_resistor', 2}, + }, + finish = { + {'bpd_stove', 1}, + }, +}) octoinv.registerCraft('cont_stove', { + name = L.stove, + desc = L.desc_cont_stove, + icon = 'octoteam/icons/stove.png', + previewModel = 'models/props_c17/furniturestove001a.mdl', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_craft', 1}, + }, + ings = { + {'bp_stove', 1}, + {'bpd_stove', 1}, + {'craft_battery', 1}, + }, + finish = octoinv.spawnProd({ + mdl = 'models/props_c17/furniturestove001a.mdl', + conts = { + stove = { name = L.stove, volume = 40, icon = octolib.icons.color('stove'), craft = true, prod = true }, + oven = { name = L.oven, volume = 60, icon = octolib.icons.color('stove'), craft = true }, + }, + prod = 'stove', + }), +}) + +octoinv.registerCraft('bpd_fridge', { + name = L.fridge, + desc = L.desc_bpd_fridge, + icon = 'octoteam/icons/box3.png', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_screwer', 1}, + {'tool_drill', 1}, + {'tool_solder', 1}, + }, + ings = { + {'craft_cable', 3}, + {'craft_spring', 2}, + {'craft_sheet', 1}, + {'craft_pulley', 2}, + {'craft_plug', 1}, + {'craft_bulb', 4}, + {'craft_transistor', 2}, + {'craft_resistor', 2}, + {'craft_relay', 4}, + }, + finish = { + {'bpd_fridge', 1}, + }, +}) octoinv.registerCraft('cont_fridge', { + name = L.fridge, + desc = L.desc_cont_fridge, + icon = 'octoteam/icons/fridge.png', + previewModel = 'models/props_c17/furniturefridge001a.mdl', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_craft', 1}, + }, + ings = { + {'bp_fridge', 1}, + {'bpd_fridge', 1}, + {'craft_battery', 1}, + }, + finish = octoinv.spawnProd({ + mdl = 'models/props_c17/furniturefridge001a.mdl', + conts = { + fridge = { name = L.fridge, volume = 60, icon = octolib.icons.color('fridge'), prod = true }, + }, + prod = 'fridge', + }), +}) + +octoinv.registerCraft('bpd_machine', { + name = L.machine, + desc = L.desc_bpd_machine, + icon = 'octoteam/icons/box3.png', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_caliper', 1}, + {'tool_solder', 1}, + {'tool_screwer', 1}, + {'tool_drill', 1}, + {'tool_wrench', 1}, + }, + ings = { + {'craft_cable', 3}, + {'craft_piston', 2}, + {'craft_spring', 4}, + {'craft_sheet', 2}, + {'craft_pulley', 2}, + {'craft_plug', 1}, + {'craft_engine', 1}, + {'craft_gauge', 2}, + }, + finish = { + {'bpd_machine', 1}, + }, +}) octoinv.registerCraft('cont_machine', { + name = L.machine, + desc = L.desc_cont_machine, + icon = 'octoteam/icons/machine.png', + previewModel = 'models/codeandmodeling_samgaze_gta/gr_prop_gr_vertmill_01c.mdl', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_craft', 1}, + }, + ings = { + {'bp_machine', 1}, + {'bpd_machine', 1}, + {'craft_battery', 1}, + }, + finish = octoinv.spawnProd({ + mdl = 'models/codeandmodeling_samgaze_gta/gr_prop_gr_vertmill_01c.mdl', + conts = { + machine_tray = { name = L.machine_tray, volume = 60, icon = octolib.icons.color('machine'), prod = true }, + machine = { name = L.machine, volume = 60, icon = octolib.icons.color('machine') }, + }, + prod = 'machine' + }), +}) + +octoinv.registerCraft('bpd_workbench', { + name = L.workbench, + desc = L.desc_bpd_workbench, + icon = 'octoteam/icons/box3.png', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_caliper', 1}, + {'tool_solder', 1}, + {'tool_wrench', 1}, + {'tool_hammer', 1}, + {'tool_drill', 1}, + }, + ings = { + {'craft_cable', 2}, + {'craft_piston', 4}, + {'craft_spring', 4}, + {'craft_bulb', 2}, + {'craft_plank', 2}, + {'craft_pulley', 2}, + {'craft_plug', 1}, + {'craft_engine', 1}, + }, + finish = { + {'bpd_workbench', 1}, + }, +}) octoinv.registerCraft('cont_workbench', { + name = L.workbench, + desc = L.cont_workbench, + icon = 'octoteam/icons/table.png', + previewModel = 'models/codeandmodeling_samgaze_gta/prop_tool_bench02.mdl', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_craft', 1}, + }, + ings = { + {'bp_workbench', 1}, + {'bpd_workbench', 1}, + }, + finish = octoinv.spawnCont('models/codeandmodeling_samgaze_gta/prop_tool_bench02.mdl', { + workbench = { name = L.workbench, volume = 100, icon = octolib.icons.color('table'), craft = true }, + },{ + destruct = { + {'bpd_workbench', 1}, + }, + }), +}) + +octoinv.registerCraft('bpd_refinery', { + name = L.refinery, + desc = L.desc_bpd_refinery, + icon = 'octoteam/icons/box3.png', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_drill', 1}, + {'tool_screwer', 1}, + {'tool_caliper', 1}, + }, + ings = { + {'craft_cable', 2}, + {'craft_sheet', 2}, + {'craft_pulley', 2}, + {'craft_plug', 1}, + {'craft_chip2', 2}, + {'craft_resistor', 2}, + {'craft_gauge', 4}, + {'craft_engine', 1}, + }, + finish = { + {'bpd_refinery', 1}, + }, +}) octoinv.registerCraft('cont_refinery', { + name = L.refinery, + desc = L.desc_refinery, + icon = 'octoteam/icons/chem_plant.png', + previewModel = 'models/mark2580/gtav/garage_stuff/sand_blaster_01a.mdl', + previewRotation = 90, + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_craft', 1}, + }, + ings = { + {'bp_refinery', 1}, + {'bpd_refinery', 1}, + }, + finish = octoinv.spawnProd({ + mdl = 'models/mark2580/gtav/garage_stuff/sand_blaster_01a.mdl', + conts = { + refinery_fuel = { name = L.refinery_fuel, volume = 100, icon = octolib.icons.color('chem_plant'), prod = true }, + refinery = { name = L.refinery, volume = 100, icon = octolib.icons.color('chem_plant') }, + }, + prod = 'refinery', + rotate = 90, + }), +}) + +octoinv.registerCraft('bpd_smelter', { + name = L.smelter, + desc = L.desc_bpd_smelter, + icon = 'octoteam/icons/box3.png', + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_drill', 1}, + {'tool_screwer', 1}, + {'tool_caliper', 1}, + }, + ings = { + {'ingot_iron', 1}, + {'craft_sheet', 2}, + {'craft_pulley', 2}, + {'craft_gauge', 2}, + }, + finish = { + {'bpd_smelter', 1}, + }, +}) octoinv.registerCraft('cont_smelter', { + name = L.smelter, + desc = L.desc_smelter, + icon = 'octoteam/icons/smelter.png', + previewModel = 'models/props/de_train/processor.mdl', + previewRotation = 90, + conts = {'_hand'}, + sound = drillSounds, + soundTime = 2, + time = 30, + tools = { + {'tool_craft', 1}, + }, + ings = { + {'bp_smelter', 1}, + {'bpd_smelter', 1}, + }, + finish = octoinv.spawnProd({ + mdl = 'models/props/de_train/processor.mdl', + conts = { + smelter_fuel = { name = L.smelter_fuel, volume = 100, icon = octolib.icons.color('smelter'), prod = true }, + smelter = { name = L.smelter, volume = 100, icon = octolib.icons.color('smelter') }, + }, + prod = 'smelter', + rotate = 90, + }), +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/craft/conts.lua b/garrysmod/addons/_config/lua/config/octoinv/craft/conts.lua new file mode 100644 index 0000000..af53cc6 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/craft/conts.lua @@ -0,0 +1,73 @@ +local woodsounds = { + 'physics/wood/wood_box_impact_bullet1.wav', + 'physics/wood/wood_box_impact_bullet2.wav', + 'physics/wood/wood_box_impact_bullet3.wav', + 'physics/wood/wood_box_impact_bullet4.wav', + 'physics/wood/wood_box_impact_hard1.wav', + 'physics/wood/wood_box_impact_hard2.wav', + 'physics/wood/wood_box_impact_hard3.wav', +} + +------------------------------------------------ +-- +-- CONTAINERS +-- +------------------------------------------------ + +-- octoinv.registerCraft('bpd_vend', { +-- name = L.vend, +-- desc = L.desc_bpd_vend, +-- icon = 'octoteam/icons/box3.png', +-- conts = {'_hand'}, +-- sound = drillSounds, +-- soundTime = 2, +-- time = 30, +-- tools = { +-- {'tool_solder', 1}, +-- {'tool_drill', 1}, +-- }, +-- ings = { +-- {'craft_cable', 4}, +-- {'craft_bulb', 5}, +-- {'craft_sheet', 2}, +-- {'craft_plug', 1}, +-- {'craft_relay', 1}, +-- {'craft_transistor', 8}, +-- {'craft_resistor', 8}, +-- }, +-- finish = { +-- {'bpd_vend', 1}, +-- }, +-- }) octoinv.registerCraft('cont_vend', { +-- name = L.vend, +-- desc = L.desc_cont_vend, +-- icon = 'octoteam/icons/machine_vend.png', +-- conts = {'_hand'}, +-- sound = drillSounds, +-- soundTime = 2, +-- time = 30, +-- tools = { +-- {'tool_craft', 1}, +-- }, +-- ings = { +-- {'bp_vend', 1}, +-- {'bpd_vend', 1}, +-- {'craft_battery', 1}, +-- }, +-- finish = function(ply, cont) +-- local ent = ents.Create 'octoinv_vend' +-- ent.dt = ent.dt or {} +-- ent.dt.owning_ent = ply +-- ent.DestructParts = { +-- {'bpd_vend', 1}, +-- } + +-- ent.SID = ply.SID +-- ent:Spawn() + +-- ply:BringEntity(ent) +-- ent:SetPlayer(ply) +-- ent:SetLocked(false) +-- return true +-- end, +-- }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/craft/cooking.lua b/garrysmod/addons/_config/lua/config/octoinv/craft/cooking.lua new file mode 100644 index 0000000..9de9871 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/craft/cooking.lua @@ -0,0 +1,377 @@ +------------------------------------------------ +-- +-- COOKING +-- +------------------------------------------------ + +local cookSounds = { + 'physics/flesh/flesh_squishy_impact_hard1.wav', + 'physics/flesh/flesh_squishy_impact_hard2.wav', + 'physics/flesh/flesh_squishy_impact_hard3.wav', + 'physics/flesh/flesh_squishy_impact_hard4.wav', + 'physics/plastic/plastic_box_impact_soft1.wav', + 'physics/plastic/plastic_box_impact_soft2.wav', + 'physics/plastic/plastic_box_impact_soft3.wav', + 'physics/plastic/plastic_box_impact_soft4.wav', + 'physics/plastic/plastic_box_break1.wav', + 'physics/plastic/plastic_box_break2.wav', +} + +-- +-- INGREDIENTS +-- + +octoinv.registerCraft('ing_dough1', { + name = L.dough1, + desc = L.desc_dough1, + conts = {'stove'}, + sound = cookSounds, + time = 15, + ings = { + {'ing_flour', 1}, + {'ing_salt', 1}, + {'ing_milk', 1}, + {'ing_egg', 2}, + }, + finish = { + {'ing_dough1', 1}, + } +}) + +octoinv.registerCraft('ing_dough2', { + name = L.dough2, + desc = L.desc_dough2, + conts = {'stove'}, + sound = cookSounds, + time = 15, + ings = { + {'ing_flour', 1}, + {'ing_sugar', 1}, + {'ing_egg', 2}, + }, + finish = { + {'ing_dough2', 1}, + } +}) + +octoinv.registerCraft('ing_icecream', { + name = L.ing_icecream2, + desc = L.desc_ing_icecream, + conts = {'stove'}, + sound = cookSounds, + time = 5, + tools = { + {'tool_scoop', 1}, + }, + ings = { + {'ing_cream', 2}, + {'ing_sugar', 2}, + }, + finish = { + {'ing_icecream', 1}, + } +}) + +octoinv.registerCraft('ing_potatomash', { + name = L.potatomash, + desc = L.desc_potatomash, + conts = {'stove'}, + sound = cookSounds, + time = 10, + tools = { + {'tool_pot', 1}, + }, + ings = { + {'ing_potato2', 2}, + {'ing_oil', 1}, + {'ing_salt', 1}, + {'ing_milk', 1}, + }, + finish = { + {'ing_potatomash', 1}, + }, +}) + +-- +-- DISHES +-- + +octoinv.registerCraft('food_meatpotato', { + name = L.meatpotato, + desc = L.desc_meatpotato, + conts = {'stove'}, + sound = cookSounds, + time = 4, + ings = { + {'tool_foodcont', 1}, + {'ing_potatomash', 1}, + {'ing_meatball', 1}, + }, + finish = { + {'food', { + name = L.meatpotato, + icon = 'octoteam/icons/food_mashed_potato_with_cutlet.png', + energy = 70, + }}, + } +}) + +octoinv.registerCraft('food_okroshka', { + name = L.okroshka, + desc = L.desc_okroshka, + conts = {'stove'}, + sound = cookSounds, + time = 10, + tools = { + {'tool_pot', 1}, + }, + ings = { + {'tool_foodcont', 1}, + {'ing_egg', 2}, + {'ing_salt', 1}, + {'ing_cucumber', 2}, + {'ing_potato2', 3}, + }, + finish = { + {'food', { + name = L.okroshka, + icon = 'octoteam/icons/food_soup.png', + energy = 65, + }}, + } +}) + +octoinv.registerCraft('food_sandwitch', { + name = L.sandwitch, + desc = L.desc_sandwitch, + conts = {'stove', '_hand'}, + sound = cookSounds, + time = 10, + ings = { + {'ing_sausage2', 1}, + {'ing_bread', 1}, + }, + finish = { + {'food', { + name = L.sandwitch, + icon = 'octoteam/icons/food_sandwich_simple.png', + energy = 15, + }}, + } +}) + +-- octoinv.registerCraft('food_sushi', { +-- name = L.sushi, +-- desc = L.desc_sushi, +-- conts = {'stove'}, +-- sound = cookSounds, +-- time = 10, +-- tools = { +-- {'tool_scoop', 1}, +-- }, +-- ings = { +-- {'tool_foodcont', 1}, +-- {'ing_fish1', 2}, +-- {'ing_rice', 2}, +-- }, +-- finish = { +-- {'food', { +-- name = L.sushi, +-- icon = 'octoteam/icons/food_sushi.png', +-- energy = 35, +-- }}, +-- } +-- }) + +octoinv.registerCraft('food_wrap', { + name = L.wrap, + desc = L.desc_wrap, + conts = {'stove'}, + sound = cookSounds, + time = 10, + ings = { + {'tool_foodcont', 1}, + {'ing_roastmeat', 2}, + {'ing_lettuce', 1}, + {'ing_salt', 1}, + {'ing_pita', 1}, + {'ing_tomato', 1}, + }, + finish = { + {'food', { + name = L.wrap, + icon = 'octoteam/icons/food_wrap.png', + energy = 60, + }}, + } +}) + +octoinv.registerCraft('food_cake_med', { + name = L.cake_med, + desc = L.desc_cake_med, + conts = {'stove'}, + sound = cookSounds, + time = 10, + ings = { + {'tool_foodcont', 1}, + {'ing_dough3', 1}, + {'ing_sugar', 2}, + {'ing_cocoa', 2}, + {'ing_cream', 1}, + {'ing_honey', 2}, + }, + finish = { + {'food', { + name = L.cake_med, + icon = 'octoteam/icons/food_honey_cake.png', + energy = 100, + }}, + } +}) + +octoinv.registerCraft('food_ceasar', { + name = L.ceasar, + desc = L.desc_ceasar, + conts = {'stove'}, + sound = cookSounds, + time = 10, + ings = { + {'tool_foodcont', 1}, + {'ing_roastmeat', 1}, + {'ing_salt', 1}, + {'ing_oil', 1}, + {'ing_egg', 2}, + {'ing_lettuce', 1}, + {'ing_cheese', 1}, + {'ing_tomato', 2}, + }, + finish = { + {'food', { + name = L.ceasar, + icon = 'octoteam/icons/food_caesar_salad.png', + energy = 65, + }}, + } +}) + +octoinv.registerCraft('food_burger1', { + name = L.burger, + desc = L.desc_burger, + conts = {'stove'}, + sound = cookSounds, + time = 10, + ings = { + {'tool_foodcont', 1}, + {'ing_bread', 2}, + {'ing_sauce', 1}, + {'ing_meatball', 1}, + }, + finish = { + {'food', { + name = L.burger, + icon = 'octoteam/icons/food_hamburger.png', + energy = 35, + }}, + } +}) + +octoinv.registerCraft('food_burger2', { + name = L.burger2, + desc = L.desc_burger2, + conts = {'stove'}, + sound = cookSounds, + time = 10, + ings = { + {'tool_foodcont', 1}, + {'ing_bread', 2}, + {'ing_sauce', 1}, + {'ing_meatball', 1}, + {'ing_lettuce', 1}, + {'ing_cheese', 1}, + }, + finish = { + {'food', { + name = L.burger2, + icon = 'octoteam/icons/food_cheeseburger.png', + energy = 45, + }}, + } +}) + +octoinv.registerCraft('food_burger3', { + name = L.burger3, + desc = L.desc_burger3, + conts = {'stove'}, + sound = cookSounds, + time = 10, + ings = { + {'tool_foodcont', 1}, + {'ing_bread', 2}, + {'ing_sauce', 1}, + {'ing_meatball', 2}, + {'ing_lettuce', 1}, + {'ing_cheese', 1}, + {'ing_tomato', 2}, + {'ing_onion', 1}, + {'ing_cucumber', 2}, + }, + finish = { + {'food', { + name = L.burger3, + icon = 'octoteam/icons/food_bigmack.png', + energy = 100, + }}, + } +}) + +octoinv.registerCraft('food_coffeetail', { + name = L.coffeetail2, + conts = {'stove'}, + sound = cookSounds, + time = 10, + tools = { + {'tool_shaker', 1}, + }, + ings = { + {'tool_foodcont', 1}, + {'drug_booze2', 1}, + {'ing_water', 1}, + {'ing_coffee', 2}, + {'ing_sugar', 4}, + }, + finish = { + {'food', { + name = L.coffeetail2, + icon = 'octoteam/icons/food_cocktail.png', + energy = 15, + drink = true, + }}, + } +}) + +octoinv.registerCraft('food_coffeetail', { + name = L.coffeetail, + conts = {'stove'}, + sound = cookSounds, + time = 15, + tools = { + {'tool_pot', 1}, + {'tool_shaker', 1}, + }, + ings = { + {'tool_foodcont', 1}, + {'ing_milk', 1}, + {'ing_egg', 1}, + {'ing_sugar', 4}, + {'ing_honey', 1}, + {'ing_salt', 1}, + }, + finish = { + {'food', { + name = L.coffeetail, + icon = 'octoteam/icons/food_gogol_mogul.png', + energy = 30, + drink = true, + }}, + } +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/craft/gun.lua b/garrysmod/addons/_config/lua/config/octoinv/craft/gun.lua new file mode 100644 index 0000000..7dd2a46 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/craft/gun.lua @@ -0,0 +1,1060 @@ +------------------------------------------------ +-- +-- GUN CRAFTING +-- +------------------------------------------------ + +local benchSounds = { + 'physics/metal/metal_box_strain1.wav', + 'physics/metal/metal_box_strain2.wav', + 'physics/metal/metal_box_strain3.wav', + 'physics/metal/metal_box_strain4.wav', + 'physics/metal/metal_canister_impact_soft1.wav', + 'physics/metal/metal_canister_impact_soft2.wav', + 'physics/metal/metal_canister_impact_soft3.wav', +} + +-- ammo + +octoinv.registerCraft('ammo_pistol', { + name = L.small_ammo, + icon = 'octoteam/icons/gun_bullet.png', + conts = {'workbench'}, + jobs = {'gun', 'gun2'}, + sound = benchSounds, + time = 5, + ings = { + {'gunpowder', 1}, + {'craft_sheet', 1}, + }, + finish = octolib.array.series({'ammo', { + name = L.small_ammo, + icon = 'octoteam/icons/gun_bullet.png', + mass = 0.8, + volume = 0.25, + ammotype = 'pistol', + ammocount = 24, + }}, 2) +}) + +octoinv.registerCraft('ammo_smg', { + name = L.large_ammo, + icon = 'octoteam/icons/gun_bullet2.png', + conts = {'workbench'}, + jobs = {'gun', 'gun2'}, + sound = benchSounds, + time = 5, + ings = { + {'gunpowder', 2}, + {'craft_sheet', 1}, + }, + finish = octolib.array.series({'ammo', { + name = L.large_ammo, + icon = 'octoteam/icons/gun_bullet2.png', + mass = 1.5, + volume = 0.5, + ammotype = 'smg1', + ammocount = 30, + }}, 1) +}) + +octoinv.registerCraft('ammo_sniper', { + name = L.sniper_ammo, + icon = 'octoteam/icons/gun_bullet2.png', + conts = {'workbench'}, + jobs = {'gun', 'gun2'}, + sound = benchSounds, + time = 5, + ings = { + {'gunpowder', 4}, + {'craft_sheet', 4}, + }, + finish = octolib.array.series({'ammo', { + name = L.sniper_ammo, + icon = 'octoteam/icons/gun_bullet2.png', + mass = 1.3, + volume = 0.6, + ammotype = 'sniper', + ammocount = 12, + }}, 2) +}) + +octoinv.registerCraft('ammo_buckshot', { + name = L.buckshot, + icon = 'octoteam/icons/gun_bullet.png', + conts = {'workbench'}, + jobs = {'gun', 'gun2'}, + sound = benchSounds, + time = 5, + ings = { + {'gunpowder', 2}, + {'craft_sheet', 3}, + }, + finish = octolib.array.series({'ammo', { + name = L.buckshot, + icon = 'octoteam/icons/gun_bullet.png', + mass = 0.6, + volume = 0.35, + ammotype = 'buckshot', + ammocount = 8, + }}, 3) +}) + +-- weapons + +octoinv.registerCraft('armor_l', { + name = L.armor_l, + desc = L.desc_armor, + conts = {'workbench'}, + icon = 'octoteam/icons/armor_heavy.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 10, + ings = { + {'craft_sheet', 12}, + {'craft_scotch', 1}, + {'craft_glue', 1}, + }, + finish = { + {'armor', { + name = L.armor_l, + icon = 'octoteam/icons/armor_heavy.png', + armor = 40, + }}, + } +}) + +octoinv.registerCraft('armor', { + name = L.armor, + desc = L.desc_armor, + conts = {'workbench'}, + icon = 'octoteam/icons/armor.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 10, + ings = { + {'craft_sheet', 5}, + {'craft_scotch', 1}, + {'craft_glue', 1}, + }, + finish = { + {'armor', { + armor = 28, + }}, + } +}) + +octoinv.registerCraft('armor_s', { + name = L.armor_s, + desc = L.desc_armor, + conts = {'workbench'}, + icon = 'octoteam/icons/armor_light.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 10, + ings = { + {'craft_sheet', 3}, + {'craft_scotch', 1}, + {'craft_glue', 1}, + }, + finish = { + {'armor', { + name = L.armor_s, + icon = 'octoteam/icons/armor_light.png', + armor = 16, + }}, + } +}) + +octoinv.registerCraft('gun_knife', { + name = L.knife, + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/knife.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 10, + ings = { + {'craft_sheet', 2}, + {'craft_grip', 1}, + {'craft_pulley', 2}, + {'craft_spring', 1}, + {'craft_glue', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_knife}, + } +}) + +octoinv.registerCraft('gun_axe', { + name = L.axe, + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_axe.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 10, + ings = { + {'craft_sheet', 2}, + {'craft_plank', 1}, + {'craft_grip', 1}, + {'craft_glue', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_axe}, + } +}) + +octoinv.registerCraft('gun_shovel', { + name = L.shovel, + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/shovel.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 10, + ings = { + {'craft_sheet', 2}, + {'craft_plank', 2}, + {'craft_glue', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_shovel}, + } +}) + +octoinv.registerCraft('gun_hook', { + name = L.hook, + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/crowbar.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 12, + ings = { + {'craft_sheet', 5}, + {'craft_grip', 1}, + {'craft_glue', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_hook}, + } +}) + +octoinv.registerCraft('gun_lockpick', { + name = L.gun_lockpick, + desc = L.desc_gun_lockpick, + conts = {'workbench'}, + icon = 'octoteam/icons/lockpick.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 10, + ings = { + {'craft_sheet', 1}, + {'craft_grip', 1}, + {'craft_glue', 1}, + }, + finish = { + {'lockpick', 6}, + } +}) + +octoinv.registerCraft('gun_cracker', { + name = L.keypad_cracker, + desc = L.desc_cracker, + conts = {'workbench'}, + icon = 'octoteam/icons/keypad_cracker.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_sheet', 4}, + {'craft_chip1', 2}, + {'craft_chip2', 1}, + {'craft_relay', 20}, + {'craft_resistor', 25}, + {'craft_transistor', 20}, + {'craft_scotch', 1}, + {'craft_glue', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.keypad_cracker}, + } +}) + +octoinv.registerCraft('gun_glock', { + name = 'Glock', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 1}, -- 1000 + {'craft_barrel', 1}, -- 1000 + {'craft_grip', 1}, -- 2000 + {'craft_trigger', 1}, -- 250 + {'craft_clip', 1}, -- 2000 + {'craft_piston', 1}, -- 125 + {'craft_pulley', 1}, -- 100 + {'craft_spring', 1}, -- 50 + {'craft_glue', 1}, -- 35 + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_glock}, + } +}) + +octoinv.registerCraft('gun_usp', { + name = 'USP', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 1}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 1}, + {'craft_trigger', 1}, + {'craft_stopper', 1}, + {'craft_clip', 1}, + {'craft_piston', 5}, + {'craft_pulley', 4}, + {'craft_spring', 3}, + {'craft_glue', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_usp}, + } +}) + +octoinv.registerCraft('gun_usps', { + name = 'USP-S', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 1}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 1}, + {'craft_trigger', 1}, + {'craft_stopper', 1}, + {'craft_clip', 1}, + {'craft_piston', 5}, + {'craft_pulley', 4}, + {'craft_spring', 3}, + {'craft_glue', 1}, + {'craft_silencer', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_usps}, + } +}) + +octoinv.registerCraft('gun_p228', { + name = 'P228', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 1}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 1}, + {'craft_trigger', 1}, + {'craft_stopper', 1}, + {'craft_clip', 1}, + {'craft_piston', 5}, + {'craft_pulley', 4}, + {'craft_spring', 3}, + {'craft_glue', 1}, + {'craft_sheet', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_p228}, + } +}) + +octoinv.registerCraft('gun_fiveseven', { + name = 'FiveseveN', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 1}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 3}, + {'craft_trigger', 1}, + {'craft_stopper', 2}, + {'craft_clip', 1}, + {'craft_piston', 5}, + {'craft_pulley', 4}, + {'craft_spring', 3}, + {'craft_glue', 1}, + {'craft_sheet', 3}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_fiveseven}, + } +}) + +octoinv.registerCraft('gun_deagle', { + name = 'Desert Eagle', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 2}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 5}, + {'craft_trigger', 1}, + {'craft_stopper', 4}, + {'craft_clip', 1}, + {'craft_piston', 8}, + {'craft_pulley', 5}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 4}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_deagle}, + } +}) + +octoinv.registerCraft('gun_357', { + name = 'Colt .357', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_revolver.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 2}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 6}, + {'craft_clip', 1}, + {'craft_piston', 8}, + {'craft_pulley', 5}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 4}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_357}, + } +}) + +octoinv.registerCraft('gun_dualelites', { + name = 'Dual Elites', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_pistol.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 2}, + {'craft_barrel', 4}, + {'craft_grip', 2}, + {'craft_drummer', 4}, + {'craft_trigger', 2}, + {'craft_stopper', 4}, + {'craft_clip', 2}, + {'craft_piston', 10}, + {'craft_pulley', 4}, + {'craft_spring', 4}, + {'craft_glue', 2}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_dualelites}, + } +}) + +octoinv.registerCraft('gun_mp5', { + name = 'MP5', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_smg.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 5}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 1}, + {'craft_piston', 8}, + {'craft_pulley', 5}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 4}, + {'craft_aim', 2}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_mp5}, + } +}) + +octoinv.registerCraft('gun_ump45', { + name = 'UMP45', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_smg.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 5}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 1}, + {'craft_piston', 8}, + {'craft_pulley', 5}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 3}, + {'craft_aim', 2}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_ump45}, + } +}) + +octoinv.registerCraft('gun_mac10', { + name = 'MAC10', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_smg.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 2}, + {'craft_barrel', 2}, + {'craft_grip', 1}, + {'craft_drummer', 5}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 1}, + {'craft_piston', 8}, + {'craft_pulley', 5}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 2}, + {'craft_aim', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_mac10}, + } +}) + +octoinv.registerCraft('gun_tmp', { + name = 'TMP', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_smg.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 5}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 1}, + {'craft_piston', 10}, + {'craft_pulley', 5}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 5}, + {'craft_aim', 2}, + {'craft_silencer', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_tmp}, + } +}) + +octoinv.registerCraft('gun_m3', { + name = 'M3', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_shotgun.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 3}, + {'craft_grip', 2}, + {'craft_drummer', 8}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 1}, + {'craft_piston', 12}, + {'craft_pulley', 8}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 3}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_m3}, + } +}) + +octoinv.registerCraft('gun_p90', { + name = 'P90', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_smg.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 2}, + {'craft_piston', 12}, + {'craft_pulley', 8}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 6}, + {'craft_aim', 2}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_p90}, + } +}) + +octoinv.registerCraft('gun_galil', { + name = 'Galil', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_rifle.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 8}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 6}, + {'craft_aim', 2}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_galil}, + } +}) + +octoinv.registerCraft('gun_scout', { + name = 'Scout', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_sniper.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 8}, + {'craft_spring', 5}, + {'craft_glue', 1}, + {'craft_sheet', 6}, + {'craft_sight', 1}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_scout}, + } +}) + +octoinv.registerCraft('gun_famas', { + name = 'FAMAS', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_rifle.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 5}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 6}, + {'craft_aim', 4}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_famas}, + } +}) + +octoinv.registerCraft('gun_xm1014', { + name = 'XM1014', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_shotgun.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 2}, + {'craft_grip', 2}, + {'craft_drummer', 10}, + {'craft_trigger', 1}, + {'craft_stopper', 10}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 6}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_xm1014}, + } +}) + +octoinv.registerCraft('gun_ak', { + name = 'AK', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_rifle.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 3}, + {'craft_grip', 3}, + {'craft_drummer', 8}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 6}, + {'craft_aim', 4}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_ak}, + } +}) + +octoinv.registerCraft('gun_m4a1', { + name = 'M4A1', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_rifle.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 4}, + {'craft_barrel', 3}, + {'craft_grip', 3}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 8}, + {'craft_aim', 5}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_m4a1}, + } +}) + +octoinv.registerCraft('gun_aug', { + name = 'AUG', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_sniper.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 4}, + {'craft_barrel', 3}, + {'craft_grip', 3}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 10}, + {'craft_sight', 3}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_aug}, + } +}) + +octoinv.registerCraft('gun_sg552', { + name = 'SG552', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_sniper.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 4}, + {'craft_barrel', 3}, + {'craft_grip', 3}, + {'craft_drummer', 6}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 8}, + {'craft_glue', 1}, + {'craft_sheet', 10}, + {'craft_sight', 3}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_sg552}, + } +}) + +octoinv.registerCraft('gun_awp', { + name = 'AWP', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_sniper.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 4}, + {'craft_barrel', 3}, + {'craft_grip', 3}, + {'craft_drummer', 12}, + {'craft_trigger', 1}, + {'craft_stopper', 12}, + {'craft_clip', 3}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 15}, + {'craft_glue', 1}, + {'craft_sheet', 10}, + {'craft_sight', 5}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_awp}, + } +}) + +octoinv.registerCraft('gun_g3sg1', { + name = 'G3SG1', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_sniper.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 4}, + {'craft_barrel', 3}, + {'craft_grip', 3}, + {'craft_drummer', 8}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 5}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 10}, + {'craft_glue', 1}, + {'craft_sheet', 10}, + {'craft_sight', 3}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_g3sg1}, + } +}) + +octoinv.registerCraft('gun_sg550', { + name = 'SG550', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_sniper.png', + sound = benchSounds, + jobs = {'gun2'}, + time = 15, + ings = { + {'craft_shutter', 5}, + {'craft_barrel', 5}, + {'craft_grip', 3}, + {'craft_drummer', 8}, + {'craft_trigger', 1}, + {'craft_stopper', 8}, + {'craft_clip', 5}, + {'craft_piston', 12}, + {'craft_pulley', 10}, + {'craft_spring', 12}, + {'craft_glue', 1}, + {'craft_sheet', 10}, + {'craft_sight', 3}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_sg550}, + } +}) + +octoinv.registerCraft('gun_m249', { + name = 'M249', + desc = L.weapons, + conts = {'workbench'}, + icon = 'octoteam/icons/gun_rifle.png', + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 15, + ings = { + {'craft_shutter', 3}, + {'craft_barrel', 5}, + {'craft_grip', 3}, + {'craft_drummer', 8}, + {'craft_trigger', 1}, + {'craft_stopper', 10}, + {'craft_clip', 25}, + {'craft_piston', 20}, + {'craft_pulley', 20}, + {'craft_spring', 15}, + {'craft_glue', 1}, + {'craft_aim', 5}, + }, + finish = { + {'weapon', octoinv.gunsItemData.weapon_octo_m249}, + } +}) + +------------------------------------------------ +-- +-- CRAFTABLE ITEMS +-- +------------------------------------------------ + +octoinv.registerCraft('craft_aim', { + name = L.crosshair, + desc = L.weapons, + conts = {'workbench'}, + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 5, + tools = { + {'tool_screwer', 1}, + }, + ings = { + {'craft_barrel', 1}, + {'craft_glass', 2}, + }, + finish = { + {'craft_sight', 1}, + } +}) + +octoinv.registerCraft('craft_silencer', { + name = L.silencer, + desc = L.weapons, + conts = {'workbench'}, + sound = benchSounds, + jobs = {'gun', 'gun2'}, + time = 5, + tools = { + {'tool_screwer', 1}, + }, + ings = { + {'craft_barrel', 1}, + {'craft_sheet', 1}, + {'craft_glue', 1}, + }, + finish = { + {'craft_silencer', 1}, + } +}) + +octoinv.registerCraft('gunpowder', { + name = L.gunpowder, + desc = L.descCraft, + conts = {'workbench'}, + sound = benchSounds, + time = 10, + tools = { + {'tool_hammer', 1}, + }, + ings = { + {'craft_coal', 1}, + {'sulfur', 1}, + {'saltpeter', 1}, + }, + finish = { + {'gunpowder', 5}, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/craft/other.lua b/garrysmod/addons/_config/lua/config/octoinv/craft/other.lua new file mode 100644 index 0000000..3c9c4e1 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/craft/other.lua @@ -0,0 +1,116 @@ +local function checkPrinterLimit(ply) + local prods = octolib.getLinkedEnts(ply:SteamID(), {'octoinv_prod'}) + local hasPrinters = octolib.table.count(prods, function(ent) + local inv = IsValid(ent) and ent.inv + return inv and inv.conts and inv.conts.printer or false + end) + if hasPrinters >= 2 then + return false, 'Достигнут лимит принтеров' + end + return true +end + +------------------------------------------------ +-- +-- OTHER +-- +------------------------------------------------ + +-- octoinv.registerCraft('printer', { +-- name = L.printer, +-- desc = L.desc_printer, +-- icon = 'octoteam/icons/atm.png', +-- conts = {'_hand'}, +-- sound = drillSounds, +-- soundTime = 2, +-- time = 25, +-- tools = { +-- {'tool_screwer', 1}, +-- {'tool_wrench', 1}, +-- }, +-- ings = { +-- {'bp_printer', 1}, +-- {'craft_screw2', 20}, +-- {'craft_screwnut', 20}, +-- {'craft_engine', 1}, +-- {'craft_cable', 3}, +-- {'craft_cnc', 1}, +-- {'craft_sheet', 2}, +-- }, +-- finish = octoinv.spawnProd({ +-- mdl = 'models/props_c17/consolebox01a.mdl', +-- conts = { +-- printer_cart = { name = L.printer_cart, volume = 3 }, +-- printer = { name = L.printer, volume = 5, prod = true }, +-- }, +-- prod = 'printer', +-- check = checkPrinterLimit, +-- other = { +-- CanBeOwnedBy = function(_, ply) +-- return checkPrinterLimit(ply) +-- end, +-- }, +-- }), +-- }) + +-- octoinv.registerCraft('craft_paper2', { +-- name = L.craft_paper2, +-- icon = 'octoteam/icons/paper_stack.png', +-- conts = {'workbench'}, +-- time = 5, +-- ings = { +-- {'craft_paper', 10} +-- }, +-- finish = { +-- {'craft_paper2', 1} +-- }, +-- }) + +octoinv.registerCraft('craft_radio', { + name = L.talkie, + icon = 'octoteam/icons/radio.png', + conts = {'workbench'}, + jobs = {'gun', 'gun2'}, + time = 15, + tools = { + {'tool_screwer', 1}, + {'tool_solder', 1}, + }, + ings = { + {'craft_resistor', 5}, + {'craft_chip2', 1}, + {'craft_bulb', 3}, + {'craft_relay', 5}, + {'craft_glue', 1}, + {'craft_cable', 10}, + {'craft_sheet', 2}, + {'craft_screwnut', 8}, + {'craft_screw2', 8}, + {'craft_transistor', 5}, + }, + finish = { + {'radio', 1} + }, +}) + +octoinv.registerCraft('craft_binoculars', { + name = L.craft_binoculars, + icon = 'octoteam/icons/binoculars.png', + conts = {'workbench'}, + jobs = {'gun', 'gun2'}, + time = 20, + tools = { + {'tool_screwer', 1}, + {'tool_hammer', 1}, + }, + ings = { + {'craft_sheet', 2}, + {'craft_glue', 1}, + {'craft_sight', 4}, + {'craft_spring', 2}, + {'craft_piston', 2}, + }, + finish = { + {'binoculars', 1} + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/_howto.txt b/garrysmod/addons/_config/lua/config/octoinv/items/_howto.txt new file mode 100644 index 0000000..c968b0a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/_howto.txt @@ -0,0 +1,54 @@ +octoinv.registerItem('weapon', { -- [string] item ID + name = 'Оружие', -- [string] print name + class = 'spawned_weapon', -- [string] (class of dropped ent) + model = 'models/weapons/w_pist_glock18.mdl', -- [string] (model of dropped ent) + icon = 'octoteam/icons/gun_pistol.png', -- [string] icon for vgui + mass = 1, -- [float] item mass + volume = 1, -- [float] item volume + nostack = true, -- [bool] (whether this item is not stackable) + nodespawn = true, -- [bool] (whether this item should never despawn when dropped) + pickup = function(ply, ent) -- [function(Player, Entity)] (pickup function, returns table to override data in item) + local class = ent:GetWeaponClass() + local name = ent.itemName or weapons.Get(class).PrintName or weapons.Get(class).Name + return { + name = name, + icon = ent.itemIcon, + model = ent:GetModel(), + ent = { + class = class, + amount = ent:Getamount(), + clip1 = ent.clip1, + clip2 = ent.clip2, + ammo = ent.ammoadd, + } + } + end, + drop = function(ply, item, amount, posData) -- [function(Player, Item, int, table(Vector pos, Angles ang, Vector vel))] + local ent = ents.Create('spawned_weapon') -- (drop function, return false or nil to prevent drop, otherwise created Entity) + ent:SetPos(posData.pos) + ent:SetAngles(posData.ang) + ent:SetModel(item:GetData('model')) + + local data = item:GetData('ent') + ent:Setamount(data.amount) + ent:SetWeaponClass(data.class) + if data.clip1 then ent.clip1 = data.clip1 end + if data.clip2 then ent.clip2 = data.clip2 end + if data.ammoadd then ent.ammoadd = data.ammo end + if item.name then ent.itemName = item.name end + if item.icon then ent.itemIcon = item.icon end + + ent:Spawn() + ent:Activate() + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(posData.vel) + end + + return ent, 1 + end, +}) + +-- more to come diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/blueprints.lua b/garrysmod/addons/_config/lua/config/octoinv/items/blueprints.lua new file mode 100644 index 0000000..a4921bd --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/blueprints.lua @@ -0,0 +1,141 @@ +local descBP = L.descBP +local descBPD = L.descBDP +------------------------------------------------ +-- +-- BLUEPRINTS +-- +------------------------------------------------ + +-- +-- benches +-- + +octoinv.registerItem('bp_stove', { + name = L.stove, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_stove', { + name = L.stove, + icon = 'octoteam/icons/parts_stove.png', + mass = 40, + volume = 35, + nostack = true, + desc = descBPD, +}) + +octoinv.registerItem('bp_fridge', { + name = L.fridge, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_fridge', { + name = L.fridge, + icon = 'octoteam/icons/parts_fridge.png', + mass = 50, + volume = 40, + nostack = true, + desc = descBPD, +}) + +octoinv.registerItem('bp_vend', { + name = L.vend, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + desc = descBP, +}) octoinv.registerItem('bpd_vend', { + name = L.vend, + icon = 'octoteam/icons/parts_vending.png', + mass = 40, + volume = 30, + desc = descBPD, +}) + +octoinv.registerItem('bp_workbench', { + name = L.workbench, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_workbench', { + name = L.workbench, + icon = 'octoteam/icons/parts_workbench.png', + mass = 45, + volume = 50, + nostack = true, + desc = descBPD, +}) + +octoinv.registerItem('bp_machine', { + name = L.machine, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_machine', { + name = L.machine, + icon = 'octoteam/icons/parts_machine.png', + mass = 60, + volume = 45, + nostack = true, + desc = descBPD, +}) + +octoinv.registerItem('bp_refinery', { + name = L.refinery, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_refinery', { + name = L.refinery, + icon = 'octoteam/icons/parts_refinery.png', + mass = 80, + volume = 55, + nostack = true, + desc = descBPD, +}) + +octoinv.registerItem('bp_smelter', { + name = L.smelter, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_smelter', { + name = L.smelter, + icon = 'octoteam/icons/parts_smelter.png', + mass = 60, + volume = 50, + nostack = true, + desc = descBPD, +}) + +-- +-- other +-- + +octoinv.registerItem('bp_printer', { + name = L.printer, + icon = 'octoteam/icons/blueprint.png', + mass = 0.1, + volume = 0.1, + nostack = true, + desc = descBP, +}) octoinv.registerItem('bpd_printer', { + name = L.printer, + icon = 'octoteam/icons/box3.png', + mass = 18, + volume = 15, + nostack = true, + desc = descBPD, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/cars.lua b/garrysmod/addons/_config/lua/config/octoinv/items/cars.lua new file mode 100644 index 0000000..640ead4 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/cars.lua @@ -0,0 +1,491 @@ +------------------------------------------------ +-- +-- CAR STUFF +-- +------------------------------------------------ + +octoinv.registerItem('tool_susp', { + name = L.tool_susp, + icon = 'octoteam/icons/tool_suspension.png', + mass = 2, + volume = 1.5, + desc = L.desc_tool_susp, + nodespawn = true, +}) + +octoinv.registerItem('car_kit', { + name = L.car_kit, + icon = 'octoteam/icons/tool_repair.png', + mass = 5, + volume = 6, + desc = L.desc_car_kit, + nodespawn = true, + use = { + function(ply, item) + if ply:Team() ~= TEAM_MECH then return false, L.this_only_mech end + return L.repair_car, 'octoteam/icons/repair.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' or ent:GetCurHealth() >= ent:GetMaxHealth() then + ply:Notify('warning', L.repair_car_hint) + return 0 + end + + ply:DelayedAction('repair', L.repair_action, { + time = 20, + check = function() return octolib.use.check(ply, ent) and ply:HasItem('car_kit') > 0 end, + succ = function() + ply:TakeItem('car_kit', 1) + local MaxHealth = ent:GetMaxHealth() + local NewHealth = math.min(ent:GetCurHealth() + 450, MaxHealth) + + if NewHealth > (MaxHealth * 0.6) then + ent:SetOnFire( false ) + ent:SetOnSmoke( false ) + end + + if NewHealth > MaxHealth * 0.3 then + ent:SetOnFire( false ) + if NewHealth <= MaxHealth * 0.6 then + ent:SetOnSmoke( true ) + end + end + + ent:SetCurHealth(NewHealth) + net.Start('simfphys_lightsfixall') + net.WriteEntity(ent) + net.Broadcast() + + local effect = ents.Create('env_spark') + effect:SetKeyValue('targetname', 'target') + effect:SetPos(ent:GetPos()) + effect:SetAngles(Angle()) + effect:Spawn() + effect:SetKeyValue('spawnflags','128') + effect:SetKeyValue('Magnitude',2) + effect:SetKeyValue('TrailLength',0.5) + effect:Fire( 'SparkOnce' ) + effect:Fire('kill','',0.5) + + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + ply:EmitSound("weapons/357/357_reload".. math.random(1, 4) ..".wav") + end, + }) + + return 0 + end + end + }, +}) + +octoinv.registerItem('car_wheel', { + name = L.car_wheel, + icon = 'octoteam/icons/wheel_flat.png', + model = 'models/props_vehicles/carparts_tire01a.mdl', + mass = 3, + volume = 3, + nostack = true, + nodespawn = true, + desc = L.desc_car_wheel, + use = { + function(ply, item) + if ply:Team() ~= TEAM_MECH then return false, L.this_only_mech end + return L.repair_wheel, 'octoteam/icons/wheel_flat.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_wheel' or not ent:GetDamaged() then + ply:Notify('warning', L.repair_wheel_hint) + return 0 + end + if ent.deposit then return ply:Notify('warning', 'Арендованные автомобили нельзя изменять') end + + ply:DelayedAction('repair', L.repair_action, { + time = 15, + check = function() return octolib.use.check(ply, ent) and ply:HasItem('car_wheel') > 0 end, + succ = function() + ply:TakeItem('car_wheel', 1) + local effect = ents.Create('env_spark') + effect:SetKeyValue('targetname', 'target') + effect:SetPos(ent:GetPos()) + effect:SetAngles(Angle()) + effect:Spawn() + effect:SetKeyValue('spawnflags','128') + effect:SetKeyValue('Magnitude',1) + effect:SetKeyValue('TrailLength',0.2) + effect:Fire( 'SparkOnce' ) + effect:Fire('kill','',0.08) + + local w = ent.GhostEnt + if IsValid(w) then + local gib = ents.Create 'gmod_sent_vehicle_fphysics_gib' + gib:SetModel(w:GetModel()) + gib:SetPos(w:GetPos() - ply:GetAimVector():GetNormalized()) + gib:SetAngles(w:GetAngles()) + gib:Spawn() + gib:Activate() + gib:GetPhysicsObject():SetMass(20) + gib.DoNotDuplicate = true + gib.NoFire = true + else + local gib = ents.Create 'gmod_sent_vehicle_fphysics_gib' + gib:SetModel('models/props_vehicles/tire001c_car.mdl') + gib:SetPos(ent:GetPos() - ply:GetAimVector():GetNormalized()) + gib:SetAngles(ent:GetAngles()) + gib:Spawn() + gib:Activate() + gib:GetPhysicsObject():SetMass(20) + gib.DoNotDuplicate = true + gib.NoFire = true + end + ent:SetDamaged(false) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + ply:EmitSound("weapons/357/357_reload".. math.random(1, 4) ..".wav") + end, + }) + + return 0 + end + end + }, +}) + +octoinv.registerItem('car_fuel', { + name = 'Канистра', + icon = 'octoteam/icons/gas_can.png', + mass = 5, + volume = 6, + desc = 'Используется для заправки автомобиля', + nodespawn = true, + use = { + function(ply, item) + return 'Заправить машину', 'octoteam/icons/gas_can.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' or ent:GetFuel() >= ent:GetMaxFuel() then + ply:Notify('warning', 'Нужно смотреть на незаправленную машину') + return 0 + end + + ply:DelayedAction('repair', 'Заправка', { + time = 20, + check = function() return octolib.use.check(ply, ent) and ply:HasItem('car_fuel') > 0 end, + succ = function() + ply:TakeItem('car_fuel', 1) + local newFuel = ent:GetFuel() + 10 + ent:SetFuel(newFuel) + ent:SetNetVar('Fuel', newFuel) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + ply:EmitSound("vehicles/jetski/jetski_no_gas_start.wav") + end, + }) + + return 0 + end + end + }, +}) + +octoinv.registerItem('car_paint', { + name = L.car_paint, + icon = 'octoteam/icons/bucket.png', + mass = 3, + volume = 2, + nostack = true, + nodespawn = true, + desc = L.desc_car_paint, + use = { + function(ply, item) + if ply:Team() ~= TEAM_MECH then return false, L.this_only_mech end + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return false, L.see_car end + if ent.police then return false, L.car_not_paint end + if ent.deposit then return ply:Notify('warning', 'Арендованные автомобили нельзя изменять') end + + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return false, why end + + return L.painting_car, 'octoteam/icons/blood.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' or ent.police or ent.deposit then return 0 end + + octolib.request.send(ply, { + { + name = L.car_color, + desc = L.desc_car_color, + type = 'color', + }, + }, function(data) + local col = data[1] or Color(255,255,255) + col.a = 255 + + local playSound = true + ply:DelayedAction('paint', L.paint, { + time = 20, + check = function() return octolib.use.check(ply, ent) and tobool(ply:HasItem(item)) end, + succ = function() + ply:TakeItem(item) + ply:StopSound('d1_town.GasJet') + ent:SetProxyColors({ col, col, CFG.reflectionTint, col }) + ent:RemoveAllDecals() + if ent.atts then + for k, attEnt in pairs(ent.atts) do if not attEnt.noPaint then attEnt:SetColor(col) end end + end + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end, + fail = function() + ply:StopSound('d1_town.GasJet') + end, + }, { + time = 1.5, + inst = true, + action = function() + if playSound then + ply:EmitSound('d1_town.GasJet') + playSound = false + end + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end) + + return 0 + end + end + }, +}) + +octoinv.registerItem('car_part', { + name = L.car_part, + icon = 'octoteam/icons/cogs.png', + mass = 1, + volume = 1, + nostack = true, + nodespawn = true, + desc = L.desc_car_part, + use = { + function(ply, item) + if ply:Team() ~= TEAM_MECH then return false, L.this_only_mech end + if not item:GetData('car') or not item:GetData('bgnum') or not item:GetData('bgval') then return false, L.item_break end + local ent + for i, v in ipairs(ents.FindInSphere(ply:GetShootPos(), 120)) do + if v:GetClass() == 'gmod_sent_vehicle_fphysics_base' then ent = v break end + end + if not IsValid(ent) then return false, L.see_car end + if ent.deposit then return ply:Notify('warning', 'Арендованные автомобили нельзя изменять') end + if ent.VehicleName ~= item:GetData('car') then return false, L.item_does_not_fit end + local bgn, bgv = item:GetData('bgnum'), item:GetData('bgval') + if ent:GetBodygroup(bgn) == bgv then return false, L.detail_already_set end + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return false, why end + + return L.set_detail, 'octoteam/icons/wrench.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return 0 end + if ent.VehicleName ~= item:GetData('car') then return 0 end + local bgn, bgv = item:GetData('bgnum'), item:GetData('bgval') + if ent:GetBodygroup(bgn) == bgv then return 0 end + + ply:DelayedAction('car_mount', L.set_hint, { + time = 20, + check = function() return octolib.use.check(ply, ent) and tobool(ply:HasItem(item)) end, + succ = function() + ply:TakeItem(item) + ent:SetBodygroup(bgn, bgv) + ent:UpdateInventory() + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound("ambient/machines/pneumatic_drill_".. math.random(1, 4) ..".wav") + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + + return 0 + end + end + }, +}) + +octoinv.registerItem('car_rims', { + name = L.car_rims, + icon = 'octoteam/icons/wheel.png', + mass = 1, + volume = 1, + nostack = true, + nodespawn = true, + desc = L.desc_car_rims, + use = { + function(ply, item) + if ply:Team() ~= TEAM_MECH then return false, L.this_only_mech end + local ent + for i, v in ipairs(ents.FindInSphere(ply:GetShootPos(), 120)) do + if v:GetClass() == 'gmod_sent_vehicle_fphysics_base' then ent = v break end + end + if not IsValid(ent) then return false, L.see_car end + if ent.deposit then return false, 'Арендованные автомобили нельзя изменять' end + local mdl = item:GetData('model') + if ent.wModelF == mdl then return false, L.detail_already_set end + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return false, why end + + return L.set_discs, 'octoteam/icons/wrench.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return 0 end + if ent.wModelF == mdl then return 0 end + + ply:DelayedAction('car_mount', L.set_hint, { + time = 20, + check = function() return octolib.use.check(ply, ent) and tobool(ply:HasItem(item)) end, + succ = function() + ply:TakeItem(item) + simfphys.ApplyWheel(ent, ent.camber or 0, mdl) + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('ambient/machines/pneumatic_drill_' .. math.random(1, 4) .. '.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + + return 0 + end + end + }, +}) + +octoinv.registerItem('car_att', { + name = L.car_att, + icon = 'octoteam/icons/cogs.png', + mass = 1, + volume = 1, + nostack = true, + nodespawn = true, + desc = L.desc_car_att, + use = { + function(ply, item) + if item:GetData('att') then + return 'Конвертировать', 'octoteam/icons/sparkler.png', function(ply, item) + local cont = item:GetParent() + item:Remove() + + local attData = simfphys.attachments[item:GetData('att')] + if not attData then return end + + cont:AddItem('car_att', { + name = attData.name, + desc = attData.desc, + icon = attData.icon, + mass = attData.mass, + volume = attData.volume, + colorable = not attData.noPaint or nil, + attmdl = attData.mdl, + model = attData.mdl, + skin = attData.skin, + scale = attData.scale, + }) + + return 0 + end + end + + local veh = ply:GetVehicle() + if not IsValid(veh) or not veh.base or veh.base:GetDriverSeat() ~= veh then + return false, 'Нужно быть на водительском сидении' + end + + return L.set_detail, 'octoteam/icons/wrench.png', function(ply, item) + local car = veh.base + local carRadius = car:GetModelRadius() + + local posDelta = (car:GetForward() + car:GetUp() * 0.8):GetNormalized() + local vPos = car:GetPos() + posDelta * carRadius + local vAng = (-posDelta):Angle() + + octolib.flyEdit(ply, { + parent = car, + props = {{ + model = item:GetData('attmdl'), + skin = item:GetData('skin'), + scale = item:GetData('scale'), + colorable = item:GetData('colorable'), + col = item:GetData('colorable') and car:GetProxyColors()[1]:ToColor() or item:GetData('col') or nil, + name = item:GetData('name'), + icon = item:GetData('icon'), + limits = item:GetData('limits') or { scale = {0.1, 3} }, + }}, + + space = octolib.flyEditor.SPACE_PARENT, + vPos = vPos, + vAng = vAng, + maxDist = carRadius * 1.2, + anchorEnt = car, + noCopy = true, + noRemove = true, + }, function(changed, options) + if not ply:HasItem(item) or not IsValid(car) then return end + + local atts = car:GetNetVar('atts', {}) + for ent, data in pairs(changed) do + if not options.props[ent] or options.props[ent].model ~= data.model then continue end + + -- local pos, ang = WorldToLocal(data.pos, data.ang, car:GetPos(), car:GetAngles()) + data.size.x = math.Clamp(data.size.x, 0.1, 3) + data.size.y = math.Clamp(data.size.y, 0.1, 3) + data.size.z = math.Clamp(data.size.z, 0.1, 3) + + local att = { + model = data.model, + pos = data.pos, + ang = data.ang, + col = data.col, + skin = data.skin, + mat = data.mat, + bgs = data.bgs, + size = data.size, + name = item:GetData('name'), + icon = item:GetData('icon'), + mass = item:GetData('mass'), + volume = item:GetData('volume'), + colorable = item:GetData('colorable'), + } + + octolib.table.strip(att, octolib.entDefaults) + att.col = data.col + att.pos = att.pos or Vector() + att.ang = att.ang or Angle() + atts[#atts + 1] = att + + ply:TakeItem(item) + ply:EmitSound('ambient/machines/pneumatic_drill_' .. math.random(1, 4) .. '.wav') + + break + end + + car:SetNetVar('atts', atts) + timer.Simple(1, function() carDealer.saveVeh(car) end) + end) + + return 0 + end + end + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/collect.lua b/garrysmod/addons/_config/lua/config/octoinv/items/collect.lua new file mode 100644 index 0000000..6cc8585 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/collect.lua @@ -0,0 +1,206 @@ +octoinv.registerItem('collector', { + name = 'Инструмент', + icon = octolib.icons.color('crowbar'), + nostack = true, + mass = 5, + volume = 5, + desc = 'Используется для сбора ресурсов', + model = 'models/weapons/hl2meleepack/w_pickaxe.mdl', + leftField = 'health', + leftMaxField = 'maxhealth', + nodespawn = true, + use = { + function(ply, item) + local collector = octoinv.collectors[item:GetData('collector') or ''] + if not collector then return false, 'Неизвестный тип инструмента' end + + return L.take_in_hand, octolib.icons.color('crowbar'), function(ply, item) + local wep = ents.Create('octoinv_collector') + if not wep:IsValid() then return 0 end + if not wep:IsWeapon() then wep:Remove() return 0 end + if not hook.Call('PlayerCanPickupWeapon', GAMEMODE, ply, wep) then return 0 end + wep:Remove() + + local wep = ply:Give('octoinv_collector') + wep:SetShouldPlayPickupSound(false) + wep.itemData = item:Export() + wep.itemCont = item:GetParent().id + wep:SetCollectorID(item:GetData('collector')) + wep.health = item:GetData('health') or collector.health + + if not wep.itemData.name or not wep.itemData.maxhealth then + wep.itemData.name = collector.name + wep.itemData.icon = collector.icon + wep.itemData.maxhealth = collector.health + end + + timer.Simple(0, function() + ply:SelectWeapon('octoinv_collector') + end) + + return 1 + end + end, + }, +}) + +-- +-- RESOURCES +-- + +octoinv.registerItem('ore_iron', { + name = L.ore_iron, + icon = 'octoteam/icons/ore_iron.png', + model = 'models/un/ore_un_un.mdl', + modelColor = Color(90,90,90), + mass = 3, + volume = 2, + desc = L.descOre, +}) + +octoinv.registerItem('ore_steel', { + name = L.ore_steel, + icon = 'octoteam/icons/ore_steel.png', + model = 'models/un/ore_un_un.mdl', + modelColor = Color(185,185,185), + mass = 3, + volume = 2, + desc = L.descOre, +}) + +octoinv.registerItem('ore_silver', { + name = L.ore_silver, + icon = 'octoteam/icons/ore_silver.png', + model = 'models/un/ore_un_un.mdl', + modelColor = Color(255,255,255), + mass = 3, + volume = 2, + desc = L.descOre, +}) + +octoinv.registerItem('ore_bronze', { + name = L.ore_bronze, + icon = 'octoteam/icons/ore_bronze.png', + model = 'models/un/ore_un_un.mdl', + modelColor = Color(214,157,0), + mass = 3, + volume = 2, + desc = L.descOre, +}) + +octoinv.registerItem('ore_gold', { + name = L.ore_gold, + icon = 'octoteam/icons/ore_gold.png', + model = 'models/un/ore_un_un.mdl', + modelColor = Color(255,207,77), + mass = 3, + volume = 2, + desc = L.descOre, +}) + +octoinv.registerItem('ore_copper', { + name = L.ore_copper, + icon = 'octoteam/icons/ore_copper.png', + model = 'models/un/ore_un_un.mdl', + modelColor = Color(79,255,77), + mass = 3, + volume = 2, + desc = L.descOre, +}) + +octoinv.registerItem('stone', { + name = L.stone, + icon = 'octoteam/icons/rock.png', + model = 'models/un/ore_un_un.mdl', + modelMaterial = 'models/props/CS_militia/militiarock', + mass = 5, + volume = 3, + desc = 'Остатки горной породы, можно попробовать раздробить в очистителе', +}) + +octoinv.registerItem('rubble', { + name = L.rubble, + icon = 'octoteam/icons/rubble.png', + model = 'models/props_marines/sandbag_static.mdl', + mass = 5, + volume = 2.5, + desc = 'Дробленые куски горной породы среднего размера', +}) + +octoinv.registerItem('sand', { + name = L.sand, + icon = 'octoteam/icons/sand.png', + model = 'models/props_marines/sandbag_static.mdl', + mass = 5, + volume = 2, + desc = 'Очень мелкие частицы горной породы', +}) + +octoinv.registerItem('sulfur', { + name = L.sulfur, + icon = 'octoteam/icons/sulfur.png', + model = 'models/un/ore_un_un.mdl', + modelMaterial = 'models/props/cs_assault/pylon', + mass = 0.8, + volume = 0.5, + desc = 'Грубые кристаллы серы', +}) + +octoinv.registerItem('ingot_iron', { + name = L.ingot_iron, + icon = 'octoteam/icons/ingot_iron.png', + mass = 1, + volume = 0.5, + desc = L.descIngot, +}) + +octoinv.registerItem('ingot_steel', { + name = L.ingot_steel, + icon = 'octoteam/icons/ingot_steel.png', + mass = 1, + volume = 0.5, + desc = L.descIngot, +}) + +octoinv.registerItem('ingot_silver', { + name = L.ingot_silver, + icon = 'octoteam/icons/ingot_silver.png', + mass = 1, + volume = 0.5, + desc = L.descIngot, +}) + +octoinv.registerItem('ingot_bronze', { + name = L.ingot_bronze, + icon = 'octoteam/icons/ingot_bronze.png', + mass = 1, + volume = 0.5, + desc = L.descIngot, +}) + +octoinv.registerItem('ingot_gold', { + name = L.ingot_gold, + icon = 'octoteam/icons/ingot_gold.png', + mass = 1, + volume = 0.5, + desc = L.descIngot, +}) + +octoinv.registerItem('ingot_copper', { + name = L.ingot_copper, + icon = 'octoteam/icons/ingot_copper.png', + mass = 1, + volume = 0.5, + desc = L.descIngot, +}) + +octoinv.registerItem('craft_coal', { + name = L.craft_coal, + icon = 'octoteam/icons/coal.png', + model = 'models/un/ore_un_un.mdl', + modelMaterial = 'models/gibs/metalgibs/metal_gibs', + mass = 3, + volume = 3, + randomWeight = 0.25, + desc = L.descFuel, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/cook.lua b/garrysmod/addons/_config/lua/config/octoinv/items/cook.lua new file mode 100644 index 0000000..63f7e76 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/cook.lua @@ -0,0 +1,696 @@ +------------------------------------------------ +-- +-- TOOLS +-- +------------------------------------------------ + +octoinv.registerItem('tool_foodcont', { + name = L.tool_foodcont, + icon = 'octoteam/icons/inbox.png', + mass = 0.1, + volume = 0.05, + randomWeight = 0.1, + desc = L.desc_tool_foodcont, +}) + +local descTool = L.descfoodtool + +octoinv.registerItem('tool_pan', { + name = L.tool_pan, + icon = 'octoteam/icons/pan.png', + mass = 0.8, + volume = 1, + randomWeight = 0.5, + desc = descTool, +}) + +octoinv.registerItem('tool_pot', { + name = L.tool_pot, + icon = 'octoteam/icons/pot.png', + mass = 1, + volume = 3, + randomWeight = 0.5, + desc = descTool, +}) + +octoinv.registerItem('tool_scoop', { + name = L.tool_scoop, + icon = 'octoteam/icons/scoop.png', + mass = 0.5, + volume = 0.25, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_shaker', { + name = L.tool_shaker, + icon = 'octoteam/icons/cocktail_shaker.png', + mass = 0.5, + volume = 1, + randomWeight = 0.5, + desc = descTool, +}) + +octoinv.registerItem('tool_oventray', { + name = L.tool_oventray, + icon = 'octoteam/icons/oventray.png', + mass = 1, + volume = 2, + randomWeight = 0.5, + desc = descTool, +}) + +octoinv.registerItem('tool_pastrybag', { + name = L.tool_pastrybag, + icon = 'octoteam/icons/pastry_bag.png', + mass = 0.3, + volume = 0.5, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_teapot', { + name = L.tool_teapot, + icon = 'octoteam/icons/pot_tea.png', + mass = 0.5, + volume = 1, + randomWeight = 0.5, + desc = descTool, +}) + +octoinv.registerItem('tool_coffeepot', { + name = L.tool_coffeepot, + icon = 'octoteam/icons/pot_coffee.png', + mass = 0.5, + volume = 1, + randomWeight = 0.5, + desc = descTool, +}) + +------------------------------------------------ +-- +-- RESOURCES +-- +------------------------------------------------ + +local descIng = L.descing + +octoinv.registerItem('ing_water', { + name = L.ing_water, + icon = 'octoteam/icons/bottle.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_egg', { + name = L.ing_egg, + icon = 'octoteam/icons/food_egg.png', + mass = 0.08, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_oil', { + name = L.ing_oil, + icon = 'octoteam/icons/food_oil.png', + mass = 0.4, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_salt', { + name = L.ing_salt, + icon = 'octoteam/icons/food_salt.png', + mass = 0.05, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_potato', { + name = L.ing_potato, + icon = 'octoteam/icons/food_potato.png', + mass = 0.12, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_cheese', { + name = L.ing_cheese, + icon = 'octoteam/icons/food_cheese.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_flour', { + name = L.ing_flour, + icon = 'octoteam/icons/food_flour.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_onion', { + name = L.ing_onion, + icon = 'octoteam/icons/food_onion.png', + mass = 0.08, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_milk', { + name = L.ing_milk, + icon = 'octoteam/icons/food_milk.png', + mass = 1, + volume = 1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_cream', { + name = L.ing_cream, + icon = 'octoteam/icons/food_milk2.png', + mass = 0.3, + volume = 0.3, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_meat', { + name = L.ing_meat, + icon = 'octoteam/icons/food_meat4.png', + mass = 0.25, + volume = 0.25, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_carrot', { + name = L.ing_carrot, + icon = 'octoteam/icons/food_carrot.png', + mass = 0.12, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_tomato', { + name = L.ing_tomato, + icon = 'octoteam/icons/food_tomato.png', + mass = 0.12, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_sugar', { + name = L.ing_sugar, + icon = 'octoteam/icons/food_sugar.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_mushroom', { + name = L.ing_mushroom, + icon = 'octoteam/icons/food_mushroom.png', + mass = 0.05, + volume = 0.05, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_sausage', { + name = L.ing_sausage, + icon = 'octoteam/icons/food_sausage.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_sausage2', { + name = L.ing_sausage2, + icon = 'octoteam/icons/food_sausage2.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_bread', { + name = L.ing_bread, + icon = 'octoteam/icons/food_bread.png', + mass = 0.15, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_honey', { + name = L.ing_honey, + icon = 'octoteam/icons/food_honey.png', + mass = 0.3, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_spice', { + name = L.ing_spice, + icon = 'octoteam/icons/food_spice.png', + mass = 0.05, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_kvas', { + name = L.ing_kvas, + icon = 'octoteam/icons/food_kvas.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_cucumber', { + name = L.ing_cucumber, + icon = 'octoteam/icons/food_cucumber.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_rice', { + name = L.ing_rice, + icon = 'octoteam/icons/food_rice.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_nut', { + name = L.ing_nut, + icon = 'octoteam/icons/food_nut.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_apple', { + name = L.ing_apple, + icon = 'octoteam/icons/food_apple.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_corn', { + name = L.ing_corn, + icon = 'octoteam/icons/food_corn.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_celery', { + name = L.ing_celery, + icon = 'octoteam/icons/food_celery.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_bacon', { + name = L.ing_bacon, + icon = 'octoteam/icons/food_bacon.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_olive', { + name = L.ing_olive, + icon = 'octoteam/icons/food_olive.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_chili', { + name = L.ing_chili, + icon = 'octoteam/icons/food_chili.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_broccoli', { + name = L.ing_broccoli, + icon = 'octoteam/icons/food_broccoli.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_pineapple', { + name = L.ing_pineapple, + icon = 'octoteam/icons/food_pineapple.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_pumpkin', { + name = L.ing_pumpkin, + icon = 'octoteam/icons/food_pumpkin.png', + mass = 0.8, + volume = 0.8, + randomWeight = 0.1, + desc = descIng, +}) + +-- octoinv.registerItem('ing_fish', { +-- name = L.ing_fish, +-- icon = 'octoteam/icons/food_fish.png', +-- mass = 0.3, +-- volume = 0.3, +-- randomWeight = 0.1, +-- desc = descIng, +-- }) + +octoinv.registerItem('ing_strawberry', { + name = L.ing_strawberry, + icon = 'octoteam/icons/food_strawberry.png', + mass = 0.3, + volume = 0.3, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_watermelon', { + name = L.ing_watermelon, + icon = 'octoteam/icons/food_watermelon.png', + mass = 1.5, + volume = 1.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_melon', { + name = L.ing_melon, + icon = 'octoteam/icons/food_melon.png', + mass = 1.5, + volume = 1.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_soy', { + name = L.ing_soy, + icon = 'octoteam/icons/food_soy.png', + mass = 0.3, + volume = 0.3, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_peas', { + name = L.ing_peas, + icon = 'octoteam/icons/food_peas.png', + mass = 0.3, + volume = 0.3, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_lettuce', { + name = L.ing_lettuce, + icon = 'octoteam/icons/food_lettuce.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_cabbage', { + name = L.ing_cabbage, + icon = 'octoteam/icons/food_cabbage.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_beet', { + name = L.ing_beet, + icon = 'octoteam/icons/food_beet.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_redish', { + name = L.ing_redish, + icon = 'octoteam/icons/food_redish.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_eggplant', { + name = L.ing_eggplant, + icon = 'octoteam/icons/food_eggplant.png', + mass = 0.4, + volume = 0.4, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_prawn', { + name = L.ing_prawn, + icon = 'octoteam/icons/food_prawn.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_avocado', { + name = L.ing_avocado, + icon = 'octoteam/icons/food_avocado.png', + mass = 0.3, + volume = 0.3, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_pita', { + name = L.ing_pita, + icon = 'octoteam/icons/food_pita.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_cocoa', { + name = L.ing_cocoa, + icon = 'octoteam/icons/food_cocoa.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_choco', { + name = L.ing_choco, + icon = 'octoteam/icons/food_choco.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_coffee', { + name = L.ing_coffee, + icon = 'octoteam/icons/food_coffee.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_tea', { + name = L.ing_tea, + icon = 'octoteam/icons/leaves.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_banana', { + name = L.ing_banana, + icon = 'octoteam/icons/food_banana.png', + mass = 0.25, + volume = 0.25, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_orange', { + name = L.ing_orange, + icon = 'octoteam/icons/food_orange.png', + mass = 0.2, + volume = 0.25, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_lemon', { + name = L.ing_lemon, + icon = 'octoteam/icons/food_lemon.png', + mass = 0.15, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_sauce', { + name = L.ing_sauce, + icon = 'octoteam/icons/food_sauce.png', + mass = 0.2, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_pasta', { + name = L.ing_pasta, + icon = 'octoteam/icons/food_pasta.png', + mass = 0.5, + volume = 0.8, + randomWeight = 0.25, + desc = descIng, +}) + +------------------------------------------------ +-- +-- CRAFTED RESOURCES +-- +------------------------------------------------ + +octoinv.registerItem('ing_potato2', { + name = L.ing_potato2, + icon = 'octoteam/icons/food_potato_boiled.png', + mass = 0.12, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_meatball', { + name = L.ing_meatball, + icon = 'octoteam/icons/food_cutlet.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_roastmeat', { + name = L.ing_roastmeat, + icon = 'octoteam/icons/food_meat5.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_potatomash', { + name = L.ing_potatomash, + icon = 'octoteam/icons/food_porrige.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_soup', { + name = L.ing_soup, + icon = 'octoteam/icons/food_bouillon.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_icecream', { + name = L.ing_icecream, + icon = 'octoteam/icons/food_icecream2.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_dough1', { + name = L.dough1, + icon = 'octoteam/icons/food_pita.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_dough2', { + name = L.dough2, + icon = 'octoteam/icons/food_pita.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_dough3', { + name = L.ing_dough3, + icon = 'octoteam/icons/food_pita.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descIng, +}) + +octoinv.registerItem('ing_pizza_base', { + name = L.ing_pizza_base, + icon = 'octoteam/icons/food_pizza_base.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descIng, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/craft.lua b/garrysmod/addons/_config/lua/config/octoinv/items/craft.lua new file mode 100644 index 0000000..e017474 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/craft.lua @@ -0,0 +1,468 @@ +local descTool = L.descTool +local descCraft = L.descCraft +local descFuel = L.descFuel +local descCraftFuel = L.descCraftFuel + +------------------------------------------------ +-- +-- TOOLS +-- +------------------------------------------------ + +octoinv.registerItem('tool_screwer', { + name = L.tool_screwer, + icon = 'octoteam/icons/screwdriver.png', + mass = 0.2, + volume = 0.15, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_hammer', { + name = L.tool_hammer, + icon = 'octoteam/icons/hammer.png', + mass = 0.2, + volume = 0.15, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_wrench', { + name = L.tool_wrench, + icon = 'octoteam/icons/wrench.png', + mass = 0.35, + volume = 0.2, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_solder', { + name = L.tool_solder, + icon = 'octoteam/icons/solder.png', + mass = 0.5, + volume = 0.3, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_ruler', { + name = L.tool_ruler, + icon = 'octoteam/icons/ruler.png', + mass = 0.35, + volume = 0.2, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_caliper', { + name = L.tool_caliper, + icon = 'octoteam/icons/caliper.png', + mass = 0.35, + volume = 0.2, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_knife_stanley', { + name = L.tool_knife_stanley, + icon = 'octoteam/icons/knife_stanley.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_drill', { + name = L.tool_drill, + icon = 'octoteam/icons/drill.png', + mass = 1.5, + volume = 1, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_saw', { + name = L.tool_saw, + icon = 'octoteam/icons/saw.png', + mass = 1.5, + volume = 1, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_pump', { + name = L.tool_pump, + icon = 'octoteam/icons/pump_hand.png', + mass = 0.8, + volume = 0.4, + randomWeight = 0.25, + desc = descTool, +}) + +octoinv.registerItem('tool_craft', { + name = L.tool_craft, + icon = 'octoteam/icons/tool_craft.png', + mass = 1.5, + volume = 1, + randomWeight = 0.25, + desc = descTool, +}) + +------------------------------------------------ +-- +-- BUILD RESOURCES +-- +------------------------------------------------ + +octoinv.registerItem('craft_screw', { + name = L.craft_screw, + icon = 'octoteam/icons/screw.png', + mass = 0.01, + volume = 0.01, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_screw2', { + name = L.craft_screw2, + icon = 'octoteam/icons/screw2.png', + mass = 0.01, + volume = 0.01, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_screwnut', { + name = L.craft_screwnut, + icon = 'octoteam/icons/screwnut.png', + mass = 0.01, + volume = 0.01, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_nail', { + name = L.craft_nail, + icon = 'octoteam/icons/nail.png', + mass = 0.01, + volume = 0.01, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_stick', { + name = L.craft_stick, + icon = 'octoteam/icons/stick.png', + mass = 0.5, + volume = 1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_plank', { + name = L.craft_plank, + icon = 'octoteam/icons/wood.png', + mass = 2, + volume = 3, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_pallet', { + name = L.craft_pallet, + icon = 'octoteam/icons/pallet.png', + mass = 2, + volume = 5, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_sheet', { + name = L.craft_sheet, + icon = 'octoteam/icons/metal_sheet.png', + mass = 1, + volume = 0.5, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_spring', { + name = L.craft_spring, + icon = 'octoteam/icons/metal_spring.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_pulley', { + name = L.craft_pulley, + icon = 'octoteam/icons/pulley.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_cable', { + name = L.craft_cable, + icon = 'octoteam/icons/cable.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_plug', { + name = L.craft_plug, + icon = 'octoteam/icons/plug.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_socket', { + name = L.craft_socket, + icon = 'octoteam/icons/socket.png', + mass = 0.1, + volume = 0.2, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_piston', { + name = L.craft_piston, + icon = 'octoteam/icons/piston.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_engine', { + name = L.craft_engine, + icon = 'octoteam/icons/engine.png', + mass = 1, + volume = 0.5, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_bulb', { + name = L.craft_bulb, + icon = 'octoteam/icons/bulb.png', + mass = 0.1, + volume = 0.3, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_chip1', { + name = L.craft_chip1, + icon = 'octoteam/icons/chip1.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_chip2', { + name = L.craft_chip2, + icon = 'octoteam/icons/chip2.png', + mass = 0.05, + volume = 0.05, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_cnc', { + name = L.craft_cnc, + icon = 'octoteam/icons/machine_cnc.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_relay', { + name = L.craft_relay, + icon = 'octoteam/icons/relay.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_resistor', { + name = L.craft_resistor, + icon = 'octoteam/icons/resistor.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_transistor', { + name = L.craft_transistor, + icon = 'octoteam/icons/transistor.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_solar', { + name = L.craft_solar, + icon = 'octoteam/icons/solar_panel.png', + mass = 1, + volume = 1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_gauge', { + name = L.craft_gauge, + icon = 'octoteam/icons/speed.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_scotch', { + name = L.craft_scotch, + icon = 'octoteam/icons/scotch.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_glue', { + name = L.craft_glue, + icon = 'octoteam/icons/glue.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_ink', { + name = L.craft_ink, + icon = 'octoteam/icons/ink.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_paper', { + name = L.craft_paper, + icon = 'octoteam/icons/paper.png', + mass = 0.005, + volume = 0.01, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('craft_paper2', { + name = L.craft_paper2, + icon = 'octoteam/icons/paper_stack.png', + mass = 0.05, + volume = 0.05, + randomWeight = 0.1, + desc = descCraft, +}) + +octoinv.registerItem('saltpeter', { + name = L.saltpeter, + icon = 'octoteam/icons/saltpeter.png', + model = 'models/props_lab/chemjar01.mdl', + mass = 0.4, + volume = 0.3, + desc = descCraft, +}) + +octoinv.registerItem('gunpowder', { + name = L.gunpowder, + icon = 'octoteam/icons/gunpowder.png', + model = 'models/props_lab/chemjar01.mdl', + mass = 0.4, + volume = 0.3, + desc = descCraft, +}) + +------------------------------------------------ +-- +-- FUEL +-- +------------------------------------------------ + +octoinv.registerItem('craft_fuel', { + name = L.craft_fuel, + icon = 'octoteam/icons/fuel_barrel.png', + mass = 1, + volume = 1, + randomWeight = 0.25, + desc = descFuel, +}) + +octoinv.registerItem('craft_gas', { + name = L.craft_gas, + icon = 'octoteam/icons/fuel_tank.png', + mass = 15, + volume = 15, + randomWeight = 0.25, + desc = descFuel, +}) + +------------------------------------------------ +-- +-- HYBRID RESOURCES +-- +------------------------------------------------ + +octoinv.registerItem('craft_battery', { + name = L.craft_battery, + icon = 'octoteam/icons/battery_car.png', + mass = 3.5, + volume = 2.5, + nostack = true, + randomWeight = 0.5, + desc = descCraftFuel, +}) + +octoinv.registerItem('craft_battery2', { + name = L.craft_battery2, + icon = 'octoteam/icons/battery_charge.png', + mass = 0.15, + volume = 0.1, + nostack = false, + randomWeight = 0.5, + desc = descCraftFuel, +}) + +------------------------------------------------ +-- +-- TRASH +-- +------------------------------------------------ + +octoinv.registerItem('craft_bottle', { + name = L.craft_bottle, + icon = 'octoteam/icons/bottle_empty.png', + mass = 0.2, + volume = 0.5, + randomWeight = 0.25, + desc = descCraft, +}) + +octoinv.registerItem('craft_glass', { + name = L.craft_glass, + icon = 'octoteam/icons/glass.png', + mass = 0.3, + volume = 0.3, + randomWeight = 0.25, + desc = descCraft, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/darkrp.lua b/garrysmod/addons/_config/lua/config/octoinv/items/darkrp.lua new file mode 100644 index 0000000..c7929bd --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/darkrp.lua @@ -0,0 +1,336 @@ +------------------------------------------------ +-- +-- DARKRP STUFF +-- +------------------------------------------------ + +octoinv.registerItem('money', { + name = L.money, + model = 'models/props/cs_assault/money.mdl', + icon = 'octoteam/icons/money_pack.png', + mass = 0.00001, + volume = 0.00001, + randomWeight = 0.001, + desc = L.desc_money, +}) + +local function isDriver(ply) + local seat = ply:GetVehicle() + if not IsValid(seat) or not IsValid(seat:GetParent()) then return false end + return seat:GetParent().GetDriverSeat and seat:GetParent():GetDriverSeat() == seat +end + +local function equipWeapon(text, doSound) + return function(ply, item) + if doSound and ply:KeyDown(IN_WALK) then return end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= 'dbg_hands' then return false, L.take_weapon end + if isDriver(ply) then return false, L.hands_busy_driver end + if not item:GetData('wepclass') then return false, L.unknown_type_weapon end + if ply:HasWeapon(item:GetData('wepclass')) then return false, L.you_already_take_weapon end + return text, item:GetData('icon'), function(ply, item) + local class = item:GetData('wepclass') + if octoinv.gunBlacklist[class] then + ply:Notify('warning', 'Это оружие запрещено использовать на сервере. Стоит рассказать администрации, как ты его заполучил') + return 1 + end + + local wep = ents.Create(class) + + if not wep:IsValid() then return 0 end + if not wep:IsWeapon() then wep:Remove() return 0 end + if not hook.Call("PlayerCanPickupWeapon", GAMEMODE, ply, wep) then return 0 end + + local ammoT = wep:GetPrimaryAmmoType() + local ammo = ply:GetAmmoCount(ammoT) + wep:Remove() + + local wep = ply:Give(class) + wep:SetClip1(item:GetData('clip1') or 0) + wep:SetClip2(item:GetData('clip2') or -1) + wep:SetShouldPlayPickupSound(false) + wep.WorldModel = item:GetData('model') or wep.WorldModel + wep:Initialize() + + ammo = math.Clamp(ammo + (item:GetData('ammoadd') or 0), 0, wep.Primary.ClipSize * 3) + ply:SetAmmo(ammo, ammoT) + + wep.itemData = item:Export() + wep.itemCont = item:GetParent().id + wep.civil = true + + if item:GetData('expire') then + wep.expireId = 'octoinv.expireWep' .. octolib.string.uuid() + timer.Create(wep.expireId, item:GetData('expire') - os.time(), 1, function() + if not IsValid(ply) then return end + local active = ply:GetActiveWeapon() == wep + if IsValid(wep) then + wep:Remove() + ply:Notify('У оружия истек срок годности') + end + if active then ply:ConCommand('lastinv') end + end) + end + + timer.Simple(0, function() + if not doSound then ply.silentEquip = true end + ply:SelectWeapon(class) + ply.silentEquip = nil + end) + + return 1 + end + end +end + +octoinv.registerItem('weapon', { + name = L.weapons, + model = 'models/weapons/w_pist_glock18.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 1, + volume = 1, + nostack = true, + desc = L.desc_weapon, + leftField = 'clip1', + leftMaxField = 'ammoadd', + nodespawn = true, + use = { + equipWeapon(L.take_in_hand, true), + equipWeapon(L.silient_take_in_hand, false), + }, +}) + +octoinv.registerItem('ammo', { + name = L.item_ammo, + model = 'models/Items/BoxSRounds.mdl', + icon = 'octoteam/icons/gun_bullet2.png', + mass = 0.01, + volume = 1, + nostack = true, + nodespawn = true, + randomWeight = 0.25, + desc = L.desc_item_ammo, + use = { + function(ply, item) + local t = item:GetData('ammotype') + if not t then return false, L.unknown_type_ammo end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep.Primary.Ammo ~= t then return false, 'Нужно держать совместимое оружие' end + if wep:Ammo1() >= wep.Primary.ClipSize * 3 then return false, 'Это оружие максимально заряжено' end + + return L.charge, 'octoteam/icons/gun_bullet.png', function(ply, item) + if not IsValid(wep) or wep.Primary.Ammo ~= t then return end + if wep:Ammo1() >= wep.Primary.ClipSize * 3 then return end + + local amount = math.Clamp(item:GetData('ammocount') or 1, 0, wep.Primary.ClipSize * 3 - wep:Ammo1()) + ply:GiveAmmo(amount, t) + ply:EmitSound('items/itempickup.wav', 60) + + return 1 + end + end, + }, +}) + +local function eat(ply, item, part, partText) + if ply:GetNetVar('Energy') == 100 then return false, L.max_hunger_hint end + + local name = item:GetData('drink') and L.drink or L.eat + local icon = item:GetData('drink') and 'octoteam/icons/bottle.png' or 'octoteam/icons/food_meal2.png' + return name .. partText, icon, function(ply, item) + if ply.eating then + ply:Notify('warning', 'Поспешишь - людей насмешишь, как говорится. Не торопись') + return + end + + local addEnergy = math.floor(item:GetData('energy') * part) + ply.eating = true + ply:DelayedAction('eating', 'Употребление: ' .. item:GetData('name'), { + time = addEnergy / 10, + check = function() return ply.inv and tobool(ply:HasItem(item)) and not ply:IsSprinting() end, + succ = function() + local newVal = math.Clamp((ply:GetNetVar('Energy') or 100) + addEnergy, 0, item:GetData('maxenergy')) + ply.eating = nil + if newVal > ply:GetNetVar('Energy') then + ply:SetLocalVar('Energy', newVal) + end + ply:EmitSound('physics/wood/wood_strain' .. math.random(1,2) .. '.wav', 65) + + local mul = 1 - part + if not item:GetData('leftMax') then item:SetData('leftMax', item:GetData('energy')) end + item:SetData('energy', math.floor(item:GetData('energy') * mul)) + item:SetData('mass', math.max(item:GetData('mass') * mul, 0.05)) + item:SetData('volume', math.max(item:GetData('volume') * mul, 0.05)) + + if item:GetData('energy') <= 0 and not item:GetData('trash') then item:Remove() end + end, + fail = function() + ply.eating = nil + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('physics/plastic/plastic_box_strain' .. math.random(1,3) .. '.wav', 60, 150) + ply:DoAnimation(ACT_GMOD_GESTURE_BECON) + end + }) + + return 0 + end +end + +octoinv.registerItem('food', { + name = L.item_food, + model = 'models/props_junk/garbage_takeoutcarton001a.mdl', + icon = 'octoteam/icons/food_meal2.png', + mass = 0.2, + volume = 0.25, + nostack = true, + randomWeight = 0.33, + desc = L.desc_item_food, + energy = 0, + maxenergy = 100, + leftField = 'energy', + leftMaxField = 'energy', + use = { + function(ply, item) if item:GetData('energy') > 0 then return eat(ply, item, 1, ' полностью') end end, + function(ply, item) if item:GetData('energy') >= 10 then return eat(ply, item, 0.5, ' половину') end end, + function(ply, item) if item:GetData('energy') >= 20 then return eat(ply, item, 0.25, ' немного') end end, + }, +}) + +octoinv.registerItem('armor', { + name = L.armor, + model = 'models/combine_vests/bluevest.mdl', + icon = 'octoteam/icons/armor.png', + mass = 3, + volume = 5, + nostack = true, + nodespawn = true, + desc = L.desc_armor2, + use = { + function(ply, item) + local amount = item:GetData('armor') or 12 + if ply:Armor() >= amount then return false, L.you_already_have_armor end + return L.wear, 'octoteam/icons/armor.png', function(ply, item) + ply.armorItem = item:Export() + ply.armorItem.armor = amount + ply:SetArmor(amount) + ply:SetLocalVar('armor', amount) + ply:EmitSound('npc/combine_soldier/gear3.wav', 55) + return 1 + end + end, + }, +}) + +octoinv.registerItem('radio', { + name = L.talkie, + model = 'models/handfield_radio.mdl', + icon = 'octoteam/icons/radio.png', + mass = 1, + volume = 0.8, + nostack = true, + nodespawn = true, + desc = L.desc_talkie, +}) + +octoinv.registerItem('lockpick', { + name = L.gun_lockpick, + model = 'models/props_c17/tools_pliers01a.mdl', + icon = 'octoteam/icons/lockpick.png', + mass = 0.3, + volume = 0.25, + randomWeight = 0.5, + nodespawn = true, + desc = L.desc_lockpick, +}) + +octoinv.registerItem('lockpick_broken', { + name = L.gun_broken_lockpick, + model = 'models/props_c17/tools_pliers01a.mdl', + icon = 'octoteam/icons/lockpick_broken.png', + mass = 0.3, + volume = 0.25, + randomWeight = 0.5, + nodespawn = true, + desc = L.desc_broken_lockpick, +}) + +octoinv.registerItem('throwable', { + name = 'Метательный предмет', + icon = 'octoteam/icons/dynamite.png', + mass = 0.5, + volume = 0.4, + usesLeft = 5, + model = 'models/Items/BoxBuckshot.mdl', + desc = '', + nodespawn = true, + nostack = true, + gc = 'ent_dbg_throwable', + leftField = 'usesLeft', + leftMaxField = 'usesLeft', + use = { + function() + return 'Сильно метнуть', 'octoteam/icons/explosion.png', function(ply, item) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_THROW) + local gc = item:GetData('gc') + if not gc then return end + timer.Simple(0.88, function() + local ent = ents.Create(item:GetData('gc')) + if not IsValid(ent) then return end + ent:Throw(ply, 1) + end) + + if not item:GetData('leftMax') then item:SetData('leftMax', item:GetData('usesLeft')) end + item:SetData('usesLeft', (item:GetData('usesLeft') or 5) - 1) + item:SetData('mass', 0.5 * item:GetData('usesLeft')) + item:SetData('volume', 0.4 * item:GetData('usesLeft')) + return item:GetData('usesLeft') <= 0 and 1 or 0 + end + end, + function() + return 'Слабо метнуть', 'octoteam/icons/explosion.png', function(ply, item) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + local gc = item:GetData('gc') + if not gc then return end + timer.Simple(0.88, function() + local ent = ents.Create(item:GetData('gc')) + if not IsValid(ent) then return end + ent:Throw(ply, 0.5) + end) + + if not item:GetData('leftMax') then item:SetData('leftMax', item:GetData('usesLeft')) end + item:SetData('usesLeft', (item:GetData('usesLeft') or 5) - 1) + item:SetData('mass', 0.5 * item:GetData('usesLeft')) + item:SetData('volume', 0.4 * item:GetData('usesLeft')) + return item:GetData('usesLeft') <= 0 and 1 or 0 + end + end, + function() + return 'Положить', 'octoteam/icons/explosion.png', function(ply, item) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.88, function() + local ent = ents.Create(item:GetData('gc')) + if not IsValid(ent) then return end + ent:Throw(ply, 0.1) + timer.Simple(0.75, function() + if not (IsValid(ent) and IsValid(ent:GetPhysicsObject())) then return end + local phys = ent:GetPhysicsObject() + phys:SetVelocity(0) + phys:Sleep() + end) + end) + + if not item:GetData('leftMax') then item:SetData('leftMax', item:GetData('usesLeft')) end + item:SetData('usesLeft', (item:GetData('usesLeft') or 5) - 1) + item:SetData('mass', 0.5 * item:GetData('usesLeft')) + item:SetData('volume', 0.4 * item:GetData('usesLeft')) + return item:GetData('usesLeft') <= 0 and 1 or 0 + end + end, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/drugs.lua b/garrysmod/addons/_config/lua/config/octoinv/items/drugs.lua new file mode 100644 index 0000000..51eb637 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/drugs.lua @@ -0,0 +1,209 @@ +------------------------------------------------ +-- +-- DRUGS +-- +------------------------------------------------ + +local function useDrugFunction(buffName, buffLength, sound) + return function() + return L.drugs_use, 'octoteam/icons/drug.png', function(ply) + if ply:HasBuff('Overdose') then + ply:Notify('warning', L.overdose_hint) + return 0 + end + if ply.bleeding then + ply:Notify('warning', 'Ты при смерти') + return 0 + end + ply:AddBuff(buffName, buffLength) + ply:EmitSound(sound, 75, 100) + return 1 + end + end +end + +octoinv.registerItem('drug_vitalex', { + name = L.vitalex, + icon = 'octoteam/icons/pills2.png', + mass = 0.2, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/props_lab/jar01b.mdl', + desc = L.description_vitalex, + use = { useDrugFunction('HealthRecovery', 45, 'npc/barnacle/barnacle_gulp2.wav') }, +}) + +octoinv.registerItem('drug_painkiller', { + name = L.painkiller, + icon = 'octoteam/icons/medicine_morphy_pen.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/props_lab/jar01b.mdl', + desc = L.description_painkiller, + use = { + function() + return L.drugs_use, 'octoteam/icons/drug.png', function(ply) + if ply:HasBuff('Overdose') then + ply:Notify('warning', L.overdose_hint) + return 0 + end + + ply:AddBuff('Painkillers', 80) + ply:SetHealth(math.min(ply:Health() + 5, ply:GetMaxHealth())) + ply:EmitSound('npc/barnacle/barnacle_gulp2.wav', 75, 100) + + return 1 + end + end, + }, +}) + +octoinv.registerItem('drug_relaxant', { + name = L.relaxant, + icon = 'octoteam/icons/drug.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/props_lab/jar01b.mdl', + desc = L.description_relaxant, + use = { useDrugFunction('Muscle Relaxant', 80, 'npc/barnacle/barnacle_gulp2.wav') }, +}) + +octoinv.registerItem('drug_vampire', { + name = L.vampire, + icon = 'octoteam/icons/drug.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/props_lab/jar01b.mdl', + desc = L.description_vampire, + use = { useDrugFunction('Vampire', 120, 'npc/barnacle/barnacle_gulp2.wav') }, +}) + +octoinv.registerItem('drug_dextradose', { + name = L.dextradose, + icon = 'octoteam/icons/drug.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/cocn.mdl', + desc = L.description_dextradose, + use = { useDrugFunction('Dextradose', 120, 'player/suit_sprint.wav') }, +}) + +octoinv.registerItem('drug_roids', { + name = L.roids, + icon = 'octoteam/icons/drug.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/cocn.mdl', + desc = L.description_roids, + use = { useDrugFunction('Steroids', 80, 'player/suit_sprint.wav') }, +}) + +octoinv.registerItem('drug_bouncer', { + name = L.cocaine, + icon = 'octoteam/icons/drug.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/cocn.mdl', + desc = L.description_cocaine, + use = { useDrugFunction('DoubleJump', 80, 'player/suit_sprint.wav') }, +}) + +octoinv.registerItem('drug_antitoxin', { + name = L.antitoxin, + icon = 'octoteam/icons/medicine_antidote_pen.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/props_lab/jar01b.mdl', + desc = L.description_antitoxin, + use = { + function(ply, item) + return L.drugs_use, 'octoteam/icons/drug.png', function(ply, item) + if ply:HasBuff('Overdose') then + ply:Notify('warning', L.overdose_hint) + return 0 + end + + ply:ClearBuffs() + ply:MoveModifier('drug', nil) + ply:MoveModifier('drug2', nil) + ply:EmitSound('npc/barnacle/barnacle_gulp2.wav', 75, 100) + + return 1 + end + end, + }, +}) + +octoinv.registerItem('drug_weed', { + name = L.marijuana, + icon = 'octoteam/icons/drugs_marijuana.png', + mass = 0.2, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/katharsmodels/contraband/zak_wiet/zak_wiet.mdl', + desc = L.description_marijuana, + use = { useDrugFunction('Weed', 35, 'player/suit_sprint.wav') }, +}) + +octoinv.registerItem('drug_pingaz', { + name = L.pingaz, + icon = 'octoteam/icons/drugs_methamphetamine.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/katharsmodels/contraband/metasync/blue_sky.mdl', + desc = L.description_pingaz, + use = { useDrugFunction('Pingaz', 120, 'player/suit_sprint.wav') }, +}) + +octoinv.registerItem('drug_preserver', { + name = L.preserver, + icon = 'octoteam/icons/medicine_adrenaline_pen.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/katharsmodels/syringe_out/syringe_out.mdl', + desc = L.description_preserver, + use = { useDrugFunction('Preserver', 120, 'ambient/levels/canals/toxic_slime_gurgle8.wav') }, +}) + +octoinv.registerItem('drug_meth', { + name = L.steroids, + icon = 'octoteam/icons/drug.png', + mass = 0.1, + volume = 0.2, + nostack = true, + nodespawn = true, + model = 'models/katharsmodels/syringe_out/syringe_out.mdl', + desc = L.description_steroids, + use = { + function(ply, item) + do return false, L.temporary_disable end -- TEMP + if ply:HasBuff('Overdose') then return false, L.overdose_hint end + return L.drugs_use, 'octoteam/icons/drug.png', function(ply, item) + ply:AddBuff('Meth', 120) + ply:EmitSound('ambient/levels/canals/toxic_slime_gurgle8.wav', 75, 100) + + return 1 + end + end, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/gun.lua b/garrysmod/addons/_config/lua/config/octoinv/items/gun.lua new file mode 100644 index 0000000..2bd331f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/gun.lua @@ -0,0 +1,525 @@ +------------------------------------------------ +-- +-- GUNS THAT CANNOT BE CONVERTED TO ITEMS +-- +------------------------------------------------ + +octoinv.gunBlacklist = octolib.array.toKeys{ + 'weapon_357','weapon_pistol','weapon_bugbait','weapon_crossbow','weapon_crowbar','weapon_frag', + 'weapon_physcannon','weapon_ar2','weapon_rpg','weapon_slam','weapon_shotgun','weapon_smg1','weapon_stunstick', + 'manhack_welder','weapon_medkit','weapon_simrepair','weapon_simremote','med_kit','stunstick','dbg_dog', + 'weapon_cuff_police','dbg_shield','dbg_punisher','weapon_keypadchecker','lightning_gun','sf2_tool', 'weapon_flashlight_uv', 'weapon_fire_hose' +} + + +local descCraft = L.desc_gun_Craft +local descBP = L.desc_gun_BP + +------------------------------------------------ +-- +-- GUNS MATERIALS +-- +------------------------------------------------ + +octoinv.registerItem('craft_shutter', { + name = L.gun_ing_shutter, + icon = 'octoteam/icons/gun_part_shutter.png', + mass = 0.2, + volume = 0.2, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_barrel', { + name = L.gun_ing_barrel, + icon = 'octoteam/icons/gun_part_barrel.png', + mass = 0.25, + volume = 0.25, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_grip', { + name = L.gun_ing_grip, + icon = 'octoteam/icons/gun_part_handle.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_sight', { + name = L.crosshair, + icon = 'octoteam/icons/gun_part_sight.png', + mass = 0.15, + volume = 0.15, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_aim', { + name = L.gun_ing_sight, + icon = 'octoteam/icons/gun_part_aim.png', + mass = 0.05, + volume = 0.05, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_drummer', { + name = L.gun_ing_aim, + icon = 'octoteam/icons/gun_part_drummer.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_trigger', { + name = L.gun_ing_drummer, + icon = 'octoteam/icons/gun_part_trigger.png', + mass = 0.03, + volume = 0.03, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_stopper', { + name = L.gun_ing_trigger, + icon = 'octoteam/icons/gun_part_stopper.png', + mass = 0.08, + volume = 0.08, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_clip', { + name = L.shop, + icon = 'octoteam/icons/gun_riflemag.png', + mass = 0.4, + volume = 0.3, + randomWeight = 0.5, + desc = descCraft, +}) + +octoinv.registerItem('craft_silencer', { + name = L.silencer, + icon = 'octoteam/icons/gun_part_silencer.png', + mass = 0.5, + volume = 0.5, + randomWeight = 0.5, + desc = descCraft, +}) + +------------------------------------------------ +-- +-- GUNS BLUEPRINTS +-- +------------------------------------------------ + +octoinv.registerItem('bp_shutter', { + name = L.gun_ing_shutter, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_barrel', { + name = L.gun_ing_barrel, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_grip', { + name = L.gun_ing_grip, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +-- not use in prod +-- octoinv.registerItem('bp_sight', { +-- name = 'Прицел', +-- icon = 'octoteam/icons/microsd.png', +-- mass = 0.02, +-- volume = 0.02, +-- randomWeight = 0.1, +-- desc = descBP, +-- }) + +octoinv.registerItem('bp_aim', { + name = L.gun_ing_sight, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_drummer', { + name = L.gun_ing_aim, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_trigger', { + name = L.gun_ing_drummer, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_stopper', { + name = L.gun_ing_trigger, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_clip', { + name = L.shop, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +------------------------------------------------ +-- +-- OTHER BLUEPRINTS +-- +------------------------------------------------ + +octoinv.registerItem('bp_screw', { + name = L.craft_screw, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_screw2', { + name = L.craft_screw2, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_screwnut', { + name = L.craft_screwnut, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_nail', { + name = L.craft_nail, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_spring', { + name = L.craft_spring, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_pulley', { + name = L.craft_pulley, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_piston', { + name = L.craft_piston, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_bulb', { + name = L.craft_bulb, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +octoinv.registerItem('bp_gauge', { + name = L.craft_gauge, + icon = 'octoteam/icons/microsd.png', + mass = 0.02, + volume = 0.02, + randomWeight = 0.1, + desc = descBP, +}) + +------------------------------------------------ +-- +-- GUN ITEM DATA BY GUN CLASSES +-- +------------------------------------------------ +octoinv.gunsItemData = { + weapon_octo_knife = { + name = L.knife, + model = 'models/weapons/w_knife_t.mdl', + icon = 'octoteam/icons/knife.png', + mass = 0.8, + volume = 0.5, + }, + weapon_octo_axe = { + name = L.axe, + model = 'models/weapons/HL2meleepack/w_axe.mdl', + icon = 'octoteam/icons/gun_axe.png', + mass = 2, + volume = 1.5, + }, + weapon_octo_shovel = { + name = L.shovel, + model = 'models/weapons/HL2meleepack/w_shovel.mdl', + icon = 'octoteam/icons/shovel.png', + mass = 2, + volume = 1.5, + }, + weapon_octo_hook = { + name = L.hook, + model = 'models/weapons/HL2meleepack/w_hook.mdl', + icon = 'octoteam/icons/crowbar.png', + mass = 3, + volume = 2, + }, + keypad_cracker = { + name = L.keypad_cracker, + model = 'models/weapons/w_c4.mdl', + icon = 'octoteam/icons/keypad_cracker.png', + mass = 2, + volume = 1.5, + }, + weapon_octo_glock = { + name = 'Glock', + model = 'models/weapons/w_pist_glock18.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 1.5, + volume = 1.5, + }, + weapon_octo_usp = { + name = 'USP', + model = 'models/weapons/w_pist_usp.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 2.5, + volume = 2.5, + }, + weapon_octo_usps = { + name = 'USP-S', + model = 'models/weapons/w_pist_usp_silencer.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 3, + volume = 3, + }, + weapon_octo_p228 = { + name = 'P228', + model = 'models/weapons/w_pist_p228.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 2, + volume = 2, + }, + weapon_octo_fiveseven = { + name = 'FiveseveN', + model = 'models/weapons/w_pist_fiveseven.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 2, + volume = 2, + }, + weapon_octo_deagle = { + name = 'Desert Eagle', + model = 'models/weapons/w_pist_deagle.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 4, + volume = 3, + }, + weapon_octo_357 = { + name = 'Colt .357', + model = 'models/weapons/w_357.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 3.5, + volume = 3, + }, + weapon_octo_dualelites = { + name = 'Dual Elites', + model = 'models/weapons/w_pist_elite.mdl', + icon = 'octoteam/icons/gun_pistol.png', + mass = 4, + volume = 4, + }, + weapon_octo_mp5 = { + name = 'MP5', + model = 'models/weapons/w_smg_mp5.mdl', + icon = 'octoteam/icons/gun_smg.png', + mass = 4.5, + volume = 4.5, + }, + weapon_octo_ump45 = { + name = 'UMP45', + model = 'models/weapons/w_smg_ump45.mdl', + icon = 'octoteam/icons/gun_smg.png', + mass = 4.3, + volume = 4.3, + }, + weapon_octo_mac10 = { + name = 'MAC10', + model = 'models/weapons/w_smg_mac10.mdl', + icon = 'octoteam/icons/gun_smg.png', + mass = 3, + volume = 3, + }, + weapon_octo_tmp = { + name = 'TMP', + model = 'models/weapons/w_smg_tmp.mdl', + icon = 'octoteam/icons/gun_smg.png', + mass = 4.8, + volume = 4.8, + }, + weapon_octo_m3 = { + name = 'M3', + model = 'models/weapons/w_shot_m3super90.mdl', + icon = 'octoteam/icons/gun_shotgun.png', + mass = 6.5, + volume = 6.5, + }, + weapon_octo_p90 = { + name = 'P90', + model = 'models/weapons/w_smg_p90.mdl', + icon = 'octoteam/icons/gun_smg.png', + mass = 5, + volume = 5, + }, + weapon_octo_galil = { + name = 'Galil', + model = 'models/weapons/w_rif_galil.mdl', + icon = 'octoteam/icons/gun_rifle.png', + mass = 6.45, + volume = 6.45, + }, + weapon_octo_scout = { + name = 'Scout', + model = 'models/weapons/w_snip_scout.mdl', + icon = 'octoteam/icons/gun_sniper.png', + mass = 6.3, + volume = 6.3, + }, + weapon_octo_famas = { + name = 'FAMAS', + model = 'models/weapons/w_rif_famas.mdl', + icon = 'octoteam/icons/gun_rifle.png', + mass = 6.6, + volume = 6.6, + }, + weapon_octo_xm1014 = { + name = 'XM1014', + model = 'models/weapons/w_shot_xm1014.mdl', + icon = 'octoteam/icons/gun_shotgun.png', + mass = 6.4, + volume = 6.4, + }, + weapon_octo_ak = { + name = 'AK', + model = 'models/weapons/w_rif_ak47.mdl', + icon = 'octoteam/icons/gun_rifle.png', + mass = 6.4, + volume = 6.4, + }, + weapon_octo_m4a1 = { + name = 'M4A1', + model = 'models/weapons/w_rif_m4a1.mdl', + icon = 'octoteam/icons/gun_rifle.png', + mass = 6.7, + volume = 6.7, + }, + weapon_octo_aug = { + name = 'AUG', + model = 'models/weapons/w_rif_aug.mdl', + icon = 'octoteam/icons/gun_sniper.png', + mass = 7.6, + volume = 7.6, + }, + weapon_octo_sg552 = { + name = 'SG552', + model = 'models/weapons/w_rif_sg552.mdl', + icon = 'octoteam/icons/gun_sniper.png', + mass = 9.4, + volume = 9.4, + }, + weapon_octo_awp = { + name = 'AWP', + model = 'models/weapons/w_snip_awp.mdl', + icon = 'octoteam/icons/gun_sniper.png', + mass = 9.1, + volume = 9.1, + }, + weapon_octo_g3sg1 = { + name = 'G3SG1', + model = 'models/weapons/w_snip_g3sg1.mdl', + icon = 'octoteam/icons/gun_sniper.png', + mass = 8.7, + volume = 8.7, + }, + weapon_octo_sg550 = { + name = 'SG550', + model = 'models/weapons/w_snip_sg550.mdl', + icon = 'octoteam/icons/gun_sniper.png', + mass = 8.6, + volume = 8.6, + }, + weapon_octo_m249 = { + name = 'M249', + model = 'models/weapons/w_mach_m249para.mdl', + icon = 'octoteam/icons/gun_rifle.png', + mass = 15, + volume = 12.3, + }, +} + +-- some data was snipped to shorten code, let's fill it +for k,v in pairs(octoinv.gunsItemData) do + v.wepclass = v.wepclass or k + v.ammoadd = v.ammoadd or 0 + v.clip1 = v.clip1 or 0 + v.clip2 = v.clip2 or 0 + + -- local swep = weapons.GetStored(v.wepclass) + -- if swep then + -- v.leftMax = swep.Primary.ClipSize + -- end +end diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/halloween.lua b/garrysmod/addons/_config/lua/config/octoinv/items/halloween.lua new file mode 100644 index 0000000..72aef75 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/halloween.lua @@ -0,0 +1,90 @@ +local tastes = { + {'арбуз', 'банан', 'виноград', 'вишня', 'груша', 'ежевика', 'зеленое яблоко', 'ириска', 'клубника', 'мармелад', 'мед', 'Тутти-фрутти', 'шоколад'}, + {'баклажан', 'бекон', 'брокколи', 'грейпфрут', 'гренка', 'гриб', 'жареные бобы', 'печенка', 'пицца', 'помидор', 'попкорн', 'рыба', 'картофельное пюре', 'кетчуп', 'кофе', 'сосиска', 'тост', 'тыква'}, + {'газонная трава', 'грязный носок', 'грязь', 'дождевой червь', 'перепревшая капуста', 'рвота', 'сопли', 'лук', 'мыло', 'тухлое яйцо', 'ушная сера', 'хрен'}, +} +local reactions = { + {'Вкуснятина!', 'Аж тает во рту!', 'Ням-ням!', 'М-м-м-м-м!', 'Повезло!'}, + {'Ну, сойдет', 'Эм, ну ладно', 'Ну-у-у-у...', 'Ну, хоть так', 'Могло быть и хуже'}, + {'Фу, гадость какая!', 'О боже...', 'Тьфу, нужно чем-то запить!', 'Черт, как это можно есть?!', 'Фу-у-у-у-у...'}, +} +local msg = 'На вкус как... %s. %s' +octoinv.registerItem('h20_sweets', { + name = 'Загадочная конфета', + icon = octolib.icons.color('food_doughnut'), + desc = 'Конфета со случайным вкусом. С каким? Не узнаешь, пока не попробуешь!\n\nВосстанавливает случайное количество сытости, в том числе отрицательное', + mass = 0.01, + volume = 0.01, + use = { + function() + return 'Съесть', octolib.icons.color('food_meal2'), function(ply, item) + if ply.eating then + ply:Notify('warning', 'Поспешишь - людей насмешишь, как говорится. Не торопись') + return + end + + ply.eating = true + ply:DelayedAction('eating', 'Употребление: ' .. item:GetData('name'), { + time = 2, + check = function() return ply.inv and tobool(ply:HasItem(item)) and not ply:IsSprinting() end, + succ = function() + ply.eating = nil + + local add = math.random(-30, 50) + if add >= 25 then + ply:Notify('hint', msg:format(tastes[1][math.random(#tastes[1])], reactions[1][math.random(#reactions[1])])) + elseif add >= 0 then + ply:Notify('rp', msg:format(tastes[2][math.random(#tastes[2])], reactions[2][math.random(#reactions[2])])) + else + ply:Notify('warning', msg:format(tastes[3][math.random(#tastes[3])], reactions[3][math.random(#reactions[3])])) + end + local newVal = math.Clamp(ply:GetNetVar('Energy', 100) + add, 0, 100) + ply:SetHunger(newVal) + + item:SetData('amount', item:GetData('amount') - 1) + if item:GetData('amount') <= 0 then item:Remove() end + end, + fail = function() + ply.eating = nil + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('physics/plastic/plastic_box_strain' .. math.random(1,3) .. '.wav', 60, 150) + ply:DoAnimation(ACT_GMOD_GESTURE_BECON) + end + }) + end + end, + } +}) + +-- octoinv.registerItem('h20_case', { +-- name = 'Загадочная коробочка', +-- icon = octolib.icons.color('gift_question'), +-- desc = 'Так и хочется ее открыть!', +-- mass = 1, +-- volume = 1, +-- nostack = true, +-- use = { +-- function(_, item) +-- if not item.cid then return end +-- local case = halloween.cases[item.cid] +-- if not case or not case.items then return end +-- return 'Открыть', octolib.icons.color('box3_open'), function(ply, item) +-- local id = octolib.array.randomWeighted(case.items) +-- local caseItem = halloween.caseItems[id] +-- if not caseItem or not caseItem.give then +-- ErrorNoHalt(id .. ' was not registered correctly') +-- end +-- timer.Simple(0, function() caseItem.give(ply, item:GetParent()) end) +-- ply:Notify(('Ты открываешь загадочную коробочку, а в ней %s!'):format(caseItem.name)) +-- octologs.createLog() +-- :Add(octologs.ply(ply), ' got ', octologs.string(caseItem.name), ' from Halloween case') +-- :Save() +-- return 1 +-- end +-- end, +-- } +-- }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/loot.lua b/garrysmod/addons/_config/lua/config/octoinv/items/loot.lua new file mode 100644 index 0000000..c847340 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/loot.lua @@ -0,0 +1,110 @@ +local doll = { + icon = 'octoteam/icons/bust.png', + mass = 0.15, + volume = 0.05, + model = 'models/props_lab/huladoll.mdl', +} + +local trash = { + -- {chance, {class, amount or data}} + {500, {'craft_screw2', 5}}, + {500, {'craft_paper', 8}}, + {500, {'craft_bottle', 1}}, + {500, {'craft_screwnut', 3}}, + {500, {'craft_nail', 4}}, + {500, {'craft_plank', 1}}, + {500, {'craft_spring', 3}}, + {500, {'craft_bulb', 2}}, + {500, {'tool_hammer', 1}}, + {500, {'tool_wrench', 1}}, + {250, {'tool_foodcont', 3}}, + {250, {'money', 50}}, + {200, {'craft_chip2', 1}}, + {200, {'money', 100}}, + {160, {'craft_battery', 1}}, + {160, {'craft_cnc', 1}}, + {160, {'food', { + name = L.trash_food_wrap, + energy = 25, + maxenergy = 75, + }}}, + {160, {'food', { + name = L.trash_food_donut, + energy = 20, + maxenergy = 75, + }}}, + {160, {'food', { + name = L.trash_food_sandwich, + energy = 15, + maxenergy = 75, + }}}, + {160, {'food', { + name = L.trash_food_pizza, + energy = 20, + maxenergy = 75, + }}}, + {160, {'food', { + name = L.trash_food_pie, + energy = 15, + maxenergy = 75, + }}}, + + {135, {'tool_pastrybag', 1}}, + {105, {'tool_scoop', 1}}, + {100, {'tool_coffeepot', 1}}, + {100, {'drug_booze', 1}}, + {100, {'ore_iron', 1}}, + {100, {'tool_craft', 1}}, + {75, {'money', 500}}, + {75, {'craft_ink', 1}}, + {70, {'tool_teapot', 1}}, + {60, {'tool_pan', 1}}, + {50, {'ent_dbg_cigarette', 1}}, + {50, {'ore_steel', 1}}, + {50, {'drug_booze2', 1}}, + {50, {'money', 1000}}, + {45, {'tool_pot', 1}}, + {45, {'tool_shaker', 1}}, + {40, {'tool_oventray', 1}}, + {35, {'bpd_fridge', 1}}, + {30, {'bpd_workbench', 1}}, + {23, {'bpd_machine', 1}}, + {23, {'bpd_stove', 1}}, + {20, {'bpd_refinery', 1}}, + {20, {'radio', 1}}, + {16, {'car_kit', 1}}, + {5, {'money', 10000}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_chelog})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_quillin})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_artis})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_nayzer})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_youlas})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_glitch})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_google})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_queeboy})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_benri})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_direded})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_bellash})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_drinda})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_arthas})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_wayzer})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_hellcom})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_hoffman})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = L.statuette_sighty})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = 'Фигурка Ванички'})}}, + {2, {'souvenir', table.Merge(table.Copy(doll), {name = 'Фигурка Кило'})}}, + {1, {'car_att', { + name = L.statuette_ermak, + icon = 'octoteam/icons/bust.png', + att = 'skull', + model = 'models/Gibs/HGIBS.mdl', + desc = L.desc_statuette_ermak, + }}}, +} + +for _,v in ipairs(trash) do + octoinv.registerLoot(v[1], { + mode = 'trash', + item = v[2], + }) +end diff --git a/garrysmod/addons/_config/lua/config/octoinv/items/other.lua b/garrysmod/addons/_config/lua/config/octoinv/items/other.lua new file mode 100644 index 0000000..1cc000b --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/items/other.lua @@ -0,0 +1,957 @@ +------------------------------------------------ +-- +-- TOOLS +-- +------------------------------------------------ + +local descTemp = L.descTemp + +octoinv.registerItem('tool_pen', { + name = L.tool_pen, + icon = 'octoteam/icons/pen.png', + mass = 0.1, + volume = 0.1, + randomWeight = 0.25, + desc = descTemp, +}) + +octoinv.registerItem('tool_pencil', { + name = L.tool_pencil, + icon = 'octoteam/icons/pencil.png', + mass = 0.05, + volume = 0.05, + randomWeight = 0.25, + desc = descTemp, +}) + +octoinv.registerItem('souvenir', { + name = L.tool_souvenir, + icon = 'octoteam/icons/coin.png', + mass = 0.1, + volume = 0.05, + nostack = true, + model = 'models/props_phx/misc/egg.mdl', +}) + + +------------------------------------------------ +-- +-- FOOD / DRUGS / SIMILAR +-- +------------------------------------------------ + +local function drink(_, _, part, partText) + return L.drink .. partText, 'octoteam/icons/bottle.png', function(ply, item) + if ply:HasBuff('Overdose') then + ply:Notify('warning', L.overdose_hint) + return 0 + end + + local drunkTime = item:GetData('drunkTime') * item:GetData('part') * part + if drunkTime >= 180 then + ply:SetLocalVar('frost', math.max(ply:GetNetVar('frost', 0) - 50, 0)) + end + + ply:AddBuff('Drunk', drunkTime) + ply:EmitSound('npc/barnacle/barnacle_gulp2.wav', 75, 100) + + item:SetData('part', item:GetData('part') * (1 - part)) + item:SetData('desc', ('%s\nОсталось примерно %s%%'):format( + item:GetData('desc'):gsub('\nОсталось примерно.+', ''), + math.floor(item:GetData('part') * 100) + )) + if item:GetData('part') <= 0 then item:Remove() end + + return 0 + end +end + +octoinv.registerItem('drug_booze', { + name = L.drugbooze, + icon = 'octoteam/icons/food_beer.png', + mass = 1.5, + volume = 1.5, + nostack = true, + model = 'models/props_junk/garbage_glassbottle002a.mdl', + desc = L.description_drugbooze, + part = 1, + drunkTime = 120, + use = { + function(ply, item) return drink(ply, item, 1, ' полностью') end, + function(ply, item) + if item:GetData('drunkTime') * item:GetData('part') >= 30 then return drink(ply, item, 0.5, ' половину') end + end, + function(ply, item) + if item:GetData('drunkTime') * item:GetData('part') >= 60 then return drink(ply, item, 0.25, ' немного') end + end, + }, +}) + +octoinv.registerItem('drug_booze2', { + name = L.drugbooze2, + icon = 'octoteam/icons/bottle_vodka.png', + mass = 1.5, + volume = 1, + nostack = true, + model = 'models/props_junk/glassbottle01a.mdl', + desc = L.description_drugbooze2, + part = 1, + drunkTime = 240, + use = { + function(ply, item) return drink(ply, item, 1, ' полностью') end, + function(ply, item) + if item:GetData('drunkTime') * item:GetData('part') >= 30 then return drink(ply, item, 0.5, ' половину') end + end, + function(ply, item) + if item:GetData('drunkTime') * item:GetData('part') >= 60 then return drink(ply, item, 0.25, ' немного') end + end, + }, +}) + +octoinv.registerItem('ent_dbg_cigarette', { + name = L.cigarettes, + icon = 'octoteam/icons/cigarette.png', + mass = 0.1, + volume = 0.2, + nostack = true, + model = 'models/boxopencigshib.mdl', + desc = L.desc_cigarette, + cigsLeft = 20, + leftField = 'cigsLeft', + leftMaxField = 'cigsLeft', + use = { + function(ply, item) + if ply:HasWeapon('dbg_cigarette') then return false, L.have_cigarette end + return L.use_cigarette, 'octoteam/icons/cigarette.png', function(ply, item) + if ply.bleeding then + ply:Notify('warning', 'Ты при смерти') + return 0 + end + local left = (item:GetData('cigsLeft') or 20) - 1 + ply:Give('dbg_cigarette') + + if not item:GetData('leftMax') then item:SetData('leftMax', item:GetData('cigsLeft')) end + item:SetData('cigsLeft', left) + item:GetParent():QueueSync() + + return left == 0 and 1 or 0 + end + end, + }, +}) + +octoinv.registerItem('bandage', { + name = L.bandage, + icon = 'octoteam/icons/bandage.png', + mass = 0.1, + volume = 0.08, + model = 'models/props/cs_office/file_box_gib2.mdl', + randomWeight = 0.2, + desc = L.desc_bandage, + use = { + function(ply, item) + if ply:Health() < 80 then return false, L.you_cant_plaster end + if ply:Health() >= 100 then return false, L.are_you_okay end + return L.seal_bruises, 'octoteam/icons/medkit.png', function(ply, item) + ply:SetHealth(math.min(ply:Health() + 5, 100)) + return 1 + end + end, + }, +}) + +octoinv.registerItem('clothes', { + name = L.clothes, + icon = 'octoteam/icons/clothes_warm.png', + mass = 0.5, + volume = 1.5, + model = 'models/props_junk/cardboard_box003a.mdl', + desc = L.desc_clothes, + use = { + function(ply, item) + if ply:isCP() then return false, L.need_unwear_police_form end + if ply:GetLocalVar('customClothes') then return false, 'Нужно снять текущую одежду' end + return L.wear2, 'octoteam/icons/clothes_warm.png', function(ply, item) + EventMakeRefugee(ply, function(ok) + if ok then + ply:TakeItem('clothes', 1) + ply:EmitSound('npc/combine_soldier/gear5.wav', 65) + ply.warmClothes = true + elseif tonumber(ply:HasItem('clothes')) <= 0 then + ply:Notify('warning', 'У тебя нет теплой одежды') + else + ply:Notify('warning', 'Теплая одежда не подошла по размеру... Попробуй сменить персонажа') + end + end, function() + return tonumber(ply:HasItem('clothes')) > 0 + end) + end + end, + }, +}) + +local ply = FindMetaTable 'Player' +function ply:SetClothes(clothes) + local mat = clothes and clothes.mat or nil + for i, original in ipairs(self:GetMaterials()) do + if string.match(original, '.+/sheet_%d+') then + self:SetSubMaterial(i - 1, mat) + end + end + + local top = self.inv and self.inv:GetContainer('top') + if top then + top.icon = clothes and clothes.icon or octoinv.defaultInventory.top.icon + top:QueueSync() + end + + ply.warmClothes = clothes and clothes.warm or nil + + local old = self:GetDBVar('customClothes') + self:SetLocalVar('customClothes', clothes) + self:SetDBVar('customClothes', clothes) + hook.Run('dbg-clothes.update', self, clothes, old) +end + +octoinv.registerItem('clothes_custom', { + name = 'Одежда', + icon = 'octoteam/icons/clothes_tshirt.png', + desc = 'Выделяйся из толпы!', + mass = 0.5, + volume = 1.5, + nostack = true, + model = 'models/props_junk/cardboard_box003a.mdl', + use = { + function(ply, item) + local matsToReplace = octolib.table.count(ply:GetMaterials(), function(v) return string.match(v, '.+/sheet_%d+') end) + if matsToReplace < 1 then return false, 'Ты не можешь это надеть сейчас' end + if ply:GetNetVar('customClothes') then return false, 'Нужно снять текущую одежду' end + + local gender = item:GetData('gender') + if gender then + local isFemale = ply:GetInfo('dbg_model'):find('female') + if gender == 'male' and isFemale then return false, 'Это мужская одежда' end + if gender == 'female' and not isFemale then return false, 'Это женская одежда' end + end + + return L.wear2, 'octoteam/icons/clothes_coat.png', function(ply, item) + ply:SetClothes(item:Export()) + ply:EmitSound('npc/combine_soldier/gear5.wav', 65) + + return 1 + end + end, + }, +}) + +concommand.Add('dbg_clothesoff', function(ply) + if not IsValid(ply) then return end + + local clothes = ply:GetNetVar('customClothes') + if not clothes then return ply:Notify('warning', 'На тебе нет одежды, которую можно снять') end + + local mats = ply:GetMaterials() + for i = 0, #mats - 1 do + if ply:GetSubMaterial(i) == clothes.mat then + local amount = ply:AddItem(clothes.class or 'clothes_custom', clothes) + if not amount or amount == 0 then + return ply:Notify('warning', 'В руках недостаточно места') + end + + ply:SetClothes(nil) + break + end + end +end) + +hook.Add('dbg-char.spawn', 'clothes', function(ply) + ply:SetClothes(ply:GetDBVar('customClothes')) +end) + +------------------------------------------------ +-- +-- CONTAINERS +-- +------------------------------------------------ + +local function spawnCont(ply, data) + local ent = ents.Create 'octoinv_cont' + ent.dt = ent.dt or {} + ent.dt.owning_ent = ply + ent.Model = data.model + ent.Skin = istable(data.skin) and math.random(data.skin[1], data.skin[2]) or data.skin or ent.Skin or 0 + ent.Mass = data.mass + ent.Containers = data.conts + ent.DestructParts = data.destruct + + ent.SID = ply.SID + ent:Spawn() + + ply:BringEntity(ent) + ent:SetPlayer(ply) + ent:SetLocked(false) + timer.Simple(3, function() + if IsValid(ent) and IsValid(ply) then + APG.entUnGhost(ent, ply) + end + end) + + return ent +end + +octoinv.registerItem('cont', { + name = L.container, + icon = 'octoteam/icons/box1.png', + mass = 1, + volume = 1, + nostack = true, + use = { + function(ply, item) + local t = item:GetData('contdata') + if not t then return false, L.item_break end + return L.collect, 'octoteam/icons/box3_go.png', function(ply, item) + return IsValid(spawnCont(ply, t)) and 1 or 0 + end + end, + }, +}) + +local lockCont = {'func_door', 'func_door_rotating', 'prop_door_rotating', 'func_movelinear', 'prop_dynamic', 'octoinv_cont', 'octoinv_prod', 'octoinv_vend', 'octoinv_storage'} +octoinv.registerItem('lock_cont', { + name = L.item_lock_cont, + icon = 'octoteam/icons/lock.png', + model = 'models/props_wasteland/prison_padlock001a.mdl', + mass = 1, + volume = 1, + nostack = true, + desc = L.desc_lock, + use = { + function(ply, item) + local pass = item:GetData('password') + if pass then return end + return 'Настроить', 'octoteam/icons/wrench.png', function(ply, item) + local oldCont = item:GetParent() + octolib.request.send(ply, {{ + type = 'strLong', + name = 'Пароль', + desc = 'Кодовая фраза. Этот замок можно будет открыть только ключом, пароль которого совпадает с собственным. Можно установить только один раз', + default = math.random(1000, 9999), + required = true, + }}, function(data) + if not item or item:GetParent() ~= oldCont then return end + local pass = tostring(data[1] or ''):sub(1, 32) + item:SetData('password', pass) + end) + end + end, + function(ply, item) + local num, pass = item:GetData('num'), item:GetData('password') + if not num then return false, L.item_break end + if not pass then return end + + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or (not table.HasValue(lockCont, ent:GetClass()) and not ent.lootable) then return false, L.need_see_on_item end + if ent:IsDoor() and ent:GetPlayerOwner() ~= ply:SteamID() then return false, L.this_is_not_your_door end + + return L.set, 'octoteam/icons/wrench.png', function(ply, item) + ply:DelayedAction('lock_mount', L.set_hint, { + time = 5, + check = function() return octolib.use.check(ply, ent) and tobool(ply:HasItem(item)) end, + succ = function() + ply:TakeItem(item) + ent.lockNum = num + if pass then + ent.password = pass + end + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('ambient/machines/pneumatic_drill_'.. math.random(1, 4) ..'.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, + }, +}) + +octoinv.registerItem('key', { + name = 'Ключ', + desc = 'Используется для открывания замков. На бирке написано: {tag}', + icon = 'octoteam/icons/key.png', + model = 'models/bull/buttons/key_switch.mdl', + mass = 0.02, + volume = 0.01, + nodespawn = true, + nostack = true, + tag = '*пусто*', + use = { + function(ply, item) + local pass = item:GetData('password') + if pass then return end + return 'Настроить', 'octoteam/icons/wrench.png', function(ply, item) + local oldCont = item:GetParent() + octolib.request.send(ply, {{ + type = 'strLong', + name = 'Пароль', + desc = 'Кодовая фраза. Этот ключ сможет открывать все замки, пароль которых совпадает с собственным. Можно установить только один раз', + default = math.random(1000, 9999), + required = true, + }}, function(data) + if not item or item:GetParent() ~= oldCont then return end + local pass = tostring(data[1] or ''):sub(1, 32) + item:SetData('password', pass) + end) + end + end, + function(ply, item) + return 'Подписать', 'octoteam/icons/pencil.png', function(ply, item) + local oldCont = item:GetParent() + octolib.request.send(ply, {{ + type = 'strLong', + name = 'Текст', + default = '', + required = true, + }}, function(data) + if not item or item:GetParent() ~= oldCont then return end + local name = utf8.sub(tostring(data[1] or ''), 1, 128) + item:SetData('tag', name) + item:GetParent():Sync() + end) + end + end, + }, +}) + +local function canLock(ply, ent) + + if ent.password then + local key = ply:FindItem({ class = 'key', password = ent.password }) + if key then + return true + else + return false + end + end + +end +hook.Add('canKeysLock', 'octoinv.keys', canLock) +hook.Add('canKeysUnlock', 'octoinv.keys', canLock) +hook.Add('octoinv.canLock', 'octoinv.keys', canLock) +hook.Add('octoinv.canUnlock', 'octoinv.keys', canLock) + +hook.Add('dbg-doors.unowned', 'octoinv.keys', function(ent) + ent.password = nil +end) + +------------------------------------------------ +-- +-- EVENT ITEMS +-- +------------------------------------------------ + +local desc = L.descEvent +octoinv.registerItem('coupon_ammo', { + name = L.coupon_ammo, + icon = 'octoteam/icons/coupon_red.png', + model = 'models/props/cs_assault/money.mdl', + desc = desc, + mass = 0.01, + volume = 0.01, +}) + +octoinv.registerItem('coupon_food', { + name = L.coupon_food, + icon = 'octoteam/icons/coupon_green.png', + model = 'models/props/cs_assault/money.mdl', + desc = desc, + mass = 0.01, + volume = 0.01, +}) + +octoinv.registerItem('coupon_exit', { + name = L.coupon_exit, + icon = 'octoteam/icons/coupon_blue.png', + model = 'models/props/cs_assault/money.mdl', + desc = desc, + mass = 0.01, + volume = 0.01, +}) + +octoinv.registerItem('petard', { + name = L.petards, + icon = 'octoteam/icons/dynamite.png', + mass = 0.5, + volume = 0.4, + usesLeft = 5, + model = 'models/Items/BoxBuckshot.mdl', + desc = L.desc_petards, + nostack = true, + use = { + function() + return 'Конвертировать', 'octoteam/icons/sparkler.png', function(ply, item) + local cont = item:GetParent() + local mass, volume, usesLeft = item:GetData('mass'), item:GetData('volume'), item:GetData('usesLeft') + item:Remove() + if usesLeft <= 0 then return end + + cont:AddItem('throwable', { + name = 'Петарды', + desc = 'Это еще что за шутки?!', + icon = 'octoteam/icons/dynamite.png', + usesLeft = usesLeft, + mass = mass, + volume = volume, + gc = 'ent_dbg_petard', + }) + end + end, + }, +}) + +octoinv.registerItem('fireworks', { + name = L.salute, + icon = 'octoteam/icons/firework.png', + mass = 1, + volume = 0.8, + model = 'models/Items/BoxSRounds.mdl', + desc = L.desc_salute, + nostack = true, + use = { + function(ply, item) + return L.to_put, 'octoteam/icons/explosion.png', function(ply, item) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.88, function() + local pos, ang, vel = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_L_Hand') or 16) + local tr = util.TraceLine { start = ply:GetShootPos(), endpos = pos, filter = ply } + if tr.Hit then + pos = tr.HitPos + tr.HitNormal * 5 + vel = tr.HitNormal * 100 + elseif not vel then + vel = Vector() + end + + ang = ply:EyeAngles() + ang.p = 0 + + local ent = ents.Create 'ent_dbg_fireworks' + ent:SetPos(pos) + ent:SetAngles(ang) + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + + ent:Spawn() + ent:Activate() + ply:LinkEntity(ent) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(vel) + end + end) + + return 1 + end + end, + }, +}) + +octoinv.registerItem('soccer', { + name = L.soccer, + desc = L.soccerballdesc, + icon = 'octoteam/icons/ball_soccer.png', + mass = 0.5, + volume = 0.5, + nostack = true, + use = { + function(ply, item) + if ply:HasItem('tool_pump') < 1 then return false, L.need_pump end + return L.pump_up, 'octoteam/icons/box3_go.png', function(ply, item) + local sid = ply:SteamID() + local count = 0 + for _, v in ipairs(ents.FindByClass('sent_soccerball')) do + if octolib.linkedCache[v] == sid then count = count + 1 end + end + if count >= 3 then + return ply:Notify('warning', 'Зачем тебе столько мячей?') + end + + local ent = ents.Create 'sent_soccerball' + if not IsValid(ent) then return end + + ent:Spawn() + ply:LinkEntity(ent) + ply:BringEntity(ent) + + return 1 + end + end, + }, +}) + +octoinv.registerItem('body_mat', { + name = L.body_mat, + desc = 'Собрал: {collector}\n\n{signature}Жертва: {corpse}{criminalsStr}', + icon = 'octoteam/icons/body_material.png', + mass = 0.2, + volume = 0.5, + nostack = true, + nodespawn = true, + collector = '???', + signature = '\n', + corpse = '???', + criminalsStr = '???', + use = { + function(ply, item) + if item.corpse and ply:isCP() then + return 'Подписать', 'octoteam/icons/pencil.png', function() + octolib.request.send(ply, {{ + type = 'strLong', + name = 'Подпись', + desc = 'Переносы строк конвертируются в пробелы', + default = utf8.sub(item.signature or '\n\n', 1, -3), + }}, function(data) + if not istable(data) then return end + local txt = string.Replace(data[1] or '', '\n', ' ') + if data[1] ~= nil and string.Trim(utf8.sub(txt, 1, 256)) ~= '' then + item:SetData('signature', string.Trim(utf8.sub(txt, 1, 256)) .. '\n\n') + else item:SetData('signature', '') end + end) + end + end + end, + function(ply, item) + local cont = item:GetParent() + if not cont then return end + if ply:isCP() and cont.id == 'utilizer' then + local own = cont:GetParent():GetOwner() + if own.utilizerBusy then return false, 'Утилизатор уже работает' end + return 'Запустить утилизацию', 'octoteam/icons/explosion.png', function() + own:NextItem() + end + end + end, + }, +}) + +octoinv.registerItem('binoculars', { + name = L.craft_binoculars, + icon = 'octoteam/icons/binoculars.png', + mass = 1.2, + volume = 1, + nostack = true, +}) + +hook.Add('closelook.canZoom', 'binoculars', function(ply) + if ply.inv and ply.inv.conts._hand and ply.inv.conts._hand:HasItem('binoculars') > 0 then + return true, true + end +end) + +octoinv.registerItem('phone', { + name = L.phone, + icon = 'octoteam/icons/phone_old.png', + mass = 0.35, + volume = 0.5, + model = 'models/lt_c/tech/cellphone.mdl', + desc = L.desc_phone, + nostack = true, + on = false, + status = 'ВЫКЛ', + vibration = false, + notification = false, + use = { + function(ply, item) + if not item.on then return end + if ply:GetNetVar('ScareState', 0) > 0.6 then + return false, 'Ты трясешься от страха' + end + return L.use, 'octoteam/icons-16/phone.png', function(ply, item) + netstream.Start(ply, 'dbg-phone.open') + end + end, + function(ply, item) + return item.on and 'Выключить' or 'Включить', 'octoteam/icons/button_power.png', function(ply, item) + local on = not item.on + item:SetData('on', on) + item:SetData('status', on and 'ВКЛ' or 'ВЫКЛ') + ply:EmitSound('buttons/button24.wav') + end + end, + function(ply, item) + if not item.on then return end + return (item.notification and 'Выключить' or 'Включить') .. ' звук', 'octoteam/icons/phone_notification.png', function(ply, item) + local on = not item.notification + item:SetData('notification', on) + if item.notification then ply:EmitSound('phone/phone_notification.wav', 38) end + end + end, + function(ply, item) + if not item.on then return end + return (item.vibration and 'Выключить' or 'Включить') .. ' вибрацию', 'octoteam/icons/phone_vibration.png', function(ply, item) + local on = not item.vibration + item:SetData('vibration', on) + if item.vibration then ply:EmitSound('phone/phone_vibration.wav', 25) end + end + end, + }, +}) + +octoinv.registerItem('phone_st', { + name = 'Домашний телефон', + icon = 'octoteam/icons/phone_office.png', + mass = 1.5, + volume = 5.5, + model = 'models/props/cs_office/phone.mdl', + desc = 'Средство связи, которое можно поставить у себя дома или на работе', + nostack = true, + class = 'ent_dbg_phone', + drop = function(ply, item, amount, posData) + local pos, ang, vel, put = + posData.pos or Vector(), + posData.ang or Angle(), + posData.vel or Vector(), + posData.put + + local ent = ents.Create('ent_dbg_phone') + ent.Model = item:GetData('model', true) or 'models/props/cs_office/phone.mdl' + ent:Spawn() + ent:SetPos(pos) + ent:SetAngles(ang) + ent.owned = true + + if put then + local a,b = ent:GetCollisionBounds() + pos:Sub(Vector(0,0,math.min(a.z, b.z))) + ent:SetPos(pos) + end + + if not IsValid(ent) then return nil, 0 end + + local ph = ent:GetPhysicsObject() + if IsValid(ph) then + ph:Wake() + ph:SetVelocity(vel) + end + + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + if IsValid(ply) and ply:IsPlayer() then + ply:LinkEntity(ent) + ent.owner = ply + end + ent.off = true + + return ent, 1 + end, + pickup = function(ply, ent) + if not ent.owned or not ent.off then return false end + end, +}) + +octoinv.registerItem('player', { + name = L.radio, + icon = 'octoteam/icons/boombox.png', + mass = 2.5, + volume = 2, + model = 'models/props/cs_office/radio.mdl', + desc = L.desc_radio, + nostack = true, + class = 'ent_dbg_radio', + drop = function(ply, item, amount, posData) + local pos, ang, vel, put = + posData.pos or Vector(), + posData.ang or Angle(), + posData.vel or Vector(), + posData.put + + local ent = ents.Create('ent_dbg_radio') + ent.Model = item:GetData('model') or ent.Model + ent:SetPos(pos) + ent:SetAngles(ang) + ent:Spawn() + + if put then + local a,b = ent:GetCollisionBounds() + pos:Sub(Vector(0,0,math.min(a.z, b.z))) + ent:SetPos(pos) + end + + if not IsValid(ent) then return nil, 0 end + + local ph = ent:GetPhysicsObject() + if IsValid(ph) then + ph:Wake() + ph:SetVelocity(vel) + end + + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + ent.radioPlayer = true + ent.owner = ply + if IsValid(ply) and ply:IsPlayer() then + ply:LinkEntity(ent) + end + + return ent, 1 + end, + pickup = function(ply, ent) + if not ent.radioPlayer then return false end + if ent.pinned then return false end + end, +}) + +octoinv.registerItem('zip', { + name = 'ZIP-пакет', + desc = 'Собрал: {collector}{signature}Содержимое:\n{storedStr}', + mass = 0.2, + volume = 0.2, + nodespawn = true, + nostack = true, + icon = 'octoteam/icons/zip_evidence.png', + model = 'models/weapons/w_package.mdl', + use = { + function(ply, item) + if item.storedStr then + return 'Подписать', 'octoteam/icons/pencil.png', function() + octolib.request.send(ply, {{ + type = 'strLong', + name = 'Подпись', + desc = 'Переносы строк конвертируются в пробелы', + default = utf8.sub(item.signature or '\n\n', 1, -3), + }}, function(data) + if not istable(data) then return end + local txt = string.Replace(data[1] or '', '\n', ' ') + if data[1] ~= nil and string.Trim(utf8.sub(txt, 1, 256)) ~= '' then + item:SetData('signature', string.Trim(utf8.sub(txt, 1, 256)) .. '\n\n') + else item:SetData('signature', '') end + end) + end + end + end, + }, +}) + +octoinv.registerItem('h_mask', { + name = L.mask, + icon = 'octoteam/icons/comedy.png', + mass = 0.3, + volume = 0.5, + desc = L.desc_mask, + nodespawn = true, + nostack = true, + use = { + function(ply, item) + local mask = item:GetData('mask') + if not mask then return false, L.item_break end + if ply:GetNetVar('hMask') then return false, 'На тебе уже надет аксессуар' end + return L.wear, 'octoteam/icons/key.png', function(ply, item) + ply:Unmask() + + local expire = item:GetData('expire') + if expire then + ply.maskExpireUid = 'octoinv.maskExpire' .. octolib.string.uuid() + timer.Create(ply.maskExpireUid, expire - os.time(), 1, function() + if not IsValid(ply) then return end + ply:SetNetVar('hMask') + ply:SetDBVar('hMask') + ply:Notify('У твоего аксессуара закончился срок годности') + end) + end + + ply:SetNetVar('hMask', {mask, item:GetData('expire')}) + ply:SetDBVar('hMask', {mask, item:GetData('expire')}) + return 1 + end + end, + }, +}) + +------------------------------------------------ +-- +-- DOOR AND DETAILS +-- +------------------------------------------------ + +octoinv.registerItem('door', { + name = 'Дверь', + icon = 'octoteam/icons/door.png', + model = 'models/props_junk/cardboard_box001a.mdl', + mass = 7.5, + volume = 7.5, + nostack = true, + desc = L.desc_door, + use = { + function(ply, item) + local door_skin = item:GetData('skin') + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or 'prop_door_rotating' ~= ent:GetClass() then return false, 'Нужно смотреть на дверь' end + if not ent:GetModel():find('models/props_c17/door01') then return false, 'Эту дверь нельзя заменить' end + if ent:GetPlayerOwner() ~= ply:SteamID() then return false, L.this_is_not_your_door end + if ent:GetSkin() == door_skin then return false, 'Нет смысла заменять дверь на точно такую же' end + + return L.set, 'octoteam/icons/wrench.png', function(ply, item) + ply:DelayedAction('replace_door', L.replace, { + time = 3, + check = function() return octolib.use.check(ply, ent) and tobool(ply:HasItem(item)) end, + succ = function() + ply:TakeItem(item) + ent:SetSkin(door_skin) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('ambient/machines/pneumatic_drill_'.. math.random(1, 4) ..'.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, + }, +}) + +octoinv.registerItem('door_handle', { + name = L.item_door_handle, + icon = 'octoteam/icons/door_handle.png', + model = 'models/props_junk/cardboard_box004a.mdl', + mass = .75, + volume = .75, + nostack = true, + desc = L.desc_door_handle, + use = { + function(ply, item) + local door_handle = item:GetData('handle') + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or 'prop_door_rotating' ~= ent:GetClass() then return false, 'Нужно смотреть на дверь' end + if not ent:GetModel():find('models/props_c17/door01') then return false, 'Ручку на этой двери нельзя заменить' end + if ent:GetPlayerOwner() ~= ply:SteamID() then return false, L.this_is_not_your_door end + if ent:GetBodygroup(1) == door_handle then return false, 'На двери уже установлена такая ручка' end + + return L.set, 'octoteam/icons/wrench.png', function(ply, item) + ply:DelayedAction('replace_door_handle', L.replace, { + time = 3, + check = function() return octolib.use.check(ply, ent) and tobool(ply:HasItem(item)) end, + succ = function() + ply:TakeItem(item) + ent:SetBodygroup(1, door_handle) + end, + }, { + time = 2, + inst = true, + action = function() + ply:EmitSound('ambient/machines/pneumatic_drill_'.. math.random(1, 4) ..'.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, + }, +}) + +hook.Add('dbg-doors.unowned', 'dbg-doors.custom', function(ent) + ent:SetSkin(ent.defaultSkin or 0) + for k,v in pairs(ent.defaultBGs or {}) do + ent:SetBodygroup(k, v) + end +end) diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/_howto.txt b/garrysmod/addons/_config/lua/config/octoinv/market/_howto.txt new file mode 100644 index 0000000..557238f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/_howto.txt @@ -0,0 +1,7 @@ +octoinv.registerMarketItem('glock', { -- [string] market item ID + name = 'Glock', -- [string] print name + icon = 'octoteam/icons/gun_pistol.png', -- [string] icon for vgui + parent = 'weapon', -- [string] parent class, must be registered before current item +}) + +-- more to come diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/car.lua b/garrysmod/addons/_config/lua/config/octoinv/market/car.lua new file mode 100644 index 0000000..abbcc24 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/car.lua @@ -0,0 +1,23 @@ +octoinv.registerMarketItem('cat_cars', { + name = L.to_car, + icon = 'octoteam/icons/car2.png', +}) + +octoinv.registerMarketItem('car_rims', { + parent = 'cat_cars', + name = 'Диски', + nostack = true, +}) + +octoinv.registerMarketItem('car_att', { + parent = 'cat_cars', + name = 'Аксессуары', + icon = 'octoteam/icons/bust.png', + nostack = true, +}) + +octoinv.registerMarketItem('car_part', { + parent = 'cat_cars', + name = 'Детали кузова', + nostack = true, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/collect.lua b/garrysmod/addons/_config/lua/config/octoinv/market/collect.lua new file mode 100644 index 0000000..2379772 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/collect.lua @@ -0,0 +1,32 @@ +octoinv.registerMarketItem('cat_collector', { + name = 'Добыча ресурсов', + icon = octolib.icons.color('crowbar'), + nostack = true, +}) + +octoinv.registerMarketItem('collector_pickaxe', { + name = 'Кирка', + parent = 'cat_collector', + nostack = true, + matches = function(item) + return item.class == 'collector' and item.collector == 'pickaxe' and item.health and item.health > 0 + end, + retain = { + sell = { + amount = 5, + price = 14500, + getOrderInfo = function() + local health = math.random(15, 500) + local price = 2000 + health * 25 + math.random(-100, 100) * 10 + return price, { + class = 'collector', + name = 'Кирка', + icon = octolib.icons.color('crowbar'), + collector = 'pickaxe', + maxhealth = 500, + health = health, + } + end, + }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/food.lua b/garrysmod/addons/_config/lua/config/octoinv/market/food.lua new file mode 100644 index 0000000..192b5f9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/food.lua @@ -0,0 +1,17 @@ +-- octoinv.registerMarketItem('food', { +-- name = 'Еда', +-- nostack = true, +-- matches = function(item) +-- return item.class == 'food' and item.trash +-- end, +-- }) + +octoinv.registerMarketItem('cat_ings', { + name = L.ings, + icon = 'octoteam/icons/food_ingredients.png', +}) + +octoinv.registerMarketItem('ing_fish1', { parent = 'cat_ings' }) +octoinv.registerMarketItem('ing_fish2', { parent = 'cat_ings' }) +octoinv.registerMarketItem('ing_fish3', { parent = 'cat_ings' }) +octoinv.registerMarketItem('ing_fish4', { parent = 'cat_ings' }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/gun.lua b/garrysmod/addons/_config/lua/config/octoinv/market/gun.lua new file mode 100644 index 0000000..3bc38e3 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/gun.lua @@ -0,0 +1,129 @@ +do return end + +local minTime = 5 * 60 * 60 + +-- ammo +octoinv.registerMarketItem('cat_ammo', { + name = 'Аммуниция', + icon = 'octoteam/icons/gun_bullet.png', + nostack = true, +}) + +local function registerAmmo(id, name, icon) + octoinv.registerMarketItem('ammo_' .. id, { + name = name, + icon = icon, + parent = 'cat_ammo', + nostack = true, + matches = function(item) + return item.class == 'ammo' and item.ammotype == id + end, + canBuy = function(ply, order) + return CFG.dev or ply:GetTimeTotal() >= minTime, 'Оружие можно покупать только наиграв на сервере 5 часов' + end, + }) +end +registerAmmo('pistol', L.small_ammo, 'octoteam/icons/gun_bullet.png') +registerAmmo('smg1', L.large_ammo, 'octoteam/icons/gun_bullet2.png') +registerAmmo('sniper', L.sniper_ammo, 'octoteam/icons/gun_bullet2.png') +registerAmmo('buckshot', L.buckshot, 'octoteam/icons/gun_bullet.png') + +-- guns +octoinv.registerMarketItem('cat_gun', { + name = 'Оружие', + icon = 'octoteam/icons/gun_pistol.png', + nostack = true, +}) + +local gunBase +local function registerGun(id, name) + octoinv.registerMarketItem(id, { + name = name, + parent = gunBase, + nostack = true, + matches = function(item) + return item.class == 'weapon' and item.wepclass == 'weapon_octo_' .. id + end, + canBuy = function(ply, order) + return CFG.dev or ply:GetTimeTotal() >= minTime, 'Оружие можно покупать только наиграв на сервере 5 часов' + end, + }) +end + +------------------- +-- PISTOLS +------------------- +octoinv.registerMarketItem('cat_gun_pistol', { + name = 'Пистолеты', + parent = 'cat_gun', + nostack = true, +}) + +gunBase = 'cat_gun_pistol' +registerGun('357', 'Colt .357') +registerGun('deagle', 'Desert Eagle') +registerGun('dualelites', 'Dual Elites') +registerGun('fiveseven', 'FiveseveN') +registerGun('glock', 'Glock 27') +registerGun('p228', 'P228') +registerGun('usp', 'USP') +registerGun('usps', 'USP-S') + +------------------- +-- RIFLES +------------------- +octoinv.registerMarketItem('cat_gun_rifle', { + name = 'Автоматы', + icon = 'octoteam/icons/gun_rifle.png', + parent = 'cat_gun', + nostack = true, +}) +gunBase = 'cat_gun_rifle' +registerGun('ak', 'AK') +registerGun('famas', 'FAMAS') +registerGun('galil', 'Galil') +registerGun('m4a1', 'M4A1') +registerGun('m249', 'M249') + +------------------- +-- SHOTGUNS +------------------- +octoinv.registerMarketItem('cat_gun_shotgun', { + name = 'Дробовики', + icon = 'octoteam/icons/gun_shotgun.png', + parent = 'cat_gun', + nostack = true, +}) +gunBase = 'cat_gun_shotgun' +registerGun('m3', 'M3') +registerGun('xm1014', 'XM1014') + +------------------- +-- SUBMACHINE GUNS +------------------- +octoinv.registerMarketItem('cat_gun_smg', { + name = 'Полуавтоматы', + icon = 'octoteam/icons/gun_smg.png', + parent = 'cat_gun', + nostack = true, +}) +gunBase = 'cat_gun_smg' +registerGun('mac10', 'MAC10') +registerGun('mp5', 'MP5') +registerGun('p90', 'P90') +registerGun('tmp', 'TMP') +registerGun('ump45', 'UMP45') + +------------------- +-- SNIPER +------------------- +octoinv.registerMarketItem('cat_gun_sniper', { + name = 'Винтовки', + icon = 'octoteam/icons/gun_sniper.png', + parent = 'cat_gun', + nostack = true, +}) +gunBase = 'cat_gun_sniper' +registerGun('awp', 'AWP') +registerGun('scout', 'Scout') +registerGun('sg550', 'SG550') diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/legacy.lua b/garrysmod/addons/_config/lua/config/octoinv/market/legacy.lua new file mode 100644 index 0000000..b3674e2 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/legacy.lua @@ -0,0 +1,38 @@ +octoinv.registerMarketItem('cat_legacy', { + name = 'Пеработка', + icon = 'octoteam/icons/reset.png', +}) + +octoinv.registerMarketItem('craft_paper2', { + parent = 'cat_legacy', + retain = { + buy = { + price = 70, + amount = 500, + getOrderInfo = function() return math.random(60, 85), math.random(5, 50) end, + }, + }, +}) + +octoinv.registerMarketItem('craft_ink', { + parent = 'cat_legacy', + retain = { + buy = { + price = 550, + amount = 80, + getOrderInfo = function() return math.random(100, 130) * 5, math.random(5, 50) end, + }, + }, +}) + +octoinv.registerMarketItem('bpd_vend', { + parent = 'cat_legacy', + retain = { + buy = { + price = 4600, + amount = 8, + getOrderInfo = function() return math.random(450, 500) * 10, math.random(1, 2) end, + }, + }, +}) + diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/resources.lua b/garrysmod/addons/_config/lua/config/octoinv/market/resources.lua new file mode 100644 index 0000000..4b8e8c5 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/resources.lua @@ -0,0 +1,84 @@ +octoinv.registerMarketItem('cat_resources', { + name = L.resources, + icon = 'octoteam/icons/ore_iron.png', +}) + +octoinv.registerMarketItem('ingot_iron', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ingot_steel', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ingot_silver', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ingot_bronze', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ingot_gold', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ingot_copper', { parent = 'cat_resources' }) + +octoinv.registerMarketItem('ore_silver', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ore_bronze', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ore_gold', { parent = 'cat_resources' }) +octoinv.registerMarketItem('ore_copper', { parent = 'cat_resources' }) +octoinv.registerMarketItem('stone', { parent = 'cat_resources' }) +octoinv.registerMarketItem('gunpowder', { parent = 'cat_resources' }) +octoinv.registerMarketItem('craft_glass', { parent = 'cat_resources' }) + +octoinv.registerMarketItem('ore_iron', { + parent = 'cat_resources', + retain = { + sell = { + price = 1950, + amount = 20, + getOrderInfo = function() return math.random(150, 225) * 10, math.random(1, 12) end, + }, + }, +}) +octoinv.registerMarketItem('ore_steel', { + parent = 'cat_resources', + retain = { + sell = { + price = 3950, + amount = 8, + getOrderInfo = function() return math.random(250, 400) * 10, math.random(1, 3) end, + }, + }, +}) + +octoinv.registerMarketItem('craft_coal', { + parent = 'cat_resources', + retain = { + sell = { + price = 500, + amount = 20, + getOrderInfo = function() return math.random(98, 110) * 5, math.random(1, 8) end, + }, + }, +}) + +octoinv.registerMarketItem('sulfur', { + parent = 'cat_resources', + retain = { + sell = { + price = 3500, + amount = 10, + getOrderInfo = function() return math.random(330, 360) * 10, math.random(1, 3) end, + }, + }, +}) + +octoinv.registerMarketItem('rubble', { + parent = 'cat_resources', + retain = { + buy = { + price = 45, + amount = 35, + getOrderInfo = function() return math.random(9, 12) * 5, math.random(1, 8) end, + }, + }, +}) + +octoinv.registerMarketItem('sand', { + parent = 'cat_resources', + retain = { + buy = { + price = 35, + amount = 35, + getOrderInfo = function() return math.random(8, 11) * 5, math.random(1, 8) end, + }, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/market/shop.lua b/garrysmod/addons/_config/lua/config/octoinv/market/shop.lua new file mode 100644 index 0000000..f8141ad --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/market/shop.lua @@ -0,0 +1,8 @@ +-- octoinv.registerMarketCatFromShop('everyday') +-- octoinv.registerMarketCatFromShop('tool') +-- octoinv.registerMarketCatFromShop('build') +-- octoinv.registerMarketCatFromShop('prod') +-- octoinv.registerMarketCatFromShop('dish') +-- octoinv.registerMarketCatFromShop('ings') +-- octoinv.registerMarketCatFromShop('pharm', { nostack = true }) +octoinv.registerMarketCatFromShop('parts') diff --git a/garrysmod/addons/_config/lua/config/octoinv/prod/fridge.lua b/garrysmod/addons/_config/lua/config/octoinv/prod/fridge.lua new file mode 100644 index 0000000..1637ba6 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/prod/fridge.lua @@ -0,0 +1,77 @@ +octoinv.registerProd('fridge', { + name = L.fridge, + icon = 'octoteam/icons/fridge.png', + destruct = { + {'bpd_fridge', 1}, + }, + sounds = { + loop = 'octoinv.prod14', + work = 'octoinv.prod12', + }, +}) + +octoinv.registerProcess('fridge', { + time = 90, + ins = {fridge = { + {'tool_foodcont', 1}, + {'ing_icecream', 1}, + {'ing_banana', 2}, + {'ing_strawberry', 2}, + {'ing_cocoa', 2}, + }}, + out = {fridge = {{'food', { + name = L.strawberry_banana_ice_cream, + desc = L.desc_strawberry_banana_ice_cream, + energy = 35, + icon = 'octoteam/icons/food_icecream.png' + }}}}, +}) + +octoinv.registerProcess('fridge', { + time = 90, + ins = {fridge = { + {'tool_foodcont', 1}, + {'ing_dough3', 1}, + {'ing_cream', 1}, + {'ing_strawberry', 1}, + {'ing_cocoa', 1}, + }}, + out = {fridge = {{'food', { + name = L.muffin, + energy = 25, + icon = 'octoteam/icons/food_cupcake.png' + }}}}, +}) + +octoinv.registerProcess('fridge', { + time = 60, + ins = {fridge = { + {'tool_foodcont', 1}, + {'ing_sugar', 2}, + {'ing_cocoa', 2}, + {'ing_cream', 1}, + }}, + out = {fridge = {{'food', { + name = L.ing_choco, + energy = 25, + icon = 'octoteam/icons/food_choco.png' + }}}}, +}) + +octoinv.registerProcess('fridge', { + time = 60, + ins = {fridge = { + {'tool_shaker', 1}, + {'tool_foodcont', 1}, + {'ing_sugar', 2}, + {'ing_milk', 1}, + {'ing_icecream', 1}, + {'ing_banana', 1}, + }}, + out = {fridge = {{'tool_shaker', 1} ,{'food', { + name = L.milkshake, + energy = 40, + icon = 'octoteam/icons/food_milkshake.png', + drink = true, + }}}}, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/prod/machine.lua b/garrysmod/addons/_config/lua/config/octoinv/prod/machine.lua new file mode 100644 index 0000000..db22afb --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/prod/machine.lua @@ -0,0 +1,108 @@ +octoinv.registerProd('machine', { + name = L.machine, + icon = 'octoteam/icons/machine.png', + mass = 400, + destruct = { + {'bpd_machine', 1}, + }, + sounds = { + loop = 'octoinv.prod12', + work = 'octoinv.prod9', + }, +}) + +octoinv.registerProcess('machine', { + time = 15, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_shutter', 1}}}, + out = {machine = {{'craft_shutter', 2}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_barrel', 1}}}, + out = {machine = {{'craft_barrel', 2}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_grip', 1}}}, + out = {machine = {{'craft_grip', 1}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_aim', 1}}}, + out = {machine = {{'craft_aim', 7}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_drummer', 1}}}, + out = {machine = {{'craft_drummer', 5}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_trigger', 1}}}, + out = {machine = {{'craft_trigger', 8}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_stopper', 1}}}, + out = {machine = {{'craft_stopper', 5}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_steel', 1}}, machine_tray = {{'bp_clip', 1}}}, + out = {machine = {{'craft_clip', 1}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_screw', 1}}}, + out = {machine = {{'craft_screw', 30}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_screw2', 1}}}, + out = {machine = {{'craft_screw2', 25}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_screwnut', 1}}}, + out = {machine = {{'craft_screwnut', 30}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_nail', 1}}}, + out = {machine = {{'craft_nail', 40}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_spring', 1}}}, + out = {machine = {{'craft_spring', 10}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_pulley', 1}}}, + out = {machine = {{'craft_pulley', 5}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'ingot_iron', 1}}, machine_tray = {{'bp_piston', 1}}}, + out = {machine = {{'craft_piston', 4}}}, +}) + +octoinv.registerProcess('machine', { + time = 10, + ins = {machine = {{'craft_glass', 1}}, machine_tray = {{'bp_bulb', 1}}}, + out = {machine = {{'craft_bulb', 3}}}, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/prod/printer.lua b/garrysmod/addons/_config/lua/config/octoinv/prod/printer.lua new file mode 100644 index 0000000..a950d9e --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/prod/printer.lua @@ -0,0 +1,23 @@ +-- octoinv.registerProd('printer', { +-- name = L.printer, +-- icon = 'octoteam/icons/atm.png', +-- destruct = { +-- {'craft_screw2', 20}, +-- {'craft_screwnut', 20}, +-- }, +-- fuel = { +-- printer = {craft_battery = 30 * 60}, +-- }, +-- sounds = { +-- loop = 'octoinv.prod16', +-- work = 'octoinv.prod17', +-- }, +-- explode = {80, 100}, +-- health = 60, +-- }) + +-- octoinv.registerProcess('printer', { +-- time = 90, +-- ins = {printer = {{'craft_paper2', 2}}, printer_cart = {{'craft_ink', 1}}}, +-- out = {printer = {{'money', 1500}}}, +-- }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/prod/refinery.lua b/garrysmod/addons/_config/lua/config/octoinv/prod/refinery.lua new file mode 100644 index 0000000..e985fb3 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/prod/refinery.lua @@ -0,0 +1,77 @@ +octoinv.registerProd('refinery', { + name = L.refinery, + icon = 'octoteam/icons/chem_plant.png', + mass = 1000, + destruct = { + {'bpd_refinery', 1}, + }, + fuel = { + refinery_fuel = { + craft_fuel = 30, + craft_coal = 180, + } + }, + sounds = { + loop = 'octoinv.prod3', + work = 'octoinv.prod4', + }, +}) + +octoinv.registerProcess('refinery', { + time = 10, + ins = {refinery = {{'ore_iron', 1}}}, + out = {refinery = {{'ingot_iron', 1}}}, +}) + +octoinv.registerProcess('refinery', { + time = 30, + ins = {refinery = {{'ore_steel', 1}}}, + out = {refinery = {{'ingot_steel', 1}}}, +}) + +octoinv.registerProcess('refinery', { + time = 20, + ins = {refinery = {{'ore_bronze', 1}}}, + out = {refinery = {{'ingot_bronze', 1}}}, +}) + +octoinv.registerProcess('refinery', { + time = 45, + ins = {refinery = {{'ore_gold', 1}}}, + out = {refinery = {{'ingot_gold', 1}}}, +}) + +octoinv.registerProcess('refinery', { + time = 15, + ins = {refinery = {{'ore_copper', 1}}}, + out = {refinery = {{'ingot_copper', 1}}}, +}) + +octoinv.registerProcess('refinery', { + time = 40, + ins = {refinery = {{'ore_silver', 1}}}, + out = {refinery = {{'ingot_silver', 1}}}, +}) + +octoinv.registerProcess('refinery', { + time = 10, + ins = {refinery = {{'stone', 1}}}, + out = {refinery = { + {'rubble', 1}, + {0.05, 'sulfur', 1}, + {0.1, 'craft_coal', 1}, + }}, +}) + +octoinv.registerProcess('refinery', { + time = 15, + ins = {refinery = {{'rubble', 1}}}, + out = {refinery = { + {'sand', 1}, + {0.005, 'jewelry_diamond', 1}, + {0.005, 'jewelry_ruby', 1}, + {0.005, 'jewelry_sapphire', 1}, + {0.005, 'jewelry_topaz', 1}, + {0.005, 'jewelry_emerald', 1}, + }}, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/prod/smelter.lua b/garrysmod/addons/_config/lua/config/octoinv/prod/smelter.lua new file mode 100644 index 0000000..1828d38 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/prod/smelter.lua @@ -0,0 +1,30 @@ +octoinv.registerProd('smelter', { + name = L.smelter, + icon = 'octoteam/icons/smelter.png', + mass = 700, + destruct = { + {'bpd_smelter', 1}, + }, + fuel = { + smelter_fuel = { + craft_fuel = 30, + craft_coal = 180, + }, + }, + sounds = { + loop = 'octoinv.prod15', + work = 'octoinv.prod11', + }, +}) + +octoinv.registerProcess('smelter', { + time = 30, + ins = {smelter = {{'sand', 1}}}, + out = {smelter = {{'craft_glass', 10}}}, +}) + +octoinv.registerProcess('smelter', { + time = 5, + ins = {smelter = {{'craft_bottle', 1}}}, + out = {smelter = {{'craft_glass', 2}}}, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/prod/stove.lua b/garrysmod/addons/_config/lua/config/octoinv/prod/stove.lua new file mode 100644 index 0000000..aa903c9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/prod/stove.lua @@ -0,0 +1,550 @@ +local ckie = {'food', { + name = L.cookie, + energy = 10, + icon = 'octoteam/icons/food_cookies.png', + mass = 0.15, + volume = 0.15, +}} + +octoinv.registerProd('stove', { + name = L.stove, + icon = 'octoteam/icons/stove.png', + mass = 650, + destruct = { + {'bpd_stove', 1}, + }, + sounds = { + loop = 'octoinv.prod15', + work = 'octoinv.prod10', + }, +}) + +-- +-- INGREDIENTS +-- + +-- Котлета +octoinv.registerProcess('stove', { + time = 15, + ins = {stove = { + {'tool_pan', 1}, + {'ing_oil', 1}, + {'ing_salt', 1}, + {'ing_meat', 1}, + }}, + out = {stove = {{'tool_pan', 1}, {'ing_meatball', 1}}}, +}) + +-- Бульон +octoinv.registerProcess('stove', { + time = 30, + ins = {stove = { + {'tool_pot', 1}, + {'ing_water', 1}, + {'ing_salt', 1}, + {'ing_spice', 1}, + }}, + out = {stove = {{'tool_pot', 1}, {'ing_soup', 1}}}, +}) + +-- Жареное мясо +octoinv.registerProcess('stove', { + time = 25, + ins = {stove = { + {'tool_pan', 1}, + {'ing_oil', 1}, + {'ing_spice', 1}, + {'ing_meat', 1}, + {'ing_flour', 1}, + }}, + out = {stove = {{'tool_pan', 1}, {'ing_roastmeat', 1}}}, +}) + +-- Вареный картофель +octoinv.registerProcess('stove', { + time = 20, + ins = {stove = { + {'tool_pot', 1}, + {'ing_water', 1}, + {'ing_potato', 2}, + }}, + out = {stove = {{'tool_pot', 1}, {'ing_potato2', 2}}}, +}) + +-- Основа для пиццы +octoinv.registerProcess('stove', { + time = 20, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_flour', 1}, + {'ing_egg', 2}, + {'ing_salt', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'ing_pizza_base', 1}}}, +}) + +-- Бисквит +octoinv.registerProcess('stove', { + time = 20, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_flour', 2}, + {'ing_sugar', 1}, + {'ing_oil', 1}, + {'ing_milk', 1}, + {'ing_egg', 2}, + }}, + out = {oven = {{'tool_oventray', 1}, {'ing_dough3', 1}}}, +}) + +-- +-- DISHES +-- + +-- Яичница +octoinv.registerProcess('stove', { + time = 20, + ins = {stove = { + {'tool_pan', 1}, + {'tool_foodcont', 1}, + {'ing_egg', 2}, + {'ing_oil', 1}, + {'ing_salt', 1}, + }}, + out = {stove = {{'tool_pan', 1}, {'food', { + name = L.omelet, + desc = L.desc_omelet, + icon = 'octoteam/icons/food_omlet.png', + energy = 20 + }}}}, +}) + +-- Пицца Пепперони +octoinv.registerProcess('stove', { + time = 55, + ins = {oven = { + {'tool_oventray', 1}, + {'tool_foodcont', 1}, + {'ing_cheese', 1}, + {'ing_flour', 1}, + {'ing_salt', 2}, + {'ing_sausage2', 2}, + {'ing_pizza_base', 1}, + {'ing_oil', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.pizza_pepperoni, + desc = L.desc_pizza_pepperoni, + energy = 85, + icon = 'octoteam/icons/food_pizza_pepperoni.png' + }}}}, +}) + +-- Пицца Маргарита +octoinv.registerProcess('stove', { + time = 55, + ins = {oven = { + {'tool_oventray', 1}, + {'tool_foodcont', 1}, + {'ing_cheese', 1}, + {'ing_flour', 1}, + {'ing_salt', 2}, + {'ing_tomato', 3}, + {'ing_pizza_base', 1}, + {'ing_oil', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.pizza_margarita, + desc = L.desc_pizza_margarita, + energy = 85, + icon = 'octoteam/icons/food_pizza_margherita.png' + }}}}, +}) + +-- Пицца Вулкано +octoinv.registerProcess('stove', { + time = 55, + ins = {oven = { + {'tool_oventray', 1}, + {'tool_foodcont', 1}, + {'ing_cheese', 3}, + {'ing_flour', 1}, + {'ing_salt', 2}, + {'ing_tomato', 3}, + {'ing_pizza_base', 1}, + {'ing_chili', 2}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.pizza_volcano, + desc = L.desc_pizza_volcano, + energy = 100, + icon = 'octoteam/icons/food_pizza_volcano.png' + }}}}, +}) + +-- Пирожок с мясом +octoinv.registerProcess('stove', { + time = 25, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_dough1', 1}, + {'ing_roastmeat', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.meat_pie, + icon = 'octoteam/icons/food_meat_pie.png', + desc = L.desc_meat_pie, + energy = 25 + }}}}, +}) + +-- Хотдог +octoinv.registerProcess('stove', { + time = 25, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_dough1', 1}, + {'ing_sausage', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.hotdog, + icon = 'octoteam/icons/food_hotdog.png', + desc = L.desc_hotdog, + energy = 25 + }}}}, +}) + +-- Яблочный пирог +octoinv.registerProcess('stove', { + time = 45, + ins = {oven = { + {'tool_oventray', 1}, + {'tool_foodcont', 1}, + {'ing_dough1', 2}, + {'ing_sugar', 3}, + {'ing_apple', 2}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.apple_pie, + desc = L.desc_apple_pie, + energy = 60, + icon = 'octoteam/icons/food_pie.png' + }}}}, +},{ + time = 120, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_dough3', 2}, + {'ing_cocoa', 2}, + }}, + out = {oven = {{'tool_oventray', 1}, ckie, ckie, ckie}}, +}) + +-- Блинчики +octoinv.registerProcess('stove', { + time = 30, + ins = {stove = { + {'tool_pan', 1}, + {'ing_oil', 1}, + {'ing_milk', 2}, + {'ing_egg', 2}, + {'ing_flour', 2}, + }}, + out = {stove = {{'tool_pan', 1}, {'food', { + name = L.cupcakes, + energy = 40, + icon = 'octoteam/icons/food_pancake.png' + }}}}, +}) + +-- Грибной суп +octoinv.registerProcess('stove', { + time = 45, + ins = {stove = { + {'tool_pot', 1}, + {'tool_foodcont', 1}, + {'ing_soup', 1}, + {'ing_potato', 3}, + {'ing_mushroom', 5}, + {'ing_lettuce', 2}, + }}, + out = {stove = {{'tool_pot', 1}, {'food', { + name = L.mushroom_soup, + energy = 50, + icon = 'octoteam/icons/food_mushroom_soup.png' + }}}}, +}) + +-- Борщ +octoinv.registerProcess('stove', { + time = 80, + ins = {stove = { + {'tool_pot', 1}, + {'tool_foodcont', 1}, + {'ing_soup', 1}, + {'ing_potato', 4}, + {'ing_beet', 3}, + {'ing_tomato', 3}, + {'ing_cabbage', 1}, + {'ing_meat', 2}, + {'ing_salt', 1}, + }}, + out = {stove = {{'tool_pot', 1}, {'food', { + name = L.borsch, + energy = 80, + icon = 'octoteam/icons/food_borsh.png' + }}}}, +}) + +-- Овощное рагу +octoinv.registerProcess('stove', { + time = 55, + ins = {stove = { + {'tool_pan', 1}, + {'tool_foodcont', 1}, + {'ing_eggplant', 1}, + {'ing_potato', 2}, + {'ing_mushroom', 1}, + {'ing_cheese', 1}, + {'ing_onion', 1}, + {'ing_celery', 1}, + {'ing_oil', 1}, + {'ing_salt', 1}, + }}, + out = {stove = {{'tool_pan', 1}, {'food', { + name = L.vegetable_stew, + energy = 55, + icon = 'octoteam/icons/food_rice.png' + }}}}, +}) + +-- Зитти +octoinv.registerProcess('stove', { + time = 60, + ins = {oven = { + {'tool_oventray', 1}, + {'tool_foodcont', 1}, + {'ing_onion', 1}, + {'ing_cheese', 2}, + {'ing_tomato', 4}, + {'ing_meat', 2}, + {'ing_pasta', 1}, + {'ing_salt', 1}, + {'ing_spice', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.zitti, + energy = 60, + icon = 'octoteam/icons/food_porrige.png' + }}}}, +}) + +-- Болоньезе +octoinv.registerProcess('stove', { + time = 50, + ins = {stove = { + {'tool_pan', 1}, + {'tool_foodcont', 1}, + {'ing_water', 1}, + {'ing_cheese', 2}, + {'ing_tomato', 3}, + {'ing_oil', 1}, + {'ing_meat', 1}, + {'ing_pasta', 1}, + {'ing_spice', 1}, + }}, + out = {stove = {{'tool_pan', 1}, {'food', { + name = L.bolognese, + energy = 50, + icon = 'octoteam/icons/food_spaghetti.png' + }}}}, +}) + +-- Лазанья +octoinv.registerProcess('stove', { + time = 100, + ins = {oven = { + {'tool_oventray', 1}, + {'tool_foodcont', 1}, + {'ing_olive', 3}, + {'ing_flour', 2}, + {'ing_milk', 1}, + {'ing_cheese', 4}, + {'ing_meat', 2}, + {'ing_oil', 2}, + {'ing_salt', 1}, + {'ing_spice', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = L.lasagna, + energy = 100, + icon = 'octoteam/icons/food_lazania.png' + }}}}, +}) + +-- Чай +octoinv.registerProcess('stove', { + time = 15, + ins = {stove = { + {'tool_teapot', 1}, + {'tool_foodcont', 1}, + {'ing_tea', 2}, + {'ing_water', 1}, + }}, + out = {stove = {{'tool_teapot', 1}, {'food', { + name = L.tea, + energy = 15, + maxenergy = 50, + model = 'models/props_junk/garbage_coffeemug001a.mdl', + icon = 'octoteam/icons/cup2.png', + drink = true, + }}}}, +}) + +-- Какао +octoinv.registerProcess('stove', { + time = 30, + ins = {stove = { + {'tool_coffeepot', 1}, + {'tool_foodcont', 1}, + {'ing_cocoa', 2}, + {'ing_milk', 1}, + {'ing_sugar', 3}, + }}, + out = {stove = {{'tool_coffeepot', 1}, {'food', { + name = L.cacao, + energy = 20, + maxenergy = 60, + model = 'models/props_junk/garbage_coffeemug001a.mdl', + icon = 'octoteam/icons/cup.png', + drink = true, + }}}}, +}) + +-- Капучино +octoinv.registerProcess('stove', { + time = 20, + ins = {stove = { + {'tool_coffeepot', 1}, + {'tool_foodcont', 1}, + {'ing_coffee', 2}, + {'ing_milk', 1}, + }}, + out = {stove = {{'tool_coffeepot', 1}, {'food', { + name = L.cappuccino, + energy = 15, + maxenergy = 60, + mass = 0.5, + volume = 0.5, + model = 'models/props_junk/garbage_coffeemug001a.mdl', + icon = 'octoteam/icons/coffee.png', + drink = true, + }}}}, +}) + +-- Эспрессо +octoinv.registerProcess('stove', { + time = 20, + ins = {stove = { + {'tool_coffeepot', 1}, + {'tool_foodcont', 1}, + {'ing_coffee', 3}, + }}, + out = {stove = {{'tool_coffeepot', 1}, {'food', { + name = L.espresso, + energy = 10, + maxenergy = 60, + mass = 0.35, + volume = 0.35, + model = 'models/props_junk/garbage_coffeemug001a.mdl', + icon = 'octoteam/icons/coffee.png', + drink = true, + }}}}, +}) + +-- Запеченный окунь +octoinv.registerProcess('stove', { + time = 30, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_fish1', 1}, + {'ing_oil', 1}, + {'ing_spice', 1}, + {'ing_salt', 1}, + {'ing_onion', 1}, + {'ing_tomato', 2}, + {'tool_foodcont', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = 'Запеченный окунь', + icon = 'octoteam/icons/food_fish_cooked.png', + energy = 60, + }}}}, +}) + +-- Запеченный карп +octoinv.registerProcess('stove', { + time = 30, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_fish2', 1}, + {'ing_oil', 1}, + {'ing_spice', 1}, + {'ing_salt', 1}, + {'ing_onion', 1}, + {'ing_lemon', 1}, + {'ing_carrot', 1}, + {'ing_tomato', 1}, + {'tool_foodcont', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, {'food', { + name = 'Запеченный карп', + icon = 'octoteam/icons/food_fish_cooked.png', + energy = 70, + }}}}, +}) + +-- Форель под медом +octoinv.registerProcess('stove', { + time = 30, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_fish3', 1}, + {'ing_spice', 1}, + {'ing_salt', 1}, + {'ing_onion', 1}, + {'ing_lemon', 2}, + {'ing_honey', 1}, + {'tool_foodcont', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, + unpack(octolib.array.series({'food', { + name = 'Форель под медом', + icon = 'octoteam/icons/food_fish_cooked_honey.png', + energy = 50, + }}, 2)) + }}, +}) + +-- Щука в сливочном соусе +octoinv.registerProcess('stove', { + time = 45, + ins = {oven = { + {'tool_oventray', 1}, + {'ing_fish4', 1}, + {'ing_spice', 1}, + {'ing_salt', 1}, + {'ing_onion', 1}, + {'ing_cream', 1}, + {'ing_sauce', 1}, + {'ing_oil', 1}, + {'tool_foodcont', 1}, + }}, + out = {oven = {{'tool_oventray', 1}, + unpack(octolib.array.series({'food', { + name = 'Щука в сливочном соусе', + icon = 'octoteam/icons/food_fish_cooked_cream.png', + energy = 60, + }}, 2)) + }}, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/car.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/car.lua new file mode 100644 index 0000000..9974251 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/car.lua @@ -0,0 +1,116 @@ +hook.Add('Initialize', 'octoinv.shop.car', function() -- let simfphys load + +local function carAtt(price, id) + local att = simfphys.attachments[id] + if not att then return end + + return { + cat = 'car_atts', + price = price, + name = att.name, + item = 'car_att', + data = { + name = att.name, + desc = att.desc, + icon = att.icon, + attmdl = att.mdl, + model = att.mdl, + skin = att.skin, + scale = att.scale, + mass = att.mass, + volume = att.volume, + }, + } +end + +-- +-- CATEGORIES +-- + +octoinv.addShopCat('car', { + name = L.to_car, + icon = 'octoteam/icons/repair.png', + jobs = {'mech'}, +}) +octoinv.addShopCat('car_parts', { + name = L.car_parts, + icon = 'octoteam/icons/car.png', + jobs = {'mech'}, +}) +octoinv.addShopCat('car_rims', { + name = L.discs, + icon = 'octoteam/icons/wheel.png', + jobs = {'mech'}, +}) +octoinv.addShopCat('car_atts', { + name = L.car_atts, + icon = 'octoteam/icons/bust.png', + jobs = {'mech'}, +}) + +-- +-- ITEMS +-- + +octoinv.addShopItem('car_fuel', { cat = 'everyday', price = 1380 }) + +-- kits +octoinv.addShopItem('car_kit', { cat = 'car', price = 8000 }) +octoinv.addShopItem('car_wheel', { cat = 'car', price = 3500 }) +octoinv.addShopItem('car_paint', { cat = 'car', price = 22500 }) +octoinv.addShopItem('tool_susp', { cat = 'car', price = 20000 }) + +for vehID, veh in pairs(carDealer.vehicles) do + if not veh.bodygroups then continue end + for bgID, group in pairs(veh.bodygroups) do + for val, variant in ipairs(group.variants) do + local bgVal = val - 1 + local variantName, variantPrice, variantMass, variantVolume = unpack(variant) + local name = ('%s – %s: %s'):format(veh.name, group.name, variantName) + octoinv.addShopItem(('%s_%d%d'):format(vehID, bgID, bgVal), { + cat = 'car_parts', + price = variantPrice, + name = name, + item = 'car_part', + data = { + name = name, + car = veh.simfphysID, + bgnum = bgID, + bgval = bgVal, + mass = variantMass or 20, + volume = variantVolume or 15, + }, + }) + end + end +end + +local rimID = 1 +for wheelMdl, rim in pairs(simfphys.rims) do + if not rim.price then continue end + octoinv.addShopItem('rims' .. rimID, { + cat = 'car_rims', + price = rim.price, + name = rim.name, + item = 'car_rims', + data = { + name = rim.name, + model = wheelMdl, + mass = 25, + volume = 15, + }, + }) + + rimID = rimID + 1 +end + +octoinv.addShopItem('att1', carAtt(175000, 'spoiler1')) +octoinv.addShopItem('att2', carAtt(195000, 'spoiler2')) +octoinv.addShopItem('att3', carAtt(190000, 'spoiler3')) +octoinv.addShopItem('att4', carAtt(150000, 'spoiler4')) +octoinv.addShopItem('att5', carAtt(200000, 'spoiler5')) +octoinv.addShopItem('att6', carAtt(120000, 'ladder')) +octoinv.addShopItem('att7', carAtt(250000, 'supercharger')) +octoinv.addShopItem('att8', carAtt(120000, 'huladoll')) + +end) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/clothes.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/clothes.lua new file mode 100644 index 0000000..fe00609 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/clothes.lua @@ -0,0 +1,106 @@ +octoinv.addShopCat('clothes', { + name = 'Одежда', + icon = 'octoteam/icons/clothes_tshirt.png', +}) + +octoinv.addShopCat('accessories', { + name = 'Аксессуары', + icon = 'octoteam/icons/clothes_cap.png', +}) + +local clothes = { + -- common + { 19000, 'models/humans/modern/octo/standart1_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "Gotu"', 'Бордовая куртка с белой футболкой, черные джинсы, белые кроссовки с синими полосами по бокам' }, + { 20000, 'models/humans/modern/octo/standart2_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "Edward"', 'Коричневая куртка с черным свитером и белой футболкой, темно-синие джинсы с кожаным ремнем и черными ботинками' }, + { 24500, 'models/humans/modern/octo/standart3_sheet', 'octoteam/icons/clothes_shirt.png', 'Костюм "Gentleman"', 'Синее полупальто с белой футболкой, черные штаны, коричневые ботинки' }, + { 23000, 'models/humans/modern/octo/standart4_sheet', 'octoteam/icons/clothes_shirt.png', 'Костюм "Frant"', 'Черный пиджак с серым свитером, серые джинсы, черные туфли' }, + { 14500, 'models/humans/modern/octo/standart5_sheet', 'octoteam/icons/clothes_jumper.png', 'Зеленая рубашка', 'Зеленая рубашка с белой футболкой с длинным рукавом, серые штаны, коричневые ботинки' }, + { 20000, 'models/humans/modern/octo/standart6_sheet', 'octoteam/icons/clothes_jacket.png', 'Кожанка "Belić"', 'Коричневая кожаная куртка с бежевой футболкой и бело-желтой олимпийкой, полуперчатки, синие штаны, белые кроссовки с синими полосками по бокам' }, + { 20000, 'models/humans/modern/octo/standart7_sheet', 'octoteam/icons/clothes_jacket.png', 'Кожанка "Niko"', 'Черная кожаная куртка с синей рубашкой и бежевой футболкой, черные штаны, черные ботинки' }, + { 16000, 'models/humans/modern/octo/standart8_sheet', 'octoteam/icons/clothes_jumper.png', 'Костюм "Bro3s"', 'Серая кофта с белой футболкой, штаны в раскраске "камо", коричневые ботинки' }, + { 17500, 'models/humans/modern/octo/standart9_sheet', 'octoteam/icons/clothes_jacket.png', 'Голубая клетчатая рубашка', 'Клетчатая рубашка в голубых тонах с серой футболкой с длинными рукавами, бежевые джинсы, черные кроссовки с белым ободом ' }, + { 17500, 'models/humans/modern/octo/standart10_sheet', 'octoteam/icons/clothes_jacket.png', 'Красная клетчатая рубашка', 'Клетчатая красная рубашка с зеленой футболкой с длинными рукавами, голубые джинсы, коричневые ботинки с белым ободом' }, + { 26500, 'models/humans/modern/octo/standart11_sheet', 'octoteam/icons/clothes_jacket.png', 'Байкер "Wolf"', 'Коричневая куртка с белой футболкой, полуперчатки, серые джинсы, коричневые ботинки' }, + { 28500, 'models/humans/modern/octo/standart12_sheet', 'octoteam/icons/clothes_jacket.png', 'Классический байкер', 'Черная кожаная куртка с белыми полосами, полуперчатки, синими джинсами и белыми кроссовками' }, + { 23500, 'models/humans/modern/octo/standart13_sheet', 'octoteam/icons/clothes_jacket.png', 'Сельский байкер', 'Красная клетчатая рубашка с оранжевым жилетом и белой футболкой, серые джинсы, черные ботинки' }, + { 23500, 'models/humans/modern/octo/standart14_sheet', 'octoteam/icons/clothes_jumper.png', 'Степной байкер', 'Зеленая клетчатая рубашка с черно-бежевым жилетом и черной футболкой, черные джинсы, коричневые ботинки' }, + { 20500, 'models/humans/modern/octo/standart15_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "Arlo"', 'Желтая кофта с черным жилетом, голубые джинсы, черные ботинки' }, + { 19500, 'models/humans/modern/octo/standart16_sheet', 'octoteam/icons/clothes_jumper.png', 'Джинсовый костюм', 'Джинсовая куртка с белой футболкой, голубые джинсы, коричневые ботинки' }, + { 22500, 'models/humans/modern/octo/standart17_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "CityLife"', 'Джинсовая куртка, серый худи, синие джинсы, белые кроссовки' }, + { 17500, 'models/humans/modern/octo/standart18_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "Dandy"', 'Клетчатая рубашка в синих тонах с серой футболкой с принтом, голубые джинсы, черные кроссовки' }, + { 26000, 'models/humans/modern/octo/standart19_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "Tommy"', 'Темно-синяя толстовка с белой футболкой, синие джинсы, черные кроссовки' }, + { 17500, 'models/humans/modern/octo/standart20_sheet', 'octoteam/icons/clothes_jacket.png', 'Голубая поло', 'Голубая рубашка поло, черные джинсы, черные ботинки' }, + { 21000, 'models/humans/modern/octo/standart21_sheet', 'octoteam/icons/clothes_jacket.png', 'Костюм "Retro"', 'Бирюзовая олимпийка с зеленой рубашкой поло, голубые джинсы, черные ботинки' }, + { 20500, 'models/humans/modern/octo/standart22_sheet', 'octoteam/icons/clothes_jacket.png', 'Худи "Polo"', 'Серая толстовка с принтом, черные джинсы, коричневые ботинки' }, + { 20000, 'models/humans/modern/octo/standart23_sheet', 'octoteam/icons/clothes_jumper.png', 'Худи "Nike SB"', 'Черный худи с принтом, синие джинсы, черные кроссовки' }, + { 17500, 'models/humans/modern/octo/sport1_sheet', 'octoteam/icons/clothes_jumper.png', 'Худи "In The Hood"', 'Черная кофта с белой футболкой, черные штаны, черные кроссовки' }, + { 20000, 'models/humans/modern/octo/sport2_sheet', 'octoteam/icons/clothes_tshirt.png', 'Зеленый спортивный костюм', 'Черно-зеленая куртка с белой футболкой, черные штаны, черные кроссовки' }, + { 21500, 'models/humans/modern/octo/sport3_sheet', 'octoteam/icons/clothes_tshirt.png', 'Красный спортивный костюм', 'Черно-оранжевая куртка с белой футболкой, голубые джинсы, белые кроссовки' }, + { 15500, 'models/humans/modern/octo/sport4_sheet', 'octoteam/icons/clothes_tshirt.png', 'Костюм "Jordan"', 'Белая кофта с символикой Jordan в центре и черной футболкой, темные брюки, оранжевые кроссовки' }, + { 18500, 'models/humans/modern/octo/sport5_sheet', 'octoteam/icons/clothes_jumper.png', 'Худи "Banana"', 'Голубая кофта с изображением банана в центре и белой футболкой, коричневые штаны, белые кроссовки' }, + { 23500, 'models/humans/modern/octo/sport6_sheet', 'octoteam/icons/clothes_tshirt.png', 'Черный спортивный костюм', 'Черная куртка с белыми полосами на руках и белой футболкой, черные штаны, черные кроссовки' }, + { 22500, 'models/humans/modern/octo/sport7_sheet', 'octoteam/icons/clothes_tshirt.png', 'Синий спортивный костюм', 'Голубая куртка с белой футболкой, голубые штаны, черные кроссовки' }, + { 20500, 'models/humans/modern/octo/sport8_sheet', 'octoteam/icons/clothes_jumper.png', 'Костюм "Marce"', 'Серая толстовка с белой футболкой, черные брюки, черные кроссовки' }, + { 22000, 'models/humans/modern/octo/sport9_sheet', 'octoteam/icons/clothes_tshirt.png', 'Синий костюм Adidas', 'Сине-голубая куртка с белой футболкой, темно-синие штаны, белые кроссовки' }, + { 24000, 'models/humans/modern/octo/sport10_sheet', 'octoteam/icons/clothes_tshirt.png', 'Зеленый костюм Adidas', 'Черно-зеленая куртка с белой футболкой, черные штаны, белые кроссовки' }, + { 19000, 'models/humans/modern/octo/sport11_sheet', 'octoteam/icons/clothes_tshirt.png', 'Красный костюм Adidas', 'Красный спортивный костюм, белые кроссовки' }, + { 20000, 'models/humans/modern/octo/sport12_sheet', 'octoteam/icons/clothes_tshirt.png', 'Костюм "Kappa"', 'Черный спортивный костюм с принтами по бокам, белые кроссовки' }, + { 23500, 'models/humans/modern/octo/sport13_sheet', 'octoteam/icons/clothes_tshirt.png', 'Зеленый костюм Nike', 'Зелено-серая куртка, белая футболка с принтом, зеленые штаны, белые кроссовки' }, + { 25000, 'models/humans/modern/octo/sport14_sheet', 'octoteam/icons/clothes_tshirt.png', 'Черный костюм Nike', 'Черно-серая куртка, черная футболка с принтом, черные штаны, белые кроссовки' }, + { 70000, 'models/humans/modern/octo/suit1_sheet', 'octoteam/icons/clothes_coat.png', 'Костюм "D\'Anglere"', 'Синий деловой костюм в полосочку с белой рубашкой, красным галстуком и черными туфлями' }, + { 60000, 'models/humans/modern/octo/suit2_sheet', 'octoteam/icons/clothes_coat.png', 'Черный деловой костюм', 'Черный деловой костюм с белой рубашкой, черным галстуком и черными туфлями' }, + { 80000, 'models/humans/modern/octo/suit3_sheet', 'octoteam/icons/clothes_coat.png', 'Белый деловой костюм', 'Белый деловой костюм с черной рубашкой, желтым галстуком и черными туфлями' }, + { 65000, 'models/humans/modern/octo/suit4_sheet', 'octoteam/icons/clothes_coat.png', 'Серый деловой костюм', 'Серый деловой костюм с голубой рубашкой, желтым галстуком и серыми туфлями' }, + { 75000, 'models/humans/modern/octo/suit5_sheet', 'octoteam/icons/clothes_coat.png', 'Зеленый деловой костюм', 'Зеленый деловой костюм с белой рубашкой, зеленым галстуком и черными туфлями' }, + { 72000, 'models/humans/modern/octo/suit6_sheet', 'octoteam/icons/clothes_coat.png', 'Голубой деловой костюм', 'Голубой деловой костюм с красной рубашкой, синим галстуком и коричневыми туфлями' }, + { 45000, 'models/humans/modern/octo/suit7_sheet', 'octoteam/icons/clothes_coat.png', 'Черный пиджак', 'Черный пиджак с белой рубашкой, черные брюки и черные туфли' }, + { 47000, 'models/humans/modern/octo/suit8_sheet', 'octoteam/icons/clothes_coat.png', 'Синий пиджак', 'Синий пиджак с белой рубашкой, синие брюки и черные туфли' }, +} + +for i, data in ipairs(clothes) do + local price, mat, icon, name, desc = unpack(data) + octoinv.addShopItem('clothes' .. i, { + cat = 'clothes', + name = name, + desc = desc, + plyMat = mat, + icon = icon, + item = 'clothes_custom', + price = price, + data = { + name = name, + desc = desc, + icon = icon, + mat = mat, + mass = 1.5, + volume = 5, + }, + }) +end + +for class, data in pairs(CFG.masks) do + if not data.price then continue end + octoinv.addShopItem(class, { + cat = 'accessories', + name = data.name, + desc = data.desc, + skin = data.skin, + item = 'h_mask', + icon = data.icon, + price = data.price, + data = { + name = data.name, + icon = data.icon, + model = data.mdl, + mask = class, + mass = 0.3, + volume = 0.3, + }, + }) +end + +local forbiddenSubmats = {} +for _, data in ipairs(clothes) do forbiddenSubmats[data[2]] = true end +hook.Add('submaterial.canUseOnPlayer', 'dbg-clothes', function(ply, target, mat) + if forbiddenSubmats[mat] and not ply:IsAdmin() then return false end +end) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/craft.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/craft.lua new file mode 100644 index 0000000..548cad0 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/craft.lua @@ -0,0 +1,96 @@ +-- +-- CATEGORIES +-- + +octoinv.addShopCat('tool', { + name = L.tools, + icon = 'octoteam/icons/repair.png', +}) + +octoinv.addShopCat('build', { + name = L.build, + icon = 'octoteam/icons/cog.png', +}) + +octoinv.addShopCat('blueprint', { + name = L.blueprint, + icon = 'octoteam/icons/blueprint.png', +}) + +octoinv.addShopCat('parts', { + name = L.parts, + icon = 'octoteam/icons/cogs.png', +}) + +octoinv.addShopCat('prod', { + name = L.prod, + icon = 'octoteam/icons/robot_arm.png', +}) + +-- +-- ITEMS +-- + +octoinv.addShopItem('tool_screwer', { cat = 'tool', price = 350 }) +octoinv.addShopItem('tool_hammer', { cat = 'tool', price = 250 }) +octoinv.addShopItem('tool_wrench', { cat = 'tool', price = 500 }) +octoinv.addShopItem('tool_solder', { cat = 'tool', price = 850 }) +octoinv.addShopItem('tool_ruler', { cat = 'tool', price = 50 }) +octoinv.addShopItem('tool_caliper', { cat = 'tool', price = 450 }) +octoinv.addShopItem('tool_pump', { cat = 'tool', price = 800 }) +octoinv.addShopItem('tool_knife_stanley', { cat = 'tool', price = 100 }) +octoinv.addShopItem('tool_drill', { cat = 'tool', price = 1500 }) +octoinv.addShopItem('tool_saw', { cat = 'tool', price = 350 }) +octoinv.addShopItem('tool_craft', { cat = 'tool', price = 1000 }) +octoinv.addShopItem('tool_pen', { cat = 'everyday', price = 45 }) +octoinv.addShopItem('tool_pencil', { cat = 'everyday', price = 10 }) + +octoinv.addShopItem('craft_screw', { cat = 'build', price = 40 }) +octoinv.addShopItem('craft_screw2', { cat = 'build', price = 50 }) +octoinv.addShopItem('craft_screwnut', { cat = 'build', price = 40 }) +octoinv.addShopItem('craft_nail', { cat = 'build', price = 30 }) +octoinv.addShopItem('craft_stick', { cat = 'build', price = 20 }) +octoinv.addShopItem('craft_plank', { cat = 'build', price = 100 }) +octoinv.addShopItem('craft_pallet', { cat = 'build', price = 150 }) +octoinv.addShopItem('craft_sheet', { cat = 'build', price = 800 }) +octoinv.addShopItem('craft_spring', { cat = 'build', price = 90 }) +octoinv.addShopItem('craft_pulley', { cat = 'build', price = 150 }) +octoinv.addShopItem('craft_cable', { cat = 'build', price = 50 }) +octoinv.addShopItem('craft_plug', { cat = 'build', price = 45 }) +octoinv.addShopItem('craft_socket', { cat = 'build', price = 60 }) +octoinv.addShopItem('craft_piston', { cat = 'build', price = 180 }) +octoinv.addShopItem('craft_engine', { cat = 'build', price = 500 }) +octoinv.addShopItem('craft_bulb', { cat = 'build', price = 45 }) +octoinv.addShopItem('craft_chip1', { cat = 'build', price = 100 }) +octoinv.addShopItem('craft_chip2', { cat = 'build', price = 450 }) +octoinv.addShopItem('craft_cnc', { cat = 'build', price = 300 }) +octoinv.addShopItem('craft_relay', { cat = 'build', price = 30 }) +octoinv.addShopItem('craft_resistor', { cat = 'build', price = 35 }) +octoinv.addShopItem('craft_transistor', { cat = 'build', price = 30 }) +octoinv.addShopItem('craft_solar', { cat = 'build', price = 800 }) +octoinv.addShopItem('craft_gauge', { cat = 'build', price = 450 }) +octoinv.addShopItem('craft_scotch', { cat = 'build', price = 50 }) +octoinv.addShopItem('craft_glue', { cat = 'build', price = 35 }) +octoinv.addShopItem('craft_paper', { cat = 'build', price = 8 }) +octoinv.addShopItem('craft_ink', { cat = 'everyday', price = 650 }) +octoinv.addShopItem('ing_water', { cat = 'everyday', price = 30 }) +octoinv.addShopItem('craft_battery', { cat = 'everyday', price = 400 }) +octoinv.addShopItem('craft_battery2', { cat = 'everyday', price = 50 }) + +octoinv.addShopItem('craft_fuel', { cat = 'prod', price = 50 }) +octoinv.addShopItem('craft_gas', { cat = 'prod', price = 400 }) +octoinv.addShopItem('saltpeter', { cat = 'prod', price = 2000 }) + +octoinv.addShopItem('bp_stove', { cat = 'blueprint', price = 150 }) +octoinv.addShopItem('bp_fridge', { cat = 'blueprint', price = 150 }) +octoinv.addShopItem('bp_refinery', { cat = 'blueprint', price = 150 }) +octoinv.addShopItem('bp_smelter', { cat = 'blueprint', price = 150 }) +octoinv.addShopItem('bp_machine', { cat = 'blueprint', price = 150 }) +octoinv.addShopItem('bp_workbench', { cat = 'blueprint', price = 150 }) + +octoinv.addShopItem('bpd_stove', { cat = 'parts', price = 6500 }) +octoinv.addShopItem('bpd_fridge', { cat = 'parts', price = 2500 }) +octoinv.addShopItem('bpd_refinery', { cat = 'parts', price = 7500 }) +octoinv.addShopItem('bpd_smelter', { cat = 'parts', price = 6000 }) +octoinv.addShopItem('bpd_machine', { cat = 'parts', price = 6500 }) +octoinv.addShopItem('bpd_workbench', { cat = 'parts', price = 3500 }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/drug.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/drug.lua new file mode 100644 index 0000000..551498a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/drug.lua @@ -0,0 +1,26 @@ +-- +-- CATEGORIES +-- + +octoinv.addShopCat('pharm', { + name = L.pharm, + icon = 'octoteam/icons/drug2.png', + jobs = {'pharm', 'pharm2', 'paramedic'}, +}) + +-- +-- ITEMS +-- + +octoinv.addShopItem('drug_vitalex', { cat = 'pharm', price = 520 }) +octoinv.addShopItem('drug_painkiller', { cat = 'pharm', price = 250 }) +octoinv.addShopItem('drug_relaxant', { cat = 'pharm', price = 450 }) +octoinv.addShopItem('drug_vampire', { cat = 'pharm', price = 750, jobs = {'pharm2'}}) +octoinv.addShopItem('drug_dextradose', { cat = 'pharm', price = 600 }) +octoinv.addShopItem('drug_roids', { cat = 'pharm', price = 450, jobs = {'pharm', 'pharm2'}}) +octoinv.addShopItem('drug_bouncer', { cat = 'pharm', price = 1350, jobs = {'pharm2'}}) +octoinv.addShopItem('drug_antitoxin', { cat = 'pharm', price = 150 }) +octoinv.addShopItem('drug_weed', { cat = 'pharm', price = 420 }) +octoinv.addShopItem('drug_pingaz', { cat = 'pharm', price = 350 , jobs = {'pharm', 'pharm2'}}) +octoinv.addShopItem('drug_preserver', { cat = 'pharm', price = 3300, jobs = {'pharm2', 'paramedic'}}) +-- octoinv.addShopItem('drug_meth', { cat = 'pharm', price = 1000 }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/food.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/food.lua new file mode 100644 index 0000000..b6b88cc --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/food.lua @@ -0,0 +1,241 @@ +-- +-- CATEGORIES +-- + +octoinv.addShopCat('food', { + name = L.food, + icon = 'octoteam/icons/food_meal2.png', +}) + +octoinv.addShopCat('dish', { + name = L.dish, + icon = 'octoteam/icons/coin_stacks.png', + jobs = {'cook'}, +}) +octoinv.addShopCat('ings', { + name = L.ings, + icon = 'octoteam/icons/food_ingredients.png', + jobs = {'cook'}, +}) + +-- +-- ITEMS +-- + +-- tools +octoinv.addShopItem('tool_foodcont', { cat = 'dish', price = 20 }) +octoinv.addShopItem('tool_pan', { cat = 'dish', price = 1000 }) +octoinv.addShopItem('tool_pot', { cat = 'dish', price = 1500 }) +octoinv.addShopItem('tool_oventray', { cat = 'dish', price = 1800 }) +octoinv.addShopItem('tool_scoop', { cat = 'dish', price = 750 }) +octoinv.addShopItem('tool_pastrybag', { cat = 'dish', price = 450 }) +octoinv.addShopItem('tool_shaker', { cat = 'dish', price = 1500 }) +octoinv.addShopItem('tool_teapot', { cat = 'dish', price = 800 }) +octoinv.addShopItem('tool_coffeepot', { cat = 'dish', price = 850 }) + +-- ingredients +octoinv.addShopItem('ing_egg', { cat = 'ings', price = 20 }) +octoinv.addShopItem('ing_oil', { cat = 'ings', price = 10 }) +octoinv.addShopItem('ing_salt', { cat = 'ings', price = 10 }) +octoinv.addShopItem('ing_potato', { cat = 'ings', price = 15 }) +octoinv.addShopItem('ing_cheese', { cat = 'ings', price = 35 }) +octoinv.addShopItem('ing_flour', { cat = 'ings', price = 15 }) +octoinv.addShopItem('ing_onion', { cat = 'ings', price = 10 }) +octoinv.addShopItem('ing_milk', { cat = 'ings', price = 50 }) +octoinv.addShopItem('ing_cream', { cat = 'ings', price = 80 }) +octoinv.addShopItem('ing_meat', { cat = 'ings', price = 100 }) +octoinv.addShopItem('ing_carrot', { cat = 'ings', price = 10 }) +octoinv.addShopItem('ing_tomato', { cat = 'ings', price = 12 }) +octoinv.addShopItem('ing_sugar', { cat = 'ings', price = 5 }) +octoinv.addShopItem('ing_mushroom', { cat = 'ings', price = 15 }) +octoinv.addShopItem('ing_sausage', { cat = 'ings', price = 80 }) +octoinv.addShopItem('ing_sausage2', { cat = 'ings', price = 55 }) +octoinv.addShopItem('ing_bread', { cat = 'ings', price = 25 }) +octoinv.addShopItem('ing_honey', { cat = 'ings', price = 100 }) +octoinv.addShopItem('ing_spice', { cat = 'ings', price = 10 }) +octoinv.addShopItem('ing_kvas', { cat = 'ings', price = 80 }) +octoinv.addShopItem('ing_cucumber', { cat = 'ings', price = 15 }) +octoinv.addShopItem('ing_nut', { cat = 'ings', price = 100 }) +octoinv.addShopItem('ing_apple', { cat = 'ings', price = 20 }) +octoinv.addShopItem('ing_corn', { cat = 'ings', price = 35 }) +octoinv.addShopItem('ing_celery', { cat = 'ings', price = 25 }) +octoinv.addShopItem('ing_bacon', { cat = 'ings', price = 70 }) +octoinv.addShopItem('ing_olive', { cat = 'ings', price = 55 }) +octoinv.addShopItem('ing_chili', { cat = 'ings', price = 50 }) +octoinv.addShopItem('ing_broccoli', { cat = 'ings', price = 45 }) +octoinv.addShopItem('ing_pineapple', { cat = 'ings', price = 120 }) +octoinv.addShopItem('ing_pumpkin', { cat = 'ings', price = 75 }) +--octoinv.addShopItem('ing_fish', { cat = 'ings', price = 130 }) +octoinv.addShopItem('ing_strawberry', { cat = 'ings', price = 80 }) +octoinv.addShopItem('ing_watermelon', { cat = 'ings', price = 60 }) +octoinv.addShopItem('ing_melon', { cat = 'ings', price = 70 }) +octoinv.addShopItem('ing_soy', { cat = 'ings', price = 25 }) +octoinv.addShopItem('ing_peas', { cat = 'ings', price = 30 }) +octoinv.addShopItem('ing_lettuce', { cat = 'ings', price = 50 }) +octoinv.addShopItem('ing_cabbage', { cat = 'ings', price = 20 }) +octoinv.addShopItem('ing_beet', { cat = 'ings', price = 15 }) +octoinv.addShopItem('ing_redish', { cat = 'ings', price = 15 }) +octoinv.addShopItem('ing_eggplant', { cat = 'ings', price = 45 }) +octoinv.addShopItem('ing_prawn', { cat = 'ings', price = 150 }) +octoinv.addShopItem('ing_avocado', { cat = 'ings', price = 60 }) +octoinv.addShopItem('ing_pita', { cat = 'ings', price = 40 }) +octoinv.addShopItem('ing_cocoa', { cat = 'ings', price = 30 }) +octoinv.addShopItem('ing_choco', { cat = 'ings', price = 40 }) +octoinv.addShopItem('ing_banana', { cat = 'ings', price = 40 }) +octoinv.addShopItem('ing_orange', { cat = 'ings', price = 35 }) +octoinv.addShopItem('ing_lemon', { cat = 'ings', price = 30 }) +octoinv.addShopItem('ing_rice', { cat = 'ings', price = 35 }) +octoinv.addShopItem('ing_sauce', { cat = 'ings', price = 40 }) +octoinv.addShopItem('ing_pasta', { cat = 'ings', price = 75 }) +octoinv.addShopItem('ing_coffee', { cat = 'ings', price = 50 }) +octoinv.addShopItem('ing_tea', { cat = 'ings', price = 45 }) + +-- crafted ingredients +octoinv.addShopItem('ing_pizza_base', { cat = 'ings', price = 200 }) +octoinv.addShopItem('ing_dough1', { cat = 'ings', price = 230 }) +octoinv.addShopItem('ing_dough2', { cat = 'ings', price = 120 }) +octoinv.addShopItem('ing_icecream', { cat = 'ings', price = 340 }) +octoinv.addShopItem('ing_meatball', { cat = 'ings', price = 230 }) +octoinv.addShopItem('ing_roastmeat', { cat = 'ings', price = 240 }) +octoinv.addShopItem('ing_potato2', { cat = 'ings', price = 120 }) +octoinv.addShopItem('ing_potatomash', { cat = 'ings', price = 400 }) +octoinv.addShopItem('ing_soup', { cat = 'ings', price = 135 }) +octoinv.addShopItem('ing_dough3', { cat = 'ings', price = 280 }) + +-- ready to eat +octoinv.addShopItem('food_chocolate', { + cat = 'food', price = 150, + name = L.chocolate, + icon = 'octoteam/icons/food_choco.png', + item = 'food', + data = { + name = L.chocolate, + icon = 'octoteam/icons/food_choco.png', + energy = 15, + maxenergy = 50, + trash = true, + }, +}) + +octoinv.addShopItem('food_chips', { + cat = 'food', price = 220, + name = L.chips, + icon = 'octoteam/icons/food_chips.png', + item = 'food', + data = { + name = L.chips, + icon = 'octoteam/icons/food_chips.png', + energy = 20, + maxenergy = 50, + trash = true, + }, +}) + +octoinv.addShopItem('food_soda', { + cat = 'food', price = 120, + name = L.food_soda, + icon = 'octoteam/icons/food_soda.png', + item = 'food', + data = { + name = L.food_soda, + icon = 'octoteam/icons/food_soda.png', + energy = 15, + maxenergy = 30, + drink = true, + trash = true, + }, +}) + +octoinv.addShopItem('food_cream', { + cat = 'food', price = 180, + name = L.cream, + icon = 'octoteam/icons/food_yogurt.png', + item = 'food', + data = { + name = L.cream, + icon = 'octoteam/icons/food_yogurt.png', + energy = 18, + maxenergy = 50, + trash = true, + }, +}) + +octoinv.addShopItem('food_crackers', { + cat = 'food', price = 135, + name = L.crackers, + icon = 'octoteam/icons/food_brezel.png', + item = 'food', + data = { + name = L.crackers, + icon = 'octoteam/icons/food_brezel.png', + energy = 15, + maxenergy = 40, + trash = true, + }, +}) + +octoinv.addShopItem('food_burger', { + cat = 'food', price = 650, + jobs = {'cook'}, + name = L.burger, + item = 'food', + data = { + name = L.burger, + energy = 35, + }, +}) + +octoinv.addShopItem('food_meatcake', { + cat = 'food', price = 450, + jobs = {'cook'}, + name = L.meatcake, + item = 'food', + data = { + name = L.meatcake, + energy = 25, + }, +}) + +octoinv.addShopItem('food_applepie', { + cat = 'food', price = 900, + jobs = {'cook'}, + name = L.applepie, + item = 'food', + data = { + name = L.applepie, + energy = 60, + }, +}) + +octoinv.addShopItem('food_pancake', { + cat = 'food', price = 600, + jobs = {'cook'}, + name = L.pancake, + item = 'food', + data = { + name = L.pancake, + energy = 40, + }, +}) + +octoinv.addShopItem('food_sandwitch', { + cat = 'food', price = 250, + jobs = {'cook'}, + name = L.sandwitch, + item = 'food', + data = { + name = L.sandwitch, + energy = 15, + }, +}) + +octoinv.addShopItem('food_wrap', { + cat = 'food', price = 850, + jobs = {'cook'}, + name = L.wrap, + item = 'food', + data = { + name = L.wrap, + energy = 60, + }, +}) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/gun.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/gun.lua new file mode 100644 index 0000000..700b75a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/gun.lua @@ -0,0 +1,50 @@ +-- +-- CATEGORIES +-- + +octoinv.addShopCat('machinebp', { + name = L.machinebp, + icon = 'octoteam/icons/microsd.png', + jobs = {'gun', 'gun2'}, +}) + +-- octoinv.addShopCat('resources', { +-- name = L.resources, +-- icon = 'octoteam/icons/ore_iron.png', +-- jobs = {'gun', 'gun2'}, +-- }) + +-- octoinv.addShopCat('ammo', { +-- name = L.ammo, +-- icon = 'octoteam/icons/gun_bullet2.png', +-- jobs = {'gun', 'gun2'}, +-- }) + +-- +-- ITEMS +-- + +-- octoinv.addShopItem('ore_iron', { cat = 'resources', price = 500 }) +-- octoinv.addShopItem('ore_steel', { cat = 'resources', price = 2000 }) +-- octoinv.addShopItem('ore_silver', { cat = 'resources', price = 3500 }) +-- octoinv.addShopItem('ore_bronze', { cat = 'resources', price = 2000 }) +-- octoinv.addShopItem('ore_gold', { cat = 'resources', price = 5500 }) +-- octoinv.addShopItem('ore_copper', { cat = 'resources', price = 1500 }) +-- octoinv.addShopItem('craft_glass', { cat = 'resources', price = 50 }) + +octoinv.addShopItem('bp_shutter', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_barrel', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_grip', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_aim', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_drummer', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_trigger', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_stopper', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_clip', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_screw', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_screw2', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_screwnut', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_nail', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_spring', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_pulley', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_piston', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('bp_bulb', { cat = 'machinebp', price = 15 }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/other.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/other.lua new file mode 100644 index 0000000..a77fd70 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/other.lua @@ -0,0 +1,335 @@ +octoinv.addShopCat('_other', { + name = L.other, + icon = 'octoteam/icons/round_help.png', +}) + +octoinv.addShopCat('security', { + name = 'Безопасность', + icon = 'octoteam/icons/lock_password.png', +}) + +octoinv.addShopCat('conts', { + name = L.conts, + icon = 'octoteam/icons/box3.png', +}) + +octoinv.addShopCat('doors', { + name = 'Двери и детали', + icon = 'octoteam/icons/door.png', +}) + +octoinv.addShopCat('electronic', { + name = L.electronic, + icon = 'octoteam/icons/electronics.png', +}) + +octoinv.addShopCat('everyday', { + name = L.everyday, + icon = 'octoteam/icons/shop.png', +}) + +------------------------------------------------ +-- +-- EVERYONE +-- +------------------------------------------------ + +-- +-- SWEPS +-- +octoinv.addShopItem('phone', { cat = 'electronic', price = 10000 }) +octoinv.addShopItem('phone_st', { cat = 'electronic', price = 7500 }) +octoinv.addShopItem('player', { cat = 'electronic', price = 12000 }) + +octoinv.addShopItem('weapon_flashlight', { + cat = 'electronic', + name = L.flashlight, + item = 'weapon', + icon = 'octoteam/icons/flashlight.png', + price = 750, + data = { + model = 'models/weapons/w_flashlight_zm.mdl', + name = L.flashlight, + icon = 'octoteam/icons/flashlight.png', + wepclass = 'weapon_flashlight', + mass = 0.5, + volume = 0.5, + ammoadd = 0, + clip1 = 0, + clip2 = 0, + }, +}) + +octoinv.addShopItem('gmod_camera', { + cat = 'electronic', + name = L.camera, + item = 'weapon', + icon = 'octoteam/icons/camera.png', + price = 1500, + data = { + model = 'models/maxofs2d/camera.mdl', + name = L.camera, + icon = 'octoteam/icons/camera.png', + wepclass = 'gmod_camera', + mass = 1.5, + volume = 1, + ammoadd = 0, + clip1 = 0, + clip2 = 0, + }, +}) + +octoinv.addShopItem('weapon_cuff_rope', { + cat = 'everyday', + name = L.cuff_rope, + item = 'weapon', + icon = 'octoteam/icons/rope.png', + price = 3800, + data = { + model = 'models/props_junk/cardboard_box004a.mdl', + name = L.cuff_rope, + icon = 'octoteam/icons/rope.png', + wepclass = 'weapon_cuff_rope', + mass = 0.8, + volume = 1, + ammoadd = 0, + clip1 = 0, + clip2 = 0, + }, +}) + +octoinv.addShopItem('guitar', { + cat = 'everyday', + name = L.guitar, + item = 'weapon', + icon = 'octoteam/icons/guitar.png', + price = 7500, + data = { + name = L.guitar, + icon = 'octoteam/icons/guitar.png', + model = 'models/custom/guitar/m_d_45.mdl', + wepclass = 'guitar', + mass = 2, + volume = 5, + ammoadd = 0, + clip1 = 0, + clip2 = 0, + }, + check = function(ply) return ply:GetNetVar('os_dobro') end +}) + +-- +-- ENTITIES +-- +octoinv.addShopItem('ent_dbg_cigarette', { cat = 'everyday', price = 200 }) +octoinv.addShopItem('drug_booze', { cat = 'everyday', price = 80 }) +octoinv.addShopItem('drug_booze2', { cat = 'everyday', price = 135 }) +octoinv.addShopItem('soccer', { cat = 'everyday', price = 450 }) + +-- +-- OTHER +-- +octoinv.addShopItem('bandage', { cat = 'everyday', price = 55 }) + +octoinv.addShopItem('bpd_crate', { cat = 'conts', price = 650, + item = 'cont', + name = L.bpd_crate, + desc = L.boxdesc, + data = { + name = L.box, + volume = 10, + mass = 8, + contdata = { + model = 'models/items/item_item_crate.mdl', + conts = { + box = { name = L.box, volume = 60 }, + } + } + } +}) + +octoinv.addShopItem('bpd_cratel', { cat = 'conts', price = 1000, + item = 'cont', + name = L.bpd_cratel, + desc = L.boxdesc, + data = { + name = L.cratel, + volume = 20, + mass = 16, + contdata = { + model = 'models/props_junk/wood_crate001a.mdl', + conts = { + box = { name = L.box, volume = 120 }, + } + } + } +}) + +octoinv.addShopItem('bpd_cratexl', { cat = 'conts', price = 1500, + item = 'cont', + name = L.bpd_cratexl, + desc = L.boxdesc, + data = { + name = L.cratexl, + volume = 30, + mass = 24, + contdata = { + model = 'models/props_junk/wood_crate002a.mdl', + conts = { + box = { name = L.box, volume = 200 }, + } + } + } +}) + +octoinv.addShopItem('bpd_cardbox', { cat = 'conts', price = 150, + item = 'cont', + name = L.bpd_cardbox, + desc = L.boxdesc, + data = { + name = L.cardbox, + volume = 0.5, + mass = 0.5, + contdata = { + model = 'models/props_junk/cardboard_box004a.mdl', + conts = { + box = { name = L.box2, volume = 5 }, + } + } + } +}) + +octoinv.addShopItem('bpd_cardboxl', { cat = 'conts', price = 350, + item = 'cont', + name = L.bpd_cardboxl, + desc = L.boxdesc, + data = { + name = L.cardboxl, + volume = 2, + mass = 2, + contdata = { + model = 'models/props_junk/cardboard_box003a.mdl', + conts = { + box = { name = L.box2, volume = 20 }, + } + } + } +}) + +-- +-- LOCKS +-- +octoinv.addShopItem('key', { cat = 'security', price = 250 }) + +octoinv.addShopItem('lock_cont5', { + cat = 'security', price = 750, + name = L.lock_cont5, + item = 'lock_cont', + data = { + name = L.lock_cont5, + num = 5, + }, +}) + +octoinv.addShopItem('lock_cont6', { + cat = 'security', price = 3000, + name = L.lock_cont6, + item = 'lock_cont', + data = { + name = L.lock_cont6, + num = 6, + }, +}) + +octoinv.addShopItem('lock_cont7', { + cat = 'security', price = 4500, + name = L.lock_cont7, + item = 'lock_cont', + data = { + name = L.lock_cont7, + num = 7, + }, +}) + +octoinv.addShopItem('lock_cont8', { + cat = 'security', price = 7500, + name = L.lock_cont8, + item = 'lock_cont', + data = { + name = L.lock_cont8, + num = 8, + }, +}) + +octoinv.addShopItem('lock_cont10', { + cat = 'security', price = 10000, + name = L.lock_cont10, + item = 'lock_cont', + data = { + name = L.lock_cont10, + num = 10, + }, +}) + +------------------------------------------------ +-- +-- DOORS AND DETAILS +-- +------------------------------------------------ + +local doorInfo = { + {L.item_door0, 1250}, + {L.item_door1, 500}, + {L.item_door2, 1650}, + {L.item_door3, 550}, + {L.item_door4, 1400}, + {L.item_door5, 1450}, + {L.item_door6, 1000}, + {L.item_door7, 1200}, + {L.item_door8, 750}, + {L.item_door9, 650}, + {L.item_door10, 750}, + {L.item_door11, 1725}, + {L.item_door12, 3750}, + {L.item_door13, 1200}, +} +for i, door_info in ipairs(doorInfo) do + octoinv.addShopItem('door' .. i-1, { + cat = 'doors', price = door_info[2], + name = door_info[1], + item = 'door', + data = { + name = door_info[1], + skin = i - 1, + }, + }) +end + +octoinv.addShopItem('door_handle1', { + cat = 'doors', price = 150, + name = L.item_door_handle1, + item = 'door_handle', + data = { + name = L.item_door_handle1, + handle = 1, + }, +}) + +octoinv.addShopItem('door_handle2', { + cat = 'doors', price = 375, + name = L.item_door_handle2, + item = 'door_handle', + data = { + name = L.item_door_handle2, + handle = 2, + }, +}) + + +------------------------------------------------ +-- +-- NEW YEAR STUFF +-- +------------------------------------------------ + +octoinv.addShopItem('clothes', { cat = 'everyday', price = 750 }) diff --git a/garrysmod/addons/_config/lua/config/octoinv/shop/xmas.lua b/garrysmod/addons/_config/lua/config/octoinv/shop/xmas.lua new file mode 100644 index 0000000..db5b9b9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoinv/shop/xmas.lua @@ -0,0 +1,154 @@ +local clothes = { + { 15000, 'models/humans/modern/octo/winter1_sheet', 'octoteam/icons/clothes_warm.png', 'Зеленый свитер с оленями', 'Теплый мужской зеленый свитер с красными вставками, на котором красуются олени и рождественские узоры', 'male' }, + { 15000, 'models/humans/modern/octo/winter2_sheet', 'octoteam/icons/clothes_warm.png', 'Коричневый свитер с оленями', 'Теплый мужской коричневый свитер, на котором красуются олени и рождественские узоры', 'male' }, + { 17000, 'models/humans/modern/octo/winter3_sheet', 'octoteam/icons/clothes_warm.png', 'Белый свитер с оленями', 'Теплый мужской белый свитер, на котором красуются олени и рождественские узоры', 'male' }, + { 26000, 'models/humans/modern/octo/winter4_sheet', 'octoteam/icons/clothes_warm.png', 'Черный пуховик', 'Черный мужской пуховик и бело-голубой теплый свитер с оленями и рождественскими узорами', 'male' }, + { 26000, 'models/humans/modern/octo/winter5_sheet', 'octoteam/icons/clothes_warm.png', 'Черно-зеленый пуховик', 'Черно-зеленый мужской пуховик и черный теплый свитер с оленями и рождественскими узорами', 'male' }, + { 19000, 'models/humans/modern/octo/winter6_sheet', 'octoteam/icons/clothes_warm.png', 'Красный свитер с оленями', 'Теплый мужской красный свитер, на котором красуются олени и рождественские узоры', 'male' }, + { 24000, 'models/humans/modern/octo/winter7_sheet', 'octoteam/icons/clothes_warm.png', 'Белый пуховик', 'Теплый мужской белый пуховик и голубые джинсы, белые кроссовки', 'male' }, + { 15000, 'models/humans/modern/octo/winter1_sheet_woman', 'octoteam/icons/clothes_warm.png', 'Красный свитер', 'Теплый женский свитер красного цвета с рождественскими узорами', 'female' }, + { 16500, 'models/humans/modern/octo/winter2_sheet_woman', 'octoteam/icons/clothes_warm.png', 'Белый свитер', 'Теплый женский свитер белого цвета с рождественскими узорами', 'female' }, + { 18000, 'models/humans/modern/octo/winter3_sheet_woman', 'octoteam/icons/clothes_warm.png', 'Зеленый свитер', 'Теплый женский свитер зеленого цвета с милым оленем', 'female' }, + { 15000, 'models/humans/modern/octo/winter4_sheet_woman', 'octoteam/icons/clothes_warm.png', 'Розовый свитер', 'Теплый женский свитер розового цвета с оленями и рождественскими узорами', 'female' }, + { 26000, 'models/humans/modern/octo/winter5_sheet_woman', 'octoteam/icons/clothes_warm.png', 'Черный женский пуховик', 'Черный женский пуховик и белый теплый свитер, синие джинсы и черные ботинки', 'female' }, +} + +octoinv.addShopCat('xmas', { + name = 'Праздник', + icon = 'octoteam/icons/confetti.png', +}) + +for i, data in ipairs(clothes) do + local price, mat, icon, name, desc, gender = unpack(data) + octoinv.addShopItem('xmas_clothes' .. i, { + cat = 'xmas', + name = name, + desc = desc, + plyMat = mat, + icon = icon, + item = 'clothes_custom', + price = price, + data = { + name = name, + desc = desc, + icon = icon, + mat = mat, + warm = true, + gender = gender, + mass = 1.5, + volume = 5, + }, + }) +end + +octoinv.addShopItem('xmas_hat', { + cat = 'xmas', + name = 'Рождественская шапка', + desc = 'Главный аксессуар праздника для каждого', + item = 'h_mask', + icon = octolib.icons.color('xmas_hat'), + price = 10000, + data = { + name = 'Рождественская шапка', + icon = octolib.icons.color('xmas_hat'), + model = 'models/cloud/kn_santahat.mdl', + mask = 'santa', + mass = 0.3, + volume = 0.3, + }, +}) + +octoinv.addShopItem('petards', { cat = 'xmas', price = 350, + item = 'throwable', + name = 'Петарды', + desc = '5 штук. Совсем безобидные! Ну, только если не кидать их прямо в лицо', + icon = 'octoteam/icons/dynamite.png', + data = { + name = 'Петарды ({usesLeft})', + desc = 'Это еще что за шутки?!', + mass = 2.5, + volume = 2, + gc = 'ent_dbg_petard', + } +}) +octoinv.addShopItem('fireworks', { cat = 'xmas', price = 1000 }) + +octoinv.addShopItem('bpd_presents', { cat = 'xmas', price = 500, + item = 'cont', + name = L.bpd_presents, + desc = L.boxdesc, + icon = 'octoteam/icons/gift.png', + data = { + name = L.present, + icon = 'octoteam/icons/gift.png', + volume = 1, + mass = 1, + contdata = { + model = 'models/brewstersmodels/christmas/present1_large.mdl', + skin = { 0, 5 }, + conts = { + box = { name = L.gift, volume = 5 }, + } + } + } +}) + +octoinv.addShopItem('bpd_present1', { cat = 'xmas', price = 750, + item = 'cont', + name = L.bpd_present1, + desc = L.boxdesc, + icon = 'octoteam/icons/gift.png', + data = { + name = L.present1, + icon = 'octoteam/icons/gift.png', + volume = 1, + mass = 1, + contdata = { + model = 'models/brewstersmodels/christmas/present2_large.mdl', + skin = { 0, 5 }, + conts = { + box = { name = L.gift, volume = 10 }, + } + } + } +}) + +octoinv.addShopItem('bpd_present2', { cat = 'xmas', price = 750, + item = 'cont', + name = L.bpd_present2, + desc = L.boxdesc, + icon = 'octoteam/icons/gift.png', + data = { + name = L.present2, + icon = 'octoteam/icons/gift.png', + volume = 1.5, + mass = 1.5, + contdata = { + model = 'models/brewstersmodels/christmas/present3_large.mdl', + skin = { 0, 5 }, + conts = { + box = { name = L.gift, volume = 10 }, + } + } + } +}) + +octoinv.addShopItem('bpd_presentl', { cat = 'xmas', price = 1250, + item = 'cont', + name = L.bpd_presentl, + desc = L.boxdesc, + icon = 'octoteam/icons/gift.png', + data = { + name = L.presentl, + icon = 'octoteam/icons/gift.png', + volume = 2, + mass = 2, + contdata = { + model = 'models/brewstersmodels/christmas/present4_large.mdl', + skin = { 0, 5 }, + conts = { + box = { name = L.gift, volume = 20 }, + } + } + } +}) diff --git a/garrysmod/addons/_config/lua/config/octolib-anim/base.lua b/garrysmod/addons/_config/lua/config/octolib-anim/base.lua new file mode 100644 index 0000000..4ee1f56 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-anim/base.lua @@ -0,0 +1,108 @@ +octolib.registerAnimationCategory('simple', { + name = 'Жесты и действия', + order = 1, + type = 'act', + anims = { + { 'Одобрить', ACT_GMOD_GESTURE_AGREE }, + { 'Подозвать', ACT_GMOD_GESTURE_BECON }, + { 'Поклониться', ACT_GMOD_GESTURE_BOW }, + { 'Пригрозить пальцем', ACT_GMOD_GESTURE_DISAGREE }, + { 'Воинское приветствие', ACT_GMOD_TAUNT_SALUTE }, + { 'Помахать рукой', ACT_GMOD_GESTURE_WAVE }, + { 'Сигнал: вперед', ACT_SIGNAL_FORWARD }, + { 'Сигнал: группировка', ACT_SIGNAL_GROUP }, + { 'Сигнал: стоп', ACT_SIGNAL_HALT }, + { 'Замахнуться рукой', ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST }, + { 'Махнуть перед собой', ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND }, + { 'Положить предмет', ACT_GMOD_GESTURE_ITEM_PLACE }, + { 'Дать предмет', ACT_GMOD_GESTURE_ITEM_GIVE }, + { 'Выбросить предмет', ACT_GMOD_GESTURE_ITEM_DROP }, + { 'Кинуть предмет', ACT_GMOD_GESTURE_ITEM_THROW }, + }, +}) + +octolib.registerAnimationCategory('freezed', { + name = 'Анимации на месте', + order = 2, + type = 'act', + freeze = true, + donate = true, + anims = { + { 'Насмехаться', ACT_GMOD_TAUNT_LAUGH }, + { 'Радоваться', ACT_GMOD_TAUNT_CHEER }, + { 'Устрашить', ACT_GMOD_TAUNT_PERSISTENCE }, + { 'Танцевать', ACT_GMOD_TAUNT_DANCE }, + { 'Роботанец', ACT_GMOD_TAUNT_ROBOT }, + { 'Горячий танец', ACT_GMOD_TAUNT_MUSCLE }, + }, +}) + +octolib.registerAnimationCategory('stand', { + name = 'Стойка', + order = 3, + type = 'seq', + stand = true, + anims = { + { 'Руки вверх', 'idle_all_01', bones = { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(30, 0, 90), + ['ValveBiped.Bip01_L_UpperArm'] = Angle(-30, 0, -90), + ['ValveBiped.Bip01_R_ForeArm'] = Angle(0, -130, 0), + ['ValveBiped.Bip01_L_ForeArm'] = Angle(0, -120, 20), + }}, + { 'Руки за голову', 'idle_all_01', bones = { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(110, -15, 90), + ['ValveBiped.Bip01_L_UpperArm'] = Angle(-115, 10, -110), + ['ValveBiped.Bip01_R_ForeArm'] = Angle(-5, -120, 0), + ['ValveBiped.Bip01_L_ForeArm'] = Angle(2, -120, 20), + ['ValveBiped.Bip01_Head1'] = Angle(0, -20, 0), + }}, + { 'Показать пальцем', 'idle_all_01', bones = { + ['ValveBiped.Bip01_R_UpperArm'] = Angle( 22, -70, 0), + ['ValveBiped.Bip01_R_Hand'] = Angle( -20, -5, 0), + ['ValveBiped.Bip01_R_Finger0'] = Angle( 0, 10, 0), + ['ValveBiped.Bip01_R_Finger1'] = Angle( 0, 20, 0), + ['ValveBiped.Bip01_R_Finger2'] = Angle( 0, -50, 0), + ['ValveBiped.Bip01_R_Finger22'] = Angle( 0, -90, 0), + ['ValveBiped.Bip01_R_Finger21'] = Angle( 0, -90, 0), + ['ValveBiped.Bip01_R_Finger3'] = Angle( -10, -50, 0), + ['ValveBiped.Bip01_R_Finger31'] = Angle( -10, -50, 0), + ['ValveBiped.Bip01_R_Finger32'] = Angle( -10, -50, 0), + ['ValveBiped.Bip01_R_Finger4'] = Angle( -20, -50, 0), + ['ValveBiped.Bip01_R_Finger41'] = Angle( 0, -50, 0), + ['ValveBiped.Bip01_R_Finger42'] = Angle( 0, -50, 0), + ['ValveBiped.Bip01_R_ForeArm'] = Angle( -10, -25, -90), + }}, + { 'Остановить', 'idle_all_01', bones = { + ['ValveBiped.Bip01_R_UpperArm'] = Angle( 25, -45, 0), + ['ValveBiped.Bip01_R_Hand'] = Angle( -20, 65, 0), + ['ValveBiped.Bip01_R_ForeArm'] = Angle( -10, -50, -90), + }}, + { 'Скрестить руки', 'pose_standing_01' }, + { 'Руки на пояс', 'pose_standing_02' }, + { 'Представить вниманию', 'pose_standing_03' }, + { 'Двойной палец вверх', 'pose_standing_04' }, + { 'Преклониться', 'pose_ducking_01' }, + { 'Поза лотоса', 'pose_ducking_02' }, + { 'Стоять настороженно', 'menu_combine' }, + { 'Стоять смирно', 'menu_gman', startTime = 1 }, + { 'Угрожать замахнувшись', 'idle_melee_angry' }, + { 'Стоять вразвалку', 'idle_suitcase' }, + { 'Плохое самочувствие', 'menu_zombie_01', startTime = 1 }, + }, +}) + +octolib.registerAnimationCategory('walk', { + name = 'Походка', + order = 4, + type = 'seq', + anims = { + { 'Идти нормально', '' }, + -- { 'Развалистая походка', 'walk_suitcase' }, + { 'Прикрываться одной рукой', 'run_all_panicked_01' }, + { 'Прикрываться двумя руками', 'run_all_panicked_02' }, + { 'Прикрывать голову руками', 'run_all_panicked_03' }, + { 'Бежать прикрываясь напролом', 'run_all_protected' }, + { 'Бежать напролом', 'run_all_charging' }, + { 'Бежать быстро', 'run_all_02' }, + }, +}) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octolib-backup/hooks.lua b/garrysmod/addons/_config/lua/config/octolib-backup/hooks.lua new file mode 100644 index 0000000..1410059 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-backup/hooks.lua @@ -0,0 +1,12 @@ +hook.Add('dbg-char.spawn', 'octolib.backup', function(ply) + + if ply.restoringBackup then return end + ply.restoringBackup = true + timer.Simple(4, function() + if IsValid(ply) then + octolib.restoreBackup(ply) + ply.restoringBackup = nil + end + end) + +end) diff --git a/garrysmod/addons/_config/lua/config/octolib-backup/mods.lua b/garrysmod/addons/_config/lua/config/octolib-backup/mods.lua new file mode 100644 index 0000000..a5d3a9f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-backup/mods.lua @@ -0,0 +1,101 @@ +-- position +octolib.registerBackupMod('l', function(ply) + return { ply:GetPos(), ply:GetAngles() } +end, function(ply, data) + local pos, ang = unpack(data) + ply:SetPos(pos) + ply:SetAngles(ang) +end) + +-- health +octolib.registerBackupMod('h', function(ply) + return { ply:Health(), ply:Armor() } +end, function(ply, data) + local hp, armor = unpack(data) + ply:SetHealth(hp) + ply:SetArmor(armor) +end) + +-- movetype +-- octolib.registerBackupMod('mt', function(ply) +-- return ply:GetMoveType() == MOVETYPE_NOCLIP +-- end, function(ply, data) +-- if data then ply:SetMoveType(MOVETYPE_NOCLIP) end +-- end) + +-- model +octolib.registerBackupMod('m', function(ply) + local out = {} + out.m = ply:GetModel() + out.s = ply:GetSkin() + out.b = {} + for _, bg in ipairs(ply:GetBodyGroups()) do + out.b[bg.id] = ply:GetBodygroup(bg.id) + end + + return out +end, function(ply, data) + if ply:GetModel() ~= data.m then + ply:SetModel(data.m) + end + if ply:GetSkin() ~= data.s then + ply:SetSkin(data.s) + end + for k,v in pairs(data.b or {}) do + ply:SetBodygroup(k, v) + end +end) + +------------------------------------------------------------ +-- OCTOINV +------------------------------------------------------------ + +-- inventory +octolib.registerBackupMod('i', function(ply) + return ply:ExportInventory() +end, function(ply, data) + ply:ImportInventory(data) +end) + +------------------------------------------------------------ +-- DOBROGRAD +------------------------------------------------------------ + +-- job +octolib.registerBackupMod('j', function(ply) + return ply:getJobTable().command +end, function(ply, data) + local _, jobID = DarkRP.getJobByCommand(data) + if jobID then ply:changeTeam(jobID, true, true) end +end) + +-- admin data +octolib.registerBackupMod('aj', function(ply) + return ply.dbgAdmin_data +end, function(ply, data) + ply.dbgAdmin_data = data +end) + +-- police job +octolib.registerBackupMod('pj', function(ply) + return ply:GetNetVar('dbg-police.job') +end, function(ply, data) + ply:SetNetVar('dbg-police.job', data) +end) + +-- citizen data (for police jobs) +octolib.registerBackupMod('cj', function(ply) + return ply.dbgPolice_citizenData +end, function(ply, data) + ply.dbgPolice_citizenData = data +end) + +-- vehicle deposit +octolib.registerBackupMod('dp', function(ply) + local veh = carDealer.getCurVeh(ply) + if IsValid(veh) and veh.deposit then + return veh.deposit + end +end, function(ply, data) + carDealer.addMoney(ply, data) +end) diff --git a/garrysmod/addons/_config/lua/config/octolib-backup/other.lua b/garrysmod/addons/_config/lua/config/octolib-backup/other.lua new file mode 100644 index 0000000..147b3a6 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-backup/other.lua @@ -0,0 +1,70 @@ +octolib.registerBackupClass('octoinv_item', + function(ent) + return { i = ent:GetNetVar('Item'), m = ent:GetModel() } + end, + function(ent, ply, data) + ent:SetData(data.i[1], data.i[2]) + end +) + +octolib.registerBackupClass('octoinv_storage', + function(ent) + return { i = ent:ExportInventory() } + end, + function(ent, ply, data) + ent.steamID = ply:SteamID() + timer.Simple(1, function() + if data.i then ent:ImportInventory(data.i) end + ent:Save() + timer.Simple(1, function() ent:Remove() end) + -- we create the entity in first place to save inventory with removing it + end) + end +) + +octolib.registerBackupClass('octoinv_cont', + function(ent) + return { m = ent:GetModel(), i = ent:ExportInventory(), d = ent.DestructParts } + end, + function(ent, ply, data) + ent.Model = data.m + ent:SetPlayer(ply) + timer.Simple(1, function() + if data.i then ent:ImportInventory(data.i) end + if data.d then ent.DestructParts = data.d end + ent:SetLocked(true) + end) + end +) + +octolib.registerBackupClass('octoinv_prod', + function(ent) + return { m = ent:GetModel(), i = ent:ExportInventory(), d = ent.DestructParts, pr = ent.prodClass } + end, + function(ent, ply, data) + ent.Model = data.m + ent:SetPlayer(ply) + timer.Simple(1, function() + if data.i then ent:ImportInventory(data.i) end + if data.d then ent.DestructParts = data.d end + if data.pr then ent:SetProdData(octoinv.prod[data.pr]) end + ent.prodClass = data.pr + ent:SetLocked(true) + end) + end +) + +octolib.registerBackupClass('octoinv_vend', + function(ent) + return { m = ent:GetModel(), i = ent:ExportInventory(), d = ent.DestructParts } + end, + function(ent, ply, data) + ent.Model = data.m + ent:SetPlayer(ply) + timer.Simple(1, function() + if data.i then ent:ImportInventory(data.i) end + if data.d then ent.DestructParts = data.d end + ent:SetLocked(true) + end) + end +) \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octolib-lang/ru/client.lua b/garrysmod/addons/_config/lua/config/octolib-lang/ru/client.lua new file mode 100644 index 0000000..42d65c3 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-lang/ru/client.lua @@ -0,0 +1,47 @@ +L.reasons = { + [1] = 'Чужое', + [2] = 'Сервер', + [3] = 'Не в сети', + [4] = 'Запрещено', + [5] = 'Запрещено', + [6] = 'Друг', + [7] = 'Общий', + [8] = 'Игрок', +} + +L.ownability_friend = ' (друг)' + +-- +-- ATM +-- + +L.atm_deposit_ok = 'Внесение средств прошло успешно!' +L.atm_deposit_fail = 'У Вас недостаточно средств' +L.atm_withdraw_ok = 'Снятие средств прошло успешно!' +L.atm_withdraw_fail = 'Недостаточно средств на счете' +L.atm_deposit = 'Внести' +L.atm_withdraw = 'Снять' +L.atm_home = 'Обратно' +L.atm_balance = 'Баланс' +L.atm_home1 = 'Добро пожаловать в ОктоБанк!' +L.atm_home2 = 'У нас нет процентов или налогов' +L.atm_home_taxes = 'Аренда недвижимости: %s' +L.atm_home3 = 'Сделайте Ваш выбор' +L.atm_transaction_success = 'УСПЕХ ТРАНЗАКЦИИ' +L.atm_transaction_failed = 'ОШИБКА ТРАНЗАКЦИИ' +L.octobank = 'OCTOBANK' + +L.test_success = [[Ура! Ты это сделал! + +Твой результат: +%s + +Больше такого не повторится, обещаем ;)]] +L.test_success2 = 'Добро пожаловать в Доброград!' +L.test_success3 = 'Да-да-да, давайте уже играть' +L.test_intro1 = 'Перед началом прохождения теста обязательно изучи **правила** и **ролевой гайд**. Не забудь про то, что правила включают в себя **раздел подробных правил игры за профессии**. В окошке прохождения теста в самом низу будут вкладки, которые позволят быстро открыть необходимые материалы.' +L.test_intro2 = [[Внимательно читай каждый вопрос и ответы к нему. Обрати внимание, что в вопросах может быть несколько верных вариантов ответа! + +Ты получаешь очки по формуле: "правильные ответы" разделить на "всего ответов", если ты наберешь в сумме %s или больше, тест будет выполнен. + +Успехов!]] diff --git a/garrysmod/addons/_config/lua/config/octolib-lang/ru/server.lua b/garrysmod/addons/_config/lua/config/octolib-lang/ru/server.lua new file mode 100644 index 0000000..4e04e9e --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-lang/ru/server.lua @@ -0,0 +1,436 @@ +-- +-- PLAYER TEST +-- + +L.player_test = {{ + ------------ + -- MENTAL -- + ------------ + {'Ты, будучи безобидным прохожим, сокращаешь дорогу через переулок, тут-то тебя и подловила парочка грабителей с ножом, как можно поступить?', + {'Попробовать дать отпор, ведь в детстве тебя учили драться'}, + {'Отдать им все, что есть и как можно скорее позвонить копам', true}, + {'Проигнорировать, притворившись AFK'}, + {'Если наиграно меньше 5-и часов, то грабить нельзя'}, + }, + {'Чем занимается офицер полиции?', + {'Самостоятельно проводит регулярные рейды, чтобы искоренить преступность'}, + {'Патрулирует окрестности, реагирует на вызовы, помогает гражданам города', true}, + {'Проверяет все, что движется, на наличие нелегальных предметов'}, + {'Все, что захочет, он здесь закон!'}, + }, + {'Как лучше отреагировать на парочку оскорблений в сторону персонажа?', + {'Достать нож и напасть на хама'}, + {'Оскорбить в ответ, а может даже устроить драку', true}, + {'Сделать скриншот и оформить жалобу на форуме'}, + {'Вызвать администратора, ведь оскорблять нельзя'}, + }, + {'Во время ограбления тебя заметил случайный прохожий. Твои действия?', + {'Попробую убить, ведь свидетели никому не нужны'}, + {'Поспешу скрыться с места преступления', true}, + {'Вырублю его, чтобы он наверняка забыл обо всем, что видел'}, + {'Начну стрелять по сторонам, пытаясь напугать всех вокруг'}, + }, + {'Какова главная цель ролевой игры?', + {'Заработать как можно больше денег, чтобы купить все необходимое'}, + {'Взаимодействие между персонажами, поддержание собственного образа', true}, + {'Делать все, как в реальной жизни'}, + {'Убийство главы города, судя по другим режимам Гаррис Мода'}, + }, + {'Твоего персонажа убили во время перестрелки. Как следует поступить?', + {'Вернуться на место убийства и вызвать полицию'}, + {'Продолжить играть, забыв ситуацию в которой умер персонаж', true}, + {'Попробовать самостоятельно отомстить'}, + {'Поменять персонажа и заказать убийство обидчика'}, + }, + {'В момент поворота за угол, на дорогу выскочил парень, которого ты благополучно сбил. Твои действия?', + {'Перееду его, пускай ходит по зебре, а не по дороге, козе-е-е-е-л!'}, + {'Остановлюсь, а после попытаюсь помочь, вызову медиков', true}, + {'Попробую скрыться с места преступления', true}, + {'Вызову админа и попрошу убрать его с дороги'}, + }, + {'Офисного работягу донимают новые законы. Как положить этому конец?', + {'Подкупить лейтенанта и полицию, чтобы они заставили мэра изменить законы'}, + {'Составить петицию для отмены законов и собрать подписи жителей', true}, + {'Устроить вооруженную революцию, ведь по другому они не понимают'}, + {'Написать в глобальный OOC чат, что законы мешают и их следует убрать'}, + }, + {'Ночью ты врываешься в магазин с маской на лице. Что будешь делать дальше?', + {'Схвачу все то, что плохо лежит, а после скроюсь', true}, + {'Поставлю всех людей, находящихся в магазине, к стене и начну обыскивать'}, + {'Попытаюсь скрытно убить хозяина магазина, а после, забрав все вещи, убежать'}, + {'Возьму случайного заложника, вызову полицию и потребую от них выкуп'}, + }, + {'Грубиян оскорбляет тебя в не ролевом чате, что ты будешь делать?', + {'Если администрации нет в сети, оформлю жалобу на форуме', true}, + {'Оскорблю в ответ, так будет справедливо'}, + {'Попробую найти с ним общий язык через LOOC чат', true}, + {'Отомщу в игре, чтобы он понял, так поступать нельзя'}, + }, + {'Несколько человек устроили пьяную драку в парке. Твои действия?', + {'Выхвачу пистолет и сделаю предупредительный выстрел в кого-нибудь из толпы'}, + {'Отойду подальше и вызову полицию', true}, + {'Побегу прямо к ним и поучаствую, ведь от кулаков нельзя умереть и драки это весело'}, + {'Если выпадет хороший шанс, метким броском камней постараюсь вырубить кого-то из них'}, + }, + {'Если убийство не запрещено местными законами города, можно ли убивать?', + {'Раз нет в местных законах, то можно и убить'}, + {'Нет, ведь существуют IC законы штата', true}, + {'Да, но при условии отыгрыша маньяка'}, + {'Нет, убивать просто так нельзя', true}, + }, + {'Ты нашел уязвимость сервера или карты. Что будешь делать?', + {'Напишу об этом в ООС'}, + {'Попытаюсь извлечь выгоду'}, + {'Напишу отчет об ошибке на баг-трекере, сейчас или после игры', true}, + {'Проигнорирую это и продолжу игру. Надо будет – сами найдут'}, + }, + {'Ситуация застала врасплох, и вокруг разразилась перестрелка, а твой персонаж в эпицентре. Твои действия?', + {'Попытаюсь избить противника, может быть повезет и он меня не убьет'}, + {'Постараюсь укрыться за чем-либо и буду действовать по ситуации', true}, + {'Начну быстро прыгать и бегать так, чтоб в меня не попали'}, + {'Если терять нечего, то постараюсь извлечь из этого выгоду, подбирая вещи умерших игроков'}, + }, + {'Если ты взял профессию токаря, как лучше поступить?', + {'Обустроить себе мастерскую и подать рекламное объявление', true}, + {'Подыскать покупателей на улице и предложить товар'}, + {'Изготовить себе и друзьям несколько пистолетов, а после освободить профессию'}, + {'Устроиться в банду, там всегда будут клиенты'}, + }, + {'Ты офицер полиции. Выходя в патруль, случайно натыкаешься на людей у здания с оружием в руках. Из их разговора понятно, что они планируют вооруженное ограбление. Твои действия?', + {'Вызову подкрепление и буду информировать коллег о передвижении бандитов', true}, + {'Направлю пистолет на людей, прикажу всем лечь на землю и не двигаться'}, + {'Попробую войти в доверие и попрошу взять себя в долю, взамен предложу защиту от других полицейских'}, + {'Если заметят, напишу в LOOC, чтобы не убивали и что я здесь по НонРП'}, + }, + {'Как лучше избежать обыска полиции, когда тебя уже задержали?', + {'Попросить друга в Discord или в Steam забрать вещи из рук'}, + {'Выхватить пистолет и попытаться отстреливаться'}, + {'Признать вину и попробовать договориться', true}, + {'Плавно ходить из стороны в сторону, не давая обыскать себя'}, + }, + {'Сидя на улице, ты услышал, как на последних этажах дома кто-то достал оружие. Как поступить в этом случае?', + {'Проигнорировать звук и продолжить заниматься своими делами', true}, + {'Ворваться в дом и попытаться обезоружить человека'}, + {'Вызвать полицию на странный шум'}, + {'Использовать /roll, и если выпадет шанс больше 50, значит, все было слышно, и можно реагировать'}, + }, + {'Пока ты был в своей квартире, на площадке началось что-то противозаконное. Как поступить?', + {'Проигнорировать, ведь это не мое дело', true}, + {'Написать в LOOC о том, что они мешают мне, и следом вызвать админа'}, + {'Открыть дверь и сделать несколько предупредительных выстрелов, чтобы спугнуть'}, + {'Вызвать полицию на нарушителей порядка', true}, + }, + {'Ты увидел, как игрока в грубой форме оскорбляют в LOOC чате. Твои действия?', + {'Вызову администратора, ведь это запрещено', true}, + {'Объясню нарушителю через голосовой чат, что он не прав'}, + {'Постараюсь объяснить, почему так нельзя, если не прекратит, то оскорблю в ответ'}, + {'Напишу жалобу на форуме и прикреплю туда скриншоты', true}, + }, + {'Во время сделки по покупке дорогой вещицы тебя обманули. Как следует поступить?', + {'Обратиться в полицию, чтобы обманщик получил по заслугам', true}, + {'Написать жалобу, ведь обмануть можно на сумму до 15000'}, + {'Вычислить грабителя и вернуть то, что он забрал', true}, + {'Пригрозить жалобой, а если он не отреагирует на это, то написать на форум'}, + }, + {'Сразу после взлома грабитель столкнулся с владельцем внутри квартиры. Что ему делать?', + {'Скорее скрыться с места преступления', true}, + {'Заставить владельца открыть хранилище'}, + {'Убить всех, кто попадется, чтобы не оставлять свидетелей'}, + {'Связать владельца и продолжить ограбление'}, + }, + {'Офицер спешил на вызов и случайно поцарапал твою машину. Как поступить?', + {'Догнать и подрезать в ответ'}, + {'Обратиться в полицию с заявлением', true}, + {'Проследовать за ним, выждать момент и пробить колеса'}, + {'Вызвать админа, ведь это преступление, а полиция не может нарушать закон'}, + }, + {'Твоего персонажа оскорбили во время разговора. Что делать?', + {'Оскорбить грубияна в ответ, а может даже устроить драку', true}, + {'Выстрелить в гада, чтобы поставить его на место'}, + {'Отомстить позже, например, пробить колесо его машины', true}, + {'Сделать скриншот и отправить жалобу на форум'}, + }, + {'Во время твоей смены в полиции попадается мелкий нарушитель общественного порядка, который пытается убежать. Как поступишь?', + {'Начну преследовать его и по возможности задержу', true}, + {'Использую тазер, он курил в общественном месте и наверняка представляет этим угрозу'}, + {'Если он не остановится, выстрелю несколько раз по ногам, чтобы замедлить'}, + {'Подам его в розыск или сообщу коллегам по рации, используя внешние приметы', true}, + }, + {'На парковке началась потасовка, где несколько скинхедов громят автомобиль, попутно избивая владельца. Твои действия?', + {'Просто пройду мимо, а если я вмешаюсь, то это может навредить моему здоровью', true}, + {'Возьму в руки первый попавшийся предмет и с оглушительным криком прыгну в толпу'}, + {'Отбегу подальше и вызову полицию, опишу им преступников', true}, + {'Стану рядом и начну записывать все на телефон'}, + }, + {'Если ты взял профессию автомеханика, как лучше поступить?', + {'Открыть свою мастерскую и принимать заказы', true}, + {'Быстро починить свою машину, а потом освободить профессию для других '}, + {'Пройтись по улице и предложить свои услуги прохожим'}, + {'Найти серьезных ребят и вступить к ним в банду, там всегда есть работа'}, + }, + {'Услышав выстрел, ты вышел на улицу и увидел посреди дороги труп. Твои действия?', + {'Испугавшись, как можно быстрее вернусь в дом и вызову полицию', true}, + {'Постараюсь обыскать тело пока никто не видит, возможно там лежит что-то ценное'}, + {'Стану священником и узнаю подробности преступления у духа жертвы'}, + {'Возьму дело в свои руки, а после вычислю убийцу и отомщу'}, + }, + {'В каких ситуациях использование оружия можно считать оправданным для полиции?', + {'Подозрительный тип бросился убегать при виде патрульных'}, + {'Гражданин проехал на красный свет, отказывается выйти из автомобиля'}, + {'Нарушитель общественного порядка практически скрылся во время преследования'}, + {'Подозреваемый намеренно пытается сбить коллегу по службе', true}, + }, + {'Тебе не хватило места на остановке, чтобы уехать в город. Твои действия?', + {'Попрошу уступить место, ведь я спешу по важным делам', true}, + {'Доберусь пешком, используя банихоп, так будет быстрее'}, + {'Незаметно заберусь в чужую машину по пути в город и доеду на ней'}, + {'Сяду на крышу остановки, это должно сработать'}, + }}, + -------------- + -- ROLEPLAY -- + -------------- + {{'Что такое IC информация?', + {'Информация, которой может владеть только администрация'}, + {'Информация, которая не относится к игровому процессу'}, + {'Информация, которая касается исключительно ролевого процесса', true}, + {'Информация, запрещенная на сервере'}, + }, + {'Что можно указать в описании внешности персонажа?', + {'Откуда он родом, его историю'}, + {'Род деятельности, профессию'}, + {'Телосложение, черты лица, одежду', true}, + {'Информацию о себе, например, Discord или VK'}, + }, + {'Как правильно обратиться к игроку неподалеку по не ролевому вопросу?', + {'В голосовой чат, но обязательно добавив фразу «сейчас по НонРП скажу»'}, + {'Использовать шепот, чтобы кроме собеседника никто не услышал'}, + {'В ООС чат'}, + {'В LOOC чат', true}, + }, + {'Где можно разместить рекламу нелегальной деятельности?', + {'В /advert, если добавить приписку «(анонимно)» или «(deepweb)»'}, + {'Полупрозрачным текстом в неприметных местах', true}, + {'Сзади полицейского департамента, все равно там никто не ходит'}, + {'Где угодно, если нет местных законов'}, + }, + {'Несколько игроков устроились работать токарем, но ты не можешь найти их магазины. Как поступить?', + {'Найти токаря в списке игроков и связаться с ним через SMS'}, + {'Разузнать об их магазинах у прохожих', true}, + {'Постараться найти нужного персонажа по имени и невзначай завести с ним разговор'}, + {'Попробовать спросить у администрации, где они находятся'}, + }, + {'Устав от обыденных ролей, ты решаешь отыграть маньяка. Так можно?', + {'Да, но если соблюдать правило Powergaming'}, + {'Да, только по разрешению куратора', true}, + {'Да, ведь сервер ролевой и на нем нет ограничений для отыгровок'}, + {'Нет, ни при каких обстоятельствах'}, + }, + {'Тебя ограбили пару дней назад и этот игрок уже сменил персонажа. Можно ли отомстить ему?', + {'Да, но только при условии, если он снова будет играть за старого персонажа', true}, + {'Можно, ведь это один и тот же игрок'}, + {'Мало просто отомстить, нужно еще и убить, чтобы он не мстил в ответ'}, + {'Достаточно придумать случайный повод и убить его, как бы невзначай'}, + }, + {'В каком случае твой персонаж может совершить убийство?', + {'Если есть шанс безнаказанно скрыться с места преступления'}, + {'Когда в полиции есть свои люди, которые могут помочь в случае чего'}, + {'При наличии серьезных обстоятельств и если это позволяет характер персонажа', true}, + {'Персонаж психологически неуравновешен или находится в состоянии алкогольного опьянения '}, + }, + {'Что может сделать полицейский после гибели во время спецоперации?', + {'Подождать несколько минут и вернуться на место действий'}, + {'Сообщить коллегам через не ролевой чат, что его персонаж погиб', true}, + {'Находиться рядом и прикрывать тыл, но не подходить близко'}, + {'Ожидать ее окончания и не возвращаться ни при каких обстоятельствах', true}, + }, + {'В каких случаях использование /advert правильно?', + {'/ad Продам колумбийскую коку, 35-54-32'}, + {'/ad (Видит только криминал) Продам AK-47, не дорого'}, + {'/ad На Джеферсон 8 парень прыгает с крыши! Все сюда!'}, + {'/ad Куплю гитару, звонить по номеру 35-54-32', true}, + }, + {'Где команда /it использована правильно?', + {'/it После удара по голове, жертва должна забыть лицо преступника'}, + {'/it Распахнул шкаф, начал рыться в поисках коробки с документами'}, + {'/it На одежде мужчины виднелись следы краски, похоже он занимается ремонтом', true}, + {'/it Скомкал лист бумаги и метким броском закинул его прямо в корзину'}, + }, + {'Использование ролевой информации, добытой не ролевым путем, что это?', + {'Powergaming'}, + {'Отыгровка в свою сторону'}, + {'NonRP'}, + {'Metagaming', true}, + }, + {'К тебе домой нагрянули офицеры с обыском. Как поступить правильно?', + {'Поставить хранилище и убрать туда все вещи'}, + {'Постараться не делать резких движений и выполнять просьбы офицеров', true}, + {'Достать оружие и дать отпор'}, + {'В случае наличия нелегала, попытаться раскидать его по квартире, обманув полицию', true}, + }, + {'Какое рекламное объявление нарушает правила?', + {'Продаю “веселые вещества”. Приходи на Джойс 4', true}, + {'Продам гараж на Стефан 9'}, + {'(Не видно полиции) Проводится набор в банду, ждем за старым складом', true}, + {'Вкусная пицца от Папы Митчелл, ждет вас на Рурк 6'}, + }, + {'Что такое OOC информация?', + {'Информация, которой может владеть только администрация'}, + {'Информация, которая не относится к ролевому процессу', true}, + {'Информация, которая касается исключительно игрового процесса и не выходит за его рамки'}, + {'Информация, запрещенная на сервере'}, + }, + {'Тебя неоправданно убили. Что будешь делать?', + {'Выслежу нарушителя и убью в ответ, чтобы проучить'}, + {'Объясню убийце его неправоту через глобальный OOC чат'}, + {'Отправлю жалобу админам онлайн или на форум', true}, + {'Буду писать в канал #help в Discord, до тех пор, пока не решат мою проблему'}, + }, + {'Жертва ограбления отказывается вынимать все из карманов, находясь у тебя на прицеле. Что будешь делать?', + {'Подстрелю ее, а потом технически обыщу'}, + {'Сперва попрошу прекратить в LOOC, а после вызову администратора или напишу на форум', true}, + {'Сделаю скриншот и буду отправлять на площадки сообщества, чтобы никто не дружил с нарушителем'}, + {'Прекращу играть ситуацию и просто проигнорирую, жалоба все равно не поможет'}, + }, + {'Превышение возможностей персонажа. Что это?', + {'Это техническое действие, не предусмотренное ролевой игрой (механ)'}, + {'Отыгровка в свою сторону'}, + {'Powergaming', true}, + {'FearRP'}, + }, + {'Тебя беспричинно пытаются арестовать. Как поступить правильно?', + {'Буду убегать, пускай даже стреляют, они все равно не правы'}, + {'Выжду момент и когда окажусь на свободе, отомщу за их нарушение'}, + {'Поговорю с полицией в LOOC, если они не исправятся, вызову админа', true}, + {'Без лишних разговор создам голосование об увольнении'}, + }, + {'Ты решаешь написать личное не ролевое сообщение. Какой чат подойдет для этого?', + {'Через /SMS, если добавить двойные скобки по краям'}, + {'Подойдет глобальный OOC чат, главное упомянуть игрока, к которому обращаються'}, + {'Через /PM чат', true}, + {'Обычный чат, но добавив приставку [LOOC] в самом начале'}, + }, + {'Что из этого подходит для объявления комендантского часа?', + {'В городе проводиться фестиваль красок! Все, кто не участвуют, идут домой'}, + {'Полиция разыскивает одного преступника, а остальные должны покинуть улицы'}, + {'Беспорядок по всему городу, проводятся несогласованные митинги', true}, + {'Ночное время, значит всем пора разойтись'}, + }, + {'Играя за повара, ты решил запастись едой на будущее. Так можно?', + {'Когда ненадолго занимаешь профессию, проблем возникнуть не должно'}, + {'При условии, что еще и продаешь эту еду для горожан', true}, + {'Можно взять профессию, сделать заказ в магазине и сменить ее обратно, никто не будет против'}, + {'Если стать где-нибудь на улице, подать объявление и на него никто не отреагирует, тогда можно заказать и для себя'}, + }, + {'Произошло ограбление, ты скрылся с места преступления и хотел бы сменить персонажа. Как правильно поступить?', + {'Проверить, нет ли погони, спрятать награбленное и только после менять персонажа'}, + {'Связаться с жертвой ограбления через /pm и предупредить о планах'}, + {'Подождать 7-8 минут, если за это время полиция не поймает персонажа, можно менять'}, + {'Убедиться, что ситуация полностью окончена и полиция бросила все поиски грабителя', true}, + }, + {'Какие из имен не нарушают правила сервера?', + {'Уинстон Черчилль'}, + {'William Anderson'}, + {'Глок Отмычкин'}, + {'Генри Палмер', true}, + }, + {'Какие из построек ниже не нарушают правила?', + {'Фургончик с мороженым на правой полосе дороги'}, + {'Торговая палатка у входа в полицейский участок'}, + {'Несколько пропов, создающих небольшую щель для стрельбы'}, + {'Пропускной пункт на трассе по указанию мэра', true}, + }, + -- {'Может ли полиция или мэр иметь принтеры?', + -- {'Конечно, но главное никому не показывать их'}, + -- {'Нет, принтеры нелегальны, а власти не могут нарушать закон'}, + -- {'Только если мэр издаст закон, который разрешает это'}, + -- {'Если принтеры на хранении у банкира, тогда можно', true}, + -- }, + {'В каком случае можно устроить переворот и свергнуть власть?', + {'Когда в законах города есть пункты, нарушающие правила сервера'}, + {'Ночью, в это время полиция должна спать и она не сможет отреагировать на это'}, + {'Если мэр ничего не делает и отказывается освобождать свой пост'}, + {'В ситуации, где выдвинутые обоснованные требования были проигнорированы мэром', true}, + }, + {'Как называется убийство без весомой на то причины?', + {'FK', true}, + {'RDM'}, + {'DM'}, + {'FD'}, + }, +}} + +L.msgWelcome = [[Привет! +Знаем, никто не любит тесты, особенно в игре. Но мы хотим, чтобы на +сервере играли люди, которые умеют или хотят научиться играть, +создавать интересные истории с другими людьми. +Тест очень простой и займет у тебя всего пару минут, попробуй. + +Осталось попыток на сегодня: %s +Если ты начнешь выполнять тест, твоя попытка уже будет засчитана]] + +L.msgTryAgain = [[Что ж, не все потеряно! + +Осталось попыток на сегодня: %s]] + +L.msgTryTomorrow = [[У тебя не осталось попыток на сегодня... +Приходи завтра! Не забудь почитать правила]] + +L.exploits_not_here = 'Мы же сказали, тут нет эксплойтов' +L.exploits_not_here2 = 'Ищешь экплойты? У нас их нет ;)' + +-- +-- saycommands +-- + +L.see_time_hint = '{name} смотрит на часы' +L.the_clock_shows = 'Часы показывают %s' +L.have_chance = '%s имеет шанс: ' +L.out_of_100 = '%s из 100' +L.threw_the_dice = '%s бросает кости: ' +L.dice_and = '%s и %s' +L.randomcard = '%s вытаскивает карту: ' +L.gesture_showed = '%s показывает ' + +L.card_parts1 = {'туз','двойка','тройка','четверка','пятерка','шестерка','семерка','восьмерка','девятка','десятка','валет','дама','король'} +L.card_parts2 = {'крестей','бубен','черв','пик'} + +L.rps = { + 'камень', + 'ножницы', + 'бумагу' +} + +L.ideas = { + 'Самое время легких денег! Все, что тебе понадобится: стол, два стула и кости. Расположи их в подходящем месте, где-то, где не так много людей и предлагай сыграть с тобой. В меню на «C > Вероятность» есть все необходимое. А если ты слишком часто проигрываешь, хватай деньги и уноси ноги!', + 'Ресторанная еда всем наскучила, народу подавай чего попроще. Арендуй помещение у центра, оборудуй там кухню, а снаружи поставь тележку с зонтиком, микроволновкой и всем необходимым. Заверни пару сосисок в булку, и ты уже уличный торговец хот-догами. Быстро, просто, а главное - прибыльно!', + 'Дома – отличный бизнес! Достаточно арендовать небольшое здание, обустроить вестибюль, установить будку для консьержа и заселять жильцов внутрь. Так же не помешало бы нанять сварливого уборщика и вечно недовольного сторожа, все не может быть слишком идеально', + 'Полиция всегда заинтересована в новых подробностях криминального мира. Если твой персонаж не первый день ввязан в уличные истории, было бы неплохо прикрыть себя послужным списком. Наладь контакт с определенными офицерами и стань информатором, а взамен потребуй награду или помощь', + 'В городе часто требуются рабочая сила и твоя помощь точно там не помешает. Подай объявление о поиске работы (C-меню > Опубликовать рекламу) и найди что-то подходящее', + 'Мистика и загадки – это всегда интригует. Комната увешанная разными безделушками, стол и хрустальный шар в центре, немного поменять образ и та-дам! Ты самый настоящий медиум, предсказывай судьбы людей, ну или.. делай вид, что предсказываешь', + 'В местной церкви слишком мало прихожан. А ведь религия это отличная идея! Обратись к ней и стань святым отцом или создай свою собственную и называй себя иначе. Найди пару верных служителей и несите веру в массы. Собирайте пожертвования и улучшайте церковь', + 'Временами музыка сама по себе приходит в голову? Это поводу обзавестись гитарой! Найди продавца через объявления и выторгуй себе новенький инструмент. Усядься в баре на стул, води рукой по струнам и лови ва-а-а-йп', + 'Все вокруг слишком заняты, только изредка покидают рабочее место ради покупки пачки чипсов и содовой. Так питаться – плохо! Возьми ситуацию в свои руки, арендуй помещение, организуй там кухню. Найми повара в помощники и пару курьеров для доставки. Подай рекламу через C-меню и доставляйте еду прямо в руки трудягам!', + 'Хранилище трещит по швам от всяких запчастей и на первый взгляд "полезных" вещей? Нужно немного опустошить его! Для твоей барахолки понадобится всего пара столиков, стул и надежное место, откуда можно таскать вещи на продажу. А теперь нужно завлечь покупателей, предложим им широкий ассортимент, подав рекламу через C-меню!', + 'Уличная жизнь бездомного полна романтики.. наверное. А почему бы и самому не проверить? Самодельная кухня, место ночлега – сооруди это и основная часть готова. А теперь вперед! Облапош парочку городских, разыграв сценку, будто на тебя наехал автомобиль или кто-то порвал твою куртку и тебе нужны деньги. Главное будь осторожен!', + 'Опасно ехать в больницу с бандитской пулей в плече. Если ты немного разбираешься в медицине, в твоих силах организовать подпольную операционную и помогать бедным злодеям штопать их раны, но за серьезные деньги, взамен на молчание', + 'Нелегальная покупка наркотиков прямо в аптеке, как это иногда ошибочно делается – глупо и опасно! Кто-то должен серьезно взяться за бизнес. Примерь на себя роль фармацевта, а через связи закажи медицинской марихуаны или еще чего. Найди парочку жадных дилеров и предложи им сотрудничество, но будь осторожнее! Они в свою очередь смогут торговать на углах, не привлекая к тебе внимания, если грамотно заметать следы', + 'Народ слишком мало развлекается, это повод устроить тусу-джусу! Оформи квартиру со вкусом, добавь приглушенного пурпурного света и закупись выпивкой, найди кого-то с машиной и включи музыку под окнами. И теперь пора зазывать народ, пусти слухи или напрямую пригласи людей потусить у тебя. Перебрасывайте в картишки через C-меню, рассказывайте истории и просто хорошо проводите время', + 'В C-меню есть вкладка «вероятность» – это огромное поле для азартных игр! Карты и кости, с их помощью можно неплохо провести время и даже организовать целое казино!', + 'Если ты неплохо управляешься в строительстве, со вкусом подбираешь материалы и хорошо расставляешь декор, это может стать отличной основой для бизнеса. Бери заказы на оформление интерьеров, а если ты найдешь еще одного умельца, зови его в напарники и работа будет проделываться в разы быстрее! А в конце заказчик сможет сохранить постройку через Advanced Duplicator 2 (инструмент)', + 'Имея крепкие ноги, ну или в лучшем случае автомобиль, можно подзаработать на доставке, устроившись курьером. Для поиска клиентов нужно подать объявление через C-меню или напрямую обратиться к бизнесам (токари, фармацевты или повара). Заключить с ними контракт (C-меню > Создать документ) для большей гарантии, а потом забирай их заказы на твое имя из почтовых ящиков и доставлять в указанное место, в конце забирая оговоренную плату', + 'В городе почти нет такси и зачастую до нужного места приходиться добираться общественным транспортом, а потом еще и пешком. Если у тебя есть машина, это отличный способ заработка, а если нет, подойди к идее иначе и возьми на себя роль оператора, заранее договорись с водителями. Когда все будет готово, можно подавать рекламу (С-меню > Рекламное объявление). Принимай вызовы и перенаправляй их таксистам', + 'Книжный магазин может стать местом для обсуждений любителей почитать, твоей личной площадкой под репетицию перед учебным докладом и даже небольшой типографией, где выпускаются истории или целые газеты (C-меню > Создать документ)', +} + +L.lockpick_action = 'Взломать' + +-- +-- dbg-work +-- +L.work_completed_hint = 'Работа выполнена! Зарплата: %s' +L.worker_msg = 'Что-то в городе сломалось, возьми детали на складе и почини это' +L.take_details = 'Взять детали' +L.block_soda = 'Блок газировки' +L.take_soda = 'Взять газировку' +L.load_in_soda_machine = 'Загрузить в автомат' +L.take_soda_hint = 'Возьми газировку на складе и загрузи ее в автомат' diff --git a/garrysmod/addons/_config/lua/config/octolib-lang/ru/shared.lua b/garrysmod/addons/_config/lua/config/octolib-lang/ru/shared.lua new file mode 100644 index 0000000..adc112d --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-lang/ru/shared.lua @@ -0,0 +1,2739 @@ +L.welcome = 'Добро пожаловать!' + +-- +-- general +-- + +L.yes = 'Да' +L.no = 'Нет' +L.ok = 'OK' +L.finish = 'Готово' +L.cancel = 'Отмена' +L.information = 'Информация' +L.remove = 'Убрать' +L.delete = 'Удалить' +L.apply = 'Применить' +L.reset = 'Cбросить' +L.rename = 'Переименовать' +L.minutes_hint = 'минут' +L.secons_hint = 'секунд' +L.hours_hint = 'часов' +L.days_hint = 'дней' +L.weeks_hint = 'недель' +L.other = 'Другое' +L.open = 'Открыть' + +-- +-- inventory +-- + +L.inventory = 'Инвентарь' +L.jacket = 'Куртка' +L.pants = 'Брюки' +L.drop = 'Выбросить' +L.put = 'Положить' +L.holster = 'Убрать' +L.unknown = 'Неизвестно' +L.inventory_corpse = 'Труп - ' +L.container = 'Контейнер' +L.inventory_hands = 'Руки' +L.unknown_container = 'Неизвестный контейнер' + +-- +-- misc +-- + +L.equipment = 'Экипировка' +L.action = 'Действие' +L.money = 'Деньги' +L.bonus_salary = '%s Надбавка за репутацию: %s' +L.didnt_fit = ' не поместилось к тебе в инвентарь' +L.weapons = 'Оружие' +L.license = 'Лицензия' +L.license_give = 'Выдать лицензию' +L.license_withdraw = 'Отозвать лицензию' +L.issue = 'Выдать' +L.sticknote_create = 'Создать заметку' +L.sticknote_hint = 'Здесь можно написать свой текст, а также изменить размер и положение заметки. Удалить ее можно с помощью кнопки в верхнем правом углу' +L.see_time = 'Узнать время' +L.place_storage = 'Поставить хранилище' +L.place_storage_confirm = 'Ты уверен, что хочешь поставить хранилище?\nОно не исчезнет до твоего выхода с сервера' +L.enter_amount_money = 'Введи сумму:' +L.write_cheque = 'Выписать чек' +L.write_doc = 'Написать документ' +L.demote = 'Уволить' +L.sell_doors = 'Продать помещения' +L.sell_doors_confirm = 'Ты точно хочешь продать все помещения?' +L.show_laws = 'Показать законы' +L.city_laws = 'Законы города' +L.markers = 'Маркеры' +L.markers_format = '%s (%sм)' +L.markers_clear = 'Очистить все' +L.markers_clear_police = 'Очистить вызовы' +L.talkie = 'Рация' +L.make_wanted = 'В розыск' +L.reason_wanted = 'Причина розыска' +L.auto = 'Авто' +L.radio = 'Радио' +L.radio_rename = 'Введите новое название станции' +L.soda = 'Газировка' +L.dobrograd_halloween = 'Доброград - Хеллоуин' +L.need_phone = 'Для этого нужен телефон' +L.lockpick = 'Взлом' +L.needlockpick = 'В руках должна быть хотя бы одна отмычка' +L.tree = 'Елка' +L.console = 'Голос из ниоткуда' +L.admintext_discord = '%s на сервере **%s** жалоба, но нет админов!\nЖалуется **%s**: %s' +L.onlyhandsitem = 'Предмет можно подобрать только в руки' +L.onlyhandsitem2 = 'Предмет можно взять только в руки' +L.inhandsnospace = 'У тебя в руках не хватает места' +L.cannotpickup = 'Ты не можешь это подобрать' +L.notmove = 'Ты не можешь переместить этот предмет сюда' +L.notenoughspace = 'Здесь недостаточно места' +L.error_inventory = 'Твои данные инвентаря повреждены, данные уже отправлены разработчику' +L.error_storage = 'Твои данные хранилища повреждены, данные уже отправлены разработчику' +L.config_title_ulogs = 'Логи - Доброград' +L.dontdoincuffs = 'Ты не можешь это сделать, будучи связанным!' +L.needscuffs = 'Нужно сначала надеть наручники' +L.change_weapon = 'Смена оружия' +L.youcanusethisweapon = 'Ты не можешь использовать данный тип оружия' +L.cracking = 'Взламываю' +L.create_failed = 'Создание отменено: %s' +L.create_success = 'Предмет готов: %s' +L.create_in_progress = 'Крафт: %s' +L.pendingbackup = 'Мы восстановили твою игровую сессию после краша сервера!' +L.loading = 'Загрузка...' +L.feature = 'Избранное' +L.search_hint = 'Поиск...' +L.infeature = 'В избранное' +L.save = 'Сохранить' +L.disable = 'Выключить' +L.volume = 'Громкость' +L.binder = 'Бинд кнопки' +L.distance = 'Дальность' +L.title = 'Название' +L.can_be_searched = 'Можно обыскать' +L.gesture_only_for_dobro = 'Этот жест только для добродеев' +L.discreet_place = 'Укромное место' +L.position = 'Позиция' +L.position_camera = 'Позиция камеры' +L.position_player = 'Позиция игрока' +L.angle = 'Наклон' +L.size = 'Размер' +L.copy = 'Копировать' +L.failure_create_object = '[#] Не получилось создать объект' +L.failure_need_see = '[#] Нужно смотреть на энтити' +L.format_command = '[#] Формат команды: octolib_child путь/к/модели.mdl номер_прикрепления' +L.format_command_space = '[#] Если в модели есть пробелы, ее нужно калючать в кавычки, номер прикрепления можно не вводить' +L.enabled = 'включен' +L.disabled = 'выключен' +L.crosshair = 'Прицел' +L.you_handcuffed = 'Твои руки связаны' +L.you_handcuffed2 = 'Ты связан' +L.your_mouth_gagged = 'У тебя во рту кляп' +L.your_eyes_closed = 'Тебе завязали глаза' +L.cuffs_breakout = '%s — вырываться' +L.left_button = 'ЛКМ' +L.right_button = 'ПКМ' +L.jobs_only_dobro = 'Эта работа для добродеев!' +L.you_need_more_hours = 'Для этого нужно играть на нашем сервере не менее %s часов!' +L.you_already_have_a_cigarette = 'У тебя уже есть сигарета' +L.you_used_bus = 'Ты воспользовался автобусом за ' +L.take_soccerball = 'Чтобы взять мяч, нужно освободить руки и остановить его' +L.result_speedometer = 'Результат замера скорости: %smph' +L.enter_car = 'Вход в машину' +L.where_details = 'А где детали?' +L.repair = 'Починить' +L.remove_letters = 'Все твои документы удалены' +L.too_much_letter = 'У тебя слишком много документов' +L.need_buy_feauture = 'Ты не купил эту плюшку!' +L.rename_city = 'Переименовать город' +L.startlottery = 'Запустить лотерею' +L.appearance = 'Внешний вид' +L.hat = 'Головной убор' +L.appearance2 = 'Внешность ' +L.get_equipment = 'Получить экипировку' +L.get_ammo = 'Пополнить боезапас' +L.hand_over_equipment = 'Сдать экипировку' +L.empty_hint = '(пусто)' +L.vend_hint = 'АВТОМАТ' +L.vend_empty = 'ВЫКЛЮЧЕН' +L.vend_of_managment = 'для управления' +L.vend_press_use = 'зажми ИСПОЛЬЗОВАТЬ' +L.speaker = 'Громкоговоритель' +L.speaker_say = 'Сказать через громкоговоритель' +L.write_text = 'Введи текст:' +L.talkie_hint = 'Введи частоту (Текущая: %s). Гражданские частоты\nнаходятся в диапазоне от 111.1 до 999.9:' +L.change_radio = 'Сменить частоту рации' +L.show_codes = 'Показать коды' +L.codes_hint = 'Показать коды' +L.codes_help = [[TEN-КОДЫ: +10-4 - Вас понял/Принято +10-6 - Нет/Отказ +10-8 - Не доступен для вызовов +10-11 -Доступен для вызовов +10-15 - Провожу арест +10-20 - Нахожусь (Место) +10-30 - ДТП +10-40 - Большое скопление людей +10-52 - нужен EMS (EMS - парамедики и работники больницы) +10-57 Viktor - погоня за автомобилем +10-57 Foxtrot - пешая погоня +10-60 - Информация об автомобиле (подозреваемый) +10-61 - Информация о человеке (подозреваемый) +10-70 - Нужна поддержка (кол-во юнитов) +10-99 - Ситуация урегулирована + +КОДЫ: +Код 0 - Офицер убит. Все юниты независимо от ситуации направляются на место проишествия +Код 1 - Офицер в бедственном положении, открыт огонь. Свободные юниты на не важных ситуациях едут туда +Код 3 - Срочный вызов +Код 4 - Помощь не требуется/все чисто +Код 6 - Режим Stand By (задержка на ситуации) +Код 7 - Перерыв (время, место)]] +L.dpd_codes_help = [[TEN-КОДЫ: +10-1 - Начинайте радиопередачу +10-2 - Повторите радиопередачу +10-4 - Принято (подтверждение) +10-6 - Ожидайте +10-7 - Запрос на подтверждение адреса +10-8 - Занят +10-9 - Задержка на ситуации +10-10 - Отклонено (отрицание) +10-11 - Свободен для вызовов +10-12 - Офицер полиции/представитель охранной службы удерживает субъект +10-13 - Офицеру полиции требуется поддержка (Серьёзное происшествие) +10-20 - Местоположение +10-30 - Ограбление +10-31 - Кража со взломом +10-32 - Кража +10-33 - Взрывное устройство или опасность детонации чего-либо +10-34 - Нападение +10-50 - Человек/группа людей устраивает беспорядки +10-51 - Человек в состоянии наркотического/алкогольного опьянения +10-52 - Спор (между гражданскими лицами) +10-53 - Инцидент с автомобилем (ДТП) +10-55 - Офицер проводит трафик-стоп обычного приоритета +10-56 - Офицер проводит трафик-стоп повышенного приоритета (требуется 10-13) +10-59 - Предупреждение о возможном Shots Fired на сцене +10-67 - Происшествие с траффиком +10-84 - Юнит прибыл на сцену + +ELEVEN-КОДЫ: +11-10 - Принимайте информацию +11-24 - Брошенный автомобиль +11-40 - Не требуется медицинское/спасательное и иное вмешательство +11-41 - Требуется вмешательство медицинских служб +11-42 - Требуется вмешательство спасательных служб +11-43 - Требуется вмешательство коронёров +11-44 - Требуется вмешательство супервайзера +11-55 - Офицер преследуется автомобилем +11-99 - Нападение на офицера! + +КОДЫ: +Код 1 - Реагируйте на вызов без использования сирен и огней +Код 2 - Реагируйте на вызов с использованием огней, но без использования сирен +Код 3 - Реагируйте на вызов с использованием огней и сирен +Код 4 - Закрытие вызова, дальнейшая помощь не требуется +Код 5 - Вызов всё ещё актуален, требуется 10-13 (поддержка офицера) +Код 6 - Офицер покидает автомобиль +Код 7 - Статус юнита: вне службы (Используется при оповещании об окончании смены) +Код 8 - Статус юнита: на службе (Используется при оповещании о начале смены) +Код 9 - Вызов S.W.A.T на сцену +Shots Fired - Были произведены выстрелы из огнестрельного оружия на активной сцене +BREAK - Прекращение всех радиопередач (Приоритет)]] +L.request_backup = 'Вызвать подкрепление' +L.request_backup_query = 'Опиши ситуацию:' +L.panic_button_press = 'Нажать кнопку паники' +L.broadcast = 'Радиовещание' +L.broadcast_hint = 'Радиовещание мэра' +L.broadcast_write_text = 'Введи текст радиовещания:' +L.resetclaws = 'Переиздать законы' +L.rename_city_hint = 'Внимание! Это стоит 5000р\nИмя города:' +L.lockdown = 'Объявление ком. часа' +L.lockdown_hint = 'Введи причину ком. часа:' +L.chance = 'Вероятность' +L.pull_card = 'Вытащить карту' +L.roll_the_dice = 'Бросить кости' +L.get_chance = 'Получить шанс' +L.rock_paper_scissors = 'Камень, ножницы, бумага' +L.idea = 'Ролевая идея' +L.model_space = '[#] Если в модели есть пробелы, ее нужно калючать в кавычки, номер прикрепления можно не вводить' +L.retest = 'Аннулирует результат теста' +L.retest_hint = 'Твой результат теста был аннулирован. Зайди на сервер, чтобы снова его пройти' +L.search_logs = 'Поиск по логам...' +L.laws_city = 'города ' +L.need_teammate = 'Тебе нужен хотя бы один напарник' +L.you_scared = 'Ты слишком напуган, чтобы это сделать' +L.no_change_seat = 'Нельзя пересаживаться на ходу' +L.frost_hint = 'Ты замерзаешь, купи теплую одежду в магазине или найди теплое место' +L.frost_hint2 = 'Этот холод просто невыносим... Твое состояние ухудшается' +L.cardamage = 'Не стоит портить чужие вещи.' +L.defaultlaw = 'Если принтеры легальны, то только на хранении у банкира' +L.analyzer_cop = ' (полицейский)' +L.can_open = 'Можно открыть' +L.item_closed = 'Предмет закрыт' +L.adminslots = 'Прости, но мы держим несколько слотов для администрации, приходи попозже' +L.chooseicon = 'Выбор иконки' +L.search2_hint = 'Искать...' +L.storage_hint = 'Хранилище - ' +L.you_not_friend = 'Тебя нет в друзьях владельца этого хранилища. Если хочешь его ограбить, зажми клавишу использования' +L.hurts_hand = 'Твои руки очень болят' +L.need_hint_license = 'Нужно указать, на что лицензия' +L.license_hint = 'Лицензия на...' +L.license_hint2 = 'Лицензия на ' +L.have_license = 'Есть лицензия' +L.addlaw = 'Издать закон' +L.addlaw_hint = 'Содержание закона:' +L.removelaw = 'Отозвать закон' +L.recall = 'Отозвать' +L.unmask = 'Снять аксессуар' +L.nonrp_actions = 'Неролевые действия' +L.applications_and_ticket = 'Заявки и жалобы' +L.send_pm = 'Отправить PM' +L.send_pm_hint = 'Отправить PM: ' +L.send_text = 'Введи текст сообщения:' +L.check_time = 'Посмотреть время' +L.call_ems = 'Вызов экстренных служб' +L.too_big = 'Этот объект слишком большой' +L.need_10_hours = 'Для использования этого инструмента нужно наиграть не менее 10 часов' +L.male = 'Мужчина' +L.woman = 'Женщина' +L.variant = 'вариант' +L.character = 'Персонаж' +L.view = 'Просмотр' +L.name_character = 'Имя персонажа' +L.desc_appereance = 'Описание внешности' +L.skin = 'Одежда' +L.skin2 = 'Набор ' +L.citizen_work = 'Гражданская работа' +L.voice = 'Голос персонажа' +L.buy_govorilka = 'Говорилку можно приобрести в магазине' +L.show_hand = 'Показать руку' +L.player_left = 'Игрок вышел ' +L.to_buy = 'Чтобы купить\nудерживай ' +L.see_around_friend = 'Взгляни вокруг, этот мир прекрасен, друг!' +L.dead_cant_do_this = 'Мертвые не могут этого делать' +L.you_rescued = '%s воскресил тебя!' +L.you_rescued_by = 'Ты воскресил %s' +L.player_not_found = 'Игрок не найден' +L.death_leave = 'Ты вышел будучи мертвым. Вот теперь ходи и жди!' +L.handcuff_breaking = 'Развязывание' +L.tie_eyes = 'Завязать глаза' +L.untie_eyes = 'Развязать глаза' +L.insert_gag = 'Вставить кляп' +L.take_out_the_gag = 'Вынуть кляп' +L.money_pot_in = '- Money Pot\n- Внутри: %sP' +L.money_pot_desc = 'Позволяет использовать сборку и выдачу денег с помощью wire-механизмов' +L.not_your_radio = 'Это не твое радио' +L.evade_ban = 'А-та-та, бегать от бана нехорошо' +L.too_short = 'Слишком коротко' +L.too_long = 'Слишком длинно' +L.onlydobro = 'Это только для добродеев' +L.name_and_surname = 'Имя Фамилия' +L.your_friend = 'Твой друг' +L.wanted_hint = 'В розыске: %s' +L.unknown_reason = 'причина не указана' +L.working_in_police = 'Работает в полиции' +L.add_friend = 'Добавить в друзья' +L.remove_friend = 'Убрать из друзей' +L.admin_mode = 'Режим админа' +L.admin_menu = 'Админ-меню' +L.hud_logs = 'Логи событий' +L.add_whitelist_hint = ' добавлен в вайтлист' +L.remove_whitelist_hint = ' удален из вайтлиста' +L.denied_whitelist_hint = 'Тебя нет в вайтлисте' +L.manage_whitelist = 'Управление вайтлистом' +L.add_in_whitelist = 'Добавить в вайтлист...' +L.add_in_whitelist2 = 'Добавить в вайтлист' +L.print_steamid_hint = 'Введи SteamID игрока:' +L.clear_field = 'Очистить поле' +L.only_features = 'Только избранные' +L.notification = 'Уведомление' +L.analyzer_prints = ', на теле обнаружены отпечатки следующих людей: %s' +L.analyzer_no_prints = ', на теле не обнаружено отпечатков' +L.analyzer_old_prints = ', образец слишком старый' +L.priest_online = 'На сервере есть священник, попробуй его найти' +L.confession = 'Исповедь' +L.confession_help = 'Исповедь помогает нам стать лучше.' +L.god_love_you = 'Бог любит тебя, но очищение приходит с регулярностью посещения церкви, а не с продолжительностью одного визита' +L.wait_boy = 'Остынь, парниша, подожди немного' +L.error_job = 'Что-то пошло не так, попробуй устроиться на работу заново' +L.error_cuffs = 'Нельзя это делать, будучи связанным!' +L.error_tazer = 'Ты ведь даже не стоишь на ногах!' +L.error_character = 'Что-то пошло не так, попробуй сменить персонажа' +L.job_denied = 'Тебе недоступна эта работа!' +L.this_locker_only_for_police = 'Этот шкафчик только для полиции' +L.text = 'Текст' +L.cant_sit_on_players = 'Нельзя сидеть на игроках!' +L.dlib_friends = 'DLib друзья' +L.dlib_open_menu_friends = 'Открыть меню друзей' +L.dlib_friends_edit_add_title = 'Добавление %s <%s> как друга' +L.dlib_friends_edit_edit_title = 'Редактирование настроек друга %s <%s>' +L.dlib_friends_edit_going = 'Вы будете другом с %s в...' +L.dlib_friends_edit_youare = 'Вы являетесь другом с %s в...' +L.dlib_friends_edit_remove = 'Удалить друга' +L.dlib_friends_invalid_title = 'Неверный SteamID' +L.dlib_friends_invalid_ok = 'Окай :(' +L.dlib_friends_invalid_desc = '%q не выглядит как SteamID!' +L.dlib_friends_settings_steam = 'Считать Steam друзей как DLib друзей' +L.dlib_friends_settings_your = 'Ваши друзья ->' +L.dlib_friends_settings_server = 'Игроки на сервере ->' +L.dlib_donate_top = 'DLib: Сделаете пожертвование?' +L.dlib_donate_button_yes = 'Так и сделать (Яндекс.Деньги)!' +L.dlib_donate_button_paypal = 'Так и сделать, только на PayPal!' +L.dlib_donate_button_no = 'Спросить меня позже' +L.dlib_donate_button_never = 'Больше никогда не спрашивать' +L.dlib_donate_button_learnabout = 'Прочитать про "Donationware"...' +L.dlib_donate_more = ' и еще %i аддонов!..' +L.reset_gag = 'Срок запрета разговора закончился' +L.time_left_for_reset_gag = 'До снятия запрета разговора осталось: %s' +L.cap_dobrograd = 'Кепка патриота' +L.cap_dobrograd_desc = 'Доброград в твоем сердце... И на твоей голове' +L.bday_desc = 'Ты отлично выглядишь в этой кепке, с днем города!' +L.you_received_cap = 'Ты уже получил свою кепку' +L.hands_free = 'Руки должны быть свободны' +L.you_dont_have_radio = 'У тебя нет рации' +L.wrong_frequency = 'Частота введена не верно' +L.set_frequency = 'Ты установил частоту группы на свою рацию' +L.could_be_frequency = 'Частоты рации могут быть от 111.1 до 999.1' +L.you_set_frequency = 'Ты установил на частоту ' +L.on_your_radio = ' на свою рацию' +L.enable_radio = 'Рация включена' +L.disable_radio = 'Рация выключена' +L.rating = 'Рейтинг' +L.mark = 'ОЦЕНКА' +L.complaints = 'ЖАЛОБЫ' +L.users = 'ПОЛЬЗОВАТЕЛИ' +L.complaints_per_week = 'ЖАЛОБ В НЕДЕЛЮ' +L.complaint_time = 'ВРЕМЯ ЖАЛОБ' +L.game_time = 'ВРЕМЯ ИГРЫ' +L.game_time_card = 'Карта времени игры' +L.сomplaints_report_card = 'Карта откликов на жалобы' +L.belt_hint = '{name} застегивает ремень' +L.belting_hint = 'Застегивание ремня' +L.unbelt_hint = '{name} расстегивает ремень' +L.unbelting_hint = 'Расстегивание ремня' +L.brax_atm_hint = 'У тебя в кармане ни гроша, поэтому мы сняли тебе ' +L.from_bank = ' со счета в банке' +L.brax_atm_salary = 'На твой банковский счет поступила зарплата в размере ' +L.govorilka_command = '!говорилка' +L.govorilka_command2 = '/говорилка' +L.too_much_documents = 'У тебя слишком много документов, избавься от старых' +L.your_document = 'Этот документ адресован тебе, поэтому теперь ты его владелец' +L.document_freeze = 'Документ закреплен' +L.dont_move_document = 'Документ должен быть неподвижен' +L.giving_document = 'Получатель документа теперь ' +L.giving_document_cancel = 'Передача документа отменена' +L.you_got = 'Ты получил ' +L.for_work_admin = ' за работу администратором!' +L.possible_use = 'Можно будет использовать через ' +L.get_job = 'Получить работу' +L.job_police = 'Ты можешь устроиться на работу в Полицейском Департаменте Доброграда. Для этого нужно оставить заявку на желаемую должность и ожидать решения руководства' +L.job_notallow = 'Ты уже имеешь профессию. Если ты хочешь устроиться на работу в полицейский департамент, уволься с текущей работы' +L.application_sent = 'Заявка отправлена' +L.career_objective = 'Желаемая должность...' +L.application_send = 'Отправить заявку' +L.retire = 'Уволиться' +L.retire_hint = 'Если тебе по какой-то причине не хочется или больше не требуется работать в полиции, ты можешь оставить заявление на увольнение и сразу же покинуть пост блюстителя порядка' +L.demote_player = 'Уволить' +L.against = 'Против' +L.in_favor_of = 'За' +L.choose_worker = 'Выбрать работника...' +L.you_executive_position = 'Ты являешься руководящей должностью и имеешь полномочия увольнять сотрудников. У тебя должна быть веская причина, а также те, кто смогут заменить ушедшего работника' +L.control = 'Управление' +L.you_have_job = 'У тебя новая работа в городе!' +L.cant_find_job = 'Не получилось найти тебе работу в этом районе, попробуй погулять' +L.late_for_job = 'Ты опоздал на выполнение работы!' +L.soda_block = 'Блок газировки' +L.load_soda = 'Загрузка газировки' +L.where_soda = 'А где газировка?' +L.to_buy_soda = 'Чтобы купить газировку, вставь в автомат 200 рублей' +L.run_out_of_bottles = 'В автомате закончились бутылки, попробуй позже' +L.only_200 = 'Автомат принимает только купюры по 200 рублей' +L.automatic_service = 'Автомат на обслуживании, приходи позднее' +L.car = 'Автомобиль' +L.look = 'Приглядеться' +L.freeview = 'Оглянуться' +L.speak_in_radio = 'Говорить в рацию' +L.octo_perma_save = 'Сохранение... Это может заставить сервер зависнуть на время' +L.octo_perma_save_ready = 'Сохранение постоянных предметов завершено' +L.octo_perma_clear = 'Очистка постоянных предметов завершена' +L.octo_perma_load = 'Перезагрузка... Это может заставить сервер зависнуть на время' +L.octo_perma_load_ready = 'Перезагрузка постоянных предметов завершена' +L.light_hint = 'Для большинства случаев мы рекомендуем следующие настройки света: Яркость - 1 или 2, Размер - 300' +L.wire_lenght = 'Длина веревки' +L.light_brightness = 'Яркость' +L.light_size = 'Размер' +L.restart_time = 'Сервер перезагрузится через ' +L.exactly_restart = 'Вот-вот перезагрузимся...' +L.restart = '[#] Перезагружаемся. Присоединяйся через пару минут' +L.restart_minute = ' мин' +L.spawn = 'Воскресить' +L.key_w = 'Газ' +L.key_s = 'Тормоз' +L.key_a = 'Влево' +L.key_d = 'Вправо' +L.key_i = 'Зажигание' +L.key_lalt = 'Сцепление' +L.key_lshift = 'Поддать газу' +L.key_mouse_left = 'Повысить передачу' +L.key_mouse_right = 'Понизить передачу' +L.key_mouse_middle = 'Поворот мышкой' +L.key_space = 'Ручной тормоз' +L.key_r = 'Переключить ручной тормоз' +L.key_f = 'Свет' +L.key_h = 'Сигнал' +L.key_m = 'Сирена' +L.key_k = 'Ручная сирена' +L.key_n = 'Спецсигнал' +L.key_comma = 'Поворотники' +L.key_j = 'Закрыть машину изнутри' +L.key_lcontrol = 'Показать зеркало' +L.key_b = 'Застегнуть ремень' +L.anim_agree = 'Одобрить' +L.anim_becon = 'Подозвать' +L.anim_bow = 'Поклониться' +L.anim_disagree = 'Пригрозить пальцем' +L.anim_salute = 'Воинское приветствие' +L.anim_wave = 'Помахать рукой' +L.anim_forward = 'Сигнал: вперед' +L.anim_group = 'Сигнал: группировка' +L.anim_halt = 'Сигнал: стоп' +L.anim_attack_fist = 'Замахнуться рукой' +L.anim_1hand = 'Махнуть перед собой' +L.anim_place = 'Положить предмет' +L.anim_give = 'Дать предмет' +L.anim_drop = 'Выбросить предмет' +L.anim_throw = 'Кинуть предмет' +L.anim_laugh = 'Насмехаться' +L.anim_cheer = 'Радоваться' +L.anim_persistence = 'Устрашить' +L.anim_dance = 'Танцевать' +L.anim_robot = 'Роботанец' +L.anim_muscle = 'Горячий танец' +L.can_change_job = 'Сменить работу можно в Q -> Персонаж' +L.can_change_name_job = 'Сменить название работы можно в Q -> Персонаж' +L.can_change_name = 'Сменить имя можно в Q -> Персонаж' +L.trash_only_hobo = 'В мусорках копаются только нищие' +L.trash_search = 'Поиск мусора' +L.trash_searched = 'Ты нашел предмет: %s' +L.trash_nothing = 'В мусорке ничего нет, подожди немного, пока мусор накопится' +L.trash_can_search = 'Чтобы искать в мусорке, нужно убрать лишнее из рук' +L.warns_list = { + {'MetaGaming', 60, 'НЕ ролевую информацию нельзя говорить в обычный чат, это портит атмосферу и нарушает правила. Для нее есть отдельные текстовые чаты: \n\n/pm [ник] [текст-сообщения] – личный НЕ ролевой чат\n/ [текст-сообщения] – локальный НЕ ролевой чат\n// [текст-сообщения] – глобальный НЕ ролевой чат \n\nЕсли ты узнал что-то вне ролевого чата или за пределами игры и воспользовался этим в самой игре, это тоже нарушение! Используй только ту информацию, которую получил в ролевом процессе.\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'PowerGaming', 30, 'Не преувеличивай физические или психические способности твоего персонажа! Находишься под прицелом – испугайся и не двигайся, рядом стрельба – прячься в укрытие или уходи подальше, слишком высоко – не прыгай. В общем, бойся того, чего побоялся бы обычный человек, а иначе это приведет к наказанию!\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'Беспричинный вандализм', 40, 'Бить окна просто так, даже оправдывая себя, как вандала – нельзя! У твоего персонажа должна быть четкая причина для этого. Ответ в виде «не понравился магазин» не подойдет. Поэтому, если у тебя есть реальный мотив делать это, НЕ выдуманный на ходу, ты можешь отправить его в @ [текст-сообщения], если же его нет, то пожалуйста прекрати нарушать атмосферу, а иначе это приведет к наказанию!\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'Вывеска', 30, 'Твоя вывеска использует слишком яркие цвета или выглядит не реалистично. Пожалуйста поправь ее и сделай более аккуратной, будто она висит на настоящем магазинчике. Если у тебя возникли вопросы, что именно не так, ты можешь уточнить это в @ [текст-сообщения]'}, + {'Граффити', 30, 'Граффити с нелегальной рекламой может находится в малолюдных местах, переулках и быть небольшого размера, белого или серого полупрозрачного цвета. Его нельзя делать слишком заметным и размещать у всех на виду, поэтому, пожалуйста исправь это.\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'Игра в свою сторону', 30, 'В текстовых отыгровках (/me, /it, /roll) нельзя переводить игру в свою пользу, если это противоречит логике. Когда твой шанс (/roll) выше, чем у оппонента, это не дает права совершать невыполнимые действия или брать вещи из ниоткуда. Просьба, не нарушай это правило. Если у тебя появились вопросы по этой теме, задай их администрации онлайн через @ [текст-вопроса].\n\nПолный список правил есть в F4 – Помощь – Правила сервера'}, + {'Игра плохого копа', 40, 'Если ты решил сыграть роль плохого полицейского, тебе нужно получить одобрение администратора. Если оно уже есть, укажи ник админа или предоставь доказательство в @ [текст-сообщения] или в / [текст-сообщения]. Если же нет, тогда тебе нужно закончить ситуацию и отпустить невиновных граждан из-за недостачи доказательств.\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'Нарушение атмосферы', 20, 'Твои действия портят игровую атмосферу. Постарайся не противоречить ролевой логике и не мешай игрокам вокруг! Просим тебя прекратить, а иначе это может привести к наказанию.\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'Настрой свет', 30, 'Свет, установленный тобой, слишком яркий. Сделай его помягче и сократи «дистанцию» или вовсе выключи его, пока тебя нет дома, а по возвращению настрой так, чтобы он не проходил сквозь стены и был приятен глазу'}, + {'Нереалистичная постройка', 45, 'Если ты видишь это сообщение, значит с твоей постройкой могут быть следующие проблемы: слишком яркие, не подходящие цвета или материалы, ее пропы не вписываются в окружение или она сама по себе не подходит под сеттинг и лор Доброграда.\n\nМы собрали статью с инструкциями, как правильно оформить постройку на нашем сервере, вот ссылка на нее – octothorp.team/link/lookbook. Также ты можешь задать вопросы по этой теме администрации через @ [текст-вопроса].\n\nПожалуйста, исправь все нереалистичные детали, иначе это может привести к удалению постройки или даже выдаче наказания.\n\nПолный список правил есть в F4 – Помощь – Правила сервера'}, + {'Описание внешности', 30, 'В этом пункте настроек персонажа описываются видимые черты его внешности: национальность, рост, телосложение, одежда и прочее. Там не должно быть названия профессии, отрывков биографии и всякой не ролевой информации. Поэтому просьба, исправь описание и смени персонажа, чтобы оно применилось.\n\nЕсли у тебя появились вопросы по этой теме, задай их администрации через @ [текст-вопроса]'}, + {'Последнее предупреждение', 20, 'Просим тебя прекратить нарушать правила, если ты продолжишь, то придется выдать наказание, но мы бы не хотели этого.\n\nПолный список правил есть в F4 - Помощь - Правила сервера'}, + {'Ролевое имя', 30, 'Имя должно быть похожим на существующее, которое можно записать в паспорт. Его нужно указывать на русском языке, вместе с фамилией, не по отдельности. Нельзя использовать имена популярных личностей. Пример подходящих имен: Генри Палмер, Виктор Степанов. Опираясь на это правило, измени имя своего персонажа на более подходящее!'}, + {'Технические действия (механ)', 35, 'Твои действия противоречат ролевой игре. Постарайся исправить это, больше не повторяя их или если это возможно, используй текстовые отыгровки (/me, /it, /roll). Например, нельзя просто украсть предмет на клавишу Е, перед этим нужно все обыграть в чате, чтобы остальные понимали, что произошло'}, +} + +L.inventory_search = 'Обыскать' +L.action_search = 'Обыск' +L.format_admintellall = 'Формат команды: /admintellall Секунды Заголовок Текст' +L.command_disabled = 'Команда отключена' +L.use_police = 'использовать полицейскую повестку' +L.no_action = 'Нет действий' +L.left_choose = 'ЛКМ - выбрать' +L.right_choose = 'ПКМ - отменить' +L.thankTexts = { + '%s - добрейшей души человек, скажите ему спасибо за поддержку сервера!', + 'Сегодня %s сделал очень хорошее дело - он бескорыстно пожертвовал серверу немного денег!', + '%s - герой сегодняшнего дня!', + 'У нас новый почетный житель Доброграда: %s!', + 'Вот этот парень действительно крут -> %s', + '%s - красавчик. Побольше бы таких ;)', +} +L.update_balance = 'Твой счет пополнился на ' +L.item_dobrodey = 'Добродей' +L.item_coffee = 'Чашечка кофе' +L.item_govorilka = 'Говорилка' +L.free_delivery = 'Бесплатная доставка' +L.use_item_govorilka = 'Ты активировал говорилку на месяц' +L.item_big_storage = 'Большое хранилище' +L.item_builder = 'Строитель' +L.use_item_builder = 'Ты активировал набор «Строитель» на месяц' +L.item_story = 'Рассказчик' +L.item_trader = 'Трейдер' +L.item_trader_desc = [[Позволяет стать большим игроком на рынке, давая ряд преймуществ: +– Лимит заявок увеличен вдвое +– Время действия заявки увеличено на 50% +– Комиссия за выставление лота уменьшена вдвое]] +L.use_item_trader = 'Ты активировал набор «Трейдер» на месяц' +L.use_item_story = 'Ты активировал набор «Рассказчик» на месяц' +L.bank_hint = 'Банк: ' +L.karma_hint = 'Карма: ' +L.energy = 'Сытность' +L.max_energy = 'Макс. сытость' +L.lock_cont = 'Кол-во пинов' +L.resultText = 'Объем: %sл||Масса: %sкг||Перетащи в инвентарь, чтобы выдать' +L.not_have_access = 'У тебя нет доступа' +L.octoinv_editor = 'Игроки' +L.nutrition = 'Питание' +L.nutrition_hint = 'Питает в течение %d:%02d' +L.octoinv_desc = 'Описание' +L.icon = 'Иконка' +L.model = 'Модель' +L.item_val = 'Объем одного предмета (л)' +L.item_massa = 'Масса одного предмета (кг)' +L.quantity = 'Количество' +L.result = 'Результат' +L.name_octoinv = 'Имя' +L.copy_steamid = 'Скопировать SteamID' +L.open_profile = 'Открыть профиль' +L.edit = 'Редактировать' +L.not_desc = 'Нет описания' +L.set_bank = 'Установить банк' +L.set_bank_hint = 'Новое значение счета в банке:' +L.set_karma = 'Установить карму' +L.set_karma_hint = 'Новое значение кармы:' +L.my_inventory = 'Мой инвентарь' +L.give_item = 'Выдать предмет' +L.item_lock = '-пиновый замок' +L.slider = 'Вместительность' +L.error_search = 'Ошибка выполнения запроса: ' +L.dont_edit = 'Сейчас этого игрока нельзя редактировать' +L.ingredients = 'Ингредиенты:' +L.tools_hint = 'Инструменты:' +L.total = 'Итог:' +L.anywhere = 'Где угодно' +L.recipes = 'Рецепты' +L.can_do = 'Могут делать: ' +L.not_required = 'Не требуется' +L.process = 'Процессы' +L.search_steamid = 'Поиск по SteamID' +L.search_content = 'Поиск по содержанию' +L.steamid_edit = ' - Редактирование' +L.qty = 'Кол-во' +L.copy_id = 'Скопировать ID' +L.items = 'Предметы' +L.some_kind_of_action = 'Какое-то действие' +L.msgPrivate = 'Привет! Кажется, у тебя закрыт профиль или список игр. Из-за участившихся случаев обхода блокировок мы вынужены подтверждать владение игрой у людей, которые заходят на сервер. Чтобы убедиться, что все в порядке, нам нужно, чтобы ты ненадолго открыл профиль, список друзей и игр в настройках приватности. Сразу после проверки ты можешь вернуть все на свои места, она происходит всего один раз. Чтобы перепроверить свой профиль, напиши в чат !checkprofile' +L.checkprofile = 'Привет! Подожди немного, пока мы проверяем твой профиль... Это совсем недолго, честно!' +L.crawl_attempt = 'Попытка обхода бана' +L.ban_on_another_server = 'Бан на другом сервере: ' +L.ban_on_another_account = 'Бан на другом аккаунте: ' +L.job = 'Работа' +L.change_job = 'Сменить работу' +L.change_name = 'Изменить имя' +L.sell_all_doors = 'Продать все двери' +L.global_actions = 'Глобальные действия' +L.detail_already_set = 'На этой машине уже установлена эта деталь' +L.car_not_found = 'Автомобиль не найден' +L.detail_not_set = 'На этой машине не установлена эта деталь' +L.att_not_specified = 'Деталь не указана' +L.car_closed = 'Автомобиль закрыт' +L.car_you_need_be_friend = 'Ты должен быть другом владельца' +L.item_does_not_fit = 'Деталь не подходит к этому автомобилю' +L.limit_hint = ' (лимит)' +L.reason_find_dobro = 'добродеев' +L.reason_dobro = 'для Добродеев' +L.reason_play = 'играть' +L.need_play = 'нужно играть ' +L.reason_unavailable = 'недоступно' +L.call_admin = 'Вызов администратора' +L.call_admin_hint = 'Вызов администратора' +L.call_admin_question = 'Что ты хочешь узнать или решить?' +L.your_question = 'Твой вопрос' +L.run_in_discreet_place = 'Теперь тебе нужно убежать в укромное место, которое ты видишь на экране. Если ты передумал, нажми на кнопку:' +L.donate = 'Плюшки' +L.directory = 'Справочник' +L.working_at = 'Мы пока еще над этим работаем...' +L.fixes = 'Фиксы' +L.help = 'Помощь' +L.actions = 'Действия' +L.gestures = 'Анимации' +L.about_project = 'О проекте' +L.about_project_hint = [[Совсем скоро тут будет небольшое описание нашего проекта.]] +L.stop_sound = 'Остановить все звуки' +L.release_all_sticking_buttons = 'Отпустить все кнопки залипания' +L.settings = 'Настройки' +L.building = 'Строительство' +L.main = 'Основное' +L.kitchen = 'Кухня' +L.bathroom = 'Ванная' +L.workshop = 'Мастерская' +L.fence = 'Ограда' +L.filling = 'Наполнение' +L.pictures = 'Картины' +L.plants = 'Растения' +L.decor = 'Декор' +L.structure = 'Структура' +L.open_wiki = 'Открыть вики' +L.training = 'Обучение' +L.textEmpty = { + 'Здесь очень одиноко...', + 'Пустовато, конечно...', + 'Заполни меня ^_^', + 'Где твои плюшки?', +} +L.octoshop_break = 'Сломано' +L.octoshop_break_hint = 'Эта древняя реликвия уже давно пропала с полок нашего магазина. Где ты это вообще достал?' +L.what_it = 'Что это?' +L.menu = 'Меню' +L.apply_coupon = 'Применить купон' +L.refresh_shop = 'Обновить данные' +L.create_coupon = 'Создать купон' +L.create = 'Создать' +L.create_hint = 'Сколько рублей будет выдано по этому купону:' +L.replies = { + {'спрашивает', 'Ты принес тыковки?'}, + {'спрашивает', 'Чего тебе?'}, + {'спрашивает', 'Ну что там опять?'}, + {'говорит', '10 штук или уходи ни с чем'}, +} +L.replies_given = { + {'говорит', 'Вот сундучок. Если будет еще, тащи сюда же'}, + {'говорит', 'Как раз то, что мне нужно, держи сундучок'}, +} +L.take_your_trash = {'спрашивает', 'Забери свое барахло, зачем мне это?'} +L.take_your_trash2 = {'спрашивает', 'Ну и что это? Когда будет хотя бы 10, тогда и поговорим'} +L.wear = 'Надеть' +L.item_break = 'Данные предмета повреждены' +L.next_bus = 'Следующий автобус: ' +L.price_bus = 'Стоимость проезда: ' +L.sit_bus = 'Чтобы поехать, сядь на лавочку' +L.sit_bus_hint = 'Сиди тут, если хочешь поехать на нем' +L.help_sit = 'Чтобы сесть, нажми %s+%s' +L.bus_you_wanted = 'Ты в розыске, никто тебя не повезет' +L.bus_not_enough_money = 'У тебя не хватает денег на проезд' +L.methods = { + {'Бесплатная доставка', 'Обычная доставка, посылка приходит в случайный ящик' }, + {'Экспресс (+5%)', 'Доставка в случайный ящик за 30-60 секунд'}, + {'Эксклюзив (+20%)', 'Доставка в один из ближайших ящиков за 1-2 минуты'}, +} +L.refresh = 'Обновить' +L.basket = 'Корзина' +L.chat_settings = 'Настройки чата' +L.chat_settings_hint = 'ESC - закрыть чат, F3 - показать курсор, F4 - настройки, стрелки - история' +L.full_transparency = 'Полная прозрачность' +L.fading_door_key = 'Дверь нельзя открывать с клавиатуры' +L.use_keypad_or_button = 'Используй кнопку или кейпад' +L.empty = 'Пусто' +L.no_access_create_inventory = 'Тебе недоступно создание инвентарей' +L.inventory_volume = 'Объем' +L.can_craft = 'Возможность крафта' +L.description_inventory = 'С помощью этого инструмента можно добавить инвентарь к почти любому предмету' +L.search_time = 'Время обыска' +L.save_changes = 'Сохранить изменения' +L.delete_container = 'Удалить контейнер' +L.add_container = 'Добавить контейнер' +L.add_container_to_prop = 'Добавить инвентарь предмету' +L.assign = 'Присвоить' +L.tool_copy = 'Скопировать' +L.tools = 'Инструменты' +L.build = 'Сборка' +L.locks = 'Замки' +L.blueprint = 'Чертежи' +L.parts = 'Комплекты деталей' +L.conts = 'Контейнеры' +L.electronic = 'Гаджеты' +L.everyday = 'Повседневное' +L.prod = 'Производство' +L.pharm = 'Медикаменты' +L.to_car = 'Для автомобилей' +L.car_parts = 'Детали кузова' +L.car_atts = 'Аксессуары для авто' +L.dish = 'Посуда' +L.ings = 'Продукты' +L.machinebp = 'Программы для станка' +L.resources = 'Сырье' +L.ammo = 'Амуниция' +L.item = 'Предмет' +L.octo_desc = 'Добавь описание к "присмотреться"' +L.octo_desc_hint = 'Этот инструмент поможет создать описание к предметам для системы "приглядеться"' +L.octo_desc_help = 'Описание появится только после полного рассмотрения предмета' +L.octo_desc_empty = 'Если оставить название пустым, оно не будет отображаться до осмотра предмета, однако иконка будет отображаться' +L.time_see = 'Время рассмотрения' +L.desc_item = 'Описание предмета' +L.octo_perma = 'Сделать постоянным' +L.octo_perma_restart = 'Перезагрузить' +L.prop_set_perma = 'Этот предмет помечен как постоянный' +L.prop_not_perma = 'Этот предмет не является постоянным' +L.trigger = 'Триггер' +L.title2 = 'Заголовок' +L.trigger_text = 'Текст уведомления' +L.trigger_desc = 'Местный триггер для уведомлений и звуков' +L.trigger_left = 'ЛКМ: Создать триггер' +L.trigger_undone = 'Триггер удален' +L.triggers = 'Триггеры' +L.triggers_plus = 'Триггеры (расширенные)' +L.trigger_cleaned = 'Все триггеры удалены' +L.trigger_limit = 'Лимит триггеров достигнут' +L.trigger_description = 'Привет! Это инструмент, в котором можно создавать зоны, в которых игроки будут получать текстовые и звуковые уведомления' +L.trigger_chat = 'В чат' +L.trigger_center = 'Центр экрана' +L.color = 'Цвет' +L.width = 'Ширина' +L.depth = 'Глубина' +L.height = 'Высота' +L.duration_sec = 'Длительность (сек)' +L.trigger_duration_hint = 'Длительность отображения сообщения. Работает не для всех типов уведомлений' +L.trigger_type_notification = 'Тип уведомления' +L.game_sound = 'Игровой звук' +L.browser_sound = 'Браузер звуков' +L.url_sound = 'Звук по URL' +L.trigger_times_hint = 'Количество раз, которое триггер сработает. Поставь 0, чтобы не ограничивать' +L.trigger_times = 'Срабатывания' +L.show_existing = 'Показывать существующие' +L.check_perma = 'Проверить на постоянность' +L.octo_perma_desc = 'Предмет будет появляться при загрузке карты' +L.octo_perma_hint = 'Этот инструмент позволяет сделать предметы постоянными. Они будут создаваться заново при перезагрузке сервера или карты' +L.remove_from_map = 'Убрать с карты' +L.remove_from_map_hint = 'Удаляет все предметы, стоящие на карте, это не затрагивает записанные сохранения' +L.perma_save_hint = 'Сохраняет текущее состояние постоянных предметов, чтобы создать точные копии при перезапуске сервера или перезагрузке предметов. Это перезаписывает предыдущее сохранение, так что если сохранить пустую карту, то все сохраненные предметы "очистятся"' +L.perma_restart_hint = 'Удаляет и заново ставит все постоянные предметы из последнего сохранения' +L.close = 'Закрыть' +L.purse_hint = 'Кошелек: ' +L.salary_hint = 'Зарплата: ' +L.players_hint = 'Игроков: ' +L.play_time_hint = 'Время игры: ' +L.f4_failure = 'Произошла ошибка при открытии меню! Попробуй обратиться к администрации сервера' +L.death_cannot_do_this = 'Мертвые не могут этого делать!' +L.you_arrested = 'Ты арестован!' +L.handcuffed_cannot_do_this = 'Ты не можешь делать это, будучи связанным' +L.gesture = 'Жесты' +L.health = 'Здоровье' +L.barhunger = 'Сытость' +L.move_there = 'Переместить сюда' +L.drop_items = 'Выбросить предметы' +L.what_hint = 'Что: ' +L.where_octoinv = '\nОткуда: ' +L.where2_octoinv = '\nКуда: ' +L.amount_octoinv = '\n\nСколько?' +L.something_in_inventory = 'Что-то в инвентаре!' +L.press_q = 'Зажми Q, чтобы открыть меню' +L.settings_in_respawn = 'Настройки применяются при возрождении персонажа. Если ты хочешь изменить данные прямо сейчас, нажми на кнопку:' +L.cancel_changes = 'Отменить смену' +L.change_character = 'Сменить персонажа' +L.my_shadow = 'Моя тень' +L.city_behind = 'Город за картой' +L.graphic = 'Графика' +L.have_cigarette = 'У тебя уже есть сигарета' +L.use_cigarette = 'Выкурить сигарету' +L.desc_character = 'Описание персонажа' +L.high_shadow = 'Тени высокого качества' +L.high_light = 'Свет высокого качества' +L.draw_flekcs = 'Мелкие частицы' +L.draw_model_decals = 'Декали на объектах' +L.waterdrawreflection = 'Отражение в воде' +L.waterdrawrefraction = 'Преломление в воде' +L.showfps = 'Показывать FPS' +L.view_dist = 'Видимость объектов' +L.snow = 'Снег' +L.enable = 'Включить' +L.life_time = 'Время жизни' +L.gameplay = 'Геймплей' +L.sticky_duck = 'Приседание' +L.space = 'Расстояние' +L.dbg_help_login = 'Показывать справку на входе' +L.voicemods = 'Изменять дальность разговора (крик/шепот)' +L.sticking = 'Залипание' +L.take_in_hand = 'Взять в руки' +L.silient_take_in_hand = 'Тихо взять в руки' +L.unknown_type_ammo = 'Неизвестный тип патронов' +L.charge = 'Зарядить' +L.take_weapon = 'Оружие можно взять только в руки' +L.hands_busy_driver = 'Руки заняты рулем' +L.unknown_type_weapon = 'У этого предмета неизвестный тип оружия' +L.you_already_take_weapon = 'У тебя в руках уже есть такое оружие' +L.max_hunger_hint = 'Ты слишком сыт для этого' +L.eat = 'Съесть' +L.drink = 'Выпить' +L.you_already_have_armor = 'На тебе и так хороший бронежилет' +L.octoinv_empty = 'Здесь одиноко...' +L.octoinv_empty2 = 'Тут ничего нет. Совсем' +L.octoinv_empty3 = 'Хм... Пусто' +L.not_found = 'Ничего не найдено' +L.drop_cost = 'Выкинуть количество' +L.drop_all = 'Выкинуть все' +L.move_cost = 'Переместить количество' +L.move_all = 'Переместить все' +L.move_items = 'Переместить предметы' +L.move = 'Переместить' +L.octoinv_free = 'Свободно %sл' +L.search_container = 'Поиск по контейнеру' +L.search_or_filter = 'Поиск / фильтр' +L.enable_or_disable = 'Включить / выключить' +L.write_part_name = 'Введи часть названия предмета:' +L.craft = 'Крафт' +L.all = 'Все' +L.cost = 'Количество...' +L.octoinv_use = 'Использовать...' +L.nothing_do_this = 'Ты ничего не можешь с этим сделать' +L.action_unavailable = 'Это действие недоступно' +L.car_front_suspension = 'Передняя подвеска' +L.rigidity = 'Жесткость' +L.car_blanking = 'Гашение' +L.car_takeaway = 'Вынос' +L.car_rear_suspension = 'Задняя подвеска' +L.car_collapse = 'Развал' +L.unavailable = 'Недоступно' +L.check_action = 'Проверка' +L.check_hint = 'Проверить' +L.emote_check = '{name} проверяет автомобиль по базе данных' +L.evacuation_emote = '{name} запрашивает эвакуатор' +L.evacuate = 'Эвакуировать' +L.evacuate_hint_ply = 'Если владелец не заберет автомобиль через 10 минут, его эвакуируют' +L.evacuate_hint_owner = 'Полиция запросила эвакуатор для твоего автомобиля. Ты можешь забрать его в течение 10 минут' +L.sit = 'Сесть' +L.plant_the_detainee = 'Посадить задержанного' +L.to_land = 'Высадить' +L.to_land_all = 'Всех' +L.suspension = 'Подвеска' +L.take_off = 'Снять' +L.dismantling = 'Демонтаж' +L.need_suspension = 'Нужен регулятор подвески' +L.regulate = 'Регулировать' +L.need_wrench_and_screwer = 'Нужен гаечный ключ и отвертка' +L.car_owner_hint = 'Владелец автомобиля: ' +L.vend_money = 'Автомат (деньги)' +L.vend_manage = 'Для управления автоматом зажми клавишу [ИСПОЛЬЗОВАТЬ]' +L.for_buy_vend = 'Для покупки вставь деньги в автомат' +L.forbidden_to_load = 'Это запрещено загружать в автомат' +L.vend_slot = 'Слот #' +L.vend_slot_empty = 'В этом слоте закончился товар' +L.vend_need_closed = 'Автомат должен быть закрыт' +L.vend_accurate_money = 'Нужно вложить точную сумму, указанную на экране' +L.read = 'Читать' +L.change = 'Изменить' +L.replace = 'Замена' +L.remove_sign = 'Убрать подпись' +L.add_sign = 'Подписать' +L.freeze = 'Закрепить' +L.unfreeze = 'Открепить' +L.write_destroy = 'Скомкать' +L.cancel_give = 'Отменить передачу' +L.write_give = 'Передать' +L.change_letter = 'Изменить письмо' +L.write_sign = 'ПОДПИСЬ:\n' +L.your_character_cannot_take_job = 'Твой персонаж не может получить свою работу сейчас' +L.arrive_in_town = ' приехал в город!' +L.somebody_near = 'Кто-то рядом, сменить персонажа не получится' +L.go_discreet_place = 'Иди в укромное место и убедись, что рядом никого нет' +L.cant_change_now = 'Не получится сменить персонажа сейчас' +L.you_already_change = 'Режим смены персонажа уже активен' +L.you_not_already_change = 'Смена персонажа и так не активна' +L.job_limit = 'Достигнут лимит данной профессии' +L.character_change_cancel = 'Смена персонажа отменена' +L.whisper_hint = ' шепчет: ' +L.yell_hint = ' кричит: ' +L.this_only_admin = 'Это только для администрации' +L.advert_from = 'Реклама от' +L.use = 'Использовать' +L.throw = 'Бросить' +L.set = 'Установить' +L.set_hint = 'Установка' +L.already_lock = 'Здесь уже стоит замок получше' +L.to_put = 'Поставить' +L.need_unwear_police_form = 'Нужно снять полицейскую форму' +L.wear2 = 'Переодеться' +L.you_cannot_wear = 'Ты не можешь переодеться' +L.seal_bruises = 'Заклеить ушибы' +L.are_you_okay = 'Ты в порядке' +L.you_cant_plaster = 'Тут пластырем не обойтись' +L.need_see_on_item = 'Нужно смотреть на предмет' +L.allow_buy = 'Разрешить покупку' +L.disallow_buy = 'Запретить покупку' +L.door_unlock = 'Открыть замок' +L.door_lock = 'Закрыть замок' +L.add_co_owner = 'Добавить совладельца' +L.remove_co_owner = 'Добавить совладельца' +L.death_lose_job = 'Ты потерял свою должность из-за смены персонажа' +L.vote_fool = 'За кого ты голосуешь, дурак?!' +L.not_your_choice = 'Не тебе решать, получит он должность или нет!' +L.him_dont_stay = 'Он ведь даже не стоит на ногах!' +L.request_approved = 'Заявка одобрена' +L.your_request_approved = 'Твоя заявка одобрена' +L.request_rejected = 'Заявка отклонена' +L.your_request_rejected = 'Твоя заявка отклонена' +L.vote_yourself = 'Голосуешь сам за себя? Попахивает онанизмом' +L.already_vote = 'Ты уже голосовал!' +L.vote_does_not_exist = 'Такого голосования не существует!' +L.win_vote_one = 'Ты победил в голосовании! Ты же один на сервере, лол' +L.already_sent_vote = 'Заявка уже подана' +L.want_job_hint = '%s хочет получить должность %s' +L.vote_started_hint = 'Голосование создано' +L.application_sent_hint = 'Заявка отправлена' +L.you_retired = 'Ты уволился из полиции' +L.you_cant_demote_up = 'Ты не можешь его уволить. Он выше тебя по рангу' +L.you_cant_demote = 'Ты не можешь уволнять сотрудников полиции' +L.now_you = 'Теперь ты %s! Заступить на службу можно в полицейской раздевалке. Секретная частота экстренных служб - ems. Ты можешь сменить частоту своей рации без формы' +L.need_wait_demote = 'Нужно подождать после предыдущего голосования' +L.already_vote_hint = 'Голосование уже идет' +L.won_in_vote = '%s победил в голосовании' +L.lose_in_vote = '%s проиграл в голосовании' +L.already_work = 'На этой должности уже работает ' +L.you_wanted = 'Ты в розыске!' +L.bad_reputation = 'У тебя слишком плохая репутация!' +L.hobo_cant_work = 'Бездомные не могут работать в полиции!' +L.job_doesnt_exist2 = 'Такой работы не существует!' +L.wait_after_demote = 'Подожди немного после увольнения' +L.you_not_work_in_police = 'Ты и так не работаешь в полиции' +L.automatic_accepted = 'Твоя заявка принята автоматически, т.к. начальников нет' +L.he_not_work_in_police = 'Он и так не работает в полиции' +L.police_demoted_by_you = 'Ты уволил %s из полиции' +L.police_demoted_you = '%s уволил тебя из полиции' +L.display_time_sec = 'Время отображения (сек)' +L.damping_time_sec = 'Время затухания (сек)' +L.font_size = 'Размер шрифта' +L.use_bold = 'Использовать жирный шрифт' +L.show_cursor_open = 'Показывать курсор при открытии' +L.add_text_shadow = 'Добавить тень под текст' +L.enter_open_chat = 'Открывать чат на Enter' +L.show_seconds_in_time = 'Показывать секунды во времени' +L.show_entering_text = 'Отображать введеный текст при закрытом чате' +L.add_time_msg = 'Добавлять к сообщению время' +L.close_sent_msg = 'Закрывать после отправки сообщения' +L.show_help_manage = 'Показывать подсказку управления' +L.close_chat_empty = 'Закрывать при отправке пустого сообщения' +L.reset_chat = 'По умолчанию' +L.clear_drug = '%s чист' +L.illegal_drugs_hint = 'Обнаружены нелегальные вещества!' +L.not_found_illegal_drugs_hint = 'Нелегальных веществ не обнаружено' +L.on_drugs = '%s под влиянием наркотиков: ' +L.illegal = 'НЕЛЕГАЛЬНО' +L.request_player = 'Уведомить игрока' +L.time = 'Время' +L.play = 'Играть' +L.left_from_server = 'Выйти с сервера' +L.marker = 'Вызов' +L.call_mech_hint = '{name} вызывает механика' +L.sms_hint = '{name} отправляет SMS' +L.ticket_admins = 'На сервере есть администрация! Открой жалобу через "@ текст"' +L.target_sms = ' получает SMS от тебя: ' +L.owner_sms = ' пишет тебе SMS: ' +L.format_toit = 'Формат команды: /toit действие * фраза' +L.collect = 'Собрать' +L.need_pump = 'Нужен насос' +L.pump_up = 'Накачать' +L.repair_action = 'Починка' +L.this_only_mech = 'Это только для механиков' +L.admin_hint = 'Админ' +L.disable2 = 'Отключить' +L.reason_kick = 'Причина кика' +L.reason_kick_hint = 'Укажи причину кика' +L.reason_ban = 'Причина бана' +L.reason_ban_hint = 'Укажи причину бана' +L.length_ban = 'Срок бана' +L.length_ban_hint = 'Укажи срок бана' +L.rank_hint = 'Ранг: ' +L.welcome_dobrograd = 'Добро пожаловать на Доброград!' +L.choose_server = 'Выбери сервер для продолжения' +L.open_rules = 'Открыть правила' +L.pass_test = 'Пройти тест' +L.new_dobrograd_desc = 'Если ты не уверен в своем умении грамотно развивать персонажа и объяснять все его поступки с точки зрения характера и мотивации, то лучше начать отсюда' +L.new_dobrograd_desc2 = 'Здесь правила в основном те же, но допускается больше так называемого "механа", нежели на первом Доброграде. Таким образом неподготовленному игроку тут будет намного проще, но все же это Доброград, а значит, что это не место для NonRP, убийств без причины и подобных грубых нарушений ролевой игры' +L.new_dobrograd_desc3 = 'Несмотря на это, вход открыт для всех, но правила соблюдать обязательно нужно' +L.went_to_1 = 'Поехать на #1' +L.went_to_2 = 'Поехать на #2' +L.back_tomorrow = 'Приходи завтра' +L.new_dobrograd2_desc = 'Этот сервер подходит для тех, кто уже хорошо разбирается в ролевой игре и уверенно себя чувствует, общаясь с другими игроками и отыгрывая разные роли' +L.new_dobrograd2_desc2 = 'Правила здесь несколько серьезнее, они обязывают игроков заранее описывать своих персонажей для выполнений серьезных действий вроде убийства, а также не выходить за рамки характера своего персонажа' +L.new_dobrograd2_desc3 = 'В связи с этим при входе на сервер проводится небольшой тест в двух категориях: Ролевые понятия и Адекватность. Но бояться нечего - тест достаточно легко пройти, если есть хоть небольшой опыт игры' +L.choose_answer = 'Выбрать ответ...' +L.answer_hint = 'Ответить' +L.ready = 'Готово' +L.request = 'Запрос' +L.fill_required = 'Заполни обязательные поля' +L.choose_value = 'Выбери значение' +L.there_is = 'Есть' +L.send = 'Отправить' +L.stop = 'Стоп' +L.search = 'Поиск' +L.take = 'Взять' +L.results = 'Результаты' +L.connection_fail_vk = 'Произошла ошибка соединения с VK! Попробуй чуть позже' +L.vk_user_not_found = 'Пользователь не найден, проверь правильность ссылки' +L.your_status = 'Твой статус: ' +L.vk_exist = '<отсутствует>' +L.vk_set_status = 'Чтобы мы поняли, что это ты, поставь статус: octoteam' +L.vk_salary = 'Ты получил 1 фишку за закрепленный пост ВК!' +L.vk_not_post = 'Ты закрепил не тот пост, если ты хочешь получать бесплатные фишки, надо закрепить репост этой новости: ' +L.vk_back_status = 'Пользователь ВК успешно прикреплен. Теперь ты можешь вернуть свой статус обратно' +L.go_to_town = '%s на пути в город %s!' +L.not_appear_here_before = '%s раньше тут не появлялся' +L.too_long2 = 'Слишком длинное' +L.too_short2 = 'Слишком короткое' +L.go_from_town = '%s уехал из города' +L.already_reward = 'Ты уже получил свою награду ;)' +L.bonus_in_atm = 'Твои бонусные деньги находятся в банкомате ;)' +L.bonus_get = '%s (%s) получил %s за регистрацию на форуме!' +L.not_your_acc = 'Это не твой аккаунт' +L.not_have_steamid = 'У этого пользователя не прикреплен SteamID, напиши администрации об этом' +L.bonus_confirm_email = 'Твой аккаунт не активирован. Подтверди свою почту для продолжения' +L.bonus_hint = 'Ты получаешь еще не все бонусы от нас! Введи команду /rewards в чат, чтобы узнать, какие бонусы можно получить' +L.bonus_paste_link = 'Вставь ссылку на свой аккаунт на форуме после команды' +L.bonus_not_found = 'Не удалось найти пользователя' +L.can_do_only_mayor = 'Это может делать только мэр' +L.title_change_to_dobrograd = 'Название города изменено на Доброград' +L.you_dead = 'Ты мертв!' +L.name_town_change = 'Название города изменено на %s' +L.town_names = { + rp_eastcoast_v4c = '[#] Истории города Доброград', + rp_evocity_dbg_220222 = '[#] Новый Доброград', + rp_truenorth_v1a = '[#] Новый Доброград #2', + rp_riverden_dbg_220313 = '[#] Центральный Доброград', +} +L.build_town = '[#] Строим ' +L.history_town = '[#] Истории города ' +L.you_already_have_storage = 'У тебя уже есть хранилище' +L.not_enough_money2 = 'Маловато деньжат для этого' +L.title_too_long = 'Длинноватое название. Попробуй поменьше' +L.this_feature_only_dobro = 'Эта плюшка только для добродеев!' +L.postfix_question = ' спрашивает: ' +L.postfix_say = ' говорит: ' +L.postfix_smile = ', улыбнувшись,' +L.postfix_sad = ' грустно' +L.sad_hint = '{name} выглядит грустно' +L.smile_hint = '{name} улыбается' +L.inspection_hint = 'Осмотр - ' +L.inspection_help = 'Мышь - поворот предмета, колесико - увеличение' +L.clear = 'Очистить' +L.butbuy = 'Заказать' +L.defaultDesc = 'Хмм... Что-то очень странное. Никто не знает как этим пользоваться, потому что разработчики пока что не добавили сюда описание...' +L.something_change_shop = 'Что-то изменилось с твоего последнего визита. Скорей всего, нужно обновить данные' +L.set_quantity = 'Установить количество' +L.set_quantity_hint = 'Новое количество товара:' +L.continue_hint = 'Продолжить' +L.top_up_balance = 'Пополнить баланс' +L.add_friend_tab = 'Добавь игрока в друзья в TAB-меню, чтобы он отобразился здесь' +L.only_specified = 'Получатель заказа, его сможет забрать только указанный человек' +L.price2 = 'Цена' +L.player_on_server = 'Этот игрок сейчас на этом сервере, поэтому его нельзя редактировать' +L.player_on_old_dbg = 'Этот игрок сейчас на ИГД, поэтому его нельзя редактировать' +L.player_on_new_dbg = 'Этот игрок сейчас на НДБГ, поэтому его нельзя редактировать' +L.can_not_edit_player = 'Нельзя редактировать инвентарь игрока, который сейчас на сервере!' +L.can_not_edit_player_storage = 'Нельзя редактировать инвентарь игрока, хранилище которого сейчас на сервере!' +L.data_saved = 'Данные сохранены' +L.call_police_hint = '{name} вызывает экстренные службы по телефону' +L.wanted_hint2 = '%s в розыске!' +L.karma_arrest = 'Если ты в тюрьме, ты сделал что-то плохое.' +L.leave_arrest = 'Выход с сервера из тюрьмы (%s)' +L.reason_not_find = 'Причина не указана' +L.expire = 'Истекает %m/%d в %H:%M' +L.using = 'Используется' +L.not_using = 'Не используется' +L.actived = 'Активировано' +L.police_arrest = '%s арестован на %s секунд по причине: %s' +L.police_call = 'Вызов #' +L.write_coupon = 'Введи код купона:' +L.tap_anywhere = 'Чтобы закрыть это окно, кликни где-нибудь снаружи' +L.active = 'Активировать' +L.equip = 'Надеть' +L.unequip = 'Снять' +L.temporary_not_desc = 'У нас пока что нет описания для этого предмета' +L.buy_for = 'Купить за ' +L.repair_car = 'Починить автомобиль' +L.repair_car_hint = 'Нужно смотреть на сломанную машину' +L.repair_wheel = 'Починить колесо' +L.repair_wheel_hint = 'Нужно смотреть на сломанное колесо' +L.paint = 'Покраска' +L.car_color = 'Цвет машины' +L.desc_car_color = 'После выбора цвета начнется процесс покраски, а затем предмет исчезнет' +L.painting_car = 'Покрасить автомобиль' +L.see_car = 'Нужно смотреть на машину' +L.car_not_paint = 'Эту машину нельзя красить' +L.set_detail = 'Установить деталь' +L.set_discs = 'Установить деталь' +L.send_sms = 'Отправить SMS' +L.text_msg = 'Текст сообщения' +L.recipient = 'Получатель...' +L.call_hint = 'Вызвать...' +L.desc_sit_and_place = 'Опиши ситуацию и местоположение:' +L.ems_hint = 'Экстренные службы' +L.call_medic = 'Вызов врача' +L.medic = 'Врача' +L.mechanic2 = 'Механика' +L.call_mech = 'Вызов механика' +L.make_order = 'Сделать заказ' +L.make_advert = 'Опубликовать рекламу' +L.text_advert = 'Текст объявления:' +L.edit_karma = '%s к карме от %s' +L.set_money = 'Установить деньги' +L.give_money = 'Дать денег' +L.how_much_money = 'Сколько денег?' +L.reset_city = 'Восстановить город' +L.new_title = 'Новое название:' +L.ban_job = 'Запретить профессию' +L.unban_job = 'разрешить профессию' +L.addkarma = 'Добавить карму' +L.send_to_jail = 'Посадить в тюрьму' +L.unarrest_hint = 'Освободить из тюрьмы' +L.octoinv_import = 'Импорт набора' +L.in_basket = 'В корзину' +L.basket_empty = 'Корзина пуста' +L.delivery = 'Доставка' +L.yourself = 'Для себя' +L.in_kit = 'В набор' +L.save_kit = 'Сохранить набор' +L.kit_hint = 'Набор ' +L.give_title_kit = 'Укажи название набора:' +L.import_successful = 'Набор успешно импортирован' +L.import_failure = 'Не получилось декодировать набор, проверь, все ли ты скопировал' +L.my_kits = 'Мои наборы' +L.export_kit = 'Экспорт набора' +L.export_kit_hint = 'Набор скопирован, теперь ты можешь вставить его куда-нибудь' +L.delete_kit = 'Удалить набор' +L.rename_kit = 'Укажи новое название набора' +L.add_in_basket = 'Добавить в корзину' +L.how_much_kits = 'Сколько наборов заказать' +L.alr_in_basket = 'В корзине' +L.delete_from_basket = 'Удалить из корзины' +L.your_car_evacuated = 'Твой автомобиль эвакуирован' +L.your_service_car_evacuated = 'Твой служебный автомобиль эвакуирован' +L.car_not_bought = 'Эта тачка не для продажи' +L.car_not_for_you = 'Эта тачка не для тебя' +L.warns = 'Предупреждения' +L.reason_watchlist = 'Причина наблюдения' +L.reason_watchlist_hint = 'Укажи причину наблюдения' +L.admingun_admintell = 'Уведомить...' +L.give_weapon = 'Дать оружие' +L.hundred_energy = '100% сытости' +L.give_ammo = 'Дать патроны' +L.give_ammo_hint = 'Сколько патронов добавить? Оставь пустым, чтобы сделать бесконечными' +L.teleport = 'Телепорт' +L.ulogs_search = 'Искать в логах' +L.condition = 'Состояние' +L.make_order_hint = '{name} делает заказ в магазине' +L.order_not_have_access = 'Ты не имеешь доступ к некоторым товарам. Попробуй обновить данные' +L.item_recently_lockpicked = 'Предмет был недавно взломан' +L.give_end_when = 'Передача будет выполнена, когда ' +L.give_end_when2 = ' примет предмет' +L.take_item = 'Принять предмет' +L.take_cancel_hint = 'Передача отменена' +L.take_cancel = 'Отменить передачу' +L.now_is_your = 'Теперь это твой предмет' +L.order_for = 'Заказать за ' +L.lacks = 'Не хватает ' +L.run = 'Запустить' +L.run_hint = 'Запустить автомат' +L.desc_run_hint = 'После нажатия кнопки ГОТОВО автомат закроется и будет принимать платежи, выдавая взамен товары. Нельзя ставить одинаковую цену на два разных слота. Чтобы извлечь из автомата деньги, нужно его открыть' +L.price_for_one = 'Цена за штуку' +L.vend_slot_hint = 'Слот #%s (%s)' +L.vend_price_fail = 'Нельзя выставлять одинаковые цены на разные слоты' +L.freeze_on_car = 'Закрепить на машине' +L.freezed_on_car = 'Предмет закреплен на машине' +L.unstable = 'Предмет сейчас слишком неустойчив' +L.item_freezed = 'Предмет закреплен' +L.item_unfreezed_car = 'Открепить от машины' +L.cost_product = 'Укажи количество товара:' +L.preview = 'Осмотреть' +L.print_job_name = '%s, введите название профессии' +L.print_job_name_or_nick = '%s, введите название профессии или ник' +L.time_in_seconds = 'Время в секундах:' +L.new_name = 'Новое имя:' +L.addkarma_help = 'Сколько добавить (отрицательные работают):' +L.lockdown_change = 'Переключить комчас' +L.already_stealing = 'Кто-то уже грабит это хранилище' +L.steal = 'Ограбить' +L.stealing = 'Поиск предметов' +L.stealing_cancel = 'Поиск отменен' +L.recent_stealed = 'Это хранилище недавно грабили, нужно немного подождать' +L.unavailable_item = 'Недоступный предмет' +L.octoinv_unavailable = ' - недоступно' +L.enter_code_export = 'Введи код набора или папки, полученный при экспорте' +L.basket_warning = 'У тебя в корзине есть вещи. Если обновить данные, они пропадут' +L.car_buy = 'Ты купил ' +L.car_buy2 = ' за ' +L.not_enough_money_car = 'Эта тачка тебе не по карману. Деньги должны лежать у тебя в банке' +L.car_not_sell = 'Это авто нельзя продать' +L.car_try_sell = 'Ты пытаешься продать то, чего у тебя нет?' +L.invisible_god_en = 'Невидимость и бессмертие включены' +L.invisible_god_dis = 'Невидимость и бессмертие отключены' +L.abonent_unavailable = 'Абонент недоступен' +L.advert_money = 'На твоем банковском счете не хватает денег! Реклама стоит %s' +L.advert_hint = '[Реклама] ' +L.broadcast_text = '[Обращение Мэра] ' +L.push = 'Толкнуть' +L.unarrest_action = 'Освободить' +L.uncuff = 'Развязать' +L.loot_corpse = 'Обыск тела' +L.loot_yourself = 'Ты не можешь обыскивать свое тело' +L.loot_police = ' (полицейский)' +L.pickup = 'Поднять' +L.gag_and_mute = 'Теперь %s не может разговаривать' +L.ungag_and_unmute = 'Теперь %s может разговаривать' +L.ungag_and_unmute2 = ' разрешил тебе говорить' +L.gag_and_mute2 = ' запретил тебе говорить' +L.admin_mode_on = 'Режим администратора включен' +L.admin_mode_off = 'Режим администратора отключен' +L.can_not_order = 'Не получилось оформить заказ' +L.order_format = 'Заказ (%s)' +L.delivery_order = 'Доставка заказа' +L.order_went = 'Заказ #%s доставлен. Рядом располагается %s. Если %s не заберет его в течение 10 минут, товары выкинут' +L.order_went2 = 'Заказ #%s доставлен. Рядом располагается %s. Забери его в течение 10 минут, или товары выкинут' +L.order_dropped = 'Заказ #%s никто не забрал, поэтому вещи были выброшены' +L.order_something_wrong = 'Что-то пошло не так при обработке заказа, попробуй еще раз' +L.order_wrong_money_back = 'Что-то произошло не так при доставке заказа #%s. Твои деньги возвращены' +L.order_wrong_money_back2 = 'Что-то произошло не так при доставке заказа #%s. Деньги возвращены владельцу' +L.mailbox = 'Почтовый ящик' +L.order_accepted = 'Заказ #%s принят в работу, ожидай уведомления о доставке' +L.order_accepted2 = 'Заказ #%s принят в работу, ты и %s получите уведомление о доставке' +L.order_accepted3 = '%s оформляет заказ #%s на твое имя, ожидай уведомления о доставке' +L.order_details_format = '%d x %s' +L.receiver = 'Получатель: ' +L.order_not_enough_money = 'На твоем банковском счете не хватает денег для заказа' +L.too_much_order = 'Это слишком объемный заказ, попробуй уменьшить' +L.hobo_car = 'У нищего не может быть автомобиля' +L.too_much_cars = 'В городе сейчас много автомобилей, попробуй чуть позже' +L.this_only_police = 'Это только для полицейских' +L.near_need_police_car = 'Рядом должен находиться полицейский автомобиль' +L.you_ban_job_time = 'Вы забанили %s(%s) работу %s на %s' +L.say_from_radio = ' говорит из рации: ' +L.team_chat_disabled = 'Групповые чаты отключены. Используй /c для установки частоты для рации и /r для чата по рации' +L.megaphone = '[Громкоговоритель] ' +L.too_often = 'Слишком часто!' +L.somebody_call_ems = 'Кто-то вызывает скорую помощь%s' +L.somebody_call_mech = 'Кто-то вызывает механика%s' +L.something_interferes = 'Что-то мешает обзору, подойди поближе' +L.too_fast = 'Слишком быстро!' +L.coupon_content = 'Содержание купона: ' +L.wrong_money = 'Неправильное количество денег!' +L.type_bonus_wrong = 'Такого типа награды не существует!' +L.reason_arrest = 'Причина ареста' +L.reason_arrest_hint = 'Укажи причину здесь' +L.reason_arrest_level = {'Тяжесть преступления','Тяжелое (100% времени)','Среднее (50% времени)','Легкое (25% времени)'} +L.player_left2 = 'Игрок вышел' +L.wrong_reason_arrest = 'Некорректная причина ареста' +L.study_corpse = 'Сбор материала' +L.body_mat_already_take = 'Похоже, материал уже собран' +L.body_mat_corpse = 'Жертва: ' +L.take_body_mat = 'Собрать материал' +L.karma_search_corpse = 'Хороший человек копаться в только что умершем не станет' +L.pack_corpse = 'Упаковка' +L.pack_corpse2 = 'Упаковать' +L.corpse_packed = 'Труп упакован, его заберут через пару минут' +L.car_sell = 'Ты продал ' +L.car_sell2 = ' за ' +L.car_sell3 = '! Деньги уже у тебя в банке' +L.car_went_often = 'Ты пригоняешь машины слишком часто, подожди немного' +L.dealer_not_found = 'Продавец не найден' +L.long_for_seller = 'Ты слишком далеко от продавца!' +L.before_give_car = 'Сначала загони свою тачку!' +L.car_block = 'Что-то загораживает проезд!' +L.car_in_garage = 'Твоя тачка в гараже' +L.car_for_long = 'Тачка слишком далеко' +L.coupon_not_exist = 'Купон не существует или уже применен!' +L.only_owner = 'Это разрешено только владельцам сервера!' +L.create_coupon_hint = 'Создан купон: ' +L.use_coupon_hint = 'Купон успешно применен!' +L.use_coupon_fail = 'Ошибка в применении купона! Обратись к администраторам' +L.octoshop_wait = 'Подожди еще ' +L.reward_failed = 'Ошибка в опледелении награды! Обратись к администраторам' +L.octoshop_update_balance = 'На твой счет в магазине поступило ' +L.auto_dont_go = 'Это авто нельзя пригнать' +L.auto_unavailable = 'Это авто пока что тебе не доступно' +L.destruct = 'Разобрать' +L.destruct_cancel = 'Разборка отменена' +L.destruct_hint = 'Разборка' +L.item_unfrozen_car = 'Предмет откреплен от машины' +L.item_unfrozen = 'Предмет откреплен' +L.car_exist = 'Этого авто нет в твоем гараже' +L.car_in_garage_unaivaible = 'Твоя тачка в гараже, потому что сейчас она тебе недоступна' +L.car_evacuate = 'Твоя машина слишком долго находится на месте. Если ты не будешь ей пользоваться, ее эвакуируют!' +L.car_evacuated = 'Твоя машина была эвакуирована' +L.desc_story = [[Дает доступ к инструментам, которые помогут вывести твою историю на новый уровень: +– Триггер (звуковые и текстовые эффекты по зонам) +– Описание (для системы "приглядеться") +– Осмотреть (интерактивное слайд-шоу для предметов)]] +L.desc_octoshop_coffee = [[Если вдруг у тебя нашлись лишние 15 фишек, ты можешь просто подарить чашечку кофе нашему разработчику, чтобы ему было не так тяжело работать в эту ночь над сервером. + +Знаем, любовь не продается, но все же ты можешь нам помочь ;)]] +L.desc_jobs_1m = [[Покупая этот предмет, ты получаешь особый статус на нашем проекте - Добродей! + +# Позволяет переименовывать город +# Позволяет покупать гитару +# Позволяет покупать и использовать дополнительные автомобили +# Открывает доступ к дополнительным профессиям: Местный, Старший токарь, Провизор, Старый бездомный и Сержант полиции. + +Профессии: +- У местного на старте есть фонарик и фотоаппарат. +- Cтарший токарь в силу своего опыта может производить большее количество оружия. +- Провизор имеет в своей аптечке гораздо больше средств (в том числе и наркотических). +- Старый бездомный всегда таскает при себе бутылку. +- Сержанту полиции изначально выдают дробовик M3, а также тяжелый бронежилет.]] +L.desc_octoshop_govorilka = [[Озвучивает все написанные тобой в чат фразы. В любой момент ты можешь изменить голос в меню Персонаж. Искать укромное место для этого необязательно.]] +L.desc_octoshop_free_delivery = [[Как уважаемый клиент, ты получаешь скидку 100% на все виды доставки предметов из интернет-магазина]] +L.big_storage = [[Эта штука расширит вместимость твоего хранилища до 350 литров!]] +L.builder = [[Расширяет лимит пропов в 2 раза, отключает ограничение по большим пропам, позволяет изменить цвет физгана, а также выдает доступ к инструментам: +– Шарнирное соединение +– Осевое соединение +– Эластичное соединение +– Гидравлика +– Веревка +– Лебедка +– Image Screen]] +L.money_15k = [[Выдает тебе на счет в банке 15,000Р. Что может быть проще?]] +L.money_50k = [[Выдает тебе на счет в банке 50,000Р. Что может быть проще?]] +L.money_100k = [[Выдает тебе на счет в банке 100,000Р. Что может быть проще?]] +L.money_250k = [[Выдает тебе на счет в банке 250,000Р. Что может быть проще?]] +L.money_500k = [[Выдает тебе на счет в банке 500,000Р. Что может быть проще?]] + +-- +-- antiexp stuff +-- + +L.grab_scanning = 'Идет сканирование, без паники...' +L.potential_cheats = 'Возможные читы' +L.player = 'Игрок' +L.files = 'Файлы' +L.search_exploits = 'Ищешь эксплойты? Тебе мимо' +L.goahead = 'Если ты здесь только за этим, иди на другой сервер' + +-- +-- jobs +-- + +L.police = 'Полиция' +L.mayor = 'Мэр' +L.citizen = 'Гражданин' +L.hobo = 'Бездомный' +L.citizen2 = 'Местный' +L.hobo2 = 'Старый бездомный' +L.cooker = 'Повар' +L.gunsmith = 'Токарь' +L.gunsmith2 = 'Старший токарь' +L.mechanic = 'Механик' +L.worker = 'Городской работник' +L.doctor = 'Фармацевт' +L.doctor2 = 'Провизор' +L.priest = 'Священник' +L.banker = 'Банкир' +L.medcop = 'Полицейский медик' +L.officer = 'Офицер полиции' +L.officer2 = 'Сержант полиции' +L.chief = 'Лейтенант полиции' +L.administrator = 'Администратор' + +-- +-- govorilka +-- + +L.voice_character = 'Голос, которым говорит твой персонаж' +L.zahar = 'Захар' +L.ermil = 'Ермил' +L.oksana = 'Оксана' +L.alyss = 'Алиса' +L.omazh = 'Оммаж' +L.jane = 'Джейн' + +-- +-- links +-- + +L.useful_links = 'Полезные ссылки' +L.links = 'Ссылки' +L.start = 'Начало игры' +L.site = 'Сайт' +L.wiki = 'Вики' +L.forum = 'Форум' +L.group_vk = 'Группа ВК' +L.group_steam = 'Группа Steam' +L.rules = 'Правила' +L.buy_chips = 'Купить фишки' +L.rules_server = 'Правила сервера' +L.discord = 'Сервер Discord' +L.our_site = 'Наш сайт' +L.write_us_in_vk = 'Написать нам через ВК' +L.we_in_vk = 'Мы в ВК' +L.we_in_steam = 'Мы в Steam' + +-- +-- weapons +-- + +L.cigarette = 'Сигарета' +L.axe = 'Топор' +L.pot = 'Ковшик' +L.hands = 'Руки' +L.check = 'Досмотреть' +L.zombie = 'Зомби' +L.stungun = 'Тазер' +L.arrest_stick = 'Арестовать' +L.admin_tool = 'Админ-tool' +L.detector = 'Детектор' +L.keys = 'Ключи' +L.door_ram = 'Таран' +L.stunstick = 'Дубинка' +L.medkit = 'Аптечка' +L.unarrest_stick = 'Освободить' +L.cuff_rope = 'Веревка' +L.cuff_police = 'Наручники' +L.keypad_cracker = 'Взломщик' +L.flashlight = 'Фонарик' +L.flashlight_uv = 'Ультрафиолетовый фонарик' +L.broom = 'Швабра' +L.knife = 'Ножик' +L.zombie_heal = 'Излечить зомби' +L.bottle = 'Бутылка' +L.brokentbottle = 'Разбитая бутылка' +L.pumpshotgun = 'Помповый дробовик' +L.snipergun = 'Снайперская винтовка' +L.hook = 'Крюк' +L.pan = 'Сковорода' +L.pipe = 'Труба' +L.policetape = 'Полицейская лента' +L.camera = 'Фотоаппарат' +L.speedometer = 'Измеритель скорости' +L.shovel = 'Лопата' +L.handcuffed = 'В наручниках' +L.guitar = 'Гитара' + +-- +-- instruction +-- + +L.instruction_cigarette = 'ЛКМ — Затянуться\nПКМ — Выкинуть' +L.instruction_hand = 'ЛКМ — закрыть\nПКМ — открыть' +L.instruction_check = 'ЛКМ — досмотреть\nПКМ — конфисковать нелегал\nR — вернуть конфискованное' +L.instruction_zombie = 'ЛКМ – бить; ПКМ – кричать' +L.instruction_arrest_stick = 'ЛКМ — арестовать\nПКМ — переключиться' +L.instruction_admin_tool = 'ЛКМ — действие на игроке\nПКМ — заткнуть игрока\nR — вкл/выкл невидимость' +L.instruction_detector = 'ЛКМ — проверить на наркотики' +L.instruction_keys = 'ЛКМ — закрыть\nПКМ — открыть' +L.instruction_door_ram = 'ЛКМ — выбить дверь или вытащить человека из транспорта\nПКМ — поднять' +L.instruction_stunstick = 'ЛКМ — ударить без урона\nПКМ — ударить с уроном\nR — пригрозить' +L.instruction_medkit = 'ЛКМ — лечить другого\nПКМ — лечить себя' +L.instruction_unarrest_stick = 'ЛКМ — освободить\nПКМ — переключиться' +L.instruction_cuff_rope = 'Слабый поводок' +L.instruction_cuff_police = 'Крепкие металлические наручники' +L.instruction_keypad_cracker = 'ЛКМ — взломать кейпад' +L.instruction_flashlight = 'ЛКМ - вкл/выкл' +L.instruction_zombie_heal = 'ЛКМ — вылечить владельца тела' +L.instruction_bottle = 'ЛКМ — ударить\nПКМ — бросить' +L.instruction_bump = 'ЛКМ - ударить' +L.instruction_bump_and_push = 'ЛКМ — ударить\nПКМ — толкнуть' +L.instruction_policetape = 'Держать ЛКМ — Повесить\nНажать ПКМ — Убрать' + +-- +-- drugs +-- + +L.steroids = 'Стероиды' +L.cigarettes = 'Сигареты' +L.gunslinger = 'Норадреналин' +L.cocaine = 'Кокаин' +L.drugbooze2 = 'Водка' +L.antitoxin = 'Антитоксин' +L.dextradose = 'Декстроза' +L.vitalex = 'Антибиотики' +L.relaxant = 'Миорелаксант' +L.drugbooze = 'Пиво' +L.pingaz = 'Метамфетамин' +L.painkiller = 'Обезболивающее' +L.preserver = 'Адреналин' +L.marijuana = 'Марихуана' +L.roids = 'Амфетамин' +L.volatile = 'Циануртриазид' +L.vampire = 'Гемофагин' + +L.drunk = 'Алкогольное опъянение' +L.overdose = 'Передозировка' +L.nicotine = 'Никотин' +L.healthrecovery = 'Выздоровление' + +-- +-- descriptions drugs +-- + +L.description_steroids = 'Увеличивают силу удара кулаком и позволяют убить человека' +L.description_cigarettes = 'Немного восстанавливает здоровье' +L.description_cocaine = 'Дает двойной прыжок' +L.description_drugbooze2 = 'Уменьшает чувствительность к урону на 10%' +L.description_antitoxin = 'Убирает все эффекты от наркотиков' +L.description_dextradose = 'Замедляет скорость падения пинов на 15%' +L.description_vitalex = 'Быстро восстанавливает здоровье' +L.description_relaxant = 'Уменьшает урон от падения' +L.description_drugbooze = 'Уменьшает чувствительность к урону на 10%' +L.description_pingaz = 'Увеличивает высоту прыжка' +L.description_painkiller = 'Уменьшает чувствительность к урону на 20%' +L.description_preserver = 'Спасает от смерти, давая второй шанс' +L.description_marijuana = 'Медленно восстанавливает здоровье' +L.description_roids = 'Увеличивает скорость бега на 20%' +L.description_volatile = 'Взрывается внутри игрока после его смерти' +L.description_vampire = 'Восстанавливает здоровье, если наносишь урон другим' +L.overdose_hint = 'У тебя передоз' +L.drugs_use = 'Принять' +L.temporary_disable = 'Этот предмет временно отключен' + +-- +-- permissions +-- + +L.description_permissions = 'Особые фишки Доброграда' +L.permissions_admin_request = 'DBG: Видеть админ-запросы' +L.permissions_admin_commands = 'DBG: DarkRP админ-команды' +L.permissions_superadmin_commands = 'DBG: DarkRP суперадмин-команды' +L.permissions_edit_inventory = 'DBG: Редактировать инвентарь' +L.permissions_spawn_sent = 'DBG: SpawnSENT' +L.permissions_spawn_swep = 'DBG: SpawnSWEP' +L.permissions_spawn_npc = 'DBG: SpawnNPC' +L.permissions_spawn_vehicle = 'DBG: SpawnVehicle' +L.permissions_spawn_ragdoll = 'DBG: SpawnRagdoll' +L.permissions_trigger_url = 'DBG: Триггер по URL' +L.permissions_create_prop_inventory = 'DBG: Создавать инвентарь у предмета' +L.permissions_permaprops = 'DBG: Делать постоянным' +L.permissions_dobrodey = 'DBG: Добродей' +L.permissions_edit_karma = 'DBG: Изменение кармы' + +-- +-- items +-- + +L.printer_cart = 'Картридж' +L.printer = 'Принтер' +L.craft_binoculars = 'Бинокль' +L.stove = 'Плита' +L.fridge = 'Холодильник' +L.vend = 'Автомат' +L.workbench = 'Верстак' +L.machine = 'Станок' +L.refinery = 'Очиститель' +L.refinery_fuel = 'Топливный бак' +L.smelter = 'Печь' +L.smelter_fuel = 'Топливный бак' +L.oven = 'Духовка' +L.machine_tray = 'Слот для программ' +L.boxdesc = 'Контейнер, можно собрать и класть в него вещи' +L.box = 'Ящик' +L.box2 = 'Коробка' +L.small_ammo = 'Малый калибр' +L.large_ammo = 'Крупный калибр' +L.sniper_ammo = 'Снайперский калибр' +L.air_ammo = 'Пневматический калибр' +L.buckshot = 'Дробь' +L.lock_cont5 = '5-пиновый замок' +L.lock_cont6 = '6-пиновый замок' +L.lock_cont7 = '7-пиновый замок' +L.lock_cont8 = '8-пиновый замок' +L.lock_cont10 = '10-пиновый замок' +L.chocolate = 'Шоколадка' +L.chips = 'Чипсы' +L.food_soda = 'Газировка' +L.cream = 'Йогурт' +L.crackers = 'Сухарики' +L.meatcake = 'Пирожок с мясом' +L.applepie = 'Яблочный пирог' +L.pancake = 'Блинчики' +L.sandwitch = 'Бутерброд' +L.wrap = 'Шаурма' +L.gift = 'Подарок' +L.dough1 = 'Сдобное тесто' +L.dough2 = 'Песочное тесто' +L.meatpotato = 'Котлета с пюрешкой' +L.ing_icecream2 = 'Заготовка для мороженого' +L.potatomash = 'Пюрешка' +L.okroshka = 'Окрошка' +L.sushi = 'Суши' +L.coffeetail = 'Гоголь-моголь' +L.coffeetail2 = 'Кофейный ликер' +L.burger = 'Гамбургер' +L.burger2 = 'Чизбургер' +L.burger3 = 'Биг Мак' +L.ceasar = 'Салат Цезарь' +L.cake_med = 'Торт «Медовик»' +L.armor_l = 'Тяжелый бронежилет' +L.armor = 'Бронежилет' +L.armor_s = 'Легкий бронежилет' +L.gun_lockpick = 'Отмычка' +L.gun_broken_lockpick = 'Сломанная отмычка' +L.silencer = 'Глушитель' +L.cookie = 'Печенье' +L.tool_screwer = 'Отвертка' +L.tool_hammer = 'Молоток' +L.tool_wrench = 'Гаечный ключ' +L.tool_solder = 'Паяльник' +L.tool_ruler = 'Линейка' +L.tool_caliper = 'Штангенциркуль' +L.tool_knife_stanley = 'Канцелярский нож' +L.tool_drill = 'Дрель' +L.tool_saw = 'Пила' +L.tool_pump = 'Насос' +L.tool_craft = 'Набор для сборки' +L.craft_screw = 'Саморез' +L.craft_screw2 = 'Болт' +L.craft_screwnut = 'Гайка' +L.craft_nail = 'Гвоздь' +L.craft_stick = 'Палка' +L.craft_plank = 'Доска' +L.craft_pallet = 'Поддон' +L.craft_sheet = 'Пластина' +L.craft_spring = 'Пружина' +L.craft_pulley = 'Ролик' +L.craft_cable = 'Кабель' +L.craft_plug = 'Вилка' +L.craft_socket = 'Розетка' +L.craft_piston = 'Поршень' +L.craft_engine = 'Двигатель' +L.craft_bulb = 'Лампочка' +L.craft_chip1 = 'Чип памяти' +L.craft_chip2 = 'Процессор' +L.craft_cnc = 'Печатающее устройство' +L.craft_relay = 'Реле' +L.craft_resistor = 'Резистор' +L.craft_transistor = 'Транзистор' +L.craft_solar = 'Солнечная панель' +L.craft_gauge = 'Калибр' +L.craft_scotch = 'Скотч' +L.craft_glue = 'Клей' +L.craft_ink = 'Чернила' +L.craft_paper = 'Бумага' +L.craft_paper2 = 'Плотная бумага' +L.craft_fuel = 'Топливо' +L.craft_gas = 'Газовый балон' +L.craft_coal = 'Уголь' +L.craft_battery = 'Аккумулятор' +L.craft_battery2 = 'Батарейка' +L.craft_bottle = 'Бутылка' +L.craft_glass = 'Стекло' +L.tool_pan = 'Сковорода' +L.tool_pot = 'Кастрюля' +L.tool_scoop = 'Черпак' +L.tool_shaker = 'Коктейльный шейкер' +L.tool_oventray = 'Противень' +L.tool_pastrybag = 'Кондитерский мешок' +L.tool_teapot = 'Чайник' +L.tool_coffeepot = 'Кофейник' +L.tool_foodcont = 'Контейнер для еды' +L.ing_water = 'Вода' +L.ing_egg = 'Яйцо' +L.ing_oil = 'Масло' +L.ing_salt = 'Соль' +L.ing_potato = 'Картофель' +L.ing_cheese = 'Сыр' +L.ing_flour = 'Мука' +L.ing_onion = 'Лук' +L.ing_milk = 'Молоко' +L.ing_cream = 'Сливки' +L.ing_meat = 'Мясо' +L.ing_carrot = 'Морковь' +L.ing_tomato = 'Помидор' +L.ing_sugar = 'Сахар' +L.ing_mushroom = 'Гриб' +L.ing_sausage = 'Сосиска' +L.ing_sausage2 = 'Колбаса' +L.ing_bread = 'Хлеб' +L.ing_honey = 'Мед' +L.ing_spice = 'Специи' +L.ing_kvas = 'Квас' +L.ing_cucumber = 'Огурец' +L.ing_rice = 'Рис' +L.ing_nut = 'Орехи' +L.ing_apple = 'Яблоко' +L.ing_corn = 'Кукуруза' +L.ing_celery = 'Сельдерей' +L.ing_bacon = 'Бекон' +L.ing_olive = 'Оливки' +L.ing_chili = 'Чили' +L.ing_broccoli = 'Брокколи' +L.ing_pineapple = 'Ананас' +L.ing_pumpkin = 'Тыква' +L.ing_fish = 'Рыба' +L.ing_strawberry = 'Клубника' +L.ing_watermelon = 'Арбуз' +L.ing_melon = 'Дыня' +L.ing_soy = 'Соя' +L.ing_peas = 'Горох' +L.ing_lettuce = 'Салат' +L.ing_cabbage = 'Капуста' +L.ing_beet = 'Свекла' +L.ing_redish = 'Редис' +L.ing_eggplant = 'Кабачок' +L.ing_prawn = 'Креветки' +L.ing_avocado = 'Авокадо' +L.ing_pita = 'Лаваш' +L.ing_cocoa = 'Какао' +L.ing_choco = 'Шоколад' +L.ing_coffee = 'Кофейное зерно' +L.ing_tea = 'Чайный лист' +L.ing_banana = 'Банан' +L.ing_orange = 'Апельсин' +L.ing_lemon = 'Лимон' +L.ing_sauce = 'Соус' +L.ing_pasta = 'Паста' +L.ing_potato2 = 'Вареный картофель' +L.ing_meatball = 'Котлета' +L.ing_roastmeat = 'Жареное мясо' +L.ing_potatomash = 'Пюрешка' +L.ing_soup = 'Бульон' +L.ing_icecream = 'Мороженое' +L.ing_dough3 = 'Бисквит' +L.ing_pizza_base = 'Основа для пиццы' +L.gun_ing_shutter = 'Затвор' +L.gun_ing_barrel = 'Ствол' +L.gun_ing_grip = 'Рукоять' +L.gun_ing_sight = 'Мушка' +L.gun_ing_aim = 'Ударник' +L.gun_ing_drummer = 'Курок' +L.gun_ing_trigger = 'Стопор' +L.shop = 'Магазин' +L.market = 'Рынок' +L.stone = 'Камень' +L.rubble = 'Щебень' +L.sand = 'Песок' +L.sulfur = 'Сера' +L.saltpeter = 'Селитра' +L.gunpowder = 'Порох' +L.ore_iron = 'Железная руда' +L.ore_steel = 'Стальная руда' +L.ore_silver = 'Серебряная руда' +L.ore_bronze = 'Бронзовая руда' +L.ore_gold = 'Золотая руда' +L.ore_copper = 'Медная руда' +L.ingot_iron = 'Железный слиток' +L.ingot_steel = 'Стальной слиток' +L.ingot_silver = 'Серебряный слиток' +L.ingot_bronze = 'Бронзовый слиток' +L.ingot_gold = 'Золотой слиток' +L.ingot_copper = 'Медный слиток' +L.statuette_chelog = 'Фигурка Челога' +L.statuette_quillin = 'Фигурка Квиллина' +L.statuette_artis = 'Фигурка Артиса' +L.statuette_nayzer = 'Фигурка Назара' +L.statuette_youlas = 'Фигурка Юласа' +L.statuette_glitch = 'Фигурка Глича' +L.statuette_google = 'Фигурка Гугла' +L.statuette_queeboy = 'Фигурка Квибоя' +L.statuette_benri = 'Фигурка Бенри' +L.statuette_direded = 'Фигурка Деда' +L.statuette_bellash = 'Фигурка Беляша' +L.statuette_drinda = 'Фигурка Дрынды' +L.statuette_arthas = 'Фигурка Артаса' +L.statuette_wayzer = 'Фигурка Вейзера' +L.statuette_hellcom = 'Фигурка Хелчома' +L.statuette_hoffman = 'Фигурка Хоффмана' +L.statuette_sighty = 'Фигурка Сайти' +L.statuette_ermak = 'Голова Ермака' +L.trash_food_wrap = 'Недоеденная шаурма' +L.trash_food_donut = 'Полупончик' +L.trash_food_sandwich = 'Бутерброд без колбасы' +L.trash_food_pizza = 'Остатки пиццы' +L.trash_food_pie = 'Невкусный пирожок' +L.mask = 'Маска' +L.mask_cleaver = 'Тесак на голову' +L.mask_monkey = 'Маска обезьяны' +L.mask_zombie = 'Маска зомби' +L.mask_hockey = 'Хоккейная маска' +L.mask_gingerbread = 'Маска печенюшки' +L.mask_pumpkin = 'Тыква-маска' +L.mask_top_hat = 'Цилиндр' +L.mask_doctor = 'Маска чумного доктора' +L.mask_pig = 'Маска свиньи' +L.mask_bear = 'Маска медведя' +L.mask_gasmask = 'Противогаз' +L.mask_hoffman = 'Маска с эмблемой короны' +L.mask_turtle = 'Черепашка' +L.mask_deer = 'Маска оленя' +L.mask_mummy = 'Маска мумии' +L.class_armor = 'Класс брони' +L.accessory = 'Аксессуар' +L.spoiler1 = 'Спойлер с поддержкой' +L.spoiler2 = 'Спойлер Крепкий' +L.spoiler3 = 'Спойлер Прямой' +L.spoiler4 = 'Спойлер Гладкий' +L.spoiler5 = 'Спойлер Волна' +L.ladder = 'Лестница' +L.supercharger = 'Нагнетатель' +L.huladoll = 'Фигурка на приборку' +L.detail = 'Деталь' +L.detail_sim_fphys_hatch_v2 = 'Торпеда - Спортивный передний бампер' +L.detail2_sim_fphys_hatch_v2 = 'Торпеда - Спортивный задний бампер' +L.detail3_sim_fphys_hatch_v2 = 'Торпеда - Спортивные пороги' +L.detail4_sim_fphys_hatch_v2 = 'Торпеда - Заводской передний бампер' +L.detail5_sim_fphys_hatch_v2 = 'Торпеда - Заводской задний бампер' +L.detail6_sim_fphys_hatch_v2 = 'Торпеда - Заводские пороги' +L.detail_sim_fphys_pwmoskvich_v2 = 'Казак - Спортивный передний бампер' +L.detail2_sim_fphys_pwmoskvich_v2 = 'Казак - Спортивный задний бампер' +L.detail3_sim_fphys_pwmoskvich_v2 = 'Казак - Спортивные пороги' +L.detail4_sim_fphys_pwmoskvich_v2 = 'Казак - Заводской передний бампер' +L.detail5_sim_fphys_pwmoskvich_v2 = 'Казак - Заводской задний бампер' +L.detail6_sim_fphys_pwmoskvich_v2 = 'Казак - Заводские пороги' +L.detail_sim_fphys_hl2_gaz63 = 'Волжанин - Спортивный передний бампер' +L.detail2_sim_fphys_hl2_gaz63 = 'Волжанин - Спортивный задний бампер' +L.detail3_sim_fphys_hl2_gaz63 = 'Волжанин - Спортивные пороги' +L.detail4_sim_fphys_hl2_gaz63 = 'Волжанин - Заводской передний бампер' +L.detail5_sim_fphys_hl2_gaz63 = 'Волжанин - Заводской задний бампер' +L.detail6_sim_fphys_hl2_gaz63 = 'Волжанин - Заводские пороги' +L.detail_sim_fphys_pwtrabant_v2 = 'Спутник - Кенгурятник' +L.detail2_sim_fphys_pwtrabant_v2 = 'Спутник - Крепкие пороги' +L.detail3_sim_fphys_pwtrabant_v2 = 'Спутник - Большая труба' +L.detail4_sim_fphys_pwtrabant_v2 = 'Спутник - Заводской бампер' +L.detail5_sim_fphys_pwtrabant_v2 = 'Спутник - Заводские пороги' +L.detail6_sim_fphys_pwtrabant_v2 = 'Спутник - Заводская труба' +L.discs = 'Диски' +L.discs_hint = 'Диски ' +L.type_ammo = 'Тип патронов' +L.item_disc = 'Ока' +L.item_disc2 = 'Внедорожные-2' +L.item_disc3 = 'От маршрутки' +L.item_disc4 = 'Бюджетные-2' +L.item_disc5 = 'Бюджетные-5' +L.item_disc6 = 'Бюджетные-4' +L.item_disc7 = 'Бюджетные-1' +L.item_disc8 = 'Бюджетные-8' +L.item_disc9 = 'Грузовые' +L.item_disc10 = 'Внедорожные' +L.item_disc11 = 'Двойные внедорожные' +L.lamp_pumpkin = 'Тыква-лампа' +L.car_hook = 'Висящий крюк' +L.scary_doll = 'Страшная кукла' +L.tool_pen = 'Ручка' +L.tool_pencil = 'Карандаш' +L.tool_souvenir = 'Сувенир' +L.coupon_ammo = 'Талон на вооружение' +L.coupon_food = 'Талон на провиант' +L.coupon_exit = 'Талон на выхода' +L.bpd_crate = 'Ящик (60л)' +L.bpd_cratel = 'Средний ящик (120л)' +L.cratel = 'Средний ящик' +L.bpd_cratexl = 'Большой ящик (200л)' +L.cratexl = 'Большой ящик' +L.bpd_cardbox = 'Маленькая коробка (5л)' +L.cardbox = 'Маленькая коробка' +L.bpd_cardboxl = 'Картонная коробка (20л)' +L.cardboxl = 'Картонная коробка' +L.bpd_presents = 'Маленький подарок (5л)' +L.present = 'Маленький подарок' +L.bpd_present1 = 'Подарок вертикальный (10л)' +L.present1 = 'Подарок вертикальный' +L.bpd_present2 = 'Подарок горизонтальный (10л)' +L.present2 = 'Подарок горизонтальный' +L.bpd_presentl = 'Большой подарок (20л)' +L.presentl = 'Большой подарок' +L.pizza_pepperoni = 'Пицца Пепперони' +L.omelet = 'Яичница' +L.pizza_margarita = 'Пицца Маргарита' +L.pizza_volcano = 'Пицца Вулкано' +L.meat_pie = 'Пирожок с мясом' +L.hotdog = 'Хотдог' +L.apple_pie = 'Яблочный пирог' +L.tea = 'Чай' +L.cacao = 'Какао' +L.muffin = 'Кексик' +L.cupcakes = 'Блинчики' +L.mushroom_soup = 'Грибной суп' +L.borsch = 'Борщ' +L.vegetable_stew = 'Овощное рагу' +L.zitti = 'Зитти' +L.bolognese = 'Болоньезе' +L.lasagna = 'Лазанья' +L.cappuccino = 'Капучино' +L.espresso = 'Эспрессо' +L.strawberry_banana_ice_cream = 'Клубнично-банановое мороженое' +L.milkshake = 'Молочный коктейль' +L.bandage = 'Пластырь' +L.clothes = 'Теплая одежда' +L.item_ammo = 'Патроны' +L.item_food = 'Еда' +L.soccer = 'Мяч' +L.body_mat = 'Материал с тела' +L.petards = 'Петарды' +L.item_lock_cont = 'Замок' +L.item_door0 = 'Дверь из черного дерева' +L.item_door1 = 'Старая самодельная дверь' +L.item_door2 = 'Дверь из красного дерева' +L.item_door3 = 'Самодельная дверь' +L.item_door4 = 'Стальная дверь' +L.item_door5 = 'Синяя стальная дверь' +L.item_door6 = 'Синяя пластиковая дверь' +L.item_door7 = 'Старая стальная дверь' +L.item_door8 = 'Дверь с фанерой' +L.item_door9 = 'Старая дверь с фанерой' +L.item_door10 = 'Заколоченная дверь' +L.item_door11 = 'Белая глянцевая дверь' +L.item_door12 = 'Стальная дверь с окошком' +L.item_door13 = 'Простая деревянная дверь' +L.item_door_handle = 'Дверная ручка' +L.item_door_handle1 = 'Поворотная ручка' +L.item_door_handle2 = 'Железная ручка' +L.tool_susp = 'Регулятор подвески' +L.car_kit = 'Ремкомплект' +L.car_wheel = 'Заплатки для колес' +L.car_paint = 'Краска для машины' +L.car_part = 'Деталь для машины' +L.car_rims = 'Диски для машины' +L.car_att = 'Аксессуар для машины' + +L.descBP = 'Чертеж, по которому можно собрать предмет, для сборки также нужны инструмент и детали' +L.descBDP = 'Детали от предмета, для сборки также нужны инструмент и чертеж' +L.descTool = 'Инструмент, используется для сборки деталей и механизмов' +L.descCraft = 'Деталь, используется для сборки других деталей или механизмов' +L.descFuel = 'Топливо, используется для работы некоторых механизмов' +L.descCraftFuel = 'Деталь, используется для сборки или поддержания работы механизмов' +L.descing = 'Ингредиент, его нельзя съесть, зато можно что-нибудь с ним приготовить' +L.descfoodtool = 'Посуда, используется для приготовления блюд' +L.desc_gun_Craft = 'Деталь, используется для сборки оружия на верстаке' +L.desc_gun_BP = 'Программа для станка, используется для вытачивания деталей' +L.descOre = 'Сырье, перерабатывается в очистителе для получения слитков для работы на станке' +L.descIngot = 'Чистый металл, используется на станке для вытачивания деталей' +L.descTemp = 'Пока что этот предмет не используется, но скоро будет ;)' +L.descEvent = 'Местная валюта на время чрезвычайного положения. Не теряй!' + +-- +-- desc items +-- + +L.desc_printer = 'Нелегальное устройство, которое позволит тебе разбогатеть, если ты вовремя будешь его заряжать. Однако гляди в оба - эта штука может выдать тебя своим звуком' +L.desc_bpd_refinery = 'Детали для очистителя' +L.desc_bpd_smelter = 'Детали для печи' +L.desc_bpd_workbench = 'Детали для верстака' +L.desc_bpd_machine = 'Детали для станка' +L.desc_bpd_stove = 'Детали для плиты' +L.desc_bpd_fridge = 'Детали для холодильника' +L.desc_bpd_vend = 'Детали для автомата' +L.desc_cont_workbench = 'Нужен для сборки из деталей' +L.desc_cont_fridge = 'Тут можно сделать десерт или коктейль' +L.desc_cont_refinery = 'Позволяет очистить руду от примесей' +L.desc_cont_smelter = 'Используется для обработки материалов при высокой температуре' +L.desc_cont_machine = 'Сделает куски металла чем-то полезным' +L.desc_cont_stove = 'А как еще готовить пищу? А еще тут есть духовка' +L.desc_cont_vend = 'Уникальные технологии дистанционной торговли' +L.desc_dough1 = 'Пригодится для пирогов и запеканок' +L.desc_dough2 = 'Так вот из чего делаются печеньки...' +L.desc_meatpotato = 'Ничего не может быть лучше котлетки с пюрешкой' +L.desc_ing_icecream = 'Отсюда начинается любое мороженое' +L.desc_potatomash = 'Не в кайф без котлетки' +L.desc_okroshka = 'Пища патриотов' +L.desc_sushi = 'Любителям японской культуры' +L.desc_coffeetail = 'Гоголь-моголь' +L.desc_coffeetail2 = 'Кофейный ликер' +L.desc_burger = 'Классика фастфуда' +L.desc_burger2 = 'Как гамбургер, только с сыром' +L.desc_burger3 = 'Все на булочке с кунжутом' +L.desc_ceasar = 'Царский салат' +L.desc_cake_med = 'Ну очень сладкий' +L.desc_wrap = 'Всегда в твоем сердце' +L.desc_sandwitch = 'Для легкого перекуса' +L.desc_armor = 'Жилет, который сохранит твои внутренности... Внутри тебя' +L.desc_gun_lockpick = 'Универсальный инструмент для открывания того, что нельзя открывать' +L.desc_cracker = 'Позволяет взломать кейпады. Говорят, что такой штукой вскрыли даже Пентагон' +L.desc_tool_foodcont = 'Используется для упаковки блюд в большинстве рецептов' +L.desc_money = 'Ценные бумажки' +L.desc_weapon = 'Можно взять в руки' +L.desc_statuette_ermak = 'Даже если вдруг ты не знаешь, кто такой Ермак, эта черепушка будет смотреться очень круто на приборке твоего авто' +L.desc_spoiler1 = 'Крепится на задней стенке автомобиля' +L.desc_spoiler2 = 'Выглядит крепко, надеемся, на ходу не отвалится' +L.desc_spoiler3 = 'Для тех, кто не любит лишние изгибы' +L.desc_spoiler4 = 'Словно продолжение кузова' +L.desc_spoiler5 = 'Для ценителей необычного' +L.desc_ladder = 'Если вдруг тебе захочется залезть к себе на крышу' +L.desc_supercharger = 'Нет, он не ускоряет автомобиль (пока), зато выглядит внушительно' +L.desc_huladoll = 'Почувствуй себя дальнобойщиком' +L.desc_lamp_pumpkin = 'Добавит немного теплого света в твой автомобиль, если вокруг темно и совсем никого...' +L.desc_car_hook = 'Если ты заботишься о создании нужной атмосферы в автомобиле не с помощью ароматизаторов...' +L.desc_scary_doll = 'Посади эту красотку на спинку заднего сидения и попроси пассажиров не оборачиваться...' +L.desc_radio = 'Позволит разбавить молчаливую атмосферу' +L.desc_mask = 'Стильно, модно, молодежно' +L.desc_gasmask = 'Надевай это, чтобы не заразиться, когда выходишь на поверхность' +L.desc_omelet = 'Что может быть проще?' +L.desc_pizza_pepperoni = 'Бессмертная классика' +L.desc_pizza_margarita = 'Ты не любишь мясо?' +L.desc_pizza_volcano = 'Жгучая штучка' +L.desc_meat_pie = 'Как у бабушки' +L.desc_hotdog = 'Или сосиска в тесте, как хочешь' +L.desc_apple_pie = 'Как его там... Ах да, Шарлотка!' +L.desc_strawberry_banana_ice_cream = 'Вообще-то, там еще шоколад' +L.desc_cigarette = 'Позволяет придать истории особое настроение' +L.desc_bandage = 'Залечит легкие ушибы' +L.desc_lockpick = 'Позволяет залезть туда, куда нельзя залезать. Осторожно, иметь это нелегально!' +L.desc_broken_lockpick = 'Сломанная отмычка. Не представляет особой ценности' +L.desc_talkie = 'Имея эту штуку в кармане, ты сможешь отправлять и принимать радиосообщения' +L.desc_armor2 = 'Позволит сохранить твой внутренний мир там, где он и должен быть' +L.desc_item_ammo = 'Патроны' +L.desc_item_food = 'Позволяет утолить чувство голода' +L.desc_phone = 'Средство связи, которое всегда под рукой' +L.desc_petards = 'Совсем безобидные! Ну только если не кидать их прямо в лицо' +L.desc_lock = 'Позволяет усилить защиту твоих контейнеров и дверей' +L.desc_door = 'Позволяет изменить внешний вид твоей двери' +L.desc_door_handle = 'Можно установить на дверь' +L.desc_salute = 'Больше красок в ночном небе в новогоднюю ночь!' +L.desc_clothes = 'Согреет тебя в это холодное время' +L.desc_tool_susp = 'Используется для изменения параметров подвески автомобиля' +L.desc_car_kit = 'Используется для починки поврежденного двигателя автомобиля' +L.desc_car_wheel = 'Используется для починки поврежденных колес' +L.desc_car_paint = 'Позволяет изменить цвет автомобиля' +L.desc_car_part = 'Деталь для тюнинга внешнего вида автомобиля' +L.desc_car_rims = 'Набор дисков для замены на автомобиле' +L.desc_car_att = 'Бесполезная, но приятная мелочевка для твоего авто' + +-- +-- containers +-- + +L.container_base = '[контейнер] Основа' +L.container_storage = '[контейнер] Хранилище' +L.container_vend = '[контейнер] Автомат' +L.container_mailbox = '[контейнер] Почтовый ящик' +L.container_prod = '[контейнер] Процессор' + +-- +-- entities +-- + +L.salute = 'Салют' +L.survivor_locker = 'Шкафчик для выживших' +L.turret = 'Турель против зомби' +L.petard = 'Петарда' +L.saluteshell = 'Салют (снаряд)' +L.drug_lab = 'Лаборатория' +L.microwave = 'Микроволновка' +L.gunlab = 'Станок' +L.analyzer = 'Анализатор' +L.phone = 'Телефон' +L.bday = 'Тележка с кепками' +L.work_in_police = 'Работа в полиции' +L.policelocker = 'Полицейский шкафчик' +L.city_warehouse = 'Городской склад' +L.h_rewards = 'Старьевщик' +L.trash = 'Мусорка' +L.travel = 'Остановка' +L.soda_machine = 'Автомат с газировкой' +L.workerbox = 'Ящик для рабочего' +L.workproblem = 'Проблемное место (для рабочего)' +L.soccerball = 'Футбольный мяч' +L.soccerballdesc = 'Можно надуть насосом и гонять по двору' + +-- +-- tools +-- + +L.ignite = 'Поджечь' +L.resizer = 'Изменить размер' +L.resizer_no_limit = 'Изменить размер(без ограничений)' +L.resizer_no_limit2 = 'Prop Resizer (без огр.)' +L.noplay = 'Не удалось воспроизвести звук: ' + +-- +-- servers +-- + +L.dobrograd = 'Доброград' +L.old_dobrograd = 'Старый Доброград' +L.new_dobrograd = 'Новый Доброград' +L.school666 = 'Школа 666' +L.desc_dobrograd = 'Уникальная механика сильно выделяется из стандартных DarkRP-серверов и разбавит уже наскучивший геймплей совершенно другим подходом к обычным действиям. Возможно, ты даже не поймешь, что играешь в GMod' +L.desc_new_dobrograd = 'Зеркало сервера Истории города Доброград. Здесь сохранена вся механика и особенности оригинального Доброграда, но играть проще и доступней, подойдет для тех, кто хочет поиграть на Доброграде, но еще не очень разбирается в ролевой игре' +L.desc_school666 = 'Веселые школьные будни и выходные. Учись, учи и делай все, что между этим. Ученики растут со временем и по мере прохождения классов, можно читать книги со школьными историями, которые обновляются каждый день, и еще много-много всего, что ты не увидишь на других серверах' +L.desc_octogames = 'Соревнования с друзьями еще никогда не были такими ламповыми. Режимы постоянно меняются, что позволит не скучать долгое время. Большая часть модов переписана нами, поэтому все сделано с <3' + +-- +-- cars +-- + +L.sim_fphys_hatch_v2 = 'Торпеда' +L.sim_fphys_pwtrabant_v2 = 'Спутник' +L.sim_fphys_pwtrabant2_v2 = 'Спутник (хетчбек)' +L.sim_fphys_pwvan_v2 = 'Бегемот' +L.sim_fphys_pwvan_v2_police = 'Бегемот (полиция)' +L.sim_fphys_hl2_zaz969 = 'Пионер' +L.sim_fphys_hl2_gaz63 = 'Волжанин' +L.sim_fphys_pwavia_v2 = 'Стрекоза' +L.sim_fphys_pwmoskvich_v2 = 'Казак' +L.sim_fphys_gaz52_v2 = 'Тягач' +L.sim_fphys_pwliaz_v2 = 'Грузовик' +L.car_only_for_dobro = 'Эта тачка только для добродеев' +L.buy_car = 'Купить машину' +L.cardealer = 'Продавец машин' +L.seller = 'Продавец' +L.sell = 'Продать' +L.you_sure = 'Ты уверен, что хочешь продать тачку?' +L.test = 'Опробовать' +L.buy = 'Купить' +L.drive_a_car = 'Загнать авто' +L.nearest_car = 'Ближайшее авто' +L.dealer = ' - Продавец' +L.carprice = 'Цена: %s' +L.drive = 'Пригнать' +L.liters = 'ЛИТРЫ' +L.before_pay = 'Сначала плати, потом заправляй' +L.price_for_liter = 'ЦЕНА 80Р/Л' +L.price2_for_liter = 'ЦЕНА 120Р/Л' +L.diesel = 'ДИЗЕЛЬ' +L.petrol = 'БЕНЗИН' +L.buy_fuel = 'Покупка топлива' +L.buy2_fuel = 'Введи количество топлива в литрах (от 1 до 500):' +L.fuel_buy = 'Ты купил %sЛ за %s' +L.how_you_do_this = 'Как ты это сделал?' +L.not_enough_money = 'Маловато деньжат, дружище' +L.incorrect_quantity = 'Неверно введено количество' + +-- +-- look +-- + +L.desc_nearly_dead = '- При смерти' +L.desc_unhealthy_look = '- Нездоровый вид' +L.desc_newbie = '- На вид приезжий' +L.desc_scared = '- На лице испуг' +L.desc_drunk = '- Нетрезвый вид' +L.desc_karma = '- Карма: %s' +L.desc_has_to_itself = '- Располагает к себе' +L.desc_looks_suspicious = '- Выглядит подозрительно' +L.desc_watchlist = '- Активных Watchlist\'ов: %s' +L.desc_nocrime = '- Запрет на криминал закончится %s' +L.desc_nocrime_perm = '- Постоянный запрет на криминал' +L.desc_nopolice = '- Запрет на игру в полиции закончится %s' +L.desc_nopolice_perm = '- Постоянный запрет на игру в полиции' +L.desc_usual = 'Самый обычный человек' +L.desc_its = '- Это %s' +L.desc_murderer = '- Убийца: %s' +L.desc_caliber = '- Судя по калибру стреляли из %s' +L.desc_time_death = '- Смерть наступила в %s' +L.deathCauses = { + unknown = { + 'Понятия не имею, как он погиб', + 'Выглядит так, будто ничто его не убивало', + 'Невероятно загадочная смерть', + }, + [DMG_GENERIC] = { + 'Без вскрытия не понять причину смерти', + 'Следов внешнего воздействия нет', + 'Снаружи ничего не видно', + }, + [DMG_CLUB] = { + 'Кажется, смерть из-за сильного избиения без оружия', + 'Похоже на результат рукопашного боя', + 'Похоже на ссадины от ударов руками', + }, + [DMG_SHOCK] = { + 'Кажется, его хорошенько огрели чем-то тупым', + 'Убийство было совершено тупым предметом', + 'Удар тупым предметом лишил его жизни', + }, + [DMG_SONIC] = { + 'Убийство было совершено режущим оружием', + 'Холодное оружие... Кажется, режущее', + 'На теле видны глубокие порезы', + }, + [DMG_BULLET] = { + 'Убийство было совершено огнестрельным оружием', + 'На теле видны следы пулевых отверстий', + 'В него точно стреляли', + }, + [DMG_FALL] = { + 'Нога выгнута, возможно, это падение с высоты', + 'Кажется, этот человек разбился насмерть', + 'Имел место очень сильный удар об пол', + }, + [DMG_BURN] = { + 'Кожа во многих местах обожжена', + 'Он явно умер от многих ожогов', + }, +} + +L.names = { + male = { + 'Джеймс','Джон','Майкл','Винсент','Стив','Уилл','Вильям','Джонни','Майки','Джимми', + 'Дуглас','Уейн','Винни','Алекс','Альберт','Дональд','Грег','Ларри','Ллойд','Лео', + 'Остин','Ричи','Сэм','Уолтер','Чарльз','Шон','Питер', 'Бенджамин','Роберт','Дэйви', + 'Мартин','Даниэль','Ирвин','Рэнди','Эдвард','Эдди','Джастин','Фредерик', 'Кристофер', + }, + female = { + 'Оливия','Эмма','София','Ева','Изабелла','Мия','Шарлотта','Эмили','Харпер','Эбигейл', + 'Эйвери','Элла','Маргарет','Лили','Хлоя','Софи','Эвелин','Анна','Эдисон','Грейс', + 'Зоуи','Одри','Арья', 'Ария', 'Амели','Элли','Натали','Скарлет','Виктория', 'Кэролайн', + 'Элен','Оливия','Аманда','Кэти','Кэтрин','Джулия','Скарлетт', + }, + surnames = { + 'Смит','Джонсон','Уильямс','Браун','Кларк','Родригес','Тейлор','Томпсон','Уайт', + 'Мартинес','Ли','Хилл','Скотт','Грин','Митчелл','Паркер','Коллинс','Моррис','Купер', + 'Говард','Эрнандес','Райт','Вашингтон','Симмонс','Фостер','Хейз', 'Лопес', + 'Скот', 'Грин', 'Бейкер', 'Нельсон', 'Картер', 'Перес', 'Тёрнер', 'Кэмпбелл', + }, +} + +-- +-- darkrp_language +-- + +L.need_admin = 'Чтобы %s, нужно быть администратором' +L.need_sadmin = 'Чтобы %s, нужно быть суперадминистратором' +L.no_privilege = 'У тебя нет нужных привилегий' +L.no_jail_pos = 'Нет тюрьмы' +L.invalid_x = '%s содержит ошибку! %s' +L.f1ChatCommandTitle = 'Команды для чата' +L.f1Search = 'Поиск...' +L.price = 'Цена: %s%d' +L.priceTag = 'Цена: %s' +L.reset_money = '%s обнулил все кошельки' +L.has_given = '%s дал тебе %s' +L.you_gave = 'Ты дал %s %s' +L.npc_killpay = '%s за убийство персонажа!' +L.deducted_x = 'Вычтено %s%d' +L.need_x = 'Нужно %s%d' +L.deducted_money = 'Вычтено %s' +L.need_money = 'Нужно %s' +L.payday_message = 'Зарплата! Ты получил %s!' +L.payday_unemployed = 'Ты сегодня без зарплаты. Иди работай!' +L.payday_missed = 'Ты сегодня без зарплаты. В тюрьме не платят' +L.property_tax_warn = 'В ближайшее время с твоего банковского счета будет списано %s за аренду. Если такой суммы не окажется на твоем счете, ты останешься без недвижимости' +L.property_tax = 'Оплата аренды недвижимости: %s' +L.property_tax_cant_afford = 'Ты не смог оплатить аренду, и теперь у тебя нет недвижимости' +L.taxday = 'День налогов! %s%% изъято из твоей зарплаты!' +L.found_cheque = 'Ты нашел чек на %s%s выписанный тебе от %s' +L.cheque_details = 'Этот чек выписан игроку %s' +L.cheque_torn = 'Ты разорвал чек' +L.cheque_pay = 'Кому: %s' +L.signed = 'От: %s' +L.found_cash = 'Ты нашел %s%d!' +L.found_money = 'Ты нашел %s!' +L.Wanted_text = 'В розыске!' +L.wanted = 'Разыскивается полицией!\nЗа %s' +L.youre_arrested = 'Тебя выпустят через %s' +L.youre_arrested_by = 'Ты арестован игроком %s' +L.youre_unarrested_by = 'Ты был отпущен игроком %s' +L.hes_arrested = '%s арестован на %d секунд!' +L.hes_unarrested = '%s выпущен из тюрьмы!' +L.warrant_ordered = 'Был получен ордер на обыск гражданина %s. Причина: %s' +L.warrant_request = '%s запросил ордер на обыск гражданина %s\nПричина: %s' +L.warrant_request2 = 'Запрос на обыск отправлен мэру' +L.warrant_approved2 = 'Теперь его дом можно обыскать' +L.warrant_denied = 'Мэр отклонил твой запрос на обыск' +L.warrant_expired = 'Ордер на обыск гражданина %s отозван' +L.warrant_required = 'Чтобы открыть эту дверь, нужен ордер на обыск' +L.warrant_required_unfreeze = 'Чтобы разморозить этот проп, нужен ордер на обыск' +L.warrant_required_unweld = 'Чтобы открепить этот проп, нужен ордер на обыск' +L.wanted_by_police = '%s разыскивается полицией!\nПричина: %s\nЗапросил: %s' +L.wanted_by_police_print = '%s подал в розыск на %s. Причина: %s' +L.wanted_expired = '%s больше не разыскивается' +L.wanted_revoked = '%s больше не разыскивается.\nРозыск отозвал: %s\nПричина: %s' +L.cant_arrest_other_cp = 'Ты не можешь арестовывать других копов!' +L.must_be_wanted_for_arrest = 'Чтобы арестовать игрока, он должен быть в розыске' +L.cant_arrest_fadmin_jailed = 'Ты не можешь арестовать игрока, который был посажен в тюрьму админом' +L.cant_arrest_no_jail_pos = 'Ты не можешь арестовывать игроков — нет тюрьмы!' +L.cant_arrest_spawning_players = 'Ты не можешь арестовывать игроков на спауне' +L.suspect_doesnt_exist = 'Подозреваемый не найден' +L.actor_doesnt_exist = 'Преступник не найден' +L.get_a_warrant = 'получить ордер' +L.make_someone_wanted = 'подать в розыск' +L.remove_wanted_status = 'убрать из розыска' +L.already_a_warrant = 'Ордер на обыск этого подозреваемого уже в действии' +L.already_wanted = 'Подозреваемый уже в розыске' +L.not_wanted = 'Подозреваемый не разыскивается' +L.need_to_be_cp = 'Ты должен быть копом' +L.suspect_must_be_alive_to_do_x = 'Подозреваемый должен быть жив для %s' +L.suspect_already_arrested = 'Подозреваемый уже в тюрьме' +L.you_must_to_be_mayor = 'Вы должны быть мэром' +L.health_hint = 'Здоровье: %s' +L.darkrpjob = 'Профессия: %s' +L.salary = 'Зарплата: %s%s' +L.wallet = 'Кошелек: %s%s' +L.weapon = 'Оружие: %s' +L.kills = 'Убийства: %s' +L.deaths = 'Смерти: %s' +L.rpname_changed = '%s сменил свое ролевое имя на %s' +L.disconnected_player = 'Ушедший игрок' +L.need_to_be_before = 'Нужно иметь профессию %s, чтобы получить профессию %s' +L.need_to_make_vote = 'Чтобы получить профессию %s, нужно создать голосование!' +L.team_limit_reached = 'Невозможно получить профессию %s из-за лимита' +L.wants_to_be = '%s\nхочет получить профессию\n%s' +L.has_not_been_made_team = '%s не получил профессию %s!' +L.job_has_become = '%s получил профессию %s!' +L.meteor_approaching = 'ВНИМАНИЕ: Метеоритный дождь!' +L.meteor_passing = 'Метеоритный дождь закончился' +L.meteor_enabled = 'Метеоритные дожди включены' +L.meteor_disabled = 'Метеоритные дожди выключени' +L.keys_allowed_to_coown = 'Ты один из совладельцев\n(Удерживай для покупки)\n' +L.keys_other_allowed = 'Совладельцы:' +L.keys_allow_ownership = '(Удерживай для добавления совладельцев)' +L.keys_disallow_ownership = '(Удерживай для удаления совладельцев)' +L.keys_owned_by = 'Владелец:' +L.keys_unowned = 'Удерживай, чтобы купить' +L.keys_everyone = '(Удерживай для разрешения покупки всем)' +L.door_unown_arrested = 'Ты не можешь ничего делать под арестом!' +L.door_unownable = 'С этой дверью нельзя ничего сделать!' +L.door_sold = 'Помещение продано за %s' +L.door_already_owned = 'Эта дверь уже кому-то пренадлежит!' +L.door_cannot_afford = 'Ты не можешь позволить себе эту дверь!' +L.door_hobo_unable = 'Ты бездомный, ты не можешь купить дверь!' +L.vehicle_cannot_afford = 'Ты не можешь позволить себе этот транспорт!' +L.estate_business_only = 'Здесь должно располагаться какое-то заведение. Если ты ищешь помещение для проживания, пожалуйста, не занимай это и найди более подходхящее' +L.estate_residency_only = 'Здесь должно располагаться чье-то жилище. Если ты ищешь помещение для постройки заведения, пожалуйста, не занимай это и найди более подходящее' +L.door_bought = 'Помещение куплено за %s' +L.vehicle_bought = 'Ты купил транспорт за %s%s' +L.door_need_to_own = 'Ты должен владеть этой дверью, чтобы делать %s' +L.door_rem_owners_unownable = 'Ты не можешь убирать совладельцев, с этой дверью нельзя ничего сделать!' +L.door_add_owners_unownable = 'Ты не можешь добавлять совладельцев, с этой дверью нельзя ничего сделать!' +L.rp_addowner_already_owns_door = '%s уже является совладельцем этой двери!' +L.add_owner = 'Добавить совладельца' +L.remove_owner = 'Удалить совладельца' +L.allow_ownership = 'Разрешить покупку' +L.disallow_ownership = 'Запретить покупку' +L.edit_door_group = 'Редактировать группы' +L.door_groups = 'Группы дверей' +L.door_group_doesnt_exist = 'Группа дверей не существует!' +L.door_group_set = 'Группы двери отредактированы' +L.sold_x_doors_for_y = 'Ты продал %d дверей за %s%d!' +L.sold_x_doors = 'Ты продал %d дверей за %s!' +L.drugs = 'Наркотики' +L.gun_lab = 'Станок' +L.gun = 'оружие' +L.food = 'Еда' +L.money_printer = 'Принтер' +L.sign_this_letter = 'Подписать письмо' +L.signed_yours = 'Ваш,' +L.money_printer_exploded = 'Твой принтер взорвался!' +L.money_printer_overheating = 'Твой принтер перегрелся!' +L.contents = 'Содержимое: ' +L.amount = 'Количество: ' +L.picking_lock = 'Взлом' +L.cannot_pocket_x = 'Ты не можешь положить это в карман!' +L.object_too_heavy = 'Этот предмет слишком тяжелый' +L.pocket_full = 'Твой карман полон!' +L.pocket_no_items = 'В твоем кармане ничего нет' +L.drop_item = 'Положить предмет' +L.bonus_destroying_entity = 'уничтожение нелегальщины' +L.switched_burst = 'Переключено на режим стрельбы очередями' +L.switched_fully_auto = 'Переключено в автоматический режим стрельбы' +L.switched_semi_auto = 'Переключено в полуавтоматический режим стрельбы' +L.keypad_checker_shoot_keypad = 'Выстрели в кейпад, чтобы увидеть, чем он управляет' +L.keypad_checker_shoot_entity = 'Выстрели в предмет, чтобы увидеть, какие кейпады им управляют' +L.keypad_checker_click_to_clear = 'Нажми ПКМ, чтобы снять выделение' +L.keypad_checker_entering_right_pass = 'Ввод правильного пароля' +L.keypad_checker_entering_wrong_pass = 'Ввод неправильного пароля' +L.keypad_checker_after_right_pass = 'после ввода правильного пароля' +L.keypad_checker_after_wrong_pass = 'после ввода неправильного пароля' +L.keypad_checker_right_pass_entered = 'Введен правильный пароль' +L.keypad_checker_wrong_pass_entered = 'Введен неправильный пароль' +L.keypad_checker_controls_x_entities = 'Этот кейпад управляет %d предметами' +L.keypad_checker_controlled_by_x_keypads = 'Этот предмет управляется %d кейпадами' +L.keypad_on = 'ВКЛ.' +L.keypad_off = 'ВЫКЛ.' +L.seconds = 'сек.' +L.persons_weapons = '%s имеет при себе нелегальное оружие:' +L.returned_persons_weapons = 'Ты вернул игроку %s конфискованное у него оружие' +L.no_weapons_confiscated = 'У игрока %s ничего не было конфисковано!' +L.no_illegal_weapons = '%s не имеет при себе нелегального оружия' +L.confiscated_these_weapons = 'Ты конфисковал:' +L.checking_weapons = 'Конфискация' +L.shipment_antispam_wait = 'Подожди немного прежде чем покупать еще один ящик' +L.shipment_cannot_split = 'Этот ящик нельзя разделить' +L.hear_noone = 'Тебя никто не слышит!' +L.hear_everyone = 'Тебя все слышат!' +L.hear_certain_persons = 'Тебя слышат: ' +L.whisper = 'шепот' +L.yell = 'крик' +L.prefix_advert = '[реклама]' +L.prefix_broadcast = '[трансляция]' +L.prefix_request = '[Вызов №%s] %s: ' +L.prefix_group = '[группа]' +L.prefix_demote = '[увольнение]' +L.talkie_x = 'Радио %d' +L.speak_in_ooc = 'говорить в OOC' +L.perform_your_action = 'совершить действие' +L.talk_to_your_group = 'говорить с группой' +L.channel_set_to_x = 'Радио настроено на канал %s!' +L.config_disabled = '%s отключено! %s' +L.see_settings = 'Посмотри в настройки DarkRP' +L.limit = 'Многовато у тебя %s!' +L.have_to_wait = 'Надо подождать %dс перед использованием %s!' +L.must_be_looking_at = 'Для выполнения этого действия нужно смотреть на %s' +L.incorrect_job = 'Твоя профессия не позволяет тебе это сделать' +L.this_is_not_your_door = 'Эта дверь не принадлежит тебе' +L.unavailable_hint = 'Это %s недоступно' +L.unable = 'Ты не можешь %s. %s' +L.cant_afford = 'Маловато деньжат для %s' +L.created_x = '%s создал %s' +L.cleaned_up = 'Твои %s были убраны' +L.you_bought_x = 'Ты купил %s за %s%d' +L.you_bought = 'Ты купил %s за %s' +L.you_received_x = 'Ты получил %s за %s' +L.cannot_drop_weapon = 'Это оружие нельзя выбросить' +L.cooks_only = 'Только для поваров' +L.unknown2 = 'Неизвестный' +L.arguments = 'аргумент' +L.no_one = 'ни один' +L.door = 'дверь' +L.vehicle = 'транспорт' +L.door_or_vehicle = 'дверь/транспорт' +L.driver = 'Водитель: %s' +L.name = 'Имя: %s' +L.locked = 'Закрыто' +L.unlocked = 'Открыто' +L.player_doesnt_exist = 'Игрок не существует' +L.job_doesnt_exist = 'Профессия не существует!' +L.must_be_alive_to_do_x = 'Ты должен быть живым, чтобы делать %s' +L.banned_or_demoted = 'Забанен/уволен' +L.wait_with_that = 'Погоди с этим' +L.could_not_find = 'Не найден %s' +L.f3tovote = 'Нажми F3 для голосования' +L.listen_up = 'Слушай сюда:' +L.reset_settings = 'Ты сбросил все настройки!' +L.must_be_x = 'Ты должен быть %s, чтобы делать %s' +L.agenda_updated = 'Повестка обновлена' +L.job_set = '%s сделал "%s" своей профессией' +L.demoted = '%s был уволен' +L.demoted_not = '%s не был уволен' +L.demote_vote_started = '%s начал голосование за увольнение игрока %s' +L.demote_vote_text = '%s хочет уволить игрока %s. Причина: %s' +L.cant_demote_self = 'Ты не можешь уволить себя' +L.i_want_to_demote_you = 'Я хочу тебя уволить. Причина: %s' +L.tried_to_avoid_demotion = 'Ты пытался избежать увольнения, но у тебя не получилось' +L.lockdown_started = 'В городе объявлен комендантский час!' +L.lockdown_ended = 'Комендантский час окончен' +L.gunlicense_requested = '%s запросил лицензию у игрока %s' +L.gunlicense_granted = '%s выдал лицензию игроку %s' +L.gunlicense_taken = '%s отозвал лицензию игрока %s' +L.gunlicense_denied = '%s отказал в выдаче лицензии игроку %s' +L.gunlicense_question_text = 'Дать лицензию игроку %s?' +L.gunlicense_remove_vote_text = '%s начал голосование на отзыв лицензии у игрока %s' +L.gunlicense_remove_vote_text2 = 'Отозвать лицензию:\n%s' +L.gunlicense_removed = 'Лицензия игрока %s была отозвана!' +L.gunlicense_not_removed = 'Лицензия игрока %s не была отозвана!' +L.vote_specify_reason = 'Ты должен определить причину!' +L.vote_started = 'Голосование создано' +L.vote_alone = 'Ты победил в голосовании, ты же один на сервере, лол' +L.vote_not_enough_players = 'Недостаточно игроков для голосования' +L.you_cannot_vote = 'Ты не можешь голосовать!' +L.x_cancelled_vote = '%s отменил последнее голосование' +L.cant_cancel_vote = 'Нельзя отменить голосование!' +L.jail_punishment = 'Наказание за побег! Арестован на %d сек' +L.admin_only = 'Только для админов!' +L.chief_or = 'Шеф или ' +L.frozen = 'Заморожен' +L.suicide = 'совершить самоубийство' +L.dead_in_jail = 'Ты мертв, пока не выйдешь из тюрьмы!' +L.died_in_jail = '%s скончался в тюрьме!' +L.not_allowed_to_purchase = 'Ты не можешь это купить' +L.you_set_x_salary_to_y = 'Ты установил зарплату у %s на %s%d' +L.x_set_your_salary_to_y = '%s изменил твою зарплату на %s%d' +L.you_set_x_money_to_y = 'Ты изменил деньги %s на %s%d' +L.x_set_your_money_to_y = '%s изменил твои деньги на %s%d' +L.you_set_x_salary = 'Ты установил зарплату у %s на %s' +L.x_set_your_salary = '%s изменил твою зарплату на %s' +L.you_set_x_money = 'Ты установил деньги у %s на %s' +L.x_set_your_money = '%s изменил твои деньги на %s' +L.you_set_x_name = 'Ты установил имя у %s на %s' +L.x_set_your_name = '%s изменил твое имя на %s' +L.already_taken = 'Занято :\\' +L.lottery_started = 'Объявлена городская лотерея! Цена лотерейного билета - %s. Если вы желаете принять участие, то отправьте ответное SMS на номер lottery с содержанием \'1\'.' +L.lottery_entered = 'Ваше участие в лотерее зарегистрировано. В случае, если Вы окажетесь победителем, мы отправим Вам уведомление. Желаем удачи!' +L.lottery_won = 'Вы победили в лотерее! Сумма выигрыша, равная %s (с учетом комиссии компании в 5 процентов), уже зачислена на Ваш банковский счет.' +L.lottery_noone_entered = 'Никто не принял участие в лотерее' +L.local_lottery_won = 'Ты выиграл %s в лотерее! Деньги уже на твоем банковском счету' +L.starving = 'Нужно поесть!' +L.afk_mode = 'Режим AFK' +L.salary_frozen = 'Твоя зарплата заморожена' +L.salary_restored = 'С возвращением! Твоя зарплата восстановлена' +L.no_auto_demote = 'Ты не будешь уволен автоматически' +L.youre_afk_demoted = 'Ты уволен за долгий AFK. В следующий раз используй /afk' +L.hes_afk_demoted = '%s уволен за долгий AFK' +L.afk_cmd_to_exit = 'Введи /afk еще раз, чтобы выйти из режима AFK' +L.player_now_afk = '%s теперь AFK' +L.player_no_longer_afk = '%s теперь не AFK' +L.hitman = 'Наемник' +L.current_hit = 'Цель: %s' +L.cannot_request_hit = 'Сейчас нанять не получится! %s' +L.player_not_hitman = 'Этот игрок не наемник!' +L.distance_too_big = 'Слишком далеко' +L.hitman_no_suicide = 'Наемник не станет убивать себя' +L.hitman_no_self_order = 'Наемник не может нанять сам себя' +L.hitman_already_has_hit = 'Наемник уже нанят кем-то' +L.price_too_low = 'Цена слишком мала!' +L.hit_target_recently_killed_by_hit = 'Наемник убил свою цель,' +L.customer_recently_bought_hit = 'Наемник только что был нанят' +L.accept_hit_question = 'Принять заказ %s\nна убийство игрока %s за %s%d?' +L.accept_hit_request = 'Принять заказ %s\nна убийство игрока %s за %s?' +L.hit_requested = 'Заказ получен!' +L.hit_aborted = 'Выполнение заказа провалилось! %s' +L.hit_accepted = 'Заказ принят!' +L.hit_declined = 'Наемник отменил заказ!' +L.hitman_left_server = 'Наемник ушел!' +L.customer_left_server = 'Заказчик ушел!' +L.target_left_server = 'Цель ушла!' +L.hit_price_set_to_x = 'Цена убийства установлена на %s%d' +L.hit_price_set = 'Цена убийства установлена на %s' +L.hit_complete = 'Заказ игрока %s выполнен!' +L.hitman_died = 'Наемник погиб!' +L.target_died = 'Цель погибла!' +L.hitman_arrested = 'Наемник арестован!' +L.hitman_changed_team = 'Наемник сменил профессию!' +L.x_had_hit_ordered_by_y = '%s был нанят игроком %s' +L.hobos_no_rights = 'Бездомные не могут голосовать!' +L.gangsters_cant_vote_for_government = 'Бандиты не могут принимать участие в голосованиях, связанных с государственными делами!' +L.government_cant_vote_for_gangsters = 'Члены государственного управление не могут принимать участие в голосованиях, связанных с бандитскими делами!' +L.time_hint = 'Время: %d' +L.add = 'Добавить' +L.x_options = 'Настроить %s' +L.sell_x = 'Продать %s' +L.set_x_title = 'Назвать %s' +L.set_x_title_long = 'Назвать %s, на которую ты сейчас смотришь' +L.jobs = 'Профессии' +L.buy_x = 'Купить %s' +L.initiate_lockdown = 'Объявить комендантский час' +L.stop_lockdown = 'Отменить комендантский час' +L.start_lottery = 'Начать лотерею' +L.laws_of_the_land = 'МЕСТНЫЕ ЗАКОНЫ' +L.law_added = 'Закон издан' +L.law_removed = 'Закон отозван' +L.law_reset = 'Законы переизданы' +L.law_too_short = 'Закон должен быть более развернутым' +L.laws_full = 'Законов слишком много' +L.default_law_change_denied = 'Нельзя изменять стандартные законы' +L.license_tab_other_weapons = 'Другое оружие:' + +-- +-- karma +-- + +L.msg_good = 'Быть примерным гражданином просто, правда?' +L.msg_good2 = 'Хороший человек - тот, кто не делает ничего плохого.' +L.msg_good3 = 'У тебя хорошая репутация в городе.' + +L.msg_strange = ' Странно, что ты это сделал, ведь у тебя хорошая репутация' +L.msg_strange2 = ' Не похоже на тебя' +L.msg_strange3 = ' Всем казалось, что это не свойственно тебе' +L.msg_strange4 = ' Печально видеть, что становишься плохим' + +L.up_karma = 'Твоя карма улучшилась.' +L.down_karma = 'Твоя карма ухудшилась.' +L.karma_notify = '%s (текущая карма: %d)' + +L.karma_kill = 'Убивать - это очень плохо.' +L.karma_suicide = 'Самоубийцы попадают в ад, это все знают.' +L.karma_scare = 'Угрожать - черта преступников.' +L.karma_damage = 'Ему, наверное, больно.' +L.karma_not_your = 'Кажется, это принадлежит не тебе.' + +-- +-- octoshop +-- + +L.octoshop_what = 'Что это' +L.octoshop_where = 'Когда активировать' +L.octoshop_validity = 'Срок действия' +L.octoshop_expansion = 'Расширение' +L.octoshop_in_some_moment = 'В любой момент' +L.octoshop_one_month = 'Один месяц' +L.octoshop_buns = 'Плюшки' +L.octoshop_game_currency = 'Игровая валюта' +L.octoshop_good_action = 'Хорошее дело' +L.octoshop_error_receiver = 'Ошибка в определении получателя' +L.octoshop_give_error = 'Ты не можешь передать этот предмет' +L.octoshop_soon_do = 'Скоро сделаем, пока что не работает ;)' +L.octoshop_you_cant_sell = 'Ты не можешь продать этот предмет' +L.octoshop_error = 'Ошибка синхронизации' +L.octoshop_error_use = 'Ты не можешь использовать этот предмет' +L.octoshop_error_equip = 'Ты не можешь надеть этот предмет' +L.octoshop_error_unequip = 'Ты не можешь снять этот предмет' +L.octoshop_you_give = 'Ты передал ' +L.octoshop_you_give2 = ', получатель: ' +L.octoshop_you_take = 'Ты получил ' +L.octoshop_you_take2 = ', отправитель: ' +L.octoshop_item_expired = 'Срок действия предмета ' +L.octoshop_item_expired2 = ' закончился' +L.octoshop_not_enough = 'Недостаточно средств' +L.octoshop_you_cant_buy = 'Ты не можешь купить этот предмет' +L.octoshop_you_bought = 'Ты купил ' + +-- +-- cats +-- + +L.openTickets = 'Открытые жалобы' +L.myTicket = 'Моя жалоба' +L.userDisconnected = 'Пользователь вышел' +L.claimedBy = 'Разбирается' +L.sendMessage = 'Написать сообщение...' +L.typeYourMessage = 'Введите сообщение:' +L.action_claim = 'Взять жалобу' +L.action_unclaim = 'Передать жалобу' +L.action_spectate = 'Наблюдать' +L.action_goto = 'К нему' +L.action_bring = 'К себе' +L.action_return = 'Вернуть на место' +L.action_returnself = 'Вернуться на место' +L.action_copySteamID = 'Скопировать SteamID' +L.action_callon = 'Включить просьбу о помощи' +L.action_calloff = 'Выключить просьбу о помощи' +L.action_close = 'Закрыть жалобу' +L.error_wait = 'Тихо-тихо... Куда так разогнался?' +L.error_noAccess = 'Ошибка доступа' +L.error_playerNotFound = 'Игрок не найден' +L.error_ticketNotEnded = 'Жалоба не закрыта' +L.error_ticketNotFound = 'Жалоба не найдена' +L.error_ticketEnded = 'Жалоба уже решена' +L.error_ticketNotClaimed = 'Жалоба никем не взята' +L.error_ticketAlreadyClaimed = 'Жалоба уже взята' +L.error_needToRate = 'Ты должен оценить прошлую жалобу!' +L.error_cantCancelHasAdmin = 'Нельзя отменить жалобу, которую рассматривают' +L.ticketClaimed = 'Жалоба взята' +L.ticketUnclaimed = 'Жалоба отдана' +L.ticketClaimedBy = 'Твою жалобу принял %s' +L.ticketUnclaimedBy = 'Твоя жалоба передана' +L.ticketClosed = 'Жалоба закрыта' +L.ticketClosedBy = '%s закрыл жалобу' +L.ticketRatedForAdmin = 'Оценка по твоей жалобе: %s' +L.ticketRatedForUser = 'Ты оценил решение жалобы на %s' +L.ticketUserLeft = 'Пользователь, чью жалобу ты решал, вышел' +L.rateAdmin = 'Нажми ниже, чтобы выбрать оценку' +L.ticket_noAdmins = 'На сервере сейчас нет свободных администраторов. Мы позвали других на помощь, и если кто-то зайдет, он увидит твою жалобу' +L.dow = {'ПН','ВТ','СР','ЧТ','ПТ','СБ','ВС'} + +-- +-- С-menu +-- + +L.c_language_money = 'Передать деньги' +L.c_language_money_description = 'Сколько денег ты хочешь передать?' +L.c_language_demote = 'Уволить' +L.c_language_demote_description = 'За что ты хочешь уволить его?' +L.c_language_wanted = 'Подать в розыск' +L.c_language_wanted_description = 'За что ты хочешь подать на него в розыск?' +L.c_language_unwanted = 'Отозвать розыск' +L.c_language_unwanted_description = 'За что ты хочешь снять с него розыск?' +L.c_language_warrant = 'Запросить обыск' +L.c_language_warrant_description = 'Почему ты хочешь обыскать игрока?' +L.c_language_warrant_prop = 'Запросить обыск на владельца пропов' +L.c_language_radio_enable = 'Включить рацию' +L.c_language_radio_disable = 'Выключить рацию' +L.c_language_radio_changechannel = 'Сменить частоту' +L.c_language_give_license = 'Дать лицензию' +L.c_language_money_drop = 'Бросить' +L.c_language_money_drop_description = 'Сколько денег ты хочешь бросить?' +L.c_language_money_put_description = 'Сколько денег ты хочешь положить?' +L.c_language_money_give = 'Дать деньги на кого смотришь' +L.c_language_money_give_description = 'Сколько денег ты хочешь дать?' +L.c_language_drop = 'Бросить оружие' +L.c_language_unown_all = 'Продать все двери' +L.c_language_lockdown = 'Объявить ком. час' +L.c_language_unlockdown = 'Отменить ком. час' +L.c_language_agenda = 'Установить агенду' +L.c_language_agenda_description = 'Введи текст агенды' +L.c_language_custom_job = 'Сменить профессию' +L.c_language_custom_job_description = 'Какую профессию ты хочешь поставить?' +L.c_language_rpname = 'Сменить имя' +L.c_language_rpname_description = 'Какое имя ты хочешь поставить?' +L.dog_names = { + 'Аксель', 'Алекс', 'Альберт', 'Альто', 'Амелия', 'Анда', 'Анна', 'Ариадна', 'Арнольд', 'Арчи', 'Арчибальд', 'Афина', + 'Бавария', 'Баки', 'Бейли', 'Белла', 'Бернард', 'Бетси', 'Бонни', 'Бруно', + 'Вега', 'Вера', 'Виксен', 'Витти', 'Воррен', + 'Генри', 'Герда', 'Грейс', + 'Дакота', 'Дарман', 'Дейзел', 'Дейзи', 'Джаспер', 'Джеймс', 'Джек', 'Джиджи', 'Джинжер', 'Дилан', 'Дино', 'Докси', 'Дэвид', + 'Зельда', 'Зоуи', 'Зус', + 'Изабелла', + 'Калеб', 'Касус', 'Кельвин', 'Клэр', 'Коннор', 'Кора', 'Купер', + 'Леона', 'Леонард', 'Лиам', 'Локи', 'Лола', 'Луна', 'Люси', + 'Маврик', 'Макс', 'Марли', 'Марта', 'Мегги', 'Мейбел', 'Метью', 'Мики', 'Милли', 'Молли', 'Монро', + 'Натали', 'Нейтон', 'Нелли', 'Несси', 'Нора', + 'Один', 'Одиссей', 'Оливер', 'Олли', 'Оскар', 'Отис', + 'Пинки', 'Пит', 'Раймонд', + 'Рози', 'Рокс', 'Рокси', 'Руди', 'Руф', + 'Сали', 'Сальма', 'Самуэль', 'Сара', 'Скреппи', 'София', 'Спайк', 'Спарки', 'Стелла', + 'Тайни', 'Терра', 'Тор', 'Тото', + 'Флойд', 'Флоренс', 'Френк', 'Фрида', + 'Харли', 'Хлоэ', + 'Цезарь', + 'Чарли', 'Честер', 'Чип', + 'Эби', 'Эйс', 'Элвис', 'Элен', 'Эли', 'Элизабет', 'Элис', 'Эльба', 'Эмир', 'Эмма', 'Энджел', 'Энрест', 'Эрнест', +} diff --git a/garrysmod/addons/_config/lua/config/octolib-use/cl_hooks.lua b/garrysmod/addons/_config/lua/config/octolib-use/cl_hooks.lua new file mode 100644 index 0000000..686a6b9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-use/cl_hooks.lua @@ -0,0 +1,11 @@ +local icon = Material('octoteam/icons/hand_point.png') +hook.Add('dbg-view.chOverride', 'octolib.use', function(tr) + + if tr.Fraction > 0.05 or HAND_DRAGGING then return end + + local ent = tr.Entity + if IsValid(ent) and octolib.use.classes[ent:GetClass()] then + return icon, 255, 0.3 + end + +end) diff --git a/garrysmod/addons/_config/lua/config/octolib-use/sv_cars.lua b/garrysmod/addons/_config/lua/config/octolib-use/sv_cars.lua new file mode 100644 index 0000000..a05f442 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-use/sv_cars.lua @@ -0,0 +1,796 @@ +local function exitPlayer(ent, sender) + + local driver = ent:GetDriver() + local HasPassenger = IsValid(driver) + + if HasPassenger then + local cuffed, cuffs = driver:IsHandcuffed() + if cuffed then + driver.isForce = true + end + + driver.handledVehicleExit = true + local can = hook.Run('CanExitVehicle', ent, driver) + driver.handledVehicleExit = nil + driver.isForce = false + if can == false then + if sender then + sender:Notify('warning', 'Что-то мешает высадить ' .. driver:Name()) + end + return + end + + driver:ExitVehicle() + driver:SetCollisionGroup(COLLISION_GROUP_PLAYER) + end + +end + +CFG.use.gmod_sent_vehicle_fphysics_base = { + + function(ply, ent) + local seat = ply:GetVehicle() + if IsValid(seat) and seat:GetParent() == ent then return end + + return L.sit, 'octoteam/icons/door_open.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + + function(ply, ent) + local seat = ply:GetVehicle() + if not IsValid(seat) or seat:GetParent() ~= ent then return end + + return 'Выйти', 'octoteam/icons/door_open.png', function(ply) + exitPlayer(ply:GetVehicle()) + end + end, + + function(ply, ent) + if ply ~= ent:CPPIGetOwner() then return end + if ent:GetIsLocked() then + return L.open, 'octoteam/icons/lock_open.png', function(ply, ent) + ent:UnLock() + end + else + return L.close, 'octoteam/icons/lock.png', function(ply, ent) + ent:Lock() + end + end + end, + + function(ply, ent) + if not ent.inv or not ent.inv.conts.trunk then return end + local canOpen = ply:Team() == TEAM_ADMIN or ply == ent:CPPIGetOwner() or (not ent:GetIsLocked() and ply:Team() == TEAM_DPD) or false + return 'Багажник', 'octoteam/icons/car_trunk.png', function(ply, ent, args) + local back = ent:NearestPoint(ent:WorldSpaceCenter() + ent.Forward * -1000) + if (ply:GetShootPos() - back):Length2DSqr() > CFG.useDistSqr then + return ply:Notify('warning', 'Подойди ближе к задней части автомобиля') + end + + if canOpen then + if args[1] == 'lock' then + ent.trunkOpen = nil + ent:EmitSound('doors/door_latch1.wav') + ent.inv.conts.trunk:RemoveUsers() + elseif args[1] == 'unlock' then + ent.trunkOpen = true + ent:EmitSound('doors/door_latch1.wav') + ply:OpenInventory(ent.inv, {'trunk'}) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + end + end + + if args[1] == 'open' then + if not ent.trunkOpen then return end + ply:OpenInventory(ent.inv, {'trunk'}) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + elseif args[1] == 'lockpick' then + if not ply.inv.conts._hand or ply.inv.conts._hand:HasItem('lockpick') <= 0 then return end + ply:SetEyeAngles((back - ply:GetShootPos()):Angle()) + ply:Lockpick(ent, { + timeOverride = ply:HasBuff('Dextradose') and 0.825 or nil, + succ = function(ply, ent) + ent.trunkOpen = true + ent:EmitSound('doors/door_latch1.wav') + end, + check = function(ply, ent) + if ply.inv.conts._hand:HasItem('lockpick') <= 0 then return false end + if not IsValid(ent) then return false end + if (ply:GetShootPos() - back):Length2DSqr() > CFG.useDistSqr then + return false + end + return true + end, + }) + end + end, function(ply, ent) + local opts = {} + if ent.trunkOpen then + opts[#opts + 1] = {'Использовать', 'open'} + if canOpen then + opts[#opts + 1] = {'Закрыть', 'lock'} + end + else + if canOpen then + opts[#opts + 1] = {'Открыть', 'unlock'} + elseif not ply:CheckCrimeDenied() then + if ply.inv.conts._hand and ply.inv.conts._hand:HasItem('lockpick') > 0 then + opts[#opts + 1] = {'Взломать', 'lockpick'} + else + opts[#opts + 1] = {'Для взлома нужна отмычка в руках'} + end + end + end + + return opts + end + end, + + function(ply, ent) + local carInv = ent:GetInventory() + if not carInv or not carInv.conts.glove then return end + if ply:Team() ~= TEAM_ADMIN and ply:Team() ~= TEAM_DPD then return end + if ent:GetIsLocked() and ply:Team() == TEAM_DPD then return end + return 'Бардачок', 'octoteam/icons/car_glovebox.png', function(ply, ent, args) + ply:OpenInventory(carInv, {'glove'}) + end + end, + + function(ply, ent) + if ply == ent:CPPIGetOwner() then return end + if ply:CheckCrimeDenied() then return end + if not ply.inv or not ply.inv.conts._hand or ply.inv.conts._hand:HasItem('lockpick') <= 0 then return end + if ply:GetNetVar('dbg-police.job', '') ~= '' then return end + local dSeat = ent.DriverSeat + if not IsValid(dSeat) then return end + return L.lockpick_action, 'octoteam/icons/lockpick.png', function(ply, ent) + local dPos = dSeat:GetPos() + if (ply:GetShootPos() - dPos):Length2DSqr() > CFG.useDistSqr then + return ply:Notify('Подойди ближе к двери водительского сидения') + end + ply:SetEyeAngles((dPos - ply:GetShootPos()):Angle()) + ply:Lockpick(ent, { + timeOverride = ply:HasBuff('Dextradose') and 0.825 or nil, + succ = function(ply, ent) + ent:UnLock() + ent.lockpicked = true + ent.nextLock = CurTime() + 30 + end, + check = function(ply, ent) + if ply.inv.conts._hand:HasItem('lockpick') <= 0 then return false end + if not IsValid(dSeat) then return false end + dPos = dSeat:GetPos() + if (ply:GetShootPos() - dPos):Length2DSqr() > CFG.useDistSqr then + return false + end + return true + end, + }) + end + end, + + function(ply, ent) + local target = ply:GetNetVar('dragging') + if not IsValid(target) then return end + + return L.plant_the_detainee, 'octoteam/icons/handcuffs.png', function(ply, ent) + local cuffed, cuffs = target:IsHandcuffed() + if IsValid(target) and cuffed and target:GetNetVar('dragger') == ply then + target.isForce = true + ent:SetPassenger(target, true) + end + end + end, + + function(ply, ent) + local owner = ent:CPPIGetOwner() + if not (ply == owner or not IsValid(owner) or ent.lockpicked or (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true))) then return end + + return L.to_land, 'octoteam/icons/clothes_boot.png', function(ply, ent, args) + if IsValid(ent) then + if args[1] then + exitPlayer(args[1], ply) + else + for _, v in ipairs(ent:GetChildren()) do + if v:GetClass() == 'prop_vehicle_prisoner_pod' then + local driver = v:GetDriver() + if IsValid(driver) then + exitPlayer(v, ply) + end + end + end + end + end + end, function(ply, ent) + local args = {{L.to_land_all}} + for _, v in ipairs(ent:GetChildren()) do + if v:GetClass() == 'prop_vehicle_prisoner_pod' and IsValid(v:GetDriver()) then + table.insert(args, {v:GetDriver():Name(), v}) + end + end + return args + end + end, + function(ply, ent) + local job = ply:getJobTable() + if not job.mech then return end + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return false, why end + return L.suspension, 'octoteam/icons/metal_spring.png', function(ply, ent, args) + if ply:HasItem('tool_susp') <= 0 then return end + + local susp = {} + local carData = list.Get('simfphys_vehicles')[ent.VehicleName].Members + if args[1] == 1 then + octolib.request.send(ply, {{ + name = L.car_front_suspension, + },{ + type = 'numSlider', min = -1, max = 1, + txt = L.height, val = ent:GetFrontSuspensionHeight() or 0, + },{ + type = 'numSlider', min = carData.FrontConstant, max = carData.FrontConstant * 2, dec = 0, + txt = L.rigidity, val = ent.consF or carData.FrontConstant, + },{ + type = 'numSlider', min = 0, max = carData.FrontDamping * 1.5, dec = 0, + txt = L.car_blanking, val = ent.dampF or carData.FrontDamping, + },{ + type = 'numSlider', min = -8, max = 4, + txt = L.car_takeaway, val = ent.wOffsetF or 0, + },{ + name = L.car_rear_suspension, + },{ + type = 'numSlider', min = -1, max = 1, + txt = L.height, val = ent:GetRearSuspensionHeight() or 0, + },{ + type = 'numSlider', min = carData.RearConstant, max = carData.RearConstant * 2, dec = 0, + txt = L.rigidity, val = ent.consR or carData.RearConstant, + },{ + type = 'numSlider', min = 0, max = carData.RearDamping * 1.5, dec = 0, + txt = L.car_blanking, val = ent.dampR or carData.RearDamping, + },{ + type = 'numSlider', min = -4, max = 4, + txt = L.car_takeaway, val = ent.wOffsetR or 0, + },{ + name = L.other, + },{ + type = 'numSlider', min = -18, max = 10, dec = 0, + txt = L.car_collapse, val = ent.camber or 0, + }}, function(data) + simfphys.SetupSuspension(ent, { + data[2] or ent:GetFrontSuspensionHeight() or 0, + data[3] or ent.consF or carData.FrontConstant or 25000, + data[4] or ent.dampF or carData.FrontDamping or 1500, + data[7] or ent:GetRearSuspensionHeight() or 0, + data[8] or ent.consR or carData.RearConstant or 25000, + data[9] or ent.dampR or carData.RearDamping or 1500 + }) + + simfphys.ApplyWheel(ent, data[12] or ent.camber or 0, ent.wModelF or carData.CustomWheelModel or 'models/salza/zaz/zaz_wheel.mdl') + simfphys.SetWheelOffset(ent, data[5] or 0, data[10] or 0) + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end) + elseif args[1] == 2 then + simfphys.SetupSuspension(ent, { + 0, + carData.FrontConstant or 25000, + carData.FrontDamping or 1500, + 0, + carData.RearConstant or 25000, + carData.RearDamping or 1500 + }) + + simfphys.ApplyWheel(ent, 0, ent.wModelF or carData.CustomWheelModel or 'models/salza/zaz/zaz_wheel.mdl') + simfphys.SetWheelOffset(ent, 0, 0) + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end + end, function(ply, ent) + if ply:HasItem('tool_susp') <= 0 then return {{L.need_suspension, -1}} end + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return {{why or L.unavailable, -1}} end + return { + {L.regulate, 1}, + {L.reset, 2}, + } + end + end, + function(ply, ent) + -- local job = ply:getJobTable() + -- if not job.mech then return end + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return false, why end + return L.take_off, 'octoteam/icons/tag_delete.png', function(ply, ent, args) + if ply:HasItem('tool_wrench') < 1 or ply:HasItem('tool_screwer') < 1 then return end + if not ply.inv or not ply.inv.conts._hand then return end + + local att = ent:GetNetVar('atts')[args[1]] + if not att or not att.name then return end + + ply:DelayedAction('car_mount', L.dismantling, { + time = 20, + check = function() return octolib.use.check(ply, ent) and ply.inv and ply.inv.conts._hand end, + succ = function() + local atts = ent:GetNetVar('atts') + local att = table.remove(atts, args[1]) + ent:SetNetVar('atts', atts) + + local attData = simfphys.getAttByModel(att.model) + if attData then + ply.inv.conts._hand:AddItem('car_att', { + name = attData.name, + desc = attData.desc, + icon = attData.icon, + mass = attData.mass, + volume = attData.volume, + colorable = not attData.noPaint or nil, + attmdl = attData.mdl, + model = attData.mdl, + skin = attData.skin, + scale = attData.scale, + }) + else + ply.inv.conts._hand:AddItem('car_att', { + name = att.name, + icon = att.icon, + mass = att.mass, + volume = att.volume, + colorable = att.colorable, + attmdl = att.model, + model = att.model, + skin = att.skin, + scale = att.scale, + }) + end + + timer.Simple(1, function() carDealer.saveVeh(ent) end) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('ambient/machines/pneumatic_drill_' .. math.random(1, 4) .. '.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end, function(ply, ent) + if ply:HasItem('tool_wrench') < 1 or ply:HasItem('tool_screwer') < 1 then return {{L.need_wrench_and_screwer, -1}} end + if not ply.inv or not ply.inv.conts._hand then return {{L.hands_free, -1}} end + local can, why = simfphys.CanPlayerTune(ply, ent) + if not can then return {{why or L.unavailable, -1}} end + + local atts = ent:GetNetVar('atts') + local opts = {} + if atts then + for id, att in ipairs(atts) do + if att.name then table.insert(opts, {att.name, id}) end + end + end + return opts + end + end, + function(ply, ent) + if ply:Team() ~= TEAM_ADMIN then return end + return 'Сбросить', 'octoteam/icons/license_plate_remove.png', function(ply, ent, args) + local owner = ent:CPPIGetOwner() + if not IsValid(owner) then return ply:Notify('warning', 'Для этого владелец автомобиля должен быть на сервере') end + + if args[1] == -1 then + local carData = list.Get('simfphys_vehicles')[ent.VehicleName].Members + simfphys.SetupSuspension(ent, { + 0, + carData.FrontConstant or 25000, + carData.FrontDamping or 1500, + 0, + carData.RearConstant or 25000, + carData.RearDamping or 1500 + }) + simfphys.ApplyWheel(ent, 0, ent.wModelF or carData.CustomWheelModel or 'models/salza/zaz/zaz_wheel.mdl') + simfphys.SetWheelOffset(ent, 0, 0) + timer.Simple(1, function() carDealer.saveVeh(ent) end) + + ply:Notify('Настройки подвески были сброшены, уведомление отправлено владельцу') + owner:Notify('ooc', 'Администрация сбросила настройки подвески на твоем автомобиле. Если у тебя есть вопросы, открой жалобу через @') + return + end + + if args[1] == -2 then + local col = ent:GetColor() + if col.r + col.g + col.b == 255 * 3 then return end + + ent:SetColor(Color(255,255,255)) + carDealer.saveVeh(ent) + + octoinv.addReturnItems(owner, {{'car_paint', 1}}) + + ply:Notify('Цвет автомобиля был сброшен, уведомление отправлено владельцу') + owner:Notify('ooc', 'Администрация сбросила покраску твоего автомобиля. Краску можно вернуть в магазине. Если у тебя есть вопросы, открой жалобу через @') + return + end + + if args[1] == -3 then + carDealer.getPlateChangeHistory(ent:GetNetVar('cd.id'), ply:SteamID(), function(gCount, history) + if not history[1] or ent:GetNetVar('cd.plate') ~= history[1] then + return ply:Notify('warning', 'У этого игрока не блатной номер') + end + local desc = '' + gCount = gCount - 1 + if gCount > 0 then + desc = desc .. 'Владелец ранее менял номера своих автомобилей ' .. gCount .. ' раз' .. octolib.string.formatCount(gCount, '', 'а', '') + else desc = desc .. 'Владелец ранее не менял номера своих автомобилей' end + if history[1] then + desc = desc .. ' История установки блатных номеров этого автомобиля: ' .. (#history == 20 and '... -> ' or '') .. string.Implode(' -> ', table.Reverse(history)) + end + octolib.request.send(ply, { + { + name = 'Сброс номера', + desc = 'Это действие нужно осуществлять только если номер нарушает правила. Запрещено использовать эту функцию в корыстных целях' + }, { + desc = desc, + }, { + type = 'check', + desc = 'Если такое нарушение было зафиксировано впервые, плюшку следует вернуть', + txt = 'Вернуть плюшку', + check = true, + }, { + desc = 'Факт сброса номера будет сохранен', + } + }, function(data) + if not IsValid(ent) then return end + carDealer.resetPlate(ent, data[3], ply) + end) + end) + return + end + + local att = ent:GetNetVar('atts')[args[1]] + if not att or not att.name then return end + + local atts = ent:GetNetVar('atts') + local att = table.remove(atts, args[1]) + ent:SetNetVar('atts', atts) + timer.Simple(1, function() carDealer.saveVeh(ent) end) + + local attData = simfphys.getAttByModel(att.model) + if attData then + octoinv.addReturnItems(owner, {{'car_att', { + name = attData.name, + desc = attData.desc, + icon = attData.icon, + mass = attData.mass, + volume = attData.volume, + colorable = not attData.noPaint or nil, + attmdl = attData.mdl, + model = attData.mdl, + skin = attData.skin, + scale = attData.scale, + }}}) + else + octoinv.addReturnItems(owner, {{'car_att', { + name = att.name, + icon = att.icon, + mass = att.mass, + volume = att.volume, + colorable = att.colorable, + attmdl = att.model, + model = att.model, + skin = att.skin, + scale = att.scale, + }}}) + end + + ply:Notify('Аксессуар был снят с автомобиля и возвращен владельцу') + owner:Notify('ooc', 'Администрация сняла аксессуар ' .. att.name .. ' с твоего автомобиля, его можно вернуть через магазин. Если у тебя есть вопросы, открой жалобу через @') + end, function(ply, ent) + local opts = { + {'Подвеска', -1}, + } + + local col = ent:GetColor() + if col.r + col.g + col.b ~= 255 * 3 then + opts[#opts + 1] = {'Цвет', -2} + end + + if ply:query('DBG: Сбрасывать номера') then + opts[#opts + 1] = {'Номер', -3} + end + + local atts = ent:GetNetVar('atts') + if atts then + for id, att in ipairs(atts) do + if att.name then table.insert(opts, {att.name, id}) end + end + end + + return opts + end + end, + function(ply, ent) + if not ply:isCP() then return end + return L.check_hint, 'octoteam/icons/card2.png', function(ply, ent, args) + ply:DoEmote(L.emote_check) + ply:DelayedAction('car_check', L.check_action, { + time = 10, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + local owner = ent:CPPIGetOwner() + local name = IsValid(owner) and owner:Name() or L.unknown + ply:Notify(L.car_owner_hint .. name) + end, + }) + end + end, + function(ply, ent) + if not ent.police then return end + + local bodygroup, takeOff, armorOn, armorOff + if ply:Team() == TEAM_DPD then + local mdl = ply:GetModel() + if mdl:find('octo_dpd/') then + bodygroup, armorOn, armorOff = 8, 50, 28 + elseif mdl:find('dpdsl/') or mdl:find('dpdfem/') then + bodygroup, armorOn, armorOff = 3, 50, 28 + else return end + elseif ply:Team() == TEAM_WCSO then + bodygroup, armorOn, armorOff = 1, 65, 40 + elseif ply:Team() == TEAM_ALPHA then + bodygroup, armorOn, armorOff = 5, 55, 45 + else return end + + takeOff = ply:GetBodygroup(bodygroup) == 1 + return (takeOff and 'Снять' or 'Надеть') .. ' бронежилет', 'octoteam/icons/armor.png', function(ply, ent, args) + local back = ent:NearestPoint(ent:WorldSpaceCenter() + ent.Forward * -1000) + if (ply:GetShootPos() - back):Length2DSqr() > CFG.useDistSqr then + return ply:Notify('warning', 'Подойди ближе к задней части автомобиля') + end + + ply:DelayedAction('armor', (takeOff and 'Снятие' or 'Экипировка') .. ' бронежилета', { + time = 5, + check = function() + return octolib.use.check(ply, ent) + end, + succ = function() + ply:DoEmote('{name}, открыв багажник с помощью ключа, ' .. (takeOff and 'снимает' or 'надевает') .. ' бронежилет и закрывает багажник') + ply:SetArmor(takeOff and armorOff or armorOn) + ply:SetBodygroup(bodygroup, takeOff and 0 or 1) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if IsValid(ent) then + ent:EmitSound('npc/combine_soldier/gear'.. math.random(1, 2) ..'.wav', 45) + end + end, + }) + end + end, + function(ply, ent) + local cdData = ent.cdData + if not (cdData and cdData.glauncher) then return end + if not ent.police then return end + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= 'weapon_octo_grenade_launcher' then return end + return 'Пополнить снаряды', 'octoteam/icons/grenade_launcher.png', function(ply, ent, args) + if ply:GetActiveWeapon() ~= wep then return end + + if ent.lockerBusy then + return ply:Notify('warning', 'Кто-то уже сейчас пользуется багажником') + end + + ply:DelayedAction('glauncher', 'Использование багажника', { + time = 4, + check = function() + if not octolib.use.check(ply, ent) then return false end + if ply:GetActiveWeapon() ~= wep then return false end + return true + end, + succ = function() + ent.lockerBusy = nil + wep:Recharge() + ply:DoEmote('{name} пополняет запас 40-мм снарядов для гранатомета') + end, + fail = function() + ent.lockerBusy = nil + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if IsValid(ent) then + ent:EmitSound('npc/combine_soldier/gear'.. math.random(1, 2) ..'.wav', 45) + end + end, + }) + + ent.lockerBusy = true + + end + end, + function(ply, ent) + if not ent.police or not (ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO) then return end + return 'Аммуниция', 'octoteam/icons/gun_rifle.png', function(ply, ent, args) + ent.locker = ent.locker or {} + + local isTrunk, weaponClass, weaponModel = unpack(args) + + local back = ent:NearestPoint(ent:WorldSpaceCenter() + ent.Forward * -1000) + if isTrunk and (ply:GetShootPos() - back):Length2DSqr() > CFG.useDistSqr then + return ply:Notify('warning', 'Подойди ближе к задней части автомобиля') + end + + if ent.lockerBusy then + return ply:Notify('warning', 'Кто-то уже сейчас достает оружие') + end + + if ent.locker[weaponClass] and not ply:HasWeapon(weaponClass) then + return ply:Notify('warning', 'Чтобы это вернуть, нужно это иметь') + end + + ply:DelayedAction('locker', 'Использование ' .. (isTrunk and 'багажника' or 'локера'), { + time = 4, + check = function() + if not octolib.use.check(ply, ent) then return false end + if ent.locker[weaponClass] and not ply:HasWeapon(weaponClass) then return false end + return true + end, + succ = function() + ent.lockerBusy = nil + if ent.locker[weaponClass] then + ply:StripWeapon(weaponClass) + for i, wep in ipairs(ply.orgWeps) do + if wep == weaponClass then + table.remove(ply.orgWeps, i) + end + end + ent.locker[weaponClass] = nil + else + ply.orgWeps = ply.orgWeps or {} + ply.orgWeps[#ply.orgWeps + 1] = weaponClass + ent.locker[weaponClass] = true + local wep = ply:Give(weaponClass) + if weaponModel then wep.WorldModel = weaponModel end + local clip1 = wep:GetMaxClip1() + if clip1 then wep:SetClip1(clip1) end + wep:Initialize() + end + end, + fail = function() + ent.lockerBusy = nil + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if IsValid(ent) then + ent:EmitSound('npc/combine_soldier/gear'.. math.random(1, 2) ..'.wav', 45) + end + end, + }) + + ent.lockerBusy = true + + end, function(ply, ent) + local opts = { + {'M4A1', false, 'weapon_octo_m4a1'}, + {'M3', false, 'weapon_octo_m3'}, + {'BeanBag', true, 'weapon_octo_beanbag'}, + {'щит', true, 'dbg_shield', 'models/bshields/rshield.mdl'}, + } + if ply:GetActiveRank('dpd') == 'swat' then table.Empty(opts) end + + for i, data in ipairs(opts) do + data[1] = (ent.locker and ent.locker[data[3]] and 'Вернуть ' or 'Взять ') .. data[1] + end + + local cdData = ent.cdData + if cdData and cdData.glauncher and (ply:GetActiveRank('dpd') == 'swat' or ply:GetActiveRank('wcso') == 'seb') then + opts[#opts + 1] = { + (ent.locker and ent.locker['weapon_octo_grenade_launcher'] and 'Вернуть ' or 'Взять ') .. 'гранатомет', + true, 'weapon_octo_grenade_launcher' + } + end + + return opts + end + end, + function(ply, ent) + if not (ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO) or not ent.police then return end + local giveBack = ent.usedspikes + if giveBack and ply:HasItem('spike_strips') <= 0 then return end + return (giveBack and 'Вернуть ' or 'Взять ') .. 'полицейские шипы', 'octoteam/icons/spike_strips.png', function(ply, ent, args) + local back = ent:NearestPoint(ent:WorldSpaceCenter() + ent.Forward * -1000) + if (ply:GetShootPos() - back):Length2DSqr() > CFG.useDistSqr then + return ply:Notify('warning', 'Подойди ближе к задней части автомобиля') + end + + if ent.spikesBusy then + ply:Notify('warning', 'Кто-то уже сейчас достает шипы') + return + end + + ply:DelayedAction('spikes', 'Использование багажника', { + time = 5, + check = function() + if not octolib.use.check(ply, ent) then return false end + if giveBack and ply:HasItem('spike_strips') <= 0 then return false end + return true + end, + succ = function() + ent.spikesBusy = nil + ply:DoEmote('{name}, открыв багажник с помощью ключа, ' .. (giveBack and 'возвращает' or 'достает') .. ' шипы и закрывает багажник') + if giveBack then + ply:TakeItem('spike_strips') + ent.usedspikes = nil + else + ply:AddItem('spike_strips', {expire = os.time() + 7200}) + ent.usedspikes = true + end + end, + fail = function() + ent.spikesBusy = nil + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if IsValid(ent) then + ent:EmitSound('physics/rubber/rubber_tire_strain' ..math.random(1,3) ..'.wav', 65) + end + end, + }) + + ent.spikesBusy = true + + end + end, + function(ply, ent) + local owner = ent:CPPIGetOwner() + local cost = 1000 + local money = BraxBank.PlayerMoney(owner) + local scoreToSet = ply:isCP() and 10 or 17 + + if ply:GetNetVar('wanted') or ply:isArrested() or not(ply:isCP() or ply == owner) then return end + if (ent.idleScore or 0) >= scoreToSet or not IsValid(owner) then return end + + return L.evacuate, 'octoteam/icons/receipt.png', function(ply, ent, args) + if ply:isCP() then + ply:Notify(L.evacuate_hint_ply) + owner:Notify(L.evacuate_hint_owner) + else + if not owner:BankHas(cost) then return ply:Notify('warning', 'Недостаточно средств на банковском счете') end + if money < cost then return ply:Notify('warning', 'Недостаточно средств') end + owner:Notify(('Ты сделал запрос на эвакуатор для своего автомобиля. С твоего счета в банке было снято %s'):format(DarkRP.formatMoney(cost))) + owner:BankAdd(-cost) + end + ent.idleScore = scoreToSet + ply:DoEmote(L.evacuation_emote) + hook.Run('dbg.evacuation', ent, ply, owner) + end + end, + function(ply, ent) + if ent:GetIsLocked() then return end + return 'Радио', 'octoteam/icons/boombox.png', function(ply, ent, args) + local r = ent.Radio + if IsValid(r) then + netstream.Start(ply, 'dbg-radio.control', r, r.whitelisted, r.curID, r.curTitle, r.curPlace, r.curCountry) + end + end + end, + function(ply, ent) + if not ent.trunkOpen then return end + local pack = ent.package + if not IsValid(pack) then return end + local owner = ent:CPPIGetOwner() + if not (ply == owner or not IsValid(owner) or ent.lockpicked or (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true)) or pack.ply == ply) then + return + end + return 'Достать груз', octolib.icons.color('box4'), function(ply, ent) + pack:DetachFromCar() + end + end, +} + diff --git a/garrysmod/addons/_config/lua/config/octolib-use/sv_conts.lua b/garrysmod/addons/_config/lua/config/octolib-use/sv_conts.lua new file mode 100644 index 0000000..b4c0377 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-use/sv_conts.lua @@ -0,0 +1,501 @@ +local function setLocked(ent, val) + if ent.SetLocked then + return ent:SetLocked(val) + end + + if not ent:GetInventory() or not ent.lootable then + return + end + + ent.locked = val + ent:EmitSound('doors/door_latch' .. (val and 1 or 3) .. '.wav', 55) + + if val then + for contID, cont in pairs(ent.inv.conts) do + for user, _ in pairs(cont.users) do + cont:RemoveUser(user) + end + end + end +end + +local contUse = { + function(ply, ent) + return L.check_hint, 'octoteam/icons/search.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + local can, why = hook.Run('octoinv.canUnlock', ply, ent) + if can == false then return end + if not ent:GetInventory() then return end + if not ent.locked then return end + return L.open, 'octoteam/icons/lock_open.png', function(ply, ent) + setLocked(ent, false) + end + end, + function(ply, ent) + local can, why = hook.Run('octoinv.canLock', ply, ent) + if can == false then return end + if not ent:GetInventory() then return end + if ent.locked then return end + if CurTime() < (ent.nextLock or 0) then return false, L.item_recently_lockpicked end + return L.close, 'octoteam/icons/lock.png', function(ply, ent) + setLocked(ent, true) + end + end, + function(ply, ent) + if not ent.locked or ent:GetNetVar('owner') == ply then return end + if ply:CheckCrimeDenied() then return end + if not ply.inv or not ply.inv.conts._hand or ply.inv.conts._hand:HasItem('lockpick') <= 0 then return end + if ply:GetNetVar('dbg-police.job', '') ~= '' then return end + return L.lockpick_action, 'octoteam/icons/lockpick.png', function(ply, ent) + ply:Lockpick(ent, { + timeOverride = ply:HasBuff('Dextradose') and 0.825 or nil, + succ = function(ply, ent) + setLocked(ent, false) + ent.nextLock = CurTime() + 30 + end, + check = function(ply, ent) + return ply.inv.conts._hand:HasItem('lockpick') > 0 + end, + }) + end + end, + function(ply, ent) + if ent:GetNetVar('owner') ~= ply or ent.pendingOwner then return end + return L.write_give, 'octoteam/icons/man_medit.png', function(ply, ent, args) + -- ent:SetPlayer(args[1]) + ent.pendingOwner = args[1] + ply:Notify('rp', L.give_end_when, args[1]:Name(), L.give_end_when2) + end, function(ply, ent) + local args = {} + for i, v in ipairs(player.GetAll()) do + if v ~= ply then + table.insert(args, { v:Name(), v }) + end + end + + return args + end + end, + function(ply, ent) + if not ent.pendingOwner or ent:GetNetVar('owner') ~= ply then return end + return L.take_cancel, 'octoteam/icons/man_mdel.png', function(ply, ent) + ent.pendingOwner = nil + ply:Notify(L.take_cancel_hint) + end + end, + function(ply, ent) + if not ent.pendingOwner or ent.pendingOwner ~= ply then return end + return L.take_item, 'octoteam/icons/man_medit.png', function(ply, ent) + if isfunction(ent.CanBeOwnedBy) then + local can, why = ent:CanBeOwnedBy(ply) + if not can then + ply:Notify('warning', why or 'Ты не можешь принять этот предмет сейчас') + ent.pendingOwner = nil + return + end + end + ent:SetPlayer(ply) + ent.pendingOwner = nil + ply:Notify(L.now_is_your) + end + end, + function(ply, ent) + if ent.static or IsValid(ent.carWeld) or ent:GetNetVar('owner') ~= ply then return end + local b = ent:NearestPoint(ent:GetPos() - Vector(0,0,1000)) + local t = { + start = b, + endpos = b - Vector(0,0,4), + filter = ent, + } + + local car = util.TraceLine(t).Entity + if IsValid(car) and car.IsSimfphyscar then + return L.freeze_on_car, 'octoteam/icons/screw.png', function(ply, ent) + ent.carWeld = constraint.Weld(ent, car, 0, 0, 5000, true, false) + ply:Notify(L.freezed_on_car) + end + else + return L.freeze, 'octoteam/icons/screw.png', function(ply, ent) + DarkRP.freeze(ply, ent, function(ok, msg) + ply:Notify(ok and 'hint' or 'warning', msg) + end) + end + end + end, + function(ply, ent) + if ent:GetNetVar('owner') ~= ply then return end + if IsValid(ent.carWeld) then + return L.item_unfreezed_car, 'octoteam/icons/screw.png', function(ply, ent) + ent.carWeld:Remove() + ent.carWeld = nil + ply:Notify(L.item_unfrozen_car) + end + elseif ent.static then + return L.unfreeze, 'octoteam/icons/screw.png', function(ply, ent) + DarkRP.unfreeze(ply, ent) + ply:Notify(L.item_unfrozen) + end + end + end, + function(ply, ent) + if ent.locked or not ent.DestructParts then return end + local job = ply:getJobTable() + if not job or not job.canDestruct then return end + return L.destruct, 'octoteam/icons/crowbar.png', function(ply, ent) + ply:DelayedAction('destruct', L.destruct_hint, { + time = 30, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + ent:Destruct() + end, + }, { + time = 1, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + local sound = 'weapons/357/357_reload'.. math.random(1, 4) ..'.wav' + ply:EmitSound(sound, 50) + end, + }) + end + end, +} + +CFG.use.octoinv_cont = contUse +CFG.use.octoinv_prod = contUse +CFG.use.octoinv_vend = table.Add(table.Copy(contUse), { + function(ply, ent) + if ent.locked or ent:GetNetVar('owner') ~= ply then return end + return L.run, 'octoteam/icons/badge_sale.png', function(ply, ent) + local r = {[0] = { + name = L.run_hint, + desc = L.desc_run_hint + }} + + for k, slot in pairs(ent.slots) do + local item = slot.cont.items[1] + if item then + r[k] = { + required = true, + type = 'strShort', + numeric = true, + name = string.format(L.vend_slot_hint, k, item:GetData('name')), + ph = L.price_for_one, + } + end + end + + octolib.request.send(ply, r, function(data) + if not ent:SetPrices(data) then + ply:Notify('warning', L.vend_price_fail) + end + end) + end + end, +}) + +CFG.use.octoinv_storage = { + function(ply, ent) + return L.check_hint, 'octoteam/icons/search.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + local canUnlock = ent.steamID == ply:SteamID() or ply:Team() == TEAM_ADMIN + if not canUnlock or ent.locked then return end + return L.close, 'octoteam/icons/lock.png', function(ply, ent) + setLocked(ent, true) + if canUnlock then ent.nextSteal = nil end + + local cont = ent.inv.conts['storage'] + for user, _ in pairs(cont.users) do + cont:RemoveUser(user) + end + end + end, + function(ply, ent) + if (ent.steamID ~= ply:SteamID() and ply:Team() ~= TEAM_ADMIN) or not ent.locked then return end + return L.open, 'octoteam/icons/lock_open.png', function(ply, ent) + setLocked(ent, false) + end + end, + function(ply, ent) + if not ent.locked or ent.steamID == ply:SteamID() then return end + if ply:CheckCrimeDenied() then return end + if not ply.inv or not ply.inv.conts._hand or ply.inv.conts._hand:HasItem('lockpick') <= 0 then return end + if ply:GetNetVar('dbg-police.job', '') ~= '' then return end + return L.lockpick_action, 'octoteam/icons/lockpick.png', function(ply, ent) + ply:Lockpick(ent, { + timeOverride = ply:HasBuff('Dextradose') and 0.825 or nil, + succ = function(ply, ent) + setLocked(ent, false) + ent.nextLock = CurTime() + 30 + end, + check = function(ply, ent) + return ply.inv.conts._hand:HasItem('lockpick') > 0 + end, + }) + end + end, + function(ply, ent) + local owner = player.GetBySteamID(ent.steamID) + if ent.locked or ply:GetNetVar('dbg-police.job', '') ~= '' then return end + if IsValid(owner) and (owner == ply or (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true))) then return end + return L.steal, 'octoteam/icons/crowbar.png', function(ply, ent) + if ent.stealing then + ply:Notify('warning', L.already_stealing) + return + end + + if CurTime() < (ent.nextSteal or 0) then + ply:Notify('warning', L.recent_stealed) + return + end + + ent.stealing = true + ply:DelayedAction('steal', L.stealing, { + time = 20, + check = function() return octolib.use.check(ply, ent) and not ent.locked end, + succ = function() + local stolen = ent.inv:MoveRandom(ply.inv.conts._hand, { + maxItems = 5, + maxVolume = 25, + }) + ent.nextSteal = CurTime() + 30 * 60 + ent.stealing = nil + + hook.Run('octoinv.stolen', table.GetFirstValue(ent.inv.conts), stolen, ply) + end, + fail = function() + ent.stealing = nil + end + }, { + time = 1, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + ply:EmitSound('physics/cardboard/cardboard_box_shake'.. math.random(1, 3) ..'.wav', 50) + end, + }) + end + end, + function(ply, ent) + local owner = player.GetBySteamID(ent.steamID) + if not (IsValid(owner) and (owner == ply or (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true)))) then return end + if ent.static or IsValid(ent.carWeld) then return end + local b = ent:NearestPoint(ent:GetPos() - Vector(0,0,1000)) + local t = { + start = b, + endpos = b - Vector(0,0,4), + filter = ent, + } + + local car = util.TraceLine(t).Entity + if IsValid(car) and car.IsSimfphyscar then + return L.freeze_on_car, 'octoteam/icons/screw.png', function(ply, ent) + ent.carWeld = constraint.Weld(ent, car, 0, 0, 5000, true, false) + ply:Notify(L.freezed_on_car) + end + else + return L.freeze, 'octoteam/icons/screw.png', function(ply, ent) + local pos = ent:GetPos() + timer.Simple(1, function() + if ent:GetPos():DistToSqr(pos) > 1 then + ply:Notify(L.unstable) + return + end + + ent:GetPhysicsObject():EnableMotion(false) + ent.static = true + ply:Notify(L.item_freezed) + end) + end + end + end, + function(ply, ent) + local owner = player.GetBySteamID(ent.steamID) + if not (IsValid(owner) and (owner == ply or (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true)))) then return end + if IsValid(ent.carWeld) then + return L.item_unfreezed_car, 'octoteam/icons/screw.png', function(ply, ent) + ent.carWeld:Remove() + ent.carWeld = nil + ply:Notify(L.item_unfrozen_car) + end + elseif ent.static then + return L.unfreeze, 'octoteam/icons/screw.png', function(ply, ent) + ent:GetPhysicsObject():EnableMotion(true) + ent.static = nil + ply:Notify(L.item_unfrozen) + end + end + end, +} + +local evidenceClasses = octolib.array.toKeys({ + 'weapon', 'lockpick', 'lockpick_broken', 'throwable', 'ammo', 'armor', + 'drug_vitalex', 'drug_painkiller', 'drug_relaxant', 'drug_vampire', 'drug_dextradose', 'drug_roids', + 'drug_bouncer', 'drug_antitoxin', 'drug_weed', 'drug_pingaz', 'drug_preserver', 'drug_meth' +}) +CFG.use.ent_dbg_trash = { + function(ply, ent) + return L.use, 'octoteam/icons/hand_point.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + if ent.pendingWorker ~= ply then return end + return 'Выбросить мусор', octolib.icons.color('trash'), function(ply, ent) + ent:NonHoboUse(ply) + end + end, + function(ply, ent) + local job = ply:getJobTable() + if not (ply:isCP() or job.canZip) or ent.evidenceSearch then return end + return 'Найти улики', octolib.icons.color('search'), function(ply, ent) + + if ply:isCP() and ply:Team() ~= TEAM_DPD and team.NumPlayers(TEAM_CORONER) >= 1 then + return ply:Notify('warning', 'На сервере сейчас имеются коронёры, обратись за их помощью по рации') + end + + if not (ply.inv and ply.inv.conts._hand) then + return ply:Notify('warning', L.hands_free) + end + + ent.evidenceSearch = true + ply:DelayedAction('searchEvidence', 'Поиск улик', { + time = math.Clamp(-ent.innerCont:FreeSpace() / 10, 5, 40), + check = function() return octolib.use.check(ply, ent) and ply:isCP() end, + succ = function() + ent.evidenceSearch = nil + local strs, mass, volume, ids = {}, 0, 0, {} + for _, item in ipairs(ent.innerCont.items) do + local class = item:GetData('class') + if not evidenceClasses[class] then continue end + strs[#strs + 1] = octoinv.itemStr({class, item}) + ids[#ids + 1] = item.id + local amount = item:GetData('amount') + mass = mass + item:GetData('mass') * amount + volume = volume + item:GetData('volume') * amount + end + + if not strs[1] then + ply:Notify('warning', 'Улик не обнаружено') + return + end + + if ply.inv.conts._hand:AddItem('zip', { + collector = ply:Nick() .. '\n\n', + storedStr = table.concat(strs, ', '), + amount = 1, + mass = mass * 0.5, + volume = math.min(volume * 0.5, 100), + expire = os.time() + 60 * 60, + }) > 0 then + for i = #ids, 1, -1 do + table.remove(ent.innerCont.items, ids[i]) + end + ent.innerCont:QueueSync() + hook.Run('dbg-trash.searchedEvidence', ent, ply, strs) + ply:Notify('Найденные улики упакованы в ZIP-пакет') + else ply:Notify('warning', 'В руках недостаточно места') end + end, + fail = function() + ent.evidenceSearch = nil + end + }, { + time = 1, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + ply:EmitSound('physics/cardboard/cardboard_box_shake'.. math.random(1, 3) ..'.wav', 50) + end, + }) + + end + end, + function(ply, ent) + if not ply:IsSuperAdmin() then return end + return 'Изменить модель', octolib.icons.color('cog'), function(ply, ent) + ply:SelectModel(ent.models, function(ply, mdl, skin, bgs) + if mdl and IsValid(ent) then + ent:SetModel(mdl.model) + ent:PhysicsInit(SOLID_VPHYSICS) + ent:SetMoveType(MOVETYPE_VPHYSICS) + ent:SetSolid(SOLID_VPHYSICS) + + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + ent:Activate() + end + end) + end + end, +} + +local propUse = octolib.table.mapSequential(contUse, function(func) + return function(ply, ent) + return func(ply, ent) + end +end) + +propUse[1] = function(ply, ent) + if not ent:GetInventory() and not ent.lootable then return end + return L.check_hint, 'octoteam/icons/search.png', function(ply, ent) + if ent.locked then + return ply:Notify('Предмет закрыт') + end + + + local lootData = ent.lootData + local animTimes = 0 + + local soundData + if lootData.period and (lootData.soundURL or lootData.soundFile) then + soundData = { + dist = 512, + url = lootData.soundURL, + file = lootData.soundFile, + level = 50, + ent = ply, + pos = ply:WorldToLocal(ply:GetShootPos()), + } + end + + ply:DelayedAction('loot', L.action_search, { + time = lootData.time or 0, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + if ent.inv then ply:OpenInventory(ent.inv) end + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if soundData and lootData.period and animTimes % lootData.period == 0 then + octolib.audio.play(soundData) + end + animTimes = animTimes + 1 + end, + }) + end +end +propUse[0] = function(ply, ent) + if not ent.isFadingDoor then return end + if not (ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO) then return end + return 'Выбить', 'octoteam/icons/door_breaching.png', function(ply, ent) + timer.Simple(0.5, function() + ent:fadeActivate() + ent:EmitSound('physics/wood/wood_plank_break1.wav', 60) + end) + end +end + +CFG.use.prop_static = propUse +CFG.use.prop_physics = propUse diff --git a/garrysmod/addons/_config/lua/config/octolib-use/sv_doors.lua b/garrysmod/addons/_config/lua/config/octolib-use/sv_doors.lua new file mode 100644 index 0000000..5260de4 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-use/sv_doors.lua @@ -0,0 +1,253 @@ +local function cpCheckDoor(ply, ent) + return ply:isCP() and not ent:IsBlocked() and not ent:CanBeOwned() and ent:GetPlayerOwner() ~= ply:SteamID() +end + +local nextCI = 1 -- Common Index +-- local forbiddenSkins = octolib.array.toKeys {4,5,6,7,8,9,12} -- door breaching + +local function toggleDoor(ent, ply, speedMul) + + ent.baseSpeed = ent.baseSpeed or tonumber(ent:GetInternalVariable('Speed')) + ent:SetKeyValue('Speed', ent.baseSpeed * speedMul) + ent:Use(ply, ply, USE_TOGGLE, 1) + + local pair = ent:GetPairedDoor() + if not ent.doorCI then + ent.doorCI = nextCI + if IsValid(pair) then pair.doorCI = nextCI end + nextCI = nextCI + 1 + end + + if ent:CanBeOwned() or ent:GetEstateID() == 0 then + timer.Create('dbg-estates.close' .. ent.doorCI, 10, 1, function() + if IsValid(ent) and (ent:CanBeOwned() or ent:GetEstateID() == 0) then + ent:Fire('Close') + if IsValid(pair) then pair:Fire('Close') end + end + end) + end + +end + +local doorUse = { + function(ply, ent) + local isOpen = ent:GetInternalVariable('m_angRotation') ~= ent:GetInternalVariable('m_angRotationClosed') + return isOpen and L.close or L.open, 'octoteam/icons/key.png', function(ply, ent) + ent.unlockSound = ent.unlockSound or ent:GetInternalVariable('soundunlockedoverride') + ent.lockSound = ent.lockSound or ent:GetInternalVariable('soundlockedoverride') + + if ent:HasSpawnFlags(4096) then + ent:Fire('addoutput', 'spawnflags ' .. bit.band(ent:GetSpawnFlags(), bit.bnot(4096))) -- remove "silent" flag + ent:SetKeyValue('soundunlockedoverride', ent.unlockSound) + ent:SetKeyValue('soundlockedoverride', ent.lockSound) + end + + toggleDoor(ent, ply, 1) + + end + end, + function(ply, ent) + local isOpen = ent:GetInternalVariable('m_angRotation') ~= ent:GetInternalVariable('m_angRotationClosed') + return (isOpen and L.close or L.open) .. ' тихо', 'octoteam/icons/leather_gloves.png', function(ply, ent) + ent.unlockSound = ent.unlockSound or ent:GetInternalVariable('soundunlockedoverride') + ent.lockSound = ent.lockSound or ent:GetInternalVariable('soundlockedoverride') + + if not ent:HasSpawnFlags(4096) then + ent:Fire('addoutput', 'spawnflags ' .. bit.bor(ent:GetSpawnFlags(), 4096)) -- add "silent" flag + ent:SetKeyValue('soundunlockedoverride', '') -- handle needs time to update "silent" flag + ent:SetKeyValue('soundlockedoverride', '') + end + + toggleDoor(ent, ply, 0.25) + + end + end, + + function(ply, ent) + if not ent:IsDoorLocked() then return end + if not (ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO) then return end + -- if not ent:GetModel():find('models/props_c17/door01') or forbiddenSkins[ent:GetSkin()] then return end + return 'Выбить дверь', 'octoteam/icons/door_breaching.png', function(ply, ent) + + if not ent:HasSpawnFlags(4096) then + ent:Fire('addoutput', 'spawnflags ' .. bit.bor(ent:GetSpawnFlags(), 4096)) -- add "silent" flag + ent:SetKeyValue('soundunlockedoverride', '') -- handle needs time to update "silent" flag + ent:SetKeyValue('soundlockedoverride', '') + end + + ent:DoUnlock() + + timer.Simple(0.5, function() + toggleDoor(ent, ply, 7.5) + ent:EmitSound('physics/wood/wood_plank_break1.wav', 60) + end) + end + end, + function(ply, ent) + if ent:IsDoorLocked() then + if ent:CanBeUnlockedBy(ply) and CurTime() > (ent.nextLock or 0) then + return L.door_unlock, 'octoteam/icons/lock_open.png', function(ply, ent) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ply:EmitSound('doors/door_latch1.wav') + ent:DoUnlock() + end + end + else + if ent:CanBeLockedBy(ply) and CurTime() > (ent.nextLock or 0) then + return L.door_lock, 'octoteam/icons/lock.png', function(ply, ent) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ply:DelayedAction('door_lock', 'Закрытие замка', { + time = 1.5, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + ply:EmitSound('doors/door_latch1.wav') + ent:DoLock() + end, + }) + end + end + end + end, + function(ply, ent) + if ent:IsDoorLocked() then + if ent:CanBeUnlockedBy(ply) and CurTime() > (ent.nextLock or 0) then + return 'Тихо открыть', 'octoteam/icons/lock_password.png', function(ply, ent) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ply:DelayedAction('door_lock', 'Открытие замка', { + time = 5, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + ent:DoUnlock() + end, + }) + end + end + else + if ent:CanBeLockedBy(ply) and CurTime() > (ent.nextLock or 0) then + return 'Тихо закрыть', 'octoteam/icons/lock_password.png', function(ply, ent) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ply:DelayedAction('door_lock', 'Закрытие замка', { + time = 5, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + ent:DoLock() + end, + }) + end + end + end + end, + function(ply, ent) + if not ent:IsDoorLocked() or ent:CanBeUnlockedBy(ply) then return end + if ply:CheckCrimeDenied() then return end + if not ply.inv or not ply.inv.conts._hand or ply.inv.conts._hand:HasItem('lockpick') <= 0 then return end + return L.lockpick_action, 'octoteam/icons/lockpick.png', function(ply, ent) + ply:Lockpick(ent, { + timeOverride = ply:HasBuff('Dextradose') and 0.825 or nil, + succ = function(ply, ent) + ent:DoUnlock() + ent:EmitSound('doors/door_latch1.wav') + ent.nextLock = CurTime() + 30 + end, + check = function(ply, ent) + return ply.inv.conts._hand:HasItem('lockpick') > 0 + end, + }) + end + end, + function(ply, ent) + if not peepholes.canAccess(ply, ent) or ply.peephole then return end + return 'Посмотреть в глазок', 'octoteam/icons/bubble.png', function(ply, ent) + if peepholes.canAccess(ply, ent) and not ply.peephole then peepholes.view(ply, ent) end + end + end, + function(ply, ent) + if ent:GetPlayerOwner() ~= ply:SteamID() then return end + return 'Поставить табличку', 'octoteam/icons/tag.png', function() + octolib.request.send(ply, {{ + type = 'strShort', + name = 'Текст', + desc = 'Укажи текст таблички (до 32 символов). Оставь пустым, чтобы убрать табличку', + default = ent:GetTitle(), + }}, function(data) + if not istable(data) or (data[1] ~= nil and not isstring(data[1])) then return end + local txt + if isstring(data[1]) then + txt = string.Trim(data[1]) + txt = utf8.sub(txt, 1, 32) + if txt == '' then txt = nil end + end + if ent:GetPlayerOwner() == ply:SteamID() then + ent:SetNetVar('tempTitle', txt) + end + end) + end + end, + function(ply, ent) + if not cpCheckDoor(ply, ent) then return end + return L.check_hint, 'octoteam/icons/card2.png', function (ply, ent) + ply:DoEmote('{name} проверяет недвижимость по базе данных') + ply:DelayedAction('est_check', L.check_action, { + time = 10, + check = function() return octolib.use.check(ply, ent) and cpCheckDoor(ply, ent) end, + succ = function() + netstream.Start(ply, 'dbg-doors.identify', ent:GetEstateID()) + ply:Notify('Информация о недвижимости обновлена') + end, + }) + end + end, + function(ply, ent) + if not ent:CanBeOwned() then return end + local job = RPExtraTeams[ply:Team()] + if job and job.hobo then return end + return L.buy, 'octoteam/icons/money_pack.png', function() + if not ent:CanBeOwned() then return end + if not ply:BankHas(ent:GetPrice()) then + return ply:Notify('warning', 'Маловато деньжат на банковском счете, дружище') + end + + local function buy(ply, ent) + if not (IsValid(ply) and IsValid(ent)) then return end + if not ent:CanBeOwned() then return end + local price = ent:GetPrice() + if not ply:BankHas(price) then + return ply:Notify('warning', 'Маловато деньжат на банковском счете, дружище') + end + ent:SetPlayerOwner(ply) + ply:BankAdd(-price) + ply:Notify(L.door_bought:format(DarkRP.formatMoney(price))) + end + + local est = ent:GetEstate() + if est and est.purpose then + local forBusiness = (est.purpose == 1) + octolib.request.send(ply, {{ + name = 'Помещение для ' .. (forBusiness and 'бизнеса' or 'проживания'), + desc = (forBusiness and L.estate_business_only or L.estate_residency_only) .. '\n\nИгнорирование этого предупреждения может привести к выселению или даже выдаче наказания', + type = 'check', + txt = 'Я буду использовать это помещение только для ' .. (forBusiness and 'бизнеса' or 'проживания'), + required = true, + }}, function(data) + if data and data[1] then buy(ply, ent) end + end) + else buy(ply, ent) end + + end + end, + function(ply, ent) + if netvars.GetNetVar('pendingTax') or ent:GetPlayerOwner() ~= ply:SteamID() then return end + return L.sell, 'octoteam/icons/money_pack.png', function() + if netvars.GetNetVar('pendingTax') or ent:GetPlayerOwner() ~= ply:SteamID() then return end + local prSell = math.floor(ent:GetPrice() * 0.666 + 0.5) + ent:RemoveOwner(ply:SteamID()) + ply:addMoney(prSell) + ply:Notify(L.door_sold:format(DarkRP.formatMoney(prSell))) + end + end, +} + +CFG.use.func_door = doorUse +CFG.use.func_door_rotating = doorUse +CFG.use.prop_door_rotating = doorUse +CFG.use.func_movelinear = doorUse diff --git a/garrysmod/addons/_config/lua/config/octolib-use/sv_hooks.lua b/garrysmod/addons/_config/lua/config/octolib-use/sv_hooks.lua new file mode 100644 index 0000000..0882db8 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-use/sv_hooks.lua @@ -0,0 +1,5 @@ +hook.Add('octolib.canUse', 'dobrograd', function(ply) + if not ply:Alive() or ply:IsGhost() or ply:IsHandcuffed() then + return false + end +end) diff --git a/garrysmod/addons/_config/lua/config/octolib-use/sv_other.lua b/garrysmod/addons/_config/lua/config/octolib-use/sv_other.lua new file mode 100644 index 0000000..8c95742 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib-use/sv_other.lua @@ -0,0 +1,645 @@ +CFG.use.player = { + function(ply, ent) + if ply:IsGhost() then return end + if ply:getJobTable().notHuman then return end + if CurTime() < (ply.nextPush or 0) then return end + return L.push, 'octoteam/icons/explosion.png', function(ply, ent) + timer.Simple(0.5, function() + if not IsValid(ply) or ply:GetEyeTrace().Entity ~= ent then return end + local dir = ply:GetAimVector() + dir:Normalize() + dir.z = 0.3 + ent:SetVelocity(dir * 500) + + ent:ViewPunch(Angle(math.random(-5, 5), math.random(-5, 5), 0)) + ent:EmitSound('physics/body/body_medium_impact_soft'..math.random(1,7)..'.wav', 45) + end) + + ply:DoAnimation(ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND) + ply.nextPush = CurTime() + 2 + end + end, + function(ply, ent) + if ply:IsGhost() then return end + if ply:getJobTable().notHuman then return end + if not ply.inv:GetContainer('_hand') then return end + return L.show_hand, 'octoteam/icons/hand.png', function(ply, ent) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ent:OpenInventory(ply.inv, {'_hand'}) + end + end, + function(ply, ent) + if ply:Team() ~= TEAM_K9 then return end + if ent:Team() == TEAM_K9 then return end + if not ent.inv then return end + return 'Обнюхать', 'octoteam/icons/search.png', function(ply, ent) + ply:DelayedAction('smell', 'Обнюхивание', { + time = 1, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + if not (IsValid(ply) and IsValid(ent) and ent.inv) then return end + local found = false + if ent:FindItem({ class = 'weapon' }) or ent:FindItem({ class = 'ammo' }) or ent:FindItem({ class = {_starts = 'drug_'} }) then + ply:Notify('warning', 'Ты что-то унюхал!') + else ply:Notify('hint', 'Кажется, этот человек чист') end + end, + }) + end + end, + function(ply, ent) + if ply:IsGhost() then return end + local job = ply:getJobTable() + if job.notHuman then return end + if not ent.bleeding and (not ply:isCP() and not job.canSearch and ent:GetNetVar('ScareState', 0) < 0.6) then return end + if ent:IsAFK() and ply:Team() ~= TEAM_ADMIN then return end + if ent:getJobTable().notHuman then return end + return L.inventory_search, 'octoteam/icons/search.png', function(ply, ent) + local time = 15 + ply:DelayedAction('searchPly', L.action_search, { + time = ply:Team() == TEAM_ADMIN and 0.5 or ply:isCP() and time / 3 or time, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + if IsValid(ply) and IsValid(ent) then + ply:OpenInventory(ent:GetInventory()) + if not ply.sg_invisible then + ent:EmitSound('npc/combine_soldier/gear6.wav') + end + hook.Run('octoinv.search', ply, ent) + end + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if IsValid(ent) then + ent:EmitSound('npc/combine_soldier/gear'.. math.random(1, 2) ..'.wav', 45) + end + end, + }) + end + end, + function(ply, ent) + if ply:IsGhost() then return end + if ply:getJobTable().notHuman then return end + if not ent.bleeding then return end + return 'Помочь встать', 'octoteam/icons/arrow_up2.png', function(ply, ent) + local time, period = (11 - ent:Health()) * 2, 2 + if ply:Team() ~= TEAM_MEDCOP and ply:Team() ~= TEAM_MEDIC then + time = time * 2 + period = 4 + end + if time <= 0 then return end + ply:DelayedAction('helpNearlyDead', 'Помощь', { + time = time, + check = function() return octolib.use.check(ply, ent) and ent.bleeding end, + succ = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ent:SetHealth(11) + end, + }, { + time = period, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + if IsValid(ent) and ent.bleeding then + ent:SetHealth(ent:Health() + 1) + end + end, + }) + end + end, + function(ply, ent) + if ply:getJobTable().notHuman then return end + local cuffed, wep = ent:IsHandcuffed() + if not cuffed or not wep.CanGag then return end + local gagged = wep:GetNetVar('gag', false) + return gagged and L.take_out_the_gag or L.insert_gag, 'octoteam/icons/silence.png', function(ply, ent) + if IsValid(wep) then + wep:Gag(not gagged) + end + end + end, + function(ply, ent) + if ply:getJobTable().notHuman then return end + local cuffed, wep = ent:IsHandcuffed() + if not cuffed or not wep.CanBlind then return end + local blind = wep:GetNetVar('blind', false) + return blind and L.untie_eyes or L.tie_eyes, 'octoteam/icons/blind.png', function(ply, ent) + if IsValid(wep) then + wep:Blind(not blind) + end + end + end, + function(ply, ent) + if ply:getJobTable().notHuman then return end + if ply:IsGhost() or not ply:isCP() or ent:GetNetVar('Arrested') or (ent:isCP() and ply:Team() ~= TEAM_FBI) then return end + return L.arrest_stick, 'octoteam/icons/handcuffs.png', function(ply, ent) + local canArrest, why = hook.Run('canArrest', ply, ent) + if canArrest == false then + if why then ply:Notify('warning', why) end + return + end + + ent:Freeze(true) + octolib.request.send(ply, {{ + required = true, + type = 'strShort', + name = 'Причина ареста', + ph = 'Укажи причину здесь', + }, { + required = true, + type = 'comboBox', + name = L.reason_arrest_level[1], + opts = { + { L.reason_arrest_level[2], 1, true }, + { L.reason_arrest_level[3], 2 / 3 }, + { L.reason_arrest_level[4], 1 / 3 }, + }, + }}, function(data) + if not IsValid(ent) then + ply:Notify('warning', L.player_left2) + return + end + + local reason, time = data[1], math.Clamp(data[2] or 1, 0, 3) + if not reason or string.Trim(reason) == '' then + ply:Notify('warning', L.wrong_reason_arrest) + else + ent:arrest(time, ply, reason) + ent:Notify('warning', L.youre_arrested_by:format(ply:Nick())) + end + + ent:Freeze(false) + end, function() + ent:Freeze(false) + end) + end + end, + function(ply, ent) + if ply:getJobTable().notHuman then return end + if ply:IsGhost() or ply:GetNetVar('Arrested') or not ent:GetNetVar('Arrested') then return end + local job = ply:getJobTable() + if not job.canUnarrest then return end + return L.unarrest_action, 'octoteam/icons/handcuffs.png', function(ply, ent) + local canUnarrest, why = hook.Run('canUnarrest', ply, ent) + if canUnarrest == false then + if why then ply:Notify('warning', why) end + return + end + + ent:unArrest(ply) + ent:Notify(L.youre_unarrested_by:format(ply:Nick())) + end + end, + function(ply, ent) + if ent:IsGhost() or ply:Team() ~= TEAM_PRISON then return end + for i = 0, #ent:GetMaterials() - 1 do + if ent:GetSubMaterial(i) == 'models/humans/modern/octo/prisoner1_sheet' then + return 'Снять тюремную робу', 'octoteam/icons/prisoner_clothes.png', function(ply, ent) + ent:SetPrisonClothes(false) + end + end + end + return 'Выдать тюремную робу', 'octoteam/icons/prisoner_clothes.png', function(ply, ent) + ent:SetPrisonClothes(true) + end + end, + function(ply, ent) + local job = ply:getJobTable() + if not ent:IsHandcuffed() or ply:IsGhost() or ply:IsHandcuffed() then return end + if job.notHuman then return end + local label, icon + if not ent.policeCuffs or ply:isCP() or job.canSearch then + label, icon = L.uncuff, 'octoteam/icons/rope.png' + else + label, icon = 'Взломать', 'octoteam/icons/lockpick.png' + end + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() ~= 'dbg_hands' then return end + + return label, icon, function(ply, ent) + local cuffed, cuffs = ent:IsHandcuffed() + if not (cuffed and IsValid(cuffs)) then return end + + if ent.policeCuffs and not ply:isCP() and not job.canSearch and ply:Team() ~= TEAM_ADMIN then + ply:Lockpick(ent, { + numOverride = 4, + timeOverride = ply:HasBuff('Dextradose') and 0.825 or nil, + succ = function(ply, ent) + local cuffed, cuffs = ent:IsHandcuffed() + if not (cuffed and IsValid(cuffs)) then return end + cuffs:Breakout() + ent:EmitSound('doors/door_latch1.wav') + end, + check = function(ply, ent) + return ply.inv.conts._hand:HasItem('lockpick') > 0 + end, + }) + else + ply:DelayedAction('fbreak', L.handcuff_breaking, { + time = cuffs.RemoveTime or 3, + check = function() return octolib.use.check(ply, ent) and not ply:IsHandcuffed() and IsValid(cuffs) end, + succ = function() + cuffs:Breakout() + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end + end, + function(ply, ent) + + if ply:getJobTable().notHuman then return end + local cert = ply:GetDBVar('cert') + if not cert then return end + + return 'Показать удостоверение', 'octoteam/icons/id.png', function(ply, ent) + if ply:GetPos():DistToSqr(ent:GetPos()) > CFG.useDistSqr then return end + local showed, whyNot = dbgCerts.show(ply, ent) + if not showed then ply:Notify('warning', whyNot) end + end + + end, +} + +CFG.use.prop_ragdoll = { + function(ply, ent) + if ply:getJobTable().notHuman then return end + if ply:IsGhost() or ply:IsHandcuffed() or not ent.inv then return end + return L.inventory_search, 'octoteam/icons/search.png', function(ply, ent) + if ent:GetRagdollOwner() == ply and ply:Team() ~= TEAM_ADMIN then + ply:Notify('warning', L.loot_yourself) + return + end + + ent.criminals = ent.criminals or {} + local name = ply:Name() + if ply:isCP() then name = name .. L.loot_police end + if not table.HasValue(ent.criminals, name) then table.insert(ent.criminals, name) end + + ply:DelayedAction('lootCorpse', L.loot_corpse, { + time = 5, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + if ent.inv then + ent.looters = ent.looters or {} + if CurTime() - ent:GetNetVar('DeathTime', 0) < 120 and not ent.looters[ply:SteamID()] then + ply:AddKarma(-2, L.karma_search_corpse) + ent.looters[ply:SteamID()] = true + end + hook.Run('octoinv.search-corpse', ply, ent) + ply:OpenInventory(ent.inv) + end + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, + function(ply, ent) + local job = ply:getJobTable() + if job.notHuman then return end + if ent.tazeplayer or ply:IsGhost() or ply:IsHandcuffed() or not (job.police or job.canPackCorpse) then return end + return L.pack_corpse2, 'octoteam/icons/scotch.png', function(ply, ent) + if ply:isCP() and ply:Team() ~= TEAM_DPD and team.NumPlayers(TEAM_CORONER) >= 1 then + return ply:Notify('warning', 'На сервере сейчас имеются коронёры, обратись за их помощью по рации') + end + ply:DelayedAction('packCorpse', L.pack_corpse, { + time = 5, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + if not IsValid(ent) then return end + local pos, ang = ent:GetBonePosition(0) + pos:Add(ang:Right() * 40) + ang:RotateAroundAxis(ang:Up(), -90) + ang:RotateAroundAxis(ang:Right(), 90) + ent:Remove() + local prop, variant = ents.Create 'prop_ragdoll', math.random(2, 3) + prop:SetModel('models/hospitals/bodybag' .. variant .. '.mdl') + prop:SetPos(pos) + prop:SetAngles(ang) + prop:Spawn() + prop:Activate() + + ent.APG_Ghosted = false + ent:DrawShadow(true) + ent:SetColor(Color(255,255,255,255)) + ent:SetCollisionGroup(COLLISION_GROUP_NONE) + + local ph = prop:GetPhysicsObject() + if IsValid(ph) then + ph:SetMass(300) + ph:Wake() + end + + ply:Notify(L.corpse_packed) + timer.Simple(60, function() + if not IsValid(prop) then return end + prop:Remove() + end) + end, + }, { + time = 1.5, + inst = true, + action = function() + ent:EmitSound('physics/rubber/rubber_tire_strain'..math.random(1,3)..'.wav', 65) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, + function(ply, ent) + local job = ply:getJobTable() + if job.notHuman then return end + if ent.tazeplayer or ply:IsGhost() or ply:IsHandcuffed() or not (job.police or job.canZip) then return end + if not ent:GetNetVar('Corpse.name') then return end + return L.take_body_mat, 'octoteam/icons/urine.png', function(ply, ent) + + if ply:isCP() and ply:Team() ~= TEAM_DPD and team.NumPlayers(TEAM_CORONER) >= 1 then + return ply:Notify('warning', 'На сервере сейчас имеются коронёры, обратись за их помощью по рации') + end + + if not ply.inv or not ply.inv.conts._hand then + ply:Notify('warning', L.hands_free) + return + end + + ply:DelayedAction('studyCorpse', L.study_corpse, { + time = 5, + check = function() return octolib.use.check(ply, ent) and ply.inv and ply.inv.conts._hand end, + succ = function() + + if not ent.studied then + ply.inv.conts._hand:AddItem('body_mat', { + collector = ply:Nick(), + corpse = ent:GetNetVar('Corpse.name'), + criminals = ent.criminals or {}, + expire = os.time() + 60 * 60, + }) + ent.studied = true + else ply:Notify('warning', L.body_mat_already_take) end + + if not ent.inv or not (job.police or job.canZip) then return end + + local strs, mass, volume = {}, 0, 0 + for _,cont in pairs(ent.inv.conts) do + for _,item in pairs(cont:GetItems()) do + strs[#strs + 1] = octoinv.itemStr({item:GetData('class'), item}) + local amount = item:GetData('amount') + mass = mass + item:GetData('mass') * amount + volume = volume + item:GetData('volume') * amount + end + end + + if #strs == 0 then + ply:Notify('warning', 'Улик не обнаружено') + return + end + + local storedStr = table.concat(strs, ', ') + + if ply.inv.conts._hand:AddItem('zip', { + collector = ply:Nick() .. '\n\n', + storedStr = storedStr, + amount = 1, + mass = mass * 0.5, + volume = volume * 0.5, + expire = os.time() + 60 * 60, + }) > 0 then + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ent.inv:Clear() + else ply:Notify('warning', 'В руках недостаточно места') end + hook.Run('octoinv.collect-evidences', ply, ent, storedStr) + + end, + }, { + time = 1.5, + inst = true, + action = function() + ent:EmitSound('physics/rubber/rubber_tire_strain'..math.random(1,3)..'.wav', 65) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, +} + +local function packZip(ply, ent) + if ply:getJobTable().notHuman then return end + if not (ply:isCP() or ply:getJobTable().canZip) then return end + local data = ent:GetNetVar('Item') + if not data then return end + if data[1] == 'zip' and not ent.isEvidence then return end + return 'Положить в ZIP-пакет', 'octoteam/icons/zip_file.png', function(ply, ent) + + if not ply.inv or not ply.inv.conts._hand then + ply:Notify('warning', L.hands_free) + return + end + + local storedStr = octoinv.itemStr(data) + + local amount = octoinv.getItemData('amount', data[1], data[2]) + local mass = amount * octoinv.getItemData('mass', data[1], data[2]) + local volume = amount * octoinv.getItemData('volume', data[1], data[2]) + + if ply.inv.conts._hand:AddItem('zip', { + collector = ply:Nick() .. '\n\n', + storedStr = storedStr, + amount = 1, + mass = mass * 0.5, + volume = volume * 0.5, + expire = os.time() + 60 * 60, + }) > 0 then + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + hook.Run('octoinv.collect-evidences', ply, ent, storedStr) + ent:Remove() + else + ply:Notify('warning', 'У тебя недостаточно места') + end + end +end + +CFG.use.ent_dbg_radio = { + function(ply, ent) + if ent.duplicated then return end + if ply:getJobTable().notHuman then return end + return L.use, 'octoteam/icons/hand_point.png', function(ply, ent, args) + netstream.Start(ply, 'dbg-radio.control', ent, ent.whitelisted, ent.curID, ent.curTitle, ent.curPlace, ent.curCountry) + end + end, + function(ply, ent) + if ent.duplicated then return end + if ply:getJobTable().notHuman then return end + if ent.owner ~= ply and not ply:IsAdmin() then return end + local to = not ent.pinned + return to and L.freeze or L.unfreeze, 'octoteam/icons/screw.png', function(ply, ent) + if to then + DarkRP.freeze(ply, ent, function(ok, msg) + if not ok then + return ply:Notify('warning', msg) + end + ent.pinned = to + end) + else + DarkRP.unfreeze(ply, ent) + ent.pinned = to + end + end + end, + packZip, +} + +CFG.use.ent_dbg_phone = { + function(ply, ent) + if ent.off then return end + if ply:getJobTable().notHuman then return end + return L.use, 'octoteam/icons/hand_point.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + if not ent.owned then return end + if ply:getJobTable().notHuman then return end + local to = not ent.off + return ent.off and L.enable or L.disable, 'octoteam/icons/button_power.png', function(ply, ent) + if not to then + DarkRP.freeze(ply, ent, function(ok, msg) + if not ok then + return ply:Notify('warning', msg) + end + ent.off = to + ent:EmitSound('buttons/button24.wav') + end) + else + if ent.off ~= to then + ent:EmitSound('buttons/button24.wav') + end + DarkRP.unfreeze(ply, ent) + ent.off = to + end + end + end, +} + +local colors = { + [0] = Color(255, 100, 100), + [1] = Color(100, 100, 255), + [2] = Color(255, 255, 100), +} + +CFG.use.ent_dbg_xmas_ornament = { + function(ply, ent) + if ent.star or ply:SteamID64() ~= ent.ownerSID64 then return end + return 'Перекрасить', 'octoteam/icons/xmas_ball.png', function(ply, ent) + octolib.request.send(ply, {{ + name = 'Новый цвет шарика', + },{ + type = 'comboBox', + opts = { + {'Красный', 0, ent:GetSkin() == 0}, + {'Синий', 1, ent:GetSkin() == 1}, + {'Желтый', 2, ent:GetSkin() == 2}, + }, + }}, function(data) + ent:SetSkin(data[2]) + ent:EmitSound('garrysmod/save_load'..math.random(4)..'.wav', 50) + netstream.StartPVS(ent:GetPos(), 'ornament.sparkles', ent:GetPos() + Vector(0,0,5), colors[data[2]]) + end) + end + end, +} + +CFG.use.octoinv_item = { + function(ply, ent) + if ply:getJobTable().notHuman then return end + return L.pickup, 'octoteam/icons/arrow_up2.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + packZip, +} + +CFG.use.dbg_police_analyser = { + function(ply, ent) + if ply:getJobTable().notHuman then return end + return L.check_hint, 'octoteam/icons/search.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + if ent.utilizerBusy then return end + if ply:getJobTable().notHuman then return end + return 'Запустить утилизатор', 'octoteam/icons/explosion.png', function(ply, ent) + ent:NextItem() + end + end, +} + +CFG.use.dbg_jobs_package = { + function(ply, ent) + + if ent.ply ~= ply then return end + if IsValid(ent.car) then return end + + if IsValid(ent.weld) then + return L.item_unfreezed_car, 'octoteam/icons/screw.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end + + local bottom = ent:NearestPoint(ent:GetPos() - Vector(0, 0, 300)) + local car = util.QuickTrace(bottom, -Vector(0,0,4), ent).Entity + + if not IsValid(car) or not car.IsSimfphyscar then + return + end + if not list.Get('simfphys_vehicles')[car.VehicleName].Members.CanAttachPackages then + return + end + + return L.freeze_on_car, 'octoteam/icons/screw.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + + if ent.ply ~= ply then return end + if IsValid(ent.car) then return end + if IsValid(ent.weld) then return end + + local bottom = ent:NearestPoint(ent:GetPos() - Vector(0, 0, 300)) + local car = util.QuickTrace(bottom, -Vector(0,0,4), ent).Entity + + if not IsValid(car) or not car.IsSimfphyscar then + return + end + if not car.trunkOpen then return end + if IsValid(car.package) then return end + if not car.inv then return false end + local cont = car.inv:GetContainer('trunk') + if not cont then return end + + local volume = ent:GetVolumeLiters() + return 'Положить в багажник', octolib.icons.color('car_trunk'), function(ply, ent) + if cont:FreeSpace() < volume then + return ply:Notify('warning', 'В багажнике не хватает ' .. (volume-cont:FreeSpace()) .. 'л места!') + end + if not IsValid(car.package) then + ent:AttachToCar(car, cont) + ply:Notify('Груз помещен в багажник') + end + end + + end, +} \ No newline at end of file diff --git a/garrysmod/addons/_config/lua/config/octolib_sh.lua b/garrysmod/addons/_config/lua/config/octolib_sh.lua new file mode 100644 index 0000000..724f247 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib_sh.lua @@ -0,0 +1,83 @@ +--------------------------------------------------------------------- +-- GENERAL +--------------------------------------------------------------------- +if CLIENT then + netstream.Hook('octolib.cfg', function(cfg) + table.Merge(CFG, cfg) + hook.Run('octolib.configLoaded', CFG) + end) +end + +CFG.serverLang = 'ru' +CFG.serverGroupID = 'dbg_dev' +CFG.serverID = 'dbg_dev' +CFG.serverGroupIDvars = 'dbg' + +CFG.disabledModules = { + -- moduleName = true, +} + +CFG.useDist = 84 +CFG.useDistSqr = CFG.useDist * CFG.useDist +CFG.radioChatDistance = 250 + +--------------------------------------------------------------------- +-- AFK +--------------------------------------------------------------------- +CFG.afkTime = 60 * 3 +CFG.afkKickTime = 60 * 30 +CFG.afkAdminNotActive = 60 * 15 +CFG.drawOverlay = true + +--------------------------------------------------------------------- +-- PLAYER +--------------------------------------------------------------------- +CFG.playerTickTime = 1 +hook.Add('octolib.eyeTraceFilter', 'dobrograd', function(ply, filter) + filter[#filter + 1] = ply:GetNetVar('dragging') + + local job = ply:getJobTable() + if job and not job.seesGhosts then + for _, ghost in ipairs(player.GetGhosts()) do + filter[#filter + 1] = ghost + end + end +end) + +hook.Add('dbg-score.hidePlayer', 'launcher', function(ply) + if not ply:GetNetVar('launcherActivated') then + return true + end +end) + +CFG.reflectionTint = (game.GetMap():find('evocity') or game.GetMap():find('riverden')) and Color(255,255,255) or Color(0,0,0) + +if CLIENT then + --------------------------------------------------------------------- + -- SKIN + --------------------------------------------------------------------- + local cols = { + b = Color(65,132,209, 255), + y = Color(240,202,77, 255), + r = Color(222,91,73, 255), + g = Color(102,170,170, 255), + o = Color(170,119,102, 255), + + bg = Color(85,68,85, 255), + } + + cols.bg95 = Color(cols.bg.r, cols.bg.g, cols.bg.b, 241) + cols.bg60 = Color(cols.bg.r, cols.bg.g, cols.bg.b, 150) + cols.bg50 = Color(cols.bg.r / 2, cols.bg.g / 2, cols.bg.b / 2, 255) + + cols.bg_d = Color(70,54,70, 255) + cols.bg_l = Color(cols.bg.r * 1.25, cols.bg.g * 1.25, cols.bg.b * 1.25, 255) + cols.bg_grey = Color(180,180,180, 255) + cols.g_d = Color(cols.g.r * 0.75, cols.g.g * 0.75, cols.g.b * 0.75, 255) + cols.r_d = Color(cols.r.r * 0.75, cols.r.g * 0.75, cols.r.b * 0.75, 255) + + cols.hvr = Color(0,0,0, 50) + cols.dsb = Color(255,255,255, 50) + + CFG.skinColors = cols +end diff --git a/garrysmod/addons/_config/lua/config/octolib_sv.lua b/garrysmod/addons/_config/lua/config/octolib_sv.lua new file mode 100644 index 0000000..5c3b70a --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octolib_sv.lua @@ -0,0 +1,144 @@ +--------------------------------------------------------------------- +-- DATABASE +--------------------------------------------------------------------- + +CFG.db = { + host = 'localhost', + user = 'root', + pass = '', + port = '3306', + + main = 'gmod_dbg_dev', + admin = 'gmod_dbg_dev', + shop = 'gmod_dbg_dev', +} + +--------------------------------------------------------------------- +-- USE +--------------------------------------------------------------------- +CFG.use = CFG.use or {} +hook.Add('octolib.use-loadConfig', 'octolib', function() + local fs, _ = file.Find('config/octolib-use/*.lua', 'LUA') + for _, f in pairs(fs) do + fname = 'config/octolib-use/' .. string.StripExtension(f) + octolib.server(fname) + end +end) + +--------------------------------------------------------------------- +-- BACKUP +--------------------------------------------------------------------- +CFG.backupPeriod = 300 +CFG.backupCleanupAfter = 600 +CFG.backupPruneTime = 3600 + +--------------------------------------------------------------------- +-- DATABASE +--------------------------------------------------------------------- +CFG.dbTick = true +CFG.dbTickTime = 1 + +--------------------------------------------------------------------- +-- PLAYERS +--------------------------------------------------------------------- +octolib.admins = octolib.admins or {} +hook.Add('PlayerFinishedLoading', 'octolib.adminslots', function() + + hook.Remove('PlayerFinishedLoading', 'octolib.adminslots') + local queryObj = serverguard.mysql:Select('serverguard_users') + queryObj:WhereNotEqual('rank', 'user') + queryObj:Callback(function(result) + if (type(result) == 'table' and #result > 0) then + serverguard.ranksCache = result + for _, v in pairs(serverguard.ranksCache) do + if CFG.adminRanks[v.rank] then + octolib.admins[v.steam_id] = true + end + end + end + end) + queryObj:Execute() + +end) + +CFG.adminSlots = 4 +CFG.adminRanks = { + trainee = true, + tadmin = true, + admin = true, + sadmin = true, + + rpmanager = true, + projectmanager = true, + contentmanager = true, + dev = true, + + superadmin = true, + founder = true, +} + +function CFG.isAdminSteamID(id) + + return octolib.admins[id] + +end + +-- +-- REWARDS +-- + +local forumReward = 20000 +CFG.forumRewardHandler = function(ply, forumData, finish) + local id = ply:SteamID() + octolib.func.chain({ + function(done) + BraxBank.PlayerMoneyAsync(id, done) + end, + function(done, balance) + if not IsValid(ply) then return end + + finish() + BraxBank.UpdateMoney(id, balance + forumReward) + octolib.notify.sendAll('hint', L.bonus_get:format(ply:Name(), forumData.name, DarkRP.formatMoney(forumReward))) + + timer.Simple(5, done) + end, + function() + if IsValid(ply) then + ply:Notify('hint', L.bonus_in_atm) + end + end, + }) +end + +-- local launcherReward = 30000 +-- hook.Add('octolib.family.created', 'dbg-rewards', function(family) +-- local ply = player.GetBySteamID64(family.steamids[1]) +-- if not IsValid(ply) then return end + +-- ply:BankAdd(launcherReward) +-- ply:Notify('Спасибо за установку лаунчера! Награда начислена на твой банковский счет') +-- octolib.notify.sendAll(nil, ('%s получил %s за установку лаунчера! Его можно скачать здесь: '):format( +-- ply:SteamName(), DarkRP.formatMoney(launcherReward) +-- ), {'https://octo.gg/launcher'}) +-- end) + +-- load .env file +local ok, env = pcall(util.JSONToTable, file.Read('.env.json', 'BASE_PATH')) +if ok then + table.Merge(CFG, env) + hook.Run('octolib.configLoaded', CFG) + + -- TODO: + local toSend = {'serverLang','octoservicesURL','serverGroupID','serverID','modules','dev'} + hook.Add('PlayerInitialSpawn', 'octolib.sendDevStatus', function(ply) + local cfg = {} + for _, k in ipairs(toSend) do cfg[k] = CFG[k] end + netstream.Start(ply, 'octolib.cfg', cfg) + end) +else + octolib.msg('No valid .env.json file found.') + return +end + +octolib.server('map') diff --git a/garrysmod/addons/_config/lua/config/octomap.lua b/garrysmod/addons/_config/lua/config/octomap.lua new file mode 100644 index 0000000..519b89b --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octomap.lua @@ -0,0 +1,80 @@ +octomap.config = octomap.config or {} + +local config = octomap.config +local map = game.GetMap() +if CLIENT then + if map:find('evocity') then + config.url = '1VhSaNj.png' + config.addX = 80 + config.addY = -22 + config.relX = 0.072 + config.relY = -0.072 + config.mapW = 2560 + config.mapH = 2560 + config.scaleMin = 0.2097152 + config.scaleMax = 1 + config.bgCol = Color(198, 234, 146) + elseif map:find('eastcoast') then + config.url = '04XoMDn.png' + config.addX = -145 + config.addY = -64 + config.relX = 0.236 + config.relY = -0.236 + config.mapW = 2560 + config.mapH = 2560 + config.scaleMin = 0.2097152 + config.scaleMax = 1 + config.bgCol = Color(241, 240, 238) + elseif map:find('truenorth') then + config.mapW = 2048 + config.mapH = 2048 + config.scaleMin = 0.2097152 + config.scaleMax = 1 + config.bgCol = Color(241, 240, 238) + config.mapLayer = 1 + + local vals = { + url = { 'XaxoM2H.png', 'f49geRN.png' }, + addX = { 7, 2 }, + addY = { -20, -22 }, + relX = { 0.0629, 0.063 }, + relY = { -0.0629, -0.063 }, + } + + local function updateVals() + for k, v in pairs(vals) do + config[k] = v[config.mapLayer] + end + end + updateVals() + + function config.getMapLayer(z) + return z > 2550 and 2 or 1 + end + + function config.updateMap() + local ply = LocalPlayer() + local newLayer = IsValid(ply) and config.getMapLayer(ply:GetPos().z) or 1 + + if newLayer ~= config.mapLayer then + config.mapLayer = newLayer + updateVals() + octomap.reloadMainMaterial() + end + end + elseif map:find('riverden') then + config.url = 'iGJkYzt.png' + config.addX = -5 + config.addY = -5 + config.relX = 0.0625 + config.relY = -0.063 + config.mapW = 2560 + config.mapH = 2560 + config.scaleMin = 0.2097152 + config.scaleMax = 1 + config.bgCol = Color(176, 213, 186) + end +end + +octolib.client('config/octomap/f4') +octolib.client('config/octomap/markers') diff --git a/garrysmod/addons/_config/lua/config/octomap/f4.lua b/garrysmod/addons/_config/lua/config/octomap/f4.lua new file mode 100644 index 0000000..70d4ea9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octomap/f4.lua @@ -0,0 +1,169 @@ +local colBG = Color(0,0,0, 200) +local function paintToolbar(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, colBG) +end + +hook.Add('octogui.f4-tabs', 'octomap', function() + + octogui.addToF4({ + order = 11.5, + id = 'map', + name = 'Карта', + icon = Material('octoteam/icons/map.png'), + build = function(f) + f:SetSize(800, 600) + f:SetSizable(true) + f:DockPadding(0, 24, 0, 4) + f:SetMinWidth(400) + f:SetMinHeight(300) + + local map = f:Add 'octomap' + map:SetOptions({ paddingR = 200 }) + + local sb = map:Add 'DPanel' + map.sidebar = sb + sb:Dock(RIGHT) + sb:SetWide(190) + sb:DockMargin(5, 5, 5, 5) + + local lv = sb:Add 'DListView' + lv:Dock(FILL) + lv:DockMargin(3, 4, 3, 4) + lv:AddColumn(''):SetFixedWidth(24) -- icon + lv:AddColumn('Название') + lv:SetHideHeaders(true) + lv:SetDataHeight(24) + lv:SetMultiSelect(false) + function lv:OnRowSelected(i, line) + local marker = octomap.getMarker(line.markerID) + if not marker or not IsValid(map) then return end + + local sbID = marker.sidebarData.id + if sbID then + local l = map.sidebarIDs[sbID] + marker = l[1] + -- move marker to end of list to enable "scrolling through" + if marker then + table.remove(l, 1) + l[#l + 1] = marker + end + end + + map:GoTo(marker.x, marker.y, octomap.config.scaleMax) + end + + + function lv:OnRowRightClick(i, line) + + local lp = LocalPlayer() + local marker = octomap.getMarker(line.markerID) + if not marker or not IsValid(map) then return end + local m = DermaMenu() + + if marker.temp then + m:AddOption('Удалить метку', function() + octolib.markers.clear(marker.id) + marker:Remove() + end):SetIcon(octolib.icons.silk16('map_delete')) + end + if lp:query('DBG: Телепорт по команде') and (lp:Team() == TEAM_ADMIN) then + m:AddOption('Телепортироваться', function() + local pos = Vector(marker:GetPos()) + Vector(0, 0, 70) + -- pos.z = octolib.space.getMapZ(pos.x, pos.y) + netstream.Start('octologs.goto', pos, lp:GetAngles()) + end):SetIcon(octolib.icons.silk16('map_go')) + end + m:Open() + end + sb.list = lv + + local hasSidebarData = { sidebarData = { _exists = true }} + + sb.Paint = paintToolbar + function sb:Refresh() + local markers = octolib.table.toKeyVal(octolib.table.filterQuery(octomap.markers, hasSidebarData)) + table.sort(markers, function(a, b) + if a[2].sort or b[2].sort then + return (a[2].sort or 1000) < (b[2].sort or 1000) + else + return a[2].sidebarData.name < b[2].sidebarData.name + end + end) + + self.list:Clear() + map.markerLines = {} + map.sidebarIDs = {} + + local function createMarker(id, marker) + local line = self.list:AddLine('', marker.sidebarData.name) + local ip = vgui.Create 'DPanel' + ip:SetPaintBackground(false) + line:SetColumnText(1, ip) + line.markerID = id + map.markerLines[id] = line + + local icon = ip:Add 'DImage' + icon:Dock(FILL) + icon:DockMargin(6, 4, 2, 4) + icon:SetImage(marker.iconPath) + end + + for _, data in ipairs(markers) do + local id, marker = unpack(data) + + local sbID = marker.sidebarData.id + if sbID then + if not map.sidebarIDs[sbID] then + -- create only one line for grouped by sidebarID + createMarker(id, marker) + map.sidebarIDs[sbID] = { marker } + else + -- add reference for the rest + local l = map.sidebarIDs[sbID] + l[#l + 1] = marker + map.markerLines[id] = map.markerLines[l[1].id] + end + else + createMarker(id, marker) + end + end + end + + function map:GetMarkerLine(marker) + return self.markerLines[marker.id] + end + + sb:Refresh() + hook.Add('octomap.addedToSidebar', 'dbg-map', function(marker) + if not IsValid(sb) then return hook.Remove('octomap.addedToSidebar', 'dbg-map') end + sb:Refresh() + end) + + local tb = map:Add 'DPanel' + tb:Dock(LEFT) + tb:DockMargin(5,5,5,5) + tb:SetWide(23) + tb:SetPaintBackground(false) + + local tbb = tb:Add 'DPanel' + tbb.Paint = paintToolbar + tbb:Dock(BOTTOM) + tbb:SetTall(46) + + local zIn = tbb:Add 'DImageButton' + zIn:SetPos(4, 4) + zIn:SetSize(16, 16) + zIn:SetImage(octolib.icons.silk16('magnifier_zoom_in')) + function zIn:DoClick() map:Zoom(1, map:FromPanelToMap(map:GetViewCenter())) end + + local zOut = tbb:Add 'DImageButton' + zOut:SetPos(4, 26) + zOut:SetSize(16, 16) + zOut:SetImage(octolib.icons.silk16('magnifier_zoom_out')) + function zOut:DoClick() map:Zoom(-1, map:FromPanelToMap(map:GetViewCenter())) end + + octomap.pnl = map + end, + }) + +end) diff --git a/garrysmod/addons/_config/lua/config/octomap/markers.lua b/garrysmod/addons/_config/lua/config/octomap/markers.lua new file mode 100644 index 0000000..db15a23 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octomap/markers.lua @@ -0,0 +1,162 @@ +local maps = { + rp_evocity_dbg_220222 = { + { + name = 'Причал', + icon = 'octoteam/icons-16/clown_fish.png', + pos = Vector(-6853, 13605, 188), + }, { + name = 'Банкомат', + icon = 'octoteam/icons-16/card_money.png', + sort = 1200, + pos = { + Vector(-6734, -11709, 137), + Vector(-10355, -12781, 138), + Vector(-5726, -9718, 135), + Vector(4329, 5759, 132), + Vector(-4791, -6135, 263), + }, + }, { + name = 'Шахта', + icon = 'octoteam/icons-16/helmet_mine.png', + sort = 1200, + pos = Vector(11329, 5403, 185), + }, + }, + rp_eastcoast_v4c = { + { + name = 'Банкомат', + icon = 'octoteam/icons-16/card_money.png', + sort = 1200, + pos = Vector(911, 113, 32), + }, + }, + rp_truenorth_v1a = { + { + name = 'Шахта', + icon = 'octoteam/icons-16/helmet_mine.png', + sort = 1200, + pos = Vector(10388, -5966, 5377), + }, { + name = 'Банкомат', + icon = 'octoteam/icons-16/card_money.png', + sort = 1200, + pos = { + Vector(12134, 9322, 0), + Vector(7143, 2718, 0), + Vector(7834, 12721, 0), + Vector(-8404, -9142, 0), + }, + }, + }, + rp_riverden_dbg_220313 = { + { + name = 'Шахта', + icon = 'octoteam/icons-16/helmet_mine.png', + sort = 1200, + pos = Vector(6888, 1590, 70), + }, { + name = 'Церковь', + icon = 'octoteam/icons-16/church.png', + sort = 1201, + pos = Vector(-10078, 6336, -192), + }, { + name = 'Вокзал', + icon = 'octoteam/icons-16/train_metro.png', + sort = 1202, + pos = Vector(-12800, 13504, 64), + }, { + name = 'Заправка', + icon = 'octoteam/icons-16/gas.png', + sort = 1203, + pos = { + Vector(-656, 13246, 64), + Vector(-9439, 2595, -192), + Vector(6656, -13696, 868), + }, + }, { + name = 'Банкомат', + icon = 'octoteam/icons-16/card_money.png', + sort = 1204, + pos = { + Vector(-12268, 14650, 0), + Vector(-13808, 3936, -255), + Vector(-4381, 1807, -255), + Vector(-4623, 10204, 0), + Vector(-9536, 10992, 0), + }, + }, + }, +} + +local mapMarkers = maps[game.GetMap()] or {} + +function octomap.sidebarMarkerClick(self, map) + + if not map.GetMarkerLine then return end + + local line = map:GetMarkerLine(self) + if not IsValid(line) then return end + + line:GetListView():OnClickLine(line, true) + line:OnSelect() + +end + +hook.Add('Think', 'octomap.addMarkers', function() + + hook.Remove('Think', 'octomap.addMarkers') + + for i, v in ipairs(mapMarkers) do + if istable(v.pos) then + for i2, pos in ipairs(v.pos) do + local m = octomap.createMarker('dbg' .. i .. '_' .. i2) + :SetIcon(v.icon) + :SetPos(pos) + :SetClickable(true) + :AddToSidebar(v.name, 'dbg' .. i) + + m.sort = v.sort + m.LeftClick = octomap.sidebarMarkerClick + end + else + local m = octomap.createMarker('dbg' .. i) + :SetIcon(v.icon) + :SetPos(v.pos) + :SetClickable(true) + :AddToSidebar(v.name) + + m.sort = v.sort + m.LeftClick = octomap.sidebarMarkerClick + end + end + + local ply = octomap.createMarker('__me') + :SetIcon('octoteam/icons-16/bullet_red.png') + :AddToSidebar('Ты') + + ply.sort = -1000 + + local lp = LocalPlayer() + function ply:Paint(x, y, map) + + local scale = map.scale + self:SetPos(lp:GetPos()) + + local p = { + { x = 0, y = 0 }, + { x = 100 * scale, y = -50 * scale }, + { x = 100 * scale, y = 50 * scale }, + } + + octolib.poly.rotate(p, -lp:GetAngles().y) + octolib.poly.translate(p, x, y) + + draw.NoTexture() + surface.SetDrawColor(0,0,0, 50) + surface.DrawPoly(p) + + octomap.metaMarker.Paint(self, x, y, scale) + + end + +end) diff --git a/garrysmod/addons/_config/lua/config/octoshop-items/base.lua b/garrysmod/addons/_config/lua/config/octoshop-items/base.lua new file mode 100644 index 0000000..81847c9 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoshop-items/base.lua @@ -0,0 +1,549 @@ +local function isActive(item) + return item:GetData('active') +end + +local function hasUses(item) + return item:GetData('usesLeft') > 0 +end + +local thankTexts = L.thankTexts + +octoshop.items['coffee'] = { + cat = L.other, + order = 100, + name = L.item_coffee, + desc = L.desc_octoshop_coffee, + attributes = { + {L.octoshop_what, octolib.icons.silk16('heart'), L.octoshop_good_action}, + }, + price = 15, + icon = 'octoteam/icons/cup2.png', + CanBuy = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + octoshop.notifyAll('ooc', string.format(thankTexts[math.random(#thankTexts)], ply:SteamName())) + + self:Remove() + end, +} + +octoshop.items['jobs_1m'] = { + cat = L.octoshop_buns, + order = 1, + name = L.item_dobrodey, + desc = L.desc_jobs_1m, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('hourglass'), L.octoshop_one_month}, + }, + price = 250, + icon = 'octoteam/icons/heart.png', + CanBuy = true, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('active') then + local ply = self:GetOwner() + ply:SetNetVar('os_dobro', true) + if not ply:GetInfo('dbg_job') or ply:GetInfo('dbg_job') == '' or ply:GetInfo('dbg_job') == 'citizen' then + ply:ConCommand('dbg_job citizen2') + if ply:TimeConnected() < 600 then + timer.Simple(2, function() + if not IsValid(ply) then return end + ply:Spawn() + end) + end + end + end + end, + OnTaken = function(self) + if self:GetData('active') then + self:GetOwner():SetNetVar('os_dobro', false) + end + end, + CanTrade = function(self) + return self:GetData('usesLeft') > 0 + end, + CanUse = function(self) + return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_dobro') + end, + Use = function(self) + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + self:OnGiven() + self:GetOwner():osNetInv() + end, +} + +do + local jobs2w = table.Copy(octoshop.items['jobs_1m']) + jobs2w.hidden = true + jobs2w.CanBuy = false + jobs2w.CanTrade = false + jobs2w.attributes[3] = {L.octoshop_validity, octolib.icons.silk16('hourglass'), 'Две недели'} + jobs2w.Use = function(self) + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 14) + self:OnGiven() + self:GetOwner():osNetInv() + end + octoshop.items['jobs_2w'] = jobs2w +end + +octoshop.items['govorilka'] = { + cat = L.octoshop_buns, + order = 1, + name = L.item_govorilka, + desc = L.desc_octoshop_govorilka, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('time'), L.octoshop_one_month}, + }, + price = 100, + icon = 'octoteam/icons/megaphone2.png', + CanBuy = true, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('equipped') then + timer.Simple(5, function() + self:Equip(true) + end) + end + end, + OnTaken = function(self) + if self:GetData('active') then + self:GetOwner():SetNetVar('os_govorilka', false) + end + end, + CanEquip = isActive, + CanUnequip = isActive, + CanTrade = hasUses, + CanUse = function(self) return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_govorilka') end, + Use = function(self) + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + local ply = self:GetOwner() + ply:osNetInv() + ply:Notify('hint', L.use_item_govorilka) + end, + Equip = function(self) + local ply = self:GetOwner() + ply:SetNetVar('os_govorilka', true) + ply:SendLua([[octogui.reloadGovorilka()]]) + self:SetData('equipped', true) + ply:osNetInv() + end, + Unequip = function(self) + local ply = self:GetOwner() + ply:SetNetVar('os_govorilka', false) + ply:SendLua([[octogui.reloadGovorilka()]]) + self:SetData('equipped', nil) + ply:osNetInv() + end, +} + +octoshop.items['delivery'] = { + cat = L.octoshop_buns, + order = 1, + name = L.free_delivery, + desc = L.desc_octoshop_free_delivery, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('hourglass'), L.octoshop_one_month}, + }, + price = 50, + icon = 'octoteam/icons/box3_drop.png', + CanBuy = true, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('active') then + self:GetOwner():SetNetVar('os_delivery', true) + end + end, + OnTaken = function(self) + if self:GetData('active') then + self:GetOwner():SetNetVar('os_delivery', false) + end + end, + CanTrade = function(self) + return self:GetData('usesLeft') > 0 + end, + CanUse = function(self) + return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_delivery') + end, + Use = function(self) + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + self:OnGiven() + self:GetOwner():osNetInv() + end, +} + +octoshop.items['storage'] = { + cat = L.octoshop_buns, + order = 1, + name = L.item_big_storage, + desc = L.big_storage, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('hourglass'), L.octoshop_one_month}, + }, + price = 100, + icon = 'octoteam/icons/case_travel.png', + CanBuy = true, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('active') then + self:GetOwner():SetNetVar('os_storage', true) + end + end, + OnTaken = function(self) + if self:GetData('active') then + self:GetOwner():SetNetVar('os_storage', false) + end + end, + CanTrade = function(self) + return self:GetData('usesLeft') > 0 + end, + CanUse = function(self) + return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_storage') + end, + Use = function(self) + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + self:OnGiven() + self:GetOwner():osNetInv() + end, +} + +octoshop.items['build'] = { + cat = L.octoshop_buns, + order = 1, + name = L.item_builder, + desc = L.builder, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('time'), L.octoshop_one_month}, + }, + price = 75, + icon = 'octoteam/icons/hammer.png', + CanBuy = true, + CanTrade = hasUses, + CanUse = function(self) return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_build') end, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('active') then + local ply = self:GetOwner() + ply:SetNetVar('os_build', true) + ply:SetNetVar('propLimit', math.max(ply:GetNetVar('propLimit') or 100, 200)) + ply:GetClientVar({'physgunColor'}, function(vars) + if not istable(vars.physgunColor) then return end + local col = Color(vars.physgunColor.r or 0, vars.physgunColor.g or 161, vars.physgunColor.b or 255) + if col == Color(0,161,255) then return end + ply:ChangePhysgunColor(col) + end) + ply:osNetInv() + end + end, + OnTaken = function(self) + if self:GetData('active') then + local ply = self:GetOwner() + ply:SetNetVar('os_build', nil) + if ply:GetNetVar('propLimit') == 200 then ply:SetNetVar('propLimit', nil) end + ply:SetNetVar('physgunColor') + ply:osNetInv() + end + end, + Use = function(self) + local ply = self:GetOwner() + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + self:OnGiven() + ply:osNetInv() + ply:Notify('hint', L.use_item_builder) + end, +} + +octoshop.items['story'] = { + cat = L.octoshop_buns, + order = 1, + name = L.item_story, + desc = L.desc_story, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('time'), L.octoshop_one_month}, + }, + price = 75, + icon = 'octoteam/icons/ink.png', + CanBuy = true, + CanTrade = hasUses, + CanUse = function(self) return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_story') end, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('active') then + local ply = self:GetOwner() + ply:SetNetVar('os_story', true) + ply:osNetInv() + end + end, + OnTaken = function(self) + if self:GetData('active') then + local ply = self:GetOwner() + ply:SetNetVar('os_story', nil) + ply:osNetInv() + end + end, + Use = function(self) + local ply = self:GetOwner() + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + self:OnGiven() + ply:osNetInv() + ply:Notify('hint', L.use_item_story) + end, +} + +octoshop.items['car_plate'] = { + cat = L.octoshop_buns, + order = 1, + name = 'Блатной номер', + desc = 'Позволяет установить любой номер на один из автомобилей в гараже. Устанавливаемый номерной знак должен быть похож на реально существующие, чтобы если его одобряла номерная комиссия, ни у кого не возникло претензий. Он не может содержать мат, любые непристойности или мемы. Админы могут сбросить номер, если он нарушает эти требования', + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + }, + price = 50, + icon = 'octoteam/icons/license_plate.png', + CanBuy = true, + CanTrade = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + carDealer.getGarage(ply:SteamID(), function(garage) + if table.Count(garage) < 1 then + ply:Notify('У тебя нет купленных автомобилей') + return + end + + local opts = {} + for id, veh in pairs(garage) do + opts[#opts + 1] = { carDealer.vehicles[veh.class].name .. ' ' .. veh.plate, id, #opts < 1 } + end + + octolib.request.send(ply, {{ + name = 'Блатной номер', + desc = 'Номер применится к выбранному автомобилю. Он не должен нарушать действующие правила. В противном случае можно его потерять', + },{ + required = true, + type = 'comboBox', + name = 'Автомобиль', + opts = opts, + }, { + required = true, + type = 'strShort', + name = 'Номер (6 или 7 символов)', + ph = carDealer.randomPlate(7), + }}, function(data) + if self.removed then return end + + local vehID, plate = unpack(data, 2) + if not isnumber(vehID) or not isstring(plate) then + ply:Notify('Нужно заполнить все поля') + return + end + + if not octolib.math.inRange(plate:len(), 6, 7) or plate:upper():find('[^' .. carDealer.plateSymbols .. ']') then + ply:Notify('Номер должен состоять из 6 или 7 символов и содержать только латинские буквы или цифры') + return + end + + carDealer.getVehByPlate(plate, function(veh) + if veh then + ply:Notify('Этот номер уже занят') + return + end + + carDealer.updateVehData(vehID, { plate = plate }, function() + hook.Run('car-dealer.plateChanged', vehID, ply:SteamID(), plate) + end) + + local curVeh = carDealer.getCurVeh(ply) + if IsValid(curVeh) and curVeh:GetNetVar('cd.id') == vehID then + curVeh:SetNetVar('cd.plate', plate) + end + + self:Remove() + end) + end) + end) + end, +} + +octoshop.items['trader'] = { + cat = L.octoshop_buns, + order = 1, + name = L.item_trader, + desc = L.item_trader_desc, + attributes = { + {L.octoshop_what, octolib.icons.silk16('plugin'), L.octoshop_expansion}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + {L.octoshop_validity, octolib.icons.silk16('hourglass'), L.octoshop_one_month}, + }, + price = 100, + icon = 'octoteam/icons/chart_up.png', + CanBuy = true, + OnGiven = function(self) + if not self:GetData('usesLeft') then self:SetData('usesLeft', 1) end + if self:GetData('active') then + self:GetOwner():SetNetVar('os_trader', true) + end + end, + OnTaken = function(self) + if self:GetData('active') then + self:GetOwner():SetNetVar('os_trader', false) + end + end, + CanTrade = function(self) + return self:GetData('usesLeft') > 0 + end, + CanUse = function(self) + return self:GetData('usesLeft') > 0 and not self:GetOwner():GetNetVar('os_trader') + end, + Use = function(self) + self:SetData('active', true) + self:SetData('usesLeft', 0) + self:SetExpireIn(60 * 60 * 24 * 30) + self:OnGiven() + local ply = self:GetOwner() + ply:osNetInv() + ply:Notify('hint', L.use_item_trader) + end, +} + +octoshop.items['money_15k'] = { + cat = L.money, + order = 5, + name = '15,000P', + desc = L.money_15k, + attributes = { + {L.octoshop_what, octolib.icons.silk16('cash_stack'), L.octoshop_game_currency}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + }, + price = 15, + icon = 'octoteam/icons/coin.png', + CanBuy = true, + CanTrade = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + ply:BankAdd(15000) + ply:Notify('hint', L.update_balance, DarkRP.formatMoney(15000)) + + self:Remove() + end, +} + +octoshop.items['money_50k'] = { + cat = L.money, + order = 5, + name = '50,000P', + desc = L.money_50k, + attributes = { + {L.octoshop_what, octolib.icons.silk16('cash_stack'), L.octoshop_game_currency}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + }, + price = 50, + icon = 'octoteam/icons/coin3.png', + CanBuy = true, + CanTrade = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + ply:BankAdd(50000) + ply:Notify('hint', L.update_balance, DarkRP.formatMoney(50000)) + + self:Remove() + end, +} + +octoshop.items['money_100k'] = { + cat = L.money, + order = 5, + name = '100,000P', + desc = L.money_100k, + attributes = { + {L.octoshop_what, octolib.icons.silk16('cash_stack'), L.octoshop_game_currency}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + }, + price = 100, + icon = 'octoteam/icons/coin_stacks.png', + CanBuy = true, + CanTrade = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + ply:BankAdd(100000) + ply:Notify('hint', L.update_balance, DarkRP.formatMoney(100000)) + + self:Remove() + end, +} + +octoshop.items['money_250k'] = { + cat = L.money, + order = 5, + name = '250,000P', + desc = L.money_250k, + attributes = { + {L.octoshop_what, octolib.icons.silk16('cash_stack'), L.octoshop_game_currency}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + }, + price = 250, + icon = 'octoteam/icons/money_pack.png', + CanBuy = true, + CanTrade = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + ply:BankAdd(250000) + ply:Notify('hint', L.update_balance, DarkRP.formatMoney(250000)) + + self:Remove() + end, +} + +octoshop.items['money_500k'] = { + cat = L.money, + order = 5, + name = '500,000P', + desc = L.money_500k, + attributes = { + {L.octoshop_what, octolib.icons.silk16('cash_stack'), L.octoshop_game_currency}, + {L.octoshop_where, octolib.icons.silk16('box_open'), L.octoshop_in_some_moment}, + }, + price = 500, + icon = 'octoteam/icons/gold_chest.png', + CanBuy = true, + CanTrade = true, + CanUse = true, + Use = function(self) + local ply = self:GetOwner() + ply:BankAdd(500000) + ply:Notify('hint', L.update_balance, DarkRP.formatMoney(500000)) + + self:Remove() + end, +} diff --git a/garrysmod/addons/_config/lua/config/octoshop_sh.lua b/garrysmod/addons/_config/lua/config/octoshop_sh.lua new file mode 100644 index 0000000..5069029 --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoshop_sh.lua @@ -0,0 +1,36 @@ +-- +-- SALARY +-- + +octoshop.salaryAFKTime = 45 -- seconds +octoshop.salaryPeriod = 60 -- minutes + +-- +-- MISC FUNCTIONS +-- + +octoshop.openTopup = function(but, pnl) + + F4:Hide() + gui.ActivateGameUI() + octoesc.OpenURL('https://octothorp.team/shop/chips?steamid=' .. LocalPlayer():SteamID()) + +end + +local owners = { + ['STEAM_0:0:20521653'] = true, -- chelog + -- ['STEAM_0:0:65232888'] = true, -- quillin + -- ['STEAM_0:1:45099154'] = true, -- Sighty + -- ['STEAM_0:1:97137915'] = true, -- Arthas + -- ['STEAM_0:0:80902445'] = true, -- Tawi + -- ['STEAM_0:1:127790060'] = true, -- /null. + -- ['STEAM_0:0:56250978'] = true, -- inquizzy + -- ['STEAM_0:1:46891732'] = true, -- doctor + -- ['STEAM_0:1:50315838'] = true, -- kilo-7 +} + +octoshop.checkOwner = function(ply) + + return owners[ply:SteamID()] + +end diff --git a/garrysmod/addons/_config/lua/config/octoshop_sv.lua b/garrysmod/addons/_config/lua/config/octoshop_sv.lua new file mode 100644 index 0000000..3ff63af --- /dev/null +++ b/garrysmod/addons/_config/lua/config/octoshop_sv.lua @@ -0,0 +1,49 @@ +-- +-- SERVER +-- + +octoshop.server_id = 'dbg' +octoshop.server_name = 'Доброград' + +-- +-- COUPONS +-- + +octoshop.couponLength = 48 -- max: 64 +octoshop.couponUseDelay = 45 -- seconds + +-- +-- SALARY +-- + +octoshop.salaryAmount = { + trainee = 1, + admin = 2, + tadmin = 3, + sadmin = 4, + superadmin = 5, + founder = 5, + + contentmanager = 5, + rpmanager = 5, + projectmanager = 5, + dev = 5, + + quenter = 1, + squenter = 1, + gamemaster = 1, + sgamemaster = 2, +} +octoshop.hackCallback = function(ply, t) + + -- notifyAdmins(ply:SteamName() .. ' (' .. ply:SteamID() .. ') пытается обмануть нас на Доброграде: ' .. t) + -- ply:Ban(1440, true) + +end + +-- +-- FUNCTIONS +-- + +octoshop.notify = octolib.notify.send +octoshop.notifyAll = octolib.notify.sendAll diff --git a/garrysmod/addons/_config/lua/config/stungun.lua b/garrysmod/addons/_config/lua/config/stungun.lua new file mode 100644 index 0000000..4a5219b --- /dev/null +++ b/garrysmod/addons/_config/lua/config/stungun.lua @@ -0,0 +1,221 @@ + +--[[ +Stungun SWEP Created by Donkie (http://steamcommunity.com/id/Donkie/) +For personal/server usage only, do not resell or distribute! +]] + +--[[ +GENERAL INFORMATION + +Weaponclass: "stungun" +Ammotype: "ammo_stungun" +The stungun is only being tested on Sandbox, DarkRP (latest & 2.4.3) and TTT (latest) before releases. +]] + +--[[ +CONFIG FILE +ONLY EDIT STUFF IN HERE +ANY EDITS OUTSIDE THIS FILE IS NOT MY RESPONSIBILITY +]] + +--[[**************** +BASIC SECTION +*****************]] + +-- Ragdoll physics effect, replaces old "ShouldRoll" +-- Set to either 0, 1 or 2 +-- 0: No effect +-- 1: Original comical rolling around +-- 2: NEW: Ragdoll shaking +STUNGUN.PhysEffect = 1 + +-- Can you un-taze people with rightclick? +STUNGUN.CanUntaze = true + +-- Should it display in thirdperson view for the tazed player? (if false, firstperson) +STUNGUN.Thirdperson = true + +-- If above is true, should users be able to press crouch button (default ctrl) to switch between third and firstperson? +STUNGUN.AllowSwitchFromToThirdperson = false + +-- Should people be able to pick a tazed player using physgun? +STUNGUN.AllowPhysgun = false + +-- Should people be able to use toolgun on tazed players? +STUNGUN.AllowToolgun = false + +-- Should tazed players take falldamage? (Warning: experimental, not recommended to have if players can pick them up using physgun.) +STUNGUN.Falldamage = false + +-- How much damage the tazer also does +-- Set to 0 to disable +STUNGUN.StunDamage = 0 + +-- Should it display name and HP on tazed players? +STUNGUN.ShowPlayerInfo = false + +-- Can the player be damaged while he's tazed? +STUNGUN.AllowDamage = true + +-- Can the player suicide while he's paralyzed? +STUNGUN.ParalyzeAllowSuicide = false + +-- Can the player suicide while he's mute? +STUNGUN.MuteAllowSuicide = false + +-- Amount of seconds the player is immune to stuns after he just got up from being paralyzed. -1 to disable. +STUNGUN.Immunity = -1 + +-- Can people of same team stungun each other? Check further below (in the advanced section) for the check-function. +-- The check function is by default set to ignore police trying to taze police. +STUNGUN.AllowFriendlyFire = true + +-- If the ragdoll version of the playermodel does not spawn correctly (incorrectly made model) then the ragdoll will be this model. +-- When done rolling around the player will get back his default model. +-- Set this to "nil" (without quotes) if you want to disable this default model and just make it not work. +STUNGUN.DefaultModel = Model("models/player/group01/male_01.mdl") + +-- Thirdperson holdtype. Put "revolver" to make him carry the gun in 2 hands, put "pistol" to make him one-hand the gun. +SWEP.HoldType = "revolver" + +-- Default charge for the weapon, when the guy picks the gun up, should it be filled already or wait to be filled? 100 is max charge, 0 is uncharged. +SWEP.Charge = 100 + +-- Should we have infinite ammo (true) or finite ammo (false)? +-- Finite ammo makes it spawn with 1 charge, unless you're running TTT in which you can specify how much ammo it should start with down below. +SWEP.InfiniteAmmo = true + +-- Recharge rate. How many seconds it takes to fill the gun back up. +SWEP.RechargeTime = 10 +SWEP.ReadyDelay = 0.25 + +-- How long range the weapon has. Players beyond this range won't get hit. +-- To put in perspective, in darkrp, the above-head-playerinfo has a default range of 400. +SWEP.Range = 200 + +--[[ +There's two seperate times for this. This is so the player has a chance to escape but the robbers still have a chance to re-taze him. +Put the paralyzetime and mutetime at same to make the player able to talk exactly when he's able to get up. +Put the mutetime slightly higher than paralyze time to make him wait a few seconds before he's able to talk after he got up. +]] + +-- How many seconds the player is rolling around as a ragdoll. +STUNGUN.ParalyzedTime = 10 + +-- How many seconds the player is mute/gagged = Unable to speak/chat. +STUNGUN.MuteTime = 12 + +-- NEW: How many seconds after the player has been unragdolled that he still won't be able to move. +STUNGUN.FreezeTime = 3 + +-- What teams are immune to the stungun? (if any). +local immuneteams = { + -- TEAM_MAYOR, + -- TEAM_CHIEF, + -- TEAM_POLICE2, + -- TEAM_MEDCOP, + -- TEAM_POLICE, +} + +--[[**************** +ADVANCED SECTION +Contact me if you need help with any function. +*****************]] +-- If you've found that specific models appear to break it, add them here and they will turn into the default model instead. +STUNGUN.BrokenModels = { + ["models/test/model.mdl"] = true +} + +--[[ +Hurt sounds +]] +local combinemodels = {["models/player/police.mdl"] = true, ["models/player/police_fem.mdl"] = true} +local females = { + ["models/player/alyx.mdl"] = true,["models/player/p2_chell.mdl"] = true, + ["models/player/mossman.mdl"] = true,["models/player/mossman_arctic.mdl"] = true} +function STUNGUN.PlayHurtSound( ply ) + local mdl = ply:GetModel() + + -- Combine + if combinemodels[mdl] or string.find(mdl, "combine") then + return "npc/combine_soldier/pain" .. math.random(1,3) .. ".wav" + end + + -- Female + if females[mdl] or string.find(mdl, "female") then + return "vo/npc/female01/pain0" .. math.random(1,9) .. ".wav" + end + + -- Male + return "vo/npc/male01/pain0" .. math.random(1,9) .. ".wav" +end + +--[[ +Custom same-team function. +]] +function STUNGUN.SameTeam(ply1, ply2) + if STUNGUN.IsDarkRP and ply1:isCP() and ply2:isCP() then + return true + end + + -- return (ply1:Team() == ply2:Team()) -- Probably dont want this in DarkRP, nor TTT, but maybe your custom TDM gamemode. +end + +--[[ +Custom Immunity function. +]] +function STUNGUN.IsPlayerImmune(ply) + local job = ply:getJobTable() + if job and job.police or ply:IsGhost() then return true end + return false +end + + +--[[**************** +DarkRP Specific stuff +Only care about these if you're running it on a DarkRP server. +*****************]] + +-- Should the stungun charges be buyable in the f4 store? +-- If yes, put in a number above 0 as price, if no, put -1 to disable. +STUNGUN.AddAmmoItem = -1 + +-- Should it be allowed to use the arrest baton on stunned people? +STUNGUN.AllowArrestOnRag = true + +-- Should it be allowed to use the unarrest baton on stunned people? +STUNGUN.AllowUnArrestOnRag = true + +--[[**************** +TTT Specific stuff +Only care about these if you're running it on a TTT server. +*****************]] + +-- Can stunned players be picked up by magnetostick? +STUNGUN.CanPickup = false + +-- Default ammo. +SWEP.Ammo = 3 + +-- Kind specifies the category this weapon is in. Players can only carry one of +-- each. Can be: WEAPON_... MELEE, PISTOL, HEAVY, NADE, CARRY, EQUIP1, EQUIP2 or ROLE. +-- Matching SWEP.Slot values: 0 1 2 3 4 6 7 8 +SWEP.Kind = WEAPON_EQUIP1 + +-- If AutoSpawnable is true and SWEP.Kind is not WEAPON_EQUIP1/2, then this gun can +-- be spawned as a random weapon. +SWEP.AutoSpawnable = false + +-- CanBuy is a table of ROLE_* entries like ROLE_TRAITOR and ROLE_DETECTIVE. If +-- a role is in this table, those players can buy this. +SWEP.CanBuy = { ROLE_DETECTIVE } + +-- InLoadoutFor is a table of ROLE_* entries that specifies which roles should +-- receive this weapon as soon as the round starts. In this case, none. +SWEP.InLoadoutFor = nil + +-- If LimitedStock is true, you can only buy one per round. +SWEP.LimitedStock = false + +-- If AllowDrop is false, players can't manually drop the gun with Q +SWEP.AllowDrop = true diff --git a/garrysmod/addons/_config/lua/config/sweets-items.lua b/garrysmod/addons/_config/lua/config/sweets-items.lua new file mode 100644 index 0000000..0aca10f --- /dev/null +++ b/garrysmod/addons/_config/lua/config/sweets-items.lua @@ -0,0 +1,331 @@ +local function attItem(mdl) + local attData = simfphys.getAttByModel(mdl) + return {'car_att', { + name = attData.name, + desc = attData.desc, + icon = attData.icon, + mass = attData.mass, + volume = attData.volume, + colorable = not attData.noPaint or nil, + attmdl = attData.mdl, + model = attData.mdl, + skin = attData.skin, + scale = attData.scale, + }} +end +local function sendItem(item, sentNo, sentYes) + return function(ply, amount) + local data + if isstring(item) then data = {{ item, amount }} + elseif istable(item) then + data = octolib.array.series(item, amount) + end + local sent, msg = halloween.sendItems(ply, data) + if not sent then + ply:Notify('warning', msg or sentNo) + else ply:Notify('hint', sentYes) end + return sent + end +end + +local function icon(name) + return 'octoteam/icons-halloween/' .. name .. '.png' +end + +halloween.registerItem('mystery_sweet', { + name = 'Загадочная конфета', + icon = icon('mystery_sweet'), + desc = 'Конфета, обладающая случайным вкусом. Арбуз? Яблоко? А может, рвота или ушная сера? Попробуй и узнаешь!', + price = 100, + deliver = sendItem('h20_sweets', 'Не удалось отправить загадочные конфеты', 'Загадочные конфеты отправлены по почте и придут через пару минут'), +}) + +halloween.registerItem('guitar', { + name = 'Гитара', + icon = icon('guitar'), + desc = 'Позволяет скрасить свое одиночество или задать атмосферу в компании', + price = 300, + deliver = sendItem({'weapon', { + name = L.guitar, + icon = octolib.icons.color('guitar'), + model = 'models/custom/guitar/m_d_45.mdl', + wepclass = 'guitar', + mass = 2, + volume = 5, + ammoadd = 0, + clip1 = 0, + clip2 = 0, + }}, 'Не удалось отправить гитару', 'Гитара отправлена по почте и придет через пару минут'), +}) + +local masks = { 'cleaver', 'monkey', 'zombie', 'hockey', 'gingerbread', 'top_hat', 'doctor', 'pig', 'bear', 'hoff_widesmile', 'hoff_tears', 'hoff_tinysmile', 'hoff_dude', 'hoff_killme', 'hoff_helpme' } +for _, v in ipairs(masks) do + local mask = CFG.masks[v] + local name = mask.name + if mask.desc then name = name .. ' ' .. mask.desc end + local item = { + name = mask.name, + icon = mask.icon, + desc = mask.desc, + mask = v, + } + halloween.registerItem('mask_' .. v, { + name = name, + icon = icon('mask_' .. v), + skin = mask.skin, + desc = 'Стильно, модно, молодежно. Можно надеть на голову', + price = 3000, + deliver = sendItem({'h_mask', item}, 'Не удалось отправить маску', 'Маска отправлена по почте и придет через пару минут'), + }) + halloween.registerCaseItem('mask_' .. v, { + name = name, + icon = icon('mask_' .. v), + maxMass = 0.3, + maxVolume = 0.5, + give = function(_, cont) + cont:AddItem('h_mask', item) + end, + }) +end + +halloween.registerItem('theme', { + name = 'Очки с оранжевыми линзами', + icon = icon('theme'), + desc = 'Понравилось хэллоуинское настроение? Ты можешь продлить его, захватив с собой эти очки с оранжевыми линзами!\n\n- Хэллоуинская тема. Ее можно будет включить в любой момент в настройках', + price = 5000, + max = 1, + deliver = function(ply) + ply:SetDBVar('halloweenTheme', true) + ply:SetLocalVar('halloweenTheme', true) + ply:ConCommand('octogui_reloadf4') + ply:Notify('hint', 'Хэллоуинскую тему можно активировать в разделе "Другое" в настройках') + return true + end, +}) + +halloween.registerItem('jar', { + name = 'Банка с глазами', + icon = icon('jar'), + desc = 'Старая банка с устрашающими глазами. Будет хорошо смотреться на приборной панели твоего автомобиля!', + price = 6000, + deliver = function(ply, amount) + local data = octolib.array.series(attItem('models/props/spookington/eyejar.mdl'), amount) + local sent, msg = halloween.sendItems(ply, data) + if not sent then + ply:Notify('warning', msg or 'Не удалось отправить банку с глазами') + else ply:Notify('hint', 'Банка с глазами отправлена по почте и придет через пару минут') end + return sent + end, +}) + +halloween.registerItem('spider', { + name = 'Паучок', + icon = icon('spider'), + desc = 'Страшный, волосатый, но отчасти милый хэллоуинский паучок. Представляешь, если он повиснет в салоне твоего автомобиля? Это будет просто безумно круто!', + price = 6000, + deliver = function(ply, amount) + local data = octolib.array.series(attItem('models/props/spookington/spider_toy.mdl'), amount) + local sent, msg = halloween.sendItems(ply, data) + if not sent then + ply:Notify('warning', msg or 'Не удалось отправить паучка') + else ply:Notify('hint', 'Паучок отправлен по почте и придет через пару минут') end + return sent + end, +}) + +halloween.registerItem('sedan', { + name = 'Седан', + icon = icon('sedan'), + desc = 'Подарочный сертификат автосалона на эксклюзивный черный седан!\n\n- Автомобиль будет доставлен в гараж\n- При продаже авто деньги не начислятся', + price = 8500, + deliverAsync = function(ply, amount, callback) + octolib.func.parallel( + octolib.array.series(function(res) + carDealer.ownVeh(ply:SteamID(), 'halloween_sedan', function(_, plate) + res(plate) + end) + end, amount) + ):Then(function(plates) + callback(true) + if not IsValid(ply) then return end + if amount == 1 then + ply:Notify('hint', 'Автомобиль с регистрационным номером ' .. plates[1] .. ' доставлен в твой гараж') + elseif amount == 2 then + ply:Notify('hint', 'Автомобили с регистрационными номерами ' .. plates[1] .. ' и ' .. plates[2] .. ' доставлены в твой гараж') + else + local last = plates[#plates] + table.remove(plates) + ply:Notify('hint', 'Автомобили с регистрационными номерами ' .. string.Implode(', ', plates) .. ' и ' .. last .. ' доставлены в твой гараж') + end + end):Catch(function(err) + ErrorNoHalt(err) + callback(false) + end) + end, +}) + +halloween.registerItem('pickup', { + name = 'Пикап', + icon = icon('pickup'), + desc = 'Подарочный сертификат автосалона на эксклюзивный оранжевый пикап!\n\n- Автомобиль будет доставлен в гараж\n- При продаже авто деньги не начислятся', + price = 8500, + deliverAsync = function(ply, amount, callback) + octolib.func.parallel( + octolib.array.series(function(res) + carDealer.ownVeh(ply:SteamID(), 'halloween_pickup', function(_, plate) + res(plate) + end) + end, amount) + ):Then(function(plates) + callback(true) + if not IsValid(ply) then return end + if amount == 1 then + ply:Notify('hint', 'Автомобиль с регистрационным номером ' .. plates[1] .. ' доставлен в твой гараж') + elseif amount == 2 then + ply:Notify('hint', 'Автомобили с регистрационными номерами ' .. plates[1] .. ' и ' .. plates[2] .. ' доставлены в твой гараж') + else + local last = plates[#plates] + table.remove(plates) + ply:Notify('hint', 'Автомобили с регистрационными номерами ' .. string.Implode(', ', plates) .. ' и ' .. last .. ' доставлены в твой гараж') + end + end):Catch(function(err) + ErrorNoHalt(err) + callback(false) + end) + end, +}) + +-- CASE ITEMS + +halloween.registerCaseItem('sweets_low', { + name = 'Загадочные конфеты', + icon = icon('mystery_sweet'), + maxMass = 0.03, + maxVolume = 0.03, + give = function(ply, cont) + local sweets = math.random(3) + cont:AddItem('h20_sweets', sweets) + ply:Notify('hint', 'Несколько загадочных конфет добавлено тебе в инвентарь') + end, +}) + +halloween.registerCaseItem('money_low', { + name = 'Потрепанный кошель', + icon = icon('money_low'), + maxMass = 0.04, + maxVolume = 0.04, + give = function(ply, cont) + local money = math.random(4000) + cont:AddItem('money', money) + ply:Notify('hint', 'Как неожиданно и приятно... ' .. DarkRP.formatMoney(money) .. ' добавлено тебе в инвентарь') + end, +}) + +halloween.registerCaseItem('money_large', { + name = 'Мешок с монетками', + icon = icon('money_large'), + maxMass = 0.25, + maxVolume = 0.25, + give = function(ply, cont) + local money = math.random(4000, 25000) + cont:AddItem('money', money) + ply:Notify('hint', 'Вот так везение... ' .. DarkRP.formatMoney(money) .. ' добавлено тебе в инвентарь') + end, +}) + +halloween.registerCaseItem('sweets_large', { + name = 'Горсть конфет', + icon = icon('mystery_sweet'), + maxMass = 0.13, + maxVolume = 0.13, + give = function(ply, cont) + local sweets = math.random(3, 13) + cont:AddItem('h20_sweets', sweets) + ply:Notify('hint', sweets .. octolib.string.formatCount(sweets, ' загадочная конфета добавлена', ' загадочных конфеты добавлены', ' загадочных конфет добавлено') .. ' тебе в инвентарь') + end, +}) + +halloween.registerCaseItem('candy', { + name = 'Испорченный леденец', + icon = icon('candy'), + maxMass = 0.1, + maxVolume = 0.05, + give = function(_, cont) + cont:AddItem('souvenir', { + name = 'Испорченный леденец', + icon = octolib.icons.color('food_candy2'), + model = 'models/griim/foodpack/candycane.mdl', + desc = 'Старый леденец. Присмотревшись к нему можно увидеть паутину. Лучше не пытаться его съесть, а как сувенир - очень даже неплохая вещица!', + }) + end, +}) + +halloween.registerCaseItem('booze', { + name = 'Хэллоуинская настойка', + icon = icon('booze'), + maxMass = 1.5, + maxVolume = 1, + give = function(_, cont) + cont:AddItem('drug_booze2', { + name = 'Хэллоуинская настойка', + icon = octolib.icons.color('bottle_wine'), + }) + end, +}) + +halloween.registerCaseItem('os_dobro', { + name = 'Карта клуба почетных жителей "Добродей"', + icon = icon('os_dobro'), + give = function(ply) + ply:osGiveItem('jobs_1m') + ply:Notify('hint', 'Ты получил плюшку "Добродей" на месяц! Активировать ее можно в F4 -> Плюшки') + end, +}) + +halloween.registerCaseItem('att_pumpkin', { + name = 'Тыква-лампа для автомобиля', + icon = icon('pumpkin'), + maxMass = 1, + maxVolume = 3, + give = function(_, cont) + cont:AddItem(unpack(attItem('models/halloween2015/pumbkin_n_f01.mdl'))) + end, +}) + +halloween.registerCaseItem('att_spider', { + name = 'Паучок для автомобиля', + icon = icon('spider'), + maxMass = 0.7, + maxVolume = 1, + give = function(_, cont) + cont:AddItem(unpack(attItem('models/props/spookington/spider_toy.mdl'))) + end, +}) + +-- CASES + +halloween.registerCase('gift', { + name = 'Подарок от Джека', + icon = icon('gift'), + desc = 'Я кладу какой-то предмет в коробочку и отправляю тебе по почте. Что за предмет - узнаешь, когда откроешь ;)', + price = 750, + items = { + {31, 'sweets_low'}, + {20, 'booze'}, + {20, 'candy'}, + {13, 'sweets_large'}, + {9, 'money_low'}, + {5, 'money_large'}, + {2, 'os_dobro'}, + {1.5, 'att_pumpkin'}, + {1.5, 'att_spider'}, + }, +}) + +halloween.registerCase('mask', { + name = 'Какая-то маска', + icon = icon('case-masks'), + desc = 'Теряешься в моем огромном ассортименте масок? Я могу помочь выбрать! Никаких предпочтений по маскам у меня нет, поэтому я честно выберу случайную маску. Никакого мошенничества!', + price = 3000, + items = octolib.table.mapSequential(masks, function(v) return {1, 'mask_' .. v} end), +}) diff --git a/garrysmod/addons/admin-logs/lua/autorun/octologs.lua b/garrysmod/addons/admin-logs/lua/autorun/octologs.lua new file mode 100644 index 0000000..1525e07 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/autorun/octologs.lua @@ -0,0 +1 @@ +octolib.shared('octologs/init') diff --git a/garrysmod/addons/admin-logs/lua/octologs/cl_poslist.lua b/garrysmod/addons/admin-logs/lua/octologs/cl_poslist.lua new file mode 100644 index 0000000..27c0d1a --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/cl_poslist.lua @@ -0,0 +1,103 @@ +concommand.Add('octologs_display', function(ply, cmd, args, argStr) + + local cache = {} + + local w = vgui.Create 'DFrame' + w:SetTitle('Octologs Display') + w:SetSize(200, 400) + w:AlignBottom(10) + w:AlignLeft(10) + + local l = w:Add 'DListView' + l:Dock(FILL) + l:AddColumn(L.title) + l:AddColumn(L.position) + l:AddColumn(L.angle) + + function l:Rebuild() + self:Clear() + for i, data in ipairs(cache) do + local title, pos, ang = unpack(data) + local line = self:AddLine( + title, + ('[%s %s %s]'):format(pos.x, pos.y, pos.z), + ('{%s %s %s}'):format(ang.p, ang.y, ang.r) + ) + + line.id = i + end + end + + function l:OnRowRightClick(i, line) + local m = DermaMenu() + m:AddOption(L.teleport, function() + local data = cache[line.id] + netstream.Start('octologs.goto', data[2], data[3]) + end) + m:AddOption(L.delete, function() + local data = cache[line.id] + if data then + local mdl = data[4] + if IsValid(mdl) then mdl:Remove() end + table.remove(cache, line.id) + l:Rebuild() + end + end) + m:Open() + end + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(20) + b:SetText(L.add) + function b:DoClick() + octolib.request.open({{ + required = true, + type = 'strShort', + name = 'Название', + default = 'Позиция ' .. #cache + 1, + }, { + required = true, + type = 'strShort', + name = 'Локация', + default = '[0 0 0]{0 0 0}', + }}, function(data) + if not data then return end + local posStr = data[2]:gmatch('%[(.-)%]')() + local angStr = data[2]:gmatch('%{(.-)%}')() + if not posStr or not angStr then return end + + local pos = Vector(posStr) + local ang = Angle(angStr) + if not isvector(pos) or not isangle(ang) then return end + + local mdl = octolib.createDummy('models/dav0r/camera.mdl') + mdl:SetPos(pos) + mdl:SetAngles(ang) + + cache[#cache + 1] = { data[1], pos, ang, mdl } + l:Rebuild() + end) + end + + hook.Add('HUDPaint', 'draw-positions', function() + for i, data in ipairs(cache) do + local pos = data[2]:ToScreen() + local x, y = pos.x, pos.y + draw.RoundedBox(0, x - 3, y - 3, 6, 6, color_black) + draw.RoundedBox(0, x - 2, y - 2, 4, 4, color_white) + draw.SimpleText(data[1], 'DermaDefault', x, y + 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + end) + + function w:OnClose() + for i, data in ipairs(cache) do + local mdl = data[4] + if IsValid(mdl) then mdl:Remove() end + end + + cache = nil + hook.Remove('HUDPaint', 'draw-positions') + end + +end) diff --git a/garrysmod/addons/admin-logs/lua/octologs/init.lua b/garrysmod/addons/admin-logs/lua/octologs/init.lua new file mode 100644 index 0000000..e985754 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/init.lua @@ -0,0 +1,10 @@ +octolib.server('sv_octologs') +octolib.server('sv_helpers') +octolib.server('sv_log') + +local fls, fds = file.Find('octologs/loggers/*.lua', 'LUA') +for i, name in ipairs(fls) do + octolib.server('octologs/loggers/' .. name:gsub('.lua', '')) +end + +octolib.client('cl_poslist') diff --git a/garrysmod/addons/admin-logs/lua/octologs/loggers/admin.lua b/garrysmod/addons/admin-logs/lua/octologs/loggers/admin.lua new file mode 100644 index 0000000..a3e27c3 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/loggers/admin.lua @@ -0,0 +1,78 @@ +local noLogCmds = octolib.array.toKeys({ 'mute', 'gag', 'invisible' }) +hook.Add('serverguard.RanCommand', 'octologs', function(ply, commandTable, silent, _args) + + local cmdStr = commandTable.command + if noLogCmds[cmdStr] or noLogCmds[cmdStr:gsub('un', '')] then return end + + if _args and #_args > 0 then + cmdStr = cmdStr .. ' ' .. table.concat(_args, ' ') + end + + octologs.createLog(octologs.CAT_ADMIN) + :Add(octologs.ply(ply), ' used SG command: ', octologs.string(cmdStr)) + :Save() + +end) + +hook.Add('octoinv.adminGive', 'octologs', function(ply, item) + + if item and IsValid(ply) and ply:IsPlayer() then + local class = item.class + local name = item.name or octoinv.items[item.class].name + local itemStr = ('%sx[%s, %s]'):format(item.amount or 1, class, name) + octologs.createLog(octologs.CAT_ADMIN) + :Add(octologs.ply(ply), ' created ', octologs.table(itemStr, item, true)) + :Save() + end + +end) + +hook.Add('dbg-admin.tell', 'octologs', function(ply, time, title, msg, target) + octologs.createLog(octologs.CAT_ADMIN) + :Add(octologs.ply(ply, {'loc', 'wep', 'job', 'hp', 'ar'}), ' sent ') + :Add(octologs.table('AdminTell', {time = time, title = title, msg = msg}, true), ' to ') + :Add(IsValid(target) and octologs.ply(target) or octologs.string('everyone')) + :Save() +end) + +hook.Add('cats.created', 'octologs', function(senderID) + local sender = player.GetBySteamID(senderID) + octologs.createLog(octologs.CAT_ADMIN) + :Add(IsValid(sender) and octologs.ply(sender, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(senderID)) + :Add(' created a ticket') + :Save() +end) + +hook.Add('cats.message', 'octologs', function(senderID, ticketID, msg) + local sender = player.GetBySteamID(senderID) + local owner = player.GetBySteamID(ticketID) + octologs.createLog(octologs.CAT_ADMIN) + :Add(IsValid(sender) and octologs.ply(sender, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(senderID), ' sent message to ') + :Add(IsValid(owner) and octologs.ply(owner, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(ticketID)) + :Add('\'s ticket: ', msg) + :Save() +end) + +hook.Add('cats.closed', 'octologs', function(senderID, ticketID) + local sender = player.GetBySteamID(senderID) + local owner = player.GetBySteamID(ticketID) + octologs.createLog(octologs.CAT_ADMIN) + :Add(IsValid(sender) and octologs.ply(sender, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(senderID), ' closed ') + :Add(IsValid(owner) and octologs.ply(owner, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(ticketID), '\'s ticket') + :Save() +end) + +hook.Add('cats.claim', 'octologs', function(senderID, ticketID, doClaim) + local sender = player.GetBySteamID(senderID) + local owner = player.GetBySteamID(ticketID) + octologs.createLog(octologs.CAT_ADMIN) + :Add(IsValid(sender) and octologs.ply(sender, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(senderID), doClaim and ' claimed ' or ' unclaimed ') + :Add(IsValid(owner) and octologs.ply(owner, {'loc', 'wep', 'job', 'hp', 'ar'}) or octologs.string(ticketID), '\'s ticket') + :Save() +end) + +hook.Add('dbg-test.edit', 'octologs', function(ply) + octologs.createLog(octologs.CAT_ADMIN) + :Add(octologs.ply(ply, {'loc', 'wep', 'job', 'hp', 'ar'}), ' edited test') + :Save() +end) diff --git a/garrysmod/addons/admin-logs/lua/octologs/loggers/darkrp.lua b/garrysmod/addons/admin-logs/lua/octologs/loggers/darkrp.lua new file mode 100644 index 0000000..51e5f06 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/loggers/darkrp.lua @@ -0,0 +1,148 @@ +hook.Add('playerArrested', 'octologs', function(ply, time, cop, reason) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop), ' arrested ', octologs.ply(ply, {'loc'}), ': ', octologs.string(reason or 'no reason')) + :Save() + +end) + +hook.Add('playerUnArrested', 'octologs', function(ply, cop) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop), ' unarrested ', octologs.ply(ply, {'loc'})) + :Save() + +end) + +hook.Add('onDoorRamUsed', 'octologs', function(succ, ply, tr) + + local ent = tr.Entity + if not IsValid(ent) then return end + + local owner = ent:GetPlayerOwner() + if owner then + owner = player.GetBySteamID(owner) + end + + local log = octologs.createLog(octologs.CAT_POLICE) + log:Add(octologs.ply(ply, {'loc', 'wep'}), ' rammed ') + if IsValid(owner) then log:Add(octologs.ply(owner, {'loc', 'wep'}), '\'s ') end + log:Add(octologs.ent(ent), ', success: ' .. tostring(succ)) + :Save() + +end) + +hook.Add('onPlayerDemoted', 'octologs', function(demoter, ply, reason) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(demoter), ' demoted ', octologs.ply(ply), ': ', octologs.string(reason or 'no reason')) + :Save() + +end) + +local function estate(ply, estIdx, msg) + local estData = dbgEstates.getData(estIdx) + local logEstDat = octologs.table(estData.name or '', { + id = estIdx, + pos = estData.doors[1]:GetPos(), + }, true) + + local logPlyDat = octologs.string(ply) + if octolib.string.isSteamID(ply) then + local realPly = player.GetBySteamID(ply) + if IsValid(realPly) then logPlyDat = octologs.ply(realPly) end + end + + octologs.createLog(octologs.CAT_PROPERTY) + :Add(logPlyDat, msg, logEstDat) + :Save() +end + +hook.Add('dbg-estates.unowned', 'octologs', function(ply, estIdx) + estate(ply, estIdx, ' unowned estate ') +end) + +hook.Add('dbg-estates.owned', 'octologs', function(ply, estIdx) + estate(ply, estIdx, ' owned estate ') +end) + +hook.Add('DarkRPGiveLicence', 'octologs', function(cop, ply, reason) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop, {'wep'}), ' gave licence to ', octologs.ply(ply, {'loc', 'wep'}), ': ', octologs.string(reason or '???')) + :Save() + +end) + +hook.Add('DarkRPTakeLicence', 'octologs', function(cop, ply) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop, {'wep'}), ' took licence from ', octologs.ply(ply, {'loc', 'wep'})) + :Save() + +end) + +hook.Add('addLaw', 'octologs', function(_, law, ply) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(ply, {'loc', 'wep'}), ' added law: ', octologs.string(law)) + :Save() + +end) + +hook.Add('removeLaw', 'octologs', function(_, law, ply) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(ply, {'loc', 'wep'}), ' removed law: ', octologs.string(law)) + :Save() + +end) + +hook.Add('playerWanted', 'octologs', function(ply, cop, reason) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop, {'loc', 'wep'}), ' set wanted ', octologs.ply(ply, {'loc', 'wep'}), ': ', octologs.string(reason or 'no reason')) + :Save() + +end) + +hook.Add('playerUnWanted', 'octologs', function(ply, cop, reason) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop, {'loc', 'wep'}), ' set unwanted ', octologs.ply(ply, {'loc', 'wep'}), ': ', octologs.string(reason or 'no reason')) + :Save() + +end) + +hook.Add('playerWarranted', 'octologs', function(ply, cop, reason) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop), ' warranted ', octologs.ply(ply), ': ', octologs.string(reason or 'no reason')) + :Save() + +end) + +hook.Add('playerUnWarranted', 'octologs', function(ply, cop) + + octologs.createLog(octologs.CAT_POLICE) + :Add(octologs.ply(cop), ' unwarranted ', octologs.ply(ply)) + :Save() + +end) + +hook.Add('fspectate.start', 'octologs', function(ply, target) + + local log = octologs.createLog(octologs.CAT_ADMIN) + :Add(octologs.ply(ply), ' started spectating') + if IsValid(target) then log:Add(' ', octologs.ply(target)) end + log:Save() + +end) + +hook.Add('fspectate.end', 'octologs', function(ply) + + octologs.createLog(octologs.CAT_ADMIN) + :Add(octologs.ply(ply), ' stopped spectating') + :Save() + +end) diff --git a/garrysmod/addons/admin-logs/lua/octologs/loggers/dobrograd.lua b/garrysmod/addons/admin-logs/lua/octologs/loggers/dobrograd.lua new file mode 100644 index 0000000..cd91231 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/loggers/dobrograd.lua @@ -0,0 +1,765 @@ +-- +-- SHOP +-- + +hook.Add('octoinv.shop.order', 'octologs', function(ply, receiver, items, price, id) + + price = DarkRP.formatMoney(price) + local data = { + 'Order ID: #' .. id, + 'Total price: ' .. price, + } table.Add(data, items) + + octologs.createLog(octologs.CAT_SHOP) + :Add(octologs.ply(ply, {'wep'}), ' placed ', octologs.table('order #' .. id, data)) + :Add(' for ', octologs.ply(receiver), ', price: ', price) + :Save() + +end) + +hook.Add('octoinv.shop.delivery', 'octologs', function(ply, receiver, id, box) + + octologs.createLog(octologs.CAT_SHOP) + :Add('Order #' .. id, ' for ', octologs.ply(receiver, {'wep'}), ' arrived in ', octologs.ent(box)) + :Save() + +end) + +hook.Add('octoinv.shop.timeout', 'octologs', function(ply, receiver, id, box) + + octologs.createLog(octologs.CAT_SHOP) + :Add('Order #' .. id, ' for ', octologs.ply(receiver, {'wep'}), ' timed out') + :Save() + +end) + +-- +-- INVENTORY +-- + +hook.Add('DarkRP.payPlayer', 'octologs', function(ply, victim, amount) + + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' payed ', octologs.ply(victim), ' ', DarkRP.formatMoney(amount)) + :Save() + +end) + +hook.Add('atm.withdraw', 'octologs', function(ply, amount) + + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply, {'wep'}), ' withdrew ', DarkRP.formatMoney(amount), ' with ATM') + :Save() + +end) + +hook.Add('atm.deposit', 'octologs', function(ply, amount) + + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply, {'wep'}), ' deposited ', DarkRP.formatMoney(amount), ' with ATM') + :Save() + +end) + +local function formatCont(cont) + + if not cont then return 'invalid' end + local toReturn = {} + + local ent, owner = cont:GetParent().owner + if IsValid(ent) then + if ent:IsPlayer() then + owner = ent + elseif IsValid(ent.owner) and ent.owner:IsPlayer() then + owner = ent.owner + end + else + return 'invalid container' + end + + if owner then + toReturn[#toReturn + 1] = { owner:Name(), octologs.plyData(ent, {'job', 'loc', 'wep', 'hp', 'ar'}) } + toReturn[#toReturn + 1] = '\'s ' + end + + toReturn[#toReturn + 1] = { cont.name or 'cont', ent ~= owner and octologs.entData(ent, {'loc'}) or {} } + + return unpack(toReturn) + +end + +hook.Add('octoinv.plymoved', 'octologs', function(ply, item, from, to, amount) + + if item and IsValid(ply) and ply:IsPlayer() then + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' moved ', ('[%dx%s]'):format(amount or 1, item:GetData('name')), ': [') + :Add(formatCont(from)) + :Add(']➞[') + :Add(formatCont(to)) + :Add(']') + :Save() + end + +end) + +hook.Add('octoinv.dropped', 'octologs', function(cont, item, ent, ply) + + if item and IsValid(ply) and ply:IsPlayer() then + local entData = ent:GetNetVar('Item') + local amount = entData and isnumber(entData[2]) and entData[2] or 1 + local itemStr = ('[%dx%s]'):format(amount or 1, item:GetData('name')) + + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' dropped ', octologs.string(itemStr), ' from [') + :Add(formatCont(cont)) + :Add(']') + :Save() + end + +end) + +hook.Add('octoinv.pickup', 'octologs', function(ply, ent, item, amount) + + if item and IsValid(ply) and ply:IsPlayer() then + local itemStr = ('[%dx%s]'):format(amount or 1, item:GetData('name')) + local log = octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' picked up ', octologs.string(itemStr)) + if IsValid(ent.droppedBy) and ent.droppedBy:IsPlayer() then + log = log:Add(' dropped by ', octologs.ply(ent.droppedBy)) + end + log:Save() + end + +end) + +hook.Add('octoinv.crafted', 'octologs', function(ply, ent, bpID, cont) + + if item and IsValid(ply) and ply:IsPlayer() then + local bp = octoinv.getBlueprint(bpID) + local name = bp.name + if istable(bp.finish) and not name then + local item = bp.finish[1] + name = octoinv.getItemData('name', unpack(item)) + end + + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' crafted ', octologs.string(name), ' in [') + :Add(formatCont(cont)) + :Add(']') + :Save() + end + +end) + +hook.Add('octoinv.collect-evidences', 'octologs', function(ply, ent, items) + if IsValid(ply) and ply:IsPlayer() then + local from + if ent:GetClass() == 'octoinv_item' then + from = octologs.string('GROUND') + else + local bodyStr = 'Труп - ' .. ent:GetNetVar('Corpse.name') + from = {octologs.string(bodyStr), octologs.entData(ent, {'mdl'})} + end + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' collected evidence [', octologs.string(items), '] from ', from) + :Save() + end +end) + +hook.Add('octoinv.used', 'octologs', function(cont, item, ply) + + if item and IsValid(ply) and ply:IsPlayer() then + local itemStr = ('[%dx%s]'):format(amount or 1, item:GetData('name')) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' used ', octologs.string(itemStr), ' from ') + :Add(formatCont(cont)) + :Save() + end + +end) + +hook.Add('octoinv.stolen', 'octologs', function(cont, items, ply) + + if items and IsValid(ply) and ply:IsPlayer() then + local itemsText = {} + for k, v in pairs(items) do itemsText[#itemsText + 1] = ('%sx%s'):format(v, k) end + + local itemStr = table.concat(itemsText, ', ') + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' stole [', octologs.string(itemStr), '] from [') + :Add(formatCont(cont)) + :Add(']. Police online: ', tostring(#player.GetPolice())) + :Save() + end + +end) + +hook.Add('octoinv.search', 'octologs', function(ply, victim) + + if IsValid(ply) and IsValid(victim) then + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' searched ', octologs.ply(victim)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + end + +end) + +hook.Add('octoinv.search-corpse', 'octologs', function(ply, ent) + + if IsValid(ply) and IsValid(ent) then + local bodyStr = 'Труп - ' .. ent:GetNetVar('Corpse.name') + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' searched ', { octologs.string(bodyStr), octologs.entData(ent, {'mdl'}) }) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + end + +end) + +hook.Add('octoinv.storageSpawned', 'octologs', function(ply, ent) + + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' dropped ', { 'octoinv_storage', octologs.entData(ent, {'mdl'}) }) + :Save() + +end) + +hook.Add('dbg-weapons.holstered', 'octologs', function(cont, itemData, ply) + + if itemData and IsValid(ply) and ply:IsPlayer() then + local itemStr = ('[%s] to [%s]'):format(itemData.name, formatCont(cont)) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' holstered [', octologs.string(itemData.name), '] to [') + :Add(formatCont(cont)) + :Add(']') + :Save() + end + +end) + +hook.Add('onLockpickCompleted', 'octologs', function(ply, success, ent) + + local owner + if ent.GetPlayerOwner and ent:GetPlayerOwner() then + owner = ent:GetPlayerOwner() + local pl = player.GetBySteamID(owner) + owner = IsValid(pl) and pl or owner + elseif ent.owner and IsValid(ent.owner) then + owner = ent.owner + elseif ent.steamID then + local ply = player.GetBySteamID(ent.steamID) + owner = IsValid(ply) and ply or ent.steamID + end + + if isstring(owner) then + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' lockpicked ', octologs.string(owner), '\'s ', octologs.ent(ent), ': ', success and 'success' or 'fail') + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + elseif not istable(owner) and IsValid(owner) then + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' lockpicked ', octologs.ply(owner), '\'s ', octologs.ent(ent), ': ', success and 'success' or 'fail') + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + else + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' lockpicked ', octologs.ent(ent), ': ', success and 'success' or 'fail') + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + end + +end) + +hook.Add('dbg-hack.1stCommand', 'octologs', function(ply, ent) + + local owner + if ent.CPPIGetOwner and ent:CPPIGetOwner() then + owner = ent:CPPIGetOwner() + elseif ent.owner and IsValid(ent.owner) then + owner = ent.owner + elseif ent.steamID then + local ply = player.GetBySteamID(ent.steamID) + owner = IsValid(ply) and ply or ent.steamID + end + + if isstring(owner) then + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' started cracking ', octologs.string(owner), '\'s ', octologs.ent(ent)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + elseif IsValid(owner) then + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' started cracking ', octologs.ply(owner), '\'s ', octologs.ent(ent)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + else + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' started cracking ', octologs.ent(ent)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + end + +end) + +hook.Add('onKeypadHack', 'octologs', function(ply, success, ent) + + if not success then return end + + local owner + if ent.CPPIGetOwner and ent:CPPIGetOwner() then + owner = ent:CPPIGetOwner() + elseif ent.owner and IsValid(ent.owner) then + owner = ent.owner + elseif ent.steamID then + local ply = player.GetBySteamID(ent.steamID) + owner = IsValid(ply) and ply or ent.steamID + end + + if isstring(owner) then + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' cracked ', octologs.string(owner), '\'s ', octologs.ent(ent)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + elseif IsValid(owner) then + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' cracked ', octologs.ply(owner), '\'s ', octologs.ent(ent)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + else + octologs.createLog(octologs.CAT_LOCKPICK) + :Add(octologs.ply(ply, {'loc', 'wep', 'hp', 'ar'}), ' cracked ', octologs.ent(ent)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + end + +end) + +-- +-- DAMAGE +-- + +hook.Add('tazer.tazed', 'octologs', function(ply, victim) + + octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(ply), ' tazed ', octologs.ply(victim, {'loc'})) + :Save() + +end) + +hook.Add('dbg.scareStart', 'octologs', function(victim, ply, wep) + + octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(ply), ' scared ', octologs.ply(victim), ' with ', octologs.wep(wep)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + +end) + +hook.Add('dbg.scareInit', 'octologs', function(victim, ply, wep) + + octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(ply), ' aimed at ', octologs.ply(victim, {'loc'}), ' with ', octologs.wep(wep)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + +end) + +hook.Add('dbg.scareEnd', 'octologs', function(ply) + + octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(ply), ' is not scared now') + :Save() + +end) + +local function formatAttacker(attacker) + if isstring(attacker) then + return octologs.string(attacker) + elseif IsValid(attacker) then + if attacker:IsPlayer() then + return octologs.ply(attacker) + elseif attacker.cdData then + return octologs.veh(attacker) + else + return octologs.ent(attacker) + end + else + return octologs.string('world') + end +end + +local function formatWeapon(wep) + if isstring(wep) then + return octologs.string(wep) + elseif IsValid(wep) then + if wep:IsWeapon() then + return octologs.wep(wep) + elseif wep.cdData then + return octologs.veh(wep) + else + return octologs.ent(wep, {'loc'}) + end + else + return octologs.string('something') + end +end + +hook.Add('EntityDamage', 'octologs', function(victim, attacker, wep, dmgInfo) + + local dmg = math.Round(dmgInfo:GetDamage()) + local wepName + if IsValid(wep) then + wepName = wep:GetClass() + local info = weapons.GetStored(wepName) + if info then wepName = info.PrintName or info.Name or wepName end + end + + if victim:IsPlayer() and victim:Alive() then + local health = victim:Health() - dmg + + victim.lastAttacker = IsValid(attacker) and attacker:IsPlayer() and attacker:Name() + if victim.attackedBy then victim.lastAttacker = victim.attackedBy end + victim.lastWeapon = victim.weaponUsed or wepName + victim.weaponUsed = nil + victim.attackedBy = nil + victim.lastDMGT = dmgInfo:GetDamageType() + + local log + if health <= 0 and (not wep or not wep:GetClass():find('gmod_sent_vehicle_fphysics')) then + local inv = victim:GetInventory() + local items = {} + if inv then + for _, cont in pairs(inv.conts) do + items[#items + 1] = cont.name + for _, item in pairs(cont.items) do + items[#items + 1] = (' %d x %s'):format(item:GetData('amount'), item:GetData('name')) + end + end + end + log = octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(victim), octologs.table(' (inventory)', items), ' was killed') + else + log = octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(victim), ' was hurt: ', octologs.string(dmg .. 'HP')) + end + + if not attacker then + attacker = wep + wep = nil + end + + if attacker then log:Add(' by ', formatAttacker(attacker)) end + if wep then log:Add(' with '):Add(formatWeapon(wep)) end + + log:Add('. Police online: ', tostring(#player.GetPolice())) + log:Save() + elseif IsValid(attacker) and victim:GetClass():find('gmod_sent_vehicle_fphysics') then + local log = octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.veh(victim, exclude)) + :Add(' was damaged: ', octologs.string(dmg .. 'HP')) + + if not attacker then + attacker = wep + wep = nil + end + + if attacker then log:Add(' by ', formatAttacker(attacker)) end + if wep then log:Add(' with ', formatWeapon(wep)) end + + log:Add('. Police online: ', tostring(#player.GetPolice())) + log:Save() + end + +end) + +hook.Add('dbg-karma.changed', 'octologs', function(ply, new, old) + + if new >= old then return end + octologs.createLog(octologs.CAT_KARMA) + :Add(octologs.ply(ply), '\'s karma: ', octologs.string(old .. ' ➞ ' .. new)) + :Save() + +end) + +hook.Add('PlayerDeath', 'octologs', function(victim, _, attacker) + + if not IsValid(victim) or not victim:IsPlayer() then return end + if not IsValid(attacker) or not attacker:IsPlayer() then return end + + if attacker == victim then + octologs.createLog(octologs.CAT_DAMAGE) + :Add(octologs.ply(victim), ' suicided') + :Save() + end + +end) + +hook.Add('OnHandcuffed', 'octologs', function(cop, ply, cuff) + + octologs.createLog(octologs.CAT_CUFF) + :Add(octologs.ply(ply), ' was cuffed by ', octologs.ply(cop, {'loc'}), ' with ', octologs.string(cuff.CuffType)) + :Add('. Police online: ', tostring(#player.GetPolice())) + :Save() + +end) + +hook.Add('OnHandcuffBreak', 'octologs', function(ply, cuff, mate) + + local log = octologs.createLog(octologs.CAT_CUFF) + :Add(octologs.ply(ply), ' was uncuffed from ', octologs.string(cuff.CuffType or 'unknown')) + if IsValid(mate) then log:Add(' by ', octologs.ply(mate)) end + log:Add('. Police online: ', tostring(#player.GetPolice())) + log:Save() + +end) + +hook.Add('dbg.evacuation', 'octologs', function(car, cop, owner) + octologs.createLog(octologs.CAT_OTHER) + :Add(octologs.ply(ply), ' requested evacuation for ', octologs.ply(owner), '\'s ', octologs.ent(car)) + :Save() +end) + +hook.Add('dbg-police.call', 'octologs', function(ply, nick, text, sentPos) + local log = octologs.createLog(octologs.CAT_OTHER) + :Add(octologs.ply(ply), ' called police under nickname "', octologs.string(nick), '"') + if sentPos then + log = log:Add(' and sent their pos to cops.') + else log = log:Add('.') end + log:Add(' Message text: ', text):Save() +end) + +-- +-- VEHICLES +-- + +timer.Simple(0, function() + local doNotLog = octolib.array.toKeys {'gmod_sent_vehicle_fphysics_attachment', 'gmod_sent_vehicle_fphysics_wheel', 'info_particle_system', 'prop_physics', 'prop_vehicle_prisoner_pod'} + hook.Add('PlayerSpawnedVehicle', 'octologs', function(ply, veh) + if doNotLog[veh:GetClass()] then return end + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned vehicle: ', octologs.string(veh:GetClass())) + :Save() + end) +end) + +hook.Add('car-dealer.bought', 'octologs', function(class, ply, price) + local cdData = carDealer.vehicles[class] + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'wep', 'hp', 'ar'}), ' bought ') + :Add(octologs.string(cdData and cdData.name or class or 'vehicle')) + :Add(' for ', DarkRP.formatMoney(price or 0)) + :Save() +end) + +local function percent(cur, max) + return math.Round(cur / (max or 1) * 100) .. '%' +end + +hook.Add('car-dealer.sold', 'octologs', function(veh, ply, price) + local data = { + 'HP: ' .. percent(veh.data.health or 1), + 'Fuel: ' .. percent(veh.data.fuel or 1), + } + + if veh.data.rims then + local mdl = veh.data.rims[1] + local rimsData = simfphys.rims[mdl] + if rimsData then + data[#data + 1] = rimsData.name + else + data[#data + 1] = mdl + end + end + + if veh.data.atts then + for _, att in ipairs(veh.data.atts) do + data[#data + 1] = att.name + end + end + + if veh.data.bg then + data[#data + 1] = util.TableToJSON(veh.data.bg) + end + + local cdData = carDealer.vehicles[veh.class or ''] + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'wep', 'hp', 'ar'}), ' sold ') + :Add(octologs.table(cdData and cdData.name or class or 'vehicle', data)) + :Add(' for ', DarkRP.formatMoney(price or 0)) + :Save() +end) + +hook.Add('car-dealer.spawnedOwned', 'octologs', function(veh, ply) + timer.Simple(1.2, function() + if not IsValid(veh) then return end + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'wep', 'hp', 'ar'}), ' spawned ') + :Add(octologs.veh(veh)) + :Save() + end) +end) + +hook.Add('car-dealer.spawnedDeposit', 'octologs', function(veh, ply) + timer.Simple(1.2, function() + if not IsValid(veh) then return end + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'wep', 'hp', 'ar'}), ' spawned ') + :Add(octologs.veh(veh)) + :Add(' for ', DarkRP.formatMoney(veh.deposit or 0)) + :Save() + end) +end) + +hook.Add('car-dealer.stored', 'octologs', function(veh, ply) + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'wep', 'hp', 'ar'}), ' stored ') + :Add(octologs.veh(veh)) + :Save() +end) + +hook.Add('car-dealer.returnedDeposit', 'octologs', function(veh, ply, amount) + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'wep', 'hp', 'ar'}), ' returned ') + :Add(DarkRP.formatMoney(amount or 0), ' for ') + :Add(octologs.veh(veh)) + :Save() +end) + +hook.Add('car-dealer.resetPlate', 'octologs', function(ply, ent, plate, returned) + local owner = ent:CPPIGetOwner() + octologs.createLog(octologs.CAT_VEHICLE) + :Add(octologs.ply(ply, {'loc', 'job', 'wep', 'hp', 'ar'}), ' reset ') + :Add(octologs.ply(owner, {'loc', 'job', 'wep', 'hp', 'ar'})) + :Add('\'s car plate (was ', octologs.string(plate), '). ') + :Add('Item was', returned and '' or 'n\'t', ' returned.') + :Save() +end) + +hook.Add('dbg-cars.itemBurned', 'octologs', function(veh, item, amount) + + if not item then return end + + local itemStr = ('[%dx%s]'):format(amount or 1, item:GetData('name')) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.string(itemStr), ' burned in ') + :Add(octologs.veh(veh)) + :Save() + +end) + +-- +-- MASK & CLOTHES +-- + +hook.Add('dbg-masks.mask', 'octologs', function(ply, maskID, justUpdate) + if justUpdate then return end + local mask = CFG.masks[maskID] + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' masked ', octologs.table(mask.name, { id = maskID })) + :Save() +end) + +hook.Add('dbg-masks.unmask', 'octologs', function(ply, maskName, maskID) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' unmasked ', octologs.table(maskName, { id = maskID })) + :Save() +end) + +hook.Add('dbg-clothes.update', 'octologs', function(ply, clothes, old) + if not (clothes and old) then return end + local log = octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' put ', clothes and 'on' or 'off', ' ') + if clothes then + log = log:Add(octologs.table(clothes.name, clothes, true)) + else log = log:Add(octologs.table(old.name, old, true)) end + log:Save() +end) + +-- +-- OTHER +-- + +hook.Add('dbg-camera.trigger', 'octologs', function(cam, vict, comment, sawAtt, att) + local log = octologs.createLog(octologs.CAT_OTHER) + :Add({cam.camName, {loc = octologs.location(cam)}}, ' ', comment, ' (triggered at ', octologs.ply(vict), ')') + if sawAtt ~= nil then + log = log:Add(' (could', sawAtt and '' or ' not', ' see the attacker, attacker was ', octologs.ply(att), ')') + end + log:Add('. Police online: ', tostring(#player.GetPolice())) + log:Save() +end) + +hook.Add('dbg-camera.damage', 'octologs', function(cam, att) + local log = octologs.createLog(octologs.CAT_OTHER) + :Add({cam.camName, {loc = octologs.location(cam)}}, ' was damaged') + if IsValid(att) then + log = log:Add(' by ') + if att:IsPlayer() then + log = log:Add(octologs.ply(att)) + else + log = log:Add(octologs.ent(att)) + end + end + log:Add('. Police online: ', tostring(#player.GetPolice())) + log:Save() +end) + +hook.Add('dbg-camera.destroy', 'octologs', function(cam) + octologs.createLog(octologs.CAT_OTHER) + :Add({cam.camName, {loc = octologs.location(cam)}}, ' was destroyed.') + :Save() +end) + +hook.Add('PostPlayerSay', 'octologs', function(ply, text) + octologs.createLog() + :Add(octologs.ply(ply, {'loc', 'wep'}), ': ', octologs.string(text)) + :Save() +end) + +hook.Add('PostConsoleSay', 'octologs', function(text) + octologs.createLog() + :Add('invalid player', ': ', octologs.string(text)) + :Save() +end) + +hook.Add('octochat.commandExecuted', 'octologs', function(ply, text, cmdData, succ, msg) + if not cmdData.log then return end + octologs.createLog() + :Add(octologs.ply(ply, {'loc', 'wep'})) + :Add(' ', succ and 'successfully' or octologs.table('unsuccessfully', {msg}), ' ') + :Add('executed chat command: ', octologs.string(text)) + :Save() +end) + +hook.Add('dbg-trash.add', 'octologs', function(ent, ply, item) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' put ', ('[%dx%s]'):format(item:GetData('amount'), item:GetData('name'))) + :Add(' in ', octologs.ent(ent)) + :Save() +end) + +hook.Add('dbg-trash.empty', 'octologs', function(ent, ply) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' emptied ', octologs.ent(ent)) + :Save() +end) + +hook.Add('dbg-trash.loot', 'octologs', function(ent, ply, item) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' found ', octoinv.itemStr(item), ' in ', octologs.ent(ent)) + :Save() +end) + +hook.Add('dbg-trash.searchedEvidence', 'octologs', function(ent, ply, items) + octologs.createLog(octologs.CAT_INVENTORY) + :Add(octologs.ply(ply), ' searched ', octologs.ent(ent), ' for evidence and found ', octologs.table(#items .. ' items', items)) + :Save() +end) + +hook.Add('gmpanel.moved', 'octologs', function(ply, target, pos, ang) + octologs.createLog(octologs.CAT_OTHER) + :Add(octologs.ply(ply), ' moved ', octologs.ply(target), ' via gmpanel') + :Save() +end) diff --git a/garrysmod/addons/admin-logs/lua/octologs/loggers/gmod.lua b/garrysmod/addons/admin-logs/lua/octologs/loggers/gmod.lua new file mode 100644 index 0000000..dc4d72b --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/loggers/gmod.lua @@ -0,0 +1,162 @@ +gameevent.Listen('player_connect') +gameevent.Listen('player_disconnect') + +hook.Add('player_connect', 'octologs', function(data) + + if (tonumber(data.bot) == 1) then return end + + local sID + if octolib.string.isSteamID(data.networkid) then + sID = data.networkid + elseif (data.networkid:find('^7656119%d+$')) then + sID = util.SteamIDFrom64(data.networkid) + else + return + end + + octologs.createLog(octologs.CAT_OTHER) + :Add('Player connected: ', { data.name, { ply = sID }}) + :Save() + +end) + +hook.Add('PlayerInitialSpawn', 'octologs', function(ply) + + ply.nextSpawnLog = CurTime() + 3 + octologs.createLog(octologs.CAT_OTHER) + :Add(octologs.ply(ply, {'wep', 'job'}), ' finished spawning') + :Save() + +end) + +hook.Add('player_disconnect', 'octologs', function(data) + + local sID + if octolib.string.isSteamID(data.networkid) then + sID = data.networkid + elseif (data.networkid:find('^7656119%d+$')) then + sID = util.SteamIDFrom64(data.networkid) + else + return + end + + local ply = player.GetBySteamID(sID) + if IsValid(ply) then + octologs.createLog(octologs.CAT_OTHER) + :Add('Player disconnected: ', octologs.ply(ply), ', ', octologs.string(data.reason)) + :Save() + else + octologs.createLog(octologs.CAT_OTHER) + :Add('Player disconnected: ', { data.name, { ply = sID }}, ', ', octologs.string(data.reason)) + :Save() + end + +end) + +hook.Add('PlayerSpawn', 'octologs', function(ply) + + if (ply.nextSpawnLog or 0) < CurTime() then return end + ply.nextSpawnLog = CurTime() + 3 + + octologs.createLog(octologs.CAT_OTHER) + :Add(octologs.ply(ply, {'wep'}), ' spawned') + :Save() + +end) + +hook.Add('OnPlayerChangedTeam', 'octologs', function(ply,before,after) + + octologs.createLog(octologs.CAT_OTHER) + :Add(octologs.ply(ply), ' changed team: ', octologs.string(team.GetName(before)), ' ➞ ', octologs.string(team.GetName(after))) + :Save() + +end) + + +-- +-- SANDBOX +-- + +hook.Add('PlayerSpawnedEffect', 'octologs', function(ply, mdl, ent) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned effect: ', octologs.ent(ent)) + :Save() + +end) + +hook.Add('PlayerSpawnedProp', 'octologs', function(ply, mdl, ent) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned prop: ', octologs.ent(ent)) + :Save() + +end) + +hook.Add('PlayerSpawnedRagdoll', 'octologs', function(ply, mdl, ent) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned ragdoll: ', octologs.ent(ent)) + :Save() + +end) + +hook.Add('PlayerSpawnedNPC', 'octologs', function(ply, ent) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned NPC: ', octologs.ent(ent)) + :Save() + +end) + +hook.Add('PlayerSpawnedSENT', 'octologs', function(ply, ent) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned entity: ', octologs.ent(ent)) + :Save() + +end) + +hook.Add('PlayerSpawnedSWEP', 'octologs', function(ply, wep) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned weapon: ', octologs.ent(wep)) + :Save() + +end) + +hook.Add('PlayerSpawnedVehicle', 'octologs', function(ply, veh) + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' spawned vehicle: ', octologs.ent(veh)) + :Save() + +end) + +local dontLogTools = octolib.array.toKeys({ 'remover','precision','material','submaterial','weld','rope','colour','advmat','shadowremover', 'imgscreen' }) +hook.Add('CanTool', 'octologs', function(ply, tr, tool) + + if dontLogTools[tool] then return end + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' used tool ', octologs.string(tool)) + :Save() + +end) + +hook.Add('OnEntityCreated', 'dbg-tools.imgscreen', function(ent) + timer.Simple(0, function() + if not IsValid(ent) or not ent.imgURL then return end + local ply = ent:CPPIGetOwner() + if not IsValid(ply) then return end + local info = { + ['Размеры'] = ('%s x %s'):format(ent.imgW, ent.imgH), + ['Ссылка'] = ent.imgURL, + ['Цвет'] = ent.imgColor, + } + + octologs.createLog(octologs.CAT_BUILD) + :Add(octologs.ply(ply, {'wep','loc','job'}), ' used tool ', octologs.table('imgscreen', info, true)) + :Save() + end) +end) \ No newline at end of file diff --git a/garrysmod/addons/admin-logs/lua/octologs/sv_helpers.lua b/garrysmod/addons/admin-logs/lua/octologs/sv_helpers.lua new file mode 100644 index 0000000..400303a --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/sv_helpers.lua @@ -0,0 +1,160 @@ +-- save on string length and floor fields +function octologs.location(ent, arg2) + + local pos, ang + if isvector(ent) then + pos, ang = ent, arg2 + elseif ent:IsPlayer() then + pos = ent:EyePos() + ang = ent:EyeAngles() + else + pos = ent:GetPos() + ang = ent:GetAngles() + end + + pos.x = math.floor(pos.x) + pos.y = math.floor(pos.y) + pos.z = math.floor(pos.z) + ang.p = math.floor(ang.p) + ang.y = math.floor(ang.y) + ang.r = math.floor(ang.r) + + return { pos, ang } + +end + +function octologs.exclude(tbl, fields) + + if fields then + for i, v in ipairs(fields) do tbl[v] = nil end + end + return tbl + +end + +function octologs.plyData(ply, exclude) + + return octologs.exclude({ + ply = ply:SteamID(), + hp = ply:Health() .. '%', + ar = ply:Armor() .. '%', + loc = octologs.location(ply), + job = team.GetName(ply:Team()), + wep = octologs.wepName(ply:GetActiveWeapon()), + }, exclude) + +end + +function octologs.ply(ply, exclude) + + if not IsValid(ply) then return 'invalid player' end + return { ply:Name(), octologs.plyData(ply, exclude) } + +end + +function octologs.entData(ent, exclude) + + return octologs.exclude({ + mdl = ent:GetModel(), + loc = octologs.location(ent), + }, exclude) + +end + +function octologs.ent(ent, exclude) + + if not IsValid(ent) then return 'invalid entity' end + return { ent:GetClass(), octologs.entData(ent, exclude) } + +end + +local function percent(cur, max) + return math.Round(cur / max * 100) .. '%' +end + +function octologs.veh(ent, exclude) + + if not IsValid(ent) then return 'invalid vehicle' end + + local toReturn = {} + + local owner = ent:CPPIGetOwner() + if IsValid(owner) then + toReturn[#toReturn + 1] = octologs.ply(owner, {'job', 'loc', 'hp', 'wep'}) + toReturn[#toReturn + 1] = '\'s ' + end + + local cdData = ent.cdData + toReturn[#toReturn + 1] = { cdData and (cdData.name or cdData.class) or ent:GetClass(), octologs.exclude({ + mdl = ent:GetModel(), + loc = octologs.location(ent), + fuel = ent.GetFuel and percent(ent:GetFuel(), ent:GetMaxFuel()) or nil, + hp = ent.GetCurHealth and percent(ent:GetCurHealth(), ent:GetMaxHealth()) or nil, + }, exclude) } + + return unpack(toReturn) + +end + +function octologs.wepName(wep) + + if not isentity(wep) or not IsValid(wep) then return 'None' end + + local class = wep:GetClass() + local tbl = wep.GetTable and wep:GetTable() + if istable(tbl) then + return tbl.PrintName or tbl.Name or class + end + + return class + +end + +function octologs.wep(wep) + + if not IsValid(wep) or not wep:IsWeapon() then return 'invalid weapon' end + return octologs.wepName(wep) + +end + +function octologs.string(str) + + return str + +end + +function octologs.table(name, t, withKeys) + + if not istable(t) then return 'invalid table' end + + local data = {} + if withKeys then + for k, v in pairs(t) do + data[#data + 1] = ('%s: %s'):format(k, v) + end + else + for k, v in pairs(t) do + data[#data + 1] = tostring(v) + end + end + + return { name, { + tbl = data + }} + +end + +netstream.Hook('octologs.goto', function(ply, pos, ang) + + if not ply:query('DBG: Телепорт по команде') then + ply:Notify('warning', 'Нет доступа') + return + end + + ply.sg_LastPosition = ply:GetPos() + ply.sg_LastAngles = ply:GetAngles() + ply:SetPos(pos - Vector(0, 0, 64)) + ply:SetEyeAngles(ang) + ply:Notify('Чтобы вернуться обратно, выполни команду "~return"') + +end) diff --git a/garrysmod/addons/admin-logs/lua/octologs/sv_log.lua b/garrysmod/addons/admin-logs/lua/octologs/sv_log.lua new file mode 100644 index 0000000..e870940 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/sv_log.lua @@ -0,0 +1,28 @@ +local Log = {} +Log.__index = Log + +function octologs.createLog(category) + + local log = {} + log.parts = {} + log.category = category + return setmetatable(log, Log) + +end + +function Log:Add(...) + + local parts = self.parts + for i, v in ipairs({...}) do + parts[#parts + 1] = v + end + + return self + +end + +function Log:Save() + + octologs.log(self.parts, self.category) + +end diff --git a/garrysmod/addons/admin-logs/lua/octologs/sv_octologs.lua b/garrysmod/addons/admin-logs/lua/octologs/sv_octologs.lua new file mode 100644 index 0000000..6970013 --- /dev/null +++ b/garrysmod/addons/admin-logs/lua/octologs/sv_octologs.lua @@ -0,0 +1,72 @@ +octologs = octologs or {} +octologs.logsQueue = octologs.logsQueue or {} +octologs.logsFailed = {} +octologs.lastLogTime = 0 + +-------------------------- +-- CATEGORY DEFINITIONS -- +octologs.CAT_OTHER = 0 +octologs.CAT_ADMIN = 1 +octologs.CAT_DONATE = 2 +octologs.CAT_BUILD = 3 +octologs.CAT_DAMAGE = 4 +octologs.CAT_INVENTORY = 5 +octologs.CAT_SHOP = 6 +octologs.CAT_POLICE = 7 +octologs.CAT_PROPERTY = 8 +octologs.CAT_LOCKPICK = 9 +octologs.CAT_CUFF = 10 +octologs.CAT_KARMA = 11 +octologs.CAT_GMPANEL = 12 +octologs.CAT_VEHICLE = 13 +-------------------------- + +octologs.api = octolib.api({ + url = 'https://octothorp.team/logs/api', + headers = { ['Authorization'] = CFG.keys.logs }, +}) + +function octologs.log(message, category) + + category = category or octologs.CAT_OTHER + local log = { os.time(), category, message } + if CurTime() - octologs.lastLogTime < 0.2 and util.TableToJSON(octologs.lastLog) == util.TableToJSON(log) then return end + + octologs.logsQueue[#octologs.logsQueue + 1] = log + octologs.lastLog = log + octologs.lastLogTime = CurTime() + +end + +local failMode = false +function octologs.sendToApi() + + if #octologs.logsQueue < 1 then return end + + local toSend = octologs.logsQueue + octologs.logsQueue = {} + + octologs.api:post('/logs', toSend) + :Then(function(res) + if failMode then + failMode = false + octolib.msg('Logs API connection restored') + end + end) + :Catch(function(err) + octolib.msg('Failed to send logs, trying to reconnect in 60 seconds') + failMode = true + + for i, log in ipairs(octologs.logsQueue) do + toSend[#toSend + 1] = log + end + octologs.logsQueue = toSend + + timer.Remove('octologs.sendToApi') + timer.Simple(60, function() + timer.Create('octologs.sendToApi', 5, 0, octologs.sendToApi) + end) + end) + +end +timer.Create('octologs.sendToApi', 5, 0, octologs.sendToApi) diff --git a/garrysmod/addons/admin-sg/lua/autorun/sg_load.lua b/garrysmod/addons/admin-sg/lua/autorun/sg_load.lua new file mode 100644 index 0000000..5fa7530 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/autorun/sg_load.lua @@ -0,0 +1,147 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +AddCSLuaFile() + +if (SERVER) then + include("sg_server.lua") +elseif (CLIENT) then + include("sg_client.lua") +end + +if (serverguard) then + -- PLEASE don't set SG_DEV unless you know what you're doing!!!! + -- This is VERY VERY HACKY and COULD BREAK EVERYTHING!!!! + if (SG_DEV and !serverguard_dev) then + if (!game.IsDedicated()) then + if (SERVER) then + util.AddNetworkString("sg_dev_reload"); + + net.Receive("sg_dev_reload", function(length, player) + if (!player:IsListenServerHost()) then + return; + end; + + serverguard_dev:PerformRefresh(); + end); + end; + end; + + serverguard_dev = { + sgtable = {}, + buffer = {} + }; + + function serverguard_dev:Attach() + self.sgtable = table.Copy(serverguard); + local _serverguard_meta = {}; + + serverguard = {}; + + function _serverguard_meta:__index(key) + return serverguard_dev.sgtable[key]; + end; + + function _serverguard_meta:__newindex(key, value) + serverguard_dev:CheckAutoRefresh(3); + + serverguard_dev.sgtable[key] = value; + end; + + setmetatable(serverguard, _serverguard_meta); + print("[SG-DEV] Attached to serverguard table."); + end; + + function serverguard_dev:Detatch() + setmetatable(serverguard, {}); + + -- Removing stuff that will linger + if (CLIENT) then + if (self.sgtable.GetMenuPanel()) then + self.sgtable.GetMenuPanel():Remove(); + end; + end; + + self.sgtable = nil; + self.buffer = nil; + serverguard = nil; + + collectgarbage("collect"); + + self.sgtable = {}; + self.buffer = {}; + + print("[SG-DEV] Detatched from serverguard table."); + end; + + function serverguard_dev:PerformRefresh() + --RunConsoleCommand("changelevel", game.GetMap()); + print("[SG-DEV] Change detected, starting reload..."); + self:Detatch(); + + if (SERVER) then + include("sg_server.lua"); + Msg("ServerGuard (SERVER) Loaded.\n") + elseif (CLIENT) then + include("sg_client.lua"); + Msg("ServerGuard (CLIENT) Loaded.\n") + end; + + if (CLIENT) then + net.Start("sg_dev_reload"); + net.WriteBit(1); + net.SendToServer(); + end; + + if (SERVER) then + hook.Add("serverguard.Initialize", "sg_dev.Initialize", function() + self:Attach(); + print("[SG-DEV] Reload complete!"); + + -- Doing a hook.Call here is BAD. + hook.Call("InitPostEntity", nil); + hook.Remove("serverguard.Initialize", "sg_dev.Initialize"); + end); + + serverguard.Initialize(); + + local host = util.GetListenServerHost(); + + if (host ~= NULL) then + hook.Call("PlayerInitialSpawn", nil, host); + end; + else + hook.Add("serverguard.LoadPlayerData", "sg_dev.LoadPlayerData", function() + self:Attach(); + print("[SG-DEV] Reload complete!"); + + hook.Remove("serverguard.LoadPlayerData", "sg_dev.LoadPlayerData"); + end); + end; + end; + + function serverguard_dev:CheckAutoRefresh(stackLevel) + stackLevel = stackLevel or 2; + local info = debug.getinfo(stackLevel, "S"); + + if (!self.buffer[info.short_src]) then + self.buffer[info.short_src] = true; + else + self:PerformRefresh(); + end; + end; + + serverguard_dev:Attach(); + end; + + if (SERVER) then + Msg("ServerGuard (SERVER) Loaded.\n") + elseif (CLIENT) then + Msg("ServerGuard (CLIENT) Loaded.\n") + end +else + Msg("!! URGENT !!\nServerGuard failed to load!\n") +end \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/LICENSE.txt b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/LICENSE.txt new file mode 100644 index 0000000..79830cb --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/LICENSE.txt @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2016 MeepDarknessMeep + +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. diff --git a/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/read.lua b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/read.lua new file mode 100644 index 0000000..d46aaa3 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/read.lua @@ -0,0 +1,204 @@ +--[[local config = include "./config.lua" +if (SERVER) then + if (config and config.netstreamcompat) then + util.AddNetworkString "BITBUF_NETSTREAM" + end + AddCSLuaFile() +end +]] +if (SERVER) then + AddCSLuaFile() +end +local function bad(val, argn, name, err) + if (not val) then + error(("bad argument #%i to '%s' (%s)"):format(argn, name, err), 2) + end +end + +local mt = { + __index = function(self, k) + return getmetatable(self)[k] + end, + __call = function(self, data) + return setmetatable({}, getmetatable(self)):Init(data) + end +} + +local function nop() end + +local bb_read = setmetatable({ + SetupQueuedCallback = function(name, cb, filter) + bad(isfunction(cb), 2, "SetupQueuedCallback", "expected function") + local states = {} + net.Receive(name, function(len, cl) + states[cl] = states[cl] or {} + local ID = net.ReadUInt(32) + len = len - 32 + local state = states[cl][ID] or (function() + len = len - 32 + local datalen = net.ReadUInt(32) + ;(filter or nop)(cl, datalen) + return {len = datalen, current = 0} + end)() + + table.insert(state, net.ReadData(len / 8)) + + states[cl][ID] = state + state.current = state.current + len / 8 + if (state.current >= state.len) then + states[cl][ID] = nil + cb(table.concat(state), cl) + end + + + end) + end +}, mt) + +function mt:Init(data) + bad(isstring(data), 1, "Init", "string expected") + self:SetData(data) + return self +end + +function mt:SetData(data) + self.RawData = {} + data:gsub("().", function(match) + self.RawData[match] = data:byte(match, match) + end) + self.Bits = 0 + self.Bytes = 1 + return self +end + +function mt:Byte() + return self:UInt(8) +end + +function mt:Data(len) + local dat = {} + for i = 1, len do + dat[i] = string.char(self:Byte()) + end + + return table.concat(dat) +end + +function mt:String() + local b = self:Byte() + local dat = { n = 0 } + while (b ~= 0) do + dat[dat.n + 1] = string.char(b) + dat.n = dat.n + 1 + b = self:Byte() + end + + return table.concat(dat) +end + +function mt:Int(totalbits) + bad(isnumber(totalbits), 1, "Int", "number expected") + bad(totalbits <= 32 and totalbits > 0, 1, "Int", "a number 1 through 32 expected") + + local retnum = 0 + + for i = 1, totalbits do + local bitread = self.Bits % 8 + + local byte = self.RawData[self.Bytes] + + -- retnum |= ((byte & (1 << bitread)) >> bitread) << (totalbits - i) + local bitmask = bit.lshift(1, 7-bitread) + local bitn = bit.rshift(bit.band(byte, bitmask), 7 - bitread) + retnum = bit.bor(retnum, bit.lshift(bitn, totalbits - i)) + + self.Bits = self.Bits + 1 + if (self.Bits % 8 == 0) then + self.Bytes = self.Bytes + 1 + end + end + + if (retnum > 0 and bit.band(retnum, 2^(totalbits - 1)) ~= 0) then + retnum = retnum - 2^totalbits + end + + return retnum +end + +function mt:UInt(bits) + local r = self:Int(bits) + if (r < 0) then + r = r + 2^bits + end + return r +end + +--[[ THE NEXT TWO FUNCTIONS ARE GIVEN RIGHTS TO ME FOR USAGE UNDER MIT LICENSE BY THE CREATOR ]] + +local function UInt32sToDouble (low, high) + local negative = false + + if high >= 0x80000000 then + negative = true + high = high - 0x80000000 + end + + local biasedExponent = bit.rshift (bit.band (high, 0x7FF00000), 20) + local mantissa = (bit.band (high, 0x000FFFFF) * 4294967296 + low) / 2 ^ 52 + + local f + if biasedExponent == 0x0000 then + f = mantissa == 0 and 0 or math.ldexp (mantissa, -1022) + elseif biasedExponent == 0x07FF then + f = mantissa == 0 and math.huge or (math.huge - math.huge) + else + f = math.ldexp (1 + mantissa, biasedExponent - 1023) + end + + return negative and -f or f +end + +local function UInt32ToFloat (n) + -- 1 sign bit + -- 8 biased exponent bits (bias of 127, biased value of 0 if 0 or denormal) + -- 23 mantissa bits (implicit 1, unless biased exponent is 0) + + local negative = false + + if n >= 0x80000000 then + negative = true + n = n - 0x80000000 + end + + local biasedExponent = bit.rshift (bit.band (n, 0x7F800000), 23) + local mantissa = bit.band (n, 0x007FFFFF) / (2 ^ 23) + + local f + if biasedExponent == 0x00 then + f = mantissa == 0 and 0 or math.ldexp (mantissa, -126) + elseif biasedExponent == 0xFF then + f = mantissa == 0 and math.huge or (math.huge - math.huge) + else + f = math.ldexp (1 + mantissa, biasedExponent - 127) + end + + return negative and -f or f +end + +function mt:Float() + return UInt32ToFloat(self:UInt(32)) +end + +function mt:Double(dbl) + local low = self:UInt(32) + local high = self:UInt(32) + return UInt32sToDouble(low, high) +end + +function mt:Write() + + net.Start(self.Name) + +end + +return bb_read \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/table.lua b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/table.lua new file mode 100644 index 0000000..8182413 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/table.lua @@ -0,0 +1,585 @@ + +-- +-- Read/Write an entity to the stream +-- CClientEntityList::GetMaxEntityIndex() returns 8096 +-- +MAX_ENTITIES = 8096 + +if (SERVER) then + AddCSLuaFile() +end +local function WriteEntity( buf, e ) + if ( IsValid( e ) or game.GetWorld() == e) then + + buf:UInt(1, 1) + buf:UInt( e:EntIndex(), 13 ) + + return + + end + + buf:UInt(0, 1) -- NULL +end + +local function ReadEntity(buf) + if ( buf:UInt(1) == 1 ) then -- non null + return Entity( buf:UInt( 13 ) ) + end + + return NULL +end + +-- +-- Read/Write a color to/from the stream +-- +local function WriteColor( buf, col ) + + assert( IsColor( col ), "WriteColor: color expected, got ".. type( col ) ) + + buf:UInt( col.r, 8 ) + buf:UInt( col.g, 8 ) + buf:UInt( col.b, 8 ) + buf:UInt( col.a, 8 ) + +end + +local function ReadColor(buf) + + return Color( + buf:UInt( 8 ), buf:UInt( 8 ), + buf:UInt( 8 ), buf:UInt( 8 ) + ) + +end + +-- +-- Sizes for ints to send +-- +local TYPE_SIZE = 4 +local UINTV_SIZE = 5 + +local Char2HexaLookup, Hexa2CharLookup = {}, {} + +-- +-- Generate Hexa Lookups +-- Hexa is a 6-bit English character encoding +-- +local HexaRanges = { + { "a", "z" }, -- 26 + { "A", "Z" }, -- 52 + { "0", "9" }, -- 62 + { "_", "_" }, -- 63 +} + +local offset = 1 + +for k, v in ipairs( HexaRanges ) do + local starts, ends = v[ 1 ]:byte(), v[ 2 ]:byte() + + for char = starts, ends do + local hexa = char - starts + offset + + Char2HexaLookup[ char ] = hexa + Hexa2CharLookup[ hexa ] = string.char( char ) + end + + offset = offset + 1 + ends - starts +end + + +-- +-- Converts an ASCII character in to a Hexa Character +-- +local function CharToHexa( c ) + return Char2HexaLookup[ c ] +end + +-- +-- Converts a Hexa character in to an ASCII character +-- +local function HexaToChar( h ) + return Hexa2CharLookup[ h ] +end + +-- +-- Returns true if the string can be represented in 7-Bit ASCII +-- Null bytes are not allowed as they are used for termination +-- +local function Is7BitString( str ) + return str:find( "[\x80-\xFF%z]" ) == nil +end + +-- +-- Returns true if the string can be represented in Hexa +-- +local function IsHexaString( str ) + return str:find( "[^a-zA-Z0-9_]" ) == nil +end + +-- +-- Returns true if the argument is NaN ( can also be interpreted as 0/0 ) +-- +local function IsNaN( x ) + return x ~= x +end + +-- +-- An imaginary NaN table for caching in writing.table +-- +local NaN = {} + +-- +-- This exists because you can't make a table index NaN +-- We need to do this so we can cache it in our references table +-- +local function IndexSafe( x ) + if ( IsNaN( x ) ) then return NaN end + return x +end + +local reading, writing + +-- +-- Gets the type of way we are going to send the data +-- Not all of these exist in reality +-- We are only going to add 16 types ( 0-15 ) since that's +-- the max we can fit into 4 bits +-- +local function SendType( x ) + + if ( x == 1 or x == 0 ) then return "bit" end + + local t = type( x ) + + -- + -- check if a number has no decimal places + -- and is able to be sent in an int + -- + + if ( t == "number" and x % 1 == 0 and x >= -0x7FFFFFFF and x <= 0xFFFFFFFF ) then + + -- test if we can fit it in a single iteration with uintv + if ( x < bit.lshift( 1, UINTV_SIZE ) and x >= 0 ) then + return "uintv" + end + + if ( x <= 0x7FFF and x >= -0x7FFF ) then + return "int16" + end + + if ( x <= 0x7FFFFFFF and x >= -0x7FFFFFFF ) then + return "int32" + end + + return "uintv" + + end + + if ( t == "string" and IsHexaString( x ) ) then + return "hexastring" + end + + if ( t == "string" and Is7BitString( x ) ) then + return "string7" + end + + if ( IsColor( x ) ) then + return "Color" + end + + if ( TypeID( x ) == TYPE_ENTITY ) then + return "Entity" + end + + return t + +end + +local StringToTypeLookup, TypeToStringLookup = { }, { } + +do + -- + -- MUST BE 16 OR LESS TYPES + -- + StringToTypeLookup = { + -- strings + string = 0, + hexastring = 1, + string7 = 2, + + --numbers + bit = 3, + int16 = 4, + int32 = 5, + number = 6, + uintv = 7, + + -- default things + boolean = 8, + + --float arrays + Vector = 9, + Angle = 10, + + --tables + table = 11, + reference = 13, + + + -- Garry's Mod specific + VMatrix = 12, + Color = 14, + Entity = 15, + } + + -- + -- backwards lookup + -- + for k,v in pairs( StringToTypeLookup ) do + TypeToStringLookup[ v ] = k + end + +end + +local function TypeToString( n ) + return TypeToStringLookup[ n ] +end + +local function StringToType( s ) + return StringToTypeLookup[ s ] +end + +local ReferenceType = StringToType( "reference" ) +local TableType = StringToType( "table" ) + +reading = { + -- + -- Normal gmod types we can't really improve + -- + Color = ReadColor, + boolean = function(buf) return buf:UInt(1) == 1 end, + number = function(buf) return buf:Double() end, + bit = function(buf) return buf:UInt(1) end, + Entity = ReadEntity, + VMatrix = error, --net.ReadMatrix, + Angle = function(buf) return Angle(buf:Float(), buf:Float(), buf:Float()) end, --net.ReadAngle, + Vector = function(buf) return Vector(buf:Float(), buf:Float(), buf:Float()) end, --net.ReadVector, + + -- + -- Simple integers + -- + int16 = function(buf) return buf:Int( 16 ) end, + int32 = function(buf) return buf:Int( 32 ) end, + + -- + -- A reference index in our already-sent-table + -- + reference = function( buf, references ) return references[ reading.uintv(buf) ] end, + + -- + -- Variable length unsigned integers + -- + uintv = function(buf) + local i = 0 + local ret = 0 + + while buf:UInt(1) == 1 do + local t = buf:UInt( UINTV_SIZE ) + ret = ret + bit.lshift( t, i * UINTV_SIZE ) + + i = i + 1 + end + + return ret + end, + + -- + -- 7 bit encoded strings + -- NULL terminated + -- + string7 = function(buf) + if ( buf:UInt(1) == 1 ) then -- it's compressed + return util.Decompress( buf:Data( reading.uintv(buf) ) ) + else -- it's not compressed + local ret = "" + + while true do + local chr = buf:UInt( 7 ) + if ( chr == 0 ) then return ret end + ret = ret..string.char( chr ) + end + end + end, + + -- + -- Our 6-bit encoded strings + -- NULL terminated + -- + hexastring = function(buf) + if ( buf:UInt(1) == 1 ) then + return util.Decompress( buf:Data( reading.uintv(buf) ) ) + else + local ret = "" + + while true do + local chr = buf:UInt( 6 ) + if ( chr == 0 ) then return ret end -- terminator + ret = ret..Hexa2CharLookup[ chr ] + end + end + end, + + -- + -- C String + -- NULL terminated + -- NOTE: Must be NULL terminated or else will break compatibility + -- with some addons! Also could lead to exploits + -- + string = function(buf) + if ( buf:UInt(1) == 1 ) then -- compressed or not + return util.Decompress( buf:Data( reading.uintv(buf) ) ) + else + return buf:String() + end + end, + + -- + -- our readtable + -- directly used as net.ReadTable + -- + table = function( buf, references ) + local ret = {} + + references = references or {} + + local reference = function( type, value ) + if ( not type or ( type ~= TableType and type ~= ReferenceType ) ) then + table.insert( references, value ) + end + end + + reference(nil, ret) + + for i = 1, reading.uintv(buf) do + local type = buf:UInt( TYPE_SIZE ) + local value = reading[ TypeToString( type ) ]( buf, references ) + + reference(type, value) + + ret[ i ] = value + end + + for i = 1, reading.uintv(buf) do + local keytype = buf:UInt(TYPE_SIZE) + local keyvalue = reading[ TypeToString( keytype ) ]( buf, references ) + + reference(keytype, keyvalue) + + local valuetype = buf:UInt(TYPE_SIZE) + local valuevalue = reading[ TypeToString( valuetype ) ]( buf, references ) + + reference(valuetype, valuevalue) + + ret[ keyvalue ] = valuevalue + + end + + return ret + end +} + +-- +-- We need this since #table returns undefined values +-- by the lua spec if it doesn't have incremental keys +-- we use pairs since it's backwards compatible +-- +-- code_gs: pairs loop is needed to run the __pairs metamethod +local function array_len(x) + local tIndicies = {} + + for k, _ in pairs(x) do + tIndicies[k] = true + end + + for i = 1, MAX_ENTITIES do + if (tIndicies[i] == nil) then + return i - 1 + end + end + + return MAX_ENTITIES +end + +writing = { + + bit = function(buf, n) buf:UInt(n, 1) end, + Color = WriteColor, + boolean = function(buf, n) buf:UInt(n and 1 or 0, 1) end, + number = function(buf, d) buf:Double(d) end, + Entity = WriteEntity, + VMatrix = error, + Vector = function(buf, v) buf:Float(v.x) buf:Float(v.y) buf:Float(v.z) end, + Angle = function(buf, v) buf:Float(v.p) buf:Float(v.y) buf:Float(v.r) end, + + int16 = function( buf, w ) buf:Int( w, 16 ) end, + int32 = function( buf, d ) buf:Int( d, 32 ) end, + + -- + -- Variable length unsigned integers + -- + uintv = function( buf, n ) + while( n > 0 ) do + buf:UInt(1, 1) + buf:UInt( n, UINTV_SIZE ) + n = bit.rshift( n, UINTV_SIZE ) + end + + buf:UInt(0, 1) + end, + + + -- + -- 7 bit encoded strings + -- NULL terminated + -- + string7 = function( buf, s ) + local null = s:find( "%z" ) + + if (null) then + s = s:sub( 1, null - 1 ) + end + + local compressed = util.Compress( s ) + + -- add one for the null terminator + if ( compressed and compressed:len() < (s:len() + 1) / 8 * 7 ) then + buf:UInt(1, 1) + writing.uintv( buf, compressed:len() ) + buf:Data( compressed ) + else + buf:UInt(0, 1) + + for i = 1, s:len() do + buf:UInt( s:byte( i, i ), 7 ) + end + + buf:UInt( 0, 7 ) + end + end, + + -- + -- Our 6-bit encoded strings + -- NULL terminated + -- + hexastring = function( buf, s ) + local null = s:find( "%z" ) + + if (null) then + s = s:sub( 1, null - 1 ) + end + + local compressed = util.Compress( s ) + + -- add one for the null terminator + if ( compressed and compressed:len() < ( s:len() + 1 ) / 8 * 6 ) then + buf:UInt(1, 1) + writing.uintv( buf, compressed:len() ) + buf:Data( compressed ) + else + buf:UInt(0, 1) + + for i = 1, s:len() do + buf:UInt( Char2HexaLookup[ s:byte( i, i ) ], 6 ) + end + + buf:UInt( 0, 6 ) + end + end, + + -- + -- C String + -- NULL terminated + -- NOTE: Must be NULL terminated or else will break compatibility + -- with some addons! Also could lead to exploits + -- + string = function( buf, x ) + local null = x:find( "%z" ) + + if (null) then + x = x:sub( 1, null - 1 ) + end + + local compressed = util.Compress( x ) + + if ( compressed and compressed:len() < x:len() + 1 ) then + buf:UInt(1, 1) + writing.uintv( buf, compressed:len() ) + buf:Data( compressed ) + else + buf:UInt(0, 1) + buf:String( x ) + end + end, + + + -- + -- our writetable + -- directly used as net.WriteTable + -- + + table = function( buf, tbl, references, num ) + references = references or {[tbl] = 1} + num = num or 1 + + local SendValue = function( value ) + if ( references[ IndexSafe( value ) ] ) then + buf:UInt( ReferenceType, TYPE_SIZE ) + writing.uintv( buf, references[ IndexSafe( value ) ] ) + + return + end + + local sendtype = SendType( value ) + + num = num + 1 + references[ IndexSafe( value ) ] = num + + buf:UInt( StringToType( sendtype ), TYPE_SIZE ) + + num = writing[ sendtype ]( buf, value, references, num ) or num + end + + local pairs_table = {} + + for k,v in pairs(tbl) do + pairs_table[k] = v + end + + local array_size = array_len( pairs_table ) + + writing.uintv( buf, array_size ) + + for i = 1, array_size do + local value = pairs_table[ i ] + pairs_table[ i ] = nil + + SendValue( value ) + end + + local object_key_count = table.Count( pairs_table ) + + writing.uintv( buf, object_key_count ) + + for k,v in next, pairs_table, nil do + SendValue( k ) + SendValue( v ) + end + + return num + end +} + +return { + write = function(b, t) writing.table(b, t) end, + read = function(b) return reading.table(b) end +} \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/write.lua b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/write.lua new file mode 100644 index 0000000..03ba381 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/bitbuf_glua/write.lua @@ -0,0 +1,231 @@ +--[[local config = include "./config.lua" +if (SERVER) then + if (config and config.netstreamcompat) then + util.AddNetworkString "BITBUF_NETSTREAM" + end + AddCSLuaFile() +end]] +if (SERVER) then + AddCSLuaFile() +end + +local function bad(val, argn, name, err) + if (not val) then + error(("bad argument #%i to '%s' (%s)"):format(argn, name, err), 2) + end +end + +local mt = { + __index = function(self, k) + return getmetatable(self)[k] + end, + __call = function(self, name) + return setmetatable({}, getmetatable(self)):Init() + end +} + +local bb_write = setmetatable({}, mt) + +function mt:Init() + self:ResetData() + return self +end + +function mt:ResetData() + self.RawData = {n = 0} + self.Bits = 0 + self.Bytes = 0 + return self +end + +function mt:Byte(b) + bad(isnumber(b), 1, "Byte", "number expected") + bad(b >= 0 and b <= 0xFF, 1, "Byte", "byte expected") + self:Int(b, 8) + return self +end + +function mt:Data(str) + bad(isstring(str), 1, "Data", "string expected") + for i = 1, str:len() do + self:Byte(str:byte(i, i)) + end + return self +end + +function mt:String(str) + bad(isstring(str), 1, "String", "string expected") + for i = 1, str:len() do + self:Byte(str:byte(i, i)) + end + self:Byte(0) + return self +end + +function mt:Int(num, totalbits) + bad(isnumber(num), 1, "Int", "number expected") + bad(isnumber(totalbits), 2, "Int", "number expected") + bad(totalbits <= 32 and totalbits > 0, 2, "Int", "a number 1 through 32 expected") + + + for i = 1, totalbits do + if (self.Bits % 8 == 0) then + self.Bytes = self.Bytes + 1 + self:EnsureBytes(self.Bytes) + end + local bitn = bit.band(1, bit.rshift(num, totalbits - i)) + self.RawData[self.Bytes] = bit.bor(self.RawData[self.Bytes], bit.lshift(bitn, 7 - (self.Bits % 8))) + + + self.Bits = self.Bits + 1 + end + + return self +end + +function mt:UInt(num, bits) + return self:Int(num, bits) +end + + +function mt:EnsureBytes(n) + for i = self.RawData.n + 1, n do + self.RawData[i] = 0 + end + + self.RawData.n = math.max(self.RawData.n, n) + return self +end + +--[[ THE NEXT TWO FUNCTIONS ARE GIVEN RIGHTS TO ME FOR USAGE UNDER MIT LICENSE BY THE CREATOR ]] +local function DoubleToUInt32s (f) + -- 1 / f is needed to check for -0 + local high = 0 + local low = 0 + if f < 0 or 1 / f < 0 then + high = high + 0x80000000 + f = -f + end + + local mantissa = 0 + local biasedExponent = 0 + + if f == math.huge then + biasedExponent = 0x07FF + elseif f ~= f then + biasedExponent = 0x07FF + mantissa = 1 + elseif f == 0 then + biasedExponent = 0x00 + else + mantissa, biasedExponent = math.frexp (f) + biasedExponent = biasedExponent + 1022 + + if biasedExponent <= 0 then + -- Denormal + mantissa = math.floor (mantissa * 2 ^ (52 + biasedExponent) + 0.5) + biasedExponent = 0 + else + mantissa = math.floor ((mantissa * 2 - 1) * 2 ^ 52 + 0.5) + end + end + + low = mantissa % 4294967296 + high = high + bit.lshift (bit.band (biasedExponent, 0x07FF), 20) + high = high + bit.band (math.floor (mantissa / 4294967296), 0x000FFFFF) + + return low, high +end + +local function FloatToUInt32 (f) + -- 1 / f is needed to check for -0 + local n = 0 + if f < 0 or 1 / f < 0 then + n = n + 0x80000000 + f = -f + end + + local mantissa = 0 + local biasedExponent = 0 + + if f == math.huge then + biasedExponent = 0xFF + elseif f ~= f then + biasedExponent = 0xFF + mantissa = 1 + elseif f == 0 then + biasedExponent = 0x00 + else + mantissa, biasedExponent = math.frexp (f) + biasedExponent = biasedExponent + 126 + + if biasedExponent <= 0 then + -- Denormal + mantissa = math.floor (mantissa * 2 ^ (23 + biasedExponent) + 0.5) + biasedExponent = 0 + else + mantissa = math.floor ((mantissa * 2 - 1) * 2 ^ 23 + 0.5) + end + end + + n = n + bit.lshift (bit.band (biasedExponent, 0xFF), 23) + n = n + bit.band (mantissa, 0x007FFFFF) + + return n +end + +function mt:Float(flt) + bad(isnumber(flt), 1, "Float", "expected number") + + self:UInt(FloatToUInt32(flt), 32) + return self +end + +function mt:Double(dbl) + bad(isnumber(dbl), 1, "Double", "expected number") + local low, high = DoubleToUInt32s(dbl) + self:UInt(low, 32) + self:UInt(high, 32) + return self +end + +local function nop() end +local SHARED_ID = 0 +function mt:QueuedWrite(name, split, sendfn) + bad(isstring(name) and util.NetworkStringToID(name) ~= 0, 1, "QueuedWrite", "a networked string") + bad(split > 0 and split <= 46000, 2, "QueuedWrite", "a number 1 to 46000 expected") + bad(isfunction(sendfn), 3, "QueuedWrite", "function expected") + local datapertick = math.floor(engine.TickInterval() * split) + local offset = 1 + local id = SHARED_ID + local TIMER_ID = "QUEUEDWRITE_"..name.."_BITBUF" + SHARED_ID = SHARED_ID + 1 + local function tick(fn) + net.Start(name) + net.WriteUInt(id, 32) + ;(fn or nop)() + local start = offset + local ends = math.min(self.RawData.n, offset + datapertick - 2) + net.WriteData(string.char(unpack(self.RawData, start, ends)), ends - start + 1) + offset = offset + datapertick - 1 + sendfn() + if (offset > self.RawData.n) then + timer.Remove(TIMER_ID) + end + end + + timer.Create(TIMER_ID, engine.TickInterval(), 0, tick) + tick(function() + net.WriteUInt(self.Bytes, 32) + end) +end + +function mt:ExportAsString() + local d = {} + for i = 1, self.RawData.n do + d[i] = string.char(self.RawData[i]) + end + return table.concat(d) +end + +return bb_write \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/cl_medialib.lua b/garrysmod/addons/admin-sg/lua/modules/cl_medialib.lua new file mode 100644 index 0000000..10796e2 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/cl_medialib.lua @@ -0,0 +1,1310 @@ +local medialib + +do +-- Note: build file expects these exact lines for them to be automatically replaced, so please don't change anything +local VERSION = "git@8c99637e" +local DISTRIBUTABLE = true + +medialib = {} + +medialib.VERSION = VERSION +medialib.DISTRIBUTABLE = DISTRIBUTABLE + +medialib.Modules = {} + +local cvar_debug = CreateConVar("medialib_debug", "0", FCVAR_ARCHIVE) +cvars.AddChangeCallback(cvar_debug:GetName(), function(_, _, val) + medialib.DEBUG = val == "1" +end) +medialib.DEBUG = cvar_debug:GetBool() + +function medialib.modulePlaceholder(name) + medialib.Modules[name] = {} +end +function medialib.module(name, opts) + if medialib.DEBUG then + print("[MediaLib] Creating module " .. name) + end + + local mod = medialib.Modules[name] or { + name = name, + options = opts, + } + + medialib.Modules[name] = mod + + return mod +end + +-- AddCSLuaFile all medialib modules +if SERVER then + for _,fname in pairs(file.Find("medialib/*", "LUA")) do + AddCSLuaFile("medialib/" .. fname) + end +end + +local file_Exists = file.Exists +function medialib.tryInclude(file) + if file_Exists(file, "LUA") then + include(file) + return true + end + + if medialib.DEBUG then + print("[MediaLib] Attempted to include nonexistent file " .. file) + end + + return false +end + +function medialib.load(name) + local mod = medialib.Modules[name] + if mod then return mod end + + if medialib.DEBUG then + print("[MediaLib] Loading unreferenced module " .. name) + end + + local file = "medialib/" .. name .. ".lua" + if not medialib.tryInclude(file) then return nil end + + return medialib.Modules[name] +end + +local medialibg = setmetatable({medialib = medialib}, {__index = _G}) + +local real_file_meta = { + read = function(self) + return file.Read(self.lua_path, "LUA") + end, + load = function(self) + --local str = self:read() + --if not str then error("MedialibDynLoad: could not load " .. self.lua_path) end + + -- TODO this does not function correctly; embedded medialib loading real_file will use global medialib as its 'medialib' instance + return include(self.lua_path) + end, + addcs = function(self) + AddCSLuaFile(self.lua_path) + end, +} +real_file_meta.__index = real_file_meta + +local virt_file_meta = { + read = function(self) + return self.source + end, + load = function(self) + local compiled = CompileString(self:read(), "MediaLib_DynFile_" .. self.name) + setfenv(compiled, medialibg) + return compiled() + end, + addcs = function() end +} +virt_file_meta.__index = virt_file_meta + +-- Used for medialib packed into a single file +medialib.FolderItems = {} + +-- Returns an iterator for files in folder +function medialib.folderIterator(folder) + local files = {} + for _,fname in pairs(file.Find("medialib/" .. folder .. "/*.lua", "LUA")) do + table.insert(files, setmetatable({ + name = fname, + lua_path = "medialib/" .. folder .. "/" .. fname + }, real_file_meta)) + end + + for k,item in pairs(medialib.FolderItems) do + local mfolder = k:match("^([^/]*).+") + if mfolder == folder then + table.insert(files, setmetatable({ + name = k:match("^[^/]*/(.+)"), + source = item + }, virt_file_meta)) + end + end + + return pairs(files) +end + +if CLIENT then + local function Rainbow() + for i=1, 30 do + MsgC(HSVToColor(30*i, 0.5, 0.9), " " .. string.rep("SEE BELOW FOR INSTRUCTIONS ", 3) .. "\n") + end + end + concommand.Add("medialib_noflash", function(_, _, args) + if args[1] == "rainbow" then Rainbow() end + + SetClipboardText("http://get.adobe.com/flashplayer/otherversions/") + + MsgN("[ MediaLib: How to get Flash Player ]") + MsgN("1. Open this website in your browser (not the ingame Steam browser): http://get.adobe.com/flashplayer/otherversions/") + MsgN(" (the link has been automatically copied to your clipboard)") + MsgN("2. Download and install the NSAPI (for Firefox) version") + MsgN("3. Restart your Garry's Mod and rejoin this server") + MsgN("[ ======================= ]") + end) + + concommand.Add("medialib_lowaudio", function(_, _, args) + if args[1] == "rainbow" then Rainbow() end + + SetClipboardText("http://windows.microsoft.com/en-us/windows7/adjust-the-sound-level-on-your-computer") + + MsgN("[ MediaLib: How to fix muted sound ]") + MsgN("1. Follow instructions here: http://windows.microsoft.com/en-us/windows7/adjust-the-sound-level-on-your-computer") + MsgN(" (the link has been automatically copied to your clipboard, you can open it in the steam ingame browser)") + MsgN("2. Increase the volume of a process called 'Awesomium Core'") + MsgN("3. You should immediately start hearing sound if a mediaclip is playing") + MsgN("[ ======================= ]") + end) + + hook.Add("OnPlayerChat", "MediaLib.ShowInstructions", function(ply, text) + if text:match("!ml_noflash") then + RunConsoleCommand("medialib_noflash", "rainbow") + RunConsoleCommand("showconsole") + elseif text:match("!ml_lowvolume") then + RunConsoleCommand("medialib_lowaudio", "rainbow") + RunConsoleCommand("showconsole") + end + end) +end + +end + +-- 'oop'; CodeLen/MinifiedLen 2927/2927; Dependencies [] +medialib.modulePlaceholder("oop") +do +local oop = medialib.module("oop") +oop.Classes = oop.Classes or {} + +function oop.class(name, parent) + local cls = oop.Classes[name] + if not cls then + cls = oop.createClass(name, parent) + oop.Classes[name] = cls + + if medialib.DEBUG then + print("[MediaLib] Registering oopclass " .. name) + end + end + + return cls +end + +function oop.resolveClass(obj) + if obj == nil then + return oop.Object + end + + local t = type(obj) + if t == "string" then + local clsobj = oop.Classes[obj] + if clsobj then return clsobj end + + error("Resolving class from inexistent class string '" .. tostring(obj) .. "'") + end + if t == "table" then + return obj + end + + error("Resolving class from invalid object '" .. tostring(obj) .. "'") +end + +-- This is a special parent used to prevent oop.Object being parent of itself +local NIL_PARENT = {} + +-- Credits to Middleclass +local metamethods = {'__add', '__call', '__concat', '__div', '__ipairs', '__le', + '__len', '__lt', '__mod', '__mul', '__pairs', '__pow', '__sub', + '__tostring', '__unm'} + +function oop.createClass(name, parent) + local cls = {} + + -- Get parent class + local par_cls + if parent ~= NIL_PARENT then + par_cls = oop.resolveClass(parent) + end + + -- Add metadata + cls.name = name + cls.super = par_cls + + -- Add a subtable for class members ie methods and class/super handles + cls.members = setmetatable({}, {__index = cls.super}) + + -- Add built-in "keywords" that Instances can access + cls.members.class = cls + cls.members.super = cls.super + + -- Instance metatable + local cls_instance_meta = {} + do + cls_instance_meta.__index = cls.members + + -- Add metamethods. The class does not have members yet, so we need to use runtime lookup + for _,name in pairs(metamethods) do + cls_instance_meta[name] = function(...) + local method = cls.members[name] + if method then + return method(...) + end + end + end + end + + -- Class metatable + local class_meta = {} + do + class_meta.__index = cls.members + class_meta.__newindex = cls.members + + class_meta.__tostring = function(self) + return "class " .. self.name + end + + -- Make the Class object a constructor. + -- ie calling Class() creates a new instance + function class_meta:__call(...) + local instance = {} + setmetatable(instance, cls_instance_meta) + + -- Call constructor if exists + local ctor = instance.initialize + if ctor then ctor(instance, ...) end + + return instance + end + end + + -- Set meta functions + setmetatable(cls, class_meta) + + return cls +end + +oop.Object = oop.createClass("Object", NIL_PARENT) + +-- Get the hash code ie the value Lua prints when you call __tostring() +function oop.Object:hashCode() + local meta = getmetatable(self) + + local old_tostring = meta.__tostring + meta.__tostring = nil + + local hash = tostring(self):match("table: 0x(.*)") + + meta.__tostring = old_tostring + + return hash +end + +function oop.Object:__tostring() + return string.format("%s@%s", self.class.name, self:hashCode()) +end +end +-- 'mediabase'; CodeLen/MinifiedLen 4305/4305; Dependencies [oop] +medialib.modulePlaceholder("mediabase") +do +local oop = medialib.load("oop") + +local Media = oop.class("Media") + +function Media:on(event, callback) + self._events = self._events or {} + self._events[event] = self._events[event] or {} + self._events[event][callback] = true +end +function Media:emit(event, ...) + if not self._events then return end + + local callbacks = self._events[event] + if not callbacks then return end + + for k,_ in pairs(callbacks) do + k(...) + end +end + +function Media:getServiceBase() + error("Media:getServiceBase() not implemented!") +end +function Media:getService() + return self._service +end +function Media:getUrl() + return self._unresolvedUrl +end + +-- If metadata is cached: return it +-- Otherwise either start a new metadata query, or if one is already going on +-- do nothing +function Media:lookupMetadata() + local md = self._metadata + + -- Already fetched + if type(md) == "table" then return md end + + -- Fetching or there was an error (TODO make error available to user) + if md == true or type(md) == "string" then return nil end + + self._metadata = true + self:getService():query(self:getUrl(), function(err, data) + if err then + self._metadata = err + else + self._metadata = data + end + end) + + return nil +end + +-- True returned from this function does not imply anything related to how +-- ready media is to play, just that it exists somewhere in memory and should +-- at least in some point in the future be playable, but even that is not guaranteed +function Media:isValid() + return false +end + +-- The GMod global IsValid requires the uppercase version +function Media:IsValid() + return self:isValid() +end + +-- vol must be a float between 0 and 1 +function Media:setVolume(vol) end +function Media:getVolume() end + +-- "Quality" must be one of following strings: "low", "medium", "high", "veryhigh" +-- Qualities do not map equally between services (ie "low" in youtube might be "medium" in twitch) +-- Services are not guaranteed to change to the exact provided quality, or even to do anything at all +function Media:setQuality(quality) end + +-- time must be an integer between 0 and duration +function Media:seek(time) end +function Media:getTime() + return 0 +end + +-- This method can be called repeatedly to keep the media somewhat in sync +-- with given time, which makes it a great function to keep eg. synchronized +-- televisions in sync. +function Media:sync(time, margin) + -- Only sync at most once per five seconds + if self._lastSync and self._lastSync > CurTime() - 5 then + return + end + + local shouldSync = self:shouldSync(time, margin) + if not shouldSync then return end + + self:seek(time + 0.1) -- Assume 0.1 sec loading time + self._lastSync = CurTime() +end + +function Media:shouldSync(time, margin) + -- Check for invalid syncing state + if not self:isValid() or not self:isPlaying() then + return false + end + + margin = margin or 2 + + local curTime = self:getTime() + local diff = math.abs(curTime - time) + + return diff > margin +end + +-- Must return one of following strings: "error", "loading", "buffering", "playing", "paused", "ended" +-- Can also return nil if state is unknown or cannot be represented properly +-- If getState does not return nil, it should be assumed to be the correct current state +function Media:getState() end + +-- Simplified function of above; simply returns boolean indicating playing state +function Media:isPlaying() + return self:getState() == "playing" +end + +function Media:play() end +function Media:pause() end +function Media:stop() end + +-- Queue a function to run after media is loaded. The function should run immediately +-- if media is already loaded. +function Media:runCommand(fn) end + +function Media:draw(x, y, w, h) end + +function Media:getTag() return self._tag end +function Media:setTag(tag) self._tag = tag end +function Media:guessDefaultTag() + for i=1, 10 do + local info = debug.getinfo(i, "S") + if not info then break end + + local src = info.short_src + local addon = src:match("addons/(.-)/") + if addon and addon ~= "medialib" then return string.format("addon:%s", addon) end + end + + return "addon:medialib" +end +function Media:setDefaultTag() + self:setTag(self:guessDefaultTag()) +end + +function Media:getDebugInfo() + return string.format("[%s] Media [%s] valid:%s state:%s url:%s time:%d", self:getTag(), self.class.name, tostring(self:isValid()), self:getState(), self:getUrl(), self:getTime()) +end + +end +-- 'media'; CodeLen/MinifiedLen 746/746; Dependencies [] +medialib.modulePlaceholder("media") +do +local media = medialib.module("media") +media.Services = {} + +function media.registerService(name, cls) + media.Services[name] = cls() +end +media.RegisterService = media.registerService -- alias + +function media.service(name) + return media.Services[name] +end +media.Service = media.service -- alias + +function media.guessService(url, opts) + for name,service in pairs(media.Services) do + local isViable = true + + if opts and opts.whitelist then + isViable = isViable and table.HasValue(opts.whitelist, name) + end + if opts and opts.blacklist then + isViable = isViable and not table.HasValue(opts.blacklist, name) + end + + if isViable and service:isValidUrl(url) then + return service + end + end +end +media.GuessService = media.guessService -- alias + +end +-- 'mediaregistry'; CodeLen/MinifiedLen 1248/1248; Dependencies [] +medialib.modulePlaceholder("mediaregistry") +do +local mediaregistry = medialib.module("mediaregistry") + +local cache = setmetatable({}, {__mode = "v"}) + +function mediaregistry.add(media) + table.insert(cache, media) +end +function mediaregistry.get() + return cache +end + +concommand.Add("medialib_listall", function() + hook.Run("MediaLib_ListAll") +end) +local vers = medialib.VERSION +hook.Add("MediaLib_ListAll", "MediaLib_" .. vers, function() + print("Media for medialib version " .. vers .. ":") + for _,v in pairs(cache) do + print(v:getDebugInfo()) + end +end) + +concommand.Add("medialib_stopall", function() + hook.Run("MediaLib_StopAll") +end) +hook.Add("MediaLib_StopAll", "MediaLib_" .. medialib.VERSION, function() + for _,v in pairs(cache) do + v:stop() + end + + table.Empty(cache) +end) + +local cvar_debug = CreateConVar("medialib_debugmedia", "0") +hook.Add("HUDPaint", "MediaLib_G_DebugMedia", function() + if not cvar_debug:GetBool() then return end + local counter = {0} + hook.Run("MediaLib_DebugPaint", counter) +end) + +hook.Add("MediaLib_DebugPaint", "MediaLib_" .. medialib.VERSION, function(counter) + local i = counter[1] + for _,media in pairs(cache) do + local t = string.format("#%d %s", i, media:getDebugInfo()) + draw.SimpleText(t, "DermaDefault", 10, 10 + i*15) + + i=i+1 + end + counter[1] = i +end) +end +-- 'servicebase'; CodeLen/MinifiedLen 2234/2234; Dependencies [oop,mediaregistry] +medialib.modulePlaceholder("servicebase") +do +local oop = medialib.load("oop") +local mediaregistry = medialib.load("mediaregistry") + +local Service = oop.class("Service") + +function Service:on(event, callback) + self._events = {} + self._events[event] = self._events[event] or {} + self._events[event][callback] = true +end +function Service:emit(event, ...) + for k,_ in pairs(self._events[event] or {}) do + k(...) + end + + if event == "error" then + MsgN("[MediaLib] Video error: " .. table.ToString{...}) + end +end + +function Service:load(url, opts) end +function Service:loadMediaObject(media, url, opts) + media._unresolvedUrl = url + media._service = self + + media:setDefaultTag() + + hook.Run("Medialib_ProcessOpts", media, opts or {}) + + mediaregistry.add(media) + + self:resolveUrl(url, function(resolvedUrl, resolvedData) + media:openUrl(resolvedUrl) + + if resolvedData and resolvedData.start and (not opts or not opts.dontSeek) then media:seek(resolvedData.start) end + end) +end + +function Service:isValidUrl(url) end + +-- Sub-services should override this +function Service:directQuery(url, callback) end + +-- A metatable for the callback chain +local _service_cbchain_meta = {} +_service_cbchain_meta.__index = _service_cbchain_meta +function _service_cbchain_meta:addCallback(cb) + table.insert(self._callbacks, cb) +end +function _service_cbchain_meta:run(err, data) + local first = table.remove(self._callbacks, 1) + if not first then return end + + first(err, data, function(err, data) + self:run(err, data) + end) +end + +-- Query calls direct query and then passes the data through a medialib hook +function Service:query(url, callback) + local cbchain = setmetatable({_callbacks = {}}, _service_cbchain_meta) + + -- First add the data gotten from the service itself + cbchain:addCallback(function(_, _, cb) return self:directQuery(url, cb) end) + + -- Then add custom callbacks + hook.Run("Medialib_ExtendQuery", url, cbchain) + + -- Then add the user callback + cbchain:addCallback(function(err, data) callback(err, data) end) + + -- Finally run the chain + cbchain:run(url) +end + +function Service:parseUrl(url) end + +-- the second argument to cb() function call has some standard keys: +-- `start` the time at which to start media in seconds +function Service:resolveUrl(url, cb) + cb(url, self:parseUrl(url)) +end + +end +-- 'timekeeper'; CodeLen/MinifiedLen 1016/1016; Dependencies [oop] +medialib.modulePlaceholder("timekeeper") +do +-- The purpose of TimeKeeper is to keep time where it is not easily available synchronously (ie. HTML based services) +local oop = medialib.load("oop") + +local TimeKeeper = oop.class("TimeKeeper") + +function TimeKeeper:initialize() + self:reset() +end + +function TimeKeeper:reset() + self.cachedTime = 0 + + self.running = false + self.runningTimeStart = 0 +end + +function TimeKeeper:getTime() + local time = self.cachedTime + + if self.running then + time = time + (RealTime() - self.runningTimeStart) + end + + return time +end + +function TimeKeeper:isRunning() + return self.running +end + +function TimeKeeper:play() + if self.running then return end + + self.runningTimeStart = RealTime() + self.running = true +end + +function TimeKeeper:pause() + if not self.running then return end + + local runningTime = RealTime() - self.runningTimeStart + self.cachedTime = self.cachedTime + runningTime + + self.running = false +end + +function TimeKeeper:seek(time) + self.cachedTime = time + + if self.running then + self.runningTimeStart = RealTime() + end +end +end +-- 'service_html'; CodeLen/MinifiedLen 7390/7390; Dependencies [oop,timekeeper] +medialib.modulePlaceholder("service_html") +do +local oop = medialib.load("oop") +medialib.load("timekeeper") + +local HTMLService = oop.class("HTMLService", "Service") +function HTMLService:load(url, opts) + local media = oop.class("HTMLMedia")() + self:loadMediaObject(media, url, opts) + return media +end + +-- Whether or not we can trust that the HTML panel will send 'playing', 'paused' +-- and other playback related events. If this returns true, 'timekeeper' will +-- not be updated in playback related methods (except stop). +function HTMLService:hasReliablePlaybackEvents(media) + return false +end + +local AwesomiumPool = {instances = {}} +concommand.Add("medialib_awepoolinfo", function() + print("AwesomiumPool> Free instance count: " .. #AwesomiumPool.instances) +end) +-- If there's bunch of awesomium instances in pool, we clean one up every 30 seconds +timer.Create("MediaLib.AwesomiumPoolCleaner", 30, 0, function() + if #AwesomiumPool.instances < 3 then return end + + local inst = table.remove(AwesomiumPool.instances, 1) + if IsValid(inst) then inst:Remove() end +end) +function AwesomiumPool.get() + local inst = table.remove(AwesomiumPool.instances, 1) + if not IsValid(inst) then + local pnl = vgui.Create("DHTML") + return pnl + end + return inst +end +function AwesomiumPool.free(inst) + if not IsValid(inst) then return end + inst:SetHTML("") + + table.insert(AwesomiumPool.instances, inst) +end + +local cvar_showAllMessages = CreateConVar("medialib_showallmessages", "0") + +local HTMLMedia = oop.class("HTMLMedia", "Media") + +local panel_width, panel_height = 1280, 720 +function HTMLMedia:initialize() + self.timeKeeper = oop.class("TimeKeeper")() + + self.panel = AwesomiumPool.get() + + local pnl = self.panel + pnl:SetPos(0, 0) + pnl:SetSize(panel_width, panel_height) + + local hookid = "MediaLib.HTMLMedia.FakeThink-" .. self:hashCode() + hook.Add("Think", hookid, function() + if not IsValid(self.panel) then + hook.Remove("Think", hookid) + return + end + + self.panel:Think() + end) + + local oldcm = pnl._OldCM or pnl.ConsoleMessage + pnl._OldCM = oldcm + pnl.ConsoleMessage = function(pself, msg) + if msg and not cvar_showAllMessages:GetBool() then + -- Filter some things out + if string.find(msg, "XMLHttpRequest", nil, true) then return end + if string.find(msg, "Unsafe JavaScript attempt to access", nil, true) then return end + if string.find(msg, "Unable to post message to", nil, true) then return end + if string.find(msg, "ran insecure content from", nil, true) then return end + end + + return oldcm(pself, msg) + end + + pnl:AddFunction("console", "warn", function(param) + -- Youtube seems to spam lots of useless stuff here (that requires this function still?), so block by default + if not cvar_showAllMessages:GetBool() then return end + + pnl:ConsoleMessage(param) + end) + + pnl:SetPaintedManually(true) + pnl:SetVisible(false) + + pnl:AddFunction("medialiblua", "Event", function(id, jsonstr) + self:handleHTMLEvent(id, util.JSONToTable(jsonstr)) + end) +end + +function HTMLMedia:getBaseService() + return "html" +end + +function HTMLMedia:openUrl(url) + self.panel:OpenURL(url) + + self.URLChanged = CurTime() +end +function HTMLMedia:runJS(js, ...) + local code = string.format(js, ...) + self.panel:QueueJavascript(code) +end + +function HTMLMedia:handleHTMLEvent(id, event) + if medialib.DEBUG then + MsgN("[MediaLib] HTML Event: " .. id .. " (" .. table.ToString(event) .. ")") + end + if id == "stateChange" then + local state = event.state + local setToState + + if event.time then + self.timeKeeper:seek(event.time) + end + if state == "playing" then + setToState = "playing" + self.timeKeeper:play() + elseif state == "ended" or state == "paused" or state == "buffering" then + setToState = state + self.timeKeeper:pause() + end + + if setToState then + self.state = setToState + self:emit(setToState) + end + elseif id == "playerLoaded" then + for _,fn in pairs(self.commandQueue or {}) do + fn() + end + elseif id == "error" then + self:emit("error", {errorId = "service_error", errorName = "Error from service: " .. tostring(event.message)}) + else + MsgN("[MediaLib] Unhandled HTML event " .. tostring(id)) + end +end +function HTMLMedia:getState() + return self.state +end + +local cvar_updatestride = CreateConVar("medialib_html_updatestride", "1", FCVAR_ARCHIVE) +function HTMLMedia:updateTexture() + local framenumber = FrameNumber() + + local framesSinceUpdate = (framenumber - (self.lastUpdatedFrame or 0)) + if framesSinceUpdate >= cvar_updatestride:GetInt() then + self.panel:UpdateHTMLTexture() + self.lastUpdatedFrame = framenumber + end +end + +function HTMLMedia:getHTMLMaterial() + if self._htmlMat then + return self._htmlMat + end + local mat = self.panel:GetHTMLMaterial() + self._htmlMat = mat + return mat +end + +function HTMLMedia:draw(x, y, w, h) + self:updateTexture() + + local mat = self:getHTMLMaterial() + surface.SetMaterial(mat) + surface.SetDrawColor(255, 255, 255) + + local w_frac, h_frac = panel_width / mat:Width(), panel_height / mat:Height() + surface.DrawTexturedRectUV(x or 0, y or 0, w or panel_width, h or panel_height, 0, 0, w_frac, h_frac) +end + +function HTMLMedia:getTime() + return self.timeKeeper:getTime() +end + +function HTMLMedia:setQuality(qual) + if self.lastSetQuality and self.lastSetQuality == qual then + return + end + self.lastSetQuality = qual + + self:runJS("medialibDelegate.run('setQuality', {quality: %q})", qual) +end + +-- This applies the volume to the HTML panel +-- There is a undocumented 'internalVolume' variable, that can be used by eg 3d vol +function HTMLMedia:applyVolume() + local ivol = self.internalVolume or 1 + local rvol = self.volume or 1 + + local vol = ivol * rvol + + if self.lastSetVolume and self.lastSetVolume == vol then + return + end + self.lastSetVolume = vol + + self:runJS("medialibDelegate.run('setVolume', {vol: %f})", vol) +end + +-- This sets a volume variable +function HTMLMedia:setVolume(vol) + self.volume = vol + self:applyVolume() +end + +function HTMLMedia:getVolume() + -- could cookies potentially set the volume to something other than 1? + return self.volume or 1 +end + +-- if we dont rely on callbacks from JS, this will be the 'buffer' time for seeks +local SEEK_BUFFER = 0.2 + +function HTMLMedia:seek(time) + -- Youtube does not seem to properly callback with a 'seek' event (works in Chrome) + -- Workaround by setting timeseeker time instantly here with a small buffer + -- Using workaround is ok because if we somehow get event later, it'll correct + -- the time that was set wrongly here + self.timeKeeper:seek(time - SEEK_BUFFER) + + self:runJS("medialibDelegate.run('seek', {time: %.1f})", time) +end + +-- See HTMLService:hasReliablePlaybackEvents() +function HTMLMedia:hasReliablePlaybackEvents() + local service = self:getService() + return service and service:hasReliablePlaybackEvents(self) +end + +function HTMLMedia:play() + if not self:hasReliablePlaybackEvents() then + self.timeKeeper:play() + end + + self:runJS("medialibDelegate.run('play')") +end +function HTMLMedia:pause() + if not self:hasReliablePlaybackEvents() then + self.timeKeeper:pause() + end + + self:runJS("medialibDelegate.run('pause')") +end +function HTMLMedia:stop() + AwesomiumPool.free(self.panel) + self.panel = nil + + self.timeKeeper:pause() + self:emit("ended", {stopped = true}) + self:emit("destroyed") +end + +function HTMLMedia:runCommand(fn) + if self._playerLoaded then + fn() + else + self.commandQueue = self.commandQueue or {} + self.commandQueue[#self.commandQueue+1] = fn + end +end + +function HTMLMedia:isValid() + return IsValid(self.panel) +end + +end +-- 'service_bass'; CodeLen/MinifiedLen 6685/6685; Dependencies [oop,mediaregistry] +medialib.modulePlaceholder("service_bass") +do +local oop = medialib.load("oop") + +local BASSService = oop.class("BASSService", "Service") +function BASSService:load(url, opts) + local media = oop.class("BASSMedia")() + self:loadMediaObject(media, url, opts) + return media +end + +local BASSMedia = oop.class("BASSMedia", "Media") + +function BASSMedia:initialize() + self.bassPlayOptions = {"noplay", "noblock"} + self.commandQueue = {} +end + +function BASSMedia:getBaseService() + return "bass" +end + +function BASSMedia:updateFFT() + local curFrame = FrameNumber() + if self._lastFFTUpdate and self._lastFFTUpdate == curFrame then return end + self._lastFFTUpdate = curFrame + + local chan = self.chan + if not IsValid(chan) then return end + + self.fftValues = self.fftValues or {} + chan:FFT(self.fftValues, FFT_512) +end + +function BASSMedia:getFFT() + return self.fftValues +end + +function BASSMedia:draw(x, y, w, h) + surface.SetDrawColor(0, 0, 0) + surface.DrawRect(x, y, w, h) + + self:updateFFT() + local fftValues = self:getFFT() + if not fftValues then return end + + local valCount = #fftValues + local valsPerX = (valCount == 0 and 1 or (w/valCount)) + + local barw = w / (valCount) + for i=1, valCount do + surface.SetDrawColor(HSVToColor(i, 0.9, 0.5)) + + local barh = fftValues[i]*h + surface.DrawRect(x + i*barw, y + (h-barh), barw, barh) + end +end + +function BASSMedia:openUrl(url) + self._openingInfo = {"url", url} + + local flags = table.concat(self.bassPlayOptions, " ") + + sound.PlayURL(url, flags, function(chan, errId, errName) + self:bassCallback(chan, errId, errName) + end) +end +function BASSMedia:openFile(path) + self._openingInfo = {"file", path} + + local flags = table.concat(self.bassPlayOptions, " ") + + sound.PlayFile(path, flags, function(chan, errId, errName) + self:bassCallback(chan, errId, errName) + end) +end + +-- Attempts to reload the stream +function BASSMedia:reload() + local type, resource = unpack(self._openingInfo or {}) + if not type then + MsgN("[Medialib] Attempting to reload BASS stream that was never started the first time!") + return + end + + -- stop existing channel if it exists + if IsValid(self.chan) then + self.chan:Stop() + self.chan = nil + end + + -- Remove stop flag, clear cmd queue, stop state checker + self._stopped = false + self:stopStateChecker() + self.commandQueue = {} + + MsgN("[Medialib] Attempting to reload BASS stream ", type, resource) + if type == "url" then + self:openUrl(resource) + elseif type == "file" then + self:openFile(resource) + elseif type then + MsgN("[Medialib] Failed to reload audio resource ", type, resource) + return + end + + self:applyVolume(true) + + if self._commandState == "play" then + self:play() + end +end + +function BASSMedia:bassCallback(chan, errId, errName) + if not IsValid(chan) then + ErrorNoHalt("[MediaLib] BassMedia play failed: ", errName) + self._stopped = true + + self:emit("error", "loading_failed", string.format("BASS error id: %s; name: %s", errId, errName)) + return + end + + -- Check if media was stopped before loading + if self._stopped then + MsgN("[MediaLib] Loading BASS media aborted; stop flag was enabled") + chan:Stop() + return + end + + self.chan = chan + + for _,c in pairs(self.commandQueue) do + c(chan) + end + + -- Empty queue + self.commandQueue = {} + + self:startStateChecker() +end + +function BASSMedia:startStateChecker() + timer.Create("MediaLib_BASS_EndChecker_" .. self:hashCode(), 1, 0, function() + if IsValid(self.chan) and self.chan:GetState() == GMOD_CHANNEL_STOPPED then + self:emit("ended") + self:stopStateChecker() + end + end) +end +function BASSMedia:stopStateChecker() + timer.Remove("MediaLib_BASS_EndChecker_" .. self:hashCode()) +end + +function BASSMedia:runCommand(fn) + if IsValid(self.chan) then + fn(self.chan) + else + self.commandQueue[#self.commandQueue+1] = fn + end +end + + +-- This applies the volume to the HTML panel +-- There is a undocumented 'internalVolume' variable, that can be used by eg 3d vol +function BASSMedia:applyVolume(force) + local ivol = self.internalVolume or 1 + local rvol = self.volume or 1 + + local vol = ivol * rvol + + if not force and self.lastSetVolume and self.lastSetVolume == vol then + return + end + self.lastSetVolume = vol + + self:runCommand(function(chan) chan:SetVolume(vol) end) +end +function BASSMedia:setVolume(vol) + self.volume = vol + self:applyVolume() +end + +function BASSMedia:getVolume() + return self.volume or 1 +end + +function BASSMedia:seek(time) + self:runCommand(function(chan) + if chan:IsBlockStreamed() then return end + + self._seekingTo = time + + local timerId = "MediaLib_BASSMedia_Seeker_" .. self:hashCode() + local function AttemptSeek() + -- someone used :seek with other time + if self._seekingTo ~= time or + -- chan not valid + not IsValid(chan) then + + timer.Destroy(timerId) + return + end + + chan:SetTime(time) + + -- seek succeeded + if math.abs(chan:GetTime() - time) < 0.25 then + timer.Destroy(timerId) + end + end + timer.Create(timerId, 0.2, 0, AttemptSeek) + AttemptSeek() + end) +end +function BASSMedia:getTime() + if self:isValid() and IsValid(self.chan) then + return self.chan:GetTime() + end + return 0 +end + +function BASSMedia:getState() + if not self:isValid() then return "error" end + + if not IsValid(self.chan) then return "loading" end + + local bassState = self.chan:GetState() + if bassState == GMOD_CHANNEL_PLAYING then return "playing" end + if bassState == GMOD_CHANNEL_PAUSED then return "paused" end + if bassState == GMOD_CHANNEL_STALLED then return "buffering" end + if bassState == GMOD_CHANNEL_STOPPED then return "paused" end -- umm?? + return +end + +function BASSMedia:play() + self:runCommand(function(chan) + chan:Play() + self:emit("playing") + self._commandState = "play" + end) +end +function BASSMedia:pause() + self:runCommand(function(chan) + chan:Pause() + self:emit("paused") + self._commandState = "pause" + end) +end +function BASSMedia:stop() + self._stopped = true + self:runCommand(function(chan) + chan:Stop() + self:emit("ended", {stopped = true}) + self:emit("destroyed") + + self:stopStateChecker() + end) +end + +function BASSMedia:isValid() + return not self._stopped +end + +local mediaregistry = medialib.load("mediaregistry") + +local netmsgid = "ML_MapCleanHack_" .. medialib.VERSION +if CLIENT then + + -- Logic for reloading BASS streams after map cleanups + -- Workaround until gmod issue #2874 gets fixed + net.Receive(netmsgid, function() + for _,v in pairs(mediaregistry.get()) do + + -- BASS media that should play, yet does not + if v:getBaseService() == "bass" and v:isValid() and IsValid(v.chan) and v.chan:GetState() == GMOD_CHANNEL_STOPPED then + v:reload() + end + end + end) +end +if SERVER then + util.AddNetworkString(netmsgid) + hook.Add("PostCleanupMap", "MediaLib_BassReload" .. medialib.VERSION, function() + net.Start(netmsgid) + net.Broadcast() + end) +end +end +medialib.FolderItems["services/gdrive.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal GDriveService = oop.class(\"GDriveService\", \"HTMLService\")\nGDriveService.identifier = \"GDrive\"\n\nlocal all_patterns = {\"^https?://drive.google.com/file/d/([^/]*)/edit\"}\n\nfunction GDriveService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction GDriveService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal function urlencode(str)\n if (str) then\n str = string.gsub (str, \"\\n\", \"\\r\\n\")\n str = string.gsub (str, \"([^%w ])\",\n function (c) return string.format (\"%%%02X\", string.byte(c)) end)\n str = string.gsub (str, \" \", \"+\")\n end\n return str \nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/mp4.html?id=%s\"\nlocal gdrive_stream_url = \"https://drive.google.com/uc?export=download&confirm=yTib&id=%s\"\nfunction GDriveService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlencode(string.format(gdrive_stream_url, urlData.id)))\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction GDriveService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nfunction GDriveService:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn GDriveService" +medialib.FolderItems["services/mp4.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal Mp4Service = oop.class(\"Mp4Service\", \"HTMLService\")\nMp4Service.identifier = \"mp4\"\n\nlocal all_patterns = {\"^https?://.*%.mp4\"}\n\nfunction Mp4Service:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction Mp4Service:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/mp4.html?id=%s\"\nfunction Mp4Service:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction Mp4Service:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nfunction Mp4Service:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn Mp4Service" +medialib.FolderItems["services/soundcloud.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal SoundcloudService = oop.class(\"SoundcloudService\", \"BASSService\")\nSoundcloudService.identifier = \"soundcloud\"\n\nlocal all_patterns = {\n\t\"^https?://www.soundcloud.com/([A-Za-z0-9_%-]+/[A-Za-z0-9_%-]+)/?.*$\",\n\t\"^https?://soundcloud.com/([A-Za-z0-9_%-]+/[A-Za-z0-9_%-]+)/?.*$\",\n}\n\n-- Support url that passes track id directly\nlocal id_pattern = \"^https?://api.soundcloud.com/tracks/(%d+)\"\n\nfunction SoundcloudService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal path = string.match(url, pattern)\n\t\tif path then\n\t\t\treturn {path = path}\n\t\tend\n\tend\n\n\tlocal id = string.match(url, id_pattern)\n\tif id then\n\t\treturn {id = id}\n\tend\nend\n\nfunction SoundcloudService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nfunction SoundcloudService:resolveUrl(url, callback)\n\tlocal apiKey = medialib.SOUNDCLOUD_API_KEY\n\tif not apiKey then\n\t\tErrorNoHalt(\"SoundCloud error: Missing SoundCloud API key\")\n\t\treturn\n\tend\n\n\tlocal urlData = self:parseUrl(url)\n\n\tif urlData.id then\n\t\t-- id passed directly; nice, we can skip resolve.json\n\t\tcallback(string.format(\"https://api.soundcloud.com/tracks/%s/stream?client_id=%s\", urlData.id, apiKey), {})\n\telse\n\t\thttp.Fetch(\n\t\t\tstring.format(\"https://api.soundcloud.com/resolve.json?url=http://soundcloud.com/%s&client_id=%s\", urlData.path, apiKey),\n\t\t\tfunction(data)\n\t\t\t\tlocal jsonTable = util.JSONToTable(data)\n\t\t\t\tif not jsonTable then\n\t\t\t\t\tErrorNoHalt(\"Failed to retrieve SC track id for \" .. urlData.path .. \": empty JSON\")\n\t\t\t\t\treturn\n\t\t\t\tend\n\n\t\t\t\tlocal id = jsonTable.id\n\t\t\t\tcallback(string.format(\"https://api.soundcloud.com/tracks/%s/stream?client_id=%s\", id, apiKey), {})\n\t\t\tend)\n\tend\nend\n\nfunction SoundcloudService:directQuery(url, callback)\n\tlocal apiKey = medialib.SOUNDCLOUD_API_KEY\n\tif not apiKey then\n\t\tcallback(\"Missing SoundCloud API key\")\n\t\treturn\n\tend\n\n\tlocal urlData = self:parseUrl(url)\n\n\tlocal metaurl\n\tif urlData.path then\n\t\tmetaurl = string.format(\"https://api.soundcloud.com/resolve.json?url=http://soundcloud.com/%s&client_id=%s\", urlData.path, apiKey)\n\telse\n\t\tmetaurl = string.format(\"https://api.soundcloud.com/tracks/%s?client_id=%s\", urlData.id, apiKey)\n\tend\n\n\thttp.Fetch(metaurl, function(result, size)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal entry = util.JSONToTable(result)\n\n\t\tif entry.errors then\n\t\t\tlocal msg = entry.errors[1].error_message or \"error\"\n\n\t\t\tlocal translated = msg\n\t\t\tif string.StartWith(msg, \"404\") then\n\t\t\t\ttranslated = \"Invalid id\"\n\t\t\tend\n\n\t\t\tcallback(translated)\n\t\t\treturn\n\t\tend\n\n\t\tcallback(nil, {\n\t\t\ttitle = entry.title,\n\t\t\tduration = tonumber(entry.duration) / 1000\n\t\t})\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nreturn SoundcloudService\n" +medialib.FolderItems["services/twitch.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal TwitchService = oop.class(\"TwitchService\", \"HTMLService\")\nTwitchService.identifier = \"twitch\"\n\nlocal all_patterns = {\n\t\"https?://www.twitch.tv/([A-Za-z0-9_%-]+)\",\n\t\"https?://twitch.tv/([A-Za-z0-9_%-]+)\"\n}\n\nfunction TwitchService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction TwitchService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/twitch.html?channel=%s\"\nfunction TwitchService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction TwitchService:directQuery(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal metaurl = string.format(\"https://api.twitch.tv/kraken/channels/%s\", urlData.id)\n\n\thttp.Fetch(metaurl, function(result, size)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal data = {}\n\t\tdata.id = urlData.id\n\n\t\tlocal jsontbl = util.JSONToTable(result)\n\n\t\tif jsontbl then\n\t\t\tif jsontbl.error then\n\t\t\t\tcallback(jsontbl.message)\n\t\t\t\treturn\n\t\t\telse\n\t\t\t\tdata.title = jsontbl.display_name .. \": \" .. jsontbl.status\n\t\t\tend\n\t\telse\n\t\t\tdata.title = \"ERROR\"\n\t\tend\n\n\t\tcallback(nil, data)\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nreturn TwitchService" +medialib.FolderItems["services/vimeo.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal VimeoService = oop.class(\"VimeoService\", \"HTMLService\")\nVimeoService.identifier = \"vimeo\"\n\nlocal all_patterns = {\n\t\"https?://www.vimeo.com/([0-9]+)\",\n\t\"https?://vimeo.com/([0-9]+)\",\n\t\"https?://www.vimeo.com/channels/staffpicks/([0-9]+)\",\n\t\"https?://vimeo.com/channels/staffpicks/([0-9]+)\",\n}\n\nfunction VimeoService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction VimeoService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"http://wyozi.github.io/gmod-medialib/vimeo.html?id=%s\"\nfunction VimeoService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction VimeoService:directQuery(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal metaurl = string.format(\"http://vimeo.com/api/v2/video/%s.json\", urlData.id)\n\n\thttp.Fetch(metaurl, function(result, size, headers, httpcode)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tif httpcode == 404 then\n\t\t\tcallback(\"Invalid id\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal data = {}\n\t\tdata.id = urlData.id\n\n\t\tlocal jsontbl = util.JSONToTable(result)\n\n\t\tif jsontbl then\n\t\t\tdata.title = jsontbl[1].title\n\t\t\tdata.duration = jsontbl[1].duration\n\t\telse\n\t\t\tdata.title = \"ERROR\"\n\t\tend\n\n\t\tcallback(nil, data)\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nfunction VimeoService:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn VimeoService\n" +medialib.FolderItems["services/webaudio.lua"] = "local oop = medialib.load(\"oop\")\nlocal WebAudioService = oop.class(\"WebAudioService\", \"BASSService\")\nWebAudioService.identifier = \"webaudio\"\n\nlocal all_patterns = {\n\t\"^https?://(.*)%.mp3\",\n\t\"^https?://(.*)%.ogg\",\n}\n\nfunction WebAudioService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction WebAudioService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nfunction WebAudioService:resolveUrl(url, callback)\n\tcallback(url, {})\nend\n\nfunction WebAudioService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nreturn WebAudioService" +medialib.FolderItems["services/webm.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal WebmService = oop.class(\"WebmService\", \"HTMLService\")\nWebmService.identifier = \"webm\"\n\nlocal all_patterns = {\"^https?://.*%.webm\"}\n\nfunction WebmService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction WebmService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"http://wyozi.github.io/gmod-medialib/webm.html?id=%s\"\nfunction WebmService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\nfunction WebmService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\")\n\t})\nend\n\nreturn WebmService" +medialib.FolderItems["services/webradio.lua"] = "local oop = medialib.load(\"oop\")\nlocal WebRadioService = oop.class(\"WebRadioService\", \"BASSService\")\nWebRadioService.identifier = \"webradio\"\n\nlocal all_patterns = {\n\t\"^https?://(.*)%.pls\",\n\t\"^https?://(.*)%.m3u\"\n}\n\nfunction WebRadioService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id = string.match(url, pattern)\n\t\tif id then\n\t\t\treturn {id = id}\n\t\tend\n\tend\nend\n\nfunction WebRadioService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nfunction WebRadioService:resolveUrl(url, callback)\n\tcallback(url, {})\nend\n\nfunction WebRadioService:directQuery(url, callback)\n\tcallback(nil, {\n\t\ttitle = url:match(\"([^/]+)$\") -- the filename is the best we can get (unless we parse pls?)\n\t})\nend\n\nreturn WebRadioService" +medialib.FolderItems["services/youtube.lua"] = "local oop = medialib.load(\"oop\")\n\nlocal YoutubeService = oop.class(\"YoutubeService\", \"HTMLService\")\nYoutubeService.identifier = \"youtube\"\n\nlocal raw_patterns = {\n\t\"^https?://[A-Za-z0-9%.%-]*%.?youtu%.be/([A-Za-z0-9_%-]+)\",\n\t\"^https?://[A-Za-z0-9%.%-]*%.?youtube%.com/watch%?.*v=([A-Za-z0-9_%-]+)\",\n\t\"^https?://[A-Za-z0-9%.%-]*%.?youtube%.com/v/([A-Za-z0-9_%-]+)\",\n}\nlocal all_patterns = {}\n\n-- Appends time modifier patterns to each pattern\nfor k,p in pairs(raw_patterns) do\n\tlocal function with_sep(sep)\n\t\ttable.insert(all_patterns, p .. sep .. \"t=(%d+)m(%d+)s\")\n\t\ttable.insert(all_patterns, p .. sep .. \"t=(%d+)s?\")\n\tend\n\n\t-- We probably support more separators than youtube itself, but that does not matter\n\twith_sep(\"#\")\n\twith_sep(\"&\")\n\twith_sep(\"?\")\n\n\ttable.insert(all_patterns, p)\nend\n\nfunction YoutubeService:parseUrl(url)\n\tfor _,pattern in pairs(all_patterns) do\n\t\tlocal id, time1, time2 = string.match(url, pattern)\n\t\tif id then\n\t\t\tlocal time_sec = 0\n\t\t\tif time1 and time2 then\n\t\t\t\ttime_sec = tonumber(time1)*60 + tonumber(time2)\n\t\t\telse\n\t\t\t\ttime_sec = tonumber(time1)\n\t\t\tend\n\n\t\t\treturn {\n\t\t\t\tid = id,\n\t\t\t\tstart = time_sec\n\t\t\t}\n\t\tend\n\tend\nend\n\nfunction YoutubeService:isValidUrl(url)\n\treturn self:parseUrl(url) ~= nil\nend\n\nlocal player_url = \"https://wyozi.github.io/gmod-medialib/youtube.html?id=%s\"\nfunction YoutubeService:resolveUrl(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal playerUrl = string.format(player_url, urlData.id)\n\n\tcallback(playerUrl, {start = urlData.start})\nend\n\n-- http://en.wikipedia.org/wiki/ISO_8601#Durations\n-- Cheers wiox :))\nlocal function PTToSeconds(str)\n\tlocal h = str:match(\"(%d+)H\") or 0\n\tlocal m = str:match(\"(%d+)M\") or 0\n\tlocal s = str:match(\"(%d+)S\") or 0\n\treturn h*(60*60) + m*60 + s\nend\n\nlocal API_KEY = \"AIzaSyBmQHvMSiOTrmBKJ0FFJ2LmNtc4YHyUJaQ\"\nfunction YoutubeService:directQuery(url, callback)\n\tlocal urlData = self:parseUrl(url)\n\tlocal metaurl = string.format(\"https://www.googleapis.com/youtube/v3/videos?part=snippet%%2CcontentDetails&id=%s&key=%s\", urlData.id, API_KEY)\n\n\thttp.Fetch(metaurl, function(result, size)\n\t\tif size == 0 then\n\t\t\tcallback(\"http body size = 0\")\n\t\t\treturn\n\t\tend\n\n\t\tlocal data = {}\n\t\tdata.id = urlData.id\n\n\t\tlocal jsontbl = util.JSONToTable(result)\n\n\t\tif jsontbl and jsontbl.items then\n\t\t\tlocal item = jsontbl.items[1]\n\t\t\tif not item then\n\t\t\t\tcallback(\"No video id found\")\n\t\t\t\treturn\n\t\t\tend\n\n\t\t\tdata.title = item.snippet.title\n\t\t\tdata.duration = tonumber(PTToSeconds(item.contentDetails.duration))\n\t\telse\n\t\t\tcallback(result)\n\t\t\treturn\n\t\tend\n\n\t\tcallback(nil, data)\n\tend, function(err) callback(\"HTTP: \" .. err) end)\nend\n\nfunction YoutubeService:hasReliablePlaybackEvents(media)\n\treturn true\nend\n\nreturn YoutubeService\n" +-- 'serviceloader'; CodeLen/MinifiedLen 533/533; Dependencies [servicebase,service_html,service_bass,media,oop] +medialib.modulePlaceholder("serviceloader") +do +medialib.load("servicebase") + +medialib.load("service_html") +medialib.load("service_bass") + +local media = medialib.load("media") + +-- Load the actual service files +for _,file in medialib.folderIterator("services") do + if medialib.DEBUG then + print("[MediaLib] Registering service " .. file.name) + end + if SERVER then file:addcs() end + local status, err = pcall(function() return file:load() end) + if status then + media.registerService(err.identifier, err) + else + print("[MediaLib] Failed to load service ", file, ": ", err) + end +end +end +-- '__loader'; CodeLen/MinifiedLen 325/325; Dependencies [mediabase,media,serviceloader] +medialib.modulePlaceholder("__loader") +do +-- This file loads required modules in the correct order. +-- For development version: this file is automatically called after autorun/medialib.lua +-- For distributable: this file is loaded after packed modules have been added to medialib + +medialib.load("mediabase") +medialib.load("media") +medialib.load("serviceloader") +end +return medialib diff --git a/garrysmod/addons/admin-sg/lua/modules/cl_themes.lua b/garrysmod/addons/admin-sg/lua/modules/cl_themes.lua new file mode 100644 index 0000000..9083e85 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/cl_themes.lua @@ -0,0 +1,310 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Client +-- Theming system that handles the creation, saving, loading, and applying of themes to panels. +-- @module serverguard.themes + +serverguard.theme = serverguard.theme or CreateClientConVar("serverguard_theme", "Default", true, false); +serverguard.themes = serverguard.themes or {}; + +local stored = {}; +local panels = {}; +local controls = {}; +local defaults = {"Default"}; + +stored["Default"] = {}; + +cvars.AddChangeCallback("serverguard_theme", function() + serverguard.themes.ApplyChanges() + + local current = serverguard.themes.GetCurrent() + + hook.Call("serverguard.themes.ThemeChanged", nil, current) +end) + +--- Creates a new theme. +-- @string name The name of the theme. +-- @string[opt] theme The name of the theme to base off of. +-- @treturn table Theme data. +function serverguard.themes.New(name, theme) + local baseTheme; + local themetype = type(theme) + + if (themetype == "string") then + baseTheme = serverguard.themes.Get(theme); + elseif (themetype == "table") then + baseTheme = theme; + end; + + local baseCopy = table.Copy(baseTheme and istable(baseTheme) and baseTheme or stored["Default"]); + + for name, color in pairs(stored["Default"]) do + for unique, data in pairs(baseCopy) do + if (!serverguard.themes.IsDefaultTheme(unique)) then + if (!data[name]) then + baseCopy[unique][name] = color + end + end + end + end + + stored[name] = baseCopy; +end; + +--- Sets a colour entry in the current theme. +-- @string name The name of the colour. +-- @color color The colour value. +function serverguard.themes.Set(name, color) + local current = serverguard.themes.GetCurrent() + + current[name] = color + + serverguard.themes.ApplyChanges() +end + +--- Retrieves a table of all available themes. +-- @treturn table Table of all themes' data. +function serverguard.themes.GetStored() + return stored +end + +--- Returns a theme's data. +-- @string unique The name of the theme. +-- @treturn table Theme data. +function serverguard.themes.Get(unique) + return stored[unique] +end + +--- Gets the name of hooked panel colours. +-- @panel panel The panel to get the names from. +-- @treturn table List of names. +function serverguard.themes.GetColorNames(panel) + return controls[panel] +end + +--- Returns the current theme. +-- @treturn table Theme data. +function serverguard.themes.GetCurrent() + local theme = serverguard.theme:GetString() + + if (!stored[theme]) then + RunConsoleCommand("serverguard_theme", "Default") + + return stored["Default"], theme + else + return stored[theme], theme + end +end + +--- Creates a colour entry for the default theme. +-- @string name The name of the colour entry. +-- @color default The default colour to be set. +-- @panel panel The name of the panel it applies to. +function serverguard.themes.CreateDefault(name, default, panel) + stored["Default"][name] = default + + controls[panel] = controls[panel] or {} + + table.insert(controls[panel], name) +end + +-- Creates a default theme. +function serverguard.themes.CreateDefaultTheme(name, data) + table.insert(defaults, name); + stored[name] = data; +end; + +--- Checks whether or not a theme is a default theme. +-- @string name The name of the theme. +-- @treturn bool Whether or not the theme is a default theme. +function serverguard.themes.IsDefaultTheme(name) + return table.HasValue(defaults, name); +end; + +--- Hooks a panel to be changed when the theme is modified. This is currently only used for labels. +-- @panel panel The panel to hook. +-- @string name The name of the theme's colour entry to apply. +function serverguard.themes.AddPanel(panel, name) + local current = serverguard.themes.GetCurrent() + + panel[name] = current[name] + + table.insert(panels, panel) + + if (IsValid(panel)) then + for name, color in pairs(current) do + if (panel[name]) then + if (panel:GetClassName() == "Label") then + panel:SetColor(color) + end + + panel[name] = color + end + end + end +end + +--- Applies theme colours to hooked panels. +function serverguard.themes.ApplyChanges() + local current = serverguard.themes.GetCurrent() + + for i = 1, #panels do + local panel = panels[i] + + if (IsValid(panel)) then + for name, color in pairs(current) do + if (panel[name]) then + if (panel.OnTigerThemeChanged and type(panel.OnTigerThemeChanged) == "function") then + panel:OnTigerThemeChanged(); + end + + if (panel:GetClassName() == "Label") then + panel:SetColor(color) + end + + panel[name] = color + end + end + end + end +end + +--- Saves all themes stored in memory. +function serverguard.themes.Save() + local data = "" + + for k, v in pairs(stored) do + if (serverguard.themes.IsDefaultTheme(k)) then + continue; + end; + + data = data .. "[" .. k .. "]\r\n" + + for t, color in pairs(v) do + data = data .. t .. "=" .. color.r .. "," .. color.g .. "," .. color.b .. "," .. color.a .. "\r\n" + end + + data = data .. "\r\n" + end + + file.Write("serverguard/themes.txt", data, "DATA") +end + +--- Removes a theme from memory and disk. +-- @string name The name of the theme to remove. +function serverguard.themes.Remove(name) + if (!stored[name]) then + return; + end + + stored[name] = nil + serverguard.themes.Save() +end + +--- Loads the themes into memory. +function serverguard.themes.Load() + local data = file.Read("serverguard/themes.txt", "DATA") + + if (data) then + local unique + + data = string.Explode("\n", data) + + for k, str in pairs(data) do + if (str != "") then + local new = string.match(str, "%[%S.+]") + + if (new) then + unique = string.gsub(new, "%[", "") + unique = string.gsub(unique, "%]", "") + + stored[unique] = {} + end + + local col = string.match(str, "%S+=%S+") + + if (col) then + local s, e = string.find(col, "%S-=") + local color = string.Explode(",", string.sub(col, e +1)) + + stored[unique][string.sub(col, s, e -1)] = Color(color[1], color[2], color[3], color[4]) + end + end + end + + -- Load any additions. + for name, color in pairs(stored["Default"]) do + for unique, data in pairs(stored) do + if (!serverguard.themes.IsDefaultTheme(unique)) then + if (!data[name]) then + stored[unique][name] = color + end + end + end + end + + serverguard.themes.Save() + + timer.Simple(2, function() local current = serverguard.themes.GetCurrent() hook.Call("serverguard.themes.ThemeChanged", nil, current) end) + else + RunConsoleCommand("serverguard_theme", "Default") + + timer.Simple(2, function() serverguard.themes.Save() end) + end +end + +-- Default themes. +serverguard.themes.CreateDefaultTheme("Dark", { + ["tiger_divider_right_label_hovered"] = Color(240, 240, 240), + ["tiger_divider_right_label"] = Color(20, 120, 170), + ["tiger_button_bg"] = Color(80, 80, 80), + ["tiger_button_stripe"] = Color(80, 80, 80), + ["tiger_base_section_outline"] = Color(80, 80, 80), + ["tiger_panel_outline"] = Color(80, 80, 80), + ["tiger_news_bg"] = Color(60, 60, 60), + ["tiger_list_bg"] = Color(40, 40, 40), + ["tiger_list_panel_list_bg_dark"] = Color(40, 40, 40), + ["tiger_list_panel_label"] = Color(140, 140, 140), + ["tiger_divider_panel_hover"] = Color(20, 120, 170), + ["tiger_panel_label"] = Color(140, 140, 140), + ["tiger_divider_left_label"] = Color(140, 140, 140), + ["tiger_panel_bg"] = Color(60, 60, 60), + ["tiger_tooltip_label"] = Color(140, 140, 140), + ["tiger_base_outline"] = Color(80, 80, 80), + ["tiger_list_column_label"] = Color(140, 140, 140), + ["tiger_list_scrollbar"] = Color(120, 120, 120, 200), + ["tiger_base_section_icon"] = Color(133, 134, 125), + ["tiger_base_section_label"] = Color(140, 140, 140), + ["tiger_divider_bg"] = Color(60, 60, 60), + ["tiger_button_hovered_stripe"] = Color(120, 120, 120), + ["tiger_list_panel_label_hover"] = Color(240, 240, 240), + ["tiger_news_content"] = Color(140, 140, 140), + ["tiger_list_panel_list_bg"] = Color(45, 45, 45), + ["tiger_button_text"] = Color(200, 200, 200), + ["tiger_tooltip_outline"] = Color(80, 80, 80), + ["tiger_list_outline"] = Color(80, 80, 80), + ["tiger_button_text_hovered"] = Color(30, 30, 30), + ["tiger_list_column_outline"] = Color(80, 80, 80), + ["tiger_button_hovered"] = Color(120, 120, 120), + ["tiger_divider_left_bg"] = Color(50, 50, 50), + ["tiger_base_bg"] = Color(50, 50, 50), + ["tiger_list_panel_list_outline"] = Color(80, 80, 80), + ["tiger_divider_left_label_hovered"] = Color(240, 240, 240), + ["tiger_divider_outline"] = Color(80, 80, 80), + ["tiger_base_footer_label"] = Color(20, 120, 170), + ["tiger_base_section_selected"] = Color(50, 50, 50), + ["tiger_base_section_icon_selected"] = Color(20, 120, 170), + ["tiger_button_outline"] = Color(100, 100, 100), + ["tiger_news_title"] = Color(20, 120, 170), + ["tiger_tooltip_bg"] = Color(60, 60, 60), + ["tiger_list_panel_list_hover"] = Color(20, 120, 170), + ["tiger_base_section_label_selected"] = Color(20, 120, 170), + ["tiger_base_footer_bg"] = Color(40, 40, 40), + ["tiger_divider_panel_outline"] = Color(80, 80, 80) +}); + +serverguard.themes.Load(); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/ban_list.lua b/garrysmod/addons/admin-sg/lua/modules/gui/ban_list.lua new file mode 100644 index 0000000..ab724dd --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/ban_list.lua @@ -0,0 +1,506 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local loadingTexture = Material("icon16/arrow_rotate_anticlockwise.png"); +local category = {}; + +category.name = "Ban list"; +category.material = "serverguard/menuicons/icon_gavel.png"; +category.permissions = {"Ban", "Unban"}; +category.loading = { + firstRun = true, + inProgress = false, + banTable = {}, + banList = {}, + currentIndex = 1, + maxIndex = 0, + reverseLookup = {}, + nextProcessTime = CurTime() +}; + +function category:Create(base) + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("Ban list"); + base.panel:Dock(FILL); + base.panel:DockPadding(24, 24, 24, 48); + + category.loadingPanel = base.panel:Add("tiger.panel"); + category.loadingPanel:Dock(FILL); + category.loadingPanel:SetVisible(false); + + function category.loadingPanel:Paint(width, height) + if (!self.rotation) then + self.rotation = 0; + end; + + self.rotation = self.rotation + 140 * FrameTime(); + + if (self.rotation > 360) then + self.rotation = 0; + end; + + draw.MaterialRotated(width / 2 - 8, height / 2 - 8, 16, 16, color_white, loadingTexture, self.rotation); + end; + + base.panel.list = base.panel:Add("tiger.list"); + base.panel.list:Dock(FILL); + base.panel.list.sortColumn = 1; + + base.panel.list:AddColumn("PLAYER", 150); + base.panel.list:AddColumn("STEAMID", 150); + base.panel.list:AddColumn("LENGTH", 75); + base.panel.list:AddColumn("ADMIN", 150); + base.panel.list:AddColumn("REASON", 150); + + hook.Call("serverguard.panel.BanList", nil, base.panel.list); + + local refresh = base.panel:Add("tiger.button"); + refresh:SetPos(4, 4); + refresh:SetText("Refresh"); + refresh:SizeToContents(); + + function refresh:DoClick() + RunConsoleCommand("serverguard_rfbans"); + end; + + local add_ban = base.panel:Add("tiger.button"); + add_ban:SetPos(4, 4); + add_ban:SetText("Add ban"); + add_ban:SizeToContents(); + + function add_ban:DoClick() + local banLength = nil + local baseb = vgui.Create("tiger.panel"); + baseb:SetTitle("Add a ban"); + baseb:SetSize(500, 250); + baseb:Center(); + baseb:MakePopup(); + baseb:DockPadding(24, 24, 24, 48); + + local form = baseb:Add("tiger.list"); + form:Dock(FILL); + + local steamIDEntry = vgui.Create("tiger.textentry"); + steamIDEntry:SetLabelText("Steam ID"); + steamIDEntry:Dock(TOP); + form:AddPanel(steamIDEntry); + + local banLengthChoice = vgui.Create("tiger.combobox"); + banLengthChoice:SetLabelText("Ban length") + banLengthChoice:Dock(TOP); + + for k, v in pairs(serverguard.banLengths) do + banLengthChoice:AddChoice(v[1], v[2]); + end; + + banLengthChoice:AddChoice("Custom", -1); + + function banLengthChoice:OnSelect(index, value, data) + if (value == "Custom" and data == -1) then + util.CreateStringRequest("Ban Length", + function(text) + local length, lengthText, bClamped = util.ParseDuration(text); + + banLengthChoice:ChooseOption(lengthText, index); + banLength = length; + end, "Okay", + function(text) + end, "Cancel" + ); + else + banLength = data; + end; + end; + + form:AddPanel(banLengthChoice); + + local nameEntry = vgui.Create("tiger.textentry"); + nameEntry:SetLabelText("Name"); + nameEntry:Dock(TOP); + form:AddPanel(nameEntry); + + local reasonEntry = vgui.Create("tiger.textentry"); + reasonEntry:SetLabelText("Reason"); + reasonEntry:Dock(TOP); + form:AddPanel(reasonEntry); + + local completeButton = baseb:Add("tiger.button"); + completeButton:SetPos(4, 4); + completeButton:SetText("Complete"); + completeButton:SizeToContents(); + + function completeButton:DoClick() + if (string.Trim(steamIDEntry:GetValue()) == "") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please enter a Steam ID!"); + return; + end; + + if (!banLength) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please select a valid ban length!"); + return; + end; + + if (string.Trim(nameEntry:GetValue()) == "") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please enter a name!"); + return; + end; + + if (string.Trim(reasonEntry:GetValue()) == "") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please enter a reason!"); + return; + end; + + RunConsoleCommand("serverguard_addmban", steamIDEntry:GetValue(), banLength, nameEntry:GetValue(), reasonEntry:GetValue()); + + baseb:Remove(); + end; + + local cancelButton = baseb:Add("tiger.button"); + cancelButton:SetPos(4, 4); + cancelButton:SetText("Cancel"); + cancelButton:SizeToContents(); + + function cancelButton:DoClick() + baseb:Remove(); + end; + + function baseb:PerformLayout(w, h) + completeButton:SetPos(w - (completeButton:GetWide() + 24), h - (completeButton:GetTall() + 14)); + + cancelButton:SetPos(0, h - (cancelButton:GetTall() + 14)); + cancelButton:MoveLeftOf(completeButton, 14); + end; + end; + + if (serverguard.player:HasPermission(LocalPlayer(), "Unban")) then + base.panel.unban = base.panel:Add("tiger.button"); + base.panel.unban:SetPos(4, 4); + base.panel.unban:SetText("Unban Steam ID"); + base.panel.unban:SizeToContents(); + + function base.panel.unban:DoClick() + local unbanPanel = vgui.Create("tiger.panel"); + unbanPanel:SetTitle("Unban Steam ID"); + unbanPanel:SetSize(300, 150); + unbanPanel:Center(); + unbanPanel:MakePopup(); + unbanPanel:DockPadding(24, 24, 24, 48); + + local steamIDEntry = unbanPanel:Add("DTextEntry"); + steamIDEntry:SetTall(20); + steamIDEntry:SetSkin("serverguard"); + steamIDEntry:Dock(TOP); + + local unbanButton = unbanPanel:Add("tiger.button"); + unbanButton:SetPos(4, 4); + unbanButton:SetText("Unban"); + unbanButton:SizeToContents(); + + function unbanButton:DoClick() + if (string.Trim(steamIDEntry:GetValue()) == "") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please enter a Steam ID!"); + return; + end; + + serverguard.command.Run("unban", false, steamIDEntry:GetValue()); + unbanPanel:Remove(); + end; + + local cancelButton = unbanPanel:Add("tiger.button"); + cancelButton:SetPos(4, 4); + cancelButton:SetText("Cancel"); + cancelButton:SizeToContents(); + + function cancelButton:DoClick() + unbanPanel:Remove(); + end; + + function unbanPanel:PerformLayout(w, h) + unbanButton:SetPos(w - (unbanButton:GetWide() + 24), h - (unbanButton:GetTall() + 14)); + + cancelButton:SetPos(0, h - (cancelButton:GetTall() + 14)); + cancelButton:MoveLeftOf(unbanButton, 14); + end; + end; + end; + + category.statusLabel = base.panel:Add("DLabel"); + category.statusLabel:SetFont("tiger.button"); + category.statusLabel:SetText(""); + category.statusLabel:SetSkin("serverguard"); + + function base.panel:PerformLayout() + local w, h = self:GetSize(); + + refresh:SetPos(w - (refresh:GetWide() + 24), h - (refresh:GetTall() + 14)); + + add_ban:SetPos(0, h - (add_ban:GetTall() + 14)); + add_ban:MoveLeftOf(refresh, 14); + + if (IsValid(base.panel.unban)) then + base.panel.unban:SetPos(0, h - (base.panel.unban:GetTall() + 14)); + base.panel.unban:MoveLeftOf(add_ban, 14); + end; + + category.statusLabel:SetPos(25, h - (category.statusLabel:GetTall() + 18)); + end; + + category.list = base.panel.list; +end; + +function category:Update(base) + if (self.loading.firstRun) then + RunConsoleCommand("serverguard_rfbans"); + + self.loading.firstRun = nil; + end; +end; + +function category:SetLoading(value) + value = tobool(value); + + self.loadingPanel:SetVisible(value); + self.list:SetVisible(!value); +end; + +local expectedBans = 0; + +function category:Think(base) + if (!category.loading.inProgress or CurTime() < category.loading.nextProcessTime) then + return; + end; + + for i = category.loading.currentIndex, math.min(category.loading.maxIndex, category.loading.currentIndex + 4) do + if (i == category.loading.maxIndex) then + category.statusLabel:SetText("Total banned players: " .. expectedBans); + category.statusLabel:SizeToContents(); + + category.loading.inProgress = false; + --category.loading.banList = {}; + + category:SetLoading(false); + else + category.statusLabel:SetText("Processing ban data... (" .. math.Round((i / expectedBans) * 100) .. "%)"); + category.statusLabel:SizeToContents(); + + category.loading.currentIndex = i + 1; + category.loading.nextProcessTime = CurTime() + 0.01; + end; + if (!category.loading.banList[i]) then + continue; + end; + + local data = category.loading.banList[i]; + local steamID = data.steamID; + local name = data.playerName; + local length = tonumber(data.length); + local reason = string.gsub(data.reason, "\n", " "); + local admin = data.admin; + + category.loading.reverseLookup[steamID] = i + + if (!name or name == "") then + name = "Unknown"; + end; + + local lengthText = ""; + + if (length <= 0) then + lengthText = "Forever"; + else + local hours = math.Round(length / 3600); + + if (hours >= 1) then + lengthText = hours .. " hour(s)"; + else + lengthText = math.Round(length / 60) .. " minute(s)"; + end; + end; + + if (IsValid(category.list)) then + local panel = category.list:AddItem(name, steamID, lengthText, admin, reason); + + panel:SetToolTipSG(reason); + + function panel:OnMousePressed() + if (serverguard.player:HasPermission(LocalPlayer(), "Unban")) then + local menu = DermaMenu(); + menu:SetSkin("serverguard"); + + menu:AddOption(string.format("Unban %s", name), function() + serverguard.command.Run("unban", false, steamID); + end); + + if (serverguard.player:HasPermission(LocalPlayer(), "Edit Ban")) then + menu:AddOption(string.format("Edit Ban", name), function() + local banLength = nil + local baseb = vgui.Create("tiger.panel"); + baseb:SetTitle(string.format("Edit Ban", name)); + baseb:SetSize(500, 185); + baseb:Center(); + baseb:MakePopup(); + baseb:DockPadding(24, 24, 24, 48); + + local form = baseb:Add("tiger.list"); + form:Dock(FILL); + local banLengthChoice = vgui.Create("tiger.combobox"); + banLengthChoice:SetLabelText("New Ban length") + banLengthChoice:Dock(TOP); + + for k, v in pairs(serverguard.banLengths) do + banLengthChoice:AddChoice(v[1], v[2]); + end; + + banLengthChoice:AddChoice("Custom", -1); + + function banLengthChoice:OnSelect(index, value, data) + if (value == "Custom" and data == -1) then + util.CreateStringRequest("Ban Length", + function(text) + local length, lengthText, bClamped = util.ParseDuration(text); + + banLengthChoice:ChooseOption(lengthText, index); + banLength = length; + end, "Okay", + function(text) + end, "Cancel" + ); + else + banLength = data; + end; + end; + + form:AddPanel(banLengthChoice); + + local reasonEntry = vgui.Create("tiger.textentry"); + reasonEntry:SetLabelText("Reason"); + reasonEntry:SetText(reason); + reasonEntry:Dock(TOP); + form:AddPanel(reasonEntry); + + local completeButton = baseb:Add("tiger.button"); + completeButton:SetPos(4, 4); + completeButton:SetText("Complete"); + completeButton:SizeToContents(); + + function completeButton:DoClick() + if (!banLength) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please select a valid ban length!"); + return; + end; + + if (string.Trim(reasonEntry:GetValue()) == "") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "Please enter a reason!"); + return; + end; + + RunConsoleCommand("serverguard_editban", steamID, banLength, name, reasonEntry:GetValue()); + + baseb:Remove(); + end; + + local cancelButton = baseb:Add("tiger.button"); + cancelButton:SetPos(4, 4); + cancelButton:SetText("Cancel"); + cancelButton:SizeToContents(); + + function cancelButton:DoClick() + baseb:Remove(); + end; + + function baseb:PerformLayout(w, h) + completeButton:SetPos(w - (completeButton:GetWide() + 24), h - (completeButton:GetTall() + 14)); + + cancelButton:SetPos(0, h - (cancelButton:GetTall() + 14)); + cancelButton:MoveLeftOf(completeButton, 14); + end; + end); + end + + menu:AddOption("Copy Steam ID", function() + SetClipboardText(steamID); + end) + + hook.Call("serverguard.panel.PlayerUnbanContext", nil, menu, name, steamID, admin, reason); + menu:Open(); + end; + end; + + end; + end; +end; + +serverguard.menu.AddSubCategory("Lists", category); + +serverguard.netstream.Hook("sgGetBanCount", function(data) + expectedBans = data; + + if (IsValid(category.statusLabel)) then + if (expectedBans == 0) then + category.statusLabel:SetText("Total banned players: " .. expectedBans); + category.statusLabel:SizeToContents(); + else + category.statusLabel:SetText("Receiving ban data..."); + category.statusLabel:SizeToContents(); + end; + end; +end); +serverguard.netstream.Hook("sgRemoveBan", function(data) + expectedBans = expectedBans - 1 + category.loading.banList[category.loading.reverseLookup[data] or 0] = nil + if (IsValid(category.list)) then + category.list:SetVisible(false) + category.list:Clear() + end + if (category.loading) then + category.loading.inProgress = true; + category.loading.currentIndex = 1; + category.loading.nextProcessTime = CurTime(); + end +end) + +serverguard.netstream.Hook("sgNewBan", function(data) + expectedBans = expectedBans + 1 + category.loading.maxIndex = category.loading.maxIndex + 1; + category.loading.banList[category.loading.maxIndex] = data; + if (IsValid(category.list)) then + category.list:SetVisible(false) + category.list:Clear() + end + if (category.loading) then + category.loading.inProgress = true; + category.loading.currentIndex = 1; + category.loading.nextProcessTime = CurTime(); + end +end) + +serverguard.netstream.Hook("sgGetBanListChunk", function(banList) + if (IsValid(category.list)) then + category.list:SetVisible(false); -- doing this before removing plays nicer with tooltips + category.list:Clear(); + end; + + if (category.loading) then + category:SetLoading(true); + + category.loading.inProgress = true; + category.loading.currentIndex = 1; + category.loading.banList = banList + category.loading.maxIndex = #category.loading.banList + category.loading.nextProcessTime = CurTime(); + category.loading.reverseLookup = {} + end; +end); + +hook.Add("serverguard.menu.Close", "serverguard.BanList", function() + if (IsValid(category.list)) then + for k, v in ipairs(category.list:GetCanvas():GetChildren()) do + if (IsValid(v.tooltip_sg)) then + v:OnCursorExited(); + end; + end; + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/command_list.lua b/garrysmod/addons/admin-sg/lua/modules/gui/command_list.lua new file mode 100644 index 0000000..ab9c752 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/command_list.lua @@ -0,0 +1,38 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {}; + +category.name = "Command list"; +category.material = "serverguard/menuicons/icon_commands.png"; + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Command list") + base.panel:Dock(FILL) + + base.panel.list = base.panel:Add("tiger.list") + base.panel.list:Dock(FILL) + + base.panel.list:AddColumn("COMMAND", 190) + base.panel.list:AddColumn("DESCRIPTION", 250) + base.panel.list:AddColumn("ARGUMENTS", 150) + + hook.Call("serverguard.panel.CommandList", nil, base.panel.list); + + local commands = serverguard.command:GetTable() + + for k, data in pairs(commands) do + if (!data.permissions or serverguard.player:HasPermission(LocalPlayer(), data.permissions)) then + base.panel.list:AddItem(data.command, data.help, serverguard.command:GetArgumentsString(data)); + end; + end; +end; + +function category:Update(base) +end + +serverguard.menu.AddSubCategory("Information", category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_base.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_base.lua new file mode 100644 index 0000000..debbbcd --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_base.lua @@ -0,0 +1,649 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + +local panel = {} + +surface.CreateFont("tiger.base.search", {font = "Arial", size = 12, weight = 400}); +surface.CreateFont("tiger.base.footer", {font = "Arial", size = 14, weight = 400}) +surface.CreateFont("tiger.base.section", {font = "Helvetica", size = 14, weight = 400}) + +serverguard.themes.CreateDefault("tiger_base_bg", Color(232, 231, 239), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_outline", Color(190, 190, 190), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_footer_bg", Color(255, 255, 255), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_footer_label", Color(31, 153, 228), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_footer_hint", Color(141, 142, 134), "tiger.base") + +-- Section. +serverguard.themes.CreateDefault("tiger_base_section_icon", Color(141, 142, 134), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_section_icon_selected", Color(31, 153, 228), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_section_label", Color(141, 142, 134), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_section_label_selected", Color(31, 153, 228), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_section_outline", Color(190, 190, 190), "tiger.base") +serverguard.themes.CreateDefault("tiger_base_section_selected", Color(232, 231, 239), "tiger.base") + +function panel:Init() + local theme = serverguard.themes.GetCurrent() + + self.sections = {} + self.searchString = ""; + self.lastSection = nil + + self:DockPadding(162, 22, 22, 46); + + self.sectionList = self:Add("tiger.list") + self.sectionList:DockPadding(0, 0, 0, 0) + self.sectionList.list:SetUseSizeLimit(false); + + function self.sectionList:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + draw.SimpleRect(0, 0, w, h, theme.tiger_list_bg) + draw.SimpleRect(w -1, 0, 1, h, theme.tiger_list_outline) + end + + self.bFooterEnabled = true; + self.bUseSmallPanels = false; + + self.updatePanel = self:Add("Panel"); + self.updatePanel:SetMouseInputEnabled(true); + self.updatePanel.OnMousePressed = function(_self, keyCode) + if (self.updateNotify) then + self.updateNotify = false; + + self.updatePanel:SetCursor("none"); + self.updatePanel:SetToolTipSG(); + + timer.Simple(3, function() + self.updateNotify = nil; + end); + end; + end; + + self.copyright = self:Add("DLabel") + self.copyright:SetMouseInputEnabled(true) + self.copyright:SetText("Thriving Ventures Ltd (modded by Octothorp Team)") + self.copyright:SetCursor("hand") + self.copyright:SetToolTipSG("Click here to visit the ServerGuard website!") + self.copyright:SetFont("tiger.base.footer") + self.copyright:SetTextColor(theme.tiger_base_footer_label) + + function self.copyright:DoClick() + gui.OpenURL("http://gmodserverguard.com/?utm_source=serverguard&utm_medium=organic&utm_content=footerlink&utm_campaign=serverguard") + end + + self:SetupSearchPanel(); +end + +function panel:SetupSearchPanel() + self.searchPanel = vgui.Create("Panel", self); + self.searchPanel:SetSize(128, 22); + self.searchPanel:Dock(TOP); + self.searchPanel:SetVisible(false); + + function self.searchPanel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + local color = theme.tiger_list_panel_list_hover; + + draw.SimpleRect(0, 0, width - 1, height, color); + end; + + self.searchPanel.label = self.searchPanel:Add("DLabel"); + self.searchPanel.label:SetTextColor(Color(0, 0, 0)); + self.searchPanel.label:SetSize(self.searchPanel:GetWide(), self.searchPanel:GetTall()); + self.searchPanel.label:SetContentAlignment(5); + self.searchPanel.label:SetFont("tiger.base.search"); + self.searchPanel.label:SetText(""); + + serverguard.themes.AddPanel(self.searchPanel.label, "tiger_list_panel_label_hover"); +end; + +function panel:OnKeyCodePressed(keyCode) + local key = input.GetKeyName(keyCode); + + if (!key) then return; end; + + local matches = self:GetSearchMatches(self.searchString) + local matchAmount = #matches; + local bUseSmallPanels = self:GetUseSmallPanels(); + local nextMatchAmount = #self:GetSearchMatches(self.searchString..key); + + if (key == "BACKSPACE") then + self.searchString = string.sub(self.searchString, 1, #self.searchString - 1); + elseif (key == "ENTER" and matchAmount == 1) then + -- HACK because docking is awfully implemented. + local id = 0; + local y = 0; + + for k, v in SortedPairs(self.sections) do + if (k == matches[1].name) then + y = id * 120; + break; + end; + + id = id + 1; + end; + + self:ResetSearch(); + + if (id == 0) then + self.sectionList.list.VBar:SetScroll(-100); + else + self.sectionList.list.VBar:SetScroll(id * 120); + end; + + matches[1].icon:DoClick(); + + return; + elseif (key == "SPACE" or string.match(key, "[a-z]")) then + if (key == "SPACE") then + key = " "; + end; + + self.searchString = self.searchString..key; + end; + + self.searchPanel.label:SetText(self.searchString); + + if (string.len(self.searchString) > 0 and !self.lastSearch) then + self.searchPanel:SetVisible(true); + self.sectionList.list.VBar:SetScroll(-100); + + self.lastSearch = true; + elseif (string.len(self.searchString) < 1) then + self.searchPanel:SetVisible(false); + + if (self.lastSearch) then + self.sectionList.list.VBar:SetScroll(-100); + end; + + self.lastSearch = nil; + end; + + for k, v in pairs(self.sections) do + if (IsValid(v)) then + if (!string.StartWith(string.lower(k), self.searchString)) then + v:SetSize(0, 0); + else + if (!bUseSmallPanels) then + v:SetSize(128, 120); + else + v:SetSize(64, 80); + end; + end; + end; + end; +end; + +function panel:ResetSearch() + local bUseSmallPanels = self:GetUseSmallPanels(); + + for k, v in pairs(self.sections) do + if (IsValid(v)) then + if (!bUseSmallPanels) then + v:SetSize(128, 120); + else + v:SetSize(64, 80); + end; + end; + end; + + self.searchString = ""; + self.lastSearch = nil; + + self.searchPanel:SetVisible(false); + self.searchPanel.label:SetText(""); +end; + +function panel:GetSearchMatches(searchString) + local matches = {}; + + for k, v in pairs(self.sections) do + if (string.StartWith(string.lower(k), searchString)) then + table.insert(matches, v); + end; + end; + + return matches; +end; + +function panel:GetFooterEnabled() + return self.bFooterEnabled; +end; + +function panel:SetFooterEnabled(bValue) + local bRealValue = tobool(bValue); + self.updatePanel:SetVisible(bValue); + self.copyright:SetVisible(bValue); + + self:DockPadding(162, 22, 22, ((bRealValue and 46) or 22)); + + self.bFooterEnabled = bRealValue; +end; + +function panel:GetUseSmallPanels() + return self.bUseSmallPanels; +end; + +function panel:UseSmallPanels(bValue) -- should only ever be called immediately after creation + local bRealValue = tobool(bValue); + + self.bUseSmallPanels = bRealValue; + self:DockPadding(76, 12, 12, ((self:GetFooterEnabled() and 36) or 12)); +end; + +function panel:SetUpdateNotification(bToggled) + local theme = serverguard.themes.GetCurrent(); + local themeCopy = table.Copy(theme); + + self.updateNotify = tobool(bToggled); + self.updateTarget = Color(255, 210, 0); + self.updateCurrent = themeCopy.tiger_base_footer_bg; + + if (bToggled) then + self.updatePanel:SetCursor("hand"); + self.updatePanel:SetToolTipSG("Version " .. serverguard.GetLatestVersion() .. " of ServerGuard is now available!\nIt is recommended to update this server's version of ServerGuard."); + else + self.updatePanel:SetCursor("none"); + self.updatePanel:SetToolTipSG(); + end; +end; + +function panel:AddSection(name, material, callback, flag) + local base = vgui.Create("Panel") + + if (self:GetUseSmallPanels()) then + base:SetSize(64, 80); + else + base:SetSize(128, 120); + end; + + base:Dock(TOP) + + base.flag = flag; + base.name = name; + + local function ShortenText(text, font, max_w) + local split = string.ToTable(text) + local res = "" + for k,v in pairs(split) do + if (util.GetTextSize(font, res .. v .. "...") > max_w) then + return res .. "..." + else + res = res .. v + end + end + return res + end + + local function DrawWrappedText(text, font, w, h, color, align_h, align_v, max_w) + local explode = string.Explode(" ", text) + local currentText = "" + local line = 0 + local y = select(2, util.GetTextSize(font, " ")) + for k,v in pairs(explode) do + if (util.GetTextSize(font, currentText .. " " .. v) > max_w) then + if (currentText != "") then + draw.SimpleText(currentText, font, w, h + y * line, color, align_h, align_v) + currentText = "" + line = line + 1 + elseif (currentText == "" and k == #explode) then + draw.SimpleText(ShortenText(v, font, max_w), font, w, h + y * line, color, align_h, align_v) + break + end + end + currentText = currentText .. " " .. v + if (k == #explode and currentText != "") then + draw.SimpleText(currentText, font, w, h + y * line, color, align_h, align_v) + end + end + end + + function base:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + if (self.selected) then + draw.SimpleRect(0, 0, w, h, theme.tiger_base_section_selected) + + draw.SimpleRect(0, 0, w, 1, theme.tiger_base_section_outline) + draw.SimpleRect(0, h -1, w, 1, theme.tiger_base_section_outline) + + DrawWrappedText(name, "tiger.base.section", w / 2, h - 24, theme.tiger_base_section_label_selected, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, w) + + if (self.icon:GetColor() != theme.tiger_base_section_icon_selected) then + self.icon:SetColor(theme.tiger_base_section_icon_selected) + end + else + DrawWrappedText(name, "tiger.base.section", w / 2, h - 24, theme.tiger_base_section_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, w) + + if (self.icon:GetColor() != theme.tiger_base_section_icon) then + self.icon:SetColor(theme.tiger_base_section_icon) + end + end + end + + if (base.flag) then + function base:Think() + local hasPermission = false; + + if (type(self.permissions) == "table") then + for k, v in pairs(self.permissions) do + if (serverguard.player:HasPermission(LocalPlayer(), v)) then + hasPermission = true; + break; + end; + end; + else + hasPermission = serverguard.player:HasPermission(LocalPlayer(), self.permissions); + end; + + if (!hasPermission) then + if (!self.icon:GetDisabled()) then + local theme = serverguard.themes.GetCurrent() + + self.icon:SetDisabled(true) + self.icon:SetColor(theme.tiger_base_section_icon) + self.icon:SetToolTipSG("You do not have permission to view this!") + + self.section:SetVisible(false) + end + else + if (self.icon:GetDisabled()) then + self.icon:SetDisabled(false) + self.icon:SetToolTipSG(nil) + end + end + end + end + + base.icon = base:Add("DImageButton") + + if (self:GetUseSmallPanels()) then + base.icon:SetPos(16, 14); + else + base.icon:SetPos(32, 20); + end; + + base.icon:SetImage(material) + + if (self:GetUseSmallPanels()) then + base.icon:SetSize(32, 32); + else + base.icon:SetSize(64, 64); + end; + + function base.icon.DoClick() + if (IsValid(self.lastSection)) then + self.lastSection.selected = false + self.lastSection.section:SetVisible(false) + end + + base.selected = true + base.section:SetVisible(true) + base.section:callback() + + self.lastSection = base + end + + base.icon.OnMousePressed = base.icon.DoClick; + + self.sectionList:AddPanel(base) + + local section = self:Add("Panel") + section:Dock(FILL) + section:SetVisible(false) + + section.callback = callback + base.section = section + + section:callback() + + self.sections[name] = base; +end + +function panel:GetSections() + return self.sections +end + +function panel:SetSectionSelected(uniqueID) + if (!self.sections[uniqueID]) then + return; + end; + + self.sections[uniqueID].icon.DoClick(); +end; + +function panel:PerformLayout() + local w, h = self:GetSize() + + self.sectionList:SetPos(1, 1) + self.sectionList:SetSize(((self:GetUseSmallPanels() and 64) or 128), h - ((self:GetFooterEnabled() and 27) or 2)) + + self.copyright:SizeToContents() + self.copyright:SetPos(8, self:GetTall() - (self.copyright:GetTall() + self.copyright:GetTall() / 2)) + + self.updatePanel:SetSize(self:GetWide(), 26); + self.updatePanel:SetPos(0, self:GetTall() - 26); +end + +function panel:Rebuild() + local lastSection; + + if (IsValid(self.lastSection)) then + lastSection = self.lastSection.name; + + self.lastSection.selected = false + self.lastSection.section:SetVisible(false) + self.lastSection.section:Remove(); + end; + + for k, v in pairs(self.sections) do + if (IsValid(v)) then + if (IsValid(v.section)) then + v.section:SetVisible(false); + v.section:Remove(); + end; + + v:SetVisible(false); + v:Remove(); + end; + end; + + self.sectionList:Clear(); + self:SetupSearchPanel(); + + self.sectionList:AddPanel(self.searchPanel); + + for k, data in util.SortedPairsByMemberValue(serverguard.menu:GetStored(), "name") do + if (data.permissions != nil) then + if (serverguard.player:HasPermission(LocalPlayer(), data.permissions)) then + self:AddSection(data.name, data.material, function(base) + if (base.created) then + data:Update(base); + else + data:Create(base); + data:Update(base); + for v, data_s in util.SortedPairsByMemberValue(serverguard.menu:GetStored()[data.name].submenus, "name") do + base.base:AddSection(data_s.name, data_s.material, function(categoryBase) + if (categoryBase.created) then + data_s:Update(categoryBase); + else + data_s:Create(categoryBase); + data_s:Update(categoryBase); + categoryBase.created = true; + end + end); + end; + base.created = true; + end + end, data.flag); + end; + else + self:AddSection(data.name, data.material, function(base) + if (base.created) then + data:Update(base); + else + data:Create(base); + data:Update(base); + for v, data_s in util.SortedPairsByMemberValue(serverguard.menu:GetStored()[data.name].submenus, "name") do + if (data_s.permissions == nil or serverguard.player:HasPermission(LocalPlayer(), data_s.permissions)) then + base.base:AddSection(data_s.name, data_s.material, function(categoryBase) + if (categoryBase.created) then + data_s:Update(categoryBase); + else + data_s:Create(categoryBase); + data_s:Update(categoryBase); + categoryBase.created = true; + end + end); + end + end; + base.created = true; + end; + end, data.flag); + end; + end; + + self:InvalidateLayout(true); + self:ResetSearch(); + + if (lastSection) then + self:SetSectionSelected(lastSection); + end; +end; + +function panel:Think() + local mousex = math.Clamp( gui.MouseX(), 1, ScrW()-1 ) + local mousey = math.Clamp( gui.MouseY(), 1, ScrH()-1 ) + + if (self.Dragging) then + local x = mousex -self.Dragging[1] + local y = mousey -self.Dragging[2] + + x = math.Clamp(x, 0, ScrW() -self:GetWide()) + y = math.Clamp(y, 0, ScrH() -self:GetTall()) + + self:SetPos(x, y) + end + + if (self.Hovered) then + if (gui.MouseY() < self.y +20) then + self:SetCursor("sizeall") + + return + end + end + + self:SetCursor("arrow") + + if (self:IsVisible()) then + for k, data in pairs(serverguard.menu:GetStored()) do + if (data.permissions == nil or serverguard.player:HasPermission(LocalPlayer(), data.permissions)) then + if (isfunction(data.Think)) then + data:Think(); + end; + end; + for v, data_s in pairs(serverguard.menu:GetStored()[data.name].submenus) do + if (data_s.permissions == nil or serverguard.player:HasPermission(LocalPlayer(), data_s.permissions)) then + if (isfunction(data_s.Think)) then + data_s:Think(); + end; + end; + end; + end; + end; +end + +function panel:OnMousePressed() + if (gui.MouseY() < self.y +20) then + self.Dragging = {gui.MouseX() -self.x, gui.MouseY() -self.y} + + self:MouseCapture(true) + + return + end +end + +function panel:OnMouseReleased() + self.Dragging = nil + + self:MouseCapture(false) +end + +function panel:OnCursorEntered() + self.Hovered = true +end + +function panel:OnCursorExited() + self.Hovered = false +end + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + draw.RoundedBox(4, 0, 0, width, height, theme.tiger_base_outline); + draw.RoundedBox(2, 1, 1, width - 2, height - 2, theme.tiger_base_bg); + + if (self:GetFooterEnabled()) then + local footerBG = theme.tiger_base_footer_bg; + + if (self.updateNotify == true) then + if (self.updateCurrent != self.updateTarget) then + for k, v in pairs(self.updateCurrent) do + local animateSpeed = math.abs(self.updateTarget[k] - self.updateCurrent[k]) * FrameTime() + 1; + self.updateCurrent[k] = math.Approach(self.updateCurrent[k], self.updateTarget[k], animateSpeed); + end; + end; + + footerBG = self.updateCurrent; + elseif (self.updateNotify == false) then + if (self.updateCurrent != theme.tiger_base_footer_bg[k]) then + for k, v in pairs(self.updateCurrent) do + local animateSpeed = math.abs(theme.tiger_base_footer_bg[k] - self.updateCurrent[k]) * FrameTime() + 1; + self.updateCurrent[k] = math.Approach(self.updateCurrent[k], theme.tiger_base_footer_bg[k], animateSpeed); + end; + end; + + footerBG = self.updateCurrent; + end; + + draw.SimpleRect(2, height - 26, width - 4, 24, footerBG); + draw.SimpleRect(1, height - 26, width - 2, 1, theme.tiger_base_outline); + else + draw.SimpleRect(1, height - 1, width - 2, 1, theme.tiger_base_outline); + end; + + DisableClipping(true); + for i = 1, 2 do + local color = Color(0, 0, 0, (255 /i) *0.2); + + surface.SetDrawColor(color); + + -- Right shadow. + surface.DrawRect(width - 1 + i, 1, 1, height - 1); + + -- Top shadow. + surface.DrawRect(1, - i, width - 1, 1); + + -- Left shadow. + surface.DrawRect(-i, 0, 1, height); + + -- Bottom shadow. + surface.DrawRect(1, height, width - 1, i); + end; + DisableClipping(false); +end; + +vgui.Register("tiger.base", panel, "EditablePanel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_button.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_button.lua new file mode 100644 index 0000000..82c6eda --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_button.lua @@ -0,0 +1,118 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ +]] + +surface.CreateFont("tiger.button", {font = "Istok", size = 14, weight = 800}); +surface.CreateFont("tiger.button.bold", {font = "Istok", size = 14, weight = 800}); +surface.CreateFont("tiger.button.boldblur", {font = "Istok", size = 14, weight = 800, blursize = 2}); + +local panel = {}; + +panel.color_bg = Color(241, 241, 241); +panel.color_outline = Color(190, 190, 190); +panel.color_text = Color(151, 137, 133); +panel.color_text_hovered = color_black; +panel.color_stripe = Color(255, 255, 255); +panel.color_hovered = Color(231, 231, 231); +panel.color_hovered_stripe = Color(241, 241, 241); + +serverguard.themes.CreateDefault("tiger_button_bg", Color(241, 241, 241), "tiger.button"); +serverguard.themes.CreateDefault("tiger_button_outline", Color(190, 190, 190), "tiger.button"); +serverguard.themes.CreateDefault("tiger_button_stripe", Color(255, 255, 255), "tiger.button"); +serverguard.themes.CreateDefault("tiger_button_text", Color(151, 137, 133), "tiger.button"); +serverguard.themes.CreateDefault("tiger_button_text_hovered", Color(0, 0, 0), "tiger.button"); +serverguard.themes.CreateDefault("tiger_button_hovered", Color(231, 231, 231), "tiger.button"); +serverguard.themes.CreateDefault("tiger_button_hovered_stripe", Color(241, 241, 241), "tiger.button"); + +AccessorFunc(panel, "m_sText", "Text"); +AccessorFunc(panel, "m_bBold", "Bold", FORCE_BOOL); + +function panel:Init() + self:SetText(""); + self:SetBold(false); +end; + +function panel:SizeToContents() + local width, height = util.GetTextSize("tiger.button", self.m_sText); + + self:SetSize(width +15, height +8); +end; + +function panel:OnCursorEntered() + self:SetCursor("hand"); + + self.hovered = true; +end; + +function panel:OnCursorExited() + self:SetCursor("arrow"); + + self.hovered = false; +end; + +function panel:OnMousePressed() + self:DoClick(); +end; + +function panel:DoClick() +end; + +function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent(); + local font = (self:GetBold() and "tiger.button.bold") or "tiger.button"; + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_button_outline); + + if (self.hovered) then + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_hovered); + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_hovered_stripe); + + draw.SimpleText(self.m_sText, font, w /2, h /2, theme.tiger_button_text_hovered, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); + + if (self:GetBold()) then + draw.SimpleText(self.m_sText, "tiger.button.boldblur", w /2, h /2, util.DimColor(theme.tiger_button_text_hovered, 50), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); + end; + else + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_bg); + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_stripe); + + draw.SimpleText(self.m_sText, font, w /2, h /2, theme.tiger_button_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); + + if (self:GetBold()) then + draw.SimpleText(self.m_sText, "tiger.button.boldblur", w /2, h /2, util.DimColor(theme.tiger_button_text, 50), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); + end; + end; + + DisableClipping(true); + for i = 1, 2 do + local color = Color(0, 0, 0, (255 /i) *0.15); + + surface.SetDrawColor(color); + + -- Right shadow. + surface.DrawRect(w -1 +i, 1, 1, h -1); + + -- Top shadow. + --surface.DrawRect(1, -i, w -1, 1); + + -- Left shadow. + --surface.DrawRect(-i, 0, 1, h); + + -- Bottom shadow. + surface.DrawRect(1, h, w -1, i); + end; + DisableClipping(false); +end; + +vgui.Register("tiger.button", panel, "Panel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_checkbox.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_checkbox.lua new file mode 100644 index 0000000..7f30bd4 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_checkbox.lua @@ -0,0 +1,144 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + +local panel = {}; + +AccessorFunc(panel, "m_Material", "Material"); + +function panel:Init() + local theme = serverguard.themes.GetCurrent(); + + self.checked = false; + + self.currentLocation = 0; + self.targetLocation = 0; + + self.currentAlpha = 0; + self.targetAlpha = 0; + + self:SetTall(30); + + self.label = self:Add("DLabel"); + self.label:SetText("Undefined"); + self.label:SizeToContents(); + self.label:Dock(LEFT); + self.label:DockMargin(8, 0, 0, 0); + self.label:SetSkin("serverguard"); + self.label:SetColor(theme.tiger_list_panel_label); + + self:SetMaterial(Material("icon16/tick.png")); +end; + +function panel:PerformIconLayout() + local material = self:GetMaterial(); + local width = self:GetWide(); + + if (self.checked) then + self.currentAlpha = 0; + self.targetAlpha = 255; + + self.currentLocation = width - material:Width(); + self.targetLocation = width - (material:Width() * 2); + + else + self.currentAlpha = (self.currentAlpha != 0) and 255 or 0; + self.targetAlpha = 0; + + self.currentLocation = width - (material:Width() * 2); + self.targetLocation = width - (material:Width() * 3); + end; +end; + +function panel:SetChecked(bValue) + self.checked = tobool(bValue); + + self:PerformIconLayout(); +end; + +function panel:GetChecked() + return self.checked; +end; + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + local material = self:GetMaterial(); + local animateSpeed; + + if (self.currentAlpha != self.targetAlpha) then + animateSpeed = math.abs(self.targetAlpha - self.currentAlpha) * 8; + self.currentAlpha = math.Approach(self.currentAlpha, self.targetAlpha, animateSpeed * FrameTime()); + end; + + if (self.currentLocation != self.targetLocation) then + animateSpeed = math.abs(self.targetLocation - self.currentLocation) * 4; + self.currentLocation = math.Approach(self.currentLocation, self.targetLocation, animateSpeed * FrameTime()); + end; + + if (self.hovered) then + draw.SimpleRect(0, 0, width, height, theme.tiger_list_panel_list_hover); + end; + + surface.SetDrawColor(Color(255, 255, 255, self.currentAlpha)); + surface.SetMaterial(self:GetMaterial()); + surface.DrawTexturedRect(self.currentLocation, self:GetTall() / 2 - material:Height() / 2, material:Width(), material:Height()); +end; + +function panel:OnCursorEntered() + self:SetCursor("hand"); + self.hovered = true; + + local theme = serverguard.themes.GetCurrent(); + + self.label:SetColor(theme.tiger_list_panel_label_hover); + self.label:InvalidateLayout(true); +end; + +function panel:OnCursorExited() + self:SetCursor("arrow"); + self.hovered = nil; + + local theme = serverguard.themes.GetCurrent(); + + self.label:SetColor(theme.tiger_list_panel_label); + self.label:InvalidateLayout(true); +end; + +function panel:OnMousePressed() + self:SetChecked(!self.checked); + self:OnChange(self.checked); +end; + +function panel:SetText(text) + self.label:SetText(text); + self.label:SizeToContents(); +end; + +function panel:GetText() + return self.label:GetText(); +end; + +function panel:PerformLayout(width, height) + if (self.lastWidth or 0) ~= width or (self.lastHeight or 0) ~= height then + self.lastWidth = width + self.lastHeight = height + self:PerformIconLayout() + end +end; + +function panel:OnChange(bValue) +end; + +vgui.Register("tiger.checkbox", panel, "Panel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_combobox.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_combobox.lua new file mode 100644 index 0000000..fef70c0 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_combobox.lua @@ -0,0 +1,125 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + +surface.CreateFont("tiger.combobox.label", { + font = "Istok", + weight = 300, + size = 12 +}); + +local panel = {}; + +AccessorFunc(panel, "m_sLabelText", "LabelText", FORCE_STRING); + +function panel:Init() + self:SetTall(30); + self:SetLabelText(""); +end; + +function panel:OpenMenu(pControlOpener) + if (pControlOpener) then + if (pControlOpener == self.TextEntry) then + return; + end; + end; + + if (#self.Choices == 0) then + return; + end; + + if (IsValid(self.Menu)) then + self.Menu:Remove(); + self.Menu = nil; + end; + + self.Menu = DermaMenu(); + self.Menu:SetSkin("serverguard"); + + local sorted = {}; + + for k, v in pairs(self.Choices) do + table.insert(sorted, { + id = k, + data = v + }); + end; + + for k, v in SortedPairsByMemberValue(sorted, "data") do + self.Menu:AddOption(v.data, function() + self:ChooseOption(v.data, v.id); + end); + end; + + local x, y = self:LocalToScreen(0, self:GetTall()); + + self.Menu:SetMinimumWidth(self:GetWide()); + self.Menu:Open(x, y, false, self); + + local width, height = self.Menu:GetSize(); + + self.Menu:SetTall(0); + self.Menu:SizeTo(width, height, 1, 0, 1, function() end); +end; + +function panel:SetText() +end; + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + local text = self:GetSelected(); + + if (self.hovered) then + draw.SimpleRect(0, 0, width, height, theme.tiger_list_panel_list_hover); + + surface.SetTextColor(theme.tiger_list_panel_label_hover) + surface.SetFont("tiger.combobox.label"); + + surface.SetTextPos(8, 4); + surface.DrawText(self:GetLabelText()); + else + surface.SetTextColor(theme.tiger_list_panel_list_hover); + surface.SetFont("tiger.combobox.label"); + + surface.SetTextPos(8, 4); + surface.DrawText(self:GetLabelText()); + + surface.SetTextColor(theme.tiger_list_panel_label); + end; + + if (!text) then + return; + end; + + surface.SetFont(self:GetFont()); + local textWidth, textHeight = surface.GetTextSize(self:GetText()); + + surface.SetTextPos(8, height - textHeight - 2); + surface.DrawText(text); + + return false; +end; + +function panel:OnCursorEntered() + self:SetCursor("hand"); + self.hovered = true; +end; + +function panel:OnCursorExited() + self:SetCursor("arrow"); + self.hovered = nil; +end; + +vgui.Register("tiger.combobox", panel, "DComboBox"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_dialog.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_dialog.lua new file mode 100644 index 0000000..a807d85 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_dialog.lua @@ -0,0 +1,123 @@ + +surface.CreateFont("tiger.dialog.title", { + font = "Roboto", + weight = 100, + size = ScreenScale(18) +}) + +surface.CreateFont("tiger.dialog.text", { + font = "Roboto", + weight = "100", + size = ScreenScale(8) +}); + +local panel = {}; + +function panel:Init() + self.buttons = {}; + self.dialogPanel = vgui.Create("tiger.panel", self); + self.dialogPanel:DockPadding(ScrW() / 4, 4, ScrW() / 4, 4); + + function self.dialogPanel:Paint(width, height) + surface.SetDrawColor(Color(255, 255, 255, 255)); + surface.DrawRect(0, 0, width, height); + end; + + self.dialogPanel.titleLabel = self.dialogPanel:Add("DLabel"); + self.dialogPanel.titleLabel:SetFont("tiger.dialog.title"); + self.dialogPanel.titleLabel:SetColor(Color(151, 137, 133)); + self.dialogPanel.titleLabel:Dock(TOP); + + self.dialogPanel.textLabel = self.dialogPanel:Add("DLabel"); + self.dialogPanel.textLabel:SetFont("tiger.dialog.text"); + self.dialogPanel.textLabel:SetColor(Color(131, 107, 103)); + self.dialogPanel.textLabel:Dock(TOP); + + self.dialogPanel.buttonList = self.dialogPanel:Add("tiger.panel"); + self.dialogPanel.buttonList:Dock(TOP); + self.dialogPanel.buttonList.Paint = function() end; + + self:SetAlpha(0); +end; + +function panel:AddButton(text, callback) + local realText = text; + local button = self.dialogPanel.buttonList:Add("tiger.button"); + + if (string.sub(text, 1, 1) == "&") then + realText = string.sub(text, 2, string.len(text)); + button:SetBold(true); + end; + + button:SetText(realText); + button:SizeToContents(); + button:DockMargin(0, 0, 4, 0); + button:Dock(RIGHT); + button.DoClick = callback; + + table.insert(self.buttons, button); +end; + +function panel:SetTitle(text) + self.dialogPanel.titleLabel:SetText(text); +end; + +function panel:SetText(text) + self.dialogPanel.textLabel:SetText(text); +end; + +function panel:Paint(width, height) + surface.SetDrawColor(Color(0, 0, 0, self:GetAlpha())); + surface.DrawRect(0, 0, width, height); +end; + +function panel:SizeToContents() + local buttonHeight = 0; + + for k, v in pairs(self.buttons) do + if (v:GetTall() > buttonHeight) then + buttonHeight = v:GetTall(); + end; + end; + + self.dialogPanel.titleLabel:SizeToContents(); + self.dialogPanel.textLabel:SizeToContents(); + + self:SetSize(ScrW(), ScrH()); + self.dialogPanel:SetSize(ScrW(), self.dialogPanel.titleLabel:GetTall() + self.dialogPanel.textLabel:GetTall() + buttonHeight + 16); +end; + +function panel:Center() + self:SetPos(0, 0); + self.dialogPanel:Center(); +end; + +function panel:FadeIn(alpha, duration) + self:AlphaTo(alpha or 250, duration or 0.5, 0, function() end); + + if (!self.dialogPanel.realHeight) then + self.dialogPanel.realHeight = self.dialogPanel:GetTall(); + end; + + self.dialogPanel:SetTall(0); + self.dialogPanel:SizeTo(self.dialogPanel:GetWide(), self.dialogPanel.realHeight, duration or 0.5, 0, 2, function() end); +end; + +function panel:FadeOut(duration, callback) + if (!callback) then + callback = function() end; + end; + + self:AlphaTo(0, duration or 0.5, 0, function() end); + + self.dialogPanel:SetTall(self.dialogPanel.realHeight); + self.dialogPanel:SizeTo(self.dialogPanel:GetWide(), 0, duration or 0.5, 0, 2, callback); +end; + +function panel:Close() + self:FadeOut(); + self:SetMouseInputEnabled(false); + self:SetKeyBoardInputEnabled(false); +end; + +vgui.Register("tiger.dialog", panel, "DPanel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_divider.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_divider.lua new file mode 100644 index 0000000..ef71a27 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_divider.lua @@ -0,0 +1,120 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ +]] + +local panel = {} + +surface.CreateFont("tiger.divider.leftLabel", {font = "Istok", size = 18, weight = 400}) +surface.CreateFont("tiger.divider.rightLabel", {font = "Roboto", size = 18, weight = 800}) + +serverguard.themes.CreateDefault("tiger_divider_bg", Color(255, 255, 255), "tiger.divider") +serverguard.themes.CreateDefault("tiger_divider_outline", Color(190, 190, 190), "tiger.divider") +--serverguard.themes.CreateDefault("tiger_divider_label", Color(141, 127, 123), "tiger.divider") + +-- The divider. +serverguard.themes.CreateDefault("tiger_divider_panel_outline", Color(190, 190, 190), "tiger.divider") +serverguard.themes.CreateDefault("tiger_divider_panel_hover", Color(31, 153, 228), "tiger.divider") + +serverguard.themes.CreateDefault("tiger_divider_left_bg", Color(245, 245, 245), "tiger.divider") +serverguard.themes.CreateDefault("tiger_divider_left_label", Color(141, 142, 134), "tiger.divider") +serverguard.themes.CreateDefault("tiger_divider_left_label_hovered", Color(231, 232, 224), "tiger.divider") + +serverguard.themes.CreateDefault("tiger_divider_right_label", Color(31, 153, 228), "tiger.divider") +serverguard.themes.CreateDefault("tiger_divider_right_label_hovered", Color(241, 242, 234), "tiger.divider") + +function panel:Init() + self.list = self:Add("tiger.list") + self.list:Dock(FILL) + self.list:DockPadding(1, 1, 1, 1) + self.list:DisablePaint() +end + +function panel:AddRow(leftText, rightText) + local base = vgui.Create("Panel") + base:SetTall(32) + base:Dock(TOP) + + local columnWidth = self:GetWide() - (self:GetWide() * 0.4) + local textFragments = string.Explode(" ", rightText) + local cutText = "" + + for k, v in pairs(textFragments) do + local fragment = cutText .. " " .. v + local fragmentWidth = util.GetTextSize("tiger.divider.rightLabel", fragment .. " >") + + if (fragmentWidth < columnWidth) then + cutText = fragment + else + cutText = string.sub(fragment, 1, string.len(fragment) - 5) .. "... >" + break + end + end + + if (string.sub(cutText, string.len(cutText), string.len(cutText)) != ">") then + cutText = cutText .. " >" + end + + function base:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + local width = util.GetTextSize("tiger.divider.leftLabel", leftText) + + draw.RoundedBox(2, 0, 0, w *0.35, h, theme.tiger_divider_left_bg) + + if (self.hovered) then + draw.SimpleRect(0, 0, w, h, theme.tiger_divider_panel_hover) + draw.SimpleText(leftText, "tiger.divider.leftLabel", (w *0.35 -width) /2, h /2, theme.tiger_divider_left_label_hovered, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(cutText, "tiger.divider.rightLabel", w *0.365, h /2, theme.tiger_divider_right_label_hovered, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + else + draw.SimpleText(leftText, "tiger.divider.leftLabel", (w *0.35 -width) /2, h /2, theme.tiger_divider_left_label, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(cutText, "tiger.divider.rightLabel", w *0.365, h /2, theme.tiger_divider_right_label, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + draw.SimpleRect(w *0.35, 0, 1, h, theme.tiger_divider_panel_outline) + draw.SimpleRect(0, h -1, w, 1, theme.tiger_divider_panel_outline) + end + + function base:OnCursorEntered() + self:SetCursor("hand") + + self.hovered = true + end + + function base:OnCursorExited() + self:SetCursor("arrow") + + self.hovered = nil + end + + function base:OnMousePressed() + if (self.DoClick) then + self:DoClick() + end + end + + self.list:AddPanel(base) + return base +end + +function panel:Clear() + self.list:Clear() +end + +function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_divider_outline) + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_divider_bg) +end + +vgui.Register("tiger.divider", panel, "Panel") \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_linegraph.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_linegraph.lua new file mode 100644 index 0000000..d67d185 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_linegraph.lua @@ -0,0 +1,180 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + + +local color_label = Color(49, 49, 49); + +surface.CreateFont("tiger.linegraph", {font = "Tahoma", size = 12, weight = 400}); + +local math = math; +local surface = surface; +local lineTexture = surface.GetTextureID("trails/smoke"); +local baseX = 30; +local baseY = 25; +local spaceX = 0; + +local panel = {}; + +function panel:Init() + self.data = {{}, {}, {}}; + self.min, self.max = 0, 0; +end + +function panel:SetStepping(step) + self.data[1] = {}; + + step = math.max(step, 1); + + for i = self.min, self.max, step do + table.insert(self.data[1], i); + end; + + self.stepping = step; +end; + +function panel:SetRange(min, max, step) + self.data[1] = {}; + + step = math.max(step, 1); + + for i = min, max, step do + table.insert(self.data[1], i); + end; + + self.min = tonumber(min); + self.max = tonumber(max); + self.stepping = step; +end; + +function panel:AddHorizontal(name) + table.insert(self.data[2], name); +end; + +function panel:ResetLines() + self.data[3] = {}; +end; + +function panel:AddLine(name, color, ...) + local data = {...} + local line = {} + + for i = 1, #self.data[2] do + line[i] = data[i] + end + + line.name = name + line.color = color + + if (name) then + spaceX = 80 + end + + table.insert(self.data[3], line) +end + +function panel:SetLine(name, color, ...) + self.data[3] = {} + + local data = {...} + local line = {} + + for i = 1, #self.data[2] do + line[i] = data[i] + end + + line.name = name + line.color = color + + if (name) then + spaceX = 80 + end + + table.insert(self.data[3], line) +end + +function panel:Paint(w, h) + + local lineSpace = (h -baseY *2) /(#self.data[1] -1) + + for i = 1, #self.data[1] do + local left = self.data[1][i] + local y = (h -baseY) -lineSpace *(i -1) + + draw.SimpleText(left, "tiger.linegraph", baseX -10, y, color_label, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + + --if (i < #self.data[1] -1) then + surface.SetDrawColor(i % 2 == 0 and Color(230, 230, 230, 100) or color_transparent) + surface.DrawRect(baseX, y -lineSpace, (w -baseX *2) -spaceX, lineSpace) + --end + + surface.SetDrawColor(Color(189, 189, 189, 255)) + surface.DrawLine(baseX, y, baseX -6, y) + end + + surface.SetDrawColor(Color(189, 189, 189, 255)) + surface.DrawLine(baseX, h -baseY, (w -baseX) -spaceX, h -baseY) + surface.DrawLine(baseX, baseY, baseX, h -baseY) + + local lineSpace2 = ((w -baseX *2) -spaceX) /(#self.data[2] -1) + local x = baseX + + for i = 1, #self.data[2] do + local name = self.data[2][i] + + draw.SimpleText(name, "tiger.linegraph", x, h -(baseY -16), color_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(Color(189, 189, 189, 255)) + surface.DrawLine(x, h -baseY, x, h -baseY +6) + + for i2 = 1, #self.data[3] do + local value = self.data[3][i2][i] + + if (value) then + local color = self.data[3][i2].color + local nextValue = self.data[3][i2][math.min(i +1, #self.data[3][i2])] + local startY = (h -baseY) -((lineSpace /self.stepping) *(value -self.min)) + local endX = i == #self.data[3][i2] and x or x +lineSpace2 + local endY = i == #self.data[3][i2] and startY or (h -baseY) -((lineSpace /self.stepping) *(nextValue -self.min)) + + surface.SetDrawColor(color) + surface.SetTexture(lineTexture) + surface.DrawLineEx(x, startY, endX, endY, 3) + + surface.DrawRect(x -2, startY -2, 4, 4) + + draw.SimpleText(value, "tiger.linegraph", x +6, startY +6, color_label, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + + x = x +lineSpace2 + end + + if (spaceX > 0) then + for i = 1, #self.data[3] do + local name = self.data[3][i].name + + local nameY = baseY +((20 *#self.data[3] /#self.data[3]) *i) + local color = self.data[3][i].color + + surface.SetDrawColor(color) + surface.DrawRect(w -spaceX /2 -45, nameY, 5, 5) + + draw.SimpleText(name, "tiger.linegraph", w -spaceX /2 -35, nameY +2, color_label, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end +end + +vgui.Register("tiger.linegraph", panel, "Panel") \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_list.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_list.lua new file mode 100644 index 0000000..0fc3a4f --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_list.lua @@ -0,0 +1,433 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ +]] + +local panel = {} + +local SORT_ASCENDING = 1 +local SORT_DESCENDING = 2 + +local sort_material_descending = Material("icon16/bullet_arrow_up.png") +local sort_material_ascending = Material("icon16/bullet_arrow_down.png") + +surface.CreateFont("tiger.list.text", {font = "Arial", size = 14, weight = 400}) +surface.CreateFont("tiger.list.column", {font = "Istok", size = 14, weight = 800}) + +AccessorFunc(panel, "m_iColumnHeight", "ColumnHeight") + +-- List. +serverguard.themes.CreateDefault("tiger_list_bg", Color(255, 255, 255), "tiger.list") +serverguard.themes.CreateDefault("tiger_list_outline", Color(190, 190, 190), "tiger.list") + +-- The labels on the panel. +serverguard.themes.CreateDefault("tiger_list_panel_label", Color(141, 127, 123), "tiger.list") +serverguard.themes.CreateDefault("tiger_list_panel_label_hover", Color(241, 242, 234), "tiger.list") + +-- The panel in the list. +serverguard.themes.CreateDefault("tiger_list_panel_list_outline", Color(190, 190, 190), "tiger.list") +serverguard.themes.CreateDefault("tiger_list_panel_list_hover", Color(31, 153, 228), "tiger.list") +serverguard.themes.CreateDefault("tiger_list_panel_list_bg", Color(255, 255, 255), "tiger.list") +serverguard.themes.CreateDefault("tiger_list_panel_list_bg_dark", Color(246, 246, 246), "tiger.list") + +-- Column. +serverguard.themes.CreateDefault("tiger_list_column_outline", Color(190, 190, 190), "tiger.list") +serverguard.themes.CreateDefault("tiger_list_column_label",Color(141, 142, 134), "tiger.list") + +function panel:Init() + self.sortType = SORT_ASCENDING + self.sortColumn = 1 + + self:DockPadding(1, 1, 1, 1) + self:SetColumnHeight(30) + + self.list = self:Add("tiger.scrollpanel"); + self.list:Dock(FILL); +end + +function panel:Clear() + self.list:Clear() +end + +function panel:AddColumn(name, width) + local base = self + + self:DockPadding(1, 30, 1, 1) + + self.columns = self.columns or {} + + local column = self:Add("Panel") + column:SetSize(width, self.m_iColumnHeight + 1) + + column.labels = {} + + function column:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + surface.SetDrawColor(theme.tiger_list_column_outline) + + -- Bottom line. + surface.DrawLine(0, h -1, w, h -1) + + if (!self.last) then + -- Right line. + surface.DrawLine(w -1, 0, w -1, h) + end + + draw.SimpleText(name, "tiger.list.column", w /2, h /2, theme.tiger_list_column_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if (base.sortColumn == self.sortID and !self.disabled) then + local width = util.GetTextSize("tiger.list.column", name) + + if (base.sortType == SORT_ASCENDING) then + draw.Material(w /2 +width /2 +2, 6, 16, 16, color_white, sort_material_ascending) + else + draw.Material(w /2 +width /2 +2, 6, 16, 16, color_white, sort_material_descending) + end + end + end + + function column:OnCursorEntered() + if (!self.disabled) then + self:SetCursor("hand") + end + end + + function column:OnCursorExited() + if (!self.disabled) then + self:SetCursor("arrow") + end + end + + function column.OnMousePressed(_self) + if (!_self.disabled) then + self.sortColumn = _self.sortID + + if (_self.sortType == SORT_ASCENDING) then + _self.sortType = SORT_DESCENDING + else + _self.sortType = SORT_ASCENDING + end + + self.sortType = _self.sortType + + self:OnSort() + end + end + + function column:SetDisabled(bool) + self.disabled = bool + end + + column.sortID = table.insert(self.columns, column) + + return column +end + +function panel:GetColumn(id) + return self.columns[id] +end + +function panel:GetVBar() + return self.list.VBar +end + +function panel:AddPanel(panel) + self.list:AddItem(panel) +end + +function panel:AddItem(...) + local scrollbar = self.list.VBar + local theme = serverguard.themes.GetCurrent() + + local panel = vgui.Create("Panel") + panel:SetTall(30) + panel:Dock(TOP) + panel:SetZPos(0) + + panel.number = 0 + panel.labels = {} + panel.others = {} + + local data = {...} + + for i = 1, #data do + local v = data[i] + local child + + if (ispanel(v)) then + child = v + child:SetParent(panel) + + AccessorFunc(child, "m_SortData", "Sort") + + self.others[i] = child + else + child = panel:Add("DLabel") + child:SetContentAlignment(5) + child:SetFont("tiger.list.text") + child:SetText(v) + child:Dock(LEFT) + child:SetColor(theme.tiger_list_panel_label) + child.IsLabel = true + + AccessorFunc(child, "m_SortData", "Sort") + + child.oldColor = theme.tiger_list_panel_label + + serverguard.themes.AddPanel(child, "tiger_list_panel_label") + + function child:SetUpdate(func) + function self:Think() func(self) end + end + + panel.labels[i] = child + end + + local column = self.columns[i] + + if (IsValid(column)) then + child.column = column + + if (child.IsLabel) then + child:SetWide(column:GetWide()) + end + + if (i == #data) then + table.insert(column.labels, child) + end + end + end + + function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + if (self.hovered) then + draw.SimpleRect(0, 0, w, h, theme.tiger_list_panel_list_hover) + else + if (self.number % 2 == 1) then + draw.SimpleRect(0, 0, w, h,theme.tiger_list_panel_list_bg_dark) + else + draw.SimpleRect(0, 0, w, h, theme.tiger_list_panel_list_bg) + end + end + + surface.SetDrawColor(theme.tiger_list_panel_list_outline) + surface.DrawLine(0, 0, w, 0); + + if (self.last and !scrollbar:IsVisible()) then + surface.DrawLine(0, h -1, w, h -1) + end + end + + function panel:OnCursorEntered() + self:SetCursor("hand") + + self.hovered = true + + local theme = serverguard.themes.GetCurrent() + + for k, label in ipairs(self:GetChildren()) do + if (label.IsLabel) then + if (!label.oldColor) then + label.oldColor = theme.tiger_list_panel_label + end + + label:SetColor(theme.tiger_list_panel_label_hover) + label:InvalidateLayout(true) + end + end + end + + function panel:OnCursorExited() + self:SetCursor("arrow") + + self.hovered = nil + + for k, label in ipairs(self:GetChildren()) do + if (label.IsLabel) then + label:SetColor(label.oldColor) + label:InvalidateLayout(true) + end + end + end + + function panel:GetLabel(index) + return self.labels[index] + end + + function panel:GetThing(index) + return self.others[index] + end + + function panel.AddItem(_self, ...) + local info = {...} + local dataIndex = #data +1 + + -- + --table.remove(self.columns[#self.columns].labels, #self.columns) + + for i = 1, #info do + local v = info[i] + local child + + if (ispanel(v)) then + child = v + child:SetParent(_self) + + AccessorFunc(child, "m_SortData", "Sort") + + _self.others[dataIndex] = child + else + child = _self:Add("DLabel") + child:SetContentAlignment(5) + child:SetFont("tiger.list.text") + child:SetText(v) + child:Dock(LEFT) + child:SetColor(_self.color_label) + child.IsLabel = true + + AccessorFunc(child, "m_SortData", "Sort") + + child.oldColor = _self.color_label + + function child:SetUpdate(func) + function self:Think() func(self) end + end + + _self.labels[i] = child + end + + local column = self.columns[dataIndex] + + if (IsValid(column)) then + child.column = column + + if (child.IsLabel) then + child:SetWide(column:GetWide()) + end + + if (i == #data) then + table.insert(column.labels, child) + end + end + + dataIndex = dataIndex +1 + end + end + + self.list:AddItem(panel) + + local children = self:GetCanvas():GetChildren() + local len = #children + + if (len == 1) then + local child = children[1] + child.number = 1 + child.first = true + child.last = true + elseif (len ~= 0) then + local child = children[1] + child.number = 1 + child.first = true + + child = children[len] + child.number = len + child.last = true + + for i = 2, len - 1 do + child.number = i + end + end + + timer.Simple(FrameTime() * 2, function() + if (self.OnSort) then + self:OnSort() + end + end) + + return panel +end + +function panel:GetCanvas() + return self.list:GetCanvas() +end + +function panel:OnSort() + local sorted = {} + -- FIXME: Sort in this loop + for k, child in ipairs(self:GetCanvas():GetChildren()) do + local label = child:GetLabel(self.sortColumn) + local value = label:GetSort() or label:GetText() + local isNumber = tonumber(value) and true + + table.insert(sorted, {number = isNumber, value = value, panel = child}) + end + + -- SORT_ASCENDING A-Z + -- SORT_DESCENDING Z-A + + if (self.sortType == SORT_ASCENDING) then + table.sort(sorted, function(a, b) if (a.number and b.number) then return tonumber(a.value) < tonumber(b.value) else return string.lower(a.value) < string.lower(b.value) end end) + else + table.sort(sorted, function(a, b) if (a.number and b.number) then return tonumber(a.value) > tonumber(b.value) else return string.lower(a.value) > string.lower(b.value) end end) + end + + for k, data in ipairs(sorted) do + data.panel:SetZPos(k) + + data.panel.number = k + data.panel.first = k == 1 + data.panel.last = k == #sorted + end +end + +function panel:DisablePaint() + function self:Paint(w, h) end +end + +function panel:PerformLayout() + local w, h = self:GetSize() + + if (self.columns) then + local x = 0 + + for i = 1, #self.columns do + local column = self.columns[i] + + column:SetPos(x, 0) + + if (i == #self.columns) then + column.last = true + column:SetWide(self:GetWide() -x) + + for k, label in pairs(column.labels) do + if (label.IsLabel) then + label:SetWide(column:GetWide()) + end + end + end + + x = x +column:GetWide() + end + end +end + +function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_list_outline) + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_list_bg) +end + +tiger_list = vgui.Register("tiger.list", panel, "Panel") \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_news.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_news.lua new file mode 100644 index 0000000..238309e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_news.lua @@ -0,0 +1,142 @@ + +local panel = {}; + +serverguard.themes.CreateDefault("tiger_news_bg", Color(255, 255, 255), "tiger.news"); +serverguard.themes.CreateDefault("tiger_news_title", Color(31, 153, 228), "tiger.news"); +serverguard.themes.CreateDefault("tiger_news_content", Color(141, 127, 123), "tiger.news"); + +function panel:Init() + self.HTML = self:Add("HTML"); + self.HTML:Dock(FILL); + + self.Template = [[ + + + + + + + +
{{title}}
+
{{date}}
+
{{stub}}
+
+
{{content}}
+ + + ]]; + + serverguard.themes.AddPanel(self, "tiger_news_bg"); +end; + +function panel:PopulateArticle(title, date, stub, content) + local theme = serverguard.themes.GetCurrent(); + local backgroundColor = util.ColorLimit(theme.tiger_news_bg, 254, 254, 254, 255); + local titleColor = theme.tiger_news_title; + local contentColor = theme.tiger_news_content; + local formatting = self.Template; + + self.Title = title; + self.Date = date; + self.Stub = stub; + self.Content = content; + + -- Colours. + formatting = string.gsub( + formatting, "{{tiger_news_bg}}", util.ColorString(backgroundColor, true) + ); + + formatting = string.gsub( + formatting, "{{tiger_news_title}}", util.ColorString(titleColor, true) + ); + + formatting = string.gsub( + formatting, "{{tiger_news_content}}", util.ColorString(contentColor, true) + ); + + -- Content. + formatting = string.gsub( + formatting, "{{title}}", title + ); + + formatting = string.gsub( + formatting, "{{date}}", date + ); + + formatting = string.gsub( + formatting, "{{stub}}", stub + ); + + formatting = string.gsub( + formatting, "{{content}}", content + ); + + self:SetHTML(formatting); +end; + +function panel:RepopulateArticle() + if (!self:IsPopulated()) then + return; + end; + + self:PopulateArticle(self.Title, self.Date, self.Stub, self.Content); +end; + +function panel:IsPopulated() + return (self.Title != nil and self.Date != nil and self.Stub != nil and self.Content != nil); +end; + +function panel:SetHTML(html) + self.HTML:SetHTML(html); +end; + +function panel:Clear() + self.HTML:SetHTML(""); + + self.Title = nil; + self.Date = nil; + self.Stub = nil; + self.Content = nil; +end; + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + draw.RoundedBox(4, 0, 0, width, height, theme.tiger_divider_outline); + draw.RoundedBox(2, 1, 1, width - 2, height - 2, theme.tiger_divider_bg); +end; + +function panel:OnTigerThemeChanged() + self:RepopulateArticle(); +end; + +vgui.Register("tiger.news", panel, "Panel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_numslider.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_numslider.lua new file mode 100644 index 0000000..4e265e6 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_numslider.lua @@ -0,0 +1,239 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + +-- Slider button. +local panel = {}; + +AccessorFunc(panel, "m_bDragging", "Dragging", FORCE_BOOL); + +function panel:Init() + self:SetMouseInputEnabled(true); + self:SetSize(16, 16); + + self.circleRadius = 4; + self.targetCircleRadius = 4; + + self:SetDragging(false); +end; + +function panel:OnMousePressed(keyCode) + if (keyCode != MOUSE_LEFT) then + return; + end; + + self:SetDragging(true); +end; + +function panel:Think() + if (self:GetDragging()) then + self.targetCircleRadius = 8; + + if (!input.IsMouseDown(MOUSE_LEFT)) then + self:SetDragging(false); + + local parent = self:GetParent(); + + if (IsValid(parent) and parent.ButtonMoved) then + parent:ButtonMoved(); + end; + end; + else + self.targetCircleRadius = 4; + end; +end; + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + if (self.circleRadius != self.targetCircleRadius) then + self.circleRadius = math.Approach(self.circleRadius, self.targetCircleRadius, 30 * FrameTime()); + end; + + if (self:GetDragging()) then + DisableClipping(true); + end; + + surface.SetDrawColor(theme.tiger_base_section_icon_selected); + + draw.NoTexture(); + draw.Circle(self.circleRadius, self:GetTall() / 2, self.circleRadius, self.circleRadius * 2); + + DisableClipping(false); +end; + +vgui.Register("tiger.numslider.button", panel, "Panel"); + +-- Slider. +local panel = {}; + +AccessorFunc(panel, "m_iMin", "Min", FORCE_NUMBER); +AccessorFunc(panel, "m_iMax", "Max", FORCE_NUMBER); +AccessorFunc(panel, "m_iValue", "Value", FORCE_NUMBER); +AccessorFunc(panel, "m_bClamp", "ClampValue", FORCE_BOOL); + +function panel:Init() + self:SetMouseInputEnabled(true); + + self.button = self:Add("tiger.numslider.button"); + self.button:SetPos(0, 0); + + self:SetMin(0); + self:SetMax(100); + self:SetValue(0); + self:SetClampValue(false); +end; + +function panel:SetValue(value) + local number = util.ToNumber(value); + + if (self:GetClampValue()) then + number = math.Clamp(number, self:GetMin(), self:GetMax()); + end; + + self.m_iValue = number; + self:ValueChanged(number); +end; + +function panel:GetFunctionalWidth() + return self:GetWide() - 10; +end; + +function panel:Think() + if (self.button:GetDragging()) then + local x, y = self.button:GetPos(); + local mouseX, mouseY = self:CursorPos(); + + self.button:SetPos(math.Clamp(mouseX, 1, self:GetWide() - 8), y); + end; +end; + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + surface.SetDrawColor(theme.tiger_base_section_label); + surface.DrawRect(2, height / 2 - 1, self:GetWide() - 2, 2); +end; + +function panel:CalculateValue() + local x, y = self.button:GetPos(); + + return math.Clamp(math.Round(x / self:GetFunctionalWidth() * self:GetMax()), self:GetMin(), self:GetMax()); +end; + +function panel:PerformLayout() + local x, y = self.button:GetPos(); + + if (!self.button:GetDragging()) then + x = math.Clamp(math.Round(math.Clamp(self:GetValue(), self:GetMin(), self:GetMax()) / self:GetMax() * self:GetFunctionalWidth()), 1, self:GetFunctionalWidth()); + end; + + if (!bNoUpdate) then + self.button:SetPos(x, self:GetTall() / 2 - self.button:GetTall() / 2); + end; +end; + +function panel:ButtonMoved() + local parent = self:GetParent(); + + self.m_iValue = self:CalculateValue(); + + if (IsValid(parent) and parent.ValueChanged) then + parent:ValueChanged(self:GetValue()); + end; +end; + +function panel:ValueChanged() + self:PerformLayout(true); +end; + +vgui.Register("tiger.numslider.slider", panel, "Panel"); + +-- Panel. +local panel = {}; + +function panel:Init() + local theme = serverguard.themes.GetCurrent(); + + self:SetTall(30); + + self.textLabel = self:Add("DLabel"); + self.textLabel:SetText("Undefined"); + self.textLabel:SizeToContents(); + self.textLabel:Dock(LEFT); + self.textLabel:DockMargin(8, 0, 8, 0); + self.textLabel:SetSkin("serverguard"); + self.textLabel:SetColor(theme.tiger_list_panel_label); + + self.slider = self:Add("tiger.numslider.slider"); + self.slider:Dock(FILL); + + self.valueEntry = self:Add("DTextEntry"); + self.valueEntry:SetText("0"); + self.valueEntry:SetSize(32, 30); + self.valueEntry:Dock(RIGHT); + self.valueEntry:DockMargin(8, 0, 8, 0); + self.valueEntry:SetDrawBackground(false); + self.valueEntry:SetDrawBorder(false); + self.valueEntry:SetTextColor(theme.tiger_list_panel_label); + self.valueEntry:SetNumeric(true); + + self.valueEntry.OnValueChange = function(panel, value) + local number = util.ToNumber(value); + + self.slider:SetValue(number); + self:ValueChanged(number); + end; +end; + +function panel:Think() + if (vgui.GetKeyboardFocus() != self.valueEntry) then + local value = tostring(self.slider:GetValue()); + + if (self.slider.button:GetDragging()) then + value = tostring(self.slider:CalculateValue()); + end; + + self.valueEntry:SetText(value); + end; +end; + +function panel:SetClampValue(bValue) + self.slider:SetClampValue(bValue); +end; + +function panel:SetText(text) + self.textLabel:SetText(text); + self.textLabel:SizeToContents(); +end; + +function panel:SetMinMax(min, max) + self.slider:SetMin(util.ToNumber(min)); + self.slider:SetMax(util.ToNumber(max)); +end; + +function panel:SetValue(value) + self.slider:SetValue(util.ToNumber(value)); + self.valueEntry:SetText(value); +end; + +function panel:GetValue() + return self.slider:GetValue(); +end; + +function panel:ValueChanged(value) +end; + +vgui.Register("tiger.numslider", panel, "Panel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_panel.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_panel.lua new file mode 100644 index 0000000..9b1f50c --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_panel.lua @@ -0,0 +1,89 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + +local panel = {} + +surface.CreateFont("tiger.panel.title", {font = "Istok", size = 24, weight = 800}) + +serverguard.themes.CreateDefault("tiger_panel_bg", Color(255, 255, 255), "tiger.panel") +serverguard.themes.CreateDefault("tiger_panel_outline", Color(190, 190, 190), "tiger.panel") +serverguard.themes.CreateDefault("tiger_panel_label", Color(151, 137, 133), "tiger.panel") + +function panel:Init() +end + +function panel:SetTitle(text) + if (!IsValid(self.label)) then + self:DockPadding(24, 24, 24, 24) + + local theme = serverguard.themes.GetCurrent() + + self.label = self:Add("DLabel") + self.label:Dock(TOP) + self.label:SetFont("tiger.panel.title") + self.label:SetColor(theme.tiger_panel_label) + self.label:DockMargin(0, 0, 0, 24) + + serverguard.themes.AddPanel(self.label, "tiger_panel_label") + end + + self.label:SetText(text) + self.label:SizeToContents() +end + +local shadow2 = {} +local shadow3 = {} + +for i = 1, 2 do + shadow2[i] = Color(0, 0, 0, (255 /i) *0.1) +end + +for i = 1, 3 do + shadow3[i] = Color(0, 0, 0, (255 /i) *0.2) +end + +function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + for i = 1, 2 do + local color = shadow2[i] + + surface.SetDrawColor(color) + + -- Right shadow. + surface.DrawRect(w -2, 2, i, h -4) + + -- Top shadow. + surface.DrawRect(2, 2 -i, w -4, 1) + + -- Left shadow. + surface.DrawRect(2 -i, 2, 1, h -4) + end + + for i = 1, 3 do + local color = shadow3[i] + + surface.SetDrawColor(color) + + -- Bottom shadow. + surface.DrawRect(2, h -3 +i, w -4, 1) + end + + draw.RoundedBox(4, 2, 2, w -4, h -4, theme.tiger_panel_outline) + draw.RoundedBox(2, 3, 3, w -6, h -6, theme.tiger_panel_bg) +end + +vgui.Register("tiger.panel", panel, "EditablePanel") \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_scrollpanel.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_scrollpanel.lua new file mode 100644 index 0000000..49f04aa --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_scrollpanel.lua @@ -0,0 +1,158 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ +]] + +local panel = {}; + +serverguard.themes.CreateDefault("tiger_list_scrollbar", Color(120, 120, 120, 200), "tiger.list"); + +AccessorFunc(panel, "m_bDrawBorder", "DrawBorder", FORCE_BOOL); +AccessorFunc(panel, "m_bUseSizeLimit", "UseSizeLimit", FORCE_BOOL); + +function panel:Init() + self:SetDrawBorder(false); + self:SetUseSizeLimit(true); + + self.VBar:SetWide(6) + self.VBar.btnUp:Remove() + self.VBar.btnDown:Remove() + + self.VBar.targetScroll = 0; + self.VBar.scrollSpeed = 4; + + self.VBar.SetUp = function(bar, barSize, canvasSize) + bar.BarSize = barSize; + bar.CanvasSize = math.max(canvasSize - barSize, 1); + + if (self:GetUseSizeLimit()) then + bar:SetEnabled(canvasSize > barSize); + else + bar:SetEnabled(true); + end; + + bar:InvalidateLayout() + end; + + function self.VBar:Paint(w, h) + end; + + function self.VBar.btnGrip:Paint(w, h) + local parent = self:GetParent():GetParent(); + local x, y = parent:ScreenToLocal(gui.MousePos()); + local x2, y2 = parent:GetPos(); + local w2, h2 = parent:GetSize(); + + if (x >= 0 and x <= w2 and y >= 0 and y <= h2) then + local theme = serverguard.themes.GetCurrent(); + + draw.SimpleRect(0, 0, w, h, theme.tiger_list_scrollbar); + end; + end; + + function self.VBar:OnMouseWheeled(delta) + self.scrollSpeed = self.scrollSpeed + 50 * FrameTime(); + self:AddScroll(delta * -self.scrollSpeed); + end; + + function self.VBar:OnCursorMoved(x, y) + if (!self.Enabled) then + return; + end; + + if (!self.Dragging) then + return; + end; + + local x = 0; + local y = gui.MouseY(); + local x, y = self:ScreenToLocal(x, y); + + y = y - self.HoldPos; + + local TrackSize = self:GetTall() -self:GetWide() * 2 - self.btnGrip:GetTall(); + + y = y / TrackSize; + + self.targetScroll = y * self.CanvasSize; + end; + + function self.VBar:PerformLayout() + local Scroll = self:GetScroll() / self.CanvasSize; + local BarSize = math.max(self:BarScale() *self:GetTall(), 0); + local Track = self:GetTall() - BarSize; + + Track = Track + 1; + Scroll = Scroll * Track; + + self.btnGrip:SetPos(0, Scroll); + self.btnGrip:SetSize(self:GetWide(), BarSize); + end; + + function self.VBar:Think() + self.scrollSpeed = math.Approach(self.scrollSpeed, 4, math.abs(4 - self.scrollSpeed) * FrameTime()); + self.Scroll = math.Approach(self.Scroll, self.targetScroll, 10 * math.abs(self.targetScroll - self.Scroll) * FrameTime()); + + if (!self.Dragging) then + if (self.targetScroll < 0) then + self.targetScroll = math.Approach(self.targetScroll, 0, 10 * math.abs(0 - self.Scroll) * FrameTime()); + elseif (self.targetScroll > self.CanvasSize) then + self.targetScroll = math.Approach(self.targetScroll, self.CanvasSize, 10 * math.abs(self.CanvasSize - self.Scroll) * FrameTime()); + end; + end; + end; + + function self.VBar:SetScroll(amount) + self.targetScroll = amount; + self:InvalidateLayout(); + + local func = self:GetParent().OnVScroll; + + if (func) then + func(self:GetParent(), self:GetOffset()); + else + self:GetParent():InvalidateLayout(); + end; + end; +end; + +function panel:Think() + self.pnlCanvas:SetPos(0, -self.VBar.Scroll); +end; + +function panel:PerformLayout() + local width, height = self:GetSize(); + + self:Rebuild(); + self.VBar:SetUp(height, self.pnlCanvas:GetTall()); + + if (self.VBar.Enabled) then + self.pnlCanvas:SetWide(width); + else + self.pnlCanvas:SetWide(width); + self.pnlCanvas:SetPos(0, 0); + end; + + self:Rebuild(); +end; + +function panel:Paint(width, height) + if (self:GetDrawBorder()) then + local theme = serverguard.themes.GetCurrent(); + + draw.RoundedBox(4, 0, 0, width, height, theme.tiger_list_outline); + draw.RoundedBox(2, 1, 1, width - 2, height - 2, theme.tiger_list_bg); + end; +end; + +vgui.Register("tiger.scrollpanel", panel, "DScrollPanel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_textentry.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_textentry.lua new file mode 100644 index 0000000..01c229c --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_textentry.lua @@ -0,0 +1,95 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ + +]] + +surface.CreateFont("tiger.textentry.label", { + font = "Istok", + weight = 300, + size = 12 +}); + +local panel = {}; + +AccessorFunc(panel, "m_sLabelText", "LabelText", FORCE_STRING); + +function panel:Init() + self:SetSkin("serverguard"); + self:SetLabelText(""); + + self:SetHistoryEnabled(false); + self.History = {}; + self.HistoryPos = 0; + + self:SetPaintBorderEnabled(false); + self:SetPaintBackgroundEnabled(false); + + self:SetDrawBorder(true); + self:SetDrawBackground(true); + + self:SetEnterAllowed(true); + self:SetUpdateOnType(false); + self:SetNumeric(false); + self:SetAllowNonAsciiCharacters(true); + self:SetTall(30); + self:SetCursor("beam"); + + self.m_bLoseFocusOnClickAway = true; +end; + +function panel:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + if (vgui.GetKeyboardFocus() == self) then + derma.SkinHook("Paint", "TextEntry", self, width, height); + else + if (self.hovered) then + draw.SimpleRect(0, 0, width, height, theme.tiger_list_panel_list_hover); + + surface.SetTextColor(theme.tiger_list_panel_label_hover) + surface.SetFont("tiger.textentry.label"); + + surface.SetTextPos(8, 4); + surface.DrawText(self:GetLabelText()); + else + surface.SetTextColor(theme.tiger_list_panel_list_hover); + surface.SetFont("tiger.textentry.label"); + + surface.SetTextPos(8, 4); + surface.DrawText(self:GetLabelText()); + + surface.SetTextColor(theme.tiger_list_panel_label); + end; + + surface.SetFont(self:GetFont()); + local textWidth, textHeight = surface.GetTextSize(self:GetText()); + + surface.SetTextPos(8, height - textHeight - 2); + surface.DrawText(self:GetText()); + end; + + return false; +end; + +function panel:OnCursorEntered() + self:SetCursor("beam"); + self.hovered = true; +end; + +function panel:OnCursorExited() + self:SetCursor("arrow"); + self.hovered = nil; +end; + +vgui.Register("tiger.textentry", panel, "DTextEntry"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_tooltip.lua b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_tooltip.lua new file mode 100644 index 0000000..6e56146 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/controls/tiger_tooltip.lua @@ -0,0 +1,159 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). + + _______ _ _ _ _____ + |__ __(_) | | | |_ _| + | | _ __ _ ___ _ __ | | | | | | + | | | |/ _` |/ _ \ '__| | | | | | | + | | | | (_| | __/ | | |__| |_| |_ + |_| |_|\__, |\___|_| \____/|_____| + __/ | + |___/ +]] + +surface.CreateFont("tiger.tooltip", {font = "Arial", size = 14, weight = 400}); + +local panel = {}; + +serverguard.themes.CreateDefault("tiger_tooltip_bg", Color(255, 255, 255), "tiger.tooltip"); +serverguard.themes.CreateDefault("tiger_tooltip_outline", Color(190, 190, 190), "tiger.tooltip"); +serverguard.themes.CreateDefault("tiger_tooltip_label", Color(151, 137, 133), "tiger.tooltip"); + +function panel:Init() + self:SetDrawOnTop(true); + + self.label = self:Add("DLabel"); + self.label:SetText(""); + self.label:SetFont("tiger.tooltip"); + self.label:SetSkin("serverguard"); + + serverguard.themes.AddPanel(self.label, "tiger_tooltip_label"); +end; + +function panel:SetText(text) + self.label:SetText(text); +end; + +function panel:SizeToContents() + self.label:SizeToContents(); + + local w, h = self.label:GetSize(); + + self:SetSize(w +14, h +14); +end; + +function panel:PerformLayout() + self.label:SetPos(7, 7); +end; + +function panel:Think() + if (!self:IsVisible()) then + return; + end; + + if (!self.parentPanel or !IsValid(self.parentPanel) and !self.collect) then + self.collect = true; + + self:AlphaTo(0, 0.2, 0, function() + self:SetVisible(false); + self:Remove(); + end); + end; +end; + +function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent(); + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_tooltip_outline); + draw.RoundedBox(2, 1, 1, w - 2, h - 2, theme.tiger_tooltip_bg); + + DisableClipping(true); + for i = 1, 2 do + local color = Color(0, 0, 0, (255 / i) * 0.1); + + surface.SetDrawColor(color); + + -- Right shadow. + surface.DrawRect(w - 1 + i, 1, 1, h - 1); + + -- Top shadow. + surface.DrawRect(1, - i, w - 1, 1); + + -- Left shadow. + surface.DrawRect(-i, 0, 1, h); + end; + + for i = 1, 3 do + local color = Color(0, 0, 0, (255 / i) * 0.16); + + surface.SetDrawColor(color); + + -- Bottom shadow. + surface.DrawRect(1, (h - 1) + i, w - 1, 1); + end; + DisableClipping(false); +end; + +vgui.Register("tiger.tooltip", panel, "Panel"); + +local registry = debug.getregistry(); + +function registry.Panel:SetToolTipSG(text) + if (text == nil) then + if (IsValid(self.tooltip_sg)) then + self.tooltip_sg:Remove(); + end; + + return; + end; + + if (!IsValid(self.tooltip_sg)) then + self.tooltip_sg = vgui.Create("tiger.tooltip"); + end; + + self.tooltip_sg.parentPanel = self; + self.tooltip_sg:SetText(text); + self.tooltip_sg:SizeToContents(); + self.tooltip_sg:SetVisible(false); + + local oldHover = self.OnCursorEntered; + local oldExited = self.OnCursorExited; + + function self:OnCursorEntered() + if (IsValid(self.tooltip_sg)) then + self.sgToolTip = true; + + self.tooltip_sg:SetVisible(true); + self.tooltip_sg:SetAlpha(0); + self.tooltip_sg:AlphaTo(255, 0.2, 0, function() + if (IsValid(self)) then + self.sgToolTip = nil; + end; + end); + + local x, y = gui.MousePos(); + + self.tooltip_sg:SetPos(x + 10, y + 10); + end + + if (oldHover) then + oldHover(self); + end; + end; + + function self:OnCursorExited() + if (IsValid(self.tooltip_sg)) then + self.tooltip_sg:AlphaTo(0, 0.2, 0, function() + if (!self.sgToolTip and self.tooltip_sg and IsValid(self)) then + self.tooltip_sg:SetVisible(false); + end; + end); + end; + + if (oldExited) then + oldExited(self); + end; + end; +end; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/derma_skin.lua b/garrysmod/addons/admin-sg/lua/modules/gui/derma_skin.lua new file mode 100644 index 0000000..df25f32 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/derma_skin.lua @@ -0,0 +1,496 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +SKIN = {} + +SKIN.PrintName = "ServerGuard Skin" +SKIN.Author = "Thriving Ventures" +SKIN.DermaVersion = 1 +SKIN.GwenTexture = Material( "gwenskin/GModDefault.png" ) + +SKIN.bg_color = Color( 101, 100, 105, 255 ) +SKIN.bg_color_sleep = Color( 70, 70, 70, 255 ) +SKIN.bg_color_dark = Color( 55, 57, 61, 255 ) +SKIN.bg_color_bright = Color( 220, 220, 220, 255 ) +SKIN.frame_border = Color( 50, 50, 50, 255 ) + + +SKIN.fontFrame = "DermaDefault" + +SKIN.control_color = Color( 120, 120, 120, 255 ) +SKIN.control_color_highlight = Color( 150, 150, 150, 255 ) +SKIN.control_color_active = Color( 110, 150, 250, 255 ) +SKIN.control_color_bright = Color( 255, 200, 100, 255 ) +SKIN.control_color_dark = Color( 100, 100, 100, 255 ) + +SKIN.bg_alt1 = Color( 50, 50, 50, 255 ) +SKIN.bg_alt2 = Color( 55, 55, 55, 255 ) + +SKIN.listview_hover = Color( 70, 70, 70, 255 ) +SKIN.listview_selected = Color( 100, 170, 220, 255 ) + +SKIN.text_bright = Color( 255, 255, 255, 255 ) +SKIN.text_normal = Color( 180, 180, 180, 255 ) +SKIN.text_dark = Color( 20, 20, 20, 255 ) +SKIN.text_highlight = Color( 255, 20, 20, 255 ) + +SKIN.texGradientUp = Material( "gui/gradient_up" ) +SKIN.texGradientDown = Material( "gui/gradient_down" ) + +SKIN.combobox_selected = SKIN.listview_selected + +SKIN.panel_transback = Color( 255, 255, 255, 50 ) +SKIN.tooltip = Color( 255, 245, 175, 255 ) + +SKIN.colPropertySheet = Color( 170, 170, 170, 255 ) +SKIN.colTab = SKIN.colPropertySheet +SKIN.colTabInactive = Color( 140, 140, 140, 255 ) +SKIN.colTabShadow = Color( 0, 0, 0, 170 ) +SKIN.colTabText = Color( 255, 255, 255, 255 ) +SKIN.colTabTextInactive = Color( 0, 0, 0, 200 ) +SKIN.fontTab = "DermaDefault" + +SKIN.colCollapsibleCategory = Color( 255, 255, 255, 20 ) + +SKIN.colCategoryText = Color( 255, 255, 255, 255 ) +SKIN.colCategoryTextInactive = Color( 200, 200, 200, 255 ) +SKIN.fontCategoryHeader = "TabLarge" + +SKIN.colNumberWangBG = Color( 255, 240, 150, 255 ) +SKIN.colTextEntryBG = Color( 240, 240, 240, 255 ) +SKIN.colTextEntryBorder = Color( 20, 20, 20, 255 ) +SKIN.colTextEntryText = Color( 20, 20, 20, 255 ) +SKIN.colTextEntryTextHighlight = Color( 20, 200, 250, 255 ) +SKIN.colTextEntryTextHighlight = Color( 20, 200, 250, 255 ) + +SKIN.colMenuBG = Color( 255, 255, 255, 200 ) +SKIN.colMenuBorder = Color( 0, 0, 0, 200 ) + +SKIN.colButtonText = Color( 255, 255, 255, 255 ) +SKIN.colButtonTextDisabled = Color( 255, 255, 255, 55 ) +SKIN.colButtonBorder = Color( 20, 20, 20, 255 ) +SKIN.colButtonBorderHighlight = Color( 255, 255, 255, 50 ) +SKIN.colButtonBorderShadow = Color( 0, 0, 0, 100 ) + + + +SKIN.tex = {} + +SKIN.tex.Selection = GWEN.CreateTextureBorder( 384, 32, 31, 31, 4, 4, 4, 4 ); + +SKIN.tex.Panels = {} +SKIN.tex.Panels.Normal = GWEN.CreateTextureBorder( 256, 0, 63, 63, 16, 16, 16, 16 ) +SKIN.tex.Panels.Bright = GWEN.CreateTextureBorder( 256+64, 0, 63, 63, 16, 16, 16, 16 ) +SKIN.tex.Panels.Dark = GWEN.CreateTextureBorder( 256, 64, 63, 63, 16, 16, 16, 16 ) +SKIN.tex.Panels.Highlight = GWEN.CreateTextureBorder( 256+64, 64, 63, 63, 16, 16, 16, 16 ) + +SKIN.tex.Button = GWEN.CreateTextureBorder( 480, 0, 31, 31, 8, 8, 8, 8 ) +SKIN.tex.Button_Hovered = GWEN.CreateTextureBorder( 480, 32, 31, 31, 8, 8, 8, 8 ) +SKIN.tex.Button_Dead = GWEN.CreateTextureBorder( 480, 64, 31, 31, 8, 8, 8, 8 ) +SKIN.tex.Button_Down = GWEN.CreateTextureBorder( 480, 96, 31, 31, 8, 8, 8, 8 ) +SKIN.tex.Shadow = GWEN.CreateTextureBorder( 448, 0, 31, 31, 8, 8, 8, 8 ) + +SKIN.tex.Tree = GWEN.CreateTextureBorder( 256, 128, 127, 127, 16, 16, 16, 16 ) +SKIN.tex.Checkbox_Checked = GWEN.CreateTextureNormal( 448, 32, 15, 15 ) +SKIN.tex.Checkbox = GWEN.CreateTextureNormal( 464, 32, 15, 15 ) +SKIN.tex.CheckboxD_Checked = GWEN.CreateTextureNormal( 448, 48, 15, 15 ) +SKIN.tex.CheckboxD = GWEN.CreateTextureNormal( 464, 48, 15, 15 ) +--SKIN.tex.RadioButton_Checked = GWEN.CreateTextureNormal( 448, 64, 15, 15 ) +--SKIN.tex.RadioButton = GWEN.CreateTextureNormal( 464, 64, 15, 15 ) +--SKIN.tex.RadioButtonD_Checked = GWEN.CreateTextureNormal( 448, 80, 15, 15 ) +--SKIN.tex.RadioButtonD = GWEN.CreateTextureNormal( 464, 80, 15, 15 ) +SKIN.tex.TreePlus = GWEN.CreateTextureNormal( 448, 96, 15, 15 ) +SKIN.tex.TreeMinus = GWEN.CreateTextureNormal( 464, 96, 15, 15 ) +--SKIN.tex.Menu_Strip = GWEN.CreateTextureBorder( 0, 128, 127, 21, 1, 1, 1, 1 ) +SKIN.tex.TextBox = GWEN.CreateTextureBorder( 0, 150, 127, 21, 4, 4, 4, 4 ) +SKIN.tex.TextBox_Focus = GWEN.CreateTextureBorder( 0, 172, 127, 21, 4, 4, 4, 4 ) +SKIN.tex.TextBox_Disabled = GWEN.CreateTextureBorder( 0, 193, 127, 21, 4, 4, 4, 4 ) +SKIN.tex.MenuBG_Column = GWEN.CreateTextureBorder( 128, 128, 127, 63, 24, 8, 8, 8 ) +SKIN.tex.MenuBG = GWEN.CreateTextureBorder( 128, 192, 127, 63, 8, 8, 8, 8 ) +SKIN.tex.MenuBG_Hover = GWEN.CreateTextureBorder( 128, 256, 127, 31, 8, 8, 8, 8 ) +SKIN.tex.MenuBG_Spacer = GWEN.CreateTextureNormal( 128, 288, 127, 3 ) +SKIN.tex.Menu_Strip = GWEN.CreateTextureBorder( 0, 128, 127, 21, 8, 8, 8, 8) +SKIN.tex.Menu_Check = GWEN.CreateTextureNormal( 448, 112, 15, 15 ) +SKIN.tex.Tab_Control = GWEN.CreateTextureBorder( 0, 256, 127, 127, 8, 8, 8, 8 ) +SKIN.tex.TabB_Active = GWEN.CreateTextureBorder( 0, 416, 63, 31, 8, 8, 8, 8 ) +SKIN.tex.TabB_Inactive = GWEN.CreateTextureBorder( 0+128, 416, 63, 31, 8, 8, 8, 8 ) +SKIN.tex.TabT_Active = GWEN.CreateTextureBorder( 0, 384, 63, 31, 8, 8, 8, 8 ) +SKIN.tex.TabT_Inactive = GWEN.CreateTextureBorder( 0+128, 384, 63, 31, 8, 8, 8, 8 ) +SKIN.tex.TabL_Active = GWEN.CreateTextureBorder( 64, 384, 31, 63, 8, 8, 8, 8 ) +SKIN.tex.TabL_Inactive = GWEN.CreateTextureBorder( 64+128, 384, 31, 63, 8, 8, 8, 8 ) +SKIN.tex.TabR_Active = GWEN.CreateTextureBorder( 96, 384, 31, 63, 8, 8, 8, 8 ) +SKIN.tex.TabR_Inactive = GWEN.CreateTextureBorder( 96+128, 384, 31, 63, 8, 8, 8, 8 ) +SKIN.tex.Tab_Bar = GWEN.CreateTextureBorder( 128, 352, 127, 31, 4, 4, 4, 4 ) + +SKIN.tex.Window = {} + +SKIN.tex.Window.Normal = GWEN.CreateTextureBorder( 0, 0, 127, 127, 8, 32, 8, 8 ) +SKIN.tex.Window.Inactive = GWEN.CreateTextureBorder( 128, 0, 127, 127, 8, 32, 8, 8 ) + +SKIN.tex.Window.Close = GWEN.CreateTextureNormal( 32, 448, 31, 31 ); +SKIN.tex.Window.Close_Hover = GWEN.CreateTextureNormal( 64, 448, 31, 31 ); +SKIN.tex.Window.Close_Down = GWEN.CreateTextureNormal( 96, 448, 31, 31 ); + +SKIN.tex.Window.Maxi = GWEN.CreateTextureNormal( 32 + 96*2, 448, 31, 31 ); +SKIN.tex.Window.Maxi_Hover = GWEN.CreateTextureNormal( 64 + 96*2, 448, 31, 31 ); +SKIN.tex.Window.Maxi_Down = GWEN.CreateTextureNormal( 96 + 96*2, 448, 31, 31 ); + +SKIN.tex.Window.Restore = GWEN.CreateTextureNormal( 32 + 96*2, 448+32, 31, 31 ); +SKIN.tex.Window.Restore_Hover = GWEN.CreateTextureNormal( 64 + 96*2, 448+32, 31, 31 ); +SKIN.tex.Window.Restore_Down = GWEN.CreateTextureNormal( 96 + 96*2, 448+32, 31, 31 ); + +SKIN.tex.Window.Mini = GWEN.CreateTextureNormal( 32 + 96, 448, 31, 31 ); +SKIN.tex.Window.Mini_Hover = GWEN.CreateTextureNormal( 64 + 96, 448, 31, 31 ); +SKIN.tex.Window.Mini_Down = GWEN.CreateTextureNormal( 96 + 96, 448, 31, 31 ); + +SKIN.tex.Scroller = {} +SKIN.tex.Scroller.TrackV = GWEN.CreateTextureBorder( 384, 208, 15, 127, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonV_Normal = GWEN.CreateTextureBorder( 384 + 16, 208, 15, 127, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonV_Hover = GWEN.CreateTextureBorder( 384 + 32, 208, 15, 127, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonV_Down = GWEN.CreateTextureBorder( 384 + 48, 208, 15, 127, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonV_Disabled = GWEN.CreateTextureBorder( 384 + 64, 208, 15, 127, 4, 4, 4, 4 ); + +SKIN.tex.Scroller.TrackH = GWEN.CreateTextureBorder( 384, 128, 127, 15, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonH_Normal = GWEN.CreateTextureBorder( 384, 128 + 16, 127, 15, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonH_Hover = GWEN.CreateTextureBorder( 384, 128 + 32, 127, 15, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonH_Down = GWEN.CreateTextureBorder( 384, 128 + 48, 127, 15, 4, 4, 4, 4 ); +SKIN.tex.Scroller.ButtonH_Disabled = GWEN.CreateTextureBorder( 384, 128 + 64, 127, 15, 4, 4, 4, 4 ); + +SKIN.tex.Scroller.LeftButton_Normal = GWEN.CreateTextureBorder( 464, 208, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.LeftButton_Hover = GWEN.CreateTextureBorder( 480, 208, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.LeftButton_Down = GWEN.CreateTextureBorder( 464, 272, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.LeftButton_Disabled = GWEN.CreateTextureBorder( 480 + 48, 272, 15, 15, 2, 2, 2, 2 ); + +SKIN.tex.Scroller.UpButton_Normal = GWEN.CreateTextureBorder( 464, 208 + 16, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.UpButton_Hover = GWEN.CreateTextureBorder( 480, 208 + 16, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.UpButton_Down = GWEN.CreateTextureBorder( 464, 272 + 16, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.UpButton_Disabled = GWEN.CreateTextureBorder( 480 + 48, 272 + 16, 15, 15, 2, 2, 2, 2 ); + +SKIN.tex.Scroller.RightButton_Normal = GWEN.CreateTextureBorder( 464, 208 + 32, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.RightButton_Hover = GWEN.CreateTextureBorder( 480, 208 + 32, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.RightButton_Down = GWEN.CreateTextureBorder( 464, 272 + 32, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.RightButton_Disabled = GWEN.CreateTextureBorder( 480 + 48, 272 + 32, 15, 15, 2, 2, 2, 2 ); + +SKIN.tex.Scroller.DownButton_Normal = GWEN.CreateTextureBorder( 464, 208 + 48, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.DownButton_Hover = GWEN.CreateTextureBorder( 480, 208 + 48, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.DownButton_Down = GWEN.CreateTextureBorder( 464, 272 + 48, 15, 15, 2, 2, 2, 2 ); +SKIN.tex.Scroller.DownButton_Disabled = GWEN.CreateTextureBorder( 480 + 48, 272 + 48, 15, 15, 2, 2, 2, 2 ); + +SKIN.tex.Menu = {} +SKIN.tex.Menu.RightArrow = GWEN.CreateTextureNormal( 464, 112, 15, 15 ); + +SKIN.tex.Input = {} + +SKIN.tex.Input.ComboBox = {} +SKIN.tex.Input.ComboBox.Normal = GWEN.CreateTextureBorder( 384, 336, 127, 31, 8, 8, 32, 8 ); +SKIN.tex.Input.ComboBox.Hover = GWEN.CreateTextureBorder( 384, 336+32, 127, 31, 8, 8, 32, 8 ); +SKIN.tex.Input.ComboBox.Down = GWEN.CreateTextureBorder( 384, 336+64, 127, 31, 8, 8, 32, 8 ); +SKIN.tex.Input.ComboBox.Disabled = GWEN.CreateTextureBorder( 384, 336+96, 127, 31, 8, 8, 32, 8 ); + +SKIN.tex.Input.ComboBox.Button = {} +SKIN.tex.Input.ComboBox.Button.Normal = GWEN.CreateTextureNormal( 496, 272, 15, 15 ); +SKIN.tex.Input.ComboBox.Button.Hover = GWEN.CreateTextureNormal( 496, 272+16, 15, 15 ); +SKIN.tex.Input.ComboBox.Button.Down = GWEN.CreateTextureNormal( 496, 272+32, 15, 15 ); +SKIN.tex.Input.ComboBox.Button.Disabled = GWEN.CreateTextureNormal( 496, 272+48, 15, 15 ); + +SKIN.tex.Input.UpDown = {} +SKIN.tex.Input.UpDown.Up = {} +SKIN.tex.Input.UpDown.Up.Normal = GWEN.CreateTextureCentered( 384, 112, 7, 7 ); +SKIN.tex.Input.UpDown.Up.Hover = GWEN.CreateTextureCentered( 384+8, 112, 7, 7 ); +SKIN.tex.Input.UpDown.Up.Down = GWEN.CreateTextureCentered( 384+16, 112, 7, 7 ); +SKIN.tex.Input.UpDown.Up.Disabled = GWEN.CreateTextureCentered( 384+24, 112, 7, 7 ); + +SKIN.tex.Input.UpDown.Down = {} +SKIN.tex.Input.UpDown.Down.Normal = GWEN.CreateTextureCentered( 384, 120, 7, 7 ); +SKIN.tex.Input.UpDown.Down.Hover = GWEN.CreateTextureCentered( 384+8, 120, 7, 7 ); +SKIN.tex.Input.UpDown.Down.Down = GWEN.CreateTextureCentered( 384+16, 120, 7, 7 ); +SKIN.tex.Input.UpDown.Down.Disabled = GWEN.CreateTextureCentered( 384+24, 120, 7, 7 ); + +SKIN.tex.Input.Slider = {} +SKIN.tex.Input.Slider.H = {} +SKIN.tex.Input.Slider.H.Normal = GWEN.CreateTextureNormal( 416, 32, 15, 15 ); +SKIN.tex.Input.Slider.H.Hover = GWEN.CreateTextureNormal( 416, 32+16, 15, 15 ); +SKIN.tex.Input.Slider.H.Down = GWEN.CreateTextureNormal( 416, 32+32, 15, 15 ); +SKIN.tex.Input.Slider.H.Disabled = GWEN.CreateTextureNormal( 416, 32+48, 15, 15 ); + +SKIN.tex.Input.Slider.V = {} +SKIN.tex.Input.Slider.V.Normal = GWEN.CreateTextureNormal( 416+16, 32, 15, 15 ); +SKIN.tex.Input.Slider.V.Hover = GWEN.CreateTextureNormal( 416+16, 32+16, 15, 15 ); +SKIN.tex.Input.Slider.V.Down = GWEN.CreateTextureNormal( 416+16, 32+32, 15, 15 ); +SKIN.tex.Input.Slider.V.Disabled = GWEN.CreateTextureNormal( 416+16, 32+48, 15, 15 ); + +SKIN.tex.Input.ListBox = {} +SKIN.tex.Input.ListBox.Background = GWEN.CreateTextureBorder( 256, 256, 63, 127, 8, 8, 8, 8 ); +SKIN.tex.Input.ListBox.Hovered = GWEN.CreateTextureBorder( 320, 320, 31, 31, 8, 8, 8, 8 ); +SKIN.tex.Input.ListBox.EvenLine = GWEN.CreateTextureBorder( 352, 256, 31, 31, 8, 8, 8, 8 ); +SKIN.tex.Input.ListBox.OddLine = GWEN.CreateTextureBorder( 352, 288, 31, 31, 8, 8, 8, 8 ); +SKIN.tex.Input.ListBox.EvenLineSelected = GWEN.CreateTextureBorder( 320, 270, 31, 31, 8, 8, 8, 8 ); +SKIN.tex.Input.ListBox.OddLineSelected = GWEN.CreateTextureBorder( 320, 288, 31, 31, 8, 8, 8, 8 ); + +SKIN.tex.ProgressBar = {} +SKIN.tex.ProgressBar.Back = GWEN.CreateTextureBorder( 384, 0, 31, 31, 8, 8, 8, 8 ); +SKIN.tex.ProgressBar.Front = GWEN.CreateTextureBorder( 384+32, 0, 31, 31, 8, 8, 8, 8 ); + + +SKIN.tex.CategoryList = {} +SKIN.tex.CategoryList.Outer = GWEN.CreateTextureBorder( 256, 384, 63, 63, 8, 8, 8, 8 ); +SKIN.tex.CategoryList.Inner = GWEN.CreateTextureBorder( 256 + 64, 384, 63, 63, 8, 21, 8, 8 ); +SKIN.tex.CategoryList.Header = GWEN.CreateTextureBorder( 320, 352, 63, 31, 8, 8, 8, 8 ); + +SKIN.tex.Tooltip = GWEN.CreateTextureBorder( 384, 64, 31, 31, 8, 8, 8, 8 ); + +SKIN.Colours = {} + +SKIN.Colours.Window = {} +SKIN.Colours.Window.TitleActive = GWEN.TextureColor( 4 + 8 * 0, 508 ); +SKIN.Colours.Window.TitleInactive = GWEN.TextureColor( 4 + 8 * 1, 508 ); + +SKIN.Colours.Button = {} +SKIN.Colours.Button.Normal = GWEN.TextureColor( 4 + 8 * 2, 508 ); +SKIN.Colours.Button.Hover = GWEN.TextureColor( 4 + 8 * 3, 508 ); +SKIN.Colours.Button.Down = GWEN.TextureColor( 4 + 8 * 2, 500 ); +SKIN.Colours.Button.Disabled = GWEN.TextureColor( 4 + 8 * 3, 500 ); + +SKIN.Colours.Tab = {} +SKIN.Colours.Tab.Active = {} +SKIN.Colours.Tab.Active.Normal = GWEN.TextureColor( 4 + 8 * 4, 508 ); +SKIN.Colours.Tab.Active.Hover = GWEN.TextureColor( 4 + 8 * 5, 508 ); +SKIN.Colours.Tab.Active.Down = GWEN.TextureColor( 4 + 8 * 4, 500 ); +SKIN.Colours.Tab.Active.Disabled = GWEN.TextureColor( 4 + 8 * 5, 500 ); + +SKIN.Colours.Tab.Inactive = {} +SKIN.Colours.Tab.Inactive.Normal = GWEN.TextureColor( 4 + 8 * 6, 508 ); +SKIN.Colours.Tab.Inactive.Hover = GWEN.TextureColor( 4 + 8 * 7, 508 ); +SKIN.Colours.Tab.Inactive.Down = GWEN.TextureColor( 4 + 8 * 6, 500 ); +SKIN.Colours.Tab.Inactive.Disabled = GWEN.TextureColor( 4 + 8 * 7, 500 ); + +SKIN.Colours.Label = {} +SKIN.Colours.Label.Default = GWEN.TextureColor( 4 + 8 * 8, 508 ); +SKIN.Colours.Label.Bright = GWEN.TextureColor( 4 + 8 * 9, 508 ); +SKIN.Colours.Label.Dark = GWEN.TextureColor( 4 + 8 * 8, 500 ); +SKIN.Colours.Label.Highlight = GWEN.TextureColor( 4 + 8 * 9, 500 ); + +SKIN.Colours.Tree = {} +SKIN.Colours.Tree.Lines = GWEN.TextureColor( 4 + 8 * 10, 508 ); -- !!! +SKIN.Colours.Tree.Normal = GWEN.TextureColor( 4 + 8 * 11, 508 ); +SKIN.Colours.Tree.Hover = GWEN.TextureColor( 4 + 8 * 10, 500 ); +SKIN.Colours.Tree.Selected = GWEN.TextureColor( 4 + 8 * 11, 500 ); + +SKIN.Colours.Properties = {} +SKIN.Colours.Properties.Line_Normal = GWEN.TextureColor( 4 + 8 * 12, 508 ); +SKIN.Colours.Properties.Line_Selected = GWEN.TextureColor( 4 + 8 * 13, 508 ); +SKIN.Colours.Properties.Line_Hover = GWEN.TextureColor( 4 + 8 * 12, 500 ); +SKIN.Colours.Properties.Title = GWEN.TextureColor( 4 + 8 * 13, 500 ); +SKIN.Colours.Properties.Column_Normal = GWEN.TextureColor( 4 + 8 * 14, 508 ); +SKIN.Colours.Properties.Column_Selected = GWEN.TextureColor( 4 + 8 * 15, 508 ); +SKIN.Colours.Properties.Column_Hover = GWEN.TextureColor( 4 + 8 * 14, 500 ); +SKIN.Colours.Properties.Border = GWEN.TextureColor( 4 + 8 * 15, 500 ); +SKIN.Colours.Properties.Label_Normal = GWEN.TextureColor( 4 + 8 * 16, 508 ); +SKIN.Colours.Properties.Label_Selected = GWEN.TextureColor( 4 + 8 * 17, 508 ); +SKIN.Colours.Properties.Label_Hover = GWEN.TextureColor( 4 + 8 * 16, 500 ); + +SKIN.Colours.Category = {} +SKIN.Colours.Category.Header = GWEN.TextureColor( 4 + 8 * 18, 500 ); +SKIN.Colours.Category.Header_Closed = GWEN.TextureColor( 4 + 8 * 19, 500 ); +SKIN.Colours.Category.Line = {} +SKIN.Colours.Category.Line.Text = GWEN.TextureColor( 4 + 8 * 20, 508 ); +SKIN.Colours.Category.Line.Text_Hover = GWEN.TextureColor( 4 + 8 * 21, 508 ); +SKIN.Colours.Category.Line.Text_Selected = GWEN.TextureColor( 4 + 8 * 20, 500 ); +SKIN.Colours.Category.Line.Button = GWEN.TextureColor( 4 + 8 * 21, 500 ); +SKIN.Colours.Category.Line.Button_Hover = GWEN.TextureColor( 4 + 8 * 22, 508 ); +SKIN.Colours.Category.Line.Button_Selected = GWEN.TextureColor( 4 + 8 * 23, 508 ); +SKIN.Colours.Category.LineAlt = {} +SKIN.Colours.Category.LineAlt.Text = GWEN.TextureColor( 4 + 8 * 22, 500 ); +SKIN.Colours.Category.LineAlt.Text_Hover = GWEN.TextureColor( 4 + 8 * 23, 500 ); +SKIN.Colours.Category.LineAlt.Text_Selected = GWEN.TextureColor( 4 + 8 * 24, 508 ); +SKIN.Colours.Category.LineAlt.Button = GWEN.TextureColor( 4 + 8 * 25, 508 ); +SKIN.Colours.Category.LineAlt.Button_Hover = GWEN.TextureColor( 4 + 8 * 24, 500 ); +SKIN.Colours.Category.LineAlt.Button_Selected = GWEN.TextureColor( 4 + 8 * 25, 500 ); + +SKIN.Colours.TooltipText = GWEN.TextureColor( 4 + 8 * 26, 500 ); + +-- + + +SKIN.Colours.Button.Normal = Color(151, 137, 133) +SKIN.Colours.Button.Hover = color_black + +SKIN.Colours.Label.Bright = GWEN.TextureColor( 4 + 8 * 9, 508 ); +SKIN.Colours.Label.Dark = GWEN.TextureColor( 4 + 8 * 8, 500 ); + +local theme = serverguard.themes.GetCurrent(); + +if (theme) then + SKIN.Colours.Button.Normal = theme.tiger_button_text or SKIN.Colours.Button.Normal; + SKIN.Colours.Button.Hover = theme.tiger_button_text_hovered or SKIN.Colours.Button.Hover; + + SKIN.Colours.Label.Bright = theme.tiger_button_text or SKIN.Colours.Label.Bright; + SKIN.Colours.Label.Default = theme.tiger_button_text or SKIN.Colours.Label.Default; + SKIN.Colours.Label.Highlight = theme.tiger_button_text_hovered or SKIN.Colours.Label.Highlight; +end + +hook.Add("serverguard.themes.ThemeChanged", "derma.SKIN.ThemeChanged", function(theme) + local skinTable = derma.GetNamedSkin("serverguard"); + + if (skinTable) then + skinTable.Colours.Button.Normal = theme.tiger_button_text or skinTable.Colours.Button.Normal; + skinTable.Colours.Button.Hover = theme.tiger_button_text_hovered or skinTable.Colours.Button.Hover; + + skinTable.Colours.Label.Bright = theme.tiger_button_text or skinTable.Colours.Label.Bright; + skinTable.Colours.Label.Default = theme.tiger_button_text or skinTable.Colours.Label.Default; + skinTable.Colours.Label.Highlight = theme.tiger_button_text_hovered or skinTable.Colours.Label.Highlight; + end; +end) + +-- +-- ComboBox +-- + +function SKIN:PaintComboBox(panel, w, h) + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_button_outline) + + if (panel.Hovered) then + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_hovered) + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_hovered_stripe) + else + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_bg) + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_stripe) + end + + DisableClipping(true) + for i = 1, 2 do + local color = Color(0, 0, 0, (255 /i) *0.15) + + surface.SetDrawColor(color) + + -- Right shadow. + surface.DrawRect(w -1 +i, 1, 1, h -1) + + -- Top shadow. + --surface.DrawRect(1, -i, w -1, 1) + + -- Left shadow. + --surface.DrawRect(-i, 0, 1, h) + + -- Bottom shadow. + surface.DrawRect(1, h, w -1, i) + end + DisableClipping(false) +end + +-- +-- Menu +-- + +function SKIN:PaintMenu(panel, w, h) + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_button_outline) + + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_bg) + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_stripe) + + DisableClipping(true) + for i = 1, 2 do + local color = Color(0, 0, 0, (255 /i) *0.15) + + surface.SetDrawColor(color) + + -- Right shadow. + surface.DrawRect(w -1 +i, 1, 1, h -1) + + -- Top shadow. + --surface.DrawRect(1, -i, w -1, 1) + + -- Left shadow. + --surface.DrawRect(-i, 0, 1, h) + + -- Bottom shadow. + surface.DrawRect(1, h, w -1, i) + end + DisableClipping(false) +end + +-- +-- MenuOption +-- + +function SKIN:PaintMenuOption(panel, w, h) + local theme = serverguard.themes.GetCurrent(); + + if (panel.m_bBackground && (panel.Hovered || panel.Highlight)) then + --self.tex.MenuBG_Hover(0, 0, w, h); + draw.SimpleRect(0, 0, w, h, theme.tiger_list_panel_list_hover); + panel:SetTextColor(theme.tiger_list_panel_label_hover); + else + panel:SetTextColor(theme.tiger_button_text); + end; + + if (panel:GetChecked()) then + self.tex.Menu_Check(5, h / 2 - 7, 15, 15); + end; +end; + + +-- +-- TextEntry +-- + +function SKIN:PaintTextEntry(panel, w, h) + local theme = serverguard.themes.GetCurrent() + local hasFocus = false; + + if (panel.m_bBackground) then + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_button_outline) + + if (panel:GetDisabled()) then + self.tex.TextBox_Disabled( 0, 0, w, h ) + elseif (panel:HasFocus()) then + hasFocus = true; + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_hovered) + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_hovered_stripe) + else + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_button_bg) + draw.SimpleRect(2, 1, w -4, 1, theme.tiger_button_stripe) + end + + DisableClipping(true) + for i = 1, 2 do + local color = Color(0, 0, 0, (255 /i) *0.15) + + surface.SetDrawColor(color) + + -- Right shadow. + surface.DrawRect(w -1 +i, 1, 1, h -1) + + -- Top shadow. + --surface.DrawRect(1, -i, w -1, 1) + + -- Left shadow. + --surface.DrawRect(-i, 0, 1, h) + + -- Bottom shadow. + surface.DrawRect(1, h, w -1, i) + end + DisableClipping(false) + end + + if (hasFocus) then + panel:DrawTextEntryText(theme.tiger_button_text_hovered, theme.tiger_button_text_hovered, panel.m_colCursor); + else + panel:DrawTextEntryText(theme.tiger_button_text, theme.tiger_button_text, panel.m_colCursor) + end; +end + +function SKIN:SchemeTextEntry(panel) + panel:SetTextColor( self.colTextEntryText ) + panel:SetHighlightColor( self.colTextEntryTextHighlight ) + panel:SetCursorColor( Color( 0, 0, 100, 255 ) ) +end + +derma.DefineSkin("serverguard", "ServerGuard derma skin", SKIN) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/information.lua b/garrysmod/addons/admin-sg/lua/modules/gui/information.lua new file mode 100644 index 0000000..13ea9a0 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/information.lua @@ -0,0 +1,21 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Information" +category.material = "serverguard/menuicons/icon_information.png" + +function category:Create(categoryBase) + + categoryBase.base = categoryBase:Add("tiger.base"); + categoryBase.base:SetFooterEnabled(false); + categoryBase.base:UseSmallPanels(true); + categoryBase.base:Dock(FILL); + +end + +serverguard.menu.AddCategory(category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/intelligence.lua b/garrysmod/addons/admin-sg/lua/modules/gui/intelligence.lua new file mode 100644 index 0000000..f342339 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/intelligence.lua @@ -0,0 +1,22 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Intelligence" +category.material = "serverguard/menuicons/icon_screen_capture.png" +category.permissions = {"Manage Private Messages", "View Disconnects", "Manage Reports", "Screencap"} + +function category:Create(categoryBase) + + categoryBase.base = categoryBase:Add("tiger.base"); + categoryBase.base:SetFooterEnabled(false); + categoryBase.base:UseSmallPanels(true); + categoryBase.base:Dock(FILL); + +end + +serverguard.menu.AddCategory(category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/lists.lua b/garrysmod/addons/admin-sg/lua/modules/gui/lists.lua new file mode 100644 index 0000000..713c7bc --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/lists.lua @@ -0,0 +1,22 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Lists" +category.material = "serverguard/menuicons/icon_banlist.png" +category.permissions = {"Ban", "Unban", "Set Rank", "Edit Ranks"} + +function category:Create(categoryBase) + + categoryBase.base = categoryBase:Add("tiger.base"); + categoryBase.base:SetFooterEnabled(false); + categoryBase.base:UseSmallPanels(true); + categoryBase.base:Dock(FILL); + +end + +serverguard.menu.AddCategory(category) diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/manage_players.lua b/garrysmod/addons/admin-sg/lua/modules/gui/manage_players.lua new file mode 100644 index 0000000..891ef29 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/manage_players.lua @@ -0,0 +1,159 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Manage players" +category.material = "serverguard/menuicons/icon_mange_players.png" +category.permissions = "Manage Players" + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Manage players") + base.panel:Dock(FILL) + + base.panel.list = base.panel:Add("tiger.list") + base.panel.list:Dock(FILL) + base.panel.list.sortColumn = 3 + + local column = base.panel.list:AddColumn("#", 25) + column:SetDisabled(true) + + base.panel.list:AddColumn("PLAYER", 230) + base.panel.list:AddColumn("RANK", 130) + base.panel.list:AddColumn("STEAMID", 150) + base.panel.list:AddColumn("PING", 75) + + hook.Call("serverguard.panel.PlayerManageList", nil, base.panel.list) + + function base.panel.list:Think() + local players = player.GetSortedPlayers(); + + for i = 1, #players do + local pPlayer = players[i] + + if (!IsValid(pPlayer.managePanel)) then + local rankData = serverguard.ranks:FindByID( + serverguard.player:GetRank(pPlayer) + ); + + if (!rankData) then + rankData = serverguard.ranks:GetRank("user"); + end; + + local steamID = pPlayer:SteamID(); + + if (pPlayer:IsBot()) then + steamID = "BOT"; + end; + + local panel = self:AddItem(i, serverguard.player:GetName(pPlayer), rankData.name, steamID, pPlayer:Ping()) + + panel.player = pPlayer + + hook.Call("serverguard.PlayerManage", nil, panel) + + function panel:OnMousePressed(code) + local rankData = serverguard.ranks:GetRank(serverguard.player:GetRank(LocalPlayer())) + local commands = serverguard.command:GetTable() + + local bNoAccess = true + + local menu = DermaMenu(); + menu:SetSkin("serverguard"); + + menu:AddOption("Copy Steam ID", function() + if (IsValid(self) and IsValid(self.player)) then + SetClipboardText(self.player:SteamID()); + end + end):SetIcon("icon16/page_copy.png"); + + for unique, data in pairs(commands) do + if (data.ContextMenu and (!data.permissions or serverguard.player:HasPermission(LocalPlayer(), data.permissions))) then + data:ContextMenu(self.player, menu, rankData); bNoAccess = false; + end; + end; + menu:Open(); + + if (bNoAccess) then + menu:Remove(); + end; + end + + function panel:Think() + if (!IsValid(self.player)) then + self:Remove() + + base.panel.list:GetCanvas():InvalidateLayout() + + timer.Simple(FrameTime() *2, function() + base.panel.list:OnSort() + end) + end + end + + local numberLabel = panel:GetLabel(1) + + numberLabel:SetUpdate(function(self) + local parent = self:GetParent() + + if (parent.number and tonumber(self:GetText()) != parent.number) then + self:SetText(parent.number) + end + end) + + local nameLabel = panel:GetLabel(2) + + nameLabel:SetUpdate(function(self) + if (IsValid(pPlayer)) then + if (self:GetText() != serverguard.player:GetName(pPlayer)) then + self:SetText(serverguard.player:GetName(pPlayer)) + end + end + end) + + local rankLabel = panel:GetLabel(3) + rankLabel:SetColor(rankData.color) + rankLabel:SetSort(rankData.immunity) + + rankLabel.rank = rankData.unique + rankLabel.oldColor = rankData.color + + rankLabel:SetUpdate(function(self) + if (IsValid(pPlayer)) then + local rankData = serverguard.ranks:GetRank(serverguard.player:GetRank(pPlayer)) + + if (self.rank != rankData.unique) then + self:SetText(rankData.name) + self:SetColor(rankData.color) + self:SetSort(rankData.immunity) + + self.rank = rankData.unique + self.oldColor = rankData.color + + base.panel.list:OnSort() + end + end + end) + + local pingLabel = panel:GetLabel(5) + + pingLabel:SetUpdate(function(self) + if (IsValid(pPlayer)) then + self:SetText(pPlayer:Ping()) + end + end) + + pPlayer.managePanel = panel + end + end + end +end + +function category:Update(base) +end + +serverguard.menu.AddCategory(category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/menu.lua b/garrysmod/addons/admin-sg/lua/modules/gui/menu.lua new file mode 100644 index 0000000..0addbfc --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/menu.lua @@ -0,0 +1,342 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard.menu = serverguard.menu or {}; + +local stored = {}; + +local panelObject = nil; +local panelObjectStay = false; +local panelQuickMenu = nil; +local hints = { + "Try binding '+serverguard_menu' to open the menu, or '+serverguard_quickmenu' for quick access to commands.", + "You can search for categories on the left simply by typing the name of the category.", + "When using commands, you can specify *, ^, or #admin as the name to target everyone, yourself, or only admins.", + "You can run any command from the console by typing 'sg '.", + "You can return to your last position after using a teleport command by using the !return command.", + "Clicking anywhere outside of the menu will also close it." +}; + +-- +-- Add a category. +-- + +function serverguard.menu.AddCategory(data) + data.material = data.material or "icon16/exclamation.png"; + data.Update = data.Update or function() end; + data.Think = data.Think or function() end; + data.submenus = data.submenus or {}; + + if data.permissions then + serverguard.permission:Add(data.permissions); + end; + + stored[data.name] = stored[data.name] or data; +end; + +-- +-- Add a subcategory. +-- + +function serverguard.menu.AddSubCategory(parent_name, data) + if (stored[parent_name]) then + data.material = data.material or "icon16/exclamation.png" + data.Update = data.Update or function() end + data.Think = data.Think or function() end + + if data.permissions then + serverguard.permission:Add(data.permissions) + end + + stored[parent_name].submenus[data.name] = stored[parent_name].submenus[data.name] or data + end +end + + +-- +-- Remove a subcategory. +-- + +function serverguard.menu:RemoveSubCategory(parent_name, uniqueID) + if (stored[parent_name].submenus[uniqueID]) then + stored[parent_name].submenus[uniqueID] = nil + + self:Rebuild() + end +end + +-- +-- Remove a category. +-- + +function serverguard.menu:RemoveCategory(uniqueID) + if (stored[uniqueID]) then + stored[uniqueID] = nil; + + self:Rebuild(); + end; +end; + +-- +-- Get all categories. +-- + +function serverguard.menu:GetStored() + return stored; +end; + +-- +-- Get category by name. +-- + +function serverguard.menu:GetByName(name) + local stored = serverguard.menu:GetStored(); + return stored[name] or false; +end; + +-- +-- Rebuild the menu. +-- + +function serverguard.menu:Rebuild() + if (IsValid(panelObject)) then + panelObject:Rebuild(); + end; +end; + +-- +-- Close the menu. +-- + +function serverguard.menu.Close(bForce) + if (IsValid(panelObject) and !panelObjectStay or bForce) then + panelObject:SetVisible(false); + hook.Call("serverguard.menu.Close", nil, panelObject); + end; +end; + +-- +-- Returns the menu panel. +-- + +function serverguard.GetMenuPanel() + return panelObject; +end; + +-- +-- Set whether or not to make the menu stay open. +-- + +function serverguard.SetMenuStay(bool) + panelObjectStay = bool; +end; + +-- +-- Returns the quick menu panel. +-- + +function serverguard.GetQuickMenuPanel() + return panelQuickMenu; +end; + +-- +-- Open the menu. +-- + +local function CreateMenu(player) + if (!IsValid(panelObject)) then + panelObject = vgui.Create("tiger.base"); + panelObject:SetSize( + (ScrW() >= 950 and 950) or ScrW(), + (ScrH() >= 650 and 650) or ScrH() + ); + + panelObject:Rebuild(); + panelObject:Center(); + panelObject:MakePopup(); + + local closeLabel = panelObject:Add("DLabel"); + closeLabel:SetPos(panelObject:GetWide() - 18, 0); + closeLabel:SetText("x"); + closeLabel:SetFont("roboto.18.bold"); + closeLabel:SetColor(Color(180, 180, 180)); + closeLabel:SetCursor("hand"); + closeLabel:SetSize(40, 30); + closeLabel:SetMouseInputEnabled(true); + + function closeLabel:DoClick() + panelObjectStay = false; + panelObject:SetVisible(false); + end; + + function closeLabel:OnCursorEntered() + self:SetColor(Color(130, 130, 130)); + end; + + function closeLabel:OnCursorExited() + self:SetColor(Color(180, 180, 180)); + end; + + if (serverguard.GetCurrentVersion() != serverguard.GetLatestVersion()) then + panelObject:SetUpdateNotification(true); + end; + + local hintNumber = math.random(#hints); + local hintText = hints[hintNumber]; + + local hintLabel = panelObject:Add("DLabel"); + hintLabel:SetMouseInputEnabled(true); + hintLabel:SetText(hintText); + hintLabel:SetFont("tiger.base.footer"); + hintLabel:SizeToContents(); + hintLabel:SetColor(serverguard.themes.GetCurrent().tiger_base_footer_hint); + hintLabel:SetPos((panelObject:GetWide() - hintLabel:GetWide()) - 5, (panelObject:GetTall() - hintLabel:GetTall()) - 7); + + function hintLabel:DoClick() + timer.Remove("serverguard.MenuHint"); + self:AlphaTo(0, 0.5); + end; + + serverguard.themes.AddPanel(hintLabel, "tiger_base_footer_hint"); + + timer.Create("serverguard.MenuHint", 10, 0, function() + hintNumber = hintNumber + 1; + + if (hintNumber > #hints) then + hintNumber = 1; + end; + + hintLabel:AlphaTo(0, 0.5, 0, function() + hintLabel:SetText(hints[hintNumber]); + hintLabel:SizeToContents(); + hintLabel:SetColor(serverguard.themes.GetCurrent().tiger_base_footer_hint); + hintLabel:SetPos((panelObject:GetWide() - hintLabel:GetWide()) - 5, (panelObject:GetTall() - hintLabel:GetTall()) - 7); + hintLabel:AlphaTo(255, 0.5, 0, function() end); + end); + end); + + panelObject:SetSectionSelected("News"); + end; + + panelObject:SetVisible(true); +end; + +concommand.Add("+serverguard_menu", CreateMenu) + +concommand.Add("serverguard_menu_toggle", function(player) + + if (IsValid(panelObject) and panelObject:IsVisible()) then + serverguard.menu.Close() + else + CreateMenu() + end + +end) + +-- +-- Close the menu. +-- + +concommand.Add("-serverguard_menu", function(player) + if (IsValid(panelObject)) then + serverguard.menu.Close(); + end +end); + +-- +-- Open the quick menu. +-- + +concommand.Add("+serverguard_quickmenu", function(pPlayer) + if (serverguard.player:HasPermission(pPlayer, "Quick Menu")) then + gui.EnableScreenClicker(true); + gui.SetMousePos(99, 99); + + local commandsTable = serverguard.command:GetTable(); + local rankData = serverguard.ranks:GetRank( + serverguard.player:GetRank(pPlayer) + ); + + local bNoAccess = true; + + local menu = DermaMenu(); + menu:SetSkin("serverguard"); + + for k, v in ipairs(player.GetSortedPlayers()) do + menu:AddOption(serverguard.player:GetName(v), function() + timer.Simple(0, function() + gui.EnableScreenClicker(true); + gui.SetMousePos(99, 99); + local playerMenu = DermaMenu(); + playerMenu:SetSkin("serverguard"); + + playerMenu:AddOption("Copy Steam ID", function() + if (IsValid(menuOption) and IsValid(v)) then + SetClipboardText(v:SteamID()); + end + end):SetIcon("icon16/page_copy.png"); + + playerMenu:SetSkin("serverguard"); + + for k2, v2 in util.SortedPairsByMemberValue(commandsTable, "help") do + if (v2.ContextMenu and (!v2.permissions or serverguard.player:HasPermission(pPlayer, v2.permissions))) then + v2:ContextMenu(v, playerMenu, rankData); bNoAccess = false; + end; + end; + + playerMenu:Open(100,100); + playerMenu.OnRemove = function(panel) + gui.EnableScreenClicker(false); + end; + + panelQuickMenu = playerMenu; + end) + end):SetImage(serverguard.ranks:GetRank(serverguard.player:GetRank(v)).texture); + end; + menu:Open(100, 100); + + menu.OnRemove = function(panel) + gui.EnableScreenClicker(false); + end; + + -- if (bNoAccess) then + -- menu:Remove(); + -- end; + + panelQuickMenu = menu; + end; +end); + +-- +-- Close the quick menu. +-- + +concommand.Add("-serverguard_quickmenu", function() + if (IsValid(panelQuickMenu)) then + panelQuickMenu:Remove(); + end; +end); + +-- +-- +-- + +timer.Simple(2, function() + local oldGetFocus = DTextEntry.OnGetFocus; + local oldLoseFocus = DTextEntry.OnLoseFocus; + + function DTextEntry:OnGetFocus() + oldGetFocus(self); + + serverguard.SetMenuStay(true); + end; + + function DTextEntry:OnLoseFocus() + oldLoseFocus(self); + + serverguard.SetMenuStay(false); + end; +end); diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/news.lua b/garrysmod/addons/admin-sg/lua/modules/gui/news.lua new file mode 100644 index 0000000..42a54d8 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/news.lua @@ -0,0 +1,138 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "News" +category.material = "serverguard/menuicons/icon_announcements.png" + +local loadingTexture = Material("icon16/arrow_rotate_anticlockwise.png") + +local function retrieveNews() + local function resetPanels() + category.panel.loadingPanel:SetVisible(false) + category.panel.newsPanel:SetVisible(false) + category.panel.divider:SetVisible(true) + + category.panel.refresh:SetVisible(true) + category.panel.back:SetVisible(false) + end + + http.Fetch(SERVERGUARD.ENDPOINT .. "get/news/top", + function(body, length, headers, responseCode) + local returnData = util.JSONToTable(body) + + if (returnData != nil && table.Count(returnData) > 0) then + if (!returnData["success"]) then + resetPanels() + return; + end + + category.panel.divider:Clear() + + for k, v in pairs(returnData["items"]) do + local panel = category.panel.divider:AddRow(v["date"], v["title"]) + + function panel:DoClick() + category.panel.newsPanel:PopulateArticle(v["title"], v["date"], v["stub"], v["content"]) + + category.panel.loadingPanel:SetVisible(false) + category.panel.newsPanel:SetVisible(true) + category.panel.divider:SetVisible(false) + + category.panel.refresh:SetVisible(false) + category.panel.back:SetVisible(true) + end + end + end + + resetPanels() + end, + + function(error) + resetPanels() + end + ) +end + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Latest news") + base.panel:Dock(FILL) + base.panel:DockPadding(24, 24, 24, 48) + + base.panel.newsPanel = base.panel:Add("tiger.news") + base.panel.newsPanel:Dock(FILL) + base.panel.newsPanel:DockPadding(1, 1, 1, 1) + base.panel.newsPanel:SetVisible(false) + + base.panel.loadingPanel = base.panel:Add("tiger.panel") + base.panel.loadingPanel:Dock(FILL) + base.panel.loadingPanel:SetVisible(false) + + function base.panel.loadingPanel:Paint(width, height) + if (!self.rotation) then + self.rotation = 0 + end + + self.rotation = self.rotation + 140 * FrameTime() + + if (self.rotation > 360) then + self.rotation = 0 + end + + draw.MaterialRotated(width / 2 - 8, height / 2 - 8, 16, 16, color_white, loadingTexture, self.rotation) + end + + base.panel.divider = base.panel:Add("tiger.divider") + base.panel.divider:Dock(FILL) + + base.panel.back = base.panel:Add("tiger.button") + base.panel.back:SetPos(4, 4) + base.panel.back:SetText("Back") + base.panel.back:SizeToContents() + base.panel.back:SetVisible(false) + + function base.panel.back:DoClick() + category.panel.loadingPanel:SetVisible(false) + category.panel.newsPanel:SetVisible(false) + category.panel.divider:SetVisible(true) + + category.panel.refresh:SetVisible(true) + category.panel.back:SetVisible(false) + end + + base.panel.refresh = base.panel:Add("tiger.button") + base.panel.refresh:SetPos(4, 4) + base.panel.refresh:SetText("Refresh") + base.panel.refresh:SizeToContents() + + function base.panel.refresh:DoClick() + base.panel.loadingPanel:SetVisible(true) + base.panel.newsPanel:SetVisible(false) + base.panel.divider:SetVisible(false) + + base.panel.refresh:SetVisible(false) + base.panel.back:SetVisible(false) + + retrieveNews() + end + + function base.panel:PerformLayout() + local width, height = self:GetSize() + + base.panel.refresh:SetPos(width - (base.panel.refresh:GetWide() + 24), height - (base.panel.refresh:GetTall() + 14)) + base.panel.back:SetPos(width - (base.panel.back:GetWide() + 24), height - (base.panel.back:GetTall() + 14)) + end + + category.panel = base.panel +end + +function category:Update(base) + retrieveNews(); +end; + +serverguard.menu.AddSubCategory("Information", category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/rank_editor.lua b/garrysmod/addons/admin-sg/lua/modules/gui/rank_editor.lua new file mode 100644 index 0000000..b67c7ee --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/rank_editor.lua @@ -0,0 +1,683 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Rank editor" +category.material = "serverguard/menuicons/icon_editranks.png" +category.permissions = "Edit Ranks" + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Edit or create ranks") + base.panel:Dock(FILL) + + hook.Add("serverguard.ranks.RankUpdate", "serverguard.gui.ranks.RankUpdate", function() + if (IsValid(base.panel)) then + category:Update(base) + end + end) + + base.panel.list = base.panel:Add("tiger.list") + base.panel.list:Dock(FILL) + base.panel.list:DockMargin(0, 54, 0, 0) + + base.panel.list:AddColumn("UNIQUE", 150) + base.panel.list:AddColumn("NAME", 150) + base.panel.list:AddColumn("IMMUNITY", 90) + base.panel.list:AddColumn("TARGETABLE", 90) + base.panel.list:AddColumn("BANLIMIT", 110) + + hook.Call("serverguard.panel.RankEditorList", nil, base.panel.list); + + local column = base.panel.list:AddColumn("IMAGE", 35) + column:SetDisabled(true) + + local ranks = serverguard.ranks:GetTable() + + local button = base.panel:Add("tiger.button") + button:SetPos(24, 74) + button:SetText("Create new rank") + button:SizeToContents() + + local combo = base.panel:Add("tiger.combobox") + combo:SetPos(160, 70) + combo:SetSize(150, 30) + combo:SetLabelText("Copy from") + combo:AddChoice("", nil, true) + for k, v in SortedPairsByMemberValue(ranks, "immunity", true) do + combo:AddChoice(v.name, v) + end + + function button:DoClick() + local menu = vgui.Create("tiger.panel") + menu:SetTitle("Rank options") + menu:SetSize(650, ScrH() * 0.9) + menu:Center() + menu:MakePopup() + + local list = menu:Add("tiger.list") + list:Dock(FILL) + list:GetCanvas():DockPadding(14, 14, 14, 14) + + local _, copyFrom = combo:GetSelected() + + local uniqueEntry = vgui.Create("tiger.textentry"); + uniqueEntry:SetLabelText("Unique name"); + uniqueEntry:Dock(TOP); + list:AddPanel(uniqueEntry); + + local nameEntry = vgui.Create("tiger.textentry"); + nameEntry:SetLabelText("Fancy name"); + nameEntry:Dock(TOP); + if copyFrom then nameEntry:SetValue(copyFrom.name) end + list:AddPanel(nameEntry); + + local immunityEntry = vgui.Create("tiger.textentry"); + immunityEntry:SetLabelText("Immunity level"); + immunityEntry:Dock(TOP); + immunityEntry:SetNumeric(true); + if copyFrom then immunityEntry:SetValue(copyFrom.immunity) end + list:AddPanel(immunityEntry); + + local targetableEntry = vgui.Create("tiger.textentry"); + targetableEntry:SetLabelText("Targetable rank"); + targetableEntry:Dock(TOP); + targetableEntry:SetNumeric(true); + if copyFrom then targetableEntry:SetValue(copyFrom.targetable) end + list:AddPanel(targetableEntry); + + local banlimitEntry = vgui.Create("tiger.textentry"); + banlimitEntry:SetLabelText("Maximum Ban Length (Example: 1y2w2d)"); + banlimitEntry:Dock(TOP); + if copyFrom then banlimitEntry:SetValue(copyFrom.banlimit) end + list:AddPanel(banlimitEntry); + + local label = vgui.Create("DLabel") + label:SetText("Rank color:") + label:SizeToContents() + label:Dock(TOP) + label:DockMargin(0, 18, 0, 0) + label:SetSkin("serverguard") + + list:AddPanel(label) + + local colorSlider = vgui.Create("DColorMixer") + colorSlider:Dock(TOP) + colorSlider:DockMargin(0, 14, 0, 14) + if copyFrom then colorSlider:SetColor(copyFrom.color) end + + list:AddPanel(colorSlider) + + local label = vgui.Create("DLabel") + label:SetText("Physgun color:") + label:SizeToContents() + label:Dock(TOP) + label:DockMargin(0, 18, 0, 0) + label:SetSkin("serverguard") + + list:AddPanel(label) + + local colorSliderPhys = vgui.Create("DColorMixer") + colorSliderPhys:Dock(TOP) + colorSliderPhys:DockMargin(0, 14, 0, 14) + if copyFrom then colorSliderPhys:SetColor(copyFrom.data.phys_color) end + + list:AddPanel(colorSliderPhys) + + local label = vgui.Create("DLabel") + label:SetText("Rank image:") + label:SizeToContents() + label:Dock(TOP) + label:DockMargin(0, 8, 0, 0) + label:SetSkin("serverguard") + + list:AddPanel(label) + + local image = "" + local textureList = vgui.Create("tiger.list") + textureList:Dock(TOP) + textureList:DockMargin(0, 14, 0, 14) + textureList:GetCanvas():DockPadding(4, 4, 4, 4) + textureList:SetTall(16 *12) + + local images = file.Find("materials/octoteam/icons-16/*.png", "GAME") + local width = 0 + + local basea = vgui.Create("Panel") + basea:SetTall(16) + basea:Dock(TOP) + basea:DockMargin(0, 0, 0, 8) + + textureList:AddPanel(basea) + + local lastSelected + + for k, v in pairs(images) do + local icon = basea:Add("DImageButton") + icon:Dock(LEFT) + icon:DockMargin(2, 0, 2, 0) + icon:SetImage("materials/octoteam/icons-16/" .. v) + icon:SetSize(16, 16) + + function icon:DoClick() + image = "materials/octoteam/icons-16/" .. v + + if (IsValid(lastSelected)) then + lastSelected.selected = nil + end + + lastSelected = self + end + + if copyFrom and copyFrom.texture:sub(18) == v then + icon:DoClick() + image = "materials/octoteam/icons-16/" .. v + end + + function icon:PaintOver(w, h) + if (lastSelected == self) then + draw.SimpleOutlined(0, 0, w, h, color_red) + end + end + + width = width +16 + + if (width > 16 *27) then + basea = vgui.Create("Panel") + basea:SetTall(16) + basea:Dock(TOP) + basea:DockMargin(0, 0, 0, 8) + + textureList:AddPanel(basea) + + width = 0 + end + end + + list:AddPanel(textureList) + + local label = vgui.Create("DLabel"); + label:SetText("Rank permissions:"); + label:SizeToContents(); + label:Dock(TOP); + label:DockMargin(0, 8, 0, 0); + label:SetSkin("serverguard"); + + list:AddPanel(label) + + local permissionList = vgui.Create("tiger.list"); + permissionList:Dock(TOP); + permissionList:DockMargin(0, 14, 0, 14); + permissionList:SetTall(16 * 16); + + list:AddPanel(permissionList); + + local appliedPermissions = {}; + + for k, v in SortedPairs(serverguard.permission:GetAll()) do + local entry = vgui.Create("tiger.checkbox"); + permissionList:AddPanel(entry); + entry:Dock(TOP); + entry:SetText(k); + + function entry:OnChange(value) + appliedPermissions[k] = value; + end; + + if copyFrom then + local val = copyFrom.data.Permissions and copyFrom.data.Permissions[k] + entry:SetChecked(val) + appliedPermissions[k] = val + end + end; + + hook.Call("serverguard.panel.RankEditorCreationMenu", nil, list, copyFrom); + + local comleteBase = vgui.Create("Panel") + comleteBase:Dock(TOP) + comleteBase:DockMargin(0, 0, 0, 14) + comleteBase:SetTall(20) + + local complete = comleteBase:Add("tiger.button") + complete:Dock(RIGHT) + complete:SetText("Complete") + complete:SizeToContents() + + function complete:DoClick() + local color = colorSlider:GetColor() + local phys_color = colorSliderPhys:GetColor() + + local dataTable = copyFrom and copyFrom.data or {}; + dataTable["phys_color"] = phys_color; + hook.Call("serverguard.panel.RankEditorCreationPopulate", nil, dataTable); + + serverguard.netstream.Start("sgNewRank", { + uniqueEntry:GetValue(), + nameEntry:GetValue(), + tonumber(immunityEntry:GetValue()), + color, + image, + appliedPermissions, + dataTable, + tonumber(targetableEntry:GetValue()), + util.ParseDuration(banlimitEntry:GetValue()), + }); + + menu:Remove() + end + + local cancel = comleteBase:Add("tiger.button") + cancel:Dock(RIGHT) + cancel:DockMargin(0, 0, 8, 0) + cancel:SetText("Cancel") + cancel:SizeToContents() + + function cancel:DoClick() + menu:Remove() + end + + list:AddPanel(comleteBase) + end +end + +function category:Update(base) + base.panel.list:Clear() + + local ranks = serverguard.ranks:GetTable() + + + for unique, data in pairs(ranks) do + + local limit, limitText = util.ParseDuration(data.banlimit) + local panel = base.panel.list:AddItem(unique, data.name, data.immunity, data.targetable, limitText) + + function panel:OnMousePressed() + local menu = DermaMenu() + menu:SetSkin("serverguard"); + + local option = menu:AddOption("Change name", function() + Derma_StringRequest("Rank options", "Change rank name", data.name, + function(text) + serverguard.netstream.Start("sgChangeRankInfo", { + unique, "name", SERVERGUARD.NETWORK.STRING, text + }); + end, + + function(text) end, + "Accept", + "Cancel" + ) + end) + + option:SetImage("icon16/book_edit.png") + + local option = menu:AddOption("Change immunity", function() + Derma_StringRequest("Rank options", "Change rank immunity", data.immunity, + function(text) + serverguard.netstream.Start("sgChangeRankInfo", { + unique, "immunity", SERVERGUARD.NETWORK.NUMBER, tonumber(text) + }); + end, + + function(text) end, + "Accept", + "Cancel" + ) + end) + + option:SetImage("icon16/shield.png") + + + local option = menu:AddOption("Change targetable rank", function() + Derma_StringRequest("Rank options", "Change rank's targetable rank", data.targetable, + function(text) + serverguard.netstream.Start("sgChangeRankInfo", { + unique, "targetable", SERVERGUARD.NETWORK.NUMBER, tonumber(text) + }); + end, + + function(text) end, + "Accept", + "Cancel" + ) + end) + + option:SetImage("icon16/shield.png") + + local option = menu:AddOption("Change maximum ban length", function() + Derma_StringRequest("Rank options", "Change maximum ban length (Example: 1y2w2d) ", data.banlimit, + function(text) + serverguard.netstream.Start("sgChangeRankInfo", { + unique, "banlimit", SERVERGUARD.NETWORK.NUMBER, util.ParseDuration(text) + }); + end, + + function(text) end, + "Accept", + "Cancel" + ) + end) + + option:SetImage("icon16/shield.png") + + + local option = menu:AddOption("Change permissions", function() + if (unique == "founder") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "You cannot change the permissions of the founder rank - it's already granted all permissions!"); + return; + end; + + local permissionPanel = vgui.Create("tiger.panel"); + permissionPanel:SetTitle("Change rank permissions"); + permissionPanel:SetSize(500, 640); + permissionPanel:Center(); + permissionPanel:MakePopup(); + permissionPanel:DockPadding(24, 24, 24, 48); + + local permissionList = permissionPanel:Add("tiger.list"); + permissionList:Dock(FILL); + permissionList:DockMargin(0, 0, 0, 14); + + local rankPermissions = serverguard.ranks:GetData(unique, "Permissions", {}); + local permissions = serverguard.permission:GetAll(); + + for k, v in SortedPairs(permissions) do + local entry = vgui.Create("tiger.checkbox"); + permissionList:AddPanel(entry); + entry:Dock(TOP); + entry:SetText(k); + entry:SetChecked(rankPermissions[k] or false); + + function entry:OnChange(value) + permissions[k] = value; + end; + + permissions[k] = rankPermissions[k] or false; + end; + + local complete = permissionPanel:Add("tiger.button"); + complete:SetPos(4, 4); + complete:SetText("Complete"); + complete:SizeToContents(); + + function complete:DoClick() + serverguard.netstream.Start("sgChangeRankData", { + unique, "Permissions", permissions + }); + + permissionPanel:Remove(); + end + + local cancel = permissionPanel:Add("tiger.button") + cancel:SetPos(4, 4) + cancel:SetText("Cancel") + cancel:SizeToContents() + + function cancel:DoClick() + permissionPanel:Remove() + end + + function permissionPanel:PerformLayout() + local w, h = self:GetSize(); + + complete:SetPos(w - (complete:GetWide() + 24), h - (complete:GetTall() + 14)); + + cancel:SetPos(0, h - (cancel:GetTall() + 14)); + cancel:MoveLeftOf(complete, 14); + end; + end); + + option:SetImage("icon16/lock.png"); + + local option = menu:AddOption("Change color", function() + local baseb = vgui.Create("tiger.panel") + baseb:SetTitle("Select rank color") + baseb:SetSize(500, 500) + baseb:Center() + baseb:MakePopup() + baseb:DockPadding(24, 24, 24, 48) + + local label = baseb:Add("DLabel") + label:SetText(data.name) + label:SetColor(data.color) + label:SetFont("tiger.list.text") + label:Dock(TOP) + label:DockMargin(0, 0, 0, 14) + + local colorMixer = baseb:Add("DColorMixer") + colorMixer:Dock(FILL) + colorMixer:SetColor(data.color) + + function colorMixer:ValueChanged(color) + label:SetColor(color) + end + + local complete = baseb:Add("tiger.button") + complete:SetPos(4, 4) + complete:SetText("Complete") + complete:SizeToContents() + + function complete:DoClick() + local color = colorMixer:GetColor(); + + serverguard.netstream.Start("sgChangeRankInfo", { + unique, "color", SERVERGUARD.NETWORK.COLOR, color + }); + + baseb:Remove() + end + + local cancel = baseb:Add("tiger.button") + cancel:SetPos(4, 4) + cancel:SetText("Cancel") + cancel:SizeToContents() + + function cancel:DoClick() + baseb:Remove() + end + + function baseb:PerformLayout() + local w, h = self:GetSize() + + complete:SetPos(w -(complete:GetWide() +24), h -(complete:GetTall() +14)) + + cancel:SetPos(0, h -(cancel:GetTall() +14)) + cancel:MoveLeftOf(complete, 14) + end + end) + + option:SetImage("icon16/color_wheel.png") + + local option = menu:AddOption("Change physgun color", function() + local baseb = vgui.Create("tiger.panel") + baseb:SetTitle("Select physgun color") + baseb:SetSize(500, 500) + baseb:Center() + baseb:MakePopup() + baseb:DockPadding(24, 24, 24, 48) + + local colorMixer = baseb:Add("DColorMixer") + colorMixer:Dock(FILL) + colorMixer:SetColor(data.data.phys_color or Color(77, 255, 255)) + + local complete = baseb:Add("tiger.button") + complete:SetPos(4, 4) + complete:SetText("Complete") + complete:SizeToContents() + + function complete:DoClick() + local color = colorMixer:GetColor(); + + serverguard.netstream.Start("sgChangeRankData", { + unique, "phys_color", color + }); + + baseb:Remove() + end + + local cancel = baseb:Add("tiger.button") + cancel:SetPos(4, 4) + cancel:SetText("Cancel") + cancel:SizeToContents() + + function cancel:DoClick() + baseb:Remove() + end + + function baseb:PerformLayout() + local w, h = self:GetSize() + + complete:SetPos(w -(complete:GetWide() +24), h -(complete:GetTall() +14)) + + cancel:SetPos(0, h -(cancel:GetTall() +14)) + cancel:MoveLeftOf(complete, 14) + end + end) + + option:SetImage("icon16/color_wheel.png") + + local option = menu:AddOption("Change image", function() + local baseb = vgui.Create("tiger.panel") + baseb:SetTitle("Select rank image") + baseb:SetSize(500, 500) + baseb:Center() + baseb:MakePopup() + baseb:DockPadding(24, 24, 24, 48) + + local textureList = baseb:Add("tiger.list") + textureList:Dock(FILL) + textureList:DockMargin(0, 14, 0, 14) + textureList:GetCanvas():DockPadding(4, 4, 4, 4) + + local width = 0 + local image = "materials/octoteam/icons-16/user.png" + local images = file.Find("materials/octoteam/icons-16/*.png", "GAME") + local basea = vgui.Create("Panel") + basea:SetTall(16) + basea:Dock(TOP) + basea:DockMargin(0, 0, 0, 8) + + textureList:AddPanel(basea) + + local lastSelected + + for k, v in pairs(images) do + local icon = basea:Add("DImageButton"); + icon:Dock(LEFT); + icon:DockMargin(2, 0, 2, 0); + icon:SetImage("octoteam/icons-16/"..v); + icon:SetSize(16, 16); + + if (data.texture == icon:GetImage()) then + lastSelected = icon; + end; + + function icon:DoClick() + image = "octoteam/icons-16/"..v; + + if (IsValid(lastSelected)) then + lastSelected.selected = nil; + end; + + lastSelected = self; + end; + + function icon:PaintOver(w, h) + if (lastSelected == self) then + draw.SimpleOutlined(0, 0, w, h, color_red); + end; + end; + + width = width +16 + + if (width > 16 *22) then + basea = vgui.Create("Panel") + basea:SetTall(16) + basea:Dock(TOP) + basea:DockMargin(0, 0, 0, 8) + + textureList:AddPanel(basea) + + width = 0 + end; + end; + + local complete = baseb:Add("tiger.button") + complete:SetPos(4, 4) + complete:SetText("Complete") + complete:SizeToContents() + + function complete:DoClick() + if (image != "") then + serverguard.netstream.Start("sgChangeRankInfo", { + unique, "texture", SERVERGUARD.NETWORK.STRING, image + }); + end + + baseb:Remove() + end + + local cancel = baseb:Add("tiger.button") + cancel:SetPos(4, 4) + cancel:SetText("Cancel") + cancel:SizeToContents() + + function cancel:DoClick() + baseb:Remove() + end + + function baseb:PerformLayout() + local w, h = self:GetSize() + + complete:SetPos(w -(complete:GetWide() +24), h -(complete:GetTall() +14)) + + cancel:SetPos(0, h -(cancel:GetTall() +14)) + cancel:MoveLeftOf(complete, 14) + end + end) + + option:SetImage("icon16/image_edit.png") + + hook.Call("serverguard.panel.RankEditorContext", nil, menu, unique, data, category, base); + + local option = menu:AddOption("Remove rank", function() + util.CreateDialog("Notice", "Are you sure you want to delete this rank?", + function() + serverguard.netstream.Start("sgRemoveRank", unique); + end, "&Yes", + function() + end, "No" + ) + end) + + option:SetImage("icon16/delete.png") + menu:Open() + end + + local rankLabel = panel:GetLabel(2); + rankLabel:SetColor(data.color); + rankLabel.oldColor = data.color; + + panel.icon = vgui.Create("DImageButton"); + panel.icon:SetImage((data.texture != "" and data.texture or "icon16/user.png")); + panel.icon:SetSize(16, 16); + + function panel.icon:PerformLayout() + DImageButton.PerformLayout(self) + + local column = panel:GetThing(6).column; + local x = column:GetPos() + + self:SetPos(x +column:GetWide() /2 -8, column:GetTall() /2 -8) + end + + panel:AddItem(panel.icon); + + end +end + +serverguard.menu.AddCategory(category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/rank_list.lua b/garrysmod/addons/admin-sg/lua/modules/gui/rank_list.lua new file mode 100644 index 0000000..9dc5866 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/rank_list.lua @@ -0,0 +1,161 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Rank list" +category.material = "serverguard/menuicons/icon_rank_list.png" +category.permissions = {"Set Rank", "Edit Ranks"} + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Rank list") + base.panel:Dock(FILL) + base.panel:DockPadding(24, 24, 24, 48) + + base.panel.list = base.panel:Add("tiger.list") + base.panel.list:Dock(FILL) + base.panel.list.sortColumn = 3 + + base.panel.list:AddColumn("PLAYER", 175) + base.panel.list:AddColumn("STEAMID", 175) + base.panel.list:AddColumn("RANK", 75) + base.panel.list:AddColumn("EXPIRES IN", 75) + base.panel.list:AddColumn("LAST PLAYED", 75) + + hook.Call("serverguard.panel.RankList", nil, base.panel.list); + + local refresh = base.panel:Add("tiger.button") + refresh:SetPos(4, 4) + refresh:SetText("Refresh") + refresh:SizeToContents() + + function refresh:DoClick() + base.panel.list:Clear() + + serverguard.netstream.Start("sgRequestPlayerRanks", true); + end + + base.panel.list.nextUpdate = CurTime() + 1 + + function base.panel.list:Think() + if (self.nextUpdate and self.nextUpdate <= CurTime()) then + refresh:DoClick() + + self.nextUpdate = nil + end + end + + function base.panel:PerformLayout() + local w, h = self:GetSize() + + refresh:SetPos(w -(refresh:GetWide() +24), h -(refresh:GetTall() +14)) + end + + category.list = base.panel.list +end + +function category:Update(base) +end + +serverguard.menu.AddSubCategory("Lists", category) + +serverguard.netstream.Hook("sgGetRankList", function(data) + local steamid = data[1]; + local rank = data[2]; + local name = data[3]; + local expires_in = data[4]; + local last_played = data[5]; + + if (rank and rank == "user") then return; end; + + local rankData = serverguard.ranks:GetRank(rank); + + local rankName = ""; + + if (!rankData) then + rankName = "Unknown rank (REMOVED)"; + else + rankName = rankData.name; + end; + + local function formatDate(time) + local format_time = "never" + + if (isnumber(time)) and time > 0 then + local time = { + sec = time % 60, + min = math.floor(time/60) % 60, + hour = math.floor(time/3600) % 24, + day = math.floor(time/86400) % 7, + week = math.floor(time/604800) % 4, + month = math.floor(time/2592000) % 12, + year = math.floor(time/31536000), + } + + format_time = (time.year > 0 and (time.year .. "y - " .. time.month .. "mo") or time.month > 0 and (time.month .. "mo - " .. time.day .. "d") or time.day > 0 and (time.day .. "d - " .. time.hour .. "h") or time.hour > 0 and (time.hour .. "h - " .. time.min .. "m") or time.min > 0 and (time.min .. "m - " .. time.sec .. "s") or (time.sec .. "s")); + end + + return format_time + end + + + local panel = category.list:AddItem(name, steamid, rankName, ((isnumber(expires_in) and expires_in <= 0) and "expired") or formatDate(expires_in) or ((expires_in == "never") and "never") or "Unknown", player.GetBySteamID(steamid) and "online" or (formatDate(os.time(last_played)) .. " ago")); + + panel.steamid = steamid; + + function panel:OnMousePressed() + local menu = DermaMenu(); + menu:SetSkin("serverguard"); + + local rankMenu, menuOption = menu:AddSubMenu("Change Rank"); + + rankMenu:SetSkin("serverguard"); + menuOption:SetImage("icon16/award_star_add.png"); + + local sorted = {}; + + for k, v in pairs(serverguard.ranks:GetTable()) do + table.insert(sorted, v); + end; + + table.sort(sorted, function(a, b) + return a.immunity > b.immunity; + end); + + for _, data in pairs(sorted) do + local timeMenu, menuRank = rankMenu:AddSubMenu(data.name); + local option = timeMenu:AddOption("Indefinitely", function() + serverguard.netstream.Start("sgChangePlayerRank", { + self.steamid, data.unique, 0 + }); + + category.list.nextUpdate = CurTime() + 1; + end); + option:SetImage("icon16/clock.png"); + + local option = timeMenu:AddOption("Custom", function() + Derma_StringRequest("Rank Length", "Specify the time after which this rank will expire (in minutes).", "", function(length) + serverguard.netstream.Start("sgChangePlayerRank", { + self.steamid, data.unique, tonumber(length) + }); + end, function(text) end, "Accept", "Cancel"); + end); + option:SetImage("icon16/clock.png"); + + if (data.texture and data.texture != "") then + menuRank:SetImage(data.texture); + end; + end; + menu:Open(); + end; + + if (rankData) then + local rankLabel = panel:GetLabel(3); + rankLabel:SetColor(rankData.color); + rankLabel:SetSort(rankData.immunity); + rankLabel.oldColor = rankData.color; + end; +end); diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/server_settings.lua b/garrysmod/addons/admin-sg/lua/modules/gui/server_settings.lua new file mode 100644 index 0000000..383e402 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/server_settings.lua @@ -0,0 +1,22 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {} + +category.name = "Server settings" +category.material = "serverguard/menuicons/icon_plugins.png" +category.permissions = {"Manage Advertisements", "Manage Chat Settings", "Manage Plugins", "Manage Reserved Slots", "Sandbox settings", "Manage MOTD"} + +function category:Create(categoryBase) + + categoryBase.base = categoryBase:Add("tiger.base"); + categoryBase.base:SetFooterEnabled(false); + categoryBase.base:UseSmallPanels(true); + categoryBase.base:Dock(FILL); + +end + +serverguard.menu.AddCategory(category) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/gui/themes.lua b/garrysmod/addons/admin-sg/lua/modules/gui/themes.lua new file mode 100644 index 0000000..35fad24 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/gui/themes.lua @@ -0,0 +1,260 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local category = {}; + +category.name = "Themes"; +category.material = "serverguard/menuicons/icon_themes.png"; + +function category:Create(base) + local themes = serverguard.themes.GetStored(); + + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("Modify theme preferences"); + base.panel:Dock(FILL); + + base.panel.list = base.panel:Add("tiger.list"); + base.panel.list:Dock(FILL); + base.panel.list:DockMargin(0, 54, 0, 0); + base.panel.list:AddColumn("Theme name", 375); + base.panel.list:AddColumn("Selected"); + + hook.Call("serverguard.panel.ThemeList", nil, base.panel.list); + + local controls = { + "tiger.base", + "tiger.list", + "tiger.panel", + "tiger.divider", + "tiger.button", + "tiger.tooltip", + "tiger.news" + }; + + local function CreateControls() + base.panel.editFrame = vgui.Create("tiger.base"); + base.panel.editFrame:SetSize(ScrW() / 2 - 50, ScrH() - 50); + base.panel.editFrame:SetPos(25, 25); + + base.panel.colorBase = vgui.Create("tiger.panel"); + base.panel.colorBase:SetSize(ScrW() / 2 - 50, ScrH() - 25); + base.panel.colorBase:SetTitle("Section colors"); + base.panel.colorBase:Center(); + base.panel.colorBase:MoveRightOf(base.panel.editFrame, 25); + base.panel.colorBase:MakePopup(); + + local saveButton = base.panel.colorBase:Add("tiger.button"); + saveButton:Dock(TOP); + saveButton:DockMargin(0, 0, 0, 14); + saveButton:SetText("Save & Close"); + saveButton:SizeToContents(); + + function saveButton:DoClick() + base.panel.colorBase:Remove(); + base.panel.editFrame:Remove(); + + serverguard.themes.Save(); + end; + + local colorList = base.panel.colorBase:Add("tiger.list"); + colorList:Dock(FILL); + colorList:GetCanvas():DockPadding(14, 14, 14, 14); + + for k, v in ipairs(controls) do + base.panel.editFrame:AddSection(v, category.material, function(base) + if (!base.created) then + base.type = v; + + if (v != "tiger.base") then + local panel = base:Add(v); + panel:Dock(FILL); + panel:SetTall(100); + + if (v == "tiger.list") then + panel:AddColumn("This is a test column", 160); + panel:AddColumn("This is a test column", 160); + panel:AddColumn("This is a test column", 160); + + for i = 1, 20 do + panel:AddItem("This is a test item", "This is a test item", "This is a test item"); + panel:AddItem("This is a test item", "This is a test item", "This is a test item"); + end; + end; + + if (v == "tiger.panel") then + panel:SetTitle("Test title"); + end; + + if (v == "tiger.divider") then + for i = 1, 20 do + panel:AddRow("Test row", "Test row"); + end; + end; + + if (v == "tiger.button") then + panel:SetText("Test button"); + end; + + if (v == "tiger.tooltip") then + panel:SetText("Test tooltip"); + panel:SetDrawOnTop(false); + end; + + if (v == "tiger.news") then + panel:DockPadding(1, 1, 1, 1); + panel:PopulateArticle("Test Article", os.date("%B %d, %Y"), "

This is a test article.

", "

Even more content!

"); + end; + end; + + base.created = true; + else + colorList:Clear(); + + local colors = serverguard.themes.GetColorNames(base.type); + + for k2, v2 in pairs(colors) do + local current = serverguard.themes.GetCurrent(); + + for k3, v3 in pairs(current) do + if (v2 == k3) then + local panel = vgui.Create("DColorMixer"); + panel:SetTall(124); + panel:SetLabel(k3); + panel:SetColor(v3); + panel:Dock(TOP); + panel:SetPalette(false); + + function panel:ValueChanged(color) + serverguard.themes.Set(k3, color); + end; + + colorList:AddPanel(panel); + end; + end; + end; + end; + end); + end; + + serverguard.menu.Close(true); + end; + + local function populateThemeList() + themes = serverguard.themes.GetStored(); + + base.panel.list:Clear(); + base.panel.selectTheme:Clear(); + + -- Theme list. + for name, data in pairs(themes) do + local panel = base.panel.list:AddItem(name, ""); + + function panel:OnMousePressed(keyCode) + if (keyCode == MOUSE_LEFT) then + RunConsoleCommand("serverguard_theme", name); + elseif (keyCode == MOUSE_RIGHT) then + local menu = DermaMenu() + menu:SetSkin("serverguard"); + + local option = menu:AddOption("Select theme", function() + RunConsoleCommand("serverguard_theme", name); + end); + + option:SetImage("icon16/accept.png"); + + if (!serverguard.themes.IsDefaultTheme(name)) then + option = menu:AddOption("Remove theme", function() + local currentTheme, currentName = serverguard.themes.GetCurrent(); + + if (name == currentName) then + RunConsoleCommand("serverguard_theme", "default"); + end; + + serverguard.themes.Remove(name); + panel:Clear(); + panel:Remove(); + panel = nil; + populateThemeList(); + end); + + option:SetImage("icon16/delete.png"); + end; + menu:Open(); + end; + end; + + local label = panel:GetLabel(2); + + label:SetUpdate(function(self) + if (serverguard.theme:GetString() == name) then + self:SetText("Yes"); + else + self:SetText(""); + end; + end); + end; + + -- Theme dropdown. + base.panel.selectTheme:AddChoice("Create new"); + + for name, data in pairs(themes) do + base.panel.selectTheme:AddChoice(name); + end; + end; + + base.panel.selectTheme = base.panel:Add("DComboBox"); + base.panel.selectTheme:SetSize(150, 22); + base.panel.selectTheme:SetPos(24, 74); + base.panel.selectTheme:SetText("Edit theme"); + base.panel.selectTheme:SetFont("tiger.button"); + base.panel.selectTheme:SetSkin("serverguard"); + base.panel.selectTheme:AddChoice("Create new"); + + function base.panel.selectTheme:OpenMenu(pControlOpener) + DComboBox.OpenMenu(self, pControlOpener); + + self.Menu:SetSkin("serverguard"); + end; + + function base.panel.selectTheme:OnSelect(index, value, data) + if (IsValid(base.panel.editFrame)) then + base.panel.editFrame:Remove(); + base.panel.colorBase:Remove(); + end; + + if (value == "Create new") then + base.panel.themeName = base.panel:Add("DTextEntry"); + base.panel.themeName:SetPos(14 + base.panel.selectTheme:GetWide() + 28, 74); + base.panel.themeName:SetSize(200, base.panel.selectTheme:GetTall()); + base.panel.themeName:SetSkin("serverguard"); + + function base.panel.themeName:OnEnter() + local value = self:GetValue(); + + serverguard.themes.New(value, serverguard.themes.GetCurrent()); + serverguard.themes.Save(); + populateThemeList(); + + RunConsoleCommand("serverguard_theme", value); + CreateControls(); + + self:Remove(); + end; + else + if (IsValid(base.panel.themeName)) then + base.panel.themeName:Remove(); + end; + + RunConsoleCommand("serverguard_theme", value); + + CreateControls(); + end; + end; + + populateThemeList(); +end; + +serverguard.menu.AddCategory(category); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_cami.lua b/garrysmod/addons/admin-sg/lua/modules/sh_cami.lua new file mode 100644 index 0000000..58f66bb --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_cami.lua @@ -0,0 +1,492 @@ +--[[ +CAMI - Common Admin Mod Interface. +Makes admin mods intercompatible and provides an abstract privilege interface +for third party addons. +IMPORTANT: This is a draft script. It is very much WIP. +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 = 20150902.1 + +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 usergroupName1 + 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. + Note: this function does NOT return an immediate result! + The result is in the callback! + 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) + 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. + 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: + None, the answer is given in the callback function in order to allow + for the admin mod to perform e.g. a database lookup. +]] +-- 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) + hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly, + privilegeName, callback, targetPly, extraInfoTbl) +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 pairs(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 \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_commands.lua b/garrysmod/addons/admin-sg/lua/modules/sh_commands.lua new file mode 100644 index 0000000..4bd72cc --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_commands.lua @@ -0,0 +1,487 @@ +--[[ + � 2017 Thriving Ventures Ltd do not share, re-distribute or modify + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- The registration and handling of chat commands. +-- @module serverguard.command + +local string = string; + +serverguard.command = serverguard.command or {}; +serverguard.command.stored = serverguard.command.stored or {}; + +--- Gets a table of all the registered commands. +-- @treturn table Command data. +function serverguard.command:GetTable() + return self.stored; +end; + +serverguard.command.GetStored = serverguard.command.GetTable; + +--- Registers a new command. +-- @table data The command table to extract info from. +function serverguard.command:Add(data) + self.stored[data.command] = data; + + if (data.permissions) then + serverguard.permission:Add(data.permissions); + end; + + if (SERVER) then + self.stored[data.command].ContextMenu = nil; + end; +end; + +--- Removes a command. +-- @string uniqueID The unique ID of the command. +function serverguard.command:Remove(uniqueID) + self.stored[uniqueID] = nil; +end; + +--- Gets a command by its unique ID. +-- @string uniqueID The unique ID of the command. +-- @treturn table Command data. +function serverguard.command:FindByID(uniqueID) + local command = self.stored[uniqueID]; + + if (command) then + return command; + else + for k, v in pairs(self.stored) do + if (v.aliases and table.HasValue(v.aliases, uniqueID)) then + return v; + end; + end; + end; +end; + +--- Gets the formatted argument string from a command table. +-- @table commandTable The command table. +-- @treturn string Formatted string. +function serverguard.command:GetArgumentsString(commandTable) + local result = ""; + local first = true; + + if (commandTable.arguments) then + for k, v in pairs(commandTable.arguments) do + if (first) then + first = false; + else + result = result .. " "; + end; + + result = result .. "<" .. v .. ">"; + end; + end; + + if (commandTable.optionalArguments) then + for k, v in pairs(commandTable.optionalArguments) do + if (first) then + first = false; + else + result = result .. " "; + end; + + result = result .. "[" .. v .. "]"; + end; + end; + + return result; +end; + +serverguard.command.Get = serverguard.command.FindByID; + +local function GetPlayerSteamID32(player) + local stid_32 = string.format("%.0f", string.sub(player:SteamID64(), 3) - "561197960265728") + + return stid_32 +end + + +if (SERVER) then + local notifyCommands = octolib.array.toKeys{ 'ban', 'unban', 'kick', 'rank' } + + --[[ Internal function run a command. --]] + local function RUN_COMMAND(commandTable, player, bIsSilent, arguments) + if (!commandTable) then + return; + end; + + bIsSilent = bIsSilent or not notifyCommands[commandTable.command or ''] + + -- Legacy command style. + if (commandTable.Execute) then + local bStatus, value = pcall(commandTable.Execute, commandTable, player, bIsSilent, arguments); + + if (!commandTable.bNoLog) then + serverguard.PrintConsole(string.format( + "%s ran command \"%s %s\"\n", serverguard.player:GetName(player), commandTable.command, table.concat(arguments, " ") + )); + end; + + if (bStatus) then + hook.Call("serverguard.RanCommand", nil, player, commandTable, bIsSilent, arguments); + else + ErrorNoHalt(string.format("The \"%s\" command has failed to run.\n%s\n", commandTable.command, value)); + return; + end; + else + if (commandTable.OnExecute) then + commandTable:OnExecute(player, arguments); + end; + + local targets = {}; + + if (commandTable.bSingleTarget) then + local target = util.FindPlayer(arguments[1], player); + + if (target and IsValid(target)) then + if (!util.PlayerMatchesImmunity(commandTable.immunity, player, target)) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "This player has a higher immunity than you."); + else + if (commandTable:OnPlayerExecute(player, target, arguments)) then + targets = {target}; + end; + end; + end; + else + targets = util.ExecuteOnPlayers(arguments[1], player, commandTable.immunity or SERVERGUARD.IMMUNITY.LESSOREQUAL, function(target) + return commandTable:OnPlayerExecute(player, target, arguments); + end); + end; + + if (!bIsSilent and commandTable.OnNotify and targets and #targets > 0 and !hook.Call("serverguard.CommandNotify", nil, player, targets, arguments)) then + local arguments = {commandTable:OnNotify(player, targets, arguments)}; + + serverguard.Notify(nil, unpack(arguments)); + end; + + if (commandTable.OnExecuted) then + commandTable:OnExecuted(player, targets, arguments); + end; + + if (!commandTable.bNoLog) then + serverguard.PrintConsole(string.format( + "%s ran command \"%s %s\"\n", serverguard.player:GetName(player), commandTable.command, table.concat(arguments, " ") + )); + end; + + hook.Call("serverguard.RanCommand", nil, player, commandTable, bIsSilent, arguments); + end; + end; + + --- Makes a player run a command. + -- @player player The player running the command. You should **not** pass this argument clientside. + -- @string command The unique ID of the command to run. + -- @bool bIsSilent Whether or not to display a notification. + -- @param ... Any arguments to pass to the command. + -- @treturn bool Whether or not the command exists. + function serverguard.command.Run(player, command, bIsSilent, ...) + local arguments = {...}; + local commandTable = serverguard.command:FindByID(command); + + if (commandTable) then + if (commandTable.bDisallowConsole and util.IsConsole(player)) then + serverguard.PrintConsole("You cannot run the \"" .. commandTable.command .. "\" command as the server.\n"); + return true; + end; + + if (hook.Call("serverguard.PlayerCanUseCommand", nil, player, commandTable, bIsSilent, arguments) != false) then + if (util.IsConsole(player)) then + RUN_COMMAND(commandTable, player, bIsSilent, arguments); + return true; + end; + + if (commandTable.permissions and #commandTable.permissions > 0) then + if (!serverguard.player:HasPermission(player, commandTable.permissions)) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format("You do not have access to this command, %s.", player:Nick())); + return true; + end; + end; + + if (!commandTable.arguments or #arguments >= #commandTable.arguments) then + RUN_COMMAND(commandTable, player, bIsSilent, arguments); + elseif (!util.IsConsole(player) and #arguments == 0 and commandTable.arguments and #commandTable.arguments == 1 and commandTable.arguments[1] == "player") then + RUN_COMMAND(commandTable, player, bIsSilent, {player:Name()}); + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "The syntax for this command is incorrect:"); + serverguard.Notify(player, SERVERGUARD.NOTIFY.WHITE, string.format("!%s %s", commandTable.command, serverguard.command:GetArgumentsString(commandTable))); + end; + end; + + return true; + end; + + return false; + end; + + hook.Add("PlayerSay", "serverguard.command.PlayerSay", function(pPlayer, text, m_bToAll, m_bDead) + if (text == "") then + return; + end; + + local prefix = string.lower(string.sub(text, 1, 1)); + local cmd = string.lower(string.Explode(' ', text)[1]) + + if (prefix == "!" or prefix == "~") then + local arguments = util.ExplodeByTags(text, " ", "\"", "\"", true); + local commandName = string.lower(string.sub(arguments[1], #prefix + 1)); + + table.remove(arguments, 1); + + if (pPlayer:GetDBVar('sgMuted') and commandName != "unmute") then + return ""; + end; + + local commandExists = serverguard.command.Run(pPlayer, commandName, false, unpack(arguments)); + + if (commandExists) then + return ""; + end; + elseif (pPlayer:GetDBVar('sgMuted')) then + return ""; + end + end); + + serverguard.netstream.Hook("sgRunCommand", function(pPlayer, data) + serverguard.command.Run(pPlayer, data.command, data.silent, unpack(data.arguments)); + end); + + concommand.Add("sg", function(pPlayer, command, arguments) + if (arguments and arguments[1]) then + local commandName = string.lower(arguments[1]); + local commandTable = serverguard.command:FindByID(commandName); + + table.remove(arguments, 1); + + if (commandTable) then + serverguard.command.Run(pPlayer, commandTable.command, false, unpack(arguments)); + end; + end; + end); +elseif (CLIENT) then + local autoComplete = {} + local autoCompleteWidth = 0 + local autoCompleteHeight = 0 + local autoCompleteActive = 0 + local autoCompletePrefix = {} + + autoCompletePrefix["!"] = true + autoCompletePrefix["~"] = true + + function serverguard.command.Run(command, bIsSilent, ...) + serverguard.netstream.Start("sgRunCommand", { + command = command, silent = bIsSilent, arguments = {...} + }); + end; + + concommand.Add("sg", function(_, command, arguments) + if (arguments and arguments[1]) then + local commandName = string.lower(arguments[1]); + local commandTable = serverguard.command:FindByID(commandName); + + table.remove(arguments, 1); + + if (commandTable) then + serverguard.command.Run(commandTable.command, false, unpack(arguments)); + end; + end; + end, function(command, arguments) + local results = {}; + local commands = serverguard.command:GetTable(); + + arguments = util.ExplodeByTags(string.Trim(arguments), " ", "\"", "\"", true); + + if (arguments[1]) then + local command = serverguard.command:FindByID(arguments[1]); + + if (command and serverguard.player:HasPermission(LocalPlayer(), command.permissions)) then + local sg_cmdabout = "sg " .. command.command .. " " .. serverguard.command:GetArgumentsString(command) + + table.insert(results, sg_cmdabout); + + if (command.arguments and #command.arguments > 0) then + for k, v in pairs(command.arguments) do + if (v == "player") then + for k, target in ipairs(player.GetAll()) do + if (arguments[2]) then + for _, result in pairs(results) do + if (result == sg_cmdabout) then + results[_] = nil + end + end + + if !(target:IsBot()) then + if (target:Name():lower():find(arguments[2]) or target:SteamID():find(arguments[2]) or target:SteamID():lower():find(arguments[2]) or target:SteamID64():find(arguments[2]) or GetPlayerSteamID32(target):find(arguments[2])) then + table.insert(results, "sg " .. command.command .. " \"" .. target:Name() .. "\"") + end + else + if (target:Name():lower():find(arguments[2]) or target:SteamID():find(arguments[2])) then + table.insert(results, "sg " .. command.command .. " \"" .. target:Name() .. "\""); + end + end + else + table.insert(results, "sg " .. command.command .. " \"" .. target:Name() .. "\"") + end + end; + end; + end; + end; + elseif (!arguments[2]) then + for k, commandTable in util.SortedPairsByMemberValue(commands, "command") do + if (string.sub(commandTable.command, 1, string.len(arguments[1])) == arguments[1] and serverguard.player:HasPermission(LocalPlayer(), commandTable.permissions)) then + table.insert(results, "sg " .. commandTable.command .. " "); + end; + end; + end; + else + for k, commandTable in util.SortedPairsByMemberValue(commands, "command") do + if (serverguard.player:HasPermission(LocalPlayer(), commandTable.permissions)) then + table.insert(results, "sg " .. commandTable.command .. " "); + end; + end; + end; + + hook.Call("serverguard.ConsoleAutoComplete", nil, arguments, results); + + return results; + end); + + local function serverGuard_AddAutoCompletePrefixes() + hook.Call("serverguard.AddPrefixes", nil, autoCompletePrefix) + end + + timer.Simple(4, serverGuard_AddAutoCompletePrefixes) + + local function serverGuard_PaintAutoComplete() + if (#autoComplete > 0) then + local x, y = chat.GetChatBoxPos() + + if (Clockwork) then + x, y = Clockwork.chatBox:GetPosition() + end + + draw.RoundedBox(2, x, y -(6 +autoCompleteHeight), autoCompleteWidth +10, autoCompleteHeight, Color(0, 0, 0, 200)) + + for k, v in pairs(autoComplete) do + if (k == autoCompleteActive) then + draw.SimpleText(v, "serverGuard_ownerFont", x +4, y -16 *k, Color(20, 193, 20, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + else + draw.SimpleText(v, "serverGuard_ownerFont", x +4, y -16 *k, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + end + end + + hook.Add("HUDPaint", "serverguard_chatcommands.HUDPaint", serverGuard_PaintAutoComplete) + + local table = table + local isTabbing = false + + local function serverGuard_CheckChatText(text) + if (!isTabbing) then + autoComplete = {} + + -- Check the prefix. + if (autoCompletePrefix[string.sub(text, 0, 1)]) then + autoCompleteWidth = 0 + autoCompleteHeight = 0 + + -- IS THIS A GOOD IDEA? LOL + local commands = table.Copy(serverguard.command.stored) + + hook.Call("serverguard.AddAutoComplete", nil, commands) + + for command, data in pairs(commands) do + if (string.find(command, text)) then + local aText = command + local width, height = util.GetTextSize("serverGuard_ownerFont", aText) + + if (data.arguments) then + local args = "" + + for k, v in pairs(data.arguments) do + args = args .. v .. " " + end + + aText = command .. " " .. args + + width, height = util.GetTextSize("serverGuard_ownerFont", aText) + end + + if (width > autoCompleteWidth) then + autoCompleteWidth = width + end + + autoCompleteHeight = autoCompleteHeight +height + + table.insert(autoComplete, aText) + end + end + + if (#autoComplete == 1) then + autoCompleteActive = 1 + end + + autoCompleteHeight = autoCompleteHeight +3 + end + end + end + + hook.Add("ChatTextChanged", "serverguard_chatcommands.ChatTextChanged", serverGuard_CheckChatText) + + local function serverGuard_ChatboxClosed() + autoComplete = {} + autoCompleteWidth = 0 + autoCompleteHeight = 0 + autoCompleteActive = 0 + + isTabbing = false + end + + hook.Add("FinishChat", "serverguard_chatcommands.FinishChat", serverGuard_ChatboxClosed) + + timer.Create("serverguard.autocomplete.timer", FrameTime() *8, 0, function() timer.Stop("serverguard.autocomplete.timer") end) + + local function serverGuard_ChatboxTabbed(text) + isTabbing = true + + autoCompleteActive = autoCompleteActive +1 + + if (autoCompleteActive > #autoComplete) then + autoCompleteActive = 1 + end + + if (autoComplete[autoCompleteActive]) then + local start = string.find(autoComplete[autoCompleteActive], "<") + + if (start) then + timer.Adjust("serverguard.autocomplete.timer", FrameTime() *8, 1, function() + isTabbing = false + + timer.Stop("serverguard.autocomplete.timer") + end) + + timer.Start("serverguard.autocomplete.timer") + + return string.sub(autoComplete[autoCompleteActive], 0, start -2) + else + timer.Adjust("serverguard.autocomplete.timer", FrameTime() *8, 1, function() + isTabbing = false + + timer.Stop("serverguard.autocomplete.timer") + end) + + timer.Start("serverguard.autocomplete.timer") + + return autoComplete[autoCompleteActive] + end + end + + isTabbing = false + end + + hook.Add("OnChatTab", "serverguard_chatcommands.OnChatTab", serverGuard_ChatboxTabbed) +end diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_config.lua b/garrysmod/addons/admin-sg/lua/modules/sh_config.lua new file mode 100644 index 0000000..d4ccc35 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_config.lua @@ -0,0 +1,514 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- Simplifies the saving/loading/networking of configuration options. Files will be saved in `data/serverguard/config/.txt`. + +-- @module serverguard.config + +serverguard.AddFolder("config"); + +serverguard.config = serverguard.config or {}; + +local stored = {} + +--- Configuration class object that's used to save, load, and network settings to clients. +-- @type CONFIG_CLASS +local CONFIG_CLASS = {}; +CONFIG_CLASS.__index = CONFIG_CLASS; +CONFIG_CLASS.__tostring = function(self) + local result = "Config class \"" .. self.name .. "\":\n"; + + for k, v in pairs(self.entries) do + result = result .. "\t" .. k .. "\t(" .. v.type .. ")\t-> " .. tostring(v.value) .. "\n"; + end; + + return result; +end; + +local CONFIG_TYPES = { + --- Adds a configuration option whose key is a boolean. + -- @string key The key it's referenced by. + -- @string value The default value that'll be set if none exists. + -- @string description[opt] The description of the option. + -- @usage config:AddBoolean("testbool", true, "Just a test boolean."); + Boolean = function(value) + return tobool(value); + end, + + --- Adds a configuration option whose key is a number. + -- @string key The key it's referenced by. + -- @string value The default value that'll be set if none exists. + -- @string description[opt] The description of the option. + -- @usage config:AddNumber("testnumber", 1234, "Numbers are great!"); + Number = function(value) + return util.ToNumber(value); + end; + + --- Adds a configuration option whose key is a string. + -- @string key The key it's referenced by. + -- @string value The default value that'll be set if none exists. + -- @string description[opt] The description of the option. + -- @usage config:AddString("teststring", "hello hello hello!", "Strings are pretty cool too."); + String = function(value) + return tostring(value); + end; +}; + +--- **This is called internally, use the create method.** +-- Creates a new configuration object with the specified name. +-- This name is used as both a reference and the name of the file it's saved to. As such, ensure that the name is something the OS will like. +-- @see serverguard.config.Create +-- @string name The name of the config object. +-- @return New config object. +function CONFIG_CLASS:New(name) + local object = setmetatable({}, CONFIG_CLASS); + object.name = name; + object.entries = {}; + object.autoSave = true; + object.saveQueued = false; + object.loaded = false; + object.permissions = {}; + return object; +end; + +for k, v in pairs(CONFIG_TYPES) do + CONFIG_CLASS["Add" .. k] = function(self, key, default, description) + self.entries[key] = { + type = k, + value = v(default), + description = tostring(description or ""), + boundPanels = {}, + callbacks = {} + }; + end; +end; + +--- Sets a configuration value and networks it to clients. +-- @string key The key of the config option. +-- @string value The value to set it to. +-- @bool bDontNetwork[opt] Whether or not to network it to clients. +function CONFIG_CLASS:SetValue(key, value, bDontNetwork) + if (!self.entries[key]) then + ErrorNoHalt("[config] Tried to set non-existent key \"" .. key .. "\"!"); + return; + end; + + local newValue = CONFIG_TYPES[self.entries[key].type](value); + self.entries[key].value = newValue; + self.loaded = true; + + for k, v in pairs(self.entries[key].callbacks) do + v(newValue); + end; + + if (SERVER) then + if (self.autoSave) then + self.saveQueued = true; + end; + else + for k, v in pairs(self.entries[key].boundPanels) do + v:OnBoundConfigChanged(newValue); + end; + end; + + if (bDontNetwork) then + return; + end; + + if (SERVER) then + serverguard.netstream.Start(nil, "sgConfigUpdate", { + name = self.name, + key = key, + type = self.entries[key].type, + value = newValue + }); + else + serverguard.netstream.Start("sgConfigUpdate", { + name = self.name, + key = key, + type = self.entries[key].type, + value = newValue + }); + end; +end; + +--- Sets a required permission to set values from the client. +-- @string permission The required permission. Can be a table of permissions. +function CONFIG_CLASS:SetPermission(permission) + if (type(permission) != "table") then + self.permissions = {tostring(permission)}; + else + self.permissions = permission; + end; +end; + +--- Checks whether or not a player has permission to set the value. +-- @player player The player to check permissions for. +-- @return bool Whether or not they have permission. +function CONFIG_CLASS:CheckPermission(player) + if (#self.permissions < 1) then + return true; + end; + + return serverguard.player:HasPermission(player, self.permissions); +end; + +--- Sets whether or not the configuration object auto-saves. +-- When set, it will be automatically saved when you set a value. +-- @bool value Whether or not to auto-save. +function CONFIG_CLASS:SetAutoSave(value) + self.autoSave = tobool(value); +end; + +--- Adds a callback function to run when the specified key has been changed. +-- @string key The key to monitor for changes. +-- @func callback The function to call when the value changes. +function CONFIG_CLASS:AddCallback(key, callback) + if (!self.entries[key]) then + return; + end; + + table.insert(self.entries[key].callbacks, callback); +end; + +--- Gets the description of the configuration option. +-- @string key The key of the configuration option. +-- @treturn string The description. +function CONFIG_CLASS:GetDescription(key) + if (!self.entries[key] or !self.entries[key].description) then + return; + end; + + return self.entries[key].description; +end; + +--- Gets the serialized representation of the configuration object. +-- @treturn string The serialized object. +function CONFIG_CLASS:GetSerialized() + local data = {}; + + for k, v in pairs(self.entries) do + data[k] = { + type = v.type, + value = v.value + }; + end; + + return serverguard.von.serialize(data); +end; + +--- Gets the value assigned to the given key. Returns nil if the key doesn't exist. +-- @string key The key to get the value from. +-- @return The value of the key. +function CONFIG_CLASS:GetValue(key) + if (!self.entries[key]) then + return nil; + end; + + return self.entries[key].value; +end; + +--- Gets a key -> value table of all the configuration options. +-- @treturn table Table of options. +function CONFIG_CLASS:GetKeyValues() + local data = {}; + + for k, v in pairs(self.entries) do + data[k] = v.value; + end; + + return data; +end; + +--- Returns the raw entry table for the configuration object. +-- @treturn table Configuration table. +function CONFIG_CLASS:GetTable() + return self.entries; +end; + +--- Loads the saved configuration options from disk. +-- Requests a full update from the server if ran on the client. +-- @treturn CONFIG_CLASS The configuration object. +function CONFIG_CLASS:Load() + if (SERVER) then + if (file.Exists("serverguard/config/" .. self.name .. ".txt", "DATA")) then + local data = serverguard.von.deserialize(file.Read("serverguard/config/" .. self.name .. ".txt", "DATA")); + + for k, v in pairs(data) do + if (!self.entries[k]) then + continue; + end; + + self:SetValue(k, v.value, true); + end; + + self.loaded = true; + else + self:Save(true); + end; + end; + + if (CLIENT) then + timer.Simple(1, function() + self:RequestFullUpdate(); + end); + end; + + return self; +end; + +if (SERVER) then + local lastSaveTime = CurTime(); + local fullRequestQueue = {}; + + local function SaveConfigClass(object) + file.Write("serverguard/config/" .. object.name .. ".txt", object:GetSerialized()); + object.saveQueued = false; + end; + + --- **(SERVERSIDE)** Queues the configuration object to be saved to disk. + -- @bool bForce[opt] Whether or not to bypass the queue and save immediately to disk. Not recommended. + function CONFIG_CLASS:Save(bForce) + if (bForce) then + SaveConfigClass(self); + return; + end; + + self.saveQueued = true; + end; + + -- **(SERVERSIDE)** Sends a full update of all configuration options to the specified player(s). + -- @player player The player to send the update to. Can also be a table of players. + function CONFIG_CLASS:SendFullUpdate(player) + local data = { + name = self.name, + entries = {} + }; + + for k, v in pairs(self.entries) do + data.entries[k] = v.value; + end; + + serverguard.netstream.Start(player, "sgReceiveFullUpdate", data); + end; + + hook.Add("Think", "serverguard.config.Think", function() + local bSaved = false; + + for k, v in pairs(stored) do + if (v.loaded and fullRequestQueue[k] and #fullRequestQueue > 0) then + v:SendFullUpdate(fullRequestQueue[k]); + + fullRequestQueue[k] = nil; + end; + + if (v.loaded and CurTime() >= lastSaveTime + 25) then + if (!v.entries or !v.saveQueued) then + continue; + end; + + SaveConfigClass(v); + bSaved = true; + end; + end; + + if (bSaved) then + lastSaveTime = CurTime(); + end; + end); + + serverguard.netstream.Hook("sgConfigUpdate", function(player, data) + if (!stored[data.name] or !stored[data.name]:CheckPermission(player)) then + return; + end; + + stored[data.name]:SetValue(data.key, data.value, true); + stored[data.name]:Save(); + end); + + serverguard.netstream.Hook("sgRequestFullUpdate", function(player, data) + if (!stored[data.name]) then + return; + end; + + if (!stored[data.name].loaded) then + if (!fullRequestQueue[data.name]) then + fullRequestQueue[data.name] = {}; + end; + + table.insert(fullRequestQueue[data.name], player); + return; + end; + + stored[data.name]:SendFullUpdate(player); + end); +else + local panelSetup = false; + + --- **(CLIENTSIDE)** Requests a full update of all configuration values from the server. + -- @see CONFIG_CLASS:Load + function CONFIG_CLASS:RequestFullUpdate() + serverguard.netstream.Start("sgRequestFullUpdate", { + name = self.name + }); + end; + + serverguard.netstream.Hook("sgConfigUpdate", function(data) + if (!stored[data.name]) then + return; + end; + + stored[data.name]:SetValue(data.key, data.value, true); + end); + + serverguard.netstream.Hook("sgReceiveFullUpdate", function(data) + if (!stored[data.name]) then + return; + end; + + for k, v in pairs(data.entries) do + stored[data.name]:SetValue(k, v, true); + stored[data.name].loaded = true; -- client doesn't really care + end; + end); + + hook.Add("PostGamemodeLoaded", "serverguard.config.PostGamemodeLoaded", function() + if (panelSetup) then + return; + end; + + local DCheckBox = vgui.GetControlTable("DCheckBox"); + local DNumSlider = vgui.GetControlTable("DNumSlider"); + + local tiger_checkbox = vgui.GetControlTable("tiger.checkbox"); + local tiger_numslider = vgui.GetControlTable("tiger.numslider"); + + local checkboxFunction = function(self, config, key) + if (!stored[config] or + !stored[config].entries[key] or + !stored[config].entries[key].type == "Boolean") then + return; + end; + + local configEntry = stored[config].entries[key]; + + self:SetChecked(configEntry.value); + self.sgOldOnChange = self.OnChange; + + self.OnChange = function(_self, value) + stored[config]:SetValue(key, value); + + self.sgOldOnChange(self, value); + end; + + self.OnBoundConfigChanged = function(_self, value) + self:SetChecked(value); + end; + + table.insert(configEntry.boundPanels, self); + end; + + local sliderFunction = function(self, config, key) + if (!stored[config] or + !stored[config].entries[key] or + !stored[config].entries[key].type == "Number") then + return; + end; + + local configEntry = stored[config].entries[key]; + + self:SetValue(configEntry.value); + self.sgOldValueChanged = self.ValueChanged; + + self.ValueChanged = function(_self, value) + stored[config]:SetValue(key, (bRound and math.Round(value)) or value); + + self.sgOldValueChanged(self, value); + end; + + self.OnBoundConfigChanged = function(_self, value) + self:SetValue(value); + end; + + table.insert(configEntry.boundPanels, self); + end; + + if (DCheckBox) then + --- Binds a panel to a configuration option. + -- It will update the panel when the configuration option is updated, and vice versa. + -- This overrides OnChange/ValueChanged/etc for panels, so make sure you call this last! It will retain previous callbacks when overriding. + -- Applicable panels include: DCheckBox, DNumSlider, tiger.checkbox, tiger.numslider. + -- @string config The name of the configuration object. + -- @string key The configuration option to bind the panel to. + -- @bool bRound[opt] **DNumSlider only:** Whether or not to round the value before updating. + DCheckBox.BindToConfig = checkboxFunction; + end; + + if (tiger_checkbox) then + tiger_checkbox.BindToConfig = checkboxFunction; + end; + + if (DNumSlider) then + DNumSlider.BindToConfig = sliderFunction; + end; + + if (tiger_numslider) then + tiger_numslider.BindToConfig = sliderFunction; + end; + + panelSetup = true; + end); +end; + +--- Creates a new configuration object with the specified name. +-- @string unique The name of the config object. +-- @return New config object. +function serverguard.config.Create(unique) + local object = CONFIG_CLASS:New(unique); + stored[unique] = object; + return object; +end; + +local oldStored = {} + +--- **(DEPRECATED)** Add a new configuration entry. The callback function is passed a table argument of the saved data. +-- @string unique The unique ID of the entry. +-- @func callback Function to execute when the data is loaded. +function serverguard.config.New(unique, callback) + oldStored[unique] = callback +end + +--- **(DEPRECATED)** Saves the configuration entry. +-- @string unique The unique ID of the entry. +-- @table data The data to save. +function serverguard.config.Save(unique, data) + file.Write("serverguard/config/" .. unique .. ".txt", serverguard.von.serialize(data)) +end + +if (SERVER) then + hook.Add("serverguard.Initialize", "serverguard.config.Initialize", function() + for unique, callback in pairs(oldStored) do + local data = file.Read("serverguard/config/" .. unique .. ".txt") + + if (data) then + callback(serverguard.von.deserialize(data)) + end + end + + hook.Call("serverguard.PostLoadConfig", nil); + end) +else + hook.Add("serverguard.LoadPlayerData", "serverguard.config.LoadPlayerData", function() + for unique, callback in pairs(oldStored) do + local data = file.Read("serverguard/config/" .. unique .. ".txt") + + if (data) then + callback(serverguard.von.deserialize(data)) + end + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_netstream.lua b/garrysmod/addons/admin-sg/lua/modules/sh_netstream.lua new file mode 100644 index 0000000..7f81f9b --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_netstream.lua @@ -0,0 +1,309 @@ +--[[ + NetStream - 1.0.2 + + Alexander Grist-Hucker + http://www.revotech.org + + Credits to: + Alexandru-Mihai Maftei aka Vercas for vON. + https://github.com/vercas/vON +--]] + +--- ## Shared +-- Networking helper library that provides easier access to networking with chunking support. +-- @module serverguard.netstream + +local type, error, pcall, pairs, AddCSLuaFile, _player = type, error, pcall, pairs, AddCSLuaFile, player; + +--[[ + AddCSLuaFile("includes/modules/von.lua"); + require("von"); +--]] + +AddCSLuaFile(); + +serverguard.netstream = serverguard.netstream or {}; + +local stored = {}; +local chunks = {}; + +local bb_write, bb_read = + include "bitbuf_glua/write.lua", + include "bitbuf_glua/read.lua" + +local coded = + include "bitbuf_glua/table.lua" + +local function encode(t) + local buf = bb_write() + coded.write(buf, t) + return buf:ExportAsString() +end + +local function decode(t) + local read = bb_read(t) + return coded.read(read) +end + +serverguard.tables = { + write = function(t) + local dat = encode(t) + net.WriteUInt(#dat, 32) + net.WriteData(dat, #dat) + end, + read = function() + return decode(net.ReadData(net.ReadUInt(32))) + end +} +local tables = serverguard.tables; + +--- Splits a table into a series of strings for easier networking. +-- @see serverguard.netstream.Build +-- @table data The data to split. +-- @treturn table A table of strings representing the split data. +function serverguard.netstream.Split(data) + if (type(data) != "string") then + data = encode(data) + end; + + local result = {}; + + for i = 1, data:len(), 0x8000 do + result[#result + 1] = data:sub(i, i + 0x7fff) + end + + return result; +end; + +--- Reconstructs a table of split strings. +-- @see serverguard.netstream.Split +-- @table data The data to reconstruct. +-- @treturn table The reconstructed data. +function serverguard.netstream.Build(data) + return decode(table.concat(data)); +end; + +--- Adds a hook to a data stream. +-- @see serverguard.netstream.Start +-- @string name Name of the data stream. +-- @func callback Function to execute when the data stream has been received. +function serverguard.netstream.Hook(name, Callback) + if (!stored[name]) then + stored[name] = {}; + end; + + table.insert(stored[name], Callback); +end; + +function serverguard.netstream.GetStored() + return stored; +end; + +if (SERVER) then + local chunkQueue = {}; + + util.AddNetworkString("ServerGuardDS"); + util.AddNetworkString("ServerGuardChunkedDS"); + + --- Sends a data stream to the target. Will send to client as server and vice versa. + -- @see serverguard.netstream.Hook + -- @player player The player to send the data to. You should **not** pass this argument clientside. + -- @string name The name of the data stream to send. + -- @table data The data to send. + function serverguard.netstream.Start(ply, name, data) + net.Start("ServerGuardDS"); + net.WriteString(name); + tables.write {data}; + + if (ply) then + if (istable(ply)) then + local recipients = RecipientFilter() + + for k, v in pairs(ply) do + -- FIXME: Do player finding here or assume they are player entities? + if (isplayer(v)) then + recipients:AddPlayer(v); + elseif (isplayer(k)) then + recipients:AddPlayer(k); + end; + end; + + net.Send(recipients) + else + net.Send(ply) + end; + else + net.Broadcast() + end; + end; + + --- Sends a data stream to the target. This is used for larger data sets that may exceed the 64kb networking limit. + -- @see serverguard.netstream.Hook + -- @player player The player to send the data to. You should **not** pass this argument clientside. + -- @string name The name of the data stream to send. + -- @table data The data to send. + function serverguard.netstream.StartChunked(pPlayer, name, data) + local recipients; + + -- Skip loop if we can + if (pPlayer) then + if (istable(pPlayer)) then + recipients = {} + local pos = -1 + + for k, v in pairs(pPlayer) do + if (isplayer(v)) then + pos = pos + 1 + recipients[pos] = v; + elseif (isplayer(k)) then + pos = pos + 1 + recipients[pos] = k; + end; + end; + + if (pos == -1) then + recipients = nil + end + else + recipients = {pPlayer} + end + else + recipients = player.GetAll() + end + + if (recipients) then + local splitData = serverguard.netstream.Split({data = (data or 0)}); + + if (splitData) then + local len = #splitData + + if (len ~= 0) then + chunkQueue[name] = { + recipients = recipients, + data = splitData; + }; + + net.Start("ServerGuardChunkedDS"); + net.WriteString(name); + net.WriteUInt(1, 32); + net.WriteUInt(len, 32); + + local len = #splitData[1] + net.WriteUInt(len, 32); + net.WriteData(splitData[1], len); + net.Send(recipients); + end + end; + end; + end; + + net.Receive("ServerGuardDS", function(length, pPlayer) + local NS_DS_NAME = net.ReadString(); + local NS_DS_DATA = tables.read(); + + if (NS_DS_NAME) then + pPlayer.nsDataStreamName = NS_DS_NAME; + pPlayer.nsDataStreamData = ""; + pPlayer.nsDataStreamData = NS_DS_DATA; + + if (stored[pPlayer.nsDataStreamName]) then + for k, v in pairs(stored[pPlayer.nsDataStreamName]) do + v(pPlayer, NS_DS_DATA[1]); + end; + end; + + pPlayer.nsDataStreamName = nil; + pPlayer.nsDataStreamData = nil; + end; + end); + + net.Receive("ServerGuardChunkedDS", function(length, player) + local NS_DS_NAME = net.ReadString(); + local NS_DS_NEXT = net.ReadUInt(32); + + if (!chunkQueue[NS_DS_NAME] or !chunkQueue[NS_DS_NAME].data[NS_DS_NEXT] or !table.HasValue(chunkQueue[NS_DS_NAME].recipients, player)) then + return; + end; + + if (NS_DS_NEXT >= #chunkQueue[NS_DS_NAME].data) then + for k, v in pairs(chunkQueue[NS_DS_NAME].recipients) do + if (v == player) then + chunkQueue[NS_DS_NAME].recipients[k] = nil; + end; + end; + end; + + net.Start("ServerGuardChunkedDS"); + net.WriteString(NS_DS_NAME); + net.WriteUInt(NS_DS_NEXT, 32); + net.WriteUInt(#chunkQueue[NS_DS_NAME].data, 32); + + net.WriteUInt(#chunkQueue[NS_DS_NAME].data[NS_DS_NEXT], 32); + net.WriteData(chunkQueue[NS_DS_NAME].data[NS_DS_NEXT], #chunkQueue[NS_DS_NAME].data[NS_DS_NEXT]); + net.Send(player); + end); +else + function serverguard.netstream.Start(name, data) + net.Start("ServerGuardDS"); + net.WriteString(name); + tables.write {data} + net.SendToServer(); + end; + + net.Receive("ServerGuardDS", function(length) + local NS_DS_NAME = net.ReadString(); + local NS_DS_DATA = tables.read(); + + if (NS_DS_NAME) then + if (stored[NS_DS_NAME]) then + for k, v in pairs(stored[NS_DS_NAME]) do + v(NS_DS_DATA[1]); + end; + end; + end; + end); + + net.Receive("ServerGuardChunkedDS", function(length) + local NS_DS_NAME = net.ReadString(); + local NS_DS_CURRENT = net.ReadUInt(32); + local NS_DS_TOTAL = net.ReadUInt(32); + + local NS_DS_LENGTH = net.ReadUInt(32); + local NS_DS_DATA = net.ReadData(NS_DS_LENGTH); + + if (!NS_DS_NAME or !NS_DS_CURRENT or !NS_DS_TOTAL or !NS_DS_LENGTH or !NS_DS_DATA) then + return; + end; + + if (!chunks[NS_DS_NAME]) then + chunks[NS_DS_NAME] = {}; + end; + + if (NS_DS_CURRENT > 0 and NS_DS_DATA) then + table.insert(chunks[NS_DS_NAME], NS_DS_DATA); + end; + + if (NS_DS_CURRENT >= NS_DS_TOTAL) then + if (chunks[NS_DS_NAME] and stored[NS_DS_NAME]) then + local bStatus, value = pcall(serverguard.netstream.Build, chunks[NS_DS_NAME]); + + if (bStatus) then + for k, v in pairs(stored[NS_DS_NAME]) do + v(value.data); + end; + + chunks[NS_DS_NAME] = nil; + else + ErrorNoHalt("NetStream: '"..NS_DS_NAME.."'\n"..value.."\n"); + end; + end; + else + net.Start("ServerGuardChunkedDS"); + net.WriteString(NS_DS_NAME); + net.WriteUInt(NS_DS_CURRENT + 1, 32); + net.SendToServer(); + end; + + NS_DS_NAME, NS_DS_CURRENT, NS_DS_TOTAL, NS_DS_LENGTH, NS_DS_DATA = nil, nil, nil, nil, nil; + end); +end; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_permissions.lua b/garrysmod/addons/admin-sg/lua/modules/sh_permissions.lua new file mode 100644 index 0000000..97dcb54 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_permissions.lua @@ -0,0 +1,127 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- Library to store and retrieve what permissions are available. +-- @module serverguard.permission + +include "modules/sh_cami.lua" + +serverguard.permission = serverguard.permission or {}; +serverguard.permission.stored = serverguard.permission.stored or {}; + +--- Check whether or not a permission exists. +-- @string identifier The name of the permission. +-- @treturn boolean Whether or not the permission exists. +function serverguard.permission:Exists(identifier) + if (type(identifier) == "string") then + return self.stored[identifier]; + end; +end; + +--- Adds a permission. +-- @string identifier The name of the permission. +function serverguard.permission:Add(identifier) + if (type(identifier) == "string") then + if (!self.stored[identifier]) then + self.stored[identifier] = true; + CAMI.RegisterPrivilege({ + Name = identifier, + MinAccess = "invalid" + }) + end; + elseif (type(identifier) == "table") then + for k, v in pairs(identifier) do + if (type(v) == "string") then + self:Add(v); + end; + end; + end; +end; + +--- Removes a permission. +-- @string identifier The name of the permission. +function serverguard.permission:Remove(identifier) + if (type(identifier) == "string") then + if (self.stored[identifier]) then + CAMI.UnregisterPrivilege(identifier) + self.stored[identifier] = nil; + end; + end; +end; + +--- Gets a table of all permissions. +-- @treturn table Table of all permissions. +function serverguard.permission:GetAll() + return self.stored; +end; + +serverguard.permission:Add("Quick Menu"); +serverguard.permission:Add("Manage Players"); +serverguard.permission:Add("Manage Plugins"); +serverguard.permission:Add("Admin"); +serverguard.permission:Add("Superadmin"); +serverguard.permission:Add("Physgun Player"); +serverguard.permission:Add("See Help Requests"); +serverguard.permission:Add("Unban"); +serverguard.permission:Add("Edit Ban"); + +hook.Add("PhysgunPickup", "serverguard.PhysgunPickup", function(pPlayer, pEntity) + if (pEntity:IsPlayer() and serverguard.player:HasPermission(pPlayer, "Physgun Player") and serverguard.player:CanTarget(pPlayer, pEntity)) then + pPlayer.sg_physgunPlayer = pEntity; + pEntity.sg_playerPhysgunned = true; + + pEntity:SetLocalVelocity(Vector(0, 0, 0)); + pEntity:SetMoveType(MOVETYPE_NONE); + pEntity:SetCollisionGroup(COLLISION_GROUP_WORLD); + + return true; + end; +end); + +hook.Add("PhysgunDrop", "serverguard.PhysgunDrop", function(pPlayer, pEntity) + if (pEntity:IsPlayer() and pEntity.sg_playerPhysgunned) then + pPlayer.sg_physgunPlayer = nil; + + pEntity:SetMoveType(MOVETYPE_WALK); + pEntity:SetCollisionGroup(COLLISION_GROUP_PLAYER); + + pEntity.sg_playerPhysgunned = false; + end; +end); + +hook.Add("KeyPress", "serverguard.KeyPress", function(pPlayer, nKey) + if (nKey == IN_ATTACK2) then + local pActiveWeapon = pPlayer:GetActiveWeapon() + + if (pActiveWeapon ~= NULL and pActiveWeapon:GetClass() == "weapon_physgun") then + local pEntity = pPlayer.sg_physgunPlayer; + + if (IsValid(pEntity)) then + if (serverguard.player:HasPermission(pPlayer, "Physgun Player") and serverguard.player:CanTarget(pPlayer, pEntity)) then + pPlayer.sg_physgunPlayer = nil; + pEntity.sg_playerPhysgunned = false; + + pEntity:SetLocalVelocity(Vector(0, 0, 0)); + pEntity:SetMoveType(MOVETYPE_NONE); + pEntity:SetCollisionGroup(COLLISION_GROUP_PLAYER); + end; + end; + end; + end; +end); + +if (SERVER) then + hook.Add("CanPlayerSuicide", "serverguard.CanPlayerSuicide", function(pPlayer) + if (pPlayer.sg_playerPhysgunned) then + return false; + end; + end); + + hook.Add("PostGamemodeLoaded", "serverguard.permissions.PostGamemodeLoaded", function() + hook.Remove("PhysgunDrop", "FAdmin_PickUpPlayers"); + end); +end; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_phrase.lua b/garrysmod/addons/admin-sg/lua/modules/sh_phrase.lua new file mode 100644 index 0000000..cdc53c0 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_phrase.lua @@ -0,0 +1,236 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- Provides support for languages through the use of phrases. +-- @module serverguard.phrase + +serverguard.phrase = serverguard.phrase or { + currentLanguage = "english" +}; + +local languages = {}; + +--- Adds a language that phrases can use. +-- @string language The name of the language. +function serverguard.phrase:AddLanguage(language) + languages[language] = {}; +end; + +--- Sets the language used for phrases. +-- @string language The name of the language. +function serverguard.phrase:SetLanguage(language) + if (!languages[language]) then + return; + end; + + self.currentLanguage = language; +end; + +--- Adds a phrase to the specified language. Supports string formatting with %s, etc. +-- @string language The name of the language. +-- @string unique The unique ID of the phrase. +-- @param format A string or table of strings/notify constants. +-- @usage serverguard.phrase:Add("english", "test_phrase", "This is a test."); +-- @usage serverguard.phrase:Add("english", "another_phrase", "This one has some %s formatting!"); +-- @usage serverguard.phrase:Add("english", "test_phrase_three", {SERVERGUARD.NOTIFY.GREEN, "This", SERVERGUARD.NOTIFY.WHITE, " has some ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " formatting."}); +function serverguard.phrase:Add(language, unique, format) + if (!languages[language]) then + return; + end; + + languages[language][unique] = format; +end; + +--- Adds languages and phrases specified in the table. See modules/sh_phrase.lua for an example table. +-- @table data Table of languages and phrases. +function serverguard.phrase:AddTable(data) + for languageUnique, language in pairs(data) do + if (!languages[language]) then + self:AddLanguage(languageUnique); + end; + + for phraseUnique, phrase in pairs(language) do + self:Add(languageUnique, phraseUnique, phrase); + end; + end; +end; + +--- Returns the text from a phrase. +-- @string unique The unique ID of the phrase. +-- @param ...[opt] Any parameters that the phrase may need. +-- @treturn string The text from the phrase. +function serverguard.phrase:Get(unique, ...) + if (!languages[self.currentLanguage][unique]) then + return ""; + end; + + local phrase = languages[self.currentLanguage][unique]; + local result = ""; + + if (type(phrase) == "table") then + for k, v in pairs(phrase) do + if (type(v) == "string") then + result = result .. v; + end; + end; + else + result = phrase; + end; + + return string.format(result, ...); +end; + +--- Returns the notify-formatted text from a phrase. +-- @string unique The unique ID of the phrase. +-- @param ...[opt] Any parameters that the phrase may need. +-- @treturn table A table formatted for use with serverguard.Notify(). +-- @see SGPF +-- @see serverguard.Notify +function serverguard.phrase:GetFormatted(unique, ...) + if (!languages[self.currentLanguage][unique]) then + return {SERVERGUARD.NOTIFY.WHITE, ""}; + end; + + local phrase = languages[self.currentLanguage][unique]; + + if (type(phrase) == "string") then + return {SERVERGUARD.NOTIFY.WHITE, phrase}; + end; + + local arguments = {...}; + local currentArgument = 1; + local result = {}; + + for k, v in pairs(phrase) do + if (type(v) != "string") then + result[#result + 1] = v; + continue; + end; + + if (string.find(v, "%", 0, true)) then + local argument = arguments[currentArgument]; + + if (type(argument) == "table") then + for k2, v2 in pairs(argument) do + result[#result + 1] = v2; + end; + + currentArgument = currentArgument + 1; + else + result[#result + 1] = string.format(v, arguments[currentArgument]); + currentArgument = currentArgument + 1; + end; + + continue; + end; + + result[#result + 1] = v; + end; + + return unpack(result); +end; + +--- A shorthand function for serverguard.phrase:Get(). +-- @string unique The unique ID of the phrase. +-- @param ...[opt] Any parameters that the phrase may need. +-- @see serverguard.phrase:Get +function SGP(unique, ...) + return serverguard.phrase:Get(unique, ...); +end; + +--- A shorthand function for serverguard.phrase:GetFormatted(). +-- @string unique The unique ID of the phrase. +-- @param ...[opt] Any parameters that the phrase may need. +-- @see serverguard.phrase:GetFormatted +-- @usage serverguard.Notify(nil, SGFP("test_phrase_three", "awesome")); +function SGPF(unique, ...) + return serverguard.phrase:GetFormatted(unique, ...); +end; + +serverguard.phrase:AddTable({ + english = { + player_cant_find_suitable = {SERVERGUARD.NOTIFY.RED, "Couldn't find any suitable players!"}, + player_found_multiple = {SERVERGUARD.NOTIFY.RED, "Found more than one player with the identifier \"%s\"!"}, + player_higher_immunity = {SERVERGUARD.NOTIFY.RED, "This player has a higher immunity than you."}, + cant_find_location = {SERVERGUARD.NOTIFY.RED, "Couldn't find a suitable location."}, + cant_find_player_with_identifier = {SERVERGUARD.NOTIFY.RED, "Can't find any player with that identifier."}, + restricted = {SERVERGUARD.NOTIFY.RED, "You are restricted and unable to use this feature for %s."}, + + -- Command phrases. + command_ban_invalid_duration = {SERVERGUARD.NOTIFY.RED, "You've entered an invalid duration! Try entering a number, or format it with duration identifiers. For example: 1w2d12h"}, + command_ban_exceed_banlimit = {SERVERGUARD.NOTIFY.RED, "You've entered a length that exceeds your rank's maximum allowed ban length. Try entering a lower number than %s!"}, + command_ban_cannot_permaban = {SERVERGUARD.NOTIFY.RED, "You do not have permission to ban permanently!"}, + command_ban_clamped_duration = {SERVERGUARD.NOTIFY.RED, "One or more of your durations have been clamped to 99 - duration lengths cannot be over 99."}, + command_ban = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". Reason: %s"}, + command_ban_perma = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ",", SERVERGUARD.NOTIFY.RED, " permanently", SERVERGUARD.NOTIFY.WHITE, ". Reason: %s"}, + + command_unban = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has unbanned ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_no_entry = {SERVERGUARD.NOTIFY.RED, "No ban entry exists for that Steam ID!"}, + + command_rank = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "%s rank to ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, ". Length: ", SERVERGUARD.NOTIFY.RED, "%s."}, + command_rank_invalid_immunity = {SERVERGUARD.NOTIFY.RED, "The rank you are trying to set has a higher immunity than yours."}, + command_rank_invalid_unique = {SERVERGUARD.NOTIFY.RED, "No rank with the '%s' unique identifier exists."}, + command_rank_valid_list = {SERVERGUARD.NOTIFY.RED, "Here are some valid ranks: %s"}, + command_rank_cannot_set_own = {SERVERGUARD.NOTIFY.RED, "You can't set your own rank in-game. Use \"serverguard_setrank \" in your server console."}, + + command_goto = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has teleported to ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_bring = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has brought ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " to their location."}, + + command_send_player = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has sent ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " to ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " location."}, + command_send = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has sent ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " to their location."}, + + command_return = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has returned to their previous location."}, + command_return_invalid = {SERVERGUARD.NOTIFY.RED, "No previous position set! Try using a teleport command."}, + + command_admintalk = {SERVERGUARD.NOTIFY.WHITE, "Sent the message to the admin chat."}, + command_admintalk_invalid = {SERVERGUARD.NOTIFY.WHITE, "There are currently no admins online."}, + + command_god = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has enabled godmode for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_god_disable = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has disabled godmode for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + + command_ignite = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has ignited ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_extinguish = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has extinguished ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + + command_spectate = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has started spectating ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_spectate_invalid = {SERVERGUARD.NOTIFY.RED, "You cannot spectate yourself!"}, + command_spectate_hint = {SERVERGUARD.NOTIFY.WHITE, "Press crouch to stop spectating."}, + + command_jail = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has jailed ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". Duration: ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_unjail = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has unjailed ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_jailtp = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has teleported and jailed ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " at their location. Duration: ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + + command_kick = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has kicked ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". Reason: %s"}, + command_maprestart = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " is restarting the map in ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " seconds!"}, + command_freezeprops = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has frozen all props."}, + command_stripweapons = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has stripped all weapons from ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_respond_invalid = {SERVERGUARD.NOTIFY.RED, "That player doesn't have a pending help request."}, + command_slay = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has slayed ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_giveweapon = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has given ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " a ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_armor = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " armor to ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_hp = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " health to ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_respawn = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has respawned ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_ammo = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has given ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " ammo to ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_slap = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has slapped ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_npctarget = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has toggled NPC targeting for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_cleardecals = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has cleared all decals."}, + command_ragdoll = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has toggled ragdolling for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_mute = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has muted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_mute_ooc = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has OOC-muted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_unmute = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has unmuted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_unmute_ooc = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has OOC-unmuted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_unmute_looc = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has LOOC-unmuted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_invisible = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has toggled invisibility for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_freeze = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has toggled freezing for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_noclip = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has toggled noclip for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_gag = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has gagged ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, "%s", "."}, + command_ungag = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has ungagged ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_restrict = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has restricted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "%s features for ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + command_unrestrict = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has unrestricted ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "%s features."}, + + command_nocollide = {SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has toggled nocollide on ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "."}, + } +}); diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_player.lua b/garrysmod/addons/admin-sg/lua/modules/sh_player.lua new file mode 100644 index 0000000..f307097 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_player.lua @@ -0,0 +1,423 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- Player manipulation. +-- @module serverguard.player + +serverguard.player = serverguard.player or {}; + +--- Sets a player's rank. +-- @player player The player to set the rank of. +-- @string rank The unique ID of the rank to set as. +-- @bool[opt] length Amount of time (in seconds) after which the rank will be taken away from the player. +-- @bool[opt] bSaveless Whether or not to save the player's rank. Defaults to false; +-- @treturn bool Whether or not the operation was successful. +function serverguard.player:SetRank(player, rank, length, bSaveless) + local rankData = serverguard.ranks:GetRank(rank); + + if (!rankData) then + ErrorNoHalt("Error: (SetRank) Attempted to set non-existent rank to player '"..tostring(rank).."'.\n"..debug.traceback().."\n"); + return false; + end; + + if (isstring(player) and string.SteamID(player)) then + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:Where("steam_id", player); + queryObj:Limit(1); + queryObj:Callback(function(result, status, lastID) + if (istable(result) and #result > 0) then + local updateObj = serverguard.mysql:Update("serverguard_users"); + updateObj:Update("rank", rank); + updateObj:Where("steam_id", player); + updateObj:Execute(); + else + local insertObj = serverguard.mysql:Insert("serverguard_users"); + insertObj:Insert("name", "Unknown"); + insertObj:Insert("rank", rank); + insertObj:Insert("steam_id", player); + insertObj:Insert("last_played", os.time()); + insertObj:Insert("data", + serverguard.von.serialize( + { + groupExpire = ((length != nil and tonumber(length) != nil and length > 0) and os.time() + math.ceil(tonumber(length))) or 0 + } + ) + ); + insertObj:Execute(); + end; + end); + queryObj:Execute(); + + return true; + end; + + if (!IsValid(player)) then + ErrorNoHalt("Error: (SetRank) Attempted to set rank to non-existent player.\n"..debug.traceback().."\n"); + return false; + end; + + player.sg_incognito = nil; + player:SetNetVar("serverguard_rank", rank); + player:SetUserGroup(rank); + + if timer.Exists("serverguard.timer.RemoveRank_" .. player:UniqueID()) then + timer.Remove("serverguard.timer.RemoveRank_" .. player:UniqueID()); + end; + + if (length != nil and tonumber(length) != nil and length > 0) then + serverguard.player:SetData(player, "groupExpire", os.time() + math.ceil(tonumber(length))); + timer.Create("serverguard.timer.RemoveRank_" .. player:UniqueID(), math.ceil(tonumber(length)), 1, function() + if (IsValid(player)) then + serverguard.player:SetRank(player, "user", 0); + end; + end); + else + serverguard.player:SetData(player, "groupExpire", false); + end + + if (!bSaveless) then + serverguard.ranks:SavePlayerRank(player, rankData.unique); + end; + + if (SERVER) then + timer.Simple(FrameTime() * 32, function() + hook.Run('octolib.updateNetVars', player) + serverguard.netstream.Start(player, "sgSetPlayerRank", true); + end); + end; + + return true; +end; + +--- Gets the player's rank unique ID. Defaults to "user" if it doesn't exist. +-- @player player The player to get the rank of. +-- @treturn string The rank's unique ID. +function serverguard.player:GetRank(player) + result = "user"; + + if (IsValid(player) and player:IsPlayer()) then + result = player:GetNetVar("serverguard_rank", "user"); + elseif (util.IsConsole(player)) then + result = "founder"; + end; + + return result; +end; + +--- Sets a player's immunity level. +-- @player player The player to set the immunity of. +-- @number immunity The new immunity level to set. +function serverguard.player:SetImmunity(player, immunity) + player:SetNetVar("serverguard_immunity", immunity); +end; + +--- Sets a player's ban limit. +-- @player player The player to set the ban limit of. +-- @number limit The new ban limit to set. +function serverguard.player:SetBanLimit(player, limit) + player:SetNetVar("serverguard_banlimit", limit); +end; + +--- Sets a player's targetable rank. +-- @player player The player to set the targetable rank of. +-- @number immunity The new targetable rank to set. +function serverguard.player:SetTargetableRank(player, immunity) + player:SetNetVar("serverguard_targetable_rank", immunity); +end; + +--- Gets a player's immunity level. Defaults to 0 if the immunity is not set. +-- @player player The player to get the immunity of. +-- @treturn number The immunity level of the player. +function serverguard.player:GetImmunity(player) + if (IsValid(player)) then + return player:GetNetVar("serverguard_immunity", 0); + else + return 100; + end; +end; + +--- Gets a player's ban length. +-- @player player The player to get the banlimit of. +-- @number length The ban limit to get. +function serverguard.player:GetBanLimit(player) + if (IsValid(player)) then + return player:GetNetVar("serverguard_banlimit", 0); + end +end; + +--- Gets a player's targetable rank. Defaults to 0 if the immunity is not set. +-- @player player The player to get the targetable rank of. +-- @treturn number The targetable rank of the player. +function serverguard.player:GetTargetableRank(player) + if (IsValid(player)) then + return player:GetNetVar("serverguard_targetable_rank", 0); + else + return 100; + end; +end; + +--- Returns whether or not the player has a better targetable level than the immunity specified. +-- @player player The player to check the targetable rank of. +-- @number immunity The immunity level to check with. +-- @treturn bool Whether or not the player has a better immunity. +function serverguard.player:HasBetterImmunity(player, immunity) + return self:GetTargetableRank(player) >= immunity; +end; +--- Returns whether or not the player has a better targetable level than the target's immunity level. +-- @player player The player to check the targetable rank of. +-- @player target The target to check with. +-- @treturn bool Whether or not the player has a better immunity. +function serverguard.player:CanTarget(player, target) + return self:GetTargetableRank(player) >= serverguard.player:GetImmunity(target); +end; + +--- Gets a player's name. Will return with the orignal name in if player.SteamName is defined. +function serverguard.player:GetName(pPlayer) + if (util.IsConsole(pPlayer)) then + return "Console"; + elseif (isplayer(pPlayer)) then + local playerName = pPlayer:Name(); + + if (pPlayer.SteamName) then + playerName = "(" .. pPlayer:SteamName() .. ") " .. playerName; + end; + + return playerName; + else + return "Unknown"; + end; +end; + +--- Sets up the spectate routine for the given player. Strips weapons, makes invisible, +-- disables collision, etc. +-- @player player The player to set up the spectate mode for. +-- @number mode The spectate mode to use. +function serverguard.player:SetupSpectate(player, mode) + if (!player.sg_spectateData) then + local weapons = player:GetWeapons(); + + player.sg_spectateData = {}; + player.sg_spectateData.weapons = {}; + player.sg_spectateData.position = player:GetPos(); + player.sg_spectateData.team = player:Team(); + + for k, v in pairs(weapons) do + table.insert(player.sg_spectateData.weapons, v:GetClass()); + end; + end; + + player:StripWeapons(); + player:SetTeam(TEAM_SPECTATOR); + player:Spectate(mode); + player:SpectateEntity(NULL); + player:SetCollisionGroup(COLLISION_GROUP_WORLD); + + if (mode == OBS_MODE_ROAMING) then + player:SetMoveType(MOVETYPE_NOCLIP); + else + player:SetMoveType(MOVETYPE_OBSERVER); + + if (IsValid(player.spectateTarget)) then + player:SpectateEntity(player.spectateTarget); + end; + end; + + if (!player.spectatorMode) then + player.spectatorMode = 1; + end; + + player.sg_spectating = true; +end; + +--- Stops a player from spectating. Returns all of their stuff to normal; weapons, +-- collision, etc. +-- @player player The player to make stop spectating. +function serverguard.player:StopSpectate(player) + player:SetTeam(player.sg_spectateData.team); + player:Spectate(OBS_MODE_NONE); + player:SetMoveType(MOVETYPE_WALK); + player:SpectateEntity(NULL); + player:SetCollisionGroup(COLLISION_GROUP_PLAYER); + + player:Spawn(); + + player:SetPos(player.sg_spectateData.position); + + for k, v in pairs(player.sg_spectateData.weapons) do + player:Give(v); + end; + + player.sg_spectating = nil; + player.sg_spectateData = nil; +end; + +--- Gets the target player that the given player is spectating. +-- @player player The spectating player. +-- @bool[opt] decrease Whether or not to get the next/previous player. +-- @treturn player The spectated player. +function serverguard.player:GetSpectatorTarget(pPlayer, decrease) + if (isentity(decrease)) then + pPlayer.spectatorIndex = pPlayer.spectatorIndex or 1; + + return decrease; + end + + local players = player.GetAll(); + + for i = 1, #players do + if (v == pPlayer) then + table.remove(players, i); + + break; + end; + end; + + pPlayer.spectatorIndex = pPlayer.spectatorIndex or 0; + pPlayer.spectatorIndex = decrease and pPlayer.spectatorIndex - 1 or pPlayer.spectatorIndex + 1; + + if (pPlayer.spectatorIndex > #players) then + pPlayer.spectatorIndex = 1; + elseif (pPlayer.spectatorIndex < 1) then + pPlayer.spectatorIndex = #players; + end; + + return players[player.spectatorIndex]; +end; + +--- Makes a player spectate the set target. +-- @player player The player to put into spectate mode. +-- @bool[opt] decrease Whether to get the next/previous target. +function serverguard.player:SpectateTarget(player, decrease) + local target = self:GetSpectatorTarget(player, decrease); + + player:SpectateEntity(target); + + player.spectateTarget = target; +end; + +--- Changes a player's spectator mode. +-- @player player The player to change the mode of. +-- @bool[opt] decrease Whether to get the next/previous target. +function serverguard.player:ChangeSpectatorMode(player, decrease) + local spectatorModes = { + OBS_MODE_ROAMING, + OBS_MODE_CHASE, + OBS_MODE_IN_EYE + }; + + player.spectatorMode = decrease and player.spectatorMode - 1 or player.spectatorMode + 1; + + if (player.spectatorMode > #spectatorModes) then + player.spectatorMode = 1; + elseif (player.spectatorMode < 1) then + player.spectatorMode = #spectatorModes; + end; + + local mode = spectatorModes[player.spectatorMode]; + + self:SetupSpectate(player, mode); +end; + +if (CLIENT) then + serverguard.netstream.Hook("sgSetPlayerRank", function(data) + serverguard.menu:Rebuild(); + end); +end; + +--- Gets whether or not the player has the given permission. +-- @player player The player to check the permissions of. +-- @string identifier The name of the permission. This can also be a table of string to check for multiple permissions. +-- @treturn bool Whether or not the player has the given permission. +function serverguard.player:HasPermission(player, identifier) + if (self:GetRank(player) == "founder") then + return true; + end; + + local permissionTable = serverguard.ranks:GetData( + self:GetRank(player), "Permissions" + ); + + if (permissionTable) then + local identype = type(identifier) + + if (identype == "string") then + return permissionTable[identifier] or false; + end + + if (identype == "table") then + for k, v in pairs(identifier) do + if (isstring(v) and permissionTable[v]) then + return true; + end; + end; + end; + end; + + return false; +end; + +do + local playerMeta = FindMetaTable("Player"); + + playerMeta.sgIsAdmin = playerMeta.sgIsAdmin or playerMeta.IsAdmin; + playerMeta.sgIsSuperAdmin = playerMeta.sgIsSuperAdmin or playerMeta.IsSuperAdmin; + playerMeta.sgIsUserGroup = playerMeta.sgIsUserGroup or playerMeta.IsUserGroup; + + -- + -- Override IsAdmin to check serverguard rank. + -- + + function playerMeta:IsAdmin() + if (self:IsSuperAdmin() or serverguard.player:HasPermission(self, "Admin")) then + return true; + end; + + return self:sgIsAdmin(); + end; + + -- + -- Override IsSuperAdmin to check serverguard rank. + -- + + function playerMeta:IsSuperAdmin() + if (serverguard.player:HasPermission(self, "Superadmin")) then + return true; + end; + + return self:sgIsSuperAdmin(); + end; + + -- + -- Override IsUserGroup to check serverguard rank. + -- + + function playerMeta:IsUserGroup(name) + if (serverguard.player:GetRank(self) == name) then + return true; + end; + + return self:sgIsUserGroup(name) + end; + + -- + -- Override GetUserGroup to check serverguard rank. + -- + + function playerMeta:GetUserGroup() + return serverguard.player:GetRank(self); + end; + + -- + -- Override Ban to use serverguard ban. + -- + + function playerMeta:Ban(length, bKick, reason) + reason = reason or "No Reason"; + + serverguard:BanPlayer(nil, self, length, reason, bKick); + end; +end; diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_plugin.lua b/garrysmod/addons/admin-sg/lua/modules/sh_plugin.lua new file mode 100644 index 0000000..33ed09d --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_plugin.lua @@ -0,0 +1,390 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +--- Library for managing plugins. +-- @module serverguard.plugin + +serverguard.plugin = serverguard.plugin or {}; + +local stored = {}; +local object = {}; + +object.__index = object; + +--- Creates a new plugin table. +-- @treturn table Plugin table. +function serverguard.plugin.New() + local plugin = {}; + + plugin.hooks = {}; + plugin.commands = {}; + plugin.categories = {}; + plugin.toggled = true; + + setmetatable(plugin, object); + + return plugin; +end; + +--- Registers a plugin. +-- @table plugin Plugin table to register. +function serverguard.plugin.Register(plugin) + stored[plugin.unique] = plugin; + + if (plugin.permissions) then + serverguard.permission:Add(plugin.permissions); + end; +end; + +--- Returns a plugin table with the specified name. +-- @string unique Unique ID of the plugin. +-- @treturn table Plugin table. +function serverguard.plugin.FindByID(unique) + return stored[unique]; +end; + +serverguard.plugin.Get = serverguard.plugin.FindByID; + +--- Returns all registered plugins. +-- @treturn table Table of plugin tables. +function serverguard.plugin.GetList() + return stored; +end; + +--- Enables or disables a plugin. This toggles any registered hooks, chat commands, or UI categories. +-- @string uniqueID Unique ID of the plugin. +-- @bool bToggled Whether or not to turn the plugin on. +function serverguard.plugin:Toggle(uniqueID, bToggled) + if (!stored[uniqueID]) then + return; + end; + + stored[uniqueID].toggled = bToggled; + + local hooks = stored[uniqueID].hooks; + local commands = stored[uniqueID].commands; + + for name, data in pairs(hooks) do + for hookUnique, callback in pairs(data) do + if (bToggled) then + hook.Add(name, hookUnique, callback); + else + hook.Remove(name, hookUnique); + end; + end; + end; + + for k, v in pairs(commands) do + if (bToggled) then + serverguard.command:Add(v); + else + serverguard.command:Remove(k); + end; + end; + + if (CLIENT) then + local categories = stored[uniqueID].categories; + + for k, v in pairs(categories) do + if (bToggled) then + serverguard.menu.AddCategory(v); + serverguard.menu:Rebuild(); + else + serverguard.menu:RemoveCategory(k); + end; + end; + end; + + hook.Call("serverguard.PluginToggled", nil, uniqueID, bToggled); +end; + +-- +-- Add a hook. +-- + +function object:Hook(name, unique, callback) + self.hooks[name] = self.hooks[name] or {}; + self.hooks[name][unique] = callback; + + hook.Add(name, unique, callback); +end; + +-- +-- Add a command. +-- + +function object:AddCommand(commandTable) + self.commands[commandTable.command] = self.commands[commandTable.command] or commandTable; + serverguard.command:Add(commandTable); +end; + +-- +-- Parse a file. +-- + +function object:IncludeFile(fileName, state) + serverguard.ParseFile(self.path .. fileName, state) +end + +if (CLIENT) then + -- + -- Add a category. + -- + + function object:AddCategory(category) + self.categories[category.name] = self.categories[category.name] or category; + serverguard.menu.AddCategory(category); + end; + + -- + -- Add a subcategory. + -- + + function object:AddSubCategory(parent_name, data) + serverguard.menu.AddSubCategory(parent_name, data); + end; +end; + +--- Load plugin files. This should **not** be called manually as it's done automatically when ServerGuard starts. +function serverguard.plugin.LoadFiles() + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Loading plugins...\n\n"); + end; + + local _, folders = file.Find("plugins/*", "LUA"); + + for k, folder in pairs(folders) do + plugin = serverguard.plugin.FindByID(folder) or serverguard.plugin.New(); + plugin.unique = folder; + plugin.path = "plugins/" .. folder.. "/"; + + if (SERVER) then + if (file.Exists("plugins/" .. folder.. "/init.lua", "LUA")) then + include("plugins/" .. folder.. "/init.lua"); + end; + + if (file.Exists("plugins/" .. folder.. "/cl_init.lua", "LUA")) then + AddCSLuaFile("plugins/" .. folder.. "/cl_init.lua"); + end; + elseif (CLIENT) then + if (file.Exists("plugins/" .. folder.. "/cl_init.lua", "LUA")) then + include("plugins/" .. folder.. "/cl_init.lua"); + end; + end; + + if (plugin.name) then + serverguard.plugin.Register(plugin); + + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Registered plugin '" .. plugin.name .. "'\n\n"); + end; + else + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Failed to load plugin '" .. folder .. "'!\n\n"); + end; + end; + + plugin = nil; + end; + + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Loaded plugins.\n"); + end; +end; + +serverguard.plugin.LoadFiles(); + +if (SERVER) then + function serverguard.plugin:SaveData(bForced, uniqueIfForced) + local pluginData = {}; + + for unique, v in pairs(stored) do + if (bForced and unique == uniqueIfForced) then + pluginData[unique] = {toggled = v.toggled, forced = bForced}; + else + pluginData[unique] = {toggled = v.toggled, forced = v.forced}; + end; + end; + + serverguard.config.Save("plugins", pluginData); + end; + + -- + -- Send the status of all plugins. + -- + + hook.Add("serverguard.LoadPlayerData", "plugin.LoadPlayerData", function(player) + local plugins = serverguard.plugin.GetList() + + for uniqueID, data in pairs(plugins) do + serverguard.netstream.Start(player, "sgGetPluginStatus", { + uniqueID, data.toggled + }); + end + end) + + -- + -- Load the state of all plugins. + -- + + serverguard.config.New("plugins", function(data) + for unique, v in pairs(data) do + if (stored[unique]) then + local toggle = v.toggled; + local forced = v.forced; + + stored[unique].toggled = toggle; + stored[unique].forced = forced; + serverguard.plugin:Toggle(unique, toggle); + end; + end; + end); + + hook.Add("serverguard.PostLoadConfig", "serverguard.plugin.PostLoadConfig", function() + for unique, v in pairs(stored) do + if (stored[unique]) then + local bToggled = v.toggled; + + if (stored[unique].gamemodes and #stored[unique].gamemodes > 0 and !v.forced) then + if (!table.HasValue(stored[unique].gamemodes, engine.ActiveGamemode())) then + bToggled = false; + + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Force-disabled plugin '"..unique.."' - gamemode not compatible!\n"); + end; + end; + end; + + serverguard.plugin.Get(unique).toggled = bToggled; + serverguard.plugin:Toggle(unique, bToggled); + end; + end; + end); +elseif (CLIENT) then + serverguard.netstream.Hook("sgGetPluginStatus", function(data) + local uniqueID = data[1]; + local state = tobool(data[2]); + local pluginTable = serverguard.plugin.Get(uniqueID); + + if (pluginTable) then + pluginTable.toggled = state; + + serverguard.plugin:Toggle(uniqueID, pluginTable.toggled); + end; + end); + + -- + -- Create the menu category. + -- + + local category = {} + + category.name = "Plugins"; + category.material = "serverguard/menuicons/icon_toggle.png"; + category.permissions = "Manage Plugins"; + + function category:Create(base) + base.quality = 50 + + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Enabled or disabled plugins") + base.panel:Dock(FILL) + + base.panel.list = base.panel:Add("tiger.list") + base.panel.list:Dock(FILL) + base.panel.list:AddColumn("PLUGIN", 400) + + function base.panel:PerformLayout() + category.list = base.panel.list + end + + hook.Call("serverguard.panel.PluginList", nil, base.panel.list); + + local column = base.panel.list:AddColumn("ENABLED") + column:SetDisabled(true) + + local plugins = serverguard.plugin.GetList() + + for unique, data in pairs(plugins) do + local panel = base.panel.list:AddItem(data.name) + panel:SetToolTipSG("Version: " .. data.version .. "\n\n" .. data.description) + + local toggleButton = vgui.Create("DImageButton") + toggleButton:SetSize(16, 16) + toggleButton:SetImage(octolib.icons.silk16('accept_button')) + toggleButton.unique = unique + toggleButton.whitelist = true + + if (data.gamemodes and #data.gamemodes > 0) then + if (!table.HasValue(data.gamemodes, engine.ActiveGamemode())) then + toggleButton.whitelist = false + end + end + + if (!toggleButton.whitelist) then + toggleButton:SetToolTipSG("This plugin is not compatible with the current running gamemode!"); + end; + + function toggleButton:DoClick() + if (serverguard.player:HasPermission(LocalPlayer(), "Manage Plugins")) then + if (!self.whitelist and !serverguard.plugin.Get(self.unique).toggled) then + local q = util.CreateDialog("Incompatible gamemode", + "This gamemode is incompatible with this plugin. Load it anyways?", + function() serverguard.command.Run("plugintoggle", false, self.unique); end, + "Yes", + function() end, + "No"); + return; + end; + + serverguard.command.Run("plugintoggle", false, self.unique); + end; + end; + + function toggleButton:Think() + local pluginTable = serverguard.plugin.Get(self.unique) + + if (pluginTable.toggled) then + if (self:GetImage() == octolib.icons.silk16('cancel')) then + self:SetImage(octolib.icons.silk16('accept_button')) + end + else + if (self:GetImage() == octolib.icons.silk16('accept_button')) then + self:SetImage(octolib.icons.silk16('cancel')) + end + end + end + + function toggleButton:PerformLayout() + DImageButton.PerformLayout(self) + + local w, h = self:GetSize() + local column = panel:GetThing(2).column + local x = column:GetPos() + + self:SetPos(x +column:GetWide() /2 -w /2, column:GetTall() /2 -h /2) + end + + panel:AddItem(toggleButton) + end + end + + function category:Update(base) + end + + serverguard.menu.AddSubCategory("Server settings", category) + + hook.Add("serverguard.menu.Close", "serverguard.PluginList", function() + if (IsValid(category.list)) then + for k, v in ipairs(category.list:GetCanvas():GetChildren()) do + if (IsValid(v.tooltip_sg)) then + v:OnCursorExited(); + end; + end; + end; + end); +end diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_ranks.lua b/garrysmod/addons/admin-sg/lua/modules/sh_ranks.lua new file mode 100644 index 0000000..173a646 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_ranks.lua @@ -0,0 +1,755 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- Handles rank loading, saving, assignment, management, etc. +-- @module serverguard.ranks + +include "modules/sh_cami.lua" + +serverguard.ranks = serverguard.ranks or {}; +serverguard.ranks.stored = serverguard.ranks.stored or {}; +serverguard.ranks.default = serverguard.ranks.default or {}; + +serverguard.ranks.default["founder"] = true; +serverguard.ranks.default["superadmin"] = true; +serverguard.ranks.default["admin"] = true; +serverguard.ranks.default["user"] = true; + +if (SERVER) then + serverguard.AddFolder("ranks"); + + --- **(SERVERSIDE)** Saves a player's rank. + -- @player player The player to save the rank of. + -- @string rank The unique ID of the rank. + function serverguard.ranks:SavePlayerRank(player, rank) + if (!player:IsBot()) then + local steamID = player:SteamID(); + + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:Where("steam_id", steamID); + queryObj:Limit(1); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + serverguard.player:SaveField(player, "Rank", rank); + else + serverguard.player:Save(player, true); + end; + end); + queryObj:Execute(); + + hook.Call("serverguard.PostSavePlayerRank", nil, player, rank); + end; + end; + + hook.Add("CAMI.PlayerHasAccess", "serverguard.ranks.CAMI.PlayerHasAccess", function(actor, privilege, callback, target, extra) + if (serverguard.permission:GetAll()[privilege]) then -- it's our permission + if (serverguard.player:HasPermission(actor, privilege) and (not IsValid(target) or serverguard.player:CanTarget(actor, target))) then + callback(true, "Allowed") + return true + end + + end + + end) + + function serverguard.ranks:CAMILoadRank(unique) + local rank = serverguard.ranks.stored[unique] + + if (unique != "user" and unique != "admin" and unique != "superadmin") then + if (self:HasPermission(unique, "Superadmin")) then + CAMI.RegisterUsergroup({Name = unique, Inherits = "superadmin"}, "SERVERGUARD"); + elseif (self:HasPermission(unique, "Admin")) then + CAMI.RegisterUsergroup({Name = unique, Inherits = "admin"}, "SERVERGUARD"); + else + CAMI.RegisterUsergroup({Name = unique, Inherits = "user"}, "SERVERGUARD"); + end; + end; + end + + + + + + --- **(SERVERSIDE)** Loads all of the ranks into memory. + -- You shouldn't call this unless you know what you're doing. + function serverguard.ranks:Load() + local queryObj = serverguard.mysql:Select("serverguard_ranks"); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + for k, v in pairs(result) do + serverguard.ranks.stored[string.lower(v.unique_id)] = { + unique = string.lower(v.unique_id), + name = v.name, + color = util.JSONToTable(v.color), + immunity = tonumber(v.immunity), + texture = v.texture, + data = util.JSONToTable(v.data), + targetable = tonumber(v.targetable), + banlimit = tonumber(v.banlimit), + }; + end; + + hook.Call("serverguard.RanksLoaded", nil); + else + for k, v in pairs(serverguard.ranks.stored) do + serverguard.ranks:Save(v.unique, v.name, v.color, v.immunity, v.texture, v.data, true, v.targetable, v.banlimit); + end; + end; + end); + queryObj:Execute(); + end; + + hook.Add("serverguard.mysql.CreateTables", "serverguard.ranks.CreateTables", function() + local RANKS_TABLE_QUERY = serverguard.mysql:Create("serverguard_ranks"); + RANKS_TABLE_QUERY:Create("id", "INTEGER NOT NULL AUTO_INCREMENT"); + RANKS_TABLE_QUERY:Create("unique_id", "VARCHAR(255) NOT NULL"); + RANKS_TABLE_QUERY:Create("name", "VARCHAR(255) NOT NULL"); + RANKS_TABLE_QUERY:Create("color", "VARCHAR(63) NOT NULL"); + RANKS_TABLE_QUERY:Create("immunity", "INT(11) NOT NULL"); + -- RANKS_TABLE_QUERY:Create("targetable", "INT(11) NOT NULL"); + -- let upgrade create targetable + RANKS_TABLE_QUERY:Create("texture", "VARCHAR(255) NOT NULL"); + RANKS_TABLE_QUERY:Create("data", "TEXT"); + RANKS_TABLE_QUERY:PrimaryKey("id"); + RANKS_TABLE_QUERY:Execute(); + end); + + hook.Add("serverguard.mysql.UpgradeSchemas", "serverguard.ranks.UpgradeSchemas", function(callback) + serverguard.mysql:UpgradeSchema("serverguard_ranks", { + { -- update 1 + "ALTER TABLE `serverguard_ranks` ADD `targetable` INT(11) NOT NULL DEFAULT '0';", + "UPDATE `serverguard_ranks` SET `targetable` = `immunity` WHERE `id` > '-1';" -- because of safe mode in mysql + }, + { -- update 2 + "ALTER TABLE `serverguard_ranks` ADD `banlimit` INT(11) NOT NULL DEFAULT '0';", + } + }, callback); + end); + + hook.Add("serverguard.mysql.OnConnected", "serverguard.ranks.OnConnected", function() + serverguard.ranks:Load(); + end); + + --- **(SERVERSIDE)** Saves a rank. + -- @string uniqueID The unique ID of the rank. + -- @string name The pretty name of the rank. + -- @color color The color of the rank. + -- @number immunity The immunity level of the rank. + -- @string texture The texture path of the rank. + -- @table data Any extra data to save with the rank. + -- @bool[opt] bNew Whether to save as a new rank, or to update an existing one with the same name. Defaults to false. + -- @number[opt] targetable The targetable rank of a player + -- @number[opt] banlimit The banlimit the player has. + function serverguard.ranks:Save(uniqueID, name, color, immunity, texture, data, bNew, targetable, banlimit) + if (bNew) then + local insertObj = serverguard.mysql:Insert("serverguard_ranks"); + insertObj:Insert("unique_id", string.lower(uniqueID)); + insertObj:Insert("name", tostring(name)); + insertObj:Insert("color", util.TableToJSON(color)); + insertObj:Insert("immunity", tonumber(immunity)); + insertObj:Insert("targetable", tonumber(targetable) or 0) + insertObj:Insert("banlimit", tonumber(banlimit) or 0) + insertObj:Insert("texture", tostring(texture)); + insertObj:Insert("data", util.TableToJSON(data)); + insertObj:Execute(); + else + local updateObj = serverguard.mysql:Update("serverguard_ranks"); + updateObj:Update("name", tostring(name)); + updateObj:Update("color", util.TableToJSON(color)); + updateObj:Update("immunity", tonumber(immunity)); + updateObj:Update("targetable", tonumber(targetable) or 0) + updateObj:Update("banlimit", tonumber(banlimit) or 0) + updateObj:Update("texture", tostring(texture)); + updateObj:Update("data", util.TableToJSON(data)); + updateObj:Where("unique_id", string.lower(uniqueID)); + updateObj:Execute(); + end; + end + + --- **(SERVERSIDE)** Saves a rank in ranktable form. + -- @string uniqueID The unique ID of the rank. + -- @table rankTable The table of data to save. + -- @bool[opt] bNew Whether to save as a new rank, or to update an existing one with the same name. Defaults to false. + function serverguard.ranks:SaveTable(uniqueID, rankTable, bNew) + if (type(uniqueID) == "string" and type(rankTable) == "nil") then + rankTable = self:FindByID(uniqueID); + end; + + self:Save(uniqueID, rankTable.name, rankTable.color, rankTable.immunity, rankTable.texture, rankTable.data, bNew, rankTable.targetable, rankTable.banlimit); + end; + + -- **(SERVERSIDE)** Saves a rank's field. + -- (DEPRECATED) + function serverguard.ranks:SaveField(uniqueID, field, value) + if (type(value) == "table") then + value = util.TableToJSON(value); + end; + + local queryObj = serverguard.mysql:Update("serverguard_ranks"); + queryObj:Update(field, value); + queryObj:Where("unique_id", string.lower(uniqueID)); + queryObj:Execute(); + end; + + --- **(SERVERSIDE)** Sends all ranks' data to a player. + -- @player player The player to send the data to. + function serverguard.ranks:SendInitialRanks(player) + netstream.Heavy(player, "sgNetworkInitialRanks", self.stored); + end + + --- **(SERVERSIDE)** Sends a rank's data to all connected players. + -- @string uniqueID The unique ID of the rank. + -- @bool[opt] bRemove Whether or not to remove the rank. Defaults to false. + function serverguard.ranks:NetworkRank(uniqueID, bRemove) + if (bRemove) then + serverguard.netstream.Start(nil, "sgNetworkRankRemove", uniqueID); + else + local rankTable = serverguard.ranks:FindByID(uniqueID); + + if (rankTable) then + serverguard.netstream.Start(nil, "sgNetworkRankUpdate", rankTable); + end; + end + end + + --- **(SERVERSIDE)** Adds a new rank. + -- @string unique The unique ID of the rank. + -- @string name The pretty name of the rank. + -- @color color The color of the rank. + -- @number immunity The immunity level of the rank. + -- @string texture The texture path of the rank. + -- @table data Any extra data to save with the rank. + -- @targetable[opt] The targetable rank of the rank. + -- @treturn string The unique ID of the rank. + function serverguard.ranks:AddRank(unique, name, immunity, color, texture, data, targetable, banlimit) + self.stored[string.lower(unique)] = { + unique = string.lower(unique), + name = name, + color = color, + immunity = tonumber(immunity) or 0, + texture = texture, + data = data or {}, + targetable = tonumber(targetable) or tonumber(immunity) or 0, + banlimit = tonumber(banlimit) or 0, + }; + + serverguard.ranks:CAMILoadRank(unique) + + hook.Call("serverguard.RankCreated", nil, unique); -- Used to set data. + + return unique; + end; + + --- **(SERVERSIDE)** Removes a rank from memory. + -- @string uniqueID The unique ID of the rank to remove. + -- @bool[opt] bSaveless Whether or not to skip saving. Defaults to false. + -- @treturn bool Whether or not the removal was successful. + -- @treturn string The error message if the removal failed. + function serverguard.ranks:RemoveRank(uniqueID, bSaveless) + if (serverguard.ranks.stored[uniqueID]) then + if (serverguard.ranks:IsDefault(uniqueID)) then + return false, "Default ranks cannot be removed!"; + end; + + serverguard.ranks.stored[uniqueID] = nil + + CAMI.UnregisterUsergroup(uniqueID, "SERVERGUARD"); + + if (!bSaveless) then + local queryObj = serverguard.mysql:Delete("serverguard_ranks"); + queryObj:Where("unique_id", uniqueID); + queryObj:Execute(); + end; + + return true; + end; + end; + + -- **(SERVERSIDE)** Returns a rank variable. + -- (DEPRECATED) + function serverguard.ranks:GetVariable(lookup, variable) + local rankData; + + if (type(lookup) == "Player" and IsValid(lookup) and lookup:IsPlayer()) then + rankData = serverguard.ranks:GetRank( + serverguard.player:GetRank(lookup) + ); + elseif (type(lookup) == "string") then + local rank = serverguard.ranks:GetRank(lookup); + + if (rank) then + rankData = rank; + else + return; + end; + elseif (type(lookup) == "table") then + rankData = lookup; + else + return; + end; + + return rankData[variable]; + end; + + -- Creates a new rank. + serverguard.netstream.Hook("sgNewRank", function(player, data) + if (serverguard.player:HasPermission(player, "Edit Ranks")) then + local uniqueID = data[1] or "unknown"; + local name = data[2] or "Unknown"; + local immunity = data[3] or 0; + local color = data[4]; + local texture = data[5]; + local targetable_rank = data[8]; + local banlimit = data[9]; + + serverguard.ranks:AddRank(uniqueID, name, immunity, color, texture, nil, targetable_rank, banlimit); + serverguard.ranks:SetData(uniqueID, "Permissions", data[6]); + + for k, v in pairs(data[7]) do + serverguard.ranks:SetData(uniqueID, k, v); + end + + serverguard.ranks:NetworkRank(uniqueID); + serverguard.ranks:SaveTable(uniqueID, nil, true); + + serverguard.Notify(player, SERVERGUARD.NOTIFY.WHITE, "The ", SERVERGUARD.NOTIFY.GREEN, name, SERVERGUARD.NOTIFY.WHITE, " rank has been created!"); + end; + end); + + -- Removes a rank. + serverguard.netstream.Hook("sgRemoveRank", function(player, data) + if (serverguard.player:HasPermission(player, "Edit Ranks")) then + local uniqueID = data; + local bStatus, fault = serverguard.ranks:RemoveRank(uniqueID); + + if (bStatus == false and fault) then + serverguard.netstream.Start(player, "sgPopupError", fault); + return; + end; + + serverguard.Notify(player, SERVERGUARD.NOTIFY.WHITE, "The rank ", SERVERGUARD.NOTIFY.GREEN, uniqueID, SERVERGUARD.NOTIFY.WHITE, " was removed!"); + serverguard.ranks:NetworkRank(uniqueID, true); + end; + end); + + -- Changes specific data in a rank. + serverguard.netstream.Hook("sgChangeRankInfo", function(pPlayer, data) + if (serverguard.player:HasPermission(pPlayer, "Edit Ranks")) then + local uniqueID = data[1]; + local field = data[2]; + local valueType = data[3]; + local value = data[4]; + + if (valueType == SERVERGUARD.NETWORK.STRING) then + value = tostring(value); + elseif (valueType == SERVERGUARD.NETWORK.NUMBER) then + value = tonumber(value); + elseif (valueType == SERVERGUARD.NETWORK.BOOLEAN) then + value = tobool(value) + elseif (valueType == SERVERGUARD.NETWORK.COLOR) then + value = Color(value.r, value.g, value.b, 255); + elseif (valueType == SERVERGUARD.NETWORK.CUSTOM) then + value = serverguard.von.deserialize(value); + end; + + serverguard.ranks.stored[uniqueID][field] = value; + serverguard.ranks:SaveField(uniqueID, field, value); + + serverguard.netstream.Start(nil, "sgNetworkRankInfo", { + unique = uniqueID, + field = field, + type = valueType, + value = value + }); + if (field == "immunity") then + for k,v in ipairs(player.GetAll()) do + if (v:GetNetVar "serverguard_rank" == uniqueID) then + serverguard.player:SetImmunity(v, value) + end + end + elseif (field == "targetable") then + for k,v in ipairs(player.GetAll()) do + if (v:GetNetVar "serverguard_rank" == uniqueID) then + serverguard.player:SetTargetableRank(v, value) + end + end + elseif (field == "banlimit") then + for k,v in ipairs(player.GetAll()) do + if (v:GetNetVar "serverguard_rank" == uniqueID) then + serverguard.player:SetBanLimit(v, value) + end + end + end + end + end); + + -- Changes a rank's data field. + serverguard.netstream.Hook("sgChangeRankData", function(player, data) + if (serverguard.player:HasPermission(player, "Edit Ranks")) then + local uniqueID = data[1]; + local field = data[2]; + local value = data[3]; + + if (field == "Permissions") then + if (uniqueID != "founder") then + serverguard.ranks:SetData(uniqueID, field, value); + serverguard.netstream.Start(nil, "sgNetworkRankData", { + uniqueID, field, value + }); + end; + else + serverguard.ranks:SetData(uniqueID, field, value); + serverguard.netstream.Start(nil, "sgNetworkRankData", { + uniqueID, field, value + }); + end; + + serverguard.ranks:SaveTable(uniqueID); + end; + end); + + -- Set a player to founder rank. + concommand.Add("serverguard_setowner", function(player, command, arguments) + if (util.IsConsole(player) or !game.IsDedicated()) then + local target = util.FindPlayer(arguments[1]) + + if (IsValid(target)) then + local rankData = serverguard.ranks:GetRank("founder") + + if (rankData) then + serverguard.player:SetRank(target, rankData.unique, 0) + serverguard.player:SetImmunity(target, rankData.immunity) + serverguard.player:SetTargetableRank(target, rankData.targetable) + serverguard.player:SetBanLimit(target, rankData.banlimit) + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, serverguard.player:GetName(target), SERVERGUARD.NOTIFY.WHITE, string.Ownership(serverguard.player:GetName(target), true) .. " rank to ", SERVERGUARD.NOTIFY.GREEN, "Founder", SERVERGUARD.NOTIFY.WHITE, ".") + else + serverguard.PrintConsole("Something went really wrong! Unable to find the founder rank.\n"); + end; + end; + elseif (IsValid(player)) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.DEFAULT, "You must run this command via rcon or the servers console!"); + end; + end) + + -- Set a players rank. + concommand.Add("serverguard_setrank", function(player, command, arguments) + if (util.IsConsole(player) or !game.IsDedicated()) then + local target = util.FindPlayer(arguments[1], NULL, true); + local rankData = serverguard.ranks:GetRank(arguments[2]); + + if (!rankData) then + serverguard.PrintConsole("Invalid rank! Here are some valid ones:\n"); + + for unique, data in pairs(serverguard.ranks:GetRanks()) do + serverguard.PrintConsole(data.unique .. "\n"); + end; + + return; + end + + if (IsValid(target)) then + serverguard.player:SetRank(target, rankData.unique, 0); + serverguard.player:SetImmunity(target, rankData.immunity); + serverguard.player:SetTargetableRank(target, rankData.targetable) + serverguard.player:SetBanLimit(target, rankData.banlimit) + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, serverguard.player:GetName(target), SERVERGUARD.NOTIFY.WHITE, string.Ownership(serverguard.player:GetName(target), true) .. " rank to ", SERVERGUARD.NOTIFY.GREEN, rankData.name, SERVERGUARD.NOTIFY.WHITE, ".") + elseif (string.SteamID(arguments[1])) then + local steamID = arguments[1]; + + serverguard.player:SetRank(steamID, rankData.unique, 0); + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, steamID, SERVERGUARD.NOTIFY.WHITE, string.Ownership(serverguard.player:GetName(target), true) .. " rank to ", SERVERGUARD.NOTIFY.GREEN, rankData.name, SERVERGUARD.NOTIFY.WHITE, ".") + else + serverguard.PrintConsole("Could not find a player with that identifier!"); + end; + end; + end); + + -- hook.Add("PlayerInitialSpawn", "serverguard.ranks.PlayerInitialSpawn", function(player) + -- local queryObj = serverguard.mysql:Select("serverguard_users"); + -- queryObj:Where("rank", "founder"); + -- queryObj:Callback(function(result, status, lastID) + -- if (type(result) != "table" or #result == 0) then + -- serverguard.Notify(player, SERVERGUARD.NOTIFY.DEFAULT, "Use the command serverguard_setowner via rcon to set yourself as founder."); + -- end; + -- end); + -- queryObj:Execute(); + -- end); + + hook.Add("PlayerLoadout", "serverguard.ranks.PlayerLoadout", function(player) + local uniqueID = serverguard.player:GetRank(player); + local color = serverguard.ranks:GetData(uniqueID, "phys_color", Color(77, 255, 255)); + player:SetWeaponColor(Vector(color.r / 255, color.g / 255, color.b / 255)); + end); + + -- Sends the rank data to a player. + serverguard.netstream.Hook("sgRequestPlayerRanks", function(player, data) + if not serverguard.player:HasPermission(player, "Set Rank") then return end + + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:WhereNotEqual("rank", "user"); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + for k, v in pairs(result) do + local expires_in; + if v.data and v.data != nil then + local temp = serverguard.von.deserialize(v.data); + if temp.groupExpire then + if temp.groupExpire == 0 then + expires_in = "never"; + else + expires_in = temp.groupExpire - os.time(); + end; + else + expires_in = "never"; + end; + end; + + serverguard.netstream.Start(player, "sgGetRankList", { + v.steam_id, v.rank, v.name, expires_in, os.date("*t", os.difftime(os.time(), v.last_played)) or "Unknown" + }); + end; + end; + end); + queryObj:Execute(); + end); + + -- Changes a player's rank. + serverguard.netstream.Hook("sgChangePlayerRank", function(player, data) + if not serverguard.player:HasPermission(player, "Set Rank") then return end + + local steamID = data[1]; + local rank = data[2]; + local length = tonumber(data[3]) or 0; + + if IsValid(player) and player:IsPlayer() then + local curImm = serverguard.ranks:FindByID(player:GetUserGroup()).immunity + local tgtImm = serverguard.ranks:GetRank(rank).immunity + if tgtImm >= curImm then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "No access.") + return + end + end + + if (rank != "") then + local data = player.sg_data or {}; + if (length != nil and isnumber(length) and length > 0) then + data.groupExpire = os.time() + length * 60; + else + data.groupExpire = false; + end; + local queryObj = serverguard.mysql:Update("serverguard_users"); + queryObj:Update("rank", rank); + queryObj:Update("data", serverguard.von.serialize(data)) + queryObj:Where("steam_id", steamID); + queryObj:Execute(); + + local target = util.FindPlayer(steamID, player, true); + local rankData = serverguard.ranks:GetRank(rank); + + if (rankData) then + if (IsValid(target)) then + serverguard.player:SetRank(target, rank, length * 60); + serverguard.player:SetImmunity(target, rankData.immunity); + serverguard.player:SetTargetableRank(target, rankData.targetable) + serverguard.player:SetBanLimit(target, rankData.banlimit) + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, player:Name(), SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, string.Ownership(target:Name(), true) .. " rank to ", SERVERGUARD.NOTIFY.GREEN, rankData.name, SERVERGUARD.NOTIFY.WHITE, ". Length: ", SERVERGUARD.NOTIFY.RED, ((length != nil and length > 0) and length .. " minute(s)" or "Indefinitely"), SERVERGUARD.NOTIFY.WHITE, "."); + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, player:Name(), SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, steamID, SERVERGUARD.NOTIFY.WHITE, "'s rank to ", SERVERGUARD.NOTIFY.GREEN, rankData.name, SERVERGUARD.NOTIFY.WHITE, ". Length: ", SERVERGUARD.NOTIFY.RED, ((length != nil and length > 0) and length .. " minute(s)." or "Indefinitely"), SERVERGUARD.NOTIFY.WHITE, "."); + end; + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "No rank with the '"..rank.."' unique idenfier exists."); + end; + else + local queryObj = serverguard.mysql:Update("serverguard_users"); + queryObj:Update("rank", rank); + queryObj:Where("steam_id", steamID); + queryObj:Execute(); + + local target = util.FindPlayer(steamID, player); + local rankData = serverguard.ranks:GetRank("user"); + + if (rankData) then + if (IsValid(target)) then + serverguard.player:SetRank(target, rankData.unique, 0); + serverguard.player:SetImmunity(target, rankData.immunity); + serverguard.player:SetTargetableRank(target, rankData.targetable) + serverguard.player:SetBanLimit(target, rankData.banlimit) + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, player:Name(), SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, string.Ownership(target:Name(), true) .. " rank to ", SERVERGUARD.NOTIFY.GREEN, rankData.name, SERVERGUARD.NOTIFY.WHITE, ".") + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, player:Name(), SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, steamID, SERVERGUARD.NOTIFY.WHITE, string.Ownership(steamID, true) .. " rank to ", SERVERGUARD.NOTIFY.GREEN, rankData.name, SERVERGUARD.NOTIFY.WHITE, ".") + end; + end; + end; + end); +else + netstream.Hook("sgNetworkInitialRanks", function(data) + for uniqueID, rank in pairs(data) do + serverguard.ranks.stored[uniqueID] = rank; + end; + end); + + serverguard.netstream.Hook("sgNetworkRankUpdate", function(data) + serverguard.ranks.stored[data.unique] = data; + end); + + serverguard.netstream.Hook("sgNetworkRankRemove", function(data) + serverguard.ranks.stored[data] = nil; + end); + + serverguard.netstream.Hook("sgNetworkRankInfo", function(data) + local uniqueID = data.unique; + local field = data.field; + local dataType = data.type; + local value = data.value; + + if (type == SERVERGUARD.NETWORK.STRING) then + value = tostring(data.value); + elseif (type == SERVERGUARD.NETWORK.NUMBER) then + value = tonumber(data.value); + elseif (type == SERVERGUARD.NETWORK.BOOLEAN) then + value = tobool(data.value); + elseif (type == SERVERGUARD.NETWORK.DECODE) then -- The only thing using this are the flags. + value = tostring(data.value); + + serverguard.flags:ApplyFlags(serverguard.ranks.stored[unique], string.Explode(",", value)); + elseif (type == SERVERGUARD.NETWORK.COLOR) then -- Only color using this. + value = Color(value.r, value.g, value.b, 255); + elseif (type == SERVERGUARD.NETWORK.CUSTOM) then -- Restrictions. + value = serverguard.von.deserialize(tostring(data.value)); + end; + + if (field == "immunity") then + for k,v in ipairs(player.GetAll()) do + if (v:GetNetVar "serverguard_rank" == uniqueID) then + serverguard.player:SetImmunity(v, value) + end + end + elseif (field == "targetable") then + for k,v in ipairs(player.GetAll()) do + if (v:GetNetVar "serverguard_rank" == uniqueID) then + serverguard.player:SetTargetableRank(v, value) + end + end + elseif (field == "banlimit") then + for k,v in ipairs(player.GetAll()) do + if (v:GetNetVar "serverguard_rank" == uniqueID) then + serverguard.player:SetBanLimit(v, value) + end + end + end + + serverguard.ranks.stored[uniqueID][field] = value; + + hook.Call("serverguard.ranks.RankUpdate", nil) + + end); + + serverguard.netstream.Hook("sgNetworkRankData", function(data) + local uniqueID = data[1]; + local field = data[2]; + local value = data[3]; + + serverguard.ranks:SetData(uniqueID, field, value); + end); +end; + +--- Returns all the ranks. +-- @treturn table The list of ranks +function serverguard.ranks:GetStored() + return self.stored; +end; + +serverguard.ranks.GetRanks = serverguard.ranks.GetStored; +serverguard.ranks.GetTable = serverguard.ranks.GetStored; + +--- Finds a rank by the given unique ID. +-- @string uniqueID The unique ID of the rank. +-- @treturn table The rank table. +function serverguard.ranks:FindByID(uniqueID) + if (self.stored[string.lower(uniqueID)]) then + return self.stored[string.lower(uniqueID)]; + end; +end; + +serverguard.ranks.GetRank = serverguard.ranks.FindByID; + +--- Whether or not the rank's unique ID is a default one. (i.e user, founder, etc) +-- @string identifier The rank's unique ID. +-- @treturn bool Whether or not the rank is a default rank. +function serverguard.ranks:IsDefault(identifier) + if (type(identifier) == "string") then + return self.default[string.lower(identifier)]; + end; +end; + +--- Sets a data field for a rank. You shouldn't call this unless you know what you're doing. +-- @string rank The rank's unique ID. +-- @string key The key to set. +-- @string value The value to set. +function serverguard.ranks:SetData(rank, key, value) + local rankTable = serverguard.ranks:FindByID(rank); + + if (rankTable) then + rankTable.data[key] = value; + end; +end; + +--- Gets a data field for a rank. You shouldn't call this unless you know what you're doing. +-- @string rank The rank's unique ID. +-- @string key The key to get. +-- @string default The default value if the key/value doesn't exist. +-- @treturn string The stored data. +function serverguard.ranks:GetData(rank, key, default) + local rankTable = serverguard.ranks:FindByID(rank); + + if (rankTable) then + if (!rankTable.data[key] and default != nil) then + self:SetData(rank, key, default); + end; + + return rankTable.data[key]; + end; +end; + +--- Gets whether or not the rank has the given permission. +-- @string rank The unique ID of the rank. +-- @string identifier The name of the permission. +function serverguard.ranks:HasPermission(rank, identifier) + if (rank == "founder") then + return true; + end; + + local permissionTable = self:GetData(rank, "Permissions", {}); + + return permissionTable[identifier]; +end; + +--- Gives a permission to the specified rank. +-- @string rank The unique ID of the rank. +-- @string identifier The permission to give the rank. +function serverguard.ranks:GivePermission(rank, identifier) + local permissionTable = self:GetData(rank, "Permissions", {}); + + if (serverguard.permission:Exists(identifier)) then + permissionTable[identifier] = true; + end; +end; + +--- Revokes a permission from the specified rank. +-- @string rank The unique ID of the rank. +-- @string identifier The permission to revoke. +function serverguard.ranks:TakePermission(rank, identifier) + local permissionTable = self:GetData(rank, "Permissions", {}); + + if (serverguard.permission:Exists(identifier)) then + permissionTable[identifier] = nil; + end; +end; diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_utilities.lua b/garrysmod/addons/admin-sg/lua/modules/sh_utilities.lua new file mode 100644 index 0000000..1c2b634 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_utilities.lua @@ -0,0 +1,1269 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +--- Extension of util library to provide convenience functions. +-- @module util + +color_white = Color(255, 255, 255, 255) +color_black = Color(0, 0, 0, 255) +color_red = Color(255, 0, 0, 255) +color_green = Color(0, 255, 0, 255) +color_green_darker = Color(0, 200, 0, 255) +color_blue = Color(0, 0, 255, 255) +color_yellow = Color(255, 255, 0, 255) +color_purple_light = Color(106, 90, 205, 255) +color_orange = Color(255, 165, 0, 255) +color_brown = Color(150, 75, 0, 255) + +local temptbl = {} + +local function MergeSortByName( tbl, iLow, iHigh, bReverse ) + if ( iLow < iHigh ) then + local iMiddle = math.floor( iLow + (iHigh - iLow) / 2 ) + MergeSortByName( tbl, iLow, iMiddle, bReverse ) + MergeSortByName( tbl, iMiddle + 1, iHigh, bReverse ) + + for i = iLow, iHigh do + temptbl[i] = tbl[i] + end + + local i = iLow + local j = iMiddle + 1 + local k = iLow + + while ( i <= iMiddle and j <= iHigh ) do + if ( temptbl[i]:Nick() <= temptbl[j]:Nick() ) then + if ( bReverse ) then + tbl[k] = temptbl[j] + j = j + 1 + else + tbl[k] = temptbl[i] + i = i + 1 + end + else + if ( bReverse ) then + tbl[k] = temptbl[i] + i = i + 1 + else + tbl[k] = temptbl[j] + j = j + 1 + end + end + + k = k + 1 + end + + while ( i <= iMiddle ) do + tbl[k] = temptbl[i] + k = k + 1 + i = i + 1 + end + end +end + +--- Gets a sorted list of players connected to the server. +-- @treturn table Player list. +function player.GetSortedPlayers(bReverse --[[= false]]) + local tbl = player.GetAll() + + MergeSortByName(tbl, 1, #tbl, bReverse) + + return tbl; +end; + +-- Taken from the gmod table library. Necessary to restore the original +-- functionality broken due to the following commit: +-- https://github.com/garrynewman/garrysmod/commit/5e9d4fa05ea9cc69c56e5c0857d5247dcabdc192 +local function fnPairsSorted(tab, index) + if (index == nil) then + index = 1; + else + for k, v in pairs(tab.__SortedIndex) do + if (v == index) then + index = k + 1; + break; + end; + end; + end; + + local key = tab.__SortedIndex[index]; + + if (!key) then + tab.__SortedIndex = nil; + return; + end; + + index = index + 1; + + return key, tab[key]; +end + +-- See fnPairsSorted +function util.SortedPairsByMemberValue(tab, valueName, descending) + descending = descending or false; + + local sortedIndex = {}; + local sortedTable = table.ClearKeys(tab, true); + + table.SortByMember(sortedTable, valueName, !descending); + + for k, v in ipairs(sortedTable) do + table.insert(sortedIndex, v.__key); + end; + + tab.__SortedIndex = sortedIndex; + + return fnPairsSorted, tab, nil; +end; + +--- Returns whether or not the player can be compared **EXACTLY** with a given identifier. +-- This is more strict, and will match exact names. +-- @player player The player to extract the information from. +-- @string identifier The string to compare with. +-- @treturn bool Whether the player matches the given identifier. +function util.PlayerMatchesIdentifier(player, identifier) + local result = hook.Call("serverguard.PlayerMatchesIdentifier", nil, player, identifier); + + return (IsValid(player) and identifier and result or string.lower(identifier) == string.lower(player:Name()) or + string.lower(identifier) == string.lower(player:Nick()) or + player:SteamID() == identifier or + player:UniqueID() == identifier or + player:SteamID64() == identifier or + (player:IPAddress():gsub(":%d+", "")) == identifier); +end; + +--- Returns whether or not the player can be compared with a given identifier. +-- This will be more lenient with finding substrings in a player's name, for example. +-- @player player The player to extract the information from. +-- @string identifier The string to compare with. +-- @treturn bool Whether the player contains the given identifier. +function util.PlayerContainsIdentifier(player, identifier) + local result = hook.Call("serverguard.PlayerContainsIdentifier", nil, player, identifier); + + return (IsValid(player) and identifier and result or (string.find(utf8.lower(player:Name()), utf8.lower(identifier), 0, true) or + string.find(utf8.lower(player:Nick()), utf8.lower(identifier), 0, true) or + player:SteamID() == identifier or + player:UniqueID() == identifier or + player:SteamID64() == identifier or + (player:IPAddress():gsub(":%d+", "")) == identifier)); +end; + +--- Returns whether or not the target's immunity level fulfills the required immunity comparison with the player. +-- @number requiredImmunity The required immunity comparison. +-- @player player The player to be compared to. +-- @player target The player to be compared with. +-- @treturn bool Whether the immunity requirement has been fulfilled. +function util.PlayerMatchesImmunity(requiredImmunity, pPlayer, target, bNoTargetSelf) + local result = hook.Call("serverguard.PlayerMatchesImmunity", nil, requiredImmunity, pPlayer, target, bNoTargetSelf); + + return result or + (requiredImmunity == SERVERGUARD.IMMUNITY.EQUAL and serverguard.player:GetImmunity(target) == serverguard.player:GetImmunity(pPlayer)) or + (requiredImmunity == SERVERGUARD.IMMUNITY.LESS and serverguard.player:GetImmunity(target) < serverguard.player:GetImmunity(pPlayer)) or + (requiredImmunity == SERVERGUARD.IMMUNITY.LESSOREQUAL and serverguard.player:GetImmunity(target) <= serverguard.player:GetImmunity(pPlayer)) or + (requiredImmunity == SERVERGUARD.IMMUNITY.ANY) or + (!bNoTargetSelf and pPlayer == target); +end; + +local identifierTags = { + -- Immunity is automatically checked beforehand so we don't have to do it here. + + ["^"] = { + filter = function(callingPlayer, targetPlayer) + return callingPlayer == targetPlayer + end, + negated = function(callingPlayer, targetPlayer) + return callingPlayer != targetPlayer + end + }, + ["\\*"] = { + filter = function(callingPlayer, targetPlayer) + return true + end, + negated = function(callingPlayer, targetPlayer) + return false + end + }, + ["#*"] = { + filter = function(callingPlayer, targetPlayer, argument) + return serverguard.player:GetRank(targetPlayer) == argument + end, + negated = function(callingPlayer, targetPlayer, argument) + return serverguard.player:GetRank(targetPlayer) != argument + end + }, + ["@"] = { + filter = function(callingPlayer, targetPlayer, argument) + return callingPlayer:GetEyeTraceNoCursor().Entity == targetPlayer + end, + negated = function(callingPlayer, targetPlayer, argument) + return callingPlayer:GetEyeTraceNoCursor().Entity != targetPlayer + end + } +}; + +--- Attempts to find all connected players matching the given query, and runs a function with that player as an argument. +-- Valid identifiers include name, Steam ID, 64-bit Steam ID, IP address, a comma-separated list, or a special tag. +-- Tags include ^ for self, * for all, and #name for all players with the given rank. You can use ! to negate the action. +-- @string identifier String to try and match players with. +-- @player callingPlayer Player who initiated the query. +-- @number requiredImmunity The required immunity level between the calling player and any targets for the function to run. +-- @func func Function to run with all applicable targets. +-- @treturn table List of applicable players. +function util.ExecuteOnPlayers(identifier, callingPlayer, requiredImmunity, func) + if (!identifier or !callingPlayer or !requiredImmunity or !func) then + return; + end; + + local tAllPlayers = player.GetAll() + local players = {}; + + for k, v in ipairs(tAllPlayers) do + if (util.PlayerMatchesImmunity(requiredImmunity, callingPlayer, v)) then + if (util.PlayerMatchesIdentifier(v, identifier)) then + -- If we match exactly, we only execute on the one player. + table.insert(players, v); + + break; + elseif (util.PlayerContainsIdentifier(v, identifier)) then + if (#players == 0) then + table.insert(players, v); + else + serverguard.Notify(callingPlayer, SGPF("player_found_multiple", identifier)); + return {}; + end; + end; + end; + end; + + -- Matching special tags. + if (#players == 0) then + local negate = (string.sub(identifier, 1, 1) == "!"); + + for k, v in pairs(identifierTags) do + local tag = k; + local text = identifier; + local argument = ""; + local func = "filter"; + + if (negate) then + text = string.sub(identifier, 2, string.len(identifier)); + func = "negated"; + end; + + local wildcardPosition = string.find(tag, "*", 0, true); + + if (wildcardPosition) then + if (string.sub(tag, wildcardPosition - 1, wildcardPosition - 1) == "\\") then + tag = string.sub(tag, wildcardPosition, wildcardPosition); + else + tag = string.gsub(tag, "*", ""); + argument = string.gsub(text, tag, ""); + tag = tag .. argument; + end; + end; + + for k, pPlayer in pairs(tAllPlayers) do + if (tag == text and util.PlayerMatchesImmunity(requiredImmunity, callingPlayer, pPlayer) and v[func](callingPlayer, pPlayer, argument)) then + table.insert(players, pPlayer); + end; + end; + end; + + -- Matching name-separated list. + if (#players == 0) then + if (string.find(identifier, ",", 0, true)) then + local identifiers = string.Explode(",", identifier); + + for k, v in pairs(identifiers) do + local found = false; + + for k2, v2 in ipairs(tAllPlayers) do + if (util.PlayerMatchesImmunity(requiredImmunity, callingPlayer, v2) and util.PlayerContainsIdentifier(v2, v)) then + if (!found) then + found = true; + table.insert(players, v2); + else + serverguard.Notify(callingPlayer, SGPF("player_found_multiple", v)); + return {}; + end; + end; + end; + end; + end; + + -- Give up if we haven't found anyone. + if (#players == 0) then + serverguard.Notify(callingPlayer, SGPF("player_cant_find_suitable")); + return {}; + end; + end; + end; + + local result = {}; + + for k, v in pairs(players) do + local status = func(v); + + if (status) then + table.insert(result, v); + end; + end; + + -- Oops, everyone was filtered out. + if (#result == 0) then + serverguard.Notify(callingPlayer, SGPF("player_cant_find_suitable")); + return {}; + end; + + return result; +end; + +function util.GetListenServerHost() + for k, v in ipairs(player.GetAll()) do + if (v:IsListenServerHost()) then + return v + end + end + + return NULL +end + +local PLAYER = FindMetaTable("Player") + +function isplayer(v) + return getmetatable(v) == PLAYER +end + +--- Obtains a list of elements suitable for `serverguard.Notify` for the given player list. +-- It is assumed that there is at least one target. +-- @table targets Player targets to output. +-- @bool bOwnership Whether or not to add an ownership suffix (such as "'s"). +-- @number targetColor A SERVERGUARD.NOTIFY color to use. +-- @treturn table Notify components. +-- @see serverguard.Notify +function util.GetNotifyListForTargets(targets, bOwnership, targetColor) + targetColor = targetColor or SERVERGUARD.NOTIFY.RED; + + if (#targets < 1) then + return {}; + end; + + if (#targets == #player.GetAll() and #targets != 1) then + if (bOwnership) then + return {targetColor, "everyone's"}; + end; + + return {targetColor, "everyone"}; + end + + local result = {}; + + table.insert(result, targetColor); + table.insert(result, targets[1]:Name()); + + for i = 2, #targets - 1 do + table.insert(result, SERVERGUARD.NOTIFY.WHITE); + table.insert(result, ", "); + table.insert(result, targetColor); + table.insert(result, targets[i]:Name()); + end; + + if (#targets > 1) then + table.insert(result, SERVERGUARD.NOTIFY.WHITE); + table.insert(result, " and "); + table.insert(result, targetColor); + table.insert(result, targets[#targets]:Name()); + end; + + if (bOwnership) then + table.insert(result, string.Ownership(result[#result], true)); + end; + + hook.Call("serverguard.GetNotifyListForTargets", nil, targets, bOwnership, targetColor, result); + + return result; +end; + +--- Attempts to find a connected player by a given identifier. +-- Valid identifiers include name, Steam ID, 64-bit Steam ID, and IP address. +-- @string identifier Identifier to search with. +-- @player user Player to provide notifies to. +-- @bool bNoMessage Whether or not to provide feedback to the player. +function util.FindPlayer(identifier, user, bNoMessage) + if (!identifier) then + return; + end; + + local output = {}; + + for k, v in ipairs(player.GetAll()) do + local playerNick = string.lower(v:Nick()); + local playerName = string.lower(v:Name()); + + if (v:SteamID() == identifier or v:UniqueID() == identifier + or v:SteamID64() == identifier or (v:IPAddress():gsub(":%d+", "")) == identifier + or playerNick == string.lower(identifier) or playerName == string.lower(identifier)) then + return v; + end; + + if (string.find(playerNick, string.lower(identifier), 0, true) + or string.find(playerName, string.lower(identifier), 0, true)) then + table.insert(output, v); + end; + end; + + if (#output == 1) then + return output[1]; + elseif (#output > 1) then + if (!bNoMessage) then + if (IsValid(user)) then + if (serverguard and serverguard.Notify) then + serverguard.Notify(user, SERVERGUARD.NOTIFY.RED, "Found more than one player with that identifier."); + else + user:ChatPrint("Found more than one player with that identifier."); + end; + else + if (SERVER) then + Msg("Found more than one player with that identifier.\n"); + end; + end; + end; + else + if (!bNoMessage) then + if (IsValid(user)) then + if (serverguard and serverguard.Notify) then + serverguard.Notify(user, SERVERGUARD.NOTIFY.RED, "Can't find any player with that identifier."); + else + user:ChatPrint("Can't find any player with that identifier."); + end; + else + if (SERVER) then + Msg("Can't find any player with that identifier.\n"); + end; + end; + end; + end; +end; + +--- Alternative tonumber function that always returns a number. Returns 0 if unable to convert to number. +-- @param value Any variable to attempt to a number. +-- @treturn number Converted number. +function util.ToNumber(value) + if (type(value) == "string") then + if (tonumber(value) == nil) then + return 0; + end; + + return tonumber(value); + end; + + if (type(value) == "boolean") then + if (value) then + return 1; + end; + + return 0; + end; + + if (type(value) == "number") then + return value; + end; + + if (value == nil) then + return 0; + end; + + return 0; +end; + +--- Formats a number to include commas. +-- @number number Number to format. +-- @treturn string Formatted number. +function util.FormatNumber(number) + if (number >= 1e14) then + return tostring(number) + end + + number = tostring(number) + + local dp = string.find(number, "%.") or #number +1 + + for i = dp -4, 1, -3 do + number = string.sub(number, 1, i) .. "," .. string.sub(number, i +1) + end + + return number +end + +--- Capitalizes the first letter of a string. +-- @string text String to capitalize. +-- @treturn string Capitalized string. +function string.Capitalize(text) + return string.upper(string.sub(text, 1, 1)) .. string.sub(text, 2) +end + +--- Returns the ownership suffix of a string. +-- @string text String to find ownership for. +-- @bool bSuffixOnly Whether or not to return only the suffix. +-- @treturn string String with ownership. +function string.Ownership(text, bSuffixOnly) + local suffix = "'s"; + + if (text[string.len(text)] == "'" or text[string.len(text)] == "'s") then + suffix = ""; + elseif (text[string.len(text)] == "s") then + suffix = "'" + end; + + if (bSuffixOnly) then + return suffix; + end; + + return text .. suffix; +end; + +--- Returns a Steam ID from a string. +-- @string text String to check for a Steam ID. +-- @treturn text The matched Steam ID. +function string.SteamID(text) + return string.match(text, "STEAM_%d:%d:%d+"); +end; + +--- Explodes a string by enclosed tabs. +-- @string text String to explode. +-- @string seperator String to separate tags by. +-- @string open String to use as the beginning of a tag. +-- @string close String to use as the end of a tag. +-- @bool bRemoveTag Whether or not to remove the tag characters from the result. +-- @treturn table Exploded string. +-- @usage util.ExplodeByTags("!this 'is a test'", " ", "'", "'", true); +function util.ExplodeByTags(text, seperator, open, close, bRemoveTag) + local results = {}; + local current = ""; + local tag = nil; + + text = string.gsub(text, "%s+", " "); + + for i = 1, #text do + local character = string.sub(text, i, i); + + if (!tag) then + if (character == open) then + if (!bRemoveTag) then + current = current..character; + end; + + tag = true; + elseif (character == seperator) then + results[#results + 1] = current; current = ""; + else + current = current..character; + end; + else + if (character == close) then + if (!bRemoveTag) then + current = current..character; + end; + + tag = nil; + else + current = current..character; + end; + end; + end; + + if (current != "") then + results[#results + 1] = current; + end; + + return results; +end; + +--- Checks to see whether or not a variable is the console. +-- @param object Any variable. +-- @treturn bool Whether or not the variable is the console. +function util.IsConsole(object) + if (type(object) == "Entity" and !IsValid(object) and (object.EntIndex and object:EntIndex() == 0)) then + return true; + end; + + return false; +end; + +--- Converts an ISO 8601 duration timestamp used by YouTube to seconds. +-- Note: this is not entirely accurate to the ISO standard - it's only tested for use with YouTube's duration format. +-- @string iso The ISO duration. +-- @treturn number The ISO duration in seconds. Returns -1 for an invalid ISO duration. +function util.IsoDurationToSeconds(iso) + local duration = 0; + local number = ""; + + if (string.sub(iso, 1, 1) != "P") then + return -1; + end; + + for i = 1, string.len(iso), 1 do + local character = string.sub(iso, i, i); + + if (character == "P" or character == "T") then + continue; + end; + + if (tonumber(character)) then + number = number .. character; + end; + + if (!tonumber(number)) then + return -1; + end; + + if (character == "D") then + duration = duration + tonumber(number) * 86400; + number = ""; + end; + + if (character == "H") then + duration = duration + tonumber(number) * 3600; + number = ""; + end; + + if (character == "M") then + duration = duration + tonumber(number) * 60; + number = ""; + end; + + if (character == "S") then + duration = duration + tonumber(number); + number = ""; + end; + end; + + return duration; +end; + +local durationUnits = { + ["m"] = {1, "minute"}, + ["h"] = {60, "hour"}, + ["d"] = {1440, "day"}, + ["w"] = {10080, "week"}, + ["n"] = {43200, "month"}, + ["y"] = {525600, "year"} +}; + +local orderedDurationUnits = { + {525600, "year"}, + {43200, "month"}, + {10080, "week"}, + {1440, "day"}, + {60, "hour"}, + {1, "minute"}, +}; + +--- Converts a duration (e.g 1d12h) to minutes. Clamps all numbers between 0 and 99. +-- @string input The input duration. This can be a number, but it will return that number. +-- @treturn number The duration in minutes. +-- @treturn text The duration in text form (1 year, 3 days, etc). +-- @treturn bool Whether or not any input values have been clamped. +function util.ParseDuration(input) + if (tonumber(input)) then + local output = {}; + local number = tonumber(input); + + if (number <= 0) then + return 0, "Indefinitely", false; + end; + + for k, v in ipairs(orderedDurationUnits) do + if (number >= v[1]) then + local count = math.floor(number / v[1]); + + if (count > 1) then + output[#output + 1] = tostring(count).." "..v[2].."s"; + else + output[#output + 1] = tostring(count).." "..v[2]; + end; + + number = number - (v[1] * count); + end; + end; + + return tonumber(input), table.concat(output, ", "), false; + end; + + local bClamped = false; + local duration = 0; + + local text = ""; + local number = ""; + + for i = 1, string.len(input), 1 do + local character = string.sub(input, i, i); + + if (tonumber(character)) then + number = number .. character; + continue; + end + + if (!tonumber(number)) then + number = ""; + continue; + end; + + if (!durationUnits[character]) then + number = ""; + continue; + end; + + if (tonumber(number) < 0 || tonumber(number) > 99) then + number = tostring(math.Clamp(tonumber(number), 0, 99)); + bClamped = true; + end; + + duration = duration + (tonumber(number) * durationUnits[character][1]); + + if (tonumber(number) > 0) then + if (string.len(text) > 0) then + text = text .. ", "; + end; + + text = text .. number .. " " .. durationUnits[character][2]; + + if (tonumber(number) > 1) then + text = text .. "s"; + end; + end; + + number = ""; + end; + + if (text == "") then + text = "Indefinitely"; + end; + + return duration, text, bClamped; +end; + +if (SERVER) then + --- (SERVER) Prints text to the chatbox of all connected players. + -- @string text Text to print. + function util.PrintAll(text) + for k, v in ipairs(player.GetAll()) do + v:ChatPrint(text) + end + end + + --- (SERVER) Prints coloured text to the chatbox of all connected players. + -- @param ... Color and text to use. + -- @usage util.PrintAllColor(Color(255, 255, 255), "Hello! ", Color(200, 30, 30), "This is a test."); + function util.PrintAllColor(...) + serverguard.netstream.Start(nil, "sgPrintAllColor", {...}); + end; + + --- (SERVER) Prints text to the console of connected admins. + -- @string text Text to print. + function util.PrintConsoleAdmins(text) + for k, pPlayer in ipairs(player.GetAll()) do + if (pPlayer:IsAdmin()) then + serverguard.netstream.Start(pPlayer, "sgPrintConsole", text); + end; + end; + end; +elseif (CLIENT) then + serverguard.bLogEnabled = CreateClientConVar("serverguard_log", "1", true, false); + + serverguard.netstream.Hook("sgPrintAllColor", function(data) + chat.AddText(unpack(data)); + end); + + serverguard.netstream.Hook("sgPrintConsole", function(data) + if (serverguard.bLogEnabled:GetBool()) then + Msg(data); + end; + end); + + -- Thanks capsadmin <3. + --- (CLIENT) Draws a textured line. + -- @number x1 Starting X coordinate. + -- @number y1 Starting Y coordinate. + -- @number x2 Ending X coordinate. + -- @number y2 Ending Y coordinate. + -- @number w With of the line. + function surface.DrawLineEx(x1, y1, x2, y2, w) + local dx, dy = x1 -x2, y1 -y2 + local rotation = math.deg(math.atan2(dx, dy)) + local distance = math.Distance(x1, y1, x2, y2) + + x1 = x1 -dx *0.5 + y1 = y1 -dy *0.5 + + surface.DrawTexturedRectRotated(x1, y1, w, distance, rotation) + end + + --- Makes a panel change the cursor to a hand when hovered. + -- @panel panel Panel to install hover to. + function util.InstallHandHover(panel) + panel:SetMouseInputEnabled(true) + + function panel:OnCursorEntered() + self:SetCursor("hand") + end + + function panel:OnCursorExited() + self:SetCursor("arrow") + end + end + + --- (CLIENT) Draws a box shadow. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of box. + -- @number h Height of box. + -- @number passes The amount of passes when drawing the shadow. + -- @number depth The depth of the shadow. + function util.PaintShadow(x, y, w, h, passes, depth) + passes = passes or 4 + depth = depth or 0.2 + + for i = 1, passes do + local color = Color(0, 0, 0, (255 /i) *depth) + + -- Top shadow. + draw.SimpleRect(x, y +(-1 +i), w, 1, color) + + -- Left shadow. + draw.SimpleRect(x +(-1 +i), y, 1, h, color) + + -- Bottom shadow. + draw.SimpleRect(x, y +(h -i), w, 1, color) + + -- Right shadow. + draw.SimpleRect(x +(w -i), y, 1, h, color) + end + end + + --- (CLIENT) Darkens a colour by a given amount. Does not include alpha. + -- @color color Colour to dim. + -- @number amount How much to dim. + -- @treturn color Dimmed colour. + function util.DimColor(color, amount) + return Color( + math.Clamp(color.r - amount, 0, 255), + math.Clamp(color.g - amount, 0, 255), + math.Clamp(color.b - amount, 0, 255), + color.a + ); + end; + + --- (CLIENT) Limits how dark a colour can be. + -- @color color Colour to limit. + -- @number r Minimum red value. + -- @number g Minimum green value. + -- @number b Minimum blue value. + -- @number a Minimum alpha value. + -- @treturn color Limited colour. + function util.ColorLimit(color, r, g, b, a) + return Color( + math.min(color.r, r), + math.min(color.g, g), + math.min(color.b, b), + math.min(color.a, a) + ) + end + + --- (CLIENT) Gets the string version of a colour. + -- @color color Colour to get string from. + -- @bool bExcludeAlpha Whether or not to include the alpha in the string. + -- @treturn string Colour string. + function util.ColorString(color, bExcludeAlpha) + return color.r .. "," .. color.g .. "," .. color.b .. (!bExcludeAlpha and "," .. color.a or "") + end + + --- (CLIENT) Draws a coloured rectangle. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color col Colour of rectangle. + function draw.SimpleRect(x, y, w, h, col) + surface.SetDrawColor(col) + surface.DrawRect(x, y, w, h) + end + + --- (CLIENT) Draws a coloured rectangle outline. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color col Colour of rectangle. + function draw.SimpleOutlined(x, y, w, h, col) + surface.SetDrawColor(col) + surface.DrawOutlinedRect(x, y, w, h) + end + + --- (CLIENT) Draws two coloured rectangle outlines. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color col Colour of rectangle. + function draw.DoubleOutlined(x, y, w, h, col) + surface.SetDrawColor(col) + surface.DrawOutlinedRect(x, y, w, h) + surface.DrawOutlinedRect(x +1, y +1, w -2, h -2) + end + + --- (CLIENT) Draws a coloured rectangle with a material. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color color Colour of rectangle. + -- @material material Material to draw with. + function draw.Material(x, y, w, h, color, material) + surface.SetDrawColor(color) + surface.SetMaterial(material) + surface.DrawTexturedRect(x, y, w, h) + end + + --- (CLIENT) Draws a coloured rotated rectangle with a material. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color color Colour of rectangle. + -- @material material Material to draw with. + -- @number rotated Amount to rotate rectangle by. + function draw.MaterialRotated(x, y, w, h, color, material, rotated) + surface.SetDrawColor(color) + surface.SetMaterial(material) + surface.DrawTexturedRectRotated(x, y, w, h, rotated) + end + + --- (CLIENT) Draws a coloured rectangle with a texture. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color color Colour of rectangle. + -- @texture texture Texture to draw with. + function draw.Texture(x, y, w, h, color, texture) + surface.SetDrawColor(color) + surface.SetTexture(texture) + surface.DrawTexturedRect(x, y, w, h) + end + + --- (CLIENT) Draws a coloured rotated rectangle with a texture. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of rectangle. + -- @number h Height of rectangle. + -- @color color Colour of rectangle. + -- @texture texture Texture to draw with. + -- @number rotated Amount to rotate rectangle by. + function draw.TextureRotated(x, y, w, h, color, texture, rotated) + surface.SetDrawColor(color) + surface.SetTexture(texture) + surface.DrawTexturedRectRotated(x, y, w, h, rotated) + end + + --- (CLIENT) Draws text with an outline. + -- @string text Text to draw. + -- @string font Font to use. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @color col Colour of text. + -- @color colOutline Colour of outline. + -- @number xAlign Horizontal alignment. + -- @number yAlign Vertical alignment. + -- @number outline Outline width. + function draw.SimpleTextOutline(text, font, x, y, col, colOutline, xAlign, yAlign, outline) + draw.SimpleText(text, font, x +(outline or 1), y +(outline or 1), colOutline, xAlign, yAlign) + draw.SimpleText(text, font, x, y, col, xAlign, yAlign) + end + + --- (CLIENT) Gets the size of text with a given font. + -- @string font Font to use. + -- @string text Text to use. + -- @treturn number Width of text. + -- @treturn number Height of text. + function util.GetTextSize(font, text) + surface.SetFont(font) + + local w, h = surface.GetTextSize(text) + + return w, h + end + + --- (CLIENT) Creates a parented label. + -- @panel parent Panel to parent to. + -- @string title Text for the label. + -- @color col Colour of text. + -- @string font Font to use. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @treturn panel Created label. + function util.simpleLabel(parent, title, col, font, x, y) + local DLabel = nil + + if (parent != nil) then + DLabel = vgui.Create("DLabel", parent) + else + DLabel = vgui.Create("DLabel") + end + + if (x and y) then + DLabel:SetPos(x, y) + end + + if (col) then + DLabel:SetColor(col) + end + + DLabel:SetText(title) + DLabel:SetFont(font) + DLabel:SizeToContents() + + return DLabel + end; + + --- (CLIENT) Creates a parented button. + -- @panel parent Panel to parent to. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number w Width of button. + -- @number h Height of button. + -- @string text Text for the button. + -- @bool bDisabled Whether or not the button is disabled. + -- @func func Callback to use when button is clicked. + -- @treturn panel Created button. + function util.simpleButton(parent, x, y, w, h, text, bDisabled, func) + local DButton = nil + + if (parent != nil) then + DButton = vgui.Create("DButton", parent) + else + DButton = vgui.Create("DButton") + end + + DButton:SetText(text) + + if (w and h) then + DButton:SetSize(w, h) + end + + if (x and y) then + DButton:SetPos(x, y) + end + + DButton:SetDisabled(bDisabled) + DButton.DoClick = func + + return DButton + end + + --- (CLIENT) Creates a visual request for an input string. + -- @string title Title of request. + -- @param ... Options to use. + -- @usage util.CreateStringRequest("Please enter a string!", function(text) print(text) end, "Okay", function(text) end, "Cancel"); + function util.CreateStringRequest(title, ...) + local arguments = {...} + + if (#arguments == 0) then + return; + end; + + local buttons = {}; + + local dialog = vgui.Create("tiger.panel"); + dialog:SetTitle(title, false); + dialog:SetSize(350, 150); + dialog:Center(); + dialog:MakePopup(); + dialog:DockPadding(24, 24, 24, 48); + + local textEntry = dialog:Add("DTextEntry"); + textEntry:SetTall(20); + textEntry:SetSkin("serverguard"); + textEntry:Dock(TOP); + + for k, v in ipairs(arguments) do + if (isfunction(v)) then + local arg = arguments[k + 1] + + if (!arg or not isstring(arg)) then + continue; + end; + + local button = dialog:Add("tiger.button"); + button:SetText(arg); + button:SizeToContents(); + + function button:DoClick() + v(textEntry:GetValue()); + + dialog:Remove(); + end; + + table.insert(buttons, button); + end; + end; + + function dialog:PerformLayout(width, height) + local button = buttons[1] + + if (button) then + button:SetPos(width - (button:GetWide() + 24), height - (button:GetTall() + 14)); + + for i = 2, #buttons do + buttons[i]:SetPos(0, height - (buttons[i]:GetTall() + 14)); + buttons[i]:MoveLeftOf(button, 14); + end; + end; + end; + end; + + --- (CLIENT) Creates a dialog. Prefix option strings with "&" to make the button text bold. + -- @string title Title of dialog. + -- @string text Text for dialog. + -- @param ... Options to use. + -- @usage util.CreateDialog("Test", "This is simply a test dialog.", function() print("Clicked okay!") end, "&Okay", function() end, "Cancel"); + function util.CreateDialog(title, text, ...) + local arguments = {...} + + local dialog = vgui.Create("tiger.dialog") + dialog:SetTitle(title) + dialog:SetText(text) + + for k, v in ipairs(arguments) do + if (isfunction(v)) then + local arg = arguments[k + 1] + + if (!arg or not isstring(arg)) then + continue; + end; + + local buttonText = arguments[k + 1] + + dialog:AddButton(buttonText, function() + v() + dialog:FadeOut(0.5, function() + dialog:Remove() + end) + end) + end + end + + dialog:SizeToContents() + dialog:Center() + dialog:MakePopup() + dialog:FadeIn() + + return dialog + end + + --- (CLIENT) Creates multiple derma controls. + -- @param object[opt] parent The parent control. + -- @param ... The controls to create. + -- @usage local label, button = util.CreateControls(parent, "DLabel", "tiger.button"); + -- @usage local panel, label, button = util.CreateControls("tiger.panel", "DLabel", "tiger.button"); + function util.CreateControls(parent, ...) + local controls = {}; + local bCreatedParent = false; + + if (isstring(parent)) then + parent = vgui.Create(parent); + bCreatedParent = true; + end; + + for k, v in ipairs({...}) do + table.insert(controls, vgui.Create(v, parent)); + end; + + if (bCreatedParent) then + return parent, unpack(controls); + else + return unpack(controls); + end; + end; + + -- Author: Wizard of Ass + -- http://www.facepunch.com/threads/1089200?p=32614030&viewfull=1#post32614030 + + local DrawText = surface.DrawText + local SetTextPos = surface.SetTextPos + local PopModelMatrix = cam.PopModelMatrix + local PushModelMatrix = cam.PushModelMatrix + + local matrix = Matrix() + local matrixAngle = Angle(0, 0, 0) + local matrixScale = Vector(0, 0, 0) + local matrixTranslation = Vector(0, 0, 0) + + --- (CLIENT) Draws rotated text. + -- @string text Text to draw. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number xScale Horizontal scale of text. + -- @number yScale Vertical scale of text. + -- @number angle Angle to rotate text by. + function draw.TextRotated(text, x, y, xScale, yScale, angle) + matrixAngle.y = angle + matrix:SetAngle(matrixAngle) + + matrixTranslation.x = x + matrixTranslation.y = y + matrix:SetTranslation(matrixTranslation) + + matrixScale.x = xScale + matrixScale.y = yScale + matrix:Scale(matrixScale) + + SetTextPos(0, 0) + + PushModelMatrix(matrix) + DrawText(text) + PopModelMatrix() + end + + --- (CLIENT) Draws a circle. + -- @number x X coordinate. + -- @number y Y coordinate. + -- @number radius Radius of circle. + -- @number segments Number of line segments to use. + function draw.Circle(x, y, radius, segments) + local points = {}; + + table.insert(points, { + x = x, + y = y, + u = 0.5, + v = 0.5 + }); + + for i = 0, segments do + local angle = math.rad((i / segments) * -360); + + table.insert(points, { + x = x + math.sin(angle) * radius, + y = y + math.cos(angle) * radius, + u = math.sin(angle) / 2 + 0.5, + v = math.cos(angle) / 2 + 0.5 + }); + end; + + local angle = math.rad(0); + + table.insert(points, { + x = x + math.sin(angle) * radius, + y = y + math.cos(angle) * radius, + u = math.sin(angle) / 2 + 0.5, + v = math.cos(angle) / 2 + 0.5 + }); + + surface.DrawPoly(points); + end; +end; diff --git a/garrysmod/addons/admin-sg/lua/modules/sh_von.lua b/garrysmod/addons/admin-sg/lua/modules/sh_von.lua new file mode 100644 index 0000000..ddb53ad --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sh_von.lua @@ -0,0 +1,638 @@ +--[[ vON 1.2.0 + + Copyright 2012-2013 Alexandru-Mihai Maftei + aka Vercas + + GitHub Repository: + https://github.com/vercas/vON + + 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 (Vercas) if you publish your work based on (and/or using) this. + + If you modify the code for any purpose, the above obligations still apply. + If you make any interesting modifications, try forking the GitHub repository instead. + + Instead of copying this code over for sharing, rather use the link: + https://github.com/vercas/vON/blob/master/von%20for%20GMod.lua + + The author may not be held responsible for any damage or losses directly or indirectly caused by + the use of von. + + If you disagree with the above, don't use the code. + +-- + + Thanks to the following people for their contribution: + - Divran Suggested improvements for making the code quicker. + Suggested an excellent new way of deserializing strings. + Lead me to finding an extreme flaw in string parsing. + - pennerlord Provided some performance tests to help me improve the code. + - Chessnut Reported bug with handling of nil values when deserializing array components. + + - People who contributed on the GitHub repository by reporting bugs, posting fixes, etc. + +-- + + The value types supported in this release of vON are: + - table + - number + - boolean + - string + - nil + - Vector + - Angle + - Entities: + - Entity + - Vehicle + - Weapon + - NPC + - Player + - NextBot + + These are the native Lua types one would normally serialize. + + Some very common GMod Lua types. + +-- + + New in this version: + - Added better, shorter and faster way of handling strings, and a new format. + Old type of strings can still be deserialized. +--]] + +local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" } + +local _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable +local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next, getEnt, getPly = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next, Entity, player.GetByID + +-- This is kept away from the table for speed. +function d_findVariable(s, i, len, lastType) + local i, c, typeRead, val = i or 1 + + -- Keep looping through the string. + while true do + -- Stop at the end. Throw an error. This function MUST NOT meet the end! + if i > len then + error("vON: Reached end of string, cannot form proper variable.") + end + + -- Cache the character. Nobody wants to look for the same character ten times. + c = sub(s, i, i) + + -- If it just read a type definition, then a variable HAS to come after it. + if typeRead then + -- Attempt to deserialize a variable of the freshly read type. + val, i = _deserialize[lastType](s, i, len) + -- Return the value read, the index of the last processed character, and the type of the last read variable. + return val, i, lastType + + -- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store. + elseif c == "@" then + return nil, i, lastType + + -- n means a number will follow. Base 10... :C + elseif c == "n" then + lastType = "number" + typeRead = true + + -- b means boolean flags. + elseif c == "b" then + lastType = "boolean" + typeRead = true + + -- ' means the start of a string. + elseif c == "'" then + lastType = "string" + typeRead = true + + -- " means the start of a string prior to version 1.2.0. + elseif c == "\"" then + lastType = "oldstring" + typeRead = true + + -- { means the start of a table! + elseif c == "{" then + lastType = "table" + typeRead = true + + -- e means an entity ID will follow. + elseif c == "e" then + lastType = "Entity" + typeRead = true +--[[ + -- c means a vehicle ID will follow. + elseif c == "c" then + lastType = "Vehicle" + typeRead = true + + -- w means a weapon entity ID will follow. + elseif c == "w" then + lastType = "Weapon" + typeRead = true + + -- x means a NPC ID will follow. + elseif c == "x" then + lastType = "NPC" + typeRead = true +--]] + -- p means a player ID will follow. + -- Kept for backwards compatibility. + elseif c == "p" then + lastType = "Entity" + typeRead = true + + -- v means a vector will follow. 3 numbers. + elseif c == "v" then + lastType = "Vector" + typeRead = true + + -- a means an Euler angle will follow. 3 numbers. + elseif c == "a" then + lastType = "Angle" + typeRead = true + + -- If no type has been found, attempt to deserialize the last type read. + elseif lastType then + val, i = _deserialize[lastType](s, i, len) + return val, i, lastType + + -- This will occur if the very first character in the vON code is wrong. + else + error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c) + end + + -- Move the pointer one step forward. + i = i + 1 + end +end + +-- This is kept away from the table for speed. +-- Yeah, crapload of parameters. +function s_anyVariable(data, lastType, isNumeric, isKey, isLast, nice, indent) + + -- Basically, if the type changes. + if lastType ~= type(data) then + -- Remember the new type. Caching the type is useless. + lastType = type(data) + + if _serialize[lastType] then + -- Return the serialized data and the (new) last type. + -- The second argument, which is true now, means that the data type was just changed. + return _serialize[lastType](data, true, isNumeric, isKey, isLast, nice, indent), lastType + else + error("vON: No serializer defined for type \"" .. lastType .. "\"!") + end + end + + -- Otherwise, simply serialize the data. + return _serialize[lastType](data, false, isNumeric, isKey, isLast, nice, indent), lastType +end + +_deserialize = { + +-- Well, tables are very loose... +-- The first table doesn't have to begin and end with { and }. + ["table"] = function(s, i, len, unnecessaryEnd) + local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1 + -- Locals, locals, locals, locals, locals, locals, locals, locals and locals. + + -- Keep looping. + while true do + -- Until it meets the end. + if i > len then + -- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example. + if unnecessaryEnd then + return ret, i + + -- Otherwise, the data has to be damaged. + else + error("vON: Reached end of string, incomplete table definition.") + end + end + + -- Cache the character. + c = sub(s, i, i) + --print(i, "table char:", c, tostring(unnecessaryEnd)) + + -- If it's the end of a table definition, return. + if c == "}" then + return ret, i + + -- If it's the component separator, switch to key:value pairs. + elseif c == "~" then + numeric = false + + elseif c == ";" then + -- Lol, nothing! + -- Remenant from numbers, for faster parsing. + + -- OK, now, if it's on the numeric component, simply add everything encountered. + elseif numeric then + -- Find a variable and it's value + val, i, lastType = d_findVariable(s, i, len, lastType) + -- Add it to the table. + ret[ind] = val + + ind = ind + 1 + + -- Otherwise, if it's the key:value component... + else + -- If a value is expected... + if expectValue then + -- Read it. + val, i, lastType = d_findVariable(s, i, len, lastType) + -- Add it? + ret[key] = val + -- Clean up. + expectValue, key = false, nil + + -- If it's the separator... + elseif c == ":" then + -- Expect a value next. + expectValue = true + + -- But, if there's a key read already... + elseif key then + -- Then this is malformed. + error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c) + + -- Otherwise the key will be read. + else + -- I love multi-return and multi-assignement. + key, i, lastType = d_findVariable(s, i, len, lastType) + end + end + + i = i + 1 + end + + return nil, i + end, + + +-- Numbers are weakly defined. +-- The declaration is not very explicit. It'll do it's best to parse the number. +-- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards. + ["number"] = function(s, i, len) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + return tonumber(sub(s, i, a - 1)), a - 1 + end + + error("vON: Number definition started... Found no end.") + end, + + +-- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false. +-- Any other attempt at boolean declaration will result in a failure. + ["boolean"] = function(s, i, len) + local c = sub(s,i,i) + -- Only one character is needed. + + -- If it's 1, then it's true + if c == "1" then + return true, i + + -- If it's 0, then it's false. + elseif c == "0" then + return false, i + end + + -- Any other supposely "boolean" is just a sign of malformed data. + error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c) + end, + + +-- Strings prior to 1.2.0 + ["oldstring"] = function(s, i, len) + local res, i, a = "", i or 1 + -- Locals, locals, locals, locals + + while true do + a = find(s, "\"", i, true) + + if a then + if sub(s, a - 1, a - 1) == "\\" then + res = res .. sub(s, i, a - 2) .. "\"" + i = a + 1 + else + return res .. sub(s, i, a - 2), a + end + else + error("vON: Old string definition started... Found no end.") + end + end + end, + +-- Strings after 1.2.0 + ["string"] = function(s, i, len) + local res, i, a = "", i or 1 + -- Locals, locals, locals, locals + + while true do + a = find(s, "\"", i, true) + + if a then + if sub(s, a - 1, a - 1) == "\\" then + res = res .. sub(s, i, a - 2) .. "\"" + i = a + 1 + else + return res .. sub(s, i, a - 1), a + end + else + error("vON: String definition started... Found no end.") + end + end + end, + + +-- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway. +-- Exactly like a number definition, except it begins with "e". + ["Entity"] = function(s, i, len) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + return getEnt(tonumber(sub(s, i, a - 1))), a - 1 + end + + error("vON: Entity ID definition started... Found no end.") + end, + + +-- A pair of 3 numbers separated by a comma (,). + ["Vector"] = function(s, i, len) + local i, a, x, y, z = i or 1 + -- Locals, locals, locals, locals + + a = find(s, ",", i) + + if a then + x = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, ",", i) + + if a then + y = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, "[;:}~]", i) + + if a then + z = tonumber(sub(s, i, a - 1)) + end + + if x and y and z then + return Vector(x, y, z), a - 1 + end + + error("vON: Vector definition started... Found no end.") + end, + + +-- A pair of 3 numbers separated by a comma (,). + ["Angle"] = function(s, i, len) + local i, a, p, y, r = i or 1 + -- Locals, locals, locals, locals + + a = find(s, ",", i) + + if a then + p = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, ",", i) + + if a then + y = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, "[;:}~]", i) + + if a then + r = tonumber(sub(s, i, a - 1)) + end + + if p and y and r then + return Angle(p, y, r), a - 1 + end + + error("vON: Angle definition started... Found no end.") + end +} + + +_serialize = { + +-- Uh. Nothing to comment. +-- Shitload of parameters. +-- Makes shit faster than simply passing it around in locals. +-- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY. + ["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first) + --print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first))) + + local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0 + -- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals. + + -- First thing to be done is separate the numeric and key:value components of the given table in two tables. + -- pairs(data) is slower than next, data as far as my tests tell me. + for k, v in next, data do + -- Skip the numeric keyz. + if not isnumber(k) or k < 1 or k > len then + keyvals[#keyvals + 1] = k + end + end + + keyvalsLen = #keyvals + + -- Main chunk - no initial character. + if not first then + result[#result + 1] = "{" + end + + -- Add numeric values. + if len > 0 then + for i = 1, len do + val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, false, 0) + result[#result + 1] = val + end + end + + -- If there are key:value pairs. + if keyvalsLen > 0 then + -- Insert delimiter. + result[#result + 1] = "~" + + -- Insert key:value pairs. + for _i = 1, keyvalsLen do + keyvalsProgress = keyvalsProgress + 1 + + val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, false, 0) + + result[#result + 1] = val..":" + + val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, false, 0) + + result[#result + 1] = val + end + end + + -- Main chunk needs no ending character. + if not first then + result[#result + 1] = "}" + end + + return concat(result) + end, + + +-- Normal concatenations is a lot faster with small strings than table.concat +-- Also, not so branched-ish. + ["number"] = function(data, mustInitiate, isNumeric, isKey, isLast) + -- If a number hasn't been written before, add the type prefix. + if mustInitiate then + if isKey or isLast then + return "n"..data + else + return "n"..data..";" + end + end + + if isKey or isLast then + return "n"..data + else + return "n"..data..";" + end + end, + + +-- I hope gsub is fast enough. + ["string"] = function(data, mustInitiate, isNumeric, isKey, isLast) + return "'" .. gsub(data, "\"", "\\\"") .. "\"" + end, + + +-- Fastest. + ["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast) + -- Prefix if we must. + if mustInitiate then + if data then + return "b1" + else + return "b0" + end + end + + if data then + return "1" + else + return "0" + end + end, + + +-- Fastest. + ["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast) + return "@" + end, + + +-- Same as numbers, except they start with "e" instead of "n". + ["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast) + data = data:EntIndex() + + if mustInitiate then + if isKey or isLast then + return "e"..data + else + return "e"..data..";" + end + end + + if isKey or isLast then + return "e"..data + else + return "e"..data..";" + end + end, + + +-- 3 numbers separated by a comma. + ["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast) + if mustInitiate then + if isKey or isLast then + return "v"..data.x..","..data.y..","..data.z + else + return "v"..data.x..","..data.y..","..data.z..";" + end + end + + if isKey or isLast then + return "v"..data.x..","..data.y..","..data.z + else + return "v"..data.x..","..data.y..","..data.z..";" + end + end, + + +-- 3 numbers separated by a comma. + ["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast) + if mustInitiate then + if isKey or isLast then + return "a"..data.p..","..data.y..","..data.r + else + return "a"..data.p..","..data.y..","..data.r..";" + end + end + + if isKey or isLast then + return "a"..data.p..","..data.y..","..data.r + else + return "a"..data.p..","..data.y..","..data.r..";" + end + end +} + +for i = 1, #extraEntityTypes do + _serialize[extraEntityTypes[i]] = _serialize.Entity +end + +local _s_table = _serialize.table +local _d_table = _deserialize.table + +_d_meta = { + __call = function(self, str) + if type(str) == "string" then + return _d_table(str, nil, #str, true) + end + error("vON: You must deserialize a string, not a "..type(str)) + end +} +_s_meta = { + __call = function(self, data) + if type(data) == "table" then + return _s_table(data, nil, nil, nil, nil, true) + end + error("vON: You must serialize a table, not a "..type(data)) + end +} + +serverguard.von = { + version = "1.2.0", + versionNumber = 1200000, -- Reserving 3 digits per version component. + + deserialize = setmetatable(_deserialize,_d_meta), + serialize = setmetatable(_serialize,_s_meta) +} diff --git a/garrysmod/addons/admin-sg/lua/modules/sv_mysql.lua b/garrysmod/addons/admin-sg/lua/modules/sv_mysql.lua new file mode 100644 index 0000000..e637de1 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sv_mysql.lua @@ -0,0 +1,745 @@ +--[[ + mysql - 1.0.0 + A simple MySQL wrapper for Garry's Mod. + + Alexander Grist-Hucker + http://www.alexgrist.com +--]] + +--- ## Serverside +-- Handles SQL module selection, SQL queries. +-- @module serverguard.mysql + +serverguard.mysql = serverguard.mysql or {}; + +local QueueTable = {}; +local Module = "mysqloo"; + +local type = type; +local tostring = tostring; +local table = table; + +--[[ + Phrases +--]] + +local MODULE_NOT_EXIST = "[mysql] The %s module does not exist!\n"; + +--- Query class object that holds information about the current query being built. +-- @type QUERY_CLASS +local QUERY_CLASS = {}; +QUERY_CLASS.__index = QUERY_CLASS; + +--- **This is called internally, use the corresponding methods.** +-- Creates a new query object. +-- @string tableName The name of the table to manipulate. +-- @string queryType The type of query to start building. (i.e CREATE, UPDATE, INSERT, etc.) +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; + +--- Escapes the given string. +-- @string text The string to escape. +-- @treturn string The escaped string. +function QUERY_CLASS:Escape(text) + return serverguard.mysql:Escape(tostring(text)); +end; + +function QUERY_CLASS:ForTable(tableName) + self.tableName = tableName; +end; + +--- Adds a WHERE clause to the SQL query. Analagous to WhereEqual. +-- @string key The key to compare. +-- @string value The value to compare to. +function QUERY_CLASS:Where(key, value) + self:WhereEqual(key, value); +end; + +--- Adds a "WHERE `column` = value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereEqual(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` = "..self:Escape(value); +end; + +--- Adds a "WHERE `column` != value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereNotEqual(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` != "..self:Escape(value); +end; + +--- Adds a "WHERE `column` LIKE value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereLike(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` LIKE "..self:Escape(value); +end; + +--- Adds a "WHERE `column` NOT LIKE value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereNotLike(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` NOT LIKE "..self:Escape(value); +end; + +--- Adds a "WHERE `column` > value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereGT(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` > "..self:Escape(value); +end; + +--- Adds a "WHERE `column` < value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereLT(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` < "..self:Escape(value); +end; + +--- Adds a "WHERE `column` >= value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereGTE(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` >= "..self:Escape(value); +end; + +--- Adds a "WHERE `column` <= value" clause to the query. +-- You can use multiple of these statements to compare more than one column. +-- @string key The column to compare. +-- @string value The value to compare with. +function QUERY_CLASS:WhereLTE(key, value) + self.whereList[#self.whereList + 1] = "`"..key.."` <= "..self:Escape(value); +end; + +--- Adds a "ORDER BY `column` DESC" clause to the query. +-- You can use multiple of these statements to sort by more than one column. +-- @string key The column to sort. +function QUERY_CLASS:OrderByDesc(key) + self.orderByList[#self.orderByList + 1] = "`"..key.."` DESC"; +end; + +--- Adds a "ORDER BY `column` ASC" clause to the query. +-- You can use multiple of these statements to sort by more than one column. +-- @string key The column to sort. +function QUERY_CLASS:OrderByAsc(key) + self.orderByList[#self.orderByList + 1] = "`"..key.."` ASC"; +end; + +--- Sets the function to be called when the query finishes. +-- @func queryCallback The callback function to execute. +function QUERY_CLASS:Callback(queryCallback) + self.callback = queryCallback; +end; + +--- Adds a "SELECT `column` FROM" clause to the query. +-- You can use multiple of these statements to select more than one column. +-- @string fieldName The column to select. +function QUERY_CLASS:Select(fieldName) + self.selectList[#self.selectList + 1] = "`"..fieldName.."`"; +end; + +--- Adds a "INSERT INTO `column` VALUES(value)" clause to the query. +-- @string key The column to insert into. +-- @string value The value to insert. +function QUERY_CLASS:Insert(key, value) + self.insertList[#self.insertList + 1] = {"`"..key.."`", self:Escape(value)}; +end; + +--- Adds a "UPDATE `column` SET key=value" clause to the query. +-- @string key The column to update. +-- @string value The value to update to. +function QUERY_CLASS:Update(key, value) + self.updateList[#self.updateList + 1] = {"`"..key.."`", self:Escape(value)}; +end; + +--- Populates the table create list with the given keys and values if the query is a CREATE query. +-- @string key The column to create. +-- @string value The value to set it to. +function QUERY_CLASS:Create(key, value) + self.createList[#self.createList + 1] = {"`"..key.."`", value}; +end; + +--- Sets the primary key for the table. +-- @string key The primary key. +function QUERY_CLASS:PrimaryKey(key) + self.primaryKey = "`"..key.."`"; +end; + +--- Adds a "LIMIT value" clause to the query. +-- @number value The value to limit the query to. +function QUERY_CLASS:Limit(value) + self.limit = value; +end; + +--- Adds a "OFFSET value" clause to the query. +-- @number value The amount to offset by. +function QUERY_CLASS:Offset(value) + self.offset = value; +end; + +local function BuildSelectQuery(queryObj) + local queryString = {"SELECT"}; + + if (type(queryObj.selectList) != "table" or #queryObj.selectList == 0) then + queryString[#queryString + 1] = " *"; + else + queryString[#queryString + 1] = " "..table.concat(queryObj.selectList, ", "); + end; + + if (type(queryObj.tableName) == "string") then + queryString[#queryString + 1] = " FROM `"..queryObj.tableName.."` "; + else + ErrorNoHalt("[mysql] No table name specified!\n"); + return; + end; + + if (type(queryObj.whereList) == "table" and #queryObj.whereList > 0) then + queryString[#queryString + 1] = " WHERE "; + queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND "); + end; + + if (type(queryObj.orderByList) == "table" and #queryObj.orderByList > 0) then + queryString[#queryString + 1] = " ORDER BY "; + queryString[#queryString + 1] = table.concat(queryObj.orderByList, ", "); + end; + + if (type(queryObj.limit) == "number") then + queryString[#queryString + 1] = " LIMIT "; + queryString[#queryString + 1] = queryObj.limit; + end; + + return table.concat(queryString); +end; + +local function BuildInsertQuery(queryObj) + local queryString = {"INSERT INTO"}; + local keyList = {}; + local valueList = {}; + + if (type(queryObj.tableName) == "string") 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 (type(queryObj.tableName) == "string") then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`"; + else + ErrorNoHalt("[mysql] No table name specified!\n"); + return; + end; + + if (type(queryObj.updateList) == "table" 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 (type(queryObj.whereList) == "table" and #queryObj.whereList > 0) then + queryString[#queryString + 1] = " WHERE "; + queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND "); + end; + + if (type(queryObj.offset) == "number") 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 (type(queryObj.tableName) == "string") then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`"; + else + ErrorNoHalt("[mysql] No table name specified!\n"); + return; + end; + + if (type(queryObj.whereList) == "table" and #queryObj.whereList > 0) then + queryString[#queryString + 1] = " WHERE "; + queryString[#queryString + 1] = table.concat(queryObj.whereList, " AND "); + end; + + if (type(queryObj.limit) == "number") 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 (type(queryObj.tableName) == "string") 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 (type(queryObj.tableName) == "string") then + queryString[#queryString + 1] = " `"..queryObj.tableName.."`"; + else + ErrorNoHalt("[mysql] No table name specified!\n"); + return; + end; + + queryString[#queryString + 1] = " ("; + + if (type(queryObj.createList) == "table" and #queryObj.createList > 0) then + local createList = {}; + + for i = 1, #queryObj.createList do + if (Module == "sqlite") then + createList[#createList + 1] = queryObj.createList[i][1].." "..string.gsub(string.gsub(string.gsub(queryObj.createList[i][2], "AUTO_INCREMENT", ""), "AUTOINCREMENT", ""), "INT ", "INTEGER "); + else + createList[#createList + 1] = queryObj.createList[i][1].." "..queryObj.createList[i][2]; + end; + end; + + queryString[#queryString + 1] = " "..table.concat(createList, ", ").."," ; + end; + + if (type(queryObj.primaryKey) == "string") then + queryString[#queryString + 1] = " PRIMARY KEY"; + queryString[#queryString + 1] = " ("..queryObj.primaryKey..")"; + end; + + queryString[#queryString + 1] = " )"; + + return table.concat(queryString); +end; + +--- Executes the query on the database. +-- @bool[opt] bQueueQuery Whether or not to queue the query. Defaults to false. +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 == "update") then + queryString = BuildUpdateQuery(self); + elseif (queryType == "delete") then + queryString = BuildDeleteQuery(self); + elseif (queryType == "drop") then + queryString = BuildDropQuery(self); + elseif (queryType == "create") then + queryString = BuildCreateQuery(self); + end; + + if (type(queryString) == "string") then + if (!bQueueQuery) then + return serverguard.mysql:RawQuery(queryString, self.callback); + else + return serverguard.mysql:Queue(queryString, self.callback); + end; + end; +end; + +--- Creates a new SELECT query. +-- @string tableName The name of the table. +-- @treturn QUERY_CLASS The query object. +function serverguard.mysql:Select(tableName) + return QUERY_CLASS:New(tableName, "SELECT"); +end; + +--- Creates a new INSERT INTO query. +-- @string tableName The name of the table. +-- @treturn QUERY_CLASS The query object. +function serverguard.mysql:Insert(tableName) + return QUERY_CLASS:New(tableName, "INSERT"); +end; + +--- Creates a new UPDATE query. +-- @string tableName The name of the table. +-- @treturn QUERY_CLASS The query object. +function serverguard.mysql:Update(tableName) + return QUERY_CLASS:New(tableName, "UPDATE"); +end; + +--- Creates a new DELETE query. +-- @string tableName The name of the table. +-- @treturn QUERY_CLASS The query object. +function serverguard.mysql:Delete(tableName) + return QUERY_CLASS:New(tableName, "DELETE"); +end; + +--- Creates a new DROP query. +-- @string tableName The name of the table. +-- @treturn QUERY_CLASS The query object. +function serverguard.mysql:Drop(tableName) + return QUERY_CLASS:New(tableName, "DROP"); +end; + +--- Creates a new CREATE TABLE query. +-- @string tableName The name of the table. +-- @treturn QUERY_CLASS The query object. +function serverguard.mysql:Create(tableName) + return QUERY_CLASS:New(tableName, "CREATE"); +end; + +--- Connects to the SQL database with the given information. +-- @string host The host name. +-- @string username The username. +-- @string password The password. +-- @string database The name of the database. +-- @number[opt] port The port to use. Defaults to 3306. +-- @string[opt] socket The unix socket to use. Defaults to none. +-- @number flags The query flags to use. Usually these aren't needed. +function serverguard.mysql:Connect(host, username, password, database, port, socket, flags) + if (!port) then + port = 3306; + end; + + if (Module == "tmysql4") then + if (type(tmysql) != "table") then + require("tmysql4"); + end; + + if (tmysql) then + local errorText = nil; + + self.connection, errorText = tmysql.initialize(host, username, password, database, port, socket, flags); + + if (!self.connection) then + self:OnConnectionFailed(errorText); + else + self:OnConnected(); + end; + else + ErrorNoHalt(string.format(MODULE_NOT_EXIST, Module)); + end; + elseif (Module == "mysqloo") then + if (type(mysqloo) != "table") then + require("mysqloo"); + end; + + if (mysqloo) then + + self.connection = mysqloo.connect(host, username, password, database, port, socket or "", (flags or 0)); + + self.connection.onConnected = function(database) + serverguard.mysql:OnConnected(); + end; + + self.connection.onConnectionFailed = function(database, errorText) + serverguard.mysql:OnConnectionFailed(errorText); + end; + + self.connection:connect(); + else + ErrorNoHalt(string.format(MODULE_NOT_EXIST, Module)); + end; + elseif (Module == "sqlite") then + serverguard.mysql:OnConnected(); + end; +end; + +--- Sends a query to the database without any preprocessing. You should not use this unless +-- you know what you're doing. +-- @string query The query to run. +-- @func callback The function to call when the query finishes. +-- @number flags The flags to set on the query. Usually this isn't needed. +function serverguard.mysql:RawQuery(query, callback, flags, ...) + if (!self.connection and Module != "sqlite") then + self:Queue(query); + end; + + if (Module == "tmysql4") then + local queryFlag = flags or QUERY_FLAG_ASSOC; + + self.connection:Query(query, function(result) + local queryStatus = result[1]["status"]; + + if (queryStatus) then + if (type(callback) == "function") then + local bStatus, value = pcall(callback, result[1]["data"], queryStatus, result[1]["lastid"]); + + if (!bStatus) then + ErrorNoHalt(string.format("[mysql] MySQL Callback Error!\n%s\n", value)); + end; + end; + else + ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, result[1]["error"])); + end; + end, queryFlag, ...); + elseif (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, self.connection:status(), queryObj:lastInsert()); + + if (!bStatus) then + ErrorNoHalt(string.format("[mysql] MySQL Callback Error!\n%s\n", value)); + end; + end; + end; + + queryObj.onError = function(queryObj, errorText, ...) + if (self.connection:status() != mysqloo.DATABASE_CONNECTED) then + self:Queue(query, callback); + self.connection:connect(); + return; + end; + + ErrorNoHalt(string.format("[mysql] MySQL Query Error!\nQuery: %s\n%s\n", query, errorText)); + end; + + queryObj:start(); + elseif (Module == "sqlite") then + local result = sql.Query(query); + + if (result == false) then + ErrorNoHalt(string.format("[mysql] SQL Query Error!\nQuery: %s\n%s\n", query, sql.LastError())); + else + if (callback) then + local bStatus, value = pcall(callback, result); + + if (!bStatus) then + ErrorNoHalt(string.format("[mysql] SQL Callback Error!\n%s\n", value)); + end; + end; + end; + else + ErrorNoHalt(string.format("[mysql] Unsupported module \"%s\"!\n", Module)); + end; +end; + +--- Adds a query to the queue to be executed once previously pending ones have finished. +-- @string queryString The query to run. +-- @func callback The function to run when the query executes. +function serverguard.mysql:Queue(queryString, callback) + if (type(queryString) == "string") then + QueueTable[#QueueTable + 1] = {queryString, callback}; + end; +end; + +--- Escapes the string to be used in a query. +-- @string text The string to escape. +-- @bool[opt] bNoQuotes Whether or not to NOT wrap with quotes. Defaults to false. +function serverguard.mysql:Escape(text, bNoQuotes) + if (self.connection) then + if (Module == "tmysql4") then + if (bNoQuotes) then + return self.connection:Escape(text); + else + return string.format("\"%s\"", self.connection:Escape(text)); + end; + elseif (Module == "mysqloo") then + if (bNoQuotes) then + return self.connection:escape(text); + else + return string.format("\"%s\"", self.connection:escape(text)); + end; + end; + else + return sql.SQLStr(text, bNoQuotes); + end; +end; + +--- Disconnects from the SQL database. +function serverguard.mysql:Disconnect() + if (self.connection) then + if (Module == "tmysql4") then + return self.connection:Disconnect(); + end; + end; +end; + +-- Function that's ran for checking the query queue. +function serverguard.mysql:Think() + if (#QueueTable > 0) then + if (type(QueueTable[1]) == "table") then + local queueObj = QueueTable[1]; + local queryString = queueObj[1]; + local callback = queueObj[2]; + + if (type(queryString) == "string") then + self:RawQuery(queryString, callback); + end; + + table.remove(QueueTable, 1); + end; + end; +end; + +--- A function to set the SQL module to be used. This should not be set unless +-- you know what you're doing! +function serverguard.mysql:SetModule(moduleName) + Module = moduleName; +end; + +-- Called when the database connects sucessfully. +function serverguard.mysql:OnConnected() + serverguard.PrintConsole("[mysql] Connected to the database!\n"); + + self.UpgradeCount = 0; + self.FinishedUpgrades = 0 + + hook.Call("serverguard.mysql.CreateTables", nil); + hook.Call("serverguard.mysql.UpgradeSchemas", nil, function() + self.FinishedUpgrades = self.FinishedUpgrades + 1 + if (self.UpgradeCount == self.FinishedUpgrades) then + hook.Call("serverguard.mysql.OnConnected", nil); + end + end); +end; + +-- Called when the database connection fails. +function serverguard.mysql:OnConnectionFailed(errorText) + ErrorNoHalt("[mysql] Unable to connect to the database!\n"..errorText.."\n"); + + hook.Call("serverguard.mysql.DatabaseConnectionFailed", nil, errorText); +end; + +include 'sg_mysql.lua' +hook.Add("serverguard.Initialize", "serverguard.mysql.Initialize", function() + local config = SERVERGUARD.MySQL + + if (config and config.enabled == 1) then + if (config.module != Module) then + Module = config.module; + end; + + serverguard.mysql:Connect(config.host, config.username, config.password, config.database, config.port, config.unixsocket); + return; + end; + + serverguard.mysql:Connect(); +end); + +hook.Add("serverguard.mysql.CreateTables", "serverguard.mysql.CreateTables", function() + local USERS_TABLE_QUERY = serverguard.mysql:Create("serverguard_users"); + USERS_TABLE_QUERY:Create("id", "INTEGER NOT NULL AUTO_INCREMENT"); + USERS_TABLE_QUERY:Create("steam_id", "VARCHAR(25) NOT NULL"); + USERS_TABLE_QUERY:Create("rank", "VARCHAR(255) DEFAULT \"\" NOT NULL"); + USERS_TABLE_QUERY:Create("name", "VARCHAR(255) NOT NULL"); + USERS_TABLE_QUERY:Create("last_played", "INT(11) NOT NULL"); + USERS_TABLE_QUERY:Create("data", "TEXT"); + USERS_TABLE_QUERY:PrimaryKey("id"); + USERS_TABLE_QUERY:Execute(); + + local BANS_TABLE_QUERY = serverguard.mysql:Create("serverguard_bans"); + BANS_TABLE_QUERY:Create("id", "INTEGER NOT NULL AUTO_INCREMENT"); + BANS_TABLE_QUERY:Create("steam_id", "VARCHAR(25) NOT NULL"); + BANS_TABLE_QUERY:Create("community_id", "TEXT NOT NULL"); + BANS_TABLE_QUERY:Create("player", "VARCHAR(255) NOT NULL"); + BANS_TABLE_QUERY:Create("reason", "TEXT NOT NULL"); + BANS_TABLE_QUERY:Create("start_time", "INT(11) NOT NULL"); + BANS_TABLE_QUERY:Create("end_time", "INT(11) NOT NULL"); + BANS_TABLE_QUERY:Create("admin", "VARCHAR(255) NOT NULL"); + BANS_TABLE_QUERY:Create("ip_address", "VARCHAR(15) NOT NULL"); + BANS_TABLE_QUERY:PrimaryKey("id"); + BANS_TABLE_QUERY:Execute(); + + local SCHEMA_TABLE_QUERY = serverguard.mysql:Create("serverguard_schema"); + SCHEMA_TABLE_QUERY:Create("id", "VARCHAR(31) NOT NULL"); + SCHEMA_TABLE_QUERY:Create("version", "INT(11) NOT NULL DEFAULT '0'"); + SCHEMA_TABLE_QUERY:PrimaryKey("id"); + SCHEMA_TABLE_QUERY:Execute() +end); + +-- Upgrades Schemas to latest +-- input: database name, table of strings for each version, callback when done +function serverguard.mysql:UpgradeSchema(id, schemas, callback) + self.UpgradeCount = self.UpgradeCount + 1 + + self:Queue("SELECT `version` from `serverguard_schema` where `id` = "..self:Escape(id)..";", + function(query) + local version = tonumber(query and query[1] and query[1].version or 0) + + if (version == #schemas) then + callback(false) + else + local complete_query = ""; + + for i = 1, #schemas do + table.insert(schemas[i], "REPLACE INTO `serverguard_schema` (`id`, `version`) VALUES ("..serverguard.mysql:Escape(id)..", '"..i.."');"); + end + + local function UpgradeRecursion(upgrade, version, index) + if (not upgrade[version]) then + return callback(true) + end + local final_version_query = index == #upgrade[version]; + local next_version = (final_version_query and 1 or 0) + version; + local next_index = final_version_query and 1 or index + 1 + if (not upgrade[next_version]) then + return callback(true) + end + serverguard.mysql:Queue(upgrade[next_version][next_index], function() + UpgradeRecursion(upgrade, next_version, next_index); + end); + + end + + UpgradeRecursion(schemas, version + 1, 0) + + end + end + ); + +end + +timer.Create("serverguard.mysql.Think", 1, 0, function() + serverguard.mysql:Think(); +end); diff --git a/garrysmod/addons/admin-sg/lua/modules/sv_player.lua b/garrysmod/addons/admin-sg/lua/modules/sv_player.lua new file mode 100644 index 0000000..1c3ba2a --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sv_player.lua @@ -0,0 +1,84 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +function serverguard.player:Load(player) + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:Where("steam_id", player:SteamID()); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + if (result[1].rank) then + local rankData = serverguard.ranks:GetRank(result[1].rank); + + if (rankData) then + serverguard.player:SetRank(player, rankData.unique, 0, true); + serverguard.player:SetImmunity(player, rankData.immunity); + serverguard.player:SetTargetableRank(player, rankData.targetable); + serverguard.player:SetBanLimit(player, rankData.banlimit); + end; + end; + + if (result[1].data and result[1].data != "NULL") then + player.sg_data = serverguard.von.deserialize(result[1].data); + else + player.sg_data = {}; + end; + + serverguard.player:Save(player); + else + player.sg_data = {}; + serverguard.player:Save(player, true); + end; + hook.Call("serverguard.LoadedPlayerData", nil, player, player:SteamID()); + end); + queryObj:Execute(); +end; + +function serverguard.player:Save(player, bNew) + if (bNew) then + local insertObj = serverguard.mysql:Insert("serverguard_users"); + insertObj:Insert("name", player:Nick()); + insertObj:Insert("rank", "user"); + insertObj:Insert("steam_id", player:SteamID()); + insertObj:Insert("last_played", os.time()); + insertObj:Execute(); + else + local updateObj = serverguard.mysql:Update("serverguard_users"); + updateObj:Update("name", player:Nick()); + updateObj:Update("rank", serverguard.player:GetRank(player)); + updateObj:Update("last_played", os.time()); + updateObj:Update("data", serverguard.von.serialize(player.sg_data)); + updateObj:Where("steam_id", player:SteamID()); + updateObj:Execute(); + end; +end; + +function serverguard.player:SaveField(player, field, value) + local updateObj = serverguard.mysql:Update("serverguard_users"); + updateObj:Update(string.lower(field), value); + updateObj:Where("steam_id", player:SteamID()); + updateObj:Execute(); +end; + +function serverguard.player:SetData(player, key, value) + if (not player.sg_data) then + player.sg_data = {} + end + + player.sg_data[key] = value; + + local updateObj = serverguard.mysql:Update("serverguard_users"); + updateObj:Update("data", serverguard.von.serialize(player.sg_data)); + updateObj:Where("steam_id", player:SteamID()); + updateObj:Execute(); +end; + +function serverguard.player:GetData(player, key, default) + if (player.sg_data and player.sg_data[key] != nil) then + return player.sg_data[key]; + end + + return default; +end; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/modules/sv_spectate.lua b/garrysmod/addons/admin-sg/lua/modules/sv_spectate.lua new file mode 100644 index 0000000..b7f4c56 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/modules/sv_spectate.lua @@ -0,0 +1,65 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--[[ + OBS_MODE_NONE - (number - 0) + OBS_MODE_IN_EYE - (number - 4) + OBS_MODE_DEATHCAM - (number - 1) + OBS_MODE_FIXED - (number - 3) + OBS_MODE_CHASE - (number - 5) + OBS_MODE_FREEZECAM - (number - 2) + OBS_MODE_ROAMING - (number - 6) + + + MOVETYPE_PUSH - (number - 7) + MOVETYPE_OBSERVER - (number - 10) + MOVETYPE_CUSTOM - (number - 11) + MOVETYPE_NOCLIP - (number - 8) + MOVETYPE_ISOMETRIC - (number - 1) + MOVETYPE_FLY - (number - 4) + MOVETYPE_NONE - (number - 0) + MOVETYPE_STEP - (number - 3) + MOVETYPE_VPHYSICS - (number - 6) + MOVETYPE_WALK - (number - 2) + MOVETYPE_LADDER - (number - 9) + MOVETYPE_FLYGRAVITY - (number - 5) +]] + +hook.Add("KeyPress", "serverguard.spectate.KeyPress", function(player, key) + if (player:Team() == TEAM_SPECTATOR and player.sg_spectating) then + if (key == IN_ATTACK) then + if (player:GetObserverMode() == OBS_MODE_CHASE or player:GetObserverMode() == OBS_MODE_IN_EYE) then + serverguard.player:SpectateTarget(player); + else + local target = serverguard.player:GetSpectatorTarget(player); + + if (IsValid(target)) then + player:SetPos(target:EyePos()) + end + end + end + + if (key == IN_ATTACK2) then + if (player:GetObserverMode() == OBS_MODE_CHASE or player:GetObserverMode() == OBS_MODE_IN_EYE) then + serverguard.player:SpectateTarget(player, true); + else + local target = serverguard.player:GetSpectatorTarget(player, true); + + if (IsValid(target)) then + player:SetPos(target:EyePos()) + end + end + end + + if (key == IN_JUMP) then + serverguard.player:ChangeSpectatorMode(player); + end + + if (key == IN_DUCK) then + serverguard.player:StopSpectate(player); + end + end +end) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/advertisement/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/advertisement/cl_init.lua new file mode 100644 index 0000000..f4e5158 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/advertisement/cl_init.lua @@ -0,0 +1,11 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT) + +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/advertisement/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/advertisement/cl_panel.lua new file mode 100644 index 0000000..2bc7268 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/advertisement/cl_panel.lua @@ -0,0 +1,254 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +surface.CreateFont("tiger.advertisement.background", {font = "Arial", size = 14, weight = 400, blursize = 2}); + +local plugin = plugin; + +local category = {}; + +category.name = "Adverts"; +category.material = "serverguard/menuicons/icon_advertisements.png"; +category.permissions = "Manage Advertisements"; + +category.advertisements = {}; + +local function NewAdvertisementWindow(title, buttonText, callback) + local basePanel = vgui.Create("tiger.panel"); + basePanel:SetTitle(title); + basePanel:SetSize(580, 440); + basePanel:Center(); + basePanel:MakePopup(); + basePanel:DockPadding(24, 24, 24, 48); + + local itemList = basePanel:Add("tiger.list"); + itemList:Dock(FILL); + + local textBox = vgui.Create("tiger.textentry"); + textBox:SetLabelText("Text to display"); + textBox:Dock(TOP); + itemList:AddPanel(textBox); + + local interval = vgui.Create("tiger.numslider"); + interval:SetText("Interval in seconds"); + interval:Dock(TOP); + interval:SetMinMax(10, 86400); + interval:SetValue(512); + itemList:AddPanel(interval); + + function interval:ValueChanged(value) + intervalValue = value; + end; + + local colorSlider = vgui.Create("DColorMixer"); + colorSlider:Dock(TOP); + colorSlider:DockMargin(0, 14, 0, 14); + itemList:AddPanel(colorSlider); + + local createButton = basePanel:Add("tiger.button"); + createButton:SetPos(4, 4); + createButton:SetText(buttonText); + createButton:SizeToContents(); + + function createButton:DoClick() + callback(textBox:GetValue(), (intervalValue or interval:GetValue()), colorSlider:GetColor()); + basePanel:Remove(); + end; + + local cancelButton = basePanel:Add("tiger.button"); + cancelButton:SetPos(4, 4); + cancelButton:SetText("Cancel"); + cancelButton:SizeToContents(); + + function cancelButton:DoClick() + basePanel:Remove(); + end; + + function basePanel:PerformLayout() + local width, height = self:GetSize(); + + createButton:SetPos(width - (createButton:GetWide() + 24), height - (createButton:GetTall() + 14)); + cancelButton:SetPos(0, height - (cancelButton:GetTall() + 14)); + cancelButton:MoveLeftOf(createButton, 14); + end; +end; + +local function EditAdvertisementWindow(title, buttonText, data, callback) + local basePanel = vgui.Create("tiger.panel"); + basePanel:SetTitle(title); + basePanel:SetSize(580, 440); + basePanel:Center(); + basePanel:MakePopup(); + basePanel:DockPadding(24, 24, 24, 48); + + local itemList = basePanel:Add("tiger.list"); + itemList:Dock(FILL); + + local textBox = vgui.Create("tiger.textentry"); + textBox:Dock(TOP); + textBox:SetLabelText("Text to display"); + textBox:SetText(data.text or ""); + itemList:AddPanel(textBox); + + local interval = vgui.Create("tiger.numslider"); + interval:Dock(TOP); + interval:SetText("Interval in seconds"); + interval:SetMinMax(10, 86400); + interval:SetValue(data.interval or 512); + itemList:AddPanel(interval); + + function interval:ValueChanged(value) + intervalValue = value; + end; + + local colorSlider = vgui.Create("DColorMixer"); + colorSlider:SetColor(data.color or Color(255, 0, 0, 255)); + colorSlider:Dock(TOP); + colorSlider:DockMargin(0, 14, 0, 14); + itemList:AddPanel(colorSlider); + + local createButton = basePanel:Add("tiger.button"); + createButton:SetPos(4, 4); + createButton:SetText(buttonText); + createButton:SizeToContents(); + + function createButton:DoClick() + callback(textBox:GetValue(), (intervalValue or interval:GetValue()), colorSlider:GetColor()); + basePanel:Remove(); + end; + + local cancelButton = basePanel:Add("tiger.button"); + cancelButton:SetPos(4, 4); + cancelButton:SetText("Cancel"); + cancelButton:SizeToContents(); + + function cancelButton:DoClick() + basePanel:Remove(); + end; + + function basePanel:PerformLayout() + local width, height = self:GetSize(); + + createButton:SetPos(width - (createButton:GetWide() + 24), height - (createButton:GetTall() + 14)); + cancelButton:SetPos(0, height - (cancelButton:GetTall() + 14)); + cancelButton:MoveLeftOf(createButton, 14); + end; +end; + +function category:Create(base) + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("Advertisements"); + base.panel:Dock(FILL); + base.panel:DockPadding(24, 24, 24, 48); + + category.list = base.panel:Add("tiger.list"); + category.list:Dock(FILL); + + category.list:AddColumn("ADVERTISEMENT", 500); + category.list:AddColumn("INTERVAL", 75); + + function category.list:Think() + if (category.nextUpdate and category.nextUpdate < CurTime()) then + category.list:Clear(); + + serverguard.netstream.Start("sgRequestAdvertisements", true); + + category.nextUpdate = nil; + category.advertisements = {}; + end; + end; + + category.nextUpdate = CurTime() + 0.3; + + local newAdvertisement = base.panel:Add("tiger.button"); + newAdvertisement:SetPos(4, 4); + newAdvertisement:SetText("Add new advertisement"); + newAdvertisement:SizeToContents(); + + function newAdvertisement:DoClick() + NewAdvertisementWindow("Add an advertisement", "Create", function(text, interval, color) + serverguard.netstream.Start("sgCreateAdvertisement", { + string.sub(text, 1, 256), math.Clamp(math.Round(tonumber(interval)), 10, 86400), color + }); + + category.list:Clear(); + category.advertisements = {}; + end); + end; + + function base.panel:PerformLayout() + local w, h = self:GetSize(); + + newAdvertisement:SetPos(w - (newAdvertisement:GetWide() + 24), h - (newAdvertisement:GetTall() + 14)); + end; +end; + +function category:Update(base) + category.nextUpdate = CurTime() + 0.3; +end; + +plugin:AddSubCategory("Server settings", category); + +-- +-- Receives the advertisement. +-- + +serverguard.netstream.Hook("sgGetAdvertisement", function(data) + local text = data[1]; + local interval = data[2]; + local color = data[3]; + local data = { + text = text, + interval = interval, + color = Color(color.r, color.g, color.b, 255) + }; + + local panel = category.list:AddItem(data.text, data.interval); + panel.id = table.insert(category.advertisements, data); + + local label = panel.labels[1]; + + function label:Paint(width, height) + surface.SetFont(label:GetFont()); + local textWidth, textHeight = surface.GetTextSize(label:GetText()); + + -- it's always going to be centered anyway + for i = 1, 4, 1 do + surface.SetDrawColor(0, 0, 0, 255); + surface.SetFont("tiger.advertisement.background"); + surface.SetTextPos(width / 2 - textWidth / 2, height / 2 - textHeight / 2); + surface.DrawText(self:GetText()); + end; + end; + + local textLabel = panel:GetLabel(1); + textLabel:SetColor(data.color); + textLabel.oldColor = data.color; + + function panel:OnMousePressed() + local menu = DermaMenu() + menu:SetSkin("serverguard"); + + menu:AddOption("Edit advertisement", function() + EditAdvertisementWindow("Modify an advertisement", "Modify", {text = data.text, interval = data.interval, color = data.color}, function(text, interval, color) + serverguard.netstream.Start("sgChangeAdvertisement", { + self.id, string.sub(text, 1, 256), math.Clamp(tonumber(interval), 10, 86400), color + }); + + category.list:Clear(); + category.advertisements = {}; + end); + end); + + menu:AddOption("Remove advertisement", function() + serverguard.netstream.Start("sgRemoveAdvertisement", self.id); + + category.list:Clear() + category.advertisements = {} + end); + menu:Open(); + end; +end); diff --git a/garrysmod/addons/admin-sg/lua/plugins/advertisement/init.lua b/garrysmod/addons/admin-sg/lua/plugins/advertisement/init.lua new file mode 100644 index 0000000..5541c2b --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/advertisement/init.lua @@ -0,0 +1,155 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard.AddFolder("advertisements"); + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +local advertisements = {}; + +local function SAVE_ADVERTISEMENTS() + file.Write("serverguard/advertisements/advertisements.txt", serverguard.von.serialize(advertisements), "DATA"); +end; + +plugin:Hook("InitPostEntity", "advertisement.InitPostEntity", function() + local data = file.Read("serverguard/advertisements/advertisements.txt", "DATA") + + if (data) then + local advertisementList = serverguard.von.deserialize(data) + + -- backwards compatibility with old advertisement format + for k, v in pairs(advertisementList) do + if (type(v) == "table") then + advertisements[#advertisements + 1] = v + continue; + end + + advertisements[#advertisements + 1] = { + text = v, + interval = 120, + color = Color(200, 30, 30, 255) + } + end + else + file.Write("serverguard/advertisements/advertisements.txt", serverguard.von.serialize(advertisements), "DATA") + end +end) + +plugin:Hook("Tick", "advertisement.Tick", function() + for k, v in pairs(advertisements) do + if (!v.nextTime or CurTime() >= v.nextTime) then + if (hook.Call("serverguard.advertisement.Show", nil, v)) then continue; end; + + util.PrintAllColor(v.color, v.text) + + v.nextTime = CurTime() + v.interval + end + end +end) + +-- +-- Sends the advertisements to the player. +-- + +serverguard.netstream.Hook("sgRequestAdvertisements", function(player, data) + if (serverguard.player:HasPermission(player, "Manage Advertisements")) then + for k, v in ipairs(advertisements) do + serverguard.netstream.Start(player, "sgGetAdvertisement", { + v.text, v.interval, v.color + }); + end; + end; +end); + +-- +-- Creates an advertisement. +-- + +serverguard.netstream.Hook("sgCreateAdvertisement", function(player, data) + if (serverguard.player:HasPermission(player, "Manage Advertisements")) then + local text = string.sub(data[1], 1, 256) + local interval = math.Clamp(data[2], 10, 86400) + local color = data[3]; + + table.insert(advertisements, { + text = text, + interval = interval, + color = Color(color.r, color.g, color.b, 255) + }); + + serverguard.Notify(player, SERVERGUARD.NOTIFY.GREEN, "Added advertisement \"" .. text .. "\"."); + + for k, v in ipairs(advertisements) do + serverguard.netstream.Start(player, "sgGetAdvertisement", { + v.text, v.interval, v.color + }); + end; + + SAVE_ADVERTISEMENTS(); + end; +end); + +-- +-- Changes or creates an advertisement. +-- + +serverguard.netstream.Hook("sgChangeAdvertisement", function(player, data) + if (serverguard.player:HasPermission(player, "Manage Advertisements")) then + local id = data[1]; + local text = string.sub(data[2], 1, 256); + local interval = math.Round(math.Clamp(data[3], 10, 86400)); + local color = data[4]; + + if (id > 0) then + advertisements[id] = { + text = text, + interval = interval, + color = Color(color.r, color.g, color.b, 255) + }; + + serverguard.Notify(player, SERVERGUARD.NOTIFY.GREEN, "Changed advertisement to \"" .. text .. "\"."); + end; + + for k, v in ipairs(advertisements) do + serverguard.netstream.Start(player, "sgGetAdvertisement", { + v.text, v.interval, v.color + }); + end + + SAVE_ADVERTISEMENTS(); + end; +end); + +-- +-- Removes an advertisement. +-- + +serverguard.netstream.Hook("sgRemoveAdvertisement", function(player, data) + if (serverguard.player:HasPermission(player, "Manage Advertisements")) then + local id = data; + + if (id) then + local data = advertisements[id]; + + if (data) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "Removed advertisement \"" .. data.text .. "\"."); + + table.remove(advertisements, id); + + for k, v in ipairs(advertisements) do + serverguard.netstream.Start(player, "sgGetAdvertisement", { + v.text, v.interval, v.color + }); + end; + + SAVE_ADVERTISEMENTS(); + end; + end; + end; +end); diff --git a/garrysmod/addons/admin-sg/lua/plugins/advertisement/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/advertisement/shared.lua new file mode 100644 index 0000000..6317c58 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/advertisement/shared.lua @@ -0,0 +1,13 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Advertisement"; +plugin.author = "`impulse"; +plugin.version = "1.0"; +plugin.description = "Advertisement messages in chat."; +plugin.permissions = {"Manage Advertisements"}; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/analytics/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/analytics/cl_init.lua new file mode 100644 index 0000000..de940f6 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/analytics/cl_init.lua @@ -0,0 +1,9 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); diff --git a/garrysmod/addons/admin-sg/lua/plugins/analytics/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/analytics/cl_panel.lua new file mode 100644 index 0000000..77f9e32 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/analytics/cl_panel.lua @@ -0,0 +1,215 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.stored = plugin.stored or {}; + +local months = { + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" +} + +local chartHTML = [[ + + + + + + + + +
+ + +]]; + +local category = {}; + +category.name = "Analytics"; +category.material = "serverguard/menuicons/icon_analytics.png"; +category.permissions = "Analytics"; + +function category:Create(base) + self.stepping = 10; + + base.panel = base:Add("tiger.panel"); + base.panel:DockPadding(4, 4, 4, 4); + base.panel:Dock(FILL); + + category.chart = base.panel:Add("DHTML"); + category.chart:SetHTML(chartHTML); + category.chart:Dock(FILL); + + function category.chart:ClearData() -- sometimes these are called at weird times + self:RunJavascript("if (typeof clearAnalyticsData === \"function\"){ clearAnalyticsData(); }"); + end; + + function category.chart:SetData(month, total, unique) + self:RunJavascript("if (typeof setAnalyticsData === \"function\"){ setAnalyticsData('" .. month .. "', " .. total .. ", " .. unique .. "); }"); + end; + + function category.chart:Refresh() + local theme = serverguard.themes.GetCurrent(); + + self:RunJavascript([[ + if (typeof drawChart === "function") + { + background_color = "rgb(]] .. util.ColorString(theme.tiger_panel_bg, true) .. [[)"; + text_color = "rgb(]] .. util.ColorString(theme.tiger_panel_label, true) .. [[)"; + + drawChart(); + } + ]]); + end; + + function category.chart:OnTigerThemeChanged() + self:Refresh(); + end; + + serverguard.themes.AddPanel(category.chart, "tiger_panel_bg"); +end; + +function category:Update(base) + if (serverguard.player:HasPermission(LocalPlayer(), "Analytics")) then + serverguard.netstream.Start("sgRequestAnalytics", true); + end; +end; + +plugin:AddSubCategory("Information", category); + +serverguard.netstream.Hook("sgSendAnalytics", function(data) + if (category and category.chart) then + category.chart:ClearData(); + + for k, v in pairs(data) do + if (months[k]) then + category.chart:SetData(months[util.ToNumber(k)], util.ToNumber(v.total or 0), util.ToNumber(v.uniques or 0)); + end; + end; + + category.chart:Refresh(); + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/analytics/init.lua b/garrysmod/addons/admin-sg/lua/plugins/analytics/init.lua new file mode 100644 index 0000000..3a9b353 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/analytics/init.lua @@ -0,0 +1,99 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); + +local visits = {}; + +plugin:Hook("serverguard.mysql.CreateTables", "analytics.CreateTables", function() + local ANALYTICS_TABLE_QUERY = serverguard.mysql:Create("serverguard_analytics"); + ANALYTICS_TABLE_QUERY:Create("id", "INTEGER NOT NULL AUTO_INCREMENT"); + ANALYTICS_TABLE_QUERY:Create("date", "VARCHAR(10) NOT NULL"); + ANALYTICS_TABLE_QUERY:Create("data", "TEXT NOT NULL"); + ANALYTICS_TABLE_QUERY:PrimaryKey("id"); + ANALYTICS_TABLE_QUERY:Execute(); +end); + + +plugin:Hook("serverguard.mysql.OnConnected", "analytics.Initialize", function() + local queryObj = serverguard.mysql:Select("serverguard_analytics"); + queryObj:Select("date"); + queryObj:Select("data"); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + for k, v in pairs(result) do + local dateTable = string.Explode("-", v.date); + local day = tonumber(dateTable[1]); + local month = tonumber(dateTable[2]); + local year = tonumber(dateTable[3]); + + if (year == util.ToNumber(os.date("%Y", os.time()))) then -- get only the analytics for this year + visits[month] = visits[month] or {}; + visits[month][day] = util.JSONToTable(v.data); + end; + end; + end; + end); + queryObj:Execute(); +end); + +plugin:Hook("PlayerInitialSpawn", "analytics.PlayerInitialSpawn", function(player) + local day = tonumber(os.date("%d", os.time())); + local month = tonumber(os.date("%m", os.time())); + local year = tonumber(os.date("%Y", os.time())); + local steamID = player:SteamID(); + + visits[month] = visits[month] or {}; + visits[month][day] = visits[month][day] or {0, {}}; + visits[month][day][1] = visits[month][day][1] + 1; + + if (!table.HasValue(visits[month][day][2], steamID)) then + table.insert(visits[month][day][2], steamID); + end; + + local queryObj = serverguard.mysql:Select("serverguard_analytics"); + queryObj:Select("id"); + queryObj:Where("date", day.."-"..month.."-"..year); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + local updateObj = serverguard.mysql:Update("serverguard_analytics"); + updateObj:Update("data", util.TableToJSON(visits[month][day])); + updateObj:Where("date", day.."-"..month.."-"..year); + updateObj:Execute(); + else + local insertObj = serverguard.mysql:Insert("serverguard_analytics"); + insertObj:Insert("date", day.."-"..month.."-"..year); + insertObj:Insert("data", util.TableToJSON(visits[month][day])); + insertObj:Execute(); + end; + end); + queryObj:Execute(); +end); + +serverguard.netstream.Hook("sgRequestAnalytics", function(player, data) + if (serverguard.player:HasPermission(player, "Analytics")) then + local analyticData = {}; + + for month, monthData in pairs(visits) do + local total = 0; + local uniques = 0; + + for day, dayData in pairs(monthData) do + total = total + dayData[1]; + uniques = uniques + table.Count(dayData[2]); + end; + + analyticData[month] = { + total = total, + uniques = uniques + }; + end; + + serverguard.netstream.Start(player, "sgSendAnalytics", analyticData); + end +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/analytics/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/analytics/shared.lua new file mode 100644 index 0000000..e85c190 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/analytics/shared.lua @@ -0,0 +1,15 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Analytics"; +plugin.author = "alexgrist"; +plugin.version = "1.1"; +plugin.description = "Provides statistics of users that join the server."; +plugin.permissions = {"Analytics"}; + +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); diff --git a/garrysmod/addons/admin-sg/lua/plugins/cmenu/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/cmenu/cl_init.lua new file mode 100644 index 0000000..4e507e6 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/cmenu/cl_init.lua @@ -0,0 +1,2 @@ +local plugin = plugin; +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); diff --git a/garrysmod/addons/admin-sg/lua/plugins/cmenu/init.lua b/garrysmod/addons/admin-sg/lua/plugins/cmenu/init.lua new file mode 100644 index 0000000..3126c4e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/cmenu/init.lua @@ -0,0 +1,2 @@ +local plugin = plugin; +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); diff --git a/garrysmod/addons/admin-sg/lua/plugins/cmenu/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/cmenu/shared.lua new file mode 100644 index 0000000..41a626f --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/cmenu/shared.lua @@ -0,0 +1,136 @@ +local plugin = plugin; + +plugin.name = "Context menu"; +plugin.author = "chelog"; +plugin.version = "1.0"; +plugin.description = "Context menu -> RMB. By chelog."; +plugin.gamemodes = {"darkrp"}; +plugin.permissions = {}; + +local function playTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format("%02i:%02i:%02i", h, m, s) + +end + +properties.Add( "sg", { + MenuLabel = L.admin_hint, + Order = 999, + MenuIcon = "icon16/wand.png", + PrependSpacer = true, + + Filter = function( self, ent, ply ) + if not IsValid(ent) then return false end + if not ent:IsPlayer() then return false end + if not LocalPlayer():IsAdmin() then return false end + + return !ent:IsOnFire() + end, + Action = function( self, ent ) + + timer.Simple(0, function() + local rankData = serverguard.ranks:GetRank(serverguard.player:GetRank(ply)) + local commands = serverguard.command:GetTable() + local rankData2 = serverguard.ranks:GetRank(serverguard.player:GetRank(v)) + + local steamID = ent:SteamID() + if steamID == 'NULL' then steamID = 'BOT' end + + gui.EnableScreenClicker( true ) + local menu = DermaMenu() + menu:SetSkin('serverguard') + + -- rank + menu:AddOption(L.rank_hint .. rankData2.name,function() + SetClipboardText(rankData2.name); + end):SetIcon(rankData2.texture) + + menu:AddSpacer() + + -- time + menu:AddOption('Наиграно здесь: ' .. playTime(ent:GetTimeHere()),function() + SetClipboardText(playTime(ent:GetTimeHere())) + end):SetIcon('icon16/clock.png') + menu:AddOption('Наиграно всего: ' .. playTime(ent:GetTimeTotal()),function() + SetClipboardText(playTime(ent:GetTimeTotal())) + end):SetIcon('icon16/clock.png') + + menu:AddSpacer() + + -- steamid + menu:AddOption(L.copy_steamid, function() + SetClipboardText(steamID) + end):SetIcon('icon16/page_copy.png') + + -- return + menu:AddOption('Return', function() + RunConsoleCommand('sgs', 'return', steamID) + end):SetIcon('icon16/wand.png') + + -- teleport to admin zone + menu:AddOption('To admin zone', function() + RunConsoleCommand('sgs', 'adminzone', steamID) + end):SetIcon('icon16/wand.png') + + -- hp 100 + menu:AddOption('100HP', function() + RunConsoleCommand('sgs', 'hp', steamID, '100') + end):SetIcon('icon16/wand.png') + + -- slay + menu:AddOption('Slay', function() + RunConsoleCommand('sgs', 'slay', steamID) + end):SetIcon('icon16/wand.png') + + -- respawn + menu:AddOption('Respawn', function() + netstream.Start('chat', '/spawn ' .. ent:Name()) + end):SetIcon('icon16/wand.png') + + local sm, pmo = menu:AddSubMenu("Mute") + pmo:SetSkin("serverguard") + pmo:SetIcon("icon16/wand.png") + sm:AddOption(L.enable, function() + RunConsoleCommand("sg", "mute", steamID) + end) + sm:AddOption(L.disable2, function() + RunConsoleCommand("sg", "unmute", steamID) + end) + + local sm, pmo = menu:AddSubMenu("Gag") + pmo:SetSkin("serverguard") + pmo:SetIcon("icon16/wand.png") + sm:AddOption(L.enable, function() + RunConsoleCommand("sg", "gag", steamID) + end) + sm:AddOption(L.disable2, function() + RunConsoleCommand("sg", "ungag", steamID) + end) + + -- kick + menu:AddOption('Kick', function() + Derma_StringRequest(L.reason_kick, L.reason_kick_hint, '', function(s) + RunConsoleCommand('sg', 'kick', steamID, s) + end, function() end, L.ok, L.cancel) + end):SetIcon('icon16/delete.png') + + -- ban + menu:AddOption('Ban', function() + Derma_StringRequest(L.length_ban, L.length_ban_hint, '', function(s1) + Derma_StringRequest(L.reason_ban, L.reason_ban_hint, '', function(s2) + RunConsoleCommand('sg', 'ban', steamID, s1, s2) + end, function() end, L.ok, L.cancel) + end, function() end, L.ok, L.cancel) + end):SetIcon('icon16/delete.png') + + menu:Open() + gui.EnableScreenClicker( false ) + end) + + end +}) diff --git a/garrysmod/addons/admin-sg/lua/plugins/dobrograd/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/cl_init.lua new file mode 100644 index 0000000..b749e90 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/cl_init.lua @@ -0,0 +1,69 @@ +local plugin = plugin; + +plugin:IncludeFile('shared.lua', SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile('sh_commands.lua', SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile('sh_drp_commands.lua', SERVERGUARD.STATE.CLIENT); + +local custom = {} + +local function clean() + custom = {} + for i = #L.warns_list, 1, -1 do + local warn = L.warns_list[i] + if warn[4] == 'custom' then + L.warns_list[i] = nil + else break end + end + +end + +clean() + +local data = file.Read('dbg_admintells.dat') +if data then data = pon.decode(data) end +if data then + for i, warn in ipairs(data) do + L.warns_list[#L.warns_list + 1] = warn + custom[#custom + 1] = warn + end +end + +function serverguard.editAdminTell() + + octolib.entries.gui('Настройка уведомлений', { + fields = { + { + name = 'Название', + type = 'strShort', + len = 32, + default = 'Новое уведомление', + }, + { + name = 'Длительность (сек)', + type = 'numSlider', + min = 5, + max = 90, + dec = 0, + default = 15, + }, + { + name = 'Текст сообщения', + type = 'strLong', + len = 512, + tall = 215, + }, + }, + labelIndex = 1, + entries = custom, + maxEntries = 25, + }, function(res) + clean() + for _,v in ipairs(res) do + v[4] = 'custom' + L.warns_list[#L.warns_list + 1] = v + end + custom = res + file.Write('dbg_admintells.dat', pon.encode(res)) + end) + +end diff --git a/garrysmod/addons/admin-sg/lua/plugins/dobrograd/init.lua b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/init.lua new file mode 100644 index 0000000..be5d235 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/init.lua @@ -0,0 +1,146 @@ +local plugin = plugin; + +plugin:IncludeFile('shared.lua', SERVERGUARD.STATE.SHARED); +plugin:IncludeFile('sh_commands.lua', SERVERGUARD.STATE.SHARED); +plugin:IncludeFile('sh_drp_commands.lua', SERVERGUARD.STATE.SHARED); + +-- temp stuff to allow Niteko spawning car seats +local temp = { + ['STEAM_0:1:57045688'] = true, +} + +hook.Add('PlayerGiveSWEP', 'dbg.restrictSpawn', function(ply, class, ent) + return serverguard.player:HasPermission(ply, 'DBG: SpawnSWEP') == true +end) + +hook.Add('PlayerSpawnSWEP', 'dbg.restrictSpawn', function(ply, class, ent) + return serverguard.player:HasPermission(ply, 'DBG: SpawnSWEP') == true +end) + +hook.Add('PlayerSpawnNPC', 'dbg.restrictSpawn', function(ply, npc, wep) + return serverguard.player:HasPermission(ply, 'DBG: SpawnNPC') == true +end) + +hook.Add('PlayerSpawnVehicle', 'dbg.restrictSpawn', function(ply, mdl, name, tbl) + if tbl.Class == 'gmod_sent_vehicle_fphysics_base' then + return serverguard.player:HasPermission(ply, 'DBG: SpawnSimfphys') == true or ply:IsSuperAdmin() + end + return serverguard.player:HasPermission(ply, 'DBG: SpawnVehicle') == true or temp[ply:SteamID()] == true +end) + +hook.Add('PlayerSpawnRagdoll', 'dbg.restrictSpawn', function(ply, model) + return serverguard.player:HasPermission(ply, 'DBG: SpawnRagdoll') == true +end) + +local wireClasses, traceData = {}, { + ["AllSolid"] = false, + ["Entity"] = Entity(0), + ["Fraction"] = 0.5, + ["FractionLeftSolid"] = 0, + ["Hit"] = true, + ["HitBox"] = 0, + ["HitGroup"] = 0, + ["HitNoDraw"] = false, + ["HitNonWorld"] = false, + ["HitNormal"] = Vector(0,0,1), + ["HitPos"] = Vector(0,0,0), + ["HitSky"] = false, + ["HitTexture"] = "**displacement**", + ["HitWorld"] = true, + ["MatType"] = 67, + ["Normal"] = Vector(0,0,1), + ["PhysicsBone"] = 0, + ["StartPos"] = Vector(0,0,0), + ["StartSolid"] = false, + ["SurfaceProps"] = 30, +} + +hook.Add('PostGamemodeLoaded', 'dbg.restrictSpawn', function() + + for tool, t in pairs(weapons.GetStored('gmod_tool').Tool) do + if t.WireClass then wireClasses[t.WireClass] = tool end + end + +end) + +hook.Add('PlayerSpawnSENT', 'dbg.restrictSpawn', function(ply, class) + + local tool = wireClasses[class] + if tool then + local override = hook.Run('CanTool', ply, traceData, tool) + if override ~= nil then + return override + else + return true + end + end + + return serverguard.player:HasPermission(ply, 'DBG: SpawnSENT') == true + +end) + +local buildTools = { + ballsocket = true, + axis = true, + elastic = true, + hydraulic = true, + rope = true, + winch = true, + wire_hydraulic = true, + imgscreen = true, + -- particlecontrol_builder = true, +} + +local storyTools = { + octo_desc = true, + octo_trigger = true, + lookable = true, +} + +hook.Add('sg.tool-override', 'os-tools', function(ply, trace, tool, ENT) + + if not IsValid(ply) then return end + tool = tool or '' + if buildTools[tool] and ply:GetNetVar('os_build') then return true end + if storyTools[tool] and ply:GetNetVar('os_story') then return true end + +end) + +local constraintTools = { + AdvBallsocket = 'ballsocket', + AttachParticleControllerBeam = 'particlecontrol', + Axis = 'axis', + Ballsocket = 'ballsocket', + Elastic = 'elastic', + Hydraulic = 'hydraulic', + Motor = 'motor', + Muscle = 'muscle', + NoCollide = 'nocollide', + Pulley = 'pulley', + Rope = 'rope', + Slider = 'slider', + Weld = 'weld', + Winch = 'winch', +} + +for id, data in pairs(duplicator.ConstraintType) do + data.Tool = constraintTools[id] +end + +hook.Add('octolib.dbvars-loaded', 'dbg-admin.crimeAndPolice', function(ply) + local ot, ct = os.time(), CurTime() + local noCrime, noPolice = ply:GetDBVar('nocrime', 0), ply:GetDBVar('nopolice', 0) + + if noCrime == true then + ply:SetNetVar('nocrime', true) + elseif noCrime > ot then + ply:SetNetVar('nocrime', ct + (noCrime - ot)) + else ply:SetDBVar('nocrime', nil) end + + if noPolice == true then + ply:SetNetVar('nopolice', true) + elseif noPolice > ot then + ply:SetNetVar('nopolice', ct + (noPolice - ot)) + else ply:SetDBVar('nopolice', nil) end + +end) diff --git a/garrysmod/addons/admin-sg/lua/plugins/dobrograd/sh_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/sh_commands.lua new file mode 100644 index 0000000..ca7ff5a --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/sh_commands.lua @@ -0,0 +1,177 @@ +-- +-- The respawn command. +-- + +local command = {}; + +command.help = "Respawn a player."; +command.command = "respawn_g"; +command.arguments = {"player"}; +command.permissions = "Respawn Ghost"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target, arguments) + if target:Alive() then target:KillSilent() end + target:SetNetVar( "_SpawnTime", CurTime() ) + + -- Needed for TTT. + if (target.SpawnForRound) then + target:SpawnForRound(); + + -- Remove their corpse. + if (IsValid(target.server_ragdoll)) then + target.server_ragdoll:Remove(); + end; + end; + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + + return SGPF("command_respawn", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets), amount); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Respawn Ghost", function() + serverguard.command.Run("respawn_g", false, pPlayer:Name()); + end); + + option:SetImage("icon16/user_go.png"); +end; + +serverguard.command:Add(command); + +local command = {}; + +command.help = L.retest; +command.command = "retest"; +command.arguments = {"player"}; +command.permissions = "Retest"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target, arguments) + dbgTest.reset(target:SteamID()) + target:Kick(L.retest_hint) + + return true; +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Retest", function() + serverguard.command.Run("retest", false, pPlayer:Name()); + end); + + option:SetImage("icon16/delete.png"); +end; + +serverguard.command:Add(command); + +local command = {}; + +command.help = 'Отключить говорилку'; +command.command = 'goomute'; +command.arguments = {'player'}; +command.permissions = 'Gag'; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(_, target) + target:SetNetVar('govorilka_mute', true) + target:SetDBVar('govorilka_mute', true) + return true; +end; + +serverguard.command:Add(command); + +local command = {}; + +command.help = 'Включить говорилку'; +command.command = 'goounmute'; +command.arguments = {'player'}; +command.permissions = 'Ungag'; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(_, target) + target:SetNetVar('govorilka_mute') + target:SetDBVar('govorilka_mute') + return true; +end; + +serverguard.command:Add(command); + +local servers = { + pt_dbg = 'Центральный', + pt_dbg2 = 'Новый', +} + +local function playTime(time) + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + return string.format('%02i:%02i:%02i', h, m, s) +end + +local command = {} +command.help = 'Get play time on servers' +command.command = 'ptime' +command.arguments = {'steamid'} +command.permissions = 'Get play time' +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(admin, silent, args) + if SERVER then + local steamID = args[1] + if not octolib.string.isSteamID(steamID) then return end + + local doNotify + if IsValid(admin) then doNotify = function(msg) admin:Notify(msg) end + else doNotify = function(msg) octolib.msg(msg) end end + + local doNotify + if IsValid(admin) then doNotify = function(msg) admin:Notify(msg) end + else doNotify = function(msg) octolib.msg(msg) end end + + octolib.getDBVar(steamID, 'pt'):Then(function(var) + doNotify('===== ' .. steamID .. ' =====') + if not var then + doNotify('Игрок не появлялся на Доброградах') + return + end + doNotify('Общее время: ' .. playTime(var)) + for serverID, serverName in pairs(servers) do + octolib.getDBVar(steamID, serverID):Then(function(tp) + if tp then + doNotify(serverName .. ': ' .. playTime(tp)) + else + doNotify(serverName .. ': Не играл') + end + end):Catch(function() + doNotify(serverName .. ': Не играл') + end) + end + end):Catch(function() + doNotify('Данные не найдены') + end) + end + + return true +end + +serverguard.command:Add(command) + +if CFG.dev then + local command = {}; + + command.help = 'Рестарт карты'; + command.command = 'dev-restart'; + command.arguments = {}; + + function command:Execute(ply, silent, arguments) + if player.GetCount() > 1 then return end + RunConsoleCommand('_restart') + end; + + serverguard.command:Add(command); +end diff --git a/garrysmod/addons/admin-sg/lua/plugins/dobrograd/sh_drp_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/sh_drp_commands.lua new file mode 100644 index 0000000..7d39185 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/sh_drp_commands.lua @@ -0,0 +1,623 @@ +local function getSteamID(ply, str) + local target = util.FindPlayer(str) + if not IsValid(target) then + target = str + if not octolib.string.isSteamID(target) then + if IsValid(ply) then ply:Notify('warning', 'Укажи никнейм или SteamID игрока') + else octolib.msg('Укажи никнейм или SteamID игрока') end + return + end + else target = target:SteamID() end + return target +end + +local command = {} + +command.help = "Set a player's name." +command.command = "name" +command.arguments = {"player", "name"} +command.permissions = "Set Player Name" +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(_, target, arguments) + target:SetName(arguments[2]) + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Set a player's hunger." +command.command = "hunger" +command.arguments = {"player", "hunger"} +command.permissions = 'DBG: Изменять голод' +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(_, target, arguments) + local hunger = tonumber(arguments[2]) + target:SetLocalVar("Energy", math.Clamp(hunger or 100, 0, 100)) + + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Set a player's model." +command.command = "setmodel" +command.arguments = {"player", "model"} +command.permissions = "Set model" +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(_, target, arguments) + local model = tostring(arguments[2]) + local bg = arguments[3] + if IsUselessModel(model) then return false end + + target:SetModel(model) + if bg then target:SetBodyGroups(bg) end + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Set a player's job name." +command.command = "jobname" +command.arguments = {"player", "job_name"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(player, target, arguments) + + local name = arguments[2] + + if not name or not isstring(name) then + + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format(L.print_job_name, player:Nick())) + return true + + else + target:updateJob(name) + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, player:Name(), SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, " job name to ", SERVERGUARD.NOTIFY.GREEN, name, SERVERGUARD.NOTIFY.WHITE, ".") + end + + return true + +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Set a player's job" +command.command = "job" +command.arguments = {"player", "job name"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(player, target, arguments) + + local name = arguments[2] + if not name or not isstring(name) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format(L.print_job_name_or_nick, player:Nick())) + return true + else + for k, v in ipairs(RPExtraTeams) do + if string.find(team.GetName(k):lower(), name:lower(), 1, false) or v.command == name:lower() then + -- if target:Team() == k then + -- serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format("%s, игрок уже в этой профессии.", player:Nick())) + -- return true + -- end + + if v.police then + target:SetNetVar('dbg-police.job', v.command) + else + target:changeTeam(k, true) + target:SetNetVar('dbg-police.job', nil) + end + + break + end + end + end + + return true +end + +serverguard.command:Add(command) + +-- BROKEN. TODO: FIX + +-- local command = {} + +-- command.help = "Ban player from the job" +-- command.command = "teamban" +-- command.arguments = {"player", "job name", "time"} +-- command.permissions = L.permissions_admin_commands +-- command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +-- function command:OnPlayerExecute(player, target, arguments) + +-- local name = arguments[2] + +-- if not name or not isstring(name) then + +-- serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format(L.print_job_name_or_nick, player:Nick())) +-- return true + +-- else + +-- local time = util.ToNumber(arguments[3]) + +-- for k, v in ipairs(RPExtraTeams) do + +-- if string.find( team.GetName(k):lower(), name:lower(), 1, false ) then + +-- target:teamBan(k, time) + +-- player:Notify(L.you_ban_job_time:format(target:GetName(), target:SteamID(), team.GetName(k), string.ToMinutesSeconds(time))) + +-- break + +-- end + +-- end +-- end + +-- return true +-- end + +-- serverguard.command:Add(command) + +-- local command = {} + +-- command.help = "Unban player from the job" +-- command.command = "teamunban" +-- command.arguments = {"player", "job name"} +-- command.permissions = L.permissions_admin_commands +-- command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +-- function command:OnPlayerExecute(player, target, arguments) + +-- local name = arguments[2] + +-- if not name or not isstring(name) then + +-- serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, string.format(L.print_job_name_or_nick, player:Nick())) +-- return true + +-- else + +-- for k, v in ipairs(RPExtraTeams) do + +-- if string.find( team.GetName(k):lower(), name:lower(), 1, false ) then + +-- target:teamUnBan(k) + +-- player:Notify('rp', 'Теперь %s может занимать профессию "%s"'):format(target:GetName(), team.GetName(k)) + +-- break + +-- end + +-- end +-- end + +-- return true +-- end + +-- serverguard.command:Add(command) + +local command = {} + +command.help = "Arrest player" +command.command = "arrest" +command.arguments = {"player", "length"} +command.optionalArguments = {"reason"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(ply, _, arguments) + + local target = getSteamID(ply, arguments[1]) + if not target then return end + + local targetPly = player.GetBySteamID(target) + + if IsValid(targetPly) and targetPly:isArrested() then + if IsValid(ply) then + ply:Notify('warning', 'Игрок уже арестован') + else + octolib.msg('Игрок уже арестован') + end + return + end + + local time = util.ParseDuration(arguments[2]) + if time < 0 then return end + time = time * 60 + + octolib.setDBVar(target, 'arrest', {arguments[3] or nil, time}):Then(function() + if IsValid(targetPly) then + targetPly:arrest(time, ply, arguments[3] or nil, true) + end + if IsValid(ply) then ply:Notify('warning', 'Игрок арестован') else octolib.msg('Игрок арестован') end + end) + + + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Unarrest player" +command.command = "unarrest" +command.arguments = {"player"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(ply, _, arguments) + + local target = getSteamID(ply, arguments[1]) + if not target then return end + + octolib.setDBVar(target, 'arrest', nil):Then(function() + local targetPly = player.GetBySteamID(target) + if IsValid(targetPly) then + targetPly:unArrest() + end + if IsValid(ply) then ply:Notify('hint', 'Игрок освобожден') else octolib.msg('Игрок освобожден') end + end) + + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Add money to player" +command.command = "addmoney" +command.arguments = {"player", "amount"} +command.permissions = L.permissions_superadmin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(ply, target, arguments) + local amount = util.ToNumber(arguments[2]) + target:addMoney(math.max(target:GetNetVar("money")*-1, amount)) + + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Sell door you'r looking at" +command.command = "selldoor" +command.arguments = {"player"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(ply, target, arguments) + local trent = target:GetEyeTrace().Entity + if not trent:IsDoor() then return end + local owner = trent:GetPlayerOwner() + trent:RemoveOwner(owner) + + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Sell all doors" +command.command = "sellalldoors" +command.arguments = {"player"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(ply, target, arguments) + local price, amount = target:UnownAllDoors() + if IsValid(ply) then + ply:Notify(('%s помещени%s игрока продано за %s'):format(amount, octolib.string.formatCount(amount, 'е', 'я', 'й'), DarkRP.formatMoney(price))) + end + return true +end + +serverguard.command:Add(command) + +local command = {} + +command.help = 'Телепорт в админ зону'; +command.command = 'adminzone'; +command.arguments = {"player"}; +command.permissions = 'Goto'; + +local maps = { + truenorth = Vector(-11643.262695, 2303.640625, 7746.319336), + evocity = Vector(-15921.785156, 15925.755859, 4703.173340), + eastcoast = Vector(-3508.420166, 2497.798584, -1175.5), + riverden = Vector(-15946, 15915, 6840), +} + +function command:OnPlayerExecute(ply, target, arguments) + for map, pos in pairs(maps) do + if game.GetMap():find(map) then + target.sg_LastPosition = target:GetPos() + target.sg_LastAngles = target:EyeAngles() + target:SetPos(pos) + end + end + + return true; +end; + +serverguard.command:Add(command); + +local command = {} + +command.help = "Add karma" +command.command = "addkarma" +command.arguments = {"player", "amount"} +command.permissions = L.permissions_edit_karma +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(ply, target, arguments) + local amount = math.floor(util.ToNumber(arguments[2])) + target:AddKarma(amount, L.edit_karma:format(octolib.string.signed(amount), ply:Name()), true, true) + + return true +end + +serverguard.command:Add(command) + +local command = {} + +if SERVER then + netvars.Register('nocrime', { + checkAccess = DarkRP.isAdmin, + }) +end + +command.help = "Deny crime roleplay" +command.command = "denycrime" +command.arguments = {"player", "length"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(ply, _, arguments) + + local target = getSteamID(ply, arguments[1]) + if not target then return end + + local time = util.ParseDuration(arguments[2]) + if time < 0 then return end + time = time * 60 + + local val + if time == 0 then val = true else val = os.time() + time end + octolib.setDBVar(target, 'nocrime', val):Then(function() + local targetPly = player.GetBySteamID(target) + if IsValid(targetPly) then + targetPly:SetNetVar('nocrime', val == true or (CurTime() + (val - os.time()))) + end + if IsValid(ply) then ply:Notify('Запрет установлен') else octolib.msg('Запрет установлен') end + end) + return true + +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Allow crime roleplay" +command.command = "allowcrime" +command.arguments = {"player"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(ply, target, arguments) + + local target = getSteamID(ply, arguments[1]) + if not target then return end + + octolib.setDBVar(target, 'nocrime', nil):Then(function() + local targetPly = player.GetBySteamID(target) + if IsValid(targetPly) then + targetPly:SetNetVar('nocrime', nil) + end + if IsValid(ply) then ply:Notify('Запрет снят') else octolib.msg('Запрет снят') end + end) + return true + +end + +serverguard.command:Add(command) + +local command = {} + +if SERVER then + netvars.Register('nopolice', { + checkAccess = DarkRP.isAdmin, + }) +end + +command.help = "Deny police roleplay" +command.command = "denypolice" +command.arguments = {"player", "length"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(ply, _, arguments) + + local target = getSteamID(ply, arguments[1]) + if not target then return end + + local time = util.ParseDuration(arguments[2]) + if time < 0 then return end + time = time * 60 + + local val + if time == 0 then val = true else val = os.time() + time end + octolib.setDBVar(target, 'nopolice', val):Then(function() + local targetPly = player.GetBySteamID(target) + if IsValid(targetPly) then + targetPly:SetNetVar('nopolice', val == true or (CurTime() + (val - os.time()))) + end + if IsValid(ply) then ply:Notify('Запрет установлен') else octolib.msg('Запрет установлен') end + end) + return true + +end + +serverguard.command:Add(command) + +local command = {} + +command.help = "Allow police roleplay" +command.command = "allowpolice" +command.arguments = {"player"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(ply, _, arguments) + + local target = getSteamID(ply, arguments[1]) + if not target then return end + + octolib.setDBVar(target, 'nopolice', nil):Then(function() + local targetPly = player.GetBySteamID(target) + if IsValid(targetPly) then + targetPly:SetNetVar('nopolice', nil) + end + if IsValid(ply) then ply:Notify('Запрет снят') else octolib.msg('Запрет снят') end + end) + return true + +end + +serverguard.command:Add(command) + +local command = {} + +command.help = L.dobrograd +command.command = "lockdown" +command.arguments = {"player"} +command.permissions = L.permissions_admin_commands +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:OnPlayerExecute(ply, target, arguments) + if netvars.GetNetVar('lockdown') then + DarkRP.unLockdown() + else + DarkRP.lockdown() + end + + return true +end + +function command:ContextMenu(pPlayer, menu, rankData) + local drpmenu, pm = menu:AddSubMenu(L.dobrograd) + pm:SetImage("icon16/star.png") + + local mo, pm = drpmenu:AddSubMenu(L.global_actions) + pm:SetImage("icon16/world.png") + + mo:AddOption(L.lockdown_change, function() + serverguard.command.Run("lockdown", true, pPlayer:Name()) + end):SetImage("icon16/exclamation.png") + + mo:AddOption(L.rename_city, function() + Derma_StringRequest(L.rename_city, L.new_title, netvars.GetNetVar('cityName', L.dobrograd), function(text) + serverguard.command.Run("renamecity", true, pPlayer:Name(), text) + end, function(text) end, L.ok, L.cancel) + end):SetImage("icon16/page_edit.png") + + mo:AddOption(L.reset_city, function() + serverguard.command.Run("resetcity", true, pPlayer:Name()) + end):SetImage("icon16/page_delete.png") + + local mo, pm = drpmenu:AddSubMenu(L.money) + pm:SetImage("icon16/money.png") + + mo:AddOption(L.give_money, function() + Derma_StringRequest(L.give_money, L.how_much_money, "", function(text) + serverguard.command.Run("addmoney", true, pPlayer:Name(), text) + end, function(text) end, L.ok, L.cancel) + end):SetImage("icon16/money_add.png") + + local mo, pm = drpmenu:AddSubMenu(L.job) + pm:SetImage("icon16/user_gray.png") + + local smo, spm = mo:AddSubMenu(L.change_job) + spm:SetImage("icon16/group_go.png") + for k,v in pairs(RPExtraTeams) do + smo:AddOption(v.name, function() + serverguard.command.Run("job", true, pPlayer:Name(), k) + end) + end + + local smo, spm = mo:AddSubMenu(L.ban_job) + spm:SetImage("icon16/door_in.png") + for k,v in pairs(RPExtraTeams) do + smo:AddOption(v.name, function() + Derma_StringRequest("Ban player from the job", "Set time for ban in seconds.", "", function(text) + serverguard.command.Run("teamban", true, pPlayer:Name(), k, text) + end, function(text) end, L.ok, L.cancel) + end) + end + + local smo, spm = mo:AddSubMenu( L.unban_job) + spm:SetImage("icon16/door_out.png") + for k,v in pairs(RPExtraTeams) do + smo:AddOption(v.name, function() + serverguard.command.Run("teamunban", true, pPlayer:Name(), k) + end) + end + + local mo, pm = drpmenu:AddSubMenu(L.other) + pm:SetImage("icon16/add.png") + + mo:AddOption(L.addkarma, function() + Derma_StringRequest(L.addkarma, L.addkarma_help, pPlayer:Name(), function(text) + serverguard.command.Run("addkarma", true, pPlayer:Name(), text) + end, function(text) end, L.ok, L.cancel) + end):SetImage("icon16/lightbulb.png") + + mo:AddOption(L.change_name, function() + Derma_StringRequest(L.change_name, L.new_name, pPlayer:Name(), function(text) + serverguard.command.Run("name", true, pPlayer:Name(), text) + end, function(text) end, L.ok, L.cancel) + end):SetImage("icon16/information.png") + + mo:AddOption(L.sell_all_doors, function() + serverguard.command.Run("sellalldoors", true, pPlayer:Name()) + end):SetImage("icon16/house_go.png") + + mo:AddOption(L.send_to_jail, function() + Derma_StringRequest(L.send_to_jail, L.time_in_seconds, "", function(text) + serverguard.command.Run("arrest", true, pPlayer:Name(), text) + end, function(text) end, L.ok, L.cancel) + end):SetImage("icon16/lock.png") + + mo:AddOption(L.unarrest_hint, function() + serverguard.command.Run("unarrest", true, pPlayer:Name()) + end):SetImage("icon16/lock_open.png") + + mo:AddOption('Телепортироваться в админ зону', function() + serverguard.command.Run("adminzone", true, pPlayer:Name()) + end):SetImage("icon16/world.png") + +end + +serverguard.command:Add(command) diff --git a/garrysmod/addons/admin-sg/lua/plugins/dobrograd/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/shared.lua new file mode 100644 index 0000000..2e632a6 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/dobrograd/shared.lua @@ -0,0 +1,84 @@ +local plugin = plugin; + +plugin.name = 'Dobrograd'; +plugin.author = 'chelog'; +plugin.version = '1.0'; +plugin.description = L.description_permissions; +plugin.gamemodes = {'darkrp'}; +plugin.permissions = { + L.permissions_admin_request, + L.permissions_admin_commands, + L.permissions_superadmin_commands, + L.permissions_edit_inventory, + L.permissions_spawn_sent, + L.permissions_spawn_swep, + L.permissions_spawn_npc, + L.permissions_spawn_vehicle, + L.permissions_spawn_ragdoll, + 'DBG: SpawnSimfphys', + L.permissions_trigger_url, + L.permissions_create_prop_inventory, + L.permissions_permaprops, + L.permissions_edit_karma, + 'DBG: Панель ивентов', + 'DBG: Расширенный доступ к панели ивентов', + 'DBG: Изменять автомобили', + 'DBG: Эвакуировать автомобили', + 'DBG: Телепорт по команде', + 'DBG: Применять тулы на игроках', + 'DBG: Глобальный IT', + 'DBG: Большие пропы', + 'DBG: Изменять голод', + 'DBG: Расширенный 3D2D Textscreen', + 'DBG: Открывать Fading Door с помощью кнопки', + 'DBG: Пропы из blacklist', + 'DBG: Редактировать blacklist пропов', + 'Get play time', + 'DBG: Сбрасывать номера', + 'DBG: Изменять звук обыска', + 'DBG: Расширенный Image Screen', + 'DBG: Редактировать тест', + 'DBG: Редактировать организации', + 'Go Incognito', + 'Set Player Name', + 'Make Animatable', + 'Unban permanently banned players', + 'StormFox Settings', + 'StormFox WeatherEdit', +}; + +hook.Add('CAMI.OnPrivilegeRegistered', 'dbg-admin.privileges', function(priv) + serverguard.permission:Add(priv) +end) + +local meta = FindMetaTable 'Player' + +function meta:CheckCrimeDenied() + local ct = CurTime() + local time = self:GetNetVar('nocrime') + if not time then return false end + if time == true then return true end + if time <= ct then + if SERVER then + self:SetDBVar('nocrime', nil) + self:SetNetVar('nocrime', nil) + end + return false + end + return time - ct +end + +function meta:CheckPoliceDenied() + local ct = CurTime() + local time = self:GetNetVar('nopolice') + if not time then return false end + if time == true then return true end + if time <= ct then + if SERVER then + self:SetDBVar('nopolice', nil) + self:SetNetVar('nopolice', nil) + end + return false + end + return time - ct +end diff --git a/garrysmod/addons/admin-sg/lua/plugins/familysharing/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/familysharing/cl_init.lua new file mode 100644 index 0000000..e0ee1f7 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/familysharing/cl_init.lua @@ -0,0 +1,9 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/familysharing/init.lua b/garrysmod/addons/admin-sg/lua/plugins/familysharing/init.lua new file mode 100644 index 0000000..d81ddf2 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/familysharing/init.lua @@ -0,0 +1,41 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); + +plugin:Hook("CheckPassword", "serverguard.familysharing.CheckFamilySharing", function(communityID, ip, serverPassword, enteredPassword, name) + local steamID = util.SteamIDFrom64(communityID); + local url = string.format(SERVERGUARD.ENDPOINT .. "sharedgame/%s", communityID); + + http.Fetch(url, function(body) + local response = util.JSONToTable(body); + + if (!response or !response.lender_steamid) then + serverguard.PrintConsole("[familysharing] Failed to check for player '" .. name .. "' [" .. steamID .. "], JSON response not valid.\n"); + return; + end; + + if (response.lender_steamid != "0") then + local lenderSteamID = util.SteamIDFrom64(response.lender_steamid); + local lenderData = serverguard.banTable[lenderSteamID]; + + if (lenderData) then + local endTime = tonumber(lenderData.endTime); + + if (endTime == 0 or endTime >= os.time()) then + serverguard.PrintConsole("[familysharing] Player '" .. name .. "' [" .. steamID .. "] had family sharing set up with banned player '" .. tostring(lenderData.player) .. "' [" .. lenderSteamID .. "] and has been banned.\n"); + serverguard:BanPlayer(nil, steamID, endTime == 0 and 0 or (endTime - os.time()), lenderData.reason); + end; + end; + end; + end, + + function(error) + serverguard.PrintConsole("[familysharing] Failed to check for player '" .. name .. "' [" .. steamID .. "], HTTP Error: " .. tostring(err) .. "\n"); + end); +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/familysharing/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/familysharing/shared.lua new file mode 100644 index 0000000..99c1905 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/familysharing/shared.lua @@ -0,0 +1,13 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Steam Family Sharing"; +plugin.author = "fangli"; +plugin.version = "1.0"; +plugin.description = "Bans users avoiding bans by using Steam Family Sharing"; +plugin.toggled = false; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/imports/imports/ulx.lua b/garrysmod/addons/admin-sg/lua/plugins/imports/imports/ulx.lua new file mode 100644 index 0000000..e5b7b18 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/imports/imports/ulx.lua @@ -0,0 +1,161 @@ +--[[ + Functions imported from ULib. + + https://github.com/TeamUlysses/ulib/blob/master/lua/ulib/shared/misc.lua +--]] + +plugin.ulx = {}; +plugin.ulib = plugin.ulx; -- Alias. + +local function explode( separator, str, limit ) + local t = {} + local curpos = 1 + while true do -- We have a break in the loop + local newpos, endpos = str:find( separator, curpos ) -- find the next separator in the string + if newpos ~= nil then -- if found then.. + table.insert( t, str:sub( curpos, newpos - 1 ) ) -- Save it in our table. + curpos = endpos + 1 -- save just after where we found it for searching next time. + else + if limit and table.getn( t ) > limit then + return t -- Reached limit + end + table.insert( t, str:sub( curpos ) ) -- Save what's left in our array. + break + end + end + + return t +end + +local function unescapeBackslash( s ) + return s:gsub( "\\\\", "\\" ) +end + +local function splitArgs( args, start_token, end_token ) + args = args:Trim() + local argv = {} + local curpos = 1 -- Our current position within the string + local in_quote = false -- Is the text we're currently processing in a quote? + start_token = start_token or "\"" + end_token = end_token or "\"" + local args_len = args:len() + + while in_quote or curpos <= args_len do + local quotepos = args:find( in_quote and end_token or start_token, curpos, true ) + + -- The string up to the quote, the whole string if no quote was found + local prefix = args:sub( curpos, (quotepos or 0) - 1 ) + if not in_quote then + local trimmed = prefix:Trim() + if trimmed ~= "" then -- Something to be had from this... + local t = explode( "%s+", trimmed ) + table.Add( argv, t ) + end + else + table.insert( argv, prefix ) + end + + -- If a quote was found, reduce our position and note our state + if quotepos ~= nil then + curpos = quotepos + 1 + in_quote = not in_quote + else -- Otherwise we've processed the whole string now + break + end + end + + return argv, in_quote +end + +local function parseKeyValues( str, convert ) + local lines = explode( "\r?\n", str ) + local parent_tables = {} -- Traces our way to root + local current_table = {} + local is_insert_last_op = false + + for i, line in ipairs( lines ) do + local tmp_string = string.char( 01, 02, 03 ) -- Replacement + local tokens = splitArgs( (line:gsub( "\\\"", tmp_string )) ) + for i, token in ipairs( tokens ) do + tokens[ i ] = unescapeBackslash( token ):gsub( tmp_string, "\"" ) + end + + local num_tokens = #tokens + + if num_tokens == 1 then + local token = tokens[ 1 ] + if token == "{" then + local new_table = {} + if is_insert_last_op then + current_table[ table.remove( current_table ) ] = new_table + else + table.insert( current_table, new_table ) + end + is_insert_last_op = false + table.insert( parent_tables, current_table ) + current_table = new_table + + elseif token == "}" then + is_insert_last_op = false + current_table = table.remove( parent_tables ) + if current_table == nil then + return nil, "Mismatched recursive tables on line " .. i + end + + else + is_insert_last_op = true + table.insert( current_table, tokens[ 1 ] ) + end + + elseif num_tokens == 2 then + is_insert_last_op = false + if convert and tonumber( tokens[ 1 ] ) then + tokens[ 1 ] = tonumber( tokens[ 1 ] ) + end + + current_table[ tokens[ 1 ] ] = tokens[ 2 ] + + elseif num_tokens > 2 then + return nil, "Bad input on line " .. i + end + end + + if #parent_tables ~= 0 then + return nil, "Mismatched recursive tables" + end + + if convert and table.Count( current_table ) == 1 and + type( current_table.Out ) == "table" then -- If we caught a stupid garry-wrapper + + current_table = current_table.Out + end + + return current_table +end + +function plugin.ulx:bans() + local bans = file.Read("ulib/bans.txt"); + + if (!bans) then + return false, "no ban data found"; + end; + + local bSuccess, bans = xpcall(parseKeyValues, function() end, bans); + + if (bSuccess) then + local currentTime = os.time(); + + for k, v in pairs(bans) do + local unbanTime = v.unban; + local translatedTime = math.ceil((unbanTime - currentTime) / 60); + + if (translatedTime >= 0) then + serverguard:BanPlayer(nil, k, translatedTime, v.reason, nil, true, string.match(v.admin, "(.+)%(")); + end; + end; + + return true; + else + return false, "can't parse key values"; + end; +end; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/imports/init.lua b/garrysmod/addons/admin-sg/lua/plugins/imports/init.lua new file mode 100644 index 0000000..25810ad --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/imports/init.lua @@ -0,0 +1,48 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("imports/ulx.lua", SERVERGUARD.STATE.SERVER); + +function plugin:Load(addon, import) + addon, import = string.lower(addon), string.lower(import); + + local addonTable = self[addon]; + + if (addonTable) then + if (addonTable[import]) then + local bSuccess, message = addonTable[import](); + + if (bSuccess) then + serverguard.PrintConsole(string.format("%s %s imported successfully.\n", addon, import)); + elseif (message) then + serverguard.PrintConsole(string.format("%s %s failed to import (%s).\n", addon, import, message)); + else + serverguard.PrintConsole(string.format("%s %s failed to import.\n", addon, import)); + end; + else + serverguard.PrintConsole(string.format("%s has no import for %q.\n", addon, import)); + end; + else + serverguard.PrintConsole(string.format("No imports found for %s.\n", addon)); + end; +end; + +concommand.Add("serverguard_import", function(player, command, arguments) + if (util.IsConsole(player)) then + local addon = arguments[1]; + local import = arguments[2]; + + if (!addon or !import) then + serverguard.PrintConsole("You must specify an addon and what to import from it.\n"); + return; + end; + + plugin:Load(addon, import); + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/imports/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/imports/shared.lua new file mode 100644 index 0000000..7eaeb97 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/imports/shared.lua @@ -0,0 +1,12 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Imports"; +plugin.author = "duck"; +plugin.version = "1.0"; +plugin.description = "Allows data from other addons to be imported for use in ServerGuard."; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/levelchange/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/levelchange/cl_init.lua new file mode 100644 index 0000000..88279af --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/levelchange/cl_init.lua @@ -0,0 +1,9 @@ +--[[ + 2017 Thriving Ventures Ltd do not share, re-distribute or modify + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT) +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/levelchange/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/levelchange/cl_panel.lua new file mode 100644 index 0000000..046a27e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/levelchange/cl_panel.lua @@ -0,0 +1,163 @@ +--[[ + 2017 Thriving Ventures Ltd do not share, re-distribute or modify + without permission of its author (gustaf@thrivingventures.com). +]] + +surface.CreateFont("sg.mapchange.divider", {font = "Roboto", size = 18, weight = 800}) +surface.CreateFont("sg.mapchange.curmap", {font = "Roboto", size = 16, weight = 800}) + +local plugin = plugin +local category = {} +category.name = "Change map" +category.material = "serverguard/menuicons/icon_mapmenu.png" +category.permissions = "Map" + +category.LoadedMaps = nil +category.RequestedMaps = nil +category.ServerMaps = nil +category.categoryPanels = {} + +serverguard.netstream.Hook("sgReceiveServerMaps", function(data) + local smaps = data[1] + category.ServerMaps = smaps +end) + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("Change Map") + base.panel:Dock(FILL) + + category.filterMapSettings = base.panel:Add("tiger.list") + category.filterMapSettings:Dock(TOP) + category.filterMapSettings:DockMargin(0, 5, 0, 5) + + category.mapList = base.panel:Add("tiger.scrollpanel") + category.mapList:Dock(FILL) + + local ThinkTime = 0.4 + category.UpdateMapsTimer = CurTime() + ThinkTime + + function category.mapList:Think() + if (CurTime() < category.UpdateMapsTimer and !category.LoadedMaps) then + category.UpdateMapsTimer = CurTime() + ThinkTime + + category:Update() + end + end + + category.curMapLabel = base.panel:Add("DLabel") + category.curMapLabel:SetFont("sg.mapchange.curmap") + category.curMapLabel:SetSkin("serverguard") + category.curMapLabel:SetText("Current map: " .. plugin.CurrentMap) + category.curMapLabel:Dock(BOTTOM) + category.curMapLabel:DockMargin(5, 5, 5, 5) + category.curMapLabel:SizeToContents() + + local refreshMaps = base.panel:Add("tiger.button") + refreshMaps:SetText("Refresh Maps") + refreshMaps:Dock(BOTTOM) + refreshMaps:DockMargin(5, 5, 5, 5) + refreshMaps:SizeToContents() + + function refreshMaps:DoClick() + category.mapList:Clear() + + category.ServerMaps = nil + category.LoadedMaps = nil + category.RequestedMaps = nil + + category.UpdateMapsTimer = CurTime() + ThinkTime + end +end +plugin:AddSubCategory("Server settings", category) + +function category:Update() + if (self.ServerMaps and !self.LoadedMaps) then + self.LoadedMaps = true + + self.filterMapSettings:SetTall(table.Count(self.ServerMaps) * 20) + + for cat_name, maps in pairs(self.ServerMaps) do + self.mapCatFilter = self.filterMapSettings:Add("tiger.checkbox") + self.mapCatFilter:SetText("Hide " .. cat_name .. " Maps") + self.mapCatFilter:Dock(TOP) + self.mapCatFilter:SetTall(20) + self.mapCatFilter:SetChecked(true) + self.mapCatFilter.SetCat = cat_name + + self.mapCat = self.mapList:Add("tiger.list") + self.mapCat:Dock(TOP) + self.mapCat:SetTall((table.Count(maps) * 40) + 35) + self.mapCat:DockMargin(0, 5, 0, 5) + self.mapCat.SetCat = cat_name + + self.mapCatDiv = self.mapCat:Add("Panel") + self.mapCatDiv:Dock(TOP) + self.mapCatDiv:SetTall(30) + + self.mapCatLabel = self.mapCatDiv:Add("DLabel") + self.mapCatLabel:SetFont("sg.mapchange.divider") + self.mapCatLabel:SetSkin("serverguard") + self.mapCatLabel:SetText(cat_name .. " Maps") + self.mapCatLabel:Dock(LEFT) + self.mapCatLabel:DockMargin(5, 5, 5, 5) + self.mapCatLabel:SizeToContents() + + table.insert(category.categoryPanels, self.mapCat) + + for i = 1, #maps do + local map_noext = maps[i]:StripExtension() + + local mapPnl = self.mapCat:Add("tiger.panel") + mapPnl:SetTall(40) + mapPnl:Dock(TOP) + + local mapImage = mapPnl:Add("DImage") + mapImage:Dock(LEFT) + mapImage:DockMargin(5, 3, 5, 3) + mapImage:SetWide(28) + mapImage:SetImage("maps/thumb/" .. map_noext .. ".png", category.material) + + local mapLabel = mapPnl:Add("DLabel") + mapLabel:SetFont("tiger.button") + mapLabel:SetSkin("serverguard") + mapLabel:SetText(map_noext) + mapLabel:Dock(LEFT) + mapLabel:DockMargin(5, 3, 5, 3) + mapLabel:SizeToContents() + + local mapSwitchButton = mapPnl:Add("tiger.button") + mapSwitchButton:SetText("Switch to Map") + mapSwitchButton:Dock(RIGHT) + mapSwitchButton:DockMargin(5, 5, 5, 5) + mapSwitchButton:SizeToContents() + + function mapSwitchButton:DoClick() + self:SetText("Changing...") + category.curMapLabel:SetText("Changing to map: " .. map_noext) + RunConsoleCommand("sg", "map", map_noext) + end + end + + self.mapCat:SetTall((table.Count(maps) * 40) + 35) + + function self.mapCatFilter:OnChange(bool) + if !(bool) then + self:SetText("Show " .. cat_name .. " Maps") + else + self:SetText("Hide " .. cat_name .. " Maps") + end + + for _, catpnls in pairs(category.categoryPanels) do + if (catpnls.SetCat == self.SetCat) then + catpnls:SetVisible(bool) + category.mapList:GetCanvas():InvalidateLayout() + end + end + end + end + elseif (!self.RequestedMaps) then + self.RequestedMaps = true + serverguard.netstream.Start("sgRequestServerMaps", true) + end +end diff --git a/garrysmod/addons/admin-sg/lua/plugins/levelchange/init.lua b/garrysmod/addons/admin-sg/lua/plugins/levelchange/init.lua new file mode 100644 index 0000000..2232539 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/levelchange/init.lua @@ -0,0 +1,54 @@ +--[[ + © 2017 Thriving Ventures Ltd do not share, re-distribute or modify + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard.AddFolder("levelchange") + +local plugin = plugin + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED) +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) + +local function serverGetMaps() + local all_maps = {} + local lc_maps = file.Find("maps/*.bsp", "GAME") + local GamemodeList = engine.GetGamemodes() + + -- Find gamemode prefixes + for k, gm in ipairs( GamemodeList ) do + local Name = gm.title or "Uncategorized" + local Maps = string.Split( gm.maps, "|" ) + + if ( Maps && gm.maps != "" ) then + for k, pattern in ipairs( Maps ) do + local patt_str = string.lower(pattern):Replace("^", "") + + if !(plugin.MapPrefixes[Name]) then plugin.MapPrefixes[Name] = {} end + if !(table.HasValue(plugin.MapPrefixes[Name], patt_str)) then + table.insert(plugin.MapPrefixes[Name], patt_str) + end + end + end + end + + -- Categorize Maps + for map_cat, map_prfx in pairs(plugin.MapPrefixes) do + for i = 1, #plugin.MapPrefixes[map_cat] do + for _, map in pairs(lc_maps) do + local map_noext = map:StripExtension() + + if (map:lower():find("^" .. map_prfx[i]) and map_noext != plugin.CurrentMap) then + if !(all_maps[map_cat]) then table.Merge(all_maps, {[map_cat] = {}}) end + table.insert(all_maps[map_cat], map) + end + end + end + end + + return all_maps +end + +serverguard.netstream.Hook("sgRequestServerMaps", function(player, data) + serverguard.netstream.Start(player, "sgReceiveServerMaps", {serverGetMaps()}) +end) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/levelchange/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/levelchange/shared.lua new file mode 100644 index 0000000..f36e06d --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/levelchange/shared.lua @@ -0,0 +1,39 @@ +--[[ + 2017 Thriving Ventures Ltd do not share, re-distribute or modify + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin + +plugin.name = "Change Map" +plugin.author = "zephruz" +plugin.version = "1" +plugin.description = "Adds a visual map/level-change menu for admins." +plugin.permissions = {} + +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) + +plugin.CurrentMap = game.GetMap() +plugin.MapPrefixes = { + ["Sandbox"] = { + "gm_", + }, + ["Roleplay"] = { + "rp_", + }, + ["Trouble in Terrorist Town"] = { + "ttt_", + }, + ["Prop Hunt"] = { + "ph_", + }, + ["Murder"] = { + "md_", + "mu_", + }, + ["Source"] = { + "cs_", + "de_", + }, + ["Uncategorized"] = {}, + } \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/motd/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/motd/cl_init.lua new file mode 100644 index 0000000..cc80dd3 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/motd/cl_init.lua @@ -0,0 +1,366 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +surface.CreateFont("serverguard.motd.slider", { + font = "Roboto", + size = 16, + weight = 300 +}); + +surface.CreateFont("serverguard.motd.delay", { + font = "Roboto", + size = 20, + weight = 300 +}); + +plugin.defaultHTML = [[ + + + + + + + +
+

Welcome to ]] .. GetHostName() .. [[!

+
+ +
+ Now that you've enabled the MOTD plugin, you're going to have to set the URL that the MOTD will load whenever it's opened. Suitable web pages can include your community's homepage/forums, a page to describe your server's rules, etc. This will be the first thing that players will see, so make it eye-catching! You can open the MOTD at any time by using the !motd command. +
+ +
+ To change the settings for the MOTD, make sure you have the Founder rank or have the Manage MOTD permission. Open up the ServerGuard menu and navigate to the MOTD category on the left. Here you will find a preview of what the MOTD will look like, along with the various settings underneath it. You can change the URL, Unlock Type, and Delay. Changing the URL to a blank box will make the MOTD open this page.
+
+ The Unlock Type will change how the player will have to close the MOTD. Changing this setting to Button will require users to click a simple button to close the MOTD. Changing it to Slider will require players to drag a button to the end of a line to close the MOTD.
+
+ The Delay setting will make it so that players will have to wait a certain amount of time before they can close the MOTD. You can set it to any time between 0 and 10 seconds. Setting the delay to 0 seconds will remove the delay entirely. +
+ + +]]; + +local firstRun = true; + +local motd; +local url = ""; +local delay = 0; +local unlockType = "slider"; +local unlockTypes = { + ["button"] = true, + ["slider"] = true +}; + +local PANEL = {}; + +function PANEL:Init() + self:SetSize(ScrW() * 0.9, ScrH() * 0.9); + self:DockPadding(24, 24, 24, 24); + self:SetAlpha(0); + self:Center(); + + self.html = vgui.Create("DHTML", self); + + if (url == "") then + self.html:SetHTML(plugin.defaultHTML); + else + self.html:OpenURL(url); + end; + + self.html:SetTall(self:GetTall() - (64 + 36)); + self.html:Dock(TOP); + + if (!unlockTypes[unlockType]) then + unlockType = "button"; + end; + + if (delay and math.Round(delay) > 0) then + self.delayStart = CurTime(); + self.delayTarget = CurTime() + math.Round(delay); + self.delayCurrent = CurTime(); + end; + + self.unlockParent = vgui.Create("Panel", self); + self.unlockParent:SetSize(self:GetWide() - 48, 32); + self.unlockParent:Dock(BOTTOM); + self.unlockParent.Paint = function(_self, width, height) + if (self.delayStart and (self.delayCurrent < self.delayTarget)) then + local theme = serverguard.themes.GetCurrent(); + draw.SimpleText("You can close this MOTD in " .. math.ceil(self.delayTarget - self.delayCurrent) .. " second(s).", "serverguard.motd.delay", width / 2, height / 2, theme.tiger_base_section_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); + end; + end; + + self.unlock = vgui.Create("serverguard.motd." .. unlockType, self.unlockParent); + self.unlock:SetCallback(function() + self:FadeOut(function() + self:Remove(); + delay = 0; + motd = nil; + end); + end); + + if (self.delayStart) then + self.unlock:SetVisible(false); + end; +end; + +function PANEL:OnDelayFinished() + delay = 0; + self.unlock:FadeIn(); +end; + +function PANEL:Think() + if (self.delayStart) then + if (self.delayCurrent < self.delayTarget) then + self.delayCurrent = CurTime(); + else + self:OnDelayFinished(); + self.delayStart = nil; + end; + end; +end; + +function PANEL:FadeIn(callback) + self:SetVisible(true); + self:SetAlpha(0); + self:AlphaTo(255, 0.15, 0, callback); +end; + +function PANEL:FadeOut(callback) + self:SetVisible(true); + self:SetAlpha(255); + self:AlphaTo(0, 0.15, 0, callback); +end; + +vgui.Register("serverguard.motd", PANEL, "tiger.panel"); + +local PANEL = {}; + +function PANEL:Init() + self.backgroundMaterial = Material("gui/gradient_down"); + self.buttonMaterial = Material("icon16/arrow_right.png"); + + self:SetSize(64, self:GetParent():GetTall() - 2); + self:SetMouseInputEnabled(true); +end; + +function PANEL:OnMousePressed(mouseCode) + self.pressed = (mouseCode == MOUSE_LEFT); +end; + +function PANEL:OnMouseReleased(mouseCode) + self.pressed = false; +end; + +function PANEL:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + draw.RoundedBox(4, 0, 0, width, height, theme.tiger_base_outline); + + surface.SetMaterial(self.backgroundMaterial); + surface.SetDrawColor(theme.tiger_base_bg); + surface.DrawTexturedRect(0, 0, width, height); + + surface.SetMaterial(self.buttonMaterial); + surface.SetDrawColor(Color(255, 255, 255, 255)); + surface.DrawTexturedRect(width / 2 - 8, height / 2 - 8, 16, 16); +end; + +function PANEL:Think() + if (!self.pressed) then + return; + end; + + if (!input.IsMouseDown(MOUSE_LEFT)) then + self.pressed = false; + end; +end; + +vgui.Register("serverguard.motd.slider.button", PANEL, "Panel"); + +local PANEL = {}; + +function PANEL:Init() + self:SetSize(math.Clamp(self:GetParent():GetWide() / 2, 250, 400), 32); + self:SetPos(self:GetParent():GetWide() / 2 - self:GetWide() / 2, 0); + + self.button = vgui.Create("serverguard.motd.slider.button", self); + self.button:SetPos(1, 1); +end; + +function PANEL:FadeIn(callback) + self:SetVisible(true); + self:SetAlpha(0); + self:AlphaTo(255, 0.15, 0, callback); +end; + +function PANEL:FadeOut(callback) + self:SetVisible(true); + self:SetAlpha(255); + self:AlphaTo(0, 0.15, 0, callback); +end; + +function PANEL:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + draw.RoundedBox(4, 0, 0, width, height, theme.tiger_base_outline); + draw.RoundedBox(2, 1, 1, width - 2, height - 2, theme.tiger_base_bg); + + draw.SimpleText("Drag to close", "serverguard.motd.slider", width / 2, height / 2, theme.tiger_base_section_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER); +end; + +function PANEL:Think() + if (self.unlocked) then + return; + end; + + if (!self.button.pressed) then + local buttonX, buttonY = self.button:GetPos(); + + if (buttonX > 1) then + local animateSpeed = buttonX * 4; + + self.button:SetPos(math.Approach(buttonX, 1, animateSpeed * FrameTime()), 1); + end; + + return; + end; + + local x, y = self:CursorPos(); + self.button:SetPos(math.Clamp(x + 1, 1, self:GetWide() - self.button:GetWide() - 1), 1); + local newX, newY = self.button:GetPos(); + + if (newX == self:GetWide() - self.button:GetWide() - 1) then + if (self.callback) then + self.callback(); + end; + + self.unlocked = true; + end; +end; + +function PANEL:SetCallback(callback) + self.callback = callback; +end; + +vgui.Register("serverguard.motd.slider", PANEL, "Panel"); + +local PANEL = {}; + +function PANEL:Init() + self:SetText("Close"); + self:SetSize(self:GetParent():GetWide() / 4, 32); + self:SetPos(self:GetParent():GetWide() / 2 - self:GetWide() / 2, 0); +end; + +function PANEL:FadeIn(callback) + self:SetVisible(true); + self:SetAlpha(0); + self:AlphaTo(255, 0.15, 0, callback); +end; + +function PANEL:FadeOut(callback) + self:SetVisible(true); + self:SetAlpha(255); + self:AlphaTo(0, 0.15, 0, callback); +end; + +function PANEL:SetCallback(callback) + self.callback = callback; +end; + +function PANEL:DoClick() + if (self.callback) then + self.callback(); + end; +end; + +vgui.Register("serverguard.motd.button", PANEL, "tiger.button"); + +local function MAKE_MOTD() + motd = vgui.Create("serverguard.motd"); + motd:MakePopup(); + motd:FadeIn(); + + if (firstRun) then + firstRun = nil; + end; +end; + +serverguard.netstream.Hook("sgOpenMOTD", function(data) + if (motd) then + if (motd:IsVisible()) then + motd:FadeOut(function() + motd:Remove(); + + MAKE_MOTD(); + end); + else + motd:Remove(); + + MAKE_MOTD(); + end; + + return; + end; + + MAKE_MOTD(); +end); + +serverguard.netstream.Hook("sgReceiveMOTDConfig", function(data) + local config = data[1]; + local bInitial = data[2]; + + for k, v in pairs(config) do + if (k == "Unlock Type") then + unlockType = tostring(v); + elseif (k == "URL") then + url = tostring(v); + elseif (k == "Delay") then + if (firstRun) then + delay = tonumber(v); + end; + end; + end; + + if (firstRun and bInitial) then + MAKE_MOTD(); + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/motd/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/motd/cl_panel.lua new file mode 100644 index 0000000..1f225d8 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/motd/cl_panel.lua @@ -0,0 +1,154 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local category = {}; + +local delay = 0; + +category.name = "MOTD"; +category.material = "serverguard/menuicons/icon_motd.png"; +category.permissions = "Manage MOTD"; + +function category:Create(base) + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("MOTD management"); + base.panel:Dock(FILL); + + base.panel.config = base.panel:Add("tiger.panel"); + base.panel.config:SetTall(68); + base.panel.config:Dock(BOTTOM); + base.panel.config:DockPadding(8, 8, 8, 8); + + base.panel.configTop = base.panel.config:Add("Panel"); + base.panel.configTop:SetTall(22); + base.panel.configTop:Dock(TOP); + + base.panel.configSpacer = base.panel.config:Add("Panel"); -- docking is wonky, so we need spacer panels + base.panel.configSpacer:SetTall(8); + base.panel.configSpacer:Dock(TOP); + + base.panel.configBottom = base.panel.config:Add("Panel"); + base.panel.configBottom:SetTall(22); + base.panel.configBottom:Dock(FILL); + + base.panel.spacer = base.panel:Add("Panel"); + base.panel.spacer:SetTall(24); + base.panel.spacer:Dock(BOTTOM); + + category.html = base.panel:Add("DHTML"); + category.html:SetHTML(plugin.defaultHTML); + category.html:Dock(FILL); + + base.panel.unlockType = base.panel.configTop:Add("DComboBox"); + base.panel.unlockType:SetWide(150); + base.panel.unlockType:Dock(LEFT); + base.panel.unlockType:SetText("Unlock Type"); + base.panel.unlockType:SetFont("tiger.button"); + base.panel.unlockType:SetSkin("serverguard"); + base.panel.unlockType:AddChoice("Slider"); + base.panel.unlockType:AddChoice("Button"); + base.panel.unlockType:SetToolTipSG("Changes how the MOTD closes."); + + function base.panel.unlockType:OpenMenu(pControlOpener) + DComboBox.OpenMenu(self, pControlOpener); + self.Menu:SetSkin("serverguard"); + end; + + function base.panel.unlockType:OnSelect(index, value, data) + local unlockType = "slider"; + + if (value == "Button") then + unlockType = "button"; + end; + + serverguard.netstream.Start("sgUpdateMOTDConfig", { + uniqueID = "Unlock Type", + value = unlockType + }); + end; + + base.panel.spacer = base.panel.configTop:Add("Panel"); + base.panel.spacer:SetWide(8); + base.panel.spacer:Dock(LEFT); + + category.url = base.panel.configTop:Add("DTextEntry"); + category.url:Dock(FILL); + category.url:SetSkin("serverguard"); + category.url:SetToolTipSG("The website to load when opened."); + + function category.url:OnEnter() + serverguard.netstream.Start("sgUpdateMOTDConfig", { + uniqueID = "URL", + value = category.url:GetValue() + }); + end; + + base.panel.delayLabel = base.panel.configBottom:Add("DLabel"); + base.panel.delayLabel:SetMouseInputEnabled(true); + base.panel.delayLabel:SetFont("tiger.button"); + base.panel.delayLabel:SetText("Delay "); + base.panel.delayLabel:SetSkin("serverguard"); + base.panel.delayLabel:SizeToContents(); + base.panel.delayLabel:Dock(LEFT); + base.panel.delayLabel:SetToolTipSG("How long you need to wait before you can close the MOTD."); + + category.delayPanel = base.panel.configBottom:Add("Slider"); + category.delayPanel:SetTall(22); + category.delayPanel:SetSkin("serverguard"); + category.delayPanel:Dock(FILL); + category.delayPanel:SetDecimals(0); + category.delayPanel:SetMin(0); + category.delayPanel:SetMax(10); + category.delayPanel:SetValue(0); + category.delayPanel.amount = delay; + + function category.delayPanel:OnValueChanged(value) + value = math.Round(value); + + if (value != category.delayPanel.amount) then + serverguard.netstream.Start("sgUpdateMOTDConfig", { + uniqueID = "Delay", + value = value + }); + + category.delayPanel.amount = value; + end; + end; +end; + +function category:Update() + serverguard.netstream.Start("sgRequestMOTDConfig", 1); +end; + +plugin:AddSubCategory("Server settings", category); + +serverguard.netstream.Hook("sgReceiveMOTDConfig", function(data) + local config = data[1]; + + for k, v in pairs(config) do + if (k == "Delay") then + delay = tonumber(v); + + if (IsValid(category.delayPanel)) then + category.delayPanel.amount = tonumber(v); + category.delayPanel:SetValue(tonumber(v)); + end; + elseif (k == "URL") then + if (IsValid(category.url)) then + local url = tostring(v); + + category.url:SetValue(url); + + if (url == "") then + category.html:SetHTML(plugin.defaultHTML); + else + category.html:OpenURL(url); + end; + end; + end; + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/motd/init.lua b/garrysmod/addons/admin-sg/lua/plugins/motd/init.lua new file mode 100644 index 0000000..3b2cccd --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/motd/init.lua @@ -0,0 +1,62 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard.AddFolder("motd"); + +local plugin = plugin; +local storedConfig = nil; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +local function LOAD_CONFIG() + if (!storedConfig) then + storedConfig = {}; + + for k, v in pairs(plugin.config) do + storedConfig[v.uniqueID] = v.default; + end; + + local savedConfig = file.Read("serverguard/motd/settings.txt"); + + if (savedConfig) then + local deserializedConfig = serverguard.von.deserialize(savedConfig); + + if (deserializedConfig and table.Count(deserializedConfig) > 0) then + table.Merge(storedConfig, deserializedConfig); + end; + end; + end; +end; + +serverguard.netstream.Hook("sgRequestMOTDConfig", function(player, data) + LOAD_CONFIG(); -- called here because it might be the first time the plugin is enabled + + serverguard.netstream.Start(player, "sgReceiveMOTDConfig", {storedConfig, false}); +end); + +serverguard.netstream.Hook("sgUpdateMOTDConfig", function(player, data) + if (!serverguard.player:HasPermission(player, "Manage MOTD") or !plugin.toggled) then + return; + end; + + if (storedConfig[data.uniqueID]) then + storedConfig[data.uniqueID] = data.value; + end; + + file.Write("serverguard/motd/settings.txt", serverguard.von.serialize(storedConfig), "DATA"); + + serverguard.netstream.Start(nil, "sgReceiveMOTDConfig", {storedConfig, false}); +end); + +plugin:Hook("InitPostEntity", "serverguard.motd.InitPostEntity", function() + LOAD_CONFIG(); +end); + +plugin:Hook("PlayerInitialSpawn", "serverguard.motd.PlayerInitialSpawn", function(player, steamID, uniqueID) + serverguard.netstream.Start(player, "sgReceiveMOTDConfig", {storedConfig, true}); +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/motd/sh_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/motd/sh_commands.lua new file mode 100644 index 0000000..18f053c --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/motd/sh_commands.lua @@ -0,0 +1,17 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local command = {}; + +command.help = "Open the MOTD."; +command.command = "motd"; + +function command:Execute(player, silent, arguments) + serverguard.netstream.Start(player, "sgOpenMOTD", 1); +end; + +plugin:AddCommand(command); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/motd/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/motd/shared.lua new file mode 100644 index 0000000..a77b9d6 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/motd/shared.lua @@ -0,0 +1,19 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "MOTD"; +plugin.author = "`impulse"; +plugin.version = "1.0"; +plugin.description = "An MOTD that is displayed when a player joins the server."; +plugin.permissions = {"Manage MOTD"}; +plugin.toggled = false; +plugin.config = { + {uniqueID = "Unlock Type", description = "Changes how the MOTD closes", default = "slider"}, + {uniqueID = "URL", description = "The website to load when opened", default = ""}, + {uniqueID = "Delay", description = "How long you need to wait before you can close the MOTD", default = 0} +}; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/octostuff/cl_bans.lua b/garrysmod/addons/admin-sg/lua/plugins/octostuff/cl_bans.lua new file mode 100644 index 0000000..def3955 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/octostuff/cl_bans.lua @@ -0,0 +1,71 @@ +surface.CreateFont('octobans.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local colors = CFG.skinColors +netstream.Hook('octobans', function(msg) + + if msg then + surface.PlaySound('ambient/creatures/pigeon_idle2.wav') + local txt = markup.Parse('' .. msg .. '', 600) + local blur = Material('pp/blurscreen') + hook.Add('RenderScreenspaceEffects', 'octobans', function() + + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -0.2, + ['$pp_colour_contrast'] = 1 + 0.5, + ['$pp_colour_colour'] = 0, + } + + if GetConVar('octogui_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat('$blur', i * 2) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(-1, -1, ScrW() + 2, ScrH() + 2) + end + else + colMod['$pp_colour_brightness'] = -0.4 + colMod['$pp_colour_contrast'] = 1 + 0.2 + DrawColorModify(colMod) + end + + draw.NoTexture() + local col = colors.bg + surface.SetDrawColor(col.r,col.g,col.b, 100) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + + end) + + + local lock = Material('octoteam/icons/lock.png') + hook.Add('HUDPaint', 'octobans', function() + + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(lock) + surface.DrawTexturedRect(ScrW() / 2 - 32, ScrH() / 2 - 180, 64, 64) + + txt:Draw((ScrW() - 600) / 2, ScrH() / 2 - 100, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 255) + + end) + else + hook.Remove('RenderScreenspaceEffects', 'octobans') + hook.Remove('HUDPaint', 'octobans') + end + +end) diff --git a/garrysmod/addons/admin-sg/lua/plugins/octostuff/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/octostuff/cl_init.lua new file mode 100644 index 0000000..c9b2d4c --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/octostuff/cl_init.lua @@ -0,0 +1,2 @@ +local plugin = plugin +plugin:IncludeFile('shared.lua', SERVERGUARD.STATE.SHARED) diff --git a/garrysmod/addons/admin-sg/lua/plugins/octostuff/init.lua b/garrysmod/addons/admin-sg/lua/plugins/octostuff/init.lua new file mode 100644 index 0000000..9bc5dc8 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/octostuff/init.lua @@ -0,0 +1,34 @@ +local plugin = plugin +plugin:IncludeFile('shared.lua', SERVERGUARD.STATE.SHARED) + +concommand.Add('sgs', function(pPlayer, command, arguments) + if (arguments and arguments[1]) then + local commandName = string.lower(arguments[1]); + + if commandName == 'cloak' or commandName == 'invisible' then + if IsValid(pPlayer) then pPlayer:Say('/invisible') end + return + end + + local commandTable = serverguard.command:FindByID(commandName); + + table.remove(arguments, 1); + + if (commandTable) then + serverguard.command.Run(pPlayer, commandTable.command, true, unpack(arguments)); + end; + end; +end); + +hook.Add('PlayerSwitchWeapon', 'sg-compat', function(ply) + if ply:GetNoDraw() then + timer.Simple(0, function() + ply:DrawWorldModel(false) + end) + end +end) + +hook.Add('octolib.event:sg', 'sg-compat', function(data) + octolib.msg('DB ServerGuard command:') + RunConsoleCommand('sg', unpack(data)) +end) diff --git a/garrysmod/addons/admin-sg/lua/plugins/octostuff/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/octostuff/shared.lua new file mode 100644 index 0000000..2fa614d --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/octostuff/shared.lua @@ -0,0 +1,146 @@ +local plugin = plugin + +plugin.name = 'Octostuff' +plugin.author = 'chelog' +plugin.version = '1.0' +plugin.description = 'Useful stuff for Octothorp Team\'s servers' +plugin.permissions = { + 'Link Accounts', + 'List Linked Accounts', + 'LuaDev', + 'octolib.flyEditor', +} + +plugin:IncludeFile('cl_bans.lua', SERVERGUARD.STATE.CLIENT) +plugin:IncludeFile('sv_bans.lua', SERVERGUARD.STATE.SERVER) + +local command = {}; + +command.help = 'Link player accounts'; +command.command = 'link'; +command.arguments = {'steamid', 'steamid'}; +command.permissions = 'Link Accounts'; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:Execute(admin, silent, args) + if not SERVER then return true end + local pID, fID = args[1], args[2] + if not octolib.string.isSteamID(pID) or not octolib.string.isSteamID(fID) then + admin:Notify('Неправильный формат SteamID') + return + end + + plugin.addFamilyLink(pID, fID):Then(function() + admin:Notify(('Аккаунты %s и %s теперь связаны'):format(pID, fID)) + end):Catch(function() + admin:Notify('warning', 'Произошла ошибка при попытке связать аккаунты ' .. pID .. ' и ' .. fID) + end) + + return true; +end; + +serverguard.command:Add(command); + +local command = {}; + +command.help = 'List linked Steam accounts'; +command.command = 'listlinked'; +command.arguments = {'steamid'}; +command.permissions = 'List Linked Accounts'; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:Execute(admin, silent, args) + if not SERVER then return true end + local steamID = args[1] + if not octolib.string.isSteamID(steamID) then return end + + octolib.getDBVar(steamID, 'family'):Then(function(family) + if istable(family) then + admin:Notify(('Список связанных с %s аккаунтов, которые появлялись на сервере: %s'):format(steamID, table.concat(family, ', '))) + else + admin:Notify('Аккаунты, связанные семейным доступом с этим SteamID, не появлялись на этом сервере') + end + end):Catch(function() + admin:Notify('Данные не найдены') + end) + + return true; +end; + +serverguard.command:Add(command); + +local capturing = {} +local command = {} + +command.help = 'Grab player screen' +command.command = 'capture' +command.arguments = {'player'} +command.permissions = 'Screencap' +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +local function notifyWaiting(sid, msg) + for _,v in ipairs(capturing[sid]) do + if IsValid(v) then v:Notify('rp', unpack(octolib.string.splitByUrl(msg))) end + end + capturing[sid] = nil +end + +local errmsg = 'Не удалось получить скриншот экрана %s (%s): %s' +function command:OnPlayerExecute(admin, target) + if not IsValid(target) then return end + if not SERVER then return true end + local name, sid = target:Name(), target:SteamID() + admin:Notify(('Захват экрана %s (%s)...'):format(name, sid)) + if not capturing[sid] then + capturing[sid] = {} + octolib.screen.sendToImgur(target):Then(function(r) + local msg + if not r.success then + msg = errmsg:format(name, sid, tostring(r.data.error)) + else msg = ('Скриншот экрана %s (%s): %s'):format(name, sid, r.data.link) end + notifyWaiting(sid, msg) + end):Catch(function(r) + notifyWaiting(sid, errmsg:format(name, sid, tostring(r))) + end) + end + capturing[sid][#capturing[sid] + 1] = admin + return true +end + +serverguard.command:Add(command) + +local command = {}; + +command.help = 'Transfer members from one rank to another' +command.command = 'ranktransfer' +command.arguments = {'from', 'to'} +command.permissions = 'Edit Ranks' +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:Execute(admin, silent, args) + + if not SERVER then return true end + local rankFrom = args[1] + local rankTo = args[2] + if not rankFrom or not rankTo or not serverguard.ranks:FindByID(rankTo) then return end + + local queryObj = serverguard.mysql:Select('serverguard_users') + queryObj:Where('rank', rankFrom) + queryObj:Callback(function(result, status, lastID) + if not istable(result) or #result < 1 then return end + for _, row in ipairs(result) do + serverguard.command.Run(admin, 'rank', false, row.steam_id, rankTo, 0) + end + end) + queryObj:Execute() + + return true + +end + +serverguard.command:Add(command) + +local meta = FindMetaTable 'Player' +function meta:query(priv) + return serverguard.player:HasPermission(self, priv) +end diff --git a/garrysmod/addons/admin-sg/lua/plugins/octostuff/sv_bans.lua b/garrysmod/addons/admin-sg/lua/plugins/octostuff/sv_bans.lua new file mode 100644 index 0000000..128b197 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/octostuff/sv_bans.lua @@ -0,0 +1,104 @@ +local function blockInput(ply, block) + ply:Freeze(block) + ply.blockedInput = block or nil +end + +local dontBanSteamIDs = {} -- store last banned ids to avoid infinite loops +local function queueTimer() + timer.Create('octobans.recursionFix', 10, 0, function() table.Empty(dontBanSteamIDs) end) +end + +local function banFamily(doBan, ply, time, reason) + reason = reason or 'No reason' + local sid = isstring(ply) and ply or IsValid(ply) and ply:SteamID() + + if dontBanSteamIDs[sid] then return end + dontBanSteamIDs[sid] = true + + octolib.family.getBySteamID(sid, function(family) + if not family then return end + + for _, familySid in ipairs(family.steamids) do + familySid = util.SteamIDFrom64(familySid) + + if familySid == sid or dontBanSteamIDs[familySid] then continue end + dontBanSteamIDs[familySid] = true + + if doBan then + octolib.msg('Banning linked account: %s -> %s', sid, familySid) + RunConsoleCommand('sg', 'ban', familySid, tostring(math.floor(time)), L.ban_on_another_account .. reason) + else + octolib.msg('Unbanning linked account: %s -> %s', sid, familySid) + serverguard:UnbanPlayer(familySid, nil, nil, true) + end + end + end) + + queueTimer() +end +hook.Add('serverguard.PlayerBanned', 'octobans', function(ply, time, reason, admin) banFamily(true, ply, time, reason) end) +hook.Add('serverguard.PlayerBannedBySteamID', 'octobans', function(sID, time, reason, admin) banFamily(true, sID, time, reason) end) +hook.Add('serverguard.PlayerUnbanned', 'octobans', function(sID, admin) banFamily(false, sID) end) + +hook.Add('octolib.family.mergedByHwid', 'octobans', function(families) + local steamids = octolib.array.reduce(families, function(steamids, family) + return table.Add(steamids, octolib.array.map(family.steamids, util.SteamIDFrom64)) + end, {}) + + octolib.db:RunQuery([[ + SELECT * FROM ]] .. CFG.db.admin .. [[.serverguard_bans + WHERE steam_id IN (']] .. table.concat(steamids, '\',\'') .. [[')]], + function(q, st, rows) + if #rows < 1 then return end + + local longestBan = rows[1] + for _, ban in ipairs(rows) do + if ban.end_time == 0 then + longestBan = ban + break + end + + if ban.end_time > longestBan.end_time then + longestBan = ban + end + end + + if longestBan then + local time = longestBan.end_time == 0 and 0 or math.ceil((longestBan.end_time - os.time()) / 60) + for _, sid in ipairs(steamids) do + dontBanSteamIDs[sid] = true + queueTimer() + + RunConsoleCommand('sg', 'ban', sid, tostring(math.floor(time)), 'Бан по связи: ' .. longestBan.reason) + end + end + end) +end) + +hook.Add('PlayerFinishedLoading', 'octobans', function(ply) + if not ply.restrictedFromPlaying then return end + netstream.Start(ply, 'octobans', ply.restrictedFromPlaying) +end) + +local noReport = octolib.array.toKeys {'RU','KZ','UA','BY','LT','LV','EE','MD','GE','AM','AZ','UZ','TM'} +hook.Add('octolib.ipInfo', 'octobans', function(ply, info) + if not info or not IsValid(ply) then return end + + if info.isp and info.isp:lower():find('nvidia') then + blockInput(ply, true) + ply.restrictedFromPlaying = 'Привет! На нашем сервере запрещена игра через GeForce NOW, так как он конфликтует с нашей системой обнаружения обходов бана. Единственный выход из данной ситуации – не использовать этот сервис. Приносим свои извинения за неудобства, но это необходимо для поддержания честной игры' + end + + if CFG.webhooks.cheats and info.countryCode and not noReport[info.countryCode] then + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = {{ + title = 'Подключение из ' .. (info.country or info.countryCode), + fields = {{ + name = L.player, + value = ply:GetName() .. '\n[' .. ply:SteamID() .. '](' .. 'https://steamcommunity.com/profiles/' .. ply:SteamID64() .. ')', + }}, + }}, + }) + end +end) diff --git a/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/cl_init.lua new file mode 100644 index 0000000..4848b1d --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/cl_init.lua @@ -0,0 +1,10 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/cl_panel.lua new file mode 100644 index 0000000..ecfbedf --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/cl_panel.lua @@ -0,0 +1,122 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local category = {}; + +category.name = "Recent disconnects"; +category.material = "serverguard/menuicons/icon_disconnects.png"; +category.permissions = "View Disconnects"; + +function category:Create(base) + base.panel = base:Add("tiger.panel") + base.panel:SetTitle("View recently disconnected players") + base.panel:Dock(FILL) + base.panel:DockPadding(24, 24, 24, 48) + + base.panel.list = base.panel:Add("tiger.list") + base.panel.list:Dock(FILL) + base.panel.list.sortColumn = 3 + + base.panel.list:AddColumn("PLAYER", 250) + base.panel.list:AddColumn("STEAMID", 250) + base.panel.list:AddColumn("RANK", 75) + + local refresh = base.panel:Add("tiger.button") + refresh:SetPos(4, 4) + refresh:SetText("Refresh") + refresh:SizeToContents() + + function refresh:DoClick() + base.panel.list:Clear() + + serverguard.netstream.Start("sgRequestDisconnects", true); + end + + function base.panel.list:Think() + if (self.nextUpdate and self.nextUpdate <= CurTime()) then + refresh:DoClick() + + self.nextUpdate = nil + end + end + + function base.panel:PerformLayout() + local w, h = self:GetSize() + + refresh:SetPos(w -(refresh:GetWide() +24), h -(refresh:GetTall() +14)) + end + + category.list = base.panel.list +end; + +function category:ClearDisconnects() + self.list:Clear(); + self.list.list:PerformLayout(); +end + +function category:Update(base) + self:ClearDisconnects(); + + serverguard.netstream.Start("sgRequestDisconnects", true); +end; + +function category:UpdateDisconnects(data) + local steamid = data[1]; + local rank = data[2]; + local name = data[3]; + + if (rank and rank == "user") then return; end; + + local rankData = serverguard.ranks:GetRank(rank); + + local rankName = ""; + + if (!rankData) then + rankName = "Unknown rank (REMOVED)"; + else + rankName = rankData.name; + end; + + local panel = category.list:AddItem(name, steamid, rankName); + panel.steamid = steamid; + + function panel:OnMousePressed() + if !serverguard.player:HasPermission(LocalPlayer(), "Ban") and serverguard.player:GetBanLimit(admin) != 0 then return end; + local menu = DermaMenu(); + menu:SetSkin("serverguard"); + + local banMenu, menuOption = menu:AddSubMenu("Ban"); + + banMenu:SetSkin("serverguard"); + menuOption:SetImage("icon16/delete.png"); + + for k, v in pairs(serverguard.banLengths) do + local option = banMenu:AddOption(v[1], function() + Derma_StringRequest("Ban Reason", "Specify ban reason.", "", function(text) + serverguard.command.Run("ban", false, steamid, v[2], text); + end, function(text) end, "Accept", "Cancel"); + end); + + option:SetImage("icon16/clock.png"); + end; + + menu:Open(); + end; + + if (rankData) then + local rankLabel = panel:GetLabel(3); + rankLabel:SetColor(rankData.color); + rankLabel:SetSort(rankData.immunity); + rankLabel.oldColor = rankData.color; + end; +end; + +plugin:AddSubCategory("Intelligence", category); + +serverguard.netstream.Hook("sgGetDisconnects", function(data) + category:UpdateDisconnects(data); +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/init.lua b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/init.lua new file mode 100644 index 0000000..2088f32 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/init.lua @@ -0,0 +1,42 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +plugin.disconnects = plugin.disconnects or {}; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +serverguard.netstream.Hook("sgRequestDisconnects", function(player, data) + if (serverguard.player:HasPermission(player, "View Disconnects")) then + for k,v in pairs(plugin.disconnects) do + serverguard.netstream.Start(player, "sgGetDisconnects", v); + end; + end; +end); + +local function removeDisconnect(steamid) + for k,v in pairs(plugin.disconnects) do + if v[1] == steamid then + table.remove(plugin.disconnects, k); + end; + end; +end; + +hook.Add("PlayerDisconnected", "serverguard.disconnects.PlayerDisconnected", function(player) + if IsValid(player) then + local steamid = player:SteamID(); + + table.insert(plugin.disconnects, {steamid, serverguard.player:GetRank(player), player:Name()}); + timer.Simple(600, function() + removeDisconnect(steamid); + end); + end; +end); + +hook.Add("PlayerInitialSpawn", "serverguard.disconnects.PlayerInitialSpawn", function(player) + removeDisconnect(player:SteamID()); +end) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/shared.lua new file mode 100644 index 0000000..821dcaf --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/recentdisconnects/shared.lua @@ -0,0 +1,13 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Recent Disconnects"; +plugin.author = "pyroman"; +plugin.version = "1.0"; +plugin.description = "Allows to view recently disconnected players."; +plugin.permissions = {"View Disconnects"}; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/reservedslots/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/cl_init.lua new file mode 100644 index 0000000..f4e5158 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/cl_init.lua @@ -0,0 +1,11 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT) + +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/reservedslots/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/cl_panel.lua new file mode 100644 index 0000000..abaad42 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/cl_panel.lua @@ -0,0 +1,41 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local category = {}; + +category.name = "Reserved slots"; +category.material = "serverguard/menuicons/icon_reserved_slots.png"; +category.permissions = "Manage Reserved Slots"; + +function category:Create(base) + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("Slot reservation"); + base.panel:Dock(FILL); + + local configList = base.panel:Add("tiger.list"); + configList:SetTall(64); + configList:Dock(TOP); + + local shouldShowReal = vgui.Create("tiger.checkbox"); + configList:AddPanel(shouldShowReal); + + shouldShowReal:Dock(TOP); + shouldShowReal:SetText("Hide reserved slots"); + shouldShowReal:BindToConfig("reservedslots", "hide"); + + local slotsReserved = base.panel:Add("tiger.numslider"); + configList:AddPanel(slotsReserved); + + slotsReserved:Dock(TOP); + slotsReserved:SetText("Amount of reserved slots"); + slotsReserved:SetMinMax(1, game.MaxPlayers() - 1); + slotsReserved:SetClampValue(true); + slotsReserved:SetValue(1); + slotsReserved:BindToConfig("reservedslots", "slots", true); +end; + +plugin:AddSubCategory("Server settings", category); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/reservedslots/init.lua b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/init.lua new file mode 100644 index 0000000..7e50b6e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/init.lua @@ -0,0 +1,70 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +local bypassRanks = { + "admin", + "superadmin", + "founder" +}; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED) +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) + +local function kickID(uniqueID, reason) + RunConsoleCommand("kickid", tostring(uniqueID), reason); +end; + +plugin.config:AddCallback("hide", function(value) + RunConsoleCommand("sv_visiblemaxplayers", value and (game.MaxPlayers() - plugin.config:GetValue("slots")) or 0); +end); + +plugin.config:AddCallback("slots", function(value) + RunConsoleCommand("sv_visiblemaxplayers", plugin.config:GetValue("hide") and (game.MaxPlayers() - value) or 0); +end); + +plugin:Hook("PlayerAuthed", "reservedslots.PlayerAuthed", function(ply, steamID, uniqueID) + local onlinePlayers = #player.GetAll(); + local maxPlayers = game.MaxPlayers(); + + if (onlinePlayers + plugin.config:GetValue("slots") >= maxPlayers) then + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:Select("rank"); + queryObj:Where("steam_id", steamID); + queryObj:Callback(function(result, status, lastID) + local rank = "user"; + + if (type(result) == "table" and #result > 0) then + rank = result[1].rank; + end; + + if (!table.HasValue(bypassRanks, rank)) then + kickID(uniqueID, "Sorry, that slot has been reserved.") + return; + end; + + local kickablePlayer, shortestTime = nil, math.huge; + + for k, pPlayer in ipairs(player.GetAll()) do + local vrank = serverguard.player:GetRank(pPlayer); + local timeConnected = pPlayer:TimeConnected(); + + if (timeConnected < shortestTime and !table.HasValue(bypassRanks, vrank)) then + kickablePlayer, timeConnected = pPlayer, timeConnected; + end; + end; + + if (kickablePlayer and IsValid(kickablePlayer)) then + kickablePlayer:Kick("Sorry, freeing up slots for reserved slots."); + end; + end); + queryObj:Execute(); + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/reservedslots/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/shared.lua new file mode 100644 index 0000000..e897b13 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/reservedslots/shared.lua @@ -0,0 +1,20 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Reserved Slots"; +plugin.author = "fangli"; +plugin.version = "1.0"; +plugin.description = "Set up reserved slots"; +plugin.permissions = {"Manage Reserved Slots"}; +plugin.toggled = false; + +local config = serverguard.config.Create("reservedslots"); + config:SetPermission("Manage Reserved Slots"); + config:AddBoolean("hide", false, "Hide reserved slots"); + config:AddNumber("slots", 2, "Amount of reserved slots"); +plugin.config = config:Load(); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/restrictions/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/restrictions/cl_init.lua new file mode 100644 index 0000000..86cf23c --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/restrictions/cl_init.lua @@ -0,0 +1,10 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/restrictions/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/restrictions/cl_panel.lua new file mode 100644 index 0000000..5633bfb --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/restrictions/cl_panel.lua @@ -0,0 +1,286 @@ + --[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local newRankRestrictions = {}; +local restrictionTypes = { + {name = "Props", type = "Number"}, + {name = "Vehicles", type = "Number"}, + {name = "Sents", type = "Number"}, + {name = "Effects", type = "Number"}, + {name = "NPCs", type = "Number"}, + {name = "Ragdolls", type = "Number"}, + {name = "Balloons", type = "Number"}, + {name = "Buttons", type = "Number"}, + {name = "Dynamite", type = "Number"}, + {name = "Effects", type = "Number"}, + {name = "Emitters", type = "Number"}, + {name = "Hoverballs", type = "Number"}, + {name = "Lamps", type = "Number"}, + {name = "Lights", type = "Number"}, + {name = "Thrusters", type = "Number"}, + {name = "Wheels", type = "Number"}, + + {name = "Wire_Lights", type = "Number"}, + {name = "Wire_Lamps", type = "Number"}, + {name = "Keypad_Wires", type = "Number"}, + {name = "Octo_Triggers", type = "Number"}, + {name = "Textscreens", type = "Number"}, + {name = "Imgscreens", type = "Number"}, + + {name = "Tools", type = "Tools"} +}; + +local function Spacer(parent) + local base = vgui.Create("Panel"); + base:SetTall(15); + base:Dock(TOP); + base:DockMargin(2, 0, 2, 14); + + parent:AddPanel(base); + + return base; +end; + +local function PopulateRestrictionList(restrictionsPanel, toolsPanel, rankTable, restrictionData) + local userRestrictions = serverguard.ranks:GetData("user", "Restrictions", {}); + + for i, data in ipairs(restrictionTypes) do + rankTable[i] = {name = data.name, value = "", type = data.type}; + + if (restrictionData[data.name]) then + rankTable[i].value = restrictionData[data.name]; + end; + + if (GAMEMODE.IsSandboxDerived and toolsPanel and data.type == "Tools") then + local toolsTable = {}; + + for key, weapon in pairs(weapons.GetList()) do + if (weapon.ClassName == "gmod_tool") then + for key2, tool in pairs(weapon.Tool) do + local cat = tool.Category or "Other"; + local name = tool.Name; + + if (!name) then + name = tool.Mode; + else + if (string.find(name, "#")) then + name = language.GetPhrase(string.gsub(name, "#", "")); + end; + end; + + table.insert(toolsTable, { + Category = cat, + Name = cat.." - "..name, + Command = tool.Mode, + Value = true + }); + end; + end; + end; + + table.sort(toolsTable, function(a, b) + if a.Category == "Other" then return false; end; + if b.Category == "Other" then return true; end; + return a.Name < b.Name; + end); + + rankTable[i].value = toolsTable; + + local lastCat = nil; + for k, v in pairs(toolsTable) do + if (lastCat and v.Category != lastCat) then + Spacer(toolsPanel); + end; + + local checkbox = vgui.Create("tiger.checkbox"); + toolsPanel:AddPanel(checkbox); + + checkbox:Dock(TOP); + checkbox:SetText(v.Name); + + if (restrictionData[data.name] and restrictionData[data.name][v.Command] ~= nil) then + checkbox:SetChecked(restrictionData[data.name][v.Command]); + rankTable[i].value[k].Value = restrictionData[data.name][v.Command]; + else + checkbox:SetChecked(true); + end; + + function checkbox:OnChange(bValue) + rankTable[i].value[k].Value = tobool(bValue); + end; + + lastCat = v.Category; + end; + elseif (data.type == "Number") then + local slider = vgui.Create("tiger.numslider"); + restrictionsPanel:AddPanel(slider); + + slider:Dock(TOP); + slider:SetText(data.name); + slider:SetMinMax(-1, 2048); + + if (restrictionData[data.name]) then + slider:SetValue(restrictionData[data.name]); + else + if (userRestrictions[data.name]) then + slider:SetValue(userRestrictions[data.name]); + rankTable[i].value = userRestrictions[data.name]; + else + slider:SetValue(1); + rankTable[i].value = 1; + end; + end; + + function slider:ValueChanged(value) + rankTable[i].value = math.Round(value); + end; + end; + end; +end; + +plugin:Hook("serverguard.panel.RankEditorCreationMenu", "restrictions.RankEditorCreationMenu", function(list, copyFrom) + local label = vgui.Create("DLabel"); + label:SetText("Rank restrictions:"); + label:SizeToContents(); + label:Dock(TOP); + label:DockMargin(0, 8, 0, 0); + label:SetSkin("serverguard"); + list:AddPanel(label); + + local restrictionList = vgui.Create("tiger.list"); + restrictionList:Dock(TOP); + restrictionList:DockMargin(0, 14, 0, 14); + restrictionList:SetTall(16 * 13); + list:AddPanel(restrictionList); + + local toolsList; + + if (GAMEMODE.IsSandboxDerived) then + local label = vgui.Create("DLabel"); + label:SetText("Rank tool restrictions:"); + label:SizeToContents(); + label:Dock(TOP); + label:DockMargin(0, 8, 0, 0); + label:SetSkin("serverguard"); + list:AddPanel(label); + + toolsList = vgui.Create("tiger.list"); + toolsList:Dock(TOP); + toolsList:DockMargin(0, 14, 0, 14); + toolsList:SetTall(16 * 16); + list:AddPanel(toolsList); + end; + + local data = copyFrom and copyFrom.data and copyFrom.data.Restrictions + PopulateRestrictionList(restrictionList, toolsList, {}, data or {}); +end); + +plugin:Hook("serverguard.panel.RankEditorCreationPopulate", "restrictions.RankEditorCreationPopulate", function(dataTable) + local appliedRestrictions = dataTable["Restrictions"] or {}; + + for k, v in pairs(newRankRestrictions) do + appliedRestrictions[v.name] = v.value; + end; + + local Tools = {} + for _, tool in ipairs(appliedRestrictions.Tools or {}) do + Tools[tool.Command] = tool.Value + end + appliedRestrictions.Tools = Tools + + dataTable["Restrictions"] = appliedRestrictions; +end); + +plugin:Hook("serverguard.panel.RankEditorContext", "restrictions.RankEditorContext", function(menu, uniqueID, rankData, categoryPanel, categoryBase) + local option = menu:AddOption("Change restrictions", function() + if (uniqueID == "founder") then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, "You cannot change the restrictions of the founder rank - it's already allowed for unlimited spawning!"); + return; + end; + + local base = vgui.Create("tiger.panel"); + base:SetTitle("Change rank restrictions"); + base:SetSize(580, 620); + base:Center(); + base:MakePopup(); + base:DockPadding(24, 24, 24, 48); + + local label = base:Add("DLabel"); + label:SetText("Restrictions:"); + label:SizeToContents(); + label:Dock(TOP); + label:SetSkin("serverguard"); + + local restrictionList = base:Add("tiger.list"); + restrictionList:SetTall(16 * 13); + restrictionList:Dock(TOP); + + local label = base:Add("DLabel"); + label:SetText("Tool restrictions:"); + label:SizeToContents(); + label:Dock(TOP); + label:DockMargin(0, 8, 0, 0); + label:SetSkin("serverguard"); + + local toolsList = base:Add("tiger.list"); + toolsList:Dock(TOP); + toolsList:SetTall(16 * 16); + + local appliedRestrictions = {} + local restrictionData = serverguard.ranks:GetData(uniqueID, "Restrictions", {}); + + PopulateRestrictionList(restrictionList, toolsList, appliedRestrictions, restrictionData); + + local complete = base:Add("tiger.button"); + complete:SetPos(4, 4); + complete:SetText("Complete"); + complete:SizeToContents(); + + function complete:DoClick() + local restrictionData = {}; + + for k, v in pairs(appliedRestrictions) do + restrictionData[v.name] = v.value; + end; + + local Tools = {} + for _, tool in ipairs(restrictionData.Tools) do + Tools[tool.Command] = tool.Value + end + restrictionData.Tools = Tools + + serverguard.netstream.Start("sgChangeRankData", { + uniqueID, "Restrictions", restrictionData + }); + + timer.Simple(FrameTime() * 8, function() + categoryPanel:Update(categoryBase); + end); + + base:Remove(); + end; + + local cancel = base:Add("tiger.button"); + cancel:SetPos(4, 4); + cancel:SetText("Cancel"); + cancel:SizeToContents(); + + function cancel:DoClick() + base:Remove(); + end; + + function base:PerformLayout() + local w, h = self:GetSize(); + + complete:SetPos(w - (complete:GetWide() + 24), h - (complete:GetTall() + 14)); + cancel:SetPos(0, h - (cancel:GetTall() + 14)); + cancel:MoveLeftOf(complete, 14); + end; + end); + + option:SetImage("icon16/script_gear.png"); +end); diff --git a/garrysmod/addons/admin-sg/lua/plugins/restrictions/init.lua b/garrysmod/addons/admin-sg/lua/plugins/restrictions/init.lua new file mode 100644 index 0000000..abc436a --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/restrictions/init.lua @@ -0,0 +1,203 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +local function ReachedLimit(player, limitType, message) + if not IsValid(player) then + return false, true + end + + local uniqueID = serverguard.player:GetRank(player); + + if (uniqueID == "founder") then + return false, true; + end; + + local restrictions = serverguard.ranks:GetData(uniqueID, "Restrictions"); + local iLimit = nil; + local amount = tonumber( + player:GetCount(string.lower(limitType)) + ); + + if (restrictions and restrictions[limitType]) then + iLimit = tonumber(restrictions[limitType]) or 0; + end; + + if limitType == 'Props' then + local override = player:GetNetVar('propLimit') + if override and iLimit and override > iLimit then + if amount >= (tonumber(override) or 0) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, message) + return true + end + return false + end + end + + if (iLimit == -1) then + return false, true; + end; + + if (iLimit and amount >= iLimit) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, message); + return true; + end; + + if (amount >= cvars.Number("sbox_max" .. string.lower(limitType))) then + return false, false; + end + + return false; +end; + +plugin:Hook("PlayerSpawnEffect", "restrictions.PlayerSpawnEffect", function(player, model) + local reachedLimit, bForce = ReachedLimit(player, "Effects", "Достигнут лимит эффектов."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PlayerSpawnNPC", "restrictions.PlayerSpawnNPC", function(player, class, weapon) + local reachedLimit, bForce = ReachedLimit(player, "NPCs", "Достигнут лимит NPC."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PlayerSpawnProp", "restrictions.PlayerSpawnProp", function(player, model) + local reachedLimit, bForce = ReachedLimit(player, "Props", "Достигнут лимит пропов.") + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PlayerSpawnRagdoll", "restrictions.PlayerSpawnRagdoll", function(player, model) + local reachedLimit, bForce = ReachedLimit(player, "Ragdolls", "Достигнут лимит рагдоллов."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PlayerSpawnSENT", "restrictions.PlayerSpawnSENT", function(player, class) + local reachedLimit, bForce = ReachedLimit(player, "Sents", "Достигнут лимит энтити."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PlayerSpawnSWEP", "restrictions.PlayerSpawnSWEP", function(player, class, swepData) + local reachedLimit, bForce = ReachedLimit(player, "Sents", "Достигнут лимит энтити."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PlayerSpawnVehicle", "restrictions.PlayerSpawnVehicle", function(player, model, class, data) + local reachedLimit, bForce = ReachedLimit(player, "Vehicles", "Достигнут лимит транспорта."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; +end); + +plugin:Hook("PostGamemodeLoaded", "restrictions.PostGamemodeLoaded", function() + local meta = FindMetaTable("Player"); + meta.oldCheckLimit = meta.oldCheckLimit or meta.CheckLimit; + function meta:CheckLimit(str) + local reachedLimit, bForce = ReachedLimit(self, str:sub(1,1):upper()..str:sub(2), "Достигнут лимит "..str.."."); + + if (reachedLimit) then + return false; + end; + + if (!reachedLimit and bForce) then + return true; + end; + + return self:oldCheckLimit(str); + end; +end); + +hook.Add('CanTool', 'restrictions.CanTool', function(player, trace, tool) + local uniqueID = serverguard.player:GetRank(player) + + if (not IsValid(player) or uniqueID == 'founder') then + return true + end + + local restrictionData = serverguard.ranks:GetData(uniqueID, 'Restrictions', {}) + local toolList = {} + + if (restrictionData.Tools) then + toolList = restrictionData.Tools + end + + if toolList[tool] == false and not hook.Run('sg.tool-override', player, trace, tool) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, 'У тебя нет доступа к этому инструменту') + return false + end + + local ent = trace.Entity + if IsValid(ent) then + if ent:GetClass() == 'gmod_sent_vehicle_fphysics_base' and player:query('DBG: Изменять автомобили') then + return true + end + + if ent:IsPlayer() and player:query('DBG: Применять тулы на игроках') then + return true + end + end +end, -10) + +hook.Add('CanProperty', 'restrictions.CanTool', function(player, property, ent) + local uniqueID = serverguard.player:GetRank(player) + + if (uniqueID == 'founder') then + return true + end + + if IsValid(ent) and ent:GetClass() == 'gmod_sent_vehicle_fphysics_base' and player:query('DBG: Изменять автомобили') then + return true + end +end, -10) diff --git a/garrysmod/addons/admin-sg/lua/plugins/restrictions/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/restrictions/shared.lua new file mode 100644 index 0000000..6969b73 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/restrictions/shared.lua @@ -0,0 +1,13 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Restrictions"; +plugin.author = "`impulse"; +plugin.version = "1.2"; +plugin.description = "Provides restrictions for each player, like how many props you can spawn or if you're allowed to noclip."; +plugin.permissions = {"Manage Restrictions"}; diff --git a/garrysmod/addons/admin-sg/lua/plugins/sandbox/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/sandbox/cl_init.lua new file mode 100644 index 0000000..f4e5158 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/sandbox/cl_init.lua @@ -0,0 +1,11 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT) + +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/sandbox/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/sandbox/cl_panel.lua new file mode 100644 index 0000000..b9e0edc --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/sandbox/cl_panel.lua @@ -0,0 +1,61 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local category = {}; + +category.name = "Sandbox settings"; +category.material = "serverguard/menuicons/icon_sandbox.png"; +category.permissions = "Sandbox settings"; + +function category:Create(base) + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("Change sandbox variables"); + base.panel:Dock(FILL); + + base.panel.list = base.panel:Add("tiger.list"); + base.panel.list:Dock(FILL); + + for k, v in ipairs(plugin.convars) do + local convar = GetConVar(v); + + if (convar) then + local panel = vgui.Create("tiger.numslider"); + panel:SetText(v); + panel:SetMinMax(0, 2048); + panel:SetValue(convar:GetInt()); + panel:Dock(TOP); + panel.created = true; + panel.oldThink = panel.Think + + function panel:ValueChanged(value) + if (!self.created) then + self.nextUpdate = CurTime(); + end; + + self.created = nil; + end; + + function panel:Think() + self:oldThink(); + + if (self.nextUpdate and self.nextUpdate +0.4 < CurTime()) then + serverguard.netstream.Start("sgSandboxChangeSetting", { + k, self:GetValue() + }); + + self.nextUpdate = nil; + end; + end; + + base.panel.list:AddPanel(panel); + elseif (SG_DEBUG or SG_UI_DEBUG) then + ErrorNoHalt("The '"..v.."' does not exist!\n"..debug.traceback().."\n"); + end; + end; +end; + +plugin:AddSubCategory("Server settings", category); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/sandbox/init.lua b/garrysmod/addons/admin-sg/lua/plugins/sandbox/init.lua new file mode 100644 index 0000000..c11af1a --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/sandbox/init.lua @@ -0,0 +1,23 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED) + +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) + +serverguard.netstream.Hook("sgSandboxChangeSetting", function(player, data) + if (player:IsAdmin()) then + local conVar = plugin.convars[data[1]]; + + if (conVar) then + local newValue = math.Round(tonumber(data[2])); + + game.ConsoleCommand(conVar.." "..tostring(newValue).."\n"); + end; + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/sandbox/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/sandbox/shared.lua new file mode 100644 index 0000000..01f9f8f --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/sandbox/shared.lua @@ -0,0 +1,148 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Sandbox Settings"; +plugin.author = "alexgrist"; +plugin.version = "1.0"; +plugin.description = "Sandbox settings."; +plugin.permissions = {"Sandbox settings"}; + +plugin.convars = { + "sbox_maxballoons", + "sbox_maxbuttons", + "sbox_maxdynamite", + "sbox_maxeffects", + "sbox_maxemitters", + "sbox_maxhoverballs", + "sbox_maxlamps", + "sbox_maxlights", + "sbox_maxnpcs", + "sbox_maxprops", + "sbox_maxragdolls", + "sbox_maxsents", + "sbox_maxspawners", + "sbox_maxthrusters", + "sbox_maxturrets", + "sbox_maxvehicles", + "sbox_maxwheels", + + "sbox_maxdoors", + "sbox_maxhoverboards", + "sbox_maxkeypads", + "sbox_maxwire_keypads", + + "sbox_maxwire_addressbuss", + "sbox_maxwire_adv_emarkers", + "sbox_maxwire_adv_inputs", + "sbox_maxwire_buttons", + "sbox_maxwire_cameracontrollers", + "sbox_maxwire_cd_disks", + "sbox_maxwire_cd_locks", + "sbox_maxwire_cd_rays", + "sbox_maxwire_clutchs", + "sbox_maxwire_colorers", + "sbox_maxwire_consolescreens", + "sbox_maxwire_cpus", + "sbox_maxwire_damage_detectors", + "sbox_maxwire_data_satellitedishs", + "sbox_maxwire_data_stores", + "sbox_maxwire_data_transferers", + "sbox_maxwire_dataplugs", + "sbox_maxwire_dataports", + "sbox_maxwire_datarates", + "sbox_maxwire_datasockets", + "sbox_maxwire_deployers", + "sbox_maxwire_detonators", + "sbox_maxwire_dhdds", + "sbox_maxwire_digitalscreens", + "sbox_maxwire_dual_inputs", + "sbox_maxwire_dynamic_buttons", + "sbox_maxwire_egps", + "sbox_maxwire_emarkers", + "sbox_maxwire_exit_points", + "sbox_maxwire_explosives", + "sbox_maxwire_expressions", + "sbox_maxwire_extbuss", + "sbox_maxwire_eyepods", + "sbox_maxwire_forcers", + "sbox_maxwire_freezers", + "sbox_maxwire_fx_emitters", + "sbox_maxwire_gate_angles", + "sbox_maxwire_gate_arithmetics", + "sbox_maxwire_gate_arrays", + "sbox_maxwire_gate_bitwises", + "sbox_maxwire_gate_comparisons", + "sbox_maxwire_gate_entitys", + "sbox_maxwire_gate_logics", + "sbox_maxwire_gate_memorys", + "sbox_maxwire_gate_rangers", + "sbox_maxwire_gate_selections", + "sbox_maxwire_gate_strings", + "sbox_maxwire_gate_times", + "sbox_maxwire_gate_trigs", + "sbox_maxwire_gate_vectors", + "sbox_maxwire_gates", + "sbox_maxwire_gimbals", + "sbox_maxwire_gpss", + "sbox_maxwire_gpus", + "sbox_maxwire_grabbers", + "sbox_maxwire_graphics_tablets", + "sbox_maxwire_gyroscopes", + "sbox_maxwire_hdds", + "sbox_maxwire_holoemitters", + "sbox_maxwire_hologrids", + "sbox_maxwire_hoverballs", + "sbox_maxwire_hoverdrivecontrolers", + "sbox_maxwire_hudindicators", + "sbox_maxwire_hydraulics", + "sbox_maxwire_igniters", + "sbox_maxwire_indicators", + "sbox_maxwire_inputs", + "sbox_maxwire_keyboards", + "sbox_maxwire_keypads", + "sbox_maxwire_lamps", + "sbox_maxwire_las_receivers", + "sbox_maxwire_latchs", + "sbox_maxwire_levers", + "sbox_maxwire_lights", + "sbox_maxwire_locators", + "sbox_maxwire_motors", + "sbox_maxwire_nailers", + "sbox_maxwire_numpads", + "sbox_maxwire_oscilloscopes", + "sbox_maxwire_outputs", + "sbox_maxwire_pixels", + "sbox_maxwire_plugs", + "sbox_maxwire_pods", + "sbox_maxwire_radios", + "sbox_maxwire_rangers", + "sbox_maxwire_relays", + "sbox_maxwire_screens", + "sbox_maxwire_sensors", + "sbox_maxwire_simple_explosives", + "sbox_maxwire_sockets", + "sbox_maxwire_soundemitters", + "sbox_maxwire_spawners", + "sbox_maxwire_speedometers", + "sbox_maxwire_spus", + "sbox_maxwire_target_finders", + "sbox_maxwire_textreceivers", + "sbox_maxwire_textscreens", + "sbox_maxwire_thrusters", + "sbox_maxwire_trails", + "sbox_maxwire_turrets", + "sbox_maxwire_twoway_radios", + "sbox_maxwire_users", + "sbox_maxwire_values", + "sbox_maxwire_vectorthrusters", + "sbox_maxwire_vehicles", + "sbox_maxwire_watersensors", + "sbox_maxwire_waypoints", + "sbox_maxwire_weights", + "sbox_maxwire_wheels" +}; diff --git a/garrysmod/addons/admin-sg/lua/plugins/scoreboard/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/scoreboard/cl_init.lua new file mode 100644 index 0000000..112767f --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/scoreboard/cl_init.lua @@ -0,0 +1,356 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); + +surface.CreateFont("serverguard.scoreboard.header", {font = "Roboto", size = 32, weight = 500}) +surface.CreateFont("serverguard.scoreboard.gamemode", {font = "Roboto", size = 18, weight = 500}) +surface.CreateFont("serverguard.scoreboard.player", {font = "Roboto", size = 18, weight = 500}) +surface.CreateFont("serverguard.scoreboard.counts", {font = "Roboto Bt", size = 11, weight = 500}) +surface.CreateFont("serverguard.scoreboard.button", {font = "Arial", size = 12, weight = 500}) + +local scoreboard = nil + +local counts = { + "Props", + "Ragdolls", + "Effects", + "Sents", + "Npcs", + "Vehicles", + "Balloons", + "Buttons", + "Dynamite", + "Effects", + "Emitters", + "Hoverballs", + "Lamps", + "Lights", + "Thrusters", + "Wheels", +} + +plugin:Hook("ScoreboardShow", "scoreboard.ScoreboardShow", function() + if (!IsValid(scoreboard)) then + scoreboard = vgui.Create("serverguard.scoreboard") + end + + scoreboard:Toggle(true) + + return true +end) + +plugin:Hook("ScoreboardHide", "scoreboard.ScoreboardHide", function() + if (IsValid(scoreboard)) then + scoreboard:Toggle(false) + end + + return true +end) + +local panel = {} + +function panel:Init() + self:SetSize(650, 600) + self:Center() + self:DockPadding(8, 76, 8, 10) + + self.list = self:Add("DScrollPanel") + self.list:Dock(FILL) + + self:InvalidateLayout(true) +end + +function panel:AddPlayer(pPlayer) + local base = self + + local panel = self.list:Add("Panel") + panel:SetTall(32) + panel:DockMargin(3, 3, 3, 1) + panel:Dock(TOP) + + panel.player = pPlayer + + panel.buttonBase = panel:Add("Panel") + panel.buttonBase:SetTall(16) + + function panel.buttonBase:Paint(w, h) + --local theme = serverguard:GetTheme(serverguard.theme:GetString()) + + --draw.RoundedBox(4, 0, 0, w, h, theme.color_menu_top) + --draw.RoundedBox(4, 1, 1, w -2, h -2, theme.color_menu_background) + --draw.SimpleRect(w /2 +1, 0, 1, h, theme.color_menu_top) + + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_base_outline) + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_base_footer_bg) + end + + function panel.Think(_self) + if (!IsValid(_self.player)) then + self:Rebuild() + else + if (serverguard.player:HasPermission(LocalPlayer(), "Kick")) then + if (!IsValid(panel.buttonBase.kick)) then + panel.buttonBase.kick = panel.buttonBase:Add("DLabel") + panel.buttonBase.kick:Dock(LEFT) + panel.buttonBase.kick:SetFont("serverguard.scoreboard.button") + panel.buttonBase.kick:SetText("KICK") + panel.buttonBase.kick:SizeToContents() + panel.buttonBase.kick:DockMargin(4, 5, 4, 4) + + util.InstallHandHover(panel.buttonBase.kick) + + function panel.buttonBase.kick:OnMousePressed() + local command = serverguard.command:FindByID("kick") + local rankData = serverguard.ranks:GetRank(serverguard.player:GetRank(LocalPlayer())) + + local menu = DermaMenu() + menu:SetSkin("serverguard"); + command:ContextMenu(panel.player, menu, rankData) + menu:Open() + end + + serverguard.themes.AddPanel(panel.buttonBase.kick, "tiger_base_section_label") + end + + if (!panel.buttonBase:IsVisible()) then + panel.buttonBase:SetVisible(true) + end + else + if (panel.buttonBase:IsVisible()) then + panel.buttonBase:SetVisible(false) + end + end + + if (serverguard.player:HasPermission(LocalPlayer(), "Ban")) then + if (!IsValid(panel.buttonBase.ban)) then + panel.buttonBase.ban = panel.buttonBase:Add("DLabel") + panel.buttonBase.ban:Dock(LEFT) + panel.buttonBase.ban:SetFont("serverguard.scoreboard.button") + panel.buttonBase.ban:SetText("BAN") + panel.buttonBase.ban:SizeToContents() + panel.buttonBase.ban:DockMargin(4, 5, 4, 4) + + util.InstallHandHover(panel.buttonBase.ban) + + function panel.buttonBase.ban:OnMousePressed() + local command = serverguard.command:FindByID("ban") + local rankData = serverguard.ranks:GetRank(serverguard.player:GetRank(LocalPlayer())) + + local menu = DermaMenu() + menu:SetSkin("serverguard"); + command:ContextMenu(panel.player, menu, rankData) + menu:Open() + end + + serverguard.themes.AddPanel(panel.buttonBase.ban, "tiger_base_section_label") + end + else + if (IsValid(panel.buttonBase.ban)) then + panel.buttonBase.ban:Remove() + panel.buttonBase:InvalidateLayout() + end + end + end + end + + function panel:PerformLayout() + local w, h = self:GetSize() + + panel.buttonBase:SizeToChildren(true, false) + panel.buttonBase:SetWide(panel.buttonBase:GetWide() +4) + panel.buttonBase:SetPos(w -(75 +panel.buttonBase:GetWide()), 8) + end + + function panel:Paint(w, h) + if (IsValid(self.player)) then + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, 32, theme.tiger_base_outline) + draw.RoundedBox(2, 1, 1, w -2, 32 -2, theme.tiger_list_panel_list_bg_dark) + + --draw.SimpleRect(1, 1, w -2, 31, theme.tiger_list_panel_list_bg_dark) + + if (IsValid(pPlayer)) then + draw.SimpleText(serverguard.player:GetName(pPlayer), "serverguard.scoreboard.player", 62, 16, theme.tiger_base_section_label, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(pPlayer:Ping(), "serverguard.scoreboard.player", w -18, 16, theme.tiger_base_section_label, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + end + + function panel:OnMousePressed() + if (IsValid(self.infoPanel) and self.infoPanel:IsVisible()) then + self:SizeTo(self:GetWide(), 32, 0.2, 0, 0.2, function() self.infoPanel:SetVisible(false) end) + else + self:SizeTo(self:GetWide(), 32 +18, 0.2, 0, 0.2) + + surface.PlaySound("garrysmod/ui_click.wav") + + if (!IsValid(self.infoPanel)) then + self.infoPanel = self:Add("Panel") + self.infoPanel:SetPos(32, 31) + self.infoPanel:SetSize(self:GetWide() -32, 19) + + function self.infoPanel:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBoxEx(8, 0, 0, w, h, theme.tiger_base_outline, false, false, true, true) + draw.RoundedBoxEx(8, 1, 1, w -2, h -2, theme.tiger_list_panel_list_bg_dark, false, false, true, true) + --draw.SimpleRect(0, 0, w, 1, theme.tiger_base_outline) + end + + for k, text in pairs(counts) do + local label = self.infoPanel:Add("DLabel") + label:SetFont("serverguard.scoreboard.counts") + label:Dock(LEFT) + label:DockMargin(10, 1, 0, 0) + + label.value = panel.player:GetCount(string.lower(text)) + label:SetText(text .. ": " .. label.value) + label:SizeToContents() + + serverguard.themes.AddPanel(label, "tiger_base_section_label") + + function label:Think() + local value = panel.player:GetCount(string.lower(text)) + + if (value != self.value) then + self:SetText(text .. ": " .. value) + self:SizeToContents() + + self.value = value + end + end + end + else + self.infoPanel:SetVisible(true) + end + end + end + + function panel:OnCursorEntered() + self:SetCursor("hand") + + surface.PlaySound("garrysmod/ui_hover.wav") + end + + function panel:OnCursorExited() + self:SetCursor("arrow") + end + + panel.avatar = panel:Add("AvatarImage") + panel.avatar:SetSize(32, 32) + panel.avatar:SetPlayer(pPlayer, 32) + panel.avatar:SetCursor("hand") + + function panel.avatar:OnMouseReleased(keyCode) + if (IsValid(pPlayer) and keyCode == MOUSE_LEFT) then + pPlayer:ShowProfile() + end + end + + panel.rankImage = panel:Add("DImage") + panel.rankImage:SetSize(16, 16) + panel.rankImage:SetPos(38, 8) + panel.rankImage:SetMouseInputEnabled(true) + + function panel.rankImage:Think() + if (IsValid(panel.player)) then + local rank = serverguard.ranks:GetRank(serverguard.player:GetRank(panel.player)) + + if (type(rank) == "table" and self:GetImage() != rank.texture) then + self:SetImage((rank.texture != "" and rank.texture or "icon16/user.png")) + self:SetToolTip(rank.name) + + panel:SetZPos(rank.immunity *-1) + + base.list:InvalidateLayout() + end + end + end + + self.list:InvalidateLayout(true) + + pPlayer.sg_scoreboard = true +end + +function panel:Rebuild() + self.list:Clear() + + for k, pPlayer in ipairs(player.GetAll()) do + pPlayer.sg_scoreboard = nil + end +end + +function panel:Think() + for k, pPlayer in ipairs(player.GetAll()) do + if (IsValid(pPlayer) and !pPlayer.sg_scoreboard) then + self:AddPlayer(pPlayer) + end + end +end + +function panel:Toggle(show) + gui.EnableScreenClicker(show) + + if (show) then + self:SetVisible(true) + self:SetAlpha(0) + self:AlphaTo(255, 0.15, 0) + + RestoreCursorPosition() + + self.show = true + else + self.show = nil + + RememberCursorPosition() + + self:AlphaTo(0, 0.15, 0, function() if (!self.show) then self:SetVisible(false) self.show = nil end end) + end +end + +function panel:Paint(w, h) + local theme = serverguard.themes.GetCurrent() + + draw.RoundedBox(4, 0, 0, w, h, theme.tiger_base_outline) + draw.RoundedBox(2, 1, 1, w -2, h -2, theme.tiger_base_bg) + + draw.SimpleText(GetHostName(), "serverguard.scoreboard.header", w /2, 26, theme.tiger_base_section_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(GAMEMODE.Name, "serverguard.scoreboard.gamemode", w /2, 56, theme.tiger_base_section_label, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + DisableClipping(true) + for i = 1, 2 do + local color = Color(0, 0, 0, (255 /i) *0.2) + + surface.SetDrawColor(color) + + -- Right shadow. + surface.DrawRect(w -1 +i, 1, 1, h -1) + + -- Top shadow. + surface.DrawRect(1, -i, w -1, 1) + + -- Left shadow. + surface.DrawRect(-i, 0, 1, h) + + -- Bottom shadow. + surface.DrawRect(1, h, w -1, i) + end + DisableClipping(false) + + -- List background. + local x, y = self.list:GetPos() + local w, h = self.list:GetSize() + + draw.SimpleRect(x, y, w, h +3, theme.tiger_base_footer_bg) +end + +vgui.Register("serverguard.scoreboard", panel, "Panel") \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/scoreboard/init.lua b/garrysmod/addons/admin-sg/lua/plugins/scoreboard/init.lua new file mode 100644 index 0000000..0d49879 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/scoreboard/init.lua @@ -0,0 +1,9 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/scoreboard/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/scoreboard/shared.lua new file mode 100644 index 0000000..fc05ad4 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/scoreboard/shared.lua @@ -0,0 +1,13 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Scoreboard"; +plugin.author = "alexgrist"; +plugin.version = "1.0"; +plugin.description = "A scoreboard with administration functionality."; +plugin.gamemodes = {"sandbox", "spacebuild"}; \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/screencap/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/screencap/cl_init.lua new file mode 100644 index 0000000..f776573 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/screencap/cl_init.lua @@ -0,0 +1,10 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT) +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT) diff --git a/garrysmod/addons/admin-sg/lua/plugins/screencap/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/screencap/cl_panel.lua new file mode 100644 index 0000000..b28af9b --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/screencap/cl_panel.lua @@ -0,0 +1,146 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local category = {}; + +category.name = "Screen capture"; +category.material = "serverguard/menuicons/icon_camera.png"; +category.permissions = "Screencap"; + +local pending = {} +netstream.Hook('sg.octolib-grab', function(ply, r) + local act = pending[ply] + if not act then return end + + act(r) +end) + +function category:Create(base) + base.panel = base:Add("tiger.panel"); + base.panel:SetTitle("Screen capture"); + base.panel:Dock(FILL); + + base.panel.list = base.panel:Add("tiger.list"); + base.panel.list:Dock(FILL); + + base.panel.list:AddColumn("PLAYER", 320); + base.panel.list:AddColumn("STEAMID", 200); + base.panel.list:AddColumn("VIEW & CAPTURE", 100):SetDisabled(true); + + function base.panel.list:Think() + local players = player.GetHumans(); + + for i = 1, #players do + local pPlayer = players[i]; + + if (!IsValid(pPlayer.screenPanel)) then + local panel = base.panel.list:AddItem(serverguard.player:GetName(pPlayer), pPlayer:SteamID()); + + panel.player = pPlayer; + panel.unique = pPlayer:UniqueID(); + + function panel:OnMousePressed(code) + end; + + function panel:Think() + if (!IsValid(self.player)) then + self:Remove(); + + base.panel.list:GetCanvas():InvalidateLayout(); + + timer.Simple(FrameTime() *2, function() + base.panel.list:OnSort(); + end); + end; + end; + + local nameLabel = panel:GetLabel(1); + + nameLabel:SetUpdate(function(self) + if (IsValid(pPlayer)) then + if (self:GetText() != serverguard.player:GetName(pPlayer)) then + self:SetText(serverguard.player:GetName(pPlayer)); + end; + end; + end); + + local lastBase = vgui.Create("Panel"); + lastBase.rotate = 0; + lastBase.progress = 0; + lastBase.SizeToColumn = true; + + lastBase.capture = lastBase:Add("DImageButton"); + lastBase.capture:SetSize(16, 16); + lastBase.capture:SetImage("icon16/film_add.png"); + lastBase.capture:SetToolTipSG("Capture Screen"); + local immAdmin, immTarget = serverguard.player:GetImmunity(LocalPlayer()), serverguard.player:GetImmunity(pPlayer) + lastBase.capture:SetVisible(immAdmin >= immTarget) + + lastBase.view = lastBase:Add("DImageButton"); + lastBase.view:SetSize(16, 16); + lastBase.view:SetImage("icon16/film_go.png"); + lastBase.view:SetToolTipSG("View Image"); + lastBase.view:SetVisible(false); + + function lastBase.view:DoClick() + octoesc.OpenURL(self.link) + end; + + function lastBase.capture:DoClick() + lastBase.ready = false + lastBase.capture:SetVisible(false) + lastBase.view:SetVisible(false) + + timer.Create('screenCapTimeout' .. tostring(panel.player), 30, 1, function() + if not IsValid(lastBase) then return end + lastBase.capture:SetVisible(true) + end) + + pending[panel.player] = function(r) + if not IsValid(lastBase) or not IsValid(lastBase.view) then return end + if r then + lastBase.view.link = 'https://imgur.com/' .. r.data.id + end + lastBase.view:SetVisible(true) + lastBase.capture:SetVisible(true) + end + netstream.Start('sg.octolib-grab', panel.player) + end; + + function lastBase:Paint(w, h) end + function lastBase:PerformLayout() + local w, h = self:GetSize(); + + if (self.view:IsVisible()) then + self.capture:SetPos(w / 2 + 12, h / 2 - 8); + self.view:SetPos(w /2 - 12, h / 2 - 8); + else + self.capture:SetPos(w /2 -8, h /2 -8); + end; + + local column = panel:GetThing(3).column; + local x = column:GetPos(); + + self:SetPos(x, 0); + end; + + panel:AddItem(lastBase); + + timer.Simple(FrameTime() *2, function() + local column = panel:GetThing(3).column; + + lastBase:SetSize(column:GetWide() -1, 30); + lastBase:InvalidateLayout(); + end); + + pPlayer.screenPanel = panel; + end; + end; + end; +end; + +plugin:AddSubCategory("Intelligence", category); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/screencap/init.lua b/garrysmod/addons/admin-sg/lua/plugins/screencap/init.lua new file mode 100644 index 0000000..15c99e7 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/screencap/init.lua @@ -0,0 +1,23 @@ +serverguard.AddFolder('screencap'); + +local plugin = plugin; + +plugin:IncludeFile('shared.lua', SERVERGUARD.STATE.SHARED); +plugin:IncludeFile('cl_panel.lua', SERVERGUARD.STATE.CLIENT); + +netstream.Hook('sg.octolib-grab', function(ply, target) + + if not IsValid(target) or not target:IsPlayer() then return end + + local immAdmin, immTarget = serverguard.player:GetImmunity(ply), serverguard.player:GetImmunity(target) + if immAdmin >= immTarget and serverguard.player:HasPermission(ply, 'Screencap') then + octolib.grab.sendToImgur(target):Then(function(r) + netstream.Start(ply, 'sg.octolib-grab', target, r) + end):Catch(function(r) + netstream.Start(ply, 'sg.octolib-grab', target, false) + end) + else + netstream.Start(ply, 'sg.octolib-grab', target, false) + end + +end) diff --git a/garrysmod/addons/admin-sg/lua/plugins/screencap/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/screencap/shared.lua new file mode 100644 index 0000000..148421a --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/screencap/shared.lua @@ -0,0 +1,13 @@ +--[[ + 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Screen Capture"; +plugin.author = "alexgrist"; +plugin.version = "1.0"; +plugin.description = "Capture someones screen and view it."; +plugin.permissions = {"Screencap"}; diff --git a/garrysmod/addons/admin-sg/lua/plugins/songplayer/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/songplayer/cl_init.lua new file mode 100644 index 0000000..d7c9678 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/songplayer/cl_init.lua @@ -0,0 +1,143 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin +local medialib = include( "modules/cl_medialib.lua" ) + +plugin:IncludeFile( "shared.lua", SERVERGUARD.STATE.CLIENT ) +plugin:IncludeFile( "sh_commands.lua", SERVERGUARD.STATE.CLIENT ) +plugin:IncludeFile( "cl_panel.lua", SERVERGUARD.STATE.CLIENT ) + +local volumeConVarName = "serverguard_songplayer_volume" + +plugin.songVolume = CreateClientConVar( volumeConVarName, "50", true ) + +cvars.AddChangeCallback( volumeConVarName, function( conVarName, oldValue, newValue ) + if IsValid( plugin.mediaclip ) then plugin.mediaclip:setVolume( newValue / 100 ) end +end ) + +local function errorWithLoadingSong() + plugin.ui.panel:SetTitle( "Error loading song!" ) + plugin.ui.panel:FadeOut() + + serverguard.PrintConsole( "There was a problem loading the YouTube video!\n" ) +end + +-- Plugin methods. +function plugin:FetchVideoInfo( id, callback ) + if IsValid( medialib ) then medialib:stop() end + if IsValid( plugin.mediaclip ) then plugin.mediaclip:stop() end + + -- Get the service that this link uses + local service = medialib.load( "media" ).guessService( id ) + if not service then errorWithLoadingSong() return end + + -- Create a mediaclip from the link + plugin.mediaclip = service:load( id ) + + -- Query media title (eg youtube video title) + service:query( id, function( err, data ) + if err then + if IsValid( plugin.ui.panel ) then + errorWithLoadingSong() + end + return + end + + callback( data, plugin.mediaclip ) + end ) +end + +function plugin:Play( id, offsetTime ) + offsetTime = offsetTime or 0 + + if IsValid( plugin.ui.panel ) then plugin.ui.panel:Remove() end + plugin.ui.panel = vgui.Create( "serverguard.songplayer" ) + + plugin.startTime = 0 + plugin.currentSongDuration = 0 + + self.ui.panel:SetLoading( true ) + self.ui.panel:SetTitle( "Loading song..." ) + + if not self.ui.panel:IsVisible() then + self.ui.panel:FadeIn() + end + + self:FetchVideoInfo( id, function( data, mediaclip ) + if data then + plugin.currentTitle = data.title + plugin.offsetTime = offsetTime + + if IsValid( plugin.ui.panel ) then + local time = string.FormattedTime( data.duration ) + local title = string.format( "%s [%s]", data.title or "unknown", string.format( "%i:%i", time.m, time.s ) ) + + plugin.ui.panel:SetTitle( title ) + plugin.ui.panel:SetLoading( false ) + end + + timer.Simple( data.duration, function() + if not IsValid( plugin.ui.panel ) then return end + + plugin.ui.panel:FadeOut() + end ) + + plugin.startTime = CurTime() - plugin.offsetTime + plugin.currentSongDuration = data.duration + + -- Play media + mediaclip:play() + mediaclip:seek( offsetTime or 0 ) + mediaclip:setVolume( plugin.songVolume:GetInt() / 100 ) + else + errorWithLoadingSong() + end + end ) +end + +plugin:Hook( "OnContextMenuOpen", "serverguard.songplayer.OnContextMenuOpen", function() + if IsValid( plugin.ui.panel ) and plugin.ui.panel:IsVisible() then + -- some gamemodes like clockwork like to arbitrarily control the context menu, so we + -- have to delay the panel update by a bit to make sure we've got the final result + timer.Simple( 0.1, function() + local x, y = plugin.ui.panel:GetPos() + local parentPanel = vgui.GetWorldPanel() + + if IsValid( g_ContextMenu ) and g_ContextMenu:IsVisible() then + parentPanel = g_ContextMenu + + if IsValid( menubar.Control ) and menubar.Control:IsVisible() then + plugin.ui.panel:SetPos( x, 30 ) + else + plugin.ui.panel:SetPos( x, 0 ) + end + end + + plugin.ui.panel:SetParent( parentPanel ) + plugin.ui.panel:MoveToFront() + end ) + end +end ) + +plugin:Hook( "OnContextMenuClose", "serverguard.songplayer.OnContextMenuClose", function() + if IsValid( plugin.ui.panel ) and plugin.ui.panel:IsVisible() then + local x, y = plugin.ui.panel:GetPos() + + plugin.ui.panel:SetPos( x, 0 ) + plugin.ui.panel:SetParent( vgui.GetWorldPanel() ) + plugin.ui.panel:MoveToFront() + end +end ) + +serverguard.netstream.Hook( "sgSongPlayerPlay", function( data ) + plugin:Play( data[1], data[2] ) +end ) + +serverguard.netstream.Hook( "sgSongPlayerStop", function( data ) + plugin.ui.panel:FadeOut() + if IsValid( plugin.mediaclip ) then plugin.mediaclip:stop() end +end ) diff --git a/garrysmod/addons/admin-sg/lua/plugins/songplayer/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/songplayer/cl_panel.lua new file mode 100644 index 0000000..8320980 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/songplayer/cl_panel.lua @@ -0,0 +1,274 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +plugin.ui = plugin.ui or {}; + +-- Progress bar. +local PANEL = {}; + +function PANEL:Init() + self:SetMouseInputEnabled(false); + self:SetTall(16); + self:SetProgress(0); +end; + +function PANEL:SetProgress(amount) + self.progress = math.Clamp(math.ceil(amount), 0, 100); +end; + +function PANEL:GetProgress() + return self.progress; +end; + +function PANEL:Think() + self:SetProgress( ( ( CurTime() - plugin.startTime ) / plugin.currentSongDuration ) * 100 ) +end; + +function PANEL:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + local progressWidth = math.Clamp(math.ceil(self.progress / 100 * self:GetWide()), 0, width - 4); + + surface.SetDrawColor(theme.tiger_base_section_label); + surface.DrawOutlinedRect(0, 0, width, height); + + surface.SetDrawColor(theme.tiger_base_section_icon_selected); + surface.DrawRect(2, 2, progressWidth, height - 4); + + surface.SetFont("tiger.button"); + + local textWidth, textHeight = surface.GetTextSize(string.FormattedTime(plugin.currentSongDuration, "%2i:%02i")); + surface.SetTextColor(theme.tiger_base_section_label); + surface.SetTextPos(width - textWidth - 2, 0); + surface.DrawText(string.FormattedTime(plugin.currentSongDuration, "%2i:%02i")); + + currentTextWidth, currentTextHeight = surface.GetTextSize(string.FormattedTime(plugin.currentSongDuration, "%2i:%02i")); + if (progressWidth >= currentTextWidth or progressWidth + currentTextWidth > width - textWidth - 2) then + surface.SetTextColor(theme.tiger_list_panel_label_hover); + surface.SetTextPos(0, 0); + else + surface.SetTextColor(theme.tiger_base_section_icon_selected); + surface.SetTextPos(progressWidth, 0); + end; + surface.DrawText(string.FormattedTime( CurTime() - plugin.startTime, "%2i:%02i")); +end; + +vgui.Register("serverguard.songplayer.bar", PANEL, "Panel"); + +-- Slider button. +local PANEL = {}; + +AccessorFunc(PANEL, "m_bDragging", "Dragging", FORCE_BOOL); + +function PANEL:Init() + self:SetMouseInputEnabled(true); + self:SetSize(16, 16); + + self.circleRadius = 4; + self.targetCircleRadius = 4; + + self:SetDragging(false); +end; + +function PANEL:OnMousePressed(keyCode) + if (keyCode != MOUSE_LEFT) then + return; + end; + + self:SetDragging(true); +end; + +function PANEL:Think() + if (self:GetDragging()) then + self.targetCircleRadius = 6; + + if (!input.IsMouseDown(MOUSE_LEFT)) then + self:SetDragging(false); + end; + else + self.targetCircleRadius = 4; + end; +end; + +function PANEL:Paint(width, height) + local theme = serverguard.themes.GetCurrent(); + + if (self.circleRadius != self.targetCircleRadius) then + self.circleRadius = math.Approach(self.circleRadius, self.targetCircleRadius, 30 * FrameTime()); + end; + + if (self:GetDragging()) then + DisableClipping(true); + end; + + surface.SetDrawColor(theme.tiger_base_section_icon_selected); + draw.NoTexture(); + draw.Circle(self.circleRadius, self:GetTall() / 2, self.circleRadius, self.circleRadius * 2); + + DisableClipping(false); +end; + +vgui.Register("serverguard.songplayer.sliderbutton", PANEL, "Panel"); + +-- Volume slider. +local PANEL = {} + +function PANEL:Init() + self:SetMouseInputEnabled(true) + self:SetSize(116, 16) + + self.button = self:Add("serverguard.songplayer.sliderbutton") + self.button:SetPos(0, 0) +end; + +function PANEL:Think() + if (self.button:GetDragging()) then + local x, y = self.button:GetPos() + local mouseX, mouseY = self:CursorPos() + + self.button:SetPos(math.Clamp(mouseX - 6, 0, self:GetWide() - 8), y) + + if (plugin.songVolume:GetInt() != self:GetAmount()) then + plugin.songVolume:SetInt( self:GetAmount() ) + + if IsValid( plugin.mediaclip ) then plugin.mediaclip:setVolume( plugin.songVolume:GetInt() / 100 ) end + end + end +end + +function PANEL:Paint(width, height) + local theme = serverguard.themes.GetCurrent() + + surface.SetDrawColor(theme.tiger_base_section_label) + surface.DrawRect(0, 7, self:GetWide(), 2) +end; + +function PANEL:SetAmount(amount) + local x = math.Clamp(math.ceil(math.Clamp(amount, 0, 100) / 100 * self:GetWide()), 0, self:GetWide()) + self.button:SetPos(x, 0) +end; + +function PANEL:GetAmount() + local buttonX, buttonY = self.button:GetPos() + + return math.ceil(buttonX / self:GetWide() * 100) +end; + +vgui.Register("serverguard.songplayer.slider", PANEL, "Panel") + +-- Main panel. +local PANEL = {}; + +AccessorFunc(PANEL, "m_Title", "Title", FORCE_STRING); +AccessorFunc(PANEL, "m_bLoading", "Loading", FORCE_BOOL); +AccessorFunc(PANEL, "m_bAnimating", "Animating", FORCE_BOOL); + +function PANEL:Init() + local theme = serverguard.themes:GetCurrent(); + + self:SetVisible(false); + self:SetMouseInputEnabled(true); + self:SetSize(180, 32); + self:SetPos(ScrW() / 2, ScrH() / 2); + self:SetPos(ScrW() / 2 - self:GetWide() / 2, 0); + self:DockPadding(6, 4, 6, 4); + + self.label = self:Add("DLabel"); + self.label:SetMouseInputEnabled(true); + self.label:Dock(TOP); + self.label:SetFont("tiger.button"); + self.label:SetTextColor(theme.tiger_button_text); + self.label:SetSkin("serverguard"); + self.label:SetText("None"); + self.label:SizeToContents(); + + self.bar = self:Add("serverguard.songplayer.bar"); + self.bar:DockMargin(0, 6, 0, 0); + self.bar:Dock(TOP); + + self.volume = self:Add("serverguard.songplayer.slider") + self.volume:DockMargin(0, 2, 0, 0) + self.volume:Dock(TOP) + + self:SetTitle("None"); + self:SetLoading(true); + self:SetAnimating(false); + + self:SetTall(self.label:GetTall() + 10); + + self.lastInteractTime = CurTime(); + self.currentAlpha = 255; + + serverguard.themes.AddPanel(self.label, "tiger_button_text"); +end; + +function PANEL:Think() + local width, height = self:GetSize(); + local x, y = self:GetPos(); + + if (gui.MouseX() > x and gui.MouseX() < x + width and + gui.MouseY() > y and gui.MouseY() < y + height) then + if (!self:GetAnimating() and self.currentAlpha < 255) then + self.currentAlpha = math.Approach(self.currentAlpha, 255, 600 * FrameTime()); + self:SetAlpha(self.currentAlpha); + end; + + self.lastInteractTime = CurTime(); + + if (!self:GetLoading()) then + local totalHeight = self.label:GetTall() + self.bar:GetTall() + self.volume:GetTall() + 14; + + self:SetTall(math.Approach(height, totalHeight, 400 * FrameTime())); + end; + else + self:SetTall(math.Approach(height, self.label:GetTall() + 10, 200 * FrameTime())); + + if (!self:GetAnimating() and CurTime() > self.lastInteractTime + 6) then + self.currentAlpha = math.Approach(self.currentAlpha, 100, 50 * FrameTime()); + self:SetAlpha(self.currentAlpha); + end; + end; +end; + +function PANEL:SetTitle(text) + local x, y = self:GetPos(); + + self.label:SetText(text); + self.label:SizeToContents(); + + self:SetWide(self.label:GetWide() + 12); + self:SetPos(ScrW() / 2 - self:GetWide() / 2, y); + + self.bar:SetWide(self:GetWide()); + + self.volume:SetWide(self:GetWide() - 16); + self.volume:SetAmount(plugin.songVolume:GetInt()); + + self.m_Title = text; +end; + +function PANEL:FadeIn() + self:SetAnimating(true); + self:SetVisible(true); + self:SetAlpha(0); + + self:AlphaTo(255, 0.5, 0, function() + self:SetAnimating(false); + self.lastInteractTime = CurTime(); + end); +end; + +function PANEL:FadeOut(callback) + self:SetAnimating(true); + self:SetVisible(true); + + self:AlphaTo(0, 0.5, 0, function() + self:SetVisible(false); + self:SetAnimating(false); + end); +end; + +vgui.Register("serverguard.songplayer", PANEL, "tiger.panel"); diff --git a/garrysmod/addons/admin-sg/lua/plugins/songplayer/init.lua b/garrysmod/addons/admin-sg/lua/plugins/songplayer/init.lua new file mode 100644 index 0000000..f7cf12e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/songplayer/init.lua @@ -0,0 +1,11 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/songplayer/sh_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/songplayer/sh_commands.lua new file mode 100644 index 0000000..daf86fa --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/songplayer/sh_commands.lua @@ -0,0 +1,50 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +local command = {}; + +command.help = "Play a song for the server to listen to."; +command.command = "playsong"; +command.arguments = {"url"}; +command.optionalArguments = {"start time"}; +command.permissions = "Play Song"; + +function command:Execute(player, silent, arguments) + local url = arguments[1]; + local time = tonumber(arguments[2]) or 0; + + if (url) then + serverguard.netstream.Start(nil, "sgSongPlayerPlay", { + url, time + }); + + if (!silent) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has started playing a song."); + end; + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "You have entered an invalid link! Please make sure that it's a valid YouTube link."); + end; +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Stop any songs currently playing."; +command.command = "stopsong"; +command.permissions = "Play Song"; + +function command:Execute(player, silent, arguments) + serverguard.netstream.Start(nil, "sgSongPlayerStop", true); + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has stopped the song player."); + end; +end; + +plugin:AddCommand(command); diff --git a/garrysmod/addons/admin-sg/lua/plugins/songplayer/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/songplayer/shared.lua new file mode 100644 index 0000000..c8023d5 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/songplayer/shared.lua @@ -0,0 +1,13 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Song Player"; +plugin.author = "`impulse, DevulTj"; +plugin.version = "1.0"; +plugin.description = "Plays audio from YouTube videos for the whole server to hear."; +plugin.permissions = {"Play Song"}; diff --git a/garrysmod/addons/admin-sg/lua/plugins/ttt/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/ttt/cl_init.lua new file mode 100644 index 0000000..8864510 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/ttt/cl_init.lua @@ -0,0 +1,32 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.CLIENT); + +plugin:Hook("TTTScoreboardColorForPlayer", "serverguard.ttt.ScoreboardColorForPlayer", function(player) + local rankData = serverguard.ranks:FindByID(serverguard.player:GetRank(player)); + + if (rankData) then + return rankData.color; + end; +end); + +plugin:Hook("TTTScoreboardColumns", "serverguard.ttt.ScoreboardColumns", function(panel) + local label = nil; + + label = panel:AddColumn("Rank", function(player) + local rankData = serverguard.ranks:FindByID(serverguard.player:GetRank(player)); + + if (rankData) then + label:SetTextColor(rankData.color); + + return rankData.name; + end; + end, 150); +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/ttt/init.lua b/garrysmod/addons/admin-sg/lua/plugins/ttt/init.lua new file mode 100644 index 0000000..7dc98df --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/ttt/init.lua @@ -0,0 +1,72 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.SHARED); + +plugin:Hook("serverguard.mysql.CreateTables", "serverguard.ttt.CreateTables", function() + local AUTOSLAY_TABLE_QUERY = serverguard.mysql:Create("serverguard_ttt_autoslays"); + AUTOSLAY_TABLE_QUERY:Create("steam_id", "VARCHAR(255) NOT NULL"); + AUTOSLAY_TABLE_QUERY:Create("amount", "TINYINT UNSIGNED NOT NULL"); + AUTOSLAY_TABLE_QUERY:PrimaryKey("steam_id"); + AUTOSLAY_TABLE_QUERY:Execute(); +end); + +plugin:Hook("PlayerInitialSpawn", "serverguard.ttt.PlayerInitialSpawn", function(player) + local queryObj = serverguard.mysql:Select("serverguard_ttt_autoslays"); + queryObj:Where("steam_id", player:SteamID()); + queryObj:Limit(1); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + player.sg_autoslays = tonumber(result[1].amount); + end; + end); + queryObj:Execute(); +end); + +plugin:Hook("TTTBeginRound", "serverguard.ttt.AutoSlay", function() + local targets = {}; + + for k, v in ipairs(player.GetAll()) do + if (v.sg_autoslays and v.sg_autoslays > 0) then + v:Kill(); + v.sg_autoslays = v.sg_autoslays - 1; + table.insert(targets, v); + serverguard.Notify(v, SGPF("local_autoslayed", v.sg_autoslays)); + + local updateObj = serverguard.mysql:Update("serverguard_ttt_autoslays"); + updateObj:Update("amount", v.sg_autoslays); + updateObj:Where("steam_id", v:SteamID()); + updateObj:Limit(1); + updateObj:Execute(); + end; + + v.sg_dna = nil; + end; + + if (#targets > 0) then + serverguard.Notify(nil, SGPF("global_autoslayed", util.GetNotifyListForTargets(targets))); + end; +end); + +plugin:Hook("DoPlayerDeath", "serverguard.ttt.DoPlayerDeath", function(player, attacker, dmgInfo) + if (IsValid(attacker) and attacker:IsPlayer() and attacker != player) then + if (attacker.sg_dna and attacker.sg_dna[player]) then + serverguard.Notify(player, SGPF(attacker:GetTraitor() and "killed_by_traitor_dna" or "killed_by_innocent_dna", attacker:Name())); + else + serverguard.Notify(player, SGPF(attacker:GetTraitor() and "killed_by_traitor" or "killed_by_innocent", attacker:Name())); + end; + end; +end); + +plugin:Hook("TTTFoundDNA", "serverguard.ttt.FoundDNA", function(player, killer, entity) + if (IsValid(killer)) then + player.sg_dna = player.sg_dna or {}; + player.sg_dna[killer] = true; + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/ttt/sh_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/ttt/sh_commands.lua new file mode 100644 index 0000000..e2cc259 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/ttt/sh_commands.lua @@ -0,0 +1,178 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; +local command = {}; + +command.help = "Add autoslays to a player."; +command.command = "aslay"; +command.arguments = {"player"}; +command.optionalArguments = {"amount", "reason"}; +command.bSingleTarget = true; +command.immunity = SERVERGUARD.IMMUNITY.LESS; +command.aliases = {"slaynr", "autoslay"}; +command.permissions = "SlayNR"; + +function command:OnPlayerExecute(player, target, arguments) + local clamped = nil; + + arguments[2] = arguments[2] or 1; + clamped = math.Clamp((target.sg_autoslays or 0) + arguments[2], 0, 99); + target.sg_autoslays = clamped; + + local queryObj = serverguard.mysql:Select("serverguard_ttt_autoslays"); + queryObj:Where("steam_id", target:SteamID()); + queryObj:Limit(1); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + local updateObj = serverguard.mysql:Update("serverguard_ttt_autoslays"); + updateObj:Update("amount", target.sg_autoslays); + updateObj:Where("steam_id", target:SteamID()); + updateObj:Limit(1); + updateObj:Execute(); + else + local insertObj = serverguard.mysql:Insert("serverguard_ttt_autoslays"); + insertObj:Insert("steam_id", target:SteamID()); + insertObj:Insert("amount", target.sg_autoslays); + insertObj:Execute(); + end; + end); + queryObj:Execute(); + + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_aslay", serverguard.player:GetName(player), arguments[2], util.GetNotifyListForTargets(targets), table.concat(arguments, " ", 3)); +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Remove autoslays from a player."; +command.command = "raslay"; +command.arguments = {"player"}; +command.optionalArguments = {"amount"}; +command.bSingleTarget = true; +command.immunity = SERVERGUARD.IMMUNITY.ANY; +command.aliases = {"raslaynr", "rslaynr"}; +command.permissions = "Remove SlayNR"; + +function command:OnPlayerExecute(player, target, arguments) + local clamped = nil; + + arguments[2] = arguments[2] or 1; + clamped = math.Clamp((target.sg_autoslays or 0) - arguments[2], 0, 99); + target.sg_autoslays = clamped; + + local updateObj = serverguard.mysql:Update("serverguard_ttt_autoslays"); + updateObj:Update("amount", target.sg_autoslays); + updateObj:Where("steam_id", target:SteamID()); + updateObj:Limit(1); + updateObj:Execute(); + + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_raslay", serverguard.player:GetName(player), arguments[2], util.GetNotifyListForTargets(targets)); +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Set a player's karma."; +command.command = "setkarma"; +command.arguments = {"player"}; +command.optionalArguments = {"amount"}; +command.immunity = SERVERGUARD.IMMUNITY.LESS; +command.aliases = {"karma"}; +command.permissions = "Set Karma"; + +function command:OnPlayerExecute(player, target, arguments) + arguments[2] = tonumber(arguments[2]) or 1000; + target:SetBaseKarma(arguments[2]); + target:SetLiveKarma(arguments[2]); + + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_setkarma", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets, true), arguments[2]); +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Set a player's credits."; +command.command = "setcredits"; +command.arguments = {"player", "amount"}; +command.immunity = SERVERGUARD.IMMUNITY.ANY; +command.aliases = {"credits"}; +command.permissions = "Set Credits"; + +function command:OnPlayerExecute(player, target, arguments) + arguments[2] = tonumber(arguments[2]); + + if (arguments[2]) then + arguments[2] = math.Clamp(arguments[2], 0, 200); + target:SetCredits(arguments[2]); + return true; + end; + + serverguard.Notify(player, SGPF("invalid_number")); +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_setcredits", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets, true), arguments[2]); +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Set a player's role."; +command.command = "setrole"; +command.arguments = {"player", "role"}; +command.immunity = SERVERGUARD.IMMUNITY.LESS; +command.bSingleTarget = true; +command.aliases = {"role", "setteam", "team"}; +command.permissions = "Set Role"; + +function command:OnPlayerExecute(player, target, arguments) + local role = plugin.roles[string.lower(arguments[2])]; + + if (role) then + target:SetRole(role); + SendFullStateUpdate(); + return true; + end; + + serverguard.Notify(player, SGPF("invalid_role")); +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_setrole", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets, true), arguments[2]); +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Restart the round."; +command.command = "restartround"; +command.aliases = {"roundrestart"}; +command.permissions = "Restart Round"; + +function command:Execute(player, silent, arguments) + RunConsoleCommand("ttt_roundrestart"); + serverguard.Notify(nil, SGPF("command_restartround", serverguard.player:GetName(player))); +end; + +plugin:AddCommand(command); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/ttt/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/ttt/shared.lua new file mode 100644 index 0000000..3bd4e02 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/ttt/shared.lua @@ -0,0 +1,86 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Trouble in Terrorist Town"; +plugin.author = "duck"; +plugin.version = "1.0"; +plugin.description = "Adds various commands for TTT."; +plugin.gamemodes = {"terrortown"}; + +serverguard.phrase:Add("english", "command_aslay", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has added ", SERVERGUARD.NOTIFY.RED, "%d", SERVERGUARD.NOTIFY.WHITE, " autoslay(s) to ", + SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". Reason: %s" +}); + +serverguard.phrase:Add("english", "command_raslay", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has removed ", SERVERGUARD.NOTIFY.RED, "%d", SERVERGUARD.NOTIFY.WHITE, " autoslay(s) from ", + SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, "." +}); + +serverguard.phrase:Add("english", "global_autoslayed", { + SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " has been automatically slayed." +}); + +serverguard.phrase:Add("english", "local_autoslayed", { + SERVERGUARD.NOTIFY.WHITE, "You have ", SERVERGUARD.NOTIFY.RED, "%d", SERVERGUARD.NOTIFY.WHITE, " autoslay(s) remaining." +}); + +serverguard.phrase:Add("english", "command_setkarma", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " karma to ", + SERVERGUARD.NOTIFY.GREEN, "%d", SERVERGUARD.NOTIFY.WHITE, "." +}); + +serverguard.phrase:Add("english", "command_setcredits", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " credits to ", + SERVERGUARD.NOTIFY.GREEN, "%d", SERVERGUARD.NOTIFY.WHITE, "." +}); + +serverguard.phrase:Add("english", "command_restartround", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has restarted the round." +}); + +serverguard.phrase:Add("english", "command_setrole", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has set ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, " role to ", + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, "." +}); + +serverguard.phrase:Add("english", "killed_by_traitor", { + SERVERGUARD.NOTIFY.WHITE, "You were killed by ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". He was a ", SERVERGUARD.NOTIFY.RED, "traitor", + SERVERGUARD.NOTIFY.WHITE, "." +}); + +serverguard.phrase:Add("english", "killed_by_traitor_dna", { + SERVERGUARD.NOTIFY.WHITE, "You were killed by ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". He was a ", SERVERGUARD.NOTIFY.RED, "traitor", + SERVERGUARD.NOTIFY.WHITE, ", and had your DNA." +}); + +serverguard.phrase:Add("english", "killed_by_innocent", { + SERVERGUARD.NOTIFY.WHITE, "You were killed by ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". He was ", SERVERGUARD.NOTIFY.GREEN, "innocent", + SERVERGUARD.NOTIFY.WHITE, "." +}); + +serverguard.phrase:Add("english", "killed_by_innocent_dna", { + SERVERGUARD.NOTIFY.WHITE, "You were killed by ", SERVERGUARD.NOTIFY.RED, "%s", SERVERGUARD.NOTIFY.WHITE, ". He was ", SERVERGUARD.NOTIFY.GREEN, "innocent", + SERVERGUARD.NOTIFY.WHITE, ", and had your DNA." +}); + +serverguard.phrase:Add("english", "invalid_role", { + SERVERGUARD.NOTIFY.RED, "You must specify a valid role (innocent, detective, traitor)." +}); + +serverguard.phrase:Add("english", "invalid_number", { + SERVERGUARD.NOTIFY.RED, "You must specify a valid number." +}); + +plugin:Hook("OnGamemodeLoaded", "serverguard.ttt.OnGamemodeLoaded", function() + plugin.roles = { + traitor = ROLE_TRAITOR, + innocent = ROLE_INNOCENT, + detective = ROLE_DETECTIVE + }; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/votes/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/votes/cl_init.lua new file mode 100644 index 0000000..90bfc2b --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/votes/cl_init.lua @@ -0,0 +1,41 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +plugin:Hook("PlayerBindPress", "votes.PlayerBindPress", function(player, bind, pressed) + if (plugin.vote) then + local option = tonumber(bind:match("slot(%d)")); + + if (option and plugin.vote.options[option]) then + serverguard.netstream.Start("CastVote", option); + + plugin.vote = nil; + + if (IsValid(plugin.panel)) then + plugin.panel.checkboxes[option]:SetChecked(true); + plugin.panel:FadeOut(); + end; + + return true; + end; + end; +end); + +serverguard.netstream.Hook("CreateVote", function(data) + plugin.vote = data; + plugin.panel = vgui.Create("tiger.panel.vote"); + + timer.Simple(15, function() + if (IsValid(plugin.panel)) then + plugin.panel:FadeOut(); + end; + end); +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/votes/cl_panel.lua b/garrysmod/addons/admin-sg/lua/plugins/votes/cl_panel.lua new file mode 100644 index 0000000..9c0d78e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/votes/cl_panel.lua @@ -0,0 +1,48 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +local panel = {}; + +function panel:Init() + self.checkboxes = {}; + self:DockPadding(16, 16, 20, 32); + self:SetPos(45, 0); + self:SetAlpha(0); + + self.label = vgui.Create("DLabel", self); + self.label:SetSkin("serverguard"); + self.label:SetFont("DefaultBold"); + self.label:DockMargin(8, 0, 0, 8); + self.label:Dock(TOP); + self.label:SetText(plugin.vote.title); + self.label:SizeToContents(); + self.label:SetWrap(true); + self.label:SetAutoStretchVertical(true); + + for k, v in ipairs(plugin.vote.options) do + local checkbox = vgui.Create("tiger.checkbox", self); + checkbox:SetText(tostring(k)..". "..v); + checkbox:Dock(TOP); + table.insert(self.checkboxes, checkbox); + end; + + self:AlphaTo(255, 0.5, 0, function() end); +end; + +function panel:FadeOut() + self:AlphaTo(0, 0.5, 1, function() + self:Remove(); + end); +end; + +function panel:PerformLayout(width, height) + self:SetSize(187, 40 + (#self.checkboxes * 30) + self.label:GetTall()); + self:CenterVertical(0.4); +end; + +vgui.Register("tiger.panel.vote", panel, "tiger.panel"); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/votes/init.lua b/garrysmod/addons/admin-sg/lua/plugins/votes/init.lua new file mode 100644 index 0000000..40f3a45 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/votes/init.lua @@ -0,0 +1,90 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.tally = {}; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.SHARED); +plugin:IncludeFile("cl_panel.lua", SERVERGUARD.STATE.CLIENT); + +function plugin:CreateVote(title, options, callback, bDefaultOptions, bRandomWinner) + if (!plugin.vote) then + if (#options < 2 and bDefaultOptions) then + table.insert(options, "Yes"); + table.insert(options, "No"); + end; + + plugin.vote = { + title = title, + options = options + }; + + serverguard.netstream.Start(nil, "CreateVote", plugin.vote); + + timer.Simple(16.5, function() + local option = table.GetWinningKey(plugin.tally); + + if (!option) then + if (bRandomWinner) then + local winner = table.Random(plugin.vote.options); + + serverguard.Notify(nil, SGPF("vote_tied_random", winner)); + + if (callback) then + callback(winner); + end; + + plugin.vote = nil; + plugin.tally = {}; + + return; + end + + serverguard.Notify(nil, SGPF("vote_tied")); + + plugin.vote = nil; + plugin.tally = {}; + + return; + end + + local optionText = plugin.vote.options[option]; + local winnerVotes, totalVotes = 0, 0; + + for k, v in pairs(plugin.tally) do + if (k == option) then + winnerVotes = v; + end; + + totalVotes = totalVotes + v; + end; + + plugin.vote = nil; + plugin.tally = {}; + + for k, v in pairs(player.GetAll()) do + v.sg_voted = nil; + end; + + serverguard.Notify(nil, SGPF("vote_winner", optionText, winnerVotes, totalVotes)); + + if (callback) then + callback(optionText, winnerVotes, totalVotes); + end; + end); + else + return false; + end; +end; + +serverguard.netstream.Hook("CastVote", function(player, data) + if (plugin.vote and !player.sg_voted and plugin.vote.options[data]) then + player.sg_voted = true; + plugin.tally[data] = (plugin.tally[data] or 0) + 1; + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/votes/sh_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/votes/sh_commands.lua new file mode 100644 index 0000000..35b9d1f --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/votes/sh_commands.lua @@ -0,0 +1,107 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +local command = {}; + +command.help = "Create a vote."; +command.command = "vote"; +command.arguments = {"title"}; +command.optionalArguments = {"options"}; +command.permissions = "Custom Vote"; + +function command:Execute(player, silent, arguments) + local title = arguments[1]; + + table.remove(arguments, 1); + + if (!plugin:CreateVote(title, arguments, nil, true)) then + serverguard.Notify(nil, SGPF("command_vote", serverguard.player:GetName(player))); + else + serverguard.Notify(player, SGPF("vote_in_progress")); + end; +end; + +plugin:AddCommand(command); + +local command = {}; + +command.help = "Create a map vote."; +command.command = "votemap"; +command.arguments = {"options"}; +command.permissions = "Vote Map"; + +function command:Execute(player, silent, arguments) + if (!plugin:CreateVote("Vote for the next map", arguments, function(option) + timer.Simple(3, function() + RunConsoleCommand("changelevel", option); + end); + end)) then + + serverguard.Notify(nil, SGPF("command_vote", serverguard.player:GetName(player))); + else + serverguard.Notify(player, SGPF("vote_in_progress")); + end; +end; + +--plugin:AddCommand(command); + +local command = {}; + +command.help = "Create a vote to kick a player."; +command.command = "votekick"; +command.arguments = {"player"}; +command.permissions = "Vote Kick"; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player); + + if (IsValid(target) and util.PlayerMatchesImmunity(SERVERGUARD.IMMUNITY.LESS, player, target)) then + if (!plugin:CreateVote("Kick "..target:Name().."?", {}, function(option) + if (option == "Yes") then + game.ConsoleCommand("kickid " .. target:UserID() .. " " .. reason .. "\n"); + end; + end, true)) then + + serverguard.Notify(nil, SGPF("command_vote", serverguard.player:GetName(player))); + else + serverguard.Notify(player, SGPF("vote_in_progress")); + end; + else + serverguard.Notify(player, SGPF("player_higher_immunity")); + end; +end; + +--plugin:AddCommand(command); + +local command = {}; + +command.help = "Create a vote to ban a player."; +command.command = "voteban"; +command.arguments = {"player"}; +command.permissions = "Vote Ban"; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player); + + if (IsValid(target) and util.PlayerMatchesImmunity(SERVERGUARD.IMMUNITY.LESS, player, target)) then + if (!plugin:CreateVote("Ban "..target:Name().." for 30 minutes?", {}, function(option) + if (option == "Yes") then + serverguard:BanPlayer(nil, target, 30, "Vote Ban"); + end; + end, true)) then + + serverguard.Notify(nil, SGPF("command_vote", serverguard.player:GetName(player))); + else + serverguard.Notify(player, SGPF("vote_in_progress")); + end; + else + serverguard.Notify(player, SGPF("player_higher_immunity")); + end; +end; + +--plugin:AddCommand(command); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/votes/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/votes/shared.lua new file mode 100644 index 0000000..f0444c7 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/votes/shared.lua @@ -0,0 +1,34 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +local plugin = plugin; + +plugin.name = "Votes"; +plugin.author = "duck"; +plugin.version = "0.1"; +plugin.description = "Adds various commands for voting."; +plugin.permissions = {"Custom Vote"}; + +serverguard.phrase:Add("english", "command_vote", { + SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has started a vote." +}); + +serverguard.phrase:Add("english", "vote_winner", { + SERVERGUARD.NOTIFY.WHITE, "Option ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, " has won the vote (%d/", "%d)" +}); + +serverguard.phrase:Add("english", "vote_tied", { + SERVERGUARD.NOTIFY.WHITE, "The vote was inconclusive." +}); + +serverguard.phrase:Add("english", "vote_tied_random", { + SERVERGUARD.NOTIFY.WHITE, "The vote was inconclusive. Option ", SERVERGUARD.NOTIFY.GREEN, "%s", SERVERGUARD.NOTIFY.WHITE, + " was randomly selected as the winner." +}); + +serverguard.phrase:Add("english", "vote_in_progress", { + SERVERGUARD.NOTIFY.RED, "There is already a vote in progress." +}); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/plugins/watchlist/cl_init.lua b/garrysmod/addons/admin-sg/lua/plugins/watchlist/cl_init.lua new file mode 100644 index 0000000..eabd442 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/watchlist/cl_init.lua @@ -0,0 +1,4 @@ +local plugin = plugin; + +plugin:IncludeFile("shared.lua", SERVERGUARD.STATE.CLIENT); +plugin:IncludeFile("sh_commands.lua", SERVERGUARD.STATE.CLIENT); diff --git a/garrysmod/addons/admin-sg/lua/plugins/watchlist/init.lua b/garrysmod/addons/admin-sg/lua/plugins/watchlist/init.lua new file mode 100644 index 0000000..08c7871 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/watchlist/init.lua @@ -0,0 +1,148 @@ +local plugin = plugin; + +plugin:IncludeFile('shared.lua', SERVERGUARD.STATE.SHARED); +plugin:IncludeFile('sh_commands.lua', SERVERGUARD.STATE.SHARED); + +local function removeExpired(wls) + local ct, changed = os.time(), false + for i = #wls, 1, -1 do + local v = wls[i] + if v[4] == '' then continue end + local d, m, y = v[4]:match('(%d%d)%.(%d%d)%.(%d%d%d%d)') + if not (d and m and y) or os.time({day = tonumber(d), month = tonumber(m), year = tonumber(y)}) <= ct then + table.remove(wls, i) + changed = true + end + end + return changed +end + +local function checkDate(d, m, y) + if m < 1 or m > 12 then return false end + if d < 1 or d > 31 then return false end + if (m == 4 or m == 6 or m == 9 or m == 11) and d > 30 then return false end + if m == 2 then + if not (y % 400 == 0 or y % 100 ~= 0 and y % 4 == 0) and d > 28 then return false end + if d > 29 then return false end + end + return true +end + +netvars.Register('watchList', { + checkAccess = function(ply) + return ply:query('DBG: WatchLists') + end, +}) + +function plugin:OpenEditor(admin, target) + if not octolib.string.isSteamID(target) and target ~= 'BOT' then + return admin:Notify('warning', 'Укажи ник онлайн-игрока или SteamID') + end + if admin == target then + admin:Notify('warning', 'Любишь быть в центре внимания?') + return + end + octolib.getDBVar(target, 'wls', {}):Then(function(wls) + if removeExpired(wls) then + octolib.setDBVar(target, 'wls', wls) + end + octolib.entries.gui(admin, 'Watchlist\'ы игрока ' .. target, { + fields = { + { + name = 'Краткое описание*', + desc = 'Название нарушения, сколько дней накидывать к бану', + type = 'strShort', + len = 32, + required = true, + }, + { + name = 'Подробное описание', + desc = 'Что сделал игрок, чтобы получить Watchlist? Опиши одной-двумя фразами', + type = 'strLong', + len = 128, + tall = 60, + ph = 'Расстрелял двух офицеров за отказ брать взятку. Запрет на игру крайм-профессий', + }, + { + name = 'Кем выдан*', + type = 'strShort', + required = true, + len = 16, + ph = 'Wani4ka', + }, + { + name = 'До какой даты действителен', + desc = 'В этот день Watchlist снимется автоматически. Можно оставить пустым', + type = 'strShort', + numeric = true, + ph = 'ДД.ММ.ГГГГ', + }, + }, + labelIndex = 1, + entries = wls, + }, function(res) + if not istable(res) then return end + if #res > 10 then + return admin:Notify('warning', 'Не более 10 одновременно действующих Watchlist\'ов') + end + local ct, ctstr = os.time(), os.date('%d.%m.%Y') + for _,v in ipairs(res) do + v[1] = utf8.sub(string.Trim(v[1] or ''), 1, 32) + v[2] = utf8.sub(string.Trim(v[2] or ''), 1, 128) + v[3] = utf8.sub(string.Trim(v[3] or ''), 1, 16) + v[4] = utf8.sub(string.Trim(v[4] or ''), 1, 10) + if v[1] == '' then + return admin:Notify('warning', v[1], ': Краткое описание Watchlist\'а не должно быть пустым') + end + if v[3] == '' then + return admin:Notify('warning', v[1], ': Укажи, кем выдан этот Watchlist') + end + if v[4] ~= '' then + local d, m, y = v[4]:match('^(%d%d)%.(%d%d)%.(%d%d%d%d)$') + if not (d and m and y) then + return admin:Notify('warning', v[1], ': Укажи правильную дату окончания действия или оставь это поле пустым') + end + d, m, y = tonumber(d), tonumber(m), tonumber(y) + if not (d and m and y) then + return admin:Notify('warning', v[1], ': Укажи правильную дату окончания действия или оставь это поле пустым') + end + if not checkDate(d, m, y) then + return admin:Notify('warning', v[1], ': Укажи правильную дату окончания действия или оставь это поле пустым') + end + if os.time({day = tonumber(d), month = tonumber(m), year = tonumber(y)}) < ct and v[4] ~= ctstr then + return admin:Notify('warning', v[1], ': Дата окончания действия задним числом') + end + end + end + if not res[1] then res = nil end + octolib.setDBVar(target, 'wls', res):Then(function() + admin:Notify('Watchlist\'ы игрока ' .. target .. ' сохранены') + + local ply = player.GetBySteamID(target) + if IsValid(ply) then + ply:SetNetVar('watchList', res and #res or nil) + end + end) + end) + end) +end + +hook.Add('octolib.dbvars-loaded', 'dbg-watch.remove', function(ply) + local wls = ply:GetDBVar('wls') + if not wls then return end + if removeExpired(wls) then + ply:SetDBVar('wls', wls[1] and wls or nil) + end + ply:SetNetVar('watchList', wls[1] and #wls or nil) + + if #wls > 0 then + local admins = {} + for _,v in ipairs(player.GetAll()) do + if v:IsAdmin() then admins[#admins + 1] = v end + end + serverguard.Notify(admins, SGPF('watchlist', ply:Name(), ply:SteamID())) + for _,v in ipairs(wls) do + serverguard.Notify(admins, SGPF('watchlist_note', v[1])) + end + end +end) diff --git a/garrysmod/addons/admin-sg/lua/plugins/watchlist/sh_commands.lua b/garrysmod/addons/admin-sg/lua/plugins/watchlist/sh_commands.lua new file mode 100644 index 0000000..8aba1ef --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/watchlist/sh_commands.lua @@ -0,0 +1,21 @@ +local plugin = plugin + +local command = {} + +command.help = 'Редактировать Watchlist\'ы игрока' +command.command = 'watch' +command.arguments = {'player'} +command.permissions = 'DBG: WatchLists' +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL + +function command:Execute(admin, silent, arguments) + local target = util.FindPlayer(arguments[1], admin, true) + if admin == target then + admin:Notify('warning', 'Любишь быть в центре внимания?') + return + end + plugin:OpenEditor(admin, IsValid(target) and target:SteamID() or arguments[1]) + return true +end + +serverguard.command:Add(command) diff --git a/garrysmod/addons/admin-sg/lua/plugins/watchlist/shared.lua b/garrysmod/addons/admin-sg/lua/plugins/watchlist/shared.lua new file mode 100644 index 0000000..4dc2afc --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/plugins/watchlist/shared.lua @@ -0,0 +1,17 @@ +local plugin = plugin; + +plugin.name = 'Dobrograd Watchlist'; +plugin.author = 'Wani4ka'; +plugin.version = '1.0'; +plugin.description = 'Особая фишка Доброграда'; +plugin.gamemodes = {'darkrp'}; +plugin.permissions = { + 'DBG: WatchLists', +}; + +serverguard.phrase:Add('english', 'watchlist', { + SERVERGUARD.NOTIFY.GREEN, '[Watchlist] ', SERVERGUARD.NOTIFY.RED, '%s', ' (%s)', SERVERGUARD.NOTIFY.WHITE, ' зашел в игру:', +}); +serverguard.phrase:Add('english', 'watchlist_note', { + SERVERGUARD.NOTIFY.GREEN, '[Watchlist] ', SERVERGUARD.NOTIFY.WHITE, '%s' +}); diff --git a/garrysmod/addons/admin-sg/lua/sg_client.lua b/garrysmod/addons/admin-sg/lua/sg_client.lua new file mode 100644 index 0000000..97695e9 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/sg_client.lua @@ -0,0 +1,70 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard = serverguard or {}; +SERVERGUARD = SERVERGUARD or {}; + +serverguard.headerImage = "serverguard/icon_footer.png" + +local folders = {} + +function serverguard.AddFolder(path) + table.insert(folders, path) +end + +include("sg_shared.lua") + +surface.CreateFont("verdana_13_bold", {font = "Verdana", size = 13, weight = 700}) + +surface.CreateFont("serverGuard_ownerFont", {font = "Calibri",size = 16}) + +surface.CreateFont("Default", {font = "Tahoma", size = 13, weight = 500, antialias = false}) +surface.CreateFont("DefaultBold", {font = "Tahoma", size = 13, weight = 1000, antialias = false}) +surface.CreateFont("DefaultSmall", {font = "Tahoma", size = 11, weight = 0, antialias = false}) + +surface.CreateFont("corbel_16", {font = "Corbel", size = 16}) +surface.CreateFont("corbel_28", {font = "Corbel", size = 28}) +surface.CreateFont("corbel_36", {font = "Corbel", size = 36}) + +surface.CreateFont("segoe.18", {font = "Segoe UI", size = 18, weight = 400}) +surface.CreateFont("roboto_12", {font = "Roboto", size = 12, weight = 400}) +surface.CreateFont("roboto.18", {font = "Roboto", size = 18, weight = 400}) + +surface.CreateFont("roboto.15.bold", {font = "Roboto", size = 15, weight = 700}) +surface.CreateFont("roboto.18.bold", {font = "Roboto", size = 18, weight = 700}) + +serverguard.netstream.Hook("sgUpdateNotification", function(data) + serverguard.latestVersion = data; +end); + +hook.Add("Think", "serverguard.PlayerLoad", function() + if (IsValid(LocalPlayer())) then + file.CreateDir("serverguard"); + + for i = 1, #folders do + file.CreateDir("serverguard/"..folders[i]); + end; + + g_serverGuard = {}; + + hook.Call("serverguard.LoadPlayerData", nil, LocalPlayer()); + hook.Remove("Think", "serverguard.PlayerLoad"); + end; +end); + +hook.Add("GUIMousePressed", "serverguard.MenuClose", function(mouseCode, aimVector) + if (mouseCode == MOUSE_LEFT and IsValid(serverguard.GetMenuPanel())) then + serverguard.menu.Close(); + end; +end); + +hook.Add("VGUIMousePressed", "serverguard.MenuSearchReset", function(panel, mouseCode) + local panelObject = serverguard.GetMenuPanel(); + + if (IsValid(panelObject)) then + panelObject:ResetSearch(); + end; +end); diff --git a/garrysmod/addons/admin-sg/lua/sg_mysql.lua b/garrysmod/addons/admin-sg/lua/sg_mysql.lua new file mode 100644 index 0000000..55c3142 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/sg_mysql.lua @@ -0,0 +1,10 @@ +SERVERGUARD.MySQL = { + enabled = 1, + module = 'mysqloo', + unixsocket = CFG.db.socket, + host = CFG.db.host, + username = CFG.db.user, + password = CFG.db.pass, + port = CFG.db.port, + database = CFG.db.admin, +} diff --git a/garrysmod/addons/admin-sg/lua/sg_server.lua b/garrysmod/addons/admin-sg/lua/sg_server.lua new file mode 100644 index 0000000..ef5d83d --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/sg_server.lua @@ -0,0 +1,128 @@ +--[[ + © 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard = { + RconEnabled = false -- Toggles ServerGuard's 'rcon' command. Only change this if you know what you're doing. +} +SERVERGUARD = {} + +local folders = {} + +function serverguard.AddFolder(path) + table.insert(folders, path) +end + +AddCSLuaFile("sg_client.lua") +AddCSLuaFile("sg_shared.lua") + +include("sg_shared.lua") + +-- +-- +-- + +function serverguard.Initialize() + file.CreateDir("serverguard") + + for i = 1, #folders do + file.CreateDir("serverguard/" .. folders[i]) + end + + hook.Call("serverguard.Initialize", nil) +end; +hook.Add("Initialize", "serverguard.Initialize", serverguard.Initialize); + +-- +-- Load a players data. +-- + +hook.Add("PlayerInitialSpawn", "serverguard.PlayerInitialSpawn", function(pPlayer) + pPlayer.serverguard = {}; + + serverguard.ranks:SendInitialRanks(pPlayer); + serverguard.player:Load(pPlayer); + + hook.Call("serverguard.LoadPlayerData", nil, pPlayer, steamID, uniqueID); +end); + +-- +-- Needed to update in a local server +-- + +hook.Add("serverguard.RanksLoaded", "serverguard.RanksLoaded", function() + if player.GetCount() < 1 then return end + serverguard.ranks:SendInitialRanks(); +end); + +-- +-- Reset ServerGuard. +-- + +local SG_IN_RESET, SG_RESET_TOKEN = nil, nil; + +local function RecursiveRemove(directory) + if (!SG_IN_RESET) then + return; + end; + + if (string.sub(directory, -1) != "/") then + directory = directory.."/"; + end; + + local files, folders = file.Find(directory.."*", "DATA"); + + for k, v in ipairs(files) do + file.Delete(directory..v); + end; + + for k, v in ipairs(folders) do + if (v != ".." and v != ".") then + RecursiveRemove(directory..v); + end; + end; + + file.Delete(directory); +end; + +concommand.Add("serverguard_reset", function(player, command, arguments) + if (util.IsConsole(player) or player:IsListenServerHost()) then + if (!SG_IN_RESET) then + SG_RESET_TOKEN = math.random(1000, 9999); + MsgC(Color(255, 0, 0), "CAUTION: This will reset ServerGuard and remove ALL data.\n"); + MsgC(Color(255, 255, 0), "We recommend you take a backup of the current data before confirming your reset.\n"); + MsgC(Color(255, 255, 0), "You will need to enter the following command to confirm your reset: "); MsgN("serverguard_reset "..tostring(SG_RESET_TOKEN)); + MsgC(Color(255, 255, 0), "The server will restart when the reset is complete.\n"); + SG_IN_RESET = true; + elseif (SG_RESET_TOKEN != nil) then + if (tonumber(arguments[1]) == SG_RESET_TOKEN) then + RecursiveRemove("serverguard/"); + + serverguard.mysql:Drop("serverguard_analytics"):Execute(); + serverguard.mysql:Drop("serverguard_bans"):Execute(); + serverguard.mysql:Drop("serverguard_ranks"):Execute(); + serverguard.mysql:Drop("serverguard_reports"):Execute(); -- backwards compatibility + serverguard.mysql:Drop("serverguard_users"):Execute(); + serverguard.mysql:Drop("serverguard_schema"):Execute(); + + hook.Call("serverguard.mysql.DropTables", nil); + + RunConsoleCommand("changelevel", game.GetMap()); + else + MsgC(Color(255, 255, 0), "You have entered an incorrect reset code.\n"); + MsgC(Color(255, 255, 0), "The reset process has been aborted. You can restart it by entering the command: "); MsgN("serverguard_reset"); + + SG_IN_RESET, SG_RESET_TOKEN = nil, nil; + end; + end; + end; +end); + +hook.Add("serverguard.PostUpdate", "serverguard.notification.PostUpdate", function(version) + if (version and serverguard.GetCurrentVersion() == "1.4.0") then + MsgC(Color(255, 255, 0), "[ServerGuard] We've noticed that you've upgraded from an old version - we HIGHLY recommend you refresh your data by using the serverguard_reset command.\n"); + MsgC(Color(255, 255, 0), "[ServerGuard] Not refreshing your data will more than likely cause errors!\n"); + end; +end); \ No newline at end of file diff --git a/garrysmod/addons/admin-sg/lua/sg_shared.lua b/garrysmod/addons/admin-sg/lua/sg_shared.lua new file mode 100644 index 0000000..8963a89 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/sg_shared.lua @@ -0,0 +1,248 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +--- ## Shared +-- Global library that holds all other libraries, and various small helper functions. +-- @module serverguard + +serverguard.version = "1.9.8"; +serverguard.latestVersion = serverguard.version; + +SERVERGUARD.ENDPOINT = "http://api.gmodserverguard.com/api/"; + +SERVERGUARD.NETWORK = {}; +SERVERGUARD.NETWORK.NUMBER = 1; +SERVERGUARD.NETWORK.STRING = 2; +SERVERGUARD.NETWORK.BOOLEAN = 3; +SERVERGUARD.NETWORK.DECODE = 4; +SERVERGUARD.NETWORK.COLOR = 5; +SERVERGUARD.NETWORK.CUSTOM = 6; + +--- Enumeration table to specify notify colour. +-- @field RED Red colour. +-- @field WHITE White colour. +-- @field GREEN Green colour. +-- @see serverguard.Notify +SERVERGUARD.NOTIFY = {}; +SERVERGUARD.NOTIFY.DEFAULT = 1; +SERVERGUARD.NOTIFY.RED = 2; +SERVERGUARD.NOTIFY.WHITE = 3; +SERVERGUARD.NOTIFY.GREEN = 4; + +--- Enumeration table to specify file state. +-- @field SERVER Server state. +-- @field CLIENT Client state. +-- @field SHARED Server and client state. +-- @see serverguard.ParseFile +SERVERGUARD.STATE = {}; +SERVERGUARD.STATE.SERVER = 1; +SERVERGUARD.STATE.CLIENT = 2; +SERVERGUARD.STATE.SHARED = 3; + +--- Enumeration table to specify immunity comparisons. +-- @field EQUAL Immunities are equal. +-- @field LESS Target immunity is less than the given one. +-- @field LESSOREQUAL Target immunity is less than or equal to the given one. +-- @field ANY Any immunity works. +-- @see util.FindPlayers +SERVERGUARD.IMMUNITY = {}; +SERVERGUARD.IMMUNITY.EQUAL = 1; -- not sure why you would explicitly need equal immunity, but it's here anyway +SERVERGUARD.IMMUNITY.LESS = 2; +SERVERGUARD.IMMUNITY.LESSOREQUAL = 3; +SERVERGUARD.IMMUNITY.ANY = 4; + +--- Returns the version of this server's copy of ServerGuard. +-- @treturn string Version number with format `#.#.#`. +function serverguard.GetCurrentVersion() + return serverguard.version; +end; + +--- Returns the latest version of ServerGuard. This could be incorrect, +-- as the server has to wait for the HTTP request to complete. +-- @treturn string Version number with format `#.#.#`. +function serverguard.GetLatestVersion() + return serverguard.latestVersion; +end; + +--- Prints to console with the time and a `[ServerGuard]` prefix. +-- @string text The text to print to the console. +function serverguard.PrintConsole(text) + Msg("[" .. os.date("%H:%M") .. "][ServerGuard] " .. text); +end; + +serverguard.NotifyColors = {}; +serverguard.NotifyColors[SERVERGUARD.NOTIFY.DEFAULT] = Color(230, 230, 230); -- For backwards compatibility. +serverguard.NotifyColors[SERVERGUARD.NOTIFY.RED] = Color(211, 78, 71); +serverguard.NotifyColors[SERVERGUARD.NOTIFY.WHITE] = Color(230, 230, 230); +serverguard.NotifyColors[SERVERGUARD.NOTIFY.GREEN] = Color(55, 163, 68); + +--- Includes files based on their state (client, server, shared). +-- @string filePath The file to parse. +-- @state state State enumeration to specify how to parse it. +-- @usage serverguard.ParseFile("tools/sv_administration.lua", SERVERGUARD.STATE.SERVER); +function serverguard.ParseFile(filePath, state) + if (state == SERVERGUARD.STATE.SHARED) then + if (SERVER) then + AddCSLuaFile(filePath); + end; + + include(filePath); + + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Loaded shared file: " .. filePath .. ".\n"); + end; + else + if (state == SERVERGUARD.STATE.CLIENT) then + if (SERVER) then + AddCSLuaFile(filePath); + elseif (CLIENT) then + include(filePath); + end; + + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Loaded clientside file: " .. filePath .. ".\n"); + end; + end; + + if (state == SERVERGUARD.STATE.SERVER and SERVER) then + include(filePath); + + if (SG_DEBUG or SG_FILE_DEBUG) then + serverguard.PrintConsole("Loaded serverside file: " .. filePath .. ".\n"); + end; + end; + end; +end; + +serverguard.ParseFile("modules/sh_von.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_netstream.lua", SERVERGUARD.STATE.SHARED); + +--- Sends a message in a player's chat box. Set player to nil/NULL or LocalPlayer() when using clientside. +-- @player player The player to send the notification to. Specify nil/NULL to send to everyone. +-- @param ... Any notify enumeration followed by text. +-- @usage serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "This", SERVERGUARD.NOTIFY.WHITE, " is a test."); +if (SERVER) then + function serverguard.Notify(player, ...) + local args = {...} + local lenargs = #args + + if (lenargs == 0) then + return + end + -- FIXME: should we reduce the table first? + serverguard.netstream.Start(player, "sgNotifyText", args) + + if (not IsValid(player)) then + local text = "" + + for i = 1, lenargs do + local arg = args[i] + + if (isstring(arg)) then + text = text .. arg + end + end + + serverguard.PrintConsole(text .. '\n') + end; + end; +else + local function Notify(args, lenargs --[[= #args]]) + local addcolor = false + + if (isnumber(args[1])) then + addcolor = false + args[1] = serverguard.NotifyColors[args[1]] or serverguard.NotifyColors[SERVERGUARD.NOTIFY.DEFAULT] + else + addcolor = true + end + + for i = 2, (lenargs or #args) do + local arg = args[i] + + if (isnumber(arg)) then + args[i] = serverguard.NotifyColors[arg] or serverguard.NotifyColors[SERVERGUARD.NOTIFY.DEFAULT] + end + end + + if (addcolor) then + chat.AddText(serverguard.NotifyColors[SERVERGUARD.NOTIFY.DEFAULT], unpack(args)) + else + chat.AddText(unpack(args)) + end + end + + function serverguard.Notify(player, ...) + local args = {...} + local lenargs = #args + + if (lenargs == 0) then + return + end + + Notify(args, lenargs) + end + + serverguard.netstream.Hook("sgNotifyText", Notify); + + serverguard.netstream.Hook("sgPopupError", function(data) + if (isstring(data)) then + util.CreateDialog("Error", data, function() end, "Close"); + end; + end); + + serverguard.contextRegistry = {}; + + function serverguard:RegisterContextMenu(callback) + table.insert(serverguard.contextRegistry, callback); + end; +end + +serverguard.ParseFile("modules/sh_phrase.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_permissions.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_utilities.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_player.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sv_player.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_commands.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sv_mysql.lua", SERVERGUARD.STATE.SERVER); +serverguard.ParseFile("modules/sh_cami.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_ranks.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/cl_themes.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/cl_medialib.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/derma_skin.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_base.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_scrollpanel.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_divider.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_list.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_panel.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_button.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_tooltip.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_linegraph.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_news.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_dialog.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_checkbox.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_numslider.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_textentry.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/controls/tiger_combobox.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/menu.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/information.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/lists.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/intelligence.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/news.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/manage_players.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/command_list.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/ban_list.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/rank_list.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/rank_editor.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/server_settings.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/gui/themes.lua", SERVERGUARD.STATE.CLIENT); +serverguard.ParseFile("modules/sh_config.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sh_plugin.lua", SERVERGUARD.STATE.SHARED); +serverguard.ParseFile("modules/sv_spectate.lua", SERVERGUARD.STATE.SERVER); + +serverguard.ParseFile("tools/sv_ranks.lua", SERVERGUARD.STATE.SERVER); +serverguard.ParseFile("tools/sv_administration.lua", SERVERGUARD.STATE.SERVER); +serverguard.ParseFile("tools/sh_commands.lua", SERVERGUARD.STATE.SHARED); diff --git a/garrysmod/addons/admin-sg/lua/tools/sh_commands.lua b/garrysmod/addons/admin-sg/lua/tools/sh_commands.lua new file mode 100644 index 0000000..d48d33f --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/tools/sh_commands.lua @@ -0,0 +1,2456 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +-- +-- Ban lengths, in seconds. +-- + +serverguard.banLengths = { + {"1 Minute", 1}, + {"10 Minutes", 10}, + {"30 Minutes", 30}, + {"1 Hour", 60}, + {"6 Hours", 360}, + {"1 Day", 1440}, + {"1 Week", 10080}, + {"1 Month", 43200}, + {"Indefinite", 0} +}; + +-- +-- Simple kick reason list. +-- + +serverguard.kickReasons = { + "Spammer", + "Crashed server", + "Mingebag", + "Griefer", + "Foul language", + "Disobeying the rules", + "DDoS Threats" +}; + + +-- +-- Jail lengths +-- + +serverguard.jailLengths = { + {"1 Minute", 60}, + {"2 Minutes", 120}, + {"5 Minutes", 300}, + {"10 Minutes", 600}, + {"30 Minutes", 1800}, + {"1 Hour", 3600}, + {"Indefinite", 0} +}; + +-- +-- The ban command. +-- + +local command = {}; + +command.help = "Ban a player."; +command.command = "ban"; +command.arguments = {"player", "length"}; +command.optionalArguments = {"reason"}; +command.permissions = {"Ban"}; + +function command:Execute(player, silent, arguments) + local target, length = arguments[1], arguments[2]; + + if util.FindPlayer(arguments[1], player) == player then + player:Notify('warning', 'Любишь быть в центре внимания?') + return + end + + if (target and length) then + serverguard:BanPlayer(player, arguments[1], arguments[2], table.concat(arguments, " ", 3)); + end; +end; + +function command:ContextMenu(player, menu, rankData) + local banMenu, menuOption = menu:AddSubMenu("Ban Player"); + + banMenu:SetSkin("serverguard"); + menuOption:SetImage("icon16/delete.png"); + + for k, v in pairs(serverguard.banLengths) do + local option = banMenu:AddOption(v[1], function() + Derma_StringRequest("Ban Reason", "Specify ban reason.", "", function(text) + serverguard.command.Run("ban", false, player:Name(), v[2], text); + end, function(text) end, "Accept", "Cancel"); + end); + + option:SetImage("icon16/clock.png"); + end; + + local option = banMenu:AddOption("Custom", function() + Derma_StringRequest("Ban Length", "Specify ban length (example: 1y1w2d1h)", "", function(length) + Derma_StringRequest("Ban Reason", "Specify ban reason.", "", function(text) + serverguard.command.Run("ban", false, player:Name(), util.ParseDuration(length), text); + end); + end, function(text) end, "Accept", "Cancel"); + end); + + option:SetImage("icon16/clock.png"); +end; + +serverguard.command:Add(command); + +-- +-- The unban command. +-- + +local command = {}; + +command.help = "Unban a player."; +command.command = "unban"; +command.arguments = {"steamid"}; +command.optionalArguments = {"reason"}; +command.permissions = {"Unban"}; + +function command:Execute(player, silent, arguments) + + local steamID = arguments[1] + local text = string.Trim(table.concat(arguments, " ", 2)) + if util.IsConsole(player) and text == '' then octolib.msg('Необходимо указать причину снятия блокировки') return end + serverguard:UnbanPlayer(steamID, player, text); + +end; + +serverguard.command:Add(command); + +-- +-- The kick command. +-- + +local command = {}; + +command.help = "Kick a player."; +command.command = "kick"; +command.arguments = {"player"}; +command.optionalArguments = {"reason"}; +command.permissions = {"Kick"}; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player); + + if (IsValid(target)) then + if player == target then + player:Notify('warning', 'Любишь быть в центре внимания?') + return + end + if (serverguard.player:HasBetterImmunity(player, serverguard.player:GetImmunity(target))) then + local reason = table.concat(arguments, " ", 2); + + if (string.Trim(reason) == "") then + reason = "No Reason"; + end; + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has kicked ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, ". Reason: " .. reason); + end; + + game.ConsoleCommand("kickid " .. target:UserID() .. " " .. reason .. "\n"); + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "This player has a higher immunity than you."); + end; + end; +end; + +function command:ContextMenu(player, menu, rankData) + local kickMenu, menuOption = menu:AddSubMenu("Kick Player") + + kickMenu:SetSkin("serverguard"); + menuOption:SetImage("icon16/delete.png"); + + for k, v in pairs(serverguard.kickReasons) do + local option = kickMenu:AddOption(v, function() + serverguard.command.Run("kick", false, player:Name(), v); + end); + + option:SetImage("icon16/page_paste.png"); + end; + + kickMenu:AddOption("Custom Reason", function() + Derma_StringRequest("Kick Reason", "Specify kick reason.", "", function(text) + serverguard.command.Run("kick", false, player:Name(), text); + end, function(text) end, "Accept", "Cancel"); + end); +end; + +serverguard.command:Add(command); + +-- +-- The rank command. +-- + +local command = {}; + +command.help = "Set a player's rank."; +command.command = "rank"; +command.arguments = {"player", "rank", "length minutes"}; +command.permissions = "Set Rank"; +command.aliases = {"setrank", "setgroup", "group"}; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player, true); + local rankData = serverguard.ranks:GetRank(arguments[2] or ""); + local length = tonumber(arguments[3]) or 0 + + if IsValid(player) and player:IsPlayer() then + local curImm = serverguard.ranks:FindByID(player:GetUserGroup()).immunity + local tgtImm = rankData.immunity + if tgtImm >= curImm then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "No access.") + return + end + end + + if (rankData) then + if (serverguard.player:HasBetterImmunity(player, rankData.immunity)) then + if (IsValid(target)) then + if (player != target) then + if (serverguard.player:HasBetterImmunity(player, serverguard.player:GetImmunity(target))) then + serverguard.player:SetRank(target, rankData.unique, length * 60); + serverguard.player:SetImmunity(target, rankData.immunity); + serverguard.player:SetTargetableRank(target, rankData.targetable) + serverguard.player:SetBanLimit(target, rankData.banlimit) + + if (!silent) then + serverguard.Notify(nil, SGPF("command_rank", serverguard.player:GetName(player), serverguard.player:GetName(target), string.Ownership(serverguard.player:GetName(target), true), rankData.name, (length != nil and length > 0) and length .. " minute(s)" or "Indefinitely")); + end; + else + serverguard.Notify(player, SGPF("player_higher_immunity")); + end; + else + serverguard.Notify(player, SGPF("command_rank_cannot_set_own")); + end; + elseif (string.SteamID(arguments[1])) then + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:Where("steam_id", arguments[1]); + queryObj:Limit(1); + queryObj:Callback(function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + target = result[1]; + + if (!util.IsConsole(player) and target.steam_id == player:SteamID()) then + serverguard.Notify(player, SGPF("command_rank_cannot_set_own")); + return; + end; + + local playerRankData = serverguard.ranks:GetRank(target.rank); + + local data = (target.data and serverguard.von.deserialize(target.data)) or {}; + + if (length != nil and isnumber(length) and length > 0) then + data.groupExpire = os.time() + length * 60; + else + data.groupExpire = false; + end; + + + if (!playerRankData or serverguard.player:HasBetterImmunity(player, playerRankData.immunity)) then + local updateObj = serverguard.mysql:Update("serverguard_users"); + updateObj:Update("rank", rankData.unique); + updateObj:Update("data", serverguard.von.serialize(data)); + updateObj:Where("steam_id", target.steam_id); + updateObj:Limit(1); + updateObj:Callback(function(result, status, lastID) + if (!silent) then + serverguard.Notify(nil, SGPF("command_rank", serverguard.player:GetName(player), target.name, string.Ownership(target.name, true), rankData.name, (length != nil and length > 0) and length .. " minute(s)" or "Indefinitely")); + end; + end); + updateObj:Execute(); + else + serverguard.Notify(player, SGPF("player_higher_immunity")); + end; + else + local insertObj = serverguard.mysql:Insert("serverguard_users"); + insertObj:Insert("name", arguments[1]); + insertObj:Insert("rank", rankData.unique); + insertObj:Insert("steam_id", arguments[1]); + insertObj:Insert("last_played", os.time()); + insertObj:Insert("data", serverguard.von.serialize(data)) + insertObj:Callback(function(result, status, lastID) + if (!silent) then + serverguard.Notify(nil, SGPF("command_rank", serverguard.player:GetName(player), arguments[1], string.Ownership(arguments[1], true), rankData.name, (length != nil and length > 0) and length .. " minute(s)" or "Indefinitely")); + end; + end); + insertObj:Execute(); + end; + end); + queryObj:Execute(); + else + serverguard.Notify(player, SGPF("cant_find_player_with_identifier")); + end; + else + serverguard.Notify(player, SGPF("command_rank_invalid_immunity")); + end; + else + serverguard.Notify(player, SGPF("command_rank_invalid_unique", arguments[2] or "")); + + local rankList = {}; + + for k, v in pairs(serverguard.ranks:GetRanks()) do + rankList[#rankList + 1] = k; + end; + + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, SGPF("command_rank_valid_list", table.concat(rankList, ", "))); + end; +end; + +function command:ContextMenu(player, menu, rankData) + local rankMenu, menuOption = menu:AddSubMenu("Set Rank"); + + rankMenu:SetSkin("serverguard"); + menuOption:SetImage("icon16/award_star_add.png"); + + local sorted = {}; + + for k, v in pairs(serverguard.ranks:GetTable()) do + table.insert(sorted, v); + end; + + table.sort(sorted, function(a, b) return a.immunity > b.immunity end); + + for _, data in pairs(sorted) do + local timeMenu, menuRank = rankMenu:AddSubMenu(data.name); + timeMenu:SetSkin("serverguard"); + + local option = timeMenu:AddOption("Indefinitely", function() + serverguard.command.Run("rank", false, player:Name(), data.unique, 0); + end); + option:SetImage("icon16/clock.png"); + + local option = timeMenu:AddOption("Custom", function() + Derma_StringRequest("Rank Length", "Specify the time after which this rank will expire (in minutes).", "", function(length) + serverguard.command.Run("rank", false, player:Name(), data.unique, tonumber(length)); + end, function(text) end, "Accept", "Cancel"); + end); + option:SetImage("icon16/clock.png"); + + if (data.texture and data.texture != "") then + menuRank:SetImage(data.texture); + end; + end; +end; + +serverguard.command:Add(command); + +hook.Add("serverguard.LoadedPlayerData", "serverguard.ranks.LoadedPlayerData", function(player, steamid) + local expiration = tonumber(serverguard.player:GetData(player, "groupExpire", 0)); + if (expiration and expiration > 0) then + if (os.time() > expiration) then + serverguard.player:SetRank(player, "user", 0); + else + timer.Create("serverguard.timer.RemoveRank_" .. player:UniqueID(), expiration - os.time(), 1, function() + if (IsValid(player)) then + serverguard.player:SetRank(player, "user", 0); + end; + end); + end; + end; +end); + +hook.Add("PlayerDisconnected", "serverguard.ranks.PlayerDisconnected", function(player) + if timer.Exists("serverguard.timer.RemoveRank_" .. player:UniqueID()) then + timer.Remove("serverguard.timer.RemoveRank_" .. player:UniqueID()); + end; +end); + +-- +-- The toggle plugin command. +-- + +local command = {}; + +command.help = "Toggle a plugin."; +command.command = "plugintoggle"; +command.arguments = {"name"}; +command.permissions = "Manage Plugins"; +command.aliases = {"toggleplugin"}; + +function command:Execute(player, silent, arguments) + local pluginTable = serverguard.plugin.Get(arguments[1]); + + if (pluginTable) then + local uniqueID = pluginTable.unique; + local bWhitelisted = true + + if (pluginTable.gamemodes and #pluginTable.gamemodes > 0) then + if (!table.HasValue(pluginTable.gamemodes, engine.ActiveGamemode())) then + bWhitelisted = false; + end; + end; + + serverguard.netstream.Start(nil, "sgGetPluginStatus", { + uniqueID, !pluginTable.toggled + }); + + serverguard.plugin:Toggle(uniqueID, !pluginTable.toggled); + if !bWhitelisted then + serverguard.plugin:SaveData(true,uniqueID); + else + serverguard.plugin:SaveData(); + end; + end; +end; + +serverguard.command:Add(command); + +-- +-- The bring command. +-- + +local command = {}; + +command.help = "Bring a player to you."; +command.command = "bring"; +command.arguments = {"player"}; +command.permissions = "Bring"; +command.bDisallowConsole = true; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(player, target) + local position = serverguard:playerSend(target, player, false); + + if (not target:Alive()) then + target:Spawn(); + end + + if (position) then + target.sg_LastPosition = target:GetPos(); + target.sg_LastAngles = target:EyeAngles(); + target:SetPos(position); + + return true; + end; +end; + +function command:OnNotify(player, targets) + return SGPF("command_bring", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Bring Player", function() + serverguard.command.Run("bring", false, player:Name()); + end); + + option:SetImage("icon16/wand.png"); +end; + +serverguard.command:Add(command); + +-- +-- The send command. +-- + +local command = {}; + +command.help = "Send a player to where you're looking, or to another player."; +command.command = "send"; +command.arguments = {"player"}; +command.optionalArguments = {"to player"}; +command.permissions = "Send"; +command.bDisallowConsole = true; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player) + + if (IsValid(target)) then + if (!serverguard.player:HasBetterImmunity(player, serverguard.player:GetImmunity(target))) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "This player has a higher immunity than you."); + return; + end; + + if (not target:Alive()) then + target:Spawn(); + end + + if (arguments[2] != nil) then + local sendTarget = util.FindPlayer(arguments[2], player); + + if (IsValid(sendTarget)) then + local position = serverguard:playerSend(target, sendTarget, target:GetMoveType() == MOVETYPE_NOCLIP); + + if (position) then + target.sg_LastPosition = target:GetPos(); + target.sg_LastAngles = target:EyeAngles(); + + target:SetPos(position); + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has sent ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, " to ", SERVERGUARD.NOTIFY.RED, string.Ownership(sendTarget:Name()), SERVERGUARD.NOTIFY.WHITE, " location."); + end; + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "Could not find any proper location to place the player."); + end; + + return; + end; + end; + + target.sg_LastPosition = target:GetPos(); + target.sg_LastAngles = target:EyeAngles(); + + local trace = player:GetEyeTrace(); + trace = trace.HitPos +trace.HitNormal *1.25; + target:SetPos(trace); + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has sent ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, " to their location."); + end; + end; +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Send Player", function() + serverguard.command.Run("send", false, player:Name()); + end); + + option:SetImage("icon16/wand.png"); +end; + +serverguard.command:Add(command); + +-- +-- The goto command. +-- + +local command = {}; + +command.help = "Go to a player."; +command.command = "goto"; +command.arguments = {"player"}; +command.permissions = "Goto"; +command.bDisallowConsole = true; +command.bSingleTarget = true; +command.immunity = SERVERGUARD.IMMUNITY.ANY; +command.aliases = {"tp", "teleport"}; + +function command:OnPlayerExecute(player, target) + local position = serverguard:playerSend(player, target, true); + + if (position) then + player:SetPos(position); + player:SetEyeAngles(Angle(target:EyeAngles().pitch, target:EyeAngles().yaw, 0)); + + return true; + else + if (serverguard.player:HasPermission(player, "Noclip")) then + player:SetMoveType(MOVETYPE_NOCLIP); + position = serverguard:playerSend(player, target, true); + + player:SetPos(position); + player:SetEyeAngles(Angle(target:EyeAngles().pitch, target:EyeAngles().yaw, 0)); + + return true; + end; + end; +end; + +function command:OnNotify(player, targets) + return SGPF("command_goto", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Go To Player", function() + serverguard.command.Run("goto", false, player:Name()); + end); + + option:SetImage("icon16/wand.png"); +end; + +serverguard.command:Add(command); + +-- +-- The return command. +-- + +local command = {}; + +command.help = "Return a player to their previous location."; +command.command = "return"; +command.arguments = {"player"}; +command.permissions = "Return"; +command.bDisallowConsole = true; + +function command:Execute(player, bSilent, arguments) + local target = util.FindPlayer(arguments[1], player); + + if (IsValid(target)) then + if (target.sg_LastPosition and target.sg_LastAngles) then + if (target.sg_jail) then + serverguard:UnjailPlayer(target); + end; + + target:SetPos(target.sg_LastPosition); + target:SetEyeAngles(Angle(target.sg_LastAngles.pitch, target.sg_LastAngles.yaw, 0)); + + target.sg_LastPosition = nil; + target.sg_LastAngles = nil; + + if (!bSilent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has returned ", SERVERGUARD.NOTIFY.RED, serverguard.player:GetName(target), SERVERGUARD.NOTIFY.WHITE, " to their previous location."); + end; + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "This player has not been previously teleported."); + end; + end; +end; + +serverguard.command:Add(command); + +-- +-- The change map command. +-- + +local command = {}; + +command.help = "Change the map."; +command.command = "map"; +command.arguments = {"map"}; +command.permissions = "Map"; +command.aliases = {"changemap", "changelevel"}; + +function command:Execute(player, silent, arguments) + local map = string.lower(arguments[1]); + + game.ConsoleCommand("changelevel " .. map .. "\n"); + + -- If the server has not changed map by this time, the map does not exist. + timer.Simple(0.2, function() + if (IsValid(player)) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "\"" .. map .. "\" is not installed on this server."); + end; + end); +end; + +serverguard.command:Add(command); + + +-- +-- The "admin chat" command. +-- + +local command = {}; + +command.help = "Talk to admins."; +command.command = "a"; +command.quotes = true; +command.arguments = {"text"}; +command.permissions = "Admin Chat"; + +function command:Execute(pPlayer, silent, arguments) + local text = table.concat(arguments, " "); + local recipients = {}; + + for k, v in ipairs(player.GetAll()) do + if (v:IsAdmin()) then + recipients[#recipients + 1] = v; + end; + end; + + if (#recipients > 0) then + serverguard.netstream.Start(recipients, "sgTalkToAdmins", {text, IsValid(pPlayer) and pPlayer:Name(), IsValid(pPlayer) and pPlayer:SteamName(), pPlayer}); + end; + + if (util.IsConsole(pPlayer)) then + return; + end; + + if (!pPlayer:IsAdmin()) then + if (#recipients > 0) then + serverguard.Notify(pPlayer, SERVERGUARD.NOTIFY.WHITE, "Sent the message to the admin chat."); + else + serverguard.Notify(pPlayer, SERVERGUARD.NOTIFY.WHITE, "There are currently no admins online."); + end; + end; +end; + +if (CLIENT) then + serverguard.netstream.Hook("sgTalkToAdmins", function(data) + local text = octolib.string.splitByUrl(data[1]); + local name = data[2] + local steam = data[3] + local pPlayer = data[4]; + + if (util.IsConsole(pPlayer)) then + if not name then + chat.AddText(Color(255, 0, 0), "[Admins] ", color_white, "Console: ", unpack(text)); + else + chat.AddText(Color(255, 0, 0), '[Admins] ', color_white, steam, ' (', name, '): ', unpack(text)) + end + return; + end; + + chat.AddText(Color(255, 0, 0), "[Admins] ", team.GetColor(pPlayer:Team()), name, color_white, ": ", unpack(text)); + end); +end; + +serverguard.command:Add(command); + +-- +-- The "clear decals" command. +-- + +local command = {}; + +command.help = "Clear decals for everyone."; +command.command = "cleardecals"; +command.permissions = "Clear Decals"; + +function command:Execute(pPlayer, silent, arguments) + serverguard.netstream.Start(nil, "sgClearDecals", 1); + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has cleared all decals."); + end; +end; + +if (CLIENT) then + serverguard.netstream.Hook("sgClearDecals", function(data) + LocalPlayer():ConCommand("r_cleardecals", true); + end); +end; + +serverguard.command:Add(command); + +-- +-- The rcon command. +-- + +if (serverguard.RconEnabled) then + local command = {} + + command.help = "Run a command on the server."; + command.command = "rcon"; + command.arguments = {"text"}; + command.permissions = "Rcon"; + + function command:Execute(_, silent, arguments) + game.ConsoleCommand(table.concat(arguments, " ") .. "\n"); + end; + + serverguard.command:Add(command); +end; + +-- +-- The god command. +-- + +local command = {} + +command.help = "Toggle god mode for a player."; +command.command = "god"; +command.arguments = {"player"}; +command.permissions = "God mode"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target) + target:GodEnable(); + return true; +end; + +function command:OnNotify(pPlayer, targets) + return SGPF("command_god", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Toggle God Mode", function() + if (!pPlayer:HasGodMode()) then + serverguard.command.Run("god", false, pPlayer:Name()); + else + serverguard.command.Run("ungod", false, pPlayer:Name()); + end; + end); + + option:SetImage("icon16/shield.png"); +end; + +serverguard.command:Add(command); + +-- +-- The ungod command. +-- + +local command = {} + +command.help = "Disable god mode for a player."; +command.command = "ungod"; +command.arguments = {"player"}; +command.permissions = "God mode"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target) + target:GodDisable(); + return true; +end; + +function command:OnNotify(pPlayer, targets) + return SGPF("command_god_disable", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets)); +end; + +serverguard.command:Add(command); + +-- +-- The "set armor" command. +-- + +local command = {} + +command.help = "Set a player's armor."; +command.command = "armor"; +command.arguments = {"player", "armor"}; +command.permissions = "Set Armor"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.aliases = {"setarmor"}; + +function command:OnPlayerExecute(_, target, arguments) + local amount = util.ToNumber(arguments[2]); + target:SetArmor(amount); + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + + return SGPF("command_armor", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets, true), amount); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Set Armor", function() + Derma_StringRequest("Set Armor", "Specify armor amount.", "", function(text) + serverguard.command.Run("armor", false, pPlayer:Name(), text); + end, function(text) end, "Accept", "Cancel") end); + + option:SetImage("icon16/heart.png"); +end; + +serverguard.command:Add(command); + +-- +-- The "set health" command. +-- + +local command = {}; + +command.help = "Set a player's health."; +command.command = "hp"; +command.arguments = {"player", "hp"}; +command.permissions = "Set Health"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.aliases = {"sethp", "sethealth"}; + +function command:OnPlayerExecute(_, target, arguments) + local amount = util.ToNumber(arguments[2]); + target:SetHealth(amount); + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + + return SGPF("command_hp", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets, true), amount); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Set Health", function() + Derma_StringRequest("Set Health", "Specify health amount.", "", function(text) + serverguard.command.Run("hp", false, pPlayer:Name(), text); + end, function(text) end, "Accept", "Cancel") end); + + option:SetImage("icon16/heart.png"); +end; + +serverguard.command:Add(command); + +-- +-- The respawn command. +-- + +local command = {}; + +command.help = "Respawn a player."; +command.command = "respawn"; +command.arguments = {"player"}; +command.permissions = "Respawn"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target, arguments) + target:Spawn(); + + -- Needed for TTT. + if (target.SpawnForRound) then + target:SpawnForRound(); + + -- Remove their corpse. + if (IsValid(target.server_ragdoll)) then + target.server_ragdoll:Remove(); + end; + end; + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + + return SGPF("command_respawn", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets), amount); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Respawn", function() + serverguard.command.Run("respawn", false, pPlayer:Name()); + end); + + option:SetImage("icon16/user_go.png"); +end; + +serverguard.command:Add(command); + +-- +-- The ammo command. +-- + +local command = {} + +command.help = "Give a player ammo."; +command.command = "ammo"; +command.arguments = {"player"}; +command.permissions = "Give Ammo"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +local infiniteAmmo = 999999 +function command:OnPlayerExecute(_, target, arguments) + local ammoAmount = util.ToNumber(arguments[2]) + + if (ammoAmount < 1) then ammoAmount = infiniteAmmo end + local weapon = target:GetActiveWeapon() + if not IsValid(weapon) then return end + target:GiveAmmo(ammoAmount, weapon:GetPrimaryAmmoType()) + if weapon:GetSecondaryAmmoType() ~= weapon:GetPrimaryAmmoType() then + target:GiveAmmo(ammoAmount, weapon:GetSecondaryAmmoType()) + end + + return true +end + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + if (amount < 1) then amount = "infinite" end + return SGPF("command_ammo", serverguard.player:GetName(pPlayer), amount, util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Give Infinite Ammo", function() + serverguard.command.Run("ammo", false, pPlayer:Name()); + end); + + option:SetImage("icon16/tab_add.png"); +end; + +serverguard.command:Add(command); + +-- +-- The slap command. +-- + +local slapSounds = { + Sound("physics/body/body_medium_impact_hard1.wav"), + Sound("physics/body/body_medium_impact_hard2.wav"), + Sound("physics/body/body_medium_impact_hard3.wav"), + Sound("physics/body/body_medium_impact_hard5.wav"), + Sound("physics/body/body_medium_impact_hard6.wav"), + Sound("physics/body/body_medium_impact_soft5.wav"), + Sound("physics/body/body_medium_impact_soft6.wav"), + Sound("physics/body/body_medium_impact_soft7.wav") +}; + +local command = {}; + +command.help = "Slap a player."; +command.command = "slap"; +command.arguments = {"player"}; +command.permissions = "Slap"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target, arguments) + target:SetHealth(target:Health() -math.random(2, 6)); + target:SetVelocity(Vector(math.random(-225, 225), math.random(-225, 225), 10)); + target:EmitSound(slapSounds[math.random(1, #slapSounds)]); + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + + return SGPF("command_slap", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets), amount); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Slap", function() + serverguard.command.Run("slap", false, pPlayer:Name()); + end); + + option:SetImage("icon16/transmit_error.png"); +end; + +serverguard.command:Add(command); + +-- +-- The "toggle npc target" command. +-- + +local command = {}; + +command.help = "Toggle NPC targeting for a player."; +command.command = "npctarget"; +command.arguments = {"player"}; +command.permissions = "NPC Target"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(_, target, arguments) + if (target.sg_notarget == nil) then + target.sg_notarget = false; + end; + + target:SetNoTarget(!target.sg_notarget); + target.sg_notarget = !target.sg_notarget; + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local amount = util.ToNumber(arguments[2]); + + return SGPF("command_npctarget", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets), amount); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Toggle NPC Target", function() + serverguard.command.Run("npctarget", false, pPlayer:Name()); + end); + + option:SetImage("icon16/transmit.png"); +end; + +serverguard.command:Add(command); + +-- +-- The slay command. +-- + +local command = {}; + +command.help = "Slay a player."; +command.command = "slay"; +command.arguments = {"player"}; +command.permissions = "Slay"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.aliases = {"kill"}; + +function command:OnPlayerExecute(_, target, arguments) + if target:IsGhost() then return end + target:Kill(); + + return true; +end; + +function command:OnNotify(pPlayer, targets) + return SGPF("command_slay", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(pPlayer, menu, rankData) + local option = menu:AddOption("Slay", function() + serverguard.command.Run("slay", false, pPlayer:Name()); + end); + + option:SetImage("icon16/user_delete.png"); +end; + +serverguard.command:Add(command); + +-- +-- The "give weapon" command. +-- + +local command = {}; + +command.help = "Give a weapon to a player."; +command.command = "giveweapon"; +command.arguments = {"player", "weapon"}; +command.permissions = "Give Weapon"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.aliases = {"give"}; + +function command:OnPlayerExecute(_, target, arguments) + if not IsValid(target) or target:IsGhost() then return end + local weapon = arguments[2]; + local ent = target:Give(weapon); + if IsValid(ent) and not ent:IsWeapon() then ent:Remove() end + + return true; +end; + +function command:OnNotify(pPlayer, targets, arguments) + local weapon = arguments[2]; + + return SGPF("command_giveweapon", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets), weapon); +end; + +serverguard.command:Add(command); + +-- +-- The ragdoll command. +-- + +local command = {}; + +command.help = "Toggle ragdoll a player."; +command.command = "ragdoll"; +command.arguments = {"player"}; +command.permissions = "Ragdoll"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(player, target, arguments) + if (IsValid(target.sgRagdoll)) then + target:MakeInvisible(false) + local pos = DarkRP.findEmptyPos(target.sgRagdoll:GetPos(), {target}, 600, 30, Vector(0, 0, 5)) + target.sgRagdoll:Remove() + target:SetParent(NULL) + target:SetNetVar('DeathRagdoll') + target:SetNetVar('Ragdolled') + target:SetPos(pos) + target:Freeze(false) + target:SelectWeapon('dbg_hands') + else + target.sgRagdoll = ents.Create("prop_ragdoll") + target.sgRagdoll:SetPos(target:GetPos()) + target.sgRagdoll:SetModel(target:GetModel()) + target.sgRagdoll:SetAngles(target:GetAngles()) + target.sgRagdoll:SetSkin(target:GetSkin()) + target.sgRagdoll:SetMaterial(target:GetMaterial()) + target.sgRagdoll:Spawn() + + target.sgRagdoll:CallOnRemove("UnragdollPlayer", function(ragdoll) + if (IsValid(target)) then + target:MakeInvisible(false) + local pos = DarkRP.findEmptyPos(target.sgRagdoll:GetPos(), {target}, 600, 30, Vector(0, 0, 5)) + target:SetParent(NULL) + target:SetNetVar('DeathRagdoll') + target:SetNetVar('Ragdolled') + target:SetPos(pos) + target:Freeze(false) + target:SelectWeapon('dbg_hands') + end + end) + + for i = 0, #target:GetMaterials() - 1 do + target.sgRagdoll:SetSubMaterial(i, target:GetSubMaterial(i)) + end + + for _, v in ipairs(target:GetBodyGroups()) do + target.sgRagdoll:SetBodygroup(v.id, target:GetBodygroup(v.id)) + end + + target.sgRagdoll:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + local velocity = target:GetVelocity() + local physObjects = target.sgRagdoll:GetPhysicsObjectCount() - 1 + + for i = 0, physObjects do + local bone = target.sgRagdoll:GetPhysicsObjectNum(i) + + if (IsValid(bone)) then + local position, angle = target:GetBonePosition(target.sgRagdoll:TranslatePhysBoneToBone(i)) + + if (position and angle) then + bone:SetPos(position) + bone:SetAngles(angle) + end + + bone:AddVelocity(velocity) + end + end + + if target:InVehicle() then target:ExitVehicle() end + target:SetParent(target.sgRagdoll) + target:SelectWeapon('dbg_hands') + target:SetNetVar('DeathRagdoll', target.sgRagdoll) + target:SetNetVar('Ragdolled', true) + + target:MakeInvisible(true) + target:Freeze(true) + end; + + return true; +end; + +hook.Add('PlayerSwitchWeapon', 'dbg-ragdoll', function(ply) + if ply:GetNetVar('Ragdolled') then return true end +end) + +function command:OnNotify(player, targets) + return SGPF("command_ragdoll", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Toggle ragdoll", function() + serverguard.command.Run("ragdoll", false, player:Name()); + end); + + option:SetImage("icon16/television.png"); +end; + +serverguard.command:Add(command); + +-- +-- The ignite command. +-- + +local command = {}; + +command.help = "Ignite a player."; +command.command = "ignite"; +command.arguments = {"player"}; +command.permissions = "Ignite"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(player, target, arguments) + target:Ignite(99999999); + + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_ignite", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Ignite", function() + serverguard.command.Run("ignite", false, player:Name()); + end); + + option:SetImage("icon16/bomb.png"); +end; + +serverguard.command:Add(command); + +-- +-- The extinguish command. +-- + +local command = {} + +command.help = "Extinguish a player."; +command.command = "extinguish"; +command.arguments = {"player"}; +command.permissions = "Extinguish"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.aliases = {"unignite"}; + +function command:OnPlayerExecute(player, target, arguments) + target:Extinguish(); + + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_extinguish", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Extinguish", function() + serverguard.command.Run("extinguish", false, player:Name()); + end); + + option:SetImage("icon16/user_delete.png"); +end; + +serverguard.command:Add(command); + +-- +-- The mute command. +-- + +local function niceTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format("%02i:%02i:%02i", h, m, s) + +end + +local function updateVar(ply, var, left) + + if not IsValid(ply) then return end + + local function reset() + if not IsValid(ply) then return end + ply:SetNetVar(var, nil) + ply:SetDBVar(var, nil) + ply:ChatPrint(L.reset_gag) + end + + if left and left > 0 then + local till = os.time() + left + ply:SetNetVar(var, till) + ply:SetDBVar(var, till) + timer.Create(var .. ply:SteamID(), left, 1, reset) + ply:ChatPrint(L.time_left_for_reset_gag:format(niceTime(left))) + else + reset() + end + +end + +local command = {}; + +command.help = "Mute a player."; +command.command = "mute"; +command.arguments = {"player", "length"}; +command.permissions = "Mute"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + local length = util.ParseDuration(arguments[2]) + if not length then return end + + updateVar(target, 'sgMuted', length * 60) + + return true; +end; + +function command:OnNotify(player, targets, arguments) + local _, length = util.ParseDuration(arguments[2]) + return SGPF("command_mute", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets), length); +end; + +-- function command:ContextMenu(player, menu, rankData) +-- local option = menu:AddOption("Mute/Unmute", function() +-- if (player:GetNetVar("sgMuted")) then +-- serverguard.command.Run("unmute", false, player:Name()); +-- else +-- serverguard.command.Run("mute", false, player:Name()); +-- end; +-- end); + +-- option:SetImage("icon16/comment.png"); +-- end; + +serverguard.command:Add(command); + +-- +-- The unmute command. +-- + +local command = {}; + +command.help = "Unmute a player."; +command.command = "unmute"; +command.arguments = {"player"}; +command.permissions = "Unmute"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + updateVar(target, 'sgMuted', nil) + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_unmute", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +serverguard.command:Add(command); + +-- +-- The OOC-mute command. +-- + +if SERVER then + hook.Add('octochat.canExecute', 'oocmute', function(ply, cmd) + if cmd == '/ooc' then + if ply:GetDBVar('sgMutedOOC', 0) >= os.time() then + local msg = 'До снятия запрета на использование OOC-чата осталось: ' .. niceTime(ply:GetDBVar('sgMutedOOC', 0) - os.time()) + local reason = ply:GetDBVar('sgMutedOOCReason') + if reason then msg = msg .. '\nПричина: ' .. reason end + return false, msg + else + ply:SetDBVar('sgMutedOOC') + ply:SetDBVar('sgMutedOOCReason') + end + elseif cmd == '/looc' then + if ply:GetDBVar('sgMutedLOOC', 0) >= os.time() then + local msg = 'До снятия запрета на использование LOOC-чата осталось: ' .. niceTime(ply:GetDBVar('sgMutedLOOC', 0) - os.time()) + local reason = ply:GetDBVar('sgMutedLOOCReason') + if reason then msg = msg .. '\nПричина: ' .. reason end + return false, msg + else + ply:SetDBVar('sgMutedLOOC') + ply:SetDBVar('sgMutedLOOCReason') + end + end + end) +end + +local command = {}; + +command.help = "Mute OOC chats for a player."; +command.command = "oocmute"; +command.arguments = {"player", "length"}; +command.permissions = "Mute"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + local length = util.ParseDuration(arguments[2]) + if not length or length <= 0 then return end + + if SERVER then + target:SetDBVar('sgMutedOOC', os.time() + length * 60) + target:Notify('warning', 'До снятия запрета на использование OOC-чата осталось: ', niceTime(length * 60)) + if arguments[3] then + target:Notify('warning', 'Причина: ', arguments[3]) + target:SetDBVar('sgMutedOOCReason', arguments[3]) + end + end + + return true; +end; + +function command:OnNotify(player, targets, arguments) + local _, length = util.ParseDuration(arguments[2]) + return SGPF("command_mute_ooc", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets), length); +end; + +serverguard.command:Add(command); + +-- +-- The OOC-unmute command. +-- + +local command = {}; + +command.help = "Unmute OOC chats for a player."; +command.command = "oocunmute"; +command.arguments = {"player"}; +command.permissions = "Unmute"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + target:SetDBVar('sgMutedOOC') + target:ChatPrint('Срок запрета на использование OOC-чата закончился') + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_unmute_ooc", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +serverguard.command:Add(command); + +local command = {}; + +command.help = "Mute LOOC chats for a player."; +command.command = "loocmute"; +command.arguments = {"player", "length"}; +command.permissions = "Mute"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + local length = util.ParseDuration(arguments[2]) + if not length or length <= 0 then return end + + if SERVER then + target:SetDBVar('sgMutedLOOC', os.time() + length * 60) + target:Notify('warning', 'До снятия запрета на использование LOOC-чата осталось: ', niceTime(length * 60)) + if arguments[3] then + target:Notify('warning', 'Причина: ', arguments[3]) + target:SetDBVar('sgMutedLOOCReason', arguments[3]) + end + end + + return true; +end; + +function command:OnNotify(player, targets, arguments) + local _, length = util.ParseDuration(arguments[2]) + return SGPF("command_mute_looc", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets), length); +end; + +serverguard.command:Add(command); + +-- +-- The LOOC-unmute command. +-- + +local command = {}; + +command.help = "Unmute LOOC chats for a player."; +command.command = "loocunmute"; +command.arguments = {"player"}; +command.permissions = "Unmute"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + target:SetDBVar('sgMutedLOOC') + target:ChatPrint('Срок запрета на использование LOOC-чата закончился') + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_unmute_looc", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +serverguard.command:Add(command); + +-- +-- The gag command. +-- + +local command = {}; + +command.help = "Gag a player."; +command.command = "gag"; +command.arguments = {"player", "length"}; +command.permissions = "Gag"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + local length = util.ParseDuration(arguments[2]) + if not length then return end + + updateVar(target, 'sgGagged', length * 60) + + return true; +end; + +function command:OnNotify(player, targets, arguments) + local _, length = util.ParseDuration(arguments[2]) + return SGPF("command_gag", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets), length); +end; + +-- function command:ContextMenu(player, menu, rankData) +-- local option = menu:AddOption("Gag/Ungag", function() +-- if (player:GetNetVar("sgGagged")) then +-- serverguard.command.Run("ungag", false, player:Name()); +-- else +-- serverguard.command.Run("gag", false, player:Name()); +-- end; +-- end); + +-- option:SetImage("icon16/sound.png"); +-- end; + +serverguard.command:Add(command); + +-- +-- The ungag command. +-- + +local command = {}; + +command.help = "Ungag a player."; +command.command = "ungag"; +command.arguments = {"player"}; +command.permissions = "Ungag"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; + +function command:OnPlayerExecute(player, target, arguments) + updateVar(target, 'sgGagged', nil) + return true; +end; + +function command:OnNotify(player, targets, arguments) + return SGPF("command_ungag", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +serverguard.command:Add(command); + +if (SERVER) then + hook.Add("PlayerCanHearPlayersVoice", "serverguard.PlayerCanHearPlayersVoice", function(listener, speaker) + if speaker:GetNetVar('sgGagged') then + return false; + end; + end); + + hook.Add('PlayerFinishedLoading', 'sg.octostuff', function(ply) + + for i, var in ipairs({'sgGagged', 'sgMuted'}) do + local till = ply:GetDBVar(var) + if till then + local left = till - os.time() + updateVar(ply, var, left > 0 and left or nil) + end + end + + end) +end; + +-- +-- The invisible command. +-- + +-- local command = {}; + +-- command.help = "Toggle a player's invisibility."; +-- command.command = "invisible"; +-- command.arguments = {"player"}; +-- command.permissions = "Invisible"; +-- command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +-- command.aliases = {"cloak"}; + +-- function command:OnPlayerExecute(player, target, arguments) +-- if (target.sg_invisible) then +-- target:SetNoDraw(false); +-- target:SetNotSolid(false); +-- target:GodDisable(); +-- target:DrawWorldModel(true); + +-- target.sg_invisible = false; +-- target:SetNetVar('Invisible') +-- self:CloakHooks() +-- else +-- target:SetNoDraw(true); +-- target:SetNotSolid(true); +-- target:GodEnable(); +-- target:DrawWorldModel(false); + + + +-- target.sg_invisible = true; +-- target:SetNetVar('Invisible', true) +-- self:CloakHooks() +-- end; + +-- return true; +-- end; + +-- function command:OnNotify(player, targets) +-- return SGPF("command_invisible", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +-- end; + +-- function command:ContextMenu(player, menu, rankData) +-- local option = menu:AddOption("Toggle Invisibility", function() +-- serverguard.command.Run("invisible", false, player:Name()); +-- end); + +-- option:SetImage("icon16/user_edit.png"); +-- end; + +-- local function OnVehicleEnterdHook(player, vehicle, role) +-- if (player.sg_invisible) then +-- serverguard.command.stored.invisible:OnPlayerExecute(nil, player) +-- serverguard.command.stored.invisible:CloakHooks() +-- end +-- end + +-- local function OnPlayerDeathHook(player, inflictor, attacker) +-- if (player.sg_invisible) then +-- serverguard.command.stored.invisible:OnPlayerExecute(nil, player) +-- serverguard.command.stored.invisible:CloakHooks() +-- end +-- end + +-- hook.Add('EntityEmitSound', 'cloak', function(data) +-- local ent = data.Entity +-- if IsValid(ent) and ent.sg_invisible then return false end +-- end) + +-- hook.Add('PlayerFootstep', 'cloak', function(ply) +-- if CLIENT and ply == LocalPlayer() then return end -- For me, absolutely no sounds results in a nightmare +-- if ply:Team() == TEAM_ADMIN and ply:GetNetVar('Invisible') then return true end +-- end) + +-- local sg_invisibleHookActive = false +-- function command:CloakHooks() +-- local playerCloakedCount = 0 +-- for k, user in pairs(player.GetAll()) do +-- if (user.sg_invisible) then +-- playerCloakedCount = playerCloakedCount + 1 +-- end +-- end +-- if (not (0 == playerCloakedCount) and not sg_invisibleHookActive) then +-- sg_invisibleHookActive = true +-- hook.Add("PlayerEnteredVehicle", "serverguard_cloak_PlayerEnteredVehicle", OnVehicleEnterdHook) +-- hook.Add("PlayerDeath", "serverguard_cloak_PlayerDeath", OnPlayerDeathHook) +-- elseif ((0 == playerCloakedCount) and sg_invisibleHookActive) then +-- sg_invisibleHookActive = false +-- hook.Remove("PlayerEnteredVehicle", "serverguard_cloak_PlayerEnteredVehicle") +-- hook.Remove("PlayerDeath", "serverguard_cloak_PlayerDeath") +-- end +-- end + + +-- serverguard.command:Add(command); + +-- +-- The freeze command. +-- + +local command = {}; + +command.help = "Freeze or unfreeze a player."; +command.command = "freeze"; +command.arguments = {"player"}; +command.permissions = "Freeze"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(player, target, arguments) + if (target.sg_isFrozen) then + target:Freeze(false); + target.sg_isFrozen = false; + + if (target:GetMoveType() == MOVETYPE_NONE) then + target:SetMoveType(MOVETYPE_WALK); + end; + else + target:Freeze(true); + target.sg_isFrozen = true; + end; + + return true; +end; + +local function FreezeBlock(player) + if (player.sg_isFrozen) then + return false + end +end +local function FreezeHookHelper(...) + for i = 1, select("#", ...) do + local name = select(i, ...) + hook.Add(name, "serverguard.freeze."..name, FreezeBlock) + end +end +FreezeHookHelper("PlayerSpawnEffect", "PlayerSpawnNPC", "PlayerSpawnObject", + "PlayerSpawnProp", "PlayerSpawnRagdoll", "PlayerSpawnSENT", + "PlayerSpawnSWEP", "PlayerSpawnVehicle" + ) + +function command:OnNotify(player, targets) + return SGPF("command_freeze", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Freeze/Unfreeze", function() + serverguard.command.Run("freeze", false, player:Name()); + end); + + option:SetImage("icon16/joystick_error.png"); +end; + +serverguard.command:Add(command); + +-- +-- The "strip weapons" command. +-- + +local command = {}; + +command.help = "Strip all weapons from a player."; +command.command = "stripweapons"; +command.arguments = {"player"}; +command.permissions = "Strip Weapons"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.aliases = {"strip"}; + +function command:OnPlayerExecute(player, target, arguments) + target:StripWeapons(); + + return true; +end; + +function command:OnNotify(player, targets) + return SGPF("command_stripweapons", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Strip Weapons", function() + serverguard.command.Run("stripweapons", false, player:Name()); + end); + + option:SetImage("icon16/gun.png"); +end; + +serverguard.command:Add(command); + +-- +-- The announce command. +-- + +local command = {}; + +command.help = "Announce something to the server."; +command.command = "announce"; +command.arguments = {"text"}; +command.permissions = "Announce"; + +function command:Execute(player, silent, arguments) + local text = table.concat(arguments, " "); + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.RED, text); +end; + +serverguard.command:Add(command); + +-- +-- The help command. +-- + +-- local command = {}; + +-- command.help = "Ask admins for help."; +-- command.command = "help"; +-- command.arguments = {"text"}; + +-- function command:Execute(pPlayer, silent, arguments) +-- local text = table.concat(arguments, " "); + +-- for k, v in ipairs(player.GetAll()) do +-- if (serverguard.player:HasPermission(v, "See Help Requests") or v == pPlayer) then +-- serverguard.Notify(v, SERVERGUARD.NOTIFY.RED, "[HELP] ", SERVERGUARD.NOTIFY.WHITE, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, ": " .. text); + +-- pPlayer.sg_pendingHelpRequest = true; +-- pPlayer.sg_pendingHelpRequestTime = os.time(); +-- end; +-- end; +-- end; + +-- serverguard.command:Add(command); + +-- local command = {}; + +-- command.help = "Respond to the latest help request."; +-- command.command = "respond"; +-- command.arguments = {"text"}; +-- command.permissions = "Respond to Help Requests"; + +-- function command:Execute(pPlayer, silent, arguments) +-- local target = nil; +-- local requestTime = 0; + +-- for k,v in pairs(player.GetAll()) do +-- if v.sg_pendingHelpRequest and v.sg_pendingHelpRequestTime > requestTime then +-- requestTime = v.sg_pendingHelpRequestTime; +-- target = v; +-- end; +-- end; + +-- if (IsValid(target)) then +-- if (target.sg_pendingHelpRequest) then +-- local text = table.concat(arguments, " ", 1); + +-- for k, v in ipairs(player.GetAll()) do +-- if (serverguard.player:HasPermission(v, "See Help Requests") or v == target) then +-- serverguard.Notify(v, SERVERGUARD.NOTIFY.RED, "[RESPONSE] ", SERVERGUARD.NOTIFY.WHITE, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, ": " .. text); + +-- pPlayer.sg_pendingHelpRequest = nil; +-- end; +-- end; +-- end; +-- else +-- serverguard.Notify(pPlayer, SERVERGUARD.NOTIFY.RED, "There are no pending help requests."); +-- end; +-- end; + +-- serverguard.command:Add(command); + +-- +-- The spectate command. +-- + +-- local command = {}; +-- +-- command.help = "Start spectating someone."; +-- command.command = "spectate"; +-- command.arguments = {"player"}; +-- command.permissions = "FSpectate"; +-- command.bDisallowConsole = true; +-- command.bSingleTarget = true; +-- command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +-- +-- function command:OnPlayerExecute(player, target, arguments) +-- if (player != target) then +-- serverguard.player:SpectateTarget(player, target); +-- serverguard.player:SetupSpectate(player, OBS_MODE_IN_EYE); +-- +-- serverguard.Notify(player, SGPF("command_spectate_hint")); +-- return true; +-- end; +-- end; +-- +-- function command:OnNotify(player, targets) +-- return SGPF("command_spectate", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +-- end; +-- +-- function command:ContextMenu(player, menu, rankData) +-- local option = menu:AddOption("Spectate", function() +-- serverguard.command.Run("spectate", false, player:Name()); +-- end); +-- +-- option:SetImage("icon16/camera.png"); +-- end; +-- +-- serverguard.command:Add(command); + +-- +-- The noclip command. +-- + +local command = {} + +command.help = "Toggle noclip mode." +command.command = "noclip" +command.permissions = "Noclip"; +command.arguments = {"player"}; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; +command.bDisallowConsole = true; + +function command:OnPlayerExecute(player, target, arguments) + if (target:GetMoveType() != MOVETYPE_NOCLIP) then + target:SetMoveType(MOVETYPE_NOCLIP); + else + target:SetMoveType(MOVETYPE_WALK); + end; + + return true; +end; + +function command:OnNotify(player, targets) + return SGPF("command_noclip", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +hook.Add("PlayerNoClip", "serverguard.PlayerNoClip", function(player) + if (player.sg_jail or player:GetNetVar("serverguard_jailed", false)) then + return false; + end; + + if (serverguard.player:HasPermission(player, "Noclip")) then + return true; + end; +end); + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Toggle Noclip", function() + serverguard.command.Run("noclip", false, player:Name()); + end); + + option:SetImage("icon16/arrow_up.png"); +end; + +serverguard.command:Add(command); + +-- +-- Restart the current map. +-- + +local command = {}; + +command.help = "Restart the current map."; +command.command = "maprestart"; +command.arguments = {"delay seconds"}; +command.permissions = "Map Restart"; +command.aliases = {"restartmap"}; + +function command:Execute(player, silent, arguments) + local delay = tonumber(arguments[1]); + + timer.Simple(delay, function() + RunConsoleCommand("changelevel", game.GetMap()); + end); + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " is restarting the map in ", SERVERGUARD.NOTIFY.RED, tostring(delay), SERVERGUARD.NOTIFY.WHITE, " seconds!") + end; +end; + +serverguard.command:Add(command); + +-- +-- Freeze all props on the server. +-- + +local command = {}; + +command.help = "Freeze all props in the server."; +command.command = "freezeprops"; +command.permissions = "Freeze Props"; + +function command:Execute(player, silent, arguments) + for k, v in ipairs(ents.FindByClass("prop_physics")) do + local physicsObject = v:GetPhysicsObject(); + + if (IsValid(physicsObject)) then + physicsObject:EnableMotion(false); + end; + end; + + if (!silent) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has frozen all props."); + end; +end; + +serverguard.command:Add(command); + +-- +-- Jail a player. +-- + +local command = {}; + +command.help = "Jail a player."; +command.command = "jail"; +command.arguments = {"player", "seconds"}; +command.permissions = "Jail"; +command.immunity = SERVERGUARD.IMMUNITY.LESSOREQUAL; + +function command:OnPlayerExecute(player, target, arguments) + if (!target.sg_jail) then + local duration = tonumber(arguments[2]) or 0; + + serverguard:JailPlayer(target, duration); + + return true; + end; +end; + +function command:OnNotify(player, targets) + return SGPF("command_jail", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets), (targets[1].sg_jailTime and targets[1].sg_jailTime .. " second(s)") or "Indefinite"); +end; + +function command:ContextMenu(player, menu, rankData) + local jailMenu, menuOption = menu:AddSubMenu("Jail"); + menuOption:SetImage("icon16/lock.png"); + + for k, v in pairs(serverguard.jailLengths) do + local option = jailMenu:AddOption(v[1], function() + serverguard.command.Run("jail", false, player:Name(), v[2]); + end); + + option:SetImage("icon16/clock.png"); + end; + + local option = jailMenu:AddOption("Custom", function() + Derma_StringRequest("Jail Length", "Specify jail time in seconds.", "", function(duration) + serverguard.command.Run("jail", false, player:Name(), tonumber(duration)); + end, function(text) end, "Accept", "Cancel"); + end); + + option:SetImage("icon16/clock.png"); +end; + +serverguard.command:Add(command); + +-- +-- Teleport and jail a player. +-- + +local command = {}; + +command.help = "Teleport and jail a player."; +command.command = "jailtp"; +command.arguments = {"player", "seconds"}; +command.permissions = "Jail"; +command.bDisallowConsole = true; + +function command:OnPlayerExecute(player, target, arguments) + if (!target.sg_jail) then + local duration = tonumber(arguments[2]) or 0; + target.sg_LastPosition = target:GetPos(); + target.sg_LastAngles = target:EyeAngles(); + + target:SetPos(player:GetEyeTraceNoCursor().HitPos + Vector(0, 0, 5)); + serverguard:JailPlayer(target, duration); + + return true; + end; +end; + +function command:OnNotify(player, targets) + return SGPF("command_jailtp", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets), (targets[1].sg_jailTime and targets[1].sg_jailTime .. " second(s)") or "Indefinite"); +end; + +serverguard.command:Add(command); + +-- +-- Unjail a player. +-- + +local command = {}; + +command.help = "Unjail a player."; +command.command = "unjail"; +command.arguments = {"player"}; +command.permissions = "Jail"; +command.aliases = {"release"}; + +function command:OnPlayerExecute(player, target) + if (target.sg_jail) then + serverguard:UnjailPlayer(target); + + return true; + end; +end; + +function command:OnNotify(player, targets) + return SGPF("command_unjail", serverguard.player:GetName(player), util.GetNotifyListForTargets(targets)); +end; + +function command:ContextMenu(player, menu, rankData) + local option = menu:AddOption("Unjail", function() + serverguard.command.Run("unjail", false, player:Name()); + end); + + option:SetImage("icon16/lock.png"); +end; + +serverguard.command:Add(command); + +-- +-- Restrict a player from using certain features. +-- + +local command = {}; + +command.help = "Restrict a player from using certain features."; +command.command = "restrict"; +command.arguments = {"player", "time"}; +command.permissions = "Restrict"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; +command.bSingleTarget = true; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player); + local time, text = util.ParseDuration(arguments[2]); + + if (IsValid(target)) then + if (serverguard.player:HasBetterImmunity(player, serverguard.player:GetImmunity(target))) then + if (time and time > 0) then + time = os.time() + (time * 60); + else + return; + end; + + serverguard.player:SetData(target, "restrictTime", time); + + if (!silent) then + serverguard.Notify(nil, SGPF("command_restrict", serverguard.player:GetName(player), serverguard.player:GetName(target), string.Ownership(serverguard.player:GetName(target), true), text)); + end; + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "This player has a higher immunity than you."); + end; + end; +end; + +serverguard.command:Add(command); + +-- +-- Unrestrict a player from using tools. +-- + +local command = {}; + +command.help = "Unrestrict a player from using certain features."; +command.command = "unrestrict"; +command.arguments = {"player"}; +command.permissions = "Restrict"; +command.immunity = SERVERGUARD.IMMUNITY.LESS; +command.bSingleTarget = true; + +function command:Execute(player, silent, arguments) + local target = util.FindPlayer(arguments[1], player); + + if (IsValid(target)) then + serverguard.player:SetData(target, "restrictTime", nil); + + if (!silent) then + serverguard.Notify(nil, SGPF("command_unrestrict", serverguard.player:GetName(player), serverguard.player:GetName(target), string.Ownership(serverguard.player:GetName(target), true))); + end; + end; +end; + +serverguard.command:Add(command); + + +local command = {}; + +command.help = "Temporarily demote/undemote yourself."; +command.command = "incognito"; +command.bDisallowConsole = true; + +function command:Execute(player, silent, arguments) + if (!player.sg_incognito) then + local rank = serverguard.player:GetRank(player); + + if (serverguard.ranks:HasPermission(rank, "Go Incognito")) then + serverguard.player:SetRank(player, "user", 0, true); + player.sg_incognito = rank; + serverguard.Notify(player, SGPF("incognito_enabled")); + else + serverguard.Notify(player, SGPF("incognito_prohibited")); + end; + else + serverguard.player:SetRank(player, player.sg_incognito, 0, false); + serverguard.Notify(player, SGPF("incognito_disabled")); + end; +end; + +serverguard.command:Add(command); + +serverguard.phrase:Add("english", "incognito_enabled", { + SERVERGUARD.NOTIFY.GREEN, "You've gone incognito." +}); + +serverguard.phrase:Add("english", "incognito_disabled", { + SERVERGUARD.NOTIFY.WHITE, "You're no longer incognito." +}); + +serverguard.phrase:Add("english", "incognito_prohibited", { + SERVERGUARD.NOTIFY.RED, "Your rank isn't allowed to go incognito." +}); + +-- local command = { +-- help = "Attempt to find friends of a player.", +-- command = "friends", +-- arguments = {"player"}, +-- permissions = "Get Friends", +-- aliases = {} +-- } + +-- local function FetchHTTPFriends(ply, cb) +-- if (IsValid(ply)) then +-- local id64 = ply:SteamID64() + +-- if (id64) then +-- http.Fetch("http://steamcommunity.com/profiles/"..id64.."/friends?xml=1&_="..os.time(), function(body, _, _, code) +-- if not IsValid(ply) then return end +-- if (body:sub(1,5) ~= "([0-9]+)" do +-- table.insert(ply.sg_HTTPFriends, id) +-- end + +-- cb() +-- end +-- end) +-- end +-- end +-- end + + +-- if (SERVER) then +-- local function PlyInit_FetchHTTPFriends(ply) +-- ply.sg_Friends = {} + +-- FetchHTTPFriends(ply, function() +-- if (ply ~= NULL) then +-- if (ply.sg_HTTPFriends) then +-- serverguard.PrintConsole(("Fetched friends for %s (success)\n"):format(ply:Nick())) + +-- --[[for k, v in pairs(ply.sg_HTTPFriends) do +-- ply.sg_Friends[k] = v +-- end]] +-- else +-- serverguard.PrintConsole(("Fetched friends for %s (fail)\n"):format(ply:Nick())) +-- end +-- end +-- end) +-- end +-- hook.Add("PlayerInitialSpawn", "serverguard.command.friends", PlyInit_FetchHTTPFriends) + +-- netstream.Hook('sgGiveFriendStatus', function(ply, data) +-- if not istable(data) then data = {data} end +-- for ply, isFriend in ipairs(data) do +-- if (ply:IsPlayer()) then +-- if !(ply.sg_Friends) then PlyInit_FetchHTTPFriends(ply) end +-- ply.sg_Friends[ply] = isFriend +-- end +-- end +-- end) + +-- else + +-- local done = {} + +-- local function SendFriendStatus(ply) +-- if (done[ply]) then +-- return +-- end +-- done[ply] = true +-- netstream.Start('sgGiveFriendStatus', {[ply] = ply:GetFriendStatus()}) +-- end + +-- gameevent.Listen "player_spawn" +-- hook.Add("player_spawn", "serverguard.commands.friends", function(d) +-- local function getfriend() +-- local p = Player(d.userid) +-- if (IsValid(p)) then +-- return SendFriendStatus(p) +-- end +-- timer.Simple(1, getfriend) +-- end +-- getfriend() +-- end) + +-- hook.Add("InitPostEntity", "serverguard.commands.friends", function() +-- local data = {} +-- for _, ply in ipairs(player.GetAll()) do +-- data[ply] = ply:GetFriendStatus() +-- end +-- netstream.Start('sgGiveFriendStatus', data) +-- end) + +-- serverguard.netstream.Hook("sgGetFriends", function(data) +-- print("DATA", data) +-- local friends = data.http +-- local mutual = data.mutual +-- if (not friends) then +-- friends = {} +-- chat.AddText(serverguard.NotifyColors[SERVERGUARD.NOTIFY.RED], "HTTP Friends failed (private profile).. continuing") +-- end +-- local lookup = {} +-- for k,v in ipairs(player.GetAll()) do +-- local id64 = v:SteamID64() + +-- if (id64) then +-- lookup[id64] = v +-- end +-- end +-- local green, white, red = +-- serverguard.NotifyColors[SERVERGUARD.NOTIFY.GREEN], +-- serverguard.NotifyColors[SERVERGUARD.NOTIFY.WHITE], +-- serverguard.NotifyColors[SERVERGUARD.NOTIFY.RED] +-- local chatt = { +-- green, "Friends found", --for", +-- --red, +-- white, ": " +-- } + +-- local remove = false +-- local done = {} +-- for i = 1, #friends do +-- local p = lookup[friends[i]] +-- if (p) then +-- done[p] = true +-- chatt[#chatt + 1] = red +-- chatt[#chatt + 1] = p:Nick() +-- chatt[#chatt + 1] = white +-- chatt[#chatt + 1] = ", " +-- remove = true +-- end +-- end +-- for i = 1, #mutual do +-- local p = mutual[i] +-- if (done[p]) then +-- continue +-- end +-- chatt[#chatt + 1] = red +-- chatt[#chatt + 1] = p:Nick() +-- chatt[#chatt + 1] = white +-- chatt[#chatt + 1] = ", " +-- remove = true +-- end + +-- if (remove) then +-- chatt[#chatt] = nil +-- chatt[#chatt] = nil +-- end +-- if (#chatt == 4) then +-- chatt[4] = nil +-- chatt[3] = nil +-- chatt[2] = "No friends found!" +-- end + +-- chat.AddText(unpack(chatt)) +-- end) +-- end + +-- function command:OnPlayerExecute(player, target) +-- if (not IsValid(target)) then +-- return +-- end +-- FetchHTTPFriends(target, function() + +-- if (not IsValid(target)) then +-- return +-- end + +-- local mutual_server_friends = {} +-- for ply, status in pairs(target.sg_Friends) do +-- if (status == "friend" and ply.sg_Friends and ply.sg_Friends[target] == "friend") then +-- table.insert(mutual_server_friends, ply) +-- end +-- end + +-- serverguard.netstream.StartChunked(player, "sgGetFriends", { +-- http = target.sg_HTTPFriends, +-- mutual = mutual_server_friends +-- }) +-- end) +-- return true +-- end + +-- serverguard.command:Add(command) + + + +local command = { + help = "Toggles nocollide between a player and entities", + command = "nocollide", + arguments = {"player"}, + permissions = "No Collide", + aliases = {"collide"} +} + +hook.Add("ShouldCollide", "serverguard.commands.nocollide", function(e1,e2) + if (e1:IsPlayer() and e1.sg_NoCollide or e2:IsPlayer() and e2.sg_NoCollide) then + return false + end +end) +if (CLIENT) then + serverguard.netstream.Hook("sgSetCollision", function(dat) + local ply = dat[1] + if not IsValid(ply) then return end + local enable = dat[2] + ply.sg_NoCollide = dat[2] + ply:SetCustomCollisionCheck(true) + ply:CollisionRulesChanged() + end) +end + +function command:OnPlayerExecute(_, target) + target.sg_NoCollide = not target.sg_NoCollide + + target:SetCustomCollisionCheck(true) + target:CollisionRulesChanged() + serverguard.netstream.Start(player.GetAll(), "sgSetCollision", { + target, + target.sg_NoCollide + }) + + return true +end + +function command:OnNotify(pPlayer, targets) + return SGPF("command_nocollide", serverguard.player:GetName(pPlayer), util.GetNotifyListForTargets(targets)); +end + +serverguard.command:Add(command) diff --git a/garrysmod/addons/admin-sg/lua/tools/sv_administration.lua b/garrysmod/addons/admin-sg/lua/tools/sv_administration.lua new file mode 100644 index 0000000..60b61b4 --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/tools/sv_administration.lua @@ -0,0 +1,991 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +serverguard.pollinterval = CreateConVar("serverguard_pollinterval", "0", {FCVAR_PROTECTED, FCVAR_SERVER_CAN_EXECUTE}, + "How often local ban data is synced with the database (in seconds). Set to 0 to disable." +); + +function serverguard:playerSend(from, to, force) + if not to:IsInWorld() and not force then return end; + + if (IsValid(from) and from:IsPlayer() and from:InVehicle()) then + from:ExitVehicle(); + end; + + local traceTable = {}; + traceTable.start = to:GetPos(); + traceTable.filter = {to, from}; + + for i = 1, 4, 1 do + traceTable.endpos = to:GetPos() + Angle(0, to:EyeAngles().yaw - 180 + 90 * (i - 1), 0):Forward() * 47; + tr = util.TraceEntity(traceTable, from); + if !tr.Hit then + break; + end; + if i == 4 and tr.Hit and !force then + return false; + end; + end; + + local result = tr.HitPos or (force and to:GetPos()) or false; + + if (result and IsValid(from) and from:IsPlayer()) then + from.sg_LastPosition = from:GetPos(); + from.sg_LastAngles = from:EyeAngles(); + end; + + return result; +end; + +local jail = { + {Vector(0, 0, 5), Angle(90, 0, 0)}, -- Bottom + {Vector(0, 0, 100), Angle(90, 0, 0)}, -- Top + {Vector(0, 40, 50), Angle(0, 90, 0)}, -- Side + {Vector(0, -40, 50), Angle(0, 90, 0)}, -- Side + {Vector(40, 0, 50), Angle(0, 0, 0)}, -- Side + {Vector(-40, 0, 50), Angle(0, 0, 0)} -- Side +}; + +-- +-- Jail a player. +-- + +local pieceModel = "models/props_c17/fence01b.mdl"; + +function serverguard:JailPlayer(player, duration) + if (IsValid(player) and isnumber(duration)) then + local pieces = {}; + + if (player:InVehicle()) then + player:ExitVehicle(); + end; + + player:SetMoveType(MOVETYPE_WALK); + player:SetLocalVelocity(Vector(0, 0, 0)); + + for k, v in pairs(jail) do + local piece = ents.Create("prop_physics"); + + piece:SetModel(pieceModel); + piece:SetPos(player:GetPos() + v[1]); + piece:SetAngles(v[2]); + piece:Spawn(); + piece:SetMoveType(MOVETYPE_NONE); + piece:GetPhysicsObject():EnableMotion(false); + piece.sg_jail = true; + + table.insert(pieces, piece); + end; + + -- If one piece gets removed, remove them all. + for i = 1, #pieces do + local piece = pieces[i]; + local otherPiece = pieces[i - 1] or pieces[i + 1]; + + piece:DeleteOnRemove(otherPiece); + otherPiece:DeleteOnRemove(piece); + end; + + player:SetPos(player:GetPos() + Vector(0, 0, 8)); + player:SetNetVar("serverguard_jailed", true); + + player.sg_jail = pieces; + player.sg_jailLocation = player:GetPos(); + + if duration > 0 then + local timerID = "serverguard.jail.timer_" .. player:UniqueID(); + player.sg_jailTime = duration; + timer.Create(timerID, duration, 1, function() + serverguard:UnjailPlayer(player); + end); + end; + end; +end; + +-- +-- Unjail a player. +-- + +function serverguard:UnjailPlayer(player) + if (IsValid(player) and player.sg_jail) then + for k, v in pairs(player.sg_jail) do + if (IsValid(v)) then + v:Remove(); + end; + end; + + player.sg_jail = nil; + player.sg_jailLocation = nil; + player.sg_jailTime = nil; + + local timerID = "serverguard.jail.timer_" .. player:UniqueID() + + if timer.Exists(timerID) then + timer.Remove(timerID) + end + + + player:SetNetVar("serverguard_jailed", false); + else + return false; + end; +end; + +hook.Add("PlayerDisconnected", "serverguard.jail.PlayerDisconnected", function(player) + if (player.sg_jail) then + serverguard:UnjailPlayer(player); + end; +end); + +hook.Add("CanPlayerSuicide", "serverguard.jail.CanPlayerSuicide", function(player) + if (player.sg_jail) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "You can't suicide because you're jailed!"); + + return false; + end; +end); + +hook.Add("PhysgunPickup", "serverguard.jail.PhysgunPickup", function(player, entity) + if (IsValid(entity) and entity.sg_jail or player.sg_jail) then + return false; + end; +end); + +-- hook.Add("PostGamemodeLoaded", "serverguard.jail.PostGamemodeLoaded", function() +-- local playerSpawnHook = GAMEMODE.PlayerSpawn; +-- function GAMEMODE:PlayerSpawn(player) +-- playerSpawnHook(self, player); +-- if (player.sg_jail) then +-- player:SetPos(player.sg_jailLocation); +-- player:SetMoveType(MOVETYPE_WALK); +-- end; +-- end; +-- end); + +hook.Add("EntityTakeDamage", "serverguard.jail.EntityTakeDamage", function(target, dmgInfo) + local attacker = dmgInfo:GetAttacker(); + + if (IsValid(attacker) and attacker.sg_jail) then + return true; + end; +end); + +local function JailBlock(player) + if (IsValid(player) and player.sg_jail) then + return false; + end; +end; + +hook.Add("PlayerSpawnProp", "serverguard.jail.PlayerSpawnProp", JailBlock); +hook.Add("PlayerSpawnRagdoll", "serverguard.jail.PlayerSpawnRagdoll", JailBlock); +hook.Add("PlayerSpawnVehicle", "serverguard.jail.PlayerSpawnVehicle", JailBlock); +hook.Add("PlayerSpawnSENT", "serverguard.jail.PlayerSpawnSENT", JailBlock); +hook.Add("PlayerSpawnSWEP", "serverguard.jail.PlayerSpawnSWEP", JailBlock); +hook.Add("PlayerSpawnEffect", "serverguard.jail.PlayerSpawnEffect", JailBlock); +hook.Add("PlayerSpawnNPC", "serverguard.jail.PlayerSpawnNPC", JailBlock); +hook.Add("PlayerSpawnObject", "serverguard.jail.PlayerSpawnObject", JailBlock); +hook.Add("GravGunPickupAllowed", "serverguard.jail.GravGunPickupAllowed", JailBlock); +hook.Add("GravGunPunt", "serverguard.jail.GravGunPunt", JailBlock); +hook.Add("OnPhysgunReload", "serverguard.jail.OnPhysgunReload", JailBlock); + +-- +-- Restrictions. +-- + +local function RestrictBlock(player) + if (IsValid(player)) then + local restrictTime = serverguard.player:GetData(player, "restrictTime"); + + if (restrictTime and restrictTime > os.time()) then + if (!player.nextRestrictNotify or CurTime() > player.nextRestrictNotify) then + restrictTime = (restrictTime - os.time()) / 60; + local minutes, text = util.ParseDuration(restrictTime); + + serverguard.Notify(player, SGPF("restricted", text)); + player.nextRestrictNotify = CurTime() + 2; + end; + + return false; + end; + end; +end; + +hook.Add("PlayerSpawnProp", "serverguard.restrict.PlayerSpawnProp", RestrictBlock); +hook.Add("PhysgunPickup", "serverguard.restrict.PhysgunPickup", RestrictBlock); +hook.Add("CanTool", "serverguard.restrict.CanTool", RestrictBlock); +hook.Add("GravGunPunt", "serverguard.restrict.GravGunPunt", RestrictBlock); +hook.Add("OnPhysgunReload", "serverguard.restrict.OnPhysgunReload", RestrictBlock); +hook.Add("PlayerSpawnSENT", "serverguard.restrict.PlayerSpawnSENT", RestrictBlock); +hook.Add("PlayerSpawnSWEP", "serverguard.restrict.PlayerSpawnSWEP", RestrictBlock); + +-- +-- Load the ban data. +-- + +serverguard.banTable = serverguard.banTable or {}; +serverguard.evadeBan = function(ply, v) + ply:Kick(L.evade_ban) +end + +local function LoadBanData() + timer.Simple(2, function() + if (hook.Call("serverguard.LoadBanData", nil)) then + return; + end; + + local callback = function(result, status, lastID) + if (type(result) == "table" and #result > 0) then + for k, v in pairs(result) do + if (tonumber(v.start_time) == nil or tonumber(v.end_time) == nil) then + local deleteObj = serverguard.mysql:Delete("serverguard_bans"); + deleteObj:Where("id", v.id); + deleteObj:Execute(); + elseif (v.end_time and tonumber(v.end_time) != 0 and tonumber(v.end_time) <= os.time()) then + serverguard:UnbanPlayer(v.steam_id); + else + local ply = player.GetBySteamID(v.steam_id) + if IsValid(ply) then serverguard.evadeBan(ply, v) end + serverguard.banTable[v.steam_id] = { + communityID = v.community_id, + player = v.player, + reason = v.reason, + startTime = tonumber(v.start_time), + endTime = tonumber(v.end_time), + admin = v.admin, + ip = v.ip_address + }; + end; + end; + end; + end; + + local queryObj = serverguard.mysql:Select("serverguard_bans"); + queryObj:Callback(callback); + queryObj:Execute(); + end); +end; + +hook.Add("serverguard.mysql.OnConnected", "serverguard_ban.OnConnected", LoadBanData); + +cvars.AddChangeCallback("serverguard_pollinterval", function(conVar, oldValue, newValue) + newValue = tonumber(newValue); + + if (!newValue) then + ErrorNoHalt("[mysql] Invalid poll interval.\n"); + return; + elseif (newValue < 0) then + newValue = 0; + end; + + if (newValue != 0) then + if (newValue <= 2) then + timer.Create("serverguard.mysql.BanPolling", newValue, 0, LoadBanData); + else + timer.Create("serverguard.mysql.BanPolling", newValue - 2, 0, LoadBanData); + end; + else + timer.Remove("serverguard.mysql.BanPolling"); + end; +end); + +-- +-- Check if player is banned. +-- + +local function CheckPlayerBanned(communityID, ip, serverPassword, enteredPassword, name) + if (hook.Call("serverguard.ShouldCheckPlayerBanned", nil, communityID, ip, serverPassword, enteredPassword, name) == false) then + return; + end; + + local steamID = util.SteamIDFrom64(communityID); + local data = serverguard.banTable[steamID]; + + serverguard.PrintConsole("Player '"..name.."' ["..steamID.."] is attempting to connect.\n"); + + if (data) then + local startTime, endTime = tonumber(data.startTime), tonumber(data.endTime); + local startText, endText = os.date('%d/%m/%Y - %H:%M:%S', data.startTime), endTime > 0 and os.date('%d/%m/%Y - %H:%M:%S', data.endTime) or '-' + + if (endTime > 0 and endTime <= os.time()) then + serverguard:UnbanPlayer(steamID); + + serverguard.PrintConsole("Player '"..name.."' ["..steamID.."] ban has expired!\n"); + else + serverguard.PrintConsole("Player '"..name.."' ["..steamID.."] was kicked for being banned!\n"); + + return false, ("Ты заблокирован!\n___\n\nВыдал: %s \nПричина: %s\nДата и время бана: %s\nДата и время разбана: %s\nТвой SteamID: %s\n___\n\nПодать апелляцию: octo.gg/dbg-unban"):format(data.admin, data.reason, startText, endText, steamID); + end; + else + -- Check if the player changed steam account. (Try to match the ip) + for steamid, info in pairs(serverguard.banTable) do + if (info.ip != "" and info.ip == ip) then + if (info.endTime != 0) then + serverguard:BanPlayer(nil, steamID, (info.endTime - os.time()), info.reason); + else + serverguard:BanPlayer(nil, steamID, 0, info.reason); + end; + + serverguard.PrintConsole("Player \""..playerName.."\" tried to connect using another steam profile!\n"); + + return false, "Banned: "..info.reason; + end; + end; + end; +end; + +hook.Add("CheckPassword", "serverguard_ban.CheckPassword", CheckPlayerBanned); + +-- +-- Name: serverguard:BanPlayer(admin, player, lengthID, reason) +-- Desc: Ban a player. +-- + +--[[ Internal function to add a ban. --]] +local function BAN_INSERT(steamID, communityID, playerName, reason, startTime, endTime, adminName, ipAddress, noReplicate) + if (!steamID) then + return; + end; + + + ipAddress = ipAddress and (ipAddress:gsub(":%d+", "")) or ""; + + local data = { + communityID = communityID, + player = playerName, + reason = reason, + startTime = tonumber(startTime), + endTime = tonumber(endTime), + admin = adminName, + ip = ipAddress + }; + serverguard.banTable[steamID] = data; + + serverguard.netstream.Start(nil, "sgNewBan", { + steamID = steamID, + playerName = data.player, + length = tonumber(data.endTime) - tonumber(data.startTime), + reason = data.reason, + admin = data.admin + }); + + local deleteObj = serverguard.mysql:Delete("serverguard_bans"); + deleteObj:Where("steam_id", steamID); + deleteObj:Execute(); + + local insertObj = serverguard.mysql:Insert("serverguard_bans"); + insertObj:Insert("steam_id", steamID); + insertObj:Insert("community_id", communityID); + insertObj:Insert("player", playerName); + insertObj:Insert("reason", reason); + insertObj:Insert("start_time", tostring(startTime)); + insertObj:Insert("end_time", tostring(endTime)); + insertObj:Insert("admin", adminName); + insertObj:Insert("ip_address", ipAddress); + insertObj:Execute(); + + if endTime == 0 and not noReplicate then + octolib.sendCmdToOthers('replicate-permaban', { steamID, communityID, playerName, reason, startTime, endTime, adminName, ipAddress }) + end +end; + +hook.Add('octolib.event:replicate-permaban', 'sg', function(data) + local steamID, communityID, playerName, reason, startTime, endTime, adminName, ipAddress = unpack(data) + octolib.msg('Replicating permaban for ' .. steamID) + BAN_INSERT(steamID, communityID, playerName, reason, startTime, endTime, adminName, ipAddress, true) + local ply = player.GetBySteamID(steamID) + if IsValid(ply) then ply:Kick(reason) end +end) + +function serverguard:BanPlayer(admin, player, length, reason, bKick, bSilent, adminNameOverride) + if (!player) then + return; + end; + + local bIsConsole = util.IsConsole(admin) or admin == nil; + local osTime = os.time(); + local startTime = osTime; + local bShouldKick = true; + local originalLength = length; + local length, lengthText, bClamped = util.ParseDuration(length); + local endTime = osTime + (length * 60); + + + if (bIsConsole) then + admin = NULL; + else + local banLimit, banLimitText = util.ParseDuration(serverguard.player:GetBanLimit(admin)) + if (length > serverguard.player:GetBanLimit(admin) and serverguard.player:GetBanLimit(admin) != 0) then + serverguard.Notify(admin, SGPF("command_ban_exceed_banlimit", banLimitText)); + return; + end + end; + + if (length == 0 and tostring(originalLength) != "0") then + serverguard.Notify(admin, SGPF("command_ban_invalid_duration")); + return; + end; + + local adminName = adminNameOverride or bIsConsole and "Console" or admin:IsPlayer() and ('%s [%s]'):format(admin:SteamName(), admin:SteamID()) or "Unknown"; + + if (!reason or reason == "") then + reason = "No Reason"; + end; + + if (bKick != nil) then + bShouldKick = bKick; + end; + + if (length == 0) then + endTime = length; + end; + + if (type(player) == "Player") then + if (!hook.Call("serverguard.PlayerBanned", nil, player, length, reason, admin)) then + if (!bIsConsole) then + if (serverguard.player:GetImmunity(admin) > serverguard.player:GetImmunity(player)) then + if ((length == 0) and serverguard.player:GetBanLimit(admin) != 0) then + serverguard.Notify(admin, SGPF("command_ban_cannot_permaban")); + return; + end; + + if (bClamped) then + serverguard.Notify(admin, SGPF("command_ban_clamped_duration")); + end; + else + serverguard.Notify(admin, SGPF("player_higher_immunity")); + return; + end; + end; + + BAN_INSERT( + player:SteamID(), player:SteamID64(), player:Name(), reason, startTime, endTime, adminName, (player:IPAddress():gsub(":%d+", "")) + ); + + if (!bSilent) then + if (length > 0) then + serverguard.Notify(nil, SGPF("command_ban", adminName, player:Name(), lengthText, reason)); + else + serverguard.Notify(nil, SGPF("command_ban_perma", adminName, player:Name(), reason)); + end; + end; + + if (bShouldKick) then + player:Kick(reason); + end; + end; + + return; + elseif (type(player) == "string") then + local playerObj = util.FindPlayer(player, nil, true); + + if (IsValid(playerObj)) then + self:BanPlayer(admin, playerObj, originalLength, reason, bKick, bSilent, adminNameOverride); + return; + else + if (string.find(player, "STEAM_(%d+):(%d+):(%d+)")) then + local callback = function(result, status, lastID) + local name = player; + local immunity = 0; + + if (type(result) == "table" and #result > 0) then + name = result[1].name; + immunity = serverguard.ranks:GetVariable(result[1].rank, "immunity"); + end; + + if (!hook.Call("serverguard.PlayerBannedBySteamID", nil, player, length, reason, admin, name)) then + if (!bIsConsole) then + if (immunity < serverguard.player:GetImmunity(admin)) then + if ((length == 0 or length > 604800) and serverguard.player:GetBanLimit(admin) != 0) then + serverguard.Notify(admin, SGPF("command_ban_cannot_permaban")); + return; + end; + + if (bClamped) then + serverguard.Notify(admin, SGPF("command_ban_clamped_duration")); + end; + else + serverguard.Notify(admin, SGPF("player_higher_immunity")); + return; + end; + end; + + BAN_INSERT( + player, util.SteamIDTo64(player), name, reason, startTime, endTime, adminName, "" + ); + + if (!bSilent) then + if (length > 0) then + serverguard.Notify(nil, SGPF("command_ban", adminName, player, lengthText, reason)); + else + serverguard.Notify(nil, SGPF("command_ban_perma", adminName, player, reason)); + end; + end; + end; + end; + + local queryObj = serverguard.mysql:Select("serverguard_users"); + queryObj:Where("steam_id", player); + queryObj:Limit(1); + queryObj:Callback(callback); + queryObj:Execute(); + return; + end; + end; + end; +end; + +-- +-- Unban a player. +-- + +function serverguard:UnbanPlayer(steamID, admin, reason, noCheck) + if not noCheck and hook.Call("serverguard.PreventPlayerUnban", nil, steamID, admin, reason) then + return; + end; + + serverguard.netstream.Start(nil, "sgRemoveBan", steamID); + + local deleteObj = serverguard.mysql:Delete("serverguard_bans"); + deleteObj:Where("steam_id", steamID); + deleteObj:Execute(); + + if (serverguard.banTable[steamID]) then + serverguard.banTable[steamID] = nil; + end; + + hook.Call("serverguard.PlayerUnbanned", nil, steamID, admin); + +end; + +-- +-- Console kicking. +-- + +local function serverGuard_KickPlayer(player, command, arguments) + if (util.IsConsole(player)) then + local target = util.FindPlayer(arguments[1], player) + + if (IsValid(target)) then + local reason = table.concat(arguments, " ", 2) + + reason = reason or "No Reason" + + serverguard.Notify(nil, SERVERGUARD.NOTIFY.DEFAULT, "Console has kicked '" .. target:Name() .. "'. Reason: " .. reason) + + target:Kick(reason) + + --game.ConsoleCommand("kickid " .. target:UserID() .. " " .. reason .. "\n") + end + end +end + +concommand.Add("serverguard_kick", serverGuard_KickPlayer) + +-- +-- Hotfixes +-- + +serverguard.hotFix = CreateConVar("serverguard_hotfix", "1", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_PROTECTED}, "Whether or not hotfixes are automatically ran"); + +local function GetHotfixes(callback) + http.Fetch(SERVERGUARD.ENDPOINT.."hotfix/all", function(body) + serverguard.hotFixes = util.JSONToTable(body); + + if (serverguard.hotFixes.master and serverguard.hotFix:GetBool()) then + RunString(serverguard.hotFixes.master, "ServerGuard hotfix master"); + else + serverguard.PrintConsole("Not running master hotfix, serverguard_hotfix set to 0.\n"); + end; + + if (callback) then + callback(body); + end; + end); +end; + +serverguard.hotFixes = {}; + +hook.Add("Think", "serverguard.fetchHotFixes", function() + -- GetHotfixes(); + hook.Remove("Think", "serverguard.fetchHotFixes"); +end); + +concommand.Add("serverguard_hotfix", function(ply, cmd, args) + if (util.IsConsole(ply) or ply:IsListenServerHost() and istable(serverguard.hotFixes)) then + if (args[1]) then + args[1] = args[1]:lower(); + end; + + -- Not found? Get the latest hotfixes and see if it exists there. + if (not serverguard.hotFixes[args[1]]) then + GetHotfixes(function() + if (not serverguard.hotFixes[args[1]]) then + print("Hotfix not found."); + else + RunString(serverguard.hotFixes[args[1]], "ServerGuard hotfix "..args[1]); + end; + end); + else + RunString(serverguard.hotFixes[args[1]], "ServerGuard hotfix "..args[1]); + end; + end; +end); +-- +-- Console banning. +-- + +local function serverGuard_BanPlayer(player, command, arguments) + if (util.IsConsole(player)) then + local target = util.FindPlayer(arguments[1], player) + + if (IsValid(target)) then + local length = tonumber(arguments[2]) * 60; + local reason = table.concat(arguments, " ", 3); + + reason = reason or "No Reason" + + if (length > 0) then + local hours = math.Round(length / 3600); + + if (hours >= 1) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, tostring(hours), SERVERGUARD.NOTIFY.WHITE, " hour(s). Reason: " .. reason); + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, tostring(math.Round(length / 60)), SERVERGUARD.NOTIFY.WHITE, " minutes(s). Reason: " .. reason); + end; + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, target:Name(), SERVERGUARD.NOTIFY.WHITE, ",", SERVERGUARD.NOTIFY.RED, " permanently", SERVERGUARD.NOTIFY.WHITE, ". Reason: " .. reason); + end; + + serverguard:BanPlayer(player, target, length, reason) + end + end +end + +concommand.Add("serverguard_ban", serverGuard_BanPlayer) + +-- +-- The unban command. +-- + +local function serverGuard_UnbanPlayer(player, command, arguments) + if (util.IsConsole(player)) then + local steamID = arguments[1]; + + if (serverguard.banTable[steamID]) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, "Console", SERVERGUARD.NOTIFY.WHITE, " has unbanned ", SERVERGUARD.NOTIFY.RED, serverguard.banTable[steamID].player, SERVERGUARD.NOTIFY.WHITE, "."); + + serverguard:UnbanPlayer(steamID); + end; + else + if (serverguard.player:HasPermission(player, "Unban")) then + local steamID = arguments[1]; + + if (serverguard.banTable[steamID]) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(player), SERVERGUARD.NOTIFY.WHITE, " has unbanned ", SERVERGUARD.NOTIFY.RED, serverguard.banTable[steamID].player, SERVERGUARD.NOTIFY.WHITE, "."); + + serverguard:UnbanPlayer(steamID, player); + elseif (!util.IsConsole(player)) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "No ban entry exists for that Steam ID!"); + end; + end; + end; +end; + +concommand.Add("serverguard_unban", serverGuard_UnbanPlayer); + +-- +-- The query command for the bans menu. +-- + +local function sendBanChunkData(player, banList) + serverguard.netstream.StartChunked(player, "sgGetBanListChunk", banList); +end; + +local function serverGuard_QueryBans(player) + if (!player.nextBanQuery) then + player.nextBanQuery = 0; + end; + + local curTime = CurTime(); + + if (player.nextBanQuery < curTime) then + if (player.banQueryData != nil) then + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "Please wait until you finish loading the ban data!"); + return; + end; + + local banCount = table.Count(serverguard.banTable); + serverguard.netstream.Start(player, "sgGetBanCount", banCount); + + local banList = {}; + + for steamID, data in pairs(serverguard.banTable) do + banList[#banList + 1] = { + steamID = steamID, + playerName = data.player, + length = tonumber(data.endTime) - tonumber(data.startTime), + reason = data.reason, + admin = data.admin + }; + end; + + if (banCount > 0) then + sendBanChunkData(player, banList); + end; + + player.nextBanQuery = curTime + 10; + else + serverguard.Notify(player, SERVERGUARD.NOTIFY.RED, "Please wait " .. math.ceil(player.nextBanQuery - curTime) .. " seconds.") + end; +end; + +concommand.Add("serverguard_rfbans", serverGuard_QueryBans) + +-- +-- Add a ban for players not on the server. +-- + +local function serverGuard_AddMenuBan(pPlayer, command, arguments) + if (serverguard.player:HasPermission(pPlayer, "Ban")) then + local found = NULL + local steamID = arguments[1] + + for k, v in ipairs(player.GetAll()) do + if (v:SteamID() == steamID) then + if (serverguard.player:GetImmunity(v) >= serverguard.player:GetImmunity(pPlayer)) then + serverguard.Notify(pPlayer, SERVERGUARD.NOTIFY.RED, "There is a player with this steamID on the server that has a higher immunity than you!") + + return + else + found = v + + break + end + end + end + + local length = tonumber(arguments[2]) * 60; + local playerName = arguments[3] + local reason = table.concat(arguments, " ", 4) + local communityID = "-1" + local startTime = os.time() + local endTime = startTime + length; + local adminName = serverguard.player:GetName(pPlayer) + local ip = "" + + reason = reason or "No Reason" + + if (length == 0) then + endTime = length; + end; + + if (IsValid(found)) then + communityID = found:SteamID64() + playerName = serverguard.player:GetName(found) + ip = (found:IPAddress():gsub(":%d+", "")); + + if (length > 0) then + local hours = math.Round(length / 3600); + + if (hours >= 1) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, playerName, SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, tostring(hours), SERVERGUARD.NOTIFY.WHITE, " hour(s). Reason: " .. reason); + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, playerName, SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, tostring(math.Round(length / 60)), SERVERGUARD.NOTIFY.WHITE, " minute(s). Reason: " .. reason); + end; + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, playerName, SERVERGUARD.NOTIFY.WHITE, ",", SERVERGUARD.NOTIFY.RED, " permanently", SERVERGUARD.NOTIFY.WHITE, ". Reason: " .. reason); + end; + + found:Kick(reason) + --game.ConsoleCommand("kickid " .. found:UserID() .. " " .. reason .. "\n") + else + if (length > 0) then + local hours = math.Round(length / 3600); + + if (hours >= 1) then + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, steamID, SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, tostring(hours), SERVERGUARD.NOTIFY.WHITE, " hour(s). Reason: " .. reason); + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, steamID, SERVERGUARD.NOTIFY.WHITE, " for ", SERVERGUARD.NOTIFY.RED, tostring(math.Round(length / 60)), SERVERGUARD.NOTIFY.WHITE, " minute(s). Reason: " .. reason); + end; + else + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has banned ", SERVERGUARD.NOTIFY.RED, steamID, SERVERGUARD.NOTIFY.WHITE, ",", SERVERGUARD.NOTIFY.RED, " permanently", SERVERGUARD.NOTIFY.WHITE, ". Reason: " .. reason); + end; + end; + + local insertObj = serverguard.mysql:Insert("serverguard_bans"); + insertObj:Insert("steam_id", steamID); + insertObj:Insert("community_id", communityID); + insertObj:Insert("player", playerName); + insertObj:Insert("reason", reason); + insertObj:Insert("start_time", tostring(startTime)); + insertObj:Insert("end_time", tostring(endTime)); + insertObj:Insert("admin", adminName); + insertObj:Insert("ip_address", ip); + insertObj:Execute(); + + local data = { + communityID = communityID, + player = playerName, + reason = reason, + startTime = startTime, + endTime = endTime, + admin = adminName + }; + + serverguard.netstream.Start(nil, "sgNewBan", { + steamID = steamID, + playerName = data.player, + length = tonumber(data.endTime) - tonumber(data.startTime), + reason = data.reason, + admin = data.admin + }); + + serverguard.banTable[steamID] = data; + end; +end; + +concommand.Add("serverguard_addmban", serverGuard_AddMenuBan) + +-- +-- Edit a players ban. +-- +local function serverGuard_EditBan(pPlayer, command, arguments) + if (serverguard.player:HasPermission(pPlayer, "Edit Ban")) then + local steamID = arguments[1] + + local length = tonumber(arguments[2]) * 60; + local playerName = arguments[3] + local reason = table.concat(arguments, " ", 4) + local communityID = "-1" + local startTime = os.time() + local endTime = startTime + length; + local adminName = serverguard.player:GetName(pPlayer) + local parsedLength, parsedLengthText = util.ParseDuration(arguments[2]) + + reason = reason or "No Reason" + + local updateObj = serverguard.mysql:Update("serverguard_bans"); + updateObj:Update("reason", reason); + updateObj:Update("start_time", tostring(startTime)); + updateObj:Update("end_time", tostring(endTime)); + updateObj:Where("steam_id", steamID); + updateObj:Execute(); + + local data = { + communityID = communityID, + player = playerName, + reason = reason, + startTime = startTime, + endTime = endTime, + admin = adminName + }; + + serverguard.banTable[steamID] = data; + serverguard.Notify(nil, SERVERGUARD.NOTIFY.GREEN, serverguard.player:GetName(pPlayer), SERVERGUARD.NOTIFY.WHITE, " has edited ", SERVERGUARD.NOTIFY.RED, playerName, "'s", SERVERGUARD.NOTIFY.WHITE, " ban. Length: ", SERVERGUARD.NOTIFY.RED, parsedLengthText, SERVERGUARD.NOTIFY.WHITE, " Reason: ", SERVERGUARD.NOTIFY.RED, reason); + end; +end; +concommand.Add("serverguard_editban", serverGuard_EditBan) + + +-- +-- Import ULX groups, users and bans. +-- + +local function serverGuard_ImportULX(pPlayer, command, arguments) + if (util.IsConsole(pPlayer)) then + if istable(ULib) and type(ULib.parseKeyValues) == "function" and type(ULib.removeCommentHeader) == "function" and type(ULib.fileRead) == "function" then + + -- Reading data + + local ulxUsers, uErr = ULib.parseKeyValues(ULib.removeCommentHeader(ULib.fileRead(ULib.UCL_USERS), "/")) + local ulxGroups, gErr = ULib.parseKeyValues(ULib.removeCommentHeader(ULib.fileRead(ULib.UCL_GROUPS), "/")) + local ulxBans, bErr = ULib.parseKeyValues(ULib.removeCommentHeader(ULib.fileRead(ULib.BANS_FILE), "/")) + + if (not ulxUsers) or (not ulxGroups) or (not ulxBans) then + print("Some of the ULX data files are corrupted or do not exist.") + return + end + + -- Importing bans + + print("Importing bans...") + + local c = 0 + for k,v in pairs(ulxBans) do + local unbanTime = tonumber(v.unban) + if (unbanTime == 0 or unbanTime > os.time()) then + BAN_INSERT( + v.steamID, util.SteamIDTo64(v.steamID), v.name or v.steamID, v.reason, v.time or 0, v.unban or 0, v.admin or "", "" + ) + c = c + 1 + end + end + + print(c .. " bans have been imported.") + + -- Importing groups + + print("Importing groups...") + + c = 0 + for k,v in pairs(ulxGroups) do + local rankData = serverguard.ranks:GetRank(k) + if (!rankData) then + serverguard.ranks:AddRank(k, k, 0, Color(100, 150, 245, 255), "", + { + Restrictions = { + ["Vehicles"] = 2, + ["Effects"] = 4, + ["Props"] = 128, + ["Ragdolls"] = 1, + ["Npcs"] = 1, + ["Tools"] = {}, + ["Sents"] = 4, + ["Balloons"] = 4, + ["Buttons"] = 6, + ["Dynamite"] = 3, + ["Effects"] = 10, + ["Emitters"] = 3, + ["Hoverballs"] = 6, + ["Lamps"] = 2, + ["Lights"] = 2, + ["Thrusters"] = 10, + ["Wheels"] = 10, + }, + + phys_color = Color(77, 255, 255) + } + ) + serverguard.ranks:NetworkRank(k); + serverguard.ranks:SaveTable(k, nil, true); + c = c + 1 + end + end + + print(c .. " groups have been imported.") + + -- Importing users + + print("Importing users...") + + c = 0 + for k,v in pairs(ulxUsers) do + local rankData = serverguard.ranks:GetRank(v.group) + if (rankData) then + serverguard.player:SetRank(k, rankData.unique, 0) + c = c + 1 + end + end + + print(c .. " users have been imported.") + + else + print("You must have ULX installed to import bans from it.") + end + end +end + +concommand.Add("serverguard_importulx", serverGuard_ImportULX) diff --git a/garrysmod/addons/admin-sg/lua/tools/sv_ranks.lua b/garrysmod/addons/admin-sg/lua/tools/sv_ranks.lua new file mode 100644 index 0000000..a74b27e --- /dev/null +++ b/garrysmod/addons/admin-sg/lua/tools/sv_ranks.lua @@ -0,0 +1,205 @@ +--[[ + � 2017 Thriving Ventures Limited do not share, re-distribute or modify + + without permission of its author (gustaf@thrivingventures.com). +]] + +-- +-- Default ranks. +-- serverguard.ranks:AddRank(unique, name, immunity, color, texture, data) +-- + +serverguard.ranks:AddRank("user", "Guest", 0, Color(100, 150, 245, 255), "icon16/user.png", { + Restrictions = { + ["Vehicles"] = 2, + ["Effects"] = 4, + ["Props"] = 128, + ["Ragdolls"] = 1, + ["Npcs"] = 1, + ["Tools"] = {}, + ["Sents"] = 4, + ["Balloons"] = 4, + ["Buttons"] = 6, + ["Dynamite"] = 3, + ["Effects"] = 10, + ["Emitters"] = 3, + ["Hoverballs"] = 6, + ["Lamps"] = 2, + ["Lights"] = 2, + ["Thrusters"] = 10, + ["Wheels"] = 10, + }, + + phys_color = Color(77, 255, 255) +}); + +-- +-- Administrator groups. +-- + +serverguard.ranks:AddRank("admin", "Administrator", 20, Color(117, 0, 211), "icon16/award_star_bronze_1.png", { + Permissions = { + ["Sandbox settings"] = false, + ["Set Rank"] = false, + ["Edit Ranks"] = false, + ["Manage Players"] = true, + ["Extinguish"] = true, + ["Invisible"] = true, + ["Slay"] = true, + ["NPC Target"] = true, + ["Ban"] = true, + ["Edit Ban"] = false, + ["Unban"] = true, + ["Manage Plugins"] = false, + ["Slap"] = true, + ["Set Armor"] = true, + ["Strip Weapons"] = true, + ["Map Restart"] = true, + ["Clear Decals"] = true, + ["Send"] = true, + ["Give Ammo"] = true, + ["Ragdoll"] = true, + ["Goto"] = true, + ["Quick Menu"] = true, + ["Kick"] = true, + ["Spectate"] = true, + ["Screencap"] = false, + ["Ignite"] = true, + ["Server Logs"] = true, + ["Map"] = true, + ["Respawn"] = true, + ["Set Health"] = true, + ["Bring"] = true, + ["Noclip"] = true, + ["Manage Restrictions"] = false, + ["Give Weapon"] = true, + ["Play Song"] = false, + ["Analytics"] = false, + ["God mode"] = true, + ["Manage Advertisements"] = false, + ["Mute"] = true, + ["Announce"] = true, + ["Freeze"] = true, + ["Rcon"] = false, + ["Admin"] = true, + ["Superadmin"] = false, + ["Manage Prop-Protection"] = false, + ["Bypass Prop-Protection"] = true, + ["Physgun Player"] = true, + ["Freeze Props"] = false, + ["Manage MOTD"] = false, + ["Manage Reports"] = true, + ["Bypass Prop Deletion"] = true, + ["Respond to Help Requests"] = true, + ["Return"] = true, + ["Admin Chat"] = true + }, + + Restrictions = { + ["Vehicles"] = 4, + ["Effects"] = 6, + ["Props"] = 256, + ["Ragdolls"] = 4, + ["Npcs"] = 2, + ["Tools"] = {}, + ["Sents"] = 8, + ["Balloons"] = 6, + ["Buttons"] = 15, + ["Dynamite"] = 6, + ["Effects"] = 25, + ["Emitters"] = 6, + ["Hoverballs"] = 10, + ["Lamps"] = 6, + ["Lights"] = 6, + ["Thrusters"] = 25, + ["Wheels"] = 25, + }, + + phys_color = Color(77, 255, 255) +}); + +serverguard.ranks:AddRank("superadmin", "Super Administrator", 25, Color(0, 150, 0), "icon16/award_star_silver_1.png", { + Permissions = { + ["Sandbox settings"] = true, + ["Set Rank"] = false, + ["Edit Ranks"] = false, + ["Manage Players"] = true, + ["Extinguish"] = true, + ["Invisible"] = true, + ["Slay"] = true, + ["NPC Target"] = true, + ["Ban"] = true, + ["Edit Ban"] = true, + ["Unban"] = true, + ["Manage Plugins"] = false, + ["Slap"] = true, + ["Set Armor"] = true, + ["Strip Weapons"] = true, + ["Map Restart"] = true, + ["Clear Decals"] = true, + ["Send"] = true, + ["Give Ammo"] = true, + ["Ragdoll"] = true, + ["Goto"] = true, + ["Quick Menu"] = true, + ["Kick"] = true, + ["Spectate"] = true, + ["Screencap"] = true, + ["Ignite"] = true, + ["Server Logs"] = true, + ["Map"] = true, + ["Respawn"] = true, + ["Set Health"] = true, + ["Bring"] = true, + ["Noclip"] = true, + ["Manage Restrictions"] = false, + ["Give Weapon"] = true, + ["Play Song"] = true, + ["Analytics"] = true, + ["God mode"] = true, + ["Manage Advertisements"] = true, + ["Mute"] = true, + ["Announce"] = true, + ["Freeze"] = true, + ["Rcon"] = false, + ["Superadmin"] = true, + ["Admin"] = true, + ["Manage Prop-Protection"] = true, + ["Bypass Prop-Protection"] = true, + ["Manage Prop Blacklist"] = true, + ["Bypass Prop Blacklist"] = true, + ["Physgun Player"] = true, + ["Freeze Props"] = true, + ["Manage MOTD"] = true, + ["Manage Reports"] = true, + ["Bypass Prop Deletion"] = true, + ["Respond to Help Requests"] = true, + ["Return"] = true, + ["Admin Chat"] = true + }, + + Restrictions = { + ["Vehicles"] = 8, + ["Effects"] = 12, + ["Props"] = 512, + ["Ragdolls"] = 6, + ["Npcs"] = 4, + ["Tools"] = {}, + ["Sents"] = 12, + ["Balloons"] = 12, + ["Buttons"] = 30, + ["Dynamite"] = 12, + ["Effects"] = 50, + ["Emitters"] = 12, + ["Hoverballs"] = 20, + ["Lamps"] = 12, + ["Lights"] = 12, + ["Thrusters"] = 50, + ["Wheels"] = 50, + }, + + phys_color = Color(77, 255, 255) +}); + +-- The founder rank is automatically granted all permissions, so no data needs to be set here. +serverguard.ranks:AddRank("founder", "Founder", 99, Color(240, 0, 0), "icon16/award_star_gold_1.png"); diff --git a/garrysmod/addons/admin-tickets/lua/autorun/cats-load.lua b/garrysmod/addons/admin-tickets/lua/autorun/cats-load.lua new file mode 100644 index 0000000..eb80da3 --- /dev/null +++ b/garrysmod/addons/admin-tickets/lua/autorun/cats-load.lua @@ -0,0 +1,16 @@ +-- please at least do not remove this comment + -- by chelog + +include "cats/shared.lua" + +if SERVER then + AddCSLuaFile "cats/client.lua" + AddCSLuaFile "cats/shared.lua" + include "cats/server.lua" +else + include "cats/client.lua" +end + +if SERVER then + cats:Log('Initialized.') +end diff --git a/garrysmod/addons/admin-tickets/lua/cats/client.lua b/garrysmod/addons/admin-tickets/lua/cats/client.lua new file mode 100644 index 0000000..a4d5157 --- /dev/null +++ b/garrysmod/addons/admin-tickets/lua/cats/client.lua @@ -0,0 +1,674 @@ +--################################################# +-- Main frame +--################################################# + +--------------------------------------------------- +-- FONTS +--------------------------------------------------- + +surface.CreateFont("cats.small", { + font = "Roboto Bold", + extended = true, + size = 16, + weight = 500, +}) +surface.CreateFont("cats.small-underline", { + font = "Roboto Bold", + extended = true, + underline = true, + size = 16, + weight = 500, +}) + +--------------------------------------------------- +-- HELPER FUNCTIONS +--------------------------------------------------- + +-- apply table to action button +local buttons -- declare here, fill later +local function applyButton(pnl, name, ply, steamID) + local data = buttons[name] + if not data then data = { + tooltip = 'error', + icon = Material(octolib.icons.silk16('error')), + click = function() end + } end + + pnl:SetToolTip(data.tooltip) + pnl.icon = data.icon + pnl.DoClick = function(self) data.click(self, ply, steamID) end +end + +-- nice time +local function niceTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format("%02i:%02i:%02i", h, m, s) + +end + +--------------------------------------------------- +-- CACHE +--------------------------------------------------- + +-- icons cache +local icons = { + action_claim = Material(octolib.icons.silk16('accept_button')), + action_unclaim = Material(octolib.icons.silk16('cancel')), + actions = Material(octolib.icons.silk16('text_list_bullets')), + action_callon = Material(octolib.icons.silk16('lightbulb_off')), + action_calloff = Material(octolib.icons.silk16('lightbulb')), + action_close = Material(octolib.icons.silk16('application_form_delete')), + noStar = Material(octolib.icons.silk16('bullet_white')), + star = Material(octolib.icons.silk16('star')), +} + +-- button tables +buttons = { + action_claim = { + tooltip = cats.lang.action_claim, + icon = icons.action_claim, + click = function(self, ply, steamID) + netstream.Start('cats.claimTicket', steamID, true) + applyButton(self, 'action_unclaim', ply, steamID) + end + }, + action_unclaim = { + tooltip = cats.lang.action_unclaim, + icon = icons.action_unclaim, + click = function(self, ply, steamID) + netstream.Start('cats.claimTicket', steamID, false) + applyButton(self, 'action_claim', ply, steamID) + end + }, + actions = { + tooltip = cats.lang.actions, + icon = icons.actions, + click = function(self, ply) + local m = DermaMenu() + for i, act in ipairs( cats.config.commands ) do + m:AddOption( act.text, function() + act.click(ply) + end):SetIcon(octolib.icons.silk16(act.icon or 'wand')) + end + m:SetPos( input.GetCursorPos() ) + m:Open() + end + }, + action_callon = { + tooltip = cats.lang.action_callon, + icon = icons.action_callon, + click = function(self, ply) + applyButton(self, 'action_calloff', ply, steamID) + end + }, + action_calloff = { + tooltip = cats.lang.action_calloff, + icon = icons.action_calloff, + click = function(self, ply) + applyButton(self, 'action_callon', ply, steamID) + end + }, + action_close = { + tooltip = cats.lang.action_close, + icon = icons.action_close, + click = function(self, ply, steamID) + netstream.Start('cats.closeTicket', steamID) + end + }, +} + +-- default button list +local actionList = { + 'action_claim', + 'actions', + -- 'action_callon', + 'action_close', +} + +-- debug ticket data +local debugTicket = { + user = LocalPlayer(), + userID = 'STEAM_X:X:XXXXXXXX', + admin = LocalPlayer(), + adminID = 'STEAM_X:X:XXXXXXXX', + chatLog = { + {"Зюзя", "Админ тп, застрял", false}, + {"СуперВася", "Ща, погоди", true}, + {"Зюзя", "Ну где вы???", false}, + {"УберПетя", "Бля, Вася, да вытащи ты его уже, наконец, он заебал вопить, как малое дите, сука, ебаный в рот", true}, + {"СуперВася", "Ну ща-ща, я дорешаю жалобу", true}, + {"УберПетя", "Да с хера ли ты берешь столько жалоб? Разберись сначала с одной, потом уж на другие иди", true}, + {"СуперВася", "Да хорошо, блять, но дай сейчас-то разберусь", true}, + {"Зюзя", "Идите оба нахуй, я выбрался уже", false}, + } +} + +-- my ticket +local myTicket + +--------------------------------------------------- +-- MAIN CODE +--------------------------------------------------- + +-- add a ticket frame to container +local function addTicketToFrame( data ) + + -- sound notification + surface.PlaySound(cats.config.newTicketSound) + + -- ticket panel + local t = cats.ticketContainer:Add("DButton") + t:SetSize(cats.config.spawnSize[1], 180) + t:SetText('') + t.expanded = true + t.ticket = data -- apply ticket + t.Paint = function(self, w, h) + local user, admin = self.ticket.user, self.ticket.admin + + surface.SetDrawColor(30,40,50, 220) + surface.DrawRect(0, 0, w, h) + if self.Hovered then + surface.SetDrawColor(255,255,255, 2) + surface.DrawRect(0, 0, w, h) + end + surface.SetDrawColor(0,0,0, 255) + surface.DrawLine(0, -1, 0, h) + surface.DrawLine(-1, h-1, w, h-1) + surface.DrawLine(w-1, h, w-1, -1) + + local time = '(' .. os.date( "%M:%S", CurTime() - self.ticket.created ) .. ')' + local userName = IsValid(user) and user:Name() or cats.lang.userDisconnected + draw.SimpleText(time .. ' ' .. userName, 'cats.small', 8, 15, Color(220,220,220), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + if IsValid(admin) then + draw.SimpleText(admin:Name(), 'cats.small', w-8, 15, Color(180,200,240), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + t.DoClick = function(self) + self.expanded = not self.expanded + for i,v in ipairs(cats.ticketContainer:GetChildren()) do + -- if self ~= v then v.expanded = false end + v:InvalidateLayout(true) + end + cats.ticketContainer:Layout() + timer.Simple(0, function() + self.chatLog:GotoTextEnd() + end) + end + t.PerformLayout = function(self) + self:SetSize(self:GetParent():GetWide(), self.expanded and 180 or 30) + self.controls:SetVisible(self.expanded) + + end + + -- controls for ticket + local c = vgui.Create("DPanel", t) + c:DockMargin(1,1,1,1) + c:Dock(BOTTOM) + c:SetTall(150) + c.Paint = function() end + t.controls = c + + -- action buttons for controls + t.controls.buttons = {} + for i, v in pairs(actionList) do + local b = vgui.Create("DButton", c) + b:SetSize(30, 30) + b:SetPos(0, (i-1)*30) + b:SetText('') + b.Paint = function(self, w, h) + if self.Hovered then draw.RoundedBox(0, 0, 0, w, h, Color(255,255,255,2)) end + surface.SetMaterial(self.icon) + surface.SetDrawColor(255,255,255) + surface.DrawTexturedRect(7, 7, 16, 16) + end + + applyButton(b, v, t.ticket.user, t.ticket.userID) + t.controls.buttons[v] = b + end + + -- chat + local cp = vgui.Create("DPanel", t.controls) + cp:Dock(FILL) + cp:DockMargin(30,0,0,0) + cp.Paint = function(self, w, h) + surface.SetDrawColor(0,0,0, 100) + surface.DrawRect(0, 0, w, h-20) + surface.SetDrawColor(0,0,0, 255) + surface.DrawLine(0, h, 0, 0) + surface.DrawLine(-1, 0, w, 0) + surface.DrawLine(-1, h-21, w, h-21) + end + t.chat = cp + + -- chat entry + local ce = vgui.Create("DButton", t.chat) + ce:Dock(BOTTOM) + ce:SetText('') + ce:SetTall(20) + ce:SetCursor('beam') + ce.Paint = function(self, w, h) + if self.Hovered then draw.RoundedBox(0, 0, 0, w, h, Color(255,255,255,1)) end + draw.SimpleText(cats.lang.sendMessage, 'cats.small', 8, 10, Color(220,220,220, 50), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + ce.DoClick = function(self) + Derma_StringRequest( + cats.lang.sendMessage, cats.lang.typeYourMessage, '', + function(val) + netstream.Start('cats.dispatchMessage', data.userID, val) + end, + nil, + cats.lang.ok, cats.lang.cancel + ) + end + + -- chat log + local cl = vgui.Create("RichText", t.chat) + cl:Dock(FILL) + cl.Paint = function(self) -- WHAT. THE. FUCK. + self.m_FontName = "cats.small" + self:SetFontInternal("cats.small") + self:SetUnderlineFont("cats.small-underline") + self:SetBGColor(Color(0,0,0,0)) + self.Paint = nil + end + cl.ActionSignal = function(_, name, val) + if name == "TextClicked" then + octoesc.OpenURL(val) + end + end + t.chatLog = cl + + cats.ticketContainer[data.userID] = t + cats.ticketFrame:PerformLayout() + +end + +local function addTicketChatLog(steamID, sender, msg, isAdmin) + + octolib.func.chain({ + + function(reply) + if not octolib.string.isSteamID(sender) then return reply(sender) end + steamworks.RequestPlayerInfo(util.SteamIDTo64(sender), reply) + end, + + function(_, name) + + local cl = cats.ticketContainer[steamID].chatLog + if not IsValid(cl) then return end + + if isAdmin then + cl:InsertColorChange(50,120,180, 255) + else + cl:InsertColorChange(180,160,50, 255) + end + cl:AppendText("\n" .. name) + + cl:InsertColorChange(220,220,220, 255) + cl:AppendText(": ") + + local data = octolib.string.splitByUrl(msg) + for _,v in ipairs(data) do + if type(v) == 'string' then + cl:AppendText(v) + else + cl:InsertClickableTextStart(v[1]) + cl:InsertColorChange(0, 130, 255, 255) + cl:AppendText(v[1]) + cl:InsertColorChange(220,220,220, 255) + cl:InsertClickableTextEnd() + end + end + + end, + + }) + +end + +-- generate main frame +hook.Add("PlayerFinishedLoading", "cats", function() + + if IsValid(cats.ticketFrame) then cats.ticketFrame:Remove() end + local w, h = cats.config.spawnSize[1], cats.config.spawnSize[2] + local x, y = cats.config.spawnPosAdmin[1], cats.config.spawnPosAdmin[2] + + -- main frame + local p0 = vgui.Create("DFrame") + p0:SetSize(w, h) + p0:SetPos(x, y) + p0:DockPadding(0, 24, 0, 0) + p0:SetTitle('') + p0:ShowCloseButton(false) + cats.ticketFrame = p0 + + -- scroll panel + local p1 = vgui.Create("DScrollPanel", p0) + p1:Dock(FILL) + local oldLayout = p1.PerformLayout + p1.PerformLayout = function(self) + oldLayout(self) + for i, v in ipairs(cats.ticketContainer:GetChildren()) do + v:InvalidateLayout() + end + end + local oldThink = p0.Think + function p0:Think() + if isfunction(oldThink) then oldThink(self) end + p1:SetVisible(hook.Run('HUDShouldDraw', 'cats') ~= false) + end + + -- icon layout + local p2 = vgui.Create("DIconLayout", p1) + p2:Dock(FILL) + p2:SetSpaceX(0) + p2:SetSpaceY(0) + cats.ticketContainer = p2 + + -- finish up main frame with some spicy hooks + local oldLayout = p0.PerformLayout + p0.PerformLayout = function(self) + oldLayout(self) + self:SetTall( math.min(p2:GetTall(), ScrH() - 100, 600) + 27 ) + self:SetVisible(#p2:GetChildren() > 0) + end + p0.Paint = function(self, w, h) + if hook.Run('HUDShouldDraw', 'cats') == false then return end + surface.SetDrawColor(30,40,50, 255) + surface.DrawRect(0, 0, w, 24) + surface.SetDrawColor(0,0,0, 255) + surface.DrawOutlinedRect(0, 0, w, 24) + draw.SimpleText(cats.lang.openTickets .. ' (' .. #p2:GetChildren() .. ')', 'cats.small', 8, 12, Color(220,220,220), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + +end) + +-- my ticket frame +if IsValid(cats.myTicketFrame) then cats.myTicketFrame:Remove() end +local function createMyTicket( data ) + + -- sound notification + surface.PlaySound(cats.config.newTicketSound) + + myTicket = data + local w, h = cats.config.spawnSize[1], cats.config.spawnSize[2] + local x, y = cats.config.spawnPosUser[1], cats.config.spawnPosUser[2] + + -- ticket frame + local t = vgui.Create("DFrame") + t:ShowCloseButton(false) + t:SetSize(w, 220) + t:SetPos(x, y) + t:DockPadding(0,30,0,0) + t:SetTitle('') + t.ticket = myTicket -- apply ticket + t.Paint = function(self, w, h) + if not self.visible then return end + local user, admin = self.ticket.user, self.ticket.admin + + surface.SetDrawColor(30,40,50, 220) + surface.DrawRect(0, 0, w, h) + surface.SetDrawColor(0,0,0, 255) + surface.DrawOutlinedRect(0,0,w,h) + + local time = '(' .. os.date( "%M:%S", CurTime() - self.ticket.created ) .. ')' + draw.SimpleText(time .. ' ' .. cats.lang.myTicket, 'cats.small', 8, 15, Color(220,220,220), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + if IsValid(admin) then + draw.SimpleText(admin:Name(), 'cats.small', w-8, 15, Color(180,200,240), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + + -- close ticket button + surface.SetFont('cats.small') + local tw, th = surface.GetTextSize(cats.lang.action_close) + local b = vgui.Create("DButton", t) + b:SetText('') + b:SetSize(tw + 16,30) + b:AlignRight(1) + b.Paint = function(self, w, h) + if self.Hovered then draw.RoundedBox(0, 0, 0, w, h, Color(255,255,255,1)) end + draw.SimpleText(cats.lang.action_close, 'cats.small', w/2, h/2, Color(220,220,220), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + b.DoClick = function(self) + netstream.Start('cats.closeTicket', LocalPlayer():SteamID()) + end + t.closeBut = b + + -- chat + local cp = vgui.Create("DPanel", t) + cp:Dock(FILL) + cp.Paint = function(self, w, h) + surface.SetDrawColor(0,0,0, 100) + surface.DrawRect(0, 0, w, h-20) + surface.SetDrawColor(0,0,0, 255) + surface.DrawLine(0, h, 0, 0) + surface.DrawLine(-1, 0, w, 0) + surface.DrawLine(-1, h-21, w, h-21) + end + t.chat = cp + + -- chat entry + local ce = vgui.Create("DButton", t.chat) + ce:Dock(BOTTOM) + ce:SetText('') + ce:SetTall(20) + ce:SetCursor('beam') + ce.Paint = function(self, w, h) + if self.Hovered then draw.RoundedBox(0, 0, 0, w, h, Color(255,255,255,1)) end + draw.SimpleText(cats.lang.sendMessage, 'cats.small', 8, 10, Color(220,220,220, 50), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + ce.DoClick = function(self) + Derma_StringRequest( + cats.lang.sendMessage, cats.lang.typeYourMessage, '', + function(val) + netstream.Start('cats.dispatchMessage', LocalPlayer():SteamID(), val) + end, + nil, + cats.lang.ok, cats.lang.cancel + ) + end + + -- chat log + local cl = vgui.Create("RichText", t.chat) + cl:Dock(FILL) + cl.Paint = function(self) -- WHAT. THE. FUCK. + self.m_FontName = "cats.small" + self:SetFontInternal("cats.small") + self:SetUnderlineFont("cats.small-underline") + self:SetBGColor(Color(0,0,0,0)) + self.Paint = nil + end + cl.ActionSignal = function(_, name, val) + if name == "TextClicked" then + octoesc.OpenURL(val) + end + end + t.chatLog = cl + + -- You can't just set visibility for self because it stops thinking + local oldThink = t.Think + function t:Think() + if isfunction(oldThink) then oldThink(self) end + self.visible = hook.Run('HUDShouldDraw', 'cats') ~= false + if not (myTicket and myTicket.adminID) then + b:SetVisible(self.visible) + end + cp:SetVisible(self.visible) + ce:SetVisible(self.visible) + cl:SetVisible(self.visible) + end + + cats.myTicketFrame = t + +end + +local function addMyTicketChatLog(sender, msg, isAdmin) + + octolib.func.chain({ + + function(reply) + if not octolib.string.isSteamID(sender) then return reply(sender) end + steamworks.RequestPlayerInfo(util.SteamIDTo64(sender), reply) + end, + + function(_, name) + + local cl = cats.myTicketFrame.chatLog + if not IsValid(cl) then return end + + if isAdmin then + cl:InsertColorChange(50,120,180, 255) + else + cl:InsertColorChange(180,160,50, 255) + end + cl:AppendText("\n" .. name) + + cl:InsertColorChange(220,220,220, 255) + cl:AppendText(": ") + + local data = octolib.string.splitByUrl(msg) + for _,v in ipairs(data) do + if type(v) == 'string' then + cl:AppendText(v) + else + cl:InsertClickableTextStart(v[1]) + cl:InsertColorChange(0, 130, 255, 255) + cl:AppendText(v[1]) + cl:InsertColorChange(220,220,220, 255) + cl:InsertClickableTextEnd() + end + end + + end, + + }) + +end + +netstream.Hook('cats.dispatchMessage', function(steamID, sender, msg) + + if steamID == LocalPlayer():SteamID() then + if sender ~= LocalPlayer():SteamID() then surface.PlaySound(cats.config.newTicketSound) end + if myTicket then + addMyTicketChatLog(sender, msg, sender ~= LocalPlayer():SteamID()) + else + createMyTicket({created = CurTime()}) + addMyTicketChatLog(sender, msg, sender ~= LocalPlayer():SteamID()) + end + elseif IsValid(cats.ticketContainer[steamID]) then + addTicketChatLog(steamID, sender, msg, sender ~= steamID) + else + local user = player.GetBySteamID(steamID) + -- if not IsValid(user) then return end + + addTicketToFrame({ + user = user, + userID = steamID, + created = CurTime(), + }) + addTicketChatLog(steamID, sender, msg, sender ~= steamID) + end + +end) + +netstream.Hook('cats.claimTicket', function(steamID, admin, doClaim) + + if not IsValid(admin) then return end + + if steamID == LocalPlayer():SteamID() and myTicket then + myTicket.admin = doClaim and admin or nil + myTicket.adminID = doClaim and admin:SteamID() or nil + cats.myTicketFrame.closeBut:SetVisible(not doClaim) + elseif IsValid(cats.ticketContainer[steamID]) then + local ticket = cats.ticketContainer[steamID].ticket + ticket.admin = doClaim and admin or nil + ticket.adminID = doClaim and admin:SteamID() or nil + + if ticket.adminID ~= LocalPlayer():SteamID() then + local b = cats.ticketContainer[steamID].controls.buttons['action_claim'] + local user = player.GetBySteamID(ticket.userID) + if doClaim then + applyButton(b, 'action_unclaim', user, ticket.userID) + b:SetEnabled(false) + else + applyButton(b, 'action_claim', user, ticket.userID) + b:SetEnabled(true) + end + end + end + +end) + +netstream.Hook('cats.closeTicket', function(steamID) + + if steamID == LocalPlayer():SteamID() and myTicket then + cats.myTicketFrame:Remove() + myTicket = nil + elseif IsValid(cats.ticketContainer[steamID]) then + cats.ticketContainer[steamID].ticket = nil + cats.ticketContainer[steamID]:Remove() + cats.ticketFrame:PerformLayout() + end + +end) + +netstream.Hook('cats.syncTickets', function(tickets) + + for steamID, t in pairs(tickets) do + local user = player.GetBySteamID(steamID) + if not IsValid(user) then continue end + + addTicketToFrame({ + user = user, + userID = steamID, + created = t.createdGameTime, + admin = t.admin, + adminID = t.adminID + }) + + if IsValid(t.admin) then + local b = cats.ticketContainer[steamID].controls.buttons['action_claim'] + applyButton(b, 'action_unclaim', t.user, steamID) + b:SetEnabled(false) + end + + for k, v in pairs(t.chatLog) do + addTicketChatLog(steamID, v[1], v[2], v[3]) + end + end + +end) + +concommand.Add("cats_test_admin", function() + + local steamID = LocalPlayer():SteamID() + addTicketToFrame({ + user = LocalPlayer(), + userID = steamID, + created = CurTime(), + admin = LocalPlayer(), + adminID = steamID, + }) + addTicketChatLog(steamID, "chelog", "Admin let me test my ticket!", false) + addTicketChatLog(steamID, "Admin", "Alright.", true) + +end) + +concommand.Add("cats_test_admin_clear", function() + + cats.ticketContainer:Clear() + +end) + +concommand.Add("cats_test_myticket", function() + + createMyTicket({created = CurTime()}) + addMyTicketChatLog("chelog", "Admin let me test my ticket!", false) + addMyTicketChatLog("Admin", "Alright.", true) + +end) diff --git a/garrysmod/addons/admin-tickets/lua/cats/server.lua b/garrysmod/addons/admin-tickets/lua/cats/server.lua new file mode 100644 index 0000000..3ad2c30 --- /dev/null +++ b/garrysmod/addons/admin-tickets/lua/cats/server.lua @@ -0,0 +1,418 @@ +--------------------------------------------------- +-- CACHE STORAGE +--------------------------------------------------- + +cats.currentTickets = cats.currentTickets or {} +cats.adminDataCache = cats.adminDataCache or {} +cats.lastNotifiedTickets = cats.lastNotifiedTickets or {} + +-- build receivers list +local function getAdmins(steamID) + + local admins = {} + for i, ply in ipairs(player.GetAll()) do + if cats.config.playerCanSeeTicket(ply, steamID) then table.insert(admins, ply) end + end + + return admins + +end + +--------------------------------------------------- +-- BASE CODE +--------------------------------------------------- + +-- create api interface + +cats.api = octolib.api({ + url = 'https://octothorp.team/cats/api', + headers = { ['Authorization'] = CFG.keys.cats }, +}) + +local ignoreMsgsTime = {} + +-- log in console +function cats:Log(text) + + print('[# CATS] ' .. os.date('%H:%M:%S - ', os.time()) .. text) + +end + +local discordMaxEmbedFields = 25 +cats.NotifyDiscord = octolib.func.debounceStart(function(self) + if table.IsEmpty(self.currentTickets) then return end + + local admins = octolib.array.toKeys(getAdmins(), 'Не занят') + local adminDescriptions = table.Copy(admins) + + -- remove busy admins + for _, ticket in pairs(self.currentTickets) do + if IsValid(ticket.admin) then + admins[ticket.admin] = nil + adminDescriptions[ticket.admin] = 'Занят жалобой ' .. ticket.userID + end + end + + -- remove afk admins + for admin in pairs(admins) do + if admin:GetAFKTime() > CFG.afkAdminNotActive then + admins[admin] = nil + adminDescriptions[admin] = 'AFK' + end + + if not self.config.actualAdminRanks[admin:GetUserGroup()] then + admins[admin] = nil + adminDescriptions[admin] = 'Ранг не обязывает' + end + end + + -- count active tickets + local notClaimed = {} + for userID, ticket in pairs(self.currentTickets) do + if not IsValid(ticket.admin) then + notClaimed[#notClaimed + 1] = userID + end + end + if not self.config.iDiffers(notClaimed, self.lastNotifiedTickets) then return end + self.lastNotifiedTickets = notClaimed + if not notClaimed[1] then return end + + for _, userID in ipairs(notClaimed) do + local user = player.GetBySteamID(userID) + if IsValid(user) then + self.config.notify(user, self.lang.ticket_noAdmins) + end + end + + local webhook = CFG.webhooks.cats + if not webhook then return end + + local fields = octolib.table.mapSequential(notClaimed, function(sid) + return { + name = 'https://octothorp.team/cats/' .. sid, + value = self.currentTickets[sid].chatLog[1][2], + } + end) + if #fields+1 > discordMaxEmbedFields then + local missing = #fields - (discordMaxEmbedFields-3) + fields = octolib.array.page(fields, discordMaxEmbedFields - 3) + fields[#fields + 1] = { + value = ('+ Еще %d %s'):format(missing, octolib.string.formatCount(missing, 'жалоба', 'жалобы', 'жалоб')), + } + end + + fields[#fields + 1] = { name = '​', value = '​' } -- divider + fields[#fields + 1] = { + name = 'Админы на сервере', + value = next(adminDescriptions) == nil and 'Никого' or table.concat(octolib.table.mapSequential(adminDescriptions, function(status, admin) + return admin:SteamName() .. ' – ' .. status + end), '\n'), + } + + local embed = { + -- author = { + -- name = GetHostName(), + -- url = 'https://octothorp.team/join-' .. CFG.serverID, + -- }, + title = GetHostName(), + description = ('**%d** %s, но активных админов нет'):format(#notClaimed, octolib.string.formatCount(#notClaimed, 'открытая жалоба', 'открытые жалобы', 'открытых жалоб')), + fields = fields, + thumbnail = { url = ('https://img.icons8.com/color/184/%d.png'):format(#notClaimed) }, + } + + if #notClaimed > 8 then + embed.color = 0xA52019 + if #notClaimed > 10 then + embed.footer = { + text = 'Это какой-то апокалипсис! Пожалуйста, срочно зайдите!', + icon_url = 'https://img.icons8.com/color/48/skull.png', + } + end + elseif #notClaimed > 4 then + embed.color = 0xFF4F00 + elseif #notClaimed > 2 then + embed.color = 0xE5BE01 + end + + octoservices:post('/discord/webhook/' .. webhook, { + content = #notClaimed > 5 and CFG.adminMention or nil, + embeds = { embed }, + }) + + if not table.IsEmpty(admins) then + RunConsoleCommand('sg', 'a', ('Пожалуйста, разберите %d %s или позовите других админов на помощь'):format(#notClaimed, octolib.string.formatCount(#notClaimed, 'жалобу', 'жалобы', 'жалоб'))) + end + +end, cats.config.notificationDelay) + +timer.Create('cats.oldTicketsChecker', 60, 0, function() + local currentTime = os.time() + local callAdmins = false + for userID, ticket in pairs(cats.currentTickets) do + if currentTime - ticket.createdTime > cats.config.oldTicketTrigger and not IsValid(ticket.admin) then + callAdmins = true + break + end + end + if callAdmins then cats:NotifyDiscord() end +end) + +-- process & broadcast new ticket message +function cats:DispatchMessage(sender, steamID, msg, sendToAPI) + + local ply = player.GetBySteamID(sender) + if msg == '' then + ply:Notify('warning', 'Укажи текст админ-запроса') + return + end + local admins = getAdmins(steamID) + + if self.currentTickets[steamID] then + -- ticket exists, append chat message + table.insert(self.currentTickets[steamID].chatLog, {sender, msg, sender ~= steamID}) + + if sendToAPI then + self.api:post('/msg/' .. steamID, { + owner = sender, + text = msg, + }):Then(function(res) + local msg = res.data + if msg and msg.time then + ignoreMsgsTime[msg.time] = true + end + end) + end + else + -- create new ticket + self.currentTickets[steamID] = { + createdTime = os.time(), + createdGameTime = CurTime(), + chatLog = {{sender, msg}}, + userID = steamID + } + + if sendToAPI then + self.api:post('/chat', { + owner = steamID, + text = msg, + }):Then(function(res) + local ticket = res.data + if ticket and ticket.created then + ignoreMsgsTime[ticket.created] = true + end + end) + end + + hook.Run('cats.created', sender) + end + + hook.Run('cats.message', sender, steamID, msg) + + -- do networking + netstream.Start(admins, 'cats.dispatchMessage', steamID, sender, msg) + +end +netstream.Hook('cats.dispatchMessage', function(ply, steamID, msg) + + local user = player.GetBySteamID(steamID) + + -- check if user exists + if not IsValid(user) then + cats.config.notify(ply, 'warning', cats.lang.error_playerNotFound) + return + end + + -- check access + if not cats.config.playerCanSeeTicket(ply, steamID) then + cats.config.notify(ply, 'warning', cats.lang.error_noAccess) + return + end + + -- dispatch + cats:DispatchMessage(ply:SteamID(), steamID, msg, true) + +end) + +netstream.Hook('cats.closeTicket', function(ply, steamID) + + local ticket = cats.currentTickets[steamID] + + -- ticket doesn't exist + if not ticket then + cats.config.notify(ply, 'warning', cats.lang.error_ticketNotFound) + return + end + + -- ticket already ended + if ticket.ended then + cats.config.notify(ply, 'warning', cats.lang.error_ticketEnded) + return + end + + if ticket.adminID == ply:SteamID() then + -- admin closed the ticket + local user = player.GetBySteamID(steamID) + if IsValid(user) then + cats.config.notify(user, cats.lang.ticketClosedBy:format(ticket.admin:Name())) + end + cats.config.notify(ply, cats.lang.ticketClosed) + elseif ticket.userID == ply:SteamID() and not IsValid(ticket.admin) then + -- user cancelled the ticket + else + -- we don't have access to this + cats.config.notify(ply, 'warning', cats.lang.error_noAccess) + return + end + + cats:CloseTicket(steamID) + hook.Run('cats.closed', ply:SteamID(), steamID) + +end) + +-- close ticket +function cats:CloseTicket(steamID) + + -- notify clients + netstream.Start(getAdmins(steamID), 'cats.closeTicket', steamID) + + self.currentTickets[steamID] = nil + self.api:delete('/chat/' .. steamID) + +end + +-- claim/unclaim ticket +function cats:ClaimTicket(steamID, ply, doClaim) + + local ticket = self.currentTickets[steamID] + + -- ticket doesn't exist + if not ticket then + self:Log('Trying to claim inexistant ticket for ' .. steamID) + return + end + + -- check access + if IsValid(ticket.admin) and ticket.adminID ~= ply:SteamID() then + cats.config.notify(ply, 'warning', cats.lang.error_noAccess) + return + end + + if doClaim then + -- claim ticket + ticket.admin = ply + ticket.adminID = ply:SteamID() + ticket.claimTime = os.time() + else + -- unclaim ticket + ticket.admin = nil + ticket.adminID = nil + end + + hook.Run('cats.claim', ply, steamID, doClaim) + netstream.Start(getAdmins(steamID), 'cats.claimTicket', steamID, ply, doClaim) + +end +netstream.Hook('cats.claimTicket', function(ply, steamID, doClaim) + + local ticket = cats.currentTickets[steamID] + + -- check access + if not cats.config.playerCanSeeTicket(ply, steamID) or ply:SteamID() == steamID then + cats.config.notify(ply, 'warning', cats.lang.error_noAccess) + return + end + + -- ticket doesn't exist + if not ticket then + cats.config.notify(ply, 'warning', cats.lang.error_ticketNotFound) + return + end + + local user = player.GetBySteamID(steamID) + if doClaim then + -- ticket already claimed + if IsValid(ticket.admin) then + cats.config.notify(ply, 'warning', cats.lang.error_ticketAlreadyClaimed) + return + end + + -- notify about claim + cats.config.notify(ply, cats.lang.ticketClaimed) + cats.config.notify(user, cats.lang.ticketClaimedBy:format(ply:Name())) + else + -- ticket not claimed yet + if not IsValid(ticket.admin) then + cats.config.notify(ply, 'warning', cats.lang.error_ticketNotClaimed) + return + end + + -- notify about unclaim + cats.config.notify(ply, cats.lang.ticketUnclaimed) + cats.config.notify(user, cats.lang.ticketUnclaimedBy:format(ply:Name())) + end + + -- do the thing + cats:ClaimTicket(steamID, ply, doClaim) + +end) + +--------------------------------------------------- +-- GAMEMODE HOOKS +--------------------------------------------------- + +-- initiate tickets +hook.Add('PlayerSay', 'cats', function(ply, text) + + local shouldTrigger, msg = cats.config.triggerText(ply, text) + if shouldTrigger then + cats:DispatchMessage(ply:SteamID(), ply:SteamID(), msg, true) + return '' + end + +end) + +-- deal with disconnected players +hook.Add('PlayerDisconnected', 'cats', function(ply) + + for steamID, ticket in pairs(cats.currentTickets) do + if steamID == ply:SteamID() then + -- notify admin + if IsValid(ticket.admin) then + cats.config.notify(ticket.admin, 'warning', cats.lang.ticketUserLeft) + end + + -- delete ticket + cats:CloseTicket(steamID) + elseif ticket.adminID == ply:SteamID() then + -- unclaim ticket + cats:ClaimTicket(steamID, ply, false) + end + end + +end) + +hook.Add('PlayerFinishedLoading', 'cats', function(ply) + + if cats.config.playerCanSeeTicket(ply, '') then + netstream.Start(ply, 'cats.syncTickets', cats.currentTickets) + end + +end) + +local lastMsgTime = 0 +timer.Create('cats.checkUpdates', 5, 0, function() + + cats.api:get('/util/updates/' .. lastMsgTime):Then(function(res) + lastMsgTime = res.data[1] + for steamID, msgs in pairs(res.data[2]) do + for i, msg in ipairs(msgs) do + if not ignoreMsgsTime[msg.time] then + cats:DispatchMessage(msg.owner, steamID, msg.text) + else + ignoreMsgsTime[msg.time] = nil + end + end + end + end) + +end) diff --git a/garrysmod/addons/admin-tickets/lua/cats/shared.lua b/garrysmod/addons/admin-tickets/lua/cats/shared.lua new file mode 100644 index 0000000..7a24852 --- /dev/null +++ b/garrysmod/addons/admin-tickets/lua/cats/shared.lua @@ -0,0 +1,181 @@ +-- proudly coded by chelog + +if SERVER then function ScrW() return 1920 end function ScrH() return 1080 end end cats = cats or {} cats.config = {} +-- ^ +-- | please do not touch these + +------------------------------------------------------ +-- BASIC CONFIG +------------------------------------------------------ + +-- positions +cats.config.spawnSize = { 450, 220 } +cats.config.spawnPosAdmin = { ScrW() - 500, 50 } +cats.config.spawnPosUser = { ScrW() - 500, ScrH() - 250 } + +-- appearance +cats.config.punchCardMode = 'dots' -- 'line', 'dots' or 'columns' +cats.config.punchCardStart = 5 + +-- rating +cats.config.defaultRating = 3 +cats.config.ratingTimeout = 60 + +-- admin notify +cats.config.oldTicketTrigger = 30 * 60 +cats.config.notificationDelay = 15 * 60 + +-- sound +cats.config.newTicketSound = 'buttons/bell1.wav' + +-- language +cats.lang = { + openTickets = L.openTickets, + myTicket = L.myTicket, + userDisconnected = L.userDisconnected, + claimedBy = L.claimedBy, + sendMessage = L.sendMessage, + typeYourMessage = L.typeYourMessage, + actions = L.actions, + action_claim = L.action_claim, + action_unclaim = L.action_unclaim, + action_spectate = L.action_spectate, + action_goto = L.action_goto, + action_bring = L.action_bring, + action_return = L.action_return, + action_returnself = L.action_returnself, + action_copySteamID = L.action_copySteamID, + action_callon = L.action_callon, + action_calloff = L.action_calloff, + action_close = L.action_close, + error_wait = L.error_wait, + error_noAccess = L.error_noAccess, + error_playerNotFound = L.error_playerNotFound, + error_ticketNotEnded = L.error_ticketNotEnded, + error_ticketNotFound = L.error_ticketNotFound, + error_ticketEnded = L.error_ticketEnded, + error_ticketNotClaimed = L.error_ticketNotClaimed, + error_ticketAlreadyClaimed = L.error_ticketAlreadyClaimed, + error_needToRate = L.error_needToRate, + error_cantCancelHasAdmin = L.error_cantCancelHasAdmin, + ticketClaimed = L.ticketClaimed, + ticketUnclaimed = L.ticketUnclaimed, + ticketClaimedBy = L.ticketClaimedBy, + ticketUnclaimedBy = L.ticketUnclaimedBy, + ticketClosed = L.ticketClosed, + ticketClosedBy = L.ticketClosedBy, + ticketRatedForAdmin = L.ticketRatedForAdmin, + ticketRatedForUser = L.ticketRatedForUser, + ticketUserLeft = L.ticketUserLeft, + rateAdmin = L.rateAdmin, + ok = L.finish, + cancel = L.cancel, + ticket_noAdmins = L.ticket_noAdmins, + dow = L.dow, +} + +cats.config.actualAdminRanks = octolib.array.toKeys { + 'trainee', + 'admin', + 'tadmin', + 'sadmin', +} + +------------------------------------------------------ +-- ADVANCED SETTINGS (do not edit unless you're a dev) +------------------------------------------------------ + +cats.config.getPlayerName = function(ply) + if not IsValid(ply) then return L.player_left end + return ply:Name() .. " (" .. ply:SteamName() .. ")" +end +cats.config.playerCanSeeTicket = function(ply, ticketSteamID) + return ply:query("DBG: Видеть админ-запросы") or ply:SteamID() == ticketSteamID +end +cats.config.triggerText = function(ply, text) + if cats.config.playerCanSeeTicket(ply, "") then return false end + + text = text:Trim() + if text:sub(1,1) == '@' then + return true, text:sub(2):Trim() + elseif text:sub(1,3) == '///' then + return true, text:sub(4):Trim() + end + + return false +end +cats.config.notify = function(ply, type, ...) + if IsValid(ply) then + ply:Notify(type, ...) + else + octolib.notify.sendAll(type, ...) + end +end +cats.config.iDiffers = function(t1, t2) + local delta = {} + for _, v in ipairs(t1) do + delta[v] = true + end + for _, v in ipairs(t2) do + if not delta[v] then return true end + delta[v] = nil + end + return not table.IsEmpty(delta) +end + +-- NOTE: these are clientside +cats.config.commands = { + { -- spectate + text = cats.lang.action_spectate, + icon = 'eye', + click = function(ply) + RunConsoleCommand('FSpectate', ply:SteamID()) + end + }, + { -- copy steamID + text = L.spawn, + icon = 'asterisk_yellow', + click = function(ply) + RunConsoleCommand("say", "/spawn " .. ply:Name()) + end + }, + { -- bring + text = cats.lang.action_bring, + icon = 'arrow_left', + click = function(ply) + RunConsoleCommand('sgs', 'bring', ply:SteamID()) + end + }, + { -- return + text = cats.lang.action_return, + icon = 'arrow_redo', + click = function(ply) + RunConsoleCommand('sgs', 'return', ply:SteamID()) + end + }, + { -- goto + text = cats.lang.action_goto, + icon = 'arrow_right', + click = function(ply) + RunConsoleCommand('sgs', 'goto', ply:SteamID()) + end + }, + { -- return self + text = cats.lang.action_returnself, + icon = 'arrow_undo', + click = function(ply) + RunConsoleCommand('sgs', 'return', LocalPlayer():SteamID()) + end + }, + { -- copy steamID + text = cats.lang.action_copySteamID, + icon = 'page_copy', + click = function(ply) + SetClipboardText( ply:SteamID() ) + end + }, +} + +-- | also please do not touch these +-- V +if SERVER then ScrW = nil ScrH = nil end diff --git a/garrysmod/addons/core-char/lua/autorun/char.lua b/garrysmod/addons/core-char/lua/autorun/char.lua new file mode 100644 index 0000000..a4c5b2a --- /dev/null +++ b/garrysmod/addons/core-char/lua/autorun/char.lua @@ -0,0 +1,5 @@ +octolib.module('ghosts') +octolib.module('karma') +octolib.module('char') +octolib.shared('config/masks') +octolib.module('masks') diff --git a/garrysmod/addons/core-char/lua/char/server.lua b/garrysmod/addons/core-char/lua/char/server.lua new file mode 100644 index 0000000..bc95c2a --- /dev/null +++ b/garrysmod/addons/core-char/lua/char/server.lua @@ -0,0 +1,448 @@ +local allowedModels = { + ['models/humans/octo/female_01.mdl'] = {20}, + ['models/humans/octo/female_02.mdl'] = {6,21}, + ['models/humans/octo/female_03.mdl'] = {6,21}, + ['models/humans/octo/female_04.mdl'] = {6,21}, + ['models/humans/octo/female_06.mdl'] = {6,21}, + ['models/humans/octo/female_07.mdl'] = {6,21}, + ['models/humans/octo/male_01_01.mdl'] = true, + ['models/humans/octo/male_01_02.mdl'] = true, + ['models/humans/octo/male_01_03.mdl'] = true, + ['models/humans/octo/male_02_01.mdl'] = true, + ['models/humans/octo/male_02_02.mdl'] = true, + ['models/humans/octo/male_02_03.mdl'] = true, + ['models/humans/octo/male_03_01.mdl'] = true, + ['models/humans/octo/male_03_02.mdl'] = true, + ['models/humans/octo/male_03_03.mdl'] = true, + ['models/humans/octo/male_03_04.mdl'] = true, + ['models/humans/octo/male_03_05.mdl'] = true, + ['models/humans/octo/male_03_06.mdl'] = true, + ['models/humans/octo/male_03_07.mdl'] = true, + ['models/humans/octo/male_04_01.mdl'] = true, + ['models/humans/octo/male_04_02.mdl'] = true, + ['models/humans/octo/male_04_03.mdl'] = true, + ['models/humans/octo/male_04_04.mdl'] = true, + ['models/humans/octo/male_05_01.mdl'] = true, + ['models/humans/octo/male_05_02.mdl'] = true, + ['models/humans/octo/male_05_03.mdl'] = true, + ['models/humans/octo/male_05_04.mdl'] = true, + ['models/humans/octo/male_05_05.mdl'] = true, + ['models/humans/octo/male_06_01.mdl'] = true, + ['models/humans/octo/male_06_02.mdl'] = true, + ['models/humans/octo/male_06_03.mdl'] = true, + ['models/humans/octo/male_06_04.mdl'] = true, + ['models/humans/octo/male_06_05.mdl'] = true, + ['models/humans/octo/male_07_01.mdl'] = true, + ['models/humans/octo/male_07_02.mdl'] = true, + ['models/humans/octo/male_07_03.mdl'] = true, + ['models/humans/octo/male_07_04.mdl'] = true, + ['models/humans/octo/male_07_05.mdl'] = true, + ['models/humans/octo/male_07_06.mdl'] = true, + ['models/humans/octo/male_08_01.mdl'] = true, + ['models/humans/octo/male_08_02.mdl'] = true, + ['models/humans/octo/male_08_03.mdl'] = true, + ['models/humans/octo/male_08_04.mdl'] = true, + ['models/humans/octo/male_09_01.mdl'] = true, + ['models/humans/octo/male_09_02.mdl'] = true, + ['models/humans/octo/male_09_03.mdl'] = true, + ['models/humans/octo/male_09_04.mdl'] = true, +} + +local names, models = L.names, table.GetKeys(allowedModels) + +local function playerCanChangeTeam(ply, tID, force) + + if not force then return false, L.can_change_job end + +end +hook.Add('playerCanChangeTeam', 'dbg-char', playerCanChangeTeam) + +local function canChangeJob() + + return false, L.can_change_name_job + +end +hook.Add('canChangeJob', 'dbg-char', canChangeJob) + +local function playerCanChangeName() + + return false, L.can_change_name + +end +hook.Add('CanChangeRPName', 'dbg-char', playerCanChangeName) + +local storedHealth, storedHunger = {}, {} + +local function getName(mdl) + local gender = octolib.models.isMale(mdl) and names.male or names.female + local name, surname = gender[math.random(#gender)], names.surnames[math.random(#names.surnames)] + return ('%s %s'):format(name, surname) +end + +local function getSkinCount(mdl) + return util.GetModelInfo(mdl).SkinCount - 1 +end + +local function getRandomModel(mdls) + local mdl = mdls[math.random(#mdls)] + local skin = math.random(0, getSkinCount(mdl)) + return mdl, skin +end + +local function spawnPlayer(ply) + timer.Remove('dbg-char.disconnect' .. ply:SteamID()) + + if not ply.passedTest then return end + + local he = not ply.died and storedHealth[ply:SteamID()] or 100 + local hu = not ply.died and storedHunger[ply:SteamID()] or 100 + timer.Simple(2, function() + if not IsValid(ply) then return end + + local name = ply:GetInfo('dbg_name') or L.name_and_surname + local model = ply:GetInfo('dbg_model') or models[math.random(#models)] + local skin = tonumber(ply:GetInfo('dbg_skin') or '0') or math.random(0, getSkinCount(model)) + local jobCmd = ply:GetInfo('dbg_job') or 'citizen' + ply:GetClientVar({'dbg_desc'}, function(vars) + + local desc = utf8.sub(vars.dbg_desc or '', 1, 350) + + desc = string.Trim(octolib.string.stripNonWord(desc, ',:%.0-9-;%(%)%/%"%\'a-zA-Z')) + + local ok = allowedModels[model] + if not ok or not util.IsValidModel(model) then + model, skin = getRandomModel(models) + end + + if skin < 0 or skin > getSkinCount(model) or (istable(ok) and table.HasValue(ok, skin)) then + skin = 0 + end + + local job, jobID = DarkRP.getJobByCommand(jobCmd) + if not job or job.noPreference or (job.customCheck and (not job.customCheck(ply))) then + job, jobID = DarkRP.getJobByCommand('citizen') + ply:ConCommand('dbg_job ' .. job.command) + end + + name = string.Trim(octolib.string.camel(octolib.string.stripNonWord(name))) + if name == L.name_and_surname or name == '' then + name = getName(model) + ply:ConCommand('dbg_name "' .. name .. '"') + end + name = utf8.sub(name, 1, 35) + if not string.find(name, ' ') then + ply:Notify('warning', 'Твое имя указано без фамилии. Пожалуйста, укажи фамилию и смени персонажа. Твое имя сброшено на стандартное') + name = getName(model) + end + + if ply:Name() ~= name then + ply:SetName(name) + end + + if ply:Team() ~= jobID then + ply:changeTeam(jobID, true, true) + end + + ply:SetCanZoom(false) + ply:SetModel(model) + ply:SetSkin(skin) + for i, mat in ipairs(ply:GetMaterials()) do + if string.match(mat, '.+/sheet_%d+') then + ply:SetSubMaterial(i-1, nil) + end + end + + if desc == '' then desc = nil end + ply:SetNetVar('dbgDesc', desc) + ply:SetNetVar('dbgLook', { + name = 'playerName', + nameRender = true, + desc = 'playerDesc', + descRender = true, + checkLoader = 'playerLoader', + time = 0.75, + bone = 'ValveBiped.Bip01_Head1', + posAbs = Vector(0, 0, 10), + lookOff = Vector(0, -100, 0), + }) + ply:SetSalary(job.salary) + + timer.Simple(0, function() + if IsValid(ply) then ply:SetHealth(he) end + end) + ply:SetLocalVar('Energy', hu) + + ply.SaveCharState = ply.SaveCharState or octolib.func.debounceEnd(function(self) + if not IsValid(self) then return end + storedHealth[self:SteamID()] = math.floor(self:Health()) + storedHunger[self:SteamID()] = math.floor(self:GetNetVar('Energy')) + end, 3) + + if not ply.inTown then + local txt = name .. ' (' .. tostring(ply:GetInfo('dbg_name')) .. ')' .. L.arrive_in_town + print('[LOG] ' .. txt) + + ply.inTown = true + hook.Run('dbg-char.firstSpawn', ply) + end + + hook.Run('dbg-char.spawn', ply) + end) + end) + +end +hook.Add('PlayerSpawn', 'dbg-char', spawnPlayer) +hook.Add('PlayerFinishedLoading', 'dbg-char', spawnPlayer) + +gameevent.Listen('player_disconnect') +hook.Add('player_disconnect', 'dbg-char', function(data) + + local sID + if octolib.string.isSteamID(data.networkid) then + sID = data.networkid + elseif (data.networkid:find('^7656119%d+$')) then + sID = util.SteamIDFrom64(data.networkid) + else return end + + timer.Create('dbg-char.disconnect' .. sID, 1200, 1, function() + storedHealth[sID], storedHunger[sID] = nil + end) + +end) + +local spawnsConfig = { + rp_eastcoast_v4c = { + Vector(-3984.572754, -264.949554, -31.968750), + Vector(-442.676147, 1273.377563, 0.031250), + Vector(-945.807617, 1902.602173, 0.031250), + Vector(-2267.563232, -1891.397095, 32.031250), + Vector(-516.171448, -1139.002197, -31.961905), + Vector(1517.340332, 1193.663452, -31.968750), + Vector(1099.295898, -3299.014893, -31.968750), + Vector(4512.401855, -355.006287, -31.968748), + Vector(3605.282959, 1370.047852, 160.031250), + Vector(1834.274292, -3965.351807, -39.968750), + }, + rp_truenorth_v1a = { + Vector(-11076, 15175, -200), + Vector(-6519, 7204, 136), + Vector(5060, 9926, 136), + Vector(10540, 12466, 8), + Vector(8496, 10592, 8), + Vector(15810, -990, 4), + Vector(13648, -12944, 8), + Vector(-13805, -12976, 16), + Vector(-16, 1666, 8), + Vector(5997, 2532, 0), + Vector(3887, 1592, 8), + }, + rp_evocity_dbg_220222 = { + -- Vector(-420, 2768, 72), -- removed on winter version + Vector(1982, 6329, 76), + Vector(3605, 7260, 68), + -- Vector(-648, 8860, 67), -- removed on winter version + -- Vector(699, 7251, 69), -- removed on winter version + Vector(6772, -1981, 116), + Vector(7534, 4679, 23), + Vector(12172, 937, 127), + Vector(10455, 13715, 58), + Vector(-2954, 12857, 185), + Vector(-13899, 14779, 250), + Vector(-9688, 11785, 185), + Vector(-7055, 10941, 185), + Vector(-10817, 9917, 72), + Vector(-2606, 581, 64), + Vector(-4028, -4148, 198), + -- Vector(-8236, -5011, 73), -- removed on winter version + Vector(-10744, -8123, 73), + Vector(-10561, -11904, 72), + Vector(-11363, -14176, 74), + Vector(-9076, -13001, 72), + Vector(3564, 7713, 131), + Vector(12628, 11807, 73), + Vector(-4198, 6333, 124), + Vector(-3645, 10879, 192), + Vector(-858, 9491, 223), + Vector(3384, 9505, 101), + Vector(-5198, -1296, 101), + }, + rp_riverden_dbg_220313 = { + Vector(-7696, 13552, 0), + Vector(-1322, 357, -237), + Vector(-10671, -12390, -242), + Vector(7298, -120, 786), + Vector(14602, 9456, 818), + Vector(3784, 13881, 9), + Vector(-2092, 4441, -229), + Vector(-14909, 11048, 0), + Vector(9301, 5161, 771), + Vector(8073, 11812, 313), + Vector(5204, 8973, -260), + Vector(3262, 4907, -247), + Vector(-2029, 11198, -36), + Vector(7249, -10760, 782), + Vector(4258, -14276, 727), + Vector(-2141, -12907, 133), + Vector(-5223, -8888, -264), + Vector(1570, -8538, -185), + Vector(-8946, -4543, -239), + Vector(-14235, 8216, -253), + Vector(-5782, 14650, 0), + Vector(-11574, 573, -264), + Vector(-9136, 11920, 0), + Vector(-13488, 15016, 0), + }, +} +local respPos = spawnsConfig[game.GetMap()] + +util.AddNetworkString 'dbg-char.respawnRequest' +util.AddNetworkString 'dbg-char.respawnCancel' + +local function checkPlayer(ply) + + local steamID = ply:SteamID() + return function() + if not IsValid(ply) or not ply.dbgChar_respawnPos then + timer.Remove('dbg-char.respawn' .. steamID) + return + end + + if ply:GetPos():DistToSqr(ply.dbgChar_respawnPos) < 900 then + for _, ent in ipairs(ents.FindInSphere(ply:EyePos(), 500)) do + if IsValid(ent) and ent:IsPlayer() and ent ~= ply and not ent:IsGhost() and not ent:GetNetVar('Invisible') then + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ent:EyePos(), + filter = {ply, ent}, + }) + if not tr.Hit then + ply:Notify('warning', L.somebody_near) + return + end + end + end + + if ply:Alive() then ply:KillSilent() end + ply:SetNetVar( '_SpawnTime', CurTime() ) + ply.dbgChar_respawnPos = nil + end + end + +end + +local function getRespawnPos(ply, minDist, maxDist) + + if minDist then + local okPos = {} + for _, pos in ipairs(respPos) do + local dist = ply:GetPos():DistToSqr(pos) + if dist > minDist * minDist and not maxDist or dist < maxDist * maxDist then + table.insert(okPos, pos) + end + end + + return okPos[math.random(#okPos)] + else + return respPos[math.random(#respPos)] + end + +end + +local function enableCharRespawn(ply, pos) + + ply.dbgChar_respawnPos = pos + + ply:AddMarker({ + id = 'change-char', + txt = L.discreet_place, + pos = pos, + col = Color(255,92,38), + icon = 'octoteam/icons-16/user.png', + }) + timer.Create('dbg-char.respawn' .. ply:SteamID(), 3, 0, checkPlayer(ply)) + + ply:Notify(L.go_discreet_place) + +end + +local function respawnRequest(ply) + + if ply.dbgChar_nextRequest and CurTime() < ply.dbgChar_nextRequest then + ply:Notify('warning', L.wait_boy) + return + end + ply.dbgChar_nextRequest = CurTime() + 15 + + if ply.dbgChar_respawnPos then + ply:Notify('warning', L.you_already_change) + return + end + + if not ply:Alive() or ply:IsGhost() or ply:GetNetVar('wanted') or ply:isArrested() then + ply:Notify('warning', L.cant_change_now) + return + end + + local _, info_job = DarkRP.getJobByCommand(ply:GetInfo('dbg_job') or 'citizen') + local job = RPExtraTeams[info_job] + local limit = job.max == 0 or team.NumPlayers(job.team) < math.ceil(player.GetCount() * job.max) + + if not limit then + ply:Notify('warning', L.job_limit) + return + end + + netstream.Start(ply, 'dbg-char.updateState', true) + local pos = getRespawnPos(ply, 1000, 8000) + if pos then + enableCharRespawn(ply, pos) + else + enableCharRespawn(ply, getRespawnPos(ply)) + end + +end + +local function removeTimer(ply) + + timer.Remove('dbg-char.respawn' .. ply:SteamID()) + ply:ClearMarkers('change-char') + ply.dbgChar_respawnPos = nil + netstream.Start(ply, 'dbg-char.updateState', false) + +end +hook.Add('PlayerDeath', 'dbg-char', removeTimer) +hook.Add('PlayerSilentDeath', 'dbg-char', removeTimer) +hook.Add('PlayerDisconnected', 'dbg-char', removeTimer) + +local function respawnCancel(ply) + + if not ply.dbgChar_respawnPos then + ply:Notify('warning', L.you_not_already_change) + return + end + + removeTimer(ply) + ply:Notify('warning', L.character_change_cancel) + +end +netstream.Hook('dbg-char.respawn', function(ply, state) + if state then + respawnRequest(ply) + else respawnCancel(ply) end +end) + +timer.Create('dbg-char.updateState', 4, 0, function() + octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + if not IsValid(ply) or not ply.SaveCharState then return end + if math.floor(ply:Health()) ~= (storedHealth[ply:SteamID()] or 100) then + ply:SaveCharState() + end + end) +end) + +local meta = FindMetaTable 'Player' +function meta:SetName(name) + if not name or string.len(name) < 2 then return end + hook.Run('onPlayerChangedName', self, self:Name(), name) + self:SetNetVar('rpname', name) +end diff --git a/garrysmod/addons/core-char/lua/char/shared.lua b/garrysmod/addons/core-char/lua/char/shared.lua new file mode 100644 index 0000000..8d1e493 --- /dev/null +++ b/garrysmod/addons/core-char/lua/char/shared.lua @@ -0,0 +1,7 @@ +local pmeta = FindMetaTable 'Player' +pmeta.SteamName = pmeta.SteamName or pmeta.Name +function pmeta:Name() + return self:GetNetVar('rpname') or self:SteamName() +end +pmeta.GetName = pmeta.Name +pmeta.Nick = pmeta.Name diff --git a/garrysmod/addons/core-char/lua/cmenu/categories/clothes.lua b/garrysmod/addons/core-char/lua/cmenu/categories/clothes.lua new file mode 100644 index 0000000..46b681f --- /dev/null +++ b/garrysmod/addons/core-char/lua/cmenu/categories/clothes.lua @@ -0,0 +1 @@ +octogui.cmenu.registerCategory('clothes') diff --git a/garrysmod/addons/core-char/lua/cmenu/items/clothes.lua b/garrysmod/addons/core-char/lua/cmenu/items/clothes.lua new file mode 100644 index 0000000..b8ba855 --- /dev/null +++ b/garrysmod/addons/core-char/lua/cmenu/items/clothes.lua @@ -0,0 +1,45 @@ +octogui.cmenu.registerItem('clothes', 'gasmask', { + text = function(ply) + local mask = ply:GetNetVar('hMask') + return ('%s противогаз'):format(mask and mask[1] == 'gasmask' and 'Снять' or 'Надеть') + end, + icon = octolib.icons.silk16('arrow_rotate_clockwise'), + check = function(ply) + return ply:GetActiveRank('dpd') == 'swat' or ply:GetActiveRank('wcso') == 'seb' + end, + say = '/gasmask', +}) + +octogui.cmenu.registerItem('clothes', 'medmask', { + text = function(ply) + local mask = ply:GetNetVar('hMask') + return ('%s мед. маску'):format(mask and mask[1] == 'medical_mask' and 'Снять' or 'Надеть') + end, + icon = octolib.icons.silk16('arrow_rotate_clockwise'), + check = function(ply) + return ply:isMedic() + end, + say = '/medmask', +}) + +octogui.cmenu.registerItem('clothes', 'takeoff', { + text = 'Снять', + icon = octolib.icons.silk16('attach'), + check = function(ply) + if ply:isArrested() then return false end + if ply:GetNetVar('hMask') ~= nil and ply:CanUnmask() then return true end + if ply:GetNetVar('customClothes') ~= nil then return true end + return false + end, + options = { + { + text = 'Маску', + cmd = {'dbg_unmask'}, + check = function(ply) return ply:GetNetVar('hMask') ~= nil and ply:CanUnmask() end, + }, { + text = 'Одежду', + cmd = {'dbg_clothesoff'}, + check = function(ply) return ply:GetNetVar('customClothes') ~= nil end, + }, + } +}) diff --git a/garrysmod/addons/core-char/lua/cmenu/properties/char.lua b/garrysmod/addons/core-char/lua/cmenu/properties/char.lua new file mode 100644 index 0000000..0922bf3 --- /dev/null +++ b/garrysmod/addons/core-char/lua/cmenu/properties/char.lua @@ -0,0 +1,14 @@ +properties.Add('demote', { + MenuLabel = L.c_language_demote, + Order = 3, + MenuIcon = octolib.icons.silk16('user_delete'), + Filter = function(self, ent, ply) + return IsValid(ent) and ent:IsPlayer() + and not ent:GetNetVar('Ghost') and not ply:GetNetVar('Ghost') + end, + Action = function(self, ent) + Derma_StringRequest(L.c_language_demote, L.c_language_demote_description, nil, function(a) + octochat.say('/demote', ent:UserID(), a) + end) + end +}) diff --git a/garrysmod/addons/core-char/lua/ghosts/client.lua b/garrysmod/addons/core-char/lua/ghosts/client.lua new file mode 100644 index 0000000..cbd9967 --- /dev/null +++ b/garrysmod/addons/core-char/lua/ghosts/client.lua @@ -0,0 +1,205 @@ +surface.CreateFont('DeathScreen', { + size = 64, + weight = 800, + antialias = true, + shadow = false, + font = 'Default'}) + +hook.Add('HUDShouldDraw', 'FPDeath', function(name) + if name ~= 'CHudCrosshair' then return end + + local ply = LocalPlayer() + local body = ply:GetNetVar('DeathRagdoll') + if not body or not IsValid(body) then body = ply:GetParent() end + if not body or not IsValid(body) or body:IsVehicle() then return end + if (not ply:Alive() and CurTime() < ply:GetNetVar('_GhostTime', 0)) or ply:GetNetVar('Tased') or ply:GetNetVar('Ragdolled') then + return false + end +end) + +local function getRagdoll(ply) + local body = ply:GetNetVar('DeathRagdoll') + if not body or not IsValid(body) then body = ply:GetParent() end + if not body or not IsValid(body) or body:IsVehicle() then return end + return body +end + +-- add first person death +hook.Add('CalcView', 'FPDeath', function(ply) + local body = getRagdoll(ply) + if not body then return end + + local head = body:LookupBone('ValveBiped.Bip01_Head1') + if (not ply:Alive() and CurTime() < ply:GetNetVar('_GhostTime', 0)) or ply:GetNetVar('Tased') or ply:GetNetVar('Ragdolled') then + -- make head disappear + body:ManipulateBoneScale(head, Vector(0, 0, 0)) + + local eyes = body:GetAttachment(body:LookupAttachment('eyes')) + local view = { + origin = eyes.Pos - eyes.Ang:Forward() * 6, + angles = eyes.Ang, + fov = 90 + } + + return view + else + -- restore head after spawn + local head = body:LookupBone('ValveBiped.Bip01_Head1') + body:ManipulateBoneScale(head, Vector(1, 1, 1)) + end +end, -10) + +local curState, lastState = 0, 0 +hook.Add('PostDrawHUD', 'PostEffectsHealth', function() + if isHoldingCamera then return end + + local ply, ct, ft = LocalPlayer(), CurTime(), FrameTime() + local imDead, spTime, ghTime = ply:GetNetVar('Ghost'), ply:GetNetVar('_SpawnTime', 0), ply:GetNetVar('_GhostTime', 0) + + if not imDead then + -- calculate target percent + local tgtState + if ply:Alive() then + tgtState = 1 - math.Clamp((ply:Health() or 0) / ply:GetMaxHealth(), 0, 1) + else + tgtState = 1 + end + + -- interpolate the value + local delta = (tgtState - curState) * (ft < 1 and ft or 1) + if math.abs(delta) < .01 then + delta = delta > 0 and .01 or -.01 + end + if tgtState - curState < .01 then + delta = tgtState - curState + end + curState = curState + delta + + -- apply effects + if curState ~= 0 then + local deathColors = { + [ '$pp_colour_addr' ] = 0, + [ '$pp_colour_addg' ] = 0, + [ '$pp_colour_addb' ] = 0, + [ '$pp_colour_brightness' ] = 0, + [ '$pp_colour_contrast' ] = 1 - curState * 0.7, + [ '$pp_colour_colour' ] = 1 - curState, + [ '$pp_colour_mulr' ] = 0, + [ '$pp_colour_mulg' ] = 0, + [ '$pp_colour_mulb' ] = 0 + } DrawColorModify(deathColors) + + if curState > 0.5 then + local _prc = (curState-.5) / .5 + DrawBloom(0.1, (_prc^3) * 1, 6, 6, 1, 0.25, 1, 1, 1) + -- DrawMotionBlur((1 - (_prc^(.2))*.8), (_prc^(.2))*(.8), 0.01) + end + end + + if curState ~= lastState then + local dsp = 1 + if curState > .8 then + dsp = 16 + elseif curState > .65 then + dsp = 15 + elseif curState > .5 then + dsp = 14 + end + ply:SetDSP(dsp) + end + + if not ply:Alive() then + local fadein = math.Clamp((ct - spTime + octodeath.config.spawnTime) / octodeath.config.fadeOutDeath, 0, 1) + local al = 255 * fadein + draw.RoundedBox(0, -5, -5, ScrW() + 10, ScrH() + 10, Color(0,0,0, al)) + else + local fadeout = math.Clamp((ct - spTime) / octodeath.config.fadeInSpawn, 0, 1) + local al = 255 * (1 - fadeout^3) + if al > 0 then + draw.RoundedBox(0, -5, -5, ScrW() + 10, ScrH() + 10, Color(255,255,255, al)) + end + end + + if ct > ghTime and ct < spTime and ghTime ~= 0 then + draw.RoundedBox(0, -5, -5, ScrW() + 10, ScrH() + 10, Color(0,0,0)) + end + elseif ply:GetNetVar('launcherActivated') then + local deathColors = { + [ '$pp_colour_addr' ] = 0, + [ '$pp_colour_addg' ] = 0, + [ '$pp_colour_addb' ] = 0.1, + [ '$pp_colour_brightness' ] = 0, + [ '$pp_colour_contrast' ] = .95, + [ '$pp_colour_colour' ] = 0.25, + [ '$pp_colour_mulr' ] = 0, + [ '$pp_colour_mulg' ] = 0, + [ '$pp_colour_mulb' ] = 0 + } DrawColorModify(deathColors) + + if curState ~= lastState then + ply:SetDSP(1) + end + + + ghTime = ghTime or ct + if ghTime and ct < ghTime - 0.5 and getRagdoll(ply) then + local fadein = math.Clamp((ghTime - ct - 0.75) / octodeath.config.ghostTime, 0, 1) + local al = 255 * (1 - fadein^3) + if al > 0 then + draw.RoundedBox(0, -5, -5, ScrW() + 10, ScrH() + 10, Color(0,0,0, al)) + end + else + draw.SimpleText( + string.ToMinutesSeconds(ply:GetNetVar('_SpawnTime', 0) - ct), + 'DeathScreen', + ScrW() / 2, + ScrH() - 10, + Color(220,220,220), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_BOTTOM + ) + local fadein = math.Clamp((ct - ghTime - 1) / octodeath.config.fadeInGhost, 0, 1) + local al = 255 * (1 - fadein^3) + if al > 0 then + draw.RoundedBox(0, -5, -5, ScrW() + 10, ScrH() + 10, Color(0,0,0, al)) + end + end + + local fadeout = math.Clamp((spTime - ct) / octodeath.config.fadeOutGhost, 0, 1) + local al = 255 * (1 - fadeout) + if al > 0 then + draw.RoundedBox(0, -5, -5, ScrW() + 10, ScrH() + 10, Color(255,255,255, al)) + end + end + lastState = curState +end) + +octolib.func.loop(function(done) + + local lp = LocalPlayer() + local imDead = lp:GetNetVar('Ghost') + + octolib.func.throttle(player.GetAll(), 10, 0.05, function(ply) + if not IsValid(ply) then return end + + local dead = ply:GetNetVar('Ghost') + if imDead then + ply:SetColor(Color(255,255,255, 30)) + else + if dead and not lp:getJobTable().seesGhosts then + ply:SetColor(Color(255,255,255, 0)) + else + ply:SetColor(Color(255,255,255, 30)) + end + end + end):Then(done) + +end) + +function player.GetGhosts() + local tbl = {} + for _, v in ipairs(player.GetAll()) do + if v:GetNetVar('Ghost') then tbl[#tbl + 1] = v end + end + return tbl +end diff --git a/garrysmod/addons/core-char/lua/ghosts/server.lua b/garrysmod/addons/core-char/lua/ghosts/server.lua new file mode 100644 index 0000000..c9489cc --- /dev/null +++ b/garrysmod/addons/core-char/lua/ghosts/server.lua @@ -0,0 +1,512 @@ +octodeath.DeathRagdolls = octodeath.DeathRagdolls or {} +local PM = FindMetaTable('Player') +local EM = FindMetaTable('Entity') +local maxPerPlayer = octodeath.config.ragdollsPerPlayer +local maxPerServer = octodeath.config.ragdollsPerServer + +local baseTime = 30 * 60 -- 30 mins +local minTime = 15 * 60 -- 15 mins +local maxTime = 4.5 * 60 * 60 -- 4.5 hours + +function octodeath.getSpawnTime(ply) + if ply:GetDBVar('ghostTime') then return ply:GetDBVar('ghostTime') end + if ply:Team() == TEAM_PRIEST then return minTime end + local karma = ply:GetKarma() + return math.Clamp(baseTime - (karma > 0 and (karma * 3) or (karma * 12)), minTime, maxTime) +end + +function octodeath.triggerDeath(ply) + local time = math.max(hook.Run('dbg-ghosts.overrideTime', ply) or octodeath.getSpawnTime(ply), 10) + ply:SetNetVar('_SpawnTime', CurTime() + time) + ply:SetNetVar('_GhostTime', CurTime() + math.min(time, octodeath.config.ghostTime)) + ply:SetDBVar('ghostTime', time) +end + +local curGhosts = {} +function PM:SetGhost(val) + if val then + curGhosts[self] = true + self:SetNetVar('Ghost', true) + else + curGhosts[self] = nil + self:SetNetVar('Ghost', false) + + if self:GetNetVar('launcherActivated') then + self:SetDBVar('ghostTime', nil) + self:SetDBVar('ghostBuffs', nil) + end + end +end + +function player.GetGhosts() + return table.GetKeys(curGhosts) +end + +hook.Add('GetPlayerChatColor', 'ghosts-chatcolor', function(ply, txt) + if ply:IsGhost() or not ply:Alive() then + return octochat.textColors.ooc + end +end) + +hook.Add('PlayerCanSeePlayersChat', 'chelog-death', function(txt, t, listener, talker) + + if talker:IsGhost() and not listener:IsGhost() and listener:Team() ~= TEAM_ADMIN then + return false + end + +end) + +hook.Add('PlayerInitialSpawn', 'SendCorpsesColorsOnConnected', function(ply) + timer.Simple(5, function() + netstream.Start(ply, 'CorpsesCreated', octodeath.DeathRagdolls) + end) +end) + +local protectedNVars = { {'name', 'seesName'}, {'attacker'}, {'bullet', 'seesCaliber'}, {'weapon', 'seesCaliber'}, {'time', 'seesTime'} } +for _, v in ipairs(protectedNVars) do + netvars.Register('Corpse.' .. v[1], { + checkAccess = function(ply) + return v[2] and ply:getJobTable()[v[2]] or ply:Team() == TEAM_ADMIN + end, + }) +end + +local deathCauses = L.deathCauses +function PM:CreateRagdoll(attacker, dmg) + + if not attacker then + attacker = dmg:GetAttacker() + end + + -- remove old player ragdolls + if not self.DeathRagdolls then self.DeathRagdolls = {} end + local numPlyR = 1 + for k,rag in pairs(self.DeathRagdolls) do + if IsValid(rag) then + numPlyR = numPlyR + 1 + else + self.DeathRagdolls[k] = nil + end + end + if maxPerPlayer >= 0 and numPlyR > maxPerPlayer then + for i = 0,numPlyR do + if numPlyR > maxPerPlayer then + self.DeathRagdolls[1]:Remove() + table.remove(self.DeathRagdolls, 1) + numPlyR = numPlyR - 1 + else + break + end + end + end + + -- remove old server ragdolls + local c2 = 1 + for k,rag in pairs(octodeath.DeathRagdolls) do + if IsValid(rag) then + c2 = c2 + 1 + else + octodeath.DeathRagdolls[k] = nil + end + end + if maxPerServer >= 0 and c2 > maxPerServer then + for i = 0,c2 do + if c2 > maxPerServer then + if IsValid(octodeath.DeathRagdolls[1]) then + octodeath.DeathRagdolls[1]:Remove() + end + table.remove(octodeath.DeathRagdolls,1) + c2 = c2 - 1 + else + break + end + end + end + + local Data = duplicator.CopyEntTable(self) + local subMats = {} + for i = 0, #self:GetMaterials() - 1 do + subMats[i] = self:GetSubMaterial(i) + end + + local destroyIn = math.max(octodeath.getSpawnTime(self), octodeath.config.minCorpseTime) + local ent = ents.Create('prop_ragdoll') + duplicator.DoGeneric(ent, Data) + ent:Spawn() + ent:SetCollisionGroup(COLLISION_GROUP_WEAPON) + for id, mat in pairs(subMats) do + ent:SetSubMaterial(id, mat) + end + ent:Fire('kill', '', destroyIn) + if ent.SetPlayerColor then + ent:SetPlayerColor(self:GetPlayerColor()) + end + ent:SetNetVar('RagdollOwner', self) + ent.ragdollSID = self:SteamID() + + local cause, weapon = table.Random(deathCauses.unknown) + for k, v in pairs(deathCauses) do + if isnumber(k) and dmg:IsDamageType(k) then + cause = table.Random(v) + weapon = self.lastWeapon or L.unknown + end + end + + ent:SetNetVar('dbgLook', { + name = '', + desc = 'corpseDesc', + descRender = true, + time = 8, + }) + + ent:SetNetVar('Corpse.name', self:Name()) + ent:SetNetVar('Corpse.attacker', self.lastAttacker or L.unknown) + ent:SetNetVar('Corpse.bullet', dmg:IsDamageType(DMG_BULLET)) + ent:SetNetVar('Corpse.cause', cause) + ent:SetNetVar('Corpse.weapon', weapon) + ent:SetNetVar('Corpse.time', CWI.TimeToString()) + + -- apply force to hit bone + local hitPos = dmg:GetDamagePosition() + local force = dmg:GetDamageForce() + local vel = self.lastVelocity or self:GetVelocity() + local physNum = ent:GetPhysicsObjectCount() + local minDist, hitBone = 1000000 + for id = 0, physNum - 1 do + local phys = ent:GetPhysicsObjectNum(id) + if IsValid(phys) then + local bone = ent:TranslatePhysBoneToBone(id) + local pos, ang = self:GetBonePosition(bone) + phys:SetPos(pos) + phys:SetAngles(ang) + phys:AddVelocity(vel) + + local testDist = hitPos:DistToSqr(pos) + if testDist < minDist then + hitBone = phys + minDist = testDist + end + end + end + + if hitBone then + hitBone:ApplyForceOffset(force / 2, hitPos) + end + + -- finish up + ent:SetNetVar('DeathTime', CurTime()) + self:SetNetVar('DeathRagdoll', ent) + timer.Simple(0, function() + if not IsValid(self) or not IsValid(ent) then return end + self:SetNetVar('DeathRagdoll') + self:SetNetVar('DeathRagdoll', ent) + end) + self:SetGhost(true) + self:SetClothes(nil) + table.insert(self.DeathRagdolls, ent) + table.insert(octodeath.DeathRagdolls, ent) + +end + +hook.Add('PlayerShouldTakeDamage', 'dbg-death', function(ply) + ply.lastVelocity = ply:GetVelocity() +end) + +if not PM.GetRagdollEntityOld then + PM.GetRagdollEntityOld = PM.GetRagdollEntity +end +function PM:GetRagdollEntity() + local ent = self:GetNetVar('DeathRagdoll') + if IsValid(ent) then + return ent + else + return self:GetRagdollEntityOld() + end +end + +if not PM.GetRagdollOwnerOld then + PM.GetRagdollOwnerOld = PM.GetRagdollOwner +end +function EM:GetRagdollOwner() + local ent = self:GetNetVar('RagdollOwner') + if IsValid(ent) then + return ent + end + if self.ragdollSID then + ent = player.GetBySteamID(self.ragdollSID) + if IsValid(ent) then return ent end + end + return self.GetRagdollOwnerOld and self:GetRagdollOwnerOld() or Entity(0) +end + +local function penalty(ply) + if ply:GetNetVar('launcherActivated') and (ply:GetNetVar('Ghost') or not ply:Alive()) then + ply:SetDBVar('ghostTime', ply:GetNetVar('_SpawnTime') - CurTime()) + end +end +hook.Add('PlayerDisconnected', 'dbg-ghost.setGhostAgain', penalty) + +local function check(ply) + if not IsValid(ply) then return end + if not ply:GetDBVar('ghostTime') then return end + ply:Notify('warning', L.death_leave) + ply:KillSilent() + ply.inv = nil +end +hook.Add('dbg-test.complete', 'dbg-ghost.checkGhost', check) + +local function death(ply) + ply:SetGhost(true) + + octodeath.triggerDeath(ply) + ply:SetLocalVar('Energy', 100) + ply.died = true + if ply.UpdateCharState then ply:UpdateCharState() end + + if ply:isArrested() then ply:unArrest() end +end +hook.Add('PlayerDeath', 'Ghosts', death) + +local function silentDeath(ply) + ply:SetGhost(true) + + octodeath.triggerDeath(ply) + ply:SetLocalVar('Energy', 100) + if ply.UpdateCharState then ply:UpdateCharState() end + + if ply:isArrested() then ply:unArrest() end +end +hook.Add('PlayerSilentDeath', 'Ghosts', silentDeath) + +local function flashlight(ply, enabled) + if ply:GetNetVar('Ghost') and not enabled then + return false + end +end +hook.Add('PlayerSwitchFlashlight', 'GhostsCannotUseFlashlights', flashlight) + +hook.Add('PlayerSpawn', 'GhostSpawn', function(ply) + + if ply:GetNetVar('Ghost') and (not ply:GetNetVar('launcherActivated') or ply:GetNetVar('_SpawnTime') > CurTime()) then + local function reset(ply) + if not IsValid(ply) then return end + + ply:StripWeapons() + ply:Give('dbg_hands') + ply:GodEnable() + + -- i know it's done on death, but shit happens + ply:ImportInventory(octoinv.defaultInventory) + end + + timer.Simple(0, reset) + timer.Simple(5, reset) -- dunno, it just happens + + -- make players look like ghosts + ply:SetColor(Color(255,255,255, 30)) + ply:SetRenderMode(RENDERMODE_TRANSALPHA) + ply:SetCustomCollisionCheck(true) + ply:CollisionRulesChanged() + ply:DrawShadow(false) + ply:SetMaterial('models/props/cs_office/clouds') + ply:SetBloodColor(DONT_BLEED) + ply:SetAvoidPlayers(false) + ply:SetHealth(100) + ply:SetLocalVar('Energy', 100) + + timer.Simple(0.5, function() + local corpse = ply:GetNetVar('DeathRagdoll') + if IsValid(corpse) then + local pos = FindSuitablePosition(corpse:GetPos(), ply, {around = 40, above = 80}, player.GetAll()) + if pos then ply:SetPos(pos) end + end + end) + end + +end) + +-- +-- OTHER +-- + +function FindSuitablePosition(pos, ent, dist, filtr) + -- NOTE: ply size: (32, 32, 72) + local function checkPos(pos) + local trace = { start = pos, endpos = pos, filter = filtr } + local tr = util.TraceEntity(trace, ent) + + return not tr.Hit + end + + -- check initial position + if checkPos(pos) then return pos end + + -- find a place around + local testpos + for i = 0, 300, 60 do + testpos = pos + Angle(0, i, 0):Forward() * dist.around + if checkPos(testpos) then return testpos end + end + + -- check a place above + testpos = pos + Vector(0, 0, dist.above) + if checkPos(pos + Vector(0, 0, dist.above)) then return testpos end + + -- if we haven't found any place + return false +end + +-- +-- SOME GHOST HOOKS +-- + +local function PlayerThink() + for ply, _ in pairs(curGhosts) do + if ply:GetNetVar('Ghost', false) and ply:GetNetVar('launcherActivated') and CurTime() >= ply:GetNetVar('_SpawnTime', 0) then + ply:ExitVehicle() + ply:SetGhost(false) + ply:SetHealth(100) + ply:SetLocalVar('Energy', 100) + ply:Spawn() + ply.died = false + + ply:GodDisable() + + -- undo ghost look + ply:SetColor(Color(255, 255, 255, 255)) + ply:SetRenderMode(RENDERMODE_NORMAL) + ply:SetCustomCollisionCheck(false) + ply:CollisionRulesChanged() + ply:DrawShadow(true) + ply:SetMaterial('') + ply:SetBloodColor(BLOOD_COLOR_RED) + ply:SetAvoidPlayers(false) + end + if not (IsValid(ply) and ply:GetNetVar('Ghost')) then curGhosts[ply] = nil end + end +end +hook.Add('Think', 'GhostThink', PlayerThink) + +local function updateGMFuncs() + if not GAMEMODE then return end + function GAMEMODE:PlayerDeathThink(ply) + if CurTime() >= ply:GetNetVar('_GhostTime', 0) then + ply:SetGhost(true) + ply:Spawn() + end + + -- disable spawning + return false + end +end +hook.Add('darkrp.loadModules', 'dbg-ghosts', updateGMFuncs) +updateGMFuncs() + +-- people can't hear ghosts +local function handleChat(listener, talker) + if talker:IsGhost() and not listener:IsGhost() and listener:Team() ~= TEAM_ADMIN then + return false + end +end +hook.Add('PlayerCanHearPlayersVoice', 'GhostsHear', handleChat) + +local function silentDont(ply) + if IsValid(ply) and (not ply:Alive() or ply:GetNetVar('Ghost')) then return false end +end +hook.Add('PlayerCanPickupItem', 'GhostsCannotInteract', silentDont) +hook.Add('PlayerShouldTakeDamage', 'GhostsCannotTakeDamage', silentDont) +hook.Add('PlayerUse', 'GhostsCannotUse', silentDont) + +hook.Add('shouldViewPunchOnDamage', 'GhostsCannotViewPunch', function(ply) + if IsValid(ply) and (not ply:Alive() or ply:GetNetVar('Ghost')) then return true end +end) + +local function noHandcuff(ply, victim) + if victim:GetNetVar('Ghost') then + return false + end +end +hook.Add('CuffsCanHandcuff', 'noHandcuff', noHandcuff) + +-- darkrp hooks +local function dont(ply) + if IsValid(ply) and ply:GetNetVar('Ghost') then + ply:Notify('warning', L.dead_cant_do_this) + return false + end +end +hook.Add('canChangeJob', 'ghosts', dont) +hook.Add('canBuyAmmo', 'ghosts', dont) +hook.Add('canBuyCustomEntity', 'ghosts', dont) +hook.Add('canBuyPistol', 'ghosts', dont) +hook.Add('canBuyShipment', 'ghosts', dont) +hook.Add('canBuyVehicle', 'ghosts', dont) +hook.Add('canDemote', 'ghosts', dont) +hook.Add('canEditLaws', 'ghosts', dont) +hook.Add('canPropertyTax', 'ghosts', dont) +hook.Add('canRequestHit', 'ghosts', dont) +hook.Add('canRequestWarrant', 'ghosts', dont) +hook.Add('canStartVote', 'ghosts', dont) +hook.Add('canTax', 'ghosts', dont) +hook.Add('canUnwant', 'ghosts', dont) +hook.Add('canVote', 'ghosts', dont) +hook.Add('canWanted', 'ghosts', dont) +hook.Add('CanPickupWeapon', 'ghosts', dont) +hook.Add('PlayerSpawnObject', 'ghosts', dont) +hook.Add('dbg-talkie.canSpeak', 'ghosts', silentDont) +hook.Add('dbg-talkie.canListen', 'ghosts', silentDont) + +hook.Add('PlayerPickupDarkRPWeapon', 'ghosts', function(ply) + if ply:GetNetVar('Ghost') then + return true + end +end) + +hook.Add('playerGetSalary', 'ghosts', function(ply) + + if ply:GetNetVar('Ghost') then + return true, '', 0 + end + +end) + +local disallowed = { + '/rockpaperscissors', + '/coin', + '/dice', + '/roll', + '/sms', + '//it', + '/toit', + '/me', + '/yell', + '/y', + '/whisper', + '/w', + '/advert', + '/broadcast', + '/cr', + '/drop', + '/moneyput', + '/putmoney', + '/dropmoney', + '/moneydrop', + '/putmoney', + '/moneyput', + '/dropweapon', + '/g', + '/give', + '/lockdown', + '/unlockdown', + '/unwarrant', + '/warrant', + '/write', +} + +local function cantChatCommand(ply, cmd) + if ply:IsGhost() and table.HasValue(disallowed, cmd) then + return false, L.dead_cant_do_this + end +end + +hook.Add('octochat.canExecute', 'ghosts-cantchatcommands', cantChatCommand) diff --git a/garrysmod/addons/core-char/lua/ghosts/shared.lua b/garrysmod/addons/core-char/lua/ghosts/shared.lua new file mode 100644 index 0000000..e5c009a --- /dev/null +++ b/garrysmod/addons/core-char/lua/ghosts/shared.lua @@ -0,0 +1,88 @@ +octodeath = octodeath or {} +octodeath.config = { + spawnTime = 5 * 60, -- time (s) after death to spawn (default: 10 minutes) + ghostTime = 5, -- time (s) after death to spawn as ghost (default: 5 seconds) + ragdollsPerPlayer = 1, -- max death ragdolls per one player (default: 1) + ragdollsPerServer = 8, -- max death ragdolls per server (default: 8) + fadeOutDeath = 1, -- time (ms) how long screen fades out after death (default: 1) + fadeInGhost = 4, -- time (ms) how long screen fades in after death (default: 4) + fadeOutGhost = 0.2, -- time (ms) how long screen fades out before spawn (default: 0.2) + fadeInSpawn = 4, -- time (ms) how long screen fades in after spawn (default: 4) + minCorpseTime = 20 * 60, +} + +local classes = { + prop_physics = true, + prop_physics_multiplayer = true, + prop_dynamic = true, + prop_static = true, + func_door = true, + func_lookdoor = true, + func_door_rotating = true, + prop_door_rotating = true, + prop_ragdoll = true, + --func_movelinear = true, + func_breakable = true, + func_physbox = true, + func_breakable_surf = true, + extended_money_printer = true, + standard_money_printer = true, + printer_battery = true, + spawned_shipment = true, + itemstore_bag = true, + itemstore_box_large = true, + itemstore_box_small = true, + itemstore_briefcase = true, + itemstore_suitcase_large = true, + itemstore_suitcase_small = true, + m9k_css_thrown_knife = true, + gmod_wire_wheel = true, + gmod_sent_vehicle_fphysics_base = true, + gmod_sent_vehicle_fphysics_wheel = true, + ent_dbg_radio = true, + ent_dbg_grenade_gas = true, + ent_dbg_grenade_shock = true, + ent_dbg_grenade_air = true, + ent_dbg_grenade_frag = true, + ent_dbg_grenade_smoke = true, + ent_dbg_petard = true, + dbg_jobs_package = true, + letter = true, + keypad = true, +} + +local function handleCollide( ent1, ent2 ) + -- disable collision between ghosts and players + if ent1:IsPlayer() and ent2:IsPlayer() then + if ent1:GetNetVar('Ghost') or ent2:GetNetVar('Ghost') then + return false + end + elseif ent1:IsPlayer() and ent1:GetNetVar('Ghost') then + return not (classes[ ent2:GetClass() ] or ent2:GetClass():StartWith('octoinv_')) + elseif ent2:IsPlayer() and ent2:GetNetVar('Ghost') then + return not (classes[ ent1:GetClass() ] or ent1:GetClass():StartWith('octoinv_')) + end +end +hook.Add('ShouldCollide', 'GhostCollisions', handleCollide) + +hook.Add('PlayerFootstep', 'GhostsMakeNoSound', function(ply) + if ply:GetNetVar('Ghost') then return true end +end) + +hook.Add('EntityEmitSound', 'ghosts', function(data) + local ent = data.Entity + if IsValid(ent) and ent:IsPlayer() and ent:GetNetVar('Ghost') then return false end +end) + +hook.Add('Think', 'toolgun-nosound', function() + + hook.Remove('Think', 'toolgun-nosound') + weapons.GetStored('gmod_tool').ShootSound = '' + +end) + +local PM = FindMetaTable 'Player' + +function PM:IsGhost() + return self:GetNetVar('Ghost', false) +end diff --git a/garrysmod/addons/core-char/lua/karma/server.lua b/garrysmod/addons/core-char/lua/karma/server.lua new file mode 100644 index 0000000..9c91cc5 --- /dev/null +++ b/garrysmod/addons/core-char/lua/karma/server.lua @@ -0,0 +1,253 @@ +local meta = FindMetaTable 'Player' + +local function databaseInit() + + MySQLite.query([[ + CREATE TABLE IF NOT EXISTS dbg_karma ( + steamID VARCHAR(50) NOT NULL PRIMARY KEY, + karma INT(8) + ) + ]]) + +end +hook.Add('DarkRPDBInitialized', 'dbg-karma', databaseInit) + +netvars.Register('dbg.karma', { + checkAccess = function(ply) + return ply:Team() == TEAM_ADMIN or ply:Team() == TEAM_PRIEST + end, +}) + +function meta:SetKarma(amount) + + if not IsValid(self) or not amount then return end + self:SetNetVar('dbg.karma', amount) + + MySQLite.query(string.format([[UPDATE dbg_karma SET karma = %d WHERE steamID = %s]], + self:GetNetVar('dbg.karma', 0), + MySQLite.SQLStr(self:SteamID()) + )) + +end + +local msgs = { + goodBoy = { + L.msg_good, + L.msg_good2, + L.msg_good3, + }, + strange = { + L.msg_strange, + L.msg_strange2, + L.msg_strange3, + L.msg_strange4, + }, +} + +function meta:AddKarma(amount, customMsg, noMultiply, force) + + if hook.Run('dbg-karma.override', self) == false then return end + if not amount or amount == 0 then return end + local job = self:getJobTable() + if not force and amount < 0 and job.disabledKarma then return end + + local reason = customMsg or (amount < 0 and L.up_karma or L.down_karma) + local curKarma = self:GetNetVar('dbg.karma', 0) + if not noMultiply and curKarma > 0 and amount < 0 then + amount = amount + math.floor(amount * curKarma / 25) + if amount < -20 then + reason = reason .. table.Random(msgs.strange) + end + end + + local newKarma = curKarma + amount + self:SetKarma(newKarma) + + if amount < 0 then + self:Notify('warning', L.karma_notify:format(reason, newKarma)) + timer.Start('karma_' .. self:SteamID()) + else + self:Notify('hint', L.karma_notify:format(reason, newKarma)) + end + + hook.Run('dbg-karma.changed', self, newKarma, curKarma) + +end + +hook.Add('PlayerInitialSpawn', 'dbg.krama', function(ply) + + MySQLite.query(string.format([[SELECT * FROM dbg_karma WHERE steamID = %s]], MySQLite.SQLStr(ply:SteamID())), function(res) + + res = res and res[1] + if res then + ply:SetNetVar('dbg.karma', res.karma) + else + ply:SetNetVar('dbg.karma', 0) + MySQLite.query(string.format( + [[INSERT INTO dbg_karma (steamID, karma) + VALUES (%s, 0)]], + MySQLite.SQLStr(ply:SteamID()) + )) + + if CFG.webhooks.cheats then + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = {{ + title = 'Первый вход на сервер', + fields = {{ + name = L.player, + value = ply:GetName() .. '\n[' .. ply:SteamID() .. '](' .. 'https://steamcommunity.com/profiles/' .. ply:SteamID64() .. ')', + }}, + }}, + }) + end + end + + ply.karmaLast = {} + + end) + + timer.Create('karma_' .. ply:SteamID(), 20 * 60, 0, function() + if not ply:IsAFK() then + ply:AddKarma(1, table.Random(msgs.goodBoy)) + end + end) + +end) + +local function respawnMassfkVictim(steamID) + octolib.setDBVar(steamID, 'ghostTime', nil):Then(function() + local victimPly = player.GetBySteamID(steamID) + if IsValid(victimPly) then + if target:Alive() then target:KillSilent() end + target:SetNetVar('_SpawnTime', CurTime()) + end + end) +end + +hook.Add('PlayerDeath', 'dbg.karma', function(ply, wep, attacker) + + if IsValid(attacker) and attacker:IsPlayer() then + + if attacker ~= ply then + local wep = ply:GetActiveWeapon() + local wasDangerous = + CurTime() - (ply.lastAim or -20) < 20 or -- if victim didn't aim recently, get penalty + (IsValid(wep) and dbgWeaponGroups[wep:GetClass()] ~= 'allowAll') -- if victim didn't have gun, get penalty + local job = attacker:getJobTable() + if not job.noKarmaDamagePenalty or not wasDangerous then + attacker:AddKarma(-5, L.karma_kill) + + -- if victim did not intend to fight back, cache kill as massfk attempt + if not wasDangerous and not attacker:IsAdmin() then + attacker.massFKcache = attacker.massFKcache or {} + attacker.massFKcache[#attacker.massFKcache + 1] = ply:SteamID() + + local timerName = 'antiMassFK_' .. attacker:SteamID() + if #attacker.massFKcache >= 3 then + attacker:BanEverywhere(0, 'MassFK') + + local massFKcache = attacker.massFKcache + timer.Simple(1, function() -- wait a bit to execute after all death handlers + for _, sID in ipairs(massFKcache) do + respawnMassfkVictim(sID) + end + end) + timer.Remove(timerName) + else + timer.Create(timerName, 120, 1, function() + attacker.massFKcache = nil + end) + end + end + end + elseif not attacker.HungerDeath then + attacker:AddKarma(-5, L.karma_suicide) + end + end + +end) + +hook.Add('EntityTakeDamage', 'dbg.karma', function(ply, dmg) + + local attacker = dmg:GetAttacker() + if IsValid(attacker) and attacker:IsPlayer() + and IsValid(ply) and ply:IsPlayer() and not ply:IsGhost() + and attacker ~= ply then + if ply:Team() == TEAM_ADMIN then dmg:ScaleDamage(0) end + + local wep = attacker:GetActiveWeapon() + if IsValid(wep) and not wep.IsLethal then return end + + if CurTime() - (attacker.karmaLast.damage or -120) > 120 then + local job = attacker:getJobTable() + if (not ply.karmaDamaged and CurTime() - (ply.lastAim or -20) > 20) and not job.noKarmaDamagePenalty then + attacker:AddKarma(-1, L.karma_damage) + attacker.karmaLast.damage = CurTime() + end + end + + attacker.karmaDamaged = true + timer.Create('karmaDamage_' .. attacker:SteamID(), 30, 1, function() + if IsValid(attacker) then attacker.karmaDamaged = nil end + end) + end + +end) + +hook.Add('dbg.scareStart', 'dbg.karma', function(ply, attacker) + + local job = attacker:getJobTable() + if CurTime() - (attacker.karmaLast.scare or -180) > 180 and not job.noKarmaDamagePenalty then + attacker:AddKarma(-1, L.karma_scare) + attacker.karmaLast.scare = CurTime() + end + +end) + +hook.Add('onLockpickCompleted', 'dbg.karma', function(ply, succ) + + if succ and CurTime() - (ply.karmaLast.lockpick or -180) > 180 then + ply:AddKarma(-1, L.karma_not_your) + ply.karmaLast.lockpick = CurTime() + end + +end) + +hook.Add('dbg-hack.1stCommand', 'dbg.karma', function(ply, ent) + if ply ~= ent:CPPIGetOwner() and CurTime() - (ply.karmaLast.keypad or -180) > 180 then + ply:AddKarma(-1, L.karma_not_your) + ply.karmaLast.keypad = CurTime() + end +end) + +hook.Add('PlayerDisconnected', 'dbg.karma', function(ply) + + timer.Remove('karma_' .. ply:SteamID()) + + -- local karma, family = ply:GetNetVar('dbg.karma'), ply:GetDBVar('family') or {} + -- for i, sID in ipairs(family) do + -- MySQLite.query(string.format([[UPDATE dbg_karma SET karma = %d WHERE steamID = %s]], + -- karma, + -- MySQLite.SQLStr(sID) + -- )) + -- end + +end) + +function octolib.banEverywhere(sid, time, reason) + octolib.sendCmdToOthers('sg', { 'ban', sid, tostring(time), reason }) + serverguard:BanPlayer(nil, sid, '0', reason, true) +end + +function meta:BanEverywhere(time, reason) + octolib.banEverywhere(self:SteamID(), time, reason) +end + +hook.Add('octoinv.pickup', 'dbg.suspectWeaponPickup', function(ply, ent) + + if IsValid(ent) and ent.isEvidence then + ply:AddKarma(-2, 'Кажется, это была важная улика.') + end + +end) diff --git a/garrysmod/addons/core-char/lua/karma/shared.lua b/garrysmod/addons/core-char/lua/karma/shared.lua new file mode 100644 index 0000000..a91d2e4 --- /dev/null +++ b/garrysmod/addons/core-char/lua/karma/shared.lua @@ -0,0 +1,8 @@ +local meta = FindMetaTable 'Player' + +function meta:GetKarma() + + if hook.Run('dbg-karma.override', self) == false then return 0 end + return self:GetNetVar('dbg.karma', 0) + +end diff --git a/garrysmod/addons/core-char/lua/masks/client.lua b/garrysmod/addons/core-char/lua/masks/client.lua new file mode 100644 index 0000000..f2f2a54 --- /dev/null +++ b/garrysmod/addons/core-char/lua/masks/client.lua @@ -0,0 +1,60 @@ +local angle_zero = Angle(0, 0, 0) + +local plyMeta = FindMetaTable 'Player' +function plyMeta:IsMaskVisible() + if self.maskVisible ~= nil then return self.maskVisible end + return self ~= LocalPlayer() +end +function plyMeta:SetMaskVisible(vis) + self.maskVisible = vis + if IsValid(self.hMask) then self.hMask:SetNoDraw(not self:IsMaskVisible()) end +end +netstream.Hook('dbg-mask.SetMaskVisible', plyMeta.SetMaskVisible) + +local maskEnts = {} +local maxDist = 2250000 +timer.Create('dbg-masks', 1, 0, function() + + local me = LocalPlayer() + if not IsValid(me) then return end + + local pos = me:EyePos() + for _, ply in ipairs(player.GetAll()) do + + local class = ply:GetMaskId() + local mask = ply.hMask + + if not class or IsValid(mask) and mask.maskClass ~= class or ply:GetPos():DistToSqr(pos) > maxDist or ply:GetNoDraw() or ply:GetNetVar('Ghost') then + if IsValid(ply.hMask) then ply.hMask:Remove() end + continue + end + + if not IsValid(mask) then + local data = CFG.masks[class] + if not data then continue end + + mask = octolib.createDummy(data.mdl) + mask.maskClass = class + ply.hMask = mask + + mask:SetParent(ply, ply:LookupAttachment('eyes') or 1) + mask:SetLocalPos(data.pos) + mask:SetLocalAngles(data.ang or angle_zero) + if data.scale then mask:SetModelScale(data.scale) end + if data.skin then mask:SetSkin(data.skin) end + mask:SetPredictable(true) + + table.insert(maskEnts, mask) + mask:SetNoDraw(not ply:IsMaskVisible()) + end + end + + for i = #maskEnts, 1, -1 do + local mask = maskEnts[i] + if IsValid(mask) and not IsValid(mask:GetParent()) then + mask:Remove() + end + if not IsValid(mask) then table.remove(maskEnts, i) end + end + +end) diff --git a/garrysmod/addons/core-char/lua/masks/server.lua b/garrysmod/addons/core-char/lua/masks/server.lua new file mode 100644 index 0000000..7c600fc --- /dev/null +++ b/garrysmod/addons/core-char/lua/masks/server.lua @@ -0,0 +1,118 @@ +local plyMeta = FindMetaTable 'Player' + +function plyMeta:Unmask() + + local mask, expire = self:GetMaskId(), self:GetMaskExpire() + if not mask then return end + if not self:CanUnmask() then return end + + if not self:Alive() or self:IsGhost() then + self:Notify('warning', 'Ты мертв!') + return + end + + local cont = self.inv and self.inv.conts._hand + if not cont then + self:Notify('warning', L.hands_free) + return + end + + local maskData = { + name = CFG.masks[mask].name, + icon = CFG.masks[mask].icon, + desc = CFG.masks[mask].desc, + mask = mask, + expire = expire, + } + + local item = cont:AddItem('h_mask', maskData) + if not item or item == 0 then + self:Notify('warning', 'В руках недостаточно места') + return + end + + self:SetNetVar('hMask', nil) + self:SetDBVar('hMask', nil) + + hook.Run('dbg-masks.unmask', self, maskData.name) + +end +concommand.Add('dbg_unmask', plyMeta.Unmask) + +hook.Add('PlayerFinishedLoading', 'dbg-masks', function(ply) + + timer.Simple(10, function() + if not IsValid(ply) then return end + local mask = ply:GetDBVar('hMask') + + if istable(mask) and mask[2] then + if mask[2] <= os.time() then + ply:SetDBVar('hMask') + ply:Notify('У твоего аксессуара закончился срок годности') + return + else + ply.maskExpireUid = 'octoinv.maskExpire' .. octolib.string.uuid() + timer.Create(ply.maskExpireUid, mask[2] - os.time(), 1, function() + if not IsValid(ply) then return end + ply:SetNetVar('hMask') + ply:SetDBVar('hMask') + ply:Notify('У твоего аксессуара закончился срок годности') + end) + end + end + + if mask then + ply:SetNetVar('hMask', mask) + hook.Run('dbg-masks.mask', ply, mask[1], true) + end + end) + +end) + +hook.Add('PlayerDeath', 'dbg-masks', function(ply) + + local mask, expire = ply:GetMaskId(), ply:GetMaskExpire() + if not mask or not CFG.masks[mask] or CFG.masks[mask].noDrop then return end + + local shouldDrop = ply:CanUnmask() + + ply:SetNetVar('hMask', nil) + ply:SetDBVar('hMask', nil) + if ply.maskExpireUid then + timer.Remove(ply.maskExpireUid) + ply.maskExpireUid = nil + end + + if not shouldDrop then return end + + local ent = ents.Create 'octoinv_item' + ent:SetPos(ply:GetShootPos()) + ent:SetAngles(AngleRand()) + ent:SetData('h_mask', { + name = CFG.masks[mask].name, + icon = CFG.masks[mask].icon, + desc = CFG.masks[mask].desc, + mask = mask, + expire = expire, + }) + ent.droppedBy = ply + + ent:Spawn() + ent:Activate() + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(ply:GetAimVector() * 150) + end + +end) + +hook.Add('PlayerDisconnected', 'dbg-masks', function(ply) + + ply:SetNetVar('hMask', nil) + if ply.maskExpireUid then + timer.Remove(ply.maskExpireUid) + end + +end) diff --git a/garrysmod/addons/core-char/lua/masks/shared.lua b/garrysmod/addons/core-char/lua/masks/shared.lua new file mode 100644 index 0000000..bb1a71c --- /dev/null +++ b/garrysmod/addons/core-char/lua/masks/shared.lua @@ -0,0 +1,19 @@ +local plyMeta = FindMetaTable('Player') + +function plyMeta:GetMaskId() + local data = self:GetNetVar('hMask') + if not data then return end + return istable(data) and data[1] or data +end + +function plyMeta:GetMaskExpire() + local data = self:GetNetVar('hMask') + if not data then return end + return istable(data) and data[2] or nil +end + +function plyMeta:CanUnmask() + local data = self:GetNetVar('hMask') + if not data then return end + return istable(data) and not data.unequip or false +end \ No newline at end of file diff --git a/garrysmod/addons/core-octochat/lua/autorun/octochat.lua b/garrysmod/addons/core-octochat/lua/autorun/octochat.lua new file mode 100644 index 0000000..32459ab --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/autorun/octochat.lua @@ -0,0 +1,10 @@ +octochat = octochat or {} + +octolib.module('octochat') +octolib.client('octochat/cl_notifications') + +octolib.module('octochat/commands') +local _, cmds = file.Find('config/octochat-commands/*', 'LUA') +for _, t in ipairs(cmds) do + octolib.module('config/octochat-commands/' .. t) +end diff --git a/garrysmod/addons/core-octochat/lua/cmenu/items/demote.lua b/garrysmod/addons/core-octochat/lua/cmenu/items/demote.lua new file mode 100644 index 0000000..9f7223b --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/cmenu/items/demote.lua @@ -0,0 +1,17 @@ +octogui.cmenu.registerItem('rp', 'demote', { + text = L.demote, + icon = octolib.icons.silk16('user_delete'), + build = function(sm) + local plys = player.GetAll() + table.sort(plys, function(a, b) return a:GetName() < b:GetName() end) + + local me = LocalPlayer() + for _, v in ipairs(plys) do + if v ~= me then + sm:AddOption(v:Name(), octolib.fStringRequest(L.c_language_demote, L.c_language_demote_description, '', function(s) + octochat.say('/demote', v:UserID(), s) + end, nil, L.ok, L.cancel)):SetColor(v:getJobTable().color) + end + end + end, +}) diff --git a/garrysmod/addons/core-octochat/lua/octochat/cl_notifications.lua b/garrysmod/addons/core-octochat/lua/octochat/cl_notifications.lua new file mode 100644 index 0000000..881a235 --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/cl_notifications.lua @@ -0,0 +1,9 @@ +for k, v in pairs(octochat.notifyColors) do + + octolib.notify.registerType(k, function(...) + surface.PlaySound('buttons/lightswitch2.wav') + octochat.msg(v, '[#] ', Color(250, 250, 200), ...) + end) + +end +octolib.notify.types._generic = octolib.notify.types.rp diff --git a/garrysmod/addons/core-octochat/lua/octochat/client.lua b/garrysmod/addons/core-octochat/lua/octochat/client.lua new file mode 100644 index 0000000..49e6e44 --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/client.lua @@ -0,0 +1,779 @@ +octochat = octochat or {} +local defaultCol = Color(85, 68, 85) + +hook.Add('HUDShouldDraw', 'octochat', function(name) + + if name == 'CHudChat' then return false end + +end) + +hook.Add('PlayerBindPress', 'octochat', function(ply, bind, press) + + if bind == 'messagemode' or bind == 'messagemode2' then + if not IsValid(octochat.pnl) then octochat.create() end + octochat.open() + octochat.team = bind == 'messagemode2' + + local text, now = hook.Run('octochat.chatOpenText', bind), octochat.pnl.entry:GetText() + if isstring(text) and utf8.sub(now, 1, utf8.len(text)) ~= text then + local new = text .. now + octochat.pnl.entry:SetText(new) + octochat.pnl.entry:SetCaretPos(utf8.len(new)) + end + + return true + end + +end) + +hook.Add('StartCommand', 'octochat', function(ply, cmd) + if cmd:KeyDown(IN_ATTACK) and input.IsKeyDown(KEY_ENTER) then + cmd:RemoveKey(IN_ATTACK) + end +end) + +hook.Add('InitPostEntity', 'octochat.noVoice', function() + if IsValid(g_VoicePanelList) then + g_VoicePanelList:Remove() + end +end) + +function chat.AddText(...) + + if not IsValid(octochat.pnl) then octochat.create() end + octochat.msg(...) + +end +chat.AddNonParsedText = chat.AddText + +function chat.GetChatBoxPos() + + if not IsValid(octochat.pnl) then octochat.create() end + return octochat.pnl:GetPos() + +end + +function chat.GetChatBoxSize() + + if not IsValid(octochat.pnl) then octochat.create() end + return octochat.pnl:GetSize() + +end + +local ply = FindMetaTable 'Player' +function ply:ChatPrint(str) + + chat.AddText(str) + +end + +netstream.Hook('octochat.addEntry', chat.AddText) + +netstream.Hook('chat', function(txt) + + chat.AddText(txt) + +end) + +function octochat.say(...) + netstream.Start('chat', table.concat({...}, ' ')) +end + +hook.Add('OnPlayerChat', 'octochat', function(ply, text) + if not IsValid(octochat.pnl) then return end + if not IsValid(ply) then + chat.AddText(octochat.textColors.rp, L.console, L.postfix_say, color_white, text) + end + return true +end) + +function octochat.recreateFont() + + surface.CreateFont('octochat.default', { + font = 'Roboto ' .. (octochat.settings.bold and 'Bold' or 'Regular'), + extended = true, + size = octochat.settings.textSize or 18, + weight = 300, + shadow = octochat.settings.shadow, + }) + surface.CreateFont('octochat.underlined', { + font = 'Roboto ' .. (octochat.settings.bold and 'Bold' or 'Regular'), + extended = true, + size = octochat.settings.textSize or 18, + weight = 300, + shadow = octochat.settings.shadow, + underline = true, + }) + +end + +local colors = CFG.skinColors +function octochat.create() + + if not octochat.settings then octochat.load() end + octochat.recreateFont() + + if IsValid(octochat.pnl) then octochat.pnl:Remove() end + octochat.pnl = vgui.Create('DFrame') + + local w, h = octochat.settings.size[1], octochat.settings.size[2] + local x, y = math.Clamp(octochat.settings.pos[1], -(w - 10), ScrW() - 10), math.Clamp(octochat.settings.pos[2], -(h - 10), ScrH() - 10) + + local pnl = octochat.pnl + pnl:SetPos(x, y) + pnl:SetSize(w, h) + pnl:DockPadding(5, 5, 5, 0) + pnl:SetTitle('') + pnl:ShowCloseButton(false) + pnl:SetSizable(true) + pnl:MakePopup() + local oldThink = pnl.Think + function pnl:Think() + if oldThink then oldThink(self) end + self.history:SetVisible(octochat.isOpen or (hook.Run('HUDShouldDraw', 'octochat') ~= false)) + end + + pnl.alpha = 0 + pnl.Paint = function(self, w, h) + if octochat.settings.showHint then + surface.DisableClipping(true) + draw.SimpleText(L.chat_settings_hint, 'DermaDefault', 5, -15, Color(255,255,255, self.alpha * 220), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + surface.DisableClipping(false) + end + + local col = octochat.settings.color + if ColorAlpha(col, 255) == defaultCol then + col = ColorAlpha(colors.bg, col.a) + end + draw.RoundedBox(4, 0, 0, w, h, Color(col.r, col.g, col.b, col.a * self.alpha)) + draw.RoundedBoxEx(4, 0, h - 30, w, 30, Color(0,0,0, 80 * self.alpha), false, false, true, true) + + self.alpha = math.Approach(self.alpha, octochat.isOpen and 1 or 0, FrameTime() * 5) + end + + pnl.history = vgui.Create('RichText', pnl) + pnl.history:Dock(FILL) + function pnl.history.PerformLayout(self) + self:SetFontInternal('octochat.default') + self:SetUnderlineFont('octochat.underlined') + end + function pnl.history:ActionSignal(name, val) + if name == 'TextClicked' then + octochat.close() + gui.ActivateGameUI() + octoesc.OpenURL(val) + end + end + + pnl.entry = vgui.Create('DTextEntry', pnl) + pnl.entry:Dock(BOTTOM) + pnl.entry:SetTall(30) + pnl.entry:SetFont('octochat.default') + pnl.entry:SetText('') + pnl.entry:SetTextColor(Color(220,220,220)) + pnl.entry:SetCursorColor(Color(220,220,220)) + pnl.entry:SetHighlightColor(Color(100,100,100)) + pnl.entry:SetDrawBorder(false) + pnl.entry:SetPaintBackground(false) + pnl.entry:SetDrawLanguageID(false) + pnl.entry:SetHistoryEnabled(true) + + pnl.entry.holdingKey = false + pnl.entry.Think = function(self) + if not octochat.isOpen then + if octochat.settings.openEnter and input.IsKeyDown(KEY_ENTER) and (octochat.lastEnter or 0) <= CurTime() - 0.5 and not IsValid(vgui.GetKeyboardFocus()) and not gui.IsGameUIVisible() then + octochat.open() + end + return + end + + if gui.IsGameUIVisible() then + gui.HideGameUI() + end + + if input.IsKeyDown(KEY_TAB) and self:HasFocus() then + self:RequestFocus() + end + + if input.IsKeyDown(KEY_F3) then + if self.holdingKey then return end + self.holdingKey = true + + local enable = not octochat.clickerOn + if not enable then octochat.clickerPos = { gui.MousePos() } end + gui.EnableScreenClicker(enable) + if enable then gui.SetMousePos(unpack(octochat.clickerPos or {0,0})) end + timer.Simple(0, function() pnl:SetMouseInputEnabled(enable) end) + octochat.clickerOn = enable + elseif input.IsKeyDown(KEY_F4) then + if self.holdingKey then return end + self.holdingKey = true + + octochat.openSettings() + elseif input.IsKeyDown(KEY_ESCAPE) then + if self.holdingKey then return end + self.holdingKey = true + + octochat.close() + gui.HideGameUI() + else + self.holdingKey = false + end + end + pnl.entry.OnEnter = function(self) + if octochat.selAC then + local ac = octochat.selAC + self:SetText(ac) + self:SetCaretPos(utf8.len(ac)) + self:OnTextChanged() + self:RequestFocus() + else + octochat.send() + end + end + pnl.entry.OnTextChanged = function(t, noMenuRemoval) + + t.HistoryPos = 0 + + local txt = t:GetText() + gamemode.Call('ChatTextChanged', txt) + t:OnValueChange(txt) + + if IsValid(t.Menu) and not noMenuRemoval then + t.Menu:Remove() + end + + t:OnChange() + if t.usingHistory then t.usingHistory = nil return end + + local words = string.Explode(' ', txt) + if not words and #words == 0 then return end + + local ac = {} + local cmd = words[1]:lower() + local fl = cmd:sub(1,1) + local ply = LocalPlayer() + if #words == 1 then + if fl == '!' or fl == '~' then + local commands = serverguard.command:GetTable() + for _, data in pairs(commands) do + local command = data.command:lower() + if (command and command ~= 'incognito' and (not data.permissions or serverguard.player:HasPermission(ply, data.permissions))) + and command:find(cmd:sub(2), 1, true) then + ac[#ac + 1] = fl .. command .. ' ' + end + end + end + if fl ~= '' and octochat then + for command in pairs(octochat.commands) do + if string.StartWith(command, cmd) and octochat.canExecuteCommand(ply, command, {}, '') then + ac[#ac + 1] = command .. ' ' + end + end + end + end + + if not ac[1] then + local lw = words[#words] + if lw == '' then return end + + for _, ply in ipairs(player.GetAll()) do + local name = ply:Name() + if utf8.lower(name):find(utf8.lower(lw), 1, true) then + -- table.insert(ac, txt:sub(1, txt:len() - lw:len()) .. name) + ac[#ac + 1] = utf8.sub(txt, 1, utf8.len(txt) - utf8.len(lw)) .. '"' .. name .. '" ' + end + end + end + + table.sort(ac) + + if ac[1] then + t.Menu = DermaMenu(t) + for _, v in ipairs(ac) do + t.Menu:AddOption(v, function() t:SetText(v) t:SetCaretPos(utf8.len(v)) t:RequestFocus() end) + end + + local x, y = t:LocalToScreen(0, 0) + t.Menu:SetMinimumWidth(t:GetWide()) + t.Menu:Open(x, y - t.Menu:GetTall(), true, t) + t.Menu:SetMaxHeight(pnl:GetTall() - 35) + t.Menu:SetPos(x, y - math.min(t.Menu:GetTall(), pnl:GetTall() - 35)) + t.Menu.OnRemove = function(m) + octochat.selAC = nil + end + end + + end + pnl.entry.OnKeyCode = function(self, code) + if code >= 1 and code <= 66 then + timer.Simple(0, function() + self.lastEdit = self:GetValue() + end) + end + end + pnl.entry.UpdateFromHistory = function(self) + self.usingHistory = true + + if IsValid(self.Menu) then + self:UpdateFromMenu() + self.usingHistory = nil + return + end + + local text + local pos = self.HistoryPos + -- is the pos within bounds? + if pos < 0 then pos = #self.History end + if pos > #self.History then pos = 0 end + + text = self.History[pos] or self.lastEdit or '' + self:SetText(text) + self:SetCaretPos(utf8.len(text)) + self:OnTextChanged() + self.HistoryPos = pos + + self.usingHistory = nil -- just in case + end + pnl.entry.UpdateFromMenu = function(t) + + local pos = t.HistoryPos + local num = t.Menu:ChildCount() + + t.Menu:ClearHighlights() + + if (pos < 1) then pos = num end + if (pos > num) then pos = 1 end + + local item = t.Menu:GetChild(pos) + if not item then + t:SetText('') + t.HistoryPos = pos + return + end + + t.Menu:HighlightItem(item) + t.Menu:ScrollToChild(item) + + local txt = item:GetText() + octochat.selAC = txt + + t.HistoryPos = pos + + end + local oldUpdateFromHistory = pnl.entry.UpdateFromHistory + pnl.entry.UpdateFromHistory = function(t) + + t.usingHistory = true + oldUpdateFromHistory(t) + t.usingHistory = nil -- just in case + + end + + -- a hack to make first message appear normally + pnl.history:AppendText('\n') + pnl.history:InsertFade(5, 2) + octochat.close() + +end + +function octochat.open() + + local pnl = octochat.pnl + pnl:SetKeyboardInputEnabled(true) + pnl:SetMouseInputEnabled(octochat.settings.autoCursor or GUIToggled) + pnl.entry:SetVisible(true) + pnl.entry:RequestFocus() + gamemode.Call('ChatTextChanged', pnl.entry:GetText()) + pnl.history:SetVerticalScrollbarEnabled(true) + pnl.history:ResetAllFades(true, true, -1) + + gamemode.Call('StartChat') + octochat.isOpen = true + octochat.lastEnter = CurTime() + +end + +function octochat.close() + + local pnl = octochat.pnl + pnl:SetKeyboardInputEnabled(false) + pnl:SetMouseInputEnabled(false) + pnl.history:SetVerticalScrollbarEnabled(false) + pnl.history:ResetAllFades(false, true, 0) + if not octochat.settings.showEntry then pnl.entry:SetVisible(false) end + if IsValid(pnl.entry.Menu) then pnl.entry.Menu:Remove() end + gamemode.Call('ChatTextChanged', '') + -- pnl.entry:SetText('') + + local pos, size = {pnl:GetPos()}, {pnl:GetSize()} + if pos[1] ~= octochat.settings.pos[1] or pos[2] ~= octochat.settings.pos[2] then octochat.settings.pos = pos octochat.save() end + if size[1] ~= octochat.settings.size[1] or size[2] ~= octochat.settings.size[2] then octochat.settings.size = size octochat.save() end + + gamemode.Call('FinishChat') + octochat.isOpen = false + octochat.lastEnter = CurTime() + +end + +function octochat.send() + + local pnl = octochat.pnl + local text = pnl.entry:GetText() + if string.Trim(text) == '' then + pnl.entry:SetText('') + pnl.entry:RequestFocus() + if octochat.settings.closeEmptySend and (octochat.lastEnter or 0) < CurTime() - 0.5 then + octochat.close() + end + return + end + + pnl.entry:AddHistory(text) + pnl.entry:SetText('') + pnl.entry:RequestFocus() + if octochat.settings.closeOnSend then octochat.close() end + + netstream.Start('chat', text, octochat.team) + +end + +function octochat.msg(...) + + if not IsValid(octochat.pnl) then octochat.create() end + + local pnl = octochat.pnl.history + local args = { ... } + + if octochat.settings.showTime then + pnl:InsertColorChange(200,200,200, 255) + pnl:AppendText(os.date(octochat.settings.showSeconds and '%H:%M:%S - ' or '%H:%M - ', os.time())) + pnl:InsertFade(octochat.settings.msgLength, octochat.settings.msgFade) + end + + if not IsColor(args[1]) then + table.insert(args, 1, Color(235,235,235)) + end + + local lastColor + for _, arg in pairs(args) do + if IsColor(arg) then + pnl:InsertColorChange(arg.r, arg.g, arg.b, 255) + lastColor = arg + elseif type(arg) == 'table' then + + if not octochat.settings.noLinks and octolib.string.isUrl(arg[1]) then + pnl:InsertClickableTextStart(arg[1]) + pnl:InsertColorChange(0, 130, 255, 255) + pnl:AppendText(arg[1]) + pnl:InsertFade(octochat.settings.msgLength, octochat.settings.msgFade) + pnl:InsertColorChange(lastColor.r, lastColor.g, lastColor.b, 255) + pnl:InsertClickableTextEnd() + else + pnl:AppendText(arg[1]) + pnl:InsertFade(octochat.settings.msgLength, octochat.settings.msgFade) + end + + elseif type(arg) == 'string' then + pnl:AppendText(arg) + pnl:InsertFade(octochat.settings.msgLength, octochat.settings.msgFade) + end + end + + pnl:AppendText('\n') + chat.PlaySound() + +end + +function octochat.load() + + local tCol = CFG.skinColors.bg + local defaultCol = Color(tCol.r, tCol.g, tCol.b, 235) + + if not file.Exists('octochat_settings.dat', 'DATA') then + file.Write('octochat_settings.dat', pon.encode({ + color = defaultCol, + pos = {20, ScrH() - 300 - 200}, + size = {600, 300}, + textSize = 18, + msgLength = 10, + msgFade = 3, + autoCursor = true, + showEntry = true, + closeOnSend = false, + showHint = true, + showTime = false, + showSeconds = false, + closeEmptySend = true, + openEnter = true, + shadow = true, + noLinks = false, + })) + end + + local succ, res = pcall(pon.decode, file.Read('octochat_settings.dat')) + if succ then + octochat.settings = { + color = res.color or defaultCol, + pos = res.pos or {20, ScrH() - 300 - 200}, + size = res.size or {600, 300}, + textSize = res.textSize or 18, + msgLength = res.msgLength or 10, + msgFade = res.msgFade or 3, + autoCursor = res.autoCursor == nil and true or res.autoCursor, + showEntry = res.showEntry == nil and true or res.showEntry, + closeOnSend = res.closeOnSend == nil and false or res.closeOnSend, + showHint = res.showHint == nil and true or res.showHint, + showTime = res.showTime == nil and false or res.showTime, + showSeconds = res.showSeconds == nil and false or res.showSeconds, + closeEmptySend = res.closeEmptySend == nil and true or res.closeEmptySend, + openEnter = res.openEnter == nil and true or res.openEnter, + shadow = res.shadow == nil and true or res.shadow, + } + else + file.Delete('octochat_settings.dat') + octochat.load() + end + +end + +function octochat.save() + + if not octochat.settings then octochat.load() end + file.Write('octochat_settings.dat', pon.encode(octochat.settings)) + +end + +function octochat.openSettings() + + if IsValid(octochat.settingsFrame) then octochat.settingsFrame:Remove() end + + local tCol = CFG.skinColors.bg + local defaultCol = Color(tCol.r, tCol.g, tCol.b, 235) + + local f = vgui.Create 'DFrame' + octochat.settingsFrame = f + + f:SetSize(350, 600) + f:SetTitle(L.chat_settings) + f:Center() + f:MakePopup() + + local e = {} + + e.c = f:Add 'DColorMixer' + e.c:Dock(TOP) + e.c:SetTall(150) + e.c:SetAlphaBar(true) + e.c:SetWangs(true) + e.c:SetPalette(false) + e.c.ValueChanged = function(c, col) + octochat.settings.color = col + end + + e.s1 = f:Add 'DNumSlider' + e.s1:Dock(TOP) + e.s1:DockMargin(8,10,0,0) + e.s1:SetText(L.display_time_sec) + e.s1:SetTall(30) + e.s1:SetMinMax(1, 60) + e.s1:SetDecimals(0) + e.s1.OnValueChanged = function(s, val) + local val = math.Clamp(val, 1, 60) + octochat.settings.msgLength = val + if e.s2:GetValue() > val then e.s2:SetValue(val) end + end + + e.s2 = f:Add 'DNumSlider' + e.s2:Dock(TOP) + e.s2:DockMargin(8,0,0,0) + e.s2:SetText(L.damping_time_sec) + e.s2:SetTall(30) + e.s2:SetMinMax(1, 15) + e.s2:SetDecimals(0) + e.s2.OnValueChanged = function(s, val) + local val = math.Clamp(val, 1, 15) + octochat.settings.msgFade = val + if e.s1:GetValue() < val then e.s1:SetValue(val) end + end + + e.s3 = f:Add 'DNumSlider' + e.s3:Dock(TOP) + e.s3:DockMargin(8,0,0,0) + e.s3:SetText(L.font_size) + e.s3:SetTall(30) + e.s3:SetMinMax(10, 24) + e.s3:SetDecimals(0) + e.s3.OnValueChanged = function(s, val) + octochat.settings.textSize = math.Clamp(val, 10, 24) + octochat.recreateFont() + end + + e.cb1 = f:Add 'DCheckBoxLabel' + e.cb1:Dock(TOP) + e.cb1:DockMargin(8,10,0,0) + e.cb1:SetTall(30) + e.cb1:SetText(L.use_bold) + e.cb1.OnChange = function(s, val) + octochat.settings.bold = val + octochat.recreateFont() + end + + e.cb2 = f:Add 'DCheckBoxLabel' + e.cb2:Dock(TOP) + e.cb2:DockMargin(8,10,0,0) + e.cb2:SetTall(30) + e.cb2:SetText(L.show_cursor_open) + e.cb2.OnChange = function(s, val) octochat.settings.autoCursor = val end + + e.cb3 = f:Add 'DCheckBoxLabel' + e.cb3:Dock(TOP) + e.cb3:DockMargin(8,10,0,0) + e.cb3:SetTall(30) + e.cb3:SetText(L.show_entering_text) + e.cb3.OnChange = function(s, val) octochat.settings.showEntry = val end + + e.cb4 = f:Add 'DCheckBoxLabel' + e.cb4:Dock(TOP) + e.cb4:DockMargin(8,10,0,0) + e.cb4:SetTall(30) + e.cb4:SetText(L.close_sent_msg) + e.cb4.OnChange = function(s, val) octochat.settings.closeOnSend = val end + + e.cb5 = f:Add 'DCheckBoxLabel' + e.cb5:Dock(TOP) + e.cb5:DockMargin(8,10,0,0) + e.cb5:SetTall(30) + e.cb5:SetText(L.show_help_manage) + e.cb5.OnChange = function(s, val) octochat.settings.showHint = val end + + e.cb6 = f:Add 'DCheckBoxLabel' + e.cb6:Dock(TOP) + e.cb6:DockMargin(8,10,0,0) + e.cb6:SetTall(30) + e.cb6:SetText(L.add_time_msg) + e.cb6.OnChange = function(s, val) octochat.settings.showTime = val end + + e.cb7 = f:Add 'DCheckBoxLabel' + e.cb7:Dock(TOP) + e.cb7:DockMargin(8,10,0,0) + e.cb7:SetTall(30) + e.cb7:SetText(L.close_chat_empty) + e.cb7.OnChange = function(s, val) octochat.settings.closeEmptySend = val end + + e.cb8 = f:Add 'DCheckBoxLabel' + e.cb8:Dock(TOP) + e.cb8:DockMargin(8,10,0,0) + e.cb8:SetTall(30) + e.cb8:SetText(L.show_seconds_in_time) + e.cb8.OnChange = function(s, val) octochat.settings.showSeconds = val end + + e.cb9 = f:Add 'DCheckBoxLabel' + e.cb9:Dock(TOP) + e.cb9:DockMargin(8,10,0,0) + e.cb9:SetTall(30) + e.cb9:SetText(L.enter_open_chat) + e.cb9.OnChange = function(s, val) octochat.settings.openEnter = val end + + e.cb10 = f:Add 'DCheckBoxLabel' + e.cb10:Dock(TOP) + e.cb10:DockMargin(8,10,0,0) + e.cb10:SetTall(30) + e.cb10:SetText(L.add_text_shadow) + e.cb10.OnChange = function(s, val) octochat.settings.shadow = val octochat.recreateFont() end + + e.cb11 = f:Add 'DCheckBoxLabel' + e.cb11:Dock(TOP) + e.cb11:DockMargin(8,10,0,0) + e.cb11:SetTall(30) + e.cb11:SetText('Не отображать кликабельные ссылки') + e.cb11.OnChange = function(s, val) octochat.settings.noLinks = val end + + e.c:SetColor(octochat.settings.color) + e.s1:SetValue(octochat.settings.msgLength) + e.s2:SetValue(octochat.settings.msgFade) + e.s3:SetValue(octochat.settings.textSize) + e.cb1:SetValue(octochat.settings.bold) + e.cb2:SetValue(octochat.settings.autoCursor) + e.cb3:SetValue(octochat.settings.showEntry) + e.cb4:SetValue(octochat.settings.closeOnSend) + e.cb5:SetValue(octochat.settings.showHint) + e.cb6:SetValue(octochat.settings.showTime) + e.cb8:SetValue(octochat.settings.showSeconds) + e.cb7:SetValue(octochat.settings.closeEmptySend) + e.cb9:SetValue(octochat.settings.openEnter) + e.cb10:SetValue(octochat.settings.shadow) + e.cb11:SetValue(octochat.settings.noLinks) + + local reset = f:Add 'DButton' + reset:SetText(L.reset_chat) + reset:SetSize(100, 30) + reset:AlignBottom(8) + reset:AlignRight(8) + function reset:DoClick() + e.c:SetColor(defaultCol) + e.s1:SetValue(10) + e.s2:SetValue(3) + e.s3:SetValue(18) + e.cb1:SetValue(false) + e.cb2:SetValue(true) + e.cb3:SetValue(true) + e.cb4:SetValue(false) + e.cb5:SetValue(true) + e.cb6:SetValue(false) + e.cb7:SetValue(true) + e.cb8:SetValue(false) + e.cb9:SetValue(true) + e.cb10:SetValue(true) + e.cb11:SetValue(true) + end + + function f:OnClose() + octochat.save() + end + +end + +hook.Add('Think', 'octochat.create', function() + + hook.Remove('Think', 'octochat.create') + octochat.create() + +end) + +hook.Add('StartChat', 'crim-compat', function(t) + + netstream.Start('isTyping', true) + +end) + +hook.Add('FinishChat', 'crim-compat', function() + + netstream.Start('isTyping', false) + +end) + +octolib.registerBindHandler('chat', { + name = 'Отправить в чат', + run = function(data) + netstream.Start('chat', data) + end, + buildBinder = function(cont, bindID, bind) + cont:SetTall(55) + + local e = octolib.textEntry(cont) + e:Dock(FILL) + e:DockMargin(5, 5, 5, 5) + e:SetMultiline(true) + e:SetPlaceholderText('Текст сообщения') + e.PaintOffset = 5 + e:SetDrawLanguageID(false) + e:SetValue(bind.data or '') + + local onChange = function(self) + octolib.setBind(bindID, bind.button, bind.action, self:GetValue(), bind.on) + end + e.OnValueChange = onChange + e.OnLoseFocus = onChange + end, +}) diff --git a/garrysmod/addons/core-octochat/lua/octochat/commands/client.lua b/garrysmod/addons/core-octochat/lua/octochat/commands/client.lua new file mode 100644 index 0000000..4d53007 --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/commands/client.lua @@ -0,0 +1,26 @@ +octochat.commands = octochat.commands or {} +octochat.permissions = octochat.permissions or {} + +local function addPermission(perm) + if not serverguard then octochat.permissions[#octochat.permissions + 1] = perm + else serverguard.permission:Add(perm) end +end +hook.Add('Think', 'octochat.loadPerms', function() +hook.Remove('Think', 'octochat.loadPerms') +for _, v in ipairs(octochat.permissions) do + serverguard.permission:Add(v) +end +octochat.permissions = {} +end) + +function octochat.defineCommand(name, data) + octochat.commands[name] = data + if istable(data) then data.name = name end + for _, v in ipairs(istable(data) and data.aliases or {}) do + octochat.commands[v] = data + end + if not istable(data) then return end + if data.permission then addPermission(data.permission) end + if data.cooldownBypass then addPermission(data.cooldownBypass) end +end +hook.Run('octochat.registerCommands') diff --git a/garrysmod/addons/core-octochat/lua/octochat/commands/server.lua b/garrysmod/addons/core-octochat/lua/octochat/commands/server.lua new file mode 100644 index 0000000..bd4ec94 --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/commands/server.lua @@ -0,0 +1,181 @@ +octochat.commands = octochat.commands or {} +octochat.permissions = octochat.permissions or {} + +function octochat.genericSayFunc(ply, txt, radius, action) + + local console = not IsValid(ply) + + if txt == '' then return end + if not console and (ply.nextSay or 0) > os.time() then return end + + local prepostfix = '' + if txt:find(')', 1, true) and not txt:find('(', 1, true) then + txt = txt:gsub('[%):=]', '') + if string.Trim(txt) == '' then + if console then return octochat.talkTo(nil, octochat.textColors.rp, L.smile_hint % { name = L.console }) end + return ply:DoEmote(L.smile_hint) + end + prepostfix = L.postfix_smile + elseif txt:find('(', 1, true) and not txt:find(')', 1, true) then + txt = txt:gsub('[%(:=]', '') + if string.Trim(txt) == '' then + if console then return octochat.talkTo(nil, octochat.textColors.rp, L.sad_hint % { name = L.console }) end + return ply:DoEmote(L.sad_hint) + end + prepostfix = L.postfix_sad + end + local postfix = action or (txt:sub(-1) == '?' and L.postfix_question or L.postfix_say) + + if console then + return octochat.talkTo(nil, octochat.textColors.rp, L.console, prepostfix, postfix, color_white, txt) + end + + local color = hook.Run("GetPlayerChatColor", ply) or octochat.textColors.rp + local heard = ply:TalkToRange(radius or 150, color, ply:Name(), prepostfix, postfix, color_white, txt) + ply.nextSay = os.time() + 1 + + if ply:GetNetVar('os_govorilka') and not ply:IsGovorilkaMuted() then + ply:DoVoice(txt, ply:GetVoice(), heard) + end + +end + +local function addPermission(perm) + if not serverguard then octochat.permissions[#octochat.permissions + 1] = perm + else serverguard.permission:Add(perm) end +end +hook.Add('Think', 'octochat.loadPerms', function() +hook.Remove('Think', 'octochat.loadPerms') +for _, v in ipairs(octochat.permissions) do + serverguard.permission:Add(v) +end +octochat.permissions = {} +end) + +function octochat.registerCommand(name, data) + name = string.lower(name) + + local toRem = {} + if octochat.commands[name] then + for _,v in ipairs(octochat.commands[name].aliases or {}) do + octochat.commands[v] = nil + toRem[#toRem + 1] = v + end + octochat.commands[name] = nil + toRem[#toRem + 1] = name + end + + if not istable(data) then return end + local toAdd = {} + data.name = name + toAdd[1] = name + if data.cooldown then data._cooldowns = {} end + octochat.commands[name] = data + if data.aliases then + for _,v in ipairs(data.aliases) do + octochat.commands[v] = data + toAdd[#toAdd + 1] = v + end + end + if data.permission then addPermission(data.permission) end + if data.cooldownBypass then addPermission(data.cooldownBypass) end +end + +function octochat.pickOutTarget(args) + + args = table.Copy(args) + if not args[1] or args[1] == '' then return false end + + local target = octochat.findPlayer(args[1]) + if not target then return false, L.could_not_find:format(tostring(args[1])) end + + table.remove(args, 1) + local txt = string.Trim(string.Implode(' ', args)) + + return target, txt +end + +hook.Add('PlayerSay', 'octochat.commands', function(ply, _txt) + + if game.IsDedicated() then + ServerLog('"' .. ply:Nick() .. '<' .. ply:UserID() .. '>' .. '<' .. ply:SteamID() .. '>' .. '<' .. team.GetName(ply:Team()) .. '>" say "' .. _txt .. '"\n') + end + + local args = octochat.explodeArg(_txt) + local alias = table.remove(args, 1) + if not alias then return end + alias = string.lower(alias) + local data = octochat.commands[alias] + if not data then return end + + local txt = string.Trim(string.Implode(' ', args)) + + local sid = ply:SteamID() + local bypassesCooldown = isfunction(data.cooldownBypass) and data.cooldownBypass(ply, txt, args, alias, data.name) + bypassesCooldown = bypassesCooldown or isstring(data.cooldownBypass) and ply:query(data.cooldownBypass) + if data.cooldown and data._cooldowns[sid] and not bypassesCooldown then + local timeLeft = math.max(1, math.ceil(data._cooldowns[sid] - os.time())) + local msg = ('Ты сможешь выполнить эту команду %s'):format(octolib.time.formatIn(timeLeft)) + ply:Notify('warning', msg) + return '' + end + + local can, why = octochat.canExecuteCommand(ply, alias, args, txt) + if not can then + hook.Run('octochat.commandExecuted', ply, _txt, data, false, why) + ply:Notify('warning', why) + return '' + end + + local result = data.execute(ply, txt, args, alias) + if result == nil or result == true then + hook.Run('octochat.commandExecuted', ply, _txt, data, true) + if not data.cooldown or bypassesCooldown then return '' end + data._cooldowns[sid] = os.time() + data.cooldown + timer.Create('octochat.cooldowns' .. sid .. data.name, data.cooldown, 1, function() + data._cooldowns[sid] = nil + end) + else + result = isstring(result) and result or 'Команда выполнена неправильно' + hook.Run('octochat.commandExecuted', ply, _txt, data, false, result) + ply:Notify('warning', unpack(octolib.string.splitByUrl(result))) + end + + return '' + +end, -5) + +hook.Add('ConsoleSay', 'octochat.commands', function(_txt) + + if game.IsDedicated() then + ServerLog('"Console" say "' .. _txt .. '"\n') + end + + local args = octochat.explodeArg(_txt) + local alias = table.remove(args, 1) + if not alias then return end + alias = string.lower(alias) + local data = octochat.commands[alias] + if not data then return end + + local txt = string.Trim(string.Implode(' ', args)) + + if not data.consoleFriendly then + octolib.msg('Эту команду могут выполнить только игроки!') + return '' + end + + local result = data.execute(nil, txt, args, alias) + if result == nil or result == true then + hook.Run('octochat.commandExecuted', nil, _txt, data, true) + else + result = isstring(result) and result or 'Команда выполнена неправильно' + hook.Run('octochat.commandExecuted', nil, _txt, data, false, result) + octolib.msg(result) + end + + return '' + +end, -5) + +hook.Run('octochat.registerCommands') diff --git a/garrysmod/addons/core-octochat/lua/octochat/commands/shared.lua b/garrysmod/addons/core-octochat/lua/octochat/commands/shared.lua new file mode 100644 index 0000000..289b33f --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/commands/shared.lua @@ -0,0 +1,24 @@ +function octochat.canExecuteCommand(ply, alias, args, txt) + local data = octochat.commands[alias] + if data == nil then + return true + end + + local can, why = true + if istable(data) then + if isstring(data.permission) then + can, why = ply:query(data.permission) + end + if isfunction(data.check) then + can, why = data.check(ply, txt, args, alias, data.name) + end + if can == false then return false, why or 'Ты не можешь выполнять эту команду' end + end + + can, why = hook.Run('octochat.canExecute', ply, istable(data) and data.name or alias, txt, args, alias) + if can == false then + return false, why or 'Ты не можешь выполнять эту команду' + end + + return true +end diff --git a/garrysmod/addons/core-octochat/lua/octochat/server.lua b/garrysmod/addons/core-octochat/lua/octochat/server.lua new file mode 100644 index 0000000..9ee0739 --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/server.lua @@ -0,0 +1,207 @@ +netstream.Hook('isTyping', function(ply, is) + + ply:SetNetVar('IsTyping', tobool(is) or nil) + +end) + +function octochat.explodeArg(arg) + local args = {} + + local from, to, diff = 1, 0, 0 + local inQuotes, wasQuotes = false, false + + for c in arg:gmatch('.') do + to = to + 1 + + if c == '"' then + inQuotes = not inQuotes + wasQuotes = true + + continue + end + + if c == ' ' and not inQuotes then + diff = wasQuotes and 1 or 0 + wasQuotes = false + args[#args + 1] = arg:sub(from + diff, to - 1 - diff) + from = to + 1 + end + end + diff = wasQuotes and 1 or 0 + + if from ~= to + 1 then args[#args + 1] = arg:sub(from + diff, to + 1 - bit.lshift(diff, 1)) end + + return args +end + +function octochat.findPlayer(info) + if not info or info == '' then return end + info = tostring(info) + local lower = utf8.lower(info) + local numInfo = tonumber(info) + + for _,v in ipairs(player.GetAll()) do + + if v:UserID() == numInfo then + return v + end + + if info == v:SteamID() then + return v + end + + if string.find(utf8.lower(v:SteamName()), lower, 1, true) ~= nil then + return v + end + + if string.find(utf8.lower(v:Name()), lower, 1, true) ~= nil then + return v + end + end + +end + +-- "say" binds handling +hook.Add('PlayerSay', 'octochat.catchDefault', function(ply, txt, t) + if ply.handledSay then return end + ply:Say(txt, t) + return '' +end) + +local ply = FindMetaTable 'Player' +function ply:ChatPrint(txt) + + netstream.Start(self, 'chat', txt) + +end + +function ply:Say(txt, t) + + if not self:TriggerCooldown('octochat.say', 1) then return end + + txt = utf8.sub(string.Trim(tostring(txt)), 1, 1024) + self.handledSay = true + txt = hook.Run('PlayerSay', self, txt, tobool(t)) or txt + self.handledSay = false + if txt == '' then return end + + if not isfunction(octochat.genericSayFunc) then return end + octochat.genericSayFunc(self, txt) + hook.Run('PostPlayerSay', self, txt) + +end +netstream.Hook('chat', ply.Say) + +concommand.Add('octochat_say', function(ply, cmd, args, argStr) + if IsValid(ply) then return ply:Say(argStr) end + txt = hook.Run('ConsoleSay', argStr) or argStr + if txt == '' then return end + + if not isfunction(octochat.genericSayFunc) then return end + octochat.genericSayFunc(nil, txt) + hook.Run('PostConsoleSay', nil, txt) +end) + +concommand.Add('octochat_help', function(ply) + if IsValid(ply) then return ply:PrintMessage(HUD_PRINTTALK, 'Эта команда доступна только консоли') end + octolib.msg('Список команд, доступных с консоли:') + for k, v in pairs(octochat.commands) do + if v.consoleFriendly then octolib.msg(k) end + end +end) + +ply.OldPrintMessage = ply.OldPrintMessage or ply.PrintMessage +function ply:PrintMessage(type, str) + if type == HUD_PRINTTALK then + netstream.Start(self, 'chat', str) + else + self:OldPrintMessage(type, str) + end +end + +local OldPrintMessage = PrintMessage +function PrintMessage(type, str) + if type == HUD_PRINTTALK then + netstream.Start(nil, 'chat', str) + else + OldPrintMessage(type, str) + end +end + +function octochat.safeNotify(ply, channel, msg) + if IsValid(ply) then ply:Notify(channel, msg) + else octolib.msg(msg or channel) end +end + +function octochat.safePlayerName(ply) + return IsValid(ply) and ply:Name() or L.console +end + +local function genericFilterFunc(pos, range) + return function(ent) + if not IsValid(ent) or not ent:IsPlayer() then return false end + + if ent.FSpectating then + if ent.FSpectatePos then + return ent.FSpectatePos:DistToSqr(pos) <= range + elseif IsValid(ent.FSpectatingEnt) then + return ent.FSpectatingEnt:GetPos():DistToSqr(pos) <= range + end + end + + return ent:GetPos():DistToSqr(pos) <= range + end +end + +local function ghostFilterFunc(pos, range) + return function(ent) + if not IsValid(ent) or not ent:IsPlayer() then return false end + + if ent.FSpectating then + if ent.FSpectatePos then + return ent.FSpectatePos:DistToSqr(pos) <= range + elseif IsValid(ent.FSpectatingEnt) then + return ent.FSpectatingEnt:GetPos():DistToSqr(pos) <= range + end + end + + return ent:GetPos():DistToSqr(pos) <= range and (ent:IsGhost() or ent:IsAdmin()) + end +end + +function octochat.talkTo(recipients, ...) + if not istable(recipients) then + if isentity(recipients) then + recipients = {recipients} + elseif recipients then return ErrorNoHalt('Invalid recipients list') end + end + netstream.Start(recipients, 'octochat.addEntry', unpack{...}) + return recipients +end + +function octochat.talkToAdmins(...) + octochat.talkTo(octolib.players.getAdmins(), ...) +end + +function octochat.talkToRange(pos, range, ...) + if not isvector(pos) then + if not isentity(pos) then return ErrorNoHalt('Expected position or entity') end + pos = pos:GetPos() + end + + return octochat.talkTo(octolib.array.filter(player.GetAll(), genericFilterFunc(pos, range * range)), ...) +end + +function ply:TalkToRange(range, ...) + local heard + if not self:IsAlive() or self:IsGhost() then + heard = octochat.talkTo(octolib.array.filter(player.GetAll(), ghostFilterFunc(self:GetPos(), range * range)), ...) + else + heard = octochat.talkToRange(self, range, ...) + end + return heard +end + +function ply:DoEmote(emote) + return self:TalkToRange(250, octochat.textColors.rp, emote % {name = self:Name()}) +end diff --git a/garrysmod/addons/core-octochat/lua/octochat/shared.lua b/garrysmod/addons/core-octochat/lua/octochat/shared.lua new file mode 100644 index 0000000..0700834 --- /dev/null +++ b/garrysmod/addons/core-octochat/lua/octochat/shared.lua @@ -0,0 +1,20 @@ +octochat.notifyColors = { + rp = Color(255, 220, 100), + warning = Color(214, 74, 65), + ooc = Color(43, 123, 167), + hint = Color(16, 140, 73), +} + +octochat.textColors = { + rp = Color(255,220,83), + ooc = Color(42,123,167), +} + + +local meta = FindMetaTable 'Player' + +function meta:IsTyping() + + return self:GetNetVar('IsTyping', false) + +end diff --git a/garrysmod/addons/core-octogroups/lua/autorun/octogroups.lua b/garrysmod/addons/core-octogroups/lua/autorun/octogroups.lua new file mode 100644 index 0000000..77cd888 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/autorun/octogroups.lua @@ -0,0 +1 @@ +-- octolib.shared('octogroups/shared') diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/cl_main.lua b/garrysmod/addons/core-octogroups/lua/octogroups/cl_main.lua new file mode 100644 index 0000000..a5539b2 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/cl_main.lua @@ -0,0 +1,79 @@ +og.groupsOwn = og.groupsOwn or {} + +--[[ API: returns whether local player has perm in group ]] +function og.hasPerm(gID, perm) + + local g = og.groupsOwn[gID] + if not g then return false end + + local m = g.members[LocalPlayer():SteamID()] + if not m then return false end + + if m.rank == 'owner' then return true end + + local r = g.ranks[m.rank] + if not r or not r.perms then return false end + + if table.HasValue(r.perms, perm) then return true end + + return false + +end + +--[[ API: returns user order by rank ]] +function og.getOrder(gID, sID) + + local g = og.groupsOwn[gID] + if not g then return 0 end + + local m = g.members[sID] + if not m or m.rank == 'member' then return 0 end + if m.rank == 'owner' then return 101 end + + local r = g.ranks[m.rank] + return r and r.order or 0 + +end + +--[[ API: Add/withdraw money ]] +function og.addMoney(gID, val) + + netstream.Start('og.addedMoney', gID, val) + +end + +--[[ API: Edit rank, pass nil to data to delete ]] +function og.editRank(gID, rID, data) + + netstream.Start('og.editRank', gID, rID, data) + +end + +-- +-- NET HOOKS +-- + +netstream.Hook('og.syncOwn', function(data) + + local sID = LocalPlayer():SteamID() + for gID, g in pairs(data) do + local m = g.members[sID] + g.myRank = m.rank or 'member' + end + + og.groupsOwn = data + hook.Run('octogroups.syncedOwn') + +end) + +netstream.Hook('og.sync', function(gID, data) + + if gID then + og.groups[gID] = data + else + og.groups = data + end + + hook.Run('octogroups.synced', gID) + +end) \ No newline at end of file diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/cl_panel.lua b/garrysmod/addons/core-octogroups/lua/octogroups/cl_panel.lua new file mode 100644 index 0000000..e559638 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/cl_panel.lua @@ -0,0 +1,117 @@ +og.tabBuilds = og.tabBuilds or {} + +surface.CreateFont('og.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local pnl = nil +local curGID, curTab = -1, 'Новости' + +local function setupGroup(g) + + if not IsValid(pnl) then return end + if IsValid(pnl.ps) then pnl.ps:Remove() end + + local ps = pnl:Add 'DPropertySheet' + ps:Dock(FILL) + pnl.ps = ps + + for i, tab in ipairs({ + 'news', + 'members', + 'storage', + 'ranks', + 'settings', + }) do + local build = og.tabBuilds[tab] + if build then build(ps, g) end + end + + ps.OnActiveTabChanged = function(self, old, new) curTab = new:GetText() end + ps:SwitchToName(curTab) + + -- octolib.fStringRequest('Снять деньги', 'Укажи сумму (деньги придут тебе в банк):', '0', function(val) + -- og.addMoney(gID, tonumber('-' .. val)) + -- end, nil, 'Снять', 'Отмена') + +end + +local function bClick(self) + local g = og.groupsOwn[self.gID or -1] + if g then + setupGroup(g) + curGID = self.gID + end +end + +local colors = CFG.skinColors +local function bPaint(self, w, h) + local selected = curGID == self.gID + + if selected then draw.RoundedBox(0, 0, 0, w, h, colors.bg) end + surface.SetDrawColor(255,255,255, (self.Hovered or selected) and 255 or 150) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect((w - 64) / 2, (h - 64) / 2, 64, 64) +end + +local function pnlUpdate() + + if not IsValid(pnl) then return end + + local m = pnl.menu + m:Clear() + + for gID, g in pairs(og.groupsOwn) do + if not og.groupsOwn[curGID] then curGID = gID end + + local b = m:Add 'DButton' + b:Dock(TOP) + b:SetTall(70) + b:SetText('') + b.icon = Material(g.icon or octolib.icons.color('group3')) + b.gID = gID + b.DoClick = bClick + b.Paint = bPaint + b:SetTooltip(g.name or 'Group') + if curGID == gID then b:DoClick() end + end + +end +hook.Add('octogroups.syncedOwn', 'octogroups.gui', pnlUpdate) + +local function pnlBuild(f) + + pnl = f + + f:SetSize(600, 500) + f:DockPadding(0, 20, 0, 0) + + local m = f:Add 'DScrollPanel' + m:DockMargin(0, 4, 0, 0) + m:Dock(LEFT) + m:SetWide(70) + m.pnlCanvas:DockPadding(0, 16, 0, 0) + + local col = Color(0,0,0, 80) + function m:Paint(w, h) draw.RoundedBoxEx(4, 0, 0, w, h, col, false, false, true, false) end + f.menu = m + + pnlUpdate() + +end + +hook.Add('octogui.f4-tabs', 'octogroups', function() + + octogui.addToF4 { + id = 'groups', + order = 11.5, + name = 'Организации', + icon = Material('octoteam/icons/buildings3.png'), + build = pnlBuild, + } + +end) +octogui.reloadF4() diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/groups/gov/group.lua b/garrysmod/addons/core-octogroups/lua/octogroups/groups/gov/group.lua new file mode 100644 index 0000000..a43fcdf --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/groups/gov/group.lua @@ -0,0 +1 @@ +octolib.server('sv_group') diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/groups/gov/sv_group.lua b/garrysmod/addons/core-octogroups/lua/octogroups/groups/gov/sv_group.lua new file mode 100644 index 0000000..dd4d84b --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/groups/gov/sv_group.lua @@ -0,0 +1,5 @@ +og.predefine({ + id = 'gov', + name = 'Правительство', + icon = octolib.icons.color('buildings3'), +}) diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/groups/police/group.lua b/garrysmod/addons/core-octogroups/lua/octogroups/groups/police/group.lua new file mode 100644 index 0000000..a43fcdf --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/groups/police/group.lua @@ -0,0 +1 @@ +octolib.server('sv_group') diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/groups/police/sv_group.lua b/garrysmod/addons/core-octogroups/lua/octogroups/groups/police/sv_group.lua new file mode 100644 index 0000000..3368212 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/groups/police/sv_group.lua @@ -0,0 +1,5 @@ +og.predefine({ + id = 'police', + name = L.police, + icon = octolib.icons.color('police_station'), +}) diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/shared.lua b/garrysmod/addons/core-octogroups/lua/octogroups/shared.lua new file mode 100644 index 0000000..f57f18f --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/shared.lua @@ -0,0 +1,30 @@ +--[[ + OCTOGROUPS + by chelog + + code notes: + sID = SteamID, gID = GroupID +]] + +og = og or {} +og.groups = og.groups or {} + +octolib.server('sv_data') +octolib.server('sv_group') +octolib.server('sv_rank') +octolib.server('sv_member') +octolib.server('sv_controls') +octolib.server('sv_player') + +octolib.client('cl_main') +octolib.client('cl_panel') + +local tab, _ = file.Find('octogroups/tabs/*.lua', 'LUA') +for i, t in ipairs(tab) do + octolib.client('octogroups/tabs/' .. t:sub(1, -5)) +end + +local _, groups = file.Find('octogroups/groups/*', 'LUA') +for i, g in ipairs(groups) do + octolib.shared('octogroups/groups/' .. g .. '/group') +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/sv_controls.lua b/garrysmod/addons/core-octogroups/lua/octogroups/sv_controls.lua new file mode 100644 index 0000000..2353458 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/sv_controls.lua @@ -0,0 +1,85 @@ +netstream.Hook('og.setMember', function(ply, gID, sID) + + if not (gID and sID) then return end + if not og.hasPerm(gID, ply:SteamID(), 'setMember') then return end + + local g = og.groups[gID] + local tgt = player.GetBySteamID(sID) + if not g or not IsValid(tgt) or g.members[sID] then return end + + ply:Notify('%s получил твое приглашение', tgt:Name()) + + octolib.request.send(tgt, {{ + name = 'Приглашение', + desc = ('Ты получил приглашение в группу %s от игрока %s. Нажми Отправить, чтобы согласиться, или закрой окно, чтобы отказаться'):format(g.name, ply:Name()), + }}, function() + og.setMember(gID, sID, 'member') + if IsValid(ply) then ply:Notify('%s принял твое приглашение', tgt:Name()) end + end, function() + if IsValid(ply) then ply:Notify('%s отклонил твое приглашение', tgt:Name()) end + end) + +end) + +netstream.Hook('og.setRank', function(ply, gID, sID, rank) + + if not (gID and sID) then return end + if not og.hasPerm(gID, ply:SteamID(), 'setRank') then return end + + local g = og.groups[gID] + if not g then return end + + local m = g.members[sID] + if not m then return end + + local r = g.ranks[rank or ''] + local my, his, new = og.getOrder(gID, ply:SteamID()), og.getOrder(gID, sID), r and r.order or -1 + if my <= his or my <= new then return end + + og.setMember(gID, sID, rank) + +end) + +netstream.Hook('og.setSetting', function(ply, gID, name, val) + + if not (gID and name and val) then return end + if not og.hasPerm(gID, ply:SteamID(), 'setSetting') then return end + + og.setSetting(gID, name, val) + +end) + +netstream.Hook('og.editRank', function(ply, gID, rank, data) + + if not (gID and rank) then return end + if not og.hasPerm(gID, ply:SteamID(), 'editRank') then return end + + og.editRank(gID, rank, data) + +end) + +netstream.Hook('og.addedMoney', function(ply, gID, val) + + if not (gID and val) then return end + if val > 0 then + if not ply:BankHas(val) then + ply:Notify('warning', 'У тебя в банке не хватает денег') + return + end + + ply:BankAdd(-val) + og.addMoney(gID, val) + ply:Notify(('Ты внес %s в группу'):format(DarkRP.formatMoney(val))) + elseif og.hasPerm(gID, ply:SteamID(), 'useMoney') then + val = -val + if not og.hasMoney(gID, val) then + ply:Notify('warning', 'В банке группы не хватает денег') + return + end + + og.addMoney(gID, -val) + ply:BankAdd(val) + ply:Notify(('Ты снял %s со счета группы'):format(DarkRP.formatMoney(val))) + end + +end) diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/sv_data.lua b/garrysmod/addons/core-octogroups/lua/octogroups/sv_data.lua new file mode 100644 index 0000000..bb182ad --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/sv_data.lua @@ -0,0 +1,100 @@ +-- initialize function +function og.init() + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS octogroups ( + id VARCHAR(32) NOT NULL, + data TEXT, + PRIMARY KEY (id) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]], og.reloadFromDB) + +end +hook.Add('octolib.db.init', 'octogroups', og.init) + +-- +-- DATA +-- + +local function save(gID) + + local g = og.groups[gID] + if not g then return end + + local data = table.Copy(g) + data.id = nil + + -- save to DB + octolib.db:PrepareQuery('update octogroups set data = ? where id = ?', { pon.encode(data), gID }) + + -- send to clients + netstream.Start(nil, 'og.sync', gID, og.publicData(g)) + + for sID, m in pairs(g.members) do + local ply = player.GetBySteamID(sID) + if IsValid(ply) then + og.reloadPlayer(ply) + end + end + +end + +-- save in think so we debounce it to only once per frame +local toSave = {} +hook.Add('Think', 'octogroups-save', function() + + for gID, _ in pairs(toSave) do + save(gID) + toSave[gID] = nil + end + +end) + +--[[ API: save group data to database ]] +function og.save(gID) + + toSave[gID] = true + +end + +local loaded = false +--[[ API: force groups to reload cache from database ]] +function og.reloadFromDB(callback) + + octolib.db:RunQuery('select * from octogroups', function(q, st, res) + table.Empty(og.groups) + + for i, row in ipairs(res) do + local g = pon.decode(row.data) + g.id = row.id + og.importGroup(g) + end + + for i, ply in ipairs(player.GetAll()) do + og.reloadPlayer(ply) + end + + if not loaded then + loaded = true + hook.Run('og.init') + end + + if isfunction(callback) then callback() end + end) + +end + +-- +-- HOOKS +-- + +hook.Add('PlayerFinishedLoading', 'og.syncInitial', function(ply) + + local toSend = {} + for gID, g in pairs(og.groups) do + toSend[gID] = og.publicData(g) + end + + netstream.Start(ply, 'og.sync', nil, toSend) + +end) diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/sv_group.lua b/garrysmod/addons/core-octogroups/lua/octogroups/sv_group.lua new file mode 100644 index 0000000..5330f02 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/sv_group.lua @@ -0,0 +1,167 @@ +--[[ API: populate fields and create missing in group data table ]] +function og.prepareGroupData(g) + + g.name = g.name or 'New Group' + g.members = g.members or {} + g.ranks = g.ranks or {} + g.settings = g.settings or {} + g.inv = g.inv or {} + g.bank = g.bank or 0 + +end + +--[[ API: get public fields from group table to send to clients ]] +function og.publicData(g) + + return { + id = g.id or -1, + name = g.name or 'New Group', + } + +end + +--[[ API: load group data to cache ]] +function og.importGroup(g) + + if not g.id then return end + + og.prepareGroupData(g) + og.groups[g.id] = g + +end + +--[[ API: create a group ]] +function og.create(g) + + if not g.id then return error('Trying to create group with no ID') end + if og.groups[g.id] then return error('Group already exists') end + + local data = table.Copy(g) + og.prepareGroupData(data) + data.id = nil + + octolib.db:PrepareQuery('insert into octogroups(id, data) values(?, ?)', { g.id, pon.encode(data) }, function() + og.reloadFromDB(function() + hook.Run('og.created', gID) + end) + end) + +end + +--[[ API: permanently delete group ]] +function og.delete(gID) + + local g = og.groups[gID] + if not g or g.predefined then return end + + octolib.db:PrepareQuery('delete from octogroups where id = ?', { gID }, function() + og.reloadFromDB(function() + hook.Run('og.deleted', gID) + end) + end) + +end + +--[[ API: change setting of the group ]] +function og.setSetting(gID, name, val) + + local g = og.groups[gID] + if not g then return end + + g.settings = g.settings or {} + g.settings[name] = val + + og.save(gID) + + hook.Run('og.setSetting', gID, name, val) + +end + +--[[ API: add money to group's bank ]] +function og.addMoney(gID, val) + + local g = og.groups[gID] + if not g then return end + + g.bank = (g.bank or 0) + val + og.save(gID) + + hook.Run('og.addedMoney', gID, val) + +end + +--[[ API: check if group has enough money in bank ]] +function og.hasMoney(gID, val) + + local g = og.groups[gID] + if not g then return end + + return (g.bank or 0) >= val + +end + +--[[ API: load group's inv into entity ]] +function og.loadInv(gID, ent) + + local g = og.groups[gID] + if not g or not IsValid(ent) then return end + + if g.inv then + ent:ImportInventory(g.inv) + end + + hook.Run('og.loadedInv', gID, ent) + +end + +--[[ API: save group's inv from entity ]] +function og.saveInv(gID, ent) + + local g = og.groups[gID] + if not g or not IsValid(ent) or not ent.inv then return end + + g.inv = ent:ExportInventory() + og.save(gID) + +end + +-- -- force a group to reload its members and link to player instances +-- function og.setupGroupPlayers(gID) + +-- local g = og.groups[gID] +-- if not g then return end + +-- for sID, m in pairs(g.members) do +-- local ply = player.GetBySteamID(sID) +-- if IsValid(ply) then +-- og.setupPlayer(ply, gID, m) +-- end +-- end + +-- end + +local predefineCache = {} +--[[ API: predefine group to ensure it always exists ]] +function og.predefine(g) + + if not g.id then return end + g.predefined = true + predefineCache[g.id] = g + +end + +hook.Add('og.init', 'og.predefine', function() + + for gID, groupData in pairs(predefineCache) do + local g = og.groups[gID] + if not g then + -- create a new group + og.create(groupData) + else + -- update existing group with default values + for k, v in pairs(groupData) do g[k] = v end + og.save(gID) + end + end + +end) diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/sv_member.lua b/garrysmod/addons/core-octogroups/lua/octogroups/sv_member.lua new file mode 100644 index 0000000..3510e2b --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/sv_member.lua @@ -0,0 +1,103 @@ +--[[ API: link group member data to player instance ]] +function og.setupPlayer(ply, gID, m) + + ply.og = ply.og or {} + ply.og[gID] = {og.groups[gID], m} + +end + +--[[ API: search groups in cache and link all data to player instance ]] +function og.reloadPlayer(ply) + + ply.og = {} + + local sID = ply:SteamID() + for gID, g in pairs(og.groups) do + local m = g.members[sID] + if m then og.setupPlayer(ply, gID, m) end + end + + local toSend = {} + for gID, gData in pairs(ply.og) do + toSend[gID] = gData[1] + end + netstream.Start(ply, 'og.syncOwn', toSend) + + hook.Run('og.reloadedPlayer', ply) + +end +hook.Add('PlayerFinishedLoading', 'octogroups', og.reloadPlayer) + +--[[ API: get player's cached groups ]] +function og.getPlayerGroups(ply) + + ply.og = ply.og or {} + + local gs = {} + for gID, m in pairs(ply.og) do + gs[#gs + 1] = og.groups[gID] + end + + return gs + +end + +--[[ API: set member as an owner of a group ]] +function og.setOwner(gID, sID) + + local g = og.groups[gID] + if not g then return end + + local rank = 'owner' + g.members[sID] = g.members[sID] or { + joined = os.time(), + } + + local m = g.members[sID] + if m.rank == rank then return end + + m.rank = rank + m.ranked = os.time() + og.save(gID) + + local ply = player.GetBySteamID(sID) + if IsValid(ply) then + og.reloadPlayer(ply) + end + + hook.Run('og.setOwner', gID, sID) + +end + +--[[ API: add a member to a group, pass nil as rank to kick ]] +function og.setMember(gID, sID, rank) + + local g = og.groups[gID] + if not g then return end + + if rank then + rank = g.ranks[rank] and rank or 'member' + + g.members[sID] = g.members[sID] or { + joined = os.time(), + } + + local m = g.members[sID] + if m.rank == rank then return end + + m.rank = rank + m.ranked = os.time() + else + g.members[sID] = nil + end + + og.save(gID) + + local ply = player.GetBySteamID(sID) + if IsValid(ply) then + og.reloadPlayer(ply) + end + + hook.Run('og.setMember', gID, sID, rank) + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/sv_player.lua b/garrysmod/addons/core-octogroups/lua/octogroups/sv_player.lua new file mode 100644 index 0000000..8f0915a --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/sv_player.lua @@ -0,0 +1,43 @@ +--[[ API: add a member to a group, pass nil as rank to kick ]] +function og.getOnline(gID) + + local plys = {} + + local g = og.groups[gID] + if not g then return plys end + + for i, ply in ipairs(player.GetAll()) do + if g.members[ply:SteamID()] then + plys[#plys + 1] = ply + end + end + + return plys + +end + +-- +-- META FUNCTIONS +-- + +local ply = FindMetaTable 'Player' + +function ply:GetMember(gID) + + local g = og.groups[gID] + if not g then return end + + local m = g.members[self:SteamID()] + return m + +end + +function ply:SetGroupRank(gID, rank) + + if rank == 'owner' then + og.setOwner(gID, self:SteamID()) + else + og.setMember(gID, self:SteamID(), rank) + end + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/sv_rank.lua b/garrysmod/addons/core-octogroups/lua/octogroups/sv_rank.lua new file mode 100644 index 0000000..3f8be03 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/sv_rank.lua @@ -0,0 +1,64 @@ +--[[ API: edit the rank in group, pass nil to data to remove rank ]] +function og.editRank(gID, rank, data) + + local g = og.groups[gID] + if not g then return end + + if rank == 'owner' or rank == 'member' then return end + + g.ranks[rank] = data + + -- unrank members if we're deleting the rank + if not data then + for sID, m in pairs(g.members) do + if m.rank == rank then + og.setMember(gID, sID, 'member') + end + end + end + + og.save(gID) + + hook.Run('og.editRank', gID, rank, data) + +end + +--[[ API: returns whether player has perm in group ]] +function og.hasPerm(gID, sID, perm) + + local override = hook.Run('og.hasPerm', gID, sID, perm) + if override ~= nil then return override end + + local g = og.groups[gID] + if not g then return false end + + local m = g.members[sID] + if not m then return false end + + if m.rank == 'owner' then return true end + + local r = g.ranks[m.rank] + if not r or not r.perms then return false end + + if table.HasValue(r.perms, perm) then return true end + + return false + +end + +--[[ API: returns user order by rank ]] +function og.getOrder(gID, sID) + + local g = og.groups[gID] + if not g then return -1 end + + local m = g.members[sID] + if not m then return -1 end + + if m.rank == 'member' then return 0 end + if m.rank == 'owner' then return 101 end + + local r = g.ranks[m.rank] + return r and r.order or 0 + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/tabs/members.lua b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/members.lua new file mode 100644 index 0000000..af6fcc5 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/members.lua @@ -0,0 +1,102 @@ +local colDefault, colHover = Color(0,0,0, 50), Color(0,0,0, 70) +og.tabBuilds['members'] = function(ps, g) + + local gID = g.id + + local p = vgui.Create 'DCategoryList' + ps:AddSheet('Участники', p, 'icon16/user_gray.png') + + if og.hasPerm(gID, 'setMember') then + local b = vgui.Create 'DButton' + p:AddItem(b) + b:SetTall(30) + b:SetText('Пригласить в организацию') + b:DockMargin(0, 0, 0, 10) + function b:DoClick() + local opts = {} + for i, ply in ipairs(player.GetAll()) do + local sID = ply:SteamID() + if not g.members[sID] then + opts[#opts + 1] = {ply:Name(), nil, function() + netstream.Start('og.setMember', gID, sID) + end} + end + end + octolib.menu(opts):Open() + end + end + + local ranks = {} + ranks.owner = p:Add('Глава', 'icon16/star.png') + for rankID, r in SortedPairsByMemberValue(g.ranks, 'order', true) do + ranks[rankID] = p:Add(r.name or 'Ранг', r.icon or 'icon16/status_offline.png') + end + ranks.member = p:Add('Без ранга', 'icon16/status_offline.png') + for rankID, cat in pairs(ranks) do + cat:DockMargin(0, 0, 0, 15) + cat:SetPaintBackground(true) + end + + local function memberPaint(self, w, h) + draw.RoundedBox(0, 0, 0, w, h, self.Hovered and colHover or colDefault) + end + + local function memberClick(self) + local opts = { + {'Открыть профиль', 'icon16/report_user.png', function() + F4:Hide() + gui.ActivateGameUI() + octoesc.OpenURL('https://steamcommunity.com/profiles/' .. util.SteamIDTo64(self.sID)) + end}, + } + + if og.hasPerm(gID, 'setRank') then + local my, his = og.getOrder(gID, LocalPlayer():SteamID()), og.getOrder(gID, self.sID) + if my > his then + local rankOpts = {} + for rID, r in SortedPairsByMemberValue(g.ranks, 'order', true) do + if my > r.order then + rankOpts[#rankOpts + 1] = {r.name or '???', r.icon or 'icon16/error.png', function() netstream.Start('og.setRank', gID, self.sID, rID) end} + end + end + if g.members[self.sID].rank ~= 'member' then + rankOpts[#rankOpts + 1] = {'Без ранга', 'icon16/status_offline.png', function() netstream.Start('og.setRank', gID, self.sID, 'member') end} + end + if #rankOpts >= 0 then opts[#opts + 1] = {'Ранг', 'icon16/user_go.png', rankOpts} end + + opts[#opts + 1] = {'Исключить', 'icon16/delete.png', function() netstream.Start('og.setRank', gID, self.sID, nil) end} + end + end + + octolib.menu(opts):Open() + end + + for sID, m in pairs(g.members) do + local ply = player.GetBySteamID(sID) + local cat = ranks[m.rank or 'member'] or ranks.member + local b = vgui.Create('DButton', cat) + b:Dock(TOP) + b:DockMargin(2,0,2,0) + b:SetTall(32) + b:SetText('...') + b:SetContentAlignment(4) + b:SetTextInset(40, 0) + b:SetFont('og.normal') + b.Paint = memberPaint + b.DoClick = memberClick + b.m = m + b.sID = sID + + local sID64 = util.SteamIDTo64(sID) + steamworks.RequestPlayerInfo(sID64, function(name) + b:SetText(('%s (%s)'):format(IsValid(ply) and ply:Name() or '-', name)) + end) + + local bA = b:Add 'AvatarImage' + bA:Dock(LEFT) + bA:SetWide(b:GetTall()) + bA:SetSteamID(sID64, 64) + bA:SetMouseInputEnabled(false) + end + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/tabs/news.lua b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/news.lua new file mode 100644 index 0000000..2ca0b0a --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/news.lua @@ -0,0 +1,12 @@ +og.tabBuilds['news'] = function(ps, g) + + local p = vgui.Create 'DScrollPanel' + ps:AddSheet('Новости', p, 'icon16/newspaper.png') + +end + +function og.openCreatePost(gID) + + -- something to be here + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/tabs/ranks.lua b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/ranks.lua new file mode 100644 index 0000000..728b791 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/ranks.lua @@ -0,0 +1,135 @@ +local colHover = Color(0,0,0, 50) +og.tabBuilds['ranks'] = function(ps, g) + + local gID = g.id + if not og.hasPerm(gID, 'editRank') then return end + + local p = vgui.Create 'DScrollPanel' + ps:AddSheet('Ранги', p, 'icon16/group.png') + + local function rankPaint(self, w, h) + if self.Hovered then draw.RoundedBox(0, 0, 0, w, h, colHover) end + if self.icon then + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(12, 12, 16, 16) + end + end + + local function rankClick(self) + octolib.menu({ + {'Редактировать', 'icon16/pencil.png', function() og.openEditRank(gID, self.rID) end}, + {'Удалить', 'icon16/delete.png', octolib.fQuery('Ты уверен? Это действие необратимо', 'Удалить ранг', 'Да', function() og.editRank(gID, self.rID, nil) end, 'Нет')}, + }):Open() + end + + local function setupButton(text, icon, doClick, rID) + local b = p:Add 'DButton' + b:Dock(TOP) + b:DockMargin(2,0,2,0) + b:SetTall(40) + b:SetText(text) + b:SetContentAlignment(4) + b:SetTextInset(40, 0) + b:SetFont('og.normal') + b.Paint = rankPaint + b.DoClick = doClick + b.icon = icon and Material(icon) + b.rID = rID + end + + for rID, r in SortedPairsByMemberValue(g.ranks, 'order', true) do + setupButton(r.name or '???', r.icon or 'icon16/error.png', rankClick, rID) + end + + setupButton('Создать ранг', nil, function() og.openEditRank(gID) end) + +end + +function og.openEditRank(gID, rID) + + if not IsValid(pnl) then return end + + local r = rID and og.groupsOwn[gID].ranks[rID] + local name = r and r.name or '' + local icon = r and r.icon or '' + local order = r and r.order or 1 + local perms = r and octolib.array.toKeys(r.perms) or {} + local check + + local p = octolib.overlay(pnl, 'DScrollPanel') + p:SetSize(400, 400) + p:SetPaintBackground(true) + p.pnlCanvas:DockPadding(10, 5, 10, 10) + + local eR = octolib.textEntry(p, 'ID ранга') + if rID then + eR:SetValue(rID) + eR:SetEnabled(false) + else + eR:SetUpdateOnType(true) + function eR:OnValueChange(v) rID = string.Trim(v) check() end + end + + local eN = octolib.textEntry(p, 'Название') + eN:SetValue(name) + eN:SetUpdateOnType(true) + function eN:OnValueChange(v) name = v check() end + + local eI = octolib.textEntryIcon(p, 'Иконка', 'materials/icon16/') + eI:SetValue(icon) + eI:SetUpdateOnType(true) + function eI:OnValueChange(v) icon = v check() end + + local sO = octolib.slider(p, 'Старшинство', 1, 100, 0) + sO:SetValue(order) + function sO:OnValueChanged(v) order = math.Clamp(math.Round(v), 1, 100) end + + local cat = p:Add 'DCollapsibleCategory' + cat:Dock(TOP) + cat:DockMargin(0, 5, 0, 0) + cat:SetExpanded(false) + cat:SetLabel('Разрешения') + + local catP = vgui.Create 'DPanel' + catP:DockPadding(5, 5, 5, 5) + catP:SetPaintBackgroundEnabled(false) + cat:SetContents(catP) + + for i, perm in ipairs({ + {'setMember', 'Приглашать участников'}, + {'setRank', 'Выдавать ранг / исключать'}, + {'post', 'Писать новости'}, + {'useMoney', 'Снимать со счета'}, + {'storage', 'Ставить хранилище'}, + {'setSetting', 'Изменять настройки'}, + {'editRank', 'Настраивать ранги'}, + }) do + local c = octolib.checkBox(catP, perm[2]) + c:SetChecked(perms[perm[1]]) + function c:OnChange(v) perms[perm[1]] = v or nil end + end + + local b = octolib.button(p, 'Сохранить', function() + p:Remove() + netstream.Start('og.editRank', gID, rID, { + name = name, + icon = icon, + order = order, + perms = table.GetKeys(perms), + }) + end) + b:DockMargin(0, 10, 0, 0) + + check = function() + local ok = true + if string.Trim(name) == '' then ok = false end + if string.Trim(icon) == '' then ok = false end + local rID, g = rID or '', og.groupsOwn[gID] + if rID == '' or rID == 'member' or rID == 'owner' then ok = false end + if g.ranks[rID] and g.ranks[rID] ~= r then ok = false end + b:SetEnabled(ok) + end + check() + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/tabs/settings.lua b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/settings.lua new file mode 100644 index 0000000..7d49329 --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/settings.lua @@ -0,0 +1,9 @@ +og.tabBuilds['settings'] = function(ps, g) + + local gID = g.id + if not og.hasPerm(gID, 'setSetting') then return end + + local p = vgui.Create 'DScrollPanel' + ps:AddSheet('Настройки', p, 'icon16/cog.png') + +end diff --git a/garrysmod/addons/core-octogroups/lua/octogroups/tabs/storage.lua b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/storage.lua new file mode 100644 index 0000000..bceab8f --- /dev/null +++ b/garrysmod/addons/core-octogroups/lua/octogroups/tabs/storage.lua @@ -0,0 +1,6 @@ +og.tabBuilds['storage'] = function(ps, g) + + local p = vgui.Create 'DScrollPanel' + ps:AddSheet('Другое', p, 'icon16/box.png') + +end \ No newline at end of file diff --git a/garrysmod/addons/core-octogui/lua/autorun/octogui.lua b/garrysmod/addons/core-octogui/lua/autorun/octogui.lua new file mode 100644 index 0000000..d6366ab --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/autorun/octogui.lua @@ -0,0 +1,13 @@ +octogui = octogui or {} + +octolib.client('config/octogui/f4') +octolib.client('config/octogui/score') + +octolib.client('octogui/esc') +octolib.client('octogui/f4') +octolib.client('octogui/circular') +octolib.client('octogui/notifications') + +-- include client first to create functions used in shared +octolib.client('octogui/cmenu/client') +octolib.shared('octogui/cmenu/shared') diff --git a/garrysmod/addons/core-octogui/lua/octogui/circular.lua b/garrysmod/addons/core-octogui/lua/octogui/circular.lua new file mode 100644 index 0000000..ecd50e0 --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/octogui/circular.lua @@ -0,0 +1,248 @@ +local function ease(t, b, c, d) + t = t / d + return -c * t * (t - 2) + b +end + +surface.CreateFont('octolib.use-normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) +surface.CreateFont('octolib.use-normal-sh', { + font = 'Calibri', + extended = true, + size = 27, + blursize = 5, + weight = 350, +}) + +local function drawText(text, font, x, y, xal, yal, col, shCol) + + draw.Text { + text = text, + font = font .. '-sh', + pos = {x, y+2}, + xalign = xal, + yalign = yal, + color = shCol, + } + + draw.Text { + text = text, + font = font, + pos = {x, y}, + xalign = xal, + yalign = yal, + color = col, + } + +end + +local colors = CFG.skinColors +function octogui.circularMenu(opts) + + local buts = octolib.table.mapSequential(opts, function(v) + v[1] = v[1] or L.action + v[2] = v[2] or 'octoteam/icons/percent0.png' + v[3] = v[3] or octolib.func.zero + + return { + text = markup.Parse(''.. v[1] ..'', 250), + textSh = markup.Parse(''.. v[1] ..'', 250), + icon = Material(v[2], ''), + anim = 0, + action = v[3], + } + end) + + local butsNum = #buts + local butSize = math.pi * 2 / butsNum + local segsPerBut = math.ceil(80 / butsNum) + + local polysBut = {} + for i = 1, butsNum do + local ang2 = butSize * (i - 1) + math.pi / 2 + polysBut[i] = {} + for i2 = 0, segsPerBut do + polysBut[i][i2] = -i2 / segsPerBut * butSize - ang2 + end + end + + if IsValid(octolib.usePnl) then octolib.usePnl:Remove() end + local p = vgui.Create('DFrame') + p:SetSize(ScrW(), ScrH()) + p:SetPos(0, 0) + p:SetMouseInputEnabled(false) + p:SetKeyboardInputEnabled(false) + p:SetDraggable(false) + p:ShowCloseButton(false) + p:SetTitle('') + octolib.usePnl = p + + p.al = 0 + p.mx = 0 + p.my = 0 + p.Paint = function(self, w, h) + local st = ease(self.al, 0, 1, 1) + local cx, cy = w/2, h/2 + local sw, sh = ScrW(), ScrH() + + draw.NoTexture() + surface.SetDrawColor(35,35,35, st * 150) + + if st > 0 then + local gap = 5 + for i = 1, butsNum do + local st2 = buts[i].anim + local arcW, bgcol, wcol = 100 + + bgcol = Color(colors.bg.r / 2, colors.bg.g / 2, colors.bg.b / 2, 180 * st + 70 * st2) + wcol = Color(180 + 75 * st2, 180 + 75 * st2, 180 + 75 * st2, 100 * st + 150 * st2) + + draw.NoTexture() + + for i2 = 1, segsPerBut do + local r1, r2 = 250 * st, math.max(250 * st - arcW, 0) + local v1, v2 = polysBut[i][i2 - 1], polysBut[i][i2] + local c1, c2 = math.cos(v1), math.cos(v2) + local s1, s2 = math.sin(v1), math.sin(v2) + + surface.SetDrawColor(bgcol.r, bgcol.g, bgcol.b, bgcol.a) + surface.DrawPoly{ + { x = cx + c1 * r2, y = cy - s1 * r2 }, + { x = cx + c1 * r1, y = cy - s1 * r1 }, + { x = cx + c2 * r1, y = cy - s2 * r1 }, + { x = cx + c2 * r2, y = cy - s2 * r2 }, + } + + if st2 ~= 0 then + local r3 = r2 - st2 * 5 + surface.SetDrawColor(238, 238, 238) + surface.DrawPoly{ + { x = cx + c1 * r3, y = cy - s1 * r3 }, + { x = cx + c1 * r2, y = cy - s1 * r2 }, + { x = cx + c2 * r2, y = cy - s2 * r2 }, + { x = cx + c2 * r3, y = cy - s2 * r3 }, + } + end + end + + local iconSize = 64 + local v = butSize * (i - 0.5) + math.pi / 2 + local c, s = math.cos(v), math.sin(v) + surface.SetMaterial(buts[i].icon) + surface.SetDrawColor(wcol.r, wcol.g, wcol.b, wcol.a) + surface.DrawTexturedRect( + cx + c * 198 * st - iconSize/2, + cy + s * 198 * st - iconSize/2, + iconSize, + iconSize + ) + end + + if self.selected ~= 0 then + buts[self.selected].textSh:Draw(w/2, h/2, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 255 * self.al) + buts[self.selected].text:Draw(w/2, h/2, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 255 * self.al) + end + + local col1, col2 = Color(238,238,238, 255 * self.al), Color(0,0,0, 255 * self.al) + drawText(L.left_choose, 'octolib.use-normal', w - 15, h - 35, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, col1, col2) + drawText(L.right_choose, 'octolib.use-normal', w - 15, h - 10, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, col1, col2) + end + end + p.Think = function(self) + local selected = 0 + if self.al > 0 then + if p.mx^2 + p.my^2 > 4000 then + local tan = math.atan2(p.my, p.mx) + math.pi/2 + tan = tan > math.pi and tan - 2 * math.pi or tan + local ang = (tan / math.pi + 1) / 2 + local butSizeRel = 1 / butsNum + selected = math.Round((ang + butSizeRel / 2) * butsNum) + end + end + self.selected = math.Clamp(selected, 0, butsNum) + + self.al = math.Approach(self.al, self.active and 1 or 0, FrameTime() / 0.3) + for i = 1, #buts do + if self.selected == i and p.active then + buts[ i ].anim = math.Approach(buts[ i ].anim, 1, FrameTime() / 0.1) + else + buts[ i ].anim = math.Approach(buts[ i ].anim, 0, FrameTime() / 0.2) + end + end + + if p.active and gui.IsGameUIVisible() then + gui.HideGameUI() + p.close() + end + end + + hook.Add('InputMouseApply', 'octolib.use-menu', function(cmd, x, y, ang) + + if not IsValid(p) then hook.Remove('InputMouseApply', 'octolib.use-menu') return end + if p.active then + if x ~= 0 or y ~= 0 then + local v = Vector(p.mx + x, p.my + y, 0) + if v:LengthSqr() > 10000 then + v = v:GetNormalized() * 100 + end + + p.mx = v.x + p.my = v.y + end + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + + return true + end + + end) + + local wasPressing2 = true + hook.Add('CreateMove', 'octolib.use-menu', function(cmd) + + if not IsValid(p) then + hook.Remove('CreateMove', 'octolib.use-menu') + else + if cmd:KeyDown(IN_ATTACK) then + if p.active and p.selected and p.selected ~= 0 then + buts[p.selected].action() + end + p.close() + end + + if p.active and not wasPressing2 then + if cmd:KeyDown(IN_ATTACK2) then + p.close() + end + end + wasPressing2 = cmd:KeyDown(IN_ATTACK2) + cmd:RemoveKey(IN_ATTACK) + cmd:RemoveKey(IN_ATTACK2) + end + + end) + + p.open = function() + + p.active = true + p.mx = 0 + p.my = 0 + + end + + p.close = function() + + p.active = false + timer.Simple(1, function() + if IsValid(p) then p:Remove() end + end) + + end + + p.open() + +end diff --git a/garrysmod/addons/core-octogui/lua/octogui/cmenu/client.lua b/garrysmod/addons/core-octogui/lua/octogui/cmenu/client.lua new file mode 100644 index 0000000..02f445f --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/octogui/cmenu/client.lua @@ -0,0 +1,186 @@ +octogui.cmenu = octogui.cmenu or {} +octogui.cmenu.categories = octogui.cmenu.categories or {} +local categories = octogui.cmenu.categories + +function octogui.cmenu.registerCategory(id, data) + if not (isstring(id)) then return end + + data = istable(data) and data or {} + if not isnumber(data.order) then + data.order = table.Count(categories) + 1 + end + + local items = categories[id] and categories[id].items or {} + data.items = items + categories[id] = data +end + +function octogui.cmenu.registerItem(category, id, data) + if not (isstring(id) and isstring(category) and istable(data)) then return end + + if not categories[category] then + octogui.cmenu.registerCategory(category) + end + + if not isnumber(data.order) then data.order = table.Count(categories[category].items) + 1 end + categories[category].items[id] = data +end + +-- +-- UTILITY STUFF +-- +surface.CreateFont('dbg_window_underline', { + font = system.IsOSX() and 'Helvetica' or 'Tahoma', + size = 13, + antialias = true, + extended = true, + underline = true, +}) + +local rtl = function(self) + self:SetFontInternal('DermaDefault') + self:SetUnderlineFont('dbg_window_underline') +end + +local as = function(_, name, val) + if name == 'TextClicked' then + gui.ActivateGameUI() + octoesc.OpenURL(val) + end +end + +function octogui.cmenu.window(title, txt) + + local f = vgui.Create 'DFrame' + f:SetSize(300, 300) + f:SetTitle(title or L.information) + f:SetSizable(true) + f:SetMinWidth(300) + f:AlignTop(5) + f:AlignLeft(5) + + local lbl = f:Add('RichText') + lbl:Dock(FILL) + txt = octolib.string.splitByUrl(txt) + lbl:InsertColorChange(255, 255, 255, 255) + for _,v in ipairs(txt) do + if istable(v) then + lbl:InsertClickableTextStart(v[1]) + lbl:InsertColorChange(0, 130, 255, 255) + lbl:AppendText(v[1]) + lbl:InsertColorChange(255, 255, 255, 255) + lbl:InsertClickableTextEnd() + else + lbl:AppendText(v) + end + end + lbl.PerformLayout = rtl + lbl.ActionSignal = as + lbl:SetToFullHeight() + +end + +--- +--- GMOD INTEGRATION +--- + +function octogui.cmenu.create() + + local m = vgui.Create 'DMenu' + local ply = LocalPlayer() + + local function handleItem(item, m) + if item.spacer then m:AddSpacer() return end + local text + if isfunction(item.text) then text = item.text(ply) + elseif item.text then text = item.text end + text = text or L.action + local icon + if isfunction(item.icon) then icon = item.icon(ply) + elseif item.icon then icon = item.icon end + local subMenu, option + if item.build then + subMenu, option = m:AddSubMenu(text) + item.build(subMenu, option) + elseif item.options then + subMenu, option = m:AddSubMenu(text) + for _, subOption in ipairs(item.options) do + if not subOption.check or subOption.check(ply) then handleItem(subOption, subMenu) end + end + else + local action = item.action + if not action then + if item.say then + action = function() octochat.say(item.say) end + end + + if item.cmd then + action = function() RunConsoleCommand(unpack(item.cmd)) end + end + + if item.url then + action = function() octoesc.OpenURL(item.url) end + end + + if istable(item.netstream) then + action = function() netstream.Start(unpack(item.netstream)) end + elseif item.netstream then + action = function() netstream.Start(item.netstream) end + end + end + + option = m:AddOption(text, action) + end + + if icon then option:SetImage(icon) end + if item.enable then + local enable = item.enable(ply) + option:SetEnabled(enable) + if not enable then option:SetAlpha(100) end + end + end + + local shouldAddSpacer = false -- each category has to add spacer before it's elements, except the first one + for catID, cat in SortedPairsByMemberValue(categories, 'order') do + if not cat.check or cat.check(ply) then + local empty = true -- means that this category has no elements + for itemID, item in SortedPairsByMemberValue(cat.items, 'order') do + if not item.check or item.check(ply) then + if shouldAddSpacer and empty then m:AddSpacer() + else shouldAddSpacer = true end + empty = false + handleItem(item, m) + end + end + end + end + m:PerformLayout() + + return m + +end + +local cmenu +hook.Add('OnContextMenuOpen', 'CMenuOnContextMenuOpen', function() + + if not IsValid(g_ContextMenu) then return end + + if not g_ContextMenu:IsVisible() then + local orig = g_ContextMenu.Open + g_ContextMenu.Open = function(self, ...) + self.Open = orig + orig(self, ...) + + cmenu = octogui.cmenu.create() + cmenu:Open() + cmenu:CenterVertical() + cmenu:SetX(-cmenu:GetWide()) + cmenu:MoveTo(8, cmenu:GetY(), .1, 0) + end + end + +end) + +hook.Add('OnContextMenuClose', 'CMenuOnContextMenuClose', function() + if IsValid(cmenu) then cmenu:Remove() end +end) diff --git a/garrysmod/addons/core-octogui/lua/octogui/cmenu/shared.lua b/garrysmod/addons/core-octogui/lua/octogui/cmenu/shared.lua new file mode 100644 index 0000000..0f9edd0 --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/octogui/cmenu/shared.lua @@ -0,0 +1,14 @@ +local props = file.Find('cmenu/properties/*.lua', 'LUA') +for _, v in ipairs(props) do + octolib.shared('cmenu/properties/' .. v:StripExtension()) +end + +local cats = file.Find('cmenu/categories/*.lua', 'LUA') +for _, v in ipairs(cats) do + octolib.client('cmenu/categories/' .. v:StripExtension()) +end + +local items = file.Find('cmenu/items/*.lua', 'LUA') +for _, v in ipairs(items) do + octolib.client('cmenu/items/' .. v:StripExtension()) +end diff --git a/garrysmod/addons/core-octogui/lua/octogui/esc.lua b/garrysmod/addons/core-octogui/lua/octogui/esc.lua new file mode 100644 index 0000000..12dc73c --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/octogui/esc.lua @@ -0,0 +1,310 @@ +hook.Add('Think', 'octoesc.init', function() +hook.Remove('Think', 'octoesc.init') + +surface.CreateFont('octoesc.large', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) + +local refreshInterval = 3 * 60 +local pw, ph = ScrW(), ScrH() + +octoesc = octoesc or {} +if IsValid(octoesc.pnl) then octoesc.pnl:Remove() end + +local p = vgui.Create 'DPanel' +octoesc.pnl = p +p:SetSize(pw, ph) +p:SetPaintBackground(false) +p:MakePopup() +p:Center() +p.al = 0 + +local wasOpened, showConsole, forceMenu = nil, nil, nil +hook.Add('Think', 'octoesc', function() + + if gui.IsGameUIVisible() and not forceMenu then + if (gui.IsConsoleVisible() and input.IsKeyDown(KEY_BACKQUOTE)) or showConsole then showConsole = true return end + + if not wasOpened then wasOpened = true return end + p:Toggle(not p.active) + gui.HideGameUI() + elseif not forceMenu then + wasOpened = nil + showConsole = nil + else + forceMenu = nil + end + +end) + +p.Toggle = function(self, val) + + if val == nil then val = self.active end + + if val then self:SetVisible(true) end + self:SetMouseInputEnabled(val) + self:SetKeyboardInputEnabled(val) + self.active = val + + if val then + self:PerformLayout() + if octoinv then octoinv.show(false) end + end + +end + +p.Think = function(self) + + self.al = math.Approach(self.al, self.active and 1 or 0, FrameTime() / 0.3) + self:SetAlpha(self.al * 255) + + if self:IsVisible() and self.al == 0 then + self:SetVisible(false) + end + + if CurTime() >= (self.nextRefresh or 0) then + p:RefreshServers() + end + +end + +local blur = Material('pp/blurscreen') +local colors = CFG.skinColors +hook.Add('RenderScreenspaceEffects', 'octoesc', function() + + local state = p.al + local a = 1 - math.pow(1 - state, 2) + + if a > 0 then + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -a * 0.2, + ['$pp_colour_contrast'] = 1 + 0.5 * a, + ['$pp_colour_colour'] = 1 - a, + } + + if GetConVar('octolib_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor(255, 255, 255, a * 255) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat('$blur', a * i * 2) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(-1, -1, ScrW() + 2, ScrH() + 2) + end + else + colMod['$pp_colour_brightness'] = -0.4 * a + colMod['$pp_colour_contrast'] = 1 + 0.2 * a + DrawColorModify(colMod) + end + + draw.NoTexture() + surface.SetDrawColor(colors.bg.r, colors.bg.g, colors.bg.b, a * 100) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + end + +end) + +local buts = p:Add 'DPanel' +buts:Dock(LEFT) +buts:SetWide(250) +buts:SetPaintBackground(false) + +local function hangClosed() + + forceMenu = true + showConsole = true + p:Toggle(false) + +end + +_oldOpenURL = _oldOpenURL or gui.OpenURL +function gui.OpenURL(url) + + hangClosed() + _oldOpenURL(url) + +end +octoesc.OpenURL = gui.OpenURL + +_oldEnableVoiceChat = _oldEnableVoiceChat or permissions.EnableVoiceChat +function permissions.EnableVoiceChat(enable) + + hangClosed() + _oldEnableVoiceChat(enable) + +end + +local function paintServer(self, w, h) + draw.RoundedBox(8, 0, 0, w, h, colors.bg_d) + draw.RoundedBox(8, 1, 1, w-2, h-2, colors.bg) +end +local function paintDivider(self, w, h) end + +for i, v in ipairs({ + {'Продолжить', function() p:Toggle(false) end}, + {'spacer'}, + {'Форум', function() octoesc.OpenURL('https://forum.octothorp.team') end}, + {'Сайт', function() octoesc.OpenURL('https://www.octothorp.team') end}, + {'Вики', function() octoesc.OpenURL('https://wiki.octothorp.team') end}, + {'spacer'}, + {'Главное меню', function() hangClosed() gui.ActivateGameUI() end}, + {'Отключиться', function() + local eID = LocalPlayer():EntIndex() + if octoinv and octoinv.invs[eID]._hand and octoinv.invs[eID]._hand.items[1] then + Derma_Query('У тебя в руках что-то есть. Если ты выйдешь, содержимое пропадет. Ты уверен?', 'Выход с сервера', L.yes, function() RunConsoleCommand('disconnect') end, L.no) + else + RunConsoleCommand('disconnect') + end + end}, +}) do + if v[1] == 'spacer' then + local s = buts:Add 'DPanel' + s:Dock(TOP) + s:SetTall(20) + s.Paint = paintDivider + else + local b = buts:Add 'DButton' + b:Dock(TOP) + b:SetTall(40) + b:DockMargin(0, 0, 0, 10) + b:SetFont('octoesc.large') + b:SetText(v[1]) + b:SetContentAlignment(4) + b:SetPaintBackground(false) + b.DoClick = v[2] + end +end + +local scrollPanel = p:Add 'DScrollPanel' +scrollPanel:Dock(FILL) + +if pw > 800 then + buts:DockMargin(100, 150, 100, 150) + scrollPanel.pnlCanvas:DockPadding(50, 100, 50, 100) +else + buts:DockMargin(50, 50, 50, 50) + scrollPanel.pnlCanvas:DockPadding(10, 20, 10, 20) +end + +local iconLayout = scrollPanel:Add 'DIconLayout' +iconLayout:Dock(FILL) +-- iconLayout:DockMargin(50, 100, 50, 100) +iconLayout:SetPaintBackground(false) +iconLayout:SetSpaceX(10) +iconLayout:SetSpaceY(10) + +p.RefreshServers = function(self) + + self.nextRefresh = CurTime() + refreshInterval + + local pnls = {} + octoservices:get('/servers/status'):Then(function(res) + iconLayout:Clear() + for _, group in ipairs(res.data or {}) do + local srvPanel = pnls[group.slug] + if not IsValid(srvPanel) then + srvPanel = iconLayout:Add 'DPanel' + srvPanel:DockPadding(15, 15, 15, 15) + srvPanel.Paint = paintServer + if pw <= 800 then + srvPanel:SetSize(pw - 400, 200) + elseif pw <= 1024 then + srvPanel:SetSize(pw - 600, 200) + else + srvPanel:SetSize((pw - 650) / 2, 180) + end + + local name = srvPanel:Add 'DLabel' + name:Dock(TOP) + name:SetTall(35) + name:SetFont('octoesc.large') + name:SetText(group.name) + + local desc = srvPanel:Add 'DLabel' + desc:Dock(FILL) + desc:SetWrap(true) + desc:SetContentAlignment(4) + desc:SetText(group.desc) + + local bot = srvPanel:Add 'DPanel' + bot:Dock(BOTTOM) + bot:SetTall(30 * #group.servers - 5) + bot:SetPaintBackground(false) + srvPanel.bot = bot + + pnls[group.slug] = srvPanel + end + + for srvID, server in ipairs(group.servers) do + local status = server.status + local contStatus = srvPanel.bot:Add 'DPanel' + contStatus:Dock(TOP) + contStatus:DockMargin(0, 0, 0, 5) + contStatus:SetTall(25) + contStatus:SetPaintBackground(false) + + local frac, barText + local butJoin = contStatus:Add 'DButton' + butJoin:Dock(RIGHT) + if status then + if status.connect == game.GetIPAddress() then + butJoin:SetText('Ты здесь') + butJoin:SetEnabled(false) + elseif status.password then + butJoin:SetText('Под паролем') + butJoin:SetEnabled(false) + elseif #status.players < status.maxplayers then + butJoin:SetText('На сервер #' .. srvID) + function butJoin:DoClick() + Derma_Query('Подключение к серверу отключит тебя от текущей игры, продолжить?', 'Ты уверен?', 'Подключиться', function() + hangClosed() gui.ActivateGameUI() + LocalPlayer():ConCommand('connect ' .. status.connect) + end, L.no) + end + else + butJoin:SetText('Нет мест') + butJoin:SetEnabled(false) + end + frac = math.min(#status.players / status.maxplayers, 1) + barText = ('%s – %s из %s'):format(status.map or '???', #status.players, status.maxplayers) + else + butJoin:SetText('Не в сети') + butJoin:SetEnabled(false) + frac = 0 + barText = 'Сервер не работает' + end + butJoin:SizeToContentsX(20) + + local bar = contStatus:Add 'DProgress' + bar:Dock(FILL) + bar:DockMargin(0,0,10,0) + bar:SetFraction(frac) + + local barLbl = bar:Add 'DLabel' + barLbl:Dock(FILL) + barLbl:SetContentAlignment(5) + barLbl:SetText(barText) + end + end + end):Catch(function(err) + print(err) + print('Произошла ошибка обновления статуса серверов, пробуем еще раз через минуту...') + self.nextRefresh = CurTime() + 60 + end) + +end + +end) diff --git a/garrysmod/addons/core-octogui/lua/octogui/f4.lua b/garrysmod/addons/core-octogui/lua/octogui/f4.lua new file mode 100644 index 0000000..172f59e --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/octogui/f4.lua @@ -0,0 +1,525 @@ +surface.CreateFont('f4.small', { + font = 'Calibri', + extended = true, + size = 14, + weight = 350, +}) +surface.CreateFont('f4.small-sh', { + font = 'Calibri', + extended = true, + size = 14, + blursize = 3, + weight = 350, +}) + +surface.CreateFont('f4.counter', { + font = 'Arial', + extended = true, + size = 14, + weight = 600, +}) + +surface.CreateFont('f4.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) +surface.CreateFont('f4.charset-label', { + font = 'Calibri', + extended = true, + size = 25, + weight = 350, +}) +surface.CreateFont('f4.charset-text', { + font = 'Calibri', + extended = true, + size = 19, + weight = 100, +}) +surface.CreateFont('f4.normal-sh', { + font = 'Calibri', + extended = true, + size = 27, + blursize = 5, + weight = 350, +}) + +surface.CreateFont('f4.medium', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) +surface.CreateFont('f4.medium-sh', { + font = 'Calibri', + extended = true, + size = 42, + blursize = 8, + weight = 350, +}) + +local function ease(t, b, c, d) + + t = t / d; + return -c * t * (t - 2) + b + +end + +local function playTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format('%02i:%02i:%02i', h, m, s) + +end + +local function drawText(text, font, x, y, xal, yal, col, shCol) + + draw.Text { + text = text, + font = font .. '-sh', + pos = {x, y+2}, + xalign = xal, + yalign = yal, + color = shCol, + } + + draw.Text { + text = text, + font = font, + pos = {x, y}, + xalign = xal, + yalign = yal, + color = col, + } + +end + +local cols = { + txt = Color(238,238,238, 255), + txtSh = Color(0,0,0, 255), + indicator = Color(255,255,255, 200), +} + +local options = {} +local function insertAndSort(data) + + if not data.id then + error('Cannot add option without "id" field') + return + end + + local new = true + for i, option in ipairs(data) do + if option.id == data.id then + options[i] = data + new = false + break + end + end + + if new then + table.insert(options, data) + end + + table.SortByMember(options, 'order', true) + +end + +local colRed = CFG.skinColors.r +local function paintBut(self, w, h) + + surface.SetAlphaMultiplier(F4.al) + + local open = IsValid(self.window) and self.window:IsVisible() + surface.SetDrawColor(255,255,255, (open or self.Hovered) and 255 or 150) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w/2 - 32, h/2 - 32, 64, 64) + + if open then + draw.RoundedBox(4, w / 2 - 16, -5, 32, 8, cols.indicator) + end + + if self.counter > 0 then + surface.DisableClipping(true) + surface.SetFont('f4.counter') + local tw = math.max(surface.GetTextSize(self.counter), 8) -- math.max to keep round shape + local x, y = w / 2 + 30, h / 2 - 30 + draw.RoundedBox(8, x - 4 - tw / 2, y - 8, tw + 8, 16, colRed) + draw.SimpleText(self.counter, 'f4.counter', tostring(self.counter):len() <= 1 and (x + 1) or x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + surface.DisableClipping(false) + end + + surface.SetAlphaMultiplier(1) + +end + +local function showWindow(w) + w:SetVisible(true) + if w.showFunc then w:showFunc(true) end + + if not w.oldPos then + w.oldPos = { w:GetPos() } + w.oldPos[2] = w.oldPos[2] - 20 + end + + w:MoveToFront() + w:MoveTo(w.oldPos[1], w.oldPos[2], 0.2, 0, 0.5) + w:AlphaTo(255, 0.2, 0) + timer.Simple(0.2, function() + w:SetVisible(true) + w:SetAlpha(255) + w:SetPos(w.oldPos[1], w.oldPos[2]) + end) +end + +local function hideWindow(w) + w.oldPos = { w:GetPos() } + w:MoveTo(w.oldPos[1], w.oldPos[2] + 20, 0.2, 0, 2) + w:AlphaTo(0, 0.2, 0) + timer.Simple(0.2, function() + if w.showFunc then w:showFunc(false) end + w:SetPos(w.oldPos[1], w.oldPos[2] + 20) + w:SetAlpha(0) + w:SetVisible(false) + end) +end + +local function teamName(ply) + local name = team.GetName(ply:Team()) + local customJob = ply:GetNetVar('customJob') + if customJob then name = customJob[1] end + return name +end + +function octogui.reloadF4() + + local ply = LocalPlayer() + if not IsValid(ply) then return end + if IsValid(F4) then F4:Remove() end + table.Empty(options) + + local f = vgui.Create 'DFrame' + f:SetSize(ScrW(), ScrH()) + f:SetTitle('') + f:MakePopup() + f:SetVisible(false) + f:SetDraggable(false) + f.btnClose:SetVisible(false) + f.btnMinim:SetVisible(false) + f.btnMaxim:SetVisible(false) + f.openTime = 0 + f.windows = {} + f.buttons = {} + f.counters = f.counters or {} + F4 = f + + hook.Add('VGUIMousePressed', 'f4', function(pnl, but) + + if not F4:IsVisible() then return end + for i, v in pairs(f.windows) do + if pnl == v or v:IsOurChild(pnl) then v:MoveToFront() end + end + + end) + + local bClose = f:Add 'DButton' + bClose:SetSize(64,64) + bClose:AlignRight(15) + bClose:AlignTop(15) + bClose:SetText('') + bClose:SetToolTip(L.close) + bClose.icon = Material('octoteam/icons/cross.png') + function bClose:DoClick() f:Hide() end + function bClose:Paint(w, h) + if self.Hovered then + surface.SetDrawColor(255,255,255, 255) + else + surface.SetDrawColor(0,0,0, 100 * F4.al) + end + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w/2 - 32, h/2 - 32, 64, 64) + end + + local blur, skinCols = Material('pp/blurscreen'), CFG.skinColors + function f:Paint(w, h) + local a = ease(self.isClosing and (math.max(self.isClosing - CurTime(), 0) / 0.3) or (1 - math.max(self.openTime + 0.3 - CurTime(), 0) / 0.3), 0, 1, 1) + self.al = a + + local colMod ={ + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -0.2 * a, + ['$pp_colour_contrast'] = 1 + 0.5 * a, + ['$pp_colour_colour'] = 1 - a, + } + + if GetConVar('octolib_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor(255, 255, 255, 255 * a) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat('$blur', a * i * 2) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(-1, -1, w + 2, h + 2) + end + else + colMod['$pp_colour_brightness'] = -0.4 * a + colMod['$pp_colour_contrast'] = 1 + 0.2 * a + DrawColorModify(colMod) + end + + draw.NoTexture() + surface.SetDrawColor(skinCols.bg.r, skinCols.bg.g, skinCols.bg.b, 100 * a) + surface.DrawRect(-1, -1, w + 2, h + 2) + + surface.SetFont('f4.medium') + local ply = LocalPlayer() + local hvrPnl = vgui.GetHoveredPanel() + + surface.SetAlphaMultiplier(a) + drawText( + teamName(ply) .. ' ' .. ply:Name(), 'f4.medium', w / 2, 15, + TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP, cols.txt, cols.txtSh + ) + + -- center + if IsValid(hvrPnl) and hvrPnl.barTxt then + drawText( + hvrPnl.barTxt, 'f4.medium', w / 2, h - 80, + TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) + end + + -- bottom left + drawText( + L.purse_hint .. DarkRP.formatMoney(ply:GetNetVar('money')), 'f4.normal', 10, h - 8, + TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) drawText( + L.salary_hint .. DarkRP.formatMoney(ply:Salary()), 'f4.normal', 10, h - 32, + TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) + local sweets = ply:GetNetVar('sweets') + if sweets then + drawText( + 'Конфет: ' .. octolib.string.separateDigits(sweets), 'f4.normal', 10, h - 56, + TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) + end + + -- bottom left + drawText( + L.players_hint .. player.GetCount(), 'f4.normal', w - 10, h - 56, + TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) drawText( + 'Полицейских: ' .. #player.GetPolice(), 'f4.normal', w - 10, h - 32, + TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) drawText( + L.play_time_hint .. playTime(LocalPlayer():GetTimeTotal()), 'f4.normal', w - 10, h - 8, + TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh + ) + surface.SetAlphaMultiplier(1) + + end + + local p = f:Add 'DPanel' + p:SetPos(0, ScrH() - 75) + p:SetTall(70) + function p:Paint(w, h) + + draw.RoundedBox(4, 0, 0, w, h, Color(0,0,0, 100 * F4.al)) + + end + + f.openWindow = {} + function p:Update() + + self:Clear() + f.openWindow = {} + + local butsNum = 0 + for i, v in ipairs(options) do + if not v.check or v.check() then + local b = p:Add 'DButton' + b:SetSize(75,70) + b:SetPos(3 + (i-1) * 75, 0) + b:SetText('') + -- b:SetToolTip(v.name) + b.barTxt = v.name + b.icon = v.icon + b.Paint = paintBut + b.window = f.windows[i] + b.counter = F4.counters[v.id] or 0 + function b:DoClick(onlyOpen) + local w = f.windows[i] + if IsValid(w) then + local st = not w:IsVisible() + if st then + showWindow(w) + elseif not onlyOpen then + hideWindow(w) + end + self.window = w + return + end + + local w = f:Add 'DFrame' + w:SetTitle(v.name) + w.btnMaxim:SetVisible(false) + w.btnMinim:SetVisible(false) + v.build(w) + w.showFunc = v.show + w:SetPos(self:LocalToScreen(32, 32) - w:GetWide() / 2, 0) + w:CenterVertical() + w:SetDeleteOnClose(false) + showWindow(w) + function w.btnClose:DoClick() hideWindow(w) end + + f.windows[i] = w + self.window = f.windows[i] + end + f.openWindow[v.id] = function() b:DoClick(true) end + f.buttons[v.id] = b + butsNum = butsNum + 1 + + if IsValid(f.windows[i]) then + local w = f.windows[i] + if w.wasVisible then + showWindow(w) + end + end + else + if IsValid(f.windows[i]) then + f.windows[i]:Remove() + f.windows[i] = nil + end + end + end + + self:SetWide(butsNum * 75 + 6) + self:CenterHorizontal() + + end + + function F4:Update() + p:Update() + end + + function F4:Think() + if self:IsVisible() and gui.IsGameUIVisible() then + self:Hide() + end + end + + function F4:SetCounter(id, amount) + + F4.counters[id] = amount + + local but = f.buttons[id] + if IsValid(but) then + but.counter = amount + end + + end + + local lastCursorPos = { ScrW() / 2, ScrH() / 2 } + function F4:Show() + + if self.isClosing then return end + + self.openTime = CurTime() + self:SetVisible(true) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + self:Update() + + gui.SetMousePos(lastCursorPos[1], lastCursorPos[2]) + + end + + function F4:Hide() + + if self.isClosing then return end + + for i, w in pairs(f.windows) do + w.wasVisible = w:IsVisible() + if w.wasVisible then hideWindow(w) end + end + + gui.HideGameUI() + lastCursorPos[1], lastCursorPos[2] = gui.MousePos() + + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + self.isClosing = CurTime() + 0.3 + timer.Simple(0.5, function() + self.isClosing = nil + self:SetVisible(false) + end) + + end + + function F4:Toggle() + + if self:IsVisible() then + self:Hide() + else + self:Show() + end + + end + + function F4:OpenWindow(id) + + if self.openWindow[id] then + if not self:IsVisible() then self:Show() end + self.openWindow[id]() + end + + end + + hook.Add('ShowSpare2', 'f4', function() + + if IsValid(F4) then + F4:Toggle() + else + octolib.notify.show('warning', L.f4_failure) + end + + end) + + function F4:OnKeyCodeReleased(key) + + if input.LookupKeyBinding(key) == 'gm_showspare2' then + F4:Hide() + end + + end + + function octogui.addToF4(data) + insertAndSort(data) + F4:Update() + end + + hook.Run('octogui.f4-tabs') + +end + +concommand.Add('octogui_reloadf4', octogui.reloadF4) + +hook.Add('Think', 'f4.init', function() + hook.Remove('Think', 'f4.init') + octogui.reloadF4() +end) diff --git a/garrysmod/addons/core-octogui/lua/octogui/notifications.lua b/garrysmod/addons/core-octogui/lua/octogui/notifications.lua new file mode 100644 index 0000000..43f9117 --- /dev/null +++ b/garrysmod/addons/core-octogui/lua/octogui/notifications.lua @@ -0,0 +1,135 @@ +local noMessages = { + 'И рассказать-то нечего...', + 'Ты в курсе всех событий!', + 'Пока что ничего не происходило', + 'Здесь мы расскажем о важных событиях', + 'Пустовато в твоей ленте...', +} + +local container +hook.Add('octogui.f4-tabs', 'octogui.notifications', function() + + octogui.addToF4({ + order = 0, + id = 'notifications', + name = 'Уведомления', + icon = Material('octoteam/icons/megaphone.png'), + build = function(f) + f:SetSize(300, 500) + f:SetSizable(true) + f:SetMinWidth(300) + f:SetMinHeight(200) + + local sp = f:Add 'DScrollPanel' + sp:Dock(FILL) + container = sp + container.pendingUpdate = true + + local function lock(doLock) + for _, but in ipairs(container.actButtons) do + if IsValid(but) then but:SetEnabled(not doLock) end + end + end + + local function actionClick(but) + lock(true) + + netstream.Request('octolib-notify.read', but.notifID, but.actID):Then(function(remove) + if remove then + table.remove(octolib.notify.cache, but.notifID) + F4:SetCounter('notifications', #octolib.notify.cache) + container.pendingUpdate = true + end + end):Finally(function() + lock(false) + end) + end + + function container:Think() + if not container.pendingUpdate then return end + container.pendingUpdate = nil + + container:Clear() + container.actButtons = {} + + if #octolib.notify.cache > 0 then + container.Paint = octolib.func.zero + for notifID, notif in ipairs(octolib.notify.cache) do + local text, date, buttons = unpack(notif) + if not buttons or #buttons < 1 then + buttons = { 'Понятно' } + end + + local p = self:Add 'DPanel' + p:Dock(TOP) + p:DockMargin(0, 0, 0, 5) + p:DockPadding(5, 5, 5, 5) + + local l = p:Add 'DMarkup' + l:Dock(TOP) + l:SetText(text) + + local bp = p:Add 'DPanel' + bp:Dock(BOTTOM) + bp:SetTall(20) + bp:SetPaintBackground(false) + + if date then + local d = bp:Add 'DLabel' + d:Dock(LEFT) + d:SetText(os.date('%d %b, %H:%M', date)) + d:SizeToContentsX() + d:SetContentAlignment(3) + d:SetAlpha(75) + end + + local sorted = octolib.table.toKeyVal(buttons) + table.sort(sorted, function(a, b) return a[1] > b[1] end) + + for _, data in ipairs(sorted) do + local but = bp:Add 'DButton' + but:Dock(RIGHT) + but:DockMargin(5, 0, 0, 0) + but:SetText(data[2]) + but:SizeToContentsX(14) + but.notifID = notifID + but.actID = data[1] + but.DoClick = actionClick + container.actButtons[#container.actButtons + 1] = but + end + + function p:PerformLayout() + self:SetTall(l:GetTall() + 35) + end + end + else + container.text = table.Random(noMessages) + function container:Paint(w, h) + draw.Text { + text = self.text, + pos = { w / 2, h / 2 }, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + end + end + end + end, + show = function(f, st) + if st then f:AlignBottom(70) end + end, + }) + + F4:SetCounter('notifications', #octolib.notify.cache) + +end) + +hook.Add('octolib.notify.cacheUpdate', 'octogui.notifications', function() + + F4:SetCounter('notifications', #octolib.notify.cache) + + if IsValid(container) then + container.pendingUpdate = true + end + +end) diff --git a/garrysmod/addons/core-octoinv/lua/autorun/octoinv.lua b/garrysmod/addons/core-octoinv/lua/autorun/octoinv.lua new file mode 100644 index 0000000..00ee732 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/autorun/octoinv.lua @@ -0,0 +1,78 @@ +------------------------------------------- +-- +-- OCTOINV by Octothorp Team +-- +-- Designed to be used in our own projects. +-- Do not offer me to sell this addon. +-- +------------------------------------------- + +octoinv = octoinv or {} +octoinv.debug = true + +function octoinv.msg(txt, ...) + print('[# INV] ' .. os.date('%H:%M:%S - ', os.time()) .. string.format(txt, ...)) +end + +function octoinv.debugmsg(txt, ...) + if not octoinv.debug then return end + octoinv.msg(txt, ...) +end + +file.CreateDir('octoinv') + +-- core +octolib.server('octoinv/core/sv_octoinv') +octolib.server('octoinv/core/sv_map') +octolib.server('octoinv/core/sv_control') +octolib.server('octoinv/core/sv_hooks') +octolib.server('octoinv/core/sv_loot') +octolib.client('octoinv/core/cl_octoinv') +octolib.client('octoinv/core/cl_help') + +-- craft +octolib.server('octoinv/craft/sv_craft') +octolib.server('octoinv/craft/sv_prod') +octolib.client('octoinv/craft/cl_preview') + +-- shop +octolib.server('octoinv/shop/sv_shop') +octolib.server('octoinv/shop/sv_control') +octolib.client('octoinv/shop/cl_shop') + +-- market +octolib.shared('octoinv/market/sh_market') +octolib.server('octoinv/market/sv_market') +octolib.server('octoinv/market/sv_orders_stack') +octolib.server('octoinv/market/sv_orders_nostack') +octolib.server('octoinv/market/sv_retain') +octolib.server('octoinv/market/sv_control') +octolib.client('octoinv/market/cl_market') + +-- collect +octolib.server('octoinv/collect/sv_collect') + +--admin +octolib.server('octoinv/admin/sv_give') +octolib.server('octoinv/admin/sv_control') +octolib.server('octoinv/admin/sv_stats') +octolib.client('octoinv/admin/cl_give') +octolib.client('octoinv/admin/cl_mapeditor') +octolib.client('octoinv/admin/cl_stats') + +octolib.server('octoinv/sv_tests') + +local function load(path) + local fs, _ = file.Find(path .. '*.lua', 'LUA') + for _, f in pairs(fs) do + local fname = path .. f:sub(1, -5) + octolib.server(fname) + end +end + +load('config/octoinv/items/') +load('config/octoinv/craft/') +load('config/octoinv/shop/') +load('config/octoinv/prod/') +load('config/octoinv/market/') +load('config/octoinv/collect/') diff --git a/garrysmod/addons/core-octoinv/lua/cmenu/categories/inventory.lua b/garrysmod/addons/core-octoinv/lua/cmenu/categories/inventory.lua new file mode 100644 index 0000000..43490fd --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/cmenu/categories/inventory.lua @@ -0,0 +1 @@ +octogui.cmenu.registerCategory('inv') diff --git a/garrysmod/addons/core-octoinv/lua/cmenu/items/money.lua b/garrysmod/addons/core-octoinv/lua/cmenu/items/money.lua new file mode 100644 index 0000000..77f9e42 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/cmenu/items/money.lua @@ -0,0 +1,15 @@ +octogui.cmenu.registerItem('inv', 'money', { + text = L.money, + icon = octolib.icons.silk16('money'), + options = { + { + text = L.drop, + icon = octolib.icons.silk16('arrow_right'), + action = octolib.fStringRequest(L.drop, L.enter_amount_money, '', function(s) octochat.say('/dropmoney', s) end, nil, L.ok, L.cancel), + }, { + text = L.put, + icon = octolib.icons.silk16('arrow_down'), + action = octolib.fStringRequest(L.put, L.enter_amount_money, '', function(s) octochat.say('/putmoney', s) end, nil, L.ok, L.cancel), + }, + }, +}) diff --git a/garrysmod/addons/core-octoinv/lua/cmenu/items/storage.lua b/garrysmod/addons/core-octoinv/lua/cmenu/items/storage.lua new file mode 100644 index 0000000..b7db6af --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/cmenu/items/storage.lua @@ -0,0 +1,7 @@ +octogui.cmenu.registerItem('inv', 'storage', { + text = L.place_storage, + icon = octolib.icons.silk16('add_package'), + action = octolib.fQuery(L.place_storage_confirm, L.place_storage, L.ok, function() + RunConsoleCommand('dbg_storage', '') + end, L.cancel), +}) diff --git a/garrysmod/addons/core-octoinv/lua/cmenu/properties/inventory.lua b/garrysmod/addons/core-octoinv/lua/cmenu/properties/inventory.lua new file mode 100644 index 0000000..f7ea04a --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/cmenu/properties/inventory.lua @@ -0,0 +1,67 @@ +properties.Add('givemoney', { + MenuLabel = L.c_language_money, + Order = 1, + MenuIcon = octolib.icons.silk16('money'), + Filter = function(self, ent, ply) + return IsValid(ent) and ent:IsPlayer() and ply:GetPos():Distance(ent:GetPos()) < 200 + and not ent:GetNetVar('Ghost') and not ply:GetNetVar('Ghost') + end, + Action = function(self, ent) + Derma_StringRequest(L.c_language_money, L.c_language_money_description, nil, function(a) + if not tonumber(a) then return end + self:MsgStart() + net.WriteEntity(ent) + net.WriteInt(math.floor(tonumber(a)), 20) + self:MsgEnd() + end) + end, + Receive = function(self, length, ply) + local ent = net.ReadEntity() + local amount = net.ReadInt(20) + if (amount or 0) <= 0 then return end + if not self:Filter(ent, ply) then return end + + if not ply:canAfford(amount) then + return ply:Notify('warning', 'У тебя нет столько денег') + end + + ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_GIVE) + + ply:addMoney(-amount) + ply:Notify(L.you_gave:format(ent:Name(), DarkRP.formatMoney(amount))) + + timer.Simple(1.2, function() + if not IsValid(ply) or not IsValid(ent) then return end + + ent:addMoney(amount) + ent:Notify(L.has_given:format(ply:Name(), DarkRP.formatMoney(amount))) + end) + + hook.Run('DarkRP.payPlayer', ply, ent, amount or 1) + + end +}) + +properties.Add('showhand', { + MenuLabel = L.show_hand, + Order = 2, + MenuIcon = octolib.icons.silk16('briefcase'), + Filter = function(self, ent, ply) + local hasHand = CLIENT or tobool(ply.inv:GetContainer('_hand')) + return IsValid(ent) and ent:IsPlayer() and + not ent:GetNetVar('Ghost') and not ply:GetNetVar('Ghost') and + hasHand and ent:GetPos():DistToSqr(ply:GetPos()) < 15000 + end, + Action = function(self, ent) + self:MsgStart() + net.WriteEntity(ent) + self:MsgEnd() + end, + Receive = function(self, length, ply) + local ent = net.ReadEntity() + if not self:Filter(ent, ply) then return end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + ent:OpenInventory(ply.inv, {'_hand'}) + end +}) diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/init.lua new file mode 100644 index 0000000..236ec72 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/init.lua @@ -0,0 +1,142 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/props_junk/garbage_bag001a.mdl' + +function ENT:Initialize() + if not self.collectableID then + self:SetCollectable({ + name = 'Золото', + collectors = {'pickaxe'}, + models = { + {'models/un/ore_un_un.mdl', color = Color(255,207,77), scale = 2.5}, + }, + drops = { + {1, {'ore_gold', 1}}, + }, + sounds = { + {'physics/concrete/boulder_impact_hard1.wav', 85}, + {'physics/concrete/boulder_impact_hard2.wav', 85}, + {'physics/concrete/boulder_impact_hard3.wav', 85}, + {'physics/concrete/boulder_impact_hard4.wav', 85}, + }, + period = 5, + max = 5, + }) + end +end + +function ENT:SetCollectableID(id, selectedModel) + + local collectable = octoinv.collectables[id] + if not collectable then return end + + self:SetCollectable(collectable, selectedModel) + +end + +function ENT:SetCollectable(collectable, selectedModel) + + self.Collectable = collectable + self.collectableID = collectable.id + + local modelData = selectedModel and octolib.table.find(collectable.models, + function(md) return md == selectedModel or md[1] == selectedModel + end) + if not modelData then + modelData = table.Random(collectable.models) + end + + if istable(modelData) then + self:SetModel(modelData[1]) + if modelData.color then self:SetColor(modelData.color) end + if modelData.skin then self:SetSkin(modelData.skin) end + if modelData.material then self:SetMaterial(modelData.material) end + if modelData.scale then self:SetModelScale(modelData.scale) end + if modelData.bodyGroups then + for k, v in pairs(modelData.bodyGroups) do + self:SetBodygroup(k, v) + end + end + else + self:SetModel(modelData) + end + + self.health = collectable.health or 100 + self:SetNetVar('dbgLook', { + name = '', + desc = collectable.name, + time = 3, + }) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:Activate() + + self:GetPhysicsObject():EnableMotion(false) + +end + +function ENT:Collect(ply, wep, tr) + + local collector = wep.Collector + if not collector then return end + + local collectable = self.Collectable + local effects = collectable.effects or {} + + if effects.hitSounds then + local data = table.Random(effects.hitSounds) + if not istable(data) then data = { data } end + self:EmitSound(unpack(data)) + end + + if effects.hit then + local effectdata = EffectData() + effectdata:SetOrigin(tr.HitPos) + effectdata:SetNormal(tr.HitNormal) + effectdata:SetScale(1) + util.Effect(effects.hit, effectdata) + end + + self.health = (self.health or 0) - collector.power + if self.health >= 0 then return end + + if effects.collectSounds then + local data = table.Random(effects.collectSounds) + if not istable(data) then data = { data } end + self:EmitSound(unpack(data)) + end + + if effects.collect then + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetNormal(self:GetUp()) + effectdata:SetScale(1) + util.Effect(effects.collect, effectdata) + end + + local drops = collectable.drops + for _ = 1, collectable.dropsAmount or 1 do + local itemData = octolib.array.randomWeighted(drops) + + local ent = ents.Create 'octoinv_item' + ent:SetPos(tr.HitPos or self:GetPos()) + ent:SetAngles(AngleRand()) + ent:Spawn() + ent:Activate() + ent:SetData(unpack(itemData)) + ent.dieTime = CurTime() + 480 + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(VectorRand() * math.random(20, 100)) + end + end + self:Remove() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/shared.lua new file mode 100644 index 0000000..92b510e --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_collectable/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' + +ENT.PrintName = 'Ресурс' +ENT.Category = 'octoinv' +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.RenderGroup = RENDERGROUP_OPAQUE diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/init.lua new file mode 100644 index 0000000..42d1a2d --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/init.lua @@ -0,0 +1,198 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/items/item_item_crate.mdl' +ENT.Skin = 0 +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true + +function ENT:Initialize() + + self:SetModel(self.Model) + self:SetSkin(self.Skin) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(self.CollisionGroup) + if self.Bodygroups then + for k, v in pairs(self.Bodygroups) do self:SetBodygroup(k, v) end + end + + local inv = self:CreateInventory() + for contID, cont in pairs(self.Containers or {}) do + local newCont = inv:AddContainer(contID, { + icon = cont.icon or octolib.icons.color('box1'), + name = cont.name, + volume = cont.volume, + craft = cont.craft, + prod = cont.prod, + }) + for _,h in ipairs(cont.hooks or {}) do + newCont:Hook(unpack(h)) + end + end + + if self.Physics then + local phys = self:GetPhysicsObject() + if phys:IsValid() then + self.baseMass = self.Mass or math.min(phys:GetMass(), 1000) + phys:Wake() + phys:SetMass(self.baseMass) + end + end + + self:SetUseType(SIMPLE_USE) + self:Activate() + + self:SetNetVar('dbgLook', { + name = '', + desc = L.can_open, + time = 1, + }) + +end + +function ENT:Use(ply, caller) + + if not ply:IsPlayer() then return end + if not ply:CanUseInventory(self.inv) then return end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not IsValid(self) then return end + if self.locked then + self:EmitSound('doors/latchlocked2.wav', 60) + if IsValid(ply) then ply:Notify(L.item_closed) end + else + ply:OpenInventory(self.inv) + end + end) + +end + +function ENT:DropUsers() + + for contID, cont in pairs(self.inv.conts) do + cont:RemoveUsers() + end + +end + +function ENT:SetLocked(val) + + self.locked = val + self:SetModel(not val and self.ModelOpen or self.Model) + self:EmitSound('doors/door_latch' .. (val and 1 or 3) .. '.wav', 55) + + if val then + for contID, cont in pairs(self.inv.conts) do + for user, _ in pairs(cont.users) do + cont:RemoveUser(user) + end + end + end + +end + +function ENT:SetPlayer(ply) + + self:LinkPlayer(ply) + self:SetNetVar('owner', ply) + +end + +function ENT:Destruct() + + if self.inv then self.inv:Remove(true) end + + if self.DestructParts then + for i, itemData in ipairs(self.DestructParts) do + local dir = VectorRand() + + local ent = ents.Create 'octoinv_item' + ent:SetPos(self:GetPos() + Vector(0,0,20)) + ent:SetAngles(AngleRand()) + + ent:Spawn() + ent:Activate() + + local owner = self:GetNetVar('owner') + if IsValid(owner) then owner:LinkEntity(ent) end + + ent:SetData(itemData[1], itemData[2]) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(dir * math.random(50, 200)) + end + end + end + + self:Remove() + +end + +function ENT:Destroy(dmg) + + self.Destroyed = true + if self.inv then self.inv:Remove(true) end + + if self.DestroyParts then + for i, itemData in ipairs(self.DestroyParts) do + local dir = VectorRand() + + local ent = ents.Create 'octoinv_item' + ent:SetPos(self:GetPos() + Vector(0,0,20)) + ent:SetAngles(AngleRand()) + + ent:Spawn() + ent:Activate() + local owner = self:GetNetVar('owner') + if IsValid(owner) then owner:LinkEntity(ent) end + + ent:SetData(itemData[1], itemData[2]) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(dir * math.random(50, 200)) + end + end + end + + if istable(self.Explode) then + local a, i = dmg:GetAttacker(), dmg:GetInflictor() + local ply = IsValid(a) and a:IsPlayer() and a or IsValid(i) and i:IsPlayer() and i or nil + local pos = self:GetPos() + util.BlastDamage(self, ply, pos, self.Explode[1] or 80, self.Explode[2] or 100) + local ef = EffectData() + ef:SetOrigin(pos) + ef:SetMagnitude(1) + util.Effect('Explosion', ef) + end + + self:Remove() + +end + +function ENT:OnTakeDamage(dmg) + + if self.ContHealth and not self.Destroyed then + self.ContHealth = self.ContHealth - dmg:GetDamage() + if self.ContHealth <= 0 then + self:Destroy(dmg) + end + end + +end + +function ENT:OnRemove() + + if self.LoopSound then self:StopSound(self.LoopSound) end + if self.WorkSound then self:StopSound(self.WorkSound) end + +end + diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/shared.lua new file mode 100644 index 0000000..dea2918 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_cont/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' + +ENT.Category = L.dobrograd +ENT.PrintName = L.container_base +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.RenderGroup = RENDERGROUP_OPAQUE diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/init.lua new file mode 100644 index 0000000..1966bc2 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/init.lua @@ -0,0 +1,132 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/props_junk/garbage_bag001a.mdl' +ENT.CollisionGroup = COLLISION_GROUP_INTERACTIVE + +local lifeTime = 1800 +function ENT:Initialize() + + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + if not IsValid(self:GetPhysicsObject()) then + self:PhysicsInitBox(Vector(-4,-4,-4), Vector(4,4,4)) + end + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(self.CollisionGroup) + + self:SetUseType(SIMPLE_USE) + self:Activate() + + self.dieTime = self.dieTime or (CurTime() + lifeTime) + self.uid = self.uid or octolib.string.uuid() + +end + +function ENT:SetData(item, data) + self.uid = self.uid or octolib.string.uuid() + + if self:GetNetVar('Item') then + timer.Remove('octoinv.entExpire' .. self.uid) + end + + self:SetNetVar('Item', { item, data }) + + local expire = istable(data) and data.expire or octoinv.items[item].expire + if expire then + timer.Create('octoinv.entExpire' .. self.uid, expire - os.time(), 1, function() + if IsValid(self) then self:Remove() end + end) + end + + self:SetModel(octoinv.getItemData('model', item, data)) + self:SetSkin(octoinv.getItemData('modelSkin', item, data) or 0) + self:SetMaterial(octoinv.getItemData('modelMaterial', item, data) or '') + self:SetColor(octoinv.getItemData('modelColor', item, data) or color_white) + + self:PhysicsInit(SOLID_VPHYSICS) + if not IsValid(self:GetPhysicsObject()) then + self:PhysicsInitBox(Vector(-4,-4,-4), Vector(4,4,4)) + end + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:Activate() + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + local amount = istable(data) and data.amount or isnumber(data) and data or 1 + local mass = istable(data) and data.mass or octoinv.items[item].mass or 0 + + self.baseMass = self.baseMass or phys:GetMass() + phys:SetMass(self.baseMass + mass * amount) + if amount < 1 then self:Remove() end + end + + self:SetNetVar('dbgLook', { + name = '', + desc = 'octoinv_item', + time = 0.5, + descRender = true, + }) + +end + +function ENT:Use(ply, caller) + + if not ply:Alive() or ply:IsGhost() then return end + + local inv = ply:GetInventory() + if not inv then + ply:Notify('warning', 'Что-то пошло не так') + return + end + + local itemData = self:GetNetVar('Item') + if not itemData then return end + + local can, why = hook.Run('octoinv.canPickup', ply, self, itemData) + if can ~= nil and not can then + ply:Notify('warning', why or L.cannotpickup) + return + end + + local cont = inv:GetContainer('_hand') or inv + + local added, item = cont:AddItem(itemData[1], itemData[2]) + if added < 1 then + ply:Notify('warning', 'У тебя не хватает места в инвентаре') + return + end + + if istable(itemData[2]) then + itemData[2].amount = itemData[2].amount and (itemData[2].amount - added) or 0 + self:SetNetVar('Item', { itemData[1], itemData[2] }) + if itemData[2].amount < 1 then self:Remove() end + elseif isnumber(itemData[2]) then + itemData[2] = itemData[2] - added + self:SetNetVar('Item', { itemData[1], itemData[2] }) + if itemData[2] < 1 then self:Remove() end + end + ply:DoAnimation(ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND) + + hook.Run('octoinv.pickup', ply, self, item, added) + +end + +function ENT:Think() + + if self.dieTime and CurTime() > self.dieTime then + self:Remove() + end + + self:NextThink(CurTime() + 30) + return true + +end + +function ENT:OnRemove() + timer.Remove('octoinv.entExpire' .. self.uid) +end \ No newline at end of file diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/shared.lua new file mode 100644 index 0000000..db9ca4c --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_item/shared.lua @@ -0,0 +1,7 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' + +ENT.Category = L.dobrograd +ENT.Author = 'chelog' + +ENT.RenderGroup = RENDERGROUP_OPAQUE diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/init.lua new file mode 100644 index 0000000..e66cb2a --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/init.lua @@ -0,0 +1,79 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/props_street/mail_dropbox.mdl' +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true + +function ENT:Initialize() + self.BaseClass.Initialize(self) + + self.inv:AddContainer('send', { + name = 'Отправка', + icon = octolib.icons.color('mailbox'), + volume = 1000, + }) + + self.deliveryContainers = {} + self.jobContainers = {} +end + +function ENT:Use(ply, caller) + if not ply:IsPlayer() then return end + if not ply:CanUseInventory(self.inv) then return end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not IsValid(self) then return end + ply:OpenInventory(self.inv, { 'send', ply:SteamID() }) + end) +end + +function ENT:Deliver(contID, items) + local cont = self.inv:GetContainer(contID) + if not cont then + cont = self.inv:AddContainer(contID, { + name = 'Получение', + icon = octolib.icons.color('mailbox'), + volume = 250, + }) + + self.deliveryContainers[contID] = cont + end + + for _, item in pairs(items) do + cont:AddItem(unpack(item)) + end +end + +function ENT:Think() + for contID, cont in pairs(self.deliveryContainers) do + if not cont.items[1] then + cont:Remove() + self.deliveryContainers[contID] = nil + end + end + + local sendCont = self.inv:GetContainer('send') + if sendCont.items[1] then + if table.IsEmpty(sendCont.users) then + for i = #sendCont.items, 1, -1 do + sendCont.items[i]:Drop(nil, nil, self:LocalToWorld(Vector(15, 0, 35)), AngleRand(), (self:GetForward() + VectorRand() * 0.2) * 100) + end + else + for ply in pairs(sendCont.users) do + if not IsValid(ply) or not ply.pendingShipments then continue end + for shipmentID, check in pairs(ply.pendingShipments) do + if check(sendCont, ply) then + octoinv.removeShipment(ply, shipmentID) + end + end + end + end + end + + self:NextThink(CurTime() + 2) + return true +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/shared.lua new file mode 100644 index 0000000..30894e2 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_mailbox/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' + +ENT.Category = L.dobrograd +ENT.PrintName = L.container_mailbox +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/init.lua new file mode 100644 index 0000000..4f5289a --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/init.lua @@ -0,0 +1,288 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +DEFINE_BASECLASS('octoinv_cont') + +ENT.Model = 'models/props_wasteland/laundry_washer003.mdl' +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true + +ENT.LoopSound = 'octoinv.prod3' +ENT.WorkSound = 'octoinv.prod13' +ENT.Containers = { + ref_fuel = { name = L.refinery_fuel, volume = 100, prod = true }, + ref = { name = L.refinery, volume = 100 }, +} + +ENT.Fuel = { + ref_fuel = { + craft_stick = 5, + craft_plank = 60, + }, +} + +ENT.Prod = { + { + time = 2, + ins = { + ref = { + {'craft_screw2', 2}, + {'craft_screwnut', 2}, + }, + }, + out = { + ref = { + {'ingot_iron', 1}, + }, + }, + }, + { + time = 5, + ins = { + ref = { + {'craft_nail', 5}, + }, + }, + out = { + ref = { + {'ingot_iron', 1}, + }, + }, + }, +} + +function ENT:Initialize() + + BaseClass.Initialize(self) + self.itemsWorking = self.itemsWorking or {} + +end + +function ENT:SetProdData(data) + + if not istable(data) then return end + + self.Prod = data.prod + self.Fuel = data.fuel + self.DestructParts = data.destruct + self.DestroyParts = data.destroy + self.Explode = data.explode + self.ContHealth = data.health + + local sounds = data.sounds + self.LoopSound = sounds and sounds.loop or 'octoinv.prod3' + self.WorkSound = sounds and sounds.work or 'octoinv.prod13' + +end + +function ENT:TakeFuel() + + if not self.Fuel then + for contID, items in pairs(self.inv.conts) do + local cont = self.inv:GetContainer(contID) + if cont.on then return 1 end + end + else + for contID, items in pairs(self.Fuel) do + local cont = self.inv:GetContainer(contID) + if not cont or not cont.on then continue end + + for class, time in pairs(items) do + if cont:HasItem(class) > 0 then + cont:TakeItem(class) + return time + end + end + end + end + + return false + +end + +function ENT:FindTask() + + -- search for a task to complete + for taskID, task in pairs(self.Prod) do + local ok = true + for contID, items in pairs(task.ins) do + local cont = self.inv:GetContainer(contID) + if not cont then + ok = false + break + end + + for i, item in ipairs(items) do + if isstring(item[1]) then + -- stackable + if cont:HasItem(item[1]) < item[2] then + ok = false + break + end + else + -- non-stackable + local res = cont:FindItem(item) + if not res then + ok = false + break + end + end + end + + if not ok then break end + end + + if ok then + self.curTask = task + self.taskStart = CurTime() + break + end + end + + if self.curTask then + -- found task, initialize + for contID, items in pairs(self.curTask.ins) do + local cont = self.inv:GetContainer(contID) + for i, item in ipairs(items) do + if isstring(item[1]) then + -- stackable + cont:TakeItem(item[1], item[2]) + else + -- non-stackable + local res = cont:FindItem(item) + if res then + self.itemsWorking[cont] = self.itemsWorking[cont] or {} + self.itemsWorking[cont][i] = res + cont:TakeItem(res) + end + end + end + end + + return true + else + return false + end + +end + +function ENT:FinishTask() + + if not self.curTask then return end + + for contID, items in pairs(self.curTask.out) do + local cont = self.inv:GetContainer(contID) + for i, item in pairs(items) do + if isfunction(item) then + local res = self.itemsWorking[cont] and self.itemsWorking[cont][i] + if res then + cont:AddItem(res) + item(res) + end + else + local chance, class, amountOrData = unpack(item) + if isstring(chance) then + chance = 1 + class, amountOrData = unpack(item) + end + local amount = isnumber(amountOrData) and amountOrData or 1 + if math.random() > chance then continue end + + local added = cont:AddItem(class, amountOrData) + if added < amount then + cont:DropNewItem(class, isnumber(amountOrData) and (amount - added) or amountOrData) + end + end + end + end + self.itemsWorking = {} + self.curTask = nil + +end + +function ENT:FailTask() + + if not self.curTask then return end + + for contID, items in pairs(self.curTask.ins) do + local cont = self.inv:GetContainer(contID) + for i, item in ipairs(items) do + if isstring(item[1]) then + local added = cont:AddItem(item[1], item[2]) + if isnumber(item[2]) and isnumber(added) and added < item[2] then + cont:DropNewItem(item[1], item[2] - added) + end + else + local res = self.itemsWorking[cont] and self.itemsWorking[cont][i] + if res then + if cont:SpaceFor(res) > 0 then + cont:AddItem(res) + else + cont:DropNewItem(res) + end + end + end + end + end + self.itemsWorking = {} + self.curTask = nil + +end + +function ENT:Think() + + if not self.inv then return end + + -- check if is working and on fuel + if not self.working then + local time = self:TakeFuel() + if time then + self.working = CurTime() + time + self:EmitSound(self.LoopSound) + else + self:NextThink(CurTime() + 1) + return true + end + elseif CurTime() > self.working then + local time = self:TakeFuel() + if time then + self.working = self.working + time + else + self.working = nil + self:StopSound(self.LoopSound) + + if self.curTask then + if CurTime() > self.taskStart + self.curTask.time then + self:FinishTask() + else + self:FailTask() + end + self:StopSound(self.WorkSound) + end + + self:NextThink(CurTime() + 1) + return true + end + end + + -- finish task if has one and time passed + if self.curTask then + if CurTime() > self.taskStart + self.curTask.time then + self:FinishTask() + if not self:FindTask() then + self:StopSound(self.WorkSound) + end + end + else + if self:FindTask() then + self:EmitSound(self.WorkSound) + else + -- no task meets requirements, take a rest + self:NextThink(CurTime() + 1) + return true + end + end + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/shared.lua new file mode 100644 index 0000000..3b2ba98 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_prod/shared.lua @@ -0,0 +1,163 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' + +ENT.Category = L.dobrograd +ENT.PrintName = L.container_prod +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.RenderGroup = RENDERGROUP_OPAQUE + +sound.Add { + name = 'octoinv.prod1', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/laundry_machine1_amb.wav', +} + +sound.Add { + name = 'octoinv.prod2', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/air_conditioner_loop_1.wav', +} + +sound.Add { + name = 'octoinv.prod3', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/refinery_loop_1.wav', +} + +sound.Add { + name = 'octoinv.prod4', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/pump_loop_1.wav', +} + +sound.Add { + name = 'octoinv.prod5', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/train_idle.wav', +} + +sound.Add { + name = 'octoinv.prod6', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/spin_loop.wav', +} + +sound.Add { + name = 'octoinv.prod7', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/lab_loop1.wav', +} + +sound.Add { + name = 'octoinv.prod8', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/ticktock.wav', +} + +sound.Add { + name = 'octoinv.prod9', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/electric_machine.wav', +} + +sound.Add { + name = 'octoinv.prod10', + channel = CHAN_STATIC, + volume = 1, + level = 60, + pitch = 100, + sound = 'ambient/machines/deep_boil.wav', +} + +sound.Add { + name = 'octoinv.prod11', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/machine2.wav', +} + +sound.Add { + name = 'octoinv.prod12', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/machine6.wav', +} + +sound.Add { + name = 'octoinv.prod13', + channel = CHAN_STATIC, + volume = 1, + level = 70, + pitch = 100, + sound = 'ambient/machines/engine4.wav', +} + +sound.Add { + name = 'octoinv.prod14', + channel = CHAN_STATIC, + volume = 1, + level = 55, + pitch = 100, + sound = 'ambient/machines/power_transformer_loop_2.wav', +} + +sound.Add { + name = 'octoinv.prod15', + channel = CHAN_STATIC, + volume = 1, + level = 55, + pitch = 100, + sound = 'ambient/machines/gas_loop_1.wav', +} + +sound.Add { + name = 'octoinv.prod16', + channel = CHAN_STATIC, + volume = 1, + level = 50, + pitch = 100, + sound = 'ambient/machines/60hzhum.wav', +} + +sound.Add { + name = 'octoinv.prod17', + channel = CHAN_STATIC, + volume = 1, + level = 35, + pitch = 100, + sound = 'ambient/levels/labs/equipment_printer_loop1.wav', +} diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/init.lua new file mode 100644 index 0000000..4549a2e --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/init.lua @@ -0,0 +1,339 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +local storageVolume = 225 +local storageVolumePlus = 350 + +ENT.Model = 'models/props/cs_militia/footlocker01_closed.mdl' +ENT.ModelOpen = 'models/props/cs_militia/footlocker01_open.mdl' +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true + +function ENT:Initialize() + + -- self:SetModel(self.Model) + self:SetLocked(true) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(self.CollisionGroup) + + self:LoadInv() + + if self.Physics then + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + phys:SetMass(math.min(phys:GetMass(), 1000)) + end + + self.baseMass = phys:GetMass() + self.invMassMultiplier = 5 + end + + self.lockNum = 6 + + self:SetUseType(SIMPLE_USE) + self:Activate() + self.spawned = true + + self:SetNetVar('dbgLook', { + name = '', + desc = L.can_open, + time = 1, + }) + +end + +function ENT:LoadInv() + + if not self.steamID then + return self:Remove() + end + + -- prevent duplicating (just in case) + for i, ent in ipairs(ents.FindByClass('octoinv_storage')) do + if ent ~= self and ent.steamID == self.steamID then + return self:Remove() + end + end + + local ply = player.GetBySteamID(self.steamID) + if not IsValid(ply) or IsValid(ply.storage) then + return self:Remove() + end + + self.ply = ply + ply.storage = self + local volume = ply:GetDBVar('storageVol') or (ply:GetNetVar('os_storage') and storageVolumePlus) or storageVolume + self:SetNetVar('owner', ply) + + octolib.db:PrepareQuery([[ + SELECT storage FROM inventory + WHERE steamID = ? + ]], { self.steamID }, function(q, st, res) + res = istable(res) and res[1] + if res and res.storage then + local data + if not pcall(function() + data = pon.decode(res.storage) + end) then + ply:Notify('warning', L.error_storage) + end + + if data and data.storage then + data.storage.name = L.storage_hint .. ply:Name() + data.storage.volume = volume + data.storage.icon = octolib.icons.color('case_travel') + self:ImportInventory(data) + local cont = self.inv:GetContainer('storage') + cont:Hook('canMoveIn', 'noZips', function(cont, ply, item) + if item and item.class == 'zip' then + return false, 'Это запрещено перемещать в хранилище' + end + end) + octoinv.msg('Loaded storage for ' .. self.steamID) + return + end + end + + self:ImportInventory({ + storage = { + name = L.storage_hint .. ply:Name(), + volume = volume, + icon = octolib.icons.color('case_travel'), + items = {}, + }, + }) + + octoinv.msg('New storage for ' .. self.steamID) + self:Save() + end) + +end + +function ENT:Save(callback) + + if not self.inv then + timer.Simple(1, function() + if not IsValid(self) or not self.Save then return end + self:Save(callback) + end) + return + end + + local inv = self:ExportInventory() + inv.storage.name = nil + inv.storage.volume = nil + inv.storage.icon = nil + + local steamID = self.steamID + octolib.db:PrepareQuery([[ + UPDATE inventory + SET storage = ? + WHERE steamID = ? + ]], { pon.encode(inv), steamID }, function(q, st, res) + -- octoinv.msg((st and 'Saved storage for ' or 'Failed to save storage for ') .. steamID) + if not st then + octoinv.msg(res) + end + if callback then callback() end + end) + +end + +function ENT:Use(ply, caller) + + if not ply:IsPlayer() then return end + if not ply:CanUseInventory(self.inv) then return end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not IsValid(self) then return end + if self.locked then + self:EmitSound('doors/latchlocked2.wav', 60) + if IsValid(ply) then ply:Notify(L.item_closed) end + else + local owner = player.GetBySteamID(self.steamID) + if ply:Team() == TEAM_ADMIN or IsValid(owner) and (owner == ply or (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true))) then + ply:OpenInventory(self.inv) + else + ply:Notify(L.you_not_friend) + end + end + end) + +end + +function ENT:SetLocked(val) + + self.locked = val + self:SetModel(not val and self.ModelOpen or self.Model) + self:EmitSound('doors/door_latch' .. (val and 1 or 3) .. '.wav', 55) + +end + +function ENT:OnRemove() + + if self.spawned then + self:Save() + + local ply = player.GetBySteamID(self.steamID or '') + if IsValid(ply) then ply:SetDBVar('nextStorage', nil) end + end + +end + +function octoinv.getStorageData(ply) + return util.Promise(function(res, rej) + + if IsValid(ply.storage) then + res(ply.storage.inv.conts.storage:Export()) + else + octolib.db:PrepareQuery('select storage from inventory where steamID = ?', { ply:SteamID() }, function(q, st, rows) + if not rows[1] or not rows[1].storage then return res({}) end + res(pon.decode(rows[1].storage).storage) + end) + end + + end) +end + +function octoinv.takeItemFromStorage(ply, classOrID, amountOrData) + return util.Promise(function(res, rej) + + octoinv.getStorageData(ply):Then(function(storage) + if isnumber(classOrID) then + -- it's non-stackable item with data to validate + local item = storage.items[classOrID] + if not item or (amountOrData and table.Count(octolib.table.diff(item, amountOrData)) > 0) then + return rej('cannot find matching item in storage') + end + + if IsValid(ply.storage) then + ply.storage.inv.conts.storage.items[classOrID]:Remove() + res() + else + table.remove(storage.items, classOrID) + octolib.db:PrepareQuery('update inventory set storage = ? where steamID = ?', { pon.encode({ storage = storage }), ply:SteamID() }) + res() + end + else + -- it's stackable item with amount to validate + local item, id + for _id, _item in ipairs(storage.items) do + if _item.class == classOrID then + item = _item + id = _id + break + end + end + + local amount = amountOrData or 1 + if not item or item.amount < amount then + return rej('cannot found enough items of this class') + end + + if IsValid(ply.storage) then + local taken = ply.storage.inv.conts.storage:TakeItem(classOrID, amount) + if taken < amount then return rej('cannot found enough items of this class') end -- just in case + res() + else + item.amount = item.amount - amount + if item.amount <= 0 then table.remove(storage.items, id) end + octolib.db:PrepareQuery('update inventory set storage = ? where steamID = ?', { pon.encode({ storage = storage }), ply:SteamID() }) + res() + end + end + end) + + end) +end + +hook.Add('PlayerInitialSpawn', 'octoinv.storage', function(ply) + + -- for i, ent in ipairs(ents.FindByClass('octoinv_storage')) do + -- if ent.steamID == ply:SteamID() then + -- ent:LoadInv() + -- end + -- end + +end) + +hook.Add('PlayerDisconnected', 'octoinv.storage', function(ply) + + local has = false + for i, ent in ipairs(ents.FindByClass('octoinv_storage')) do + if ent.steamID == ply:SteamID() then + ply:SetDBVar('nextStorage', os.time() + 630) + ent:Save() + has = true + end + end + + if not has and ply:GetDBVar('nextStorage', 0) < os.time() then + ply:SetDBVar('nextStorage', nil) + end + +end) + +hook.Add('octoinv.canLock', 'octoinv.storage', function(ply, ent) + if ent:GetNetVar('owner') == ply then + return true + end +end) + +hook.Add('octoinv.canUnlock', 'octoinv.storage', function(ply, ent) + if ent:GetNetVar('owner') == ply then + return true + end +end) + +concommand.Add('dbg_storage', function(ply) + + if ply.restoringBackup then + ply:Notify('warning', 'Подожди, загружаем твои данные...') + return + end + + if hook.Run('octoinv.overrideStorages', ply) == false then + ply:Notify('warning', 'Хранилища отключены на время ивента') + return + end + + if IsValid(ply.storage) or ply:GetDBVar('nextStorage', 0) > os.time() then + ply:Notify('warning', L.you_already_have_storage) + return + end + + if not ply:Alive() or ply:IsGhost() then + ply:Notify('warning', L.dead_cant_do_this) + return + end + + if ply:IsHandcuffed() then + ply:Notify('warning', L.error_cuffs) + return + end + + if ply:isArrested() then + ply:Notify('warning', L.you_arrested) + return + end + + local ent = ents.Create 'octoinv_storage' + ent.dt = ent.dt or {} + ent.dt.owning_ent = ply + + ent.SID = ply.SID + ent.steamID = ply:SteamID() + ent:Spawn() + + ply:BringEntity(ent) + ent:LinkPlayer(ply) + + hook.Run('octoinv.storageSpawned', ply, ent) + +end) diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/shared.lua new file mode 100644 index 0000000..5af7c5d --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_storage/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' + +ENT.Category = L.dobrograd +ENT.PrintName = L.container_storage +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.RenderGroup = RENDERGROUP_OPAQUE diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/cl_init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/cl_init.lua new file mode 100644 index 0000000..44e27ed --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/cl_init.lua @@ -0,0 +1,83 @@ +include 'shared.lua' + +surface.CreateFont('octoinv-3d.normal', { + font = 'Calibri', + extended = true, + size = 32, + weight = 350, +}) + +local colors = CFG.skinColors +function ENT:Draw() + + self:DrawModel() + + local al = math.Clamp(1 - (self:GetPos():DistToSqr(EyePos()) - 40000) / 40000, 0, 1) + if al > 0 then + local pos, ang = LocalToWorld(Vector(8.15, -23, 43), Angle(0, 90, 90), self:GetPos(), self:GetAngles()) + cam.Start3D2D(pos, ang, 0.1) + surface.SetAlphaMultiplier(al) + draw.RoundedBox(8, 0, 0, 350, 580, colors.bg50) + local data = self:GetNetVar('contents') + if data then + for i, item in ipairs(data) do + local x, y = 5, 5 + (i-1) * 83 + draw.RoundedBoxEx(8, x + 70, y, 270, 70, Color(170,119,102), false, true, false, true) + draw.RoundedBoxEx(8, x, y, 70, 70, color_white, true, false, true, false) + if item.icon then + surface.SetDrawColor(255,255,255) + surface.SetMaterial(Material(item.icon)) + surface.DrawTexturedRect(x+3, y+3, 64, 64) + + draw.SimpleText(DarkRP.formatMoney(item.price), '3d.medium', x+330, y+45, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + local name = item.name + surface.SetFont('octoinv-3d.normal') + local tw, th = surface.GetTextSize(name) + -- if item.amount then name = string.format('(%s) %s', item.amount, name) end + if tw > 250 then + render.ClearStencil() + render.SetStencilEnable(true) + + render.SetStencilWriteMask(1) + render.SetStencilTestMask(1) + render.SetStencilReferenceValue(1) + + render.SetStencilCompareFunction(STENCIL_NEVER) + render.SetStencilFailOperation(STENCIL_REPLACE) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + + surface.SetDrawColor(0,0,0, 255) + surface.DrawRect(x + 71, y + 1, 268, 78) + + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + render.SetStencilReferenceValue(1) + render.SetStencilFailOperation(STENCILOPERATION_ZERO) + render.SetStencilZFailOperation(STENCILOPERATION_ZERO) + render.SetStencilPassOperation(STENCILOPERATION_KEEP) + + draw.SimpleText(name, 'octoinv-3d.normal', x+330 + (math.sin(CurTime() / 2) + 1) / 2 * (tw - 250), y+19, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + + render.SetStencilEnable(false) + else + draw.SimpleText(name, 'octoinv-3d.normal', x+330, y+19, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + else + surface.SetDrawColor(255,255,255) + surface.SetMaterial(Material('octoteam/icons/refresh.png')) + surface.DrawTexturedRect(x+3, y+3, 64, 64) + + draw.SimpleText(L.empty_hint, '3d.medium', x+330, y+32 , Color(255,255,255, 50), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + else + draw.SimpleText(L.vend_hint, '3d.medium', 175, 250, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(L.vend_empty, '3d.medium', 175, 280, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(L.vend_of_managment, 'octoinv-3d.normal', 175, 520, Color(255,255,255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(L.vend_press_use, 'octoinv-3d.normal', 175, 545, Color(255,255,255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + surface.SetAlphaMultiplier(1) + cam.End3D2D() + end + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/init.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/init.lua new file mode 100644 index 0000000..360d612 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/init.lua @@ -0,0 +1,193 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/props/apoc/vendingmachine.mdl' +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true + +ENT.slots = {} +ENT.Containers = { + vend = { name = L.vend_money, volume = 50, icon = octolib.icons.color('machine_vend') }, +} + +function ENT:Initialize() + + self.BaseClass.Initialize(self) + + self:SetSlotsNum(7) + self.lockNum = 6 + +end + +function ENT:Use(ply, caller) + + if not ply:IsPlayer() then return end + if not ply:CanUseInventory(self.inv) then return end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not IsValid(self) then return end + if self.locked then + if self.owner == ply then + ply:Notify(L.vend_manage) + else + ply:Notify(L.for_buy_vend) + end + else + ply:OpenInventory(self.inv) + end + end) + +end + +function ENT:DropFromSlot(id) + + if not self.slots[id] then return end + + local item = self.slots[id].cont.items[1] + if item then + local pos, ang = LocalToWorld(Vector(0,10,-30), Angle(0,0,0), self:GetPos(), self:GetAngles()) + item:Drop(1, nil, pos, ang, self:GetForward() * 150) + + self:UpdateContents() + end + +end + +function ENT:UpdateContents() + + if not self.locked then + self:SetNetVar('contents', nil) + return + end + + local data = {} + for i, slot in ipairs(self.slots) do + local item = slot.cont.items[1] + if item and item:GetData('amount') > 0 then + table.insert(data, { + icon = item:GetData('icon'), + name = item:GetData('name'), + price = slot.price, + }) + else + table.insert(data, {}) + end + end + + self:SetNetVar('contents', data) + +end + +local restricted = octolib.array.toKeys { + 'money', 'weapon', 'armor', 'ammo', 'lockpick', 'drug_vitalex', 'drug_painkiller', 'drug_relaxant', 'drug_vampire', + 'drug_dextradose', 'drug_roids', 'drug_bouncer', 'drug_antitoxin', 'drug_weed', 'drug_pingaz', 'drug_preserver', 'drug_meth', +} + +local function canMoveIn(cont, ply, item, contFrom) + + if item and item.class and restricted[item.class] then + return false, L.forbidden_to_load + end + +end + +function ENT:SetSlotsNum(slots) + + local pos, ang = LocalToWorld(Vector(0,10,-30), Angle(0,0,0), self:GetPos(), self:GetAngles()) + for i, slot in ipairs(self.slots) do + slot.cont:Remove(true, pos, ang, self:GetForward() * 150) + end + + self.slots = {} + for i = 1, slots do + self.slots[i] = { + cont = self.inv + :AddContainer('vend' .. i, {name = L.vend_slot .. i, volume = 30, icon = octolib.icons.color('machine_vend')}) + :Hook('canMoveIn', 'vend', canMoveIn), + price = 0 + } + end + + self:UpdateContents() + +end + +function ENT:SetPrices(prices) + + self:SetLocked(true) + for i, slot in ipairs(self.slots) do slot.price = 0 end + + local set = {} + for k, price in pairs(prices) do + if not self.slots[k] then continue end + + local amount = tonumber(prices[k]) + if set[amount] then + self:SetLocked(false) + return + end + + self.slots[k].price = math.floor(amount or 0) + set[amount] = true + end + + self:UpdateContents() + return true + +end + +function ENT:Think() + + if not self.locked and self:GetNetVar('contents') then + self:UpdateContents() + end + + local cont = self.inv.conts.vend + for i, item in ipairs(cont.items) do + if item.class ~= 'money' then + local pos, ang = LocalToWorld(Vector(0,10,-30), Angle(0,0,0), self:GetPos(), self:GetAngles()) + item:Drop(nil, nil, pos, ang, self:GetForward() * 150) + end + end + + self:NextThink(CurTime() + 1) + return true + +end + +function ENT:StartTouch(ent) + + if not IsValid(ent) or ent:GetClass() ~= 'octoinv_item' or not ent:GetNetVar('Item') or ent.aquired then return end + local ply = ent.droppedBy + + if not self.locked then + if IsValid(ply) then ply:Notify('warning', L.vend_need_closed) end + return + end + + local amount, slotID = ent:GetNetVar('Item')[2] + for i, slot in ipairs(self.slots) do + if slot.price == amount then slotID = i break end + end + + if not slotID then + if IsValid(ply) then ply:Notify('warning', L.vend_accurate_money) end + return + end + + local item = self.slots[slotID].cont.items[1] + if not item or item:GetData('amount') < 1 then + if IsValid(ply) then ply:Notify('warning', L.vend_slot_empty) end + return + end + + self:DropFromSlot(slotID) + + ent.aquired = true + ent:Remove() + self.inv.conts.vend:AddItem('money', amount) + +end diff --git a/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/shared.lua b/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/shared.lua new file mode 100644 index 0000000..2d7988f --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/entities/octoinv_vend/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' + +ENT.Category = L.dobrograd +ENT.PrintName = L.container_vend +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_give.lua b/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_give.lua new file mode 100644 index 0000000..7e1d034 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_give.lua @@ -0,0 +1,1264 @@ +surface.CreateFont('octoinv-help.heading', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) + +surface.CreateFont('octoinv-help.subheading', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local defaultData = { + amount = 1, + volume = 0, + mass = 0, + name = L.unknown, + desc = L.not_desc, + icon = 'octoteam/icons/error.png', + model = 'models/props_junk/garbage_bag001a.mdl', +} + +local function classData(class, field) + local data = octoinv.items[class] + return data and data[field] or defaultData[field] +end + +local function itemData(item, field) + + local data = octoinv.items[item.class or item[1]] + return istable(item[2]) and item[2][field] or item[field] or (data and data[field]) or defaultData[field] + +end + +local function iconPath(icon, class) + + if not icon then + return class and classData(class, 'icon') or defaultData.icon + elseif isstring(icon) then + return icon + else + return icon:GetName() .. '.png' + end + +end + +-- +-- ADMIN THINGIES +-- + +local editorWindows = {} +local f +concommand.Add('octoinv_editor', function() + + if not LocalPlayer():query(L.permissions_edit_inventory) then + notification.AddLegacy(L.not_have_access, NOTIFY_ERROR, 5) + return + end + + if IsValid(f) then f:Remove() end + f = vgui.Create 'DFrame' + f:SetSize(400, 500) + f:SetTitle(L.octoinv_editor) + f:Center() + f:MakePopup() + + local bp = f:Add 'DPanel' + bp:DockPadding(5, 5, 5, 5) + bp:Dock(TOP) + bp:SetTall(40) + + local e1 = bp:Add 'DTextEntry' + e1:DockMargin(0, 0, 5, 0) + e1:Dock(LEFT) + e1:SetWide(190) + e1:SetPlaceholderText(L.search_steamid) + function e1:OnEnter() + local val = self:GetText() + if string.Trim(val) ~= '' then + netstream.Start('octoinv.search', { steamID = val }) + end + end + + local e2 = bp:Add 'DTextEntry' + e2:DockMargin(0, 0, 0, 0) + e2:Dock(RIGHT) + e2:SetWide(190) + e2:SetPlaceholderText(L.search_content) + function e2:OnEnter() + local val = self:GetText() + if string.Trim(val) ~= '' then + netstream.Start('octoinv.search', { query = val }) + end + end + + local lp = f:Add 'DPanel' + lp:DockMargin(0, 5, 0, 0) + lp:DockPadding(5, 5, 5, 5) + lp:Dock(FILL) + + local l = lp:Add 'DListView' + l:Dock(FILL) + l:AddColumn('SteamID') + l:AddColumn(L.name_octoinv) + l:SetMultiSelect(false) + f.l = l + + function l:OnRowRightClick(id, line) + local sID = line.steamID + local m = DermaMenu() + if not IsValid(editorWindows[line.steamID]) then + m:AddOption(L.edit, function() netstream.Start('octoinv.editor.open', line.steamID, line:GetColumnText(2)) end):SetIcon('icon16/pencil.png') + end + m:AddOption(L.copy_steamid, function() SetClipboardText(sID) end):SetIcon('icon16/page_copy.png') + m:AddOption(L.open_profile, function() octoesc.OpenURL('https://steamcommunity.com/profiles/' .. util.SteamIDTo64(sID)) end):SetIcon('icon16/report_user.png') + m:Open() + end + function l:DoDoubleClick(id, line) + if not IsValid(editorWindows[line.steamID]) then + netstream.Start('octoinv.editor.open', line.steamID, line:GetColumnText(2)) + end + end + +end) + +local removers = { + toilet = 'Смой предметы, которые нужно удалить, в унитаз.', + denture = 'Скорми предметы, которые нужно удалить, Ненасытному Жоре.', + explosion = 'Брось предметы, которые нужно удалить, в эпицентр взрыва.', + explosion2 = 'Выкинь предметы, которые нужно удалить, в эпицентр термоядерного взрыва.', + zip_file = 'Собери предметы, которые нужно удалить, в ZIP-пакет.', + robber = 'Отдай предметы, которые нужно удалить, беспощадному краймеру.', + group3 = 'Пожертвуй предметы, которые нужно удалить, в Фонд Нуждающихся Игроков.', + chem_plant = 'Отправь предметы, которые нужно удалить, на мусороперерабатывающую фабрику.', + gift_octo_pride = 'Преподнеси предметы, которые нужно удалить, в жертву Челогу.', + clothes_backpack2 = 'Запихни предметы, которые нужно удалить, в старый рюкзак.', + bottle_vodka = 'Окуни предметы, которые нужно удалить, в пузырь растворителя.', + heart = 'Предай предметы, которые нужно удалить, бесконечной любви.', + inbox = 'Отложи предметы, которые нужно удалить, в долгий ящик.', + potion3 = 'Приготовь зелье из предметов, которые нужно удалить, в странной колбе.', + shop2 = 'Сдай предметы, которые нужно удалить, в секонд-хенд.', + truck = 'Вывези предметы, которые нужно удалить, на грузовике "".', + hand = 'Положи предметы, которые нужно удалить, в руку попрошайки.', + slot_machine = 'Проиграй предметы, которые нужно удалить, в игровом автомате.', + food_chips = 'Подкинь предметы, которые нужно удалить, в пакет чипсов твоего друга.', + camera = 'Помести предметы, которые нужно удалить, в камеру.', + spy = 'Сообщи о предметах, которые нужно удалить, иностранному агенту.', + cross = 'Удали предметы, которые нужно удалить, через соответствующий контейнер.', + chip1 = 'Загрузи предметы, которые нужно удалить, на ненадёжный файлообменник.', +} + +netstream.Hook('octoinv.editor.open', function(data) + + if IsValid(editorWindows[data.steamID]) then editorWindows[data.steamID]:Remove() end + + local w = vgui.Create 'DFrame' + editorWindows[data.steamID] = w + + w:SetWide(375) + w:SetTall(data.noEdit and 205 or 240) + w:AlignBottom(20) + w:AlignLeft(20) + w:SetTitle(data.steamID .. L.steamid_edit) + w:MakePopup() + w.saved = true + + steamworks.RequestPlayerInfo(util.SteamIDTo64(data.steamID), function(n) + if IsValid(w) then + w:SetTitle(data.steamID .. ' (' .. n .. ')' .. L.steamid_edit) + end + end) + if data.noEdit then + octolib.label(w, data.noEdit) + end + local lbl = octolib.label(w, 'В твой инвентарь добавлены контейнеры Куртки, Брюк и Хранилища.') + local wide = math.max(375, surface.GetTextSize(lbl:GetText()) + 10) + + local bank = octolib.numberWang(w, 'Банковский счет:', data.bank or 0, -2147483647, 2147483647) + bank:SetEnabled(not data.noEdit) + bank.savedVal = data.bank or 0 + function bank:OnValueChanged(val) + if val ~= self.savedVal then + w.saved = false + end + end + + local karma = octolib.numberWang(w, 'Карма:', data.karma or 0, -2147483647, 2147483647) + karma:SetEnabled(not data.noEdit) + karma.savedVal = data.karma or 0 + karma.OnValueChanged = bank.OnValueChanged + + local btmPan = w:Add('DPanel') + btmPan:Dock(BOTTOM) + btmPan:SetTall(25) + btmPan:SetPaintBackground(false) + + if not data.noEdit then + lbl = octolib.label(w, removers[data.remover]) + wide = math.max(wide, surface.GetTextSize(lbl:GetText()) + 10) + lbl = octolib.label(w, 'Чтобы сохранить изменения, нажми на кнопку "Сохранить" ниже.') + wide = math.max(wide, surface.GetTextSize(lbl:GetText()) + 10) + octolib.label(w, 'Чтобы завершить редактирование, закрой это окно.') + wide = math.max(wide, surface.GetTextSize(lbl:GetText()) + 10) + surface.SetFont('DermaDefault') + w:SetWide(math.max(375, surface.GetTextSize(removers[data.remover]) + 10)) + + local saveBtn = octolib.button(btmPan, 'Сохранить', function() + netstream.Start('octoinv.editor.save', data.steamID, bank:GetValue(), karma:GetValue()) + w.saved = true + bank.savedVal, karma.savedVal = bank:GetValue(), karma:GetValue() + end) + saveBtn:Dock(RIGHT) + saveBtn:SetWide(100) + end + + local invBtn = octolib.button(btmPan, 'Инвентарь', function() RunConsoleCommand('+menu') end) + invBtn:Dock(RIGHT) + invBtn:DockMargin(0, 0, 5, 0) + invBtn:SetWide(100) + + w.oClose = w.Close + function w:Close() + if not self.saved then + octolib.confirmDialog(self, 'Сохранить изменения?', function(ans) + if ans then + netstream.Start('octoinv.editor.save', data.steamID, bank:GetValue(), karma:GetValue()) + end + self:oClose() + end) + else self:oClose() end + end + function w:OnRemove() + netstream.Start('octoinv.editor.close', data.steamID) + end + + octoinv.notifyChange() + +end) + +netstream.Hook('octoinv.editor.change', function(steamID) + if IsValid(editorWindows[steamID]) then + editorWindows[steamID].saved = false + else + netstream.Start('octoinv.editor.close', steamID) + end +end) + +netstream.Hook('octoinv.search', function(data) + + if not IsValid(f) then return end + + f.l:Clear() + if isstring(data) then + notification.AddLegacy(L.error_search .. data, NOTIFY_ERROR, 5) + return + end + + for i, v in ipairs(data) do + local line = f.l:AddLine(v.steamID, '') + steamworks.RequestPlayerInfo(util.SteamIDTo64(v.steamID), function(n) line:SetColumnText(2, n) end) + line.steamID = v.steamID + end + +end) + +-- +-- ADD ITEM WINDOW +-- +local function setupLabel(parent, text) + + local l = parent:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5, 0, 5, 0) + l:SetTall(16) + l:SetText(text) + + return l + +end + +local function setupEntry(parent, text, val, onUpdate) + + setupLabel(parent, text) + + local e = parent:Add 'DTextEntry' + e:Dock(TOP) + e:DockMargin(0,0,0,5) + e:SetTall(25) + e:SetUpdateOnType(true) + e:SetPlaceholderText(text) + e:SetValue(val) + e.OnValueChange = onUpdate + + return e + +end + +local function setupSlider(parent, text, data, onUpdate) + + local e = parent:Add 'DNumSlider' + e:Dock(TOP) + e:DockMargin(0,0,0,5) + e:SetTall(25) + e:SetText(text) + e:SetMin(data[1] or 0) + e:SetMax(data[2] or 100) + e:SetDecimals(data[3] or 1) + e:SetValue(data[1] or 0) + e.OnValueChanged = onUpdate + + return e + +end + +local function setupCombo(parent, text, opts, onUpdate) + + setupLabel(parent, text) + + local e = parent:Add 'DComboBox' + e:Dock(TOP) + e:DockMargin(0,0,0,5) + e:SetTall(25) + for i, v in ipairs(opts) do e:AddChoice(unpack(v)) end + e:ChooseOptionID(1) + e.OnSelect = onUpdate + + return e + +end + +local function setupCheckBox(parent, text, bool, onUpdate) + + local e = parent:Add 'DCheckBoxLabel' + e:Dock(TOP) + e:DockMargin(0,0,0,5) + e:SetTall(25) + e:SetText(text) + e:SetValue(bool or false) + e.OnChange = onUpdate + + return e +end + +octoinv.itemCreateFields = octoinv.itemCreateFields or {} + +octoinv.itemCreateFields['food'] = { + {'slider', L.energy, {0, 100, 0}, function(item, val) item.energy = math.Round(val) end, function(item) return item.energy end}, + {'slider', L.max_energy, {0, 100, 0}, function(item, val) item.maxenergy = math.Round(val) end, function(item) return item.maxenergy end}, + {'checkbox', 'Напиток', false, function(item, val) item.drink = val or nil end, function(item) return item.drink end}, + {'checkbox', 'Оставляет мусор', false, function(item, val) item.trash = val or nil end, function(item) return item.trash end}, +} + +octoinv.itemCreateFields['lock_cont'] = { + {'slider', L.lock_cont, {1, 20, 0}, function(item, val) + val = math.Round(val) + item.name = val .. L.item_lock + item.num = val + end, function(item) return item.num end}, + {'entry', 'Пароль', '', function(item, val) + item.password = string.Trim(val) ~= '' and val or nil + end, function(item) return item.password end}, +} + +octoinv.itemCreateFields['cont'] = { + {'slider', L.slider, {1, 1000, 0}, function(item, val) + val = math.Round(val) + item.contdata = item.contdata or {} + if item.model then item.contdata.model = item.model end + item.contdata.conts = { box = { name = item.name, volume = val }} + end, function(item) + return item.contdata and item.contdata.conts and item.contdata.conts.box and item.contdata.conts.box.volume or 1 + end}, +} + +octoinv.itemCreateFields['key'] = { + {'entry', 'Пароль', '', function(item, val) + item.password = string.Trim(val) ~= '' and val or nil + end, function(item) + return item.password + end}, +} + +octoinv.itemCreateFields['door'] = { + {'slider', 'Скин двери', {0, 13, 0}, function(item, val) + item.name = L['item_door' .. math.Round(val)] + item.skin = val + end, function(item) + return item.skin + end}, +} + +octoinv.itemCreateFields['door_handle'] = { + {'slider', 'Вид ручки', {1, 2, 0}, function(item, val) + val = math.Round(val) + item.name = L['item_door_handle' .. val] + item.handle = val + end, function(item) + return item.handle + end}, +} + +octoinv.itemCreateFields['fish_line'] = { + {'combo', 'Вид', { + {'Тонкая', {true, 'thin'}}, + {'Крепкая', {false, 'thick'}}, + }, function(item, name, data) + item.name = name .. ' леска' + item.thin = data[1] + item.icon = 'octoteam/icons/fishing_line_' .. data[2] .. '.png' + end, function(item) + return item.name + end}, +} + +octoinv.itemCreateFields['fish_bait'] = { + {'combo', 'Содержимое', { + {'Бекон', {'Наживка из бекона', 'bacon'}}, + {'Сыр', {'Насадка из сыра', 'cheese'}}, + {'Черви', {'Черви', 'classic', 'worm'}}, + {'Живцы', {'Живцы', 'fish'}}, + {'Креветки', {'Наживка из креветок', 'prawn', 'shrimp'}}, + {'Светящиеся окуни', {'Светящиеся окуни', 'synthetic', 'glowing_perch'}}, + }, function(item, name, data) + item.name = data[1] + item.bait = data[2] + item.icon = 'octoteam/icons/bait_' .. (data[3] or data[2]) .. '.png' + end, function(item) + return item.name + end}, +} + +octoinv.itemCreateFields['throwable'] = { + {'combo', 'Вид', { + {'Светошумовые гранаты', {'ent_dbg_grenade_stun', 'Оглушают и ослепляют на короткое время', 'weapon_grenade_flash'}}, + {'Шоковые гранаты', {'ent_dbg_grenade_shock', 'Оглушают и ослепляют на короткое время. Заставляют выкинуть оружие и испускают немного слезоточивого газа', 'weapon_grenade_shock'}}, + {'Дымовые гранаты', {'ent_dbg_grenade_smoke', 'Заполняют непроглядным дымом все помещение', 'weapon_grenade_smoke'}}, + {'Газовые гранаты', {'ent_dbg_grenade_gas', 'При разрыве испускают слезоточивый газ. Не действует при надетом противогазе', 'weapon_grenade_gas'}}, + {'Страйкбольные гранаты', {'ent_dbg_grenade_air', 'Выбивают пневматическое оружие из рук. Может также немного поранить осколками', 'gun_grenade'}}, + {'Осколочные гранаты', {'ent_dbg_grenade_frag', 'Когда оставить в живых уже далеко не главная задача...', 'weapon_grenade_frag'}}, + {'Петарды', {'ent_dbg_petard', 'Это еще что за шутки?!', 'dynamite'}}, + {'Метательные яблоки', {'ent_dbg_apple', 'Яблочки, которые можно метать', 'food_apple'}}, + }, function(item, name, data) + item.name = name + item.usesLeft = item.usesLeft or 5 + item.gc = data[1] + item.desc = data[2] + item.icon = 'octoteam/icons/' .. data[3] .. '.png' + end, function(item) + return item.name + end}, + {'slider', 'Количество', {1, 10, 0}, function(item, val) + val = math.Round(val) + if val == 5 then + item.usesLeft = nil + else + item.usesLeft = val + end + item.mass = 0.5 + item.volume = 0.4 + end, function(item) + return item.usesLeft or 5 + end}, +} + +octoinv.itemCreateFields['ammo'] = { + {'combo', L.type_ammo, { + {L.small_ammo, {'pistol', 24}}, + {L.large_ammo, {'smg1', 30}}, + {L.buckshot, {'buckshot', 8}}, + {L.sniper_ammo, {'sniper', 12}}, + {L.air_ammo, {'air', 30}}, + }, function(item, name, data) + item.name = name + item.ammotype = data[1] + item.ammocount = data[2] + end, function(item) + return item.name + end}, +} + +octoinv.itemCreateFields['idcard'] = { + {'entry', 'Удостоверение', 'сотрудника корпорации "Wani4ka, Inc."', function(item, val) + val = string.Trim(val or '') + if val == '' or val == '-' then item.cn = nil return end + item.cn = utf8.sub(val, 1, 64) + end, function(item) + return item.cn + end + }, {'entry', 'Доп. поле', 'Отдел исследований в области оптики', function(item, val) + val = string.Trim(val or '') + if val == '' or val == '-' then item.add = nil return end + item.add = utf8.sub(val, 1, 192) + end, function(item) + return item.add + end + }, {'entry', 'Выдано', 'Ивану Березкину', function(item, val) + val = string.Trim(val or '') + if val == '' or val == '-' then item.em = nil return end + item.em = utf8.sub(val, 1, 128) + end, function(item) + return item.em + end + }, {'entry', 'На должности', 'Разработчик', function(item, val) + val = string.Trim(val or '') + if val == '' or val == '-' then item.pos = nil return end + item.pos = utf8.sub(val, 1, 128) + end, function(item) + return item.pos + end + }, {'entry', 'Дата выдачи', '24.01.2016', function(item, val) + val = string.Trim(val or '') + if val == '' then val = '-' end + item.iss = val + end, function(item) + return item.iss + end + }, {'entry', 'Действ. до', '-', function(item, val) + val = string.Trim(val or '-') + if val == '' or val == '-' then item.vthru = nil return end + item.vthru = val + end, function(item) + return item.vthru + end + } +} + +octoinv.itemCreateFields['armor'] = { + {'combo', L.class_armor, { + {L.armor_s, 16}, + {L.armor, 28}, + {L.armor_l, 40}, + }, function(item, name, armor) + item.name = name + item.armor = armor + end, function(item) + return item.name + end}, +} + +octoinv.itemCreateFields['car_rims'] = { + {'combo', L.discs, { + {'Ferrari 250 GTO', 'models/diggercars/250gto/250gto_wheel.mdl'}, + {'Porsche 356', 'models/diggercars/356/porsche_550356_wheel.mdl'}, + {'Porsche 914', 'models/diggercars/914_6/porsche_914_wheel.mdl'}, + {'Acura NSX 1997', 'models/diggercars/a_nsx97/wheel.mdl'}, + {'Alfa Romeo Montreal', 'models/diggercars/alfa_romeo_montreal/wheel.mdl'}, + {'BMW M5 E28', 'models/diggercars/bmw_m5e28/wheel.mdl'}, + {'BMW M5 E39', 'models/diggercars/bmw_m5e39/wheel.mdl'}, + {'BMW X5 M', 'models/diggercars/bmw_x5m/wheel.mdl'}, + {'BMW M1', 'models/diggercars/bmwm1/bmwm1_wheel.mdl'}, + {'Porsche Boxster 2003', 'models/diggercars/boxster03/wheel.mdl'}, + {'Honda Civic 1994', 'models/diggercars/civic94/wheel.mdl'}, + {'Lancia Fulvia', 'models/diggercars/fulvia/wheel.mdl'}, + {'Porsche GT3', 'models/diggercars/gt3 2004/wheel.mdl'}, + {'Honda NSX-R GT', 'models/diggercars/h_nsxrgt/wheel.mdl'}, + {'Legacy', 'models/diggercars/legacy/wheel.mdl'}, + {'Packard', 'models/diggercars/packcard/wheel.mdl'}, + {'Porsche 930', 'models/diggercars/porsche_930/wheel.mdl'}, + {'Porsche 930-2', 'models/diggercars/porsche_930/wheelr.mdl'}, + {'Porsche 991 Carrera S', 'models/diggercars/991_carrera_s/wheel2.mdl'}, + {'Porsche 930 Targa', 'models/diggercars/porsche_930targa/wheel.mdl'}, + {'Porsche 944', 'models/diggercars/porsche_944/wheel.mdl'}, + {'Shelby Daytona', 'models/diggercars/shelbydaytonacoupe/wheel.mdl'}, + {'Audi Sport Quattro', 'models/diggercars/sportquattro/wheel.mdl'}, + {'Lancia Stratos', 'models/diggercars/stratos/wheel.mdl'}, + {'Firebird Transam3', 'models/diggercars/transam3/wheel.mdl'}, + {'Acura NSX 1997-2', 'models/diggercars/a_nsx97/wheel2.mdl'}, + {'Acura NSX 2005', 'models/diggercars/a_nsx05/wheel.mdl'}, + {'Acura RSXS', 'models/diggercars/acura_rsxs/wheel.mdl'}, + {'Alfa Romeo', 'models/diggercars/alfa/wheel.mdl'}, + {'Ariel Atom', 'models/diggercars/ariel_atom/wheel.mdl'}, + {'BMW M5 E34', 'models/diggercars/bmw_m5e34/wheel.mdl'}, + {'BMW M5 E60', 'models/diggercars/bmw_m5e60/wheel.mdl'}, + {'BMW X5 2009', 'models/diggercars/bmw_x5_09/wheel.mdl'}, + {'BMW X6 M', 'models/diggercars/bmw_x6m/wheel.mdl'}, + {'Fiat 500', 'models/diggercars/fiat500/wheel.mdl'}, + {'Ford Cortina', 'models/diggercars/cortina/wheel.mdl'}, + {'Honda Civic 1991', 'models/diggercars/civic91/wheel.mdl'}, + {'Honda Civic 1999', 'models/diggercars/civic99/wheel.mdl'}, + {'Honda Integra 2000', 'models/diggercars/h_integra2000/wheel.mdl'}, + {'Honda Integra', 'models/diggercars/h_integra/wheel.mdl'}, + {'Honda NSX-R', 'models/diggercars/h_nsxr/wheel.mdl'}, + {'Lamborghini Aventador', 'models/diggercars/aventador/wheel.mdl'}, + {'Lamborghini Jalpa', 'models/diggercars/jalpa/wh1.mdl'}, + {'Mercedes-Benz 300 SL', 'models/diggercars/300sl/wheel.mdl'}, + {'Mercedes-Benz CLK GTR', 'models/diggercars/clkgtr/wheel.mdl'}, + {'Mercedes-Benz W123', 'models/diggercars/w123/mb_w123_wheel.mdl'}, + {'Opel Speedster', 'models/diggercars/opelspeedster/wheel.mdl'}, + {'Porsche 914-2', 'models/diggercars/porsche_914/wheel.mdl'}, + {'Porsche 959', 'models/diggercars/959/porsche_959_wheel.mdl'}, + {'Porsche 964', 'models/diggercars/964speedster/porsche_964_wheel.mdl'}, + {'Porsche 997', 'models/diggercars/997 turbo/wheel.mdl'}, + {'Porsche Carrera GT', 'models/diggercars/carrera gt/wheel.mdl'}, + {'Porsche GT1', 'models/diggercars/gt1sv/porsche_gt1_wheel.mdl'}, + {'Saab 99', 'models/diggercars/saab99turbo/wheel.mdl'}, + {'Toyota GT-One', 'models/diggercars/toyotagtone/wheel.mdl'}, + {L.item_disc, 'models/diggercars/vaz1111/oka_wheel.mdl'}, + {L.item_disc2, 'models/hl2prewar/hatch/hatch_v2_wheel.mdl'}, + {L.item_disc3, 'models/hl2prewar/van/van_wheel.mdl'}, + {L.item_disc4, 'models/salza/trabant/trabant_wheel.mdl'}, + {L.item_disc5, 'models/salza/volga/volga_wheel.mdl'}, + {L.item_disc6, 'models/salza/zaz/zaz_wheel.mdl'}, + {L.item_disc7, 'models/salza/hatchback/hatchback_wheel.mdl'}, + {L.item_disc8, 'models/salza/avia/avia_wheel.mdl'}, + {L.item_disc9, 'models/salza/skoda_liaz/skoda_liaz_fwheel.mdl'}, + {L.item_disc10, 'models/props_phx/wheels/trucktire.mdl'}, + {L.item_disc11, 'models/props_phx/wheels/trucktire2.mdl'}, + }, function(item, name, mdl) + item.name = L.discs .. ' ' .. name + item.model = mdl + end, function(item) + return item.name:gsub(L.discs .. ' ', '') + end} +} + +octoinv.itemCreateFields['clothes_custom'] = { + {'entry', 'Материал', '', function(item, val) + item.mat = string.Trim(val) ~= '' and val or nil + end, function(item) + return item.mat + end}, + {'combo', 'Тип одежды', { + {'Универсальная', nil}, + {'Мужская', 'male'}, + {'Женская', 'female'}, + }, function(item, _, gender) + item.gender = gender + end, function(item) + return not item.gender and 'Универсальная' + or item.gender == 'male' and 'Мужская' + or item.gender == 'female' and 'Женская' + end}, + {'checkbox', 'Теплая', false, function(item, val) item.warm = val or nil end, function(item) return item.warm end}, +} + +octoinv.itemCreateFields['collector'] = { + {'combo', 'Тип', { + {'Кирка', 'pickaxe'}, + }, function(item, name, collectorID) + item.name = name + item.collector = collectorID + end, function(item) + return item.name + end}, +} + +hook.Add('Initialize', 'octoinv.give', function() + + octoinv.itemCreateFields['car_att'] = { + {'combo', L.accessory, octolib.table.mapSequential(simfphys.attachments, function(att, id) return { att.name or id, att } end), function(item, name, att) + item.name = att.name + item.desc = att.desc + item.model = att.mdl + item.attmdl = att.mdl + item.skin = att.skin + item.scale = att.scale + item.colorable = not att.noPaint or nil + item.col = att.col or nil + item.mass = att.mass + item.volume = att.volume + end, function(item) + return item.name + end}, + {'checkbox', 'Красится в цвет авто', false, function(item, val) + item.colorable = val + end, function(item) + return item.colorable + end}, + } + + -- local opts = {} + -- for vehID, parts in pairs(simfphys.carBGs) do + -- for _, part in ipairs(parts) do + -- local spData = list.Get('simfphys_vehicles')[vehID] + -- if spData then + -- opts[#opts + 1] = { spData.Name .. ' – ' .. part[1], { vehID, part[3], part[4], part[5], part[6] }} + -- end + -- end + -- end + + -- octoinv.itemCreateFields['car_part'] = {{'combo', L.detail, opts, function(item, name, data) + -- item.name = name + -- item.car = data[1] + -- item.bgnum = data[2] + -- item.bgval = data[3] + -- item.mass = data[4] + -- item.volume = data[5] + -- end}} + + octoinv.itemCreateFields['h_mask'] = { + {'combo', L.mask, octolib.table.mapSequential(CFG.masks, function(mask, id) return {mask.name or id, id} end), function(item, name, mask) + item.name = name + item.mask = mask + item.icon = CFG.masks[mask].icon + item.desc = CFG.masks[mask].desc + end, function(item) + return item.name or item.mask + end}, + } + + local wepsData = {} + for id, wep in SortedPairsByMemberValue(weapons.GetList(), 'PrintName') do + if wep.PrintName then + wepsData[#wepsData + 1] = { ('%s (%s)'):format(wep.PrintName, wep.ClassName), wep.ClassName } + end + end + + octoinv.itemCreateFields['weapon'] = { + {'combo', L.weapons, wepsData, function(item, name, class) + item.name = name:gsub('%(.+%)', '') item.wepclass = class + end, function(item) + return item.name + end}, + } + +end) + +local function importItem(code) + return pon.decode(string.Trim(code)) or {} +end + +local function openSystemItems(callback) + + local f = vgui.Create 'DFrame' + f:SetSize(300, 600) + if IsValid(octoinv.pnlGive) then + local x, y = octoinv.pnlGive:GetPos() + f:SetPos(x - 305, y) + else + f:Center() + end + f:MakePopup() + f:SetTitle('Системные предметы') + + local tree = f:Add 'DTree' + tree:Dock(FILL) + function tree:OnNodeSelected(node) + if not node.item then return end + callback(node.item) + end + + local function rebuildTree(query) + query = query and query:Trim() ~= '' and utf8.lower(query) or nil + tree:Clear() + + local created = {} + + local shopItems, craftItems, processItems = {}, {}, {} + for id, shopItem in pairs(octoinv.shopItems) do + local item = shopItem.data and table.Copy(shopItem.data) or {} + item.class = shopItem.item or id + + local creationID = item.class .. ':' .. octoinv.getItemData(item, 'name') + if not created[creationID] then + shopItems[#shopItems + 1] = item + created[creationID] = true + end + end + + for id, craft in pairs(octoinv.crafts) do + local finish = istable(craft.finish) and craft.finish[1] + if istable(finish) then + local item = { class = finish[1] } + if istable(finish[2]) then + table.Merge(item, finish[2]) + elseif isnumber(finish[2]) then + item.amount = finish[2] + end + + local creationID = item.class .. ':' .. octoinv.getItemData(item, 'name') + if not created[creationID] then + craftItems[#craftItems + 1] = item + created[creationID] = true + end + end + end + + for id, prod in pairs(octoinv.prods) do + for _, process in ipairs(prod.prod) do + for contID, items in pairs(process.out) do + for _, finish in ipairs(items) do + local chance, class, data = unpack(finish) + if isstring(chance) then + class, data = chance, class + end + + local item = { class = class } + if istable(data) then + table.Merge(item, data) + elseif isnumber(data) then + item.amount = data + end + + local creationID = item.class .. ':' .. octoinv.getItemData(item, 'name') + if not created[creationID] then + processItems[#processItems + 1] = item + created[creationID] = true + end + end + end + end + end + + local function addNode(parent, item) + if query and not utf8.lower(octoinv.getItemData(item, 'name')):find(query) and not utf8.lower(item.class):find(query) then return end + + local node = parent:AddNode(octoinv.getItemData(item, 'name'), iconPath(octoinv.getItemData(item, 'icon'))) + node.item = item + end + + local function sorter(a, b) + return octoinv.getItemData(a, 'name') < octoinv.getItemData(b, 'name') + end + table.sort(shopItems, sorter) + table.sort(craftItems, sorter) + table.sort(processItems, sorter) + + local shopNode = tree:AddNode('Магазин', octolib.icons.silk16('basket_shopping')) + for _, item in ipairs(shopItems) do addNode(shopNode, item) end + + local craftNode = tree:AddNode('Крафты', octolib.icons.silk16('hammer')) + for _, item in ipairs(craftItems) do addNode(craftNode, item) end + + local processNode = tree:AddNode('Процессы', octolib.icons.silk16('time')) + for _, item in ipairs(processItems) do addNode(processNode, item) end + + if query then + shopNode:SetExpanded(true, true) + craftNode:SetExpanded(true, true) + processNode:SetExpanded(true, true) + end + end + rebuildTree() + + local search = f:Add 'DTextEntry' + search:Dock(TOP) + search:DockMargin(5,5,5,10) + search:SetTall(15) + search:SetTooltip(L.search_or_filter) + search:SetUpdateOnType(true) + search.PaintOffset = 5 + search:SetPlaceholderText(L.search_hint) + search.OnValueChange = octolib.func.debounce(function(self, val) + rebuildTree(val) + end, 0.5) + +end + +local resultText = (L.resultText):gsub('||', string.char(10)) +concommand.Add('octoinv_give', function() + + if not LocalPlayer():query(L.permissions_edit_inventory) then + octolib.notify.show('warning', L.not_have_access) + return + end + + if IsValid(octoinv.pnlGive) then + octoinv.pnlGive:SetVisible(true) + return + end + + local f = vgui.Create 'DFrame' + f:SetSize(700, 600) + f:SetTitle(L.give_item) + f:Center() + f:MakePopup() + f:SetDeleteOnClose(false) + octoinv.pnlGive = f + + local presetsPan = f:Add('DScrollPanel') + presetsPan:Dock(RIGHT) + presetsPan:DockMargin(5, 0, 0, 0) + presetsPan.oldPerformLayout = presetsPan.PerformLayout + function presetsPan:PerformLayout(w, h) + self:oldPerformLayout(w, h) + self:SetWide(self:GetVBar():IsVisible() and 79 or 64) + end + + local presetsButtons = presetsPan:Add('DPanel') + presetsButtons:Dock(TOP) + presetsButtons:SetTall(22) + presetsButtons:SetPaintBackground(false) + + local butAdd = presetsButtons:Add('DImageButton') + butAdd:Dock(LEFT) + butAdd:SetWide(32) + butAdd:SetStretchToFit(false) + butAdd:SetIcon('icon16/disk.png') + butAdd:AddHint('Сохранить в шаблон') + + local butImport = presetsButtons:Add('DImageButton') + butImport:Dock(RIGHT) + butImport:SetWide(32) + butImport:SetStretchToFit(false) + butImport:SetIcon('icon16/page_go.png') + butImport:AddHint('Импортировать шаблон') + + local presets = presetsPan:Add('DListLayout') + presets:Dock(FILL) + presets:MakeDroppable('octoinv_presets') + + local lCont = f:Add 'DPanel' + lCont:Dock(LEFT) + lCont:DockMargin(0,0,5,0) + lCont:SetWide(250) + lCont:SetPaintBackground(false) + + local lv = lCont:Add 'DListView' + lv:Dock(FILL) + lv:AddColumn('ID'):SetFixedWidth(100) + lv:AddColumn(L.title):SetFixedWidth(150) + + local bp = f:Add 'DPanel' + bp:Dock(BOTTOM) + bp:DockMargin(0,5,0,0) + bp:SetTall(25) + bp:SetPaintBackground(false) + + local b1 = bp:Add 'DButton' + b1:Dock(RIGHT) + b1:SetText(L.my_inventory) + b1:SizeToContentsX(20) + function b1:DoClick() octoinv.show(not (IsValid(octoinv.mainPnl) and octoinv.mainPnl:IsVisible())) end + + local b2 = bp:Add 'DButton' + b2:Dock(RIGHT) + b2:SetText(L.octoinv_editor) + b2:SizeToContentsX(20) + b2:DockMargin(0, 0, 5, 0) + function b2:DoClick() RunConsoleCommand('octoinv_editor') end + + local itemToGive, amount = {}, 1 + + local function getItemData() + local item = { class = itemToGive[1], amount = amount } + for k, v in pairs(itemToGive[2]) do + local data = octoinv.items[item.class] + local default = (data and data[k]) or defaultData[k] + if v ~= default then + item[k] = v + end + end + item._mo = nil -- delete cached markup object + return item + end + + local pr = f:Add 'DPanel' + pr:Dock(BOTTOM) + pr:DockMargin(0,5,0,0) + pr:DockPadding(8,5,5,5) + pr:SetTall(74) + function pr:Update() + self:Clear() + + local item = getItemData() + local b = octoinv.createItemPanel(self, item, true) + b:Dock(RIGHT) + b:SetWide(64) + b:Droppable('octoinv_give') + b.item = item + b:SetTooltip(util.TableToJSON(item, true)) + + local l1 = self:Add 'DLabel' + l1:Dock(TOP) + l1:SetContentAlignment(1) + l1:SetTall(23) + l1:SetFont('octoinv-help.subheading') + l1:SetText(L.result) + + local l2 = self:Add 'DLabel' + l2:Dock(FILL) + l2:SetContentAlignment(4) + l2:SetText(resultText:format(itemData(item, 'volume') * amount, itemData(item, 'mass') * amount)) + end + + function butAdd:DoClick() + local item = getItemData() + if item.icon and not isstring(item.icon) then item.icon = item.icon:GetName() .. '.png' end + local items = octolib.vars.get('octoinv.presets') or {} + items[#items + 1] = item + octolib.vars.set('octoinv.presets', items) + presets:Update() + presetsPan:GetVBar():AnimateTo(presets:GetTall(), 0.5, 0) + end + + local setup = f:Add 'DScrollPanel' + setup:Dock(FILL) + setup:SetPaintBackground(true) + setup.pnlCanvas:DockPadding(10,5,10,5) + function setup:Update(itemID, data) + self:Clear() + + local class = octoinv.items[itemID] + if not class then return end + + data = data or {} + itemToGive = {itemID, table.Copy(data)} + + setupEntry(self, L.quantity, tostring(data.amount or 1), function(self, val) + local val = tonumber(val) + if val then + amount = val + else + self:SetValue(1) + end + + pr:Update() + end) + + if class.nostack then + setupEntry(self, L.title, itemData(itemToGive, 'name'), function(self, val) + itemToGive[2].name = tostring(val) + pr:Update() + end) + + setupEntry(self, L.octoinv_desc, itemData(itemToGive, 'desc'), function(self, val) + itemToGive[2].desc = tostring(val) + pr:Update() + end) + + local icon = setupEntry(self, L.icon, iconPath(itemData(itemToGive, 'icon')), function(self, val) + itemToGive[2].icon = Material(tostring(val)) + pr:Update() + end) + local b = icon:Add 'DButton' + b:Dock(RIGHT) + b:SetWide(25) + b:SetText('') + b:SetIcon('icon16/color_swatch.png') + b:SetPaintBackground(false) + function b:DoClick() + if IsValid(self.picker) then return end + self.picker = octolib.icons.picker(function(val) icon:SetValue(val) end) + end + + setupEntry(self, L.model, itemData(itemToGive, 'model'), function(self, val) + itemToGive[2].model = tostring(val) + pr:Update() + end) + + setupEntry(self, L.item_val, tostring(itemData(itemToGive, 'volume') or 1), function(self, val) + local val = tonumber(val) + if not val then return end + + itemToGive[2].volume = val + pr:Update() + end) + + setupEntry(self, L.item_massa, tostring(itemData(itemToGive, 'mass') or 1), function(self, val) + local val = tonumber(val) + if not val then return end + + itemToGive[2].mass = val + pr:Update() + end) + + setupEntry(self, 'Срок действия (сек)', tostring(itemData(itemToGive, 'expire') or 0), function(self, val) + local expire = tonumber(val) + if expire <= 0 then expire = nil end + itemToGive[2].expire = expire + pr:Update() + end) + + local specific = octoinv.itemCreateFields[itemID] + if specific then + for i, v in ipairs(specific) do + local type, name, settings, set, get = unpack(v) + local spData = {type} + if type == 'entry' then + spData[2] = setupEntry(self, name, settings, function(self, val) + set(itemToGive[2], val) + pr:Update() + end) + + local val = get(itemToGive[2]) + if val then + spData[2]:SetValue(val) + end + elseif type == 'combo' then + spData[2] = setupCombo(self, name, settings, function(self, i, val, data) + set(itemToGive[2], val, data) + pr:Update() + end) + + local val = get(itemToGive[2]) + if val then + spData[2]:SetValue(val) + else + spData[2]:ChooseOptionID(1) + end + elseif type == 'slider' then + spData[2] = setupSlider(self, name, settings, function(self, val) + set(itemToGive[2], val) + pr:Update() + end) + spData[2]:SetValue(get(itemToGive[2]) or settings[1] or 0) + elseif type == 'checkbox' then + spData[2] = setupCheckBox(self, name, settings, function(self, val) + set(itemToGive[2], val) + pr:Update() + end) + + local val = get(itemToGive[2]) + if val then + spData[2]:SetChecked(val) + end + end + end + end + end + + pr:Update() + end + + local function rebuildList(query) + lv:Clear() + if query then query = utf8.lower(query) end + + for id, item in pairs(octoinv.items) do + local itemName = itemData(item, 'name') + if not query or string.Trim(query) == '' + or utf8.lower(id):find(query, 1, true) or utf8.lower(itemName):find(query, 1, true) then + local l = lv:AddLine(id, itemName) + l.itemID = id + end + end + lv:SortByColumn(1) + end + rebuildList() + + local search = lCont:Add 'DTextEntry' + search:Dock(TOP) + search:DockMargin(5,5,5,10) + search:SetTall(15) + search:SetTooltip(L.search_or_filter) + search:SetUpdateOnType(true) + search.PaintOffset = 5 + search:SetPlaceholderText(L.search_hint) + function search:OnValueChange(val) + rebuildList(val) + end + + function lv:OnRowSelected(i, l) + setup:Update(l.itemID) + end + lv:SelectFirstItem() + + local function silentSelect(class) + for i,v in ipairs(lv.Sorted) do + if v:GetValue(1) == class then + lv:ClearSelection() + v:SetSelected(true) + lv.VBar:AnimateTo(i * lv:GetDataHeight() - 2 * lv:GetHeaderHeight(), 0.5, 0) + end + end + end + + local function doRightClick(self) + local menu = DermaMenu() + menu:AddOption('Открыть', function() + silentSelect(self.item.class) + setup:Update(self.item.class, table.Copy(self.item)) + end):SetIcon('octoteam/icons-16/arrow_right.png') + menu:AddOption('Экспортировать', function() + SetClipboardText(pon.encode(self.item)) + octolib.notify.show('Шаблон скопирован, можно отправить его кому-нибудь для импорта') + end):SetIcon('icon16/page_go.png') + menu:AddOption('Удалить', function() + local items = octolib.vars.get('octoinv.presets') or {} + table.remove(items, self.index) + octolib.vars.set('octoinv.presets', items) + self:Remove() + for i,v in ipairs(presets:GetChildren()) do + v.index = i + end + timer.Simple(0, function() presetsPan:InvalidateLayout() end) + end):SetIcon('octoteam/icons-16/cancel.png') + menu:Open() + end + local function doClick(self) + silentSelect(self.item.class) + setup:Update(self.item.class, table.Copy(self.item)) + end + + function presets:AddPreset(index, item, noInvalidates) + local b = octoinv.createItemPanel(self, item, true) + b:DockMargin(0, 5, 0, 0) + b:SetTall(64) + b.item = item + b.index = index + b:SetTooltip(util.TableToJSON(item, true)) + b.DoRightClick = doRightClick + b.DoClick = doClick + if not noInvalidates then + timer.Simple(0, function() + presetsPan:InvalidateLayout() + end) + end + end + function presets:Update() + self:Clear() + local savedItems = octolib.vars.get('octoinv.presets') or {} + for index, item in ipairs(savedItems) do + item._mo = nil -- delete cached markup object + self:AddPreset(index, item, true) + end + timer.Simple(0, function() + presetsPan:InvalidateLayout() + end) + end + presets:Update() + + function presets:OnModified() + local newVar = {} + for i,data in ipairs(self:GetChildren()) do + newVar[i] = data.item + end + octolib.vars.set('octoinv.presets', newVar) + self:Update() + end + + function butImport:DoClick() + octolib.request.open({ + { + name = 'Код шаблона', + desc = 'Вставь сюда код шаблона предмета, полученный при экспорте', + type = 'strShort', + } + }, function(data) + if not istable(data) or not isstring(data[1]) then return end + + local succ, item = pcall(importItem, data[1]) + if not succ then + return octolib.notify.show('warning', 'Не удалось импортировать шаблон. Проверь код предмета') + end + if not item.class or not octoinv.items[item.class] then + return octolib.notify.show('warning', 'Предмет с таким классом не найден') + end + + local items = octolib.vars.get('octoinv.presets') or {} + items[#items + 1] = item + octolib.vars.set('octoinv.presets', items) + presets:Update() + octolib.notify.show('Шаблон предмета успешно импортирован') + end) + end + + local butSystem = bp:Add('DButton') + butSystem:Dock(LEFT) + butSystem:DockMargin(0, 0, 5, 0) + butSystem:SetText('Системные') + butSystem:SizeToContentsX(20) + function butSystem:DoClick() + openSystemItems(function(item) + silentSelect(item.class) + setup:Update(item.class, table.Copy(item)) + end) + end + +end) + +if IsValid(octoinv.pnlGive) then octoinv.pnlGive:Remove() end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_mapeditor.lua b/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_mapeditor.lua new file mode 100644 index 0000000..5862323 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_mapeditor.lua @@ -0,0 +1,61 @@ +local icon = octolib.icons.silk16('location_pin', 'smooth mips') + +concommand.Add('octoinv_map_editor', function(ply, data) + netstream.Start('octoinv.editMap') +end) + +netstream.Hook('octoinv.editMap', function(props, canCreate) + local categoryProps = octolib.table.filter(props, function(p) return not p.parent end) + + local showIcons = octolib.array.toKeys(table.GetKeys(categoryProps), false) + hook.Add('HUDPaint', 'dbg-jobs.editMap', function() + if not octolib.flyEditor.active then + return hook.Remove('HUDPaint', 'dbg-jobs.editMap') + end + + for _, movable in pairs(octolib.flyEditor.movables) do + local parentCsEnt = movable.csent:GetParent() + local parent = parentCsEnt and parentCsEnt.movable and parentCsEnt.movable.id + if parent and showIcons[parent] then + local pos = movable.csent:GetPos():ToScreen() + surface.SetMaterial(icon) + surface.SetDrawColor(255,255,255, 255) + surface.DrawTexturedRect(pos.x - 8, pos.y - 8, 16, 16) + end + end + end) + + octolib.flyEditor.start({ + props = categoryProps, -- load categories first + canCreate = canCreate, + maxDist = 0, + noclip = true, + buttons = { + {'Отображать сквозь карту', octolib.icons.silk32('eye'), function() + octolib.menu( + octolib.table.mapSequential(categoryProps, function(v, k) + return {v.name, showIcons[k] and octolib.icons.silk16('tick') or octolib.icons.silk16('cross'), function() + showIcons[k] = not showIcons[k] + end} + end) + ):Open() + end} + }, + }, function(movables) + netstream.Heavy('octoinv.editMap', octolib.table.map(movables, function(movable) + return { + model = movable.model, + pos = movable.pos, + ang = movable.ang, + parent = movable.parent, + } + end)) + end) + + timer.Simple(0, function() + -- add child props later + for id, prop in pairs(octolib.table.filter(props, function(p) return isstring(p.parent) end)) do + octolib.flyEditor.addMovable(prop, id) + end + end) +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_stats.lua b/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_stats.lua new file mode 100644 index 0000000..a3ebc83 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/admin/cl_stats.lua @@ -0,0 +1,107 @@ +local update = octolib.func.debounceStart(function() + netstream.Request('octoinv.stats'):Then(function(data) + if IsValid(octoinv.statsWindow) then + octoinv.statsWindow:Update(data) + end + end) +end, 5) + +concommand.Add('octoinv_stats', function() + local me = LocalPlayer() + if not me:IsSuperAdmin() then return end + + if IsValid(octoinv.statsWindow) then + octoinv.statsWindow:Remove() + end + + local fr = vgui.Create 'DFrame' + fr:SetSize(250, 250) + fr:SetTitle('Выброшенные вещи') + fr:AlignLeft(30) + fr:AlignBottom(30) + octoinv.statsWindow = fr + + local lst = fr:Add 'DListView' + lst:Dock(FILL) + lst:AddColumn('Класс') + lst:AddColumn('Количество') + lst:SetMultiSelect(false) + + local rowsByUid = {} + octoinv.statsWindow.items = rowsByUid + function fr:Update(data) + local byUid = {} + for _, item in ipairs(data) do + byUid[item.uid] = item + if not rowsByUid[item.uid] then + local amount = istable(item.data[2]) and item.data[2].amount or isnumber(item.data[2]) and item.data[2] or 1 + rowsByUid[item.uid] = lst:AddLine(item.data[1], amount) + end + rowsByUid[item.uid].pos = item.pos + rowsByUid[item.uid].uid = item.uid + end + for k, v in pairs(rowsByUid) do + if not byUid[k] then + lst:RemoveLine(v:GetID()) + rowsByUid[k] = nil + end + end + lst:PerformLayout() + end + + function lst:OnRowSelected(_, line) + octoinv.statsWindow.selected = line.uid + end + + function lst:OnRowRightClick(_, line) + local menu = DermaMenu() + menu:AddOption('Телепорт', function() + netstream.Start('octologs.goto', line.pos + Vector(0,0,64), Angle(0,0,0)) + end) + menu:AddSpacer() + menu:AddOption('Удалить', function() + netstream.Start('octoinv.stats.removeItem', line.uid) + self:RemoveLine(line:GetID()) + rowsByUid[line.uid] = nil + self:PerformLayout() + end) + menu:Open() + end + + timer.Create('octoinv.stats.update', 1, 0, update) + function fr:OnRemove() + timer.Remove('octoinv.stats.update') + end + +end) + +hook.Add('HUDPaint', 'octoinv.stats', function() + if not IsValid(octoinv.statsWindow) then return end + + local sizeOuter = 8 + 2 * math.sin(CurTime() * 3) + local sizeInner = sizeOuter / 2 + local offset = sizeInner / 2 + local selected = octoinv.statsWindow.selected + + for uid, line in pairs(octoinv.statsWindow.items) do + if uid == selected then continue end + local pos = line.pos:ToScreen() + if pos.visible then + draw.RoundedBox(sizeInner, pos.x-sizeInner, pos.y-sizeInner, sizeOuter, sizeOuter, color_black) + draw.RoundedBox(offset, pos.x-offset, pos.y-offset, sizeInner, sizeInner, color_white) + end + end + + -- draw selected last so that it's always visible + sizeOuter, sizeInner, offset = sizeOuter*2, sizeInner*2, offset*2 + if selected then + local line = octoinv.statsWindow.items[selected] + if not line then return end + local pos = line.pos:ToScreen() + if pos.visible then + draw.RoundedBox(sizeInner, pos.x-sizeInner, pos.y-sizeInner, sizeOuter, sizeOuter, color_black) + draw.RoundedBox(offset, pos.x-offset, pos.y-offset, sizeInner, sizeInner, color_red) + end + end + +end) \ No newline at end of file diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_control.lua b/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_control.lua new file mode 100644 index 0000000..ce2589a --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_control.lua @@ -0,0 +1,87 @@ +local pointTypes = { + -- collect + { + insertCanCreate = function(canCreate) + for collectableID, collectable in pairs(octoinv.collectables) do + for i, mdl in ipairs(collectable.models) do + local data = { + parent = 'collect_' .. collectableID, + name = collectable.name .. ' *', + } + + if istable(mdl) then + data.model = mdl[1] + data.skin = mdl.skin + data.scale = mdl.scale + data.col = mdl.color + else + data.model = mdl + end + + canCreate[#canCreate + 1] = {collectable.name .. ' ' .. i, data} + end + end + end, + insertProps = function(props) + for collectableID, collectable in pairs(octoinv.collectables) do + props['collect_' .. collectableID] = { + name = collectable.name, + icon = octolib.icons.silk16('folder'), + model = 'models/hunter/blocks/cube025x025x025.mdl', + static = true, + } + + for _, point in ipairs(octoinv.mapConfig.collect[collectableID] or {}) do + local model, pos, ang = unpack(point) + local modelData = octolib.table.find(collectable.models, function(md) return md == model or md[1] == model end) + if not modelData then continue end + + props[#props + 1] = { + name = collectable.name, + parent = 'collect_' .. collectableID, + model = model, + pos = pos, + ang = ang, + scale = modelData.scale, + col = modelData.color, + } + end + end + end, + saveMovables = function(config, movables) + config.collect = {} + for _, movable in pairs(movables) do + local parent = movable.parent + if not parent then continue end + + local collectableID = parent:gsub('collect_', '') + if not octoinv.collectables[collectableID] then continue end + + config.collect[collectableID] = config.collect[collectableID] or {} + local saved = config.collect[collectableID] + saved[#saved + 1] = { movable.model, movable.pos, movable.ang } + end + end, + }, +} + +netstream.Hook('octoinv.editMap', function(ply, data) + if not ply:IsSuperAdmin() then return end + + if data then + local config = octoinv.mapConfig + for _, pointType in ipairs(pointTypes) do + pointType.saveMovables(config, data) + end + + return octoinv.saveMapConfig(config) + end + + local props, canCreate = {}, {} + for _, pointType in ipairs(pointTypes) do + pointType.insertProps(props) + pointType.insertCanCreate(canCreate) + end + + netstream.Heavy(ply, 'octoinv.editMap', props, canCreate) +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_give.lua b/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_give.lua new file mode 100644 index 0000000..93b26c4 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_give.lua @@ -0,0 +1,322 @@ +local function isAllowed(ply) + + return serverguard.player:HasPermission(ply, L.permissions_edit_inventory) + +end + +local function sendResults(ply, data) + + + netstream.Start(ply, 'octoinv.search', data) + +end + +local function editorInteractHook(cont, ply, item, contFrom) + if istable(cont) and cont.noEdit then + return false, cont.noEdit + end + if istable(contFrom) and contFrom.noEdit then + return false, contFrom.noEdit + end + if cont.steamID or istable(contFrom) and contFrom.steamID then + netstream.Start(cont:GetParent().owner, 'octoinv.editor.change', cont.steamID) + end +end + +local removers = { + {'Унитаз', 'toilet'}, + {'Ненасытный Жора', 'denture'}, + {'Взрыв', 'explosion'}, + {'Термоядерный взрыв', 'explosion2'}, + {'ZIP-пакет', 'zip_file'}, + {'Краймер', 'robber'}, + {'Фонд Нуждающихся Игроков', 'group3'}, + {'Мусороперерабатывающая фабрика', 'chem_plant'}, + {'Подношение Челогу', 'gift_octo_pride'}, + {'Старый рюкзак', 'clothes_backpack2'}, + {'Пузырь растворителя', 'bottle_vodka'}, + {'Бесконечная любовь', 'heart'}, + {'Долгий ящик', 'inbox'}, + {'Странная колба', 'potion3'}, + {'Секонд-хенд', 'shop2'}, + {'Грузовик ""', 'truck'}, + {'Рука попрошайки', 'hand'}, + {'Игровой автомат', 'slot_machine'}, + {'Пакет чипсов твоего друга', 'food_chips'}, + {'Камера', 'camera'}, + {'Иностранный агент', 'spy'}, + {'Соответствующий контейнер', 'cross'}, + {'Ненадежный файлообменник', 'chip1'}, +} + +netstream.Hook('octoinv.editor.open', function(ply, steamID, name) + + if not isAllowed(ply) then + ply:Notify('warning', L.not_have_access) + return + end + if not steamID then return end + + local noEdit = IsValid(player.GetBySteamID(steamID)) and L.player_on_server + octolib.func.chain({ + function(done) + if noEdit then return done() end + octolib.db:PrepareQuery([[select exists(select 1 from `octolib_backup_dbg` where steamID = ? limit 1) as `exists`]], {steamID}, function(q, st, res) + if istable(res) then + noEdit = noEdit or res[1].exists == 1 and L.player_on_old_dbg + end + done() + end) + end, + function(done) + if noEdit then return done() end + octolib.db:PrepareQuery([[select exists(select 1 from `octolib_backup_dbg2` where steamID = ? limit 1) as `exists`]], {steamID}, function(q, st, res) + if istable(res) then + noEdit = noEdit or res[1].exists == 1 and L.player_on_new_dbg + end + done() + end) + end, + -- function(done) + -- if noEdit then return done() end + -- octolib.db:PrepareQuery([[select exists(select 1 from `octolib_backup_dbg22` where steamID = ? limit 1) as `exists`]], {steamID}, function(q, st, res) + -- if istable(res) then + -- noEdit = noEdit or res[1].exists == 1 and L.player_on_new_dbg + -- end + -- done() + -- end) + -- end, + function(done) + octolib.db:PrepareQuery([[select money from `dbg_atm` where steamID = ? limit 1]], {steamID}, function(q, st, res) + done(istable(res) and res[1].money or BraxBank.StartMoney()) + end) + end, + function(done, bank) + octolib.db:PrepareQuery([[select karma from `dbg_karma` where steamID = ? limit 1]], {steamID}, function(q, st, res) + done(bank, istable(res) and res[1].karma or 0) + end) + end, + function(done, bank, karma) + octolib.db:PrepareQuery([[select inv, `storage` from `inventory` where steamID = ? limit 1]], {steamID}, function(q, st, res) + res = istable(res) and res[1] + if res then + done(bank, karma, res.inv and pon.decode(res.inv) or {}, res.storage and pon.decode(res.storage) or {}) + end + end) + end, + function(done, bank, karma, inv, storage) + local plyInv = ply:GetInventory() + if not plyInv then return end + + ply.editorConts = ply.editorConts or {} + ply.editorConts[steamID] = ply.editorConts[steamID] or {} + ply.noEdits = ply.noEdits or {} + ply.noEdits[steamID] = noEdit + + local function addCont(contID, contData) + local id = '_e' .. steamID .. '_' .. contID + + local default = octoinv.defaultInventory[contID] or {} + for k,v in pairs(default) do + contData[k] = contData[k] or v + end + contData.name = (contData.name or L.container) .. ' - ' .. (name or steamID) + + if ply.editorConts[steamID][contID] then + ply.editorConts[steamID][contID]:Remove() + end + + local cont = plyInv:AddContainer(id, contData) + cont.noEdit = noEdit + cont.steamID = steamID + cont.steamName = name + cont.contID = contID + + cont:Hook('canMoveOut', 'octoinv.editor', editorInteractHook) + cont:Hook('canMoveIn', 'octoinv.editor', editorInteractHook) + cont:Hook('canSeeUseList', 'octoinv.editor', editorInteractHook) + cont:Hook('canUse', 'octoinv.editor', editorInteractHook) + cont:Hook('canDrop', 'octoinv.editor', editorInteractHook) + + for _,item in ipairs(contData.items or {}) do + cont:AddItem(item.class, item, true) + end + + ply.editorConts[steamID][contID] = cont + return cont + end + + for contID, contData in pairs(inv) do + addCont(contID, contData):QueueSync() + end + + storage = storage.storage + if storage then + storage.name = 'Хранилище' + storage.volume = 350 + storage.icon = octolib.icons.color('case_travel') + addCont('_storage', storage):QueueSync() + end + + local remover + if not noEdit then + remover = octolib.array.random(removers) + addCont('_remover', { + name = remover[1], + volume = 9999, + icon = octolib.icons.color(remover[2]), + }) + end + + netstream.Start(ply, 'octoinv.editor.open', { + steamID = steamID, + bank = bank, + karma = karma, + remover = remover and remover[2] or nil, + noEdit = noEdit + }) + end, + }) + +end) + +local defaultStorage = { + name = 'Хранилище', + volume = 350, + icon = octolib.icons.color('case_travel'), +} + +netstream.Hook('octoinv.editor.save', function(ply, steamID, bank, karma) + if not isAllowed(ply) then + ply:Notify('warning', L.not_have_access) + return + end + + if not isstring(steamID) or not isnumber(bank) or not isnumber(karma) then return end + if not ply.editorConts or not ply.editorConts[steamID] then return end + if ply.noEdits[steamID] then return ply:Notify(ply.noEdits[steamID]) end + local conts = ply.editorConts[steamID] + + local stCont, remCont = conts._storage, conts._remover + conts._storage, conts._remover = nil + + local function getExportedCont(cont, default) + local res = cont:Export() + default = default or {} + for k,v in pairs(default) do + if res[k] == v or k == 'name' and res[k] == v .. ' - ' .. res.steamName then + res[k] = nil + end + end + if default.name and res.name == default.name .. ' - ' .. res.steamName then + res.name = nil + end + res.contID, res.noEdit, res.steamID, res.steamName = nil + return res + end + + local inv = {} + for contID, cont in pairs(conts) do + inv[contID] = getExportedCont(cont, octoinv.defaultInventory[contID]) + end + + local storage = {} + if stCont then + storage.storage = getExportedCont(stCont, defaultStorage) + end + + octolib.db:PrepareQuery('update inventory set inv = ?, storage = ? where steamID = ?', {pon.encode(inv), pon.encode(storage), steamID}) + octolib.db:PrepareQuery('update dbg_atm set money = ? where steamID = ?', {math.Clamp(bank, -2147483647, 2147483647), steamID}) + octolib.db:PrepareQuery('update dbg_karma set karma = ? where steamID = ?', {math.Clamp(karma, -2147483647, 2147483647), steamID}) + ply:Notify('Информация об игроке сохранена') + + conts._storage, conts._remover = stCont, remCont +end) + +netstream.Hook('octoinv.editor.close', function(ply, steamID) + if ply.editorConts and ply.editorConts[steamID] then + for _,cont in pairs(ply.editorConts[steamID]) do + cont:Remove() + end + ply.editorConts[steamID] = nil + end + if ply.noEdits then ply.noEdits[steamID] = nil end +end) + +netstream.Hook('octoinv.search', function(ply, data) + + if not isAllowed(ply) then + ply:Notify('warning', L.not_have_access) + return + end + if not data then return end + + if data.steamID and data.steamID ~= '' then + octolib.db:PrepareQuery('select steamID from inventory where steamID = ? limit 250', { data.steamID }, function(q, st, res) sendResults(ply, res) end) + elseif data.query and data.query ~= '' then + local q = '\'%' .. octolib.db:escape(data.query) .. '%\'' + octolib.db:RunQuery('select steamID from inventory where inv like '..q..' or storage like '..q..' limit 250', function(q, st, res) sendResults(ply, res) end) + end + +end) + +netstream.Hook('octoinv.edit', function(ply, steamID, data) + + if not isAllowed(ply) then + ply:Notify('warning', L.not_have_access) + return + end + if not steamID or not data then return end + + if IsValid(player.GetBySteamID(steamID)) then + ply:Notify('warning', L.can_not_edit_player) + return + end + + for i, ent in ipairs(ents.FindByClass('octoinv_storage')) do + if ent.steamID == steamID then + ply:Notify('warning', L.can_not_edit_player_storage) + return + end + end + + octolib.db:PrepareQuery('update inventory set inv = ?, storage = ? where steamID = ?', { pon.encode(data.inv), pon.encode(data.storage), steamID }) + octolib.db:PrepareQuery('update dbg_atm set money = ? where steamID = ?', { data.bank or 0, steamID }) + octolib.db:PrepareQuery('update dbg_karma set karma = ? where steamID = ?', { data.karma or 0, steamID }) + ply:Notify(L.data_saved) + +end) + +netstream.Hook('octoinv.create', function(ply, entID, contID, item) + + if not isAllowed(ply) then + ply:Notify(L.not_have_access) + return + end + + local ent = Entity(entID) + local cont = IsValid(ent) and ent.inv and ent.inv.conts[contID] + if not cont then return end + + local class = octoinv.items[item.class] + if not class then return end + + if class.nostack then + local toGive = table.Copy(item) + toGive._mo = nil + if toGive.expire then + toGive.expire = toGive.expire + os.time() + end + + local amount, class = tonumber(toGive.amount or 1) or 1, toGive.class + toGive.amount, toGive.class = nil + for i = 1, amount do + cont:AddItem(class, toGive) + end + else + cont:AddItem(item.class, tonumber(item.amount or 1) or 1) + end + + hook.Run('octoinv.adminGive', ply, item) + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_stats.lua b/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_stats.lua new file mode 100644 index 0000000..b79d8c9 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/admin/sv_stats.lua @@ -0,0 +1,35 @@ +local function entToItem(item) + return { + pos = item:GetPos(), + uid = item.uid, + data = item:GetNetVar('Item'), + } +end + +netstream.Listen('octoinv.stats', function(reply, ply) + if not ply:IsAdmin() then return end + local ct = CurTime() + if (ply.octoinv_nextStatsRequest or 0) > ct then return end + ply.octoinv_nextStatsRequest = ct + 4 + reply(octolib.array.map(ents.FindByClass('octoinv_item'), entToItem)) +end) + +netstream.Hook('octoinv.stats.removeItem', function(ply, uid) + if not ply:IsAdmin() then return end + for _, v in ipairs(ents.FindByClass('octoinv_item')) do + if v.uid == uid then + v:Remove() + break + end + end +end) + +local warnOn = 100 +timer.Create('octoinv.stats.check', 120, 0, function() + local amount = #ents.FindByClass('octoinv_item') + if amount > warnOn then + RunConsoleCommand('sg', 'a', ('На сервере %d %s! Пожалуйста, проверьте и удалите ненужные с помощью консольной команды octoinv_stats'):format( + amount, octolib.string.formatCount(amount, 'выброшенный предмет', 'выброшенных предмета', 'выброшенных предметов') + )) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/collect/sv_collect.lua b/garrysmod/addons/core-octoinv/lua/octoinv/collect/sv_collect.lua new file mode 100644 index 0000000..69799f6 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/collect/sv_collect.lua @@ -0,0 +1,46 @@ +octoinv.collectables = octoinv.collectables or {} +octoinv.collectors = octoinv.collectors or {} + +local function createTimer(collectableID) + local collectableData = octoinv.collectables[collectableID] + local delay = math.random(unpack(collectableData.period)) + + timer.Create('octoinv.spawnCollectable:' .. collectableID, delay, 1, function() + createTimer(collectableID) + + local existing = octolib.table.mapSequential(ents.FindByClass('octoinv_collectable'), function(e) + return e.collectableID == collectableID and e or nil + end) + + if #existing < collectableData.max then + octoinv.spawnCollectable(collectableID) + end + end) +end + +function octoinv.registerCollectable(id, data) + data.id = id + octoinv.collectables[id] = data + + createTimer(id) +end + +function octoinv.registerCollector(id, data) + data.id = id + octoinv.collectors[id] = data +end + +function octoinv.spawnCollectable(id) + for _, point in RandomPairs(octoinv.mapConfig.collect[id] or {}) do + local model, pos, ang = unpack(point) + if #octolib.table.mapSequential(ents.FindInSphere(pos, 4), function(e) return e:GetClass() == 'octoinv_collectable' and e or nil end) > 0 then continue end + + local ent = ents.Create('octoinv_collectable') + ent:SetPos(pos) + ent:SetAngles(ang) + ent:SetCollectableID(id, model) + ent:Spawn() + + return ent + end +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/cl_help.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/cl_help.lua new file mode 100644 index 0000000..0cf0d46 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/cl_help.lua @@ -0,0 +1,486 @@ +surface.CreateFont('octoinv-help.heading', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) + +surface.CreateFont('octoinv-help.subheading', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local defaultData = { + amount = 1, + volume = 0, + mass = 0, + name = L.unknown, + desc = L.not_desc, + icon = 'octoteam/icons/error.png', + model = 'models/props_junk/garbage_bag001a.mdl', +} + +local function classData(class, field) + local data = octoinv.items[class] + return data and data[field] or defaultData[field] +end + +local function itemData(item, field) + + local data = octoinv.items[item.class or item[1]] + return istable(item[2]) and item[2][field] or item[field] or (data and data[field]) or defaultData[field] + +end + +local function iconPath(icon, class) + + if not icon then + return class and classData(class, 'icon') or defaultData.icon + elseif isstring(icon) then + return icon + else + return icon:GetName() .. '.png' + end + +end + +local function craftIcon(craft) + + if istable(craft) and istable(craft.finish) and istable(craft.finish[1]) then + return iconPath(itemData(craft.finish[1], 'icon')) + elseif isstring(craft.icon) then + return iconPath(craft.icon) + else + return defaultData.icon + end + +end + + +-- +-- CRAFT AND STUFF MANUAL +-- + +surface.CreateFont('octoinv-help.heading', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) + +surface.CreateFont('octoinv-help.subheading', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local contNames = { + printer = L.printer, + printer_cart = L.printer_cart, + refinery = L.refinery, + refinery_fuel = L.refinery_fuel, + smelter = L.smelter, + smelter_fuel = L.smelter_fuel, + machine = L.machine, + machine_tray = L.machine_tray, + workbench = L.workbench, + fridge = L.fridge, + stove = L.stove, + oven = L.oven, + _hand = L.inventory_hands, +} + +function octoinv.initHelp() + + if IsValid(octoinv.helpPnl) then octoinv.helpPnl:Remove() end + + local ps = vgui.Create 'DPropertySheet' + ps:SetSize(800, 600) + octoinv.helpPnl = ps + + concommand.Add('octoinv_help', function() + if not IsValid(octoinv.helpPnl) then octoinv.initHelp() end + octoinv.helpPnl:SetVisible(not octoinv.helpPnl:IsVisible()) + end) + + local matArrow = Material('octoteam/icons/arrow_left2.png') + local matAction = Material('octoteam/icons/error.png') + local function paintIcon(self, w, h) + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w/2 - 32, h/2 - 32, 64, 64) + if self.text then + draw.Text({ + text = self.text, + pos = { w/2 + 3, h/2 - 1 }, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = color_white, + }) + end + end + + do -- crafts + local p = ps:Add 'DPanel' + p:SetPaintBackground(false) + + local d = p:Add 'DScrollPanel' + d:Dock(FILL) + d.pnlCanvas:DockPadding(10,10,10,10) + + local lv = p:Add 'DListView' + lv:Dock(LEFT) + lv:SetWide(250) + lv:SetHideHeaders(true) + lv:DockMargin(0,0,5,0) + lv:AddColumn(''):SetFixedWidth(32) + lv:AddColumn(L.title) + lv:SetDataHeight(32) + lv:SetMultiSelect(false) + function lv:OnRowSelected(i, line) + d:Clear() + local craft = line.craft + + local p1 = d:Add 'DPanel' + p1:Dock(TOP) + p1:DockMargin(0,0,0,10) + p1:DockPadding(10,5,10,5) + p1:SetPaintBackground(false) + + local from = p1:Add 'DIconLayout' + from:Dock(RIGHT) + from:SetWide(268) + from:SetSpaceX(4) + from:SetSpaceY(4) + + if craft.ings and #craft.ings > 0 then + local l = from:Add 'DLabel' + l:SetFont('octoinv-help.subheading') + l:SetSize(268, 30) + l:SetText(L.ingredients) + l.OwnLine = true + for i, item in ipairs(craft.ings) do + local temp = {class = item[1], amount = item[2]} + octoinv.createItemPanel(from, temp) + end + end + if craft.tools and #craft.tools > 0 then + local l = from:Add 'DLabel' + l:SetFont('octoinv-help.subheading') + l:SetSize(268, 30) + l:SetText(L.tools_hint) + l.OwnLine = true + for i, item in ipairs(craft.tools) do + local temp = {class = item[1], amount = item[2]} + octoinv.createItemPanel(from, temp) + end + end + from:PerformLayout() + + local to = p1:Add 'DIconLayout' + to:Dock(LEFT) + to:SetWide(64) + to:SetSpaceX(4) + to:SetSpaceY(4) + if istable(craft.finish) and #craft.finish > 0 then + local l = to:Add 'DLabel' + l:SetFont('octoinv-help.subheading') + l:SetSize(64, 30) + l:SetText(L.total) + l.OwnLine = true + for i, item in ipairs(craft.finish) do + if istable(item) then + if istable(item[2]) then + local temp = {class = item[1]} + for k, v in pairs(item[2]) do temp[k] = v end + octoinv.createItemPanel(to, temp) + else + octoinv.createItemPanel(to, {class = item[1], amount = item[2] or 1}) + end + end + end + else + octoinv.createItemPanel(to, { + class = 'souvenir', + name = craft.name or L.some_kind_of_action, + desc = craft.desc or L.not_desc, + icon = Material(iconPath(craft.icon)), + mass = 0, + volume = 0, + }) + end + to:PerformLayout() + + local conts = {} + if craft.conts then + for i, contID in ipairs(table.GetKeys(craft.conts)) do table.insert(conts, contNames[contID] or contID) end + end + if #conts == 0 then table.insert(conts, L.anywhere) end + + local arrow = p1:Add 'DPanel' + arrow:Dock(FILL) + arrow:DockMargin(0,32,0,0) + arrow.Paint = paintIcon + arrow.text = table.concat(conts, ', ') + arrow.icon = matArrow + + if craft.jobs and table.Count(craft.jobs) > 0 then + local txt = {} + for jobID, _ in pairs(craft.jobs) do + local job = DarkRP.getJobByCommand(jobID) + if job and job.name then table.insert(txt, job.name) end + end + + local l1 = d:Add 'DLabel' + l1:Dock(TOP) + l1:SetTall(30) + l1:SetWrap(true) + l1:SetFont('octoinv-help.subheading') + l1:SetText(L.can_do .. table.concat(txt, ', ')) + end + + p1:SizeToChildren(false, true) + local ph = p1:GetTall() + p1:SetTall(p1:GetTall() + 10) + local fh, th = from:GetTall(), to:GetTall() + if fh ~= th then + local m = (ph - (fh > th and th or fh)) / 2 + (fh > th and to or from):DockMargin(0, m, 0, m) + end + end + ps:AddSheet(L.recipes, p, octolib.icons.silk16('document_info')) + + for class, craft in SortedPairsByMemberValue(octoinv.crafts, 'name') do + if craft.name then + local icon = vgui.Create 'DImage' + icon:SetImage(craftIcon(craft)) + local line = lv:AddLine(icon, craft.name) + line.craft = craft + end + end + lv:SelectFirstItem() + end + + do -- processes + local p = ps:Add 'DPanel' + p:SetPaintBackground(false) + + local d = p:Add 'DScrollPanel' + d:Dock(FILL) + d.pnlCanvas:DockPadding(10,10,10,10) + + local lv = p:Add 'DListView' + lv:Dock(LEFT) + lv:SetWide(250) + lv:SetHideHeaders(true) + lv:DockMargin(0,0,5,0) + lv:AddColumn(''):SetFixedWidth(32) + lv:AddColumn(L.title) + lv:SetDataHeight(32) + lv:SetMultiSelect(false) + function lv:OnRowSelected(i, line) + d:Clear() + local prod = line.prod + + local l1 = d:Add 'DLabel' + l1:Dock(TOP) + l1:SetTall(60) + l1:SetFont('octoinv-help.heading') + l1:SetText(L.nutrition) + l1:SetContentAlignment(5) + + if prod.fuel then + local fuel = d:Add 'DIconLayout' + fuel:Dock(TOP) + fuel:SetWide(200) + fuel:SetSpaceX(4) + fuel:SetSpaceY(4) + for contID, items in pairs(prod.fuel) do + local l = fuel:Add 'DLabel' + l:SetFont('octoinv-help.subheading') + l:SetSize(200, 30) + l:SetText(contNames[contID] or contID) + l.OwnLine = true + for class, time in pairs(items) do + octoinv.createItemPanel(fuel, { + class = class, + amount = 1, + desc = (L.nutrition_hint):format(math.floor(time / 60), time % 60), + }) + end + end + fuel:PerformLayout() + else + local l = d:Add 'DLabel' + l:Dock(TOP) + l:SetTall(30) + l:SetFont('octoinv-help.subheading') + l:SetText(L.not_required) + end + + local l1 = d:Add 'DLabel' + l1:Dock(TOP) + l1:SetTall(60) + l1:SetFont('octoinv-help.heading') + l1:SetText(L.process) + l1:SetContentAlignment(5) + + for i, proc in pairs(prod.prod) do + local p1 = d:Add 'DPanel' + p1:Dock(TOP) + p1:DockMargin(0,0,0,15) + p1:DockPadding(10,5,10,5) + -- p1:SetPaintBackground(false) + + local from = p1:Add 'DIconLayout' + from:Dock(RIGHT) + from:SetWide(200) + from:SetSpaceX(4) + from:SetSpaceY(4) + + local to = p1:Add 'DIconLayout' + to:Dock(LEFT) + to:SetWide(132) + to:SetSpaceX(4) + to:SetSpaceY(4) + + local arrow = p1:Add 'DPanel' + arrow:Dock(FILL) + arrow:DockMargin(0,32,0,0) + arrow.Paint = paintIcon + arrow.icon = matArrow + arrow.text = ('%d:%02d'):format(math.floor(proc.time / 60), proc.time % 60) + + if proc.desc then + from:SetWide(166) + local l1 = from:Add 'DLabel' + l1.OwnLine = true + l1:SetSize(166, 94) + l1:SetContentAlignment(4) + l1:SetWrap(true) + l1:SetText(proc.desc[1]) + + to:SetWide(166) + local l2 = to:Add 'DLabel' + l2.OwnLine = true + l2:SetSize(166, 94) + l2:SetContentAlignment(4) + l2:SetWrap(true) + l2:SetText(proc.desc[2]) + + arrow:DockMargin(0,0,0,0) + else + for contID, items in pairs(proc.ins) do + local l = from:Add 'DLabel' + l:SetFont('octoinv-help.subheading') + l:SetSize(200, 30) + l:SetText(contNames[contID] or contID) + l.OwnLine = true + for i, item in ipairs(items) do + local temp = { class = item[1], amount = item[2] } + octoinv.createItemPanel(from, temp) + end + end + from:PerformLayout() + + for contID, items in pairs(proc.out) do + local l = to:Add 'DLabel' + l:SetFont('octoinv-help.subheading') + l:SetSize(132, 30) + l:SetText(contNames[contID] or contID) + l.OwnLine = true + for i, item in ipairs(items) do + local chance, class, amountOrData = unpack(item) + if isstring(chance) then + chance = nil + class, amountOrData = unpack(item) + end + if istable(item[2]) then + local temp = { + class = class, + status = chance and (chance * 100 .. '%') or nil, + } + for k, v in pairs(amountOrData) do temp[k] = v end + octoinv.createItemPanel(to, temp) + else + octoinv.createItemPanel(to, { + class = class, + amount = amountOrData or 1, + status = chance and (chance * 100 .. '%') or nil, + }) + end + end + end + to:PerformLayout() + end + + p1:SizeToChildren(false, true) + local ph = p1:GetTall() + p1:SetTall(math.max(p1:GetTall() + 10, 104)) + local fh, th = from:GetTall(), to:GetTall() + if fh ~= th then + local m = (ph - (fh > th and th or fh)) / 2 + (fh > th and to or from):DockMargin(0, m, 0, m) + end + end + end + ps:AddSheet(L.process, p, octolib.icons.silk16('clock')) + + for class, prod in SortedPairsByMemberValue(octoinv.prods, 'name') do + if prod.name then + local icon = vgui.Create 'DImage' + icon:SetImage(iconPath(prod.icon)) + local line = lv:AddLine(icon, prod.name) + line.prod = prod + end + end + lv:SelectFirstItem() + end + + do -- item ids + local function lrc(self, id, line) + local m = DermaMenu() + m:AddOption(L.copy_id, function() SetClipboardText(line:GetColumnText(2)) end):SetIcon('icon16/page_copy.png') + m:Open() + end + + local function irc(self) + local m = DermaMenu() + m:AddOption(L.copy_id, function() SetClipboardText(self.itemID) end):SetIcon('icon16/page_copy.png') + m:Open() + end + + local sp = ps:Add 'DScrollPanel' + local li = sp:Add 'DIconLayout' + li:SetSpaceX(4) + li:SetSpaceY(4) + li:Dock(FILL) + ps:AddSheet(L.items, sp, octolib.icons.silk16('box_search')) + for class, item in SortedPairsByMemberValue(octoinv.items, 'name') do + if item.name then + local temp = {class = class, amount = 1} + local i = octoinv.createItemPanel(li, temp) + i.itemID = class + i.DoRightClick = irc + end + end + + local lw = ps:Add 'DListView' + lw:AddColumn(L.title) + lw:AddColumn('ID') + lw:SetMultiSelect(false) + lw.OnRowRightClick = lrc + ps:AddSheet(L.weapons, lw, octolib.icons.silk16('gun')) + for id, wep in SortedPairsByMemberValue(weapons.GetList(), 'PrintName') do + if wep.PrintName and not wep.HideFromHelp then + lw:AddLine(wep.PrintName, wep.ClassName) + end + end + end + + octoinv.helpPnl:SetVisible(false) + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/cl_octoinv.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/cl_octoinv.lua new file mode 100644 index 0000000..e52fee8 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/cl_octoinv.lua @@ -0,0 +1,1256 @@ +surface.CreateFont('octoinv.item', { + font = 'Arial Bold', + extended = true, + size = 18, + weight = 300, + antialias = true, +}) + +surface.CreateFont('octoinv.item-sh', { + font = 'Arial Bold', + extended = true, + size = 18, + weight = 300, + antialias = true, + blursize = 5, +}) + +surface.CreateFont('octoinv.itemParam', { + font = 'Arial Bold', + extended = true, + size = 13, + weight = 300, + antialias = true, + underline = true, +}) + +surface.CreateFont('octoinv.small', { + font = 'Arial', + extended = true, + size = 14, + weight = 800, + antialias = true, +}) + +surface.CreateFont('octoinv.footer', { + font = 'Arial Bold', + extended = true, + size = 16, + weight = 300, + antialias = true, +}) + +surface.CreateFont('octoinv.footer-sh', { + font = 'Arial Bold', + extended = true, + size = 16, + weight = 300, + blursize = 5, + antialias = false, +}) + +surface.CreateFont('octoinv.title', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) + +surface.CreateFont('octoinv.title-sh', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, + blursize = 5, + antialias = false, +}) + +surface.CreateFont('octoinv.subtitle', { + font = 'Calibri', + extended = true, + size = 34, + weight = 350, +}) + +hook.Add('Think', '_octoinv', function() +hook.Remove('Think', '_octoinv') + +local skinBG = CFG.skinColors.bg +local icons = { + error = octolib.icons.color('error', ''), + -- mass = octolib.icons.color('weight', ''), + -- volume = octolib.icons.color('size', ''), + craft = octolib.icons.color('hammer', ''), + wait = octolib.icons.color('sandwatch', ''), + actions = octolib.icons.color('hand_point', ''), + gestures = octolib.icons.color('hand_peace', ''), + moveAll = Material(octolib.icons.silk16('lorry_go')), + search = Material(octolib.icons.silk16('zoom')), + on = Material(octolib.icons.silk16('lightbulb')), + off = Material(octolib.icons.silk16('lightbulb_off')), +} + +octoinv.invs = octoinv.invs or {} +octoinv.guiCurInv = { pnls = {}, posCache = {} } +octoinv.guiCurInv.posCache[LocalPlayer():EntIndex()] = octolib.vars.get('octoinv.positions') or { } +local pendingCraftBut = nil +local notifyState, notifyTimeout = 0, 0 +local lastOpen, hangOpen = 0, false + +local function showWindow(w) + local pos = { w:GetPos() } + + w:SetVisible(true) + w:MoveToFront() + w:MoveTo(pos[1], pos[2] - 20, 0.2, 0, 0.5) + w:AlphaTo(255, 0.2, 0) + timer.Simple(0.25, function() + if not IsValid(w) then return end + w:SetVisible(true) + w:SetAlpha(255) + w:SetPos(pos[1], pos[2] - 20) + end) +end + +local function hideWindow(w) + local pos = { w:GetPos() } + w:MoveTo(pos[1], pos[2] + 20, 0.2, 0, 2) + w:AlphaTo(0, 0.2, 0) + timer.Simple(0.25, function() + if not IsValid(w) then return end + w:Remove() + end) +end + +local function drawText(text, font, x, y, xal, yal, col, shCol) + + draw.Text { + text = text, + font = font .. '-sh', + pos = {x, y+2}, + xalign = xal, + yalign = yal, + color = shCol, + } + + draw.Text { + text = text, + font = font, + pos = {x, y}, + xalign = xal, + yalign = yal, + color = col, + } + +end + +hook.Add('VGUIMousePressed', 'octoinv', function(pnl, but) + + if not IsValid(octoinv.mainPnl) or not octoinv.mainPnl:IsVisible() then return end + for p, v in pairs(octoinv.guiCurInv.pnls) do + if IsValid(p) and (pnl == p or p:IsOurChild(pnl)) then p:MoveToFront() end + end + +end) + +local cols = { + black35 = Color(0,0,0, 35), + black100 = Color(0,0,0, 100), + black250 = Color(0,0,0, 250), + white150 = Color(255,255,255, 150), + greyDark = Color(50,50,50, 255), + indicator = Color(255,255,255, 200), + health = Color(170,119,102), + armor = Color(170,187,187), + hunger = Color(102,170,170), + txt = Color(238,238,238, 255), + txtSh = Color(0,0,0, 255), +} +local skinCols = CFG.skinColors + +local paints = { + invBut = function(self, w, h) + local al = self:IsHovered() and 1 or 0.75 + if IsValid(self.window) then + draw.RoundedBox(4, 16, -5, 32, 8, cols.indicator) + al = 1 + end + surface.SetMaterial(self.icon) + surface.SetDrawColor(255,255,255, al * 255) + surface.DrawTexturedRect((w - 64) / 2, (h - 64) / 2, 64, 64) + end, + actBut = function(self, w, h) + local al = self:IsHovered() and 1 or 0.75 + surface.SetMaterial(self.icon) + surface.SetDrawColor(255,255,255, al * 255) + surface.DrawTexturedRect(5, 5, 16, 16) + end, + actButSmall = function(self, w, h) + local al = self:IsHovered() and 1 or 0.75 + surface.SetMaterial(self.icon) + surface.SetDrawColor(255,255,255, al * 255) + surface.DrawTexturedRect((w - 32) / 2, (h - 32) / 2, 32, 32) + end, + invPnlBar = function(self, w, h) + draw.RoundedBox(0, 0, 0, w, h, cols.black35) + end, + canvas = function(self, w, h) + local al1 = math.Clamp(self.VBar:GetScroll() * 15, 0, 255) + local al2 = math.Clamp((self.VBar.CanvasSize - self.VBar:GetScroll()) * 15, 0, 255) + draw.RoundedBox(0, 0, 0, w - 13, 1, Color(50,50,50, al1)) + draw.RoundedBox(0, 0, h - 1, w - 13, 1, Color(50,50,50, al2)) + end, + canvasOver = function(self, w, h) + local pnls = dragndrop.GetDroppable('octoinv_items') + if pnls and pnls[1] then + for i, pnl in ipairs(pnls) do + if pnl.invOwner == self.invPnl.invOwner and pnl.contID == self.invPnl.contID then return end + end + + draw.RoundedBox(4, 0, 0, w, h, cols.black250) + draw.RoundedBox(4, 1, 1, w-2, h-2, Color(skinBG.r, skinBG.g, skinBG.b, 150)) + draw.Text({ + text = L.move_there, + font = 'octoinv.item', + pos = { w / 2, h / 2 }, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = color_white + }) + end + end, + item = function(self, w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(238,238,238)) + + surface.SetMaterial(self.icon) + surface.SetDrawColor(255,255,255) + surface.DrawTexturedRect(16, 12, 32, 32) + + local item, hint, hintW, hintH, replTable = self.item, self.hint, self.hintW, self.hintH, self.replTable + draw.Text({ + text = self.nameOverride or (octoinv.getItemData(item, 'name') % replTable), + pos = { 5, h - 10 }, + xalign = TEXT_ALIGN_LEFT, + yalign = TEXT_ALIGN_CENTER, + color = cols.greyDark + }) + + local left = octoinv.getItemUpperMO(item) + if left then + local w2 = 8 + left:GetWidth() + draw.RoundedBoxEx(8, w - w2, 0, w2, 16, skinBG, false, false, true, false) + left:Draw(w - w2 / 2 + 1, 7, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif octoinv.getItemData(item, 'amount') ~= 1 then + local amount = octoinv.getItemData(item, 'amount') + local w2 = 16 + (string.len(amount) - 1) * 6 + draw.RoundedBoxEx(8, w - w2, 0, w2, 16, skinBG, false, false, true, false) + draw.Text({ + text = amount, + font = 'octoinv.small', + pos = { w - w2 / 2 + 1, 7 }, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = color_white + }) + end + + if self.animSt > 0 and hint then + local al = 255 * self.animSt + surface.DisableClipping(true) + + local bx, by, bw, bh = -hintW - 16, -(hintH - 64) / 2 - 4, hintW + 12, hintH + 8 + draw.RoundedBox(4, bx, by, bw, bh, Color(skinBG.r * 0.6, skinBG.g * 0.6, skinBG.b * 0.6, al)) + draw.NoTexture() + surface.DrawPoly({ + {x = -4, y = 36}, + {x = -4, y = 28}, + {x = 0, y = 32}, + }) + draw.RoundedBox(4, bx + 1, by + 1, bw - 2, bh - 2, Color(skinBG.r, skinBG.g, skinBG.b, al)) + draw.NoTexture() + surface.DrawPoly({ + {x = -5, y = 36}, + {x = -5, y = 28}, + {x = -1, y = 32}, + }) + hint:Draw(bx + 6, by + 4, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, al) + + surface.DisableClipping(false) + end + end, +} + +local function createMainPanel() + + if IsValid(octoinv.mainPnl) then octoinv.mainPnl:Remove() end + local p = vgui.Create 'DPanel' + octoinv.mainPnl = p + + p:SetSize(ScrW(), ScrH()) + p:SetPos(0, 0) + p:MakePopup() + + if IsValid(octoinv.guiInvList) then + if octoinv.invs then + octoinv.refresh() + end + else + local pnl = p:Add 'DPanel' + pnl:SetPos(0, 0) + pnl:SetSize(500, 74) + function pnl:Paint(w, h) + local col = ColorAlpha(skinCols.bg50, 200) + draw.RoundedBox(4, 0, 0, 34, h, col) + draw.RoundedBox(4, 56, 0, w - 56, h, col) + end + pnl.invs = {} + octoinv.guiInvList = pnl + + local but1 = pnl:Add 'DButton' + but1:SetPos(0, 0) + but1:SetSize(32, 37) + but1:SetText('') + but1.icon = icons.actions + but1.barTxt = L.actions + but1.Paint = paints.actButSmall + but1.DoClick = function(self) + local m = octogui.cmenu.create() + local x, y = self:LocalToScreen(16 - m:GetWide() / 2, 15 - m:GetTall()) + m:Open(x, y) + m:SetAlpha(0) + showWindow(m) + end + + local but2 = pnl:Add 'DButton' + but2:SetPos(0, 37) + but2:SetSize(32, 37) + but2:SetText('') + but2.icon = icons.gestures + but2.barTxt = L.gestures + but2.Paint = paints.actButSmall + but2.DoClick = function(self) + local menu = octolib.createAnimSelectMenu() + menu:PerformLayout() + + local x, y = self:LocalToScreen(16 - menu:GetWide() / 2, 15 - menu:GetTall()) + menu:Open(x, y) + menu:SetAlpha(0) + showWindow(menu) + end + + local barHealth = pnl:Add 'DPanel' + barHealth:SetPos(36, 0) + barHealth:SetSize(8, 74) + barHealth.barTxt = L.health + function barHealth:Paint(w, h) + local ply = LocalPlayer() + draw.RoundedBox(4, 0, 0, w, h, ColorAlpha(skinCols.bg50, 200)) + + local armor = math.floor(math.Clamp((ply:Armor() or 0) / 100, 0, 1) * (h - w)) + if armor > 0 then draw.RoundedBox(4, 0, h - armor - 8, w, armor + w, cols.armor) end + local health = math.floor(math.Clamp((ply:Health() or 0) / ply:GetMaxHealth(), 0, 1) * (h - w - 2)) + if health > 0 then draw.RoundedBox(4, 1, h - health - 9, w - 2, health + w, cols.health) end + end + + local barHunger = pnl:Add 'DPanel' + barHunger:SetPos(46, 0) + barHunger:SetSize(8, 74) + barHunger.barTxt = L.barhunger + function barHunger:Paint(w, h) + local ply = LocalPlayer() + draw.RoundedBox(4, 0, 0, w, h, ColorAlpha(skinCols.bg50, 200)) + local hunger = math.floor(math.Clamp((ply:GetNetVar('Energy') or 0) / 100, 0, 1) * (h - w - 2)) + if hunger > 0 then draw.RoundedBox(4, 1, h - hunger - 9, w - 2, hunger + w, cols.hunger) end + end + end + p:SetVisible(false) + + function p:Paint(w, h) + + local hvrPnl = vgui.GetHoveredPanel() + local txt = IsValid(hvrPnl) and hvrPnl.barTxt + if txt then + drawText(txt, 'octoinv.title', (w + 54) / 2, h - 84, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM, cols.txt, cols.txtSh) + end + + hook.Run('octoinv.paintPnl', w, h) + + end + + function p:OnKeyCodeReleased(key) + + if input.LookupKeyBinding(key) == '+menu' then + octoinv.show(false) + end + + end + + p:Receiver('octoinv_items', function(self, pnls, drop, menuID, x, y) + if drop then + local pnl = pnls[1] + if not pnl or pnl:GetParent():GetParent():GetParent() == self or not pnl.contID then return end -- omg + + local owner, contID2 = pnl.invOwner, pnl.contID + if pnl.itemID then + local function drop(amount) + netstream.Start('octoinv.drop', owner, contID2, pnl.itemID, amount) + end + + if not menuID or menuID == 1 then + drop(pnl.amount) + elseif menuID == 2 then + local itemName = octoinv.getItemData(octoinv.invs[owner][contID2].items[pnl.itemID], 'name') + local contName = octoinv.invs[owner][contID2].name + Derma_StringRequest(L.drop_items, L.what_hint .. itemName .. L.where_octoinv .. contName .. L.amount_octoinv, pnl.amount, function(s) + local amount = tonumber(s) + if amount and amount > 0 then drop(amount) end + end, nil, L.drop, L.cancel) + end + else + for i, item in ipairs(octoinv.invs[owner][contID2].items) do + netstream.Start('octoinv.drop', owner, contID2, i, item.amount or 1) + end + end + end + end, { + L.drop_all, + L.drop_cost, + }) + +end + +local lastCursorPos = { ScrW() / 2, ScrH() / 2 } +function octoinv.show(show) + + if not IsValid(octoinv.mainPnl) then + createMainPanel() + return + end + + local w = octoinv.mainPnl + if show then + w:SetVisible(true) + w:MoveTo(0, 0, 0.2, 0, 2) + w:AlphaTo(255, 0.2, 0) + w:SetMouseInputEnabled(true) + w:SetKeyboardInputEnabled(true) + timer.Create('octoinv.toggle', 0.25, 1, function() + w:SetPos(0, 0) + w:SetAlpha(255) + w:SetVisible(true) + end) + + gui.SetMousePos(lastCursorPos[1], lastCursorPos[2]) + elseif not timer.Exists('octoinv.toggle') then + lastCursorPos[1], lastCursorPos[2] = gui.MousePos() + + w:SetVisible(true) + w:MoveTo(0, 20, 0.2, 0, 2) + w:AlphaTo(0, 0.2, 0) + w:SetMouseInputEnabled(false) + w:SetKeyboardInputEnabled(false) + timer.Create('octoinv.toggle', 0.25, 1, function() + w:SetPos(0, 20) + w:SetAlpha(0) + w:SetVisible(false) + end) + end + +end + +function octoinv.refreshInv(owner, contID, open) + + for pnl, _ in pairs(octoinv.guiCurInv.pnls) do + if IsValid(pnl) and pnl.invOwner == owner and pnl.contID == contID then + if octoinv.invs[owner] and octoinv.invs[owner][contID] then + pnl:Refresh() + else + hideWindow(pnl) + end + return + end + end + + if open then octoinv.openInv(owner, contID, ScrW() / 2) end + +end + +function octoinv.refresh() + + if not IsValid(octoinv.guiInvList) then createMainPanel() end + + local pnl = octoinv.guiInvList + pnl.invs = pnl.invs or {} + + for owner, invs in pairs(octoinv.invs) do + if not owner then + octoinv.invs[owner] = nil + end + end + + for owner, invs in pairs(pnl.invs) do + for contID, but in pairs(invs) do + if not octoinv.invs[but.invOwner] or not octoinv.invs[but.invOwner][but.contID] then + but:Remove() + pnl.invs[owner][contID] = nil + if table.Count(pnl.invs[owner]) < 1 then + pnl.invs[owner] = nil + end + end + end + end + + local totalW = 62 + local function processOwner(ent) + local invs = octoinv.invs[ent] + if not invs then return end + + for contID, inv in SortedPairs(invs, true) do + pnl.invs[ent] = pnl.invs[ent] or {} + + local but = pnl.invs[ent][contID] + if not IsValid(but) then + but = vgui.Create('DButton', pnl) + but.invOwner = ent + but.contID = contID + end + but:SetPos(totalW, 0) + but:SetSize(64, 74) + but:SetText('') + but.icon = inv.icon or icons.error + but.barTxt = inv.name or L.container + but.Paint = paints.invBut + but.DoClick = function(self) + local arrowX, _ = self:LocalToScreen(32, 0) + self.window = octoinv.openInv(ent, contID, arrowX) + end + + pnl.invs[ent][contID] = but + + totalW = totalW + 70 + end + end + totalW = totalW + 2 + + if octoinv.invs[LocalPlayer():EntIndex()] then + processOwner(LocalPlayer():EntIndex()) + end + + for owner, invs in pairs(octoinv.invs) do + if owner ~= LocalPlayer():EntIndex() then + processOwner(owner) + end + end + + pnl:SetWide(totalW) + + pnl:AlignBottom(5) + pnl:CenterHorizontal() + +end + +local function interpDesc(item) + return octoinv.getItemData(item, 'desc'):gsub('{(.-)}', function(key) + local replace = octoinv.getItemData(item, key) + if replace then + return '' .. replace .. '' + end + end) +end + +function octoinv.createItemPanel(list, item, constExpire) + + local itemPnl = list:Add 'DButton' + itemPnl:SetSize(64, 64) + itemPnl:SetText('') + + itemPnl.animSt = 0 + local icon = octoinv.getItemData(item, 'icon') + itemPnl.icon = isstring(icon) and Material(icon) or icon + itemPnl.item = item + + local amount = octoinv.getItemData(item, 'amount') + local vol, mass = octoinv.getItemData(item, 'volume'), octoinv.getItemData(item, 'mass') + local replTable = octoinv.getReplacementTable(item) + local text = { + '', octoinv.getItemData(item, 'name') % replTable, '\n', + octoinv.getItemData(item, 'desc') % replTable, '\n\n', + vol, ' x ', amount, ' = ', math.Round(vol * amount, 3), 'л\n', + mass, ' x ', amount, ' = ', math.Round(mass * amount, 3), 'кг', + } + + if item.expire then + local timeLeft = item.expire + if not constExpire then timeLeft = timeLeft - os.time() end + text[#text + 1] = '\n' + if timeLeft <= 0 then + text[#text + 1] = 'Срок действия закончился' + elseif timeLeft >= 60 then + text[#text + 1] = 'Осталось: ' .. octolib.time.formatDuration(timeLeft) + else + text[#text + 1] = 'Осталось: меньше минуты' + end + end + + itemPnl.hint = markup.Parse(table.concat(text), 200) + itemPnl.replTable = replTable + + itemPnl.hintW, itemPnl.hintH = itemPnl.hint:Size() + itemPnl.Paint = paints.item + itemPnl.Think = function(self) + local tgt = (self:IsHovered() or self:IsChildHovered()) and 1 or 0 + self.animSt = math.Approach(self.animSt, tgt, FrameTime() * 5) + + if not self.hint and self:IsHovered() then + local amount = octoinv.getItemData(item, 'amount') + local vol, mass = octoinv.getItemData(item, 'volume'), octoinv.getItemData(item, 'mass') + local text = { + '', octoinv.getItemData(item, 'name'), '\n', + interpDesc(item), '\n\n', + vol, ' x ', amount, ' = ', (vol * amount), 'л\n', + mass, ' x ', amount, ' = ', (mass * amount), 'кг', + } + + if item.expire then + local timeLeft = item.expire + if not constExpire then timeLeft = timeLeft - os.time() end + text[#text + 1] = '\n' + if timeLeft <= 0 then + text[#text + 1] = 'Срок действия закончился' + elseif timeLeft >= 60 then + text[#text + 1] = 'Осталось: ' .. octolib.time.formatDuration(timeLeft) + else + text[#text + 1] = 'Осталось: меньше минуты' + end + end + + itemPnl.hint = markup.Parse(table.concat(text), 200) + itemPnl.hintW, itemPnl.hintH = itemPnl.hint:Size() + end + end + itemPnl.OnCursorEntered = function(self) self:SetDrawOnTop(true) end + itemPnl.OnCursorExited = function(self) self:SetDrawOnTop(false) end + + itemPnl.amount = amount + + return itemPnl + +end + +function octoinv.openInv(ent, contID, arrowX, but) + + for pnl, _ in pairs(octoinv.guiCurInv.pnls) do + if not IsValid(pnl) then + octoinv.guiCurInv.pnls[pnl] = nil + else + local owner, id = pnl.invOwner, pnl.contID + if owner == ent and id == contID then + hideWindow(pnl) + return + end + end + end + + if not octoinv.invs[ent] or not octoinv.invs[ent][contID] then return end + + local inv = octoinv.invs[ent][contID] + local items = inv.items + local rh = 101 + 68 * math.max(math.ceil(table.Count(items) / 4), 1) + local w, h = 300, math.min(rh, 380) + local x = arrowX - 150 + + local closePanel = false + if not octoinv.mainPnl:IsVisible() then + -- hacky thing against dframe not being able to receive input + octoinv.show(true, false) + closePanel = true + end + + local pnl = octoinv.mainPnl:Add 'DFrame' + pnl:SetSize(w, h - 30) + pnl:DockPadding(0, 24, 0, 0) + if octoinv.guiCurInv.posCache[ent] and octoinv.guiCurInv.posCache[ent][contID] then + local x, y = unpack(octoinv.guiCurInv.posCache[ent][contID][1]) + x = math.Clamp(x, 0, ScrW() - 50) + y = math.Clamp(y, 0, ScrH() - 50) + pnl:SetPos(x, y) + if octoinv.guiCurInv.posCache[ent][contID][2][2] % 68 ~= 3 then -- if it isn't a size set automatically + pnl:SetSize(unpack(octoinv.guiCurInv.posCache[ent][contID][2])) + end + + pnl:InvalidateLayout() + timer.Simple(0.2, function() if not IsValid(pnl) then return end pnl:InvalidateChildren(true) end) + else + pnl:SetPos(x, ScrH() - h - 50) + end + + pnl:SetAlpha(0) + showWindow(pnl) + function pnl.btnClose:DoClick() + hideWindow(pnl) + end + + pnl:SetTitle(inv.name) + pnl:SetSizable(true) + pnl:SetMinWidth(300) + pnl:SetMinHeight(140) + function pnl:PerformLayout() + self.btnClose:SetPos( self:GetWide() - 28, 0 ) + self.btnClose:SetSize( 24, 24 ) + self.lblTitle:SetPos( 8, 2 ) + self.lblTitle:SetSize( self:GetWide() - 56, 20 ) + end + pnl.btnMaxim:Remove() + pnl.btnMinim:Remove() + function pnl.OnRemove(self) + if not octoinv.guiCurInv.posCache[ent] then octoinv.guiCurInv.posCache[ent] = {} end + octoinv.guiCurInv.posCache[ent][contID] = { {self:GetPos()}, {self:GetSize()} } + end + + pnl.invOwner = ent + pnl.contID = contID + + local canvas = vgui.Create('DScrollPanel', pnl) + -- canvas:SetSize(w, h - 65) + canvas:Dock(FILL) + canvas:DockMargin(5, 6, 5, 6) + canvas.invPnl = pnl + canvas.Paint = paints.canvas + canvas.PaintOver = paints.canvasOver + canvas:Receiver('octoinv_items', function(self, pnls, drop, menuID, x, y) + if drop then + local pnl = pnls[1] + if not pnl or pnl:GetParent():GetParent():GetParent() == self or not pnl.contID then return end -- omg + + local owner, contID2 = pnl.invOwner, pnl.contID + if pnl.itemID then + local function move(amount) + netstream.Start('octoinv.move', {[owner] = {[contID2] = {[pnl.itemID] = amount}}}, ent, contID) + end + + if not menuID or menuID == 1 then + move(pnl.amount or 1) + elseif menuID == 2 then + local itemName = octoinv.getItemData(octoinv.invs[owner][contID2].items[pnl.itemID], 'name') + local contName = inv.name + Derma_StringRequest(L.move_items, L.what_hint .. itemName .. L.where2_octoinv .. contName .. L.amount_octoinv, pnl.amount, function(s) + local amount = tonumber(s) + if amount and amount > 0 then move(amount) end + end, nil, L.move, L.cancel) + end + elseif pnl.items then + local items = {} + for i, item in pairs(pnl.items) do + items[i] = item.amount or 1 + end + netstream.Start('octoinv.move', {[owner] = {[contID2] = items}}, ent, contID) + else + local items = {} + for i, item in ipairs(octoinv.invs[owner][contID2].items) do + items[i] = item.amount or 1 + end + netstream.Start('octoinv.move', {[owner] = {[contID2] = items}}, ent, contID) + end + end + end, { + L.move_all, + L.move_cost, + }) + + canvas:Receiver('octoinv_give', function(self, pnls, drop, menuID, x, y) + local item = drop and pnls[1] and pnls[1].item + if not item then return end + if item.icon then item.icon = isstring(item.icon) and item.icon or (item.icon:GetName() .. '.png') end + netstream.Start('octoinv.create', ent, contID, item) + end) + + local list = canvas:Add 'DIconLayout' + list:Dock(FILL) + list:DockMargin(5, 0, 0, 0) + list:SetSpaceX(4) + list:SetSpaceY(4) + + local bar = pnl:Add 'DPanel' + bar:Dock(TOP) + bar:SetTall(35) + bar:DockPadding(5,5,5,5) + bar.Paint = paints.invPnlBar + + local pr = bar:Add 'DProgress' + pr:Dock(FILL) + pr:SetFraction(0) + + local prl = pr:Add 'DLabel' + prl:Dock(FILL) + prl:SetContentAlignment(5) + prl:SetText('') + + local moveAll, searchBut, toggleBut, craftBut + pnl.Refresh = function(self, search) + list:Clear() + if IsValid(moveAll) then moveAll:Remove() end + if IsValid(searchBut) then searchBut:Remove() end + if IsValid(toggleBut) then toggleBut:Remove() end + if IsValid(craftBut) then craftBut:Remove() end + + local inv = octoinv.invs[ent][contID] + local items = inv.items + local free = octoinv.calcFreeSpace(inv) + + if search then + search = utf8.lower(search) + items = table.Copy(items) + for k, v in pairs(items) do + if not utf8.lower(octoinv.getItemData(v, 'name')):find(search) then + items[k] = nil + end + end + end + + prl:SetText(string.format(L.octoinv_free, free)) + local from, to, start = pr:GetFraction(), 1 - free / inv.volume, CurTime() + local delta = to - from + pr.Think = function(self) + local newVal = from + delta * octolib.tween.easing.outQuad(math.min(CurTime() - start, 0.5) / 0.5, 0, 1, 1) + self:SetFraction(newVal) + if newVal == to then self.Think = function() end end + end + + -- move all and search buts + if free ~= inv.volume then + moveAll = bar:Add 'DButton' + moveAll:Dock(LEFT) + moveAll:SetWide(25) + moveAll:SetText('') + moveAll:Droppable('octoinv_items') + moveAll.items = items + moveAll.invOwner = ent + moveAll.contID = contID + moveAll.icon = icons.moveAll + moveAll:SetToolTip(L.move_all) + moveAll.Paint = paints.actBut + + searchBut = bar:Add 'DButton' + searchBut:Dock(LEFT) + searchBut:SetWide(25) + searchBut:SetText('') + searchBut.icon = icons.search + searchBut.Paint = paints.actBut + searchBut.DoClick = function(self) + Derma_StringRequest(L.search_container, L.write_part_name, '', function(q) + pnl:Refresh(q ~= '' and q) + end) + end + searchBut:SetToolTip(L.search_or_filter) + end + + -- toggle but + if inv.prod then + -- place here but + toggleBut = bar:Add 'DButton' + toggleBut:Dock(RIGHT) + toggleBut:SetWide(25) + toggleBut:SetText('') + toggleBut.icon = inv.on and icons.on or icons.off + toggleBut.Paint = paints.actBut + toggleBut.DoClick = function(self) + netstream.Start('octoinv.toggle', ent, contID) + end + toggleBut:SetToolTip(L.enable_or_disable) + end + + -- craft but + if inv.craft then + craftBut = bar:Add 'DButton' + craftBut:Dock(RIGHT) + craftBut:SetWide(25) + craftBut:SetText('') + craftBut.icon = icons.craft + craftBut.invOwner = ent + craftBut.contID = contID + craftBut.Paint = paints.actBut + craftBut.DoClick = function(self) + pendingCraftBut = self + self.icon = icons.wait + netstream.Start('octoinv.craftlist', ent, contID) + end + craftBut:SetToolTip(L.craft) + end + + if table.Count(items) > 0 then + local i = 0 + for itemID, item in pairs(items) do + local itemPnl = octoinv.createItemPanel(list, item) + itemPnl.itemID = itemID + itemPnl.invOwner = pnl.invOwner + itemPnl.contID = pnl.contID + itemPnl:Droppable('octoinv_items') + itemPnl.DoRightClick = function(self) + local m = DermaMenu() + + local s, o = m:AddSubMenu(L.drop) + o:SetIcon('octoteam/icons/box3_drop.png') + o.m_Image:SetSize(16,16) + local function drop(amount) + netstream.Start('octoinv.drop', ent, contID, itemID, amount) + end + + local o = s:AddOption(L.all, function() drop(octoinv.getItemData(item, 'amount')) end) + local o = s:AddOption(L.cost, function() + local itemName = octoinv.getItemData(octoinv.invs[ent][contID].items[itemID], 'name') + local contName = octoinv.invs[ent][contID].name + Derma_StringRequest(L.drop_items, L.what_hint .. itemName .. L.where_octoinv .. contName .. L.amount_octoinv, octoinv.getItemData(item, 'amount'), function(s) + local amount = tonumber(s) + if amount and amount > 0 then drop(amount) end + end, nil, L.drop, L.cancel) + end) + + if octoinv.getItemData(item, 'canuse') then + local o = m:AddOption(L.octoinv_use, function() + octoinv.pendingUse = { ent, contID, itemID } + netstream.Start('octoinv.uselist', ent, contID, itemID) + end) + o:SetIcon('octoteam/icons/hand_point.png') + o.m_Image:SetSize(16,16) + end + + m:Open() + end + if octoinv.getItemData(item, 'canuse') then + itemPnl.DoClick = function(self) + octoinv.pendingUse = { ent, contID, itemID } + netstream.Start('octoinv.uselist', ent, contID, itemID) + end + end + + i = i + 1 + end + + -- if it isn't a size set automatically, adjust + if octoinv.guiCurInv.posCache[ent] and octoinv.guiCurInv.posCache[ent][contID] and octoinv.guiCurInv.posCache[ent][contID][2][2] % 68 == 3 then + local rh = 101 + 68 * math.ceil(math.max(i / 4), 1) + pnl:SetSize(300, math.min(rh, 380) - 30) + end + + list:InvalidateLayout(true) + timer.Simple(0.2, function() if not IsValid(list) then return end list:InvalidateChildren(true) end) + else + local label = list:Add 'DLabel' + label:SetSize(w - 15, 64) + label:SetFont('octoinv.item') + label:SetContentAlignment(5) + label:SetTextColor(cols.white150) + label:SetText(search and L.not_found or table.Random({ + L.octoinv_empty, + L.octoinv_empty2, + L.octoinv_empty3, + })) + end + end + pnl:Refresh() + + if closePanel then octoinv.show(false) end + + octoinv.guiCurInv.pnls[pnl] = true + return pnl + +end + +local defaultData = { + amount = 1, + volume = 0, + mass = 0, + name = L.unknown, + desc = L.not_desc, + icon = 'icon16/exclamation.png', +} + +function octoinv.getItemData(item, field) + + local data = octoinv.items[item.class] + return item[field] or (data and data[field]) or defaultData[field] + +end + + +function octoinv.getReplacementTable(item, class) + + local isTable = istable(item) + if not isTable and not class then return {} end + if not class and not item.class then return {} end + + local out = table.Copy(octoinv.items[isTable and item.class or class]) + if isTable then + for k, v in pairs(item) do + out[k] = v + end + end + + return out + +end + +function octoinv.getItemUpperMO(item) + + if item._mo then return item._mo end + + local status = octoinv.getItemData(item, 'status') + if status then + item._mo = markup.Parse(('%s'):format(status)) + return item._mo + end + + local leftMaxField = octoinv.getItemData(item, 'leftMaxField') + local leftMax = octoinv.getItemData(item, 'leftMax') or leftMaxField and octoinv.getItemData(item, leftMaxField) + if not leftMax then return end + + -- leftField is the key of table field to display "remaining" value from + local left = octoinv.getItemData(item, 'leftVal') or octoinv.getItemData(item, octoinv.getItemData(item, 'leftField')) or 0 + local gbMul = octolib.math.remap(left / leftMax, 0, 0.4, 0.3, 1, true) + local col = Color(255,255 * gbMul,255 * gbMul) + item._mo = markup.Parse(('%s/%s'):format(col.r, col.g, col.b, left, leftMax)) + return item._mo + +end + +function octoinv.calcFreeSpace(inv) + + local space = inv.volume + for id, item in pairs(inv.items) do + space = space - octoinv.getItemData(item, 'volume') * octoinv.getItemData(item, 'amount') + end + + return math.Round(space, 4) + +end + +function octoinv.putItems(tgt, contID) + + netstream.Start('octoinv.move', octoinv.queue, tgt, contID) + +end + +function octoinv.notifyChange() + + if IsValid(octoinv.mainPnl) and octoinv.mainPnl:IsVisible() then return end + notifyTimeout = CurTime() + 5 + +end + +local notifyIcon = Material('octoteam/icons/arrow_down.png') +hook.Add('HUDPaint', 'octoinv.notify', function() + + if hook.Run('HUDShouldDraw', 'octoinv.notify') == false then return end + notifyState = math.Approach(notifyState, CurTime() >= notifyTimeout and 0 or 1, FrameTime() * 3) + if notifyState > 0 then + local st = octolib.tween.easing.outQuad(notifyState, 0, 1, 1) + local x, y = math.floor((ScrW() - 130) / 2), math.floor(ScrH() - 50 * st) + draw.SimpleText(L.something_in_inventory, 'octoinv.item-sh', x + 5, y, color_black, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(L.something_in_inventory, 'octoinv.item', x + 5, y, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(L.press_q, 'octoinv.footer-sh', x + 5, y + 18, color_black, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(L.press_q, 'octoinv.footer', x + 5, y + 18, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(notifyIcon) + surface.DrawTexturedRect(x - 37, y, 32, 32) + end + +end) + +hook.Add('ShutDown', 'octoinv.save', function() + + local positions = table.Copy(octoinv.guiCurInv.posCache[LocalPlayer():EntIndex()] or { }) + + for containerPanel, _ in pairs(octoinv.guiCurInv.pnls) do + if IsValid(containerPanel) then + positions[containerPanel.contID] = { + { containerPanel:GetPos() }, + { containerPanel:GetSize() } + } + end + end + + octolib.vars.set('octoinv.positions', positions, true) + +end) + +netstream.Hook('octoinv.sync', function(owner, id, cont) + + local open = false + if owner then + if (not octoinv.invs[owner] and owner ~= LocalPlayer():EntIndex()) or (id == '_hand' and cont.items[1]) then + octoinv.notifyChange() + end + + octoinv.invs[owner] = octoinv.invs[owner] or {} + local ownerEnt = Entity(owner) + if ownerEnt:IsPlayer() and ownerEnt ~= LocalPlayer() then cont.name = ownerEnt:Name() .. ' - ' .. cont.name end + octoinv.invs[owner][id] = cont + if isstring(cont.icon) then cont.icon = Material(cont.icon) end + end + + octoinv.refreshInv(owner, id, open) + octoinv.refresh() + +end) + +netstream.Hook('octoinv.forget', function(owner, id) + + if not octoinv.invs[owner] then return end + octoinv.invs[owner][id] = nil + if table.Count(octoinv.invs[owner]) < 1 then + octoinv.invs[owner] = nil + end + + octoinv.refresh() + octoinv.refreshInv(owner, id) + +end) + +netstream.Hook('octoinv.sendRegistered', function(data) + + octoinv.items = data.items + octoinv.shopCats = data.shopCats + octoinv.shopItems = data.shopItems + octoinv.crafts = data.crafts + octoinv.prods = data.prods + + for itemID, item in pairs(data.market) do + octoinv.registerMarketItem(itemID, item) + end + + for itemID, item in pairs(octoinv.items) do + item.icon = Material(item.icon) + end + +end) + +netstream.Hook('octoinv.craftlist', function(data) + + local pnl = pendingCraftBut + if not IsValid(pnl) then return end + + local m = DermaMenu() + + local added = 0 + for craftID, craft in SortedPairs(data) do + local item = octoinv.items[craft.class] + local icon = craft.icon or (item and item.icon and (item.icon:GetName() .. '.png')) or 'octoteam/icons/error.png' + local o = m:AddOption(craft.name or item.name, function() + netstream.Start('octoinv.craft', pnl.invOwner, pnl.contID, craftID) + end) + o:SetIcon(icon) + o.m_Image:SetSize(16,16) + o:InvalidateLayout() + o:SetToolTip(craft.desc or (item and interpDesc(item)) or L.not_desc) + + added = added + 1 + end + + if added < 1 then + local o = m:AddOption(L.nothing_do_this) + o:SetIcon('octoteam/icons/emote_question.png') + o.m_Image:SetSize(16,16) + o:InvalidateLayout() + end + + m:Open(pendingCraftBut:LocalToScreen(26, 0)) + pendingCraftBut.icon = icons.craft + +end) + +netstream.Hook('octoinv.uselist', function(data) + + local cache = octoinv.pendingUse + if not cache[1] then return end + + local m = DermaMenu() + + local added = 0 + if data then + for i, useData in pairs(data) do + if useData[2] then + local o = m:AddOption(useData[2], function() + netstream.Start('octoinv.use', cache[1], cache[2], cache[3], useData[1]) + end) + o:SetIcon(useData[3] or 'octoteam/icons/error.png') + o.m_Image:SetSize(16,16) + o:InvalidateLayout() + else + local o = m:AddOption(useData[3] or L.action_unavailable) + o:SetIcon('icon16/error.png') + o:SetDisabled(true) + o:SetAlpha(100) + end + + added = added + 1 + end + end + + if added < 1 then + local o = m:AddOption(L.nothing_do_this) + o:SetIcon('octoteam/icons/emote_question.png') + o.m_Image:SetSize(16,16) + o:InvalidateLayout() + end + + m:Open() + +end) + +local smWeps = { + weapon_physgun = true, + gmod_tool = true, + gmod_camera = true, + octo_camera = true, +} +local noInvWeps = { + dbg_dog = true, +} + +concommand.Add('+menu', function() + + if hook.Run('octolib.shouldOpenMenu') == false then return end + + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and smWeps[wep:GetClass()] then + hook.Call( "OnSpawnMenuOpen", GAMEMODE ) + else + octoinv.show(true) + + if CurTime() - lastOpen < 0.3 then hangOpen = true end + lastOpen = CurTime() + + notifyTimeout = CurTime() + octoinv.refresh() + end + +end) + +concommand.Add('-menu', function() + + if not input.IsKeyTrapping() then hook.Call( "OnSpawnMenuClose", GAMEMODE ) end + + local wep = LocalPlayer():GetActiveWeapon() + if not IsValid(wep) or not smWeps[wep:GetClass()] then + if hangOpen then hangOpen = false return end + octoinv.show(false) + end + +end) + +end) + +local meta = FindMetaTable 'Player' +function meta:HasItem(class) + local index = self:EntIndex() + local inv = octoinv.invs and octoinv.invs[index] + if not inv then return false end + for _, v in pairs(inv) do + for _, item in ipairs(v.items) do + if item.class == class then + return true + end + end + end + return false +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/ext_entity.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/ext_entity.lua new file mode 100644 index 0000000..0a646e3 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/ext_entity.lua @@ -0,0 +1,110 @@ +local ent = FindMetaTable 'Entity' + +function ent:CreateInventory() + + return octoinv.createInv(self) + +end + +function ent:GetInventory() + + return self.inv + +end + +function ent:PrintInventory() + + if not self.inv then + print("no inventory") + return + end + + local inv = table.Copy(self.inv) + for contID, cont in pairs(inv.conts) do + cont.id = nil + cont.inv = nil + for itemID, item in ipairs(cont.items) do + item.classTbl = nil + item.cont = nil + end + end + + PrintTable(inv) + +end + +function ent:ExportInventory() + + return self.inv and self.inv:Export() or nil + +end + +function ent:ImportInventory(data) + + local ct = os.time() + local inv = self:CreateInventory() + for contID, cont in pairs(data) do + local newCont = inv:AddContainer(contID, cont) + if cont.items then + for itemID, item in ipairs(cont.items) do + if item.expire and item.expire <= ct then continue end + newCont:AddItem(item.class, item, true) + end + end + end + +end + +function ent:AddContainer(...) + + local inv = self:GetInventory() + if not inv then return end + + return inv:AddContainer(...) + +end + +function ent:AddItem(...) + + local inv = self:GetInventory() + if not inv then return end + + return inv:AddItem(...) + +end + +function ent:TakeItem(...) + + local inv = self:GetInventory() + if not inv then return end + + return inv:TakeItem(...) + +end + +function ent:HasItem(...) + + local inv = self:GetInventory() + if not inv then return false end + + return inv:HasItem(...) + +end + +function ent:FindItems(...) + + local inv = self:GetInventory() + if not inv then return end + + return inv:FindItems(...) + +end + +function ent:FindItem(...) + + local inv = self:GetInventory() + if not inv then return end + + return inv:FindItem(...) + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/ext_player.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/ext_player.lua new file mode 100644 index 0000000..41c57ae --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/ext_player.lua @@ -0,0 +1,262 @@ +local ply = FindMetaTable 'Player' + +function ply:CanUseInventory(inv) + + if not IsValid(self) or not inv then return false end + + local pos = self:GetPos() + local can = IsValid(self) and self:Alive() and not self:GetNetVar('Ghost') + and IsValid(inv.owner) and inv.owner:NearestPoint(pos):DistToSqr(pos) < 7056 + + return can + +end + +function ply:HasAccessToContainer(owner, contID) + + if not self:Alive() or self:GetNetVar('Ghost') then return false end + + local inv = owner:GetInventory() + if not inv then return false end + + local cont = inv:GetContainer(contID) + if not cont or (inv.owner ~= self and not cont.users[self]) then return false end + + return true, cont + +end + +function ply:OpenInventory(inv, conts) + + if not self:Alive() or self:GetNetVar('Ghost') then return false end + if not inv then return end + + inv:AddUser(self, conts) + +end + +function ply:CloseInventory(inv, conts) + + if not inv then return end + + inv:RemoveUser(self, conts) + +end + +function ply:SyncOctoinv() + + local itemData = {} + for itemID, item in pairs(octoinv.items) do + itemData[itemID] = { + name = item.name, + icon = item.icon, + desc = item.desc, + status = item.status, + mass = item.mass, + model = item.model, + volume = item.volume, + class = item.class, + nostack = item.nostack, + leftField = item.leftField, + leftMax = item.leftMax, + leftMaxField = item.leftMaxField, + canuse = item.use ~= nil, + } + end + + local shopCatData = {} + for catID, cat in pairs(octoinv.shopCats) do + shopCatData[catID] = { + name = cat.name, + icon = cat.icon, + } + end + + local craftData = {} + for craftID, craft in pairs(octoinv.blueprints) do + craftData[craftID] = { + name = craft.name, + desc = craft.desc, + conts = craft.conts, + jobs = craft.jobs, + time = craft.time, + tools = craft.tools, + ings = craft.ings, + finish = craft.finish, + icon = craft.icon, + } + end + + local prodData = {} + for prodID, prod in pairs(octoinv.prod) do + prodData[prodID] = { + name = prod.name, + icon = prod.icon, + fuel = prod.fuel, + prod = prod.prod, + } + end + + local marketData = {} + for itemID, item in pairs(octoinv.marketItems) do + marketData[itemID] = { + name = item.name, + icon = item.icon, + parent = item.parent, + nostack = item.nostack, + } + end + + netstream.Heavy(self, 'octoinv.sendRegistered', { + items = itemData, + shopCats = shopCatData, + shopItems = octoinv.shopItems, + crafts = craftData, + prods = prodData, + market = marketData, + }) + +end + +function ply:SaveInventory(noMsg) + + if hook.Run('octoinv.overrideInventories') == false then return end + + local veh = carDealer.getCurVeh(self) + if IsValid(veh) then carDealer.saveVeh(veh) end + if IsValid(self.storage) then self.storage:Save() end + + local inv = self:ExportInventory() + if not istable(inv) then return end + + local steamID = self:SteamID() + octolib.db:PrepareQuery([[ + UPDATE inventory + SET inv = ? + WHERE steamID = ? + ]], { pon.encode(inv), steamID }, function(q, st, res) + if not noMsg then + octoinv.msg((st and 'Saved inventory for ' or 'Failed to save inventory for ') .. steamID) + if not st then + octoinv.msg(res) + end + end + end) + +end + +-- NOTE: switching weapons in in dbg-weapons module +function ply:SetHandHold(val) + + self:SetNetVar('HandHold', val) + self:MoveModifier('hand', val and { + norun = true, + nojump = true, + } or nil) + + local wep = self:GetActiveWeapon() + if IsValid(wep) then + wep:SetHoldType(val and 'duel' or 'normal') + end + +end + +function ply:BringEntity(ent, rotate) + + local dir = self:GetAimVector() + dir.z = 0 + dir:Normalize() + + local ang = self:EyeAngles() + ang.p = 0 + ang.r = 0 + ang.y = ang.y + 180 - (rotate or 0) + + local pos = self:GetShootPos() + dir * 25 + ent:SetPos(pos) + ent:SetAngles(ang) + pos = ent:NearestPoint(pos + dir * 512) + + ent:SetPos(pos) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + +end + +function ply:PickupItem(ent) + + if not IsValid(ent) then return end + + local entClass = ent:GetClass() + local itemClass = octoinv.itemClasses[entClass] + if itemClass then + local inv = self:GetInventory() + local cont = inv and inv:GetContainer('_hand') + if cont then + local pickup, data = octoinv.items[itemClass].pickup, ent.pickupItemData + if pickup then + local dataOverride = pickup(self, ent) + if dataOverride == false then return end + + table.Merge(data, dataOverride or {}) + end + + local item, amount = octoinv.createItem(itemClass, cont, data) + if item then + self:DoAnimation(ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND) + ent:Remove() + hook.Run('octoinv.pickup', self, ent, item, amount) + end + else + self:Notify('warning', L.onlyhandsitem) + end + end + +end + +function ply:LoadInventory() + + if hook.Run('octoinv.overrideInventories') == false then + self.loadedInv = true + return + end + + local steamID = self:SteamID() + octolib.db:PrepareQuery([[ + SELECT inv FROM inventory + WHERE steamID = ? + ]], { steamID }, function(q, st, res) + res = istable(res) and res[1] + if res and res.inv then + -- temp + if not pcall(function() + self:ImportInventory(pon.decode(res.inv)) + if not self.inv.conts.top.icon then + self.inv.conts.top.icon = octolib.icons.color('clothes_jacket') + self.inv.conts.bottom.icon = octolib.icons.color('clothes_jeans') + end + end) then + self:Notify('warning', L.error_inventory) + self:ImportInventory(octoinv.defaultInventory) + end + + octoinv.msg('Loaded inventory for ' .. steamID) + else + self:ImportInventory(octoinv.defaultInventory) + + octoinv.msg('New inventory for ' .. steamID) + octolib.db:PrepareQuery([[ + INSERT INTO inventory(steamID) + VALUES (?) + ]], { steamID }, function(q, st, res) + self:SaveInventory() + end) + end + + self.loadedInv = true + end) + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_cont.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_cont.lua new file mode 100644 index 0000000..6cc6991 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_cont.lua @@ -0,0 +1,560 @@ +octoinv.ContainerClass = octoinv.ContainerClass or {} + +hook.Add('octoinv.taken', 'octoinv.expire', function(cont) + if cont:FindItem({expire = {_exists = true, _lte = os.time()}}) then + cont:CreateExpireTimer() + else + timer.Remove('octoinv.expireThink' .. cont.uid) + end +end) +hook.Add('octoinv.added', 'octoinv.expire', function(cont, item) + local expire = item:GetData('expire') + if expire then + if expire <= os.time() then return item:Remove() end + cont:CreateExpireTimer() + end +end) + +local Container = octoinv.ContainerClass +Container.__index = Container + +-- (container) creates and returns a new container +function octoinv.createCont(id, data) + + if not id then return end + local default = octoinv.defaultInventory[id] or {} + local cont = { + id = id, + uid = octolib.string.uuid(), + name = data.name or default.name or L.container, + volume = data.volume or default.volume or 10, + craft = data.craft or default.craft, + prod = data.prod or default.prod, + icon = data.icon or default.icon, + hooks = {}, + items = {}, + users = {}, + } + + setmetatable(cont, Container) + return cont + +end + +-- (nil) destroys the container +function Container:Remove(dropItems, pos, ang, vel) + + local inv = self:GetParent() + local owner = inv.owner + + self:RemoveUser(owner) + self:RemoveUsers() + timer.Remove('octoinv.expireThink' .. self.uid) + + if dropItems then + for i = #self.items, 1, -1 do + if self.items[i].cont == self then + if pos ~= false then + pos = pos or IsValid(owner) and owner:GetPos() or Vector() + ang = ang or IsValid(owner) and owner:GetAngles() or Angle() + vel = vel or VectorRand() * 50 + end + + self.items[i]:Drop(nil, nil, pos, ang, vel) + end + end + end + + inv.conts[self.id] = nil + octoinv.unsyncCont(self) + +end + +-- (item) returns item in container by its id +function Container:GetItem(id) + + return self.items[id] + +end + +-- (table) returns all items in container +function Container:GetItems() + + return self.items + +end + +function Container:CreateExpireTimer() + local tName = 'octoinv.expireThink' .. self.uid + if not timer.Exists(tName) then + timer.Create(tName, 60, 0, function() + local expiringLeft = 0 + for _,v in ipairs(self.items) do + if v:GetData('expire') and not v:CheckExpired() then + expiringLeft = expiringLeft + 1 + end + end + + if expiringLeft < 1 then + timer.Remove(tName) + end + end) + end +end + +-- (inventory) returns container's parent inventory +function Container:GetParent() + + return self.inv + +end +Container.GetInventory = Container.GetParent + +-- (int) returns amount of specified item in container +function Container:HasItem(item) + + if istable(item) then + -- it's an item reference + for i, _item in ipairs(self.items) do + if _item == item then + return _item + end + end + elseif isstring(item) then + -- it's a class + local amount = 0 + for i, _item in ipairs(self.items) do + if _item.class == item then + amount = amount + _item:GetData('amount') + end + end + + --return amount ~= 0 and amount or false + return amount + end + + return false + +end + +-- (float) returns free space in container +function Container:FreeSpace() + + if self.space then return self.space end + + local space = self.volume + for i, item in ipairs(self.items) do + space = space - item:GetData('volume') * item:GetData('amount') + end + self.space = math.Round(space, 4) + + return self.space + +end + +-- (float) returns mass of container +function Container:GetMass() + + if self.mass then return self.mass end + + local mass = 0 + for i, item in ipairs(self.items) do + mass = mass + item:GetData('mass') * item:GetData('amount') + end + + self.mass = math.Round(mass, 4) + return self.mass + +end + +-- (bool) returns whether player can craft inside container +function Container:CanCraft() + + return self.craft or false + +end + +-- (bool) returns whether player can process inside container +function Container:CanProd() + + return self.prod or false + +end + +-- (bool) sets container power on of off and returns current state +function Container:ToggleProd() + + if not self.prod then return end + + self.on = not self.on + self:QueueSync() + + return self.on + +end + +-- (any) calls a hook chain on container and returns first non-nil value +function Container:Call(event, ...) + + if not event or not self.hooks[event] then return end + for name, func in pairs(self.hooks[event]) do + local q, w, e, r, t, y = func(self, ...) + if q ~= nil then return q, w, e, r, t, y end + end + +end + +-- (container) adds a hook function to given event and returns self +function Container:Hook(event, name, func) + + if not event or (func ~= nil and not isfunction(func)) then return end + self.hooks[event] = self.hooks[event] or {} + self.hooks[event][name] = func + + if table.Count(self.hooks[event]) < 1 then self.hooks[event] = nil end + + return self + +end + +-- (int) returns maximum amount of items which container can take now +function Container:SpaceFor(item, data) + + if isstring(item) and octoinv.items[item] then + -- calc by class name + local volume = istable(data) and data.volume or octoinv.items[item].volume + if volume == 0 then return 2147483648 end + + local amount = self:FreeSpace() / volume + return math.floor(tonumber(tostring(amount))) + elseif istable(item) then + -- use item's data + local volume = item:GetData('volume') * item:GetData('amount') + local amount = self:FreeSpace() / volume + return math.floor(tonumber(tostring(amount))) + end + + return 0 + +end + +-- (int) +function Container:DropNewItem(class, data) + + local item + if istable(class) then + item = self:AddItem(class) + else + item = octoinv.createItem(class, self, data, true) + end + if item then item:Drop() end + +end + +-- (nil) sync container to clients +function Container:Sync() + + self:GetParent():UpdateMass() + + local owner = self:GetParent().owner + local receivers = {} + if IsValid(owner) and owner:IsPlayer() then + table.insert(receivers, owner) + if self.id == '_hand' then + if self.volume - self:FreeSpace() >= 3 then + owner:SetHandHold(true) + else + owner:SetHandHold(nil) + end + end + end + for ply, _ in pairs(self.users) do + if IsValid(ply) and ply:IsPlayer() then + table.insert(receivers, ply) + else + self.users[ply] = nil + end + end + + local toSend = table.Copy(self) + toSend.inv = nil + toSend.id = nil + toSend.ownerID = nil + toSend.hooks = nil + for i, item in ipairs(toSend.items) do + item.classTbl = nil + item.cont = nil + for k, v in pairs(item) do + if isfunction(v) then item[k] = nil end + end + end + + netstream.Start(receivers, "octoinv.sync", self.ownerID, self.id, toSend) + +end + +function Container:ResetSpaceMass() + + -- invalidate cache + self.space = nil + self.mass = nil + +end + +-- (nil) add container to sync queue and adjust item ids +function Container:QueueSync() + + -- restore item ids in case if they have changed + for i, item in ipairs(self.items) do item.id = i end + -- update owner index + self.ownerID = self.inv and IsValid(self.inv.owner) and self.inv.owner:EntIndex() or nil + -- add to queue + octoinv.syncCont(self) + +end + +-- (table) returs table of items matching octolib.filterQuery +function Container:FindItems(query) + + local keys = table.GetKeys(query) + if keys[1] == 'class' and not keys[2] and isstring(query.class) then + local class = query.class + local found = {} + for _, _item in ipairs(self.items) do + if _item.class == class then + found[#found + 1] = _item + end + end + return found + end + + return octolib.array.filterQuery(self.items, query) + +end + +-- (item) returns first item matching octolib.filterQuery +function Container:FindItem(query) + + local keys = table.GetKeys(query) + if keys[1] == 'class' and not keys[2] and isstring(query.class) then + local class = query.class + for _, _item in ipairs(self.items) do + if _item.class == class then + return _item + end + end + return + end + + return octolib.array.filterQuery(self.items, query, 1)[1] + +end + +-- (table) returs table of unique items matching list of octolib.filterQuery +function Container:FindItemsByQueryList(queries) + + local foundItems = {} + for _, query in ipairs(queries) do + local ok = false + for _, item in ipairs(self:FindItems(query)) do + if not foundItems[item] then + foundItems[item] = true + ok = true + break + end + end + + if not ok then return end + end + + return table.GetKeys(foundItems) + +end + +-- (int, item) adds an item to container and returns item and added amount +function Container:AddItem(item, data, force) + + if istable(item) then + -- TODO: add checks + local cont = item:GetParent() + if cont then cont:TakeItem(item) end + table.insert(self.items, item) + item.cont = self + self:AddItemStats(item) + self:QueueSync() + + hook.Run('octoinv.added', self, item, 1) + return 1, item + elseif isstring(item) then + local itemTbl = octoinv.items[item] + if not itemTbl then return 0 end + + local canAdd = force and math.huge or self:SpaceFor(item, data) + if not canAdd or canAdd < 1 then return 0 end + + if itemTbl.nostack then + -- data is override in stackables + if istable(data) and data.amount and canAdd < data.amount then return 0, nil end + local item = octoinv.createItem(item, self, data, force) + self:AddItemStats(item) + self:QueueSync() + + hook.Run('octoinv.added', self, item, 1) + return item and item:GetData('amount'), item + else + -- data is amount in stackables + local amount = istable(data) and data.amount or data or 1 + if amount <= 0 then return 0 end + + local given = math.min(canAdd, amount) + local itemR = self:FindItem({class = item}) + if itemR then + itemR:SetData('amount', itemR:GetData('amount') + given) + else + itemR = octoinv.createItem(item, self, { amount = given }, force) + self:AddItemStats(itemR) + end + self:QueueSync() + + hook.Run('octoinv.added', self, itemR, given) + return given, itemR + end + end + + return false + +end +Container.GiveItem = Container.AddItem + +-- (int) removes an item and returns how many were removed +function Container:TakeItem(item, amount) + + if istable(item) then + -- take item instance + if not item.id then return octoinv.msg('ERROR: No id present on item. Aborting removal.') end + if item.cont ~= self then return octoinv.msg('ERROR: Trying to remove other container\'s item. Aborting removal.') end + + if self.items[item.id] then + local amount = item:GetData('amount') + table.remove(self.items, item.id) + self:TakeItemStats(item) + self:QueueSync() + hook.Run('octoinv.taken', self, item, amount) + return amount + else + return 0 + end + elseif isstring(item) then + -- take items by class + local item = self:FindItem({class = item}) + if not item then return 0 end + + local taken = math.min(amount or 1, item:GetData('amount')) + if not taken or taken <= 0 then return 0 end + + item:SetData('amount', item:GetData('amount') - taken) + self:QueueSync() + + hook.Run('octoinv.taken', self, item, taken) + return taken + end + + return false + +end +Container.RemoveItem = Container.TakeItem + +-- (nil) adds mass and volume of item to container +function Container:AddItemStats(item, amountOverride) + self.mass = (self.mass and item:GetMass(amountOverride) or 0) + self:GetMass() + self.space = (self.space and -item:GetVolume(amountOverride) or 0) + self:FreeSpace() +end + +-- (nil) removes mass and volume of item from container +function Container:TakeItemStats(item, amountOverride) + self.mass = (self.mass and -item:GetMass(amountOverride) or 0) + self:GetMass() + self.space = (self.space and item:GetVolume(amountOverride) or 0) + self:FreeSpace() +end + +-- (nil) removes all items from container +function Container:Clear() + table.Empty(self.items) + self:ResetSpaceMass() + self:QueueSync() +end + +-- (nil) adds a user to container +function Container:AddUser(ply) + + self.users[ply] = true + self:Call('userAdd', ply) + self:QueueSync() + +end + +-- (nil) removes a user from container +function Container:RemoveUser(ply) + + self.users[ply] = nil + self:Call('userRemove', ply) + + if IsValid(ply) and ply:IsPlayer() then + netstream.Start(ply, "octoinv.forget", self.ownerID, self.id) + end + +end + +-- (nil) removes all users from container +function Container:RemoveUsers() + + for ply, _ in pairs(self.users) do + self:RemoveUser(ply) + end + +end + +-- (container) moves container to another inventory and returns the container +function Container:MoveTo(inv) + + if not inv then return false end + + local prevInv = self:GetParent() + if prevInv then + prevInv.conts[self.id] = nil + if IsValid(prevInv.owner) and prevInv.owner:IsPlayer() then + self:RemoveUser(prevInv.owner) + end + end + self:RemoveUsers() + + inv.conts[self.id] = self + self.inv = inv + + self:QueueSync() + +end +Container.Move = Container.MoveTo + +-- (table) returns serializable table of this item +function Container:Export() + local cont = table.Copy(self) + cont.id = nil + cont.uid = nil + cont.inv = nil + cont.users = nil + cont.space = nil + cont.mass = nil + cont.ownerID = nil + cont.hooks = nil + for k,v in pairs(octoinv.defaultInventory[self.id] or {}) do + if cont[k] == v then + cont[k] = nil + end + end + local items = {} + for itemID, item in pairs(cont.items) do + items[itemID] = item:Export() + end + cont.items = items + return cont +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_inv.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_inv.lua new file mode 100644 index 0000000..6dc0567 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_inv.lua @@ -0,0 +1,412 @@ +octoinv.InventoryClass = octoinv.InventoryClass or {} +local Inventory = octoinv.InventoryClass +Inventory.__index = Inventory + +-- (inventory) creates a new inventory and returns it +function octoinv.createInv(owner) + + if not owner or not IsValid(owner) then return end + if owner.inv then + owner.inv:Remove() + end + + local inv = { + owner = owner, + conts = {} + } + + setmetatable(inv, Inventory) + owner.inv = inv + + local tname = 'octoinv_' .. tostring(owner) + timer.Create(tname, 1, 0, function() + if inv and IsValid(inv.owner) then + for contID, cont in pairs(inv.conts) do + for ply, _ in pairs(cont.users) do + if not ply:CanUseInventory(inv) then + inv:RemoveUser(ply) + end + end + end + else + timer.Remove(tname) + end + end) + + return inv + +end + +-- (entity) gets owner of inventory +function Inventory:GetOwner() + + return self.owner + +end + +-- (nil) sets owner of inventory +function Inventory:SetOwner(ent) + + if self.owner then + self.owner.inv = nil + end + + if ent.inv then + ent.inv:Remove() + end + + for contID, cont in pairs(self.conts) do + cont:RemoveUsers() + cont:QueueSync() + end + + self.owner = ent + ent.inv = self + +end + +-- (nil) destroys the inventory +function Inventory:Remove(...) + + for contID, cont in pairs(self.conts) do + cont:Remove(...) + end + + self.owner.inv = nil + self.owner = nil + +end + +-- (container) creates a container, attaches to inventory and returns it +function Inventory:AddContainer(id, ...) + + if not id then return end + name = name or L.container + volume = volume or 10 + + if self.conts[id] then + octoinv.msg('ERROR: Trying to add container with duplicate id!') + return + end + + local cont = octoinv.createCont(id, ...) + cont.inv = self + + self.conts[id] = cont + cont:QueueSync() + + return cont + +end + +-- (container[]) returns all containers of the inventory +function Inventory:GetContainers() + return self.conts +end +Inventory.GetChildren = Inventory.GetContainers + +-- (container) returns container with specified id +function Inventory:GetContainer(id) + + return self.conts[id] + +end + +-- (int) returns amount of specified item in container or whole inventory +function Inventory:HasItem(item, contID) + + if istable(item) then + if contID then + return cont:HasItem(item) + else + for contID, cont in pairs(self.conts) do + local has = cont:HasItem(item) + if has then return has end + end + + return false + end + elseif isstring(item) then + local amount = 0 + if not contID then + for contID, cont in pairs(self.conts) do + amount = amount + (cont:HasItem(item) or 0) + end + else + local cont = self.conts[contID] + if not cont then return 0 end + amount = amount + (cont:HasItem(item) or 0) + end + + return amount + end + + return false + +end + +-- (table) returs table of items matching octolib.filterQuery +function Inventory:FindItems(...) + + local found = {} + for contID, cont in pairs(self.conts) do + table.Add(found, cont:FindItems(...)) + end + + return found + +end + +-- (item) returns first item matching octolib.filterQuery +function Inventory:FindItem(query) + + for _, cont in pairs(self.conts) do + local found = cont:FindItem(query) + if found then return found end + end + +end + +-- (float) returns free space in inventory +function Inventory:FreeSpace() + + local space = 0 + for _, cont in pairs(self.conts) do + space = space + cont:FreeSpace() + end + + return space + +end + +-- (float) returns all items mass +function Inventory:GetMass() + + local mass = 0 + for _, cont in pairs(self.conts) do + mass = mass + cont:GetMass() + end + + return mass + +end + +-- (nil) updates mass of owner +function Inventory:UpdateMass() + + if not IsValid(self.owner) then return end + + local phys = self.owner:GetPhysicsObject() + if not IsValid(phys) or not self.owner.baseMass then return end + + local mass = self.owner.baseMass + self:GetMass() * (self.owner.invMassMultiplier or 2) + phys:SetMass(mass) + phys:Wake() + + hook.Run('octoinv.massUpdated', self.owner, mass) + +end + +-- (int) returns maximum amount of items of defined type which container or whole invenory can take now +function Inventory:SpaceFor(class, data, contID) + + local canTake = 0 + if not contID then + for contID, cont in pairs(self.conts) do + canTake = canTake + cont:SpaceFor(class, data) + end + else + local cont = self.conts[contID] + if not cont then return 0 end + canTake = cont:SpaceFor(class, data) + end + + return canTake + +end + +-- (int) adds an item to container or inventory and returns added amount +function Inventory:AddItem(item, data) + + if istable(item) then + for k, v in pairs(self.conts) do + if cont:SpaceFor(item, data) > 0 then + return cont:AddItem(item, data) + end + end + elseif isstring(item) then + local itemTbl = octoinv.items[item] + if not itemTbl then return 0 end + + if itemTbl.nostack then + for contID, cont in pairs(self.conts) do + local given, item = cont:AddItem(item, data) + if given >= 1 then return given, item end + end + + return 0 + else + local amount = data or 1 + if amount <= 0 then return 0 end + + local givenTotal = 0 + for contID, cont in pairs(self.conts) do + local given = cont:AddItem(item, amount) + amount, givenTotal = amount - given, givenTotal + given + if amount <= 0 then break end + end + + return givenTotal + end + end + +end + +-- (int) takes an item to container or inventory and returns taken amount +function Inventory:TakeItem(item, data, contID) + + if istable(item) then + local _item, taken = self:HasItem(item, contID) + if _item then taken = _item:SetData('amount', _item:GetData('amount') - (data or 1)) end + return taken + elseif isstring(item) then + local has = self:HasItem(item, contID) + if has <= 0 then return 0 end + + local amount = data or has + amount = math.min(amount, has) + if amount <= 0 then return 0 end + + local takenTotal = 0 + if not contID then + for contID, cont in pairs(self.conts) do + local taken = cont:TakeItem(item, amount) + amount, takenTotal = amount - taken, takenTotal + taken + if amount <= 0 then break end + end + else + local cont = self.conts[contID] + if not cont then return 0 end + + local taken = cont:TakeItem(item, amount) + amount, takenTotal = amount - taken, takenTotal + taken + end + + return takenTotal + end + + return false + +end +Inventory.RemoveItem = Inventory.TakeItem + +-- (nil) removes all items from inventory +function Inventory:Clear() + for _,cont in pairs(self.conts) do + cont:Clear() + end +end + +-- (nil) performs actions on random items +function Inventory:PerformRandom(opts, func) + + opts = opts or {} + opts.maxItems = opts.maxItems or 5 + opts.maxVolume = opts.maxVolume or 20 + + local curItems, curVol = 0, 0 + while curItems < opts.maxItems and curVol < opts.maxVolume do + local contFrom = table.Random(self.conts) + if not contFrom then break end + local item = table.Random(contFrom.items) + if not item then break end + + local randomWeight = item:GetData('randomWeight') or 1 + local amount = math.floor(1 / randomWeight) + + local performed = func(item, amount) + if performed == -1 then break end + + curVol = curVol + item:GetData('volume') * performed + curItems = curItems + performed * randomWeight + end + +end + +-- (table) moves random items to container based on given criteria +function Inventory:MoveRandom(cont, opts) + + if not istable(cont) or not cont.inv then return end + + local movedData = {} + self:PerformRandom(opts, function(item, amount) + if cont:SpaceFor(item.class, item.nostack and item or nil) < amount then return -1 end + + local moved = item:MoveTo(cont, amount) or 0 + + local name = item:GetData('name') + movedData[name] = (movedData[name] or 0) + moved + + return moved + end) + + return movedData + +end + +-- (nil) adds a user to all containers of this inventory +function Inventory:AddUser(ply, conts) + + if conts then + for i, contID in pairs(conts) do + local cont = self.conts[contID] + if cont then cont:AddUser(ply) end + end + else + for contID, cont in pairs(self.conts) do + cont:AddUser(ply) + end + end + +end + +-- (nil) removes specific user from containers of this inventory +function Inventory:RemoveUser(ply, conts) + + if conts then + for i, contID in pairs(conts) do + local cont = self.conts[contID] + if cont then cont:RemoveUser(ply) end + end + else + for contID, cont in pairs(self.conts) do + cont:RemoveUser(ply) + end + end + +end + +-- (nil) removes all users from this inventory +function Inventory:RemoveUsers() + + for contID, cont in pairs(self.conts) do + for user, _ in pairs(cont.users) do + cont:RemoveUser(user) + end + end + +end + +-- (table) returns serializable table of this inventory +function Inventory:Export() + local inv = table.Copy(self.conts) + for contID, cont in pairs(inv) do + if string.sub(contID, 1, 1) == '_' then + inv[contID] = nil + continue + end + inv[contID] = cont:Export() + end + + return inv +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_item.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_item.lua new file mode 100644 index 0000000..6eb82d8 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/meta_item.lua @@ -0,0 +1,370 @@ +octoinv.ItemClass = octoinv.ItemClass or {} +local Item = octoinv.ItemClass +Item.__index = Item + +local function getOffsetZ(ent) + local a, b = ent:GetCollisionBounds() + return math.min(a.z, b.z) +end + +-- (item) creates and returns a new item +function octoinv.createItem(class, cont, data, force) + + if not class or not octoinv.items[class] or not cont then return end + local rAmount = istable(data) and data.amount or isnumber(data) and data or 1 + local amount = force and rAmount or math.min(cont:SpaceFor(class, data), rAmount) + if amount < 1 then return end + + local item = cont:FindItem({class = class}) + if item and not item:GetData('nostack') then + item:SetData('amount', item:GetData('amount') + amount) + hook.Run('octoinv.added', cont, item, amount) + else + local tbl = octoinv.items[class] + item = { + class = class, + classTbl = tbl, + cont = cont, + } + + if istable(data) then + for k, v in pairs(data) do + item[k] = v + end + end + + setmetatable(item, Item) + table.insert(cont.items, item) + hook.Run('octoinv.added', cont, item, 1) + end + + cont:QueueSync() + return item, amount + +end + +-- (nil) destroys the item +function Item:Remove(amount) + + local cont = self:GetParent() + if not cont then return end + + if amount then + cont:TakeItem(self.class, amount) + else + cont:TakeItem(self) + self.cont = nil + end + +end + +-- (bool) checks and removes item if is expired +function Item:CheckExpired(noRemove) + + local expire = self:GetData('expire') + if expire and expire <= os.time() then + if not noRemove then self:Remove() end + return true + end + + return false + +end + +-- (entity) drops an item and return created entity +function Item:Drop(amount, owner, pos, ang, vel) + + if self:CheckExpired() then return end + + local cont = self:GetParent() + if not cont or cont:Call('canDrop', self:GetParent(), self) == false then return end + + local owner = owner or cont:GetParent().owner + amount = amount or self:GetData('amount') + amount = math.max(math.ceil(amount), 1) + + local put = false + if owner:IsPlayer() then + local trace = owner:GetEyeTraceLimited(CFG.useDist) + put = trace.Hit and trace.HitNormal.z >= 0.85 + if put then + pos = trace.HitPos + ang = owner:EyeAngles() + ang.p = 0 + ang:RotateAroundAxis(vector_up, 180) + vel = Vector(0,0,0) + end + end + + local function drop(isPlayer) + local has = (self.nostack and cont:HasItem(self) or (cont:HasItem(self.class) >= amount)) and self:GetParent() == cont + if not has then return end + + local ent, dropped = NULL, 0 + local classTbl = self.classTbl + if classTbl.class then + if classTbl.drop then + ent, dropped = classTbl.drop(owner, self, amount, { + pos = pos, + ang = ang, + vel = vel, + put = put + }) + else + ent = ents.Create(classTbl.class) + ent.apgIgnore = true + ent.droppedBy = owner + ent:SetPos(pos) + ent:SetAngles(ang) + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + ent.Model = self:GetData('model') or ent.Model + + if classTbl.dropped then + classTbl.dropped(owner, ent, self) + end + + ent:Spawn() + ent:Activate() + if put then + pos:Sub(Vector(0,0,getOffsetZ(ent))) + ent:SetPos(pos) + end + if isPlayer then + owner:LinkEntity(ent) + end + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(vel) + end + + dropped = amount + end + else + ent = ents.Create 'octoinv_item' + ent.apgIgnore = true + ent.droppedBy = owner + ent:SetPos(pos) + ent:SetAngles(ang) + ent.Model = self:GetData('model') or ent.Model + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + + ent:Spawn() + ent:Activate() + if self:GetData('nodespawn') then + ent.dieTime = nil + end + if put then + pos:Sub(Vector(0,0,getOffsetZ(ent))) + ent:SetPos(pos) + end + if isPlayer then + owner:LinkEntity(ent) + end + + if classTbl.nostack then + local data = table.Copy(self) + data.class = nil + data.classTbl = nil + data.cont = nil + data.id = nil + ent:SetData(self.class, data) + else + ent:SetData(self.class, amount) + end + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(vel) + end + + dropped = amount + end + + if IsValid(ent) then + ent.pickupItemData = self:Export() + end + + cont = self:GetParent() + + hook.Run('octoinv.dropped', self.cont, self, ent, owner, dropped) + self:SetData('amount', self:GetData('amount') - dropped) + + if cont then cont:QueueSync() end + end + + if owner:IsPlayer() then + owner:DoAnimation(ACT_GMOD_GESTURE_ITEM_GIVE) + timer.Simple(0.88, function() + if not IsValid(owner) then return drop() end + if not pos or not ang then + pos, ang = owner:GetBonePosition(owner:LookupBone('ValveBiped.Bip01_L_Hand') or 16) + end + local tr = util.TraceLine { start = owner:GetShootPos(), endpos = pos, filter = owner } + if tr.Hit then + pos = tr.HitPos + tr.HitNormal * 5 + vel = tr.HitNormal * 200 + elseif not vel then + vel = owner:GetAimVector() + vel.z = 0 + vel = vel * 200 + VectorRand() * math.random(15,40) + end + + drop(true) + end) + else + pos = pos or (owner:GetPos() + Vector(0,0,30)) + ang = ang or owner:GetAngles() + vel = vel or (VectorRand() * math.random(20, 80)) + + drop() + end + +end + +-- (table) returns a table of available actions for a player +function Item:UseList(ply) + + if self:CheckExpired() then return end + + local uses = self:GetData('use') + if not istable(uses) then return false end + + local res = {} + for i, use in ipairs(uses) do + local name, icon = use(ply, self) + if name ~= nil then + table.insert(res, { i, name, icon }) + end + end + + return self:GetParent():Call('canSeeUseList', self:GetParent(), ply, self, res) == false and {} or res + +end + +-- (bool) returns whether an item was used and amount changed +function Item:Use(id, ply) + + local uses = self:GetData('use') + if not uses or not uses[id] then return false end + + local can, why = self:GetParent():Call('canUse', self:GetParent(), ply, self, id) + if can == false then + ply:Notify(why or 'Ты не можешь выполнить это действие с этим предметом') + return false + end + + local name, icon, action = uses[id](ply, self) + if not name then return false end + + local used = action(ply, self) + if used and used ~= 0 then + local cont = self:GetParent() + self:SetData('amount', self:GetData('amount') - used) + cont:QueueSync() + return true + end + + return false + +end + +-- (container) returns item's parent container +function Item:GetParent() + + return self.cont + +end +Item.GetContainer = Item.GetParent + +-- (item) sets specific item data and returns the item +function Item:SetData(field, val) + + local cont = self:GetParent() + if field == 'amount' then + if val <= 0 then + self:Remove() + else + local diff = val - self:GetData('amount') + if diff > 0 then + if cont then cont:AddItemStats(self, diff) end + elseif diff < 0 then + if cont then cont:TakeItemStats(self, -diff) end + end + end + elseif field == 'mass' then + local diff = val - self:GetData('mass') + if cont then cont.mass = cont.mass + diff * self:GetData('amount') end + elseif field == 'volume' then + local diff = val - self:GetData('volume') + if cont then cont.space = cont.space - diff * self:GetData('amount') end + elseif field == 'expire' then + if val <= os.time() then + self:Remove() + elseif cont then + cont:CreateExpireTimer() + end + end + + self[field] = val + if cont then cont:QueueSync() end + + return self + +end + +-- (any) returns specific item data (name, volume, etc) +function Item:GetData(field, skipDefault) + + if self[field] ~= nil then return self[field] end + if skipDefault then return end + + if self.classTbl[field] ~= nil then return self.classTbl[field] end + return octoinv.defaultData[field] + +end + +function Item:GetMass(amountOverride) + return self:GetData('mass') * (amountOverride or self:GetData('amount')) +end + +function Item:GetVolume(amountOverride) + return self:GetData('volume') * (amountOverride or self:GetData('amount')) +end + +-- (int) moves item to container, returns amount moved +function Item:MoveTo(cont, amount) + + if self:CheckExpired() or not self.cont then return end + + local prevCont = self:GetParent() + amount = isnumber(amount) and math.max(math.ceil(amount), 1) + if self:GetData('nostack') then + if not cont or cont:SpaceFor(self) < 1 then return 0 end + cont:AddItem(self) + return 1 + else + local given = cont:AddItem(self.class, math.min(amount, self:GetData('amount'))) + if prevCont then prevCont:TakeItem(self.class, given) end + return given + end + +end +Item.Move = Item.MoveTo + +-- (table) returns serializable table of this item +function Item:Export() + local data = table.Copy(self) + data.cont = nil + data.id = nil + local classTbl = data.classTbl + data.classTbl = nil + for k,v in pairs(data) do + if v == classTbl[k] then + data[k] = nil + end + end + return data +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_control.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_control.lua new file mode 100644 index 0000000..5fe41a0 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_control.lua @@ -0,0 +1,134 @@ +netstream.Hook('octoinv.move', function(ply, items, toEntID, toContID) + + local toEnt = Entity(toEntID) + if not IsValid(toEnt) then return end + local can, why = hook.Run('octoinv.canMove', ply, items, toEnt, toContID) + if can == false then + if why then ply:Notify(why) end + return + end + + local notifiedNoSpace = false + for fromEntID, conts in pairs(items) do + local fromEnt = Entity(fromEntID) + if not IsValid(fromEnt) then return end + + local fromInv = fromEnt:GetInventory() + local toInv = toEnt:GetInventory() + if not fromInv then return end + + if not toInv or (toContID and not toInv:GetContainer(toContID)) then return end + local contTo = toInv:GetContainer(toContID) + + for contID, contItems in pairs(conts) do + local contFrom = fromInv:GetContainer(contID) + if not contFrom then return end + + if not ply:HasAccessToContainer(fromEnt, contID) or not ply:HasAccessToContainer(toEnt, toContID) then continue end + + local queue = {} + for itemID, amount in pairs(contItems) do + local item = contFrom:GetItem(itemID) + + local outCan, outWhy = contFrom:Call('canMoveOut', ply, item, contTo) + if outCan == false then ply:Notify('warning', outWhy or L.notmove) continue end + local inCan, inWhy = contTo:Call('canMoveIn', ply, item, contFrom) + if inCan == false then ply:Notify('warning', inWhy or L.notmove) continue end + + if item then + table.insert(queue, {itemID, item, amount}) + else + contItems[itemID] = nil + end + end + for i = #queue, 1, -1 do + local itemID, item, amount = queue[i][1], queue[i][2], queue[i][3] + local moved = item:MoveTo(contTo, amount) or 1 + contItems[itemID] = contItems[itemID] - moved + if moved > 0 then + hook.Run('octoinv.plymoved', ply, item, contFrom, contTo, moved) + elseif not notifiedNoSpace then + ply:Notify('warning', L.notenoughspace) + notifiedNoSpace = true + end + if contItems[itemID] <= 0 then contItems[itemID] = nil end + end + if table.Count(items[fromEntID][contID]) <= 0 then items[fromEntID][contID] = nil end + if table.Count(items[fromEntID]) <= 0 then items[fromEntID] = nil end + end + end + + -- netstream.Start(ply, 'syncQueue', items) + +end) + +netstream.Hook('octoinv.drop', function(ply, owner, contID, itemID, amount) + + owner = Entity(owner) + if not IsValid(owner) then return end + + if not ply:HasAccessToContainer(owner, contID) then return end + local cont = owner:GetInventory():GetContainer(contID) + + local item = cont:GetItem(itemID) + if item then + local can, why = hook.Run('octoinv.canDrop', ply, item, amount, owner, contID) + if can == false then + if why then owner:Notify(why) end + return + end + item:Drop(amount, ply) + end + +end) + +netstream.Hook('octoinv.uselist', function(ply, owner, contID, itemID) + + + owner = Entity(owner) + if not IsValid(owner) then return end + + if not ply:HasAccessToContainer(owner, contID) then return end + local cont = owner:GetInventory():GetContainer(contID) + + local item = cont:GetItem(itemID) + if item then + netstream.Start(ply, 'octoinv.uselist', item:UseList(ply)) + else + netstream.Start(ply, 'octoinv.uselist', false) + end + +end) + +netstream.Hook('octoinv.use', function(ply, owner, contID, itemID, useID) + + owner = Entity(owner) + if not IsValid(owner) then return end + + if not ply:HasAccessToContainer(owner, contID) then return end + local cont = owner:GetInventory():GetContainer(contID) + + local item = cont:GetItem(itemID) + if item then + local can, why = hook.Run('octoinv.canUse', cont, item, ply) + if can == false then + ply:Notify(why or 'Ты не можешь использовать этот предмет') + return + end + item:Use(useID, ply) + hook.Run('octoinv.used', cont, item, ply) + end + +end) + +netstream.Hook('octoinv.toggle', function(ply, owner, contID) + + owner = Entity(owner) + if not IsValid(owner) then return end + + if not ply:HasAccessToContainer(owner, contID) then return end + local cont = owner:GetInventory():GetContainer(contID) + + cont:ToggleProd() + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_hooks.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_hooks.lua new file mode 100644 index 0000000..c90571a --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_hooks.lua @@ -0,0 +1,53 @@ +hook.Add('octolib.db.init', 'octoinv', function(db) + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS inventory ( + steamID VARCHAR(30) NOT NULL, + inv TEXT, + storage TEXT, + PRIMARY KEY (steamID) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + +end) + +hook.Add('PlayerDisconnected', 'octoinv', function(ply) + + if hook.Run('octoinv.overrideInventories') == false then return end + + local hand = ply.inv and ply.inv.conts._hand + if hand then + local ang = ply:EyeAngles() + hand:Remove(true, ply:EyePos(), ang, ang:Forward() * 200 + VectorRand() * 20) + end + + ply:SaveInventory() + + if ply.inv then + ply.inv:Remove() + end + +end) + +hook.Add('PlayerSpawn', 'octoinv', function(ply) + + if not ply.inv and ply.loadedInv then + ply:ImportInventory(octoinv.defaultInventory) + end + +end) + +hook.Add('PlayerFinishedLoading', 'octoinv', function(ply) + + ply:SyncOctoinv() + ply:LoadInventory() + +end) + +hook.Add('EntityRemoved', 'octoinv', function(ent) + + if ent.inv then + ent.inv:Remove() + end + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_loot.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_loot.lua new file mode 100644 index 0000000..8af5249 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_loot.lua @@ -0,0 +1,35 @@ +octoinv.loot = {} + +function octoinv.registerLoot(chance, data) + + local item = table.Copy(data) + item.chance = chance + if data.mode then + item.mode = istable(data.mode) and octolib.array.toKeys(data.mode) or {[data.mode] = true} + end + + octoinv.loot[#octoinv.loot + 1] = {chance, item} + return item + +end + +function octoinv.getRandomLoot(opts) + + local items = opts.mode + and octolib.array.filter(octoinv.loot, function(v) return not v[2].mode or v[2].mode[opts.mode] end) + or octoinv.loot + + return table.Copy(octolib.array.randomWeighted(items, opts.flatten)) + +end + +-- location can be either player, inventory or container +function octoinv.placeRandomLoot(location, opts) + + local loot = octoinv.getRandomLoot(opts) + local amount, item = location:AddItem(unpack(loot.item)) + if item and loot.given then loot.given(item) end + + return item, loot + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_map.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_map.lua new file mode 100644 index 0000000..f2dfbf4 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_map.lua @@ -0,0 +1,8 @@ +file.CreateDir('octoinv/configs') +octoinv.mapConfig = util.JSONToTable(file.Read('octoinv/configs/' .. game.GetMap() .. '.json') or '{}') or {} +octoinv.mapConfig.collect = octoinv.mapConfig.collect or {} + +function octoinv.saveMapConfig(data) + if data then octoinv.mapConfig = data end + file.Write('octoinv/configs/' .. game.GetMap() .. '.json', util.TableToJSON(octoinv.mapConfig)) +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_octoinv.lua b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_octoinv.lua new file mode 100644 index 0000000..2931087 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/core/sv_octoinv.lua @@ -0,0 +1,100 @@ +-- +-- INIT STUFF +-- + +octoinv.items = octoinv.items or {} +octoinv.itemClasses = octoinv.itemClasses or {} + +-- default data to be returned if it's missing from both class and item tables +octoinv.defaultData = { + amount = 1, + volume = 0, + mass = 0, + name = L.unknown, + icon = 'icon16/exclamation.png', + model = 'models/props_junk/garbage_bag001a.mdl', +} + +octoinv.defaultHand = { + name = L.inventory_hands, + volume = 100, + icon = octolib.icons.color('hand'), + craft = true, +} +-- default inventory setup for new players +octoinv.defaultInventory = { + top = { + name = L.jacket, + volume = 5, + icon = octolib.icons.color('clothes_jacket'), + items = {}, + }, + bottom = { + name = L.pants, + volume = 1, + icon = octolib.icons.color('clothes_jeans'), + items = {}, + }, + _hand = defaultHand, +} + +-- +-- UTIL +-- + +-- sync thingie (to fix multiple updates per frame) +local syncQueue = {} +function octoinv.syncCont(cont) syncQueue[cont] = true end +function octoinv.unsyncCont(cont) syncQueue[cont] = nil end +hook.Add('Think', 'octoinv.sync', function() + for cont, v in pairs(syncQueue) do + cont:Sync() + syncQueue[cont] = nil + end +end) + +function octoinv.registerItem(id, t) + + if not id then + octoinv.msg('ERROR: Cannot register item \'%s\' - no id!', t.name) + return + end + + t.name = t.name or id + t.mass = t.mass or 0 + t.volume = t.volume or 0 + octoinv.items[id] = t + + if t.class then + octoinv.itemClasses[t.class] = id + end + +end + +function octoinv.getItemData(field, class, data) + + local classTbl = octoinv.items[class] + if not classTbl then return end + + if field == 'amount' and isnumber(data) then return data end + return istable(data) and data[field] or classTbl[field] or octoinv.defaultData[field] or nil + +end + +function octoinv.itemStr(item) + + local amount = isnumber(item[2]) and item[2] or octoinv.getItemData('amount', item[1], item[2]) + return ('%sx%s'):format(amount, octoinv.getItemData('name', item[1], item[2])) + +end + +-- +-- LOAD RESOURCES +-- + +-- load meta +octolib.server('meta_item') +octolib.server('meta_cont') +octolib.server('meta_inv') +octolib.server('ext_entity') +octolib.server('ext_player') diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/craft/cl_preview.lua b/garrysmod/addons/core-octoinv/lua/octoinv/craft/cl_preview.lua new file mode 100644 index 0000000..caeef4e --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/craft/cl_preview.lua @@ -0,0 +1,53 @@ +local lastDir, lastAng, lastPos, woke + +local activatePreviewPhysics = octolib.func.debounce(function() + if not IsValid(octoinv.preview) then return end + local physObj = octoinv.preview:GetPhysicsObject() + if IsValid(physObj) then physObj:Wake() end +end, 0.3) + +netstream.Hook('octoinv.craftPreview', function(mdl, rotate) + if IsValid(octoinv.preview) then octoinv.preview:Remove() end + if mdl then + octoinv.preview = ents.CreateClientProp(mdl) + octoinv.preview:PhysicsInit(SOLID_VPHYSICS) + octoinv.preview:SetMoveType(MOVETYPE_VPHYSICS) + octoinv.preview:SetNotSolid(true) + octoinv.preview:SetRenderMode(RENDERMODE_TRANSCOLOR) + octoinv.preview:SetColor(Color(255, 255, 255, 150)) + octoinv.preview.rotate = rotate + lastDir, lastAng, lastPos, woke = nil + end +end) + +hook.Add('Think', 'octoinv.craftPreview', function() + + if not IsValid(octoinv.preview) then return end + local ply = LocalPlayer() + local physObj = octoinv.preview:GetPhysicsObject() + + local dir = ply:GetAimVector() + dir.z = 0 + dir:Normalize() + + local ang = ply:EyeAngles() + ang.p = 0 + ang.r = 0 + ang.y = ang.y + 180 - (octoinv.preview.rotate or 0) + + local pos = ply:GetShootPos() + dir * 25 + if lastDir == dir and lastAng == ang and lastPos == pos then + if not woke then + activatePreviewPhysics() + woke = true + end + return + end + lastDir, lastAng, lastPos, woke = dir, ang, Vector(pos.x, pos.y, pos.z), false + if IsValid(physObj) then physObj:Sleep() end + octoinv.preview:SetPos(pos) + octoinv.preview:SetAngles(ang) + pos = octoinv.preview:NearestPoint(pos + dir * 512) + octoinv.preview:SetPos(pos) + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/craft/sv_craft.lua b/garrysmod/addons/core-octoinv/lua/octoinv/craft/sv_craft.lua new file mode 100644 index 0000000..01988ed --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/craft/sv_craft.lua @@ -0,0 +1,235 @@ +octoinv.blueprints = octoinv.blueprints or {} +local blueprints = octoinv.blueprints + +function octoinv.registerCraft(id, data) + + if istable(data) then + if isstring(id) then + id = id + end + + if istable(data.jobs) then + data.jobs = octolib.array.toKeys(data.jobs) + end + + if istable(data.conts) then + data.conts = octolib.array.toKeys(data.conts) + end + + blueprints[id] = data + blueprints[id].id = id + end + +end + +function octoinv.getBlueprint(id) + + if id and isstring(id) and blueprints[id] then + return blueprints[id] or false + end + +end + +local function canCraft(ply, ent, bpID, cont) + + local ok, why = hook.Run('octoinv.canCraft', ply, ent, bpID, cont) + if ok ~= nil then return ok, why end + + local bp = octoinv.getBlueprint(bpID) + if not istable(bp) then return false, 'Craft id is invalid' end + + if istable(bp.jobs) and not bp.jobs[ply:getJobTable().command] then + return false, 'Your job doesn\'t have access to craft '.. bp.name + end + + if istable(bp.conts) and not bp.conts[cont.id] then + return false, 'Your container doesn\'t have access to craft '.. bp.name + end + + if istable(bp.tools) then + for k, v in ipairs(bp.tools) do + if cont:HasItem(v[1]) < v[2] then + return false, 'You don\'t have items to craft '.. bp.name + end + end + end + + if isfunction(bp.check) then + local ok, why = bp.check(ply, cont, ent) + if ok ~= nil then return ok, why end + end + + if istable(bp.ings) then + for k, v in ipairs(bp.ings) do + if cont:HasItem(v[1]) < v[2] then + return false, 'You don\'t have materials to craft '.. bp.name + end + end + end + + return true + +end + +local function craft(ply, ent, bpID, cont) + + local done = hook.Run('octoinv.craft', ply, ent, bpID, cont) + if done then return hook.Run('octoinv.crafted', ply, ent, bpID, cont) end + + local bp = octoinv.getBlueprint(bpID) + if bp and IsValid(ply) then + local can, data = canCraft(ply, ent, bpID, cont) + if not can then return end + + local name = bp.name + if istable(data) and data.name then name = data.name end + if istable(bp.finish) and not name then + local item = bp.finish[1] + name = octoinv.getItemData('name', unpack(item)) + end + + if isstring(bp.previewModel) then + netstream.Start(ply, 'octoinv.craftPreview', bp.previewModel, bp.previewRotation) + end + + local sndTemp = 0 + ply:DelayedAction('craft', L.create_in_progress:format(name), { + time = bp.time or 5, + check = function() + if not IsValid(ply) or ply:KeyDown(IN_SPEED) or not ply:Alive() then + ply:Notify('warning', L.create_failed:format(name)) + return false + end + + if not canCraft(ply, ent, bpID, cont) then + return false + end + + local contAccess = ply:HasAccessToContainer(ent, cont.id) + if not contAccess then + ply:Notify('warning', L.create_failed:format(name)) + return false + end + + return true + end, + succ = function() + netstream.Start(ply, 'octoinv.craftPreview') + if not canCraft(ply, ent, bpID, cont) then return end + + if isfunction(bp.finish) then + local ok, why = bp.finish(ply, cont) + if ok then + if bp.ings then + for i, v in ipairs(bp.ings) do + cont:TakeItem(v[1], v[2]) + end + end + ply:Notify(L.create_success:format(name)) + else + ply:Notify(why or L.create_failed:format(name)) + end + elseif istable(bp.finish) then + for i, v in ipairs(bp.ings) do + cont:TakeItem(v[1], v[2]) + end + + for i, v in ipairs(bp.finish) do + local amount, item = cont:AddItem(v[1], v[2]) + if not tobool(amount) then + cont:DropNewItem(v[1], v[2]) + end + end + + ply:Notify(L.create_success:format(name)) + end + + hook.Run('octoinv.crafted', ply, ent, bpID, cont) + end, + fail = function() + netstream.Start(ply, 'octoinv.craftPreview') + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + sndTemp = sndTemp + 1 + if sndTemp >= (bp.soundTime or 1) then + local sound = bp.sound and table.Random(bp.sound) or 'weapons/357/357_reload'.. math.random(1, 4) ..'.wav' + ply:EmitSound(sound, 50) + sndTemp = 0 + end + end, + }) + + return true + end + + return false, 'Craft '..bpID..' is invalid' + +end + +netstream.Hook('octoinv.craftlist', function(ply, ent, contID) + + ent = Entity(ent) + local contAccess, cont = ply:HasAccessToContainer(ent, contID) + + if not contAccess then return end + if not cont:CanCraft() then return end + + local available = {} + for id, v in pairs(blueprints) do + local can, data = canCraft(ply, ent, id, cont) + if can then + local opt = { + name = v.name, + desc = v.desc, + icon = v.icon, + } + + if istable(data) then + if data.name then opt.name = data.name end + if data.desc then opt.desc = data.desc end + if data.icon then opt.icon = data.icon end + end + + if isfunction(v.finish) then + available[id] = opt + elseif istable(v.finish) then + local itemData = octoinv.items[v.finish[1][1]] + if istable(itemData) then + if itemData.name then opt.name = opt.name or itemData.name end + if itemData.desc then opt.desc = opt.desc or itemData.desc end + if itemData.icon then opt.icon = opt.icon or itemData.icon end + end + + available[id] = opt + end + end + end + + netstream.Start(ply, 'octoinv.craftlist', available) + +end) + +netstream.Hook('octoinv.craft', function(ply, ent, contID, bpID) + + if not ent then return end + ent = Entity(ent) + local contAccess, cont = ply:HasAccessToContainer(ent, contID) + + if not contAccess then return end + if not cont:CanCraft() then return end + + local bp = octoinv.getBlueprint(bpID) + if bp then + local can, data = canCraft(ply, ent, bp.id, cont) + if can then + craft(ply, ent, bp.id, cont) + elseif data then + ply:Notify(data) + end + end + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/craft/sv_prod.lua b/garrysmod/addons/core-octoinv/lua/octoinv/craft/sv_prod.lua new file mode 100644 index 0000000..5774f64 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/craft/sv_prod.lua @@ -0,0 +1,99 @@ +octoinv.prod = octoinv.prod or {} + +function octoinv.registerProd(id, t) + + t.prod = {} + octoinv.prod[id] = t + +end + +function octoinv.registerProcess(prodID, t, processID) + + if not octoinv.prod[prodID] then + octoinv.msg('ERROR: Trying to register process for inexistant prod \'%s\'', prodID) + return + end + + if not t.time or not t.ins or not t.out then + octoinv.msg('ERROR: Trying to register process with incomplete data for \'%s\'', prodID) + return + end + + local processes = octoinv.prod[prodID].prod + processes[processID or (#processes + 1)] = t + +end + +function octoinv.spawnCont(mdl, conts, data) + return function(ply, cont) + local ent = ents.Create 'octoinv_cont' + ent.dt = ent.dt or {} + ent.dt.owning_ent = ply + ent.Model = mdl + ent.Containers = conts + + if istable(data) then + ent.Mass = data.mass + ent.DestructParts = data.destruct + ent.DestroyParts = data.destroy + ent.Explode = data.explode + ent.Health = data.health + end + + ent.SID = ply.SID + ent:Spawn() + + ply:BringEntity(ent) + ent:SetPlayer(ply) + ent:SetLocked(false) + timer.Simple(3, function() + if IsValid(ent) and IsValid(ply) then + APG.entUnGhost(ent, ply) + end + end) + return true + end +end + +function octoinv.spawnProd(data) + if not istable(data) then return octolib.func.zero end + return function(ply, cont) + if isfunction(data.check) then + local ok, why = data.check(ply, cont) + if ok == false then + return false, why + end + end + + local ent = ents.Create 'octoinv_prod' + ent.dt = ent.dt or {} + ent.dt.owning_ent = ply + ent.Model = data.mdl + ent.Containers = data.conts + + if isstring(data.prod) then + ent.prodClass = data.prod + data.prod = octoinv.prod[data.prod] + end + ent:SetProdData(data.prod) + + if istable(data.other) then + for k,v in pairs(data.other) do + ent[k] = v + end + end + + ent.SID = ply.SID + ent:Spawn() + + ply:BringEntity(ent, data.rotate) + ent:SetPlayer(ply) + ent:SetLocked(false) + timer.Simple(3, function() + if IsValid(ent) and IsValid(ply) then + APG.entUnGhost(ent, ply) + end + end) + return true + end +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/cl_market.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/cl_market.lua new file mode 100644 index 0000000..761c1be --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/cl_market.lua @@ -0,0 +1,593 @@ +octoinv.marketSummary = octoinv.marketSummary or {} + +local timeRules = { + {1, 'секунда', 'секунды', 'секунд'}, + {60, 'минута', 'минуты', 'минут'}, + {60 * 60, 'час', 'часа', 'часов'}, +} + +local function handleReply(err) + if err then + Derma_Message(err, 'Ошибка', 'Понятно') + end +end + +local PANEL = {} + +function PANEL:Init() + + self:Dock(FILL) + self:SetPaintBackground(false) + self:SetVisible(false) + + local treeCont = self:Add 'DPanel' + treeCont:Dock(LEFT) + treeCont:SetWide(363) + treeCont:SetPaintBackground(false) + + local tree = treeCont:Add 'DTree' + tree:Dock(FILL) + tree.OnNodeSelected = function(tree, node) + self:OpenItem(node.itemID) + end + self.tree = tree + + local upperCont = treeCont:Add 'DPanel' + upperCont:SetPaintBackground(false) + upperCont:Dock(TOP) + upperCont:SetTall(25) + upperCont:DockMargin(0,0,0,5) + + local srch = upperCont:Add 'DTextEntry' + srch:DockMargin(5,5,5,5) + srch:Dock(FILL) + srch:SetTooltip(L.search_or_filter) + srch:SetUpdateOnType(true) + srch.PaintOffset = 5 + srch:SetPlaceholderText(L.search_hint) + srch.OnValueChange = function(srch, val) + self:FilterItems(val) + end + + local butMy = upperCont:Add 'DButton' + butMy:Dock(RIGHT) + butMy:DockMargin(5,0,0,0) + butMy:SetText('Мои заявки') + butMy:SizeToContentsX(20) + butMy.DoClick = function() + self:OpenMyOrders() + end + self.butMy = butMy + + local form = self:Add 'DPanel' + form:Dock(BOTTOM) + form:DockMargin(5, 0, 0, 0) + form:SetTall(0) + self.form = form + + local view = self:Add 'DPanel' + view:Dock(FILL) + view:DockMargin(5, 0, 0, 0) + view:SetPaintBackground(false) + self.view = view + + self.nodes = {} + self:BuildTree() + +end + +function PANEL:Think() + + if self.pendingUpdate then + self.pendingUpdate = nil + self:Update() + end + +end + +function PANEL:OpenMyOrders() + + local p = octolib.overlay(self:GetParent(), 'DPanel') + p:SetSize(400, 300) + p:DockPadding(5, 5, 5, 5) + + local l = p:Add 'DListView' + l:Dock(FILL) + l:AddColumn('Тип'):SetFixedWidth(60) + l:AddColumn('Предмет'):SetFixedWidth(150) + l:AddColumn('К-во'):SetFixedWidth(35) + l:AddColumn('Цена') + l:AddColumn('Осталось') + l:AddLine('Загрузка...') + + local function rebuildMyOrders() + netstream.Request('octoinv.myOrders'):Then(function(orders) + l:Clear() + + for _, order in ipairs(orders) do + local line = l:AddLine( + order.type == octoinv.ORDER_BUY and 'Покупка' or 'Продажа', + octoinv.getItemData(order.data or { class = order.item }, 'name') or 'Неизвестно', + order.amount or 1, + DarkRP.formatMoney(order.price or 0), + octolib.string.formatCountExt(math.max(order.expire - os.time(), 0), timeRules) + ) + line.order = order + end + + function l:Think() + if CurTime() < (self.nextThink or 0) then return end + self.nextThink = CurTime() + 1 + + for _, line in ipairs(self:GetLines()) do + if line.order then + line:SetValue(5, octolib.string.formatCountExt(math.max(line.order.expire - os.time(), 0), timeRules)) + end + end + end + + end) + end + rebuildMyOrders() + + function l:OnRowRightClick(_, line) + if not line.order then return end + octolib.menu({ + {'Продлить', 'icon16/arrow_refresh.png', function() + netstream.Request('octoinv.editOrder', line.order.mode, line.order.id, 'renew') + :Then(handleReply) + :Finally(rebuildMyOrders) + end}, + {'Отменить', 'icon16/delete.png', function() + netstream.Request('octoinv.editOrder', line.order.mode, line.order.id, 'cancel') + :Then(handleReply) + :Finally(rebuildMyOrders) + end}, + }):Open() + end + +end + +function PANEL:BuildTree() + + local tr = self.tree + + local items = octolib.table.toKeyVal(octoinv.marketItems) + table.sort(items, function(a, b) return octoinv.marketItemName(a[1]) < octoinv.marketItemName(b[1]) end) + + for _, itemData in ipairs(items) do + local itemID, item = unpack(itemData) + + local node = tr:AddNode(octoinv.marketItemName(itemID), octoinv.marketItemIcon(itemID)) + local info = node.Label:Add 'DLabel' + info:Dock(FILL) + info:DockMargin(0, 0, 5, 0) + info:SetContentAlignment(6) + info:SetText('загрузка...') + info:SetAlpha(110) + node.info = info + + item.node = node + node.itemID = itemID + self.nodes[itemID] = node + end + + for _, itemData in ipairs(items) do + local itemID = itemData[1] + local node = self.nodes[itemID] + + local parentID = octoinv.marketItems[itemID].parent + local parentNode = octoinv.marketItems[parentID] and octoinv.marketItems[parentID].node + if IsValid(parentNode) then + parentNode:InsertNode(node) + node:SetDrawLines(true) + end + end + + netstream.Start('octoinv.marketSummary') + +end + +local function okVal(val) + return val ~= 0 and val ~= math.huge +end + +local function recalcRecursive(node) + if node.totalSell then + return node.minSell, node.maxBuy, node.totalSell, node.totalBuy + end + + local itemID = node.itemID + local iData = itemID and octoinv.marketItems[itemID] + if not iData then return end + + local cache = octoinv.marketSummary[itemID] or {} + local minSell, maxBuy, totalSell, totalBuy = + cache.minSell or math.huge, + cache.maxBuy or 0, + cache.totalSell or 0, + cache.totalBuy or 0 + + if IsValid(node.ChildNodes) then + for _, child in ipairs(node.ChildNodes:GetChildren()) do + local _minSell, _maxBuy, _totalSell, _totalBuy = recalcRecursive(child, child.itemID) + minSell = math.min(minSell, _minSell or math.huge) + maxBuy = math.max(maxBuy, _maxBuy or 0) + totalSell = totalSell + (_totalSell or 0) + totalBuy = totalBuy + (_totalBuy or 0) + end + end + + if iData.nostack then + local info = {} + if okVal(totalSell) or okVal(minSell) then + info[#info + 1] = totalSell .. ' от ' .. DarkRP.formatMoney(minSell) + end + node.info:SetText(#info > 0 and table.concat(info, ', ') or '—') + + node.minSell, node.totalSell = minSell, totalSell + else + local info = {} + if okVal(totalSell) or okVal(minSell) then + info[#info + 1] = totalSell .. ' от ' .. DarkRP.formatMoney(minSell) + end + -- if okVal(totalBuy) or okVal(maxBuy) then + -- info[#info + 1] = totalBuy .. ' до ' .. DarkRP.formatMoney(maxBuy) + -- end + node.info:SetText(#info > 0 and table.concat(info, ', ') or '—') + + node.minSell, node.maxBuy, node.totalSell, node.totalBuy = minSell, maxBuy, totalSell, totalBuy + end + + return minSell, maxBuy, totalSell, totalBuy + +end + +function PANEL:Update() + + for _, node in pairs(self.nodes) do + node.minSell, node.totalSell, node.maxBuy, node.totalBuy = nil + end + + for _, node in pairs(self.nodes) do + recalcRecursive(node) + end + +end + +local function recursiveShow(node) + node:SetVisible(true) + if node.SetExpanded then node:SetExpanded(true, true) end + + local parent = node:GetParent() + if parent.ClassName == 'DTree' then return end + + recursiveShow(parent) +end + +function PANEL:FilterItems(val) + + if val == '' then + -- show all + for _, node in pairs(self.nodes) do + node:SetVisible(true) + if node.SetExpanded then node:SetExpanded(false, true) end + end + else + -- hide all + for _, node in pairs(self.nodes) do + node:SetVisible(false) + end + + -- show matched + for itemID, node in pairs(self.nodes) do + if utf8.lower(octoinv.marketItemName(itemID)):find(utf8.lower(val), 1, true) then + recursiveShow(node) + end + end + end + + self.tree:InvalidateLayout() + +end + +function PANEL:OpenItem(itemID) + + local iData = octoinv.marketItems[itemID or ''] + if not iData or (iData.children and not iData.nostack) then return end + + local node = iData.node + if not IsValid(node) then return end + + local view = self.view + local form = self.form + view:Clear() + + local icon = view:Add 'DImage' + icon:SetSize(64, 64) + icon:SetImage('octoteam/icons/clock.png') + icon:Center() + function icon:Think() + self:SetAlpha(octolib.math.remap(math.sin(CurTime() * 5), -1, 1, 50, 255)) + end + + if iData.nostack then + netstream.Hook('octoinv.listNoStackOrders', function(_itemID, data) + if not IsValid(self) or _itemID ~= itemID then return end + view:Clear() + form:Clear() + form:SetTall(0) + form.lastItemID = itemID + + local cont = view:Add 'DScrollPanel' + cont:Dock(FILL) + cont:DockMargin(5, 0, 0, 0) + cont.selected = {} + + local title = octolib.label(cont, 'Продают') + title:SetTall(30) + title:SetContentAlignment(5) + title:SetFont('octoinv.subtitle') + local totalSell = node.totalSell or 0 + local info = octolib.label(cont, totalSell .. ' предложени' .. octolib.string.formatCount(totalSell, 'е', 'я', 'й')) + info:SetContentAlignment(5) + + local icons = cont:Add 'DIconLayout' + icons:Dock(FILL) + icons:DockMargin(12, 5, 0, 0) + icons:SetSpaceX(4) + icons:SetSpaceY(6) + + if not iData.children then + local sell = octolib.button(cont, 'Продать', function() + netstream.Request('octoinv.createNoStackOrder', itemID):Then(handleReply) + end) + sell:SetTall(30) + sell:DockMargin(0, 5, 0, 5) + else + local l = octolib.label(cont, 'Для продажи выбери отдельный предмет слева') + l:DockMargin(0, 5, 0, 5) + l:SetContentAlignment(5) + end + + local buy = octolib.button(form, 'Купить', function(self) + for _, id in ipairs(table.GetKeys(cont.selected)) do + netstream.Request('octoinv.buyNoStackOrder', id):Then(handleReply) + cont.selected[id] = nil + end + self:Update() + end) + buy:Dock(FILL) + buy:SetTall(0) + function buy:Update() + form:SizeTo(form:GetWide(), #table.GetKeys(cont.selected) > 0 and 30 or 0, 0.2, 0, -1) + end + + local function clickItem(pnl) + if IsValid(pnl.selectedIcon) then + pnl.selectedIcon:Remove() + cont.selected[pnl.orderID] = nil + buy:Update() + else + local icon = pnl:Add 'DImage' + icon:SetMouseInputEnabled(false) + icon:SetImage('icon16/tick.png') + icon:SetSize(16, 16) + icon:AlignLeft(4) + icon:AlignTop(2) + pnl.selectedIcon = icon + cont.selected[pnl.orderID] = true + buy:Update() + end + end + + for _, order in ipairs(data) do + local p = icons:Add 'DPanel' + p:SetPaintBackground(false) + p:SetSize(64, 80) + + local i = octoinv.createItemPanel(p, order.data) + i.orderID = order.id + i.DoClick = clickItem + + local l = p:Add 'DLabel' + l:Dock(BOTTOM) + l:SetTall(16) + l:SetContentAlignment(5) + l:SetTextColor(color_white) + l:SetText(DarkRP.formatMoney(order.price)) + end + + if #data >= 50 then + local l = icons:Add 'DLabel' + l:SetSize(340, 30) + l:SetContentAlignment(5) + l:SetText('Показаны только первые 50 предложений') + l.OwnLine = true + end + end) + netstream.Start('octoinv.listNoStackOrders', itemID) + else + netstream.Hook('octoinv.getStackOrdersDOM', function(_itemID, data) + if not IsValid(self) or _itemID ~= itemID then return end + view:Clear() + + if form.lastItemID ~= itemID then + form:Clear() + form:SetTall(30) + + local orderType = octolib.comboBox(form, nil, { + {'Хочу продать', octoinv.ORDER_SELL, true}, + {'Хочу купить', octoinv.ORDER_BUY}, + }) + orderType:Dock(LEFT) + orderType:DockMargin(3,3,3,3) + orderType:SetWide(100) + form.orderType = orderType + + local amount = octolib.textEntry(form) + amount:Dock(LEFT) + amount:DockMargin(5,8,8,8) + amount:SetWide(50) + amount:SetPlaceholderText('Кол-во') + amount:SetNumeric(true) + amount:SetDrawLanguageID(false) + amount.PaintOffset = 5 + form.amount = amount + + local l1 = octolib.label(form, 'шт по') + l1:Dock(LEFT) + l1:DockMargin(0,5,3,5) + l1:SetWide(30) + l1:SetContentAlignment(5) + + local price = octolib.textEntry(form) + price:Dock(LEFT) + price:DockMargin(5,8,8,8) + price:SetWide(65) + price:SetPlaceholderText('Цена') + price:SetNumeric(true) + price:SetDrawLanguageID(false) + price.PaintOffset = 5 + form.price = price + + local l2 = octolib.label(form, 'Р') + l2:Dock(LEFT) + l2:SetWide(8) + l2:DockMargin(0,3,10,3) + l2:SetContentAlignment(5) + + local place = octolib.button(form, 'Отправить', function() + local _, t = orderType:GetSelected() + local price, amount = tonumber(price:GetValue()), tonumber(amount:GetValue()) + if not price or not amount then return end + + if t == octoinv.ORDER_SELL then + local fee = math.ceil(CFG.getMarketFee(LocalPlayer()) * price * amount) + if fee < 0 then return end + Derma_Query('Комиссия за создание лота составит ' .. DarkRP.formatMoney(fee) .. '\nПри своевременном обновлении она не списывается вновь. Продолжить?', 'Продажа предмета', 'Отправить', function() + netstream.Request('octoinv.createStackOrder', t, itemID, price, amount):Then(handleReply) + end, 'Отмена') + else + if price * amount < 0 then return end + netstream.Request('octoinv.createStackOrder', t, itemID, price, amount):Then(handleReply) + end + end) + place:Dock(FILL) + place:DockMargin(0, 3, 3, 3) + + form.lastItemID = itemID + end + + local contSell = view:Add 'DPanel' + contSell:Dock(LEFT) + contSell:SetWide(view:GetWide() / 2 - 2) + contSell:SetPaintBackground(false) + + local titleSell = octolib.label(contSell, 'Продают') + titleSell:SetTall(30) + titleSell:SetContentAlignment(5) + titleSell:SetFont('octoinv.subtitle') + local totalSell = node.totalSell or 0 + local infoSell = octolib.label(contSell, totalSell .. ' предложени' .. octolib.string.formatCount(totalSell, 'е', 'я', 'й')) + infoSell:SetContentAlignment(5) + + local listSell = contSell:Add 'DListView' + listSell:Dock(FILL) + listSell:DockMargin(0, 5, 0, 5) + listSell:SetHideHeaders(true) + listSell:AddColumn('Количество') + listSell:AddColumn('Цена') + + local contBuy = view:Add 'DPanel' + contBuy:Dock(FILL) + contBuy:DockMargin(4, 0, 0, 0) + contBuy:SetPaintBackground(false) + + local titleBuy = octolib.label(contBuy, 'Покупают') + titleBuy:SetTall(30) + titleBuy:SetContentAlignment(5) + titleBuy:SetFont('octoinv.subtitle') + titleBuy:SetText('Покупают') + local totalBuy = node.totalBuy or 0 + local infoBuy = octolib.label(contBuy, totalBuy .. ' предложени' .. octolib.string.formatCount(totalBuy, 'е', 'я', 'й')) + infoBuy:SetContentAlignment(5) + + local listBuy = contBuy:Add 'DListView' + listBuy:Dock(FILL) + listBuy:DockMargin(0, 5, 0, 5) + listBuy:SetHideHeaders(true) + listBuy:AddColumn('Количество') + listBuy:AddColumn('Цена') + + function listSell:OnRowSelected(i, line) + listBuy:ClearSelection() + form.orderType:ChooseOptionID(2) + form.amount:SetValue(line.amount) + form.price:SetValue(line.price) + end + + function listBuy:OnRowSelected(i, line) + listSell:ClearSelection() + form.orderType:ChooseOptionID(1) + form.amount:SetValue(line.amount) + form.price:SetValue(line.price) + end + + for _, order in ipairs(octolib.array.filter(table.Reverse(data), function(v) return v[1] == 0 end)) do + local _, price, amount = unpack(order) + local line = listSell:AddLine(amount .. ' шт', 'по ' .. DarkRP.formatMoney(price)) + line.price, line.amount = price, amount + line.Columns[1]:SetContentAlignment(6) + end + + for _, order in ipairs(octolib.array.filter(data, function(v) return v[1] == 1 end)) do + local _, price, amount = unpack(order) + local line = listBuy:AddLine(amount .. ' шт', 'по ' .. DarkRP.formatMoney(price)) + line.price, line.amount = price, amount + line.Columns[1]:SetContentAlignment(6) + end + end) + netstream.Start('octoinv.getStackOrdersDOM', itemID) + end + +end + +vgui.Register('octoinv_market', PANEL, 'DPanel') + +hook.Add('octogui.f4-tabs', 'octoinv.market', function() + + octogui.addToF4({ + id = 'market', + name = L.market, + order = 12.2, + icon = Material('octoteam/icons/chart_lines.png'), + build = function(f) + if not IsValid(octoinv.pnlMarket) then + octoinv.pnlMarket = vgui.Create 'octoinv_market' + end + f:SetSize(750, 550) + f:Add(octoinv.pnlMarket) + octoinv.pnlMarket:SetVisible(true) + end, + show = function(f, st) + if st then octoinv.pnlMarket:Update() end + octoinv.pnlMarket:SetVisible(st) + end, + }) + +end) + +netstream.Hook('octoinv.marketSummary', function(itemID, data) + + if itemID then + octoinv.marketSummary[itemID] = data + else + octoinv.marketSummary = data + end + + if IsValid(octoinv.pnlMarket) then + octoinv.pnlMarket.pendingUpdate = true + end + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/sh_market.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/sh_market.lua new file mode 100644 index 0000000..c887cb8 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/sh_market.lua @@ -0,0 +1,104 @@ +function CFG.getMarketOrderLifeTime(ply) + return (IsValid(ply) and ply:GetNetVar('os_trader') and 7 or 3) * 24 * 60 * 60 +end + +function CFG.getMarketFee(ply) + return IsValid(ply) and ply:GetNetVar('os_trader') and 0.05 or 0.1 +end + +function CFG.getMarketMaxOrders(ply) + return IsValid(ply) and ply:GetNetVar('os_trader') and 20 or 10 +end + +octoinv.marketItems = octoinv.marketItems or {} + +octoinv.ORDER_SELL = 0 +octoinv.ORDER_BUY = 1 + +local recalcChildren = octolib.func.debounce(function() + for itemID, item in pairs(octoinv.marketItems) do + local parentItem = octoinv.marketItems[item.parent or ''] + if parentItem then + parentItem.children = parentItem.children or {} + parentItem.children[#parentItem.children + 1] = itemID + end + end +end, 0) + +function octoinv.registerMarketItem(id, data) + + if not isstring(id) or not istable(data) then return end + + octoinv.marketItems[id] = data + + if octoinv.marketRetainItems then + octoinv.marketRetainItems[id] = data.retain + end + + recalcChildren() + +end + +function octoinv.registerMarketCatFromShop(id, overrides) + + local cat = octoinv.shopCats[id or ''] + if not cat then return end + + local catID = 'cat_' .. id + local data = overrides and table.Copy(overrides) or {} + data.name = cat.name + data.icon = cat.icon + octoinv.registerMarketItem(catID, data) + + for itemID, item in pairs(octoinv.shopItems) do + if item.cat ~= id or item.data then continue end + + local itemData = octoinv.items[itemID] + if not itemData then continue end + + octoinv.registerMarketItem(itemID, { + parent = catID, + name = item.name, + icon = item.icon, + nostack = itemData.nostack, + }) + end + +end + +function octoinv.marketItemName(id, data) + + local iData = octoinv.marketItems[id] + if not iData then return 'Предмет' end + if isstring(iData.name) then return iData.name end + if isfunction(iData.name) then return iData.name(data) end + + if octoinv.items[id] then + return SERVER and octoinv.getItemData('name', id) or octoinv.getItemData({ class = id }, 'name') + end + + return iData.parent and octoinv.marketItemName(iData.parent, data) or 'Предмет' + +end + +function octoinv.marketItemIcon(id, data) + + local iData = octoinv.marketItems[id] + if not iData then return 'octoteam/icons/missingno.png' end + if isstring(iData.icon) then return iData.icon end + if isfunction(iData.icon) then return iData.icon(data) end + + if octoinv.items[id] then + if SERVER then + return octoinv.getItemData('icon', id) + else + local icon = octoinv.getItemData({ class = id }, 'icon') + if not isstring(icon) then icon = icon:GetName() .. '.png' end + return icon + end + end + + local icon = iData.parent and octoinv.marketItemIcon(iData.parent, data) or 'octoteam/icons/missingno.png' + return icon + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_control.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_control.lua new file mode 100644 index 0000000..2942aba --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_control.lua @@ -0,0 +1,355 @@ +octoinv.marketSubs = octoinv.marketSubs or {} + +local function recursiveSet(itemID, ply, val) + local iData = octoinv.marketItems[itemID] + if not iData then return end + + if val then + octoinv.marketSubs[itemID] = octoinv.marketSubs[itemID] or {} + table.insert(octoinv.marketSubs[itemID], ply) + elseif octoinv.marketSubs[itemID] then + table.RemoveByValue(octoinv.marketSubs[itemID], ply) + if #octoinv.marketSubs[itemID] < 1 then octoinv.marketSubs[itemID] = nil end + end + + if iData.children then + for _, itemID in ipairs(iData.children) do + recursiveSet(itemID, ply, val) + end + end +end + +local function subscribe(ply, itemID) + local old = ply.marketSub + if old then + if old == itemID then return end + recursiveSet(old, ply, false) + end + + recursiveSet(itemID, ply, true) + ply.marketSub = itemID +end + +hook.Add('PlayerDisconnected', 'octoinv.marketSub', function(ply) + for _, plys in pairs(octoinv.marketSubs) do + table.RemoveByValue(plys, ply) + end +end) + +local function checkCanTrade(ply, ignoreLimit) + return util.Promise(function(res, rej) + -- if not ply:Alive() or ply:IsGhost() then + -- return rej('Мертвые не могут торговать на рынке') + -- end + + if not ply:HasMobilePhone() then + return rej('Для этого нужен мобильный телефон') + end + + for _, notif in ipairs(ply:GetDBVar('notifs') or {}) do + if notif[1] == 'market' then + return rej('Нужно закрыть все уведомления с рынка') + end + end + + if not ignoreLimit then + octoinv.getPlayerOrders(ply):Then(function(orders) + if #orders >= CFG.getMarketMaxOrders(ply) then + return rej('Достигнут лимит активных заявок') + end + + res() + end) + else + res() + end + end) +end + +local function checkCanTradeForChain(reply, ply, ignoreLimit) + return function(done, ...) + local args = {...} + checkCanTrade(ply, ignoreLimit) + :Then(function() done(unpack(args)) end) + :Catch(function(msg) reply(msg) end) + end +end + +-- +-- COMMON +-- + +netstream.Hook('octoinv.marketSummary', function(ply) + if not ply:TriggerCooldown('marketSummary', 10) then return 'Слишком часто' end + netstream.Start(ply, 'octoinv.marketSummary', nil, octoinv.marketSummary) +end) + +netstream.Listen('octoinv.myOrders', function(reply, ply) + if not ply:TriggerCooldown('marketMyOrders', 1) then return 'Слишком часто' end + octoinv.getPlayerOrders(ply):Then(reply) +end) + +netstream.Listen('octoinv.editOrder', function(reply, ply, mode, id, action) + if not ply:TriggerCooldown('editOrder', 0.5) then return 'Слишком часто' end + + octolib.func.chain({ + checkCanTradeForChain(reply, ply, true), + function() + local sID = ply:SteamID() + local lifeTime = CFG.getMarketOrderLifeTime(ply) + if mode == 'stack' then + octoinv.getStackOrder(id):Then(function(order) + if order.steamID == sID then + if action == 'cancel' then + octoinv.deleteStackOrder(id) + octoinv.refundStackOrder(order) + elseif action == 'renew' then + if order.expire < os.time() + 24 * 60 * 60 then + octolib.db:PrepareQuery('update octoinv_orders_stack set expire = ? where id = ?', { os.time() + lifeTime, id }) + else + return reply('Продлевать можно только заказы, у которых осталось менее 24 часов') + end + end + end + reply() + end) + elseif mode == 'nostack' then + octoinv.getNoStackOrder(id):Then(function(order) + if order.steamID == sID then + if action == 'cancel' then + octoinv.deleteNoStackOrder(id) + octoinv.refundNoStackOrder(order) + elseif action == 'renew' then + if order.expire < os.time() + 24 * 60 * 60 then + octolib.db:PrepareQuery('update octoinv_orders_nostack set expire = ? where id = ?', { os.time() + lifeTime, id }) + else + return reply('Продлевать можно только заказы, у которых осталось менее 24 часов') + end + end + end + reply() + end) + end + end, + }) +end) + +-- +-- STACK ORDERS +-- + +netstream.Hook('octoinv.getStackOrdersDOM', function(ply, itemID) + if not ply:TriggerCooldown('marketItem', 0.5) then return 'Слишком часто' end + subscribe(ply, itemID) + octoinv.getStackOrdersDOM(itemID):Then(function(data) + netstream.Start(ply, 'octoinv.getStackOrdersDOM', itemID, data) + end) +end) + +netstream.Listen('octoinv.createStackOrder', function(reply, ply, type, itemID, price, amount) + local iData = octoinv.marketItems[itemID] + if not iData then return reply('Тип предмета не найден в базе данных') end + if not isnumber(price) or price <= 0 or not isnumber(amount) or amount <= 0 then return reply('Некорректные параметры') end + + if type == octoinv.ORDER_SELL and iData.canSell then + local can, why = iData.canSell(ply, price, amount) + if not can then return reply(why or 'Ты не можешь продать этот товар') end + elseif type == octoinv.ORDER_BUY and iData.canBuy then + local can, why = iData.canBuy(ply, price, amount) + if not can then return reply(why or 'Ты не можешь купить этот товар') end + end + + local sendCont + octolib.func.chain({ + checkCanTradeForChain(reply, ply), + function(done) + if type == octoinv.ORDER_SELL then + local fee = math.ceil(CFG.getMarketFee(ply) * price * amount) + if not ply:BankHas(fee) then + return reply('На счете в банке должно быть ' .. DarkRP.formatMoney(fee) .. ' для оплаты комиссии') + end + + local box, distSqr = octoinv.getNearestMailbox(ply) + if distSqr > 10000 then return reply('Для продажи надо доставить подходящие предметы в любой почтовый ящик') end + + sendCont = box.inv.conts.send + if sendCont:HasItem(itemID) < amount then return reply('В почтовом ящике нет достаточного количества подходящих предметов') end + + sendCont:TakeItem(itemID, amount) + ply:BankAdd(-fee) + done() + elseif type == octoinv.ORDER_BUY then + if not ply:BankHas(price * amount) then + return reply('На твоем счете недостаточно средств') + end + ply:BankAdd(-price * amount) + done() + end + end, + function(done) + octoinv.createStackOrder(ply:SteamID(), type, itemID, price, amount, os.time() + CFG.getMarketOrderLifeTime(ply)) + :Then(done) + :Catch(function() + reply('Что-то пошло не так при создании лота') + end) + end, + function() + ply:Notify(('Заявка на %s создана: %s за %s'):format( + type == octoinv.ORDER_SELL and 'продажу' or 'покупку', + octoinv.itemStr({ itemID, amount }), + DarkRP.formatMoney(price) + )) + reply() + end, + }) +end) + +-- +-- NOSTACK ORDERS +-- + +netstream.Hook('octoinv.listNoStackOrders', function(ply, itemID) + if not ply:TriggerCooldown('marketItem', 0.5) then return 'Слишком часто' end + subscribe(ply, itemID) + octoinv.listNoStackOrders(itemID):Then(function(data) + netstream.Start(ply, 'octoinv.listNoStackOrders', itemID, data) + end) +end) + +netstream.Listen('octoinv.createNoStackOrder', function(reply, ply, itemID) + local sendCont, requestData, item + octolib.func.chain({ + checkCanTradeForChain(reply, ply), + function(done) + local box, distSqr = octoinv.getNearestMailbox(ply) + if distSqr > 10000 then return reply('Для продажи надо доставить подходящие предметы в любой почтовый ящик') end + + sendCont = box.inv.conts.send + local items = octoinv.findMatchingMarketItems(sendCont, itemID) + if table.Count(items) < 1 then return reply('В почтовом ящике нет подходящих предметов') end + + local startPrice = octoinv.marketSummary[itemID] and octoinv.marketSummary[itemID].minSell or nil + local fee = CFG.getMarketFee(ply) * 100 + octolib.request.send(ply, { + item = { + name = 'Предмет', + type = 'item', + items = items, + single = true, + required = true, + }, + price = { + name = 'Стоимость', + desc = 'Цена, которую заплатит покупатель. Тебе нужно заплатить ' .. fee .. '% от этой суммы для создания лота', + type = 'strShort', + numeric = true, + default = startPrice, + required = true, + }, + }, done) + end, + checkCanTradeForChain(reply, ply), -- check one more time if something happened during request + function(done, data) + requestData = data + requestData.price = tonumber(requestData.price or '') + + item = sendCont.items[requestData.item] + if not item then return reply('Предмет не найден') end + if not isnumber(requestData.price) or requestData.price <= 0 then return reply('Некорректные параметры') end + + local iData, _itemID = octoinv.marketDataFromItem(item) + if itemID ~= _itemID then return reply('Предмет не найден') end + + if iData.canSell then + local can, why = iData.canSell(ply, price, amount) + if not can then return reply(why or 'Ты не можешь продать этот товар') end + end + + requestData.price = math.floor(requestData.price) + local fee = math.ceil(CFG.getMarketFee(ply) * requestData.price) + if not ply:BankHas(fee) then + return reply('На счете в банке должно быть ' .. DarkRP.formatMoney(fee) .. ' для оплаты комиссии') + end + + ply:BankAdd(-fee) + item:Remove() + + octoinv.createNoStackOrder(ply:SteamID(), octoinv.ORDER_SELL, itemID, requestData.price, item:Export(), os.time() + CFG.getMarketOrderLifeTime(ply)) + :Then(done) + :Catch(function() + reply('Что-то пошло не так при создании лота') + end) + end, + function() + ply:Notify(('Заявка на продажу создана: %s за %s'):format(octoinv.itemStr({ item.class, item.data }), DarkRP.formatMoney(requestData.price))) + reply() + end, + }) +end) + +netstream.Listen('octoinv.buyNoStackOrder', function(reply, ply, id) + local buyer, order = ply:SteamID() + octolib.func.chain({ + checkCanTradeForChain(reply, ply, true), + function(done) + octoinv.getNoStackOrder(id):Then(done) + end, + function(done, _order) + order = _order + if not order then return reply('Лота не существует. Возможно, его уже купили') end + + local iData = octoinv.marketItems[order.item] + if not iData then return reply('Тип предмета не найден в базе данных') end + + if iData.canBuy then + local can, why = iData.canBuy(ply, order) + if not can then return reply(why or 'Ты не можешь купить этот товар') end + end + + if not ply:BankHas(order.price) then + return reply('У тебя недостаточно денег на счету') + end + + ply:BankAdd(-order.price) + octoinv.deleteNoStackOrder(order.id):Then(done) + end, + function() + octoinv.finishNoStackSell(order.steamID, order.item, order.price, order.data) + octoinv.finishNoStackBuy(buyer, order.item, order.price, order.data) + reply() + end, + }) +end) + +octolib.notify.registerActions('market', { + function(ply, data) + if not data.money and not data.item then return end + return 'Получить', function(ply, data) + if not ply:HasMobilePhone() then + ply:Notify('warning', 'Для этого нужен мобильный телефон') + return + end + + if data.money then + ply:BankAdd(data.money) + end + + if data.item then + local box = octoinv.sendToMailbox(ply, { data.item }) + if not box then return end + + ply:AddMarker { + txt = 'Вещи с рынка', + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'dist', { 100 }}, + icon = 'octoteam/icons-16/lorry.png', + } + + ply:Notify('Вещи, купленные на рынке, отправлены в почтовый ящик') + end + + return true + end + end, +}) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_market.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_market.lua new file mode 100644 index 0000000..e648e95 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_market.lua @@ -0,0 +1,235 @@ +octoinv.marketQueue = {} +octoinv.marketQueuePending = false +octoinv.executeOrderFuncs = octoinv.executeOrderFuncs or {} + +hook.Add('octolib.db.init', 'octoinv.market', function() + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS octoinv_orders_stack ( + id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + steamID VARCHAR(30), + type TINYINT(1) UNSIGNED NOT NULL, + item VARCHAR(30) NOT NULL, + price INT(10) UNSIGNED NOT NULL, + amount INT(6) UNSIGNED NOT NULL, + expire INT(10) UNSIGNED, + PRIMARY KEY (id) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS octoinv_orders_nostack ( + id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + steamID VARCHAR(30), + type TINYINT(1) UNSIGNED NOT NULL, + item VARCHAR(30) NOT NULL, + price INT(10) UNSIGNED NOT NULL, + data TEXT, + expire INT(10) UNSIGNED, + PRIMARY KEY (id) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + octoinv.refreshMarketSummary() + + timer.Create('octoinv.marketCheckExpired', 60, 0, function() + octolib.db:PrepareQuery('select * from octoinv_orders_stack where expire < ?', { os.time() }, function(q, st, rows) + for _, order in ipairs(rows) do + octoinv.refundStackOrder(order) + octoinv.marketUpdateItem(order.item) + end + end) + octolib.db:PrepareQuery('delete from octoinv_orders_stack where expire < ?', { os.time() }) + + octolib.db:PrepareQuery('select * from octoinv_orders_nostack where expire < ?', { os.time() }, function(q, st, rows) + for _, order in ipairs(rows) do + octoinv.refundNoStackOrder(order) + octoinv.marketUpdateItem(order.item) + end + end) + octolib.db:PrepareQuery('delete from octoinv_orders_nostack where expire < ?', { os.time() }) + end) + +end) + +function octoinv.refreshMarketSummary() + + octolib.func.parallel({ + stack = function(done) octoinv.getStackOrdersSummary():Then(done) end, + noStack = function(done) octoinv.getNoStackOrdersSummary():Then(done) end, + }):Then(function(data) + local out = {} + table.Merge(out, data.stack) + table.Merge(out, data.noStack) + octoinv.marketSummary = out + netstream.Start(nil, 'octoinv.marketSummary', nil, out) + end) + +end + +function octoinv.getMarketItemFamily(item) + + local out = { item } + if octoinv.marketItems[item] and octoinv.marketItems[item].children then + for _, childID in pairs(octoinv.marketItems[item].children) do + for _, itemID in ipairs(octoinv.getMarketItemFamily(childID)) do + out[#out + 1] = itemID + end + end + end + + return out + +end + +function octoinv.getMarketItemFamilySQL(item) + + return table.concat(octolib.table.map(octoinv.getMarketItemFamily(item), function(item) return sql.SQLStr(item) end), ',') + +end + +function octoinv.marketDataFromItem(item) + + local class = istable(item) and item.class or item + + local iData = octoinv.marketItems[class] + if iData and not iData.matches then + return iData, class + end + + for itemID, iData in pairs(octoinv.marketItems) do + if iData.matches and iData.matches(item) then + return iData, itemID + end + end + +end + +function octoinv.findMatchingMarketItems(cont, itemID) + + local out = {} + + local iData = octoinv.marketItems[itemID] + if not iData then return out end + + if iData.matches then + for i, item in ipairs(cont.items) do + if iData.matches(item) then + out[i] = item + end + end + else + for i, item in ipairs(cont.items) do + if item.class == itemID then + out[i] = item + end + end + end + + return out + +end + +function octoinv.nextMarketOrder() + + local order = table.remove(octoinv.marketQueue, 1) + if not order then + octoinv.marketQueuePending = false + return + else + octoinv.marketQueuePending = true + end + + local func = octoinv.executeOrderFuncs[order.orderType or ''] + if func then func(order) end + +end + +function octoinv.getPlayerOrders(ply) + + return util.Promise(function(res, rej) + local sID = ply:SteamID() + return octolib.func.parallel({ + stack = function(done) + octolib.db:PrepareQuery('select * from octoinv_orders_stack where steamID = ? and expire > ?', { sID, os.time() }, function(q, st, rows) + if not st then return rej(rows) end + done(octolib.table.mapSequential(rows, function(row) + row.mode = 'stack' + return row + end)) + end) + end, + nostack = function(done) + octolib.db:PrepareQuery('select * from octoinv_orders_nostack where steamID = ? and expire > ?', { sID, os.time() }, function(q, st, rows) + if not st then return rej(rows) end + done(octolib.table.mapSequential(rows, function(row) + row.mode = 'nostack' + row.data = pon.decode(row.data) + return row + end)) + end) + end, + }):Then(function(data) + local out = {} + table.Add(out, data.stack) + table.Add(out, data.nostack) + res(out) + end) + end) + +end + +local syncQueue = {} +hook.Add('Think', 'octoinv.marketSync', function() + + for itemID, _ in pairs(syncQueue) do + local item = octoinv.marketItems[itemID] + syncQueue[itemID] = nil + if not item then continue end + + if item.nostack then + octoinv.getNoStackOrdersSummary(itemID):Then(function(data) + octoinv.marketSummary[itemID] = data + netstream.Start(nil, 'octoinv.marketSummary', itemID, data) + + for _, ply in ipairs(octoinv.marketSubs[itemID] or {}) do + local itemID = ply.marketSub + if not itemID then continue end + + octoinv.listNoStackOrders(itemID):Then(function(data) + netstream.Start(ply, 'octoinv.listNoStackOrders', itemID, data) + end) + end + end) + else + octoinv.getStackOrdersSummary(itemID):Then(function(data) + octoinv.marketSummary[itemID] = data + netstream.Start(nil, 'octoinv.marketSummary', itemID, data) + + for _, ply in ipairs(octoinv.marketSubs[itemID] or {}) do + local itemID = ply.marketSub + if not itemID then continue end + + octoinv.getStackOrdersDOM(itemID):Then(function(data) + netstream.Start(ply, 'octoinv.getStackOrdersDOM', itemID, data) + end) + end + end) + end + end + +end) + +function octoinv.marketUpdateItem(itemID, dontSendCmd) + + syncQueue[itemID] = true + + if not dontSendCmd then + octolib.sendCmdToOthers('marketUpdateItem', { itemID = itemID }) + end + +end + +hook.Add('octolib.event:marketUpdateItem', 'octoinv.market', function(data) + octoinv.marketUpdateItem(data.itemID, true) +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_orders_nostack.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_orders_nostack.lua new file mode 100644 index 0000000..6ec17c4 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_orders_nostack.lua @@ -0,0 +1,204 @@ +function octoinv.createNoStackOrder(steamID, type, item, price, data, expire) + + return util.Promise(function(res, rej) + octoinv.marketQueue[#octoinv.marketQueue + 1] = { + orderType = 'nostack', + res = res, + rej = rej, + steamID = steamID, + type = type, + item = item, + price = price, + data = data, + expire = expire, + } + + if not octoinv.marketQueuePending then octoinv.nextMarketOrder() end + end) + +end + +function octoinv.getNoStackOrder(id) + + return util.Promise(function(res, rej) + octolib.db:PrepareQuery('select * from octoinv_orders_nostack where id = ? limit 1', { id }, function(q, ok, data) + if ok and istable(data) then + if data[1] then + data[1].data = pon.decode(data[1].data) + res(data[1]) + else + rej('Order not found') + end + else + rej(data) + end + end) + end) + +end + +function octoinv.listNoStackOrders(item, limit, includeSteamID) + + local fields = 'id, item, price, data' + if includeSteamID then fields = fields .. ', steamID' end + + limit = tonumber(limit) or 50 + + return util.Promise(function(res, rej) + if not item then return rej('Item class must be provided') end + + octolib.db:PrepareQuery([[ + SELECT ]] .. fields .. [[ FROM octoinv_orders_nostack + WHERE item IN (]] .. octoinv.getMarketItemFamilySQL(item) .. [[) AND expire > ? + ORDER BY price ASC + LIMIT ]] .. limit + , { os.time(), }, function(q, ok, data) + if ok and istable(data) then + for _, row in ipairs(data) do + if not row.data then continue end + row.data = pon.decode(row.data) + end + res(data) + else + rej(data) + end + end) + end) + +end + +function octoinv.getNoStackOrdersSummary(item) + + return util.Promise(function(res, rej) + local where = 'expire > ' .. os.time() + if item then where = where .. ' AND item = ' .. SQLStr(item) end + + octolib.db:RunQuery([[ + SELECT + item, + COUNT(id) AS totalSell, + MIN(price) AS minSell + FROM octoinv_orders_nostack + WHERE ]] .. where .. [[ + GROUP BY item + ]], function(q, ok, data) + if ok and istable(data) then + local out = {} + for _,v in ipairs(data) do + if not v.item then continue end + out[v.item] = v + v.item = nil + end + if item then + res(out[item] or {}) + else + res(out) + end + else + rej(data) + end + end) + end) + +end + +function octoinv.deleteNoStackOrder(id) + + return util.Promise(function(res, rej) + octolib.db:PrepareQuery('select * from octoinv_orders_nostack where id = ?', { id }, function(q, ok, rows) + local row = istable(rows) and rows[1] + if row then + octolib.db:PrepareQuery('delete from octoinv_orders_nostack where id = ?', { id }, function(q, ok, err) + if ok then + if q:affectedRows() > 0 then + res() + octoinv.marketUpdateItem(row.item) + else + rej('order not found') + end + else + rej(err) + end + end) + else + rej('Order does not exist') + end + end) + end) + +end + +function octoinv.executeOrderFuncs.nostack(order) + + order.price = order.price or 1 + order.data = order.data or {} + order.expire = order.expire or (os.time() + CFG.getMarketOrderLifeTime()) + + if order.type == octoinv.ORDER_SELL then + local itemData = octoinv.marketItems[order.item] + if not itemData or itemData.children or not itemData.nostack then + order.rej('Unknown item class') + return octoinv.nextMarketOrder() + end + + octolib.db:PrepareQuery([[ + INSERT INTO octoinv_orders_nostack(steamID, type, item, data, price, expire) VALUES(?,?,?,?,?,?) + ]], { order.steamID, order.type, order.item, pon.encode(order.data), order.price, order.expire }, function(q, ok, err) + if ok then + octoinv.marketUpdateItem(order.item) + order.res({ + id = q:lastInsert(), steamID = order.steamID, item = order.item, type = order.type, + data = order.data, price = order.price, expire = order.expire, + }) + else + order.rej(err) + end + return octoinv.nextMarketOrder() + end) + else + order.rej('Unknown order type') + return octoinv.nextMarketOrder() + end + +end + +function octoinv.finishNoStackSell(seller, itemID, price, data) + + if not seller then return end + + local itemStr = octoinv.itemStr({ data.class, data }) + octoinv.marketUpdateItem(itemID) + + local sellerText = ('Заявка исполнена: продажа %s за %s'):format(itemStr, DarkRP.formatMoney(price)) + octolib.notify.send(seller, 'market', sellerText, { + text = sellerText, + money = price, + }) + +end + +function octoinv.finishNoStackBuy(buyer, itemID, price, data) + + if not buyer then return end + + local item = { data.class or itemID, data } + octoinv.marketUpdateItem(itemID) + + local buyerText = ('Заявка исполнена: покупка %s за %s'):format(octoinv.itemStr(item), DarkRP.formatMoney(price)) + octolib.notify.send(buyer, 'market', buyerText, { item = item }) + +end + +function octoinv.refundNoStackOrder(order) + + if not order.steamID then return end + + if not istable(order.data) then + order.data = pon.decode(order.data) + end + + local item = { order.data.class or order.item, order.data } + local text = ('Заявка отменена: продажа %s за %s'):format(octoinv.itemStr(item), DarkRP.formatMoney(order.price)) + octolib.notify.send(order.steamID, 'market', text, { item = item }) + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_orders_stack.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_orders_stack.lua new file mode 100644 index 0000000..8bb60db --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_orders_stack.lua @@ -0,0 +1,376 @@ +function octoinv.listStackOrders(item, type, includeSteamID) + + local fields = 'id, item, price, amount, expire' + if includeSteamID then fields = fields .. ', steamID' end + + if type == octoinv.ORDER_BUY then + query = 'SELECT ' .. fields .. ' FROM octoinv_orders_stack WHERE item IN (' .. octoinv.getMarketItemFamilySQL(item) .. ') AND type = ' .. octoinv.ORDER_BUY .. ' AND expire > ' .. os.time() .. ' ORDER BY price DESC LIMIT 100' + elseif type == octoinv.ORDER_SELL then + query = 'SELECT ' .. fields .. ' FROM octoinv_orders_stack WHERE item IN (' .. octoinv.getMarketItemFamilySQL(item) .. ') AND type = ' .. octoinv.ORDER_SELL .. ' AND expire > ' .. os.time() .. ' ORDER BY price ASC LIMIT 100' + else + query = 'SELECT ' .. fields .. ' FROM octoinv_orders_stack WHERE item IN (' .. octoinv.getMarketItemFamilySQL(item) .. ') AND expire > ' .. os.time() .. ' LIMIT 100' + end + + return util.Promise(function(res, rej) + octolib.db:RunQuery(query, function(q, ok, data) + if ok and istable(data) then + res(data) + else + rej(data) + end + end) + end) + +end + +function octoinv.getStackOrdersSummary(item) + + return util.Promise(function(res, rej) + local where = 'expire > ' .. os.time() + if item then where = where .. ' AND item = ' .. SQLStr(item) end + + octolib.db:RunQuery([[ + WITH + buy AS ( + SELECT + item AS itemBuy, + SUM(amount) AS totalBuy, + MAX(price) AS maxBuy + FROM octoinv_orders_stack + WHERE ]] .. where .. [[ AND type = ]] .. octoinv.ORDER_BUY .. [[ + GROUP BY itemBuy + ), + sell AS ( + SELECT + item as itemSell, + SUM(amount) AS totalSell, + MIN(price) AS minSell + FROM octoinv_orders_stack + WHERE ]] .. where .. [[ AND type = ]] .. octoinv.ORDER_SELL .. [[ + GROUP BY itemSell + ) + + SELECT * FROM buy + LEFT JOIN sell ON buy.itemBuy = sell.itemSell + UNION + SELECT * FROM buy + RIGHT JOIN sell ON buy.itemBuy = sell.itemSell + ]], function(q, ok, data) + if ok and istable(data) then + local out = {} + for _,v in ipairs(data) do + v.item = v.itemBuy or v.itemSell -- it worked without this before, wtf? + v.itemBuy, v.itemSell = nil + if not v.item then continue end + v.totalBuy = tonumber(v.totalBuy) + v.totalSell = tonumber(v.totalSell) + out[v.item] = v + v.item = nil + end + if item then + res(out[item] or {}) + else + res(out) + end + else + rej(data) + end + end) + end) + +end + +function octoinv.getStackOrdersDOM(item, depth) + + depth = tonumber(depth) or 20 + + return util.Promise(function(res, rej) + if not item then return rej('Item class must be provided') end + + local time = os.time() + octolib.db:PrepareQuery([[ + WITH + sell AS ( + SELECT type, price, SUM(amount) AS orders + FROM octoinv_orders_stack + WHERE TYPE = ? AND expire > ? AND item = ? + GROUP BY price + ORDER BY price ASC + LIMIT ]] .. depth .. [[ + ), + buy AS ( + SELECT type, price, SUM(amount) AS orders + FROM octoinv_orders_stack + WHERE TYPE = ? AND expire > ? AND item = ? + GROUP BY price + ORDER BY price DESC + LIMIT ]] .. depth .. [[ + ) + SELECT * FROM buy UNION SELECT * FROM sell + ORDER BY price DESC + ]], { octoinv.ORDER_SELL, time, item, octoinv.ORDER_BUY, time, item }, function(q, ok, data) + if ok and istable(data) then + local out = octolib.table.map(data, function(row) return { row.type, row.price, tonumber(row.orders) } end) + res(out) + else + rej(data) + end + end) + end) + +end + +function octoinv.getStackOrder(id) + + return util.Promise(function(res, rej) + octolib.db:PrepareQuery('select * from octoinv_orders_stack where id = ? limit 1', { id }, function(q, ok, data) + if ok and istable(data) then + if data[1] then + res(data[1]) + else + rej('Order not found') + end + else + rej(data) + end + end) + end) + +end + +function octoinv.createStackOrder(steamID, type, item, price, amount, expire) + + return util.Promise(function(res, rej) + octoinv.marketQueue[#octoinv.marketQueue + 1] = { + orderType = 'stack', + res = res, + rej = rej, + steamID = steamID, + type = type, + item = item, + price = price, + amount = amount, + expire = expire + } + + if not octoinv.marketQueuePending then octoinv.nextMarketOrder() end + end) + +end + +function octoinv.deleteStackOrder(id) + + return util.Promise(function(res, rej) + octolib.db:PrepareQuery('select * from octoinv_orders_stack where id = ?', { id }, function(q, ok, rows) + local row = istable(rows) and rows[1] + if row then + octolib.db:PrepareQuery('delete from octoinv_orders_stack where id = ?', { id }, function(q, ok, err) + if ok then res() else rej(err) end + octoinv.marketUpdateItem(row.item) + end) + else + rej('Order does not exist') + end + end) + end) + +end + +function octoinv.executeOrderFuncs.stack(order) + + order.price = order.price or 1 + order.amount = order.amount or 1 + order.expire = order.expire or (os.time() + CFG.getMarketOrderLifeTime()) + + local itemData = octoinv.marketItems[order.item] + if not itemData or itemData.children or itemData.nostack then + order.rej('Unknown item class') + return octoinv.nextMarketOrder() + end + + if order.type == octoinv.ORDER_BUY then + octolib.func.chain({ + function(done) + octolib.db:PrepareQuery([[ + SELECT * FROM octoinv_orders_stack WHERE item = ? AND type = ? AND price <= ? AND expire > ? + ]], { order.item, octoinv.ORDER_SELL, order.price, os.time() }, done) + end, + function(done, q, ok, data) + if not ok then return order.rej(data) end + + local deals = {} + local excess = 0 + + local left = order.amount + for _, existing in ipairs(data) do + local sold = math.min(existing.amount, left) + left = left - sold + excess = excess + sold * (order.price - existing.price) + + if existing.amount - sold <= 0 then + octoinv.deleteStackOrder(existing.id) + else + octolib.db:PrepareQuery('UPDATE octoinv_orders_stack SET amount = amount - ? WHERE id = ?', { sold, existing.id }) + end + + if sold > 0 then + local dealID = #deals + 1 + deals[dealID] = ('%s) %s по %s'):format(dealID, sold, DarkRP.formatMoney(existing.price)) + + octoinv.finishStackSell(existing.steamID, order.item, sold, existing.price) + end + + if left <= 0 then + octoinv.marketUpdateItem(order.item) + break + end + end + + local sold = order.amount - left + if sold > 0 then + octoinv.finishStackBuy(order.steamID, order.item, sold, order.price, deals, excess) + end + + if left > 0 then + octolib.db:PrepareQuery([[ + INSERT INTO octoinv_orders_stack(steamID, type, item, amount, price, expire) VALUES(?,?,?,?,?,?) + ]], { order.steamID, order.type, order.item, left, order.price, order.expire }, done) + else + order.res() + return octoinv.nextMarketOrder() + end + end, + function(done, q, ok, err) + if ok then + octoinv.marketUpdateItem(order.item) + order.res({ + id = q:lastInsert(), steamID = order.steamID, item = order.item, type = order.type, + amount = order.amount, price = order.price, expire = order.expire, + }) + else + order.rej(err) + end + return octoinv.nextMarketOrder() + end, + }) + elseif order.type == octoinv.ORDER_SELL then + octolib.func.chain({ + function(done) + octolib.db:PrepareQuery([[ + SELECT * FROM octoinv_orders_stack WHERE item = ? AND type = ? AND price >= ? AND expire > ? + ]], { order.item, octoinv.ORDER_BUY, order.price, os.time() }, done) + end, + function(done, q, ok, data) + if not ok then return order.rej(data) end + + local deals = {} + + local left = order.amount + for _, existing in ipairs(data) do + local sold = math.min(existing.amount, left) + left = left - sold + local excess = sold * (existing.price - order.price) + + if existing.amount - sold <= 0 then + octoinv.deleteStackOrder(existing.id) + else + octolib.db:PrepareQuery('UPDATE octoinv_orders_stack SET amount = amount - ? WHERE id = ?', { sold, existing.id }) + end + + if sold > 0 then + local dealID = #deals + 1 + deals[dealID] = ('%s) %s по %s'):format(dealID, sold, DarkRP.formatMoney(existing.price)) + + octoinv.finishStackBuy(existing.steamID, order.item, sold, existing.price, { ('1) %s по %s'):format(sold, DarkRP.formatMoney(existing.price)) }, excess) + end + + if left <= 0 then + octoinv.marketUpdateItem(order.item) + break + end + end + + local sold = order.amount - left + if sold > 0 then + octoinv.finishStackSell(order.steamID, order.item, sold, order.price) + end + + if left > 0 then + octolib.db:PrepareQuery([[ + INSERT INTO octoinv_orders_stack(steamID, type, item, amount, price, expire) VALUES(?,?,?,?,?,?) + ]], { order.steamID, order.type, order.item, left, order.price, order.expire }, done) + else + order.res() + return octoinv.nextMarketOrder() + end + end, + function(done, q, ok, err) + if ok then + octoinv.marketUpdateItem(order.item) + order.res({ + id = q:lastInsert(), steamID = order.steamID, item = order.item, type = order.type, + amount = order.amount, price = order.price, expire = order.expire, + }) + else + order.rej(err) + end + return octoinv.nextMarketOrder() + end, + }) + else + order.rej('Unknown order type') + return octoinv.nextMarketOrder() + end + +end + +function octoinv.finishStackSell(steamID, item, amount, price) + + if not steamID then return end + + local notifText = ('Сделка завершена: продажа %sx %s'):format(amount, octoinv.getItemData('name', item)) + local notifData = { + text = notifText .. (' по %s (итого %s)'):format(DarkRP.formatMoney(price), DarkRP.formatMoney(price * amount)), + money = price * amount, + } + + octolib.notify.send(steamID, 'market', notifText, notifData) + +end + +function octoinv.finishStackBuy(steamID, item, amount, price, deals, excess) + + if not steamID then return end + + local notifText = ('Сделка завершена: покупка %sx %s'):format(amount, octoinv.getItemData('name', item)) + local notifData = { + text = notifText .. ' по ' .. DarkRP.formatMoney(price), + item = { item, amount }, + } + + if deals then + notifData.text = notifData.text .. '\n' .. table.concat(deals, '\n') + end + + if excess and excess > 0 then + notifData.text = notifData.text .. '\nСдача: ' .. DarkRP.formatMoney(excess) + notifData.money = excess + end + + octolib.notify.send(steamID, 'market', notifText, notifData) + +end + +function octoinv.refundStackOrder(order) + + if not order.steamID then return end + + if order.type == octoinv.ORDER_SELL then + local text = ('Заявка отменена: продажа %sx %s по %s'):format(order.amount, octoinv.getItemData('name', order.item), DarkRP.formatMoney(order.price)) + octolib.notify.send(order.steamID, 'market', text, { item = { order.item, order.amount }}) + elseif order.type == octoinv.ORDER_BUY then + local text = ('Заявка отменена: покупка %sx %s по %s'):format(order.amount, octoinv.getItemData('name', order.item), DarkRP.formatMoney(order.price)) + octolib.notify.send(order.steamID, 'market', text, { money = order.price * order.amount }) + end + +end diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_retain.lua b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_retain.lua new file mode 100644 index 0000000..3f99604 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/market/sv_retain.lua @@ -0,0 +1,84 @@ +octoinv.marketRetainItems = octoinv.marketRetainItems or {} + +function octoinv.marketRetainUpdate() + + local expire = os.time() + octolib.time.toSeconds(6, 'hours') + for itemID, retain in pairs(octoinv.marketRetainItems) do + local marketData = octoinv.marketItems[itemID] + if not marketData then + octoinv.marketRetainItems[itemID] = nil + continue + end + + local summary = octoinv.marketSummary[itemID] + + local retainSell = retain.sell + if retainSell then + local amount = summary and summary.totalSell or 0 + local price = summary and summary.minSell or 0 + if amount < retainSell.amount or price > retainSell.price then + if marketData.nostack then + for _ = 1, retainSell.amount do + local price, data = retainSell.getOrderInfo() + if not price then return end + + octoinv.createNoStackOrder(nil, octoinv.ORDER_SELL, itemID, price, data, expire) + end + else + local currentAmount = 0 + while currentAmount < retainSell.amount do + local price, amount = retainSell.getOrderInfo() + if not price or amount < 1 then return end + + octoinv.createStackOrder(nil, octoinv.ORDER_SELL, itemID, price, amount, expire) + currentAmount = currentAmount + amount + end + end + end + end + + local retainBuy = retain.buy + if retainBuy then + local amount = summary and summary.totalBuy or 0 + local price = summary and summary.maxBuy or 0 + if amount < retainBuy.amount or price < retainBuy.price then + if marketData.nostack then + -- for _ = 1, retainBuy.amount do + -- local price, data = retainBuy.getOrderInfo() + -- if not price then return end + + -- octoinv.createNoStackOrder(nil, octoinv.ORDER_BUY, itemID, price, data, expire) + -- end + octoinv.msg('Cannot create buy nostack order: not implemented') + else + local currentAmount = 0 + while currentAmount < retainBuy.amount do + local price, amount = retainBuy.getOrderInfo() + if not price or amount < 1 then return end + + octoinv.createStackOrder(nil, octoinv.ORDER_BUY, itemID, price, amount, expire) + currentAmount = currentAmount + amount + end + end + end + end + end + +end + +local retainInterval = octolib.time.toSeconds(1, 'hour') +local function tryRetainUpdate() + + -- use dbvars so that all servers won't try it at once + octolib.getDBVar('octoinv', 'nextRetainUpdate'):Then(function(updateAt) + if os.time() >= updateAt then + octolib.setDBVar('octoinv', 'nextRetainUpdate', os.time() + retainInterval) + octoinv.marketRetainUpdate() + end + end):Catch(function(err) + octolib.setDBVar('octoinv', 'nextRetainUpdate', os.time() + retainInterval) + end) + +end +tryRetainUpdate() +timer.Create('octoinv.market.retain', retainInterval, 0, tryRetainUpdate) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/shop/cl_shop.lua b/garrysmod/addons/core-octoinv/lua/octoinv/shop/cl_shop.lua new file mode 100644 index 0000000..2bd7495 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/shop/cl_shop.lua @@ -0,0 +1,889 @@ +surface.CreateFont('octoinv.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local changed, shopcache, presetcache, foldercache, returncache, preview = true + +local w +local function openPreview(item) + + if not item.model then return end + if IsValid(w) then w:Remove() end + + w = vgui.Create 'DFrame' + preview = w + + w:SetSize(400, 400) + w:SetSizable(true) + w:Center() + w:MakePopup() + w:SetTitle(L.inspection_hint .. item.name) + w:DockPadding(0,24,0,0) + + local m = w:Add 'DAdjustableModelPanel' + m:Dock(FILL) + m:SetModel(item.plyMat and LocalPlayer():GetModel() or item.model) + m.LayoutEntity = octolib.func.zero + + local ent = m:GetEntity() + local mins, maxs = ent:GetModelBounds() + ent:SetSkin(item.skin or 0) + m:SetCamPos(mins:Distance(maxs) * Vector(1, 0, 0)) + m:SetFOV(50) + m.aLookAngle = Angle(0, 45, 0) + m.vCamPos = m.Entity:OBBCenter() - m.aLookAngle:Forward() * m.vCamPos:Length() + + if item.plyMat then + local pos = m.Entity:OBBCenter() - Vector(0, 0, -36) + m.aLookAngle = Angle(0, 180, 0) + m.vCamPos = pos - m.aLookAngle:Forward() * (pos - m.vCamPos):Length() + for i, mat in ipairs(ent:GetMaterials()) do + if string.match(mat, '.+/sheet_%d+') then + ent:SetSubMaterial(i - 1, item.plyMat) + end + end + end + + local h = m:Add 'DLabel' + h:Dock(TOP) + h:SetTall(30) + h:SetContentAlignment(5) + h:SetMouseInputEnabled(false) + h:SetText(L.inspection_help) + +end + +local methods = { + { 0, unpack(L.methods[1]) }, + { 0.05, unpack(L.methods[2]) }, + { 0.2, unpack(L.methods[3]) }, +} + +function octoinv.createShop() + + if IsValid(octoinv.pnlShop) then octoinv.pnlShop:Remove() end + local basket, method, receiver = {}, 1, nil + local pendingUpdate, loadingTab, shopTab, presetTab + if not file.Exists('octoinv/shop_presets.dat', 'DATA') then + file.Write('octoinv/shop_presets.dat', '{}') + end + + local colBG = CFG.skinColors.bg + local f = vgui.Create 'DFrame' + f:SetSize(450, 600) + f:SetDraggable(false) + f:SetTitle('') + f:DockPadding(3, 3, 3, 3) + f:Center() + f.Paint = function(self, w, h) + draw.RoundedBox(4, 0, 23, w, h-23, colBG) + end + function f:PerformLayout() + self.lblTitle:SetPos(8, 2) + self.lblTitle:SetSize(self:GetWide() - 25, 20) + end + + local ps = f:Add 'DPropertySheet' + ps:Dock(FILL) + f.btnClose:SetParent(ps) + f.btnMaxim:Remove() + f.btnMinim:Remove() + + local rf = ps:Add 'DButton' + rf:SetSize(70, 18) + rf:SetText(L.refresh) + function rf:DoClick() + octoinv.requestUpdate() + end + + local oldLayout = ps.PerformLayout + function ps:PerformLayout() + oldLayout(self) + if IsValid(f.btnClose) then f.btnClose:SetPos(ps:GetWide() - 31, -3) end + rf:SetPos(ps:GetWide() - 100) + end + + + -- basket + local p_basket = ps:Add 'DPanel' + p_basket:Dock(FILL) + p_basket:SetPaintBackground(false) + local bTab = ps:AddSheet(L.basket, p_basket, octolib.icons.silk16('cart')) + + -- info panel + local drawer = f:Add 'DDrawer' + drawer:SetOpenSize(117) + drawer:SetOpenTime(0.2) + drawer.ToggleButton:SetVisible(false) + + local info_p = drawer:Add 'DPanel' + info_p:Dock(FILL) + info_p:DockMargin(3,0,3,3) + + local info_name = info_p:Add 'DLabel' + info_name:Dock(TOP) + info_name:DockMargin(5, 5, 5, 0) + info_name:SetTall(25) + info_name:SetFont('octoinv.normal') + info_name:SetText(L.item) + + local defaultDesc = L.defaultDesc + local info_desc = info_p:Add 'DLabel' + info_desc:Dock(FILL) + info_desc:DockMargin(5, 5, 5, 5) + info_desc:SetContentAlignment(7) + info_desc:SetWrap(true) + info_desc:SetText(defaultDesc) + + local info_buy = info_p:Add 'DButton' + info_buy:SetText(L.in_basket) + info_buy:SetTall(25) + function info_buy:PerformLayout() + self:SizeToContentsX(20) + self:AlignTop(5) + self:AlignRight(5) + end + + local bp = p_basket:Add 'DPanel' + bp:Dock(BOTTOM) + bp:DockMargin(8,5,8,8) + bp:SetTall(55) + bp:SetPaintBackground(false) + + local bp1 = bp:Add 'DPanel' + bp1:Dock(TOP) + bp1:DockMargin(0,0,0,0) + bp1:SetTall(25) + bp1:SetPaintBackground(false) + + local bp2 = bp:Add 'DPanel' + bp2:Dock(FILL) + bp2:DockMargin(0,5,0,0) + bp2:SetTall(25) + bp2:SetPaintBackground(false) + + local butClear = bp2:Add 'DButton' + butClear:Dock(LEFT) + butClear:SetWide(80) + butClear:SetText(L.clear) + butClear.DoClick = function(self) + basket = {} + p_basket:UpdateData() + end + + local butBuy = bp2:Add 'DButton' + butBuy:Dock(RIGHT) + butBuy:SetWide(80) + butBuy:SetText(L.butbuy) + function butBuy:PerformLayout() + self:SizeToContentsX(20) + self:AlignRight() + end + function butBuy:DoClick() + if table.Count(basket) < 1 then return end + + local toSend = {} + for k,v in pairs(basket) do toSend[k] = v[2] end + netstream.Start('octoinv.shop', toSend, method, receiver) + end + + local cbReceiver = bp1:Add 'DComboBox' + cbReceiver:Dock(FILL) + cbReceiver:SetSortItems(false) + function cbReceiver:OnSelect(i, text, val) receiver = val end + cbReceiver:SetTooltip(table.concat({ + L.only_specified, + L.add_friend_tab, + }, string.char(10))) + + local lv = p_basket:Add 'DListView' + lv:Dock(FILL) + lv:DockMargin(8,0,8,0) + lv:SetMultiSelect(false) + lv:AddColumn(L.title) + lv:AddColumn(L.qty):SetFixedWidth(60) + lv:AddColumn(L.price2):SetFixedWidth(80) + function lv:OnRowRightClick(i, line) + local menu = DermaMenu() + + menu:AddOption(L.quantity, function() + local bitem = basket[line.itemID] + Derma_StringRequest(L.set_quantity, L.set_quantity_hint, bitem[2], function(res) + local amount = tonumber(res) + if amount then + bitem[2] = math.Clamp(amount, 0, 1000) + if bitem[2] == 0 then basket[line.itemID] = nil end + end + p_basket:UpdateData() + end) + end):SetIcon('icon16/chart_bar.png') + + menu:AddOption(L.delete, function() + basket[line.itemID] = nil + p_basket:UpdateData() + end):SetIcon('icon16/delete.png') + + menu:Open() + end + + function p_basket:UpdateData() + lv:Clear() + + local lp = LocalPlayer() + local items, total = 0, 0 + for itemID, itemData in pairs(basket) do + local price = itemData[1].price * itemData[2] + total = total + price + + local line = lv:AddLine(itemData[1].name, itemData[2], DarkRP.formatMoney(price)) + line.Columns[2]:SetContentAlignment(5) + line.Columns[3]:SetContentAlignment(6) + line.itemID = itemID + items = items + 1 + end + + local deliveryPrice = lp:GetNetVar('os_delivery') and 0 or math.ceil(total * methods[method][1]) + if items < 1 then + lv:AddLine(L.basket_empty) + elseif deliveryPrice > 0 then + local line = lv:AddLine(L.delivery, '', DarkRP.formatMoney(deliveryPrice)) + line.Columns[3]:SetContentAlignment(6) + end + total = total + deliveryPrice + + if lp:BankHas(total) then + butBuy:SetEnabled(true) + butBuy:SetText(L.order_for .. DarkRP.formatMoney(total)) + else + butBuy:SetEnabled(false) + butBuy:SetText(L.lacks .. DarkRP.formatMoney(total - BraxBank.PlayerMoney(lp))) + end + + cbReceiver:Clear() + local resetReceiver = true + for i, ply in ipairs(player.GetAll()) do + if FPP.Buddies[ply:SteamID()] ~= nil and ply:Alive() and not ply:GetNetVar('Ghost') and ply ~= lp then + cbReceiver:AddChoice(ply:Name(), ply, ply == receiver) + if ply == receiver then resetReceiver = false end + end + end + if resetReceiver then receiver = nil end + cbReceiver:AddChoice(L.yourself, nil, resetReceiver) + end + p_basket:UpdateData() + + function info_p:UpdateData(itemID, item) + if IsValid(info_p.preview) then info_p.preview:Remove() end + local pr + if item.model then + info_name:DockMargin(125, 5, 5, 0) + info_desc:DockMargin(125, 5, 5, 5) + pr = info_p:Add 'DAdjustableModelPanel' + pr:SetSize(117, 117) + pr:SetPos(1, 1) + pr:SetModel(item.model) + function pr:LayoutEntity(ent) end + + local ent = pr:GetEntity() + local mins, maxs = ent:GetModelBounds() + ent:SetSkin(item.skin or 0) + pr:SetCamPos(mins:Distance(maxs) * Vector(1, 0, 0)) + pr:SetFOV(50) + pr.aLookAngle = Angle(0, 45, 0) + pr.vCamPos = pr.Entity:OBBCenter() - pr.aLookAngle:Forward() * pr.vCamPos:Length() + else + info_name:DockMargin(80, 5, 5, 0) + info_desc:DockMargin(80, 5, 5, 5) + pr = info_p:Add 'DImage' + pr:SetSize(64, 64) + pr:SetPos(10, 10) + pr:SetCursor('none') + pr:SetImage(item.icon) + end + info_p.preview = pr + info_name:SetText(item.name) + info_desc:SetText(item.desc or defaultDesc) + + info_buy:SetText(not basket[itemID] and L.in_basket or L.alr_in_basket) + info_buy:SetEnabled(not basket[itemID]) + info_buy.DoClick = function(self) + self:SetEnabled(false) + self:SetText(L.alr_in_basket) + basket[itemID] = {item, 1} + p_basket:UpdateData() + end + end + + local p_shop = ps:Add 'DPanel' + p_shop:Dock(FILL) + p_shop:SetPaintBackground(false) + + local shopTree = p_shop:Add 'DTree' + shopTree:Dock(FILL) + shopTree:DockMargin(8,0,8,8) + function shopTree:OnNodeSelected(node) + if node.itemID then + info_p:UpdateData(node.itemID, node.item) + else + node:SetExpanded(not node.m_bExpanded) + end + end + function shopTree:DoRightClick(node) + if not node.itemID then return end + + local menu = DermaMenu() + menu:AddOption(L.add_in_basket, function() + Derma_StringRequest(L.add_in_basket, L.cost_product, 1, function(res) + local amount = tonumber(res) + if amount then + basket[node.itemID] = {node.item, math.Clamp((basket[node.itemID] and basket[node.itemID][2] or 0) + amount, 0, 1000)} + if amount == 0 then basket[node.itemID] = nil end + end + p_basket:UpdateData() + end) + end):SetIcon('icon16/add.png') + + if basket[node.itemID] then + menu:AddOption(L.delete_from_basket, function() + basket[node.itemID] = nil + p_basket:UpdateData() + end):SetIcon('icon16/delete.png') + end + + if node.item.model then + menu:AddOption(L.preview, function() + openPreview(node.item) + end):SetIcon('icon16/zoom.png') + end + + menu:Open() + end + + function shopTree:UpdateData(q) + self:Clear() + if not shopcache then + local catNode = self:AddNode(L.loading) + catNode:SetIcon('octoteam/icons/sandwatch.png') + return + end + + local data = shopcache + if q and q ~= '' then + data = table.Copy(data) + for cat, items in pairs(data) do + for itemID, item in pairs(items) do + if not utf8.lower(item.name):find(utf8.lower(q)) then + items[itemID] = nil + end + end + end + end + + local addedTotal = 0 + for cat, items in pairs(data) do + local catData = octoinv.shopCats[cat] + local catNode = self:AddNode(catData.name) + catNode:SetIcon(catData.icon) + + local added = 0 + for itemID, item in SortedPairsByMemberValue(items, 'name') do + local itemNode = catNode:AddNode(item.name .. ' - ' .. DarkRP.formatMoney(item.price)) + itemNode:SetIcon(item.icon) + -- itemNode.Columns[2]:SetContentAlignment(6) + itemNode.itemID = itemID + itemNode.item = item + added = added + 1 + addedTotal = addedTotal + 1 + end + if added < 1 then + catNode:Remove() + elseif q and q ~= '' then + catNode:SetExpanded(true, true) + end + end + + if addedTotal < 1 then + local catNode = self:AddNode(L.not_found) + catNode:SetIcon('octoteam/icons/error.png') + end + end + + local search = p_shop:Add 'DTextEntry' + search:Dock(TOP) + search:DockMargin(13,5,13,10) + search:SetTall(15) + search:SetTooltip(L.search_or_filter) + search:SetUpdateOnType(true) + search.PaintOffset = 5 + search:SetPlaceholderText(L.search_hint) + function search:OnValueChange(val) + shopTree:UpdateData(val) + end + shopTab = ps:AddSheet(L.shop, p_shop, 'octoteam/icons/shop.png') + shopTab.Tab.Image:SetSize(16, 16) + shopTab.Tab:InvalidateLayout() + + local p_preset = ps:Add 'DPanel' + p_preset:Dock(FILL) + p_preset:SetPaintBackground(false) + + local presetTree = p_preset:Add 'DTree' + presetTree:Dock(FILL) + presetTree:DockMargin(8,0,8,5) + function presetTree:OnNodeSelected(node) + if node.presetID then + node:SetExpanded(not node.m_bExpanded) + end + end + + local function fldRename(old, new) + for _, preset in ipairs(presetcache) do + if (preset.folder or 'Несортированные') == old then + preset.folder = new + changed = true + end + end + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + presetTree:UpdateData() + end + + local function fldRemove(name) + local toRem = {} + for id,preset in ipairs(presetcache) do + if (preset.folder or 'Несортированные') == name then toRem[#toRem+1] = id end + end + for i = #toRem, 1, -1 do + table.remove(presetcache, toRem[i]) + changed = true + end + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + presetTree:UpdateData() + end + + local function fldExport(name) + local items = {} + for _,preset in ipairs(presetcache) do + if (preset.folder or 'Несортированные') == name then items[#items+1] = preset end + end + for _,item in ipairs(items) do item.folder = nil end + local ans = {} + ans.folder, ans.items = name, items + SetClipboardText(pon.encode(ans)) + octolib.notify.show('hint', 'Папка скопирована, теперь ты можешь вставить ее куда-нибудь') + end + + function presetTree:DoRightClick(node) + if node.isFolder then + local thisFld = node:GetText() + + local menu = DermaMenu() + menu:AddOption('Переименовать', function() + Derma_StringRequest('Переименование', 'Укажи новое название папки', thisFld, function(name) + if (name == thisFld) or (name == '') then return end + if foldercache[name] then + Derma_Query('Папка с таким именем уже существует. Объединить содержимое?', 'Существующий каталог', + 'Да', function() fldRename(thisFld, name) end, 'Нет') + else fldRename(thisFld, name) end + end, nil, 'Переименовать', 'Отмена') + end):SetIcon('icon16/pencil.png') + + if table.Count(foldercache) > 1 then + local move, moveOpt = menu:AddSubMenu('Переместить содержимое') + moveOpt:SetIcon('icon16/door_in.png') + for fld in pairs(foldercache) do + if fld ~= thisFld then move:AddOption(fld, function() fldRename(thisFld, fld) end):SetIcon('icon16/folder.png') end + end + end + + menu:AddOption('Экспорт папки', function() fldExport(thisFld) end):SetIcon('icon16/page_go.png') + + menu:AddOption('Удалить папку', function() + Derma_Query('Точно хочешь удалить папку?\nЭто действие нельзя отменить', 'Удаление папки', 'Да', function() fldRemove(thisFld) end, 'Нет') + end):SetIcon('icon16/delete.png') + + menu:Open() + end + if not node.presetID then return end + + local menu = DermaMenu() + if not node.invalid then + menu:AddOption(L.add_in_basket, function() + Derma_StringRequest(L.add_in_basket, L.how_much_kits, 1, function(res) + local pAmount = tonumber(res) + if pAmount and shopcache then + pAmount = math.Clamp(pAmount, 1, 100) + + local itemData = {} + for cat, items in pairs(shopcache) do + for itemID, item in pairs(items) do + itemData[itemID] = item + end + end + + local preset = presetcache[node.presetID] + for itemID, amount in pairs(preset.items) do + local item = itemData[itemID] + if item then + if basket[itemID] then + basket[itemID][2] = basket[itemID][2] + amount * pAmount + else + basket[itemID] = {item, amount * pAmount} + end + end + end + end + p_basket:UpdateData() + end) + end):SetIcon('icon16/add.png') + + end + + local move,moveOpt = menu:AddSubMenu('Переместить') + local curFld = presetcache[node.presetID].folder or 'Несортированные' + moveOpt:SetIcon('icon16/door_in.png') + move:AddOption('Новая папка', function() + Derma_StringRequest('Создание новой папки', 'Укажи название папки', curFld, function(folder) + if (folder == curFld) or (folder == '') then return end + presetcache[node.presetID].folder = folder + changed = true + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + presetTree:UpdateData() + end) + end):SetIcon('icon16/folder_add.png') + for fld in pairs(foldercache) do + if fld ~= curFld then + move:AddOption(fld, function() + presetcache[node.presetID].folder = fld + changed = true + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + presetTree:UpdateData() + end):SetIcon('icon16/folder.png') + end + end + + menu:AddOption(L.rename, function() + Derma_StringRequest(L.rename, L.rename_kit, presetcache[node.presetID].name, function(name) + if (name == presetcache[node.presetID].name) or (name == '') then return end + presetcache[node.presetID].name = name + changed = true + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + presetTree:UpdateData() + end, nil, L.rename, L.cancel) + end):SetIcon('icon16/pencil.png') + + menu:AddOption(L.export_kit, function() + SetClipboardText(pon.encode(presetcache[node.presetID])) + octolib.notify.show('hint', L.export_kit_hint) + end):SetIcon('icon16/page_go.png') + + menu:AddOption(L.delete_kit, function() + table.remove(presetcache, node.presetID) + changed = true + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + + presetTree:UpdateData() + end):SetIcon('icon16/delete.png') + + menu:Open() + end + + local function update(tree, presets, itemData) + + foldercache = {} + for presetID, preset in ipairs(presets) do + local folderName = preset.folder or 'Несортированные' + local folder = nil + if not foldercache[folderName] then + folder = tree:AddNode(folderName) + folder.isFolder = true + foldercache[folderName] = folder + else folder = foldercache[folderName] end + local presetNode = folder:AddNode(preset.name) + + local price = 0 + for itemID, amount in pairs(preset.items) do + local item = itemData[itemID] + if not item then + item = { name = L.unavailable_item, icon = 'octoteam/icons/error.png', price = 0 } + presetNode.invalid = true + end + if amount <= 0 then + presetNode.invalid = true + end + + local itemNode = presetNode:AddNode(amount .. ' x ' .. item.name .. ' - ' .. DarkRP.formatMoney(item.price * amount)) + itemNode:SetIcon(item.icon) + price = price + item.price * amount + end + + if not presetNode.invalid then + presetNode:SetText(preset.name .. ' - ' .. DarkRP.formatMoney(price)) + presetNode:SetIcon('octoteam/icons/blueprint.png') + else + presetNode:SetText(preset.name .. L.octoinv_unavailable) + presetNode:SetIcon('octoteam/icons/error.png') + end + presetNode.presetID = presetID + end + + end + + + function presetTree:UpdateData() + self:Clear() + + local data = shopcache + if not data then + local catNode = self:AddNode(L.loading) + catNode:SetIcon('octoteam/icons/sandwatch.png') + return + end + + local presets + pcall(function() presets = pon.decode(file.Read('octoinv/shop_presets.dat', 'DATA')) end) + presetcache = presets or {} + + local itemData = {} + for cat, items in pairs(data) do + for itemID, item in pairs(items) do + itemData[itemID] = item + end + end + + if changed then + table.sort(presetcache, function(a, b) + return (a.folder or 'Несортированные')..'/'..a.name < (b.folder or 'Несортированные')..'/'..b.name + end) + changed = false + end + update(self, presetcache, itemData) + end + + local butPreset = bp2:Add 'DButton' + butPreset:Dock(LEFT) + butPreset:DockMargin(5,0,0,0) + butPreset:SetWide(80) + butPreset:SetText(L.in_kit) + butPreset.DoClick = function(self) + if table.Count(basket) < 1 then return end + presetcache = presetcache or {} + Derma_StringRequest(L.save_kit, L.give_title_kit, L.kit_hint .. #presetcache + 1, function(name) + local items = {} + for k,v in pairs(basket) do items[k] = v[2] end + table.insert(presetcache, 1, {name = name, items = items}) + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + + presetTree:UpdateData() + end) + end + + local tooltip = {} + local cbMethod = bp1:Add 'DComboBox' + cbMethod:Dock(LEFT) + cbMethod:SetWide(170) + cbMethod:DockMargin(0, 0, 5, 0) + cbMethod:SetSortItems(false) + for i, m in ipairs(methods) do + cbMethod:AddChoice(m[2], i, i == 1) + table.insert(tooltip, m[2] .. ': ' .. m[3]) + end + function cbMethod:OnSelect(i, text, val) + method = val + p_basket:UpdateData() + end + cbMethod:SetTooltip(table.concat(tooltip, string.char(10))) + + local presetButs = p_preset:Add 'DPanel' + presetButs:Dock(BOTTOM) + presetButs:SetTall(25) + presetButs:DockMargin(8, 0, 8, 8) + presetButs:SetPaintBackground(false) + + local butImport = presetButs:Add 'DButton' + butImport:Dock(RIGHT) + butImport:SetWide(95) + butImport:SetText(L.octoinv_import) + function butImport:DoClick() + Derma_StringRequest(L.octoinv_import, L.enter_code_export, '', function(s) + local _, data = pcall(pon.decode, s) + if not istable(data) then + print('SHOP IMPORT ERROR:', data) + return octolib.notify.show('warning', L.import_failure) + end + + if data.folder then + for _,item in ipairs(data.items) do + item.folder = data.folder + table.insert(presetcache, 1, item) + end + else table.insert(presetcache, 1, data) end + file.Write('octoinv/shop_presets.dat', pon.encode(presetcache)) + presetTree:UpdateData() + octolib.notify.show('hint', L.import_successful) + end, nil, L.ok, L.cancel) + end + + presetTab = ps:AddSheet(L.my_kits, p_preset, 'octoteam/icons/blueprint.png') + presetTab.Tab.Image:SetSize(16, 16) + presetTab.Tab:InvalidateLayout() + + function ps:OnActiveTabChanged(old, new) + if new == shopTab.Tab then + drawer:Open() + ps:DockMargin(0,0,0,115) + else + drawer:Close() + ps:DockMargin(0,0,0,0) + end + end + + local p_return = ps:Add 'DPanel' + p_return:SetPaintBackground(false) + + local returnSelected = {} + + local sp = p_return:Add 'DScrollPanel' + sp:Dock(FILL) + + local returnList = sp:Add 'DIconLayout' + returnList:Dock(FILL) + returnList:SetSpaceX(4) + returnList:SetSpaceY(4) + + local but_return = p_return:Add 'DButton' + but_return:SetSize(80, 25) + but_return:AlignRight(0) + but_return:AlignBottom(0) + but_return:SetText('Отправить') + but_return:SetEnabled(false) + + function but_return:DoClick() + self:SetEnabled(false) + netstream.Start('octoinv.return', table.GetKeys(returnSelected)) + end + + local function clickReturnItem(self) + local enable = not returnSelected[self.itemID] or nil + returnSelected[self.itemID] = enable + but_return:SetEnabled(table.Count(returnSelected) > 0) + self.selectedIcon:SetVisible(enable) + end + + function returnList:UpdateData() + self:Clear() + + for itemID, item in ipairs(returncache) do + if not item.class then + item.class = item[1] + item.amount = isnumber(item[2]) and item[2] or 1 + if istable(item[2]) then + for k, v in pairs(item[2]) do + item[k] = v + end + end + item[1], item[2] = nil, nil + end + local pnl = octoinv.createItemPanel(self, item) + pnl.itemID = itemID + pnl.DoClick = clickReturnItem + + local icon = pnl:Add 'DImage' + icon:SetMouseInputEnabled(false) + icon:SetImage('icon16/tick.png') + icon:SetSize(16, 16) + icon:AlignRight(4) + icon:AlignTop(2) + icon:SetVisible(false) + pnl.selectedIcon = icon + end + + self:InvalidateLayout() + self:InvalidateParent() + timer.Simple(0.2, function() if not IsValid(self) then return end self:InvalidateChildren() end) + end + + function p_return:PerformLayout() + but_return:AlignRight(0) + but_return:AlignBottom(0) + end + + ps:AddSheet('Возврат', p_return, octolib.icons.silk16('lorry')) + + f:SetDeleteOnClose(false) + f:SetVisible(false) + + function octoinv.requestUpdate() + + local function doit() + basket = {} + search:SetValue('') + p_basket:UpdateData() + + netstream.Start('octoinv.shoplist') + end + + if table.Count(basket) > 0 then + Derma_Query(L.basket_warning, L.refresh_shop, L.continue_hint, doit, L.cancel) + else + doit() + end + + end + + function octoinv.updateShop() + + p_basket:UpdateData() + if pendingUpdate then + if table.Count(basket) > 0 then + Derma_Query(L.something_change_shop, + L.refresh_shop, L.refresh, octoinv.requestUpdate, L.cancel) + else + octoinv.requestUpdate() + end + pendingUpdate = false + end + + end + + pendingUpdate = true + function octoinv.openShop() + + f:SetVisible(true) + f:MakePopup() + octoinv.updateShop() + + return f + + end + + netstream.Hook('octoinv.shop', function() + if octoinv.pnlShop:IsVisible() then + basket = {} + p_basket:UpdateData() + else + pendingUpdate = true + end + end) + + netstream.Hook('octoinv.shoplist', function(data) + shopcache = data + if not IsValid(ps) then return end + shopTree:UpdateData() + presetTree:UpdateData() + end) + + netstream.Hook('octoinv.return', function(data) + returncache = data or {} + table.Empty(returnSelected) + if not IsValid(ps) then return end + returnList:UpdateData() + end) + + octoinv.pnlShop = f + +end + +hook.Add('InitPostEntity', 'octoinv-shop.init', octoinv.createShop) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/shop/sv_control.lua b/garrysmod/addons/core-octoinv/lua/octoinv/shop/sv_control.lua new file mode 100644 index 0000000..4e534ba --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/shop/sv_control.lua @@ -0,0 +1,175 @@ +netstream.Hook('octoinv.shoplist', octoinv.syncShop) + +local orderID = 1 +netstream.Hook('octoinv.shop', function(ply, basket, methodID, receiver) + + if not IsValid(receiver) then receiver = ply end + + local override, why = hook.Run('octoinv.shop.order-override', ply, basket, methodID, receiver) + if override ~= nil then + if istable(override) then + if override.ply then ply = override.ply end + if override.basket then basket = override.basket end + if override.methodID then methodID = override.methodID end + if override.receiver then receiver = override.receiver end + else + ply:Notify('warning', why or L.can_not_order) + return + end + end + + local method = methodID and octoinv.deliveryMethods[methodID] + if not method then return end + + local sID = receiver:SteamID() + + local totalVolume, totalPrice, totalCount = octoinv.calcOrderPriceAndVolume(ply, basket) + if not totalVolume or totalPrice <= 0 then return ply:addExploitAttempt() end + + local deliveryPrice = ply:GetNetVar('os_delivery') and 0 or method.price + totalPrice = math.ceil(totalPrice * (1 + deliveryPrice)) + + if totalVolume > 250 or totalCount > 50 then return ply:Notify('warning', L.too_much_order) end + if not ply:BankHas(totalPrice) then return ply:Notify('warning', L.order_not_enough_money) end + + ply:BankAdd(-totalPrice) + ply:DoEmote(L.make_order_hint) + + local curOrderID = orderID + orderID = orderID + 1 + netstream.Start(ply, 'octoinv.shop', true) + if receiver == ply then + ply:Notify(L.order_accepted:format(curOrderID)) + else + ply:Notify(L.order_accepted2:format(curOrderID, receiver:Name())) + receiver:Notify(L.order_accepted3:format(ply:Name(), curOrderID)) + end + + local itemsPretty = {} + for itemID, amount in pairs(basket) do + local item = octoinv.shopItems[itemID] + local itemData = octoinv.items[item.item or itemID] + table.insert(itemsPretty, ('%d x %s'):format(amount, item.name or itemData.name or 'Неизвестно')) + end + + hook.Run('octoinv.shop.order', ply, receiver, itemsPretty, totalPrice, curOrderID) + + local time = method.time + timer.Simple(math.random(unpack(time)), function() + if not IsValid(receiver) then receiver = ply end + + local items = {} + for itemID, amount in pairs(basket) do + local item = octoinv.shopItems[itemID] + local itemData = octoinv.items[item.item or itemID] + + if itemData.nostack then + for _ = 1, amount do + items[#items + 1] = { item.item or itemID, item.data } + end + else + items[#items + 1] = { item.item or itemID, amount } + end + end + + local box = IsValid(receiver) and method.findBox(receiver) or NULL + box = octoinv.sendToMailbox(receiver:SteamID(), items, box) + if not IsValid(box) then + if IsValid(ply) then + ply:BankAdd(totalPrice) + ply:Notify('warning', (L.order_wrong_money_back):format(curOrderID)) + end + if receiver ~= ply and IsValid(receiver) then + receiver:Notify('warning', (L.order_wrong_money_back2):format(curOrderID)) + end + return + end + + local boxpos = box:GetPos() + local nearestEstate = dbgEstates.getNearest(boxpos) + local address = nearestEstate and nearestEstate.name or '???' + + if ply ~= receiver then + ply:Notify(L.order_went:format(curOrderID, address, receiver:Name())) + ply:AddMarker { + txt = (L.order_format):format(receiver:Name()), + pos = boxpos + Vector(0,0,40), + col = Color(255,92,38), + des = {'timedist', { 600, 100 }}, + icon = 'octoteam/icons-16/lorry.png', + } + end + + receiver:Notify(L.order_went2:format(curOrderID, address)) + receiver:AddMarker { + txt = L.delivery_order, + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'dist', { 100 }}, + icon = 'octoteam/icons-16/lorry.png', + } + + hook.Run('octoinv.shop.delivery', ply, receiver, curOrderID, box) + + timer.Create('octoinv.destroy' .. box:EntIndex() .. sID, 600, 1, function() + if not IsValid(bot) then return end + local inv = bot:GetInventory() + if not inv then return end + local cont = inv:GetContainer(sID) + if cont then + if cont:FreeSpace() ~= cont.volume then + if IsValid(ply) then ply:Notify(('Заказ #%s никто не забрал, поэтому вещи были выброшены'):format(curOrderID)) end + hook.Run('octoinv.shop.timeout', ply, receiver, curOrderID, box) + end + cont:Remove(true) + end + end) + end) + +end) + +netstream.Hook('octoinv.return', function(ply, itemIDs) + + if not istable(itemIDs) then return end + table.sort(itemIDs) -- to make remove loop safe + + local cache = ply:GetDBVar('return') + if not cache then return end + + local toSend = {} + for i = #itemIDs, 1, -1 do + local id = itemIDs[i] + if not cache[id] then continue end + toSend[#toSend + 1] = cache[id] + table.remove(cache, id) + end + + if #toSend < 1 then return end + + if #ents.FindByClass('octoinv_mailbox') < 1 then + return ply:Notify('На карте нет почтовых ящиков! Обратись к администрации') + end + + ply:SetDBVar('return', #cache > 0 and cache or nil) + octoinv.syncShop(ply) + + local itemsPretty = {} + for _, item in pairs(toSend) do + table.insert(itemsPretty, octoinv.itemStr(item)) + end + hook.Run('octoinv.shop.order', ply, ply, itemsPretty, 0, 'return') + + ply:Notify('Вещи будут доставлены в течение нескольких минут') + timer.Simple(math.random(60, 120), function() + local box = octoinv.sendToMailbox(ply, toSend) + ply:AddMarker({ + txt = 'Возврат вещей', + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'timedist', { 600, 100 }}, + icon = 'octoteam/icons-16/lorry.png', + }) + ply:Notify('Вещи со склада возврата прибыли в почтовый ящик') + end) + +end) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/shop/sv_shop.lua b/garrysmod/addons/core-octoinv/lua/octoinv/shop/sv_shop.lua new file mode 100644 index 0000000..604e4d8 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/shop/sv_shop.lua @@ -0,0 +1,246 @@ +octoinv.shopCats = octoinv.shopCats or {} +octoinv.shopItems = octoinv.shopItems or {} + +local catJobs, itemJobs +octoinv.recalcShopPerms = octolib.func.debounce(function() + + catJobs = {} + for catID, cat in pairs(octoinv.shopCats) do + if istable(cat.jobs) then + catJobs[catID] = {} + for i, v in ipairs(cat.jobs) do catJobs[catID][v] = true end + end + end + + itemJobs = {} + for itemID, item in pairs(octoinv.shopItems) do + if istable(item.jobs) then + itemJobs[itemID] = {} + for i, v in ipairs(item.jobs) do itemJobs[itemID][v] = true end + end + end + +end, 0) +octoinv.recalcShopPerms() + +function octoinv.addShopCat(id, data) + + if id then + octoinv.shopCats[id] = data + else + octoinv.shopCats[#octoinv.shopCats + 1] = data + end + +end + +function octoinv.addShopItem(id, data) + + if id then + octoinv.shopItems[id] = data + else + octoinv.shopItems[#octoinv.shopItems + 1] = data + end + + octoinv.recalcShopPerms() + +end + +function octoinv.sendToMailbox(sID, items, box) + + if not IsValid(box) then + box = table.Random(ents.FindByClass('octoinv_mailbox')) + end + if not IsValid(box) then return false end + + if not isstring(sID) then + sID = sID:SteamID() + end + + box:Deliver(sID, items) + return box + +end + +function octoinv.getNearestMailbox(ply) + + local plyPos = ply:GetPos() + local data = octolib.table.reduce(ents.FindByClass('octoinv_mailbox'), function(acc, ent) + local dist = ent:GetPos():DistToSqr(plyPos) + if dist < acc.dist then + return { dist = dist, box = ent } + else + return acc + end + end, { dist = math.huge }) + + return data.box, data.dist + +end + +function octoinv.expectShipment(ply, id, check) + + ply.pendingShipments = ply.pendingShipments or {} + ply.pendingShipments[id] = check + +end + +function octoinv.removeShipment(ply, id) + + if not IsValid(ply) or not ply.pendingShipments then return end + + ply.pendingShipments[id] = nil + if table.IsEmpty(ply.pendingShipments) then + ply.pendingShipments = nil + end + +end + +function octoinv.syncShop(ply) + + local catsCache = {} + local res = {} + for itemID, item in pairs(octoinv.shopItems) do + local itemData = octoinv.items[item.item or itemID] + if not itemData then print('Unknown item: ' .. itemID) continue end + + local catID = octoinv.shopCats[item.cat] and item.cat or '_other' + if catsCache[catID] == true then + continue + elseif catsCache[catID] ~= false then + catsCache[catID] = catJobs[catID] and not catJobs[catID][ply:getJobTable().command] -- category job restrictions + or octoinv.shopCats[catID].check and not octoinv.shopCats[catID].check(ply) -- category custom checks + if catsCache[catID] == true then continue end + end + + if itemJobs[itemID] and not itemJobs[itemID][ply:getJobTable().command] -- item job restrictions + or octoinv.shopItems[itemID].check and not octoinv.shopItems[itemID].check(ply) -- item custom checks + then continue end + + local price = isfunction(item.price) and item.price(ply) or item.price + res[catID] = res[catID] or {} + res[catID][itemID] = { + name = item.name or itemData.name, + desc = item.desc or itemData.desc, + icon = item.icon or itemData.icon, + model = item.model or item.data and item.data.model or itemData.model or nil, + plyMat = item.plyMat or itemData.plyMat, + skin = item.skin or itemData.skin, + price = price, + } + end + + netstream.Start(ply, 'octoinv.shoplist', res) + netstream.Start(ply, 'octoinv.return', ply:GetDBVar('return')) + +end + +function octoinv.calcOrderPriceAndVolume(ply, basket) + + local totalVolume, totalPrice, totalCount = 0, 0, 0 + local catsCache = {} + for itemID, amount in pairs(basket) do + amount = math.floor(amount) + basket[itemID] = amount + + local item = octoinv.shopItems[itemID] + if not item or amount <= 0 then + basket[itemID] = nil + continue + end + local itemData = octoinv.items[item.item or itemID] + + local catID = item.cat or '_other' + if catsCache[catID] == true then + continue + elseif catsCache[catID] ~= false then + catsCache[catID] = catJobs[catID] and not catJobs[catID][ply:getJobTable().command] -- category job restrictions + or octoinv.shopCats[catID].check and not octoinv.shopCats[catID].check(ply) -- category custom checks + if catsCache[catID] == true then + ply:Notify('warning', L.order_not_have_access .. '(' .. itemData.name .. ')') + return + end + end + + if itemJobs[itemID] and not itemJobs[itemID][ply:getJobTable().command] -- item job restrictions + or octoinv.shopItems[itemID].check and not octoinv.shopItems[itemID].check(ply) -- item custom checks + then + ply:Notify('warning', L.order_not_have_access .. '(' .. itemData.name .. ')') + return + end + + totalVolume = totalVolume + (item.data and item.data.volume or itemData.volume or 0) * amount + totalPrice = totalPrice + (item.price or 0) * amount + totalCount = totalCount + (itemData.nostack and amount or 1) + end + + return totalVolume, totalPrice, totalCount + +end + +octoinv.deliveryMethods = {{ + time = { 120, 180 }, + price = 0, + findBox = function(ply) + local boxes = ents.FindByClass('octoinv_mailbox') + for i, ent in RandomPairs(boxes) do + if ent:GetPos():DistToSqr(ply:GetPos()) > 9000000 then + return ent + end + end + return table.Random(boxes) + end, +}, { + time = { 30, 60 }, + price = 0.05, + findBox = function(ply) + local boxes = ents.FindByClass('octoinv_mailbox') + for i, ent in RandomPairs(boxes) do + if ent:GetPos():DistToSqr(ply:GetPos()) > 9000000 then + return ent + end + end + return table.Random(boxes) + end, +}, { + time = { 60, 120 }, + price = 0.2, + findBox = function(ply) + local curDist, box = math.huge + for i, ent in ipairs(ents.FindByClass('octoinv_mailbox')) do + local dist = ent:GetPos():DistToSqr(ply:GetPos()) + if dist < curDist and dist > 250000 then + curDist = dist + box = ent + end + end + return box + end, +}} + +function octoinv.addReturnItems(steamID, items) + + if not isstring(steamID) then + -- arg is player entity + steamID = steamID:SteamID() + end + + octolib.getDBVar(steamID, 'return', {}):Then(function(cache) + for _, item in ipairs(items) do + cache[#cache + 1] = item + end + octolib.setDBVar(steamID, 'return', cache) + + local ply = player.GetBySteamID(steamID) + if IsValid(ply) then + octoinv.syncShop(ply) + end + end) + +end + +local function queueClientUpdate(ply) + + netstream.Start(ply, 'octoinv.shop', true) + +end +hook.Add('PlayerSpawn', 'octoinv.shop', queueClientUpdate) diff --git a/garrysmod/addons/core-octoinv/lua/octoinv/sv_tests.lua b/garrysmod/addons/core-octoinv/lua/octoinv/sv_tests.lua new file mode 100644 index 0000000..9f780e7 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/octoinv/sv_tests.lua @@ -0,0 +1,56 @@ +local ent, bot + +local entityTests = { + { + name = 'Create entity', + run = function(finish) + ent = ents.Create('prop_physics') + ent:SetModel('models/chairs/armchair.mdl') + ent:SetPos(Vector(0,0,30)) + ent:Spawn() + if not IsValid(ent) then return finish('Entity is not valid') end + + finish() + end, + }, { + name = 'Import inventory', + run = function(finish) + ent:ImportInventory({ cont = { name = 'Container', volume = 50 }}) + finish() + end, + }, { + name = 'Add items', + run = function(finish) + ent:AddItem('money', 1000) + ent:AddItem('souvenir', { name = 'OctoRP', volume = 40 }) + finish() + end, + }, { + name = 'Check given items', + run = function(finish) + if ent:HasItem('money') ~= 1000 then return finish('Money amount mismatch') end + if ent:FindItem({ class = 'souvenir' }):GetData('volume') ~= 40 then return finish('Item volume mismatch') end + if ent:FindItem({ class = 'souvenir' }):GetData('name') ~= 'OctoRP' then return finish('Item name mismatch') end + finish() + end, + }, { + name = 'Remove entity', + run = function(finish) + ent:GetInventory():Remove() + if ent:GetInventory() ~= nil then return finish('Inventory not removed') end + + ent:Remove() + finish() + end, + }, +} + +octolib.registerTests({ + name = 'octoinv', + children = { + { + name = 'Entities', + children = entityTests, + }, + }, +}) diff --git a/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/cl_init.lua b/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/cl_init.lua new file mode 100644 index 0000000..38d752f --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/cl_init.lua @@ -0,0 +1,4 @@ +include 'shared.lua' + +SWEP.SecondaryAttack = octolib.func.zero +SWEP.Reload = octolib.func.zero diff --git a/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/init.lua b/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/init.lua new file mode 100644 index 0000000..e63bace --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/init.lua @@ -0,0 +1,120 @@ +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' +include 'shared.lua' + +SWEP.SecondaryAttack = octolib.func.zero +SWEP.Reload = octolib.func.zero + +function SWEP:Initialize() + + self.collectorID = 'pickaxe' + + if self.collectorID then + self:SetCollectorID(self.collectorID) + else + self:SetCollector({ + hold = 'melee2', + interval = 1.5, + delay = 0.15, + worldModel = { + model = 'models/weapons/hl2meleepack/w_pickaxe.mdl', + pos = Vector(-1.9, -0.6, -0.7), + ang = Angle(168.6, -100.5, -4), + scale = 0.8 + }, + }) + end + + self.BaseClass.Initialize(self) + +end + +function SWEP:Deploy() + + self.BaseClass.Deploy(self) + + local ct = CurTime() + local interval = self.Collector.interval or 1 + self:SetNextPrimaryFire(ct + interval) + self:SetNextSecondaryFire(ct + interval) + +end + +function SWEP:SetCollectorID(id) + + local collector = octoinv.collectors[id] + if not collector then return end + + self:SetCollector(collector) + +end + +function SWEP:SetCollector(collector) + + if collector.worldModel then + local worldModel = collector.worldModel + self:SetWorldModel(worldModel.model, worldModel) + end + + self.Collector = collector + self.collectorID = collector.id + self:SetNetVar('Collector', self.Collector) + + self.health = self.health or collector.health + + self:SetHoldType(collector.hold or 'melee2') + +end + +function SWEP:TryCollect() + + local owner = self:GetOwner() + + local tr = {} + tr.start = owner:GetShootPos() + tr.endpos = tr.start + owner:GetAimVector() * 100 + tr.filter = owner + tr = util.TraceLine(tr) + + if not tr.Hit then return end + -- self:FireBullets({ + -- Src = tr.HitPos + tr.HitNormal, + -- Dir = -tr.HitNormal, + -- Damage = 2, + -- Force = 5, + -- Distance = 5, + -- }) + + if self.Collector.sounds then + local data = table.Random(self.Collector.sounds) + if not istable(data) then data = { data } end + sound.Play(data[1], tr.HitPos, unpack(data, 2)) + end + + local ent = tr.Entity + if IsValid(ent) and ent:GetClass() == 'octoinv_collectable' and table.HasValue(ent.Collectable.collectors, self.Collector.id) then + ent:Collect(owner, self, tr) + end + + self.health = (self.health or 1) - 1 + if self.health <= 0 then + self:EmitSound('physics/wood/wood_box_impact_hard' .. math.random(1, 3) .. '.wav', 75, 100, 0.8) + self:Remove() + + local owner = self:GetOwner() + timer.Simple(0, function() + owner:SelectWeapon('dbg_hands') + end) + end + +end + +hook.Add('dbg-weapons.getItemData', 'octoinv.collect', function(wep) + if wep:GetClass() ~= 'octoinv_collector' then return end + + return table.Merge(table.Copy(wep.itemData or {}), { + class = 'collector', + collector = wep.collectorID, + health = wep.health, + }) +end) diff --git a/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/shared.lua b/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/shared.lua new file mode 100644 index 0000000..2837789 --- /dev/null +++ b/garrysmod/addons/core-octoinv/lua/weapons/octoinv_collector/shared.lua @@ -0,0 +1,40 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'octolib_custom' +SWEP.Category = 'octoinv' +SWEP.PrintName = 'Инструмент для сбора' +SWEP.Instructions = '' +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.ViewModel = '' +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.DrawCrosshair = true +SWEP.HoldType = 'melee2' + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +function SWEP:PrimaryAttack() + + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:SetNextPrimaryFire(CurTime() + (self:GetNetVar('Collector').interval or 1)) + + if SERVER then + timer.Create('collector.tryCollect_' .. self:EntIndex(), self.Collector.delay or 0, 1, function() + if not IsValid(self) then return end + self:TryCollect() + end) + end + +end diff --git a/garrysmod/addons/core-octoshop/lua/autorun/octoshop.lua b/garrysmod/addons/core-octoshop/lua/autorun/octoshop.lua new file mode 100644 index 0000000..d48bf63 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/autorun/octoshop.lua @@ -0,0 +1,77 @@ +------------------------------------------ +-- +-- OCTOSHOP by Octothorp Team +-- +-- Designed to be used in my own projects. +-- Do not offer me to sell this addon. +-- +-- Developed and maintained by chelog, +-- telegram: t.me/chelog +-- discord: chelog#8888 +-- mail: chelog@octothorp.team +-- +------------------------------------------ + +octoshop = octoshop or {} +octoshop.debug = true + +function octoshop.msg(txt) + print('[# SHOP] ' .. os.date('%H:%M:%S - ', os.time()) .. txt) +end + +function octoshop.debugmsg(txt) + if not octoshop.debug then return end + print('[# SHOP] ' .. os.date('%H:%M:%S - ', os.time()) .. txt) +end + +-- load dependencies +if SERVER then + require('mysqloo') + + if not mysqloo then + local divider = ('='):rep(20) + octoshop.msg(divider) + octoshop.msg('ERROR: Could not detect mysqloo module! Aborting.') + octoshop.msg(divider) + return + end + + if not mysqloo.CreateDatabase then include('lib/mysqloolib.lua') end + if not mysqloo.CreateDatabase then + local divider = ('='):rep(20) + octoshop.msg(divider) + octoshop.msg('ERROR: Could not detect mysqloolib! Aborting.') + octoshop.msg(divider) + return + end +end + +-- load files +if SERVER then + include('config/octoshop_sv.lua') +end + +local shConfig = 'config/octoshop_sh.lua' +if file.Exists(shConfig, 'LUA') then + if SERVER then AddCSLuaFile(shConfig) end + include(shConfig) +end + +local fs, _ = file.Find('octoshop/*.lua', 'LUA') +for _, f in pairs(fs) do + fname = 'octoshop/' .. f + if SERVER and f:sub(1,3) == 'sv_' then + include(fname) + elseif f:sub(1,3) == 'cl_' then + if SERVER then AddCSLuaFile(fname) end + if CLIENT then include(fname) end + elseif f:sub(1,3) == 'sh_' then + if SERVER then AddCSLuaFile(fname) end + include(fname) + else + octoshop.msg('Unknown file type: ' .. fname .. ', ignoring.') + end +end + +-- initialize +octoshop.init() diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/cl_octoshop.lua b/garrysmod/addons/core-octoshop/lua/octoshop/cl_octoshop.lua new file mode 100644 index 0000000..03ea8e6 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/cl_octoshop.lua @@ -0,0 +1,787 @@ +octoshop.items = {} + +surface.CreateFont('octoshop.xtitle', { + font = 'Calibri', + extended = true, + size = 29, + weight = 350, +}) + +surface.CreateFont('octoshop.title', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +surface.CreateFont('octoshop.name', { + font = 'Arial Bold', + extended = true, + size = 17, + weight = 350, +}) + +surface.CreateFont('octoshop.xprice', { + font = 'Calibri', + extended = true, + size = 32, + weight = 500, +}) + +surface.CreateFont('octoshop.price', { + font = 'Arial', + extended = true, + size = 14, + weight = 350, +}) + +local function ease( t, b, c, d ) + + t = t / d; + return -c * t * (t - 2) + b + +end + +-- +-- PANEL EXTENSION +-- + +surface.CreateFont('dbg-util.tag', { + font = 'Calibri', + extended = true, + size = 18, + weight = 350, +}) + +surface.CreateFont('dbg-util.tag-sh', { + font = 'Calibri', + extended = true, + size = 40, + weight = 350, + blursize = 10, +}) + +local meta = FindMetaTable 'Panel' + +local function paintHint(self, w, h) + + surface.DisableClipping(true) + + local al = self.anim + surface.SetFont('dbg-util.tag') + local tw, th = surface.GetTextSize(self.hint) + local x, y = w / 2, -16 + + self.shText = self.shText or ('|'):rep(math.floor((tw+30)/15)) + draw.SimpleText(self.shText, 'dbg-util.tag-sh', x, y, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + draw.RoundedBox(4, (w-tw-14) / 2, y - 10, tw + 14, 22, Color(170,119,102, al*255)) + draw.SimpleText(self.hint, 'dbg-util.tag', x, y, Color(255,255,255, al*255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText('u', 'marlett', x, y + 6, Color(170,119,102, al*255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + surface.DisableClipping(false) + +end + +local function thinkHint(self) + + self.anim = math.Approach( self.anim, self.Hovered and 1 or 0, FrameTime() / 0.1 ) + +end + +function meta:AddOctoHint(text) + + self.realPaint = self.realPaint or self.Paint or function() end + self.realThink = self.realThink or self.Think or function() end + self.realOnCursorEntered = self.realOnCursorEntered or self.OnCursorEntered or function() end + self.realOnCursorExited = self.realOnCursorExited or self.OnCursorExited or function() end + + self.anim = 0 + self.hint = text or 'Hint text' + + self.Paint = function(self, w, h) + if self.anim > 0 then paintHint(self, w, h) end + self:realPaint(w, h) + end + + self.Think = function(self) + self:realThink() + thinkHint(self) + end + + self.OnCursorEntered = function(self) + self:realOnCursorEntered() + self:SetDrawOnTop(true) + end + self.OnCursorExited = function(self) + self:realOnCursorExited() + self:SetDrawOnTop(false) + end + +end + +-- +-- MENU CODE +-- + +local icons = { + active = Material(octolib.icons.silk16('tick')), + equipped = Material(octolib.icons.silk16('lightbulb')), + unequipped = Material(octolib.icons.silk16('lightbulb_off')), + expire = Material(octolib.icons.silk16('hourglass')), +} + +local function paintIcon(self, w, h) + + surface.SetDrawColor(255,255,255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w/2 - 8, h/2 - 8, 16, 16) + +end + +local function paintInvItem(self, w, h) + + local strokeCol = self.Hovered and self.col or Color(220,220,220) + draw.RoundedBox(4, 0, 0, w, h, strokeCol) + + local bgCol = self.Depressed and Color(238,238,238) or color_white + draw.RoundedBox(4, 1, 1, w-2, h-2, bgCol) + + surface.SetDrawColor(255,255,255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w/2 - 32, 26, 64, 64) + + surface.SetFont('octoshop.name') + local tw, th = surface.GetTextSize(self.name) + local x = 0 + if tw > w-16 and self.Hovered then + x = (-math.cos((CurTime() - self.animStart) * 1.5) + 1) / 2 * (w-16 - tw) + end + draw.DrawText(self.name, 'octoshop.name', x + 8, h-23, Color(80,80,80), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.RoundedBox(0, w-1, h-25, 1, 20, strokeCol) + draw.RoundedBox(0, 0, h-25, 1, 20, strokeCol) + draw.RoundedBox(0, w-2, h-25, 1, 20, bgCol) + draw.RoundedBox(0, 1, h-25, 1, 20, bgCol) + +end + +local function paintShopItem(self, w, h) + + local strokeCol = self.Hovered and self.col or Color(220,220,220) + draw.RoundedBox(4, 0, 0, w, h, strokeCol) + + local bgCol = self.Depressed and Color(238,238,238) or color_white + draw.RoundedBox(4, 1, 1, w-2, h-2, bgCol) + draw.RoundedBox(0, 1, h-51, w-2, 1, Color(220,220,220)) + + surface.SetDrawColor(255,255,255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w/2 - 32, 26, 64, 64) + + draw.DrawText(octoshop.formatMoney(self.price), 'octoshop.price', w-8, h-21, Color(30,30,30), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + + surface.SetFont('octoshop.name') + local tw, th = surface.GetTextSize(self.name) + local x = 0 + if tw > w-16 and self.Hovered then + x = (-math.cos((CurTime() - self.animStart) * 1.5) + 1) / 2 * (w-16 - tw) + end + draw.DrawText(self.name, 'octoshop.name', x + 8, h-45, Color(80,80,80), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.RoundedBox(0, w-1, h-50, 1, 45, strokeCol) + draw.RoundedBox(0, 0, h-50, 1, 45, strokeCol) + draw.RoundedBox(0, w-2, h-50, 1, 45, bgCol) + draw.RoundedBox(0, 1, h-50, 1, 45, bgCol) + +end + +local function createPanel() + +if IsValid(octoshop.pnl) then octoshop.pnl:Remove() end +local p = vgui.Create 'DPropertySheet' +octoshop.pnl = p + +p:SetSize(400,600) +p:Center() +p:SetSkin('Default') + +local moneyMat = Material(octolib.icons.silk16('coins')) +p.balance = p:Add 'DLabel' +p.balance:SetSize(24,20) +p.balance:SetText(L.loading) +p.balance:SetTextColor(Color(255,255,255)) +p.balance:SizeToContentsX(0) +p.balance:AlignRight(85) +p.balance:AlignTop(1) +p.balance.Paint = function(self,w,h) + surface.DisableClipping(true) + draw.RoundedBoxEx(4, -25,0, w+45,h, Color(59,59,59), true, false, false, false) + draw.RoundedBoxEx(4, -24,1, w+43,h, Color(156,160,164), true, false, false, false) + + surface.SetDrawColor(255,255,255) + surface.SetMaterial(moneyMat) + surface.DrawTexturedRect(-20,3,16,16) + + surface.DisableClipping(false) +end + +p.rf = p:Add 'DButton' +p.rf:SetSize(55,20) +p.rf:SetText(L.menu) +p.rf:AlignRight(24) +p.rf:AlignTop(1) +p.rf.DoClick = function(self) + local m = DermaMenu() + + m:AddOption(L.top_up_balance, function() + if isfunction(octoshop.openTopup) then + octoshop.openTopup(self, p) + else + gui.OpenURL('https://octothorp.team/shop/chips?steamid=' .. LocalPlayer():SteamID()) + end + end):SetIcon('icon16/money_add.png') + + m:AddOption(L.apply_coupon, function() + Derma_StringRequest(L.apply_coupon, L.write_coupon, '', function(s) + net.Start('octoshop.useCoupon') + net.WriteString(s) + net.SendToServer() + end, function() end, L.apply, L.cancel) + end):SetIcon('icon16/tag_green.png') + + m:AddOption(L.refresh_shop, function() + net.Start('octoshop.rInventory') net.SendToServer() + end):SetIcon('icon16/arrow_rotate_clockwise.png') + + if octoshop.checkOwner(LocalPlayer()) then + m:AddSpacer() + + m:AddOption(L.create_coupon, function() + Derma_StringRequest(L.create_coupon, L.create_hint, '', function(s) + RunConsoleCommand('octoshop_coupon_create', 'balance', s) + end, function() end, L.create, L.cancel) + end):SetIcon('icon16/tag_blue_add.png') + end + + m:Open() +end + +p.cl = p:Add 'DButton' +p.cl:SetSize(24,20) +p.cl:AlignRight(4) +p.cl:AlignTop(1) +p.cl:SetText('') +p.cl:SetImage('icon16/cross.png') +function p.cl:DoClick() + p:Hide() +end + +local tShop = vgui.Create 'DPanel' +tShop:Dock(FILL) +tShop:DockMargin(8, 0, 8, 8) +p:AddSheet(L.shop, tShop, octolib.icons.silk16('cart')) +p.shop = tShop + +local tInv = vgui.Create 'DPanel' +tInv:Dock(FILL) +tInv:DockMargin(8, 0, 8, 8) +p:AddSheet(L.inventory, tInv, octolib.icons.silk16('box_front_open')) +p.inv = tInv + +function p:Show(closeCallback) + + net.Start('octoshop.rInventory') net.SendToServer() + + if isfunction(closeCallback) then self.closeCallback = closeCallback end + self:SetVisible(true) + self:MakePopup() + +end + +function p:Hide() + + self:SetVisible(false) + if isfunction(self.closeCallback) then + self:closeCallback() + self.closeCallback = nil + end + +end + +local textEmpty = L.textEmpty + +local catCol = Color(0,0,0, 80) +local function paintCategory(self, w, h) + + draw.RoundedBox(0, 0, 0, w, 30, catCol) + +end + +function p:UpdateInventory(data) + + local t = p.inv + t:Clear() + + if not istable(data) or #data < 1 then + local lbl = t:Add 'DLabel' + lbl:Dock(FILL) + lbl:SetContentAlignment(5) + lbl:SetTextColor(Color(120,120,120)) + lbl:SetFont('octoshop.title') + lbl:SetText(textEmpty[math.random(#textEmpty)]) + else + local sp = t:Add 'DScrollPanel' + sp:Dock(FILL) + + local cats = {} + for i, item in ipairs(data) do + local class = octoshop.getClassTable(item.class) + + local cat = octoshop.getClassTable(item.class).cat or L.other + local l = cats[cat] + if not l then + local c = sp:Add 'DCollapsibleCategory' + c:SetWide(400) + c:Dock(TOP) + c:SetLabel(cat) + c:SetExpanded(true) + c.Header:SetFont('octoshop.title') + c.Header:SetTall(30) + c.Paint = paintCategory + + l = vgui.Create 'DIconLayout' + l:DockMargin(5,5,5,5) + l:DockPadding(0,0,0,10) + l:SetSpaceX(5) + l:SetSpaceY(5) + + c:SetContents(l) + local oldLayout = c.PerformLayout + function c:PerformLayout() + if not self.manualLayout then + for k, v in pairs(cats) do + v.manualLayout = true + v:InvalidateLayout() + end + else + v.manualLayout = nil + end + + oldLayout(self) + l:InvalidateLayout(true) + for i, v in ipairs(l:GetChildren()) do + v:InvalidateLayout() + end + end + + cats[cat] = l + end + + local ip = l:Add 'DButton' + ip:SetSize(116, 135) + ip:SetText('') + ip.icon = item.data and item.data.icon or class.icon + ip.name = item.data and item.data.name or class.name + ip.col = class.color + ip.Paint = paintInvItem + function ip:PerformLayout() + self:SetWide((sp.pnlCanvas:GetWide() - 20)/3) + end + function ip:OnCursorEntered() + self.animStart = CurTime() + end + function ip:DoClick() + local m = DermaMenu() + if item.canUse then + m:AddOption(L.active, function() + net.Start('octoshop.action' ) + net.WriteUInt(item.id, 32) + net.WriteString('use') + net.SendToServer() + end):SetIcon(octolib.icons.silk16('box_open')) + end + if item.canEquip then + m:AddOption(L.equip, function() + net.Start('octoshop.action' ) + net.WriteUInt(item.id, 32) + net.WriteString('equip') + net.SendToServer() + end):SetIcon('icon16/lightbulb.png') + end + if item.canUnequip then + m:AddOption(L.unequip, function() + net.Start('octoshop.action' ) + net.WriteUInt(item.id, 32) + net.WriteString('unequip') + net.SendToServer() + end):SetIcon('icon16/lightbulb_off.png') + end + if item.canTrade then + local plys = player.GetAll() + table.sort(plys, function(a, b) + return a:Name() < b:Name() + end) + + local sm, pm = m:AddSubMenu(L.write_give) + pm:SetIcon(octolib.icons.silk16('arrow_right')) + + for i, ply in ipairs(plys) do + if ply ~= LocalPlayer() then + sm:AddOption(ply:Name(), function() + net.Start('octoshop.action' ) + net.WriteUInt(item.id, 32) + net.WriteString('trade') + net.WriteEntity(ply) + net.SendToServer() + end) + end + end + end + if item.canSell then + m:AddSpacer() + m:AddOption(L.sell, function() + net.Start('octoshop.action' ) + net.WriteUInt(item.id, 32) + net.WriteString('sell') + net.SendToServer() + end):SetIcon('icon16/money_delete.png') + end + m:Open() + end + + local iconNum = 0 + if item.active then + local sip = vgui.Create 'DPanel' + sip:SetParent(ip) + sip:SetSize(20,20) + sip:AlignTop(4) + sip:AlignRight(20 * iconNum) + sip.icon = icons.active + sip.Paint = paintIcon + sip:AddOctoHint(L.actived) + + iconNum = iconNum + 1 + end + + if item.canEquip and not item.equipped then + local sip = vgui.Create 'DPanel' + sip:SetParent(ip) + sip:SetSize(20,20) + sip:AlignTop(4) + sip:AlignRight(20 * iconNum) + sip.icon = icons.unequipped + sip.Paint = paintIcon + sip:AddOctoHint(L.not_using) + + iconNum = iconNum + 1 + elseif item.equipped then + local sip = vgui.Create 'DPanel' + sip:SetParent(ip) + sip:SetSize(20,20) + sip:AlignTop(4) + sip:AlignRight(20 * iconNum) + sip.icon = icons.equipped + sip.Paint = paintIcon + sip:AddOctoHint(L.using) + + iconNum = iconNum + 1 + end + + if item.expire then + local sip = vgui.Create 'DPanel' + sip:SetParent(ip) + sip:SetSize(20,20) + sip:AlignTop(4) + sip:AlignRight(20 * iconNum) + sip.icon = icons.expire + sip.Paint = paintIcon + sip:AddOctoHint(os.date(L.expire, item.expire)) + + iconNum = iconNum + 1 + end + end + end + +end + +function p:UpdateShop() + + local t = p.shop + t:Clear() + + local sp = t:Add 'DScrollPanel' + sp:Dock(FILL) + + local data = {} + for class, item in pairs(octoshop.items) do + local i = table.Copy(item) + i.class = class + table.insert(data, i) + end + + table.sort(data, function(a, b) + if a.order and b.order and a.order ~= b.order then + return a.order < b.order + else + return a.price < b.price + end + end) + + local cats = {} + for i, item in ipairs(data) do + if not item.hidden then + local cat = octoshop.getClassTable(item.class).cat or L.other + local l = cats[cat] + if not l then + local c = sp:Add 'DCollapsibleCategory' + c:SetWide(400) + c:Dock(TOP) + c:SetLabel(cat) + c:SetExpanded(true) + c.Header:SetFont('octoshop.title') + c.Header:SetTall(30) + c.Paint = paintCategory + + l = vgui.Create 'DIconLayout' + l:DockMargin(5,5,5,5) + l:DockPadding(0,0,0,10) + l:SetSpaceX(5) + l:SetSpaceY(5) + + c:SetContents(l) + local oldLayout = c.PerformLayout + function c:PerformLayout() + if not self.manualLayout then + for k, v in pairs(cats) do + v.manualLayout = true + v:InvalidateLayout() + end + else + v.manualLayout = nil + end + + oldLayout(self) + l:InvalidateLayout(true) + for i, v in ipairs(l:GetChildren()) do + v:InvalidateLayout() + end + end + + cats[cat] = l + end + + local ip = l:Add 'DButton' + ip:SetSize(116, 170) + ip:SetText('') + ip.icon = item.icon + ip.name = item.name + ip.col = item.color + ip.price = item.price + ip.Paint = paintShopItem + function ip:PerformLayout() + self:SetWide((sp.pnlCanvas:GetWide() - 20)/3) + end + function ip:OnCursorEntered() + self.animStart = CurTime() + end + function ip:DoClick() + octoshop.openShopItem(item.class) + end + end + end + +end + +p:Hide() +net.Start('octoshop.rShop') net.SendToServer() + +hook.Remove('Think', 'octoshop.init') +end + +function octoshop.init() + + hook.Add('Think', 'octoshop.init', createPanel) + octoshop.msg('Initialized.') + +end + +net.Receive('octoshop.rBalance', function(len, ply) + + octoshop.balance = net.ReadUInt(32) or 0 + octoshop.pnl.balance:SetText(octoshop.formatMoney(octoshop.balance)) + octoshop.pnl.balance:SizeToContentsX(0) + octoshop.pnl.balance:AlignRight(85) + +end) + +net.Receive('octoshop.rInventory', function(len) + + local data = net.ReadTable() + for i, item in ipairs(data) do + if item.data and item.data.icon then + item.data.icon = Material(item.data.icon) + end + end + + octoshop.pnl:UpdateInventory(data) + +end) + +function octoshop.getClassTable(class) + + return octoshop.items[class] or { + name = L.what_it, + cat = L.octoshop_break, + desc = L.octoshop_break_hint, + price = 0, + order = 999, + attributes = {}, + icon = Material('icon16/help.png', ''), + color = Color(102,170,170), + canBuy = false, + } + +end + +local colors = CFG.skinColors +function octoshop.openShopItem(class) + + local classT = octoshop.getClassTable(class) + + local f = vgui.Create 'DButton' + f:SetSize(ScrW(), ScrH()) + f:MakePopup() + f:SetText('') + function f:Paint(w, h) + draw.RoundedBox(0, -1, -1, w+2, h+2, Color(0,0,0, 180)) + end + function f:DoClick() + self:Remove() + end + + local p = f:Add 'DPanel' + p:DockPadding(10,10,10,10) + p:SetSize(500, 500) + p:Center() + p:SetSkin('Default') + + local p_h = p:Add 'DPanel' + p_h:Dock(TOP) + p_h:SetTall(120) + function p_h:Paint(w, h) + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(classT.icon) + surface.DrawTexturedRect(40, 28, 64, 64) + + draw.SimpleText(classT.name, 'octoshop.xtitle', 150, 30, Color(30,30,30), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local txt = classT.canBuy and L.buy_for .. octoshop.formatMoney(classT.price) or L.unavailable + surface.SetFont('octoshop.xprice') + local tw, th = surface.GetTextSize(txt) + + p_h.b = p_h:Add 'DButton' + p_h.b:SetText('') + p_h.b:SetSize(tw + 20, 40) + p_h.b:SetPos(150, 55) + function p_h.b:Paint(w, h) + if classT.canBuy then + draw.RoundedBox(4, 0, 0, w, h, colors.g) + if self.Hovered then draw.RoundedBox(4, 0, 0, w, h, Color(255,255,255, 20)) end + draw.SimpleText(txt, 'octoshop.xprice', w/2, h/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.RoundedBox(4, 0, 0, w, h, Color(220,220,220)) + draw.RoundedBox(4, 1, 1, w-2, h-2, Color(238,238,238)) + draw.SimpleText(txt, 'octoshop.xprice', w/2, h/2, Color(120,120,120), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + function p_h.b:DoClick() + if classT.canBuy then + net.Start('octoshop.purchase') + net.WriteString(class) + net.SendToServer() + + f:Remove() + end + end + + local p_f = p:Add 'DLabel' + p_f:Dock(BOTTOM) + p_f:SetTall(15) + p_f:SetText(L.tap_anywhere) + p_f:SetTextColor(Color(120,120,120)) + p_f:SetContentAlignment(5) + + if #classT.attributes > 0 then + local p_m = p:Add 'DPanel' + p_m:Dock(LEFT) + p_m:SetWide(150) + p_m:SetPaintBackground(false) + for i, att in ipairs(classT.attributes) do + local a = p_m:Add 'DPanel' + a:Dock(TOP) + a:SetTall(24) + a:DockMargin(0,0,0,5) + a:SetPaintBackground(false) + + local i = a:Add 'DPanel' + i:Dock(LEFT) + i:SetWide(24) + i.icon = Material(att[2], '') + function i:Paint(w, h) + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(4, 4, 16, 16) + end + i:AddOctoHint(att[1]) + + local l = a:Add 'DLabel' + l:Dock(FILL) + l:DockMargin(5,0,0,0) + l:SetText(att[3]) + l:SetTextColor(Color(30,30,30)) + end + end + + local p_d = p:Add 'DLabel' + p_d:Dock(TOP) + p_d:DockMargin(0,0,8,0) + p_d:SetWrap(true) + p_d:SetTextColor(Color(30,30,30)) + p_d:SetText(classT.desc) + p_d:SetContentAlignment(7) + p_d:SetTextInset(8, 5) + function p_d:Paint(w, h) + surface.DisableClipping(true) + draw.RoundedBox(4, 0, 0, w+8, h+5, Color(220,220,220)) + draw.RoundedBox(4, 1, 1, w+6, h+3, Color(255,255,255)) + surface.DisableClipping(false) + end + function p_d:PerformLayout() + self:SizeToContentsY() + end + +end + +net.Receive('octoshop.rShop', function(len) + + local data = net.ReadTable() + for i, item in ipairs(data) do + octoshop.items[item.class] = { + name = item.name or L.what_it, + cat = item.cat or L.other, + desc = item.desc or L.temporary_not_desc, + price = item.price or 0, + order = item.order or 999, + icon = Material(item.icon or 'icon16/help.png', ''), + color = item.col or Color(102,170,170), + hidden = item.hidden, + attributes = item.attributes or {}, + canBuy = item.canBuy, + } + end + + octoshop.pnl:UpdateShop() + +end) + +octoshop.init() diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/sh_octoshop.lua b/garrysmod/addons/core-octoshop/lua/octoshop/sh_octoshop.lua new file mode 100644 index 0000000..fbccec4 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/sh_octoshop.lua @@ -0,0 +1,5 @@ +function octoshop.formatMoney(amount) + + return amount .. octolib.string.formatCount(amount, ' фишка', ' фишки', ' фишек') + +end diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/sv_data.lua b/garrysmod/addons/core-octoshop/lua/octoshop/sv_data.lua new file mode 100644 index 0000000..42eed19 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/sv_data.lua @@ -0,0 +1,152 @@ +hook.Add('octolib.db.init', 'octoshop', function() + + -- + -- CREATE COMMON TABLES + -- + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.octoshop_users ( + id INT(8) UNSIGNED NOT NULL AUTO_INCREMENT, + steamID VARCHAR(30) NOT NULL, + steamID64 VARCHAR(20) NOT NULL, + balance INT(10) NOT NULL, + totalTopup INT(10) NOT NULL, + totalSpent INT(10) NOT NULL, + totalPurchases INT(10) NOT NULL, + PRIMARY KEY (id), + UNIQUE (steamID) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.octoshop_servers ( + id VARCHAR(30) NOT NULL, + address VARCHAR(25) NOT NULL, + name VARCHAR(80) NOT NULL, + totalSpent INT(10) NOT NULL, + totalPurchases INT(10) NOT NULL, + items JSON NOT NULL, + PRIMARY KEY (id), + UNIQUE (address) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.octoshop_payments ( + id INT(8) UNSIGNED NOT NULL AUTO_INCREMENT, + userID INT(8) UNSIGNED NOT NULL, + amount INT(10) NOT NULL, + anonymous TINYINT(1) NOT NULL DEFAULT 0, + timeCompleted INT(10) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT Cons_OS_PaymentUser + FOREIGN KEY (userID) REFERENCES ]] .. CFG.db.shop .. [[.octoshop_users(id) + ON UPDATE CASCADE ON DELETE CASCADE + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.octoshop_purchases ( + id INT(8) UNSIGNED NOT NULL AUTO_INCREMENT, + userID INT(8) UNSIGNED NOT NULL, + serverID VARCHAR(30) NOT NULL, + itemID INT(8) UNSIGNED NOT NULL, + itemClass VARCHAR(30) NOT NULL, + price INT(10) NOT NULL, + timeCompleted INT(10) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT Cons_OS_PurchaseServer + FOREIGN KEY (serverID) REFERENCES ]] .. CFG.db.shop .. [[.octoshop_servers(id) + ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT Cons_OS_PurchaseUser + FOREIGN KEY (userID) REFERENCES ]] .. CFG.db.shop .. [[.octoshop_users(id) + ON UPDATE CASCADE ON DELETE CASCADE + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + -- + -- CREATE SERVER DATA + -- + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[ ( + id INT(8) UNSIGNED NOT NULL AUTO_INCREMENT, + userID INT(8) UNSIGNED NOT NULL, + itemName VARCHAR(255) NOT NULL, + itemClass VARCHAR(30) NOT NULL, + data TEXT, + PRIMARY KEY (id), + CONSTRAINT Cons_OS_Items_]] .. octoshop.server_id .. [[ + FOREIGN KEY (userID) REFERENCES ]] .. CFG.db.shop .. [[.octoshop_users(id) + ON UPDATE CASCADE ON DELETE CASCADE + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.coupons_]] .. octoshop.server_id .. [[ ( + code VARCHAR(64) NOT NULL, + reward VARCHAR(255) NOT NULL, + userID INT(8) UNSIGNED, + timeUsed INT(10), + PRIMARY KEY(code), + CONSTRAINT Cons_OS_CouponUser_]] .. octoshop.server_id .. [[ + FOREIGN KEY (userID) REFERENCES ]] .. CFG.db.shop .. [[.octoshop_users(id) + ON UPDATE CASCADE ON DELETE CASCADE + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + + -- octolib.db:PrepareQuery([[ + -- SELECT name FROM ]] .. CFG.db.shop .. [[.octoshop_servers + -- WHERE id = ? + -- ]], { + -- octoshop.server_id, + -- }, function(q, st, data) + -- data = istable(data) and data[1] + -- if data then + -- octoshop.msg('DATABASE: Server ' .. data.name .. ' loaded.') + -- octolib.db:PrepareQuery([[ + -- UPDATE ]] .. CFG.db.shop .. [[.octoshop_servers + -- SET address = ?, name = ? + -- WHERE id = ? + -- ]], { + -- game.GetIPAddress(), + -- octoshop.server_name, + -- octoshop.server_id, + -- }) + -- else + -- octoshop.msg('DATABASE: New server, setting up...') + -- octolib.db:PrepareQuery([[ + -- INSERT INTO ]] .. CFG.db.shop .. [[.octoshop_servers (id, address, name, totalSpent, totalPurchases, items) + -- VALUES (?, ?, ?, 0, 0, 'null') + -- ]], { + -- octoshop.server_id, + -- game.GetIPAddress(), + -- octoshop.server_name, + -- }) + -- end + -- end) + + local items = {} + for class, item in pairs(octoshop.items) do + table.insert(items, { + class = class, + name = item.name, + desc = item.desc, + price = item.price, + order = item.order, + icon = item.icon, + hidden = item.hidden, + attributes = item.attributes, + }) + end + + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.octoshop_servers + SET items = ? + WHERE id = ? + ]], { + util.TableToJSON(items), + octoshop.server_id, + }) + +end) diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/sv_items.lua b/garrysmod/addons/core-octoshop/lua/octoshop/sv_items.lua new file mode 100644 index 0000000..e5d9735 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/sv_items.lua @@ -0,0 +1,562 @@ +util.AddNetworkString 'octoshop.action' +util.AddNetworkString 'octoshop.purchase' + +octoshop.items = {} + +local fs, _ = file.Find('config/octoshop-items/*.lua', 'LUA') +for _, f in pairs(fs) do + include('config/octoshop-items/' .. f) +end + +local dataSync = {} +local function syncItemsData() + + for id, item in pairs(dataSync) do + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[ + SET data = ?, itemName = ? + WHERE id = ? + ]], { util.TableToJSON(item.data), item.name, item.id }, function(q, st, data) + if st then + octoshop.debugmsg('Item data updated for ID: ' .. item.id) + else + octoshop.msg('Failed to update item data for ID: ' .. item.id .. ', error:') + octoshop.msg(data) + end + end) + + dataSync[id] = nil + end + +end +hook.Add('Think', 'octoshop.dataSync', syncItemsData) + +local osItem = {} +osItem.__index = osItem + +function osItem:New(class, owner, callback) + + if not class or not octoshop.items[class] or not IsValid(owner) or not owner.osID then + octoshop.debugmsg('Could not create item "' .. class .. '" for ' .. tostring(owner)) + return false + end + + local item = { + class = class, + classTable = octoshop.items[class], + name = octoshop.items[class].name, + owner = owner, + data = {} + } setmetatable(item, osItem) + + octolib.db:PrepareQuery([[ + INSERT INTO ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[(userID, itemName, itemClass, data) + VALUES(?,?,?,?) + ]], { + owner.osID, + octoshop.items[class].name, + class, + '[]', + }, function(q, st, data) + if not item or not IsValid(owner) then return end + if st then + item.id = q:lastInsert() + octoshop.debugmsg('Created item "' .. class .. '" for ' .. tostring(owner) .. ', ID: ' .. item.id) + owner.osItems[item.id] = item + local shouldDo = true + if isfunction(callback) then if callback(item) then shouldDo = false end end + if shouldDo then item:OnGiven() end + owner:osNetInv() + else + octoshop.debugmsg('Could not save created item "' .. class .. '" for ' .. tostring(owner) .. ', removing...') + item:Remove() + end + end) + + return item + +end + +function osItem:CreateDromData(owner, data) + + if not data.id or not data.class or not octoshop.items[data.class] or not IsValid(owner) or not owner.osID then + octoshop.msg('Could not load item "' .. data.class .. '", ID: ' .. data.id .. ' for ' .. tostring(owner)) + return false + end + + local item = { + id = data.id, + class = data.class, + classTable = octoshop.items[data.class], + name = data.name or octoshop.items[data.class].name, + owner = owner, + data = data.data or {}, + } setmetatable(item, osItem) + + owner.osItems[item.id] = item + -- octoshop.msg('Loaded item "' .. data.class .. '", ID: ' .. data.id .. ' for ' .. tostring(owner)) + item:OnGiven() + + return item + +end + +function osItem:Remove(noDB) + + if not noDB then + octolib.db:PrepareQuery([[ + DELETE FROM ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[ + WHERE id = ? + ]], { self.id }) + end + + local owner = self:GetOwner() + if IsValid(owner) and istable(owner.osItems) then + if owner.osItems[self.id] then + self:OnTaken() + self.removed = true + owner.osItems[self.id] = nil + owner:osNetInv() + else + return false + end + end + + octoshop.debugmsg('Removed item "' .. self.class .. '", ID: ' .. self.id) + return true + +end + +function osItem:Move(newOwner, noDB) + + if not IsValid(newOwner) or not istable(newOwner.osItems) or not newOwner.osID then return false end + + local oldOwner = self:GetOwner() + if IsValid(oldOwner) and istable(oldOwner.osItems) then + if oldOwner.osItems[self.id] then + self:OnTaken() + oldOwner.osItems[self.id] = nil + else + return false + end + end + + if not noDB then + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[ + SET userID = ? + WHERE id = ? + ]], { newOwner.osID, self.id }) + end + + newOwner.osItems[self.id] = self + self.owner = newOwner + self:OnGiven() + + oldOwner:osNetInv() + newOwner:osNetInv() + + return true + +end + +function osItem:Validate() + + if not IsValid(self.owner) or not self.owner.osID then + return self:Remove() + end + + octolib.db:PrepareQuery([[ + SELECT id, userID FROM ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[ + WHERE id = ? + ]], { self.id }, function(q, st, data) + data = istable(data) and data[1] + if not data or data.userID ~= self.owner.osID then + self:Remove() + end + end) + + return self + +end + +function osItem:GetClassTable() + + return self.classTable + +end + +function osItem:OnGiven() + + if self:GetData('expire') and os.time() >= self:GetData('expire') then + octoshop.notify(self:GetOwner(), 'warning', L.octoshop_item_expired .. self.name ..L.octoshop_item_expired2) + self:Remove() + return + end + + local class = self:GetClassTable() + if isfunction(class.OnGiven) then + class.OnGiven(self) + end + + return self + +end + +function osItem:OnTaken() + + local class = self:GetClassTable() + if isfunction(class.OnTaken) then + class.OnTaken(self) + end + +end + +function osItem:OnBuy() + + local class = self:GetClassTable() + if isfunction(class.OnBuy) then + class.OnBuy(self) + end + + return self + +end + +function osItem:CanUse() + + local class = self:GetClassTable() + local can = class.CanUse + if isfunction(class.CanUse) then can = class.CanUse(self) end + + return can + +end + +function osItem:Use(force) + + if force or self:CanUse() then + local class = self:GetClassTable() + if isfunction(class.Use) then + class.Use(self) + end + end + + return self + +end + +function osItem:IsEquipped() + + return self:GetData('equipped') + +end + +function osItem:CanEquip(isUnequip) + + if self:IsEquipped() and not isUnequip then return false end + + local class = self:GetClassTable() + local can = class.CanEquip + if isfunction(class.CanEquip) then can = class.CanEquip(self) end + + return can + +end + +function osItem:Equip(force) + + if force or self:CanEquip() then + local class = self:GetClassTable() + if isfunction(class.Equip) then + class.Equip(self) + end + end + + return self + +end + +function osItem:CanUnequip() + + if not self:IsEquipped() then return false end + + local class = self:GetClassTable() + local can = class.CanUnequip + if isfunction(class.CanUnequip) then can = class.CanUnequip(self) end + + if can == nil then + can = self:CanEquip(true) + end + + return can + +end + +function osItem:Unequip(force) + + if force or self:CanUnequip() then + local class = self:GetClassTable() + if isfunction(class.Unequip) then + class.Unequip(self) + end + end + + return self + +end + +function osItem:CanTrade() + + if self:GetData('notrade') then return false end + + local class = self:GetClassTable() + local can = class.CanTrade + if isfunction(class.CanTrade) then can = class.CanTrade(self) end + + return can + +end + +function osItem:SetExpire(time) + + self:SetData('expire', time) + return self + +end + +function osItem:SetExpireIn(time) + + self:SetExpire(os.time() + time) + return self + +end + +function osItem:GetExpire() + + return self:GetData('expire') + +end + +function osItem:SetData(key, value) + + self.data[key] = value + if self.id then + dataSync[self.id] = self + else + timer.Simple(0.1, function() self:SetData(key, value) end) + end + return self + +end + +function osItem:GetData(key) + + return self.data[key] + +end + +function osItem:GetOwner() + + return self.owner + +end + +-- +-- PLAYER EXTENSION +-- + +local ply = FindMetaTable 'Player' + +function ply:osSyncItems() + + octoshop.debugmsg('Items sync started for ' .. tostring(self)) + if istable(self.osItems) then + table.Empty(self.osItems) + else + self.osItems = {} + end + + octolib.db:PrepareQuery([[ + SELECT id, itemName, itemClass, data FROM ]] .. CFG.db.shop .. [[.items_]] .. octoshop.server_id .. [[ + WHERE userID = ? + ]], { self.osID }, function(q, st, data) + if st then + for _, itemData in pairs(data) do + osItem:CreateDromData(self, { + id = itemData.id, + class = itemData.itemClass, + name = itemData.itemName, + data = util.JSONToTable(itemData.data), + }) + end + + octoshop.msg('Loaded items for ' .. tostring(self)) + else + return octoshop.msg('Could not load items for ' .. tostring(self)) + end + end) + +end + +function ply:osGetItems() + + return self.osItems + +end + +function ply:osGetItem(id) + + local inv = self:osGetItems() + if not istable(inv) then return false end + + if isnumber(id) then + return inv[id] + elseif isstring(id) then + for k, v in pairs(inv) do + if v.class == id then return inv[k] end + end + end + + return false + +end + +function ply:osGiveItem(class, callback) + + return osItem:New(class, self, callback) + +end + +function ply:osTakeItem(id, noDB) + + local item = self:osGetItem(id) + if item then + return item:Remove(noDB) + else + return false + end + +end + +function ply:osMoveItem(id, newOwner, noDB) + + local item = ply:osGetItem(id) + if item then + return item:Move(newOwner, noDB) + else + return false + end + +end + +function ply:osPurchaseItem(class) + + local classTable = octoshop.items[class] + + if not classTable or not (isfunction(classTable.CanBuy) and classTable.CanBuy(self) or classTable.CanBuy) then + octoshop.notify(self, 'warning', L.octoshop_you_cant_buy) + return false + end + + if not self:osAddMoney(-classTable.price) then + octoshop.notify(self, 'warning', L.octoshop_not_enough) + self:osNetBalance() + return false + end + + self:osGiveItem(class, function(item) + item:OnBuy() + octoshop.notify(self, L.octoshop_you_bought .. classTable.name) + + octolib.db:PrepareQuery([[ + INSERT INTO ]] .. CFG.db.shop .. [[.octoshop_purchases(userID, serverID, itemID, itemClass, price, timeCompleted) + VALUES(?,?,?,?,?,?) + ]], { + self.osID, + octoshop.server_id, + item.id, + class, + classTable.price, + os.time(), + }) + + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.octoshop_servers + SET totalPurchases = totalPurchases + 1, totalSpent = totalSpent + ? + WHERE id = ? + ]], { + classTable.price, + octoshop.server_id, + }) + + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.octoshop_users + SET totalPurchases = totalPurchases + 1, totalSpent = totalSpent + ? + WHERE id = ? + ]], { + classTable.price, + self.osID, + }) + end) + +end +net.Receive('octoshop.purchase', function(len, ply) + + local class = net.ReadString() + ply:osPurchaseItem(class) + +end) + +net.Receive('octoshop.action', function(len, ply) + + local id = net.ReadUInt(32) + local action = net.ReadString() + + local item = ply:osGetItem(id) + if not item then + octoshop.notify(ply, L.octoshop_error) + ply:osNetInv() + return + end + + if action == 'use' then + if item:CanUse() then + item:Use() + else + octoshop.notify(ply, L.octoshop_error_use) + end + elseif action == 'equip' then + if item:CanEquip() then + item:Equip() + else + octoshop.notify(ply, L.octoshop_error_equip) + end + elseif action == 'unequip' then + if item:CanUnequip() then + item:Unequip() + else + octoshop.notify(ply, L.octoshop_error_unequip) + end + elseif action == 'trade' then + local newOwner = net.ReadEntity() + if item:CanTrade() then + if IsValid(newOwner) and newOwner:IsPlayer() and newOwner.osID then + item:Move(newOwner) + octoshop.notify(ply, L.octoshop_you_give .. item.name .. L.octoshop_you_give2 .. newOwner:Name()) + octoshop.notify(newOwner, L.octoshop_you_take .. item.name .. L.octoshop_you_take2 .. ply:Name()) + else + octoshop.notify(ply, L.octoshop_error_receiver) + end + else + octoshop.notify(ply, L.octoshop_give_error) + end + elseif action == 'sell' then + if item:CanSell() then + octoshop.notify(ply, 'ooc', L.octoshop_soon_do) + else + octoshop.notify(ply, L.octoshop_you_cant_sell) + end + end + +end) diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/sv_octoshop.lua b/garrysmod/addons/core-octoshop/lua/octoshop/sv_octoshop.lua new file mode 100644 index 0000000..e8b0c45 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/sv_octoshop.lua @@ -0,0 +1,5 @@ +function octoshop.init() + + -- why? + +end diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/sv_player.lua b/garrysmod/addons/core-octoshop/lua/octoshop/sv_player.lua new file mode 100644 index 0000000..744c289 --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/sv_player.lua @@ -0,0 +1,327 @@ +util.AddNetworkString 'octoshop.rBalance' +util.AddNetworkString 'octoshop.rInventory' +util.AddNetworkString 'octoshop.rShop' +util.AddNetworkString 'octoshop.useCoupon' + +local meta = FindMetaTable 'Player' + +function meta:osCooldown(delay, ignoreCheck) + + local canDo = ignoreCheck or not self.osNextAction or CurTime() > self.osNextAction + if not canDo then + octoshop.notify(self, 'warning', L.too_fast) + else + self.osNextAction = CurTime() + delay + end + + return canDo + +end + +function meta:osSyncPlayerData() + + octolib.db:PrepareQuery([[ + SELECT id, balance FROM ]] .. CFG.db.shop .. [[.octoshop_users + WHERE steamID = ? + ]], { + self:SteamID() + }, function(q, st, data) + if not IsValid(self) then return end + + data = istable(data) and data[1] + if data then + -- load data + self.osID = data.id + self.osBalance = data.balance + self:osSyncItems() + else + -- new player + octoshop.msg('New player: ' .. tostring(self)) + octolib.db:PrepareQuery([[ + INSERT INTO ]] .. CFG.db.shop .. [[.octoshop_users (steamID, steamID64, balance, totalTopup, totalSpent, totalPurchases) + VALUES (?, ?, 0, 0, 0, 0) + ]], { + self:SteamID(), + self:SteamID64(), + }, function(q, st, data) + if not IsValid(self) then return end + + if st then + self:osSyncPlayerData() + else + octoshop.msg('ERROR: Could not initialize player: ' .. tostring(self)) + -- ply:Kick('Мы не смогли загрузить твои данные из магазина. Похоже на хакерские проделки, дружище') + end + end) + end + end) + +end +hook.Add('PlayerFinishedLoading', 'octoshop', meta.osSyncPlayerData) + +function meta:osGetMoney() + + self.osBalance = self.osBalance or 0 + return self.osBalance + +end + +function meta:osHasMoney(val) + + return self:osGetMoney() >= val + +end + +function meta:osAddMoney(val) + + if not self.osID then return false end + if not self:osHasMoney(-val) then return false end + + self.osBalance = self.osBalance + val + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.octoshop_users + SET balance = balance + ? + WHERE id = ? + ]], { val, self.osID }, function(q, st, data) + if not IsValid(self) then return end + if st then + self:osSyncBalance() + else + octoshop.msg('Failed to update balance for ' .. tostring(self)) + end + end) + + return true + +end + +function meta:osSyncBalance() + + if not self.osID then return false end + + octolib.db:PrepareQuery([[ + SELECT id, balance FROM ]] .. CFG.db.shop .. [[.octoshop_users + WHERE id = ? + ]], { self.osID }, function(q, st, data) + if not IsValid(self) then return end + data = istable(data) and data[1] + if data then + if self.osBalance and self.osBalance < data.balance then + octoshop.notify(self, 'ooc', L.octoshop_update_balance .. octoshop.formatMoney(data.balance - self.osBalance)) + end + + self.osBalance = data.balance + self:osNetBalance() + octoshop.debugmsg('Updated balance for ' .. tostring(self) .. ': ' .. octoshop.formatMoney(self.osBalance)) + else + octoshop.msg('Failed to update balance for ' .. tostring(self)) + end + end) + +end + +function meta:osNetBalance() + + net.Start('octoshop.rBalance') + net.WriteUInt(self.osBalance or 0, 32) + net.Send(self) + +end + +function meta:osNetInv() + + local toSend = {} + for itemID, item in pairs(self:osGetItems()) do + table.insert(toSend, { + id = itemID, + class = item.class, + name = item.name, + canUse = item:CanUse(), + canEquip = item:CanEquip(), + canUnequip = item:CanUnequip(), + canTrade = item:CanTrade(), + equipped = item:IsEquipped(), + expire = item:GetExpire(), + active = item:GetData('active'), + data = item.data, + }) + end + + net.Start('octoshop.rInventory') + net.WriteTable(toSend) + net.Send(self) + +end + +function meta:osNetShop() + + local toSend = {} + for class, item in pairs(octoshop.items) do + local canBuy = item.CanBuy + table.insert(toSend, { + class = class, + name = item.name, + cat = item.cat, + desc = item.desc, + price = item.price, + order = item.order, + icon = item.icon, + hidden = item.hidden, + attributes = item.attributes, + canBuy = isfunction(canBuy) and canBuy(self) or canBuy, + }) + end + + net.Start('octoshop.rShop') + net.WriteTable(toSend) + net.Send(self) + +end +net.Receive('octoshop.rShop', function(len, ply) + + local cd = ply:osCooldown(3) + if not cd then return end + + ply:osNetShop() + +end) + +net.Receive('octoshop.rInventory', function(len, ply) + + local cd = ply:osCooldown(3) + if not cd then return end + + ply:osSyncBalance() + ply:osNetInv() + +end) + +local couponSymbols = { + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r', + 's','t','u','v','w','x','y','z','1','2','3','4','5','6','7','8','9','0', +} +local rewardTypes = { + balance = function(ply, data) + local amount = tonumber(data) + if not amount then + octoshop.notify(ply, 'warning', L.wrong_money) + return + end + + ply:osAddMoney(amount) + octoshop.notify(ply, 'ooc', L.coupon_content .. octoshop.formatMoney(amount)) + return true + end +} + +function octoshop.createCoupon(rewardType, rewardData, ply) + + if not rewardTypes[rewardType] then + if IsValid(ply) then + octoshop.notify(ply, 'warning', L.type_bonus_wrong) + end + octoshop.msg('Error creating coupon ' .. code .. ': No such reward type') + end + + local code = '' + for i = 1, octoshop.couponLength or 64 do + code = code .. couponSymbols[math.random(#couponSymbols)] + end + + local data = rewardType .. ':' .. rewardData + octolib.db:PrepareQuery([[ + INSERT INTO ]] .. CFG.db.shop .. [[.coupons_]] .. octoshop.server_id .. [[(code, reward) + VALUES(?, ?) + ]], { code, data }, function(q, st, data) + if st then + if IsValid(ply) then + octoshop.notify(ply, L.create_coupon_hint .. code) + end + octoshop.msg('Created coupon: ' .. code) + else + end + end) + +end + +function octoshop.removeCoupon(code) + + octolib.db:PrepareQuery([[ + DELETE FROM ]] .. CFG.db.shop .. [[.coupons_]] .. octoshop.server_id .. [[ + WHERE code = ? + ]], { code }) + +end + +function octoshop.useCoupon(ply, code) + + octolib.db:PrepareQuery([[ + SELECT code, reward FROM ]] .. CFG.db.shop .. [[.coupons_]] .. octoshop.server_id .. [[ + WHERE code = ? AND timeUsed IS NULL + ]], { code }, function(q, st, data) + if not IsValid(ply) then return end + data = istable(data) and data[1] + if data then + local scp = data.reward:find(':') + if not scp then + octoshop.notify(ply, 'warning', L.reward_failed) + return + end + + local rewardType = data.reward:sub(1, scp - 1) + local rewardData = data.reward:sub(scp + 1) + if isfunction(rewardTypes[rewardType]) then + if rewardTypes[rewardType](ply, rewardData) then + octoshop.notify(ply, L.use_coupon_hint) + octolib.db:PrepareQuery([[ + UPDATE ]] .. CFG.db.shop .. [[.coupons_]] .. octoshop.server_id .. [[ + SET userID = ?, timeUsed = ? + WHERE code = ? + ]], { ply.osID, os.time(), code }, function(q, st, data) + if st then + octoshop.msg(tostring(ply) .. ' used coupon ' .. code) + else + octoshop.msg('Error updating coupon ' .. code .. ': ' .. data) + end + end) + ply.nextCoupon = CurTime() + (octoshop.couponUseDelay or 30) + else + octoshop.notify(ply, 'warning', L.use_coupon_fail) + end + else + octoshop.notify(ply, 'warning', L.coupon_not_exist) + octoshop.msg('Error using coupon ' .. code .. ': Reward type is not registered!') + octoshop.removeCoupon(code) + return + end + else + octoshop.notify(ply, 'warning', L.coupon_not_exist) + end + end) + +end + +net.Receive('octoshop.useCoupon', function(len, ply) + + if (ply.nextCoupon or 0) > CurTime() then + octoshop.notify(ply, 'warning', L.octoshop_wait .. math.ceil(ply.nextCoupon - CurTime()) .. 'с...') + return + end + + local code = net.ReadString() + ply.nextCoupon = CurTime() + 2 + + octoshop.useCoupon(ply, code) + +end) + +concommand.Add('octoshop_coupon_create', function(ply, cmd, args, argStr) + + if not octoshop.checkOwner(ply) then + octoshop.notify(ply, 'warning', L.only_owner) + return + end + + octoshop.createCoupon(args[1], args[2], ply) + +end) diff --git a/garrysmod/addons/core-octoshop/lua/octoshop/sv_salary.lua b/garrysmod/addons/core-octoshop/lua/octoshop/sv_salary.lua new file mode 100644 index 0000000..b43442f --- /dev/null +++ b/garrysmod/addons/core-octoshop/lua/octoshop/sv_salary.lua @@ -0,0 +1,18 @@ +timer.Create('octoshop.salary', 60, 0, function() + + octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + if not IsValid(ply) or ply:IsAFK() then return end + + ply.os_salaryPoints = (ply.os_salaryPoints or 0) + 1 + if ply.os_salaryPoints < octoshop.salaryPeriod then return end + + local amount = octoshop.salaryAmount[ply:GetUserGroup()] + if not amount then return end + + ply:osAddMoney(amount) + octoshop.notify(ply, 'ooc', L.you_got .. octoshop.formatMoney(amount) .. L.for_work_admin) + + ply.os_salaryPoints = 0 + end) + +end) diff --git a/garrysmod/addons/core-view/lua/autorun/client/closelook.lua b/garrysmod/addons/core-view/lua/autorun/client/closelook.lua new file mode 100644 index 0000000..a50fdac --- /dev/null +++ b/garrysmod/addons/core-view/lua/autorun/client/closelook.lua @@ -0,0 +1,513 @@ +dbgView = dbgView or {} +dbgView.look = dbgView.look or { + enabled = false, + state = 0, + cache = {}, +} + +surface.CreateFont('dbg-hud.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, + shadow = true, +}) +surface.CreateFont('dbg-hud.normal-sh', { + font = 'Calibri', + extended = true, + size = 27, + blursize = 5, + weight = 350, +}) + +surface.CreateFont('dbg-hud.small', { + font = 'Roboto', + extended = true, + size = 17, + weight = 350, + shadow = true, +}) +surface.CreateFont('dbg-hud.small-sh', { + font = 'Roboto', + extended = true, + size = 17, + blursize = 4, + weight = 350, +}) + +surface.CreateFont('octoinv.3d', { + font = 'Arial Bold', + extended = true, + size = 18, + weight = 300, + antialias = true, +}) + +surface.CreateFont('octoinv.3d-sh', { + font = 'Arial Bold', + extended = true, + size = 18, + weight = 300, + blursize = 5, + antialias = true, +}) + +local look = dbgView.look + +local key_on = GetConVar('cl_dbg_key_look'):GetInt() +cvars.AddChangeCallback('cl_dbg_key_look', function(cv, old, new) key_on = tonumber(new) end) + +hook.Add('PlayerBindPress', 'dbg-look', function(ply, bind) if bind == '+zoom' then return true end end) +hook.Add('PlayerButtonDown', 'dbg-look', function(ply, key) if key == key_on and IsFirstTimePredicted() then look.enable(true) end end) +hook.Add('PlayerButtonUp', 'dbg-look', function(ply, key) if key == key_on and IsFirstTimePredicted() then look.enable(false) end end) + +function look.enable(val) + + if val then + look.enabled = true + netstream.Start('dbg-look.enable', true) + + timer.Create('dbg-look', 0.4, 0, look.update) + look.update() + else + look.enabled = false + netstream.Start('dbg-look.enable', false) + + timer.Remove('dbg-look') + for _, cache in pairs(look.cache) do + cache.killing = true + end + end + +end + +local function getPos(ent, data) + + local pos, ang = ent:WorldSpaceCenter(), ent:GetAngles() + if data.bone then + local bone = ent:LookupBone(data.bone) + if bone then pos, ang = ent:GetBonePosition(bone) end + end + if data.posRel then pos = LocalToWorld(data.posRel, angle_zero, pos, ang) end + if data.posAbs then pos:Add(data.posAbs) end + + return pos, ang + +end + +local corpseProps = { 'name', 'attacker', 'bullet', 'cause', 'weapon', 'time' } +local function getCorpse(ent) + local result = {} + for _, v in ipairs(corpseProps) do + result[v] = ent:GetNetVar('Corpse.' .. v) + end + return result +end + +local cos = math.cos(math.rad(40)) +function look.update() + + local ply, found = LocalPlayer(), {} + local ep = ply:EyePos() + for _, ent in pairs(ents.FindInCone(ep, ply:GetAimVector(), ply:GetNetVar('closelookZoom') and 1200 or 300, cos)) do + local data = ent.GetNetVar and ent:GetNetVar('dbgLook') + if data and not ent:GetNoDraw() then + local pos = getPos(ent, data) + local filter = { ply } + if ply:InVehicle() then + local veh = ply:GetVehicle() + filter[#filter + 1] = veh + filter[#filter + 1] = veh:GetParent() + end + if ent:IsPlayer() and ent:InVehicle() then + local veh = ent:GetVehicle() + filter[#filter + 1] = veh + filter[#filter + 1] = veh:GetParent() + end + local tr = util.TraceLine({ start = ep, endpos = pos, filter = filter }) + if not tr.Hit or tr.Entity == ent then + found[ent] = true + if not look.cache[ent] then + look.cache[ent] = { + data = data, + al = 0, + rot = 0, + descAl = 0, + h = 0, + } + end + end + end + end + + for ent, cache in pairs(look.cache) do + cache.killing = not found[ent] + end + +end + +hook.Add('EntityRemoved', 'dbg-look', function(ent) + + look.cache[ent] = nil + +end) + +local icon = Material('octoteam/icons/percent_inactive_white.png') +local st, mat = 0, Material('overlays/vignette01') +local colSh, colName = Color(0,0,0), Color(255,255,255) +local colors = CFG.skinColors +local lp, job, efwd, alive, ghost, seesGhosts, medic, seesCaliber, admin, priest +hook.Add('Think', 'dbg-look', function() + + if not look.enabled and st == 0 then return end + st = math.Approach(st, look.enabled and 1 or 0, FrameTime() * 1.5) + + look.state = octolib.tween.easing.outQuad(st, 0, 1, 1) + + lp = LocalPlayer() + efwd, job = lp:GetAimVector(), lp:getJobTable() + alive, ghost, seesGhosts, medic, seesCaliber, seesName, seesTime, police, admin, priest = + lp:Alive(), + lp:GetNetVar('Ghost'), + job.seesGhosts, + job.medic, + job.seesCaliber, + job.seesName, + job.seesTime, + job.police, + lp:Team() == TEAM_ADMIN, + lp:Team() == TEAM_PRIEST + + efwd.z = 0 + efwd:Normalize() + +end) + +hook.Add('HUDPaint', 'dbg-look', function() + + if st == 0 then return end + if hook.Run('HUDShouldDraw', 'dbg-look') == false then return end + + mat:SetFloat('$alpha', look.state) + render.SetMaterial(mat) + render.DrawScreenQuad() + + local ft, cx, cy = FrameTime(), ScrW() / 2, ScrH() / 2 + for ent, cache in pairs(look.cache) do + if not IsValid(ent) or (cache.al <= 0 and cache.killing) then + look.cache[ent] = nil + else + cache.al = math.Approach(cache.al, cache.killing and 0 or 1, ft * 3) + local al = octolib.tween.easing.outQuad(cache.al, 0, 1, 1) + surface.SetAlphaMultiplier(al) + + local pos = getPos(ent, cache.data) + pos = pos:ToScreen() + + local x, y = math.floor(pos.x), math.floor(pos.y) + local spos = Vector(x, y, 0) + if cache.data.lookOff then + local off = cache.data.lookOff + spos.x = spos.x - off.x + spos.y = spos.y - off.y + end + local tAl = al * math.Clamp(220 - Vector(x, y, 0):DistToSqr(Vector(cx, cy, 0)) / 200, 0, 200) / 200 + local name, desc = cache.data.name, cache.data.desc + + local run, descAl, descOn = tAl == 1, cache.descAl, cache.descOn + local descAlSm = descAl + if desc and desc ~= '' then + if descOn then + local func = cache.data.descRender and look.render[desc] + descAl = math.Approach(descAl, 1, ft * 1.5) + descAlSm = al * octolib.tween.easing.outQuad(descAl, 0, 1, 1) + if isfunction(func) then + func(ent, cache, x, y, al, descAlSm) + else + cache.mu = cache.mu or markup.Parse(('%s'):format(desc), 250) + cache.h = cache.mu:GetHeight() / 2 + local my = y + 5 * descAlSm + cache.mu:Draw(x, my, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 255 * descAl) + end + elseif run and descAl == 1 then + descAl = 0 + descOn = true + cache.lookTime = 0 + else + descAl = math.Approach(descAl, run and 1 or 0, admin and ft * 5 or ft / (cache.data.time or 3)) + end + cache.descAl = descAl + cache.descOn = descOn + + local func = cache.data.checkLoader and look.render[cache.data.checkLoader] + if not isfunction(func) or func(ent, cache) then + if descOn then tAl = math.max(tAl - descAlSm, 0) end + if tAl > 0 then + local rot = (cache.rot - ft * (run and 240 or 90 * tAl)) % 360 + cache.rot = rot + + local iSize = descOn and (36 + 16 * descAlSm) or (36 * tAl) + surface.SetMaterial(icon) + surface.SetDrawColor(38, 166, 154, tAl * 255) + surface.DrawTexturedRectRotated(x, y, iSize, iSize, rot) + end + end + end + + local func = cache.data.nameRender and look.render[name] + if isfunction(func) then + func(ent, cache, x, y, al, descAlSm, descOn) + else + local ty = descOn and y - descAlSm * (cache.h + 5) or y + draw.Text { + text = name, + font = 'dbg-hud.normal-sh', + pos = {x, ty - 3}, + color = colSh, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + draw.Text { + text = name, + font = 'dbg-hud.normal', + pos = {x, ty - 3}, + color = colName, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + end + end + end + + surface.SetAlphaMultiplier(1) + +end) + +hook.Add('PlayerFinishedLoading', 'dbg-hud', function() + + hook.Remove('PreDrawHalos', 'PropertiesHover') + +end) + +look.render = { + playerLoader = function(ply) + return ply.showInfo + end, + playerName = function(ply, data, x, y, al1, al2, on) + ply.showInfo = ply ~= lp and (ghost or seesGhosts or ply:GetRenderMode() ~= RENDERMODE_TRANSALPHA) + if not ply.showInfo then return end + + local mask = ply:GetNetVar('hMask') + if not admin and mask and CFG.masks[mask] and CFG.masks[mask].hideName then return end + + local ang2 = ply:GetAimVector() + ang2.z = 0 + ang2:Normalize() + + local al = math.Clamp(1 - efwd:Dot(ang2) * 3, 0, 1) + if al > 0 then + surface.SetAlphaMultiplier(al * al1) + + local ty = on and y - al2 * (data.h + 5) or y + local action = ply:GetNetVar('currentAction') + if action then + local y2 = ty - 22 + action = action .. ('.'):rep(math.floor(CurTime() * 5) % 4) + draw.SimpleText(action, 'dbg-hud.small-sh', x, y2, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(action, 'dbg-hud.small', x, y2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + local name = ply:Name() + draw.Text { + text = name, + font = 'dbg-hud.normal-sh', + pos = {x, ty - 3}, + color = colSh, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + draw.Text { + text = name, + font = 'dbg-hud.normal', + pos = {x, ty - 3}, + color = colName, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + end + data.nameAl = al + end, + playerDesc = function(ply, data, x, y, al1, al2) + if not ply.showInfo then return end + + surface.SetAlphaMultiplier(al2) + + if data.mu then + data.h = data.mu:GetHeight() / 2 + local my = y + 5 * al2 + data.mu:Draw(x, my, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 255 * al2) + else + local health, temp = ply:Health(), {} + if health < 25 then + temp[#temp + 1] = L.desc_nearly_dead + elseif health < 60 then + temp[#temp + 1] = L.desc_unhealthy_look + end + + local time = ply:GetTimeTotal() + if time < 18000 then + temp[#temp + 1] = L.desc_newbie + end + + if ply:GetNetVar('ScareState', 0) > 0.6 then + temp[#temp + 1] = L.desc_scared + end + + if ply:GetNetVar('Drunk') then + temp[#temp + 1] = L.desc_drunk + end + + if ply:GetNetVar('belted') then + temp[#temp + 1] = '- Пристегнут' + end + + local karma = ply:GetNetVar('dbg.karma', 0) + if priest or admin then + temp[#temp + 1] = L.desc_karma:format(karma) + else + if karma > 200 then + temp[#temp + 1] = L.desc_has_to_itself + elseif karma < -100 then + temp[#temp + 1] = L.desc_looks_suspicious + end + end + + if admin then + local note = ply:GetNetVar('watchList') + if note then + temp[#temp + 1] = L.desc_watchlist:format(note) + end + + local time = ply:CheckCrimeDenied() + if time == true then + temp[#temp + 1] = L.desc_nocrime_perm + elseif time then + temp[#temp + 1] = L.desc_nocrime:format(octolib.time.formatIn(time)) + end + local time = ply:CheckPoliceDenied() + if time == true then + temp[#temp + 1] = L.desc_nopolice_perm + elseif time then + temp[#temp + 1] = L.desc_nopolice:format(octolib.time.formatIn(time)) + end + end + + local desc = ply:GetNetVar('dbgDesc') + if desc and desc ~= '' then + temp[#temp + 1] = '- ' .. desc + elseif #temp == 0 then + temp[#temp + 1] = L.desc_usual + end + + data.mu = markup.Parse('' .. table.concat(temp, '\n') .. '', 300) + end + end, + corpseDesc = function(ent, data, x, y, al1, al2) + surface.SetAlphaMultiplier(al2) + + if data.mu then + data.h = data.mu:GetHeight() / 2 + local my = y + 5 * al2 + data.mu:Draw(x, my, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 255 * al2) + else + local temp, corpse = {}, getCorpse(ent) + if not next(corpse) then return end + + if corpse.cause then + temp[#temp + 1] = '- ' .. corpse.cause + end + + if seesCaliber or admin then + if corpse.bullet and corpse.weapon then + temp[#temp + 1] = L.desc_caliber:format(corpse.weapon) + end + + end + + if seesTime or admin then + if corpse.time then + temp[#temp + 1] = L.desc_time_death:format(corpse.time) + end + end + + if (seesName or admin) and corpse.name then + temp[#temp + 1] = L.desc_its:format(corpse.name) + end + + if admin and corpse.attacker then + temp[#temp + 1] = L.desc_murderer:format(corpse.attacker) + end + + data.mu = markup.Parse(('%s'):format(table.concat(temp, '\n')), 250) + end + end, + octoinv_item = function(ent, data, x, y, al1, al2) + surface.SetAlphaMultiplier(al2) + + local data = ent:GetNetVar('Item') + local isTable = istable(data[2]) + if (not ent.icon or not ent.name or not ent.amount) and data then + ent.name = (isTable and data[2].name or octoinv.items[data[1]].name or L.unknown) % octoinv.getReplacementTable(data[2], data[1]) + ent.icon = isTable and data[2].icon and Material(data[2].icon) or octoinv.items[data[1]].icon or Material('octoteam/icons/error.png') + ent.amount = isTable and data[2].amount or isnumber(data[2]) and data[2] or 1 + end + + draw.RoundedBox(4, x - 20, y - 20, 40, 40, colors.bg) + surface.SetDrawColor(255,255,255) + surface.SetMaterial(ent.icon) + surface.DrawTexturedRect(x - 16, y - 16, 32, 32) + + -- local tAl = math.Clamp(350 - Vector(x,y,0):DistToSqr(Vector(ScrW()/2, ScrH()/2, 0)) / 100, 0, 255) + draw.Text { + text = ent.name, + font = 'octoinv.3d-sh', + pos = {x, y + 20}, + color = Color(0,0,0), + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_TOP, + } + draw.Text { + text = ent.name, + font = 'octoinv.3d', + pos = {x, y + 20}, + color = Color(255,255,255), + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_TOP, + } + + local left + if isTable then + data[2].class = data[1] + left = octoinv.getItemUpperMO(data[2]) + end + + if left then + local w = 10 + left:GetWidth() + draw.RoundedBox(8, x + 18 - w / 2, y - 26, w, 16, Color(85,68,85)) + left:Draw(x + 20, y - 18, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif ent.amount ~= 1 then + local w = 16 + (string.len(ent.amount) - 1) * 6 + draw.RoundedBox(8, x + 18 - w / 2, y - 26, w, 16, colors.bg) + draw.Text({ + text = ent.amount, + font = 'octoinv.small', + pos = { x + 18, y - 18 }, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + color = Color(255,255,255), + }) + end + + surface.SetAlphaMultiplier(1 - al2) + end, +} diff --git a/garrysmod/addons/core-view/lua/autorun/client/dbg_view.lua b/garrysmod/addons/core-view/lua/autorun/client/dbg_view.lua new file mode 100644 index 0000000..d66cd88 --- /dev/null +++ b/garrysmod/addons/core-view/lua/autorun/client/dbg_view.lua @@ -0,0 +1,539 @@ +dbgView = dbgView or {} +dbgView.mods = dbgView.mods or {} +dbgView.useSights = true + +dbgView.disabledWeps = { + gmod_camera = true, + gmod_tool = true, + weapon_physgun = true, + dbg_admingun = true, + octo_camera = true, +} + +hook.Add('dbg-view.override', 'dbg-view.disabledWeapons', function() + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and dbgView.disabledWeps[wep:GetClass()] then + return false + end +end) + +-- function dbgView.modifier(name, data) + +-- dbgView.mods[name] = data + +-- end + +function dbgView.hideHead(doHide) + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local hat = ply:LookupBone('ValveBiped.Bip01_Head1') or 6 + ply:ManipulateBoneScale(hat, doHide and Vector(0.01, 0.01, 0.01) or Vector(1, 1, 1)) + dbgView.headHidden = doHide + +end +netstream.Hook('dbgView.hideHead', dbgView.hideHead) + +function dbgView.flyTo(pos, ang, time) + + time = time or 1 + ease = ease or -1 + + dbgView.startPos = dbgView.lastPos + dbgView.startAng = dbgView.lastAng + dbgView.tgtPos = pos + dbgView.tgtAng = ang + dbgView.flyStart = CurTime() + dbgView.flyEnd = CurTime() + time + dbgView.animActive = pos ~= nil + +end + +function dbgView.calcView(ply, pos, ang, fov) + local func = hook.GetTable().CalcView['dbg-view'] + if not (func and dbgView.active) then return { origin = pos, angles = ang, fov = fov } end + return func(ply, pos, ang, fov) or { origin = pos, angles = ang, fov = fov } +end + +function dbgView.calcWeaponView(ply, pos, ang, fov) + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.CalcView then + local view = wep:CalcView(ply, pos, ang, fov) + if not view then return end + + dbgView.viewpos = view.origin + return view + end +end + +function dbgView.fovMod(delta, time) + + dbgView.startFov = dbgView.fov + dbgView.tgtFov = (GetConVar('fov_desired'):GetFloat() or 90) + (delta or 0) + dbgView.fovStart = CurTime() + dbgView.fovEnd = CurTime() + time + +end + +function dbgView.lookMod(enabled, position, angles, radius) + + dbgView.lookActive = tobool(enabled) or nil + if not dbgView.lookActive then + dbgView.lookPosition = nil + dbgView.lookAngles = nil + dbgView.lookRadius = nil + return + end + + angles:Normalize() + + dbgView.lookPosition = position + dbgView.lookRadius = radius + dbgView.lookAngles = angles + +end + +function dbgView.setFov(fov, time) + if fov ~= 0 then + fov = fov - (GetConVar('fov_desired'):GetFloat() or 90) + end + dbgView.fovMod(fov, time) +end + +netstream.Hook('dbg-view.setFov', dbgView.setFov) + +local pmeta = FindMetaTable('Player') +local setFov = pmeta.setFOV +function pmeta:SetFOV(fov, time, requester) + if setFov then setFov(self, fov, time, requester) end + dbgView.setFov(fov, time or 0) +end + +local ply, anc, prevang, lookOffActive, blind = NULL, NULL, Angle(), false, false +local chPosOff, chAngOff, chDefault = Vector(0,0,0), Angle(0,90,90), Material('octoteam/icons/percent0.png') +local sens, lastModel, lastVeh = GetConVar('sensitivity'):GetFloat(), '', NULL +local traceMaxs, traceMins = Vector(5, 5, 3), Vector(-5, -5, -3) +local angle_zero = Angle() +local function filterList(ent) + return not (ent == ply or ent:GetRenderMode() == RENDERMODE_TRANSALPHA or ent:GetClass() == 'prop_ragdoll') +end + +local function recreateAnchor() + + if IsValid(anc) then anc:Remove() end + anc = octolib.createDummy('models/props_junk/popcan01a.mdl') + anc:SetParent(ply, 1) + anc:SetLocalPos(Vector()) + anc:SetLocalAngles(Angle()) + anc:SetNoDraw(true) + anc:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE) + +end + +local function inputMouseApply(cmd, x, y, ang) + + if lookOffActive or dbgView.lookActive then + if lookOffActive then + dbgView.lookOff.p = math.Clamp(dbgView.lookOff.p + y * sens / 200, -45, 45) + dbgView.lookOff.y = math.Clamp(dbgView.lookOff.y - x * sens / 200, -60, 60) + end + + if dbgView.lookActive then + dbgView.lookAngles = dbgView.lookAngles + Angle(y, -x, 0) * sens / 200 + dbgView.lookAngles.pitch = math.Clamp(dbgView.lookAngles.pitch, -65, 90) + end + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + return true + elseif dbgView.lookOff.p ~= 0 or dbgView.lookOff.y ~= 0 then + dbgView.lookOff.p = math.Approach(dbgView.lookOff.p, 0, math.max(math.abs(dbgView.lookOff.p), 0.2) * FrameTime() * 10) + dbgView.lookOff.y = math.Approach(dbgView.lookOff.y, 0, math.max(math.abs(dbgView.lookOff.y), 0.2) * FrameTime() * 10) + end + +end + +local function think() + blind = false + + local veh = ply:GetVehicle() + if ply:GetViewEntity() == ply and not IsValid(veh) and ply:GetMoveType() ~= MOVETYPE_NOCLIP then + blind = util.TraceHull({ + maxs = traceMaxs, + mins = traceMins, + start = dbgView.viewpos, + endpos = dbgView.viewpos, + filter = filterList, + }).Hit or util.TraceLine({ + start = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_Pelvis') or 0), + endpos = dbgView.viewpos, + filter = filterList + }).Hit + end +end + +local function calcView(ply, pos, angles, fov) + + -- local mods = { + -- origin = Vector(0,0,0), + -- angles = Angle(), + -- fov = 1, + -- znear = 0, + -- headScale = 0.01, + -- } + + -- for k, mod in pairs(dbgView.mods) do + -- if mod.origin then mods.origin = mods.origin + mod.origin end + -- if mod.angles then + -- mods.angles:RotateAroundAxis(mods.angles:Right(), mod.angles.p) + -- mods.angles:RotateAroundAxis(mods.angles:Up(), mod.angles.y) + -- mods.angles:RotateAroundAxis(mods.angles:Forward(), mod.angles.r) + -- end + -- if mod.fov then mods.fov = mods.fov * mod.fov end + -- if mod.znear then mods.znear = mods.znear + mod.znear end + -- if mod.headScale then mods.headScale = mods.headScale * mod.headScale end + -- end + + if ply:GetViewEntity() == ply and ply:Alive() and IsValid(anc) then + ply.viewAngs = Angle(angles.p, angles.y, angles.r) + + local model = ply:GetModel() + if model ~= lastModel then + for i = 0, ply:GetBoneCount() - 1 do + ply:ManipulateBoneScale(i, Vector(1,1,1)) + end + dbgView.hideHead(true) + recreateAnchor() + + lastModel = model + end + + local veh = ply:GetVehicle() + if veh ~= lastVeh then + recreateAnchor() + lastVeh = veh + end + + if not IsValid(anc) or anc:GetParent() ~= ply then + recreateAnchor() + end + + local view = { + fov = dbgView.fov, + znear = 3, + } + + local ancp, anca = anc:GetPos(), anc:GetAngles() + angles = angles + dbgView.lookOff + angles.p = math.Clamp(angles.p, -75, 75) + view.angles = angles + view.origin = ancp + dbgView.viewpos = view.origin + + if debugViewForwardDist then + view.origin:Add(angles:Forward() * debugViewForwardDist) + end + + -- if IsValid(veh) then + -- view.origin = LocalToWorld(veh:GetNetVar('Driver') and offsetDriver or offsetVehicle, angle_zero, ancp, anca) + -- else + -- view.origin = LocalToWorld(offsetFoot, angle_zero, ancp, anca) + -- end + + -- apply modifiers + -- view.origin = view.origin + mods.origin + -- view.angles = view.angles + mods.angles + -- view.fov = view.fov * mods.fov + -- view.znear = view.znear + mods.znear + + dbgView.calcPos = view.origin + dbgView.calcAng = view.angles + + if dbgView.flyStart then + local st = math.Clamp(math.TimeFraction(dbgView.flyStart, dbgView.flyEnd, CurTime()), 0, 1) + local frac = octolib.tween.easing.inOutQuad(st, 0, 1, 1) + if frac > 0 then + view.origin = LerpVector(frac, dbgView.startPos, dbgView.tgtPos or dbgView.calcPos) + view.angles = LerpAngle(frac, dbgView.startAng, dbgView.tgtAng or dbgView.calcAng) + elseif frac == 1 and not dbgView.tgtPos then + dbgView.flyStart = nil + end + end + + if dbgView.fovStart and dbgView.tgtFov then + local st = math.Clamp(math.TimeFraction(dbgView.fovStart, dbgView.fovEnd, CurTime()), 0, 1) + local frac = octolib.tween.easing.inOutQuad(st, 0, 1, 1) + if frac > 0 then + dbgView.fov = Lerp(frac, dbgView.startFov, dbgView.tgtFov) + elseif frac == 1 then + dbgView.fov = dbgView.tgtFov + dbgView.fovStart = nil + end + end + + if dbgView.lookActive then + local trace = util.TraceHull({ + start = dbgView.lookPosition, + endpos = dbgView.lookPosition - dbgView.lookAngles:Forward() * dbgView.lookRadius, + mins = Vector(-3, -3, -3), + maxs = Vector(3, 3, 3), + filter = ply, + }) + + view.origin = trace.HitPos + view.angles = dbgView.lookAngles + end + + dbgView.lastPos = view.origin + dbgView.lastAng = view.angles + + return view + else + dbgView.viewpos = pos + end + +end + +local function createMove(cmd) + dbgView.realang = dbgView.realang + cmd:GetViewAngles() - prevang + local veh = ply:GetVehicle() + if IsValid(veh) then + lookOffActive = false + dbgView.realang.y = dbgView.realang.y - 90 + dbgView.realang:Normalize() + dbgView.realang.y = math.Clamp(dbgView.realang.y, -110, 110) + if veh:GetNetVar('saw') then + dbgView.realang.p = math.Clamp(dbgView.realang.p, -75, 10 + 35*(1-(math.abs(dbgView.realang.y)/135)^2)) + else + dbgView.realang.p = math.Clamp(dbgView.realang.p, -25, 20*(1-(math.abs(dbgView.realang.y)/135)^2)) + end + local negate = dbgView.realang.y < 0 + dbgView.realang.y = dbgView.realang.y + 90 + dbgView.realang.r = (negate and -1 or 1) * (math.pow(dbgView.realang.y - 90, 2)) * (dbgView.realang.p - 0) / 28000 + else + dbgView.realang.p = math.Clamp(dbgView.realang.p, -75, 75) + dbgView.realang.r = 0 + end + dbgView.realang:Normalize() + + cmd:SetViewAngles(dbgView.realang) + prevang = cmd:GetViewAngles() +end + +local function shouldDrawLocalPlayer(ply) + return true +end + +local function hudShouldDraw(name) + if name == 'CHudCrosshair' then return false end +end + +local function hudPaint() + if blind then + draw.RoundedBox(0, -5, -5, ScrW()+10, ScrH()+10, color_black) + end + +end + +local function postDrawTranslucentRenderables() + + local override = hook.Run('dbg-view.chShouldDraw', ply) + if override == nil then + local wep, veh = ply:GetActiveWeapon(), ply:GetVehicle() + if IsValid(wep) and not dbgView.disabledWeps[wep:GetClass()] and wep.DrawCrosshair then + override = not IsValid(veh) or ply:GetAllowWeaponsInVehicle() + end + end + + if not override then return end + + local aim = (ply.viewAngs or ply:EyeAngles()):Forward() + local tr = hook.Run('dbg-view.chTraceOverride') + if not tr then + local pos = ply:GetShootPos() + local endpos = pos + aim * 2000 + tr = util.TraceLine({ + start = pos, + endpos = endpos, + filter = function(ent) + return ent ~= ply and ent:GetRenderMode() ~= RENDERMODE_TRANSALPHA + end + }) + end + + local _icon, _alpha, _scale = hook.Run('dbg-view.chOverride', tr) + local n = tr.Hit and tr.HitNormal or -aim + if math.abs(n.z) > 0.98 then + n:Add(-aim * 0.01) + end + local chPos, chAng = LocalToWorld(chPosOff, chAngOff, tr.HitPos or endpos, n:Angle()) + cam.Start3D2D(chPos, chAng, math.pow(tr.Fraction, 0.5) * (_scale or 0.2)) + cam.IgnoreZ(true) + if not hook.Run('dbg-view.chPaint', tr, _icon) then + if _icon then surface.SetDrawColor(255,255,255, _alpha or 150) + else + local clr = octolib.vars.get('dbg-crosshair.color') or color_white + surface.SetDrawColor(clr.r, clr.g, clr.b, _alpha or 150) + end + surface.SetMaterial(_icon or chDefault) + surface.DrawTexturedRect(-32, -32, 64, 64) + end + cam.IgnoreZ(false) + cam.End3D2D() + +end + +local k_freeview, k_sights = 0, 0 +cvars.AddChangeCallback('cl_dbg_key_freeview', function(cv, old, new) k_freeview = tonumber(new) end, 'dbg-view') +cvars.AddChangeCallback('cl_dbg_key_sights', function(cv, old, new) k_sights = tonumber(new) end, 'dbg-view') +local function playerButtonDown(ply, key) + if not IsFirstTimePredicted() then return end + + if key == k_freeview then lookOffActive = true end + if key == k_sights then + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetNetVar('IsReady') then + dbgView.useSights = not dbgView.useSights + end + end +end +local function playerButtonUp(ply, key) + if key == k_freeview and IsFirstTimePredicted() then lookOffActive = false end +end + +local function turnOn() + + ply = LocalPlayer() + dbgView.realang, dbgView.viewpos = ply:EyeAngles(), ply:GetShootPos() + dbgView.lookOff = Angle() + dbgView.fov = GetConVar('fov_desired'):GetFloat() or 90 + prevang = dbgView.realang + k_freeview = GetConVar('cl_dbg_key_freeview'):GetInt() + k_sights = GetConVar('cl_dbg_key_sights'):GetInt() + + hook.Add('Think', 'dbg-view', think) + hook.Add('CalcView', 'dbg-view', calcView) + hook.Add('CalcView', 'dbg-view.weapon', dbgView.calcWeaponView, -1) + hook.Add('CreateMove', 'dbg-view', createMove) + hook.Add('HUDPaint', 'dbg-view', hudPaint, -10) + hook.Add('HUDShouldDraw', 'dbg-view', hudShouldDraw) + hook.Add('InputMouseApply', 'dbg-view', inputMouseApply) + hook.Add('PlayerButtonDown', 'dbg-view', playerButtonDown) + hook.Add('PlayerButtonUp', 'dbg-view', playerButtonUp) + hook.Add('PostDrawTranslucentRenderables', 'dbg-view', postDrawTranslucentRenderables) + hook.Add('ShouldDrawLocalPlayer', 'dbg-view', shouldDrawLocalPlayer) + + dbgView.hideHead(true) + + recreateAnchor() + timer.Create('dbg-view.validateAnchor', 10, 0, function() + if not IsValid(anc) or ply:GetPos():DistToSqr(anc:GetPos()) > 10000 then + recreateAnchor() + end + end) + + dbgView.active = true + +end + +local function turnOff() + + hook.Remove('Think', 'dbg-view') + hook.Remove('CalcView', 'dbg-view') + hook.Remove('CalcView', 'dbg-view.weapon') + hook.Remove('CreateMove', 'dbg-view') + hook.Remove('HUDPaint', 'dbg-view') + hook.Remove('HUDShouldDraw', 'dbg-view') + hook.Remove('InputMouseApply', 'dbg-view') + hook.Remove('PlayerButtonDown', 'dbg-view') + hook.Remove('PlayerButtonUp', 'dbg-view') + hook.Remove('PostDrawTranslucentRenderables', 'dbg-view') + hook.Remove('ShouldDrawLocalPlayer', 'dbg-view') + + dbgView.hideHead(false) + + if IsValid(anc) then anc:Remove() end + timer.Remove('dbg-view.validateAnchor') + + dbgView.active = false + +end + +hook.Add('Think', 'dbg-view.override', function() + + local active = hook.Run('dbg-view.override') ~= false + if not active and dbgView.active then + turnOff() + elseif active and not dbgView.active then + turnOn() + end + +end) + +netstream.Hook('dbg-quickLook', function() + if not dbgView.active then + octolib.notify.show('warning', 'Ты не можешь взглянуть на себя, когда держишь в руках физган, тулган или камеру') + return + end + + local ply = LocalPlayer() + + local dir = dbgView.lastAng:Forward() + dbgView.lastAng:Right() * 0.3 + dir.z = -0.2 + dir:Normalize() + + local pos = dbgView.lastPos + dir * 40 + local tr = util.TraceHull({ + start = dbgView.lastPos, + endpos = pos, + mins = Vector(-3, -3, -3), + maxs = Vector(3, 3, 3), + filter = ply, + }) + if tr.Hit then + pos = tr.HitPos + end + + local lpPos = ply:GetPos() + Vector(0, 0, 42) + local ang = (lpPos - pos):Angle() + dbgView.flyTo(pos, ang, 1) + timer.Simple(0.2, function() + dbgView.hideHead(false) + ply:SetMaskVisible(true) + end) + local startPos = pos + ang:Forward() * dbgView.lastPos:Distance(pos) + timer.Simple(1, function() + dbgView.lookMod(true, startPos, ang, 40) + end) + + local sphere = { radius = 300, alpha = 0 } + hook.Add('PostDrawTranslucentRenderables', 'dbg-view.quickLook', function() + render.SetColorMaterial() + render.CullMode(MATERIAL_CULLMODE_CW) + render.DrawSphere(lpPos, sphere.radius, 20, 20, Color(30,25,30, sphere.alpha)) + render.CullMode(MATERIAL_CULLMODE_CCW) + end) + + octolib.tween.create(1, sphere, { radius = 50, alpha = 500 }, 'inOutQuad') + + timer.Simple(3, function() + dbgView.flyTo(nil, nil, 1) + dbgView.lookMod(false) + timer.Simple(0.7, function() + dbgView.hideHead(true) + ply:SetMaskVisible(false) + end) + + octolib.tween.create(1, sphere, { radius = 300, alpha = 0 }, 'inOutQuad', function() + hook.Remove('PostDrawTranslucentRenderables', 'dbg-view.quickLook') + end) + end) +end) + +turnOff() + +concommand.Add('cl_dbg_debug_view', function(_, _, args) + if not CFG.dev then return end + debugViewForwardDist = args[1] and tonumber(args[1]) +end) diff --git a/garrysmod/addons/core-view/lua/autorun/server/dbg_view.lua b/garrysmod/addons/core-view/lua/autorun/server/dbg_view.lua new file mode 100644 index 0000000..550a62d --- /dev/null +++ b/garrysmod/addons/core-view/lua/autorun/server/dbg_view.lua @@ -0,0 +1,35 @@ +local pmeta = FindMetaTable('Player') +local setFov = pmeta.SetFOV +function pmeta:SetFOV(fov, time, requester) + if setFov then setFov(self, fov, time, requester) end + netstream.Start(self, 'dbg-view.setFov', fov, time or 0) +end + +netstream.Hook('dbg-look.enable', function(ply, enable) + + if enable then + local increaseDist, changeFov = hook.Run('closelook.canZoom', ply) + if increaseDist then ply:SetLocalVar('closelookZoom', true) end + if changeFov then ply:SetFOV(50, 1) end + elseif ply:GetNetVar('closelookZoom') then + ply:SetLocalVar('closelookZoom', nil) + + local _, changeFov = hook.Run('closelook.canZoom', ply) + if changeFov then ply:SetFOV(0, 0.5) end + end + +end) + +netstream.Hook('dbg-quickLook', function(ply) + if ply:IsFrozen() then return end + + local doFreeze = not ply:GetNetVar('dragger') + + netstream.Start(ply, 'dbg-quickLook') + if doFreeze then ply:Freeze(true) end + + timer.Simple(3.8, function() + if not IsValid(ply) then return end + if doFreeze then ply:Freeze(false) end + end) +end) diff --git a/garrysmod/addons/core-view/lua/cmenu/items/look.lua b/garrysmod/addons/core-view/lua/cmenu/items/look.lua new file mode 100644 index 0000000..f39b454 --- /dev/null +++ b/garrysmod/addons/core-view/lua/cmenu/items/look.lua @@ -0,0 +1,8 @@ +octogui.cmenu.registerItem('rp', 'look', { + check = function() + return not LocalPlayer():GetNetVar('dragger') and dbgView.active + end, + text = 'Взглянуть на себя', + icon = octolib.icons.silk16('magnifier'), + netstream = 'dbg-quickLook', +}) diff --git a/garrysmod/addons/core-voice/lua/autorun/_voice.lua b/garrysmod/addons/core-voice/lua/autorun/_voice.lua new file mode 100644 index 0000000..ab117a1 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/autorun/_voice.lua @@ -0,0 +1,3 @@ +octolib.module('voice') +octolib.module('talkie') +octolib.client('talkie/vgui/notify') diff --git a/garrysmod/addons/core-voice/lua/cmenu/categories/talkie.lua b/garrysmod/addons/core-voice/lua/cmenu/categories/talkie.lua new file mode 100644 index 0000000..ffc8651 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/cmenu/categories/talkie.lua @@ -0,0 +1,3 @@ +octogui.cmenu.registerCategory('talkie', { + check = function(ply) return ply:HasTalkie() end, +}) diff --git a/garrysmod/addons/core-voice/lua/cmenu/items/talkie.lua b/garrysmod/addons/core-voice/lua/cmenu/items/talkie.lua new file mode 100644 index 0000000..6178902 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/cmenu/items/talkie.lua @@ -0,0 +1,48 @@ +octogui.cmenu.registerItem('talkie', 'stations', { + text = 'Настройки рации', + icon = octolib.icons.silk16('transmit_blue'), + options = { + { + text = 'Частота', + icon = octolib.icons.silk16('textfield_rename'), + build = function(sm) + local ply = LocalPlayer() + + for _, v in SortedPairsByMemberValue(talkie.activeChannels, 2) do + local inUse = v[1] == ply:GetFrequency() + local option = sm:AddOption(v[2] or 'Неопознанная частота', inUse and octolib.func.zero or function() + RunConsoleCommand('set_talkie_channel', v[1]) + end) + + if inUse then option:SetImage(octolib.icons.silk16('bullet_green')) end + end + + local civilOption = sm:AddOption('Гражданская частота', octolib.fStringRequest(L.change_radio, L.talkie_hint:format(ply:GetFrequency()), '', function(s) + RunConsoleCommand('set_talkie_channel', s) + end, nil, L.ok, L.cancel)) + + if ply:GetFrequency():find('^(%d%d%d%.%d)$') then + civilOption:SetImage(octolib.icons.silk16('bullet_green')) + end + end, + }, { + text = 'Слышать основную частоту', + icon = function(ply) + if not ply:GetNetVar('NoTalkieParenting') then + return octolib.icons.silk16('tick') + end + end, + netstream = 'dbg-talkie.toggleParenting', + }, + }, +}) + +octogui.cmenu.registerItem('talkie', 'toggle', { + text = function(ply) + return ply:IsTalkieDisabled() and L.c_language_radio_enable or L.c_language_radio_disable + end, + icon = function(ply) + return octolib.icons.silk16(ply:IsTalkieDisabled() and 'transmit_blue' or 'transmit') + end, + cmd = {'toggle_talkie'}, +}) diff --git a/garrysmod/addons/core-voice/lua/talkie/channels/ch_dpd.lua b/garrysmod/addons/core-voice/lua/talkie/channels/ch_dpd.lua new file mode 100644 index 0000000..426e03a --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/channels/ch_dpd.lua @@ -0,0 +1,25 @@ +local divisions = { + dec = { + name = 'Детективы', + access = {'dec','sdec','ins','ddep', 'dcom'}, + }, + swat = { + name = 'S.W.A.T', + access = {'swat'}, + }, +} + +for freq, division in pairs(divisions) do + local chan = talkie.createChannel(freq) + chan.name, chan.parent = division.name, 'ems' + function chan:IsListeningAllowed(ply) + if ply:Team() == TEAM_FBI then return true end + if ply:GetActiveRank('dpd') == 'chi' then return true end + if ply:Team() == TEAM_ADMIN then return true end + if table.HasValue(division.access, ply:GetActiveRank('dpd') or '') then + return true + end + return false + end + chan.IsSpeakingAllowed = chan.IsListeningAllowed +end diff --git a/garrysmod/addons/core-voice/lua/talkie/channels/ch_ems.lua b/garrysmod/addons/core-voice/lua/talkie/channels/ch_ems.lua new file mode 100644 index 0000000..5de591d --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/channels/ch_ems.lua @@ -0,0 +1,16 @@ +local groups = { 'dpd', 'medic', 'fire', 'coroners', 'prison' } + +local chan = talkie.createChannel('ems') +chan.name = 'Экстренные службы' +function chan:IsListeningAllowed(ply) + if ply:Team() == TEAM_FBI then return true end + if ply:Team() == TEAM_ADMIN then return true end + if ply:GetNetVar('dbg-police.job', '') ~= '' then return true end + for _, groupID in ipairs(groups) do + if ply.currentOrg == groupID then + return true + end + end + return false +end +chan.IsSpeakingAllowed = chan.IsListeningAllowed diff --git a/garrysmod/addons/core-voice/lua/talkie/channels/ch_orgs.lua b/garrysmod/addons/core-voice/lua/talkie/channels/ch_orgs.lua new file mode 100644 index 0000000..f030585 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/channels/ch_orgs.lua @@ -0,0 +1,23 @@ +-- id, name +local orgs = { {'gov', 'Правительство'}, {'dpd', 'DPD'}, {'fire', 'Пожарные'}, {'coroners', 'Коронеры'}, {'csd', 'Городская служба'}, {'fbi', 'ФБР'}, {'bank', 'Банк'}, {'prison', 'Тюрьма'}, {'elsec', 'Elegant Security'}, {'alpha', 'Alpha'}, {'taxi', 'Таксисты'} } + +local function canUse(self, ply) + if ply:Team() == TEAM_ADMIN then return true end + if ply:Team() == TEAM_FBI then return true end + return ply.currentOrg == self.frequency +end + +for _, org in ipairs(orgs) do + local chan = talkie.createChannel(org[1]) + chan.name, chan.parent = org[2], 'ems' + chan.IsListeningAllowed, chan.IsSpeakingAllowed = canUse, canUse +end + +local chan = talkie.createChannel('medic') +chan.name, chan.parent = 'Медики', 'ems' +local function medicOrCoroner(self, ply) + if ply:Team() == TEAM_ADMIN then return true end + if ply:Team() == TEAM_FBI then return true end + return ply.currentOrg == 'medic' or ply.currentOrg == 'coroners' +end +chan.IsListeningAllowed, chan.IsSpeakingAllowed = medicOrCoroner, medicOrCoroner diff --git a/garrysmod/addons/core-voice/lua/talkie/channels/ch_tactical.lua b/garrysmod/addons/core-voice/lua/talkie/channels/ch_tactical.lua new file mode 100644 index 0000000..429b266 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/channels/ch_tactical.lua @@ -0,0 +1,10 @@ +local function policeOnly(self, ply) + return (ply:isCP() and not ply:GetActiveRank('gov')) or ply:Team() == TEAM_ADMIN +end + +for i = 1, 3 do + local chan = talkie.createChannel('tac' .. i) + chan.name = 'Тактический ' .. i + chan.parent = 'ems' + chan.IsListeningAllowed, chan.IsSpeakingAllowed = policeOnly, policeOnly +end diff --git a/garrysmod/addons/core-voice/lua/talkie/channels/ch_wcso.lua b/garrysmod/addons/core-voice/lua/talkie/channels/ch_wcso.lua new file mode 100644 index 0000000..b6508cf --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/channels/ch_wcso.lua @@ -0,0 +1,13 @@ +local chan = talkie.createChannel('wcso') +chan.name, chan.parent = 'WCSO', 'ems' +function chan:IsListeningAllowed(ply) + return ply:Team() == TEAM_FBI or ply:Team() == TEAM_ADMIN or ply:GetActiveRank('wcso') ~= nil +end +chan.IsSpeakingAllowed = chan.IsListeningAllowed + +local chan = talkie.createChannel('seb') +chan.name, chan.parent = 'S.E.B.', 'wcso' +function chan:IsListeningAllowed(ply) + return ply:Team() == TEAM_FBI or ply:Team() == TEAM_ADMIN or ply:GetActiveRank('wcso') == 'seb' +end +chan.IsSpeakingAllowed = chan.IsListeningAllowed diff --git a/garrysmod/addons/core-voice/lua/talkie/channels/meta_channel.lua b/garrysmod/addons/core-voice/lua/talkie/channels/meta_channel.lua new file mode 100644 index 0000000..730d175 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/channels/meta_channel.lua @@ -0,0 +1,58 @@ +talkie.channelMeta = talkie.channelMeta or {} +talkie.channels = talkie.channels or {} + +local Channel = talkie.channelMeta +Channel.__index = Channel + +function talkie.createChannel(freq) + + if not freq then return ErrorNoHalt('Attempted to create channel without frequency') end + talkie.channels[freq] = talkie.channels[freq] or {} + + local chan = talkie.channels[freq] + chan.frequency = freq + chan.canListen, chan.canSpeak = {}, {} + setmetatable(chan, Channel) + + chan._updateSpeaking = octolib.func.debounceStart(Channel._updateSpeaking, 1) + chan._updateListening = octolib.func.debounceStart(Channel._updateListening, 1) + + return chan + +end + +function Channel:IsListeningAllowed(ply) + return true -- to be overriden +end +function Channel:IsSpeakingAllowed(ply) + return true -- to be overriden +end + +Channel._updateSpeaking = function(self, ply) + + if not IsValid(ply) then return end + + local previous = self.canSpeak[ply] + local new = self:IsSpeakingAllowed(ply) or nil + self.canSpeak[ply] = new + if previous ~= new then + ply:SyncTalkieChannels() + end + +end + +Channel._updateListening = function(self, ply) + if not IsValid(ply) then return end + + local previous = self.canListen[ply] + local new = self:IsListeningAllowed(ply) or nil + self.canListen[ply] = new + if previous ~= new then + ply:SyncTalkieChannels() + end + + if not new and ply:GetFrequency() == self.frequency then + ply:DisconnectTalkie() + end + +end diff --git a/garrysmod/addons/core-voice/lua/talkie/client.lua b/garrysmod/addons/core-voice/lua/talkie/client.lua new file mode 100644 index 0000000..6e2afd1 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/client.lua @@ -0,0 +1,377 @@ +local meta = FindMetaTable 'Player' + +local function hasTalkie(ply) + ply.hasTalkie = ply:HasItem('radio') +end +function meta:HasTalkie() + if RPExtraTeams[self:Team()].hasTalkie then return true end + if not self._updateHavingTalkie then + self._updateHavingTalkie = octolib.func.debounceStart(hasTalkie, 0.5) + end + self:_updateHavingTalkie() + return self.hasTalkie +end + + +local radioAnim = { + sitting = { + Bip01_L_Hand = { ang = Angle(-10, -45, -105) }, + Bip01_L_Forearm = { ang = Angle(0, -22, -60) }, + Bip01_L_Clavicle = { ang = Angle(0, 0, 0) }, + Bip01_L_UpperArm = { ang = Angle(-10, -30, 0) }, + + Bip01_L_Finger0 = { ang = Angle(-15, -10, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, -20, 0) }, + Bip01_L_Finger1 = { ang = Angle(20, -18, -30) }, + Bip01_L_Finger12 = { ang = Angle(0, -10, 0) }, + Bip01_L_Finger2 = { ang = Angle(15, -12, -10) }, + Bip01_L_Finger22 = { ang = Angle(0, -10, 0) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + + Bip01_R_Forearm = { ang = Angle(0, 0, 0) }, + Bip01_R_Hand = { ang = Angle(0, 0, 0) }, + fov = 0.3, + }, + + crouching = { + Bip01_L_Hand = { ang = Angle(6, -30, -20) }, + Bip01_L_Forearm = { ang = Angle(0, 0, -40) }, + Bip01_L_Clavicle = { ang = Angle(5, 20, 0) }, + Bip01_L_UpperArm = { ang = Angle(30, -65, -5) }, + + Bip01_L_Finger0 = { ang = Angle(-30, 0, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, -15, 0) }, + Bip01_L_Finger1 = { ang = Angle(17, 23, 0) }, + Bip01_L_Finger12 = { ang = Angle(-10, 25, 0) }, + Bip01_L_Finger2 = { ang = Angle(15, 20, -10) }, + Bip01_L_Finger22 = { ang = Angle(0, 80, 30) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + + Bip01_R_Forearm = { ang = Angle(0, 0, 0) }, + Bip01_R_Hand = { ang = Angle(0, 0, 0) }, + fov = 0.2, + }, + + passive = { + Bip01_L_Hand = { ang = Angle(0, 0, 80) }, + Bip01_L_Forearm = { ang = Angle(-5, -86, -8) }, + Bip01_L_Clavicle = { ang = Angle(0, 0, 0) }, + Bip01_L_UpperArm = { ang = Angle(0, 0, 0) }, + + Bip01_L_Finger0 = { ang = Angle(0, 0, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, 0, 0) }, + Bip01_L_Finger1 = { ang = Angle(8, 55, 0) }, + Bip01_L_Finger12 = { ang = Angle(-8, 1, 0) }, + Bip01_L_Finger2 = { ang = Angle(0, 49, 0) }, + Bip01_L_Finger21 = { ang = Angle(0, -15, 0) }, + Bip01_L_Finger22 = { ang = Angle(0, -5, -15) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + + Bip01_R_Forearm = { ang = Angle(-5, 76, 28) }, + Bip01_R_Hand = { ang = Angle(35, 0, 0) }, + fov = 0.2, + }, + + crouching_pas = { + Bip01_L_Hand = { ang = Angle(0, 0, 80) }, + Bip01_L_Forearm = { ang = Angle(-5, -65, -8) }, + Bip01_L_Clavicle = { ang = Angle(0, 0, 0) }, + Bip01_L_UpperArm = { ang = Angle(0, 0, 0) }, + + Bip01_L_Finger0 = { ang = Angle(0, 0, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, 0, 0) }, + Bip01_L_Finger1 = { ang = Angle(8, 65, 0) }, + Bip01_L_Finger12 = { ang = Angle(-8, 1, 0) }, + Bip01_L_Finger2 = { ang = Angle(0, 49, 0) }, + Bip01_L_Finger21 = { ang = Angle(0, -15, 0) }, + Bip01_L_Finger22 = { ang = Angle(0, -5, -25) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + + Bip01_R_Forearm = { ang = Angle(0, 0, 0) }, + Bip01_R_Hand = { ang = Angle(0, 0, 0) }, + fov = 0.2, + }, +} + +local radioPos = { + pos = Vector(1.3, 0.7, 1), + ang = Angle(-140, -15, 100) +} + +local radio_sounds = { + + on = { + 'npc/combine_soldier/vo/on1.wav', + 'npc/combine_soldier/vo/on2.wav' + }, + + off = { + 'npc/combine_soldier/vo/off1.wav', + 'npc/combine_soldier/vo/off2.wav' + } + +} + +local on = radio_sounds.on +local off = radio_sounds.off + +hook.Add('UpdateAnimation', 'dbg-talkie.UpdateAnimation', function(ply, vel) + + if not IsValid(ply) then return end + if ply:InVehicle() then return end + if ply:GetModel() == 'models/error.mdl' then return end + ply.TalkieAnimWeight = math.Approach( + ply.TalkieAnimWeight or 0, + (ply:IsUsingTalkie() and ply:OnGround()) and (1 - vel:LengthSqr() / 50000) or 0, + FrameTime() * 5 + ) + + local weight = ply.TalkieAnimWeight or 0 + + if weight > 0 then + local anim = talkie.getProperAnim(ply) + for bone, data in pairs(anim) do + if bone ~= 'fov' then + + local weapon = ply:GetActiveWeapon() + + if IsValid(weapon) and weapon:GetClass():find('octo', 1, false) and (bone == 'Bip01_R_Forearm' or bone == 'Bip01_R_Hand') then continue end + + local id = ply:LookupBone('ValveBiped.' .. bone) + if not id then continue end + if data.pos then ply:ManipulateBonePosition(id, data.pos * weight) end + if data.ang then ply:ManipulateBoneAngles(id, data.ang * weight) end + end + end + + local state = (IsValid(ply:GetVehicle()) and 'sitting') or 'passive' + + if not IsValid(ply.Talkie) then + talkie.createDummy(ply) + elseif ply.lastState ~= state then + talkie.removeDummy(ply) + ply.lastState = state + return + end + + else + if IsValid(ply.Talkie) then talkie.removeDummy(ply) end + end + +end) + +function talkie.getProperAnim(ply) + local veh = ply:GetVehicle() + local weapon = ply:GetActiveWeapon() + + if IsValid(veh) then + return radioAnim.sitting + elseif IsValid(weapon) and ply:GetActiveWeapon():GetHoldType() == 'passive' then + return ply:Crouching() and radioAnim.crouching_pas or radioAnim.passive + elseif ply:Crouching() then + return radioAnim.crouching + else + return radioAnim.passive + end + +end + +function talkie.createDummy(ply) + + if IsValid(ply) and not IsValid(ply.Talkie) then + local attID = ply:LookupAttachment('anim_attachment_LH') + + local radio_m = octolib.createDummy('models/handfield_radio.mdl') + radio_m:SetParent(ply, attID) + radio_m:SetLocalPos(radioPos.pos) + radio_m:SetLocalAngles(radioPos.ang) + radio_m:SetSkin(1) + + ply.Talkie = radio_m + end + +end + +function talkie.removeDummy(ply) + + if IsValid(ply) and IsValid(ply.Talkie) then + ply.Talkie:Remove() + ply.Talkie = nil + for bone,data in pairs(talkie.getProperAnim(ply)) do + if bone ~= 'fov' then + local id = ply:LookupBone('ValveBiped.' .. bone) + if not id then continue end + if data.pos then ply:ManipulateBonePosition(id, Vector()) end + if data.ang then ply:ManipulateBoneAngles(id, Angle()) end + end + end + end +end + + +octolib.func.loop(function(done) + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + + if IsValid(ply) and IsValid(ply.Talkie) and (not ply:Alive() or ply:GetNoDraw() or not ply:IsUsingTalkie()) then + talkie.removeDummy(ply) + end + + end):Then(done) +end) + +local usingTalkie = false +local function stopSpeaking(ply, disable_sounds) + + if usingTalkie then + + RunConsoleCommand('-talkie') + usingTalkie = false + permissions.EnableVoiceChat(false) + + if not disable_sounds then + surface.PlaySound(off[math.random(1, #off)]) + end + end +end + +--local bind_key_convar = CreateClientConVar('radio_bind_key', 32, true, false, 'Биндит вашу кнопку на рацию') + +hook.Add('PlayerButtonDown', 'dbg-talkie.bind', function(ply, key) + + local bind = GetConVar('radio_bind_key'):GetInt() or KEY_R + + if bind == key then + if not ply:HasTalkie() or (ply:IsTalkieDisabled() or (ply:GetMoveType() ~= MOVETYPE_WALK and not IsValid(ply:GetVehicle()))) then return end + + if not usingTalkie then + permissions.EnableVoiceChat(true) + RunConsoleCommand('+talkie') + usingTalkie = true + end + end +end) + +hook.Add('PlayerButtonUp', 'dbg-talkie.bind', function(ply, key) + + local bind = GetConVar('radio_bind_key'):GetInt() or KEY_R + if bind == key and usingTalkie then + stopSpeaking(ply, true) + end +end) + +hook.Add('PlayerBindPress', 'dbg-talkie.bindOverride', function(ply, bind) + if bind:find('voicerecord', nil, true) and usingTalkie then return true end +end) + +hook.Add('KeyPress', 'dbg-talkie.noJump', function(ply, key) + if (key == IN_JUMP) and IsFirstTimePredicted() and usingTalkie then + stopSpeaking(ply) + end +end) + +local cmds = {'/r ', '/wr ', '/yr ', '/lr ', '/radio ', '/yradio ', '/wradio ', '/lradio '} +hook.Add('ChatTextChanged', 'dbg-talkie', function(txt) + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local showRadio + for _, cmd in ipairs(cmds) do + if txt:StartWith(cmd) then + showRadio = true + break + else + showRadio = false + end + end + + if showRadio then + if not usingTalkie and ply:HasTalkie() and not ply:IsTalkieDisabled() then + netstream.Start('dbg-talkie.updateSpeakStatus', true) + + surface.PlaySound(on[math.random(1, #on)]) + usingTalkie = true + end + elseif usingTalkie then + netstream.Start('dbg-talkie.updateSpeakStatus', false) + + stopSpeaking(ply, true) + end +end) + +netstream.Hook('dbg-talkie.remove', function(data) + if data and data:IsPlayer() and IsValid(data.Talkie) then + talkie.removeDummy(data) + if data == LocalPlayer() then + stopSpeaking(data) + end + end +end) + +talkie.activeChannels = talkie.activeChannels or {} +netstream.Hook('dbg-talkie.sync', function(chans) + talkie.activeChannels = chans +end) + +-- VOICE PANELS +local displayNotifies = CreateClientConVar('cl_dbg_talkienotifies', '1', true) +talkie.voicePnls = talkie.voicePnls or {} + +local function usingTalkie(ply) + if LocalPlayer():GetPos():DistToSqr(ply:GetPos()) <= ply:GetNetVar('TalkRange', 150000) then return false end + return ply:IsUsingTalkie() +end + +local function endVoice(ply) + local plyPnl = talkie.voicePnls[ply] + if not IsValid(plyPnl) or plyPnl.fadeAnim then return end + plyPnl.fadeAnim = Derma_Anim('FadeOut', plyPnl, plyPnl.FadeOut) + plyPnl.fadeAnim:Start(2) +end +hook.Add('PlayerEndVoice', 'dbg-talkie.onEnd', endVoice) + +hook.Add('PlayerStartVoice', 'dbg-talkie.onStart', function(ply) + if not (usingTalkie(ply) and displayNotifies:GetBool()) then return end + + local vl = talkie.voiceListPnl + if not IsValid(vl) then return end + + endVoice(ply) + + if IsValid(talkie.voicePnls[ply]) then + if talkie.voicePnls[ply].fadeAnim then + talkie.voicePnls[ply].fadeAnim:Stop() + talkie.voicePnls[ply].fadeAnim = nil + end + talkie.voicePnls[ply]:SetAlpha(255) + return + end + + if not IsValid(ply) then return end + + local pnl = vl:Add('talkie_notify') + pnl:Setup(ply) + + talkie.voicePnls[ply] = pnl + +end) + +local function cleanVoice() + for k, v in pairs(talkie.voicePnls) do + if not IsValid(k) then endVoice(k) end + end +end +timer.Create('VoiceClean', 10, 0, cleanVoice) + +hook.Add('InitPostEntity', 'CreateVoiceVGUI', function() + if IsValid(talkie.voiceListPnl) then talkie.voiceListPnl:Remove() end + talkie.voiceListPnl = vgui.Create('DPanel') + talkie.voiceListPnl:ParentToHUD() + talkie.voiceListPnl:SetPos(ScrW() - 300, 100) + talkie.voiceListPnl:SetSize(250, ScrH() - 200) + talkie.voiceListPnl:SetPaintBackground(false) +end) diff --git a/garrysmod/addons/core-voice/lua/talkie/server.lua b/garrysmod/addons/core-voice/lua/talkie/server.lua new file mode 100644 index 0000000..e030fe6 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/server.lua @@ -0,0 +1,286 @@ +octolib.server('talkie/channels/meta_channel') +local channels = file.Find('talkie/channels/ch_*.lua', 'LUA') +for _, ch in ipairs(channels) do + octolib.server('talkie/channels/' .. ch:gsub('.lua', '')) +end + +local relations = {} +timer.Create('dbg-talkie.flushRelations', 1, 0, function() + relations = {} +end) + +--[[------------------------------------------------------------------------- +---------------------------------------------------------------------------]] +local meta = FindMetaTable 'Player' + +function meta:GetFrequency() + return self:GetLocalVar('TalkieFreq', '111.1') +end + +function meta:CanListenToChannel(freq, instant) + local hr = hook.Run('dbg-talkie.canListen', self, freq) + if hr == false then return hr end + local chan = talkie.channels[freq] + if not chan then return true end -- civil frequency, no limits + if instant then + chan.canListen[self] = chan:IsListeningAllowed(self) or nil + else chan:_updateListening(self) end + chan.canSpeak[self] = chan.canListen[self] -- you can't speak if you can't listen + return chan.canListen[self] == true +end + +function meta:CanSpeakToChannel(freq, instant) + local hr = hook.Run('dbg-talkie.canSpeak', self, freq) + if hr == false then return hr end + + local chan = talkie.channels[freq] + if not chan then return true end -- civil frequency, no limits + + if instant then + chan.canSpeak[self] = chan:IsSpeakingAllowed(self) or nil + else chan:_updateSpeaking(self) end + return chan.canSpeak[self] == true +end + +function meta:ConnectTalkie(freq) + + if not self:CanListenToChannel(freq, true) then return false, 'Твоя рация не поддерживает такую частоту' end + + local old = self:GetFrequency() + if talkie.channels[old] then + talkie.channels[old].canListen[self], talkie.channels[old].canSpeak[self] = nil + end + self.previousFrequency = old + + self:SetLocalVar('TalkieFreq', freq) + if IsValid(self) then self:Notify('Рация переведена на частоту ' .. freq) end + + return true + +end + +function meta:DisconnectTalkie() + local could = self:ConnectTalkie(self.previousFrequency or '111.1') + if not could then self:ConnectTalkie('111.1') end +end + +local function _hasTalkie(ply) + if not IsValid(ply) then return end + ply.hasTalkie = RPExtraTeams[ply:Team()].hasTalkie or tobool(ply:HasItem('radio')) +end +function meta:HasTalkie() + if not self._updateHavingTalkie then + self._updateHavingTalkie = octolib.func.debounceStart(_hasTalkie, 0.5) + end + self:_updateHavingTalkie() + return self.hasTalkie +end + +function meta:SyncTalkieChannels() + local channels = {} + for k, v in pairs(talkie.channels) do + if v:IsListeningAllowed(self) then + channels[#channels + 1] = {k, v.name} + end + end + netstream.Start(self, 'dbg-talkie.sync', channels) +end +hook.Add('dbg-char.spawn', 'dbg-talkie', meta.SyncTalkieChannels) + +--[[------------------------------------------------------------------------- +---------------------------------------------------------------------------]] +local hasTalkie = meta.HasTalkie +local isUsingTalkie = meta.IsUsingTalkie +local isTalkieDisabled = meta.IsTalkieDisabled + +local function updateCache(listener, speaker) + + local sCache = relations[speaker] + if sCache == false then return false end + sCache = sCache or {} + if sCache[listener] ~= nil then return sCache[listener] end + relations[speaker] = sCache + + if not (isUsingTalkie(speaker) and hasTalkie(speaker)) then + relations[speaker] = false + return false + end + if isTalkieDisabled(listener) or not hasTalkie(listener) then + sCache[listener] = false + return false + end + + local freqL, freqS = listener:GetFrequency(), speaker:GetFrequency() + if freqL ~= freqS then + if listener:GetNetVar('NoTalkieParenting') then + sCache[listener] = false + -- VoiceBox.FX.IsRadioComm(listener:EntIndex(), speaker:EntIndex(), false) + return false + end + local chan = talkie.channels[freqL] + if not chan or not chan.parent or chan.parent ~= freqS then + sCache[listener] = false + -- VoiceBox.FX.IsRadioComm(listener:EntIndex(), speaker:EntIndex(), false) + return false + end + end + + if speaker:CanSpeakToChannel(freqS) and listener:CanListenToChannel(freqL) then + sCache[listener] = true + -- if speaker:GetInfoNum('dbg_disablevoicefx', 0) == 1 then + -- -- VoiceBox.FX.IsRadioComm(listener:EntIndex(), speaker:EntIndex(), false) + -- else + -- -- VoiceBox.FX.IsRadioComm(listener:EntIndex(), speaker:EntIndex(), not VoiceBox.FX.__PlayerCanHearPlayersVoice(listener, speaker)) + -- end + return true + end + + sCache[listener] = false + return false + -- VoiceBox.FX.IsRadioComm(listener:EntIndex(), speaker:EntIndex(), false) + + +end +hook.Add('PlayerCanHearPlayersVoice', 'dbg-talkie.updateCache', function(listener, speaker) + local status = updateCache(listener, speaker) + if status then return true end +end) + +hook.Add('PlayerDisconnected', 'dbg-talkie', function(ply) + for _, v in pairs(talkie.channels) do + v.canListen[ply], v.canSpeak[ply] = nil + end +end) + + +hook.Add('OnPlayerChangedTeam', 'dbg-talkie', function(ply) + ply:SyncTalkieChannels() + if ply:HasTalkie() and not ply:CanListenToChannel(ply:GetFrequency(), true) then + ply:DisconnectTalkie() + end +end) + +--[[------------------------------------------------------------------------- +---------------------------------------------------------------------------]] +local radio_sounds = { + + on = { + 'npc/combine_soldier/vo/on1.wav', + 'npc/combine_soldier/vo/on2.wav' + }, + + off = { + 'npc/combine_soldier/vo/off1.wav', + 'npc/combine_soldier/vo/off2.wav' + } + +} + +local on = radio_sounds.on +local off = radio_sounds.off + +local function updateSpeakStatus(ply, state, sounds) + + if not ply:Alive() or ply:IsGhost() or not ply:HasTalkie() then return end + if ply:IsTalkieDisabled() then return end + if ply:GetNetVar('sgGagged') then return end + + if state then + + local weapon = ply:GetActiveWeapon() + + if IsValid(weapon) and weapon:GetHoldType() ~= 'passive' and not ply:Crouching() and not ply:InVehicle() then + ply.last_wep = {wep = weapon:GetClass(), hold = weapon:GetHoldType()} + weapon:SetHoldType('passive') + end + + ply:SetNetVar('UsingTalkie', true) + if sounds then ply:EmitSound(on[math.random(1, #on)], 45) end + else + + ply:SetNetVar('UsingTalkie', false) + + if ply:Alive() then + local weapon = ply:GetActiveWeapon() + local last_wep = ply.last_wep + if last_wep then + local wep, hold = ply.last_wep.wep, ply.last_wep.hold + + if IsValid(weapon) and wep and hold and weapon:GetClass() == wep then + weapon:SetHoldType(hold or 'normal') + end + end + end + ply.last_wep = {} + if sounds then ply:EmitSound(off[math.random(1, #off)], 45) end + end +end + +concommand.Add('+talkie', function(ply) + updateSpeakStatus(ply, true, true) +end) + +concommand.Add('-talkie', function(ply) + updateSpeakStatus(ply, false, true) +end) + +netstream.Hook('dbg-talkie.updateSpeakStatus', function(ply, state) + updateSpeakStatus(ply, state) +end) + + +concommand.Add('set_talkie_channel', function(ply, cmd, args) + + if (ply.nextChannelChange or 0) > CurTime() then return ply:Notify('warning', 'Подожди немного') end + local freq = args[1] + if not freq then return ply:PrintMessage(HUD_PRINTCONSOLE, 'Usage: set_talkie_channel [frequency]') end + if not isstring(freq) then return end + if not talkie.channels[freq] and not tostring(freq):find('^(%d%d%d%.%d)$') then + return ply:Notify('warning', 'Твоя рация не может переключиться на эту частоту') + end + + local result, msg = ply:ConnectTalkie(freq) + if result == false then + return ply:Notify('warning', msg or 'Твоя рация не может переключиться на эту частоту') + end + ply.nextChannelChange = CurTime() + 1.5 + +end) + +concommand.Add('toggle_talkie', function(ply) + if ply:HasTalkie() and ply:Alive() then + if ply:IsTalkieDisabled() then + ply:SetLocalVar('TalkieDisabled', nil) + ply:Notify('hint', L.enable_radio) + else + ply:SetLocalVar('TalkieDisabled', true) + ply:Notify('hint', L.disable_radio) + end + else + ply:Notify('warning', L.you_dont_have_radio) + end +end) + +netstream.Hook('dbg-talkie.toggleParenting', function(ply) + ply:SetLocalVar('NoTalkieParenting', not ply:GetNetVar('NoTalkieParenting') or nil) +end) + +--[[------------------------------------------------------------------------- +---------------------------------------------------------------------------]] + +octolib.func.loop(function(done) + octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + + if not IsValid(ply) then return end + if not ply:HasTalkie() or ply:IsTalkieDisabled() or not ply:GetNetVar('UsingTalkie') then return end + + local weapon = ply:GetActiveWeapon() + if not IsValid(weapon) then return end + + if weapon:GetHoldType() ~= 'passive' and not ply:Crouching() and not ply:InVehicle() then + ply.last_wep = {wep = weapon:GetClass(), hold = weapon:GetHoldType()} + weapon:SetHoldType('passive') + end + + end):Then(done) +end) diff --git a/garrysmod/addons/core-voice/lua/talkie/shared.lua b/garrysmod/addons/core-voice/lua/talkie/shared.lua new file mode 100644 index 0000000..ee045de --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/shared.lua @@ -0,0 +1,15 @@ +talkie = talkie or {} + +local meta = FindMetaTable 'Player' + +function meta:IsUsingTalkie() + return self:GetNetVar('UsingTalkie', false) +end + +function meta:IsTalkieDisabled() + return self:GetNetVar('TalkieDisabled', false) +end + +function meta:GetFrequency() + return self:GetNetVar('TalkieFreq', '111.1') +end diff --git a/garrysmod/addons/core-voice/lua/talkie/vgui/notify.lua b/garrysmod/addons/core-voice/lua/talkie/vgui/notify.lua new file mode 100644 index 0000000..3f989fb --- /dev/null +++ b/garrysmod/addons/core-voice/lua/talkie/vgui/notify.lua @@ -0,0 +1,68 @@ +local PANEL = {} +local colors = CFG.skinColors + +function PANEL:Init() + + self.name = self:Add('DLabel') + self.name:SetFont('GModNotify') + self.name:Dock(FILL) + self.name:DockMargin(8, 0, 0, 0) + self.name:SetTextColor(color_white) + + self:SetColor(colors.bg) + self:SetSize(250, 32 + 8) + self:DockPadding(4, 4, 4, 4) + self:DockMargin(2, 2, 2, 2) + self:Dock(BOTTOM) + +end + +function PANEL:Setup(ply) + self.ply = ply + self.name:SetText(ply:Nick()) + self:InvalidateLayout() +end + +function PANEL:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, self.col_d) + draw.RoundedBox(4, 1, 1, w-2, h-2, self.col60) +end + +function PANEL:SetColor(clr) + self.col_d = Color(clr.r * 0.75, clr.g * 0.75, clr.b * 0.75) + self.col60 = ColorAlpha(clr, 150) +end + +local function remap(val, min, max) + return min + val * (max - min) +end +function PANEL:Think() + + if IsValid(self.ply) then + self.name:SetText(self.ply:Nick()) + local mul = self.ply:VoiceVolume() + local col = colors.bg + self:SetColor(Color(remap(mul, col.r, col.r * 2), remap(mul, col.g, col.g * 2), remap(mul, col.b, col.b * 2), 240)) + end + + if self.fadeAnim then + self.fadeAnim:Run() + end + +end + +function PANEL:FadeOut(anim, delta, data) + + if anim.Finished then + if IsValid(talkie.voicePnls[self.ply]) then + talkie.voicePnls[self.ply]:Remove() + talkie.voicePnls[self.ply] = nil + end + return + end + self:SetAlpha(255 - (255 * delta)) + +end + +vgui.Register('talkie_notify', PANEL, 'DPanel') + diff --git a/garrysmod/addons/core-voice/lua/voice/client.lua b/garrysmod/addons/core-voice/lua/voice/client.lua new file mode 100644 index 0000000..6852428 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/voice/client.lua @@ -0,0 +1,64 @@ +local enabled = CreateClientConVar('dbg_voicemods', '1', true) + +local ranges = { [10000] = 1, [150000] = 2, [500000] = 3, [2250000] = 4 } +local talking = false + +local function setMode(vm) + local curRange = LocalPlayer():GetNetVar('TalkRange') + if not curRange or vm ~= (ranges[curRange] or 2) then + netstream.Start('IWannaChangeVoiceMod', vm) + end +end + +hook.Add('PlayerStartVoice', 'VoiceMods', function(ply) + if ply ~= LocalPlayer() or not enabled:GetBool() then return end + talking = true + + local vm = 2 + if ply:KeyDown(IN_SPEED) then + vm = 3 + elseif ply:KeyDown(IN_WALK) then + vm = 1 + end + + setMode(vm) +end) + +hook.Add('PlayerEndVoice', 'VoiceMods', function(ply) + if ply ~= LocalPlayer() then return end + talking = false +end) + +hook.Add('KeyPress', 'VoiceMods', function(ply, key) + + if not talking or not IsFirstTimePredicted() then return end + + if key == IN_SPEED then + setMode(3) + elseif key == IN_WALK then + setMode(1) + end + +end) + +hook.Add('KeyRelease', 'VoiceMods', function(ply) + + if not talking or not IsFirstTimePredicted() then return end + if not ply:KeyDown(IN_SPEED) or ply:KeyDown(IN_WALK) then + setMode(2) + end + +end) + +hook.Add('octochat.chatOpenText', 'dbg-stuff', function(bind) + + local ply = LocalPlayer() + if bind == 'messagemode2' then + return '/r ' + elseif ply:KeyDown(IN_SPEED) then + return '/y ' + elseif ply:KeyDown(IN_WALK) then + return '/w ' + end + +end) diff --git a/garrysmod/addons/core-voice/lua/voice/server.lua b/garrysmod/addons/core-voice/lua/voice/server.lua new file mode 100644 index 0000000..f9db901 --- /dev/null +++ b/garrysmod/addons/core-voice/lua/voice/server.lua @@ -0,0 +1,27 @@ +local ranges = { 10000, 150000, 500000, 2250000 } + +local function updateVoiceMod(ply, mod) + if not IsValid(ply) then return end + mod = mod or ply.talkRangeMod or 2 + + if mod == 4 then mod = 3 end + if mod == 3 and ply:isGov() then + local veh = ply:GetVehicle() + if IsValid(veh) and IsValid(veh:GetParent()) and veh:GetParent().police then + mod = 4 + else + veh = octolib.use.getTrace(ply).Entity + if IsValid(veh) and veh.police then mod = 4 end + end + end + + ply:SetNetVar('TalkRange', ranges[mod] or ranges[2]) + ply.talkRangeMod = mod + +end + +netstream.Hook('IWannaChangeVoiceMod', updateVoiceMod) + +timer.Create('dbg-voice.updateMods', 4, 0, function() + octolib.func.throttle(player.GetAll(), 10, 0.3, updateVoiceMod) +end) diff --git a/garrysmod/addons/core-weapons/lua/autorun/client/dobrograd.lua b/garrysmod/addons/core-weapons/lua/autorun/client/dobrograd.lua new file mode 100644 index 0000000..5f85756 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/autorun/client/dobrograd.lua @@ -0,0 +1,7 @@ +hook.Add('PlayerSwitchWeapon', 'dbg.weaponGroups', function() + return true +end) + +hook.Add('PlayerFinishedLoading', 'dbg.weapons', function() + weapons.GetStored('gmod_tool').ShootSound = '' +end) diff --git a/garrysmod/addons/core-weapons/lua/autorun/client/octoweapons_init_cl.lua b/garrysmod/addons/core-weapons/lua/autorun/client/octoweapons_init_cl.lua new file mode 100644 index 0000000..fee3e5a --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/autorun/client/octoweapons_init_cl.lua @@ -0,0 +1,14 @@ +--air +hook.Add('Initialize', 'dbg-weps.init', function() + game.AddAmmoType({ + name = 'air', + dmgtype = bit.band(DMG_BULLET, DMG_NEVERGIB, DMG_DIRECT), + tracer = TRACER_LINE, + plydmg = 0, + npcdmg = 0, + force = 0, + minsplash = 5, + maxsplash = 2.5, + maxcarry = 100, + }) +end) diff --git a/garrysmod/addons/core-weapons/lua/autorun/dobrograd.lua b/garrysmod/addons/core-weapons/lua/autorun/dobrograd.lua new file mode 100644 index 0000000..7c0151c --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/autorun/dobrograd.lua @@ -0,0 +1,24 @@ +hook.Add('KeyPress', 'dbg.weapons', function(ply, key) + local attack1 = key == IN_ATTACK + local attack2 = key == IN_ATTACK2 + if not attack1 and not attack2 then return end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then return end + + -- called before PrimaryAttack / SecondaryAttack + if attack1 and wep.PrimaryAttackPressed then wep:PrimaryAttackPressed() end + if attack2 and wep.SecondaryAttackPressed then wep:SecondaryAttackPressed() end +end) + +hook.Add('KeyRelease', 'dbg.weapons', function(ply, key) + local attack1 = key == IN_ATTACK + local attack2 = key == IN_ATTACK2 + if not attack1 and not attack2 then return end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then return end + + if attack1 and wep.PrimaryAttackReleased then wep:PrimaryAttackReleased() end + if attack2 and wep.SecondaryAttackReleased then wep:SecondaryAttackReleased() end +end) \ No newline at end of file diff --git a/garrysmod/addons/core-weapons/lua/autorun/octoweapons_init_sh.lua b/garrysmod/addons/core-weapons/lua/autorun/octoweapons_init_sh.lua new file mode 100644 index 0000000..36e004c --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/autorun/octoweapons_init_sh.lua @@ -0,0 +1,52 @@ +local function addWeaponSound(soundName, path, distantPath, silenced) + sound.Add({ + name = soundName, + channel = CHAN_USER_BASE + 10, + volume = 1, + sound = path, + pitch = {95, 105}, + level = 75, + }) + + if distantPath then + sound.Add({ + name = soundName .. '-distant', + channel = CHAN_USER_BASE + 11, + volume = silenced and 0.05 or 1, + sound = distantPath, + pitch = {95, 105}, + level = silenced and 85 or 95, + }) + end +end + +addWeaponSound('beanbag.fire', 'weapons/beanbag/beanbagfire.wav', 'weapons/beanbag/beanbagfire.wav', true) +addWeaponSound('ak47.fire', 'dbg/weapons/ak47/cv47-1.wav', 'dbg/weapons/ak47/cv47-1-distant.wav') +addWeaponSound('aug.fire', 'dbg/weapons/aug/aug-1.wav', 'dbg/weapons/aug/aug-1-distant.wav') +addWeaponSound('awp.fire', 'dbg/weapons/awp/awm-1.wav', 'dbg/weapons/awp/awm-1-distant.wav') +addWeaponSound('deagle.fire', 'dbg/weapons/deagle/deagle-1.wav', 'dbg/weapons/deagle/deagle-1-distant.wav') +addWeaponSound('elite.fire', 'dbg/weapons/elite/elite-1.wav', 'dbg/weapons/elite/elite-1-distant.wav') +addWeaponSound('famas.fire', 'dbg/weapons/famas/famas-1.wav', 'dbg/weapons/famas/famas-1-distant.wav') +addWeaponSound('fiveseven.fire', 'dbg/weapons/fiveseven/fiveseven-1.wav', 'dbg/weapons/fiveseven/fiveseven-1-distant.wav') +addWeaponSound('g3sg1.fire', 'dbg/weapons/g3sg1/g3sg1-1.wav', 'dbg/weapons/g3sg1/g3sg1-1-distant.wav') +addWeaponSound('galil.fire', 'dbg/weapons/galil/galil-1.wav', 'dbg/weapons/galil/galil-1-distant.wav') +addWeaponSound('glock.fire', 'dbg/weapons/glock/glock-1.wav', 'dbg/weapons/glock/glock-1-distant.wav') +addWeaponSound('glock18.fire', 'dbg/weapons/glock/glock18-1.wav', 'dbg/weapons/glock/glock18-1-distant.wav') +addWeaponSound('m249.fire', 'dbg/weapons/m249/m249-1.wav', 'dbg/weapons/m249/m249-1-distant.wav') +addWeaponSound('m3.fire', 'dbg/weapons/m3/m3-1.wav', 'dbg/weapons/m3/m3-1-distant.wav') +addWeaponSound('m3nova.fire', 'dbg/weapons/m3/nova-1.wav', 'dbg/weapons/m3/nova-1-distant.wav') +addWeaponSound('m4a1.fire', 'dbg/weapons/m4a1/m4a4_1.wav', 'dbg/weapons/m4a1/m4a4_1_distant.wav') +addWeaponSound('m4a1s.fire', 'dbg/weapons/m4a1/m4a1_1s.wav', 'dbg/weapons/m4a1/m4a1_1s_distant.wav', true) +addWeaponSound('mac10.fire', 'dbg/weapons/mac10/mac10-1.wav', 'dbg/weapons/mac10/mac10-1-distant.wav') +addWeaponSound('mp5navy.fire', 'dbg/weapons/mp5navy/mp5-1.wav', 'dbg/weapons/mp5navy/mp5-1-distant.wav') +addWeaponSound('p228.fire', 'dbg/weapons/p228/p228-1.wav', 'dbg/weapons/p228/p228-1-distant.wav') +addWeaponSound('p90.fire', 'dbg/weapons/p90/p95-1.wav', 'dbg/weapons/p90/p95-1_distant.wav') +addWeaponSound('revolver.fire', 'dbg/weapons/revolver/revolver-1_01.wav', 'dbg/weapons/revolver/revolver-1_distant.wav') +addWeaponSound('scout.fire', 'dbg/weapons/scout/ssg08-1.wav', 'dbg/weapons/scout/ssg08-1-distant.wav') +addWeaponSound('sg550.fire', 'dbg/weapons/sg550/sg550-1.wav', 'dbg/weapons/sg550/sg550-1-distant.wav') +addWeaponSound('sg552.fire', 'dbg/weapons/sg552/sg556-1.wav', 'dbg/weapons/sg552/sg556-1-distant.wav') +addWeaponSound('tmp.fire', 'dbg/weapons/tmp/tmp-1.wav', 'dbg/weapons/tmp/tmp-1-distant.wav', true) +addWeaponSound('ump45.fire', 'dbg/weapons/ump45/ump45-1.wav', 'dbg/weapons/ump45/ump45-1-distant.wav') +addWeaponSound('usp.fire', 'dbg/weapons/usp/usp_unsil-1.wav', 'dbg/weapons/usp/usp_unsil-1-distant.wav') +addWeaponSound('usps.fire', 'dbg/weapons/usp/usp1.wav', 'dbg/weapons/usp/usp1-distant.wav', true) +addWeaponSound('xm1014.fire', 'dbg/weapons/xm1014/xm1014-1.wav', 'dbg/weapons/xm1014/xm1014-1-distant.wav') diff --git a/garrysmod/addons/core-weapons/lua/autorun/server/dobrograd.lua b/garrysmod/addons/core-weapons/lua/autorun/server/dobrograd.lua new file mode 100644 index 0000000..271f5c7 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/autorun/server/dobrograd.lua @@ -0,0 +1,325 @@ +local groups = { + blunt = { + 'weapon_octo_bottle', + 'weapon_octo_pan', + 'weapon_octo_pipe', + 'weapon_octo_pot', + 'weapon_octo_shovel', + 'weapon_cuff_rope', + }, + sharp = { + 'weapon_octo_axe', + 'weapon_octo_bottle_broken', + 'weapon_octo_hook', + 'weapon_octo_knife', + 'weapon_octo_pickaxe', + }, + pistols = { + 'weapon_octo_air_p228', + 'weapon_octo_air_glock17', + 'weapon_octo_deagle', + 'weapon_octo_dualelites', + 'weapon_octo_fiveseven', + 'weapon_octo_glock', + 'weapon_octo_glock17', + 'weapon_octo_p228', + 'weapon_octo_usp', + 'weapon_octo_usps', + 'weapon_octo_357', + }, + smgs = { + 'weapon_octo_air_ump45', + 'weapon_octo_mac10', + 'weapon_octo_mp5', + 'weapon_octo_p90', + 'weapon_octo_tmp', + 'weapon_octo_ump45', + }, + rifles = { + 'weapon_octo_air_ak', + 'weapon_octo_air_aug', + 'weapon_octo_air_m4a1', + 'weapon_octo_ak', + 'weapon_octo_aug', + 'weapon_octo_famas', + 'weapon_octo_g3sg1', + 'weapon_octo_galil', + 'weapon_octo_m4a1', + 'weapon_octo_sg550', + 'weapon_octo_sg552', + }, + snipers = { + 'weapon_octo_air_scout', + 'weapon_octo_awp', + 'weapon_octo_scout', + 'weapon_octo_sg550', + }, + shotguns = { + 'weapon_octo_air_m3', + 'weapon_octo_beanbag', + 'weapon_octo_m3', + 'weapon_octo_xm1014', + }, + heavy = { + 'weapon_octo_m249', + }, + grenades = { + -- nothing yet + }, + allowAll = { + 'weapon_cuffed', + 'guitar', + 'weapon_flashlight', + 'weapon_flashlight_uv', + 'weapon_zombie', + 'dbg_hands', + 'dbg_cigarette', + 'weapon_simfillerpistol', + 'gmod_camera', + 'weapon_octo_fishing_rod', + } +} + +dbgWeaponGroups = {} +for group, weps in pairs(groups) do + for _, class in ipairs(weps) do + dbgWeaponGroups[class] = group + end +end + +local function run() + if not GAMEMODE then return end + + -- insert darkrp weapons + for _, class in ipairs(GAMEMODE.Config.DefaultWeapons) do dbgWeaponGroups[class] = 'allowAll' end + for _, class in ipairs(GAMEMODE.Config.AdminWeapons) do dbgWeaponGroups[class] = 'allowAll' end +end +hook.Add('darkrp.loadModules', 'dbg-weapons', run) +run() + +local minTime = 5 * 60 * 60 +hook.Add( 'PlayerCanPickupWeapon', 'weapongroups', function( ply, wep ) + local check = true + local class = wep:GetClass() + + if dbgWeaponGroups[class] == 'allowAll' + or ply:jobHasWeapon(class) then + check = false + end + + if check then + if not CFG.dev and ply:GetTimeTotal() < minTime then + ply:Notify('ooc', 'Оружие можно использовать только после 5 часов игры') + return false + end + + local job = ply:getJobTable() + local crimeDenied = ply:CheckCrimeDenied() + if (crimeDenied and not job.police) and wep.IsLethal then + if crimeDenied == true then + ply:Notify('ooc', 'Ты не можешь использовать оружие') + return false + elseif crimeDenied then + ply:Notify('ooc', 'Ты сможешь использовать оружие ' .. octolib.time.formatIn(crimeDenied)) + return false + end + end + + local allowedWeapons = job.allowedWeapons + if allowedWeapons ~= nil then + local allowed = false + for _, group in ipairs(allowedWeapons) do + if dbgWeaponGroups[wep:GetClass()] == group then + allowed = true + end + end + + if not allowed then + ply:Notify('ooc', L.youcanusethisweapon) + return false + end + end + end +end) + +local delaysGroups = { + blunt = 1, + sharp = 1, + pistols = 1.5, + smgs = 2, + rifles = 3, + snipers = 5, + heavy = 8, + shotguns = 2.5, + grenades = 1, +} + +local delaysWeps = { + -- dbg_hands = 0.5, + -- gmod_tool = 0, + -- weapon_physgun = 0, + stungun = 1.5, + stunstick = 0.75, + dbg_shield = 4, +} + +local onlyWalk = { + smgs = true, + rifles = true, + snipers = true, + heavy = true, + shotguns = true, +} + +local allowHolster = { + weapon_flashlight = true, + gmod_camera = true, +} + +local hasHands = { + dbg_hands = true, + weapon_physgun = true, + gmod_tool = true, + weapon_octo_fishing_rod = true, + dbg_admingun = true, + dbg_punisher = true, + weapon_flashlight = true, +} + +hook.Add('PlayerSpawn', 'dbg-weapons', function(ply) + local inv = ply:GetInventory() + if ply:GetActiveWeaponClass() == 'dbg_hands' and inv and not inv:GetContainer('_hand') then + inv:AddContainer('_hand', octoinv.defaultHand):QueueSync() + end +end) + +hook.Add('PlayerSwitchWeapon', 'dbg.weaponGroups', function(ply, old, new) + + local silent = ply:KeyDown(IN_WALK) or ply.silentEquip + local inv = ply:GetInventory() + if inv and IsValid(new) then + local class = new:GetClass() + local cont = inv:GetContainer('_hand') + if cont and not hasHands[class] then + cont:Remove(true, false) + elseif class == 'dbg_hands' or hasHands[class] then + if not cont then inv:AddContainer('_hand', octoinv.defaultHand):QueueSync() end + end + end + + if not ply.weaponReady then + local class = new:GetClass() + if ply.bleeding and dbgWeaponGroups[class] ~= 'allowAll' then + ply:Notify('warning', 'Ты при смерти') + if IsValid(new) and ply:HasWeapon(class) and not allowHolster[class] and hook.Call('canDropWeapon', GAMEMODE, ply, new) then + ply:dropDRPWeapon(new) + end + return true + end + if class == 'stunstick' then silent = true end + local time = delaysGroups[dbgWeaponGroups[class] or ''] or delaysWeps[class] or 0 + local veh = ply:GetVehicle() + if silent then time = time * 3 end + if time > 0 then ply.lastAim = CurTime() end + + octolib.stopAnimations(ply) + ply:DelayedAction('switchWeapon', L.change_weapon, { + time = ply:isCP() and time / 1.75 or time, + check = function() + if not IsValid(ply) or ply:KeyDown(IN_SPEED) and onlyWalk[dbgWeaponGroups[class] or ''] then return false end + + local curWep = ply:GetActiveWeapon() + if (curWep ~= old and IsValid(curWep) and IsValid(old)) or not IsValid(new) then return false end + + return ply:Alive() and ply:GetVehicle() == veh and ply:GetNetVar('ScareState', 0) < 0.6 + end, + succ = function() + ply.weaponReady = true + ply:SelectWeapon(class) + end, + fail = function() + if IsValid(new) and ply:HasWeapon(class) and not allowHolster[class] and hook.Call('canDropWeapon', GAMEMODE, ply, new) then + ply:dropDRPWeapon(new) + end + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + if not silent then + ply:EmitSound('npc/combine_soldier/gear' .. math.random(1, 2) .. '.wav', 30) + end + end, + }) + + return true + else + ply.weaponReady = nil + end + + if IsValid(old) and not allowHolster[old:GetClass()] and hook.Call('canDropWeapon', GAMEMODE, ply, old) then + ply:HolsterWeapon(old) + return true + end + +end) + +hook.Add('EntityTakeDamage', 'dbg.weapons', function(victim, dmgInfo) + + if not IsValid(victim) then return end + + local attacker, wep + + attacker = dmgInfo:GetAttacker() + if IsValid(attacker) then + if attacker:IsPlayer() then + wep = attacker:GetActiveWeapon() + else + wep = attacker + if wep.IsSimfphyscar then + attacker = IsValid(wep.DriverSeat) and wep.DriverSeat:GetDriver() + end + end + end + + local dmgInflictor = dmgInfo:GetInflictor() + if IsValid(dmgInflictor) then + if dmgInflictor:IsPlayer() then + attacker = dmgInflictor + wep = attacker:GetActiveWeapon() + else + wep = dmgInflictor + if wep.IsSimfphyscar then + attacker = IsValid(wep.DriverSeat) and wep.DriverSeat:GetDriver() + end + end + end + + if IsValid(attacker) and (attacker:IsWeapon() or attacker.Projectile) then + wep = attacker + end + + if IsValid(dmgInflictor) and (dmgInflictor:IsWeapon() or dmgInflictor.Projectile) then + wep = dmgInflictor + end + + hook.Run('EntityDamage', victim, attacker, wep, dmgInfo) + +end) + +local ply = FindMetaTable 'Player' + +function ply:RemoveWeapon(wep) + + if not IsValid(wep) or self:GetWeapon(wep:GetClass()) ~= wep then return end + + if self:GetActiveWeapon() == wep then + timer.Simple(0.5, function() + if not IsValid(self) then return end + self:SelectWeapon('dbg_hands') + end) + end + wep:Remove() + +end diff --git a/garrysmod/addons/core-weapons/lua/autorun/server/octoweapons_init_sv.lua b/garrysmod/addons/core-weapons/lua/autorun/server/octoweapons_init_sv.lua new file mode 100644 index 0000000..a8480f4 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/autorun/server/octoweapons_init_sv.lua @@ -0,0 +1,26 @@ +--air +hook.Add('Initialize', 'dbg-weps.init', function() + game.AddAmmoType({ + name = 'air', + dmgtype = bit.band(DMG_BULLET, DMG_NEVERGIB, DMG_DIRECT), + tracer = TRACER_LINE, + plydmg = 0, + npcdmg = 0, + force = 0, + minsplash = 5, + maxsplash = 2.5, + maxcarry = 100, + }) +end) + +hook.Add('EntityTakeDamage', 'dbg-weps.air', function(ply, dmg) + + if not IsValid(ply) or not ply:IsPlayer() then return end + if dmg:GetDamageType() ~= DMG_BULLET and dmg:GetDamageType() ~= DMG_BLAST then return end + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and not wep.IsLethal and not (GAMEMODE.Config.DisallowDrop[wep:GetClass()] or ply:jobHasWeapon(wep:GetClass())) then + ply:HolsterWeapon(wep) + end + +end) diff --git a/garrysmod/addons/core-weapons/lua/cmenu/items/armor.lua b/garrysmod/addons/core-weapons/lua/cmenu/items/armor.lua new file mode 100644 index 0000000..e26b15e --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/cmenu/items/armor.lua @@ -0,0 +1,10 @@ +octogui.cmenu.registerItem('inv', 'armor', { + check = function(ply) + return ply:Alive() and ply:Armor() > 0 and ply:GetNetVar('armor') == ply:Armor() + end, + text = 'Снять бронежилет', + icon = octolib.icons.silk16('shield_delete'), + action = function() + netstream.Start('dbg-armor.unwear') + end, +}) diff --git a/garrysmod/addons/core-weapons/lua/cmenu/items/grenade_launcher.lua b/garrysmod/addons/core-weapons/lua/cmenu/items/grenade_launcher.lua new file mode 100644 index 0000000..b32ae9a --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/cmenu/items/grenade_launcher.lua @@ -0,0 +1,24 @@ +octogui.cmenu.registerItem('inv', 'glauncher', { + text = 'Гранатомет', + icon = octolib.icons.silk16('grenade_launcher'), + check = function() + local wep = LocalPlayer():GetActiveWeapon() + return IsValid(wep) and wep:GetClass() == 'weapon_octo_grenade_launcher' + end, + build = function(sm) + local wep = LocalPlayer():GetActiveWeapon() + local charges, cfg = wep:GetNetVar('charges', {}), wep.ChargeTypes + local curCharge = wep:GetNetVar('charge') + local delayed = wep:GetNetVar('chargeDelayed') + for k, v in pairs(cfg) do + local mo, pm = sm:AddSubMenu(('%s (%d/%d%s)'):format(v.name, charges[k] or 0, v.max, curCharge == k and ', заряжен' or '')) + mo:AddOption('Мгновенного действия', function() + netstream.Start('dbg-glauncher.applyCharge', k, false) + end):SetChecked(curCharge == k and not delayed) + mo:AddOption('Замедленного действия', function() + netstream.Start('dbg-glauncher.applyCharge', k, true) + end):SetChecked(curCharge == k and delayed) + pm:SetIcon(octolib.icons.silk16(v.icon)) + end + end, +}) diff --git a/garrysmod/addons/core-weapons/lua/cmenu/items/weapon.lua b/garrysmod/addons/core-weapons/lua/cmenu/items/weapon.lua new file mode 100644 index 0000000..083cc26 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/cmenu/items/weapon.lua @@ -0,0 +1,9 @@ +octogui.cmenu.registerItem('inv', 'weapon', { + text = L.weapons, + icon = octolib.icons.silk16('gun'), + options = { + {text = L.drop, icon = octolib.icons.silk16('arrow_right'), say = '/dropweapon'}, + {text = L.holster, icon = octolib.icons.silk16('arrow_left'), say = '/holsterweapon'}, + {text = 'Проверить магазин', icon = octolib.icons.silk16('information'), say = '/ammo'}, + }, +}) diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_airammo/shared.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_airammo/shared.lua new file mode 100644 index 0000000..e27ba61 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_airammo/shared.lua @@ -0,0 +1,43 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = 'Пули для страйкбола' +ENT.Category = L.dobrograd +ENT.Author = "Wani4ka" +ENT.Contact = "4wk@wani4ka.ru" + +if SERVER then + AddCSLuaFile() +end + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +function ENT:Initialize() + if SERVER then + self:SetModel('models/items/ammocrate_smg1.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + end +end + +function ENT:Use(ply) + + if not ply:IsPlayer() then return end + local wep = ply:GetActiveWeapon() + if not wep.Primary or wep.Primary.Ammo ~= 'air' then + return ply:Notify('warning', 'Эти пульки подходят только для страйкбольного оружия') + end + if wep:Ammo1() >= 100 then + return ply:Notify('warning', 'У тебя уже полный запас пулек') + end + + ply:GiveAmmo(100 - wep:Ammo1(), 'air') + wep:DefaultReload(ACT_VM_RELOAD) + + self:SetSequence(2) -- Open + timer.Create('airammo.close' .. self:EntIndex(), 1, 1, function() + if IsValid(self) then self:SetSequence(1) end -- Close + end) + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_apple.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_apple.lua new file mode 100644 index 0000000..a226c65 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_apple.lua @@ -0,0 +1,12 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Метательное яблоко' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/props_everything/applered.mdl' +ENT.LifeTime = 15 diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_air.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_air.lua new file mode 100644 index 0000000..fe835e7 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_air.lua @@ -0,0 +1,54 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Страйкбольная' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/weapons/w_eq_fraggrenade_thrown.mdl' +ENT.LifeTime = 5 +ENT.SoundExplode = {'weapons/357_fire2.wav', 100, 150, 1} + +function ENT:OnExplode() + + for _,v in ipairs(ents.FindInSphere(self:GetPos(), 100)) do + if v:IsPlayer() and self:CanDamage(v) then + local wep = v:GetActiveWeapon() + if IsValid(wep) and wep:GetClass():find('_air') then + v:dropDRPWeapon(wep) + end + + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BLAST) + dmg:SetDamage(1) + v:TakeDamageInfo(dmg) + end + end + +end + +function ENT:ExplodeEffect() + + local e = ParticleEmitter(Vector()) + if IsValid(e) then + for i = 1, 20 do + local p = e:Add('effects/spark', self:GetPos()) + p:SetDieTime(0.1) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(2) + p:SetEndSize(0) + p:SetVelocity(VectorRand() * 100) + end + end + + timer.Simple(2, function() + if IsValid(e) then e:Finish() end + end) + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_frag.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_frag.lua new file mode 100644 index 0000000..da99087 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_frag.lua @@ -0,0 +1,24 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Осколочная' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/csgo/weapons/w_eq_fraggrenade.mdl' +ENT.LifeTime = 5 + +ENT.SoundHit = { Sound('Flashbang.Bounce') } + +function ENT:OnExplode() + + local attacker = IsValid(self.owner) and self.owner or self + util.BlastDamage(self, attacker, self:GetPos(), 400, 150) + local e = EffectData() + e:SetOrigin(self:GetPos()) + util.Effect('Explosion', e) + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/cl_init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/cl_init.lua new file mode 100644 index 0000000..90693c0 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/cl_init.lua @@ -0,0 +1,92 @@ +include('shared.lua') + +function ENT:Think() + + if not IsValid(self.em) then return end + if not self:GetNetVar('exploded') then + return self.BaseClass.Think(self) + end + + if LocalPlayer():EyePos():DistToSqr(self:GetPos()) <= self.RadiusSqr*100 then + local p = self.em:Add(string.format('particle/smokesprites_00%02d', math.random(1,10)), self:GetPos()) + if p then + local dir = VectorRand() + Vector(0, 0, 0.4) + p:SetVelocity(dir * 200) + p:SetDieTime(math.Rand(6, 8)) + p:SetStartAlpha(math.Rand(100, 150)) + p:SetEndAlpha(0) + p:SetStartSize(math.Rand(40, 50)) + p:SetEndSize(math.Rand(150, 200)) + p:SetRoll(math.Rand(0, 360)) + p:SetRollDelta(math.Rand(-0.5, 0.5)) + local col = math.Rand(135, 145) + p:SetColor(col, col, col) + p:SetAirResistance(200) + p:SetGravity(dir * math.random(50, 75)) + p:SetCollide(true) + end + end + + self:SetNextClientThink(CurTime() + 0.2) + return true + +end + +local function hasGasMask(ply) + local hMask = ply:GetNetVar('hMask') + return hMask and hMask[1] == 'gasmask' +end + +local affectedBy, affectedRadius +local grenades = {'ent_dbg_grenade_gas', 'ent_dbg_grenade_shock'} +timer.Create('dbg-grenades.gas', 0.5, 0, function() + + local lp = LocalPlayer() + if IsValid(lp) and not hasGasMask(lp) and not (lp:GetNetVar('Ghost') or lp:GetNetVar('Invisible')) then + affectedBy, affectedRadius = nil + local ourPos = lp:GetShootPos() + for _, class in ipairs(grenades) do + for _, v in ipairs(ents.FindByClass(class)) do + if v:GetNetVar('exploded') and ourPos:DistToSqr(v:GetPos()) <= v.RadiusSqr and v:CanDamage(lp, 'eyes') then + affectedBy = v + affectedRadius = v.RadiusSqr + return + end + end + end + end + + affectedBy = nil + +end) + +local effectAmount = 0 +local xRand, yRand = math.Rand(1, 3), math.Rand(1, 3) +hook.Add('RenderScreenspaceEffects', 'dbg-grenades.gas', function() + + local lp = LocalPlayer() + + if IsValid(affectedBy) then + local dst = (affectedRadius - lp:GetShootPos():DistToSqr(affectedBy:GetPos())) / affectedRadius + effectAmount = math.min(effectAmount + dst * FrameTime(), 1) + end + + if effectAmount > 0 then + DrawMaterialOverlay('effects/water_warp01', 0.5 * effectAmount) + DrawColorModify({ + ['$pp_colour_contrast'] = 1 - effectAmount * 0.8, + ['$pp_colour_colour'] = 1 - effectAmount, + ['$pp_colour_brightness'] = effectAmount * 0.5, + }) + + local ct = CurTime() + local off = effectAmount * 10 + dbgView.lookOff.p = math.sin(ct * yRand) * off + dbgView.lookOff.y = math.sin(ct * xRand) * off + end + + if effectAmount > 0 then + effectAmount = math.Approach(effectAmount, 0, FrameTime() * 0.01) + end + +end) diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/init.lua new file mode 100644 index 0000000..11ce95e --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/init.lua @@ -0,0 +1,22 @@ +AddCSLuaFile('cl_init.lua') +AddCSLuaFile('shared.lua') +include('shared.lua') + +function ENT:Think() + + if not self:GetNetVar('exploded') then return end + + local ourPos = self:GetPos() + for _,v in ipairs(ents.FindInSphere(ourPos, self.Radius)) do + if v:IsPlayer() and v:GetShootPos():DistToSqr(ourPos) <= self.RadiusSqr and self:CanDamage(v, 'eyes') then + timer.Simple(math.random(0.5,1.5), function() + if IsValid(v) then + v:EmitSound('ambient/voices/cough'..math.random(1,4)..'.wav') + end + end) + end + end + + self:NextThink(CurTime() + 2) + return true +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/shared.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/shared.lua new file mode 100644 index 0000000..16847f7 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_gas/shared.lua @@ -0,0 +1,47 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Газовая' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +ENT.Model = 'models/csgo/weapons/w_eq_smokegrenade.mdl' +ENT.LifeTime = 5 +ENT.SoundExplode = {'physics/plastic/plastic_barrel_impact_bullet1.wav', 100, 100, 1} + +ENT.SoundExplode = {'weapons/smokegrenade/sg_explode.wav', 100, 100, 1} +ENT.SoundHit = {'weapons/smokegrenade/grenade_hit1.wav'} +ENT.Radius = 250 +ENT.RadiusSqr = ENT.Radius ^ 2 + +local function hasGasMask(ply) + local hMask = ply:GetNetVar('hMask') + return hMask and hMask[1] == 'gasmask' +end + +function ENT:CanDamage(ply) + return not hasGasMask(ply) and self.BaseClass.CanDamage(self, ply, 'eyes') +end + +function ENT:OnExplode() + + self:SetNetVar('exploded', true) + + timer.Simple(120, function() + if IsValid(self) then + for _,v in ipairs(ents.FindInSphere(self:GetPos(), 200)) do + if v:IsPlayer() and self:CanDamage(v) then + v:SetLocalVar('gas', CurTime() + 10) + timer.Simple(10.5, function() + if IsValid(v) then v:SetLocalVar('gas') end + end) + end + end + self:Remove() + end + end) + + return true + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/cl_init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/cl_init.lua new file mode 100644 index 0000000..82b5ad0 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/cl_init.lua @@ -0,0 +1,34 @@ +include('shared.lua') + +function ENT:Think() + + if not IsValid(self.em) then return end + if not self:GetNetVar('exploded') then + return self.BaseClass.Think(self) + end + + if LocalPlayer():EyePos():DistToSqr(self:GetPos()) <= 4000000 then + self:SetNoDraw(true) + local p = self.em:Add(string.format('particle/smokesprites_00%02d', math.random(1,10)), self:GetPos()) + if p then + local dir = VectorRand() + Vector(0, 0, 0.4) + p:SetVelocity(dir * 100) + p:SetDieTime(math.Rand(6, 8)) + p:SetStartAlpha(math.Rand(100, 150)) + p:SetEndAlpha(0) + p:SetStartSize(math.Rand(20, 50)) + p:SetEndSize(math.Rand(100, 150)) + p:SetRoll(math.Rand(0, 360)) + p:SetRollDelta(math.Rand(-0.5, 0.5)) + local col = math.Rand(135, 145) + p:SetColor(col, col, col) + p:SetAirResistance(200) + p:SetGravity(dir * math.random(50, 75)) + p:SetCollide(true) + end + end + + self:SetNextClientThink(CurTime() + 0.2) + return true + +end \ No newline at end of file diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/init.lua new file mode 100644 index 0000000..bb5f32d --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/init.lua @@ -0,0 +1,105 @@ +AddCSLuaFile('cl_init.lua') +AddCSLuaFile('shared.lua') +include('shared.lua') + +local function lookingAt(ply, ent) + local aim = ply:EyeAngles():Forward() + local entVector = ent:GetPos() - ply:GetShootPos() + local dot = aim:Dot(entVector) / entVector:Length() + return dot * 8 >= math.pi +end + +function ENT:Think() + + if not self:GetNetVar('exploded') then return end + + for _,v in ipairs(ents.FindInSphere(self:GetPos(), 200)) do + if v:IsPlayer() and self:CanDamage(v, 'eyes') then + timer.Simple(math.random(0.5,1.5), function() + if IsValid(v) then + v:EmitSound('ambient/voices/cough'..math.random(1,4)..'.wav') + end + end) + end + end + + self:NextThink(CurTime() + 2) + return true +end + +function ENT:OnExplode() + self:SetNetVar('exploded', true) + for _,v in ipairs(ents.FindInSphere(self:GetPos(), 400)) do + if not v:IsPlayer() then continue end + if not self:CanDamage(v, 'eyes') then + netstream.Start(v, 'dbg-grenades.flash', self) + continue + end + local looking = lookingAt(v, self) + if looking then + netstream.Start(v, 'dbg-grenades.shock', 16, 255, 12) + v:SetEyeAngles(v:EyeAngles() + Angle(math.random(-45, 45), math.random(-45, 45))) + v:MoveModifier('shock', { + norun = true, + walkmul = 0.5, + }) + v:SetDSP(38) -- distorted speaker + timer.Simple(0.25, function() + if IsValid(v) then + v:SetDSP(36) -- shock muffle 2 + end + end) + timer.Simple(1.75, function() + if IsValid(v) then + v:SetDSP(33) -- explosion ring 2 + end + end) + timer.Simple(2, function() + if IsValid(v) then + v:SetDSP(16) + end + end) + timer.Simple(16, function() + if IsValid(v) then + v:SetDSP(1) + v:MoveModifier('shock', nil) + end + end) + else + netstream.Start(v, 'dbg-grenades.shock', 4, 100, 4) + v:SetEyeAngles(v:EyeAngles() + Angle(math.random(-15, 15), math.random(-15, 15))) + v:SetDSP(16) + timer.Simple(4, function() + if IsValid(v) then + v:SetDSP(1) + end + end) + end + if self:CanDamage(v) then + local wep = v:GetActiveWeapon() + if IsValid(wep) and wep:GetClass():find('_octo') then + if not GAMEMODE.Config.DisallowDrop[wep:GetClass()] and not v:jobHasWeapon(wep:GetClass()) then + local ent = v:dropDRPWeapon(wep) + if IsValid(ent) and wep.IsLethal then + ent.isEvidence = true + end + else + v:SelectWeapon('dbg_hands') + end + end + + local dmg = DamageInfo() + dmg:SetDamageType(DMG_BLAST) + dmg:SetDamage(10) + v:TakeDamageInfo(dmg) + end + end + + timer.Simple(45, function() + if IsValid(self) then + self:Remove() + end + end) + + return true +end \ No newline at end of file diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/shared.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/shared.lua new file mode 100644 index 0000000..20179eb --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_shock/shared.lua @@ -0,0 +1,48 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Шоковая' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/csgo/weapons/w_eq_decoy.mdl' +ENT.LifeTime = 3 +ENT.SoundExplode = {'weapons/357_fire2.wav', 100, 100, 1} +ENT.SoundHit = {'weapons/smokegrenade/grenade_hit1.wav'} +ENT.Radius = 250 +ENT.RadiusSqr = ENT.Radius ^ 2 + +function ENT:ExplodeEffect() + + local e = ParticleEmitter(Vector()) + if IsValid(e) then + for i = 1, 20 do + local p = e:Add('effects/spark', self:GetPos()) + p:SetDieTime(0.1) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(2) + p:SetEndSize(0) + p:SetVelocity(VectorRand() * 100) + end + end + + timer.Simple(2, function() + if IsValid(e) then e:Finish() end + end) + +end + +local function hasGasMask(ply) + local hMask = ply:GetNetVar('hMask') + return hMask and hMask[1] == 'gasmask' +end + +function ENT:CanDamage(ply) + return not hasGasMask(ply) and self.BaseClass.CanDamage(self, ply, 'eyes') +end \ No newline at end of file diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_smoke.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_smoke.lua new file mode 100644 index 0000000..897d57f --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_smoke.lua @@ -0,0 +1,84 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Дымовая' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/csgo/weapons/w_eq_smokegrenade.mdl' +ENT.LifeTime = 3 + +ENT.SoundExplode = {'weapons/smokegrenade/sg_explode.wav', 100, 100, 1} +ENT.SoundHit = {'weapons/smokegrenade/grenade_hit1.wav'} + +if CLIENT then + + function ENT:Think() + if not IsValid(self.em) then return end + if not self:GetNetVar('exploded') then + return self.BaseClass.Think(self) + end + + if LocalPlayer():EyePos():DistToSqr(self:GetPos()) <= 4000000 then + local particle = self.em:Add(string.format('particle/smokesprites_00%02d', math.random(1,10)), self:GetPos()) + if particle then + local dir = VectorRand() + Vector(0, 0, 0.8) + dir.z = dir.z * 0.5 + particle:SetVelocity(dir * 200) + particle:SetDieTime(math.Rand(6, 8)) + particle:SetStartAlpha(math.Rand(220, 255)) + particle:SetEndAlpha(0) + particle:SetStartSize(math.Rand(40, 50)) + particle:SetEndSize(math.Rand(150, 200)) + particle:SetRoll(math.Rand(0, 360)) + particle:SetRollDelta(math.Rand(-0.5, 0.5)) + local col = math.Rand(135, 145) + particle:SetColor(col, col, col) + particle:SetAirResistance(200) + particle:SetGravity(dir * math.random(25, 50)) + particle:SetCollide(true) + end + end + + self:SetNextClientThink(CurTime() + 0.1) + return true + end + +else + + function ENT:Think() + if not self:GetNetVar('exploded') then return end + + for _,v in ipairs(ents.FindInSphere(self:GetPos(), 150)) do + if v:IsPlayer() and self:CanDamage(v, 'eyes') then + timer.Simple(math.random(0.5,1.5), function() + if IsValid(v) then + v:EmitSound('ambient/voices/cough'..math.random(1,4)..'.wav') + end + end) + end + end + + self:NextThink(CurTime() + 2) + return true + end + +end + +function ENT:OnExplode() + + self:SetNetVar('exploded', true) + timer.Simple(30, function() + if IsValid(self) then + self:Remove() + end + end) + + return true + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/cl_init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/cl_init.lua new file mode 100644 index 0000000..8a66d8a --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/cl_init.lua @@ -0,0 +1,58 @@ +include('shared.lua') + +local effectLen = 2 +local start, endd, alpha, len = -1, -1, -1, -1 +local xRand, yRand = 0, 0 + +local function createFlash(ent) + ent = IsValid(ent) and ent or LocalPlayer() + local flash = DynamicLight(ent:EntIndex()) + if flash then + flash.pos = ent:GetPos() + Vector(0,0,10) + flash.r = 255 + flash.g = 255 + flash.b = 255 + flash.brightness = 2 + flash.decay = 2000 + flash.size = 2048 + flash.dietime = CurTime() + 1.5 + end +end + +netstream.Hook('dbg-grenades.shock', function(en, rAlpha, rLen) + start = CurTime() + endd = start + en + alpha = rAlpha + len = rLen + xRand, yRand = math.Rand(2, 4), math.Rand(2, 4) + createFlash(LocalPlayer()) +end) +netstream.Hook('dbg-grenades.flash', createFlash) + +hook.Add('HUDPaint', 'dbg-grenades.shock', function() + if endd < CurTime() then return end + local val + if endd - CurTime() > len then + val = alpha + else + val = alpha * (endd - CurTime()) / len + end + + surface.SetDrawColor(255, 255, 255, math.Round(val)) + surface.DrawRect(0, 0, surface.ScreenWidth(), surface.ScreenHeight()) + --draw.DrawText(tostring(val), 'DermaLarge', 10, 10, color_black) +end) + +hook.Add('RenderScreenspaceEffects', 'dbg-grenades.shock', function() + local e = endd + effectLen + local ct = CurTime() + if e <= ct then return end + local l = len + effectLen + local val = (e - ct) / l + DrawMotionBlur(1 - val, 1, 0) + DrawBloom(3, 1/(val*alpha), 6, 6, 12, 0, 255, 255, 255) + + local off = 20 * val + dbgView.lookOff.p = math.sin(ct * yRand) * off + dbgView.lookOff.y = math.sin(ct * xRand) * off +end) diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/init.lua new file mode 100644 index 0000000..e52ff47 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/init.lua @@ -0,0 +1,98 @@ +AddCSLuaFile('cl_init.lua') +AddCSLuaFile('shared.lua') +include('shared.lua') + +function ENT:Initialize() + + local half = Vector(1.5, 0.5, 0.5) + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + if self.Mass then phys:SetMass(self.Mass) end + phys:Wake() + end + + local sd = self.SoundFlame + if sd and sd[1] then + local s = CreateSound(self, sd[1]) + s:SetSoundLevel(sd[2]) + s:PlayEx(sd[4], sd[3]) + self.sndFlame = s + end + +end + +function ENT:PhysicsCollide(data) + + if data.Speed > 50 and self.SoundHit then self:EmitSound(unpack(self.SoundHit)) end + + timer.Simple(self.LifeTime, function() + if not IsValid(self) then return end + self:Explode() + end) + +end + +local function lookingAt(ply, ent) + local aim = ply:EyeAngles():Forward() + local entVector = ent:GetPos() - ply:GetShootPos() + local dot = aim:Dot(entVector) / entVector:Length() + return dot * 8 >= math.pi +end + +function ENT:OnExplode() + + util.BlastDamage(self, self, self:GetPos(), 100, 35) + + for _,v in ipairs(ents.FindInSphere(self:GetPos(), 400)) do + if not v:IsPlayer() then continue end + if not self:CanDamage(v, 'eyes') then + netstream.Start(v, 'dbg-grenades.flash', self) + continue + end + local looking = lookingAt(v, self) + if looking then + netstream.Start(v, 'dbg-grenades.shock', 16, 255, 12) + v:SetEyeAngles(v:EyeAngles() + Angle(math.random(-45, 45), math.random(-45, 45))) + v:MoveModifier('shock', { + norun = true, + walkmul = 0.5, + }) + v:SetDSP(38) -- distorted speaker + timer.Simple(0.25, function() + if IsValid(v) then + v:SetDSP(36) -- shock muffle 2 + end + end) + timer.Simple(1.75, function() + if IsValid(v) then + v:SetDSP(33) -- explosion ring 2 + end + end) + timer.Simple(2, function() + if IsValid(v) then + v:SetDSP(16) + end + end) + timer.Simple(16, function() + if IsValid(v) then + v:SetDSP(1) + v:MoveModifier('shock', nil) + end + end) + else + netstream.Start(v, 'dbg-grenades.shock', 4, 100, 4) + v:SetEyeAngles(v:EyeAngles() + Angle(math.random(-15, 15), math.random(-15, 15))) + v:SetDSP(16) + timer.Simple(4, function() + if IsValid(v) then + v:SetDSP(1) + end + end) + end + end + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/shared.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/shared.lua new file mode 100644 index 0000000..785c392 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_grenade_stun/shared.lua @@ -0,0 +1,16 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Светошумовая' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/csgo/weapons/w_eq_flashbang.mdl' +ENT.LifeTime = 1 +ENT.SoundExplode = {'weapons/flashbang/flashbang_explode2.wav', 100, 100, 1} +ENT.SoundHit = {'weapons/smokegrenade/grenade_hit1.wav'} diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_petard.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_petard.lua new file mode 100644 index 0000000..07756c4 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_petard.lua @@ -0,0 +1,65 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Петарда' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +DEFINE_BASECLASS('ent_dbg_throwable') + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/weapons/Shotgun_shell.mdl' +ENT.LifeTime = 10 + +ENT.FlameEffect = 'effects/spark' +ENT.FlameAlpha = 255 +ENT.FlamePos = Vector(-3, 0, 0.8) +ENT.FlameDieTime = 0.1 +ENT.FlameSize = 2 +ENT.ExplosionAmount = 20 + +ENT.SoundFlame = {'ambient/gas/steam2.wav', 55, 175, 08} +ENT.SoundExplode = {'weapons/357_fire2.wav', 100, 150, 1} + +function ENT:ExplodeEffect() + + local e = ParticleEmitter(Vector()) + if IsValid(e) and self.ExplosionAmount > 0 then + for i = 1, self.ExplosionAmount do + local p = e:Add(self.FlameEffect, self:LocalToWorld(self.FlamePos)) + p:SetDieTime(self.FlameDieTime) + p:SetStartAlpha(self.FlameAlpha) + p:SetEndAlpha(0) + p:SetStartSize(self.FlameSize) + p:SetEndSize(0) + p:SetVelocity(VectorRand() * 100) + end + end + timer.Simple(2, function() + if IsValid(e) then e:Finish() end + end) + +end + + +function ENT:Initialize() + + BaseClass.Initialize(self) + + local half = Vector(1.5, 0.5, 0.5) + self:SetModel(self.Model) + self:PhysicsInitBox(-half, half) + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + if self.Mass then phys:SetMass(self.Mass) end + phys:Wake() + phys:SetMass(5) + end + +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_snowball.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_snowball.lua new file mode 100644 index 0000000..23927f4 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_snowball.lua @@ -0,0 +1,48 @@ +ENT.Base = 'ent_dbg_throwable' +ENT.PrintName = 'Снежок' +ENT.Category = 'Гранаты' +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.ExplodeAfterCollision = true + +if SERVER then + AddCSLuaFile() +end + +ENT.Model = 'models/weapons/w_snowball_thrown.mdl' +ENT.LifeTime = 0 + +if SERVER then + function ENT:PhysicsCollide(data, phys) + self.BaseClass.PhysicsCollide(self, data, phys) + + local ply = data.HitEntity + if IsValid(ply) and ply:IsPlayer() then + ply:ViewPunch(Angle(math.random(-5, 5), math.random(-5, 5), math.random(-5, 5))) + end + end +end + +if CLIENT then + function ENT:ExplodeEffect(pos) + local e = ParticleEmitter(Vector()) + + for _ = 1, 50 do + local p = e:Add('effects/splash2', pos + VectorRand()) + p:SetDieTime(3 + math.Rand(0, 2)) + p:SetStartAlpha(255) + p:SetStartSize(math.Rand(0.8, 1.2)) + p:SetEndAlpha(0) + p:SetEndSize(1) + p:SetGravity(Vector(0,0,-200)) + p:SetVelocity(VectorRand() * 50) + p:SetColor(255, 255, 255) + p:SetCollide(true) + p:SetAirResistance(-50) + end + + timer.Simple(5, function() + if IsValid(e) then e:Finish() end + end) + end +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/cl_init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/cl_init.lua new file mode 100644 index 0000000..a07c22d --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/cl_init.lua @@ -0,0 +1,42 @@ +include('shared.lua') + +function ENT:Initialize() + + self.em = ParticleEmitter(Vector()) + +end + +function ENT:Think() + + if self.FlameEffect then + local e = self.em + if IsValid(e) then + local p = e:Add(self.FlameEffect, self:LocalToWorld(self.FlamePos)) + p:SetDieTime(self.FlameDieTime) + p:SetStartAlpha(self.FlameAlpha) + p:SetEndAlpha(0) + p:SetStartSize(self.FlameSize) + p:SetEndSize(0) + p:SetVelocity((VectorRand() - self:GetForward()) * 40) + end + end + + self:NextThink(CurTime() + 0.1) + return true + +end + +function ENT:OnRemove() + + local e = self.em + timer.Simple(1, function() + if IsValid(e) then e:Finish() end + end) + +end + +netstream.Hook('throwable.explode', function(ent, pos) + if IsValid(ent) and ent.ExplodeEffect then + ent:ExplodeEffect(pos or ent:GetPos()) + end +end) diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/init.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/init.lua new file mode 100644 index 0000000..7a3416c --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/init.lua @@ -0,0 +1,116 @@ +AddCSLuaFile('cl_init.lua') +AddCSLuaFile('shared.lua') +include('shared.lua') + +function ENT:Initialize() + + local half = Vector(1.5, 0.5, 0.5) + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + if self.Mass then phys:SetMass(self.Mass) end + phys:Wake() + end + + local sd = self.SoundFlame + if sd and sd[1] then + local s = CreateSound(self, sd[1]) + s:SetSoundLevel(sd[2]) + s:PlayEx(sd[4], sd[3]) + self.sndFlame = s + end + + if not self.ExplodeAfterCollision then + timer.Simple(self.LifeTime, function() + if IsValid(self) then self:Explode() end + end) + end + +end + +function ENT:Throw(ply, forceMul) + + if not IsValid(ply) then return end + + local force = self.ThrowForce * forceMul + local pos, ang, vel = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_L_Hand') or 16) + local tr = util.TraceLine { start = ply:GetShootPos(), endpos = pos, filter = ply } + if tr.Hit then + pos = tr.HitPos + tr.HitNormal * 5 + vel = tr.HitNormal * force * 0.4 + else + vel = ply:GetAimVector() + -- vel.z = 0 + vel = (vel + VectorRand() * math.random() * 0.1) * force + end + + self:SetPos(pos) + self:SetAngles(ang) + self.LifeTime = self.LifeTime * math.Rand(0.8, 1.2) + + self:Spawn() + self:Activate() + self.owner = ply + ply:LinkEntity(self) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(vel) + end + +end + +function ENT:PhysicsCollide(data, phys) + + if data.Speed > 50 and self.SoundHit then self:EmitSound(unpack(self.SoundHit)) end -- play hit sound + + if data.Speed > self.BreakSensitivity and IsValid(data.HitEntity) and data.HitEntity:GetClass():find('breakable') then -- break surface if enough speed + data.HitEntity:Fire('Break') + phys:SetVelocity(data.OurOldVelocity) + end + + if self.ExplodeAfterCollision and not self.activated then -- make projectile explode + self.activated = true + + local pos = data.HitPos or self:GetPos() + if self.LifeTime > 0 then + timer.Simple(self.LifeTime, function() + if IsValid(self) then self:Explode(pos) end + end) + else + self:Explode(pos) + end + end + +end + +function ENT:Explode(pos) + + netstream.Start(nil, 'throwable.explode', self, pos) + + local sd = self.SoundExplode + if sd and sd[1] then + self:EmitSound(sd[1], sd[2], sd[3] + math.random(-10, 10), sd[4]) + end + + if self:OnExplode() ~= true then + self:Remove() + end + +end + +function ENT:OnRemove() + + if self.sndFlame then + self.sndFlame:Stop() + end + +end + +function ENT:OnExplode() + -- to be overridden +end diff --git a/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/shared.lua b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/shared.lua new file mode 100644 index 0000000..42c7fe2 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/entities/ent_dbg_throwable/shared.lua @@ -0,0 +1,37 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = '[основа]' +ENT.Category = 'Гранаты' + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.Model = 'models/weapons/w_eq_fraggrenade_thrown.mdl' +ENT.ThrowForce = 750 +ENT.LifeTime = 5 +ENT.BreakSensitivity = math.huge +ENT.ExplodeAfterCollision = false + +function ENT:CanDamage(target, att) + + if target:IsGhost() or (target.IsInvisible and target:IsInvisible()) then return false end + local attID = target:LookupAttachment(att or 'chest') + local tr = util.TraceLine({ + start = self:GetPos(), + endpos = target:GetAttachment(attID).Pos, + filter = { self, target }, + }) + + return not tr.HitWorld + +end + +-- ENT.SoundExplode = { 'weapons/357_fire2.wav', 75, 150, 1 } +-- ENT.SoundHit = { Sound('Flashbang.Bounce') } +-- ENT.SoundFlame = {'ambient/gas/steam2.wav', 55, 175, 08} + +-- ENT.FlameEffect = 'effects/spark' +-- ENT.FlameAlpha = 255 +-- ENT.FlamePos = Vector(-3, 0, 0.8) +-- ENT.FlameDieTime = 0.1 +-- ENT.FlameSize = 2 diff --git a/garrysmod/addons/core-weapons/lua/weapons/broom/shared.lua b/garrysmod/addons/core-weapons/lua/weapons/broom/shared.lua new file mode 100644 index 0000000..1a92e8d --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/broom/shared.lua @@ -0,0 +1,105 @@ +SWEP.Pos = Vector(-3, -3, 3) +SWEP.Ang = Angle(70, 180, 0) +SWEP.Base = 'weapon_base' +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.Category = L.dobrograd +SWEP.PrintName = L.broom +SWEP.ViewModel = '' +SWEP.Author = '' +SWEP.Instructions = '' +SWEP.WorldModel = 'models/props_junk/cardboard_box004a.mdl' +SWEP.HoldType = 'passive' + +-- game.AddDecal('transparent', 'decals/transparent') + +function SWEP:Initialize() + + self:SetWeaponHoldType(self.HoldType) + +end + +function SWEP:PrimaryAttack() + + if SERVER then + local tr = self.Owner:GetEyeTrace() + if self.Owner:EyePos():DistToSqr(tr.HitPos) > 10000 then return end + + self:SetHoldType('slam') + timer.Simple(0.1, function() self.Owner:SetAnimation(PLAYER_ATTACK1) end) + timer.Simple(0.9, function() self:SetHoldType(self.HoldType) end) + timer.Simple(0.3, function() + netstream.Start(nil, 'dbg-broom.clean', tr.HitPos, tr.HitNormal) + sound.Play('weapons/broom/sweep'..tostring(math.random(1,5))..'.mp3', tr.HitPos, 70, 100, 1) + end) + end + + self:SetNextPrimaryFire(CurTime() + 1) + +end + +netstream.Hook('dbg-broom.clean', function(pos, n) + + -- for i = 0, 2 do + -- util.Decal('transparent', pos + n, pos - n) + -- end + + local info = EffectData() + info:SetNormal(n) + info:SetOrigin(pos) + for i = 0, 10 do + info:SetScale(math.random(0.1,1)) + util.Effect('WheelDust', info) + end + +end) + +function SWEP:Think() + + -- nothing + +end + +function SWEP:SecondaryAttack() + + -- nothing + +end + +function SWEP:CreateModel() + if self.creatingModel or IsValid(self.WModel) then return end + self.creatingModel = true + timer.Simple(0, function() + if not IsValid(self) then return end + self.WModel = octolib.createDummy('models/props_c17/pushbroom.mdl', RENDERGROUP_OPAQUE) + self.WModel:SetNoDraw(true) + self.WModel:SetBodygroup(1, 1) + self.creatingModel = false + end) +end + +function SWEP:DrawWorldModel() + + if not IsValid(self.WModel) then + return self:CreateModel() + end + + local wm = self.WModel + if IsValid(self.Owner) then + local bone = self.Owner:LookupBone('ValveBiped.Bip01_L_Hand') + local pos, ang = self.Owner:GetBonePosition(bone) + + if bone then + ang:RotateAroundAxis(ang:Right(), self.Ang.p) + ang:RotateAroundAxis(ang:Forward(), self.Ang.y) + ang:RotateAroundAxis(ang:Up(), self.Ang.r) + wm:SetRenderOrigin(pos + ang:Right() * self.Pos.x + ang:Forward() * self.Pos.y + ang:Up() * self.Pos.z) + wm:SetRenderAngles(ang) + wm:DrawModel() + wm:SetModelScale(0.8, 0) + end + else + wm:DrawModel() + end + +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_357.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_357.lua new file mode 100644 index 0000000..6dbed84 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_357.lua @@ -0,0 +1,27 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Colt .357" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound("revolver.fire") +SWEP.Primary.DistantSound = Sound("revolver.fire-distant") +SWEP.Primary.Damage = 34 +SWEP.Primary.RPM = 300 +SWEP.Primary.ClipSize = 6 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 3.24 +SWEP.Primary.KickDown = 0.72 +SWEP.Primary.KickHorizontal = 0.02 + +SWEP.WorldModel = "models/weapons/w_357.mdl" +SWEP.MuzzlePos = Vector(12, -0.8, 4.7) +SWEP.MuzzleAng = Angle(-4, -0.5, 4) +SWEP.AimPos = Vector(-10.5, -0.79, 4.6) +SWEP.AimAng = Angle(-4, -0.5, 4) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_ak.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_ak.lua new file mode 100644 index 0000000..43c2a28 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_ak.lua @@ -0,0 +1,22 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. AK" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.RPM = 850 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0.6 +SWEP.Primary.KickHorizontal = 0.4 + +SWEP.WorldModel = "models/weapons/w_rif_ak47.mdl" +SWEP.AimPos = Vector(-4, -1.03, 5.65) +SWEP.AimAng = Angle(-8, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_aug.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_aug.lua new file mode 100644 index 0000000..a449986 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_aug.lua @@ -0,0 +1,34 @@ +SWEP.Base = "weapon_octo_base_zoom" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. AUG" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = "air" +SWEP.Primary.Damage = 0 +SWEP.Primary.Sound = Sound( "weapon.BulletImpact" ) +SWEP.Primary.DistantSound = Sound( "" ) +SWEP.Primary.RPM = 720 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0.52 +SWEP.Primary.KickHorizontal = 0.25 +SWEP.Primary.Spread = 0 + +SWEP.CanScare = false +SWEP.IsLethal = false +SWEP.ReloadTime = 2.2 +SWEP.WorldModel = "models/weapons/w_rif_aug.mdl" +SWEP.AimPos = Vector(-5, -0.68, 5.5) +SWEP.AimAng = Angle(-8, 0, 0) +SWEP.SightPos = Vector(-3.2, -0.67, 5.87) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1 +SWEP.SightFOV = 18 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_glock17.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_glock17.lua new file mode 100644 index 0000000..1763c16 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_glock17.lua @@ -0,0 +1,29 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. Glock 17" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.RPM = 825 +SWEP.Primary.ClipSize = 17 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 1.2 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.02 +SWEP.Primary.Automatic = false + +SWEP.PassiveHoldType = "normal" +SWEP.ActiveHoldType = "revolver" +SWEP.ReloadTime = 2.5 + +SWEP.WorldModel = "models/weapons/w_pist_glock18.mdl" +SWEP.Icon = 'octoteam/icons/gun_pistol.png' +SWEP.AimPos = Vector(-10.5, -1.19, 4) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_m3.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_m3.lua new file mode 100644 index 0000000..3143271 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_m3.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. M3" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "Weapon_M3.1" ) +SWEP.Primary.DistantSound = Sound( "" ) +SWEP.Primary.Automatic = false +SWEP.Primary.RPM = 70 +SWEP.Primary.ClipSize = 7 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 5.28 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.1 +SWEP.Primary.Distance = 1250 + +SWEP.WorldModel = "models/weapons/w_shot_m3super90.mdl" +SWEP.AimPos = Vector(-5, -0.94, 4.6) +SWEP.AimAng = Angle(-9, 0, 0) + +SWEP.Primary.NumShots = 8 +SWEP.Icon = 'octoteam/icons/gun_shotgun.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_m4a1.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_m4a1.lua new file mode 100644 index 0000000..4bd695c --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_m4a1.lua @@ -0,0 +1,23 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. M4A1" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.RPM = 750 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0.33 +SWEP.Primary.KickHorizontal = 0.65 + + +SWEP.WorldModel = "models/weapons/w_rif_m4a1.mdl" +SWEP.AimPos = Vector(-8, -0.97, 5.9) +SWEP.AimAng = Angle(-9, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_p228.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_p228.lua new file mode 100644 index 0000000..fa4ba86 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_p228.lua @@ -0,0 +1,29 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. P228" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.RPM = 400 +SWEP.Primary.ClipSize = 15 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.47 +SWEP.Primary.KickDown = 0.15 +SWEP.Primary.KickHorizontal = 0.01 + +SWEP.Primary.Automatic = false + +SWEP.PassiveHoldType = "normal" +SWEP.ActiveHoldType = "revolver" +SWEP.ReloadTime = 1.1 + +SWEP.WorldModel = "models/weapons/w_pist_p228.mdl" +SWEP.AimPos = Vector(-10.5, -1.16, 4.15) +SWEP.AimAng = Angle(-2, 5, 0) +SWEP.Icon = 'octoteam/icons/gun_pistol.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_scout.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_scout.lua new file mode 100644 index 0000000..e904b49 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_scout.lua @@ -0,0 +1,35 @@ +SWEP.Base = "weapon_octo_base_sniper" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. Scout" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = "air" +SWEP.Primary.Sound = Sound( "weapon.BulletImpact" ) +SWEP.Primary.DistantSound = Sound( "" ) +SWEP.Primary.Damage = 0 +SWEP.Primary.RPM = 60 +SWEP.Primary.ClipSize = 10 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 3.35 +SWEP.Primary.KickDown = 0.6 +SWEP.Primary.KickHorizontal = 0.01 +SWEP.Primary.Spread = 0 +SWEP.ReloadTime = 2.2 +SWEP.CanScare = false +SWEP.IsLethal = false -- shouldn't take karma whet shots fired + +SWEP.WorldModel = "models/weapons/w_snip_scout.mdl" +SWEP.AimPos = Vector(-0.5, -0.98, 5.8) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(1.6, -0.99, 6.17) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1.3 +SWEP.SightFOV = 10 +SWEP.SightZNear = 12 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_ump45.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_ump45.lua new file mode 100644 index 0000000..9abafff --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_air_ump45.lua @@ -0,0 +1,28 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Пневмат. UMP45" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "Weapon_UMP45.1" ) +SWEP.Primary.DistantSound = Sound( "" ) +SWEP.Primary.RPM = 600 +SWEP.Primary.ClipSize = 25 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.1 +SWEP.Primary.KickDown = 0.072 +SWEP.Primary.KickHorizontal = 1 + +SWEP.WorldModel = "models/weapons/w_smg_ump45.mdl" +SWEP.MuzzlePos = Vector(12, -0.9, 7) +SWEP.MuzzleAng = Angle(-9.5, 0, 0) +SWEP.AimPos = Vector(-7, -0.8, 5.4) +SWEP.AimAng = Angle(-9.5, 0, 0) + +SWEP.ActiveHoldType = "smg" diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_ak.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_ak.lua new file mode 100644 index 0000000..38c67fc --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_ak.lua @@ -0,0 +1,26 @@ +SWEP.Base = "weapon_octo_base_rifle" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "AK" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "ak47.fire" ) +SWEP.Primary.DistantSound = Sound( "ak47.fire-distant" ) +SWEP.Primary.Damage = 22 +SWEP.Primary.RPM = 850 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 1.2 +SWEP.Primary.KickHorizontal = 0.8 + + +SWEP.WorldModel = "models/weapons/w_rif_ak47.mdl" +SWEP.AimPos = Vector(-4, -1.03, 5.2) +SWEP.AimAng = Angle(-9, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_aug.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_aug.lua new file mode 100644 index 0000000..79cafac --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_aug.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_zoom" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "AUG" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "aug.fire" ) +SWEP.Primary.DistantSound = Sound( "aug.fire-distant" ) +SWEP.Primary.Damage = 26 +SWEP.Primary.RPM = 720 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 1.05 +SWEP.Primary.KickHorizontal = 0.5 +SWEP.Primary.Spread = 0 + +SWEP.WorldModel = "models/weapons/w_rif_aug.mdl" +SWEP.AimPos = Vector(-5, -0.68, 5.6) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(-3.2, -0.67, 5.87) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1 +SWEP.SightFOV = 18 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_awp.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_awp.lua new file mode 100644 index 0000000..4f84c3c --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_awp.lua @@ -0,0 +1,32 @@ +SWEP.Base = "weapon_octo_base_sniper" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "AWP" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "awp.fire" ) +SWEP.Primary.DistantSound = Sound( "awp.fire-distant" ) +SWEP.Primary.Damage = 50 +SWEP.Primary.RPM = 50 +SWEP.Primary.ClipSize = 10 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 6.7 +SWEP.Primary.KickDown = 1.2 +SWEP.Primary.KickHorizontal = 0.02 +SWEP.Primary.Spread = 0 +SWEP.ZoomAmount = 50 + +SWEP.WorldModel = "models/weapons/w_snip_awp.mdl" +SWEP.AimPos = Vector(-3, -0.8, 6.5) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(-0.52, -0.78, 6.88) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1.6 +SWEP.SightFOV = 10 +SWEP.SightZNear = 12 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_axe.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_axe.lua new file mode 100644 index 0000000..773041b --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_axe.lua @@ -0,0 +1,40 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = 'Топор' + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'sharp' +SWEP.HitDistance = 45 +SWEP.HitInclination = 0.4 +SWEP.HitPushback = 700 +SWEP.HitRate = 1 +SWEP.Damage = {34, 40} +SWEP.ScareMultiplier = 0.8 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Canister.ImpactHard') +SWEP.HitSoundBody = Sound('Weapon_Knife.Hit') +SWEP.PushSoundBody = Sound('Flesh.ImpactSoft') + +SWEP.Icon = 'octoteam/icons/gun_axe.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_axe.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_axe.mdl') +SWEP.ActiveHoldType = 'melee2' + +hook.Add('EntityTakeDamage', 'dbg-weapons.axe', function(ent, dmgInfo) + local attacker = dmgInfo:GetAttacker() + if IsValid(ent) and ent:IsDoor() and attacker:Team() == TEAM_FIREFIGHTER then + if math.random(1,3) == 2 then + ent:DoUnlock() + ent:Fire('setanimation', 'open', 0) + ent:EmitSound('physics/wood/wood_crate_break' .. math.random(1, 5) .. '.wav') + end + end +end) \ No newline at end of file diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/cl_init.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/cl_init.lua new file mode 100644 index 0000000..fc537f2 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/cl_init.lua @@ -0,0 +1,275 @@ +include 'shared.lua' + +SWEP.PrintName = 'Octothorp Weapon' +SWEP.Slot = 3 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawWeaponInfoBox = false +SWEP.BounceWeaponIcon = false + +CreateClientConVar('octoweapons_sight_resolution', 512, true) +local sightMaterials = {} +local maskPoly, sightResolution, sightRT, sightMaterial +local sightOverlayMaterial = Material('octoteam/overlays/scope1') + +local function updateSettings() + sightResolution = GetConVar('octoweapons_sight_resolution'):GetInt() + sightRT = GetRenderTarget('weaponSight-' .. sightResolution, sightResolution, sightResolution) + if not sightMaterials[sightResolution] then + sightMaterials[sightResolution] = CreateMaterial('weaponSight-' .. sightResolution, 'UnlitGeneric', {}) + end + sightMaterial = sightMaterials[sightResolution] + maskPoly = {} + + local x, y, r, seg = 0, 0, sightResolution / 2 - 1, 24 + maskPoly[#maskPoly + 1] = {x = x, y = y, u = 0.5, v = 0.5} + for i = 0, seg do + local a = math.rad((i / seg) * -360) + maskPoly[#maskPoly + 1] = {x = x + math.sin(a) * r, y = y + math.cos(a) * r, u = math.sin(a) / 2 + 0.5, v = math.cos(a) / 2 + 0.5} + end +end +updateSettings() +cvars.AddChangeCallback('octoweapons_sight_resolution', updateSettings, 'octoweapons') + +local isRenderingScope = false +local function renderScope(wep) + isRenderingScope = true + local pos, dir, ang = wep:GetShootPosAndDir() + render.PushRenderTarget(sightRT) + if util.TraceLine({ + start = pos - dir * 15, + endpos = pos + dir * ((wep.SightZNear or 5) + 2), + filter = LocalPlayer(), + }).Hit then + render.Clear(0,0,0, 255) + else + render.RenderView({ + origin = pos, + angles = ang, + fov = wep.SightFOV, + znear = wep.SightZNear, + }) + end + render.PopRenderTarget() + isRenderingScope = false +end + +function SWEP:SetReady(b) + + if not self:GetNetVar('CanSetReady') then return end + if b and not self.Owner:IsOnGround() then return end + + self.DrawCrosshair = b + +end + +function SWEP:Think() + + if self:GetNetVar('NoReadyInput') then return end + if self.Owner:KeyDown(IN_ATTACK2) and not self:GetNetVar('IsReady') then + self:SetReady(true) + elseif not self.Owner:KeyDown(IN_ATTACK2) and self:GetNetVar('IsReady') then + self:SetReady(false) + end + +end + +function SWEP:CalcView(ply, pos, ang, fov) + + if not self.AimPos then return end + + local animIn = dbgView.useSights and self:GetHoldType() == self.ActiveHoldType and self:GetNetVar('IsReady') + local aimProgress = math.Approach(self.aimProgress or 0, animIn and 1 or 0, FrameTime() * (animIn and 1 or 3)) + self.aimProgress = aimProgress + if aimProgress <= 0 then return end + + local attID = ply:LookupAttachment('anim_attachment_rh') + if not attID then return end + + if animIn then + aimProgress = math.Clamp(aimProgress - 0.4, 0, 1) / 0.6 + end + local easedProgress = octolib.tween.easing.inOutQuad(aimProgress, 0, 1, 1) + local view = dbgView.calcView(ply, pos, ang, fov) + local att = ply:GetAttachment(attID) + local tgtPos, tgtAng = LocalToWorld(self.AimPos, self.AimAng, att.Pos, att.Ang) + view.origin = LerpVector(easedProgress, view.origin, tgtPos) + view.angles = LerpAngle(easedProgress, view.angles, tgtAng) + dbgView.lookOff + view.znear = 1.5 + + return view + +end + +function SWEP:DrawWorldModel() + + self:DrawModel() + + if self.SightPos and self.aimProgress and self.aimProgress > 0 then + local ply = self:GetOwner() + local attID = ply:LookupAttachment('anim_attachment_rh') + if not attID then return end + + local att = ply:GetAttachment(attID) + local sightPos, sightAng = LocalToWorld(self.SightPos, self.SightAng, att.Pos, att.Ang) + + local minusHalfRes = sightResolution / -2 + cam.Start3D2D(sightPos, sightAng, self.SightSize / sightResolution) + cam.IgnoreZ(true) + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilTestMask(255) + render.SetStencilWriteMask(255) + render.SetStencilReferenceValue(42) + + -- draw mask + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilZFailOperation(STENCIL_KEEP) + surface.SetDrawColor(0,0,0, 1) + draw.NoTexture() + surface.DrawPoly(maskPoly) + + -- draw view + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilFailOperation(STENCIL_ZERO) + render.SetStencilZFailOperation(STENCIL_ZERO) + sightMaterial:SetTexture('$basetexture', sightRT) + sightMaterial:SetFloat('$alpha', math.Clamp(math.Remap(self.aimProgress, 0.6, 1, 0, 1), 0, 1)) + surface.SetMaterial(sightMaterial) + surface.DrawTexturedRect(minusHalfRes, minusHalfRes, sightResolution, sightResolution) + surface.SetDrawColor(255,255,255) + surface.SetMaterial(sightOverlayMaterial) + surface.DrawTexturedRect(minusHalfRes, minusHalfRes, sightResolution, sightResolution) + + render.SetStencilEnable(false) + cam.IgnoreZ(false) + cam.End3D2D() + end + + -- local pos, dir = self:GetShootPosAndDir() + -- render.DrawLine(pos, pos + dir * 20, color_white, true) + -- render.DrawWireframeSphere(pos, 1, 5, 5, color_white, true) + +end + +function SWEP:Reload() + + -- keep calm and do nothing + +end + +net.Receive('octoweapons.sound', function(len, ply) + local amount = net.ReadUInt(5) + local pos = net.ReadVector() + for _ = 1, amount do + sound.Play(net.ReadString(), pos) + end +end) + +hook.Add('CreateMove', 'octoweapons', function(cmd) + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and wep:GetNetVar('IsReady') and bit.band(cmd:GetButtons(), IN_JUMP) > 0 then + cmd:SetButtons(cmd:GetButtons() - IN_JUMP) + end +end) + +hook.Add('HUDPaint', 'dbg-scare', function() + local ply = LocalPlayer() + local scare = ply:GetNetVar('ScareState', 0) + if scare > 0 then + draw.RoundedBox(0, -1, -1, ScrW()+2, ScrH()+2, Color(0,0,0, 180 * scare)) + end +end) + +hook.Add('PreDrawEffects', 'octoweapons', function() + if isRenderingScope then return end + + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and wep.SightPos and wep.aimProgress > 0 then + renderScope(wep) + end +end) + +hook.Add('dbg-view.chTraceOverride', 'octoweapons', function() + local wep = LocalPlayer():GetActiveWeapon() + if not IsValid(wep) or not wep.IsOctoWeapon then return end + + local pos, dir = wep:GetShootPosAndDir() + return util.TraceLine({ + start = pos, + endpos = pos + dir * 2000, + filter = function(ent) + return ent ~= ply and ent:GetRenderMode() ~= RENDERMODE_TRANSALPHA + end + }) +end) + +hook.Add('dbg-view.chShouldDraw', 'octoweapons', function(tr) + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and wep.AimPos then + return false + end +end) + +hook.Add('RenderScene', 'octoweapons', function(pos, ang, fov) + local view = dbgView.calcWeaponView(LocalPlayer(), pos, ang, fov) + if not view then return end + + render.Clear(0, 0, 0, 255, true, true, true) + render.RenderView({ + x = 0, + y = 0, + w = ScrW(), + h = ScrH(), + angles = view.angles, + origin = view.origin, + drawhud = true, + drawviewmodel = false, + dopostprocess = true, + drawmonitors = true, + }) + + return true +end) + +local function requestBend(doBend) + local wep = LocalPlayer():GetActiveWeapon() + if not IsValid(wep) or not wep.CanBend or not wep:GetNetVar('IsReady') then return end + + net.Start('octoweapons.bend') + net.WriteBool(doBend) + net.SendToServer() +end + +local k_bend = 0 +timer.Simple(0, function() k_bend = GetConVar('cl_dbg_key_bend'):GetInt() end) +cvars.AddChangeCallback('cl_dbg_key_bend', function(cv, old, new) k_bend = tonumber(new) end, 'octoweapons') + +hook.Add('PlayerButtonDown', 'octoweapons', function(ply, key) + if key == k_bend then requestBend(true) end +end) + +hook.Add('PlayerButtonUp', 'octoweapons', function(ply, key) + if key == k_bend then requestBend(nil) end +end) + +local disableBindsWhileAiming = octolib.array.toKeys({ '+menu_context' }) +hook.Add('PlayerBindPress', 'octoweapons', function(ply, bind, pressed) + if not pressed or not disableBindsWhileAiming[bind] then return end + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or not wep.CanBend or not wep:GetNetVar('IsReady') then return end + + return true +end) + +net.Receive('octoweapons.bend', function() + local ply, fraction = net.ReadEntity(), net.ReadInt(8) + + local wep = ply:GetActiveWeapon() + local targetAngles = octolib.table.map(IsValid(wep) and (wep.BendAngles[wep:GetHoldType()] or wep.BendAngles._default) or {}, function(ang) + return ang * fraction + end) + octolib.manipulateBones(ply, targetAngles, 0.3) +end) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/init.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/init.lua new file mode 100644 index 0000000..74d650c --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/init.lua @@ -0,0 +1,255 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +util.AddNetworkString('octoweapons.sound') +util.AddNetworkString('octoweapons.bend') + +-- 1 = left, 2 = right +local function applyBend(ply, doBend) + local ct = CurTime() + if ply.bendApplied == doBend or doBend and (ply.nextBend or 0) > ct then return end + ply.bendApplied = doBend + if doBend then + ply.nextBend = ct + 0.75 + end + + local mul = doBend and 1 or 0 + + net.Start('octoweapons.bend') + net.WriteEntity(ply) + net.WriteInt(mul, 8) + net.SendPVS(ply:GetPos()) + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or not wep.CanBend then return end + + timer.Simple(0.32, function() + for bone, ang in pairs(wep.BendAngles[wep:GetHoldType()] or wep.BendAngles._default) do + ply:ManipulateBoneAngles(ply:LookupBone(bone), ang * mul) + end + end) +end + +function SWEP:SetReady(b) + + if not self:GetNetVar('CanSetReady') then return end + local ply = self.Owner + if not ply:IsOnGround() then return end + + local newHT = b and self.ActiveHoldType or self.PassiveHoldType + if IsValid(ply:GetVehicle()) and newHT == 'revolver' then newHT = 'pistol' end + self:SetHoldType(newHT) + self:SetNetVar('IsReady', b) + + self.lastOwner = ply + if not self.CanRunWhenReady then + ply:MoveModifier('weapon', b and { + norun = true, + nojump = true, + } or nil) + end + + if b then + self:SetNextPrimaryFire(CurTime() + (self.ReadyDelay or 60 / self.Primary.RPM)) + else + applyBend(ply, nil) + end +end + +hook.Add('octolib.canUseAnimation', 'dbg_weapons', function(ply, cat) + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetNetVar('IsReady') and (cat.stand or cat.freeze) then + return false + end + +end) + +function SWEP:Think() + + local ply = self.Owner + if ply:KeyDown(IN_ATTACK2) and not self:GetNetVar('IsReady') and not ply:IsUsingTalkie() then + if ply.octolib_customAnim and ply.octolib_customAnim[1] == 'walk' then + octolib.stopAnimations(ply) + end + + if ply.octolib_customAnim or CurTime() < (ply.nextAnim or 0) then + return + end + + self:SetReady(true) + elseif !ply:KeyDown(IN_ATTACK2) and self:GetNetVar('IsReady') then + self:SetReady(false) + end + + if self:GetNetVar('IsReady') then + if ply.bendApplied and ply:GetVelocity():Length2DSqr() > 10 then + applyBend(ply, nil) + end + + local _, aim = self:GetShootPosAndDir() + local trStart = ply:GetShootPos() + local trEnd = trStart + aim * 400 + local tr = util.TraceLine({ + start = trStart, + endpos = trEnd, + filter = function(ent) + if not ent:IsPlayer() then return true end + return ent ~= ply and not ent:IsGhost() + end + }) + + local tgt = tr.Hit and tr.Entity + if IsValid(tgt) and tgt:IsPlayer() and self.CanScare and tgt:Team() ~= TEAM_ADMIN then + if ply:isCP() and tgt:isCP() then return end + local jtOwner, jtTgt = ply:getJobTable(), tgt:getJobTable() + if jtOwner.orgID and jtOwner.orgID == jtTgt.orgID then return end + tgt:SetNetVar('ScareState', math.min(tgt:GetNetVar('ScareState', 0) + (FrameTime() / 3) * (self.ScareMultiplier or 1), 1)) + if tgt.lastScare <= 0.6 and tgt:GetNetVar('ScareState', 0) > 0.6 then + tgt:EmitSound('d1_trainstation.cryingloop') + tgt:MoveModifier('scare', { + walkmul = 0.5, + norun = true, + nujump = true, + }) + hook.Run('dbg.scareStart', tgt, ply, self) + end + + if not self.isScaring then + hook.Run('dbg.scareInit', tgt, ply, self) + end + self.isScaring = true + else + self.isScaring = nil + end + + ply.lastAim = CurTime() + end + +end + +function SWEP:Reload() + + if not self:GetNetVar('CanSetReady') then return end + if self:Clip1() >= self:GetMaxClip1() or self:Ammo1() < 1 then return end + + self:SetNextPrimaryFire(CurTime() + self.ReloadTime) + + self:SetReady(false) + self:SetHoldType(self.ActiveHoldType) -- just to be sure we do it BEFORE sending animation + timer.Simple(.2, function() + self.Weapon:DefaultReload(ACT_VM_RELOAD) + end) + + self:SetNetVar('CanSetReady', false) + timer.Simple(self.ReloadTime + .3, function() + if IsValid(self) then + self:SetHoldType(self.PassiveHoldType) + self:SetNetVar('CanSetReady', true) + end + end) + +end + +function SWEP:ResetStats() + + local owner = self.lastOwner or self.Owner + if IsValid(owner) then + owner:MoveModifier('weapon', nil) + applyBend(owner, nil) + end + +end + +function SWEP:Holster() + + self:ResetStats() + return true + +end + +function SWEP:OnRemove() + + self:ResetStats() + +end + +function SWEP:OnDrop() + + self:ResetStats() + +end + +function SWEP:PlaySounds(sounds, filter) + net.Start('octoweapons.sound', true) + net.WriteUInt(#sounds, 5) + net.WriteVector(self:GetPos()) + for _, name in ipairs(sounds) do + net.WriteString(name) + end + if filter then + net.Send(filter) + else + net.SendPVS() + end +end + +net.Receive('octoweapons.bend', function(_, ply) + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.CanBend and ply:GetVelocity():Length2DSqr() < 10 then + applyBend(ply, net.ReadBool() or nil) + end +end) + +hook.Add('PlayerSpawn', 'dbg-scare', function(ply) + + ply.lastScare = 0 + ply:StopSound('d1_trainstation.cryingloop') + +end) + +hook.Add('Think', 'dbg-scare', function(ply) + + for i, ply in ipairs(player.GetAll()) do + local scare = ply:GetNetVar('ScareState', 0) + if scare <= ply.lastScare and scare ~= 0 then + scare = math.max(scare - FrameTime() / 10, 0) + ply:SetNetVar('ScareState', scare) + + if ply.lastScare > 0.6 and ply:GetNetVar('ScareState', 0) <= 0.6 then + ply:StopSound('d1_trainstation.cryingloop') + ply:MoveModifier('scare', nil) + hook.Run('dbg.scareEnd', ply) + end + end + ply.lastScare = scare + end + +end) + +hook.Add('PlayerDisconnected', 'dbg-scare', function(ply) + + if ply:GetNetVar('ScareState', 0) > 0.6 then + ply:Kill() + octodeath.triggerDeath(ply) + end + +end) + +local disabledCmds = {'/cr', '/sms', '/pm'} + +hook.Add('octochat.canExecute', 'dbg-scare', function(ply, cmd) + + if ply:GetNetVar('ScareState', 0) > 0.6 and table.HasValue(disabledCmds, cmd) then + return false, L.you_scared + end + +end) + +hook.Add('closelook.canZoom', 'dbg-weapons', function(ply) + local weapon = ply:GetActiveWeapon() + if IsValid(weapon) and weapon.HasZoom and weapon:GetNetVar('IsReady') then + return true + end +end) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/shared.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/shared.lua new file mode 100644 index 0000000..2cd63b9 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base/shared.lua @@ -0,0 +1,299 @@ +SWEP.Spawnable = false +SWEP.AdminSpawnable = false + +SWEP.MuzzleAttachment = '1' +SWEP.DrawCrosshair = false +SWEP.ViewModelFOV = 65 +SWEP.ViewModelFlip = true + +SWEP.Primary.Sound = Sound('') +SWEP.Primary.Cone = 0.2 +SWEP.Primary.Recoil = 10 +SWEP.Primary.Damage = 10 +SWEP.Primary.Spread = .008 +SWEP.Primary.NumShots = 1 +SWEP.Primary.RPM = 60 +SWEP.Primary.ClipSize = 0 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0 +SWEP.Primary.KickHorizontal = 0 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = 'none' + +SWEP.Secondary.ClipSize = 0 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' +SWEP.Secondary.IronFOV = 0 + +SWEP.ReloadTime = 1 + +SWEP.ViewModel = 'models/weapons/v_crowbar.mdl' +SWEP.WorldModel = 'models/weapons/w_crowbar.mdl' + +SWEP.PassiveHoldType = 'normal' +SWEP.ActiveHoldType = 'pistol' + +SWEP.IsOctoWeapon = true +SWEP.Icon = 'octoteam/icons/gun_pistol.png' +SWEP.IsLethal = true +SWEP.CanScare = true +SWEP.CanBend = true + +SWEP.BendAngles = { + _default = { + ['ValveBiped.Bip01_Spine'] = Angle(15, 0, 0), + ['ValveBiped.Bip01_Spine1'] = Angle(15, 0, 0), + }, + ar2 = { + ['ValveBiped.Bip01_Spine'] = Angle(30, 30, 0), + ['ValveBiped.Bip01_Spine1'] = Angle(-15, -15, 15), + }, + smg = { + ['ValveBiped.Bip01_Spine'] = Angle(30, 30, 0), + ['ValveBiped.Bip01_Spine1'] = Angle(-15, -15, 15), + }, +} + +local muzzlePosAngPerHoldtype = { + _default = { Vector(10, 0.65, 3.5), Angle(-2, 5, 0) }, + revolver = { Vector(8, 0.65, 4), Angle(-2, 5, 0) }, + pistol = { Vector(10, 0.25, 3.5), Angle(-2, 5, 0) }, + ar2 = { Vector(25, -1, 7.5), Angle(-9, 0, 0) }, + smg = { Vector(12, -1, 7.5), Angle(-9, 0, 0) }, + duel = { Vector(9, 1, 3.5), Angle(0, 11, 0) }, +} + +--[[------------------------------------------------------------------------- +FUNCTIONS +---------------------------------------------------------------------------]] + +function SWEP:Initialize() + + self:SetReady(false) + if SERVER then + self:SetNetVar('CanSetReady', true) + end + self:OnInitialize() + +end +SWEP.OnInitialize = octolib.func.zero -- to be overriden + +function SWEP:Precache() + + util.PrecacheSound(self.Primary.Sound) + util.PrecacheModel(self.WorldModel) + +end + +function SWEP:CanFire() + + if not self:GetNetVar('CanSetReady') or not self:GetNetVar('IsReady') or + CurTime() < self:GetNextPrimaryFire() or self.Owner:GetNetVar('ScareState', 0) > 0.6 + or self.Owner:WaterLevel() == 3 + then return false end + + local ply = self.Owner + local t = {} + t.start = ply:GetShootPos() + t.endpos = self:GetShootPosAndDir() + t.filter = ply + return not util.TraceLine(t).Hit + +end + +function SWEP:GetShootPosAndDir() + + local ply = self.Owner + local attID = ply:LookupAttachment('anim_attachment_rh') + + if attID then + local att = ply:GetAttachment(attID) + local offPos, offAng = self.MuzzlePos, self.MuzzleAng + if not offPos then + if muzzlePosAngPerHoldtype[self.ActiveHoldType] then + offPos, offAng = unpack(muzzlePosAngPerHoldtype[self.ActiveHoldType]) + else + offPos, offAng = unpack(muzzlePosAngPerHoldtype._default) + end + end + + local pos, ang = LocalToWorld(offPos, offAng, att.Pos, att.Ang) + return pos, ang:Forward(), ang + else + return ply:GetShootPos(), (ply.viewAngs or ply:EyeAngles()):Forward(), ply.viewAngs + end + +end + +function SWEP:Equip() + + self:SetReady(false) + +end + +function SWEP:Deploy() + + self:SetReady(false) + if SERVER and self.DeploySound then + self.Owner:EmitSound(self.DeploySound) + end + return true + +end + +function SWEP:PrimaryAttack() + + local ct = CurTime() + if not IsFirstTimePredicted() or not self:CanFire() or + (self.nextFire or 0) > ct or self:GetNextPrimaryFire() > ct + then return end + + if self:Clip1() == 0 then + self:EmitSound('Weapon_AR2.Empty') + self.nextFire = ct + 1 + self:SetNextPrimaryFire(self.nextFire) + return + end + + local ply = self.Owner + + local up, down, horiz = self.Primary.KickUp, self.Primary.KickDown, self.Primary.KickHorizontal + local mul = 2 + if ply:Crouching() then mul = mul / 2 end + if ply:InVehicle() then + mul = mul * 2 -- to compensate lack of view punch + else + mul = mul + ply:GetVelocity():Length() / 80 + end + local kickDir = Angle(math.Rand(-down, -up) * mul, math.Rand(-horiz, horiz) * mul, 0) + + if CLIENT then + local ang = ply:EyeAngles() + ang.p = ang.p + (kickDir.p / 2) + ang.y = ang.y + (kickDir.y / 2) + ply:SetEyeAngles(ang) + -- else + -- ply:ViewPunch(kickDir) + end + + self.nextFire = ct + 1 / (self.Primary.RPM / 60) + self:SetNextPrimaryFire(self.nextFire) + self:TakePrimaryAmmo(1) + + ply:SetAnimation(PLAYER_ATTACK1) + if CLIENT then + ply:EmitSound(self.Primary.Sound) + if self.Primary.DistantSound and StormFox2.Environment.GetOutSideFade() > 0.5 then + ply:EmitSound(self.Primary.DistantSound) + end + else + local filter = RecipientFilter() + filter:AddAllPlayers() + filter:RemovePlayer(ply) + + self:PlaySounds({ self.Primary.Sound, self.Primary.DistantSound }, filter) + end + self:ShootEffects() + if not self.NoMuzzleflash then ply:MuzzleFlash() end + + local damage = self.Primary.Damage + local spread = self.Primary.Spread + local numShots = self.Primary.NumShots + + local shootPos, shootDir = self:GetShootPosAndDir() + + if self:CustomFireBullets(shootPos, shootDir) then + return + end + + if SERVER then + local bullet = {} + bullet.Num = numShots + bullet.Src = shootPos + bullet.Dir = shootDir + bullet.Spread = Vector(spread, spread, 0) + bullet.Tracer = 3 + bullet.TracerName = TracerName + bullet.Force = damage * 0.25 + bullet.Damage = damage + bullet.Distance = self.Primary.Distance + bullet.Callback = function(attacker, bulletTrace, dmg) + local ent = bulletTrace.Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then + return self:BulletHitCallback(bulletTrace, bullet) + end + + -- try to trace bullet path through car + + local dir = (bulletTrace.HitPos - bulletTrace.StartPos):GetNormalized() + local tr = {} + tr.start = bulletTrace.HitPos - dir * 5 + tr.endpos = bulletTrace.HitPos + dir * 200 + tr.filter = ent + local cEnt = util.TraceLine(tr).Entity + if IsValid(cEnt) and cEnt:IsPlayer() then + if IsValid(cEnt:GetVehicle()) and IsValid(cEnt:GetVehicle():GetParent()) and cEnt:GetVehicle():GetParent().cdBulletproof then + dmg:SetDamage(0) + return + end + bullet.Src = tr.start + bullet.Dir = dir + bullet.Spread = Vector() + bullet.IgnoreEntity = ent + bullet.Callback = function(attacker, bulletTrace) + self:BulletHitCallback(bulletTrace, bullet) + end + bullet.Distance = 200 + ply:FireBullets(bullet) + end + end + local seat = ply:GetVehicle() + if IsValid(seat) and IsValid(seat:GetParent()) then + bullet.IgnoreEntity = seat:GetParent() + end + ply:FireBullets(bullet) + end + +end + +function SWEP:SecondaryAttack() + + -- keep calm and do nothing + +end + +function SWEP:BulletHitCallback(trace, bullet) + self:PlayImpactEffect(trace) +end + +function SWEP:CustomFireBullets(shootPos, shootDir) + -- to be overridden, return true to disable default behaviour +end + +function SWEP:Holster(wep) + + if self.Owner:KeyDown(IN_ATTACK2) then return false end + return true + +end + +function SWEP:PlayImpactEffect(trace) + local data = EffectData() + data:SetStart(trace.StartPos) + data:SetOrigin(trace.HitPos) + data:SetNormal(trace.HitNormal) + data:SetEntity(trace.Entity) + data:SetSurfaceProp(trace.SurfaceProps) + data:SetHitBox(trace.HitBox) + util.Effect('Impact', data, true, true) +end + +hook.Add('PlayerSwitchWeapon', 'dbg-scare', function(ply, oldW, newW) + + if ply:GetNetVar('ScareState', 0) > 0.6 and IsValid(newW) and newW:GetClass() ~= 'weapon_cuffed' then + return true + end + +end) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_air.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_air.lua new file mode 100644 index 0000000..a3a01c7 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_air.lua @@ -0,0 +1,62 @@ +SWEP.Base = "weapon_octo_base" +SWEP.Category = L.dobrograd +SWEP.Spawnable = false +SWEP.AdminSpawnable = false + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = "air" +SWEP.Primary.Automatic = true +SWEP.Primary.Sound = Sound("weapon.BulletImpact") +SWEP.Primary.DistantSound = Sound( "" ) +SWEP.Primary.Damage = 0 +SWEP.Primary.RPM = 60 + +SWEP.PassiveHoldType = "passive" +SWEP.ActiveHoldType = "ar2" + +SWEP.Primary.ClipSize = 10 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 3.35 +SWEP.Primary.KickDown = 0.6 +SWEP.Primary.KickHorizontal = 0.01 + +SWEP.ReloadTime = 2.2 +SWEP.Icon = 'octoteam/icons/gun_rifle.png' + +SWEP.CanScare = false +SWEP.IsLethal = false -- shouldn't take karma whet shots fired + +function SWEP:CustomFireBullets(shootPos, shootDir) + + local ply = self:GetOwner() + local ang = shootDir:Angle() + local spread = self.Primary.Spread + for _ = 1, self.Primary.NumShots or 1 do + local dir = shootDir + ang:Right() * math.Rand(-spread, spread) + ang:Up() * math.Rand(-spread, spread) + local tr = util.TraceLine({ + start = shootPos, + endpos = shootPos + dir * (self.Primary.Distance or 200), + filter = self:GetOwner(), + }) + + if IsValid(tr.Entity) then + local dmg = DamageInfo() + dmg:SetAttacker(ply) + dmg:SetInflictor(self) + dmg:SetDamage(self.Primary.Damage) + dmg:SetDamageForce(self.Primary.Damage * shootDir) + dmg:SetDamageType(DMG_BULLET) + dmg:SetDamagePosition(tr.HitPos) + if SERVER then tr.Entity:TakeDamageInfo(dmg) end + self:BulletHitCallback(tr, {}) + end + end + + return true + +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/cl_init.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/cl_init.lua new file mode 100644 index 0000000..0f82600 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/cl_init.lua @@ -0,0 +1,8 @@ +include 'shared.lua' + +SWEP.PrintName = 'Octothorp Cold Weapon' +SWEP.Slot = 1 +SWEP.SlotPos = 2 +SWEP.DrawAmmo = false +SWEP.DrawWeaponInfoBox = false +SWEP.BounceWeaponIcon = false diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/init.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/init.lua new file mode 100644 index 0000000..be22cfb --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/init.lua @@ -0,0 +1,93 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +function SWEP:GenerateSegments(width, amount) + + local segmentWidth = width / amount + local segments = {} + for i = 0, amount - 1 do + local ang = math.rad(i * segmentWidth) + segments[#segments + 1] = { sin = math.sin(ang), cos = math.cos(ang) } + end + self.Segments = segments + +end + +function SWEP:OnInitialize() + + self:GenerateSegments(self.SwingWidth or 175, self.SwingSegmentAmount or 35) + +end + +-- calculate the trajectory and fire bullet +local trajectoryTypes = { + + point = function(self) + local owner = self:GetOwner() + local pos, aim = owner:GetShootPos(), owner:EyeAngles() + local tr = util.TraceLine({ + start = pos, + endpos = pos + (aim:Forward() * self.HitDistance * 1.5), + filter = function(ent) + if ent == owner then return false end + if ent:IsPlayer() and ent:IsGhost() then return false end + return true + end, + mask = MASK_SHOT, + }) + if tr.Hit then return tr end + end, + + swing = function(self) + local owner = self:GetOwner() + local pos, aim = owner:GetShootPos(), owner:EyeAngles() + for _, seg in ipairs(self.Segments) do + local from = pos - (aim:Up() * 10) + local tr = util.TraceLine({ + start = from, + endpos = from + (aim:Up() * (self.HitDistance * 0.7 * seg.cos)) + (aim:Forward() * (self.HitDistance * 1.5 * seg.sin)) + (aim:Right() * (self.HitInclination * self.HitDistance * seg.cos)), + filter = self.Owner, + mask = MASK_SHOT_HULL + }) + if tr.Hit then return tr end + end + end, + +} + +-- fire bullet +function SWEP:Attack() + + local tr = trajectoryTypes[self.TrajectoryType](self) + if not tr then return end + -- if found something then shoot + + local bullet = {} + bullet.Num = 1 + bullet.Src = tr.HitPos + tr.HitNormal + bullet.Dir = -tr.HitNormal + bullet.Spread = Vector(0, 0, 0) + bullet.Tracer = 0 + bullet.Force = 10 + bullet.Hullsize = 0 + bullet.Distance = self.HitDistance * 1.5 + bullet.Damage = self.GetDamage and self:GetDamage(tr) or math.random(unpack(self.Damage)) + bullet.AmmoType = self.Primary.Ammo + self.Owner:FireBullets(bullet) + + local ent = tr.Entity + if IsValid(ent) and (ent:IsPlayer() or ent:GetClass():find('npc') or ent:GetClass():find('prop_ragdoll')) then + self:EmitSound(self.HitSoundBody) + ent:SetVelocity(self.Owner:GetAimVector() * Vector(1,1,0) * self.HitPushback) + else + self:EmitSound(self.HitSoundWorld) + end + + self:OnBulletShot(tr, bullet) + +end + +function SWEP:OnBulletShot(trace, bullet) + -- to be overriden +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/shared.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/shared.lua new file mode 100644 index 0000000..f305df2 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_cold/shared.lua @@ -0,0 +1,91 @@ +SWEP.Base = 'weapon_octo_base' + +SWEP.Spawnable = false +SWEP.AdminSpawnable = false + +SWEP.MuzzleAttachment = '1' +SWEP.DrawCrosshair = false +SWEP.ViewModelFOV = 65 +SWEP.ViewModelFlip = true + +SWEP.ViewModelFOV = 67 +SWEP.UseHands = false + +SWEP.ReadyDelay = 1 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = 'none' + +SWEP.Primary.Sound = Sound('') +SWEP.Primary.Cone = 0.2 +SWEP.Primary.Recoil = 10 +SWEP.Primary.Damage = 10 +SWEP.Primary.Spread = .01 +SWEP.Primary.NumShots = 1 +SWEP.Primary.RPM = 60 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0 +SWEP.Primary.KickHorizontal = 0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' +SWEP.Secondary.IronFOV = 0 + +SWEP.ReloadTime = 0.5 + +SWEP.PassiveHoldType = 'normal' +SWEP.ActiveHoldType = 'melee' +SWEP.CanRunWhenReady = true +SWEP.TrajectoryType = 'swing' -- could be also 'point' +SWEP.DeploySound = nil + +SWEP.Damage = {34, 40} + +SWEP.SwingWidth = 175 +SWEP.SwingSegmentAmount = 35 + +game.AddAmmoType({ + name = 'blunt', + dmgtype = DMG_SHOCK, + tracer = TRACER_NONE, + plydmg = 0, + npcdmg = 0, + force = 1000, + minsplash = 10, + maxsplash = 5, +}) + +game.AddAmmoType({ + name = 'sharp', + dmgtype = DMG_SONIC, + tracer = TRACER_NONE, + plydmg = 0, + npcdmg = 0, + force = 1000, + minsplash = 10, + maxsplash = 5, +}) + +function SWEP:PrimaryAttack() + + if not IsFirstTimePredicted() then return end + + local time = CurTime() + + local ok = self:GetNetVar('CanSetReady') and self:GetNetVar('IsReady') and self:GetNextPrimaryFire() <= time and self.Owner:GetNetVar('ScareState', 0) <= 0.6 + if not ok then return end + self:SetNextPrimaryFire(time + self.HitRate) + self.Owner:SetAnimation(PLAYER_ATTACK1) + + if SERVER then + if self.SwingSound then self.Owner:EmitSound(self.SwingSound) end -- prevent doubling on client + timer.Create('hitdelay' .. self:EntIndex(), 0.1, 1, function() self:Attack() end) + end + +end + +SWEP.Reload = octolib.func.zero diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_pistol.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_pistol.lua new file mode 100644 index 0000000..66eebe8 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_pistol.lua @@ -0,0 +1,18 @@ +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Base = "weapon_octo_base" +SWEP.WeaponCategory = "pistol" + +SWEP.Primary.Ammo = "pistol" +SWEP.Primary.Automatic = false + +SWEP.PassiveHoldType = "normal" +SWEP.ActiveHoldType = "revolver" +SWEP.HasFlashlight = true + +SWEP.ReloadTime = 2.5 +SWEP.Icon = 'octoteam/icons/gun_pistol.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_rifle.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_rifle.lua new file mode 100644 index 0000000..66ae9bf --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_rifle.lua @@ -0,0 +1,18 @@ +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Base = "weapon_octo_base" +SWEP.WeaponCategory = "rifle" + +SWEP.Primary.Ammo = "smg1" +SWEP.Primary.Automatic = true + +SWEP.PassiveHoldType = "passive" +SWEP.ActiveHoldType = "ar2" +SWEP.HasFlashlight = true + +SWEP.ReloadTime = 2.2 +SWEP.Icon = 'octoteam/icons/gun_rifle.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_shotgun.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_shotgun.lua new file mode 100644 index 0000000..7793607 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_shotgun.lua @@ -0,0 +1,18 @@ +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Base = "weapon_octo_base" +SWEP.WeaponCategory = "shotgun" + +SWEP.Primary.Ammo = "buckshot" +SWEP.Primary.Automatic = false + +SWEP.PassiveHoldType = "passive" +SWEP.ActiveHoldType = "ar2" +SWEP.HasFlashlight = true + +SWEP.ReloadTime = 3 +SWEP.Icon = 'octoteam/icons/gun_shotgun.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_smg.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_smg.lua new file mode 100644 index 0000000..4b542ac --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_smg.lua @@ -0,0 +1,18 @@ +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Base = "weapon_octo_base" +SWEP.WeaponCategory = "smg" + +SWEP.Primary.Ammo = "pistol" +SWEP.Primary.Automatic = true + +SWEP.PassiveHoldType = "passive" +SWEP.ActiveHoldType = "ar2" +SWEP.HasFlashlight = true + +SWEP.ReloadTime = 2 +SWEP.Icon = 'octoteam/icons/gun_smg.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_sniper.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_sniper.lua new file mode 100644 index 0000000..4d21f32 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_sniper.lua @@ -0,0 +1,51 @@ +if SERVER then + + AddCSLuaFile() + +end + +game.AddAmmoType({ + name = "sniper", + dmgtype = DMG_BULLET, + tracer = TRACER_LINE, + force = 2000, +}) + +SWEP.Base = "weapon_octo_base" +SWEP.WeaponCategory = "sniper" + +SWEP.Primary.Ammo = "sniper" +SWEP.Primary.Automatic = true + +SWEP.PassiveHoldType = "passive" +SWEP.ActiveHoldType = "ar2" +SWEP.HasFlashlight = true +SWEP.HasZoom = true + +SWEP.ReloadTime = 3 +SWEP.Icon = 'octoteam/icons/gun_sniper.png' + +-- local mat = Material("overlays/scope_lens") +-- hook.Add( "HUDPaint", "octoweapons", function() +-- local wep = LocalPlayer():GetActiveWeapon() +-- if IsValid(wep) and wep.WeaponCategory == "sniper" then +-- local curfov = math.Round( LocalPlayer():GetFOV() ) +-- local perc = (defaultfov - curfov) / 60 +-- if perc ~= 0 then +-- surface.SetDrawColor( 255,255,255, perc*180 ) +-- surface.SetMaterial( mat ) +-- surface.DrawTexturedRect( 0, 0, ScrW(), ScrH() ) +-- end +-- end +-- end) + +-- hook.Add( "PreDrawHUD", "octoweapons", function() +-- local wep = LocalPlayer():GetActiveWeapon() +-- if IsValid(wep) and wep.WeaponCategory == "sniper" then +-- local curfov = math.Round( LocalPlayer():GetFOV() ) +-- local perc = (defaultfov - curfov) / 60 +-- if perc ~= 0 then +-- DrawToyTown( 4 * perc, ScrH() / 2 ) +-- end +-- end +-- end) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_zoom.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_zoom.lua new file mode 100644 index 0000000..74ed01e --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_base_zoom.lua @@ -0,0 +1,19 @@ +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Base = "weapon_octo_base" +SWEP.WeaponCategory = "zoom" + +SWEP.Primary.Ammo = "smg1" +SWEP.Primary.Automatic = true + +SWEP.PassiveHoldType = "passive" +SWEP.ActiveHoldType = "ar2" +SWEP.HasFlashlight = true +SWEP.HasZoom = true + +SWEP.ReloadTime = 3 +SWEP.Icon = 'octoteam/icons/gun_rifle.png' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_beanbag.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_beanbag.lua new file mode 100644 index 0000000..323723b --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_beanbag.lua @@ -0,0 +1,50 @@ +SWEP.Base = "weapon_octo_base_air" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Bean Bag" + +if SERVER then + AddCSLuaFile() +end + +SWEP.Primary.Sound = Sound( "beanbag.fire" ) +SWEP.Primary.Automatic = false +SWEP.Primary.Damage = 1 +SWEP.Primary.RPM = 70 +SWEP.Primary.ClipSize = 4 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 5.28 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.1 +SWEP.Primary.Distance = 500 + +SWEP.WorldModel = "models/weapons/w_shot_m3super90_beanbag.mdl" +SWEP.AimPos = Vector(-5, -0.94, 4.6) +SWEP.AimAng = Angle(-9, 0, 0) + +SWEP.Primary.NumShots = 1 +SWEP.Icon = "octoteam/icons/gun_shotgun.png" + +SWEP.CanScare = true +SWEP.IsLethal = true + +if SERVER then + function SWEP:BulletHitCallback(trace, bullet) + local ent = trace.Entity + if not IsValid(ent) or not ent:IsPlayer() then return end + + ent:MoveModifier('stunstick', { + walkmul = 0.1, + norun = true, + nojump = true, + }) + + timer.Create('stunstick' .. ent:EntIndex(), 5, 1, function() + if not IsValid(ent) then return end + ent:MoveModifier('stunstick', nil) + end) + end + +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_bottle.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_bottle.lua new file mode 100644 index 0000000..7327364 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_bottle.lua @@ -0,0 +1,36 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = 'Бутылка' + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'sharp' +SWEP.HitDistance = 35 +SWEP.HitInclination = 0.2 +SWEP.HitPushback = 100 +SWEP.HitRate = 0.50 +SWEP.Damage = {2, 8} +SWEP.ScareMultiplier = 0.2 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('GlassBottle.Break') +SWEP.HitSoundBody = Sound('GlassBottle.Break') + +SWEP.Icon = 'octoteam/icons/bottle_empty.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_bottle.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_bottle.mdl') +SWEP.ActiveHoldType = 'melee' + +function SWEP:OnBulletShot() + if SERVER then + self:Remove() + self.Owner:Give('weapon_octo_bottle_broken') + self.Owner:SelectWeapon('weapon_octo_bottle_broken') + end +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_bottle_broken.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_bottle_broken.lua new file mode 100644 index 0000000..1bc6192 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_bottle_broken.lua @@ -0,0 +1,36 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = 'Разбитая бутылка' + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'sharp' +SWEP.HitDistance = 32 +SWEP.HitInclination = 0.2 +SWEP.HitPushback = 0 +SWEP.HitRate = 0.40 +SWEP.Damage = {5, 15} +SWEP.ScareMultiplier = 0.4 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('GlassBottle.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh_Bloody.ImpactHard') + +SWEP.Icon = 'octoteam/icons/bottle_broken.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_brokenbottle.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_brokenbottle.mdl') +SWEP.ActiveHoldType = 'knife' +SWEP.TrajectoryType = 'point' + +function SWEP:GetDamage(tr) + local ent = tr.Entity + if not (IsValid(ent) and ent:IsPlayer()) then return end + local limit = math.max(self.Owner:HasBuff('Meth') and 100 or (ent:Health() - 20), 0) + return math.min(limit, math.random(unpack(self.Damage))) -- do not kill him +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_deagle.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_deagle.lua new file mode 100644 index 0000000..3f6f660 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_deagle.lua @@ -0,0 +1,25 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Desert Eagle" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "deagle.fire" ) +SWEP.Primary.DistantSound = Sound( "deagle.fire-distant" ) +SWEP.Primary.Damage = 35 +SWEP.Primary.RPM = 300 +SWEP.Primary.ClipSize = 7 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 3.24 +SWEP.Primary.KickDown = 0.72 +SWEP.Primary.KickHorizontal = 0.01 + +SWEP.WorldModel = "models/weapons/w_pist_deagle.mdl" +SWEP.AimPos = Vector(-10.5, -1.28, 4.5) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_dualelites.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_dualelites.lua new file mode 100644 index 0000000..a07df8d --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_dualelites.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Dual Elites" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "elite.fire" ) +SWEP.Primary.DistantSound = Sound( "elite.fire-distant" ) +SWEP.Primary.Damage = 25 +SWEP.Primary.RPM = 800 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.9 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 1.4 +SWEP.Primary.Spread = 0.06 + +SWEP.WorldModel = "models/weapons/w_pist_elite.mdl" + +SWEP.ActiveHoldType = "duel" +SWEP.ReloadTime = 2.5 + +SWEP.AimPos = Vector(-10.5, -2.85, 4.5) +SWEP.AimAng = Angle(0, 11, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_famas.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_famas.lua new file mode 100644 index 0000000..858c79f --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_famas.lua @@ -0,0 +1,27 @@ +SWEP.Base = "weapon_octo_base_rifle" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "FAMAS" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "famas.fire" ) +SWEP.Primary.DistantSound = Sound( "famas.fire-distant" ) +SWEP.Primary.Damage = 22 +SWEP.Primary.RPM = 950 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 1.05 +SWEP.Primary.KickHorizontal = 0.5 + +SWEP.WorldModel = "models/weapons/w_rif_famas.mdl" +SWEP.MuzzlePos = Vector(20, -1, 7.4) +SWEP.MuzzleAng = Angle(-8, 0, 0) +SWEP.AimPos = Vector(-6, -0.91, 7.6) +SWEP.AimAng = Angle(-8, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_fiveseven.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_fiveseven.lua new file mode 100644 index 0000000..eb9b104 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_fiveseven.lua @@ -0,0 +1,25 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "FiveseveN" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "fiveseven.fire" ) +SWEP.Primary.DistantSound = Sound( "fiveseven.fire-distant" ) +SWEP.Primary.Damage = 25 +SWEP.Primary.RPM = 500 +SWEP.Primary.ClipSize = 20 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 1 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 + +SWEP.WorldModel = "models/weapons/w_pist_fiveseven.mdl" +SWEP.AimPos = Vector(-10.5, -1.25, 3.92) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_g3sg1.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_g3sg1.lua new file mode 100644 index 0000000..3aa7d3b --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_g3sg1.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_zoom" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "G3SG1" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "g3sg1.fire" ) +SWEP.Primary.DistantSound = Sound( "g3sg1.fire-distant" ) +SWEP.Primary.Damage = 35 +SWEP.Primary.RPM = 450 +SWEP.Primary.ClipSize = 20 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 2 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0 + +SWEP.WorldModel = "models/weapons/w_snip_g3sg1.mdl" +SWEP.AimPos = Vector(-6.2, -0.94, 6.7) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(-3.6, -0.94, 6.98) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1.6 +SWEP.SightFOV = 12 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_galil.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_galil.lua new file mode 100644 index 0000000..017f5ce --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_galil.lua @@ -0,0 +1,27 @@ +SWEP.Base = "weapon_octo_base_rifle" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Galil" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "galil.fire" ) +SWEP.Primary.DistantSound = Sound( "galil.fire-distant" ) +SWEP.Primary.Damage = 20 +SWEP.Primary.RPM = 750 +SWEP.Primary.ClipSize = 25 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 1.27 +SWEP.Primary.KickHorizontal = 0.8 + +SWEP.WorldModel = "models/weapons/w_rif_galil.mdl" +SWEP.MuzzlePos = Vector(25, -1, 8.2) +SWEP.MuzzleAng = Angle(-10, 0, 0) +SWEP.AimPos = Vector(-4, -0.75, 5.6) +SWEP.AimAng = Angle(-10, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_glock.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_glock.lua new file mode 100644 index 0000000..f1f6c67 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_glock.lua @@ -0,0 +1,26 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Glock" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "glock.fire" ) +SWEP.Primary.DistantSound = Sound( "glock.fire-distant" ) +SWEP.Primary.Damage = 26 +SWEP.Primary.RPM = 825 +SWEP.Primary.ClipSize = 9 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 1.2 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.02 + +SWEP.WorldModel = "models/weapons/w_pist_glock18.mdl" +SWEP.AimPos = Vector(-10.5, -1.23, 4) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_glock17.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_glock17.lua new file mode 100644 index 0000000..a986407 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_glock17.lua @@ -0,0 +1,26 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Glock 17" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "glock18.fire" ) +SWEP.Primary.DistantSound = Sound( "glock18.fire-distant" ) +SWEP.Primary.Damage = 26 +SWEP.Primary.RPM = 825 +SWEP.Primary.ClipSize = 17 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 1.2 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.02 + +SWEP.WorldModel = "models/weapons/w_pist_glock18.mdl" +SWEP.AimPos = Vector(-10.5, -1.23, 4) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/cl_init.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/cl_init.lua new file mode 100644 index 0000000..f0ab0bf --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/cl_init.lua @@ -0,0 +1 @@ +include 'shared.lua' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/init.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/init.lua new file mode 100644 index 0000000..6df345a --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/init.lua @@ -0,0 +1,78 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +netstream.Hook('dbg-glauncher.applyCharge', function(ply, charge, delayed) + + if not isstring(charge) then return end + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= 'weapon_octo_grenade_launcher' then return end + local ct = CurTime() + if (wep.nextRecharge or 0) > ct then return end + wep.nextRecharge = ct + 4 + + local charges = wep:GetNetVar('charges', {}) + local amount = charges[charge] or 0 + if amount < 1 then + return ply:Notify('warning', 'У тебя нет снарядов этого вида!') + end + + charges[charge] = amount - 1 + local oldCharge = wep:GetNetVar('charge') + if oldCharge then + charges[oldCharge] = (charges[oldCharge] or 0) + 1 + end + + wep:SetNetVar('charges', charges) + wep:SetNetVar('charge', charge) + wep:SetNetVar('chargeDelayed', tobool(delayed)) + wep:EmitSound('grenade_launcher/grenade_launcher_reload.wav') + ply:DoEmote('{name} заряжает 40-мм ' .. utf8.lower(wep.ChargeTypes[charge].name) .. ' в гранатомет') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(1.5, function() + if IsValid(ply) then ply:DoAnimation(ACT_HL2MP_GESTURE_RELOAD_SMG1) end + end) + +end) + +local function getMax(cfg) + return cfg.max +end +function SWEP:Recharge() + self:SetNetVar('charges', octolib.table.map(self.ChargeTypes, getMax)) +end + +function SWEP:Shoot(proj, forceMul) + local ply = self:GetOwner() + + local force = 2000 * forceMul + local pos, ang, vel = ply:GetBonePosition(ply:LookupBone('ValveBiped.Bip01_Head1') or 6) + pos = pos - Vector(0, 0, 5) + local tr = util.TraceLine { start = ply:GetShootPos(), endpos = pos, filter = ply } + if tr.Hit then + pos = tr.HitPos + tr.HitNormal * 5 + vel = tr.HitNormal * force * 0.4 + else + vel = ply:GetForward() + vel = vel * force + end + + proj:SetPos(pos) + proj:SetAngles(ang) + + proj.LifeTime = proj.charge.delay * (self:GetNetVar('chargeDelayed') and 1 or 0.1) * math.Rand(0.8, 1.2) + proj.BreakSensitivity = 100 + proj.ExplodeAfterCollision = true + proj:Spawn() + proj:Activate() + proj.owner = ply + ply:LinkEntity(proj) + proj:SetGravity(0.5) + + local phys = proj:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:SetVelocity(vel) + end + +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/shared.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/shared.lua new file mode 100644 index 0000000..82bf474 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_grenade_launcher/shared.lua @@ -0,0 +1,129 @@ +SWEP.Base = 'octolib_custom' +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Гранатомет' +SWEP.ViewModel = '' +SWEP.Author = '' +SWEP.Instructions = '' +SWEP.HoldType = 'smg' +SWEP.DrawCrosshair = true +SWEP.HasFlashlight = true + +SWEP.Icon = 'octoteam/icons/grenade_launcher.png' + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = 'none' + +SWEP.Primary.Sound = Sound('') +SWEP.Primary.Cone = 0.2 +SWEP.Primary.Recoil = 10 +SWEP.Primary.Damage = 10 +SWEP.Primary.Spread = .01 +SWEP.Primary.NumShots = 1 +SWEP.Primary.RPM = 60 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0 +SWEP.Primary.KickHorizontal = 0 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' +SWEP.Secondary.IronFOV = 0 + +SWEP.WorldModel = 'models/saraphines/eft/eft_fn_gl40.mdl' +SWEP.WorldModelAtt = 'anim_attachment_rh' +SWEP.WorldModelPos = Vector(4, -1, 5.5) +SWEP.WorldModelAng = Angle(-12, 0, 0) +SWEP.WorldModelSkin = 1 +SWEP.WorldModelBodyGroups = { + [1] = 1, + [7] = 1, + [9] = 1, +} + +SWEP.ChargeTypes = { + frag = { + name = 'Осколочный снаряд', + icon = 'grenade_frag', + max = 5, + class = 'ent_dbg_grenade_frag', + bodygroup = 0, + delay = 2, + }, + smoke = { + name = 'Дымовой снаряд', + icon = 'grenade_smoke', + max = 5, + class = 'ent_dbg_grenade_smoke', + bodygroup = 2, + delay = 2, + }, + stun = { + name = 'Светошумовой снаряд', + icon = 'grenade_flash', + max = 5, + class = 'ent_dbg_grenade_stun', + bodygroup = 4, + delay = 1, + }, + shock = { + name = 'Шоковый снаряд', + icon = 'grenade_shock', + max = 5, + class = 'ent_dbg_grenade_shock', + bodygroup = 6, + delay = 2, + }, + gas = { + name = 'Газовый снаряд', + icon = 'grenade_gas', + max = 5, + class = 'ent_dbg_grenade_gas', + bodygroup = 10, + delay = 2, + }, +} + +function SWEP:PrimaryAttack() + if not IsValid(self:GetOwner()) then return end + + local ct = CurTime() + if not IsFirstTimePredicted() or + (self.nextFire or 0) > ct or self:GetNextPrimaryFire() > ct or (self.nextRecharge or 0) > ct + then return end + + local charge = self:GetNetVar('charge') + if not (charge and self.ChargeTypes[charge]) then + self:EmitSound('Weapon_AR2.Empty') + self.nextFire = ct + 1 + self:SetNextPrimaryFire(self.nextFire) + return + end + + if SERVER then + self:GetOwner():EmitSound('grenade_launcher/grenade_launcher_shot.wav') + local cfg = self.ChargeTypes[charge] + local proj = ents.Create(cfg.class) + proj:SetModel('models/saraphines/eft/eft_fn_gl40_grenade.mdl') + proj.Model = 'models/saraphines/eft/eft_fn_gl40_grenade.mdl' + proj.charge = cfg + self:Shoot(proj, 2) + proj:SetBodygroup(0, cfg.bodygroup or 0) + self:SetNetVar('charge', nil) + self:SetNetVar('chargeDelayed', nil) + end + + self.nextFire = ct + 1 + self:SetNextPrimaryFire(ct + 1) + +end + +function SWEP:SecondaryAttack() + + -- nothing + +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_hook.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_hook.lua new file mode 100644 index 0000000..1d378b7 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_hook.lua @@ -0,0 +1,29 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = L.hook + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'sharp' +SWEP.HitDistance = 50 +SWEP.HitInclination = 0.4 +SWEP.HitPushback = -1000 +SWEP.HitRate = 1.25 +SWEP.Damage = {34, 50} +SWEP.ScareMultiplier = 0.7 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Canister.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh.Break') +SWEP.PushSoundBody = Sound('Flesh.ImpactSoft') + +SWEP.Icon = 'octoteam/icons/crowbar.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_hook.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_hook.mdl') +SWEP.ActiveHoldType = 'melee' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_knife.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_knife.lua new file mode 100644 index 0000000..4b06e31 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_knife.lua @@ -0,0 +1,79 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = L.knife +SWEP.Instructions = 'ПКМ - прицелиться.\nЛКМ - атаковать.\nR - мощный удар (наносит огромный урон при ударе в спину)' + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'sharp' +SWEP.HitDistance = 54 +SWEP.HitPushback = 0 +SWEP.HitRate = 0.4 +SWEP.Damage = {5, 15} +SWEP.ScareMultiplier = 0.6 + +local slash = Sound('Weapon_Knife.Slash') +local stab = Sound('Weapon_Knife.Stab') +SWEP.SwingSound = slash +SWEP.HitSoundWorld = Sound('Weapon_Knife.HitWall') +SWEP.HitSoundBody = Sound('Weapon_Knife.Hit') + +SWEP.Icon = 'octoteam/icons/knife.png' +SWEP.ViewModel = Model('models/weapons/v_knife_t.mdl') +SWEP.WorldModel = Model('models/weapons/w_knife_t.mdl') +SWEP.ActiveHoldType = 'knife' +SWEP.TrajectoryType = 'point' +SWEP.DeploySound = Sound('Weapon_Knife.Deploy') + +function SWEP:GetDamage(tr) + local ent = tr.Entity + if not IsValid(ent) then return 0 end + if self.powerful then + return self:EntityFaceBack(ent) and 195 or 65 + else return math.random(6) == 3 and 20 or 15 end +end + +function SWEP:OnBulletShot() + self.nextReload = CurTime() + 5 +end + +function SWEP:EntityFaceBack(ent) + + local angle = self.Owner:GetAngles().y -ent:GetAngles().y + if angle < -180 then angle = 360 + angle end + if angle <= 90 and angle >= -90 then return true end + return false + +end + +function SWEP:Reload() + if not IsFirstTimePredicted() then return end + local time = CurTime() + local ok = self:GetNetVar('CanSetReady') and self:GetNetVar('IsReady') and (self.nextReload or 0) <= time and self.Owner:GetNetVar('ScareState', 0) <= 0.6 + if not ok then return end + + self:SetNextPrimaryFire(time + 5) + self.nextReload = CurTime() + 5 + self.Owner:SetAnimation(PLAYER_ATTACK1) + + if SERVER then + if self.SwingSound then self.Owner:EmitSound(self.SwingSound) end -- prevent doubling on client + timer.Create('hitdelay' .. self:EntIndex(), 0.1, 1, function() + self.powerful, self.SwingSound = true, stab + self:Attack() + self.powerful, self.SwingSound = false, slash + end) + end + +end + +function SWEP:DoImpactEffect(tr) + util.Decal('ManhackCut', tr.HitPos + tr.HitNormal, tr.HitPos - tr.HitNormal) + return true +end diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m249.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m249.lua new file mode 100644 index 0000000..9aa2773 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m249.lua @@ -0,0 +1,29 @@ +SWEP.Base = "weapon_octo_base_rifle" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "M249" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "m249.fire" ) +SWEP.Primary.DistantSound = Sound( "m249.fire-distant" ) +SWEP.Primary.Damage = 32 +SWEP.Primary.RPM = 650 +SWEP.Primary.ClipSize = 60 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 1 +SWEP.Primary.KickHorizontal = 1.04 +SWEP.Primary.Spread = 0.04 + +SWEP.WorldModel = "models/weapons/w_mach_m249para.mdl" +SWEP.AimPos = Vector(-6, -0.82, 6.4) +SWEP.AimAng = Angle(-9, 0, 0) + +SWEP.MuzzlePos = Vector(25, -1, 10) +SWEP.MuzzleAng = Angle(-10, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m3.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m3.lua new file mode 100644 index 0000000..4d51697 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m3.lua @@ -0,0 +1,29 @@ +SWEP.Base = "weapon_octo_base_shotgun" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "M3" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "m3.fire" ) +SWEP.Primary.DistantSound = Sound( "m3.fire-distant" ) +SWEP.Primary.Damage = 13 +SWEP.Primary.RPM = 70 +SWEP.Primary.ClipSize = 7 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 5.28 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.1 +SWEP.Primary.Distance = 1250 + +SWEP.WorldModel = "models/weapons/w_shot_m3super90.mdl" +SWEP.AimPos = Vector(-5, -0.94, 4.6) +SWEP.AimAng = Angle(-9, 0, 0) + +SWEP.Primary.NumShots = 8 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m4a1.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m4a1.lua new file mode 100644 index 0000000..532d390 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_m4a1.lua @@ -0,0 +1,25 @@ +SWEP.Base = "weapon_octo_base_rifle" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "M4A1" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "m4a1.fire" ) +SWEP.Primary.DistantSound = Sound( "m4a1.fire-distant" ) +SWEP.Primary.Damage = 27 +SWEP.Primary.RPM = 750 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0.66 +SWEP.Primary.KickHorizontal = 0.64 + +SWEP.WorldModel = "models/weapons/w_rif_m4a1.mdl" +SWEP.AimPos = Vector(-8, -0.97, 5.9) +SWEP.AimAng = Angle(-9, 0, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_mac10.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_mac10.lua new file mode 100644 index 0000000..22fc089 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_mac10.lua @@ -0,0 +1,29 @@ +SWEP.Base = "weapon_octo_base_smg" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "MAC10" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "mac10.fire" ) +SWEP.Primary.DistantSound = Sound( "mac10.fire-distant" ) +SWEP.Primary.Damage = 15 +SWEP.Primary.RPM = 1270 +SWEP.Primary.ClipSize = 32 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0.375 +SWEP.Primary.KickHorizontal = 1.9 +SWEP.Primary.Spread = 0.08 + +SWEP.WorldModel = "models/weapons/w_smg_mac10.mdl" +SWEP.AimPos = Vector(-12, -1.62, 5.2) +SWEP.AimAng = Angle(-2, 5, 0) + +SWEP.PassiveHoldType = "normal" +SWEP.ActiveHoldType = "pistol" diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_mp5.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_mp5.lua new file mode 100644 index 0000000..9622829 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_mp5.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_smg" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "MP5" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "mp5navy.fire" ) +SWEP.Primary.DistantSound = Sound( "mp5navy.fire-distant" ) +SWEP.Primary.Damage = 18 +SWEP.Primary.RPM = 800 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.3 +SWEP.Primary.KickDown = 0.63 +SWEP.Primary.KickHorizontal = 0.7 + + +SWEP.WorldModel = "models/weapons/w_smg_mp5.mdl" +SWEP.MuzzlePos = Vector(12, -0.6, 7.5) +SWEP.MuzzleAng = Angle(-7.5, 2.2, 0) +SWEP.AimPos = Vector(-7, -1.38, 7.4) +SWEP.AimAng = Angle(-7.5, 2.2, 0) + +SWEP.ActiveHoldType = "smg" diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_p228.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_p228.lua new file mode 100644 index 0000000..657f373 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_p228.lua @@ -0,0 +1,25 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "P228" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "p228.fire" ) +SWEP.Primary.DistantSound = Sound( "p228.fire-distant" ) +SWEP.Primary.Damage = 30 +SWEP.Primary.RPM = 400 +SWEP.Primary.ClipSize = 15 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.95 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 + +SWEP.WorldModel = "models/weapons/w_pist_p228.mdl" +SWEP.AimPos = Vector(-10.5, -1.16, 4.15) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_p90.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_p90.lua new file mode 100644 index 0000000..262358f --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_p90.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_smg" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "P90" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "p90.fire" ) +SWEP.Primary.DistantSound = Sound( "p90.fire-distant" ) +SWEP.Primary.Damage = 18 +SWEP.Primary.RPM = 970 +SWEP.Primary.ClipSize = 50 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.3 +SWEP.Primary.KickDown = 0.45 +SWEP.Primary.KickHorizontal = 0.49 + + +SWEP.WorldModel = "models/weapons/w_smg_p90.mdl" +SWEP.MuzzlePos = Vector(12, -0.35, 6) +SWEP.MuzzleAng = Angle(-3, 2.2, 0) +SWEP.AimPos = Vector(-7, -1.03, 10.15) +SWEP.AimAng = Angle(-3, 2.2, 0) + +SWEP.ActiveHoldType = "smg" diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pan.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pan.lua new file mode 100644 index 0000000..7d993d9 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pan.lua @@ -0,0 +1,28 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = L.pan + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'blunt' +SWEP.HitDistance = 40 +SWEP.HitInclination = 0.2 +SWEP.HitPushback = 300 +SWEP.HitRate = 1 +SWEP.Damage = {12, 18} +SWEP.ScareMultiplier = 0.5 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Metal_Box.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh.ImpactHard') + +SWEP.Icon = 'octoteam/icons/pan.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_pan.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_pan.mdl') +SWEP.ActiveHoldType = 'melee' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pickaxe.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pickaxe.lua new file mode 100644 index 0000000..2eba7ca --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pickaxe.lua @@ -0,0 +1,29 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = 'Кирка' + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'sharp' +SWEP.HitDistance = 40 +SWEP.HitInclination = 0.4 +SWEP.HitPushback = 1000 +SWEP.HitRate = 1.35 +SWEP.Damage = {34, 50} +SWEP.ScareMultiplier = 0.7 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Canister.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh.ImpactHard') +SWEP.PushSoundBody = Sound('Flesh.ImpactSoft') + +SWEP.Icon = 'octoteam/icons/pickaxe.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_pickaxe.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_pickaxe.mdl') +SWEP.ActiveHoldType = 'melee2' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pipe.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pipe.lua new file mode 100644 index 0000000..bcd657e --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pipe.lua @@ -0,0 +1,28 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = 'Труба' + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'blunt' +SWEP.HitDistance = 45 +SWEP.HitInclination = 0.2 +SWEP.HitPushback = 400 +SWEP.HitRate = 1.1 +SWEP.Damage = {34, 50} +SWEP.ScareMultiplier = 0.4 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Canister.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh.ImpactHard') + +SWEP.Icon = 'octoteam/icons/pipe.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_pipe.mdl') +SWEP.WorldModel = Model('models/props_canal/mattpipe.mdl') +SWEP.ActiveHoldType = 'melee2' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pot.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pot.lua new file mode 100644 index 0000000..32c055e --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_pot.lua @@ -0,0 +1,28 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = L.pot + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'blunt' +SWEP.HitDistance = 40 +SWEP.HitInclination = 0.2 +SWEP.HitPushback = 200 +SWEP.HitRate = 0.90 +SWEP.Damage = {10, 15} +SWEP.ScareMultiplier = 0.4 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Metal_Box.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh.ImpactHard') + +SWEP.Icon = 'octoteam/icons/pot.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_pot.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_pot.mdl') +SWEP.ActiveHoldType = 'melee' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_scout.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_scout.lua new file mode 100644 index 0000000..376c33d --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_scout.lua @@ -0,0 +1,31 @@ +SWEP.Base = "weapon_octo_base_sniper" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "Scout" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "scout.fire" ) +SWEP.Primary.DistantSound = Sound( "scout.fire-distant" ) +SWEP.Primary.Damage = 35 +SWEP.Primary.RPM = 60 +SWEP.Primary.ClipSize = 10 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 6.7 +SWEP.Primary.KickDown = 1.2 +SWEP.Primary.KickHorizontal = 0.02 +SWEP.Primary.Spread = 0 + +SWEP.WorldModel = "models/weapons/w_snip_scout.mdl" +SWEP.AimPos = Vector(-0.5, -0.98, 5.8) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(1.6, -0.99, 6.17) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1.3 +SWEP.SightFOV = 10 +SWEP.SightZNear = 12 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_sg550.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_sg550.lua new file mode 100644 index 0000000..3c5c570 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_sg550.lua @@ -0,0 +1,31 @@ +SWEP.Base = "weapon_octo_base_sniper" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "SG550" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "sg550.fire" ) +SWEP.Primary.DistantSound = Sound( "sg550.fire-distant" ) +SWEP.Primary.Damage = 51 +SWEP.Primary.RPM = 450 +SWEP.Primary.ClipSize = 20 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.9 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0 + +SWEP.WorldModel = "models/weapons/w_snip_sg550.mdl" +SWEP.Icon = 'octoteam/icons/gun_rifle.png' +SWEP.AimPos = Vector(-3.5, -0.8, 5.2) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(-1.5, -0.79, 5.41) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1.3 +SWEP.SightFOV = 12 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_sg552.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_sg552.lua new file mode 100644 index 0000000..7fffd88 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_sg552.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_zoom" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "SG552" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "sg552.fire" ) +SWEP.Primary.DistantSound = Sound( "sg552.fire-distant" ) +SWEP.Primary.Damage = 28 +SWEP.Primary.RPM = 690 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0 +SWEP.Primary.KickDown = 0.55 +SWEP.Primary.KickHorizontal = 0.9 +SWEP.Primary.Spread = 0 + +SWEP.WorldModel = "models/weapons/w_rif_sg552.mdl" +SWEP.AimPos = Vector(-4, -0.89, 5.6) +SWEP.AimAng = Angle(-9, 0, 0) +SWEP.SightPos = Vector(-0.8, -0.89, 6.2) +SWEP.SightAng = Angle(0, -90, 100) +SWEP.SightSize = 1.4 +SWEP.SightFOV = 18 diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_shovel.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_shovel.lua new file mode 100644 index 0000000..1787e76 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_shovel.lua @@ -0,0 +1,29 @@ +SWEP.Base = 'weapon_octo_base_cold' +SWEP.Category = L.dobrograd .. ' - Холодное' +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = L.shovel + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Ammo = 'blunt' +SWEP.HitDistance = 50 +SWEP.HitInclination = 0.4 +SWEP.HitPushback = 2000 +SWEP.HitRate = 1.25 +SWEP.Damage = {15, 25} +SWEP.ScareMultiplier = 0.7 + +SWEP.SwingSound = Sound('WeaponFrag.Roll') +SWEP.HitSoundWorld = Sound('Canister.ImpactHard') +SWEP.HitSoundBody = Sound('Flesh.ImpactHard') +SWEP.PushSoundBody = Sound('Flesh.ImpactSoft') + +SWEP.Icon = 'octoteam/icons/shovel.png' +SWEP.ViewModel = Model('models/weapons/HL2meleepack/v_shovel.mdl') +SWEP.WorldModel = Model('models/weapons/HL2meleepack/w_shovel.mdl') +SWEP.ActiveHoldType = 'melee2' diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_tmp.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_tmp.lua new file mode 100644 index 0000000..a8ae6ab --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_tmp.lua @@ -0,0 +1,27 @@ +SWEP.Base = "weapon_octo_base_smg" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "TMP" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "tmp.fire" ) +SWEP.Primary.DistantSound = Sound( "tmp.fire-distant" ) +SWEP.Primary.Damage = 19 +SWEP.Primary.RPM = 980 +SWEP.Primary.ClipSize = 20 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.05 +SWEP.Primary.KickDown = 0.4 +SWEP.Primary.KickHorizontal = 1.3 + +SWEP.WorldModel = "models/weapons/w_smg_tmp.mdl" +SWEP.MuzzlePos = Vector(20, -0.5, 7) +SWEP.MuzzleAng = Angle(-9, 1, 0) +SWEP.AimPos = Vector(-8, -1.04, 3.8) +SWEP.AimAng = Angle(-9, 1, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_ump45.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_ump45.lua new file mode 100644 index 0000000..90f52d7 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_ump45.lua @@ -0,0 +1,30 @@ +SWEP.Base = "weapon_octo_base_smg" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "UMP45" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "ump45.fire" ) +SWEP.Primary.DistantSound = Sound( "ump45.fire-distant" ) +SWEP.Primary.Damage = 23 +SWEP.Primary.RPM = 600 +SWEP.Primary.ClipSize = 25 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.1 +SWEP.Primary.KickDown = 0.072 +SWEP.Primary.KickHorizontal = 1 + + +SWEP.WorldModel = "models/weapons/w_smg_ump45.mdl" +SWEP.MuzzlePos = Vector(12, -0.9, 7) +SWEP.MuzzleAng = Angle(-9.5, 0, 0) +SWEP.AimPos = Vector(-7, -0.8, 5.4) +SWEP.AimAng = Angle(-9.5, 0, 0) + +SWEP.ActiveHoldType = "smg" diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_usp.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_usp.lua new file mode 100644 index 0000000..7c10435 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_usp.lua @@ -0,0 +1,25 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "USP" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "usp.fire" ) +SWEP.Primary.DistantSound = Sound( "usp.fire-distant" ) +SWEP.Primary.Damage = 25 +SWEP.Primary.RPM = 825 +SWEP.Primary.ClipSize = 12 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 0.42 +SWEP.Primary.KickDown = 0.075 +SWEP.Primary.KickHorizontal = 0.03 + +SWEP.WorldModel = "models/weapons/w_pist_usp.mdl" +SWEP.AimPos = Vector(-10.5, -1.13, 4.05) +SWEP.AimAng = Angle(-2, 5, 0) diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_usps.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_usps.lua new file mode 100644 index 0000000..8f46125 --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_usps.lua @@ -0,0 +1,27 @@ +SWEP.Base = "weapon_octo_base_pistol" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "USP-S" + +if SERVER then + + AddCSLuaFile() + +end + +SWEP.Primary.Sound = Sound( "usps.fire" ) +SWEP.Primary.DistantSound = Sound( "usps.fire-distant" ) +SWEP.Primary.Damage = 21 +SWEP.Primary.RPM = 825 +SWEP.Primary.ClipSize = 12 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 1.7 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 + +SWEP.WorldModel = "models/weapons/w_pist_usp_silencer.mdl" +SWEP.AimPos = Vector(-10.5, -1.13, 4.05) +SWEP.AimAng = Angle(-2, 5, 0) + +SWEP.NoMuzzleflash = true diff --git a/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_xm1014.lua b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_xm1014.lua new file mode 100644 index 0000000..031865f --- /dev/null +++ b/garrysmod/addons/core-weapons/lua/weapons/weapon_octo_xm1014.lua @@ -0,0 +1,27 @@ +SWEP.Base = "weapon_octo_base_shotgun" +SWEP.Category = L.dobrograd +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.PrintName = "XM1014" + +if SERVER then + AddCSLuaFile() +end + +SWEP.Primary.Sound = Sound( "xm1014.fire" ) +SWEP.Primary.DistantSound = Sound( "xm1014.fire-distant" ) +SWEP.Primary.Damage = 15 +SWEP.Primary.RPM = 200 +SWEP.Primary.ClipSize = 7 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.KickUp = 5.28 +SWEP.Primary.KickDown = 0.3 +SWEP.Primary.KickHorizontal = 0.03 +SWEP.Primary.Spread = 0.1 +SWEP.Primary.Distance = 1250 + +SWEP.WorldModel = "models/weapons/w_shot_xm1014.mdl" +SWEP.AimPos = Vector(-5, -0.8, 4.2) +SWEP.AimAng = Angle(-9, 0, 0) + +SWEP.Primary.NumShots = 8 diff --git a/garrysmod/addons/event-halloween/lua/autorun/halloween.lua b/garrysmod/addons/event-halloween/lua/autorun/halloween.lua new file mode 100644 index 0000000..3b1ac39 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/autorun/halloween.lua @@ -0,0 +1,24 @@ +-- halloween = halloween or {} + +-- timer.Simple(0, function() +-- if CFG.dev then +-- octolib.client('halloween/cl_dev') +-- end +-- end) +-- octolib.client('halloween/cl_config') +-- octolib.client('halloween/cl_images') +octolib.client('halloween/cl_textures') +-- octolib.server('halloween/sv_images') + +-- octolib.server('halloween/sv_sweets') +-- octolib.client('halloween/cl_halloween') + +-- octolib.server('halloween/sv_rewards') +-- octolib.client('halloween/cl_rewards') +-- octolib.server('config/sweets-items') +hook.Add('dbg-char.firstSpawn', 'dbg-halloween.theme', function(ply) + if ply:GetDBVar('halloweenTheme') then + ply:SetLocalVar('halloweenTheme', true) + ply:ConCommand('octogui_reloadf4') + end +end) diff --git a/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/cl_init.lua b/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/cl_init.lua new file mode 100644 index 0000000..712a9e1 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/cl_init.lua @@ -0,0 +1,21 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + + self:DrawModel() + +end + +netstream.Hook('dbg-halloween.playPhrase', function(ent, snd) + sound.PlayFile(snd, '3d', function(st) + if IsValid(st) then + st:SetPos(ent:LocalToWorld(ent:OBBCenter())) + st:Set3DFadeDistance(55, 80) + st:Play() + end + end) +end) diff --git a/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/init.lua b/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/init.lua new file mode 100644 index 0000000..63d55c6 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/init.lua @@ -0,0 +1,116 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +local sweets = {10, 20} + +local plyPhrases = { + 'Сладость или гадость!', + 'Кошелек или жизнь!', + --'Это ограбление, живо весь шоколад на пол! И смотри мне, чтобы без орешков - от них мне хочется слушать Грин Дей', +} + +local template = 'sound/dobrohalloween/day%s.wav' +local entPhrases = { +-- {'path to sound file', length in seconds} + {'1/1', 11}, + {'1/2', 8}, + {'1/3', 9}, + {'1/4', 10}, + {'1/5', 12}, + {'1/6', 8}, + {'1/7', 6}, + {'1/8', 10}, + {'1/9', 9}, + {'1/10', 10}, + {'1/11', 13}, + {'2/1', 8}, + {'2/2', 8}, + {'2/3', 7}, + {'2/5', 9}, + {'2/6', 11}, + {'2/7', 12}, + {'2/8', 8}, + {'2/9', 13}, + {'2/10', 9}, + {'2/12', 15}, + {'2/13', 10}, + {'2/14', 15}, + {'2/15', 9}, + {'2/custom1', 10}, + {'2/custom2', 14}, + {'2/custom3', 13}, + {'2/custom4', 17}, + {'2/custom5', 20}, +} +octolib.array.shuffle(entPhrases) + +local function doKnock(ply) + + ply:EmitSound('physics/wood/wood_crate_impact_hard' .. math.random(2, 3) .. '.wav', 40, math.random(90, 110)) + ply:DoAnimation(ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST) + +end + +local lastTaken = 0 + +function ENT:Initialize() + + return self:Remove() + -- self:SetModel('models/props_c17/door01_left.mdl') + -- self:SetBodygroup(1, 1) + -- self:PhysicsInit(SOLID_VPHYSICS) + -- self:SetMoveType(MOVETYPE_VPHYSICS) + -- self:SetSolid(SOLID_VPHYSICS) + -- self:SetUseType(SIMPLE_USE) + + -- lastTaken = lastTaken % #entPhrases + 1 + -- self.phrase = {template:format(entPhrases[lastTaken][1]), entPhrases[lastTaken][2]} + -- self.sweetsTaken, self.sweetsWaiting = {}, {} +end + +function ENT:GiveSweets(ply, sid) + self.sweetsWaiting[sid] = nil + if not IsValid(ply) then return end + if ply:GetShootPos():DistToSqr(self:NearestPoint(ply:GetShootPos())) <= CFG.useDistSqr then + local amount = math.random(unpack(sweets)) + ply:AddSweets(amount) + self.sweetsTaken[sid] = true + hook.Run('dbg-halloween.gotSweets', ply, amount, self) + end + ply.wantsSweets = nil +end + +function ENT:Act(ply, sid) + + if not IsValid(ply) then return end + netstream.Start(ply, 'dbg-halloween.playPhrase', self, self.phrase[1]) + timer.Simple(self.phrase[2] * 0.75, function() + if IsValid(self) then self:GiveSweets(ply, sid) elseif IsValid(ply) then ply.wantsSweets = nil end + end) + +end + +function ENT:Use(ply) + + if not ply:IsPlayer() then return end + local sid = ply:SteamID() + if self.sweetsWaiting[sid] or self.sweetsTaken[sid] then return end + if ply.wantsSweets then return end + if not ply.halloweenTheme then + return ply:Notify('warning', 'А как же хэллоуинское настроение? Активируй его во вкладке "Хэллоуин" в F4-меню!') + end + + self.sweetsWaiting[sid] = true + ply.wantsSweets = true + doKnock(ply) + timer.Simple(0.3, function() doKnock(ply) end) + timer.Simple(0.6, function() doKnock(ply) end) + ply:Say(plyPhrases[math.random(#plyPhrases)]) + + timer.Simple(2, function() + if IsValid(self) then self:Act(ply, sid) elseif IsValid(ply) then ply.wantsSweets = nil end + end) + +end diff --git a/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/shared.lua b/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/shared.lua new file mode 100644 index 0000000..5c75e34 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/entities/dbg_h_door/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'Источник конфет' +ENT.Category = L.dobrograd_halloween +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +ENT.Spawnable = false -- !!!!!!! +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/cl_init.lua b/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/cl_init.lua new file mode 100644 index 0000000..d0684b2 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/cl_init.lua @@ -0,0 +1,11 @@ +include 'shared.lua' + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/init.lua b/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/init.lua new file mode 100644 index 0000000..ce4b8f3 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/init.lua @@ -0,0 +1,30 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + return self:Remove() + -- self:SetModel('models/treasurechest/treasurechest.mdl') + -- self:PhysicsInit(SOLID_VPHYSICS) + -- self:SetMoveType(MOVETYPE_VPHYSICS) + -- self:SetSolid(SOLID_VPHYSICS) + -- self:SetUseType(SIMPLE_USE) +end + +function ENT:Use(ply) + if not ply:IsPlayer() then return end + local ct = CurTime() + if (ply.nextRewardsUse or 0) > ct then return end + if ply:GetNetVar('sweets', 0) <= 0 then + ply.nextRewardsUse = ct + 60 + return octochat.talkTo(ply, octochat.textColors.rp, 'Джек говорит: ', color_white, 'Нет конфет? Очень жаль. Надеюсь, увидимся на следующий Хэллоуин!') + end + ply.nextRewardsUse = ct + 2 + netstream.Start(ply, 'dbg-halloween.openRewards', self, halloween.collectData(ply)) + local mod = 1 - halloween.getPriceModifier(ply) + if mod > 0 then + octochat.talkTo(ply, octochat.textColors.rp, 'Джек говорит: ', color_white, ('О, мне про тебя рассказывали! Сделаю-ка я тебе скидку в %s%%'):format(mod * 100)) + end +end diff --git a/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/shared.lua b/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/shared.lua new file mode 100644 index 0000000..241d98d --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/entities/dbg_h_rewards/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'Конфетные призы' +ENT.Category = L.dobrograd_halloween +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/event-halloween/lua/halloween/cl_config.lua b/garrysmod/addons/event-halloween/lua/halloween/cl_config.lua new file mode 100644 index 0000000..d5f9a85 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/cl_config.lua @@ -0,0 +1,271 @@ +local images = { + 'D81AsUU.png', -- freddy + 'NyJwvT6.png', -- headless + 'UH8uDXU.png', -- pennywise + 'QcjN7kN.png', -- scarecrow + '7sp3ChY.png', -- scream + 'LleVpog.png', -- vampire + 'tQc7IY2.png', -- werewolf + '9LF6kcF.png', -- zombie +} +local w = { + { -- Йорк + {'KzQyhe9.png', 'Cs3PNZo.png'}, -- 3, 13 + {'qbTQV6X.png', 'NSNobBF.png'}, -- 9 + }, { -- Ирвин + {'Xiusudh.png', 'IcGvAPK.png'}, -- 1 + {'B9PPmp8.png', '81YobY3.png'}, -- 3, 4, 8, 10, 13 + {'Jgir8mw.png', 'xBLr3qA.png'}, -- 9 + }, { -- Рурк + {'Jgir8mw.png', 'xBLr3qA.png'}, -- 1 + {'e7Kmmf0.png', 'hBT64Hl.png'}, -- 8 + }, { -- Джойс + {'KiOrU2e.png', 'xBLr3qA.png'}, -- 3 + {'6Rjf9nD.png', '5UO55J0.png'}, -- 5 + {'NkXud13.png', 'fFP8rhx.png'}, -- 6 + {'Cq8VAau.png', 'OXyQvnu.png'}, -- 8 + {'liHdx0A.png', 'JiZgoBt.png'}, -- 10 + {'Jgir8mw.png', 'xBLr3qA.png'}, -- 13 + {'CRafYma.png', 'fWUZUXW.png'}, -- 15 + }, { -- Стефан + {'akaIYOQ.png', 'cjVKuUO.png'}, -- 2 + }, +} + +-- Everythings starts from bottom-right and moves snake-like and moves left and up +local windowPositions = { + { -- Йорк 3 + { Vector(-3903.7,1644.1,235.9), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1516,236), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1260.1,236), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1260.1,364), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1388.1,364), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1516.1,363.9), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1772.0,364), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1772,491.9), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1644.1,492), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1516.0,491.9), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1388.1,492), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + { Vector(-3903.7,1260.1,492), Angle(0,90,90), 399.6, 717.8, w[1][1] }, + }, { -- Йорк 9 + { Vector(-3116.1,767.3,295.9), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2860.1,767.3,296), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2732.1,767.3,296), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2732.1,767.3,423.9), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2988.1,767.3,423.9), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-3116.1,767.3,424), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-3116.1,767.3,551.9), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2988.1,767.3,551.9), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2860.1,767.3,552), Angle(0,180,90), 400, 717.8, w[1][2] }, + { Vector(-2732.1,767.3,551.9), Angle(0,180,90), 400, 717.8, w[1][2] }, + }, { -- Йорк 13 + { Vector(-2092.1,827.3,235.9), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-1836.7,827.3,234.8), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-1708.2,827.3,235.7), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-1708.2,827.3,363.9), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-1836.1,827.3,363.9), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-2092.1,827.3,363.9), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-2092.1,827.3,491.9), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-1836.2,827.3,491.9), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + { Vector(-1708.1,827.3,491.8), Angle(0,180,90), 398.7, 718.1, w[1][1] }, + }, { -- Ирвин 1 + { Vector(-895.3,1423.9,223.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1551.9,223.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1679.9,224), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1807.9,224), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1807.8,351.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1679.8,351.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1551.9,352), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1423.9,479.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1551.9,479.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1679.9,479.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + { Vector(-895.3,1807.8,479.9), Angle(0,-90,90), 319.1, 603.8, w[2][1] }, + }, { -- Ирвин 3 + { Vector(-895.3,915.8,235.9), Angle(0,-90,90), 397.3, 719.1, w[2][2] }, + { Vector(-895.3,1171.9,235.9), Angle(0,-90,90), 397.3, 719.1, w[2][2] }, + { Vector(-895.3,915.9,363.9), Angle(0,-90,90), 397.3, 719.1, w[2][2] }, + { Vector(-895.3,915.9,491.9), Angle(0,-90,90), 397.3, 719.1, w[2][2] }, + { Vector(-895.3,1043.9,491.9), Angle(0,-90,90), 397.3, 719.1, w[2][2] }, + { Vector(-895.3,1171.8,491.8), Angle(0,-90,90), 397.3, 719.1, w[2][2] }, + }, { -- Ирвин 4 + { Vector(-1669.7,620.1,235.9), Angle(0,90,90), 397.3, 721.4, w[2][2] }, + { Vector(-1668.7,492.1,235.8), Angle(0,90,90), 397.3, 721.4, w[2][2] }, + { Vector(-1668.7,492.2,363.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1669.7,620.2,363.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1669.7,748.1,363.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1669.7,748.2,491.8), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1669.7,620.1,491.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1668.7,492.2,491.8), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + }, { -- Ирвин 8 + { Vector(-1664.7,-19.8,235.8), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1664.7,-147.9,235.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1664.7,-403.9,235.8), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1664.7,-403.9,363.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1664.7,-19.8,491.8), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1664.7,-147.8,491.8), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + { Vector(-1664.7,-403.8,491.9), Angle(0,90,90), 398.3, 721.4, w[2][2] }, + }, { -- Ирвин 9 + { Vector(-895.3,-732.1,264), Angle(0,-90,90), 400.2, 720, w[2][3] }, + { Vector(-895.3,-604.1,263.9), Angle(0,-90,90), 400.2, 720, w[2][3] }, + { Vector(-895.3,-604.1,391.9), Angle(0,-90,90), 400.2, 720, w[2][3] }, + { Vector(-895.3,-732.1,391.9), Angle(0,-90,90), 400.2, 720, w[2][3] }, + { Vector(-895.3,-732.1,519.9), Angle(0,-90,90), 400.2, 720, w[2][3] }, + { Vector(-895.3,-604.1,520), Angle(0,-90,90), 400.2, 720, w[2][3] }, + }, { -- Ирвин 10 + { Vector(-1632.7,-531.8,203.8), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-787.8,203.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-787.9,331.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-531.8,331.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-531.9,459.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-787.9,459.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-787.8,587.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + { Vector(-1632.7,-531.9,587.9), Angle(0,90,90), 397.4, 718.4, w[2][2] }, + }, { -- Ирвин 13 + { Vector(-1440.1,-1351.7,187.8), Angle(0,180,90), 319.3, 598.4, w[2][2] }, + { Vector(-1600.1,-1351.7,187.9), Angle(0,180,90), 319.3, 598.4, w[2][2] }, + { Vector(-1520.1,-1351.7,187.9), Angle(0,180,90), 319.3, 598.4, w[2][2] }, + { Vector(-1600.2,-1351.7,347.9), Angle(0,180,90), 319.3, 598.4, w[2][2] }, + { Vector(-1440.1,-1351.7,347.9), Angle(0,180,90), 319.3, 598.4, w[2][2] }, + }, { -- Рурк 1 + { Vector(-812.1,-560.7,264), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-428.1,-560.7,264), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-300.1,-624.7,263.9), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-172.1,-624.7,391.9), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-300.2,-624.7,391.9), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-428.1,-560.7,391.9), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-812.1,-560.7,519.9), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-428.1,-560.7,520), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-300.1,-624.7,519.9), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + { Vector(-172.2,-624.7,520), Angle(0,180,90), 399.7, 719.2, w[3][1] }, + }, { -- Рурк 8 + { Vector(1330.9,-959.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1394.9,-959.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1459,-959.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1523,-1023.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1586.9,-1023.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1650.8,-1023.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1715,-1023.7,272.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1330.7,-959.7,400.2), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1394.6,-959.7,400.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1458.8,-959.7,400.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1522.7,-1023.7,400.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1586.7,-1023.7,400.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1650.6,-1023.7,400.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1714.8,-1023.7,400.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1714.7,-1023.7,528.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1650.7,-1023.7,528.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1586.6,-1023.7,528.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1522.9,-1023.7,528.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1458.8,-959.7,528.2), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1394.6,-959.7,528.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1330.9,-959.7,528.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1330.7,-959.7,656.2), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1394.9,-959.7,656.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1458.8,-959.7,656.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1522.9,-1023.7,656.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1587.1,-1023.7,656.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1651.1,-1023.7,656.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + { Vector(1715.1,-1023.7,656.1), Angle(0,180,90), 378.5, 719.2, w[3][2] }, + }, { -- Джойс 3 + { Vector(2567.7,-19.2,231.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,52.9,231.9), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,180.9,231.5), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,236.8,231.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,236.9,359.5), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,180.9,359.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,-19.2,359.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,-19.3,487.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,52.9,487.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,180.9,487.5), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + { Vector(2567.7,236.9,487.6), Angle(0,-90,90), 338.6, 636.1, w[4][1] }, + }, { -- Джойс 5 + { Vector(2336.7,-432.1,191.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-304.1,191.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-176.1,191.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-176.1,319.8), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-304.2,319.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2336.7,-432.1,319.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2336.7,-432.2,447.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-304.1,447.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-176.1,447.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-176.1,575.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2368.7,-304.1,575.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + { Vector(2336.7,-432.1,575.9), Angle(0,-90,90), 318.5, 639.6, w[4][2] }, + }, { -- Джойс 6 + { Vector(2375.7,-813.1,199.9), Angle(0,-90,90), 378.1, 720, w[4][3] }, + { Vector(2375.7,-685.1,199.9), Angle(0,-90,90), 378.1, 720, w[4][3] }, + { Vector(2375.7,-685.1,327.9), Angle(0,-90,90), 378.1, 720, w[4][3] }, + { Vector(2375.7,-813.1,327.9), Angle(0,-90,90), 378.1, 720, w[4][3] }, + { Vector(2375.7,-813.1,455.9), Angle(0,-90,90), 378.1, 720, w[4][3] }, + { Vector(2375.7,-685.1,455.9), Angle(0,-90,90), 378.1, 720, w[4][3] }, + }, { -- Джойс 8 + { Vector(2480.7,-3032,203.9), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-2776.1,203.9), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-2776,332), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-3032.1,331.9), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-3032.1,460), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-2776.1,459.9), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-2776.1,587.9), Angle(0,-90,90), 399.9, 720, w[4][4] }, + { Vector(2480.7,-3032.1,587.9), Angle(0,-90,90), 399.9, 720, w[4][4] }, + }, { -- Джойс 10 + { Vector(3816.1,-3119.3,191.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3688.1,-3119.3,192), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3560.1,-3119.3,191.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3432,-3119.3,192), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3432,-3119.3,319.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3560.1,-3119.3,319.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3688.1,-3119.3,319.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3816.1,-3119.3,447.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3688,-3119.3,447.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3560.1,-3119.3,447.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + { Vector(3432.1,-3119.3,447.9), Angle(0,0,90), 320.1, 599.3, w[4][5] }, + }, { -- Джойс 13 + { Vector(1466.3,-2750.9,199.6), Angle(0,90,90), 339.3, 631.8, w[4][6] }, + { Vector(1466.3,-2935.9,199.6), Angle(0,90,90), 339.3, 631.8, w[4][6] }, + { Vector(1466.3,-2991.9,199.6), Angle(0,90,90), 339.3, 631.8, w[4][6] }, + { Vector(1466.3,-2992,331.5), Angle(0,90,90), 339.3, 631.8, w[4][6] }, + { Vector(1466.3,-2935.9,331.6), Angle(0,90,90), 339.3, 631.8, w[4][6] }, + { Vector(1466.3,-2806.9,331.6), Angle(0,90,90), 339.3, 631.8, w[4][6] }, + }, { -- Джойс 15 + { Vector(1506.5,-3401.4,185.6), Angle(0,128.7,90), 291.4, 533.2, w[4][7] }, + { Vector(1521.3,-3470.9,185.6), Angle(0,90,90), 291.4, 533.2, w[4][7] }, + { Vector(1487.9,-3533.8,185.6), Angle(0,51.3,90), 291.4, 533.2, w[4][7] }, + { Vector(1487.9,-3533.8,313.6), Angle(0,51.3,90), 291.4, 533.2, w[4][7] }, + { Vector(1521.3,-3470.9,313.6), Angle(0,90,90), 291.4, 533.2, w[4][7] }, + { Vector(1506.4,-3401.3,313.6), Angle(0,128.7,90), 291.4, 533.2, w[4][7] }, + { Vector(1506.4,-3401.3,441.5), Angle(0,128.7,90), 291.4, 533.2, w[4][7] }, + { Vector(1521.3,-3470.9,441.6), Angle(0,90,90), 291.4, 533.2, w[4][7] }, + { Vector(1487.9,-3533.8,441.6), Angle(0,51.3,90), 291.4, 533.2, w[4][7] }, + }, { -- Стефан 2 + { Vector(3692.2,-767.3,203.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3436.2,-767.3,203.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3564.1,-767.3,331.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3692.1,-767.3,459.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3564.1,-767.3,459.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3436.1,-767.3,459.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3436.1,-767.3,587.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3564.1,-767.3,587.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + { Vector(3692.1,-767.3,587.9), Angle(0,0,90), 398.3, 719.8, w[5][1] }, + } +} + +function halloween.parseData(data) + if not images then return {} end + local response = {} + for _, v in ipairs(data) do + local window = windowPositions[v[1]][v[2]] + response[#response + 1] = { + pos = window[1], + ang = window[2], + url = { + bg = window[5][1], + window = window[5][2], + shape = images[v[3]], + }, + w = window[3], + h = window[4], + } + end + images, windowPositions = nil -- one-time call function + return response +end diff --git a/garrysmod/addons/event-halloween/lua/halloween/cl_dev.lua b/garrysmod/addons/event-halloween/lua/halloween/cl_dev.lua new file mode 100644 index 0000000..d1006e5 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/cl_dev.lua @@ -0,0 +1,149 @@ +local dist, distFade = 2000 * 2000, 300 * 300 +local image = { + pos = Vector(0,0,0), + ang = Angle(0,0,0), + url = 'BBZ7st2.png', + w = 284, + h = 633, +} + +local function drawImage() + + local myPos = EyePos() + local al = math.Clamp(1 - (image.pos:DistToSqr(myPos) - distFade) / dist, 0, 1) + if al <= 0 then return end + local mat = octolib.getImgurMaterial(image.url) + if mat == octolib.loadingMat then return end + + cam.Start3D2D(image.pos, image.ang, 0.1) + surface.SetDrawColor(255, 165, 0) + surface.DrawRect(0, 0, image.w, image.h) + surface.SetDrawColor(0, 0, 0) + surface.SetMaterial(mat) + surface.DrawTexturedRect(0, 0, image.w, image.h) + cam.End3D2D() +end + +local fr + +concommand.Add('halloween_imgs', function() + + if IsValid(fr) then fr:Remove() end + fr = vgui.Create 'DFrame' + fr:SetSize(300, 300) + fr:AlignBottom(10) + fr:AlignLeft(10) + + local tr = LocalPlayer():GetEyeTrace() + local pos, ang = tr.HitPos, tr.HitNormal:Angle() + ang:RotateAroundAxis(ang:Up(), 90) + ang:RotateAroundAxis(ang:Forward(), 90) + + local prop = fr:Add 'DProperties' + prop:Dock(FILL) + + local function slider(cat, name, min, max, change, decimals) + local r = prop:CreateRow(cat, name) + r:Setup('Float', {min = min, max = max}) + r:SetValue(0) + + local l = r:GetChild(1):GetChild(0) + l.Paint = function() end + l:GetChild(0):SetDecimals(decimals or 2) + function r:DataChanged(v) change(v) end + + return r + end + + local cPos, cAng = Vector(0, 0, 0.25), Angle(0, 0, 0) + slider('Позиция', 'X', -10, 10, function(val) + cPos.x = val + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + end):SetValue(0) + slider('Позиция', 'Y', -10, 10, function(val) + cPos.y = val + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + end):SetValue(0) + slider('Позиция', 'Z', -10, 10, function(val) + cPos.z = val + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + end):SetValue(0.5) + + slider('Угол', 'P', -180, 180, function(val) + cAng.p = val + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + end):SetValue(ang.p) + slider('Угол', 'Y', -180, 180, function(val) + cAng.y = val + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + end):SetValue(ang.y) + slider('Угол', 'R', -180, 180, function(val) + cAng.r = val + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + end):SetValue(ang.r) + + image.pos, image.ang = LocalToWorld(cPos, cAng, pos, ang) + + slider('Размеры', 'W', 1, 1000, function(val) + image.w = val + end):SetValue(image.w) + slider('Размеры', 'H', 1, 1000, function(val) + image.h = val + end):SetValue(image.h) + + hook.Add('PostDrawOpaqueRenderables', 'dbg-halloween.images.dev', drawImage) + + for _, pnl in pairs(prop.Categories) do + pnl.Container.Paint = octolib.func.zero + end + + octolib.button(fr, 'Скопировать', function() + SetClipboardText(('{ Vector(%.1f,%.1f,%.1f), Angle(%.1f,%.1f,%.1f), %.1f, %.1f },'):format( + image.pos.x, image.pos.y, image.pos.z, + image.ang.p, image.ang.y, image.ang.r, + image.w, image.h):gsub('-0.0', '0'):gsub('%.0', '')) + end) + + function fr:OnRemove() + hook.Remove('PostDrawOpaqueRenderables', 'dbg-halloween.images.dev') + end + +end) + +local bounds = Vector(10,10,10) +local offset = Vector(0,0,10) +local lootboxFr +concommand.Add('halloween_lootbox', function() + local ply = LocalPlayer() + if IsValid(lootboxFr) then lootboxFr:Remove() end + + hook.Add('PostDrawTranslucentRenderables', 'dbg-halloween.lootbox.dev', function() + local tr = ply:GetEyeTrace() + local pos = tr.HitPos + offset + local col = util.TraceHull({ + mins = -bounds, + maxs = bounds, + start = pos, + endpos = pos, + ignoreworld = true, + }).Hit and color_red or color_green + render.DrawWireframeBox(pos, Angle(0,0,0), -bounds, bounds, col, true) + end) + lootboxFr = vgui.Create 'DFrame' + lootboxFr:SetSize(300, 100) + lootboxFr:AlignBottom(10) + lootboxFr:AlignLeft(10) + + local btn = lootboxFr:Add('DButton') + btn:Dock(FILL) + btn:SetText('Скопировать') + function btn:DoClick() + local pos = ply:GetEyeTrace().HitPos + offset + SetClipboardText('Vector(' .. math.Round(pos.x, 1) .. ', ' .. math.Round(pos.y, 1) .. ', ' .. math.Round(pos.z, 1) .. '),') + end + + function lootboxFr:OnRemove() + hook.Remove('PostDrawTranslucentRenderables', 'dbg-halloween.lootbox.dev') + end + +end) diff --git a/garrysmod/addons/event-halloween/lua/halloween/cl_halloween.lua b/garrysmod/addons/event-halloween/lua/halloween/cl_halloween.lua new file mode 100644 index 0000000..849fa72 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/cl_halloween.lua @@ -0,0 +1,84 @@ +local msg = [[ Раздача подарков за конфеты! + + В город приехал Джек, такой парнишка, который готов скупить все конфеты в этом городе. Он разместил свою лавку в самом сердце города и работает круглосуточно. + + Поспеши! 1 декабря Джек уже поедет обратно в свой родной Сент-Хеленс.]] + +local cols = { + bg = Color(52, 49, 52), + g = Color(222, 132, 38), + hvr = Color(0,0,0, 50), +} +cols.bg_d = Color(cols.bg.r * 0.75, cols.bg.g * 0.75, cols.bg.b * 0.75) +cols.g_d = Color(cols.g.r * 0.75, cols.g.g * 0.75, cols.g.b * 0.75) +cols.bg60 = ColorAlpha(cols.bg, 150) + +local function paintPanel(_, w, h) + + surface.SetDrawColor(255, 255, 255, 75) + + local mat = octolib.getImgurMaterial('mI3Fq48.jpg') + surface.SetMaterial(mat) + local ww, hh = 64, 64 + if mat ~= octolib.loadingMat then + ww, hh = w, h + end + + surface.DrawTexturedRect((w - ww) / 2, (h - hh) / 2, ww, hh) + +end + +hook.Add('octogui.f4-tabs', 'halloween', function() + + octogui.addToF4({ + order = 0.1, + id = 'halloween', + name = 'Хэллоуин', + icon = Material('octoteam/icons/jackolantern.png'), + build = function(f) + f:SetSize(500, 500) + + local pan = f:Add 'DPanel' + pan.Paint = paintPanel + pan:Dock(FILL) + + local lbl = octolib.label(pan, msg) + lbl:DockMargin(10, 5, 10, 10) + lbl:SetFont('f4.normal') + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:Dock(FILL) + + end, + show = function() + F4:SetCounter('halloween', 0) + octolib.vars.set('hlw_sweets', true) + end + }) + if not octolib.vars.get('hlw_sweets') then + F4:SetCounter('halloween', 1) + end + +end) + +hook.Add('Think', 'dbg-halloween.sweetsCommand', function() +hook.Remove('Think', 'dbg-halloween.sweetsCommand') +octochat.defineCommand('!sweets', { + aliases = {'~sweets'}, + check = DarkRP.isAdmin, +}) +end) + +hook.Add('octolib.netVarUpdate', 'dbg-halloween', function(_, varName, varVal) + if varName == 'sweets' and varVal then F4:SetCounter('halloween', 1) end +end) + +hook.Add('InitPostEntity', 'dbg-halloween', function() + if game.GetMap() == 'rp_truenorth_v1a' then + msg = [[ Раздача подарков за конфеты! + + В историческом и первом новом районах города можно найти Джека, такого парнишку, который готов скупить все конфеты в этом городе. Он разместил свою лавку в самом сердце города и работает круглосуточно. + + Поспеши! 1 декабря Джек уже поедет обратно в свой родной Сент-Хеленс.]] + end +end) diff --git a/garrysmod/addons/event-halloween/lua/halloween/cl_images.lua b/garrysmod/addons/event-halloween/lua/halloween/cl_images.lua new file mode 100644 index 0000000..1830697 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/cl_images.lua @@ -0,0 +1,39 @@ +local dist, distFade = 2000 * 2000, 300 * 300 + + +local function drawImage(data) + + local al = math.Clamp(1 - (data.pos:DistToSqr(EyePos()) - distFade) / dist, 0, 1) + if al <= 0 then return end + local matBg = octolib.getImgurMaterial(data.url.bg) + if matBg == octolib.loadingMat then return end + local matWindow = octolib.getImgurMaterial(data.url.window) + if matWindow == octolib.loadingMat then return end + local matShape = octolib.getImgurMaterial(data.url.shape) + if matShape == octolib.loadingMat then return end + + al = al * 255 + cam.Start3D2D(data.pos, data.ang, 0.1) + surface.SetDrawColor(159,159,159, al) + surface.SetMaterial(matBg) + surface.DrawTexturedRect(0, 0, data.w, data.h) + surface.SetDrawColor(0,0,0, math.min(al * 4, 255)) + surface.SetMaterial(matShape) + surface.DrawTexturedRect(0, 0, data.w, data.h) + surface.SetDrawColor(159,159,159, al) + surface.SetMaterial(matWindow) + surface.DrawTexturedRect(0, 0, data.w, data.h) + cam.End3D2D() + +end + +netstream.Hook('dbg-halloween.images', function(data) + renderData = halloween.parseData(data) + if renderData and renderData[1] then + hook.Add('PostDrawOpaqueRenderables', 'dbg-halloween.images', function() + for _, v in ipairs(renderData) do + drawImage(v) + end + end) + else hook.Remove('PostDrawOpaqueRenderables', 'dbg-halloween.images') end +end) diff --git a/garrysmod/addons/event-halloween/lua/halloween/cl_rewards.lua b/garrysmod/addons/event-halloween/lua/halloween/cl_rewards.lua new file mode 100644 index 0000000..95ed738 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/cl_rewards.lua @@ -0,0 +1,383 @@ +local cols = CFG.skinColors +local function paintItem(self, w, h) + local strokeCol = cols.bg_d + + draw.RoundedBox(4, 0, 0, w, h, strokeCol) + local bgCol = Color(cols.bg60) + if self.darker then + bgCol.r, bgCol.g, bgCol.b = bgCol.r-17, bgCol.g-17, bgCol.b-17 + end + if self.Depressed then + bgCol.r, bgCol.g, bgCol.b = bgCol.r-17, bgCol.g-17, bgCol.b-17 + end + draw.RoundedBox(4, 1, 1, w-2, h-2, bgCol) + + local btm = self.price and 51 or 31 + draw.RoundedBox(0, 1, h-btm, w-2, 1, strokeCol) + + if self.icon then + surface.SetDrawColor(255,255,255) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(w / 2 - 45, 16, 90, 90) + end + + if self.price then + draw.DrawText(DarkRP.formatMoney(self.price, ' конфет'), 'dbg-score.small', w-8, h-24, Color(235,235,235), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + surface.SetFont('dbg-score.normal') + local tw = surface.GetTextSize(self.name) + local x = 0 + if tw > w-16 and self.Hovered then + x = (-math.cos((RealTime() - self.animStart) * 1.5) + 1) / 2 * (w-16 - tw) + end + draw.DrawText(self.name, 'dbg-score.normal', x + 8, h - btm + 3, Color(255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.RoundedBox(0, w-1, h - btm + 1, 1, 45, strokeCol) + draw.RoundedBox(0, 0, h - btm + 1, 1, 45, strokeCol) + draw.RoundedBox(0, w-2, h - btm + 1, 1, 45, bgCol) + draw.RoundedBox(0, 1, h - btm + 1, 1, 45, bgCol) +end + +halloween.loadingMat = halloween.loadingMat or Material(octolib.icons.color('jackolantern')) +local function lock() + halloween.rewardsLocked = true + local pan = halloween.rewards + if IsValid(pan) then + pan.wrap:SetMouseInputEnabled(false) + pan.PaintOver = function(_, w, h) + draw.RoundedBoxEx(4, 0, 24, w, h-24, Color(0,0,0,200), false, false, true, true) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(halloween.loadingMat) + surface.DrawTexturedRectRotated(w / 2 - 15, h / 2, 64, 64, RealTime() * 100 % 360) + end + end +end + +local function startAnimation(self) + self.animStart = RealTime() +end + +local function caseClick(self) + local case = self.data + + local o, o2 = octolib.overlay(nil, 'DPanel') + o:SetSize(400, 392) + o2:MakePopup() + + local name = o:Add('DLabel') + name:Dock(TOP) + name:DockMargin(3, 3, 3, 3) + name:SetContentAlignment(5) + name:SetTall(30) + name:SetText(case.name) + name:SetFont('dbg-score.large') + + local descCont = o:Add('DPanel') + descCont:Dock(TOP) + descCont:SetTall(100) + descCont:SetPaintBackground(false) + descCont:DockMargin(5, 0, 5, 0) + + local icon = descCont:Add('DPanel') + icon:Dock(LEFT) + icon:SetWide(100) + icon.mat = Material(case.icon) + function icon:Paint(w, h) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(self.mat) + local sz = math.min(w, h) + surface.DrawTexturedRect((w-sz) / 2, (h-sz) / 2, sz, sz) + end + + local desc = descCont:Add('DLabel') + desc:Dock(FILL) + desc:DockMargin(5, 0, 0, 0) + desc:SetText(case.desc or '') + desc:SetFont('dbg-score.small') + desc:SetMultiline(true) + desc:SetWrap(true) + + local itemsCont = o:Add('DPanel') + itemsCont:Dock(TOP) + itemsCont:SetTall(192) + itemsCont:SetPaintBackground(false) + itemsCont:DockMargin(5, 0, 5, 0) + + local title = itemsCont:Add('DLabel') + title:Dock(TOP) + title:SetFont('f4.normal') + title:SetText('Что я могу положить внутрь:') + title:SetTall(30) + + local items = itemsCont:Add('DHorizontalScroller') + items:Dock(FILL) + items:SetOverlap(-5) + + for _, v in SortedPairsByMemberValue(case.items, 1, true) do + local caseItem = halloween.caseItems[v[2]] + if caseItem then + local item = items:Add('DPanel') + item:SetWide(116) + items:AddPanel(item) + item.Paint = paintItem + item.name = caseItem.name + item.icon = Material(caseItem.icon) + item.OnCursorEntered = startAnimation + end + end + + if case.max <= 0 then + local lbl = octolib.label(o, 'Извини, приятель, не могу продать тебе эту вещь') + lbl:Dock(BOTTOM) + lbl:DockMargin(5, 5, 5, 5) + lbl:SetContentAlignment(5) + return + end + + local amount + local orderButton = octolib.button(o, 'Получить', function() + local count = math.Round(amount:GetValue()) + Derma_Query(('Ты точно хочешь получить %s x %s за %s?'):format(count, case.name, DarkRP.formatMoney(count * case.price, ' конфет')), 'Подтверждение', 'Да', function() + netstream.Start('dbg-halloween.claim', halloween.rewards.ent, 'case:' .. case.id, count) + lock() + o:Remove() + end, 'Нет') + end) + orderButton:Dock(BOTTOM) + + amount = octolib.slider(o, 'Количество', 1, case.max, 0) + amount:SetValue(1) + amount:Dock(BOTTOM) + amount:DockMargin(10, 0, 0, 0) +end + +local function itemClick(self) + local item = self.data + + local o, o2 = octolib.overlay(nil, 'DPanel') + o:SetSize(400, 190) + o2:MakePopup() + + local name = o:Add('DLabel') + name:Dock(TOP) + name:DockMargin(3, 3, 3, 3) + name:SetContentAlignment(5) + name:SetTall(30) + name:SetText(item.name) + name:SetFont('dbg-score.large') + + local descCont = o:Add('DPanel') + descCont:Dock(TOP) + descCont:SetTall(100) + descCont:SetPaintBackground(false) + descCont:DockMargin(5, 0, 5, 0) + + local icon = descCont:Add('DPanel') + icon:Dock(LEFT) + icon:SetWide(100) + icon.mat = Material(item.icon) + function icon:Paint(w, h) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(self.mat) + local sz = math.min(w, h) + surface.DrawTexturedRect((w-sz) / 2, (h-sz) / 2, sz, sz) + end + + local desc = descCont:Add('DLabel') + desc:Dock(FILL) + desc:DockMargin(5, 0, 0, 0) + desc:SetText(item.desc or '') + desc:SetFont('dbg-score.small') + desc:SetMultiline(true) + desc:SetWrap(true) + + if item.max <= 0 then + local lbl = octolib.label(o, 'Извини, приятель, не могу продать тебе эту вещь') + lbl:Dock(BOTTOM) + lbl:DockMargin(5, 5, 5, 5) + lbl:SetContentAlignment(5) + return + end + + local amount + local orderButton = octolib.button(o, 'Получить', function() + local count = math.Round(amount:GetValue()) + Derma_Query(('Ты точно хочешь получить %s x %s за %s?'):format(count, item.name, DarkRP.formatMoney(count * item.price, ' конфет')), 'Подтверждение', 'Да', function() + netstream.Start('dbg-halloween.claim', halloween.rewards.ent, item.id, count) + lock() + o:Remove() + end, 'Нет') + end) + orderButton:Dock(BOTTOM) + + amount = octolib.slider(o, 'Количество', 1, item.max, 0) + amount:SetValue(1) + amount:Dock(BOTTOM) + amount:DockMargin(10, 0, 0, 0) + +end + +local function sizeToText(self) + self:SizeToContentsY(10) +end +local function sortByPriceAndName(a, b) + if a.price ~= b.price then return a.price < b.price end + return a.name < b.name +end + +function halloween.openRewards(ent, data, unlock) + if unlock then + halloween.rewardsLocked = nil + end + halloween.caseItems = data.caseItems + local x, y + if IsValid(halloween.rewards) then + x, y = halloween.rewards:GetPos() + halloween.rewards:Remove() + end + + local fr = vgui.Create 'DFrame' + fr:SetTitle('Джек и его лавка') + fr:SetSize(400, 555) + if x then + fr:SetPos(x, y) + else fr:Center() end + fr:MakePopup() + fr:SetSizable(true) + fr:SetMinWidth(400) + fr:SetMinHeight(295) + function fr:OnSizeChanged(w) + if w ~= 400 then + self:SetWidth(400) + end + end + fr.ent = ent + halloween.rewards = fr + + octolib.changeSkinColor(Color(52, 49, 52), Color(222, 132, 38), 0) + + local wrap = fr:Add('DScrollPanel') + wrap:Dock(FILL) + fr.wrap = wrap + if halloween.rewardsLocked then + lock() + end + + local lbl = octolib.label(wrap, 'Приве-е-ет! Ты пришел получить от меня подарки за конфеты? Смотри, у меня есть такие коробочки:') + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetFont('dbg-score.normal') + lbl.PerformLayout = sizeToText + + local cases = wrap:Add('DIconLayout') + cases:Dock(TOP) + cases:SetSpaceX(10) + cases:SetSpaceY(10) + + table.sort(data.cases, sortByPriceAndName) + for _, v in ipairs(data.cases) do + + local case = cases:Add('DButton') + case:SetSize(116, 170) + + case.icon = Material(v.icon) + case.name = v.name + case.price = v.price + case.darker = v.max <= 0 + case.data = v + case:SetText('') + + case.Paint = paintItem + case.OnCursorEntered = startAnimation + case.DoClick = caseClick + + end + + local lbl = octolib.label(wrap, 'Если боишься испытывать удачу и хочешь купить что-то более конкретное, можешь приобрести что-нибудь отсюда:') + lbl:DockMargin(0, 5, 0, 0) + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetFont('dbg-score.normal') + lbl.PerformLayout = sizeToText + + local items = wrap:Add('DIconLayout') + items:Dock(TOP) + items:SetSpaceX(10) + items:SetSpaceY(10) + + table.sort(data.items, sortByPriceAndName) + for _, v in ipairs(data.items) do + + local item = items:Add('DButton') + item:SetSize(116, 170) + + item.icon = Material(v.icon) + item.name = v.name + item.price = v.price + item.darker = v.max <= 0 + item.data = v + item:SetText('') + + item.Paint = paintItem + item.OnCursorEntered = startAnimation + item.DoClick = itemClick + + end + + local lbl = octolib.label(wrap, 'Ну а если больше ничего покупать у меня не хочешь, давай мне все свои конфеты, я тебе за них отсыплю денег, и больше мы с тобой не торгуемся') + lbl:DockMargin(0, 5, 0, 0) + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetFont('dbg-score.normal') + lbl.PerformLayout = sizeToText + + local balance = LocalPlayer():GetNetVar('sweets', 0) + local fancy = octolib.string.separateDigits(balance) + local btn = octolib.button(wrap, ('Обменять %s %s на %s'):format(fancy, octolib.string.formatCount(balance, 'конфету', 'конфеты', 'конфет'), DarkRP.formatMoney(balance * 65)), function() + Derma_Query('Ты мне — ВСЕ свои конфеты, я тебе — деньги, и больше я тебе НИЧЕГО НЕ ПРОДАМ. Идет?', 'Конфеты на деньги', 'Да', function() + netstream.Start('dbg-halloween.flushRewards', ent) + lock() + end, 'Нет') + end) + btn:SetFont('dbg-score.small') + btn:DockMargin(0, 0, 0, 5) + + local balWrap = fr:Add('DPanel') + balWrap:Dock(BOTTOM) + balWrap:DockMargin(0, 5, 0, 0) + balWrap:SetTall(27) + local bal = balWrap:Add('DLabel') + bal:Dock(FILL) + bal:SetContentAlignment(5) + bal:SetFont('f4.normal') + bal:SetText('Конфет: ' .. fancy) + + function fr:OnClose() + octolib.changeSkinColor(Color(85,68,85), Color(102,170,170), 0) + end +end + +netstream.Hook('dbg-halloween.openRewards', halloween.openRewards) + +netstream.Hook('dbg-halloween.closeRewards', function() + halloween.rewardsLocked = nil + local pan = halloween.rewards + if IsValid(pan) then pan:Remove() end +end) + +-- JACK +local function findJack() + for _, v in ipairs(ents.FindByClass('base_ai')) do + if v:GetNetVar('Jack') then + timer.Remove('dbg-halloween.findJack') + timer.Create('dbg-halloween.updateJack', 5, 0, function() + if IsValid(v) then v:ResetSequence('idle_all_01') + else + timer.Create('dbg-halloween.findJack', 1, 0, findJack) + timer.Remove('dbg-halloween.updateJack') + end + end) + end + end +end +timer.Create('dbg-halloween.findJack', 1, 0, findJack) diff --git a/garrysmod/addons/event-halloween/lua/halloween/cl_textures.lua b/garrysmod/addons/event-halloween/lua/halloween/cl_textures.lua new file mode 100644 index 0000000..5d0353a --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/cl_textures.lua @@ -0,0 +1,51 @@ +do return end + +local maps = { + rp_eastcoast_v4c = { + ['de_tides/tides_grass_a'] = {'de_tides/tides_grass_a_fall'}, + ['decals/ivy01'] = {'decals/ivy01_fall'}, + ['nature/dirtfloor006a'] = {'nature/dirtfloor006a_fall'}, + ['nature/blendgrassgravel001c'] = {'nature/dirtfloor006a_fall', true}, + ['models/props_foliage/tree_springers_01a_lod'] = {'models/props_foliage/tree_springers_01a_lod-leaves'}, + ['models/props_foliage/tree_springers_01a'] = {'models/props_foliage/tree_springers_01a_leaves'}, + }, + rp_evocity_dbg_210308 = { + ['nature/blendgrassgravel02'] = {'nature/forest_grass_01_fall'}, + ['nature/forest_grass_01'] = {'nature/forest_grass_01_fall'}, + ['maps/rp_evocity_dbg_210308/nature/blendgrassgravel02_wvt_patch'] = {'nature/forest_grass_01_fall'}, + ['nature/blendgrassgravel_noprop_02'] = {'nature/forest_dirt_04_fall'}, + ['nature/blendgrassgravel_noprop_01'] = {'nature/forest_grass_01_fall'}, + ['nature/blendgrassdirt01_noprop'] = {'nature/forest_grass_01_fall'}, + ['models/props_foliage/arbre01'] = {'models/props_foliage/arbre01_fall'}, + ['models/props_park/pine_branches'] = {'models/props_park/pine_branches_fall'}, + ['models/props_foliage/mall_trees_branches01'] = {'models/props_foliage/mall_trees_branches01_fall'}, + ['models/msc/e_bigbush'] = {'models/msc/e_bigbush_fall'}, + ['models/msc/e_bigbush3'] = {'models/msc/e_bigbush3_fall'}, + ['nature/blendgrassdirt02_noprop'] = {'nature/forest_grass_01_fall', true}, + ['nature/blendgrassdirt02_noprop'] = {'nature/forest_dirt_04_fall'}, + ['nature/cliff03b'] = {'nature/cliff03b_fall'}, + ['nature/clifftrees_cardboard02a'] = {'nature/clifftrees_cardboard02a_fall'}, + ['maps/rp_evocity_dbg_210308/nature/blendgrassdirt01_noprop_wvt_patch'] = {'nature/forest_grass_01_fall'}, + ['models/fork/tree_pine04_lowdetail_cluster_card'] = {'fork/tree_pine04_card_fall'}, + ['de_cbble/grassdirt_blend'] = {'de_cbble/grassfloor01_fall'}, + ['de_cbble/grassfloor01'] = {'de_cbble/grassfloor01_fall'}, + ['maps/rp_evocity_dbg_210308/de_cbble/grassdirt_blend_wvt_patch'] = {'de_cbble/grassfloor01_fall'}, + }, +} + +hook.Add('PlayerFinishedLoading', 'halloween', function() + + local toReplace = maps[game.GetMap()] + if toReplace then + for matPath, data in pairs(toReplace) do + local tex, second = unpack(data) + local mat = Material(matPath) + if second then + mat:SetTexture('$basetexture2', tex) + else + mat:SetTexture('$basetexture', tex) + end + end + end + +end) diff --git a/garrysmod/addons/event-halloween/lua/halloween/sv_images.lua b/garrysmod/addons/event-halloween/lua/halloween/sv_images.lua new file mode 100644 index 0000000..c330adc --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/sv_images.lua @@ -0,0 +1,88 @@ +local reward = 1000 -- sweets + +octolib.notify.registerType('addSweets', { + function() + return 'Получить', function(ply, data) + if not ply.halloweenTheme then + return ply:Notify('warning', 'А как же хэллоуинское настроение? Активируй его во вкладке "Хэллоуин" в F4-меню!') + end + ply:AddSweets(data.v) + return true + end + end, +}) + +local windowsCount = { + 12, -- Йорк 3 + 10, -- Йорк 9 + 9, -- Йорк 13 + 11, -- Ирвин 1 + 6, -- Ирвин 3 + 8, -- Ирвин 4 + 7, -- Ирвин 8 + 6, -- Ирвин 9 + 8, -- Ирвин 10 + 5, -- Ирвин 13 + 10, -- Рурк 1 + 28, -- Рурк 8 + 11, -- Джойс 3 + 12, -- Джойс 5 + 6, -- Джойс 6 + 8, -- Джойс 8 + 11, -- Джойс 10 + 6, -- Джойс 13 + 9, -- Джойс 15 + 9, -- Стефан 2 +} +local images = {1, 2, 3, 4, 5, 6, 7, 8} -- for definitions, see cl_config + +local function randomImages() + + -- generate images + octolib.array.shuffle(images) + + -- generate positions + local response = {} + for i, v in RandomPairs(windowsCount) do + response[#response + 1] = {i, math.random(v), images[#response + 1]} -- house id, window id, image id + if #response == 4 then break end + end + + return response +end + +local function generatePlayerImages(sid64, callback) + local images = randomImages() + octolib.db:PrepareQuery([[INSERT INTO ]] .. CFG.db.main .. [[.halloween_images (steamid64, data) VALUES (?, ?)]], {sid64, util.TableToJSON(images):replace('.0', '')}, function() + callback(images) + end) +end + +function halloween.getPlayerImages(ply, callback) + local sid64 = ply:SteamID64() + octolib.db:PrepareQuery([[SELECT data FROM ]] .. CFG.db.main .. [[.halloween_images WHERE steamid64=?]], {sid64}, function(q, st, res) + if not istable(res) or not res[1] then + generatePlayerImages(sid64, callback) + else callback(util.JSONToTable(res[1].data)) end + end) +end + +hook.Add('dbg-char.firstSpawn', 'dbg-halloween.images', function(ply) + if game.GetMap() ~= 'rp_eastcoast_v4c' then return end + halloween.getPlayerImages(ply, function(imgs) + if IsValid(ply) then netstream.Start(ply, 'dbg-halloween.images', imgs) end + end) +end) + +hook.Add('octolib.event:halloween.done', 'dbg-halloween.images', function(data) + octolib.notify.send(util.SteamIDFrom64(data.user), 'addSweets', 'Тебе прислали конфеты. Забери их в F4', { + text = ('Тебе прислали %s %s'):format(reward, octolib.string.formatCount(reward, 'конфету', 'конфеты', 'конфет')), + v = reward, + }) +end) + +hook.Add('PlayerCanOOC', 'dbg-halloween.images', function(_, txt) + if txt:find('spooky.wani4ka.ru', 1, true) then + return false, 'Пожалуйста, не распространяй эту ссылку в общих чатах. Лучше отправь ее в личные сообщения тому, кто ее попросил, или в локальный OOC-чат своим друзьям' + end +end) diff --git a/garrysmod/addons/event-halloween/lua/halloween/sv_rewards.lua b/garrysmod/addons/event-halloween/lua/halloween/sv_rewards.lua new file mode 100644 index 0000000..0a86f48 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/sv_rewards.lua @@ -0,0 +1,282 @@ +halloween.items = halloween.items or {} +halloween.caseItems = halloween.caseItems or {} +halloween.cases = halloween.cases or {} + +function halloween.registerItem(id, data) + if istable(data) or data == nil then + halloween.items[id] = data + end +end + +function halloween.registerCaseItem(id, data) + if data == nil then + halloween.caseItems[id] = nil + return + elseif not istable(data) then return end + data.maxMass, data.maxVolume = data.maxMass or 0, data.maxVolume or 0 + halloween.caseItems[id] = data +end + +function halloween.registerCase(id, data) + if data == nil then + halloween.cases[id] = nil + return + elseif not istable(data) then return end + data.mass = octolib.array.reduce(data.items, function(a, v) return math.max(a, halloween.caseItems[v[2]].maxMass) end, 0) + data.volume = octolib.array.reduce(data.items, function(a, v) return math.max(a, halloween.caseItems[v[2]].maxVolume) end, 0) + octolib.array.shuffle(data.items) + halloween.cases[id] = data +end + +function halloween.getClaimedAmount(ply, id) + return ply:GetDBVar('hrewards', {})[id] or 0 +end + +function halloween.getPriceModifier(ply) + if ply:GetUserGroup() ~= 'user' then return 0.8 end -- 20% discount for staff + return 1 +end + +function halloween.sendItems(ply, toSend) + if #ents.FindByClass('octoinv_mailbox') < 1 then + ply:Notify('warning', 'На карте нет почтовых ящиков! Обратись к администрации') + return false + end + + local itemsPretty = {} + for _, item in ipairs(toSend) do + table.insert(itemsPretty, octoinv.itemStr(item)) + end + hook.Run('octoinv.shop.order', ply, ply, itemsPretty, 0, 'halloween') + + timer.Simple(math.random(60, 120), function() + local box = octoinv.sendToMailbox(ply, toSend) + ply:AddMarker({ + txt = 'Хэллоуинская доставка', + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'timedist', { 600, 100 }}, + icon = 'octoteam/icons-16/lorry.png', + }) + ply:Notify('Твой хэллоуинский заказ доставлен в почтовый ящик!') + end) + return true + +end + +function halloween.claimCase(ply, id, amount, func) + + local case = halloween.cases[id] + if not case then return func(false) end + amount = math.min(math.Round(amount), 100, (case.max or math.huge) - halloween.getClaimedAmount(ply, id)) + if amount <= 0 then return func(false) end + local cost = case.price * amount * halloween.getPriceModifier(ply) + if ply:GetNetVar('sweets', 0) < cost then + ply:Notify('warning', 'У тебя недостаточно конфет!') + return func(false) + end + + local item = octolib.array.series({'h20_case', { cid = id, mass = case.mass, volume = case.volume, expire = os.time() + 86400 }}, amount) + local sent, msg = halloween.sendItems(ply, item) + + if sent then + ply:Notify('hint', 'Загадочная коробка отправлена по почте и придет через пару минут') + ply:SetDBVar('sweets', ply:GetDBVar('sweets', 0) - cost) + ply:SetNetVar('sweets', ply:GetDBVar('sweets')) + else ply:Notify('warning', msg or 'Не удалось отправить загадочную коробку') end + + func(sent) + +end + +function halloween.claim(ply, id, amount, func) + + func = func or octolib.func.zero + if not (isstring(id) and isnumber(amount)) then return func(false) end + + if string.StartWith(id, 'case:') then + return halloween.claimCase(ply, id:sub(6), amount, func) + end + + local data = halloween.items[id] + if not data then return func(false) end + amount = math.min(math.Round(amount), 100, (data.max or math.huge) - halloween.getClaimedAmount(ply, id)) + if amount <= 0 then return func(false) end + + local cost = data.price * amount * halloween.getPriceModifier(ply) + if ply:GetNetVar('sweets', 0) < cost then + ply:Notify('warning', 'У тебя недостаточно конфет!') + return func(false) + end + + local sid = ply:SteamID() + local function reward(succ) + if not succ then return func(false) end + octolib.getDBVar(sid, 'hrewards', {}):Then(function(items) -- get items data + items[id] = (items[id] or 0) + 1 + return octolib.setDBVar(sid, 'hrewards', items) -- update items data + end):Then(function() + return octolib.getDBVar(sid, 'sweets', 0) -- get sweets amount + end):Then(function(sweets) + if IsValid(ply) then ply:SetNetVar('sweets', sweets - cost) end + return octolib.setDBVar(sid, 'sweets', sweets - cost) -- update sweets amount + end):Then(function() + func(succ) -- callback + end) + end + + if data.deliver then + return reward(data.deliver(ply, amount)) + end + + if data.deliverAsync then + return data.deliverAsync(ply, amount, reward) + end + + func(false) -- ??? + +end + +function halloween.collectData(ply) + local items = {} + local claimed = ply:GetDBVar('hrewards', {}) + local sweets = ply:GetDBVar('sweets', 0) + local priceMod = halloween.getPriceModifier(ply) + for k, v in pairs(halloween.items) do + local amount = math.min(100, (v.max or math.huge) - (claimed[k] or 0), math.floor(sweets / (v.price * priceMod))) + items[#items + 1] = { + id = k, + name = v.name, + icon = v.icon, + desc = v.desc, + max = amount, + mdl = v.mdl, + skin = v.skin, + price = v.price * priceMod, + } + end + local caseItems = octolib.table.map(halloween.caseItems, function(v, k) return { id = k, name = v.name, icon = v.icon } end) + local cases = {} + for k, v in pairs(halloween.cases) do + local amount = math.min(100, (v.max or math.huge) - (claimed[k] or 0), math.floor(sweets / (v.price * priceMod))) + cases[#cases + 1] = { + id = k, + name = v.name, + icon = v.icon, + desc = v.desc, + max = amount, + price = v.price * priceMod, + items = v.items, + } + end + return { items = items, caseItems = caseItems, cases = cases } +end + +netstream.Hook('dbg-halloween.claim', function(ply, ent, id, amount) + if not IsValid(ent) or not ent:GetNetVar('Jack') then + return netstream.Start(ply, 'dbg-halloween.closeRewards') + end + local head = ply:GetShootPos() + if ent:NearestPoint(head):DistToSqr(head) > CFG.useDistSqr then + return netstream.Start(ply, 'dbg-halloween.closeRewards') + end + + local responded = false + local function response() + if responded then return end + responded = true + if not IsValid(ply) then return end + if ply:GetNetVar('sweets', 0) <= 0 then + octochat.talkTo(ply, octochat.textColors.rp, 'Джек говорит: ', color_white, 'Здорово поторговались! Спасибо, приятель. Хорошего дня тебе') + netstream.Start(ply, 'dbg-halloween.closeRewards') + ply:SetNetVar('sweets') + ply:SetDBVar('sweets') + else + netstream.Start(ply, 'dbg-halloween.openRewards', ent, halloween.collectData(ply), true) + end + end + halloween.claim(ply, id, amount, response) + timer.Simple(10, response) +end) + +netstream.Hook('dbg-halloween.flushRewards', function(ply, ent) + if not IsValid(ent) or not ent:GetNetVar('Jack') then + return netstream.Start(ply, 'dbg-halloween.closeRewards') + end + local head = ply:GetShootPos() + if ent:NearestPoint(head):DistToSqr(head) > CFG.useDistSqr then + return netstream.Start(ply, 'dbg-halloween.closeRewards') + end + + local sweets = ply:GetDBVar('sweets') + if not sweets then + ply:SetNetVar('sweets') + return netstream.Start(ply, 'dbg-halloween.closeRewards') + end + local money = sweets * 65 + ply:BankAdd(money) + ply:SetDBVar('sweets') + ply:SetNetVar('sweets') + + octochat.talkTo(ply, octochat.textColors.rp, 'Джек говорит: ', color_white, 'Перевел ' .. DarkRP.formatMoney(money) .. ' тебе на банковский счет') + netstream.Start(ply, 'dbg-halloween.closeRewards') +end) + +-- JACK +local function jackInteraction(self, name, _, ply) + + if not (name == 'Use' and IsValid(ply) and ply:IsPlayer()) then return end + local ct = CurTime() + if (ply.nextRewardsUse or 0) > ct then return end + + if ply:GetNetVar('sweets', 0) <= 0 then + ply.nextRewardsUse = ct + 60 + return octochat.talkTo(ply, octochat.textColors.rp, 'Джек говорит: ', color_white, 'Нет конфет? Очень жаль. Надеюсь, увидимся на следующий Хэллоуин!') + end + ply.nextRewardsUse = ct + 2 + + netstream.Start(ply, 'dbg-halloween.openRewards', self, halloween.collectData(ply)) + local mod = 1 - halloween.getPriceModifier(ply) + if mod > 0 then + octochat.talkTo(ply, octochat.textColors.rp, 'Джек говорит: ', color_white, ('О, мне про тебя рассказывали! Сделаю-ка я тебе скидку в %s%%'):format(mod * 100)) + end + +end + +local jackSpawns = { + rp_eastcoast_v4c = { Vector(-1931.4, 1649.8, 1.5), Angle(0, 90, 0) }, + rp_evocity_dbg_210308 = { Vector(-7851.3, -10327, 72), Angle(0, 90, 0) }, +} +local function respawnJack() + + for _, v in ipairs(ents.FindByClass('base_ai')) do + if v:GetNetVar('Jack') then v:Remove() end + end + + local data = jackSpawns[game.GetMap()] + if not data then return end + + local jack = ents.Create('base_ai') + jack:SetPos(data[1]) + jack:SetAngles(data[2]) + jack:SetModel('models/humans/octo/male_09_04.mdl') + jack:SetSkin(16) + + jack:SetHullType(HULL_HUMAN) + jack:SetHullSizeNormal() + jack:SetNPCState(NPC_STATE_SCRIPT) + jack:SetSolid(SOLID_BBOX) + jack:CapabilitiesAdd(bit.bor(CAP_ANIMATEDFACE, CAP_TURN_HEAD)) + jack:SetUseType(SIMPLE_USE) + + jack:Spawn() + jack:ResetSequence('idle_all_01') + jack:SetNetVar('Jack', true) + jack.AcceptInput = jackInteraction + +end +hook.Add('InitPostEntity', 'dbg-halloween.spawnJack', respawnJack) +concommand.Add('jack_reload', function(ply) + if IsValid(ply) and not ply:IsAdmin() then return end + respawnJack() +end) diff --git a/garrysmod/addons/event-halloween/lua/halloween/sv_sweets.lua b/garrysmod/addons/event-halloween/lua/halloween/sv_sweets.lua new file mode 100644 index 0000000..269aa15 --- /dev/null +++ b/garrysmod/addons/event-halloween/lua/halloween/sv_sweets.lua @@ -0,0 +1,58 @@ +local plyMeta = FindMetaTable 'Player' + +function plyMeta:AddSweets(sweets, msg) + self:SetDBVar('sweets', self:GetDBVar('sweets', 0) + sweets) + self:SetNetVar('sweets', self:GetDBVar('sweets')) + if sweets > 0 then + msg = msg or 'Ты получил %s!' + self:Notify('hint', msg:format(sweets .. ' ' .. octolib.string.formatCount(sweets, 'конфету', 'конфеты', 'конфет'))) + end +end + +hook.Add('dbg-char.firstSpawn', 'dbg-halloween.sweets', function(ply) + ply:SetNetVar('sweets', ply:GetDBVar('sweets')) + if ply:GetDBVar('halloweenTheme') then -- !!!!!!!!!! + ply:SetNetVar('halloweenTheme', true) + ply:ConCommand('octogui_reloadf4') + end +end) + +netstream.Hook('dbg-halloween.themeToggle', function(ply, status) + ply.halloweenTheme = tobool(status) +end) + + +do return end + +-- LOGS +hook.Add('dbg-halloween.gotSweets', 'octologs', function(ply, amount, ent) + local log = octologs.createLog() + :Add(octologs.ply(ply, {'hp', 'ar', 'job', 'wep'})) + :Add(' got ', octolib.string.separateDigits(amount), ' sweets') + if IsValid(ent) then + log = log:Add(' from ') + :Add(ent:IsPlayer() and octologs.ply(ent, {'hp', 'ar', 'loc', 'job', 'wep'}) or octologs.ent(ent, {'mdl'})) + end + log:Save() +end) + +-- COMMAND +hook.Add('Think', 'dbg-halloween.sweetsCommand', function() +hook.Remove('Think', 'dbg-halloween.sweetsCommand') + +octochat.registerCommand('!sweets', { + aliases = {'~sweets'}, + execute = function(ply, _, args) + + local target, txt = octochat.pickOutTarget(args) + if not IsValid(target) then return txt or 'Формат: !sweets Игрок Количество' end + local amount = tonumber(txt) or 0 + target:AddSweets(amount or 0) + hook.Run('dbg-halloween.gotSweets', target, amount, ply) + ply:Notify('rp', target:Name(), ' получает ', tostring(amount), ' ', octolib.string.formatCount(amount, 'конфету', 'конфеты', 'конфет')) + + end, + check = DarkRP.isAdmin, +}) + +end) diff --git a/garrysmod/addons/feature-atm/lua/autorun/client/brax_atm.lua b/garrysmod/addons/feature-atm/lua/autorun/client/brax_atm.lua new file mode 100644 index 0000000..3f1d46d --- /dev/null +++ b/garrysmod/addons/feature-atm/lua/autorun/client/brax_atm.lua @@ -0,0 +1,16 @@ +BraxBank = BraxBank or {} + +function BraxBank.PlayerMoney(ply) + + return ply:GetNetVar('BankMoney', 0) + +end + +local ply = FindMetaTable 'Player' + +function ply:BankHas(val) + + if val < 0 then return false end + return BraxBank.PlayerMoney(self) >= val + +end diff --git a/garrysmod/addons/feature-atm/lua/autorun/server/brax_atm.lua b/garrysmod/addons/feature-atm/lua/autorun/server/brax_atm.lua new file mode 100644 index 0000000..8fa7629 --- /dev/null +++ b/garrysmod/addons/feature-atm/lua/autorun/server/brax_atm.lua @@ -0,0 +1,264 @@ +BraxBank = {} + +local startMoney = CreateConVar("braxnet_atm_startmoney", 0, {FCVAR_ARCHIVE, FCVAR_PROTECTED, FCVAR_SERVER_CAN_EXECUTE}, "User start money") +local salary = CreateConVar("braxnet_atm_salary", 0, {FCVAR_ARCHIVE, FCVAR_PROTECTED, FCVAR_SERVER_CAN_EXECUTE}, "Payday goes straight into bank account.") + +-- Helper functions + +local function notifyAboutBalanceUpdate(ply, delta) + if not ply:HasPhone() then return end + local msg + if delta < 0 then + msg = ('С твоего банковского счета списано %s.'):format(DarkRP.formatMoney(-delta)) + else + msg = ('На твой банковский счет зачислено %s.'):format(DarkRP.formatMoney(delta)) + end + msg = msg .. (' Баланс: %s'):format(DarkRP.formatMoney(BraxBank.PlayerMoney(ply))) + ply:SendSMS(octochat.textColors.rp, 'Банк', L.owner_sms, Color(250,250,200), msg) +end + +local function databaseInit() + + MySQLite.query([[ + CREATE TABLE IF NOT EXISTS dbg_atm ( + steamID VARCHAR(50) NOT NULL PRIMARY KEY, + money INT(15) + ) + ]]) + +end +hook.Add("DarkRPDBInitialized", "dbg-atm", databaseInit) + +function BraxBank.StartMoney() + return startMoney:GetInt() +end + +function BraxBank.InitPlayer(ply) + + MySQLite.query(string.format([[SELECT * FROM dbg_atm WHERE steamID = %s]], MySQLite.SQLStr(ply:SteamID())), function(res) + + res = res and res[1] + if res then + ply:SetLocalVar('BankMoney', res.money) + else + local startMoney = BraxBank.StartMoney() + ply:SetLocalVar('BankMoney', startMoney) + + MySQLite.query(string.format( + [[INSERT INTO dbg_atm (steamID, money) + VALUES (%s, %d)]], + MySQLite.SQLStr(ply:SteamID()), + startMoney + )) + + print('[DBG-ATM] Adding bank for ' .. tostring(ply)) + end + + end) + +end + +function BraxBank.SavePlayer(ply) + + MySQLite.query(string.format([[UPDATE dbg_atm SET money = %d WHERE steamID = %s]], + BraxBank.PlayerMoney(ply), + MySQLite.SQLStr(ply:SteamID()) + )) + +end + +function BraxBank.PlayerMoney(ply) + + return ply:GetNetVar('BankMoney', 0) + +end + +function BraxBank.PlayerMoneyAsync(ply, handler) + + if not isfunction(handler) then return end + + if isstring(ply) then + local inst = player.GetBySteamID(ply) + if IsValid(inst) then + handler(BraxBank.PlayerMoney(inst)) + return + end + + MySQLite.query(string.format([[SELECT money FROM dbg_atm WHERE steamID = %s]], MySQLite.SQLStr(ply)), function(res) + res = res and res[1] + handler(res and res.money or GetConVarNumber('braxnet_atm_startmoney')) + end) + else + handler(BraxBank.PlayerMoney(ply)) + end + +end + +function BraxBank.UpdateMoney(ply, amount, doNotNotify) + + if isstring(ply) and not IsValid(player.GetBySteamID(ply)) then + MySQLite.query(string.format([[UPDATE dbg_atm SET money = %d WHERE steamID = %s]], + amount, MySQLite.SQLStr(ply) + )) + else + if isstring(ply) then ply = player.GetBySteamID(ply) end + if not IsValid(ply) then return end + + local delta = amount - ply:GetLocalVar('BankMoney', 0) + ply:SetLocalVar('BankMoney', amount) + BraxBank.SavePlayer(ply) + if not doNotNotify then notifyAboutBalanceUpdate(ply, delta) end + end + +end + +function BraxBank.TakeAction(ply) + + ply:addExploitAttempt() + +end + +hook.Add("PlayerFinishedLoading", "dbg-atm", BraxBank.InitPlayer) +-- hook.Add("PlayerDisconnected", "dbg-atm", BraxBank.SavePlayer) + +local startmoney = 300 +hook.Add('dbg-char.spawn', 'dbg-atm', function(ply) + + if hook.Run('octoinv.overrideInventories') == false then return end + + local money = BraxBank.PlayerMoney(ply) + if not ply:IsGhost() and ply.inv and not ply:canAfford(startmoney) and money >= startmoney then + BraxBank.UpdateMoney(ply, money - startmoney) + ply:addMoney(startmoney) + ply:Notify('ooc', L.brax_atm_hint, DarkRP.formatMoney(startmoney), L.from_bank) + end + +end) + +--[[ + Return codes!! + 1 = NULL + 2 = Deposit, bank does not have money + 3 = Deposit, ok + 4 = Insert, User does not have enough money + 5 = Insert, ok +]]-- + +util.AddNetworkString( "BraxAtmWithdraw" ) +net.Receive( "BraxAtmWithdraw", function( length, client ) + + local WithdrawValue = net.ReadInt(32) + local UserMoney = BraxBank.PlayerMoney(client) + + local atm + for _,v in pairs(ents.FindByClass("brax_atm")) do + if IsValid(v) and v:GetClass() == "brax_atm" and v:GetPos():Distance(client:GetShootPos()) < 256 then atm = v end + end + if atmcheck == false then BraxBank.TakeAction(client) return end + if WithdrawValue <= 0 then BraxBank.TakeAction(client) return end + + if not client:BankHas(WithdrawValue) then + BraxBankAtmReturnCode(atm, 2, client) + return + end + + local NewVal = UserMoney - WithdrawValue + if NewVal < 0 then BraxBank.TakeAction(client) return end + + BraxBank.UpdateMoney(client, NewVal) + client:addMoney(WithdrawValue) + hook.Run('atm.withdraw', client, WithdrawValue) + + BraxBankAtmReturnCode(atm, 3, client) + +end) + +-- INSERT MONEY +util.AddNetworkString( "BraxAtmDeposit" ) +net.Receive( "BraxAtmDeposit", function( length, client ) + + local DepositValue = net.ReadInt(32) + local UserMoney = BraxBank.PlayerMoney(client) + + -- do some simple cheat checks + local atm + for _,v in pairs(ents.FindByClass("brax_atm")) do + if IsValid(v) and v:GetClass() == "brax_atm" and v:GetPos():Distance(client:GetShootPos()) < 256 then atm = v end + end + if atmcheck == false then BraxBank.TakeAction(client) return end + if DepositValue <= 0 then BraxBank.TakeAction(client) return end + + if not client:canAfford(DepositValue) then + BraxBankAtmReturnCode(atm, 4, client) + return + end + + local NewVal = UserMoney + DepositValue + if NewVal < 0 then BraxBank.TakeAction(client) return end + + BraxBank.UpdateMoney(client, NewVal) + client:addMoney(-DepositValue) + hook.Run('atm.deposit', client, DepositValue) + BraxBankAtmReturnCode(atm, 5, client) + +end) + +function BraxBankAtmUpdate(client) + + local m = BraxBank.PlayerMoney(client) + net.Start( "BraxAtmFetch" ) + net.WriteInt(m, 32) + net.Send(client) + +end + +util.AddNetworkString( "BraxAtmReturnCode" ) +function BraxBankAtmReturnCode(ent, code, client) + + net.Start( "BraxAtmReturnCode" ) + net.WriteEntity(ent) + net.WriteInt(code, 32) + net.Send(client) + +end + +concommand.Add("brax_atm_update", function(p, c, a) + + BraxBankAtmUpdate(p) + +end) + +util.AddNetworkString( "BraxAtmFetch" ) + +hook.Add("playerGetSalary","BraxAtmSalary", function(ply, amount) + if salary:GetInt() > 0 then + local money = BraxBank.PlayerMoney(ply) + BraxBank.UpdateMoney(ply, money+amount) + return false, L.brax_atm_salary .. DarkRP.formatMoney(amount), 0 + end +end) + +-- +-- META +-- + +local ply = FindMetaTable 'Player' + +function ply:BankAdd(val, doNotNotify) + + BraxBank.UpdateMoney(self, BraxBank.PlayerMoney(self) + val, true) + if not doNotNotify then notifyAboutBalanceUpdate(self, val) end + +end + +function ply:BankHas(val) + + if val < 0 then return false end + + local balance = BraxBank.PlayerMoney(self) + if balance < 0 then return false end + + return balance >= val + +end + diff --git a/garrysmod/addons/feature-atm/lua/entities/brax_atm/cl_init.lua b/garrysmod/addons/feature-atm/lua/entities/brax_atm/cl_init.lua new file mode 100644 index 0000000..c145ac0 --- /dev/null +++ b/garrysmod/addons/feature-atm/lua/entities/brax_atm/cl_init.lua @@ -0,0 +1,432 @@ +include('shared.lua') + +-- store temporary variables +BraxATM = {} +BraxATM.UserMoney = 0 +BraxATM.ReturnCode = 0 + +local sounds = { + [3] = 'atm/withdraw.wav', + [5] = 'atm/deposit.wav', + [2] = 'buttons/button8.wav', + [4] = 'buttons/button8.wav', +} + +net.Receive( 'BraxAtmFetch', function( length, client ) + BraxATM.UserMoney = net.ReadInt(32) +end ) + +net.Receive( 'BraxAtmReturnCode', function( length, client ) + local ent = net.ReadEntity() + BraxATM.ReturnCode = net.ReadInt(32) + local sound = sounds[BraxATM.ReturnCode] + if sound then + ent:EmitSound(sound, 75) + end +end ) + +--CreateClientConVar('brax_language', 'en', true, false) + +-- english translation +language.Add('ATM_Deposit_Ok', L.atm_deposit_ok) +language.Add('ATM_Deposit_Fail', L.atm_deposit_fail ) +language.Add('ATM_Withdraw_Ok', L.atm_withdraw_ok) +language.Add('ATM_Withdraw_Fail', L.atm_withdraw_fail) +language.Add('ATM_Deposit', L.atm_deposit) +language.Add('ATM_Withdraw', L.atm_withdraw) +language.Add('ATM_Del','←') +language.Add('ATM_Home', L.atm_home) +language.Add('ATM_Balance', L.atm_balance) +language.Add('ATM_Home1', L.atm_home1) +language.Add('ATM_Home2', L.atm_home2) +language.Add('ATM_Home3', L.atm_home3) +language.Add('ATM_Return_code','хуй хуй хуй') +language.Add('ATM_Transaction_Success', L.atm_transaction_success) +language.Add('ATM_Transaction_Failed', L.atm_transaction_failed) +language.Add('ATM_Transaction_Pending', 'Транзакция обрабатывается') + +local RC = {'#ATM_Transaction_Pending','#ATM_Withdraw_Fail','#ATM_Withdraw_Ok','#ATM_Deposit_Fail','#ATM_Deposit_Ok'} + +--[[ + Return codes!! + 1 = Pending + 2 = Deposit, bank does not have money + 3 = Deposit, ok + 4 = Insert, User does not have enough money + 5 = Insert, ok +]]-- + +surface.CreateFont( 'AtmFontTitle', { + font = 'Impact', + size = 50, + weight = 400, + blursize = 0, + scanlines = 0, + antialias = true +} ) + +surface.CreateFont( 'AtmFontButton', { + font = 'Arial', + size = 24, + weight = 400, + blursize = 0, + scanlines = 0, + antialias = true +} ) + +surface.CreateFont( 'AtmFontInput', { + font = 'Lucida Console', + size = 24, + weight = 700, + blursize = 0, + scanlines = 0, + antialias = true +} ) + +surface.CreateFont( 'AtmFontInfo', { + font = 'Arial', + size = 22, + weight = 400, + blursize = 0, + scanlines = 0, + antialias = true +} ) + +surface.CreateFont( 'AtmFontInfoBold', { + font = 'Arial', + size = 24, + weight = 700, + blursize = 0, + scanlines = 0, + antialias = true +} ) + +local function WorldToScreen(vWorldPos,vPos,vScale,aRot) + local vWorldPos=vWorldPos-vPos; + vWorldPos:Rotate(Angle(0,-aRot.y,0)); + vWorldPos:Rotate(Angle(-aRot.p,0,0)); + vWorldPos:Rotate(Angle(0,0,-aRot.r)); + return vWorldPos.x/vScale,(-vWorldPos.y)/vScale; +end + +function ENT:Initialize() + + -- Reset all variables + self.cursor = {y=0,x=0,click=false} + self.Action = 0 + self.InputValue = 0 + self.UserMoney = 0 + self.ReturnCode = 0 + self.ScrPos = 0 + self.ScrOff = 0 + self.ScrAng = 0 + self.ScrScale = 0.025 + self.Title = L.octobank + self.ScreenSize = {340,270} + self.Ding = false + +end + +function ENT:ScreenTop() return -self.ScreenSize[2]/2 end +function ENT:ScreenLeft() return -self.ScreenSize[1]/2 end +function ENT:ScreenBottom() return self.ScreenSize[2]/2 end +function ENT:ScreenRight() return self.ScreenSize[1]/2 end + +function ENT:AddToTotal(num) + if self.InputValue > 10000000 then return end + self.InputValue = self.InputValue * 10 + num +end + +function ENT:ATMWithdraw(val) + if val == 0 then + self:EmitSound('atm/click.ogg', 75) + return + end + net.Start( 'BraxAtmWithdraw' ) + net.WriteInt(val, 32) + net.SendToServer() + self.InputValue = 0 + self.Action = 3 +end + +function ENT:ATMDeposit(val) + if val == 0 then + self:EmitSound('atm/click.ogg', 75) + return + end + net.Start( 'BraxAtmDeposit' ) + net.WriteInt(val, 32) + net.SendToServer() + self.InputValue = 0 + self.Action = 3 +end + +function ENT:AddButton(text, x, y, w, h, pos, func, icon) + + surface.SetDrawColor( 60, 80, 80, 255, 255 ); + + if not pos then return end + + if pos.x > x and pos.x < x+w and pos.y > y and pos.y < y+h then + + surface.SetDrawColor( 80, 80, 80, 255 ); + + -- Main key function + + local ply = LocalPlayer() + + if ply:KeyDown(IN_USE) and !self.cursor.click and not ply:GetNetVar('Ghost') then + self.cursor.click = true + elseif not ply:KeyDown(IN_USE) and self.cursor.click then + self.cursor.click = false + self:EmitSound('atm/click.ogg', 75) + func() + end + + end + + surface.DrawRect( x, y, w, h ); + + draw.TexturedQuad + { + texture = surface.GetTextureID 'gui/gradient_up', + color = Color(10, 10, 10, 180), + x = x, + y = y, + w = w, + h = h + } + + surface.SetDrawColor( 25, 25, 25, 255, 255 ); + surface.DrawOutlinedRect( x, y, w, h ); + + local tx = surface.GetTextSize('test') + + surface.SetFont('AtmFontButton') + surface.SetTextColor(255,255,255,255) + surface.SetTextPos(x+(icon and 32 or 4), y+4) + surface.DrawText(text) + + if icon then + surface.SetDrawColor(255,255,255,255) + surface.SetMaterial(Material('icon16/'..icon..'.png')) + surface.DrawTexturedRect( x+8, y+8, 16, 16 ) + end +end + +function ENT:AddNumPad(cursor, x, y) + local sz = 32 + self:AddButton('1', x, y, sz, sz, cursor, function() self:AddToTotal(1) end) + self:AddButton('2', x+sz, y, sz, sz, cursor, function() self:AddToTotal(2) end) + self:AddButton('3', x+sz*2, y, sz, sz, cursor, function() self:AddToTotal(3) end) + + self:AddButton('4', x, y+sz, sz, sz, cursor, function() self:AddToTotal(4) end) + self:AddButton('5', x+sz, y+sz, sz, sz, cursor, function() self:AddToTotal(5) end) + self:AddButton('6', x+sz*2, y+sz, sz, sz, cursor, function() self:AddToTotal(6) end) + + self:AddButton('7', x, y+sz*2, sz, sz, cursor, function() self:AddToTotal(7) end) + self:AddButton('8', x+sz, y+sz*2, sz, sz, cursor, function() self:AddToTotal(8) end) + self:AddButton('9', x+sz*2, y+sz*2, sz, sz, cursor, function() self:AddToTotal(9) end) + + self:AddButton('0', x+sz*2, y+sz*3, sz, sz, cursor, function() self:AddToTotal(0) end) + self:AddButton('#ATM_Del', x, y+sz*3, sz*2, sz, cursor, function() self.InputValue = math.floor(self.InputValue / 10) end) +end + +function ENT:Draw() + + self:DrawModel() + + local player = LocalPlayer() + local dist = (player:GetShootPos() - self:GetPos()):LengthSqr() + if (dist > 6400) then + self.Action = 0 + self.InputValue = 0 + self.Title = L.octobank + return + end + + self.ScrScale = 0.035 + + self.ScrPos = self:GetPos() + self.ScrAng = self:GetAngles() + Angle(75,0,0) + self.ScrOff = self:GetUp() * 51.5 + self:GetForward()*8.5 + self:GetRight()*8.1 + + self.ScrAng:RotateAroundAxis(self.ScrAng:Up(), 90) + + cam.Start3D2D(self.ScrPos + self.ScrOff, self.ScrAng, self.ScrScale ) + + self.cursor.lx = self.cursor.x + self.cursor.ly = self.cursor.y + + -- Set cursor pos, important + self.cursor.x, self.cursor.y = WorldToScreen(LocalPlayer():GetEyeTrace().HitPos,self.ScrPos+self.ScrOff,self.ScrScale*0.9,self.ScrAng) + + self.cursor.x = self.cursor.x + 15 + self.cursor.y = self.cursor.y - 50 + + surface.SetDrawColor(155,155,155,255) + surface.SetMaterial(Material('newcity/atm.png')) + surface.DrawTexturedRect( -self.ScreenSize[1]/2, -self.ScreenSize[2]/2, self.ScreenSize[1], self.ScreenSize[2] ) + + -- Bottom bar + surface.SetDrawColor(60,60,60, 255 ) + surface.DrawRect(self:ScreenLeft(), self:ScreenBottom()-32,self.ScreenSize[1],32) + + -- Main menu navigation + if self.Action == 0 then + + draw.SimpleText('#ATM_Home1', 'AtmFontInfoBold', 0, -60, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText('#ATM_Home2', 'AtmFontInfo', 0, -35, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if netvars.GetNetVar('pendingTax') and player:GetNetVar('taxes', 0) > 0 then + local txt = L.atm_home_taxes:format(DarkRP.formatMoney(player:GetNetVar('taxes'))) + draw.SimpleText(txt, 'AtmFontInfo', 0, -10, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + draw.SimpleText('#ATM_Home3', 'AtmFontInfo', 0, 60, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + self:AddButton('#ATM_Withdraw', self:ScreenLeft(), self:ScreenBottom()-32, 150, 32, self.cursor, function() + self.Action = 1 + self.InputValue = 0 + RunConsoleCommand('brax_atm_update') + self.Title = '#ATM_Withdraw' + end,'arrow_down') + + self:AddButton('#ATM_Deposit', self:ScreenRight()-150, self:ScreenBottom()-32, 150, 32, self.cursor, function() + self.Action = 2 + self.InputValue = 0 + RunConsoleCommand('brax_atm_update') + self.Title = '#ATM_Deposit' + end,'arrow_up') + end + + if self.Action == 1 or self.Action == 2 then + + -- color if not valid + local BalanceColor = Color(0,0,0,255) + if self.InputValue > BraxATM.UserMoney and self.Action == 1 then BalanceColor = Color(255,0,0,255) end + if self.InputValue > LocalPlayer():GetNetVar('money', 0) and self.Action == 2 then BalanceColor = Color(255,0,0,255) end + + -- Input value + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawRect(-155,-55,190,32) + draw.SimpleText(DarkRP.formatMoney(self.InputValue), 'AtmFontInput', 30, -49, BalanceColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_RIGHT) + + -- Balance + draw.SimpleText('#ATM_Balance', 'AtmFontInfoBold', 35, -20, Color(0,0,0,255), TEXT_ALIGN_RIGHT, TEXT_ALIGN_RIGHT) + draw.SimpleText(DarkRP.formatMoney(BraxATM.UserMoney), 'AtmFontInfo', 35, 4, Color(0, 0, 0, 255), TEXT_ALIGN_RIGHT, TEXT_ALIGN_RIGHT) + + self:AddNumPad(self.cursor, 40,-55) + + end + + + -- Deposit start screen + -- PUT MONEY + if self.Action == 2 then + self:AddButton('#ATM_Deposit', self:ScreenRight()-150, self:ScreenBottom()-32, 150, 32, self.cursor, function() + BraxATM.ReturnCode = 1 + self:ATMDeposit(self.InputValue) + end, 'arrow_up') + end + + -- Withdraw start screen + -- TAKE MONEY + if self.Action == 1 then + self:AddButton('#ATM_Withdraw', self:ScreenRight()-150, self:ScreenBottom()-32, 150, 32, self.cursor, function() + BraxATM.ReturnCode = 1 + self:ATMWithdraw(self.InputValue) + end,'arrow_down') + end + + -- End screen + if self.Action == 3 then + local ding, icon, msg + if BraxATM.ReturnCode == 1 then + -- Transaction pending + icon, msg = 'octoteam/icons/clock.png', 'Подождите...' + elseif BraxATM.ReturnCode == 2 or BraxATM.ReturnCode == 4 then + -- Transaction fail + ding, icon, msg = 'buttons/button8.wav', 'octoteam/icons/cross.png', '#ATM_Transaction_Failed' + else + -- Transaction success + ding, icon, msg = 'buttons/bell1.wav', 'octoteam/icons/check.png', '#ATM_Transaction_Success' + end + if ding and not self.Ding then + surface.PlaySound(ding) + self.Ding = true + end + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(Material(icon)) + surface.DrawTexturedRect(-32, -72, 64, 64) + draw.SimpleText(msg, 'AtmFontInfoBold', 0, 25, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + draw.SimpleText(language.GetPhrase(RC[BraxATM.ReturnCode] or ''), 'AtmFontInfo', 0, 45, Color(0, 0, 0, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + --draw.SimpleText(language.GetPhrase('#ATM_Return_code')..' '..BraxATM.ReturnCode, 'AtmFontInfo', 0, 77, Color(0, 0, 0, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + -- Home button + if self.Action > 0 then + self:AddButton('#ATM_Home',self:ScreenLeft(),self:ScreenBottom()-32, 150, 32, self.cursor, function() + self.Action = 0 + self.Title = L.octobank + self.Ding = false + end,'house') + end + + -- Top bar and title + surface.SetDrawColor(43,86,167, 255 ) + surface.DrawRect(self:ScreenLeft(), self:ScreenTop(),self.ScreenSize[1],self.ScreenSize[2]/6) + draw.SimpleText(self.Title, 'AtmFontTitle', self:ScreenLeft()+4, self:ScreenTop()+22, Color(255, 255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(255,255,255,255 ) + + -- nice math + for i=1,6 do + local x = math.sin( CurTime() * 2+i ) * 12 + self:ScreenRight()-25 + local y = math.cos( CurTime() * 3+i ) * 12 + self:ScreenTop()+20 + surface.DrawRect(x,y,3,3) + end + + -- Cursor + if self.cursor.x > self:ScreenLeft() and self.cursor.x < self:ScreenRight() and self.cursor.y > self:ScreenTop() and self.cursor.y < self:ScreenBottom() then + surface.SetDrawColor(255,255,255,255) + surface.SetMaterial(Material('icon16/cursor.png')) --'icon32/hand_point_090.png' + surface.DrawTexturedRect(self.cursor.x-3, self.cursor.y, 16, 16) + --surface.DrawRect(self.cursor.x, self.cursor.y,1,1) -- debug test + end + + cam.End3D2D() +end + +local function CanUseBraxATM(entity) + return IsValid(entity) and + entity:GetClass() == 'brax_atm' and + LocalPlayer():GetShootPos():DistToSqr(entity:GetPos()) <= 6400 +end +hook.Add('PlayerButtonDown', 'brax_atm.input', function(player, button) + + local entity = player:GetEyeTrace().Entity + if not CanUseBraxATM(entity) or + not IsFirstTimePredicted() + then + return + end + + if button == KEY_BACKSPACE then + entity.InputValue = input.IsShiftDown() + and 0 + or math.floor(entity.InputValue / 10) + elseif button >= KEY_PAD_0 and button <= KEY_PAD_9 then + entity:AddToTotal(button - KEY_PAD_0) + elseif button >= KEY_0 and button <= KEY_9 then + entity:AddToTotal(button - KEY_0) + end + +end) +hook.Add('PlayerBindPress', 'brax_atm.block', function(player, bind, pressed, code) + + if CanUseBraxATM(player:GetEyeTrace().Entity) and code >= KEY_0 and code <= KEY_9 then + return true + end + +end) diff --git a/garrysmod/addons/feature-atm/lua/entities/brax_atm/init.lua b/garrysmod/addons/feature-atm/lua/entities/brax_atm/init.lua new file mode 100644 index 0000000..c8e5683 --- /dev/null +++ b/garrysmod/addons/feature-atm/lua/entities/brax_atm/init.lua @@ -0,0 +1,31 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +include( "shared.lua" ) + +function ENT:SpawnFunction( ply, tr ) + if !tr.Hit then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 1 + local ent = ents.Create( "brax_atm" ) + ent:SetPos( SpawnPos ) + ent:Spawn() + ent:Activate() + return ent +end + +function ENT:Initialize() + + self.BreakOpenHealthMax = 10 + self.BreakOpenHealth = 10 + self.BreakOpenBroken = false + + self:SetModel("models/props_unique/atm01.mdl") + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + phys:Wake() + end + +end diff --git a/garrysmod/addons/feature-atm/lua/entities/brax_atm/shared.lua b/garrysmod/addons/feature-atm/lua/entities/brax_atm/shared.lua new file mode 100644 index 0000000..583c0f8 --- /dev/null +++ b/garrysmod/addons/feature-atm/lua/entities/brax_atm/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" + +ENT.PrintName = "ATM Machine" +ENT.Author = "Braxen" +ENT.Contact = "" +ENT.Purpose = "very nice" +ENT.Instructions = "yes." +ENT.Category = "BraxnetRP" + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/feature-camera/lua/autorun/server/init.lua b/garrysmod/addons/feature-camera/lua/autorun/server/init.lua new file mode 100644 index 0000000..c444523 --- /dev/null +++ b/garrysmod/addons/feature-camera/lua/autorun/server/init.lua @@ -0,0 +1,83 @@ +CFG.use.ent_dbg_camera = { + function(ply, ent) + if ent.pendingWorker ~= ply then return end + return 'Починить', 'octoteam/icons/wrench.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + if not ply:IsSuperAdmin() then return end + return 'Настроить', 'octoteam/icons/keypad.png', function(ply, ent) + local dt, notifyData = ent:GetNetVar('rotationData', {}), octolib.array.toKeys(ent.notifyData) or {} + local notifyOpts = octolib.table.mapSequential(table.GetKeys(simpleOrgs.orgs), function(v) return {simpleOrgs.orgs[v].name, v, notifyData[v]} end) + notifyOpts[#notifyOpts + 1] = {'Полиция', 'cp', notifyData['cp']} + octolib.request.send(ply, { + pitch = { + name = 'Наклон', + type = 'numSlider', + dec = 0, + min = -45, + max = 30, + default = dt.p or 0, + }, + center = { + name = 'Центр', + type = 'numSlider', + dec = 0, + min = -120, + max = 120, + default = dt.center or 0, + }, + radius = { + name = 'Радиус вращения', + desc = 'Левая граница: центр - alpha, правая: центр + alpha', + type = 'numSlider', + dec = 0, + min = 0, + max = 90, + default = dt.r or 0, + }, + speed = { + name = 'Скорость вращения', + desc = 'Градусы/сек', + type = 'numSlider', + dec = 1, + min = 0, + max = 30, + default = math.deg(dt.v or 0), + }, + viewDist = { + name = 'Дальность обзора', + type = 'numSlider', + dec = 0, + min = 500, + max = 2000, + default = dt.viewDist or 1500, + }, + notifyData = { + name = 'Оповещения', + desc = 'Выбери, какую организацию будет оповещать камера о всех нарушениях', + type = 'checkGroup', + opts = notifyOpts, + required = true, + }, + }, function(data) + if IsValid(ent) then + data.pitch = math.Clamp(data.pitch or dt.p or 0, -45, 30) + data.center = math.Clamp(data.center or dt.center or 0, -120, 120) + data.radius = math.Clamp(data.radius or dt.r or 0, 0, 90) + data.speed = math.Clamp(data.speed or dt.v or 0, 0, 30) + data.viewDist = math.Clamp(data.viewDist or dt.viewDist, 500, 2000) + ent.notifyData = data.notifyData + + local ac = math.abs(data.center) + if ac + data.radius > 120 then + data.radius = 120 - ac + end + + ent:SetRotationData(data.pitch, data.center, data.radius, math.rad(data.speed), data.viewDist) + end + end) + end + end, +} diff --git a/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/cl_init.lua b/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/cl_init.lua new file mode 100644 index 0000000..9d97cea --- /dev/null +++ b/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/cl_init.lua @@ -0,0 +1,77 @@ +include('shared.lua') +local mat = Material('sprites/light_glow02_add_noz') + +function ENT:Initialize() + self.drawLight = false + self.pixvishandle = util.GetPixelVisibleHandle() + self.nextDraw = CurTime() + timer.Simple(0, function() if IsValid(self) then self.oldRotationData = nil end end) +end + +function ENT:Move() -- vertim boshkoy + local center, p = self:GetRotation() + self:ManipulateBoneAngles(1, Angle(0, 0, center)) + self:ManipulateBoneAngles(2, Angle(0, p, 0)) +end + +function ENT:Think() + + if self:GetNetVar('broken') then + self.drawLight = false + return + end + + local lp = LocalPlayer() + local ct = CurTime() + + if ct > self.nextDraw and IsValid(lp) then + self.drawLight = not self.drawLight and self:GetPos():DistToSqr(lp:GetPos()) < self.LightDrawDistSqr + self.nextDraw = ct + 0.75 + + --[[ if self.drawLight and self:GetNetVar('rotationStart', ct) < ct then + self:EmitSound('npc/turret_floor/ping.wav', 50, 100, 0.5) + end ]] + end + + local data = self:GetNetVar('rotationData') + if data and (data.r > 0 and data.v > 0 or data ~= self.oldRotationData) then + self:Move() + self.oldRotationData = data + end + +end + +local fieldSegs = 20 +local rPos = {} +for i = 1, fieldSegs do + local ang = math.pi * 2 / fieldSegs * i + rPos[i] = { math.cos(ang), math.sin(ang) } +end + +local colField = Color(255, 100, 100) +local coneRadius = math.tan(math.rad(ENT.FOV)) +function ENT:Draw() + pcall(function() + self:DrawModel() + + if self.drawLight then + local pos = LocalToWorld(Vector(2.6, -8, -1.5), Angle(), self:GetBonePosition(2)) + local visible = util.PixelVisible(pos, 2, self.pixvishandle) + if visible and visible >= 0.6 then + visible = (visible - 0.6) * 2.5 + render.SetMaterial(mat) + render.DrawSprite(pos, 5, 5, Color(255,0,0, 255*visible)) + end + end + + local d = self:GetViewDist() + if octolib.drawDebug and not self:GetNetVar('broken') and self:GetPos():DistToSqr(LocalPlayer():GetPos()) < d*d*4 then + local pos, ang = self:ScreenPos() + local endpos = pos + ang:Forward() * d + for i = 1, fieldSegs do + local right, up = ang:Right() * rPos[i][1] * coneRadius * d, ang:Up() * rPos[i][2] * d + render.DrawLine(pos, endpos + right + up, colField, true) + end + end + end) +end diff --git a/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/init.lua b/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/init.lua new file mode 100644 index 0000000..31d6196 --- /dev/null +++ b/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/init.lua @@ -0,0 +1,294 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +duplicator.RegisterEntityClass('ent_dbg_camera', function(ply, data) + local ent = duplicator.GenericDuplicatorFunction(ply, data) + local rotData = ent.rotationData + ent:SetRotationData(rotData.p, rotData.center, rotData.r, rotData.v, rotData.viewDist) + + if IsValid(ply) then + undo.Create('ent_dbg_camera') + undo.AddEntity(ent) + undo.SetPlayer(ply) + undo.Finish() + ply:AddCount('ent_dbg_cameras', ent) + ply:AddCleanup('ent_dbg_cameras', ent) + end + + return ent +end, 'Data', 'rotationData', 'notifyData') + +local angCos = math.cos(math.rad(ENT.FOV)) + +local function isSuspicious(ply) + if ply:isWanted() then return true, 'обнаружила разыскиваемого человека' end + if ply.hacking or ply.lockpickCache then return true, 'зафиксировала попытку взлома' end + if ply:GetActiveWeapon() ~= nil and ply:GetActiveWeapon().isScaring then return true, 'зафиксировала угрозу оружием' end + if IsValid(ply:GetNetVar('dragging')) then + local wep = ply:GetNetVar('dragging'):GetActiveWeapon() + if wep.CuffType ~= 'weapon_cuff_police' then + return true, 'зафиксировала похищение человека' + end + end +end + +local function desc(ply) + if not ply:GetNetVar('dbgDesc') then return end + return string.Trim(ply:GetNetVar('dbgDesc')) ~= '' and ply:GetNetVar('dbgDesc') or L.desc_usual +end + +local function findWitness(ply, includeBusy) + local ct = CurTime() + for _,v in ipairs(ents.FindInSphere(ply:GetPos(), 2000)) do + if v:GetClass() ~= 'ent_dbg_camera' or (not includeBusy and v.lastCall + 120 > ct) then continue end + if v:CanSee(ply, true) and v:GetPos():DistToSqr(ply:GetShootPos()) <= v:GetViewDist() * v:GetViewDist() then + return v + end + end +end + +local function handleDeath(vict, att) + local vCam = findWitness(vict, true) + if not vCam then return end + local sawAtt = findWitness(att, true) ~= nil + vCam:CameraReport('зафиксировала убийство', sawAtt and desc(att)) + hook.Run('dbg-camera.trigger', vCam, vict, 'зафиксировала убийство', sawAtt, att) +end + +hook.Add('EntityDamage', 'dbg-camera', function(vict, att, wep, dmgInfo) + if vict:IsPlayer() and IsValid(att) and att:IsPlayer() then + if vict:Health() <= dmgInfo:GetDamage() then + return handleDeath(vict, att) + end + local vCam = findWitness(vict) + if not vCam then return end + local sawAtt = findWitness(att) ~= nil + vCam:CameraReport('зафиксировала нападение', sawAtt and desc(att)) + hook.Run('dbg-camera.trigger', vCam, vict, 'зафиксировала нападение', sawAtt, att) + end +end) + +local function addCameraMarker(ply, marker, text) + ply:EmitSound('ambient/chatter/cb_radio_chatter_' .. math.random(1,3) .. '.wav', 45, 100, 0.5) + ply:Notify('warning', text) + ply:AddMarker(marker) +end + +function ENT:CameraReport(reason, desc) + if self:GetNetVar('broken') then return end + local notifyData = self.notifyData + if not notifyData then return end + local text = self.camName .. ' ' .. reason + if desc then + text = text .. '. Внешность: ' .. desc + end + + notifyData = octolib.array.toKeys(notifyData) + + local marker = { + id = 'camera' .. self.camID, + txt = 'Камера #' .. self.camID, + pos = self:GetPos(), + col = Color(235,120,120), + des = {'timedist', {120, 300}}, + icon = 'octoteam/icons-16/exclamation.png', + } + + for _,v in ipairs(player.GetAll()) do + if notifyData[v.currentOrg] then + addCameraMarker(v, marker, text) + end + if notifyData['cp'] and v:isCP() then + addCameraMarker(v, marker, text) + end + end + + self.lastCall = CurTime() + self:SetNetVar('rotationStart', CurTime() + 120) +end + +function ENT:CanSee(target, checkDeg) + if checkDeg and octolib.math.loopedDist(self:GetRotation(), self:GetRotationTo(target), -180, 180) > 45 then + return false + end + if target:IsInvisible() then return false end + + local seat = target:GetVehicle() + local vehicle = IsValid(seat) and seat:GetParent() or nil + + local att = target:GetAttachment(target:LookupAttachment('eyes')) + if not att then return false end + local tr = util.TraceLine({ + start = self:ScreenPos(), + endpos = att.Pos, + filter = {self, target, seat, vehicle}, + }) + return not tr.Hit +end + +function ENT:SparkEffect(pos) + local effect = ents.Create('env_spark') + effect:SetKeyValue('targetname', 'target') + effect:SetPos(pos or self:ScreenPos()) + effect:SetAngles(Angle()) + effect:Spawn() + effect:SetKeyValue('spawnflags','128') + effect:SetKeyValue('Magnitude',1) + effect:SetKeyValue('TrailLength',0.2) + effect:Fire('SparkOnce') + effect:Fire('kill','',0.5) +end + +local dmgDescriptions = { + [DMG_GENERIC] = { + 'Кажется, просто перебои с электричеством', + 'Коротнуло. Это все', + 'Видимо, дождем залило. Вот и коротнуло', + }, + [DMG_CRUSH] = { + 'Кажется, ее ударили чем-то тяжелым', + 'Камера едва держится. Ее чем-то сбили', + }, + [DMG_BULLET] = { + 'На корпусе видны следы от пуль', + 'В нее явно стреляли', + }, + [DMG_SLASH] = { + 'Кто-то перерезал провода', + 'Провода отрезаны', + }, + [DMG_BURN] = { + 'Камера пахнет жженым пластиком. Либо она сгорела, либо ее подожгли', + 'Камера перегорела, или ее подожгли', + 'Корпус весь обгорел', + }, + [DMG_CLUB] = { + 'Кто-то вырвал все провода', + }, + [DMG_SHOCK] = { + 'Камера едва держится. Ее чем-то сбили', + }, + [DMG_SONIC] = { + 'Кто-то перерезал провода', + 'Провода отрезаны', + 'Кто-то баловался острым предметом и отрезал провода', + } +} + +function ENT:Break(type) + self:CameraReport('была выведена из строя') + hook.Run('dbg-camera.destroy', self) + self:SetNetVar('broken', true) + self:SetColor(Color(64,64,64)) + self:SparkEffect() + self:ManipulateBoneAngles(2, Angle(0,-60,0)) + timer.Create('dbg-camera.repair' .. self:EntIndex(), 60 * 30, 1, function() + if IsValid(self) then + self:Repair() + end + end) + if type then + type = bit.band(type, 1023) + local reasonsTable = dmgDescriptions[type] or dmgDescriptions[DMG_GENERIC] + self:SetNetVar('dbgLook', { + name = '', + desc = reasonsTable[math.random(#reasonsTable)], + time = 4, + }) + end +end + +function ENT:GetRotationTo(ply) + local ang = (ply:GetShootPos() - self:GetPos()):Angle() + return (ang - self:GetAngles()).y +end + +function ENT:FaceTo(ply) + self:SetNetVar('freezeRotate', self:GetRotationTo(ply)) +end + +function ENT:Repair() + self:SetNetVar('broken') + self:SetNetVar('dbgLook') + self:SetColor(Color(255,255,255)) + timer.Remove('dbg-camera.repair' .. self:EntIndex()) + self:SetNetVar('rotationStart', CurTime()) + self:RemoveAllDecals() + self.health = 50 + self:ManipulateBoneAngles(2, Angle(0, self.rotationData and self.rotationData.p or 0)) +end + +function ENT:OnTakeDamage(info) + self:SparkEffect(info:GetDamagePosition()) + if self.health <= 0 then return end + self.health = self.health - info:GetDamage() + if self.health <= 0 then + self:Break(info:GetDamageType()) + else + hook.Run('dbg-camera.damage', self, info:GetAttacker()) + end +end + +function ENT:Initialize() + + self:SetModel('models/tobadforyou/surveillance_camera.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.lastCall = -math.huge + self.camID = #ents.FindByClass('ent_dbg_camera') + self.camName = 'Камера #' .. self.camID + self:Repair() + if not self.rotationData then + self:SetRotationData(-25, 0, 0, 0, self.ViewDist) + end + +end + +function ENT:SetRotationData(pitch, center, radius, speed, dist) + local data = {p = pitch, center = center, r = radius, v = speed, viewDist = dist} + self:SetNetVar('rotationData', data) + self.rotationData = data +end + +function ENT:Scan() + + local ct = CurTime() + if ct - self.lastCall <= 120 then + if ct - self.lastCall <= 5 then + self:EmitSound('buttons/button17.wav', 75, 110, 1) + end + return 1 + end + + local pos, ang = self:ScreenPos() + for _,v in ipairs(ents.FindInCone(pos, ang:Forward(), self:GetViewDist(), angCos)) do + if not v:IsPlayer() then continue end + local isSusp, suspMsg = isSuspicious(v) + if not isSusp then continue end + if self:CanSee(v) then + hook.Run('dbg-camera.trigger', self, v, suspMsg) + self:CameraReport(suspMsg, desc(v)) + self:FaceTo(v) + self:EmitSound('buttons/button17.wav', 75, 110, 1) + return 1 + end + end + return 2 + +end + +function ENT:Think() + + if self:GetNetVar('broken') then + if math.random(2) == 1 then + self:SparkEffect() + end + self:NextThink(CurTime() + 0.5) + return true + end + self:NextThink(CurTime() + self:Scan()) + return true + +end diff --git a/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/shared.lua b/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/shared.lua new file mode 100644 index 0000000..d1a43b0 --- /dev/null +++ b/garrysmod/addons/feature-camera/lua/entities/ent_dbg_camera/shared.lua @@ -0,0 +1,46 @@ +ENT.Type = 'anim' +ENT.Base = 'ent_dbg_workproblem' +ENT.PrintName = 'Камера' +ENT.Category = L.dobrograd +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.RepairDistSqr = 20000 +ENT.LightDrawDistSqr = 500 * 500 +ENT.FOV = 40 +ENT.ViewDist = 1500 +ENT.FieldViewDistSqr = 1500 * 1500 +ENT.notifyData = {'cp'} + +local function linear(val) + local p = val % 2 - 1 + return val % 4 > 2 and p or -p +end + +function ENT:GetRotation() + + local data = self:GetNetVar('rotationData') + if not data or not data.center then return angle_zero, 0 end + if data.v == 0 or data.r == 0 then return data.center, data.p end + + local ct = CurTime() + local x = ct - self:GetNetVar('rotationStart', ct) + if x <= 0 then return self:GetNetVar('freezeRotate', data.center), data.p end + + return data.center + linear(x * data.v) * data.r, data.p + +end + +function ENT:ScreenPos(rel) + local y, p = self:GetRotation() + local pos, ang = LocalToWorld(rel or Vector(1.4, 0, -1.5), Angle(-p, y, 0), self:GetPos(), self:GetAngles()) + return pos + ang:Forward() * 7.5, ang +end + +function ENT:GetViewDist() + local data = self:GetNetVar('rotationData') + return data and data.viewDist or self.ViewDist or 1500 +end diff --git a/garrysmod/addons/feature-cars/lua/autorun/car-dealer.lua b/garrysmod/addons/feature-cars/lua/autorun/car-dealer.lua new file mode 100644 index 0000000..445353c --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/autorun/car-dealer.lua @@ -0,0 +1,22 @@ +octolib.shared('car-dealer/sh_dealer') + +octolib.shared('config/car-dealer') + +octolib.server('car-dealer/sv_core') +octolib.server('car-dealer/sv_player') +octolib.server('car-dealer/sv_queue') +octolib.server('car-dealer/sv_hooks') +octolib.server('car-dealer/sv_menu') +octolib.server('car-dealer/sv_colors') +octolib.server('car-dealer/sv_refund') +octolib.server('car-dealer/sv_test') + +octolib.client('car-dealer/vgui/menu') +octolib.client('car-dealer/vgui/vehbutton') +octolib.client('car-dealer/vgui/vehlist') +octolib.client('car-dealer/vgui/vehmodel') +octolib.client('car-dealer/vgui/vehviewer') +octolib.client('car-dealer/cl_menu') +octolib.client('car-dealer/cl_colors') +octolib.client('car-dealer/cl_refund') +octolib.client('car-dealer/cl_test') diff --git a/garrysmod/addons/feature-cars/lua/autorun/simfphys__init.lua b/garrysmod/addons/feature-cars/lua/autorun/simfphys__init.lua new file mode 100644 index 0000000..a8e6f48 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/autorun/simfphys__init.lua @@ -0,0 +1,2 @@ +AddCSLuaFile("simfphys/init.lua") +include("simfphys/init.lua") diff --git a/garrysmod/addons/feature-cars/lua/autorun/simfphys_extra.lua b/garrysmod/addons/feature-cars/lua/autorun/simfphys_extra.lua new file mode 100644 index 0000000..6667074 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/autorun/simfphys_extra.lua @@ -0,0 +1,1114 @@ +local light_table = { + L_HeadLampPos = Vector(-26,106,30.3), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(26,106,30.3), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(-28.5,-109.63,49.5), + L_RearLampAng = Angle(40,-90,0), + R_RearLampPos = Vector(28.5,-109.63,49.5), + R_RearLampAng = Angle(40,-90,0), + + Headlight_sprites = { + {pos = Vector(-23,106,30.23),size = 60, color = Color( 220,220,220,50)}, + {pos = Vector(-30,106,30.36),size = 60, color = Color( 220,220,220,50)}, + {pos = Vector(23,106,30.23),size = 60, color = Color( 220,220,220,50)}, + {pos = Vector(30,106,30.36),size = 60, color = Color( 220,220,220,50)}, + }, + + Headlamp_sprites = { + {pos = Vector(-26,106,30.23),material = "sprites/light_ignorez",size = 80, color = Color( 220,220,220,120)}, + {pos = Vector(26,106,30.23),material = "sprites/light_ignorez",size = 80, color = Color( 220,220,220,120)}, + }, + + Rearlight_sprites = { + {pos = Vector(-35.5,-109.63,40.75),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + {pos = Vector(-35.5,-109.63,38),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + + {pos = Vector(-24.5,-109.63,40.75),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + {pos = Vector(-24.5,-109.63,38),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + + + {pos = Vector(35.5,-109.63,40.75),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + {pos = Vector(35.5,-109.63,38),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + + {pos = Vector(24.5,-109.63,40.75),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + {pos = Vector(24.5,-109.63,38),material = "sprites/light_ignorez",size = 30,color = Color( 255, 0, 0, 50)}, + }, + Brakelight_sprites = { + {pos = Vector(-35.5,-109.63,40.75),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + {pos = Vector(-35.5,-109.63,38),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + + {pos = Vector(-24.5,-109.63,40.75),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + {pos = Vector(-24.5,-109.63,38),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + + + {pos = Vector(35.5,-109.63,40.75),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + {pos = Vector(35.5,-109.63,38),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + + {pos = Vector(24.5,-109.63,40.75),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + {pos = Vector(24.5,-109.63,38),material = "sprites/light_ignorez",size = 33,color = Color( 255, 0, 0, 50)}, + }, + RearMarker_sprites = { + Vector(-42.87,-101.7,35.71), + Vector(42.87,-101.7,35.71) + }, + --[[ + FogLight_sprites = { + {pos = Vector(-26.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(-27.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(-28.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(-29.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(-30.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(-31.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(-32.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + + {pos = Vector(26.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(27.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(28.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(29.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(30.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(31.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + {pos = Vector(32.06,109.39,15.74),material = "sprites/light_ignorez",size = 12, color = Color( 220,220,220,100)}, + }, + ]]-- + + Turnsignal_sprites = { + Left = { + Vector(-30,-109.16,40.87), + Vector(-30,-109.31,38.06), + + {pos = Vector(-26.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(-27.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(-28.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(-29.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(-30.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(-31.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(-32.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + }, + Right = { + Vector(29.5,-109.16,40.87), + Vector(29.5,-109.31,38.06), + + {pos = Vector(26.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(27.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(28.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(29.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(30.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(31.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + {pos = Vector(32.06,109.39,15.74),material = "sprites/light_ignorez",size = 20}, + }, + }, + + + ems_sounds = {"simulated_vehicles/police/siren_madmax.wav","common/null.wav"}, + ems_sprites = { + { + pos = Vector(15.89,20.46,55.79), + material = "sprites/light_glow02_add_noz", + size = 60, + Colors = {Color(0,0,250,250),Color(0,0,255,255),Color(0,0,250,250),Color(0,0,200,200),Color(0,0,150,150),Color(0,0,100,100),Color(0,0,50,50),Color(0,0,0,0)}, -- the script will go from color to color + Speed = 0.05, + } + }, + + SubMaterials = { + off = { + Base = { + [1] = "", + [3] = "" + }, + Brake = { + [1] = "models/madmax/lights/brake_1", + [3] = "" + }, + }, + on_lowbeam = { + Base = { + [1] = "models/madmax/lights/brake_1", + [3] = "models/madmax/lights/lowbeam" + }, + Brake = { + [1] = "models/madmax/lights/brake_2", + [3] = "models/madmax/lights/lowbeam" + }, + }, + on_highbeam = { + Base = { + [1] = "models/madmax/lights/brake_1", + [3] = "models/madmax/lights/highbeam" + }, + Brake = { + [1] = "models/madmax/lights/brake_2", + [3] = "models/madmax/lights/highbeam" + }, + }, + } +} +list.Set( "simfphys_lights", "madmax", light_table) + +local light_table = { + L_HeadLampPos = Vector( 115, 20, 0 ), + L_HeadLampAng = Angle(10,5,0), + R_HeadLampPos = Vector( 115, -20, 0 ), + R_HeadLampAng = Angle(10,-5,0), + + L_RearLampPos = Vector(-115,20,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-115,-20,5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + {pos = Vector(102,27.5,-1),material = "sprites/light_ignorez",size = 20, color = Color( 220,205,160,120)}, + {pos = Vector(102,-27.5,-1),material = "sprites/light_ignorez",size = 20, color = Color( 220,205,160,120)}, + {pos = Vector(102,21,-1),material = "sprites/light_ignorez",size = 20, color = Color( 220,205,160,120)}, + {pos = Vector(102,-21,-1),material = "sprites/light_ignorez",size = 20, color = Color( 220,205,160,120)}, + + {pos = Vector(102,27.5,-1),size = 60, color = Color( 220,205,160,50)}, + {pos = Vector(102,-27.5,-1),size = 60, color = Color( 220,205,160,50)}, + {pos = Vector(102,21,-1),size = 60, color = Color( 220,205,160,50)}, + {pos = Vector(102,-21,-1),size = 60, color = Color( 220,205,160,50)}, + }, + Headlamp_sprites = { + {pos = Vector(102,27.5,-1),material = "sprites/light_ignorez",size = 80, color = Color( 220,205,160,70)}, + {pos = Vector(102,-27.5,-1),material = "sprites/light_ignorez",size = 80, color = Color( 220,205,160,70)}, + {pos = Vector(102,21,-1),material = "sprites/light_ignorez",size = 80, color = Color( 220,205,160,70)}, + {pos = Vector(102,-21,-1),material = "sprites/light_ignorez",size = 80, color = Color( 220,205,160,70)}, + }, + Rearlight_sprites = { + {pos = Vector(-121,25.5,5),material = "sprites/light_ignorez",size = 45,color = Color( 255, 0, 0, 150)}, + {pos = Vector(-121,-25.5,5),material = "sprites/light_ignorez",size = 45,color = Color( 255, 0, 0, 150)}, + }, + Brakelight_sprites = { + {pos = Vector(-121,13.5,5),material = "sprites/light_ignorez",size = 45,color = Color( 255, 0, 0, 150)}, + {pos = Vector(-121,-13.5,5),material = "sprites/light_ignorez",size = 45,color = Color( 255, 0, 0, 150)}, + }, + Reverselight_sprites = { + {pos = Vector(-121,19.5,5),material = "sprites/light_ignorez",size = 25,color = Color( 255, 255, 255, 100)}, + {pos = Vector(-121,-19.5,5),material = "sprites/light_ignorez",size = 25,color = Color( 255, 255, 255, 100)}, + }, + + DelayOn = 0.5, + DelayOff = 0.25, + BodyGroups = { + On = {8,0}, + Off = {8,1} + }, + + Turnsignal_sprites = { + Left = { + Vector(103.28,27,-9.54), + Vector(103.28,28.5,-9.54), + {pos = Vector(-121,25.5,5),material = "sprites/light_ignorez",size = 55,color = Color( 255, 0, 0, 165)}, + }, + Right = { + Vector(103.28,-27,-9.54), + Vector(103.28,-28.5,-9.54), + {pos = Vector(-121,-25.5,5),material = "sprites/light_ignorez",size = 55,color = Color( 255, 0, 0, 165)}, + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = "" + }, + Brake = { + [10] = "models/gtav/dukes/lights/brake" + }, + Reverse = { + [10] = "models/gtav/dukes/lights/reverse" + }, + Brake_Reverse = { + [10] = "models/gtav/dukes/lights/brake_reverse" + }, + }, + on_lowbeam = { + Base = { + [10] = "models/gtav/dukes/lights/lowbeam" + }, + Brake = { + [10] = "models/gtav/dukes/lights/lowbeam_brake" + }, + Reverse = { + [10] = "models/gtav/dukes/lights/lowbeam_reverse" + }, + Brake_Reverse = { + [10] = "models/gtav/dukes/lights/lowbeam_brake_reverse" + }, + }, + on_highbeam = { + Base = { + [10] = "models/gtav/dukes/lights/highbeam" + }, + Brake = { + [10] = "models/gtav/dukes/lights/highbeam_brake" + }, + Reverse = { + [10] = "models/gtav/dukes/lights/highbeam_reverse" + }, + Brake_Reverse = { + [10] = "models/gtav/dukes/lights/highbeam_brake_reverse" + }, + }, + } +} +list.Set( "simfphys_lights", "dukes", light_table) + +local light_table = { + L_HeadLampPos = Vector( 28.5, 122, 31.5 ), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector( -28.5, 120, 31.5 ), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(23,-120,36), + L_RearLampAng = Angle(25,-90,0), + R_RearLampPos = Vector(-23,-120,36), + R_RearLampAng = Angle(25,-90,0), + + Headlight_sprites = { + {pos = Vector(-33,121.5,31.5),material = "sprites/light_ignorez",size = 28, color = Color( 220,205,160,255)}, + {pos = Vector(-33,121.5,31.5),size = 64, color = Color( 220,205,160,100)}, + + {pos = Vector(33,121.5,31.5),material = "sprites/light_ignorez",size = 28, color = Color( 220,205,160,255)}, + {pos = Vector(33,121.5,31.5),size = 64, color = Color( 220,205,160,100)}, + }, + Headlamp_sprites = { + {pos = Vector(-24,121.5,31.5),material = "sprites/light_ignorez",size = 28, color = Color( 220,205,160,255)}, + {pos = Vector(-24,121.5,31.5),size = 64, color = Color( 220,205,160,100)}, + + {pos = Vector(24,121.5,31.5),material = "sprites/light_ignorez",size = 28, color = Color( 220,205,160,255)}, + {pos = Vector(24,121.5,31.5),size = 64, color = Color( 220,205,160,100)}, + }, + Rearlight_sprites = { + Vector(33.5,-120,36),Vector(31.9,-120,36),Vector(30.3,-120,36),Vector(28.7,-120,36),Vector(27.1,-120,36),Vector(25.5,-120,36),Vector(23.9,-120,36),Vector(22.3,-120,36),Vector(20.7,-120,36),Vector(19.1,-120,36),Vector(17.5,-120,36),Vector(15.9,-120,36),Vector(14.3,-120,36),Vector(12.7,-120,36),Vector(11.1,-120,36),Vector(9.5,-120,36),Vector(7.9,-120,36), + Vector(-33.5,-120,36),Vector(-31.9,-120,36),Vector(-30.3,-120,36),Vector(-28.7,-120,36),Vector(-27.1,-120,36),Vector(-25.5,-120,36),Vector(-23.9,-120,36),Vector(-22.3,-120,36),Vector(-20.7,-120,36),Vector(-19.1,-120,36),Vector(-17.5,-120,36),Vector(-15.9,-120,36),Vector(-14.3,-120,36),Vector(-12.7,-120,36),Vector(-11.1,-120,36),Vector(-9.5,-120,36),Vector(-7.9,-120,36) + }, + Reverselight_sprites = { + Vector(-17.6,-121.1,23.1), + Vector(17.6,-121.1,23.1) + }, + FrontMarker_sprites = { + Vector(-30,120.5,18), + Vector(30,120.5,18), + Vector(-42.5,110.3,24.4), + Vector(42.5,110.3,24.4) + }, + RearMarker_sprites = { + Vector(-41.5,-113,28), + Vector(41.5,-113,28) + }, + + Turnsignal_sprites = { + Left = { + Vector(-30,120.5,18), + Vector(-42.5,110.3,24.4), + }, + Right = { + Vector(30,120.5,18), + Vector(42.5,110.3,24.4) + }, + + TurnBrakeLeft = { + Vector(-33.5,-120,36),Vector(-31.9,-120,36),Vector(-30.3,-120,36),Vector(-28.7,-120,36),Vector(-27.1,-120,36),Vector(-25.5,-120,36),Vector(-23.9,-120,36),Vector(-22.3,-120,36),Vector(-20.7,-120,36),Vector(-19.1,-120,36),Vector(-17.5,-120,36),Vector(-15.9,-120,36), + }, + + TurnBrakeRight = { + Vector(33.5,-120,36),Vector(31.9,-120,36),Vector(30.3,-120,36),Vector(28.7,-120,36),Vector(27.1,-120,36),Vector(25.5,-120,36),Vector(23.9,-120,36),Vector(22.3,-120,36),Vector(20.7,-120,36),Vector(19.1,-120,36),Vector(17.5,-120,36),Vector(15.9,-120,36), + }, + }, + + DelayOn = 2.1, + DelayOff = 2.1, + Animation = { + On = "lightcoveropenreal", + Off = "lightcoverclosereal" + } +} +list.Set( "simfphys_lights", "charger", light_table) + + +local light_table = { + ModernLights = true, + + L_HeadLampPos = Vector(87.8,24.1,22.7), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(87.8,-24.1,22.7), + R_HeadLampAng = Angle(15,0,0), + + L_RearLampPos = Vector(-88,24.5,31.2), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-88,-24.5,31.2), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + {pos = Vector(84.2,29.4,22.3),material = "sprites/light_ignorez",size = 18, color = Color(215,240,255,255)}, + {pos = Vector(84.2,29.4,22.3),size = 64, color = Color( 215,240,255,50)}, + + {pos = Vector(84.2,-29.4,22.3),material = "sprites/light_ignorez",size = 18, color = Color(215,240,255,255)}, + {pos = Vector(84.2,-29.4,22.3),size = 64, color = Color( 215,240,255,50)}, + + {pos = Vector(87.8,24.1,22.3),material = "sprites/light_ignorez",size = 18, color = Color(215,240,255,255)}, + {pos = Vector(87.8,24.1,22.3),size = 64, color = Color( 215,240,255,50)}, + + {pos = Vector(87.8,-24.1,22.3),material = "sprites/light_ignorez",size = 18, color = Color(215,240,255,255)}, + {pos = Vector(87.8,-24.1,22.3),size = 64, color = Color( 215,240,255,50)}, + + }, + Headlamp_sprites = { + {pos = Vector(90,-19,21.9),material = "sprites/light_ignorez",size = 18, color = Color(215,240,255,255)}, + {pos = Vector(90,-19,21.9),size = 64, color = Color( 215,240,255,50)}, + + {pos = Vector(90,19,21.9),material = "sprites/light_ignorez",size = 18, color = Color(215,240,255,255)}, + {pos = Vector(90,19,21.9),size = 64, color = Color( 215,240,255,50)}, + }, + Rearlight_sprites = { + Vector(-88,22.2,31.2),Vector(-86.2,26.9,30.9), + Vector(-88,-22.2,31.2),Vector(-86.2,-26.9,30.9) + }, + Brakelight_sprites = { + Vector(-88,22.2,31.2),Vector(-86.2,26.9,30.9), + Vector(-88,-22.2,31.2),Vector(-86.2,-26.9,30.9), + + Vector(-70,5.9,49.8),Vector(-70,4.425,49.85),Vector(-70,2.95,49.9),Vector(-70,1.475,49.95),Vector(-70,0,50),Vector(-70,-5.9,49.8),Vector(-70,-4.425,49.85),Vector(-70,-2.95,49.9),Vector(-70,-1.475,49.95), + }, + Reverselight_sprites = { + Vector(-90,16.6,30.9), + Vector(-90,-16.6,30.9) + }, + FogLight_sprites = { + {pos = Vector(87.79,26.65,9.62),material = "sprites/light_ignorez",size = 16, color = Color(215,240,255,255)}, + {pos = Vector(87.79,26.65,9.62),size = 32, color = Color( 215,240,255,50)}, + + {pos = Vector(87.79,-26.65,9.62),material = "sprites/light_ignorez",size = 16, color = Color(215,240,255,255)}, + {pos = Vector(87.79,-26.65,9.62),size = 32, color = Color( 215,240,255,50)}, + } +} +list.Set( "simfphys_lights", "alfons", light_table) + +local light_table = { + L_HeadLampPos = Vector(0,66.3,21.84), + L_HeadLampAng = Angle(20,90,0), + + R_HeadLampPos = Vector(0,-58.01,70.71), + R_HeadLampAng = Angle(0,90,0), + + L_RearLampPos = Vector(-14.9,-99.9,39.13), + L_RearLampAng = Angle(40,-90,0), + + Headlight_sprites = { + Vector(-12.25,67.23,22.33), + Vector(-3.91,67.03,22.14), + Vector(4.63,66.33,21.96), + Vector(13.4,66.72,22.16) + }, + Headlamp_sprites = { + Vector(14.3,-59.87,70.12), + Vector(7.34,-58.62,70.32), + Vector(-7.79,-58.55,70.09), + Vector(-14.97,-60.01,69.99) + }, + Rearlight_sprites = { + Vector(-14.9,-99.9,39.13) + }, + Brakelight_sprites = { + Vector(-14.9,-99.9,39.1) + }, +} +list.Set( "simfphys_lights", "elitejeep", light_table) + + +local V = { + Name = "Synergy Elite Jeep", + Model = "models/vehicles/buggy_elite.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Half Life 2 / Synergy", + + Members = { + Mass = 1700, + + LightsTable = "elitejeep", + + FrontWheelRadius = 18, + RearWheelRadius = 20, + + SeatOffset = Vector(0,0,-3), + SeatPitch = 0, + + PassengerSeats = { + { + pos = Vector(16,-35,21), + ang = Angle(0,0,9) + } + }, + + Backfire = true, + ExhaustPositions = { + { + pos = Vector(-15.69,-105.94,14.94), + ang = Angle(90,-90,0) + }, + { + pos = Vector(16.78,-105.46,14.35), + ang = Angle(90,-90,0) + } + }, + + StrengthenSuspension = true, + + FrontHeight = 13.5, + FrontConstant = 27000, + FrontDamping = 2200, + FrontRelativeDamping = 1500, + + RearHeight = 13.5, + RearConstant = 32000, + RearDamping = 2200, + RearRelativeDamping = 1500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 44, + Efficiency = 1.4, + GripOffset = 0, + BrakePower = 40, + + IdleRPM = 750, + LimitRPM = 7500, + PeakTorque = 100, + PowerbandStart = 2500, + PowerbandEnd = 7300, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(20.92,6.95,26.83), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 0.6, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/v8elite/v8elite_idle.wav", + + snd_low = "simulated_vehicles/v8elite/v8elite_low.wav", + snd_low_revdown = "simulated_vehicles/v8elite/v8elite_revdown.wav", + snd_low_pitch = 0.8, + + snd_mid = "simulated_vehicles/v8elite/v8elite_mid.wav", + snd_mid_gearup = "simulated_vehicles/v8elite/v8elite_second.wav", + snd_mid_pitch = 1, + + snd_horn = "simulated_vehicles/horn_4.wav", + + DifferentialGear = 0.38, + Gears = {-0.1,0,0.1,0.18,0.25,0.31,0.40} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_v8elite", V ) + +local V = { + Name = "Synergy Van", + Model = "models/vehicles/7seatvan.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Half Life 2 / Synergy", + + Members = { + Mass = 2500, + + FrontWheelRadius = 15.5, + RearWheelRadius = 15.5, + + SeatOffset = Vector(0,0,-5), + SeatPitch = 6, + + PassengerSeats = { + { + pos = Vector(27,60,33), + ang = Angle(0,0,9) + }, + { + pos = Vector(0,60,33), + ang = Angle(0,0,9) + }, + { + pos = Vector(30,-25,37.5), + ang = Angle(0,90,0) + }, + { + pos = Vector(-30,-25,37.5), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-30,-60,37.5), + ang = Angle(0,-90,0) + }, + { + pos = Vector(30,-60,37.5), + ang = Angle(0,90,0) + } + }, + + FrontHeight = 12, + FrontConstant = 45000, + FrontDamping = 2500, + FrontRelativeDamping = 2500, + + RearHeight = 12, + RearConstant = 45000, + RearDamping = 2500, + RearRelativeDamping = 2500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 350, + + TurnSpeed = 8, + + MaxGrip = 45, + Efficiency = 1.8, + GripOffset = 0, + BrakePower = 55, + + IdleRPM = 750, + LimitRPM = 6000, + PeakTorque = 80, + PowerbandStart = 1000, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-47.65,-76.59,47.43), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/4banger/4banger_idle.wav", + + snd_low = "simulated_vehicles/4banger/4banger_low.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/4banger/4banger_mid.wav", + snd_mid_gearup = "simulated_vehicles/4banger/4banger_second.wav", + snd_mid_pitch = 0.8, + + DifferentialGear = 0.52, + Gears = {-0.1,0,0.1,0.2,0.3,0.4} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_van", V ) + + +local V = { + Name = "1969 Dodge Charger", + Model = "models/vehicles/cars/69charger.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Base", + Members = { + Mass = 1700, + + LightsTable = "charger", + + FrontWheelRadius = 15, + RearWheelRadius = 15, + + SeatOffset = Vector(0,0,-2.5), + SeatPitch = 0, + + PassengerSeats = { + { + pos = Vector(20,0,20), + ang = Angle(0,0,9) + }, + { + pos = Vector(20,-30,20), + ang = Angle(0,0,9) + }, + { + pos = Vector(-20,-30,20), + ang = Angle(0,0,9) + } + }, + + ExhaustPositions = { + { + pos = Vector(17.7,-121,17.5), + ang = Angle(90,-90,0) + }, + { + pos = Vector(-17.7,-121,17.5), + ang = Angle(90,-90,0) + } + }, + + FrontHeight = 13, + FrontConstant = 28000, + FrontDamping = 2800, + FrontRelativeDamping = 2800, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 2900, + RearRelativeDamping = 2900, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 43, + Efficiency = 1.45, + GripOffset = -2, + BrakePower = 50, + + IdleRPM = 750, + LimitRPM = 5600, + PeakTorque = 230, + PowerbandStart = 1000, + PowerbandEnd = 5400, + Turbocharged = false, + Supercharged = false, + + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + FuelFillPos = Vector(-37.29,-92.65,46.53), + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.95, + snd_idle = "simulated_vehicles/master_chris_charger69/charger_idle.wav", + + snd_low = "simulated_vehicles/master_chris_charger69/charger_low.wav", + snd_low_revdown = "simulated_vehicles/master_chris_charger69/charger_revdown.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/master_chris_charger69/charger_mid.wav", + snd_mid_gearup = "simulated_vehicles/master_chris_charger69/charger_second.wav", + snd_mid_geardown = "simulated_vehicles/master_chris_charger69/charger_shiftdown.wav", + snd_mid_pitch = 1.15, + + snd_horn = "simulated_vehicles/horn_3.wav", + + DifferentialGear = 0.7, + Gears = {-0.12,0,0.12,0.21,0.32,0.42} + } +} +if (file.Exists( "models/vehicles/cars/69charger.mdl", "GAME" )) then + list.Set( "simfphys_vehicles", "sim_fphys_charger", V ) +end + + + +local V = { + Name = "Mad Max Interceptor", + Model = "models/vehicles/madmax/interceptordrive.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Base", + + Members = { + Mass = 1385, + AirFriction = -1500, + + LightsTable = "madmax", + + FrontWheelRadius = 14.6, + RearWheelRadius = 15.6, + + SeatOffset = Vector(-4,0,-4), + SeatPitch = 0, + + PassengerSeats = { + { + pos = Vector(21,0,20), + ang = Angle(0,0,9) + }, + { + pos = Vector(21,-30,20), + ang = Angle(0,0,9) + }, + { + pos = Vector(-21,-30,20), + ang = Angle(0,0,9) + } + }, + + + ExhaustPositions = { + { + pos = Vector(49,-38,23.5), + ang = Angle(30,0,0) + }, + { + pos = Vector(49,-34.5,23.5), + ang = Angle(30,0,0) + }, + { + pos = Vector(49,-31,23.5), + ang = Angle(30,0,0) + }, + { + pos = Vector(49,-27.5,23.5), + ang = Angle(30,0,0) + }, + { + pos = Vector(-49,-38,23.5), + ang = Angle(-30,0,0) + }, + { + pos = Vector(-49,-34.5,23.5), + ang = Angle(-30,0,0) + }, + { + pos = Vector(-49,-31,23.5), + ang = Angle(-30,0,0) + }, + { + pos = Vector(-49,-27.5,23.5), + ang = Angle(-30,0,0) + } + }, + + ModelInfo = { + LinkDoorAnims = { + ["enter2"] = { + enter = "enter1", + exit = "exit1", + }, + ["enter1"] = { + enter = "enter2", + exit = "exit2", + } + } + }, + + FrontHeight = 10, + FrontConstant = 28000, + FrontDamping = 2800, + FrontRelativeDamping = 2800, + + RearHeight = 8, + RearConstant = 32000, + RearDamping = 2900, + RearRelativeDamping = 2900, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 44, + Efficiency = 1.2, + GripOffset = -3.8, + BrakePower = 60, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 200, + PowerbandStart = 2000, + PowerbandEnd = 6500, + Turbocharged = false, + Supercharged = true, + + FuelFillPos = Vector(-43.17,-72.93,49), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.85, + snd_idle = "simulated_vehicles/shelby/shelby_idle.wav", + + snd_low = "simulated_vehicles/shelby/shelby_low.wav", + snd_low_revdown = "simulated_vehicles/shelby/shelby_revdown.wav", + snd_low_pitch = 0.8, + + snd_mid = "simulated_vehicles/shelby/shelby_mid.wav", + snd_mid_gearup = "simulated_vehicles/shelby/shelby_second.wav", + snd_mid_geardown = "simulated_vehicles/shelby/shelby_shiftdown.wav", + snd_mid_pitch = 1, + + snd_horn = "simulated_vehicles/horn_7.wav", + + DifferentialGear = 0.58, + Gears = {-0.12,0,0.12,0.21,0.32,0.40,0.48} + } +} +if (file.Exists( "models/vehicles/madmax/interceptordrive.mdl", "GAME" )) then + list.Set( "simfphys_vehicles", "sim_fphys_interceptor", V ) +end + + +local V = { + Name = "Alfa Romeo Brera", + Model = "models/red_hd_brera/red_hd_brera.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Base", + SpawnAngleOffset = 90, + + Members = { + Mass = 1700, + + EnginePos = Vector(65.01,0,35), + + LightsTable = "alfons", + + CustomWheels = true, + CustomSuspensionTravel = 15, + + CustomWheelModel = "models/red_hd_brera/red_hd_brera_wheel.mdl", + CustomWheelPosFL = Vector(54,34.5,9), + CustomWheelPosFR = Vector(54,-34.5,9), + CustomWheelPosRL = Vector(-59,34.5,9), + CustomWheelPosRR = Vector(-59,-34.5,9), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-11,-19,42), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-3,-18,10), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-30,-18,10), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-30,18,10), + ang = Angle(0,-90,10) + } + }, + + Backfire = true, + ExhaustPositions = { + { + pos = Vector(-94.91,24.24,9.65), + ang = Angle(90,180,0) + }, + { + pos = Vector(-96.27,18.81,9.63), + ang = Angle(90,180,0) + }, + { + pos = Vector(-94.91,-24.24,9.65), + ang = Angle(90,180,0) + }, + { + pos = Vector(-96.27,-18.81,9.63), + ang = Angle(90,180,0) + } + }, + + FrontHeight = 8, + FrontConstant = 29000, + FrontDamping = 2500, + FrontRelativeDamping = 2500, + + RearHeight = 8, + RearConstant = 29000, + RearDamping = 2500, + RearRelativeDamping = 2500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 43, + Efficiency = 1.6, + GripOffset = 0, + BrakePower = 50, + + IdleRPM = 750, + LimitRPM = 7500, + Revlimiter = true, + PeakTorque = 120, + PowerbandStart = 2000, + PowerbandEnd = 7000, + Turbocharged = true, + Supercharged = false, + + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 65, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/alfaromeo/alfons_idle.wav", + + snd_low ="simulated_vehicles/alfaromeo/alfons_low.wav", + snd_low_revdown = "simulated_vehicles/alfaromeo/alfons_revdown.wav", + snd_low_pitch = 0.8, + + snd_mid = "simulated_vehicles/alfaromeo/alfons_mid.wav", + snd_mid_gearup = "simulated_vehicles/alfaromeo/alfons_gear.wav", + snd_mid_geardown = "simulated_vehicles/alfaromeo/alfons_shiftdown.wav", + snd_mid_pitch = 1, + + DifferentialGear = 0.5, + Gears = {-0.12,0,0.12,0.21,0.32,0.42,0.5} + } +} +if file.Exists( "models/red_hd_brera/red_hd_brera.mdl", "GAME" ) then + list.Set( "simfphys_vehicles", "sim_fphys_alfons", V ) +end + + +local V = { + Name = "GTA 5 Dukes", + Model = "models/blu/gtav/dukes/dukes.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Base", + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + + Members = { + Mass = 1700, + + EnginePos = Vector(69.61,0,15), + + LightsTable = "dukes", + + CustomWheels = true, + CustomSuspensionTravel = 15, + + CustomWheelModel = "models/winningrook/gtav/dukes/dukes_wheel.mdl", + CustomWheelPosFL = Vector(63.5,36,-13), + CustomWheelPosFR = Vector(63.5,-36,-13), + CustomWheelPosRL = Vector(-64,36.5,-9), + CustomWheelPosRR = Vector(-64,-36.5,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-18,-18,19), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-3,-19,-13), + ang = Angle(0,-90,17) + } + }, + + ExhaustPositions = { + { + pos = Vector(-122.25,20.93,-7.28), + ang = Angle(90,165,0), + OnBodyGroups = { + [6] = {0}, + } + }, + { + pos = Vector(-122.1,-20.95,-7.42), + ang = Angle(90,195,0), + OnBodyGroups = { + [6] = {0}, + } + }, + { + pos = Vector(-43.43,-38.07,-12.96), + ang = Angle(90,-125,0), + OnBodyGroups = { + [6] = {1}, + } + }, + { + pos = Vector(-35.28,-40.72,-13.18), + ang = Angle(90,-125,0), + OnBodyGroups = { + [6] = {1}, + } + }, + { + pos = Vector(-43.43,38.07,-12.96), + ang = Angle(90,125,0), + OnBodyGroups = { + [6] = {1}, + } + }, + { + pos = Vector(-35.28,40.72,-13.18), + ang = Angle(90,125,0), + OnBodyGroups = { + [6] = {1}, + } + } + }, + + FrontHeight = 8, + FrontConstant = 29000, + FrontDamping = 2500, + FrontRelativeDamping = 2500, + + RearHeight = 9, + RearConstant = 29000, + RearDamping = 2500, + RearRelativeDamping = 2500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 43, + Efficiency = 1.2, + GripOffset = -2, + BrakePower = 40, + + IdleRPM = 600, + LimitRPM = 7700, + PeakTorque = 200, + PowerbandStart = 1500, + PowerbandEnd = 7400, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-92.72,39.75,8.31), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.8, + snd_idle = "simulated_vehicles/gta5_dukes/dukes_idle.wav", + + snd_low = "simulated_vehicles/gta5_dukes/dukes_low.wav", + snd_low_revdown = "simulated_vehicles/gta5_dukes/dukes_revdown.wav", + snd_low_pitch = 0.8, + + snd_mid = "simulated_vehicles/gta5_dukes/dukes_mid.wav", + snd_mid_gearup = "simulated_vehicles/gta5_dukes/dukes_second.wav", + snd_mid_pitch = 1, + + snd_horn = "simulated_vehicles/horn_3.wav", + + DifferentialGear = 0.6, + Gears = {-0.12,0,0.12,0.21,0.32,0.42,0.5} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_dukes", V ) diff --git a/garrysmod/addons/feature-cars/lua/autorun/simfphys_octocars.lua b/garrysmod/addons/feature-cars/lua/autorun/simfphys_octocars.lua new file mode 100644 index 0000000..5bc2dc8 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/autorun/simfphys_octocars.lua @@ -0,0 +1,1357 @@ +local Category = "Octocars" + +-- DO NOT JUST ADD YOUR VEHICLE HERE, MAKE A NEW FILE IN LUA/AUTORUN WITH A DIFFERENT NAME! IF YOU EDIT THIS FILE YOU WILL OVERWRITE THE ORIGINAL VEHICLES! + +local V = { + Name = L.sim_fphys_hatch_v2, + Model = "models/octocar/hatchback.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 1100, + + EnginePos = Vector(54.27,0,37.26), + + LightsTable = "golf", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/hl2prewar/hatch/hatch_v2_wheel.mdl", + CustomWheelPosFL = Vector(44.5,30,10), + CustomWheelPosFR = Vector(44.5,-30,10), + CustomWheelPosRL = Vector(-46,32,10), + CustomWheelPosRR = Vector(-46,-32,10), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(0,0,15), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8.5,-16,44), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-16,14), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-24,-16,14), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-24,16,14), + ang = Angle(0,-90,20) + } + }, + + ExhaustPositions = { + { + pos = Vector(-60,-15,10), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-61.59,32.11,31.83), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 6.5, + FrontConstant = 20000, + FrontDamping = 1000, + FrontRelativeDamping = 500, + + RearHeight = 6.5, + RearConstant = 20000, + RearDamping = 1000, + RearRelativeDamping = 500, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 5, + + MaxGrip = 28, + Efficiency = 1, + GripOffset = 1, + BrakePower = 15, + + IdleRPM = 750, + LimitRPM = 3500, + PeakTorque = 40, + PowerbandStart = 1750, + PowerbandEnd = 3000, + Turbocharged = false, + Supercharged = false, + + PowerBias = -1, + + EngineSoundPreset = -1, + + Sound_Idle = "simulated_vehicles/generic3/generic3_idle.wav", + Sound_IdlePitch = 0.9, + + Sound_Mid = "simulated_vehicles/generic1/generic1_low.wav", + Sound_MidPitch = 0.8, + Sound_MidVolume = 3, + Sound_MidFadeOutRPMpercent = 47, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.26, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/generic3/generic3_mid.wav", + Sound_HighPitch = 0.8, + Sound_HighVolume = 3.0, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.29, + + DifferentialGear = 0.58, + Gears = {-0.1,0,0.12,0.2,0.28,0.35}, + + RadioPos = Vector(35,0,30), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 36.4, 30.1), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_hatch_v2", V ) + + +local V = { + Name = L.sim_fphys_pwvan_v2, + Model = "models/octocar/van.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 2500, + + EnginePos = Vector(89.98,0,51.3), + + LightsTable = "van", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/van/van_wheel.mdl", + CustomWheelPosFL = Vector(45,44,20), + CustomWheelPosFR = Vector(45,-44,20), + CustomWheelPosRL = Vector(-72,44,20), + CustomWheelPosRR = Vector(-72,-44,20), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,30), + + CustomSteerAngle = 35, + + SeatOffset = Vector(36,-23,72), + SeatPitch = 8, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(45,-27,33), + ang = Angle(0,-90,9) + }, + { + pos = Vector(45,0,33), + ang = Angle(0,-90,9) + }, + { + pos = Vector(-38,-32,31), + ang = Angle(0,0,0) + }, + { + pos = Vector(-10,32,37), + ang = Angle(0,-175,0) + }, + { + pos = Vector(-40,32,37), + ang = Angle(0,175,0) + }, + }, + + ExhaustPositions = { + { + pos = Vector(-100,-15,20), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-93.45,46.02,42.24), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 12, + FrontConstant = 45000, + FrontDamping = 5000, + FrontRelativeDamping = 5000, + + RearHeight = 12, + RearConstant = 45000, + RearDamping = 5000, + RearRelativeDamping = 5000, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 750, + + TurnSpeed = 4, + + MaxGrip = 55, + Efficiency = 1, + GripOffset = 0.8, + BrakePower = 28, + + IdleRPM = 750, + LimitRPM = 5000, + PeakTorque = 50, + PowerbandStart = 1000, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = -1, + + Sound_Idle = "simulated_vehicles/gta5_dukes/dukes_idle.wav", + Sound_IdlePitch = 0.7, + + Sound_Mid = "simulated_vehicles/alfaromeo/alfaromeo_low.wav", + Sound_MidPitch = 0.8, + Sound_MidVolume = 2, + Sound_MidFadeOutRPMpercent = 50, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.5, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/jeep/jeep_low.wav", + Sound_HighPitch = 0.85, + Sound_HighVolume = 3.5, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.7, + + DifferentialGear = 0.42, + Gears = {-0.1,0,0.12,0.24,0.36,0.4}, + + RadioPos = Vector(85,0,50), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 36.4, 32.7), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwvan_v2", V ) + + +local V = { + Name = L.sim_fphys_pwvan_v2_police, + Model = "models/octocar/van_police.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 2500, + + EnginePos = Vector(89.98,0,51.3), + + LightsTable = "van", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/van/van_wheel.mdl", + CustomWheelPosFL = Vector(45,44,20), + CustomWheelPosFR = Vector(45,-44,20), + CustomWheelPosRL = Vector(-72,44,20), + CustomWheelPosRR = Vector(-72,-44,20), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,30), + + CustomSteerAngle = 35, + + SeatOffset = Vector(36,-23,72), + SeatPitch = 8, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(45,-27,33), + ang = Angle(0,-90,9) + }, + { + pos = Vector(45,0,33), + ang = Angle(0,-90,9) + }, + { + pos = Vector(-39,-32,31), + ang = Angle(0,0,0) + }, + { + pos = Vector(-39,32,31), + ang = Angle(0,180,0) + }, + { + pos = Vector(-12,32,31), + ang = Angle(0,180,0) + }, + { + pos = Vector(10,0,31), + ang = Angle(0,90,0) + }, + }, + + ExhaustPositions = { + { + pos = Vector(-100,-15,20), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-93.45,46.02,42.24), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 12, + FrontConstant = 45000, + FrontDamping = 5000, + FrontRelativeDamping = 5000, + + RearHeight = 12, + RearConstant = 45000, + RearDamping = 5000, + RearRelativeDamping = 5000, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 750, + + TurnSpeed = 4, + + MaxGrip = 55, + Efficiency = 1, + GripOffset = 0.8, + BrakePower = 28, + + IdleRPM = 750, + LimitRPM = 5000, + PeakTorque = 55, + PowerbandStart = 1000, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = -1, + + Sound_Idle = "simulated_vehicles/gta5_dukes/dukes_idle.wav", + Sound_IdlePitch = 0.7, + + Sound_Mid = "simulated_vehicles/alfaromeo/alfaromeo_low.wav", + Sound_MidPitch = 0.8, + Sound_MidVolume = 2, + Sound_MidFadeOutRPMpercent = 50, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.5, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/jeep/jeep_low.wav", + Sound_HighPitch = 0.85, + Sound_HighVolume = 3.5, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.7, + + DifferentialGear = 0.42, + Gears = {-0.1,0,0.1,0.2,0.3,0.4}, + + RadioPos = Vector(85,0,50), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 36.4, 32.7), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwvan_v2_police", V ) + + +local V = { + Name = L.sim_fphys_pwmoskvich_v2, + Model = "models/octocar/car03.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 1350, + + EnginePos = Vector(55.76,0,44.4), + + LightsTable = "moskvich", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/moskvich/moskvich_wheel.mdl", + CustomWheelPosFL = Vector(52,36,12), + CustomWheelPosFR = Vector(52,-36,12), + CustomWheelPosRL = Vector(-57,30,12), + CustomWheelPosRR = Vector(-57,-30.5,12), + CustomWheelAngleOffset = Angle(0,0,0), + + CustomMassCenter = Vector(0,0,15), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-12,-16,49), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-4,-16,17.5), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-40,16,19), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-40,-16,19), + ang = Angle(0,-90,10) + } + }, + + ExhaustPositions = { + { + pos = Vector(-80,-15,12), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-78.34,33.36,33.18), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 6.5, + FrontConstant = 25000, + FrontDamping = 1500, + FrontRelativeDamping = 1500, + + RearHeight = 6.5, + RearConstant = 25000, + RearDamping = 1500, + RearRelativeDamping = 1500, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 5, + + MaxGrip = 30, + Efficiency = 1, + GripOffset = 0.8, + BrakePower = 30, + + IdleRPM = 750, + LimitRPM = 5500, + PeakTorque = 45, + PowerbandStart = 1500, + PowerbandEnd = 4800, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/4banger/4banger_idle.wav", + Sound_IdlePitch = 0.7, + + Sound_Mid = "simulated_vehicles/4banger/4banger_low.wav", + Sound_MidPitch = 0.8, + Sound_MidVolume = 2, + Sound_MidFadeOutRPMpercent = 37, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.26, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/4banger/4banger_mid.wav", + Sound_HighPitch = 0.9, + Sound_HighVolume = 2, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.29, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_5.wav", + + DifferentialGear = 0.4, + Gears = {-0.15,0,0.15,0.23,0.28,0.34,0.4}, + + RadioPos = Vector(35,0,30), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 38.7, 29.4), + HUDAng = Angle(0, 0, 65), + + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwmoskvich_v2", V ) + +local V = { + Name = L.sim_fphys_pwtrabant_v2, + Model = "models/octocar/car01.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = -90, + + Members = { + Mass = 1200, + + EnginePos = Vector(0.6,56.38,38.7), + + LightsTable = "trabbi", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/trabant/trabant_wheel.mdl", + CustomWheelPosFL = Vector(-32,50,12), + CustomWheelPosFR = Vector(32,50,12), + CustomWheelPosRL = Vector(-32,-41.5,12), + CustomWheelPosRR = Vector(32,-41.5,12), + CustomWheelAngleOffset = Angle(0,0,0), + + CustomMassCenter = Vector(0,0,1), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-14,45), + SeatPitch = 0, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(16,-2,12.5), + ang = Angle(0,0,8) + }, + { + pos = Vector(0,-2,12.5), + ang = Angle(0,0,8) + } + }, + + ExhaustPositions = { + { + pos = Vector(-15,-60,10), + ang = Angle(0,0,90) + }, + }, + + FuelFillPos = Vector(5.41,46.61,39.91), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 6.5, + FrontConstant = 24000, + FrontDamping = 1200, + FrontRelativeDamping = 1200, + + RearHeight = 6.5, + RearConstant = 24000, + RearDamping = 1200, + RearRelativeDamping = 1200, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 5, + + MaxGrip = 30, + Efficiency = 1, + GripOffset = 1, + BrakePower = 18, + + IdleRPM = 750, + LimitRPM = 4000, + PeakTorque = 40, + PowerbandStart = 1500, + PowerbandEnd = 3500, + Turbocharged = false, + Supercharged = false, + + PowerBias = -1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/4banger/4banger_idle.wav", + Sound_IdlePitch = 0.7, + + Sound_Mid = "simulated_vehicles/generic1/generic1_low.wav", + Sound_MidPitch = 0.7, + Sound_MidVolume = 3, + Sound_MidFadeOutRPMpercent = 47, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.26, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/generic2/generic2_low.wav", + Sound_HighPitch = 0.7, + Sound_HighVolume = 3.0, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.29, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + DifferentialGear = 0.4, + Gears = {-0.12,0,0.13,0.2,0.28,0.35,0.42}, + + RadioPos = Vector(0,45,30), + RadioAng = Angle(0,-90,0), + HUDPos = Vector(0, 36.4, 31.4), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwtrabant_v2", V ) + + + +local V = { + Name = L.sim_fphys_pwtrabant2_v2, + Model = "models/octocar/car02.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + + Members = { + Mass = 850, + + EnginePos = Vector(0,56.38,38.7), + + LightsTable = "trabbi", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/hl2prewar/car002/car_002_wheel.mdl", + CustomWheelPosFL = Vector(-32,50,12), + CustomWheelPosFR = Vector(32,50,12), + CustomWheelPosRL = Vector(-32,-41.5,12), + CustomWheelPosRR = Vector(32,-41.5,12), + CustomWheelAngleOffset = Angle(0,0,0), + + CustomMassCenter = Vector(0,0,1), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-14,45), + SeatPitch = 0, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(16,-2,12.5), + ang = Angle(0,0,8) + }, + { + pos = Vector(0,-2,12.5), + ang = Angle(0,0,8) + } + }, + + ExhaustPositions = { + { + pos = Vector(-15,-65,10), + ang = Angle(0,0,90) + }, + }, + + FuelFillPos = Vector(5.41,46.61,39.91), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 6.5, + FrontConstant = 24000, + FrontDamping = 1200, + FrontRelativeDamping = 1200, + + RearHeight = 6.5, + RearConstant = 24000, + RearDamping = 1200, + RearRelativeDamping = 1200, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 5, + + MaxGrip = 30, + Efficiency = 0.68, + GripOffset = -1, + BrakePower = 10, + + IdleRPM = 750, + LimitRPM = 6500, + PeakTorque = 45, + PowerbandStart = 2000, + PowerbandEnd = 6000, + Turbocharged = false, + Supercharged = false, + + PowerBias = -1, + + EngineSoundPreset = -1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/4banger/4banger_idle.wav", + Sound_IdlePitch = 0.7, + + Sound_Mid = "simulated_vehicles/generic1/generic1_low.wav", + Sound_MidPitch = 0.7, + Sound_MidVolume = 3, + Sound_MidFadeOutRPMpercent = 47, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.26, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/generic2/generic2_low.wav", + Sound_HighPitch = 0.7, + Sound_HighVolume = 3.0, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.29, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + DifferentialGear = 0.4, + Gears = {-0.1,0,0.1,0.19,0.23,0.27}, + + HUDPos = Vector(0, 36.4, 31.4), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwtrabant2_v2", V ) + + +local V = { + Name = L.sim_fphys_hl2_gaz63, + Model = "models/octocar/car04.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 1350, + + EnginePos = Vector(65.39,0,44.84), + + LightsTable = "volga", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/hl2prewar/car005/car_005_wheel.mdl", + CustomWheelPosFL = Vector(64,36,13), + CustomWheelPosFR = Vector(64,-36,13), + CustomWheelPosRL = Vector(-55,34,13), + CustomWheelPosRR = Vector(-55,-34,13), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,2.5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-17.5,52), + SeatPitch = 5, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-17.5,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(6,0,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,-17.5,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,17.5,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,-0,18.5), + ang = Angle(0,-90,12) + } + }, + + ExhaustPositions = { + { + pos = Vector(-80,-15,15), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-80.3,37.79,35.54), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 6.5, + FrontConstant = 25000, + FrontDamping = 1500, + FrontRelativeDamping = 1500, + + RearHeight = 6.5, + RearConstant = 25000, + RearDamping = 1500, + RearRelativeDamping = 1500, + + FastSteeringAngle = 30, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 5, + + MaxGrip = 35, + Efficiency = 1, + GripOffset = 1, + BrakePower = 25, + + IdleRPM = 750, + LimitRPM = 5000, + PeakTorque = 50, + PowerbandStart = 1500, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/generic2/generic2_idle.wav", + Sound_IdlePitch = 0.9, + + Sound_Mid = "simulated_vehicles/jeep/jeep_mid.wav", + Sound_MidPitch = 0.6, + Sound_MidVolume = 5, + Sound_MidFadeOutRPMpercent = 37, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.476, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/misc/v8high2.wav", + Sound_HighPitch = 0.7, + Sound_HighVolume = 7.0, + Sound_HighFadeInRPMpercent = 30, + Sound_HighFadeInRate = 0.19, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_5.wav", + + DifferentialGear = 0.40, + Gears = {-0.16,0,0.18,0.25,0.3,0.35,0.42}, + + RadioPos = Vector(50,0,30), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 37.7, 30.1), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_hl2_gaz63", V ) + + +local V = { + Name = L.sim_fphys_hl2_zaz969, + Model = "models/octocar/car05cabrio.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 1150, + + EnginePos = Vector(-74,0,48), + + LightsTable = "zaz", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/zaz/zaz_wheel.mdl", + CustomWheelPosFL = Vector(61,32,17), + CustomWheelPosFR = Vector(61,-34,17), + CustomWheelPosRL = Vector(-53,32,17), + CustomWheelPosRR = Vector(-53,-34,17), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,2.5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-17.5,54), + SeatPitch = 5, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-17.5,20), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,-17.5,24), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,17.5,24), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,0,24), + ang = Angle(0,-90,12) + } + }, + + ExhaustPositions = { + { + pos = Vector(-80,20,15), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-67.9,-37.75,38.59), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + FrontHeight = 6.5, + FrontConstant = 25000, + FrontDamping = 1500, + FrontRelativeDamping = 1500, + + RearHeight = 6.5, + RearConstant = 25000, + RearDamping = 1500, + RearRelativeDamping = 1500, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 5, + + MaxGrip = 35, + Efficiency = 1, + GripOffset = 1, + BrakePower = 22, + + IdleRPM = 750, + LimitRPM = 4500, + PeakTorque = 45, + PowerbandStart = 2000, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/master_chris_charger69/charger_idle.wav", + Sound_IdlePitch = 1.6, + + Sound_Mid = "simulated_vehicles/master_chris_charger69/charger_mid.wav", + Sound_MidPitch = 0.65, + Sound_MidVolume = 2, + Sound_MidFadeOutRPMpercent = 110, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.476, --how fast it fades out 0 = instant 1 = never + + Sound_High = "", + Sound_HighPitch = 0.8, + Sound_HighVolume = 5.0, + Sound_HighFadeInRPMpercent = 45, + Sound_HighFadeInRate = 0.29, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_1.wav", + + DifferentialGear = 0.47, + Gears = {-0.12,0,0.14,0.26,0.32,0.4}, + + RadioPos = Vector(50,0,40), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 36.4, 32.8), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_hl2_zaz969", V ) + +local light_table = { + L_HeadLampPos = Vector(-36.87,87.86,47.32), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(36.33,87.27,46.67), + R_HeadLampAng = Angle(15,90,0), + + Headlight_sprites = { + {pos = Vector(-36.87,87.86,47.32),material = "sprites/light_ignorez",size = 64}, + {pos = Vector(-36.87,87.86,47.32),size = 75}, + + {pos = Vector(36.33,87.27,46.67),material = "sprites/light_ignorez",size = 64}, + {pos = Vector(36.33,87.27,46.67),size = 75} + }, + Headlamp_sprites = { + {pos = Vector(-36.87,87.86,47.32),size = 110}, + {pos = Vector(36.33,87.27,46.67),size = 110} + }, + Turnsignal_sprites = { + Left = { + Vector(-38.2,87.81,60), + Vector(-50,74,58), + }, + Right = { + Vector(37.8,87.41,60), + Vector(50,74,58), + }, + } +} +list.Set( "simfphys_lights", "gaz52_v2", light_table) + +local V = { + Name = L.sim_fphys_gaz52_v2, + Model = "models/octocar/truck03.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = -90, + + Members = { + Mass = 3000, + + EnginePos = Vector(0,61.23,76.81), + + LightsTable = "gaz52_v2", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/gaz52/gaz52_wheel.mdl", + -- CustomWheelModel_R = "models/hl2prewar/truck003/truck_003_r_wheel.mdl", + CustomWheelPosFL = Vector(-40,55,28), + CustomWheelPosFR = Vector(40,55,28), + CustomWheelPosRL = Vector(-45,-120,28), + CustomWheelPosRR = Vector(45,-120,28), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(0,0,20), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-20,-20,85), + SeatPitch = 10, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(23,-2,50), + ang = Angle(0,0,5) + } + }, + + ExhaustPositions = { + { + pos = Vector(-20,-20,30), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-25.29,-34.76,75), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + FrontHeight = 15, + FrontConstant = 38000, + FrontDamping = 6000, + FrontRelativeDamping = 6000, + + RearHeight = 15, + RearConstant = 40000, + RearDamping = 6200, + RearRelativeDamping = 6200, + + FastSteeringAngle = 30, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + + MaxGrip = 85, + Efficiency = 0.69, + GripOffset = -2, + BrakePower = 15, + + IdleRPM = 500, + LimitRPM = 5000, + PeakTorque = 80, + PowerbandStart = 650, + PowerbandEnd = 4700, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/jeep/jeep_idle.wav", + Sound_IdlePitch = 0.9, + + Sound_Mid = "simulated_vehicles/misc/m50.wav", + Sound_MidPitch = 0.6, + Sound_MidVolume = 5, + Sound_MidFadeOutRPMpercent = 37, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.476, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/misc/v8high2.wav", + Sound_HighPitch = 0.7, + Sound_HighVolume = 7.0, + Sound_HighFadeInRPMpercent = 30, + Sound_HighFadeInRate = 0.19, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_1.wav", + + DifferentialGear = 0.45, + Gears = {-0.1,0,0.06,0.14,0.23,0.31}, + + RadioPos = Vector(0,40,60), + RadioAng = Angle(0,-90,0), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_gaz52_v2", V ) + + + + +local V = { + Name = L.sim_fphys_pwliaz_v2, + Model = "models/octocar/truck02.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = -90, + + Members = { + Mass = 3200, + + EnginePos = Vector(0,30,51.17), + + LightsTable = "liaz", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/skoda_liaz/skoda_liaz_fwheel.mdl", + CustomWheelModel_R = "models/salza/skoda_liaz/skoda_liaz_rwheel.mdl", + CustomWheelPosFL = Vector(-44,57,25), + CustomWheelPosFR = Vector(40,57,25), + CustomWheelPosRL = Vector(-50,-98,30), + CustomWheelPosRR = Vector(47,-98,30), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(-0,40,20), + + CustomSteerAngle = 35, + + SeatOffset = Vector(40,-27,105), + SeatPitch = 10, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(27,58,60), + ang = Angle(0,0,5) + } + }, + + ExhaustPositions = { + { + pos = Vector(-40,20,28), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(-17.8,2.09,51.93), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + StrengthenSuspension = false, + + FrontHeight = 20, + FrontConstant = 35000, + FrontDamping = 6000, + FrontRelativeDamping = 6000, + + RearHeight = 20, + RearConstant = 35000, + RearDamping = 6000, + RearRelativeDamping = 6000, + + FastSteeringAngle = 30, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + + MaxGrip = 75, + Efficiency = 0.62, + GripOffset = -1, + BrakePower = 28, + + IdleRPM = 500, + LimitRPM = 5500, + PeakTorque = 85, + PowerbandStart = 650, + PowerbandEnd = 5300, + Turbocharged = false, + Supercharged = true, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "vehicles/crane/crane_startengine1.wav", + Sound_IdlePitch = 1, + + Sound_Mid = "simulated_vehicles/misc/m50.wav", + Sound_MidPitch = 0.7, + Sound_MidVolume = 5, + Sound_MidFadeOutRPMpercent = 100, + Sound_MidFadeOutRate = 1, + + Sound_High = "", + + Sound_Throttle = "", + + DifferentialGear = 0.32, + Gears = {-0.1,0,0.1,0.15,0.2,0.25,0.3}, + + RadioPos = Vector(0,115,70), + RadioAng = Angle(0,-90,0), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwliaz_v2", V ) + + + +local V = { + Name = L.sim_fphys_pwavia_v2, + Model = "models/octocar/truck01.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + -- SpawnAngleOffset = 90, + + Members = { + Mass = 2500, + + EnginePos = Vector(49.37,-2.41,44.13), + + LightsTable = "avia", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/hl2prewar/truck001/truck_001_wheel.mdl", + CustomWheelPosFL = Vector(78,37,17), + CustomWheelPosFR = Vector(78,-40,17), + CustomWheelPosRL = Vector(-55,38.5,17), + CustomWheelPosRR = Vector(-55,-37,17), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,40), + + CustomSteerAngle = 35, + + SeatOffset = Vector(55,-20,95), + SeatPitch = 15, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(79,-21,45), + ang = Angle(0,-90,0) + } + }, + + ExhaustPositions = { + { + pos = Vector(33,-22,15), + ang = Angle(-90,0,0) + }, + }, + + FuelFillPos = Vector(60,-43,50), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 70, + + FrontHeight = 8, + FrontConstant = 60000, + FrontDamping = 3500, + FrontRelativeDamping = 3500, + + RearHeight = 8, + RearConstant = 60000, + RearDamping = 3500, + RearRelativeDamping = 3500, + + FastSteeringAngle = 30, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 4, + + MaxGrip = 49, + Efficiency = 1, + GripOffset = 0.5, + BrakePower = 35, + + IdleRPM = 750, + LimitRPM = 4200, + PeakTorque = 50, + PowerbandStart = 1500, + PowerbandEnd = 3800, + Turbocharged = false, + Supercharged = false, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/alfaromeo/alfaromeo_idle.wav", + Sound_IdlePitch = 0.7, + + Sound_Mid = "simulated_vehicles/alfaromeo/alfaromeo_low.wav", + Sound_MidPitch = 0.7, + Sound_MidVolume = 2, + Sound_MidFadeOutRPMpercent = 37, -- at wich percentage of limitrpm the sound fades out + Sound_MidFadeOutRate = 0.56, --how fast it fades out 0 = instant 1 = never + + Sound_High = "simulated_vehicles/jeep/jeep_mid.wav", + Sound_HighPitch = 1.0, + Sound_HighVolume = 9.0, + Sound_HighFadeInRPMpercent = 20, + Sound_HighFadeInRate = 0.59, + + Sound_Throttle = "", -- mutes the default throttle sound + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_1.wav", + + DifferentialGear = 0.45, + Gears = {-0.12,0,0.12,0.2,0.28,0.36,0.5}, + + RadioPos = Vector(115,0,55), + RadioAng = Angle(0,180,0), + HUDPos = Vector(0, 36.5, 31.1), + HUDAng = Angle(0, 0, 65), + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwavia_v2", V ) diff --git a/garrysmod/addons/feature-cars/lua/autorun/simfphys_prewar.lua b/garrysmod/addons/feature-cars/lua/autorun/simfphys_prewar.lua new file mode 100644 index 0000000..820928a --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/autorun/simfphys_prewar.lua @@ -0,0 +1,1793 @@ +local Category = "Half Life 2 - Prewar" + +local light_table = { + L_HeadLampPos = Vector(118.8,30.5,41.8), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(118.8,-35,41.8), + R_HeadLampAng = Angle(15,0,0), + + Headlight_sprites = { + Vector(118.8,30.5,41.8), + Vector(118.8,-35,41.8) + }, + Headlamp_sprites = { + Vector(118.8,30.5,41.8), + Vector(118.8,-35,41.8) + }, + + Turnsignal_sprites = { + Left = { + Vector(116.88,36.33,53.57), + Vector(115.4,37.9,53.59), + Vector(113.67,39.87,53.72), + Vector(-100.76,20.37,18.99), + Vector(-100.71,18.55,18.97), + Vector(-100.71,16.69,19.1), + }, + Right = { + Vector(114.88,-40.33,53.57), + Vector(113.4,-41.9,53.59), + Vector(111.67,-43.87,53.72), + Vector(-100.76,-22.37,18.99), + Vector(-100.71,-20.55,18.97), + Vector(-100.71,-18.69,19.1), + }, + } +} +list.Set( "simfphys_lights", "avia", light_table) + + +local light_table = { + L_HeadLampPos = Vector(-36.87,87.86,47.32), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(36.33,87.27,46.67), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(43.03,-194,25), + L_RearLampAng = Angle(60,-90,0), + R_RearLampPos = Vector(-43.03,-194,25), + R_RearLampAng = Angle(60,-90,0), + + Headlight_sprites = { + {pos = Vector(-36.87,87.86,47.32),material = "sprites/light_ignorez",size = 64}, + {pos = Vector(-36.87,87.86,47.32),size = 75}, + + {pos = Vector(36.33,87.27,46.67),material = "sprites/light_ignorez",size = 64}, + {pos = Vector(36.33,87.27,46.67),size = 75} + }, + Headlamp_sprites = { + {pos = Vector(-36.87,87.86,47.32),size = 110}, + {pos = Vector(36.33,87.27,46.67),size = 110} + }, + Rearlight_sprites = { + Vector(43.04,-194,21.05), + Vector(43.03,-194,22.49), + Vector(43.23,-194,23.34), + Vector(43.14,-194,24.32), + + Vector(-43.04,-194,21.05), + Vector(-43.03,-194,22.49), + Vector(-43.23,-194,23.34), + Vector(-43.14,-194,24.32) + }, + Brakelight_sprites = { + Vector(43.04,-194,21.05), + Vector(43.03,-194,22.49), + Vector(43.23,-194,23.34), + Vector(43.14,-194,24.32), + + Vector(-43.04,-194,21.05), + Vector(-43.03,-194,22.49), + Vector(-43.23,-194,23.34), + Vector(-43.14,-194,24.32) + }, + FrontMarker_sprites = { + Vector(55.4,-156.48,56.9), + Vector(56.74,-70.56,55.19), + Vector(50,73.98,57.71), + Vector(-53.4,-156.48,56.9), + Vector(-54,-70.56,55.19), + Vector(-50,73.98,57.71) + }, + + Turnsignal_sprites = { + Left = { + Vector(-38.2,87.81,58.93), + Vector(-42.96,-193.67,28.29), + }, + Right = { + Vector(37.8,87.41,58.52), + Vector(43.38,-194.54,28.99), + }, + } +} +list.Set( "simfphys_lights", "gaz", light_table) + + +local light_table = { + L_HeadLampPos = Vector(71.15,23.26,27.92), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(71.07,-23.15,27.95), + R_HeadLampAng = Angle(15,0,0), + + L_RearLampPos = Vector(-72,26.5,29), + L_RearLampAng = Angle(30,185,0), + R_RearLampPos = Vector(-72,-26.5,29), + R_RearLampAng = Angle(30,175,0), + + Headlight_sprites = { + Vector(71.15,23.26,27.92), + Vector(71.07,-23.15,27.95) + }, + Headlamp_sprites = { + {pos = Vector(71.15,23.26,27.92),size = 80, color = Color( 220,205,160,50)}, + {pos = Vector(71.07,-23.15,27.95),size = 80, color = Color( 220,205,160,50)}, + }, + Rearlight_sprites = { + Vector(-72,22,29),Vector(-72,23.5,29),Vector(-72,25,29),Vector(-72,26.5,29),Vector(-72,28,29),Vector(-72,29.5,29),Vector(-72,31,29), + Vector(-72,-22,29),Vector(-72,-23.5,29),Vector(-72,-25,29),Vector(-72,-26.5,29),Vector(-72,-28,29),Vector(-72,-29.5,29),Vector(-72,-31,29), + }, + Brakelight_sprites = { + Vector(-72,22,29),Vector(-72,23.5,29),Vector(-72,25,29),Vector(-72,26.5,29),Vector(-72,28,29),Vector(-72,29.5,29),Vector(-72,31,29), + Vector(-72,-22,29),Vector(-72,-23.5,29),Vector(-72,-25,29),Vector(-72,-26.5,29),Vector(-72,-28,29),Vector(-72,-29.5,29),Vector(-72,-31,29), + }, + + Turnsignal_sprites = { + Left = { + Vector(-72.14,29.97,31.85), + Vector(-72.14,27.97,31.85), + Vector(-72.14,25.97,31.85), + Vector(72.19,24.97,20.34), + }, + Right = { + Vector(-72.54,-30.32,31.81), + Vector(-72.54,-28.32,31.81), + Vector(-72.54,-26.32,31.81), + Vector(72.19,-24.6,20.34), + }, + }, + + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(-10,7,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,10,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,13,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,16,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,-7,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,-10,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,-13,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,-16,61), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + } + }, +} +list.Set( "simfphys_lights", "golf", light_table) + +local light_table = { + L_HeadLampPos = Vector(-36.74,121.35,45.43), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(32.15,118.88,45.13), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(-47,-133.97,28.14), + L_RearLampAng = Angle(30,-90,0), + R_RearLampPos = Vector(44.13,-134.42,27.34), + R_RearLampAng = Angle(30,-90,0), + + Headlight_sprites = { + {pos = Vector(-36.74,121.35,45.43),material = "sprites/light_ignorez",size = 40}, + {pos = Vector(-36.74,121.35,45.43),size = 55}, + + {pos = Vector(32.15,118.88,45.13),material = "sprites/light_ignorez",size = 40}, + {pos = Vector(32.15,118.88,45.13),size = 55}, + }, + Headlamp_sprites = { + {pos = Vector(-36.74,121.35,45.43),size = 80}, + {pos = Vector(32.15,118.88,45.13),size = 80}, + }, + Rearlight_sprites = { + Vector(-47,-133.97,28.14), + Vector(44.13,-134.42,27.34), + }, + Reverselight_sprites = { + Vector(32.33,-134.11,27.34), + }, + + Turnsignal_sprites = { + Left = { + Vector(-39.88,119.03,66.5), + }, + Right = { + Vector(36.11,119.71,66.5), + }, + + TurnBrakeLeft = { + Vector(-47,-133.97,28.14), + }, + + TurnBrakeRight = { + Vector(44.13,-134.42,27.34), + }, + }, +} +list.Set( "simfphys_lights", "liaz", light_table) + +local light_table = { + L_HeadLampPos = Vector(75.7,28.09,31.28), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(75.7,-28.09,31.28), + R_HeadLampAng = Angle(15,0,0), + + L_RearLampPos = Vector(-99.86,23.01,29.9), + L_RearLampAng = Angle(45,180,0), + R_RearLampPos = Vector(-99.86,-23.01,29.9), + R_RearLampAng = Angle(45,180,0), + + Headlight_sprites = { + {pos = Vector(75.7,28.09,31.28),material = "sprites/light_ignorez",size = 32, color = Color( 220,205,160,255)}, + {pos = Vector(75.7,28.09,31.28),size = 64, color = Color( 220,205,160,50)}, + + {pos = Vector(75.7,-28.09,31.28),material = "sprites/light_ignorez",size = 32, color = Color( 220,205,160,255)}, + {pos = Vector(75.7,-28.09,31.28),size = 64, color = Color( 220,205,160,50)}, + }, + Headlamp_sprites = { + {pos = Vector(75.7,28.09,31.28),size = 80, color = Color( 220,205,160,80)}, + {pos = Vector(75.7,-28.09,31.28),size = 80, color = Color( 220,205,160,80)}, + }, + Rearlight_sprites = { + Vector(-99.8,21.57,29.93),Vector(-99.86,23.01,29.9),Vector(-99.75,24.52,29.92), + Vector(-99.8,-21.57,29.93),Vector(-99.86,-23.01,29.9),Vector(-99.75,-24.52,29.92), + }, + Brakelight_sprites = { + Vector(-99.8,21.57,29.93),Vector(-99.86,23.01,29.9),Vector(-99.75,24.52,29.92), + Vector(-99.8,-21.57,29.93),Vector(-99.86,-23.01,29.9),Vector(-99.75,-24.52,29.92), + }, + Reverselight_sprites = { + Vector(-99.98,27.41,30.76), + Vector(-99.98,-27.41,30.76) + }, + + Turnsignal_sprites = { + Left = { + Vector(80.52,25.03,20.21), + Vector(80.47,23.03,20.25), + Vector(-100.5,18.95,29.97), + }, + Right = { + Vector(80.52,-25.03,20.21), + Vector(80.47,-23.03,20.25), + Vector(-100.5,-18.95,29.97), + }, + }, + + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(-14,9,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,12,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,15,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,18,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,-9,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,-12,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,-15,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-14,-18,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + } + }, +} +list.Set( "simfphys_lights", "moskvich", light_table) + +local light_table = { + L_HeadLampPos = Vector(-28.77,70.69,30.73), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(29.13,70.77,30.58), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(30.83,-78.44,25), + L_RearLampAng = Angle(45,-95,0), + R_RearLampPos = Vector(-30.83,-78.50,25), + R_RearLampAng = Angle(45,-85,0), + + Headlight_sprites = { + Vector(-28.77,70.69,30.73), + Vector(29.13,70.77,30.58) + }, + Headlamp_sprites = { + {pos = Vector(-28.77,70.69,30.73),size = 80, color = Color( 220,205,160,50)}, + {pos = Vector(29.13,70.77,30.58),size = 80, color = Color( 220,205,160,50)}, + }, + Rearlight_sprites = { + Vector(30.83,-78.44,24.),Vector(30.83,-78.44,25),Vector(30.83,-78.44,26), + Vector(-30.83,-78.50,24),Vector(-30.83,-78.50,25),Vector(-30.83,-78.50,26) + }, + Brakelight_sprites = { + Vector(30.83,-78.44,24.),Vector(30.83,-78.44,25),Vector(30.83,-78.44,26), + Vector(-30.83,-78.50,24),Vector(-30.83,-78.50,25),Vector(-30.83,-78.50,26) + }, + Reverselight_sprites = { + Vector(30.77,-76.39,20.09), + Vector(-31.01,-76.14,20.29), + }, + + Turnsignal_sprites = { + Left = { + Vector(-28.5,70.97,20.41), + Vector(-30,70.97,20.41), + Vector(-31.5,70.97,20.41), + + Vector(-30.63,-79,34), + Vector(-30.63,-79.,32), + Vector(-30.63,-79,30), + Vector(-30.63,-79,28), + }, + Right = { + Vector(28.5,70.97,20.41), + Vector(30,70.97,20.41), + Vector(31.5,70.97,20.41), + + Vector(30.63,-79,34), + Vector(30.63,-79.,32), + Vector(30.63,-79,30), + Vector(30.63,-79,28), + }, + }, + + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(7,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(10,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(13,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(16,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-7,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-10,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-13,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-16,2,62.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + } + } +} +list.Set( "simfphys_lights", "trabbi", light_table) + +local light_table = { + L_HeadLampPos = Vector(97.45,36.17,37.08), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(97.23,-36.19,37.03), + R_HeadLampAng = Angle(15,0,0), + + L_RearLampPos = Vector(-117,-32.5,41), + L_RearLampAng = Angle(45,180,0), + R_RearLampPos = Vector(-117,32.5,41), + R_RearLampAng = Angle(45,180,0), + + Headlight_sprites = { + Vector(97.41,33.55,37.05),Vector(97.45,36.17,37.08),Vector(97.61,38.86,37.14), + Vector(97.25,-33.56,37.04),Vector(97.23,-36.19,37.03),Vector(97.13,-38.64,37.08) + }, + Headlamp_sprites = { + Vector(97.45,36.17,37.08), + Vector(97.23,-36.19,37.03) + }, + Rearlight_sprites = { + {pos = Vector(-117,-32.5,41),material = "sprites/light_ignorez",size = 35,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-117,-32.5,41),size = 45,color = Color( 255, 0, 0, 250)}, + + {pos = Vector(-117,32.5,41),material = "sprites/light_ignorez",size = 35,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-117,32.5,41),size = 45,color = Color( 255, 0, 0, 250)}, + }, + Reverselight_sprites = { + Vector(-117,-32.5,45),Vector(-117,-34.5,45),Vector(-117,-30.5,45), + Vector(-117,32.5,45),Vector(-117,34.5,45),Vector(-117,30.5,45) + }, + + Turnsignal_sprites = { + Left = { + Vector(96.64,36.27,27.21), + Vector(96.64,35,27.21), + }, + Right = { + Vector(96.64,-36.27,27.21), + Vector(96.64,-35,27.21), + }, + TurnBrakeLeft = { + {pos = Vector(-117,32.5,41),material = "sprites/light_ignorez",size = 50}, + {pos = Vector(-117,32.5,41),size = 55}, + }, + TurnBrakeRight = { + {pos = Vector(-117,-32.5,41),material = "sprites/light_ignorez",size = 50}, + {pos = Vector(-117,-32.5,41),size = 55}, + }, + }, + + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(30,9,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,12,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,15,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,18,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,-9,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,-12,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,-15,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + }, { + pos = Vector(30,-18,96), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + } + }, +} +list.Set( "simfphys_lights", "van", light_table) + +local light_table = { + L_HeadLampPos = Vector(91.33,30.44,30.63), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(91.33,-30.44,30.63), + R_HeadLampAng = Angle(15,0,0), + + L_RearLampPos = Vector( -102.69,29.97,34.21), + L_RearLampAng = Angle(45,180,0), + R_RearLampPos = Vector( -102.69,-29.97,34.21), + R_RearLampAng = Angle(45,180,0), + + Headlight_sprites = { + Vector(91.33,30.44,30.63), + Vector(91.33,-30.44,30.63) + }, + Headlamp_sprites = { + Vector(91.33,30.44,30.63), + Vector(91.33,-30.44,30.63) + }, + Rearlight_sprites = { + {pos = Vector(-102.23,30,35.85),material = "sprites/light_ignorez",size = 35,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-102.23,30,35.85),size = 45,color = Color( 255, 0, 0, 90)}, + + {pos = Vector(-102.23,-30,35.85),material = "sprites/light_ignorez",size = 35,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-102.23,-30,35.85),size = 45,color = Color( 255, 0, 0, 90)}, + }, + Brakelight_sprites = { + {pos = Vector(-102.23,-30,35.85),material = "sprites/light_ignorez",size = 45,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-102.23,-30,35.85),size = 50,color = Color( 255, 0, 0, 150)}, + + {pos = Vector(-102.23,30,35.85),material = "sprites/light_ignorez",size = 45,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-102.23,30,35.85),size = 50,color = Color( 255, 0, 0, 150)}, + }, + Reverselight_sprites = { + Vector(-101.8,-29.4,30.7),Vector(-101.8,-31.09,30.7), + Vector(-101.8,29.4,30.7),Vector(-101.8,31.09,30.7), + }, + Turnsignal_sprites = { + Left = { + Vector(-102.62,31,33.24), + Vector(-102.62,29,33.24), + Vector(92.09,31,22.4), + Vector(91.71,33,22.4), + }, + Right = { + Vector(-102.62,-31,33.24), + Vector(-102.62,-29,33.24), + Vector(92.09,-31,22.4), + Vector(91.71,-33,22.4), + }, + }, + + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(-5,9,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,12,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,15,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,18,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,-9,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,-12,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,-15,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-5,-18,69), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + } + }, +} +list.Set( "simfphys_lights", "volga", light_table) + +local light_table = { + L_HeadLampPos = Vector(87.3,29.59,35.42), + L_HeadLampAng = Angle(15,0,0), + R_HeadLampPos = Vector(87.34,-31.76,35.52), + R_HeadLampAng = Angle(15,0,0), + + L_RearLampPos = Vector(-95.5,22.25,32), + L_RearLampAng = Angle(45,180,0), + R_RearLampPos = Vector(-95.5,-24.75,32), + R_RearLampAng = Angle(45,180,0), + + Headlight_sprites = { + Vector(87.3,29.59,35.42), + Vector(87.34,-31.76,35.52) + }, + Headlamp_sprites = { + Vector(87.3,29.59,35.42), + Vector(87.34,-31.76,35.52) + }, + + Rearlight_sprites = { + Vector(-95.5,21,34),Vector(-95.5,21,33),Vector(-95.5,21,32),Vector(-95.5,22.25,34),Vector(-95.5,22.25,32),Vector(-95.5,23.5,34),Vector(-95.5,23.5,33),Vector(-95.5,23.5,32), + Vector(-95.5,-23.5,34),Vector(-95.5,-23.5,33),Vector(-95.5,-23.5,32),Vector(-95.5,-24.75,34),Vector(-95.5,-24.75,32),Vector(-95.5,-26,34),Vector(-95.5,-26,33),Vector(-95.5,-26,32) + }, + Brakelight_sprites = { + Vector(-95.5,15.5,34.8),Vector(-95.5,15.5,33.4),Vector(-95.5,15.5,32.6),Vector(-95.5,15.5,31.2), + Vector(-95.5,-18,34.8),Vector(-95.5,-18,33.4),Vector(-95.5,-18,32.6),Vector(-95.5,-18,31.2) + }, + Reverselight_sprites = { + Vector(-95.5,18.25,34.8),Vector(-95.5,18.25,33.4),Vector(-95.5,18.25,32.6),Vector(-95.5,18.25,31.2), + Vector(-95.5,-20.75,34.8),Vector(-95.5,-20.75,33.4),Vector(-95.5,-20.75,32.6),Vector(-95.5,-20.75,31.2) + }, + Turnsignal_sprites = { + Left = { + Vector(86.78,22.39,31.92), + Vector(-95.41,26.7,33.76), + Vector(-95.42,26.72,32.22), + }, + + Right = { + Vector(86.78,-24.39,31.92), + Vector(-95.41,-28.7,33.76), + Vector(-95.42,-28.72,32.22), + }, + }, + + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(-52,9,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,12,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,15,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,18,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,-9,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,-12,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(255,5,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,-15,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + }, { + pos = Vector(-52,-18,54.5), + material = "sprites/light_glow02_add_noz", + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,5,0,255)}, + Speed = 0.1, + } + }, +} +list.Set( "simfphys_lights", "zaz", light_table) + +local V = { + Name = "HL2 Golf", + Model = "models/blu/hatchback/pw_hatchback.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = 90, + + Members = { + Mass = 800, + + EnginePos = Vector(54.27,0,37.26), + + LightsTable = "golf", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/hatchback/hatchback_wheel.mdl", + CustomWheelPosFL = Vector(44.5,28,12), + CustomWheelPosFR = Vector(44.5,-28,12), + CustomWheelPosRL = Vector(-46,29.5,12), + CustomWheelPosRR = Vector(-46,-29.5,12), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8.5,-16,44), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-16,14), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-24,-16,14), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-24,16,14), + ang = Angle(0,-90,20) + } + }, + + FrontHeight = 6.5, + FrontConstant = 20000, + FrontDamping = 1000, + FrontRelativeDamping = 500, + + RearHeight = 6.5, + RearConstant = 20000, + RearDamping = 1000, + RearRelativeDamping = 500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 8, + + MaxGrip = 23, + Efficiency = 1, + GripOffset = -0.7, + BrakePower = 25, + + IdleRPM = 750, + LimitRPM = 6200, + PeakTorque = 75, + PowerbandStart = 1750, + PowerbandEnd = 5700, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-61.59,32.11,31.83), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/4banger/4banger_idle.wav", + + snd_low = "simulated_vehicles/4banger/4banger_low.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/4banger/4banger_mid.wav", + snd_mid_gearup = "simulated_vehicles/4banger/4banger_second.wav", + snd_mid_pitch = 0.8, + + snd_horn = "simulated_vehicles/horn_3.wav", + + DifferentialGear = 0.78, + Gears = {-0.08,0,0.08,0.18,0.26,0.33} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwhatchback", V ) + + +local V = { + Name = "HL2 Van", + Model = "models/blu/van/pw_van.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = 90, + + Members = { + Mass = 2500, + + EnginePos = Vector(89.98,0,51.3), + + LightsTable = "van", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/van/van_wheel.mdl", + CustomWheelPosFL = Vector(45,44,20), + CustomWheelPosFR = Vector(45,-44,20), + CustomWheelPosRL = Vector(-72,44,20), + CustomWheelPosRR = Vector(-72,-44,20), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,15), + + CustomSteerAngle = 35, + + SeatOffset = Vector(36,-23,72), + SeatPitch = 8, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(45,-27,33), + ang = Angle(0,-90,9) + }, + { + pos = Vector(45,0,33), + ang = Angle(0,-90,9) + }, + { + pos = Vector(-38,-29,28), + ang = Angle(0,0,0) + } + }, + + FrontHeight = 12, + FrontConstant = 45000, + FrontDamping = 3500, + FrontRelativeDamping = 3500, + + RearHeight = 12, + RearConstant = 45000, + RearDamping = 3500, + RearRelativeDamping = 3500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 350, + + TurnSpeed = 8, + + MaxGrip = 45, + Efficiency = 1.8, + GripOffset = -2, + BrakePower = 55, + + IdleRPM = 750, + LimitRPM = 6000, + PeakTorque = 95, + PowerbandStart = 1000, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-93.45,46.02,42.24), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/generic3/generic3_idle.wav", + + snd_low = "simulated_vehicles/generic3/generic3_low.wav", + snd_low_revdown = "simulated_vehicles/generic3/generic3_revdown.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/generic3/generic3_mid.wav", + snd_mid_gearup = "simulated_vehicles/generic3/generic3_second.wav", + snd_mid_pitch = 1, + + DifferentialGear = 0.52, + Gears = {-0.1,0,0.1,0.2,0.3,0.4} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwvan", V ) + + +local V = { + Name = "HL2 Moskvich", + Model = "models/blu/moskvich/moskvich.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = 90, + + Members = { + Mass = 1350, + + EnginePos = Vector(55.76,0,44.4), + + LightsTable = "moskvich", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/moskvich/moskvich_wheel.mdl", + CustomWheelPosFL = Vector(52,32,12), + CustomWheelPosFR = Vector(52,-32,12), + CustomWheelPosRL = Vector(-55,29.5,12), + CustomWheelPosRR = Vector(-55,-29.5,12), + CustomWheelAngleOffset = Angle(0,0,0), + + CustomMassCenter = Vector(0,0,2.5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-12,-16,49), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-4,-16,17.5), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-40,16,19), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-40,-16,19), + ang = Angle(0,-90,10) + } + }, + + FrontHeight = 6.5, + FrontConstant = 25000, + FrontDamping = 1500, + FrontRelativeDamping = 1500, + + RearHeight = 6.5, + RearConstant = 25000, + RearDamping = 1500, + RearRelativeDamping = 1500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 8, + + MaxGrip = 35, + Efficiency = 1, + GripOffset = -1.5, + BrakePower = 38, + + IdleRPM = 750, + LimitRPM = 6000, + PeakTorque = 100, + PowerbandStart = 1500, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-78.34,33.36,33.18), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/generic1/generic1_idle.wav", + + snd_low = "simulated_vehicles/generic1/generic1_low.wav", + snd_low_revdown = "simulated_vehicles/generic1/generic1_revdown.wav", + snd_low_pitch = 0.8, + + snd_mid = "simulated_vehicles/generic1/generic1_mid.wav", + snd_mid_gearup = "simulated_vehicles/generic1/generic1_second.wav", + snd_mid_pitch = 1.1, + + snd_horn = "simulated_vehicles/horn_5.wav", + + DifferentialGear = 0.6, + Gears = {-0.1,0,0.1,0.18,0.26,0.34,0.42} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwmoskvich", V ) + + + +local V = { + Name = "HL2 Trabant", + Model = "models/blu/trabant/trabant.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + + Members = { + Mass = 850, + + EnginePos = Vector(0.6,56.38,38.7), + + LightsTable = "trabbi", + + FirstPersonViewPos = Vector(0,-15,6), + + AirFriction = -8000, + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/trabant/trabant_wheel.mdl", + CustomWheelPosFL = Vector(-32,50,12), + CustomWheelPosFR = Vector(32,50,12), + CustomWheelPosRL = Vector(-32,-41.5,12), + CustomWheelPosRR = Vector(32,-41.5,12), + CustomWheelAngleOffset = Angle(0,0,0), + + CustomMassCenter = Vector(0,0,3), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8.5,-16,44), + SeatPitch = 0, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(16,-2,12.5), + ang = Angle(0,0,8) + }, + { + pos = Vector(0,-2,12.5), + ang = Angle(0,0,8) + } + }, + + FrontHeight = 7, + FrontConstant = 20000, + FrontDamping = 1800, + FrontRelativeDamping = 1800, + + RearHeight = 7, + RearConstant = 20000, + RearDamping = 1800, + RearRelativeDamping = 1800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 8, + + MaxGrip = 30, + Efficiency = 1.1, + GripOffset = -1, + BrakePower = 30, + + IdleRPM = 750, + LimitRPM = 7500, + PeakTorque = 85, + PowerbandStart = 2000, + PowerbandEnd = 7000, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(5.41,46.61,39.91), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = "simulated_vehicles/generic5/generic5_idle.wav", + + snd_low = "simulated_vehicles/generic5/generic5_low.wav", + snd_low_revdown = "simulated_vehicles/generic5/generic5_revdown.wav", + snd_low_pitch = 0.7, + + snd_mid = "simulated_vehicles/generic5/generic5_mid.wav", + snd_mid_gearup = "simulated_vehicles/generic5/generic5_gear.wav", + snd_mid_pitch = 0.7, + + DifferentialGear = 0.6, + Gears = {-0.1,0,0.1,0.2,0.28} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwtrabant", V ) + + + +local V = { + Name = "HL2 Trabant 2", + Model = "models/blu/trabant/trabant02.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + + Members = { + Mass = 850, + + AirFriction = -8000, + + EnginePos = Vector(0,56.38,38.7), + + FirstPersonViewPos = Vector(0,-15,6), + + LightsTable = "trabbi", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/trabant/trabant02_wheel.mdl", + CustomWheelPosFL = Vector(-32,50,12), + CustomWheelPosFR = Vector(32,50,12), + CustomWheelPosRL = Vector(-32,-41.5,12), + CustomWheelPosRR = Vector(32,-41.5,12), + CustomWheelAngleOffset = Angle(0,0,0), + + CustomMassCenter = Vector(0,0,3), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8.5,-16,44), + SeatPitch = 0, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(16,-2,12.5), + ang = Angle(0,0,8) + }, + { + pos = Vector(0,-2,12.5), + ang = Angle(0,0,8) + } + }, + + FrontHeight = 7, + FrontConstant = 20000, + FrontDamping = 1800, + FrontRelativeDamping = 1800, + + RearHeight = 7, + RearConstant = 20000, + RearDamping = 1800, + RearRelativeDamping = 1800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 8, + + MaxGrip = 30, + Efficiency = 1.1, + GripOffset = -1, + BrakePower = 30, + + IdleRPM = 750, + LimitRPM = 7500, + PeakTorque = 85, + PowerbandStart = 2000, + PowerbandEnd = 7000, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(5.41,46.61,39.91), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = "simulated_vehicles/generic5/generic5_idle.wav", + + snd_low = "simulated_vehicles/generic5/generic5_low.wav", + snd_low_revdown = "simulated_vehicles/generic5/generic5_revdown.wav", + snd_low_pitch = 0.7, + + snd_mid = "simulated_vehicles/generic5/generic5_mid.wav", + snd_mid_gearup = "simulated_vehicles/generic5/generic5_gear.wav", + snd_mid_pitch = 0.7, + + DifferentialGear = 0.6, + Gears = {-0.1,0,0.1,0.2,0.28} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwtrabant02", V ) + + +local V = { + Name = "HL2 Volga", + Model = "models/blu/volga/volga.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = 90, + + Members = { + Mass = 1350, + + EnginePos = Vector(65.39,0,44.84), + + LightsTable = "volga", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/volga/volga_wheel.mdl", + CustomWheelPosFL = Vector(64,34,13), + CustomWheelPosFR = Vector(64,-34,13), + CustomWheelPosRL = Vector(-55,34,13), + CustomWheelPosRR = Vector(-55,-34,13), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,3.5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-4,-17.5,52), + SeatPitch = 5, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-17.5,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(6,0,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,-17.5,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,17.5,18.5), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,-0,18.5), + ang = Angle(0,-90,12) + } + }, + + FrontHeight = 6.5, + FrontConstant = 25000, + FrontDamping = 1300, + FrontRelativeDamping = 1300, + + RearHeight = 6.5, + RearConstant = 25000, + RearDamping = 1300, + RearRelativeDamping = 1300, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 8, + + MaxGrip = 35, + Efficiency = 1, + GripOffset = -1.5, + BrakePower = 38, + + IdleRPM = 750, + LimitRPM = 6000, + PeakTorque = 100, + PowerbandStart = 1500, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-80.3,37.79,35.54), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/generic2/generic2_idle.wav", + + snd_low = "simulated_vehicles/generic2/generic2_low.wav", + snd_low_revdown = "simulated_vehicles/generic2/generic2_revdown.wav", + snd_low_pitch = 1, + + snd_mid = "simulated_vehicles/generic2/generic2_mid.wav", + snd_mid_gearup = "simulated_vehicles/generic2/generic2_second.wav", + snd_mid_pitch = 1.1, + + snd_horn = "simulated_vehicles/horn_5.wav", + + DifferentialGear = 0.62, + Gears = {-0.1,0,0.1,0.18,0.26,0.31,0.38} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwvolga", V ) + + +local V = { + Name = "HL2 ZAZ", + Model = "models/blu/zaz/zaz.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = 90, + + Members = { + Mass = 1150, + + EnginePos = Vector(63.64,0,47.96), + + LightsTable = "zaz", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/zaz/zaz_wheel.mdl", + CustomWheelPosFL = Vector(61,32,17), + CustomWheelPosFR = Vector(61,-34,17), + CustomWheelPosRL = Vector(-53,32,17), + CustomWheelPosRR = Vector(-53,-34,17), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,3.5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-17.5,54), + SeatPitch = 5, + SeatYaw = 90, + + --[[ + ModelInfo = { + Skin = 1 + }, + ]]-- + + PassengerSeats = { + { + pos = Vector(6,-17.5,20), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,-17.5,24), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,17.5,24), + ang = Angle(0,-90,12) + }, + { + pos = Vector(-30,0,24), + ang = Angle(0,-90,12) + } + }, + + FrontHeight = 6.5, + FrontConstant = 25000, + FrontDamping = 1300, + FrontRelativeDamping = 1300, + + RearHeight = 6.5, + RearConstant = 25000, + RearDamping = 1300, + RearRelativeDamping = 1300, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 8, + + MaxGrip = 35, + Efficiency = 1, + GripOffset = -1.5, + BrakePower = 38, + + IdleRPM = 750, + LimitRPM = 7250, + PeakTorque = 60, + PowerbandStart = 2000, + PowerbandEnd = 6950, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-67.9,-37.75,38.59), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/generic3/generic3_idle.wav", + + snd_low = "simulated_vehicles/generic3/generic3_low.wav", + snd_low_revdown = "simulated_vehicles/generic3/generic3_revdown.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/generic3/generic3_mid.wav", + snd_mid_gearup = "simulated_vehicles/generic3/generic3_second.wav", + snd_mid_pitch = 0.9, + + DifferentialGear = 0.42, + Gears = {-0.1,0,0.1,0.17,0.24,0.3,0.37,0.41} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwzaz", V ) + + +local V = { + Name = "HL2 GAZ52", + Model = "models/blu/gaz52/gaz52.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + + Members = { + Mass = 3000, + + EnginePos = Vector(0,61.23,76.81), + + LightsTable = "gaz", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/gaz52/gaz52_wheel.mdl", + CustomWheelPosFL = Vector(-40,55,25), + CustomWheelPosFR = Vector(40,55,25), + CustomWheelPosRL = Vector(-45,-120,25), + CustomWheelPosRR = Vector(45,-120,25), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(0,0,18), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-20,-23,85), + SeatPitch = 10, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(23,-2,50), + ang = Angle(0,0,5) + } + }, + + FrontHeight = 8, + FrontConstant = 38000, + FrontDamping = 6000, + FrontRelativeDamping = 6000, + + RearHeight = 12.5, + RearConstant = 38000, + RearDamping = 6000, + RearRelativeDamping = 6000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 8, + + MaxGrip = 85, + Efficiency = 1.2, + GripOffset = -12, + BrakePower = 80, + + IdleRPM = 500, + LimitRPM = 5000, + PeakTorque = 150, + PowerbandStart = 650, + PowerbandEnd = 4700, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-25.29,-34.76,50), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 140, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "vehicles/crane/crane_startengine1.wav", + + snd_low ="simulated_vehicles/alfaromeo/alfaromeo_low.wav", + snd_low_pitch = 0.35, + + snd_mid = "simulated_vehicles/alfaromeo/alfaromeo_mid.wav", + snd_mid_gearup = "simulated_vehicles/alfaromeo/alfaromeo_second.wav", + snd_mid_pitch = 0.5, + + DifferentialGear = 0.25, + Gears = {-0.1,0,0.1,0.19,0.29,0.37,0.44,0.5,0.57} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwgaz52", V ) + + + + +local V = { + Name = "HL2 Liaz", + Model = "models/blu/skoda_liaz/skoda_liaz.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + + Members = { + Mass = 3000, + + EnginePos = Vector(-1.75,-0.56,51.17), + + LightsTable = "liaz", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + FirstPersonViewPos = Vector(0,-10,12), + + CustomWheelModel = "models/salza/skoda_liaz/skoda_liaz_fwheel.mdl", + CustomWheelModel_R = "models/salza/skoda_liaz/skoda_liaz_rwheel.mdl", + CustomWheelPosFL = Vector(-44,57,25), + CustomWheelPosFR = Vector(40,57,25), + CustomWheelPosRL = Vector(-50,-98,25), + CustomWheelPosRR = Vector(47,-98,25), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(0,30,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(40,-27,100), + SeatPitch = 10, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(27,58,60), + ang = Angle(0,0,5) + } + }, + + StrengthenSuspension = false, + + FrontHeight = 16, + FrontConstant = 32000, + FrontDamping = 4000, + FrontRelativeDamping = 4000, + + RearHeight = 13.5, + RearConstant = 20000, + RearDamping = 3000, + RearRelativeDamping = 2000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 8, + + MaxGrip = 75, + Efficiency = 2, + GripOffset = -5, + BrakePower = 80, + + IdleRPM = 500, + LimitRPM = 5500, + PeakTorque = 55, + PowerbandStart = 650, + PowerbandEnd = 5300, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-17.8,2.09,51.93), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 140, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = "vehicles/crane/crane_startengine1.wav", + Sound_IdlePitch = 1, + + Sound_Mid = "simulated_vehicles/alfaromeo/alfaromeo_low.wav", + Sound_MidPitch = 0.5, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 100, + Sound_MidFadeOutRate = 1, + + Sound_High = "", + + Sound_Throttle = "", + + DifferentialGear = 0.22, + Gears = {-0.1,0,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.5} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwliaz", V ) + + + +local V = { + Name = "HL2 avia", + Model = "models/blu/avia/avia.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = Category, + SpawnAngleOffset = 90, + + Members = { + Mass = 2500, + + EnginePos = Vector(49.37,-2.41,44.13), + + LightsTable = "avia", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/salza/avia/avia_wheel.mdl", + CustomWheelPosFL = Vector(78,37,17), + CustomWheelPosFR = Vector(78,-40,17), + CustomWheelPosRL = Vector(-55,38.5,17), + CustomWheelPosRR = Vector(-55,-37,17), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(55,-20,95), + SeatPitch = 15, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(79,-21,45), + ang = Angle(0,-90,0) + } + }, + + FrontHeight = 8, + FrontConstant = 40000, + FrontDamping = 3500, + FrontRelativeDamping = 3500, + + RearHeight = 8, + RearConstant = 40000, + RearDamping = 3500, + RearRelativeDamping = 3500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 49, + Efficiency = 1.1, + GripOffset = -2, + BrakePower = 45, + + IdleRPM = 750, + LimitRPM = 4200, + PeakTorque = 160, + PowerbandStart = 1500, + PowerbandEnd = 3800, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(9.79,35.14,30.77), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/jeep/jeep_idle.wav", + + snd_low = "simulated_vehicles/jeep/jeep_low.wav", + snd_low_revdown = "simulated_vehicles/jeep/jeep_revdown.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/jeep/jeep_mid.wav", + snd_mid_gearup = "simulated_vehicles/jeep/jeep_second.wav", + snd_mid_pitch = 1, + + DifferentialGear = 0.45, + Gears = {-0.15,0,0.15,0.25,0.35,0.45,0.52} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_pwavia", V ) diff --git a/garrysmod/addons/feature-cars/lua/autorun/spawnlist_jettancapricepol.lua b/garrysmod/addons/feature-cars/lua/autorun/spawnlist_jettancapricepol.lua new file mode 100644 index 0000000..448c64d --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/autorun/spawnlist_jettancapricepol.lua @@ -0,0 +1,271 @@ +AddCSLuaFile() +local light_table = { + L_HeadLampPos = Vector(26,113,30), + L_HeadLampAng = Angle(18,90,0), + + R_HeadLampPos = Vector(-26,113,30), + R_HeadLampAng = Angle(18,90,0), + + L_RearLampPos = Vector(26,-113,30), + L_RearLampAng = Angle(0,270,0), + + R_RearLampPos = Vector(-26,-113,30), + R_RearLampAng = Angle(0,270,0), + + Headlight_sprites = { + Vector(26,113,30), + Vector(-26,113,30), + }, + Headlamp_sprites = { + Vector(33,113,30), + Vector(-33,113,30), + }, + Rearlight_sprites = { + Vector(-26,-114,30), + Vector(26,-114,30), + Vector(-35,-114,30), + Vector(35,-114,30), + Vector(-17,-114,30), + Vector(17,-114,30), + Vector(-26,-114,28), + Vector(26,-114,28), + Vector(-35,-114,28), + Vector(35,-114,28), + Vector(-17,-114,28), + Vector(17,-114,28), + }, + Brakelight_sprites = { + Vector(-26,-114,30), + Vector(26,-114,30), + Vector(-35,-114,30), + Vector(35,-114,30), + Vector(-17,-114,30), + Vector(17,-114,30), + Vector(-26,-114,28), + Vector(26,-114,28), + Vector(-35,-114,28), + Vector(35,-114,28), + Vector(-17,-114,28), + Vector(17,-114,28), + Vector(-26,-114,30), + Vector(26,-114,30), + Vector(-35,-114,30), + Vector(35,-114,30), + Vector(-17,-114,30), + Vector(17,-114,30), + Vector(-26,-114,28), + Vector(26,-114,28), + Vector(-35,-114,28), + Vector(35,-114,28), + Vector(-17,-114,28), + Vector(17,-114,28), + }, + Reverselight_sprites = { + Vector(-10,-114,28), + Vector(10,-114,28), + }, + ems_sounds = {"simulated_vehicles/police/siren_1.wav","simulated_vehicles/police/siren_2.wav"}, + ems_sprites = { + { + pos = Vector(9,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(17,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(24,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(30,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,5,255,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(-9,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(-17,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(-24,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(-30,3,70), + material = "sprites/light_glow02_add_noz", + size = 100, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,0,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(-28,0,70), + material = "sprites/light_glow02_add_noz", + size = 45, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(255,180,0,255)}, + Speed = 0.07, + }, + { + pos = Vector(28,0,70), + material = "sprites/light_glow02_add_noz", + size = 45, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(255,180,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.07, + }, + } +} +list.Set( "simfphys_lights", "polcaprice", light_table) +local V = { + Name = "1989 Caprice Police", + Model = "models/sentry/caprice.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Jettans Cars", + + Members = { + Mass = 1800, + FrontWheelMass = 70, + RearWheelMass = 70, + + LightsTable = "polcaprice", + + FrontWheelRadius = 15, + RearWheelRadius = 15, + + CustomMassCenter = Vector(0,0,0), + SeatOffset = Vector(-2,1,-5), + SeatPitch = 0, + + FuelFillPos = Vector(38,-80.3,35.54), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 75, + + SpeedoMax = 200, + ModelInfo = { + Bodygroups = {0,0,0,0,0,0}, + Color = Color(255,255,155,255), + Skin = 0, + WheelColor = Color(255,255,255,255) + }, + PassengerSeats = { + { + pos = Vector(19,6,15), + ang = Angle(0,0,15) + }, { + pos = Vector(-19,-29,15), + ang = Angle(0,0,19) + }, { + pos = Vector(0,-29,15), + ang = Angle(0,0,19) + }, { + pos = Vector(19,-29,15), + ang = Angle(0,0,15) + } + }, + + Backfire = f, + ExhaustPositions = { + { + pos = Vector(28,-112,16), + ang = Angle(90,-90,0), + OnBodyGroups = { [4] = {0,2} } + } + }, + + StrengthenSuspension = true, + FrontHeight = 16, + FrontConstant = 27000, + FrontDamping = 1500, + FrontRelativeDamping = 800, + + RearHeight = 16, + RearConstant = 32000, + RearDamping = 1500, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 5, + + MaxGrip = 35, + Efficiency = 1, + GripOffset = 0.05, + BrakePower = 15, + + IdleRPM = 750, + LimitRPM = 5500, + + PeakTorque = 90, + PowerbandStart = 2000, + PowerbandEnd = 6000, + + Turbocharged = false, + Supercharged = false, + PowerBias = 1, + + EngineSoundPreset = 1, + + snd_pitch = 1, + snd_idle = "vehicles/sgmcars/caprice/idle.wav", + + snd_low = "simulated_vehicles/jeep/jeep_low.wav", + + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/jeep/jeep_mid.wav", + snd_mid_gearup = "vehicles/sgmcars/caprice/second.wav", + + snd_mid_pitch = 1, + + Sound_Idle = "simulated_vehicles/misc/nanjing_loop.wav", + Sound_IdlePitch = 1, + + Sound_Mid = "simulated_vehicles/misc/m50.wav", + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 58, + Sound_MidFadeOutRate = 0.476, + + Sound_High = "simulated_vehicles/misc/v8high2.wav", + Sound_HighPitch = 1, + Sound_HighVolume = 0.75, + Sound_HighFadeInRPMpercent = 58, + Sound_HighFadeInRate = 0.19, + + Sound_Throttle = "", + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + + snd_horn = "simulated_vehicles/horn_3.wav", + + DifferentialGear = 0.7, + Gears = {-0.1,0,0.1,0.18,0.25,0.31,0.4,0.52} + + } +} +list.Set( "simfphys_vehicles", "sim_fphys_polcaprice", V ) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/cl_colors.lua b/garrysmod/addons/feature-cars/lua/car-dealer/cl_colors.lua new file mode 100644 index 0000000..a37eef4 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/cl_colors.lua @@ -0,0 +1,50 @@ +netstream.Hook('car-dealer.firstColor', function(id, colors) + if IsValid(carDealer.firstColorPanel) then + carDealer.firstColorPanel:Remove() + end + + local o, o2 = octolib.overlay(nil, 'DPanel', true) + o:SetSize(306, 250) + o2:MakePopup() + carDealer.firstColorPanel = o + + local lbl = octolib.label(o, 'Поздравляем с покупкой! Этот автомобиль доступен в нескольких цветах. Выбери тот, который подходит тебе больше всего:') + lbl:DockMargin(10, 10, 10, 10) + lbl:SetAutoStretchVertical(true) + lbl:SetMultiline(true) + lbl:SetWrap(true) + + local grid = o:Add('DIconLayout') + grid:Dock(FILL) + grid:DockMargin(10, 5, 10, 0) + grid:SetSpaceX(10) + grid:SetSpaceY(10) + + local function paintFunc(self, w, h) + draw.NoTexture() + surface.SetDrawColor(self.col.r, self.col.g, self.col.b) + draw.Circle(w/2, h/2, 32, 64) + end + local function clickFunc(self) + netstream.Start('car-dealer.firstColor', id, self.index) + o:Remove() + end + + for i, v in ipairs(colors) do + local pan = grid:Add('DButton') + pan:SetSize(64, 64) + pan:SetText('') + pan.col = Color(v[2], v[3], v[4]) + pan.index = i + pan.Paint, pan.DoClick = paintFunc, clickFunc + pan:AddHint(v[1]) + end + + lbl = octolib.label(o, 'В любой момент ты сможешь перекрасить за деньги автомобиль у механика. Эта покраска бесплатная') + lbl:Dock(BOTTOM) + lbl:DockMargin(10, 10, 10, 10) + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetAutoStretchVertical(true) + +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/cl_menu.lua b/garrysmod/addons/feature-cars/lua/car-dealer/cl_menu.lua new file mode 100644 index 0000000..b125505 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/cl_menu.lua @@ -0,0 +1,294 @@ +carDealer.enabled = true +carDealer.cache = carDealer.cache or { + owned = {}, + categories = {}, +} + +local function attachWheel(ent, mdlOrig, mdl, pos, ang, radius) + + local w = octolib.createDummy(mdlOrig) + w:SetParent(ent) + w:SetLocalPos(pos) + w:SetLocalAngles(ang) + w:SetNoDraw(true) + ent.wheels[#ent.wheels + 1] = w + + if mdl ~= mdlOrig then + local mins, maxs = w:GetRenderBounds() + local origwheelsize = maxs - mins + if not radius then + radius = math.max(origwheelsize.x, origwheelsize.y, origwheelsize.z) * 0.5 + end + + w:SetModel(mdl) + local mins, maxs = w:GetRenderBounds() + local wheelsize = maxs - mins + local size = (radius * 2) / math.max(wheelsize.x, wheelsize.y, wheelsize.z) + w:SetModelScale(size) + end + +end + +local function wheelAngle(members, data, model, right) + local Forward = Vector(0, 1, 0) + local Right = Vector(right and 1 or -1, 0, 0) + local Up = Vector(0, 0, 1) + if members.SpawnAngleOffset then + local ang = Angle(0, members.SpawnAngleOffset, 0) + Forward:Rotate(ang) + Right:Rotate(ang) + Up:Rotate(ang) + end + + local angleoffset = members.CustomWheelAngleOffset + local mirAng = right and 1 or -1 + local ang = Right:Angle() + ang:RotateAroundAxis(Forward, angleoffset.p * mirAng) + ang:RotateAroundAxis(Right, angleoffset.r * mirAng) + ang:RotateAroundAxis(Up, 90) + ang:RotateAroundAxis(Forward, (data.camber or 0) * mirAng) + + local add = simfphys.GetWheelAngle(model) + if add then + ang:RotateAroundAxis(Forward, add.p * mirAng) + ang:RotateAroundAxis(Right, add.r * mirAng) + ang:RotateAroundAxis(Up, -add.y) + end + + return ang +end + +function carDealer.attachWheels(ent, vehID, data) + + data = data or {} + local cdData = carDealer.vehicles[vehID] + if not cdData then return end + + local spData = list.Get('simfphys_vehicles')[cdData.simfphysID] + assert(spData ~= nil, 'Wrong simfphysID for ' .. vehID) + + ent.wheels = {} + local m = spData.Members + local mdlOrigF, mdlOrigR = m.CustomWheelModel, m.CustomWheelModel_R or m.CustomWheelModel + local mdlF, mdlR = + data.rims and data.rims[1] or m.CustomWheelModel, + data.rims and data.rims[2] or m.CustomWheelModel_R or m.CustomWheelModel + + if m.CustomWheelPosFL then + attachWheel(ent, mdlOrigF, mdlF, m.CustomWheelPosFL - Vector(0, 0, m.FrontHeight / 4), wheelAngle(m, data, mdlF, false), m.FrontWheelRadius) + end + if m.CustomWheelPosFR then + attachWheel(ent, mdlOrigF, mdlF, m.CustomWheelPosFR - Vector(0, 0, m.FrontHeight / 4), wheelAngle(m, data, mdlF, true), m.FrontWheelRadius) + end + if m.CustomWheelPosRL then + attachWheel(ent, mdlOrigR, mdlR, m.CustomWheelPosRL - Vector(0, 0, m.RearHeight / 4), wheelAngle(m, data, mdlR, false), m.RearWheelRadius) + end + if m.CustomWheelPosRR then + attachWheel(ent, mdlOrigR, mdlR, m.CustomWheelPosRR - Vector(0, 0, m.RearHeight / 4), wheelAngle(m, data, mdlR, true), m.RearWheelRadius) + end + + ent:CallOnRemove('DeleteWheels', function(ent) + if not ent.wheels then return end + for _, w in ipairs(ent.wheels) do + if IsValid(w) and w ~= NULL then w:Remove() end + end + end) + +end + +function carDealer.attachAccessories(ent, atts) + + ent.atts = {} + + for _, data in pairs(atts) do + local attEnt = octolib.createDummy(data.model, data.rg) + data.parent = ent + octolib.applyEntData(attEnt, data) + attEnt.col = data.col + attEnt:SetNoDraw(true) + ent.atts[#ent.atts + 1] = attEnt + end + + ent:CallOnRemove('DeleteAttachments', function(ent) + if not ent.atts then return end + for _, a in ipairs(ent.atts) do + if IsValid(a) and a ~= NULL then a:Remove() end + end + end) + +end + +netstream.Hook('car-dealer.sync', function(owned, categories) + for id, veh in pairs(owned) do veh.id = id end + carDealer.cache = { + owned = owned, + categories = categories, + } + + hook.Run('car-dealer.sync', carDealer.cache) +end) + +hook.Add('InitPostEntity', 'car-dealer', function() + + local atts = simfphys.attachments + carDealer.canAttach = function(ent, att) + if not att then return false end + if not IsValid(ent) then return false end + if not ent.vehicleName or not atts[att].cars[ent.vehicleName] then return false end + + local t = atts[att].type + if not force and ent.atts and IsValid(ent.atts[t]) and ent.atts[t]:GetModel() == atts[att].mdl then + return false + end + + return true + end + carDealer.removeAttachment = function(ent, type) + if ent.atts and IsValid(ent.atts[type]) and ent.atts[type] ~= NULL then + ent.atts[type]:Remove() + ent.atts[type] = nil + end + end + carDealer.addAttachment = function(ent, att) + if not carDealer.canAttach(ent, att) then return end + + local t = atts[att].type + carDealer.removeAttachment(ent, t) + + local attData = atts[att].cars[ent.vehicleName] + local attEnt = octolib.createDummy(atts[att].mdl) + attEnt:SetParent(ent) + attEnt:SetLocalPos(attData[1] or Vector()) + attEnt:SetLocalAngles(attData[2] or Angle()) + attEnt:SetModelScale(attData[3] or 1) + attEnt:SetSkin(atts[att].skin or 0) + attEnt.attClass = att + attEnt.noPaint = atts[att].noPaint + if not attEnt.noPaint then attEnt:SetColor(ent:GetColor()) end + attEnt:SetNoDraw(true) + + ent.atts = ent.atts or {} + ent.atts[t] = attEnt + end + +end) + +hook.Add('octogui.f4-tabs', 'car-dealer', function() + + octogui.addToF4({ + order = 11.6, + id = 'dealer', + name = 'Гараж', + icon = Material('octoteam/icons/car2.png'), + build = function(f) + f:SetSize(800, 600) + f:DockPadding(0, 24, 0, 0) + f:Center() + + local br = f:Add 'DButton' + br:SetPos(700, 2) + br:SetSize(60, 20) + br:SetText('Обновить') + function br:DoClick() + netstream.Start('car-dealer.sync') + end + + f:Add 'cd_menu' + netstream.Start('car-dealer.sync') + end, + show = function(f, st) + carDealer.menu:SetMinimized(not st) + end + }) + +end) + +surface.CreateFont('car-dealer.plate', { + font = 'License Plate', + size = 64, + weight = 500, + antialias = true, + extended = true, +}) + +local function roundVector(pos, decimals) + local float = '%.' .. decimals .. 'f' + return table.concat({'(',float,', ',float,', ',float,')'}, ''):format(pos.x, pos.y, pos.z) +end +local function roundAngle(ang, decimals) + local float = '%.' .. decimals .. 'f' + return table.concat({'[',float,', ',float,', ',float,']'}, ''):format(ang.p, ang.y, ang.r) +end + +hook.Add('octolib.configLoaded', 'car-dealer', function() + if not CFG.dev then return end + + concommand.Add('car_setup', function() + local seat = LocalPlayer():GetVehicle() + local car = IsValid(seat) and seat:GetParent() + if not IsValid(car) then return octolib.notify.show('warning', 'Нужно находиться в автомобиле') end + + local radius = car:GetModelRadius() + octolib.flyEditor.start({ + parent = car, + props = { + { + name = 'Передний номер', + model = 'models/octoteam/vehicles/attachments/licenceplate_01.mdl', + pos = Vector(radius, 0, 0), + ang = Angle(0, 0, 0), + }, { + name = 'Задний номер', + model = 'models/octoteam/vehicles/attachments/licenceplate_01.mdl', + pos = Vector(-radius, 0, 0), + ang = Angle(0, 180, 0), + }, { + name = 'Приборная панель', + model = 'models/props_c17/gravestone_coffinpiece001a.mdl', + pos = Vector(0, radius / 8, radius / 8), + ang = Angle(0, -90, 90), + size = Vector(0.096, 0.07, 0.0015), + }, { + name = 'Зеркало в салоне', + model = 'models/props/cs_italy/orange.mdl', + pos = Vector(radius / 2, 0, radius / 4), + ang = Angle(0, 0, 0), + }, { + name = 'Левое зеркало', + model = 'models/props/cs_italy/orange.mdl', + pos = Vector(0, radius / 2, 0), + ang = Angle(0, 0, 0), + }, { + name = 'Правое зеркало', + model = 'models/props/cs_italy/orange.mdl', + pos = Vector(0, -radius / 2, 0), + ang = Angle(0, 0, 0), + }, { + name = 'Радио', + model = 'models/props_lab/reciever01d.mdl', + pos = Vector(0, 0, 0), + ang = Angle(0, 180, 0), + }, + }, + + space = octolib.flyEditor.SPACE_PARENT, + vPos = vPos, + vAng = vAng, + maxDist = 500, + anchorEnt = car, + noCopy = true, + noRemove = true, + }, function(data) + local fPlate, rPlate, dash, mMirror, lMirror, rMirror, radio = unpack(data) + chat.AddText('---') + chat.AddText('Автомобиль: ' .. car:GetSpawn_List()) + chat.AddText('Передний номер: ' .. roundVector(fPlate.pos, 3) .. roundAngle(fPlate.ang, 1)) + chat.AddText('Задний номер: ' .. roundVector(rPlate.pos, 3) .. roundAngle(rPlate.ang, 1)) + chat.AddText('Приборная панель: ' .. roundVector(dash.pos, 3) .. roundAngle(dash.ang, 1)) + chat.AddText('Зеркало в салоне: ' .. roundVector(mMirror.pos, 3)) + chat.AddText('Левое зеркало: ' .. roundVector(lMirror.pos, 3)) + chat.AddText('Правое зеркало: ' .. roundVector(rMirror.pos, 3)) + chat.AddText('Радио: ' .. roundVector(radio.pos, 3) .. roundAngle(radio.ang, 1)) + end) + end) +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/cl_refund.lua b/garrysmod/addons/feature-cars/lua/car-dealer/cl_refund.lua new file mode 100644 index 0000000..9fa6a2e --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/cl_refund.lua @@ -0,0 +1,114 @@ +local function category(cont, name) + local cat = cont:Add('DCollapsibleCategory') + cat:Dock(TOP) + cat:SetTall(100) + cat:SetExpanded(0) + cat:SetLabel(name) + local layout = vgui.Create('DListLayout') + layout:SetSize(100, 100) + layout:DockPadding(5, 5, 5, 5) + layout:SetPaintBackground(true) + cat:SetContents(layout) + return cat, layout +end + +local function point(cont, title, pr) + + local wrap = cont:Add('DPanel') + wrap:Dock(TOP) + wrap:SetTall(30) + wrap:SetDrawBackground(false) + + local key = wrap:Add('DLabel') + key:Dock(LEFT) + key:SetFont('f4.normal') + key:SetText(title) + key:SizeToContentsX() + + local value = wrap:Add('DLabel') + value:Dock(FILL) + value:SetFont('f4.normal') + value:SetText(DarkRP.formatMoney(pr or 0)) + value:SetContentAlignment(6) +end + +local function openFrame(data) + + if not octolib.label then + timer.Simple(1, function() + openFrame(data) + end) + return + end + + local fr = vgui.Create('DFrame') + fr:SetSize(400, 600) + fr:SetTitle('Новый автопарк') + fr:Center() + fr:MakePopup() + fr.btnClose:SetVisible(false) + function fr:OnClose() + netstream.Start('cd.refundOld') + end + + fr:SetSizable(true) + fr:SetMinHeight(250) + function fr:OnSizeChanged(w, h) + if w ~= 400 then + self:SetWide(400) + end + end + + local e = octolib.label(fr, 'Привет!') + e:DockMargin(5, 0, 0, 0) + e:SetFont('f4.normal') + e:SetTall(35) + + local e = octolib.label(fr, [[Привет! У нас обновился автопарк, и у тебя в гараже остались старые автомобили. Они будут проданы, а аксессуары возвращены через магазин. Вот подробная информация:]]) + e:SetWrap(true) + e:SetMultiline(true) + e:SetTall(70) + e:DockMargin(5,0,5,0) + + local scr = fr:Add('DScrollPanel') + scr:Dock(FILL) + scr:DockMargin(0, 5, 0, 5) + + local total, sum, cat, layout = 0, 0 + for _, pt in ipairs(data) do + + if not pt[1] then + local div = layout:Add('DVerticalDivider') + div:Dock(TOP) + div:DockMargin(0, 2, 0, 2) + div:SetTall(2) + function div:Paint(w, h) + local pos = 0 + local seg = w * 0.0125 + while pos < w do + draw.RoundedBox(0, pos, 0, seg, h, Color(69,51,69)) + pos = pos + seg*2 + end + end + point(layout, 'Итого', sum) + elseif not pt[2] then + cat, layout = category(scr, pt[1]) + sum = 0 + else + point(layout, pt[1], pt[2]) + sum = sum + pt[2] + total = total + pt[2] + end + + end + + local butClose = fr:Add 'DButton' + butClose:Dock(BOTTOM) + butClose:SetTall(30) + butClose:SetText('Получить ' .. DarkRP.formatMoney(total)) + function butClose:DoClick() + fr:Close() + end +end + +netstream.Hook('cd.refundOld', openFrame) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/cl_test.lua b/garrysmod/addons/feature-cars/lua/car-dealer/cl_test.lua new file mode 100644 index 0000000..f919d7c --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/cl_test.lua @@ -0,0 +1,28 @@ +netstream.Hook('followVehicles', function(vehs, dir) + hook.Add('CalcView', 'followVehicles', function(ply, pos, ang, fov) + + local pos = Vector() + for i = #vehs, 1, -1 do + local veh = vehs[i] + if IsValid(veh) then + pos = pos + veh:GetPos() + else + table.remove(vehs, i) + end + end + + if #vehs <= 0 then return hook.Remove('CalcView', 'followVehicles') end + + pos = pos / #vehs - dir * 50 + Vector(0, 0, 400) + + local ang = dir:Angle() + ang:RotateAroundAxis(ang:Right(), -80) + + return { + origin = pos, + angles = ang, + fov = 110, + } + + end) +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sh_dealer.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sh_dealer.lua new file mode 100644 index 0000000..20e6282 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sh_dealer.lua @@ -0,0 +1,53 @@ +carDealer = carDealer or {} +carDealer.categories = carDealer.categories or {} +carDealer.vehicles = carDealer.vehicles or {} + +function carDealer.addCategory(id, data) + + carDealer.categories[id] = data + carDealer.lastCategory = id + +end + +function carDealer.addVeh(id, data) + + if not data.price then data.price = 0 end + if not data.category then data.category = carDealer.lastCategory or 'main' end + + carDealer.vehicles[id] = data + + if CLIENT then + local spData = list.Get('simfphys_vehicles')[data.simfphysID] + if spData and spData.Model then util.PrecacheModel(spData.Model) end + end + +end + +function carDealer.getCurVeh(ply) + + if not IsValid(ply) then return end + + -- use table because 'pon.entityCreated' + local vehTbl = ply:GetNetVar('cd.vehicle') + if istable(vehTbl) then + return ply:GetNetVar('cd.vehicle')[1] + end + +end + +function carDealer.limitedSpawn(max, limitGroup, msg) + + return function(ply, class) + local count = 0 + for _, v in ipairs(ents.FindByClass('gmod_sent_vehicle_fphysics_base')) do + local car = v.cdClass and carDealer.vehicles[v.cdClass] + local category = car and carDealer.categories[car.category] + if category and category.limitGroup == limitGroup then + count = count + 1 + if count >= max then return false, msg end + end + end + return true + end + +end diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_colors.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_colors.lua new file mode 100644 index 0000000..dbc973a --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_colors.lua @@ -0,0 +1,1066 @@ +carDealer.waitingColors = carDealer.waitingColors or {} +local waiting = carDealer.waitingColors + +hook.Add('car-dealer.bought', 'car-dealer.firstColor', function(class, ply, price, id) + local cdData = carDealer.vehicles[class] + if not cdData or cdData.default and cdData.default.col then return end + + local colors = {} + colors[1] = {'Белый', 255, 255, 255} + for _, v in ipairs(carDealer.carColorBounds) do + colors[#colors + 1] = carDealer.defaultCarColors[math.random(unpack(v))] + end + waiting[ply:SteamID()] = {id, colors} + netstream.Start(ply, 'car-dealer.firstColor', id, colors) +end) + +hook.Add('PlayerDisconnected', 'car-dealer.firstColor', function(ply) + waiting[ply:SteamID()] = nil +end) + +netstream.Hook('car-dealer.firstColor', function(ply, id, colorNum) + if not (isnumber(id) and isnumber(colorNum)) then return end + local sid = ply:SteamID() + local asked = waiting[sid] + if not (istable(asked) and asked[1] == id and octolib.math.inRange(colorNum, 1, #asked[2])) then return end + waiting[sid] = nil + if colorNum == 1 then return end -- we don't need to change color to white + local colData = asked[2][colorNum] + local col = Color(colData[2], colData[3], colData[4]) + carDealer.updateVehData(id, { col = { col, col, Color(0,0,0), col } }, function() + if IsValid(ply) then + ply:Notify('Твой автомобиль был покрашен в ' .. utf8.lower(colData[1]) .. '!') + carDealer.sync(ply) + end + end) +end) + +carDealer.carColorBounds = { + {1, 45}, + {46, 102}, + {103, 203}, + {204, 241}, + {242, 328}, + {329, 562}, + {563, 1017}, +} + +carDealer.defaultCarColors = { + {'Черный',0,0,0}, + {'Графитно-черный',28,28,28}, + {'Цвет мокрого асфальта',80,80,80}, + {'Транспортный черный',30,30,30}, + {'Сигнальный черный',40,40,40}, + {'Черно-коричневый',33,33,33}, + {'Черный янтарь',10,10,10}, + {'Серый коричневый',64,58,58}, + {'Почти черный',19,19,19}, + {'Темно-серый',73,66,61}, + {'Зеленовато-черный',24,21,19}, + {'Серая умбра',51,47,44}, + {'Темный синевато-черный',70,69,68}, + {'Коричнево-оливковый',37,34,27}, + {'Коричнево-зеленый',57,53,42}, + {'Серый оливковый',62,59,50}, + {'Темный зеленовато-серый',69,67,59}, + {'Коричневый серый',70,69,49}, + {'Черно-оливковый',59,60,54}, + {'Оливково-зеленый',66,70,50}, + {'Винтовочный зеленый',65,72,51}, + {'Пихтовый зеленый',49,55,43}, + {'Черновато-зеленый',20,22,19}, + {'Брезентово-серый',76,81,74}, + {'Темный зеленовато-желто-зеленый',49,56,48}, + {'Зеленоватый мокрый асфальт',78,84,82}, + {'Космос',65,74,76}, + {'Черно-зеленый',52,62,64}, + {'Антрацитово-серый',41,49,51}, + {'Железно-серый',67,75,77}, + {'Темный серо-синий',44,51,55}, + {'Черно-серый',35,40,43}, + {'Синий серый',71,75,78}, + {'Синевато-черный',21,23,25}, + {'Черновато-синий',22,26,30}, + {'Гранитовый серый (Гранитный)',47,53,59}, + {'Сланцево-серый',67,71,80}, + {'Графитовый серый',71,74,81}, + {'Серый синий',38,37,45}, + {'Антрацитовый',70,68,81}, + {'Серовато-пурпурно-синий',65,61,81}, + {'Черно-синий',24,23,28}, + {'Серовато-фиолетовый',70,57,75}, + {'Стальной синий',35,26,36}, + {'Ливерный',83,75,79}, + {'Перламутровый темно-серый',130,130,130}, + {'Перламутровый светло-серый',156,156,156}, + {'Бело-алюминиевый',165,165,165}, + {'Тусклый серый',105,105,105}, + {'Телегрей',144,144,144}, + {'Серый',128,128,128}, + {'Блошиный (Красновато-коричневый)',117,90,87}, + {'Пурпурно-серый',136,112,107}, + {'Красновато-серый',139,108,98}, + {'Бобровый',159,129,112}, + {'Средний серый',129,112,102}, + {'Синевато-серый',125,116,109}, + {'Серый Крайола',149,145,140}, + {'Перламутрово-бежевый',106,93,77}, + {'Перламутровый мышино-серый',137,129,118}, + {'Кварцевый',153,149,140}, + {'Бежево-серый',109,101,82}, + {'Кварцевый серый',108,105,96}, + {'Зеленовато-серый',122,118,102}, + {'Желто-серый',143,139,102}, + {'Каменно-серый',139,140,122}, + {'Бледно-зелено-серый',141,145,122}, + {'Серый мох',108,112,89}, + {'Тростниково-зеленый',108,113,86}, + {'Серый бетон',104,108,94}, + {'Цементно-серый (Цементный)',125,132,113}, + {'Сигнальный серый',150,153,146}, + {'Серовато-зеленый',87,94,78}, + {'Защитный хаки (Камуфляжный)',120,134,107}, + {'Зелено-серый',77,86,69}, + {'Мышино-серый',100,107,99}, + {'Серая спаржа',70,89,69}, + {'Пыльно-серый',125,127,125}, + {'Транспортный серый',141,148,141}, + {'Лягушка в обмороке',123,145,123}, + {'Фельдграу',77,93,83}, + {'Базальтово-серый',78,87,84}, + {'Серебристо-серый',138,149,151}, + {'Серая белка',120,133,139}, + {'Серовато-синий',74,84,92}, + {'Темный телегрей',130,137,143}, + {'Светлый аспидно-серый',119,136,153}, + {'Серый шифер, аспидно-серый',112,128,144}, + {'Маренго',76,88,102}, + {'Бледный синий',145,145,146}, + {'Серый нейтральный',160,160,164}, + {'Светлый пурпурно-синий',131,125,162}, + {'Перламутрово-ежевичный',108,104,116}, + {'Перламутрово-фиолетовый',134,115,161}, + {'Светло-фиолетовый',135,108,153}, + {'Бледный пурпурно-синий',138,127,142}, + {'Бледно-фиолетовый',149,123,141}, + {'Розовый Маунтбэттена',153,122,141}, + {'Пастельно-фиолетовый',161,133,148}, + {'Баклажановый Крайола',110,81,96}, + {'Платиново-серый',127,118,121}, + {'Серовато-пурпурный',114,82,92}, + {'Темный пурпурно-серый',86,64,66}, + {'Розово-коричневый',188,143,143}, + {'Серебряный',192,192,192}, + {'Гейнсборо',220,220,220}, + {'Дымчато-белый',245,245,245}, + {'Светло-серый',187,187,187}, + {'Светлый телегрей',208,208,208}, + {'Светлый серый',215,215,215}, + {'Бороды Абдель-Керима',213,213,213}, + {'Бледный пурпурно-розовый',253,189,186}, + {'Дыня Крайола',253,188,180}, + {'Светлый пурпурно-серый',200,169,158}, + {'Серебряный Крайола',205,197,194}, + {'Розовато-серый',200,166,150}, + {'Циннвальдит',235,194,175}, + {'Светло-серебристый',201,192,187}, + {'Синевато-белый',249,223,207}, + {'Пурпурно-белый',250,219,200}, + {'Песок пустыни',239,205,184}, + {'Бледно-песочный',218,189,171}, + {'Абрикосовый',251,206,177}, + {'Светлый синевато-серый',190,173,161}, + {'Серовато-оранжевый',194,168,148}, + {'Крайоловый Абрикос',253,213,177}, + {'Льняной',250,240,230}, + {'Миндаль Крайола',239,222,205}, + {'Абрикосовый Крайола',253,217,181}, + {'Сливочно-кремовый',242,221,198}, + {'Лесной волк',219,215,210}, + {'Белый антик',250,235,215}, + {'Бедра испуганной нимфы',250,238,221}, + {'Кремовый хаки',195,176,145}, + {'Зеленовато-белый',245,230,203}, + {'Старое кружево',253,245,230}, + {'Кремовый',253,244,227}, + {'Экрю',205,184,145}, + {'Пшеничный',245,222,179}, + {'Светлый зеленовато-белый',186,175,150}, + {'Бананомания',250,231,181}, + {'Сливочный',242,232,201}, + {'Серый шелк',202,196,176}, + {'Жемчужно-белый',234,230,202}, + {'Бледно-золотистый',238,232,170}, + {'Весенне-зеленый Крайола',236,234,190}, + {'Бледный весенний бутон',236,235,189}, + {'Галечный серый',184,183,153}, + {'Цвет шампанского',252,252,238}, + {'Бежевый',245,245,220}, + {'Светло-желтый золотистый',250,250,210}, + {'Очень бледный зеленый',216,222,186}, + {'Болотный',172,183,142}, + {'Агатовый серый',181,184,177}, + {'Серый зеленый чай',202,218,186}, + {'Зеленый чай',208,240,192}, + {'Темный зеленый чай',186,219,173}, + {'Бабушкины яблоки',168,228,160}, + {'Бело-зеленый',189,236,182}, + {'Очень светлый зеленый',152,199,147}, + {'Зеленый лишайник, мох (Цвет зеленого мха)',173,223,173}, + {'Умеренно зеленый',192,220,192}, + {'Темное зеленое море',143,188,143}, + {'Селадон',172,225,175}, + {'Очень светлый синевато-зеленый',160,214,180}, + {'Морской зеленый Крайола',159,226,191}, + {'Магическая мята',170,240,209}, + {'Гридеперлевый',199,208,204}, + {'Панг',199,252,236}, + {'Очень светлый зеленовато-синий',163,198,192}, + {'Пастельно-бирюзовый',127,181,181}, + {'Очень бледный синий',193,202,202}, + {'Бледно-синий',175,238,238}, + {'Пыльный голубой',176,224,230}, + {'Снежно-синий',172,229,238}, + {'Светлый синий',173,216,230}, + {'Бледно-васильковый',171,205,239}, + {'Очень светлый синий',166,189,215}, + {'Светлый стальной синий',176,196,222}, + {'Ниагара',157,177,204}, + {'Барвинок Крайола',197,208,230}, + {'Кадетский синий Крайола',176,183,198}, + {'Серое окно',157,161,170}, + {'Дикий синий Крайола',162,173,208}, + {'Ламантин',151,154,170}, + {'Голубой колокольчик Крайола',162,162,208}, + {'Лаванда (Лавандовый)',230,230,250}, + {'Очень светлый пурпурно-синий',186,172,199}, + {'Глициния (Глициниевый)',201,160,220}, + {'Глициния Крайола',205,164,222}, + {'Очень светлый фиолетовый',238,190,241}, + {'Чертополох',216,191,216}, + {'Светлая слива',221,160,221}, + {'Сиреневый',200,162,200}, + {'Розовый кварц',170,152,169}, + {'Орхидея Крайола',230,168,215}, + {'Оперный розовато-лиловый',183,132,167}, + {'Чертополох Крайола',235,199,223}, + {'Очень бледный пурпурно-синий',203,186,197}, + {'Лавандовый Крайола',252,180,213}, + {'Очень светло-пурпурный',227,169,190}, + {'Очень бледный фиолетовый',216,177,191}, + {'Розовый поросенок',253,221,230}, + {'Тусклый пурпурный',174,132,139}, + {'Тускло-амарантово-розовый',221,190,195}, + {'Очень бледно-пурпурный',230,187,193}, + {'Бледно-розовый',250,218,221}, + {'Бледно-каштановый',221,173,175}, + {'Серовато-пурпурно-розовый',204,146,147}, + {'Белоснежный',255,250,250}, + {'Белый',255,255,255}, + {'Тускло-розовый',255,228,225}, + {'Циннвальдитово-розовый',255,203,187}, + {'Цвет морской раковины (Морская пена)',255,245,238}, + {'Темно-персиковый',255,218,185}, + {'Бисквитный',255,228,196}, + {'Желтовато-белый',255,226,183}, + {'Очищенный миндаль',255,235,205}, + {'Побег папайи',255,239,213}, + {'Мокасиновый',255,228,181}, + {'Персиковый',255,229,180}, + {'Цветочный белый',255,250,240}, + {'Космические сливки',255,248,231}, + {'Цвет шелковистых нитевидных пестиков початков неспелой кукурузы',255,248,220}, + {'Лимонно-кремовый',255,250,205}, + {'Цвет слоновой кости (Айвори)',255,253,223}, + {'Кремово-желтый',255,253,208}, + {'Слоновая кость',255,255,240}, + {'Светло-желтый',255,255,224}, + {'Медовая роса',240,255,240}, + {'Мятно-кремовый',245,255,250}, + {'Светлый циан',224,255,255}, + {'Небесная лазурь',240,255,255}, + {'Синяя Элис',240,248,255}, + {'Барвинок, перванш',204,204,255}, + {'Призрачно-белый',248,248,255}, + {'Светлая мальва (Светло-розовато-лиловый)',220,208,255}, + {'Магнолия',248,244,255}, + {'Сладкая вата',255,188,217}, + {'Розово-лавандовый',255,240,245}, + {'Бледно-розоватый',255,203,219}, + {'Пастельно-розовый',255,209,220}, + {'Розовый',255,192,203}, + {'Светло-розовый',255,182,193}, + {'Темный серо-красный',72,42,42}, + {'Ивово-коричневый',50,20,20}, + {'Темный серо-красно-коричневый',55,31,28}, + {'Шоколадно-коричневый',69,50,46}, + {'Цвет блошиного брюшка',78,22,9}, + {'Махагон коричневый',76,47,39}, + {'Темный красно-серый',82,60,54}, + {'Кофейный',68,45,37}, + {'Темный Коричневый',53,23,12}, + {'Глубокий коричневый',77,34,14}, + {'Коричневато-серый',80,61,51}, + {'Темный терракотовый',78,59,49}, + {'Темный серо-коричневый',50,34,26}, + {'Бистр',61,43,31}, + {'Темный желтовато-коричневый',63,37,18}, + {'Коричневато-черный',20,15,11}, + {'Темно-серо-коричневый',72,60,50}, + {'Темный оливково-коричневый',48,33,18}, + {'Сепия коричневый',56,44,30}, + {'Оливковый серый',77,66,52}, + {'Серовато-оливковый',82,68,44}, + {'Очень темный хаки',76,60,24}, + {'Темный серо-оливковый',43,37,23}, + {'Желто-оливковый',71,64,46}, + {'Очень темный оливковый',54,44,18}, + {'Сероватый оливково-зеленый',72,68,45}, + {'Темный серо-оливково-зеленый',39,38,26}, + {'Умеренный оливково-зеленый',67,75,27}, + {'Бутылочно-зеленый',52,59,41}, + {'Темный оливково-зеленый',35,44,22}, + {'Глубокий оливково-зеленый',20,35,0}, + {'Хромовый зеленый',46,58,35}, + {'Темный желтовато-зеленый',48,75,38}, + {'Оливково-черный',18,25,16}, + {'Насыщенный оливково-зеленый',10,69,0}, + {'Миртовый',33,66,30}, + {'Очень темный желто-зеленый',19,39,18}, + {'Очень глубокий желто-зеленый',0,40,0}, + {'Темный зеленый',32,58,39}, + {'Перламутрово-зеленый',28,84,45}, + {'Глубокий желтовато-зеленый',0,84,31}, + {'Зеленый мох',47,69,56}, + {'Очень темный зеленый',22,37,28}, + {'Фталоцианиновый зеленый',18,53,36}, + {'Глубокий зеленый',0,69,36}, + {'Темно-зеленый',1,50,32}, + {'Цвет Красного моря',31,64,55}, + {'Глубокий синевато-зеленый',0,56,43}, + {'Очень темный синевато-зеленый',0,29,24}, + {'Темный синевато-зеленый',1,58,51}, + {'Аспидно-серый',47,79,79}, + {'Перламутровый опаловый',25,55,55}, + {'Сине-зеленый',31,58,61}, + {'Зеленый орел',0,73,83}, + {'Темный зеленовато-синий',0,56,65}, + {'Зелено-синий',31,52,56}, + {'Очень темный зеленовато-синий',2,32,39}, + {'Темно-синий',0,33,55}, + {'Берлинская лазурь',0,49,83}, + {'Океанская синь',29,51,74}, + {'Горечавково-синий',14,41,75}, + {'Перламутровый ночной',16,44,84}, + {'Кобальтово-синий',30,33,61}, + {'Ночной синий',37,40,80}, + {'Сапфирово-синий',29,30,51}, + {'Ультрамариново-синий',32,33,79}, + {'Глубокий пурпурно-синий',26,21,63}, + {'Темный пурпурно-синий',26,22,42}, + {'Глубокий фиолетово-черный',36,9,53}, + {'Очень глубокий пурпурный',50,11,53}, + {'Глубокий пурпурный',83,26,80}, + {'Очень темно-пурпурный',35,13,33}, + {'Очень глубокий красно-пурпурный',71,7,54}, + {'Темно-пурпурный',71,42,63}, + {'Очень темный красно-пурпурный',39,10,31}, + {'Черновато-пурпурный',29,16,24}, + {'Очень темный пурпурно-красный',40,7,26}, + {'Очень глубокий пурпурно-красный',71,0,39}, + {'Пурпурно-черный',27,17,22}, + {'Темный красно-пурпурный',79,39,58}, + {'Пурпурно-фиолетовый',74,25,44}, + {'Очень темный красный',50,10,24}, + {'Темный черновато-пурпурный',69,45,53}, + {'Очень глубокий красный',79,0,20}, + {'Черновато-красный',31,14,17}, + {'Черно-красный',65,34,39}, + {'Бурый',69,22,28}, + {'Глубокий красно-коричневый',73,0,5}, + {'Красновато-черный',30,17,18}, + {'Темный красно-коричневый',50,16,17}, + {'Болгарский розовый',72,6,7}, + {'Коричнево-бордовый',165,42,42}, + {'Оксид красный',100,36,36}, + {'Фалунский красный',128,24,24}, + {'Коричнево-малиновый',128,0,0}, + {'Розово-серо-коричневый',144,93,93}, + {'Медно-розовый (Бледный розовато-лиловый)',153,102,102}, + {'Темно-красный',139,0,0}, + {'Сигнальный красный',165,32,25}, + {'Карминно-красный',162,35,29}, + {'Красно-коричневый',89,35,33}, + {'Серовато-красный',140,71,67}, + {'Коричнево-красный',120,31,25}, + {'Розово-эбонитовый',103,72,70}, + {'Глубокий красно-оранжевый',169,29,17}, + {'Насыщенный красно-коричневый',127,24,13}, + {'Каштаново-коричневый',99,58,52}, + {'Темный красно-оранжевый',155,47,31}, + {'Золотисто-каштановый',113,47,38}, + {'Томатно-красный',161,35,18}, + {'Известковая глина',121,68,59}, + {'Умбра жженая',138,51,36}, + {'Темно-каштановый',152,105,96}, + {'Умеренный серо-коричневый',103,76,71}, + {'Серовато-красно-коричневый',94,56,48}, + {'Бисмарк-фуриозо',165,38,10}, + {'Кирпичный',136,69,53}, + {'Медно-коричневый',142,64,42}, + {'Перламутровый медный',118,60,40}, + {'Сигнальный коричневый',108,59,42}, + {'Терракотовый',144,77,48}, + {'Сепия Крайола',165,105,79}, + {'Светлый серо-красно-коричневый',150,106,87}, + {'Серовато-коричневый',90,61,48}, + {'Сиена',160,82,45}, + {'Насыщенный коричневый',117,51,19}, + {'Умеренный коричневый',103,57,35}, + {'Орехово-коричневый',91,58,41}, + {'Светлый коричневато-серый',139,109,92}, + {'Светлый серо-коричневый',148,107,84}, + {'Светлый коричневый',168,101,64}, + {'Олень коричневый',89,53,31}, + {'Глиняный коричневый',115,66,34}, + {'Бежево-коричневый',121,85,61}, + {'Оранжево-коричневый',166,94,46}, + {'Коричневый цвета кожанного седла для лошади',139,69,19}, + {'Серовато-желто-коричневый',120,88,64}, + {'Красно-желто-коричневый',128,70,27}, + {'Глубокий желто-коричневый',89,51,21}, + {'Камелопардовый',162,95,42}, + {'Бледно-коричневый',117,92,72}, + {'Умеренный желто-коричневый',125,81,45}, + {'Цвет медвежьего ушка',131,77,24}, + {'Насыщенный желто-коричневый',149,80,12}, + {'Коричнево-бежевый',138,102,66}, + {'Перламутрово-золотой',112,83,53}, + {'Коричневый',150,75,0}, + {'Сырая умбра',113,75,35}, + {'Сепия (Каракатица)',112,66,20}, + {'Светло-коричневый',152,118,84}, + {'Темно-коричневый',101,67,33}, + {'Коричный',123,63,0}, + {'Охра коричневая',149,95,32}, + {'Оливково-коричневый',111,79,40}, + {'Светлый оливковый серый',136,115,89}, + {'Натуральная умбра',115,74,18}, + {'Темный серо-желтый',164,124,69}, + {'В меру оливково-коричневый',100,64,15}, + {'Светлый оливково-коричневый',148,93,11}, + {'Светлый серо-оливковый',139,115,75}, + {'Полумрак Крайола',138,121,93}, + {'Шамуа',160,128,64}, + {'Песочный серо-коричневый',150,113,23}, + {'Зелено-коричневый',130,108,52}, + {'Умеренно-оливковый',94,73,15}, + {'Светло-оливковый',132,106,32}, + {'Темный желто-коричневый',145,129,81}, + {'Медово-желтый',169,131,7}, + {'Хаки',128,107,42}, + {'Серовато-желто-зеленый',144,132,91}, + {'Темный зеленовато-желтый',155,129,39}, + {'Серый хаки',106,95,49}, + {'Глубокий зеленовато-желтый',159,130,0}, + {'Серо-бежевый',158,151,100}, + {'Желтый карри',157,145,1}, + {'Умеренный желто-зеленый',139,137,64}, + {'Оливковый',128,128,0}, + {'Оливково-желтый',153,153,80}, + {'Насыщенный желто-зеленый',127,143,24}, + {'Нежно-оливковый',107,142,35}, + {'Темно-оливковый',85,104,50}, + {'Глубокий желтый зеленый',66,94,23}, + {'Умеренный желтовато-зеленый',101,127,75}, + {'Спаржа',123,160,91}, + {'Кленовый зеленый',80,125,42}, + {'Спаржа Крайола',135,169,107}, + {'Цвет елки',42,92,3}, + {'Резедово-зеленый',88,114,70}, + {'Травяной',93,161,48}, + {'Папоротниково-зеленый',61,100,45}, + {'Темный желто-зеленый',87,166,57}, + {'Насыщенный желтовато-зеленый',71,132,48}, + {'Зеленый папоротник',79,121,66}, + {'Майский зеленый',76,145,65}, + {'Травяной зеленый',53,104,45}, + {'Индийский зеленый',19,136,8}, + {'Лиственно-зеленый',45,87,44}, + {'Лесной зеленый',34,139,34}, + {'Зеленый',0,128,0}, + {'Травяной (Очень темный лимонный зеленый)',0,100,0}, + {'Мусульманский зеленый',0,153,0}, + {'Арлекин',68,148,74}, + {'Охотничий зеленый',53,94,59}, + {'Изумрудно-зеленый',40,114,51}, + {'Сигнальный зеленый',49,127,67}, + {'Транспортный зеленый',48,132,70}, + {'Умеренный зеленый',56,102,70}, + {'Блестящий зеленый',71,167,106}, + {'Яркий зеленый',0,125,52}, + {'Зеленое море',46,139,87}, + {'Мятно-зеленый',32,96,61}, + {'Пигментный зеленый',0,165,80}, + {'Зеленый Мичиганского университета',0,102,51}, + {'Темный весенне-зеленый',23,114,69}, + {'Светлый синевато-зеленый',102,158,133}, + {'Насыщенный зеленый',0,107,60}, + {'Дартмутский зеленый',0,105,62}, + {'Патиново-зеленый',49,102,80}, + {'Сосновый зеленый',44,85,69}, + {'Нефритовый',0,168,107}, + {'Зеленый трилистник',0,154,99}, + {'Бирюзово-зеленый',30,89,69}, + {'Ядовито-зеленый',64,130,109}, + {'Умеренный синевато-зеленый',47,101,86}, + {'Блестящий синевато-зеленый',0,155,118}, + {'Изумруд',0,155,119}, + {'Мокрый тропический лес',23,128,109}, + {'Мятно-бирюзовый',73,126,118}, + {'Насыщенный синевато-зеленый',0,109,91}, + {'Яркий синевато-зеленый',0,131,110}, + {'Опаловый зеленый',1,93,82}, + {'Персидский зеленый',0,166,147}, + {'Зеленая сосна',1,121,111}, + {'Зеленая сосна Крайола',21,128,120}, + {'Пастельно-синий',93,155,155}, + {'Темный циан',0,139,139}, + {'Цвет окраски птицы чирок (Сине-зеленый)',0,128,128}, + {'Темно-бирюзовый',17,96,98}, + {'Кадетский синий',95,158,160}, + {'Мурена',28,107,114}, + {'Светлый зеленовато-синий',100,154,158}, + {'Бирюзово-синий',63,136,143}, + {'Блестящий зеленовато-синий',42,141,156}, + {'Водная синь',37,109,123}, + {'Умеренный зеленовато-синий',48,98,107}, + {'Насыщенный зеленовато-синий',0,103,126}, + {'Лазурно-синий',2,86,105}, + {'Лазурно-серый (Зеленовато-синий)',0,123,167}, + {'Перламутровый горечавково-синий',42,100,120}, + {'Средний персидский синий',0,103,165}, + {'Мертвенный индиго',0,65,106}, + {'Насыщенный синий',0,83,138}, + {'Капри синий',27,85,131}, + {'Глубокий синий',0,47,85}, + {'Темно-лазурный',8,69,126}, + {'Полуночно-синий',0,51,102}, + {'Полуночный синий Крайола',26,72,118}, + {'Цвет ВКонтакте',77,113,152}, + {'Умеренный синий',57,87,120}, + {'Транспортный синий',6,57,113}, + {'Отдаленно-синий',73,103,141}, + {'Бриллиантово-синий',62,95,138}, + {'Цвет Черного моря',26,71,128}, + {'Фиолетово-синий',53,77,115}, + {'Синяя пыль',0,51,153}, + {'Голубино-синий',96,110,140}, + {'Цвет Фейсбука',59,89,152}, + {'Сапфировый',8,37,103}, + {'Сигнальный синий',30,36,96}, + {'Блестящий пурпурно-синий',98,99,155}, + {'Полуночный черный',25,25,112}, + {'Темный ультрамариновый',0,0,139}, + {'Темно-синий (Цвет формы морских офицеров)',0,0,128}, + {'Ультрамариновый',18,10,143}, + {'Насыщенный пурпурно-синий',71,67,137}, + {'Темный аспидно-синий',72,61,139}, + {'Пурпурно-синий',32,21,94}, + {'Умеренный пурпурно-синий',66,60,99}, + {'Глубокий фиолетовый',66,49,137}, + {'Персидский индиго',50,18,122}, + {'Блестящий фиолетовый',117,93,154}, + {'Насыщенный фиолетовый',83,55,122}, + {'Королевский пурпурный Крайола',120,81,169}, + {'Темный индиго',49,0,98}, + {'Индиго',75,0,130}, + {'Умеренный фиолетовый',84,57,100}, + {'Серобуромалиновый',115,81,132}, + {'Темный пурпурно-фиолетовый',102,0,153}, + {'Сине-сиреневый',108,70,117}, + {'Яркий фиолетовый Крайола',143,80,157}, + {'Сливовый',102,0,102}, + {'Темный маджента',139,0,139}, + {'Фиолетово-баклажанный',153,17,153}, + {'Пурпурный',128,0,128}, + {'Яркий пурпурный',148,51,145}, + {'Сливовый Крайола',142,69,133}, + {'Византия',112,41,99}, + {'Умеренно-темный пурпурный',128,62,117}, + {'Темная Византия',93,57,84}, + {'Умеренный пурпурный',127,72,112}, + {'Яркий красно-пурпурный',126,0,89}, + {'Сигнальный фиолетовый',146,78,125}, + {'Баклажановый',153,0,102}, + {'Глубокий красно-пурпурный',100,19,73}, + {'Красно-сиреневый',109,63,91}, + {'Транспортный пурпурный',160,52,114}, + {'Амарантовый глубоко-пурпурный',159,43,104}, + {'Насыщенный красно-пурпурный',154,54,107}, + {'Баклажанный Крайола',97,64,81}, + {'Мальва (Розовато-лиловый)',153,51,102}, + {'Глубокий пурпурно-красный',111,0,53}, + {'Умеренный красно-пурпурный',140,69,102}, + {'Бордово-фиолетовый',100,28,52}, + {'Серовато-красно-пурпурный',125,77,93}, + {'Вишневый (Вишня)',145,30,66}, + {'Темный пурпурно-красный',91,30,49}, + {'Темно-серая мальва (Розовато-лилово-серый)',145,95,109}, + {'Очень темный алый',86,3,25}, + {'Умеренный пурпурно-красный',167,56,83}, + {'Бургундский',144,0,32}, + {'Глубокий карминный',169,32,62}, + {'Глубокий красный',123,0,28}, + {'Красно-фиолетовый',146,43,62}, + {'Кармин',150,0,24}, + {'Перламутрово-рубиновый',114,20,34}, + {'Серовато-пурпурно-красный',140,72,82}, + {'Винно-красный',94,33,41}, + {'Розовый лес',101,0,11}, + {'Пурпурно-красный',117,21,30}, + {'Темный красный',104,28,35}, + {'Рубиново-красный',155,17,30}, + {'Кордованский',137,63,69}, + {'Сангина',146,0,10}, + {'Бордовый',155,45,48}, + {'Транспортный красный',204,6,5}, + {'Индийский красный, каштановый',205,92,92}, + {'Люминесцентный красный',248,0,0}, + {'Персидский красный',204,51,51}, + {'Бордо (Красно-бордовый)',176,0,0}, + {'Фузи-вузи',204,102,102}, + {'Светлый серо-пурпурно-красный',178,112,112}, + {'Глубокий желто-розовый',246,74,70}, + {'Темный розовый',199,104,100}, + {'Светлый карминово-розовый',230,103,97}, + {'Темно-алый',203,40,33}, + {'Кораллово-красный',179,40,33}, + {'Карминно-розовый',235,76,66}, + {'Сочный каштановый Крайола',185,78,72}, + {'Каштановый Крайола',188,93,88}, + {'Оранжевая заря',253,94,83}, + {'Бледно-карминный',176,63,53}, + {'Марсала',173,101,95}, + {'Китайский красный (Киноварь)',227,66,52}, + {'Перламутрово-розовый',180,76,67}, + {'Средний карминный',175,64,53}, + {'Огненно-красный',175,43,30}, + {'Горько-сладкий',253,124,110}, + {'Умеренный розовый',238,144,134}, + {'Шапка Деда Мороза',202,58,39}, + {'Шапка Санта-Клауса',237,72,48}, + {'Мандариновое танго',225,82,61}, + {'Транспортный оранжевый',245,64,33}, + {'Темный желто-розовый',204,108,92}, + {'Светлый серо-красный',177,114,103}, + {'Лососево-оранжевый',229,81,55}, + {'Темно-коралловый',205,91,69}, + {'Красно-оранжевый',201,60,32}, + {'Гранатовый',243,71,35}, + {'Умеренный красно-оранжевый',211,83,57}, + {'Яркий красно-оранжевый',241,58,19}, + {'Серовато-розовый',207,155,143}, + {'Лососево-красный',217,80,48}, + {'Серовато-красно-оранжевый',184,93,67}, + {'Сиена жженая',233,116,81}, + {'Огненная сиенна Крайола',234,126,93}, + {'Светло-тициановый',216,75,32}, + {'Светлый красно-коричневый',170,102,81}, + {'Темно-лососевый',233,150,122}, + {'Умеренный желто-розовый',238,147,116}, + {'Коричневый Крайола',180,103,77}, + {'Тициановый',213,62,7}, + {'Серовато-желто-розовый',211,155,133}, + {'Пылкий красно-оранжевый',247,94,37}, + {'Перламутрово-оранжевый',195,88,49}, + {'Медный Крайола',221,148,117}, + {'Ржаво-коричневый',183,65,14}, + {'Морковный',243,98,35}, + {'Коричневато-оранжевый',177,81,36}, + {'Бежево-красный',193,135,107}, + {'Ванильный',213,113,63}, + {'Красное дерево',192,64,0}, + {'Умеренный оранжевый',232,121,62}, + {'Античная латунь',205,149,117}, + {'Сомон',239,175,140}, + {'Глубокий оранжевый',195,77,10}, + {'Коричневато-розовый',205,154,123}, + {'Перекати-поле',222,170,136}, + {'Сырая охра',214,138,89}, + {'Цвет загара Крайола',250,167,108}, + {'Красно-буро-оранжевый',205,87,0}, + {'Бледный серо-коричневый',188,152,126}, + {'Жженый апельсин (Выгоревший оранжевый)',204,85,0}, + {'Шоколадный',210,105,30}, + {'Светлый серо-желто-коричневый',180,135,100}, + {'Насыщенный оранжевый',236,124,38}, + {'Рыжий',215,125,49}, + {'Красный песок',244,164,96}, + {'Желто-оранжевый',237,118,14}, + {'Умеренный оранжево-желтый',247,148,60}, + {'Медный',184,115,51}, + {'Бронзовый',205,127,50}, + {'Перу',205,133,63}, + {'Желтовато-серый',202,168,133}, + {'Глубокий оранжево-желтый',215,110,0}, + {'Охра',204,119,34}, + {'Темный оранжево-желтый',195,118,41}, + {'Темный мандарин',234,117,0}, + {'Светло-морковный',237,145,33}, + {'Коричнево-желтый цвета увядших листьев',193,154,107}, + {'Тусклый мандарин',242,133,0}, + {'Светлый желто-коричневый',187,139,84}, + {'Пастельно-желтый',239,169,74}, + {'Цвет загара (Желто-коричневый)',210,180,140}, + {'Золотой Крайола',231,198,151}, + {'Серовато-желтый',206,162,98}, + {'Солнечно-желтый',243,159,24}, + {'Темно-желтый',176,125,43}, + {'Светлая Сиена (Почти чистый оранжевый)',226,139,0}, + {'Умеренный желтый',215,157,65}, + {'Насыщенный желтый',229,158,31}, + {'Желто-персиковый',250,223,173}, + {'Гуммигут',228,155,15}, + {'Маисовый',237,209,156}, + {'Кукурузно-желтый',228,160,16}, + {'Глубокий желтый',181,121,0}, + {'Георгиново-желтый',243,165,5}, + {'Песочно-желтый',198,166,100}, + {'Дынно-желтый',244,169,0}, + {'Серовато-зеленовато-желтый',196,165,95}, + {'Медовый',254,229,172}, + {'Золотисто-березовый',218,165,32}, + {'Темный золотарник (Темно-золотой)',184,134,11}, + {'Нарциссово-желтый',220,157,0}, + {'Бледный желто-зеленый',240,214,152}, + {'Желто-золотой',205,164,52}, + {'Золотарник Крайола',252,217,117}, + {'Одуванчиковый',253,219,109}, + {'Умеренный зеленовато-желтый',196,164,61}, + {'Шафрановый',244,196,48}, + {'Оранжево-желтый Крайола',248,213,104}, + {'Темно-грушевый',216,169,3}, + {'Светло-песочный',253,234,168}, + {'Песочный',252,221,118}, + {'Насыщенный зеленовато-желтый',204,168,23}, + {'Желтый ракитник',214,174,1}, + {'Светлая слоновая кость',230,214,144}, + {'Шафраново-желтый',245,208,51}, + {'Старое золото',207,181,59}, + {'Кожа буйвола (Палевый)',240,220,130}, + {'Яркий зеленовато-желтый',244,200,0}, + {'Старый лен',238,220,130}, + {'Сигнальный желтый',229,190,1}, + {'Желтый Крайола',252,232,131}, + {'Транспортно-желтый',250,210,1}, + {'Лимонно-желтый',199,180,70}, + {'Охра желтая',174,160,75}, + {'Грушевый',239,211,52}, + {'Желтая слоновая кость',225,204,79}, + {'Латунный',181,166,66}, + {'Рапсово-желтый',243,218,11}, + {'Цвет пергидрольной блондинки',238,230,163}, + {'Светлый хаки',240,230,140}, + {'Зелено-желтый Крайола',240,232,145}, + {'Лимонный',253,233,16}, + {'Кукурузный',251,236,93}, + {'Темный хаки',189,183,107}, + {'Светлый желто-зеленый',220,211,106}, + {'Цвет детской неожиданности',247,242,26}, + {'Цинково-желтый',248,243,43}, + {'Оливково-зеленый Крайола',186,184,108}, + {'Вердепешевый',218,216,113}, + {'Зелено-бежевый',190,189,127}, + {'Лазерный лимон',254,254,34}, + {'Блестящий желто-зеленый',206,210,58}, + {'Грушево-зеленый',209,226,49}, + {'Яркий желто-зеленый',147,170,0}, + {'Июньский бутон',189,218,87}, + {'Обычный весенний бутон',201,220,135}, + {'Желто-зеленый Крайола',197,227,132}, + {'Очень светлый желто-зеленый',198,223,144}, + {'Желто-зеленый',154,205,50}, + {'Весенний бутон',167,252,0}, + {'Гусеница',178,236,93}, + {'Фисташковый',190,245,116}, + {'Зеленая лужайка',124,252,0}, + {'Блестящий желтовато-зеленый',140,203,94}, + {'Бледно-зеленый',137,172,118}, + {'Ирландский зеленый',76,187,23}, + {'Вердепомовый',52,201,36}, + {'Лаймово-зеленый',50,205,50}, + {'Бледный зеленый',152,251,152}, + {'Светло-зеленый',144,238,144}, + {'Цвет влюбленной жабы',60,170,60}, + {'Пастельно-зеленый',119,221,119}, + {'Папоротник Крайола',113,188,120}, + {'Темный пастельно-зеленый',3,192,60}, + {'Лиственный зеленый Крайола',109,174,129}, + {'Малахитовый',11,218,81}, + {'Изумрудный',80,200,120}, + {'Умеренно-зеленое море',60,179,113}, + {'Умеренный весенний зеленый',0,250,154}, + {'Мятный',62,180,137}, + {'Зеленый Крайола',28,172,120}, + {'Умеренный аквамариновый',102,205,170}, + {'Трилистник Крайола',69,206,162}, + {'Горный луг',48,186,143}, + {'Зеленые джунгли Крайола',59,176,143}, + {'Карибский зеленый',28,211,162}, + {'Зеленые джунгли Крайола 90-го года',41,171,135}, + {'Светло-бирюзовый',64,224,208}, + {'Бирюзовый',48,213,200}, + {'Светлый серо-синий',132,195,190}, + {'Светлое зеленое море',32,178,170}, + {'Ярко-бирюзовый',8,232,222}, + {'Умеренно-бирюзовый',72,209,204}, + {'Тиффани',10,186,181}, + {'Синий цвета яиц странствующего дрозда',31,206,203}, + {'Цвет яйца дрозда',0,204,204}, + {'Аквамариновый Крайола',120,219,226}, + {'Бирюзово-голубой Крайола',119,221,231}, + {'Синий чирок',24,167,181}, + {'Голубой Крайола',128,218,235}, + {'Воды пляжа Бонди',0,149,182}, + {'Сине-зеленый Крайола',13,152,186}, + {'Тихоокеанский синий',28,169,201}, + {'Лазурный Крайола',29,172,214}, + {'Ярко-синий',0,124,173}, + {'Цвет Твиттера',31,174,233}, + {'Городское небо (Пасмурно-небесный)',135,206,235}, + {'Цвет Хабрахабра',120,162,183}, + {'Васильковый Крайола',154,206,235}, + {'Светло-голубой',135,206,250}, + {'Блестящий синий',66,133,180}, + {'Цвет морской волны (Аква)',0,140,240}, + {'Светлый сине-серый',108,146,175}, + {'Синий-синий иней',175,218,252}, + {'Темно-голубой',59,131,189}, + {'Сизый',121,160,193}, + {'Небесно-синий',34,113,179}, + {'Синяя сталь',70,130,180}, + {'Зелено-синий Крайола',17,100,180}, + {'Сине-серый Крайола',102,153,204}, + {'Темно-синий Крайола',25,116,210}, + {'Светло-синий',166,202,240}, + {'Джинсовый синий',21,96,189}, + {'Светлый джинсовый',43,108,196}, + {'Синий Клейна',58,117,196}, + {'Кобальт синий (Кобальтовый)',0,71,171}, + {'Синий Крайола',31,117,254}, + {'Васильковый',100,149,237}, + {'Синяя лазурь (Лазурно-голубой)',42,82,190}, + {'Королевский синий',65,105,225}, + {'Индиго Крайола',93,118,203}, + {'Синий экран смерти',18,47,170}, + {'Фиолетово-синий Крайола',50,74,178}, + {'Умеренный аспидно-синий',123,104,238}, + {'Сине-фиолетовый Крайола',115,102,189}, + {'Аспидно-синий',106,90,205}, + {'Средний пурпурный',147,112,216}, + {'Пурпурное сердце',116,66,200}, + {'Пурпурное горное величие',157,129,186}, + {'Аметистовый',153,102,204}, + {'Сине-лиловый',138,43,226}, + {'Фиолетовый Крайола (Пурпурный)',146,110,174}, + {'Темная орхидея',153,50,204}, + {'Темно-фиолетовый',148,0,211}, + {'Умеренный цвет орхидеи',186,85,211}, + {'Фиалковый',234,141,247}, + {'Фуксия Крайола',195,100,197}, + {'Розовый фламинго',252,116,253}, + {'Шокирующий розовый Крайола',251,126,253}, + {'Глубокая фуксия Крайола',193,84,193}, + {'Розово-фиолетовый',238,130,238}, + {'Ярко-фиолетовый',205,0,205}, + {'Орхидея',218,112,214}, + {'Светло-розовая фуксия',249,132,239}, + {'Фуксия (Фуксин)',247,84,225}, + {'Византийский',189,51,164}, + {'Блестящий пурпурный',221,128,204}, + {'Сияющая орхидея',181,101,167}, + {'Бледно-пурпурный',249,132,229}, + {'Амарантовый маджента',237,60,202}, + {'Ярко-розовый',252,15,192}, + {'Лавандовый розовый',251,160,227}, + {'Модная фуксия',244,0,161}, + {'Королевская фуксия',202,44,146}, + {'Мовеин (Анилиновый пурпур)',239,0,151}, + {'Умеренный фиолетово-красный',199,21,133}, + {'Красно-фиолетовый Крайола',192,68,143}, + {'Светло-пурпурный',186,127,162}, + {'Персидский розовый',254,40,162}, + {'Амарантовый светло-вишневый',205,38,130}, + {'Фанданго',181,84,137}, + {'Шелковица Крайола',197,75,140}, + {'Светло-вишневый Крайола',221,68,146}, + {'Маджента Крайола',246,100,175}, + {'Телемагента',207,52,118}, + {'Вересково-фиолетовый',222,76,138}, + {'Грузинский розовый',215,24,104}, + {'Фиолетово-красный Крайола',247,83,148}, + {'Светлый красно-пурпурный',187,108,138}, + {'Цвет суеты',227,37,107}, + {'Французский розовый',246,74,138}, + {'Амарантово-розовый',241,156,187}, + {'Малиново-розовый',179,68,108}, + {'Бледный фиолетово-красный',216,112,147}, + {'Лиловый',219,112,147}, + {'Жимолость',203,101,134}, + {'Джазовый джем',202,55,103}, + {'Глубокий пурпурно-розовый',235,82,132}, + {'Розовый (Пощекочи меня)',252,137,172}, + {'Амарантово-пурпурный',171,39,79}, + {'Яркий пурпурно-красный',213,38,91}, + {'Румянец',222,93,131}, + {'Темно-розовый',231,84,128}, + {'Насыщенный пурпурно-красный',179,40,81}, + {'Светлая вишня',222,49,99}, + {'Коричнево-малиновый Крайола',200,56,90}, + {'Пюсовый',204,136,153}, + {'Красный Крайола',238,32,77}, + {'Розовый щербет',247,143,167}, + {'Турецкий розовый',181,114,129}, + {'Розовато-лиловый Крайола',239,152,170}, + {'Насыщенный пурпурно-розовый',246,118,142}, + {'Малиновый',220,20,60}, + {'Бледный красно-пурпурный',172,117,128}, + {'Крутой розовый Крайола',251,96,127}, + {'Амарантовый',229,43,80}, + {'Кардинал',196,30,58}, + {'Дикий арбуз Крайола',252,108,133}, + {'Пламенная маджента Крайола',248,23,62}, + {'Светлый розово-лиловый',234,137,154}, + {'Яркий красный',193,0,32}, + {'Умеренный пурпурно-розовый',226,128,144}, + {'Темный пурпурно-розовый',199,101,116}, + {'Розово-золотой',183,110,121}, + {'Скарлет',252,40,71}, + {'Кирпично-красный',203,65,84}, + {'Малиново-красный',197,29,52}, + {'Насыщенный красный',191,34,51}, + {'Терракота',204,78,92}, + {'Ализариновый красный',227,38,54}, + {'Светлый малиново-красный',230,50,68}, + {'Умеренный красный',171,52,58}, + {'Цвет пожарной машины',206,32,41}, + {'Розовая долина',171,78,82}, + {'Глубокий карминно-розовый',239,48,56}, + {'Розовый антик',211,110,112}, + {'Ориент красный',179,36,40}, + {'Красное дерево Крайола',205,74,76}, + {'Насыщенный розовый',253,123,124}, + {'Старинный розовый',192,128,129}, + {'Клубнично-красный',213,48,50}, + {'Глубокий коралловый',255,64,64}, + {'Оранжево-красный Крайола',255,43,43}, + {'Красный',255,0,0}, + {'Красно-оранжевый Крайола',255,83,73}, + {'Алый',255,36,0}, + {'Томатный',255,99,71}, + {'Светло-коралловый',255,188,173}, + {'Насыщенный желто-розовый',255,122,92}, + {'Скандальный оранжевый',255,110,74}, + {'Ярко-мандариновый',255,160,137}, + {'Яркий желто-розовый',255,132,92}, + {'Лососевый',255,140,105}, + {'Коралловый',255,127,80}, + {'Огненный оранжевый',255,127,73}, + {'Киноварь',255,77,0}, + {'Международный оранжевый (Сигнальный)',255,79,0}, + {'Оранжевый Крайола',255,117,56}, + {'Цвет маленького мандарина',255,164,116}, + {'Оранжево-розовый',255,153,102}, + {'Светлый желто-розовый',255,178,139}, + {'Манго-танго',255,130,67}, + {'Бледный желто-розовый',255,200,168}, + {'Пастельно-оранжевый',255,117,20}, + {'Светлый оранжевый',255,161,97}, + {'Тыква (Тыквенный)',255,117,24}, + {'Яркий оранжевый',255,104,0}, + {'Персиковый Крайола',255,207,171}, + {'Макароны и сыр',255,189,136}, + {'Темный янтарь',255,126,0}, + {'Неоново-морковный',255,163,67}, + {'Оранжево-персиковый',255,204,153}, + {'Насыщенный оранжево-желтый',255,142,13}, + {'Последний вздох Жако',255,146,24}, + {'Мандариновый',255,136,0}, + {'Темно-оранжевый',255,140,0}, + {'Яркий оранжево-желтый',255,142,0}, + {'Бледный оранжево-желтый',255,202,134}, + {'Насыщенный красно-оранжевый',255,185,97}, + {'Желто-оранжевый Крайола',255,174,66}, + {'Люминесцентный ярко-оранжевый',255,164,32}, + {'Белый навахо',255,222,173}, + {'Сигнальный оранжевый',255,153,0}, + {'Темно-мандариновый',255,168,18}, + {'Кожура апельсина',255,160,0}, + {'Блестящий оранжевый',255,184,65}, + {'Бриллиантовый оранжево-желтый',255,176,46}, + {'Желто-розовый',255,228,178}, + {'Оранжевый',255,165,0}, + {'Бледно-желтый',255,219,139}, + {'Ярко-желтый',255,179,0}, + {'Светлый глубокий желтый',255,211,95}, + {'Отборный желтый',255,186,0}, + {'Янтарный',255,191,0}, + {'Бледный зеленовато-желтый',255,223,132}, + {'Блестящий желтый',255,207,64}, + {'Восход солнца',255,207,72}, + {'Горчичный',255,219,88}, + {'Светлый зеленовато-желтый',255,222,90}, + {'Цвет Яндекса',255,204,0}, + {'Блестящий зеленовато-желтый',255,220,51}, + {'Золотой (Золотистый)',255,215,0}, + {'Цвет желтого школьного автобуса',255,216,0}, + {'Светло-золотистый',255,236,139}, + {'Лимонно-желтый Крайола',255,244,79}, + {'Канареечный (Ярко-желтый)',255,255,153}, + {'Желтый',255,255,0}, + {'Незрелый желтый',255,255,102}, + {'Желтая сера',237,255,33}, + {'Электрик лайм (Лаймовый)',204,255,0}, + {'Электрик лайм Крайола',206,255,29}, + {'Зелено-лаймовый',191,255,0}, + {'Зелено-желтый',173,255,47}, + {'Шартрез, Ядовито-зеленый',127,255,0}, + {'Ярко-зеленый',102,255,0}, + {'Лайм',0,255,0}, + {'Салатовый',153,255,153}, + {'Мята (Цвет зеленой мяты)',152,255,152}, + {'Кричащий зеленый',118,255,122}, + {'Морской зеленый',84,255,159}, + {'Весенне-зеленый (Зеленая весна)',0,255,127}, + {'Аквамариновый',127,255,212}, + {'Циан, Цвет морской волны',0,255,255}, + {'Электрик',125,249,255}, + {'Голубой (Морозное небо)',0,191,255}, + {'Небесный',127,199,255}, + {'Голубой',66,170,255}, + {'Защитно-синий',30,144,255}, + {'Лазурный, Азур',0,127,255}, + {'Синий Градуса',0,125,255}, + {'Синий',0,0,255}, + {'Персидский синий',102,0,255}, + {'Фиолетово-сизый',128,0,255}, + {'Фиолетовый',139,0,255}, + {'Ярко-сиреневый',224,176,255}, + {'Гелиотроп (Гелиотроповый)',223,115,255}, + {'Розовая фуксия',255,119,255}, + {'Маджента, Фуксия (Пурпурный)',255,0,255}, + {'Пурпурная пицца',255,0,204}, + {'Экстравагантный розовый Крайола',255,51,204}, + {'Звезды в шоке',255,71,202}, + {'Глубокий розовый',255,20,147}, + {'Дикая клубника Крайола',255,67,164}, + {'Розовая гвоздика',255,170,204}, + {'Блестящий пурпурно-розовый',255,151,187}, + {'Американский розовый',255,3,62}, + {'Радикальный красный',255,73,108}, + {'Карминово-красный',255,0,51}, + {'Пылкий розовый',255,126,147}, + {'Лососевый Крайола',255,155,170}, + {'Светлый пурпурно-розовый',255,168,175}, +} diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_core.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_core.lua new file mode 100644 index 0000000..247030b --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_core.lua @@ -0,0 +1,513 @@ +local defaultColors = { Color(255,255,255), Color(255,255,255), Color(0,0,0), Color(255,255,255) } + +hook.Add('octolib.db.init', 'car-dealer.init', function() + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS cardealer_owned ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT, + garage VARCHAR(30) NOT NULL, + class VARCHAR(30) NOT NULL, + plate VARCHAR(7) NOT NULL, + data TEXT NOT NULL, + PRIMARY KEY (id), + UNIQUE (plate) + ) + ]]) + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS cardealer_plates ( + vehicleID INT UNSIGNED NOT NULL, + garage VARCHAR(30) NOT NULL, + plate VARCHAR(7) NOT NULL, + changeDate INT NOT NULL, + INDEX cardealer_plates_ibfk_1 (vehicleID), + CONSTRAINT cardealer_plates_ibfk_1 + FOREIGN KEY (vehicleID) + REFERENCES cardealer_owned(id) + ON UPDATE CASCADE ON DELETE CASCADE + ) + ]]) + +end) + +function carDealer.getVehById(id, callback) + + callback = callback or octolib.func.zero + + octolib.db:PrepareQuery('select * from cardealer_owned where id=? limit 1', { id }, function(_, _, data) + local data = istable(data) and data[1] + if not data then return callback() end + data.data = pon.decode(data.data) or {} + callback(data) + end) + +end + +function carDealer.getVehByPlate(plate, callback) + + callback = callback or octolib.func.zero + + octolib.db:PrepareQuery('select * from cardealer_owned where plate=? limit 1', { plate:upper() }, function(_, _, data) + local data = istable(data) and data[1] + if not data then return callback() end + data.data = pon.decode(data.data) or {} + callback(data) + end) + +end + +function carDealer.getGarage(garage, callback) + + callback = callback or octolib.func.zero + + octolib.db:PrepareQuery('select id, class, plate, data from cardealer_owned where garage=?', { garage }, function(_, _, data) + if istable(data) then + local toReturn = {} + for _, row in ipairs(data) do + toReturn[row.id] = { + class = row.class, + plate = row.plate, + data = pon.decode(row.data) or {}, + } + end + callback(toReturn) + else + callback({}) + end + end) + +end + +function carDealer.randomPlate(length) + + local symbols = carDealer.plateSymbols + local plate = {} + for i = 1, length do + plate[i] = symbols[math.random(#symbols)] + end + + return table.concat(plate) + +end + +function carDealer.firstAvailablePlate(callback) + + callback = callback or octolib.func.zero + + octolib.func.loop(function(again) + local plate = carDealer.randomPlate(carDealer.plateLength) + carDealer.getVehByPlate(plate, function(veh) + if veh then + again() + else + callback(plate) + end + end) + end) + +end + +function carDealer.ownVeh(garage, class, callback) + + callback = callback or octolib.func.zero + + local cdData = carDealer.vehicles[class] + assert(cdData ~= nil, 'Wrong vehicle class: ' .. class) + + octolib.func.chain({ + function(done) + carDealer.firstAvailablePlate(done) + end, + function(done, plate) + octolib.db:PrepareQuery('insert into cardealer_owned(garage, class, plate, data) values(?, ?, ?, ?)', { + garage, class, plate, pon.encode({}), + }, function(q, st, res) + if not st then return end + done(q:lastInsert(), plate) + end) + end, + function(_, id, plate) + local ply = player.GetBySteamID(garage) + if IsValid(ply) then carDealer.sync(ply) end + callback(id, plate) -- call outer callback + end, + }) + +end + +function carDealer.unownVeh(id, callback) + + callback = callback or octolib.func.zero + + octolib.func.chain({ + function(done) + carDealer.getVehById(id, done) + end, + function(done, veh) + if not veh then return callback(false) end + + local ply = player.GetBySteamID(veh.garage) + if IsValid(ply) and carDealer.getCurVehID(ply) == id then + carDealer.despawnVeh(carDealer.getCurVeh(ply)) + end + + octolib.db:PrepareQuery('delete from cardealer_owned where id=?', { id }, function(q, st, res) + if q:affectedRows() < 1 then return callback(false) end + done(true, veh, ply) + end) + end, + function(done, unowned, veh, ply) + octolib.db:PrepareQuery('delete from cardealer_plates where vehicleID=?', {id}, function() + done(unowned, veh, ply) + end) + end, + function(_, unowned, veh, ply) + if unowned and IsValid(ply) then + carDealer.sync(ply) + end + callback(veh) + end, + }) + +end + +function carDealer.getCurVehPrice(ent) + + if not IsValid(ent) or not ent.cdClass then return 0 end + local data = carDealer.vehicles[ent.cdClass] + if not data then return end + + local price = ent.deposit or data.price + price = price * ent:GetCurHealth() / ent:GetMaxHealth() + price = price - (ent:GetMaxFuel() - ent:GetFuel()) * simfphys.fuelPrices[ent:GetFuelType()] + for _, wheel in ipairs(ent.Wheels or {}) do + if wheel:GetDamaged() then price = price - 3500 end + end + + return math.max(0, math.Round(price)) + +end + +function carDealer.nearestPos(data) + + if not istable(data) or + not isvector(data.pPos) or + not istable(data.vars) or + not isfunction(data.callback) then return end + + local count = 0 + octolib.func.loop(function(done) + + local mnDist = math.huge + local freeSpace, pos, ang = Vector(75, 75, 75) + for _,v in ipairs(data.vars) do + local dist = v[1]:DistToSqr(data.pPos) + if dist >= mnDist then continue end + local tr = util.TraceHull({ + mins = -freeSpace, + maxs = freeSpace, + start = v[1], + endpos = v[1], + ignoreworld = true, + filter = data.filter, + }) + + if not tr.Hit then + pos, ang = unpack(v) + if data.okDist and dist <= data.okDist then + return data.callback(pos, ang, dist) + end + mnDist = dist + end + end + + count = count + 1 + if not pos and (not isfunction(data.check) or data.check()) and count < (data.maxAttempts or 20) then + timer.Simple(3, done) + else data.callback(pos, ang, mnDist) end + + end) + +end + +function carDealer.getOwner(ent) + + return ent:GetNetVar('cd.owner') + +end + +function carDealer.saveVeh(ent, callback) + + callback = callback or octolib.func.zero + + if not IsValid(ent) then return callback() end + if not ent:GetNetVar('cd.id') then return callback(ent) end + + local cdData = ent.cdData + if not cdData or cdData.deposit then return callback(ent) end + + spData = list.Get('simfphys_vehicles')[cdData.simfphysID] + if not spData then return end + + local members = spData.Members + + local data = {} + data.health = ent:GetCurHealth() / ent:GetMaxHealth() + data.fuel = ent:GetFuel() / ent:GetMaxFuel() + data.col = octolib.table.mapSequential(ent:GetProxyColors(), function(v) + if not v.r then return Color() end + return Color((v.r or 1) * 255, (v.g or 1) * 255, (v.b or 1) * 255, (v.a or 1) * 255) + end) + data.atts = ent:GetNetVar('atts') + + data.wheels = {} + local allWheelsOK = false + for k,v in ipairs(ent.Wheels or {}) do + data.wheels[k] = v:GetDamaged() + if v:GetDamaged() then allWheelsOK = true end + end + if not allWheelsOK then data.wheels = '--delete--' end + + data.camber = ent.camber or 0 + data.wOffset = { ent.wOffsetF or 0, ent.wOffsetR or 0 } + data.rims = { + ent.wModelF or members.CustomWheelModel, + ent.wModelR or members.CustomWheelModel or members.CustomWheelModel_R + } + data.susp = { + ent:GetFrontSuspensionHeight() or 0, + ent.consF or members.FrontConstant or 25000, + ent.dampF or members.FrontDamping or 1500, + ent:GetRearSuspensionHeight() or 0, + ent.consR or members.RearConstant or 25000, + ent.dampR or members.RearDamping or 1500 + } + data.inv = ent:ExportInventory() + + -- these are reset when marked for deletion leading to data loss + if not ent:IsMarkedForDeletion() then + data.skin = ent:GetSkin() + data.bg = {} + for _, v in pairs(ent:GetBodyGroups()) do data.bg[v.id] = ent:GetBodygroup(v.id) end + end + + carDealer.updateVehData(ent:GetNetVar('cd.id'), data, function() callback(ent) end) + +end + +function carDealer.updateVehData(id, override, callback) + + callback = callback or octolib.func.zero + + octolib.func.chain({ + function(done) + carDealer.getVehById(id, done) + end, + function(done, veh) + if not veh then return callback() end + + if override.plate then + octolib.db:PrepareQuery('update cardealer_owned set plate=? where id=?', { + string.upper(override.plate), id + }) + veh.plate = string.upper(override.plate) + override.plate = nil + end + for k, v in pairs(override) do + veh.data[k] = v ~= '--delete--' and v or nil + end + + octolib.db:PrepareQuery('update cardealer_owned set data=? where id=?', { + pon.encode(veh.data), id + }, function() + done(veh) + end) + end, + function(_, veh) + if veh then + local ply = player.GetBySteamID(veh.garage) + if IsValid(ply) then + carDealer.sync(ply) + end + end + callback() -- call outer callback + end, + }) + +end + +function carDealer.spawnVeh(class, pos, ang, saved) + + saved = saved or {} + + local cdData = carDealer.vehicles[class] + if not cdData then return end + local cdcData = cdData.category and carDealer.categories[cdData.category] or {} + + spData = list.Get('simfphys_vehicles')[cdData.simfphysID] + if not spData then return end + + if cdData.SpawnAngleOffset then + ang = cdData.SpawnAngleOffset + end + + local veh = simfphys.SpawnVehicle(nil, pos, ang, spData.Model, spData.Class, cdData.simfphysID, spData, true) + if not veh then return end + veh.cdClass = class + veh.cdData = cdData + veh.preventEms = not cdcData.ems + veh.doNotEvacuate = cdcData.doNotEvacuate + veh.cdBulletproof = cdData.bulletproof + veh.baseMass = veh.Mass * 0.75 + + local cols = saved.col or cdData.default and cdData.default.col or defaultColors + if isfunction(cols) then cols = cols() end + veh:SetProxyColors({ cols[1], cols[2], CFG.reflectionTint, cols[4] }) + veh:SetSkin(saved.skin or cdData.default and cdData.default.skin or 0) + local mats = saved.mats or cdData.default and cdData.default.mats or {} + for k, v in pairs(mats) do + veh:SetSubMaterial(k-1, v) + end + veh:SetNetVar('atts', saved.atts) + + timer.Simple(1, function() + if not IsValid(veh) then return end + + if saved.fuel then veh:SetFuel(veh:GetMaxFuel() * math.max(saved.fuel, 0)) end + if cdData.plateCol then + veh:SetNetVar('cd.plateCol', { + cdData.plateCol.bg, + cdData.plateCol.border, + cdData.plateCol.title, + cdData.plateCol.txt, + }) + elseif cdData.police then + veh:SetNetVar('cd.plateCol', { + Color(75, 86, 208), + Color(40, 40, 40), + Color(255, 255, 255), + Color(255, 255, 255), + }) + end + + if saved.health then + local health = saved.health + veh:TakeDamage(math.min(veh:GetCurHealth() - 1, veh:GetMaxHealth() * (1 - health))) + + if health <= 0.05 then -- crash light signals + net.Start('simfphys_turnsignal') + net.WriteEntity(veh) + net.WriteInt(1, 32) + net.Broadcast() + end + end + + if cdData.police then + local mh = veh:GetMaxHealth() * 2 + veh:SetMaxHealth(mh) + veh:SetCurHealth(mh) + -- veh:SetMaxTorque(spData.Members.PeakTorque * 0.9) + veh.police = true + veh.snd_horn = 'octoteam/vehicles/police/warning.wav' + end + + if cdData.radioWhitelist and veh.Radio then + veh.Radio:SetStationsWhitelist(cdData.radioWhitelist) + end + + if saved.wheels then + for k,v in ipairs(veh.Wheels) do + v:SetDamaged(saved.wheels[k]) + end + end + + local members = spData.Members + if saved.rims or saved.camber then + local f = saved.rims and saved.rims[1] or members.CustomWheelModel + local r = saved.rims and saved.rims[2] or members.CustomWheelModel_R or f + simfphys.ApplyWheel(veh, saved.camber or 0, f, r) + end + + for k, v in pairs(saved.bg or cdData.default and cdData.default.bg or {}) do + veh:SetBodygroup(k, v) + end + if saved.inv then veh:ImportInventory(saved.inv) end + veh:UpdateInventory() + + if saved.susp then + simfphys.SetupSuspension(veh, { + saved.susp[1] or 0, + saved.susp[2] or members.FrontConstant or 25000, + saved.susp[3] or members.FrontDamping or 1500, + saved.susp[4] or 0, + saved.susp[5] or members.RearConstant or 25000, + saved.susp[6] or members.RearDamping or 1500 + }) + end + + if saved.wOffset then + simfphys.SetWheelOffset(veh, saved.wOffset[1] or 0, saved.wOffset[2] or 0) + end + + if saved.torque then veh:SetMaxTorque(members.PeakTorque * saved.torque) end + if saved.grip then veh:SetMaxTraction(members.MaxGrip * saved.grip) end + if saved.steer then veh:SetSteerSpeed(members.TurnSpeed * saved.steer) end + if saved.csteer then veh.CounterSteeringMul = saved.csteer end + if saved.turbo ~= nil then veh:SetTurboCharged(saved.turbo) end + if saved.super ~= nil then veh:SetSuperCharged(saved.super) end + end) + + return veh + +end + +function carDealer.despawnVeh(veh) + + if not IsValid(veh) then return end + + carDealer.saveVeh(veh) + veh.handledRemove = true + veh:Remove() + +end + +function carDealer.storeVeh(veh, callback) + + callback = callback or octolib.func.zero + if not IsValid(veh) then return callback(false) end + + -- already being saved in EntityRemoved + -- octolib.func.chain({ + -- function(done) + -- carDealer.saveVeh(veh, done) + -- end, + -- function() + carDealer.despawnVeh(veh) + callback(true) + -- end, + -- }) + +end + +hook.Add('car-dealer.plateChanged', 'car-dealer.logToDb', function(id, owner, newPlate) + octolib.db:PrepareQuery('insert into cardealer_plates (vehicleID, garage, plate, changeDate) values (?, ?, ?, ?)', + {id, owner, string.upper(newPlate), os.time()}) +end) + +function carDealer.getPlateChangeHistory(id, garage, done) + octolib.func.chain({ + function(done) + octolib.db:PrepareQuery([[select count(*) as count from cardealer_plates where garage = ?]], {garage}, function(q, st, res) + done(istable(res) and res[1] and res[1].count or 0) + end) + end, + function(_, gCount) + octolib.db:PrepareQuery([[select plate from cardealer_plates where vehicleID = ? order by changeDate desc limit 20]], {id}, function(q, st, res) + local hist = {} + for _,v in ipairs(res or {}) do + hist[#hist + 1] = v.plate + end + done(gCount, hist) + end) + end, + }) +end diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_hooks.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_hooks.lua new file mode 100644 index 0000000..1824533 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_hooks.lua @@ -0,0 +1,143 @@ +hook.Add('PlayerInitialSpawn', 'car-dealer', function(ply) + + timer.Simple(15, function() + if not IsValid(ply) then return end + carDealer.sync(ply) + + for _, ent in ipairs(ents.FindByClass('gmod_sent_vehicle_fphysics_base')) do + if ent:CPPIGetOwner() == ply then + simfphys.SetOwner(ply, ent) + carDealer.setCurVeh(ply, ent) + break + end + end + end) + + timer.Remove('storeVehicle_' .. ply:SteamID()) + +end) + +hook.Add('PlayerDisconnected', 'car-dealer', function(ply) + local veh = carDealer.getCurVeh(ply) + if not IsValid(veh) then return end + + if not veh.deposit then + timer.Create('storeVehicle_' .. ply:SteamID(), 10 * 60, 1, function() + carDealer.storeVeh(veh) + end) + + ply:SetDBVar('nextCar', os.time() + 10 * 60 + 30) + else + carDealer.storeVeh(veh) + end +end) + +hook.Add('EntityRemoved', 'car-dealer', function(ent) + + if ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + + if ent.deposit then + carDealer.returnDeposit(ent) + else + carDealer.saveVeh(ent) + end + + local ply = carDealer.getOwner(ent) + if not IsValid(ply) then return end + + ply:SetNetVar('cd.vehicle') + ply:SetLocalVar('cd.vehID') + ply:SetLocalVar('cd.vehName') + carDealer.sync(ply) + carDealer.clearMarkers(ply) + +end) + +local idleTime = 20 +local function someoneInside(veh) + for _, v in ipairs(veh:GetChildren()) do + if v:GetClass() ~= 'prop_vehicle_prisoner_pod' then continue end + local dr = v:GetDriver() + if IsValid(dr) and not dr:IsAFK() then return true end + end + return false +end + +timer.Create('octocars.removeIdle', 60, 0, function() + + local ct = CurTime() + local vehs = ents.FindByClass('gmod_sent_vehicle_fphysics_base') + + for _, veh in ipairs(vehs) do + + if veh.doNotEvacuate and not veh.idleScore then continue end + if someoneInside(veh) then continue end + + local pos = veh:GetPos() + if veh.idleLastPos then + local moved = false + if veh.idleLastPos:DistToSqr(pos) < 10 then + veh.idleScore = (veh.idleScore or 0) + 1 + else + if veh.doNotEvacuate then + veh.idleScore = nil + continue + end + veh.idleScore = math.max((veh.idleScore or 0) - 3, 0) + moved = true + end + + local ply = carDealer.getOwner(veh) or veh:CPPIGetOwner() + if veh.idleScore >= idleTime then + if ct < (veh.despawnAfter or 0) then return end + carDealer.storeVeh(veh) + if IsValid(ply) then + ply:Notify(L.car_evacuated) + end + continue + elseif veh.idleScore == idleTime - 5 and IsValid(ply) and not moved then + ply:Notify(L.car_evacuate) + end + end + + veh.idleLastPos = pos + end + +end) + +hook.Add('OnPlayerChangedTeam', 'car-dealer.refresh', function(ply, old, new) + + local block = false + local cp = GAMEMODE.CivilProtection + if cp then block = cp[new] end + if RPExtraTeams[new].hobo then + block = true + end + ply:SetLocalVar('cd.noPrivate', block) + carDealer.sync(ply) + + if new == TEAM_ADMIN then return end + local curVeh = carDealer.getCurVeh(ply) + if IsValid(curVeh) then + if not carDealer.canUse(ply, curVeh.cdClass) then + timer.Create('storeVehicle_' .. ply:SteamID(), 5 * 60, 1, function() + carDealer.storeVeh(curVeh) + carDealer.notify(ply, 'warning', L.car_in_garage_unaivaible) + end) + return + end + + if cp and cp[old] ~= cp[new] and new ~= TEAM_ADMIN and old ~= TEAM_ADMIN then + carDealer.storeVeh(curVeh) + carDealer.notify(ply, 'warning', cp[old] and L.your_service_car_evacuated or L.your_car_evacuated) + elseif RPExtraTeams[new].hobo then + carDealer.storeVeh(curVeh) + carDealer.notify(ply, 'warning', L.your_car_evacuated) + end + end + +end) + +hook.Add('playerSellVehicle', 'car-dealer', function() + return false, 'Автомобиль можно продать только у дилера' +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_menu.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_menu.lua new file mode 100644 index 0000000..cef0c6f --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_menu.lua @@ -0,0 +1,428 @@ +netstream.Hook('car-dealer.sync', function(ply) + carDealer.sync(ply) +end) + +local busyFor = {} +hook.Add('PlayerDisconnected', 'car-dealer.busy', function(ply) + busyFor[ply] = nil +end) + +function carDealer.Listen(channel, handler) + netstream.Listen(channel, function(reply, ply, ...) + + if busyFor[ply] then + carDealer.notify(ply, 'warning', 'Продавец машин сейчас занят, попробуй повторить попытку позже') + return reply() + end + busyFor[ply] = true + + handler(function(...) + if IsValid(ply) then busyFor[ply] = nil end + reply(unpack({...})) + end, ply, unpack({...})) + + end) +end + +local function tryQueuedSpawn(ply, idOrClass, callback) + + local queued = ply:GetLocalVar('car-dealer.queued') + queued = queued and queued[1] + if queued then + if queued ~= idOrClass then + carDealer.notify(ply, 'warning', 'Ты уже ожидаешь автомобиль. Дождись своей очереди или покинь ее') + callback() + end + + if ply:GetLocalVar('car-dealer.queuedReady') then + carDealer.spawningQueuedAuto = true + ply:Notify('admin', 0) + + if isnumber(queued) then + carDealer.spawnOwnedVeh(ply, queued, function(ok, veh) + carDealer.removeFromQueue(ply) + carDealer.spawningQueuedAuto = false + + if not ok then + carDealer.notify(ply, 'warning', veh or 'Не получилось создать автомобиль') + return callback() + end + + callback(veh) + end) + else + carDealer.spawnDepositVeh(ply, queued, function(ok, veh) + carDealer.removeFromQueue(ply) + carDealer.spawningQueuedAuto = false + + if not ok then + carDealer.notify(ply, 'warning', veh or 'Не получилось создать автомобиль') + return callback() + end + + callback(veh) + end) + end + else + carDealer.notify(ply, 'warning', 'Ты уже ожидаешь автомобиль. Дождись своей очереди или покинь ее') + callback() + end + end + + carDealer.notify(ply, 'Твой запрос поставлен в очередь. Ожидай уведомления о готовности автомобиля') + carDealer.addToQueue(ply, idOrClass) + + callback() + +end + +carDealer.Listen('car-dealer.spawn', function(reply, ply, id) + + if ply:IsGhost() or ply:GetLocalVar('cd.cantHaveOwned') then + carDealer.notify(ply, 'warning', 'Ты не можешь пригонять автомобили сейчас') + return reply() + end + + local veh = carDealer.getCurVeh(ply) + if IsValid(veh) then + carDealer.notify(ply, 'warning', 'Сначала надо загнать свой автомобиль') + carDealer.setCurVeh(ply, veh) + return reply() + end + + local nextCar = ply:GetDBVar('nextCar') + if nextCar and os.time() < nextCar then + carDealer.notify(ply, 'warning', 'Твой автомобиль находится в другой части города') + return reply() + end + + carDealer.getVehById(id, function(veh) + if not veh or veh.garage ~= ply:SteamID() then + carDealer.notify(ply, 'warning', 'Ты не владеешь этим автомобилем') + return reply() + end + + local class = veh and veh.class + local cdData = carDealer.vehicles[class] + if not cdData then + carDealer.notify(ply, 'warning', 'Не получилось найти данные об автомобиле') + return reply() + end + + local category = carDealer.categories[cdData.category] + local ok, why = carDealer.canUse(ply, class) + if not ok or not category then + carDealer.notify(ply, 'warning', why or 'Ты не можешь использовать этот автомобиль') + return reply() + end + + if isfunction(category.spawnCheck) then + local can, why = category.spawnCheck(ply, class) + if can == false then + carDealer.notify(ply, why or 'Ты не можешь пригнать этот автомобиль сейчас') + return reply() + end + end + + if category.queue then + tryQueuedSpawn(ply, id, function(veh) + if not veh then return reply() end + + carDealer.notify(ply, (veh.cdData.name or 'Автомобиль') .. ' ждет тебя неподалеку. Забери его в течение 10 минут, или автомобиль эвакуируют') + veh.despawnAfter = CurTime() + 15 * 60 + end) + else + carDealer.spawnOwnedVeh(ply, id, function(ok, veh) + if ok then + carDealer.notify(ply, (veh.cdData.name or 'Автомобиль') .. ' ждет тебя неподалеку. Забери его в течение 10 минут, или автомобиль эвакуируют') + else + carDealer.notify(ply, 'warning', veh or 'Не получилось создать автомобиль') + end + + carDealer.sync(ply) + reply() + end) + end + end) + +end) + +carDealer.Listen('car-dealer.despawn', function(reply, ply) + + local queued = ply:GetLocalVar('car-dealer.queued') + if queued then + carDealer.spawningQueuedAuto = false + carDealer.removeFromQueue(ply) + ply:Notify('admin', 0) + carDealer.notify(ply, 'Ты покинул очередь') + return reply(true) + end + + local veh = carDealer.getCurVeh(ply) + if not IsValid(veh) or not veh.cdData then + carDealer.notify(ply, 'warning', 'У тебя нет автомобилей в городе') + return reply() + end + + if CurTime() < (veh.despawnAfter or 0) then + carDealer.notify(ply, 'warning', 'Автомобиль можно загнать не ранее 15 минут после получения') + return reply() + end + + if ply:IsGhost() then + carDealer.notify(ply, 'warning', 'Ты не можешь загонять автомобили сейчас') + return reply() + end + + local catData = carDealer.categories[veh.cdData.category] + local spawns = catData.spawns[game.GetMap()] + or carDealer.civilSpawns[game.GetMap()] + + if not spawns then + carDealer.notify(ply, 'warning', 'Напиши админам, что они опростоволосились, и передай привет от автодилера') + return reply() + end + + octolib.func.chain({ + function(done) + carDealer.nearestPos({ + pPos = veh:GetPos(), + vars = spawns, + okDist = 10000, + check = function() + return IsValid(ply) and IsValid(veh) + end, + callback = done, + filter = table.Add(table.Add({ply, veh}, veh:GetChildren()), constraint.GetAllConstrainedEntities(veh)), + }) + end, + function(done, pos, _, distSqr) + if not IsValid(ply) or not IsValid(veh) then return end + if not pos then + carDealer.notifty(ply, 'warning', 'Не получилось найти свободное место') + return reply(true) + end + if distSqr > 10000 then + carDealer.notify(ply, 'Оставь свой автомобиль на указанной точке и нажми "Загнать" снова. Если оно будет занято, повтори это действие') + ply:AddMarker({ + id = 'car.despawn', + txt = 'Парковочное место', + pos = pos + Vector(0,0,10), + col = Color(255,92,38), + des = {'timedist', {600, 100}}, + icon = 'octoteam/icons-16/car.png', + }) + reply(true) + else done() end + end, + function(done) + carDealer.saveVeh(veh, done) + end, + function(done) + hook.Run('car-dealer.stored', veh, ply) + carDealer.despawnVeh(veh, done) + carDealer.notify(ply, (veh.cdData.name or 'Автомобиль') .. ' в гараже') + carDealer.clearMarkers(ply) + carDealer.sync(ply) + reply(true) + end, + }) + +end) + +carDealer.Listen('car-dealer.rent', function(reply, ply, class) + + if ply:IsGhost() then + carDealer.notify(ply, 'warning', 'Ты не можешь пригонять автомобили сейчас') + return reply() + end + + local veh = carDealer.getCurVeh(ply) + if IsValid(veh) then + carDealer.notify(ply, 'warning', 'Сначала надо загнать свой автомобиль') + carDealer.setCurVeh(ply, veh) + return reply() + end + + local cdData = carDealer.vehicles[class] + if not cdData then + carDealer.notify(ply, 'warning', 'Не получилось найти данные об автомобиле') + return reply() + end + + local category = carDealer.categories[cdData.category] + local ok, why = carDealer.canUse(ply, class) + if not ok or not category or not cdData.deposit then + carDealer.notify(ply, 'warning', why or 'Ты не можешь арендовать этот автомобиль') + return reply() + end + + if isfunction(category.spawnCheck) then + local can, why = category.spawnCheck(ply, class) + if can == false then + carDealer.notify(ply, why or 'Ты не можешь пригнать этот автомобиль сейчас') + return reply() + end + end + + local price = hook.Run('car-dealer.priceOverride', ply, class) or cdData.price or carDealer.defaultDeposit + if not carDealer.hasMoney(ply, price) then + carDealer.notify(ply, 'warning', 'На банковском счете недостаточно средств') + return reply() + end + + if category.queue then + tryQueuedSpawn(ply, class, function(veh) + if not veh then return reply() end + + carDealer.addMoney(ply, -price) + carDealer.notify(ply, 'С твоего банковского счета снят залог за ' .. (veh.cdData.name or 'автомобиль') .. ' в размере ' .. carDealer.formatMoney(price)) + carDealer.notify(ply, (cdData.name or 'Автомобиль') .. ' ждет тебя неподалеку. Забери его в течение 10 минут, или автомобиль эвакуируют') + veh.deposit = price + reply(true) + end) + else + carDealer.spawnDepositVeh(ply, class, function(ok, veh) + if not ok then + carDealer.notify(ply, 'warning', veh or 'Не получилось создать автомобиль') + return reply() + end + + carDealer.addMoney(ply, -price) + carDealer.notify(ply, 'С твоего банковского счета снят залог за ' .. (veh.cdData.name or 'автомобиль') .. ' в размере ' .. carDealer.formatMoney(price)) + carDealer.notify(ply, (cdData.name or 'Автомобиль') .. ' ждет тебя неподалеку. Забери его в течение 10 минут, или автомобиль эвакуируют') + veh.deposit = price + reply(true) + end) + end + +end) + +carDealer.Listen('car-dealer.buy', function(reply, ply, class, bg) + + class = class or '' + bg = bg or {} + + if ply:IsGhost() then + carDealer.notify(ply, 'warning', 'Ты не можешь покупать автомобили сейчас') + return reply() + end + + local cdData = carDealer.vehicles[class] + local category = cdData and carDealer.categories[cdData.category] + + local ok, why = carDealer.canBuy(ply, class) + if not ok or not category then + carDealer.notify(ply, 'warning', why or 'Ты не можешь купить этот автомобиль') + return reply() + end + + if cdData.deposit then return reply() end + + local pr = hook.Run('car-dealer.priceOverride', ply, class, bg) or cdData.price + + local bgDefault = cdData.default and cdData.default.bg + local bgData = cdData.bodygroups + if not bgData then + bg = {} + elseif bg then + for k, v in pairs(bg) do + local variant = bgData[k] and bgData[k].variants[v + 1] + if not variant then + bg[k] = nil + continue + end + + local originalVal = bgDefault and bgDefault[k] or 0 + if v ~= originalVal then + pr = pr + variant[2] + end + end + end + + if bgDefault then + for k, v in pairs(bgDefault) do + if not bg[k] then bg[k] = v end + end + end + + if not carDealer.hasMoney(ply, pr) then + carDealer.notify(ply, 'warning', carDealer.formatMoney(pr) .. ' должны лежать у тебя в банке') + return reply() + end + + carDealer.addMoney(ply, -pr) + carDealer.ownVeh(ply:SteamID(), class, function(id, plate) + carDealer.notify(ply, 'Ты приобрел ' .. (cdData.name or 'Автомобиль') .. ' с регистрационным номером ' .. plate) + hook.Run('car-dealer.bought', class, ply, pr, id) + + carDealer.updateVehData(id, { bg = bg }) + reply(true) + end) + +end) + +carDealer.Listen('car-dealer.sell', function(reply, ply, id) + + if not id then return end + if ply:IsGhost() then + return reply() + end + + local veh + octolib.func.chain({ + function(done) + local veh = carDealer.getCurVeh(ply) + if IsValid(veh) and veh:GetNetVar('cd.id') == id then + carDealer.notify(ply, 'Сначала надо загнать свой автомобиль') + return reply() + end + + carDealer.getVehById(id, done) + end, + function(done, _veh) + veh = _veh + if not veh or veh.garage ~= ply:SteamID() then + carDealer.notify(ply, 'warning', 'В твоем гараже нет этого автомобиля') + return reply() + end + + carDealer.unownVeh(id, done) + end, + function(_, unowned) + if not unowned then return reply() end + + local toSend = {} + for _, att in ipairs(veh.data.atts or {}) do + toSend[#toSend + 1] = {'car_att', { + name = att.name, + desc = att.desc, + icon = att.icon, + mass = att.mass, + volume = att.volume, + colorable = att.colorable, + attmdl = att.model, + model = att.model, + skin = att.skin, + scale = att.scale, + }} + end + + if #toSend > 0 then + octoinv.addReturnItems(ply, toSend) + carDealer.notify(ply, 'hint', 'На автомобиле были аксессуары, ты можешь их вернуть через магазин') + end + + local cdData = carDealer.vehicles[veh.class] + local price = cdData.price * carDealer.sellPrice + carDealer.addMoney(ply, price) + carDealer.notify(ply, 'Ты продал ' .. (cdData.name or 'Автомобиль') .. ' за ' .. DarkRP.formatMoney(price)) + if ply:GetLocalVar('car-dealer.queued') and ply:GetLocalVar('car-dealer.queued')[1] == id then + carDealer.removeFromQueue(ply) + end + hook.Run('car-dealer.sold', veh, ply, price) + reply(true) + end, + }) + +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_player.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_player.lua new file mode 100644 index 0000000..508f81f --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_player.lua @@ -0,0 +1,348 @@ +function carDealer.setCurVeh(ply, veh) + + -- use table because 'pon.entityCreated' + ply:SetNetVar('cd.vehicle', { veh }) + + veh:SetNetVar('cd.owner', nil) + veh:SetNetVar('cd.owner', ply) + +end + +function carDealer.getCurVehID(ply) + + local veh = carDealer.getCurVeh(ply) + return veh and veh:GetNetVar('cd.id') or nil + +end + +function carDealer.getAvailableCategories(ply) + + local result = {} + for k,v in pairs(carDealer.categories) do + if (not isfunction(v.canUse) or v.canUse(ply) ~= false) + and (not isfunction(v.canSee) or v.canSee(ply) ~= false) then + result[#result + 1] = k + end + end + + return result + +end + +function carDealer.clearMarkers(ply) + + if IsValid(ply) then + ply:ClearMarkers('car.spawn') + ply:ClearMarkers('car.despawn') + end + +end + +function carDealer.spawnOwnedVeh(ply, id, callback, force) + + callback = callback or octolib.func.zero + + local veh = carDealer.getCurVeh(ply) + if IsValid(veh) then + return callback(false, 'Сначала надо загнать свой автомобиль') + end + + local class, garage, dbData, cdData + octolib.func.chain({ + function(done) -- checks and stuff + carDealer.getVehById(id, done) + end, + function(done, data) -- prepare entity + dbData = data or {} + class, garage = dbData.class, dbData.garage + + if not dbData or (garage ~= ply:SteamID() and not force) then + return callback(false, 'Ты не приобрел этот автомобиль') + end + + cdData = class and carDealer.vehicles[class] + if not cdData or not carDealer.canUse(ply, class) then return callback(false) end + + local catData = cdData.category and carDealer.categories[cdData.category] + if not catData then return callback(false) end + + local spawns = catData.spawns[game.GetMap()] + or carDealer.civilSpawns[game.GetMap()] + + if not spawns then + return callback(false, 'Напиши админам, что они опростоволосились, и передай привет от автодилера') + end + + carDealer.notify(ply, 'Поиск свободного места...') + carDealer.nearestPos({ + pPos = ply:GetPos(), + vars = spawns, + check = function() + return IsValid(ply) + end, + callback = done, + }) + end, + function(_, pos, ang) + if not IsValid(ply) then return callback(false) end + if not pos then return callback(false, 'Не получилось найти свободное место') end + + local veh = carDealer.spawnVeh(class, pos, ang, dbData.data) + if not IsValid(veh) then return callback(false, 'Не получилось создать автомобиль') end + + local mins = veh:GetCollisionBounds() + local tr = util.TraceLine({ + start = pos, + endpos = pos + Vector(0, 0, -1000), + collisiongroup = COLLISION_GROUP_WORLD, + }) + + if tr.Hit then + veh:SetPos(tr.HitPos + Vector(0, 0, 20 - mins.z)) + end + + veh:SetNetVar('cd.id', id) + simfphys.SetOwner(ply, veh) + veh:Lock() + + carDealer.setCurVeh(ply, veh) + veh:SetNetVar('cd.plate', dbData.plate) + + veh.idleScore = 10 + carDealer.clearMarkers(ply) + ply:AddMarker({ + id = 'car.spawn', + txt = cdData.name or 'Автомобиль', + pos = veh:GetPos() + Vector(0,0,10), + col = Color(255,92,38), + des = {'timedist', {600, 300}}, + icon = 'octoteam/icons-16/car.png', + }) + + if dbData.plate then + hook.Run('car-dealer.spawnedOwned', veh, ply) + callback(true, veh) + else + carDealer.firstAvailablePlate(function(plate) + veh:SetNetVar('cd.plate', plate) + hook.Run('car-dealer.spawnedOwned', veh, ply) + callback(true, veh) + end) + end + end, + }) + +end + +function carDealer.spawnDepositVeh(ply, class, callback) + + callback = callback or octolib.func.zero + + local veh = carDealer.getCurVeh(ply) + if IsValid(veh) then + return callback(false, 'Сначала надо загнать свой автомобиль') + end + + local cdData = class and carDealer.vehicles[class] + if not cdData or not carDealer.canUse(ply, class) then return callback(false) end + + local catData = cdData.category and carDealer.categories[cdData.category] + if not catData then return callback(false) end + + local spawns = catData.spawns[game.GetMap()] + or carDealer.civilSpawns[game.GetMap()] + + if not spawns then + return callback(false, 'Напиши админам, что они опростоволосились, и передай привет от автодилера') + end + + carDealer.nearestPos({ + pPos = ply:GetPos(), + vars = spawns, + maxAttempts = 1, + check = function() + return IsValid(ply) + end, + callback = function(pos, ang) + + if not pos then + return callback(false, 'Не получилось найти свободное место') + end + + local veh = carDealer.spawnVeh(class, pos, ang) + if not IsValid(veh) then return callback(false, 'Не получилось создать автомобиль') end + + local mins = veh:GetCollisionBounds() + local tr = util.TraceLine({ + start = pos, + endpos = pos + Vector(0, 0, -1000), + collisiongroup = COLLISION_GROUP_WORLD, + }) + + if tr.Hit then + veh:SetPos(tr.HitPos + Vector(0, 0, 20 - mins.z)) + end + + simfphys.SetOwner(ply, veh) + veh:Lock() + + carDealer.setCurVeh(ply, veh) + veh.steamID = ply:SteamID() + + veh.idleScore = 10 + carDealer.clearMarkers(ply) + ply:AddMarker({ + id = 'car.spawn', + txt = cdData.name or 'Автомобиль', + pos = veh:GetPos() + Vector(0,0,10), + col = Color(255,92,38), + des = {'timedist', {600, 300}}, + icon = 'octoteam/icons-16/car.png', + }) + + carDealer.firstAvailablePlate(function(plate) + veh:SetNetVar('cd.plate', plate) + hook.Run('car-dealer.spawnedDeposit', veh, ply) + callback(true, veh) -- call outer callback + end) + + end, + }) + +end + +function carDealer.returnDeposit(ent) + + local ply = ent:GetNetVar('cd.owner') + local account = IsValid(ply) and ply or ent.steamID + if not account then return end + + local amount = carDealer.getCurVehPrice(ent) + local name = ent.cdData.name + hook.Run('car-dealer.returnedDeposit', ent, ply, amount) + BraxBank.PlayerMoneyAsync(account, function(money) + BraxBank.UpdateMoney(account, money + amount) + if IsValid(ply) then + carDealer.notify(ply, 'hint', 'Возврат залога за ' .. name .. ': ' .. carDealer.formatMoney(amount)) + end + end) + +end + +function carDealer.sync(ply) + + if not IsValid(ply) then return end + carDealer.getGarage(ply:SteamID(), function(garage) + local categories = carDealer.getAvailableCategories(ply) + netstream.Start(ply, 'car-dealer.sync', garage, categories) + end) + +end + +function carDealer.canUseCategory(ply, catID) + + if not catID then return false end + local catData = carDealer.categories[catID] + if not catData then return false end + + if catData.canUse then + local ok, why = catData.canUse(ply) + if ok == false then + return false, why or 'Ты не можешь использовать эту категорию' + end + end + + return true + +end + +function carDealer.canUse(ply, class) + + local cdData = carDealer.vehicles[class] + if not cdData then return false, 'Этой модели автомобиля не существует' end + + if cdData.canUse then + local ok, why = cdData.canUse(ply) + if ok == false then + return false, why or 'Ты не можешь использовать этот автомобиль' + end + end + + return carDealer.canUseCategory(ply, cdData.category) + +end + +function carDealer.canBuy(ply, class) + + local cdData = carDealer.vehicles[class] + if not cdData then return false, 'Этой модели автомобиля не существует' end + + local canUse, canUseWhy = carDealer.canUse(ply, class) + if not canUse then return canUse, canUseWhy end + + if cdData.canBuy then + local ok, why = cdData.canBuy(ply) + if ok == false then + return false, why or 'Ты не можешь купить этот автомобиль' + end + end + + if cdData.canSee then + local ok, why = cdData.canSee(ply) + if ok == false then + return false, why or 'А-та-та' + end + end + + -- dont check for nil as it's done in canUseClass + local catData = carDealer.categories[cdData.category] + + if catData.canBuy then + local ok, why = catData.canBuy(ply) + if ok == false then + return false, why or 'Ты не можешь купить этот автомобиль' + end + end + + if catData.canSee then + local ok, why = catData.canSee(ply) + if ok == false then + return false, why or 'А-та-та' + end + end + + return true + +end + +function carDealer.resetPlate(ent, returnItem, admin) + local id, owner = ent:GetNetVar('cd.id'), ent:CPPIGetOwner() + if not id or not owner then return end + local oldPlate = ent:GetNetVar('cd.plate') + octolib.func.chain({ + carDealer.firstAvailablePlate, + function(done, plate) + carDealer.updateVehData(id, {plate = plate}, function() + done(plate) + end) + end, + function(done, plate) + ent:SetNetVar('cd.plate', plate) + owner:Notify('Администрация сбросила номер ' .. tostring(oldPlate) .. ' на твоем автомобиле') + if returnItem then + owner:osGiveItem('car_plate', function() + owner:Notify('Плюшка "Блатной номер" была возвращена') + done() + end) + else + owner:Notify('Плюшка "Блатной номер" возвращена не будет') + done() + end + end, + function() + if admin then + hook.Run('car-dealer.resetPlate', admin, ent, oldPlate, returnItem) + end + end, + }) +end diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_queue.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_queue.lua new file mode 100644 index 0000000..cebed16 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_queue.lua @@ -0,0 +1,70 @@ +local maxCars = carDealer.maxCars[game.GetMap()] or carDealer.maxCars.default +carDealer.queue = carDealer.queue or {} + +function carDealer.addToQueue(ply, id) + + if ply:GetLocalVar('car-dealer.queued') then + carDealer.removeFromQueue(ply) + end + + carDealer.queue[#carDealer.queue + 1] = ply + ply:SetLocalVar('car-dealer.queued', {id, #carDealer.queue}) + ply:SetLocalVar('car-dealer.queuedReady') + +end + +function carDealer.removeFromQueue(ply) + + local key = table.RemoveByValue(carDealer.queue, ply) + if key then + for i = key, #carDealer.queue do + local v = carDealer.queue[i] + v:SetLocalVar('car-dealer.queued', {v:GetLocalVar('car-dealer.queued')[1], i}) + end + end + + if IsValid(ply) then + ply:SetLocalVar('car-dealer.queued') + ply:SetLocalVar('car-dealer.queuedReady') + end + +end +hook.Add('PlayerDisconnected', 'car-dealer.queue', carDealer.removeFromQueue) + +timer.Create('car-dealer.queue', CFG.dev and 5 or 30, 0, function() + + if carDealer.spawningQueuedAuto then return end + + local ply = carDealer.queue[1] + while ply do + if #carDealer.queue < 1 then return end + if not IsValid(ply) then + table.remove(carDealer.queue, 1) + for i,v in ipairs(carDealer.queue) do + v:SetLocalVar('car-dealer.queued', {v:GetLocalVar('car-dealer.queued')[1], i}) + end + ply = carDealer.queue[1] + elseif ply:GetLocalVar('car-dealer.queuedReady') then + carDealer.notify(ply, 'warning', 'Ты не подтвердил машину вовремя и был исключен из очереди') + carDealer.removeFromQueue(ply) + ply = carDealer.queue[1] + else + break + end + end + if not ply then return end + + local count = 0 + for _, v in ipairs(ents.FindByClass('gmod_sent_vehicle_fphysics_base')) do + local cat = carDealer.categories[v.cdData and v.cdData.category or ''] + if cat and cat.queue then + count = count + 1 + if count >= maxCars then return end + end + end + + ply:SetLocalVar('car-dealer.queuedReady', true) + ply:Notify('admin', CFG.dev and 5 or 30, 'Машина готова к доставке!', 'Открой меню гаража и подтверди получение, прежде чем закончится таймер под этим сообщением') + carDealer.sync(ply) + +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_refund.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_refund.lua new file mode 100644 index 0000000..8031239 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_refund.lua @@ -0,0 +1,153 @@ +local content = file.Read('dbg_car_refund.json') +if not content then return end + +local refundDB = util.JSONToTable(content) +local oldBeforeID = 3000 + +local function shouldSell(row) + return refundDB[row.class] ~= nil +end + +local function getSellInfoForCar(row) + + if not shouldSell(row) then return end + + local refundData = refundDB[row.class] + if not istable(row.data) then + row.data = pon.decode(row.data) + end + + local info = {} + table.insert(info, 1, { refundData.name .. ', рег. номер ' .. row.plate }) + table.insert(info, { 'Автомобиль', refundData.price }) -- vehicle itself + + local function addInfo(reason, price) + price = math.floor(price) + if price == 0 then return end + table.insert(info, { reason, price }) + end + + for k, v in pairs(row.data.bg or {}) do + local bgData = refundData.bgs and refundData.bgs[k] and refundData.bgs[k][v] + if bgData then addInfo(bgData.name, bgData.price) end + end + local rimsData = simfphys.rims[(row.data.rims or {})[1] or ''] + if rimsData then addInfo(rimsData.name, rimsData.price) end + + if row.id < oldBeforeID then addInfo('Старая модель (> 90 дней)', -refundData.price * 0.2) end + addInfo('Ремонт', math.floor(-50000 * (1 - (row.data.health or 1)))) + addInfo('Дозаправка', math.floor(-refundData.fuelPrice * (1 - (row.data.fuel or 1)))) + + return info + +end + +local function getTotalSellInfo(rows) + + local totalInfo, totalMoney = {}, 0 + local toSell = octolib.array.filter(rows, shouldSell) + + for i, row in ipairs(toSell) do + local thisInfo = getSellInfoForCar(row) + totalMoney = totalMoney + octolib.table.reduce(thisInfo, function(acc, line) return acc + (line[2] or 0) end, 0) + table.Add(totalInfo, thisInfo) + table.insert(totalInfo, { }) + end + + return totalMoney, totalInfo, toSell + +end + +local function sendSellInfo(ply) + octolib.func.chain({ + function(next) + octolib.db:PrepareQuery('select * from cardealer_owned where garage = ?', { ply:SteamID() }, next) + end, + function(next, q, st, rows) + if #rows < 1 then return end + local totalMoney, totalInfo = getTotalSellInfo(rows) + if #totalInfo < 1 then return end + + netstream.Start(ply, 'cd.refundOld', totalInfo) + ply.hasRefundableCars = true + end + }) +end +hook.Add('dbg-test.complete', 'car-dealer.refund', sendSellInfo) + +local function sellVehicles(ply) + if not ply.hasRefundableCars then return end + octolib.func.chain({ + function(next) + octolib.db:PrepareQuery('select * from cardealer_owned where garage = ?', { ply:SteamID() }, next) + end, + function(next, q, st, rows) + if #rows < 1 then return end + local totalMoney, totalInfo, toSell = getTotalSellInfo(rows) + if #toSell < 1 then return end + + carDealer.addMoney(ply, totalMoney) + carDealer.notify(ply, 'hint', ('На твой банковский счет зачислено %s за старые автомобили'):format(DarkRP.formatMoney(totalMoney))) + + local carAtts = {} + for _, row in ipairs(toSell) do + for _, att in ipairs(row.data and row.data.atts or {}) do + carAtts[#carAtts + 1] = {'car_att', { + name = att.name, + desc = att.desc, + icon = att.icon, + mass = att.mass, + volume = att.volume, + colorable = att.colorable, + attmdl = att.model, + model = att.model, + skin = att.skin, + scale = att.scale, + }} + end + + octolib.db:PrepareQuery('delete from cardealer_owned where id = ?', { row.id }) + end + + if #carAtts > 0 then + octoinv.addReturnItems(ply, carAtts) + carDealer.notify(ply, 'hint', 'На автомобилях были аксессуары, их можно вернуть через магазин') + end + + ply.hasRefundableCars = nil + carDealer.sync(ply) + end + }) +end +netstream.Hook('cd.refundOld', sellVehicles) + +-- local refundDB = {} +-- for id, vehData in pairs(carDealer.vehicles) do +-- if vehData.deposit or vehData.price == 0 then continue end + +-- local data = {} +-- refundDB[id] = data + +-- data.name = vehData.name +-- data.price = vehData.price +-- data.spID = vehData.simfphysID + +-- local bgs = simfphys.carBGs[data.spID] +-- if bgs then +-- data.bgs = {} +-- for i, v in ipairs(bgs) do +-- if i == 1 then continue end +-- local name, price, bgID, bgNum, mass, volume = unpack(v) +-- data.bgs[bgID] = data.bgs[bgID] or {} +-- data.bgs[bgID][bgNum] = { +-- name = name, +-- price = price, +-- } +-- end +-- end + +-- local spData = list.Get('simfphys_vehicles')[data.spID] +-- data.fuelPrice = (simfphys.fuelPrices[spData.Members.FuelType] or 0) * spData.Members.FuelTankSize +-- end + +-- file.Write('dbg_car_refund.json', util.TableToJSON(refundDB)) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/sv_test.lua b/garrysmod/addons/feature-cars/lua/car-dealer/sv_test.lua new file mode 100644 index 0000000..5a1bbab --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/sv_test.lua @@ -0,0 +1,89 @@ +if not CFG.dev then return end + +local maxGear = 10 +local function vehThink(self) + + if CurTime() >= (self.nextGearChange or 0) and self.CurrentGear < math.min(maxGear, #self.Gears) and self:GetRPM() / self:GetLimitRPM() > 0.8 then + self.PressedKeys['Alt'] = true + self.PressedKeys['W'] = false + self.PressedKeys['Shift'] = false + timer.Simple(0.5, function() + if not IsValid(self) then return end + self.CurrentGear = self.CurrentGear + 1 + self:SetGear(self.CurrentGear) + self.PressedKeys['Alt'] = false + self.PressedKeys['W'] = true + self.PressedKeys['Shift'] = true + end) + self.nextGearChange = CurTime() + 1.5 + end + +end + +concommand.Add('vehicle_test', function(ply, cmd, args) + + local duration = table.remove(args, 1) + + local nextPos = ply:GetEyeTrace().HitPos + Vector(0, 0, 50) + local dir = ply:GetAimVector() + dir.z = 0 + local ang = dir:Angle() + local dirShift = ply:GetRight() + + local spawned = {} + local function spawnVehs() + for _, class in ipairs(args) do + local cdData = carDealer.vehicles[class] + if not cdData then continue end + + local spData = list.Get('simfphys_vehicles')[cdData.simfphysID] + if not spData then continue end + + local veh = simfphys.SpawnVehicle(nil, nextPos, ang, spData.Model, spData.Class, class, spData, true) + veh.IsAutonomous = true + veh.OnTick = vehThink + veh:SetActive(true) + -- veh:SetProxyColors({ HSVToColor(math.random(0, 360), math.Rand(0.3, 0.8), math.Rand(0, 1)) }) + -- veh:SetLightsEnabled(true) + -- veh:SetLampsEnabled(true) + -- veh:SetEMSEnabled(true) + spawned[#spawned + 1] = veh + + -- local prop = ents.Create 'prop_physics' + -- prop:SetModel('models/hunter/blocks/cube2x2x2.mdl') + -- prop:SetPos(nextPos + ang:Forward() * 250) + -- prop:Spawn() + -- prop:GetPhysicsObject():SetMass(800) + -- prop:Activate() + -- spawned[#spawned + 1] = prop + + nextPos = nextPos + dirShift * 150 + end + end + + local function startVehs() + for _, veh in ipairs(spawned) do + if not IsValid(veh) or not veh.StartEngine then continue end + veh.keys = {} + veh:StartEngine() + veh.PressedKeys['W'] = true + veh.PressedKeys['Shift'] = true + end + end + + local function removeVehs(all) + for _, veh in ipairs(all and ents.FindByClass('gmod_sent_vehicle_fphysics_base') or spawned) do + veh:Remove() + end + end + + -- removeVehs(true) + spawnVehs() + timer.Simple(1, startVehs) + timer.Simple(duration, removeVehs) + + timer.Simple(0.5, function() + netstream.Start(ply, 'followVehicles', spawned, dir) + end) + +end) diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/vgui/menu.lua b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/menu.lua new file mode 100644 index 0000000..857ca6d --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/menu.lua @@ -0,0 +1,101 @@ +local PANEL = {} + +function PANEL:Init() + + carDealer.menu = self + self:Dock(FILL) + + self.lists = {} + self.viewer = self:Add 'cd_vehViewer' + self.list = self:Add 'cd_vehList' + +end + +hook.Add('car-dealer.sync', 'car-dealer.menu', function() + local m = carDealer.menu + if not IsValid(m) then return end + + m.pendingRefresh = true +end) + +hook.Add('pon.entityCreated', 'octolib.netVar', function(ent, tbl, key) + local vehTbl = LocalPlayer():GetNetVar('cd.vehicle') + if tbl == vehTbl then + local m = carDealer.menu + if IsValid(m) then m.pendingRefresh = true end + end +end) + +function PANEL:Paint() + -- no paint +end + +function PANEL:Refresh() + + self.list:Refresh() + self.viewer:Refresh() + +end + +function PANEL:SetMinimized(val) + + self.minimized = val + + if not val and self.pendingRefresh then + self:Refresh() + -- netstream.Start('car-dealer.sync') + end + +end + +function PANEL:Think() + + if self.pendingRefresh then + self.pendingRefresh = nil + self:Refresh() + end + +end + +-- function PANEL:Select(n) +-- local pan = self.vehs[n] +-- local pos = n-1 +-- if not pan then +-- pan = self.vehs[1] +-- pos = 0 +-- end +-- if pan then +-- pan:DoClick() +-- self.list:GetVBar():AnimateTo(pos * 64 - 32, 0, 0, 1) +-- end +-- end + +-- function PANEL:SetDealer(dealerID) + +-- local vl = self.list +-- vl:Clear() +-- self.vehs = {} + +-- if not carDealer.categories[dealerID] then return end + +-- for vehName, veh in SortedPairsByMemberValue(carDealer.vehicles, 'price') do +-- if veh.categories and not table.HasValue(veh.categories, dealerID) then continue end + +-- local b = vgui.Create 'cd_vehButton' +-- b:SetVehicle(vehName, nil, dealerID) +-- vl:AddItem(b) +-- self.vehs[#self.vehs+1] = b +-- end +-- self.dealerID = dealerID + +-- hook.Add('car-dealer.sync', 'menu', function() +-- if IsValid(self) then self:SetDealer(dealerID) end +-- end) + +-- end + +-- function PANEL:GetDealer() +-- return self.dealerID +-- end + +vgui.Register('cd_menu', PANEL, 'DPanel') diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehbutton.lua b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehbutton.lua new file mode 100644 index 0000000..9d70b08 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehbutton.lua @@ -0,0 +1,107 @@ +-- local cols = { +-- bg = Color(0,0,0, 100), +-- bgOwned = Color(50,100,35, 150), +-- bgSelected = Color(0,0,0, 120), +-- bgHover = Color(255,255,255, 10), +-- } + +surface.CreateFont('car-dealer.button.title', { + font = 'Roboto', + size = 20, + weight = 500, + antialias = true, + extended = true, +}) + +local PANEL = {} + +function PANEL:Init() + + self:SetText('') + self:Dock(LEFT) + self:DockMargin(0, 0, 5, 0) + self:SetWide(135) + + local m = self:Add 'cd_vehModel' + m:SetMouseInputEnabled(false) + self.mdl = m + + local l = m:Add 'DLabel' + l:Dock(BOTTOM) + l:SetTall(40) + l:SetContentAlignment(5) + l:SetFont('car-dealer.button.title') + l:SetText('Автомобиль') + self.title = l + + local tp = self:Add 'DPanel' + tp:SetPaintBackground(false) + tp:SetPos(20, 5) + tp:SetSize(95, 16) + tp:SetTall(16) + self.tags = tp + +end + +function PANEL:AddTag(tag) + local icon = self.tags:Add 'DImageButton' + icon:Dock(RIGHT) + icon:DockMargin(5, 0, 0, 0) + icon:SetWide(16) + icon:SetImage(tag[1]) + icon:AddOctoHint(tag[2]) + self.tags:SetVisible(true) +end + +function PANEL:SetVehicle(vehID, data) + + data = data or {} + + local cdData = carDealer.vehicles[vehID] + if not cdData then return end + + self.vehID = vehID + self.mdl:SetVehicle(vehID, data) + self.title:SetText(data.plate or cdData.name) + + self.tags:Clear() + self.tags:SetVisible(false) + local tags = table.Copy(cdData.tags or {}) + hook.Run('car-dealer.populateTags', vehID, cdData, tags) + for _, tag in ipairs(tags) do + if not data.id or tag[3] then self:AddTag(tag) end + end + + self.data = data + +end + +function PANEL:Paint() + + -- nothing... yet + +end + +function PANEL:DoClick() + + carDealer.menu.viewer:SetVehicle(self.vehID, self.data) + + -- local vehName = self.vehName + -- if not vehName then return end + + -- local me + -- for k,p in ipairs(self:GetParent():GetChildren()) do + -- p.selected = nil + -- if p == self then me = k end + -- end + -- self.selected = true + -- carDealer.perCategorieSels[self.dealer] = self.vehID or me + + -- local pnl = carDealer.curPage + -- if not IsValid(pnl) or not IsValid(carDealer.viewer) then return end + + -- carDealer.viewer:SetVeh(self.vehData or self.vehName, self.vehID) + +end + +vgui.Register('cd_vehButton', PANEL, 'DButton') diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehlist.lua b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehlist.lua new file mode 100644 index 0000000..bbb230f --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehlist.lua @@ -0,0 +1,219 @@ +local PANEL = {} + +function PANEL:Init() + + self:Dock(BOTTOM) + self:SetTall(140) + +end + +function PANEL:Refresh() + + if self.lastOwned then + self.changesOwned = octolib.table.diff(self.lastOwned, carDealer.cache.owned) + self.changesCategories = octolib.table.diff(self.lastCategories, carDealer.cache.categories) + else + self.changesOwned = carDealer.cache.owned + self.changesCategories = carDealer.cache.categories + end + self.lastCategories = carDealer.cache.categories + self.lastOwned = carDealer.cache.owned + + self:RefreshGarage() + + local items = self:GetItems() + for i = #items, 2, -1 do + local item = items[i] + if item and IsValid(item.Tab) and #self.changesCategories > 0 then + self:CloseTab(item.Tab, true) + end + end + + if not carDealer.cache.categories then return end + + if #self.changesCategories > 0 then + for _, catID in pairs(carDealer.cache.categories) do + self:AddCategory(catID) + end + end + + self:SwitchToName(self.lastTab) + +end + +function PANEL:AddCategory(catID) + + local data = carDealer.categories[catID] + if not data then return end + + local ply = LocalPlayer() + if data.canSee and data.canSee(ply) == false then return end + + local name, icon = data.name, data.icon + local p = vgui.Create 'DPanel' + local item = self:AddSheet(name, p, icon) + item.Tab.catID = catID + + local scr = p:Add 'DPanel' + scr:SetPaintBackground(false) + scr:DockPadding(5, 0, 5, 0) + function scr:PerformLayout() + self:SetTall(self:GetParent():GetTall()) + end + scr:SetWide(10) + + local totalVehicles = 0 + for vehID, veh in SortedPairsByMemberValue(carDealer.vehicles, 'price', false) do + if (not veh.category or veh.category == catID) and (not veh.canSee or veh.canSee(ply) ~= false) then + local but = scr:Add 'cd_vehButton' + but:SetVehicle(vehID) + scr:SetWide(scr:GetWide() + but:GetWide() + 5) + totalVehicles = totalVehicles + 1 + end + end + + carDealer.menu.lists[catID] = scr + +end + +function PANEL:RefreshGarage() + + local tab = self:GetItems()[1] + if not tab then + local p = vgui.Create 'DPanel' + tab = self:AddSheet('Мой гараж', p, 'icon16/house.png') + carDealer.menu.lists._garage = p + + if not carDealer.cache.owned then return end + + local scr = p:Add 'DPanel' + scr:SetPaintBackground(false) + scr:DockPadding(5, 0, 5, 0) + function scr:PerformLayout() + self:SetTall(self:GetParent():GetTall()) + end + scr:SetWide(10) + p.scr = scr + end + + local scr = tab.Panel.scr + for _, pnl in ipairs(scr:GetChildren()) do + if pnl.data and pnl.data.id and self.changesOwned[pnl.data.id] then + pnl:Remove() + end + end + + for id, veh in pairs(carDealer.cache.owned) do + if self.changesOwned[id] then + local but = scr:Add 'cd_vehButton' + but:SetVehicle(veh.class, veh) + scr:SetWide(scr:GetWide() + but:GetWide() + 5) + + if not carDealer.menu.viewer.class then + but:DoClick() + end + end + end + + self.activeScroller = scr + timer.Simple(0.5, function() + self:UpdateCamPositions() + end) + +end + +function PANEL:OnActiveTabChanged(_, new) + + self.lastTab = new:GetText() + self.activeScroller = new:GetPanel():GetChild(0) + timer.Simple(0, function() + self:UpdateCamPositions() + end) + +end + +function PANEL:UpdateCamPositions() + + if not self.activeScroller then return end + + local x = -self.activeScroller:GetPos() + local w = self.activeScroller:GetParent():GetSize() + for _, but in ipairs(self.activeScroller:GetChildren()) do + local bx = but:GetPos() + local bx2 = bx + but:GetWide() + if bx2 > x and bx < x + w then + local off = w / 2 - ((bx + bx2) / 2 - x) + local pos = Angle(-10, 90 + off * 0.09, 0):Forward() * 450 + but.mdl:SetCamPos(pos - Vector(0, 0, 15)) + but.mdl:SetLookAng((-pos):Angle()) + end + end + +end + +local scrollAreaSize = 40 +function PANEL:Think() + + local scr = self.activeScroller + if not IsValid(scr) then return end + + local cx, cy = self:LocalCursorPos() + if octolib.math.inRange(cy, 0, self:GetTall()) then + local w, updated = self:GetWide(), false + if octolib.math.inRange(cx, 0, scrollAreaSize) then + local x, y = scr:GetPos() + local newX = math.Clamp(x + math.ceil(FrameTime() * 250), w - scr:GetWide() - 10, 0) + if newX > x then + scr:SetPos(newX, y) + updated = true + end + elseif octolib.math.inRange(cx, w - scrollAreaSize, w) then + local x, y = scr:GetPos() + local newX = math.Clamp(x - math.ceil(FrameTime() * 250), w - scr:GetWide() - 10, 0) + if newX < x then + scr:SetPos(newX, y) + updated = true + end + end + + if updated then + self:UpdateCamPositions() + end + end + +end + +function PANEL:CloseTab(tab, removePnl) + + for k, v in pairs(self.Items) do + if v.Tab == tab then + table.remove(self.Items, k) + end + end + + for k, v in pairs(self.tabScroller.Panels) do + if v == tab then + table.remove(self.tabScroller.Panels, k) + end + end + + self.tabScroller:InvalidateLayout(true) + + if tab == self:GetActiveTab() then + local nextItem = self.Items[#self.Items] + self.m_pActiveTab = nextItem and nextItem.Tab or nil + end + + local pnl = tab:GetPanel() + if removePnl then + pnl:Remove() + end + + tab:Remove() + self:InvalidateLayout(true) + + return pnl + +end + +vgui.Register('cd_vehList', PANEL, 'DPropertySheet') diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehmodel.lua b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehmodel.lua new file mode 100644 index 0000000..19201ae --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehmodel.lua @@ -0,0 +1,188 @@ +local defaultColors = { Color(255,255,255), Color(255,255,255), Color(0,0,0), Color(255,255,255) } + +local PANEL = {} + +function PANEL:Init() + + self:Dock(FILL) + self.vRawCamPos = Vector() + self.tgtLookAngle = Angle() + self.camPos = Vector(200, 400, 50) + +end + +function PANEL:SetVehicle(vehID, data) + + data = data or {} + + local cdData = carDealer.vehicles[vehID] + if not cdData then return end + + self.vehID = vehID + + local spData = list.Get('simfphys_vehicles')[cdData.simfphysID] + assert(spData ~= nil, 'Wrong simfphysID for ' .. vehID) + + self:Cleanup() + local default = cdData.default + + self:SetModel(spData.Model) + local ent = self.Entity + ent:SetPos(-ent:OBBCenter() + (cdData.previewOffset or Vector())) + ent:SetAngles(Angle(0, cdData.SpawnAngleOffset or spData.SpawnAngleOffset or 0, 0)) + local cols = default and default.col or defaultColors + self.Entity:SetProxyColors({ cols[1], cols[2], CFG.reflectionTint, cols[4] }) + self.Entity:SetSkin(default and default.skin or 0) + carDealer.attachWheels(ent, vehID, data.data) + self:SetCamPos(self.camPos) + self.vRawCamPos = self.camPos + self:SetLookAng((-self.camPos):Angle()) + self.tgtLookAngle = self.aLookAngle + self:SetFOV((cdData.customFOV or 30) * (self.fovMultiplier or 1)) + + for k, v in pairs(default and default.bg or {}) do + ent:SetBodygroup(k, v) + end + + local status = data.data + if status then + local ent = self.Entity + if status.col then + self.Entity:SetProxyColors({ status.col[1], status.col[2], CFG.reflectionTint, status.col[4] }) + end + if status.skin then ent:SetSkin(status.skin) end + if status.bg then + for k, v in pairs(status.bg) do + ent:SetBodygroup(k, v) + end + end + + if status.atts then + carDealer.attachAccessories(ent, status.atts) + end + end + local mats = status and status.mats or default and default.mats or {} + for k, v in pairs(mats) do + self.Entity:SetSubMaterial(k-1, v) + end + + self.viewOffset = cdData.previewOffset or self.viewOffset or Vector() + if self.camOffset then + local off = self.camOffset + self.vCamPos = self.vRawCamPos + + self.aLookAngle:Right() * off.x + + self.aLookAngle:Forward() * off.y + + self.aLookAngle:Up() * off.z + end + +end + +function PANEL:FirstPersonControls() + + if not self.canControl then return end + + local x, y = self:CaptureMouse() + local scale = self:GetFOV() / 180 + x = x * -0.5 * scale + y = y * 0.5 * scale + + if self.MouseKey == MOUSE_LEFT then + self:SetCursor('blank') + self.tgtLookAngle = self.tgtLookAngle + Angle(y * 4, x * 4, 0) + self.tgtLookAngle.p = math.Clamp(self.tgtLookAngle.p, -25, 25) + end + +end + +function PANEL:Think() + + self.BaseClass.Think(self) + + if self.canControl and IsValid(self.Entity) and self.aLookAngle ~= self.tgtLookAngle then + self.aLookAngle = LerpAngle(FrameTime() * 8, self.aLookAngle, self.tgtLookAngle) + + local pos = self.Entity:OBBCenter() - self.viewOffset + self.vRawCamPos = pos - self.aLookAngle:Forward() * (pos - self.vRawCamPos):Length() + + if self.camOffset then + local off = self.camOffset + self.vCamPos = self.vRawCamPos + + self.aLookAngle:Right() * off.x + + self.aLookAngle:Forward() * off.y + + self.aLookAngle:Up() * off.z + else + self.vCamPos = self.vRawCamPos + end + end + +end + +function PANEL:OnMouseReleased(k) + + self.BaseClass.OnMouseReleased(self, k) + self:SetCursor('hand') + +end + +function PANEL:OnMouseWheeled() + -- prevent baseclass function +end + +function PANEL:LayoutEntity() + -- prevent baseclass function +end + +function PANEL:PostDrawModel(ent) + + if ent.wheels then + render.SetColorModulation(1, 1, 1) + for _, w in ipairs(ent.wheels) do + w:DrawModel() + end + end + + if ent.atts then + for _, a in ipairs(ent.atts) do + local col = a:GetColor() + render.SetColorModulation(col.r / 255, col.g / 255, col.b / 255) + a:DrawModel() + end + end + +end + +function PANEL:FixPosition() + + local pos = self.Entity:OBBCenter() - (self.viewOffset or vector_zero) + self.vCamPos = pos - self.aLookAngle:Forward() * (pos - self.vCamPos):Length() + +end + +function PANEL:Cleanup() + + local ent = self.Entity + if IsValid(ent) then + if ent.wheels then + for _, w in ipairs(ent.wheels) do + w:Remove() + end + end + + if ent.atts then + for _, a in ipairs(ent.atts) do + a:Remove() + end + end + + ent:Remove() + end + +end + +function PANEL:OnRemove() + + self:Cleanup() + +end + +vgui.Register('cd_vehModel', PANEL, 'DAdjustableModelPanel') diff --git a/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehviewer.lua b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehviewer.lua new file mode 100644 index 0000000..bc7a252 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/car-dealer/vgui/vehviewer.lua @@ -0,0 +1,663 @@ +surface.CreateFont('car-dealer.view.title', { + font = 'Calibri', + size = 56, + weight = 500, + antialias = true, + extended = true, +}) + +surface.CreateFont('car-dealer.view.subtitle', { + font = 'Calibri', + size = 28, + weight = 500, + antialias = true, + extended = true, +}) + +surface.CreateFont('car-dealer.view.subtitle-plate', { + font = 'Plat Nomor', + size = 24, + weight = 500, + antialias = true, + extended = true, +}) + +local function getTrunkSize(simfphysID, ent) + local spData = list.Get('simfphys_vehicles')[simfphysID] + if not spData then return end + + local trunkData = spData.Members.Trunk + if not trunkData then return end + + for i = 2, #trunkData do + if not istable(trunkData[i]) then break end + local volume, bgID, bgVal = unpack(trunkData[i]) + if ent:GetBodygroup(bgID) == bgVal then + return volume + end + end + + return trunkData[1] +end + +local PANEL = {} + +function PANEL:Init() + + self:Dock(FILL) + + local mdl = self:Add 'cd_vehModel' + mdl:Dock(FILL) + mdl.canControl = true + mdl.camOffset = Vector(60, 0, 0) + mdl.camPos = Vector(300, 400, 75) + mdl.fovMultiplier = 0.9 + self.mdl = mdl + + local mats = cdData and cdData.default.mats or {} + for k, v in pairs(mats) do + self.mdl:SetSubMaterial(k-1, v) + end + + local mdlButs = mdl:Add 'DPanel' + mdlButs:Dock(BOTTOM) + mdlButs:DockMargin(10, 0, 0, 10) + mdlButs:SetTall(32) + mdlButs:SetPaintBackground(false) + self.mdlButs = mdlButs + + local title = self:Add 'DLabel' + title:SetFont('car-dealer.view.title') + title:SetText('Выбери машину внизу') + self.title = title + + -- local tags = mdl:Add 'DPanel' + -- tags:SetPaintBackground(false) + -- tags:SetTall(16) + -- self.tags = tags + + local health = self:Add 'DProgressLabel' + health:SetWide(200) + self.statusHealth = health + + local fuel = self:Add 'DProgressLabel' + fuel:SetWide(200) + self.statusFuel = fuel + + local subtitle = self:Add 'DLabel' + subtitle:SetFont('car-dealer.view.subtitle') + subtitle:SetText('') + self.subtitle = subtitle + + local mass = self:Add 'DProgressLabel' + mass:SetWide(200) + mass.max = octolib.table.reduce(carDealer.vehicles, function(a, cdData) + return math.max(a, list.Get('simfphys_vehicles')[cdData.simfphysID].Members.Mass) + end, 0) + self.mass = mass + + local grip = self:Add 'DProgressLabel' + grip:SetWide(200) + grip.max = octolib.table.reduce(carDealer.vehicles, function(a, cdData) + return math.max(a, list.Get('simfphys_vehicles')[cdData.simfphysID].Members.MaxGrip) + end, 0) + self.grip = grip + + local torque = self:Add 'DProgressLabel' + torque:SetWide(200) + torque.max = octolib.table.reduce(carDealer.vehicles, function(a, cdData) + return math.max(a, list.Get('simfphys_vehicles')[cdData.simfphysID].Members.PeakTorque) + end, 0) + self.torque = torque + + local steerSpeed = self:Add 'DProgressLabel' + steerSpeed:SetWide(200) + steerSpeed.max = octolib.table.reduce(carDealer.vehicles, function(a, cdData) + return math.max(a, list.Get('simfphys_vehicles')[cdData.simfphysID].Members.TurnSpeed) + end, 0) + self.steerSpeed = steerSpeed + + local fuel = self:Add 'DProgressLabel' + fuel:SetWide(200) + fuel.max = octolib.table.reduce(carDealer.vehicles, function(a, cdData) + return math.max(a, list.Get('simfphys_vehicles')[cdData.simfphysID].Members.FuelTankSize) + end, 0) + self.fuel = fuel + + local lv = self:Add 'DListView' + lv:SetWide(200) + lv:AddColumn('Параметр') + lv:AddColumn('Значение') + lv:SetHideHeaders(true) + lv:SetTall(200) + self.lv = lv + + local buts = self:Add 'DPanel' + buts:SetPaintBackground(false) + buts:SetSize(200, 200) + self.buts = buts + +end + +function PANEL:PerformLayout() + + local y = 10 + + local title = self.title + title:SizeToContents() + title:AlignRight(15) + title:AlignTop(10) + y = y + title:GetTall() + + -- local tags = self.tags + -- tags:SetSize(100, 16) + -- tags:AlignTop(35) + -- tags:AlignRight(title:GetWide() + 30) + + local subtitle = self.subtitle + if subtitle:GetText() ~= '' then + subtitle:SizeToContents() + subtitle:AlignRight(15) + subtitle:SetPos(subtitle:GetPos(), y - 5) + y = y + subtitle:GetTall() + 5 + end + + if self.vehData and self.vehData.id then + self.mass:SetVisible(false) + self.fuel:SetVisible(false) + self.torque:SetVisible(false) + self.grip:SetVisible(false) + self.steerSpeed:SetVisible(false) + self.lv:SetVisible(false) + + local health = self.statusHealth + health:SetVisible(true) + health:AlignRight(15) + health:SetPos(health:GetPos(), y) + y = y + health:GetTall() + 2 + + local fuel = self.statusFuel + fuel:SetVisible(true) + fuel:AlignRight(15) + fuel:SetPos(fuel:GetPos(), y) + y = y + fuel:GetTall() + 15 + else + self.statusHealth:SetVisible(false) + self.statusFuel:SetVisible(false) + + local mass = self.mass + mass:SetVisible(true) + mass:AlignRight(15) + mass:SetPos(mass:GetPos(), y) + y = y + mass:GetTall() + 2 + + local fuel = self.fuel + fuel:SetVisible(true) + fuel:AlignRight(15) + fuel:SetPos(fuel:GetPos(), y) + y = y + fuel:GetTall() + 5 + + local torque = self.torque + torque:SetVisible(true) + torque:AlignRight(15) + torque:SetPos(torque:GetPos(), y) + y = y + torque:GetTall() + 2 + + local grip = self.grip + grip:SetVisible(true) + grip:AlignRight(15) + grip:SetPos(grip:GetPos(), y) + y = y + grip:GetTall() + 2 + + local steerSpeed = self.steerSpeed + steerSpeed:SetVisible(true) + steerSpeed:AlignRight(15) + steerSpeed:SetPos(steerSpeed:GetPos(), y) + y = y + steerSpeed:GetTall() + 2 + + local lv = self.lv + lv:SetVisible(true) + lv:AlignRight(15) + lv:SetPos(lv:GetPos(), y) + y = y + lv:GetTall() + 2 + end + + local buts = self.buts + buts:AlignRight(15) + buts:AlignBottom(15) + +end + +function PANEL:DelayedRefresh() + + timer.Simple(0.5, function() + self:Refresh() + end) + +end + +function PANEL:SetVehicle(class, data) + + data = data or {} + data.data = data.data or {} + + local cdData = carDealer.vehicles[class] + if not cdData then return end + + local catData = carDealer.categories[cdData.category] + if not catData then return end + + self.class = class + self.vehData = data + + local spData = list.Get('simfphys_vehicles')[cdData.simfphysID] + assert(spData ~= nil, 'Wrong simfphysID for ' .. class) + + local m = spData.Members + local price = hook.Run('car-dealer.priceOverride', LocalPlayer(), class) or cdData.price + + self.mdl:SetVehicle(class, data) + self.title:SetText(cdData.name) + self.subtitle:SetText(data.plate or DarkRP.formatMoney(price)) + self.subtitle:SetFont(data.plate and 'car-dealer.view.subtitle-plate' or 'car-dealer.view.subtitle') + + if data.id then + local status = data.data + self.statusHealth:SetFraction(status.health or 1) + self.statusHealth:SetText('Исправность: ' .. math.Round((status.health or 1) * 100, 0) .. '%') + + local liters = math.Round((status.fuel or 1) * m.FuelTankSize, 1) + self.statusFuel:SetFraction(status.fuel or 1) + self.statusFuel:SetText('В баке: ' .. liters .. 'л') + end + + self.mass:SetFraction(m.Mass / self.mass.max) + self.mass:SetText('Масса: ' .. math.floor(m.Mass * 0.75) .. 'кг') + self.fuel:SetFraction(m.FuelTankSize / self.fuel.max) + self.fuel:SetText('Объем бака: ' .. m.FuelTankSize .. 'л') + self.torque:SetFraction(m.PeakTorque / self.torque.max) + self.torque:SetText('Мощность двигателя') + self.grip:SetFraction(m.MaxGrip / self.grip.max) + self.grip:SetText('Сцепление с дорогой') + self.steerSpeed:SetFraction(m.TurnSpeed / self.steerSpeed.max) + self.steerSpeed:SetText('Скорость поворота') + + local lv = self.lv + lv:Clear() + lv:AddLine('Тип топлива', m.FuelType == FUELTYPE_DIESEL and 'Дизель' or 'Бензин') + lv:AddLine('Привод', m.PowerBias > 0.5 and 'Задний' or m.PowerBias < -0.5 and 'Передний' or 'Полный') + lv:AddLine('Угол поворота', m.CustomSteerAngle .. '°') + lv:AddLine('Мест', #(m.PassengerSeats or {}) + 1) + + local trunk = getTrunkSize(cdData.simfphysID, self.mdl.Entity) + local trunkLine = lv:AddLine('Багажник', trunk and (trunk .. 'л') or 'Нет') + + local function setBodygroup(bgID, bgVal) + self.mdl.Entity:SetBodygroup(bgID, bgVal) + + local trunk = getTrunkSize(cdData.simfphysID, self.mdl.Entity) + trunkLine:SetColumnText(2, trunk and (trunk .. 'л') or 'Нет') + end + + self.mdlButs:Clear() + local lData = list.Get('simfphys_lights')[m.LightsTable or ''] + if lData and lData.SubMaterials then + local mats = lData.SubMaterials + + local headLightMode = 0 + local lightMode = 'Base' + local function updateSubMats() + local t + if headLightMode == 0 then + t = mats.off[lightMode] or mats.off.Base + elseif headLightMode == 1 then + t = mats.on_lowbeam[lightMode] or mats.on_lowbeam.Base + elseif headLightMode == 2 then + t = mats.on_highbeam[lightMode] or mats.on_highbeam.Base + end + for k, v in pairs(t) do + self.mdl.Entity:SetSubMaterial(k, v) + end + end + + local butLights = self.mdlButs:Add 'DImageButton' + butLights:Dock(LEFT) + butLights:DockMargin(0, 0, 5, 0) + butLights:SetWide(32) + butLights:SetIcon('octoteam/icons-car/light_close.png') + butLights:SetAlpha(100) + butLights:AddHint('Переключить фары') + local ppTarget = 0 + function butLights.DoClick() + headLightMode = octolib.math.loop(headLightMode + 1, 0, 3) + if headLightMode == 0 then + butLights:SetIcon('octoteam/icons-car/light_close.png') + butLights:SetAlpha(100) + ppTarget = 0 + elseif headLightMode == 1 then + butLights:SetIcon('octoteam/icons-car/light_close.png') + butLights:SetAlpha(255) + ppTarget = 1 + elseif headLightMode == 2 then + butLights:SetIcon('octoteam/icons-car/light_far.png') + butLights:SetAlpha(255) + ppTarget = 1 + end + updateSubMats() + end + if lData.PoseParameters then + local curState = 0 + function butLights.Think() + curState = math.Approach(curState, ppTarget, FrameTime() * 2) + self.mdl.Entity:SetPoseParameter(lData.PoseParameters.name, curState) + end + end + + local butBrake = self.mdlButs:Add 'DImageButton' + butBrake:Dock(LEFT) + butBrake:DockMargin(0, 0, 5, 0) + butBrake:SetWide(32) + butBrake:AddHint('Переключить тормоз') + butBrake:SetIcon('octoteam/icons-car/brake.png') + butBrake:SetAlpha(100) + butBrake.on = false + function butBrake.DoClick() + butBrake.on = not butBrake.on + butBrake:SetAlpha(butBrake.on and 255 or 100) + if butBrake.on then + if lightMode == 'Base' then + lightMode = 'Brake' + elseif lightMode == 'Reverse' then + lightMode = 'Brake_Reverse' + end + else + if lightMode == 'Brake' then + lightMode = 'Base' + elseif lightMode == 'Brake_Reverse' then + lightMode = 'Reverse' + end + end + updateSubMats() + end + + local butReverse = self.mdlButs:Add 'DImageButton' + butReverse:Dock(LEFT) + butReverse:DockMargin(0, 0, 5, 0) + butReverse:SetWide(32) + butReverse:AddHint('Переключить реверс') + butReverse:SetIcon('octoteam/icons-car/left_3.png') + butReverse:SetAlpha(100) + butReverse.on = false + function butReverse.DoClick() + butReverse.on = not butReverse.on + butReverse:SetAlpha(butReverse.on and 255 or 100) + if butReverse.on then + if lightMode == 'Base' then + lightMode = 'Reverse' + elseif lightMode == 'Brake' then + lightMode = 'Brake_Reverse' + end + else + if lightMode == 'Reverse' then + lightMode = 'Base' + elseif lightMode == 'Brake_Reverse' then + lightMode = 'Brake' + end + end + updateSubMats() + end + + if mats.turnsignals then + local butTurn = self.mdlButs:Add 'DImageButton' + butTurn:Dock(LEFT) + butTurn:DockMargin(0, 0, 5, 0) + butTurn:SetWide(32) + butTurn:AddHint('Переключить фары') + butTurn:SetIcon('octoteam/icons-car/alarm.png') + butTurn:SetAlpha(100) + butTurn.on = false + butTurn.curState = false + function butTurn.DoClick() + butTurn.on = not butTurn.on + butTurn:SetAlpha(butTurn.on and 255 or 100) + if not butTurn.on then + for k in pairs(mats.turnsignals.left) do self.mdl.Entity:SetSubMaterial(k, '') end + for k in pairs(mats.turnsignals.right) do self.mdl.Entity:SetSubMaterial(k, '') end + butTurn.curState = false + end + end + function butTurn.Think() + local ct = CurTime() + if not butTurn.on or ct < (butTurn.nextToggle or 0) then return end + + butTurn.curState = not butTurn.curState + for k, v in pairs(mats.turnsignals.left) do self.mdl.Entity:SetSubMaterial(k, butTurn.curState and v or '') end + for k, v in pairs(mats.turnsignals.right) do self.mdl.Entity:SetSubMaterial(k, butTurn.curState and v or '') end + butTurn.nextToggle = ct + 0.5 + end + end + end + + + -- self.tags:Clear() + -- local tags = table.Copy(cdData.tags or {}) + -- hook.Run('car-dealer.populateTags', class, cdData, tags) + -- for _, tag in ipairs(tags) do + -- if not data.id or tag[3] then + -- local icon = self.tags:Add 'DImageButton' + -- icon:Dock(RIGHT) + -- icon:DockMargin(5, 0, 0, 0) + -- icon:SetWide(16) + -- icon:SetImage(tag[1]) + -- icon:AddOctoHint(tag[2]) + -- end + -- end + + self:InvalidateLayout(true) + + self.buts:Clear() + local ply = LocalPlayer() + local veh = carDealer.getCurVeh(ply) + local q = ply:GetLocalVar('car-dealer.queued') + if not data.id and not cdData.deposit then + local bgs = {} + local bgPrices = {} + + local bBuy = self.buts:Add 'DButton' + bBuy:Dock(BOTTOM) + bBuy:DockMargin(0, 5, 0, 0) + bBuy:SetTall(25) + bBuy:SetIcon('icon16/money.png') + bBuy:SetText('Купить') + bBuy.DoClick = function() + local priceTotal = octolib.table.reduce(bgPrices, function(a, v) return a + v end, price) + local text = ('Ты уверен, что хочешь купить %s за %s?') + :format(cdData.name or 'Автомобиль', carDealer.formatMoney(priceTotal)) + + Derma_Query(text, 'Купить автомобиль', 'Да', function() + bBuy:SetEnabled(false) + netstream.Request('car-dealer.buy', class, bgs):Then(function(ok) + if not ok then return end + carDealer.menu.list:SwitchToName('Мой гараж') + end):Finally(function() + if IsValid(bBuy) then bBuy:SetEnabled(true) end + end) + end, 'Нет') + end + + if cdData.bodygroups then + local bOptions = self.buts:Add 'DButton' + bOptions:Dock(BOTTOM) + bOptions:DockMargin(0, 5, 0, 0) + bOptions:SetTall(25) + bOptions:SetIcon('icon16/cog.png') + bOptions:SetText('Конфигурация') + bOptions.DoClick = function() + local m = DermaMenu() + for bgID, group in pairs(cdData.bodygroups) do + local sm = m:AddSubMenu(group.name) + for val, variant in pairs(group.variants) do + local bgVal = val - 1 + local originalVal = cdData.default and cdData.default.bg and cdData.default.bg[bgID] or 0 + local isOriginal = bgVal == originalVal + local variantName, variantPrice = unpack(variant) + local o = sm:AddOption(isOriginal and variantName or ('%s (+%s)'):format(variantName, carDealer.formatMoney(variantPrice)), function() + setBodygroup(bgID, bgVal) + bgs[bgID] = not isOriginal and bgVal or nil + bgPrices[bgID] = not isOriginal and variantPrice or nil + + self.subtitle:SetText(carDealer.formatMoney(octolib.table.reduce(bgPrices, function(a, v) return a + v end, price))) + self:InvalidateLayout() + end) + if self.mdl.Entity:GetBodygroup(bgID) == bgVal then + o:SetIcon('icon16/tick.png') + end + end + end + m:Open() + end + end + elseif IsValid(veh) and veh:GetNetVar('cd.id') == data.id then + -- cdData.deposit or not ply:GetLocalVar('cd.cantHaveOwned') + local butDespawn = self.buts:Add 'DButton' + butDespawn:Dock(BOTTOM) + butDespawn:DockMargin(0, 5, 0, 0) + butDespawn:SetTall(25) + butDespawn:SetIcon('icon16/car.png') + butDespawn:SetText('Загнать') + butDespawn.DoClick = function() + butDespawn:SetEnabled(false) + netstream.Request('car-dealer.despawn'):Finally(function() + if IsValid(butDespawn) then butDespawn:SetEnabled(true) end + self:DelayedRefresh() + end) + end + elseif q then + if q[1] == data.id or q[1] == class then + local b2 = self.buts:Add 'DButton' + b2:Dock(BOTTOM) + b2:DockMargin(0, 5, 0, 0) + b2:SetTall(25) + b2:SetIcon('icon16/cancel.png') + b2:SetText('Отменить') + b2.DoClick = function() + b2:SetEnabled(false) + netstream.Request('car-dealer.despawn'):Finally(function() + if IsValid(b2) then b2:SetEnabled(true) end + self:DelayedRefresh() + end) + end + + local ready = ply:GetLocalVar('car-dealer.queuedReady') + local b1 = self.buts:Add 'DButton' + b1:Dock(BOTTOM) + b1:DockMargin(0, 5, 0, 0) + b1:SetTall(25) + b1:SetIcon('icon16/accept.png') + b1:SetText(ready and 'Подтвердить' or 'В очереди (' .. q[2] .. ')...') + b1:SetEnabled(ready) + b1.DoClick = function() + b1:SetEnabled(false) + local function finally() + if IsValid(b1) then b1:SetEnabled(true) end + self:DelayedRefresh() + end + + if q[1] == data.id then + netstream.Request('car-dealer.spawn', data.id):Finally(finally) + elseif q[1] == class then + netstream.Request('car-dealer.rent', class):Finally(finally) + end + end + if not ready then + hook.Add('octolib.netVarUpdate', 'car-dealer.queue', function(ent, var, val) + if ent ~= ply:EntIndex() or var ~= 'car-dealer.queued' then return end + if not IsValid(b1) or not val then return hook.Remove('octolib.netVarUpdate', 'car-dealer.queue') end + b1:SetText('В очереди (' .. val[2] .. ')...') + octolib.notify.show('hint', 'Обновлена очередь за автомобилем: ', tostring(val[2])) + end) + end + else + local b1 = self.buts:Add 'DButton' + b1:Dock(BOTTOM) + b1:DockMargin(0, 5, 0, 0) + b1:SetTall(25) + b1:SetIcon('icon16/accept.png') + b1:SetText('Ждем другое авто...') + b1:SetEnabled(false) + end + else + if data.id then + local b2 = self.buts:Add 'DButton' + b2:Dock(BOTTOM) + b2:DockMargin(0, 5, 0, 0) + b2:SetTall(25) + b2:SetIcon('icon16/delete.png') + b2:SetText('Продать') + b2:SetEnabled(carDealer.enabled) + b2.DoClick = function() + local mul = carDealer.sellPrice or 1 + local text = ('Ты уверен, что хочешь продать %s за %s?') + :format(cdData.name or 'Автомобиль', carDealer.formatMoney(math.floor(mul * price))) + + Derma_Query(text, 'Продать автомобиль', 'Да', function() + if IsValid(b2) then b2:SetEnabled(false) end + netstream.Request('car-dealer.sell', data.id):Finally(function() + if IsValid(b2) then b2:SetEnabled(true) end + self:DelayedRefresh() + end) + end, 'Нет') + end + + local b1 = self.buts:Add 'DButton' + b1:Dock(BOTTOM) + b1:DockMargin(0, 5, 0, 0) + b1:SetTall(25) + b1:SetIcon('icon16/car.png') + b1:SetText('Пригнать') + b1.DoClick = function() + b1:SetEnabled(false) + netstream.Request('car-dealer.spawn', data.id):Finally(function() + if IsValid(b1) then b1:SetEnabled(true) end + self:DelayedRefresh() + end) + end + elseif cdData.deposit then + local bRent = self.buts:Add 'DButton' + bRent:Dock(BOTTOM) + bRent:DockMargin(0, 5, 0, 0) + bRent:SetTall(25) + bRent:SetIcon('icon16/money.png') + bRent:SetText('Арендовать') + bRent.DoClick = function() + local text = ('Ты уверен, что хочешь арендовать %s за %s?') + :format(cdData.name or 'Автомобиль', carDealer.formatMoney(price)) + + Derma_Query(text, 'Арендовать автомобиль', 'Да', function() + bRent:SetEnabled(false) + netstream.Request('car-dealer.rent', class):Then(function() + if IsValid(bRent) then bRent:SetEnabled(true) end + self:DelayedRefresh() + end) + end, 'Нет') + end + end + end + +end + +function PANEL:Refresh() + + if self.vehData and self.vehData.id and self.vehData ~= carDealer.cache.owned[self.vehData.id] then + self.vehData = carDealer.cache.owned[self.vehData.id] + end + + self:SetVehicle(self.class, self.vehData) + +end + +function PANEL:Paint() + -- no paint +end + +vgui.Register('cd_vehViewer', PANEL, 'DPanel') diff --git a/garrysmod/addons/feature-cars/lua/cmenu/categories/car.lua b/garrysmod/addons/feature-cars/lua/cmenu/categories/car.lua new file mode 100644 index 0000000..3966117 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/cmenu/categories/car.lua @@ -0,0 +1,7 @@ +octogui.cmenu.registerCategory('car', { + check = function(ply) + local seat = ply:GetVehicle() + if not (IsValid(seat) and IsValid(seat:GetParent())) then return false end + return true + end, +}) diff --git a/garrysmod/addons/feature-cars/lua/cmenu/items/cars.lua b/garrysmod/addons/feature-cars/lua/cmenu/items/cars.lua new file mode 100644 index 0000000..950707c --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/cmenu/items/cars.lua @@ -0,0 +1,32 @@ +octogui.cmenu.registerItem('car', 'radio', { + text = L.radio, + check = function(ply) + local seat = ply:GetVehicle() + if seat:GetParent().GetDriverSeat and seat:GetParent():GetDriverSeat() == seat then + return true + end + + return simfphys.GetSeatProperty(seat, 'hasRadio') + end, + icon = octolib.icons.silk16('radio_modern'), + netstream = 'dbg-radio.openCar', +}) + +octogui.cmenu.registerItem('car', 'seat', { + text = 'Пересесть', + icon = octolib.icons.silk16('chair'), + build = function(sm) + netstream.Request('dbg-cars.seatStatus'):Then(function(data) + if not IsValid(sm) then return end + for _, v in ipairs(data) do + local opt = sm:AddOption(v.name, function() + net.Start('simfphys_request_seatswitch') + net.WriteInt(v.id, 32) + net.SendToServer() + end) + opt:SetChecked(v.check) + if v.icon then opt:SetImage(octolib.icons.silk16(v.icon)) end + end + end) + end, +}) diff --git a/garrysmod/addons/feature-cars/lua/effects/simfphys_backfire.lua b/garrysmod/addons/feature-cars/lua/effects/simfphys_backfire.lua new file mode 100644 index 0000000..28c5fe7 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/effects/simfphys_backfire.lua @@ -0,0 +1,116 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local bdamaged = data:GetFlags() >= 1 + local lPos = data:GetOrigin() + local lAng = data:GetAngles() + local Entity = data:GetEntity() + + local Delay = bdamaged and 0 or math.random(0,0.4) + timer.Simple( Delay, function() + if IsValid( Entity ) then + local Vel = Entity:GetVelocity() + local Pos = Entity:LocalToWorld( lPos ) + Vel * FrameTime() + local Ang = Entity:LocalToWorldAngles( lAng ) + + local snd1 = "simulated_vehicles/sfx/ex_backfire_damaged_"..math.Round(math.random(1,3),1)..".ogg" + local snd2 = Entity:GetBackfireSound() + if snd2 == "" then + snd2 = "simulated_vehicles/sfx/ex_backfire_"..math.Round(math.random(1,4),1)..".ogg" + end + + local snd = bdamaged and snd1 or snd2 + sound.Play( snd, Pos, 90, 100 ) + + local dlight = DynamicLight( Entity:EntIndex() * math.random(1,4) ) + if dlight then + dlight.pos = Pos + dlight.r = 255 + dlight.g = 180 + dlight.b = 100 + dlight.brightness = 2 + dlight.Decay = 1000 + dlight.Size = 120 + dlight.DieTime = CurTime() + 0.5 + end + + local emitter1 = ParticleEmitter( Pos, false ) + local emitter2 = ParticleEmitter( Pos, false ) + + for i = 0, 12 do + local particle1 = emitter1:Add( "effects/muzzleflash2", Pos ) + local particle2 = emitter2:Add( Materials[ math.Round( math.Rand(1, table.Count( Materials ) ) , 0 ) ], Pos ) + + if particle1 then + particle1:SetVelocity( Vel + Ang:Forward() * (5 + Vel:Length() / 20) ) + particle1:SetDieTime( 0.1 ) + particle1:SetStartAlpha( 255 ) + particle1:SetStartSize( math.random(4,12) ) + particle1:SetEndSize( 0 ) + particle1:SetRoll( math.Rand( -1, 1 ) ) + particle1:SetColor( 255,255,255 ) + particle1:SetCollide( false ) + end + + if particle2 then + particle2:SetVelocity( Vel + Ang:Forward() * (10 + Vel:Length() / 20) ) + particle2:SetDieTime( 0.3 ) + particle2:SetStartAlpha( 60 ) + particle2:SetStartSize( 0 ) + particle2:SetEndSize( math.random(8,20) ) + particle2:SetRoll( math.Rand( -1, 1 ) ) + particle2:SetColor( 100,100,100 ) + particle2:SetCollide( false ) + end + + if bdamaged then + local emitter3 = ParticleEmitter( Pos, false ) + + local particle3 = emitter3:Add( Materials[ math.Round( math.Rand(1, table.Count( Materials ) ) , 0 ) ], Pos ) + + if particle3 then + particle3:SetVelocity( Vel + Ang:Forward() * math.random(30,60) ) + particle3:SetDieTime( 0.5 ) + particle3:SetAirResistance( 20 ) + particle3:SetStartAlpha( 100 ) + particle3:SetStartSize( 0 ) + particle3:SetEndSize( math.random(25,50) ) + particle3:SetRoll( math.Rand( -1, 1 ) ) + particle3:SetColor( 40,40,40 ) + particle3:SetCollide( false ) + end + + emitter3:Finish() + end + end + + emitter1:Finish() + emitter2:Finish() + end + end ) +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-cars/lua/effects/simfphys_engine_fire.lua b/garrysmod/addons/feature-cars/lua/effects/simfphys_engine_fire.lua new file mode 100644 index 0000000..6f8c495 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/effects/simfphys_engine_fire.lua @@ -0,0 +1,117 @@ +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +local function RandVector(min,max) + min = min or -1 + max = max or 1 + + local vec = Vector(math.Rand(min,max),math.Rand(min,max),math.Rand(min,max)) + return vec +end + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Entity = data:GetEntity() + + if not IsValid( Entity ) then return end + + local Vel = Entity:GetVelocity() * 0.9 + + self:Smoke( Pos, Vel ) + self:Flame1( Pos, Vel ) + self:Flame2( Pos, Vel ) +end + +function EFFECT:Smoke( pos, vel ) + local emitter = ParticleEmitter( pos, false ) + + local particle = emitter:Add( Materials[math.Round(math.Rand(1,table.Count( Materials )),0)], pos ) + + local vz = math.min(vel:Length(),600) + + if particle then + particle:SetVelocity( RandVector() * 5 + Vector(0,0,40 + vz) + vel * 0.1 ) + particle:SetDieTime( 1 ) + particle:SetAirResistance( vz ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 25 ) + particle:SetEndSize( math.Rand(60,80) ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Flame1( pos, vel ) + for i = 0,3 do + pos = pos + RandVector() * 5 + + local emitter = ParticleEmitter( pos, false ) + + local particle = emitter:Add( "particles/flamelet"..math.random(1,5), pos ) + + if particle then + particle:SetVelocity( Vector(0,0,40) + vel ) + particle:SetDieTime( 0.45 - (math.min(vel:Length(),600) / 600) * 0.25 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(10,14) ) + particle:SetEndSize( math.Rand(0,6) ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 40 ) ) + particle:SetCollide( false ) + end + + emitter:Finish() + end +end + +function EFFECT:Flame2( pos, vel ) + pos = pos + RandVector() * 5 + + local emitter = ParticleEmitter( pos, false ) + + local particle = emitter:Add( "particles/fire1", pos ) + + if particle then + particle:SetVelocity( Vector(0,0,70) + vel ) + particle:SetDieTime( 0.6 - (math.min(vel:Length(),600) / 600) * 0.4 ) + particle:SetAirResistance( 0 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(10,14) ) + particle:SetEndSize( math.Rand(0,6) ) + particle:SetRoll( math.Rand(-1,1) * 180 ) + particle:SetColor( 255,255,255 ) + particle:SetGravity( Vector( 0, 0, 70 ) ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-cars/lua/effects/simfphys_engine_smoke.lua b/garrysmod/addons/feature-cars/lua/effects/simfphys_engine_smoke.lua new file mode 100644 index 0000000..32d06a4 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/effects/simfphys_engine_smoke.lua @@ -0,0 +1,62 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +local function RandVector(min,max) + min = min or -1 + max = max or 1 + + local vec = Vector(math.Rand(min,max),math.Rand(min,max),math.Rand(min,max)) + return vec +end + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + local Ent = data:GetEntity() + + if not IsValid( Ent ) then return end + + local Vel = Ent:GetVelocity():Length() + + local emitter = ParticleEmitter( Pos, false ) + + local particle = emitter:Add( Materials[math.Round(math.Rand(1,table.Count( Materials )),0)], Pos ) + + if particle then + particle:SetVelocity( RandVector() * 100 + Vector(0,0,math.min(Vel,600) ) ) + particle:SetDieTime( 1.25 ) + particle:SetAirResistance( 600 ) + particle:SetStartAlpha( math.max(80 - Vel / 100,0) ) + particle:SetStartSize( math.Rand(0,15) ) + particle:SetEndSize( math.Rand(50,65) ) + particle:SetRoll( math.Rand(-1,1) * 100 ) + particle:SetColor( 80,80,80 ) + particle:SetGravity( Vector( 0, 0, 500 ) ) + particle:SetCollide( false ) + end + + emitter:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-cars/lua/effects/simfphys_exhaust.lua b/garrysmod/addons/feature-cars/lua/effects/simfphys_exhaust.lua new file mode 100644 index 0000000..b3fdbad --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/effects/simfphys_exhaust.lua @@ -0,0 +1,90 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local lPos = data:GetOrigin() + local lAng = data:GetAngles() - Angle(90,0,0) + local Entity = data:GetEntity() + local Size = data:GetMagnitude() + + local TurboCharged = Entity:GetTurboCharged() + local SuperCharged = Entity:GetSuperCharged() + + if IsValid( Entity ) then + local Vel = Entity:GetVelocity() + local Dir = Entity:LocalToWorldAngles( lAng ):Forward() + local Pos = Entity:LocalToWorld( lPos ) + + local emitter = ParticleEmitter( Pos, false ) + + local MaxHealth = Entity:GetMaxHealth() + local Health = Entity:GetCurHealth() + + local particle = emitter:Add( Materials[ math.Round( math.Rand(1, table.Count( Materials ) ) , 0 ) ], Pos ) + local cAdd = (1 - (Health / MaxHealth)) * 100 + local cInt = math.Clamp(100 - 40 * Size,0,255) + local rand = Vector( math.random(-1,1), math.random(-1,1), math.random(-1,1) ) * 0.25 + + if particle then + particle:SetVelocity( Vel + (Dir + rand) * (50 + Size * 100) ) + particle:SetDieTime( 0.4 + Size * 0.6 ) + particle:SetAirResistance( 200 ) + particle:SetStartAlpha( math.max(20 + Size ^ 3 * (20 + cAdd) - Vel:Length() / 800,0)) + particle:SetStartSize( ((SuperCharged and TurboCharged) and 4 or 2) ) + particle:SetEndSize( 10 + Size * 60 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( math.Clamp(cInt - cAdd,0,255), math.Clamp(cInt - cAdd,0,255), math.Clamp(cInt - cAdd * 0.5,0,255) ) + particle:SetGravity( Vector( 0, 0, 100 ) + Vel * 0.5 ) + particle:SetCollide( false ) + end + + emitter:Finish() + + if SuperCharged and TurboCharged then + if Size > 0.4 then + for i = 0, 12 do + local Pos2 = Pos + Dir * i * 0.7 * math.random(1,2) * 0.5 + local emitter1 = ParticleEmitter( Pos2, false ) + local particle1 = emitter1:Add( "effects/muzzleflash2", Pos2 ) + + if particle1 then + particle1:SetVelocity( Vel + Dir * (5 + Vel:Length() / 20) ) + particle1:SetDieTime( 0.05 ) + particle1:SetStartAlpha( 255 * Size ) + particle1:SetStartSize( math.max( math.random(4,12) - i * 0.5,0.1 ) * Size ) + particle1:SetEndSize( 0 ) + particle1:SetRoll( math.Rand( -1, 1 ) ) + particle1:SetColor( 255,255,255 ) + particle1:SetCollide( false ) + end + + emitter1:Finish() + end + end + end + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-cars/lua/effects/simfphys_explosion.lua b/garrysmod/addons/feature-cars/lua/effects/simfphys_explosion.lua new file mode 100644 index 0000000..fb6dc58 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/effects/simfphys_explosion.lua @@ -0,0 +1,155 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +local function RandVector(min,max) + min = min or -1 + max = max or 1 + + local vec = Vector(math.Rand(min,max),math.Rand(min,max),math.Rand(min,max)) + return vec +end + +function EFFECT:Init( data ) + local Pos = data:GetOrigin() + + self:Explosion( Pos ) + self:ShockWave( Pos ) + self:Debris( Pos ) + + local random = math.random(1,3) + + if random == 1 then + sound.Play( "ambient/explosions/explode_3.wav", Pos, 95, 100, 1 ) + elseif random == 2 then + sound.Play( "ambient/explosions/explode_8.wav", Pos, 95, 140, 1 ) + else + sound.Play( "ambient/explosions/explode_5.wav", Pos, 95, 100, 1 ) + end +end + +function EFFECT:Debris( pos ) + local emitter = ParticleEmitter( pos, false ) + + for i = 0,60 do + local particle = emitter:Add( "effects/fleck_tile"..math.random(1,2), pos ) + local vel = RandVector(-1,1) * 300 + vel.z = math.Rand(200,600) + if particle then + particle:SetVelocity( vel ) + particle:SetDieTime( math.Rand(3,5) ) + particle:SetAirResistance( 10 ) + particle:SetStartAlpha( 150 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 0,0,0 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + particle:SetBounce( 0.3 ) + end + end + + emitter:Finish() +end + +function EFFECT:Explosion( pos ) + local emitter = ParticleEmitter( pos, false ) + + for i = 0,60 do + local particle = emitter:Add( Materials[math.random(1,table.Count( Materials ))], pos ) + + if particle then + particle:SetVelocity( RandVector(-1,1) * 800 ) + particle:SetDieTime( math.Rand(0.8,1.6) ) + particle:SetAirResistance( math.Rand(200,600) ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( math.Rand(20,60) ) + particle:SetEndSize( math.Rand(80,140) ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 100 ) ) + particle:SetCollide( false ) + end + end + + for i = 0, 40 do + local particle = emitter:Add( "sprites/flamelet"..math.random(1,5), pos ) + + if particle then + particle:SetVelocity( RandVector(-1,1) * 1000 ) + particle:SetDieTime( 0.14 ) + particle:SetStartAlpha( 255 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( math.Rand(60,120) ) + particle:SetEndAlpha( 100 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 200,150,150 ) + particle:SetCollide( false ) + end + end + + emitter:Finish() + + local dlight = DynamicLight( math.random(0,9999) ) + if dlight then + dlight.pos = pos + dlight.r = 255 + dlight.g = 180 + dlight.b = 100 + dlight.brightness = 8 + dlight.Decay = 2000 + dlight.Size = 300 + dlight.DieTime = CurTime() + 0.1 + end +end + +function EFFECT:ShockWave( pos ) + local emitter1 = ParticleEmitter( pos, false ) + + for i = 0,36 do + local particle = emitter1:Add( Materials[math.Round(math.Rand(1,table.Count( Materials )),0)], pos ) + + if particle then + local ang = i * 10 + local X = math.cos( math.rad(ang) ) + local Y = math.sin( math.rad(ang) ) + + particle:SetVelocity( Vector(X,Y,0) * math.Rand(4000,5000) ) + particle:SetDieTime( 1 ) + particle:SetAirResistance( 800 ) + particle:SetStartAlpha( 200 ) + particle:SetStartSize( 0 ) + particle:SetEndSize( math.Rand(40,100) ) + particle:SetRoll( math.Rand(-1,1) ) + particle:SetColor( 40,40,40 ) + particle:SetGravity( Vector( 0, 0, 1000 ) ) + particle:SetCollide( false ) + end + end + + emitter1:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-cars/lua/effects/simfphys_tiresmoke.lua b/garrysmod/addons/feature-cars/lua/effects/simfphys_tiresmoke.lua new file mode 100644 index 0000000..5456fc4 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/effects/simfphys_tiresmoke.lua @@ -0,0 +1,104 @@ + +local Materials = { + "particle/smokesprites_0001", + "particle/smokesprites_0002", + "particle/smokesprites_0003", + "particle/smokesprites_0004", + "particle/smokesprites_0005", + "particle/smokesprites_0006", + "particle/smokesprites_0007", + "particle/smokesprites_0008", + "particle/smokesprites_0009", + "particle/smokesprites_0010", + "particle/smokesprites_0011", + "particle/smokesprites_0012", + "particle/smokesprites_0013", + "particle/smokesprites_0014", + "particle/smokesprites_0015", + "particle/smokesprites_0016" +} + +function EFFECT:Init( data ) + local Entity = data:GetEntity() + local Mul = data:GetMagnitude() + local Pos = data:GetOrigin() + local Dir = data:GetNormal() + local WheelSize = data:GetRadius() + local Color = data:GetStart() + + local Ran = Vector( math.Rand( -WheelSize, WheelSize ), math.Rand( -WheelSize, WheelSize ),math.Rand( -WheelSize, WheelSize ) ) * 0.3 + local OffsetPos = Pos + Ran + Vector(0,0,WheelSize * 0.2) + + if IsValid( Entity ) then + local Vel = Entity:GetVelocity() / 3 + + local OffsetPos = OffsetPos + Ran * 0.4 + Vector(0,0,-WheelSize * 0.8) + + local emitter = ParticleEmitter(OffsetPos, false ) + + local particle = emitter:Add( Materials[math.Round(math.Rand(1, table.Count(Materials) ),0)], OffsetPos ) + + local Mul = 0.3 + Mul * 0.05 + + local Color = render.ComputeLighting( Pos, Vector( 0, 0, 1 ) ) + + Color.x = 55 + ( math.Clamp( Color.x, 0, 1 ) ) * 200 + Color.y = 55 + ( math.Clamp( Color.y, 0, 1 ) ) * 200 + Color.z = 55 + ( math.Clamp( Color.z, 0, 1 ) ) * 200 + + if particle then + particle:SetGravity( Vector(0,0,12) + Ran * 0.2 ) + particle:SetVelocity( Dir * 10 * (3 - Mul) + Vector(0,0,15) + Ran * Mul + Vel ) + particle:SetDieTime( 0.5 ) + particle:SetStartAlpha( 20 ) + particle:SetStartSize( WheelSize * 0.7 * Mul ) + particle:SetEndSize( math.Rand( 80, 160 ) * Mul ^ 2 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( Color.x,Color.y,Color.z ) + particle:SetCollide( false ) + end + + emitter:Finish() + else + local OffsetPos2 = OffsetPos + Ran * 0.4 + Vector(0,0,-WheelSize) + + local emitter1 = ParticleEmitter(OffsetPos, false ) + local emitter2 = ParticleEmitter(OffsetPos2, false ) + + local particle1 = emitter1:Add( Materials[math.Round(math.Rand(1, table.Count(Materials) ),0)], OffsetPos ) + local particle2 = emitter2:Add( Materials[math.Round(math.Rand(1, table.Count(Materials) ),0)], OffsetPos2 ) + + if particle1 then + particle1:SetVelocity( Vector(0,0,-50) ) + particle1:SetDieTime( 1 ) + particle1:SetStartAlpha( 255 * Mul ^ 2 ) + particle1:SetStartSize( 16 * Mul ) + particle1:SetEndSize( 32 * Mul ) + particle1:SetRoll( math.Rand( -1, 1 ) ) + particle1:SetColor( Color.x * 0.9,Color.y * 0.9,Color.z * 0.9 ) + particle1:SetCollide( true ) + end + + if particle2 then + particle2:SetGravity( Vector(0,0,12) + Ran * 0.2 ) + particle2:SetVelocity( Dir * 30 * (3 - Mul) + Vector(0,0,15) + Ran * Mul ) + particle2:SetDieTime( math.Rand( 1, 2 ) * Mul ) + particle2:SetStartAlpha( 100 * Mul ) + particle2:SetStartSize( WheelSize * 0.7 * Mul ) + particle2:SetEndSize( math.Rand( 80, 160 ) * Mul ^ 2 ) + particle2:SetRoll( math.Rand( -1, 1 ) ) + particle2:SetColor( Color.x,Color.y,Color.z ) + particle2:SetCollide( false ) + end + + emitter1:Finish() + emitter2:Finish() + end +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/cl_init.lua b/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/cl_init.lua new file mode 100644 index 0000000..4ffdefa --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/cl_init.lua @@ -0,0 +1,24 @@ +include 'shared.lua' + +local offPos, offAng = Vector(0, 0, 0), Angle(0, 90, 90) +function ENT:Draw() + + self:DrawModel() + + surface.SetAlphaMultiplier(self.alpha or 1) + + local pos, ang = LocalToWorld(offPos, offAng, self:GetPos(), self:GetAngles()) + cam.Start3D2D(pos, ang, 0.065) + draw.Text({ + text = self.text, + font = 'car-dealer.plate', + pos = { 0, 10 }, + color = self.color, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + }) + cam.End3D2D() + + surface.SetAlphaMultiplier(1) + +end diff --git a/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/init.lua b/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/init.lua new file mode 100644 index 0000000..91b21f8 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/init.lua @@ -0,0 +1,10 @@ +include 'shared.lua' +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' + +function ENT:Initialize() + + self:SetModel('models/octoteam/vehicles/attachments/licenceplate_01.mdl') + self.DoNotDuplicate = true + +end diff --git a/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/shared.lua b/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/shared.lua new file mode 100644 index 0000000..339e001 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/dbg_car_plate/shared.lua @@ -0,0 +1,4 @@ +ENT.Type = 'anim' +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_BOTH diff --git a/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/cl_init.lua b/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/cl_init.lua new file mode 100644 index 0000000..6824120 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/cl_init.lua @@ -0,0 +1,25 @@ +include 'shared.lua' + +function ENT:Initialize() + + local maxs = Vector(40, 40, 40) + self:SetRenderBounds(-maxs, maxs) + +end + +local ropeMat = Material('cable/cable') + +function ENT:Draw() + + self:DrawModel() + + local truck = self:GetNetVar('truck') + if not IsValid(truck) then return end + + render.SetMaterial(ropeMat) + render.StartBeam(2) + render.AddBeam(self:LocalToWorld(self.WinchPosHook), 1.5, 0, color_white) + render.AddBeam(truck:LocalToWorld(self.WinchPosTow), 1.5, 2, color_white) + render.EndBeam() + +end diff --git a/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/init.lua b/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/init.lua new file mode 100644 index 0000000..c9e71a4 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/init.lua @@ -0,0 +1,143 @@ +include 'shared.lua' +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' + +function ENT:Initialize() + + self:SetModel('models/octoteam/vehicles/bobcat_towtruck_crane.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.DoNotDuplicate = true + self.nextWeld = 0 + + local ph = self:GetPhysicsObject() + ph:SetMass(15) + ph:SetDamping(2, 2) + self:Activate() + + self.controller = ents.Create('gmod_winch_controller') + local controller = self.controller + controller:Spawn() + controller.min_length = 40 + controller.max_length = 80 + self:DeleteOnRemove(controller) + +end + +function ENT:SetTruck(veh) + + self:SetPos(veh:LocalToWorld(self.WinchPosTow - Vector(0,0,53))) + self:SetAngles(veh:GetAngles()) + + local controller = self.controller + veh:DeleteOnRemove(controller) + veh:DeleteOnRemove(self) + + self.controller = controller + veh.TowController = controller + veh.HasSpecial = true + + self:SetNetVar('truck', veh) + veh.TowHook = self + self:SetupWinch() + +end + +local function CalcElasticConsts(Phys1, Phys2, Ent1, Ent2, iFixed) + local minMass = 0 + + if Ent1:IsWorld() then + minMass = Phys2:GetMass() + elseif Ent2:IsWorld() then + minMass = Phys1:GetMass() + else + minMass = math.min(Phys1:GetMass(), Phys2:GetMass()) + end + + local const = minMass * 100 + local damp = const * 0.2 + + if iFixed == 0 then + const = minMass * 50 + damp = const * 0.1 + end + + return const, damp +end + +function ENT:SetupWinch(ent) + + local old = self.controller.constraint + if IsValid(old) then old:Remove() end + + local tow = self:GetNetVar('truck') + local tgt = IsValid(ent) and ent ~= tow and ent or self + + local winchPosTgt = tgt == self and self.WinchPosHook or tgt:WorldToLocal(self:LocalToWorld(self.WinchPosHook)) + local strenght, damp = CalcElasticConsts(tow:GetPhysicsObject(), tgt:GetPhysicsObject(), tow, tgt, false) + local const, rope = constraint.Elastic(tow, tgt, 0, 0, self.WinchPosTow, winchPosTgt, strenght, damp, 0, '', 0, true) + + const:SetTable({ + Type = 'Winch', + Ent1 = tow, + Ent2 = tgt, + Bone1 = vehPh, + Bone2 = vehHk, + LPos1 = self.WinchPosTow, + LPos2 = winchPosTgt, + width = 1.5, + fwd_speed = 5, + bwd_speed = 5, + material = 'cable/cable', + toggle = false, + }) + + self.controller:SetConstraint(const) + self.controller:SetRope(rope) + self.hookedEnt = tgt + +end + +function ENT:Think() + + if not IsValid(self.hookedEnt) then + self:SetupWinch() + end + +end + +function ENT:PhysicsCollide(data, ph) + + if IsValid(self.weld) or self.nextWeld > CurTime() then return end + + local veh = data.HitEntity + if not IsValid(veh) or veh:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + + self.nextWeld = CurTime() + 1 + timer.Simple(0, function() + self:Attach(veh) + end) + +end + +function ENT:Attach(veh) + + local weld = constraint.Weld(self, veh, 0, 0, 0, true, false) + self.weld = weld + + self:SetupWinch(veh) + +end + +function ENT:OnHandsPickup(ply, hands) + + if IsValid(self.weld) then + self.weld:Remove() + self.nextWeld = CurTime() + 1 + + self:SetupWinch() + end + +end diff --git a/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/shared.lua b/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/shared.lua new file mode 100644 index 0000000..2f68f1c --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/dbg_tow_hook/shared.lua @@ -0,0 +1,7 @@ +ENT.Type = 'anim' +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.WinchPosTow = Vector(-131.7, 0, 64) +ENT.WinchPosHook = Vector(1, 0, 8.5) diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_attachment.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_attachment.lua new file mode 100644 index 0000000..642f62c --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_attachment.lua @@ -0,0 +1,32 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Think() + return false +end + +if SERVER then + function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetNotSolid( true ) + self.DoNotDuplicate = true + end +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + end + + function ENT:DrawTranslucent() + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_attachment_translucent.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_attachment_translucent.lua new file mode 100644 index 0000000..de165cd --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_attachment_translucent.lua @@ -0,0 +1,33 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Think() + return false +end + +if SERVER then + function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetNotSolid( true ) + self.DoNotDuplicate = true + end +end + +if CLIENT then + function ENT:Draw() + self:DrawModel() + end + + function ENT:DrawTranslucent() + self:Draw() + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/cl_init.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/cl_init.lua new file mode 100644 index 0000000..d686e09 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/cl_init.lua @@ -0,0 +1,814 @@ +include("shared.lua") +include("cl_lights.lua") + +function ENT:Initialize() + self.SmoothRPM = 0 + self.OldActive = false + self.OldGear = 0 + self.OldThrottle = 0 + self.FadeThrottle = 0 + self.SoundMode = 0 + + self.DamageSnd = CreateSound(self, "simulated_vehicles/engine_damaged.wav") + + self.EngineSounds = {} + + self.spawnlist = self.spawnlist or list.Get('simfphys_vehicles')[self:GetSpawn_List()] + if self.spawnlist then + self.Plates = self.spawnlist.Members.Plates + end + +end + +function ENT:Think() + local curtime = CurTime() + + if self.newTransmitState then + self:SetPoseParameters(curtime) + end + + if not self.updateTransmitState then return end + if (self.nextTransmitStateCheck or 0) > curtime then return end + self.nextTransmitStateCheck = curtime + 0.06 + + if self.newTransmitState then + local Active = self:GetActive() + local Throttle = self:GetThrottle() + local LimitRPM = self:GetLimitRPM() + self:ManageSounds(Active, Throttle, LimitRPM) + self:ManageEffects(Active, Throttle, LimitRPM) + self:CalcFlasher() + + local curAtts = self:GetNetVar('atts') + if not self.wasInRange or self.atts ~= curAtts then + self.atts = curAtts + self:RefreshAttachments() + end + self.wasInRange = true + + self:RefreshPlates() + else + self.atts = {} + if self.wasInRange then + self:RefreshAttachments() + self:RemovePlates() + end + self.wasInRange = false + end + +end + +hook.Add('NotifyShouldTransmit', 'dbg-cars.updateTransmitState', function(ent, state) + if ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + ent.updateTransmitState, ent.newTransmitState = true, state +end) + +function ENT:RefreshAttachments() + + self.attsCSEnts = self.attsCSEnts or {} + local atts = self.attsCSEnts + + for _, ent in ipairs(atts) do + ent:Remove() + end + table.Empty(atts) + + if not self.atts then return end + + for _, data in pairs(self.atts) do + if not data.model then continue end + local ent = octolib.createDummy(data.model, data.rg) + data.parent = self + octolib.applyEntData(ent, data) + atts[#atts + 1] = ent + end + +end + +function ENT:CalcFlasher() + self.Flasher = self.Flasher or 0 + + local flashspeed = self.turnsignals_damaged and 0.08 or 0.035 + + self.Flasher = self.Flasher and self.Flasher + flashspeed or 0 + if self.Flasher >= 1 then + self.Flasher = self.Flasher - 1 + end + + self.flashnum = math.min( math.abs( math.cos( math.rad( self.Flasher * 360 ) ) ^ 2 * 1.5 ) , 1) + + if not self.signal_left and not self.signal_right then return end + + if LocalPlayer() == self:GetDriver() then + local fl_snd = self.flashnum > 0.9 + + if fl_snd ~= self.fl_snd then + self.fl_snd = fl_snd + if fl_snd then + self:EmitSound( "simulated_vehicles/sfx/flasher_on.ogg" ) + else + self:EmitSound( "simulated_vehicles/sfx/flasher_off.ogg" ) + end + end + end +end + +function ENT:GetFlasher() + self.flashnum = self.flashnum or 0 + return self.flashnum +end + +function ENT:SetPoseParameters( curtime ) + self.sm_vSteer = self.sm_vSteer and self.sm_vSteer + (self:GetVehicleSteer() - self.sm_vSteer) * 0.3 or 0 + self:SetPoseParameter("vehicle_steer", self.sm_vSteer ) + + -- if not istable( self.pp_data ) then + -- self.ppNextCheck = self.ppNextCheck or curtime + 0.5 + -- if self.ppNextCheck < curtime then + -- self.ppNextCheck = curtime + 0.5 + + -- net.Start("simfphys_request_ppdata",true) + -- net.WriteEntity( self ) + -- net.SendToServer() + -- end + -- else + -- if not self.CustomWheels then + -- for i = 1, table.Count( self.pp_data ) do + -- local Wheel = self.pp_data[i].entity + + -- if IsValid( Wheel ) then + -- local addPos = Wheel:GetDamaged() and self.pp_data[i].dradius or 0 + + -- local Pose = (self.pp_data[i].pos - self:WorldToLocal( Wheel:GetPos()).z + addPos ) / self.pp_data[i].travel + -- self:SetPoseParameter( self.pp_data[i].name, Pose ) + -- end + -- end + -- end + -- end + + self:InvalidateBoneCache() +end + +function ENT:GetEnginePos() + local Attachment = self:GetAttachment( self:LookupAttachment( "vehicle_engine" ) ) + local pos = self:GetPos() + + if Attachment then + pos = Attachment.Pos + end + + if not self.EnginePos then + self.spawnlist = self.spawnlist or list.Get( "simfphys_vehicles" )[ self:GetSpawn_List() ] + local vehiclelist = self.spawnlist + + if vehiclelist then + self.EnginePos = vehiclelist.Members.EnginePos or false + else + self.EnginePos = false + end + + elseif isvector( self.EnginePos ) then + pos = self:LocalToWorld( self.EnginePos ) + end + + return pos +end + +function ENT:GetRPM() + local RPM = self.SmoothRPM and self.SmoothRPM or 0 + return RPM +end + +function ENT:DamageEffects() + local Pos = self:GetEnginePos() + local Scale = self:GetCurHealth() / self:GetMaxHealth() + local smoke = self:OnSmoke() and Scale <= 0.5 + local fire = self:OnFire() + + if self.wasSmoke ~= smoke then + self.wasSmoke = smoke + if smoke then + self.smokesnd = CreateSound(self, "ambient/gas/steam2.wav") + self.smokesnd:PlayEx(0.2,90) + else + if self.smokesnd then + self.smokesnd:Stop() + end + end + end + + if self.wasFire ~= fire then + self.wasFire = fire + if fire then + self:EmitSound( "ambient/fire/mtov_flame2.wav" ) + + self.firesnd = CreateSound(self, "ambient/fire/fire_small1.wav") + self.firesnd:Play() + else + if self.firesnd then + self.firesnd:Stop() + end + end + end + + if smoke then + if Scale <= 0.5 then + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetEntity( self ) + util.Effect( "simfphys_engine_smoke", effectdata ) + end + end + + if fire then + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetEntity( self ) + util.Effect( "simfphys_engine_fire", effectdata ) + end +end + +function ENT:ManageEffects( Active, fThrottle, LimitRPM ) + self:DamageEffects() + + Active = Active and (self:GetFlyWheelRPM() ~= 0) + if not Active then return end + if not self.ExhaustPositions then return end + + local Scale = fThrottle * (0.2 + math.min(self:GetRPM() / LimitRPM,1) * 0.8) ^ 2 + + for i = 1, table.Count( self.ExhaustPositions ) do + if self.ExhaustPositions[i].OnBodyGroups then + if self:BodyGroupIsValid( self.ExhaustPositions[i].OnBodyGroups ) then + local effectdata = EffectData() + effectdata:SetOrigin( self.ExhaustPositions[i].pos ) + effectdata:SetAngles( self.ExhaustPositions[i].ang ) + effectdata:SetMagnitude( Scale ) + effectdata:SetEntity( self ) + util.Effect( "simfphys_exhaust", effectdata ) + end + else + local effectdata = EffectData() + effectdata:SetOrigin( self.ExhaustPositions[i].pos ) + effectdata:SetAngles( self.ExhaustPositions[i].ang ) + effectdata:SetMagnitude( Scale ) + effectdata:SetEntity( self ) + util.Effect( "simfphys_exhaust", effectdata ) + end + end +end + +function ENT:ManageSounds( Active, fThrottle, LimitRPM ) + local Inside = self:IsPlayerInside(LocalPlayer()) + local FlyWheelRPM = self:GetFlyWheelRPM() + local Active = Active and (FlyWheelRPM ~= 0) + local IdleRPM = self:GetIdleRPM() + + local IsCruise = self:GetIsCruiseModeOn() + + local Throttle = IsCruise and math.Clamp(self:GetThrottle() ^ 3,0.01,0.7) or fThrottle + local Gear = self:GetGear() + local Clutch = self:GetClutch() + local FadeRPM = LimitRPM * 0.5 + + self.FadeThrottle = self.FadeThrottle + math.Clamp(Throttle - self.FadeThrottle,-0.2,0.2) + self.SmoothRPM = self.SmoothRPM + math.Clamp(FlyWheelRPM - self.SmoothRPM,-350,600) + + self.OldThrottle2 = self.OldThrottle2 or 0 + if Throttle ~= self.OldThrottle2 then + self.OldThrottle2 = Throttle + if Throttle == 0 then + if self.SmoothRPM > LimitRPM * 0.6 then + self:Backfire() + end + end + end + + if self:GetRevlimiter() and LimitRPM > 2500 then + if (self.SmoothRPM >= LimitRPM - 200) and self.FadeThrottle > 0 then + self.SmoothRPM = self.SmoothRPM - 1200 + self.FadeThrottle = 0.2 + self:Backfire() + end + end + + if Active ~= self.OldActive then + local preset = self:GetEngineSoundPreset() + local UseGearResetter = self:SetSoundPreset( preset ) + + self.SoundMode = UseGearResetter and 2 or 1 + + self.OldActive = Active + + if Active then + local MaxHealth = self:GetMaxHealth() + local Health = self:GetCurHealth() + + if Health <= (MaxHealth * 0.6) then + self.DamageSnd:PlayEx(0,0) + end + + if self.SoundMode == 2 then + self.HighRPM = CreateSound(self, self.EngineSounds[ "HighRPM" ] ) + self.LowRPM = CreateSound(self, self.EngineSounds[ "LowRPM" ]) + self.Idle = CreateSound(self, self.EngineSounds[ "Idle" ]) + + self.HighRPM:PlayEx(0,0) + self.LowRPM:PlayEx(0,0) + self.Idle:PlayEx(0,0) + else + local IdleSound = self.EngineSounds[ "IdleSound" ] + local LowSound = self.EngineSounds[ "LowSound" ] + local HighSound = self.EngineSounds[ "HighSound" ] + local ThrottleSound = self.EngineSounds[ "ThrottleSound" ] + + if IdleSound then + self.Idle = CreateSound(self, IdleSound) + self.Idle:PlayEx(0,0) + end + + if LowSound then + self.LowRPM = CreateSound(self, LowSound) + self.LowRPM:PlayEx(0,0) + end + + if HighSound then + self.HighRPM = CreateSound(self, HighSound) + self.HighRPM:PlayEx(0,0) + end + + if ThrottleSound then + self.Valves = CreateSound(self, ThrottleSound) + self.Valves:PlayEx(0,0) + end + end + else + self:SaveStopSounds() + end + end + + if Active then + local Volume = 0.25 + 0.25 * ((self.SmoothRPM / LimitRPM) ^ 1.5) + self.FadeThrottle * 0.5 + local Pitch = math.Clamp( (20 + self.SmoothRPM / 50) * self.PitchMulAll,0,255) + + if self.DamageSnd then + self.DamageSnd:ChangeVolume( math.Clamp((self.SmoothRPM / LimitRPM) * 0.6 ^ 1.5, 0, 1) * (Inside and 0.25 or 1) ) + self.DamageSnd:ChangePitch( 100 ) + end + + if self.SoundMode == 2 then + if self.FadeThrottle ~= self.OldThrottle then + self.OldThrottle = self.FadeThrottle + if self.FadeThrottle == 0 and Clutch == 0 then + if self.SmoothRPM >= FadeRPM then + if IsCruise ~= true then + if self.LowRPM then + self.LowRPM:Stop() + end + self.LowRPM = CreateSound(self, self.EngineSounds[ "RevDown" ] ) + self.LowRPM:PlayEx(0,0) + end + end + end + end + + if Gear ~= self.OldGear then + if self.SmoothRPM >= FadeRPM and Gear > 3 then + if Clutch ~= 1 then + if self.OldGear < Gear then + if self.HighRPM then + self.HighRPM:Stop() + end + + self.HighRPM = CreateSound(self, self.EngineSounds[ "ShiftUpToHigh" ] ) + self.HighRPM:PlayEx(0,0) + + if self.SmoothRPM > LimitRPM * 0.6 then + if math.random(0,4) >= 3 then + timer.Simple(0.4, function() + if not IsValid( self ) then return end + self:Backfire() + end) + end + end + else + if self.FadeThrottle > 0 then + if self.HighRPM then + self.HighRPM:Stop() + end + + self.HighRPM = CreateSound(self, self.EngineSounds[ "ShiftDownToHigh" ] ) + self.HighRPM:PlayEx(0,0) + end + end + end + else + if Clutch ~= 1 then + if self.OldGear > Gear and self.FadeThrottle > 0 and Gear >= 3 then + if self.HighRPM then + self.HighRPM:Stop() + end + + self.HighRPM = CreateSound(self, self.EngineSounds[ "ShiftDownToHigh" ] ) + self.HighRPM:PlayEx(0,0) + else + if self.HighRPM then + self.HighRPM:Stop() + end + + if self.LowRPM then + self.LowRPM:Stop() + end + + self.HighRPM = CreateSound(self, self.EngineSounds[ "HighRPM" ] ) + self.LowRPM = CreateSound(self, self.EngineSounds[ "LowRPM" ]) + self.HighRPM:PlayEx(0,0) + self.LowRPM:PlayEx(0,0) + end + end + end + self.OldGear = Gear + end + + self.Idle:ChangeVolume( math.Clamp( math.min((self.SmoothRPM / IdleRPM) * 3,1.5 + self.FadeThrottle * 0.5) * 0.7 - self.SmoothRPM / 2000 ,0,1) * (Inside and 0.25 or 1) ) + self.Idle:ChangePitch( math.Clamp( Pitch * 3,0,255) ) + + self.LowRPM:ChangeVolume( math.Clamp(Volume - (self.SmoothRPM - 2000) / 2000 * self.FadeThrottle,0,1) * (Inside and 0.25 or 1)) + self.LowRPM:ChangePitch( math.Clamp( Pitch * self.PitchMulLow,0,255) ) + + local hivol = math.max((self.SmoothRPM - 2000) / 2000,0) * Volume + self.HighRPM:ChangeVolume( math.Clamp(self.FadeThrottle < 0.4 and hivol * self.FadeThrottle or hivol * self.FadeThrottle * 2.5, 0, 1) * (Inside and 0.25 or 1) ) + self.HighRPM:ChangePitch( math.Clamp( Pitch * self.PitchMulHigh,0,255) ) + else + if Gear ~= self.OldGear then + if self.SmoothRPM >= FadeRPM and Gear > 3 then + if Clutch ~= 1 then + if self.OldGear < Gear then + if self.SmoothRPM > LimitRPM * 0.6 then + if math.random(0,4) >= 3 then + timer.Simple(0.4, function() + if not IsValid( self ) then return end + self:Backfire() + end) + end + end + end + end + end + self.OldGear = Gear + end + + + local IdlePitch = self.Idle_PitchMul + self.Idle:ChangeVolume( math.Clamp( math.min((self.SmoothRPM / IdleRPM) * 3,1.5 + self.FadeThrottle * 0.5) * 0.7 - self.SmoothRPM / 2000,0,1) * (Inside and 0.25 or 1)) + self.Idle:ChangePitch( math.Clamp( Pitch * 3 * IdlePitch,0,255) ) + + local LowPitch = self.Mid_PitchMul + local LowVolume = self.Mid_VolumeMul * 0.5 + local LowFadeOutRPM = LimitRPM * (self.Mid_FadeOutRPMpercent / 100) + local LowFadeOutRate = LimitRPM * self.Mid_FadeOutRate + self.LowRPM:ChangeVolume( math.Clamp( (Volume - math.Clamp((self.SmoothRPM - LowFadeOutRPM) / LowFadeOutRate,0,1)) * LowVolume,0,1) * (Inside and 0.25 or 1)) + self.LowRPM:ChangePitch( math.Clamp(Pitch * LowPitch,0,255) ) + + local HighPitch = self.High_PitchMul + local HighVolume = self.High_VolumeMul * 0.5 + local HighFadeInRPM = LimitRPM * (self.High_FadeInRPMpercent / 100) + local HighFadeInRate = LimitRPM * self.High_FadeInRate + self.HighRPM:ChangeVolume( math.Clamp( math.Clamp((self.SmoothRPM - HighFadeInRPM) / HighFadeInRate,0,Volume) * HighVolume,0,1) * (Inside and 0.25 or 1)) + self.HighRPM:ChangePitch( math.Clamp(Pitch * HighPitch,0,255) ) + + local ThrottlePitch = self.Throttle_PitchMul + local ThrottleVolume = self.Throttle_VolumeMul * 0.5 + self.Valves:ChangeVolume( math.Clamp(math.Clamp((self.SmoothRPM - 2000) / 2000,0,Volume) * (0.2 + 0.15 * self.FadeThrottle) * ThrottleVolume, 0, 1) * (Inside and 0.25 or 1)) + self.Valves:ChangePitch( math.Clamp(Pitch * ThrottlePitch,0,255) ) + end + end + + local hookDir = self:GetNetVar('hookDirection') or 0 + if hookDir ~= self.oldHookDir then + if hookDir == 0 then + if IsValid(self.hookSound) then self.hookSound:Stop() end + self.hookSound = CreateSound(self, 'plats/crane/vertical_stop.wav') + self.hookSound:SetSoundLevel(50) + self.hookSound:PlayEx(0.5, 100) + else + if IsValid(self.hookSound) then self.hookSound:Stop() end + self.hookSound = CreateSound(self, 'plats/crane/vertical_start.wav') + self.hookSound:SetSoundLevel(60) + self.hookSound:PlayEx(0.5, hookDir == -1 and 90 or 100) + end + end + + self.oldHookDir = hookDir +end + +function ENT:Backfire( damaged ) + if not self:GetBackFire() and not damaged then return end + + if not self.ExhaustPositions then return end + + local expos = self.ExhaustPositions + + for i = 1, table.Count( expos ) do + if math.random(1,3) >= 2 or damaged then + local Pos = expos[i].pos + local Ang = expos[i].ang - Angle(90,0,0) + + if expos[i].OnBodyGroups then + if self:BodyGroupIsValid( expos[i].OnBodyGroups ) then + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetAngles( Ang ) + effectdata:SetEntity( self ) + effectdata:SetFlags( damaged and 1 or 0 ) + util.Effect( "simfphys_backfire", effectdata ) + end + else + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetAngles( Ang ) + effectdata:SetEntity( self ) + effectdata:SetFlags( damaged and 1 or 0 ) + util.Effect( "simfphys_backfire", effectdata ) + end + end + end +end + +function ENT:Draw() + + self:DrawModel() + + -- local distSqr = self:GetPos():DistToSqr(EyePos()) + -- self:DrawPlates(distSqr) + -- self:DrawLights(distSqr) + +end + +local emptyTable = {} +local defaultCols = { + bg = Color(255, 255, 255), + border = Color(40, 40, 40), + title = Color(255, 255, 255), + text = Color(0, 0, 0), +} + +function ENT:RefreshPlates() + + if not self.Plates and self.spawnlist then self.Plates = self.spawnlist.Members.Plates end + if not self.Plates then return end + + local txt = self:GetNetVar('cd.plate') + if txt == false then return self:RemovePlates() end + + local distSqr = self:GetPos():DistToSqr(EyePos()) + local al = math.Clamp(1 - (distSqr - 4000000) / 4000000, 0, 1) + if al <= 0 then return self:RemovePlates() end + + txt = txt or 'SPAWNED' + + local cols = self:GetNetVar('cd.plateCol') or emptyTable + if cols ~= self.oldCols then self:RemovePlates() end + self.oldCols = cols + + if not self.PlateEnts then self.PlateEnts = {} end + for k, plate in pairs(self.Plates) do + local ent = self.PlateEnts[k] + if not IsValid(ent) then + ent = ents.CreateClientside('dbg_car_plate') + ent:SetModel('models/octoteam/vehicles/attachments/licenceplate_01.mdl') + ent:SetParent(self) + ent:SetLocalPos(plate.pos) + ent:SetLocalAngles(plate.ang) + ent:SetProxyColors({ + Color(cols[1] or plate.colBG or defaultCols.bg), + Color(cols[2] or plate.colBorder or defaultCols.border), + Color(cols[3] or plate.colTitle or defaultCols.title), + }) + + self.PlateEnts[k] = ent + else + ent.alpha = al + ent.text = txt + ent.color = cols[4] or plate.colText or defaultCols.text + end + end + +end + +function ENT:RemovePlates() + local ents = self.PlateEnts + if not ents then return end + + self.PlateEnts = nil + timer.Simple(0, function() + for _, v in pairs(ents) do + if isentity(v) and v:IsValid() then v:Remove() end + end + end) +end + +function ENT:SetSoundPreset(index) + self.spawnlist = self.spawnlist or list.Get( "simfphys_vehicles" )[self:GetSpawn_List()] + local vehiclelist = self.spawnlist + + if vehiclelist and not self.ExhaustPositions then + self.ExhaustPositions = vehiclelist.Members.ExhaustPositions + end + + if index == -1 then + if vehiclelist then + local data = self:GetNetVar('soundoverride') + + if data and data.type == 1 then + + self.EngineSounds[ "Idle" ] = data.snd_idle + self.EngineSounds[ "LowRPM" ] = data.snd_low + self.EngineSounds[ "HighRPM" ] = data.snd_mid + self.EngineSounds[ "RevDown" ] = data.snd_low_revdown + self.EngineSounds[ "ShiftUpToHigh" ] = data.snd_mid_gearup + self.EngineSounds[ "ShiftDownToHigh" ] = data.snd_mid_geardown + + self.PitchMulLow = data.snd_low_pitch or 1 + self.PitchMulHigh = data.snd_mid_pitch or 1 + self.PitchMulAll = data.snd_pitch or 1 + + else + + local idle = vehiclelist.Members.snd_idle or "" + local low = vehiclelist.Members.snd_low or "" + local mid = vehiclelist.Members.snd_mid or "" + local revdown = vehiclelist.Members.snd_low_revdown or "" + local gearup = vehiclelist.Members.snd_mid_gearup or "" + local geardown = vehiclelist.Members.snd_mid_geardown or "" + + self.EngineSounds[ "Idle" ] = idle ~= "" and idle or false + self.EngineSounds[ "LowRPM" ] = low ~= "" and low or false + self.EngineSounds[ "HighRPM" ] = mid ~= "" and mid or false + self.EngineSounds[ "RevDown" ] = revdown ~= "" and revdown or low + self.EngineSounds[ "ShiftUpToHigh" ] = gearup ~= "" and gearup or mid + self.EngineSounds[ "ShiftDownToHigh" ] = geardown ~= "" and geardown or gearup + + self.PitchMulLow = vehiclelist.Members.snd_low_pitch or 1 + self.PitchMulHigh = vehiclelist.Members.snd_mid_pitch or 1 + self.PitchMulAll = vehiclelist.Members.snd_pitch or 1 + end + else + local ded = "common/bugreporter_failed.wav" + + self.EngineSounds[ "Idle" ] = ded + self.EngineSounds[ "LowRPM" ] = ded + self.EngineSounds[ "HighRPM" ] = ded + self.EngineSounds[ "RevDown" ] = ded + self.EngineSounds[ "ShiftUpToHigh" ] = ded + self.EngineSounds[ "ShiftDownToHigh" ] = ded + + self.PitchMulLow = 0 + self.PitchMulHigh = 0 + self.PitchMulAll = 0 + end + + if self.EngineSounds[ "Idle" ] ~= false and self.EngineSounds[ "LowRPM" ] ~= false and self.EngineSounds[ "HighRPM" ] ~= false then + self:PrecacheSounds() + + return true + else + self:SetSoundPreset( 0 ) + return false + end + end + + if index == 0 then + local data = self:GetNetVar('soundoverride') + + if data and data.type ~= 1 then + self.EngineSounds[ "IdleSound" ] = data.Sound_Idle or "simulated_vehicles/misc/e49_idle.wav" + self.Idle_PitchMul = data.Sound_IdlePitch or 1 + + self.EngineSounds[ "LowSound" ] = data.Sound_Mid or "simulated_vehicles/misc/gto_onlow.wav" + self.Mid_PitchMul = data.Sound_MidPitch or 1 + self.Mid_VolumeMul = data.Sound_MidVolume or 0.75 + self.Mid_FadeOutRPMpercent = data.Sound_MidFadeOutRPMpercent or 68 + self.Mid_FadeOutRate = data.Sound_MidFadeOutRate or 0.4 + + self.EngineSounds[ "HighSound" ] = data.Sound_High or "simulated_vehicles/misc/nv2_onlow_ex.wav" + self.High_PitchMul = data.Sound_HighPitch or 1 + self.High_VolumeMul = data.Sound_HighVolume or 1 + self.High_FadeInRPMpercent = data.Sound_HighFadeInRPMpercent or 26.6 + self.High_FadeInRate = data.Sound_HighFadeInRate or 0.266 + + self.EngineSounds[ "ThrottleSound" ] = data.Sound_Throttle or "simulated_vehicles/valve_noise.wav" + self.Throttle_PitchMul = data.Sound_ThrottlePitch or 0.65 + self.Throttle_VolumeMul = data.Sound_ThrottleVolume or 1 + else + self.EngineSounds[ "IdleSound" ] = vehiclelist and vehiclelist.Members.Sound_Idle or "simulated_vehicles/misc/e49_idle.wav" + self.Idle_PitchMul = (vehiclelist and vehiclelist.Members.Sound_IdlePitch) or 1 + + self.EngineSounds[ "LowSound" ] = vehiclelist and vehiclelist.Members.Sound_Mid or "simulated_vehicles/misc/gto_onlow.wav" + self.Mid_PitchMul = (vehiclelist and vehiclelist.Members.Sound_MidPitch) or 1 + self.Mid_VolumeMul = (vehiclelist and vehiclelist.Members.Sound_MidVolume) or 0.75 + self.Mid_FadeOutRPMpercent = (vehiclelist and vehiclelist.Members.Sound_MidFadeOutRPMpercent) or 68 + self.Mid_FadeOutRate = (vehiclelist and vehiclelist.Members.Sound_MidFadeOutRate) or 0.4 + + self.EngineSounds[ "HighSound" ] = vehiclelist and vehiclelist.Members.Sound_High or "simulated_vehicles/misc/nv2_onlow_ex.wav" + self.High_PitchMul = (vehiclelist and vehiclelist.Members.Sound_HighPitch) or 1 + self.High_VolumeMul = (vehiclelist and vehiclelist.Members.Sound_HighVolume) or 1 + self.High_FadeInRPMpercent = (vehiclelist and vehiclelist.Members.Sound_HighFadeInRPMpercent) or 26.6 + self.High_FadeInRate = (vehiclelist and vehiclelist.Members.Sound_HighFadeInRate) or 0.266 + + self.EngineSounds[ "ThrottleSound" ] = vehiclelist and vehiclelist.Members.Sound_Throttle or "simulated_vehicles/valve_noise.wav" + self.Throttle_PitchMul = (vehiclelist and vehiclelist.Members.Sound_ThrottlePitch) or 0.65 + self.Throttle_VolumeMul = (vehiclelist and vehiclelist.Members.Sound_ThrottleVolume) or 1 + end + + self.PitchMulLow = 1 + self.PitchMulHigh = 1 + self.PitchMulAll = 1 + + self:PrecacheSounds() + + return false + end + + if index > 0 then + local clampindex = math.Clamp(index,1,table.Count(simfphys.SoundPresets)) + self.EngineSounds[ "Idle" ] = simfphys.SoundPresets[clampindex][1] + self.EngineSounds[ "LowRPM" ] = simfphys.SoundPresets[clampindex][2] + self.EngineSounds[ "HighRPM" ] = simfphys.SoundPresets[clampindex][3] + self.EngineSounds[ "RevDown" ] = simfphys.SoundPresets[clampindex][4] + self.EngineSounds[ "ShiftUpToHigh" ] = simfphys.SoundPresets[clampindex][5] + self.EngineSounds[ "ShiftDownToHigh" ] = simfphys.SoundPresets[clampindex][6] + + self.PitchMulLow = simfphys.SoundPresets[clampindex][7] + self.PitchMulHigh = simfphys.SoundPresets[clampindex][8] + self.PitchMulAll = simfphys.SoundPresets[clampindex][9] + + self:PrecacheSounds() + + return true + end + + return false +end + +function ENT:PrecacheSounds() + for index, sound in pairs( self.EngineSounds ) do + if not isbool(sound) then + if file.Exists( "sound/"..sound, "GAME" ) then + util.PrecacheSound( sound ) + else + print("Warning soundfile \""..sound.."\" not found. Using \"common/null.wav\" instead to prevent fps rape") + self.EngineSounds[index] = "common/null.wav" + end + end + end +end + +function ENT:SaveStopSounds() + if self.HighRPM then + self.HighRPM:Stop() + end + + if self.LowRPM then + self.LowRPM:Stop() + end + + if self.Idle then + self.Idle:Stop() + end + + if self.Valves then + self.Valves:Stop() + end + + if self.DamageSnd then + self.DamageSnd:Stop() + end +end + +function ENT:OnRemove() + self:SaveStopSounds() + + if self.smokesnd then + self.smokesnd:Stop() + end + + if self.firesnd then + self.firesnd:Stop() + end + + self.atts = {} + self:RefreshAttachments() + self:RemovePlates() +end + +function ENT:IsPlayerInside(ply) + local veh = ply:GetVehicle() + if not IsValid(veh) then return false end + + return veh:GetParent() == self +end + +netstream.Hook('simfphys.updateFuelUse', function(ent, fuelUse) + ent.fuelUse = fuelUse +end) diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/cl_lights.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/cl_lights.lua new file mode 100644 index 0000000..781c76e --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/cl_lights.lua @@ -0,0 +1,3 @@ +function ENT:DrawLights(distSqr) + -- TODO +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/init.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/init.lua new file mode 100644 index 0000000..5ce9976 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/init.lua @@ -0,0 +1,1279 @@ +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_lights.lua" ) +include("shared.lua") +include("spawn.lua") +include("simfunc.lua") +include("numpads.lua") + +function ENT:OnSpawn() +end + +function ENT:OnTick() +end + +function ENT:OnDelete() +end + +function ENT:OnDestroyed() +end + +function ENT:Think() + local Time = CurTime() + + self:OnTick() + + self.NextTick = self.NextTick or 0 + if self.NextTick <= Time then + self.NextTick = Time + 0.0625 + + if IsValid( self.DriverSeat ) then + local Driver = self.DriverSeat:GetDriver() + Driver = IsValid( self.RemoteDriver ) and self.RemoteDriver or Driver + + local OldDriver = self:GetDriver() + if OldDriver ~= Driver then + self:SetDriver( Driver ) + + local HadDriver = IsValid( OldDriver ) + local HasDriver = IsValid( Driver ) + + if HasDriver then + self:SetActive( true ) + self:SetupControls( Driver ) + + -- if Driver:GetInfoNum( "cl_simfphys_autostart", 1 ) > 0 then + -- self:StartEngine() + -- end + + else + -- self:UnLock() + + -- if self.ems then + -- self.ems:Stop() + -- end + + if self.horn then + self.horn:Stop() + end + + if self.PressedKeys then + for k,v in pairs( self.PressedKeys ) do + if isbool( v ) then + self.PressedKeys[k] = false + end + end + end + + if self.keys then + for i = 1, table.Count( self.keys ) do + numpad.Remove( self.keys[i] ) + end + end + + if HadDriver then + if self.k_togglehandbrake and not self:EngineActive() then + self:SetActive( false ) + end + -- if OldDriver:GetInfoNum( "cl_simfphys_autostart", 1 ) > 0 then + -- self:StopEngine() + -- self:SetActive( false ) + -- else + -- self:ResetJoystick() + -- + -- if not self:EngineActive() then + -- self:SetActive( false ) + -- end + -- end + -- else + -- self:SetActive( false ) + -- self:StopEngine() + end + end + end + end + + if self:IsInitialized() then + self:SetColors() + self:SimulateVehicle( Time ) + self:ControlLighting( Time ) + + -- if istable( WireLib ) then + -- self:UpdateWireOutputs() + -- end + + self.NextWaterCheck = self.NextWaterCheck or 0 + if self.NextWaterCheck < Time then + self.NextWaterCheck = Time + 0.2 + self:WaterPhysics() + end + + if self:GetActive() then + self:SetPhysics( (math.abs(self.ForwardSpeed) < 50) and (self.Brake > 0 or self.HandBrake > 0) ) + else + self:SetPhysics( true ) + end + end + end + + self:NextThink( Time ) + + return true +end + +function ENT:ForceGear(desGear) + self.CurrentGear = math.Clamp(math.Round(desGear, 0), 1, #self.Gears) + self:SetGear(self.CurrentGear) +end + +function ENT:OnActiveChanged( name, old, new) + if new == old then return end + + if not self:IsInitialized() then return end + + local TurboCharged = self:GetTurboCharged() + local SuperCharged = self:GetSuperCharged() + + if new == true then + + self.HandBrakePower = self:GetMaxTraction() - self:GetTractionBias() * self:GetMaxTraction() + + -- if self:GetEMSEnabled() then + -- if self.ems then + -- self.ems:Play() + -- end + -- end + + if TurboCharged then + self.Turbo = CreateSound(self, self.snd_spool or "simulated_vehicles/turbo_spin.wav") + self.Turbo:PlayEx(0,0) + end + + if SuperCharged then + self.Blower = CreateSound(self, self.snd_bloweroff or "simulated_vehicles/blower_spin.wav") + self.BlowerWhine = CreateSound(self, self.snd_bloweron or "simulated_vehicles/blower_gearwhine.wav") + + self.Blower:PlayEx(0,0) + self.BlowerWhine:PlayEx(0,0) + end + else + self:StopEngine() + + if TurboCharged then + self.Turbo:Stop() + end + + if SuperCharged then + self.Blower:Stop() + self.BlowerWhine:Stop() + end + + -- self:SetIsBraking( false ) + end + + if istable( self.Wheels ) then + for i = 1, table.Count( self.Wheels ) do + local Wheel = self.Wheels[ i ] + if IsValid(Wheel) then + Wheel:SetOnGround( 0 ) + end + end + end +end + +ENT.SendThrottleAnim = octolib.func.debounceStart(function(self) + local ply = IsValid(self) and self:GetDriver() + if IsValid(ply) then + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'rfoot', self.InputThrottle) + end +end, 0.25) + +function ENT:OnThrottleChanged( name, old, new) + if new == old then return end + self:SendThrottleAnim() + + local Health = self:GetCurHealth() + local MaxHealth = self:GetMaxHealth() + local Active = self:EngineActive() + + if new == 1 then + if Health < MaxHealth * 0.6 then + if Active then + if math.Round(math.random(0,4),0) == 1 then + self:DamagedStall() + end + end + end + end + + if new == 0 then + if self:GetTurboCharged() then + if (self.SmoothTurbo > 350) then + local Volume = math.Clamp( ((self.SmoothTurbo - 300) / 150) ,0, 1) * 0.5 + self.SmoothTurbo = 0 + self.BlowOff:Stop() + self.BlowOff = CreateSound(self, self.snd_blowoff or "simulated_vehicles/turbo_blowoff.ogg") + self.BlowOff:PlayEx(Volume,100) + end + end + end +end + +function ENT:WaterPhysics() + if self:WaterLevel() <= 1 then self.IsInWater = false return end + + if self:GetDoNotStall() then + + self:SetOnFire( false ) + self:SetOnSmoke( false ) + + return + end + + if not self.IsInWater then + if self:EngineActive() then + self:EmitSound( "vehicles/jetski/jetski_off.wav" ) + end + + self.IsInWater = true + self:SetEngineActive(false) + self.EngineRPM = 0 + self:SetFlyWheelRPM( 0 ) + + self:SetOnFire( false ) + self:SetOnSmoke( false ) + end + + local phys = self:GetPhysicsObject() + phys:ApplyForceCenter( -self:GetVelocity() * 0.5 * phys:GetMass() * 2.5 ) +end + +function ENT:SetColors() + if self.ColorableProps then + + local Color = self:GetColor() + local dot = Color.r * Color.g * Color.b * Color.a + + if dot ~= self.OldColor then + + for i, prop in pairs( self.ColorableProps ) do + if IsValid(prop) then + prop:SetColor( Color ) + prop:SetRenderMode( self:GetRenderMode() ) + end + end + + self.OldColor = dot + end + end +end + +function ENT:ControlLighting( curtime ) + if (self.NextLightCheck or 0) < curtime and self.LightsActivated ~= self.DoCheck then + self.DoCheck = self.LightsActivated + + if self.LightsActivated then + self:SetLightsEnabled(true) + end + end +end + +function ENT:GetEngineData() + local LimitRPM = math.max(self:GetLimitRPM(),4) + local Powerbandend = math.Clamp(self:GetPowerBandEnd(),3,LimitRPM - 1) + local Powerbandstart = math.Clamp(self:GetPowerBandStart(),2,Powerbandend - 1) + local IdleRPM = math.Clamp(self:GetIdleRPM(),1,Powerbandstart - 1) + local Data = { + IdleRPM = IdleRPM, + Powerbandstart = Powerbandstart, + Powerbandend = Powerbandend, + LimitRPM = LimitRPM, + } + return Data +end + +local halfpi1, halfpi2 = 180 / math.pi, math.pi / 180 +local var1 = 0.0568182 * 0.75 +function ENT:SimulateVehicle( curtime ) + local Active = self:GetActive() + + local EngineData = self:GetEngineData() + local LimitRPM = EngineData.LimitRPM + local Powerbandend = EngineData.Powerbandend + local Powerbandstart = EngineData.Powerbandstart + local IdleRPM = EngineData.IdleRPM + + self.Forward = self:LocalToWorldAngles( self.VehicleData.LocalAngForward ):Forward() + self.Right = self:LocalToWorldAngles( self.VehicleData.LocalAngRight ):Forward() + self.Up = self:GetUp() + + self.Vel = self:GetVelocity() + self.VelNorm = self.Vel:GetNormalized() + + local speed = self.Vel:Length() + if speed > 0 then + self.MoveDir = math.acos( math.Clamp( self.Forward:Dot(self.VelNorm), -1, 1) ) * halfpi1 + self.ForwardSpeed = math.cos(self.MoveDir * halfpi2) * self.Vel:Length() + else + self.MoveDir = 0 + self.ForwardSpeed = 0 + end + + if self.poseon then + self.cpose = self.cpose or self.LightsPP.min + self.cpose = math.Approach(self.cpose, self.poseon, FrameTime() * 2) + self:SetPoseParameter(self.LightsPP.name, self.cpose) + end + + self:SetPoseParameter("vehicle_guage", (math.abs(self.ForwardSpeed) * var1) / (self.SpeedoMax or 120)) + + if self.RPMGaugePP then + local flywheelrpm = self:GetFlyWheelRPM() + local rpm + if self:GetRevlimiter() then + local throttle = self:GetThrottle() + local maxrpm = self:GetLimitRPM() + local revlimiter = (maxrpm > 2500) and (throttle > 0) + rpm = math.Round(((flywheelrpm >= maxrpm - 200) and revlimiter) and math.Round(flywheelrpm - 200 + math.sin(curtime * 50) * 600,0) or flywheelrpm,0) + else + rpm = flywheelrpm + end + + self:SetPoseParameter(self.RPMGaugePP, rpm / self.RPMGaugeMax) + end + + if Active then + local ply = self:GetDriver() + local IsValidDriver = IsValid( ply ) + + local GearUp = self.PressedKeys["M1"] and 1 or self.PressedKeys["joystick_gearup"] + local GearDown = self.PressedKeys["M2"] and 1 or self.PressedKeys["joystick_geardown"] + + local W = self.PressedKeys["W"] and 1 or 0 + local A = not self.turnmenuOpened and (self.PressedKeys["A"] and 1 or self.PressedKeys["joystick_steer_left"]) or 0 + local S = self.PressedKeys["S"] and 1 or 0 + local D = not self.turnmenuOpened and (self.PressedKeys["D"] and 1 or self.PressedKeys["joystick_steer_right"]) or 0 + + if IsValidDriver then self:PlayerSteerVehicle( ply, A, D ) end + + local k_Shift = self.PressedKeys["Shift"] + local Shift = k_Shift and 1 or 0 + + local Alt = self.PressedKeys["Alt"] and 1 or 0 + local Space = self.PressedKeys["Space"] and 1 or self.PressedKeys["joystick_handbrake"] + + self:SimulateEngine( IdleRPM, LimitRPM, Powerbandstart, Powerbandend, curtime ) + if IsValidDriver or self.IsAutonomous then + self:SimulateTransmission(W,S,Shift,Alt,Space,GearUp,GearDown,IdleRPM,Powerbandstart,Powerbandend,cruise,curtime) + self:SimulateWheels( math.max(Space,Alt), LimitRPM ) + else + local vel = self:GetVelocity():LengthSqr() + if vel > 40 or math.abs(self.EngineTorque) > 0.001 then + self:SimulateWheels( math.max(Space,Alt), LimitRPM ) + end + + if vel < 40 and vel > 0.01 then + local phys = self:GetPhysicsObject() + phys:AddVelocity(-phys:GetVelocity() * 0.5) + phys:AddAngleVelocity(-phys:GetAngleVelocity() * 0.5) + end + end + + if self.WheelOnGroundDelay < curtime then + self:WheelOnGround() + self.WheelOnGroundDelay = curtime + 0.15 + end + end + + if self.CustomWheels then + self:PhysicalSteer() + end +end + +function ENT:SetupControls( ply ) + self:ResetJoystick() + + if self.keys then + for i = 1, table.Count( self.keys ) do + numpad.Remove( self.keys[i] ) + end + end + + if IsValid(ply) then + self.cl_SteerSettings = { + Overwrite = (ply:GetInfoNum( "cl_simfphys_overwrite", 0 ) >= 1), + TurnSpeed = math.Clamp(ply:GetInfoNum( "cl_simfphys_steerspeed", 8 ), 1, 20), + fadespeed = math.Clamp(ply:GetInfoNum( "cl_simfphys_fadespeed", 535 ), 1, 1000), + fastspeedangle = math.Clamp(ply:GetInfoNum( "cl_simfphys_steerangfast", 10 ), 1, 30), + smoothsteer = (ply:GetInfoNum( "cl_simfphys_smoothsteer", 0 ) >= 1), + } + + local W = ply:GetInfoNum( "cl_simfphys_keyforward", 0 ) + local A = ply:GetInfoNum( "cl_simfphys_keyleft", 0 ) + local S = ply:GetInfoNum( "cl_simfphys_keyreverse", 0 ) + local D = ply:GetInfoNum( "cl_simfphys_keyright", 0 ) + + local aW = ply:GetInfoNum( "cl_simfphys_key_air_forward", 0 ) + local aA = ply:GetInfoNum( "cl_simfphys_key_air_left", 0 ) + local aS = ply:GetInfoNum( "cl_simfphys_key_air_reverse", 0 ) + local aD = ply:GetInfoNum( "cl_simfphys_key_air_right", 0 ) + + local GearUp = ply:GetInfoNum( "cl_simfphys_keygearup", 0 ) + local GearDown = ply:GetInfoNum( "cl_simfphys_keygeardown", 0 ) + + local R = ply:GetInfoNum( "cl_simfphys_keyhandbrake_toggle", 0 ) + + local F = ply:GetInfoNum( "cl_simfphys_lights", 0 ) + + local M = ply:GetInfoNum( "cl_simfphys_emssiren", 0 ) + local N = ply:GetInfoNum( "cl_simfphys_emslights", 0 ) + local kSpecial = ply:GetInfoNum( "cl_simfphys_special", 0 ) + + local H = ply:GetInfoNum( "cl_simfphys_keyhorn", 0 ) + + local I = ply:GetInfoNum( "cl_simfphys_keyengine", 0 ) + + local Shift = ply:GetInfoNum( "cl_simfphys_keywot", 0 ) + + local Alt = ply:GetInfoNum( "cl_simfphys_keyclutch", 0 ) + local Space = ply:GetInfoNum( "cl_simfphys_keyhandbrake", 0 ) + + local lock = ply:GetInfoNum( "cl_simfphys_key_lock", 0 ) + local turnMenu = ply:GetInfoNum('cl_simfphys_key_turnmenu', 0) + + local w_dn = numpad.OnDown( ply, W, "k_forward",self, true ) + local w_up = numpad.OnUp( ply, W, "k_forward",self, false ) + local s_dn = numpad.OnDown( ply, S, "k_reverse",self, true ) + local s_up = numpad.OnUp( ply, S, "k_reverse",self, false ) + local a_dn = numpad.OnDown( ply, A, "k_left",self, true ) + local a_up = numpad.OnUp( ply, A, "k_left",self, false ) + local d_dn = numpad.OnDown( ply, D, "k_right",self, true ) + local d_up = numpad.OnUp( ply, D, "k_right",self, false ) + + local aw_dn = numpad.OnDown( ply, aW, "k_a_forward",self, true ) + local aw_up = numpad.OnUp( ply, aW, "k_a_forward",self, false ) + local as_dn = numpad.OnDown( ply, aS, "k_a_reverse",self, true ) + local as_up = numpad.OnUp( ply, aS, "k_a_reverse",self, false ) + local aa_dn = numpad.OnDown( ply, aA, "k_a_left",self, true ) + local aa_up = numpad.OnUp( ply, aA, "k_a_left",self, false ) + local ad_dn = numpad.OnDown( ply, aD, "k_a_right",self, true ) + local ad_up = numpad.OnUp( ply, aD, "k_a_right",self, false ) + + local gup_dn = numpad.OnDown( ply, GearUp, "k_gup",self, true ) + local gup_up = numpad.OnUp( ply, GearUp, "k_gup",self, false ) + + local gdn_dn = numpad.OnDown( ply, GearDown, "k_gdn",self, true ) + local gdn_up = numpad.OnUp( ply, GearDown, "k_gdn",self, false ) + + local shift_dn = numpad.OnDown( ply, Shift, "k_wot",self, true ) + local shift_up = numpad.OnUp( ply, Shift, "k_wot",self, false ) + + local alt_dn = numpad.OnDown( ply, Alt, "k_clutch",self, true ) + local alt_up = numpad.OnUp( ply, Alt, "k_clutch",self, false ) + + local space_dn = numpad.OnDown( ply, Space, "k_hbrk",self, true ) + local space_up = numpad.OnUp( ply, Space, "k_hbrk",self, false ) + + local k_cruise_dn = numpad.OnDown( ply, R, "k_hbrk_t",self, true ) + local k_cruise_up = numpad.OnUp( ply, R, "k_hbrk_t",self, false ) + + local k_lights_dn = numpad.OnDown( ply, F, "k_lgts",self, true ) + local k_lights_up = numpad.OnUp( ply, F, "k_lgts",self, false ) + + local k_flights_dn = numpad.OnDown( ply, M, "k_flgts",self, true ) + local k_flights_up = numpad.OnUp( ply, M, "k_flgts",self, false ) + + local k_siren_dn = numpad.OnDown( ply, N, "k_siren",self, true ) + local k_siren_up = numpad.OnUp( ply, N, "k_siren",self, false ) + + local k_special_dn = numpad.OnDown( ply, kSpecial, "k_spcl",self, true ) + local k_special_up = numpad.OnUp( ply, kSpecial, "k_spcl",self, false ) + + local k_horn_dn = numpad.OnDown( ply, H, "k_hrn",self, true ) + local k_horn_up = numpad.OnUp( ply, H, "k_hrn",self, false ) + + local k_engine_dn = numpad.OnDown( ply, I, "k_eng",self, true ) + local k_engine_up = numpad.OnUp( ply, I, "k_eng",self, false ) + + local k_lock_dn = numpad.OnDown( ply, lock, "k_lock",self, true ) + local k_lock_up = numpad.OnUp( ply, lock, "k_lock",self, false ) + + local k_tm_dn = numpad.OnDown(ply, turnMenu, 'k_turnmenu', self, true) + local k_tm_up = numpad.OnUp(ply, turnMenu, 'k_turnmenu', self, false) + + self.keys = { + w_dn,w_up, + s_dn,s_up, + a_dn,a_up, + d_dn,d_up, + aw_dn,aw_up, + as_dn,as_up, + aa_dn,aa_up, + ad_dn,ad_up, + gup_dn,gup_up, + gdn_dn,gdn_up, + shift_dn,shift_up, + alt_dn,alt_up, + space_dn,space_up, + k_cruise_dn,k_cruise_up, + k_lights_dn,k_lights_up, + k_horn_dn,k_horn_up, + k_flights_dn,k_flights_up, + k_siren_dn,k_siren_up, + k_special_dn,k_special_up, + k_engine_dn,k_engine_up, + k_lock_dn,k_lock_up, + k_tm_dn,k_tm_up, + } + end +end + +function ENT:PlayAnimation( animation ) + local anims = string.Implode( ",", self:GetSequenceList() ) + + if not animation or not string.match( string.lower(anims), string.lower( animation ), 1 ) then return end + + local sequence = self:LookupSequence( animation ) + + self:ResetSequence( sequence ) + self:SetPlaybackRate( 1 ) + self:SetSequence( sequence ) +end + +function ENT:PhysicalSteer() + + if IsValid(self.SteerMaster) then + local physobj = self.SteerMaster:GetPhysicsObject() + if not IsValid(physobj) then return end + + if physobj:IsMotionEnabled() then + physobj:EnableMotion(false) + end + + self.SteerMaster:SetAngles( self:LocalToWorldAngles( Angle(0,math.Clamp(-self.VehicleData[ "Steer" ],-self.CustomSteerAngle,self.CustomSteerAngle),0) ) ) + end + + if IsValid(self.SteerMaster2) then + local physobj = self.SteerMaster2:GetPhysicsObject() + if not IsValid(physobj) then return end + + if physobj:IsMotionEnabled() then + physobj:EnableMotion(false) + end + + self.SteerMaster2:SetAngles( self:LocalToWorldAngles( Angle(0,math.Clamp(self.VehicleData[ "Steer" ],-self.CustomSteerAngle,self.CustomSteerAngle),0) ) ) + end +end + +function ENT:IsInitialized() + return (self.EnableSuspension == 1) +end + +function ENT:EngineActive() + return self:GetEngineActive() +end + +function ENT:IsDriveWheelsOnGround() + return (self.DriveWheelsOnGround == 1) +end + +function ENT:GetRPM() + local RPM = self.EngineRPM and self.EngineRPM or 0 + return RPM +end + +function ENT:GetEngineRPM() + local flywheelrpm = self:GetRPM() + local rpm + if self:GetRevlimiter() then + local throttle = self:GetThrottle() + local maxrpm = self:GetLimitRPM() + local revlimiter = (maxrpm > 2500) and (throttle > 0) + rpm = math.Round(((flywheelrpm >= maxrpm - 200) and revlimiter) and math.Round(flywheelrpm - 200 + math.sin(CurTime()* 50) * 600,0) or flywheelrpm,0) + else + rpm = flywheelrpm + end + + return rpm +end + +function ENT:GetDiffGear() + return math.max(self:GetDifferentialGear(),0.01) +end + +function ENT:DamagedStall() + if not self:GetActive() then return end + + local rtimer = math.random(5, 15) + self.CantStart = true + + self:StallAndRestart( rtimer, true ) + timer.Simple( rtimer, function() + if not IsValid( self ) then return end + net.Start( "simfphys_backfire" ) + net.WriteEntity( self ) + net.Broadcast() + self.CantStart = nil + end) +end + +function ENT:StopEngine() + if self:EngineActive() then + self:EmitSound( "vehicles/jetski/jetski_off.wav" ) + + self.EngineRPM = 0 + self:SetEngineActive(false) + + self:SetFlyWheelRPM( 0 ) + self:SetIsCruiseModeOn( false ) + end +end + +function ENT:CanStart() + local FuelSystemOK = self:GetFuel() > 0 + local canstart = self:GetCurHealth() > (self:GetMaxHealth() * 0.1) and FuelSystemOK + + return canstart and not self.CantStart +end + +function ENT:StartEngine() + if not self:CanStart() then return end + + if not self:EngineActive() then + if not self.IsInWater then + self.EngineRPM = self:GetEngineData().IdleRPM + self:SetEngineActive(true) + else + if self:GetDoNotStall() then + self.EngineRPM = self:GetEngineData().IdleRPM + self:SetEngineActive(true) + end + end + end +end + +function ENT:StallAndRestart() + self:StopEngine() +end + +function ENT:PlayerSteerVehicle(ply, left, right) + if not IsValid(ply) then return end + + local leftRight = right - left + local speedFrac = math.Clamp((self.ForwardSpeed - 300) / (self:GetFastSteerConeFadeSpeed() - 300), 0, 1) + local wantedSteer = leftRight ~= 0 and leftRight or self.wantedSteer or 0 + local localDrift = math.acos(math.Clamp(self.Right:Dot(self.VelNorm), -1, 1)) * halfpi1 - 90 + local steerAngle = octolib.math.remap(speedFrac, 0, 1, self.VehicleData.steerangle, math.Clamp(math.abs(localDrift) * 1.2, self.FastSteeringAngle, self.VehicleData.steerangle)) + local steerSpeed = self:GetSteerSpeed() * steerAngle / self.VehicleData.steerangle + + if wantedSteer == 0 and self.CounterSteeringMul and self.ForwardSpeed > 50 then + wantedSteer = wantedSteer - math.min(localDrift, self.CounterSteeringAng or 90) * self.CounterSteeringMul / steerAngle + end + + local oldAng = self.SmoothAng + self.SmoothAng = math.Approach(self.SmoothAng, math.Clamp(wantedSteer, -1, 1) * steerAngle, steerSpeed * FrameTime() * 24) + + if oldAng > 5 and self.SmoothAng <= 5 and self.turnMode == 3 + or oldAng < -5 and self.SmoothAng >= -5 and self.turnMode == 2 then + self:TurnSignal(0) + end + + if self.SmoothAng ~= self.VehicleData.Steer then + self:SteerVehicle(self.SmoothAng) + end +end + +function ENT:SteerVehicle(steer) + self.VehicleData.Steer = steer + self:SetVehicleSteer(steer / self.VehicleData.steerangle) +end + +local function flash(veh, times) + local oldModes = { + veh:GetLightsEnabled(), + veh:GetIsBraking(), + } + + for i = 0, times - 1 do + timer.Simple(i * 0.25, function() + veh:SetLightsEnabled(not oldModes[1]) + veh:SetIsBraking(not oldModes[2]) + end) + timer.Simple(i * 0.25 + 0.15, function() + veh:SetLightsEnabled(oldModes[1]) + veh:SetIsBraking(oldModes[2]) + end) + end + timer.Simple(times * 0.25 + 0.2, function() + veh:SetLightsEnabled(oldModes[1]) + veh:SetIsBraking(oldModes[2]) + end) +end + +function ENT:Lock() + if self.lockpicked or self:GetIsLocked() then return end + self:SetIsLocked(true) + self:EmitSound("car/carlock.wav") + flash(self, 2) + -- netstream.StartPVS(self:GetPos(), 'simfphys.flash', self, 2) +end + +function ENT:UnLock() + if self.lockpicked or not self:GetIsLocked() then return end + self:SetIsLocked(false) + self:EmitSound("car/carunlock.wav") + flash(self, 1) + -- netstream.StartPVS(self:GetPos(), 'simfphys.flash', self, 1) +end + +function ENT:ForceLightsOff() + local vehiclelist = list.Get( "simfphys_lights" )[self.LightsTable] or false + if not vehiclelist then return end + + if vehiclelist.Animation then + if self.LightsActivated then + self.LightsActivated = false + self.LampsActivated = false + + self:SetLightsEnabled(false) + self:SetLampsEnabled(false) + end + end +end + +function ENT:EnteringSequence( ply ) + local LinkedDoorAnims = istable(self.ModelInfo) and istable(self.ModelInfo.LinkDoorAnims) + if not istable(self.Enterpoints) and not LinkedDoorAnims then return end + + local sequence + local pos + local dist + + if LinkedDoorAnims then + for i,_ in pairs( self.ModelInfo.LinkDoorAnims ) do + local seq_ = self.ModelInfo.LinkDoorAnims[ i ].enter + + local a_pos = self:GetAttachment( self:LookupAttachment( i ) ).Pos + local a_dist = (ply:GetPos() - a_pos):Length() + + if not sequence then + sequence = seq_ + pos = a_pos + dist = a_dist + else + if a_dist < dist then + sequence = seq_ + pos = a_pos + dist = a_dist + end + end + end + else + for i = 1, table.Count( self.Enterpoints ) do + local a_ = self.Enterpoints[ i ] + + local a_pos = self:GetAttachment( self:LookupAttachment( a_ ) ).Pos + local a_dist = (ply:GetPos() - a_pos):Length() + + if i == 1 then + sequence = a_ + pos = a_pos + dist = a_dist + else + if (a_dist < dist) then + sequence = a_ + pos = a_pos + dist = a_dist + end + end + end + end + + self:PlayAnimation( sequence ) + self:ForceLightsOff() +end + +function ENT:GetMouseSteer() + return 0 +end + +function ENT:Use( ply ) + if IsValid(ply:GetNetVar('dragging')) then + local target = ply:GetNetVar('dragging') + target.isForce = true + self:SetPassenger(target, true) + return + end + self:SetPassenger( ply ) +end + +function ENT:CanDrive( ply ) + local owner = self:CPPIGetOwner() + return not (ply:IsHandcuffed() or ply:getJobTable().notHuman) and (ply == owner or self.lockpicked or (IsValid(owner) and owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true))) or ply:Team() == TEAM_ADMIN or false +end + +function ENT:FindAvailableSeat(ply) + if not IsValid(self:GetDriver()) and not ply:KeyDown(IN_SPEED) and self:CanDrive(ply) and IsValid(self.DriverSeat) then + return self.DriverSeat + end + if not self.PassengerSeats then return end + local closestSeat = self:GetClosestSeat(ply) + if not IsValid(closestSeat) or IsValid(closestSeat:GetDriver()) then + for i = 1, #self.pSeat do + if IsValid(self.pSeat[i]) and not IsValid(self.pSeat[i]:GetDriver()) then + return self.pSeat[i] + end + end + else return closestSeat end +end + +function ENT:SetPassenger( ply, instant ) + if not IsValid(ply) then return end + + if self:GetIsLocked() or self:HasPassengerEnemyTeam( ply ) then + self:EmitSound( "doors/latchlocked2.wav" ) + return + end + + if ply:InVehicle() or ply.entering then return end + + local owner = self:CPPIGetOwner() + local seat = self:FindAvailableSeat(ply) + if not IsValid(seat) then return end + + ply.entering = true + ply:DelayedAction('enterCar', L.enter_car, { + time = instant and 0 or 2, + check = function() + if not IsValid(ply) then return false end + if not IsValid(self) or not isfunction(self.FindAvailableSeat) then return false end + if not IsValid(seat) or IsValid(seat:GetDriver()) or (seat == self.DriverSeat and not self:CanDrive(ply)) then + seat = self:FindAvailableSeat(ply) + if not IsValid(seat) then return false end + end + return octolib.use.check(ply, self) + end, + succ = function() + local driver = seat == self.DriverSeat + self:EnteringSequence(ply) + ply:EnterVehicle(seat) + ply:SetCollisionGroup(COLLISION_GROUP_WEAPON) + netstream.Start(ply, 'simfphys.updateFuelUse', self, self.fuelUse) + + if driver then + ply:SetAllowWeaponsInVehicle(false) + ply:SelectWeapon('dbg_hands') + else ply:SetAllowWeaponsInVehicle(true) end + + ply:SetEyeAngles(Angle(0,90,0)) + + if driver and ply == owner then self.lockpicked = nil end + ply.entering = nil + end, + fail = function() + ply.entering = nil + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + +end +hook.Add('PlayerLeaveVehicle', 'simfphys-delay.fix', function(ply, veh) ply.entering = nil end) + +function ENT:GetClosestSeat( ply ) + local Seat = self.pSeat[1] + if not IsValid(Seat) then return false end + + local Distance = (Seat:GetPos() - ply:GetPos()):LengthSqr() + + for i = 1, table.Count( self.pSeat ) do + local Dist = (self.pSeat[i]:GetPos() - ply:GetPos()):LengthSqr() + if (Dist < Distance) then + Seat = self.pSeat[i] + end + end + + return Seat +end + +function ENT:HasPassengerEnemyTeam( ply ) + if not GetConVar( "sv_simfphys_teampassenger" ):GetBool() then return false end + + if not IsValid( ply ) then return true end + + local myteam = ply:Team() + if IsValid( self:GetDriver() ) then + if self:GetDriver():Team() ~= myteam then + return true + end + end + + if self.PassengerSeats then + for i = 1, table.Count( self.pSeat ) do + if IsValid(self.pSeat[i]) then + + local Passenger = self.pSeat[i]:GetDriver() + if IsValid( Passenger ) then + if Passenger:Team() ~= myteam then + return true + end + end + end + end + end + + return false +end + +function ENT:SetPhysics( enable ) + if enable then + if not self.PhysicsEnabled then + for i = 1, table.Count( self.Wheels ) do + local Wheel = self.Wheels[i] + if IsValid(Wheel) then + Wheel:GetPhysicsObject():SetMaterial("jeeptire") + end + end + self.PhysicsEnabled = true + end + else + if self.PhysicsEnabled ~= false then + for i = 1, table.Count( self.Wheels ) do + local Wheel = self.Wheels[i] + if IsValid(Wheel) then + Wheel:GetPhysicsObject():SetMaterial("friction_00") + end + end + self.PhysicsEnabled = false + end + end +end + +function ENT:SetSuspension( index , bIsDamaged ) + local bIsDamaged = bIsDamaged or false + + local h_mod = index <= 2 and self:GetFrontSuspensionHeight() or self:GetRearSuspensionHeight() + + local heights = { + [1] = self.FrontHeight + self.VehicleData.suspensiontravel_fl * -h_mod, + [2] = self.FrontHeight + self.VehicleData.suspensiontravel_fr * -h_mod, + [3] = self.RearHeight + self.VehicleData.suspensiontravel_rl * -h_mod, + [4] = self.RearHeight + self.VehicleData.suspensiontravel_rr * -h_mod, + [5] = self.RearHeight + self.VehicleData.suspensiontravel_rl * -h_mod, + [6] = self.RearHeight + self.VehicleData.suspensiontravel_rr * -h_mod + } + local Wheel = self.Wheels[index] + if not IsValid(Wheel) then return end + + local subRadius = bIsDamaged and Wheel.dRadius or 0 + + local newheight = heights[index] + subRadius + + local Elastic = self.Elastics[index] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", newheight ) + end + + if self.StrengthenSuspension == true then + local Elastic2 = self.Elastics[index * 10] + if IsValid(Elastic2) then + Elastic2:Fire( "SetSpringLength", newheight ) + end + end +end + +function ENT:OnFrontSuspensionHeightChanged( name, old, new ) + if old == new then return end + if not self.CustomWheels and new > 0 then new = 0 end + if not self:IsInitialized() then return end + + if IsValid(self.Wheels[1]) then + local Elastic = self.Elastics[1] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", self.FrontHeight + self.VehicleData.suspensiontravel_fl * -new ) + end + + if self.StrengthenSuspension == true then + + local Elastic2 = self.Elastics[10] + + if IsValid(Elastic2) then + Elastic2:Fire( "SetSpringLength", self.FrontHeight + self.VehicleData.suspensiontravel_fl * -new ) + end + end + end + + if IsValid(self.Wheels[2]) then + local Elastic = self.Elastics[2] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", self.FrontHeight + self.VehicleData.suspensiontravel_fr * -new ) + end + + if self.StrengthenSuspension == true then + + local Elastic2 = self.Elastics[20] + + if (IsValid(Elastic2)) then + Elastic2:Fire( "SetSpringLength", self.FrontHeight + self.VehicleData.suspensiontravel_fr * -new ) + end + end + end +end + +function ENT:OnRearSuspensionHeightChanged( name, old, new ) + if old == new then return end + if not self.CustomWheels and new > 0 then new = 0 end + if not self:IsInitialized() then return end + + if IsValid(self.Wheels[3]) then + local Elastic = self.Elastics[3] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rl * -new ) + end + + if self.StrengthenSuspension == true then + + local Elastic2 = self.Elastics[30] + + if IsValid(Elastic2) then + Elastic2:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rl * -new ) + end + end + end + + if IsValid(self.Wheels[4]) then + local Elastic = self.Elastics[4] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rr * -new ) + end + + if self.StrengthenSuspension == true then + + local Elastic2 = self.Elastics[40] + + if IsValid(Elastic2) then + Elastic2:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rr * -new ) + end + end + end + + if IsValid(self.Wheels[5]) then + local Elastic = self.Elastics[5] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rl * -new ) + end + + if self.StrengthenSuspension == true then + + local Elastic2 = self.Elastics[50] + + if IsValid(Elastic2) then + Elastic2:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rl * -new ) + end + end + end + + if IsValid(self.Wheels[6]) then + local Elastic = self.Elastics[6] + if IsValid(Elastic) then + Elastic:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rr * -new ) + end + + if self.StrengthenSuspension == true then + + local Elastic2 = self.Elastics[60] + + if IsValid(Elastic2) then + Elastic2:Fire( "SetSpringLength", self.RearHeight + self.VehicleData.suspensiontravel_rr * -new ) + end + end + end +end + +function ENT:OnTurboCharged( name, old, new ) + if old == new then return end + local Active = self:GetActive() + + if new == true and Active then + self.Turbo:Stop() + self.Turbo = CreateSound(self, self.snd_spool or "simulated_vehicles/turbo_spin.wav") + self.Turbo:PlayEx(0,0) + + elseif new == false then + if self.Turbo then + self.Turbo:Stop() + end + end +end + +function ENT:OnSuperCharged( name, old, new ) + if old == new then return end + local Active = self:GetActive() + + if new == true and Active then + self.Blower:Stop() + self.BlowerWhine:Stop() + + self.Blower = CreateSound(self, self.snd_bloweroff or "simulated_vehicles/blower_spin.wav") + self.BlowerWhine = CreateSound(self, self.snd_bloweron or "simulated_vehicles/blower_gearwhine.wav") + + self.Blower:PlayEx(0,0) + self.BlowerWhine:PlayEx(0,0) + elseif new == false then + if self.Blower then + self.Blower:Stop() + end + if self.BlowerWhine then + self.BlowerWhine:Stop() + end + end +end + +function ENT:OnRemove() + if self.Wheels then + for i = 1, table.Count( self.Wheels ) do + local Ent = self.Wheels[ i ] + if IsValid(Ent) then + Ent:Remove() + end + end + end + if self.keys then + for i = 1, table.Count( self.keys ) do + numpad.Remove( self.keys[i] ) + end + end + if self.Turbo then + self.Turbo:Stop() + end + if self.Blower then + self.Blower:Stop() + end + if self.BlowerWhine then + self.BlowerWhine:Stop() + end + if self.horn then + self.horn:Stop() + end + if self.ems then + self.ems:Stop() + end + + self:OnDelete() +end + +function ENT:PlayPP( On ) + if not self.LightsPP then return end + self.poseon = On and self.LightsPP.max or self.LightsPP.min +end + +function ENT:DamageLoop() + if not self:OnFire() then return end + + local CurHealth = self:GetCurHealth() + + if CurHealth <= 0 then return end + + if self:GetMaxHealth() > 30 then + if CurHealth > 30 then + self:TakeDamage(1, Entity(0), Entity(0) ) + elseif CurHealth < 30 then + self:SetCurHealth( CurHealth + 1 ) + end + end + + if self.inv and math.random(10) == 1 then + self.inv:PerformRandom({ maxVolume = 1, maxItems = 100 }, function(item, amount) + local nostack = item:GetData('nostack') + if nostack then + item:Remove() + else + item:Remove(amount) + end + + hook.Run('dbg-cars.itemBurned', self, item, nostack and 1 or amount) + return amount + end) + end + + timer.Simple( 0.15, function() + if IsValid( self ) then + self:DamageLoop() + end + end) +end + +function ENT:SetOnFire( bOn ) + if bOn == self:OnFire() then return end + self:SetNetVar( "OnFire", bOn ) + + if bOn then + self:DamagedStall() + self:DamageLoop() + end +end + +function ENT:SetOnSmoke( bOn ) + if bOn == self:OnSmoke() then return end + self:SetNetVar( "OnSmoke", bOn ) + + if bOn then + self:DamagedStall() + end +end + +function ENT:SetMaxHealth( nHealth ) + self:SetNetVar( "MaxHealth", nHealth ) +end + +function ENT:SetCurHealth( nHealth ) + self:SetNetVar( "Health", nHealth ) +end + +function ENT:SetMaxFuel( nFuel ) + self:SetNetVar( "MaxFuel", nFuel ) +end + +function ENT:SetFuel( nFuel ) + self.curFuel = nFuel +end + +function ENT:SetFuelUse( nFuel ) + self.fuelUse = nFuel + local passengers = {} + for _, v in ipairs(self:GetChildren()) do + if v:GetClass() == 'prop_vehicle_prisoner_pod' and IsValid(v:GetDriver()) then + passengers[#passengers + 1] = v:GetDriver() + end + end + netstream.Start(passengers, 'simfphys.updateFuelUse', self, nFuel) +end + +function ENT:SetFuelType( fueltype ) + self:SetNetVar( "FuelType", fueltype ) +end + +function ENT:SetFuelPos( vPos ) + self:SetFuelPortPosition( vPos ) +end + +function ENT:TurnSignal(mode) + + net.Start("simfphys_turnsignal") + net.WriteEntity(self) + net.WriteInt(mode, 32) + net.Broadcast() + + self.turnMode = mode + + if mode == 0 or mode == 1 then return end + timer.Create('simfphys_turn' .. self:EntIndex(), 10, 1, function() + if not IsValid(self) then return end + if self.turnMode == mode then self:TurnSignal(0) end + end) + +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/numpads.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/numpads.lua new file mode 100644 index 0000000..df0d5a8 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/numpads.lua @@ -0,0 +1,354 @@ +local function numpadFunction(key, pre, post) + return function(sid, ent, keydown) + local ply = player.GetBySteamID(sid) + if not IsValid(ply) or not IsValid(ent) or ply.inFlyEditor then return false end + if isfunction(pre) then + keydown = pre(ply, ent, keydown) + end + if ent.PressedKeys then + ent.PressedKeys[key] = keydown + end + if isfunction(post) then post(ply, ent, keydown) end + end +end + +numpad.Register('k_forward', numpadFunction('W')) +numpad.Register('k_reverse', numpadFunction('S', nil, function(ply, ent, keydown) + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'rfoot', keydown and -1 or 0) +end)) +numpad.Register('k_left', numpadFunction('A')) +numpad.Register('k_right', numpadFunction('D')) +numpad.Register('k_a_forward', numpadFunction('aW')) +numpad.Register('k_a_reverse', numpadFunction('aS')) +numpad.Register('k_a_left', numpadFunction('aA')) +numpad.Register('k_a_right', numpadFunction('aD')) +numpad.Register('k_gup', numpadFunction('M1', function(ply, ent, keydown) + return keydown and not ply.blockcontrols +end)) +numpad.Register('k_gdn', numpadFunction('M2', function(ply, ent, keydown) + return keydown and not ply.blockcontrols +end)) +numpad.Register('k_wot', numpadFunction('Shift')) +numpad.Register('k_clutch', numpadFunction('Alt', nil, function(ply, ent, keydown) + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'lfoot', keydown) +end)) +numpad.Register('k_hbrk', numpadFunction('Space', nil, function(ply, ent, keydown) + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'brake', keydown) + + if not keydown or ply:GetInfoNum('cl_octolib_sticky_handbrake', 1) < 1 then return end + + local ct = CurTime() + if ct - (ply.lastHandbrakeDown or 0) < 0.2 then + ent:ToggleHandbrake() + end + ply.lastHandbrakeDown = ct +end)) +numpad.Register('k_hbrk_t', function(sid, ent, keydown) + if not IsValid(player.GetBySteamID(sid)) or not IsValid(ent) or not keydown then return false end + ent:ToggleHandbrake() +end ) + +-- numpad.Register('k_cc', function(sid, ent, keydown) +-- if not IsValid(player.GetBySteamID(sid)) or not IsValid(ent) then return false end + +-- if keydown then +-- ent:ToggleHandbrake()ply +-- end +-- end ) + +netstream.Hook('dbg-cars.belt', function(ply) + + local seat = ply:GetVehicle() + local car = IsValid(seat) and seat:GetParent() + if ply.belting or not IsValid(car) or car:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + + ply.belting = true + if not ply:GetNetVar('belted') then + ply:DoAnimation(ACT_SIGNAL_HALT) + ply:DoEmote(L.belt_hint) + ply:DelayedAction('belt', L.belting_hint, { + time = 1.5, + check = function() return IsValid(ply) and ply:Alive() and ply:GetVehicle() == seat end, + succ = function() + ply:SetLocalVar('belted', true) + ply.belting = nil + end, + fail = function() + ply.belting = nil + end, + }) + else + ply:DoAnimation(ACT_SIGNAL_HALT) + ply:DoEmote(L.unbelt_hint) + ply:DelayedAction('belt', L.unbelting_hint, { + time = 1.5, + check = function() return IsValid(ply) and ply:Alive() and ply:GetVehicle() == seat end, + succ = function() + ply:SetLocalVar('belted', nil) + ply.belting = nil + end, + fail = function() + ply.belting = nil + end, + }) + end + +end) + +hook.Add('PlayerLeaveVehicle', 'dbg-cars.unbelt', function(ply, veh) ply.belting = nil ply:SetLocalVar('belted', nil) end) +hook.Add('CanExitVehicle', 'dbg-cars.unbelt', function(veh, ply) + + local belted = tobool(ply:GetNetVar('belted')) + if ply.belting or belted then + if tobool(ply.belting) ~= belted and ply:TriggerCooldown('numpads.beltWarning', 3) then + ply:Notify('warning', 'Нужно отстегнуть ремень') + end + if ply.belting and belted and ply:TriggerCooldown('numpads.unbeltWarning', 3) then + ply:Notify('warning', 'Нужно подождать, пока ремень отстегивается') + end + return false + end + +end) + +numpad.Register('k_hrn', function(sid, ent, keydown) + if not IsValid(player.GetBySteamID(sid)) or not IsValid(ent) then return false end + + if keydown then + ent.horn = CreateSound(ent, ent.snd_horn or 'simulated_vehicles/horn_1.wav') + if ent.siren and ent.ems then ent.ems:Stop() end + ent.horn:Play() + elseif ent.horn then + ent.horn:Stop() + if ent.siren and ent.ems then ent.ems:PlayEx(0.4, 100) end + end + +end) + +numpad.Register('k_eng', function(sid, ent, keydown) + local ply = player.GetBySteamID(sid) + if not IsValid(ply) or not IsValid(ent) or ply.inFlyEditor then return false end + + if keydown then + if ent:EngineActive() then + ent:StopEngine() + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'engine', true) + timer.Simple(0.3, function() + if not IsValid(ply) then return end + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'engine', false) + end) + else + if ply.starting then return end + ply.starting = true + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'engine', true) + ply:DelayedAction('engine', 'Запуск двигателя', { + time = 1, + check = function() return IsValid(ent) and IsValid(ply) and ply:Alive() and ent:GetDriver() == ply end, + succ = function() + ply.starting = nil + ent:StartEngine(true) + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'engine', false) + end, + fail = function() ply.starting = nil end, + }) + end + end +end) + +numpad.Register('k_lock', function( sid, ent, keydown ) + local ply = player.GetBySteamID(sid) + if not IsValid(ply) or not IsValid(ent) or ply.inFlyEditor then return false end + + if keydown then + if ent:GetIsLocked() then + ent:UnLock() + else + if ply:KeyDown(IN_WALK) and istable(ent.pSeat) then + if ent:GetVelocity():LengthSqr() > 100 then + return ply:Notify('Нельзя высаживать пассажиров на ходу') + end + for _,v in ipairs(ent.pSeat) do + if not IsValid(v) then continue end + local driver = v:GetDriver() + if IsValid(driver) then + driver:ExitVehicle() + end + end + end + ent:Lock() + end + + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'engine', true) + timer.Simple(0.3, function() + if not IsValid(ply) then return end + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'engine', false) + end) + end +end ) + +numpad.Register('k_turnmenu', function(sid, ent, keydown) + local ply = player.GetBySteamID(sid) + if not IsValid(ply) or not IsValid(ent) then return false end + ent.turnmenuOpened = keydown or nil + if not keydown then netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'lhandle') end +end) + +numpad.Register('k_flgts', function( sid, ent, keydown ) + if not IsValid(player.GetBySteamID(sid)) or not IsValid(ent) or ent.preventEms then return false end + + local v_list = list.Get('simfphys_lights')[ent.LightsTable] or false + if not (v_list and v_list.ems_sprites) then return end + + local Time = CurTime() + + if keydown then + ent.KeyPressedTime = Time + else + if (Time - ent.KeyPressedTime) < 0.15 and not ent.emson then + ent.emson = true + if not ent:GetEMSEnabled() then + ent:EmitSound('items/flashlight1.wav') + end + end + + if (Time - ent.KeyPressedTime) >= 0.22 then + if ent.emson then + ent.emson = false + if ent:GetEMSEnabled() then + ent:EmitSound('buttons/lightswitch2.wav') + end + end + end + ent:SetEMSEnabled(ent.emson) + end + +end) + +numpad.Register('k_siren', function( sid, ent, keydown ) + if not IsValid(player.GetBySteamID(sid)) or not IsValid(ent) or ent.preventEms then return false end + + local v_list = list.Get('simfphys_lights')[ent.LightsTable] or false + if not (v_list and v_list.ems_sounds) then return end + + local ply, Time = player.GetBySteamID(sid), CurTime() + + if keydown then + if ply:KeyDown(IN_DUCK) and not ent.siren then + ent.ems = CreateSound(ent, v_list.ems_sounds[1]) + ent.ems:PlayEx(0.4, 100) + end + ent.KeyPressedTime = Time + else + + if ent.ems then + ent.ems:Stop() + end + + if (Time - ent.KeyPressedTime) < 0.15 and not ent.siren and not ply:KeyDown(IN_DUCK) then + ent.siren = true + ent.cursound = 0 + end + + if (Time - ent.KeyPressedTime) >= 0.22 then + if ent.siren then + ent.siren = false + end + else + if ent.siren then + local sounds = v_list.ems_sounds + local numsounds = table.Count( sounds ) + + if numsounds <= 1 and ent.ems then + ent.siren = false + ent.ems = nil + return + end + + ent.cursound = ent.cursound + 1 + if ent.cursound > table.Count( sounds ) then + ent.cursound = 1 + end + + ent.ems = CreateSound(ent, sounds[ent.cursound]) + ent.ems:PlayEx(0.4, 100) + end + end + end + +end) + +numpad.Register('k_spcl', function( sid, ent, keydown ) + if not IsValid(player.GetBySteamID(sid)) or not IsValid(ent) or not ent.HasSpecial then return false end + + local controller = ent.TowController + if not IsValid(controller) then return end + + local tName = 'towSync' .. ent:EntIndex() + if keydown then + controller.nextDir = controller.nextDir == 1 and -1 or 1 + controller:SetDirection(controller.nextDir) + timer.Create(tName, 0.2, 0, function() + if not IsValid(ent) then return timer.Remove(tName) end + if controller.current_length == controller.max_length or controller.current_length == controller.min_length then + ent:SetNetVar('hookDirection', 0) + timer.Remove(tName) + end + end) + else + controller:SetDirection(0) + timer.Remove(tName) + end + + ent:SetNetVar('hookDirection', controller:GetDirection()) +end) + +numpad.Register('k_lgts', function( sid, ent, keydown ) + local ply = player.GetBySteamID(sid) + if not IsValid(ply) or not IsValid(ent) or not ent.LightsTable then return false end + if keydown then + ent.LightsPressStart = CurTime() + else + local vehiclelist = list.Get('simfphys_lights')[ent.LightsTable] or false + if not vehiclelist then return end + + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'rhandle') + + if CurTime() - (ent.LightsPressStart or 0) > 0.3 then + if ent.LightsActivated then + ent.LightsActivated = false + ent:SetLightsEnabled(false) + ent.LampsActivated = false + ent:SetLampsEnabled(false) + ent:EmitSound('items/flashlight1.wav') + ent:PlayPP(false) + + if vehiclelist.BodyGroups then + ent:SetBodygroup(vehiclelist.BodyGroups.Off[1], vehiclelist.BodyGroups.Off[2] ) + end + if vehiclelist.Animation then + ent:PlayAnimation( vehiclelist.Animation.Off ) + end + end + + return + end + + if ent.LightsActivated then + local lamps = not ent.LampsActivated + ent.LampsActivated = lamps + ent:SetLampsEnabled(lamps) + end + + ent.LightsActivated = true + ent:SetLightsEnabled(true) + ent:EmitSound('buttons/lightswitch2.wav') + + if vehiclelist.BodyGroups then + ent:SetBodygroup(vehiclelist.BodyGroups.On[1], vehiclelist.BodyGroups.On[2] ) + end + if vehiclelist.Animation then + ent:PlayAnimation( vehiclelist.Animation.On ) + end + ent:PlayPP(true) + end +end) diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/shared.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/shared.lua new file mode 100644 index 0000000..90bf2f1 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/shared.lua @@ -0,0 +1,142 @@ +ENT.Type = "anim" + +ENT.PrintName = "Comedy Effect" +ENT.Author = "Blu" +ENT.Information = "" +ENT.Category = "Fun + Games" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +ENT.AutomaticFrameAdvance = true +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.Editable = (GetConVar("sv_simfphys_devmode"):GetInt() or 1) >= 1 +ENT.IsSimfphyscar = true + +function ENT:SetupDataTables() + self:NetworkVar( "Float",1, "SteerSpeed", { KeyName = "steerspeed", Edit = { type = "Float", order = 1,min = 1, max = 16, category = "Steering"} } ) + self:NetworkVar( "Float",2, "FastSteerConeFadeSpeed", { KeyName = "faststeerconefadespeed", Edit = { type = "Float", order = 2,min = 1, max = 5000, category = "Steering"} } ) + self:NetworkVar( "Float",3, "FastSteerAngle", { KeyName = "faststeerangle", Edit = { type = "Float", order = 3,min = 0, max = 1, category = "Steering"} } ) + + self:NetworkVar( "Float",4, "FrontSuspensionHeight", { KeyName = "frontsuspensionheight", Edit = { type = "Float", order = 4,min = -1, max = 1, category = "Suspension" } } ) + self:NetworkVar( "Float",5, "RearSuspensionHeight", { KeyName = "rearsuspensionheight", Edit = { type = "Float", order = 5,min = -1, max = 1, category = "Suspension" } } ) + + self:NetworkVar( "Int",0, "EngineSoundPreset", { KeyName = "enginesoundpreset", Edit = { type = "Int", order = 6,min = -1, max = 14, category = "Engine"} } ) + self:NetworkVar( "Int",1, "IdleRPM", { KeyName = "idlerpm", Edit = { type = "Int", order = 7,min = 1, max = 25000, category = "Engine"} } ) + self:NetworkVar( "Int",2, "LimitRPM", { KeyName = "limitrpm", Edit = { type = "Int", order = 8,min = 4, max = 25000, category = "Engine"} } ) + self:NetworkVar( "Int",3, "PowerBandStart", { KeyName = "powerbandstart", Edit = { type = "Int", order = 9,min = 2, max = 25000, category = "Engine"} } ) + self:NetworkVar( "Int",4, "PowerBandEnd", { KeyName = "powerbandend", Edit = { type = "Int", order = 10,min = 3, max = 25000, category = "Engine"} } ) + self:NetworkVar( "Float",6, "MaxTorque", { KeyName = "maxtorque", Edit = { type = "Float", order = 11,min = 20, max = 1000, category = "Engine"} } ) + self:NetworkVar( "Bool",10, "Revlimiter", { KeyName = "revlimiter", Edit = { type = "Boolean", order = 12, category = "Engine"} } ) + self:NetworkVar( "Bool",1, "TurboCharged", { KeyName = "turbocharged", Edit = { type = "Boolean", order = 13, category = "Engine"} } ) + self:NetworkVar( "Bool",4, "SuperCharged", { KeyName = "supercharged", Edit = { type = "Boolean", order = 14, category = "Engine"} } ) + self:NetworkVar( "Bool",14, "BackFire", { KeyName = "backfire", Edit = { type = "Boolean", order = 15, category = "Engine"} } ) + self:NetworkVar( "Bool",15, "DoNotStall", { KeyName = "donotstall", Edit = { type = "Boolean", order = 16, category = "Engine"} } ) + + self:NetworkVar( "Float",7, "DifferentialGear", { KeyName = "differentialgear", Edit = { type = "Float", order = 17,min = 0.2, max = 6, category = "Transmission"} } ) + + self:NetworkVar( "Float",8, "BrakePower", { KeyName = "brakepower", Edit = { type = "Float", order = 18,min = 0.1, max = 500, category = "Wheels"} } ) + self:NetworkVar( "Float",9, "PowerDistribution", { KeyName = "powerdistribution", Edit = { type = "Float", order = 19,min = -1, max = 1, category = "Wheels"} } ) + self:NetworkVar( "Float",10, "Efficiency", { KeyName = "efficiency", Edit = { type = "Float", order = 20,min = 0.2, max = 4, category = "Wheels"} } ) + self:NetworkVar( "Float",11, "MaxTraction", { KeyName = "maxtraction", Edit = { type = "Float", order = 21,min = 5, max = 1000, category = "Wheels"} } ) + self:NetworkVar( "Float",12, "TractionBias", { KeyName = "tractionbias", Edit = { type = "Float", order = 22,min = -0.99, max = 0.99, category = "Wheels"} } ) + self:NetworkVar( "Bool",17, "BulletProofTires", { KeyName = "bulletprooftires", Edit = { type = "Boolean", order = 23, category = "Wheels"} } ) + self:NetworkVar( "Vector",0, "TireSmokeColor", { KeyName = "tiresmokecolor", Edit = { type = "VectorColor", order = 24, category = "Wheels"} } ) + + self:NetworkVar( "Float",13, "FlyWheelRPM" ) + self:NetworkVar( "Float",14, "Throttle" ) + self:NetworkVar( "Int",5, "Gear" ) + self:NetworkVar( "Int",6, "Clutch" ) + self:NetworkVar( "Bool",5, "IsCruiseModeOn" ) + self:NetworkVar( "Bool",7, "IsBraking" ) + self:NetworkVar( "Bool",8, "LightsEnabled" ) + self:NetworkVar( "Bool",9, "LampsEnabled" ) + self:NetworkVar( "Bool",12, "EMSEnabled" ) + self:NetworkVar( "Bool",16, "HandBrakeEnabled" ) + self:NetworkVar( "Bool",18, "IsLocked" ) + self:NetworkVar( "Bool",19, "EngineActive" ) + + self:NetworkVar( "Float",15, "VehicleSteer" ) + self:NetworkVar( "Entity",0, "Driver" ) + self:NetworkVar( "Entity",1, "DriverSeat" ) + self:NetworkVar( "Bool",3, "Active" ) + + self:NetworkVar( "String",1, "Spawn_List") + self:NetworkVar( "String",2, "Lights_List") + -- self:NetworkVar( "String",3, "Soundoverride") + + self:NetworkVar( "Vector",1, "FuelPortPosition" ) + + if SERVER then + self:NetworkVarNotify( "FrontSuspensionHeight", self.OnFrontSuspensionHeightChanged ) + self:NetworkVarNotify( "RearSuspensionHeight", self.OnRearSuspensionHeightChanged ) + self:NetworkVarNotify( "TurboCharged", self.OnTurboCharged ) + self:NetworkVarNotify( "SuperCharged", self.OnSuperCharged ) + self:NetworkVarNotify( "Active", self.OnActiveChanged ) + self:NetworkVarNotify( "Throttle", self.OnThrottleChanged ) + end +end + +function ENT:IsSimfphyscar() + return true +end + +local VehicleMeta = FindMetaTable("Entity") +local OldIsVehicle = VehicleMeta.IsVehicle +function VehicleMeta:IsVehicle() + return (self.IsSimfphyscar ~= nil and self:IsSimfphyscar()) or OldIsVehicle(self) +end + +function ENT:GetCurHealth() + return self:GetNetVar( "Health", self:GetMaxHealth() ) +end + +function ENT:GetMaxHealth() + return self:GetNetVar( "MaxHealth", 2000 ) +end + +function ENT:GetMaxFuel() + return self:GetNetVar( "MaxFuel", 60 ) +end + +function ENT:GetFuel() + return self.curFuel or self:GetNetVar( "Fuel", self:GetMaxFuel() ) +end + +function ENT:GetFuelUse() + return self.fuelUse or 0 +end + +function ENT:GetFuelType() + return self:GetNetVar( "FuelType", 1 ) +end + +function ENT:GetFuelPos() + return self:LocalToWorld( self:GetFuelPortPosition() ) +end + +function ENT:OnSmoke() + return self:GetNetVar( "OnSmoke", false ) +end + +function ENT:OnFire() + return self:GetNetVar( "OnFire", false ) +end + +function ENT:GetBackfireSound() + return self:GetNetVar( "backfiresound", '' ) +end + +function ENT:SetBackfireSound( the_sound ) + self:SetNetVar( "backfiresound", the_sound ) +end + +function ENT:BodyGroupIsValid( bodygroups ) + for index, groups in pairs( bodygroups ) do + local mygroup = self:GetBodygroup( index ) + for g_index = 1, table.Count( groups ) do + if mygroup == groups[g_index] then return true end + end + end + return false +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/simfunc.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/simfunc.lua new file mode 100644 index 0000000..2dbb30a --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/simfunc.lua @@ -0,0 +1,400 @@ +function ENT:ToggleHandbrake() + self.k_togglehandbrake = not self.k_togglehandbrake +end + +function ENT:WheelOnGround() + self.FrontWheelPowered = self:GetPowerDistribution() ~= 1 + self.RearWheelPowered = self:GetPowerDistribution() ~= -1 + + local dir = -self.Up + local lenMul = math.Clamp(-self.Vel.z / 50,2.5,6) + for i = 1, #self.Wheels do + local Wheel = self.Wheels[i] + if IsValid(Wheel) then + local dmgMul = Wheel:GetDamaged() and 0.5 or 1 + local surfacemul = simfphys.TractionData[Wheel:GetSurfaceMaterial():lower()] + + self.VehicleData[ "SurfaceMul_" .. i ] = (surfacemul and math.max(surfacemul,0.001) or 1) * dmgMul + + local WheelPos = self:LogicWheelPos( i ) + + local WheelRadius = WheelPos.IsFrontWheel and self.FrontWheelRadius or self.RearWheelRadius + local startpos = Wheel:GetPos() + local len = WheelRadius + lenMul + local HullSize = Vector(WheelRadius,WheelRadius,0) + local tr = util.TraceHull( { + start = startpos, + endpos = startpos + dir * len, + maxs = HullSize, + mins = -HullSize, + filter = self.VehicleData["filter"] + } ) + + if tr.Hit then + self.VehicleData[ "onGround_" .. i ] = 1 + Wheel:SetSpeed( Wheel.FX ) + Wheel:SetSkidSound( Wheel.skid ) + Wheel:SetSurfaceMaterial( util.GetSurfacePropName( tr.SurfaceProps ) ) + Wheel:SetOnGround(1) + else + self.VehicleData[ "onGround_" .. i ] = 0 + Wheel:SetOnGround(0) + end + end + end + + local FrontOnGround = math.max(self.VehicleData[ "onGround_1" ],self.VehicleData[ "onGround_2" ]) + local RearOnGround = math.max(self.VehicleData[ "onGround_3" ],self.VehicleData[ "onGround_4" ],self.VehicleData[ "onGround_5" ],self.VehicleData[ "onGround_6" ]) + + self.DriveWheelsOnGround = math.max(self.FrontWheelPowered and FrontOnGround or 0,self.RearWheelPowered and RearOnGround or 0) +end + +function ENT:SimulateEngine(IdleRPM,LimitRPM,Powerbandstart,Powerbandend,c_time) + local PObj = self:GetPhysicsObject() + + local IsRunning = self:EngineActive() + local Throttle = self:GetThrottle() + + -- if not self:IsDriveWheelsOnGround() then + -- self.Clutch = 1 + -- end + + if self.Gears[self.CurrentGear] == 0 then + self.GearRatio = 1 + self.Clutch = 1 + -- self.HandBrake = self.HandBrake + (self.HandBrakePower - self.HandBrake) * 0.2 + else + self.GearRatio = self.Gears[self.CurrentGear] * self:GetDiffGear() + end + + self:SetClutch( self.Clutch ) + local InvClutch = 1 - self.Clutch + + local GearedRPM = self.WheelRPM / math.abs(self.GearRatio) + + local MaxTorque = self:GetMaxTorque() + + local DesRPM = Lerp(InvClutch, math.max(IdleRPM + (LimitRPM - IdleRPM) * Throttle,0), GearedRPM ) + + local TurboCharged = self:GetTurboCharged() + local SuperCharged = self:GetSuperCharged() + local boost = (TurboCharged and self:SimulateTurbo(Powerbandend) or 0) * 0.4 + (SuperCharged and self:SimulateBlower(Powerbandend) or 0) + + if self:GetCurHealth() <= self:GetMaxHealth() * 0.4 then + MaxTorque = MaxTorque * (self:GetCurHealth() / (self:GetMaxHealth() * 0.4)) + end + + self.EngineRPM = self:EngineActive() and (math.Clamp(self.EngineRPM + math.Clamp(DesRPM - self.EngineRPM,-math.max(self.EngineRPM / 15, 1 ),math.max(-self.RpmDiff / 1.5 * InvClutch + (self.Torque * 5) / 0.15 * self.Clutch, 1)) + self.RPM_DIFFERENCE * Throttle,0,LimitRPM)) or 0 + self.Torque = (Throttle + boost) * math.max(MaxTorque * math.min(self.EngineRPM / Powerbandstart, (LimitRPM - self.EngineRPM) / (LimitRPM - Powerbandend),1), 0) + self:SetFlyWheelRPM( math.min(self.EngineRPM + self.exprpmdiff * 2 * InvClutch,LimitRPM) ) + + self.RpmDiff = self.EngineRPM - GearedRPM + + local signGearRatio = ((self.GearRatio > 0) and 1 or 0) + ((self.GearRatio < 0) and -1 or 0) + local signThrottle = (Throttle > 0) and 1 or 0 + local signSpeed = ((self.ForwardSpeed > 0) and 1 or 0) + ((self.ForwardSpeed < 0) and -1 or 0) + + local TorqueDiff = (self.RpmDiff / LimitRPM) * 0.15 * self.Torque + local EngineBrake = (signThrottle == 0) and self.EngineRPM * (self.EngineRPM / LimitRPM) ^ 2 / 300 * signSpeed or 0 + + local GearedPower = ((self.ThrottleDelay <= c_time and (self.Torque + TorqueDiff) * signThrottle * signGearRatio or 0) - EngineBrake) / math.abs(self.GearRatio) / 50 + + self.EngineTorque = IsRunning and GearedPower * InvClutch or 0 + + if not self:GetDoNotStall() and IsRunning and self.EngineRPM <= IdleRPM * 0.2 then + -- self.CurrentGear = 2 + self:StallAndRestart() + end + + if IsRunning then + local FuelUse = (Throttle * 0.6 + 0.4) * ((self.EngineRPM / LimitRPM) * MaxTorque * 20 + self.Torque * 10) / 5000000 + local Fuel = self:GetFuel() + self:SetFuel( Fuel - FuelUse * 5 ) + self.UsedFuel = self.UsedFuel and (self.UsedFuel + FuelUse) or 0 + + if Fuel <= 0 then + self:StopEngine() + end + else + self.UsedFuel = 0 + end + + if CurTime() >= (self.CheckUse or 0) then + self.CheckUse = CurTime() + 1 + local newFuelUse = self.UsedFuel * 60 + if self:GetFuelUse() ~= newFuelUse then + self:SetFuelUse(newFuelUse) + end + self.UsedFuel = 0 + end + + if CurTime() >= (self.nextFuelUpdate or 0) and self:GetNetVar("Fuel") ~= self.curFuel then + self:SetNetVar("Fuel", math.Clamp(self.curFuel, 0, self:GetMaxFuel())) + self.nextFuelUpdate = CurTime() + 10 + end + + local ReactionForce = (self.EngineTorque * 2 - math.Clamp(self.ForwardSpeed,-self.Brake,self.Brake)) * self.DriveWheelsOnGround + local BaseMassCenter = PObj:GetMassCenter() + local dt_mul = math.max( math.min(self:GetPowerDistribution() + 0.5,1),0) + + PObj:ApplyForceOffset( -self.Forward * self.Mass * ReactionForce, BaseMassCenter + self.Up * dt_mul ) + PObj:ApplyForceOffset( self.Forward * self.Mass * ReactionForce, BaseMassCenter - self.Up * dt_mul ) +end + +function ENT:SimulateTransmission(k_throttle,k_brake,k_fullthrottle,k_clutch,k_handbrake,k_gearup,k_geardown,IdleRPM,Powerbandstart,Powerbandend,cruisecontrol,curtime) + + local GearsCount = table.Count( self.Gears ) + local cruiseThrottle = math.min( math.max(self.cc_speed - math.abs(self.ForwardSpeed),0) / 10 ^ 2, 1) + + if isnumber(self.ForceTransmission) then + isauto = self.ForceTransmission <= 1 + end + + self.Brake = self:GetBrakePower() * math.max( k_brake, self.PressedKeys["joystick_brake"] ) + self.HandBrake = self.HandBrakePower * (self.k_togglehandbrake and 1 or k_handbrake) + self.Clutch = math.max( k_clutch, k_handbrake, self.PressedKeys["joystick_clutch"] ) + + local AutoThrottle = self:EngineActive() and ((self.EngineRPM < IdleRPM) and (IdleRPM - self.EngineRPM) / IdleRPM or 0) / 4 or 0 + self.InputThrottle = math.max( (0.5 + 0.5 * k_fullthrottle) * k_throttle, self.PressedKeys["joystick_throttle"] ) + local Throttle = self.InputThrottle + AutoThrottle + self:SetThrottle(Throttle) + + if k_gearup ~= self.GearUpPressed then + self.GearUpPressed = k_gearup + + if k_gearup == 1 and k_clutch == 1 then + if self.CurrentGear ~= GearsCount then + self.ThrottleDelay = curtime + 0.4 - 0.4 * k_clutch + end + + local newGear = math.Clamp(self.CurrentGear + 1,1,GearsCount) + if newGear ~= self.CurrentGear then + local ply = self:GetDriver() + if IsValid(ply) then + ply:EmitSound('car/changegear.wav', 55, 100, 1) + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'gear') + end + self.CurrentGear = newGear + end + end + end + + if k_geardown ~= self.GearDownPressed then + self.GearDownPressed = k_geardown + + if k_geardown == 1 and k_clutch == 1 then + if self.CurrentGear == 1 then + self.ThrottleDelay = curtime + 0.25 + end + + local newGear = math.Clamp(self.CurrentGear - 1,1,GearsCount) + if newGear ~= self.CurrentGear then + local ply = self:GetDriver() + if IsValid(ply) then + ply:EmitSound('car/changegear.wav', 55, 100, 1) + netstream.StartPVS(ply:GetPos(), 'simfphys.anim', ply, 'gear') + end + self.CurrentGear = newGear + end + end + end + + self:SetIsBraking( self.Brake > 0 ) + self:SetGear( self.CurrentGear ) + self:SetHandBrakeEnabled( self.HandBrake > 0 ) + +end + +function ENT:GetTransformedDirection() + local SteerAngForward = self.Forward:Angle() + local SteerAngRight = self.Right:Angle() + local SteerAngForward2 = self.Forward:Angle() + local SteerAngRight2 = self.Right:Angle() + + SteerAngForward:RotateAroundAxis(-self.Up, self.VehicleData[ "Steer" ]) + SteerAngRight:RotateAroundAxis(-self.Up, self.VehicleData[ "Steer" ]) + SteerAngForward2:RotateAroundAxis(-self.Up, -self.VehicleData[ "Steer" ]) + SteerAngRight2:RotateAroundAxis(-self.Up, -self.VehicleData[ "Steer" ]) + + local SteerForward = SteerAngForward:Forward() + local SteerRight = SteerAngRight:Forward() + local SteerForward2 = SteerAngForward2:Forward() + local SteerRight2 = SteerAngRight2:Forward() + + return {Forward = SteerForward,Right = SteerRight,Forward2 = SteerForward2, Right2 = SteerRight2} +end + +function ENT:LogicWheelPos( index ) + local IsFront = index == 1 or index == 2 + local IsRight = index == 2 or index == 4 or index == 6 + + return {IsFrontWheel = IsFront, IsRightWheel = IsRight} +end + +function ENT:SimulateWheels(k_clutch,LimitRPM) + local Steer = self:GetTransformedDirection() + + local MaxGrip = self:GetMaxTraction() + local GripOffset = self:GetTractionBias() * MaxGrip + local Efficiency = self:GetEfficiency() + + for i = 1, table.Count( self.Wheels ) do + local Wheel = self.Wheels[i] + + if IsValid( Wheel ) then + local WheelPos = self:LogicWheelPos( i ) + local WheelRadius = WheelPos.IsFrontWheel and self.FrontWheelRadius or self.RearWheelRadius + local WheelDiameter = WheelRadius * 2 + local SurfaceMultiplicator = self.VehicleData[ "SurfaceMul_" .. i ] + local MaxTraction = ((WheelPos.IsFrontWheel and (MaxGrip + GripOffset) or (MaxGrip - GripOffset)) * SurfaceMultiplicator) * 1.5 + + local IsPoweredWheel = (WheelPos.IsFrontWheel and self.FrontWheelPowered or not WheelPos.IsFrontWheel and self.RearWheelPowered) and 1 or 0 + + local OnGround = self.VehicleData[ "onGround_" .. i ] + + local Forward = WheelPos.IsFrontWheel and Steer.Forward or self.Forward + local Right = WheelPos.IsFrontWheel and Steer.Right or self.Right + + if self.CustomWheels then + if WheelPos.IsFrontWheel then + Forward = IsValid(self.SteerMaster) and Steer.Forward or self.Forward + Right = IsValid(self.SteerMaster) and Steer.Right or self.Right + else + if IsValid( self.SteerMaster2 ) then + Forward = Steer.Forward2 + Right = Steer.Right2 + end + end + end + + local Velocity = Wheel:GetVelocity() + local VelocityLength = Velocity:Length() + local VelForward = Velocity:GetNormalized() + local Stopped = VelocityLength == 0 + + local AngY = Stopped and 0 or math.acos( math.Clamp( Forward:Dot(VelForward) ,-1,1) ) + local AngX = Stopped and 0 or math.asin( math.Clamp( Right:Dot(VelForward) ,-1,1) ) + local VelY = Stopped and 0 or math.cos(AngY) * VelocityLength + local VelX = Stopped and 0 or math.sin(AngX) * VelocityLength + local absFy = math.abs(VelX) + local absFx = math.abs(VelY) + + local PowerBiasMul = WheelPos.IsFrontWheel and (1 - self:GetPowerDistribution()) * 0.5 or (1 + self:GetPowerDistribution()) * 0.5 + + local LockWheel = not WheelPos.IsFrontWheel and self.HandBrake > 0 + local ForwardForce = LockWheel and 0 or (self.EngineTorque * PowerBiasMul * IsPoweredWheel * 2.5) + if (self.PowerBoost and (ForwardForce > 0 and VelY < 175 or ForwardForce < 0 and VelY > -175)) then ForwardForce = ForwardForce * self.PowerBoost end + + local TractionCycle = Vector(math.min(absFy,MaxTraction),ForwardForce,0):Length() + local GripLoss = LockWheel and absFx > 30 and MaxTraction or math.max(TractionCycle - MaxTraction,0) + local GripRemaining = math.max(MaxTraction - GripLoss,math.min(absFy / 25,MaxTraction / 2)) + + local signForwardForce = ((ForwardForce > 0) and 1 or 0) + ((ForwardForce < 0) and -1 or 0) + local signEngineTorque = ((self.EngineTorque > 0) and 1 or 0) + ((self.EngineTorque < 0) and -1 or 0) + local brake = math.max(self.Brake, self.HandBrake) + local BrakeForce = math.Clamp(-VelY,-brake,brake) * SurfaceMultiplicator + + local Power = ForwardForce * Efficiency - GripLoss * signForwardForce + BrakeForce + local Force = -Right * math.Clamp(VelX,-GripRemaining,GripRemaining) + Forward * Power + + local wRad = Wheel:GetDamaged() and Wheel.dRadius or WheelRadius + local TurnWheel = LockWheel and 0 or (((VelY * OnGround + GripLoss * 35 * signEngineTorque * IsPoweredWheel) / wRad * 1.85) + self.EngineRPM / 80 * (1 - OnGround) * IsPoweredWheel * (1 - k_clutch)) * 1.6 + + Wheel.FX = VelY + Wheel.skid = LockWheel and (VelocityLength / 500) or (GripLoss / MaxTraction) + + local RPM = LockWheel and 0 or (absFx / (3.14 * WheelDiameter)) * 52 * OnGround + local GripLossFaktor = math.Clamp(GripLoss,0,MaxTraction) / MaxTraction + + self.VehicleData[ "WheelRPM_".. i ] = RPM + self.VehicleData[ "GripLossFaktor_".. i ] = GripLossFaktor + self.VehicleData[ "Exp_GLF_".. i ] = GripLossFaktor ^ 2 + Wheel:SetGripLoss( GripLossFaktor ) + + if WheelPos.IsFrontWheel then + self.VehicleData[ "spin_" .. i ] = self.VehicleData[ "spin_" .. i ] + TurnWheel + else + if self.HandBrake < MaxTraction then + self.VehicleData[ "spin_" .. i ] = self.VehicleData[ "spin_" .. i ] + TurnWheel + end + end + + if self.CustomWheels then + local GhostEnt = self.GhostWheels[i] + local ang = GhostEnt:GetAngles() + local axle = Wheel:LocalToWorldAngles(Angle(0, 0, (WheelPos.IsRightWheel and 1 or -1) * (self.camber or 0))):Right() + ang:RotateAroundAxis(axle, -TurnWheel) + + self.GhostWheels[i]:SetAngles( ang ) + else + self:SetPoseParameter(self.VehicleData[ "pp_spin_" .. i ],self.VehicleData[ "spin_" .. i ]) + end + + if not self.PhysicsEnabled then + Wheel:GetPhysicsObject():ApplyForceCenter( Force * 185 * OnGround ) + end + end + end + + local target_diff = math.max(LimitRPM * 0.95 - self.EngineRPM,0) + + if self.FrontWheelPowered and self.RearWheelPowered then + self.WheelRPM = math.max(self.VehicleData[ "WheelRPM_1" ] or 0,self.VehicleData[ "WheelRPM_2" ] or 0,self.VehicleData[ "WheelRPM_3" ] or 0,self.VehicleData[ "WheelRPM_4" ] or 0) + self.RPM_DIFFERENCE = target_diff * math.max(self.VehicleData[ "GripLossFaktor_1" ] or 0,self.VehicleData[ "GripLossFaktor_2" ] or 0,self.VehicleData[ "GripLossFaktor_3" ] or 0,self.VehicleData[ "GripLossFaktor_4" ] or 0) + self.exprpmdiff = target_diff * math.max(self.VehicleData[ "Exp_GLF_1" ] or 0,self.VehicleData[ "Exp_GLF_2" ] or 0,self.VehicleData[ "Exp_GLF_3" ] or 0,self.VehicleData[ "Exp_GLF_4" ] or 0) + elseif not self.FrontWheelPowered and self.RearWheelPowered then + self.WheelRPM = math.max(self.VehicleData[ "WheelRPM_3" ] or 0,self.VehicleData[ "WheelRPM_4" ] or 0) + self.RPM_DIFFERENCE = target_diff * math.max(self.VehicleData[ "GripLossFaktor_3" ] or 0,self.VehicleData[ "GripLossFaktor_4" ] or 0) + self.exprpmdiff = target_diff * math.max(self.VehicleData[ "Exp_GLF_3" ] or 0,self.VehicleData[ "Exp_GLF_4" ] or 0) + elseif self.FrontWheelPowered and not self.RearWheelPowered then + self.WheelRPM = math.max(self.VehicleData[ "WheelRPM_1" ] or 0,self.VehicleData[ "WheelRPM_2" ] or 0) + self.RPM_DIFFERENCE = target_diff * math.max(self.VehicleData[ "GripLossFaktor_1" ] or 0,self.VehicleData[ "GripLossFaktor_2" ] or 0) + self.exprpmdiff = target_diff * math.max(self.VehicleData[ "Exp_GLF_1" ] or 0,self.VehicleData[ "Exp_GLF_2" ] or 0) + else + self.WheelRPM = 0 + self.RPM_DIFFERENCE = 0 + self.exprpmdiff = 0 + end +end + +function ENT:SimulateTurbo(LimitRPM) + if not self.Turbo then return end + + local Throttle = self:GetThrottle() + + self.SmoothTurbo = self.SmoothTurbo + math.Clamp(math.min(self.EngineRPM / LimitRPM,1) * 600 * (0.75 + 0.25 * Throttle) - self.SmoothTurbo,-15,15) + + local Volume = math.Clamp( ((self.SmoothTurbo - 300) / 150) ,0, 1) * 0.5 + local Pitch = math.Clamp( self.SmoothTurbo / 7 , 0 , 255) + + local boost = math.Clamp( -0.25 + (self.SmoothTurbo / 500) ^ 5,0,1) + + self.Turbo:ChangeVolume( Volume ) + self.Turbo:ChangePitch( Pitch ) + + return boost +end + +function ENT:SimulateBlower(LimitRPM) + if not self.Blower or not self.BlowerWhine then return end + + local Throttle = self:GetThrottle() + + self.SmoothBlower = self.SmoothBlower + math.Clamp(math.min(self.EngineRPM / LimitRPM,1) * 500 - self.SmoothBlower,-20,20) + + local Volume1 = math.Clamp( self.SmoothBlower / 400 * (1 - 0.4 * Throttle) ,0, 1) + local Volume2 = math.Clamp( self.SmoothBlower / 400 * (0.10 + 0.4 * Throttle) ,0, 1) + + local Pitch1 = 50 + math.Clamp( self.SmoothBlower / 4.5 , 0 , 205) + local Pitch2 = Pitch1 * 1.2 + + local boost = math.Clamp( (self.SmoothBlower / 600) ^ 4 ,0,1) + + self.Blower:ChangeVolume( Volume1 ) + self.Blower:ChangePitch( Pitch1 ) + + self.BlowerWhine:ChangeVolume( Volume2 ) + self.BlowerWhine:ChangePitch( Pitch2 ) + + return boost +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/spawn.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/spawn.lua new file mode 100644 index 0000000..1a9d845 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_base/spawn.lua @@ -0,0 +1,793 @@ +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetNotSolid( true ) + self:SetUseType( SIMPLE_USE ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + self:SetCustomCollisionCheck(true) + + local PObj = self:GetPhysicsObject() + if not IsValid( PObj ) then print("[SIMFPHYS] ERROR COULDN'T INITIALIZE VEHICLE! '"..self:GetModel().."' has no physics model!") return end + + PObj:EnableMotion( false ) + + self:SetValues() + + timer.Simple( 0.1, function() + if not IsValid( self ) then return end + self:InitializeVehicle() + end) +end + +function ENT:PostEntityPaste( ply , ent , createdEntities ) + self:SetValues() + + self:SetActive( false ) + self:SetDriver( NULL ) + self:SetLightsEnabled( false ) + self:SetLampsEnabled( false ) + + self:SetDriverSeat( NULL ) + self:SetFlyWheelRPM( 0 ) + self:SetThrottle( 0 ) +end + +function ENT:UpdateTransmitState() + return TRANSMIT_PVS +end + +function ENT:SetupView() + local AttachmentID = self:LookupAttachment( "vehicle_driver_eyes" ) + local AttachmentID2 = self:LookupAttachment( "vehicle_passenger0_eyes" ) + + local a_data1 = self:GetAttachment( AttachmentID ) + local a_data2 = self:GetAttachment( AttachmentID2 ) + + local ID + local ViewPos + + if a_data1 then + ID = AttachmentID + ViewPos = a_data1 + + elseif a_data2 then + ID = AttachmentID2 + ViewPos = a_data2 + + else + ID = false + ViewPos = {Ang = self:LocalToWorldAngles( Angle(0, 90,0) ),Pos = self:GetPos()} + end + + local ViewAng = ViewPos.Ang - Angle(0,0,self.SeatPitch) + ViewAng:RotateAroundAxis(self:GetUp(), -90 - (self.SeatYaw or 0)) + + local data = { + ID = ID, + ViewPos = ViewPos.Pos, + ViewAng = ViewAng, + } + + return data +end + +function ENT:SetupEnteringAnims() + local attachments = self:GetAttachments() + + self.Exitpoints = {} + self.Enterpoints = {} + + for _,i in pairs(attachments) do + local curstring = string.lower( i.name ) + + if string.match( curstring, "exit", 1 ) then + table.insert(self.Exitpoints, curstring) + end + + if string.match( curstring, "enter", 1 ) then + table.insert(self.Enterpoints, curstring) + end + end + + if table.Count( self.Enterpoints ) < 1 then + self.Enterpoints = nil + end + + if table.Count( self.Exitpoints ) < 1 then + self.Exitpoints = nil + end +end + +function ENT:InitializeVehicle() + if not IsValid( self ) then return end + + local physObj = self:GetPhysicsObject() + + if not IsValid( physObj ) then return end + + if self.LightsTable then + local vehiclelist = list.Get( "simfphys_lights" )[self.LightsTable] or false + if vehiclelist then + if vehiclelist.PoseParameters then + self.LightsPP = vehiclelist.PoseParameters + end + + if vehiclelist.BodyGroups then + self:SetBodygroup(vehiclelist.BodyGroups.Off[1], vehiclelist.BodyGroups.Off[2] ) + end + end + end + + self.Mass = self.Mass or 1200 + physObj:SetDragCoefficient( self.AirFriction or -250 ) + physObj:SetMass( self.Mass * 0.75 ) + self.baseMass = self.Mass * 0.75 + + if self.Inertia then + physObj:SetInertia( self.Inertia ) + end + + local tanksize = self.FuelTankSize and self.FuelTankSize or 65 + local fueltype = self.FuelType and self.FuelType or FUELTYPE_PETROL + + self:SetMaxFuel( tanksize ) + self:SetFuel( self:GetMaxFuel() ) + self:SetFuelType( fueltype ) + self:SetFuelPos( self.FuelFillPos and self.FuelFillPos or Vector(0,0,0) ) + + local View = self:SetupView() + + self.DriverSeat = ents.Create( "prop_vehicle_prisoner_pod" ) + self.DriverSeat:SetMoveType( MOVETYPE_NONE ) + + self.DriverSeat:SetModel( "models/nova/airboat_seat.mdl" ) + self.DriverSeat:SetKeyValue( "vehiclescript","scripts/vehicles/prisoner_pod.txt" ) + self.DriverSeat:SetKeyValue( "limitview", self.LimitView and 1 or 0 ) + self.DriverSeat:SetPos( View.ViewPos ) + self.DriverSeat:SetAngles( View.ViewAng ) + self.DriverSeat:SetOwner( self ) + self.DriverSeat:Spawn() + self.DriverSeat:Activate() + self.DriverSeat:SetPos( View.ViewPos + self.DriverSeat:GetUp() * (-34 + self.SeatOffset.z) + self.DriverSeat:GetRight() * (self.SeatOffset.y) + self.DriverSeat:GetForward() * (-6 + self.SeatOffset.x) ) + + if View.ID ~= false then + self:SetupEnteringAnims() + self.DriverSeat:SetParent( self , View.ID ) + else + self.DriverSeat:SetParent( self ) + end + + self.DriverSeat:GetPhysicsObject():EnableDrag( false ) + self.DriverSeat:GetPhysicsObject():EnableMotion( false ) + self.DriverSeat:GetPhysicsObject():SetMass( 1 ) + self.DriverSeat.fphysSeat = true + self.DriverSeat:SetNetVar('fphysSeat', true) + self.DriverSeat.base = self + self.DriverSeat.DoNotDuplicate = true + self:DeleteOnRemove( self.DriverSeat ) + self:SetDriverSeat( self.DriverSeat ) + self.DriverSeat:SetNotSolid( true ) + self.DriverSeat:SetNoDraw( true ) + self.DriverSeat:DrawShadow( false ) + simfphys.SetOwner( self.EntityOwner, self.DriverSeat ) + + local function createMassEntFor(ent) + local massEnt = ents.Create 'prop_physics' + massEnt:SetNoDraw(true) + massEnt:DrawShadow(false) + massEnt:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE) + massEnt:SetModel('models/hunter/blocks/cube025x025x025.mdl') + massEnt:SetPos(ent:GetPos()) + massEnt:SetAngles(ent:GetAngles()) + massEnt:Spawn() + + local massEntPh = massEnt:GetPhysicsObject() + massEntPh:EnableDrag( false ) + massEntPh:SetMass(1) + + massEnt.weld = constraint.Weld(massEnt, self, 0, 0) + simfphys.SetOwner(self.EntityOwner, massEnt) + ent:DeleteOnRemove(massEnt) + ent.MassEnt = massEnt + end + + createMassEntFor(self.DriverSeat) + + if self.PassengerSeats then + for i = 1, table.Count( self.PassengerSeats ) do + self.pSeat[i] = ents.Create( "prop_vehicle_prisoner_pod" ) + self.pSeat[i]:SetModel( "models/nova/airboat_seat.mdl" ) + self.pSeat[i]:SetKeyValue( "vehiclescript","scripts/vehicles/prisoner_pod.txt" ) + self.pSeat[i]:SetKeyValue( "limitview", 0) + self.pSeat[i]:SetPos( self:LocalToWorld( self.PassengerSeats[i].pos ) ) + self.pSeat[i]:SetAngles( self:LocalToWorldAngles( self.PassengerSeats[i].ang ) ) + self.pSeat[i]:SetOwner( self ) + self.pSeat[i]:Spawn() + self.pSeat[i]:Activate() + self.pSeat[i]:SetNotSolid( true ) + self.pSeat[i]:SetNoDraw( true ) + self.pSeat[i].fphysSeat = true + self.pSeat[i]:SetNetVar('fphysSeat', true) + self.pSeat[i].base = self + self.pSeat[i].DoNotDuplicate = true + simfphys.SetOwner( self.EntityOwner, self.pSeat[i] ) + + self.pSeat[i]:DrawShadow( false ) + self.pSeat[i]:GetPhysicsObject():EnableMotion( false ) + self.pSeat[i]:GetPhysicsObject():EnableDrag(false) + self.pSeat[i]:GetPhysicsObject():SetMass(1) + + self:DeleteOnRemove( self.pSeat[i] ) + + self.pSeat[i]:SetParent( self ) + createMassEntFor(self.pSeat[i]) + end + end + + if istable(WireLib) then + local passengersSeats = istable( self.pSeat ) and self.pSeat or {} + WireLib.TriggerOutput(self, "PassengerSeats", passengersSeats ) + + WireLib.TriggerOutput(self, "DriverSeat", self.DriverSeat ) + end + + if self.Attachments then + for i = 1, table.Count( self.Attachments ) do + local prop = ents.Create( ((self.Attachments[i].IsGlass == true) and "gmod_sent_vehicle_fphysics_attachment_translucent" or "gmod_sent_vehicle_fphysics_attachment") ) + prop:SetModel( self.Attachments[i].model ) + prop:SetMaterial( self.Attachments[i].material ) + prop:SetRenderMode( RENDERMODE_TRANSALPHA ) + prop:SetPos( self:LocalToWorld( self.Attachments[i].pos ) ) + prop:SetAngles( self:LocalToWorldAngles( self.Attachments[i].ang ) ) + prop:SetOwner( self ) + prop:Spawn() + prop:Activate() + prop:DrawShadow( true ) + prop:SetNotSolid( true ) + prop:SetParent( self ) + prop.DoNotDuplicate = true + simfphys.SetOwner( self.EntityOwner, prop ) + + if self.Attachments[i].skin then + prop:SetSkin( self.Attachments[i].skin ) + end + + if self.Attachments[i].bodygroups then + for b = 1, table.Count( self.Attachments[i].bodygroups ) do + prop:SetBodygroup(b, self.Attachments[i].bodygroups[b] ) + end + end + + if self.Attachments[i].useVehicleColor == true then + self.ColorableProps[i] = prop + prop:SetColor( self:GetColor() ) + else + prop:SetColor( self.Attachments[i].color or Color(255,255,255,255) ) + end + + self:DeleteOnRemove( prop ) + end + end + + if self.Radio then + local r = ents.Create('ent_dbg_radio') + r.Model = 'models/props_lab/reciever01d.mdl' + r:Spawn() + + r:SetParent(self) + r:SetLocalPos(self.Radio.pos or Vector()) + r:SetLocalAngles(self.Radio.ang or Angle()) + self.Radio = r + self:DeleteOnRemove(r) + r.carRadio = true + end + + local postSpawn = simfphys.postSpawn[self.VehicleName] + if postSpawn then + postSpawn(self) + end + + self:GetVehicleData() + self:UpdateInventory() +end + +function ENT:GetVehicleData() + self:SetPoseParameter("vehicle_steer",1) + self:SetPoseParameter("vehicle_wheel_fl_height",1) + self:SetPoseParameter("vehicle_wheel_fr_height",1) + self:SetPoseParameter("vehicle_wheel_rl_height",1) + self:SetPoseParameter("vehicle_wheel_rr_height",1) + + timer.Simple( 0.15, function() + if not IsValid(self) then return end + self.posepositions["Pose0_Steerangle"] = self.CustomWheels and Angle(0,0,0) or self:GetAttachment( self:LookupAttachment( "wheel_fl" ) ).Ang + self.posepositions["Pose0_Pos_FL"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosFL ) or self:GetAttachment( self:LookupAttachment( "wheel_fl" ) ).Pos + self.posepositions["Pose0_Pos_FR"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosFR ) or self:GetAttachment( self:LookupAttachment( "wheel_fr" ) ).Pos + self.posepositions["Pose0_Pos_RL"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosRL ) or self:GetAttachment( self:LookupAttachment( "wheel_rl" ) ).Pos + self.posepositions["Pose0_Pos_RR"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosRR ) or self:GetAttachment( self:LookupAttachment( "wheel_rr" ) ).Pos + + self:WriteVehicleDataTable() + end ) +end + +function ENT:ResetJoystick() + self.PressedKeys["joystick_steer_left"] = 0 + self.PressedKeys["joystick_steer_right"] = 0 + self.PressedKeys["joystick_brake"] = 0 + self.PressedKeys["joystick_throttle"] = 0 + self.PressedKeys["joystick_gearup"] = 0 + self.PressedKeys["joystick_geardown"] = 0 + self.PressedKeys["joystick_handbrake"] = 0 + self.PressedKeys["joystick_clutch"] = 0 + self.PressedKeys["joystick_air_w"] = 0 + self.PressedKeys["joystick_air_a"] = 0 + self.PressedKeys["joystick_air_s"] = 0 + self.PressedKeys["joystick_air_d"] = 0 +end + +function ENT:SetValues() + self:SetGear( 2 ) + + self.EnableSuspension = 0 + self.WheelOnGroundDelay = 0 + self.SmoothAng = 0 + self.Steer = 0 + self:SetEngineActive(false) + self.EngineTorque = 0 + + self.pSeat = {} + self.exfx = {} + self.Wheels = {} + self.Elastics = {} + self.GhostWheels = {} + self.PressedKeys = {} + self:ResetJoystick() + + self.ColorableProps = {} + self.posepositions = {} + + self.HandBrakePower = 0 + self.DriveWheelsOnGround = 0 + self.WheelRPM = 0 + self.EngineRPM = 0 + self.RpmDiff = 0 + self.Torque = 0 + self.CurrentGear = 2 + self.GearUpPressed = 0 + self.GearDownPressed = 0 + self.RPM_DIFFERENCE = 0 + self.exprpmdiff = 0 + self.OldLockBrakes = 0 + self.ThrottleDelay = 0 + self.Brake = 0 + self.HandBrake = 0 + self.AutoClutch = 0 + self.NextShift = 0 + self.ForwardSpeed = 0 + self.EngineWasOn = 0 + self.SmoothTurbo = 0 + self.SmoothBlower = 0 + self.cc_speed = 0 + self.LightsActivated = false + + self.VehicleData = {} + for i = 1, 6 do + self.VehicleData[ "spin_"..i ] = 0 + self.VehicleData[ "SurfaceMul_"..i ] = 1 + self.VehicleData[ "onGround_"..i ] = 0 + end + + self.VehicleData[ "Steer" ] = 0 +end + +function ENT:WriteVehicleDataTable() + self:SetPoseParameter("vehicle_steer",0) + self:SetPoseParameter("vehicle_wheel_fl_height",0) + self:SetPoseParameter("vehicle_wheel_fr_height",0) + self:SetPoseParameter("vehicle_wheel_rl_height",0) + self:SetPoseParameter("vehicle_wheel_rr_height",0) + + timer.Simple( 0.15, function() + if not IsValid(self) then return end + self.posepositions["Pose1_Steerangle"] = self.CustomWheels and Angle(0,0,0) or self:GetAttachment( self:LookupAttachment( "wheel_fl" ) ).Ang + self.posepositions["Pose1_Pos_FL"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosFL ) or self:GetAttachment( self:LookupAttachment( "wheel_fl" ) ).Pos + self.posepositions["Pose1_Pos_FR"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosFR ) or self:GetAttachment( self:LookupAttachment( "wheel_fr" ) ).Pos + self.posepositions["Pose1_Pos_RL"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosRL ) or self:GetAttachment( self:LookupAttachment( "wheel_rl" ) ).Pos + self.posepositions["Pose1_Pos_RR"] = self.CustomWheels and self:LocalToWorld( self.CustomWheelPosRR ) or self:GetAttachment( self:LookupAttachment( "wheel_rr" ) ).Pos + self.posepositions["PoseL_Pos_FL"] = self:WorldToLocal( self.posepositions.Pose1_Pos_FL ) + self.posepositions["PoseL_Pos_FR"] = self:WorldToLocal( self.posepositions.Pose1_Pos_FR ) + self.posepositions["PoseL_Pos_RL"] = self:WorldToLocal( self.posepositions.Pose1_Pos_RL ) + self.posepositions["PoseL_Pos_RR"] = self:WorldToLocal( self.posepositions.Pose1_Pos_RR ) + + self.VehicleData["suspensiontravel_fl"] = self.CustomWheels and self.FrontHeight or math.Round( (self.posepositions.Pose0_Pos_FL - self.posepositions.Pose1_Pos_FL):Length() , 2) + self.VehicleData["suspensiontravel_fr"] = self.CustomWheels and self.FrontHeight or math.Round( (self.posepositions.Pose0_Pos_FR - self.posepositions.Pose1_Pos_FR):Length() , 2) + self.VehicleData["suspensiontravel_rl"] = self.CustomWheels and self.RearHeight or math.Round( (self.posepositions.Pose0_Pos_RL - self.posepositions.Pose1_Pos_RL):Length() , 2) + self.VehicleData["suspensiontravel_rr"] = self.CustomWheels and self.RearHeight or math.Round( (self.posepositions.Pose0_Pos_RR - self.posepositions.Pose1_Pos_RR):Length() , 2) + + local Figure1 = math.Round( math.acos( math.Clamp(self.posepositions.Pose0_Steerangle:Up():Dot(self.posepositions.Pose1_Steerangle:Up()),-1,1) ) * (180 / math.pi) , 2) + local Figure2 = math.Round( math.acos( math.Clamp(self.posepositions.Pose0_Steerangle:Forward():Dot(self.posepositions.Pose1_Steerangle:Forward()),-1,1) ) * (180 / math.pi) , 2) + local Figure3 = math.Round( math.acos( math.Clamp(self.posepositions.Pose0_Steerangle:Right():Dot(self.posepositions.Pose1_Steerangle:Right()),-1,1) ) * (180 / math.pi) , 2) + self.VehicleData["steerangle"] = self.CustomWheels and self.CustomSteerAngle or math.max(Figure1,Figure2,Figure3) + + local pFL = self.posepositions.Pose0_Pos_FL + local pFR = self.posepositions.Pose0_Pos_FR + local pRL = self.posepositions.Pose0_Pos_RL + local pRR = self.posepositions.Pose0_Pos_RR + local pAngL = self:WorldToLocalAngles( ((pFL + pFR) / 2 - (pRL + pRR) / 2):Angle() ) + pAngL.r = 0 + pAngL.p = 0 + + self.VehicleData["LocalAngForward"] = pAngL + + local yAngL = self.VehicleData.LocalAngForward - Angle(0,90,0) + yAngL:Normalize() + + self.VehicleData["LocalAngRight"] = yAngL + self.VehicleData[ "pp_spin_1" ] = "vehicle_wheel_fl_spin" + self.VehicleData[ "pp_spin_2" ] = "vehicle_wheel_fr_spin" + self.VehicleData[ "pp_spin_3" ] = "vehicle_wheel_rl_spin" + self.VehicleData[ "pp_spin_4" ] = "vehicle_wheel_rr_spin" + + self.Turbo = CreateSound(self, "") + self.Blower = CreateSound(self, "") + self.BlowerWhine = CreateSound(self, "") + self.BlowOff = CreateSound(self, "") + + self:SetFastSteerAngle(self.FastSteeringAngle / self.VehicleData["steerangle"]) + self:SetNotSolid( false ) + self:SetupVehicle() + end ) +end + +function ENT:SetupVehicle() + local BaseMass = self:GetPhysicsObject():GetMass() + local MassCenterOffset = self.CustomMassCenter or Vector(0,0,0) + local BaseMassCenter = self:LocalToWorld( self:GetPhysicsObject():GetMassCenter() - MassCenterOffset ) + + local OffsetMass = BaseMass * 0.25 + local CenterWheels = (self.posepositions["Pose1_Pos_FL"] + self.posepositions["Pose1_Pos_FR"] + self.posepositions["Pose1_Pos_RL"] + self.posepositions["Pose1_Pos_RR"]) / 4 + + local Sub = CenterWheels - BaseMassCenter + local Dir = Sub:GetNormalized() + local Dist = Sub:Length() + local DistAdd = BaseMass * Dist / OffsetMass + + local OffsetMassCenter = BaseMassCenter + Dir * (Dist + DistAdd) + + self.MassOffset = ents.Create( "prop_physics" ) + self.MassOffset:SetModel( "models/hunter/plates/plate.mdl" ) + self.MassOffset:SetPos( OffsetMassCenter ) + self.MassOffset:SetAngles( Angle(0,0,0) ) + self.MassOffset:Spawn() + self.MassOffset:Activate() + self.MassOffset:GetPhysicsObject():EnableMotion(false) + self.MassOffset:GetPhysicsObject():SetMass( OffsetMass ) + self.MassOffset:GetPhysicsObject():EnableDrag( false ) + self.MassOffset:SetOwner( self ) + self.MassOffset:DrawShadow( false ) + self.MassOffset:SetNotSolid( true ) + self.MassOffset:SetNoDraw( true ) + self.MassOffset.DoNotDuplicate = true + simfphys.SetOwner( self.EntityOwner, self.MassOffset ) + + local constraint = constraint.Weld(self.MassOffset,self, 0, 0, 0,true, true) + constraint.DoNotDuplicate = true + + if self.CustomWheels then + if self.CustomWheelModel then + if not file.Exists( self.CustomWheelModel, "GAME" ) then + if IsValid( self.EntityOwner ) then + self.EntityOwner:PrintMessage( HUD_PRINTTALK, "ERROR: \""..self.CustomWheelModel.."\" does not exist! Removing vehicle. (Class: "..self:GetSpawn_List()..")") + end + self:Remove() + return + end + + if self.SteerFront ~= false then + self.SteerMaster = ents.Create( "prop_physics" ) + self.SteerMaster:SetModel( self.CustomWheelModel ) + self.SteerMaster:SetPos( self:GetPos() ) + self.SteerMaster:SetAngles( self:GetAngles() ) + self.SteerMaster:Spawn() + self.SteerMaster:Activate() + + local pobj = self.SteerMaster:GetPhysicsObject() + + if IsValid(pobj) then + pobj:EnableMotion(false) + else + if IsValid( self.EntityOwner ) then + self.EntityOwner:PrintMessage( HUD_PRINTTALK, "ERROR: \""..self.CustomWheelModel.."\" doesn't have an collision model! Removing vehicle. (Class: "..self:GetSpawn_List()..")") + end + self.SteerMaster:Remove() + self:Remove() + + return + end + + self.SteerMaster:SetOwner( self ) + self.SteerMaster:DrawShadow( false ) + self.SteerMaster:SetNotSolid( true ) + self.SteerMaster:SetNoDraw( true ) + self.SteerMaster.DoNotDuplicate = true + self:DeleteOnRemove( self.SteerMaster ) + simfphys.SetOwner( self.EntityOwner, self.SteerMaster ) + end + + if self.SteerRear then + self.SteerMaster2 = ents.Create( "prop_physics" ) + self.SteerMaster2:SetModel( self.CustomWheelModel ) + self.SteerMaster2:SetPos( self:GetPos() ) + self.SteerMaster2:SetAngles( self:GetAngles() ) + self.SteerMaster2:Spawn() + self.SteerMaster2:Activate() + + local pobj = self.SteerMaster2:GetPhysicsObject() + if IsValid(pobj) then + pobj:EnableMotion(false) + else + if IsValid( self.EntityOwner ) then + self.EntityOwner:PrintMessage( HUD_PRINTTALK, "ERROR: \""..self.CustomWheelModel.."\" doesn't have an collision model! Removing vehicle. (Class: "..self:GetSpawn_List()..")") + end + self.SteerMaster2:Remove() + self:Remove() + return + end + + self.SteerMaster2:SetOwner( self ) + self.SteerMaster2:DrawShadow( false ) + self.SteerMaster2:SetNotSolid( true ) + self.SteerMaster2:SetNoDraw( true ) + self.SteerMaster2.DoNotDuplicate = true + self:DeleteOnRemove( self.SteerMaster2 ) + simfphys.SetOwner( self.EntityOwner, self.SteerMaster2 ) + end + + local radius = IsValid(self.SteerMaster) and (self.SteerMaster:OBBMaxs() - self.SteerMaster:OBBMins()) or (self.SteerMaster2:OBBMaxs() - self.SteerMaster2:OBBMins()) + self.FrontWheelRadius = self.FrontWheelRadius or math.max( radius.x, radius.y, radius.z ) * 0.5 + self.RearWheelRadius = self.RearWheelRadius or self.FrontWheelRadius + + self:CreateWheel(1, WheelFL, self:LocalToWorld( self.CustomWheelPosFL ), self.FrontHeight, self.FrontWheelRadius, false , self:LocalToWorld( self.CustomWheelPosFL + Vector(0,0,self.CustomSuspensionTravel * 0.5) ),self.CustomSuspensionTravel, self.FrontConstant, self.FrontDamping, self.FrontRelativeDamping) + self:CreateWheel(2, WheelFR, self:LocalToWorld( self.CustomWheelPosFR ), self.FrontHeight, self.FrontWheelRadius, true , self:LocalToWorld( self.CustomWheelPosFR + Vector(0,0,self.CustomSuspensionTravel * 0.5) ),self.CustomSuspensionTravel, self.FrontConstant, self.FrontDamping, self.FrontRelativeDamping) + self:CreateWheel(3, WheelRL, self:LocalToWorld( self.CustomWheelPosRL ), self.RearHeight, self.RearWheelRadius, false , self:LocalToWorld( self.CustomWheelPosRL + Vector(0,0,self.CustomSuspensionTravel * 0.5) ),self.CustomSuspensionTravel, self.RearConstant, self.RearDamping, self.RearRelativeDamping) + self:CreateWheel(4, WheelRR, self:LocalToWorld( self.CustomWheelPosRR ), self.RearHeight, self.RearWheelRadius, true , self:LocalToWorld( self.CustomWheelPosRR + Vector(0,0,self.CustomSuspensionTravel * 0.5) ), self.CustomSuspensionTravel, self.RearConstant, self.RearDamping, self.RearRelativeDamping) + + if self.CustomWheelPosML then + self:CreateWheel(5, WheelML, self:LocalToWorld( self.CustomWheelPosML ), self.RearHeight, self.RearWheelRadius, false , self:LocalToWorld( self.CustomWheelPosML + Vector(0,0,self.CustomSuspensionTravel * 0.5) ),self.CustomSuspensionTravel, self.RearConstant, self.RearDamping, self.RearRelativeDamping) + end + + if self.CustomWheelPosMR then + self:CreateWheel(6, WheelMR, self:LocalToWorld( self.CustomWheelPosMR ), self.RearHeight, self.RearWheelRadius, true , self:LocalToWorld( self.CustomWheelPosMR + Vector(0,0,self.CustomSuspensionTravel * 0.5) ), self.CustomSuspensionTravel, self.RearConstant, self.RearDamping, self.RearRelativeDamping) + end + else + if IsValid( self.EntityOwner ) then + self.EntityOwner:PrintMessage( HUD_PRINTTALK, "ERROR: no wheel model defined. Removing vehicle. (Class: "..self:GetSpawn_List()..")") + end + self:Remove() + end + else + self:CreateWheel(1, WheelFL, self:GetAttachment( self:LookupAttachment( "wheel_fl" ) ).Pos, self.FrontHeight, self.FrontWheelRadius, false , self.posepositions.Pose1_Pos_FL, self.VehicleData.suspensiontravel_fl, self.FrontConstant, self.FrontDamping, self.FrontRelativeDamping) + self:CreateWheel(2, WheelFR, self:GetAttachment( self:LookupAttachment( "wheel_fr" ) ).Pos, self.FrontHeight, self.FrontWheelRadius, true , self.posepositions.Pose1_Pos_FR, self.VehicleData.suspensiontravel_fr, self.FrontConstant, self.FrontDamping, self.FrontRelativeDamping) + self:CreateWheel(3, WheelRL, self:GetAttachment( self:LookupAttachment( "wheel_rl" ) ).Pos, self.RearHeight, self.RearWheelRadius, false , self.posepositions.Pose1_Pos_RL, self.VehicleData.suspensiontravel_rl, self.RearConstant, self.RearDamping, self.RearRelativeDamping) + self:CreateWheel(4, WheelRR, self:GetAttachment( self:LookupAttachment( "wheel_rr" ) ).Pos, self.RearHeight, self.RearWheelRadius, true , self.posepositions.Pose1_Pos_RR, self.VehicleData.suspensiontravel_rr, self.RearConstant, self.RearDamping, self.RearRelativeDamping) + end + + timer.Simple( 0.01, function() + if not istable( self.Wheels ) then return end + + for i = 1, table.Count( self.Wheels ) do + local Ent = self.Wheels[ i ] + local PhysObj = Ent:GetPhysicsObject() + + if IsValid( PhysObj ) then + PhysObj:EnableMotion( true ) + end + end + + timer.Simple( 0.1, function() + if not IsValid( self ) then return end + + self:GetPhysicsObject():EnableMotion(true) + + local PhysObj = self.MassOffset:GetPhysicsObject() + if IsValid( PhysObj ) then + PhysObj:EnableMotion(true) + end + end ) + end ) + + self.VehicleData["filter"] = table.Copy( self.Wheels ) + table.insert( self.VehicleData["filter"], self ) + + self.EnableSuspension = 1 + self:OnSpawn() +end + +function ENT:CreateWheel(index, name, attachmentpos, height, radius, swap_y , poseposition, suspensiontravel, constant, damping, rdamping) + local fAng = self:LocalToWorldAngles( self.VehicleData.LocalAngForward ) + local rAng = self:LocalToWorldAngles( self.VehicleData.LocalAngRight ) + + local Forward = fAng:Forward() + local Right = swap_y and -rAng:Forward() or rAng:Forward() + local Up = self:GetUp() + + local RopeLength = 150 + local LimiterLength = 60 + local LimiterRopeLength = math.sqrt( (suspensiontravel * 0.5) ^ 2 + LimiterLength ^ 2 ) + local WheelMass = self.Mass / 32 + + if self.FrontWheelMass and (index == 1 or index == 2) then + WheelMass = self.FrontWheelMass + end + if self.RearWheelMass and (index == 3 or index == 4 or index == 5 or index == 6) then + WheelMass = self.RearWheelMass + end + + self.name = ents.Create( "gmod_sent_vehicle_fphysics_wheel" ) + self.name:SetPos( attachmentpos - Up * height) + self.name:SetAngles( fAng ) + self.name:Spawn() + self.name:Activate() + self.name:PhysicsInitSphere( radius, "jeeptire" ) + self.name:SetCollisionBounds( Vector(-radius,-radius,-radius), Vector(radius,radius,radius) ) + self.name:GetPhysicsObject():EnableMotion(false) + self.name:GetPhysicsObject():SetMass( WheelMass ) + self.name:SetBaseEnt( self ) + simfphys.SetOwner( self.EntityOwner, self.name ) + self.name.EntityOwner = self.EntityOwner + self.name.Index = index + self.name.Radius = radius + + if self.CustomWheels then + local Model = (self.CustomWheelModel_R and (index == 3 or index == 4 or index == 5 or index == 6)) and self.CustomWheelModel_R or self.CustomWheelModel + local ghostAng = Right:Angle() + local mirAng = swap_y and 1 or -1 + ghostAng:RotateAroundAxis(Forward,self.CustomWheelAngleOffset.p * mirAng) + ghostAng:RotateAroundAxis(Right,self.CustomWheelAngleOffset.r * mirAng) + ghostAng:RotateAroundAxis(Up,-self.CustomWheelAngleOffset.y) + + local Camber = self.CustomWheelCamber or 0 + ghostAng:RotateAroundAxis(Forward, Camber * mirAng) + + self.GhostWheels[index] = ents.Create( "gmod_sent_vehicle_fphysics_attachment" ) + self.GhostWheels[index]:SetModel( Model ) + self.GhostWheels[index]:SetPos( self.name:GetPos() ) + self.GhostWheels[index]:SetAngles( ghostAng ) + self.GhostWheels[index]:SetOwner( self ) + self.GhostWheels[index]:Spawn() + self.GhostWheels[index]:Activate() + self.GhostWheels[index]:SetNotSolid( true ) + self.GhostWheels[index].DoNotDuplicate = true + self.GhostWheels[index]:SetParent( self.name ) + self:DeleteOnRemove( self.GhostWheels[index] ) + simfphys.SetOwner( self.EntityOwner, self.GhostWheels[index] ) + + self.GhostWheels[index]:SetRenderMode( RENDERMODE_TRANSALPHA ) + + if self.ModelInfo then + if self.ModelInfo.WheelColor then + self.GhostWheels[index]:SetColor( self.ModelInfo.WheelColor ) + end + end + + self.name.GhostEnt = self.GhostWheels[index] + + local nocollide = constraint.NoCollide(self,self.name,0,0) + nocollide.DoNotDuplicate = true + end + + local targetentity = self + if self.CustomWheels then + if index == 1 or index == 2 then + targetentity = self.SteerMaster or self + end + if index == 3 or index == 4 then + targetentity = self.SteerMaster2 or self + end + end + + local Ballsocket = constraint.AdvBallsocket(targetentity,self.name,0,0,Vector(0,0,0),Vector(0,0,0),0,0, -0.01, -0.01, -0.01, 0.01, 0.01, 0.01, 0, 0, 0, 1, 1) + local Rope1 = constraint.Rope(self,self.name,0,0,self:WorldToLocal( self.name:GetPos() + Forward * RopeLength * 0.5 + Right * RopeLength), Vector(0,0,0), Vector(RopeLength * 0.5,RopeLength,0):Length(), 0, 0, 0,"cable/cable2", true ) + local Rope2 = constraint.Rope(self,self.name,0,0,self:WorldToLocal( self.name:GetPos() - Forward * RopeLength * 0.5 + Right * RopeLength), Vector(0,0,0), Vector(RopeLength * 0.5,RopeLength,0):Length(), 0, 0, 0,"cable/cable2", true ) + + if self.StrengthenSuspension == true or (self.StrengthenFrontSuspension and index <= 2) or (self.StrengthenRearSuspension and index >= 3) then + local Rope3 = constraint.Rope(self,self.name,0,0,self:WorldToLocal( poseposition - Up * suspensiontravel * 0.5 + Right * LimiterLength), Vector(0,0,0),LimiterRopeLength * 0.99, 0, 0, 0,"cable/cable2", false ) + local Rope4 = constraint.Rope(self,self.name,0,0,self:WorldToLocal( poseposition - Up * suspensiontravel * 0.5 - Right * LimiterLength), Vector(0,0,0),LimiterRopeLength * 1, 0, 0, 0,"cable/cable2", false ) + local elastic1 = constraint.Elastic(self.name, self, 0, 0, Vector(0,0,height), self:WorldToLocal( self.name:GetPos() ), constant * 0.5, damping * 0.5, rdamping * 0.5,"cable/cable2",0, false) + local elastic2 = constraint.Elastic(self.name, self, 0, 0, Vector(0,0,height), self:WorldToLocal( self.name:GetPos() ), constant * 0.5, damping * 0.5, rdamping * 0.5,"cable/cable2",0, false) + + Rope3.DoNotDuplicate = true + Rope4.DoNotDuplicate = true + elastic1.DoNotDuplicate = true + elastic2.DoNotDuplicate = true + self.Elastics[index] = elastic1 + self.Elastics[index * 10] = elastic2 + else + local Rope3 = constraint.Rope(self,self.name,0,0,self:WorldToLocal( poseposition - Up * suspensiontravel * 0.5 + Right * LimiterLength), Vector(0,0,0),LimiterRopeLength, 0, 0, 0,"cable/cable2", false ) + local elastic = constraint.Elastic(self.name, self, 0, 0, Vector(0,0,height), self:WorldToLocal( self.name:GetPos() ), constant, damping, rdamping,"cable/cable2",0, false) + + Rope3.DoNotDuplicate = true + elastic.DoNotDuplicate = true + self.Elastics[index] = elastic + end + + self.Wheels[index] = self.name + + Ballsocket.DoNotDuplicate = true + Rope1.DoNotDuplicate = true + Rope2.DoNotDuplicate = true + + if index == 2 then + if IsValid( self.Wheels[ 1 ] ) and IsValid( self.Wheels[ 2 ] ) then + local nocollide = constraint.NoCollide( self.Wheels[ 1 ], self.Wheels[ 2 ], 0, 0 ) + nocollide.DoNotDuplicate = true + end + + elseif index == 4 then + if IsValid( self.Wheels[ 3 ] ) and IsValid( self.Wheels[ 4 ] ) then + local nocollide = constraint.NoCollide( self.Wheels[ 3 ], self.Wheels[ 4 ], 0, 0 ) + nocollide.DoNotDuplicate = true + end + + elseif index == 6 then + if IsValid( self.Wheels[ 5 ] ) and IsValid( self.Wheels[ 6 ] ) then + local nocollide = constraint.NoCollide( self.Wheels[ 5 ], self.Wheels[ 6 ], 0, 0 ) + nocollide.DoNotDuplicate = true + end + end +end + +function ENT:UpdateInventory() + if not self.inv then + local inv = self:CreateInventory() + inv:AddContainer('glove', { volume = 3 }) + end + + local vehName = self.cdData and self.cdData.name or self.VehicleTable and self.VehicleTable.Name or self.VehicleName + + local glove = self.inv.conts.glove + glove.name = vehName .. ' – бардачок' + glove.icon = octolib.icons.color('car_glovebox') + glove:QueueSync() + + local forward = self.Forward or self:GetForward() + local trunkPos = self:NearestPoint(self:WorldSpaceCenter() + forward * -1000) - forward * 10 + + local trunk = self.inv.conts.trunk + local trunkData = self.Trunk + if trunkData then + local contData = { + name = vehName .. ' – багажник', + icon = octolib.icons.color('car_trunk'), + } + + for i = 2, #trunkData do + if not istable(trunkData[i]) then break end + local volume, bgID, bgVal = unpack(trunkData[i]) + if self:GetBodygroup(bgID) == bgVal then + contData.volume = volume + end + end + + if not contData.volume and trunkData[1] then + contData.volume = trunkData[1] + end + + if contData.volume then + if trunk then + trunk.name = contData.name + trunk.icon = contData.icon + trunk.volume = contData.volume + trunk:ResetSpaceMass() + trunk:QueueSync() + else + self.inv:AddContainer('trunk', contData):QueueSync() + end + elseif trunk then + trunk:Remove(true, trunkPos) + end + elseif trunk then + trunk:Remove(true, trunkPos) + end +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump.lua new file mode 100644 index 0000000..a79e7ff --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump.lua @@ -0,0 +1,371 @@ +AddCSLuaFile() + +ENT.Type = 'anim' +ENT.PrintName = 'gas pump (petrol)' +ENT.Category = 'simfphys' + +ENT.Spawnable = true +ENT.AdminOnly = true + +function ENT:SetupDataTables() + self:NetworkVar( 'Entity',0, 'User' ) + self:NetworkVar( 'Bool',0, 'Active' ) + self:NetworkVar( 'Float',0, 'FuelUsed' ) + + if SERVER then + self:NetworkVarNotify( 'Active', self.OnActiveChanged ) + end +end + +local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p +end + +if CLIENT then + local cable = Material( 'cable/cable2' ) + local colors = CFG.skinColors + + surface.CreateFont( 'simfphys_gaspump', { + font = 'Calibri', + size = 34, + weight = 300, + antialias = true, + extended = true + }) + + surface.CreateFont( 'simfphys_gaspump2', { + font = 'Consolas', + size = 22, + weight = 300, + antialias = true, + extended = true + }) + + surface.CreateFont( 'simfphys_gaspump_note', { + font = 'Calibri', + size = 16, + weight = 200, + antialias = true, + extended = true + }) + + local function GetDigit( value ) + local fvalue = math.floor(value,0) + + local decimal = 1000 + (value - fvalue) * 1000 + + local digit1 = fvalue % 10 + local digit2 = (fvalue - digit1) % 100 + local digit3 = (fvalue - digit1 - digit2) % 1000 + + local digit4 = decimal % 10 + local digit5 = (decimal - digit4) % 100 + local digit6 = (decimal - digit4 - digit5) % 1000 + + local digits = { + [1] = math.Round(digit1,0), + [2] = math.Round(digit2 / 10,0), + [3] = math.Round(digit3 / 100,0), + [4] = math.Round(digit5 / 10,0), + [5] = math.Round(digit6 / 100,0), + } + return digits + end + + function ENT:Draw() + self:DrawModel() + + if LocalPlayer():GetPos():DistToSqr(self:GetPos()) > 350000 then return end + + local pos = self:LocalToWorld( Vector(10,0,45) ) + local ang = self:LocalToWorldAngles( Angle(0,90,90) ) + local ply = self:GetUser() + + local startPos = self:LocalToWorld( Vector(0.06,-17.77,55.48) ) + local p2 = self:LocalToWorld( Vector(8,-17.77,30) ) + local p3 = self:LocalToWorld( Vector(0,-20,30) ) + local endPos = self:LocalToWorld( Vector(0.06,-20.3,37) ) + + if IsValid( ply ) then + local id = ply:LookupAttachment('anim_attachment_rh') + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + endPos = (attachment.Pos + attachment.Ang:Forward() * -3 + attachment.Ang:Right() * 2 + attachment.Ang:Up() * -3.5) + p3 = endPos + attachment.Ang:Right() * 5 - attachment.Ang:Up() * 20 + end + + for i = 1,15 do + local active = IsValid( ply ) + + local de = active and 1 or 2 + + if (not active and i > 1) or active then + + local sp = bezier(startPos, p2, p3, endPos, (i - de) / 15) + local ep = bezier(startPos, p2, p3, endPos, i / 15) + + render.SetMaterial( cable ) + render.DrawBeam( sp, ep, 2, 1, 1, Color( 100, 100, 100, 255 ) ) + end + end + + cam.Start3D2D( self:LocalToWorld( Vector(10,0,45) ), self:LocalToWorldAngles( Angle(0,90,90) ), 0.1 ) + draw.NoTexture() + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawRect( -150, -120, 300, 240 ) + + draw.RoundedBox( 5, -130, -110, 260, 200, colors.g ) + draw.RoundedBox( 5, -128, -108, 256, 196, colors.bg ) + + -- draw.RoundedBox( 5, -92, -36, 184, 32, Color( 102,170,170, 150 ) ) + draw.RoundedBox( 5, -90, -14, 180, 28, Color( 238, 238, 238, 255 ) ) + draw.RoundedBox( 5, -88, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -68, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -48, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -23, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -3, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, 23, -12, 66, 24, Color( 255, 255, 255, 255 ) ) + + -- draw.RoundedBox( 5, -91, -25, 182, 30, Color( 240, 200, 0, 150 ) ) + -- draw.RoundedBox( 5, -90, -24, 180, 28, Color( 50, 50, 50, 255 ) ) + -- draw.RoundedBox( 5, -88, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -68, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -48, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -28, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -8, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, 12, -22, 76, 24, Color( 0, 0, 0, 255 ) ) + + draw.SimpleText( L.liters, 'simfphys_gaspump2', 55, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + -- draw.SimpleText( 'ГАЛЛОНЫ', 'simfphys_gaspump', 50, -20, Color( 0,0,0, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + local liter = self:GetFuelUsed() + -- local gallon = liter * 0.264172 + local l_digits = GetDigit( math.Round( liter, 2) ) + -- local g_digits = GetDigit( math.Round( gallon, 2) ) + + draw.SimpleText( l_digits[4], 'simfphys_gaspump2', 11, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[5], 'simfphys_gaspump2', -9, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( '.', 'simfphys_gaspump2', -21, -8, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[1], 'simfphys_gaspump2', -34, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[2], 'simfphys_gaspump2', -54, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[3], 'simfphys_gaspump2', -74, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + + + -- draw.SimpleText( g_digits[4], 'simfphys_gaspump', 6, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[5], 'simfphys_gaspump', -14, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( ',', 'simfphys_gaspump', -26, -15, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[1], 'simfphys_gaspump', -34, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[2], 'simfphys_gaspump', -54, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[3], 'simfphys_gaspump', -74, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + + draw.SimpleText( L.before_pay, 'simfphys_gaspump_note', 0, 50, Color( 238, 238, 238, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + draw.SimpleText( L.price2_for_liter, 'simfphys_gaspump_note', 0, 65, Color( 238, 238, 238, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + draw.SimpleText( L.petrol, 'simfphys_gaspump', 0, -60, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + cam.End3D2D() + end + + net.Receive('dbg.fuelPurchase', function(len) + Derma_StringRequest( + L.buy_fuel, + L.buy2_fuel, + '50', + function(res) + local amount = tonumber(res) + if not amount or amount <= 0 then + octolib.notify.show('warning', L.incorrect_quantity) + return + end + + amount = math.min(amount, 500) + net.Start('dbg.fuelPurchase') + net.WriteUInt(amount, 10) + net.SendToServer() + end, + function() + end, + L.buy, + L.cancel + ) + end) + + return +else + util.AddNetworkString('dbg.fuelPurchase') + net.Receive('dbg.fuelPurchase', function(len, ply) + local amount = net.ReadUInt(10) + local wep = ply:GetWeapon('weapon_simfillerpistol') + if IsValid(wep) then + local price = amount * simfphys.fuelPrices[wep:GetFuelType()] + if not ply:canAfford(price) then + ply:Notify('warning', L.not_enough_money) + return + end + + ply:addMoney(-price) + ply:Notify(L.fuel_buy:format(amount, DarkRP.formatMoney(price))) + ply.usedFuel = 0 + ply.usedFuelMax = amount + else + ply:Notify('warning', L.how_you_do_this) + end + end) +end + +function ENT:Use( ply ) + if not self:GetActive() then + if not ply.gas_InUse then + ply.usedFuel = 0 + ply.usedFuelMax = 0 + self:SetActive( true ) + self:SetUser( ply ) + ply:Give( 'weapon_simfillerpistol' ) + ply:SelectWeapon( 'weapon_simfillerpistol' ) + ply.gas_InUse = true + + local weapon = ply:GetActiveWeapon() + if IsValid( weapon ) and weapon:GetClass() == 'weapon_simfillerpistol' then + weapon:SetFuelType( FUELTYPE_PETROL ) + end + + net.Start('dbg.fuelPurchase') + net.Send(ply) + end + else + if ply == self:GetUser() then + ply:StripWeapon( 'weapon_simfillerpistol' ) + self:SetActive( false ) + self:SetUser( NULL ) + ply.gas_InUse = false + end + end +end + +function ENT:OnActiveChanged( name, old, new) + if new == old then return end + + if new then + if self.sound then + self.sound:Stop() + self.sound = nil + end + self.sound = CreateSound(self, 'vehicles/crane/crane_idle_loop3.wav') + self.sound:PlayEx(0,0) + self.sound:ChangeVolume( 0.2,2 ) + self.sound:ChangePitch( 255,3 ) + if IsValid( self.PumpEnt ) then + self.PumpEnt:SetNoDraw( true ) + end + else + if IsValid( self.PumpEnt ) then + self.PumpEnt:SetNoDraw( false ) + end + + if self.sound then + self.sound:ChangeVolume( 0,2 ) + self.sound:ChangePitch( 0,3 ) + end + end +end + +function ENT:Initialize() + self:SetModel( 'models/props_wasteland/gaspump001a.mdl' ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.PumpEnt = ents.Create( 'prop_dynamic' ) + self.PumpEnt:SetModel( 'models/props_equipment/gas_pump_p13.mdl' ) + self.PumpEnt:SetPos( self:LocalToWorld( Vector(-0.2,-14.6,45.7) ) ) + self.PumpEnt:SetAngles( self:LocalToWorldAngles( Angle(-0.3,92.3,-0.1) ) ) + self.PumpEnt:SetMoveType( MOVETYPE_NONE ) + self.PumpEnt:Spawn() + self.PumpEnt:Activate() + self.PumpEnt:SetNotSolid( true ) + self.PumpEnt:DrawShadow( false ) + self.PumpEnt:SetParent( self ) + + local PObj = self:GetPhysicsObject() + if not IsValid( PObj ) then return end + + PObj:EnableMotion( false ) +end + +function ENT:Think() + self:NextThink( CurTime() + 0.5 ) + + local ply = self:GetUser() + if IsValid( ply ) then + self:SetFuelUsed( ply.usedFuel ) + + local Dist = (ply:GetPos() - self:GetPos()):Length() + + if ply:Alive() then + if ply:InVehicle() then + if ply:HasWeapon( 'weapon_simfillerpistol' ) then + ply:StripWeapon( 'weapon_simfillerpistol' ) + end + ply.gas_InUse = false + self:Disable() + else + if ply:HasWeapon( 'weapon_simfillerpistol' ) then + if not IsValid(ply:GetActiveWeapon()) or ply:GetActiveWeapon():GetClass() ~= 'weapon_simfillerpistol' or Dist >= 280 then + ply:StripWeapon( 'weapon_simfillerpistol' ) + ply.gas_InUse = false + self:Disable() + end + else + ply.gas_InUse = false + self:Disable() + end + end + else + ply.gas_InUse = false + self:Disable() + end + end + + return true +end + +function ENT:Disable() + self:SetUser( NULL ) + self:SetActive( false ) +end + +function ENT:OnRemove() + if self.sound then + self.sound:Stop() + end + + local ply = self:GetUser() + + if IsValid( ply ) then + ply.gas_InUse = false + if ply:Alive() then + if ply:HasWeapon( 'weapon_simfillerpistol' ) then + ply:StripWeapon( 'weapon_simfillerpistol' ) + end + end + end +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:PhysicsCollide( data, physobj ) +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump_diesel.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump_diesel.lua new file mode 100644 index 0000000..58bee73 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump_diesel.lua @@ -0,0 +1,302 @@ +AddCSLuaFile() + +ENT.Type = 'anim' +ENT.PrintName = 'gas pump (diesel)' +ENT.Category = 'simfphys' + +ENT.Spawnable = true +ENT.AdminOnly = true + +function ENT:SetupDataTables() + self:NetworkVar( 'Entity',0, 'User' ) + self:NetworkVar( 'Bool',0, 'Active' ) + self:NetworkVar( 'Float',0, 'FuelUsed' ) + + if SERVER then + self:NetworkVarNotify( 'Active', self.OnActiveChanged ) + end +end + +local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p +end + +if CLIENT then + local cable = Material( 'cable/cable2' ) + local colors = CFG.skinColors + + local function GetDigit( value ) + local fvalue = math.floor(value,0) + + local decimal = 1000 + (value - fvalue) * 1000 + + local digit1 = fvalue % 10 + local digit2 = (fvalue - digit1) % 100 + local digit3 = (fvalue - digit1 - digit2) % 1000 + + local digit4 = decimal % 10 + local digit5 = (decimal - digit4) % 100 + local digit6 = (decimal - digit4 - digit5) % 1000 + + local digits = { + [1] = math.Round(digit1,0), + [2] = math.Round(digit2 / 10,0), + [3] = math.Round(digit3 / 100,0), + [4] = math.Round(digit5 / 10,0), + [5] = math.Round(digit6 / 100,0), + } + return digits + end + + function ENT:Draw() + self:DrawModel() + + if LocalPlayer():GetPos():DistToSqr(self:GetPos()) > 350000 then return end + + local pos = self:LocalToWorld( Vector(10,0,45) ) + local ang = self:LocalToWorldAngles( Angle(0,90,90) ) + local ply = self:GetUser() + + local startPos = self:LocalToWorld( Vector(0.06,-17.77,55.48) ) + local p2 = self:LocalToWorld( Vector(8,-17.77,30) ) + local p3 = self:LocalToWorld( Vector(0,-20,30) ) + local endPos = self:LocalToWorld( Vector(0.06,-20.3,37) ) + + if IsValid( ply ) then + local id = ply:LookupAttachment('anim_attachment_rh') + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + endPos = (attachment.Pos + attachment.Ang:Forward() * -3 + attachment.Ang:Right() * 2 + attachment.Ang:Up() * -3.5) + p3 = endPos + attachment.Ang:Right() * 5 - attachment.Ang:Up() * 20 + end + + for i = 1,15 do + local active = IsValid( ply ) + + local de = active and 1 or 2 + + if (not active and i > 1) or active then + + local sp = bezier(startPos, p2, p3, endPos, (i - de) / 15) + local ep = bezier(startPos, p2, p3, endPos, i / 15) + + render.SetMaterial( cable ) + render.DrawBeam( sp, ep, 2, 1, 1, Color( 100, 100, 100, 255 ) ) + end + end + + cam.Start3D2D( self:LocalToWorld( Vector(10,0,45) ), self:LocalToWorldAngles( Angle(0,90,90) ), 0.1 ) + draw.NoTexture() + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawRect( -150, -120, 300, 240 ) + + draw.RoundedBox( 5, -130, -110, 260, 200, Color( 170,119,102 ) ) + draw.RoundedBox( 5, -128, -108, 256, 196, colors.bg ) + + -- draw.RoundedBox( 5, -92, -36, 184, 32, Color( 170,119,102, 150 ) ) + draw.RoundedBox( 5, -90, -14, 180, 28, Color( 238, 238, 238, 255 ) ) + draw.RoundedBox( 5, -88, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -68, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -48, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -23, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, -3, -12, 19, 24, Color( 255, 255, 255, 255 ) ) + draw.RoundedBox( 5, 23, -12, 66, 24, Color( 255, 255, 255, 255 ) ) + + -- draw.RoundedBox( 5, -91, -25, 182, 30, Color( 240, 200, 0, 150 ) ) + -- draw.RoundedBox( 5, -90, -24, 180, 28, Color( 50, 50, 50, 255 ) ) + -- draw.RoundedBox( 5, -88, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -68, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -48, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -28, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, -8, -22, 19, 24, Color( 0, 0, 0, 255 ) ) + -- draw.RoundedBox( 5, 12, -22, 76, 24, Color( 0, 0, 0, 255 ) ) + + draw.SimpleText( L.liters, 'simfphys_gaspump2', 55, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + -- draw.SimpleText( 'ГАЛЛОНЫ', 'simfphys_gaspump', 50, -20, Color( 0,0,0, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + local liter = self:GetFuelUsed() + -- local gallon = liter * 0.264172 + local l_digits = GetDigit( math.Round( liter, 2) ) + -- local g_digits = GetDigit( math.Round( gallon, 2) ) + + draw.SimpleText( l_digits[4], 'simfphys_gaspump2', 11, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[5], 'simfphys_gaspump2', -9, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( '.', 'simfphys_gaspump2', -21, -8, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[1], 'simfphys_gaspump2', -34, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[2], 'simfphys_gaspump2', -54, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[3], 'simfphys_gaspump2', -74, -10, Color( 0,0,0, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + + + -- draw.SimpleText( g_digits[4], 'simfphys_gaspump', 6, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[5], 'simfphys_gaspump', -14, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( ',', 'simfphys_gaspump', -26, -15, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[1], 'simfphys_gaspump', -34, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[2], 'simfphys_gaspump', -54, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + -- draw.SimpleText( g_digits[3], 'simfphys_gaspump', -74, -20, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + + draw.SimpleText( L.before_pay, 'simfphys_gaspump_note', 0, 50, Color( 238, 238, 238, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + draw.SimpleText( L.price_for_liter, 'simfphys_gaspump_note', 0, 65, Color( 238, 238, 238, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + draw.SimpleText( L.diesel, 'simfphys_gaspump', 0, -60, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + cam.End3D2D() + end + return +end + +function ENT:Use( ply ) + if not self:GetActive() then + if not ply.gas_InUse then + ply.usedFuel = 0 + ply.usedFuelMax = 0 + self:SetActive( true ) + self:SetUser( ply ) + ply:Give( 'weapon_simfillerpistol' ) + ply:SelectWeapon( 'weapon_simfillerpistol' ) + ply.gas_InUse = true + + local weapon = ply:GetActiveWeapon() + if IsValid( weapon ) and weapon:GetClass() == 'weapon_simfillerpistol' then + weapon:SetFuelType( FUELTYPE_DIESEL ) + end + + net.Start('dbg.fuelPurchase') + net.Send(ply) + end + else + if ply == self:GetUser() then + ply:StripWeapon( 'weapon_simfillerpistol' ) + self:SetActive( false ) + self:SetUser( NULL ) + ply.gas_InUse = false + end + end +end + +function ENT:OnActiveChanged( name, old, new) + if new == old then return end + + if new then + if self.sound then + self.sound:Stop() + self.sound = nil + end + self.sound = CreateSound(self, 'vehicles/crane/crane_idle_loop3.wav') + self.sound:PlayEx(0,0) + self.sound:ChangeVolume( 0.2,2 ) + self.sound:ChangePitch( 255,3 ) + if IsValid( self.PumpEnt ) then + self.PumpEnt:SetNoDraw( true ) + end + else + if IsValid( self.PumpEnt ) then + self.PumpEnt:SetNoDraw( false ) + end + + if self.sound then + self.sound:ChangeVolume( 0,2 ) + self.sound:ChangePitch( 0,3 ) + end + end +end + +function ENT:Initialize() + self:SetModel( 'models/props_wasteland/gaspump001a.mdl' ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.PumpEnt = ents.Create( 'prop_dynamic' ) + self.PumpEnt:SetModel( 'models/props_equipment/gas_pump_p13.mdl' ) + self.PumpEnt:SetPos( self:LocalToWorld( Vector(-0.2,-14.6,45.7) ) ) + self.PumpEnt:SetAngles( self:LocalToWorldAngles( Angle(-0.3,92.3,-0.1) ) ) + self.PumpEnt:SetMoveType( MOVETYPE_NONE ) + self.PumpEnt:Spawn() + self.PumpEnt:Activate() + self.PumpEnt:SetNotSolid( true ) + self.PumpEnt:DrawShadow( false ) + self.PumpEnt:SetParent( self ) + + local PObj = self:GetPhysicsObject() + if not IsValid( PObj ) then return end + + PObj:EnableMotion( false ) +end + +function ENT:Think() + self:NextThink( CurTime() + 0.5 ) + + local ply = self:GetUser() + if IsValid( ply ) then + self:SetFuelUsed( ply.usedFuel ) + + local Dist = (ply:GetPos() - self:GetPos()):Length() + + if ply:Alive() then + if ply:InVehicle() then + if ply:HasWeapon( 'weapon_simfillerpistol' ) then + ply:StripWeapon( 'weapon_simfillerpistol' ) + end + ply.gas_InUse = false + self:Disable() + else + if ply:HasWeapon( 'weapon_simfillerpistol' ) then + if not IsValid(ply:GetActiveWeapon()) or ply:GetActiveWeapon():GetClass() ~= 'weapon_simfillerpistol' or Dist >= 280 then + ply:StripWeapon( 'weapon_simfillerpistol' ) + ply.gas_InUse = false + self:Disable() + end + else + ply.gas_InUse = false + self:Disable() + end + end + else + ply.gas_InUse = false + self:Disable() + end + end + + return true +end + +function ENT:Disable() + self:SetUser( NULL ) + self:SetActive( false ) +end + +function ENT:OnRemove() + if self.sound then + self.sound:Stop() + end + + local ply = self:GetUser() + + if IsValid( ply ) then + ply.gas_InUse = false + if ply:Alive() then + if ply:HasWeapon( 'weapon_simfillerpistol' ) then + ply:StripWeapon( 'weapon_simfillerpistol' ) + end + end + end +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:PhysicsCollide( data, physobj ) +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump_electric.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump_electric.lua new file mode 100644 index 0000000..5a96649 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gaspump_electric.lua @@ -0,0 +1,281 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.PrintName = "gas pump (electric)" +ENT.Category = "simfphys" + +ENT.Spawnable = true +ENT.AdminOnly = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity",0, "User" ) + self:NetworkVar( "Bool",0, "Active" ) + self:NetworkVar( "Float",0, "FuelUsed" ) + + if SERVER then + self:NetworkVarNotify( "Active", self.OnActiveChanged ) + end +end + +local function bezier(p0, p1, p2, p3, t) + local e = p0 + t * (p1 - p0) + local f = p1 + t * (p2 - p1) + local g = p2 + t * (p3 - p2) + + local h = e + t * (f - e) + local i = f + t * (g - f) + + local p = h + t * (i - h) + + return p +end + +if CLIENT then + local cable = Material( "cable/cable2" ) + local electric_meme = Material( "conquest/energy_collector" ) + + local function GetDigit( value ) + local fvalue = math.floor(value,0) + + local decimal = 1000 + (value - fvalue) * 1000 + + local digit1 = fvalue % 10 + local digit2 = (fvalue - digit1) % 100 + local digit3 = (fvalue - digit1 - digit2) % 1000 + + local digit4 = decimal % 10 + local digit5 = (decimal - digit4) % 100 + local digit6 = (decimal - digit4 - digit5) % 1000 + + local digits = { + [1] = math.Round(digit1,0), + [2] = math.Round(digit2 / 10,0), + [3] = math.Round(digit3 / 100,0), + [4] = math.Round(digit5 / 10,0), + [5] = math.Round(digit6 / 100,0), + } + return digits + end + + function ENT:Draw() + self:DrawModel() + + if LocalPlayer():GetPos():DistToSqr(self:GetPos()) > 350000 then return end + + local pos = self:LocalToWorld( Vector(10,0,45) ) + local ang = self:LocalToWorldAngles( Angle(0,90,90) ) + local ply = self:GetUser() + + local startPos = self:LocalToWorld( Vector(0.06,-17.77,55.48) ) + local p2 = self:LocalToWorld( Vector(8,-17.77,30) ) + local p3 = self:LocalToWorld( Vector(0,-20,30) ) + local endPos = self:LocalToWorld( Vector(0.06,-20.3,37) ) + + if IsValid( ply ) then + local id = ply:LookupAttachment("anim_attachment_rh") + local attachment = ply:GetAttachment( id ) + + if not attachment then return end + + endPos = (attachment.Pos + attachment.Ang:Forward() * -3 + attachment.Ang:Right() * 2 + attachment.Ang:Up() * -3.5) + p3 = endPos + attachment.Ang:Right() * 5 - attachment.Ang:Up() * 20 + end + + for i = 1,15 do + local active = IsValid( ply ) + + local de = active and 1 or 2 + + if (not active and i > 1) or active then + + local sp = bezier(startPos, p2, p3, endPos, (i - de) / 15) + local ep = bezier(startPos, p2, p3, endPos, i / 15) + + render.SetMaterial( cable ) + render.DrawBeam( sp, ep, 2, 1, 1, Color( 100, 100, 100, 255 ) ) + end + end + + cam.Start3D2D( self:LocalToWorld( Vector(10,0,45) ), self:LocalToWorldAngles( Angle(0,90,90) ), 0.1 ) + draw.NoTexture() + surface.SetDrawColor( 0, 0, 0, 255 ) + surface.DrawRect( -150, -120, 300, 240 ) + + draw.RoundedBox( 5, -130, -110, 260, 200, Color(0,127,255,150) ) + draw.RoundedBox( 5, -129, -109, 258, 198, Color( 50, 50, 50, 255 ) ) + + draw.RoundedBox( 5, -91, -75, 182, 30, Color(0,127,255,150) ) + draw.RoundedBox( 5, -90, -74, 180, 28, Color( 50, 50, 50, 255 ) ) + draw.RoundedBox( 5, -88, -72, 19, 24, Color( 0, 0, 0, 255 ) ) + draw.RoundedBox( 5, -68, -72, 19, 24, Color( 0, 0, 0, 255 ) ) + draw.RoundedBox( 5, -48, -72, 19, 24, Color( 0, 0, 0, 255 ) ) + draw.RoundedBox( 5, -28, -72, 19, 24, Color( 0, 0, 0, 255 ) ) + draw.RoundedBox( 5, -8, -72, 19, 24, Color( 0, 0, 0, 255 ) ) + draw.RoundedBox( 5, 12, -72, 76, 24, Color( 0, 0, 0, 255 ) ) + + surface.SetDrawColor( 0,127,255 ) + surface.SetMaterial( electric_meme ) + surface.DrawTexturedRect( -35, -12, 70, 70 ) + + draw.SimpleText( "kW/h", "simfphys_gaspump", 50, -70, Color( 200, 200, 200, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + local kwh = self:GetFuelUsed() / 2 + local l_digits = GetDigit( math.Round( kwh, 2) ) + + draw.SimpleText( l_digits[4], "simfphys_gaspump", 6, -70, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[5], "simfphys_gaspump", -14, -70, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( ",", "simfphys_gaspump", -26, -65, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[1], "simfphys_gaspump", -34, -70, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[2], "simfphys_gaspump", -54, -70, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + draw.SimpleText( l_digits[3], "simfphys_gaspump", -74, -70, Color( 200, 200, 200, 255 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP ) + + draw.SimpleText( "ELECTRIC", "simfphys_gaspump", 0, -100, Color(0,127,255,150), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP ) + + cam.End3D2D() + end + return +end + +function ENT:Use( ply ) + if not self:GetActive() then + if not ply.gas_InUse then + ply.usedFuel = 0 + self:SetActive( true ) + self:SetUser( ply ) + ply:Give( "weapon_simfillerpistol" ) + ply:SelectWeapon( "weapon_simfillerpistol" ) + ply.gas_InUse = true + + local weapon = ply:GetActiveWeapon() + if IsValid( weapon ) and weapon:GetClass() == "weapon_simfillerpistol" then + weapon:SetFuelType( FUELTYPE_ELECTRIC ) + end + end + else + if ply == self:GetUser() then + ply:StripWeapon( "weapon_simfillerpistol" ) + self:SetActive( false ) + self:SetUser( NULL ) + ply.gas_InUse = false + end + end +end + +function ENT:OnActiveChanged( name, old, new) + if new == old then return end + + if new then + if self.sound then + self.sound:Stop() + self.sound = nil + end + self.sound = CreateSound(self, "npc/scanner/combat_scan_loop6.wav") + self.sound:PlayEx(0,0) + self.sound:ChangeVolume( 0.4,1 ) + self.sound:ChangePitch( 130,2 ) + if IsValid( self.PumpEnt ) then + self.PumpEnt:SetNoDraw( true ) + end + else + if IsValid( self.PumpEnt ) then + self.PumpEnt:SetNoDraw( false ) + end + + if self.sound then + self.sound:ChangeVolume( 0,2 ) + self.sound:ChangePitch( 0,3 ) + end + end +end + +function ENT:Initialize() + self:SetModel( "models/props_wasteland/gaspump001a.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.PumpEnt = ents.Create( "prop_dynamic" ) + self.PumpEnt:SetModel( "models/props_equipment/gas_pump_p13.mdl" ) + self.PumpEnt:SetPos( self:LocalToWorld( Vector(-0.2,-14.6,45.7) ) ) + self.PumpEnt:SetAngles( self:LocalToWorldAngles( Angle(-0.3,92.3,-0.1) ) ) + self.PumpEnt:SetMoveType( MOVETYPE_NONE ) + self.PumpEnt:Spawn() + self.PumpEnt:Activate() + self.PumpEnt:SetNotSolid( true ) + self.PumpEnt:DrawShadow( false ) + self.PumpEnt:SetParent( self ) + + local PObj = self:GetPhysicsObject() + if not IsValid( PObj ) then return end + + PObj:EnableMotion( false ) +end + +function ENT:Think() + if CLIENT then return end + + self:NextThink( CurTime() + 0.5 ) + + local ply = self:GetUser() + if IsValid( ply ) then + self:SetFuelUsed( ply.usedFuel ) + + local Dist = (ply:GetPos() - self:GetPos()):Length() + + if ply:Alive() then + if ply:InVehicle() then + if ply:HasWeapon( "weapon_simfillerpistol" ) then + ply:StripWeapon( "weapon_simfillerpistol" ) + end + ply.gas_InUse = false + self:Disable() + else + if ply:HasWeapon( "weapon_simfillerpistol" ) then + if not IsValid(ply:GetActiveWeapon()) or ply:GetActiveWeapon():GetClass() ~= "weapon_simfillerpistol" or Dist >= 200 then + ply:StripWeapon( "weapon_simfillerpistol" ) + ply.gas_InUse = false + self:Disable() + end + else + ply.gas_InUse = false + self:Disable() + end + end + else + ply.gas_InUse = false + self:Disable() + end + end + + return true +end + +function ENT:Disable() + self:SetUser( NULL ) + self:SetActive( false ) +end + +function ENT:OnRemove() + if self.sound then + self.sound:Stop() + end + + local ply = self:GetUser() + + if IsValid( ply ) then + ply.gas_InUse = false + if ply:Alive() then + if ply:HasWeapon( "weapon_simfillerpistol" ) then + ply:StripWeapon( "weapon_simfillerpistol" ) + end + end + end +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:PhysicsCollide( data, physobj ) +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gib.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gib.lua new file mode 100644 index 0000000..f6d21b6 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_gib.lua @@ -0,0 +1,139 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +if CLIENT then + local Mat = CreateMaterial("simfphysdamage", "VertexLitGeneric", {["$basetexture"] = "models/player/player_chrome1"}) + + function ENT:Draw() + self:DrawModel() + + render.ModelMaterialOverride( Mat ) + render.SetBlend( 0.8 ) + self:DrawModel() + + render.ModelMaterialOverride() + render.SetBlend(1) + end + net.Receive("simfphys_explosion_fx", function(length) + local self = net.ReadEntity() + if IsValid( self ) then + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "simfphys_explosion", effectdata ) + end + end) +end + +if SERVER then + util.AddNetworkString( "simfphys_explosion_fx" ) + + function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:GetPhysicsObject():EnableMotion(true) + self:GetPhysicsObject():Wake() + self:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + + timer.Simple( 0.05, function() + if not IsValid( self ) then return end + if self.MakeSound == true then + net.Start( "simfphys_explosion_fx" ) + net.WriteEntity( self ) + net.Broadcast() + + util.ScreenShake( self:GetPos(), 50, 50, 1.5, 700 ) + util.BlastDamage( self, Entity(0), self:GetPos(), 300,200 ) + + local Light = ents.Create( "light_dynamic" ) + Light:SetPos( self:GetPos() + Vector( 0, 0, 10 ) ) + local Lightpos = Light:GetPos() + Vector( 0, 0, 10 ) + Light:SetPos( Lightpos ) + Light:SetKeyValue( "_light", "220 40 0 255" ) + Light:SetKeyValue( "style", 1) + Light:SetKeyValue( "distance", 255 ) + Light:SetKeyValue( "brightness", 2 ) + Light:SetParent( self ) + Light:Spawn() + Light:Fire( "TurnOn", "", "0" ) + + timer.Simple( 0.7, function() + if not IsValid( self ) then return end + + self.particleeffect = ents.Create( "info_particle_system" ) + self.particleeffect:SetKeyValue( "effect_name" , "fire_large_01") + self.particleeffect:SetKeyValue( "start_active" , 1) + self.particleeffect:SetOwner( self ) + self.particleeffect:SetPos( self:LocalToWorld( self:GetPhysicsObject():GetMassCenter() + Vector(0,0,15) ) ) + self.particleeffect:SetAngles( self:GetAngles() ) + self.particleeffect:Spawn() + self.particleeffect:Activate() + self.particleeffect:SetParent( self ) + + self.FireSound = CreateSound(self, "ambient/fire/firebig.wav") + self.FireSound:Play() + end) + + timer.Simple( 120, function() + if not IsValid( self ) then return end + + if IsValid( Light ) then + Light:Remove() + end + + if IsValid( self.particleeffect ) then + self.particleeffect:Remove() + end + + if self.FireSound then + self.FireSound:Stop() + end + end) + elseif not self.NoFire then + self.particleeffect = ents.Create( "info_particle_system" ) + self.particleeffect:SetKeyValue( "effect_name" , "fire_small_03") + self.particleeffect:SetKeyValue( "start_active" , 1) + self.particleeffect:SetOwner( self ) + self.particleeffect:SetPos( self:LocalToWorld( self:GetPhysicsObject():GetMassCenter() ) ) + self.particleeffect:SetAngles( self:GetAngles() ) + self.particleeffect:Spawn() + self.particleeffect:Activate() + self.particleeffect:SetParent( self ) + self.particleeffect:Fire( "Stop", "", math.random(0.5,3) ) + end + + end) + + self.RemoveDis = GetConVar("sv_simfphys_gib_lifetime"):GetFloat() + self.RemoveTimer = CurTime() + self.RemoveDis + end + + function ENT:Think() + if self.RemoveTimer < CurTime() then + if self.RemoveDis > 0 then + self:Remove() + end + end + + self:NextThink( CurTime() + 0.2 ) + return true + end + + function ENT:OnRemove() + if self.FireSound then + self.FireSound:Stop() + end + end + + function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) + end + + function ENT:PhysicsCollide( data, physobj ) + end +end diff --git a/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_wheel.lua b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_wheel.lua new file mode 100644 index 0000000..917308d --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/entities/gmod_sent_vehicle_fphysics_wheel.lua @@ -0,0 +1,537 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.DoNotDuplicate = true + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 1, "OnGround" ) + self:NetworkVar( "String", 2, "SurfaceMaterial" ) + self:NetworkVar( "Float", 3, "Speed" ) + self:NetworkVar( "Float", 4, "SkidSound" ) + self:NetworkVar( "Float", 5, "GripLoss" ) + self:NetworkVar( "Bool", 1, "Damaged" ) + self:NetworkVar( "Entity", 1, "BaseEnt" ) + + if SERVER then + self:NetworkVarNotify( "Damaged", self.OnDamaged ) + end +end + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/props_vehicles/tire001c_car.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:SetUseType( SIMPLE_USE ) + + self:DrawShadow( false ) + + self.OldMaterial = "" + self.OldMaterial2 = "" + self.OldVar = 0 + self.OldVar2 = 0 + + local Color = self:GetColor() + local dot = Color.r * Color.g * Color.b * Color.a + self.OldColor = dot + + timer.Simple( 0.01, function() + if not IsValid( self ) then return end + + self.WheelDust = ents.Create( "info_particle_system" ) + self.WheelDust:SetKeyValue( "effect_name" , "WheelDust") + self.WheelDust:SetKeyValue( "start_active" , 0) + self.WheelDust:SetOwner( self ) + self.WheelDust:SetPos( self:GetPos() + Vector(0,0,-self:BoundingRadius() * 0.4) ) + self.WheelDust:SetAngles( self:GetAngles() ) + self.WheelDust:Spawn() + self.WheelDust:Activate() + self.WheelDust:SetParent( self ) + self.WheelDust.DoNotDuplicate = true + simfphys.SetOwner( self.EntityOwner, self.WheelDust ) + end) + + self.snd_roll = "simulated_vehicles/sfx/concrete_roll.wav" + self.snd_roll_dirt = "simulated_vehicles/sfx/dirt_roll.wav" + self.snd_roll_grass = "simulated_vehicles/sfx/grass_roll.wav" + + self.snd_skid = "simulated_vehicles/sfx/concrete_skid.wav" + self.snd_skid_dirt = "simulated_vehicles/sfx/dirt_skid.wav" + self.snd_skid_grass = "simulated_vehicles/sfx/grass_skid.wav" + + self.RollSound = CreateSound(self, self.snd_roll) + self.RollSound_Dirt = CreateSound(self, self.snd_roll_dirt) + self.RollSound_Grass = CreateSound(self, self.snd_roll_grass) + + self.Skid = CreateSound(self, self.snd_skid) + self.Skid_Dirt = CreateSound(self, self.snd_skid_dirt) + self.Skid_Grass = CreateSound(self, self.snd_skid_grass) + end + + function ENT:Use( ply ) + local base = self:GetBaseEnt() + if not IsValid( base ) then return end + + base:SetPassenger( ply ) + end + + function ENT:Think() + if self.GhostEnt then + local Color = self:GetColor() + local dot = Color.r * Color.g * Color.b * Color.a + if dot ~= self.OldColor then + if IsValid( self.GhostEnt ) then + self.GhostEnt:SetColor( Color ) + self.GhostEnt:SetRenderMode( self:GetRenderMode() ) + end + self.OldColor = dot + end + end + + if self:GetDamaged() then + self:WheelFxBroken() + else + self:WheelFx() + end + + self:NextThink( CurTime() + 0.15 ) + return true + end + + function ENT:WheelFxBroken() + local ForwardSpeed = math.abs( self:GetSpeed() ) + local SkidSound = math.Clamp( self:GetSkidSound(),0,255) + local Speed = self:GetVelocity():Length() + local WheelOnGround = self:GetOnGround() + local EnableDust = (Speed * WheelOnGround > 200) + local Material = self:GetSurfaceMaterial() + local GripLoss = self:GetGripLoss() + + if EnableDust ~= self.OldVar then + self.OldVar = EnableDust + if EnableDust then + if Material == "grass" then + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Start" ) + end + elseif Material == "dirt" or Material == "sand" then + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Start" ) + end + end + else + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Stop" ) + end + end + end + + if EnableDust then + if Material ~= self.OldMaterial then + if Material == "grass" then + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Start" ) + end + elseif Material == "dirt" or Material == "sand" then + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Start" ) + end + else + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Stop" ) + end + end + self.OldMaterial = Material + end + end + + if self.RollSound_Broken then + local Volume = math.Clamp(SkidSound * 0.5 + ForwardSpeed / 1500,0,1) * WheelOnGround + local PlaySound = Volume > 0.1 + + self.OldPlaySound = self.OldPlaySound or false + if PlaySound ~= self.OldPlaySound then + self.OldPlaySound = PlaySound + if PlaySound then + self.RollSound_Broken:PlayEx(0,0) + else + self.RollSound_Broken:Stop() + end + end + + + self.RollSound_Broken:ChangeVolume( Volume ) + self.RollSound_Broken:ChangePitch(100 + math.Clamp((ForwardSpeed - 100) / 250 + (SkidSound * Speed / 800) + GripLoss * 22,0,155)) + end + end + + function ENT:WheelFx() + local ForwardSpeed = math.abs( self:GetSpeed() ) + local SkidSound = math.Clamp( self:GetSkidSound(),0,255) + local Speed = self:GetVelocity():Length() + local WheelOnGround = self:GetOnGround() + local EnableDust = (Speed * WheelOnGround > 200) + local Material = self:GetSurfaceMaterial() + local GripLoss = self:GetGripLoss() + + if EnableDust ~= self.OldVar then + self.OldVar = EnableDust + if EnableDust then + if Material == "grass" then + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Start" ) + end + self.RollSound_Grass = CreateSound(self, self.snd_roll_grass) + self.RollSound_Grass:PlayEx(0,0) + self.RollSound_Dirt:Stop() + self.RollSound:Stop() + elseif Material == "dirt" or Material == "sand" then + if IsValid( self.WheelDust ) then + self.WheelDust:Fire( "Start" ) + end + self.RollSound_Dirt = CreateSound(self, self.snd_roll_dirt) + self.RollSound_Dirt:PlayEx(0,0) + self.RollSound_Grass:Stop() + self.RollSound:Stop() + else + self.RollSound_Grass:Stop() + self.RollSound_Dirt:Stop() + self.RollSound = CreateSound(self, self.snd_roll) + self.RollSound:PlayEx(0,0) + end + else + if IsValid( self.WheelDust )then + self.WheelDust:Fire( "Stop" ) + end + self.RollSound:Stop() + self.RollSound_Grass:Stop() + self.RollSound_Dirt:Stop() + end + end + + if EnableDust then + if Material ~= self.OldMaterial then + if Material == "grass" then + if IsValid(self.WheelDust) then + self.WheelDust:Fire( "Start" ) + end + self.RollSound_Grass = CreateSound(self, self.snd_roll_grass) + self.RollSound_Grass:PlayEx(0,0) + self.RollSound_Dirt:Stop() + self.RollSound:Stop() + + elseif Material == "dirt" or Material == "sand" then + if IsValid(self.WheelDust) then + self.WheelDust:Fire( "Start" ) + end + self.RollSound_Grass:Stop() + self.RollSound_Dirt = CreateSound(self, self.snd_roll_dirt) + self.RollSound_Dirt:PlayEx(0,0) + self.RollSound:Stop() + else + if IsValid(self.WheelDust) then + self.WheelDust:Fire( "Stop" ) + end + self.RollSound_Grass:Stop() + self.RollSound_Dirt:Stop() + self.RollSound = CreateSound(self, self.snd_roll) + self.RollSound:PlayEx(0,0) + end + self.OldMaterial = Material + end + + if Material == "grass" then + self.RollSound_Grass:ChangeVolume(math.Clamp((ForwardSpeed - 100) / 1600,0,1), 0) + self.RollSound_Grass:ChangePitch(80 + math.Clamp((ForwardSpeed - 100) / 250,0,255), 0) + elseif Material == "dirt" or Material == "sand" then + self.RollSound_Dirt:ChangeVolume(math.Clamp((ForwardSpeed - 100) / 1600,0,1), 0) + self.RollSound_Dirt:ChangePitch(80 + math.Clamp((ForwardSpeed - 100) / 250,0,255), 0) + else + self.RollSound:ChangeVolume(math.Clamp((ForwardSpeed - 100) / 1500,0,1), 0) + self.RollSound:ChangePitch(100 + math.Clamp((ForwardSpeed - 400) / 200,0,255), 0) + end + end + + + if WheelOnGround ~= self.OldVar2 then + self.OldVar2 = WheelOnGround + if WheelOnGround == 1 then + if Material == "grass" or Material == "snow" then + self.Skid:Stop() + self.Skid_Grass = CreateSound(self, self.snd_skid_grass) + self.Skid_Grass:PlayEx(0,0) + self.Skid_Dirt:Stop() + elseif Material == "dirt" or Material == "sand" then + self.Skid_Grass:Stop() + self.Skid_Dirt = CreateSound(self, self.snd_skid_dirt) + self.Skid_Dirt:PlayEx(0,0) + self.Skid:Stop() + elseif Material == "ice" then + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + self.Skid:Stop() + else + self.Skid = CreateSound(self, self.snd_skid) + self.Skid:PlayEx(0,0) + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + end + else + self.Skid:Stop() + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + end + end + + if WheelOnGround == 1 then + if Material ~= self.OldMaterial2 then + if Material == "grass" or Material == "snow" then + self.Skid:Stop() + self.Skid_Grass = CreateSound(self, self.snd_skid_grass) + self.Skid_Grass:PlayEx(0,0) + self.Skid_Dirt:Stop() + + elseif Material == "dirt" or Material == "sand" then + self.Skid:Stop() + self.Skid_Grass:Stop() + self.Skid_Dirt = CreateSound(self, self.snd_skid_dirt) + self.Skid_Dirt:PlayEx(0,0) + elseif Material == "ice" then + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + self.Skid:Stop() + else + self.Skid = CreateSound(self, self.snd_skid) + self.Skid:PlayEx(0,0) + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + end + self.OldMaterial2 = Material + end + + if Material == "grass" or Material == "snow" then + self.Skid_Grass:ChangeVolume( math.Clamp(SkidSound,0,1) * (self.inside and 0.25 or 1) ) + self.Skid_Grass:ChangePitch(math.min(90 + (SkidSound * Speed / 500),150)) + elseif Material == "dirt" or Material == "sand" then + self.Skid_Dirt:ChangeVolume( math.Clamp(SkidSound,0,1) * 0.8 * (self.inside and 0.25 or 1)) + self.Skid_Dirt:ChangePitch(math.min(120 + (SkidSound * Speed / 500),150)) + else + self.Skid:ChangeVolume( math.Clamp(SkidSound * 0.5,0,1) * (self.inside and 0.25 or 1) ) + self.Skid:ChangePitch(math.min(85 + (SkidSound * Speed / 800) + GripLoss * 22,150)) + end + end + end + + function ENT:OnRemove() + self.RollSound_Grass:Stop() + self.RollSound_Dirt:Stop() + self.RollSound:Stop() + + self.Skid:Stop() + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + + if self.RollSound_Broken then + self.RollSound_Broken:Stop() + end + if self.PreBreak then + self.PreBreak:Stop() + end + end + + function ENT:PhysicsCollide( data, physobj ) + if data.Speed > 100 and data.DeltaTime > 0.2 then + if data.Speed > 400 then + self:EmitSound( "Rubber_Tire.ImpactHard" ) + self:EmitSound( "simulated_vehicles/suspension_creak_".. math.random(1,6) ..".ogg" ) + else + self:EmitSound( "Rubber.ImpactSoft" ) + end + end + end + + function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) + + if self:GetDamaged() or not simfphys.DamageEnabled then return end + + local Damage = dmginfo:GetDamage() + local DamagePos = dmginfo:GetDamagePosition() + local Type = dmginfo:GetDamageType() + local BaseEnt = self:GetBaseEnt() + + if TYPE == DMG_BLAST then return end -- no tirepopping on explosions + + if IsValid(BaseEnt) then + if BaseEnt:GetBulletProofTires() then return end + + if Damage > 1 then + if not self.PreBreak then + self.PreBreak = CreateSound(self, "ambient/gas/cannister_loop.wav") + self.PreBreak:PlayEx(0.5,100) + + timer.Simple(math.Rand(0.5,5), function() + if IsValid(self) and not self:GetDamaged() then + self:SetDamaged( true ) + if self.PreBreak then + self.PreBreak:Stop() + self.PreBreak = nil + end + end + end) + else + self:SetDamaged( true ) + self.PreBreak:Stop() + self.PreBreak = nil + end + end + end + end + + function ENT:OnDamaged( name, old, new) + if new == old then return end + + if new == true then + self.dRadius = self:BoundingRadius() * 0.28 + + self:EmitSound( "simulated_vehicles/sfx/tire_break.ogg" ) + + if IsValid(self.GhostEnt) then + self.GhostEnt:SetParent( nil ) + self.GhostEnt:GetPhysicsObject():EnableMotion( false ) + self.GhostEnt:SetPos( self:LocalToWorld( Vector(0,0,-self.dRadius) ) ) + self.GhostEnt:SetParent( self ) + end + + self.Skid:Stop() + self.Skid_Grass:Stop() + self.Skid_Dirt:Stop() + + self.RollSound:Stop() + self.RollSound_Grass:Stop() + self.RollSound_Dirt:Stop() + + self.RollSound_Broken = CreateSound(self, "simulated_vehicles/sfx/tire_damaged.wav") + else + if IsValid( self.GhostEnt ) then + self.GhostEnt:SetParent( nil ) + self.GhostEnt:GetPhysicsObject():EnableMotion( false ) + self.GhostEnt:SetPos( self:LocalToWorld( Vector(0,0,0) ) ) + self.GhostEnt:SetParent( self ) + end + + if self.RollSound_Broken then + self.RollSound_Broken:Stop() + end + end + + local BaseEnt = self:GetBaseEnt() + if IsValid( BaseEnt ) then + BaseEnt:SetSuspension( self.Index , new ) + end + end +end + +if CLIENT then + local maxDist = 4000000 + function ENT:Initialize() + self.FadeHeat = 0 + + timer.Simple( 0.01, function() + if not IsValid( self ) then return end + self.Radius = self:BoundingRadius() + end) + end + + function ENT:Think() + self:ManageSmoke() + self:NextThink(CurTime() + 0.05) + return true + end + + function ENT:ManageSmoke() + if self:GetPos():DistToSqr(EyePos()) > maxDist then return end + + local BaseEnt = self:GetBaseEnt() + if not IsValid( BaseEnt ) then return end + + local WheelOnGround = self:GetOnGround() + local GripLoss = self:GetGripLoss() + local Material = self:GetSurfaceMaterial() + + if WheelOnGround > 0 and (Material == "concrete" or Material == "rock" or Material == "tile") and GripLoss > 0 then + self.FadeHeat = math.Clamp( self.FadeHeat + GripLoss * 0.1,0,10) + else + self.FadeHeat = self.FadeHeat * 0.95 + end + + local Scale = self.FadeHeat ^ 3 / 1200 + local SmokeOn = (self.FadeHeat >= 7) + local DirtOn = GripLoss > 0.05 + local lcolor = BaseEnt:GetTireSmokeColor() * 255 + local Speed = self:GetVelocity():Length() + local OnRim = self:GetDamaged() + + local Forward = self:GetForward() + local Dir = (BaseEnt:GetGear() < 2) and Forward or -Forward + + local WheelSize = self.Radius or 0 + local Pos = self:GetPos() + + local ct = CurTime() + if ct >= (self.nextEffect or 0) then + self.nextEffect = ct + 0.02 + if SmokeOn and not OnRim then + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( Dir ) + effectdata:SetMagnitude( Scale ) + effectdata:SetRadius( WheelSize ) + effectdata:SetStart( Vector( lcolor.r, lcolor.g, lcolor.b ) ) + effectdata:SetEntity( NULL ) + util.Effect( "simfphys_tiresmoke", effectdata ) + end + + if WheelOnGround == 0 then return end + + if DirtOn then + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + effectdata:SetNormal( Dir ) + effectdata:SetMagnitude( GripLoss ) + effectdata:SetRadius( WheelSize ) + effectdata:SetEntity( self ) + util.Effect( "simfphys_tiresmoke", effectdata ) + end + + if (Speed > 150 or DirtOn) and OnRim then + self:MakeSparks( GripLoss, Dir, Pos, WheelSize ) + end + end + end + + function ENT:MakeSparks( Scale, Dir, Pos, WheelSize ) + self.NextSpark = self.NextSpark or 0 + + if self.NextSpark < CurTime() then + + self.NextSpark = CurTime() + 0.03 + local effectdata = EffectData() + effectdata:SetOrigin( Pos - Vector(0,0,WheelSize * 0.5) ) + effectdata:SetNormal( (Dir + Vector(0,0,0.5)) * Scale * 0.5) + util.Effect( "manhacksparks", effectdata, true, true ) + end + end + + function ENT:Draw() + return false + end + + function ENT:OnRemove() + end +end diff --git a/garrysmod/addons/feature-cars/lua/simfphys/anim.lua b/garrysmod/addons/feature-cars/lua/simfphys/anim.lua new file mode 100644 index 0000000..19c4336 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/anim.lua @@ -0,0 +1,85 @@ +hook.Add("CalcMainActivity", "simfphysSeatActivityOverride", function(ply) + local vehicle = ply:GetVehicle() + + if not IsValid( vehicle ) then return end + + if not vehicle.vehiclebase and not vehicle.dontcheckmeagainpls then + local parent = vehicle:GetParent() + + if IsValid( parent ) then + + if simfphys.IsCar( parent ) then + vehicle.vehiclebase = parent + vehicle.unlockmymemes = CurTime() + 0.25 + end + + vehicle.dontcheckmeagainpls = true + end + end + + if vehicle.unlockmymemes then + if vehicle.unlockmymemes > CurTime() then return end + end + + local vehiclebase = vehicle.vehiclebase + if not IsValid( vehiclebase ) then return end + + if ply.m_bWasNoclipping then + ply.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + + if CLIENT then + ply:SetIK( true ) + end + end + + local IsDriverSeat = vehicle == vehiclebase:GetDriverSeat() + + ply.CalcIdeal = ACT_HL2MP_SIT + ply.CalcSeqOverride = IsDriverSeat and ply:LookupSequence( "drive_jeep" ) or -1 + + if not IsDriverSeat and ply:GetAllowWeaponsInVehicle() and IsValid( ply:GetActiveWeapon() ) then + + local holdtype = ply:GetActiveWeapon():GetHoldType() + + if holdtype == "smg" then + holdtype = "smg1" + end + + local seqid = ply:LookupSequence( "sit_" .. holdtype ) + + if seqid ~= -1 then + ply.CalcSeqOverride = seqid + end + end + + return ply.CalcIdeal, ply.CalcSeqOverride +end) + +if CLIENT then + hook.Add("UpdateAnimation", "simfphysPoseparameters", function(ply , vel, seq) + local vehicle = ply:GetVehicle() + if not IsValid( vehicle ) then return end + + if vehicle.unlockmymemes then + if vehicle.unlockmymemes > CurTime() then return end + end + + local vehiclebase = vehicle.vehiclebase + if not IsValid( vehiclebase ) then return end + + local IsDriverSeat = vehicle == vehiclebase:GetDriverSeat() + if not IsDriverSeat then return end + + local Steer = vehiclebase:GetVehicleSteer() + ply.steerSmoothed = Lerp(0.3, ply.steerSmoothed or 0, Steer) + + ply:SetPoseParameter("vehicle_steer", ply.steerSmoothed) + ply:InvalidateBoneCache() + +-- GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + + return true + end) +end diff --git a/garrysmod/addons/feature-cars/lua/simfphys/attachments.lua b/garrysmod/addons/feature-cars/lua/simfphys/attachments.lua new file mode 100644 index 0000000..e090cac --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/attachments.lua @@ -0,0 +1,543 @@ +simfphys.attachments = { + + siren1 = { + type = 'siren', + mdl = 'models/octocar/police_siren.mdl', + }, + + siren2 = { + type = 'siren', + mdl = 'models/octocar/police_siren2.mdl', + }, + + siren3 = { + type = 'siren', + mdl = 'models/jaanus/wiretool/wiretool_siren.mdl', + name = 'Проблесковый маячок', mass = 5, volume = 12, + desc = 'Используется', + col = Color(0,0,127), + noPaint = true, + }, + + spoiler1 = { + type = 'spoiler', + name = L.spoiler1, mass = 5, volume = 12, + desc = L.desc_spoiler1, + mdl = 'models/octocar/attachable/spoiler1.mdl', + }, + + spoiler2 = { + type = 'spoiler', + mdl = 'models/octocar/attachable/spoiler2.mdl', + name = L.spoiler2, mass = 5, volume = 12, + desc = L.desc_spoiler2, + }, + + spoiler3 = { + type = 'spoiler', + mdl = 'models/octocar/attachable/spoiler3.mdl', + name = L.spoiler3, mass = 5, volume = 12, + desc = L.desc_spoiler3, + }, + + spoiler4 = { + type = 'spoiler', + mdl = 'models/octocar/attachable/spoiler4.mdl', + name = L.spoiler4, mass = 5, volume = 12, + desc = L.desc_spoiler4, + }, + + spoiler5 = { + type = 'spoiler', + mdl = 'models/octocar/attachable/spoiler5.mdl', + name = L.spoiler5, mass = 5, volume = 12, + desc = L.desc_spoiler5, + }, + + ladder = { + type = 'ladder', + noPaint = true, + mdl = 'models/octocar/attachable/ladder.mdl', + name = L.ladder, mass = 3, volume = 8, + desc = L.desc_ladder, + }, + + supercharger = { + type = 'supercharger', + mdl = 'models/octocar/attachable/supercharger.mdl', + name = L.supercharger, mass = 10, volume = 10, + desc = L.desc_supercharger, + }, + + huladoll = { + type = 'dash', + noPaint = true, + mdl = 'models/props_lab/huladoll.mdl', + name = L.huladoll, mass = 0.5, volume = 1, + desc = L.desc_huladoll, + }, + + snowman = { + type = 'dash', + noPaint = true, + mdl = 'models/unconid/xmas/snowman_u.mdl', + name = 'Снеговичок на приборку', mass = 0.5, volume = 1, + desc = 'Принесет новогоднее настроение в твой автомобиль', + }, + + skull = { + type = 'other1', + noPaint = true, + mdl = 'models/gibs/hgibs.mdl', + name = L.statuette_ermak, mass = 1, volume = 2, + desc = L.desc_statuette_ermak, + }, + + pumpkin = { + type = 'other2', + noPaint = true, + mdl = 'models/halloween2015/pumbkin_n_f01.mdl', + skin = 1, + scale = 0.2, + name = L.lamp_pumpkin, mass = 1, volume = 3, + desc = L.desc_lamp_pumpkin, + }, + + metalhook = { + type = 'other3', + noPaint = true, + mdl = 'models/props_junk/meathook001a.mdl', + name = L.car_hook, mass = 1, volume = 3, + desc = L.desc_car_hook, + }, + + doll = { + type = 'other4', + noPaint = true, + mdl = 'models/props_c17/doll01.mdl', + name = L.scary_doll, mass = 1, volume = 3, + desc = L.desc_scary_doll, + }, + + jar = { + type = 'other5', + noPaint = true, + mdl = 'models/props/spookington/eyejar.mdl', + name = 'Банка с глазами', mass = 3, volume = 1, + desc = 'Масса взрослого человеческого глаза равна 7-8г', + }, + + spider = { + type = 'other6', + noPaint = true, + mdl = 'models/props/spookington/spider_toy.mdl', + name = 'Паучок', mass = 0.7, volume = 1, + desc = 'Внутри забит поролоном, хотя... кто знает...', + }, + +} + +local attsByMdl = {} +for _, att in pairs(simfphys.attachments) do + attsByMdl[att.mdl] = att +end + +function simfphys.getAttByModel(mdl) + return attsByMdl[mdl] +end + +simfphys.rims = { + ['models/diggercars/250gto/250gto_wheel.mdl'] = { + price = 150000, + name = L.discs_hint .. 'Ferrari 250 GTO', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/356/porsche_550356_wheel.mdl'] = { + price = 155000, + name = L.discs_hint .. 'Porsche 356', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/914_6/porsche_914_wheel.mdl'] = { + price = 80000, + name = L.discs_hint .. 'Porsche 914', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/a_nsx97/wheel.mdl'] = { + price = 95000, + name = L.discs_hint .. 'Acura NSX 1997', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/alfa_romeo_montreal/wheel.mdl'] = { + price = 168000, + name = L.discs_hint .. 'Alfa Romeo Montreal', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/bmw_m5e28/wheel.mdl'] = { + price = 150000, + name = L.discs_hint .. 'BMW M5 E28', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_m5e39/wheel.mdl'] = { + price = 135000, + name = L.discs_hint .. 'BMW M5 E39', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_x5m/wheel.mdl'] = { + price = 120000, + name = L.discs_hint .. 'BMW X5 M', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmwm1/bmwm1_wheel.mdl'] = { + price = 160000, + name = L.discs_hint .. 'BMW M1', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/boxster03/wheel.mdl'] = { + price = 100000, + name = L.discs_hint .. 'Porsche Boxster 2003', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/civic94/wheel.mdl'] = { + price = 95000, + name = L.discs_hint .. 'Honda Civic 1994', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/fulvia/wheel.mdl'] = { + price = 145000, + name = L.discs_hint .. 'Lancia Fulvia', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/gt3 2004/wheel.mdl'] = { + price = 110000, + name = L.discs_hint .. 'Porsche GT3', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/h_nsxrgt/wheel.mdl'] = { + price = 98000, + name = L.discs_hint .. 'Honda NSX-R GT', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/legacy/wheel.mdl'] = { + price = 85000, + name = L.discs_hint .. 'Legacy', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/packcard/wheel.mdl'] = { + price = 190000, + name = L.discs_hint .. 'Packard', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/porsche_930/wheel.mdl'] = { + price = 175000, + name = L.discs_hint .. 'Porsche 930', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/porsche_930/wheelr.mdl'] = { + price = 170000, + name = L.discs_hint .. 'Porsche 930-2', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/991_carrera_s/wheel2.mdl'] = { + price = 125000, + name = L.discs_hint .. 'Porsche 991 Carrera S', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/porsche_930targa/wheel.mdl'] = { + price = 140000, + name = L.discs_hint .. 'Porsche 930 Targa', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/porsche_944/wheel.mdl'] = { + price = 110000, + name = L.discs_hint .. 'Porsche 944', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/shelbydaytonacoupe/wheel.mdl'] = { + price = 165000, + name = L.discs_hint .. 'Shelby Daytona', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/sportquattro/wheel.mdl'] = { + price = 115000, + name = L.discs_hint .. 'Audi Sport Quattro', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/stratos/wheel.mdl'] = { + price = 165000, + name = L.discs_hint .. 'Lancia Stratos', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/transam3/wheel.mdl'] = { + price = 195000, + name = L.discs_hint .. 'Firebird Transam3', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/a_nsx97/wheel2.mdl'] = { + price = 148000, + name = L.discs_hint .. 'Acura NSX 1997-2', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/a_nsx05/wheel.mdl'] = { + price = 125000, + name = L.discs_hint .. 'Acura NSX 2005', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/acura_rsxs/wheel.mdl'] = { + price = 135000, + name = L.discs_hint .. 'Acura RSXS', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/alfa/wheel.mdl'] = { + price = 110000, + name = L.discs_hint .. 'Alfa Romeo', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/ariel_atom/wheel.mdl'] = { + price = 190000, + name = L.discs_hint .. 'Ariel Atom', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_m5e34/wheel.mdl'] = { + price = 120000, + name = L.discs_hint .. 'BMW M5 E34', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_m5e60/wheel.mdl'] = { + price = 110000, + name = L.discs_hint .. 'BMW M5 E60', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_x5_09/wheel.mdl'] = { + price = 140000, + name = L.discs_hint .. 'BMW X5 2009', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_x5_09/wheel2.mdl'] = { + ang = Angle(0, 90, 0), + }, + ['models/diggercars/bmw_x6m/wheel.mdl'] = { + price = 140000, + name = L.discs_hint .. 'BMW X6 M', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/fiat500/wheel.mdl'] = { + price = 90000, + name = L.discs_hint .. 'Fiat 500', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/cortina/wheel.mdl'] = { + price = 85000, + name = L.discs_hint .. 'Ford Cortina', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/civic91/wheel.mdl'] = { + price = 95000, + name = L.discs_hint .. 'Honda Civic 1991', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/civic99/wheel.mdl'] = { + price = 135000, + name = L.discs_hint .. 'Honda Civic 1999', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/h_integra2000/wheel.mdl'] = { + price = 115000, + name = L.discs_hint .. 'Honda Integra 2000', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/h_integra/wheel.mdl'] = { + price = 125000, + name = L.discs_hint .. 'Honda Integra', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/h_nsxr/wheel.mdl'] = { + price = 160000, + name = L.discs_hint .. 'Honda NSX-R', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/aventador/wheel.mdl'] = { + price = 190000, + name = L.discs_hint .. 'Lamborghini Aventador', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/jalpa/wh1.mdl'] = { + price = 175000, + name = L.discs_hint .. 'Lamborghini Jalpa', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/300sl/wheel.mdl'] = { + price = 185000, + name = L.discs_hint .. 'Mercedes-Benz 300 SL', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/clkgtr/wheel.mdl'] = { + price = 160000, + name = L.discs_hint .. 'Mercedes-Benz CLK GTR', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/w123/mb_w123_wheel.mdl'] = { + price = 165000, + name = L.discs_hint .. 'Mercedes-Benz W123', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/opelspeedster/wheel.mdl'] = { + price = 175000, + name = L.discs_hint .. 'Opel Speedster', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/porsche_914/wheel.mdl'] = { + price = 150000, + name = L.discs_hint .. 'Porsche 914-2', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/959/porsche_959_wheel.mdl'] = { + price = 165000, + name = L.discs_hint .. 'Porsche 959', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/964speedster/porsche_964_wheel.mdl'] = { + price = 175000, + name = L.discs_hint .. 'Porsche 964', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/997 turbo/wheel.mdl'] = { + price = 170000, + name = L.discs_hint .. 'Porsche 997', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/carrera gt/wheel.mdl'] = { + price = 188000, + name = L.discs_hint .. 'Porsche Carrera GT', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/gt1sv/porsche_gt1_wheel.mdl'] = { + price = 165000, + name = L.discs_hint .. 'Porsche GT1', + ang = Angle(0, -90, 0), + }, + ['models/diggercars/saab99turbo/wheel.mdl'] = { + price = 175000, + name = L.discs_hint .. 'Saab 99', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/toyotagtone/wheel.mdl'] = { + price = 155000, + name = L.discs_hint .. 'Toyota GT-One', + ang = Angle(0, 90, 0), + }, + ['models/diggercars/vaz1111/oka_wheel.mdl'] = { + price = 50000, + name = L.discs_hint .. L.item_disc, + ang = Angle(0, -90, 0), + }, + ['models/hl2prewar/hatch/hatch_v2_wheel.mdl'] = { + price = 110000, + name = L.discs_hint .. L.item_disc2, + }, + ['models/hl2prewar/van/van_wheel.mdl'] = { + price = 22000, + name = L.discs_hint .. L.item_disc3, + }, + ['models/salza/trabant/trabant_wheel.mdl'] = { + price = 20000, + name = L.discs_hint .. L.item_disc4, + }, + ['models/salza/volga/volga_wheel.mdl'] = { + price = 15500, + name = L.discs_hint .. L.item_disc5, + }, + ['models/salza/zaz/zaz_wheel.mdl'] = { + price = 15000, + name = L.discs_hint .. L.item_disc6, + }, + ['models/salza/hatchback/hatchback_wheel.mdl'] = { + price = 12000, + name = L.discs_hint .. L.item_disc7, + ang = Angle(0, -90, 0), + }, + ['models/salza/avia/avia_wheel.mdl'] = { + price = 10000, + name = L.discs_hint .. L.item_disc8, + ang = Angle(0, 180, 0), + }, + ['models/salza/skoda_liaz/skoda_liaz_fwheel.mdl'] = { + price = 13500, + name = L.discs_hint .. L.item_disc9, + ang = Angle(0, 180, 0), + }, + ['models/props_phx/wheels/trucktire.mdl'] = { + price = 80000, + name = L.discs_hint .. L.item_disc10, + ang = Angle(90, 0, 0), + }, + ['models/props_phx/wheels/trucktire2.mdl'] = { + price = 175000, + name = L.discs_hint .. L.item_disc11, + ang = Angle(90, 0, 0), + }, + ['models/hl2prewar/car002/car_002_wheel.mdl'] = { + ang = Angle(0, 0, 0), + }, + ['models/hl2prewar/car005/car_005_wheel.mdl'] = { + ang = Angle(0, -90, 0), + }, + ['models/hl2prewar/hatch/hatch_v2_wheel.mdl'] = { + ang = Angle(0, 180, 0), + }, + ['models/hl2prewar/van/van_wheel.mdl'] = { + ang = Angle(0, 0, 0), + }, + ['models/salza/trabant/trabant_wheel.mdl'] = { + ang = Angle(0, 0, 0), + }, + ['models/salza/volga/volga_wheel.mdl'] = { + ang = Angle(0, -90, 0), + }, + ['models/salza/zaz/zaz_wheel.mdl'] = { + ang = Angle(0, -90, 0), + }, + ['models/props_vehicles/carparts_wheel01a.mdl'] = { + ang = Angle(0, -90, 0), + }, + ['models/salza/moskvich/moskvich_wheel.mdl'] = { + ang = Angle(0, 0, 0), + }, +} + +function simfphys.GetWheelAngle(model) + + if not model then return end + model = string.lower(model) + + local rim = simfphys.rims[model] + if rim then return rim.ang end + + local v_list = list.Get("simfphys_vehicles") + for listname, _ in pairs(v_list) do + if v_list[listname].Members.CustomWheels then + local FrontWheel = v_list[listname].Members.CustomWheelModel + local RearWheel = v_list[listname].Members.CustomWheelModel_R + + if FrontWheel then + FrontWheel = string.lower(FrontWheel) + end + + if RearWheel then + RearWheel = string.lower(RearWheel) + end + + if model == FrontWheel or model == RearWheel then + local Angleoffset = v_list[listname].Members.CustomWheelAngleOffset + if Angleoffset then + return Angleoffset + end + end + end + end + + local list = list.Get("simfphys_Wheels")[model] + local output = list and list.Angle + + return output or Angle() + +end diff --git a/garrysmod/addons/feature-cars/lua/simfphys/base_functions.lua b/garrysmod/addons/feature-cars/lua/simfphys/base_functions.lua new file mode 100644 index 0000000..bb8c5b9 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/base_functions.lua @@ -0,0 +1,441 @@ +CreateConVar("sv_simfphys_devmode", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"1 = enabled, 0 = disabled (requires a restart)") +CreateConVar("sv_simfphys_enabledamage", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"1 = enabled, 0 = disabled") +CreateConVar("sv_simfphys_gib_lifetime", "30", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"How many seconds before removing the gibs (0 = never remove)") +CreateConVar("sv_simfphys_playerdamage", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"should players take damage from collisions in vehicles?") +CreateConVar("sv_simfphys_damagemultiplicator", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"vehicle damage multiplicator") +CreateConVar("sv_simfphys_fuel", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"enable fuel? 1 = enabled, 0 = disabled") +CreateConVar("sv_simfphys_fuelscale", "0.1", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"fuel tank size multiplier. 1 = Realistic fuel tank size (about 2-3 hours of fullthrottle driving, Lol, have fun)") +CreateConVar("sv_simfphys_teampassenger", "0", {FCVAR_REPLICATED , FCVAR_ARCHIVE},"allow players of different teams to enter the same vehicle?, 0 = allow everyone, 1 = team only") + +simfphys = istable(simfphys) and simfphys or {} +simfphys.DamageEnabled = false +simfphys.DamageMul = 1 +simfphys.pDamageEnabled = false +simfphys.Fuel = true +simfphys.FuelMul = 0.1 + +FUELTYPE_NONE = 0 +FUELTYPE_PETROL = 1 +FUELTYPE_DIESEL = 2 +FUELTYPE_ELECTRIC = 3 + +game.AddParticles("particles/vehicle.pcf") +game.AddParticles("particles/fire_01.pcf") + +PrecacheParticleSystem("fire_large_01") +PrecacheParticleSystem("WheelDust") +PrecacheParticleSystem("smoke_gib_01") +PrecacheParticleSystem("burning_engine_01") + +cvars.AddChangeCallback("sv_simfphys_enabledamage", function(convar, oldValue, newValue) simfphys.DamageEnabled = (tonumber(newValue)~=0) end) +cvars.AddChangeCallback("sv_simfphys_damagemultiplicator", function(convar, oldValue, newValue) simfphys.DamageMul = tonumber(newValue) end) +cvars.AddChangeCallback("sv_simfphys_playerdamage", function(convar, oldValue, newValue) simfphys.pDamageEnabled = (tonumber(newValue)~=0) end) +cvars.AddChangeCallback("sv_simfphys_fuel", function(convar, oldValue, newValue) simfphys.Fuel = (tonumber(newValue)~=0) end) +cvars.AddChangeCallback("sv_simfphys_fuelscale", function(convar, oldValue, newValue) simfphys.FuelMul = tonumber(newValue) end) + +simfphys.DamageEnabled = GetConVar("sv_simfphys_enabledamage"):GetBool() +simfphys.DamageMul = GetConVar("sv_simfphys_damagemultiplicator"):GetFloat() +simfphys.pDamageEnabled = GetConVar("sv_simfphys_playerdamage"):GetBool() +simfphys.Fuel = GetConVar("sv_simfphys_fuel"):GetBool() +simfphys.FuelMul = GetConVar("sv_simfphys_fuelscale"):GetFloat() + +simfphys.ice = CreateConVar("sv_simfphys_traction_ice", "0.35", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.gmod_ice = CreateConVar("sv_simfphys_traction_gmod_ice", "0.1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.snow = CreateConVar("sv_simfphys_traction_snow", "0.7", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.slipperyslime = CreateConVar("sv_simfphys_traction_slipperyslime", "0.2", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.grass = CreateConVar("sv_simfphys_traction_grass", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.sand = CreateConVar("sv_simfphys_traction_sand", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.dirt = CreateConVar("sv_simfphys_traction_dirt", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.concrete = CreateConVar("sv_simfphys_traction_concrete", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.metal = CreateConVar("sv_simfphys_traction_metal", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.glass = CreateConVar("sv_simfphys_traction_glass", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.gravel = CreateConVar("sv_simfphys_traction_gravel", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.rock = CreateConVar("sv_simfphys_traction_rock", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) +simfphys.wood = CreateConVar("sv_simfphys_traction_wood", "1", {FCVAR_REPLICATED , FCVAR_ARCHIVE}) + +local carClasses = { + gmod_sent_vehicle_fphysics_base = true, + gmod_sent_vehicle_fphysics_wheel = true, +} +local noCollisionClasses = { + octoinv_item = true, + ent_dbg_workproblem = true, +} + +hook.Add('ShouldCollide', 'dbg-cars', function(ent1, ent2) + + local c1, c2 = ent1:GetClass(), ent2:GetClass() + if (carClasses[c1] or carClasses[c2]) and (ent1.APG_Ghosted or ent2.APG_Ghosted) then + return false + end + if (carClasses[c1] or carClasses[c2]) and (noCollisionClasses[c1] or noCollisionClasses[c2]) then + return false + end + if (carClasses[c1] or carClasses[c2]) and (c1 == 'prop_effect' or c2 == 'prop_effect') then + -- local effect = c1 == 'prop_effect' and ent1 or ent2 + -- local owner = effect:CPPIGetOwner() + -- if IsValid(owner) then badBoy(owner) end + return false + end + +end) + +function simfphys.IsCar(ent) + if not IsValid(ent) then return false end + + local IsVehicle = ent:GetClass():lower() == "gmod_sent_vehicle_fphysics_base" + + return IsVehicle +end + +if SERVER then + util.AddNetworkString("simfphys_settings") + util.AddNetworkString("simfphys_turnsignal") + + net.Receive("simfphys_turnsignal", function(length, ply) + local ent = net.ReadEntity() + local mode = net.ReadInt(32) + + if not IsValid(ent) or not ent.TurnSignal then return end + ent:TurnSignal(mode) + end) + + net.Receive("simfphys_settings", function(length, ply) + if not IsValid(ply) or not ply:IsSuperAdmin() then return end + + local dmgEnabled = tostring(net.ReadBool() and 1 or 0) + local giblifetime = tostring(net.ReadFloat()) + + local dmgMul = tostring(net.ReadFloat()) + local pdmgEnabled = tostring(net.ReadBool() and 1 or 0) + + local fuel = tostring(net.ReadBool() and 1 or 0) + local fuelscale = tostring(net.ReadFloat()) + + local newtraction = net.ReadTable() + + local teamonly = tostring(net.ReadBool() and 1 or 0) + + RunConsoleCommand("sv_simfphys_enabledamage", dmgEnabled) + RunConsoleCommand("sv_simfphys_gib_lifetime", giblifetime) + RunConsoleCommand("sv_simfphys_damagemultiplicator", dmgMul) + RunConsoleCommand("sv_simfphys_playerdamage", pdmgEnabled) + RunConsoleCommand("sv_simfphys_fuel", fuel) + RunConsoleCommand("sv_simfphys_fuelscale", fuelscale) + + RunConsoleCommand("sv_simfphys_teampassenger", teamonly) + + for k, v in pairs(newtraction) do + RunConsoleCommand("sv_simfphys_traction_"..k, v) + end + simfphys.UpdateFrictionData() + end) + + function simfphys.SpawnVehicleSimple(spawnname, pos, ang) + + if not isstring(spawnname) then print("invalid spawnname") return NULL end + if not isvector(pos) then print("invalid spawn position") return NULL end + if not isangle(ang) then print("invalid spawn angle") return NULL end + + local vehicle = list.Get("simfphys_vehicles")[ spawnname ] + + if not vehicle then print("vehicle \""..spawnname.."\" does not exist!") return NULL end + + local Ent = simfphys.SpawnVehicle(nil, pos, ang, vehicle.Model, vehicle.Class, spawnname, vehicle, true) + + return Ent + end + + function simfphys.SpawnVehicle(Player, Pos, Ang, Model, Class, VName, VTable, bNoOwner) + + if not bNoOwner then + if not gamemode.Call("PlayerSpawnVehicle", Player, Model, VName, VTable) then return end + end + + if not file.Exists(Model, "GAME") then + if IsValid(Player) then + Player:PrintMessage(HUD_PRINTTALK, "ERROR: \""..Model.."\" does not exist! (Class: "..VName..")") + end + ErrorNoHalt(Player, ' attempted to spawn vehicle ', VName, ' with invalid model ', Model) + return + end + + local Ent = ents.Create("gmod_sent_vehicle_fphysics_base") + if not Ent then return NULL end + + Ent:SetModel(Model) + Ent:SetAngles(Ang) + Ent:SetPos(Pos) + + Ent:Spawn() + Ent:Activate() + + Ent.VehicleName = VName + Ent.VehicleTable = VTable + Ent.EntityOwner = Player + Ent:SetSpawn_List(VName) + + if IsValid(Player) then + gamemode.Call("PlayerSpawnedVehicle", Player, Ent) + end + + if VTable.Members then + table.Merge(Ent, VTable.Members) + + if Ent.ModelInfo then + if Ent.ModelInfo.Bodygroups then + for i = 1, table.Count(Ent.ModelInfo.Bodygroups) do + Ent:SetBodygroup(i, Ent.ModelInfo.Bodygroups[i]) + end + end + + if Ent.ModelInfo.Skin then + Ent:SetSkin(Ent.ModelInfo.Skin) + end + + if Ent.ModelInfo.Color then + Ent:SetColor(Ent.ModelInfo.Color) + + local Color = Ent.ModelInfo.Color + local dot = Color.r * Color.g * Color.b * Color.a + Ent.OldColor = dot + + local data = { + Color = Color, + RenderMode = 0, + RenderFX = 0 + } + duplicator.StoreEntityModifier(Ent, "colour", data) + end + end + + Ent:SetTireSmokeColor(Vector(180,180,180) / 255) + + Ent.Turbocharged = Ent.Turbocharged or false + Ent.Supercharged = Ent.Supercharged or false + + Ent:SetEngineSoundPreset(Ent.EngineSoundPreset) + Ent:SetMaxTorque(Ent.PeakTorque) + + Ent:SetDifferentialGear(Ent.DifferentialGear) + + Ent:SetSteerSpeed(Ent.TurnSpeed) + Ent:SetFastSteerConeFadeSpeed(Ent.SteeringFadeFastSpeed) + Ent:SetFastSteerAngle(Ent.FastSteeringAngle) + + Ent:SetEfficiency(Ent.Efficiency) + Ent:SetMaxTraction(Ent.MaxGrip) + Ent:SetTractionBias(Ent.GripOffset / Ent.MaxGrip) + Ent:SetPowerDistribution(Ent.PowerBias) + + Ent:SetBackFire(Ent.Backfire or false) + Ent:SetDoNotStall(Ent.DoNotStall or false) + + Ent:SetIdleRPM(Ent.IdleRPM) + Ent:SetLimitRPM(Ent.LimitRPM) + Ent:SetRevlimiter(Ent.Revlimiter or false) + Ent:SetPowerBandEnd(Ent.PowerbandEnd) + Ent:SetPowerBandStart(Ent.PowerbandStart) + + Ent:SetTurboCharged(Ent.Turbocharged) + Ent:SetSuperCharged(Ent.Supercharged) + Ent:SetBrakePower(Ent.BrakePower) + + Ent:SetLights_List(Ent.LightsTable or "no_lights") + + Ent:SetBulletProofTires(Ent.BulletProofTires or false) + + Ent:SetBackfireSound(Ent.snd_backfire or "") + + duplicator.StoreEntityModifier(Ent, "VehicleMemDupe", VTable.Members) + end + + return Ent + end + + function simfphys.SetOwner(ply, ent) + if not IsValid(ent) or not IsValid(ply) then return end + + if CPPI then ent:CPPISetOwner(ply) end + ent.EntityOwner = ply + gamemode.Call("PlayerSpawnedVehicle", ply, ent) + end +end + +function simfphys.UpdateFrictionData() + simfphys.TractionData = {} + + timer.Simple(0.1,function() + simfphys.TractionData["ice"] = simfphys.ice:GetFloat() + simfphys.TractionData["gmod_ice"] = simfphys.gmod_ice:GetFloat() + simfphys.TractionData["snow"] = simfphys.snow:GetFloat() + simfphys.TractionData["slipperyslime"] = simfphys.slipperyslime:GetFloat() + simfphys.TractionData["grass"] = simfphys.grass:GetFloat() + simfphys.TractionData["sand"] = simfphys.sand:GetFloat() + simfphys.TractionData["dirt"] = simfphys.dirt:GetFloat() + simfphys.TractionData["concrete"] = simfphys.concrete:GetFloat() + simfphys.TractionData["metal"] = simfphys.metal:GetFloat() + simfphys.TractionData["glass"] = simfphys.glass:GetFloat() + simfphys.TractionData["gravel"] = simfphys.gravel:GetFloat() + simfphys.TractionData["rock"] = simfphys.rock:GetFloat() + simfphys.TractionData["wood"] = simfphys.wood:GetFloat() + end) +end +simfphys.UpdateFrictionData() + +simfphys.SoundPresets = { + { + "simulated_vehicles/gta5_dukes/dukes_idle.wav", + "simulated_vehicles/gta5_dukes/dukes_low.wav", + "simulated_vehicles/gta5_dukes/dukes_mid.wav", + "simulated_vehicles/gta5_dukes/dukes_revdown.wav", + "simulated_vehicles/gta5_dukes/dukes_second.wav", + "simulated_vehicles/gta5_dukes/dukes_second.wav", + 0.8, + 1, + 0.8 + }, + { + "simulated_vehicles/master_chris_charger69/charger_idle.wav", + "simulated_vehicles/master_chris_charger69/charger_low.wav", + "simulated_vehicles/master_chris_charger69/charger_mid.wav", + "simulated_vehicles/master_chris_charger69/charger_revdown.wav", + "simulated_vehicles/master_chris_charger69/charger_second.wav", + "simulated_vehicles/master_chris_charger69/charger_shiftdown.wav", + 0.75, + 0.9, + 0.95 + }, + { + "simulated_vehicles/shelby/shelby_idle.wav", + "simulated_vehicles/shelby/shelby_low.wav", + "simulated_vehicles/shelby/shelby_mid.wav", + "simulated_vehicles/shelby/shelby_revdown.wav", + "simulated_vehicles/shelby/shelby_second.wav", + "simulated_vehicles/shelby/shelby_shiftdown.wav", + 0.8, + 1, + 0.85 + }, + { + "simulated_vehicles/jeep/jeep_idle.wav", + "simulated_vehicles/jeep/jeep_low.wav", + "simulated_vehicles/jeep/jeep_mid.wav", + "simulated_vehicles/jeep/jeep_revdown.wav", + "simulated_vehicles/jeep/jeep_second.wav", + "simulated_vehicles/jeep/jeep_second.wav", + 0.9, + 1, + 1 + }, + { + "simulated_vehicles/v8elite/v8elite_idle.wav", + "simulated_vehicles/v8elite/v8elite_low.wav", + "simulated_vehicles/v8elite/v8elite_mid.wav", + "simulated_vehicles/v8elite/v8elite_revdown.wav", + "simulated_vehicles/v8elite/v8elite_second.wav", + "simulated_vehicles/v8elite/v8elite_second.wav", + 0.8, + 1, + 1 + }, + { + "simulated_vehicles/4banger/4banger_idle.wav", + "simulated_vehicles/4banger/4banger_low.wav", + "simulated_vehicles/4banger/4banger_mid.wav", + "simulated_vehicles/4banger/4banger_low.wav", + "simulated_vehicles/4banger/4banger_second.wav", + "simulated_vehicles/4banger/4banger_second.wav", + 0.8, + 0.9, + 1 + }, + { + "simulated_vehicles/jalopy/jalopy_idle.wav", + "simulated_vehicles/jalopy/jalopy_low.wav", + "simulated_vehicles/jalopy/jalopy_mid.wav", + "simulated_vehicles/jalopy/jalopy_revdown.wav", + "simulated_vehicles/jalopy/jalopy_second.wav", + "simulated_vehicles/jalopy/jalopy_shiftdown.wav", + 0.95, + 1.1, + 0.9 + }, + { + "simulated_vehicles/alfaromeo/alfaromeo_idle.wav", + "simulated_vehicles/alfaromeo/alfaromeo_low.wav", + "simulated_vehicles/alfaromeo/alfaromeo_mid.wav", + "simulated_vehicles/alfaromeo/alfaromeo_low.wav", + "simulated_vehicles/alfaromeo/alfaromeo_second.wav", + "simulated_vehicles/alfaromeo/alfaromeo_second.wav", + 0.65, + 0.8, + 1 + }, + { + "simulated_vehicles/generic1/generic1_idle.wav", + "simulated_vehicles/generic1/generic1_low.wav", + "simulated_vehicles/generic1/generic1_mid.wav", + "simulated_vehicles/generic1/generic1_revdown.wav", + "simulated_vehicles/generic1/generic1_second.wav", + "simulated_vehicles/generic1/generic1_second.wav", + 0.8, + 1.1, + 1 + }, + { + "simulated_vehicles/generic2/generic2_idle.wav", + "simulated_vehicles/generic2/generic2_low.wav", + "simulated_vehicles/generic2/generic2_mid.wav", + "simulated_vehicles/generic2/generic2_revdown.wav", + "simulated_vehicles/generic2/generic2_second.wav", + "simulated_vehicles/generic2/generic2_second.wav", + 1, + 1.1, + 1 + }, + { + "simulated_vehicles/generic3/generic3_idle.wav", + "simulated_vehicles/generic3/generic3_low.wav", + "simulated_vehicles/generic3/generic3_mid.wav", + "simulated_vehicles/generic3/generic3_revdown.wav", + "simulated_vehicles/generic3/generic3_second.wav", + "simulated_vehicles/generic3/generic3_second.wav", + 0.9, + 0.9, + 1 + }, + { + "simulated_vehicles/generic4/generic4_idle.wav", + "simulated_vehicles/generic4/generic4_low.wav", + "simulated_vehicles/generic4/generic4_mid.wav", + "simulated_vehicles/generic4/generic4_revdown.wav", + "simulated_vehicles/generic4/generic4_gear.wav", + "simulated_vehicles/generic4/generic4_shiftdown.wav", + 1, + 1.1, + 1 + }, + { + "simulated_vehicles/generic5/generic5_idle.wav", + "simulated_vehicles/generic5/generic5_low.wav", + "simulated_vehicles/generic5/generic5_mid.wav", + "simulated_vehicles/generic5/generic5_revdown.wav", + "simulated_vehicles/generic5/generic5_gear.wav", + "simulated_vehicles/generic5/generic5_gear.wav", + 0.7, + 0.7, + 1 + }, + { + "simulated_vehicles/gta5_gauntlet/gauntlet_idle.wav", + "simulated_vehicles/gta5_gauntlet/gauntlet_low.wav", + "simulated_vehicles/gta5_gauntlet/gauntlet_mid.wav", + "simulated_vehicles/gta5_gauntlet/gauntlet_revdown.wav", + "simulated_vehicles/gta5_gauntlet/gauntlet_gear.wav", + "simulated_vehicles/gta5_gauntlet/gauntlet_gear.wav", + 0.95, + 1.1, + 1 + } +} diff --git a/garrysmod/addons/feature-cars/lua/simfphys/base_lights.lua b/garrysmod/addons/feature-cars/lua/simfphys/base_lights.lua new file mode 100644 index 0000000..ce26d7b --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/base_lights.lua @@ -0,0 +1,105 @@ +local light_table = { + L_HeadLampPos = Vector(-42,148,21.1), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(42,148,21.1), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(45.6,-147,27.2), + L_RearLampAng = Angle(40,-90,0), + R_RearLampPos = Vector(-45.6,-147,27.2), + R_RearLampAng = Angle(40,-90,0), + + Headlight_sprites = { + Vector(-46.3,145.6,21.1), + Vector(46.3,145.6,21.1) + }, + Headlamp_sprites = { + Vector(-37.6,145.7,21), + Vector(37.6,145.7,21) + }, + Rearlight_sprites = { + Vector(45.6,-146.2,27.2), + Vector(-45.6,-146.2,27.2) + }, + Brakelight_sprites = { + Vector(45.6,-146.2,27.2), + Vector(-45.6,-146.2,27.2) + } +} +list.Set( "simfphys_lights", "conapc", light_table) + + +local light_table = { + L_HeadLampPos = Vector(32.7,79.5,29.0), + L_HeadLampAng = Angle(15,90,0), + R_HeadLampPos = Vector(-30.75,79.5,28.9), + R_HeadLampAng = Angle(15,90,0), + + L_RearLampPos = Vector(15.9,-139.2,53), + L_RearLampAng = Angle(40,-90,0), + R_RearLampPos = Vector(-17.44,-139.2,53), + R_RearLampAng = Angle(40,-90,0), + + Headlight_sprites = { + Vector(-34.5,77.5,29), + Vector(36.4,77.5,29.5), + Vector(-27.1,77.5,29), + Vector(29,77.5,29.5) + }, + Headlamp_sprites = { + {pos =Vector(-34.5,77.5,29),size = 60}, + {pos =Vector(36.4,77.5,29.5),size = 60}, + {pos =Vector(-27.1,77.5,29),size = 60}, + {pos =Vector(29,77.5,29.5),size = 60}, + }, + Rearlight_sprites = { + Vector(25.8,-139.2,53),Vector(24.28,-139.2,53),Vector(22.76,-139.2,53),Vector(21.24,-139.2,53),Vector(19.72,-139.2,53),Vector(18.2,-139.2,53),Vector(16.68,-139.2,53),Vector(15.16,-139.2,53),Vector(13.64,-139.2,53),Vector(12.12,-139.2,53),Vector(10.6,-139.2,53),Vector(9.08,-139.2,53),Vector(7.56,-139.2,53),Vector(6.04,-139.2,53), + Vector(-27.32,-139.2,53),Vector(-25.8,-139.2,53),Vector(-24.28,-139.2,53),Vector(-22.76,-139.2,53),Vector(-21.24,-139.2,53),Vector(-19.72,-139.2,53),Vector(-18.2,-139.2,53),Vector(-16.68,-139.2,53),Vector(-15.16,-139.2,53),Vector(-13.64,-139.2,53),Vector(-12.12,-139.2,53),Vector(-10.6,-139.2,53),Vector(-9.08,-139.2,53),Vector(-7.56,-139.2,53) + }, + Brakelight_sprites = { + Vector(25.8,-139.2,53),Vector(24.28,-139.2,53),Vector(22.76,-139.2,53),Vector(21.24,-139.2,53),Vector(19.72,-139.2,53),Vector(18.2,-139.2,53),Vector(16.68,-139.2,53),Vector(15.16,-139.2,53),Vector(13.64,-139.2,53),Vector(12.12,-139.2,53),Vector(10.6,-139.2,53),Vector(9.08,-139.2,53),Vector(7.56,-139.2,53),Vector(6.04,-139.2,53), + Vector(-27.32,-139.2,53),Vector(-25.8,-139.2,53),Vector(-24.28,-139.2,53),Vector(-22.76,-139.2,53),Vector(-21.24,-139.2,53),Vector(-19.72,-139.2,53),Vector(-18.2,-139.2,53),Vector(-16.68,-139.2,53),Vector(-15.16,-139.2,53),Vector(-13.64,-139.2,53),Vector(-12.12,-139.2,53),Vector(-10.6,-139.2,53),Vector(-9.08,-139.2,53),Vector(-7.56,-139.2,53) + }, + Turnsignal_sprites = { + Left = { + {pos =Vector(-34.5,77.5,29),size = 80,material = "sprites/light_ignorez",color = Color( 255, 200, 0, 200)}, + {pos =Vector(-34.5,77.5,29),size = 40,color = Color( 255, 200, 0, 200)}, + {pos = Vector(-34.73,-139.52,52.38),material = "sprites/light_ignorez",size = 40,color = Color( 255, 60, 0, 125)}, + {pos = Vector(-34.73,-139.52,52.38),size = 80,color = Color( 255, 0, 0, 90)}, + }, + Right = { + {pos =Vector(36,77.5,29),size = 80,material = "sprites/light_ignorez",color = Color( 255, 200, 0, 200)}, + {pos =Vector(36,77.5,29),size = 40,color = Color( 255, 200, 0, 200)}, + {pos = Vector(33.23,-139.52,52.38),material = "sprites/light_ignorez",size = 40,color = Color( 255, 60, 0, 125)}, + {pos = Vector(33.23,-139.52,52.38),size = 80,color = Color( 255, 0, 0, 90)}, + }, + }, +} +list.Set( "simfphys_lights", "jalopy", light_table) + + +local light_table = { + L_HeadLampPos = Vector(-11,55,35), + L_HeadLampAng = Angle(20,90,0), + R_HeadLampPos = Vector(11,55,35), + R_HeadLampAng = Angle(20,90,0), + + L_RearLampPos = Vector(-14.9,-99.9,39.13), + L_RearLampAng = Angle(40,-90,0), + + Headlight_sprites = { + Vector(-11,57,38.8), + Vector(11,57,38.8) + }, + Headlamp_sprites = { + Vector(-11,57,38.8), + Vector(11,57,38.8) + }, + Rearlight_sprites = { + Vector(-14.9,-101,39.13) + }, + Brakelight_sprites = { + Vector(-14.9,-101,39.1) + }, +} +list.Set( "simfphys_lights", "jeep", light_table) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/base_vehicles.lua b/garrysmod/addons/feature-cars/lua/simfphys/base_vehicles.lua new file mode 100644 index 0000000..8d6efd0 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/base_vehicles.lua @@ -0,0 +1,577 @@ +local V = { + Name = "HL2 Jeep", + Model = "models/buggy.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Half Life 2 / Synergy", + + Members = { + Mass = 1700, + + --OnTick = function(ent) print("hi") end, + --OnSpawn = function(ent) print("i spawned") end, + --OnDelete = function(ent) print("im removed :(") end, + --OnDestroyed = function(ent) print("im destroyed :((((") end, + + LightsTable = "jeep", + + FrontWheelRadius = 18, + RearWheelRadius = 20, + + CustomMassCenter = Vector(0,0,0), + + SeatOffset = Vector(0,0,-2), + SeatPitch = 0, + + SpeedoMax = 120, + + StrengthenSuspension = false, + + FrontHeight = 13.5, + FrontConstant = 27000, + FrontDamping = 2800, + FrontRelativeDamping = 2800, + + RearHeight = 13.5, + RearConstant = 32000, + RearDamping = 2900, + RearRelativeDamping = 2900, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 44, + Efficiency = 1.337, + GripOffset = 0, + BrakePower = 40, + + IdleRPM = 750, + LimitRPM = 6500, + + PeakTorque = 100, + PowerbandStart = 2200, + PowerbandEnd = 6300, + + FuelFillPos = Vector(17.64,-14.55,30.06), + + PowerBias = 0.5, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = "simulated_vehicles/jeep/jeep_idle.wav", + + snd_low = "simulated_vehicles/jeep/jeep_low.wav", + snd_low_revdown = "simulated_vehicles/jeep/jeep_revdown.wav", + snd_low_pitch = 0.9, + + snd_mid = "simulated_vehicles/jeep/jeep_mid.wav", + snd_mid_gearup = "simulated_vehicles/jeep/jeep_second.wav", + + snd_mid_pitch = 1, + + Sound_Idle = "simulated_vehicles/misc/nanjing_loop.wav", + Sound_IdlePitch = 1, + + Sound_Mid = "simulated_vehicles/misc/m50.wav", + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 58, + Sound_MidFadeOutRate = 0.476, + + Sound_High = "simulated_vehicles/misc/v8high2.wav", + Sound_HighPitch = 1, + Sound_HighVolume = 0.75, + Sound_HighFadeInRPMpercent = 58, + Sound_HighFadeInRate = 0.19, + + Sound_Throttle = "", + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_1.wav", + + DifferentialGear = 0.3, + Gears = {-0.15,0,0.15,0.25,0.35,0.45} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_jeep", V ) + + +local V = { + Name = "HL2 Combine APC", + Model = "models/combine_apc.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Half Life 2 / Synergy", + + Members = { + Mass = 3500, + MaxHealth = 3000, + + FrontWheelRadius = 28, + RearWheelRadius = 28, + + SeatOffset = Vector(-25,0,104), + SeatPitch = 0, + + PassengerSeats = { + }, + + FrontHeight = 10, + FrontConstant = 50000, + FrontDamping = 3000, + FrontRelativeDamping = 3000, + + RearHeight = 10, + RearConstant = 50000, + RearDamping = 3000, + RearRelativeDamping = 3000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 70, + Efficiency = 1.8, + GripOffset = 0, + BrakePower = 70, + BulletProofTires = true, + + IdleRPM = 750, + LimitRPM = 6000, + PeakTorque = 100, + PowerbandStart = 1500, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(32.82,-78.31,81.89), + + PowerBias = 0, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/c_apc/apc_idle.wav", + Sound_IdlePitch = 1, + + Sound_Mid = "simulated_vehicles/c_apc/apc_mid.wav", + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 100, + Sound_MidFadeOutRate = 1, + + Sound_High = "", + + Sound_Throttle = "", + + snd_horn = "ambient/alarms/apc_alarm_pass1.wav", + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.2,0.3} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_combineapc", V ) + + +local V = { + Name = "HL2:EP2 Jalopy", + Model = "models/vehicle.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Half Life 2 / Synergy", + + Members = { + Mass = 1700, + LightsTable = "jalopy", + + FrontWheelRadius = 18, + RearWheelRadius = 20, + + SeatOffset = Vector(-1,0,5), + SeatPitch = 3, + + PassengerSeats = { + { + pos = Vector(21,-22,21), + ang = Angle(0,0,9) + } + }, + + ExhaustPositions = { + { + pos = Vector(-21.63,-142.52,37.55), + ang = Angle(90,-90,0) + }, + { + pos = Vector(19.65,-144.09,38.03), + ang = Angle(90,-90,0) + } + }, + + FrontHeight = 11.5, + FrontConstant = 27000, + FrontDamping = 2800, + FrontRelativeDamping = 2800, + + RearHeight = 8.5, + RearConstant = 32000, + RearDamping = 2900, + RearRelativeDamping = 2900, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 45, + Efficiency = 1.22, + GripOffset = -0.5, + BrakePower = 50, + + IdleRPM = 750, + LimitRPM = 6000, + PeakTorque = 130, + PowerbandStart = 2200, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-39.07,-108.1,60.81), + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = "simulated_vehicles/jalopy/jalopy_idle.wav", + + snd_low = "simulated_vehicles/jalopy/jalopy_low.wav", + snd_low_revdown = "simulated_vehicles/jalopy/jalopy_revdown.wav", + snd_low_pitch = 0.95, + + snd_mid = "simulated_vehicles/jalopy/jalopy_mid.wav", + snd_mid_gearup = "simulated_vehicles/jalopy/jalopy_second.wav", + snd_mid_pitch = 1.1, + + Sound_Idle = "simulated_vehicles/jalopy/jalopy_idle.wav", + Sound_IdlePitch = 0.95, + + Sound_Mid = "simulated_vehicles/jalopy/jalopy_mid.wav", + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 55, + Sound_MidFadeOutRate = 0.25, + + Sound_High = "simulated_vehicles/jalopy/jalopy_high.wav", + Sound_HighPitch = 0.75, + Sound_HighVolume = 0.9, + Sound_HighFadeInRPMpercent = 55, + Sound_HighFadeInRate = 0.4, + + Sound_Throttle = "", + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + DifferentialGear = 0.3, + Gears = {-0.15,0,0.15,0.25,0.35,0.45} + } +} +if (file.Exists( "models/vehicle.mdl", "GAME" )) then + list.Set( "simfphys_vehicles", "sim_fphys_jalopy", V ) +end + + +local V = { + Name = "Driveable Couch", + Model = "models/props_c17/FurnitureCouch002a.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Base", + SpawnAngleOffset = 90, + + Members = { + Mass = 500, + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/props_phx/wheels/magnetic_small_base.mdl", + + CustomWheelPosFL = Vector(12,22,-15), + CustomWheelPosFR = Vector(12,-22,-15), + CustomWheelPosRL = Vector(-12,22,-15), + CustomWheelPosRR = Vector(-12,-22,-15), + CustomWheelAngleOffset = Angle(90,0,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-13.5,21), + SeatPitch = 15, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-14,-12), + ang = Angle(0,-90,0) + } + }, + + FrontHeight = 7, + FrontConstant = 12000, + FrontDamping = 400, + FrontRelativeDamping = 50, + + RearHeight = 7, + RearConstant = 12000, + RearDamping = 400, + RearRelativeDamping = 50, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 120, + + TurnSpeed = 8, + + MaxGrip = 20, + Efficiency = 1, + GripOffset = 0, + BrakePower = 5, + BulletProofTires = true, + + IdleRPM = 600, + LimitRPM = 10000, + PeakTorque = 40, + PowerbandStart = 650, + PowerbandEnd = 700, + Turbocharged = false, + Supercharged = false, + DoNotStall = true, + + FuelType = FUELTYPE_ELECTRIC, + FuelTankSize = 80, + + PowerBias = 0, + + EngineSoundPreset = 0, + + Sound_Idle = "", + Sound_IdlePitch = 0, + + Sound_Mid = "vehicles/apc/apc_idle1.wav", + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 100, + Sound_MidFadeOutRate = 1, + + Sound_High = "", + + Sound_Throttle = "", + + snd_horn = "simulated_vehicles/horn_0.wav", + + DifferentialGear = 0.7, + Gears = {-0.1,0,0.1} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_couch", V ) + + +local V = { + Name = "HL2 APC", + Model = "models/props_vehicles/apc001.mdl", + Class = "gmod_sent_vehicle_fphysics_base", + Category = "Half Life 2 / Synergy", + SpawnOffset = Vector(0,0,50), + + Members = { + Mass = 4800, + MaxHealth = 2800, + + EnginePos = Vector(-16.1,-81.68,47.25), + + LightsTable = "conapc", + + CustomWheels = true, + CustomSuspensionTravel = 10, + + CustomWheelModel = "models/props_vehicles/apc_tire001.mdl", + CustomWheelPosFL = Vector(-45,77,-22), + CustomWheelPosFR = Vector(45,77,-22), + CustomWheelPosRL = Vector(-45,-74,-22), + CustomWheelPosRR = Vector(45,-74,-22), + CustomWheelAngleOffset = Angle(0,180,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(65,-13,50), + SeatPitch = 15, + SeatYaw = 0, + + PassengerSeats = { + { + pos = Vector(13,75,-2), + ang = Angle(0,0,0) + }, + }, + + Attachments = { + { + model = "models/hunter/plates/plate075x105.mdl", + material = "lights/white", + color = Color(0,0,0,255), + pos = Vector(0.04,57.5,16.74), + ang = Angle(90,-90,0) + }, + { + model = "models/hunter/plates/plate025x05.mdl", + material = "lights/white", + color = Color(0,0,0,255), + pos = Vector(-25.08,91.34,29.46), + ang = Angle(4.2,-109.19,68.43) + }, + { + pos = Vector(-24.63,77.76,8.65), + ang = Angle(24.05,-12.81,-1.87), + model = "models/hunter/plates/plate05x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(24.63,77.76,8.65), + ang = Angle(24.05,-167.19,1.87), + model = "models/hunter/plates/plate05x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(-30.17,61.36,32.79), + ang = Angle(-1.21,-92.38,-130.2), + model = "models/hunter/plates/plate025x05.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(30.17,61.36,32.79), + ang = Angle(-1.21,-87.62,130.2), + model = "models/hunter/plates/plate025x05.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(0,72.92,40.54), + ang = Angle(0,-180,0.79), + model = "models/hunter/plates/plate1x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(25.08,91.34,29.46), + ang = Angle(4.2,-70.81,-68.43), + model = "models/hunter/plates/plate025x05.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(-29.63,79.02,19.28), + ang = Angle(90,-18,0), + model = "models/hunter/plates/plate05x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(29.63,79.02,19.28), + ang = Angle(90,-162,0), + model = "models/hunter/plates/plate05x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(0,75.33,5.91), + ang = Angle(0,0,0), + model = "models/hunter/plates/plate1x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(0,98.02,35.74), + ang = Angle(63,90,0), + model = "models/hunter/plates/plate025x025.mdl", + material = "lights/white", + color = Color(0,0,0,255) + }, + { + pos = Vector(0,100.55,7.41), + ang = Angle(90,-90,0), + model = "models/hunter/plates/plate1x1.mdl", + material = "lights/white", + color = Color(0,0,0,255) + } + }, + + FrontHeight = 20, + FrontConstant = 50000, + FrontDamping = 4000, + FrontRelativeDamping = 3000, + + RearHeight = 20, + RearConstant = 50000, + RearDamping = 4000, + RearRelativeDamping = 3000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 535, + + TurnSpeed = 8, + + MaxGrip = 140, + Efficiency = 1.25, + GripOffset = -14, + BrakePower = 120, + BulletProofTires = true, + + IdleRPM = 750, + LimitRPM = 5500, + PeakTorque = 180, + PowerbandStart = 1000, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + + FuelFillPos = Vector(-61.39,49.54,15.79), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 120, + + PowerBias = 0, + + EngineSoundPreset = 0, + + Sound_Idle = "simulated_vehicles/misc/nanjing_loop.wav", + Sound_IdlePitch = 1, + + Sound_Mid = "simulated_vehicles/misc/m50.wav", + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 58, + Sound_MidFadeOutRate = 0.476, + + Sound_High = "simulated_vehicles/misc/v8high2.wav", + Sound_HighPitch = 1, + Sound_HighVolume = 0.75, + Sound_HighFadeInRPMpercent = 58, + Sound_HighFadeInRate = 0.19, + + Sound_Throttle = "", + Sound_ThrottlePitch = 0, + Sound_ThrottleVolume = 0, + + snd_horn = "simulated_vehicles/horn_2.wav", + + DifferentialGear = 0.27, + Gears = {-0.09,0,0.09,0.18,0.28,0.35} + } +} +list.Set( "simfphys_vehicles", "sim_fphys_conscriptapc", V ) \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/damage.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/damage.lua new file mode 100644 index 0000000..b99614d --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/damage.lua @@ -0,0 +1,6 @@ +local function damagedbackfire( length ) + local veh = net.ReadEntity() + if not IsValid(veh) or not veh.Backfire then return end + veh:Backfire( true ) +end +net.Receive("simfphys_backfire", damagedbackfire) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/fonts.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/fonts.lua new file mode 100644 index 0000000..f7e4adf --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/fonts.lua @@ -0,0 +1,53 @@ +surface.CreateFont( "simfphysfont", { + font = "Verdana", + extended = false, + size = ScrH() >= 900 and (ScrH() >= 1080 and 20 or 18) or 12, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "DSimfphysFont", { + font = "Arial", + extended = false, + size = 22, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "DSimfphysFont_hint", { + font = "Arial", + extended = false, + size = 21, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = true, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/hud.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/hud.lua new file mode 100644 index 0000000..d58b4a4 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/hud.lua @@ -0,0 +1,647 @@ +local screenw = ScrW() +local screenh = ScrH() +local Widescreen = (screenw / screenh) > (4 / 3) +local sizex = screenw * (Widescreen and 1 or 1.32) + +local turnmenu = KEY_COMMA +local mirrorkey = KEY_M +local beltkey = KEY_B +local ms_key = MOUSE_MIDDLE + +local ms_deadzone = 0.06 +local ms_sensitivity = 1 +local ms_return = 0 + +CreateClientConVar('dbg_cars_ms_sensitivity', ms_sensitivity, true) +CreateClientConVar('dbg_cars_ms_deadzone', ms_deadzone, true) +CreateClientConVar('dbg_cars_ms_return', ms_return, true) + +cvars.AddChangeCallback('dbg_cars_ms_sensitivity', function(convar, oldValue, newValue) ms_sensitivity = tonumber(newValue) end) +cvars.AddChangeCallback('dbg_cars_ms_return', function(convar, oldValue, newValue) ms_return = tonumber(newValue) end) +cvars.AddChangeCallback('dbg_cars_ms_deadzone', function(convar, oldValue, newValue) ms_deadzone = tonumber(newValue) end) + +cvars.AddChangeCallback('cl_simfphys_key_turnmenu', function(convar, oldValue, newValue) turnmenu = tonumber(newValue) end) +cvars.AddChangeCallback('cl_simfphys_key_mirror', function(convar, oldValue, newValue) mirrorkey = tonumber(newValue) end) +cvars.AddChangeCallback('cl_simfphys_key_belt', function(convar, oldValue, newValue) beltkey = tonumber(newValue) end) + +hook.Add('PlayerFinishedLoading', 'dbg-cars.hud', function() + turnmenu = GetConVar('cl_simfphys_key_turnmenu'):GetInt() + mirrorkey = GetConVar('cl_simfphys_key_mirror'):GetInt() + beltkey = GetConVar('cl_simfphys_key_belt'):GetInt() + ms_key = GetConVar('cl_simfphys_keymousesteer'):GetInt() +end) + +ms_sensitivity = GetConVar('dbg_cars_ms_sensitivity'):GetFloat() +ms_deadzone = GetConVar('dbg_cars_ms_deadzone'):GetFloat() +ms_return = GetConVar('dbg_cars_ms_return'):GetFloat() + +local function getMyCar() + local mySeat = LocalPlayer():GetVehicle() + if not IsValid(mySeat) or not IsValid(mySeat.vehiclebase) then return end + + return mySeat.vehiclebase, mySeat +end + +local wantedSteer, inputSteer = 0, 0 +local function sendWantedSteer() + net.Start('car.steer', true) + net.WriteFloat(wantedSteer) + net.SendToServer() +end + +local mouseSteerEnabled +hook.Add('PlayerButtonDown', 'dbg-cars.steer', function(ply, key) + if key == ms_key and IsFirstTimePredicted() then + local veh, seat = getMyCar() + if veh and veh:GetDriverSeat() ~= seat then return end + mouseSteerEnabled = not mouseSteerEnabled + end +end) + +hook.Add('InputMouseApply', 'dbg-cars.steer', function(cmd, x, y, ang) + if not getMyCar() then + mouseSteerEnabled = nil + return + end + + if not mouseSteerEnabled then + if inputSteer ~= 0 then + inputSteer = 0 + wantedSteer = 0 + sendWantedSteer() + end + return + end + + inputSteer = math.Clamp(inputSteer + x * 0.0012 * ms_sensitivity, -1, 1) + + local absInput = math.abs(inputSteer) + local steer = absInput > ms_deadzone and ((absInput - math.abs(ms_deadzone)) * octolib.math.sign(inputSteer) / (1 - ms_deadzone)) or 0 + if steer ~= wantedSteer then + wantedSteer = steer + sendWantedSteer() + end + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + return true +end) + +hook.Add('Think', 'dbg-cars.steer', function() + if ms_return > 0 then + inputSteer = math.Approach(inputSteer, 0, ms_return * FrameTime()) + end +end) + +local steerPanelRadius = 300 +hook.Add('HUDPaint', 'dbg-cars.hud', function() + if mouseSteerEnabled then + local car = getMyCar() + if not car then return end + + local cx = ScrW() / 2 + local y = ScrH() - 50 + draw.RoundedBox(4, cx - steerPanelRadius, y, steerPanelRadius * 2 + 1, 8, Color(0,0,0, 255)) + draw.RoundedBox(4, cx - steerPanelRadius * ms_deadzone, y, steerPanelRadius * ms_deadzone * 2, 8, Color(255,255,255, 5)) + draw.RoundedBox(8, cx + steerPanelRadius * car.sm_vSteer - 8, y - 4, 16, 16, Color(120,120,120)) + draw.RoundedBox(8, cx + steerPanelRadius * inputSteer - 8, y - 4, 16, 16, Color(255,255,255, 255)) + end +end) + +surface.CreateFont('dbg-cars.hud.large', { + font = 'Calibri', + extended = true, + size = 60, + weight = 350, + shadow = true, +}) + +surface.CreateFont('dbg-cars.hud.normal', { + font = 'Calibri', + extended = true, + size = 32, + weight = 350, + shadow = true, +}) + +local cols = { + bg = Color(0,0,0), + back = Color(60,60,60), + active = Color(255,255,255), + inactive = Color(60,60,60), + rpm = Color(38,166,154), + orange = Color(240,202,77), + danger = Color(223,55,33), + lightFar = Color(65,132,209), +} + +local icons = { + brake = Material('octoteam/icons-car/brake.png'), + lock = Material('octoteam/icons-car/lock.png'), + lightClose = Material('octoteam/icons-car/light_close.png'), + lightFar = Material('octoteam/icons-car/light_far.png'), + tire = Material('octoteam/icons-car/tire_flat.png'), + alarm = Material('octoteam/icons-car/alarm.png'), + belt = Material('octoteam/icons-car/belt.png'), + fuel = Material('octoteam/icons-car/fuel.png'), + left = Material('octoteam/icons-car/left_1.png'), + right = Material('octoteam/icons-car/right_1.png'), +} + +local function flash(freq) + return math.sin(CurTime() * freq) / 2 + 0.5 +end + +local dashChecks = { + function(car) return icons.left, (car.flashnum or 0) > 0.75 and car.signal_left and cols.orange or cols.bg end, + function(car) + local lights, lamps = car:GetLightsEnabled(), car:GetLampsEnabled() + local col = lamps and cols.lightFar + or lights and cols.active + or cols.inactive + if not car:GetEngineActive() and lights then col = col:Lerp(flash(3), cols.danger) end + return lamps and icons.lightFar or icons.lightClose, col + end, + function(car) return icons.lock, car:GetIsLocked() end, + function(car) + local belt = LocalPlayer():GetNetVar('belted') + local col = belt and cols.active or cols.inactive + if (car.speed or 0) > 1 and not belt then col = col:Lerp(flash(3), cols.danger) end + return icons.belt, col + end, + function(car) + local brake = car:GetHandBrakeEnabled() + local col = brake and cols.active or cols.inactive + if not car:GetEngineActive() and not brake then col = col:Lerp(flash(3), cols.danger) end + return icons.brake, col + end, + function(car) return car:GetEMSEnabled() and icons.alarm, cols.bg:Lerp(flash(4), cols.active) end, + function(car) return (car:GetFuel() / car:GetMaxFuel()) < 0.2 and icons.fuel, cols.inactive:Lerp(flash(3), cols.danger) end, + function(car) return icons.right, (car.flashnum or 0) > 0.75 and car.signal_right and cols.orange or cols.bg end, +} + +local function niceGear(gear) + return gear == 1 and 'R' or gear == 2 and 'N' or (gear - 2) +end + +local lastRPM, lastFuelUse = 0, 0 +local function drawDash(car) + draw.RoundedBox(8, -120, -40, 240, 80, cols.bg) + + draw.Text { + text = niceGear(car:GetGear()), + font = 'dbg-cars.hud.large', + pos = {0, -14}, + color = cols.back, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + + lastRPM = math.Approach(lastRPM, car:GetRPM() / car:GetLimitRPM(), FrameTime()) + local colRPM = lastRPM < 0.7 and cols.rpm or cols.rpm:Lerp((lastRPM - 0.7) / 0.3, cols.danger) + + local bottom = 3 + draw.NoTexture() + for i = 1, 10 do + local height = i * 2 + 2 + surface.SetDrawColor((i / 10 < lastRPM + 0.09) and colRPM or cols.back) + surface.DrawRect(-75 + (i-1) * 6, bottom - height, 4, height) + end + + local fuel = car:GetFuel() / car:GetMaxFuel() + local colFuel = fuel > 0.7 and cols.orange or cols.orange:Lerp(1 - fuel / 0.7, cols.danger) + draw.NoTexture() + for i = 1, 10 do + local height = i * 2 + 2 + surface.SetDrawColor((i / 10 < fuel + 0.05) and colFuel or cols.back) + surface.DrawRect(71 - (i-1) * 6, bottom - height, 4, height) + end + + car.speed = math.Round(car:GetVelocity():Length() * 0.0568182, 0) + draw.Text { + text = car.speed, + font = 'dbg-cars.hud.normal', + pos = {0, -10}, + color = cols.active, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + + local icons = {} + for _, func in ipairs(dashChecks) do + local icon, col = func(car) + if icon then + icons[#icons + 1] = { icon, col } + end + end + + local xStart = -(#icons * 28) / 2 + for i, icon in ipairs(icons) do + local x, y = xStart + (i-1) * 28, 6 + if istable(icon[2]) then + surface.SetDrawColor(icon[2]) + elseif icon[2] then + surface.SetDrawColor(cols.active) + else + surface.SetDrawColor(cols.inactive) + end + surface.SetMaterial(icon[1]) + surface.DrawTexturedRect(x, y, 24, 24) + end + + -- local x, y = 192 - 40 * (#icons + 1) - 36, -16 + + -- surface.SetDrawColor(cols.inactive) + -- surface.SetMaterial(icons.fuel) + -- surface.DrawTexturedRect(x, y, 32, 32) + + -- lastFuelUse = octolib.math.lerp(lastFuelUse, car:GetFuelUse() * 10, FrameTime(), 0.003) + -- draw.Text { + -- text = ('%0.1f'):format(lastFuelUse), + -- font = 'dbg-cars.hud.normal', + -- pos = {-7, 0}, + -- color = cols.active, + -- xalign = TEXT_ALIGN_LEFT, + -- yalign = TEXT_ALIGN_CENTER, + -- } + + -- local gb = 255 + -- if fuel < 0.5 then gb = fuel / 0.5 * 255 end + + -- surface.SetDrawColor(255,gb,gb, 255) + -- surface.SetMaterial(icons.fuel) + -- surface.DrawTexturedRectUV(x, y + 32 * (1 - fuel), 32, 32 * fuel, 0, 1 - fuel, 1, 1) + + -- if (car.flashnum or 0) > 0.75 then + -- if car.signal_left then + -- surface.SetDrawColor(255,150,0, 255) + -- surface.SetMaterial(icons.turnLeft) + -- surface.DrawTexturedRect(-237, -16, 32, 32) + -- end + + -- if car.signal_right then + -- surface.SetDrawColor(255,180,0, 255) + -- surface.SetMaterial(icons.turnRight) + -- surface.DrawTexturedRect(205, -16, 32, 32) + -- end + -- end +end + +local fallback = { + pos = Vector(0, 38, 30), + ang = Angle(0, 0, 65), +} + +hook.Add('PostDrawTranslucentRenderables', 'dbg-cars.hud', function() + + local car = getMyCar() + if not IsValid(car) then return end + + car.spawnlist = car.spawnlist or list.Get('simfphys_vehicles')[car:GetSpawn_List()] + + local dashData = car.spawnlist.Members.Dash + local posOff = dashData and dashData.pos or fallback.pos + local angOff = dashData and dashData.ang or fallback.ang + local pos, ang = LocalToWorld(posOff, angOff, car:GetPos(), car:GetAngles()) + cam.Start3D2D(pos, ang, 0.05) + drawDash(car) + cam.End3D2D() + +end) + + +local turnmode = 0 +local turnmenu_wasopen = false + +local function drawTurnMenu(vehicle) + + if octolib.flyEditor.active then return end + + if input.IsKeyDown(GetConVar('cl_simfphys_keyforward'):GetInt()) then + turnmode = 0 + end + + if input.IsKeyDown(GetConVar('cl_simfphys_keyleft'):GetInt()) then + turnmode = 2 + end + + if input.IsKeyDown(GetConVar('cl_simfphys_keyright'):GetInt()) then + turnmode = 3 + end + + if input.IsKeyDown(GetConVar('cl_simfphys_keyreverse'):GetInt()) then + turnmode = 1 + end + + local cX = ScrW() / 2 + local cY = ScrH() / 2 + + local sx = sizex * 0.065 + local sy = sizex * 0.065 + + local selectorX = (turnmode == 2 and (-sx - 1) or 0) + (turnmode == 3 and (sx + 1) or 0) + local selectorY = (turnmode == 0 and (-sy - 1) or 0) + + draw.RoundedBox(8, cX - sx * 0.5 - 1 + selectorX, cY - sy * 0.5 - 1 + selectorY, sx + 2, sy + 2, Color(240, 200, 0, 255)) + draw.RoundedBox(8, cX - sx * 0.5 + selectorX, cY - sy * 0.5 + selectorY, sx, sy, Color(50, 50, 50, 255)) + + draw.RoundedBox(8, cX - sx * 0.5, cY - sy * 0.5, sx, sy, Color(0, 0, 0, 100)) + draw.RoundedBox(8, cX - sx * 0.5, cY - sy * 1.5 - 1, sx, sy, Color(0, 0, 0, 100)) + draw.RoundedBox(8, cX - sx * 1.5 - 1, cY - sy * 0.5, sx, sy, Color(0, 0, 0, 100)) + draw.RoundedBox(8, cX + sx * 0.5 + 1, cY - sy * 0.5, sx, sy, Color(0, 0, 0, 100)) + + surface.SetDrawColor(240, 200, 0, 100) + --X + if turnmode == 0 then + surface.SetDrawColor(240, 200, 0, 255) + end + surface.DrawLine(cX - sx * 0.3, cY - sy - sy * 0.3, cX + sx * 0.3, cY - sy + sy * 0.3) + surface.DrawLine(cX + sx * 0.3, cY - sy - sy * 0.3, cX - sx * 0.3, cY - sy + sy * 0.3) + surface.SetDrawColor(240, 200, 0, 100) + + -- <= + if turnmode == 2 then + surface.SetDrawColor(240, 200, 0, 255) + end + surface.DrawLine(cX - sx + sx * 0.3, cY - sy * 0.15, cX - sx + sx * 0.3, cY + sy * 0.15) + surface.DrawLine(cX - sx + sx * 0.3, cY + sy * 0.15, cX - sx, cY + sy * 0.15) + surface.DrawLine(cX - sx + sx * 0.3, cY - sy * 0.15, cX - sx, cY - sy * 0.15) + surface.DrawLine(cX - sx, cY - sy * 0.3, cX - sx, cY - sy * 0.15) + surface.DrawLine(cX - sx, cY + sy * 0.3, cX - sx, cY + sy * 0.15) + surface.DrawLine(cX - sx, cY + sy * 0.3, cX - sx - sx * 0.3, cY) + surface.DrawLine(cX - sx, cY - sy * 0.3, cX - sx - sx * 0.3, cY) + surface.SetDrawColor(240, 200, 0, 100) + + -- => + if turnmode == 3 then + surface.SetDrawColor(240, 200, 0, 255) + end + surface.DrawLine(cX + sx - sx * 0.3, cY - sy * 0.15, cX + sx - sx * 0.3, cY + sy * 0.15) + surface.DrawLine(cX + sx - sx * 0.3, cY + sy * 0.15, cX + sx, cY + sy * 0.15) + surface.DrawLine(cX + sx - sx * 0.3, cY - sy * 0.15, cX + sx, cY - sy * 0.15) + surface.DrawLine(cX + sx, cY - sy * 0.3, cX + sx, cY - sy * 0.15) + surface.DrawLine(cX + sx, cY + sy * 0.3, cX + sx, cY + sy * 0.15) + surface.DrawLine(cX + sx, cY + sy * 0.3, cX + sx + sx * 0.3, cY) + surface.DrawLine(cX + sx, cY - sy * 0.3, cX + sx + sx * 0.3, cY) + surface.SetDrawColor(240, 200, 0, 100) + + -- ^ + if turnmode == 1 then + surface.SetDrawColor(240, 200, 0, 255) + end + surface.DrawLine(cX, cY - sy * 0.4, cX + sx * 0.4, cY + sy * 0.3) + surface.DrawLine(cX, cY - sy * 0.4, cX - sx * 0.4, cY + sy * 0.3) + surface.DrawLine(cX + sx * 0.4, cY + sy * 0.3, cX - sx * 0.4, cY + sy * 0.3) + surface.DrawLine(cX, cY - sy * 0.26, cX + sx * 0.3, cY + sy * 0.24) + surface.DrawLine(cX, cY - sy * 0.26, cX - sx * 0.3, cY + sy * 0.24) + surface.DrawLine(cX + sx * 0.3, cY + sy * 0.24, cX - sx * 0.3, cY + sy * 0.24) + + surface.SetDrawColor(255, 255, 255, 255) +end + +local function simfphysHUD() + local ply = LocalPlayer() + local turnmenu_isopen = false + + if not IsValid(ply) or not ply:Alive() then turnmenu_wasopen = false return end + + local vehiclebase, vehicle = getMyCar() + if not vehiclebase then + turnmenu_wasopen = false + return + end + + local IsDriverSeat = vehicle == vehiclebase:GetDriverSeat() + if not IsDriverSeat then turnmenu_wasopen = false return end + + if vehiclebase.HasTurnSignals and input.IsKeyDown(turnmenu) then + turnmenu_isopen = true + drawTurnMenu(vehiclebase) + end + + if turnmenu_isopen ~= turnmenu_wasopen then + turnmenu_wasopen = turnmenu_isopen + + if turnmenu_isopen then + turnmode = 0 + else + net.Start('simfphys_turnsignal') + net.WriteEntity(vehiclebase) + net.WriteInt(turnmode, 32) + net.SendToServer() + + if turnmode == 1 or turnmode == 2 or turnmode == 3 then + vehiclebase:EmitSound('simulated_vehicles/sfx/turnsignal_start.ogg') + else + vehiclebase:EmitSound('simulated_vehicles/sfx/turnsignal_end.ogg') + end + end + end +end +hook.Add('HUDPaint', 'simfphys_HUD', simfphysHUD) + +-- draw.arc function by bobbleheadbob +-- https://dl.dropboxusercontent.com/u/104427432/Scripts/drawarc.lua +-- https://facepunch.com/showthread.php?t=1438016&p=46536353&viewfull=1#post46536353 + +function surface.PrecacheArc(cx,cy,radius,thickness,startang,endang,roughness,bClockwise) + local triarc = {} + local deg2rad = math.pi / 180 + + -- Correct start/end ang + local startang,endang = startang or 0, endang or 0 + if bClockwise and (startang < endang) then + local temp = startang + startang = endang + endang = temp + temp = nil + elseif (startang > endang) then + local temp = startang + startang = endang + endang = temp + temp = nil + end + + + -- Define step + local roughness = math.max(roughness or 1, 1) + local step = roughness + if bClockwise then + step = math.abs(roughness) * -1 + end + + + -- Create the inner circle's points. + local inner = {} + local r = radius - thickness + for deg=startang, endang, step do + local rad = deg2rad * deg + table.insert(inner, { + x=cx+(math.cos(rad)*r), + y=cy+(math.sin(rad)*r) + }) + end + + + -- Create the outer circle's points. + local outer = {} + for deg=startang, endang, step do + local rad = deg2rad * deg + table.insert(outer, { + x=cx+(math.cos(rad)*radius), + y=cy+(math.sin(rad)*radius) + }) + end + + + -- Triangulize the points. + for tri=1,#inner*2 do -- twice as many triangles as there are degrees. + local p1,p2,p3 + p1 = outer[math.floor(tri/2)+1] + p3 = inner[math.floor((tri+1)/2)+1] + if tri%2 == 0 then --if the number is even use outer. + p2 = outer[math.floor((tri+1)/2)] + else + p2 = inner[math.floor((tri+1)/2)] + end + + table.insert(triarc, {p1,p2,p3}) + end + + -- Return a table of triangles to draw. + return triarc + +end + +function surface.DrawArc(arc) + for k,v in ipairs(arc) do + surface.DrawPoly(v) + end +end + +function draw.Arc(cx,cy,radius,thickness,startang,endang,roughness,color,bClockwise) + surface.SetDrawColor(color) + surface.DrawArc(surface.PrecacheArc(cx,cy,radius,thickness,startang,endang,roughness,bClockwise)) +end + +local showMirror = false +hook.Add('PlayerButtonDown', 'dbg-cars.mirror', function(ply, key) + + if IsFirstTimePredicted() then + if key == mirrorkey then + showMirror = not showMirror + end + + if key == beltkey then + netstream.Start('dbg-cars.belt') + end + end + +end) + +local rts = {} +local mirrorMat = CreateMaterial('dbg-cars-mirrorMat', 'UnlitGeneric', {}) + +local cmod = Material('pp/colour') +local colFrame = Color(50,50,50) +local function updateMirror(pos, angOff, veh, w, h) + + local ply = LocalPlayer() + + local ang = veh:LocalToWorldAngles(Angle(angOff.p, angOff.y + 180, angOff.r)) + + local key = w .. '-' .. h + rts[key] = rts[key] or GetRenderTarget('dbg-cars-mirrorRT' .. key, w, h) + + local oldHideHead = dbgView.headHidden + dbgView.hideHead(false) + ply:SetMaskVisible(true) + local oldRT = cmod:GetTexture('$fbtexture') + cmod:SetTexture('$fbtexture', rts[key]) + render.PushRenderTarget(rts[key]) + render.RenderView({ + origin = pos, + angles = ang, + w = w, h = h, + aspectratio = w / h, + x = 0, y = 0, + fov = 90, + }) + render.PopRenderTarget() + if not oldRT then return rts[key] end + cmod:SetTexture('$fbtexture', oldRT) + dbgView.hideHead(oldHideHead) + ply:SetMaskVisible() + return rts[key] + +end + + +local function checkLook(ply, pos) + return (ply.viewAngs or ply:EyeAngles()):Forward():Dot((pos - ply:EyePos()):GetNormalized()) >= 0.95 +end + +local function drawMirror(pos, angOff, veh, x, y, w, h, noFlip) + local rt = updateMirror(pos, angOff, veh, w, h) + draw.RoundedBox(4, x - 4, y - 4, w + 8, h + 8, colFrame) + mirrorMat:SetTexture('$basetexture', rt) + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(mirrorMat) + if noFlip then + surface.DrawTexturedRect(x, y, w, h) + else + surface.DrawTexturedRectUV(x, y, w, h, 1, 0, 0, 1) + end +end + +local function getSize(data) + local w, h + if data.w then + w = ScrW() * data.w + h = w / data.ratio + else + h = ScrH() * data.h + w = h * data.ratio + end + return w, h +end + +hook.Add('HUDPaint', 'dbg-cars.mirror', function() + + if not showMirror then return end + + local ply = LocalPlayer() + if not ply:InVehicle() or ply:GetNetVar('blind') then return end + + local veh = ply:GetVehicle() + local car = veh:GetParent() + if not IsValid(car) or car:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + if simfphys.GetSeatProperty(veh, 'noMirrors') then return end + car.spawnlist = car.spawnlist or list.Get('simfphys_vehicles')[car:GetSpawn_List()] + local mirrors = car.spawnlist.Members.Mirrors + if not mirrors then return end + + local w, h = ScrW(), ScrH() + + local pos = mirrors.left and car:LocalToWorld(mirrors.left.pos) + if mirrors.left and checkLook(ply, pos) then + w, h = getSize(mirrors.left) + drawMirror(pos, mirrors.left.ang or Angle(), car, 15, (ScrH() - h) / 2, w, h, mirrors.left.noFlip) + return + end + + pos = mirrors.top and car:LocalToWorld(mirrors.top.pos) + if mirrors.top and checkLook(ply, pos) then + w, h = getSize(mirrors.top) + drawMirror(pos, mirrors.top.ang or Angle(), car, (ScrW() - w) / 2, 15, w, h, mirrors.top.noFlip) + return + end + + pos = mirrors.right and car:LocalToWorld(mirrors.right.pos) + if mirrors.right and checkLook(ply, pos) then + w, h = getSize(mirrors.right) + drawMirror(pos, mirrors.right.ang or Angle(), car, ScrW() - (w + 15), (ScrH() - h) / 2, w, h, mirrors.right.noFlip) + return + end + +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/killicons.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/killicons.lua new file mode 100644 index 0000000..e036213 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/killicons.lua @@ -0,0 +1 @@ +killicon.Add( "gmod_sent_vehicle_fphysics_base", "HUD/killicons/simfphys_car", Color( 255, 80, 0, 255 ) ) \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/lighting.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/lighting.lua new file mode 100644 index 0000000..3d8f35a --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/lighting.lua @@ -0,0 +1,1094 @@ +local checkinterval = 2 +local maxLampDist = 3000 * 3000 +local maxLightSpriteDist = 3000 * 3000 +local NextCheck = CurTime() + checkinterval + +local hdr = GetConVar('mat_hdr_level') +local mat = Material('sprites/light_ignorez') +local mat2 = Material('sprites/light_glow02_add_noz') + +if file.Exists('materials/sprites/glow_headlight_ignorez.vmt', 'GAME') then + mat2 = Material('sprites/glow_headlight_ignorez') +end + +local SpritesDisabled = false +local AllowVisualDamage = true +local FrontProjectedLights = true +local RearProjectedLights = true +local Shadows = false + +cvars.AddChangeCallback('cl_simfphys_hidesprites', function(convar, oldValue, newValue) SpritesDisabled = (tonumber(newValue) ~= 0) end) +cvars.AddChangeCallback('cl_simfphys_spritedamage', function(convar, oldValue, newValue) AllowVisualDamage = (tonumber(newValue) ~= 0) end) +cvars.AddChangeCallback('cl_simfphys_frontlamps', function(convar, oldValue, newValue) FrontProjectedLights = (tonumber(newValue) ~= 0) end) +cvars.AddChangeCallback('cl_simfphys_rearlamps', function(convar, oldValue, newValue) RearProjectedLights = (tonumber(newValue) ~= 0) end) +cvars.AddChangeCallback('cl_simfphys_shadows', function(convar, oldValue, newValue) Shadows = (tonumber(newValue) ~= 0) end) + +SpritesDisabled = GetConVar('cl_simfphys_hidesprites'):GetBool() +AllowVisualDamage = GetConVar('cl_simfphys_spritedamage'):GetBool() +FrontProjectedLights = GetConVar('cl_simfphys_frontlamps'):GetBool() +RearProjectedLights = GetConVar('cl_simfphys_rearlamps'):GetBool() +Shadows = GetConVar('cl_simfphys_shadows'):GetBool() + +if not istable(vtable) then + vtable = {} +end + +local function BodyGroupIsValid(bodygroups, entity) + for index, groups in pairs(bodygroups) do + local mygroup = entity:GetBodygroup(index) + for g_index = 1, table.Count(groups) do + if (mygroup == groups[g_index]) then return true end + end + end + return false +end + +local function UpdateSubMats(ent, Lowbeam, Highbeam, IsBraking, IsReversing) + if not istable(ent.SubMaterials) then return end + + if ent.SubMaterials.turnsignals then + local IsTurningLeft = ent.signal_left + local IsTurningRight = ent.signal_right + local IsFlashing = ent:GetFlasher() == 1 + + if ent.WasTurningLeft ~= IsTurningLeft or ent.WasTurningRight ~= IsTurningRight or ent.WasFlashing ~= IsFlashing then + if ent.SubMaterials.turnsignals.left then + for k,v in pairs(ent.SubMaterials.turnsignals.left) do + local mat = (IsFlashing and IsTurningLeft) and v or '' + ent:SetSubMaterial(k, mat) + end + end + if ent.SubMaterials.turnsignals.right then + for k,v in pairs(ent.SubMaterials.turnsignals.right) do + local mat = (IsFlashing and IsTurningRight) and v or '' + ent:SetSubMaterial(k, mat) + end + end + + ent.WasTurningLeft = IsTurningLeft + ent.WasTurningRight = IsTurningRight + ent.WasFlashing = IsFlashing + end + end + + if ent.WasReversing == IsReversing and ent.WasBraking == IsBraking and ent.WasLowbeam == Lowbeam and ent.WasHighbeam == Highbeam then return end + + if Lowbeam then + if Highbeam then + if ent.SubMaterials.on_highbeam then + if not IsReversing and not IsBraking then + if ent.SubMaterials.on_highbeam.Base then + for k,v in pairs(ent.SubMaterials.on_highbeam.Base) do + ent:SetSubMaterial(k, v) + end + end + elseif IsBraking then + if IsReversing then + if ent.SubMaterials.on_highbeam.Brake_Reverse then + for k,v in pairs(ent.SubMaterials.on_highbeam.Brake_Reverse) do + ent:SetSubMaterial(k, v) + end + end + else + if ent.SubMaterials.on_highbeam.Brake then + for k,v in pairs(ent.SubMaterials.on_highbeam.Brake) do + ent:SetSubMaterial(k, v) + end + end + end + else + if ent.SubMaterials.on_highbeam.Reverse then + for k,v in pairs(ent.SubMaterials.on_highbeam.Reverse) do + ent:SetSubMaterial(k, v) + end + end + end + end + else + if ent.SubMaterials.on_lowbeam then + if not IsReversing and not IsBraking then + if ent.SubMaterials.on_lowbeam.Base then + for k,v in pairs(ent.SubMaterials.on_lowbeam.Base) do + ent:SetSubMaterial(k, v) + end + end + elseif IsBraking then + if IsReversing then + if ent.SubMaterials.on_lowbeam.Brake_Reverse then + for k,v in pairs(ent.SubMaterials.on_lowbeam.Brake_Reverse) do + ent:SetSubMaterial(k, v) + end + end + else + if ent.SubMaterials.on_lowbeam.Brake then + for k,v in pairs(ent.SubMaterials.on_lowbeam.Brake) do + ent:SetSubMaterial(k, v) + end + end + end + else + if ent.SubMaterials.on_lowbeam.Reverse then + for k,v in pairs(ent.SubMaterials.on_lowbeam.Reverse) do + ent:SetSubMaterial(k, v) + end + end + end + end + end + else + if ent.SubMaterials.off then + if not IsReversing and not IsBraking then + if ent.SubMaterials.off.Base then + for k,v in pairs(ent.SubMaterials.off.Base) do + ent:SetSubMaterial(k, v) + end + end + elseif IsBraking then + if IsReversing then + if ent.SubMaterials.off.Brake_Reverse then + for k,v in pairs(ent.SubMaterials.off.Brake_Reverse) do + ent:SetSubMaterial(k, v) + end + end + else + if ent.SubMaterials.off.Brake then + for k,v in pairs(ent.SubMaterials.off.Brake) do + ent:SetSubMaterial(k, v) + end + end + end + else + if ent.SubMaterials.off.Reverse then + for k,v in pairs(ent.SubMaterials.off.Reverse) do + ent:SetSubMaterial(k, v) + end + end + end + end + end + + ent.WasReversing = IsReversing + ent.WasBraking = IsBraking + ent.WasLowbeam = Lowbeam + ent.WasHighbeam = Highbeam +end + +local function ManageProjTextures() + if not vtable then return end + + local eyePos = LocalPlayer():GetShootPos() + for i, ent in pairs(vtable) do + if IsValid(ent) then + if ent:GetPos():DistToSqr(eyePos) > maxLampDist then + if ent.checkProjectors then + for _, proj in pairs(ent.Projtexts) do + if IsValid(proj.projector) then + proj.projector:Remove() + proj.projector = nil + proj.LampsActive = nil + end + end + ent.checkProjectors = nil + end + continue + end + ent.checkProjectors = true + + local vel = ent:GetVelocity() * RealFrameTime() + ent.triggers = { + [1] = ent:GetLightsEnabled(), + [2] = ent:GetLampsEnabled(), + [4] = ent:GetIsBraking(), + [5] = ent:GetGear() == 1, + [6] = ent.signal_left, + [7] = ent.signal_right, + [8] = ent:GetIsBraking(), + [9] = ent:GetIsBraking(), + } + + UpdateSubMats(ent, ent.triggers[1], ent.triggers[2], ent.triggers[4], ent.triggers[5]) + + local mul = StormFox2.Time.IsNight() and 1 or 0.2 + for _, proj in pairs(ent.Projtexts) do + local trigger = ent.triggers[proj.trigger] + local enable = ent.triggers[1] or trigger + + if proj.Damaged or (proj.trigger == 2 and not FrontProjectedLights) or (proj.trigger == 4 and not RearProjectedLights) then + trigger = false + enable = false + end + + if ent.HasSpecialTurnSignals and proj.trigger == 4 and (ent.triggers[6] or ent.triggers[7]) then + trigger = false + end + + if proj.LampsActive ~= trigger then + proj.LampsActive = trigger + + if enable then + proj.istriggered = trigger + local brightness = trigger and proj.ontrigger.brightness or proj.brightness + local mat = trigger and proj.ontrigger.mat or proj.mat + local col = trigger and proj.ontrigger.col or proj.col + local FarZ = trigger and proj.ontrigger.FarZ or proj.FarZ + + if trigger and brightness > 0 then + local thelamp = ProjectedTexture() + thelamp:SetBrightness(brightness * mul) + thelamp:SetTexture(mat) + thelamp:SetColor(col) + thelamp:SetEnableShadows(Shadows) + thelamp:SetFarZ(FarZ) + thelamp:SetNearZ(proj.NearZ) + thelamp:SetFOV(proj.Fov) + if FarZ > 500 then + thelamp:SetConstantAttenuation(0.5) + end + + proj.projector = thelamp + elseif IsValid(proj.projector) then + proj.projector:Remove() + proj.projector = nil + end + elseif IsValid(proj.projector) then + proj.projector:Remove() + proj.projector = nil + end + end + + if IsValid(proj.projector) then + local pos = ent:LocalToWorld(proj.pos) + local ang = ent:LocalToWorldAngles(proj.ang) + + if proj.istriggered ~= trigger then + proj.istriggered = trigger + + if proj.ontrigger.brightness then + local brightness = trigger and proj.ontrigger.brightness or proj.brightness + proj.projector:SetBrightness(brightness * mul) + end + + if proj.ontrigger.mat then + local mat = trigger and proj.ontrigger.mat or proj.mat + proj.projector:SetTexture(mat) + end + + if proj.ontrigger.FarZ then + local FarZ = trigger and proj.ontrigger.FarZ or proj.FarZ + proj.projector:SetFarZ(FarZ) + end + end + + proj.projector:SetPos(pos + vel) + proj.projector:SetAngles(ang) + proj.projector:Update() + end + end + else + vtable[i] = nil + end + end +end + +local function SetupProjectedTextures(ent , vehiclelist) + ent.Projtexts = {} + + local proj_col = vehiclelist.ModernLights and Color(215,240,255) or Color(220,205,160) + + if isvector(vehiclelist.L_HeadLampPos) and isangle(vehiclelist.L_HeadLampAng) then + ent.Projtexts['FL'] = { + trigger = 2, + ontrigger = { + mat = 'effects/flashlight/headlight_highbeam', + FarZ = 2000, + brightness = hdr:GetBool() and 8 or 0.5, + }, + pos = vehiclelist.L_HeadLampPos, + ang = vehiclelist.L_HeadLampAng, + mat = 'effects/flashlight/headlight_lowbeam', + col = proj_col, + brightness = 0.5, + FarZ = 1000, + NearZ = 75, + Fov = 80, + } + end + + if isvector(vehiclelist.R_HeadLampPos) and isangle(vehiclelist.R_HeadLampAng) then + ent.Projtexts['FR'] = { + trigger = 2, + ontrigger = { + mat = 'effects/flashlight/headlight_highbeam', + FarZ = 2000, + brightness = hdr:GetBool() and 8 or 0.5, + }, + pos = vehiclelist.R_HeadLampPos, + ang = vehiclelist.R_HeadLampAng, + mat = 'effects/flashlight/headlight_lowbeam', + col = proj_col, + brightness = 0.5, + FarZ = 1000, + NearZ = 75, + Fov = 80, + } + end + + if isvector(vehiclelist.L_RearLampPos) and isangle(vehiclelist.L_RearLampAng) then + ent.Projtexts['RL'] = { + trigger = 4, + ontrigger = { + brightness = hdr:GetBool() and 5 or 0.3, + }, + pos = vehiclelist.L_RearLampPos, + ang = vehiclelist.L_RearLampAng, + mat = 'effects/flashlight/soft', + col = Color(30,0,0), + brightness = 0, + FarZ = 200, + NearZ = 45, + Fov = 140, + } + + ent.Projtexts['RL2'] = { + trigger = 5, + ontrigger = { + brightness = hdr:GetBool() and 5 or 0.3, + }, + pos = vehiclelist.L_RearLampPos, + ang = vehiclelist.L_RearLampAng, + mat = 'effects/flashlight/soft', + col = Color(50,50,50), + brightness = 0, + FarZ = 200, + NearZ = 45, + Fov = 140, + } + end + + if isvector(vehiclelist.R_RearLampPos) and isangle(vehiclelist.R_RearLampAng) then + ent.Projtexts['RR'] = { + trigger = 4, + ontrigger = { + brightness = hdr:GetBool() and 5 or 0.3, + }, + pos = vehiclelist.R_RearLampPos, + ang = vehiclelist.R_RearLampAng, + mat = 'effects/flashlight/soft', + col = Color(30,0,0), + brightness = 0, + FarZ = 200, + NearZ = 45, + Fov = 140, + } + + ent.Projtexts['RR2'] = { + trigger = 5, + ontrigger = { + brightness = hdr:GetBool() and 5 or 0.3, + }, + pos = vehiclelist.R_RearLampPos, + ang = vehiclelist.R_RearLampAng, + mat = 'effects/flashlight/soft', + col = Color(50,50,50), + brightness = 0, + FarZ = 200, + NearZ = 45, + Fov = 140, + } + end + + ent:CallOnRemove('remove_projected_textures', function(vehicle) + for _, proj in pairs(ent.Projtexts) do + local thelamp = proj.projector + if IsValid(thelamp) then + thelamp:Remove() + end + end + end) +end + +local function SetUpLights(vname , ent) + ent.Sprites = {} + + local vehiclelist = list.Get('simfphys_lights')[vname] + if not vehiclelist then ent.SubMaterials = false return end + + ent.LightsEMS = vehiclelist.ems_sprites or false + local hl_col = vehiclelist.ModernLights and {215,240,255} or {220,205,160} + + SetupProjectedTextures(ent , vehiclelist) + + if not vehiclelist or not vehiclelist.SubMaterials then + ent.SubMaterials = false + else + ent.SubMaterials = vehiclelist.SubMaterials + end + + if istable(vehiclelist.ems_sprites) then + ent.PixVisEMS = {} + for i = 1, table.Count(vehiclelist.ems_sprites) do + ent.PixVisEMS[i] = util.GetPixelVisibleHandle() + + ent.LightsEMS[i].material = ent.LightsEMS[i].material and Material(ent.LightsEMS[i].material) or mat2 + end + end + + if istable(vehiclelist.Headlight_sprites) then + for _, data in pairs(vehiclelist.Headlight_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 1 + + if not isvector(data) then + s.color = data.color and data.color or Color(hl_col[1], hl_col[2], hl_col[3], 255) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(hl_col[1], hl_col[2], hl_col[3], 255) + s.material = mat + s.size = 16 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(hl_col[1], hl_col[2], hl_col[3], 150) + s2.material = mat2 + s2.size = 64 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.Rearlight_sprites) then + for _, data in pairs(vehiclelist.Rearlight_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 1 + + if not isvector(data) then + s.color = data.color and data.color or Color(255, 0, 0, 125) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(255, 120, 0, 125) + s2.material = mat2 + s2.size = 12 + table.insert(ent.Sprites, s2) + + s.pos = data + s.color = Color(255, 0, 0, 90) + s.material = mat + s.size = 32 + table.insert(ent.Sprites, s) + end + end + end + + if istable(vehiclelist.Brakelight_sprites) then + for _, data in pairs(vehiclelist.Brakelight_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 4 + + if not isvector(data) then + s.color = data.color and data.color or Color(255, 0, 0, 125) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(255, 0, 0, 90) + s.material = mat + s.size = 32 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(255, 120, 0, 125) + s2.material = mat2 + s2.size = 12 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.Reverselight_sprites) then + for _, data in pairs(vehiclelist.Reverselight_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 5 + + if not isvector(data) then + s.color = data.color and data.color or Color(255, 255, 255, 255) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(255, 255, 255, 150) + s.material = mat + s.size = 12 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(255, 255, 255, 80) + s2.material = mat2 + s2.size = 25 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.FrontMarker_sprites) then + for _, data in pairs(vehiclelist.FrontMarker_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 1 + + if isvector(data) then + s.pos = data + s.color = Color(200, 100, 0, 150) + s.material = mat + s.size = 12 + table.insert(ent.Sprites, s) + end + end + end + + if istable(vehiclelist.RearMarker_sprites) then + for _, data in pairs(vehiclelist.RearMarker_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 1 + + if isvector(data) then + s.pos = data + s.color = Color(205, 0, 0, 150) + s.material = mat + s.size = 12 + table.insert(ent.Sprites, s) + end + end + end + + if istable(vehiclelist.Headlamp_sprites) then + for _, data in pairs(vehiclelist.Headlamp_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 2 + + if not isvector(data) then + s.color = data.color and data.color or Color(hl_col[1], hl_col[2], hl_col[3], 255) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(hl_col[1], hl_col[2], hl_col[3], 255) + s.material = mat + s.size = 16 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(hl_col[1], hl_col[2], hl_col[3], 150) + s2.material = mat2 + s2.size = 64 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.FogLight_sprites) then + for _, data in pairs(vehiclelist.FogLight_sprites) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 3 + + if not isvector(data) then + s.color = data.color and data.color or Color(hl_col[1], hl_col[2], hl_col[3], 255) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 32 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(hl_col[1], hl_col[2], hl_col[3], 200) + s.material = mat2 + s.size = 32 + table.insert(ent.Sprites, s) + end + end + end + + if istable(vehiclelist.Turnsignal_sprites) then + ent.HasTurnSignals = true + + if istable(vehiclelist.Turnsignal_sprites.Left) then + for _, data in pairs(vehiclelist.Turnsignal_sprites.Left) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 6 + + if not isvector(data) then + s.color = data.color and data.color or Color(200, 100, 0, 255) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 24 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(255, 150, 0, 150) + s.material = mat + s.size = 20 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(200, 100, 0, 80) + s2.material = mat2 + s2.size = 70 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.Turnsignal_sprites.Right) then + for _, data in pairs(vehiclelist.Turnsignal_sprites.Right) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 7 + + if not isvector(data) then + s.color = data.color and data.color or Color(200, 100, 0, 255) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 24 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(255, 150, 0, 150) + s.material = mat + s.size = 20 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(200, 100, 0, 80) + s2.material = mat2 + s2.size = 70 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.Turnsignal_sprites.TurnBrakeLeft) then + ent.HasSpecialTurnSignals = true + for _, data in pairs(vehiclelist.Turnsignal_sprites.TurnBrakeLeft) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 8 + + if not isvector(data) then + s.color = data.color and data.color or Color(255, 0, 0, 125) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(255, 60, 0, 90) + s.material = mat + s.size = 40 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(255, 120, 0, 125) + s2.material = mat2 + s2.size = 16 + table.insert(ent.Sprites, s2) + end + end + end + + if istable(vehiclelist.Turnsignal_sprites.TurnBrakeRight) then + ent.HasSpecialTurnSignals = true + for _, data in pairs(vehiclelist.Turnsignal_sprites.TurnBrakeRight) do + local s = {} + s.PixVis = util.GetPixelVisibleHandle() + s.trigger = 9 + + if not isvector(data) then + s.color = data.color and data.color or Color(255, 0, 0, 125) + s.material = data.material and Material(data.material) or mat2 + s.size = data.size and data.size or 16 + s.pos = data.pos + if (data.OnBodyGroups) then s.bodygroups = data.OnBodyGroups end + table.insert(ent.Sprites, s) + else + s.pos = data + s.color = Color(255, 60, 0, 90) + s.material = mat + s.size = 40 + table.insert(ent.Sprites, s) + + local s2 = {} + s2.PixVis = util.GetPixelVisibleHandle() + s2.trigger = s.trigger + s2.pos = data + s2.color = Color(255, 120, 0, 125) + s2.material = mat2 + s2.size = 16 + table.insert(ent.Sprites, s2) + end + end + end + end + + ent.EnableLights = true + table.insert(vtable, ent) +end + +local function DrawEMSLights(ent) + local Time = CurTime() + + if ent.LightsEMS then + + for i = 1, table.Count(ent.LightsEMS) do + if not ent.LightsEMS[i].Damaged then + + local size = ent.LightsEMS[i].size + local LightPos = ent:LocalToWorld(ent.LightsEMS[i].pos) + local Visible = util.PixelVisible(LightPos, 4, ent.PixVisEMS[i]) + local mat = ent.LightsEMS[i].material + local numcolors = table.Count(ent.LightsEMS[i].Colors) + + ent.LightsEMS[i].Timer = ent.LightsEMS[i].Timer or 0 + ent.LightsEMS[i].Index = ent.LightsEMS[i].Index or 0 + + if numcolors > 1 and ent.LightsEMS[i].Timer < Time then + ent.LightsEMS[i].Timer = Time + ent.LightsEMS[i].Speed + ent.LightsEMS[i].Index = ent.LightsEMS[i].Index + 1 + + if ent.LightsEMS[i].Index > numcolors then + ent.LightsEMS[i].Index = 1 + end + end + + local col = ent.LightsEMS[i].Colors[ent.LightsEMS[i].Index] + + if ent.LightsEMS[i].OnBodyGroups then + Visible = ent:BodyGroupIsValid(ent.LightsEMS[i].OnBodyGroups) and Visible or 0 + end + + if Visible and Visible >= 0.6 and col ~= Color(0,0,0,0) then + Visible = (Visible - 0.6) / 0.4 + + render.SetMaterial(mat) + -- they are way too dim by default + -- TODO: render specific 3d sprites from photon along with halos + for _ = 1, 5 do + render.DrawSprite(LightPos, size, size, Color(col['r'], col['g'], col['b'], col['a'] * Visible)) + end + end + end + end + end +end + +hook.Add('Think', 'simfphys_lights_managment', function() + local curtime = CurTime() + + ManageProjTextures() + + if NextCheck < curtime then + NextCheck = curtime + checkinterval + + for _, ent in pairs(ents.FindByClass('gmod_sent_vehicle_fphysics_base')) do + if ent.EnableLights ~= true then + local listname = ent:GetLights_List() + + if listname then + if listname ~= 'no_lights' then + SetUpLights(listname, ent) + else + ent.EnableLights = true + end + end + end + end + end +end) + +hook.Add('PostDrawTranslucentRenderables', 'simfphys_draw_sprites', function() + if SpritesDisabled then return end + if vtable then + for _, ent in pairs(vtable) do + if IsValid(ent) and istable(ent.triggers) and ent:GetPos():DistToSqr(EyePos()) < maxLightSpriteDist then + if ent:GetEMSEnabled() then + DrawEMSLights(ent) + end + + for _, sprite in pairs(ent.Sprites) do + if not sprite.Damaged then + local regTrigger = ent.triggers[ sprite.trigger ] + local typeSpecial = (sprite.trigger == 8 and ent.triggers[ 6 ]) or (sprite.trigger == 9 and ent.triggers[7]) + if typeSpecial then regTrigger = false end + + if regTrigger or typeSpecial then + local LightPos = ent:LocalToWorld(sprite.pos) + local Visible = util.PixelVisible(LightPos, 4, sprite.PixVis) + local s_col = sprite.color + local s_mat = sprite.material + local s_size = sprite.size + + if sprite.bodygroups then + Visible = BodyGroupIsValid(sprite.bodygroups, ent) and Visible or 0 + end + + if Visible and Visible >= 0.6 then + Visible = (Visible - 0.6) / 0.4 + render.SetMaterial(s_mat) + + local c_Alpha = s_col['a'] * Visible + if (sprite.trigger == 6 or sprite.trigger == 7 or typeSpecial) and not ent.forceFlasher then + c_Alpha = c_Alpha * (ent:GetFlasher() ^ 7) + end + + render.DrawSprite(LightPos, s_size, s_size, Color(s_col['r'], s_col['g'], s_col['b'], c_Alpha)) + end + end + end + end + end + end + end +end) + +local glassimpact = Sound('Glass.BulletImpact') +local function spritedamage(length) + if not AllowVisualDamage then return end + + local veh = net.ReadEntity() + if not IsValid(veh) then return end + + local pos = veh:LocalToWorld(net.ReadVector()) + local Rad = net.ReadBool() and 26 or 8 + local curtime = CurTime() + + veh.NextImpactsnd = veh.NextImpactsnd or 0 + + if istable(veh.Sprites) then + + for i, sprite in pairs(veh.Sprites) do + + if not sprite.Damaged then + + local spritepos = veh:LocalToWorld(sprite.pos) + local Dist = (spritepos - pos):Length() + + if Dist < Rad then + veh.Sprites[i].Damaged = true + + if sprite.trigger >= 6 then + veh.turnsignals_damaged = true + end + + local effectdata = EffectData() + effectdata:SetOrigin(spritepos) + util.Effect('GlassImpact', effectdata, true, true) + + if veh.NextImpactsnd < curtime then + veh.NextImpactsnd = curtime + 0.05 + sound.Play(glassimpact, spritepos, 75) + end + end + end + end + end + + if istable(veh.Projtexts) then + + for i, proj in pairs(veh.Projtexts) do + + if not proj.Damaged then + + local lamppos = veh:LocalToWorld(proj.pos) + local Dist = (lamppos - pos):Length() + + if Dist < Rad * 2 then + veh.Projtexts[i].Damaged = true + end + end + end + end + + if istable(veh.LightsEMS) then + + for i = 1, table.Count(veh.LightsEMS) do + + if not veh.LightsEMS[i].Damaged then + + local spritepos = veh:LocalToWorld(veh.LightsEMS[i].pos) + local Dist = (spritepos - pos):Length() + + if Dist < Rad then + veh.LightsEMS[i].Damaged = true + + local effectdata = EffectData() + effectdata:SetOrigin(spritepos) + util.Effect('GlassImpact', effectdata, true, true) + + if veh.NextImpactsnd < curtime then + veh.NextImpactsnd = curtime + 0.05 + sound.Play(glassimpact, spritepos, 75) + end + end + end + end + end +end +net.Receive('simfphys_spritedamage', spritedamage) + +local function spriterepair(length) + local veh = net.ReadEntity() + + if not IsValid(veh) then return end + + veh.turnsignals_damaged = nil + + if istable(veh.Sprites) then + for i in pairs(veh.Sprites) do + veh.Sprites[i].Damaged = false + end + end + + if istable(veh.Projtexts) then + for i in pairs(veh.Projtexts) do + veh.Projtexts[i].Damaged = false + end + end + + if istable(veh.LightsEMS) then + for i = 1, table.Count(veh.LightsEMS) do + veh.LightsEMS[i].Damaged = false + end + end +end +net.Receive('simfphys_lightsfixall', spriterepair) + + +local function turnSignal(ent, turnmode) + if not IsValid(ent) then return end + + ent.lastTurnMode = turnmode + + if turnmode == 0 then + ent.signal_left = false + ent.signal_right = false + + local vehicle = LocalPlayer():GetVehicle() + if IsValid(vehicle) and IsValid(vehicle.vehiclebase) and vehicle.vehiclebase == ent then + vehicle.vehiclebase:EmitSound('simulated_vehicles/sfx/turnsignal_end.ogg') + end + end + + if turnmode == 1 then + ent.signal_left = true + ent.signal_right = true + end + + if turnmode == 2 then + ent.signal_left = true + ent.signal_right = false + end + + if turnmode == 3 then + ent.signal_left = false + ent.signal_right = true + end +end +net.Receive('simfphys_turnsignal', function() + turnSignal(net.ReadEntity(), net.ReadInt(32)) +end) + +local wasNight = false +timer.Create('simfphys.updateProjectorsAtNight', 5, 0, function() + local isNight = StormFox2.Time.IsNight() + if isNight == wasNight then return end + wasNight = isNight + + for i, ent in pairs(vtable) do + if IsValid(ent) then + for _, proj in pairs(ent.Projtexts) do + if IsValid(proj.projector) then + proj.projector:Remove() + proj.projector = nil + proj.LampsActive = nil + end + end + end + end +end) + +hook.Add('NotifyShouldTransmit', 'simfphys.lighting', function(ent, transmit) + if not transmit or not ent.SubMaterials then return end + + timer.Simple(0, function() + UpdateSubMats(ent) + end) +end) + +-- local function setFlasher(veh, on) +-- if not IsValid(veh) then return end +-- veh.signal_left = on +-- veh.signal_right = on +-- veh.forceFlasher = on +-- end + +-- netstream.Hook('simfphys.flash', function(veh, times) +-- for i = 0, times - 1 do +-- timer.Simple(i * 0.25, function() setFlasher(veh, true) end) +-- timer.Simple(i * 0.25 + 0.15, function() setFlasher(veh, false) end) +-- end +-- timer.Simple(times * 0.25 + 0.15, function() +-- turnSignal(veh, veh.lastTurnMode or 0) +-- end) +-- end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/poseparameter.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/poseparameter.lua new file mode 100644 index 0000000..3e97805 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/poseparameter.lua @@ -0,0 +1,162 @@ +local function receiveppdata( length ) + local ent = net.ReadEntity() + + if IsValid( ent ) then + ent.CustomWheels = net.ReadBool() + + if not ent.CustomWheels then + local wheelFL = net.ReadEntity() + local posFL = net.ReadFloat() + local travelFL = net.ReadFloat() + + local wheelFR = net.ReadEntity() + local posFR = net.ReadFloat() + local travelFR = net.ReadFloat() + + local wheelRL = net.ReadEntity() + local posRL = net.ReadFloat() + local travelRL = net.ReadFloat() + + local wheelRR = net.ReadEntity() + local posRR = net.ReadFloat() + local travelRR = net.ReadFloat() + + if not IsValid( wheelFL ) or not IsValid( wheelFR ) or not IsValid( wheelRL ) or not IsValid( wheelRR ) then return end + + ent.pp_data = { + [1] = { + name = "vehicle_wheel_fl_height", + entity = wheelFL, + pos = posFL, + travel = travelFL, + dradius = (wheelFL:BoundingRadius() * 0.28), + }, + [2] = { + name = "vehicle_wheel_fr_height", + entity = wheelFR, + pos = posFR, + travel = travelFR, + dradius = (wheelFR:BoundingRadius() * 0.28), + }, + [3] = { + name = "vehicle_wheel_rl_height", + entity = wheelRL, + pos = posRL, + travel = travelRL, + dradius = (wheelRL:BoundingRadius() * 0.28), + }, + [4] = { + name = "vehicle_wheel_rr_height", + entity = wheelRR, + pos = posRR, + travel = travelRR, + dradius = (wheelRR:BoundingRadius() * 0.28), + }, + } + end + end +end +net.Receive("simfphys_send_ppdata", receiveppdata) + +simfphys.anims = { + gear = function(ply, arg) + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_Clavicle'] = Angle(-16.6, 1.8, -13.2), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0.0, 0.0, -33.3), + }, 0.3) + timer.Simple(0.3, function() + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_Clavicle'] = Angle(0,0,0), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0,0,0), + }, 0.3) + end) + end, + brake = function(ply, arg) + if arg then + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_Clavicle'] = Angle(-20, 1.8, -17), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0.0, 0.0, -10), + }, 0.3) + else + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_Clavicle'] = Angle(0,0,0), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0,0,0), + }, 0.3) + end + end, + engine = function(ply, arg) + if arg then + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(3.0, -3.4, 9.6), + ['ValveBiped.Bip01_R_Forearm'] = Angle(-2.3, 26.2, 7.4), + ['ValveBiped.Bip01_R_Hand'] = Angle(32.0, 1.4, -70.6), + }, 0.3) + else + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(0,0,0), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0,0,0), + ['ValveBiped.Bip01_R_Hand'] = Angle(0,0,0), + }, 0.3) + end + end, + rhandle = function(ply, arg) + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(3.0, -3.0, 0.0), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0.8, 9.5, 0.0), + ['ValveBiped.Bip01_R_Hand'] = Angle(0.0, -18.0, 17.8), + }, 0.2) + timer.Simple(0.2, function() + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(0,0,0), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0,0,0), + ['ValveBiped.Bip01_R_Hand'] = Angle(0,0,0), + }, 0.2) + end) + end, + lhandle = function(ply, arg) + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_L_UpperArm'] = Angle(-3.0, -6.0, 0.0), + ['ValveBiped.Bip01_L_Forearm'] = Angle(0.8, 10.3, 0.0), + ['ValveBiped.Bip01_L_Hand'] = Angle(0.0, -18.0, -17.8), + }, 0.2) + timer.Simple(0.2, function() + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_L_UpperArm'] = Angle(0,0,0), + ['ValveBiped.Bip01_L_Forearm'] = Angle(0,0,0), + ['ValveBiped.Bip01_L_Hand'] = Angle(0,0,0), + }, 0.2) + end) + end, + lfoot = function(ply, arg) + if arg then + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_L_Foot'] = Angle(0.0, 25.0, 0.0), + }, 0.2) + else + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_L_Foot'] = Angle(0.0, 0.0, 0.0), + }, 0.2) + end + end, + rfoot = function(ply, arg) + if arg < 0 then + -- brake + arg = -arg + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_Thigh'] = Angle(-4.2, 0.0, 0.0) * arg, + ['ValveBiped.Bip01_R_Foot'] = Angle(-13.0, 15.0, 0.0) * arg, + }, 0.2) + else + -- throttle + octolib.manipulateBones(ply, { + ['ValveBiped.Bip01_R_Foot'] = Angle(0.0, 25.0, 0.0) * arg, + ['ValveBiped.Bip01_R_Thigh'] = Angle(0,0,0), + }, 0.2) + end + end, +} + +netstream.Hook('simfphys.anim', function(ply, id, arg) + local anim = simfphys.anims[id] + if anim and IsValid(ply) then anim(ply, arg) end +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/seatcontrols.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/seatcontrols.lua new file mode 100644 index 0000000..980ad4b --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/seatcontrols.lua @@ -0,0 +1,108 @@ +local pressedkeys = {} +local chatopen = false +local spawnmenuopen = false +local contextmenuopen = false + +local requests = { + [KEY_1] = 0, + [KEY_2] = 1, + [KEY_3] = 2, + [KEY_4] = 3, + [KEY_5] = 4, + [KEY_6] = 5, + [KEY_7] = 6, + [KEY_8] = 7, + [KEY_9] = 8, + [KEY_0] = 9, +} + +local function lockControls( bLock ) + net.Start("simfphys_blockcontrols") + net.WriteBool( bLock ) + net.SendToServer() +end + +hook.Add( "OnContextMenuOpen", "simfphys_seatswitching_cmenuopen", function() + contextmenuopen = true + lockControls( true ) +end) + +hook.Add( "OnContextMenuClose", "simfphys_seatswitching_cmenuclose", function() + contextmenuopen = false + lockControls( false ) +end) + +hook.Add( "OnSpawnMenuOpen", "simfphys_seatswitching_menuopen", function() + spawnmenuopen = true + lockControls( true ) +end) + +hook.Add( "OnSpawnMenuClose", "simfphys_seatswitching_menuclose", function() + spawnmenuopen = false + lockControls( false ) +end) + +hook.Add( "FinishChat", "simfphys_seatswitching_chatend", function() + chatopen = false + lockControls( false ) +end) + +hook.Add( "StartChat", "simfphys_seatswitching_chatstart", function() + chatopen = true + lockControls( true ) +end) + +hook.Add( "PlayerBindPress", "simfphys_seatswitching", function(ply, bind, pressed) + if bind:sub(1,4) == 'slot' then + if ply:KeyDown(IN_WALK) then return true end + end +end) + +hook.Add( "Think", "simfphys_seatswitching", function() + if chatopen or spawnmenuopen or contextmenuopen then return end + + local ply = LocalPlayer() + local vehicle = ply:GetVehicle() + if not IsValid( vehicle ) then return end + + local vehiclebase = vehicle.vehiclebase + if not IsValid( vehiclebase ) then return end + + for key, request in pairs( requests ) do + local keydown = LocalPlayer():KeyDown(IN_WALK) and input.IsKeyDown( key ) + + if pressedkeys[key] ~= keydown then + pressedkeys[key] = keydown + if keydown then + net.Start("simfphys_request_seatswitch") + net.WriteInt( request, 32 ) + net.SendToServer() + end + end + end +end ) + +function simfphys.GetSeatProperty(seat, property) + + if not IsValid(seat) then return end + local car = seat:GetParent() + if not IsValid(car) or car:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + if seat[property] ~= nil then return seat[property] end + + car.spawnlist = car.spawnlist or list.Get('simfphys_vehicles')[car:GetSpawn_List()] + local lst = car.spawnlist.Members + local lPos = car:WorldToLocal(seat:GetPos()) + for _,v in ipairs(lst.PassengerSeats) do + if lPos:DistToSqr(v.pos) < 10 then + if v[property] ~= nil then + seat[property] = v[property] + else + seat[property] = false + end + break + end + end + + return seat[property] + +end diff --git a/garrysmod/addons/feature-cars/lua/simfphys/client/tab.lua b/garrysmod/addons/feature-cars/lua/simfphys/client/tab.lua new file mode 100644 index 0000000..3c561d6 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/client/tab.lua @@ -0,0 +1,702 @@ +local auto = CreateClientConVar( "cl_simfphys_auto", 1 , true, true ) +local sport = CreateClientConVar( "cl_simfphys_sport", 0 , true, true ) +local sanic = CreateClientConVar( "cl_simfphys_sanic", 0 , true, true ) +local ctenable = CreateClientConVar( "cl_simfphys_ctenable", 1 , true, true ) +local ctmul = CreateClientConVar( "cl_simfphys_ctmul", 0.7 , true, true ) +local ctang = CreateClientConVar( "cl_simfphys_ctang", 15 , true, true ) +local hud = CreateClientConVar( "cl_simfphys_hud", "1", true, false ) +local alt_hud = CreateClientConVar( "cl_simfphys_althud", "1", true, false ) +local alt_hud_arc = CreateClientConVar( "cl_simfphys_althud_arcs", "0", true, false ) + +local hud_x = CreateClientConVar( "cl_simfphys_hud_offset_x", "0", true, false ) +local hud_y = CreateClientConVar( "cl_simfphys_hud_offset_y", "0", true, false ) +local hud_mph = CreateClientConVar( "cl_simfphys_hudmph", "0", true, false ) +local hud_mpg = CreateClientConVar( "cl_simfphys_hudmpg", "0", true, false ) +local hud_realspeed = CreateClientConVar( "cl_simfphys_hudrealspeed", "0", true, false ) +local autostart = CreateClientConVar( "cl_simfphys_autostart", "1", true, true ) + +-- local mousesteer = CreateClientConVar( "cl_simfphys_mousesteer", "0", true, true ) +local mssensitivity = CreateClientConVar( "cl_simfphys_ms_sensitivity", "1", true, true ) +local msretract = CreateClientConVar( "cl_simfphys_ms_return", "1", true, true ) +local msdeadzone = CreateClientConVar( "cl_simfphys_ms_deadzone", "3", true, true ) +local msexponent = CreateClientConVar( "cl_simfphys_ms_exponent", "1.5", true, true ) +local mslockpitch = CreateClientConVar( "cl_simfphys_ms_lockpitch", "0", true, true ) +local mshud = CreateClientConVar( "cl_simfphys_ms_hud", "1", true, false ) +local k_msfreelook = CreateClientConVar( "cl_simfphys_ms_keyfreelook", KEY_Y, true, true ) +local mslockedpitch = CreateClientConVar( "cl_simfphys_ms_lockedpitch", "5", true, true ) + +local overwrite = CreateClientConVar( "cl_simfphys_overwrite", 0, true, true ) +local smoothsteer = CreateClientConVar( "cl_simfphys_smoothsteer", 0, true, true ) +local steerspeed = CreateClientConVar( "cl_simfphys_steerspeed", 8, true, true ) +local faststeerang = CreateClientConVar( "cl_simfphys_steerangfast", 10, true, true ) +local fadespeed = CreateClientConVar( "cl_simfphys_fadespeed", 535, true, true ) + +CreateClientConVar( "cl_simfphys_hidesprites", "0", true, false ) +CreateClientConVar( "cl_simfphys_spritedamage", "1", true, false ) +CreateClientConVar( "cl_simfphys_frontlamps", "1", true, false ) +CreateClientConVar( "cl_simfphys_rearlamps", "1", true, false ) +CreateClientConVar( "cl_simfphys_shadows", "0", true, false ) + +local function simplebinder_old( x, y, tbl, num, parent, sizex, sizey) + local kentry = tbl[num] + local key = kentry[1] + local setdefault = key:GetInt() + + local sizex = sizex or 400 + local sizey = sizey or 40 + + local Shape = vgui.Create( "DShape", parent) + Shape:SetType( "Rect" ) + Shape:SetPos( x, y ) + Shape:SetSize( sizex, sizey ) + Shape:SetColor( Color( 0, 0, 0, 255 ) ) + + local Shape = vgui.Create( "DShape", parent) + Shape:SetType( "Rect" ) + Shape:SetPos( x + 1, y + 1 ) + Shape:SetSize( sizex - 2, sizey - 2 ) + Shape:SetColor( Color( 241, 241, 241, 255 ) ) + + local binder = vgui.Create( "DBinder", parent) + binder:SetPos( sizex * 0.5 + x, y ) + binder:SetSize( sizex * 0.5, sizey ) + binder:SetValue( setdefault ) + function binder:SetSelectedNumber( num ) + self.m_iSelectedNumber = num + self:ConVarChanged( num ) + self:UpdateText() + self:OnChange( num ) + + key:SetInt( num ) + end + + local TextLabel = vgui.Create( "DPanel", parent) + TextLabel:SetPos( x, y ) + TextLabel:SetSize( sizex * 0.5, sizey ) + TextLabel.Paint = function() + draw.SimpleText( kentry[3], "DSimfphysFont", sizex * 0.25, 20, Color( 100, 100, 100, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + end + return binder +end + +local function createcheckbox(x, y, label, command, parent, default) + local boxy = vgui.Create( "DCheckBoxLabel", parent) + boxy:SetParent( parent ) + boxy:SetPos( x, y ) + boxy:SetText( label ) + boxy:SetConVar( command ) + boxy:SetValue( default ) + boxy:SizeToContents() + return boxy +end + +local function createslider(x, y, sizex, sizey, label, command, parent,min,max,default) + local slider = vgui.Create( "DNumSlider", parent) + slider:SetPos( x, y ) + slider:SetSize( sizex, sizey ) + slider:SetText( label ) + slider:SetMin( min ) + slider:SetMax( max ) + slider:SetDecimals( 2 ) + slider:SetConVar( command ) + slider:SetValue( default ) + return slider +end + +local function buildclientsettingsmenu( self ) + local Shape = vgui.Create( "DShape", self.PropPanel) + Shape:SetType( "Rect" ) + Shape:SetPos( 20, 20 ) + Shape:SetSize( 350, 180 ) + Shape:SetColor( Color( 0, 0, 0, 200 ) ) + createcheckbox(25,25,"Show Hud","cl_simfphys_hud",self.PropPanel,hud:GetInt()) + createcheckbox(210,25,"Racing Hud","cl_simfphys_althud",self.PropPanel,alt_hud:GetInt()) + createcheckbox(210,45,"Draw Arcs\n(will cause problems\nwith multicore!)","cl_simfphys_althud_arcs",self.PropPanel,alt_hud_arc:GetInt()) + createcheckbox(25,45,"MPH instead of KMH","cl_simfphys_hudmph",self.PropPanel,hud_mph:GetInt()) + createcheckbox(25,65,"Speed relative to \nplayersize instead \nworldsize","cl_simfphys_hudrealspeed",self.PropPanel,hud_realspeed:GetInt()) + createcheckbox(25,110,"Fuel consumption \nin MPG instead \nof L/100KM","cl_simfphys_hudmpg",self.PropPanel,hud_mpg:GetInt()) + createslider(30,155,345,20,"Hud offset X","cl_simfphys_hud_offset_x",self.PropPanel,-1,1,hud_x:GetFloat()) + createslider(30,175,345,20,"Hud offset Y","cl_simfphys_hud_offset_y",self.PropPanel,-1,1,hud_y:GetFloat()) + + local Shape = vgui.Create( "DShape", self.PropPanel) + Shape:SetType( "Rect" ) + Shape:SetPos( 20, 210 ) + Shape:SetSize( 350, 85 ) + Shape:SetColor( Color( 0, 0, 0, 200 ) ) + createcheckbox(25,215,"Hide Sprites","cl_simfphys_hidesprites",self.PropPanel,0) + createcheckbox(210,215,"Allow light damaging","cl_simfphys_spritedamage",self.PropPanel,0) + createcheckbox(25,235,"Front Projected Textures","cl_simfphys_frontlamps",self.PropPanel,0) + createcheckbox(25,255,"Rear Projected Textures","cl_simfphys_rearlamps",self.PropPanel,0) + createcheckbox(25,275,"Enable Shadows","cl_simfphys_shadows",self.PropPanel,0) + + local Shape = vgui.Create( "DShape", self.PropPanel) + Shape:SetType( "Rect" ) + Shape:SetPos( 20, 305 ) + Shape:SetSize( 350, 85 ) + Shape:SetColor( Color( 0, 0, 0, 200 ) ) + createcheckbox(25,310,"Always Fullthrottle","cl_simfphys_sanic",self.PropPanel,sanic:GetInt()) + createcheckbox(25,330,"Engine Auto Start/Stop","cl_simfphys_autostart",self.PropPanel,autostart:GetInt()) + createcheckbox(25,350,"Automatic Transmission","cl_simfphys_auto",self.PropPanel,auto:GetInt()) + createcheckbox(25,370,"Automatic Sportmode (late up and downshifts)","cl_simfphys_sport",self.PropPanel,sport:GetInt()) + + local Shape = vgui.Create( "DShape", self.PropPanel) + Shape:SetType( "Rect" ) + Shape:SetPos( 20, 400 ) + Shape:SetSize( 350, 115 ) + Shape:SetColor( Color( 0, 0, 0, 200 ) ) + + local ctitem_1 = createcheckbox(25,405,"Enable Countersteer","cl_simfphys_ctenable",self.PropPanel,ctenable:GetInt()) + local ctitem_2 = createslider(30,425,345,40,"Countersteer Mul","cl_simfphys_ctmul",self.PropPanel,0.1,2,ctmul:GetFloat()) + local ctitem_3 = createslider(30,445,345,40,"Countersteer MaxAng","cl_simfphys_ctang",self.PropPanel,1,90,ctang:GetFloat()) + + local Reset = vgui.Create( "DButton" ) + Reset:SetParent( self.PropPanel ) + Reset:SetText( "Reset" ) + Reset:SetPos( 25, 485 ) + Reset:SetSize( 340, 25 ) + Reset.DoClick = function() + ctitem_1:SetValue( 1 ) + ctitem_2:SetValue( 0.7 ) + ctitem_3:SetValue( 15 ) + ctenable:SetInt( 1 ) + ctmul:SetFloat( 0.7 ) + ctang:SetFloat( 15 ) + end + + local Shape = vgui.Create( "DShape", self.PropPanel) + Shape:SetType( "Rect" ) + Shape:SetPos( 20, 525 ) + Shape:SetSize( 350, 165 ) + Shape:SetColor( Color( 0, 0, 0, 200 ) ) + + local st_item_1 = createcheckbox(25,530,"Use these settings\n(you need to re-enter the vehicle)","cl_simfphys_overwrite",self.PropPanel,overwrite:GetInt()) + local st_item_2 = createslider(30,550,345,40,"steer speed","cl_simfphys_steerspeed",self.PropPanel,1,16,steerspeed:GetFloat()) + local st_item_3 = createslider(30,570,345,40,"fast speed steer angle","cl_simfphys_steerangfast",self.PropPanel,0,90,faststeerang:GetFloat()) + local st_item_4 = createslider(30,595,345,40,"fade speed(units/seconds)\nfor fast speed steer angle","cl_simfphys_fadespeed",self.PropPanel,1,5000,fadespeed:GetFloat()) + local st_item_5 = createcheckbox(25,635,"extra smooth steering","cl_simfphys_smoothsteer",self.PropPanel,smoothsteer:GetInt()) + + local Reset = vgui.Create( "DButton" ) + Reset:SetParent( self.PropPanel ) + Reset:SetText( "Reset" ) + Reset:SetPos( 25, 660 ) + Reset:SetSize( 340, 25 ) + Reset.DoClick = function() + st_item_1:SetValue( 0 ) + st_item_2:SetValue( 8 ) + st_item_3:SetValue( 10 ) + st_item_4:SetValue( 535 ) + st_item_5:SetValue( 0 ) + + overwrite:SetInt( 0 ) + steerspeed:SetFloat( 8 ) + faststeerang:SetFloat( 10 ) + fadespeed:SetFloat( 535 ) + smoothsteer:SetInt( 0 ) + end +end + + +-- local function buildcontrolsmenu( self ) +-- local Background = vgui.Create( "DShape", self.PropPanel) +-- Background:SetType( "Rect" ) +-- Background:SetPos( 20, 40 ) +-- Background:SetColor( Color( 0, 0, 0, 200 ) ) + +-- local TextLabel = vgui.Create( "DPanel", self.PropPanel) +-- TextLabel:SetPos( 0, 0 ) +-- TextLabel:SetSize( 600, 40 ) +-- TextLabel.Paint = function() +-- draw.SimpleTextOutlined( "Чтобы настройки изменились, нужно зайти в машину заново!", "DSimfphysFont_hint", 300, 20, Color( 255, 0, 0, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER , 1,Color( 0, 0, 0, 255 ) ) +-- end + +-- local yy = 45 +-- local binders = {} +-- for i = 1, table.Count( k_list ) do +-- binders[i] = simplebinder(25,yy,k_list,i,self.PropPanel) +-- yy = yy + 45 +-- end + +-- local ResetButton = vgui.Create( "DButton" ) +-- ResetButton:SetParent( self.PropPanel ) +-- ResetButton:SetText( "Reset" ) +-- ResetButton:SetPos( 25, yy + 10 ) +-- ResetButton:SetSize( 500, 25 ) +-- ResetButton.DoClick = function() +-- for i = 1, table.Count( binders ) do +-- local kentry = k_list[i] +-- local key = kentry[1] +-- local default = kentry[2] + +-- key:SetInt( default ) +-- binders[i]:SetValue( default ) +-- end +-- end + +-- Background:SetSize( 510, yy ) +-- end + +local function buildmsmenu( self ) + local Shape = vgui.Create( "DShape", self.PropPanel) + Shape:SetType( "Rect" ) + Shape:SetPos( 20, 20 ) + Shape:SetSize( 350, 310 ) + Shape:SetColor( Color( 0, 0, 0, 200 ) ) + + -- local msitem_1 = createcheckbox(25,25,"Enable Mouse Steering","cl_simfphys_mousesteer",self.PropPanel,mousesteer:GetInt()) + local msitem_2 = createcheckbox(25,55,"Lock Pitch View","cl_simfphys_ms_lockpitch",self.PropPanel,mslockpitch:GetInt()) + local msitem_8 = createcheckbox(25,85,"Show Hud","cl_simfphys_ms_hud",self.PropPanel,mshud:GetInt()) + + + local msitem_9 = createslider(60,50,315,40,"","cl_simfphys_ms_lockedpitch",self.PropPanel,-90,90,mslockedpitch:GetFloat()) + + local msitem_4 = createslider(30,110,345,40,"Deadzone","cl_simfphys_ms_deadzone",self.PropPanel,0,16,msdeadzone:GetFloat()) + local msitem_5 = createslider(30,140,345,40,"Exponent","cl_simfphys_ms_exponent",self.PropPanel,1,4,msexponent:GetFloat()) + local msitem_6 = createslider(30,170,345,40,"Sensitivity","cl_simfphys_ms_sensitivity",self.PropPanel,0.01,10,mssensitivity:GetFloat()) + local msitem_7 = createslider(30,200,345,40,"Return Speed","cl_simfphys_ms_return",self.PropPanel,0,10,msretract:GetFloat()) + + local msitem_3 = simplebinder_old(25,240,{{k_msfreelook,KEY_Y,"Unlock View"}},1,self.PropPanel,340, 40) + + local DermaButton = vgui.Create( "DButton" ) + DermaButton:SetParent( self.PropPanel ) + DermaButton:SetText( "Reset" ) + DermaButton:SetPos( 25, 300 ) + DermaButton:SetSize( 340, 25 ) + DermaButton.DoClick = function() + msitem_1:SetValue( 0 ) + msitem_2:SetValue( 0 ) + msitem_3:SetValue( KEY_Y ) + msitem_4:SetValue( 3 ) + msitem_5:SetValue( 1.5 ) + msitem_6:SetValue( 1 ) + msitem_7:SetValue( 1 ) + msitem_8:SetValue( 1 ) + msitem_9:SetValue( 5 ) + + mshud:SetInt( 1 ) + -- mousesteer:SetInt( 0 ) + mssensitivity:SetInt( 1 ) + msretract:SetInt( 1 ) + msdeadzone:SetFloat( 3 ) + msexponent:SetFloat( 1.5 ) + mslockpitch:SetInt( 0 ) + k_msfreelook:SetInt( KEY_Y ) + mslockedpitch:SetFloat( 5 ) + end +end + +local function buildserversettingsmenu( self ) + local Background = vgui.Create( "DShape", self.PropPanel) + Background:SetType( "Rect" ) + Background:SetPos( 20, 20 ) + Background:SetColor( Color( 0, 0, 0, 200 ) ) + local y = 0 + + if LocalPlayer():IsSuperAdmin() then + y = y + 25 + local CheckBoxTeam = vgui.Create( "DCheckBoxLabel", self.PropPanel) + CheckBoxTeam:SetPos( 25, y ) + CheckBoxTeam:SetText( "Disallow players of different teams to enter the same vehicle" ) + CheckBoxTeam:SetValue( GetConVar( "sv_simfphys_teampassenger" ) :GetInt() ) + CheckBoxTeam:SizeToContents() + + y = y + 25 + local CheckBoxDamage = vgui.Create( "DCheckBoxLabel", self.PropPanel) + CheckBoxDamage:SetPos( 25, y ) + CheckBoxDamage:SetText( "Enable Damage" ) + CheckBoxDamage:SetValue( GetConVar( "sv_simfphys_enabledamage" ) :GetInt() ) + CheckBoxDamage:SizeToContents() + + y = y + 18 + local DamageMul = vgui.Create( "DNumSlider", self.PropPanel) + DamageMul:SetPos( 30, y ) + DamageMul:SetSize( 345, 30 ) + DamageMul:SetText( "Damage Multiplicator" ) + DamageMul:SetMin( 0 ) + DamageMul:SetMax( 10 ) + DamageMul:SetDecimals( 3 ) + DamageMul:SetValue( GetConVar( "sv_simfphys_damagemultiplicator" ):GetFloat() ) + + y = y + 32 + local CheckBoxpDamage = vgui.Create( "DCheckBoxLabel", self.PropPanel) + CheckBoxpDamage:SetPos( 25, y ) + CheckBoxpDamage:SetText( "Enable Player Damage (On Collision)" ) + CheckBoxpDamage:SetValue( GetConVar( "sv_simfphys_playerdamage" ) :GetInt() ) + CheckBoxpDamage:SizeToContents() + + y = y + 25 + local GibRemoveTimer = vgui.Create( "DNumSlider", self.PropPanel) + GibRemoveTimer:SetPos( 30, y ) + GibRemoveTimer:SetSize( 345, 30 ) + GibRemoveTimer:SetText( "Gib Lifetime\n(0 = never remove)" ) + GibRemoveTimer:SetMin( 0 ) + GibRemoveTimer:SetMax( 3600 ) + GibRemoveTimer:SetDecimals( 0 ) + GibRemoveTimer:SetValue( GetConVar( "sv_simfphys_gib_lifetime" ):GetInt() ) + + y = y + 45 + local CheckBoxFuel = vgui.Create( "DCheckBoxLabel", self.PropPanel) + CheckBoxFuel:SetPos( 25, y ) + CheckBoxFuel:SetText( "Enable Fuelsystem" ) + CheckBoxFuel:SetValue( GetConVar( "sv_simfphys_fuel" ) :GetInt() ) + CheckBoxFuel:SizeToContents() + + y = y + 18 + local ScaleFuel = vgui.Create( "DNumSlider", self.PropPanel) + ScaleFuel:SetPos( 30, y ) + ScaleFuel:SetSize( 345, 30 ) + ScaleFuel:SetText( "Fuel tank size multiplier" ) + ScaleFuel:SetMin( 0 ) + ScaleFuel:SetMax( 1 ) + ScaleFuel:SetDecimals( 2 ) + ScaleFuel:SetValue( GetConVar( "sv_simfphys_fuelscale" ):GetFloat() ) + + y = y + 45 + local tractionLabel = vgui.Create( "DLabel", self.PropPanel ) + tractionLabel:SetPos( 25, y ) + tractionLabel:SetText( "Traction Multiplicator for:" ) + tractionLabel:SizeToContents() + + local NewTractionData = {} + local DemSliders = {} + y = y + 15 + for k, v in pairs( simfphys.TractionData ) do + DemSliders[k] = vgui.Create( "DNumSlider", self.PropPanel) + DemSliders[k]:SetPos( 30, y ) + DemSliders[k]:SetSize( 345, 30 ) + DemSliders[k]:SetText( k ) + DemSliders[k]:SetMin( 0 ) + DemSliders[k]:SetMax( 2 ) + DemSliders[k]:SetDecimals( 2 ) + DemSliders[k]:SetValue( simfphys[k]:GetFloat() ) + DemSliders[k].OnValueChanged = function( item, value ) + NewTractionData[ k ] = value + end + y = y + 25 + end + + y = y + 30 + local DermaButton = vgui.Create( "DButton" ) + DermaButton:SetParent( self.PropPanel ) + DermaButton:SetText( "Apply" ) + DermaButton:SetPos( 25, y - 10 ) + DermaButton:SetSize( 340, 25 ) + DermaButton.DoClick = function() + net.Start("simfphys_settings") + net.WriteBool( CheckBoxDamage:GetChecked() ) + net.WriteFloat( GibRemoveTimer:GetValue() ) + net.WriteFloat( DamageMul:GetValue() ) + net.WriteBool( CheckBoxpDamage:GetChecked() ) + net.WriteBool( CheckBoxFuel:GetChecked() ) + net.WriteFloat( ScaleFuel:GetValue() ) + net.WriteTable( NewTractionData ) + net.WriteBool( CheckBoxTeam:GetChecked() ) + net.SendToServer() + end + + y = y + 30 + local DermaButton = vgui.Create( "DButton" ) + DermaButton:SetParent( self.PropPanel ) + DermaButton:SetText( "Reset" ) + DermaButton:SetPos( 25, y - 10 ) + DermaButton:SetSize( 340, 25 ) + DermaButton.DoClick = function() + + NewTractionData["ice"] = 0.35 + NewTractionData["gmod_ice"] = 0.1 + NewTractionData["slipperyslime"] = 0.2 + NewTractionData["snow"] = 0.7 + NewTractionData["grass"] = 1 + NewTractionData["sand"] = 1 + NewTractionData["dirt"] = 1 + NewTractionData["concrete"] = 1 + NewTractionData["metal"] = 1 + NewTractionData["glass"] = 1 + NewTractionData["gravel"] = 1 + NewTractionData["rock"] = 1 + NewTractionData["wood"] = 1 + + for k, v in pairs( NewTractionData ) do + DemSliders[k]:SetValue( v ) + end + + CheckBoxDamage:SetValue( 1 ) + GibRemoveTimer:SetValue( 120 ) + DamageMul:SetValue( 1 ) + CheckBoxpDamage:SetValue( 1 ) + CheckBoxFuel:SetValue( 1 ) + ScaleFuel:SetValue( 0.1 ) + CheckBoxTeam:SetValue( 0 ) + + net.Start("simfphys_settings") + net.WriteBool( true ) + net.WriteFloat( 120 ) + net.WriteFloat( 1 ) + net.WriteBool( true ) + net.WriteBool( true ) + net.WriteFloat( 0.1 ) + net.WriteTable( NewTractionData ) + net.WriteBool( false ) + net.SendToServer() + end + else + y = y + 25 + local Label = vgui.Create( "DLabel", self.PropPanel ) + Label:SetPos( 30, y ) + Label:SetText( "Damage is "..((GetConVar( "sv_simfphys_enabledamage" ):GetInt() > 0) and "enabled" or "disabled") ) + Label:SizeToContents() + + y = y + 25 + local Label = vgui.Create( "DLabel", self.PropPanel ) + Label:SetPos( 30, y ) + Label:SetText( "Damage Multiplicator is: "..GetConVar( "sv_simfphys_damagemultiplicator" ):GetFloat() ) + Label:SizeToContents() + + y = y + 25 + local yes = "Players can take damage from collisions" + local no = "Players can't take damage from collisions" + local Label = vgui.Create( "DLabel", self.PropPanel ) + Label:SetPos( 30, y ) + Label:SetText( GetConVar( "sv_simfphys_playerdamage" ):GetBool() and yes or no ) + Label:SizeToContents() + + y = y + 25 + local Label = vgui.Create( "DLabel", self.PropPanel ) + local lifetime = GetConVar( "sv_simfphys_gib_lifetime" ):GetInt() + Label:SetPos( 30, y ) + Label:SetText( (lifetime > 0) and ("Gib Lifetime = "..lifetime.." seconds") or "Gibs never despawn" ) + Label:SizeToContents() + + y = y + 25 + local Label = vgui.Create( "DLabel", self.PropPanel ) + Label:SetPos( 30, y ) + Label:SetText( "Vehicles "..(GetConVar( "sv_simfphys_fuel" ):GetBool() and "are running on fuel" or "don't use fuel") ) + Label:SizeToContents() + + y = y + 25 + local Label = vgui.Create( "DLabel", self.PropPanel ) + local fuelscale = math.Round( GetConVar( "sv_simfphys_fuelscale" ):GetFloat() , 3 ) + Label:SetPos( 30, y ) + Label:SetText( "Fuel tank size multiplier is: "..fuelscale ) + Label:SizeToContents() + + if GetConVar( "sv_simfphys_teampassenger" ):GetBool() then + y = y + 25 + local Label = vgui.Create( "DLabel", self.PropPanel ) + Label:SetPos( 30, y ) + Label:SetText( "Only players of the same team can enter the same vehicle" ) + Label:SizeToContents() + end + + y = y + 40 + local Label = vgui.Create( "DLabel", self.PropPanel ) + Label:SetPos( 30, y ) + Label:SetText( "Traction multiplier for..." ) + Label:SizeToContents() + + y = y + 15 + for k, v in pairs( simfphys.TractionData ) do + local tractionLabel = vgui.Create( "DLabel", self.PropPanel ) + tractionLabel:SetPos( 105, y ) + tractionLabel:SetText( k ) + tractionLabel:SizeToContents() + + local tractionLabel = vgui.Create( "DLabel", self.PropPanel ) + tractionLabel:SetPos( 170, y ) + tractionLabel:SetText( "=" ) + tractionLabel:SizeToContents() + + local tractionLabel = vgui.Create( "DLabel", self.PropPanel ) + tractionLabel:SetPos( 185, y ) + tractionLabel:SetText( math.Round(v,2) ) + tractionLabel:SizeToContents() + + y = y + 25 + end + y = y - 25 + end + + Background:SetSize( 350, y ) +end + + +hook.Add( "SimfphysPopulateVehicles", "AddEntityContent", function( pnlContent, tree, node ) + + local Categorised = {} + + -- Add this list into the tormoil + local Vehicles = list.Get( "simfphys_vehicles" ) + if Vehicles then + for k, v in pairs( Vehicles ) do + + v.Category = v.Category or "Other" + Categorised[ v.Category ] = Categorised[ v.Category ] or {} + v.ClassName = k + v.PrintName = v.Name + table.insert( Categorised[ v.Category ], v ) + + end + end + -- + -- Add a tree node for each category + -- + for CategoryName, v in SortedPairs( Categorised ) do + + -- Add a node to the tree + local node = tree:AddNode( CategoryName, "icon16/bricks.png" ) + + -- When we click on the node - populate it using this function + node.DoPopulate = function( self ) + + -- If we've already populated it - forget it. + if self.PropPanel then return end + + -- Create the container panel + self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + self.PropPanel:SetVisible( false ) + self.PropPanel:SetTriggerSpawnlistChange( false ) + + for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do + + spawnmenu.CreateContentIcon( "simfphys_vehicles", self.PropPanel, { + nicename = ent.PrintName or ent.ClassName, + spawnname = ent.ClassName, + material = "entities/"..ent.ClassName..".png", + admin = ent.AdminOnly + } ) + + end + + end + + -- If we click on the node populate it and switch to it. + node.DoClick = function( self ) + + self:DoPopulate() + pnlContent:SwitchPanel( self.PropPanel ) + + end + + end + + -- KEYBOARD + -- local node = tree:AddNode( "Controls", "icon16/keyboard.png" ) + -- node.DoPopulate = function( self ) + -- if self.PropPanel then return end + + -- self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + -- self.PropPanel:SetVisible( false ) + -- self.PropPanel:SetTriggerSpawnlistChange( false ) + + -- buildcontrolsmenu( self ) + -- end + -- node.DoClick = function( self ) + -- self:DoPopulate() + -- pnlContent:SwitchPanel( self.PropPanel ) + -- end + + -- MOUSE STEERING + local node = tree:AddNode( "Mouse Steering", "icon16/mouse.png" ) + node.DoPopulate = function( self ) + if self.PropPanel then return end + + self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + self.PropPanel:SetVisible( false ) + self.PropPanel:SetTriggerSpawnlistChange( false ) + + buildmsmenu( self ) + end + node.DoClick = function( self ) + self:DoPopulate() + pnlContent:SwitchPanel( self.PropPanel ) + end + + -- JOYSTICK + if istable( jcon ) and file.Exists("lua/bin/gmcl_joystick_win32.dll", "GAME") then + + local node = tree:AddNode( "Joystick Configuration", "icon16/joystick.png" ) + node.DoClick = function( self ) + RunConsoleCommand("joyconfig") + end + end + + -- CLIENT SETTINGS + -- local node = tree:AddNode( "Client Settings", "icon16/wrench.png" ) + -- node.DoPopulate = function( self ) + -- if self.PropPanel then return end + -- + -- self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + -- self.PropPanel:SetVisible( false ) + -- self.PropPanel:SetTriggerSpawnlistChange( false ) + -- + -- buildclientsettingsmenu( self ) + -- end + -- node.DoClick = function( self ) + -- self:DoPopulate() + -- pnlContent:SwitchPanel( self.PropPanel ) + -- end + + -- SERVER SETTINGS + local node = tree:AddNode( "Server Settings", "icon16/wrench_orange.png" ) + node.DoPopulate = function( self ) + self.PropPanel = vgui.Create( "ContentContainer", pnlContent ) + self.PropPanel:SetVisible( false ) + self.PropPanel:SetTriggerSpawnlistChange( false ) + + buildserversettingsmenu( self ) + end + node.DoClick = function( self ) + self:DoPopulate() + pnlContent:SwitchPanel( self.PropPanel ) + end + + + -- Select the first node + local FirstNode = tree:Root():GetChildNode( 0 ) + if IsValid( FirstNode ) then + FirstNode:InternalDoClick() + end + +end ) + +spawnmenu.AddCreationTab( "simfphys", function() + + local ctrl = vgui.Create( "SpawnmenuContentPanel" ) + ctrl:CallPopulateHook( "SimfphysPopulateVehicles" ) + return ctrl + +end, "icon16/car.png", 50 ) + + +spawnmenu.AddContentType( "simfphys_vehicles", function( container, obj ) + if not obj.material then return end + if not obj.nicename then return end + if not obj.spawnname then return end + + local icon = vgui.Create( "ContentIcon", container ) + icon:SetContentType( "simfphys_vehicles" ) + icon:SetSpawnName( obj.spawnname ) + icon:SetName( obj.nicename ) + icon:SetMaterial( obj.material ) + icon:SetAdminOnly( obj.admin ) + icon:SetColor( Color( 0, 0, 0, 255 ) ) + icon.DoClick = function() + RunConsoleCommand( "simfphys_spawnvehicle", obj.spawnname ) + surface.PlaySound( "ui/buttonclickrelease.wav" ) + end + icon.OpenMenu = function( icon ) + + local menu = DermaMenu() + menu:AddOption( "Copy to Clipboard", function() SetClipboardText( obj.spawnname ) end ) + --menu:AddSpacer() + --menu:AddOption( "Delete", function() icon:Remove() hook.Run( "SpawnlistContentChanged", icon ) end ) + menu:Open() + + end + + if IsValid( container ) then + container:Add( icon ) + end + + return icon + +end ) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/init.lua b/garrysmod/addons/feature-cars/lua/simfphys/init.lua new file mode 100644 index 0000000..ffdf3f9 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/init.lua @@ -0,0 +1,56 @@ +simfphys = simfphys or {} +simfphys.gMultiplier = 66 / (1 / engine.TickInterval()) + +if SERVER then + AddCSLuaFile("simfphys/client/killicons.lua") + AddCSLuaFile("simfphys/client/fonts.lua") + AddCSLuaFile("simfphys/client/tab.lua") + AddCSLuaFile("simfphys/client/hud.lua") + AddCSLuaFile("simfphys/client/seatcontrols.lua") + AddCSLuaFile("simfphys/client/lighting.lua") + AddCSLuaFile("simfphys/client/damage.lua") + AddCSLuaFile("simfphys/client/poseparameter.lua") + + AddCSLuaFile("simfphys/anim.lua") + AddCSLuaFile("simfphys/attachments.lua") + AddCSLuaFile("simfphys/base_functions.lua") + AddCSLuaFile("simfphys/rescuespawnlists.lua") + AddCSLuaFile("simfphys/base_lights.lua") + AddCSLuaFile("simfphys/base_vehicles.lua") + AddCSLuaFile("simfphys/view.lua") + AddCSLuaFile("simfphys/misc.lua") + + include("simfphys/base_functions.lua") + include("simfphys/server/exitpoints.lua") + include("simfphys/server/spawner.lua") + include("simfphys/server/seatcontrols.lua") + include("simfphys/server/damage.lua") + include("simfphys/server/poseparameter.lua") + -- include("simfphys/server/joystick.lua") + include("simfphys/attachments.lua") + include("simfphys/server/octothorp.lua") + include("simfphys/server/tow.lua") +end + +if CLIENT then + include("simfphys/base_functions.lua") + include("simfphys/client/killicons.lua") + include("simfphys/client/fonts.lua") + include("simfphys/client/tab.lua") + include("simfphys/client/hud.lua") + include("simfphys/client/seatcontrols.lua") + include("simfphys/client/lighting.lua") + include("simfphys/client/damage.lua") + include("simfphys/client/poseparameter.lua") + include("simfphys/attachments.lua") +end + +include("simfphys/anim.lua") +include("simfphys/base_lights.lua") +include("simfphys/base_vehicles.lua") +include("simfphys/view.lua") +include("simfphys/misc.lua") + +timer.Simple( 0.5, function() + include("simfphys/rescuespawnlists.lua") +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/misc.lua b/garrysmod/addons/feature-cars/lua/simfphys/misc.lua new file mode 100644 index 0000000..ce40d3d --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/misc.lua @@ -0,0 +1,50 @@ +local function PlayerPickup( ply, ent ) + if (ent:GetClass():lower() == 'gmod_sent_vehicle_fphysics_wheel') then + return false + end +end +hook.Add( 'GravGunPickupAllowed', 'disableWheelPickup', PlayerPickup ) + +properties.Add('evacuation', { + MenuLabel = 'Эвакуация авто', + Order = 750, + MenuIcon = octolib.icons.silk16('evacuator'), + Action = octolib.func.zero, + + Filter = function(_, ent, ply) + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return false end + if not ply:query('DBG: Эвакуировать автомобили') then return false end + return true + end, + + MenuOpen = function(self, option, ent) + local submenu = option:AddSubMenu() + submenu:AddOption('Отключить', function() self:Turn(ent, false) end):SetIcon(octolib.icons.silk16('lightbulb_off')) + submenu:AddOption('Включить', function() self:Turn(ent, true) end):SetIcon(octolib.icons.silk16('lightbulb')) + end, + + Turn = function(self, ent, state) + self:MsgStart() + net.WriteEntity(ent) + net.WriteBool(state) + self:MsgEnd() + end, + + Receive = function(self, _, ply) + + local ent = net.ReadEntity() + if not (IsValid(ply) and IsValid(ent) and self:Filter(ent, ply)) then return false end + + local state = net.ReadBool() + ent.doNotEvacuate = not state or nil + ent.idleScore = state and 0 or nil + + if state then + ply:Notify('ooc', 'Теперь этот автомобиль будет эвакуирован, если будет долго находиться на месте. Таймер эвакуатора сброшен') + else + ply:Notify('ooc', 'Теперь этот автомобиль не будет эвакуирован (полиция или владелец автомобиля все равно могут инициировать эвакуацию)') + end + + return true + end, +}) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/rescuespawnlists.lua b/garrysmod/addons/feature-cars/lua/simfphys/rescuespawnlists.lua new file mode 100644 index 0000000..00ee715 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/rescuespawnlists.lua @@ -0,0 +1,110 @@ +local mylist = list.Get( "Vehicles" ) +for listname, _ in pairs( mylist ) do + if mylist[listname].Class == "gmod_sent_vehicle_fphysics_base" then + local V = { + Name = "blank", + Model = "error.mdl", + Category = "Added by rescue script", + SpawnOffset = Vector(0,0,0), + + Members = { + } + } + + V.Name = mylist[listname].Name + V.Model = mylist[listname].Model + V.Category = "(restored) "..mylist[listname].Category + + V.SpawnOffset = mylist[listname].Members.SpawnOffset or Vector(0,0,0) + if mylist[listname].Members.Mass then V.Members.Mass = mylist[listname].Members.Mass end + if mylist[listname].Members.LightsTable then V.Members.LightsTable = mylist[listname].Members.LightsTable end + + if mylist[listname].Members.MaxHealth then V.Members.MaxHealth = mylist[listname].Members.MaxHealth end + if mylist[listname].Members.AirFriction then V.Members.AirFriction = mylist[listname].Members.AirFriction end + + if mylist[listname].Members.FrontWheelRadius then V.Members.FrontWheelRadius = mylist[listname].Members.FrontWheelRadius end + if mylist[listname].Members.RearWheelRadius then V.Members.RearWheelRadius = mylist[listname].Members.RearWheelRadius end + if mylist[listname].Members.CustomWheels then V.Members.CustomWheels = mylist[listname].Members.CustomWheels end + if mylist[listname].Members.CustomSuspensionTravel then V.Members.CustomSuspensionTravel = mylist[listname].Members.CustomSuspensionTravel end + if mylist[listname].Members.CustomWheelModel then V.Members.CustomWheelModel = mylist[listname].Members.CustomWheelModel end + if mylist[listname].Members.CustomWheelModel_R then + V.Members.CustomWheelModel_R = mylist[listname].Members.CustomWheelModel_R + else + if mylist[listname].Members.CustomWheelModel then + V.Members.CustomWheelModel_R = mylist[listname].Members.CustomWheelModel + end + end + if mylist[listname].Members.CustomWheelPosFL then V.Members.CustomWheelPosFL = mylist[listname].Members.CustomWheelPosFL end + if mylist[listname].Members.CustomWheelPosFR then V.Members.CustomWheelPosFR = mylist[listname].Members.CustomWheelPosFR end + if mylist[listname].Members.CustomWheelPosRL then V.Members.CustomWheelPosRL = mylist[listname].Members.CustomWheelPosRL end + if mylist[listname].Members.CustomWheelPosRR then V.Members.CustomWheelPosRR = mylist[listname].Members.CustomWheelPosRR end + if mylist[listname].Members.CustomWheelAngleOffset then V.Members.CustomWheelAngleOffset = mylist[listname].Members.CustomWheelAngleOffset end + if mylist[listname].Members.CustomMassCenter then V.Members.CustomMassCenter = mylist[listname].Members.CustomMassCenter end + if mylist[listname].Members.CustomSteerAngle then V.Members.CustomSteerAngle = mylist[listname].Members.CustomSteerAngle end + if mylist[listname].Members.CustomMassCenter then V.Members.CustomMassCenter = mylist[listname].Members.CustomMassCenter end + V.Members.SeatOffset = mylist[listname].Members.SeatOffset + V.Members.SeatPitch = mylist[listname].Members.SeatPitch + if mylist[listname].Members.SeatYaw then V.Members.SeatYaw = mylist[listname].Members.SeatYaw end + if mylist[listname].Members.SpeedoMax then V.Members.SpeedoMax = mylist[listname].Members.SpeedoMax end + if mylist[listname].Members.PassengerSeats then V.Members.PassengerSeats = mylist[listname].Members.PassengerSeats end + if mylist[listname].Members.ModelInfo then V.Members.ModelInfo = mylist[listname].Members.ModelInfo end + if mylist[listname].Members.ExhaustPositions then V.Members.ExhaustPositions = mylist[listname].Members.ExhaustPositions end + if mylist[listname].Members.Attachments then V.Members.Attachments = mylist[listname].Members.Attachments end + if mylist[listname].Members.StrengthenSuspension then V.Members.StrengthenSuspension = mylist[listname].Members.StrengthenSuspension end + V.Members.FrontHeight = mylist[listname].Members.FrontHeight + V.Members.FrontConstant = mylist[listname].Members.FrontConstant + V.Members.FrontDamping = mylist[listname].Members.FrontDamping + V.Members.FrontRelativeDamping = mylist[listname].Members.FrontRelativeDamping + V.Members.RearHeight = mylist[listname].Members.RearHeight + V.Members.RearConstant = mylist[listname].Members.RearConstant + V.Members.RearDamping = mylist[listname].Members.RearDamping + V.Members.RearRelativeDamping = mylist[listname].Members.RearRelativeDamping + V.Members.FastSteeringAngle = mylist[listname].Members.FastSteeringAngle + V.Members.SteeringFadeFastSpeed = mylist[listname].Members.SteeringFadeFastSpeed + V.Members.TurnSpeed = mylist[listname].Members.TurnSpeed + V.Members.MaxGrip = mylist[listname].Members.MaxGrip + V.Members.Efficiency = mylist[listname].Members.Efficiency + V.Members.GripOffset = mylist[listname].Members.GripOffset + V.Members.BrakePower = mylist[listname].Members.BrakePower + V.Members.IdleRPM = mylist[listname].Members.IdleRPM + V.Members.LimitRPM = mylist[listname].Members.LimitRPM + if mylist[listname].Members.Revlimiter then V.Members.Revlimiter = mylist[listname].Members.Revlimiter end + V.Members.PeakTorque = mylist[listname].Members.PeakTorque + V.Members.PowerbandStart = mylist[listname].Members.PowerbandStart + V.Members.PowerbandEnd = mylist[listname].Members.PowerbandEnd + if mylist[listname].Members.Turbocharged then V.Members.Turbocharged = mylist[listname].Members.Turbocharged end + if mylist[listname].Members.snd_blowoff then V.Members.snd_blowoff = mylist[listname].Members.snd_blowoff end + if mylist[listname].Members.Supercharged then V.Members.Supercharged = mylist[listname].Members.Supercharged end + V.Members.PowerBias = mylist[listname].Members.PowerBias + V.Members.EngineSoundPreset = mylist[listname].Members.EngineSoundPreset + if mylist[listname].Members.snd_pitch then V.Members.snd_pitch = mylist[listname].Members.snd_pitch end + if mylist[listname].Members.snd_idle then V.Members.snd_idle = mylist[listname].Members.snd_idle end + if mylist[listname].Members.snd_low then V.Members.snd_low = mylist[listname].Members.snd_low end + if mylist[listname].Members.snd_low_revdown then V.Members.snd_low_revdown = mylist[listname].Members.snd_low_revdown end + if mylist[listname].Members.snd_low_pitch then V.Members.snd_low_pitch = mylist[listname].Members.snd_low_pitch end + if mylist[listname].Members.snd_mid then V.Members.snd_mid = mylist[listname].Members.snd_mid end + if mylist[listname].Members.snd_mid_gearup then V.Members.snd_mid_gearup = mylist[listname].Members.snd_mid_gearup end + if mylist[listname].Members.snd_mid_pitch then V.Members.snd_mid_pitch = mylist[listname].Members.snd_mid_pitch end + if mylist[listname].Members.Sound_Idle then V.Members.Sound_Idle = mylist[listname].Members.Sound_Idle end + if mylist[listname].Members.Sound_IdlePitch then V.Members.Sound_IdlePitch = mylist[listname].Members.Sound_IdlePitch end + if mylist[listname].Members.Sound_Mid then V.Members.Sound_Mid = mylist[listname].Members.Sound_Mid end + if mylist[listname].Members.Sound_MidPitch then V.Members.Sound_MidPitch = mylist[listname].Members.Sound_MidPitch end + if mylist[listname].Members.Sound_MidVolume then V.Members.Sound_MidVolume = mylist[listname].Members.Sound_MidVolume end + if mylist[listname].Members.Sound_MidFadeOutRPMpercent then V.Members.Sound_MidFadeOutRPMpercent = mylist[listname].Members.Sound_MidFadeOutRPMpercent end + if mylist[listname].Members.Sound_MidFadeOutRate then V.Members.Sound_MidFadeOutRate = mylist[listname].Members.Sound_MidFadeOutRate end + if mylist[listname].Members.Sound_High then V.Members.Sound_High = mylist[listname].Members.Sound_High end + if mylist[listname].Members.Sound_HighPitch then V.Members.Sound_HighPitch = mylist[listname].Members.Sound_HighPitch end + if mylist[listname].Members.Sound_HighVolume then V.Members.Sound_HighVolume = mylist[listname].Members.Sound_HighVolume end + if mylist[listname].Members.Sound_HighFadeInRPMpercent then V.Members.Sound_HighFadeInRPMpercent = mylist[listname].Members.Sound_HighFadeInRPMpercent end + if mylist[listname].Members.Sound_HighFadeInRate then V.Members.Sound_HighFadeInRate = mylist[listname].Members.Sound_HighFadeInRate end + if mylist[listname].Members.Sound_Throttle then V.Members.Sound_Throttle = mylist[listname].Members.Sound_Throttle end + if mylist[listname].Members.Sound_ThrottlePitch then V.Members.Sound_ThrottlePitch = mylist[listname].Members.Sound_ThrottlePitch end + if mylist[listname].Members.Sound_ThrottleVolume then V.Members.Sound_ThrottleVolume = mylist[listname].Members.Sound_ThrottleVolume end + if mylist[listname].Members.snd_horn then V.Members.snd_horn = mylist[listname].Members.snd_horn end + V.Members.DifferentialGear = mylist[listname].Members.DifferentialGear + V.Members.Gears = mylist[listname].Members.Gears + + list.Set( "simfphys_vehicles", listname, V ) + list.GetForEdit( "Vehicles" )[ listname ] = nil + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/damage.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/damage.lua new file mode 100644 index 0000000..96eb92b --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/damage.lua @@ -0,0 +1,320 @@ +util.AddNetworkString('simfphys_spritedamage') +util.AddNetworkString('simfphys_lightsfixall') +util.AddNetworkString('simfphys_backfire') + +local function Spark(pos , normal , snd) + local effectdata = EffectData() + effectdata:SetOrigin(pos - normal) + effectdata:SetNormal(-normal) + util.Effect('stunstickimpact', effectdata, true, true) + + if snd then + sound.Play(Sound(snd), pos, 75) + end +end + +local function DestroyVehicle(ent) + if not IsValid(ent) then return end + if ent.destroyed then return end + + ent:OnDestroyed() + + ent.destroyed = true + + local ply = ent.EntityOwner + local skin = ent:GetSkin() + local Col = ent:GetColor() + Col.r = Col.r * 0.8 + Col.g = Col.g * 0.8 + Col.b = Col.b * 0.8 + + local bprop = ents.Create('gmod_sent_vehicle_fphysics_gib') + bprop:SetModel(ent:GetModel()) + bprop:SetPos(ent:GetPos()) + bprop:SetAngles(ent:GetAngles()) + bprop:Spawn() + bprop:Activate() + bprop:GetPhysicsObject():SetVelocity(ent:GetVelocity() + Vector(math.random(-5,5),math.random(-5,5),math.random(150,250))) + bprop:GetPhysicsObject():SetMass(ent.Mass * 0.75) + bprop.DoNotDuplicate = true + bprop.MakeSound = true + bprop:SetColor(Col) + bprop:SetSkin(skin) + + simfphys.SetOwner(ply , bprop) + + if IsValid(ply) then + undo.Create('Gib') + undo.SetPlayer(ply) + undo.AddEntity(bprop) + undo.SetCustomUndoText('Undone Gib') + undo.Finish('Gib') + ply:AddCleanup('Gibs', bprop) + end + + if ent.CustomWheels == true and not ent.NoWheelGibs then + for i = 1, table.Count(ent.GhostWheels) do + local Wheel = ent.GhostWheels[i] + if IsValid(Wheel) then + local prop = ents.Create('gmod_sent_vehicle_fphysics_gib') + prop:SetModel(Wheel:GetModel()) + prop:SetPos(Wheel:LocalToWorld(Vector(0,0,0))) + prop:SetAngles(Wheel:LocalToWorldAngles(Angle(0,0,0))) + prop:SetOwner(bprop) + prop:Spawn() + prop:Activate() + prop:GetPhysicsObject():SetVelocity(ent:GetVelocity() + Vector(math.random(-5,5),math.random(-5,5),math.random(0,25))) + prop:GetPhysicsObject():SetMass(20) + prop.DoNotDuplicate = true + bprop:DeleteOnRemove(prop) + + simfphys.SetOwner(ply , prop) + end + end + end + + local Driver = ent:GetDriver() + if IsValid(Driver) and ent.RemoteDriver ~= Driver then + Driver:Kill() + end + + if ent.PassengerSeats then + for i = 1, table.Count(ent.PassengerSeats) do + local Passenger = ent.pSeat[i]:GetDriver() + if IsValid(Passenger) then + Passenger:Kill() + end + end + end + + ent:Extinguish() + ent:Remove() +end + +local function DamageVehicle(ent , damage, type, allowExplode) + if not simfphys.DamageEnabled then return end + + local MaxHealth = ent:GetMaxHealth() + local CurHealth = ent:GetCurHealth() + + local NewHealth = math.max(math.Round(CurHealth - damage, 0), allowedExplode and 0 or 10) + ent:SetCurHealth(NewHealth) + + if NewHealth <= (MaxHealth * 0.5) then + if NewHealth <= (MaxHealth * 0.175) then + ent:SetOnFire(true) + ent:SetOnSmoke(false) + else + ent:SetOnSmoke(true) + end + end + + if MaxHealth > 30 and NewHealth <= 30 and ent:EngineActive() then + ent:DamagedStall() + end + + if NewHealth <= 0 then + if type ~= DMG_GENERIC and type ~= DMG_CRUSH or damage > 400 then + hook.Run('VehicleDestroyed', ent) + DestroyVehicle(ent) + return + end + + if ent:EngineActive() then + ent:DamagedStall() + end + + return + end +end +simfphys.DamageVehicle = DamageVehicle + +local function HurtPlayers(ent, damage) + -- if not simfphys.pDamageEnabled then return end + + local Driver = ent:GetDriver() + + if IsValid(Driver) then + Driver:TakeDamage(damage * (Driver:GetNetVar('belted') and 0.4 or 1), Entity(0), ent) + end + + if ent.PassengerSeats then + for i = 1, table.Count(ent.PassengerSeats) do + local Passenger = ent.pSeat[i]:GetDriver() + + if IsValid(Passenger) then + Passenger:TakeDamage(damage * (Passenger:GetNetVar('belted') and 0.4 or 1), Entity(0), ent) + end + end + end +end + +local function bcDamage(vehicle , position , cdamage) + if not simfphys.DamageEnabled then return end + + cdamage = cdamage or false + net.Start('simfphys_spritedamage') + net.WriteEntity(vehicle) + net.WriteVector(position) + net.WriteBool(cdamage) + net.Broadcast() +end + +hook.Add('PhysgunDrop', 'dbg-cars.damage', function(ply, ent) ent.lastUnfreeze = CurTime() end) +hook.Add('PlayerSpawnedProp', 'dbg-cars.damage', function(ply, mdl, ent) ent.lastUnfreeze = CurTime() end) + +local propClasses = octolib.array.toKeys({'prop_physics'}) +local function onCollide(ent, data) + if ent._nodmg then return end + + local ent2 = data.HitEntity + if IsValid(ent2) then + if ent2:GetClass():StartWith('npc_') then + Spark(data.HitPos , data.HitNormal , 'MetalVehicle.ImpactSoft') + return + end + + if ent2.APG_Picked and propClasses[ent2:GetClass()] then + ent2:Remove() + return + end + if CurTime() - (ent2.lastUnfreeze or 0) < 5 then + ent._nodmg = true + timer.Simple(0.5, function() + if IsValid(ent) then ent._nodmg = nil end + end) + + return + end + end + + -- local vec + -- if ent2 == Entity(0) or not data.HitObject:IsMotionEnabled() then + -- vec = data.OurOldVelocity + -- else + -- vec = data.OurOldVelocity - data.TheirOldVelocity + -- end + + -- local imp = vec:Length() * math.abs(vec:GetNormalized():Dot(data.HitNormal)) + -- local mass = data.HitObject:GetMass() + -- if ent2 ~= Entity(0) and mass ~= 0 then + -- imp = imp / math.max(500 / mass, 1) + -- end + + local imp = data.OurNewVelocity:Distance(data.OurOldVelocity) + if ent2:GetClass() == 'gmod_sent_vehicle_fphysics_base' then + imp = imp / math.Clamp(data.OurOldVelocity:LengthSqr() / data.TheirOldVelocity:LengthSqr(), 0.7, 1) + end + + local pos = data.HitPos + local plyDmg, carDmg + + if imp > 1200 then + Spark(pos , data.HitNormal , 'MetalVehicle.ImpactHard') + bcDamage(ent , ent:WorldToLocal(pos) , true) + plyDmg = imp * 0.07 + carDmg = imp * 0.75 + elseif imp > 800 then + Spark(pos , data.HitNormal , 'MetalVehicle.ImpactHard') + bcDamage(ent , ent:WorldToLocal(pos) , true) + plyDmg = imp * 0.05 + carDmg = imp * 0.6 + elseif imp > 400 then + bcDamage(ent , ent:WorldToLocal(pos) , true) + plyDmg = imp * 0.04 + carDmg = imp * 0.45 + elseif imp > 200 then + local hitent = ent2:IsPlayer() + if not hitent then + bcDamage(ent, ent:WorldToLocal(pos) , true) + carDmg = imp * 0.25 + end + end + + if plyDmg then HurtPlayers(ent, plyDmg) end + if carDmg and imp > 400 then + ent:TakeDamage(carDmg * simfphys.DamageMul, Entity(0), Entity(0)) + -- reduce chance to not stall as we approach imp = 1500 + local dontStallChance = octolib.math.remap(imp, 400, 1500, ent.police and 10 or 2, 0) + if math.random(10) > dontStallChance then + ent:DamagedStall() + end + end + + if ent2:IsPlayer() and imp > 100 then + local ply = ent2 + local dmg = DamageInfo() + dmg:SetDamage(math.Clamp(imp / 1.25, 0, ply:Health() - 10)) + dmg:SetAttacker(ent:GetDriver() or Entity(0)) + dmg:SetInflictor(ent) + dmg:SetDamageType(DMG_VEHICLE) + ply:TakeDamageInfo(dmg) + end +end + +local function OnDamage(ent, dmginfo) + ent:TakePhysicsDamage(dmginfo) + + if not ent:IsInitialized() then return end + + local Damage = dmginfo:GetDamage() + local DamagePos = dmginfo:GetDamagePosition() + local Type = dmginfo:GetDamageType() + local Driver = ent:GetDriver() + bcDamage(ent , ent:WorldToLocal(DamagePos)) + + local Mul = 1 + if Type == DMG_BLAST then + Mul = 10 + end + + if Type == DMG_BULLET then + Mul = 2 + end + + DamageVehicle(ent , Damage * Mul, Type) + + -- if ent.IsArmored then return end + -- + -- if IsValid(Driver) then + -- local Distance = (DamagePos - Driver:GetPos()):Length() + -- if (Distance < 40) then + -- local Damage = (40 - Distance) / 22 + -- dmginfo:ScaleDamage(Damage) + -- Driver:TakeDamageInfo(dmginfo) + -- BloodEffect(DamagePos) + -- end + -- end + -- + -- if ent.PassengerSeats then + -- for i = 1, table.Count(ent.PassengerSeats) do + -- local Passenger = ent.pSeat[i]:GetDriver() + -- + -- if IsValid(Passenger) then + -- local Distance = (DamagePos - Passenger:GetPos()):Length() + -- local Damage = (40 - Distance) / 22 + -- if (Distance < 40) then + -- dmginfo:ScaleDamage(Damage) + -- Passenger:TakeDamageInfo(dmginfo) + -- BloodEffect(DamagePos) + -- end + -- end + -- end + -- end +end + +hook.Add('OnEntityCreated', 'simfphys_damagestuff', function(ent) + if simfphys.IsCar(ent) then + timer.Simple(0.2, function() + if not IsValid(ent) then return end + + local Health = math.floor(ent.MaxHealth and ent.MaxHealth or (1000 + ent:GetPhysicsObject():GetMass() / 3)) + + ent:SetMaxHealth(Health) + ent:SetCurHealth(Health) + + ent.PhysicsCollide = onCollide + ent.OnTakeDamage = OnDamage + end) + end +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/exitpoints.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/exitpoints.lua new file mode 100644 index 0000000..d080605 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/exitpoints.lua @@ -0,0 +1,81 @@ +local function getExitPoint(seat, car) + + local seatPos = seat:GetPos() + seat:OBBCenter() + local right = car.Right + local dir = right * octolib.math.sign((seatPos - car:GetPos()):Dot(right)) + + local outerTrace = util.TraceHull({ + start = seatPos + dir * 150, + endpos = seatPos, + mins = Vector(-16,-16,0), + maxs = Vector(16,16,40), + filter = function(ent) return ent == car end, + ignoreworld = true, + }) + if not outerTrace.Hit then return seatPos end + + local pos = outerTrace.HitPos + outerTrace.HitNormal + local downTrace = util.TraceHull({ + start = pos + Vector(0, 0, 20), + endpos = pos - Vector(0, 0, 50), + mins = Vector(-16,-16,0), + maxs = Vector(16,16,0), + }) + if downTrace.Hit then + pos.z = downTrace.HitPos.z + pos:Add(downTrace.HitNormal) + end + + local playerTrace = util.TraceHull({ + start = pos, + endpos = pos, + mins = Vector(-15,-15,0), + maxs = Vector(15,15,40), + filter = function(ent) return not (ent.IsGhost and ent:IsGhost()) end, + }) + if playerTrace.Hit then return end + + return pos + +end + +hook.Add('PlayerLeaveVehicle', 'simfphysVehicleExit', function(ply, seat) + + if ply.exitPoint then + ply:SetPos(ply.exitPoint) + ply.exitPoint = nil + end + + local vel = seat:GetVelocity():Length() + if vel > 350 then + ply:TakeDamage((vel - 200) / 5, Entity(0), nil) + end + +end) + +hook.Add('PlayerEnteredVehicle', 'simfphys.seats', function(ply) + ply.exitPoint = nil +end) + +hook.Add('CanExitVehicle', 'octo-cars', function(seat, ply) + + local car = seat.base + if not seat.fphysSeat or not IsValid(car) then return end + + if not ply.handledVehicleExit then return false end + + local drSeat = car.DriverSeat + local driver = IsValid(drSeat) and drSeat:GetDriver() + if car:GetIsLocked() and IsValid(driver) and not car.lockpicked then + return false + end + + local pos = getExitPoint(seat, car) + if not pos then + ply:Notify('warning', 'Что-то мешает выйти из автомобиля') + return false + end + + ply.exitPoint = pos + +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/octothorp.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/octothorp.lua new file mode 100644 index 0000000..10a3331 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/octothorp.lua @@ -0,0 +1,266 @@ +-- +-- OCTOSIMFPHYS +-- + +function simfphys.GetRight(ent, index, WheelPos) + local Steer = ent:GetTransformedDirection() + + local Right = ent.Right + + if WheelPos.IsFrontWheel then + Right = (IsValid(ent.SteerMaster) and Steer.Right or ent.Right) * (WheelPos.IsRightWheel and 1 or -1) + else + Right = (IsValid(ent.SteerMaster) and Steer.Right2 or ent.Right) * (WheelPos.IsRightWheel and 1 or -1) + end + + return Right +end + +function simfphys.SetWheelOffset(ent, offset_front, offset_rear) + if not IsValid(ent) then return end + + offset_front = math.Clamp(offset_front, -8, 4) + offset_rear = math.Clamp(offset_rear, -8, 4) + + ent.wOffsetF = offset_front + ent.wOffsetR = offset_rear + + if not istable(ent.Wheels) or not istable(ent.GhostWheels) then return end + + for i = 1, table.Count(ent.GhostWheels) do + local Wheel = ent.Wheels[ i ] + local WheelModel = ent.GhostWheels[i] + local WheelPos = ent:LogicWheelPos(i) + + if IsValid(Wheel) and IsValid(WheelModel) then + local Pos = Wheel:GetPos() + local Right = simfphys.GetRight(ent, i, WheelPos) + local offset = WheelPos.IsFrontWheel and offset_front or offset_rear + + WheelModel:SetParent(nil) + + local physObj = WheelModel:GetPhysicsObject() + if IsValid(physObj) then + physObj:EnableMotion(false) + end + + WheelModel:SetPos(Pos + Right * offset) + WheelModel:SetParent(Wheel) + end + end +end + +function simfphys.ApplyWheel(ent, camber, front, rear) + + rear = rear or front + local aof, aor = simfphys.GetWheelAngle(front), simfphys.GetWheelAngle(rear) + if not aof or not aor then print('Wheel model not registered') return end + + ent.wModelF = front + ent.wModelR = rear + ent.waOffsetF = aof + ent.waOffsetR = aor + ent.camber = math.Clamp(camber or 0, -18, 10) + + ent.PressedKeys["A"] = nil + ent.PressedKeys["D"] = nil + ent.SmoothAng = 0 + ent:SteerVehicle(0) + + timer.Simple(0.05, function() + if not IsValid(ent) then return end + + for i = 1, table.Count(ent.GhostWheels) do + local Wheel = ent.GhostWheels[i] + + if IsValid(Wheel) then + local isfrontwheel = (i == 1 or i == 2) + local swap_y = (i == 2 or i == 4 or i == 6) + + local angleoffset = isfrontwheel and ent.waOffsetF or ent.waOffsetR + + local model = isfrontwheel and ent.wModelF or ent.wModelR + + local fAng = ent:LocalToWorldAngles(ent.VehicleData.LocalAngForward) + local rAng = ent:LocalToWorldAngles(ent.VehicleData.LocalAngRight) + + local Forward = fAng:Forward() + local Right = swap_y and -rAng:Forward() or rAng:Forward() + local Up = ent:GetUp() + + local ghostAng = Right:Angle() + local mirAng = swap_y and 1 or -1 + ghostAng:RotateAroundAxis(Forward,angleoffset.p * mirAng) + ghostAng:RotateAroundAxis(Right,angleoffset.r * mirAng) + ghostAng:RotateAroundAxis(Up,-angleoffset.y) + + ghostAng:RotateAroundAxis(Forward, ent.camber * mirAng) + + Wheel:SetModelScale(1) + Wheel:SetModel(model) + Wheel:SetAngles(ghostAng) + + timer.Simple(0.05, function() + if not IsValid(Wheel) or not IsValid(ent) then return end + local wheelsize = Wheel:OBBMaxs() - Wheel:OBBMins() + local radius = isfrontwheel and ent.FrontWheelRadius or ent.RearWheelRadius + local size = (radius * 2) / math.max(wheelsize.x,wheelsize.y,wheelsize.z) + + Wheel:SetModelScale(size) + end) + end + end + end) +end + +function simfphys.ValidateModel(model) + local v_list = list.Get("simfphys_vehicles") + for listname, _ in pairs(v_list) do + if v_list[listname].Members.CustomWheels then + local FrontWheel = v_list[listname].Members.CustomWheelModel + local RearWheel = v_list[listname].Members.CustomWheelModel_R + + if FrontWheel then + FrontWheel = string.lower(FrontWheel) + end + + if RearWheel then + RearWheel = string.lower(RearWheel) + end + + if model == FrontWheel or model == RearWheel then + return true + end + end + end + + local list = list.Get("simfphys_Wheels")[model] + + if list then + return true + end + + return false +end + +function simfphys.SetupSuspension(ent, susp) + + local carData = list.Get("simfphys_vehicles")[ent.VehicleName].Members + susp[1] = math.Clamp(susp[1], -1, 1) + susp[2] = math.Clamp(susp[2], carData.FrontConstant, carData.FrontConstant * 2) + susp[3] = math.Clamp(susp[3], 0, carData.FrontDamping * 1.5) + susp[4] = math.Clamp(susp[4], math.max(susp[1] - 1, -1), math.min(susp[1] + 1, 1)) + susp[5] = math.Clamp(susp[5], carData.RearConstant, carData.RearConstant * 2) + susp[6] = math.Clamp(susp[6], 0, carData.RearDamping * 1.5) + local data = { + [1] = {susp[2], susp[3]}, + [2] = {susp[2], susp[3]}, + [3] = {susp[5], susp[6]}, + [4] = {susp[5], susp[6]}, + [5] = {susp[5], susp[6]}, + [6] = {susp[5], susp[6]} + } + + local elastics = ent.Elastics + if (elastics) then + for i = 1, table.Count(elastics) do + local elastic = elastics[i] + if not elastic or not data[i] then continue end + + if (ent.StrengthenSuspension == true) then + if (IsValid(elastic)) then + elastic:Fire("SetSpringConstant", data[i][1] * 0.5, 0) + elastic:Fire("SetSpringDamping", data[i][2] * 0.5, 0) + end + local elastic2 = elastics[i * 10] + if (IsValid(elastic2)) then + elastic2:Fire("SetSpringConstant", data[i][1] * 0.5, 0) + elastic2:Fire("SetSpringDamping", data[i][2] * 0.5, 0) + end + else + if (IsValid(elastic)) then + elastic:Fire("SetSpringConstant", data[i][1], 0) + elastic:Fire("SetSpringDamping", data[i][2], 0) + end + end + + ent.dampF = data[1][2] + ent.consF = data[1][1] + ent.dampR = data[4][2] + ent.consR = data[4][1] + end + end + + ent:SetFrontSuspensionHeight(susp[1]) + ent:SetRearSuspensionHeight(susp[4]) + +end + +function simfphys.CanPlayerTune(ply, ent) + + if not IsValid(ply) or not IsValid(ent) then return false, L.car_not_found end + if ent:GetIsLocked() then return false, L.car_closed end + + local owner = ent:CPPIGetOwner() + if not IsValid(owner) or ply ~= owner and not (owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true)) then return false, L.car_you_need_be_friend end + + return true + +end + +simfphys.postSpawn = simfphys.postSpawn or {} +function simfphys.AddPostSpawnAction(class, func) + simfphys.postSpawn[class] = func +end + +function simfphys.GetSeatProperty(seat, property) + + if not IsValid(seat) then return end + local car = seat:GetParent() + if not IsValid(car) or car:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' then return end + if seat[property] ~= nil then return seat[property] end + + car.spawnlist = car.spawnlist or list.Get('simfphys_vehicles')[car:GetSpawn_List()] + local lst = car.spawnlist.Members + local lPos = car:WorldToLocal(seat:GetPos()) + for _,v in ipairs(lst.PassengerSeats) do + if lPos:DistToSqr(v.pos) < 10 then + if v[property] ~= nil then + seat[property] = v[property] + else + seat[property] = false + end + break + end + end + + return seat[property] + +end + +simfphys.fuelPrices = { + [FUELTYPE_DIESEL] = 80, + [FUELTYPE_PETROL] = 120, + [FUELTYPE_ELECTRIC] = 10, +} + +util.AddNetworkString 'car.steer' +net.Receive('car.steer', function(len, ply) + local seat = ply:GetVehicle() + if not IsValid(seat) or not IsValid(seat.base) then return end + + local car = seat.base + if car:GetDriverSeat() ~= seat then return end + + car.wantedSteer = net.ReadFloat() +end) + +hook.Add('CanTool', 'octo-cars', function(ply, tr, tool) + + local ent = tr.Entity + if tool ~= 'remover' and IsValid(ent) and ent:GetClass() == 'gmod_sent_vehicle_fphysics_base' + and IsValid(ply) and not ply:query('DBG: Изменять автомобили') then + return false + end + +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/poseparameter.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/poseparameter.lua new file mode 100644 index 0000000..f3bed80 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/poseparameter.lua @@ -0,0 +1,33 @@ +-- util.AddNetworkString( "simfphys_request_ppdata" ) +-- util.AddNetworkString( "simfphys_send_ppdata" ) + +-- local function sendppdata( length, ply ) +-- local ent = net.ReadEntity() + +-- if not IsValid( ent ) then return end + +-- if ent.IsInitialized and not ent:IsInitialized() then return end +-- if not istable( ent.Wheels ) then return end + +-- net.Start( "simfphys_send_ppdata", true ) +-- net.WriteEntity( ent ) +-- net.WriteBool( ent.CustomWheels ) + +-- net.WriteEntity( ent.Wheels[1] ) +-- net.WriteFloat( ent.posepositions.PoseL_Pos_FL.z ) +-- net.WriteFloat( ent.VehicleData.suspensiontravel_fl ) + +-- net.WriteEntity( ent.Wheels[2] ) +-- net.WriteFloat( ent.posepositions.PoseL_Pos_FR.z ) +-- net.WriteFloat( ent.VehicleData.suspensiontravel_fr ) + +-- net.WriteEntity( ent.Wheels[3] ) +-- net.WriteFloat( ent.posepositions.PoseL_Pos_RL.z ) +-- net.WriteFloat( ent.VehicleData.suspensiontravel_rl ) + +-- net.WriteEntity( ent.Wheels[4] ) +-- net.WriteFloat( ent.posepositions.PoseL_Pos_RR.z ) +-- net.WriteFloat( ent.VehicleData.suspensiontravel_rr ) +-- net.Send( ply ) +-- end +-- net.Receive("simfphys_request_ppdata", sendppdata) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/seatcontrols.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/seatcontrols.lua new file mode 100644 index 0000000..2e9a18f --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/seatcontrols.lua @@ -0,0 +1,152 @@ +util.AddNetworkString 'simfphys_request_seatswitch' +util.AddNetworkString 'simfphys_blockcontrols' + +netstream.Listen('dbg-cars.seatStatus', function(done, ply) + local ct = CurTime() + if ply.lastSeatStatus and ply.lastSeatStatus.til >= ct then + return done(ply.lastSeatStatus.data) + end + + local veh = ply:GetVehicle() + if not IsValid(veh) then return end + local car = veh:GetParent() + if not (IsValid(car) and car:GetClass() == 'gmod_sent_vehicle_fphysics_base') then return end + + local response = {} + + if IsValid(car:GetDriverSeat()) and car:CanDrive(ply) then + local seat = car:GetDriverSeat() + response[#response + 1] = { + id = 0, + name = 'Водительское сидение', + check = veh == seat or nil, + icon = veh ~= seat and IsValid(seat:GetDriver()) and 'delete' or nil, + } + end + + for i, seat in ipairs(car.pSeat or {}) do + response[#response + 1] = { + id = i, + name = 'Пассажирское сидение ' .. i, + check = veh == seat or nil, + icon = veh ~= seat and IsValid(seat:GetDriver()) and 'delete' or nil, + } + end + + ply.lastSeatStatus = { + til = ct + 1, + data = response, + } + + done(response) +end) + +net.Receive('simfphys_blockcontrols', function(len, ply) + if not IsValid(ply) then return end + ply.blockcontrols = net.ReadBool() +end) + +net.Receive('simfphys_request_seatswitch', function(len, ply) + local vehicle = ply:GetVehicle() and ply:GetVehicle().vehiclebase + local req_seat = net.ReadInt(32) + + if not IsValid(vehicle) then return end + if not IsValid(ply) then return end + + if vehicle:GetVelocity():LengthSqr() > 100 then + ply:Notify('warning', L.no_change_seat) + return + end + + if ply:IsHandcuffed() or ply:GetNetVar('belted') or ply.belting then + return + end + + ply.NextSeatSwitch = ply.NextSeatSwitch or 0 + + if ply.NextSeatSwitch < CurTime() then + ply.NextSeatSwitch = CurTime() + 0.5 + + if req_seat == 0 then + if not IsValid(vehicle:GetDriver()) and vehicle:CanDrive(ply) then + ply:ExitVehicle() + + if IsValid(vehicle.DriverSeat) then + ply:SelectWeapon('dbg_hands') + timer.Simple(0.05, function() + if not IsValid(vehicle) then return end + if not IsValid(ply) then return end + if IsValid(vehicle:GetDriver()) then return end + + ply:EnterVehicle(vehicle.DriverSeat) + vehicle:EnteringSequence(ply) + ply:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + ply:SetAllowWeaponsInVehicle(false) + local angles = Angle(0,90,0) + ply:SetEyeAngles(angles) + end) + end + end + else + if not vehicle.pSeat then return end + + local seat = vehicle.pSeat[req_seat] + + if IsValid(seat) and not IsValid(seat:GetDriver()) then + ply:ExitVehicle() + + timer.Simple(0.05, function() + if not IsValid(vehicle) then return end + if not IsValid(ply) then return end + if IsValid(seat:GetDriver()) then return end + + ply:EnterVehicle(seat) + ply:SetAllowWeaponsInVehicle(true) + ply:SetCollisionGroup(COLLISION_GROUP_WEAPON) + local angles = Angle(0,90,0) + ply:SetEyeAngles(angles) + end) + end + end + end +end) + +hook.Add('PlayerEnteredVehicle', 'simfphys.seats', function(ply, seat) + local car = seat:GetParent() + if not simfphys.IsCar(car) then return end + + local carInv = car:GetInventory() + if carInv and carInv.conts.glove then + ply:CloseInventory(carInv, {'trunk'}) + if seat == car:GetDriverSeat() then ply:OpenInventory(carInv, {'glove'}) end + end + + if IsValid(seat.MassEnt) then + local mass = 100 + local inv = ply:GetInventory() + if inv then mass = mass + inv:GetMass() * 2 end + + local ph = seat.MassEnt:GetPhysicsObject() + ph:SetMass(mass) + ph:Wake() + end +end) + +hook.Add('PlayerLeaveVehicle', 'simfphys.seats', function(ply, seat) + local car = seat:GetParent() + if not simfphys.IsCar(car) then return end + + octolib.stopAnimations(ply) + + local carInv = car:GetInventory() + if carInv and carInv.conts.glove then + ply:CloseInventory(carInv, {'glove'}) + end + + if IsValid(seat.MassEnt) then + local ph = seat.MassEnt:GetPhysicsObject() + ph:SetMass(1) + ph:Wake() + end +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/spawner.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/spawner.lua new file mode 100644 index 0000000..e364c4f --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/spawner.lua @@ -0,0 +1,51 @@ + +function SpawnSimfphysVehicle( Player, vname, tr ) + if not vname then return end + + local Tickrate = 1 / engine.TickInterval() + + -- if ( Tickrate <= 25 ) and not Player.IsInformedAboutTheServersLowTickrate then + -- Player:PrintMessage( HUD_PRINTTALK, "(SIMFPHYS) WARNING! Server tickrate is "..Tickrate.." we recommend 33 or greater for this addon to work properly!") + -- Player:PrintMessage( HUD_PRINTTALK, "Known problems caused by a too low tickrate:") + -- Player:PrintMessage( HUD_PRINTTALK, "- Wobbly suspension") + -- Player:PrintMessage( HUD_PRINTTALK, "- Wheelspazz or shaking after an crash on bumps or while drifting") + -- Player:PrintMessage( HUD_PRINTTALK, "- Moondrive (wheels turning slower than they should)") + -- Player:PrintMessage( HUD_PRINTTALK, "- Worse vehicle performance (less grip, slower accelerating)") + -- + -- Player.IsInformedAboutTheServersLowTickrate = true + -- end + + local VehicleList = list.Get( "simfphys_vehicles" ) + local vehicle = VehicleList[ vname ] + + if not vehicle then return end + + if not tr then + tr = Player:GetEyeTraceNoCursor() + end + + local Angles = Player:GetAngles() + Angles.pitch = 0 + Angles.roll = 0 + Angles.yaw = Angles.yaw + 180 + (vehicle.SpawnAngleOffset and vehicle.SpawnAngleOffset or 0) + + local pos = tr.HitPos + Vector(0,0,25) + (vehicle.SpawnOffset or Vector(0,0,0)) + + local Ent = simfphys.SpawnVehicle( Player, pos, Angles, vehicle.Model, vehicle.Class, vname, vehicle ) + + if not IsValid( Ent ) then return end + + undo.Create( "Vehicle" ) + undo.SetPlayer( Player ) + undo.AddEntity( Ent ) + undo.SetCustomUndoText( "Undone " .. vehicle.Name ) + undo.Finish( "Vehicle (" .. tostring( vehicle.Name ) .. ")" ) + + Player:AddCleanup( "vehicles", Ent ) +end +concommand.Add( "simfphys_spawnvehicle", function( ply, cmd, args ) SpawnSimfphysVehicle( ply, args[1] ) end ) + +local function VehicleMemDupe( Player, Entity, Data ) + table.Merge( Entity, Data ) +end +duplicator.RegisterEntityModifier( "VehicleMemDupe", VehicleMemDupe ) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/server/tow.lua b/garrysmod/addons/feature-cars/lua/simfphys/server/tow.lua new file mode 100644 index 0000000..02b7d65 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/server/tow.lua @@ -0,0 +1,7 @@ +simfphys.AddPostSpawnAction('sim_fphys_gta4_bobcat_towtruck', function(veh) + + local hk = ents.Create 'dbg_tow_hook' + hk:Spawn() + hk:SetTruck(veh) + +end) diff --git a/garrysmod/addons/feature-cars/lua/simfphys/view.lua b/garrysmod/addons/feature-cars/lua/simfphys/view.lua new file mode 100644 index 0000000..4c45b12 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/simfphys/view.lua @@ -0,0 +1,180 @@ +local LockedPitch = 5 +if CLIENT then + cvars.AddChangeCallback( "cl_simfphys_ms_lockedpitch", function( convar, oldValue, newValue ) LockedPitch = tonumber( newValue ) end) + LockedPitch = GetConVar( "cl_simfphys_ms_lockedpitch" ):GetFloat() +end + + +local function simfphyslerpView( ply, view ) + + ply.simfphys_smooth_in = ply.simfphys_smooth_in or 1 + ply.simfphys_smooth_out = ply.simfphys_smooth_out or 1 + + if ply:InVehicle() then + if ply.simfphys_smooth_in < 0.999 then + ply.simfphys_smooth_in = ply.simfphys_smooth_in + (1 - ply.simfphys_smooth_in) * FrameTime() * 5 + + view.origin = LerpVector(ply.simfphys_smooth_in, ply.simfphys_eyepos_in, view.origin ) + view.angles = LerpAngle(ply.simfphys_smooth_in, ply.simfphys_eyeang_in, view.angles ) + end + + local vehicle = ply:GetVehicle() + if IsValid(vehicle) then + ply.simfphys_eyeang_out = view.angles + ply.simfphys_eyepos_out = view.origin + end + else + if ply.simfphys_smooth_out < 0.999 then + ply.simfphys_smooth_out = ply.simfphys_smooth_out + (1 - ply.simfphys_smooth_out) * FrameTime() * 5 + + view.origin = LerpVector(ply.simfphys_smooth_out, ply.simfphys_eyepos_out, ply:GetShootPos() ) + view.angles = LerpAngle(ply.simfphys_smooth_out, ply.simfphys_eyeang_out, ply:EyeAngles() ) + end + + ply.simfphys_eyeang_in = view.angles + ply.simfphys_eyepos_in = view.origin + end + + return view +end + +hook.Add( "CalcView", "simfphys_camtransitionshit", function( ply, pos, angles, fov ) + if not ply:InVehicle() then + ply.simfphys_smooth_in = 0 + ply.simfphys_smooth_out = ply.simfphys_smooth_out or 1 + + if ply.simfphys_smooth_out < 0.999 then + + local view = {} + view.origin = Vector(0,0,0) + view.angles = angles + view.fov = fov + view.drawviewer = false + + return simfphyslerpView( ply, view ) + else + ply.simfphys_eyeang_in = angles + ply.simfphys_eyepos_in = pos + end + end +end) + +local function GetViewOverride( vehicle ) + if not IsValid( vehicle ) then return Vector(0,0,0) end + + if not vehicle.customview then + local car = vehicle.vehiclebase + car.spawnlist = car.spawnlist or list.Get( "simfphys_vehicles" )[ car:GetSpawn_List() ] + local vehiclelist = car.spawnlist + + if vehiclelist then + vehicle.customview = vehiclelist.Members.FirstPersonViewPos or Vector(0,-9,5) + else + vehicle.customview = Vector(0,-9,5) + end + end + + return vehicle.customview +end + +hook.Add("CalcVehicleView", "simfphysViewOverride", function(Vehicle, ply, view) + + local vehiclebase = Vehicle.vehiclebase + + if not IsValid(vehiclebase) then return end + + local IsDriverSeat = Vehicle == vehiclebase:GetDriverSeat() + + if Vehicle.GetThirdPersonMode == nil or ply:GetViewEntity() ~= ply then + return + end + + ply.simfphys_smooth_out = 0 + + if not Vehicle:GetThirdPersonMode() then + local viewoverride = GetViewOverride( Vehicle ) + + local X = viewoverride.X + local Y = viewoverride.Y + local Z = viewoverride.Z + + view.origin = IsDriverSeat and view.origin + Vehicle:GetForward() * X + Vehicle:GetRight() * Y + Vehicle:GetUp() * Z or view.origin + Vehicle:GetUp() * 5 + + return simfphyslerpView( ply, view ) + end + + local mn, mx = vehiclebase:GetRenderBounds() + local radius = ( mn - mx ):Length() + local radius = radius + radius * Vehicle:GetCameraDistance() + + local TargetOrigin = view.origin + ( view.angles:Forward() * -radius ) + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() + local collide = not c:StartWith( "prop_physics" ) and not c:StartWith( "prop_dynamic" ) and not c:StartWith( "prop_ragdoll" ) and not e:IsVehicle() and not c:StartWith( "gmod_" ) and not c:StartWith( "player" ) + return collide + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + view.drawviewer = true + + if tr.Hit and not tr.StartSolid then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return simfphyslerpView( ply, view ) +end) + +-- hook.Add("StartCommand", "simfphys_lockview", function(ply, ucmd) +-- local vehicle = ply:GetVehicle() +-- if not IsValid(vehicle) then return end + +-- local vehiclebase = vehicle.vehiclebase + +-- if not IsValid(vehiclebase) then return end + +-- local IsDriverSeat = vehicle == vehiclebase:GetDriverSeat() + +-- if not IsDriverSeat then return end +-- if not (ply:GetInfoNum( "cl_simfphys_mousesteer", 0 ) == 1) then return end + +-- local ang = ucmd:GetViewAngles() + +-- if ply.Freelook then +-- vehicle.lockedpitch = ang.p +-- vehicle.lockedyaw = ang.y +-- return +-- end + +-- vehicle.lockedpitch = vehicle.lockedpitch or 0 +-- vehicle.lockedyaw = vehicle.lockedyaw or 90 + +-- local dir = 0 +-- if vehicle.lockedyaw < 90 and vehicle.lockedyaw > -90 then +-- dir = math.abs(vehicle.lockedyaw - 90) +-- end +-- if vehicle.lockedyaw >= 90 then +-- dir = -math.abs(vehicle.lockedyaw - 90) +-- end +-- if vehicle.lockedyaw < -90 and vehicle.lockedyaw >= -270 then +-- dir = -math.abs(vehicle.lockedyaw + 270) +-- end + +-- vehicle.lockedyaw = vehicle.lockedyaw + dir * 0.05 +-- vehicle.lockedpitch = vehicle.lockedpitch + (LockedPitch - vehicle.lockedpitch) * 0.05 + +-- if ply:GetInfoNum( "cl_simfphys_ms_lockpitch", 0 ) == 1 then +-- ang.p = vehicle.lockedpitch +-- end + +-- ang.y = vehicle.lockedyaw + +-- ucmd:SetViewAngles( ang ) +-- end) diff --git a/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyseditor.lua b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyseditor.lua new file mode 100644 index 0000000..23025a8 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyseditor.lua @@ -0,0 +1,351 @@ + +TOOL.Category = "simfphys" +TOOL.Name = "#Vehicle Editor" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "steerspeed" ] = 8 +TOOL.ClientConVar[ "fadespeed" ] = 535 +TOOL.ClientConVar[ "faststeerangle" ] = 0.3 +TOOL.ClientConVar[ "soundpreset" ] = 0 +TOOL.ClientConVar[ "idlerpm" ] = 800 +TOOL.ClientConVar[ "maxrpm" ] = 6200 +TOOL.ClientConVar[ "powerbandstart" ] = 2000 +TOOL.ClientConVar[ "powerbandend" ] = 6000 +TOOL.ClientConVar[ "maxtorque" ] = 280 +TOOL.ClientConVar[ "turbocharged" ] = "0" +TOOL.ClientConVar[ "supercharged" ] = "0" +TOOL.ClientConVar[ "revlimiter" ] = "0" +TOOL.ClientConVar[ "diffgear" ] = 0.65 +TOOL.ClientConVar[ "traction" ] = 43 +TOOL.ClientConVar[ "tractionbias" ] = -0.02 +TOOL.ClientConVar[ "brakepower" ] = 45 +TOOL.ClientConVar[ "powerdistribution" ] = 1 +TOOL.ClientConVar[ "efficiency" ] = 1.25 +TOOL.ClientConVar[ "csmul" ] = 0 +TOOL.ClientConVar[ "csang" ] = 90 +TOOL.ClientConVar[ "drag" ] = -250 + +if CLIENT then + language.Add( "tool.simfphyseditor.name", "simfphys vehicle editor" ) + language.Add( "tool.simfphyseditor.desc", "A tool used to edit simfphys vehicles" ) + language.Add( "tool.simfphyseditor.0", "Left click apply settings. Right click copy settings. Reload to reset" ) + language.Add( "tool.simfphyseditor.1", "Left click apply settings. Right click copy settings. Reload to reset" ) + + language.Add( "tool.simfphyseditor.steerspeed", "Steer Speed" ) + language.Add( "tool.simfphyseditor.steerspeed.help", "How fast the steering will move to its target angle" ) + language.Add( "tool.simfphyseditor.fastspeed", "Fast Steercone Fadespeed" ) + language.Add( "tool.simfphyseditor.fastspeed.help", "At wich speed (gmod units per second) we want to fade from slow steer angle to fast steer angle" ) + language.Add( "tool.simfphyseditor.faststeerang", "Fast Steer Angle" ) + language.Add( "tool.simfphyseditor.faststeerang.help", "Steering angle at high speeds." ) + language.Add( "tool.simfphyseditor.tractionbias", "Tractionbias" ) + language.Add( "tool.simfphyseditor.tractionbias.help", "A negative value will get more understeer, a positive value more oversteer. NOTE: this will not affect under/oversteer caused by engine power." ) + language.Add( "tool.simfphyseditor.powerdist", "Powerdistribution" ) + language.Add( "tool.simfphyseditor.powerdist.help", "How much power goes to the front and rear wheels, 1 = rear wheel drive -1 = front wheel drive 0 = all wheel drive with power distributed equally on front and rear wheels." ) + language.Add( "tool.simfphyseditor.efficiency", "Efficiency" ) + language.Add( "tool.simfphyseditor.efficiency.help", "This defines how good the wheels can put the engine power to the ground. Also may affect max torque and hand brake performance. Its a cheap way to make your car accelerate faster without having to deal with griploss." ) + language.Add( "tool.simfphyseditor.turbo", "Turbocharged" ) + language.Add( "tool.simfphyseditor.turbo.help", "Enables Turbo sounds and increases torque at high RPM" ) + language.Add( "tool.simfphyseditor.blower", "Supercharged" ) + language.Add( "tool.simfphyseditor.blower.help", "Enables Supercharger sounds and increases torque at low to mid RPM" ) + language.Add( "tool.simfphyseditor.revlimiter", "Revlimiter" ) + language.Add( "tool.simfphyseditor.revlimiter.help", "Enables bouncy revlimiter. NOTE: This does not work if Limit RPM is less than 2500!" ) + +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + ent:SetSteerSpeed( tonumber( self:GetClientInfo( "steerspeed" ) ) ) + ent:SetFastSteerConeFadeSpeed( tonumber( self:GetClientInfo( "fadespeed" ) ) ) + ent:SetFastSteerAngle( tonumber( self:GetClientInfo( "faststeerangle" ) ) ) + ent:SetEngineSoundPreset( tonumber( self:GetClientInfo( "soundpreset" ) ) ) + ent:SetIdleRPM( tonumber( self:GetClientInfo( "idlerpm" ) ) ) + ent:SetLimitRPM( tonumber( self:GetClientInfo( "maxrpm" ) ) ) + ent:SetPowerBandStart( tonumber( self:GetClientInfo( "powerbandstart" ) ) ) + ent:SetPowerBandEnd( tonumber( self:GetClientInfo( "powerbandend" ) ) ) + ent:SetMaxTorque( tonumber( self:GetClientInfo( "maxtorque" ) ) ) + ent:SetTurboCharged( self:GetClientInfo( "turbocharged" ) == "1") + ent:SetSuperCharged( self:GetClientInfo( "supercharged" ) == "1") + ent:SetRevlimiter( self:GetClientInfo( "revlimiter" ) == "1") + ent:SetDifferentialGear( tonumber( self:GetClientInfo( "diffgear" ) ) ) + ent:SetMaxTraction( math.max( tonumber( self:GetClientInfo( "traction" ) ) , 5) ) + ent:SetTractionBias( math.Clamp(tonumber( self:GetClientInfo( "tractionbias" ) ),-0.99,0.99) ) + ent:SetBrakePower( tonumber( self:GetClientInfo( "brakepower" ) ) ) + ent:SetPowerDistribution( math.Clamp(tonumber( self:GetClientInfo( "powerdistribution" ) ) ,-1,1) ) + ent:SetEfficiency( tonumber( self:GetClientInfo( "efficiency" ) ) ) + ent.CounterSteeringMul = tonumber( self:GetClientInfo( "csmul" ) ) + ent.CounterSteeringAng = tonumber( self:GetClientInfo( "csang" ) ) + ent.AirFriction = tonumber( self:GetClientInfo( "drag" ) ) + ent:GetPhysicsObject():SetDragCoefficient(ent.AirFriction) + end + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if (SERVER) then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + + ply:ConCommand( "simfphyseditor_steerspeed " ..ent:GetSteerSpeed() ) + ply:ConCommand( "simfphyseditor_fadespeed " ..ent:GetFastSteerConeFadeSpeed() ) + ply:ConCommand( "simfphyseditor_faststeerangle " ..ent:GetFastSteerAngle() ) + ply:ConCommand( "simfphyseditor_soundpreset " ..ent:GetEngineSoundPreset() ) + ply:ConCommand( "simfphyseditor_idlerpm " ..ent:GetIdleRPM() ) + ply:ConCommand( "simfphyseditor_maxrpm " ..ent:GetLimitRPM() ) + ply:ConCommand( "simfphyseditor_powerbandstart " ..ent:GetPowerBandStart() ) + ply:ConCommand( "simfphyseditor_powerbandend " ..ent:GetPowerBandEnd() ) + ply:ConCommand( "simfphyseditor_maxtorque " ..ent:GetMaxTorque() ) + ply:ConCommand( "simfphyseditor_turbocharged " ..(ent:GetTurboCharged() and 1 or 0) ) + ply:ConCommand( "simfphyseditor_supercharged " ..(ent:GetSuperCharged() and 1 or 0) ) + ply:ConCommand( "simfphyseditor_revlimiter " ..(ent:GetRevlimiter() and 1 or 0) ) + ply:ConCommand( "simfphyseditor_diffgear " ..ent:GetDifferentialGear() ) + ply:ConCommand( "simfphyseditor_traction " ..ent:GetMaxTraction() ) + ply:ConCommand( "simfphyseditor_tractionbias " ..ent:GetTractionBias() ) + ply:ConCommand( "simfphyseditor_brakepower " ..ent:GetBrakePower() ) + ply:ConCommand( "simfphyseditor_powerdistribution " ..ent:GetPowerDistribution() ) + ply:ConCommand( "simfphyseditor_efficiency " ..ent:GetEfficiency() ) + ply:ConCommand( "simfphyseditor_csmul " .. (ent.CounterSteeringMul or 0) ) + ply:ConCommand( "simfphyseditor_csang " .. (ent.CounterSteeringAng or 90) ) + ply:ConCommand( "simfphyseditor_drag " .. (ent.AirFriction or -250) ) + end + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if (SERVER) then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + ent:SetSteerSpeed( VehicleList.Members.TurnSpeed ) + ent:SetFastSteerConeFadeSpeed( VehicleList.Members.SteeringFadeFastSpeed ) + ent:SetFastSteerAngle( VehicleList.Members.FastSteeringAngle / ent.VehicleData["steerangle"] ) + ent:SetEngineSoundPreset( VehicleList.Members.EngineSoundPreset ) + ent:SetIdleRPM( VehicleList.Members.IdleRPM ) + ent:SetLimitRPM( VehicleList.Members.LimitRPM ) + ent:SetPowerBandStart( VehicleList.Members.PowerbandStart ) + ent:SetPowerBandEnd( VehicleList.Members.PowerbandEnd ) + ent:SetMaxTorque( VehicleList.Members.PeakTorque ) + ent:SetTurboCharged( VehicleList.Members.Turbocharged or false ) + ent:SetSuperCharged( VehicleList.Members.Supercharged or false ) + ent:SetRevlimiter( VehicleList.Members.Revlimiter or false ) + ent:SetDifferentialGear( VehicleList.Members.DifferentialGear ) + ent:SetMaxTraction( VehicleList.Members.MaxGrip ) + ent:SetTractionBias( VehicleList.Members.GripOffset / VehicleList.Members.MaxGrip ) + ent:SetBrakePower( VehicleList.Members.BrakePower ) + ent:SetPowerDistribution( VehicleList.Members.PowerBias ) + ent:SetEfficiency( VehicleList.Members.Efficiency ) + ent.CounterSteeringMul = VehicleList.Members.CounterSteeringMul + ent.CounterSteeringAng = VehicleList.Members.CounterSteeringAng + ent.AirFriction = VehicleList.Members.AirFriction or -250 + ent:GetPhysicsObject():SetDragCoefficient(ent.AirFriction) + end + + return true +end + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.simfphyseditor.name", Description = "#tool.simfphyseditor.desc" } ) + + panel:AddControl( "ComboBox", { MenuButton = 1, Folder = "simfphys", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "--- Steering ---" } ) + panel:AddControl( "Slider", + { + Label = "#tool.simfphyseditor.steerspeed", + Type = "Float", + Min = "1", + Max = "16", + Command = "simfphyseditor_steerspeed", + Help = true + }) + panel:AddControl( "Slider", + { + Label = "#tool.simfphyseditor.fastspeed", + Type = "Float", + Min = "1", + Max = "5000", + Command = "simfphyseditor_fadespeed", + Help = true + }) + panel:AddControl( "Slider", + { + Label = "#tool.simfphyseditor.faststeerang", + Type = "Float", + Min = "0", + Max = "1", + Command = "simfphyseditor_faststeerangle", + Help = true + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "--- Engine ---" } ) + panel:AddControl( "Slider", + { + Label = "Engine Sound Preset", + Type = "Int", + Min = "-1", + Max = "14", + Command = "simfphyseditor_soundpreset" + }) + panel:AddControl( "Slider", + { + Label = "Idle RPM", + Type = "Int", + Min = "1", + Max = "25000", + Command = "simfphyseditor_idlerpm" + }) + panel:AddControl( "Slider", + { + Label = "Limit RPM", + Type = "Int", + Min = "4", + Max = "25000", + Command = "simfphyseditor_maxrpm" + }) + panel:AddControl( "Slider", + { + Label = "Powerband Start", + Type = "Int", + Min = "2", + Max = "25000", + Command = "simfphyseditor_powerbandstart" + }) + panel:AddControl( "Slider", + { + Label = "Powerband End", + Type = "Int", + Min = "3", + Max = "25000", + Command = "simfphyseditor_powerbandend" + }) + panel:AddControl( "Slider", + { + Label = "Max Torque", + Type = "Float", + Min = "20", + Max = "1000", + Command = "simfphyseditor_maxtorque" + }) + panel:AddControl( "Checkbox", + { + Label = "#tool.simfphyseditor.revlimiter", + Command = "simfphyseditor_revlimiter", + Help = true + }) + panel:AddControl( "Checkbox", + { + Label = "#tool.simfphyseditor.turbo", + Command = "simfphyseditor_turbocharged", + Help = true + }) + panel:AddControl( "Checkbox", + { + Label = "#tool.simfphyseditor.blower", + Command = "simfphyseditor_supercharged", + Help = true + }) + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "--- Transmission ---" } ) + panel:AddControl( "Slider", + { + Label = "DifferentialGear", + Type = "Float", + Min = "0.2", + Max = "6", + Command = "simfphyseditor_diffgear" + }) + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "--- Wheels ---" } ) + panel:AddControl( "Slider", + { + Label = "Max Traction", + Type = "Float", + Min = "5", + Max = "500", + Command = "simfphyseditor_traction" + }) + panel:AddControl( "Slider", + { + Label = "#tool.simfphyseditor.tractionbias", + Type = "Float", + Min = "-0.99", + Max = "0.99", + Command = "simfphyseditor_tractionbias", + Help = true + }) + panel:AddControl( "Slider", + { + Label = "Brakepower", + Type = "Float", + Min = "0.1", + Max = "500", + Command = "simfphyseditor_brakepower" + }) + panel:AddControl( "Slider", + { + Label = "#tool.simfphyseditor.powerdist", + Type = "Float", + Min = "-1", + Max = "1", + Command = "simfphyseditor_powerdistribution", + Help = true + }) + panel:AddControl( "Slider", + { + Label = "#tool.simfphyseditor.efficiency", + Type = "Float", + Min = "0.2", + Max = "2", + Command = "simfphyseditor_efficiency", + Help = true + }) + + panel:AddControl( "Slider", + { + Label = "CSteer Multiplier", + Type = "Float", + Min = "0", + Max = "1", + Command = "simfphyseditor_csmul" + }) + + panel:AddControl( "Slider", + { + Label = "CSteer Max Angle", + Type = "Float", + Min = "0", + Max = "90", + Command = "simfphyseditor_csang" + }) + + panel:AddControl( "Slider", + { + Label = "Air Friction", + Type = "Float", + Min = "-250", + Max = "250", + Command = "simfphyseditor_drag" + }) +end diff --git a/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphysgeareditor.lua b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphysgeareditor.lua new file mode 100644 index 0000000..42ac3d7 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphysgeareditor.lua @@ -0,0 +1,235 @@ + +TOOL.Category = "simfphys" +TOOL.Name = "#Transmission Editor" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "numgears" ] = 5 +TOOL.ClientConVar[ "gear_r" ] = -0.1 +TOOL.ClientConVar[ "gear_1" ] = 0.1 +TOOL.ClientConVar[ "gear_2" ] = 0.2 +TOOL.ClientConVar[ "gear_3" ] = 0.3 +TOOL.ClientConVar[ "gear_4" ] = 0.4 +TOOL.ClientConVar[ "gear_5" ] = 0.5 +TOOL.ClientConVar[ "gear_6" ] = 0.6 +TOOL.ClientConVar[ "gear_7" ] = 0.7 +TOOL.ClientConVar[ "gear_8" ] = 0.8 +TOOL.ClientConVar[ "gear_9" ] = 0.9 +TOOL.ClientConVar[ "gear_10" ] = 1 +TOOL.ClientConVar[ "gear_11" ] = 1.1 +TOOL.ClientConVar[ "gear_12" ] = 1.2 +TOOL.ClientConVar[ "gear_diff" ] = 0.5 +TOOL.ClientConVar[ "forcetype" ] = "0" +TOOL.ClientConVar[ "type" ] = 2 + +local function SetGears( ply, ent, gears) + if ( SERVER ) then + ent.Gears = gears + duplicator.StoreEntityModifier( ent, "gearmod", gears ) + end +end +duplicator.RegisterEntityModifier( "gearmod", SetGears ) + +if CLIENT then + language.Add( "tool.simfphysgeareditor.name", "Transmission Editor" ) + language.Add( "tool.simfphysgeareditor.desc", "A tool used to edit gear ratios on simfphys vehicles" ) + language.Add( "tool.simfphysgeareditor.0", "Left click apply settings. Right click copy settings. Reload to reset" ) + language.Add( "tool.simfphysgeareditor.1", "Left click apply settings. Right click copy settings. Reload to reset" ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if (SERVER) then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + local gears = {tonumber( self:GetClientInfo( "gear_r" ) ),0} + for i = 1, tonumber( self:GetClientInfo( "numgears" ) ) do + local index = i + 2 + gears[index] = tonumber( self:GetClientInfo( "gear_"..i ) ) + end + + SetGears(self:GetOwner(), ent, gears ) + ent:SetDifferentialGear( tonumber( self:GetClientInfo( "gear_diff" ) ) ) + + if tobool( self:GetClientInfo( "forcetype" ) ) then + ent.ForceTransmission = math.Clamp(tonumber( self:GetClientInfo( "type" ) ),1,2) + else + ent.ForceTransmission = nil + end + end + + return true +end + + +function TOOL:RightClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if (SERVER) then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + local gears = ent.Gears + local diffgear = ent:GetDifferentialGear() + local num = table.Count( gears ) - 2 + + for i = 3, 13 do + ply:ConCommand( "simfphysgeareditor_gear_"..(i - 2).." "..(gears[i] or 0.001)) + end + ply:ConCommand( "simfphysgeareditor_gear_r "..gears[1]) + ply:ConCommand( "simfphysgeareditor_numgears "..num) + ply:ConCommand( "simfphysgeareditor_gear_diff "..diffgear) + + local forcetype = isnumber( ent.ForceTransmission ) + + ply:ConCommand( "simfphysgeareditor_forcetype "..tostring(forcetype and 1 or 0) ) + + if forcetype then + ply:ConCommand( "simfphysgeareditor_type "..ent.ForceTransmission) + end + end + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if (SERVER) then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + SetGears(self:GetOwner(), ent, VehicleList.Members.Gears ) + ent:SetDifferentialGear( VehicleList.Members.DifferentialGear ) + + ent.ForceTransmission = VehicleList.Members.ForceTransmission + end + + return true +end + +local ConVarsDefault = TOOL:BuildConVarList() +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.simfphysgeareditor.name", Description = "#tool.simfphysgeareditor.desc" } ) + panel:AddControl( "ComboBox", { MenuButton = 1, Folder = "transeditor", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + local Frame = vgui.Create( "DPanel", panel ) + Frame:SetPos( 10, 130 ) + Frame:SetSize( 275, 700 ) + Frame.Paint = function( self, w, h ) + end + + local Label = vgui.Create( "DLabel", panel ) + Label:SetPos( 15, 80 ) + Label:SetSize( 280, 40 ) + Label:SetText( "Amount Gears" ) + Label:SetTextColor( Color(0,0,0,255) ) + + local n_slider = vgui.Create( "DNumSlider", panel) + n_slider:SetPos( 15, 80 ) + n_slider:SetSize( 280, 40 ) + n_slider:SetMin( 1 ) + n_slider:SetMax( 12 ) + n_slider:SetDecimals( 0 ) + n_slider:SetConVar( "simfphysgeareditor_numgears" ) + n_slider.OnValueChanged = function( self, amount ) + Frame:Clear() + + local value = math.Round( amount, 0 ) + local yy = 0 + + for i = 1, value do + local Label = vgui.Create( "DLabel", Frame ) + Label:SetPos( 5, yy ) + Label:SetSize( 275, 40 ) + Label:SetText( "Gear "..i ) + Label:SetTextColor( Color(0,0,0,255) ) + + local g_slider = vgui.Create( "DNumSlider", Frame) + g_slider:SetPos( 5, yy ) + g_slider:SetSize( 275, 40 ) + g_slider:SetMin( 0.001 ) + g_slider:SetMax( 5 ) + g_slider:SetDecimals( 3 ) + g_slider:SetConVar( "simfphysgeareditor_gear_"..i ) + + yy = yy + 25 + end + + yy = yy + 25 + + local Label = vgui.Create( "DLabel", Frame ) + Label:SetPos( 5, yy ) + Label:SetSize( 275, 40 ) + Label:SetText( "Reverse" ) + Label:SetTextColor( Color(0,0,0,255) ) + local g_slider = vgui.Create( "DNumSlider", Frame) + g_slider:SetPos( 5, yy ) + g_slider:SetSize( 275, 40 ) + g_slider:SetMin( -5 ) + g_slider:SetMax( -0.001 ) + g_slider:SetDecimals( 3 ) + g_slider:SetConVar( "simfphysgeareditor_gear_r" ) + + yy = yy + 50 + + local Label = vgui.Create( "DLabel", Frame ) + Label:SetPos( 5, yy ) + Label:SetSize( 275, 40 ) + Label:SetText( "Final Gear (Differential)" ) + Label:SetTextColor( Color(0,0,0,255) ) + local g_slider = vgui.Create( "DNumSlider", Frame) + g_slider:SetPos( 5, yy ) + g_slider:SetSize( 275, 40 ) + g_slider:SetMin( 0.001 ) + g_slider:SetMax( 5 ) + g_slider:SetDecimals( 3 ) + g_slider:SetConVar( "simfphysgeareditor_gear_diff" ) + + yy = yy + 50 + + local Label = vgui.Create( "DLabel", Frame ) + Label:SetPos( 30, yy ) + Label:SetSize( 280, 40 ) + Label:SetText( "Force Transmission Type" ) + Label:SetTextColor( Color(0,0,0,255) ) + + local CheckBox = vgui.Create( "DCheckBoxLabel", Frame ) + CheckBox:SetPos( 5,yy ) + CheckBox:SetText( "" ) + CheckBox:SetConVar( "simfphysgeareditor_forcetype" ) + CheckBox:SetSize( 280, 40 ) + + yy = yy + 30 + + local Label = vgui.Create( "DLabel", Frame ) + Label:SetPos( 5, yy ) + Label:SetSize( 275, 40 ) + Label:SetText( "Type \n1 = Automatic\n2 = Manual" ) + Label:SetTextColor( Color(0,0,0,255) ) + local g_slider = vgui.Create( "DNumSlider", Frame) + g_slider:SetPos( 5, yy ) + g_slider:SetSize( 275, 40 ) + g_slider:SetMin( 1 ) + g_slider:SetMax( 2 ) + g_slider:SetDecimals( 0 ) + g_slider:SetConVar( "simfphysgeareditor_type" ) + + end +end diff --git a/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphysmiscsoundeditor.lua b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphysmiscsoundeditor.lua new file mode 100644 index 0000000..2239141 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphysmiscsoundeditor.lua @@ -0,0 +1,151 @@ +TOOL.Category = "simfphys" +TOOL.Name = "#Sound Editor - Misc" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "TurboBlowOff" ] = "simulated_vehicles/turbo_blowoff.ogg" +TOOL.ClientConVar[ "TurboSpin" ] = "simulated_vehicles/turbo_spin.wav" +TOOL.ClientConVar[ "SuperChargerOn" ] = "simulated_vehicles/blower_gearwhine.wav" +TOOL.ClientConVar[ "SuperChargerOff" ] = "simulated_vehicles/blower_spin.wav" +TOOL.ClientConVar[ "HornSound" ] = "simulated_vehicles/horn_1.wav" +TOOL.ClientConVar[ "BackfireSound" ] = "" + +if CLIENT then + language.Add( "tool.simfphysmiscsoundeditor.name", "Misc Sound Editor" ) + language.Add( "tool.simfphysmiscsoundeditor.desc", "A tool used to edit miscellaneous sounds on simfphys vehicles" ) + language.Add( "tool.simfphysmiscsoundeditor.0", "Left click apply settings. Right click copy settings. Reload to reset" ) + language.Add( "tool.simfphysmiscsoundeditor.1", "Left click apply settings. Right click copy settings. Reload to reset" ) + + presets.Add( "simfphys_miscsound", "Horn 0 - Out of my way", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_0.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 1", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_1.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 2", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_2.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 3", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_3.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 4", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_4.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 5", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_5.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 6 - Vote Daniels", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_6.wav", } ) + presets.Add( "simfphys_miscsound", "Horn 7", { simfphysmiscsoundeditor_HornSound = "simulated_vehicles/horn_7.wav", } ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + ent.snd_blowoff = self:GetClientInfo( "TurboBlowOff" ) + ent.snd_spool = self:GetClientInfo( "TurboSpin" ) + ent.snd_bloweroff = self:GetClientInfo( "SuperChargerOff" ) + ent.snd_bloweron = self:GetClientInfo( "SuperChargerOn" ) + ent.snd_horn = self:GetClientInfo( "HornSound" ) + ent:SetBackfireSound( self:GetClientInfo( "BackfireSound" ) ) + end + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + + local Sounds = {} + Sounds.TurboBlowOff = ent.snd_blowoff or "simulated_vehicles/turbo_blowoff.ogg" + Sounds.TurboSpin = ent.snd_spool or "simulated_vehicles/turbo_spin.wav" + Sounds.SuperCharger1 = ent.snd_bloweroff or "simulated_vehicles/blower_spin.wav" + Sounds.SuperCharger2 = ent.snd_bloweron or "simulated_vehicles/blower_gearwhine.wav" + Sounds.HornSound = ent.snd_horn or "simulated_vehicles/horn_1.wav" + + ply:ConCommand( "simfphysmiscsoundeditor_TurboBlowOff "..Sounds.TurboBlowOff ) + ply:ConCommand( "simfphysmiscsoundeditor_TurboSpin "..Sounds.TurboSpin ) + ply:ConCommand( "simfphysmiscsoundeditor_SuperChargerOn "..Sounds.SuperCharger2 ) + ply:ConCommand( "simfphysmiscsoundeditor_SuperChargerOff "..Sounds.SuperCharger1 ) + ply:ConCommand( "simfphysmiscsoundeditor_HornSound "..Sounds.HornSound ) + + local backfiresound = ent:GetBackfireSound() + if backfiresound == "" then + ply:ConCommand( "simfphysmiscsoundeditor_BackfireSound simulated_vehicles/sfx/ex_backfire_1.ogg" ) + else + ply:ConCommand( "simfphysmiscsoundeditor_BackfireSound "..backfiresound ) + end + end + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local vehiclelist = list.Get( "simfphys_vehicles" )[ ent:GetSpawn_List() ] + + ent.snd_blowoff = vehiclelist.Members.snd_blowoff or "simulated_vehicles/turbo_blowoff.ogg" + ent.snd_spool = vehiclelist.Members.snd_spool or "simulated_vehicles/turbo_spin.wav" + ent.snd_bloweroff = vehiclelist.Members.snd_bloweroff or "simulated_vehicles/blower_spin.wav" + ent.snd_bloweron = vehiclelist.Members.snd_bloweron or "simulated_vehicles/blower_gearwhine.wav" + ent.snd_horn = vehiclelist.Members.snd_horn or "simulated_vehicles/horn_1.wav" + ent:SetBackfireSound( vehiclelist.Members.snd_backfire or "" ) + end + + return true +end + +local ConVarsDefault = TOOL:BuildConVarList() +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.simfphysmiscsoundeditor.name", Description = "#tool.simfphysmiscsoundeditor.desc" } ) + panel:AddControl( "ComboBox", { MenuButton = 1, Folder = "simfphys_miscsound", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Textbox", + { + Label = "Turbo blowoff", + Command = "simfphysmiscsoundeditor_TurboBlowOff" + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Textbox", + { + Label = "Turbo", + Command = "simfphysmiscsoundeditor_TurboSpin" + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Textbox", + { + Label = "Supercharger 1", + Command = "simfphysmiscsoundeditor_SuperChargerOn" + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Textbox", + { + Label = "Supercharger 2", + Command = "simfphysmiscsoundeditor_SuperChargerOff" + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Textbox", + { + Label = "Horn", + Command = "simfphysmiscsoundeditor_HornSound" + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Textbox", + { + Label = "Backfire", + Command = "simfphysmiscsoundeditor_BackfireSound" + }) + +end diff --git a/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyssoundeditor.lua b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyssoundeditor.lua new file mode 100644 index 0000000..1ce828a --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyssoundeditor.lua @@ -0,0 +1,427 @@ +TOOL.Category = "simfphys" +TOOL.Name = "#Sound Editor - Engine" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "Idle" ] = "simulated_vehicles/misc/e49_idle.wav" +TOOL.ClientConVar[ "IdlePitch" ] = 1 +TOOL.ClientConVar[ "Mid" ] = "simulated_vehicles/misc/gto_onlow.wav" +TOOL.ClientConVar[ "MidPitch" ] = 1 +TOOL.ClientConVar[ "MidVolume" ] = 0.75 +TOOL.ClientConVar[ "MidFadeOutRPMpercent" ] = 68 +TOOL.ClientConVar[ "MidFadeOutRate" ] = 0.4 +TOOL.ClientConVar[ "High" ] = "simulated_vehicles/misc/nv2_onlow_ex.wav" +TOOL.ClientConVar[ "HighPitch" ] = 1 +TOOL.ClientConVar[ "HighVolume" ] = 1 +TOOL.ClientConVar[ "HighFadeInRPMpercent" ] = 26.6 +TOOL.ClientConVar[ "HighFadeInRate" ] = 0.266 +TOOL.ClientConVar[ "Throttle" ] = "simulated_vehicles/valve_noise.wav" +TOOL.ClientConVar[ "ThrottlePitch" ] = 0.65 +TOOL.ClientConVar[ "ThrottleVolume" ] = 1 +TOOL.ClientConVar[ "Type" ] = "0" +TOOL.ClientConVar[ "ShiftDown" ] = "" +TOOL.ClientConVar[ "ShiftUp" ] = "" +TOOL.ClientConVar[ "RevDown" ] = "" + +if CLIENT then + language.Add( "tool.simfphyssoundeditor.name", "Engine Sound Editor" ) + language.Add( "tool.simfphyssoundeditor.desc", "A tool used to edit engine sounds on simfphys vehicles" ) + language.Add( "tool.simfphyssoundeditor.0", "Left click apply settings. Right click copy settings. Reload to reset" ) + language.Add( "tool.simfphyssoundeditor.1", "Left click apply settings. Right click copy settings. Reload to reset" ) + + presets.Add( "simfphys_sound", "Jalopy", { + simfphyssoundeditor_High = "simulated_vehicles/jalopy/jalopy_high.wav", + simfphyssoundeditor_HighFadeInRate = "0.40", + simfphyssoundeditor_HighFadeInRPMpercent = "55.00", + simfphyssoundeditor_HighPitch = "0.75", + simfphyssoundeditor_HighVolume = "0.90", + simfphyssoundeditor_Idle = "simulated_vehicles/jalopy/jalopy_idle.wav", + simfphyssoundeditor_IdlePitch = "0.95", + simfphyssoundeditor_Mid = "simulated_vehicles/jalopy/jalopy_mid.wav", + simfphyssoundeditor_MidFadeOutRate = "0.25", + simfphyssoundeditor_MidFadeOutRPMpercent = "55.00", + simfphyssoundeditor_MidPitch = "1.00", + simfphyssoundeditor_MidVolume = "1.00", + simfphyssoundeditor_ThrottlePitch = "0.00", + simfphyssoundeditor_ThrottleVolume = "0.00", + simfphyssoundeditor_Type = "0", + simfphyssoundeditor_ShiftDown = "", + simfphyssoundeditor_ShiftUp = "", + simfphyssoundeditor_RevDown = "" + } ) + + presets.Add( "simfphys_sound", "APC", { + simfphyssoundeditor_High = "simulated_vehicles/misc/v8high2.wav", + simfphyssoundeditor_HighFadeInRate = "0.19", + simfphyssoundeditor_HighFadeInRPMpercent = "58.00", + simfphyssoundeditor_HighPitch = "1.00", + simfphyssoundeditor_HighVolume = "0.75", + simfphyssoundeditor_Idle = "simulated_vehicles/misc/nanjing_loop.wav", + simfphyssoundeditor_IdlePitch = "1.00", + simfphyssoundeditor_Mid = "simulated_vehicles/misc/m50.wav", + simfphyssoundeditor_MidFadeOutRate = "0.48", + simfphyssoundeditor_MidFadeOutRPMpercent = "58.00", + simfphyssoundeditor_MidPitch = "1.00", + simfphyssoundeditor_MidVolume = "1.00", + simfphyssoundeditor_ThrottlePitch = "0.00", + simfphyssoundeditor_ThrottleVolume = "0.00", + simfphyssoundeditor_Type = "0", + simfphyssoundeditor_ShiftDown = "", + simfphyssoundeditor_ShiftUp = "", + simfphyssoundeditor_RevDown = "" + } ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + if self:GetClientInfo( "Type" ) == "1" then + ent:SetEngineSoundPreset( -1 ) + + local outputstring = {} + outputstring[1] = self:GetClientInfo( "Type" ) + outputstring[2] = self:GetClientInfo( "High" ) + outputstring[3] = self:GetClientInfo( "HighPitch" ) + outputstring[4] = self:GetClientInfo( "Idle" ) + outputstring[5] = self:GetClientInfo( "IdlePitch" ) + outputstring[6] = self:GetClientInfo( "Mid" ) + outputstring[7] = self:GetClientInfo( "MidPitch" ) + outputstring[8] = self:GetClientInfo( "RevDown" ) + outputstring[9] = self:GetClientInfo( "ShiftDown" ) + outputstring[10] = self:GetClientInfo( "ShiftUp" ) + + ent:SetSoundoverride( string.Implode(",", outputstring ) ) + else + local outputstring = {} + outputstring[1] = self:GetClientInfo( "Idle" ) + outputstring[2] = self:GetClientInfo( "IdlePitch" ) + outputstring[3] = self:GetClientInfo( "Mid" ) + outputstring[4] = self:GetClientInfo( "MidPitch" ) + outputstring[5] = self:GetClientInfo( "MidVolume" ) + outputstring[6] = self:GetClientInfo( "MidFadeOutRPMpercent" ) + outputstring[7] = self:GetClientInfo( "MidFadeOutRate" ) + outputstring[8] = self:GetClientInfo( "High" ) + outputstring[9] = self:GetClientInfo( "HighPitch" ) + outputstring[10] = self:GetClientInfo( "HighVolume" ) + outputstring[11] = self:GetClientInfo( "HighFadeInRPMpercent" ) + outputstring[12] = self:GetClientInfo( "HighFadeInRate" ) + outputstring[13] = self:GetClientInfo( "Throttle" ) + outputstring[14] = self:GetClientInfo( "ThrottlePitch" ) + outputstring[15] = self:GetClientInfo( "ThrottleVolume" ) + + ent:SetEngineSoundPreset( 0 ) + ent:SetSoundoverride( string.Implode(",", outputstring ) ) + end + end + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + + local SoundType = ent:GetEngineSoundPreset() + local Sounds = {} + + local vehiclelist = list.Get( "simfphys_vehicles" )[ ent:GetSpawn_List() ] + + if SoundType == -1 then + Sounds.Type = 1 + + Sounds.Idle = vehiclelist.Members.snd_idle or "" + Sounds.Low = vehiclelist.Members.snd_low or "" + Sounds.High = vehiclelist.Members.snd_mid or "" + Sounds.RevDown = vehiclelist.Members.snd_low_revdown or Sounds.Low + Sounds.ShiftUp = vehiclelist.Members.snd_mid_gearup or Sounds.High + Sounds.ShiftDown = vehiclelist.Members.snd_mid_geardown or Sounds.ShiftUp + + Sounds.Pitch_Low = vehiclelist.Members.snd_low_pitch or 1 + Sounds.Pitch_High = vehiclelist.Members.snd_mid_pitch or 1 + Sounds.Pitch_All = vehiclelist.Members.snd_pitch or 1 + + elseif SoundType == 0 then + Sounds.Type = 0 + + local soundoverride = ent:GetSoundoverride() + local data = string.Explode( ",", soundoverride) + + if soundoverride ~= "" then + Sounds.Idle = data[1] + Sounds.Pitch_All = data[2] + + Sounds.Low = data[3] + Sounds.Pitch_Low = data[4] + Sounds.MidVolume = data[5] + Sounds.MidFadeOutPercent = data[6] + Sounds.MidFadeOutRate = data[7] + + Sounds.High = data[8] + Sounds.Pitch_High = data[9] + Sounds.HighVolume = data[10] + Sounds.HighFadeInPercent = data[11] + Sounds.HighFadeInRate = data[12] + + Sounds.ThrottleSound = data[13] + Sounds.ThrottlePitch = data[14] + Sounds.ThrottleVolume = data[15] + else + Sounds.Idle = vehiclelist and vehiclelist.Members.Sound_Idle or "simulated_vehicles/misc/e49_idle.wav" + Sounds.Pitch_All = vehiclelist and vehiclelist.Members.Sound_IdlePitch or 1 + + Sounds.Low = vehiclelist and vehiclelist.Members.Sound_Mid or "simulated_vehicles/misc/gto_onlow.wav" + Sounds.Pitch_Low = vehiclelist and vehiclelist.Members.Sound_MidPitch or 1 + Sounds.MidVolume = vehiclelist and vehiclelist.Members.Sound_MidVolume or 0.75 + Sounds.MidFadeOutPercent = vehiclelist and vehiclelist.Members.Sound_MidFadeOutRPMpercent or 68 + Sounds.MidFadeOutRate = vehiclelist and vehiclelist.Members.Sound_MidFadeOutRate or 0.4 + + Sounds.High = vehiclelist and vehiclelist.Members.Sound_High or "simulated_vehicles/misc/nv2_onlow_ex.wav" + Sounds.Pitch_High = vehiclelist and vehiclelist.Members.Sound_HighPitch or 1 + Sounds.HighVolume = vehiclelist and vehiclelist.Members.Sound_HighVolume or 1 + Sounds.HighFadeInPercent = vehiclelist and vehiclelist.Members.Sound_HighFadeInRPMpercent or 26.6 + Sounds.HighFadeInRate = vehiclelist and vehiclelist.Members.Sound_HighFadeInRate or 0.266 + + Sounds.ThrottleSound = vehiclelist and vehiclelist.Members.Sound_Throttle or "simulated_vehicles/valve_noise.wav" + Sounds.ThrottlePitch = vehiclelist and vehiclelist.Members.Sound_ThrottlePitch or 0.65 + Sounds.ThrottleVolume = vehiclelist and vehiclelist.Members.Sound_ThrottleVolume or 1 + end + else + local demSounds = simfphys.SoundPresets[ SoundType ] + Sounds.Type = 1 + + Sounds.Idle = demSounds[1] + Sounds.Low = demSounds[2] + Sounds.High = demSounds[3] + Sounds.RevDown = demSounds[4] + Sounds.ShiftUp = demSounds[5] + Sounds.ShiftDown = demSounds[6] + + Sounds.Pitch_Low = demSounds[7] + Sounds.Pitch_High = demSounds[8] + Sounds.Pitch_All = demSounds[9] + end + + ply:ConCommand( "simfphyssoundeditor_High "..Sounds.High ) + ply:ConCommand( "simfphyssoundeditor_HighPitch "..Sounds.Pitch_High ) + ply:ConCommand( "simfphyssoundeditor_Idle "..Sounds.Idle ) + ply:ConCommand( "simfphyssoundeditor_IdlePitch "..Sounds.Pitch_All ) + ply:ConCommand( "simfphyssoundeditor_Mid "..Sounds.Low ) + ply:ConCommand( "simfphyssoundeditor_MidPitch "..Sounds.Pitch_Low ) + ply:ConCommand( "simfphyssoundeditor_Type "..Sounds.Type ) + + if Sounds.Type == 1 then + ply:ConCommand( "simfphyssoundeditor_RevDown "..Sounds.RevDown ) + ply:ConCommand( "simfphyssoundeditor_ShiftDown "..Sounds.ShiftDown ) + ply:ConCommand( "simfphyssoundeditor_ShiftUp "..Sounds.ShiftUp ) + + ply:ConCommand( "simfphyssoundeditor_HighFadeInRate 0.2" ) + ply:ConCommand( "simfphyssoundeditor_HighFadeInRPMpercent 20" ) + ply:ConCommand( "simfphyssoundeditor_HighVolume 1" ) + ply:ConCommand( "simfphyssoundeditor_MidFadeOutRate 0.5" ) + ply:ConCommand( "simfphyssoundeditor_MidFadeOutRPMpercent 10") + ply:ConCommand( "simfphyssoundeditor_MidVolume 1" ) + + ply:ConCommand( "simfphyssoundeditor_ThrottlePitch 0" ) + ply:ConCommand( "simfphyssoundeditor_ThrottleVolume 0" ) + else + ply:ConCommand( "simfphyssoundeditor_HighFadeInRate "..Sounds.HighFadeInRate ) + ply:ConCommand( "simfphyssoundeditor_HighFadeInRPMpercent "..Sounds.HighFadeInPercent ) + ply:ConCommand( "simfphyssoundeditor_HighVolume "..Sounds.HighVolume ) + ply:ConCommand( "simfphyssoundeditor_MidFadeOutRate "..Sounds.MidFadeOutRate ) + ply:ConCommand( "simfphyssoundeditor_MidFadeOutRPMpercent "..Sounds.MidFadeOutPercent ) + ply:ConCommand( "simfphyssoundeditor_MidVolume "..Sounds.MidVolume ) + + ply:ConCommand( "simfphyssoundeditor_Throttle "..Sounds.ThrottleSound ) + ply:ConCommand( "simfphyssoundeditor_ThrottlePitch "..Sounds.ThrottlePitch ) + ply:ConCommand( "simfphyssoundeditor_ThrottleVolume "..Sounds.ThrottleVolume ) + end + end + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if SERVER then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + ent:SetEngineSoundPreset( VehicleList.Members.EngineSoundPreset ) + ent:SetSoundoverride( "" ) + end + + return true +end + +local function Slider( parent, name, concommand, ypos, min, max, decimals ) + local Label = vgui.Create( "DLabel", parent ) + Label:SetPos( 50, ypos ) + Label:SetSize( 225, 40 ) + Label:SetText( name ) + Label:SetTextColor( Color(0,0,0,255) ) + + local Slider = vgui.Create( "DNumSlider", parent ) + Slider:SetPos( 50, ypos ) + Slider:SetSize( 225, 40 ) + Slider:SetMin( min ) + Slider:SetMax( max ) + Slider:SetDecimals( decimals ) + Slider:SetConVar( concommand ) +end + +local function TextEntry( parent, name, concommand, ypos ) + local Label = vgui.Create( "DLabel", parent ) + Label:SetPos( 0, ypos ) + Label:SetSize( 275, 40 ) + Label:SetText( name ) + Label:SetTextColor( Color(0,0,0,255) ) + + local TextEntry = vgui.Create( "DTextEntry", parent ) + TextEntry:SetPos( 0, ypos + 30 ) + TextEntry:SetSize( 275, 20 ) + TextEntry:SetText( GetConVar( concommand ):GetString() ) + TextEntry:SetUpdateOnType( true ) + TextEntry.OnValueChange = function( self, value ) + RunConsoleCommand( concommand , tostring( value ) ) + end +end + +local refresh = false +cvars.AddChangeCallback( "simfphyssoundeditor_Idle", function( convar, oldValue, newValue ) + if oldValue ~= newValue then refresh = true end +end ) +cvars.AddChangeCallback( "simfphyssoundeditor_RevDown", function( convar, oldValue, newValue ) + if oldValue ~= newValue then refresh = true end +end ) +cvars.AddChangeCallback( "simfphyssoundeditor_ShiftUp", function( convar, oldValue, newValue ) + if oldValue ~= newValue then refresh = true end +end ) + +local function BuildMenu( Frame, panel ) + Frame:Clear() + + local checked = GetConVar( "simfphyssoundeditor_Type" ):GetString() == "1" + + local yy = 0 + + if not checked then + TextEntry( Frame, "Idle Sound:", "simfphyssoundeditor_Idle", yy ) + yy = yy + 50 + Slider( Frame, "Pitch", "simfphyssoundeditor_IdlePitch", yy, 0, 2.55, 2 ) + yy = yy + 50 + + TextEntry( Frame, "Mid Sound:", "simfphyssoundeditor_Mid", yy) + yy = yy + 50 + Slider( Frame, "Pitch", "simfphyssoundeditor_MidPitch", yy, 0, 2.55, 3 ) + yy = yy + 50 + Slider( Frame, "Volume", "simfphyssoundeditor_MidVolume", yy, 0, 1, 2 ) + yy = yy + 50 + Slider( Frame, "Fade out RPM %", "simfphyssoundeditor_MidFadeOutRPMpercent", yy, 0, 100, 0 ) + yy = yy + 50 + Slider( Frame, "Fade out rate\n(0 = instant)", "simfphyssoundeditor_MidFadeOutRate", yy, 0, 1, 2 ) + yy = yy + 50 + + TextEntry( Frame, "High Sound:", "simfphyssoundeditor_High", yy) + yy = yy + 50 + Slider( Frame, "Pitch", "simfphyssoundeditor_HighPitch", yy, 0, 2.55, 3 ) + yy = yy + 50 + Slider( Frame, "Volume", "simfphyssoundeditor_HighVolume", yy, 0, 1, 2 ) + yy = yy + 50 + Slider( Frame, "Fade in RPM %", "simfphyssoundeditor_HighFadeInRPMpercent", yy, 0, 100, 0 ) + yy = yy + 50 + Slider( Frame, "Fade in rate\n(0 = instant)", "simfphyssoundeditor_HighFadeInRate", yy, 0, 1, 2 ) + yy = yy + 50 + + TextEntry( Frame, "On Throttle Sound:", "simfphyssoundeditor_Throttle", yy) + yy = yy + 50 + Slider( Frame, "Pitch", "simfphyssoundeditor_ThrottlePitch", yy, 0, 2.55, 3 ) + yy = yy + 50 + Slider( Frame, "Volume", "simfphyssoundeditor_ThrottleVolume", yy, 0, 1, 2 ) + yy = yy + 50 + else + TextEntry( Frame, "Idle:", "simfphyssoundeditor_Idle", yy ) + yy = yy + 100 + + TextEntry( Frame, "Gear Up:", "simfphyssoundeditor_ShiftUp", yy ) + yy = yy + 50 + TextEntry( Frame, "Gear Down:", "simfphyssoundeditor_ShiftDown", yy ) + yy = yy + 50 + TextEntry( Frame, "Gear (continue):", "simfphyssoundeditor_High", yy ) + yy = yy + 50 + Slider( Frame, "Pitch (Gear)", "simfphyssoundeditor_HighPitch", yy, 0, 2.55, 3 ) + yy = yy + 75 + + TextEntry( Frame, "Revdown:", "simfphyssoundeditor_RevDown", yy ) + yy = yy + 50 + TextEntry( Frame, "Revdown (continue):", "simfphyssoundeditor_Mid", yy ) + yy = yy + 50 + Slider( Frame, "Pitch (Revdown)", "simfphyssoundeditor_MidPitch", yy, 0, 2.55, 3 ) + yy = yy + 100 + + + local Label = vgui.Create( "DLabel", Frame ) + Label:SetPos( 0, yy ) + Label:SetSize( 275, 40 ) + Label:SetText( "Pitch (Master)" ) + Label:SetTextColor( Color(0,0,0,255) ) + + local Slider = vgui.Create( "DNumSlider", Frame ) + Slider:SetPos( 0, yy ) + Slider:SetSize( 275, 40 ) + Slider:SetMin( 0 ) + Slider:SetMax( 2.55 ) + Slider:SetDecimals( 3 ) + Slider:SetConVar( "simfphyssoundeditor_IdlePitch" ) + end +end + +local ConVarsDefault = TOOL:BuildConVarList() +function TOOL.BuildCPanel( panel ) + local ply = LocalPlayer() + + panel:AddControl( "Header", { Text = "#tool.simfphyssoundeditor.name", Description = "#tool.simfphyssoundeditor.desc" } ) + panel:AddControl( "ComboBox", { MenuButton = 1, Folder = "simfphys_sound", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + local Frame = vgui.Create( "DPanel", panel ) + Frame:SetPos( 10, 130 ) + Frame:SetSize( 275, 900 ) + Frame.Paint = function( self, w, h ) + end + Frame.Think = function( self ) + if refresh then + refresh = false + BuildMenu( Frame, panel ) + end + end + + local Label = vgui.Create( "DLabel", panel ) + Label:SetPos( 35, 85 ) + Label:SetSize( 280, 40 ) + Label:SetText( "Advanced Sound" ) + Label:SetTextColor( Color(0,0,0,255) ) + + local CheckBox = vgui.Create( "DCheckBoxLabel", panel ) + CheckBox:SetPos( 15,85 ) + CheckBox:SetText( "" ) + CheckBox:SetConVar( "simfphyssoundeditor_Type" ) + CheckBox:SetSize( 280, 40 ) + CheckBox.OnChange = function( self, checked ) + BuildMenu( Frame, panel ) + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyssuspensioneditor.lua b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyssuspensioneditor.lua new file mode 100644 index 0000000..973e521 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyssuspensioneditor.lua @@ -0,0 +1,217 @@ + +TOOL.Category = "simfphys" +TOOL.Name = "#Suspension Editor" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "constant_f" ] = 25000 +TOOL.ClientConVar[ "constant_r" ] = 25000 + +TOOL.ClientConVar[ "height_f" ] = 0 +TOOL.ClientConVar[ "height_r" ] = 0 + +TOOL.ClientConVar[ "damping_f" ] = 2500 +TOOL.ClientConVar[ "damping_r" ] = 2500 + +if CLIENT then + language.Add( "tool.simfphyssuspensioneditor.name", "Suspension Editor" ) + language.Add( "tool.simfphyssuspensioneditor.desc", "A tool used to edit suspension on simfphys vehicles" ) + language.Add( "tool.simfphyssuspensioneditor.0", "Left click apply settings. Right click copy settings. Reload to reset" ) + language.Add( "tool.simfphyssuspensioneditor.1", "Left click apply settings. Right click copy settings. Reload to reset" ) +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if not SERVER then return true end + + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local data = { + [1] = {tonumber( self:GetClientInfo( "constant_f" ) ),tonumber( self:GetClientInfo( "damping_f" ) )}, + [2] = {tonumber( self:GetClientInfo( "constant_f" ) ),tonumber( self:GetClientInfo( "damping_f" ) )}, + [3] = {tonumber( self:GetClientInfo( "constant_r" ) ),tonumber( self:GetClientInfo( "damping_r" ) )}, + [4] = {tonumber( self:GetClientInfo( "constant_r" ) ),tonumber( self:GetClientInfo( "damping_r" ) )}, + [5] = {tonumber( self:GetClientInfo( "constant_r" ) ),tonumber( self:GetClientInfo( "damping_r" ) )}, + [6] = {tonumber( self:GetClientInfo( "constant_r" ) ),tonumber( self:GetClientInfo( "damping_r" ) )} + } + + local elastics = ent.Elastics + if (elastics) then + for i = 1, table.Count( elastics ) do + local elastic = elastics[i] + if (ent.StrengthenSuspension == true) then + if (IsValid(elastic)) then + elastic:Fire( "SetSpringConstant", data[i][1] * 0.5, 0 ) + elastic:Fire( "SetSpringDamping", data[i][2] * 0.5, 0 ) + end + local elastic2 = elastics[i * 10] + if (IsValid(elastic2)) then + elastic2:Fire( "SetSpringConstant", data[i][1] * 0.5, 0 ) + elastic2:Fire( "SetSpringDamping", data[i][2] * 0.5, 0 ) + end + else + if (IsValid(elastic)) then + elastic:Fire( "SetSpringConstant", data[i][1], 0 ) + elastic:Fire( "SetSpringDamping", data[i][2], 0 ) + end + end + + ent.FrontDampingOverride = data[1][2] + ent.FrontConstantOverride = data[1][1] + ent.RearDampingOverride = data[4][2] + ent.RearConstantOverride = data[4][1] + end + end + + ent:SetFrontSuspensionHeight( tonumber( self:GetClientInfo( "height_f" ) ) ) + ent:SetRearSuspensionHeight( tonumber( self:GetClientInfo( "height_r" ) ) ) + + return true +end + +function TOOL:RightClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if (SERVER) then + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + if ent.FrontDampingOverride and ent.FrontConstantOverride and ent.RearDampingOverride and ent.RearConstantOverride then + ply:ConCommand( "simfphyssuspensioneditor_constant_f " ..ent.FrontConstantOverride ) + ply:ConCommand( "simfphyssuspensioneditor_constant_r " ..ent.RearConstantOverride ) + + ply:ConCommand( "simfphyssuspensioneditor_damping_f " ..ent.FrontDampingOverride ) + ply:ConCommand( "simfphyssuspensioneditor_damping_r " ..ent.RearDampingOverride ) + else + ply:ConCommand( "simfphyssuspensioneditor_constant_f " ..VehicleList.Members.FrontConstant ) + ply:ConCommand( "simfphyssuspensioneditor_constant_r " ..VehicleList.Members.RearConstant ) + + ply:ConCommand( "simfphyssuspensioneditor_damping_f " ..VehicleList.Members.FrontDamping ) + ply:ConCommand( "simfphyssuspensioneditor_damping_r " ..VehicleList.Members.RearDamping ) + end + + ply:ConCommand( "simfphyssuspensioneditor_height_f " ..ent:GetFrontSuspensionHeight() ) + ply:ConCommand( "simfphyssuspensioneditor_height_r " ..ent:GetRearSuspensionHeight() ) + end + + return true +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + + if not simfphys.IsCar( ent ) then return false end + + if not SERVER then return true end + + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + local data = { + [1] = {VehicleList.Members.FrontConstant,VehicleList.Members.FrontDamping,VehicleList.Members.FrontHeight}, + [2] = {VehicleList.Members.FrontConstant,VehicleList.Members.FrontDamping,VehicleList.Members.FrontHeight}, + [3] = {VehicleList.Members.RearConstant,VehicleList.Members.RearDamping,VehicleList.Members.RearHeight}, + [4] = {VehicleList.Members.RearConstant,VehicleList.Members.RearDamping,VehicleList.Members.RearHeight}, + [5] = {VehicleList.Members.RearConstant,VehicleList.Members.RearDamping,VehicleList.Members.RearHeight}, + [6] = {VehicleList.Members.RearConstant,VehicleList.Members.RearDamping,VehicleList.Members.RearHeight}, + } + + local elastics = ent.Elastics + if (elastics) then + for i = 1, table.Count( elastics ) do + local elastic = elastics[i] + if (ent.StrengthenSuspension == true) then + if (IsValid(elastic)) then + elastic:Fire( "SetSpringConstant", data[i][1] * 0.5, 0 ) + elastic:Fire( "SetSpringDamping", data[i][2] * 0.5, 0 ) + end + local elastic2 = elastics[i * 10] + if (IsValid(elastic2)) then + elastic2:Fire( "SetSpringConstant", data[i][1] * 0.5, 0 ) + elastic2:Fire( "SetSpringDamping", data[i][2] * 0.5, 0 ) + end + else + if (IsValid(elastic)) then + elastic:Fire( "SetSpringConstant", data[i][1], 0 ) + elastic:Fire( "SetSpringDamping", data[i][2], 0 ) + end + end + end + end + ent:SetFrontSuspensionHeight( 0 ) + ent:SetRearSuspensionHeight( 0 ) + + return true +end + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.simfphyssuspensioneditor.name", Description = "#tool.simfphyssuspensioneditor.desc" } ) + panel:AddControl( "ComboBox", { MenuButton = 1, Folder = "suspensionedtior", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "--- Front ---" } ) + panel:AddControl( "Slider", + { + Label = "Front Height", + Type = "Float", + Min = "-1", + Max = "1", + Command = "simfphyssuspensioneditor_height_f" + }) + panel:AddControl( "Slider", + { + Label = "Front Constant", + Type = "Float", + Min = "0", + Max = "50000", + Command = "simfphyssuspensioneditor_constant_f" + }) + panel:AddControl( "Slider", + { + Label = "Front Damping", + Type = "Float", + Min = "0", + Max = "5000", + Command = "simfphyssuspensioneditor_damping_f" + }) + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "--- Rear ---" } ) + panel:AddControl( "Slider", + { + Label = "Rear Height", + Type = "Float", + Min = "-1", + Max = "1", + Command = "simfphyssuspensioneditor_height_r" + }) + panel:AddControl( "Slider", + { + Label = "Rear Constant", + Type = "Float", + Min = "0", + Max = "50000", + Command = "simfphyssuspensioneditor_constant_r" + }) + panel:AddControl( "Slider", + { + Label = "Rear Damping", + Type = "Float", + Min = "0", + Max = "5000", + Command = "simfphyssuspensioneditor_damping_r" + }) +end diff --git a/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyswheeleditor.lua b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyswheeleditor.lua new file mode 100644 index 0000000..e612656 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/gmod_tool/stools/simfphyswheeleditor.lua @@ -0,0 +1,523 @@ + +TOOL.Category = "simfphys" +TOOL.Name = "#Wheel Model Editor" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "frontwheelmodel" ] = "models/props_vehicles/apc_tire001.mdl" +TOOL.ClientConVar[ "rearwheelmodel" ] = "models/props_vehicles/apc_tire001.mdl" +TOOL.ClientConVar[ "sameasfront" ] = 1 +TOOL.ClientConVar[ "camber" ] = 0 +TOOL.ClientConVar[ "offsetfront" ] = 0 +TOOL.ClientConVar[ "offsetrear" ] = 0 + +if CLIENT then + language.Add( "tool.simfphyswheeleditor.name", "Wheel Model Editor" ) + language.Add( "tool.simfphyswheeleditor.desc", "Changes the wheels for simfphys vehicles with CustomWheels Enabled" ) + language.Add( "tool.simfphyswheeleditor.0", "Left click apply wheel model. Reload to reset" ) + language.Add( "tool.simfphyswheeleditor.1", "Left click apply wheel model. Reload to reset" ) +end + +local function GetRight( ent, index, WheelPos ) + local Steer = ent:GetTransformedDirection() + + local Right = ent.Right + + if WheelPos.IsFrontWheel then + Right = (IsValid( ent.SteerMaster ) and Steer.Right or ent.Right) * (WheelPos.IsRightWheel and 1 or -1) + else + Right = (IsValid( ent.SteerMaster ) and Steer.Right2 or ent.Right) * (WheelPos.IsRightWheel and 1 or -1) + end + + return Right +end + +local function SetWheelOffset( ent, offset_front, offset_rear ) + if not IsValid( ent ) then return end + + ent.WheelTool_Foffset = offset_front + ent.WheelTool_Roffset = offset_rear + + if not istable( ent.Wheels ) or not istable( ent.GhostWheels ) then return end + + for i = 1, table.Count( ent.GhostWheels ) do + local Wheel = ent.Wheels[ i ] + local WheelModel = ent.GhostWheels[i] + local WheelPos = ent:LogicWheelPos( i ) + + if IsValid( Wheel ) and IsValid( WheelModel ) then + local Pos = Wheel:GetPos() + local Right = GetRight( ent, i, WheelPos ) + local offset = WheelPos.IsFrontWheel and offset_front or offset_rear + + WheelModel:SetParent( nil ) + + local physObj = WheelModel:GetPhysicsObject() + if IsValid( physObj ) then + physObj:EnableMotion( false ) + end + + WheelModel:SetPos( Pos + Right * offset ) + WheelModel:SetParent( Wheel ) + end + end +end + +local function ApplyWheel(ply, ent, data) + + ent.CustomWheelAngleOffset = data[2] + ent.CustomWheelAngleOffset_R = data[4] + + timer.Simple( 0.05, function() + if not IsValid( ent ) then return end + + for i = 1, table.Count( ent.GhostWheels ) do + local Wheel = ent.GhostWheels[i] + + if IsValid( Wheel ) then + local isfrontwheel = (i == 1 or i == 2) + local swap_y = (i == 2 or i == 4 or i == 6) + + local angleoffset = isfrontwheel and ent.CustomWheelAngleOffset or ent.CustomWheelAngleOffset_R + + local model = isfrontwheel and data[1] or data[3] + + local fAng = ent:LocalToWorldAngles( ent.VehicleData.LocalAngForward ) + local rAng = ent:LocalToWorldAngles( ent.VehicleData.LocalAngRight ) + + local Forward = fAng:Forward() + local Right = swap_y and -rAng:Forward() or rAng:Forward() + local Up = ent:GetUp() + + local Camber = data[5] or 0 + + local ghostAng = Right:Angle() + local mirAng = swap_y and 1 or -1 + ghostAng:RotateAroundAxis(Forward,angleoffset.p * mirAng) + ghostAng:RotateAroundAxis(Right,angleoffset.r * mirAng) + ghostAng:RotateAroundAxis(Up,-angleoffset.y) + + ghostAng:RotateAroundAxis(Forward, Camber * mirAng) + + Wheel:SetModelScale( 1 ) + Wheel:SetModel( model ) + Wheel:SetAngles( ghostAng ) + + timer.Simple( 0.05, function() + if not IsValid( Wheel ) or not IsValid( ent ) then return end + local wheelsize = Wheel:OBBMaxs() - Wheel:OBBMins() + local radius = isfrontwheel and ent.FrontWheelRadius or ent.RearWheelRadius + local size = (radius * 2) / math.max(wheelsize.x,wheelsize.y,wheelsize.z) + + Wheel:SetModelScale( size ) + end) + end + end + end) +end + +local function ValidateModel( model ) + local v_list = list.Get( "simfphys_vehicles" ) + for listname, _ in pairs( v_list ) do + if v_list[listname].Members.CustomWheels then + local FrontWheel = v_list[listname].Members.CustomWheelModel + local RearWheel = v_list[listname].Members.CustomWheelModel_R + + if FrontWheel then + FrontWheel = string.lower( FrontWheel ) + end + + if RearWheel then + RearWheel = string.lower( RearWheel ) + end + + if model == FrontWheel or model == RearWheel then + return true + end + end + end + + local list = list.Get( "simfphys_Wheels" )[model] + + if list then + return true + end + + return false +end + +local function GetAngleFromSpawnlist( model ) + if not model then print("invalid model") return Angle(0,0,0) end + + model = string.lower( model ) + + local v_list = list.Get( "simfphys_vehicles" ) + for listname, _ in pairs( v_list ) do + if v_list[listname].Members.CustomWheels then + local FrontWheel = v_list[listname].Members.CustomWheelModel + local RearWheel = v_list[listname].Members.CustomWheelModel_R + + if FrontWheel then + FrontWheel = string.lower( FrontWheel ) + end + + if RearWheel then + RearWheel = string.lower( RearWheel ) + end + + if model == FrontWheel or model == RearWheel then + local Angleoffset = v_list[listname].Members.CustomWheelAngleOffset + if Angleoffset then + return Angleoffset + end + end + end + end + + local list = list.Get( "simfphys_Wheels" )[model] + local output = list and list.Angle or Angle(0,0,0) + + return output +end + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + if not simfphys.IsCar( ent ) then return false end + + if not SERVER then return true end + + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local PhysObj = ent:GetPhysicsObject() + if not IsValid( PhysObj ) then return end + + local freezeWhenDone = PhysObj:IsMotionEnabled() + local freezeWheels = {} + PhysObj:EnableMotion( false ) + ent:SetNotSolid( true ) + + local ResetPos = ent:GetPos() + local ResetAng = ent:GetAngles() + + ent:SetPos( ResetPos + Vector(0,0,30) ) + ent:SetAngles( Angle(0,ResetAng.y,0) ) + + for i = 1, table.Count( ent.Wheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid( Wheel ) then + local wPObj = Wheel:GetPhysicsObject() + + if IsValid( wPObj ) then + freezeWheels[ i ] = {} + freezeWheels[ i ].dofreeze = wPObj:IsMotionEnabled() + freezeWheels[ i ].pos = Wheel:GetPos() + freezeWheels[ i ].ang = Wheel:GetAngles() + Wheel:SetNotSolid( true ) + wPObj:EnableMotion( true ) + wPObj:Wake() + end + end + end + + timer.Simple( 0.25, function() + if not IsValid( ent ) then return end + + local front_model = self:GetClientInfo("frontwheelmodel") + local front_angle = GetAngleFromSpawnlist(front_model) + + local sameasfront = self:GetClientInfo("sameasfront") == "1" + local camber = self:GetClientInfo("camber") + + local rear_model = sameasfront and front_model or self:GetClientInfo("rearwheelmodel") + local rear_angle = GetAngleFromSpawnlist(rear_model) + + local front_offset = self:GetClientInfo("offsetfront") + local rear_offset = self:GetClientInfo("offsetrear") + + if not front_model or not rear_model or not front_angle or not rear_angle then print("wtf bro how did you do this") return false end + + if not ValidateModel( front_model ) or not ValidateModel( rear_model ) then + local ply = self:GetOwner() + ply:PrintMessage( HUD_PRINTTALK, "selected wheel does not exist on the server") + return false + end + + if ent.CustomWheels then + if ent.GhostWheels then + ent:SteerVehicle( 0 ) + + for i = 1, table.Count( ent.Wheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid( Wheel ) then + local physobj = Wheel:GetPhysicsObject() + physobj:EnableMotion( true ) + physobj:Wake() + end + end + + ent.Camber = camber + ApplyWheel(self:GetOwner(), ent, {front_model,front_angle,rear_model,rear_angle,camber}) + SetWheelOffset( ent, front_offset, rear_offset ) + end + end + + timer.Simple( 0.25, function() + if not IsValid( ent ) then return end + if not IsValid( PhysObj ) then return end + + PhysObj:EnableMotion( freezeWhenDone ) + ent:SetNotSolid( false ) + ent:SetPos( ResetPos ) + ent:SetAngles( ResetAng ) + + for i = 1, table.Count( freezeWheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid( Wheel ) then + local wPObj = Wheel:GetPhysicsObject() + + Wheel:SetNotSolid( false ) + + if IsValid( wPObj ) then + wPObj:EnableMotion( freezeWheels[i].dofreeze ) + end + + Wheel:SetPos( freezeWheels[ i ].pos ) + Wheel:SetAngles( freezeWheels[ i ].ang ) + end + end + end) + end) + + return true +end + +function TOOL:RightClick( trace ) + return false +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + local ply = self:GetOwner() + + if not simfphys.IsCar( ent ) then return false end + + if not SERVER then return true end + + if not ply:query('DBG: Изменять автомобили') then return ply:Notify('warning', 'Нет доступа') end + if ent:GetNetVar('cd.id') then return ply:Notify('warning', 'Нельзя изменять купленные авто') end + + local PhysObj = ent:GetPhysicsObject() + if not IsValid( PhysObj ) then return end + + local freezeWhenDone = PhysObj:IsMotionEnabled() + local freezeWheels = {} + PhysObj:EnableMotion( false ) + ent:SetNotSolid( true ) + + local ResetPos = ent:GetPos() + local ResetAng = ent:GetAngles() + + ent:SetPos( ResetPos + Vector(0,0,30) ) + ent:SetAngles( Angle(0,ResetAng.y,0) ) + + for i = 1, table.Count( ent.Wheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid( Wheel ) then + local wPObj = Wheel:GetPhysicsObject() + + if IsValid( wPObj ) then + freezeWheels[ i ] = {} + freezeWheels[ i ].dofreeze = wPObj:IsMotionEnabled() + freezeWheels[ i ].pos = Wheel:GetPos() + freezeWheels[ i ].ang = Wheel:GetAngles() + Wheel:SetNotSolid( true ) + wPObj:EnableMotion( true ) + wPObj:Wake() + end + end + end + + timer.Simple( 0.25, function() + if not IsValid( ent ) then return end + + local vname = ent:GetSpawn_List() + local VehicleList = list.Get( "simfphys_vehicles" )[vname] + + if ent.CustomWheels then + if ent.GhostWheels then + ent:SteerVehicle( 0 ) + + for i = 1, table.Count( ent.Wheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid( Wheel ) then + local physobj = Wheel:GetPhysicsObject() + physobj:EnableMotion( true ) + physobj:Wake() + end + end + + local front_model = VehicleList.Members.CustomWheelModel + local front_angle = VehicleList.Members.CustomWheelAngleOffset + local rear_model = VehicleList.Members.CustomWheelModel_R and VehicleList.Members.CustomWheelModel_R or front_model + local rear_angle = VehicleList.Members.CustomWheelAngleOffset + + ApplyWheel(self:GetOwner(), ent, {front_model,front_angle,rear_model,rear_angle}) + SetWheelOffset( ent, 0, 0 ) + end + end + + timer.Simple( 0.25, function() + if not IsValid( ent ) then return end + if not IsValid( PhysObj ) then return end + + PhysObj:EnableMotion( freezeWhenDone ) + ent:SetNotSolid( false ) + ent:SetPos( ResetPos ) + ent:SetAngles( ResetAng ) + + for i = 1, table.Count( freezeWheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid( Wheel ) then + local wPObj = Wheel:GetPhysicsObject() + + Wheel:SetNotSolid( false ) + + if IsValid( wPObj ) then + wPObj:EnableMotion( freezeWheels[i].dofreeze ) + end + + Wheel:SetPos( freezeWheels[ i ].pos ) + Wheel:SetAngles( freezeWheels[ i ].ang ) + end + end + end) + end) + + return true +end + +local ConVarsDefault = TOOL:BuildConVarList() +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.simfphyswheeleditor.name", Description = "#tool.simfphyswheeleditor.desc" } ) + panel:AddControl( "ComboBox", { MenuButton = 1, Folder = "wheeleditor", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + panel:AddControl( "Slider", + { + Label = "Camber", + Type = "Float", + Min = "-60", + Max = "60", + Command = "simfphyswheeleditor_camber" + }) + panel:AddControl( "Slider", + { + Label = "Offset (Front)", + Type = "Float", + Min = "-50", + Max = "50", + Command = "simfphyswheeleditor_offsetfront" + }) + panel:AddControl( "Slider", + { + Label = "Offset (Rear)", + Type = "Float", + Min = "-50", + Max = "50", + Command = "simfphyswheeleditor_offsetrear" + }) + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "Front Wheel Model" } ) + panel:AddControl( "PropSelect", { Label = "", ConVar = "simfphyswheeleditor_frontwheelmodel", Height = 0, Models = list.Get( "simfphys_Wheels" ) } ) + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "Rear Wheel Model" } ) + panel:AddControl( "Checkbox", + { + Label = "same as front", + Command = "simfphyswheeleditor_sameasfront", + }) + panel:AddControl( "PropSelect", { Label = "", ConVar = "simfphyswheeleditor_rearwheelmodel", Height = 0, Models = list.Get( "simfphys_Wheels" ) } ) + +end + +list.Set( "simfphys_Wheels", "models/props_phx/wheels/magnetic_large.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/smallwheel.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/normal_tire.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/breakable_tire.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/drugster_front.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/drugster_back.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/moped_tire.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/trucktire.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/trucktire2.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/747wheel.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/props_phx/wheels/monster_truck.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/bmw.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_2.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/rim_1.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_extruded_48.mdl", {Angle = Angle(-90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_race.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/tractors.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_rounded_36.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_rounded_36s.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_rugged_24.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_rugged_24w.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_spike_24.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/mechanics/wheels/wheel_spike_48.mdl", {Angle = Angle(90,0,0)} ) +list.Set( "simfphys_Wheels", "models/xeon133/racewheel/race-wheel-30.mdl", {Angle = Angle(0,-90,0)} ) +list.Set( "simfphys_Wheels", "models/xeon133/racewheelskinny/race-wheel-30_s.mdl", {Angle = Angle(0,-90,0)} ) +list.Set( "simfphys_Wheels", "models/NatesWheel/nateswheel.mdl", {Angle = Angle(0,180,0)} ) +list.Set( "simfphys_Wheels", "models/XQM/airplanewheel1medium.mdl", {Angle = Angle(0,180,0)} ) +list.Set( "simfphys_Wheels", "models/xeon133/offroad/off-road-30.mdl", {Angle = Angle(0,180,0)} ) + +local LW_Wheels = { + "models/lonewolfie/wheels/wheel_2015chargerpolice.mdl", + "models/lonewolfie/wheels/wheel_ametorqthrust.mdl", + "models/lonewolfie/wheels/wheel_ametorqthrust_eaglest.mdl", + "models/lonewolfie/wheels/wheel_com_rt_gold.mdl", + "models/lonewolfie/wheels/wheel_com_th2.mdl", + "models/lonewolfie/wheels/wheel_cucv_big.mdl", + "models/lonewolfie/wheels/wheel_cucv_small.mdl", + "models/lonewolfie/wheels/wheel_fiat595.mdl", + "models/lonewolfie/wheels/wheel_ham_edition_race.mdl", + "models/lonewolfie/wheels/wheel_impalapolice.mdl", + "models/lonewolfie/wheels/wheel_laxaniltc701.mdl", + "models/lonewolfie/wheels/wheel_miktometdrag_big.mdl", + "models/lonewolfie/wheels/wheel_miktometdrag_highprofile.mdl", + "models/lonewolfie/wheels/wheel_miktometdrag_lowprofile.mdl", + "models/lonewolfie/wheels/wheel_monstertruck.mdl", + "models/lonewolfie/wheels/wheel_oet_type_rxx.mdl", + "models/lonewolfie/wheels/wheel_speedline22b.mdl", + "models/lonewolfie/wheels/wheel_suvpolice.mdl", + "models/lonewolfie/wheels/wheel_volkte37.mdl", + "models/lonewolfie/wheels/wheel_wed_sa_97.mdl", + "models/lonewolfie/wheels/wheel_welddrag.mdl", + "models/lonewolfie/wheels/wheel_welddrag_big.mdl" +} + +for i = 1, table.Count( LW_Wheels ) do + if file.Exists( LW_Wheels[i], "GAME" ) then + list.Set( "simfphys_Wheels", LW_Wheels[i], {Angle = Angle(0,90,0)} ) + end +end + +timer.Simple( 0.1, function() + local v_list = list.Get( "simfphys_vehicles" ) + for listname, _ in pairs( v_list ) do + if (v_list[listname].Members.CustomWheels) then + local FrontWheel = v_list[listname].Members.CustomWheelModel + local RearWheel = v_list[listname].Members.CustomWheelModel_R + if (FrontWheel) then + if (file.Exists( FrontWheel, "GAME" )) then + list.Set( "simfphys_Wheels", FrontWheel, {} ) + end + end + if (RearWheel) then + if (file.Exists( RearWheel, "GAME" )) then + list.Set( "simfphys_Wheels", RearWheel, {} ) + end + end + end + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-cars/lua/weapons/weapon_simfillerpistol.lua b/garrysmod/addons/feature-cars/lua/weapons/weapon_simfillerpistol.lua new file mode 100644 index 0000000..33ec954 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/weapon_simfillerpistol.lua @@ -0,0 +1,394 @@ +AddCSLuaFile() + +SWEP.Category = "simfphys" +SWEP.Spawnable = false +SWEP.AdminSpawnable = false +SWEP.ViewModel = "models/weapons/v_slam.mdl" +SWEP.WorldModel = "models/props_equipment/gas_pump_p13.mdl" +SWEP.UseHands = false +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 10 +SWEP.Weight = 42 +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true +SWEP.HoldType = "slam" + +SWEP.RefilAmount = 0.95 +SWEP.MaxDistance = 120 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +local inv_time = 0 + +function SWEP:SetupDataTables() + self:NetworkVar( "Int",0, "FuelType" ) +end + +function SWEP:IsAimingAt( vpos ) + if not IsValid( self.Owner ) then return false end + local dir = self.Owner:GetAimVector() + local pos = self.Owner:GetShootPos() + local des_dir = (vpos - pos):GetNormalized() + + local dist = (pos - vpos):Length() + local ang = math.acos( math.Clamp( dir:Dot(des_dir) ,-1,1) ) * (180 / math.pi) + + local IsAimingAt = ang < 5 and dist < self.MaxDistance + + return IsAimingAt +end + +if SERVER then + util.AddNetworkString( "simfphys_gasspill" ) + + net.Receive( "simfphys_gasspill", function( length, ply ) + local Pos = net.ReadVector() + local Dir = net.ReadVector() + + if not ply:HasWeapon('weapon_simfillerpistol') then + ply:addExploitAttempt(0.25) + return + end + + timer.Simple(0.2, function() + util.Decal("beersplash", Pos - Dir, Pos + Dir) + + for i = 0,8 do + local sc = math.Rand(0.1,0.15) + local effectdata = EffectData() + effectdata:SetOrigin( Pos ) + --effectdata:SetAngles( angle ) + effectdata:SetNormal( Dir * 2 ) + effectdata:SetMagnitude( sc ) + effectdata:SetScale( sc ) + effectdata:SetRadius( sc ) + util.Effect( "StriderBlood", effectdata ) + end + + sound.Play( Sound( "ambient/water/water_spray"..math.random(1,3)..".wav" ), Pos, 55) + end) + end ) +end + +if CLIENT then + SWEP.PrintName = "Fuel filler pistol" + SWEP.Slot = 1 + SWEP.SlotPos = 12 + SWEP.IconLetter = "k" + + --SWEP.WepSelectIcon = surface.GetTextureID( "weapons/s_repair" ) + SWEP.DrawWeaponInfoBox = false + + function SWEP:OnRemove() + if IsValid(self.pViewModel) then + self.pViewModel:Remove() + end + end + + function SWEP:CreateModel() + if self.creatingModel or IsValid(self.pViewModel) then return end + self.creatingModel = true + timer.Simple(0, function() + if not IsValid(self) then return end + self.pViewModel = octolib.createDummy("models/props_equipment/gas_pump_p13.mdl", RENDERGROUP_OPAQUE) + self.pViewModel:SetNoDraw(true) + self.creatingModel = nil + end) + end + + function SWEP:ViewModelDrawn() + if IsValid( self.Owner ) then + + local ZOOM = self.Owner:KeyDown( IN_ZOOM ) + + self.ViewModelFOV = ZOOM and 30 or 10 + + if ZOOM then return end + + local vm = self.Owner:GetViewModel() + local bm = vm:GetBoneMatrix(0) + local pos = bm:GetTranslation() + local ang = bm:GetAngles() + + pos = pos + ang:Up() * 220 + pos = pos + ang:Right() * 2 + pos = pos + ang:Forward() * -12 + + ang:RotateAroundAxis(ang:Forward(), -85) + ang:RotateAroundAxis(ang:Right(), -20) + ang:RotateAroundAxis(ang:Up(), -70) + + if not IsValid(self.pViewModel) then + return self:CreateModel() + end + + self.pViewModel:SetPos(pos) + self.pViewModel:SetAngles(ang) + self.pViewModel:DrawModel() + end + end + + function SWEP:DrawWorldModel() + if not IsValid( self.Owner ) then return end + + local id = self.Owner:LookupAttachment("anim_attachment_rh") + local attachment = self.Owner:GetAttachment( id ) + + if not attachment then return end + + local pos = attachment.Pos + attachment.Ang:Forward() * 6 + attachment.Ang:Right() * -1.5 + attachment.Ang:Up() * 2.2 + local ang = attachment.Ang + ang:RotateAroundAxis(attachment.Ang:Up(), 20) + ang:RotateAroundAxis(attachment.Ang:Right(), -30) + ang:RotateAroundAxis(attachment.Ang:Forward(), 0) + + self:SetRenderOrigin( pos ) + self:SetRenderAngles( ang ) + + self:DrawModel() + end + + + local fuelposmat = Material( "sprites/fuelfiller_icon" ) + local wrong_type = Material( "conquest/aim_friendly" ) + + function SWEP:DrawHUD() + if LocalPlayer():InVehicle() then return end + + local screenw = ScrW() + local screenh = ScrH() + local Widescreen = (screenw / screenh) > (4 / 3) + local sizex = screenw * (Widescreen and 1 or 1.32) + local sizey = screenh + local xpos = sizex * 0.02 + local ypos = sizey * 0.85 + local dia = screenw * 0.025 + local radius = dia * 0.5 + + local Trace = self.Owner:GetEyeTrace() + local ent = Trace.Entity + + surface.SetDrawColor( 255, 0, 0,255 * math.Clamp(inv_time + 0.5 - CurTime(),0,1) ) + surface.SetMaterial( wrong_type ) + surface.DrawTexturedRect( screenw * 0.5 - radius, screenh * 0.5 - radius, dia, dia ) + + surface.SetDrawColor( 0, 0, 0, 80 ) + surface.DrawRect( xpos, ypos, sizex * 0.118, sizey * 0.02 ) + + if not IsValid( ent ) then + draw.SimpleText( "0 / 0", "simfphysfont", xpos + sizex * 0.059, ypos + sizey * 0.01, Color( 238,238,238, 255 ), 1, 1 ) + return + end + + if simfphys.IsCar( ent ) then + local fuelpos = ent:GetFuelPos() + local IsAimingAt = self:IsAimingAt( fuelpos ) + local col = IsAimingAt and Color(0,255,0,50) or Color(255,255,255,255) + local size = IsAimingAt and 4 or 8 + cam.Start3D() + render.SetMaterial( fuelposmat ) + render.DrawSprite( fuelpos, size, size, col ) + cam.End3D() + + local MaxFuel = math.Round( ent:GetMaxFuel() , 1) + local Fuel = math.Round( ent:GetFuel() , 1 ) + local fueltype = ent:GetFuelType() + + if fueltype == FUELTYPE_ELECTRIC then + MaxFuel = MaxFuel * 0.5 + Fuel = Fuel * 0.5 + end + + local fueltype_color = Color(102,170,170,150) + if fueltype == 1 then + fueltype_color = Color(102,170,170,150) + elseif fueltype == 2 then + fueltype_color = Color(170,119,102,150) + end + + surface.SetDrawColor( fueltype_color.r, fueltype_color.g, fueltype_color.b, fueltype_color.a ) + surface.DrawRect( xpos, ypos, ((sizex * 0.118) / MaxFuel) * Fuel, sizey * 0.02, fueltype_color ) + + draw.SimpleText( Fuel.." / "..MaxFuel, "simfphysfont", xpos + sizex * 0.059, ypos + sizey * 0.01, Color( 238, 238, 238, 255 ), 1, 1 ) + else + draw.SimpleText( "0 / 0", "simfphysfont", xpos + sizex * 0.059, ypos + sizey * 0.01, Color( 238, 238, 238, 255 ), 1, 1 ) + end + end +end + +function SWEP:Initialize() + self.Weapon:SetHoldType( self.HoldType ) + if IsValid( self.Owner ) then + self.Owner.usedFuel = 0 + end +end + +function SWEP:OwnerChanged() +end + +function SWEP:Think() + if CLIENT then + self.nextThink = self.nextThink or 0 + + if self.nextThink > CurTime() then return end + + self.nextThink = CurTime() + 0.02 + + if not IsValid( self.Owner ) then return end + if not self.Owner:KeyDown( IN_ATTACK ) then return end + if self:GetNetVar('NoFuel') then return end + + local Trace = self.Owner:GetEyeTrace() + local ent = Trace.Entity + local InRange = (Trace.HitPos - self.Owner:GetPos()):Length() < self.MaxDistance + local HIT = IsValid( ent ) and simfphys.IsCar( ent ) and InRange + + if HIT then + if self:GetFuelType() ~= ent:GetFuelType() then inv_time = CurTime() return end + end + + if self:GetFuelType() == FUELTYPE_ELECTRIC then return end + + local id = self.Owner:LookupAttachment("anim_attachment_rh") + local attachment = self.Owner:GetAttachment( id ) + + if not attachment then return end + + local Pos = (attachment.Pos + attachment.Ang:Forward() * 14 + attachment.Ang:Right() * -5 + attachment.Ang:Up() * 7 ) + + local FirstPerson = self.Owner == LocalPlayer() and self.Owner:GetViewEntity() == self.Owner + if FirstPerson then + Pos = self.Owner:GetShootPos() + self.Owner:EyeAngles():Forward() * 10 + self.Owner:EyeAngles():Right() * 3 - self.Owner:EyeAngles():Up() * 2 + end + + + local emitter = ParticleEmitter( Pos, false ) + local particle = emitter:Add( "effects/slime1", Pos ) + + if not HIT and InRange or (IsValid( ent ) and simfphys.IsCar( ent ) and ent:GetFuel() >= ent:GetMaxFuel()) or (IsValid( ent ) and simfphys.IsCar( ent ) and not self:IsAimingAt( ent:GetFuelPos() ) ) then + self.NextSplash = self.NextSplash or 0 + if self.NextSplash < CurTime() then + self.NextSplash = CurTime() + math.Rand(0.05,0.2) + + net.Start( "simfphys_gasspill" ) + net.WriteVector( Trace.HitPos ) + net.WriteVector( Trace.HitNormal ) + net.SendToServer() + end + end + + if particle then + local dir = attachment.Ang + dir:RotateAroundAxis(attachment.Ang:Up(), 20) + + if FirstPerson then + dir = self.Owner:EyeAngles() + Angle(-15,10,0) + end + + particle:SetVelocity( dir:Forward() * 320 ) + particle:SetDieTime( HIT and 0.1 or 0.3 ) + particle:SetAirResistance( 400 ) + particle:SetStartAlpha( 100 ) + particle:SetStartSize( 1 ) + particle:SetEndSize( HIT and 1 or 2 ) + particle:SetRoll( math.Rand( -1, 1 ) ) + particle:SetColor( 240,200,0,255 ) + particle:SetGravity( Vector( 0, 0, -600 ) ) + particle:SetCollide( true ) + end + emitter:Finish() + end +end + +function SWEP:CanPrimaryAttack() + self.NextFire = self.NextFire or 0 + + if self.NextFire > CurTime() then + return false + end + + return true +end + +function SWEP:SetNextPrimaryFire( time ) + self.NextFire = time +end + +function SWEP:PrimaryAttack() + if not self:CanPrimaryAttack() then return end + + local Trace = self.Owner:GetEyeTrace() + local ent = Trace.Entity + local InRange = (Trace.HitPos - self.Owner:GetPos()):Length() < self.MaxDistance + local HIT = IsValid( ent ) and simfphys.IsCar( ent ) and InRange + + if SERVER then + self.Owner.usedFuel = self.Owner.usedFuel or 0 + if self.Owner.usedFuel >= self.Owner.usedFuelMax then + self:SetNetVar('NoFuel', true) + return + end + + local fuelUse = math.min(self.RefilAmount, self.Owner.usedFuelMax - self.Owner.usedFuel) + + if HIT then + local Fuel = ent:GetFuel() + local MaxFuel = ent:GetMaxFuel() + + if self:IsAimingAt( ent:GetFuelPos() ) and Fuel < MaxFuel then + timer.Simple(0.2, function() + if not IsValid( ent ) or not IsValid( self ) or not IsValid( self.Owner ) then return end + if self:GetFuelType() ~= ent:GetFuelType() then return end + + local newFuel = Fuel + fuelUse + ent:SetFuel(newFuel) + ent:SetNetVar('Fuel', newFuel) + + timer.Simple(0.2, function() + if self:GetFuelType() == FUELTYPE_ELECTRIC then + sound.Play( Sound( "items/battery_pickup.wav" ), Trace.HitPos, 50) + + local effectdata = EffectData() + effectdata:SetOrigin( Trace.HitPos ) + effectdata:SetNormal( Trace.HitNormal * 3 ) + effectdata:SetMagnitude( 2 ) + effectdata:SetRadius( 8 ) + util.Effect( "Sparks", effectdata, true, true ) + + self.Owner.usedFuel = self.Owner.usedFuel + fuelUse + else + sound.Play( Sound( "vehicles/jetski/jetski_no_gas_start.wav" ), Trace.HitPos, 65) + end + end) + end) + end + end + + if self:GetFuelType() ~= FUELTYPE_ELECTRIC then + self.Owner.usedFuel = self.Owner.usedFuel + fuelUse + end + + end + self:SetNextPrimaryFire( CurTime() + 0.5 ) +end + +function SWEP:SecondaryAttack() + return false +end + +function SWEP:Deploy() + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + return true +end + +function SWEP:Holster() + return true +end diff --git a/garrysmod/addons/feature-cars/lua/weapons/weapon_simremote.lua b/garrysmod/addons/feature-cars/lua/weapons/weapon_simremote.lua new file mode 100644 index 0000000..ca073a2 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/weapon_simremote.lua @@ -0,0 +1,191 @@ +AddCSLuaFile() + +SWEP.Category = "simfphys" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.ViewModel = "models/weapons/c_pistol.mdl" +SWEP.WorldModel = "models/weapons/w_pistol.mdl" +SWEP.UseHands = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 53 +SWEP.Weight = 42 +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true +SWEP.HoldType = "pistol" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +function SWEP:SetupDataTables() + self:NetworkVar( "Entity",0, "Car" ) + self:NetworkVar( "Bool",0, "Active" ) +end + +if (CLIENT) then + SWEP.PrintName = "Remote Controller" + SWEP.Purpose = "remote controls simfphys vehicles" + SWEP.Instructions = "Left-Click on a simfphys car to link. Press the Use-Key to start remote controlling." + SWEP.Author = "Blu" + SWEP.Slot = 1 + SWEP.SlotPos = 10 + SWEP.HideFromHelp = true + + hook.Add( "PreDrawHalos", "s_remote_halos", function() + local ply = LocalPlayer() + local weapon = ply:GetActiveWeapon() + + if IsValid( ply ) and IsValid( weapon ) then + if ply:InVehicle() then return end + + if weapon:GetClass() == "weapon_simremote" then + if not weapon:GetActive() then + local car = weapon:GetCar() + + if IsValid( car ) then + halo.Add( {car}, Color( 0, 127, 255 ) ) + end + end + end + end + end ) + + + function SWEP:PrimaryAttack() + if self:GetActive() then return false end + + local trace = self.Owner:GetEyeTrace() + local ent = trace.Entity + + if not simfphys.IsCar( ent ) then return false end + + self.Weapon:EmitSound( "Weapon_Pistol.Empty" ) + + return true + end + + function SWEP:SecondaryAttack() + if self:GetActive() then return false end + + self.Weapon:EmitSound( "Weapon_Pistol.Empty" ) + + return true + end + + return +end + +function SWEP:Initialize() + self.Weapon:SetHoldType( self.HoldType ) +end + +function SWEP:OwnerChanged() +end + +function SWEP:Think() + local ply = self.Owner + + if ply:KeyPressed( IN_USE ) then + if self:GetActive() or not IsValid( self:GetCar() ) then + self:Disable() + else + self:Enable() + end + end +end + +function SWEP:PrimaryAttack() + if self:GetActive() then return false end + + local ply = self.Owner + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not simfphys.IsCar( ent ) then return false end + + self:SetCar( ent ) + + ply:ChatPrint("Remote Controller linked.") + + return true +end + +function SWEP:SecondaryAttack() + if self:GetActive() then return false end + + local ply = self.Owner + + if IsValid( self:GetCar() ) then + self:SetCar( NULL ) + ply:ChatPrint("Remote Controller unlinked.") + + return true + end + + return false +end + +function SWEP:Enable() + local ply = self.Owner + local car = self:GetCar() + + if IsValid( car ) then + if IsValid( car:GetDriver() ) then + ply:ChatPrint("vehicle is already in use") + else + self:SetActive( true ) + self.OldMoveType = ply:GetMoveType() + + ply:SetMoveType( MOVETYPE_NONE ) + ply:DrawViewModel( false ) + + car.RemoteDriver = ply + end + end +end + +function SWEP:Disable() + local ply = self.Owner + local car = self:GetCar() + + if self:GetActive() then + if self.OldMoveType then + ply:SetMoveType( self.OldMoveType ) + else + ply:SetMoveType( MOVETYPE_WALK ) + end + end + + self:SetActive( false ) + self.OldMoveType = nil + ply:DrawViewModel( true ) + + if IsValid( car ) then + car.RemoteDriver = nil + end +end + +function SWEP:Deploy() + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + return true +end + +function SWEP:Holster() + if IsValid( self:GetCar() ) then + self:Disable() + end + return true +end + +function SWEP:OnDrop() + if IsValid( self:GetCar() ) then + self:Disable() + self.TheCar = nil + end +end diff --git a/garrysmod/addons/feature-cars/lua/weapons/weapon_simrepair.lua b/garrysmod/addons/feature-cars/lua/weapons/weapon_simrepair.lua new file mode 100644 index 0000000..50ea478 --- /dev/null +++ b/garrysmod/addons/feature-cars/lua/weapons/weapon_simrepair.lua @@ -0,0 +1,196 @@ +AddCSLuaFile() + +SWEP.Category = "simfphys" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.ViewModel = "models/weapons/c_physcannon.mdl" +SWEP.WorldModel = "models/weapons/w_physics.mdl" +SWEP.UseHands = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 53 +SWEP.Weight = 42 +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true +SWEP.HoldType = "physgun" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +if (CLIENT) then + SWEP.PrintName = "Vehicle Repair Tool" + SWEP.Purpose = "repairs simfphys vehicles" + SWEP.Instructions = "Primary to repair" + SWEP.Author = "Blu" + SWEP.Slot = 1 + SWEP.SlotPos = 9 + SWEP.IconLetter = "k" + + SWEP.WepSelectIcon = surface.GetTextureID( "weapons/s_repair" ) + --SWEP.DrawWeaponInfoBox = false +end + +function SWEP:Initialize() + self.Weapon:SetHoldType( self.HoldType ) +end + +function SWEP:OwnerChanged() +end + +function SWEP:Think() +end + +function SWEP:PrimaryAttack() + self.Weapon:SetNextPrimaryFire( CurTime() + 0.1 ) + local Trace = self.Owner:GetEyeTrace() + local ent = Trace.Entity + + if !IsValid(ent) then return end + local class = ent:GetClass():lower() + + local IsVehicle = class == "gmod_sent_vehicle_fphysics_base" + local IsWheel = class == "gmod_sent_vehicle_fphysics_wheel" + + if IsVehicle then + local Dist = (Trace.HitPos - self.Owner:GetPos()):Length() + + if (Dist <= 100) then + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + if (SERVER) then + local MaxHealth = ent:GetMaxHealth() + local Health = ent:GetCurHealth() + + if Health < MaxHealth then + local NewHealth = math.min(Health + 30,MaxHealth) + + if NewHealth > (MaxHealth * 0.6) then + ent:SetOnFire( false ) + ent:SetOnSmoke( false ) + end + + if NewHealth > (MaxHealth * 0.3) then + ent:SetOnFire( false ) + if NewHealth <= (MaxHealth * 0.6) then + ent:SetOnSmoke( true ) + end + end + + ent:SetCurHealth( NewHealth ) + + local effect = ents.Create("env_spark") + effect:SetKeyValue("targetname", "target") + effect:SetPos( Trace.HitPos + Trace.HitNormal * 2 ) + effect:SetAngles( Trace.HitNormal:Angle() ) + effect:Spawn() + effect:SetKeyValue("spawnflags","128") + effect:SetKeyValue("Magnitude",1) + effect:SetKeyValue("TrailLength",0.2) + effect:Fire( "SparkOnce" ) + effect:Fire("kill","",0.08) + else + self.Weapon:SetNextPrimaryFire( CurTime() + 0.5 ) + + sound.Play(Sound( "hl1/fvox/beep.wav" ), self:GetPos(), 75) + + net.Start( "simfphys_lightsfixall" ) + net.WriteEntity( ent ) + net.Broadcast() + + if istable(ent.Wheels) then + for i = 1, table.Count( ent.Wheels ) do + local Wheel = ent.Wheels[ i ] + if IsValid(Wheel) then + Wheel:SetDamaged( false ) + end + end + end + end + end + end + elseif IsWheel then + local Dist = (Trace.HitPos - self.Owner:GetPos()):Length() + + if (Dist <= 100) then + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + if (SERVER) then + if ent:GetDamaged() then + + ent:SetDamaged( false ) + + local effect = ents.Create("env_spark") + effect:SetKeyValue("targetname", "target") + effect:SetPos( Trace.HitPos + Trace.HitNormal * 2 ) + effect:SetAngles( Trace.HitNormal:Angle() ) + effect:Spawn() + effect:SetKeyValue("spawnflags","128") + effect:SetKeyValue("Magnitude",1) + effect:SetKeyValue("TrailLength",0.2) + effect:Fire( "SparkOnce" ) + effect:Fire("kill","",0.08) + else + self.Weapon:SetNextPrimaryFire( CurTime() + 0.5 ) + + sound.Play(Sound( "hl1/fvox/beep.wav" ), self:GetPos(), 75) + end + end + end + end +end + +function SWEP:DrawHUD() + if (LocalPlayer():InVehicle()) then return end + + local screenw = ScrW() + local screenh = ScrH() + local Widescreen = (screenw / screenh) > (4 / 3) + local sizex = screenw * (Widescreen and 1 or 1.32) + local sizey = screenh + local xpos = sizex * 0.02 + local ypos = sizey * 0.85 + + local Trace = self.Owner:GetEyeTrace() + local ent = Trace.Entity + + draw.RoundedBox( 0, xpos, ypos, sizex * 0.118, sizey * 0.02, Color( 0, 0, 0, 80 ) ) + + if (!IsValid(ent)) then + draw.SimpleText( "0 / 0", "simfphysfont", xpos + sizex * 0.059, ypos + sizey * 0.01, Color( 255, 235, 0, 255 ), 1, 1 ) + return + end + + local IsVehicle = ent:GetClass():lower() == "gmod_sent_vehicle_fphysics_base" + + if (IsVehicle) then + local MaxHealth = ent:GetMaxHealth() + local Health = ent:GetCurHealth() + + draw.RoundedBox( 0, xpos, ypos, ((sizex * 0.118) / MaxHealth) * Health, sizey * 0.02, Color( (Health < MaxHealth * 0.6) and 255 or 0, (Health >= MaxHealth * 0.3) and 255 or 0, 0, 100 ) ) + + draw.SimpleText( Health.." / "..MaxHealth, "simfphysfont", xpos + sizex * 0.059, ypos + sizey * 0.01, Color( 255, 235, 0, 255 ), 1, 1 ) + else + draw.SimpleText( "0 / 0", "simfphysfont", xpos + sizex * 0.059, ypos + sizey * 0.01, Color( 255, 235, 0, 255 ), 1, 1 ) + end +end + +function SWEP:SecondaryAttack() + return false +end + +function SWEP:Deploy() + self.Weapon:SendWeaponAnim( ACT_VM_DRAW ) + return true +end + +function SWEP:Holster() + return true +end \ No newline at end of file diff --git a/garrysmod/addons/feature-certs/lua/autorun/server/certs.lua b/garrysmod/addons/feature-certs/lua/autorun/server/certs.lua new file mode 100644 index 0000000..9619fce --- /dev/null +++ b/garrysmod/addons/feature-certs/lua/autorun/server/certs.lua @@ -0,0 +1,70 @@ +dbgCerts = dbgCerts or {} +include('config/certificates.lua') + +function dbgCerts.give(ply, id, title, add, em, pos, iss, vthru) + ply:SetDBVar('cert', { + id = id, + titl = title, + add = add, + em = em, + pos = pos, + iss = iss, + vthru = vthru, + }) + ply:Notify('Тебе выдали ' .. title .. '! Чтобы показать его, в "круговом меню" взаимодействия с другим игроком выбери "Показать удостоверение."') + return true +end + +local nextShow = {} + +timer.Create('dbg-idcards.clear', 60, 0, function() + for shower,showeees in pairs(nextShow) do + for showeee,nextTime in pairs(showeees) do + if nextTime <= CurTime() then + nextShow[shower][showeee] = nil + end + end + if #showeees == 0 then nextShow[shower] = nil end + end +end) + +local function read(ply, cert) + octochat.talkTo(ply, octochat.textColors.rp, '====================') + octochat.talkTo(ply, octochat.textColors.rp, dbgCerts.certTitles[cert.id]) + if cert.add then + octochat.talkTo(ply, octochat.textColors.rp, cert.add) + end + if cert.em then + octochat.talkTo(ply, octochat.textColors.rp, 'Выдано ', cert.em) + end + if cert.pos then + octochat.talkTo(ply, octochat.textColors.rp, 'На должности ', cert.pos) + end + if cert.iss then + octochat.talkTo(ply, octochat.textColors.rp, 'Дата выдачи: ', os.date('%d.%m.%Y', cert.iss)) + end + octochat.talkTo(ply, octochat.textColors.rp, 'Действует до: ', os.date('%d.%m.%Y', cert.vthru)) + octochat.talkTo(ply, octochat.textColors.rp, '====================') +end + +function dbgCerts.show(shower, showee, noTimer, noEmote) + local cert = shower:GetDBVar('cert') + if not cert then return false, 'У тебя нет действительного удостоверения' end + if not dbgCerts.certTitles[cert.id] or cert.vthru < os.time() then + shower:SetDBVar('cert', nil) + return false, 'У тебя нет действительного удостоверения' + end + local sid, tid = shower:SteamID(), showee:SteamID() + if not noTimer then + if not nextShow[sid] then nextShow[sid] = {} end + if (nextShow[sid][tid] or CurTime()) > CurTime() then + return false, 'Этот игрок уже недавно видел твое удостоверение' + end + nextShow[sid][tid] = CurTime() + 120 + end + if not noEmote then shower:DoEmote('{name} показывает удостоверение') end + timer.Simple(1, function() + if IsValid(showee) then read(showee, cert) end + end) + return true +end diff --git a/garrysmod/addons/feature-citywork/lua/autorun/dbg-work.lua b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work.lua new file mode 100644 index 0000000..de47ab4 --- /dev/null +++ b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work.lua @@ -0,0 +1,5 @@ +octolib.server('dbg-work/init') +octolib.server('dbg-work/work_repair') +octolib.server('dbg-work/work_vend') +octolib.server('dbg-work/work_camera') +octolib.server('dbg-work/work_trash') diff --git a/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/init.lua b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/init.lua new file mode 100644 index 0000000..d87b153 --- /dev/null +++ b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/init.lua @@ -0,0 +1,103 @@ +dbgWork = dbgWork or {} + +local workInterval = {2 * 60, 10 * 60} +local workDuration = 10 * 60 +local maxDist = 10000 + +maxDist = maxDist * maxDist + +dbgWork.works = dbgWork.works or {} +function dbgWork.registerWork(id, data) + + dbgWork.works[id] = data + +end + +function dbgWork.isWorker(ply) + + if not IsValid(ply) or not ply:Alive() or ply:IsGhost() then return false end + + local job = ply:getJobTable() + return job and job.worker + +end + +function dbgWork.planWork(ply) + + if not dbgWork.isWorker(ply) then return end + + local time = math.random(unpack(workInterval)) + time = ply:Team() == TEAM_CSD and time * 0.5 or time + + timer.Remove('dbg-workers.cancel' .. ply:SteamID()) + timer.Create('dbg-workers.create' .. ply:SteamID(), time, 1, function() dbgWork.createWork(ply) end) + +end + +local activeWorks = {} +function dbgWork.createWork(ply) + + if not dbgWork.isWorker(ply) then return end + + local work, workID + for id, data in SortedPairsByMemberValue(dbgWork.works, 'priority', true) do + work = data.start(ply, workDuration, maxDist) + if work then + workID = id + break + end + end + + if not workID then + ply:Notify('warning', L.cant_find_job) + dbgWork.planWork(ply) + return + end + + work.workID = workID + work.worker = ply + activeWorks[ply] = work + + ply:Notify(L.you_have_job) + if work.msg then ply:Notify(work.msg) end + timer.Remove('dbg-workers.create' .. ply:SteamID()) + timer.Create('dbg-workers.cancel' .. ply:SteamID(), workDuration, 1, function() + if dbgWork.isWorker(ply) and activeWorks[ply] then + -- stop the work after timeout + ply:Notify('warning', L.late_for_job) + dbgWork.works[workID].cancel(activeWorks[ply]) + activeWorks[ply] = nil + + dbgWork.planWork(ply) + end + end) + +end + +function dbgWork.giveReward(work, min, max) + + local addMoney = work.worker:Team() == TEAM_CSD and math.Round(math.Rand(0.4, 0.45), 2) or 0 + local amount = work.salary or (math.random(min, max) * 10) + amount = math.Round(amount + amount * addMoney) + + work.worker:addMoney(amount) + work.worker:Notify(L.work_completed_hint:format(DarkRP.formatMoney(amount))) + +end + +function dbgWork.finishWork(ply) + + if not dbgWork.isWorker(ply) or not activeWorks[ply] then return end + + local work = activeWorks[ply] + if work.cancelOnFinish then dbgWork.works[work.workID].cancel(work) end + dbgWork.works[work.workID].finish(work) + activeWorks[ply] = nil + + dbgWork.planWork(ply) + +end + +hook.Add('dbg-char.spawn', 'dbg-workers', dbgWork.planWork) +hook.Add('simple-orgs.getEquip', 'dbg-workers', dbgWork.planWork) +hook.Add('simple-orgs.handOver', 'dbg-workers', dbgWork.planWork) \ No newline at end of file diff --git a/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_camera.lua b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_camera.lua new file mode 100644 index 0000000..4ec46c8 --- /dev/null +++ b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_camera.lua @@ -0,0 +1,89 @@ +local work = {} +work.priority = 100 + +function work.start(ply, time, maxDist) + + local camera, box + for i, v in RandomPairs(ents.FindByClass('ent_dbg_camera')) do + if v:GetNetVar('broken') and not v.pendingWorker and v:GetPos():DistToSqr(ply:GetPos()) < maxDist then + camera = v + break + end + end + if not IsValid(camera) then return end + + for i, v in RandomPairs(ents.FindByClass('ent_dbg_workerbox')) do + if v:GetPos():DistToSqr(camera:GetPos()) < maxDist then box = v break end + end + if not IsValid(box) then return end + + ply:AddMarker { + id = 'work1', + txt = L.take_details, + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'time', {time}}, + icon = 'octoteam/icons-16/setting_tools.png', + } + + ply:AddMarker { + id = 'work2', + txt = L.repair, + pos = camera:ScreenPos(), + col = Color(255,92,38), + des = {'time', {time}}, + icon = 'octoteam/icons-16/wrench_orange.png', + } + + local items = {} + if math.random(3) == 1 then table.insert(items, {'tool_wrench', 1}) end + if math.random(3) == 1 then table.insert(items, {'tool_solder', 1}) end + if math.random(3) == 1 then table.insert(items, {'tool_screwer', 1}) end + if #items < 1 then table.insert(items, {'tool_screwer', 1}) end + if math.random(3) == 1 then table.insert(items, {'craft_screwnut', 5}) end + if math.random(3) == 1 then table.insert(items, {'craft_screw2', 5}) end + if math.random(3) == 1 then table.insert(items, {'craft_resistor', 3}) end + if math.random(3) == 1 then table.insert(items, {'craft_relay', 2}) end + if math.random(3) == 1 then table.insert(items, {'craft_scotch', 1}) end + if math.random(3) == 1 then table.insert(items, {'craft_glue', 1}) end + if math.random(3) == 1 then table.insert(items, {'craft_bulb', 2}) end + + if box.inv.conts[ply:SteamID()] then box.inv.conts[ply:SteamID()]:Remove() end + local cont = box.inv.conts[ply:SteamID()] or box.inv:AddContainer(ply:SteamID(), {name = L.city_warehouse, volume = 250, icon = octolib.icons.color('box1')}) + for i, v in ipairs(items) do cont:AddItem(v[1], v[2]) end + + camera:SetWork(ply, { + items = items, + time = math.random(10, 40), + finish = function(ply) + camera:Repair() + dbgWork.finishWork(ply) + end, + }) + + return { + msg = 'Кто-то разбил камеру видеонаблюдения, возьми детали на складе и почини ее', + cancelOnFinish = true, + camera = camera, + cont = cont, + } + +end + +function work.finish(work) + + dbgWork.giveReward(work, 200, 250) + +end + +function work.cancel(work) + + if work.cont then work.cont:Remove() end + if IsValid(work.camera) then + work.camera.pendingWorker = nil + work.camera.finish = nil + end + +end + +dbgWork.registerWork('camera', work) diff --git a/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_repair.lua b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_repair.lua new file mode 100644 index 0000000..4db7e4f --- /dev/null +++ b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_repair.lua @@ -0,0 +1,79 @@ +local work = {} +work.priority = 10 + +function work.start(ply, time, maxDist) + + local problem, box + for i, v in RandomPairs(ents.FindByClass('ent_dbg_workproblem')) do + if not v.pendingWorker and v:GetPos():DistToSqr(ply:GetPos()) < maxDist then problem = v break end + end + if not IsValid(problem) then return end + + for i, v in RandomPairs(ents.FindByClass('ent_dbg_workerbox')) do + if v:GetPos():DistToSqr(problem:GetPos()) < maxDist then box = v break end + end + if not IsValid(box) then return end + + ply:AddMarker { + id = 'work1', + txt = L.take_details, + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'time', {time}}, + icon = 'octoteam/icons-16/setting_tools.png', + } + + ply:AddMarker { + id = 'work2', + txt = L.repair, + pos = problem:GetPos(), + col = Color(255,92,38), + des = {'time', {time}}, + icon = 'octoteam/icons-16/wrench_orange.png', + } + + local items = {} + if math.random(3) == 1 then table.insert(items, {'tool_wrench', 1}) end + if math.random(3) == 1 then table.insert(items, {'tool_solder', 1}) end + if math.random(3) == 1 then table.insert(items, {'tool_screwer', 1}) end + if #items < 1 then table.insert(items, {'tool_screwer', 1}) end + if math.random(3) == 1 then table.insert(items, {'craft_screwnut', 5}) end + if math.random(3) == 1 then table.insert(items, {'craft_screw2', 5}) end + if math.random(3) == 1 then table.insert(items, {'craft_resistor', 3}) end + if math.random(3) == 1 then table.insert(items, {'craft_relay', 2}) end + if math.random(3) == 1 then table.insert(items, {'craft_scotch', 1}) end + if math.random(3) == 1 then table.insert(items, {'craft_glue', 1}) end + if math.random(3) == 1 then table.insert(items, {'craft_bulb', 2}) end + + if box.inv.conts[ply:SteamID()] then box.inv.conts[ply:SteamID()]:Remove() end + local cont = box.inv.conts[ply:SteamID()] or box.inv:AddContainer(ply:SteamID(), {name = L.city_warehouse, volume = 250, icon = octolib.icons.color('box1')}) + for i, v in ipairs(items) do cont:AddItem(v[1], v[2]) end + + problem:SetWork(ply, { + items = items, + time = math.random(10, 40), + finish = dbgWork.finishWork, + }) + + return { + cancelOnFinish = true, + msg = L.worker_msg, + problem = problem, + } + +end + +function work.finish(work) + + dbgWork.giveReward(work, 50, 125) + +end + +function work.cancel(work) + + work.problem:UnsetWork() + if work.cont then work.cont:Remove() end + +end + +dbgWork.registerWork('repair', work) diff --git a/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_trash.lua b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_trash.lua new file mode 100644 index 0000000..4563d9b --- /dev/null +++ b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_trash.lua @@ -0,0 +1,68 @@ +local work = {} +work.priority = 25 + +function work.start(ply, time, maxDist) + + local maxVolume, trash, box = 0 + for _, v in ipairs(ents.FindByClass('ent_dbg_trash')) do + local volume = -v.innerCont:FreeSpace() + if not v.pendingWorker and volume > 75 and v:GetPos():DistToSqr(ply:GetPos()) < maxDist and volume > maxVolume then + maxVolume, trash = volume, v + end + end + if not IsValid(trash) then return end + + for _, v in RandomPairs(ents.FindByClass('ent_dbg_workerbox')) do + if v:GetPos():DistToSqr(trash:GetPos()) < maxDist then box = v break end + end + if not IsValid(box) then return end + + ply:AddMarker { + id = 'work1', + txt = 'Собрать мусор', + pos = trash:LocalToWorld(trash:OBBCenter()), + col = Color(255,92,38), + des = {'time', {time}}, + icon = octolib.icons.silk16('bin'), + } + + ply:AddMarker { + id = 'work2', + txt = 'Отнести на утилизацию', + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'time', {time}}, + icon = octolib.icons.silk16('recycle_bag'), + } + + trash.pendingWorker = ply + box.finishTrash = box.finishTrash or {} + box.finishTrash[ply:SteamID()] = dbgWork.finishWork + + return { + msg = 'Собери мусор в мусорке и отнеси его на утилизацию', + cancelOnFinish = true, + trash = trash, + salary = math.ceil(math.abs(trash.innerCont:FreeSpace()) * 10), + } + +end + +function work.finish(work) + + dbgWork.giveReward(work, 150, 250) + +end + +function work.cancel(work) + + if IsValid(work.trash) then + work.trash.pendingWorker = nil + end + if IsValid(work.box) then + work.box.finishTrash[work.worker:SteamID()] = nil + end + +end + +dbgWork.registerWork('trash', work) diff --git a/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_vend.lua b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_vend.lua new file mode 100644 index 0000000..d0801b8 --- /dev/null +++ b/garrysmod/addons/feature-citywork/lua/autorun/dbg-work/work_vend.lua @@ -0,0 +1,73 @@ +local work = {} +work.priority = 50 + +function work.start(ply, time, maxDist) + + local vend, box + for i, v in RandomPairs(ents.FindByClass('ent_dbg_vend')) do + if v.bottlesLeft < 6 and not v.pendingWorker and v:GetPos():DistToSqr(ply:GetPos()) < maxDist then vend = v break end + end + if not IsValid(vend) then return end + + for i, v in RandomPairs(ents.FindByClass('ent_dbg_workerbox')) do + if v:GetPos():DistToSqr(vend:GetPos()) < maxDist then box = v break end + end + if not IsValid(box) then return end + + ply:AddMarker { + id = 'work1', + txt = L.take_soda, + pos = box:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'time', {time}}, + icon = 'octoteam/icons-16/box.png', + } + + ply:AddMarker { + id = 'work2', + txt = L.load_in_soda_machine, + pos = vend:GetPos() + Vector(0,0,40), + col = Color(255,92,38), + des = {'time', {time}}, + icon = 'octoteam/icons-16/basket_put.png', + } + + local cont = box.inv.conts[ply:SteamID()] or box.inv:AddContainer(ply:SteamID(), {name = L.city_warehouse, volume = 250, icon = octolib.icons.color('box1')}) + cont:AddItem('souvenir', { + name = L.block_soda, + icon = 'octoteam/icons/box3.png', + model = 'models/Items/BoxMRounds.mdl', + volume = 15, + mass = 20, + }) + + vend.pendingWorker = ply + vend.finish = dbgWork.finishWork + + return { + msg = L.take_soda_hint, + cancelOnFinish = true, + vend = vend, + cont = cont, + oldUse = oldUse, + } + +end + +function work.finish(work) + + dbgWork.giveReward(work, 150, 250) + +end + +function work.cancel(work) + + if work.cont then work.cont:Remove() end + if IsValid(work.vend) then + work.vend.pendingWorker = nil + work.vend.finish = nil + end + +end + +dbgWork.registerWork('vend', work) diff --git a/garrysmod/addons/feature-crime/lua/autorun/_crime.lua b/garrysmod/addons/feature-crime/lua/autorun/_crime.lua new file mode 100644 index 0000000..85757e3 --- /dev/null +++ b/garrysmod/addons/feature-crime/lua/autorun/_crime.lua @@ -0,0 +1,5 @@ +hook.Add('octolib.loaded', 'crime', function() + hook.Remove('octolib.loaded', 'crime') + octolib.module('hack') + octolib.module('lockpick') +end) diff --git a/garrysmod/addons/feature-crime/lua/hack/client.lua b/garrysmod/addons/feature-crime/lua/hack/client.lua new file mode 100644 index 0000000..941a45c --- /dev/null +++ b/garrysmod/addons/feature-crime/lua/hack/client.lua @@ -0,0 +1,133 @@ +surface.CreateFont('hacker', { + font = 'Consolas', + size = 16, + weight = 700, + antialias = true, + extended = true, +}) +local color_hacker = Color(32, 194, 14) +local introText = 'Keypad Cracker v1.0.1\n(c) ' .. os.date('%Y', os.time()) .. ' Octothorp Team. All rights reserved.\n\nConnecting to keypad' + +local terminal, log + +local function print(txt, append) + if not IsValid(log) then return netstream.Start('dbg-hack.cancel') end + if not append then log:AppendText('\n') end + log:AppendText(txt or '') +end + +local function lock(locked) + if not IsValid(log) then return netstream.Start('dbg-hack.cancel') end + log:LockInput(locked) +end + +local function reqInput() + if not IsValid(log) then return netstream.Start('dbg-hack.cancel') end + log.req = true +end + +local function createTerminal() + + terminal = vgui.Create('DFrame') + terminal:SetSize(700, 585) + terminal:SetTitle('Terminal') + terminal:Center() + terminal:MakePopup() + terminal:SetSizable(true) + terminal:SetMinWidth(400) + terminal:SetMinHeight(300) + function terminal:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, color_black) + end + function terminal:OnClose() + netstream.Start('dbg-hack.cancel') + end + + log = terminal:Add('RichText') + log:Dock(FILL) + log:InsertColorChange(32, 194, 14, 255) + function log:PerformLayout() + self:SetFontInternal('hacker') + end + print(introText) + + local cmd = terminal:Add('DPanel') + cmd:SetPaintBackground(false) + cmd:Dock(BOTTOM) + cmd:SetTall(20) + cmd:DockMargin(5, 0, 5, 0) + + local dlr = cmd:Add('DLabel') + dlr:Dock(LEFT) + dlr:SetFont('hacker') + dlr:SetTextColor(color_hacker) + dlr:SetText('>') + dlr:SetWide(15) + + local entry = cmd:Add('DTextEntry') + entry:Dock(FILL) + entry:SetTextColor(color_hacker) + entry:SetFont('hacker') + entry:SetPaintBackground(false) + + local history, point = {}, 1 + function entry:OnKeyCodeTyped(key) + if not self:IsEnabled() then return end + if key == KEY_ENTER then + if not log.req then + print('> ' .. self:GetValue()) + history[#history + 1] = self:GetValue() + point = #history + 1 + else + print(self:GetValue(), true) + log.req = nil + end + netstream.Start('dbg-hack.input', self:GetValue()) + self:SetText('') + return true + elseif key == KEY_UP then + if point == 1 then return end + point = point - 1 + self:SetText(history[point]) + self:SetCaretPos(#self:GetText()) + return true + elseif key == KEY_DOWN then + if point > #history then return end + point = point + 1 + self:SetText(history[point] or '') + self:SetCaretPos(#self:GetText()) + return true + end + end + + function log:LockInput(locked) + entry:SetEnabled(locked ~= true) + if locked ~= true then + entry:RequestFocus() + end + dlr:SetVisible(locked ~= true) + end + log:LockInput(true) + + timer.Create('kpad.load', 0.5, 0, function() + print('.', true) + end) + +end + +netstream.Hook('dbg-hack.cancel', function() + if IsValid(terminal) then terminal:Close() end +end) +netstream.Hook('dbg-hack.loaded', function() + timer.Remove('kpad.load') + print('Welcome to OctoOS 20.04.03 LTS (GNU/Linux 4.15.0-29-generic x86_64)\n') + print('* Documentation: https://octothorp.team/help') + print('* Management: https://octothorp.team/landscape') + print('* Support: https://octothorp.team/advantage\n') + print('97 packages can be updated.\n12 updates are security updates.') +end) + +netstream.Hook('dbg-hack.print', print) +netstream.Hook('dbg-hack.start', createTerminal) +netstream.Hook('dbg-hack.requestInput', reqInput) +netstream.Hook('dbg-hack.updateLockStatus', lock) diff --git a/garrysmod/addons/feature-crime/lua/hack/server.lua b/garrysmod/addons/feature-crime/lua/hack/server.lua new file mode 100644 index 0000000..352e717 --- /dev/null +++ b/garrysmod/addons/feature-crime/lua/hack/server.lua @@ -0,0 +1,330 @@ +local commands = {} +local function registerCommand(cmd, handler) + if not isstring(cmd) or not isfunction(handler) then return end + commands[string.lower(cmd)] = handler +end + +--============= UTILITY FUNCTIONS =============-- +local function stop(ply) + netstream.Start(ply, 'dbg-hack.cancel') + if ply.hacking and IsValid(ply.hacking.target) then + hook.Run('onKeypadHack', ply, false, ply.hacking.target) + end + if ply.hacking and isfunction(ply.hacking.cancel) then ply.hacking.cancel() end + timer.Remove('dbg-hack.load' .. ply:SteamID()) + ply.hacking = nil +end + +local function lock(ply, locked) + ply.hacking.blocked = (locked == true) + netstream.Start(ply, 'dbg-hack.updateLockStatus', locked == true) +end + +local function invalidEnt(ply) + local tgt = ply.hacking.target + return not IsValid(tgt) or ply:GetShootPos():DistToSqr(tgt:GetPos()) > 2500 +end + +local function invalid(ply) + if not IsValid(ply) or not ply.hacking then return true end + if invalidEnt(ply) then return true, stop(ply) end + return false +end + +local function requestInput(ply, cback) + if invalid(ply) then return end + ply.hacking.pendingInput = cback + netstream.Start(ply, 'dbg-hack.requestInput') + lock(ply) +end + +local function beep(ply) + ply:EmitSound('buttons/button15.wav') +end + +local function print(ply, text, append) + if not IsValid(ply) or not ply.hacking then return false end + beep(ply) + netstream.Start(ply, 'dbg-hack.print', text, append == true) + return true +end + +local function input(ply, data) + local cmd = string.lower(data[1]) + if isfunction(commands[cmd]) then + table.remove(data, 1) + commands[cmd](ply, data) + else + print(ply, cmd .. ': command not found') + lock(ply) + end +end + +-- Player types something in terminal and presses the Enter key +netstream.Hook('dbg-hack.input', function(ply, data) + + if not isstring(data) or invalid(ply) then return end + if ply.hacking.blocked then return lock(ply, true) end + lock(ply, true) + + local h = ply.hacking + if h.pendingInput then + h.pendingInput(data) + h.pendingInput = nil + return + end + input(ply, string.Split(data, ' ')) + +end) + +-- Player closes the terminal window (need to stop hacking process) +netstream.Hook('dbg-hack.cancel', function(ply) + if not ply.hacking then return end + if isfunction(ply.hacking.cancel) then ply.hacking.cancel() end + timer.Remove('dbg-hack.load' .. ply:SteamID()) + ply.hacking = nil +end) +hook.Add('PlayerDisconnected', 'dbg-hack.cancel', function(ply) + if ply.hacking and isfunction(ply.hacking.cancel) then ply.hacking.cancel() end + timer.Remove('dbg-hack.load' .. ply:SteamID()) +end) + +--================= COMMANDS =================-- +registerCommand('pwdserver', function(ply) + hook.Run('dbg-hack.1stCommand', ply, ply.hacking.target) + print(ply, ply.hacking.target.pwdserver) + lock(ply) +end) + +registerCommand('traceroute', function(ply, data) + if not data[1] then + print(ply, 'Usage: traceroute target_ip') + return lock(ply) + end + if data[1] ~= ply.hacking.target.pwdserver then + print(ply, 'Could not connect to ' .. data[1] .. ': connection was closed unexpectedly.') + return lock(ply) + end + print(ply, 'traceroute to ' .. data[1] .. ', 30 hops max, 38 byte packets') + + local tm, i = 0, 0 + local function printNext() + + if invalid(ply) then return end + i = i + 1 + if not ply.hacking.target.traceroute[i] then return lock(ply) end + + print(ply, string.format('%2d', i)) + + local time = math.Rand(0.1, 0.4) + local ip = ply.hacking.target.traceroute[i] + timer.Simple(time, function() + if not invalid(ply) then + print(ply, ' ' .. ip, true) + end + end) + + time = time + math.Rand(0.5, 0.7) + timer.Simple(time, function() + if invalid(ply) then return end + print(ply, string.rep(' ', 15 - #ip + 1), true) + local timeStr = '' + for j = 1, 3 do + tm = tm + math.Rand(0.001, 0.5) + timeStr = timeStr .. string.format('%.3f ms ', tm) + end + print(ply, timeStr, true) + printNext() + end) + + end + printNext() + +end) + +registerCommand('./inject.sh', function(ply, data) + local ip = data[1] + if not isstring(ip) then + print(ply, 'Usage: ./inject.sh target_ip') + return lock(ply) + end + local attNum = 0 + for i,v in ipairs(ply.hacking.target.traceroute) do + if v == ip then + attNum = i + break + end + end + if attNum == 0 then + print(ply, 'Could not inject interceptor into ' .. ip .. ': connection was closed unexpectedly.') + return lock(ply) + end + ply.hacking.interceptor = attNum + print(ply, 'successfully injected interceptor on ' .. ip) + lock(ply) +end) + +local alphabet = '123456789' +local function randomPass() + local str = '' + for i = 1, 4 do + str = str .. alphabet[math.random(#alphabet)] + end + return str +end + +registerCommand('./unhash.sh', function(ply, data) + if not isstring(data[1]) then + print(ply, 'Usage: ./unhash.sh target_hash') + return lock(ply) + end + local ans = {} + if data[1] ~= ply.hacking.target.problem then + local seed = tonumber(tostring(data[1]), 16) + if not seed then + print(ply, 'Incorrect hash') + return lock(ply) + end + math.randomseed(tonumber(tostring(data[1]), 16)) + for i = 1, 5 do + ans[#ans + 1] = randomPass() + end + math.randomseed(CurTime()) + else ans = ply.hacking.target.passs end + + local i = 0 + local function printNext() + if invalid(ply) then return end + i = i + 1 + if not ans[i] then return lock(ply) end + print(ply, string.format('%2d. %s', i, ans[i])) + timer.Simple(math.Rand(0.1, 3), printNext) + end + print(ply, 'Possible combinations:') + timer.Simple(math.Rand(0.1, 3), printNext) + +end) + +registerCommand('sudo', function(ply, data) + + if not isstring(data[1]) then + print(ply, 'Usage: sudo command command_args') + return lock(ply) + end + local cmd = string.lower(data[1]) + if cmd ~= 'unlock' then + return input(ply, data) + end + + local hashPart + if ply.hacking.interceptor then + local h, ind = ply.hacking.target.problem, ply.hacking.target.hashpts[ply.hacking.interceptor] + hashPart = '' + for i = 1, ind-1 do + hashPart = hashPart .. '**' + end + hashPart = hashPart .. h[ind * 2 - 1] .. h[ind * 2] + for i = ind + 1, 4 do + hashPart = hashPart .. '**' + end + hashPart = hashPart + end + + print(ply, '[sudo] password for kpad: ') + requestInput(ply, function(pwd) + if hashPart then + print(ply, 'Received pwd hash part: ' .. hashPart) + end + if pwd == ply.hacking.target:GetPassword() then + print(ply, 'Unlocked.') + ply.hacking.target:Process(true) + hook.Run('onKeypadHack', ply, true, ply.hacking.target) + if isfunction(ply.hacking.succeed) then ply.hacking.succeed() end + else + print(ply, 'Sorry, try again.') + ply.hacking.target:Process(false) + if isfunction(ply.hacking.fail) then ply.hacking.fail() end + end + lock(ply) + end) + +end) + +--================ EASTER EGGS ================-- +local files = { + {'-rwxrwx--x', '1176', 'Feb', '16', '00:19', 'inject.sh'}, + {'-rwxrwxr--', '484', 'Mar', '29', '22:18', 'README.TXT'}, + {'-rwxrwx--x', '9008', 'May', '10', '22:54', 'unhash.sh'}, +} + +registerCommand('ls', function(ply) + print(ply, 'total ' .. #files) + for _,v in ipairs(files) do + print(ply, string.format('%10s 1 crackr crackr %4s %3s %2s %5s %s', unpack(v))) + end + lock(ply) +end) + +registerCommand('cat', function(ply, args) + if not isstring(args[1]) then + print(ply, 'Usage: cat file_name') + return lock(ply) + end + if string.lower(args[1]) ~= 'readme.txt' then + print(ply, '[cat] file ' .. args[1] .. ' not found or permission denied for user kpad') + return lock(ply) + end + + print(ply, [[ HOW TO USE THIS PIECE OF SHIT: +1. Detect keypad's current upsource IP using "pwdserver" command: + > pwdserver + 37.212.16.22 +2. Check it's packet path using "traceroute" command: + > traceroute 37.212.16.22 + traceroute to 37.212.16.22, 30 hops max, 38 byte packets + ... +3. Set interceptors on one of IPs from traceroute: + > ./inject.sh 37.212.16.22 + Successfully injected interceptor on 37.212.16.22 +4. Try to unlock keypad: + > sudo unlock + [sudo] password for kpad: 1111 + Received pwd hash part: ******2f** + Sorry, try again. +5. Collect all hash parts and run unhash.sh: + > ./unhash.sh 16bd7c2fd0 + Possible combinations: + 1. 1234 + 2. 8888 + 3. 1984 +6. Try to unlock keypad with all passwords prompted: + > sudo unlock + [sudo] password for kpad: 1234 + Sorry, try again. + > sudo unlock + [sudo] password for kpad: 8888 + Unlocked. +Feel free contact me if you have any questions: giwamam817@jmail7.com +]]) + lock(ply) +end) + +local pmeta = FindMetaTable('Player') +function pmeta:Hack(ent, succ, fail, cancel) + if self:GetShootPos():DistToSqr(ent:GetPos()) > 2500 then + return self:addExploitAttempt() + end + ent:GenerateProblem() + self.hacking = { + target = ent, + succeed = succ, + fail = fail, + cancel = cancel, + blocked = true, + } + netstream.Start(self, 'dbg-hack.start') + timer.Create('dbg-hack.load' .. self:SteamID(), 6, 1, function() + netstream.Start(self, 'dbg-hack.loaded') + lock(self) + end) +end diff --git a/garrysmod/addons/feature-crime/lua/lockpick/client.lua b/garrysmod/addons/feature-crime/lua/lockpick/client.lua new file mode 100644 index 0000000..6b2779f --- /dev/null +++ b/garrysmod/addons/feature-crime/lua/lockpick/client.lua @@ -0,0 +1,248 @@ +surface.CreateFont('dbg-lockpick.normal', { + font = 'Calibri', + extended = true, + size = 24, + weight = 350, +}) + +local txtHint = ([[Подними все пины[n]и поверни цилиндр[n][n]Мышь - двигать отмычку[n]ЛКМ - повернуть цилиндр[n]ПКМ - отменить взлом]]):gsub('%[n%]', string.char(10)) + +local colBarrelBG, colBarrelFG = Color(150,150,150), Color(50,50,50) +local colors = CFG.skinColors +netstream.Hook('dbg-lockpick', function(ent, key, data) + + local function clear() + hook.Remove('HUDPaint', 'dbg-lockpick') + hook.Remove('Think', 'dbg-lockpick') + hook.Remove('RenderScreenspaceEffects', 'dbg-lockpick') + hook.Remove('InputMouseApply', 'dbg-lockpick') + hook.Remove('CreateMove', 'dbg-lockpick') + hook.Remove('dbg-view.chPaint', 'dbg-lockpick') + timer.Remove('dbg-lockpick') + end + + if ent then + local bNum, bTime, bWidth, bSpace = data.num or 5, data.time or 0.75, data.width or 25, data.space or 40 + local x, nextPush = 0, RealTime() + local lmbReleased = false + local xmax = bNum * bSpace / 2 + local xmin = -xmax + + local barrels = {} + for i = 1, bNum do + local bxmin = xmin + (i - 1) * bSpace + (bSpace - bWidth) / 2 + barrels[i] = { + xmin = bxmin, + xmax = bxmin + bWidth, + time = i * bTime, + st = 0, + } + end + + -- make barrels shuffle specific to this entity + math.randomseed(ent:EntIndex()) + for i = 1, bNum do + local b1, b2 = barrels[i], barrels[math.random(bNum)] + b1.time, b2.time = b2.time, b1.time + end + math.randomseed(CurTime()) + + local function succ() + local resp = '' + for c in string.gmatch(key, '.') do + resp = resp .. string.char(string.byte(c) + 8) + end + + netstream.Start('dbg-lockpick', ent, resp) + end + + local function fail() + netstream.Start('dbg-lockpick', ent, '') + end + + local function drawPick(x, y) + + x, y = math.floor(x), math.floor(y) + draw.NoTexture() + surface.SetDrawColor(150, 150, 150) + surface.DrawRect(x - 1, y, 4, 8) + surface.DrawPoly({ + { x = x - 1, y = y + 10 }, + { x = x - 1, y = y + 6 }, + { x = x + 201, y = y + 4 }, + { x = x + 201, y = y + 12 }, + }) + surface.DrawRect(x + 200, y, 100, 16) + + end + + local totalW = bNum * bSpace + local colBG, bgX, bgY, bgW, bgH = Color(255,255,255, 10), (ScrW() - totalW) / 2 - 5, ScrH() / 2 - 110, totalW + 10, 190 + hook.Add('HUDPaint', 'dbg-lockpick', function() + + draw.RoundedBox(8, bgX, bgY, bgW, bgH, colBG) + + local cx, cy = ScrW() / 2, ScrH() / 2 + for i = 1, bNum do + local b = barrels[i] + draw.RoundedBox(4, cx + b.xmin, cy - 100, bWidth, 150, colBarrelBG) + draw.RoundedBox(2, cx + b.xmin + 1, cy - b.st * 98 - 1, bWidth - 2, 50, colBarrelFG) + end + + local y = cy + 60 + if RealTime() < nextPush then + y = y - (nextPush - RealTime()) / 0.3 * 16 + end + drawPick(cx + x, y) + + draw.DrawText(txtHint, 'dbg-lockpick.normal', cx + totalW / 2 + 20, cy - 95, color_white, TEXT_ALIGN_LEFT) + + end) + local lastTime = nil + hook.Add('Think', 'dbg-lockpick', function() + + for i = 1, bNum do + local b = barrels[i] + if b.st > 0 then + local delta = CurTime() - (lastTime or CurTime()) -- a fix timescale exploit + b.st = math.Approach(b.st, 0, delta / b.time) + end + end + lastTime = CurTime() + + end) + + local suspicion = 0 + local xSum, ySum = 0, 0 + timer.Create('dbg-lockpick', 0.5, 0, function() + if (xSum ~= 0 and ySum == 0) or (xSum == 0 and ySum ~= 0) then + suspicion = suspicion + 1 + elseif xSum ~= 0 or ySum ~= 0 then + suspicion = math.max(suspicion - 1, 0) + end + + if suspicion > 3 then + clear() + netstream.Start('dbg-lockpick.exploit') + end + + xSum, ySum = 0, 0 + end) + + local sens = GetConVar('sensitivity'):GetFloat() + hook.Add('InputMouseApply', 'dbg-lockpick', function(cmd, _x, _y) + + local pushed = false + if RealTime() > nextPush and lmbReleased then + local clamp = FrameTime() * 7500 + _x = math.Clamp(_x, -clamp, clamp) + x = math.Clamp(x + _x * sens / 50, xmin, xmax) + if -_y / FrameTime() > 3000 then + local b + for i = 1, bNum do + local _b = barrels[i] + if x > _b.xmin and x < _b.xmax then b = _b break end + end + if not b then + fail() + else + b.st = 1 + end + + netstream.Start('dbg-lockpick.push') + nextPush = RealTime() + 0.3 + pushed = true + end + end + + if math.abs(_x) > 0.0001 then xSum = xSum + _x end + if math.abs(_y) > 0.0001 and not pushed then ySum = ySum + _y end + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + + return true + + end) + + hook.Add('CreateMove', 'dbg-lockpick', function(cmd) + + if not cmd:KeyDown(IN_ATTACK) then + lmbReleased = true + end + if cmd:KeyDown(IN_ATTACK) and RealTime() > nextPush and lmbReleased then + local ok = true + for i = 1, bNum do + local b = barrels[i] + if b.st <= 0 then ok = false break end + end + if ok then + succ() + hook.Remove('Think', 'dbg-lockpick') + hook.Remove('InputMouseApply', 'dbg-lockpick') + hook.Remove('CreateMove', 'dbg-lockpick') + timer.Remove('dbg-lockpick') + else + fail() + end + nextPush = RealTime() + 0.3 + end + + if cmd:KeyDown(IN_ATTACK2) then + netstream.Start('dbg-lockpick', nil) + end + + cmd:RemoveKey(IN_ATTACK) + cmd:RemoveKey(IN_ATTACK2) + cmd:ClearMovement() + + end) + + local blur = Material('pp/blurscreen') + hook.Add('RenderScreenspaceEffects', 'dbg-lockpick', function() + + local a = 1 + if a > 0 then + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -a * 0.2, + ['$pp_colour_contrast'] = 1 + 0.5 * a, + ['$pp_colour_colour'] = 1 - a, + } + + if GetConVar('octolib_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor(255, 255, 255, a * 255) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat('$blur', a * i * 2) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(-1, -1, ScrW() + 2, ScrH() + 2) + end + else + colMod['$pp_colour_brightness'] = -0.4 * a + colMod['$pp_colour_contrast'] = 1 + 0.2 * a + DrawColorModify(colMod) + end + + local col = colors.bg + draw.NoTexture() + surface.SetDrawColor(col.r, col.g, col.b, a * 100) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + end + + end) + else + clear() + end + +end) diff --git a/garrysmod/addons/feature-crime/lua/lockpick/server.lua b/garrysmod/addons/feature-crime/lua/lockpick/server.lua new file mode 100644 index 0000000..6b87761 --- /dev/null +++ b/garrysmod/addons/feature-crime/lua/lockpick/server.lua @@ -0,0 +1,157 @@ +local function check(ply, ent, checkFunc) + + if not IsValid(ent) then return false end + if ply:IsGhost() then return false end + if not ply.inv or not ply.inv.conts._hand or ply.inv.conts._hand:HasItem('lockpick') <= 0 then + ply:Notify('warning', L.needlockpick) + return + end + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() ~= 'dbg_hands' then return false end + + if ply.lockpickCache and isfunction(ply.lockpickCache.check) and not ply.lockpickCache.check(ply, ent) then + return false + end + if isfunction(checkFunc) and not checkFunc(ply, ent) then return false end + + if util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * CFG.useDist, + filter = function(e) + return e == ent + end, + }).Entity ~= ent then return false end + + return true + +end + +local function succ(ply, ent) + + if ply.lockpickCache.succ then + ply.lockpickCache.succ(ply, ent) + end + + ply:LockpickStop() + hook.Run('onLockpickCompleted', ply, true, ent) + +end + +local pushSounds = {'weapons/357/357_reload1.wav', 'weapons/357/357_reload3.wav', 'weapons/357/357_reload4.wav'} +local failSounds = {'weapons/crowbar/crowbar_impact1.wav', 'weapons/crowbar/crowbar_impact2.wav'} + +local function fail(ply, ent) + + if CurTime() < (ply.nextLockpickFail or 0) then return end + ply.nextLockpickFail = CurTime() + 1 + + local snd = table.Random(failSounds) + ply:EmitSound(snd, 65, 100, 0.8) + + if ply.lockpickCache and ply.lockpickCache.fail then + ply.lockpickCache.fail(ply, ent) + end + + ply:LockpickStop() + ply.inv.conts._hand:TakeItem('lockpick', 1) + ply.inv.conts._hand:AddItem('lockpick_broken', 1) + hook.Run('onLockpickCompleted', ply, false, ent) + +end + +netstream.Hook('dbg-lockpick.push', function(ply) + + if not ply.lockpickCache or not ply.lockpickCache.key or not check(ply, ply.lockpickCache.ent) then + ply:LockpickStop() + return + end + local w = ply:GetActiveWeapon() + if IsValid(w) then w:SetHoldType('revolver') end + if math.random(3) ~= 3 then + local snd = table.Random(pushSounds) + ply:EmitSound(snd, 60, 100, 0.8) + ply:SetAnimation(PLAYER_ATTACK1) + end + +end) + +netstream.Hook('dbg-lockpick', function(ply, ent, resp) + + if not ply.lockpickCache then return end + + if ent then + if ply.lockpickCache.key ~= resp then + fail(ply, ent) + elseif check(ply, ent) then + succ(ply, ent) + end + else + ply:LockpickStop() + end + +end) + +netstream.Hook('dbg-lockpick.exploit', function(ply) + if ply:TriggerCooldown('lockpick.exploit', 1.5) then + ply.lockpickExploit = (ply.lockpickExploit or 0) + 1 + octochat.talkToAdmins(octochat.notifyColors.warning, ('%s (%s) подозревается в использовании макросов для взлома (%d/3)'):format(ply:Name(), ply:SteamID(), ply.lockpickExploit)) + if ply.lockpickExploit >= 3 then + ply:BanEverywhere(0, 'Exploits 1') + end + end +end) + +local ply = FindMetaTable 'Player' +function ply:Lockpick(ent, data) + + data = data or {} + if not check(self, ent, data.check) then return end + + local key = '' + for i = 1, 16 do key = key .. string.char(math.random(64)) end + + local resp = '' + for c in string.gmatch(key, '.') do + resp = resp .. string.char(string.byte(c) + 8) + end + + + self.lockpickCache = { + key = resp, + succ = data.succ, + fail = data.fail, + check = data.check, + ent = ent, + } + local w = self:GetActiveWeapon() + if IsValid(w) then + self.lockpickCache.holdtype = w:GetHoldType() + w:SetHoldType('revolver') + end + octolib.stopAnimations(self) + + self:SetNetVar('currentAction', L.lockpick) + netstream.Start(self, 'dbg-lockpick', ent, key, { + num = data.numOverride or ent.lockNum, + time = data.timeOverride or ent.lockTime, + space = data.spaceOverride or ent.lockSpace, + width = data.widthOverride or ent.lockWidth, + }) + +end + +function ply:LockpickStop() + + if self.lockpickCache then + local w = self:GetActiveWeapon() + if IsValid(w) and self.lockpickCache.holdtype then + w:SetHoldType(self.lockpickCache.holdtype or 'normal') + end + end + + self.lockpickCache = nil + self:SetNetVar('currentAction', nil) + netstream.Start(self, 'dbg-lockpick', nil) + +end diff --git a/garrysmod/addons/feature-cuffs/lua/autorun/client/cl_cuffs.lua b/garrysmod/addons/feature-cuffs/lua/autorun/client/cl_cuffs.lua new file mode 100644 index 0000000..abcb666 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/autorun/client/cl_cuffs.lua @@ -0,0 +1,42 @@ +local renderPos = { + blind = {Vector(4.5,4,2.6), Vector(4.8,5.8,0), Vector(4.5,4,-2.8), Vector(3.4,-1,-3.8), Vector(2.5,-3.5,0), Vector(3.4,-1,3.8)}, + gag = {Vector(2.0,5.2,2), Vector(2.0,6.5,-0.1), Vector(2.0,5.5,-2), Vector(1,1,-3.4), Vector(0.2,-2,0), Vector(1,1,3.4)}, +} +local rope = Material('cable/rope') + +local function renderCircle(pos, ang, k) + local p = renderPos[k][1] + local first = pos + (ang:Forward() * p.x) + (ang:Right() * p.y) + (ang:Up() * p.z) + local last = first + for i = 2, #renderPos[k] do + p = renderPos[k][i] + local new = pos + (ang:Forward() * p.x) + (ang:Right() * p.y) + (ang:Up() * p.z) + render.DrawBeam(new, last, 1.5, 0, 1, color_white) + last = new + end + render.DrawBeam(last, first, 1.5, 0, 1, color_white) +end + +hook.Add('PostPlayerDraw', 'dbg-cuffs', function(ply) + if not IsValid(ply) then return end + + local cuffed, cuff = ply:IsHandcuffed() + if not cuffed or not IsValid(cuff) then return end + + render.SetMaterial(rope) + + local pos, ang + local bone = cuff.Owner:LookupBone('ValveBiped.Bip01_Head1') + if bone then + pos, ang = cuff.Owner:GetBonePosition(bone) + end + if not pos and not ang then return end + + if cuff:GetNetVar('blind') then + renderCircle(pos, ang, 'blind') + end + + if cuff:GetNetVar('gag') then + renderCircle(pos, ang, 'gag') + end +end) diff --git a/garrysmod/addons/feature-cuffs/lua/autorun/server/sv_cuffs.lua b/garrysmod/addons/feature-cuffs/lua/autorun/server/sv_cuffs.lua new file mode 100644 index 0000000..51678f3 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/autorun/server/sv_cuffs.lua @@ -0,0 +1,73 @@ +octolib.server('sv_drag') + +hook.Add('playerCanChangeTeam', 'dbg-cuffs', function(ply, _, force) + if force then return end + if ply.dragging then + return false, 'Сначала отпусти игрока' + elseif ply:IsHandcuffed() then + return false, L.dontdoincuffs + end +end) + +hook.Add('CanPlayerEnterVehicle', 'dbg-cuffs', function(ply) + if ply.isForce then return end + if ply:IsHandcuffed() and not ply:GetNetVar('dragger') then return false end + if ply.dragging then return false end +end) + +hook.Add('CanExitVehicle', 'dbg-cuffs', function(veh, ply) + if ply:IsHandcuffed() and not ply.isForce then return false end +end) + +hook.Add('PlayerDisconnected', 'dbg-cuffs', function(ply) + if IsValid(ply.draggedBy) then + ply.draggedBy.dragging = nil + end +end) + +hook.Add('PlayerCanSeePlayersChat', 'dbg-cuffs', function(text, teamOnly, listener, ply) + if not IsValid(ply) then return end + local cuffed, wep = ply:IsHandcuffed() + if cuffed and wep:GetNetVar('gag') then return false end +end) + +hook.Add('PlayerCanHearPlayersVoice', 'dbg-cuffs', function(listener, ply) + if not IsValid(ply) then return end + local cuffed, wep = ply:IsHandcuffed() + if cuffed and wep:GetNetVar('gag') then return false end +end) + +local function cant(ply) + if ply:IsHandcuffed() then return false, L.dontdoincuffs end +end + +hook.Add('canDropWeapon', 'dbg-cuffs', function(ply, wep) + if wep:GetClass() == 'weapon_cuffed' then return false, L.dontdoincuffs end +end) +hook.Add('canRequestWarrant', 'dbg-cuffs', function(crim, cop, reason) + if cop:IsHandcuffed() then return false, L.dontdoincuffs end +end) +hook.Add('canWanted', 'dbg-cuffs', function(crim, cop, reason) + if cop:IsHandcuffed() then return false, L.dontdoincuffs end +end) +hook.Add('canArrest', 'dbg-cuffs', function(cop, crim) + if IsValid(crim) and not crim:IsHandcuffed() then return false, L.needscuffs end +end) +hook.Add('CanChangeRPName', 'dbg-cuffs', cant) + +hook.Add('OnHandcuffed', 'dbg-cuffs', function(ply, target, cuffs) + target.policeCuffs = ply:GetActiveWeapon():GetClass() == 'weapon_cuff_police' or nil +end) + +hook.Add('PlayerUse', 'dbg-cuffs', cant) +hook.Add('octoinv.canLock', 'dbg-cuffs', cant) +hook.Add('octoinv.canUnlock', 'dbg-cuffs', cant) +hook.Add('octoinv.canPickup', 'dbg-cuffs', cant) +hook.Add('octoinv.canCraft', 'dbg-cuffs', cant) +hook.Add('octoinv.canDrop', 'dbg-cuffs', cant) +hook.Add('octoinv.canMove', 'dbg-cuffs', cant) +hook.Add('octoinv.canUse', 'dbg-cuffs', function(cont, item, ply) + if ply:IsHandcuffed() then return false, L.dontdoincuffs end +end) + +hook.Add('octolib.canUseAnimation', 'dbg-cuffs', cant) diff --git a/garrysmod/addons/feature-cuffs/lua/autorun/server/sv_drag.lua b/garrysmod/addons/feature-cuffs/lua/autorun/server/sv_drag.lua new file mode 100644 index 0000000..b4dcdc8 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/autorun/server/sv_drag.lua @@ -0,0 +1,54 @@ +local useDist = CFG.useDist * CFG.useDist + +local function cancelDrag(cop, crim) + if IsValid(crim) then + crim:SetNetVar('dragger') + end + if IsValid(cop) then + cop:SetNetVar('dragging') + cop.dragging = nil + end +end + +local noDragWeps = octolib.array.toKeys({'weapon_physgun', 'med_kit'}) + +hook.Add('KeyPress', 'dbg-cuffs.drag', function(ply, key) + + if key ~= IN_ATTACK then return end + if ply:InVehicle() or ply:IsGhost() or ply:IsHandcuffed() then return end + local wep = ply:GetActiveWeapon() + if IsValid(wep) and noDragWeps[wep:GetClass()] then return end + + local tgt = octolib.use.getTrace(ply).Entity + if not (IsValid(tgt) and tgt:IsPlayer() and tgt:IsHandcuffed()) then return end + if IsValid(tgt:GetNetVar('dragger')) or IsValid(ply:GetNetVar('dragging')) then return end + tgt:SetNetVar('dragger', ply) + ply:SetNetVar('dragging', tgt) +end) + +hook.Add('KeyRelease', 'dbg-cuffs.drag', function(ply, key) + if key == IN_ATTACK and IsValid(ply:GetNetVar('dragging')) then + cancelDrag(ply, ply:GetNetVar('dragging')) + end +end) + +hook.Add('PlayerDisconnect', 'dbg-cuffs.drag', function(ply) + if IsValid(ply:GetNetVar('dragging')) then + cancelDrag(ply, ply:GetNetVar('dragging')) + end + if IsValid(ply:GetNetVar('dragger')) then + cancelDrag(ply:GetNetVar('dragger'), ply) + end +end) + +hook.Add('FindUseEntity', 'dbg-cuffs.drag', function(ply, ent) + if not IsValid(ent) or ply:GetNetVar('dragging') then + local traceEnt = util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * 72, + filter = {ply, ply:GetNetVar('dragging')}, + }).Entity + if IsValid(traceEnt) then return traceEnt end + end + return ent +end) diff --git a/garrysmod/addons/feature-cuffs/lua/autorun/sh_cuffs.lua b/garrysmod/addons/feature-cuffs/lua/autorun/sh_cuffs.lua new file mode 100644 index 0000000..7929ceb --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/autorun/sh_cuffs.lua @@ -0,0 +1,58 @@ +local pmeta = FindMetaTable('Player') + +function pmeta:IsHandcuffed() + local wep = self:GetActiveWeapon() + if IsValid(wep) and wep.IsHandcuffs then + return true, wep + end + return false +end + +hook.Add('dbg-travel.canTransfer', 'dbg-cuffs', function(ply) + if ply:IsHandcuffed() then return false, L.dontdoincuffs end +end) + +local maxSid = GetConVar('cl_sidespeed'):GetInt() - 21 +local maxFwd = GetConVar('cl_forwardspeed'):GetInt() - 21 + +hook.Add('StartCommand', 'dbg-cuffs.drag', function(ply, cmd) + + local cuffed, wep = ply:IsHandcuffed() + if cuffed and IsValid(ply:GetNetVar('dragger')) then + local cop = ply:GetNetVar('dragger') + + local pos = cop:EyePos() + cop:GetAimVector() * wep:GetNetVar('RopeLength', 100) + local dirFwd = ply:GetAimVector() + local dirTgt = pos - ply:GetPos() + local dist = dirTgt:Length2DSqr() + if SERVER and dist > 100000 then + ply:SetNetVar('dragger') + ply:SetNetVar('dragging') + end + + cmd:ClearMovement() + + local ang = ply.lastEyeAngles or ply:EyeAngles() + ang.p = cmd:GetViewAngles().p + ang.y = octolib.math.lerpAngle(ang.y, cop:EyeAngles().y, FrameTime() * 2, FrameTime() * 4) + ply.lastEyeAngles = ang + cmd:SetViewAngles(ang) + cmd:SetButtons(0) + if SERVER and cop:KeyDown(IN_JUMP) then + cmd:SetButtons(IN_JUMP) + end + + if dist > 50 then + dirFwd.z = 0 + dirTgt.z = 0 + dirFwd:Normalize() + dirTgt:Normalize() + local dirSid = Vector(dirFwd.x, dirFwd.y, 0) + dirSid:Rotate(Angle(0, 90, 0)) + + cmd:SetForwardMove(dirFwd:Dot(dirTgt) * math.min(maxFwd, dist)) + cmd:SetSideMove(-dirSid:Dot(dirTgt) * math.min(maxSid, dist)) + end + end + +end) diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/cl_init.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/cl_init.lua new file mode 100644 index 0000000..dbc7ce8 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/cl_init.lua @@ -0,0 +1,115 @@ +include('shared.lua') + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) +end + +function SWEP:Initialize() + self:SetHoldType('slam') +end + +function SWEP:Holster() + if IsValid(self.cmdl_LeftCuff) then self.cmdl_LeftCuff:Remove() end + if IsValid(self.cmdl_RightCuff) then self.cmdl_RightCuff:Remove() end + return true +end +SWEP.OnRemove = SWEP.Holster + +local CuffMdl = 'models/hunter/tubes/tube2x2x1.mdl' + +function SWEP:CreateCuffs() + if self.creatingCuffs then return end + timer.Simple(0, function() + if not IsValid(self) then return end + if not IsValid(self.leftCuff) then + self.leftCuff = octolib.createDummy(CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE) + self.leftCuff:SetNoDraw(true) + end + if not IsValid(self.rightCuff) then + self.rightCuff = octolib.createDummy(CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE) + self.rightCuff:SetNoDraw(true) + end + self.creatingCuffs = false + end) +end + +function SWEP:RenderCuffs(vm, data) + if not IsValid(vm) then return end + + if not IsValid(self.leftCuff) or not IsValid(self.rightCuff) then + return self:CreateCuffs() + end + + local rpos, rang = self:GetBonePos('ValveBiped.Bip01_R_Hand', vm) + if not rpos or not rang then return end + + -- Right + local u, r, f = rang:Up(), rang:Right(), rang:Forward() + local fixed_rpos = rpos + (f * data.right.pos.x) + (r * data.right.pos.y) + (u * data.right.pos.z) + self.rightCuff:SetPos(fixed_rpos) + rang:RotateAroundAxis(u, data.right.ang.y) + rang:RotateAroundAxis(r, data.right.ang.p) + rang:RotateAroundAxis(f, data.right.ang.r) + self.rightCuff:SetAngles(rang) + + local matrix = Matrix() + matrix:Scale(data.right.scale) + self.rightCuff:EnableMatrix('RenderMultiply', matrix) + + self.rightCuff:SetMaterial(self.CuffMaterial) + self.rightCuff:DrawModel() + + -- Left + local dist = data.left.pos:DistToSqr(fixed_rpos) + if dist > 10000 then + data.left.pos = fixed_rpos + data.left.vel = Vector(0, 0, 0) + elseif dist > 100 then + local target = (fixed_rpos - data.left.pos) * 0.3 + data.left.vel.x = math.Approach(data.left.vel.x, target.x, 1) + data.left.vel.y = math.Approach(data.left.vel.y, target.y, 1) + data.left.vel.z = math.Approach(data.left.vel.z, target.z, 3) + end + + data.left.vel.x = math.Approach(data.left.vel.x, 0, 0.5) + data.left.vel.y = math.Approach(data.left.vel.y, 0, 0.5) + data.left.vel.z = math.Approach(data.left.vel.z, 0, 0.5) + + data.left.vel.z = math.Approach(data.left.vel.z, (fixed_rpos.z - data.left.pos.z - 10)*data.left.gravity, 3) + data.left.pos:Add(data.left.vel) + + self.leftCuff:SetPos(data.left.pos) + self.leftCuff:SetAngles(data.left.ang) + self.leftCuff:EnableMatrix('RenderMultiply', matrix) + + self.leftCuff:SetMaterial(self.CuffMaterial) + self.leftCuff:DrawModel() + + -- Rope + if not self.RopeMat then self.RopeMat = Material(self.CuffRope) end + render.SetMaterial(self.RopeMat) + render.DrawBeam(data.left.pos + data.rope.l, fixed_rpos, 0.7, 0, 5, color_white) +end + +SWEP.wrender = { + left = {pos=Vector(0,0,0), vel=Vector(0,0,0), gravity=1, ang=Angle(0,30,90)}, + right = {pos=Vector(2.95,2.5,-0.2), ang=Angle(30,0,90), scale = Vector(0.044,0.044,0.044)}, + rope = {l = Vector(0,0,2), r = Vector(3,-1.65,1)}, +} +function SWEP:DrawWorldModel() + self:RenderCuffs(self.Owner, self.wrender) +end + +function SWEP:GetBonePos(bonename, vm) + local bone = vm:LookupBone(bonename) + if not bone then return end + + local pos, ang = Vector(0, 0, 0), Angle(0, 0, 0) + local matrix = vm:GetBoneMatrix(bone) + if matrix then + pos, ang = matrix:GetTranslation(), matrix:GetAngles() + end + if self.ViewModelFlip then ang.r = -ang.r end + return pos, ang +end diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/init.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/init.lua new file mode 100644 index 0000000..1ab40f2 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/init.lua @@ -0,0 +1,60 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +SWEP.nextCuff = 0 + +function SWEP:PrimaryAttack() + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + + if self.nextCuff > CurTime() then return end + + local ply = self.Owner + local tgt = octolib.use.getTrace(ply).Entity + if not IsValid(tgt) or not tgt:IsPlayer() or tgt:IsGhost() then return end + + if self.ScareRequired and tgt:GetNetVar('ScareState', 0) <= 0.4 then + return ply:Notify('warning', ('%s сопротивляется! Направь на этого человека %sогнестрельное оружие, чтобы припугнуть, а затем попробуй сковать еще раз'):format(tgt:Name(), ply:HasWeapon('stungun') and 'тазер или ' or '')) + end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + ply:DelayedAction('cuffing', 'Сковывание', { + time = self.CuffTime, + check = function() return octolib.use.check(ply, tgt) and ply:GetActiveWeapon() == self end, + succ = function() + tgt:ExitVehicle() + ply:EmitSound(self.CuffSound) + self.nextCuff = CurTime() + self.CuffRecharge + self:DoHandcuff(tgt) + end, + }) +end + +function SWEP:Holster() + return true +end +SWEP.OnRemove = SWEP.Holster + +function SWEP:DoHandcuff(ply) + if not IsValid(ply) or ply:IsHandcuffed() or not IsValid(self.Owner) then return end + local veh = ply:GetVehicle() + if IsValid(veh) and veh.fphysSeat then return end + + local cuffs = ply:Give('weapon_cuffed') + cuffs:SetNetVar('CuffMaterial', self.CuffMaterial) + cuffs:SetNetVar('CuffRope', self.CuffRope) + cuffs.RemoveTime = self.RemoveTime + cuffs.CanBlind = self.CanBlind + cuffs.CanGag = self.CanGag + cuffs.CuffType = self:GetClass() or '' + ply:SelectWeapon('weapon_cuffed') + + hook.Call('OnHandcuffed', GAMEMODE, self.Owner, ply, cuffs) + + if not self.CuffReusable then + if IsValid(self.Owner) then self.Owner:ConCommand('lastinv') end + self:Remove() + end +end diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/shared.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/shared.lua new file mode 100644 index 0000000..075d1d8 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_base/shared.lua @@ -0,0 +1,73 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'weapon_base' + +SWEP.Category = 'Handcuffs' +SWEP.Author = 'Wani4ka' +SWEP.Instructions = '' + +SWEP.Spawnable = false +SWEP.AdminOnly = false +SWEP.AdminSpawnable = false + +SWEP.Slot = 5 +SWEP.PrintName = 'Handcuffs' + +SWEP.ViewModelFOV = 60 +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.WorldModel = 'models/props_junk/cardboard_box004a.mdl' +SWEP.ViewModel = 'models/weapons/c_bugbait.mdl' +SWEP.UseHands = true + +SWEP.Primary.Recoil = 1 +SWEP.Primary.Damage = 5 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0 +SWEP.Primary.Delay = 0.25 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = 'none' +SWEP.Primary.ClipMax = -1 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' +SWEP.Secondary.ClipMax = -1 + +SWEP.DeploySpeed = 1.5 + +SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK +SWEP.ReloadAnim = ACT_VM_RELOAD + +SWEP.CuffTime = 1.0 +SWEP.CuffSound = Sound('buttons/lever7.wav') + +SWEP.CuffMaterial = 'phoenix_storms/metalfloor_2-3' +SWEP.CuffRope = 'cable/cable2' + +SWEP.RopeLength = 0 + +SWEP.CuffReusable = false -- Can reuse (ie, not removed on use) +SWEP.CuffRecharge = 30 -- Time before re-use + +SWEP.RemoveTime = 10 + +SWEP.CanBlind = false +SWEP.CanGag = false + +function SWEP:Initialize() + self:SetHoldType('slam') + if SERVER then + self:SetNetVar('RopeLength', self.RopeLength) + end +end +function SWEP:SecondaryAttack() end +function SWEP:Reload() end diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_police.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_police.lua new file mode 100644 index 0000000..c85b006 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_police.lua @@ -0,0 +1,24 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_cuff_base' + +SWEP.Category = 'Handcuffs' +SWEP.Instructions = 'ЛКМ - заковать' + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.AdminSpawnable = true + +SWEP.PrintName = L.cuff_police + +SWEP.CuffTime = 0.3 +SWEP.CuffSound = Sound('buttons/lever7.wav') + +SWEP.CuffMaterial = 'phoenix_storms/gear' +SWEP.CuffRope = 'cable/cable2' +SWEP.RopeLength = 25 +SWEP.CuffReusable = true +SWEP.RemoveTime = 10 + +SWEP.CanBlind = false +SWEP.CanGag = false diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_rope.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_rope.lua new file mode 100644 index 0000000..0ecdc28 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuff_rope.lua @@ -0,0 +1,24 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_cuff_base' + +SWEP.Category = 'Handcuffs' +SWEP.Instructions = 'ЛКМ - завязать руки' + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.AdminSpawnable = true + +SWEP.PrintName = L.cuff_rope + +SWEP.CuffTime = 2 +SWEP.CuffSound = Sound('buttons/lever7.wav') + +SWEP.CuffMaterial = 'models/props_foliage/tree_deciduous_01a_trunk' +SWEP.CuffRope = 'cable/rope' +SWEP.RopeLength = 100 +SWEP.CuffReusable = false +SWEP.ScareRequired = true + +SWEP.CanBlind = true +SWEP.CanGag = true diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/cl_init.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/cl_init.lua new file mode 100644 index 0000000..cc50b17 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/cl_init.lua @@ -0,0 +1,127 @@ +include('shared.lua') + +function SWEP:OnRemove() -- Fixes invisible other weapons + if IsValid(self.Owner) then + local viewModel = self.Owner:GetViewModel() + if IsValid(viewModel) then viewModel:SetMaterial('') end + end + if IsValid(self.leftCuff ) then self.leftCuff:Remove() end + if IsValid(self.rightCuff ) then self.rightCuff:Remove() end + return true +end + +function SWEP:DrawHUDBackground() + if self:GetNetVar('blind') then + surface.SetDrawColor(Color(0, 0, 0, 253)) + surface.DrawRect(0, 0, ScrW(), ScrH()) + + surface.SetDrawColor(color_black) + for i = 1, ScrH(), 5 do + surface.DrawRect(0, i, ScrW(), 4) + end + for i = 1, ScrW(), 5 do + surface.DrawRect(i, 0, 4, ScrH()) + end + end +end + +local CuffMdl = 'models/hunter/tubes/tube2x2x1.mdl' +local DefaultRope = 'cable/cable2' + +local wrender = { + left = {pos=Vector(0,0,0), ang=Angle(90,0,0), scale = Vector(0.035,0.035,0.035)}, + right = {pos=Vector(0.2,0,0), ang=Angle(90,0,0), scale = Vector(0.035,0.035,0.035)}, + rope = {l = Vector(-0.2,1.3,-0.25), r = Vector(0.4,1.4,-0.2)}, +} + +function SWEP:CreateCuffs() + if self.creatingCuffs then return end + timer.Simple(0, function() + if not IsValid(self) then return end + if not IsValid(self.leftCuff) then + self.leftCuff = octolib.createDummy(CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE) + self.leftCuff:SetNoDraw(true) + end + if not IsValid(self.rightCuff) then + self.rightCuff = octolib.createDummy(CuffMdl, RENDER_GROUP_VIEW_MODEL_OPAQUE) + self.rightCuff:SetNoDraw(true) + end + self.creatingCuffs = false + end) +end + +function SWEP:DrawWorldModel() + local vm, data, lbone, rbone = self.Owner, wrender, 'ValveBiped.Bip01_L_Hand', 'ValveBiped.Bip01_R_Hand' + if not IsValid(vm) then return end + + if not IsValid(self.leftCuff) or not IsValid(self.rightCuff) then + return self:CreateCuffs() + end + + local lpos, lang = self:GetBonePos(lbone, vm) + local rpos, rang = self:GetBonePos(rbone, vm) + + -- Left + local u,r,f = lang:Up(), lang:Right(), lang:Forward() -- Prevents moving axes + self.leftCuff:SetPos(lpos + (f*data.left.pos.x) + (r*data.left.pos.y) + (u*data.left.pos.z)) + lang:RotateAroundAxis(u, data.left.ang.y) + lang:RotateAroundAxis(r, data.left.ang.p) + lang:RotateAroundAxis(f, data.left.ang.r) + self.leftCuff:SetAngles(lang) + + local matrix = Matrix() + matrix:Scale(data.left.scale) + self.leftCuff:EnableMatrix('RenderMultiply', matrix) + + self.leftCuff:SetMaterial(self:GetNetVar('CuffMaterial', '')) + self.leftCuff:DrawModel() + + -- Right + u,r,f = rang:Up(), rang:Right(), rang:Forward() -- Prevents moving axes + self.rightCuff:SetPos( rpos + (f*data.right.pos.x) + (r*data.right.pos.y) + (u*data.right.pos.z) ) + rang:RotateAroundAxis(u, data.right.ang.y) + rang:RotateAroundAxis(r, data.right.ang.p) + rang:RotateAroundAxis(f, data.right.ang.r) + self.rightCuff:SetAngles(rang) + + local matrix = Matrix() + matrix:Scale(data.right.scale) + self.rightCuff:EnableMatrix('RenderMultiply', matrix) + + self.rightCuff:SetMaterial(self:GetNetVar('CuffMaterial', '')) + self.rightCuff:DrawModel() + + -- Rope accross half the map... + if (lpos.x == 0 and lpos.y == 0 and lpos.z == 0) or (rpos.x == 0 and rpos.y == 0 and rpos.z == 0) then return end + + if self:GetNetVar('CuffRope') ~= self.LastMatStr then + self.RopeMat = Material(self:GetNetVar('CuffRope')) + self.LastMatStr = self:GetNetVar('CuffRope') + end + if not self.RopeMat then self.RopeMat = Material(DefaultRope) end + render.SetMaterial(self.RopeMat) + render.DrawBeam(lpos + (lang:Forward()*data.rope.l.x) + (lang:Right()*data.rope.l.y) + (lang:Up()*data.rope.l.z), + rpos + (f*data.rope.r.x) + (r*data.rope.r.y) + (u*data.rope.r.z), 0.7, 0, 5, color_white) + + if IsValid(self.Owner) and IsValid(self.Owner:GetNetVar('dragger')) then + local dragger = self.Owner:GetNetVar('dragger') + local bone = dragger:LookupBone('ValveBiped.Bip01_R_Hand') + if bone then + render.DrawBeam(rpos + (f*data.rope.r.x) + (r*data.rope.r.y) + (u*data.rope.r.z), dragger:GetBonePosition(bone), 0.7, 0, 5, color_white) + end + end +end + +function SWEP:GetBonePos(bonename, vm) + local bone = vm:LookupBone(bonename) + if not bone then return end + + local pos, ang = Vector(0, 0, 0), Angle(0, 0, 0) + local matrix = vm:GetBoneMatrix(bone) + if matrix then + pos, ang = matrix:GetTranslation(), matrix:GetAngles() + end + if self.ViewModelFlip then ang.r = -ang.r end + return pos, ang +end + diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/init.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/init.lua new file mode 100644 index 0000000..f39d767 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/init.lua @@ -0,0 +1,97 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +local bones = { + ['ValveBiped.Bip01_R_UpperArm'] = Angle(-28,18,-21), + ['ValveBiped.Bip01_L_Hand'] = Angle(0,0,119), + ['ValveBiped.Bip01_L_Forearm'] = Angle(15,20,40), + ['ValveBiped.Bip01_L_UpperArm'] = Angle(15, 26, 0), + ['ValveBiped.Bip01_R_Forearm'] = Angle(0,50,0), + ['ValveBiped.Bip01_R_Hand'] = Angle(45,34,-15), + ['ValveBiped.Bip01_L_Finger01'] = Angle(0,50,0), + ['ValveBiped.Bip01_R_Finger0'] = Angle(10,2,0), + ['ValveBiped.Bip01_R_Finger1'] = Angle(-10,0,0), + ['ValveBiped.Bip01_R_Finger11'] = Angle(0,-40,0), + ['ValveBiped.Bip01_R_Finger12'] = Angle(0,-30,0) +} +local cuffData = { + norun = true, +} + +function SWEP:SetupBones(ply, reset) + if not IsValid(ply) then return end + for k,v in pairs(bones) do + local bone = ply:LookupBone(k) + if bone then + ply:ManipulateBoneAngles(bone, reset and Angle(0, 0, 0) or v) + end + end +end + +function SWEP:Holster() + self:SetHoldType(self.HoldType) + return false +end + +function SWEP:Deploy() + self:SetHoldType(self.HoldType) + return true +end + +-- function SWEP:Think() +-- if self:GetHoldType(self.HoldType) then +-- self:SetHoldType(self.HoldType) +-- self:SetupBones(newOwner) +-- end +-- end + +function SWEP:Equip(newOwner) + newOwner:DropObject() + -- self:SetupBones(newOwner, true) + newOwner:SelectWeapon(self:GetClass()) + self:SetupBones(newOwner) + newOwner:MoveModifier('cuffed', cuffData) + return true +end + +function SWEP:OnRemove() + if IsValid(self.Owner) then + self:SetupBones(self.Owner, true) + self.Owner:MoveModifier('cuffed') + if IsValid(self.Owner:GetNetVar('dragger')) then + self.Owner:SetNetVar('dragging') + end + self.Owner:SetNetVar('dragger') + end +end + +function SWEP:Uncuff() + local ply = IsValid(self.Owner) and self.Owner + self:Remove() + if ply then ply:ConCommand('lastinv') end +end + +function SWEP:Breakout() + + if IsValid(self.Owner) then + self.Owner:EmitSound('physics/metal/metal_barrel_impact_soft4.wav') + hook.Call('OnHandcuffBreak', GAMEMODE, self.Owner, self, self.friendBreaking) + end + + self:Uncuff() +end + +function SWEP:Gag(val) + if self.CanGag then self:SetNetVar('gag', val == true or nil) end +end + +function SWEP:Blind(val) + if self.CanBlind then self:SetNetVar('blind', val == true or nil) end +end + +hook.Add('PlayerSwitchWeapon', 'dbg-cuffs', function(ply, old, new) + if new:GetClass() ~= 'weapon_cuffed' and ply:HasWeapon('weapon_cuffed') then + return false + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/shared.lua b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/shared.lua new file mode 100644 index 0000000..411e3b0 --- /dev/null +++ b/garrysmod/addons/feature-cuffs/lua/weapons/weapon_cuffed/shared.lua @@ -0,0 +1,64 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'weapon_base' + +SWEP.Category = 'Handcuffs' +SWEP.Author = 'Wani4ka' +SWEP.Instructions = 'Наслаждайся' + +SWEP.Spawnable = false +SWEP.AdminOnly = false +SWEP.AdminSpawnable = false + +SWEP.Slot = 5 +SWEP.PrintName = L.handcuffed + +SWEP.ViewModelFOV = 60 +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.WorldModel = 'models/weapons/w_toolgun.mdl' +SWEP.ViewModel = 'models/weapons/c_arms_citizen.mdl' +SWEP.UseHands = true + +SWEP.Primary.Recoil = 1 +SWEP.Primary.Damage = 5 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0 +SWEP.Primary.Delay = 1 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = 'none' +SWEP.Primary.ClipMax = -1 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' +SWEP.Secondary.ClipMax = -1 + +SWEP.DeploySpeed = 1.5 + +SWEP.PrimaryAnim = ACT_VM_PRIMARYATTACK +SWEP.ReloadAnim = ACT_VM_RELOAD +SWEP.HoldType = 'passive' + +SWEP.IsHandcuffs = true +SWEP.CuffType = '' + +-- For anything that might try to drop this +SWEP.CanDrop = false +SWEP.PreventDrop = true + +function SWEP:PrimaryAttack() end +function SWEP:SecondaryAttack() end +function SWEP:Reload() end + +function SWEP:Holster() + return false +end diff --git a/garrysmod/addons/feature-damage/lua/autorun/server/enhanceddmg.lua b/garrysmod/addons/feature-damage/lua/autorun/server/enhanceddmg.lua new file mode 100644 index 0000000..6047e1a --- /dev/null +++ b/garrysmod/addons/feature-damage/lua/autorun/server/enhanceddmg.lua @@ -0,0 +1,327 @@ +--[[ + Code is a mess, gotta fix + Todo: Make the sounds a single function, decrapify the model check. Also stop having the same piece of code multiple times, thats a bad practice +]]-- +AddCSLuaFile() + +hook.Add('Initialize', 'dbg.dmg', function() + timer.Create('damage.drown', 1, 0, function() + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + if ply:WaterLevel() == 3 then + local curScore = ply.drowningScore or 0 + if curScore >= 10 then + local dmginfo = DamageInfo() + dmginfo:SetDamage(10) + dmginfo:SetDamageType(DMG_DROWN) + dmginfo:SetAttacker(game.GetWorld()) + dmginfo:SetInflictor(game.GetWorld()) + ply:TakeDamageInfo(dmginfo) + else + ply.drowningScore = curScore + 1 + end + else + ply.drowningScore = nil + end + end) + end) + + --This is terrible but whatevs + local function BreakLeg(ply,duration) + if !GetConVar('enhanceddamage_legbreak'):GetBool() then print('TEST') return end + if !ply.legshot then + ply.legshot = true + ply:MoveModifier('dmg', { + walkmul = 0.5, + norun = true, + nojump = true, + }) + timer.Create('breakLeg_' .. ply:SteamID(), duration, 1, function() ply:MoveModifier('dmg', nil) end) + end + end + + local function FallDamage(ply,speed) + if ply:IsGhost() or ply:Team() == TEAM_ADMIN then return 0 end + local damage = speed / 7.5 + if (damage > ply:Health() / 2 and damage < ply:Health()) then + BreakLeg(ply,10) + end + ply.lastDMGT = DMG_FALL + return damage + end + + local GM = GAMEMODE or GM + function DarkRP.damageHands(ply, chance) + + if not ply:IsPlayer() or ply:Team() == TEAM_ADMIN then return false end + if math.random(100) > chance then return end + local weapon = ply:GetActiveWeapon() + if not IsValid(weapon) then return end + + if not GM.Config.DisallowDrop[weapon:GetClass()] then + if not ply:jobHasWeapon(weapon:GetClass()) then + if not weapon.NoHandDamageDrop then + local ent = ply:dropDRPWeapon(weapon) + if IsValid(ent) and weapon.IsLethal then + ent.isEvidence = true + end + end + else + ply:SelectWeapon('dbg_hands') + end + end + + ply.noPickups = true + timer.Create('resetNoPickups' .. ply:SteamID(), 30, 1, function() if IsValid(ply) then ply.noPickups = nil end end) + end + + hook.Add('octoinv.canPickup', 'dbg-damage', function(ply, ent, item) + if ply.noPickups then return false, L.hurts_hand end + end) + hook.Add('octoinv.canUse', 'dbg-damage', function(cont, item, ply) + if ply.noPickups then return false, L.hurts_hand end + end) + hook.Add('PlayerSwitchWeapon', 'dbg-damage', function(ply) + if ply.noPickups then return true, L.hurts_hand end + end) + local hitgroupNames = { + ['HITGROUP_HAND'] = 'руку', + [HITGROUP_HEAD] = 'голову', + ['HITGROUP_NUTS'] = 'голову', + [HITGROUP_LEFTLEG] = 'левую ногу', + [HITGROUP_RIGHTLEG] = 'правую ногу', + [HITGROUP_LEFTARM] = 'левую руку', + [HITGROUP_RIGHTARM] = 'правую руку', + [HITGROUP_STOMACH] = 'область живота', + [HITGROUP_CHEST] = 'область груди', + } + + local function notifyDamage(ply, hitgroup) + local hitgroupName = hitgroupNames[hitgroup] + if hitgroupName then + ply:Notify('hint', 'Тебе попали в ' .. hitgroupName) + end + end + + local function Damage(ply, hitgroup, dmginfo) + local dmgpos = dmginfo:GetDamagePosition() + + local PelvisIndx = ply:LookupBone('ValveBiped.Bip01_Pelvis') + if (PelvisIndx == nil) then return dmginfo end --Maybe Hitgroup still works, need testing + local PelvisPos = ply:GetBonePosition( PelvisIndx ) + local NutsDistance = dmgpos:DistToSqr(PelvisPos) + + local LHandIndex = ply:LookupBone('ValveBiped.Bip01_L_Hand') + local LHandPos = ply:GetBonePosition( LHandIndex ) + local LHandDistance = dmgpos:DistToSqr(LHandPos) + + local RHandIndex = ply:LookupBone('ValveBiped.Bip01_R_Hand') + local RHandPos = ply:GetBonePosition(RHandIndex) + local RHandDistance = dmgpos:DistToSqr(RHandPos) + + local LHandIndex = ply:LookupBone('ValveBiped.Bip01_L_Hand') + local LHandPos = ply:GetBonePosition( LHandIndex ) + local LHandDistance = dmgpos:DistToSqr(LHandPos) + + local RCalfIndex = ply:LookupBone('ValveBiped.Bip01_R_Calf') + local RCalfPos = ply:GetBonePosition(RCalfIndex) + local RCalfDistance = dmgpos:DistToSqr(RCalfPos) + + local LCalfIndex = ply:LookupBone('ValveBiped.Bip01_L_Calf') + local LCalfPos = ply:GetBonePosition(LCalfIndex) + local LCalfDistance = dmgpos:DistToSqr(LCalfPos) + + local HeadIndex = ply:LookupBone('ValveBiped.Bip01_Head1') + local HeadPos = ply:GetBonePosition(HeadIndex) + Vector(0,0,3) + local HeadDistance = dmgpos:DistToSqr(HeadPos) + + if (LHandDistance < 100 || RHandDistance < 100 ) then + hitgroup = 'HITGROUP_HAND' + elseif HeadDistance < 80 then + hitgroup = HITGROUP_HEAD + elseif (NutsDistance <= 49 && NutsDistance >= 25) then + hitgroup = 'HITGROUP_NUTS' + elseif LCalfDistance < 350 then + hitgroup = HITGROUP_LEFTLEG + elseif RCalfDistance < 350 then + hitgroup = HITGROUP_RIGHTLEG + end + + if (hitgroup == HITGROUP_HEAD) then + dmginfo:ScaleDamage(10) + elseif (hitgroup == HITGROUP_LEFTARM || hitgroup == HITGROUP_RIGHTARM) then + dmginfo:ScaleDamage(1) + DarkRP.damageHands(ply, 50) + elseif (hitgroup == HITGROUP_LEFTLEG || hitgroup == HITGROUP_RIGHTLEG) then + dmginfo:ScaleDamage(0.75) + if ply:IsPlayer() then BreakLeg(ply,5) end + elseif (hitgroup == HITGROUP_CHEST) then + dmginfo:ScaleDamage(3) + elseif (hitgroup == HITGROUP_STOMACH) then + dmginfo:ScaleDamage(1) + elseif (hitgroup == 'HITGROUP_NUTS') then + dmginfo:ScaleDamage(1.5) + if ply:IsPlayer() then BreakLeg(ply,5) end + elseif (hitgroup == 'HITGROUP_HAND') then + dmginfo:ScaleDamage(0.45) + DarkRP.damageHands(ply, 75) + end + + notifyDamage(ply, hitgroup) + end + + hook.Add('ScalePlayerDamage','EnhancedPlayerDamage',Damage) + hook.Add('GetFallDamage','EnhancedFallDamage',FallDamage) + + local bleeding = {} + local allowHolster = { + weapon_flashlight = true, + gmod_camera = true, + } + + timer.Create('dbg-damage.dying', 1, 0, function() + for k = #bleeding, 1, -1 do + local sid = bleeding[k] + local ply = player.GetBySteamID(sid) + + if not IsValid(ply) then + + timer.Remove('dbg-damage.dying' .. sid) + table.remove(bleeding, k) + + elseif ply:Health() > 10 or not ply:Alive() or ply:IsGhost() then + + ply:MoveModifier('bleeding', nil) + ply.bleeding = nil + timer.Remove('dbg-damage.dying' .. sid) + table.remove(bleeding, k) + + end + + end + end) + + local function dying(ply, dmgInfo) + if not IsValid(ply) or not ply:IsPlayer() or ply:IsGhost() then return end + if ply:Team() == TEAM_ADMIN then return end + if ply.bleeding then return end + local left = ply:Health() - dmgInfo:GetDamage() + if left <= 0 then return end + if left <= 10 then + local w = ply:GetActiveWeapon() + if IsValid(w) and ply:HasWeapon(w:GetClass()) and not allowHolster[w:GetClass()] and hook.Call('canDropWeapon', GM, ply, w) then + ply:dropDRPWeapon(w) + end + + ply:Notify('warning', 'Ты при смерти. Если тебе не окажут помощь, ты погибнешь') + ply.bleeding = true + bleeding[#bleeding + 1] = ply:SteamID() + ply:MoveModifier('bleeding', { + walkmul = 0.5, + norun = true, + nojump = true, + nostand = true, + }) + timer.Create('dbg-damage.dying' .. ply:SteamID(), 18, 0, function() + if not ply:IsMale() then + ply:EmitSound(Sound('vo/npc/female01/moan0' .. math.random(1,5) .. '.wav')) + else + ply:EmitSound(Sound('vo/npc/male01/moan0' .. math.random(1,5) .. '.wav')) + end + if ply:Health() <= 1 then + local dmg = DamageInfo() + dmg:SetDamage(1) + ply.attackedBy = ply.lastAttacker + if ply.lastDMGT then + dmg:SetDamageType(ply.lastDMGT) + end + ply.weaponUsed = ply.lastWeapon + ply:TakeDamageInfo(dmg) + else ply:SetHealth(ply:Health() - 1) end + end) + end + end + local function cant(ply) + if ply.bleeding then return false, 'Ты при смерти' end + end + + hook.Add('EntityTakeDamage', 'dbg-damage.dying', dying) + hook.Add('CanPlayerEnterVehicle', 'dbg-damage.dying', cant) + hook.Add('octoinv.canPickup', 'dbg-damage.dying', cant) + hook.Add('octoinv.canUse', 'dbg-damage.dying', cant) + hook.Add('dbg-hands.canPunch', 'dbg-damage.dying', cant) + hook.Add('dbg-hands.canCloseLockable', 'dbg-damage.dying', cant) + hook.Add('dbg-hands.canOpenLockable', 'dbg-damage.dying', cant) + hook.Add('dbg-hands.canDrag', 'dbg-damage.dying', cant) + + hook.Add('PlayerDisconnected', 'dbg-damage.dying', function(ply) + if ply.bleeding then + local dmg = DamageInfo() + dmg:SetDamage(ply:GetMaxHealth()) + ply.attackedBy = ply.lastAttacker + if ply.lastDMGT then + dmg:SetDamageType(ply.lastDMGT) + end + ply.weaponUsed = ply.lastWeapon + ply:TakeDamageInfo(dmg) + + local sid = ply:SteamID() + timer.Remove('dbg-damage.dying' .. sid) + table.RemoveByValue(bleeding, sid) + + octodeath.triggerDeath(ply) + end + end) +end) + +netstream.Hook('dbg-armor.unwear', function(ply) + if not ply:Alive() then return end + local data = ply.armorItem + if not data then + ply:Notify('warning', 'У тебя нет надетого бронежилета') + return + end + if data.armor ~= ply:Armor() then + ply:Notify('warning', 'Твой бронежилет поврежден') + return + end + local inv = ply:GetInventory() + local cont = inv and inv:GetContainer('_hand') + if not cont then + ply:Notify('warning', 'Освободи руки, чтобы туда можно было положить бронежилет') + return + end + if cont:AddItem('armor', data) >= 1 then + ply:SetArmor(0) + ply.armorItem = nil + ply:SetLocalVar('armor', nil) + ply:EmitSound('npc/combine_soldier/gear3.wav', 55) + else + ply:Notify('warning', 'В руках недостаточно места') + end +end) + +CreateConVar('enhanceddamage_enabled', 1, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable enhanced damage') + +CreateConVar('enhanceddamage_headdamagescale', 2, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') +CreateConVar('enhanceddamage_armdamagescale', 0.50, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') +CreateConVar('enhanceddamage_legdamagescale',0.50, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') +CreateConVar('enhanceddamage_chestdamagescale', 1.25, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') +CreateConVar('enhanceddamage_stomachdamagescale',0.75, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') +CreateConVar('enhanceddamage_nutsdamagescale', 2, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') +CreateConVar('enhanceddamage_handdamagescale', 0.25, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') + +CreateConVar('enhanceddamage_armdropchance',20, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'The weapon drop chance for ') +CreateConVar('enhanceddamage_handdropchance', 40, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Change the scale for this bodypart') + +CreateConVar('enhanceddamage_enablesounds', 1, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable the sounds when hurt ') + +CreateConVar('enhanceddamage_legbreak', 1, {FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable enhanced damage') +CreateConVar('enhanceddamage_npcweapondrop',1,{FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable weapon dropping for npcs (Really buggy)') +CreateConVar('enhanceddamage_falldamage',1,{FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable enhanced falldamage (Much more "realistic" and breaks your bones)') +CreateConVar('enhanceddamage_npcfalldamage',1,{FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable falldamage for NPC') +CreateConVar('enhanceddamage_drowningdamage',1,{FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Toggle drowning') + + +CreateConVar('enhanceddamage_ragdolls',0,{FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Enable enhanced ragdolls.') +CreateConVar('enhanceddamage_autoremoveragdolls',20,{FCVAR_SERVER_CAN_EXECUTE,FCVAR_NOTIFY,FCVAR_ARCHIVE},'Time before the ragdolls are remove (0 for never)') diff --git a/garrysmod/addons/feature-drugs/lua/autorun/client/cl_drugs.lua b/garrysmod/addons/feature-drugs/lua/autorun/client/cl_drugs.lua new file mode 100644 index 0000000..5d81ac1 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/autorun/client/cl_drugs.lua @@ -0,0 +1,140 @@ +local Mybuffs = {} + +surface.CreateFont( "Drugz_Font1", { font = "Trebuchet", size = 16, weight = 800, antialias = true } ) + +net.Receive( "UpdateBuffs", function( length ) -- this net message is recieved once per frame +Mybuffs = net.ReadTable() +end) + +net.Receive( "SendBuffs", function( length ) +local fag = net.ReadEntity() +local drugtab = net.ReadTable() +local rekt = false +if table.ToString(drugtab, false) == "{}" then chat.AddText( Color(0,205,50), L.clear_drug:format(fag:Nick()) ) return end +chat.AddText( Color(0,205,50), L.on_drugs:format(fag:Nick()) ) + + for k, v in pairs(drugtab) do + local ref = Drugmod_Buffs[k] + if ref.Illegal then chat.AddText( Color(255,0,0), k ) rekt = true else chat.AddText( Color(0,155,150), k ) end + end + +if rekt then chat.AddText( Color(255,0,0), L.illegal_drugs_hint ) else chat.AddText( Color(0,205,50), L.not_found_illegal_drugs_hint ) end + +end) + +local dividebuffs = 50 +local startstack = (ScrH() / 2) + +local function DrawBuffs() + +local grad = Material( "gui/gradient" ) +local i = 1 +for k, v in pairs(Mybuffs) do + if not Drugmod_Buffs[k] then continue end + i = i + 1 + local dcol = Drugmod_Buffs[k].Col + surface.SetDrawColor(Color(30, 30, 30, 255)) + surface.DrawRect( 0, startstack - ( i * dividebuffs ) + 8, 205, 45 ) + surface.SetDrawColor(Color(dcol.r, dcol.g, dcol.b, 100)) + surface.SetMaterial( grad ) + surface.DrawTexturedRect( 205, startstack - ( i * dividebuffs ) + 8, 30, 45 ) +-- surface.DrawRect( 200, startstack - ( i * dividebuffs ) + 8, 5, 45 ) + surface.DrawRect( 0, startstack - ( i * dividebuffs ) + 8, 205, 2 ) + draw.SimpleText( Drugmod_Buffs[k].ItemName, "Trebuchet18", 5, startstack - ( (i * dividebuffs) - 12 ), Drugmod_Buffs[k].Col, 0, 0 ) + draw.SimpleText( math.Round(math.Clamp(v - CurTime(), 1, v - CurTime() + 1)), "Trebuchet18", 175, startstack - ( (i * dividebuffs) - 12 ), Color(255,255,255, 255), 0, 0 ) +-- draw.SimpleText( Drugmod_Buffs[k].Description, "PopuliHUD2", 5, ScrH() / 2 - ( (i * 30) - 30 ), Color(200,200,200,255), 0, 0 ) + draw.DrawText( Drugmod_Buffs[k].Description, "Trebuchet18", 5, startstack - ( (i * dividebuffs) - 30 ), Color(200,200,200,255) ) +end + +end +--hook.Add("HUDPaint", "drawmybuffs", DrawBuffs) + +hook.Add("RenderScreenspaceEffects", "test", function() +for k, v in pairs(Mybuffs) do + if !Drugmod_Buffs[k] then continue end + local tbl = Drugmod_Buffs[k] + if tbl.ColorModify then DrawColorModify( tbl.ColorModify ) end + if tbl.MotionBlur then DrawMotionBlur( tbl.MotionBlur[1], tbl.MotionBlur[2], tbl.MotionBlur[3] ) end + if tbl.SobelEffect then DrawSobel( tbl.SobelEffect ) end + if tbl.Sharpen then DrawSharpen( tbl.Sharpen[1], tbl.Sharpen[2] ) end +-- if tbl.Bloom then DrawBloom( 0.35, 2, 9, 9, 1, 1, 1, 1, 1 ) end + + if k == "Drunk" then + local dmul = (v - CurTime()) / 90 + DrawMotionBlur( (dmul / 8), (dmul / 2), 0.01 ) + end + + if k == "Pingaz" then + local dmul = (v - CurTime()) / 180 + local bmul = (math.abs(math.sin(RealTime() * math.pi * 1.5))) / 3 + DrawBloom( (1 - bmul) * (1.5 - dmul), 2, 9, 9, 1, 1, 1, 1, 1 ) +-- DrawMotionBlur( (dmul / 8), (dmul / 2), 0.01 ) + end + +end + +end) + + +--[[ hook.Add("HUDPaint", "DrawDrugs", function() + + local tr = LocalPlayer():GetEyeTrace() + if !tr.Entity:IsValid() or !tr.Entity.DrugDescription or LocalPlayer():GetPos():Distance( tr.Entity:GetPos() ) > 200 then return end + local ent = tr.Entity + + local p = ent:GetPos():ToScreen() + surface.SetFont( "Default" ) + local offx, offy = surface.GetTextSize(ent.DrugDescription) + if !ent.DrugLegal then offy = offy + 15 end + + surface.SetDrawColor(Color(30,30,30, 180)) + surface.DrawRect( (p.x - 5) - (offx/2), p.y - 15, offx + 10, offy + 20 ) + + draw.SimpleText(ent.PrintName,"Drugz_Font1",p.x,p.y - 8 , ent.DrugColor,1, 1) + if !ent.DrugLegal then draw.SimpleText(L.illegal,"Drugz_Font1",p.x,(p.y + offy) - 6 , Color(255,0,0),1, 1) end + + draw.DrawText(ent.DrugDescription,"Default",p.x,p.y,Color(255,255,255, 150),1) + +end) + ]] + +local smoothing +local Speedmul, SmoothHorizontal, SmoothVertical = 0,0,0 +local count = 0 +local sign +local side +local value +local swayspeed = 0.02 + +timer.Create( "DM_viewswayiterate", 0.01, 0, function() + if !LocalPlayer():IsValid() or !Mybuffs["Drunk"] or !LocalPlayer():Alive() then return end + Speedmul = swayspeed * 8 + count = count + ( swayspeed * 11 ) * Speedmul +-- smoothing = Lerp(0.01 * 5, smoothing, ( fov2 + Speedmul * 2 )) + SmoothHorizontal = -math.abs( math.sin(count) * 1 ) + SmoothVertical = math.sin(count)*1.5 +end) + +function Drunkview( ply, pos, ang, fov ) + if ply:InVehicle() or ply:GetObserverMode() != OBS_MODE_NONE then return end + if !Mybuffs["Drunk"] then return end +-- if LocalPlayer():GetActiveWeapon().Base == "cw_base" then return end + + local view = dbgView.calcView(ply, pos, ang, fov) + +-- if not smoothing then smoothing = fov end + + local dmul = (Mybuffs["Drunk"] - CurTime()) / 90 + local ang = view.angles + + ang:RotateAroundAxis(ang:Right(), SmoothHorizontal * dmul) + ang:RotateAroundAxis(ang:Up(), (SmoothVertical * 0.5) * dmul) + ang:RotateAroundAxis(ang:Forward(), (SmoothVertical * 2) * dmul ) + + view.angles = ang + view.fov = view.fov + (-SmoothVertical * 2) * dmul + + return view +end + +hook.Add( "CalcView", "DM_Drunkview", Drunkview, -5 ) diff --git a/garrysmod/addons/feature-drugs/lua/autorun/server/drugz_resource.lua b/garrysmod/addons/feature-drugs/lua/autorun/server/drugz_resource.lua new file mode 100644 index 0000000..37a66ff --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/autorun/server/drugz_resource.lua @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + +--SERVER HOSTERS: Delete the lineeeee below if you don't want to make people have to download the DurgzMod models (or just put // in front of local). +local DURGZ_ADD_FILES = true; +--Remove the two slashes from the line below if you want the people joining your server the download that HUGE happy face that goes across your screen when you take mushrooms. This is NOT reccommended because the file is really big and it would probably add a good 1-2 minutes to the delay the person joining. +local ADD_AWESOME_FACE = true; +--Delete the line below if you don't want to make people have to download the spawn icons. (or just put // in front of local). +local ADD_SPAWN_ICONS = true; +--Delete the line below if you don't want to make people download sounds (or comment out with // or --). +local ADD_SOUNDS = true; + + + + + + + + + + + + + + + + + + + +--Ignore this stuff if you don't Lua code. + + +local function AddSIcon(materials) + for k,v in pairs(materials)do + resource.AddFile("materials/vgui/entities/"..v..".vmt"); + resource.AddFile("materials/vgui/entities/"..v..".vtf"); + end +end + +local function r(materials, models) + if(materials)then + for k,v in pairs(materials)do + resource.AddFile("materials/"..v); + end + end + if(models)then + for k,v in pairs(models)do + resource.AddFile("models/"..v); + end + end +end + +local function syringeMats(mats) + for k,v in pairs(mats)do + resource.AddFile("materials/katharsmodels/syringe_out/syringe_"..v); + end +end + +local function addShib(mat, modl) + for k,v in pairs(mat)do + resource.AddFile("materials/models/shibboro/"..v..".vmt") + resource.AddFile("materials/models/shibboro/"..v..".vtf") + end + + for k,v in pairs(modl)do + resource.AddFile("models/"..v..".mdl"); + end + +end + +local function addKillicon(mat) + for k,v in pairs(mat)do + resource.AddFile("materials/killicons/durgz_"..v.."_killicon.vmt"); + resource.AddFile("materials/killicons/durgz_"..v.."_killicon.vtf"); + end +end + +local function AddFiles() + syringeMats( + { + "body.vmt", + "grip.vmt", + "liquid.vmt", + "lowerstopper.vmt", + "needle.vmt", + "stopper.vmt", + "tip.vmt", + "body.vtf", + "body_mask.vtf", + "grip.vtf", + "liquid.vtf", + "lowerstopper.vtf", + "needle.vtf", + "stopper.vtf", + "tip.vtf" + } + ) + + r( + { + /*"models/shibcoffee/Cup.vmt", + "models/shibcoffee/Cup.vtf", + "models/shibcoffee/CupHOLD.vmt", + "models/shibcoffee/CupHOLD.vtf", + "models/shibcoffee/Holder.vmt", + "models/shibcoffee/Holder.vtf",*/ + "models/marioragdoll/Super Mario Galaxy/star/starSS01.vmt", + "models/marioragdoll/Super Mario Galaxy/star/starSS01.vtf", + "models/marioragdoll/Super Mario Galaxy/star/starSS06.vmt", + "models/marioragdoll/Super Mario Galaxy/star/starSS06.vtf", + "models/marioragdoll/Super Mario Galaxy/star/yellow.vmt", + "models/marioragdoll/Super Mario Galaxy/star/yellow.vtf", + /*"neodement/ecstasy_bx.vmt", + "neodement/ecstasy_bx.vtf", + "neodement/ecstasy_bx_flake.vmt", + "neodement/ecstasy_bx_flake.vtf",*/ + "katharsmodels/contraband/contraband_normal.vtf", + "katharsmodels/contraband/contraband_one.vmt", + "katharsmodels/contraband/contraband_one.vtf", + "katharsmodels/contraband/contraband_two.vmt", + "ipha/mushd.vmt", + "ipha/mushd.vtf", + "models/drug/drug.vmt", + "models/drug/drug.vtf", + "models/druggg_mod/PopCan01a.vmt", + "models/druggg_mod/PopCan01a.vtf", + "jaanus/aspbtl_a.vmt", + "jaanus/aspbtl_a.vtf", + "jaanus/aspirin_.vtf", + "jaanus/aspirin_.vmt", + "models/drug/waterbottl/water_bottle.vmt", + "models/drug/waterbottl/water_bottle.vtf", + "models/drug/waterbottl/water_bottle_ref.vtf", + "smile/smile.vmt", + "smile/smile.vtf" + }, + + { + "cocn.mdl", + "shibcuppyhold.mdl", + "ipha/mushroom_small.mdl", + "drug_mod/alcohol_can.mdl", + "drug_mod/the_bottle_of_water.mdl", + "katharsmodels/contraband/zak_wiet/zak_wiet.mdl", + "katharsmodels/syringe_out/syringe_out.mdl", + "jaanus/aspbtl.mdl", + "smile/smile.mdl", + /*"drug_mod/ecstasy_crl.mdl",*/ + "marioragdoll/Super Mario Galaxy/star/star.mdl" + } + + + ) + + + addShib( + { + "cigsshib", + "openyesshib" + }, + + { + "boxopencigshib", + "pissedmeoff" + } + ) + + + +end + +local function AddFilesNoExceptions() + if( ADD_SOUNDS ) then + resource.AddFile("sound/drugs/insufflation.wav"); + end + + if( ADD_AWESOME_FACE )then + r({"vgui/durgzmod/awesomeface.vmt", "vgui/durgzmod/awesomeface.vtf"}); + end + + if( ADD_SPAWN_ICONS )then + AddSIcon( + { + "durgz_cigarette", + "durgz_cocaine", + "durgz_weed", + "durgz_lsd", + "durgz_mushroom", + "durgz_heroine", + "durgz_water", + "durgz_aspirin", + /*"durgz_caffeine", + "durgz_ecstasy",*/ + "durgz_meth", + "durgz_pcp", + "durgz_alcohol"//, + //"durgz_opium" + } + ) + end + + AddSIcon({"weapon_durgz"}); + + r( + { + "highs/shader3.vtf", + "highs/shader3.vmt", + "highs/shader3_dudv.vtf", + "highs/shader3_dudv.vmt", + "highs/shader3_normal.vtf", + "highs/shader3_normal.vmt", + //"highs/ecstasy_smile.vtf", + //"highs/ecstasy_smile.vmt", + "highs/invuln_overlay_normal.vtf", + "highs/invuln_overlay_blue.vmt", + "highs/invulnoverlay/invuln_overlay.vtf", + } + ) + + + local drugs = { + "weed", + "cocaine", + "cigarette", + "alcohol", + "mushroom", + "meth", + /*"ecstasy", + "caffeine",*/ + "pcp", + "lsd"//, + //"opium" + } + addKillicon(drugs) + +end + +if( DURGZ_ADD_FILES )then + AddFiles() +end +AddFilesNoExceptions() +-- game.ConsoleCommand("sv_tags durgzmod2.2\n") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/autorun/server/sv_drugs.lua b/garrysmod/addons/feature-drugs/lua/autorun/server/sv_drugs.lua new file mode 100644 index 0000000..f486ee5 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/autorun/server/sv_drugs.lua @@ -0,0 +1,224 @@ +util.AddNetworkString("UpdateBuffs") +util.AddNetworkString("SendBuffs") + + +--Drugmod_Buffs = {} + +-- this is c+p from my populi gamemode, please for the love of god do not run this addon on a populi server or you'll screw everything up +local plymeta = FindMetaTable("Player") + +function plymeta:AddBuff( buff, duration ) + + if !self:IsValid() or !self:Alive() then return end + if !Drugmod_Buffs[buff] then ErrorNoHalt( "Drugmod Error: attempting to give "..self:Nick().." an invalid drug effect: "..buff.."\n" ) return end + + if self.Buffs[buff] then + self.Buffs[buff] = math.Clamp(self.Buffs[buff] + duration, CurTime(), CurTime() + Drugmod_Buffs[buff].MaxDuration ) + else + self.Buffs[buff] = math.Clamp(CurTime() + duration, CurTime(), CurTime() + Drugmod_Buffs[buff].MaxDuration ) + if Drugmod_Buffs[buff].InitializeOnce then + local s, e = pcall( Drugmod_Buffs[buff].InitializeOnce, self ) + if !s then print("Drugmod InitializeOnce error in buff: "..k.." on player "..ply:Nick().." : "..e) end + end + end + + self.OldBuffs = self.OldBuffs or {} + self.OldBuffs[buff] = self.Buffs[buff] + 20 * 60 + + local s, e = pcall( Drugmod_Buffs[buff].Initialize, self ) + if !s then print("Drugmod Initialize error in buff: "..k.." on player "..ply:Nick().." : "..e) end + + self:CheckOverdose() + + net.Start("UpdateBuffs") + net.WriteTable(self.Buffs) + net.Send(self) + +end + +function plymeta:RemoveBuff( buff ) + + if !self:IsValid() or !self:Alive() then return end + if !Drugmod_Buffs[buff] then ErrorNoHalt( "Drugmod Error: attempting to remove invalid buff from "..self:Nick().."\n" ) return end + + if self.Buffs[buff] then self.Buffs[buff] = nil end + + net.Start("UpdateBuffs") + net.WriteTable(self.Buffs) + net.Send(self) + +end + +function plymeta:ClearBuffs() + + if self.Buffs then + for k,v in pairs(self.Buffs) do + local s, e = pcall(Drugmod_Buffs[k].Terminate, self) -- run the end of buff function + if not s then print("Drugmod terminate error in buff: "..k.." on player "..self:Nick().." : "..e) end + end + end + self.Buffs = {} + self.OldBuffs = {} + net.Start("UpdateBuffs") + net.WriteTable(self.Buffs) + net.Send(self) + +end + +function plymeta:HasBuff( name ) + if self.Buffs[name] then return true else return false end +end + +function plymeta:CountBuffs() + return table.Count(self.Buffs) +end + +function plymeta:CheckOverdose() + if Drugmod_Config["Overdose Threshold"] < 1 then return end + if self:CountBuffs() > Drugmod_Config["Overdose Threshold"] then + self:ClearBuffs() + self:AddBuff( "Overdose", 30 ) + end +end + +hook.Add("PlayerDeath", "NoFunAllowedAfterYouDie", function( ply ) ply:ClearBuffs() end) +hook.Add("PlayerSpawn", "CopyPastedHook1", function( ply ) ply:ClearBuffs() end) +hook.Add("PlayerInitialSpawn", "CopyPastedHook2", function( ply ) ply:ClearBuffs() end) + +local function BuffsLogic() + + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + if not ply.Buffs then + ply.Buffs = {} + doUpdate = true + end + + local doUpdate = false + for k, v in pairs(ply.Buffs) do + if isfunction(Drugmod_Buffs[k].Iterate) then + local s, e = pcall( Drugmod_Buffs[k].Iterate, ply, v - CurTime() ) + if not s then print("Drugmod iterate error in buff: "..k.." on player "..ply:Nick().." : "..e) end + end + + if v <= CurTime() then + ply.Buffs[k] = nil -- delete the buff from their active buffs table + doUpdate = true + local s, e = pcall( Drugmod_Buffs[k].Terminate, ply ) -- run the end of buff function + if not s then print("Drugmod terminate error in buff: "..k.." on player "..ply:Nick().." : "..e) end + end + end + + if ply.OldBuffs then + for k, v in pairs(ply.OldBuffs) do + if v <= CurTime() then + ply.OldBuffs[k] = nil + end + end + end + + if doUpdate then + net.Start("UpdateBuffs") + net.WriteTable(ply.Buffs) + net.Send(ply) + end + end) + +end +timer.Create("drugmod_bufflogic", 3, 0, BuffsLogic) + +hook.Add("SetupMove", "DoubleJumpHook", function(ply, cmd) + + if !ply:HasBuff( "DoubleJump" ) then return end + + if ply:OnGround() then ply:SetNetVar("jumplevel", 0) return end + if !cmd:KeyPressed(IN_JUMP) then return end + + ply:SetNetVar("jumplevel", ply:GetNetVar("jumplevel", 0) + 1 ) + + if ply:GetNetVar("jumplevel", 0) > 1 then return end + + local vel = ply:GetVelocity() + + vel.z = ply:GetJumpPower() + + cmd:SetVelocity(vel) + + ply:DoCustomAnimEvent(PLAYERANIMEVENT_JUMP, -1) + +end) + +local function DMOD_TakeDamage( ent, dmg ) + if ent:IsPlayer() then + local atk = dmg:GetAttacker() + local amt = dmg:GetDamage() + local hp = ent:Health() + + if atk:IsValid() and atk:IsPlayer() and atk:Alive() then + + if atk:HasBuff( "Gunslinger" ) and dmg:IsBulletDamage() then + dmg:ScaleDamage( 1.25 ) + end + + if atk:HasBuff( "Meth" ) and atk:GetActiveWeapon():GetClass() == "weapon_fists" then + dmg:ScaleDamage( 2.5 ) + end + + if atk:HasBuff( "Vampire" ) then + atk:SetHealth(math.Clamp(atk:Health() + amt / 4, 0, atk:GetMaxHealth() ) ) + end + + if ent:HasBuff( "Painkillers" ) and !dmg:IsFallDamage() and dmg:GetDamageType() != DMG_DROWN then + dmg:ScaleDamage( 0.8 ) + end + + if ent:HasBuff( "Drunk" ) and !dmg:IsFallDamage() and dmg:GetDamageType() != DMG_DROWN then + dmg:ScaleDamage( 0.9 ) + end + + end + + if dmg:IsFallDamage() and ent:HasBuff( "Muscle Relaxant" ) then + dmg:ScaleDamage( 0.5 ) + end + + if ent:HasBuff( "Preserver" ) and hp - amt < 1 then + ent:EmitSound( "items/gift_drop.wav", 80, 100 ) + dmg:ScaleDamage( 0 ) + dmg:SetDamage( 0 ) + ent:SetHealth( 25 ) + ent:RemoveBuff( "Preserver" ) + + for i = 1, 6 do + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() + Vector( math.random( -10, 10), math.random( -10, 10), math.random( 0, 60) ) ) + util.Effect( "cball_bounce", effectdata ) + end + + end + + end +end +hook.Add("EntityTakeDamage", "DMOD_TakeDamage", DMOD_TakeDamage) + +hook.Add( "OnPlayerChangedTeam", "DM_fixjobchange", function(ply) ply:ClearBuffs() end) + +hook.Add('PlayerSay', 'zzzdrug-booze', function(ply, text, t) + + if ply:HasBuff('Drunk') then + if ply:GetInfo('cl_dbg_alcohol_effect') ~= '1' then return end + if ply.sayOverride or text:sub(1,1):find('[%!%~%/]') then return end + local r = '' + for c in string.gmatch(text, utf8.charpattern) do + if math.random(5) == 1 and not c:find('%p') then + for i = 1, math.random(2,4) do + r = r .. (math.random(3) == 1 and utf8.upper(c) or utf8.lower(c)) + end + else + r = r .. c + end + end + return r:gsub('%s+', ' ') + end + +end) diff --git a/garrysmod/addons/feature-drugs/lua/autorun/sh_drugs.lua b/garrysmod/addons/feature-drugs/lua/autorun/sh_drugs.lua new file mode 100644 index 0000000..c0a446e --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/autorun/sh_drugs.lua @@ -0,0 +1,266 @@ +Drugmod_Config = { + ["Overdose Threshold"] = 3, -- more than 3 active drugs will cause you to overdose and die, set to 0 for unlimited drug effects +} + +Drugmod_Buffs = { + + ["Weed"] = { + ItemName = L.marijuana, + Description = "Duuuuude", + Col = Color(155, 255, 155), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) ply:SetHealth(math.Clamp(ply:Health() + 3, 0, ply:GetMaxHealth() ) ) end, + ColorModify = { + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0, + ["$pp_colour_addb"] = 0, + ["$pp_colour_brightness"] = 0, + ["$pp_colour_contrast"] = 1.5, + ["$pp_colour_colour"] = 1.5, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }, + Illegal = true, +-- SobelEffect = 20, + }, + + ["HealthRecovery"] = { + ItemName = L.healthrecovery, + Description = "You feel healthy!", + Col = Color(55, 255, 55), + MaxDuration = 60, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) ply:SetHealth(math.Clamp(ply:Health() + 3, 0, ply:GetMaxHealth() ) ) end, + }, + + ["DoubleJump"] = { + ItemName = L.cocaine, + Description = "You can jump again in midair", + Col = Color(255, 255, 55), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + }, + + ["Volatile"] = { + ItemName = L.volatile, + Description = "Explode upon death", + Col = Color(255, 85, 55), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) if ply:Health() < 2 then ply:Kill() else ply:SetHealth(math.Clamp(ply:Health() - 2, 0, ply:GetMaxHealth() ) ) end end, + Illegal = true, + }, + + ["Dextradose"] = { + ItemName = L.dextradose, + Description = "40% faster lockpicking", + Col = Color(155, 55, 155), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + Illegal = true, + }, + + ["Steroids"] = { + ItemName = L.roids, + Description = "20% faster run speed", + Col = Color(255, 125, 125), + MaxDuration = 180, + Initialize = function(ply) end, + InitializeOnce = function(ply) ply:MoveModifier('drug2', { runmul = 1.2 }) end, + Terminate = function(ply) ply:MoveModifier('drug2', nil) end, + Iterate = function( ply, duration ) end, + Illegal = true, + }, + + ["Vampire"] = { + ItemName = L.vampire, + Description = "Leech enemies life force", + Col = Color(195, 55, 55), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + Illegal = true, + }, + + ["Painkillers"] = { + ItemName = L.painkiller, + Description = "20% damage resist", + Col = Color(205, 205, 255), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + }, + + ["Drunk"] = { + ItemName = L.drunk, + Description = "You are having a gooood time", + Col = Color(55, 55, 255), + MaxDuration = 180, + Initialize = function(ply) ply:SetNetVar('Drunk', true) end, + Terminate = function(ply) ply:SetNetVar('Drunk') end, + Iterate = function( ply, duration ) end, + }, + + ["Gunslinger"] = { + ItemName = L.gunslinger, + Description = "25% increased gun damage", + Col = Color(255, 155, 55), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + Illegal = true, + }, + + ["Muscle Relaxant"] = { + ItemName = L.relaxant, + Description = "Greatly reduced impact damage", + Col = Color(255, 155, 255), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + }, + + ["Meth"] = { + ItemName = L.steroids, + Description = "Fists of fury!", + Col = Color(5, 185, 245), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + ColorModify = { + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0, + ["$pp_colour_addb"] = 0.1, + ["$pp_colour_brightness"] = 0, + ["$pp_colour_contrast"] = 1.3, + ["$pp_colour_colour"] = 1, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }, + MotionBlur = { 0.3, 0.8, 0.01 }, + Illegal = true, + }, + + ["Pingaz"] = { + ItemName = L.pingaz, + Description = "Get your bounce on", + Col = Color(255, 225, 5), + MaxDuration = 180, + Initialize = function(ply) + local playerClass = baseclass.Get(player_manager.GetPlayerClass(ply)) + ply:SetJumpPower(playerClass.JumpPower * 1.5) + end, + Terminate = function(ply) + local playerClass = baseclass.Get(player_manager.GetPlayerClass(ply)) + ply:SetJumpPower(playerClass.JumpPower) + end, + Iterate = function( ply, duration ) end, + ColorModify = { + ["$pp_colour_addr"] = 0.1, + ["$pp_colour_addg"] = 0, + ["$pp_colour_addb"] = 0.1, + ["$pp_colour_brightness"] = 0, + ["$pp_colour_contrast"] = 1.5, + ["$pp_colour_colour"] = 2, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }, + Sharpen = { 1.2, 1.2 }, + Illegal = true, + }, + + ["Preserver"] = { + ItemName = L.preserver, + Description = "Save yourself from lethal damage", + Col = Color(150, 255, 195), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + }, + + ["Overdose"] = { + ItemName = L.overdose, + Description = "Now you fucked up!", + Col = Color(255, 0, 0), + MaxDuration = 180, + Initialize = function(ply) ply:EmitSound("vo/npc/male01/moan0"..math.random(1,5)..".wav") end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) + local d = DamageInfo() + d:SetDamage( 5 ) + d:SetDamageType( DMG_PARALYZE ) + d:SetAttacker( game.GetWorld() ) + d:SetInflictor( game.GetWorld() ) + ply:TakeDamageInfo( d ) + if math.random(1,10) > 8 then ply:EmitSound("vo/npc/male01/moan0"..math.random(1,5)..".wav") end + end, + ColorModify = { + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0, + ["$pp_colour_addb"] = 0, + ["$pp_colour_brightness"] = 0, + ["$pp_colour_contrast"] = 1, + ["$pp_colour_colour"] = 0.2, + ["$pp_colour_mulr"] = 5, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }, + MotionBlur = { 0.4, 0.8, 0.01 }, + }, + + ["Nicotine"] = { + ItemName = L.nicotine, + Description = "You feel relaxed", + Col = Color(80, 80, 80), + MaxDuration = 180, + Initialize = function(ply) end, + Terminate = function(ply) end, + Iterate = function( ply, duration ) end, + ColorModify = { + ["$pp_colour_addr"] = 0, + ["$pp_colour_addg"] = 0, + ["$pp_colour_addb"] = 0, + ["$pp_colour_brightness"] = 0, + ["$pp_colour_contrast"] = 0.95, + ["$pp_colour_colour"] = 0.95, + ["$pp_colour_mulr"] = 0, + ["$pp_colour_mulg"] = 0, + ["$pp_colour_mulb"] = 0 + }, + Sharpen = { 1.1, 1.1 }, + }, + + +} + +local function DMOD_DoPlayerDeath( ply, attacker, dmg ) + if ply:HasBuff( "Volatile" ) then + local pos = ply:GetPos() + timer.Simple( 0.3, function() + util.BlastDamage( ply, ply, pos, 350, 125 ) + + local effectdata = EffectData() + effectdata:SetOrigin(pos) + util.Effect("Explosion", effectdata) + ply:ClearBuffs() + end) + end +end +hook.Add("DoPlayerDeath", "DMOD_DoPlayerDeath", DMOD_DoPlayerDeath) diff --git a/garrysmod/addons/feature-drugs/lua/effects/durgz_weed_smoke/init.lua b/garrysmod/addons/feature-drugs/lua/effects/durgz_weed_smoke/init.lua new file mode 100644 index 0000000..9e4d176 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/effects/durgz_weed_smoke/init.lua @@ -0,0 +1,49 @@ +local function randn(x) + return math.Rand(-x, x); +end +function EFFECT:Init(data) + local e = ParticleEmitter( data:GetOrigin() ); + for i=1, 10 do + --declare variablez + local smokesize = 1; + local pos = Vector(randn(1), randn(1), randn(smokesize) + 60); + local p = e:Add( "particle/particle_smokegrenade", data:GetOrigin() + pos ); + if (p) then + local gravsideways = randn(0.1); + local shade = math.random(220,240); + --set the stuff + p:SetVelocity(VectorRand() * math.Rand(2000,2300)); + + p:SetLifeTime(0); + p:SetDieTime(math.Rand(3,4)); + + p:SetColor(shade,shade,shade); + p:SetStartAlpha(math.Rand(160,180)); + p:SetEndAlpha(0); + + p:SetStartSize(math.Rand(20,25)); + p:SetEndSize(math.Rand(10, 15)); + + p:SetRoll(math.Rand(-180, 180)); + p:SetRollDelta(math.Rand(-0.2,0.2)); + + p:SetAirResistance(math.Rand(520,620)); + p:SetGravity( Vector( gravsideways, gravsideways, math.Rand(-60, -80) ) ); + + p:SetCollide( true ); + p:SetBounce( 0.42 ); + + p:SetLighting(1); + end + end + + e:Finish() + +end + +function EFFECT:Think( ) + return false +end +function EFFECT:Render() +end + diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_antitoxin.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_antitoxin.lua new file mode 100644 index 0000000..e50585e --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_antitoxin.lua @@ -0,0 +1,58 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.antitoxin -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_lab/jar01b.mdl" +ENT.DrugModelColor = Color(95,155,95) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "" -- what effect it gives you +ENT.DrugTime = 0 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(95,155,95) -- the colour of the title text +ENT.DrugDescription = L.description_antitoxin -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) + activator:ClearBuffs() + activator:EmitSound(self.DrugSound, 75, 100) + activator:MoveModifier('drug', nil) + activator:MoveModifier('drug2', nil) + self:Remove() +end + + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_booze.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_booze.lua new file mode 100644 index 0000000..aa2c310 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_booze.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.drugbooze -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_junk/garbage_glassbottle002a.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Drunk" -- what effect it gives you +ENT.DrugTime = 240 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(55, 55, 255) -- the colour of the title text +ENT.DrugDescription = L.description_drugbooze -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_booze2.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_booze2.lua new file mode 100644 index 0000000..5baf210 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_booze2.lua @@ -0,0 +1,58 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.drugbooze2 -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_junk/GlassBottle01a.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Drunk" -- what effect it gives you +ENT.DrugTime = 480 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(85, 85, 255) -- the colour of the title text +ENT.DrugDescription = L.description_drugbooze2 -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +activator:SetHealth(math.Clamp(activator:Health() + 5, 0, activator:GetMaxHealth() ) ) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_bouncer.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_bouncer.lua new file mode 100644 index 0000000..77c871d --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_bouncer.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.cocaine -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/cocn.mdl" +ENT.DrugModelColor = Color(255,255,55) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "DoubleJump" -- what effect it gives you +ENT.DrugTime = 80 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(255,255,55) -- the colour of the title text +ENT.DrugDescription = L.description_cocaine -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_ciggies.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_ciggies.lua new file mode 100644 index 0000000..8077960 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_ciggies.lua @@ -0,0 +1,63 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.cigarettes -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/boxopencigshib.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Nicotine" -- what effect it gives you +ENT.DrugTime = 60 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(25,25,25) -- the colour of the title text +ENT.DrugDescription = L.description_cigarettes -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +if activator.bleeding then + activator:Notify('warning', 'Ты при смерти') + return +end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:SetHealth(math.Clamp(activator:Health() + 10, 0, activator:GetMaxHealth() ) ) +for i = 1, 3 do timer.Simple( (i * 0.8) - 0.8, function() if activator:IsValid() then activator:EmitSound("", 100, 235) end end) end +timer.Simple( 1.8, function() if activator:IsValid() then activator:EmitSound("player/suit_sprint.wav", 100, 40) end end) +self:Remove() +end + + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_dextradose.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_dextradose.lua new file mode 100644 index 0000000..6ca75b3 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_dextradose.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.dextradose -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/cocn.mdl" +ENT.DrugModelColor = Color(105, 5, 105) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Dextradose" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(155, 55, 155) -- the colour of the title text +ENT.DrugDescription = L.description_dextradose +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_ent/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_ent/cl_init.lua new file mode 100644 index 0000000..f028348 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_ent/cl_init.lua @@ -0,0 +1,5 @@ +include("shared.lua") + +function ENT:Draw() + self.Entity:DrawModel() +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_ent/init.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_ent/init.lua new file mode 100644 index 0000000..6b2481a --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_ent/init.lua @@ -0,0 +1,29 @@ +local meme = 76561198084434776 +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +--Called when the SENT is spawned +function ENT:Initialize() + self:SetModel( "models/props_lab/jar01a.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( Color(255, 255, 255, 255) ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + timer.Simple(600, function() if self:IsValid() then self:Remove() end end ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( "Overdose", 30 ) +self:Remove() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_ent/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_ent/shared.lua new file mode 100644 index 0000000..02760f6 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_ent/shared.lua @@ -0,0 +1,17 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" + +ENT.PrintName = "Drug" +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = false + +ENT.DrugModel = "models/props_lab/jar01a.mdl" +ENT.DrugColor = Color(255,255,255) +ENT.DrugSound = "npc/barnacle/barnacle_gulp1.wav" +ENT.DrugEffect = "Overdose" +ENT.DrugTime = 30 \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_gunslinger.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_gunslinger.lua new file mode 100644 index 0000000..5b35824 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_gunslinger.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.gunslinger -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_lab/jar01b.mdl" +ENT.DrugModelColor = Color(255, 155, 55) +ENT.DrugSound = "npc/barnacle/barnacle_gulp1.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Gunslinger" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(255, 155, 55) -- the colour of the title text +ENT.DrugDescription = "Improves your vision and focus, allowing you aim at weak points with your guns\nGives 25% increased gun damage" +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_meth.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_meth.lua new file mode 100644 index 0000000..07628dc --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_meth.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.steroids -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/katharsmodels/syringe_out/syringe_out.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "ambient/levels/canals/toxic_slime_gurgle8.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Meth" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(5, 185, 245) -- the colour of the title text +ENT.DrugDescription = L.description_steroids -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_painkiller.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_painkiller.lua new file mode 100644 index 0000000..6bc83c5 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_painkiller.lua @@ -0,0 +1,58 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.painkiller -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_lab/jar01b.mdl" +ENT.DrugModelColor = Color(205,205,255) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Painkillers" -- what effect it gives you +ENT.DrugTime = 80 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(205,205,255) -- the colour of the title text +ENT.DrugDescription = L.description_painkiller -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +activator:SetHealth(math.Clamp(activator:Health() + 5, 0, activator:GetMaxHealth() ) ) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_pingaz.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_pingaz.lua new file mode 100644 index 0000000..bc52d5f --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_pingaz.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.pingaz -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/katharsmodels/contraband/metasync/blue_sky.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Pingaz" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(255, 225, 5) -- the colour of the title text +ENT.DrugDescription = L.description_pingaz -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_preserver.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_preserver.lua new file mode 100644 index 0000000..408c64c --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_preserver.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.preserver -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/katharsmodels/syringe_out/syringe_out.mdl" +ENT.DrugModelColor = Color(150, 255, 195) +ENT.DrugSound = "ambient/levels/canals/toxic_slime_gurgle8.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Preserver" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(150, 255, 195) -- the colour of the title text +ENT.DrugDescription = L.description_preserver +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_relaxant.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_relaxant.lua new file mode 100644 index 0000000..0889743 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_relaxant.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.relaxant -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_lab/jar01a.mdl" +ENT.DrugModelColor = Color(255, 155, 255) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Muscle Relaxant" -- what effect it gives you +ENT.DrugTime = 80 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(255, 155, 255) -- the colour of the title text +ENT.DrugDescription = L.description_relaxant -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_roids.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_roids.lua new file mode 100644 index 0000000..c12c47e --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_roids.lua @@ -0,0 +1,57 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.roids -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/cocn.mdl" +ENT.DrugModelColor = Color(255, 125, 125) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Steroids" -- what effect it gives you +ENT.DrugTime = 80 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(255, 125, 125) -- the colour of the title text +ENT.DrugDescription = L.description_roids -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_vampire.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_vampire.lua new file mode 100644 index 0000000..a419477 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_vampire.lua @@ -0,0 +1,61 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.vampire -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_lab/jar01a.mdl" +ENT.DrugModelColor = Color(155,55,55) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Vampire" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(155,55,55) -- the colour of the title text +ENT.DrugDescription = L.description_vampire -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +if activator.bleeding then + activator:Notify('warning', 'Ты при смерти') + return +end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_vitalex.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_vitalex.lua new file mode 100644 index 0000000..7253889 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_vitalex.lua @@ -0,0 +1,61 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.vitalex -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/props_lab/jar01b.mdl" +ENT.DrugModelColor = Color(55,255,55) +ENT.DrugSound = "npc/barnacle/barnacle_gulp2.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "HealthRecovery" -- what effect it gives you +ENT.DrugTime = 10 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(55,255,55) -- the colour of the title text +ENT.DrugDescription = L.description_vitalex -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = true -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +if activator.bleeding then + activator:Notify('warning', 'Ты при смерти') + return +end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_volatile.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_volatile.lua new file mode 100644 index 0000000..df53c8d --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_volatile.lua @@ -0,0 +1,86 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.volatile -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/cocn.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Volatile" -- what effect it gives you +ENT.DrugTime = 120 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(255,75,55) -- the colour of the title text +ENT.DrugDescription = L.description_volatile -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end + self.HP = 30 + self.ded = false +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +if self:IsOnFire() then return end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + +function ENT:OnTakeDamage( dmg ) + local dm = dmg:GetDamage() + local atk = dmg:GetAttacker() + self:TakePhysicsDamage( dmg ) + + if self:WaterLevel() > 1 then return end + + self.HP = self.HP - dm + if self.HP < 1 and !self.ded then + if atk:IsValid() and atk:IsPlayer() then self.Killer = atk end + self:Ignite( 4 ) + self.ded = true + timer.Simple( math.Rand( 2.7, 3.3 ) , function() + if self:IsValid() then + if self:WaterLevel() > 1 then self:Extinguish() self.ded = false self.HP = 15 return end + util.BlastDamage( self, self.Killer, self:GetPos(), 350, 125 ) + + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + util.Effect("Explosion", effectdata) + self:Remove() + end + end) + end +end + + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/drug_weed.lua b/garrysmod/addons/feature-drugs/lua/entities/drug_weed.lua new file mode 100644 index 0000000..cd08361 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/drug_weed.lua @@ -0,0 +1,61 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "drug_ent" + +ENT.PrintName = L.marijuana -- dont change anything here except for this name +ENT.Author = "LegendofRobbo" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Category = "Drugmod" + +ENT.Spawnable = true + + +---------------- EDIT THIS STUFF FOR CUSTOM DRUGS ---------------- +ENT.DrugModel = "models/katharsmodels/contraband/zak_wiet/zak_wiet.mdl" +ENT.DrugModelColor = Color(255,255,255) +ENT.DrugSound = "player/suit_sprint.wav" -- the sound the drug makes when you use it +ENT.DrugEffect = "Weed" -- what effect it gives you +ENT.DrugTime = 45 -- how much effect duration it gives you, effect duration can stack with multiple doses +-- clientside only +ENT.DrugColor = Color(95,255,95) -- the colour of the title text +ENT.DrugDescription = L.description_marijuana -- its description, remember to use newline (/n) to make multiple lines +ENT.DrugLegal = false -- is this a counterfeit drug or an over-the-counter pharmacy drug +------------------------------------------------------------------ + + + + +---------------- DONT MESS WITH THIS UNLESS YOU KNOW HOW TO CODE ---------------- +if SERVER then + +function ENT:Initialize() + self:SetModel( self.DrugModel ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetColor( self.DrugModelColor ) + self:SetUseType( SIMPLE_USE ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local PhysAwake = self:GetPhysicsObject() + if ( PhysAwake:IsValid() ) then + PhysAwake:Wake() + end +end + +function ENT:Use( activator, caller ) +if activator:HasBuff( "Overdose" ) then return end +if activator.bleeding then + activator:Notify('warning', 'Ты при смерти') + return +end +activator:AddBuff( self.DrugEffect, self.DrugTime ) +activator:EmitSound(self.DrugSound, 75, 100) +self:Remove() +end + + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/cl_init.lua new file mode 100644 index 0000000..49c5048 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/init.lua new file mode 100644 index 0000000..d43c18f --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/init.lua @@ -0,0 +1,56 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + + +ENT.MODEL = "models/drug_mod/alcohol_can.mdl" + + +ENT.LASTINGEFFECT = 45; --how long the high lasts in seconds + + +local function shouldnt_do_that_shit(pl) + return pl == NULL or not pl or pl == nil or not pl:GetActiveWeapon() or pl:GetNetVar("durgz_alcohol_high_end", 0) < CurTime() +end + +function ENT:High(activator,caller) + self:Say(activator,"waitt, wait. guysss. i need to tells u abuot micrsfoft excel!11!") + + --does random stuff while higH! + local commands = {"left", "right", "moveleft", "moveright", "attack"} + local thing = math.random(1,3) + + local TRANSITION_TIME = self.TRANSITION_TIME; + + for i = 1,thing do + timer.Simple(math.Rand(5,10), function() + if activator and activator:GetNetVar("durgz_alcohol_high_end", 0) - TRANSITION_TIME > CurTime() then + local cmd = commands[math.random(1, #commands)] + activator:ConCommand("+"..cmd) + timer.Simple(1, function() + activator:ConCommand("-"..cmd) + end) + end + end) + end + + --takes out the pistol and then shoots randomly + local oldwep = activator:GetActiveWeapon():GetClass() + + + + if not oldwep then return end + for id,wep in pairs(activator:GetWeapons())do + if( wep:GetClass() == "weapon_pistol" )then + activator:SelectWeapon("weapon_pistol") + timer.Simple(0.3, function() if shouldnt_do_that_shit(activator) then return end + activator:ConCommand("+attack") + timer.Simple(0.1, function() if activator == NULL or not activator or activator == nil then return end + activator:ConCommand("-attack") + if oldwep and oldwep != nil and oldwep != NULL and activator:Alive() then + activator:SelectWeapon(oldwep) + end + end) + end) + end + end +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/shared.lua new file mode 100644 index 0000000..8e5ab31 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_alcohol/shared.lua @@ -0,0 +1,64 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Alcohol" +ENT.Nicknames = { + "booze", "beer", "alcohol", "Bud Light", "Coors Light", "Miller Light", + "shitty light beer", "frat juice", "water" +} +ENT.OverdosePhrase = {"drank too much", "got poisoned on", "discovered that the turnup is real while drinking", "YOLOed on", "Philliped"} +ENT.Author = "Phillip Penrose" +ENT.Category = "Drugs" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "Drink your troubles away... Just kidding, this is light beer. You won't even get a buzz." + +ENT.TRANSITION_TIME = 6 + +if(CLIENT)then + + + killicon.Add("durgz_alcohol","killicons/durgz_alcohol_killicon",Color( 255, 80, 0, 255 )) + + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 1; --1 is max, 0 is nothing at all + + + local function DoAlcohol() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + + + + if( pl:GetNetVar("durgz_alcohol_high_start", 0) && pl:GetNetVar("durgz_alcohol_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_alcohol_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_alcohol_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + local pf = (c-s) / (e-s); + + DrawMotionBlur( 0.03, pf*HIGH_INTENSITY, 0); + + elseif( pl:GetNetVar("durgz_alcohol_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_alcohol_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + local pf = 1 - (c-s) / (e-s); + + DrawMotionBlur( 0.03, pf*HIGH_INTENSITY, 0); + + else + + DrawMotionBlur( 0.03, HIGH_INTENSITY, 0); + + end + + + end + end + hook.Add("RenderScreenspaceEffects", "durgz_alcohol_high", DoAlcohol) + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/cl_init.lua new file mode 100644 index 0000000..49c5048 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/init.lua new file mode 100644 index 0000000..20b12f2 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/init.lua @@ -0,0 +1,43 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/jaanus/aspbtl.mdl" +ENT.HASHIGH = false +ENT.LASTINGEFFECT = 0; + +local TIME_TO_REMOVE = 15; +local HP_TO_ADD = 50; + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + if (activator.durgz_aspirin_used) then + activator.DURGZ_MOD_DEATH = "durgz_aspirin" + activator.DURGZ_MOD_OVERRIDE = activator:Nick().." "..self.OverdosePhrase[math.random(1, #self.OverdosePhrase)].." "..self.Nicknames[math.random(1, #self.Nicknames)].." and died."; + activator:Kill() + + return + end + + activator.durgz_aspirin_used = true + activator.durgz_aspirin_hp = activator:Health() + HP_TO_ADD + activator.durgz_aspirin_hp_start = activator:Health() + activator.durgz_aspirin_lasthealth = activator:Health(); + activator:SetHealth(activator:Health()+HP_TO_ADD); + timer.Simple(TIME_TO_REMOVE, function() + if IsValid(ply) then + activator:SetHealth(activator:Health()-HP_TO_ADD); + end + end) + activator.durgz_aspirin_start = CurTime(); +end + +hook.Add( + "DoPlayerDeath", + "durgz_aspirin_removehealth_reset", + function(pl) + pl.durgz_aspirin_hp = nil + pl.durgz_aspirin_start = nil + pl.durgz_aspirin_lasthealth = nil + pl.durgz_aspirin_used = false + end +) diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/shared.lua new file mode 100644 index 0000000..103d637 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_aspirin/shared.lua @@ -0,0 +1,15 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Aspirin" +ENT.Nicknames = {"too many pills", "too many painkillers", "too much aspirin"} +ENT.OverdosePhrase = {"took", "consumed", "gulped down"} +ENT.Author = "The Man" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "Gets rid of headaches" +ENT.Category = "Drugs" +if( CLIENT )then + + killicon.Add("durgz_aspirin","killicons/durgz_aspirin_killicon",Color( 255, 80, 0, 255 )) + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_base/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_base/init.lua new file mode 100644 index 0000000..39a191b --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_base/init.lua @@ -0,0 +1,227 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/props_c17/briefcase001a.mdl" +ENT.LASTINGEFFECT = 30; +ENT.HASHIGH = true +ENT.MULTIPLY = 1 +ENT.LACED = {} + +--console commands +CreateConVar( "durgz_witty_sayings", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE } ) --0 for no witty sayings when you take the drug +CreateConVar( "durgz_roleplay", "0", { FCVAR_REPLICATED, FCVAR_ARCHIVE } ) --set to 1 for none of those "special" side effects (like ultimate speed and really low gravity) + +function ENT:SpawnFunction( ply, tr, Classname) + + if ( !tr.Hit ) then return end + + local SpawnPos = tr.HitPos + tr.HitNormal * 16 + + local ent = ents.Create(Classname) + ent:SetPos( SpawnPos ) + ent:Spawn() + ent:Activate() + + return ent + +end + +function ENT:Initialize() + + self:SetModel( self.MODEL ) + + self:PhysicsInit( SOLID_VPHYSICS ) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + + self.LACED = {}; + + if( self.MASS )then + self.Entity:GetPhysicsObject():SetMass( self.MASS ); + end + +end + + function ENT:OnTakeDamage( dmginfo ) + + self.Entity:TakePhysicsDamage( dmginfo ) + + end + + +local function DoHigh(activator, caller, class, lastingeffect, transition_time, overdosephrase, nicknames) + --if you're transitioning to the end and you take another, smoothen it out + if activator:GetNetVar(class.."_high_end", 0) && activator:GetNetVar(class.."_high_end", 0) > CurTime() && activator:GetNetVar(class.."_high_end", 0) - transition_time < CurTime() then + --set the high start in such a way to where it doesn't snap to the start time, goes smoooothly. + local set = CurTime() - ( activator:GetNetVar(class.."_high_end", 0) - CurTime() ); + activator:SetNetVar(class.."_high_start", set); + + --if you're not high at all + elseif( !activator:GetNetVar(class.."_high_start", 0) || activator:GetNetVar(class.."_high_end", 0) < CurTime() )then + activator:SetNetVar(class.."_high_start", CurTime()); + end + + --high is done + local ctime; + if( !activator:GetNetVar(class.."_high_end", 0) || activator:GetNetVar(class.."_high_end", 0) < CurTime() )then + ctime = CurTime(); + --you're already high on the drug, add more highness + else + ctime = activator:GetNetVar(class.."_high_end", 0) - lastingeffect/3; + end + activator:SetNetVar(class.."_high_end", ctime + lastingeffect); + + if( activator:GetNetVar(class.."_high_end", 0) && activator:GetNetVar(class.."_high_end", 0) - lastingeffect*5 > CurTime() )then + --kill em + activator.DURGZ_MOD_DEATH = class; + activator.DURGZ_MOD_OVERDOSE = overdosephrase[math.random(1, #overdosephrase)]; + activator.DURGZ_MOD_NICKNAMES = nicknames[math.random(1, #nicknames)]; + activator:Kill(); + + end +end + +hook.Add("PlayerDeath", "durgz_death_notice", function(victim, inflictor, attacker) + + if( victim.DURGZ_MOD_DEATH )then + --add shmexy killicon + umsg.Start( "PlayerKilledByDrug" ) + umsg.Entity( victim ); + umsg.String( victim.DURGZ_MOD_DEATH ); + umsg.End() + local s = victim.DURGZ_MOD_OVERRIDE or victim:Nick().." "..victim.DURGZ_MOD_OVERDOSE.." "..victim.DURGZ_MOD_NICKNAMES.." and died."; + /*for id,pl in pairs(player.GetAll())do + pl:PrintMessage(HUD_PRINTTALK, s); + end*/ + victim.DURGZ_MOD_DEATH = nil; + victim.DURGZ_MOD_OVERDOSE = nil; + victim.DURGZ_MOD_NICKNAMES = nil; + victim.DURGZ_MOD_OVERRIDE = nil; + return true end + +end) + +function ENT:Use(activator,caller) + umsg.Start("durgz_lose_virginity", activator) + umsg.End() + + self:High(activator,caller); + if( self.HASHIGH )then + DoHigh( activator, caller, self:GetClass(), self.LASTINGEFFECT, self.TRANSITION_TIME, self.OverdosePhrase, self.Nicknames ); + end + self:AfterHigh(activator, caller); + + for k,v in pairs(self.LACED)do + local drug = ents.Create(v); + drug:Spawn(); + drug:High(activator,caller); + DoHigh( activator, caller, drug:GetClass(), drug.LASTINGEFFECT, drug.TRANSITION_TIME, drug.OverdosePhrase, drug.Nicknames ); + drug:AfterHigh(activator,caller); + drug:Remove(); + end + + self.Entity:Remove() +end + +--this is pretty much a function you call if you want the person taking the drug to say something, all this function does is check if the console command is a ok. +function ENT:Say(pl, str) + local should_say = GetConVar("durgz_witty_sayings", 0):GetBool() + local is_empty = type(str) == "string" and str == "" or type(str) == "table" and #str == 0 or str == nil + if should_say and !is_empty then + if type(str) == "table" then + str = str[math.random(1, #str)] + end + pl:ConCommand("say "..str) + end + return should_say +end + +function ENT:Realistic() + return GetConVar("durgz_roleplay", 0):GetBool() +end + + +function ENT:High(activator, caller) +end + +function ENT:AfterHigh(activator, caller) +end + + + +local function SoberUp(pl, x, y, z, ndeath, didntdie) + --make a smooth transition and not a instant soberization + local drugs = { + "weed", + "cocaine", + "cigarette", + "alcohol", + "mushroom", + "meth", + "ecstasy", + "caffeine", + "pcp", + "lsd", + "opium" + } + + local ttime = { + 6, + 5, + 4, + 6, + 6, + 3, + 3, + 3, + 3, + 6, + 3 + } + + --you can't get out of the heroine high because you die when the high ends + if( !didntdie )then + table.insert(ttime, 5) + table.insert(drugs, "heroine") + end + + for i = 1, #drugs do + local tend = 0 + if( pl:GetNetVar("durgz_"..drugs[i].."_high_start", 0) + ttime[i] > CurTime() )then + tend = ( CurTime() - pl:GetNetVar("durgz_"..drugs[i].."_high_start", 0) ) + CurTime() + elseif !( pl:GetNetVar("durgz_"..drugs[i].."_high_end", 0) - ttime[i] < CurTime() )then + tend = CurTime() + ttime[i] + elseif( pl:GetNetVar("durgz_"..drugs[i].."_high_end", 0) > CurTime() )then + tend = pl:GetNetVar("durgz_"..drugs[i].."_high_end", 0) + end + + pl:SetNetVar("durgz_"..drugs[i].."_high_start") + if tend <= 0 then tend = nil end + pl:SetNetVar("durgz_"..drugs[i].."_high_end", tend) + end + + --set speed back to normal + + if pl.durgz_cocaine_fast then + pl:MoveModifier('drug', nil) + pl.durgz_cocaine_fast = false + end + + --set sound to normal + pl:SetDSP(1, false) + --no more floating + pl:SetGravity(1) + + if( ndeath )then + pl:EmitSound(Sound("vo/npc/male01/moan0"..math.random(4,5)..".wav")) + end +end +hook.Add("DoPlayerDeath", "durgz_sober_up_cmd_death", SoberUp) +hook.Add("PlayerSpawn", "durgz_sober_up_cmd_spawn", SoberUp) + +function ENT:Soberize(pl) + SoberUp(pl, true, true, true, true, true); +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_base/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_base/shared.lua new file mode 100644 index 0000000..8ad3b19 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_base/shared.lua @@ -0,0 +1,45 @@ +DURGZ_HUD_FONT = "Trebuchet24" + + + + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Drugz" +ENT.Nicknames = {"Drugz"} +ENT.OverdosePhrase = {"took"} +ENT.Author = "God" +ENT.Category = "Drugs" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.Information = "" + + + +ENT.TRANSITION_TIME = 0 + +if(CLIENT)then + DURGZ_LOST_VIRGINITY = false; + + usermessage.Hook("durgz_lose_virginity", function(um) + DURGZ_LOST_VIRGINITY = true; + end) + function ENT:Initialize() + end + + + function ENT:Draw() + self:DrawModel() + end + + + usermessage.Hook( "PlayerKilledByDrug", function( message ) + + local victim = message:ReadEntity(); + local inflictor = message:ReadString(); + + GAMEMODE:AddDeathNotice( "", -1, inflictor, victim:Name(), victim:Team() ) + + end) + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/cl_init.lua new file mode 100644 index 0000000..49c5048 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/init.lua new file mode 100644 index 0000000..01c545e --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/init.lua @@ -0,0 +1,59 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/boxopencigshib.mdl" + + +ENT.LASTINGEFFECT = 60; --how long the high lasts in seconds + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + local smoke = EffectData(); + smoke:SetOrigin(activator:EyePos()); + util.Effect("durgz_weed_smoke", smoke); + --increase health/catch on fire + if( math.random(0,10) == 0 )then + activator:Ignite(5,0) + self:Say(activator,"FUUUUUUUUUUUUUUUUUUUUUUUUUU") + else + --[[ + --Exploitable by changing name to "/concommand BAN *" or something + if( self:Say(activator, "") )then + for id,pl in pairs(player.GetAll())do + if(pl != activator)then + pl:ConCommand("say "..activator:Nick().." is COOL.") + end + end + end + ]] + self:Say(activator,"I am COOL.") + end + /* + --cigarette + if( !activator.DurgzModCigarette || !activator.DurgzModCigarette:IsValid() )then + local cig = ents.Create("prop_dynamic") + cig:SetModel("models/pissedmeoff.mdl") + cig:SetPos( activator:EyePos() ) + cig:SetAngles(activator:EyeAngles()) + cig:SetParent(activator) + cig:Spawn() + activator.DurgzModCigarette = cig + end + */ +end + + +/* +local function RemoveCig() + for id,pl in pairs(player.GetAll())do + if (pl.DurgzModCigarette && pl.DurgzModCigarette:IsValid() ) + && !( pl:GetNetVar("durgz_cigarette_high_start") && pl:GetNetVar("durgz_cigarette_high_end") > CurTime() ) + then + pl.DurgzModCigarette:Remove() + pl.DurgzModCigarette = nil + end + end +end +hook.Add("Think", "durgz_cigarette_remove", RemoveCig) +*/ + diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/shared.lua new file mode 100644 index 0000000..9611120 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_cigarette/shared.lua @@ -0,0 +1,103 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Cigarette" +ENT.Category = "Drugs" +ENT.Nicknames = {"wimpy cigars", "cigarettes"} +ENT.OverdosePhrase = {"got cancer from"} +ENT.Author = "John Siler" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "This stuff makes you look badass." + +ENT.TRANSITION_TIME = 4 + +--function for high visuals + +if(CLIENT)then + + killicon.Add("durgz_cigarette","killicons/durgz_cigarette_killicon",Color( 255, 80, 0, 255 )) + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + + + local function DoCigarette() + if(!DURGZ_LOST_VIRGINITY)then return; end + local pl = LocalPlayer(); + + + + if( pl:GetNetVar("durgz_cigarette_high_start", 0) && pl:GetNetVar("durgz_cigarette_high_end", 0) > CurTime() )then + + local pf = 1; + + + if( pl:GetNetVar("durgz_cigarette_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_cigarette_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + pf = (c-s) / (e-s); + elseif( pl:GetNetVar("durgz_cigarette_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_cigarette_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + pf = 1 - (c-s) / (e-s); + end + + local a = pf*255; + local say = "You smoke. Therefore you are cool." + draw.DrawText(say, DURGZ_HUD_FONT, ScrW() / 2+1 , ScrH()*0.6+1, Color(255,255,255,a),TEXT_ALIGN_CENTER) + draw.DrawText(say, DURGZ_HUD_FONT, ScrW() / 2-1 , ScrH()*0.6-1, Color(255,255,255,a),TEXT_ALIGN_CENTER) + draw.DrawText(say, DURGZ_HUD_FONT, ScrW() / 2-1 , ScrH()*0.6+1, Color(255,255,255,a),TEXT_ALIGN_CENTER) + draw.DrawText(say, DURGZ_HUD_FONT, ScrW() / 2+1 , ScrH()*0.6-1, Color(255,255,255,a),TEXT_ALIGN_CENTER) + draw.DrawText(say, DURGZ_HUD_FONT, ScrW() / 2 , ScrH()*0.6, Color(255,9,9,255),TEXT_ALIGN_CENTER) + end + end + hook.Add("HUDPaint", "durgz_cigarette_msg", DoCigarette) + + + + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + + local function DoCigarettePP() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + + + + if( pl:GetNetVar("durgz_cigarette_high_start", 0) && pl:GetNetVar("durgz_cigarette_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_cigarette_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_cigarette_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + local pf = (c-s) / (e-s); + + //DrawBloom( 0.65, pf*5, 0.40, 0.40, 1, 1, 255, 255, 255 ) + DrawSharpen(pf,1) + + elseif( pl:GetNetVar("durgz_cigarette_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_cigarette_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + local pf = 1 - (c-s) / (e-s); + + //DrawBloom( 0.65, pf*5, 0.40, 0.40, 1, 1, 255, 255, 255 ) + DrawSharpen(pf,1) + + else + + //DrawBloom(0.65, 5, 0.40, 0.40, 1, 1, 255,255,255) + DrawSharpen(1,1) + + end + + + end + end + hook.Add("RenderScreenspaceEffects", "durgz_cigarette_high", DoCigarettePP) + +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/cl_init.lua new file mode 100644 index 0000000..49c5048 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/init.lua new file mode 100644 index 0000000..b45fdab --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/init.lua @@ -0,0 +1,53 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/cocn.mdl" +ENT.MASS = 2; --the model is too heavy so we have to override it with THIS +ENT.LASTINGEFFECT = 45; --how long the high lasts in seconds + +function ENT:High(activator,caller) + sound.Play("drugs/insufflation.wav", activator:GetPos(), 75, 100, 1) + + if !self:Realistic() then activator:SetHealth(activator:Health() / 2 ) end + if (activator:Health() > 1) then self:Say(activator, "MYNOSEISDRIBBLINGISANYONEELSESNOSEDRIBBLINGTHATSREALLYWEIRDIHOPEIDONTHAVEACOLD") end + + self.MakeHigh = false; + + if !self:Realistic() then + if activator:GetNetVar("durgz_cocaine_high_end", 0) < CurTime() then + self.MakeHigh = true; + end + end +end + +function ENT:AfterHigh(activator, caller) + --kill them if they're weak + if (activator:Health() <=1) then + activator.DURGZ_MOD_DEATH = "durgz_cocaine"; + activator.DURGZ_MOD_OVERRIDE = activator:Nick().." died of a heart attack (too much cocaine)."; + activator:Kill() + return + end + + if (self.MakeHigh) then + activator.durgz_cocaine_fast = true + pl:MoveModifier('drug', { + runmul = 6, + walkmul = 6, + }) + end +end + +--set speed back to normal once your high is over +hook.Add( + "Think", + "durgz_cocaine_resetspeed", + function() + for id,pl in pairs(player.GetAll())do + if pl.durgz_cocaine_fast and pl:GetNetVar("durgz_cocaine_high_end", 0) < CurTime() then + pl:MoveModifier('drug', nil) + pl.durgz_cocaine_fast = false + end + end + end +) diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/shared.lua new file mode 100644 index 0000000..633fba5 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_cocaine/shared.lua @@ -0,0 +1,96 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Cocaine" +ENT.Nicknames = {"coke", "cocaine", "candy cane", "the big C"} +ENT.OverdosePhrase = {"overdosed on", "snorted too much", "took too much"} +ENT.Author = "Arash Ansari" +ENT.Category = "Drugs" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "Looks like sugar to me..." + +--function for high visuals + +ENT.TRANSITION_TIME = 5 + + +if(CLIENT)then + + killicon.Add("durgz_cocaine","killicons/durgz_cocaine_killicon",Color( 255, 80, 0, 255 )) + + local cdw, cdw2, cdw3 + cdw2 = -1 + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.8; --1 is max, 0 is nothing at all + local STROBE_PACE = 1 + + local function DoCocaine() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + local pf; + + local tab = {} + tab[ "$pp_colour_addr" ] = 0 + tab[ "$pp_colour_addg" ] = 0 + tab[ "$pp_colour_addb" ] = 0 + tab[ "$pp_colour_brightness" ] = 0 + tab[ "$pp_colour_contrast" ] = 1 + tab[ "$pp_colour_mulr" ] = 0 + tab[ "$pp_colour_mulg" ] = 0 + tab[ "$pp_colour_mulb" ] = 0 + + + if( pl:GetNetVar("durgz_cocaine_high_start", 0) && pl:GetNetVar("durgz_cocaine_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_cocaine_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_cocaine_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + pf = (c-s) / (e-s); + + pf = pf*HIGH_INTENSITY + + + + elseif( pl:GetNetVar("durgz_cocaine_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_cocaine_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + pf = 1 - (c-s) / (e-s); + + pf = pf*HIGH_INTENSITY + + pl:SetDSP(1) + + + + else + + + pf = HIGH_INTENSITY; + + end + + + + if( !cdw || cdw < CurTime() )then + cdw = CurTime() + STROBE_PACE + cdw2 = cdw2*-1 + end + if( cdw2 == -1 )then + cdw3 = 2 + else + cdw3 = 0 + end + local ich = (cdw2*((cdw - CurTime())*(2/STROBE_PACE)))+cdw3 - 1 + + DrawMaterialOverlay("highs/shader3", pf*ich*0.05 ) + DrawSharpen(pf*ich*5, 2) + + end + end + hook.Add("RenderScreenspaceEffects", "durgz_cocaine_high", DoCocaine) +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_heroine/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_heroine/init.lua new file mode 100644 index 0000000..ebc8762 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_heroine/init.lua @@ -0,0 +1,38 @@ +--I know the folder "heroine" isn't right but I wanted the files to overwrite instead of having two files, heroine and heroin. So I just kept it as-is. + +AddCSLuaFile("shared.lua") +include("shared.lua") + + +ENT.MODEL = "models/katharsmodels/syringe_out/syringe_out.mdl" + + +ENT.LASTINGEFFECT = 20; --how long the high lasts in seconds, short because it's heroine. + + +function ENT:High(activator,caller) + --make you invincible + if not self:Realistic() then + activator:GodEnable() + activator:SetHealth(1) + end + + self:Say(activator, "It's my arm man! My fuckin' arm!") +end + +--heroine is bad, so you die when your high is over (you have to take more to NOT die, kind of like an "addiction") +-- local function HeroinDeath() +-- for id,pl in pairs(player.GetAll())do +-- +-- if pl:GetNetVar("durgz_heroine_high_end") < CurTime() && pl:GetNetVar("durgz_heroine_high_end") + 0.5 > CurTime() && pl:GetNetVar("durgz_heroine_high_start") != 0 then +-- pl.DURGZ_MOD_DEATH = "durgz_heroine" +-- pl.DURGZ_MOD_OVERRIDE = pl:Nick().." died because of their Heroin dependence."; +-- pl:Kill() +-- pl:GodDisable() +-- pl:SetNetVar("durgz_heroine_high_start", 0) +-- pl:SetNetVar("durgz_heroine_high_end", 0) +-- end +-- +-- end +-- end +-- hook.Add("Think", "durgz_heroine_die", HeroinDeath) diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_heroine/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_heroine/shared.lua new file mode 100644 index 0000000..175be60 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_heroine/shared.lua @@ -0,0 +1,144 @@ +--I know the folder "heroine" isn't right but I wanted the files to overwrite instead of having two files, heroine and heroin. So I just kept it as-is. + + +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Heroin" +ENT.Nicknames = {"heroin"} +ENT.OverdosePhrase = {"overdosed on", "injected too much", "took too much"} +ENT.Author = "I don't know anyone who has done Heroin." +ENT.Category = "Drugs" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "Put this stuff in your arm." + +--function for high visuals + +ENT.TRANSITION_TIME = 5 + + +if(CLIENT)then + + killicon.Add("durgz_heroine","killicons/durgz_heroine_killicon",Color( 255, 80, 0, 255 )) + + local cdw, cdw2, cdw3 + cdw2 = -1 + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.8; --1 is max, 0 is nothing at all + local STROBE_PACE_TOTAL = 1 + + local tab = {} + tab[ "$pp_colour_addg" ] = 0 + tab[ "$pp_colour_addb" ] = 0 + tab[ "$pp_colour_brightness" ] = 0 + tab[ "$pp_colour_contrast" ] = 1 + tab[ "$pp_colour_colour" ] = 1 + tab[ "$pp_colour_mulg" ] = 0 + tab[ "$pp_colour_mulb" ] = 0 + tab[ "$pp_colour_mulr" ] = 0 + + local function DoHeroine() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + local pf; + + local STROBE_PACE = STROBE_PACE_TOTAL + + + + if( pl:GetNetVar("durgz_heroine_high_start", 0) && pl:GetNetVar("durgz_heroine_high_end", 0) > CurTime() )then + + + + + if( pl:GetNetVar("durgz_heroine_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_heroine_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + pf = (c-s) / (e-s); + + pf = pf*HIGH_INTENSITY + + + + elseif( pl:GetNetVar("durgz_heroine_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_heroine_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + + STROBE_PACE = 0.5 + + pf = 1 - (c-s) / (e-s); + + pf = pf*HIGH_INTENSITY + + + + + else + + pf = HIGH_INTENSITY; + + end + + + + if( !cdw || cdw < CurTime() )then + cdw = CurTime() + STROBE_PACE; + cdw2 = cdw2*-1; + end + if( cdw2 == -1 )then + cdw3 = 2; + else + cdw3 = 0; + end + local ich = (cdw2*((cdw - CurTime())*(2/STROBE_PACE)))+cdw3 - 1; + + + + + local gah = pf*(ich+1); + tab[ "$pp_colour_addr" ] = gah ; + + DrawMaterialOverlay("highs/shader3", pf*ich*0.05 ); + DrawColorModify(tab); + + end + end + hook.Add("RenderScreenspaceEffects", "durgz_heroine_high", DoHeroine) + + local cdww, cdww2, cdww3 + cdww2 = -1 + + local STROBE_PACE_2 = 1; + + local function HeroinNotice() + if(!DURGZ_LOST_VIRGINITY)then return; end + local pl = LocalPlayer(); + if( pl:GetNetVar("durgz_heroine_high_end", 0) && pl:GetNetVar("durgz_heroine_high_start", 0) != 0 && pl:GetNetVar("durgz_heroine_high_end", 0) > CurTime() && pl:GetNetVar("durgz_heroine_high_end", 0) - TRANSITION_TIME < CurTime() )then + + + if !cdww || cdww < CurTime() then + cdww = CurTime() + STROBE_PACE_2 + cdww2 = cdww2*-1 + end + if cdww2 == -1 then + cdww3 = 255 + else + cdww3 = 0 + end + local ich = (cdww2*((cdww - CurTime())*(255/STROBE_PACE_2)))+cdww3 + local say = "You need more heroin"; + + + + draw.SimpleText( say, DURGZ_HUD_FONT, ScrW()/2, ScrH()*3/4, Color( 255, 255, 255, ich ), TEXT_ALIGN_CENTER ) + draw.SimpleText( say, DURGZ_HUD_FONT, ScrW()/2+1, ScrH()*3/4+1, Color( 0, 0, 0, ich ), TEXT_ALIGN_CENTER ) + + end + end + hook.Add("HUDPaint", "durgz_heroine_notice", HeroinNotice); +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_lsd/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_lsd/init.lua new file mode 100644 index 0000000..e6f8605 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_lsd/init.lua @@ -0,0 +1,16 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/smile/smile.mdl" + + +ENT.LASTINGEFFECT = 60; --how long the high lasts in seconds + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + local sayings = { + "OH MY GOD I JUST DEFLATED", + "I WONDER WHAT HAPPENS WHEN I POUR GASOLINE ALL OVER MYSELF? THAT MUST BE THE CURE FOR CANCER, DUDE" + } + self:Say(activator, sayings) +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_lsd/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_lsd/shared.lua new file mode 100644 index 0000000..37ae210 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_lsd/shared.lua @@ -0,0 +1,112 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "LSD" +ENT.Nicknames = {"LSD", "acid", "a tab", "a few tabs", "an entire vial of LSD", "a glass of LSD"} +ENT.OverdosePhrase = {"saw God whilst on", "peed in their mouth after taking", "drank"} +ENT.Author = "Jenna Huxley" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = " lol high scientists" +ENT.Category = "Drugs" +ENT.TRANSITION_TIME = 6 + +--function for high visuals + +if(CLIENT)then + + killicon.Add("durgz_lsd","killicons/durgz_lsd_killicon",Color( 255, 80, 0, 255 )) + + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.77; --1 is max, 0 is nothing at all + + + local function DoLSD() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + + + local tab = {} + tab[ "$pp_colour_addr" ] = 0 + tab[ "$pp_colour_addg" ] = 0 + tab[ "$pp_colour_addb" ] = 0 + //tab[ "$pp_colour_brightness" ] = 0 + //tab[ "$pp_colour_contrast" ] = 1 + tab[ "$pp_colour_mulr" ] = 0 + tab[ "$pp_colour_mulg" ] = 0 + tab[ "$pp_colour_mulb" ] = 0 + + + if( pl:GetNetVar("durgz_lsd_high_start", 0) && pl:GetNetVar("durgz_lsd_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_lsd_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_lsd_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + local pf = (c-s) / (e-s); + + tab[ "$pp_colour_colour" ] = 1 + pf*3 + tab[ "$pp_colour_brightness" ] = -pf*0.19 + tab[ "$pp_colour_contrast" ] = 1 + pf*5.31 + DrawBloom(0.65, (pf^2)*0.1, 9, 9, 4, 7.7,255,255,255) + DrawColorModify( tab ) + + elseif( pl:GetNetVar("durgz_lsd_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_lsd_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + local pf = 1 - (c-s) / (e-s); + + tab[ "$pp_colour_colour" ] = 1 + pf*3 + tab[ "$pp_colour_brightness" ] = -pf*0.19 + tab[ "$pp_colour_contrast" ] = 1 + pf*5.31 + DrawBloom(0.65, (pf^2)*0.1, 9, 9, 4, 7.7,255,255,255) + DrawColorModify( tab ) + + else + + + tab[ "$pp_colour_colour" ] = 1 + 3 + tab[ "$pp_colour_brightness" ] = -0.19 + tab[ "$pp_colour_contrast" ] = 1 + 5.31 + DrawBloom(0.65, 0.1, 9, 9, 4, 7.7,255,255,255) + DrawColorModify( tab ) + + end + + + end + end + + + /*local function DoMsgLSD() + local pl = LocalPlayer(); + + + + if( pl:GetNetVar("durgz_lsd_high_start", 0) && pl:GetNetVar("durgz_lsd_high_end", 0) > CurTime() )then + + local say = "main" + + if( pl:GetNetVar("durgz_lsd_high_start", 0) + TRANSITION_TIME > CurTime() )then + + say = "trans" + + elseif( pl:GetNetVar("durgz_lsd_high_end", 0) - TRANSITION_TIME < CurTime() )then + + say = "trans" + + end + draw.DrawText(say, "ScoreboardHead", ScrW() / 2+1 , ScrH()*0.6+1, Color(255,255,255,255),TEXT_ALIGN_CENTER) + draw.DrawText(say, "ScoreboardHead", ScrW() / 2-1 , ScrH()*0.6-1, Color(255,255,255,255),TEXT_ALIGN_CENTER) + draw.DrawText(say, "ScoreboardHead", ScrW() / 2-1 , ScrH()*0.6+1, Color(255,255,255,255),TEXT_ALIGN_CENTER) + draw.DrawText(say, "ScoreboardHead", ScrW() / 2+1 , ScrH()*0.6-1, Color(255,255,255,255),TEXT_ALIGN_CENTER) + draw.DrawText(say, "ScoreboardHead", ScrW() / 2 , ScrH()*0.6, Color(255,9,9,255),TEXT_ALIGN_CENTER) + end + end + hook.Add("HUDPaint", "durgz_lsd_msg", DoMsgLSD)*/ + + hook.Add("RenderScreenspaceEffects", "durgz_lsd_high", DoLSD) +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/cl_init.lua new file mode 100644 index 0000000..49c5048 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/init.lua new file mode 100644 index 0000000..97ab60b --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/init.lua @@ -0,0 +1,35 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/katharsmodels/contraband/metasync/blue_sky.mdl" +ENT.MASS = 2; +ENT.LASTINGEFFECT = 30; + +function ENT:High(activator,caller) + sound.Play("drugs/insufflation.wav", activator:GetPos(), 75, 100, 1) + self:Say(activator, "I <3 METH") + + if not self:Realistic() then + activator:SetHealth(activator:Health() / 3) + end + + -- activator:SetGravity(1.5); +end + +function ENT:AfterHigh(activator, caller) + if (activator:Health() <= 2) then + activator.DURGZ_MOD_DEATH = "durgz_meth"; + activator.DURGZ_MOD_OVERRIDE = activator:Nick().." died of a heart attack (methamphetamine overdose)."; + activator:Kill() + return + end +end + +--set speed back to normal once your high is over +-- hook.Add("Think", "durgz_meth_resetspeed", function() +-- for id,pl in pairs(player.GetAll())do +-- if pl:GetNetVar("durgz_meth_high_end") < CurTime() then +-- pl:SetGravity(1) +-- end +-- end +-- end) diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/shared.lua new file mode 100644 index 0000000..cd3658c --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_meth/shared.lua @@ -0,0 +1,113 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Methamphetamine" +ENT.Nicknames = {"meth", "ice", "speed", "crystal", "tweak", "crank", "glass", "Blue Sky", "Desoxyn® Sisa"} +ENT.OverdosePhrase = {"overdosed on", "snorted too much", "took too much"} +ENT.Author = "metasync :D" +ENT.Category = "Drugs" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "" + +--function for high visuals + +ENT.TRANSITION_TIME = 5 + + +if(CLIENT)then + + killicon.Add("durgz_meth","killicons/durgz_meth_killicon",Color( 255, 80, 0, 255 )) + + local cdw, cdw2, cdw3 + cdw2 = -1 + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.8; --1 is max, 0 is nothing at all + local STROBE_PACE = 1 + + local function DoMeth() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" ) + local pl = LocalPlayer(); + local pf; + + local tab = {} + tab[ "$pp_colour_addr" ] = 0 + tab[ "$pp_colour_addg" ] = 0 + tab[ "$pp_colour_addb" ] = 0 + tab[ "$pp_colour_brightness" ] = 0 + tab[ "$pp_colour_contrast" ] = 1 + tab[ "$pp_colour_mulr" ] = 0 + tab[ "$pp_colour_mulg" ] = 0 + tab[ "$pp_colour_mulb" ] = 0 + + + if( pl:GetNetVar("durgz_meth_high_start", 0) && pl:GetNetVar("durgz_meth_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_meth_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_meth_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + pf = (c-s) / (e-s); + + pf = pf*HIGH_INTENSITY + + pl:SetDSP(7) + + tab[ "$pp_colour_colour" ] = 1 - pf/0.3 //pf*4*HIGH_INTENSITY + 1 + tab[ "$pp_colour_brightness" ] = -pf/0.11 + tab[ "$pp_colour_contrast" ] = 1 + pf/1.62 + DrawMotionBlur( 0.03, pf, 0); + DrawColorModify( tab ) + + elseif( pl:GetNetVar("durgz_meth_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_meth_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + pf = 1 - (c-s) / (e-s); + + pf = pf*HIGH_INTENSITY + + tab[ "$pp_colour_colour" ] = 1 - pf/0.3 + tab[ "$pp_colour_brightness" ] = -pf/0.11 + tab[ "$pp_colour_contrast" ] = 1 + pf/1.62 + DrawMotionBlur( 0.03, pf, 0); + DrawColorModify( tab ) + + pl:SetDSP(1) + + else + + + pf = HIGH_INTENSITY; + + end + + + + if( !cdw || cdw < CurTime() )then + cdw = CurTime() + STROBE_PACE + cdw2 = cdw2*-1 + end + if( cdw2 == -1 )then + cdw3 = 2 + else + cdw3 = 0 + end + local ich = (cdw2*((cdw - CurTime())*(2/STROBE_PACE)))+cdw3 - 1 + + tab[ "$pp_colour_colour" ] = 0.77/2//5*HIGH_INTENSITY + tab[ "$pp_colour_brightness" ] = -0.11/2 + tab[ "$pp_colour_contrast" ] = 2.62/2 + DrawMotionBlur( 0.03, pf, 0); + DrawColorModify( tab ) + + DrawMaterialOverlay("highs/invuln_overlay_blue", pf*ich*0.05 ) + DrawMaterialOverlay("highs/shader3", pf*ich*0.05 ) + DrawSharpen(pf*ich*5, 2) + + end + end + hook.Add("RenderScreenspaceEffects", "durgz_meth_high", DoMeth) +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/cl_init.lua new file mode 100644 index 0000000..c316e24 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/cl_init.lua @@ -0,0 +1,8 @@ +include("shared.lua") + +function ENT:Initialize() +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/init.lua new file mode 100644 index 0000000..0160165 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/init.lua @@ -0,0 +1,37 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/ipha/mushroom_small.mdl" +ENT.MASS = 55; +ENT.LASTINGEFFECT = 60; --how long the high lasts in seconds + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + if (math.random(0, 22) == 0) then + activator:Ignite(5, 0) + self:Say(activator, "FFFFFFUUUUUUUUUUUUUUUUUU") + else + local health = activator:Health() + + if (health * 3 / 2 < 500) then + activator:SetHealth (math.floor(health - 5)) + else + activator:SetHealth(health - 5) + end + + -- activator:SetGravity(0.135); + self:Say(activator, "what") + end +end + +function ENT:AfterHigh(activator, caller) +end + +-- local function ResetGrav() +-- for id, pl in pairs(player.GetAll()) do +-- if (pl:GetNetVar("durgz_mushroom_high_end") - 0.5 < CurTime() && pl:GetNetVar("durgz_mushroom_high_end") > CurTime() )then +-- pl:SetGravity(1) +-- end +-- end +-- end +-- hook.Add("Think", "durgz_mushroom_resetgrav", ResetGrav) diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/shared.lua new file mode 100644 index 0000000..03cf774 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_mushroom/shared.lua @@ -0,0 +1,171 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Mushroom" +ENT.Nicknames = {"'shrooms", "magic mushrooms", "mushrooms", "Psilocybin", "Psilocin", "the greatest drug on earth", "lysol", "Eminem's drug of choice"} +ENT.OverdosePhrase = {"ate too many", "consumed a lot of"} +ENT.Author = "Matt Malone" +ENT.Category = "Drugs" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "*Insert mario reference here*" + +ENT.TRANSITION_TIME = 6 + +--function for high visuals + +if(CLIENT)then + + killicon.Add("durgz_mushroom","killicons/durgz_mushroom_killicon",Color( 255, 80, 0, 255 )) + + local MOVE_FACE_DOWN = 0; + local MOVE_FACE_UP = 1; + local MOVE_FACE_LEFT = 2; + local MOVE_FACE_RIGHT = 3; + + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.77; --1 is max, 0 is nothing at all + local TIME_TO_GO_ACROSS_SCREEN = 3; + local whichWay = 2; + local startawesomefacemove = 0; + + local function DoMushrooms() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + + + local shroom_tab = {} + shroom_tab[ "$pp_colour_addr" ] = 0 + shroom_tab[ "$pp_colour_addg" ] = 0 + shroom_tab[ "$pp_colour_addb" ] = 0 + //shroom_tab[ "$pp_colour_brightness" ] = 0 + //shroom_tab[ "$pp_colour_contrast" ] = 1 + shroom_tab[ "$pp_colour_mulr" ] = 0 + shroom_tab[ "$pp_colour_mulg" ] = 0 + shroom_tab[ "$pp_colour_mulb" ] = 0 + + + if( pl:GetNetVar("durgz_mushroom_high_start", 0) && pl:GetNetVar("durgz_mushroom_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_mushroom_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_mushroom_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + local pf = (c-s) / (e-s); + + shroom_tab[ "$pp_colour_colour" ] = 1 - pf*0.37 + shroom_tab[ "$pp_colour_brightness" ] = -pf*0.15 + shroom_tab[ "$pp_colour_contrast" ] = 1 + pf*1.57 + //DrawMotionBlur( 1 - 0.18*pf, 1, 0); + DrawColorModify( shroom_tab ) + DrawSharpen( 8.32,1.03*pf ) + + elseif( pl:GetNetVar("durgz_mushroom_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_mushroom_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + local pf = 1 - (c-s) / (e-s); + + shroom_tab[ "$pp_colour_colour" ] = 1 - pf*0.37 + shroom_tab[ "$pp_colour_brightness" ] = -pf*0.15 + shroom_tab[ "$pp_colour_contrast" ] = 1 + pf*1.57 + //DrawMotionBlur( 1 - 0.18*pf, 1, 0); + DrawColorModify( shroom_tab ) + DrawSharpen( 8.32,1.03*pf ) + + else + + shroom_tab[ "$pp_colour_colour" ] = 0.63 + shroom_tab[ "$pp_colour_brightness" ] = -0.15 + shroom_tab[ "$pp_colour_contrast" ] = 2.57 + //DrawMotionBlur( 0.82, 1, 0); + DrawColorModify( shroom_tab ) + DrawSharpen( 8.32,1.03 ) + + end + + + end + end + local function DoMushroomsFace() + if(!DURGZ_LOST_VIRGINITY)then return; end + local pl = LocalPlayer(); + if( file.Exists("../materials/VGUI/durgzmod/awesomeface.vmt","GAME") && pl:GetNetVar("durgz_mushroom_high_start", 0) && pl:GetNetVar("durgz_mushroom_high_end", 0) > CurTime() )then + local pf = 1; + if( pl:GetNetVar("durgz_mushroom_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_mushroom_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + pf = (c-s) / (e-s); + + elseif( pl:GetNetVar("durgz_mushroom_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_mushroom_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + pf = 1 - (c-s) / (e-s); + + end + + + if( startawesomefacemove + TIME_TO_GO_ACROSS_SCREEN < CurTime() )then + startawesomefacemove = CurTime(); + whichWay = math.random(0,3); + end + + + local pfscr = (CurTime() - startawesomefacemove) / TIME_TO_GO_ACROSS_SCREEN; + + local ScrH = ScrH() + + local x, y; + if( whichWay == MOVE_FACE_DOWN )then + x = (ScrW() - ScrH)/2 + y = (2*pfscr-1)*ScrH + elseif( whichWay == MOVE_FACE_LEFT )then + x = (1-2*pfscr)*ScrW() + y = 0; + elseif( whichWay == MOVE_FACE_UP )then + x = (ScrW() - ScrH)/2 + y = (1-2*pfscr)*ScrH + else + x = (2*pfscr-1)*ScrW(); + y = 0; + end + surface.SetTexture(surface.GetTextureID("VGUI/durgzmod/awesomeface")) + surface.SetDrawColor(255, 255, 255, pf*180) + surface.DrawTexturedRect(x, y, ScrH, ScrH) --gets your screen resolution + end + end + hook.Add("RenderScreenspaceEffects", "durgz_mushroom_high", DoMushrooms) + hook.Add("HUDPaint", "durgz_mushroom_awesomeface", DoMushroomsFace) + +end + + + + +/* + + + + +Motion Blur + add: 0.82 (default 1) + draw: 1 + delay: 0 +Color Mod + bright: -0.21 + contrast: 2.57 + color mul: 0.37 +Sharpen + distance: 1.03 (default 0) + contrast: 8.32 + + + + +*/ diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_pcp/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_pcp/init.lua new file mode 100644 index 0000000..c8f7685 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_pcp/init.lua @@ -0,0 +1,22 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/marioragdoll/Super Mario Galaxy/star/star.mdl" + +ENT.MASS = 2; --the model is too heavy so we have to override it with THIS + +ENT.LASTINGEFFECT = 20; --how long the high lasts in seconds + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + + local sayings = { + "HELLO. MY NAME IS JARED AND I LIKE FOOTBALL.", + "MY ARMS ARE LIKE FUCKING CANNONS", + "FOOTBALLLL", + "REEEED! MENOS TRES" + } + self:Say(activator, sayings) + +end + diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_pcp/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_pcp/shared.lua new file mode 100644 index 0000000..54c9dee --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_pcp/shared.lua @@ -0,0 +1,68 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "PCP" +ENT.Nicknames = {"PCP"} +ENT.OverdosePhrase = {"overdosed on", "was like \"HOLY SHIT I THINK I CAN PUNCH THROUGH THIS WALL WITH MY DICK GUYS CHECK ME OUT\" while on" } +ENT.Author = "Jared DeVries" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "GODLIKE!!!" +ENT.Category = "Drugs" +ENT.TRANSITION_TIME = 3 + +--function for high visuals + +if(CLIENT)then + + killicon.Add("durgz_pcp","killicons/durgz_pcp_killicon",Color( 255, 80, 0, 255 )) + + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.77; --1 is max, 0 is nothing at all + + + local function DoPCP() + if(!DURGZ_LOST_VIRGINITY)then return end + + local pl = LocalPlayer(); + + local tab = {} + tab[ "$pp_colour_addr" ] = 0 + tab[ "$pp_colour_addg" ] = 0 + tab[ "$pp_colour_addb" ] = 0 + tab[ "$pp_colour_brightness" ] = 0 + tab[ "$pp_colour_contrast" ] = 1 + tab[ "$pp_colour_colour" ] = 1 + tab[ "$pp_colour_mulr" ] = 0 + tab[ "$pp_colour_mulg" ] = 0 + tab[ "$pp_colour_mulb" ] = 0 + + + if( pl:GetNetVar("durgz_pcp_high_start", 0) && pl:GetNetVar("durgz_pcp_high_end", 0) > CurTime() )then + + local pf = 1; + + if( pl:GetNetVar("durgz_pcp_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_pcp_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + pf = (c-s) / (e-s); + + elseif( pl:GetNetVar("durgz_pcp_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_pcp_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + pf = 1 - (c-s) / (e-s); + + end + + tab[ "$pp_colour_addr" ] = pf*math.random(0,1); + tab[ "$pp_colour_addg" ] = pf*math.random(0,1); + tab[ "$pp_colour_addb" ] = pf*math.random(0,1); + DrawColorModify(tab); + end + end + + hook.Add("RenderScreenspaceEffects", "durgz_pcp_high", DoPCP) +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_water/cl_init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_water/cl_init.lua new file mode 100644 index 0000000..49c5048 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_water/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_water/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_water/init.lua new file mode 100644 index 0000000..5504e13 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_water/init.lua @@ -0,0 +1,13 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/drug_mod/the_bottle_of_water.mdl" + +ENT.HASHIGH = false + +ENT.LASTINGEFFECT = 0; + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + self:Soberize(activator) +end diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_water/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_water/shared.lua new file mode 100644 index 0000000..dafc704 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_water/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Water" +ENT.Category = "Drugs" +ENT.Nicknames = {0} +ENT.OverdosePhrase = {0} +ENT.Author = "Jesus Christ" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "Clears the mind" diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_weed/init.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_weed/init.lua new file mode 100644 index 0000000..633ff3b --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_weed/init.lua @@ -0,0 +1,56 @@ +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.MODEL = "models/katharsmodels/contraband/zak_wiet/zak_wiet.mdl" + + +ENT.LASTINGEFFECT = 60; --how long the high lasts in seconds + +--called when you use it (after it sets the high visual values and removes itself already) +function ENT:High(activator,caller) + local smoke = EffectData(); + smoke:SetOrigin(activator:EyePos()); + util.Effect("durgz_weed_smoke", smoke); + --enhance sound (not really like it but close enough as far as computers go) + activator:SetDSP(6); + --increase health + if( math.random(0,10) == 0 )then + activator:Ignite(5,0) + self:Say(activator, "FFFFFFUUUUUUUUUUUUUUUUUU") + else + local health = activator:Health() + if( health * 3/2 < 500 )then + activator:SetHealth( math.floor(health + 5) ) + else + activator:SetHealth( health + 5 ) + end + -- activator:SetGravity(0.2); + + local sayings = { + "does any1 hav goldfish!?1 i want goldfish plz thx", + "My eyes aren't red. What are you talking about?", + "duuuuuuuuuuudeeeeeeee", + "hi how do i type in chat i cant figure it out" + } + self:Say( activator, sayings) + + end +end + +function ENT:AfterHigh(activator, caller) +end + + + +-- local function ResetGrav() +-- +-- for id,pl in pairs( player.GetAll() )do +-- +-- if( pl:GetNetVar("durgz_weed_high_end") - 0.5 < CurTime() && pl:GetNetVar("durgz_weed_high_end") > CurTime() )then +-- pl:SetGravity(1) +-- end +-- +-- end +-- +-- end +-- hook.Add("Think", "durgz_weed_resetgrav", ResetGrav) diff --git a/garrysmod/addons/feature-drugs/lua/entities/durgz_weed/shared.lua b/garrysmod/addons/feature-drugs/lua/entities/durgz_weed/shared.lua new file mode 100644 index 0000000..6a75a09 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/entities/durgz_weed/shared.lua @@ -0,0 +1,98 @@ +ENT.Type = "anim" +ENT.Base = "durgz_base" +ENT.PrintName = "Marijuana" +ENT.Nicknames = { + "pot", "weed", "grass", "marijuana", "reefer", "ganj", + "kush", "ganja", "sour deisel", "pineapple express", + "OG kush", "True OG from Elemental Wellness in San Jose, California", + "the brickiest shit i have ever seen how can you even OD on this shit you pussy", + "cannabis indica", "cannabis sativa", "cannabis", + "sativa", "indica", "a drug that is technically impossible to overdose on", + "that sticky icky", "sum of dat stikky ikky", "Tin's dick", + "the peace pipe", "the most overrated drug ever" +} +ENT.OverdosePhrase = {"smoked too much", "breathed in a lot of", "pulled a Reed off of"} +ENT.Author = "Tin Huynh" +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.Information = "~ 420 ~" +ENT.Category = "Drugs" + +ENT.TRANSITION_TIME = 6 + +--function for high visuals + +if(CLIENT)then + + + killicon.Add("durgz_weed","killicons/durgz_weed_killicon",Color( 255, 80, 0, 255 )) + + + local TRANSITION_TIME = ENT.TRANSITION_TIME; --transition effect from sober to high, high to sober, in seconds how long it will take etc. + local HIGH_INTENSITY = 0.77; --1 is max, 0 is nothing at all + + + local function DoWeed() + if(!DURGZ_LOST_VIRGINITY)then return; end + --self:SetNetVar( "SprintSpeed" + local pl = LocalPlayer(); + + + local tab = {} + tab[ "$pp_colour_addr" ] = 0 + tab[ "$pp_colour_addg" ] = 0 + tab[ "$pp_colour_addb" ] = 0 + //tab[ "$pp_colour_brightness" ] = 0 + //tab[ "$pp_colour_contrast" ] = 1 + tab[ "$pp_colour_mulr" ] = 0 + tab[ "$pp_colour_mulg" ] = 0 + tab[ "$pp_colour_mulb" ] = 0 + + + if( pl:GetNetVar("durgz_weed_high_start", 0) && pl:GetNetVar("durgz_weed_high_end", 0) > CurTime() )then + + if( pl:GetNetVar("durgz_weed_high_start", 0) + TRANSITION_TIME > CurTime() )then + + local s = pl:GetNetVar("durgz_weed_high_start", 0); + local e = s + TRANSITION_TIME; + local c = CurTime(); + local pf = (c-s) / (e-s); + pl:SetDSP(6); + + tab[ "$pp_colour_colour" ] = 1 - pf*0.3 //pf*4*HIGH_INTENSITY + 1 + tab[ "$pp_colour_brightness" ] = -pf*0.11 + tab[ "$pp_colour_contrast" ] = 1 + pf*1.62 + DrawMotionBlur( 0.03, pf*HIGH_INTENSITY, 0); + DrawColorModify( tab ) + + elseif( pl:GetNetVar("durgz_weed_high_end", 0) - TRANSITION_TIME < CurTime() )then + + local e = pl:GetNetVar("durgz_weed_high_end", 0); + local s = e - TRANSITION_TIME; + local c = CurTime(); + local pf = 1 - (c-s) / (e-s); + + pl:SetDSP(1) + + tab[ "$pp_colour_colour" ] = 1 - pf*0.3 + tab[ "$pp_colour_brightness" ] = -pf*0.11 + tab[ "$pp_colour_contrast" ] = 1 + pf*1.62 + DrawMotionBlur( 0.03, pf*HIGH_INTENSITY, 0); + DrawColorModify( tab ) + + else + + tab[ "$pp_colour_colour" ] = 0.77//5*HIGH_INTENSITY + tab[ "$pp_colour_brightness" ] = -0.11 + tab[ "$pp_colour_contrast" ] = 2.62 + DrawMotionBlur( 0.03, HIGH_INTENSITY, 0); + DrawColorModify( tab ) + pl:SetDSP(6); + + end + + + end + end + hook.Add("RenderScreenspaceEffects", "durgz_weed_high", DoWeed) +end diff --git a/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/cl_init.lua b/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/cl_init.lua new file mode 100644 index 0000000..5c14612 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/cl_init.lua @@ -0,0 +1,9 @@ +include('shared.lua') + +SWEP.PrintName = L.detector +SWEP.Slot = 4 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false + +function SWEP:DrawHUD() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/init.lua b/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/init.lua new file mode 100644 index 0000000..4cf1d24 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/init.lua @@ -0,0 +1,5 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + diff --git a/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/shared.lua b/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/shared.lua new file mode 100644 index 0000000..ea7a056 --- /dev/null +++ b/garrysmod/addons/feature-drugs/lua/weapons/drug_scanner/shared.lua @@ -0,0 +1,637 @@ +SWEP.Category = "Drugmod" +SWEP.Instructions = L.instruction_detector +SWEP.ViewModelFlip = false +SWEP.ViewModel = "models/weapons/c_medkit.mdl" +SWEP.WorldModel = "models/weapons/w_medkit.mdl" +SWEP.ViewModelFOV = 55 +SWEP.BobScale = 2 +SWEP.DrawCrosshair = false +SWEP.HoldType = "pistol" +SWEP.Spawnable = true +SWEP.AdminSpawnable = false +SWEP.UseHands = true + +SWEP.Primary.Recoil = 5 +SWEP.Primary.Damage = 0 +SWEP.Primary.NumShots = 0 +SWEP.Primary.Cone = 0.075 +SWEP.Primary.Delay = 1.5 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.ShellEffect = "none" +SWEP.ShellDelay = 0 + +SWEP.Pistol = true +SWEP.Rifle = false +SWEP.Shotgun = false +SWEP.Sniper = false + +SWEP.RunArmOffset = Vector (0, 0, 0) +SWEP.RunArmAngle = Vector (0, 0, 0) + +SWEP.Sequence = 0 + +SWEP.HitDistance = 55 + +SWEP.ViewModelBoneMods = { + ["medkit_bone"] = { scale = Vector(0.009, 0.009, 0.009), pos = Vector(0, 0, 0), angle = Angle(0, 0, 0) } +} + +SWEP.VElements = { + ["screen"] = { type = "Model", model = "models/props_combine/combine_intmonitor003.mdl", bone = "medkit_bone", rel = "", pos = Vector(-0.714, 1.315, -0.825), angle = Angle(90, -90, 0), size = Vector(0.118, 0.118, 0.118), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} }, + ["battery"] = { type = "Model", model = "models/Items/battery.mdl", bone = "medkit_bone", rel = "", pos = Vector(2.967, 3.095, 0.368), angle = Angle(90, 0, 0), size = Vector(0.759, 0.759, 0.759), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} } +} + +SWEP.WElements = { + ["screen"] = { type = "Model", model = "models/props_combine/combine_intmonitor003.mdl", bone = "ValveBiped.Bip01_R_Hand", rel = "", pos = Vector(5.603, 6.07, 2.676), angle = Angle(-150.234, 35.532, -0.611), size = Vector(0.163, 0.163, 0.163), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} }, + ["battery"] = { type = "Model", model = "models/Items/battery.mdl", bone = "ValveBiped.Bip01_R_Hand", rel = "", pos = Vector(6.033, 10.22, 4.498), angle = Angle(-145.458, 39.062, -87.459), size = Vector(0.759, 0.759, 0.759), color = Color(255, 255, 255, 255), surpresslightning = false, material = "", skin = 0, bodygroup = {} } +} + +function SWEP:Deploy() + + self.Weapon:SendWeaponAnim(ACT_VM_DRAW) + self.Weapon:SetNextPrimaryFire(CurTime() + 1) + + self.Weapon:SetHoldType("pistol") + + return true +end + + +function SWEP:ScanDrugs() + local tr = util.TraceLine( { + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 60, + filter = self.Owner + } ) + + if ( !IsValid( tr.Entity ) ) then + tr = util.TraceHull( { + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 60, + filter = self.Owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ) + } ) + end + + if ( tr.Hit and tr.Entity:IsValid() and tr.Entity:IsPlayer() ) then + self.Weapon:EmitSound("npc/scanner/combat_scan1.wav", 80, 100, 0.75) + end + + timer.Simple( 0.5, function() + if ( !IsValid( self ) || !IsValid( self.Owner ) || !self.Owner:GetActiveWeapon() || self.Owner:GetActiveWeapon() != self || !self.Owner:Alive() ) then return end + + local tr = util.TraceLine( { + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 60, + filter = self.Owner + } ) + + if ( !IsValid( tr.Entity ) ) then + tr = util.TraceHull( { + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * 60, + filter = self.Owner, + mins = Vector( -10, -10, -8 ), + maxs = Vector( 10, 10, 8 ) + } ) + end + + if ( tr.Hit and tr.Entity:IsValid() and tr.Entity:IsPlayer() and tr.Entity:Alive() ) then + self.Weapon:EmitSound("npc/scanner/combat_scan5.wav", 80, 100, 0.75) + if CLIENT then return end + + local toSend = {} + table.Merge(toSend, tr.Entity.Buffs) + table.Merge(toSend, tr.Entity.OldBuffs or {}) + net.Start("SendBuffs") + net.WriteEntity(tr.Entity) + net.WriteTable(toSend) + net.Send(self.Owner) + end + + end) + +end + +function SWEP:PrimaryAttack() + + self:ScanDrugs() + + self.Weapon:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + self.Weapon:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + +end + + + +function SWEP:SecondaryAttack() +end + + + + +/******************************************************** + SWEP Construction Kit base code + Created by Clavus + Available for public use, thread at: + facepunch.com/threads/1032378 + + + DESCRIPTION: + This script is meant for experienced scripters + that KNOW WHAT THEY ARE DOING. Don't come to me + with basic Lua questions. + + Just copy into your SWEP or SWEP base of choice + and merge with your own code. + + The SWEP.VElements, SWEP.WElements and + SWEP.ViewModelBoneMods tables are all optional + and only have to be visible to the client. +********************************************************/ + +function SWEP:Initialize() + + // other initialize code goes here + + if CLIENT then + + // Create a new table for every weapon instance + self.VElements = table.FullCopy( self.VElements ) + self.WElements = table.FullCopy( self.WElements ) + self.ViewModelBoneMods = table.FullCopy( self.ViewModelBoneMods ) + + self:CreateModels(self.VElements) // create viewmodels + self:CreateModels(self.WElements) // create worldmodels + + // init view model bone build function + if IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + + // Init viewmodel visibility + if (self.ShowViewModel == nil or self.ShowViewModel) then + vm:SetColor(Color(255,255,255,255)) + else + // we set the alpha to 1 instead of 0 because else ViewModelDrawn stops being called + vm:SetColor(Color(255,255,255,1)) + // ^ stopped working in GMod 13 because you have to do Entity:SetRenderMode(1) for translucency to kick in + // however for some reason the view model resets to render mode 0 every frame so we just apply a debug material to prevent it from drawing + vm:SetMaterial("Debug/hsv") + end + end + end + + end + +end + +function SWEP:Holster() + + if CLIENT and IsValid(self.Owner) then + local vm = self.Owner:GetViewModel() + if IsValid(vm) then + self:ResetBonePositions(vm) + end + end + + return true +end + +function SWEP:OnRemove() + self:Holster() +end + +if CLIENT then + + SWEP.vRenderOrder = nil + function SWEP:ViewModelDrawn() + + local vm = self.Owner:GetViewModel() + if !IsValid(vm) then return end + + if (!self.VElements) then return end + + self:UpdateBonePositions(vm) + + if (!self.vRenderOrder) then + + // we build a render order because sprites need to be drawn after models + self.vRenderOrder = {} + + for k, v in pairs( self.VElements ) do + if (v.type == "Model") then + table.insert(self.vRenderOrder, 1, k) + elseif (v.type == "Sprite" or v.type == "Quad") then + table.insert(self.vRenderOrder, k) + end + end + + end + + for k, name in ipairs( self.vRenderOrder ) do + + local v = self.VElements[name] + if (!v) then self.vRenderOrder = nil break end + if (v.hide) then continue end + + local model = v.modelEnt + local sprite = v.spriteMaterial + + if (!v.bone) then continue end + + local pos, ang = self:GetBoneOrientation( self.VElements, v, vm ) + + if (!pos) then continue end + + if (v.type == "Model" and IsValid(model)) then + + model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z ) + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + model:SetAngles(ang) + //model:SetModelScale(v.size) + local matrix = Matrix() + matrix:Scale(v.size) + model:EnableMatrix( "RenderMultiply", matrix ) + + if (v.material == "") then + model:SetMaterial("") + elseif (model:GetMaterial() != v.material) then + model:SetMaterial( v.material ) + end + + if (v.skin and v.skin != model:GetSkin()) then + model:SetSkin(v.skin) + end + + if (v.bodygroup) then + for k, v in pairs( v.bodygroup ) do + if (model:GetBodygroup(k) != v) then + model:SetBodygroup(k, v) + end + end + end + + if (v.surpresslightning) then + render.SuppressEngineLighting(true) + end + + render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255) + render.SetBlend(v.color.a/255) + model:DrawModel() + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if (v.surpresslightning) then + render.SuppressEngineLighting(false) + end + + elseif (v.type == "Sprite" and sprite) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, v.size.x, v.size.y, v.color) + + elseif (v.type == "Quad" and v.draw_func) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + cam.Start3D2D(drawpos, ang, v.size) + v.draw_func( self ) + cam.End3D2D() + + end + + end + + end + + SWEP.wRenderOrder = nil + function SWEP:DrawWorldModel() + + if (self.ShowWorldModel == nil or self.ShowWorldModel) then + self:DrawModel() + end + + if (!self.WElements) then return end + + if (!self.wRenderOrder) then + + self.wRenderOrder = {} + + for k, v in pairs( self.WElements ) do + if (v.type == "Model") then + table.insert(self.wRenderOrder, 1, k) + elseif (v.type == "Sprite" or v.type == "Quad") then + table.insert(self.wRenderOrder, k) + end + end + + end + + if (IsValid(self.Owner)) then + bone_ent = self.Owner + else + // when the weapon is dropped + bone_ent = self + end + + for k, name in pairs( self.wRenderOrder ) do + + local v = self.WElements[name] + if (!v) then self.wRenderOrder = nil break end + if (v.hide) then continue end + + local pos, ang + + if (v.bone) then + pos, ang = self:GetBoneOrientation( self.WElements, v, bone_ent ) + else + pos, ang = self:GetBoneOrientation( self.WElements, v, bone_ent, "ValveBiped.Bip01_R_Hand" ) + end + + if (!pos) then continue end + + local model = v.modelEnt + local sprite = v.spriteMaterial + + if (v.type == "Model" and IsValid(model)) then + + model:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z ) + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + model:SetAngles(ang) + //model:SetModelScale(v.size) + local matrix = Matrix() + matrix:Scale(v.size) + model:EnableMatrix( "RenderMultiply", matrix ) + + if (v.material == "") then + model:SetMaterial("") + elseif (model:GetMaterial() != v.material) then + model:SetMaterial( v.material ) + end + + if (v.skin and v.skin != model:GetSkin()) then + model:SetSkin(v.skin) + end + + if (v.bodygroup) then + for k, v in pairs( v.bodygroup ) do + if (model:GetBodygroup(k) != v) then + model:SetBodygroup(k, v) + end + end + end + + if (v.surpresslightning) then + render.SuppressEngineLighting(true) + end + + render.SetColorModulation(v.color.r/255, v.color.g/255, v.color.b/255) + render.SetBlend(v.color.a/255) + model:DrawModel() + render.SetBlend(1) + render.SetColorModulation(1, 1, 1) + + if (v.surpresslightning) then + render.SuppressEngineLighting(false) + end + + elseif (v.type == "Sprite" and sprite) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + render.SetMaterial(sprite) + render.DrawSprite(drawpos, v.size.x, v.size.y, v.color) + + elseif (v.type == "Quad" and v.draw_func) then + + local drawpos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + cam.Start3D2D(drawpos, ang, v.size) + v.draw_func( self ) + cam.End3D2D() + + end + + end + + end + + function SWEP:GetBoneOrientation( basetab, tab, ent, bone_override ) + + local bone, pos, ang + if (tab.rel and tab.rel != "") then + + local v = basetab[tab.rel] + + if (!v) then return end + + // Technically, if there exists an element with the same name as a bone + // you can get in an infinite loop. Let's just hope nobody's that stupid. + pos, ang = self:GetBoneOrientation( basetab, v, ent ) + + if (!pos) then return end + + pos = pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z + ang:RotateAroundAxis(ang:Up(), v.angle.y) + ang:RotateAroundAxis(ang:Right(), v.angle.p) + ang:RotateAroundAxis(ang:Forward(), v.angle.r) + + else + + bone = ent:LookupBone(bone_override or tab.bone) + + if (!bone) then return end + + pos, ang = Vector(0,0,0), Angle(0,0,0) + local m = ent:GetBoneMatrix(bone) + if (m) then + pos, ang = m:GetTranslation(), m:GetAngles() + end + + if (IsValid(self.Owner) and self.Owner:IsPlayer() and + ent == self.Owner:GetViewModel() and self.ViewModelFlip) then + ang.r = -ang.r // Fixes mirrored models + end + + end + + return pos, ang + end + + function SWEP:CreateModels( tab ) + + if (!tab) then return end + + // Create the clientside models here because Garry says we can't do it in the render hook + for k, v in pairs( tab ) do + if (v.type == "Model" and v.model and v.model != "" and (!IsValid(v.modelEnt) or v.createdModel != v.model) and + string.find(v.model, ".mdl") and file.Exists (v.model, "GAME") ) then + + v.modelEnt = octolib.createDummy(v.model, RENDER_GROUP_VIEW_MODEL_OPAQUE) + if (IsValid(v.modelEnt)) then + v.modelEnt:SetPos(self:GetPos()) + v.modelEnt:SetAngles(self:GetAngles()) + v.modelEnt:SetParent(self) + v.modelEnt:SetNoDraw(true) + v.createdModel = v.model + else + v.modelEnt = nil + end + + elseif (v.type == "Sprite" and v.sprite and v.sprite != "" and (!v.spriteMaterial or v.createdSprite != v.sprite) + and file.Exists ("materials/"..v.sprite..".vmt", "GAME")) then + + local name = v.sprite.."-" + local params = { ["$basetexture"] = v.sprite } + // make sure we create a unique name based on the selected options + local tocheck = { "nocull", "additive", "vertexalpha", "vertexcolor", "ignorez" } + for i, j in pairs( tocheck ) do + if (v[j]) then + params["$"..j] = 1 + name = name.."1" + else + name = name.."0" + end + end + + v.createdSprite = v.sprite + v.spriteMaterial = CreateMaterial(name,"UnlitGeneric",params) + + end + end + + end + + local allbones + local hasGarryFixedBoneScalingYet = false + + function SWEP:UpdateBonePositions(vm) + + if self.ViewModelBoneMods then + + if (!vm:GetBoneCount()) then return end + + // !! WORKAROUND !! // + // We need to check all model names :/ + local loopthrough = self.ViewModelBoneMods + if (!hasGarryFixedBoneScalingYet) then + allbones = {} + for i=0, vm:GetBoneCount() do + local bonename = vm:GetBoneName(i) + if (self.ViewModelBoneMods[bonename]) then + allbones[bonename] = self.ViewModelBoneMods[bonename] + else + allbones[bonename] = { + scale = Vector(1,1,1), + pos = Vector(0,0,0), + angle = Angle(0,0,0) + } + end + end + + loopthrough = allbones + end + // !! ----------- !! // + + for k, v in pairs( loopthrough ) do + local bone = vm:LookupBone(k) + if (!bone) then continue end + + // !! WORKAROUND !! // + local s = Vector(v.scale.x,v.scale.y,v.scale.z) + local p = Vector(v.pos.x,v.pos.y,v.pos.z) + local ms = Vector(1,1,1) + if (!hasGarryFixedBoneScalingYet) then + local cur = vm:GetBoneParent(bone) + while(cur >= 0) do + local pscale = loopthrough[vm:GetBoneName(cur)].scale + ms = ms * pscale + cur = vm:GetBoneParent(cur) + end + end + + s = s * ms + // !! ----------- !! // + + if vm:GetManipulateBoneScale(bone) != s then + vm:ManipulateBoneScale( bone, s ) + end + if vm:GetManipulateBoneAngles(bone) != v.angle then + vm:ManipulateBoneAngles( bone, v.angle ) + end + if vm:GetManipulateBonePosition(bone) != p then + vm:ManipulateBonePosition( bone, p ) + end + end + else + self:ResetBonePositions(vm) + end + + end + + function SWEP:ResetBonePositions(vm) + + if (!vm:GetBoneCount()) then return end + for i=0, vm:GetBoneCount() do + vm:ManipulateBoneScale( i, Vector(1, 1, 1) ) + vm:ManipulateBoneAngles( i, Angle(0, 0, 0) ) + vm:ManipulateBonePosition( i, Vector(0, 0, 0) ) + end + + end + + /************************** + Global utility code + **************************/ + + // Fully copies the table, meaning all tables inside this table are copied too and so on (normal table.Copy copies only their reference). + // Does not copy entities of course, only copies their reference. + // WARNING: do not use on tables that contain themselves somewhere down the line or you'll get an infinite loop + function table.FullCopy( tab ) + + if (!tab) then return nil end + + local res = {} + for k, v in pairs( tab ) do + if (type(v) == "table") then + res[k] = table.FullCopy(v) // recursion ho! + elseif (type(v) == "Vector") then + res[k] = Vector(v.x, v.y, v.z) + elseif (type(v) == "Angle") then + res[k] = Angle(v.p, v.y, v.r) + else + res[k] = v + end + end + + return res + + end + +end + diff --git a/garrysmod/addons/feature-estates/lua/autorun/estates.lua b/garrysmod/addons/feature-estates/lua/autorun/estates.lua new file mode 100644 index 0000000..32c43fd --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/autorun/estates.lua @@ -0,0 +1,11 @@ +octolib.server('estates/sv_estates') +octolib.client('estates/cl_estates') + +octolib.shared('estates/ext_doors') +octolib.shared('estates/sh_groups') + +octolib.client('estates/cl_doors') + +octolib.server('estates/ext_doors_sv') +octolib.server('estates/sv_players') +octolib.server('estates/sv_tax') diff --git a/garrysmod/addons/feature-estates/lua/cmenu/items/sell-estates.lua b/garrysmod/addons/feature-estates/lua/cmenu/items/sell-estates.lua new file mode 100644 index 0000000..39216ad --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/cmenu/items/sell-estates.lua @@ -0,0 +1,7 @@ +octogui.cmenu.registerItem('rp', 'sellall', { + text = L.sell_doors, + icon = octolib.icons.silk16('door_out'), + action = octolib.fQuery(L.sell_doors_confirm, L.sell_doors, L.ok, function() + netstream.Start('dbg-estates.sellAll') + end, L.cancel), +}) diff --git a/garrysmod/addons/feature-estates/lua/estates/cl_doors.lua b/garrysmod/addons/feature-estates/lua/estates/cl_doors.lua new file mode 100644 index 0000000..f995fe9 --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/cl_doors.lua @@ -0,0 +1,103 @@ +local white = Color(255, 255, 255, 200) +local red = Color(128, 30, 30, 255) + +surface.CreateFont('dbg-doors', { + font = 'Calibri', + extended = true, + size = 76, + weight = 350, +}) + +local types = { + [1] = '(Для бизнеса)', + [2] = '(Для проживания)' +} + +local function doorText(ent) + + local playerOwned = ent:GetPlayerOwner() + local doorInfo = {} + + local door = dbgEstates.getData(ent:GetEstateID()) or {} + local owners = door.owners or {} + if not owners[1] then return doorInfo end + + doorInfo[#doorInfo + 1] = L.keys_owned_by + for _,v in ipairs(owners) do + + if octolib.string.isSteamID(v) then + local ply = player.GetBySteamID(v) + doorInfo[#doorInfo + 1] = IsValid(ply) and ply:Name() or ('Игрок вышел (' .. playerOwned .. ')') + + elseif string.StartWith(v, 'g:') then + local name = dbgDoorGroups.groups[v:sub(3)] + if name then + doorInfo[#doorInfo + 1] = name + end + + elseif string.StartWith(v, 'j:') then + local job = DarkRP.getJobByCommand(v:sub(3)) + if job then + doorInfo[#doorInfo + 1] = job.name + end + end + + end + + if LocalPlayer():Team() == TEAM_ADMIN then doorInfo[#doorInfo + 1] = types[door.purpose] end + + return doorInfo +end + +local identifiedInfo = {} + +netstream.Hook('dbg-estates.unown', function(estIdx) + identifiedInfo[estIdx] = nil +end) +netstream.Hook('dbg-doors.identify', function(estIdx) + if not LocalPlayer():isCP() then return end + local door = dbgEstates.getData(estIdx).doors[1] + local info = doorText(door) + if #info > 0 then identifiedInfo[estIdx] = info end +end) + +local buyText +hook.Add('dbg-view.chPaint', 'dbg-doors', function(tr, icon) + + local ent = LocalPlayer():GetEyeTrace().Entity + if tr.Fraction < 0.05 and IsValid(ent) and ent:IsDoor() then + local blocked, owned = ent:IsBlocked(), ent:IsOwned() + local doorInfo = {} + + local title = ent:GetTitle() + if title then + doorInfo[#doorInfo + 1] = title + end + + local job = RPExtraTeams[LocalPlayer():Team()] + if not job.hobo then + if owned then + local mergeInfo + if LocalPlayer():Team() == TEAM_ADMIN then + mergeInfo = doorText(ent) + else + mergeInfo = identifiedInfo[ent:GetEstateID()] or {} + end + for _, v in ipairs(mergeInfo) do + doorInfo[#doorInfo + 1] = v + end + elseif not blocked then + buyText = buyText or L.to_buy .. (utf8.upper(input.LookupBinding('use')) or 'E') + doorInfo[#doorInfo + 1] = L.priceTag:format(DarkRP.formatMoney(ent:GetPrice())) + doorInfo[#doorInfo + 1] = buyText + end + end + + draw.DrawText(table.concat(doorInfo, '\n'), 'dbg-doors', 2, 37, color_black, TEXT_ALIGN_CENTER) + draw.DrawText(table.concat(doorInfo, '\n'), 'dbg-doors', 0, 35, (blocked or owned) and red or white, TEXT_ALIGN_CENTER) + end + +end) + +local meta = FindMetaTable('Entity') +function meta:drawOwnableInfo() end diff --git a/garrysmod/addons/feature-estates/lua/estates/cl_estates.lua b/garrysmod/addons/feature-estates/lua/estates/cl_estates.lua new file mode 100644 index 0000000..7d0514a --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/cl_estates.lua @@ -0,0 +1,45 @@ +dbgEstates = dbgEstates or {} +local empty = {} + +local function data() + return netvars.GetNetVar('dbg-estates', empty) +end + +--[[ + (table) + Returns a data about estate, + or an empty table if data is missing. +]] +function dbgEstates.getData(estIdx) + return estIdx and data()[estIdx] or empty +end + +--[[ + (bool) + Returns wether estate with this id exists +]] +function dbgEstates.exists(estIdx) + return data()[estIdx] ~= nil +end + +local permaMarkers = {} +hook.Add('octolib.netVarUpdate', 'dbg-estates', function(_, name, data) + if name == 'dbg-estates' then + for k,v in pairs(permaMarkers) do + if not data[k] or not data[k].marker or not data[k].marker.perma then + v:Remove() + end + end + for k,v in pairs(data) do + if v.marker and v.marker.perma then + local mData = v.marker + if not permaMarkers[k] or permaMarkers[k].removed then + permaMarkers[k] = octomap.createMarker('est_' .. k):SetClickable(true) + permaMarkers[k].LeftClick = octomap.sidebarMarkerClick + end + permaMarkers[k]:SetIcon(mData.icon):SetPos(mData.pos):AddToSidebar(mData.name, mData.group or ('est_' .. k)) + permaMarkers[k].sort = mData.sort + end + end + end +end) diff --git a/garrysmod/addons/feature-estates/lua/estates/ext_doors.lua b/garrysmod/addons/feature-estates/lua/estates/ext_doors.lua new file mode 100644 index 0000000..b8beb44 --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/ext_doors.lua @@ -0,0 +1,84 @@ +local meta = FindMetaTable('Entity') + +local doorClasses = octolib.array.toKeys { 'prop_door_rotating', 'func_door_rotating', 'func_door' } + +function meta:IsDoor() + if not IsValid(self) then return false end + return tobool(doorClasses[self:GetClass()]) +end + +function meta:isKeysOwnable() + return self:IsVehicle() or self:IsDoor() +end + +--[[ + (string|nil) + Returns ID of estate this door belongs to +]] +function meta:GetEstateID() + return self:GetNetVar('estate', 0) +end + +--[[ + (table|nil) + Returns information about estate this door belongs to +]] +function meta:GetEstate() + return dbgEstates.getData(self:GetEstateID()) +end + +--[[ + (SteamID|nil) + Returns SteamID of player who owns this door, or nil if no owner +]] +function meta:GetPlayerOwner() + if not self:IsDoor() or self:IsBlocked() then return end + local data = self:GetEstate() + if not data then return end + for _,v in ipairs(data.owners or {}) do + if octolib.string.isSteamID(v) then + return v + end + end +end + +function meta:IsOwned() + if not self:IsDoor() or self:IsBlocked() then return end + local data = self:GetEstate() + if not data then return false end + data.owners = data.owners or {} + return data.owners[1] ~= nil +end + +function meta:IsBlocked() + if not self:IsDoor() then return end + return self:GetEstateID() == 0 +end + +function meta:CanBeOwned() + if not self:IsDoor() or self:IsBlocked() then return end + return not self:GetEstate().owners[1] +end + +function meta:GetPrice() + if not self:IsDoor() or self:IsBlocked() then return 0 end + local data = self:GetEstate() + return data and data.price or GAMEMODE.Config.doorcost or 50 +end + +function meta:GetTitle() + return self:IsDoor() and self:GetNetVar('tempTitle') +end + +function meta:ShouldObey(ply) + if not self:IsDoor() or self:IsBlocked() then return end + if self:GetPlayerOwner() == ply:SteamID() then return true end + + local data = self:GetEstate() -- check job + local job = 'j:' .. RPExtraTeams[ply:Team()].command + for _,v in ipairs(data.owners or {}) do + if v == job then return true end + end + + return false +end diff --git a/garrysmod/addons/feature-estates/lua/estates/ext_doors_sv.lua b/garrysmod/addons/feature-estates/lua/estates/ext_doors_sv.lua new file mode 100644 index 0000000..8a0a8e4 --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/ext_doors_sv.lua @@ -0,0 +1,140 @@ +local meta = FindMetaTable('Entity') + +function meta:AddGroupOwner(gID) + if not self:IsDoor() or self:IsBlocked() then return end + if not isstring(gID) or not dbgDoorGroups.groups[gID] then return end + if dbgEstates.addOwner(self:GetEstateID(), 'g:' .. gID) then + hook.Run('dbg-estates.owned', 'g:' .. gID, self:GetEstateID()) + return true + end + return false +end + +function meta:AddJobOwner(jID) + if not self:IsDoor() or self:IsBlocked() then return end + if not isstring(jID) or not DarkRP.getJobByCommand(jID) then return end + if dbgEstates.addOwner(self:GetEstateID(), 'j:' .. jID) then + hook.Run('dbg-estates.owned', 'j:' .. jID, self:GetEstateID()) + return true + end + return false +end + +function meta:SetPlayerOwner(ply) + if not self:IsDoor() or self:IsBlocked() then return end + if not IsEntity(ply) or not ply:IsPlayer() or not self:IsDoor() then return end + if dbgEstates.addOwner(self:GetEstateID(), ply:SteamID()) then + hook.Run('dbg-estates.owned', ply:SteamID(), self:GetEstateID()) + return true + end + return false +end + +function meta:RemoveOwner(owner) + if not self:IsDoor() or self:IsBlocked() then return end + if IsEntity(owner) then + if not owner:IsPlayer() then return end + owner = owner:SteamID() + elseif not isstring(owner) then return end + local estId = self:GetEstateID() + if dbgEstates.removeOwner(estId, owner) then + hook.Run('dbg-estates.unowned', owner, estId) + if octolib.string.isSteamID(owner) then + local doors = self:GetEstate().doors + if not doors then return end + for _,v in ipairs(doors) do + v:Fire('Close') + v:DoLock() + hook.Run('dbg-doors.unowned', v, owner, estId) + end + end + return true + end +end + +function meta:SetBlocked(blocked) + + if not self:IsDoor() or self:IsBlocked() == blocked then return end + + if blocked then + dbgEstates.unlinkDoor(self) + else + dbgEstates.createEstate(self) + end +end + +function meta:SetTitle(title, save) + if title ~= nil and not isstring(title) then return end + if self:GetPlayerOwner() == nil or save then + self.title = title + if self:IsBlocked() then + dbgEstates.unlinkDoor(self) + end + dbgEstates.planDBUpdate() + end + self:SetNetVar('tempTitle', title or self.title) +end + +function meta:IsDoorLocked() + if not self:IsDoor() then return end + return self:GetInternalVariable('m_bLocked') or false +end + +function meta:CanBeLockedBy(ply) + if not self:IsDoor() or self:IsBlocked() then return end + local h = hook.Run('canKeysLock', ply, self) + if h ~= nil then return h end + return self:ShouldObey(ply) +end + +function meta:CanBeUnlockedBy(ply) + if not self:IsDoor() or self:IsBlocked() then return end + local h = hook.Run('canKeysUnlock', ply, self) + if h ~= nil then return h end + return self:ShouldObey(ply) +end + +function meta:DoLock() + self:Fire('lock', '', 0) + hook.Run('onKeysLocked', self) +end + +function meta:DoUnlock() + self:Fire('unlock', '', 0) + hook.Run('onKeysUnlocked', self) +end + +-- This method is used for "double doors", which need to be opened and closed at the same time. +function meta:GetPairedDoor() + + -- we've already found our pair + if self.secondDoor ~= nil then return self.secondDoor end + + -- let's check if we have master + local tbl = self:GetSaveTable() + local pair = tbl.m_hOwnerEntity + if IsValid(pair) then + self.secondDoor, pair.secondDoor = pair, self + return self.secondDoor + end + pair = tbl.m_hMaster + if IsValid(pair) then + self.secondDoor, pair.secondDoor = pair, self + return self.secondDoor + end + + -- we're master, let's find our slave + for _, v in ipairs(ents.GetAll()) do + if not v:IsDoor() then continue end + local tbl = v:GetSaveTable() + if tbl.m_hOwnerEntity == self or tbl.m_hMaster == self then + self.secondDoor, v.secondDoor = v, self + return self.secondDoor + end + end + + -- we're single + self.secondDoor = NULL + return self.secondDoor + +end diff --git a/garrysmod/addons/feature-estates/lua/estates/sh_groups.lua b/garrysmod/addons/feature-estates/lua/estates/sh_groups.lua new file mode 100644 index 0000000..4d73825 --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/sh_groups.lua @@ -0,0 +1,15 @@ +dbgDoorGroups = dbgDoorGroups or {} +dbgDoorGroups.groups = dbgDoorGroups.groups or {} + +function dbgDoorGroups.registerGroup(id, name) + if not isstring(id) then return end + name = tostring(name) + id = string.lower(id) + dbgDoorGroups.groups[id] = name +end + +-- DEFAULT +hook.Add('Think', 'dbg-groups', function() + hook.Remove('Think', 'dbg-groups') + dbgDoorGroups.registerGroup('police', 'Полиция') +end) diff --git a/garrysmod/addons/feature-estates/lua/estates/sv_estates.lua b/garrysmod/addons/feature-estates/lua/estates/sv_estates.lua new file mode 100644 index 0000000..299297a --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/sv_estates.lua @@ -0,0 +1,347 @@ +dbgEstates = dbgEstates or {} +dbgEstates.data = dbgEstates.data or {} +local empty = {} +local toRem = {} + +local radius, limit = 5, 15 +local sides = { Vector(1,0,0), Vector(-1,0,0), Vector(0,1,0), Vector(0,-1,0), Vector(0,0,1), Vector(0,0,-1) } +local function getDoorPos(ent) + local entPos = ent:GetPos() + if table.HasValue(ents.FindInSphere(entPos, radius), ent) then return entPos end + + for i = 0, limit do + local delta = radius * 2 * i + for _, side in ipairs(sides) do + pos = entPos + side * delta + if table.HasValue(ents.FindInSphere(pos, radius), ent) then return pos end + end + end +end + +local function nextAvailable() + local nxt, estIdx = 1 + while not estIdx do + estIdx = not dbgEstates.data[nxt] and nxt or nil + nxt = nxt + 1 + end + return estIdx +end + +local planDBUpdate = octolib.func.debounce(function() + local all = {} + for _,v in ipairs(toRem) do + all[#all + 1] = v + end + table.Empty(toRem) + for k,v in pairs(dbgEstates.data) do + local tbl = table.Copy(v) + tbl.owners = {} + tbl.doors = {} + for _,owner in ipairs(v.owners or {}) do + if not octolib.string.isSteamID(owner) then + tbl.owners[#tbl.owners + 1] = owner + end + end + for _,door in ipairs(v.doors or {}) do + tbl.doors[#tbl.doors + 1] = { + getDoorPos(door) or door:MapCreationID(), + door.title, + } + end + all[#all + 1] = {k, tbl} + end + octolib.func.throttle(all, 5, 0.5, function(nxt) + if isnumber(nxt) then + octolib.db:PrepareQuery([[DELETE FROM `dbg_estates` WHERE `id`=?]], {nxt}) + else + local k, v = unpack(nxt) + local dat = pon.encode(v or {}) + octolib.db:PrepareQuery([[INSERT INTO `dbg_estates` (`id`, `map`, `data`) VALUES (?,?,?) ON DUPLICATE KEY UPDATE `data`=?]], + {k, game.GetMap(), dat, dat}) + end + end) +end, 0) + +local updateNW = octolib.func.debounce(function() + netvars.SetNetVar('dbg-estates', dbgEstates.data) +end, 0) + +-- unlinks the door from estates +local function removeFromPreviousEstate(door) + local curEst = door:GetNetVar('estate') + if curEst and dbgEstates.data[curEst] and dbgEstates.data[curEst].doors then + table.RemoveByValue(dbgEstates.data[curEst].doors, door) + if not dbgEstates.data[curEst].doors[1] then + dbgEstates.data[curEst] = nil + toRem[#toRem + 1] = curEst + updateNW() + planDBUpdate() + end + end + door:SetNetVar('estate') +end + +--[[ + (void) + Sets specified price for estate and saves it +]] +function dbgEstates.setEstatePrice(estIdx, price) + if not isnumber(price) or price < 0 then return end + if not dbgEstates.data[estIdx] then return end + dbgEstates.data[estIdx].price = price + updateNW() + planDBUpdate() +end + +--[[ + (void) + Sets estate purpose of estate and saves it + 0 - both business and residency + 1 - business only + 2 - residency only +]] +function dbgEstates.setPurpose(estIdx, purpose) + if not purpose then purpose = 0 end + if not (isnumber(purpose) and octolib.math.inRange(purpose, 0, 2)) then return end + if not dbgEstates.data[estIdx] then return end + if purpose == 0 then + dbgEstates.data[estIdx].purpose = nil + else + dbgEstates.data[estIdx].purpose = purpose + end + updateNW() + planDBUpdate() +end + +--[[ + (void) + Sets marker position for estate and saves it +]] +function dbgEstates.updateMarker(estIdx, pos) + if pos ~= nil and not istable(pos) then return end + if not dbgEstates.data[estIdx] then return end + dbgEstates.data[estIdx].marker = pos + updateNW() + planDBUpdate() +end + +--[[ + (table) + Returns estate's data, + or empty table if doesn't exist. + You should NOT modify this! +]] +function dbgEstates.getData(estIdx) + return dbgEstates.data[estIdx] or empty +end + +--[[ + (bool) + Returns wether estate with this id exists +]] +function dbgEstates.exists(estIdx) + return dbgEstates.data[estIdx] ~= nil +end + +--[[ + (void) + Forces something to own an estate. + Can be: + 'STEAM_X:Y:Z' for player (removes any current SteamID owners) + 'g:command' for organization (e.g. police) + 'j:command' for job (e.g. mayor) +]] +function dbgEstates.addOwner(estIdx, owner) + if not estIdx or not dbgEstates.data[estIdx] then return end + dbgEstates.data[estIdx].owners = dbgEstates.data[estIdx].owners or {} + + local owners = dbgEstates.data[estIdx].owners + if table.HasValue(owners, owner) then return false end + + local isPly = octolib.string.isSteamID(owner) + if isPly then -- there can be only one player owner + for i = #owners, 1, -1 do + if octolib.string.isSteamID(owners[i]) then + table.remove(owners, i) + end + end + end + + owners[#owners + 1] = owner + updateNW() + if not isPly then planDBUpdate() end + + return true +end + +--[[ + (bool) + Forces something to unown an estate. + Can be: + 'STEAM_X:Y:Z' for player (removes any current SteamID owners) + 'g:command' for organization (e.g. police) + 'j:command' for job (e.g. mayor) +]] +function dbgEstates.removeOwner(estIdx, owner) + if not estIdx or not dbgEstates.data[estIdx] then return end + dbgEstates.data[estIdx].owners = dbgEstates.data[estIdx].owners or {} + + local owns = dbgEstates.data[estIdx].owners + local removed = table.RemoveByValue(owns, owner) + if removed then + if not octolib.string.isSteamID(owner) then + planDBUpdate() + else + for _,v in ipairs(dbgEstates.data[estIdx].doors or empty) do + v:SetNetVar('tempTitle', v.title) + v:DoLock() + end + end + updateNW() + end + + return tobool(removed) +end + +--[[ + (void) + Changes owners of an estate. + Please use it only if you know what you do! +]] +function dbgEstates.updateOwners(estIdx, owners) + if (owners ~= nil and not istable(owners)) or not estIdx or not dbgEstates.data[estIdx] then return end + dbgEstates.data[estIdx].owners = owners or {} + planDBUpdate() + updateNW() +end + +--[[ + (void) + Saves everything to database +]] +function dbgEstates.planDBUpdate() + planDBUpdate() +end + +--[[ + (bool) + Links door to a new estate +]] +function dbgEstates.linkDoor(door, estIdx) + if not IsEntity(door) or not door:IsDoor() then return end + if not dbgEstates.exists(estIdx) then return end + dbgEstates.data[estIdx].doors = dbgEstates.data[estIdx].doors or {} + dbgEstates.data[estIdx].doors[#dbgEstates.data[estIdx].doors + 1] = door + door:SetNetVar('estate', estIdx) + if not dbgEstates.data[estIdx].owners[1] then + door:DoLock() + end + updateNW() + planDBUpdate() + return true +end + +--[[ + (void) + Removes a door from any estates so that + it can't be owned by anyone. +]] +function dbgEstates.unlinkDoor(door) + if not IsEntity(door) or not door:IsDoor() then return end + removeFromPreviousEstate(door) + door:DoUnlock() + if door.title then -- we want to save title so we move it to a zero estate + dbgEstates.data[0] = dbgEstates.data[0] or { + doors = {}, + price = 0, + owners = {}, + name = 'Заблокированные', + } + dbgEstates.linkDoor(door, 0) + else + updateNW() + planDBUpdate() + end +end + +--[[ + (int) + Moves door to a new estate with only this door + and returns ID of the new estate. +]] +function dbgEstates.createEstate(door, name) + if not IsEntity(door) or not door:IsDoor() then return end + removeFromPreviousEstate(door) + local estIdx = nextAvailable() + dbgEstates.data[estIdx] = { + doors = {door}, + price = GAMEMODE.Config.doorcost or 50, + owners = {}, + name = tostring(name), + } + door:SetNetVar('estate', estIdx) + door:DoLock() + updateNW() + planDBUpdate() + return estIdx +end + +function dbgEstates.getNearest(pos) + local nearest = math.huge + local nearestid = 0 + + for i, est in pairs(dbgEstates.data) do + for id, ent in pairs(est.doors) do + if not IsValid(ent) then continue end + local dis = ent:GetPos():DistToSqr(pos) + if dis < nearest then + nearest = dis + nearestid = i + end + end + end + return dbgEstates.data[nearestid] +end + +hook.Add('octolib.db.init', 'dbg-premises.init', function() + + octolib.db:RunQuery([[CREATE TABLE IF NOT EXISTS `dbg_estates` ( + `id` INT UNSIGNED NOT NULL, + `map` VARCHAR(50) NOT NULL, + `data` TEXT NULL, + PRIMARY KEY (`id`, `map`) + )]]) + + dbgEstates.data = {} + octolib.db:PrepareQuery([[SELECT * FROM `dbg_estates` WHERE `map`=?]], {game.GetMap()}, function(q, st, res) + if not istable(res) or not res[1] then return end + for _,v in ipairs(res) do + local dat = pon.decode(v.data or '[}') or {} + if dat.doors then + for i = 1, #dat.doors do + local ent + local posOrID = dat.doors[i][1] + if isnumber(posOrID) then + -- ent = ents.GetMapCreatedEntity(posOrID) + else + for _, e in ipairs(ents.FindInSphere(dat.doors[i][1], radius)) do + if IsValid(e) and e:IsDoor() then ent = e break end + end + end + + local title = dat.doors[i][2] + dat.doors[i] = ent + if not ent then continue end + + ent:SetNetVar('estate', v.id) + ent.title = title + ent:SetNetVar('tempTitle', title) + ent:DoLock() + end + end + dbgEstates.data[v.id] = dat + end + updateNW() + end) + +end) diff --git a/garrysmod/addons/feature-estates/lua/estates/sv_players.lua b/garrysmod/addons/feature-estates/lua/estates/sv_players.lua new file mode 100644 index 0000000..ba830eb --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/sv_players.lua @@ -0,0 +1,46 @@ +local meta = FindMetaTable('Player') + +function meta:GetOwnedEstates() + local ests = {} + local sID = self:SteamID() + for i,data in pairs(netvars.GetNetVar('dbg-estates') or {}) do + for _,v in ipairs(data.owners or {}) do + if v == sID then + ests[#ests + 1] = i + end + end + end + return ests +end + +function meta:UnownAllDoors() + local sID, price, amount = self:SteamID(), 0, 0 + for i,v in pairs(netvars.GetNetVar('dbg-estates') or {}) do + if dbgEstates.removeOwner(i, sID) then + price = price + (v.price or GAMEMODE.config.doorcost) + amount = amount + 1 + hook.Run('dbg-estates.unowned', sID, i) + local doors = dbgEstates.getData(i).doors + if not doors then return end + for _,door in ipairs(doors) do + door:Fire('Close') + door:DoLock() + hook.Run('dbg-doors.unowned', door, sID, i) + end + end + end + return price, amount +end + +netstream.Hook('dbg-estates.sellAll', function(ply) + if not ply:Alive() or ply:IsGhost() then + return ply:Notify('warning', L.dead_cant_do_this) + end + if netvars.GetNetVar('pendingTax') then + return ply:Notify('warning', 'Скоро платить налоги, подожди') + end + local pr, amount = ply:UnownAllDoors() + pr = math.floor(pr * 0.666 + 0.5) + ply:addMoney(pr) + ply:Notify(('Ты продал %s помещени%s за %s'):format(amount, octolib.string.formatCount(amount, 'е', 'я', 'й'), DarkRP.formatMoney(pr))) +end) diff --git a/garrysmod/addons/feature-estates/lua/estates/sv_tax.lua b/garrysmod/addons/feature-estates/lua/estates/sv_tax.lua new file mode 100644 index 0000000..a46c49d --- /dev/null +++ b/garrysmod/addons/feature-estates/lua/estates/sv_tax.lua @@ -0,0 +1,108 @@ +-- YOU CAN CHANGE THESE VALUES IF YOU WANT TO +local taxDelay = 600 +local warnIn = 120 + +--[[ +MIT License + +Copyright (c) 2019-2020 Octothorp Team + +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 payed = {} + +local function updateTaxes(ply) + if not octolib.string.isSteamID(ply) then return end + ply = player.GetBySteamID(ply) + if not IsValid(ply) then return end + + local taxes = octolib.table.reduce(ply:GetOwnedEstates(), function(a, v) + local data = dbgEstates.getData(v) + if data then + a = a + (data.price or GAMEMODE.Config.doorcost) * (GAMEMODE.Config.propertytaxcoeff or 0.1) + end + return a + end, 0) + ply:SetNetVar('taxes', math.ceil(taxes)) +end + +hook.Add('dbg-estates.owned', 'dbg-estates.tax', updateTaxes) +hook.Add('dbg-estates.unowned', 'dbg-estates.tax', updateTaxes) +hook.Add('PlayerInitialSpawn', 'dbg-estates.tax', function(ply) + + updateTaxes(ply) + + if payed[ply:SteamID()] then + ply:Notify(('Пока тебя не было, с твоего банковского счета было снято %s за аренду помещений'):format(DarkRP.formatMoney(payed[ply:SteamID()]))) + payed[ply:SteamID()] = nil + end + +end) + +local function taxTick() + for _,v in ipairs(player.GetAll()) do + local tax = v:GetNetVar('taxes', 0) + if tax > 0 then + v:Notify(L.property_tax_warn:format(DarkRP.formatMoney(tax))) + end + end + netvars.SetNetVar('pendingTax', true) + + timer.Simple(warnIn, function() + local plyTaxes = {} + for i,v in pairs(netvars.GetNetVar('dbg-estates') or {}) do + for _,own in ipairs(v.owners or {}) do + if octolib.string.isSteamID(own) then + plyTaxes[own] = plyTaxes[own] or {} + plyTaxes[own].tax = (plyTaxes[own].tax or 0) + (v.price or GAMEMODE.Config.doorcost) + plyTaxes[own].ests = plyTaxes[own].ests or {} + plyTaxes[own].ests[#plyTaxes[own].ests + 1] = i + end + end + end + + for steamID,tax in pairs(plyTaxes) do + local price = math.ceil(tax.tax * (GAMEMODE.Config.propertytaxcoeff or 0.1)) + local ply = player.GetBySteamID(steamID) + BraxBank.PlayerMoneyAsync(steamID, function(bal) + if bal <= price then + for _,v in ipairs(tax.ests) do + dbgEstates.removeOwner(v, steamID) + end + if IsValid(ply) then + ply:Notify('warning', L.property_tax_cant_afford) + end + return + end + + BraxBank.UpdateMoney(steamID, bal - price) + if IsValid(ply) then + ply:Notify(L.property_tax:format(DarkRP.formatMoney(price))) + else + payed[steamID] = (payed[steamID] or 0) + price + end + end) + end + netvars.SetNetVar('pendingTax') + + timer.Simple(taxDelay - warnIn, taxTick) + end) +end +timer.Simple(taxDelay - warnIn, taxTick) diff --git a/garrysmod/addons/feature-fishing/lua/autorun/server/fishing.lua b/garrysmod/addons/feature-fishing/lua/autorun/server/fishing.lua new file mode 100644 index 0000000..5ec9457 --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/autorun/server/fishing.lua @@ -0,0 +1,88 @@ +octolib.server('fishing/octoinv/items') +octolib.server('fishing/octoinv/shop') +octolib.server('fishing/octoinv/crafts') + +local baits = {'fish', 'prawn', 'bacon', 'cheese', 'classic', 'synthetic'} +local favorite = {} + +hook.Add('Initialize', 'dbg-fishing.init', function() + local k = 0 + math.randomseed(os.time()) + for _,v in RandomPairs(baits) do + favorite[v] = true + k = k + 1 + if k == 2 then break end + end + octolib.msg('Selected favorite fish baits for today: %s', table.concat(table.GetKeys(favorite), ', ')) +end) + +local bodyMats = {} +timer.Create('dbg-fishing.checkRagdolls', 15, 0, function() + for _,v in ipairs(ents.FindByClass('prop_ragdoll')) do + if v.tazeplayer or v.studied then continue end + if not v:GetNetVar('Corpse.name') then return end + if v:WaterLevel() > 0 then + bodyMats[#bodyMats + 1] = { v:GetNetVar('Corpse.name'), v.criminals or {} } + v.studied = true + end + end +end) + +local function getBodyMat() + if not bodyMats[1] then return end + local data = { + collector = '(найдено рыбками)', + corpse = bodyMats[1][1], + criminals = bodyMats[1][2], + expire = os.time() + 60 * 60, + } + table.remove(bodyMats, 1) + return data +end + +local fishItems = { + {5, 'ing_fish1'}, + {5, 'ing_fish2'}, + {3, 'ing_fish3'}, + {1, 'ing_fish4'}, +} + +fishing = {} +function fishing.getLoot(ply, wep) + + if not IsValid(ply) or not IsValid(wep) then return end + local isFavorite = favorite[wep.bait or ''] + + -- 20% with thick line to get loot + if not wep.thin and math.random(5) == 1 then + return octoinv.getRandomLoot({ + mode = 'trash', + flatten = math.Clamp(ply:GetKarma() or 0, -1000, 1000) / 2000 + }).item + end + + -- 25% with thick line and not favorite bait to get body mat (if any) + if not wep.thin and not isFavorite and math.random(4) == 1 then + local bm = getBodyMat() + if bm then return {'body_mat', bm} end + end + + -- 25% of getting double fish with favorive bait + local amount = (isFavorite and math.random(4) == 1) and 2 or 1 + return {octolib.array.randomWeighted(fishItems), amount} + +end + +hook.Add('dbg-weapons.getItemData', 'dbg-fishing.getItemData', function(wep) + + if wep:GetClass() ~= 'weapon_octo_fishing_rod' then return end + + return table.Merge(table.Copy(wep.itemData or {}), { + class = 'fishing_rod', + volume = 0.37, + thin = wep.thin, + usesLeft = wep.usesLeft or 0, + desc = ('Используется для ловли рыбы.\n\nЛеска %s.\nОсталось использований: %s'):format(wep.thin and 'тонкая' or 'крепкая', wep.usesLeft or 0), + }) + +end) diff --git a/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/cl_init.lua b/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/cl_init.lua new file mode 100644 index 0000000..c4d9b1f --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/cl_init.lua @@ -0,0 +1,22 @@ +include 'shared.lua' + +function ENT:Initialize() + self.effects = EffectData() + self.effects:SetScale(4) + self.effects:SetEntity(self) + self.effects:SetFlags(0) +end + +function ENT:Think() + if self:GetNetVar('baiting') and self:WaterLevel() > 0 then + self.effects:SetOrigin(self:GetPos()) + util.Effect('WaterSplash', self.effects, true, true) + end + + self:SetNextClientThink(CurTime() + 1) + return true +end + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/init.lua b/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/init.lua new file mode 100644 index 0000000..1802ea9 --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/init.lua @@ -0,0 +1,97 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +local thinkTime = 3 +local tryPeriod = 5 -- 5 * 3 = 15 seconds +local tryChance = 0.2 -- 20% of tries are successful (average success every 15 * 5 = 75 seconds) + +function ENT:Initialize() + + if not self.rod or not self.owner then + return self:Remove() + end + + self:SetModel('models/holograms/sphere.mdl') + self:SetColor(Color(215,44,57)) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetModelScale(0.5) + self.tryAfter = tryPeriod + + timer.Simple(0, function() + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:SetBuoyancyRatio(0.05) + phys:Wake() + end + end) + +end + +function ENT:Think() + if not IsValid(self.rod) or not IsValid(self.owner) then + self:Remove() + return + end + + if self:GetNetVar('baiting') or self:GetPos():DistToSqr(self.owner:GetPos()) > 1000000 then + self:Remove() + self.rod.bait = nil + return + end + + if self:WaterLevel() < 1 then + local time = (self.notInWaterTime or 0) + 1 + if time >= thinkTime then + self:Remove() + return + end + self.notInWaterTime = time + + self:NextThink(CurTime() + 1) + return true + else + self.notInWaterTime = nil + end + + self.tryAfter = self.tryAfter - 1 + if self.tryAfter <= 0 then + self:SetNetVar('baiting', self:TryBait() or nil) + self.tryAfter = tryPeriod + end + + self:NextThink(CurTime() + thinkTime) + return true +end + +function ENT:NoPlayersAround() + + return #octolib.array.filter(ents.FindInSphere(self:GetPos(), 100), function(ent) return ent:IsPlayer() end) == 0 + +end + +function ENT:DeepEnough() + + local t = {} + t.start = self:GetPos() + t.endpos = t.start - Vector(0, 0, 50) + t.filter = self + t = util.TraceLine(t) + return not t.Hit + +end + +function ENT:TryBait() + + return math.random(1 / tryChance) == 1 and self:NoPlayersAround() and self:DeepEnough() + +end + +hook.Add('EntityRemoved', 'dbg-fishing.fixHook', function(ent) + if ent:GetClass() == 'ent_dbg_fish_float' then + local rod = ent.rod + if IsValid(rod) then rod:SetNetVar('hook', nil) end + end +end) diff --git a/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/shared.lua b/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/shared.lua new file mode 100644 index 0000000..ef5332c --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/entities/ent_dbg_fish_float/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = 'Поплавок' +ENT.Category = L.dobrograd +ENT.Author = "Wani4ka" +ENT.Contact = "4wk@wani4ka.ru" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/feature-fishing/lua/fishing/octoinv/crafts.lua b/garrysmod/addons/feature-fishing/lua/fishing/octoinv/crafts.lua new file mode 100644 index 0000000..2583d36 --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/fishing/octoinv/crafts.lua @@ -0,0 +1,15 @@ +octoinv.registerCraft('fishing_rod', { + name = 'Удочка', + desc = 'Используется для ловли рыбы', + icon = 'octoteam/icons/fishing_rod.png', + time = 5, + conts = {'_hand'}, + ings = { + {'craft_stick', 1}, + {'craft_pulley', 1}, + {'fish_tackle', 1}, + }, + finish = { + {'fishing_rod', 1}, + }, +}) diff --git a/garrysmod/addons/feature-fishing/lua/fishing/octoinv/items.lua b/garrysmod/addons/feature-fishing/lua/fishing/octoinv/items.lua new file mode 100644 index 0000000..43cd3d6 --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/fishing/octoinv/items.lua @@ -0,0 +1,186 @@ +octoinv.registerItem('fish_line', { + name = 'Леска', + icon = 'octoteam/icons/rope.png', + mass = 0.017, + nostack = true, + volume = 0.02, + desc = L.descCraft, +}) + +octoinv.registerItem('fish_tackle', { + name = 'Рыболовная снасть', + icon = 'octoteam/icons/fishing_tackle.png', + mass = 0.21, + volume = 0.04, + desc = L.descCraft, +}) + +octoinv.registerItem('fish_bait', { + name = 'Приманка', + icon = 'octoteam/icons/box3.png', + mass = 0.4, + volume = 0.5, + desc = 'Приманка, используется для ловли рыбы', + nostack = true, + use = { + function(ply, item) + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= 'weapon_octo_fishing_rod' then return false, 'В руках должна быть удочка' end + if wep.bait then return false, 'На крючке уже есть приманка' end + return 'Зацепить на удочку', 'octoteam/icons/fishing_tackle.png', function(ply, item) + local left = (item:GetData('baitsLeft') or 10) - 1 + + wep.bait = item:GetData('bait') + item:SetData('desc', ('В пачке осталось %s %s'):format(left, octolib.string.formatCount(left, 'штука', 'штуки', 'штук'))) + item:SetData('baitsLeft', left) + + return left <= 0 and 1 or 0 + end + end, + } +}) + +local function useAddLine(ply, item, tThin) + local action = ('Прикрепить %s леску'):format(tThin and 'тонкую' or 'крепкую') + if not item then return end + if item:GetData('thin') ~= nil then return end + local cont = item:GetParent() + if not cont then return end + local line = cont:FindItem({class = 'fish_line', thin = tThin}) + if not line then return false, action end + return action, 'octoteam/icons/rope.png', function(ply, item) + if not line then return end + ply:DelayedAction('fishlineadd', 'Крепление лески', { + time = 5, + check = function() return IsValid(ply) and ply:HasItem(item) and item:GetData('thin') == nil and cont:HasItem(line) end, + succ = function() + cont:TakeItem(line) + item:SetData('thin', tThin) + item:SetData('usesLeft', 50) + item:SetData('desc', ('Используется для ловли рыбы.\n\nЛеска %s, новая'):format(tThin and 'тонкая' or 'крепкая')) + cont:QueueSync() + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + local sound = 'weapons/357/357_reload'.. math.random(1, 4) ..'.wav' + ply:EmitSound(sound, 50) + end, + }) + end +end + +octoinv.registerItem('fishing_rod', { + name = 'Удочка', + icon = 'octoteam/icons/fishing_rod.png', + mass = 1.13, + nostack = true, + volume = 0.37, + randomWeight = 0.25, + desc = 'Используется для ловли рыбы', + model = 'models/fishingmod_custom/fishingrod_beta2.mdl', + use = { + function(ply, item) + if item:GetData('thin') == nil then return end + return L.take_in_hand, 'octoteam/icons/fishing_rod.png', function(ply, item) + if item:GetData('thin') == nil then return end + local wep = ents.Create('weapon_octo_fishing_rod') + if not wep:IsValid() then return 0 end + if not wep:IsWeapon() then wep:Remove() return 0 end + if not hook.Call('PlayerCanPickupWeapon', GAMEMODE, ply, wep) then return 0 end + wep:Remove() + + local wep = ply:Give('weapon_octo_fishing_rod') + wep:SetShouldPlayPickupSound(false) + wep.itemData = item:Export() + wep.itemCont = item:GetParent().id + wep.thin = item:GetData('thin') + wep.usesLeft = item:GetData('usesLeft') or 0 + + timer.Simple(0, function() + ply:SelectWeapon('weapon_octo_fishing_rod') + end) + + return 1 + end + end, + function(ply, item) + if item:GetData('thin') == nil then return end + local msg = ('%s леску'):format(item:GetData('usesLeft') == 50 and 'Снять' or 'Выкинуть') + return msg, 'octoteam/icons/rope.png', function(ply, item) + local thin = item:GetData('thin') or false + ply:DelayedAction('fishlineremove', 'Снятие лески', { + time = 5, + check = function() return IsValid(ply) and tobool(ply:HasItem(item)) and item:GetData('thin') ~= nil end, + succ = function() + if not ply:HasItem(item) then return end + if item:GetData('thin') == nil then return end + if item:GetData('usesLeft') == 50 then + local cnt, line = ply:AddItem('fish_line') + if line ~= nil then + line:SetData('name', item:GetData('thin') and 'Тонкая леска' or 'Крепкая леска') + line:SetData('thin', item:GetData('thin')) + end + end + item:SetData('thin', nil) + item:SetData('usesLeft', 0) + item:SetData('desc', 'Используется для ловли рыбы') + item:GetParent():QueueSync() + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + local sound = 'weapons/357/357_reload'.. math.random(1, 4) ..'.wav' + ply:EmitSound(sound, 50) + end, + }) + end + end, + function(ply, item) + return useAddLine(ply, item, true) + end, + function(ply, item, cont) + return useAddLine(ply, item, false) + end, + } +}) + +octoinv.registerItem('ing_fish1', { + name = 'Окунь', + icon = 'octoteam/icons/fish_perch.png', + mass = 2.5, + volume = 2, + randomWeight = 0.2, + desc = L.descing, +}) + +octoinv.registerItem('ing_fish2', { + name = 'Карп', + icon = 'octoteam/icons/fish_carp.png', + mass = 2.5, + volume = 2, + randomWeight = 0.2, + desc = L.descing, +}) + +octoinv.registerItem('ing_fish3', { + name = 'Форель', + icon = 'octoteam/icons/fish_trout.png', + mass = 5, + volume = 3, + randomWeight = 0.25, + desc = L.descing, +}) + +octoinv.registerItem('ing_fish4', { + name = 'Щука', + icon = 'octoteam/icons/fish_pike.png', + mass = 8, + volume = 5, + randomWeight = 0.1, + desc = L.descing, +}) diff --git a/garrysmod/addons/feature-fishing/lua/fishing/octoinv/shop.lua b/garrysmod/addons/feature-fishing/lua/fishing/octoinv/shop.lua new file mode 100644 index 0000000..08e10e2 --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/fishing/octoinv/shop.lua @@ -0,0 +1,104 @@ +octoinv.addShopCat('fishing', { + name = 'Рыбалка', + icon = 'octoteam/icons/fishing_rod.png', +}) + +octoinv.addShopItem('bait_bacon', { + cat = 'fishing', price = 400, + name = 'Наживка из бекона', + icon = 'octoteam/icons/bait_bacon.png', + item = 'fish_bait', + data = { + name = 'Наживка из бекона', + icon = 'octoteam/icons/bait_bacon.png', + bait = 'bacon', + }, +}) + +octoinv.addShopItem('bait_cheese', { + cat = 'fishing', price = 400, + name = 'Насадка из сыра', + icon = 'octoteam/icons/bait_cheese.png', + item = 'fish_bait', + data = { + name = 'Насадка из сыра', + icon = 'octoteam/icons/bait_cheese.png', + bait = 'cheese', + }, +}) + +octoinv.addShopItem('bait_classic', { + cat = 'fishing', price = 400, + name = 'Черви', + icon = 'octoteam/icons/bait_worm.png', + item = 'fish_bait', + data = { + name = 'Черви', + icon = 'octoteam/icons/bait_worm.png', + bait = 'classic', + }, +}) + +octoinv.addShopItem('bait_fish', { + cat = 'fishing', price = 400, + name = 'Живцы', + icon = 'octoteam/icons/bait_fish.png', + item = 'fish_bait', + data = { + name = 'Живцы', + icon = 'octoteam/icons/bait_fish.png', + bait = 'fish', + }, +}) + +octoinv.addShopItem('bait_prawn', { + cat = 'fishing', price = 400, + name = 'Наживка из креветок', + icon = 'octoteam/icons/bait_shrimp.png', + item = 'fish_bait', + data = { + name = 'Наживка из креветок', + icon = 'octoteam/icons/bait_shrimp.png', + bait = 'prawn', + }, +}) + +octoinv.addShopItem('bait_synthetic', { + cat = 'fishing', price = 400, + name = 'Светящиеся окуни', + icon = 'octoteam/icons/bait_glowing_perch.png', + item = 'fish_bait', + data = { + name = 'Светящиеся окуни', + icon = 'octoteam/icons/bait_glowing_perch.png', + bait = 'synthetic', + }, +}) + +octoinv.addShopItem('fish_tackle', { + cat = 'fishing', price = 2000, +}) + +octoinv.addShopItem('fish_line1', { + cat = 'fishing', price = 1000, + name = 'Леска крепкая', + icon = 'octoteam/icons/fishing_line_thick.png', + item = 'fish_line', + data = { + name = 'Крепкая леска', + icon = 'octoteam/icons/fishing_line_thick.png', + thin = false, + }, +}) + +octoinv.addShopItem('fish_line2', { + cat = 'fishing', price = 1000, + name = 'Леска тонкая', + icon = 'octoteam/icons/fishing_line_thin.png', + item = 'fish_line', + data = { + name = 'Тонкая леска', + icon = 'octoteam/icons/fishing_line_thin.png', + thin = true, + }, +}) diff --git a/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/cl_init.lua b/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/cl_init.lua new file mode 100644 index 0000000..f8241da --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/cl_init.lua @@ -0,0 +1,92 @@ +include 'shared.lua' + +local wmPos = Vector(35.7, -3.8, 26.2) +local wmAng = Angle(-30.2, 0, 0) + +local angle_zero = Angle() +local lineMat = Material('cable/xbeam') +local linePos = Vector(66, 0, 39) +local segs = 15 + +local function offZ(x) + return 0.25 - (x - 0.5)^2 +end + +function SWEP:CreateWorldModel() + + if IsValid(self.wm) then + self:RemoveWorldModel() + end + + local wm = octolib.createDummy(self.WorldModel) + + local owner = self:GetOwner() + if not IsValid(owner) then + wm:SetParent(self) + wm:SetPos(Vector()) + wm:SetAngles(Angle()) + end + + self.wm = wm + +end + +function SWEP:RemoveWorldModel() + + if IsValid(self.wm) then + self.wm:Remove() + end + +end + +function SWEP:Equip() self:CreateWorldModel() end +function SWEP:Deploy() self:CreateWorldModel() end +function SWEP:OwnerChanged() self:CreateWorldModel() end +function SWEP:Holster() self:RemoveWorldModel() end +function SWEP:OnRemove() self:RemoveWorldModel() end + +function SWEP:DrawWorldModel() + + local owner = self:GetOwner() + if IsValid(owner) and owner:GetActiveWeapon() == self and IsValid(self.wm) then + local attID = owner:LookupAttachment('anim_attachment_RH') + if attID <= 0 then return end + local att = owner:GetAttachment(attID) + local newPos, newAng = LocalToWorld(wmPos, wmAng, att.Pos, att.Ang) + self.wm:SetPos(newPos) + self.wm:SetAngles(newAng) + -- self.wm:DrawModel() + + local hkID = self:GetNetVar('hook') + local hk = hkID and Entity(hkID) + if not IsValid(hk) then return end + + local pos1 = LocalToWorld(linePos, angle_zero, att.Pos, att.Ang) + local pos2 = hk:GetPos() + local dir = (pos2 - pos1):GetNormalized() + local dist = pos1:Distance(pos2) + + render.SetMaterial(lineMat) + render.StartBeam(segs) + render.AddBeam(pos1, 0.5, 0, color_white) + for i = 1, segs - 2 do + local frac = 1 / segs * i + local pos = pos1 + dir * frac * dist + pos.z = pos.z - offZ(frac) * dist / 5 + render.AddBeam(pos, 0.5, i + 1, color_white) + end + render.AddBeam(pos2, 0.5, segs, color_white) + render.EndBeam() + else + self:DrawModel() + end + +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 1) +end + +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire(CurTime() + 1) +end diff --git a/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/init.lua b/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/init.lua new file mode 100644 index 0000000..a2ccc2b --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/init.lua @@ -0,0 +1,140 @@ +AddCSLuaFile "shared.lua" +AddCSLuaFile "cl_init.lua" +include "shared.lua" + +local throwSound = Sound( "WeaponFrag.Throw" ) + +function SWEP:Deploy() + + self:UpdateHoldType('default') + +end + +function SWEP:UpdateHoldType(mode) + + local ht = self.HoldType + if mode == 'default' then + local owner = self:GetOwner() + if IsValid(owner) and owner:InVehicle() then + ht = 'physgun' + end + elseif mode == 'swing1' then + ht = 'melee2' + elseif mode == 'swing2' then + ht = 'grenade' + end + + self:SetHoldType(ht) + +end + +function SWEP:PrimaryAttack() + + self:SetNextPrimaryFire(CurTime() + 1) + local owner = self:GetOwner() + if not IsValid(owner) then return end + if IsValid(self.hook) then return end + if not self.bait then + owner:Notify('Рыба не будет ловиться без приманки') + return + end + if (self.usesLeft or 0) <= 0 then + owner:Notify('warning', 'Леска закончилась') + return + end + + self:UpdateHoldType('swing1') + owner:Freeze(true) + timer.Simple(0.1, function() self.Owner:SetAnimation(PLAYER_ATTACK1) end) + + timer.Simple(0.2, function() + owner:EmitSound(throwSound) + local h = ents.Create('ent_dbg_fish_float') + h.rod = self + h.owner = owner + + local pos = owner:EyePos() + local dir = (owner:GetEyeTrace().HitPos - pos):GetNormalized() + h:SetPos(Vector(pos.x + dir.x * 100, pos.y + dir.y * 100, pos.z)) + h:SetAngles(owner:GetAngles()) + h:Spawn() + self.hook = h + self:SetNetVar('hook', self.hook:EntIndex()) + + local phys = h:GetPhysicsObject() + if IsValid(phys) then + local vel = owner:GetForward() * 1500 + vel.z = dir.z * 1000 + phys:SetMass(1) + phys:SetVelocity(vel) + phys:Wake() + end + end) + + timer.Simple(0.4, function() + self:UpdateHoldType('default') + owner:Freeze(false) + owner.fishing = true + end) + +end + +function SWEP:SecondaryAttack() + + self:SetNextSecondaryFire(CurTime() + 1) + + self:UpdateHoldType('swing2') + timer.Simple(0.25, function() + self:UpdateHoldType('default') + if IsValid(self.hook) then + if self.hook:GetNetVar('baiting') then + local item = fishing.getLoot(self:GetOwner(), self) + if item then + local take = 1 + if not item[1]:find('ing_fish') then + take = (self.thin and 2 or 1) * 2 + end + self.bait = nil + self.usesLeft = (self.usesLeft or take) - take + self:GetOwner():AddItem(unpack(item)) + self:GetOwner():Notify(('Ты поймал предмет: %s'):format(octoinv.itemStr(item))) + end + self.hook:SetNetVar('baiting', nil) + end + self.hook:Remove() + self.hook = nil + end + end) + + return true + +end + +function SWEP:Reload() + if not IsValid(self.hook) and self.bait then + self.bait = nil + self:GetOwner():DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + end +end + +function SWEP:Holster() + if IsValid(self.hook) then + self:GetOwner():SetNetVar('baiting', nil) + self.hook:Remove() + self.hook = nil + end + + return true +end + +local function updateHoldType(ply) + timer.Simple(0, function() + if not IsValid(ply) then return end + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'weapon_octo_fishing_rod' then + wep:UpdateHoldType('default') + end + end) +end +hook.Add('PlayerEnteredVehicle', 'dbg-fishing', updateHoldType) +hook.Add('PlayerLeaveVehicle', 'dbg-fishing', updateHoldType) diff --git a/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/shared.lua b/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/shared.lua new file mode 100644 index 0000000..dc4cec8 --- /dev/null +++ b/garrysmod/addons/feature-fishing/lua/weapons/weapon_octo_fishing_rod/shared.lua @@ -0,0 +1,40 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = "weapon_octo_base" +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Удочка' +SWEP.Instructions = 'ЛКМ - забросить\nПКМ - тянуть\nE по приманке - надеть приманку\nR - снять приманку' +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 62 +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/fishingmod_custom/fishingrod_beta2.mdl" +SWEP.HoldType = "revolver" +SWEP.UseHands = true +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = true +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Author = "Wani4ka (Otothorp Team)" +SWEP.Contact = "" +SWEP.Purpose = "Ловля рыбы" +SWEP.UseHands = true +SWEP.Model = 'models/fishingmod_custom/fishingrod_beta2.mdl' + +SWEP.Primary.Ammo = 'none' +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultCip = -1 +SWEP.Primary.Automatic = false + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' + +SWEP.CanScare = false +SWEP.IsLethal = false diff --git a/garrysmod/addons/feature-fog/lua/autorun/client/dbg-fog.lua b/garrysmod/addons/feature-fog/lua/autorun/client/dbg-fog.lua new file mode 100644 index 0000000..c9da642 --- /dev/null +++ b/garrysmod/addons/feature-fog/lua/autorun/client/dbg-fog.lua @@ -0,0 +1,26 @@ +-- local col, dist, dist2 = Vector(0.75,0.75,0.75), 3000, 3000 * 0.65 +-- netstream.Hook('dbg-fog', function(_col, _dist, _dist2) +-- col = _col +-- dist = _dist +-- dist2 = _dist2 or (_dist * 0.65) +-- end) + +-- hook.Add('SetupWorldFog', 'FoxController', function() +-- render.FogMode(1) +-- render.FogStart(dist2) +-- render.FogEnd(dist) +-- render.FogMaxDensity(1) +-- render.FogColor(col.x * 255, col.y * 255, col.z * 255) + +-- return true +-- end) + +-- hook.Add('SetupSkyboxFog', 'FoxControllerSky', function(scale) +-- render.FogMode(MATERIAL_FOG_LINEAR) +-- render.FogStart(dist2 * scale) +-- render.FogEnd(dist * scale) +-- render.FogMaxDensity(1) +-- render.FogColor(col.x * 255, col.y * 255, col.z * 255) + +-- return true +-- end) diff --git a/garrysmod/addons/feature-fog/lua/autorun/server/dbg-fog.lua b/garrysmod/addons/feature-fog/lua/autorun/server/dbg-fog.lua new file mode 100644 index 0000000..d7593b6 --- /dev/null +++ b/garrysmod/addons/feature-fog/lua/autorun/server/dbg-fog.lua @@ -0,0 +1,53 @@ +-- local col, dist, dist2 = Vector(0.75,0.75,0.75), 3000, 3000 * 0.65 +-- local f + +-- hook.Add('InitPostEntity', 'dbg-fog', function() + +-- for k,v in pairs(ents.FindByClass('env_fog_controller')) do +-- if IsValid(v) then f = v end +-- end +-- f = f or ents.Create('env_fog_controller') + +-- f:SetKeyValue('fogcolor', '200 200 200') +-- f:SetKeyValue('fogcolor2', '200 200 200') +-- f:SetKeyValue('fogdir', '1 0 0') +-- f:SetKeyValue('fogstart', dist2) +-- f:SetKeyValue('fogend', dist) +-- f:SetKeyValue('farz', dist + 150) +-- f:SetKeyValue('fogenable', 1) +-- f:SetKeyValue('fogblend', 1) +-- f:Spawn() +-- f:Activate() + +-- timer.Simple(0, function() +-- f:Fire('TurnOn') +-- f:Input('SetStartDist', f, f, dist2) +-- f:Input('SetEndDist', f, f, dist) +-- f:Input('SetFarZ', f, f, dist + 150) +-- f:Input('StartFogTransition', f, f) +-- end) + +-- end) + +-- function UpdateFog(_col, _dist, _dist2) + +-- for k,v in pairs(ents.FindByClass('env_fog_controller')) do +-- if IsValid(v) then f = v end +-- end +-- if not IsValid(f) then return end + +-- col, dist, dist2 = _col, _dist, _dist2 or (dist * 0.65) + +-- f:SetKeyValue('fogstart', dist2) +-- f:SetKeyValue('fogend', dist) +-- f:SetKeyValue('farz', dist + 150) + +-- timer.Simple(0, function() +-- f:Input('SetStartDist', f, f, dist2) +-- f:Input('SetEndDist', f, f, dist) +-- f:Input('SetFarZ', f, f, dist + 150) +-- end) + +-- netstream.Start(nil, 'dbg-fog', col, dist, dist2) + +-- end diff --git a/garrysmod/addons/feature-gmpanel/lua/autorun/gmpanel.lua b/garrysmod/addons/feature-gmpanel/lua/autorun/gmpanel.lua new file mode 100644 index 0000000..29d866f --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/autorun/gmpanel.lua @@ -0,0 +1,12 @@ +octolib.client('gmpanel/cl_init') +octolib.server('gmpanel/sv_init') +octolib.client('gmpanel/cl_quick') +octolib.client('gmpanel/cl_groups') +octolib.client('gmpanel/cl_scenarios') +octolib.module('gmpanel/global') +octolib.client('gmpanel/actions/cl_actions') + +local _, actions = file.Find('gmpanel/actions/*', 'LUA') +for _,act in ipairs(actions) do + octolib.module('gmpanel/actions/' .. act) +end diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/cl_actions.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/cl_actions.lua new file mode 100644 index 0000000..a039714 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/cl_actions.lua @@ -0,0 +1,382 @@ +gmpanel.actions = gmpanel.actions or {} +gmpanel.actions.available = gmpanel.actions.available or {} +gmpanel.actions.added = gmpanel.actions.added or {} + +pcall(function() + local f = file.Read('gmpanel_actions.dat') + if f then gmpanel.actions.added = pon.decode(f) or {} end +end) + +local pan, pAvailable, settings, active, right + +local colors = CFG.skinColors +local function addedLinePaint(self, w, h) + local off, off2 = 0, 0 + if IsValid(settings) and settings.editUid == self.uid then + draw.RoundedBox(4, 0, 0, w, h, colors.g) + off, off2 = 1, 2 + end + if self.sel then + draw.RoundedBox(4, off, off, w - off2, h - off2, Color(170,119,102)) + elseif self.id % 2 == 1 then + draw.RoundedBox(4, off, off, w - off2, h - off2, Color(0,0,0, 35)) + end +end + +local function addedLineSelect(self) + for _,v in ipairs(active:GetChildren()) do + v.sel = nil + end + self.sel = true +end + +local function onModified(self) + local byUid = {} + for _,v in ipairs(gmpanel.actions.added) do + byUid[v.uid] = v + end + gmpanel.actions.added = {} + for _,v in ipairs(self:GetChildren()) do + gmpanel.actions.added[#gmpanel.actions.added + 1] = byUid[v.uid] + v.sel = v.uid == settings.editUid or nil + end + gmpanel.actions.save() +end + +local function addedLineClick(self, mcode) + + if isfunction(self.oldMP) then self:oldMP(mcode) end + if mcode ~= MOUSE_RIGHT then return end + + self:Select() + + local saved = gmpanel.actions.added[self.id] + if not saved then return end + + local menu = DermaMenu() + + menu:AddOption('Экспортировать', function() + local data = table.Copy(saved) + data.uid = nil + SetClipboardText(pon.encode(data)) + octolib.notify.show('Код действия скопирован в буфер обмена') + end):SetIcon(octolib.icons.silk16('page_go')) + + menu:AddOption('Редактировать', function() + + if not IsValid(settings) then return end + + local action = gmpanel.actions.available[saved.id] + if not action then return end + + pAvailable.dirty = true + pAvailable:ClearSelection() + for _,v in ipairs(pAvailable:GetLines()) do + if v.id == saved.id then + pAvailable:SelectItem(v) + break + end + end + pAvailable.dirty = nil + + settings:Clear() + action.openSettings(settings, saved) + settings.editUid = self.uid + gmpanel.actions.insertButtons(saved.id, saved) + + end):SetIcon('octoteam/icons-16/pencil.png') + + menu:AddOption('Удалить', function() + gmpanel.actions.editAction(self.uid) + end):SetIcon('octoteam/icons-16/cross.png') + menu:Open() + +end + +local function rebuildAdded() + if not IsValid(pan) then return end + if IsValid(right) then right:Remove() end + right = pan:Add('DPanel') + right:Dock(RIGHT) + right:SetWide(150) + right:SetPaintBackground(false) + active = octolib.label(right, 'Заготовленные действия') + active:DockMargin(0, 0, 0, 5) + active:SetContentAlignment(5) + + active = right:Add('DScrollPanel') + active:Dock(FILL) + active:DockMargin(5, 0, 0, 0) + active = active:Add('DListLayout') + active:Dock(FILL) + active:MakeDroppable('gmpanel.actions.added') + + function active:AddPanel(i, data) + local cont = self:Add('DPanel') + cont:SetTall(20) + cont.Paint = addedLinePaint + cont:SetMouseInputEnabled(true) + cont.id = i + cont.uid = data.uid + cont.oldMP, cont.OnMouseReleased, cont.Select = cont.OnMouseReleased, addedLineClick, addedLineSelect + + local icon = cont:Add('DImage') + icon:Dock(LEFT) + icon:SetWide(16) + icon:DockMargin(0, 2, 5, 2) + icon:SetImage(data._icon) + + local name = cont:Add('DLabel') + name:Dock(FILL) + name:SetText(data._name) + + return cont + end + + for i,v in ipairs(gmpanel.actions.added) do + active:AddPanel(i, v) + end + active.OnModified = onModified + + local btnImport = right:Add('DButton') + btnImport:Dock(BOTTOM) + btnImport:SetTall(20) + btnImport:DockMargin(0, 5, 0, 0) + btnImport:SetText('Импорт действия') + btnImport:SetIcon(octolib.icons.silk16('page_code')) + function btnImport:DoClick() + Derma_StringRequest('Импорт действия', 'Вставь код действия, полученный при экспорте', '', function(inp) + local succ, data = pcall(pon.decode, inp) + if not succ or not istable(data) then + return octolib.notify.show('warning', 'Кажется, код действия поврежден') + end + gmpanel.actions.addAction(data.id, data) + end) + end +end + +gmpanel.actions.save = octolib.func.debounce(function() + file.Write('gmpanel_actions.dat', pon.encode(gmpanel.actions.added)) +end, 1) + +function gmpanel.actions.registerAction(id, action) + gmpanel.actions.available[id] = action +end + +function gmpanel.actions.addAction(id, actionData) + if not id or not gmpanel.actions.available[id] then return end + actionData.id = id + actionData.uid = octolib.string.uuid():sub(1, 8) + gmpanel.actions.added[#gmpanel.actions.added + 1] = actionData + gmpanel.actions.save() + rebuildAdded() + gmpanel.scenarios.updateActions() + return true +end + +function gmpanel.actions.editAction(uid, actionData) + + if not uid then return end + local idx + for i,v in ipairs(gmpanel.actions.added) do + if v.uid == uid then + idx = i + break + end + end + if not idx then return end + + if not actionData then + + table.remove(gmpanel.actions.added, idx) + rebuildAdded() + + else + gmpanel.actions.added[idx] = actionData + if IsValid(active) then + local cont = active:GetChildren()[idx] + cont:GetChildren()[1]:SetImage(actionData._icon) + cont:GetChildren()[2]:SetText(actionData._name) + end + end + + gmpanel.scenarios.updateActions() + gmpanel.actions.save() + +end + +function gmpanel.actions.close() + if IsValid(pan) then + pan:Remove() + end +end + +function gmpanel.actions.defaultExecute(dataPassed, players) + dataPassed = table.Copy(dataPassed) + local id = dataPassed.id + dataPassed.id, dataPassed._name, dataPassed._icon = nil + dataPassed.players = players + netstream.Start('dbg-event.execute', id, dataPassed) +end + +function gmpanel.actions.insertButtons(id, data) + + if not (id and gmpanel.actions.available[id]) then return end + local action = gmpanel.actions.available[id] + data = data or {} + + octolib.button(settings, 'Тест', function() + + local func = action.execute or gmpanel.actions.defaultExecute + + local data + if isfunction(action.getData) then + data = action.getData(settings) + else data = {} end + if data == nil then + octolib.notify.show('warning', 'Кажется, не все поля заполнены верно') + return + end + data = istable(data) and data or {value = data} + data.id = id + data.test = true + + func(data, {LocalPlayer():SteamID()}) + + end) + + local btmPan = settings:Add('DPanel') + btmPan:Dock(BOTTOM) + btmPan:SetTall(48) + btmPan:SetPaintBackground(false) + + local nameCont = btmPan:Add('DPanel') + nameCont:Dock(LEFT) + nameCont:SetWide(150) + nameCont:SetPaintBackground(false) + + octolib.label(nameCont, 'Кнопка:') + + local wrapper = nameCont:Add('DPanel') + wrapper:Dock(FILL) + wrapper:DockMargin(0, 0, 0, 5) + + local icon = wrapper:Add('DImageButton') + icon:Dock(LEFT) + icon:DockMargin(2, 4, 2, 4) + icon:SetSize(16, 16) + icon:SetIcon(data._icon or 'icon16/control_play.png') + function icon:DoClick() + octolib.icons.picker(function(val) + self:SetImage(val) + end, 'materials/' .. self:GetImage(), 'materials/icon16/') + end + + local name = wrapper:Add('DTextEntry') + name:Dock(FILL) + name:SetPaintBackground(false) + name:SetTextColor(color_white) + name:SetValue(data._name or '') + + local b = btmPan:Add('DPanel') + b:Dock(RIGHT) + b:DockMargin(0, 0, 0, 5) + b:SetPaintBackground(false) + b:SetWide(100) + b = b:Add('DButton') + b:Dock(BOTTOM) + b:SetTall(30) + b:SetText(settings.editUid and 'Сохранить' or 'Добавить') + b:SetIcon('icon16/folder.png') + function b:DoClick() + + if string.Trim(name:GetValue()) == '' then + octolib.notify.show('Пожалуйста, добавь название действия. Тебе так будет проще') + return + end + + local data + if isfunction(action.getData) then + data = action.getData(settings) + else data = {} end + if data == nil then + octolib.notify.show('warning', 'Кажется, не все поля заполнены верно') + return + end + data = istable(data) and data or {value = data} + data._name, data._icon = name:GetValue(), icon:GetImage() + + if settings.editUid then + data.id = id + data.uid = settings.editUid + gmpanel.actions.editAction(settings.editUid, data) + else gmpanel.actions.addAction(id, data) end + end +end + +hook.Add('gmpanel.populateActionsMenu', 'gmpanel.actions', function(menu, players) + + for _,v in ipairs(gmpanel.actions.added) do + + local action = gmpanel.actions.available[v.id] + if action then + menu:AddOption(v._name, function() + local func = action.execute or gmpanel.actions.defaultExecute + func(v, players) + end):SetIcon(v._icon) + end + + end + + menu:AddSpacer() + +end) + +local function build() + gmpanel.actions.close() + + pan = vgui.Create('DFrame') + pan:SetSize(700, 500) + pan:SetTitle('Действия') + pan:Center() + pan:MakePopup() + pan:SetDeleteOnClose(true) + + pAvailable = pan:Add('DListView') + pAvailable:Dock(LEFT) + pAvailable:SetWide(175) + pAvailable:SetHideHeaders(true) + pAvailable:DockMargin(0, 0, 5, 0) + pAvailable:AddColumn(''):SetFixedWidth(32) + pAvailable:AddColumn(L.title) + pAvailable:SetDataHeight(32) + pAvailable:SetMultiSelect(false) + + for k,v in pairs(gmpanel.actions.available) do + local icon = vgui.Create('DImage') + icon:SetImage(v.icon) + pAvailable:AddLine(icon, v.name).id = k + end + + settings = pan:Add('DPanel') + settings:Dock(FILL) + settings:DockPadding(5, 5, 5, 5) + + function pAvailable:OnRowSelected(_, row) + if self.dirty then return end + for _,v in ipairs(active:GetChildren()) do + v.sel = nil + end + settings:Clear() + gmpanel.actions.available[row.id].openSettings(settings, {}) + settings.editUid = nil + gmpanel.actions.insertButtons(row.id, {}) + end + + rebuildAdded() + +end + +function gmpanel.actions.open() + build() +end diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/commands/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/commands/client.lua new file mode 100644 index 0000000..eb2f54d --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/commands/client.lua @@ -0,0 +1,19 @@ +gmpanel.actions.registerAction('commands', { + name = 'Команды', + icon = 'octoteam/icons/fingerprint.png', + openSettings = function(panel, data) + + octolib.label(panel, 'Отправляет от имени гейм-мастера сообщение') + local lbl = octolib.label(panel, '@p в сообщении заменяется на SteamID для каждого игрока в группе') + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetTall(25) + local cmd = octolib.textEntry(panel, 'Команда:') + cmd:SetValue(data.value or '~bring @p') + panel.cmd = cmd + + end, + getData = function(panel) + return IsValid(panel.cmd) and panel.cmd:GetText() or nil + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/commands/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/commands/server.lua new file mode 100644 index 0000000..78ea5a6 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/commands/server.lua @@ -0,0 +1,7 @@ +gmpanel.registerAction('commands', function(obj, ply) + if not isstring(obj.value) then return false end + local cmd, players = obj.value, gmpanel.buildTargets(obj.players or {}) + for _,pl in ipairs(players) do + ply:Say(cmd:gsub('@p', pl:SteamID())) + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/deafen/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/deafen/client.lua new file mode 100644 index 0000000..d88511f --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/deafen/client.lua @@ -0,0 +1,65 @@ +local curState, tgtState = 0, 0 + +netstream.Hook('gmpanel.darkenScreen', function(val) + tgtState = val +end) + +hook.Add('PostDrawHUD', 'gmpanel.darkenScreen', function() + if curState == tgtState and curState == 0 then return end + + local ft = FrameTime() + -- interpolate the value + local delta = (tgtState - curState) * (ft < 1 and ft or 1) + if math.abs(delta) < .01 then + delta = delta > 0 and .01 or -.01 + end + if math.abs(tgtState - curState) < .01 then + delta = tgtState - curState + end + curState = curState + delta + + -- apply effects + if curState ~= 0 then + local deathColors = { + [ '$pp_colour_addr' ] = 0, + [ '$pp_colour_addg' ] = 0, + [ '$pp_colour_addb' ] = 0, + [ '$pp_colour_brightness' ] = 0, + [ '$pp_colour_contrast' ] = 1 - curState * 0.7, + [ '$pp_colour_colour' ] = 1 - curState, + [ '$pp_colour_mulr' ] = 0, + [ '$pp_colour_mulg' ] = 0, + [ '$pp_colour_mulb' ] = 0 + } DrawColorModify(deathColors) + + if curState > 0.5 then + local _prc = (curState-.5) / .5 + DrawBloom(0.1, (_prc^3) * 1, 6, 6, 1, 0.25, 1, 1, 1) + end + end + +end) + +gmpanel.actions.registerAction('deafen', { + name = 'Оглушение', + icon = 'octoteam/icons/man_mdel.png', + openSettings = function(panel, data) + + octolib.label(panel, 'Визуальные эффекты затемнения экрана и приглушения звука') + + local screen = octolib.slider(panel, 'Затемнение экрана:', 0, 2, 0) + screen:SetValue(data.screen or 0) + panel.screen = screen + + local sound = octolib.slider(panel, 'Приглушение звука:', 0, 2, 0) + sound:SetValue(data.sound or 0) + panel.sound = sound + + end, + getData = function(panel) + return { + screen = IsValid(panel.screen) and panel.screen:GetValue() or 0, + sound = IsValid(panel.sound) and panel.sound:GetValue() or 0, + } + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/deafen/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/deafen/server.lua new file mode 100644 index 0000000..44e7ef8 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/deafen/server.lua @@ -0,0 +1,15 @@ +local dsps = { + 1, -- normal + 15, -- medium + 16, -- hard +} + +gmpanel.registerAction('deafen', function(obj) + if not isnumber(obj.screen) or not isnumber(obj.sound) then return false end + local players = gmpanel.buildTargets(obj.players or {}) + local dsp = dsps[obj.sound + 1] or dsps[1] + for _,ply in ipairs(players) do + ply:SetDSP(dsp) + netstream.Start(ply, 'gmpanel.darkenScreen', obj.screen) + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/effects/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/effects/client.lua new file mode 100644 index 0000000..6c61e10 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/effects/client.lua @@ -0,0 +1,8 @@ +-- gmpanel.actions.registerAction('effects', { +-- name = 'Эффекты', +-- icon = 'octoteam/icons/bubbles.png', +-- openSettings = function(panel) +-- octolib.label(panel, 'Будет добавлено позже'):DockMargin(10, 10, 10, 10) +-- end +-- }) + diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/effects/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/effects/server.lua new file mode 100644 index 0000000..4ba2805 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/effects/server.lua @@ -0,0 +1 @@ +-- diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/hunger/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/hunger/client.lua new file mode 100644 index 0000000..67cfd29 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/hunger/client.lua @@ -0,0 +1,21 @@ +gmpanel.actions.registerAction('hunger', { + name = 'Голод', + icon = 'octoteam/icons/food_meat3.png', + openSettings = function(panel, data) + + local lbl = octolib.label(panel, 'Отключает голод игрокам. НЕ ЗАБУДЬ ВКЛЮЧИТЬ ОБРАТНО ПОСЛЕ ИВЕНТА!') + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetTall(25) + + local onoff = octolib.checkBox(panel, 'Не снимать голод') + if data.value == nil then + onoff:SetChecked(true) + else onoff:SetChecked(data.value) end + panel.status = onoff + + end, + getData = function(panel) + return IsValid(panel.status) and panel.status:GetChecked() + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/hunger/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/hunger/server.lua new file mode 100644 index 0000000..148e258 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/hunger/server.lua @@ -0,0 +1,13 @@ +gmpanel.registerAction('hunger', function(obj, ply) + local status, players = obj.value or nil + local players = gmpanel.buildTargets(obj.players or {}) + for _,pl in ipairs(players) do + pl.hungerDisabled = status + end + + ply:Notify('Изменение голода для игроков в группе ' .. (status and 'от' or 'в') .. 'ключено') +end) + +hook.Add('hungerUpdate', 'gmpanel', function(ply) + if ply.hungerDisabled then return true end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/karma/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/karma/client.lua new file mode 100644 index 0000000..61b0cfb --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/karma/client.lua @@ -0,0 +1,20 @@ +gmpanel.actions.registerAction('karma', { + name = 'Карма', + icon = 'octoteam/icons/star.png', + openSettings = function(panel, data) + + octolib.label(panel, 'Отключает изменение кармы для игроков') + octolib.label(panel, 'Сбрасывается, если игрок перезайдет') + octolib.label(panel, 'ПОЖАЛУЙСТА, НЕ ЗАБУДЬ ВЕРНУТЬ ОБРАТНО ПОСЛЕ ИВЕНТА!'):SetFont('DermaDefaultBold') + + local onoff = octolib.checkBox(panel, 'Не снимать карму') + if data.value == nil then + onoff:SetChecked(true) + else onoff:SetChecked(data.value) end + panel.status = onoff + + end, + getData = function(panel) + return IsValid(panel.status) and panel.status:GetChecked() + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/karma/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/karma/server.lua new file mode 100644 index 0000000..a7121bf --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/karma/server.lua @@ -0,0 +1,28 @@ +gmpanel.registerAction('karma', function(obj, ply) + local status, players = obj.value or nil + local players = gmpanel.buildTargets(obj.test and {ply:SteamID()} or obj.players or {}) + for _,pl in ipairs(players) do + pl.karmaDisabled = status + end + + if obj.test then + + local amount = -1 + local curKarma = ply:GetNetVar('dbg.karma', 0) + if curKarma > 0 and amount < 0 then + amount = amount + math.floor(amount * curKarma / 25) + end + + ply:Notify('Текущая карма: ' .. curKarma) + timer.Simple(0.2, function() + if not IsValid(ply) then return end + ply:AddKarma(-1, 'Ты воспользовался панелью гейм-мастеров.') + timer.Simple(0.2, function() + if not IsValid(ply) then return end + ply:AddKarma(-amount, 'Ты воспользовался панелью гейм-мастеров.') + ply.karmaDisabled = nil + end) + end) + + else ply:Notify('Изменение кармы для игроков в группе ' .. (status and 'от' or 'в') .. 'ключено') end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/lookable/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/lookable/client.lua new file mode 100644 index 0000000..0aed760 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/lookable/client.lua @@ -0,0 +1,76 @@ +local origin +timer.Simple(0, function() + origin = octolib.dataEditor.registered['tool.lookable.pages'] +end) + +gmpanel.actions.registerAction('lookable', { + name = 'Осмотреть', + icon = octolib.icons.color('search'), + openSettings = function(panel, data) + + octolib.label(panel, 'Звук перелистывания страниц (локальный или по URL):') + local tp = panel:Add('DPanel') + tp:Dock(TOP) + tp:SetTall(30) + + local snd = octolib.textEntry(tp) + if data.sound then + snd:SetValue(data.sound.url or data.sound.file) + end + snd:SetPlaceholderText('Без звука') + snd:Dock(FILL) + panel.snd = snd + + octolib.button(tp, 'браузер', function() + RunConsoleCommand('wire_sound_browser_open') + end):Dock(RIGHT) + + local vol = octolib.slider(panel, 'Громкость звука', 0, 1, 2) + vol:SetValue(data.sound and data.sound.volume or 1) + panel.vol = vol + local dist = octolib.slider(panel, 'Дальность звука', 50, 5000, 0) + dist:SetValue(data.sound and data.sound.dist or 200) + panel.dist = dist + + local wrap = panel:Add('DPanel') + wrap:Dock(TOP) + wrap:SetTall(275) + wrap:SetPaintBackground(false) + local editorData = table.Copy(origin) + editorData.load = function(load) + load(editorData.pages or octolib.vars.get('tools.lookable.pages') or {}) + end + editorData.save = octolib.func.zero + local editor = octolib.dataEditor.open(editorData) + local fr = editor.frame + for _, v in ipairs(editor.frame:GetChildren()) do + if v ~= fr.btnClose and v ~= fr.btnMaxim and v ~= fr.btnMinim and v ~= fr.lblTitle then + v:SetParent(wrap) + end + end + editor.frame:Remove() + panel.getCache = editor.getCache + + end, + getData = function(panel) + local path = panel.snd:GetValue() + local dist = panel.dist:GetValue() + local isURL = path:sub(1, 4) == 'http' + + local data = { pages = panel.getCache() } + if string.Trim(path) ~= '' then + data.sound = { + volume = tonumber(panel.vol:GetValue()) or 1, + + url = isURL and path or nil, + dist = isURL and dist or nil, + distInner = isURL and (dist * 0.1) or nil, + + file = not isURL and path or nil, + level = not isURL and (dist / 4) or nil, + } + end + + return { data = data } + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/lookable/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/lookable/server.lua new file mode 100644 index 0000000..ab3636e --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/lookable/server.lua @@ -0,0 +1,3 @@ +gmpanel.registerAction('lookable', function(obj) + netstream.Start(gmpanel.buildTargets(obj.players or {}), 'tools.lookable', obj.data) +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/messages/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/messages/client.lua new file mode 100644 index 0000000..0111438 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/messages/client.lua @@ -0,0 +1,41 @@ +gmpanel.actions.registerAction('messages', { + name = 'Сообщения', + icon = 'octoteam/icons/mail.png', + openSettings = function(panel, data) + + local voices = {{'Нет', nil, true,}} + for _,v in ipairs(govorilka.voices) do + voices[#voices + 1] = {v.ru_name, v.en_name} + end + + local name = octolib.textEntry(panel, 'Имя (первая часть)') + name:SetValue(data.name or 'Иван Березкин') + panel.name = name + + local action = octolib.textEntry(panel, 'Действие (вторая часть)') + action:SetValue(data.action or 'говорит') + panel.action = action + + local message = octolib.textEntry(panel, 'Текст') + message:SetValue(data.message or 'Привет, гейм-мастер!') + panel.message = message + + local voice = octolib.comboBox(panel, 'Озвучка', voices) + for i = 1, #voice.Choices do + if voice:GetOptionData(i) == data.voice then + voice:ChooseOptionID(i) + end + end + panel.voice = voice + + end, + getData = function(panel) + local _, v = panel.voice:GetSelected() + return { + name = string.Trim(panel.name:GetText()), + action = string.Trim(panel.action:GetText()), + message = string.Trim(panel.message:GetText()), + voice = v, + } + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/messages/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/messages/server.lua new file mode 100644 index 0000000..89abe5d --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/messages/server.lua @@ -0,0 +1,16 @@ +gmpanel.registerAction('messages', function(data) + if data.action ~= '' then + data.action = ' ' .. data.action + end + data.players = gmpanel.buildTargets(data.players or {}) + + local msg = {octochat.textColors.rp, data.name, data.action} + if data.message ~= '' then + msg = {octochat.textColors.rp, data.name, data.action, ': ', color_white, data.message} + end + + for _,pl in ipairs(data.players) do + octochat.talkTo(pl, unpack(msg)) + if data.voice and data.message ~= '' then pl:DoVoice(data.message, data.voice, pl) end + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/models/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/models/client.lua new file mode 100644 index 0000000..2673640 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/models/client.lua @@ -0,0 +1,141 @@ +local function updatePreview(preview) + local mn, mx = preview.Entity:GetRenderBounds() + local size = 0 + size = math.max(size, math.abs(mn.x) + math.abs(mx.x)) + size = math.max(size, math.abs(mn.y) + math.abs(mx.y)) + size = math.max(size, math.abs(mn.z) + math.abs(mx.z)) + + preview:SetFOV(22) + preview:SetCamPos(Vector(size, size, size + 15)) + preview:SetLookAt((mn + mx) * 0.5) +end + +local function dragMousePress(self) + self.pressX = gui.MousePos() + self.pressed = true + self.dnr = true +end + +local function dragMouseRelease(self) self.pressed = false end + +local function layoutEntity(self, ent) + if (self.pressed) then + local mx = gui.MousePos() + self.angles = self.angles - Angle(0, (self.pressX or mx) - mx, 0) + self.pressX = gui.MousePos() + elseif not self.dnr then + self.angles = self.angles - Angle(0, 0.3, 0) + end + + ent:SetAngles(self.angles) +end + +gmpanel.actions.registerAction('models', { + name = 'Модели', + icon = 'octoteam/icons/man_m.png', + openSettings = function(panel, data) + + local ply = LocalPlayer() + + local model = octolib.textEntry(panel, 'Модель:') + model:SetText(data.model or ply:GetModel()) + model:SetUpdateOnType(true) + panel.model = model + + local ext = panel:Add('DPanel') + ext:Dock(TOP) + ext:SetPaintBackground(false) + ext:SetTall(300) + + local mdlBack = ext:Add('DPanel') + mdlBack:Dock(LEFT) + mdlBack:SetWide(175) + + local preview = mdlBack:Add('DModelPanel') + preview:Dock(FILL) + preview:SetAnimated(true) + + local sk, bg = data.skin or ply:GetSkin(), data.bodygroups + if not bg then + bg = {} + for _,v in ipairs(ply:GetBodyGroups()) do + bg[v.id] = ply:GetBodygroup(v.id) + end + end + + -- Hold to rotate + preview.angles = Angle(0, 0, 0) + preview.LayoutEntity = layoutEntity + preview.DragMousePress = dragMousePress + preview.DragMouseRelease = dragMouseRelease + + function preview:Refresh() + self:SetModel(model:GetText()) + self.Entity:SetSkin(sk) + for k,v in pairs(bg) do + self.Entity:SetBodygroup(k, v) + end + updatePreview(preview) + end + preview:Refresh() + + local bgEdit = ext:Add('DScrollPanel') + bgEdit:Dock(FILL) + bgEdit:DockMargin(2, 0, 0, 0) + function bgEdit:Refresh() + + self:Clear() + + local max = NumModelSkins(model:GetText()) - 1 + local sl = octolib.slider(self, 'Скин', 0, max, 0) + function sl:OnValueChanged(v) + sk = math.Round(v) + preview:Refresh() + end + sl:SetValue(sk <= max and sk or preview.Entity:GetSkin()) + panel.skin = sl + + local curBgs = {} + for _,v in ipairs(preview.Entity:GetBodyGroups()) do + if v.num > 1 then + curBgs[#curBgs + 1] = v.id + end + end + + if table.Count(octolib.table.diff(table.GetKeys(bg), curBgs)) > 0 then + bg = {} + else preview:Refresh() end + + panel.bodygroups = {} + + for _,v in ipairs(preview.Entity:GetBodyGroups()) do + if v.num > 1 then + local sl = octolib.slider(self, 'Аксессуар ' .. v.id, 0, v.num - 1, 0) + sl:SetValue(bg[v.id] or preview.Entity:GetBodygroup(v.id)) + bg[v.id] = sl:GetValue() + function sl:OnValueChanged(val) + val = math.Round(val) + if bg[v.id] ~= val then + bg[v.id] = val + preview:Refresh() + end + end + panel.bodygroups[v.id] = sl + end + end + end + bgEdit:Refresh() + + function model:OnValueChange() + bgEdit:Refresh() + end + + end, + getData = function(panel) + return { + model = panel.model:GetText(), + skin = panel.skin:GetValue(), + bodygroups = octolib.table.map(panel.bodygroups, function(bg) return bg:GetValue() end), + } + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/models/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/models/server.lua new file mode 100644 index 0000000..fe9658c --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/models/server.lua @@ -0,0 +1,14 @@ +gmpanel.registerAction('models', function(obj) + local model = obj.model + local skin = obj.skin + if not model or not skin then return false end + local bg = obj.bodygroups or {} + local players = gmpanel.buildTargets(obj.players or {}) + for _,pl in ipairs(players) do + pl:SetModel(model) + pl:SetSkin(skin) + for k,v in pairs(bg) do + pl:SetBodygroup(k, v) + end + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/move/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/move/client.lua new file mode 100644 index 0000000..87ad4f8 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/move/client.lua @@ -0,0 +1,34 @@ +gmpanel.actions.registerAction('move', { + name = 'Переместить', + icon = 'octoteam/icons/resize.png', + openSettings = function(panel, data) + local posang = octolib.textEntry(panel, 'Позиция и угол') + posang:SetPlaceholderText('[0 0 0]{0 0 0}') + if data.pos and data.ang then + posang:SetValue(('[%d %d %d]{%d %d %d}'):format(data.pos.x, data.pos.y, data.pos.z, data.ang.p, data.ang.y, data.ang.r)) + end + octolib.button(panel, 'Вставить текущее местоположение', function() + local pos, ang = LocalPlayer():GetPos(), LocalPlayer():EyeAngles() + posang:SetValue(('[%d %d %d]{%d %d %d}'):format(pos.x, pos.y, pos.z, ang.p, ang.y, ang.r)) + end):DockMargin(0, 0, 0, 10) + panel.posang = posang + + end, + getData = function(panel) + + local text = panel.posang:GetValue() + local posStr = text:gmatch('%[(.-)%]')() + local angStr = text:gmatch('%{(.-)%}')() + if not posStr or not angStr then return end + + local pos = Vector(posStr) + local ang = Angle(angStr) + if not isvector(pos) or not isangle(ang) then return end + + return { + pos = pos, + ang = ang, + } + + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/move/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/move/server.lua new file mode 100644 index 0000000..3e497ad --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/move/server.lua @@ -0,0 +1,9 @@ +gmpanel.registerAction('move', function(obj, ply) + if not (isvector(obj.pos) and isangle(obj.ang)) then return end + local players = gmpanel.buildTargets(obj.players or {}) + for _, target in ipairs(players) do + target:SetPos(obj.pos) + target:SetEyeAngles(obj.ang) + hook.Run('gmpanel.moved', ply, target, obj.pos, obj.ang) + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/notifications/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/notifications/client.lua new file mode 100644 index 0000000..99e32f5 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/notifications/client.lua @@ -0,0 +1,31 @@ +gmpanel.actions.registerAction('notifications', { + name = 'Уведомления', + icon = 'octoteam/icons/error.png', + openSettings = function(panel, data) + local channel = octolib.comboBox(panel, 'Канал', { + {'RP (Желтый цвет)', 'rp'}, + {'WARNING (Красный цвет)', 'warning'}, + {'OOC (Синий цвет)', 'ooc'}, + {'HINT (Зеленый цвет)', 'hint'}, + }) + channel:ChooseOptionID(1) + for i = 1, #channel.Choices do + if channel:GetOptionData(i) == data.channel then + channel:ChooseOptionID(i) + end + end + panel.channel = channel + + local text = octolib.textEntry(panel, 'Текст') + text:SetValue(data.text or 'Случайное уведомление') + panel.text = text + + end, + getData = function(panel) + local _, channel = panel.channel:GetSelected() + return { + text = panel.text:GetText(), + channel = channel, + } + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/notifications/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/notifications/server.lua new file mode 100644 index 0000000..7b60716 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/notifications/server.lua @@ -0,0 +1,7 @@ +gmpanel.registerAction('notifications', function(obj) + local players = gmpanel.buildTargets(obj.players or {}) + local msg = octolib.string.splitByUrl(tostring(obj.text or 'Уведомление')) + for _,pl in ipairs(players) do + pl:Notify(obj.channel or 'rp', unpack(msg)) + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/ragdoll/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/ragdoll/client.lua new file mode 100644 index 0000000..3590707 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/ragdoll/client.lua @@ -0,0 +1,17 @@ +gmpanel.actions.registerAction('ragdoll', { + name = 'Ragdoll', + icon = 'octoteam/icons/drug2.png', + openSettings = function(panel, data) + + octolib.label(panel, 'Выполняет "~ragdoll" на игроках'):DockMargin(5, 5, 5, 5) + local onoff = octolib.checkBox(panel, 'Включить') + if data.value ~= nil then + onoff:SetChecked(tobool(data.value)) + else onoff:SetChecked(true) end + panel.status = onoff + + end, + getData = function(panel) + return panel.status:GetChecked() + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/ragdoll/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/ragdoll/server.lua new file mode 100644 index 0000000..6c15216 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/ragdoll/server.lua @@ -0,0 +1,9 @@ +gmpanel.registerAction('ragdoll', function(obj, ply) + local status, players = obj.value or false, gmpanel.buildTargets(obj.players or {}) + for _,pl in ipairs(players) do + local state = IsValid(pl.sgRagdoll) + if status ~= state then + ply:Say('~ragdoll ' .. pl:SteamID()) + end + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/sounds/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/sounds/client.lua new file mode 100644 index 0000000..55f6195 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/sounds/client.lua @@ -0,0 +1,156 @@ +local playing = {} + +netstream.Hook('dbg-event.action.sound', function(dat) + + if istable(dat.stopsounds) then + for _,v in ipairs(dat.stopsounds) do + if IsValid(playing[v]) then + playing[v]:Stop() + playing[v] = nil + end + end + end + + if dat.url then + + sound.PlayURL(dat.url, 'noplay', function(st) + if not IsValid(st) then return octolib.notify.show('warning', 'Не удалось загрузить звук. Возможно, ссылка неправильная') end + st:SetVolume((dat.volume or 100) / 100) + st:Play() + local id = dat.soundId + if id then + if IsValid(playing[id]) then + playing[id]:Stop() + end + playing[id] = st + end + end) + + elseif dat.file then + local cs = CreateSound(LocalPlayer(), dat.file) + cs:ChangeVolume((dat.volume or 100) / 100) + cs:ChangePitch(dat.level) + cs:Play() + local id = dat.soundId + if id then + if IsValid(playing[id]) then + playing[id]:Stop() + end + playing[id] = cs + end + end + +end) + +local function url(panel, data) + local url = octolib.textEntry(panel) + url:SetValue(data.url or ('https://www.soundhelix.com/examples/mp3/SoundHelix-Song-' .. math.random(1,16) .. '.mp3')) + + local volume = octolib.slider(panel, 'Громкость', 0, 300) + volume:SetValue(data.volume or 100) + + return {url = url, volume = volume} +end + +local function file(panel, data) + local tp = panel:Add('DPanel') + tp:Dock(TOP) + tp:SetTall(30) + + local fl = octolib.textEntry(tp) + fl:SetValue(data.file or 'physics/plastic/plastic_box_break1.wav') + fl:Dock(FILL) + + octolib.button(tp, 'браузер', function() + RunConsoleCommand('wire_sound_browser_open') + end):Dock(RIGHT) + + local level = octolib.slider(panel, 'Дальность', 20, 179, 0) + level:SetValue(data.level or 75) + + local pitch = octolib.slider(panel, 'Высота', 0, 255, 0) + pitch:SetValue(data.pitch or 100) + + local volume = octolib.slider(panel, 'Громкость', 0, 100) + volume:SetValue(data.volume or 100) + + return {file = fl, level = level, pitch = pitch, volume = volume} +end + +gmpanel.actions.registerAction('sounds', { + name = 'Звуки', + icon = 'octoteam/icons/megaphone2.png', + openSettings = function(panel, data) + panel.src = {} + local b = octolib.comboBox(panel, nil, {{'По URL', nil, true,}, {'Внутриигровой',},}) + + local pan = panel:Add('DPanel') + pan:Dock(TOP) + pan:SetPaintBackground(false) + pan:SetTall(64) + + local soundID = octolib.textEntry(panel, 'Короткое название звука. Можно оставить пустым') + soundID:SetValue(data.soundId or '') + soundID:SetUpdateOnType(true) + function soundID:OnValueChange() + local fixed = self:GetText():gsub(' ', '') + if fixed ~= self:GetText() then + local cpos = self:GetCaretPos() + self:SetText(fixed) + self:SetCaretPos(math.min(cpos, utf8.len(fixed))) + end + end + panel.soundID = soundID + + function b:OnSelect(i) + pan:Clear() + panel.src = i == 1 and url(pan, data) or file(pan, data) + st = i + end + b:ChooseOptionID(data.file and 2 or 1) + + local stopsounds = octolib.textEntry(panel, 'Названия останавливаемых звуков через пробел. Необязательно') + stopsounds:SetValue(string.Implode(' ', data.stopsounds or {})) + stopsounds:SetUpdateOnType(true) + function stopsounds:OnValueChange() + local fixed = string.TrimLeft(self:GetText():gsub(' +', ' '), ' ') + if fixed ~= self:GetText() then + local cpos = self:GetCaretPos() + self:SetText(fixed) + self:SetCaretPos(math.min(cpos, utf8.len(fixed))) + elseif string.EndsWith(self:GetText(), ' ') then + local entries, realEntries, was = fixed:split(' '), {}, {} + for _,v in ipairs(entries) do + if v ~= '' and not was[v] then + realEntries[#realEntries + 1], was[v] = v, true + end + end + fixed = string.Implode(' ', realEntries) .. ' ' + if fixed ~= self:GetText() then + local cpos = self:GetCaretPos() + self:SetText(fixed) + self:SetCaretPos(math.min(cpos, utf8.len(fixed))) + end + end + end + panel.stopsounds = stopsounds + + end, + getData = function(panel) + + local result = {} + result.soundId = string.Trim(utf8.sub(panel.soundID:GetText(), 1, 48)) + if result.soundId == '' then result.soundId = nil end + + result.stopsounds = {} + local was = {} + for _,v in ipairs(panel.stopsounds:GetText():split(' ')) do + v = utf8.sub(v, 1, 48) + if v ~= '' and not was[v] then + result.stopsounds[#result.stopsounds + 1], was[v] = v, true + end + end + + return table.Merge(result, octolib.table.map(panel.src, function(x) return x:GetValue() end)) + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/sounds/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/sounds/server.lua new file mode 100644 index 0000000..0ea77b7 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/sounds/server.lua @@ -0,0 +1,26 @@ +gmpanel.registerAction('sounds', function(obj) + + if isstring(obj.soundId) then + obj.soundId = utf8.sub(obj.soundId, 1, 48) + else obj.soundId = nil end + + if istable(obj.stopsounds) and table.IsSequential(obj.stopsounds) then + for i = #obj.stopsounds, 1, -1 do + local str = obj.stopsounds[i] + if not isstring(str) then + table.remove(obj.stopsounds, i) + continue + end + str = utf8.sub(str:gsub(' ', ''), 1, 48) + if str == '' then + table.remove(obj.stopsounds, i) + continue + end + obj.stopsounds[i] = str + end + else obj.stopsounds = {} end + + local players = gmpanel.buildTargets(obj.players or {}) + obj.players = nil + netstream.Start(players, 'dbg-event.action.sound', obj) +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/talkie/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/talkie/client.lua new file mode 100644 index 0000000..fdf0a3c --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/talkie/client.lua @@ -0,0 +1,60 @@ +local function url(panel, data) + local url = octolib.textEntry(panel) + url:SetValue(data.url or ('https://www.soundhelix.com/examples/mp3/SoundHelix-Song-' .. math.random(1,16) .. '.mp3')) + + local volume = octolib.slider(panel, 'Громкость', 0, 300) + volume:SetValue(data.volume or 100) + + return {url = url, volume = volume} +end + +gmpanel.actions.registerAction('talkie', { + name = 'Рация', + icon = 'octoteam/icons/radio.png', + openSettings = function(panel, data) + panel.mode = data.mode or 'add' + local b = octolib.comboBox(panel, nil, {{'Подключить'}, {'Отключить'}}) + + local freq = octolib.textEntry(panel, 'Частота') + local freqName, freqLabel = octolib.textEntry(panel, 'Название частоты (не сработает, если частота уже создана)') + local canSpeak = octolib.checkBox(panel, 'Говорить') + + if data.speak == nil then + data.speak = true + end + + function b:OnSelect(i) + panel.mode = i == 1 and 'add' or 'remove' + if i == 1 then + panel.mode = 'add' + freqName:SetVisible(true) + freqLabel:SetVisible(true) + canSpeak:SetVisible(true) + + freqName:SetValue(data.name or '') + canSpeak:SetChecked(data.speak or false) + else + panel.mode = 'remove' + freqName:SetVisible(false) + freqLabel:SetVisible(false) + canSpeak:SetVisible(false) + end + + end + b:ChooseOptionID(panel.mode == 'add' and 1 or 2) + + panel.freq, panel.name, panel.speak = freq, freqName, canSpeak + + end, + getData = function(panel) + local result = { + mode = panel.mode, + freq = panel.freq:GetValue(), + } + if result.mode == 'add' then + result.name = panel.name:GetValue() + result.speak = panel.speak:GetChecked() + end + return result + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/talkie/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/talkie/server.lua new file mode 100644 index 0000000..afec84d --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/talkie/server.lua @@ -0,0 +1,75 @@ +local function canListen(chan, ply) + return chan.allowedListen[ply:SteamID()] or false +end +local function canSpeak(chan, ply) + return chan.allowedSpeak[ply:SteamID()] or false +end + +local function add(freq, obj) + + local chan = talkie.channels[freq] + if not chan then + chan = talkie.createChannel(freq) + chan.IsListeningAllowed = canListen + chan.IsSpeakingAllowed = canSpeak + end + + if string.Trim(obj.name or '') == '' then + obj.name = 'Неизвестная частота' + else obj.name = string.Trim(obj.name) end + chan.name = chan.name or obj.name + chan.allowedSpeak = chan.allowedSpeak or {} + chan.allowedListen = chan.allowedListen or {} + + for _, v in ipairs(obj.players) do + chan.allowedListen[v] = true + chan.allowedSpeak[v] = tobool(obj.speak) or nil + local ply = player.GetBySteamID(v) + if IsValid(ply) then + ply:ConnectTalkie(freq) + ply:SyncTalkieChannels() + end + end + +end + +local function remove(freq, obj) + + local chan = talkie.channels[freq] + if not chan then return end + for _, v in ipairs(obj.players) do + chan.allowedSpeak[v], chan.allowedListen[v] = nil + local ply = player.GetBySteamID(v) + if IsValid(ply) then + ply:SyncTalkieChannels() + if ply:GetNetVar('TalkieFreq') == freq then + ply:DisconnectTalkie() + end + end + end + if table.Count(chan.allowedListen) == 0 then + talkie.channels[freq] = nil + end + +end + +gmpanel.registerAction('talkie', function(obj) + local freq = obj.freq + if not isstring(freq) then return false end + freq = 'gmp_' .. freq + if obj.mode == 'add' then + add(freq, obj) + else remove(freq, obj) end +end) + +hook.Add('PlayerDisconnected', 'gmpanel.cleanTalkieFreqs', function(ply) + local sid = ply:SteamID() + for k, v in pairs(talkie.channels) do + if string.StartWith(k, 'gmp_') and v.allowedListen[sid] then + v.allowedListen[sid], v.allowedSpeak[sid] = nil + if table.Count(v.allowedListen) == 0 then + talkie.channels[k] = nil + end + end + end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/tracker/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/tracker/client.lua new file mode 100644 index 0000000..12a9d6e --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/tracker/client.lua @@ -0,0 +1,32 @@ +gmpanel.actions.registerAction('tracker', { + name = 'Маячок', + icon = 'octoteam/icons/gps.png', + openSettings = function(panel, data) + octolib.label(panel, 'Уведомление, которое отправляется игровому мастеру') + local channel = octolib.comboBox(panel, 'Канал', { + {'RP (Желтый цвет)', 'rp'}, + {'WARNING (Красный цвет)', 'warning'}, + {'OOC (Синий цвет)', 'ooc'}, + {'HINT (Зеленый цвет)', 'hint'}, + }) + channel:ChooseOptionID(1) + for i = 1, #channel.Choices do + if channel:GetOptionData(i) == data.channel then + channel:ChooseOptionID(i) + end + end + panel.channel = channel + + local text = octolib.textEntry(panel, 'Текст') + text:SetValue(data.text or 'Случайное уведомление') + panel.text = text + + end, + getData = function(panel) + local _, channel = panel.channel:GetSelected() + return { + text = panel.text:GetText(), + channel = channel, + } + end, +}) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/tracker/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/tracker/server.lua new file mode 100644 index 0000000..75e7671 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/actions/tracker/server.lua @@ -0,0 +1,4 @@ +gmpanel.registerAction('tracker', function(obj, ply) + local msg = octolib.string.splitByUrl(tostring(obj.text or 'Уведомление')) + if IsValid(ply) then ply:Notify(obj.channel or 'rp', unpack(msg)) end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_groups.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_groups.lua new file mode 100644 index 0000000..cf7f3ba --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_groups.lua @@ -0,0 +1,171 @@ +gmpanel.groups = gmpanel.groups or {} +gmpanel.groups.groups = gmpanel.groups or {} + +local pan = pan or nil + +function gmpanel.groups.close() + if IsValid(pan) then + pan:Close() + end +end + +function gmpanel.groups.isOpen() + return IsValid(pan) +end + +local players + +local function addItem(ply) + local cont = players:Add('DPanel') + cont:Dock(TOP) + cont:DockMargin(0, 1, 0, 0) + + local checkBox = cont:Add('DCheckBox') + checkBox:Dock(LEFT) + checkBox:SetWide(24) + players.players[#players.players+1] = {checkBox, ply:SteamID(), cont,} + + local label = cont:Add('DLabel') + label:SetText(ply:Nick()..' ('..ply:SteamName()..')') + label:Dock(FILL) + label:DockMargin(5, 0, 0, 0) + + return checkBox +end + +local function comparator(p1, p2) + return p1:Nick() < p2:Nick() +end + +local function openGroup(i, panel) + local info = gmpanel.groups.groups[i] or {name = 'Группа', players = {},} + + octolib.label(panel, 'Игроки'):DockMargin(5, 5, 0, 5) + players = panel:Add('DScrollPanel') + players.players = {} + players:Dock(TOP) + players:SetTall(250) + players:DockMargin(5, 0, 0, 5) + local function refresh(tbl) + local sel = {} + if tbl ~= nil then + for _,p in ipairs(tbl) do + sel[p] = true + end + else + for _,p in ipairs(players.players) do + if p[1]:GetChecked() then + sel[p[2]]= true + end + end + end + for _,p in ipairs(players.players) do + p[3]:Remove() + end + players.players = {} + -- sort players by their nicknames + local online = player.GetAll() + table.sort(online, comparator) + for _,ply in ipairs(online) do + local cb = addItem(ply) + if sel[ply:SteamID()] ~= nil then + cb:SetChecked(true) + end + end + end + refresh(info.players) + local function collectSelected() + local ans = {} + for _,p in ipairs(players.players) do + if p[1]:GetChecked() then + ans[#ans+1] = p[2] + end + end + return ans + end + local b = panel:Add('DButton') + b:Dock(TOP) + b:DockMargin(5, 2, 5, 5) + b:SetTall(30) + b:SetText('Обновить') + b.DoClick = function() refresh() end + + local name = octolib.textEntry(panel, 'Название группы') + name:DockMargin(5, 0, 5, 5) + name:SetValue(info.name or 'Группа') + + b = panel:Add('DPanel') + b:Dock(BOTTOM) + b:DockMargin(5, 0, 5, 5) + b:SetTall(45) + b:SetPaintBackground(false) + b = b:Add('DButton') + b:Dock(RIGHT) + b:SetText('Сохранить') + b:SetIcon('icon16/folder.png') + b:SizeToContentsX(60) + function b:DoClick() + gmpanel.groups.groups[i] = { + name = name:GetValue() or 'Группа', + players = collectSelected(), + } + gmpanel.quick.update() + gmpanel.groups.open() + end +end + +local function swap(a, b) + gmpanel.groups.groups[a], gmpanel.groups.groups[b] = gmpanel.groups.groups[b], gmpanel.groups.groups[a] +end + +local function build() + gmpanel.groups.close() + + pan = vgui.Create('DFrame') + pan:SetSize(700, 500) + pan:SetTitle('Группы') + pan:Center() + pan:MakePopup() + pan:SetDeleteOnClose(true) + + local groups = pan:Add('DListView') + groups:Dock(LEFT) + groups:SetWide(250) + groups:SetHideHeaders(true) + groups:DockMargin(0, 0, 5, 0) + groups:AddColumn(''):SetFixedWidth(32) + groups:AddColumn(L.title) + groups:SetDataHeight(32) + groups:SetMultiSelect(false) + + local right = pan:Add('DPanel') + right:Dock(FILL) + + for _,v in ipairs(gmpanel.groups.groups) do + local icon = vgui.Create('DImage') + icon:SetImage('octoteam/icons/group2.png') + groups:AddLine(icon, v.name) + end + local icon = vgui.Create('DImage') + icon:SetImage('octoteam/icons/round_add.png') + groups:AddLine(icon, 'Новая группа...') + + function groups:OnRowSelected(i, row) + right:Clear() + openGroup(i, right) + end + + function groups:OnRowRightClick(i, row) + if i > #gmpanel.groups.groups then return end + local menu = DermaMenu() + if i > 1 then menu:AddOption('Выше', function() swap(i, i-1) gmpanel.quick.update() build() end):SetIcon('icon16/arrow_up.png') end + if i < #gmpanel.groups.groups then menu:AddOption('Ниже', function() swap(i, i+1) gmpanel.quick.update() build() end):SetIcon('icon16/arrow_down.png') end + menu:AddSpacer() + menu:AddOption('Удалить', function() table.remove(gmpanel.groups.groups, k) gmpanel.quick.update() build() end):SetIcon('icon16/cancel.png') + menu:Open() + end +end + +function gmpanel.groups.open() + build() +end diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_init.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_init.lua new file mode 100644 index 0000000..8c0ccfb --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_init.lua @@ -0,0 +1,80 @@ +local permission, permissionGlobal = 'DBG: Панель ивентов', 'DBG: Расширенный доступ к панели ивентов' + +gmpanel = gmpanel or {} +gmpanel.index = gmpanel.index or {} + +local index = nil + +function gmpanel.index.close() + if (index ~= nil and IsValid(index)) then index:Close() end +end + +local function rebuildIndex() + gmpanel.index.close() + + index = vgui.Create('DFrame') + index:SetSize(200, 240) + index:SetTitle('Панель гейм-мастера') + index:Center() + index:MakePopup() + index:SetDeleteOnClose(true) + + local b = index:Add('DButton') + b:Dock(TOP) + b:SetTall(30) + b:DockMargin(0, 10, 0, 0) + b:SetText('Кнопки') + b.DoClick = gmpanel.quick.open + + b = index:Add('DButton') + b:Dock(TOP) + b:SetTall(30) + b:DockMargin(0, 10, 0, 0) + b:SetText('Группы') + b.DoClick = gmpanel.groups.open + + b = index:Add('DButton') + b:Dock(TOP) + b:SetTall(30) + b:DockMargin(0, 10, 0, 0) + b:SetText('Действия') + b.DoClick = gmpanel.actions.open + + b = index:Add('DButton') + b:Dock(TOP) + b:SetTall(30) + b:DockMargin(0, 10, 0, 0) + b:SetText('Сценарии') + b.DoClick = gmpanel.scenarios.open + + b = index:Add('DButton') + b:Dock(TOP) + b:SetTall(30) + b:DockMargin(0, 10, 0, 0) + b:SetText('Настройки') + b.DoClick = gmpanel.global.open +end + +function gmpanel.index.open() + rebuildIndex() +end + +concommand.Add('gmpanel', function() + if not LocalPlayer():query(permission) and not LocalPlayer():query(permissionGlobal) then + octolib.notify.show('warning', L.not_have_access) + return + end + rebuildIndex() +end) + +concommand.Add('+gmpanel', function() + if not LocalPlayer():query(permission) and not LocalPlayer():query(permissionGlobal) then + octolib.notify.show('warning', L.not_have_access) + return + end + rebuildIndex() +end) + +concommand.Add('-gmpanel', function() + gmpanel.index.close() +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_quick.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_quick.lua new file mode 100644 index 0000000..655931a --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_quick.lua @@ -0,0 +1,181 @@ +gmpanel.quick = gmpanel.quick or {} + +local showRadius = false +local radius = 0 + +hook.Add('PostDrawTranslucentRenderables', 'gmpanel.radius', function() + if not showRadius then return end + render.SetColorMaterial() + local pos = LocalPlayer():GetEyeTrace().HitPos + local r = radius + if pos:DistToSqr(LocalPlayer():GetPos()) <= r * r then + r = -r + end + render.DrawSphere(pos, r, 30, 30, Color(255, 255, 255, 32)) + + for _,v in ipairs(ents.FindInSphere(pos, radius)) do + if v:IsPlayer() then + local mins, maxs = v:GetCollisionBounds() + local ang = v:GetAngles() + ang.p = 0 + render.DrawWireframeBox(v:GetPos(), ang, mins, maxs, color_red, false) + end + end +end) + +local pan + +function gmpanel.quick.close() + if IsValid(pan) then + pan:Close() + end +end + +local function performInRadius() + local e = ents.FindInSphere(LocalPlayer():GetEyeTrace().HitPos, radius) + local players = {} + for _,v in ipairs(e) do + if v:IsPlayer() then + players[#players + 1] = v:SteamID() + end + end + + local menu = DermaMenu() + hook.Run('gmpanel.populateActionsMenu', menu, players) + menu:AddSpacer() + menu:AddOption('Объединить в группу', function() + Derma_StringRequest('Объединить в группу', 'Укажи название группы', 'Группа', function(str) + gmpanel.groups.groups[#gmpanel.groups.groups + 1] = { + name = string.Trim(str), + players = players, + } + gmpanel.quick.update() + if gmpanel.groups.isOpen() then gmpanel.groups.open() end + end) + end):SetIcon('icon16/group_add.png') + menu:Open() +end + +local function performForGroup(self) + local menu = DermaMenu() + hook.Run('gmpanel.populateActionsMenu', menu, self.players) + menu:Open() +end + +local function build(w, h, x, y) + gmpanel.quick.close() + pan = vgui.Create('DFrame') + pan:SetSize(isnumber(w) and w or 250, isnumber(h) and h or ScrH() * 0.7) + pan:SetTitle('Панель Гейм-Мастера') + if isnumber(x) and isnumber(y) then + pan:SetPos(x, y) + else + pan:AlignBottom(10) + pan:AlignLeft(10) + end + pan:SetDeleteOnClose(true) + pan:SetSizable(true) + pan:SetMinHeight(math.max(265, 0.2 * ScrH())) + pan:SetMinWidth(250) + function pan:OnClose() + showRadius = val + end + + local scr = pan:Add('DListView') + scr:Dock(FILL) + + local p = scr:Add('DPanel') + p:SetTall(70) + p:Dock(TOP) + p:DockMargin(0, 0, 0, 10) + + octolib.label(p, 'Выполнить в радиусе от точки прицела'):DockMargin(5, 2, 0, 2) + + local pp = p:Add('DPanel', 48) + pp:Dock(TOP) + pp:SetPaintBackground(false) + pp:SetTall(24) + local btn = pp:Add('DButton') + btn:Dock(RIGHT) + btn:SetWide(24) + btn:SetIcon('icon16/transmit.png') + btn:SetText('') + function btn:DoClick() + end + btn.DoClick = performInRadius + + local slider = pp:Add('DNumSlider') + slider:Dock(FILL) + slider:DockMargin(5, 0, 0, 0) + slider:SetMinMax(0, 2000) + slider:SetDecimals(1) + slider:SetText('Радиус:') + + local show = octolib.checkBox(p, 'Показать') + show:DockMargin(5, 5, 0, 2) + function show:OnChange(val) + showRadius = val + end + function slider:OnValueChanged(val) + radius = val + end + + for _,v in ipairs(gmpanel.groups.groups) do + local b = scr:Add('DButton') + b:Dock(TOP) + b:SetTall(32) + b:SetText(v.name) + b.players = v.players + b.DoClick = performForGroup + b:DockMargin(0,5,0,0) + end + + local activeScenarios = pan:Add('DPanel') + activeScenarios:Dock(BOTTOM) + activeScenarios:SetTall(100) + + local lbl = octolib.label(activeScenarios, 'Активные сценарии') + lbl:DockMargin(0, 5, 0, 5) + lbl:SetContentAlignment(5) + + local lst = activeScenarios:Add('DListView') + lst:Dock(FILL) + lst:AddColumn('Время'):SetWidth(20) + lst:AddColumn('Прогресс'):SetWidth(40) + lst:AddColumn('Название') + lst:AddColumn('Группа') + lst:SetHideHeaders(true) + lst:SetMultiSelect(false) + + local function updateScenarios() + if not IsValid(lst) then return timer.Remove('gmpanel.quick.updateScenarios') end + lst:Clear() + for uid, scenData in SortedPairs(gmpanel.scenarios.active) do + local time = math.Round(timer.TimeLeft('gmpanel.scenario.' .. uid) or 0) + lst:AddLine(time, scenData[1], scenData[2], scenData[3]).uid = uid + end + end + timer.Create('gmpanel.quick.updateScenarios', 1, 0, updateScenarios) + updateScenarios() + + function lst:OnRowRightClick(_, line) + local uid = line.uid + local menu = DermaMenu() + menu:AddOption('Отменить', function() + gmpanel.scenarios.active[uid] = nil + timer.Remove('gmpanel.scenario.' .. uid) + octolib.notify.show('Выполнение сценария отменено') + end):SetIcon(octolib.icons.silk16('cross')) + menu:Open() + end + +end + +function gmpanel.quick.update() + if not IsValid(pan) then return end + build(pan:GetWide(), pan:GetTall(), pan:GetPos()) +end + +gmpanel.quick.close() + +gmpanel.quick.open = build diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_scenarios.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_scenarios.lua new file mode 100644 index 0000000..dd066d3 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/cl_scenarios.lua @@ -0,0 +1,309 @@ +gmpanel.scenarios = gmpanel.scenarios or {} +gmpanel.scenarios.list = gmpanel.scenarios.list or {} +gmpanel.scenarios.active = gmpanel.scenarios.active or {} + +pcall(function() + local f = file.Read('gmpanel_scenarios.dat') + if f then gmpanel.scenarios.list = pon.decode(f) or {} end +end) + +gmpanel.scenarios.save = octolib.func.debounce(function() + file.Write('gmpanel_scenarios.dat', pon.encode(gmpanel.scenarios.list)) +end, 1) + +local pan, left, editor, scenarios, actionsPan, actions + +local function defaultLinePaint(self, w, h, off) + off = off or 0 + if self.id % 2 == 1 then + draw.RoundedBox(4, off, off, w - off, h - off, Color(0,0,0, 35)) + end +end + +local colors = CFG.skinColors +local function scenarioPaint(self, w, h) + local off, off2 = 0, 0 + if IsValid(editor) and editor.editUid == self.uid then + draw.RoundedBox(4, 0, 0, w, h, colors.g) + off, off2 = 1, 2 + end + if self.sel then + draw.RoundedBox(4, off, off, w - off2, h - off2, Color(170,119,102)) + else defaultLinePaint(self, w, h, off2) end +end + +local function addedLineSelect(self) + for _,v in ipairs(scenarios:GetChildren()) do + v.sel = nil + end + self.sel = true +end + +local function onModified(self) + local byUid = {} + for _,v in ipairs(gmpanel.scenarios.list) do + byUid[v.uid] = v + end + gmpanel.scenarios.list = {} + for _,v in ipairs(self:GetChildren()) do + gmpanel.scenarios.list[#gmpanel.scenarios.list + 1] = byUid[v.uid] + v.sel = v.uid == editor.editUid or nil + end + gmpanel.scenarios.save() +end + +local function addedLineClick(self, mcode) + + if isfunction(self.oldMP) then self:oldMP(mcode) end + if mcode ~= MOUSE_RIGHT then return end + + self:Select() + + local saved = gmpanel.scenarios.list[self.id] + if not saved then return end + + local menu = DermaMenu() + + menu:AddOption('Экспортировать', function() + local data = table.Copy(saved) + data.uid = nil + SetClipboardText(pon.encode(data)) + octolib.notify.show('Код сценария скопирован в буфер обмена') + end):SetIcon(octolib.icons.silk16('page_go')) + + menu:AddOption('Редактировать', function() + if IsValid(editor) then + editor:OpenEditor(saved) + end + end):SetIcon(octolib.icons.silk16('pencil')) + + menu:AddOption('Удалить', function() + gmpanel.scenarios.edit(self.uid) + end):SetIcon(octolib.icons.silk16('cross')) + menu:Open() + +end + +local function rebuildScenarios() + + if not IsValid(left) then return end + if IsValid(scenarios) then scenarios:Remove() end + + scenarios = left:Add('DScrollPanel') + scenarios:Dock(FILL) + scenarios = scenarios:Add('DListLayout') + scenarios:Dock(FILL) + scenarios:MakeDroppable('gmpanel.scenarios') + + function scenarios:AddPanel(i, data) + local cont = self:Add('DPanel') + cont:SetTall(20) + cont.Paint = scenarioPaint + cont:SetMouseInputEnabled(true) + cont.id = i + cont.uid = data.uid + cont.oldMP, cont.OnMouseReleased, cont.Select = cont.OnMouseReleased, addedLineClick, addedLineSelect + + local icon = cont:Add('DImage') + icon:Dock(LEFT) + icon:SetWide(16) + icon:DockMargin(0, 2, 5, 2) + icon:SetImage(data._icon) + + local name = cont:Add('DLabel') + name:Dock(FILL) + name:SetText(data._name) + + return cont + end + + for i,v in ipairs(gmpanel.scenarios.list) do + scenarios:AddPanel(i, v) + end + scenarios.OnModified = onModified + +end + +function gmpanel.scenarios.execute(scenData, players) + local uid = table.Count(gmpanel.scenarios.active) + 1 + + local actions, size = {}, #scenData.actions + for num, action in ipairs(scenData.actions) do + local predefined = gmpanel.actions.available[action.id] + if predefined then + actions[#actions + 1] = function(nxt) + netstream.Request('dbg-event.mapPlayerNames', players):Then(function(names) + gmpanel.scenarios.active[uid] = { num .. ' / ' .. size, predefined.name, table.concat(names, ', ') } + timer.Create('gmpanel.scenario.' .. uid, action._delay or 0, 1, function() + local func = predefined.execute or gmpanel.actions.defaultExecute + func(action, players) + nxt() + end) + end) + end + end + end + actions[#actions + 1] = function(nxt) + gmpanel.scenarios.active[uid] = nil + end + + octolib.func.chain(actions) +end + +function gmpanel.scenarios.add(scenData) + scenData.uid = octolib.string.uuid():sub(1, 8) + gmpanel.scenarios.list[#gmpanel.scenarios.list + 1] = scenData + gmpanel.scenarios.save() + rebuildScenarios() + return true +end + +function gmpanel.scenarios.edit(uid, scenData) + + if not uid then return end + local idx + for i,v in ipairs(gmpanel.scenarios.list) do + if v.uid == uid then + idx = i + break + end + end + if not idx then return end + + if not scenData then + + table.remove(gmpanel.scenarios.list, idx) + if IsValid(editor) and editor.editUid == uid then editor.editUid = nil end + rebuildScenarios() + + else + gmpanel.scenarios.list[idx] = scenData + if IsValid(scenarios) then + local cont = scenarios:GetChildren()[idx] + cont:GetChildren()[1]:SetImage(scenData._icon) + cont:GetChildren()[2]:SetText(scenData._name) + end + end + + gmpanel.scenarios.save() + +end + +hook.Add('gmpanel.populateActionsMenu', 'gmpanel.scenarios', function(menu, players) + for _,v in ipairs(gmpanel.scenarios.list) do + menu:AddOption(v._name, function() + gmpanel.scenarios.execute(v, players) + end):SetIcon(v._icon) + end + menu:AddSpacer() +end) + +function gmpanel.scenarios.close() + if IsValid(pan) then + pan:Close() + end +end + +function gmpanel.scenarios.updateActions() + + if IsValid(actions) then actions:Remove() end + if not IsValid(actionsPan) then return end + + actions = actionsPan:Add('DListLayout') + actions:Dock(FILL) + actions:MakeDroppable('gmpanel.actions.added') + + -- timer.Simple(0, function() + for i,data in ipairs(gmpanel.actions.added) do + + local cont = actions:Add('DPanel') + cont:Dock(TOP) + cont:SetTall(20) + cont.Paint = defaultLinePaint + cont:SetMouseInputEnabled(true) + cont.data = data + cont.id = i + + local icon = cont:Add('DImage') + icon:Dock(LEFT) + icon:SetWide(16) + icon:DockMargin(0, 2, 5, 2) + icon:SetImage(data._icon) + + local name = cont:Add('DLabel') + name:Dock(FILL) + name:SetText(data._name) + + end + -- end) + +end + +local function build() + gmpanel.groups.close() + + pan = vgui.Create('DFrame') + pan:SetSize(700, 500) + pan:SetTitle('Сценарии') + pan:Center() + pan:MakePopup() + pan:SetDeleteOnClose(true) + pan:SetSizable(true) + pan:SetMinWidth(700) + pan:SetMinHeight(500) + + left = pan:Add('DPanel') + left:Dock(LEFT) + left:SetWide(150) + + rebuildScenarios() + + local btnImport = left:Add('DButton') + btnImport:Dock(BOTTOM) + btnImport:SetTall(20) + btnImport:DockMargin(0, 5, 0, 0) + btnImport:SetText('Импорт сценария') + btnImport:SetIcon(octolib.icons.silk16('page_code')) + function btnImport:DoClick() + Derma_StringRequest('Импорт сценария', 'Вставь код сценария, полученный при экспорте', '', function(inp) + local succ, data = pcall(pon.decode, inp) + if not succ or not istable(data) then + return octolib.notify.show('warning', 'Кажется, код сценария поврежден') + end + gmpanel.scenarios.add(data) + end) + end + + local btnCreate = left:Add('DButton') + btnCreate:Dock(BOTTOM) + btnCreate:SetTall(20) + btnCreate:DockMargin(0, 5, 0, 0) + btnCreate:SetText('Создать сценарий') + btnCreate:SetIcon('octoteam/icons-16/page_white_put.png') + function btnCreate:DoClick() + editor:OpenEditor({}) + for _,v in ipairs(scenarios:GetChildren()) do + v.sel = nil + end + end + + editor = pan:Add('gmp_scenario_editor') + editor:Dock(FILL) + + actionsPan = pan:Add('DPanel') + actionsPan:Dock(RIGHT) + actionsPan:SetWide(150) + actionsPan:SetPaintBackground(false) + actionsPan = actionsPan:Add('DScrollPanel') + actionsPan:Dock(FILL) + actionsPan:DockMargin(5, 0, 0, 0) + local lbl = octolib.label(actionsPan, 'Заготовленные действия') + lbl:DockMargin(0, 0, 0, 5) + lbl:SetContentAlignment(5) + gmpanel.scenarios.updateActions() + +end + +function gmpanel.scenarios.open() + build() +end diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/global/client.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/global/client.lua new file mode 100644 index 0000000..378c585 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/global/client.lua @@ -0,0 +1,198 @@ +gmpanel.global = gmpanel.global or {} + +local fetch = {command = 'fetch',} + +local pan = pan or nil + +function gmpanel.global.close() + if IsValid(pan) then + pan:Close() + end +end + +local function cbox(base, var, txt, def) + local b = octolib.vars.checkBox(base, var, txt) + if octolib.vars.get(var) == nil then b:SetValue(def) end + return b +end + +local function spacer(base) + local p = base:Add('Panel') + p:Dock(TOP) + function p:Paint(w) + draw.RoundedBox(0, 0, 0, w, 1, Color(127, 127, 127)) + end +end + +local function var(k) + return 'gmpanel.global.'..k +end + +local function getVar(k, def) + local v = octolib.vars.get(var(k)) + if v ~= nil then return v else return def end +end + +local function category(cont, name) + local cat = cont:Add('DCollapsibleCategory') + cat:Dock(TOP) + cat:SetTall(100) + cat:SetExpanded(0) + cat:SetLabel(name) + local layout = vgui.Create('DListLayout') + layout:SetSize(100, 100) + layout:DockPadding(5, 5, 5, 5) + layout:SetPaintBackground(true) + cat:SetContents(layout) + return cat, layout +end + +local cback + +netstream.Hook('dbg-event.global.fetch', function(data) + octolib.vars.set(var'karma', data.karma) + if data.respawn ~= nil then + octolib.vars.set(var'respawn', data.respawn) + end + octolib.vars.set(var'inventories', data.inventories) + octolib.vars.set(var'storages', data.storages) + octolib.vars.set(var'fog.def', data.fogdef) + if not data.fogdef then + octolib.vars.set(var'fog.color', data.fogcolor:ToColor()) + octolib.vars.set(var'fog.dist', data.fogdst) + end + octolib.vars.set(var'ooc', data.ooc) + octolib.vars.set(var'radio', data.radio) + octolib.vars.set(var'ems', data.ems) + octolib.vars.set(var'calls', data.calls) + octolib.vars.set(var'sms', data.sms) + octolib.vars.set(var'advert', data.advert) + if cback then cback() end +end) + +local function build() + gmpanel.global.close() + + pan = vgui.Create('DFrame') + pan:SetSize(500, 500) + pan:SetTitle('Настройки') + pan:Center() + pan:MakePopup() + pan:SetDeleteOnClose(true) + + local btns = pan:Add('DPanel') + btns:Dock(BOTTOM) + btns:DockMargin(5, 5, 5, 5) + btns:SetTall(30) + + local fetchBtn = btns:Add('DButton') + fetchBtn:Dock(RIGHT) + fetchBtn:DockMargin(1, 0, 0, 0) + fetchBtn:SetText('Текущие значения') + fetchBtn:SetIcon('icon16/report.png') + fetchBtn:SizeToContentsX(45) + function fetchBtn:DoClick() + self:SetEnabled(false) + cback = function() + self:SetEnabled(true) + cback = nil + end + + netstream.Start('dbg-event.execute', 'global', fetch) + end + local applyBtn = btns:Add('DButton') + applyBtn:Dock(FILL) + applyBtn:DockMargin(0, 0, 1, 0) + applyBtn:SetTall(30) + applyBtn:SetText('Применить') + applyBtn:SetIcon('icon16/tick.png') + + local cont = pan:Add('DScrollPanel') + cont:Dock(FILL) + cont:DockMargin(5, 5, 5, 5) + + local krc, krcl = category(cont, 'Карма и возрождение') + local karma = cbox(krcl, var'karma', 'Изменение кармы', true) + karma:DockMargin(0, 0, 0, 0) + local respawn = octolib.vars.slider(krcl, var'respawn', 'Время возрождения (сек)', 0, 3600, 0) + respawn:DockMargin(0, 0, 0, 0) + respawn:SetVisible(not karma:GetChecked()) + local kvc = karma.OnChange + function karma:OnChange(val) + respawn:SetVisible(not val) + krc:Toggle() krc:Toggle() + kvc(self, val) + end + + local ic, icl = category(cont, 'Инвентарь') + cbox(icl, var'inventories', 'Синхронизация инвентарей', true):DockMargin(0, 0, 0, 0) + cbox(icl, var'storages', 'Хранилища', true):DockMargin(0, 0, 0, 0) + + local fc, fcl = category(cont, 'Туман') + local def = cbox(fcl, var'fog.def', 'По умолчанию', true) + def:DockMargin(0, 0, 0, 0) + local col, lbl = octolib.vars.colorPicker(fcl, var'fog.color', 'Цвет тумана', false) + col:DockMargin(0, 0, 0, 0) + col:SetVisible(not def:GetChecked()) + lbl:SetVisible(not def:GetChecked()) + local map = game.GetMap() + local dist = 3000 - math.Clamp(player.GetCount() - 10, 0, 40) * 38 + if map:find('evocity') then dist = dist + 1000 end + if map:find('riverden') then dist = dist + 3000 end + if map:find('truenorth') then dist = dist + 4000 end + local dst = octolib.vars.slider(fcl, var'fog.dist', 'Дальность тумана', 50, dist, 0) + dst:DockMargin(0, 0, 0, 0) + dst:SetVisible(not def:GetChecked()) + local cvc = def.OnChange + function def:OnChange(val) + col:SetVisible(not val) + lbl:SetVisible(not val) + dst:SetVisible(not val) + fc:Toggle() fc:Toggle() + cvc(self, val) + end + + local oocc, ooccl = category(cont, 'Глобальный OOC') + cbox(ooccl, var'ooc', 'Глобальный OOC', true):DockMargin(0, 0, 0, 0) + + local netc, netcl = category(cont, 'Связь') + cbox(netcl, var'radio', 'Возможность говорить в рацию', true):DockMargin(0, 0, 0, 0) + cbox(netcl, var'ems', 'Вызовы экстренных служб', true):DockMargin(0, 0, 0, 0) + cbox(netcl, var'calls', 'Вызовы врача, механика, таксиста и т.д.', true):DockMargin(0, 0, 0, 0) + cbox(netcl, var'sms', 'SMS', true):DockMargin(0, 0, 0, 0) + cbox(netcl, var'advert', 'Рекламные объявления', true):DockMargin(0, 0, 0, 0) + + function applyBtn:DoClick() + self:SetEnabled(false) + local data = {command = 'save'} + if krc:GetExpanded() then + data.karma = getVar('karma', true) + if not getVar('karma') then data.respawn = getVar('respawn', 0) end + end + if ic:GetExpanded() then + data.inventories = getVar('inventories', true) + data.storages = getVar('storages', true) + end + if fc:GetExpanded() then + data.fogdef = getVar('fog.def', true) + if not data.fogdef then + data.fogcolor = getVar('fog.color') + data.fogdst = getVar('fog.dist') + end + end + if oocc:GetExpanded() then data.ooc = getVar('ooc', true) end + if netc:GetExpanded() then + data.raio = getVar('radio', true) + data.ems = getVar('ems', true) + data.calls = getVar('calls', true) + data.sms = getVar('sms', true) + data.advert = getVar('advert', true) + end + netstream.Start('dbg-event.execute', 'global', data) + self:SetEnabled(true) + end +end + +function gmpanel.global.open() + build() +end diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/global/server.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/global/server.lua new file mode 100644 index 0000000..5b9df7d --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/global/server.lua @@ -0,0 +1,140 @@ +local callsCommands = octolib.array.toKeys { + '/sms', + '/med', + '/callmech', + '/callfire', + '/callworker', + '/calltaxi', +} + +local data = { + karma = true, + inventories = true, + storages = true, + ooc = true, + fogdef = true, + radio = true, + ems = true, + calls = true, + sms = true, + advert = true, +} + +local function toVector(col) + return Vector(col.r / 255, col.g / 255, col.b / 255) +end + +local function copy(tbl) + if tbl.karma ~= nil then data.karma = tbl.karma data.respawn = nil end + if tbl.respawn ~= nil then data.respawn = tbl.respawn end + if tbl.inventories ~= nil then data.inventories = tbl.inventories end + if tbl.storages ~= nil then data.storages = tbl.storages end + if tbl.fogdef ~= nil then + data.fogdef = tbl.fogdef + if not tbl.fogdef then + data.fogcolor = toVector(tbl.fogcolor) + data.fogdst = tbl.fogdst + end + end + if tbl.ooc ~= nil then data.ooc = tbl.ooc end + if tbl.radio ~= nil then data.radio = tbl.radio end + if tbl.ems ~= nil then data.ems = tbl.ems end + if tbl.calls ~= nil then data.calls = tbl.calls end + if tbl.sms ~= nil then data.sms = tbl.sms end + if tbl.advert ~= nil then data.advert = tbl.advert end +end + +local function save(ply, opts) + if opts.fogdef ~= nil then + ply:Notify('Настройки тумана применяются до минуты') + end + + if opts.storages == false then + ply:Notify('Удаляем все хранилища') + for i, v in ipairs(ents.FindByClass('octoinv_storage')) do v:Remove() end + end + + if opts.inventories == false and data.inventories then + opts.inventories = true + ply:Notify('Сохраняем инвентари игроков') + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + data.inventories = true + ply:SaveInventory() + data.inventories = false + ply:ImportInventory(octoinv.defaultInventory) + ply:GetInventory():AddContainer('_hand', octoinv.defaultHand):QueueSync() + data.inventories = true + end):Then(function() + data.inventories = false + ply:Notify('Инвентари игроков сохранены') + end) + end + + if opts.inventories and data.inventories == false then + ply:Notify('Сохраняем инвентари игроков') + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + data.inventories = true + ply:LoadInventory() + ply:GetInventory():AddContainer('_hand', octoinv.defaultHand):QueueSync() + data.inventories = false + end):Then(function() + ply:Notify('Инвентари игроков загружены') + data.inventories = true + end) + end + + copy(opts) +end + +local function fetch(ply) + netstream.Start(ply, 'dbg-event.global.fetch', data) +end + +gmpanel.registerAction('global', function(opts, ply) + local cmd = opts.command + if not cmd then return end + if cmd == 'save' then save(ply, opts) + elseif cmd == 'fetch' then fetch(ply) end +end) + +hook.Add('PlayerCanOOC', 'dbg-event.global.ooc', function(ply) + if ply:Team() == TEAM_ADMIN then return end + if data.ooc == false then return false, L.config_disabled:format('OOC', '') end +end) + +hook.Add('octoinv.overrideStorages', 'dbg-event.global', function(ply) + if ply:Team() == TEAM_ADMIN then return end + if data.storages == false then return false end +end) + +hook.Add('dbg-ghosts.overrideTime', 'dbg-event.global', function(ply) + if data.karma == false then return data.respawn or 0 end +end) + +hook.Add('octoinv.overrideInventories', 'dbg-event.global', function() + if data.inventories == false then return false end +end) + +hook.Add('dbg-fog.update', 'dbg-event.global', function(col, dist, dist2) + if data.fogdef then return end + return data.fogcolor, data.fogdst + 200, data.fogdst +end) + +hook.Add('dbg-karma.override', 'dbg-event.global', function(ply) + if data.karma == false then return false end + if ply.karmaDisabled then return false end +end) + +hook.Add('dbg-talkie.canSpeak', 'dbg-event.global', function() + if data.radio == false then return false end +end) + +hook.Add('octochat.canExecute', 'dbg-event.global', function(ply, cmd) + if data.calls == false and callsCommands[cmd] then return false end + if data.sms == false and cmd == '/sms' then return false end + if data.advert == false and (cmd == '/advert' or cmd == '/ad') then return false end +end) + +hook.Add('dbg.canCallEMS', 'dbg-event.global', function() + if data.ems == false then return false end +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/gmpanel/sv_init.lua b/garrysmod/addons/feature-gmpanel/lua/gmpanel/sv_init.lua new file mode 100644 index 0000000..f7e4118 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/gmpanel/sv_init.lua @@ -0,0 +1,61 @@ +gmpanel = gmpanel or {} + +local permission, permissionGlobal = 'DBG: Панель ивентов', 'DBG: Расширенный доступ к панели ивентов' + +function gmpanel.checkPermissions(ply) + return ply:query(permission) or ply:query(permissionGlobal) +end + +function gmpanel.buildTargets(opts) + local targets = {} + for _,id in ipairs(opts) do + local pl = player.GetBySteamID(id) + if IsValid(pl) then + targets[#targets + 1] = pl + end + end + return targets +end + +local actions = {} + +function gmpanel.registerAction(action, callback) + actions[action] = callback +end + +hook.Add('dbg-event.testExecution', 'dbg-event.checkPermissions', function(ply, action) + if action == 'global' and not ply:query(permissionGlobal) then return false end + if not ply:query(permission) then return false end +end) + +netstream.Hook('dbg-event.execute', function(ply, action, opts) + + local can, msg = hook.Run('dbg-event.testExecution', ply, action, opts) + if can == false then + ply:Notify('warning', msg or L.not_have_access) + return + end + local func = actions[action] + if not func then + ply:Notify('warning', 'Неизвестный метод') + return + end + + if func(opts, ply) == false then return end + local log = octologs.createLog(octologs.CAT_GMPANEL) + :Add(octologs.ply(ply), ' performed action on gmpanel: ', octologs.string(action), ', ') + local players = opts.players + if players then + log:Add(octologs.table('players', players), ', ') + opts.players = nil + end + log:Add(octologs.table('data', opts, true)):Save() + +end) + +netstream.Listen('dbg-event.mapPlayerNames', function(reply, ply, sids) + if not (istable(sids) and gmpanel.checkPermissions(ply)) then return reply({}) end + reply(octolib.table.mapSequential(gmpanel.buildTargets(sids), function(pl) + return pl:Name() + end)) +end) diff --git a/garrysmod/addons/feature-gmpanel/lua/vgui/gmp_scenario_action.lua b/garrysmod/addons/feature-gmpanel/lua/vgui/gmp_scenario_action.lua new file mode 100644 index 0000000..7192c78 --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/vgui/gmp_scenario_action.lua @@ -0,0 +1,189 @@ +local function findInTable(tbl, target) + for i, v in ipairs(tbl) do + if v == target then return i end + end + return -1 +end + +local function automaticResize(self) + self:SizeToChildren(false, true) + self.w, self.h = self:GetSize() +end + +local function addButton(pnl, name, icon, doClick) + local btn = pnl:Add('DImageButton') + btn:Dock(RIGHT) + btn:DockMargin(0,4,5,4) + btn:SetWide(16) + btn:SetImage(octolib.icons.silk16(icon)) + btn:SetTooltip(name) + btn.DoClick = doClick + return btn +end + +local function doRemove(self) + local editor = self:GetParent():GetParent().editor + self:GetParent():GetParent():Remove() + timer.Simple(0, function() if IsValid(editor) then editor:UpdateNumbers() end end) +end + +local function doCopy(self) + + local el = self:GetParent():GetParent() + local editor = el.editor + local dnd, btmPan = editor.dnd, editor.btmPan + dnd:SetParent() + btmPan:SetParent() + + local act = gmpanel.actions.available[el.data.id] + local curData + if isfunction(act.getData) then + curData = act.getData(el.panel) + else curData = {} end + + if curData == nil then + return octolib.notify.show('warning', 'Кажется, не все поля заполнены верно') + end + + curData = istable(curData) and curData or {value = curData} + curData.id = el.data.id + curData._delay = el.time:GetValue() + + editor:AddAction(curData) + dnd:SetParent(editor) + btmPan:SetParent(editor) + +end + +local function doMoveUp(self) + local act = self:GetParent():GetParent() + local actions = act.editor.actions + local num = findInTable(actions, act) + if IsValid(actions[num - 1]) then + actions[num], actions[num - 1] = actions[num - 1], actions[num] + act.editor:UpdateOrder() + timer.Simple(0, function() + act.editor:ScrollToChild(act) + end) + end +end + +local function doMoveDown(self) + local act = self:GetParent():GetParent() + local actions = act.editor.actions + local num = findInTable(actions, act) + if IsValid(actions[num + 1]) then + actions[num], actions[num + 1] = actions[num + 1], actions[num] + act.editor:UpdateOrder() + timer.Simple(0, function() + act.editor:ScrollToChild(act) + end) + end +end + +local function doRename(self) + local lbl, act = self:GetParent(), self:GetParent():GetParent() + Derma_StringRequest('Переименовать', 'Укажи новое название', lbl.action, function(newName) + newName = string.Trim(newName) + if newName == '' then return end + lbl.action = newName + act.data._actName = newName + act:UpdateNumber() + end) +end + +local function doResize(self) + local act = self:GetParent():GetParent() + act:SizeTo(act.w, act.minimized and act.h or 32, 0.5, 0, -1, function() + if not act.minimized then + act.PerformLayout = automaticResize + end + end) + act.minimized = not act.minimized + if act.minimized then act.PerformLayout = octolib.func.zero end + self:SetImage(octolib.icons.silk16(act.minimized and 'arrow_out' or 'arrow_in')) + self:SetTooltip(act.minimized and 'Развернуть' or 'Свернуть') +end + +local function doMove(self) + local act = self:GetParent():GetParent() + local editor = act.editor + local num = findInTable(editor.actions, act) + if not IsValid(editor.actions[num]) then return end + Derma_StringRequest('Перемещение', 'Укажи текущий номер действия, после которого будет стоять это действие (от 0 до ' .. #editor.actions .. ')', num-1, function(sNewPos) + local newPos = tonumber(sNewPos) + if not newPos or newPos < 0 or newPos > #editor.actions then return end + if num == newPos then return end + table.remove(editor.actions, num) + table.insert(editor.actions, newPos + (num >= newPos and 1 or 0), act) + editor:UpdateOrder() + timer.Simple(0, function() + act.editor:ScrollToChild(act) + end) + end) +end + + +--------------------------- + + +local PANEL = {} + +function PANEL:Init() + + self:DockPadding(5,5,5,5) + + local lbl = octolib.label(self, '') + lbl:SetFont('f4.normal') + lbl:SetContentAlignment(4) + lbl:SetTall(24) + self.lbl = lbl + + addButton(lbl, 'Удалить', 'cross', doRemove) + addButton(lbl, 'Дублировать', 'page_copy', doCopy) + addButton(lbl, 'Переместить', 'transform_move', doMove) + self.btnUp = addButton(lbl, 'Вверх', 'arrow_up', doMoveUp) + self.btnDown = addButton(lbl, 'Вниз', 'arrow_down', doMoveDown) + addButton(lbl, 'Переименовать', 'pencil', doRename) + addButton(lbl, 'Свернуть', 'arrow_in', doResize) + + local pnl = self:Add('DPanel') + pnl:Dock(TOP) + pnl:SetPaintBackground(false) + pnl.PerformLayout = automaticResize + self.panel = pnl + + local time = octolib.label(self, 'Через сколько секунд после выполнения предыдущего действия выполнить это:') + time:DockMargin(0, 10, 0, 0) + time:SetMultiline(true) + time:SetWrap(true) + time:SetTall(30) + time = octolib.numberWang(self, nil, 0, 0, 1200) + time:SetDecimals(1) + time:DockMargin(0, 0, 0, 0) + self.time = time + +end + +function PANEL:UpdateNumber(num) + self.num = num or self.num + self.lbl:SetText(self.num .. '. ' .. self.lbl.action) + self.btnUp:SetVisible(self.num > 1) + self.btnDown:SetVisible(true) +end + +function PANEL:SetAction(action, data) + self.lbl.action = data._actName or action.name + self.data = data + self.id = data.id + action.openSettings(self.panel, data) + self.time:SetValue(data._delay or 0) +end + +PANEL.PerformLayout = automaticResize + +function PANEL:OnRemove() + table.RemoveByValue(self.editor.actions, self) +end + +vgui.Register('gmp_scenario_action', PANEL, 'DPanel') diff --git a/garrysmod/addons/feature-gmpanel/lua/vgui/gmp_scenario_editor.lua b/garrysmod/addons/feature-gmpanel/lua/vgui/gmp_scenario_editor.lua new file mode 100644 index 0000000..bcc0f7b --- /dev/null +++ b/garrysmod/addons/feature-gmpanel/lua/vgui/gmp_scenario_editor.lua @@ -0,0 +1,248 @@ +local PANEL = {} + +function PANEL:Init() + self:GetCanvas():DockPadding(5,10,5,5) + self.actions = {} + + local act0 = self:Add('DPanel') + act0:Dock(TOP) + act0:DockPadding(5,5,5,5) + act0:DockMargin(0,0,0,10) + function act0:PerformLayout() + self:SizeToChildren(false, true) + end + local lbl = octolib.label(act0, '0. Сценарий активирован') + lbl:SetFont('f4.normal') + lbl:SetTall(30) + act0:SetVisible(false) + self.act0 = act0 + + local dnd = self:Add('DPanel') + dnd:Dock(TOP) + dnd:SetTall(60) + dnd:SetMouseInputEnabled(true) + dnd:SetCursor('hand') + dnd:SetVisible(false) + self.dnd = dnd + + local function openNewActionSelector() + local o = octolib.overlay(self:GetParent(), 'DPanel') + o:SetSize(175, 0) + local lst = o:Add('DListView') + lst:Dock(FILL) + lst:SetHideHeaders(true) + lst:AddColumn(''):SetFixedWidth(32) + lst:AddColumn(L.title) + lst:SetDataHeight(32) + lst:SetMultiSelect(false) + + for k,v in pairs(gmpanel.actions.available) do + local icon = vgui.Create('DImage') + icon:SetImage(v.icon) + lst:AddLine(icon, v.name).id = k + o:SetTall(math.min(400, o:GetTall() + 32)) + end + o:SetTall(math.min(400, o:GetTall() + 16)) + + lst.OnRowSelected = function(_, _, row) + if IsValid(self) then + self:AddAction({id = row.id}) + end + o:Remove() + end + end + + dnd.OnMouseReleased = function(_, mcode) + if mcode == MOUSE_LEFT then openNewActionSelector() end + end + + local lbl = octolib.label(dnd, 'Перетащи сюда действие') + lbl:SetFont('f4.normal') + lbl:SetTall(20) + lbl:SetTextColor(Color(100, 100, 100)) + lbl:SetContentAlignment(5) + lbl:DockMargin(0,5,0,0) + lbl:SetMouseInputEnabled(true) + lbl:SetCursor('hand') + lbl.DoClick = openNewActionSelector + + lbl = octolib.label(dnd, 'Или нажми, чтобы создать новое') + lbl:SetFont('f4.normal') + lbl:SetTall(20) + lbl:SetTextColor(Color(100, 100, 100)) + lbl:SetContentAlignment(5) + lbl:DockMargin(0,5,0,0) + lbl:SetMouseInputEnabled(true) + lbl:SetCursor('hand') + lbl.DoClick = openNewActionSelector + + dnd:Receiver('gmpanel.actions.added', function(_, pnl, dropped) + if not dropped then return end + pnl = istable(pnl) and pnl[1] + if IsValid(pnl) then self:AddAction(gmpanel.actions.added[pnl.id]) end + end) + + local btmPan = self:Add('DPanel') + btmPan:Dock(TOP) + btmPan:SetTall(48) + btmPan:SetPaintBackground(false) + btmPan:SetVisible(false) + self.btmPan = btmPan + + local nameCont = btmPan:Add('DPanel') + nameCont:Dock(LEFT) + nameCont:SetWide(150) + nameCont:SetPaintBackground(false) + + octolib.label(nameCont, 'Кнопка:') + + local wrapper = nameCont:Add('DPanel') + wrapper:Dock(FILL) + wrapper:DockMargin(0, 0, 0, 5) + + local icon = wrapper:Add('DImageButton') + icon:Dock(LEFT) + icon:DockMargin(2, 4, 2, 4) + icon:SetSize(16, 16) + function icon:DoClick() + octolib.icons.picker(function(val) + self:SetImage(val) + end, 'materials/' .. self:GetImage(), 'materials/icon16/') + end + self.icon = icon + + local name = wrapper:Add('DTextEntry') + name:Dock(FILL) + name:SetPaintBackground(false) + name:SetTextColor(color_white) + self.name = name + + local b = btmPan:Add('DPanel') + b:Dock(RIGHT) + b:DockMargin(0,0,0,5) + b:SetPaintBackground(false) + b:SetWide(100) + b = b:Add('DButton') + b:Dock(BOTTOM) + b:SetTall(30) + b:SetText('Сохранить') + b:SetIcon('icon16/folder.png') + b.DoClick = function() + + if string.Trim(name:GetValue()) == '' then + octolib.notify.show('Пожалуйста, добавь название сценария. Тебе так будет проще') + return + end + + local actions = {} + + for _,v in ipairs(self.actions) do + if not (v.panel and v.id and gmpanel.actions.available[v.id]) then continue end + + local act = gmpanel.actions.available[v.id] + local resp + if isfunction(act.getData) then + resp = act.getData(v.panel) + else resp = {} end + + if resp == nil then + return octolib.notify.show('warning', 'Кажется, не все поля заполнены верно') + end + + resp = istable(resp) and resp or {value = resp} + resp.id = v.id + resp._delay = v.time:GetValue() + actions[#actions + 1] = resp + + end + local data = { + _name = name:GetValue(), + _icon = icon:GetImage(), + actions = actions, + uid = self.editUid, + } + + if self.editUid then + data.uid = self.editUid + gmpanel.scenarios.edit(self.editUid, data) + else gmpanel.scenarios.add(data) end + + end + +end + +function PANEL:AddAction(data) + if not data.id then return end + local action = gmpanel.actions.available[data.id] + if not action or not isfunction(action.openSettings) then return end + + data = table.Copy(data) + data._name, data._icon, data.uid = nil + + self.dnd:SetParent() + self.btmPan:SetParent() + local act = self:Add('gmp_scenario_action') + act.editor = self + act:Dock(TOP) + act:DockMargin(0,0,0,10) + act:SetAction(action, data) + act:UpdateNumber(#self.actions + 1) + self.dnd:SetParent(self) + self.btmPan:SetParent(self) + + local lastAct = self.actions[#self.actions] + if IsValid(lastAct) then lastAct.btnDown:SetVisible(true) end + act.btnDown:SetVisible(false) + + self.actions[#self.actions + 1] = act +end + +function PANEL:OpenEditor(data) + for _, v in ipairs(self.actions) do + v:SetParent() + v:Remove() + end + self.actions = {} + self.editUid = data.uid + + self.dnd:SetParent() + self.btmPan:SetParent() + self.dnd:SetParent(self) + self.btmPan:SetParent(self) + + self.act0:SetVisible(true) + for _,v in ipairs(data.actions or {}) do + self:AddAction(v) + end + + self.dnd:SetVisible(true) + self.btmPan:SetVisible(true) + self.icon:SetIcon(data._icon or 'icon16/page_white_text.png') + self.name:SetValue(data._name or '') + +end + +function PANEL:UpdateNumbers() + for i, v in ipairs(self.actions) do + v:UpdateNumber(i) + end + if self.actions[1] then + self.actions[#self.actions].btnDown:SetVisible(false) + end +end + +function PANEL:UpdateOrder() + local canvas = self:GetCanvas() + local dnd, btmPan = self.dnd, self.btmPan + dnd:SetParent() + btmPan:SetParent() + for _, v in ipairs(self.actions) do + v:SetParent() + v:SetParent(canvas) + end + dnd:SetParent(self) + btmPan:SetParent(self) + self:UpdateNumbers() +end + +vgui.Register('gmp_scenario_editor', PANEL, 'DScrollPanel') diff --git a/garrysmod/addons/feature-govorilka/lua/autorun/client/cl_govorilka.lua b/garrysmod/addons/feature-govorilka/lua/autorun/client/cl_govorilka.lua new file mode 100644 index 0000000..d128afa --- /dev/null +++ b/garrysmod/addons/feature-govorilka/lua/autorun/client/cl_govorilka.lua @@ -0,0 +1,66 @@ +CreateClientConVar('cl_govorilka_voice', 'zahar', true, true) +local useProxy = false +local link = { + [false] = 'http://tts.voicetech.yandex.net/tts?speaker=%s&text=%s', + [true] = 'https://octothorp.team/api/tts/y/%s/%s', +} + +local function init() + link[true] = CFG.octoservicesURL .. '/tts/y/%s/%s' + + local encode = link[false]:format('zahar', octolib.string.urlEncode('-')) + sound.PlayURL(encode, 'noplay 3d', function(station) + if IsValid(station) then + station:SetPos(LocalPlayer():GetPos()) + station:SetVolume(2) + station:Play() + useProxy = false + else + useProxy = true + end + end) +end +if octoservices then + init() +else + hook.Add('octoservices.init', 'govorilka', init) +end + +local meta = FindMetaTable 'Entity' +function meta:DoVoice(text, voice) + + if not IsValid(self) then return end + + local encode = (link[useProxy]):format(voice or self:GetVoice(), octolib.string.urlEncode(text)) + local flag = 'noplay 3D' + sound.PlayURL(encode, flag, function(station) + if IsValid(station) then + if not IsValid(self) then return end + + station:SetPos(self:GetPos()) + station:SetVolume(2) + station:Play() + + local timerName = 'govorilka_' .. self:EntIndex() + timer.Create(timerName, 0.2, 0, function() + if IsValid(station) and IsValid(self) then + station:SetPos(self:GetPos()) + else + timer.Remove(timerName) + end + end) + timer.Create(timerName .. '_remove', 30, 1, function() + timer.Remove(timerName) + end) + end + end) + +end + +netstream.Hook('govorilka.play', function(ent, text, voice) + + if IsValid(ent) then + ent:DoVoice(text, voice) + end + +end) diff --git a/garrysmod/addons/feature-govorilka/lua/autorun/server/sv_govorilka.lua b/garrysmod/addons/feature-govorilka/lua/autorun/server/sv_govorilka.lua new file mode 100644 index 0000000..5cbdced --- /dev/null +++ b/garrysmod/addons/feature-govorilka/lua/autorun/server/sv_govorilka.lua @@ -0,0 +1,40 @@ +hook.Add('PlayerInitialSpawn', 'govorilka.initialSpawn', function(ply) + timer.Simple(3, function() + if not IsValid(ply) then return end + ply:SetVoice(ply:GetInfo('cl_govorilka_voice')) + ply:SetNetVar('govorilka_mute', ply:GetDBVar('govorilka_mute')) + end) +end) + +-- hook.Add('octolib.db.init', 'govorilka.init', function() +-- octolib.db:RunQuery([[ +-- CREATE TABLE IF NOT EXISTS `govorilka_stats` ( +-- `id` INT NOT NULL AUTO_INCREMENT, +-- `performDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(), +-- `val` INT NOT NULL DEFAULT 0, +-- `premium` TINYINT(1) DEFAULT 0, +-- PRIMARY KEY (`id`) +-- ); +-- ]]) +-- end) + +local plyMeta = FindMetaTable 'Player' + +function plyMeta:SetVoice(voice) + if isstring(voice) then + self:SetNetVar('govorilka_voice', voice) + end +end + +netstream.Hook('govorilka.changeVoice', plyMeta.SetVoice) + +local entMeta = FindMetaTable 'Entity' +function entMeta:DoVoice(text, voice, recipients) + + netstream.Start(recipients, 'govorilka.play', self, text, voice) + + -- local mul = recipients and isentity(recipients) and 1 or istable(recipients) and #recipients or player.GetAll() + -- local premium = self:IsPlayer() and self:GetNetVar('os_dobro') and 1 or 0 + -- octolib.db:PrepareQuery([[INSERT INTO govorilka_stats (val, premium) VALUES (?, ?)]], {utf8.len(text) * mul, premium}) + +end diff --git a/garrysmod/addons/feature-govorilka/lua/autorun/sh_govorilka.lua b/garrysmod/addons/feature-govorilka/lua/autorun/sh_govorilka.lua new file mode 100644 index 0000000..2f27541 --- /dev/null +++ b/garrysmod/addons/feature-govorilka/lua/autorun/sh_govorilka.lua @@ -0,0 +1,29 @@ +govorilka = govorilka or {} +govorilka.voices = { + { ru_name = L.zahar, en_name = 'zahar' }, + { ru_name = L.ermil, en_name = 'ermil' }, + { ru_name = L.oksana, en_name = 'oksana' }, + { ru_name = L.alyss, en_name = 'alyss' }, + { ru_name = L.omazh, en_name = 'omazh' }, + { ru_name = L.jane, en_name = 'jane' }, +} + +local plyMeta = FindMetaTable 'Entity' +function plyMeta:GetVoiceName() + + local voice = self:GetVoice() + for _, v in ipairs(govorilka.voices) do + if voice == v.en_name then + return v.ru_name + end + end + +end + +function plyMeta:GetVoice() + return self:GetNetVar('govorilka_voice', 'zahar') +end + +function plyMeta:IsGovorilkaMuted() + return self:GetNetVar('govorilka_mute', false) +end diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extra_functions.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extra_functions.lua new file mode 100644 index 0000000..ccac51e --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extra_functions.lua @@ -0,0 +1,782 @@ +sound.Add({ + name = 'REV_BEEP', + channel = CHAN_STATIC, + volume = 1.0, + level = 80, + sound = 'octoteam/vehicles/shared/REVERSE_WARNING.wav' +}) + +sound.Add({ + name = 'REV_BEEP_MRTASTY', + channel = CHAN_STATIC, + volume = 1.0, + level = 80, + sound = 'octoteam/vehicles/mrtasty_reversebeep.wav' +}) + +sound.Add({ + name = 'BRK', + channel = CHAN_STATIC, + volume = 1.0, + level = 60, + sound = 'octoteam/vehicles/shared/brake_disc.wav' +}) + +sound.Add({ + name = 'BRK_TRK', + channel = CHAN_STATIC, + volume = 1.0, + level = 60, + sound = 'octoteam/vehicles/shared/rig_brake_disc.wav' +}) + +sound.Add({ + name = 'BRK_TRK_STOP', + channel = CHAN_STATIC, + volume = 1.0, + level = 70, + sound = 'octoteam/vehicles/shared/BRAKE_RELEASE.wav' +}) + +sound.Add({ + name = 'COOLING_FAN', + channel = CHAN_STATIC, + volume = 1.0, + level = 50, + sound = 'octoteam/vehicles/shared/cooling_start.wav' +}) + +sound.Add({ + name = 'COOLING_FAN_END', + channel = CHAN_STATIC, + volume = 1.0, + level = 50, + sound = 'octoteam/vehicles/shared/cooling_end.wav' +}) + +sound.Add({ + name = 'GTA4_BULLHORN', + channel = CHAN_STATIC, + volume = 1.0, + level = 90, + sound = 'octoteam/vehicles/horns/police_horn.wav' +}) + +sound.Add({ + name = 'GTA4_SIREN_WAIL', + channel = CHAN_STATIC, + volume = 1.0, + level = 90, + sound = 'octoteam/vehicles/horns/police_wail.wav' +}) + +sound.Add({ + name = 'GTA4_SIREN_YELP', + channel = CHAN_STATIC, + volume = 1.0, + level = 90, + sound = 'octoteam/vehicles/horns/police_yelp.wav' +}) + +sound.Add({ + name = 'GTA4_SIREN_WARNING', + channel = CHAN_STATIC, + volume = 1.0, + level = 90, + sound = 'octoteam/vehicles/horns/police_warning.wav' +}) + +--GTA 4 functions for GTA 4 cars, written by a person who knows jackshit about LUA, code is most likely v bad +--if you do want to copy bits of it feel free, but do know that it's probably not good + +REN = istable(REN) and REN or {} --define table to store all functions in + +function REN.GTA4EngStartInit(self, Type) --this bit selects the ignition type + if Type == 0 then + self.GTA4Ignition = CreateSound(self, 'octoteam/vehicles/shared/START_'..math.random(1,4)..'.wav') + self.GTA4IgnitionTail = CreateSound(self, 'octoteam/vehicles/shared/START_'..math.random(1,4)..'_TAIL.wav') + self.GTA4Ignition:SetSoundLevel(75) + self.GTA4IgnitionTail:SetSoundLevel(75) + elseif Type == 1 then + self.GTA4Ignition = CreateSound(self, 'octoteam/vehicles/shared/TRUCK_IGNITION_1.wav') + self.GTA4IgnitionTail = CreateSound(self, 'octoteam/vehicles/shared/TRUCK_IGNITION_TAIL.wav') + self.GTA4Ignition:SetSoundLevel(80) + self.GTA4IgnitionTail:SetSoundLevel(80) + elseif Type == 2 then + self.GTA4Ignition = CreateSound(self, 'octoteam/vehicles/shared/MOPED_600_CC_IGNITION.wav') + self.GTA4IgnitionTail = CreateSound(self, 'octoteam/vehicles/shared/MOPED_600_CC_IGNITION_TAIL.wav') + self.GTA4Ignition:SetSoundLevel(60) + self.GTA4IgnitionTail:SetSoundLevel(60) + end + + self.Ignition = false +end + +local function GTA4IgnitionSwitch(self) --this bit actually turns the engine on after playing the ignition sound + self.GTA4Ignition:Play() + self.GTA4IgnitionTail:Stop() + if self.Ignition then return end + self.Ignition = true + timer.Create('GTA4_IGNITION_' .. self:EntIndex(), (math.random(3,7)/10), 1, function() + if self.GTA4Ignition:IsPlaying() then + self.GTA4Ignition:Stop() + end + if !self.GTA4IgnitionTail:IsPlaying() then + self.GTA4IgnitionTail:Play() + end + self.Ignition = false + + timer.Remove('GTA4_IGNITION_' .. self:EntIndex()) + + if !self:CanStart() then return end + + self:SetActive(true) + self.EngineRPM = self:GetEngineData().LimitRPM + self:SetEngineActive(true) + end) +end + +local function GTA4IgnitionBeater(self) --failed iginiton for the beaters + self.GTA4Ignition:Play() + self.GTA4IgnitionTail:Stop() + if self.Ignition then return end + + local Beater = math.random(0,1) + + self.Ignition = true + timer.Create('GTA4_IGNITION_' .. self:EntIndex(), (math.random(3,15)/10), 1, function() + if self.GTA4Ignition:IsPlaying() then + self.GTA4Ignition:Stop() + end + if !self.GTA4IgnitionTail:IsPlaying() then + self.GTA4IgnitionTail:Play() + end + + self.Ignition = false + + timer.Remove('GTA4_IGNITION_' .. self:EntIndex()) + + timer.Simple(0.5, function() + if IsValid(self) and IsValid(self:GetDriver()) then + if Beater == 1 and !(self.BeaterCounter > 1) then + GTA4IgnitionBeater(self) + self.BeaterCounter = self.BeaterCounter + 1 + else + GTA4IgnitionSwitch(self) + self.BeaterCounter = 0 + end + end + end) + end) +end + +function REN.GTA4EngStart(self) --this bit calls for the engine turning on + + self.StartEngine = function(self, bIgnoreSettings) + if not self:EngineActive() then + if not bIgnoreSettings then + self.CurrentGear = 2 + end + + if self.BeaterCountdown then + local Beater = math.random(0,1) + if Beater == 1 then + GTA4IgnitionBeater(self) + end + end + + if not self.IsInWater then + GTA4IgnitionSwitch(self) + else + if self:GetDoNotStall() then + GTA4IgnitionSwitch(self) + end + end + end + end +end + +function REN.GTA4EngStop(self, TypeShut) --engnie shutdown + + self.StopEngine = function(self) + if self:EngineActive() then + if TypeShut == 1 then + self:EmitSound('octoteam/vehicles/shared/SHUT_DOWN_1.wav') + end + + self.EngineRPM = 0 + self:SetEngineActive(false) + + self:SetFlyWheelRPM(0) + self:SetIsCruiseModeOn(false) + end + end +end + +function REN.GTA4Bullhorn(self) --bullhorn script for Службы vehicles + -- if not IsValid(self) then return end + -- local ply = self:GetDriver() + -- if not IsValid(ply) or not ply:IsPlayer() then return end --if there is no driver, end the script, otherwise it would error + + -- if ply:KeyDown(2048) and IsValid(ply) then + -- self.Bullhorn = CreateSound(self, 'GTA4_BULLHORN') + -- self.Bullhorn:Play() + -- else + -- if self.Bullhorn then + -- self.Bullhorn:Stop() + -- end + -- end +end + +function REN.GTA4AlarmInit(self) + self.ALRM = CreateSound(self, 'octoteam/vehicles/horns/CAR_ALARM_'..math.random(1,3)..'.wav') --picks an alarm sound + self.ALRM:SetSoundLevel(90) + + self.CurHealthGot = false + self.ALRMon = false + self.ALRMArmed = false + self.CurHealthLocked = self:GetCurHealth() + + -- self.ALRMRnd = math.random(0,2) + self.ALRMRnd = 0 +end + +local function ActivateHorn(self, bool) + if not self.horn then + self.horn = CreateSound(self, self.snd_horn or 'simulated_vehicles/horn_1.wav') + self.horn:PlayEx(0,100) + end + if bool then + self.horn:Play() + else + self.horn:Stop() + end + -- self.HornKeyIsDown = bool + -- self:ControlHorn() +end + +local function ChirpAlarmLocked(self) + if self.ALRMRnd == 2 then + self.ALRM:Play() + else + -- ActivateHorn(self, true) + end + self:SetLightsEnabled(true) + timer.Simple(0.1, function() + if IsValid(self) then + self:SetLightsEnabled(false) + if self.ALRMRnd == 2 then + self.ALRM:Stop() + else + -- ActivateHorn(self, false) + end + end + end) +end + +local function ChirpAlarmUnLocked(self) + if self.ALRMRnd == 2 then + self.ALRM:Play() + else + -- ActivateHorn(self, true) + end + timer.Simple(0.12, function() + if IsValid(self) then + self:SetLightsEnabled(true) + if self.ALRMRnd == 2 then + self.ALRM:Stop() + else + -- ActivateHorn(self, false) + end + end + end) + timer.Simple(0.24, function() + if IsValid(self) then + if self.ALRMRnd == 2 then + self.ALRM:Play() + else + -- ActivateHorn(self, true) + end + end + end) + timer.Simple(0.36, function() + if IsValid(self) then + self:SetLightsEnabled(false) + if self.ALRMRnd == 2 then + self.ALRM:Stop() + else + -- ActivateHorn(self, false) + end + end + end) +end + +local function TurnOffAlarm(self) + if !self.ALRMArmed then return end + + if self.ALRMon then + if self.ALRM then + self.ALRM:Stop() + end + end + if timer.Exists('GTA4_ALARM_' .. self:EntIndex()) then + timer.Remove('GTA4_ALARM_' .. self:EntIndex()) + end + self:SetLightsEnabled(false) + self.ALRMon = false + self.ALRMed = false + self.CurHealthGot = false +end + +function REN.GTA4Alarm(self, ALRMVal) -- alarm function + if self:EngineActive() then return end + if self.ALRMRnd == 0 then return end + + if self:GetIsLocked() then + if !self.CurHealthGot then self.CurHealthLocked = self:GetCurHealth() end + self.CurHealthGot = true + + if !self.ALRMArmed then ChirpAlarmLocked(self) end + self.ALRMArmed = true + + if self:GetCurHealth() < self.CurHealthLocked then + self.ALRMon = true + if self.ALRMRnd == 2 then + self.ALRM:Play() + end + + if self.ALRMed then return end + + if !timer.Exists('GTA4_ALARM_' .. self:EntIndex()) then + timer.Create('GTA4_ALARM_' .. self:EntIndex(), 1, 1, function() + self:SetLightsEnabled(true) + if self.ALRMRnd == 1 then + ActivateHorn(self, true) + end + + timer.Simple(0.5, function() + if IsValid(self) then + self:SetLightsEnabled(false) + if self.ALRMRnd == 1 then + ActivateHorn(self, false) + end + end + end) + end) + end + + if !timer.Exists('GTA4_ALARM_KILL_' .. self:EntIndex()) and self.ALRMon then + timer.Create('GTA4_ALARM_KILL_' .. self:EntIndex(), math.random(15,25), 1, function() + if IsValid(self) then + TurnOffAlarm(self) + self.ALRMed = true + end + end) + end + end + end + + if !self:GetIsLocked() then + if self.ALRMArmed then ChirpAlarmUnLocked(self) end + timer.Remove('GTA4_ALARM_KILL_' .. self:EntIndex()) + TurnOffAlarm(self) + self.ALRMed = false + self.ALRMArmed = false + end +end + +function REN.GTA4GearSoundsHelp(self) --actual gear change noise + self.GTA4GearSwitch = CreateSound(self, 'octoteam/vehicles/shared/EXTERNAL_GEAR_CHANGE_'..math.random(1,5)..'.wav') + self.GTA4GearSwitch:SetSoundLevel(70) + self.GTA4GearSwitch:PlayEx(0.7,100) + + self.GearChanged = self:GetGear() --set the checking value to match the current gear +end + +function REN.GTA4GearSounds(self) --gear change calling script + if not (self:GetGear() == 2) then + if self.GearChanged == self:GetGear() then return end --check if the checking value matches the current gear, if not then play the noise + REN.GTA4GearSoundsHelp(self) + end +end + +--[[function REN.GTA4SkidSounds(self, wheelsnds4) --replace skid sounds with GTA4 skid sounds (even though the tarmac skid is probably the exact same lmao) + + if wheelsnds4 == nil then + wheelsnds4 = {} + wheelsnds4.snd_skid = 'octoteam/vehicles/shared/tarmac_skid.wav' + wheelsnds4.snd_skid_dirt = 'octoteam/vehicles/shared/gravel_skid.wav' + wheelsnds4.snd_skid_grass = 'octoteam/vehicles/shared/grass_skid.wav' + end + + for i = 1, table.Count(self.Wheels) do + local Wheel = self.Wheels[i] + Wheel.snd_skid = wheelsnds4.snd_skid + Wheel.snd_skid_dirt = wheelsnds4.snd_skid_dirt + Wheel.snd_skid_grass = wheelsnds4.snd_skid_grass + end + +end]] --nevermind, all the sounds are the exact same lol + +function REN.GTA4ReverseBeep(self) + self.REVBP = CreateSound(self, 'REV_BEEP') + + if (self:GetGear() == 1) then + self.REVBP:Play() + else + self.REVBP:Stop() + end +end + +function REN.GTA4ReverseBeepMrTasty(self) -- insane level of detail from GTA4 + self.REVBP = CreateSound(self, 'REV_BEEP_MRTASTY') + + if (self:GetGear() == 1) then + self.REVBP:Play() + else + self.REVBP:Stop() + end +end + +function REN.GTA4Braking(self) --brake disc sound + if not self.BrakeSqueal then return end + + if self:GetIsBraking() and self.ForwardSpeed > 20 and self.ForwardSpeed < 400 then --check if the forward speed is correct and if the car is braking + if not self.BRK or not self.BRK:IsPlaying() then + self.BRK = CreateSound(self, 'BRK') + self.BRK:PlayEx(0.75,100) + end + else + if self.BRK and self.BRK:IsPlaying() then + self.BRK:Stop() + end + end +end + +function REN.GTA4TruckBraking(self) --brake disc sound but truck + if self:GetIsBraking() and self.ForwardSpeed > 20 and self.ForwardSpeed < 500 then + if not self.BRK or not self.BRK:IsPlaying() then + self.BRK = CreateSound(self, 'BRK_TRK') + self.BRK:PlayEx(0.75,100) + end + else + if self.BRK and self.BRK:IsPlaying() then + self.BRK:Stop() + + if not self.BRKSTP or not self.BRKSTP:IsPlaying() then + self.BRKSTP = CreateSound(self, 'BRK_TRK_STOP') + self.BRKSQK = CreateSound(self, 'octoteam/vehicles/shared/BRAKE_SQUEAK_'..math.random(1,3)..'.wav') + self.BRKSTP:Play() + self.BRKSQK:Play() + end + end + end + + --this works but I wrote it a while ago so I don't remember how + -- c: +end + +function REN.GTA4CooldownTick(self) --cooldown tick noise + self.GTA4Cooldown = CreateSound(self, 'octoteam/vehicles/shared/HEAT_TICK_'..math.random(1,6)..'.wav') + self.GTA4Cooldown:SetSoundLevel(45) + self.GTA4Cooldown:PlayEx(0.5, 100) +end + +--[[function REN.GTA4Cooldown(self) --script that handles cooldown ticks and engine heat (old version, it works but it's badly designed) + + local THRTL = (self:GetEngineRPM() / 1000) / 1.5 + if !self.EngHeat then return end + + if (self:EngineActive() and not (self.EngHeat > 4999)) then + if self.EngHeat > -3 then + self.EngHeat = self.EngHeat + (THRTL - 1) + if self.EngHeat > 4999 then + self.EngHeat = self.EngHeat - 5 + end + else + self.EngHeat = self.EngHeat + 1 + end + end + + if (self.EngHeat > 4000) and self.CoolingAllowed then + self.CoolingAllowed = false + self.Cooling = true + if !self.GTA4Cooling:IsPlaying() then + self.GTA4Cooling:Play() + self.GTA4CoolingEnd:Stop() + end + timer.Create('GTA4_COOLING_END_' .. self:EntIndex(), 15, 1, function() + if self.GTA4Cooling:IsPlaying() then + self.GTA4Cooling:Stop() + if !self.GTA4CoolingEnd:IsPlaying() then + self.GTA4CoolingEnd:Play() + end + end + self.Cooling = false + timer.Remove('GTA4_COOLING_END_' .. self:EntIndex()) + end) + timer.Create('GTA4_COOLING_TIMER_' .. self:EntIndex(), 25, 1, function() + self.CoolingAllowed = true + timer.Remove('GTA4_COOLING_TIMER_' .. self:EntIndex()) + end) + end + + if self.Cooling then + self.EngHeat = self.EngHeat - 2 + end + + if not (self:EngineActive() or self.EngHeat < -1) then + self.EngHeat = self.EngHeat - 0.5 + end + + local LOW = (5010 - self.EngHeat) / 1000 + local HIGH = LOW + 0.3 + + if not timer.Exists('GTA4_COOLDOWN_' .. self:EntIndex()) then + if (!self:EngineActive() and self.EngHeat > 2000) then + timer.Create('GTA4_COOLDOWN_' .. self:EntIndex(), math.random(LOW,HIGH), 1, function() + REN.GTA4CooldownTick(self) + timer.Remove('GTA4_COOLDOWN_' .. self:EntIndex()) + end) + end + end +end]] + +function REN.GTA4Cooldown(self) --new engine heat simulation, cooldown ticks and cooling + --safety checks + if !self.EngHeat then return end + if !self.Cooling then return end + + --init (still runs on every frame lol) + local THRTL = math.Round(self:GetEngineRPM() / 1000 , 2) + local LIMIT = self:GetEngineData().LimitRPM + local IDLE = self:GetEngineData().IdleRPM + local LOW2 = ((LIMIT*1.75 - self.EngHeat) / 1000) - LIMIT / 1000 + local LOW = math.Clamp(LOW2,0.2,4) + local HIGH = LOW + 0.025 + + --handle cooldown ticks after engine is off + if (!timer.Exists('GTA4_COOLDOWN_' .. self:EntIndex()) and LOW < 2.75) then + if !self:EngineActive() then + timer.Create('GTA4_COOLDOWN_' .. self:EntIndex(), math.random(HIGH, HIGH + 0.01), 2, function() + if !self:EngineActive() then REN.GTA4CooldownTick(self) end + end) + end + end + if (!timer.Exists('GTA4_COOLDOWN2_' .. self:EntIndex()) and LOW < 2.75) then + if !self:EngineActive() then + timer.Create('GTA4_COOLDOWN2_' .. self:EntIndex(), LOW + 0.2, 1, function() + if !self:EngineActive() then + self.GTA4Cooldown2 = CreateSound(self, 'octoteam/vehicles/shared/HEAT_TICK_4.wav') + self.GTA4Cooldown2:SetSoundLevel(45) + self.GTA4Cooldown2:PlayEx(0.4,100) + end + end) + end + end + + --handle engine cooling + if self.EngHeat > LIMIT + LIMIT/2 then + self.Cooling = 1 + if !self.GTA4Cooling:IsPlaying() then + self.GTA4Cooling:Play() + self.GTA4CoolingEnd:Stop() + end + elseif self.EngHeat < LIMIT then + if self.GTA4Cooling:IsPlaying() then + self.GTA4Cooling:Stop() + if !self.GTA4CoolingEnd:IsPlaying() then + self.GTA4CoolingEnd:Play() + end + end + self.Cooling = 0 + end + + --handle engine heat simulation + COOL = -2 * self.Cooling + THRTL = math.Round(THRTL / (self.EngHeat / 3000) , 3) + THRTL = math.Clamp(THRTL, 0, 10) + self.EngHeat = math.Clamp(self.EngHeat + (THRTL - self.EngHeat / (LIMIT*1.25) + COOL),IDLE/100,LIMIT*2) + + --[[debug + print('THRTL: '..THRTL) + print('EngHeat: '..self.EngHeat) + print(LOW..', '..HIGH) + print('Cooling Treshold: '..(LIMIT + LIMIT/2)) + print('---')]] +end + +function REN.GTA4Handbrake(self, HNDBRK) --handbrake noise + local ply = self:GetDriver() + + if !IsValid(self) or !IsValid(self.DriverSeat) or !IsValid(self.DriverSeat:GetDriver()) then return end --if there is no driver, end the script, otherwise it would error + if !ply:IsPlayer() then return end --checks if the driver is an actual player, not an AI + + local filter = RecipientFilter() + if IsValid(self:GetDriver()) then + filter:AddPlayer(ply) + end + + if (HNDBRK == 0) then + self.HandbrakeSND = CreateSound(self, 'octoteam/vehicles/shared/STANDARD_HANDBRAKE.wav', filter) + elseif (HNDBRK == 1) then + self.HandbrakeSND = CreateSound(self, 'octoteam/vehicles/shared/SPORTS_HANDBRAKE.wav', filter) + elseif (HNDBRK == 2) then + self.HandbrakeSND = CreateSound(self, 'octoteam/vehicles/shared/TRUCK_HANDBRAKE.wav', filter) + elseif (HNDBRK == 3) then + self.HandbrakeSND = CreateSound(self, 'common/null.wav', filter) + end + self.HandbrakeSND:SetSoundLevel(120) + + if self:GetHandBrakeEnabled() then + if self.CanHandbrake then + local vehicle = ply:GetVehicle() + if vehicle:GetThirdPersonMode()~= nil then + if !vehicle:GetThirdPersonMode() then + self.HandbrakeSND:Play() + end + end + end + self.CanHandbrake = false + end + + if !self:GetHandBrakeEnabled() then + self.CanHandbrake = true + end +end + +function REN.GTA4BeaterInit(self) + DMGNumber = math.random(1,5) + self.BeaterCountdown = 0 + self.BeaterCounter = 0 + + self.GTA4Beater1 = CreateSound(self, 'octoteam/vehicles/old_car_noises/EXTRA_DAMAGE_'..DMGNumber..'_A.wav') --picks 2 random but matching engine damage noises + self.GTA4Beater2 = CreateSound(self, 'octoteam/vehicles/old_car_noises/EXTRA_DAMAGE_'..DMGNumber..'_B.wav') --these stay the same for the car, but can be different for different cars + self.GTA4Beater1:SetSoundLevel(70) + self.GTA4Beater2:SetSoundLevel(70) +end + +function REN.GTA4Beater(self) + PITCH = (self:GetEngineRPM()/50) + 80 + if !self.BeaterCountdown then return end + + if self:GetCurHealth() > (self.GetMaxHealth(self) / 2) + 100 then --makes beater cars un-repairable, like in IV + self:SetCurHealth((self.GetMaxHealth(self) / 2) + 100) + end + + if self:EngineActive() then + self.BeaterCountdown = 0 + if self.ForwardSpeed < 500 then + self.GTA4Beater1:PlayEx(0.3,PITCH) + if self.GTA4Beater2 then + self.GTA4Beater2:Stop() + end + elseif self.ForwardSpeed > 500 then + if self.GTA4Beater1 then + self.GTA4Beater1:Stop() + end + self.GTA4Beater2:PlayEx(0.3,PITCH) + end + if not timer.Exists('GTA4_BEATER_' .. self:EntIndex()) then + timer.Create('GTA4_BEATER_' .. self:EntIndex(), math.random(1,10), 1, function() + local Beater = math.random(0,1) + + if self:EngineActive() then + if Beater == 1 and self.ForwardSpeed > 100 then + net.Start('simfphys_backfire') + net.WriteEntity(self) --make the car randomly backfire while driving + net.Broadcast() + else + sound.Play('octoteam/vehicles/old_car_noises/BREAKDOWN_'..math.random(1,5)..'.wav', self:GetPos()) --plays random breakdown noises. I think gta4 does this, maybe not though + end + end + timer.Remove('GTA4_BEATER_' .. self:EntIndex()) + end) + end + elseif !self:EngineActive() then + if not (timer.Exists('GTA4_BEATERS_' .. self:EntIndex()) and self.BeaterCountdown < 3) then + timer.Create('GTA4_BEATERS_' .. self:EntIndex(), 0.2, 1, function() + sound.Play('octoteam/vehicles/old_car_noises/BREAKDOWN_'..math.random(1,5)..'.wav', self:GetPos()) --breakdown noises when the engine stops + self.BeaterCountdown = self.BeaterCountdown + 1 + end) + end + if self.GTA4Beater1 then + self.GTA4Beater1:Stop() + end + if self.GTA4Beater2 then + self.GTA4Beater2:Stop() + end + end +end + +function REN.GTA4SimfphysInit(self, Type, TypeShut) + REN.GTA4EngStartInit(self, Type) + REN.GTA4EngStart(self) + REN.GTA4EngStop(self, TypeShut) + REN.GTA4AlarmInit(self) + + self.GTA4Cooling = CreateSound(self, 'COOLING_FAN') + self.GTA4CoolingEnd = CreateSound(self, 'COOLING_FAN_END') + self.Cooling = 0 + self.BrakingAllowed = true + + self.EngHeat = 1 + + self.GearChanged = 0 +end + +function REN.GTA4Delete(self) --fixed, now no longer errors if the car is removed before it initializes + timer.Remove('GTA4_COOLDOWN_' .. self:EntIndex()) + timer.Remove('GTA4_COOLDOWN2_' .. self:EntIndex()) + timer.Remove('GTA4_BEATER_' .. self:EntIndex()) + timer.Remove('GTA4_BEATERS_' .. self:EntIndex()) + timer.Remove('GTA4_COOLING_END_' .. self:EntIndex()) + timer.Remove('GTA4_IGNITION_' .. self:EntIndex()) + timer.Remove('GTA4_ALARM_' .. self:EntIndex()) + if self.GTA4Cooling then + self.GTA4Cooling:Stop() + end + if self.GTA4CoolingEnd then + self.GTA4CoolingEnd:Stop() + end + if self.GTA4Ignition then + self.GTA4Ignition:Stop() + end + if self.GTA4IgnitionTail then + self.GTA4IgnitionTail:Stop() + end + if self.GTA4Beater1 then + self.GTA4Beater1:Stop() + end + if self.GTA4Beater2 then + self.GTA4Beater2:Stop() + end + if self.BRK then + self.BRK:Stop() + end + if self.Bullhorn then + self.Bullhorn:Stop() + end + if self.ALRM then + self.ALRM:Stop() + end +end + +function REN.GTA4SimfphysOnTick(self, RevBeep, Braking, HndBrk) + REN.GTA4Cooldown(self) + REN.GTA4GearSounds(self) + REN.GTA4Handbrake(self, HndBrk) + REN.GTA4Alarm(self) + + if (RevBeep == 1) then + REN.GTA4ReverseBeep(self) + elseif (RevBeep == 2) then + REN.GTA4ReverseBeepMrTasty(self) + end + + if (Braking == 0) then + REN.GTA4Braking(self) + else + REN.GTA4TruckBraking(self) + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_biff.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_biff.lua new file mode 100644 index 0000000..d6a47f1 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_biff.lua @@ -0,0 +1,51 @@ +function REN.GTA4Biff(self, NMB) + local skin = self:GetSkin() + local prxyClr + if (self.GetProxyColors) then + prxyClr = self:GetProxyColors() + end + + if NMB == 0 then return end + + if NMB == 1 then + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/biff_extra2.mdl' ) + elseif NMB == 2 then + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/biff_extra3.mdl' ) + if (self.GetProxyColors) then self.extramdl:SetProxyColors(prxyClr) end + end + + self.extramdl:SetSkin(skin ) + self.extramdl:SetPos(self:GetPos() ) + self.extramdl:SetAngles(self:GetAngles() ) + self.extramdl.DoNotDuplicate = true + self.extramdl:Spawn() + + self.extraweld = constraint.Weld(self.extramdl, self, 0, 0, 0, 1, 1) + + self:CallOnRemove('RemoveBed',function(self) + if self.destroyed then + if NMB == 1 then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/biff_extra2.mdl' ) + elseif NMB == 2 then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/biff_extra3.mdl' ) + end + + self.extragib:SetSkin(skin ) + self.extragib.DoNotDuplicate = true + self.extragib:SetPos(self:GetPos() ) + self.extragib:SetAngles(self:GetAngles() ) + + self.Gib.extragib = self.extragib + + self.extragib:Spawn() + + self.Gib:CallOnRemove('RemoveBedGib',function(self) + self.extragib:Remove() + end) + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_flatbed.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_flatbed.lua new file mode 100644 index 0000000..c588a2d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_flatbed.lua @@ -0,0 +1,52 @@ +function REN.GTA4Flatbed(self, NMB) + local skin = self:GetSkin() + local prxyClr + if (self.GetProxyColors) then + prxyClr = self:GetProxyColors() + end + + if NMB == 0 then return end + + if NMB == 1 then + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/flatbed_extra1.mdl' ) + if (self.GetProxyColors) then self.extramdl:SetProxyColors(prxyClr) end + elseif NMB == 2 then + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/flatbed_extra2.mdl' ) + if (self.GetProxyColors) then self.extramdl:SetProxyColors(prxyClr) end + end + + self.extramdl:SetSkin(skin ) + self.extramdl:SetPos(self:GetPos() ) + self.extramdl:SetAngles(self:GetAngles() ) + self.extramdl.DoNotDuplicate = true + self.extramdl:Spawn() + + self.extraweld = constraint.Weld(self.extramdl, self, 0, 0, 0, 1, 1) + + self:CallOnRemove('RemoveBed',function(self) + if self.destroyed then + if NMB == 1 then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/flatbed_extra1.mdl' ) + elseif NMB == 2 then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/flatbed_extra2.mdl' ) + end + + self.extragib:SetSkin(skin ) + self.extragib.DoNotDuplicate = true + self.extragib:SetPos(self:GetPos() ) + self.extragib:SetAngles(self:GetAngles() ) + + self.Gib.extragib = self.extragib + + self.extragib:Spawn() + + self.Gib:CallOnRemove('RemoveBedGib',function(self) + self.extragib:Remove() + end) + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_packer.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_packer.lua new file mode 100644 index 0000000..ea9dc91 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_extras_packer.lua @@ -0,0 +1,57 @@ +function REN.GTA4Packer(self, NMB) + local tankgone = 0 + local skin = self:GetSkin() + local prxyClr + if (self.GetProxyColors) then + prxyClr = self:GetProxyColors() + end + + if NMB == 0 then return end + + if NMB == 1 then + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/packer_extra1.mdl' ) + elseif NMB == 2 then + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/packer_extra3.mdl' ) + end + + self.extramdl:SetSkin(skin ) + self.extramdl:SetPos(self:GetPos() ) + self.extramdl:SetAngles(self:GetAngles() ) + self.extramdl.DoNotDuplicate = true + self.extramdl:Spawn() + + self.extraweld = constraint.Weld(self.extramdl, self, 0, 0, 0, 1, 1) + + if NMB == 1 then + self.extramdl:CallOnRemove('TankGone',function(self) tankgone = 1 end ) + end + + self:CallOnRemove('RemoveBed',function(self) + if tankgone == 1 then return end + + if self.destroyed then + if NMB == 1 then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/packer_extra1.mdl' ) + elseif NMB == 2 then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/packer_extra3.mdl' ) + end + + self.extragib:SetSkin(skin ) + self.extragib.DoNotDuplicate = true + self.extragib:SetPos(self:GetPos() ) + self.extragib:SetAngles(self:GetAngles() ) + + self.Gib.extragib = self.extragib + + self.extragib:Spawn() + + self.Gib:CallOnRemove('RemoveBedGib',function(self) + self.extragib:Remove() + end) + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_forklift_shit.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_forklift_shit.lua new file mode 100644 index 0000000..aef3e86 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_forklift_shit.lua @@ -0,0 +1,86 @@ +function REN.GTA4Forklift(self) + local skin = self:GetSkin() + local prxyClr + if (self.GetProxyColors) then + prxyClr = self:GetProxyColors() + end + + self.extramdl = ents.Create('prop_physics' ) + self.extramdl:SetModel('models/octoteam/vehicles/forklift_fork.mdl' ) + + self.extramdl:SetSkin(skin ) + self.extramdl:SetPos(self:GetPos() ) + self.extramdl:SetAngles(self:GetAngles() ) + self.extramdl.DoNotDuplicate = true + self.extramdl:Spawn() + + self.extraweld = constraint.Weld(self.extramdl, self, 0, 0, 5000, 1, 1) + + self:CallOnRemove('RemoveBed',function(self) + if self.destroyed then + self.extragib = ents.Create('gmod_sent_vehicle_fphysics_gib' ) + self.extragib:SetModel('models/octoteam/vehicles/forklift_fork.mdl' ) + + self.extragib:SetSkin(skin ) + self.extragib.DoNotDuplicate = true + self.extragib:SetPos(self:GetPos() ) + self.extragib:SetAngles(self:GetAngles() ) + + self.Gib.extragib = self.extragib + + self.extragib:Spawn() + + self.Gib:CallOnRemove('RemoveForkGib',function(self) + self.extragib:Remove() + end) + end + end) +end + +local function TankerDamage(ent, DmgPos, Damage) + for k, v in pairs(ent.NAKTankerHB ) do + if DmgPos:WithinAABox(v.OBBMin, v.OBBMax) then + ent:TakeDamage(Damage*10) + if vFireInstalled then + CreateVFire(ent, ent:LocalToWorld(DmgPos), DmgPos:GetNormalized(), 15) + end + end + end +end + +function REN.ForkliftHitbox(self ) + --Bullet Damage + self.NAKOnTakeDamage = self.OnTakeDamage + self.OnTakeDamage = function(self, dmginfo) + local Damage = dmginfo:GetDamage() + local DamagePos = self:WorldToLocal(dmginfo:GetDamagePosition()) + local Explosion = dmginfo:IsExplosionDamage() + TankerDamage(self, DamagePos, Damage) + self:NAKOnTakeDamage(dmginfo) + end + --Physics Damage + self.NAKHBPhysicsCollide = self.PhysicsCollide + self.PhysicsCollide = function(self, data, physobj) + if (not data.HitEntity:IsNPC()) and (not data.HitEntity:IsNextBot()) and + (not data.HitEntity:IsPlayer()) then + if (data.DeltaTime > 0.2) then + local spd = data.Speed + data.OurOldVelocity:Length() + data.TheirOldVelocity:Length() + local dmgmult = math.Round(spd / 20, 0) + local damagePos = self:WorldToLocal(data.HitPos) + TankerDamage(self, damagePos, dmgmult) + end + end + self:NAKHBPhysicsCollide(data, physobj) + end + + --//need to code a timer or something that slowly lets out fuel, and base how much fuel is left on the explosion size! + self.OnDestroyed = function(self) + if !self.destroyed then return end + if vFireInstalled then + CreateVFireBall(50, 100, self:GetPos()+Vector(0,0,200), Vector(0, 100, 25), nil) + CreateVFireBall(50, 100, self:GetPos()+Vector(0,0,200), Vector(0, -100,25), nil) + CreateVFireBall(50, 100, self:GetPos()+Vector(0,0,200), Vector(100, 0, 25), nil) + CreateVFireBall(50, 100, self:GetPos()+Vector(0,0,200), Vector(-100, 0,25), nil) + end + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_full_color_table.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_full_color_table.lua new file mode 100644 index 0000000..9dea810 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_full_color_table.lua @@ -0,0 +1,173 @@ +function REN.GTA4ColorTable(Col1,Col2,Col3) + +Cols = {} + +--GTA4's 'carcols.dat' file reformatted into a lua table, +--for easy editing and calling colors on all vehicles. + +--BLACK's +Cols[0] = Color(10,10,10) -- 0 black +Cols[1] = Color(37,37,39) -- 1 black poly +--GREYS/SILVERS +Cols[2] = Color(101,106,121) -- 2 concord blue poly +Cols[3] = Color(88,89,90) -- 3 pewter gray poly +Cols[4] = Color(156,161,163) -- 4 silver stone poly +Cols[5] = Color(150,145,140) -- 5 winning silver poly +Cols[6] = Color(81,84,89) -- 6 steel gray poly +Cols[7] = Color(63,62,69) -- 7 shadow silver poly +Cols[8] = Color(165,169,167) -- 8 silver stone poly +Cols[9] = Color(151,149,146) -- 9 porcelain silver poly +Cols[10] = Color(118,123,124) -- 10 gray poly +Cols[11] = Color(90,87,82) -- 11 anthracite gray poly +Cols[12] = Color(173,176,176) -- 12 astra silver poly +Cols[13] = Color(132,137,136) -- 13 ascot gray +Cols[14] = Color(148,157,159) -- 14 clear crystal blue frost poly +Cols[15] = Color(164,167,165) -- 15 silver poly +Cols[16] = Color(88,88,83) -- 16 dk.titanium poly +Cols[17] = Color(164,160,150) -- 17 titanium frost poly +Cols[18] = Color(175,177,177) -- 18 police white +Cols[19] = Color(109,108,110) -- 19 medium gray poly +Cols[20] = Color(100,104,106) -- 20 med.gray poly +Cols[21] = Color(82,86,97) -- 21 steel gray poly +Cols[22] = Color(140,146,154) -- 22 slate gray +Cols[23] = Color(91,93,94) -- 23 gun metal poly +Cols[24] = Color(189,190,198) -- 24 light blue grey +Cols[25] = Color(182,182,182) -- 25 securicor light gray +Cols[26] = Color(100,100,100) -- 26 arctic white + +--RED +Cols[27] = Color(226,6,6) -- 27 very red +Cols[28] = Color(150,8,0) -- 28 torino red pearl +Cols[29] = Color(107,0,0) -- 29 formula red +Cols[30] = Color(97,16,9) -- 30 blaze red +Cols[31] = Color(74,10,10) -- 31 graceful red mica +Cols[32] = Color(115,11,11) -- 32 garnet red poly +Cols[33] = Color(87,7,7) -- 33 desert red +Cols[34] = Color(38,3,6) -- 34 cabernet red poly +Cols[35] = Color(158,0,0) -- 35 turismo red +Cols[36] = Color(20,0,2) -- 36 desert red +Cols[37] = Color(15,4,4) -- 37 currant red solid +Cols[38] = Color(15,8,10) -- 38 brt.currant red poly +Cols[39] = Color(57,25,29) -- 39 elec.currant red poly +Cols[40] = Color(85,39,37) -- 40 med.cabernet solid +Cols[41] = Color(76,41,41) -- 41 wild strawberry poly +Cols[42] = Color(116,29,40) -- 42 med.red solid +Cols[43] = Color(109,40,55) -- 43 bright red +Cols[44] = Color(115,10,39) -- 44 bright red +Cols[45] = Color(100,13,27) -- 45 med.garnet red poly +Cols[46] = Color(98,11,28) -- 46 brilliant red poly +Cols[47] = Color(115,24,39) -- 47 brilliant red poly2 +Cols[48] = Color(171,152,143) -- 48 alabaster solid +Cols[49] = Color(32,32,44) -- 49 twilight blue poly + +--GREEN +Cols[50] = Color(68,98,79) -- 50 torch red +Cols[51] = Color(46,91,32) -- 51 green +Cols[52] = Color(30,46,50) -- 52 deep jewel green +Cols[53] = Color(48,79,69) -- 53 agate green +Cols[54] = Color(77,98,104) -- 54 petrol blue green poly +Cols[55] = Color(94,112,114) -- 55 hoods +Cols[56] = Color(25,56,38) -- 56 green +Cols[57] = Color(45,58,53) -- 57 dark green poly +Cols[58] = Color(51,95,63) -- 58 rio red +Cols[59] = Color(71,120,60) -- 59 securicor dark green +Cols[60] = Color(147,163,150) -- 60 seafoam poly +Cols[61] = Color(154,167,144) -- 61 pastel alabaster solid + +--BLUE +Cols[62] = Color(38,55,57) -- 62 midnight blue +Cols[63] = Color(76,117,183) -- 63 striking blue +Cols[64] = Color(70,89,122) -- 64 saxony blue poly +Cols[65] = Color(93,126,141) -- 65 jasper green poly +Cols[66] = Color(59,78,120) -- 66 mariner blue +Cols[67] = Color(61,74,104) -- 67 harbor blue poly +Cols[68] = Color(109,122,136) -- 68 diamond blue poly +Cols[69] = Color(22,34,72) -- 69 surf blue +Cols[70] = Color(39,47,75) -- 70 nautical blue poly +Cols[71] = Color(78,104,129) -- 71 lt.crystal blue poly +Cols[72] = Color(106,122,140) -- 72 med regatta blue poly +Cols[73] = Color(111,130,151) -- 73 spinnaker blue solid +Cols[74] = Color(14,49,109) -- 74 ultra blue poly +Cols[75] = Color(57,90,131) -- 75 bright blue poly +Cols[76] = Color(32,75,107) -- 76 nassau blue poly +Cols[77] = Color(43,62,87) -- 77 med.sapphire blue poly +Cols[78] = Color(54,65,85) -- 78 steel blue poly +Cols[79] = Color(108,132,149) -- 79 lt.sapphire blue poly +Cols[80] = Color(77,93,96) -- 80 malachite poly +Cols[81] = Color(64,108,143) -- 81 med.maui blue poly +Cols[82] = Color(19,69,115) -- 82 bright blue poly +Cols[83] = Color(16,80,130) -- 83 bright blue poly +Cols[84] = Color(56,86,148) -- 84 blue +Cols[85] = Color(0,28,50) -- 85 dk.sapphire blue poly +Cols[86] = Color(89,110,135) -- 86 lt.sapphire blue poly +Cols[87] = Color(34,52,87) -- 87 med.sapphire blue firemist +Cols[88] = Color(32,32,44) -- 88 twilight blue poly + +--YELLOW +Cols[89] = Color(245,137,15) -- 89 taxi yellow +Cols[90] = Color(145,115,71) -- 90 race yellow solid +Cols[91] = Color(142,140,70) -- 91 pastel alabaster +Cols[92] = Color(170,173,142) -- 92 oxford white solid +Cols[93] = Color(174,155,127) -- 93 flax +Cols[94] = Color(150,129,108) -- 94 med.flax +Cols[95] = Color(122,117,96) -- 95 pueblo beige +Cols[96] = Color(157,152,114) -- 96 light ivory +Cols[97] = Color(152,149,134) -- 97 smoke silver poly +Cols[98] = Color(156,141,113) -- 98 bisque frost poly + +--ORANGE + +--PURPLE +Cols[99] = Color(105,30,59) -- 99 classic red +Cols[100] = Color(114,42,63) -- 100 vermilion solid +Cols[101] = Color(124,27,68) -- 101 vermillion solid + +--BROWN +Cols[102] = Color(34,25,24) -- 102 biston brown poly +Cols[103] = Color(127,105,86) -- 103 lt.beechwood poly +Cols[104] = Color(71,53,50) -- 104 dk.beechwood poly +Cols[105] = Color(105,88,83) -- 105 dk.sable poly +Cols[106] = Color(98,68,40) -- 106 med.beechwood poly +Cols[107] = Color(125,98,86) -- 107 woodrose poly +Cols[108] = Color(170,157,132) -- 108 sandalwood frost poly +Cols[109] = Color(123,113,94) -- 109 med.sandalwood poly +Cols[110] = Color(171,146,118) -- 110 copper beige +Cols[111] = Color(99,92,90) -- 111 warm grey mica + +--WHITE +Cols[112] = Color(201,201,201) -- 112 white +Cols[113] = Color(214,218,214) -- 113 frost white +Cols[114] = Color(159,157,148) -- 114 honey beige poly +Cols[115] = Color(147,163,150) -- 115 seafoam poly +Cols[116] = Color(156,156,152) -- 116 lt.titanium poly +Cols[117] = Color(167,162,143) -- 117 lt.champagne poly +Cols[118] = Color(15,106,137) -- 118 arctic pearl + +Cols[119] = Color(161,153,131) -- 119 lt.driftwood poly +Cols[120] = Color(163,173,198) -- 120 white diamond pearl +Cols[121] = Color(155,139,128) -- 121 antelope beige +Cols[122] = Color(132,148,171) -- 122 currant blue poly +Cols[123] = Color(158,164,171) -- 123 crystal blue poly +--PURPLE + +--SPEC FLIP COLOURS +Cols[124] = Color(134,68,110) -- 124 temple curtain purple +Cols[125] = Color(226,6,6) -- 125 cherry red +Cols[126] = Color(71,120,60) -- 126 securicor dark green +Cols[127] = Color(215,142,16) -- 127 taxi yellow +Cols[128] = Color(42,119,161) -- 128 police car blue + +--MSC +Cols[129] = Color(66,31,33) -- 129 mellow burgundy +Cols[130] = Color(111,103,95) -- 130 desert taupe poly +Cols[131] = Color(252,38,0) -- 131 lammy orange +Cols[132] = Color(252,109,0) -- 132 lammy yellow +Cols[133] = Color(255,255,255) -- 133 very white + +--NEW COLOURS's (TBoGT) + +Cols[134] = Color(245,180,0) -- 134 SuperD yellow +Cols[135] = Color(5,5,5) -- 135 black +Cols[136] = Color(189,149,75) -- 136 Gold + +return Cols[Col1],Cols[Col2],Cols[Col3] end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_fuller_color_table.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_fuller_color_table.lua new file mode 100644 index 0000000..a5ff8f1 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/_simfphys_gta4_fuller_color_table.lua @@ -0,0 +1,167 @@ +function REN.GTA4ColorTable2(Col1,Col2,Col3,Col4) + +Cols = {} + +--GTA4's 'carcols.dat' file reformatted into a lua table, +--for easy editing and calling colors on all vehicles. + +--BLACK's +Cols[0] = Color(10,10,10) -- 0 black +Cols[1] = Color(37,37,39) -- 1 black poly +--GREYS/SILVERS +Cols[2] = Color(101,106,121) -- 2 concord blue poly +Cols[3] = Color(88,89,90) -- 3 pewter gray poly +Cols[4] = Color(156,161,163) -- 4 silver stone poly +Cols[5] = Color(150,145,140) -- 5 winning silver poly +Cols[6] = Color(81,84,89) -- 6 steel gray poly +Cols[7] = Color(63,62,69) -- 7 shadow silver poly +Cols[8] = Color(165,169,167) -- 8 silver stone poly +Cols[9] = Color(151,149,146) -- 9 porcelain silver poly +Cols[10] = Color(118,123,124) -- 10 gray poly +Cols[11] = Color(90,87,82) -- 11 anthracite gray poly +Cols[12] = Color(173,176,176) -- 12 astra silver poly +Cols[13] = Color(132,137,136) -- 13 ascot gray +Cols[14] = Color(148,157,159) -- 14 clear crystal blue frost poly +Cols[15] = Color(164,167,165) -- 15 silver poly +Cols[16] = Color(88,88,83) -- 16 dk.titanium poly +Cols[17] = Color(164,160,150) -- 17 titanium frost poly +Cols[18] = Color(175,177,177) -- 18 police white +Cols[19] = Color(109,108,110) -- 19 medium gray poly +Cols[20] = Color(100,104,106) -- 20 med.gray poly +Cols[21] = Color(82,86,97) -- 21 steel gray poly +Cols[22] = Color(140,146,154) -- 22 slate gray +Cols[23] = Color(91,93,94) -- 23 gun metal poly +Cols[24] = Color(189,190,198) -- 24 light blue grey +Cols[25] = Color(182,182,182) -- 25 securicor light gray +Cols[26] = Color(100,100,100) -- 26 arctic white + +--RED +Cols[27] = Color(226,6,6) -- 27 very red +Cols[28] = Color(150,8,0) -- 28 torino red pearl +Cols[29] = Color(107,0,0) -- 29 formula red +Cols[30] = Color(97,16,9) -- 30 blaze red +Cols[31] = Color(74,10,10) -- 31 graceful red mica +Cols[32] = Color(115,11,11) -- 32 garnet red poly +Cols[33] = Color(87,7,7) -- 33 desert red +Cols[34] = Color(38,3,6) -- 34 cabernet red poly +Cols[35] = Color(158,0,0) -- 35 turismo red +Cols[36] = Color(20,0,2) -- 36 desert red +Cols[37] = Color(15,4,4) -- 37 currant red solid +Cols[38] = Color(15,8,10) -- 38 brt.currant red poly +Cols[39] = Color(57,25,29) -- 39 elec.currant red poly +Cols[40] = Color(85,39,37) -- 40 med.cabernet solid +Cols[41] = Color(76,41,41) -- 41 wild strawberry poly +Cols[42] = Color(116,29,40) -- 42 med.red solid +Cols[43] = Color(109,40,55) -- 43 bright red +Cols[44] = Color(115,10,39) -- 44 bright red +Cols[45] = Color(100,13,27) -- 45 med.garnet red poly +Cols[46] = Color(98,11,28) -- 46 brilliant red poly +Cols[47] = Color(115,24,39) -- 47 brilliant red poly2 +Cols[48] = Color(171,152,143) -- 48 alabaster solid +Cols[49] = Color(32,32,44) -- 49 twilight blue poly + +--GREEN +Cols[50] = Color(68,98,79) -- 50 torch red +Cols[51] = Color(46,91,32) -- 51 green +Cols[52] = Color(30,46,50) -- 52 deep jewel green +Cols[53] = Color(48,79,69) -- 53 agate green +Cols[54] = Color(77,98,104) -- 54 petrol blue green poly +Cols[55] = Color(94,112,114) -- 55 hoods +Cols[56] = Color(25,56,38) -- 56 green +Cols[57] = Color(45,58,53) -- 57 dark green poly +Cols[58] = Color(51,95,63) -- 58 rio red +Cols[59] = Color(71,120,60) -- 59 securicor dark green +Cols[60] = Color(147,163,150) -- 60 seafoam poly +Cols[61] = Color(154,167,144) -- 61 pastel alabaster solid + +--BLUE +Cols[62] = Color(38,55,57) -- 62 midnight blue +Cols[63] = Color(76,117,183) -- 63 striking blue +Cols[64] = Color(70,89,122) -- 64 saxony blue poly +Cols[65] = Color(93,126,141) -- 65 jasper green poly +Cols[66] = Color(59,78,120) -- 66 mariner blue +Cols[67] = Color(61,74,104) -- 67 harbor blue poly +Cols[68] = Color(109,122,136) -- 68 diamond blue poly +Cols[69] = Color(22,34,72) -- 69 surf blue +Cols[70] = Color(39,47,75) -- 70 nautical blue poly +Cols[71] = Color(78,104,129) -- 71 lt.crystal blue poly +Cols[72] = Color(106,122,140) -- 72 med regatta blue poly +Cols[73] = Color(111,130,151) -- 73 spinnaker blue solid +Cols[74] = Color(14,49,109) -- 74 ultra blue poly +Cols[75] = Color(57,90,131) -- 75 bright blue poly +Cols[76] = Color(32,75,107) -- 76 nassau blue poly +Cols[77] = Color(43,62,87) -- 77 med.sapphire blue poly +Cols[78] = Color(54,65,85) -- 78 steel blue poly +Cols[79] = Color(108,132,149) -- 79 lt.sapphire blue poly +Cols[80] = Color(77,93,96) -- 80 malachite poly +Cols[81] = Color(64,108,143) -- 81 med.maui blue poly +Cols[82] = Color(19,69,115) -- 82 bright blue poly +Cols[83] = Color(16,80,130) -- 83 bright blue poly +Cols[84] = Color(56,86,148) -- 84 blue +Cols[85] = Color(0,28,50) -- 85 dk.sapphire blue poly +Cols[86] = Color(89,110,135) -- 86 lt.sapphire blue poly +Cols[87] = Color(34,52,87) -- 87 med.sapphire blue firemist +Cols[88] = Color(32,32,44) -- 88 twilight blue poly + +--YELLOW +Cols[89] = Color(245,137,15) -- 89 taxi yellow +Cols[90] = Color(145,115,71) -- 90 race yellow solid +Cols[91] = Color(142,140,70) -- 91 pastel alabaster +Cols[92] = Color(170,173,142) -- 92 oxford white solid +Cols[93] = Color(174,155,127) -- 93 flax +Cols[94] = Color(150,129,108) -- 94 med.flax +Cols[95] = Color(122,117,96) -- 95 pueblo beige +Cols[96] = Color(157,152,114) -- 96 light ivory +Cols[97] = Color(152,149,134) -- 97 smoke silver poly +Cols[98] = Color(156,141,113) -- 98 bisque frost poly + +--ORANGE + +--PURPLE +Cols[99] = Color(105,30,59) -- 99 classic red +Cols[100] = Color(114,42,63) -- 100 vermilion solid +Cols[101] = Color(124,27,68) -- 101 vermillion solid + +--BROWN +Cols[102] = Color(34,25,24) -- 102 biston brown poly +Cols[103] = Color(127,105,86) -- 103 lt.beechwood poly +Cols[104] = Color(71,53,50) -- 104 dk.beechwood poly +Cols[105] = Color(105,88,83) -- 105 dk.sable poly +Cols[106] = Color(98,68,40) -- 106 med.beechwood poly +Cols[107] = Color(125,98,86) -- 107 woodrose poly +Cols[108] = Color(170,157,132) -- 108 sandalwood frost poly +Cols[109] = Color(123,113,94) -- 109 med.sandalwood poly +Cols[110] = Color(171,146,118) -- 110 copper beige +Cols[111] = Color(99,92,90) -- 111 warm grey mica + +--WHITE +Cols[112] = Color(201,201,201) -- 112 white +Cols[113] = Color(214,218,214) -- 113 frost white +Cols[114] = Color(159,157,148) -- 114 honey beige poly +Cols[115] = Color(147,163,150) -- 115 seafoam poly +Cols[116] = Color(156,156,152) -- 116 lt.titanium poly +Cols[117] = Color(167,162,143) -- 117 lt.champagne poly +Cols[118] = Color(15,106,137) -- 118 arctic pearl + +Cols[119] = Color(161,153,131) -- 119 lt.driftwood poly +Cols[120] = Color(163,173,198) -- 120 white diamond pearl +Cols[121] = Color(155,139,128) -- 121 antelope beige +Cols[122] = Color(132,148,171) -- 122 currant blue poly +Cols[123] = Color(158,164,171) -- 123 crystal blue poly +--PURPLE + +--SPEC FLIP COLOURS +Cols[124] = Color(134,68,110) -- 124 temple curtain purple +Cols[125] = Color(226,6,6) -- 125 cherry red +Cols[126] = Color(71,120,60) -- 126 securicor dark green +Cols[127] = Color(215,142,16) -- 127 taxi yellow +Cols[128] = Color(42,119,161) -- 128 police car blue + +--MSC +Cols[129] = Color(66,31,33) -- 129 mellow burgundy +Cols[130] = Color(111,103,95) -- 130 desert taupe poly +Cols[131] = Color(252,38,0) -- 131 lammy orange +Cols[132] = Color(252,109,0) -- 132 lammy yellow +Cols[133] = Color(255,255,255) -- 133 very white + +return Cols[Col1],Cols[Col2],Cols[Col3],Cols[Col4] end \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_admiral.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_admiral.lua new file mode 100644 index 0000000..dcb2df3 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_admiral.lua @@ -0,0 +1,968 @@ +local V = { + Name = 'Admiral', + Model = 'models/octoteam/vehicles/admiral.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1650, + Trunk = { 35 }, + + EnginePos = Vector(70,0,5), + + LightsTable = 'gta4_admiral', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,22)} + -- CarCols[2] = {REN.GTA4ColorTable(7,7,79)} + -- CarCols[3] = {REN.GTA4ColorTable(16,16,93)} + -- CarCols[4] = {REN.GTA4ColorTable(34,34,32)} + -- CarCols[5] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[6] = {REN.GTA4ColorTable(54,54,53)} + -- CarCols[7] = {REN.GTA4ColorTable(62,62,65)} + -- CarCols[8] = {REN.GTA4ColorTable(70,70,63)} + -- CarCols[9] = {REN.GTA4ColorTable(72,72,64)} + -- CarCols[10] = {REN.GTA4ColorTable(102,102,105)} + -- CarCols[11] = {REN.GTA4ColorTable(104,104,105)} + -- CarCols[12] = {REN.GTA4ColorTable(116,116,122)} + -- CarCols[13] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[14] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[15] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[16] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[17] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/admiral_wheel.mdl', + + CustomWheelPosFL = Vector(65,35.35,-16), + CustomWheelPosFR = Vector(65,-35.35,-16), + CustomWheelPosRL = Vector(-65,35.35,-16), + CustomWheelPosRR = Vector(-65,-35.35,-16), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 40, + + SeatOffset = Vector(-8,-19.5,13), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-20,-18), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-36,20,-18), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-36,-20,-18), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-117.4,18.7,-17.6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-117.4,23,-17.6), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 34000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 7, + RearConstant = 34000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4.5, + + MaxGrip = 52, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 48, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-91,-39,6), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/admiral_idle.wav', + + snd_low = 'octoteam/vehicles/admiral_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/admiral_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/admiral_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/admiral_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/admiral_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(24.1, 18.958, 10.751), ang = Angle(-0.0, -90.0, 79.6) }, + Radio = { pos = Vector(26.065, 0.010, 1.051), ang = Angle(-19.1, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(107.712, -0.000, -10.633), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-116.884, -0.001, 0.997), ang = Angle(-26.3, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(3.524, -0.002, 23.213), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(20.625, 38.713, 12.755), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(19.786, -38.372, 12.884), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_admiral', V ) + +local light_table = { + L_HeadLampPos = Vector(101,26,-1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(101,-26,-1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-114,29,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-114,-29,4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(101,26,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(101,-26,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(23.6,26.2,8.6), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(101,26,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(101,-26,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23.8,26.2,9.7), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-114,29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-116,16,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-114,-29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-116,-16,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-118,16,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-116,29,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,-16,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-116,-29,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-115.8,22.8,2.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-115.8,-22.8,2.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(21.4, -38.1, 11.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(21.4, 38.1, 11.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(102.6, -12.3, -1.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.03 + }, + { + pos = Vector(102.6, 12.3, -1.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.025 + }, + { + pos = Vector(-76.6, -28.9, 13.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-76.6, 28.9, 13.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-115.7, -8.4, 0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-115.7, 8.4, 0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(105.6, -9.3, -11.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(105.6, 9.3, -11.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(5.8, 22, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(5.8, 18, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(5.8, 14, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(5.8, 10, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(5.8, -22, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(5.8, -18, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(5.8, -14, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(5.8, -10, 25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + }, + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95.2,35.5,-0.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-111.6,37.4,2.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(24.4,22,12), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(95.2,-35.5,-0.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-111.6,-37.4,2.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(24.4,16.3,12), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [5] = '', + [10] = '' + }, + Brake = { + [4] = '', + [5] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = '' + }, + Reverse = { + [4] = '', + [5] = '', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + Brake_Reverse = { + [4] = '', + [5] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = '', + [10] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = '', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = '', + [10] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = '', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [5] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + right = { + [9] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_admiral', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_airtug.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_airtug.lua new file mode 100644 index 0000000..66bcf5c --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_airtug.lua @@ -0,0 +1,261 @@ +local V = { + Name = 'Airtug', + Model = 'models/octoteam/vehicles/airtug.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + FLEX = { + Trailers = { + outputPos = Vector(-70,0,-5), + outputType = 'ballsocket' + } + }, + + Members = { + Mass = 1400.0, + + EnginePos = Vector(30,0,20), + + LightsTable = 'gta4_airtug', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,113,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 2, 0) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/airtug_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/airtug_wheel_r.mdl', + + CustomWheelPosFL = Vector(34,21,-11), + CustomWheelPosFR = Vector(34,-21,-11), + CustomWheelPosRL = Vector(-34,19,-5), + CustomWheelPosRR = Vector(-34,-19,-5), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 9, + RearWheelRadius = 15, + + CustomMassCenter = Vector(0,0,-2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-35,-12,45), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-30,-10,15), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + + FrontHeight = 10, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 65, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 10, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 140.0, + PowerbandStart = 2000, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = true, + + FuelFillPos = Vector(18,28,1), + FuelType = FUELTYPE_ELECTRIC, + FuelTankSize = 45, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/airtug_idle.wav', + + snd_low = 'octoteam/vehicles/airtug_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/airtug_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/airtug_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/airtug_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/airtug_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/airtug_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.10, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_airtug', V ) + +local light_table = { + L_HeadLampPos = Vector(52,21.4,1.3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(52,-21.4,1.3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-49.9,20.6,13.1), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-49.9,-20.6,13.1), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(52,21.4,1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(52,-21.4,1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(2.4,5.5,35.4), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(52,21.4,1.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(52,-21.4,1.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(2.4,7.2,35.4), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-49.9,20.6,13.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-49.9,-20.6,13.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-49.9,20.6,13.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-49.9,-20.6,13.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-49.9,16,13.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-49.9,-16,13.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + SubMaterials = { + off = { + Base = { + [2] = '', + [3] = '' + }, + Reverse = { + [2] = '', + [3] = 'models/gta4/vehicles/airtug/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [2] = 'models/gta4/vehicles/airtug/detail2_on', + [3] = '' + }, + Reverse = { + [2] = 'models/gta4/vehicles/airtug/detail2_on', + [3] = 'models/gta4/vehicles/airtug/detail2_on' + }, + }, + on_highbeam = { + Base = { + [2] = 'models/gta4/vehicles/airtug/detail2_on', + [3] = '' + }, + Reverse = { + [2] = 'models/gta4/vehicles/airtug/detail2_on', + [3] = 'models/gta4/vehicles/airtug/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_airtug', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ambulance.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ambulance.lua new file mode 100644 index 0000000..3800773 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ambulance.lua @@ -0,0 +1,667 @@ +sound.Add({ + name = 'AMBULANCE_WARNING', + channel = CHAN_STATIC, + volume = 1.0, + level = 90, + sound = 'octoteam/vehicles/horns/ambulance_rumble.wav' +} ) + +local V = { + Name = 'Ambulance', + Model = 'models/octoteam/vehicles/ambulance.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 3000, + Trunk = { 100 }, + + EnginePos = Vector(100,0,10), + + LightsTable = 'gta4_ambulance', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,28,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/ambulance_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/ambulance_wheel_r.mdl', + + CustomWheelPosFL = Vector(87,39,-17), + CustomWheelPosFR = Vector(87,-39,-17), + CustomWheelPosRL = Vector(-88,42,-17), + CustomWheelPosRR = Vector(-88,-42,-17), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,30), + + CustomSteerAngle = 35, + + SeatOffset = Vector(30,-25,33), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(35,-25,1), + ang = Angle(0,-90,0), + hasRadio = true + }, + { + pos = Vector(-145,36,1), + ang = Angle(0,180,0) + }, + { + pos = Vector(-145,-36,1), + ang = Angle(0,0,0) + }, + }, + ExhaustPositions = { + { + pos = Vector(-58.3,49.5,-21.7), + ang = Angle(-90,-90,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 65000, + FrontDamping = 2000, + FrontRelativeDamping = 2000, + + RearHeight = 10, + RearConstant = 65000, + RearDamping = 2000, + RearRelativeDamping = 2000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 70, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 60, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-112,53,7), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + AirFriction = -60, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/ambulance_idle.wav', + + snd_low = 'octoteam/vehicles/ambulance_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/ambulance_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/ambulance_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/ambulance_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/ambulance_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'simulated_vehicles/horn_2.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(60.542, 24.688, 26.905), ang = Angle(-0.0, -90.0, 61.9) }, + Radio = { pos = Vector(62.325, -0.008, 20.204), ang = Angle(-24.9, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(119.707, -0.010, -13.295), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-162.513, -0.000, -11.507), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(71.283, 51.079, 33.999), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(71.845, -51.321, 33.811), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_ambulance', V ) + +local colOff = Color(0,0,0, 0) +local emsCenter = Vector(31, 0, 58.5) +local emsRearCenter = Vector(-164, 0, 71) + +local light_table = { + L_HeadLampPos = Vector(110.8,36.4,11.8), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(110.8,-36.4,11.8), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-163.4,37.7,-11.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-163.4,-37.7,-11.5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(110.8,36.4,11.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(110.8,-36.4,11.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(61.1,21,28.1), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(112,27.1,11.8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(112,-27.1,11.8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(61.1,19.5,28.1), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-163.4,37.7,-11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163.4,-37.7,-11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-163.4,30.7,-11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163.4,-30.7,-11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/medic/siren1.wav','octoteam/vehicles/medic/siren2.wav','octoteam/vehicles/medic/siren3.wav'}, + ems_sprites = { + -- + -- FRONT + -- + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(0,0,255, 20), colOff, Color(0,0,255, 20), Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + Color(255,255,255, 8), colOff, Color(255,255,255, 8), Color(255,255,255, 8), colOff, Color(255,255,255, 8), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(0,0,255, 20), colOff, Color(0,0,255, 20), Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + Color(255,255,255, 8), colOff, Color(255,255,255, 8), Color(255,255,255, 8), colOff, Color(255,255,255, 8), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + + -- + -- SIDE + -- + { + pos = emsCenter + Vector(5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(0, -25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(-5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(0, 25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsCenter + Vector(-5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + + -- + -- REAR + -- + { + pos = emsRearCenter + Vector(0, -21, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(0,0,255, 20), colOff, Color(0,0,255, 20), Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + Color(255,255,255, 8), colOff, Color(255,255,255, 8), Color(255,255,255, 8), colOff, Color(255,255,255, 8), colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, 21, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(0,0,255, 20), colOff, Color(0,0,255, 20), Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + Color(255,255,255, 8), colOff, Color(255,255,255, 8), Color(255,255,255, 8), colOff, Color(255,255,255, 8), colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, 42, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, 31.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, 21, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, 10.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, 0, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, -10.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, -21, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, -31.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + Color(255,255,255, 200), colOff, Color(255,255,255, 200), Color(255,255,255, 200), colOff, Color(255,255,255, 200), colOff, + }, + Speed = 0.05 + }, + { + pos = emsRearCenter + Vector(0, -42, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.05 + }, + + -- + -- HEAD/SIDE LIGHTS + -- + { + pos = Vector(115, 15, 12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(115, -15, 12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(25,25,255, 255), colOff, Color(25,25,255, 255), colOff, + }, + Speed = 0.07 + }, + }, + + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(108.6,43.1,11.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(108.6,43.1,4.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-163.4,44.4,-11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(61.1,31,28.1), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(108.6,-43.1,11.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(108.6,-43.1,4.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-163.4,-44.4,-11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(61.1,30,28.1), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [5] = '', + [10] = '', + }, + Brake = { + [4] = '', + [5] = '', + [10] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + [5] = '', + [10] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + [5] = '', + [10] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + [5] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + [10] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + [5] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + [10] = 'models/gta4/vehicles/ambulance/ambulance_lights_on', + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/ambulance/ambulance_lights_on' + }, + right = { + [14] = 'models/gta4/vehicles/ambulance/ambulance_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_ambulance', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_banshee.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_banshee.lua new file mode 100644 index 0000000..5874339 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_banshee.lua @@ -0,0 +1,387 @@ +local V = { + Name = 'Banshee', + Model = 'models/octoteam/vehicles/banshee.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1500, + Trunk = { 25 }, + + Backfire = true, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_banshee', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,88)} + -- CarCols[2] = {REN.GTA4ColorTable(0,1,87)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[4] = {REN.GTA4ColorTable(1,1,90)} + -- CarCols[5] = {REN.GTA4ColorTable(1,4,8)} + -- CarCols[6] = {REN.GTA4ColorTable(12,1,87)} + -- CarCols[7] = {REN.GTA4ColorTable(23,23,90)} + -- CarCols[8] = {REN.GTA4ColorTable(28,28,89)} + -- CarCols[9] = {REN.GTA4ColorTable(31,9,32)} + -- CarCols[10] = {REN.GTA4ColorTable(35,0,28)} + -- CarCols[11] = {REN.GTA4ColorTable(39,39,28)} + -- CarCols[12] = {REN.GTA4ColorTable(46,46,28)} + -- CarCols[13] = {REN.GTA4ColorTable(49,49,118)} + -- CarCols[14] = {REN.GTA4ColorTable(52,4,118)} + -- CarCols[15] = {REN.GTA4ColorTable(53,53,50)} + -- CarCols[16] = {REN.GTA4ColorTable(62,89,65)} + -- CarCols[17] = {REN.GTA4ColorTable(66,1,71)} + -- CarCols[18] = {REN.GTA4ColorTable(89,1,50)} + -- CarCols[19] = {REN.GTA4ColorTable(36,4,90)} + -- CarCols[20] = {REN.GTA4ColorTable(77,48,118)} + -- CarCols[21] = {REN.GTA4ColorTable(16,16,19)} + -- CarCols[22] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[23] = {REN.GTA4ColorTable(15,0,93)} + -- CarCols[24] = {REN.GTA4ColorTable(128,132,94)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/banshee_wheel.mdl', + + CustomWheelPosFL = Vector(55,34,-10), + CustomWheelPosFR = Vector(55,-34,-10), + CustomWheelPosRL = Vector(-55,34,-10), + CustomWheelPosRR = Vector(-55,-34,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 40, + + SeatOffset = Vector(-38,-18,16), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-25,-18,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-35,38,-15.5), + ang = Angle(-90,-80,0), + }, + { + pos = Vector(-35,-38,-15.5), + ang = Angle(-90,80,0), + }, + }, + + FrontHeight = 4, + FrontConstant = 38000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 4, + RearConstant = 38000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + CounterSteeringMul = 0.95, + + MaxGrip = 80, + Efficiency = 1.1, + GripOffset = -4, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 76, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = true, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-67,35,14), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + PowerBias = 0.8, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/banshee_idle.wav', + + snd_low = 'octoteam/vehicles/banshee_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/banshee_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/banshee_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/banshee_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/banshee_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/banshee_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.45, + Gears = {-0.12,0,0.12,0.22,0.3,0.45,0.6}, + + Dash = { pos = Vector(-8.365, 17.729, 11.988), ang = Angle(-0.0, -90.0, 77.6) }, + Radio = { pos = Vector(-5.885, -1.588, 0.980), ang = Angle(-27.6, 164.0, 0.0) }, + Plates = { + Front = { pos = Vector(94.657, 0.001, -7.148), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-101.311, -0.000, 4.724), ang = Angle(-7.5, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(-7.062, 0.001, 22.430), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(-2.927, 33.530, 15.114), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(-2.888, -34.756, 15.376), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_banshee', V ) + +local light_table = { + L_HeadLampPos = Vector(73,29,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(73,-29,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-100,23,3.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-100,-23,3.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(73,29,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(73,-29,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(75,23,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(75,-23,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(-5,24.5,13), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(73,29,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(73,-29,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(75,23,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(75,-23,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(-5,24.5,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-100,23,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100,-23,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-100,23,5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-100,-23,5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { +--[[ { + pos = Vector(-5,21.5,15), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-100,23,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { +--[[ { + pos = Vector(-5,14,15), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-100,-23,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [9] = '', + [11] = '' + }, + Brake = { + [4] = '', + [9] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [11] = '' + }, + Reverse = { + [4] = '', + [9] = '', + [11] = 'models/gta4/vehicles/banshee/banshee_lights_on' + }, + Brake_Reverse = { + [4] = '', + [9] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [11] = 'models/gta4/vehicles/banshee/banshee_lights_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [11] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/banshee/banshee_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [11] = 'models/gta4/vehicles/banshee/banshee_lights_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [11] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/banshee/banshee_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [9] = 'models/gta4/vehicles/banshee/banshee_lights_on', + [11] = 'models/gta4/vehicles/banshee/banshee_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_banshee', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_battalion.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_battalion.lua new file mode 100644 index 0000000..fe7f959 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_battalion.lua @@ -0,0 +1,610 @@ +local V = { + Name = 'Battalion', + Model = 'models/octoteam/vehicles/landstalker_uv.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 2300.0, + hasSiren = true, + + EnginePos = Vector(70,0,20), + + LightsTable = 'gta4_battalion_fire', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,95)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,4)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,9)} + -- CarCols[4] = {REN.GTA4ColorTable(6,6,63)} + -- CarCols[5] = {REN.GTA4ColorTable(40,40,27)} + -- CarCols[6] = {REN.GTA4ColorTable(57,57,51)} + -- CarCols[7] = {REN.GTA4ColorTable(64,64,63)} + -- CarCols[8] = {REN.GTA4ColorTable(85,85,118)} + -- CarCols[9] = {REN.GTA4ColorTable(88,88,87)} + -- CarCols[10] = {REN.GTA4ColorTable(98,98,91)} + -- CarCols[11] = {REN.GTA4ColorTable(104,104,103)} + -- CarCols[12] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[13] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[14] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[15] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[16] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/landstalker_wheel.mdl', + + CustomWheelPosFL = Vector(69,32,-1), + CustomWheelPosFR = Vector(69,-32,-1), + CustomWheelPosRL = Vector(-68,32,-1), + CustomWheelPosRR = Vector(-68,-32,-1), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(0,-17,37), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(10,-18,5), + ang = Angle(0,-90,10), + hasRadio = true, + }, + { + pos = Vector(-31,18,5), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-31,-18,5), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-116,30,-6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-116,-30,-6), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 32000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 60, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 62, + PowerbandStart = 1700, + PowerbandEnd = 5300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.8, + + FuelFillPos = Vector(-84,38,28), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/perennial_idle.wav', + + snd_low = 'octoteam/vehicles/perennial_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/perennial_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/perennial_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/perennial_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/perennial_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/patriot_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(36.649, 16.578, 30.5), ang = Angle(-0.0, -90.0, 69.8) }, + Radio = { pos = Vector(38.974, 0.004, 27.900), ang = Angle(-21.1, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(106.015, 0.008, 4.537), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-111.827, -0.001, 23.487), ang = Angle(-7.3, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(19.955, 0.004, 50.460), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(28.124, 42.399, 36.578), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.124, -42.399, 36.578), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + }, +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_battalion', V ) + +local light_table = { + L_HeadLampPos = Vector(93,28,21), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-28,21), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-109,33,25), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-109,-33,25), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,28,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-28,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(37,17.5,28), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(96,23,20),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,-23,20),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(37,16.5,28), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(101,25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(101,-25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-109,33,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-33,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-111,34,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-111,-34,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-110,31,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-110,-31,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(24.6, 6.3, 51.5), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,255,255,255), + Color(255,255,255,0), + Color(255,0,0,255), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + }, + Speed = 0.1 + }, + { + pos = Vector(24.6, 17.9, 51.5), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,0,0,255), + Color(255,255,255,0), + Color(255,255,255,255), + }, + Speed = 0.1 + }, + { + pos = Vector(24.6, -17.9, 51.5), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,0,0,255), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,255,255,255), + Color(255,0,0,0), + Color(255,0,0,255), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + }, + Speed = 0.1 + }, + { + pos = Vector(24.6, -6.3, 51.5), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,255), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,255,255,255), + Color(255,0,0,0), + Color(255,0,0,255), + Color(255,0,0,0), + }, + Speed = 0.1 + }, + { + pos = Vector(-101.2, -17.1, 50.6), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,0,0,0), + Color(255,0,0,255), + Color(255,0,0,0), + Color(255,255,255,255), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + }, + Speed = 0.075 + }, + { + pos = Vector(-100.8, -4.2, 50.6), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,0,0,0), + Color(255,0,0,255), + Color(255,0,0,0), + Color(255,255,255,255), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + }, + Speed = 0.075 + }, + --left side row whites + { + pos = Vector(-101.2, 17.1, 50.6), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,0,0,255), + Color(255,255,255,0), + Color(255,255,255,255), + }, + Speed = 0.075 + }, + { + pos = Vector(-100.8, 4.2, 50.6), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,255), + Color(255,255,255,0), + Color(255,255,255,255), + }, + Speed = 0.075 + }, + { + pos = Vector(98.3, -12.1, 18.9), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,0,0,0), + Color(255,0,0,255), + Color(255,0,0,0), + Color(255,255,255,255), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + Color(255,0,0,0), + }, + Speed = 0.075 + }, + { + pos = Vector(98.3, 12.1, 18.9), + material = 'sprites/light_ignorez', + size = 30, + Colors = { + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,255,255,0), + Color(255,0,0,255), + Color(255,255,255,0), + Color(255,255,255,255), + }, + Speed = 0.075 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(89,33,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-109,35,21.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(37,19,28), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(89,-33,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-109,-35,21.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(37,15,28), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [9] = '', + [12] = '' + }, + Brake = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = '' + }, + Reverse = { + [10] = '', + [11] = '', + [9] = '', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + Brake_Reverse = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = '', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = '', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = '', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + turnsignals = { + left = { + [14] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_battalion_fire', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bearcat.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bearcat.lua new file mode 100644 index 0000000..3005a8b --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bearcat.lua @@ -0,0 +1,442 @@ + +local V = { + Name = "BearCat", + Model = "models/perrynsvehicles/bearcat_g3/bearcat_g3.mdl", + Category = "Доброград - Службы", + SpawnAngleOffset = -90, + + Members = { + Mass = 4600, + + LightsTable = "bearcat", + + MaxHealth = 5000, + IsArmored = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/perrynsvehicles/bearcat_g3/bearcatwheel.mdl', + + CustomWheelPosFL = Vector(47, 60.9, 24), + CustomWheelPosFR = Vector(-47, 60.9, 24), + CustomWheelPosRL = Vector(47, -66, 24), + CustomWheelPosRR = Vector(-47, -66, 24), + + CustomWheelAngleOffset = Angle(0, 0, 0), + + CustomMassCenter = Vector(0,0,2), + CustomSteerAngle = 40, + + EnginePos = Vector(0,70,63), + + SeatOffset = Vector(-10,0,-4), + SeatPitch = 0, + + PassengerSeats = { + { + pos = Vector(23,13,35), + ang = Angle(0,0,14), + hasRadio = true + }, + + { + pos = Vector(23,-22,46), + ang = Angle(0,180,14), + noMirrors = true + }, + { + pos = Vector(-23,-22,46), + ang = Angle(0,180,14), + noMirrors = true + }, + { + pos = Vector(-26,-55,46), + ang = Angle(0,-90,14), + noMirrors = true + }, + { + pos = Vector(26,-55,46), + ang = Angle(0,90,14), + noMirrors = true, + }, + { + pos = Vector(-26,-80,46), + ang = Angle(0,-90,14), + noMirrors = true, + }, + { + pos = Vector(26,-80,46), + ang = Angle(0,90,14), + noMirrors = true + }, + { + pos = Vector(-26,-105,46), + ang = Angle(0,-90,14), + noMirrors = true + }, + { + pos = Vector(26,-105,46), + ang = Angle(0,90,14), + noMirrors = true + }, + }, + + ExhaustPositions = { + { + pos = Vector(46.942,-113.798,22.144), + ang = Angle(90,0,0), + }, + { + pos = Vector(46.942,-118.55,22.144), + ang = Angle(90,0,0), + }, + }, + + StrengthenSuspension = true, + + FrontHeight = 13, + FrontConstant = 100000, + FrontDamping = 10000, + FrontRelativeDamping = 10000, + + RearHeight = 13, + RearConstant = 100000, + RearDamping = 10000, + RearRelativeDamping = 10000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 80, + BulletProofTires = true, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 70, + PowerbandStart = 1700, + PowerbandEnd = 5300, + Turbocharged = false, + Supercharged = false, + PowerBoost = 2.5, + + FuelFillPos = Vector(-42.734,-116.155,45.938), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + snd_pitch = 1, + snd_idle = "vehicles/perryn/bearcat_g3/idle.wav", + + snd_low = "vehicles/perryn/bearcat_g3/first.wav", + snd_low_revdown = "vehicles/perryn/bearcat_g3/first.wav", + snd_low_pitch = 1, + + snd_mid = "vehicles/perryn/bearcat_g3/second.wav", + snd_mid_gearup = "vehicles/perryn/bearcat_g3/second.wav", + snd_mid_geardown = "vehicles/perryn/bearcat_g3/second.wav", + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stockade_horn.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(-23.423, 25.145, 64.612), ang = Angle(0, 0, 67.7) }, + Radio = { pos = Vector(0.120, 29.209, 62.969), ang = Angle(-22.3, -86.5, -1.3) }, + Plates = { + Back = { pos = Vector(-34.009, -127.657, 41.582), ang = Angle(0, -90, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(-0.117, 25.5, 83), + ang = Angle(0, 90, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(-60.297, 32.532, 74.323), + ang = Angle(0, 90, 0), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(61.104, 33.109, 74.527), + ang = Angle(0, 90, 0), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_bearcat', V) + +local light_table = { + L_HeadLampPos = Vector(-29,92,31.7), + L_HeadLampAng = Angle(180,-90,0), + + R_HeadLampPos = Vector(29,92,31.7), + R_HeadLampAng = Angle(180,-90,0), + + L_RearLampPos = Vector(28,-111,31.5), + L_RearLampAng = Angle(0,-90,0), + + R_RearLampPos = Vector(-28,-111,31.5), + R_RearLampAng = Angle(0,-90,0), + + Headlight_sprites = { + {pos = Vector(-38.369,89.847,53.994),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(38.369,89.847,53.994),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + + }, + Headlamp_sprites = { + {pos = Vector(-38.369,89.847,53.994),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(38.369,89.847,53.994),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-33.699,-122.366,103.461),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(33.699,-122.366,103.461),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-33.74,7.665,103.478),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(33.74,7.665,103.478),size = 45,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + }, + + FogLight_sprites = { + {pos = Vector(-35.327,97.56,38.025), size = 40,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(35.327,97.56,38.025), size = 40,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(0,13.847,96.667), size = 10,material="octoteam/sprites/lights/gta4_corona",color=Color(255,200,0)}, + {pos = Vector(-10.826,13.847,96.667), size = 10,material="octoteam/sprites/lights/gta4_corona",color=Color(255,200,0)}, + {pos = Vector(10.826,13.847,96.667), size = 10,material="octoteam/sprites/lights/gta4_corona",color=Color(255,200,0)}, + {pos = Vector(-33.761,13.847,95.893), size = 10,material="octoteam/sprites/lights/gta4_corona",color=Color(255,200,0)}, + {pos = Vector(33.761,13.847,95.893), size = 10,material="octoteam/sprites/lights/gta4_corona",color=Color(255,200,0)}, + }, + Rearlight_sprites = { + {pos = Vector(-37.999,-126.711,50.993),size = 40,material="octoteam/sprites/lights/gta4_corona"}, + {pos = Vector(37.999,-126.711,50.993),size = 40,material="octoteam/sprites/lights/gta4_corona"}, + }, + + Brakelight_sprites = { + {pos = Vector(-7.684,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 1*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 2*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 3*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 4*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 5*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 6*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 7*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 8*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 9*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 10*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 11*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 12*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 13*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(-7.684 + 14*1.024,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(7.684,-126.969,94.392),size = 10,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + }, + + Reverselight_sprites = { + {pos = Vector(-31.552,-126.664,50.993),size = 40,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + {pos = Vector(31.552,-126.664,50.993),size = 40,material="octoteam/sprites/lights/gta4_corona", color=Color(255,255,255)}, + + }, + Turnsignal_sprites = { + Left = { + {pos = Vector(-38.037,89.151,47.113),size = 25,color=Color(255,120,0),material="octoteam/sprites/lights/gta4_corona"}, + {pos = Vector(-50.799,82.77,49.458),size = 25,color=Color(255,120,0),material="octoteam/sprites/lights/gta4_corona"}, + {pos = Vector(-62.517,36.393,68.662),size = 40,color=Color(255,120,0),material="octoteam/sprites/lights/gta4_corona"}, + + }, + TurnBrakeLeft = { + {pos = Vector(-37.999,-126.711,50.993),size = 40,material="octoteam/sprites/lights/gta4_corona",color = Color(255,80,0,255)}, + }, + Right = { + {pos = Vector(38.037,89.151,47.113),size = 25,color=Color(255,120,0),material="octoteam/sprites/lights/gta4_corona"}, + {pos = Vector(50.799,82.77,49.458),size = 25,color=Color(255,120,0),material="octoteam/sprites/lights/gta4_corona"}, + {pos = Vector(62.517,36.393,68.662),size = 40,color=Color(255,120,0),material="octoteam/sprites/lights/gta4_corona"}, + + }, + TurnBrakeRight = { + {pos = Vector(37.999,-126.711,50.993),size = 40,material="octoteam/sprites/lights/gta4_corona",color = Color(255,80,0,255)}, + }, + }, + ems_sounds = {'octoteam/vehicles/swat/siren1.wav','octoteam/vehicles/swat/siren2.wav'}, + ems_sprites = { + -- Roof front + { + pos = Vector(-19.17,22.999,94.815), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),}, + Speed = 0.1, + }, + { + pos = Vector(-14.593,22.999,94.815), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),}, + Speed = 0.1, + }, + { + pos = Vector(19.17,22.999,94.815), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255),Color(0,0,0),}, + Speed = 0.1, + }, + { + pos = Vector(14.593,22.999,94.815), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255),Color(0,0,0),}, + Speed = 0.1, + }, + + + -- Grille Color(0,100,255) + { + pos = Vector(-12.265,98.25,55.855), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255)}, + Speed = 0.1, + }, + -- Grille Color(255,50,0) + { + pos = Vector(12.265,98.25,55.855), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),}, + Speed = 0.1, + }, + + + --Back Color(255,50,0) + { + pos = Vector(21.255,-131.564,94.949), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),Color(255,50,0),Color(0,0,0),}, + Speed = 0.1, + }, + --Back Color(0,100,255) + { + pos = Vector(-21.255,-131.564,94.949), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,0,0),Color(0,100,255),Color(0,0,0),Color(0,100,255),Color(0,0,0),}, + Speed = 0.1, + }, + + --Side Color(255,50,0) + { + pos = Vector(44.298,-123.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(0,0,0)}, + Speed = 0.1, + }, + --Side Color(0,100,255) + { + pos = Vector(-44.298,-123.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(0,0,0)}, + Speed = 0.1, + }, + + --Fog left + { + pos = Vector(-35.327,97.56,38.025), + material = "sprites/light_glow02_add_noz", + size = 40, + Colors = {Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),}, + Speed = 0.1, + }, + --Fog right + { + pos = Vector(35.327,97.56,38.025), + material = "sprites/light_glow02_add_noz", + size = 40, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),}, + Speed = 0.1, + }, + + --Reverse left + { + pos = Vector(-31.552,-126.664,50.993), + material = "sprites/light_glow02_add_noz", + size = 40, + Colors = {Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),}, + Speed = 0.1, + }, + --Reverse right + { + pos = Vector(31.552,-126.664,50.993), + material = "sprites/light_glow02_add_noz", + size = 40, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),}, + Speed = 0.1, + }, + { + pos = Vector(44.298,-110.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,100,255),Color(0,0,0),}, + Speed = 0.1, + }, + { + pos = Vector(-44.298,-107.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,100,255),Color(0,0,0),}, + Speed = 0.1, + }, + { + pos = Vector(44.298,-104.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),}, + Speed = 0.2, + }, + { + pos = Vector(44.298,-7.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),}, + Speed = 0.2, + }, + { + pos = Vector(44.298,-1.547,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(0,0,0)}, + Speed = 0.2, + }, + { + pos = Vector(-44.298,-7.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),}, + Speed = 0.2, + }, + { + pos = Vector(-44.298,-1.547,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(255,50,0),Color(0,0,0),Color(0,0,0)}, + Speed = 0.2, + }, + { + pos = Vector(-44.298,-101.747,93.241), + material = "sprites/light_glow02_add_noz", + size = 20, + Colors = {Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(0,0,0),Color(255,255,255),Color(0,0,0),Color(255,255,255),Color(0,0,0),}, + Speed = 0.2, + }, + } +} +list.Set("simfphys_lights", "bearcat", light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_benson.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_benson.lua new file mode 100644 index 0000000..a9ea63d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_benson.lua @@ -0,0 +1,339 @@ +local V = { + Name = 'Benson', + Model = 'models/octoteam/vehicles/benson.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 7500.0, + + EnginePos = Vector(140,0,10), + + LightsTable = 'gta4_benson', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,6)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,17,113)} + -- CarCols[2] = {REN.GTA4ColorTable(116,108,116)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/benson_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/benson_wheel_r.mdl', + + CustomWheelPosFL = Vector(132,45,-25), + CustomWheelPosFR = Vector(132,-45,-25), + CustomWheelPosRL = Vector(-140,46,-25), + CustomWheelPosRR = Vector(-140,-46,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 35, + + SeatOffset = Vector(65,-23.5,50), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(75,-22,3), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-40.3,16.3,-25), + ang = Angle(-100,-25,0), + }, + }, + + FrontHeight = 18, + FrontConstant = 50000, + FrontDamping = 3000, + FrontRelativeDamping = 700, + + RearHeight = 18, + RearConstant = 50000, + RearDamping = 3000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 103, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 45, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 120.0, + PowerbandStart = 1700, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(60,57,-14), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 140, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/benson_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.12, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_benson', V ) + +local light_table = { + L_HeadLampPos = Vector(168,43,-6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(168,-43,-6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-224,53.4,84), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-224,-53.4,84), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(168,43,-6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(168,-43,-6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(99,21,32), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(168,43,-6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(168,-43,-6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(99,19.5,32), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-224,53.4,84), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-224,-53.4,84), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-225,53.4,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-225,-53.4,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(160.2,44.6,7.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(99,30,32), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-225,53.4,33), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(160.2,-44.6,7.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(99,29,32), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-225,-53.4,33), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [2] = '', + [8] = '', + [11] = '', + }, + Brake = { + [2] = '', + [8] = 'models/gta4/vehicles/benson/detail2_on', + [11] = '', + }, + Reverse = { + [2] = '', + [8] = '', + [11] = 'models/gta4/vehicles/benson/detail2_on', + }, + Brake_Reverse = { + [2] = '', + [8] = 'models/gta4/vehicles/benson/detail2_on', + [11] = 'models/gta4/vehicles/benson/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = '', + [11] = '', + }, + Brake = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = 'models/gta4/vehicles/benson/detail2_on', + [11] = '', + }, + Reverse = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = '', + [11] = 'models/gta4/vehicles/benson/detail2_on', + }, + Brake_Reverse = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = 'models/gta4/vehicles/benson/detail2_on', + [11] = 'models/gta4/vehicles/benson/detail2_on', + }, + }, + on_highbeam = { + Base = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = '', + [11] = '', + }, + Brake = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = 'models/gta4/vehicles/benson/detail2_on', + [11] = '', + }, + Reverse = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = '', + [11] = 'models/gta4/vehicles/benson/detail2_on', + }, + Brake_Reverse = { + [2] = 'models/gta4/vehicles/benson/detail2_on', + [8] = 'models/gta4/vehicles/benson/detail2_on', + [11] = 'models/gta4/vehicles/benson/detail2_on', + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/benson/detail2_on' + }, + right = { + [7] = 'models/gta4/vehicles/benson/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_benson', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_biff.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_biff.lua new file mode 100644 index 0000000..4c2fc3a --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_biff.lua @@ -0,0 +1,369 @@ +local V = { + Name = 'Biff', + Model = 'models/octoteam/vehicles/biff.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 9000.0, + + EnginePos = Vector(140,0,30), + + LightsTable = 'gta4_biff', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(40,40,39)} + -- CarCols[2] = {REN.GTA4ColorTable(52,52,53)} + -- CarCols[3] = {REN.GTA4ColorTable(97,97,97)} + -- CarCols[4] = {REN.GTA4ColorTable(90,90,90)} + -- CarCols[5] = {REN.GTA4ColorTable(52,52,52)} + -- CarCols[6] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[7] = {REN.GTA4ColorTable(36,36,36)} + -- CarCols[8] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[9] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[10] = {REN.GTA4ColorTable(55,55,55)} + -- CarCols[11] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[12] = {REN.GTA4ColorTable(64,64,64)} + -- CarCols[13] = {REN.GTA4ColorTable(71,71,71)} + -- CarCols[14] = {REN.GTA4ColorTable(77,77,77)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + -- REN.GTA4Biff(ent, math.random(0,2)) + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/biff_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/biff_wheel_r.mdl', + + CustomWheelPosFL = Vector(126,45,-27), + CustomWheelPosFR = Vector(126,-45,-27), + CustomWheelPosML = Vector(-74,36,-27), + CustomWheelPosMR = Vector(-74,-36,-27), + CustomWheelPosRL = Vector(-126,36,-27), + CustomWheelPosRR = Vector(-126,-36,-27), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(63,-21,57), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(76,-20,14), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(59,59,84), + ang = Angle(-90,-90,0), + }, + { + pos = Vector(59,-59,84), + ang = Angle(-90,90,0), + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 22, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 148, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 110.0, + PowerbandStart = 1700, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(71.3,44,-9.3), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 160, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/firetruk_idle.wav', + + snd_low = 'octoteam/vehicles/firetruk_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/firetruk_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/firetruk_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/firetruk_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/firetruk_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'TRUCK_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.11, + Gears = {-0.6,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_biff', V ) + +local light_table = { + L_HeadLampPos = Vector(161,32,-3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(161,-32,-3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-160,42,1), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-160,-42,1), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(161,32,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(161,-32,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(159,42,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(159,-42,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(104,17,39), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(161,32,-3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(161,-32,-3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(104,16,39), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-160,42,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-160,-42,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-160,36.5,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-160,-36.5,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-160,42,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-160,-42,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-160,36.5,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-160,-36.5,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-160,31.9,0.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-160,-31.9,0.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(117,46,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-160,48,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(104,24,39), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(117,-46,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-160,-48,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(104,23,39), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [11] = '' + }, + Reverse = { + [3] = '', + [11] = 'models/gta4/vehicles/biff/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/biff/detail2_on', + [11] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/biff/detail2_on', + [11] = 'models/gta4/vehicles/biff/detail2_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/biff/detail2_on', + [11] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/biff/detail2_on', + [11] = 'models/gta4/vehicles/biff/detail2_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/biff/detail2_on' + }, + right = { + [6] = 'models/gta4/vehicles/biff/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_biff', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_blista.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_blista.lua new file mode 100644 index 0000000..dd70cdd --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_blista.lua @@ -0,0 +1,382 @@ +local V = { + Name = 'Blista Compact', + Model = 'models/octoteam/vehicles/blista.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Хетчбеки', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Хетчбеки', + + Members = { + Mass = 1400.0, + + EnginePos = Vector(65,0,10), + + LightsTable = 'gta4_blista', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,2)..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[2] = {REN.GTA4ColorTable(3,0,1)} + -- CarCols[3] = {REN.GTA4ColorTable(4,4,1)} + -- CarCols[4] = {REN.GTA4ColorTable(10,10,92)} + -- CarCols[5] = {REN.GTA4ColorTable(16,0,1)} + -- CarCols[6] = {REN.GTA4ColorTable(31,0,1)} + -- CarCols[7] = {REN.GTA4ColorTable(31,31,33)} + -- CarCols[8] = {REN.GTA4ColorTable(34,0,1)} + -- CarCols[9] = {REN.GTA4ColorTable(36,10,37)} + -- CarCols[10] = {REN.GTA4ColorTable(36,36,1)} + -- CarCols[11] = {REN.GTA4ColorTable(49,49,75)} + -- CarCols[12] = {REN.GTA4ColorTable(52,0,1)} + -- CarCols[13] = {REN.GTA4ColorTable(56,56,57)} + -- CarCols[14] = {REN.GTA4ColorTable(55,0,1)} + -- CarCols[15] = {REN.GTA4ColorTable(62,55,1)} + -- CarCols[16] = {REN.GTA4ColorTable(64,0,1)} + -- CarCols[17] = {REN.GTA4ColorTable(68,0,72)} + -- CarCols[18] = {REN.GTA4ColorTable(72,0,2)} + -- CarCols[19] = {REN.GTA4ColorTable(95,0,1)} + -- CarCols[20] = {REN.GTA4ColorTable(103,104,1)} + -- CarCols[21] = {REN.GTA4ColorTable(106,0,1)} + -- CarCols[22] = {REN.GTA4ColorTable(109,109,1)} + -- CarCols[23] = {REN.GTA4ColorTable(1,109,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/blista_wheel.mdl', + + CustomWheelPosFL = Vector(58,29,-7), + CustomWheelPosFR = Vector(58,-29,-7), + CustomWheelPosRL = Vector(-58,29,-7), + CustomWheelPosRR = Vector(-58,-29,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0.02,-2.4), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-14,-15,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-1,-15,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-91,-21,-6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-91,-16.8,-6), + ang = Angle(-90,0,0), + OnBodyGroups = { + [1] = {1}, + } + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 72, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 150.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-60,35,16), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/blista_idle.wav', + + snd_low = 'octoteam/vehicles/blista_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/blista_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/blista_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/blista_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/blista_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/blista_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.18, + Gears = {-0.5,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_blista', V ) + +local light_table = { + L_HeadLampPos = Vector(91,23,8), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(91,-23,8), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-86,23,13), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-86,-23,13), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(91,23,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(91,-23,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(21.6,14.4,21.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,23,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-23,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(21.6,15.2,21.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-86,23,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,-23,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-86,23,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,0,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 90, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,-23,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-86,18,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-86,-18,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(94,22,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-81,34,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(21.6,17.4,21.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(94,-22,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-81,-34,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(21.6,12.4,21.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [12] = '', + [9] = '', + }, + Brake = { + [11] = '', + [12] = 'models/gta4/vehicles/blista/blista_lights_on', + [9] = '', + }, + Reverse = { + [11] = '', + [12] = '', + [9] = 'models/gta4/vehicles/blista/blista_lights_on', + }, + Brake_Reverse = { + [11] = '', + [12] = 'models/gta4/vehicles/blista/blista_lights_on', + [9] = 'models/gta4/vehicles/blista/blista_lights_on', + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = '', + [9] = '', + }, + Brake = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = 'models/gta4/vehicles/blista/blista_lights_on', + [9] = '', + }, + Reverse = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = '', + [9] = 'models/gta4/vehicles/blista/blista_lights_on', + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = 'models/gta4/vehicles/blista/blista_lights_on', + [9] = 'models/gta4/vehicles/blista/blista_lights_on', + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = '', + [9] = '', + }, + Brake = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = 'models/gta4/vehicles/blista/blista_lights_on', + [9] = '', + }, + Reverse = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = '', + [9] = 'models/gta4/vehicles/blista/blista_lights_on', + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/blista/blista_lights_on', + [12] = 'models/gta4/vehicles/blista/blista_lights_on', + [9] = 'models/gta4/vehicles/blista/blista_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/blista/blista_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/blista/blista_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_blista', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bobcat.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bobcat.lua new file mode 100644 index 0000000..387dc1b --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bobcat.lua @@ -0,0 +1,359 @@ +local V = { + Name = 'Bobcat', + Model = 'models/octoteam/vehicles/bobcat.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 1800, + Trunk = { + nil, + {15, 2, 1}, + {50, 2, 2}, + }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_bobcat', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,4)..math.random(0,2)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[2] = {REN.GTA4ColorTable(1,11,5)} + -- CarCols[3] = {REN.GTA4ColorTable(5,5,5)} + -- CarCols[4] = {REN.GTA4ColorTable(34,34,32)} + -- CarCols[5] = {REN.GTA4ColorTable(53,53,54)} + -- CarCols[6] = {REN.GTA4ColorTable(54,54,55)} + -- CarCols[7] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[8] = {REN.GTA4ColorTable(69,69,67)} + -- CarCols[9] = {REN.GTA4ColorTable(76,76,67)} + -- CarCols[10] = {REN.GTA4ColorTable(95,95,95)} + -- CarCols[11] = {REN.GTA4ColorTable(102,102,103)} + -- CarCols[12] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[13] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[14] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[15] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[16] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/bobcat_wheel.mdl', + + CustomWheelPosFL = Vector(70,36,-14), + CustomWheelPosFR = Vector(70,-36,-14), + CustomWheelPosRL = Vector(-72,36,-14), + CustomWheelPosRR = Vector(-72,-36,-14), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-1,-19,26), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-18,-6), + ang = Angle(0,-90,10), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-36,37,-18.5), + ang = Angle(-100,-70,0), + }, + { + pos = Vector(-39.5,37,-18.5), + ang = Angle(-100,-70,0), + }, + }, + + FrontHeight = 9, + FrontConstant = 32000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 9, + RearConstant = 32000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 60, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 28, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 30, + PowerbandStart = 1700, + PowerbandEnd = 4600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-17,43,-7 ), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + AirFriction = -35, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/rancher_idle.wav', + BrakeSqueal = true, + + snd_low = 'octoteam/vehicles/rancher_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/rancher_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/rancher_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/rancher_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/rancher_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/rancher_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(29.880, 18.970, 18.408), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(33.423, 0.490, 12.959), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(104.949, 0, -11.772), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-113.898, -25.011, -16.542), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(21.243, 0.000, 39.467), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(34.139, 43.608, 26.021), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(33.912, -43.490, 26.065), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_bobcat', V ) + +local light_table = { + L_HeadLampPos = Vector(103,32,10), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(103,-32,10), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-103,35,2), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-103,-35,2), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(103,32,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(103,-32,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + }, + + Headlamp_sprites = { + {pos = Vector(102,32,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-32,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-103,35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103,35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-103,35,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-103,-35,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(103,32,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,35,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,23,16.8), + material = 'gta4/dash_left', + size = 1, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(103,-32,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,-35,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,16.8,16.8), + material = 'gta4/dash_right', + size = 1, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [9] = '', + }, + Reverse = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = '', + [9] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [9] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [9] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + }, + }, + turnsignals = { + left = { + [5] = 'models/gta4/vehicles/bobcat/bobcat_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/bobcat/bobcat_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_bobcat', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bobcat_towtruck.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bobcat_towtruck.lua new file mode 100644 index 0000000..1987b6a --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bobcat_towtruck.lua @@ -0,0 +1,359 @@ +local V = { + Name = 'Tow Truck', + Model = 'models/octoteam/vehicles/bobcat_towtruck.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 1800, + Trunk = { + nil, + {15, 2, 1}, + {50, 2, 2}, + }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_bobcat_towtruck', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,4)..math.random(0,2)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[2] = {REN.GTA4ColorTable(1,11,5)} + -- CarCols[3] = {REN.GTA4ColorTable(5,5,5)} + -- CarCols[4] = {REN.GTA4ColorTable(34,34,32)} + -- CarCols[5] = {REN.GTA4ColorTable(53,53,54)} + -- CarCols[6] = {REN.GTA4ColorTable(54,54,55)} + -- CarCols[7] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[8] = {REN.GTA4ColorTable(69,69,67)} + -- CarCols[9] = {REN.GTA4ColorTable(76,76,67)} + -- CarCols[10] = {REN.GTA4ColorTable(95,95,95)} + -- CarCols[11] = {REN.GTA4ColorTable(102,102,103)} + -- CarCols[12] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[13] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[14] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[15] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[16] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/bobcat_wheel.mdl', + + CustomWheelPosFL = Vector(70,36,-14), + CustomWheelPosFR = Vector(70,-36,-14), + CustomWheelPosRL = Vector(-72,36,-14), + CustomWheelPosRR = Vector(-72,-36,-14), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-1,-19,26), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-18,-6), + ang = Angle(0,-90,10), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-36,37,-18.5), + ang = Angle(-100,-70,0), + }, + { + pos = Vector(-39.5,37,-18.5), + ang = Angle(-100,-70,0), + }, + }, + + StrengthenSuspension = true, + + FrontHeight = 8, + FrontConstant = 25000, + FrontDamping = 1500, + FrontRelativeDamping = 1500, + + RearHeight = 8, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 2000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 60, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 28, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 38, + PowerbandStart = 1700, + PowerbandEnd = 4600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-17,43,-7 ), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + AirFriction = -45, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/rancher_idle.wav', + BrakeSqueal = true, + + snd_low = 'octoteam/vehicles/rancher_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/rancher_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/rancher_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/rancher_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/rancher_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/rancher_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(29.880, 18.970, 18.408), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(33.423, 0.490, 12.959), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(104.949, 0, -11.772), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-113.898, -25.011, -16.542), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(21.243, 0.000, 39.467), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(34.139, 43.608, 26.021), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(33.912, -43.490, 26.065), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_bobcat_towtruck', V ) + +local light_table = { + L_HeadLampPos = Vector(103,32,10), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(103,-32,10), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-103,35,2), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-103,-35,2), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(103,32,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(103,-32,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + }, + + Headlamp_sprites = { + {pos = Vector(102,32,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-32,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-103,35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103,35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-35,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-103,35,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-103,-35,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(103,32,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,35,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,23,16.8), + material = 'gta4/dash_left', + size = 1, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(103,-32,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,-35,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,16.8,16.8), + material = 'gta4/dash_right', + size = 1, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [9] = '', + }, + Reverse = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = '', + [9] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [9] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [11] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + [9] = 'models/gta4/vehicles/bobcat/bobcat_lights_on', + }, + }, + turnsignals = { + left = { + [5] = 'models/gta4/vehicles/bobcat/bobcat_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/bobcat/bobcat_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_bobcat_towtruck', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_boxville.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_boxville.lua new file mode 100644 index 0000000..dac4b88 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_boxville.lua @@ -0,0 +1,378 @@ +local V = { + Name = 'Boxville', + Model = 'models/octoteam/vehicles/boxville.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 3000.0, + + EnginePos = Vector(80,0,0), + + LightsTable = 'gta4_boxville', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,8)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,1,112)} + -- CarCols[2] = {REN.GTA4ColorTable(113,28,112)} + -- CarCols[3] = {REN.GTA4ColorTable(113,31,112)} + -- CarCols[4] = {REN.GTA4ColorTable(113,50,112)} + -- CarCols[5] = {REN.GTA4ColorTable(113,58,112)} + -- CarCols[6] = {REN.GTA4ColorTable(92,64,112)} + -- CarCols[7] = {REN.GTA4ColorTable(92,85,112)} + -- CarCols[8] = {REN.GTA4ColorTable(93,112,112)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/ambulance_wheel.mdl', + + CustomWheelPosFL = Vector(66,35,-25), + CustomWheelPosFR = Vector(66,-35,-25), + CustomWheelPosRL = Vector(-84,35,-25), + CustomWheelPosRR = Vector(-84,-35,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 16.3, + RearWheelRadius = 16.3, + + CustomMassCenter = Vector(0,0,20), + + CustomSteerAngle = 30, + + SeatOffset = Vector(15,-22,32), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(20,-25,-2), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-142,-32.1,-29.8), + ang = Angle(-135,35,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 35000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 35000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 70, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 110.0, + PowerbandStart = 1500, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-55,44,-12), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/ambulance_idle.wav', + + snd_low = 'octoteam/vehicles/ambulance_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/ambulance_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/ambulance_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/ambulance_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/ambulance_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/boxville_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.14, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1}, + + Dash = { pos = Vector(45.649, 21.990, 22.681), ang = Angle(-0.0, -90.0, 66) }, + Radio = { pos = Vector(59.870, 0.0, 12.648), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(103.603, 0, -18.330), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-145.821, 0, -17.810), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(41.004, 49.253, 38.247), + h = 3 / 4, + ratio = 1 / 3, + }, + right = { + pos = Vector(41.004, -49.253, 38.247), + h = 3 / 4, + ratio = 1 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_boxville', V ) + +local light_table = { + L_HeadLampPos = Vector(99,33.5,-1.7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(99,-33.5,-1.7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-149,34,-17), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-149,-34,-17), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(99,33.5,-1.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(99,-33.5,-1.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(46,20,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(99,33.5,-1.7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(99,-33.5,-1.7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(45,20,22), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(99,36.3,-9.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,255), + }, + { + pos = Vector(99,-36.3,-9.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-149,34,-17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-149,-34,-17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-149,26.5,-17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-149,-26.5,-17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-149,34.2,-7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-149,-34.2,-7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95,42,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(46,29,24), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(95,-42,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(46,28,24), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [7] = '', + [11] = '' + }, + Brake = { + [3] = '', + [7] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [11] = '' + }, + Reverse = { + [3] = '', + [7] = '', + [11] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + Brake_Reverse = { + [3] = '', + [7] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [11] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = '', + [11] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [11] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = '', + [11] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [11] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = '', + [11] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [11] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = '', + [11] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [7] = 'models/gta4/vehicles/boxville/boxville_lights_on', + [11] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/boxville/boxville_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_boxville', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_buccaneer.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_buccaneer.lua new file mode 100644 index 0000000..6d8f992 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_buccaneer.lua @@ -0,0 +1,377 @@ +local V = { + Name = 'Buccaneer', + Model = 'models/octoteam/vehicles/buccaneer.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,15), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(80,0,0), + + LightsTable = 'gta4_buccaneer', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(19,19,19)} + -- CarCols[2] = {REN.GTA4ColorTable(16,16,16)} + -- CarCols[3] = {REN.GTA4ColorTable(38,38,38)} + -- CarCols[4] = {REN.GTA4ColorTable(50,50,50)} + -- CarCols[5] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[6] = {REN.GTA4ColorTable(72,72,72)} + -- CarCols[7] = {REN.GTA4ColorTable(77,77,77)} + -- CarCols[8] = {REN.GTA4ColorTable(88,88,88)} + -- CarCols[9] = {REN.GTA4ColorTable(94,94,94)} + -- CarCols[10] = {REN.GTA4ColorTable(97,97,97)} + -- CarCols[11] = {REN.GTA4ColorTable(102,102,102)} + -- CarCols[10] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[13] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[14] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[15] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[16] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/buccaneer_wheel.mdl', + + CustomWheelPosFL = Vector(76,32,-16), + CustomWheelPosFR = Vector(76,-32,-16), + CustomWheelPosRL = Vector(-60,33,-16), + CustomWheelPosRR = Vector(-60,-33,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-11,-17,10), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-17,-20), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-113,21.7,-21.4), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 72, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 140.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-62,35,14), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/merit_idle.wav', + + snd_low = 'octoteam/vehicles/merit_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/merit_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/merit_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/merit_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/merit_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/buccaneer_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.5,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_buccaneer', V ) + +local light_table = { + L_HeadLampPos = Vector(114,27,-2), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(114,-27,-2), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-119,33.5,-9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-119,-33.5,-9), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(114,27,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(114,-27,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(115,21,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(115,-21,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(18.9,25,6), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(114,27,-2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(114,-27,-2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(115,21,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(115,-21,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(19.1,25,6.8), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-119,33.5,-9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-119,-33.5,-9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-119,33.5,-10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-119,-33.5,-10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(119,24,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(107,36,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + +--[[ { + pos = Vector(19.4,17.7,8.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-119,33.5,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(119,-24,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(107,-36,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + +--[[ { + pos = Vector(19.4,17,8.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-119,-33.5,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [10] = '', + [4] = '', + }, + Brake = { + [3] = '', + [10] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [4] = '', + }, + Reverse = { + [3] = '', + [10] = '', + [4] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + }, + Brake_Reverse = { + [3] = '', + [10] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [4] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = '', + [4] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [4] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = '', + [4] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [4] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = '', + [4] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [4] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = '', + [4] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [10] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + [4] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on', + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/buccaneer/buccaneer_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_buccaneer', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_burrito.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_burrito.lua new file mode 100644 index 0000000..3abf033 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_burrito.lua @@ -0,0 +1,363 @@ +local V = { + Name = 'Burrito', + Model = 'models/octoteam/vehicles/burrito.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(90,0,10), + + LightsTable = 'gta4_burrito', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,13)) + -- ent:SetBodyGroups('0'..math.random(0,4) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(9,9,9)} + -- CarCols[2] = {REN.GTA4ColorTable(11,11,11)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[4] = {REN.GTA4ColorTable(13,13,13)} + -- CarCols[5] = {REN.GTA4ColorTable(23,23,3)} + -- CarCols[6] = {REN.GTA4ColorTable(68,68,68)} + -- CarCols[7] = {REN.GTA4ColorTable(31,31,31)} + -- CarCols[8] = {REN.GTA4ColorTable(36,36,36)} + -- CarCols[9] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[10] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[11] = {REN.GTA4ColorTable(55,55,55)} + -- CarCols[12] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[13] = {REN.GTA4ColorTable(64,64,64)} + -- CarCols[14] = {REN.GTA4ColorTable(71,71,71)} + -- CarCols[15] = {REN.GTA4ColorTable(77,77,77)} + -- CarCols[16] = {REN.GTA4ColorTable(90,90,90)} + -- CarCols[17] = {REN.GTA4ColorTable(104,104,104)} + -- CarCols[18] = {REN.GTA4ColorTable(106,106,104)} + -- CarCols[19] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[20] = {REN.GTA4ColorTable(4,4,4)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/burrito_wheel.mdl', + + CustomWheelPosFL = Vector(80,40,-25), + CustomWheelPosFR = Vector(80,-40,-25), + CustomWheelPosRL = Vector(-80,40,-25), + CustomWheelPosRR = Vector(-80,-40,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 30, + + SeatOffset = Vector(30,-27,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(32,-25,-5), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-112,-19.6,-25.8), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 500, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 500, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 100, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 130.0, + PowerbandStart = 2200, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-50,48,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/burrito_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.16, + Gears = {-0.3,0,0.15,0.35,0.5,0.75}, + + Dash = { pos = Vector(55.160, 26.1, 23.322), ang = Angle(-0.0, -90.0, 79.2) }, + Radio = { pos = Vector(59.099, -0.065, 19.415), ang = Angle(-16.4, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(112.3, -0.008, -15), ang = Angle(-0.2, -0.0, 0.0) }, + Back = { pos = Vector(-115.5, 0.030, -15), ang = Angle(0, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(44.949, 0.014, 42.977), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(55.039, 47.047, 29.117), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(55.253, -46.805, 30.006), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_burrito', V ) + +local light_table = { + L_HeadLampPos = Vector(104,36,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(104,-36,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-113,36,19), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-113,-36,19), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(104,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(104,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(57,20,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(105,27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(105,-27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(57,21,23), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-113,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-113,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-113,36.3,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-113,-36.3,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(102,43,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(102,43,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(57,31,23), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-113,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(102,-43,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(102,-43,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(57,30,23), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-113,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [7] = '', + [10] = '', + }, + Reverse = { + [6] = '', + [7] = '', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = '', + [10] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [10] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_burrito', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_burrito_csd.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_burrito_csd.lua new file mode 100644 index 0000000..1e63413 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_burrito_csd.lua @@ -0,0 +1,414 @@ +local V = { + Name = 'Burrito (CSD)', + Model = 'models/octoteam/vehicles/burrito.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(90,0,10), + + LightsTable = 'gta4_burrito_csd', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,13)) + -- ent:SetBodyGroups('0'..math.random(0,4) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(9,9,9)} + -- CarCols[2] = {REN.GTA4ColorTable(11,11,11)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[4] = {REN.GTA4ColorTable(13,13,13)} + -- CarCols[5] = {REN.GTA4ColorTable(23,23,3)} + -- CarCols[6] = {REN.GTA4ColorTable(68,68,68)} + -- CarCols[7] = {REN.GTA4ColorTable(31,31,31)} + -- CarCols[8] = {REN.GTA4ColorTable(36,36,36)} + -- CarCols[9] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[10] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[11] = {REN.GTA4ColorTable(55,55,55)} + -- CarCols[12] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[13] = {REN.GTA4ColorTable(64,64,64)} + -- CarCols[14] = {REN.GTA4ColorTable(71,71,71)} + -- CarCols[15] = {REN.GTA4ColorTable(77,77,77)} + -- CarCols[16] = {REN.GTA4ColorTable(90,90,90)} + -- CarCols[17] = {REN.GTA4ColorTable(104,104,104)} + -- CarCols[18] = {REN.GTA4ColorTable(106,106,104)} + -- CarCols[19] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[20] = {REN.GTA4ColorTable(4,4,4)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/burrito_wheel.mdl', + + CustomWheelPosFL = Vector(80,40,-25), + CustomWheelPosFR = Vector(80,-40,-25), + CustomWheelPosRL = Vector(-80,40,-25), + CustomWheelPosRR = Vector(-80,-40,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 30, + + SeatOffset = Vector(30,-27,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(32,-25,-5), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-112,-19.6,-25.8), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 500, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 500, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 100, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 130.0, + PowerbandStart = 2200, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-50,48,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/burrito_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.16, + Gears = {-0.3,0,0.15,0.35,0.5,0.75}, + + Dash = { pos = Vector(55.160, 26.1, 23.322), ang = Angle(-0.0, -90.0, 79.2) }, + Radio = { pos = Vector(59.099, -0.065, 19.415), ang = Angle(-16.4, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(112.3, -0.008, -15), ang = Angle(-0.2, -0.0, 0.0) }, + Back = { pos = Vector(-115.5, 0.030, -15), ang = Angle(0, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(44.949, 0.014, 42.977), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(55.039, 47.047, 29.117), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(55.253, -46.805, 30.006), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_burrito_csd', V ) + +local light_table = { + L_HeadLampPos = Vector(104,36,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(104,-36,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-113,36,19), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-113,-36,19), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(104,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(104,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(57,20,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(105,27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(105,-27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(57,21,23), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-113,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-113,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-113,36.3,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-113,-36.3,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(102,43,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(102,43,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(57,31,23), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-113,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(102,-43,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(102,-43,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(57,30,23), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-113,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [7] = '', + [10] = '', + }, + Reverse = { + [6] = '', + [7] = '', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = '', + [10] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [10] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [7] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + }, + + ems_sprites = {{ + pos = Vector(45, 23.6, 51), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(45, 8, 51), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(45, -23.6, 51), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(45, -8, 51), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-108, -24.9, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-108, -8.0, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-108, 24.9, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-108, 8.0, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }}, + +} +list.Set('simfphys_lights', 'gta4_burrito_csd', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bus.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bus.lua new file mode 100644 index 0000000..e8c66af --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_bus.lua @@ -0,0 +1,379 @@ +sound.Add({ + name = 'BUS_HORN', + channel = CHAN_STATIC, + volume = 1.0, + level = 85, + sound = 'octoteam/vehicles/horns/bus_horn.wav' +} ) + +local V = { + Name = 'Bus', + Model = 'models/octoteam/vehicles/bus.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 3000, + Trunk = { 100 }, + + EnginePos = Vector(-240,0,0), + + LightsTable = 'gta4_bus', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(53,8,53)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 3) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/bus_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/bus_wheel_r.mdl', + + CustomWheelPosFL = Vector(145,47,-25), + CustomWheelPosFR = Vector(145,-47,-25), + CustomWheelPosRL = Vector(-145,41,-25), + CustomWheelPosRR = Vector(-145,-41,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(180,-34,70), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(76,-33,3), + ang = Angle(0,-90,0), + hasRadio = true + }, + { + pos = Vector(76,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(38,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(38,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(0,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(0,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(36,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(36,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-176,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-176,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-221,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-221,33,3), + ang = Angle(0,-90,0) + }, + }, + ExhaustPositions = { + { + pos = Vector(-245.1,48.4,77.5), + ang = Angle(-90,-15,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 65000, + FrontDamping = 2000, + FrontRelativeDamping = 2000, + + RearHeight = 6, + RearConstant = 65000, + RearDamping = 2000, + RearRelativeDamping = 2000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 50, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.8, + + FuelFillPos = Vector(-240,55,0), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 140, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/bus_idle.wav', + + snd_low = 'octoteam/vehicles/bus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/bus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/bus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/bus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/bus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'BUS_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(221.076, 33.305, 25.619), ang = Angle(-0.0, -90.0, 53.4) }, + Radio = { pos = Vector(226.149, 22.585, 23.061), ang = Angle(-36.3, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(237.777, -0.022, -12.570), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-269.402, -0.202, -25.433), ang = Angle(-4.4, -180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(223.264, 61.857, 42.534), + w = 1 / 5, + ratio = 1 / 2, + }, + right = { + pos = Vector(228.512, -60.299, 42.042), + w = 1 / 5, + ratio = 1 / 2, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_bus', V ) + +local light_table = { + L_HeadLampPos = Vector(239,41,-15), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(239,-41,-15), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-266.5,50.3,9.8), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-266.5,-50.3,9.8), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(239,41,-15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(239,-41,-15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(222.4,35,27.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(239,32,-15),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(239,23,-15),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(239,-32,-15),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(239,-23,-15),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(222.4,36,27.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-266.5,50.3,9.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-266.5,-50.3,9.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-266.5,50.3,3.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-266.5,-50.3,3.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(239,49.1,-15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-266.5,50.3,-2.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(222,37.5,26.9), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(239,-49.1,-15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-266.5,-50.3,-2.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(222,35,26.9), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [10] = '', + [6] = '' + }, + Brake = { + [9] = '', + [10] = '', + [6] = 'models/gta4/vehicles/bus/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/bus/detail2_on', + [10] = '', + [6] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/bus/detail2_on', + [10] = '', + [6] = 'models/gta4/vehicles/bus/detail2_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/bus/detail2_on', + [10] = 'models/gta4/vehicles/bus/detail2_on', + [6] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/bus/detail2_on', + [10] = 'models/gta4/vehicles/bus/detail2_on', + [6] = 'models/gta4/vehicles/bus/detail2_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/bus/detail2_on' + }, + right = { + [7] = 'models/gta4/vehicles/bus/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_bus', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cabby.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cabby.lua new file mode 100644 index 0000000..d65c28a --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cabby.lua @@ -0,0 +1,1231 @@ +local V = { + Name = 'Cabby', + Model = 'models/octoteam/vehicles/cabby.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 1550, + Trunk = { 45 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_cabby', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(89,89,1)} + -- CarCols[2] = {REN.GTA4ColorTable(89,1,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/cabby_wheel.mdl', + + CustomWheelPosFL = Vector(63,33,-19), + CustomWheelPosFR = Vector(63,-33,-19), + CustomWheelPosRL = Vector(-63,33,-19), + CustomWheelPosRR = Vector(-63,-33,-19), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 34, + + SeatOffset = Vector(6,-19.5,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(15,-18,-13), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,20,-13), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-27,-20,-13), + ang = Angle(0,-90,15) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104,27,-20), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 30000, + FrontDamping = 900, + FrontRelativeDamping = 900, + + RearHeight = 8, + RearConstant = 30000, + RearDamping = 900, + RearRelativeDamping = 900, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 550, + + TurnSpeed = 4, + + MaxGrip = 46, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 38, + PowerbandStart = 1200, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,39,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + AirFriction = -40, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/minivan_idle.wav', + + snd_low = 'octoteam/vehicles/minivan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/minivan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/minivan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/minivan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/minivan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/taxi_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(37.423, 18.793, 12.673), ang = Angle(-0.0, -90.0, 73.2) }, + Radio = { pos = Vector(41.925, 0.023, 15.373), ang = Angle(-13.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(106.977, -0.005, -15.061), ang = Angle(1.9, 0.0, 0.0) }, + Back = { pos = Vector(-102.730, -0.001, 3.212), ang = Angle(1.9, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(22.466, 0.000, 31.690), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(40.628, 43.190, 16.104), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(40.656, -43.068, 16.149), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_cabby', V ) + +local light_table = { + L_HeadLampPos = Vector(95,29,-2.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-29,-2.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101,36,3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101,-36,3), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(95,29,-2.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-29,-2.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(39,18.5,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,29,-2.6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-29,-2.6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(39,19,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-101,36,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-36,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-101,36,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-36,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-101,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-101,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(98.0, -14.9, -0.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(98.0, 14.9, -0.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(98.0, -11.0, -0.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(98.0, 11.0, -0.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-104.2, -8.8, 3.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-104.2, 8.8, 3.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-84.3, -34.9, 19.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-84.3, 34.9, 19.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(40.8, -40.7, 13.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(40.8, 40.7, 13.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(0.1, -11.5, 43.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 45, + Colors = { + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(0.1, 11.5, 43.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 45, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(21.2, -22, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(21.2, -18, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(21.2, -14, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(21.2, -10, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(21.2, 22, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(21.2, 18, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(21.2, 14, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(21.2, 10, 33.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-75.5, -22, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-75.5, -18, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-75.5, -14, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-75.5, -10, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-75.5, 22, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-75.5, 18, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-75.5, 14, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-75.5, 10, 36.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,36,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-101,36,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,19.5,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(86,-36,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-101,-36,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,18,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [12] = '', + [8] = '' + }, + Brake = { + [7] = '', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [8] = '' + }, + Reverse = { + [7] = '', + [12] = '', + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + Brake_Reverse = { + [7] = '', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [8] = '' + }, + Brake = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [8] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [8] = '' + }, + Brake = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [8] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_cabby', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cavalcade.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cavalcade.lua new file mode 100644 index 0000000..c5cf2d5 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cavalcade.lua @@ -0,0 +1,433 @@ +local V = { + Name = 'Cavalcade', + Model = 'models/octoteam/vehicles/cavalcade.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 3500.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_cavalcade', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(13,133,91)} + -- CarCols[2] = {REN.GTA4ColorTable(0,133,0)} + -- CarCols[3] = {REN.GTA4ColorTable(0,133,4)} + -- CarCols[4] = {REN.GTA4ColorTable(1,133,9)} + -- CarCols[5] = {REN.GTA4ColorTable(6,133,63)} + -- CarCols[6] = {REN.GTA4ColorTable(40,133,27)} + -- CarCols[7] = {REN.GTA4ColorTable(57,133,51)} + -- CarCols[8] = {REN.GTA4ColorTable(64,133,63)} + -- CarCols[9] = {REN.GTA4ColorTable(85,133,118)} + -- CarCols[10] = {REN.GTA4ColorTable(88,133,87)} + -- CarCols[11] = {REN.GTA4ColorTable(98,133,91)} + -- CarCols[12] = {REN.GTA4ColorTable(104,133)} + -- CarCols[13] = {REN.GTA4ColorTable(103,2,133)} + -- CarCols[14] = {REN.GTA4ColorTable(21,133,72)} + -- CarCols[15] = {REN.GTA4ColorTable(22,133,72)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(215,215,215), + }, + + CustomWheelModel = 'models/octoteam/vehicles/cavalcade_wheel.mdl', + + CustomWheelPosFL = Vector(64,34,-18), + CustomWheelPosFR = Vector(64,-34,-18), + CustomWheelPosRL = Vector(-63,34,-18), + CustomWheelPosRR = Vector(-63,-34,-18), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-18,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-20,-7), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-33,20,-7), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-33,-20,-7), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-99.7,33.4,-17.7), + ang = Angle(-100,-70,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 32000, + FrontDamping = 1000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 1000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 100, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 135.0, + PowerbandStart = 2000, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-79,37,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 110, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/cavalcade_idle.wav', + + snd_low = 'octoteam/vehicles/cavalcade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/cavalcade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/cavalcade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/cavalcade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/cavalcade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/cavalcade_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.15, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_cavalcade', V ) + +local V2 = {} +V2.Name = 'Roman\'s Cavalcade' +V2.Model = 'models/octoteam/vehicles/cavalcade.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,20) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Большие' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('0100') + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(19,133,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +list.Set('simfphys_vehicles', 'sim_fphys_gta4_rom2', V2 ) + +local V3 = {} +V3.Name = 'Spanish Lords Cavalcade' +V3.Model = 'models/octoteam/vehicles/cavalcade.mdl' +V3.Class = 'gmod_sent_vehicle_fphysics_base' +V3.Category = 'Доброград - Особые' +V3.SpawnOffset = Vector(0,0,20) +V3.SpawnAngleOffset = 90 +V3.NAKGame = 'Доброград' +V3.NAKType = 'Большие' + +local V3Members = {} +for k,v in pairs(V.Members) do + V3Members[k] = v +end +V3.Members = V3Members +V3.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('0111') + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(34,127,28)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V3.Members.ModelInfo = { + WheelColor = Color(215,142,16), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_cav2', V3 ) + +local light_table = { + L_HeadLampPos = Vector(90,33,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(90,-33,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-114,29,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-114,-29,4), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(90,33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(90,-33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(29.7,18,17.2), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,26,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-26,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29.7,19,17.2), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(97,32,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,21,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-32,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-21,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-106,34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-106,-34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-106,34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-106,-34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-106,33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-106,-33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(89,33,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-106,34,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.7,20,17.2), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(89,-33,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-106,-34,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.7,17,17.2), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [8] = '', + [13] = '' + }, + Reverse = { + [7] = '', + [8] = '', + [13] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [8] = '', + [13] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [8] = '', + [13] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [8] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [13] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [8] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [13] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_cavalcade', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_chavos.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_chavos.lua new file mode 100644 index 0000000..700b091 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_chavos.lua @@ -0,0 +1,395 @@ +local V = { + Name = 'Chavos', + Model = 'models/octoteam/vehicles/chavos.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1550.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_chavos', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(1,0,7)} + -- CarCols[2] = {REN.GTA4ColorTable(10,0,10)} + -- CarCols[3] = {REN.GTA4ColorTable(33,33,27)} + -- CarCols[4] = {REN.GTA4ColorTable(45,0,30)} + -- CarCols[5] = {REN.GTA4ColorTable(49,49,50)} + -- CarCols[6] = {REN.GTA4ColorTable(50,49,51)} + -- CarCols[7] = {REN.GTA4ColorTable(57,0,58)} + -- CarCols[8] = {REN.GTA4ColorTable(64,64,63)} + -- CarCols[9] = {REN.GTA4ColorTable(68,68,43)} + -- CarCols[10] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[11] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[12] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[13] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[14] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/chavos_wheel.mdl', + + CustomWheelPosFL = Vector(56,30,-3), + CustomWheelPosFR = Vector(56,-30,-3), + CustomWheelPosRL = Vector(-60,30,-3), + CustomWheelPosRR = Vector(-60,-30,-3), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-11,-15,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-15,-7), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-33,15,-7), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-33,-15,-7), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-92,-19,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 145.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-62,-33,23), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/sultan_idle.wav', + + snd_low = 'octoteam/vehicles/sultan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/sultan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/sultan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/sultan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/sultan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sultanrs_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.18, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_chavos', V ) + +local light_table = { + L_HeadLampPos = Vector(85,26,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(85,-26,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-89,29,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-89,-29,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(85,26,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(85,-26,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(19.5,22.5,21), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(87.5,18,8.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(87.5,-18,8.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(19.5,22.5,22), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(88,25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(88,-25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-89,29,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-89,-29,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-89,22,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-89,-22,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-89,26.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-89,-26.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(81,30.5,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-89,29,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(20,19,24), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(81,-30.5,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-89,-29,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(20,14,24), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [7] = '', + [12] = '', + [11] = '', + }, + Brake = { + [8] = '', + [7] = '', + [12] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [11] = '', + }, + Reverse = { + [8] = '', + [7] = '', + [12] = '', + [11] = 'models/gta4/vehicles/chavos/chavos_lights_on', + }, + Brake_Reverse = { + [8] = '', + [7] = '', + [12] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [11] = 'models/gta4/vehicles/chavos/chavos_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = '', + [12] = '', + [11] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = '', + [12] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [11] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = '', + [12] = '', + [11] = 'models/gta4/vehicles/chavos/chavos_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = '', + [12] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [11] = 'models/gta4/vehicles/chavos/chavos_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [12] = '', + [11] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [12] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [11] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [12] = '', + [11] = 'models/gta4/vehicles/chavos/chavos_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [7] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [12] = 'models/gta4/vehicles/chavos/chavos_lights_on', + [11] = 'models/gta4/vehicles/chavos/chavos_lights_on', + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/chavos/chavos_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/chavos/chavos_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_chavos', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cognoscenti.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cognoscenti.lua new file mode 100644 index 0000000..071a901 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_cognoscenti.lua @@ -0,0 +1,402 @@ +local V = { + Name = 'Cognoscenti', + Model = 'models/octoteam/vehicles/cognoscenti.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1900, + Trunk = { 40 }, + + EnginePos = Vector(80,0,10), + + LightsTable = 'gta4_cognoscenti', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[2] = {REN.GTA4ColorTable(0,1,8)} + -- CarCols[3] = {REN.GTA4ColorTable(1,0,8)} + -- CarCols[4] = {REN.GTA4ColorTable(3,1,8)} + -- CarCols[5] = {REN.GTA4ColorTable(5,6,8)} + -- CarCols[6] = {REN.GTA4ColorTable(6,6,8)} + -- CarCols[7] = {REN.GTA4ColorTable(33,0,8)} + -- CarCols[8] = {REN.GTA4ColorTable(52,0,54)} + -- CarCols[9] = {REN.GTA4ColorTable(85,85,84)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/cognoscenti_wheel.mdl', + + CustomWheelPosFL = Vector(83,34,-11), + CustomWheelPosFR = Vector(83,-34,-11), + CustomWheelPosRL = Vector(-80,34,-11), + CustomWheelPosRR = Vector(-80,-34,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 40, + + SeatOffset = Vector(7,-20,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(17,-20,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-60,17,-10), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-60,-17,-10), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-128,23,-8), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-128,-23,-8), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 36000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 7, + RearConstant = 36000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4.5, + + MaxGrip = 58, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 50, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-96,-36,16), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/admiral_idle.wav', + + snd_low = 'octoteam/vehicles/admiral_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/admiral_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/admiral_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/admiral_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/admiral_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(41.097, 19.298, 15.755), ang = Angle(-0.0, -90.0, 74.7) }, + Radio = { pos = Vector(43.211, -0.006, 7.809), ang = Angle(8.6, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(123.235, 0.006, -9.527), ang = Angle(4.6, -0.0, -0.0) }, + Back = { pos = Vector(-129.770, 0.002, 11.074), ang = Angle(-23.2, -180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(21.262, 0.009, 31.857), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(34.439, 37.977, 22.139), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(35.405, -38.451, 22.029), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_cognoscenti', V ) + +local light_table = { + L_HeadLampPos = Vector(110,32,2), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(110,-32,2), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-127,27,7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-127,-27,7), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(110,32,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(110,-32,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(113,24,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(113,-24,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + +--[[ { + pos = Vector(41,28,14), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(110,32,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(110,-32,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(113,24,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(113,-24,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(41,28,15), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(117,27.5,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(117,-27.5,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-127,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-127,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-127,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-127,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-130,14,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-130,-14,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(109,32,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-122,30,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(42,23,17), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(109,-32,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-122,-30,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(42,16,17), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [10] = '', + [13] = '', + }, + Brake = { + [7] = '', + [10] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [13] = '', + }, + Reverse = { + [7] = '', + [10] = '', + [13] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + }, + Brake_Reverse = { + [7] = '', + [10] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [13] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [10] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [13] = '', + }, + Reverse = { + [7] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [10] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [13] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [10] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [13] = '', + }, + Reverse = { + [7] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [10] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + [13] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/cognoscenti/cognoscenti_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_cognoscenti', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_comet.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_comet.lua new file mode 100644 index 0000000..891eb97 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_comet.lua @@ -0,0 +1,414 @@ +local V = { + Name = 'Comet', + Model = 'models/octoteam/vehicles/comet.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1450, + Trunk = { 25 }, + + EnginePos = Vector(-60,0,10), + + LightsTable = 'gta4_comet', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,63)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,12)} + -- CarCols[3] = {REN.GTA4ColorTable(33,33,27)} + -- CarCols[4] = {REN.GTA4ColorTable(46,46,127)} + -- CarCols[5] = {REN.GTA4ColorTable(6,6,8)} + -- CarCols[6] = {REN.GTA4ColorTable(49,49,59)} + -- CarCols[7] = {REN.GTA4ColorTable(127,127,123)} + -- CarCols[8] = {REN.GTA4ColorTable(62,62,63)} + -- CarCols[9] = {REN.GTA4ColorTable(22,22,64)} + -- CarCols[10] = {REN.GTA4ColorTable(56,56,59)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/comet_wheel.mdl', + + CustomWheelPosFL = Vector(50,30,-10), + CustomWheelPosFR = Vector(50,-30,-10), + CustomWheelPosRL = Vector(-49,34,-10), + CustomWheelPosRR = Vector(-49,-34,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,-5), + + CustomSteerAngle = 40, + + SeatOffset = Vector(-16,-17,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-2,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-88,20,-13), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-88,-20,-13), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 38000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 6, + RearConstant = 38000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + CounterSteeringMul = 0.95, + + MaxGrip = 78, + Efficiency = 1.1, + GripOffset = -4, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 70, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = true, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-60,-37,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 55, + + PowerBias = 0.65, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/infernus_idle.wav', + + snd_low = 'octoteam/vehicles/infernus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/infernus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/infernus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/infernus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/infernus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.45, + Gears = {-0.12,0,0.12,0.21,0.3,0.45,0.6}, + + Dash = { pos = Vector(15.366, 15.588, 8.8), ang = Angle(-0.0, -90.0, 74.9) }, + Radio = { pos = Vector(20.677, -0.027, 4.173), ang = Angle(-10.3, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(90.552, 0.003, -8.097), ang = Angle(9.9, 0.0, -0.0) }, + Back = { pos = Vector(-91.049, -0.002, -6.423), ang = Angle(2.4, -180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(9.557, 0.037, 23.938), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(16.511, 33.585, 13.793), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(16.959, -32.668, 14.338), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_comet', V ) + +local light_table = { + L_HeadLampPos = Vector(70,28,4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(70,-28,4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-86,23,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-86,-23,5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(70,28,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(70,-28,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(71,24,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,100), + }, + { + pos = Vector(71,-24,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,100), + }, + +--[[ { + pos = Vector(18,22,9), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(71,24,0),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(71,-24,0),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(18,22,10), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(82,21,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(82,-21,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-86,23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,-23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-87,23,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-87,-23,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-86,20.5,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-86,-20.5,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(71,28,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-86,26,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(18,21,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(71,-28,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-86,-26,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(18,11,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [9] = '', + [11] = '' + }, + Brake = { + [8] = '', + [9] = 'models/gta4/vehicles/comet/comet_lights_on', + [11] = '' + }, + Reverse = { + [8] = '', + [9] = '', + [11] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + Brake_Reverse = { + [8] = '', + [9] = 'models/gta4/vehicles/comet/comet_lights_on', + [11] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = 'models/gta4/vehicles/comet/comet_lights_on', + [11] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = 'models/gta4/vehicles/comet/comet_lights_on', + [11] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = 'models/gta4/vehicles/comet/comet_lights_on', + [11] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/comet/comet_lights_on', + [9] = 'models/gta4/vehicles/comet/comet_lights_on', + [11] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/comet/comet_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_comet', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_coquette.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_coquette.lua new file mode 100644 index 0000000..812c4ab --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_coquette.lua @@ -0,0 +1,388 @@ +local V = { + Name = 'Coquette', + Model = 'models/octoteam/vehicles/coquette.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1600.0, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_coquette', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,50)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,8)} + -- CarCols[3] = {REN.GTA4ColorTable(11,11,92)} + -- CarCols[4] = {REN.GTA4ColorTable(13,13,98)} + -- CarCols[5] = {REN.GTA4ColorTable(33,33,89)} + -- CarCols[6] = {REN.GTA4ColorTable(34,34,27)} + -- CarCols[7] = {REN.GTA4ColorTable(49,49,53)} + -- CarCols[8] = {REN.GTA4ColorTable(53,53,58)} + -- CarCols[9] = {REN.GTA4ColorTable(57,57,60)} + -- CarCols[10] = {REN.GTA4ColorTable(113,35,113)} + -- CarCols[11] = {REN.GTA4ColorTable(74,74,63)} + -- CarCols[12] = {REN.GTA4ColorTable(80,80,84)} + -- CarCols[13] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[14] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[15] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[16] = {REN.GTA4ColorTable(13,13,91)} + -- CarCols[17] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/coquette_wheel.mdl', + + CustomWheelPosFL = Vector(54,33,-6), + CustomWheelPosFR = Vector(54,-33,-6), + CustomWheelPosRL = Vector(-54,34,-6), + CustomWheelPosRR = Vector(-54,-34,-6), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-27,-19,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-13,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-88,22,-4), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-88,-22,-4), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 95, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 160.0, + PowerbandStart = 1500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-61,37,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/coquette_idle.wav', + + snd_low = 'octoteam/vehicles/coquette_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/coquette_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/coquette_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/coquette_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/coquette_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.25, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_coquette', V ) + +local light_table = { + L_HeadLampPos = Vector(71,27,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(71,-27,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-90,19,13), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-90,-19,13), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(71,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(71,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(71,22,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(71,-22,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(9,10,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(71,22,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(71,-22,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(71,27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(71,-27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(9,10,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-90,19,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-90,-19,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-88,27,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-88,-27,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-90,0,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-90,19,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-90,-19,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(72,31,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-88,27,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(9,20.7,13.2), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(72,-31,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-88,-27,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(9,13.4,13.2), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [10] = '', + [9] = '' + }, + Brake = { + [5] = '', + [10] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [9] = '' + }, + Reverse = { + [5] = '', + [10] = '', + [9] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + Brake_Reverse = { + [5] = '', + [10] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [9] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = '', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = '', + [9] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [9] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = '', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = '', + [9] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [10] = 'models/gta4/vehicles/coquette/coquette_lights_on', + [9] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/coquette/coquette_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_coquette', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_df8.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_df8.lua new file mode 100644 index 0000000..0fd5216 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_df8.lua @@ -0,0 +1,407 @@ +local V = { + Name = 'DF8-90', + Model = 'models/octoteam/vehicles/df8.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1450, + Trunk = { 25 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_df8', + + OnSpawn = function(ent) + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/df8_wheel.mdl', + + CustomWheelPosFL = Vector(64,32,-9), + CustomWheelPosFR = Vector(64,-32,-9), + CustomWheelPosRL = Vector(-64,32,-9), + CustomWheelPosRR = Vector(-64,-32,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-20,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-20,-12), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-35,20,-12), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-35,-20,-12), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104,20,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-104,-20,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 27000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 7, + RearConstant = 27000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 46, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 39, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,-34,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 40, + + AirFriction = -60, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/e109_idle.wav', + + snd_low = 'octoteam/vehicles/e109_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/e109_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/e109_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/e109_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/e109_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/banshee_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(22.264, 18.693, 15.785), ang = Angle(-0.0, -90.0, 84.5) }, + Radio = { pos = Vector(26.557, -1.965, 10.618), ang = Angle(-7.7, 159.6, 2.4) }, + Plates = { + Front = { pos = Vector(104.050, -0.006, -5.885), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-107.377, -0.000, -2.960), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(8.188, -0.000, 28.153), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(27.104, 39.941, 20.285), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(27.357, -39.483, 20.157), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_df8', V ) + +local light_table = { + L_HeadLampPos = Vector(90,27,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(90,-27,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-105,25,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-105,-25,9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(90,27,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(90,-27,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,21,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,-21,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26,27,16), + material = 'gta4/dash_lowbeam', + size = 1, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,21,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-21,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,27,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-27,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26,27,15), + material = 'gta4/dash_highbeam', + size = 1, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(97,22,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-22,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-105,25,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105,-25,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-105,25,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105,-25,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-105,13,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-105,-13,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,32,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-101,32,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,21.25,16), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(91,-32,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-101,-32,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,16.13,16), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [9] = '', + [13] = '' + }, + Brake = { + [8] = '', + [9] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [13] = '' + }, + Reverse = { + [8] = '', + [9] = '', + [13] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + Brake_Reverse = { + [8] = '', + [9] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [13] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = '', + [13] = '' + }, + Brake = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [13] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = '', + [13] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [13] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = '', + [13] = '' + }, + Brake = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [13] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = '', + [13] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [9] = 'models/gta4/vehicles/df8/df8_90_lights_on', + [13] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/df8/df8_90_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_df8', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_dilettante.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_dilettante.lua new file mode 100644 index 0000000..3fa1bb2 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_dilettante.lua @@ -0,0 +1,810 @@ +local V = { + Name = 'Dilettante', + Model = 'models/octoteam/vehicles/dilettante.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1250, + Trunk = { 40 }, + + EnginePos = Vector(65,0,5), + + LightsTable = 'gta4_dilettante', + + OnSpawn = function(ent) + REN.GTA4SimfphysInit(ent, 0, 0) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/dilettante_wheel.mdl', + + CustomWheelPosFL = Vector(57,30,-7), + CustomWheelPosFR = Vector(57,-30,-7), + CustomWheelPosRL = Vector(-56,30,-7), + CustomWheelPosRR = Vector(-56,-30,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-18,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(10,-17,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-34,15,-9), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-34,-15,-9), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-90,21,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-90,-21,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 9, + FrontConstant = 24000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 9, + RearConstant = 24000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 35, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 31, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-67,32,16), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + AirFriction = -40, + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/dilettante_idle.wav', + BrakeSqueal = true, + + snd_low = 'octoteam/vehicles/dilettante_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/dilettante_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/dilettante_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/dilettante_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/dilettante_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/dilettante_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(29.153, 17.036, 18.406), ang = Angle(-0.0, -90.0, 71.9) }, + Radio = { pos = Vector(30.819, 0.014, 8.560), ang = Angle(-19.1, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(92.812, 0.011, -5.637), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-87.219, -0.010, 14.971), ang = Angle(-9.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(16.781, 0.002, 33.368), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(32.102, 36.223, 20.604), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(30.637, -36.015, 20.661), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_dilettante', V ) + +local light_table = { + L_HeadLampPos = Vector(83,25,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(83,-25,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-86,20,18), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-86,-20,18), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(83,25,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(83,-25,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(29.1,24,18), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(84.4,18.8,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(84.4,-18.8,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29.1,24,17), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-86,20,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,-20,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-87,13,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-87,-13,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-87,0,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(32.1, 34.7, 18.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.03 + }, + { + pos = Vector(32.1, -34.7, 18.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.025 + }, + { + pos = Vector(-47.6, 22.2, 34.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-47.6, -22.2, 34.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(86.1, -7.8, 4.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(86.1, 7.8, 4.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.034 + }, + { + pos = Vector(17.5, -21.2, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(17.5, -17, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.034 + }, + { + pos = Vector(17.5, -13, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.033 + }, + { + pos = Vector(17.5, -9, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.032 + }, + { + pos = Vector(17.5, 21.2, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(17.5, 17, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.034 + }, + { + pos = Vector(17.5, 13, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.033 + }, + { + pos = Vector(17.5, 9, 34.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.032 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(78,29.6,6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-85,28,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.5,20,20), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(78,-29.6,6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-85,-28,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.5,15,20), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [11] = '', + [8] = '', + [10] = '' + }, + Brake = { + [5] = '', + [11] = '', + [8] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [10] = '' + }, + Reverse = { + [5] = '', + [11] = '', + [8] = '', + [10] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + Brake_Reverse = { + [5] = '', + [11] = '', + [8] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [10] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = '', + [8] = '', + [10] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [10] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = '', + [8] = '', + [10] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [10] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [8] = '', + [10] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [8] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [10] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [8] = '', + [10] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [11] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [8] = 'models/gta4/vehicles/dilettante/dilettante_lights_on', + [10] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/dilettante/dilettante_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_dilettante', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_dukes.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_dukes.lua new file mode 100644 index 0000000..3378762 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_dukes.lua @@ -0,0 +1,355 @@ +local V = { + Name = 'Dukes', + Model = 'models/octoteam/vehicles/dukes.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_dukes', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,133,32)} + -- CarCols[2] = {REN.GTA4ColorTable(0,133,62)} + -- CarCols[3] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[4] = {REN.GTA4ColorTable(1,133,1)} + -- CarCols[5] = {REN.GTA4ColorTable(5,133,8)} + -- CarCols[6] = {REN.GTA4ColorTable(11,133,8)} + -- CarCols[7] = {REN.GTA4ColorTable(31,133,30)} + -- CarCols[8] = {REN.GTA4ColorTable(36,133,27)} + -- CarCols[9] = {REN.GTA4ColorTable(52,133,50)} + -- CarCols[10] = {REN.GTA4ColorTable(62,133,53)} + -- CarCols[11] = {REN.GTA4ColorTable(82,133,63)} + -- CarCols[12] = {REN.GTA4ColorTable(80,133,65)} + -- CarCols[13] = {REN.GTA4ColorTable(65,133,65)} + -- CarCols[14] = {REN.GTA4ColorTable(61,133,61)} + -- CarCols[15] = {REN.GTA4ColorTable(93,133,93)} + -- CarCols[16] = {REN.GTA4ColorTable(104,133,103)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/dukes_wheel.mdl', + + CustomWheelPosFL = Vector(62,33,-12), + CustomWheelPosFR = Vector(62,-33,-12), + CustomWheelPosRL = Vector(-63,32,-12), + CustomWheelPosRR = Vector(-63,-32,-12), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-15,-17,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-34,39,-12.7), + ang = Angle(-90,-55,0), + }, + { + pos = Vector(-41,36,-12.7), + ang = Angle(-90,-55,0), + }, + { + pos = Vector(-34,-39,-12.7), + ang = Angle(-90,55,0), + }, + { + pos = Vector(-41,-36,-12.7), + ang = Angle(-90,55,0), + }, + }, + + FrontHeight = 13, + FrontConstant = 18000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 13, + RearConstant = 18000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 73, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 15, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6500, + PeakTorque = 135.0, + PowerbandStart = 1500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-90,40,14), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/dukes_idle.wav', + + snd_low = 'octoteam/vehicles/vigero_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/vigero_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/vigero_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/vigero_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/vigero_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/dukes_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.21, + Gears = {-0.3,0,0.15,0.35,0.5,0.75} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_dukes', V ) + +local V2 = {} +V2.Name = 'Highway Reaper Dukes' +V2.Model = 'models/octoteam/vehicles/dukes.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,20) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Маслкары' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('0311' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(132,0,131)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_dukes2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(98,27.4,-1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(98,-27.4,-1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-118.2,25,4.7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-118.2,-25,4.7), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(98,27.4,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-27.4,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,21.1,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-21.1,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(98,27.4,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-27.4,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,21.1,-1),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-21.1,-1),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-118.2,25,4.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118.2,-25,4.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-118.2,19,4.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-118.2,-19,4.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + TurnBrakeLeft = { + { + pos = Vector(-118.2,13,4.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + TurnBrakeRight = { + { + pos = Vector(-118.2,-13,4.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [6] = '', + [9] = '' + }, + Brake = { + [5] = '', + [6] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [9] = '' + }, + Reverse = { + [5] = '', + [6] = '', + [9] = 'models/gta4/vehicles/dukes/dukes_lights_on' + }, + Brake_Reverse = { + [5] = '', + [6] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [9] = 'models/gta4/vehicles/dukes/dukes_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = '', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = '', + [9] = 'models/gta4/vehicles/dukes/dukes_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [9] = 'models/gta4/vehicles/dukes/dukes_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = '', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = '', + [9] = 'models/gta4/vehicles/dukes/dukes_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [6] = 'models/gta4/vehicles/dukes/dukes_lights_on', + [9] = 'models/gta4/vehicles/dukes/dukes_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_dukes', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_e109.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_e109.lua new file mode 100644 index 0000000..c252eea --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_e109.lua @@ -0,0 +1,371 @@ +local V = { + Name = 'Contender', + Model = 'models/octoteam/vehicles/e109.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2250, + Trunk = { + nil, + {50, 1, 1}, + {50, 1, 2}, + {50, 1, 4}, + }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_e109', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,3)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[3] = {REN.GTA4ColorTable(20,20,8)} + -- CarCols[4] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[5] = {REN.GTA4ColorTable(7,1,12)} + -- CarCols[6] = {REN.GTA4ColorTable(1,0,8)} + -- CarCols[7] = {REN.GTA4ColorTable(47,47,43)} + -- CarCols[8] = {REN.GTA4ColorTable(49,40,43)} + -- CarCols[9] = {REN.GTA4ColorTable(57,60,51)} + -- CarCols[10] = {REN.GTA4ColorTable(65,64,63)} + -- CarCols[11] = {REN.GTA4ColorTable(70,73,75)} + -- CarCols[12] = {REN.GTA4ColorTable(85,85,58)} + -- CarCols[13] = {REN.GTA4ColorTable(95,94,95)} + -- CarCols[14] = {REN.GTA4ColorTable(102,90,106)} + -- CarCols[15] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[16] = {REN.GTA4ColorTable(21,21,72 )} + -- CarCols[17] = {REN.GTA4ColorTable(22,22,72 )} + -- CarCols[18] = {REN.GTA4ColorTable(13,11,91 )} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/e109_wheel.mdl', + + CustomWheelPosFL = Vector(70,36,-10), + CustomWheelPosFR = Vector(70,-36,-10), + CustomWheelPosRL = Vector(-76,36,-10), + CustomWheelPosRR = Vector(-76,-36,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-12,-20,30), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-3,-20,-2), + ang = Angle(0,-90,10), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-108.4,36.6,-14.4), + ang = Angle(-100,-45,0), + }, + { + pos = Vector(-108.4,-36.6,-14.4), + ang = Angle(-100,45,0), + }, + }, + + FrontHeight = 9, + FrontConstant = 38000, + FrontDamping = 950, + FrontRelativeDamping = 950, + + RearHeight = 9, + RearConstant = 38000, + RearDamping = 950, + RearRelativeDamping = 950, + + FastSteeringAngle = 12, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3.5, + + MaxGrip = 60, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 34, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 46, + PowerbandStart = 1700, + PowerbandEnd = 4600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2.5, + + FuelFillPos = Vector(-33,40,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/e109_idle.wav', + + snd_low = 'octoteam/vehicles/e109_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/e109_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/e109_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/e109_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/e109_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/vigero_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.2,0.32,0.44,0.5}, + + Dash = { pos = Vector(22.741, 19.793, 23.546), ang = Angle(-0.0, -90.0, 72.3) }, + Radio = { pos = Vector(25.582, -0.017, 19.804), ang = Angle(-17.0, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(109.110, -0.003, -3.951), ang = Angle(2.2, 0.0, 0.0) }, + Back = { pos = Vector(-121.217, 0.000, -7.136), ang = Angle(7.8, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(13.758, -0.002, 40.761), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(20.287, 41.161, 31.411), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(20.633, -41.754, 31.598), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_e109', V ) + +local V2 = {} +V2.Name = 'Irish Mob Contender' +V2.Model = 'models/octoteam/vehicles/e109.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,20) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Большие' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('041' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(56,56,59)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_e109_2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(99,32,13), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(99,-32,13), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-120,39,15), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-120,-39,15), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(99,32,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(99,-32,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(23,20,22), + material = 'gta4/dash_lowbeam', + size = 1, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(100,26,13),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(100,-26,13),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,18,22), + material = 'gta4/dash_highbeam', + size = 1, + color = Color(255,57,50,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-120,39,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-120,-39,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-120,39,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-120,-39,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(97,36.6,14.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-120,39,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,22,22), + material = 'gta4/dash_left', + size = 1, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(97,-36.6,14.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-120,-39,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,16,22), + material = 'gta4/dash_right', + size = 1, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [6] = '', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/e109/e_109_lights_on', + [6] = '', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/e109/e_109_lights_on', + [6] = 'models/gta4/vehicles/e109/e_109_lights_on', + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/e109/e_109_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/e109/e_109_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_e109', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_emperor.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_emperor.lua new file mode 100644 index 0000000..e181be8 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_emperor.lua @@ -0,0 +1,395 @@ +local V = { + Name = 'Emperor', + Model = 'models/octoteam/vehicles/emperor.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1600.0, + Trunk = { 30 }, + + EnginePos = Vector(65,0,5), + + LightsTable = 'gta4_emperor', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(1,0,3)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,3 )} + -- CarCols[3] = {REN.GTA4ColorTable(7,0,7 )} + -- CarCols[4] = {REN.GTA4ColorTable(10,0,10)} + -- CarCols[5] = {REN.GTA4ColorTable(11,1,11)} + -- CarCols[6] = {REN.GTA4ColorTable(16,1,16)} + -- CarCols[7] = {REN.GTA4ColorTable(21,2,21)} + -- CarCols[8] = {REN.GTA4ColorTable(31,31,31 )} + -- CarCols[9] = {REN.GTA4ColorTable(48,38,48 )} + -- CarCols[10] = {REN.GTA4ColorTable(55,1,55)} + -- CarCols[11] = {REN.GTA4ColorTable(57,10,57)} + -- CarCols[12] = {REN.GTA4ColorTable(61,16,61)} + -- CarCols[13] = {REN.GTA4ColorTable(68,102,68)} + -- CarCols[14] = {REN.GTA4ColorTable(78,108,79)} + -- CarCols[15] = {REN.GTA4ColorTable(95,102,95)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/emperor_wheel.mdl', + + CustomWheelPosFL = Vector(63,34,-11), + CustomWheelPosFR = Vector(63,-34,-11), + CustomWheelPosRL = Vector(-65,34,-11), + CustomWheelPosRR = Vector(-65,-34,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-17.5,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-18,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-36,20,-13), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-36,-20,-13), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-112.7,-28,-11.7), + ang = Angle(-110,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 29000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 6, + RearConstant = 29000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 41, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-90,39,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + AirFriction = -65, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/stainer_idle.wav', + + snd_low = 'octoteam/vehicles/stainer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stainer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stainer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stainer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stainer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/emperor_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(22.464, 17.350, 13.474), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(28.947, 0.007, 8.110), ang = Angle(-0.0, 175.6, 0.0) }, + Plates = { + Front = { pos = Vector(100.350, -0.003, -8.412), ang = Angle(-0.8, 0.0, 0.0) }, + Back = { pos = Vector(-115.711, -0.008, 4.021), ang = Angle(-1.5, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(6.247, -0.006, 29.563), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(15.777, 41.186, 15.818), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(16.516, -41.216, 15.833), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_emperor', V ) + +local light_table = { + L_HeadLampPos = Vector(95,24,4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-24,4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-124,35,-2), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-124,-35,-2), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(95,24,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-24,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,32,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-32,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(95,24,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-24,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,32,4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-32,4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-124,35,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-124,-35,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-123.6,35,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-123.6,-35,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-116,9,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-116,-9,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(93,37.2,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-123,35,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(23.5,18.4,15.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(93,-37.2,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-123,-35,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(23.5,17.4,15.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [9] = '', + [12] = '' + }, + Brake = { + [4] = '', + [9] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = '' + }, + Reverse = { + [4] = '', + [9] = '', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + Brake_Reverse = { + [4] = '', + [9] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = '', + [12] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = '', + [12] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [9] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_emperor', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_emperor2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_emperor2.lua new file mode 100644 index 0000000..38df8f7 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_emperor2.lua @@ -0,0 +1,382 @@ +local V = { + Name = 'Emperor (Beater)', + Model = 'models/octoteam/vehicles/emperor2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2100.0, + + EnginePos = Vector(65,0,5), + + LightsTable = 'gta4_emperor2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,1)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(1,0,3)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,3 )} + -- CarCols[3] = {REN.GTA4ColorTable(7,0,7 )} + -- CarCols[4] = {REN.GTA4ColorTable(10,0,10)} + -- CarCols[5] = {REN.GTA4ColorTable(11,1,11)} + -- CarCols[6] = {REN.GTA4ColorTable(16,1,16)} + -- CarCols[7] = {REN.GTA4ColorTable(21,2,21)} + -- CarCols[8] = {REN.GTA4ColorTable(31,31,31 )} + -- CarCols[9] = {REN.GTA4ColorTable(48,38,48 )} + -- CarCols[10] = {REN.GTA4ColorTable(55,1,55)} + -- CarCols[11] = {REN.GTA4ColorTable(57,10,57)} + -- CarCols[12] = {REN.GTA4ColorTable(61,16,61)} + -- CarCols[13] = {REN.GTA4ColorTable(68,102,68)} + -- CarCols[14] = {REN.GTA4ColorTable(78,108,79)} + -- CarCols[15] = {REN.GTA4ColorTable(95,102,95)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + REN.GTA4BeaterInit(ent) + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Beater(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/emperor2_wheel.mdl', + + CustomWheelPosFL = Vector(63,34,-11), + CustomWheelPosFR = Vector(63,-34,-11), + CustomWheelPosRL = Vector(-65,34,-11), + CustomWheelPosRR = Vector(-65,-34,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-17.5,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-18,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-36,20,-13), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-36,-20,-13), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-112.7,-28,-11.7), + ang = Angle(-110,0,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-117,-26.5,-16), + ang = Angle(-110,20,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-116.7,18.7,-14.2), + ang = Angle(-110,-5,0), + OnBodyGroups = { + [1] = {2}, + } + }, + { + pos = Vector(-116.7,22.3,-14.2), + ang = Angle(-110,-5,0), + OnBodyGroups = { + [1] = {2}, + } + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 63, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 15, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4000, + PeakTorque = 130.0, + PowerbandStart = 1700, + PowerbandEnd = 3500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-90,39,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/stainer_idle.wav', + + snd_low = 'octoteam/vehicles/stainer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stainer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stainer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stainer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stainer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/emperor2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_emperor2', V ) + +local light_table = { + L_HeadLampPos = Vector(95,24,4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-24,4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-124,35,-2), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-124,-35,-2), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(95,24,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-24,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-32,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(95,24,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-24,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-32,4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-124,35,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-124,-35,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-123.6,35,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-123.6,-35,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-116,9,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-116,-9,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-123,35,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(23.5,18.4,15.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(93,-37.2,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-123,-35,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(23.5,17.4,15.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [13] = '', + [5] = '', + [12] = '' + }, + Brake = { + [13] = '', + [5] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = '' + }, + Reverse = { + [13] = '', + [5] = '', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + Brake_Reverse = { + [13] = '', + [5] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + on_lowbeam = { + Base = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = '', + [12] = '' + }, + Brake = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = '' + }, + Reverse = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = '', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + Brake_Reverse = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + on_highbeam = { + Base = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = '', + [12] = '' + }, + Brake = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = '' + }, + Reverse = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = '', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + Brake_Reverse = { + [13] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [5] = 'models/gta4/vehicles/emperor/emperor_lights_on', + [12] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/emperor/emperor_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_emperor2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_esperanto.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_esperanto.lua new file mode 100644 index 0000000..256ed46 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_esperanto.lua @@ -0,0 +1,408 @@ +local V = { + Name = 'Esperanto', + Model = 'models/octoteam/vehicles/esperanto.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1500.0, + Trunk = { 30 }, + + EnginePos = Vector(70,0,5), + + LightsTable = 'gta4_esperanto', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(20,20,18)} + -- CarCols[2] = {REN.GTA4ColorTable(16,16,15)} + -- CarCols[3] = {REN.GTA4ColorTable(7,7,15)} + -- CarCols[9] = {REN.GTA4ColorTable(1,1,8)} + -- CarCols[7] = {REN.GTA4ColorTable(26,26,8)} + -- CarCols[6] = {REN.GTA4ColorTable(34,34,33)} + -- CarCols[7] = {REN.GTA4ColorTable(40,40,39)} + -- CarCols[8] = {REN.GTA4ColorTable(50,50,51)} + -- CarCols[9] = {REN.GTA4ColorTable(52,52,51)} + -- CarCols[10] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[11] = {REN.GTA4ColorTable(65,65,68)} + -- CarCols[12] = {REN.GTA4ColorTable(82,82,80)} + -- CarCols[13] = {REN.GTA4ColorTable(97,97,97)} + -- CarCols[14] = {REN.GTA4ColorTable(111,111,110)} + -- CarCols[15] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[16] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[17] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[18] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[19] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/esperanto_wheel.mdl', + + CustomWheelPosFL = Vector(60,33,-10), + CustomWheelPosFR = Vector(60,-33,-10), + CustomWheelPosRL = Vector(-60,33,-10), + CustomWheelPosRR = Vector(-60,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-18,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-18,-12), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-36,18,-12), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-36,-18,-12), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-100,28.5,-11.7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-100,-28.5,-11.7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 28000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 8, + RearConstant = 28000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 39, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,36,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 40, + + AirFriction = -45, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/esperanto_idle.wav', + BrakeSqueal = true, + + snd_low = 'octoteam/vehicles/esperanto_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/esperanto_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/esperanto_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/esperanto_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/esperanto_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/esperanto_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(23, 17.759, 13.138), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(28.770, -0.514, 7.001), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(100.938, 0.003, -7.359), ang = Angle(-0.8, 0.0, 0.0) }, + Back = { pos = Vector(-99.935, -0.021, 3.340), ang = Angle(-20.5, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(7.453, 0.014, 29.380), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(24.050, 38.076, 16.854), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(23.120, -37.956, 16.929), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_esperanto', V ) + +local light_table = { + L_HeadLampPos = Vector(93,26,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-26,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101,33,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101,-33,4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(93,26,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(93,-26,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,18,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,-18,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(23,18,15), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(94,18,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(94,-18,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,26,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-26,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,18,14.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-101,33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-101,13,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-101,-13,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(94,18,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,19,15), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-101,21,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(94,-18,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,17,15), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-101,-21,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [7] = '', + [10] = '' + }, + Brake = { + [9] = '', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = '' + }, + Reverse = { + [9] = '', + [7] = '', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + Brake_Reverse = { + [9] = '', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_esperanto', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_faction.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_faction.lua new file mode 100644 index 0000000..b7a4e68 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_faction.lua @@ -0,0 +1,420 @@ +local V = { + Name = 'Faction', + Model = 'models/octoteam/vehicles/faction.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1200.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_faction', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[2] = {REN.GTA4ColorTable(2,0,4)} + -- CarCols[3] = {REN.GTA4ColorTable(6,0,4)} + -- CarCols[4] = {REN.GTA4ColorTable(8,1,8)} + -- CarCols[5] = {REN.GTA4ColorTable(11,1,8)} + -- CarCols[6] = {REN.GTA4ColorTable(25,6,8)} + -- CarCols[7] = {REN.GTA4ColorTable(42,42,34)} + -- CarCols[8] = {REN.GTA4ColorTable(47,47,34)} + -- CarCols[9] = {REN.GTA4ColorTable(52,0,52)} + -- CarCols[10] = {REN.GTA4ColorTable(57,1,57)} + -- CarCols[11] = {REN.GTA4ColorTable(61,1,61)} + -- CarCols[12] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[13] = {REN.GTA4ColorTable(76,76,63)} + -- CarCols[14] = {REN.GTA4ColorTable(78,78,65)} + -- CarCols[15] = {REN.GTA4ColorTable(86,86,68)} + -- CarCols[16] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[17] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[18] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[19] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[20] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/faction_wheel.mdl', + + CustomWheelPosFL = Vector(58,32,-7), + CustomWheelPosFR = Vector(58,-32,-7), + CustomWheelPosRL = Vector(-60,32,-7), + CustomWheelPosRR = Vector(-60,-32,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-13,-17,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-17,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-95,32,-9), + ang = Angle(-100,-45,0), + }, + { + pos = Vector(-95,-32,-9), + ang = Angle(-100,45,0), + }, + }, + + FrontHeight = 9, + FrontConstant = 23000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 8, + RearConstant = 23000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 550, + + TurnSpeed = 3.5, + + MaxGrip = 35, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 27, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-81,35,13), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 40, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/faction_idle.wav', + BrakeSqueal = true, + + snd_low = 'octoteam/vehicles/faction_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/faction_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/faction_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/faction_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/faction_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/faction_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(18.369, 16.982, 16.792), ang = Angle(-0.0, -90.0, 77.1) }, + Radio = { pos = Vector(24.152, -1.018, 12.339), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(103.757, -0.004, -6.021), ang = Angle(1.1, 0.0, -0.0) }, + Back = { pos = Vector(-105.127, 0.002, 9.425), ang = Angle(-11.7, -180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(5.862, -0.003, 30.826), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(16.260, 36.051, 22.960), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(15.697, -35.998, 22.630), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_faction', V ) + +local light_table = { + L_HeadLampPos = Vector(96,26.5,6.7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(96,-26.5,6.7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-105,25,10), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-105,-25,10), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(96,26.5,6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,-26.5,6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,20.3,6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,230,100,255), + }, + { + pos = Vector(96,-20.3,6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,230,100,255), + }, + +--[[ { + pos = Vector(18.4,24.6,17), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(96,26.5,6.7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,-26.5,6.7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(18.2,24.6,16), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-105,25,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105,-25,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-105,16,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105,-16,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-110,24.6,1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-110,-24.6,1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(100,23,-5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(80.2,34.5,-2.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-104,34,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(18.7,24.6,18.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(100,-23,-5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(80.2,-34.5,-2.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-104,-34,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(18.7,9.1,18.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [11] = '', + [8] = '', + }, + Brake = { + [5] = '', + [11] = 'models/gta4/vehicles/faction/faction_lights_on', + [8] = '', + }, + Reverse = { + [5] = '', + [11] = '', + [8] = 'models/gta4/vehicles/faction/faction_lights_on', + }, + Brake_Reverse = { + [5] = '', + [11] = 'models/gta4/vehicles/faction/faction_lights_on', + [8] = 'models/gta4/vehicles/faction/faction_lights_on', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = '', + [8] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = 'models/gta4/vehicles/faction/faction_lights_on', + [8] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/faction/faction_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = 'models/gta4/vehicles/faction/faction_lights_on', + [8] = 'models/gta4/vehicles/faction/faction_lights_on', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = '', + [8] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = 'models/gta4/vehicles/faction/faction_lights_on', + [8] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/faction/faction_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/faction/faction_lights_on', + [11] = 'models/gta4/vehicles/faction/faction_lights_on', + [8] = 'models/gta4/vehicles/faction/faction_lights_on', + }, + }, + turnsignals = { + left = { + [4] = 'models/gta4/vehicles/faction/faction_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/faction/faction_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_faction', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fbi.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fbi.lua new file mode 100644 index 0000000..94c49c8 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fbi.lua @@ -0,0 +1,531 @@ +local V = { + Name = 'FIB Buffalo', + Model = 'models/octoteam/vehicles/fbi.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 1500, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_fbi', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,8)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/fbi_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(67,32,-12), + CustomWheelPosFR = Vector(67,-32,-12), + CustomWheelPosRL = Vector(-61,32,-12), + CustomWheelPosRR = Vector(-61,-32,-12), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-17,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-30,18,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-30,-18,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-102,22,-14), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-102,-22,-14), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 5.5, + FrontConstant = 33000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 5.5, + RearConstant = 33000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 20, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 5.5, + CounterSteeringMul = 0.85, + + MaxGrip = 62, + Efficiency = 1.1, + GripOffset = 0, + BrakePower = 42, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6500, + PeakTorque = 68, + PowerbandStart = 1200, + PowerbandEnd = 6200, + Turbocharged = true, + Supercharged = true, + DoNotStall = false, + PowerBoost = 1.3, + + FuelFillPos = Vector(-61,37,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/buffalo_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/buffalo_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.45, + Gears = {-0.12,0,0.12,0.22,0.3,0.45,0.6}, + + Dash = { pos = Vector(22.469, 17.686, 13.903), ang = Angle(-0.0, -90.0, 77.0) }, + Radio = { pos = Vector(28.962, 0.010, 4.947), ang = Angle(-19.6, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(103.097, -0.016, -12.006), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-99.164, 0.008, 7.328), ang = Angle(-22.1, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(15.675, 0.030, 25.598), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(29.291, 36.604, 16.617), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.574, -37.075, 16.650), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_fbi', V ) + +local light_table = { + L_HeadLampPos = Vector(91,29,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(91,-29,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-94,35,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-94,-35,9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(91,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26.5,17,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,29,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-29,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26.5,18,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,30,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-30,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-94,35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-94,-35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-96,30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,30,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-30,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(101,17.5,-1.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.05 + }, + { + pos = Vector(101,-17.5,-1.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,155,255,50), + Color(255,155,255,100), + -- + Color(255,155,255,150), + Color(255,155,255,255), + Color(255,155,255,150), + -- + Color(255,155,255,100), + Color(255,155,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.05 + }, + { + pos = Vector(-68,23,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,155,255,50), + Color(255,155,255,100), + -- + Color(255,155,255,150), + Color(255,155,255,255), + Color(255,155,255,150), + -- + Color(255,155,255,100), + Color(255,155,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.05 + }, + { + pos = Vector(-68,-23,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.05 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95,31,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,31,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.5,19,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(95,-31,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,-31,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.5,16,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [10] = '', + [8] = '' + }, + Brake = { + [7] = '', + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [8] = '' + }, + Reverse = { + [7] = '', + [10] = '', + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [7] = '', + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = '', + [8] = '' + }, + Brake = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [8] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = '', + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = '', + [8] = '' + }, + Brake = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [8] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = '', + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_fbi', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feltzer.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feltzer.lua new file mode 100644 index 0000000..4b7f3f0 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feltzer.lua @@ -0,0 +1,411 @@ +local V = { + Name = 'Feltzer', + Model = 'models/octoteam/vehicles/feltzer.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1550, + Trunk = { 30 }, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_feltzer', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2)..'1' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(13,13,65)} + -- CarCols[2] = {REN.GTA4ColorTable(62,62,63)} + -- CarCols[3] = {REN.GTA4ColorTable(0,0,63)} + -- CarCols[4] = {REN.GTA4ColorTable(1,1,64)} + -- CarCols[5] = {REN.GTA4ColorTable(3,3,12)} + -- CarCols[6] = {REN.GTA4ColorTable(6,6,16)} + -- CarCols[7] = {REN.GTA4ColorTable(7,7,15)} + -- CarCols[8] = {REN.GTA4ColorTable(15,15,17)} + -- CarCols[9] = {REN.GTA4ColorTable(28,28,28)} + -- CarCols[10] = {REN.GTA4ColorTable(40,40,28)} + -- CarCols[11] = {REN.GTA4ColorTable(49,49,50)} + -- CarCols[12] = {REN.GTA4ColorTable(50,50,51)} + -- CarCols[13] = {REN.GTA4ColorTable(57,57,55)} + -- CarCols[14] = {REN.GTA4ColorTable(65,65,55)} + -- CarCols[15] = {REN.GTA4ColorTable(74,74,63)} + -- CarCols[16] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[17] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[18] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[19] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[20] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/feltzer_wheel.mdl', + + CustomWheelPosFL = Vector(54,31,-10), + CustomWheelPosFR = Vector(54,-31,-10), + CustomWheelPosRL = Vector(-54,33,-10), + CustomWheelPosRR = Vector(-54,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,-5), + + CustomSteerAngle = 38, + + SeatOffset = Vector(-25,-17,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-10,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-95,20,-11), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-95,-20,-11), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 38000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 8, + RearConstant = 38000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + CounterSteeringMul = 0.83, + + MaxGrip = 70, + Efficiency = 1, + GripOffset = -2, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 60, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-67,34,13), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 40, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feltzer_idle.wav', + + snd_low = 'octoteam/vehicles/feltzer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feltzer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feltzer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feltzer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feltzer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/cavalcade_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.35, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(8.230, 17.082, 10.5), ang = Angle(-0.0, -90.0, 71.1) }, + Radio = { pos = Vector(14.965, 0.000, 5.526), ang = Angle(-10.0, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(93.877, -0.008, -10.839), ang = Angle(9.8, 0.0, 0.0) }, + Back = { pos = Vector(-90.890, 0.004, 8.821), ang = Angle(-4.6, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(-2.283, 0.249, 22.410), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(11.902, 35.314, 15.312), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(11.768, -35.243, 14.669), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_feltzer', V ) + +local light_table = { + L_HeadLampPos = Vector(75,29,2), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(75,-29,2), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-89,27,7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-89,-27,7), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(75,29,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(75,-29,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(10,18.7,9.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(82,22,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(82,-22,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(10,18,9.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-89,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-89,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-89,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-89,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-90,27,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-90,-27,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(82,25.4,-0.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-88,27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(10.25,21,10), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(82,-25.4,-0.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-88,-27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(10.25,14.8,10), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [5] = '', + [12] = '' + }, + Brake = { + [10] = '', + [11] = '', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = '' + }, + Reverse = { + [10] = '', + [11] = '', + [5] = '', + [12] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + Brake_Reverse = { + [10] = '', + [11] = '', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = '', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = '', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = '', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = '', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [11] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [5] = 'models/gta4/vehicles/feltzer/feltzer_lights_on', + [12] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/feltzer/feltzer_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_feltzer', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feroci.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feroci.lua new file mode 100644 index 0000000..03e4d8f --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feroci.lua @@ -0,0 +1,449 @@ +local V = { + Name = 'Feroci', + Model = 'models/octoteam/vehicles/feroci.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1600.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_feroci', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01000' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(19,133,93)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[3] = {REN.GTA4ColorTable(4,133,5)} + -- CarCols[4] = {REN.GTA4ColorTable(7,133,8)} + -- CarCols[5] = {REN.GTA4ColorTable(16,133,8)} + -- CarCols[6] = {REN.GTA4ColorTable(21,133,12)} + -- CarCols[7] = {REN.GTA4ColorTable(26,133,12)} + -- CarCols[8] = {REN.GTA4ColorTable(31,133,28)} + -- CarCols[9] = {REN.GTA4ColorTable(37,133,34)} + -- CarCols[10] = {REN.GTA4ColorTable(39,133,33)} + -- CarCols[11] = {REN.GTA4ColorTable(57,133,56)} + -- CarCols[12] = {REN.GTA4ColorTable(52,133,59)} + -- CarCols[13] = {REN.GTA4ColorTable(67,133,56)} + -- CarCols[14] = {REN.GTA4ColorTable(87,133,85)} + -- CarCols[15] = {REN.GTA4ColorTable(85,133,63)} + -- CarCols[16] = {REN.GTA4ColorTable(95,133,95)} + -- CarCols[17] = {REN.GTA4ColorTable(102,133,103)} + -- CarCols[18] = {REN.GTA4ColorTable(2,133,63)} + -- CarCols[19] = {REN.GTA4ColorTable(21,133,72)} + -- CarCols[20] = {REN.GTA4ColorTable(22,133,72)} + -- CarCols[21] = {REN.GTA4ColorTable(13,133,91)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/feroci_wheel.mdl', + + CustomWheelPosFL = Vector(60,29,-4), + CustomWheelPosFR = Vector(60,-29,-4), + CustomWheelPosRL = Vector(-59,29,-4), + CustomWheelPosRR = Vector(-59,-29,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 32, + + SeatOffset = Vector(-9,-17,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(2,-17,-8), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-27,17,-6), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-17,-6), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-103,22,-5), + ang = Angle(-90,0,0), + OnBodyGroups = { + [2] = {0}, + } + }, + { + pos = Vector(-109,16.4,-4.3), + ang = Angle(-90,0,0), + OnBodyGroups = { + [2] = {1}, + } + }, + { + pos = Vector(-109,20.3,-4.3), + ang = Angle(-90,0,0), + OnBodyGroups = { + [2] = {1}, + } + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 22, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 140.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,32,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/feroci_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_feroci', V ) + +local V2 = {} +V2.Name = 'LC Triads Feroci' +V2.Model = 'models/octoteam/vehicles/feroci.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('00111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,96,69)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(122,117,96), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_feroci3', V2 ) + +local light_table = { + L_HeadLampPos = Vector(93,21,10), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-21,10), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-103,26,20.8), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-103,-26,20.8), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,21,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-21,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(23,17.5,17), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,21,10),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-21,10),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,15.7,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,22,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-22,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-103,26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103,26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,0,24.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + OnBodyGroups = { + [3] = {0}, + } + }, + { + pos = Vector(-103,-26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-104,22,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-104,-22,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,30,8.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,29,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,18.9,19.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(91,-30,8.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,-29,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,14.4,19.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [10] = '', + [12] = '', + }, + Brake = { + [8] = '', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [12] = '', + }, + Reverse = { + [8] = '', + [10] = '', + [12] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + Brake_Reverse = { + [8] = '', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [12] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [12] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [12] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [12] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [12] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [12] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [12] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [12] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [12] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/feroci/feroci_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/feroci/feroci_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_feroci', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feroci2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feroci2.lua new file mode 100644 index 0000000..a59f9c3 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_feroci2.lua @@ -0,0 +1,391 @@ +local V = { + Name = 'Feroci (FlyUS)', + Model = 'models/octoteam/vehicles/feroci2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1600.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_feroci2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/feroci2_wheel.mdl', + + CustomWheelPosFL = Vector(60,29,-4), + CustomWheelPosFR = Vector(60,-29,-4), + CustomWheelPosRL = Vector(-59,29,-4), + CustomWheelPosRR = Vector(-59,-29,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 32, + + SeatOffset = Vector(-9,-17,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(2,-17,-8), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-27,17,-6), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-17,-6), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-103,22,-5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 22, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 140.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,32,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/feroci_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_feroci2', V ) + +local light_table = { + L_HeadLampPos = Vector(93,21,10), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-21,10), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-103,26,20.8), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-103,-26,20.8), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,21,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-21,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(23,17.5,17), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,21,10),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-21,10),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,15.7,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,22,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-22,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-103,26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103,26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,0,24.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-26,20.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-104,22,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-104,-22,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'common/null.wav'}, + ems_sprites = { + { + pos = Vector(-2,0,46), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,135,0,255), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.1 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,30,8.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,29,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,18.9,19.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(91,-30,8.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,-29,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,14.4,19.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [10] = '', + [11] = '', + }, + Brake = { + [8] = '', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [11] = '', + }, + Reverse = { + [8] = '', + [10] = '', + [11] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + Brake_Reverse = { + [8] = '', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [11] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [11] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [11] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [11] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [10] = 'models/gta4/vehicles/feroci/feroci_lights_on', + [11] = 'models/gta4/vehicles/feroci/feroci_lights_on', + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/feroci/feroci_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/feroci/feroci_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_feroci2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_firetruk.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_firetruk.lua new file mode 100644 index 0000000..a346936 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_firetruk.lua @@ -0,0 +1,624 @@ +sound.Add({ + name = 'FIRETRUK_HORN', + channel = CHAN_STATIC, + volume = 1.0, + level = 90, + sound = 'octoteam/vehicles/fire/engine/horn.wav' +} ) + +local V = { + Name = 'Fire Truck', + Model = 'models/octoteam/vehicles/firetruk.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 3000, + Trunk = { 100 }, + + EnginePos = Vector(160,0,0), + + LightsTable = 'gta4_firetruk', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,28,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(150,8,0), + }, + + CustomWheelModel = 'models/octoteam/vehicles/firetruk_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/firetruk_wheel_r.mdl', + + CustomWheelPosFL = Vector(104,38,-25), + CustomWheelPosFR = Vector(104,-38,-25), + CustomWheelPosRL = Vector(-91,38,-25), + CustomWheelPosRR = Vector(-91,-38,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 23.1, + RearWheelRadius = 23.2, + + CustomMassCenter = Vector(0,0,35), + + CustomSteerAngle = 35, + + SeatOffset = Vector(125,-27,30), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(130,-27,-2), + ang = Angle(0,-90,0), + hasRadio = true + }, + { + pos = Vector(42,-27,4), + ang = Angle(0,-90,0) + }, + { + pos = Vector(42,0,4), + ang = Angle(0,-90,0) + }, + { + pos = Vector(42,27,4), + ang = Angle(0,-90,0) + }, + { + pos = Vector(94,-27,4), + ang = Angle(0,90,0) + }, + { + pos = Vector(94,27,4), + ang = Angle(0,90,0) + }, + }, + + FrontHeight = 6, + FrontConstant = 65000, + FrontDamping = 2000, + FrontRelativeDamping = 2000, + + RearHeight = 6, + RearConstant = 65000, + RearDamping = 2000, + RearRelativeDamping = 2000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 70, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 60, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-112,53,7), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + AirFriction = -65, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/firetruk_idle.wav', + + snd_low = 'octoteam/vehicles/firetruk_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/firetruk_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/firetruk_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/firetruk_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/firetruk_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/fire/engine/horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(152.660, 25.913, 20.201), ang = Angle(-0.0, -90.0, 68.9) }, + Radio = { pos = Vector(154.534, 3.947, 20.433), ang = Angle(-16.9, 155.1, 3.1) }, + Plates = { + Front = { pos = Vector(188.185, 0, -20.485), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-153.702, 12.336, -14.985), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(156.945, 51.876, 39.757), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(156.559, -52.183, 42.992), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_firetruk', V ) + +local light_table = { + L_HeadLampPos = Vector(172,34,1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(172,-34,1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-155,38,7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-155,-38,7), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(172,34,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(172,-34,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(154,30,22), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(172,26,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(172,-26,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(154,27.5,22), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-155,38,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-155,-38,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-155,38,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-155,-38,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-155,38,-11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-155,-38,-11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/fire/engine/siren1.wav','octoteam/vehicles/fire/engine/siren2.wav','octoteam/vehicles/fire/engine/siren3.wav'}, + ems_sprites = { + { + pos = Vector(56,31,65), + material = 'octoteam/sprites/lights/gta4_corona', + size = 180, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + { + pos = Vector(-151,32,63), + material = 'octoteam/sprites/lights/gta4_corona', + size = 170, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(140,31,65), + material = 'octoteam/sprites/lights/gta4_corona', + size = 180, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(148,0,65), + material = 'octoteam/sprites/lights/gta4_corona', + size = 170, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(140,-31,65), + material = 'octoteam/sprites/lights/gta4_corona', + size = 180, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-151,-32,63), + material = 'octoteam/sprites/lights/gta4_corona', + size = 170, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(56,-31,65), + material = 'octoteam/sprites/lights/gta4_corona', + size = 180, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(172,31,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-155,38,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(153.5,28.9,21.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(172,-31,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-155,-38,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(153.5,23.9,21.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [10] = '', + [14] = '', + [6] = '' + }, + Brake = { + [3] = '', + [10] = '', + [14] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [6] = '' + }, + Reverse = { + [3] = '', + [10] = '', + [14] = '', + [6] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + Brake_Reverse = { + [3] = '', + [10] = '', + [14] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [6] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = '', + [14] = '', + [6] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = '', + [14] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [6] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = '', + [14] = '', + [6] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = '', + [14] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [6] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [14] = '', + [7] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [14] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [7] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [14] = '', + [7] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [10] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [14] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on', + [7] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + right = { + [15] = 'models/gta4/vehicles/firetruk/firetruck_lights_glass_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_firetruk', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_flatbed.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_flatbed.lua new file mode 100644 index 0000000..0a8c072 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_flatbed.lua @@ -0,0 +1,341 @@ +local V = { + Name = 'Flatbed', + Model = 'models/octoteam/vehicles/flatbed.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 12000.0, + + EnginePos = Vector(140,0,50), + + LightsTable = 'gta4_flatbed', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(1,1,8)} + -- CarCols[2] = {REN.GTA4ColorTable(6,6,8)} + -- CarCols[3] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[4] = {REN.GTA4ColorTable(12,12,12)} + -- CarCols[5] = {REN.GTA4ColorTable(31,31,31)} + -- CarCols[6] = {REN.GTA4ColorTable(40,40,40)} + -- CarCols[7] = {REN.GTA4ColorTable(52,53,53)} + -- CarCols[8] = {REN.GTA4ColorTable(54,54,54)} + -- CarCols[9] = {REN.GTA4ColorTable(56,56,56)} + -- CarCols[10] = {REN.GTA4ColorTable(70,70,70)} + -- CarCols[11] = {REN.GTA4ColorTable(73,73,73)} + -- CarCols[12] = {REN.GTA4ColorTable(77,77,76)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4Flatbed(ent, math.random(0,2)) + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/phantom_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/phantom_wheel_r.mdl', + + CustomWheelPosFL = Vector(147,50,-12), + CustomWheelPosFR = Vector(147,-50,-12), + CustomWheelPosML = Vector(-88,43,-12), + CustomWheelPosMR = Vector(-88,-43,-12), + CustomWheelPosRL = Vector(-146,43,-12), + CustomWheelPosRR = Vector(-146,-43,-12), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 24.4, + RearWheelRadius = 22.3, + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(45,-27,90), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(65,-25,45), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(47,53,149), + ang = Angle(0,0,0), + }, + { + pos = Vector(47,-53,149), + ang = Angle(0,0,0), + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 22, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 148, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 115.0, + PowerbandStart = 1700, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(60,55,10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 200, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/phantom_idle.wav', + + snd_low = 'octoteam/vehicles/phantom_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/phantom_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/phantom_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/phantom_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/phantom_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'TRUCK_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.10, + Gears = {-0.6,0,0.2,0.35,0.5,0.75,1,1.25}, + + Dash = { pos = Vector(91.652, 25.123, 69.482), ang = Angle(-0.0, -90.0, 62.5) }, + Radio = { pos = Vector(93.214, 2.981, 68.736), ang = Angle(-26.7, 155.1, 3.3) }, + Plates = { + Front = { pos = Vector(184.269, 0, -9.726), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-195.942, 0, 5.937), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(78.544, 64.902, 86.892), + h = 3 / 4, + ratio = 1 / 2.5, + }, + right = { + pos = Vector(78.544, -64.902, 86.892), + h = 3 / 4, + ratio = 1 / 2.5, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_flatbed', V ) + +local light_table = { + L_HeadLampPos = Vector(171,46,31), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(171,-46,31), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-197,28,17), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-197,-28,17), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(171,46,31), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(171,-46,31), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(92,18,70), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(171,46,31),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(171,-46,31),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(92.8,18,71.4), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-197,28,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-197,-28,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-197,38,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-197,-38,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(165,54,31), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-197,48,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(92.8,30,71.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(165,-54,31), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-197,-48,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(92.8,24,71.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [9] = '', + }, + Brake = { + [5] = '', + [9] = 'models/gta4/vehicles/flatbed/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/flatbed/detail2_on', + [9] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/flatbed/detail2_on', + [9] = 'models/gta4/vehicles/flatbed/detail2_on', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/flatbed/detail2_on', + [9] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/flatbed/detail2_on', + [9] = 'models/gta4/vehicles/flatbed/detail2_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/flatbed/detail2_on' + }, + right = { + [11] = 'models/gta4/vehicles/flatbed/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_flatbed', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_forklift.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_forklift.lua new file mode 100644 index 0000000..a2728f3 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_forklift.lua @@ -0,0 +1,186 @@ +local V = { + Name = 'Forklift', + Model = 'models/octoteam/vehicles/forklift.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 1400.0, + NAKTankerHB = { + Tank = { + OBBMax = Vector(-35,-20,28), + OBBMin = Vector(-47,20,46), + }, + }, + + EnginePos = Vector(-30,0,10), + + LightsTable = 'gta4_forklift', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(89,28,89)} + -- CarCols[2] = {REN.GTA4ColorTable(89,74,89)} + -- CarCols[3] = {REN.GTA4ColorTable(90,74,89)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.ForkliftHitbox(ent) + + REN.GTA4Forklift(ent) + REN.GTA4SimfphysInit(ent, 2, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + SteerFront = false, + SteerRear = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/forklift_wheel.mdl', + + CustomWheelPosFL = Vector(32,20,-8), + CustomWheelPosFR = Vector(32,-20,-8), + CustomWheelPosRL = Vector(-33,20,-8), + CustomWheelPosRR = Vector(-33,-20,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,-2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-25,0,50), + SeatPitch = 10, + SeatYaw = 90, + + ExhaustPositions = { + { + pos = Vector(-52,21.3,5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-52,-21.3,5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 100, + + TurnSpeed = 3, + + MaxGrip = 65, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 10, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4000, + PeakTorque = 50.0, + PowerbandStart = 1500, + PowerbandEnd = 3000, + Turbocharged = false, + Supercharged = false, + DoNotStall = true, + + FuelFillPos = Vector(18,28,1), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/forklift_idle.wav', + + snd_low = 'octoteam/vehicles/forklift_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/forklift_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/forklift_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/forklift_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/forklift_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/airtug_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.30, + Gears = {-0.25,0,0.35} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_forklift', V ) + +local light_table = { + L_HeadLampPos = Vector(20,14.3,71), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(20,-14.3,71), + R_HeadLampAng = Angle(5,0,0), + + Headlight_sprites = { + { + pos = Vector(20,14.3,71), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(20,-14.3,71), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(20,14.3,71),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(20,-14.3,71),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + DelayOn = 0, + DelayOff = 0, + + SubMaterials = { + off = { + Base = { + [3] = '', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/forklift/forklift_stickers_on', + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_forklift', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fortune.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fortune.lua new file mode 100644 index 0000000..3889885 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fortune.lua @@ -0,0 +1,399 @@ +local V = { + Name = 'Fortune', + Model = 'models/octoteam/vehicles/fortune.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Хетчбеки', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Хетчбеки', + + Members = { + Mass = 1400, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_fortune', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[2] = {REN.GTA4ColorTable(3,3,3)} + -- CarCols[3] = {REN.GTA4ColorTable(4,4,4)} + -- CarCols[4] = {REN.GTA4ColorTable(10,10,10)} + -- CarCols[5] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[6] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[7] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[8] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[9] = {REN.GTA4ColorTable(13,13,80)} + -- CarCols[10] = {REN.GTA4ColorTable(16,16,16)} + -- CarCols[11] = {REN.GTA4ColorTable(31,31,31)} + -- CarCols[12] = {REN.GTA4ColorTable(31,31,33)} + -- CarCols[13] = {REN.GTA4ColorTable(34,34,34)} + -- CarCols[14] = {REN.GTA4ColorTable(36,36,33)} + -- CarCols[15] = {REN.GTA4ColorTable(49,49,75)} + -- CarCols[16] = {REN.GTA4ColorTable(52,52,52)} + -- CarCols[17] = {REN.GTA4ColorTable(56,56,57)} + -- CarCols[18] = {REN.GTA4ColorTable(55,55,55)} + -- CarCols[19] = {REN.GTA4ColorTable(62,62,1)} + -- CarCols[20] = {REN.GTA4ColorTable(64,64,1)} + -- CarCols[21] = {REN.GTA4ColorTable(68,68,72)} + -- CarCols[22] = {REN.GTA4ColorTable(72,72,2)} + -- CarCols[23] = {REN.GTA4ColorTable(77,77,74)} + -- CarCols[24] = {REN.GTA4ColorTable(80,80,50)} + -- CarCols[25] = {REN.GTA4ColorTable(87,87,74)} + -- CarCols[26] = {REN.GTA4ColorTable(95,95,95)} + -- CarCols[27] = {REN.GTA4ColorTable(103,103,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/fortune_wheel.mdl', + + CustomWheelPosFL = Vector(67,30,-14), + CustomWheelPosFR = Vector(67,-30,-14), + CustomWheelPosRL = Vector(-60,30,-14), + CustomWheelPosRR = Vector(-60,-30,-14), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0.02,-2.4), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-13,-17,16), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-1,-17,-18), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-97,-17,-15.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 27000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 7, + RearConstant = 27000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 46, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 39, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-76,-34,8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 35, + + AirFriction = -50, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/lokus_idle.wav', + + snd_low = 'octoteam/vehicles/lokus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/lokus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/lokus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/lokus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/lokus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/fortune_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(24.611, 16.938, 11.693), ang = Angle(-0.0, -90.0, 61.4) }, + Radio = { pos = Vector(27.494, 0.003, 5.008), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(105.732, -0.011, -10.466), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-102.308, -0.007, -10.224), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(10.416, -0.016, 24.228), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(33.774, 37.675, 10.917), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(32.980, -37.800, 11.236), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_fortune', V ) + +local light_table = { + L_HeadLampPos = Vector(99,25,1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(99,-25,1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-99,27,3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-99,-27,3), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(99,25,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(99,-25,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(24,17,10), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(99,25,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(99,-25,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(24,15,10), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-99,27,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-27,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-99,12,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-12,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 90, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-99,26,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-99,-26,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(96,31,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,31,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,19,13), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(96,-31,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,-31,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,13.5,13), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [8] = '', + [11] = '', + }, + Brake = { + [4] = '', + [8] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [11] = '', + }, + Reverse = { + [4] = '', + [8] = '', + [11] = 'models/gta4/vehicles/fortune/fortune_lights_on', + }, + Brake_Reverse = { + [4] = '', + [8] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [11] = 'models/gta4/vehicles/fortune/fortune_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = '', + [11] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [11] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = '', + [11] = 'models/gta4/vehicles/fortune/fortune_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [11] = 'models/gta4/vehicles/fortune/fortune_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = '', + [11] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [11] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = '', + [11] = 'models/gta4/vehicles/fortune/fortune_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [8] = 'models/gta4/vehicles/fortune/fortune_lights_on', + [11] = 'models/gta4/vehicles/fortune/fortune_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/fortune/fortune_lights_on' + }, + right = { + [3] = 'models/gta4/vehicles/fortune/fortune_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_fortune', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_futo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_futo.lua new file mode 100644 index 0000000..8767b3e --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_futo.lua @@ -0,0 +1,429 @@ +local V = { + Name = 'Futo', + Model = 'models/octoteam/vehicles/futo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Хетчбеки', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Хетчбеки', + + Members = { + Mass = 1250, + Trunk = { 25 }, + + EnginePos = Vector(60,0,5), + + LightsTable = 'gta4_futo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(32,0,30)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[3] = {REN.GTA4ColorTable(0,3,12)} + -- CarCols[4] = {REN.GTA4ColorTable(3,3,8)} + -- CarCols[5] = {REN.GTA4ColorTable(4,0,0)} + -- CarCols[6] = {REN.GTA4ColorTable(13,1,8)} + -- CarCols[7] = {REN.GTA4ColorTable(34,0,30)} + -- CarCols[8] = {REN.GTA4ColorTable(36,36,30)} + -- CarCols[9] = {REN.GTA4ColorTable(40,40,40)} + -- CarCols[10] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[11] = {REN.GTA4ColorTable(54,52,2)} + -- CarCols[12] = {REN.GTA4ColorTable(62,61,62)} + -- CarCols[13] = {REN.GTA4ColorTable(68,0,2)} + -- CarCols[14] = {REN.GTA4ColorTable(79,79,79)} + -- CarCols[15] = {REN.GTA4ColorTable(85,85,2)} + -- CarCols[16] = {REN.GTA4ColorTable(86,1,86)} + -- CarCols[17] = {REN.GTA4ColorTable(87,0,2)} + -- CarCols[18] = {REN.GTA4ColorTable(98,1,98)} + -- CarCols[19] = {REN.GTA4ColorTable(108,106,2)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/futo_wheel.mdl', + + CustomWheelPosFL = Vector(55,28,-8), + CustomWheelPosFR = Vector(55,-28,-8), + CustomWheelPosRL = Vector(-45,28,-8), + CustomWheelPosRR = Vector(-45,-28,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(8,0,-2.4), + + CustomSteerAngle = 45, + + SeatOffset = Vector(-14,-14,18), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-1,-15,-14), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-83,-13.5,-9.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 30000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 7, + RearConstant = 30000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 8, + CounterSteeringMul = 0.85, + + MaxGrip = 30, + Efficiency = 1.3, + GripOffset = 3, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 8000, + PeakTorque = 50, + PowerbandStart = 1200, + PowerbandEnd = 6800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-60,-31,13), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 40, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/blista_idle.wav', + + snd_low = 'octoteam/vehicles/blista_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/blista_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/blista_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/blista_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/blista_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/minivan_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.12,0.27,0.38,0.45,0.55}, + + Dash = { pos = Vector(18.620, 13.255, 14.250), ang = Angle(-0.0, -90.0, 72.9) }, + Radio = { pos = Vector(23.938, -2.792, 10.563), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(90.380, 0.007, -3.712), ang = Angle(1.7, -0.0, -0.0) }, + Back = { pos = Vector(-82.380, -0.000, 6.627), ang = Angle(-15.4, -180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(8.597, -0.008, 27.446), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(19.050, 34.465, 16.453), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(18.522, -34.593, 16.828), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_futo', V ) + +local V2 = {} +V2.Name = 'Futo GT' +V2.Model = 'models/octoteam/vehicles/futo.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Хетчбеки' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(32,0,30)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[3] = {REN.GTA4ColorTable(0,3,12)} + -- CarCols[4] = {REN.GTA4ColorTable(3,3,8)} + -- CarCols[5] = {REN.GTA4ColorTable(4,0,0)} + -- CarCols[6] = {REN.GTA4ColorTable(13,1,8)} + -- CarCols[7] = {REN.GTA4ColorTable(34,0,30)} + -- CarCols[8] = {REN.GTA4ColorTable(36,36,30)} + -- CarCols[9] = {REN.GTA4ColorTable(40,40,40)} + -- CarCols[10] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[11] = {REN.GTA4ColorTable(54,52,2)} + -- CarCols[12] = {REN.GTA4ColorTable(62,61,62)} + -- CarCols[13] = {REN.GTA4ColorTable(68,0,2)} + -- CarCols[14] = {REN.GTA4ColorTable(79,79,79)} + -- CarCols[15] = {REN.GTA4ColorTable(85,85,2)} + -- CarCols[16] = {REN.GTA4ColorTable(86,1,86)} + -- CarCols[17] = {REN.GTA4ColorTable(87,0,2)} + -- CarCols[18] = {REN.GTA4ColorTable(98,1,98)} + -- CarCols[19] = {REN.GTA4ColorTable(108,106,2)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(10,10,10), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_futo2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(83,23,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(83,-23,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-82,16,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-82,-16,9), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(83,23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(83,-23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(19,20.2,15.6), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(83,23,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(83,-23,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(19,20.2,14.6), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-82,16,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-82,-16,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-82,25,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-82,-25,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 90, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-81.5,18.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-81.5,-18.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-78,29.5,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(19,14.2,15.65), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-78,-29.5,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(19,12.7,15.65), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [10] = '', + [11] = '', + }, + Brake = { + [5] = '', + [10] = 'models/gta4/vehicles/futo/futo_lights_on', + [11] = '', + }, + Reverse = { + [5] = '', + [10] = '', + [11] = 'models/gta4/vehicles/futo/futo_lights_on', + }, + Brake_Reverse = { + [5] = '', + [10] = 'models/gta4/vehicles/futo/futo_lights_on', + [11] = 'models/gta4/vehicles/futo/futo_lights_on', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = 'models/gta4/vehicles/futo/futo_lights_on', + [11] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/futo/futo_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = 'models/gta4/vehicles/futo/futo_lights_on', + [11] = 'models/gta4/vehicles/futo/futo_lights_on', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = 'models/gta4/vehicles/futo/futo_lights_on', + [11] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/futo/futo_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/futo/futo_lights_on', + [10] = 'models/gta4/vehicles/futo/futo_lights_on', + [11] = 'models/gta4/vehicles/futo/futo_lights_on', + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/futo/futo_lights_on' + }, + right = { + [4] = 'models/gta4/vehicles/futo/futo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_futo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fxt.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fxt.lua new file mode 100644 index 0000000..bf2bf56 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_fxt.lua @@ -0,0 +1,369 @@ +local V = { + Name = 'Cavalcade FXT', + Model = 'models/octoteam/vehicles/fxt.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 3500.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_fxt', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,4)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,9)} + -- CarCols[3] = {REN.GTA4ColorTable(6,6,63)} + -- CarCols[4] = {REN.GTA4ColorTable(40,40,27)} + -- CarCols[5] = {REN.GTA4ColorTable(57,57,51)} + -- CarCols[6] = {REN.GTA4ColorTable(64,64,63)} + -- CarCols[7] = {REN.GTA4ColorTable(85,85,118)} + -- CarCols[8] = {REN.GTA4ColorTable(88,88,87)} + -- CarCols[9] = {REN.GTA4ColorTable(98,98,91)} + -- CarCols[10] = {REN.GTA4ColorTable(104,104,103)} + -- CarCols[11] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[12] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[13] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[14] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[15] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/cavalcade_wheel.mdl', + + CustomWheelPosFL = Vector(64,34,-18), + CustomWheelPosFR = Vector(64,-34,-18), + CustomWheelPosRL = Vector(-78,34,-18), + CustomWheelPosRR = Vector(-78,-34,-18), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-18,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-20,-7), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-33,20,-7), + ang = Angle(0,-90,10), + noMirrors = true + }, + { + pos = Vector(-33,-20,-7), + ang = Angle(0,-90,10), + noMirrors = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-114,33.4,-17.7), + ang = Angle(-100,-70,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 32000, + FrontDamping = 1000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 1000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 100, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 130.0, + PowerbandStart = 2000, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-79,37,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 110, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/cavalcade_idle.wav', + + snd_low = 'octoteam/vehicles/cavalcade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/cavalcade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/cavalcade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/cavalcade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/cavalcade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.15, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_fxt', V ) + +local light_table = { + L_HeadLampPos = Vector(90,33,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(90,-33,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-121,29,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-121,-29,4), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(90,33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(90,-33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(29.7,18,17.2), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,26,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-26,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29.7,19,17.2), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(97,32,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,21,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-32,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-21,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-121,34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-121,-34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-121,34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-121,-34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-121,33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-121,-33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(89,33,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-121,34,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.7,20,17.2), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(89,-33,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-121,-34,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.7,17,17.2), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [9] = '', + [12] = '' + }, + Reverse = { + [8] = '', + [9] = '', + [12] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [9] = '', + [12] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [9] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [12] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [9] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on', + [12] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/cavalcade/cavalcade_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_fxt', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_habanero.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_habanero.lua new file mode 100644 index 0000000..a5d627b --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_habanero.lua @@ -0,0 +1,434 @@ +local V = { + Name = 'Habanero', + Model = 'models/octoteam/vehicles/habanero.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 1650, + Trunk = { 50 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_habanero', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,56)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,11)} + -- CarCols[3] = {REN.GTA4ColorTable(3,3,11)} + -- CarCols[4] = {REN.GTA4ColorTable(14,14,11)} + -- CarCols[5] = {REN.GTA4ColorTable(19,19,14)} + -- CarCols[6] = {REN.GTA4ColorTable(42,42,30)} + -- CarCols[7] = {REN.GTA4ColorTable(45,45,27)} + -- CarCols[8] = {REN.GTA4ColorTable(57,57,126)} + -- CarCols[9] = {REN.GTA4ColorTable(54,54,50)} + -- CarCols[10] = {REN.GTA4ColorTable(52,52,83)} + -- CarCols[11] = {REN.GTA4ColorTable(70,70,80)} + -- CarCols[12] = {REN.GTA4ColorTable(82,82,63)} + -- CarCols[13] = {REN.GTA4ColorTable(85,85,63)} + -- CarCols[14] = {REN.GTA4ColorTable(90,90,91)} + -- CarCols[15] = {REN.GTA4ColorTable(94,94,94)} + -- CarCols[16] = {REN.GTA4ColorTable(104,104,106)} + -- CarCols[17] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[18] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[19] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[20] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[21] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/habanero_wheel.mdl', + + CustomWheelPosFL = Vector(57,33,-14), + CustomWheelPosFR = Vector(57,-33,-14), + CustomWheelPosRL = Vector(-57,33,-14), + CustomWheelPosRR = Vector(-57,-33,-14), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,3), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-17,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-17,-15), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-31,17,-15), + ang = Angle(0,-90,10), + }, + { + pos = Vector(-31,-17,-15), + ang = Angle(0,-90,10), + }, + }, + ExhaustPositions = { + { + pos = Vector(-98,20,-13.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-98,-20,-13.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 9, + FrontConstant = 34000, + FrontDamping = 950, + FrontRelativeDamping = 950, + + RearHeight = 9, + RearConstant = 34000, + RearDamping = 950, + RearRelativeDamping = 950, + + TurnSpeed = 4, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 550, + + MaxGrip = 50, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 42, + PowerbandStart = 1200, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.35, + + FuelFillPos = Vector(-67,-36,14), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 60, + + AirFriction = -50, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/perennial_idle.wav', + + snd_low = 'octoteam/vehicles/perennial_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/perennial_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/perennial_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/perennial_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/perennial_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/habanero_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(24.810, 16.475, 12.440), ang = Angle(-0.0, -90.0, 86.7) }, + Radio = { pos = Vector(28.662, 0.298, 11.801), ang = Angle(-6.7, 162.5, 2.4) }, + Plates = { + Front = { pos = Vector(98.436, -0.002, -9.954), ang = Angle(4.6, 0.0, 0.0) }, + Back = { pos = Vector(-103.151, 0.001, -6.594), ang = Angle(-5.1, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(16.800, -0.011, 30.482), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(29.526, 35.754, 19.851), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(30.102, -35.873, 19.718), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_habanero', V ) + +local light_table = { + L_HeadLampPos = Vector(84,29,2), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(84,-29,2), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-93,22,17), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-93,-22,17), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(84,29,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(84,-29,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26.4,17,10.8), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(88,24,2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(88,-24,2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26.4,16,10.8), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(101,25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(101,-25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-93,22,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-93,-22,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-87,31,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-87,-31,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-94,22,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-94,-22,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(89,27,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-89,32,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.7,18.8,13.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(89,-27,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-89,-32,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.7,14.3,13.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [7] = '', + [10] = '', + [12] = '' + }, + Brake = { + [8] = '', + [7] = '', + [10] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [12] = '' + }, + Reverse = { + [8] = '', + [7] = '', + [10] = '', + [12] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + Brake_Reverse = { + [8] = '', + [7] = '', + [10] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [12] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = '', + [10] = '', + [12] = '' + }, + Brake = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [12] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = '', + [10] = '', + [12] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [12] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [10] = '', + [12] = '' + }, + Brake = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [10] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [12] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [10] = '', + [12] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [7] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [10] = 'models/gta4/vehicles/habanero/habanero_lights_on', + [12] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/habanero/habanero_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_habanero', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_hakumai.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_hakumai.lua new file mode 100644 index 0000000..8afcaa4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_hakumai.lua @@ -0,0 +1,416 @@ +local V = { + Name = 'Hakumai', + Model = 'models/octoteam/vehicles/hakumai.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1300, + Trunk = { 30 }, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_hakumai', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(33,30,37)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[3] = {REN.GTA4ColorTable(0,3,8)} + -- CarCols[4] = {REN.GTA4ColorTable(4,0,8)} + -- CarCols[5] = {REN.GTA4ColorTable(7,0,8)} + -- CarCols[6] = {REN.GTA4ColorTable(10,10,8)} + -- CarCols[7] = {REN.GTA4ColorTable(16,9,8)} + -- CarCols[8] = {REN.GTA4ColorTable(22,22,8)} + -- CarCols[9] = {REN.GTA4ColorTable(26,16,18)} + -- CarCols[10] = {REN.GTA4ColorTable(31,0,1)} + -- CarCols[11] = {REN.GTA4ColorTable(34,34,34)} + -- CarCols[12] = {REN.GTA4ColorTable(54,1,54)} + -- CarCols[13] = {REN.GTA4ColorTable(57,1,57)} + -- CarCols[14] = {REN.GTA4ColorTable(61,11,61)} + -- CarCols[15] = {REN.GTA4ColorTable(68,1,65)} + -- CarCols[16] = {REN.GTA4ColorTable(72,6,1)} + -- CarCols[17] = {REN.GTA4ColorTable(77,77,77)} + -- CarCols[18] = {REN.GTA4ColorTable(108,1,109)} + -- CarCols[19] = {REN.GTA4ColorTable(109,1,109)} + -- CarCols[20] = {REN.GTA4ColorTable(114,111,111)} + -- CarCols[21] = {REN.GTA4ColorTable(117,109,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/hakumai_wheel.mdl', + + CustomWheelPosFL = Vector(57,30,-6), + CustomWheelPosFR = Vector(57,-30,-6), + CustomWheelPosRL = Vector(-57,30,-6), + CustomWheelPosRR = Vector(-57,-30,-6), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-16,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-17,-7), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-33,17,-7), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-33,-17,-7), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-100,23,-4), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-100,19.3,-4), + ang = Angle(-90,0,0), + OnBodyGroups = { + [1] = {1}, + } + }, + }, + + FrontHeight = 8, + FrontConstant = 27000, + FrontDamping = 900, + FrontRelativeDamping = 900, + + RearHeight = 8, + RearConstant = 27000, + RearDamping = 900, + RearRelativeDamping = 900, + + FastSteeringAngle = 25, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 7, + CounterSteeringMul = 0.85, + + MaxGrip = 30, + Efficiency = 1.3, + GripOffset = 2, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 56, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,34,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/hakumai_idle.wav', + + snd_low = 'octoteam/vehicles/hakumai_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/hakumai_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/hakumai_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/hakumai_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/hakumai_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/hakumai_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.12,0.27,0.38,0.45,0.55}, + + Dash = { pos = Vector(18.604, 16.2, 18.1), ang = Angle(-0.0, -90.0, 68.4) }, + Radio = { pos = Vector(24.598, 0.005, 13.223), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(95.386, 0.001, -4.366), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-100.817, 0.001, 0.652), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(7.694, 0.001, 31.367), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(23.054, 37.744, 20.720), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(23.314, -37.920, 20.549), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_hakumai', V ) + +local light_table = { + L_HeadLampPos = Vector(88,24,8), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(88,-24,8), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-100,24,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-100,-24,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(88,24,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(88,-24,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(21,25,18), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(88,24,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(88,-24,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(21,24,18), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-100,24,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100,-24,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-100,14,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100,-14,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-99,19,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-99,-19,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0.2, + DelayOff = 0, + PoseParameters = { + name = 'lights', + min = 0, + max = 1, + }, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(93,30,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-99,33,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,21,21), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(93,-30,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-99,-33,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,20,21), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [8] = '', + [7] = '', + }, + Brake = { + [3] = '', + [8] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [7] = '', + }, + Reverse = { + [3] = '', + [8] = '', + [7] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + }, + Brake_Reverse = { + [3] = '', + [8] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [7] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = '', + [7] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [7] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = '', + [7] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [7] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = '', + [7] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [7] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = '', + [7] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [8] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + [7] = 'models/gta4/vehicles/hakumai/hakumai_lights_on', + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/hakumai/hakumai_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/hakumai/hakumai_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_hakumai', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_huntley.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_huntley.lua new file mode 100644 index 0000000..9b4ef5e --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_huntley.lua @@ -0,0 +1,1393 @@ +local V = { + Name = 'Huntley Sport', + Model = 'models/octoteam/vehicles/huntley.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2300, + Trunk = { 50 }, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_huntley', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('00' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(76,76,58,76)} + -- CarCols[2] = {REN.GTA4ColorTable2(0,0,103,0)} + -- CarCols[3] = {REN.GTA4ColorTable2(1,1,79,1)} + -- CarCols[4] = {REN.GTA4ColorTable2(3,3,103,3)} + -- CarCols[5] = {REN.GTA4ColorTable2(4,4,82,4)} + -- CarCols[6] = {REN.GTA4ColorTable2(6,6,84,6)} + -- CarCols[7] = {REN.GTA4ColorTable2(11,11,86,11)} + -- CarCols[8] = {REN.GTA4ColorTable2(16,16,92,16)} + -- CarCols[9] = {REN.GTA4ColorTable2(23,23,25,23)} + -- CarCols[10] = {REN.GTA4ColorTable2(34,34,28,34)} + -- CarCols[11] = {REN.GTA4ColorTable2(36,36,27,36)} + -- CarCols[12] = {REN.GTA4ColorTable2(47,47,91,47)} + -- CarCols[13] = {REN.GTA4ColorTable2(52,52,53,52)} + -- CarCols[14] = {REN.GTA4ColorTable2(53,53,51,53)} + -- CarCols[15] = {REN.GTA4ColorTable2(64,64,65,64)} + -- CarCols[16] = {REN.GTA4ColorTable2(69,69,63,69)} + -- CarCols[17] = {REN.GTA4ColorTable2(70,70,64,70)} + -- CarCols[18] = {REN.GTA4ColorTable2(0,0,12,32)} + -- CarCols[19] = {REN.GTA4ColorTable2(73,73,58,73)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/huntley_wheel.mdl', + + CustomWheelPosFL = Vector(60,33,-12), + CustomWheelPosFR = Vector(60,-33,-12), + CustomWheelPosRL = Vector(-60,33,-12), + CustomWheelPosRR = Vector(-60,-33,-12), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-18,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-5,-20,-7), + ang = Angle(0,-90,10), + hasRadio = true, + }, + { + pos = Vector(-37,20,-6), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-37,-20,-6), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-100,18,-9), + ang = Angle(-90,0,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-100,-18,-9), + ang = Angle(-90,0,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-38.5,41.5,-17.1), + ang = Angle(-80,-70,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-38.5,-41.5,-17.1), + ang = Angle(-80,70,0), + OnBodyGroups = { + [1] = {1}, + } + }, + }, + + FrontHeight = 8, + FrontConstant = 40000, + FrontDamping = 1200, + FrontRelativeDamping = 1200, + + RearHeight = 8, + RearConstant = 40000, + RearDamping = 1200, + RearRelativeDamping = 1200, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 60, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 62, + PowerbandStart = 1700, + PowerbandEnd = 5300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.8, + + FuelFillPos = Vector(-80,36,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + AirFriction = -60, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(21.661, 18.166, 16.7), ang = Angle(-0.0, -90.0, 74.7) }, + Radio = { pos = Vector(30.318, 0.048, 17.829), ang = Angle(-21.2, 180.0, -0.0) }, + Plates = { + Front = { pos = Vector(94.744, 0.001, -7.049), ang = Angle(0.9, 0.0, -0.0) }, + Back = { pos = Vector(-98.672, -0.002, 15.790), ang = Angle(-4.6, -180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(10.909, -0.002, 40.261), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(23.973, 39.382, 28.223), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(24.246, -38.981, 27.872), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_huntley', V ) + +local V2 = {} +V2.Name = 'Jamaican Huntley Sport' +V2.Model = 'models/octoteam/vehicles/huntley.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Большие' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(0,59,113,90)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +list.Set('simfphys_vehicles', 'sim_fphys_gta4_huntley2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(83,32,11), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(83,-32,11), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-98,32,11), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-98,-32,11), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(83,32,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(83,-32,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(29.7,18,17.2), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(85,25,11),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(85,-25,11),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29.7,19,17.2), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(90,26,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(90,-26,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-98,32,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-98,-32,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-98,32,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-98,-32,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-81,0,43), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-98,32,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-98,-32,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(91.0, -24.0, 0.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(91.0, 24.0, 0.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(94.6, -8.6, -6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.03 + }, + { + pos = Vector(94.6, 8.6, -6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.025 + }, + { + pos = Vector(89.1, -12.9, 10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(89.1, 12.9, 10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(26.1, -36.7, 23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(26.1, 36.7, 23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(34.3, -38.1, -13.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(34.3, 38.1, -13.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-34.3, 38.1, -13.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-34.3, -38.1, -13.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-98.7, -9.0, 15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-98.7, 9.0, 15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(12, 22, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(12, 18, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(12, 14, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(12, 10, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(12, -22, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(12, -18, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(12, -14, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(12, -10, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-79.3, -22, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-79.3, -18, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-79.3, -14, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-79.3, -10, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-79.3, 22, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-79.3, 18, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-79.3, 14, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-79.3, 10, 41.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(87,23,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97.4,32.3,18.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.7,20,17.2), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(87,-23,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97.4,-32.3,18.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.7,17,17.2), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [13] = '', + [9] = '', + [12] = '' + }, + Brake = { + [3] = '', + [13] = '', + [9] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [12] = '' + }, + Reverse = { + [3] = '', + [13] = '', + [9] = '', + [12] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + Brake_Reverse = { + [3] = '', + [13] = '', + [9] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [12] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = '', + [9] = '', + [12] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = '', + [9] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [12] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = '', + [9] = '', + [12] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = '', + [9] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [12] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [9] = '', + [12] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [9] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [12] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [13] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [9] = 'models/gta4/vehicles/huntley/huntley_lights_on', + [12] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + }, + turnsignals = { + left = { + [14] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/huntley/huntley_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_huntley', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_infernus.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_infernus.lua new file mode 100644 index 0000000..cc08b7d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_infernus.lua @@ -0,0 +1,376 @@ +local V = { + Name = 'Infernus', + Model = 'models/octoteam/vehicles/infernus.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1700.0, + + Backfire = true, + + EnginePos = Vector(-50,0,10), + + LightsTable = 'gta4_infernus', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,63)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,59)} + -- CarCols[3] = {REN.GTA4ColorTable(33,33,27)} + -- CarCols[4] = {REN.GTA4ColorTable(46,46,127)} + -- CarCols[5] = {REN.GTA4ColorTable(6,6,8)} + -- CarCols[6] = {REN.GTA4ColorTable(49,49,59)} + -- CarCols[7] = {REN.GTA4ColorTable(59,59,127)} + -- CarCols[8] = {REN.GTA4ColorTable(88,88,124)} + -- CarCols[9] = {REN.GTA4ColorTable(62,62,63)} + -- CarCols[10] = {REN.GTA4ColorTable(22,22,64)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/infernus_wheel.mdl', + + CustomWheelPosFL = Vector(56,32,-9), + CustomWheelPosFR = Vector(56,-32,-9), + CustomWheelPosRL = Vector(-56,35,-9), + CustomWheelPosRR = Vector(-56,-35,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 30, + + SeatOffset = Vector(-6,-17,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-84,0,8.7), + ang = Angle(-70,0,0), + }, + { + pos = Vector(-84,3,8.7), + ang = Angle(-70,0,0), + }, + { + pos = Vector(-84,-3,8.7), + ang = Angle(-70,0,0), + }, + { + pos = Vector(-85,0,5.8), + ang = Angle(-70,0,0), + }, + { + pos = Vector(-83,0,11.6), + ang = Angle(-70,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 93, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 160.0, + PowerbandStart = 1500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-18,29,19), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 0.65, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/infernus_idle.wav', + + snd_low = 'octoteam/vehicles/infernus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/infernus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/infernus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/infernus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/infernus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.25, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_infernus', V ) + +local light_table = { + L_HeadLampPos = Vector(73.5,31.7,5.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(73.5,-31.7,5.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-83.8,28,10.7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-83.8,-28,10.7), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(73.5,31.7,5.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(73.5,-31.7,5.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(28.6,17.8,10.6), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(75.5,25.7,4.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(75.5,-25.7,4.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(28.6,17.2,10.6), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-83.8,28,10.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-83.8,-28,10.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-83.8,28,10.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-83.8,-28,10.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-85.4,33.3,5.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-85.4,-33.3,5.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-85.4,33.3,5.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(28.6,18,12.2), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-85.4,-33.3,5.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(28.6,16.9,12.2), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [14] = '', + [11] = '', + [15] = '' + }, + Brake = { + [6] = '', + [14] = '', + [11] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [15] = '' + }, + Reverse = { + [6] = '', + [14] = '', + [11] = '', + [15] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + Brake_Reverse = { + [6] = '', + [14] = '', + [11] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [15] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = '', + [11] = '', + [15] = '' + }, + Brake = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = '', + [11] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [15] = '' + }, + Reverse = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = '', + [11] = '', + [15] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = '', + [11] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [15] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [11] = '', + [15] = '' + }, + Brake = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [11] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [15] = '' + }, + Reverse = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [11] = '', + [15] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [14] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [11] = 'models/gta4/vehicles/infernus/infernus_lights_on', + [15] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/infernus/infernus_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_infernus', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ingot.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ingot.lua new file mode 100644 index 0000000..2501cf2 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ingot.lua @@ -0,0 +1,404 @@ +local V = { + Name = 'Ingot', + Model = 'models/octoteam/vehicles/ingot.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,15), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1400, + Trunk = { 50 }, + + EnginePos = Vector(65,0,0), + + LightsTable = 'gta4_ingot', + + OnSpawn = function(ent) + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/ingot_wheel.mdl', + + CustomWheelPosFL = Vector(58,29,-16), + CustomWheelPosFR = Vector(58,-29,-16), + CustomWheelPosRL = Vector(-58,29,-16), + CustomWheelPosRR = Vector(-58,-29,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-16,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-16,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-31,16,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-31,-16,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-92,22,-11), + ang = Angle(-180,0,0), + }, + }, + + FrontHeight = 9, + FrontConstant = 24000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 9, + RearConstant = 24000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 40, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 28, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 38, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-72,-33,9), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + AirFriction = -60, + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/lokus_idle.wav', + + snd_low = 'octoteam/vehicles/lokus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/lokus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/lokus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/lokus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/lokus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/ingot_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(21.390, 15.208, 9.086), ang = Angle(-0.0, -90.0, 73.4) }, + Radio = { pos = Vector(27.524, 0.003, 8.706), ang = Angle(-19.3, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(98.185, -0.001, -8.530), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-97.431, -0.004, 1.924), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(6.877, -0.002, 25.818), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(25.552, 36.748, 13.819), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(25.506, -36.920, 13.885), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_ingot', V ) + +local V2 = {} +V2.Name = 'Russian Mafia Ingot' +V2.Model = 'models/octoteam/vehicles/ingot.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,15) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0111'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,33)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +list.Set('simfphys_vehicles', 'sim_fphys_gta4_ingot2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(90,23,-1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(90,-23,-1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-94,31,3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-94,-31,3), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(90,23,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(90,-23,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(24,23,12.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(90,23,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-23,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(24,23,11.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-94,31,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-94,-31,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-97,24,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-24,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,16,1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-16,1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,30,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-96,24,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(24,17,13.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(86,-30,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-96,-24,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(24,13.7,13.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [9] = '', + }, + Brake = { + [10] = '', + [11] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [9] = '', + }, + Reverse = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + }, + Brake_Reverse = { + [10] = '', + [11] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [9] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = '', + [9] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [9] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [9] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = '', + [9] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [9] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [11] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + [9] = 'models/gta4/vehicles/ingot/flatpack_lights_on', + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/ingot/flatpack_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/ingot/flatpack_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_ingot', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_intruder.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_intruder.lua new file mode 100644 index 0000000..76416c4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_intruder.lua @@ -0,0 +1,398 @@ +local V = { + Name = 'Intruder', + Model = 'models/octoteam/vehicles/intruder.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_intruder', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('000000' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[2] = {REN.GTA4ColorTable(1,133,20)} + -- CarCols[3] = {REN.GTA4ColorTable(3,133,20)} + -- CarCols[4] = {REN.GTA4ColorTable(6,133,22)} + -- CarCols[5] = {REN.GTA4ColorTable(11,133,25)} + -- CarCols[6] = {REN.GTA4ColorTable(38,133,28)} + -- CarCols[7] = {REN.GTA4ColorTable(46,127,46)} + -- CarCols[8] = {REN.GTA4ColorTable(53,133,54)} + -- CarCols[9] = {REN.GTA4ColorTable(54,133,54)} + -- CarCols[10] = {REN.GTA4ColorTable(56,133,58)} + -- CarCols[11] = {REN.GTA4ColorTable(70,133,65)} + -- CarCols[12] = {REN.GTA4ColorTable(78,133,80)} + -- CarCols[13] = {REN.GTA4ColorTable(104,133,104)} + -- CarCols[14] = {REN.GTA4ColorTable(16,133,76)} + -- CarCols[15] = {REN.GTA4ColorTable(9,133,91)} + -- CarCols[16] = {REN.GTA4ColorTable(15,133,93)} + -- CarCols[17] = {REN.GTA4ColorTable(19,133,93)} + -- CarCols[18] = {REN.GTA4ColorTable(13,133,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/intruder_wheel.mdl', + + CustomWheelPosFL = Vector(65,31,-11), + CustomWheelPosFR = Vector(65,-31,-11), + CustomWheelPosRL = Vector(-55,31,-11), + CustomWheelPosRR = Vector(-55,-31,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-3,-17,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(10,-17,-12), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-27,17,-12), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-17,-12), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-102,18.5,-13.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-102,23,-13.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 24, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 135.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,36,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/buffalo_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/taxi2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.15, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_intruder', V ) + +local V2 = {} +V2.Name = 'LC Triads Intruder VX' +V2.Model = 'models/octoteam/vehicles/intruder.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('011111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,32)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(122,117,96), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_intruder2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(88,31,4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(88,-31,4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101,26,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101,-26,9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(88,31,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(88,-31,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,25,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-25,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(30,17,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,25,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-25,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(30,15.5,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(96,31,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(96,-31,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-101,26,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-26,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-101,26,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-26,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(87,31,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,35,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,18.57,13.6), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(87,-31,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,-35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,-35,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,14.14,13.6), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [9] = '', + }, + Brake = { + [4] = '', + [9] = 'models/gta4/vehicles/intruder/endo_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/intruder/endo_lights_on', + [9] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/intruder/endo_lights_on', + [9] = 'models/gta4/vehicles/intruder/endo_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/intruder/endo_lights_on', + [9] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/intruder/endo_lights_on', + [9] = 'models/gta4/vehicles/intruder/endo_lights_on', + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/intruder/endo_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/intruder/endo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_intruder', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_landstalker.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_landstalker.lua new file mode 100644 index 0000000..246b8d7 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_landstalker.lua @@ -0,0 +1,402 @@ +local V = { + Name = 'Landstalker', + Model = 'models/octoteam/vehicles/landstalker.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2300.0, + + EnginePos = Vector(70,0,20), + + LightsTable = 'gta4_landstalker', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,95)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,4)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,9)} + -- CarCols[4] = {REN.GTA4ColorTable(6,6,63)} + -- CarCols[5] = {REN.GTA4ColorTable(40,40,27)} + -- CarCols[6] = {REN.GTA4ColorTable(57,57,51)} + -- CarCols[7] = {REN.GTA4ColorTable(64,64,63)} + -- CarCols[8] = {REN.GTA4ColorTable(85,85,118)} + -- CarCols[9] = {REN.GTA4ColorTable(88,88,87)} + -- CarCols[10] = {REN.GTA4ColorTable(98,98,91)} + -- CarCols[11] = {REN.GTA4ColorTable(104,104,103)} + -- CarCols[12] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[13] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[14] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[15] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[16] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/landstalker_wheel.mdl', + + CustomWheelPosFL = Vector(69,32,-1), + CustomWheelPosFR = Vector(69,-32,-1), + CustomWheelPosRL = Vector(-68,32,-1), + CustomWheelPosRR = Vector(-68,-32,-1), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(0,-17,37), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(10,-18,5), + ang = Angle(0,-90,10), + hasRadio = true, + }, + { + pos = Vector(-31,18,5), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-31,-18,5), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-116,30,-6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-116,-30,-6), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 32000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 135.0, + PowerbandStart = 2000, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-84,38,28), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/perennial_idle.wav', + + snd_low = 'octoteam/vehicles/perennial_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/perennial_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/perennial_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/perennial_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/perennial_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/patriot_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_landstalker', V ) + +local light_table = { + L_HeadLampPos = Vector(93,28,21), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-28,21), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-109,33,25), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-109,-33,25), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,28,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-28,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(37,17.5,28), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(96,23,20),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,-23,20),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(37,16.5,28), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(101,25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(101,-25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-109,33,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-33,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-111,34,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-111,-34,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-110,31,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-110,-31,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(89,33,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-109,35,21.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(37,19,28), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(89,-33,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-109,-35,21.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(37,15,28), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [9] = '', + [12] = '' + }, + Brake = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = '' + }, + Reverse = { + [10] = '', + [11] = '', + [9] = '', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + Brake_Reverse = { + [10] = '', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = '', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = '', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = '', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [11] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [9] = 'models/gta4/vehicles/landstalker/landstalker_lights_on', + [12] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + turnsignals = { + left = { + [14] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/landstalker/landstalker_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_landstalker', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_lokus.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_lokus.lua new file mode 100644 index 0000000..7422e61 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_lokus.lua @@ -0,0 +1,404 @@ +local V = { + Name = 'Lokus', + Model = 'models/octoteam/vehicles/lokus.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_lokus', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,12)} + -- CarCols[3] = {REN.GTA4ColorTable(7,7,12)} + -- CarCols[4] = {REN.GTA4ColorTable(20,20,12)} + -- CarCols[5] = {REN.GTA4ColorTable(45,45,31)} + -- CarCols[6] = {REN.GTA4ColorTable(40,40,32)} + -- CarCols[7] = {REN.GTA4ColorTable(49,49,49)} + -- CarCols[8] = {REN.GTA4ColorTable(52,52,56)} + -- CarCols[9] = {REN.GTA4ColorTable(62,62,65)} + -- CarCols[10] = {REN.GTA4ColorTable(70,70,63)} + -- CarCols[11] = {REN.GTA4ColorTable(71,71,65)} + -- CarCols[12] = {REN.GTA4ColorTable(77,77,71)} + -- CarCols[13] = {REN.GTA4ColorTable(104,104,103)} + -- CarCols[14] = {REN.GTA4ColorTable(106,106,103)} + -- CarCols[15] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[16] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[17] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[18] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[19] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/lokus_wheel.mdl', + + CustomWheelPosFL = Vector(58,31,-2), + CustomWheelPosFR = Vector(58,-31,-2), + CustomWheelPosRL = Vector(-58,31,-2), + CustomWheelPosRR = Vector(-58,-31,-2), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-18,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-17,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-40,17,-5), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-40,-17,-5), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104,18.5,-3), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-104,-18.5,-3), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 18, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 135.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,34,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/lokus_idle.wav', + + snd_low = 'octoteam/vehicles/lokus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/lokus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/lokus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/lokus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/lokus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/merit_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.15, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_lokus', V ) + +local light_table = { + L_HeadLampPos = Vector(82,28,10), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(82,-28,10), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-100,31,16), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-100,-31,16), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(82,28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(82,-28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(29,11,20), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(85,22,10),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(85,-22,10),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29,11,21), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(87,24,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(87,-24,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-100,31,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100,-31,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-101.5,24,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101.5,-24,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-101.5,24.7,18.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-101.5,-24.7,18.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(79,30.4,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-99.5,31,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.4,22.5,23), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(79,-30.4,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-99.5,-31,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.4,16,23), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [12] = '', + [9] = '', + [11] = '', + }, + Brake = { + [5] = '', + [12] = '', + [9] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [11] = '', + }, + Reverse = { + [5] = '', + [12] = '', + [9] = '', + [11] = 'models/gta4/vehicles/lokus/lokus_lights_on', + }, + Brake_Reverse = { + [5] = '', + [12] = '', + [9] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [11] = 'models/gta4/vehicles/lokus/lokus_lights_on', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = '', + [9] = '', + [11] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = '', + [9] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [11] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = '', + [9] = '', + [11] = 'models/gta4/vehicles/lokus/lokus_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = '', + [9] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [11] = 'models/gta4/vehicles/lokus/lokus_lights_on', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [9] = '', + [11] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [9] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [11] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/lokus/lokus_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [12] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [9] = 'models/gta4/vehicles/lokus/lokus_lights_on', + [11] = 'models/gta4/vehicles/lokus/lokus_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/lokus/lokus_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/lokus/lokus_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_lokus', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_manana.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_manana.lua new file mode 100644 index 0000000..3a250e4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_manana.lua @@ -0,0 +1,430 @@ +local V = { + Name = 'Manana', + Model = 'models/octoteam/vehicles/manana.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1500, + Trunk = { 35 }, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_manana', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,12)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,12)} + -- CarCols[3] = {REN.GTA4ColorTable(3,3,12)} + -- CarCols[9] = {REN.GTA4ColorTable(6,6,12)} + -- CarCols[7] = {REN.GTA4ColorTable(7,7,12)} + -- CarCols[6] = {REN.GTA4ColorTable(10,10,12)} + -- CarCols[7] = {REN.GTA4ColorTable(16,16,12)} + -- CarCols[8] = {REN.GTA4ColorTable(31,31,31)} + -- CarCols[9] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[10] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[11] = {REN.GTA4ColorTable(62,62,62)} + -- CarCols[12] = {REN.GTA4ColorTable(67,67,67)} + -- CarCols[13] = {REN.GTA4ColorTable(86,86,86)} + -- CarCols[14] = {REN.GTA4ColorTable(88,88,88)} + -- CarCols[15] = {REN.GTA4ColorTable(95,95,95)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/manana_wheel.mdl', + + CustomWheelPosFL = Vector(62,33,-10), + CustomWheelPosFR = Vector(62,-33,-10), + CustomWheelPosRL = Vector(-64,33,-10), + CustomWheelPosRR = Vector(-64,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-25,-18,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-10,-18,-14), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-110,28,-11.5), + ang = Angle(-120,0,0), + }, + { + pos = Vector(-110,-28,-11.5), + ang = Angle(-120,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 27000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 6, + RearConstant = 27000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 40, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-78,36,13.5), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 40, + + AirFriction = -50, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/esperanto_idle.wav', + + snd_low = 'octoteam/vehicles/esperanto_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/esperanto_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/esperanto_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/esperanto_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/esperanto_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/manana_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(7.566, 18.065, 12.497), ang = Angle(-0.0, -90.0, 74.3) }, + Radio = { pos = Vector(13.549, -0.866, 8.229), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(99.400, 0.001, -8.514), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-114.732, -0.006, 4.896), ang = Angle(-19.7, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(3.439, -0.000, 26.633), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(17.101, 40.335, 15.533), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(17.554, -40.878, 16.050), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_manana', V ) + +local light_table = { + L_HeadLampPos = Vector(92,32,5.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(92,-32,5.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-118,11,-3.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-118,-11,-3.5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(92,32,5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(92,-32,5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(92,24.5,5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(92,-24.5,5.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(8,27.5,13.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(92,32,5.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,-32,5.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,24.5,5.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,-24.5,5.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(8,27.5,12.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-118,11,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,29,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,-11,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,-29,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-118,11,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,29,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,-11,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-118,-29,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-118,20,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-118,-20,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(87,37,2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-113,38,1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(8,19.2,14.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(87,-37,2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-113,-38,1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(8,18.5,14.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [8] = '', + [10] = '' + }, + Brake = { + [9] = '', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = '' + }, + Reverse = { + [9] = '', + [8] = '', + [10] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + Brake_Reverse = { + [9] = '', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/manana/manana_lights_on', + [8] = 'models/gta4/vehicles/manana/manana_lights_on', + [10] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/manana/manana_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_manana', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_marbella.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_marbella.lua new file mode 100644 index 0000000..3c7392f --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_marbella.lua @@ -0,0 +1,388 @@ +local V = { + Name = 'Marbelle', + Model = 'models/octoteam/vehicles/marbella.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_marbella', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[2] = {REN.GTA4ColorTable(70,70,68)} + -- CarCols[3] = {REN.GTA4ColorTable(72,72,72)} + -- CarCols[4] = {REN.GTA4ColorTable(0,0,12)} + -- CarCols[5] = {REN.GTA4ColorTable(3,3,12)} + -- CarCols[6] = {REN.GTA4ColorTable(4,4,12)} + -- CarCols[7] = {REN.GTA4ColorTable(6,6,12)} + -- CarCols[8] = {REN.GTA4ColorTable(7,7,12)} + -- CarCols[9] = {REN.GTA4ColorTable(10,10,12)} + -- CarCols[10] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[11] = {REN.GTA4ColorTable(19,19,12)} + -- CarCols[12] = {REN.GTA4ColorTable(21,21,12)} + -- CarCols[13] = {REN.GTA4ColorTable(34,34,34)} + -- CarCols[14] = {REN.GTA4ColorTable(53,53,53)} + -- CarCols[15] = {REN.GTA4ColorTable(95,95,95)} + -- CarCols[16] = {REN.GTA4ColorTable(102,102,102)} + -- CarCols[17] = {REN.GTA4ColorTable(66,66,66)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/marbella_wheel.mdl', + + CustomWheelPosFL = Vector(62,31,-10), + CustomWheelPosFR = Vector(62,-31,-10), + CustomWheelPosRL = Vector(-62,31,-10), + CustomWheelPosRR = Vector(-62,-31,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-12,-17.5,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-18,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-36,20,-11), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-36,-20,-11), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-105,-28,-9), + ang = Angle(-130,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 63, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 15, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 130.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-77,34,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 75, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/marbella_idle.wav', + + snd_low = 'octoteam/vehicles/marbella_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/marbella_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/marbella_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/marbella_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/marbella_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/marbella_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_marbella', V ) + +local light_table = { + L_HeadLampPos = Vector(94,26,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(94,-26,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-107,19,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-107,-19,9), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(94,20,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,-20,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,26,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,-26,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(94,20,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(94,-20,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(94,26,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(94,-26,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-107,19,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,-19,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-107,19,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,0,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,-19,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-114,25,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-114,-25,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(98,32,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + { + pos = Vector(-111,32,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(19,16.5,17), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(98,-32,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + { + pos = Vector(-111,-32,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(19,15.5,17), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [7] = '', + [12] = '', + [11] = '' + }, + Brake = { + [9] = '', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [11] = '' + }, + Reverse = { + [9] = '', + [7] = '', + [12] = '', + [11] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + Brake_Reverse = { + [9] = '', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [11] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = '', + [11] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [11] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = '', + [11] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [11] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = '', + [11] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [11] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = '', + [11] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [7] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [12] = 'models/gta4/vehicles/marbella/marbella_lights_on', + [11] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/marbella/marbella_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_marbella', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_merit.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_merit.lua new file mode 100644 index 0000000..cf31f7d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_merit.lua @@ -0,0 +1,424 @@ +local V = { + Name = 'Merit', + Model = 'models/octoteam/vehicles/merit.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1450, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_merit', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(1,0,7)} + -- CarCols[2] = {REN.GTA4ColorTable(10,0,10)} + -- CarCols[3] = {REN.GTA4ColorTable(33,33,27)} + -- CarCols[4] = {REN.GTA4ColorTable(45,1,30)} + -- CarCols[5] = {REN.GTA4ColorTable(49,49,50)} + -- CarCols[6] = {REN.GTA4ColorTable(50,49,51)} + -- CarCols[7] = {REN.GTA4ColorTable(57,0,58)} + -- CarCols[8] = {REN.GTA4ColorTable(64,64,63)} + -- CarCols[9] = {REN.GTA4ColorTable(68,64,8)} + -- CarCols[10] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[11] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[12] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[13] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[14] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[15] = {REN.GTA4ColorTable(2,133,63)} + -- CarCols[16] = {REN.GTA4ColorTable(21,133,72)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/merit_wheel.mdl', + + CustomWheelPosFL = Vector(65,32,-16), + CustomWheelPosFR = Vector(65,-32,-16), + CustomWheelPosRL = Vector(-59,32,-16), + CustomWheelPosRR = Vector(-59,-32,-16), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 32, + + SeatOffset = Vector(0,-19.5,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-20,-18), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104.4,21.3,-15.2), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 28000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 7, + RearConstant = 28000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 39, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-82.4,-34.3,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + AirFriction = -45, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/merit_idle.wav', + + snd_low = 'octoteam/vehicles/merit_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/merit_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/merit_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/merit_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/merit_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/merit_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(30.752, 18.121, 11.628), ang = Angle(-0.0, -90.0, 70.6) }, + Radio = { pos = Vector(33.077, 1.293, 2.084), ang = Angle(-21.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(105.428, 13.770, -11.570), ang = Angle(9.1, 12.8, -0.0) }, + Back = { pos = Vector(-102.606, -0.002, 6.751), ang = Angle(-9.2, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(18.062, 0.002, 25.635), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(28.599, 40.558, 12.955), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.430, -40.797, 12.255), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_merit', V ) + +local light_table = { + L_HeadLampPos = Vector(95.5,29.2,-1.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95.5,-29.2,-1.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101.7,28,5.9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101.7,-28,5.9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(95.5,29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95.5,-29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(30.6,24.7,10.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(97.7,23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(97.7,-23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(30.6,24.7,9.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(98.7,29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(98.7,-29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-101.7,28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101.7,-28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-102.7,20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102.7,-20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-102.2,21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-102.2,-21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(92.4,33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,20.8,12.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(92.4,-33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,-29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,15.5,12.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [12] = '', + [13] = '', + [11] = '' + }, + Brake = { + [3] = '', + [12] = 'models/gta4/vehicles/merit/police2_lights_on', + [13] = '', + [11] = '' + }, + Reverse = { + [3] = '', + [12] = '', + [13] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '' + }, + Brake_Reverse = { + [3] = '', + [12] = 'models/gta4/vehicles/merit/police2_lights_on', + [13] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '', + [13] = '', + [11] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = 'models/gta4/vehicles/merit/police2_lights_on', + [13] = '', + [11] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '', + [13] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = 'models/gta4/vehicles/merit/police2_lights_on', + [13] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '', + [13] = '', + [11] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = 'models/gta4/vehicles/merit/police2_lights_on', + [13] = '', + [11] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Reverse = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '', + [13] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = 'models/gta4/vehicles/merit/police2_lights_on', + [13] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_merit', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_merit_alpha.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_merit_alpha.lua new file mode 100644 index 0000000..d3242c9 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_merit_alpha.lua @@ -0,0 +1,734 @@ +local V = { + Name = 'Merit (Alpha)', + Model = 'models/octoteam/vehicles/police2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 1700, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_merit_alpha', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/police2_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(65,32,-16), + CustomWheelPosFR = Vector(65,-32,-16), + CustomWheelPosRL = Vector(-59,32,-16), + CustomWheelPosRR = Vector(-59,-32,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 32, + + SeatOffset = Vector(0,-19.5,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-20,-18), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104.4,21.3,-15.2), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 37000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 6, + RearConstant = 37000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 5, + CounterSteeringMul = 0.85, + + MaxGrip = 56, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 52, + PowerbandStart = 1200, + PowerbandEnd = 6600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.2, + + FuelFillPos = Vector(-82.4,-34.3,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.04, + snd_idle = 'octoteam/vehicles/merit_idle.wav', + + snd_low = 'octoteam/vehicles/merit_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/merit_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/merit_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/merit_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/merit_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/police/warning.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(26.848, 18.013, 8.520), ang = Angle(-0.0, -90.0, 79.2) }, + Radio = { pos = Vector(33.926, 0.008, 4.649), ang = Angle(-22.0, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(105.224, 15.504, -11.844), ang = Angle(0.0, 14.5, 0.0) }, + Back = { pos = Vector(-102.317, 0.011, 6.691), ang = Angle(-9.3, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(17.398, -0.002, 26.016), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(27.358, 40.902, 12.988), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(27.295, -40.646, 13.462), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_merit_alpha', V ) + +local colOff = Color(0,0,0, 0) +local emsCenter = Vector(-1, 0, 34.7) + +local light_table = { + L_HeadLampPos = Vector(95.5,29.2,-1.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95.5,-29.2,-1.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101.7,28,5.9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101.7,-28,5.9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(95.5,29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95.5,-29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(30.6,24.7,10.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(97.7,23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(97.7,-23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(30.6,24.7,9.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(98.7,29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(98.7,-29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-101.7,28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101.7,-28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-102.7,20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102.7,-20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-102.2,21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-102.2,-21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + -- + -- FRONT + -- + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 0, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + + -- + -- REAR + -- + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + + -- + -- SIDE + -- + { + pos = emsCenter + Vector(5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, -25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, 25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,135,0,150), colOff, Color(255,135,0,150), colOff, + }, + Speed = 0.07 + }, + + -- + -- HEAD/TAIL LIGHTS + -- + { + pos = Vector(98.5, 29.2, -13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(98.5, -29.2, -13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-102.2, 21.2, 10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-102.2, -21.2, 10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(92.4,33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,20.8,12.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(92.4,-33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,-29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,15.5,12.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [11] = '', + [9] = '', + [5] = '' + }, + Brake = { + [4] = '', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [5] = '' + }, + Reverse = { + [4] = '', + [11] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + Brake_Reverse = { + [4] = '', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = '', + [5] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [5] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = '', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_merit_alpha', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_minivan.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_minivan.lua new file mode 100644 index 0000000..322d891 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_minivan.lua @@ -0,0 +1,373 @@ +local V = { + Name = 'Minivan', + Model = 'models/octoteam/vehicles/minivan.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2300.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_minivan', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,2)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[3] = {REN.GTA4ColorTable(3,3,2)} + -- CarCols[4] = {REN.GTA4ColorTable(6,6,8)} + -- CarCols[5] = {REN.GTA4ColorTable(7,7,8)} + -- CarCols[6] = {REN.GTA4ColorTable(10,10,12)} + -- CarCols[7] = {REN.GTA4ColorTable(26,26,12)} + -- CarCols[8] = {REN.GTA4ColorTable(45,45,32)} + -- CarCols[9] = {REN.GTA4ColorTable(62,62,53)} + -- CarCols[10] = {REN.GTA4ColorTable(71,71,71)} + -- CarCols[11] = {REN.GTA4ColorTable(77,77,76)} + -- CarCols[12] = {REN.GTA4ColorTable(87,87,86)} + -- CarCols[13] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[14] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[15] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[16] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[17] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/minivan_wheel.mdl', + + CustomWheelPosFL = Vector(63,33,-19), + CustomWheelPosFR = Vector(63,-33,-19), + CustomWheelPosRL = Vector(-63,33,-19), + CustomWheelPosRR = Vector(-63,-33,-19), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 34, + + SeatOffset = Vector(6,-19.5,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(15,-18,-13), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,20,-13), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-27,-20,-13), + ang = Angle(0,-90,15) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104,27,-20), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 35000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 35000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 130.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,39,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/minivan_idle.wav', + + snd_low = 'octoteam/vehicles/minivan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/minivan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/minivan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/minivan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/minivan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/minivan_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.25,0,0.15,0.35,0.5,0.75} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_minivan', V ) + +local light_table = { + L_HeadLampPos = Vector(95,29,-2.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-29,-2.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101,36,3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101,-36,3), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(95,29,-2.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-29,-2.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(39,18.5,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,29,-2.6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-29,-2.6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(39,19,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-101,36,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-36,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-101,36,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-36,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-101,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-101,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,36,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-101,36,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,19.5,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(86,-36,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-101,-36,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,18,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [12] = '', + [9] = '' + }, + Brake = { + [7] = '', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [9] = '' + }, + Reverse = { + [7] = '', + [12] = '', + [9] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + Brake_Reverse = { + [7] = '', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [9] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [9] = '' + }, + Brake = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [9] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [9] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [9] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [9] = '' + }, + Brake = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [9] = '' + }, + Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = '', + [9] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [12] = 'models/gta4/vehicles/minivan/taxivan_lights_on', + [9] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + right = { + [3] = 'models/gta4/vehicles/minivan/taxivan_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_minivan', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_moonbeam.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_moonbeam.lua new file mode 100644 index 0000000..cb4f1cc --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_moonbeam.lua @@ -0,0 +1,692 @@ +local V = { + Name = 'Moonbeam', + Model = 'models/octoteam/vehicles/moonbeam.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2600, + Trunk = { + nil, + {80, 1, 2}, + }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_moonbeam', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2)..math.random(0,3)..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(102,104,104)} + -- CarCols[2] = {REN.GTA4ColorTable(103,102,103)} + -- CarCols[3] = {REN.GTA4ColorTable(108,109,108)} + -- CarCols[4] = {REN.GTA4ColorTable(11,102,12)} + -- CarCols[5] = {REN.GTA4ColorTable(10,1,12)} + -- CarCols[6] = {REN.GTA4ColorTable(1,7,12)} + -- CarCols[7] = {REN.GTA4ColorTable(20,17,12)} + -- CarCols[8] = {REN.GTA4ColorTable(31,33,27)} + -- CarCols[9] = {REN.GTA4ColorTable(40,41,30)} + -- CarCols[10] = {REN.GTA4ColorTable(52,53,51)} + -- CarCols[11] = {REN.GTA4ColorTable(60,57,53)} + -- CarCols[12] = {REN.GTA4ColorTable(67,64,63)} + -- CarCols[13] = {REN.GTA4ColorTable(77,73,68)} + -- CarCols[14] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[15] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[16] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[17] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[18] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/moonbeam_wheel.mdl', + + CustomWheelPosFL = Vector(62,32,-13), + CustomWheelPosFR = Vector(62,-32,-13), + CustomWheelPosRL = Vector(-62,34,-13), + CustomWheelPosRR = Vector(-62,-34,-13), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,15), + + CustomSteerAngle = 30, + + SeatOffset = Vector(2,-19,30), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(14,-19,-3), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-33,20,-4), + ang = Angle(0,-90,10) + }, + -- { + -- pos = Vector(-33,0,-3), + -- ang = Angle(0,-90,10) + -- }, + { + pos = Vector(-33,-19,-5), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-75,20,4), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-75,0,4), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-75,-20,4), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-101.7,-17.8,-13.5), + ang = Angle(-120,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 60000, + FrontDamping = 1500, + FrontRelativeDamping = 1500, + + RearHeight = 6, + RearConstant = 60000, + RearDamping = 1500, + RearRelativeDamping = 1500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 70, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 42, + PowerbandStart = 1700, + PowerbandEnd = 4800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-80,40,20), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 80, + + AirFriction = -15, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/moonbeam_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(39.974, 19.916, 24.007), ang = Angle(-0.0, -90.0, 79.0) }, + Radio = { pos = Vector(39.335, -1.035, 17.923), ang = Angle(-0.0, 155.8, 0.0) }, + Plates = { + Front = { pos = Vector(93.231, -0.002, -6.893), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-108.438, -0.007, -7.804), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(30.283, -0.007, 45.993), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(43.020, 40.054, 31.419), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(43.707, -40.297, 30.792), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_moonbeam', V ) + +local light_table = { + L_HeadLampPos = Vector(85,26,11), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(85,-26,11), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-103,36,18), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-103,-36,18), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(85,26,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(85,-26,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(41,22.3,26), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(85,26,11),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(85,-26,11),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(41,23.1,26), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-103,36,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-36,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103,36,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-36,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-103,36,14.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-103,-36,14.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(33.6, 21.7, 49.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(33.6, -21.7, 49.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.034 + }, + { + pos = Vector(33.6, 6.4, 49.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 45, + Colors = { + Color(255, 0, 0), + Color(255, 0, 0), + Color(0,0,0,0), + -- + Color(255, 0, 0), + Color(255, 0, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 0, 0), + Color(255, 0, 0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(33.6, -6.4, 49.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 45, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 0, 0), + Color(255, 0, 0), + Color(0,0,0,0), + -- + Color(255, 0, 0), + Color(255, 0, 0), + Color(0,0,0,0), + -- + Color(255, 0, 0), + Color(255, 0, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-98.1, 19.5, 46.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,255), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-98.1, -19.5, 46.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-98.1, -4.1, 46.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-98.1, 4.1, 46.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(84,34,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,36,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,135,150), + }, + +--[[ { + pos = Vector(41,23.7,25.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(84,-34,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-103,-36,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,135,150), + }, + +--[[ { + pos = Vector(41,18.6,25.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [12] = '', + [10] = '', + [11] = '', + }, + Brake = { + [12] = '', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = '', + }, + Reverse = { + [12] = '', + [10] = '', + [11] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + Brake_Reverse = { + [12] = '', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + }, + on_lowbeam = { + Base = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = '', + }, + Brake = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = '', + }, + Reverse = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + Brake_Reverse = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + }, + on_highbeam = { + Base = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = '', + }, + Brake = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = '', + }, + Reverse = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + Brake_Reverse = { + [12] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [10] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on', + [11] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/moonbeam/moonbeam_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_moonbeam', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_mrtasty.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_mrtasty.lua new file mode 100644 index 0000000..a34f7f3 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_mrtasty.lua @@ -0,0 +1,347 @@ +local V = { + Name = 'Mr Tasty', + Model = 'models/octoteam/vehicles/mrtasty.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 3000.0, + + EnginePos = Vector(80,0,20), + + LightsTable = 'gta4_mrtasty', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,8)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,66,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 2, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/mrtasty_wheel.mdl', + + CustomWheelPosFL = Vector(65,33,-8), + CustomWheelPosFR = Vector(65,-33,-8), + CustomWheelPosRL = Vector(-65,33,-8), + CustomWheelPosRR = Vector(-65,-33,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 13.3, + RearWheelRadius = 13.3, + + CustomMassCenter = Vector(0,0,20), + + CustomSteerAngle = 30, + + SeatOffset = Vector(15,-22,50), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(30,-22,13), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-117,-28.3,-13.6), + ang = Angle(-135,35,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 35000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 35000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 110.0, + PowerbandStart = 1500, + PowerbandEnd = 4000, + Turbocharged = true, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-94,-39,2), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/ambulance_idle.wav', + + snd_low = 'octoteam/vehicles/ambulance_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/ambulance_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/ambulance_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/ambulance_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/ambulance_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/boxville_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.11, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_mrtasty', V ) + +local light_table = { + L_HeadLampPos = Vector(94,31.7,9.2), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(94,-31.7,9.2), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-123.5,22.8,-2.4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-123.5,-22.8,-2.4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(94,31.7,9.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,26.6,9.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,-26.6,9.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,-31.7,9.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(53,17,34), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,26.6,9.2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-26.6,9.2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(53,25,34), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-123.5,22.8,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-123.5,-22.8,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-123.5,22.8,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 50, + color = Color(255,0,0,150), + }, + { + pos = Vector(-123.5,-22.8,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 50, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-123.5,16.2,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,255,255,150), + }, + { + pos = Vector(-123.5,-16.2,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = { + 'octoteam/vehicles/mrtasty_song_1.wav', + 'octoteam/vehicles/mrtasty_song_2.wav', + 'octoteam/vehicles/mrtasty_song_3.wav', + 'octoteam/vehicles/mrtasty_song_4.wav', + 'octoteam/vehicles/mrtasty_song_5.wav', + 'octoteam/vehicles/mrtasty_song_6.wav', + 'octoteam/vehicles/mrtasty_song_gtasa.wav', + }, + ems_sprites = { + { + pos = Vector(0,0,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 0, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 1 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(94,29.2,4.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-123.5,29.2,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(54,25,35), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(94,-29.2,4.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-123.5,-29.2,-2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(54,17,35), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [7] = '' + }, + Reverse = { + [4] = '', + [7] = 'models/gta4/vehicles/boxville/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/boxville/detail2_on', + [7] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/boxville/detail2_on', + [7] = 'models/gta4/vehicles/boxville/detail2_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/boxville/detail2_on', + [7] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/boxville/detail2_on', + [7] = 'models/gta4/vehicles/boxville/detail2_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/boxville/detail2_on' + }, + right = { + [8] = 'models/gta4/vehicles/boxville/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_mrtasty', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_mule.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_mule.lua new file mode 100644 index 0000000..b9256f8 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_mule.lua @@ -0,0 +1,390 @@ +local V = { + Name = 'Mule', + Model = 'models/octoteam/vehicles/mule.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 4000, + Trunk = { 600 }, + + EnginePos = Vector(110,0,20), + + LightsTable = 'gta4_mule', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,11)) + -- ent:SetBodyGroups('0'..math.random(0,6) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(112,2,113)} + -- CarCols[2] = {REN.GTA4ColorTable(116,1,115)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/mule_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/mule_wheel_r.mdl', + + CustomWheelPosFL = Vector(100,40,-31), + CustomWheelPosFR = Vector(100,-40,-31), + CustomWheelPosRL = Vector(-88,42,-31), + CustomWheelPosRR = Vector(-88,-42,-31), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 20.1, + RearWheelRadius = 20.2, + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(93,-25,51), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(105,-25,0), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-29,14,-32), + ang = Angle(-120,-25,0), + }, + }, + + FrontHeight = 20, + FrontConstant = 25000, + FrontDamping = 3000, + FrontRelativeDamping = 3000, + + RearHeight = 20, + RearConstant = 25000, + RearDamping = 3000, + RearRelativeDamping = 3000, + + StrengthenSuspension = true, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 110, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 45, + PowerbandStart = 1700, + PowerbandEnd = 4800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 3, + + FuelFillPos = Vector(49,39,-17), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + AirFriction = -60, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/benson_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.11, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1,1.25}, + + Dash = { pos = Vector(128.397, 23.858, 28.242), ang = Angle(-0.0, -90.0, 66.4) }, + Radio = { pos = Vector(133.440, 0.006, 27.802), ang = Angle(-23.4, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(155.232, -0.001, -25.789), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-157.296, 13.530, -18.833), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(125.490, 49.212, 37.553), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(124.891, -49.048, 36.249), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_mule', V ) + +local light_table = { + L_HeadLampPos = Vector(150,35,-13), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(150,-35,-13), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-158,28,-22), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-158,-28,-22), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(150,35,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(150,-35,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(128,27,27.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(151,28,-13),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(151,-28,-13),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(128,26,27.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-158,28,-22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-158,-28,-22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-158,28,-15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-158,-28,-15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-158,29,-27), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-158,-29,-27), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(149,41,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-175,43,-6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(129,28,29.5), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(149,-41,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-175,-43,-6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(129,19,29.5), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [2] = '', + [11] = '', + [7] = '', + [10] = '', + }, + Brake = { + [2] = '', + [11] = '', + [7] = 'models/gta4/vehicles/mule/detail2_on', + [10] = '', + }, + Reverse = { + [2] = '', + [11] = '', + [7] = '', + [10] = 'models/gta4/vehicles/mule/detail2_on', + }, + Brake_Reverse = { + [2] = '', + [11] = '', + [7] = 'models/gta4/vehicles/mule/detail2_on', + [10] = 'models/gta4/vehicles/mule/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = '', + [7] = '', + [10] = '', + }, + Brake = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = '', + [7] = 'models/gta4/vehicles/mule/detail2_on', + [10] = '', + }, + Reverse = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = '', + [7] = '', + [10] = 'models/gta4/vehicles/mule/detail2_on', + }, + Brake_Reverse = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = '', + [7] = 'models/gta4/vehicles/mule/detail2_on', + [10] = 'models/gta4/vehicles/mule/detail2_on', + }, + }, + on_highbeam = { + Base = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = 'models/gta4/vehicles/mule/detail2_on', + [7] = '', + [10] = '', + }, + Brake = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = 'models/gta4/vehicles/mule/detail2_on', + [7] = 'models/gta4/vehicles/mule/detail2_on', + [10] = '', + }, + Reverse = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = 'models/gta4/vehicles/mule/detail2_on', + [7] = '', + [10] = 'models/gta4/vehicles/mule/detail2_on', + }, + Brake_Reverse = { + [2] = 'models/gta4/vehicles/mule/detail2_on', + [11] = 'models/gta4/vehicles/mule/detail2_on', + [7] = 'models/gta4/vehicles/mule/detail2_on', + [10] = 'models/gta4/vehicles/mule/detail2_on', + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/mule/detail2_on' + }, + right = { + [9] = 'models/gta4/vehicles/mule/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_mule', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_noose.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_noose.lua new file mode 100644 index 0000000..107e21b --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_noose.lua @@ -0,0 +1,563 @@ +local V = { + Name = 'NOOSE Cruiser', + Model = 'models/octoteam/vehicles/noose.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_noose', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,113,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelModel = 'models/octoteam/vehicles/noose_wheel.mdl', + + CustomWheelPosFL = Vector(60,35,-13), + CustomWheelPosFR = Vector(60,-35,-13), + CustomWheelPosRL = Vector(-60,35,-13), + CustomWheelPosRR = Vector(-60,-35,-13), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-19.5,16), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-20,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-32,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-32,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-114.6,22.3,-14.3), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 87, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 22, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 150.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-82.4,-37,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/stainer_idle.wav', + + snd_low = 'octoteam/vehicles/stainer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stainer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stainer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stainer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stainer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stainer_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.22, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_noose', V ) + +local light_table = { + L_HeadLampPos = Vector(98,25,2.3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(98,-25,2.3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-114,30,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-114,-30,5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(98,25,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-25,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(23,19,11.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(98,25,2.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-25,2.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,18,11.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-114,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-114,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-114,21.7,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-114,-21.7,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'GTA4_SIREN_WAIL','GTA4_SIREN_YELP','GTA4_SIREN_WARNING'}, + ems_sprites = { + { + pos = Vector(-17.3,16.5,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + { + pos = Vector(-17.3,11.1,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-17.3,5.6,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-17.3,0,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-17.3,-5.6,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-17.3,-11.1,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-17.3,-16.5,37), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(93.8,34.6,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,20,11.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-114,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(93.8,-34.6,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,17,11.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-114,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [5] = '', + [4] = '' + }, + Brake = { + [11] = '', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Reverse = { + [11] = '', + [5] = '', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [11] = '', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_noose', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_nstockade.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_nstockade.lua new file mode 100644 index 0000000..1b839a5 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_nstockade.lua @@ -0,0 +1,558 @@ +local V = { + Name = 'Enforcer', + Model = 'models/octoteam/vehicles/nstockade.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 5000, + Trunk = { 100 }, + + EnginePos = Vector(102,0,34), + + LightsTable = 'gta4_nstockade', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + MaxHealth = 5000, + IsArmored = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelModel = 'models/octoteam/vehicles/nstockade_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/nstockade_wheel_r.mdl', + + CustomWheelPosFL = Vector(93,44,-4), + CustomWheelPosFR = Vector(93,-44,-4), + CustomWheelPosRL = Vector(-93,44,-4), + CustomWheelPosRR = Vector(-93,-44,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 35, + + SeatOffset = Vector(20,-30,70), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(30,-29,30), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-131,-40,30), + ang = Angle(0,0,0), + noMirrors = true, + }, + { + pos = Vector(-105,-40,30), + ang = Angle(0,0,0), + noMirrors = true, + }, + { + pos = Vector(-131,40,30), + ang = Angle(0,180,0), + noMirrors = true, + }, + { + pos = Vector(-105,40,30), + ang = Angle(0,180,0), + noMirrors = true, + }, + { + pos = Vector(-160,-20,-6), + ang = Angle(0,90,0), + noMirrors = true, + }, + { + pos = Vector(-160,20,-6), + ang = Angle(0,90,0), + noMirrors = true, + }, + }, + ExhaustPositions = { + { + pos = Vector(-56.1,-46.4,-11.3), + ang = Angle(-90,90,0), + }, + { + pos = Vector(-52.1,-46.4,-11.3), + ang = Angle(-90,90,0), + }, + }, + + StrengthenSuspension = true, + + FrontHeight = 10, + FrontConstant = 35000, + FrontDamping = 4000, + FrontRelativeDamping = 4000, + + RearHeight = 10, + RearConstant = 35000, + RearDamping = 4000, + RearRelativeDamping = 4000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = true, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 75, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-10.1,50.6,6.7), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stockade_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(56.072, 29.429, 53.880), ang = Angle(0.0, -90.0, 71.9) }, + Radio = { pos = Vector(60.643, 3.031, 55.760), ang = Angle(-25.4, 156.2, 3.7) }, + Plates = { + Front = { pos = Vector(140.167, 0.014, 4.973), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-146.770, 18.756, 7.788), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(53.704, 57.466, 67.447), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(53.932, -57.128, 69.094), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_nstockade', V ) + +local light_table = { + L_HeadLampPos = Vector(128,41,19), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(128,-41,19), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-148.7,42.6,33.4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-148.7,-42.6,33.4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(128,41,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(128,-41,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(58,37,55), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(128,41,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(128,-41,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(58,38,55), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-148.7,42.6,33.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-148.7,-42.6,33.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-148.7,42.6,78.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-148.7,-42.6,78.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/swat/siren1.wav','octoteam/vehicles/swat/siren2.wav'}, + ems_sprites = { + { + pos = Vector(70.7,36,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,24,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0, 0, 255,50), + Color(0, 0, 255,100), + -- + Color(0, 0, 255,150), + Color(0, 0, 255,255), + Color(0, 0, 255,150), + -- + Color(0, 0, 255,100), + Color(0, 0, 255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,12,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,0,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0, 0, 255,50), + Color(0, 0, 255,100), + -- + Color(0, 0, 255,150), + Color(0, 0, 255,255), + Color(0, 0, 255,150), + -- + Color(0, 0, 255,100), + Color(0, 0, 255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,-12,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,-24,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0, 0, 255,50), + Color(0, 0, 255,100), + -- + Color(0, 0, 255,150), + Color(0, 0, 255,255), + Color(0, 0, 255,150), + -- + Color(0, 0, 255,100), + Color(0, 0, 255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,-36,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-148.7,42.6,23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(60,35,57), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-148.7,-42.6,23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(60,22,57), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [2] = '', + [3] = '' + }, + Brake = { + [2] = '', + [3] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [2] = 'models/gta4/vehicles/stockade/detail2_on', + [3] = '' + }, + Brake = { + [2] = 'models/gta4/vehicles/stockade/detail2_on', + [3] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + on_highbeam = { + Base = { + [2] = 'models/gta4/vehicles/stockade/detail2_on', + [3] = '' + }, + Brake = { + [2] = 'models/gta4/vehicles/stockade/detail2_on', + [3] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/stockade/detail2_on' + }, + right = { + [9] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_nstockade', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_oracle.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_oracle.lua new file mode 100644 index 0000000..4ee3809 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_oracle.lua @@ -0,0 +1,437 @@ +local V = { + Name = 'Oracle', + Model = 'models/octoteam/vehicles/oracle.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1800.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_oracle', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('00100' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,61)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,54)} + -- CarCols[4] = {REN.GTA4ColorTable(7,7,48)} + -- CarCols[5] = {REN.GTA4ColorTable(10,10,18)} + -- CarCols[6] = {REN.GTA4ColorTable(11,11,97)} + -- CarCols[7] = {REN.GTA4ColorTable(20,20,86)} + -- CarCols[8] = {REN.GTA4ColorTable(22,22,66)} + -- CarCols[9] = {REN.GTA4ColorTable(46,46,91)} + -- CarCols[10] = {REN.GTA4ColorTable(40,40,35)} + -- CarCols[11] = {REN.GTA4ColorTable(49,49,53)} + -- CarCols[12] = {REN.GTA4ColorTable(52,52,51)} + -- CarCols[13] = {REN.GTA4ColorTable(54,54,53)} + -- CarCols[14] = {REN.GTA4ColorTable(57,57,58)} + -- CarCols[15] = {REN.GTA4ColorTable(70,70,118)} + -- CarCols[16] = {REN.GTA4ColorTable(76,76,118)} + -- CarCols[17] = {REN.GTA4ColorTable(106,106,108)} + -- CarCols[18] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[19] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[20] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[21] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[22] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/oracle_wheel.mdl', + + CustomWheelPosFL = Vector(63,33,-20), + CustomWheelPosFR = Vector(63,-33,-20), + CustomWheelPosRL = Vector(-63,33,-20), + CustomWheelPosRR = Vector(-63,-33,-20), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-18,10), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-17,-20), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-37,17,-20), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-37,-17,-20), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-105,25,-21), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-105,-25,-21), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 83, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 143.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-73,37,5), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_oracle', V ) + +local V2 = {} +V2.Name = 'Irish Mob Oracle' +V2.Model = 'models/octoteam/vehicles/oracle.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,20) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01011' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,57)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +list.Set('simfphys_vehicles', 'sim_fphys_gta4_oracle2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(86,32,-4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(86,-32,-4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-100,33,2), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-100,-33,2), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(86,32,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(86,-32,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(28,29,7), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(90,25,-4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-25,-4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(28,29,6), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(93,28,-21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-28,-21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-100,33,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100,-33,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-100,33,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100,-33,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-105,14,5.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-105,-14,5.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(92,29,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-100,33,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(28,24,9), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(92,-29,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-100,-33,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(28,17.5,9), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [5] = '', + [11] = '', + [10] = '', + }, + Brake = { + [4] = '', + [5] = '', + [11] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [10] = '', + }, + Reverse = { + [4] = '', + [5] = '', + [11] = '', + [10] = 'models/gta4/vehicles/oracle/oracle_lights_on', + }, + Brake_Reverse = { + [4] = '', + [5] = '', + [11] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [10] = 'models/gta4/vehicles/oracle/oracle_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = '', + [11] = '', + [10] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = '', + [11] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = '', + [11] = '', + [10] = 'models/gta4/vehicles/oracle/oracle_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = '', + [11] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [10] = 'models/gta4/vehicles/oracle/oracle_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [11] = '', + [10] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [11] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [11] = '', + [10] = 'models/gta4/vehicles/oracle/oracle_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [5] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [11] = 'models/gta4/vehicles/oracle/oracle_lights_on', + [10] = 'models/gta4/vehicles/oracle/oracle_lights_on', + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/oracle/oracle_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/oracle/oracle_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_oracle', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_packer.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_packer.lua new file mode 100644 index 0000000..5857d6d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_packer.lua @@ -0,0 +1,376 @@ +local V = { + Name = 'Packer', + Model = 'models/octoteam/vehicles/packer.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 12000.0, + + EnginePos = Vector(130,0,10), + + LightsTable = 'gta4_packer', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(76,113,113)} + -- CarCols[2] = {REN.GTA4ColorTable(64,79,79)} + -- CarCols[3] = {REN.GTA4ColorTable(56,58,59)} + -- CarCols[4] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[5] = {REN.GTA4ColorTable(8,31,8)} + -- CarCols[6] = {REN.GTA4ColorTable(1,78,8)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + -- REN.GTA4Packer(ent, math.random(0,2)) + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/packer_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/packer_wheel_r.mdl', + + CustomWheelPosFL = Vector(126,47,-37), + CustomWheelPosFR = Vector(126,-47,-37), + CustomWheelPosML = Vector(-68,40,-37), + CustomWheelPosMR = Vector(-68,-40,-37), + CustomWheelPosRL = Vector(-123,40,-37), + CustomWheelPosRR = Vector(-123,-40,-37), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(105,-34,75), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(120,-32,20), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-32,19,-26), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-32,-19,-26), + ang = Angle(-90,0,0), + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 22, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 148, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 105.0, + PowerbandStart = 1700, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(50,55,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 140, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/firetruk_idle.wav', + + snd_low = 'octoteam/vehicles/firetruk_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/firetruk_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/firetruk_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/firetruk_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/firetruk_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'BUS_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.1, + Gears = {-0.6,0,0.2,0.35,0.5,0.75,1,1.25,1.5}, + + Dash = { pos = Vector(146.133, 33.316, 47.920), ang = Angle(-0.0, -90.0, 61) }, + Radio = { pos = Vector(149.366, 5.902, 48.122), ang = Angle(-25.3, 158.7, 3.2) }, + Plates = { + Front = { pos = Vector(162.456, 0, -22.916), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-160.683, 0, -14.038), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(135.762, 66.460, 56.646), + h = 3 / 4, + ratio = 1 / 2.5, + }, + right = { + pos = Vector(135.762, -66.460, 56.646), + h = 3 / 4, + ratio = 1 / 2.5, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_packer', V ) + +local light_table = { + L_HeadLampPos = Vector(159,47,-5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(159,-47,-5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-163,31,-8), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-163,-31,-8), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(159,47,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(159,-47,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(147,34,48), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(159,39,-5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(159,-39,-5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(147,35,48), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-163,31,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163,-31,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163,38,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163,-38,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-163,31,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163,-31,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163,38,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-163,-38,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-163,15.5,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-163,-15.5,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(159,43,-11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-163,44,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(148,38,50), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(159,-43,-11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-163,-44,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(148,26,50), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [12] = '', + [9] = '' + }, + Reverse = { + [11] = '', + [12] = '', + [9] = 'models/gta4/vehicles/packer/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/packer/detail2_on', + [12] = '', + [9] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/packer/detail2_on', + [12] = '', + [9] = 'models/gta4/vehicles/packer/detail2_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/packer/detail2_on', + [12] = 'models/gta4/vehicles/packer/detail2_on', + [9] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/packer/detail2_on', + [12] = 'models/gta4/vehicles/packer/detail2_on', + [9] = 'models/gta4/vehicles/packer/detail2_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/packer/detail2_on' + }, + right = { + [7] = 'models/gta4/vehicles/packer/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_packer', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_patriot.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_patriot.lua new file mode 100644 index 0000000..ff73a56 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_patriot.lua @@ -0,0 +1,435 @@ +local V = { + Name = 'Patriot', + Model = 'models/octoteam/vehicles/patriot.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 3200, + Trunk = { 100 }, + + EnginePos = Vector(70,0,20), + + LightsTable = 'gta4_patriot', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,5)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,0 )} + -- CarCols[3] = {REN.GTA4ColorTable(6,6,8)} + -- CarCols[4] = {REN.GTA4ColorTable(0,0,74)} + -- CarCols[5] = {REN.GTA4ColorTable(1,1,92)} + -- CarCols[6] = {REN.GTA4ColorTable(7,7,93)} + -- CarCols[7] = {REN.GTA4ColorTable(10,10,93)} + -- CarCols[8] = {REN.GTA4ColorTable(13,13,79)} + -- CarCols[9] = {REN.GTA4ColorTable(15,15,8)} + -- CarCols[10] = {REN.GTA4ColorTable(21,21,8)} + -- CarCols[11] = {REN.GTA4ColorTable(36,36,35)} + -- CarCols[12] = {REN.GTA4ColorTable(57,57,51)} + -- CarCols[13] = {REN.GTA4ColorTable(62,62,64)} + -- CarCols[14] = {REN.GTA4ColorTable(64,64,58)} + -- CarCols[15] = {REN.GTA4ColorTable(78,78,84)} + -- CarCols[16] = {REN.GTA4ColorTable(88,88,51)} + -- CarCols[17] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[18] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[19] = {REN.GTA4ColorTable(4,4,2)} + -- CarCols[20] = {REN.GTA4ColorTable(21,21,2)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/patriot_wheel.mdl', + + CustomWheelPosFL = Vector(72,36,-10), + CustomWheelPosFR = Vector(72,-36,-10), + CustomWheelPosRL = Vector(-71,36,-10), + CustomWheelPosRR = Vector(-71,-36,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,-2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(5,-19,35), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(15,-20,3), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-35,20,3), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-35,-20,3), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-39,43,-9), + ang = Angle(-90,-90,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-13.7,46.4,-9.9), + ang = Angle(-90,-90,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-15.8,46.4,-7.6), + ang = Angle(-90,-90,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-13.7,-46.4,-9.9), + ang = Angle(-90,90,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-15.8,-46.4,-7.6), + ang = Angle(-90,90,0), + OnBodyGroups = { + [1] = {1}, + } + }, + }, + + FrontHeight = 15, + FrontConstant = 28000, + FrontDamping = 2000, + FrontRelativeDamping = 2000, + + RearHeight = 15, + RearConstant = 28000, + RearDamping = 2000, + RearRelativeDamping = 2000, + + StrengthenSuspension = true, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 78, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 75, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 3, + + FuelFillPos = Vector(-80,43,22), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + AirFriction = -45, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/patriot_idle.wav', + + snd_low = 'octoteam/vehicles/patriot_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/patriot_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/patriot_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/patriot_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/patriot_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/patriot_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(32.123, 18.146, 28.268), ang = Angle(-0.0, -90.0, 73.9) }, + Radio = { pos = Vector(39.646, -0.013, 27.583), ang = Angle(-24.6, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(108.397, -0.000, -0.832), ang = Angle(1.3, 0.0, -0.0) }, + Back = { pos = Vector(-103.991, 0.021, 16.574), ang = Angle(-0.9, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(41.931, 0.005, 46.332), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(38.821, 46.975, 32.626), + w = 1 / 5, + ratio = 3 / 5, + }, + right = { + pos = Vector(38.886, -46.202, 31.039), + w = 1 / 5, + ratio = 3 / 5, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_patriot', V ) + +local light_table = { + L_HeadLampPos = Vector(104,24,17), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(104,-24,17), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-102.7,33.6,39.6), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-102.7,-33.6,39.6), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(104,24,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(104,-24,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(37,19,27), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(104,24,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(104,-24,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(37,20,27), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-102.7,33.6,39.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102.7,-33.6,39.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103.7,34.4,34.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103.7,-34.4,34.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-104,35,31.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-104,-35,31.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(101,35.9,15.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,32.9,44.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(37,21,27), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(101,-35.9,15.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,-32.9,44.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(37,17,27), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [9] = '', + [11] = '' + }, + Brake = { + [6] = '', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = '' + }, + Reverse = { + [6] = '', + [9] = '', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + Brake_Reverse = { + [6] = '', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = '' + }, + Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = '' + }, + Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_patriot', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_perennial.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_perennial.lua new file mode 100644 index 0000000..d1699d9 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_perennial.lua @@ -0,0 +1,398 @@ +local V = { + Name = 'Perennial', + Model = 'models/octoteam/vehicles/perennial.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(80,0,10), + + LightsTable = 'gta4_perennial', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,12)} + -- CarCols[2] = {REN.GTA4ColorTable(3,3,12)} + -- CarCols[3] = {REN.GTA4ColorTable(2,2,12)} + -- CarCols[4] = {REN.GTA4ColorTable(7,7,18)} + -- CarCols[5] = {REN.GTA4ColorTable(9,9,18)} + -- CarCols[6] = {REN.GTA4ColorTable(31,31,27)} + -- CarCols[7] = {REN.GTA4ColorTable(39,39,29)} + -- CarCols[8] = {REN.GTA4ColorTable(50,50,48)} + -- CarCols[9] = {REN.GTA4ColorTable(52,52,51)} + -- CarCols[10] = {REN.GTA4ColorTable(53,53,50)} + -- CarCols[11] = {REN.GTA4ColorTable(77,77,63)} + -- CarCols[12] = {REN.GTA4ColorTable(82,82,58)} + -- CarCols[13] = {REN.GTA4ColorTable(102,102,92)} + -- CarCols[14] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[15] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[16] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[17] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[18] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/perennial_wheel.mdl', + + CustomWheelPosFL = Vector(63,33,-9), + CustomWheelPosFR = Vector(63,-33,-9), + CustomWheelPosRL = Vector(-64,33,-9), + CustomWheelPosRR = Vector(-64,-33,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 33, + + SeatOffset = Vector(0,-19.5,26), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(10,-20,-8), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-33,20,-8), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-33,-20,-8), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-107.4,26.7,-4.7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-107.4,-26.7,-4.7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 130.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,40,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/perennial_idle.wav', + + snd_low = 'octoteam/vehicles/perennial_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/perennial_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/perennial_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/perennial_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/perennial_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/perennial_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_perennial', V ) + +local light_table = { + L_HeadLampPos = Vector(93,29,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-29,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-106.3,29,20.7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-106.3,-29,20.7), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(39,17,17), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,29,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-29,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(39,18,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(93,30,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-30,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + + Rearlight_sprites = { + { + pos = Vector(-106.3,29,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-106.3,-29,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-107.7,20,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107.7,-20,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108.7,0,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-108.7,9.2,19.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-108.7,-9.2,19.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,38,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100,37,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,19,17), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(86,-38,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100,-37,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,16,17), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [12] = '', + [6] = '' + }, + Brake = { + [11] = '', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = '' + }, + Reverse = { + [11] = '', + [12] = '', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + Brake_Reverse = { + [11] = '', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_perennial', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_perennial2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_perennial2.lua new file mode 100644 index 0000000..fef0071 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_perennial2.lua @@ -0,0 +1,402 @@ +local V = { + Name = 'Perennial (FlyUS)', + Model = 'models/octoteam/vehicles/perennial2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(80,0,10), + + LightsTable = 'gta4_perennial2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {Color(255,255,255)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/perennial_wheel.mdl', + + CustomWheelPosFL = Vector(63,33,-9), + CustomWheelPosFR = Vector(63,-33,-9), + CustomWheelPosRL = Vector(-64,33,-9), + CustomWheelPosRR = Vector(-64,-33,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 33, + + SeatOffset = Vector(0,-19.5,26), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(10,-20,-8), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-33,20,-8), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-33,-20,-8), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-107.4,26.7,-4.7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-107.4,-26.7,-4.7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 130.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,40,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/perennial_idle.wav', + + snd_low = 'octoteam/vehicles/perennial_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/perennial_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/perennial_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/perennial_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/perennial_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/perennial_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_perennial2', V ) + +local light_table = { + L_HeadLampPos = Vector(93,29,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-29,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-106.3,29,20.7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-106.3,-29,20.7), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(39,17,17), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,29,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-29,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(39,18,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(93,30,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(93,-30,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + + Rearlight_sprites = { + { + pos = Vector(-106.3,29,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-106.3,-29,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-107.7,20,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107.7,-20,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108.7,0,20.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-108.7,9.2,19.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-108.7,-9.2,19.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'common/null.wav'}, + ems_sprites = { + { + pos = Vector(-4,0,54), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,135,0,255), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.1 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,38,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100,37,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,19,17), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(86,-38,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100,-37,21), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(39,16,17), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [12] = '', + [6] = '' + }, + Brake = { + [11] = '', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = '' + }, + Reverse = { + [11] = '', + [12] = '', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + Brake_Reverse = { + [11] = '', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = '', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [12] = 'models/gta4/vehicles/perennial/perennial_lights_on', + [6] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/perennial/perennial_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_perennial2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_peyote.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_peyote.lua new file mode 100644 index 0000000..10536dc --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_peyote.lua @@ -0,0 +1,389 @@ +local V = { + Name = 'Peyote', + Model = 'models/octoteam/vehicles/peyote.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1500, + Trunk = { 35 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_peyote', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,12)} + -- CarCols[2] = {REN.GTA4ColorTable(17,17,12)} + -- CarCols[5] = {REN.GTA4ColorTable(41,41,35)} + -- CarCols[4] = {REN.GTA4ColorTable(46,46,127)} + -- CarCols[5] = {REN.GTA4ColorTable(51,51,127)} + -- CarCols[6] = {REN.GTA4ColorTable(52,52,59)} + -- CarCols[7] = {REN.GTA4ColorTable(55,55,60)} + -- CarCols[8] = {REN.GTA4ColorTable(61,61,113)} + -- CarCols[9] = {REN.GTA4ColorTable(65,65,80)} + -- CarCols[10] = {REN.GTA4ColorTable(68,68,12)} + -- CarCols[11] = {REN.GTA4ColorTable(76,76,126)} + -- CarCols[12] = {REN.GTA4ColorTable(91,91,89)} + -- CarCols[13] = {REN.GTA4ColorTable(93,93,91)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/peyote_wheel.mdl', + + CustomWheelPosFL = Vector(63,34,-2), + CustomWheelPosFR = Vector(63,-34,-2), + CustomWheelPosRL = Vector(-63,34,-2), + CustomWheelPosRR = Vector(-63,-34,-2), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-33,-17,26), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-22,-17,-6), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-98,32,-8), + ang = Angle(-110,-25,0), + }, + { + pos = Vector(-98,-32,-8), + ang = Angle(-110,25,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 27000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 8, + RearConstant = 27000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 40, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-65,39,19), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 35, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/patriot_idle.wav', + + snd_low = 'octoteam/vehicles/patriot_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/patriot_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/patriot_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/patriot_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/patriot_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/patriot_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(0.805, -0.036, 23), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(5.775, 0.019, 16.600), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(95.926, 13.785, -3.091), ang = Angle(15.4, 9.7, -0.0) }, + Back = { pos = Vector(-115.504, -29.275, -5.971), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(-1.223, -0.011, 35.403), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(-5.350, 40.093, 26.101), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(-4.864, -40.369, 25.709), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_peyote', V ) + +local light_table = { + L_HeadLampPos = Vector(91,34,18), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(91,-34,18), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-113,35,10), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-113,-35,10), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(91,34,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(91,-34,18), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(2,17,24.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,34,8),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-34,8),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(2,16,24.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-113,35,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-113,-35,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-113,35,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-113,-35,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-113,35,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-113,-35,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-113,35,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(2,18,24.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-113,-35,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(2,15,24.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [6] = '', + [10] = '', + [11] = '', + }, + Brake = { + [5] = '', + [6] = '', + [10] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [11] = '', + }, + Reverse = { + [5] = '', + [6] = '', + [10] = '', + [11] = 'models/gta4/vehicles/peyote/peyote_lights_on', + }, + Brake_Reverse = { + [5] = '', + [6] = '', + [10] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [11] = 'models/gta4/vehicles/peyote/peyote_lights_on', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = '', + [10] = '', + [11] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = '', + [10] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [11] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = '', + [10] = '', + [11] = 'models/gta4/vehicles/peyote/peyote_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = '', + [10] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [11] = 'models/gta4/vehicles/peyote/peyote_lights_on', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [10] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [11] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/peyote/peyote_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [6] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [10] = 'models/gta4/vehicles/peyote/peyote_lights_on', + [11] = 'models/gta4/vehicles/peyote/peyote_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/peyote/peyote_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/peyote/peyote_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_peyote', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_phantom.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_phantom.lua new file mode 100644 index 0000000..89cb60a --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_phantom.lua @@ -0,0 +1,381 @@ +sound.Add({ + name = 'TRUCK_HORN', + channel = CHAN_STATIC, + volume = 1.0, + level = 95, + sound = 'octoteam/vehicles/horns/phantom_horn.wav' +} ) + +local V = { + Name = 'Phantom', + Model = 'models/octoteam/vehicles/phantom.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + FLEX = { + Trailers = { + outputPos = Vector(-134,0,16), + outputType = 'axis' + } + }, + + Members = { + Mass = 10000.0, + + EnginePos = Vector(150,0,30), + + LightsTable = 'gta4_phantom', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,12)} + -- CarCols[2] = {REN.GTA4ColorTable(26,26,12)} + -- CarCols[3] = {REN.GTA4ColorTable(34,34,34)} + -- CarCols[4] = {REN.GTA4ColorTable(33,33,34)} + -- CarCols[5] = {REN.GTA4ColorTable(38,38,34)} + -- CarCols[6] = {REN.GTA4ColorTable(62,62,56)} + -- CarCols[7] = {REN.GTA4ColorTable(60,60,56)} + -- CarCols[8] = {REN.GTA4ColorTable(77,77,75)} + -- CarCols[9] = {REN.GTA4ColorTable(90,90,75)} + -- CarCols[10] = {REN.GTA4ColorTable(85,85,75)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/phantom_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/phantom_wheel_r.mdl', + + CustomWheelPosFL = Vector(158,50,-27), + CustomWheelPosFR = Vector(158,-50,-27), + CustomWheelPosML = Vector(-100,43,-27), + CustomWheelPosMR = Vector(-100,-43,-27), + CustomWheelPosRL = Vector(-158,43,-27), + CustomWheelPosRR = Vector(-158,-43,-27), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 24.4, + RearWheelRadius = 22.3, + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(60,-27,70), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(75,-25,25), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(55,53.2,110.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(55,-53.2,110.5), + ang = Angle(-90,0,0), + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 22, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 148, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4000, + PeakTorque = 135.0, + PowerbandStart = 1700, + PowerbandEnd = 3500, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(20,56,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 200, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/phantom_idle.wav', + + snd_low = 'octoteam/vehicles/phantom_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/phantom_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/phantom_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/phantom_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/phantom_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'TRUCK_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.14, + Gears = {-0.35,0,0.2,0.35,0.5,0.75,1,1.25,1.5} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_phantom', V ) + +local light_table = { + L_HeadLampPos = Vector(192,49,8), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(192,-49,8), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-191,31,-3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-191,-31,-3), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(192,49,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(192,-49,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(100,35.3,50), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(192,41,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(192,-41,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(99,35.3,49), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-191,31,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-191,-31,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-191,37.5,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-191,-37.5,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-191,11.5,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-191,-11.5,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-191,46,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(100.5,33.3,51.7), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-191,-46,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(100.5,18.3,51.7), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [11] = '', + [8] = '', + [10] = '' + }, + Brake = { + [4] = '', + [11] = '', + [8] = 'models/gta4/vehicles/phantom/detail2_on', + [10] = '' + }, + Reverse = { + [4] = '', + [11] = '', + [8] = '', + [10] = 'models/gta4/vehicles/phantom/detail2_on' + }, + Brake_Reverse = { + [4] = '', + [11] = '', + [8] = 'models/gta4/vehicles/phantom/detail2_on', + [10] = 'models/gta4/vehicles/phantom/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = '', + [8] = '', + [10] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = '', + [8] = 'models/gta4/vehicles/phantom/detail2_on', + [10] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = '', + [8] = '', + [10] = 'models/gta4/vehicles/phantom/detail2_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = '', + [8] = 'models/gta4/vehicles/phantom/detail2_on', + [10] = 'models/gta4/vehicles/phantom/detail2_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = 'models/gta4/vehicles/phantom/detail2_on', + [8] = '', + [10] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = 'models/gta4/vehicles/phantom/detail2_on', + [8] = 'models/gta4/vehicles/phantom/detail2_on', + [10] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = 'models/gta4/vehicles/phantom/detail2_on', + [8] = '', + [10] = 'models/gta4/vehicles/phantom/detail2_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/phantom/detail2_on', + [11] = 'models/gta4/vehicles/phantom/detail2_on', + [8] = 'models/gta4/vehicles/phantom/detail2_on', + [10] = 'models/gta4/vehicles/phantom/detail2_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/phantom/detail2_on' + }, + right = { + [5] = 'models/gta4/vehicles/phantom/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_phantom', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pinnacle.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pinnacle.lua new file mode 100644 index 0000000..d050d39 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pinnacle.lua @@ -0,0 +1,407 @@ +local V = { + Name = 'Pinnacle', + Model = 'models/octoteam/vehicles/pinnacle.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_pinnacle', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(2,2,108)} + -- CarCols[2] = {REN.GTA4ColorTable(3,3,108)} + -- CarCols[3] = {REN.GTA4ColorTable(7,7,55)} + -- CarCols[4] = {REN.GTA4ColorTable(9,9,48)} + -- CarCols[5] = {REN.GTA4ColorTable(10,10,12)} + -- CarCols[6] = {REN.GTA4ColorTable(14,14,12)} + -- CarCols[7] = {REN.GTA4ColorTable(17,17,12)} + -- CarCols[8] = {REN.GTA4ColorTable(20,20,12)} + -- CarCols[9] = {REN.GTA4ColorTable(25,25,12)} + -- CarCols[10] = {REN.GTA4ColorTable(31,31,28)} + -- CarCols[11] = {REN.GTA4ColorTable(33,33,91)} + -- CarCols[12] = {REN.GTA4ColorTable(53,53,60)} + -- CarCols[13] = {REN.GTA4ColorTable(62,62,60)} + -- CarCols[14] = {REN.GTA4ColorTable(70,70,63)} + -- CarCols[15] = {REN.GTA4ColorTable(78,78,18)} + -- CarCols[16] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[17] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[18] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[19] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[20] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pinnacle_wheel.mdl', + + CustomWheelPosFL = Vector(60,33,-10), + CustomWheelPosFR = Vector(60,-33,-10), + CustomWheelPosRL = Vector(-60,33,-10), + CustomWheelPosRR = Vector(-60,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-7,-20,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-19,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-33,19,-13), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-33,-19,-13), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-106,18.5,-10.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-106,-18.5,-10.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 18, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 135.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-74,-36,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feltzer_idle.wav', + + snd_low = 'octoteam/vehicles/feltzer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feltzer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feltzer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feltzer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feltzer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/banshee_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.15, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pinnacle', V ) + +local light_table = { + L_HeadLampPos = Vector(84,30,8), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(84,-30,8), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-98,33,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-98,-33,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(84,30,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(84,-30,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(25,18,13.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(84,30,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(84,-30,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(25,19,13.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(92,27,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(92,-27,-8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-98,33,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-98,-33,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-100.5,30,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100.5,-30,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-101.5,24.7,18.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-101.5,-24.7,18.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,32,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-32,16.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(80,33,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-98,36,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,20,13.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(80,-33,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-98,-36,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,17,13.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [10] = '', + [11] = '', + }, + Brake = { + [3] = '', + [10] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [11] = '', + }, + Reverse = { + [3] = '', + [10] = '', + [11] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + }, + Brake_Reverse = { + [3] = '', + [10] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [11] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [11] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [11] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = '', + [11] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [11] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [10] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + [11] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/pinnacle/pinnacle_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_pinnacle', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pmp600.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pmp600.lua new file mode 100644 index 0000000..4bbdd0f --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pmp600.lua @@ -0,0 +1,361 @@ +local V = { + Name = 'PMP 600', + Model = 'models/octoteam/vehicles/pmp600.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_pmp600', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,37)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[3] = {REN.GTA4ColorTable(0,0,76)} + -- CarCols[4] = {REN.GTA4ColorTable(1,1,95)} + -- CarCols[5] = {REN.GTA4ColorTable(6,6,97)} + -- CarCols[6] = {REN.GTA4ColorTable(11,11,97)} + -- CarCols[7] = {REN.GTA4ColorTable(20,20,12)} + -- CarCols[8] = {REN.GTA4ColorTable(21,21,24)} + -- CarCols[9] = {REN.GTA4ColorTable(23,23,24)} + -- CarCols[10] = {REN.GTA4ColorTable(28,28,126)} + -- CarCols[11] = {REN.GTA4ColorTable(45,45,35)} + -- CarCols[12] = {REN.GTA4ColorTable(53,53,58)} + -- CarCols[13] = {REN.GTA4ColorTable(54,54,55)} + -- CarCols[14] = {REN.GTA4ColorTable(62,62,51)} + -- CarCols[15] = {REN.GTA4ColorTable(69,69,65)} + -- CarCols[16] = {REN.GTA4ColorTable(70,70,63)} + -- CarCols[17] = {REN.GTA4ColorTable(78,78,63)} + -- CarCols[18] = {REN.GTA4ColorTable(85,85,123)} + -- CarCols[19] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[20] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[21] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[22] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[23] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pmp600_wheel.mdl', + + CustomWheelPosFL = Vector(67,32,-8), + CustomWheelPosFR = Vector(67,-32,-8), + CustomWheelPosRL = Vector(-67,32,-8), + CustomWheelPosRR = Vector(-67,-32,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-18,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-17,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-37,17,-10), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-37,-17,-10), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-107,22,-9), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-107,-22,-9), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 83, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 140.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-76,34,20), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/admiral_idle.wav', + + snd_low = 'octoteam/vehicles/admiral_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/admiral_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/admiral_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/admiral_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/admiral_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pmp600', V ) + +local light_table = { + L_HeadLampPos = Vector(93,19,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-19,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-102,31,16), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-102,-31,16), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(93,19,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(93,-19,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(30,23,20), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(93,19,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-19,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(30,14.5,20), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(98,18,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(98,-18,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-102,31,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102,-31,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-102,31,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102,-31,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-103,31,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-103,-31,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(93,19,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-104,31,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,22,20), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(93,-19,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-104,-31,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(30,15.5,20), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [10] = '', + }, + Reverse = { + [4] = '', + [10] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + [10] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + [10] = 'models/gta4/vehicles/pmp600/pmp600_lights_on', + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/pmp600/pmp600_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/pmp600/pmp600_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_pmp600', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_police.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_police.lua new file mode 100644 index 0000000..9972f17 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_police.lua @@ -0,0 +1,696 @@ +local V = { + Name = 'Police Cruiser', + Model = 'models/octoteam/vehicles/police.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 1650, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_police', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/police_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(60,35,-13), + CustomWheelPosFR = Vector(60,-35,-13), + CustomWheelPosRL = Vector(-60,35,-13), + CustomWheelPosRR = Vector(-60,-35,-13), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-19.5,16), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-20,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-32,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-32,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-114.6,22.3,-14.3), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 36000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 6, + RearConstant = 36000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4.5, + + MaxGrip = 56, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 46, + PowerbandStart = 1200, + PowerbandEnd = 6600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.2, + + FuelFillPos = Vector(-82.4,-37,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + AirFriction = -75, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/stainer_idle.wav', + + snd_low = 'octoteam/vehicles/stainer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stainer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stainer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stainer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stainer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/police/warning.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(18.956, 19.036, 13.307), ang = Angle(-0.0, -90.0, 80.3) }, + Radio = { pos = Vector(26.232, -0.010, 8.225), ang = Angle(-19.8, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(104.138, -0.031, -7.778), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-114.153, 0.025, 3.693), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(10.393, 0.005, 26.879), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(19.971, 39.204, 17.188), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(21.423, -39.155, 17.184), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_police', V ) + +local colOff = Color(0,0,0, 0) +local emsCenter = Vector(-9, 0, 35.7) + +local light_table = { + L_HeadLampPos = Vector(98,25,2.3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(98,-25,2.3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-114,30,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-114,-30,5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(98,25,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-25,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(23,19,11.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(98,25,2.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-25,2.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,18,11.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-114,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-114,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-114,21.7,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-114,-21.7,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + -- + -- FRONT + -- + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(255,0,0, 20), colOff, Color(255,0,0, 20), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 220, + Colors = { + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + }, + Speed = 0.07 + }, + + -- + -- REAR + -- + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(255,0,0, 20), colOff, Color(255,0,0, 20), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 0, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 220, + Colors = { + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + }, + Speed = 0.07 + }, + + -- + -- SIDE + -- + { + pos = emsCenter + Vector(5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, -25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, 25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + + -- + -- HEAD/TAIL LIGHTS + -- + { + pos = Vector(99, 16, -13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(99, -16, -13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-114, 21.7, 3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-114, -21.7, 3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(93.8,34.6,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,20,11.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-114,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(93.8,-34.6,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,17,11.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-114,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [5] = '', + [4] = '' + }, + Brake = { + [11] = '', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Reverse = { + [11] = '', + [5] = '', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [11] = '', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on', + [5] = 'models/gta4/vehicles/stainer/noose_lights_on', + [4] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_police', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_police2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_police2.lua new file mode 100644 index 0000000..b45897b --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_police2.lua @@ -0,0 +1,734 @@ +local V = { + Name = 'Police Patrol', + Model = 'models/octoteam/vehicles/police2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 1700, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_police2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/police2_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(65,32,-16), + CustomWheelPosFR = Vector(65,-32,-16), + CustomWheelPosRL = Vector(-59,32,-16), + CustomWheelPosRR = Vector(-59,-32,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 32, + + SeatOffset = Vector(0,-19.5,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-20,-18), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104.4,21.3,-15.2), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 37000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 6, + RearConstant = 37000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 5, + CounterSteeringMul = 0.85, + + MaxGrip = 56, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 52, + PowerbandStart = 1200, + PowerbandEnd = 6600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.2, + + FuelFillPos = Vector(-82.4,-34.3,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.04, + snd_idle = 'octoteam/vehicles/merit_idle.wav', + + snd_low = 'octoteam/vehicles/merit_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/merit_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/merit_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/merit_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/merit_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/police/warning.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(26.848, 18.013, 8.520), ang = Angle(-0.0, -90.0, 79.2) }, + Radio = { pos = Vector(33.926, 0.008, 4.649), ang = Angle(-22.0, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(105.224, 15.504, -11.844), ang = Angle(0.0, 14.5, 0.0) }, + Back = { pos = Vector(-102.317, 0.011, 6.691), ang = Angle(-9.3, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(17.398, -0.002, 26.016), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(27.358, 40.902, 12.988), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(27.295, -40.646, 13.462), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_police2', V ) + +local colOff = Color(0,0,0, 0) +local emsCenter = Vector(-1, 0, 34.7) + +local light_table = { + L_HeadLampPos = Vector(95.5,29.2,-1.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95.5,-29.2,-1.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101.7,28,5.9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101.7,-28,5.9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(95.5,29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95.5,-29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(30.6,24.7,10.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(97.7,23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(97.7,-23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(30.6,24.7,9.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(98.7,29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(98.7,-29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-101.7,28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101.7,-28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-102.7,20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102.7,-20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-102.2,21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-102.2,-21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + -- + -- FRONT + -- + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(255,0,0, 20), colOff, Color(255,0,0, 20), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 0, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 220, + Colors = { + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + }, + Speed = 0.07 + }, + + -- + -- REAR + -- + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(255,0,0, 20), colOff, Color(255,0,0, 20), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 220, + Colors = { + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + }, + Speed = 0.07 + }, + + -- + -- SIDE + -- + { + pos = emsCenter + Vector(5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, -25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, 25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + + -- + -- HEAD/TAIL LIGHTS + -- + { + pos = Vector(98.5, 29.2, -13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(98.5, -29.2, -13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-102.2, 21.2, 10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-102.2, -21.2, 10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(92.4,33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,20.8,12.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(92.4,-33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,-29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,15.5,12.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [11] = '', + [9] = '', + [5] = '' + }, + Brake = { + [4] = '', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [5] = '' + }, + Reverse = { + [4] = '', + [11] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + Brake_Reverse = { + [4] = '', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = '', + [5] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [5] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = '' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = '', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/merit/police2_lights_on', + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [5] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_police2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_polpatriot.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_polpatriot.lua new file mode 100644 index 0000000..d57fab1 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_polpatriot.lua @@ -0,0 +1,604 @@ +local V = { + Name = 'Police Patriot', + Model = 'models/octoteam/vehicles/polpatriot.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 3200, + Trunk = { 100 }, + + EnginePos = Vector(70,0,20), + + LightsTable = 'gta4_polpatriot', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelModel = 'models/octoteam/vehicles/polpatriot_wheel.mdl', + + CustomWheelPosFL = Vector(72,36,-10), + CustomWheelPosFR = Vector(72,-36,-10), + CustomWheelPosRL = Vector(-71,36,-10), + CustomWheelPosRR = Vector(-71,-36,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,-2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(5,-19,35), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(15,-20,3), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-35,20,3), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-35,-20,3), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-39,43,-9), + ang = Angle(-90,-90,0), + }, + }, + + FrontHeight = 15, + FrontConstant = 28000, + FrontDamping = 2000, + FrontRelativeDamping = 2000, + + RearHeight = 15, + RearConstant = 28000, + RearDamping = 2000, + RearRelativeDamping = 2000, + + StrengthenSuspension = true, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 78, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 80, + PowerbandStart = 1700, + PowerbandEnd = 5300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 3.5, + + FuelFillPos = Vector(-80,43,22), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + AirFriction = -45, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/patriot_idle.wav', + + snd_low = 'octoteam/vehicles/patriot_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/patriot_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/patriot_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/patriot_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/patriot_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/police_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(32.123, 18.146, 28.268), ang = Angle(-0.0, -90.0, 73.9) }, + Radio = { pos = Vector(39.646, -0.013, 27.583), ang = Angle(-24.6, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(108.397, -0.000, -0.832), ang = Angle(1.3, 0.0, -0.0) }, + Back = { pos = Vector(-103.991, 0.021, 16.574), ang = Angle(-0.9, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(41.931, 0.005, 46.332), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(38.821, 46.975, 32.626), + w = 1 / 5, + ratio = 3 / 5, + }, + right = { + pos = Vector(38.886, -46.202, 31.039), + w = 1 / 5, + ratio = 3 / 5, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_polpatriot', V ) + +local light_table = { + L_HeadLampPos = Vector(104,24,17), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(104,-24,17), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-102.7,33.6,39.6), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-102.7,-33.6,39.6), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(104,24,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(104,-24,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(37,19,27), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(104,24,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(104,-24,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(37,20,27), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-102.7,33.6,39.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102.7,-33.6,39.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-103.7,34.4,34.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103.7,-34.4,34.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-104,35,31.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-104,-35,31.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'GTA4_SIREN_WAIL','GTA4_SIREN_YELP','GTA4_SIREN_WARNING'}, + ems_sprites = { + { + pos = Vector(36,22,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + { + pos = Vector(36,15,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(36,7.5,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(36,0,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(36,-7.5,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(36,-15,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(36,-22,57), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(101,35.9,15.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,32.9,44.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(37,21,27), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(101,-35.9,15.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,-32.9,44.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(37,17,27), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [9] = '', + [11] = '' + }, + Brake = { + [6] = '', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = '' + }, + Reverse = { + [6] = '', + [9] = '', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + Brake_Reverse = { + [6] = '', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = '' + }, + Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = '' + }, + Brake = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = '' + }, + Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [9] = 'models/gta4/vehicles/patriot/patriot_lights_on', + [11] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/patriot/patriot_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_polpatriot', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pony.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pony.lua new file mode 100644 index 0000000..c541d55 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pony.lua @@ -0,0 +1,401 @@ +local V = { + Name = 'Pony', + Model = 'models/octoteam/vehicles/pony.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500, + Trunk = { 200 }, + + EnginePos = Vector(85,0,10), + + LightsTable = 'gta4_pony', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,11)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[2] = {REN.GTA4ColorTable(13,13,12)} + -- CarCols[3] = {REN.GTA4ColorTable(20,20,12)} + -- CarCols[4] = {REN.GTA4ColorTable(23,23,12)} + -- CarCols[5] = {REN.GTA4ColorTable(31,31,12)} + -- CarCols[6] = {REN.GTA4ColorTable(56,56,12)} + -- CarCols[7] = {REN.GTA4ColorTable(77,77,12)} + -- CarCols[8] = {REN.GTA4ColorTable(95,95,12)} + -- CarCols[9] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[10] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[11] = {REN.GTA4ColorTable(64,64,64)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pony_wheel.mdl', + + CustomWheelPosFL = Vector(75,38,-22), + CustomWheelPosFR = Vector(75,-38,-22), + CustomWheelPosRL = Vector(-75,38,-22), + CustomWheelPosRR = Vector(-75,-38,-22), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(26,-23,30), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(32,-23,-3), + ang = Angle(0,-90,10), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-108,14,-23), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 40000, + FrontDamping = 2500, + FrontRelativeDamping = 2500, + + RearHeight = 6, + RearConstant = 40000, + RearDamping = 2500, + RearRelativeDamping = 2500, + + StrengthenSuspension = true, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 42, + PowerbandStart = 1700, + PowerbandEnd = 4800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 3, + + FuelFillPos = Vector(-47,45,-14), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + AirFriction = -15, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/speedo_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(55.160, 23.2, 23.322), ang = Angle(-0.0, -90.0, 79.2) }, + Radio = { pos = Vector(59.099, -0.065, 19.415), ang = Angle(-16.4, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(108.571, -0.008, -9.883), ang = Angle(-0.2, -0.0, 0.0) }, + Back = { pos = Vector(-111.838, 0.030, -14.323), ang = Angle(3.3, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(44.949, 0.014, 42.977), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(55.039, 47.047, 29.117), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(55.253, -46.805, 30.006), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pony', V ) + +local light_table = { + L_HeadLampPos = Vector(102,30,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(102,-30,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-108,42,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-108,-42,9), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(102,30,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(102,-30,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(55.7,25,22.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(102,30,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-30,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(55.7,26,22.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-108,42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-108,42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-107,42,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-107,-42,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(101,38,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(101,38,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108,42,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(56,28,24), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(101,-38,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(101,-38,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108,-42,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(56,19,24), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [9] = '', + [11] = '', + }, + Brake = { + [6] = '', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Reverse = { + [6] = '', + [9] = '', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + Brake_Reverse = { + [6] = '', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_pony', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pony_coroner.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pony_coroner.lua new file mode 100644 index 0000000..3349336 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pony_coroner.lua @@ -0,0 +1,449 @@ +local V = { + Name = 'Pony (coroner)', + Model = 'models/octoteam/vehicles/pony.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500, + Trunk = { 200 }, + + EnginePos = Vector(85,0,10), + + LightsTable = 'gta4_pony_coroner', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,11)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[2] = {REN.GTA4ColorTable(13,13,12)} + -- CarCols[3] = {REN.GTA4ColorTable(20,20,12)} + -- CarCols[4] = {REN.GTA4ColorTable(23,23,12)} + -- CarCols[5] = {REN.GTA4ColorTable(31,31,12)} + -- CarCols[6] = {REN.GTA4ColorTable(56,56,12)} + -- CarCols[7] = {REN.GTA4ColorTable(77,77,12)} + -- CarCols[8] = {REN.GTA4ColorTable(95,95,12)} + -- CarCols[9] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[10] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[11] = {REN.GTA4ColorTable(64,64,64)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pony_wheel.mdl', + + CustomWheelPosFL = Vector(75,38,-22), + CustomWheelPosFR = Vector(75,-38,-22), + CustomWheelPosRL = Vector(-75,38,-22), + CustomWheelPosRR = Vector(-75,-38,-22), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(26,-23,30), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(32,-23,-3), + ang = Angle(0,-90,10), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-108,14,-23), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 40000, + FrontDamping = 2500, + FrontRelativeDamping = 2500, + + RearHeight = 6, + RearConstant = 40000, + RearDamping = 2500, + RearRelativeDamping = 2500, + + StrengthenSuspension = true, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 42, + PowerbandStart = 1700, + PowerbandEnd = 4800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 3, + + FuelFillPos = Vector(-47,45,-14), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + AirFriction = -15, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/speedo_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(55.160, 23.2, 23.322), ang = Angle(-0.0, -90.0, 79.2) }, + Radio = { pos = Vector(59.099, -0.065, 19.415), ang = Angle(-16.4, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(108.571, -0.008, -9.883), ang = Angle(-0.2, -0.0, 0.0) }, + Back = { pos = Vector(-111.838, 0.030, -14.323), ang = Angle(3.3, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(44.949, 0.014, 42.977), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(55.039, 47.047, 29.117), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(55.253, -46.805, 30.006), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pony_coroner', V ) + +local light_table = { + L_HeadLampPos = Vector(102,30,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(102,-30,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-108,42,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-108,-42,9), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(102,30,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(102,-30,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(55.7,25,22.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(102,30,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-30,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(55.7,26,22.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-108,42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-108,42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-107,42,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-107,-42,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(101,38,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(101,38,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108,42,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(56,28,24), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(101,-38,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(101,-38,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108,-42,15), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(56,19,24), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [9] = '', + [11] = '', + }, + Brake = { + [6] = '', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Reverse = { + [6] = '', + [9] = '', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + Brake_Reverse = { + [6] = '', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/pony/pony_lights_on', + [9] = 'models/gta4/vehicles/pony/pony_lights_on', + [11] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/pony/pony_lights_on' + }, + }, + }, + + ems_sprites = {{ + pos = Vector(51.1, 23.6, 48), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(51.1, 8, 48), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(51.1, -23.6, 48), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(51.1, -8, 48), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-102.7, -24.9, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-102.7, -8.0, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-102.7, 24.9, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(120,60,0,255),Color(0,0,0,255),Color(0,0,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }, { + pos = Vector(-102.7, 8.0, 46), + material = 'sprites/light_glow02_add_noz', + size = 80, + Colors = {Color(0,0,0,255),Color(0,0,0,255),Color(120,60,0,255),Color(0,0,0,255)}, + Speed = 0.15, + }}, +} +list.Set('simfphys_lights', 'gta4_pony_coroner', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_premier.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_premier.lua new file mode 100644 index 0000000..6d11915 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_premier.lua @@ -0,0 +1,907 @@ +local V = { + Name = 'Premier', + Model = 'models/octoteam/vehicles/premier.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1100, + Trunk = { 20 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_premier', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(6,6,12)} + -- CarCols[2] = {REN.GTA4ColorTable(13,13,12)} + -- CarCols[3] = {REN.GTA4ColorTable(23,23,12)} + -- CarCols[9] = {REN.GTA4ColorTable(21,21,60)} + -- CarCols[7] = {REN.GTA4ColorTable(26,26,72)} + -- CarCols[6] = {REN.GTA4ColorTable(36,36,39)} + -- CarCols[7] = {REN.GTA4ColorTable(38,38,35)} + -- CarCols[8] = {REN.GTA4ColorTable(62,62,60)} + -- CarCols[9] = {REN.GTA4ColorTable(58,58,59)} + -- CarCols[10] = {REN.GTA4ColorTable(64,64,59)} + -- CarCols[11] = {REN.GTA4ColorTable(65,65,58)} + -- CarCols[12] = {REN.GTA4ColorTable(76,76,50)} + -- CarCols[13] = {REN.GTA4ColorTable(88,88,63)} + -- CarCols[14] = {REN.GTA4ColorTable(99,99,27)} + -- CarCols[15] = {REN.GTA4ColorTable(106,106,90)} + -- CarCols[16] = {REN.GTA4ColorTable(117,117,59)} + -- CarCols[17] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[18] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[19] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[20] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[21] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/premier_wheel.mdl', + + CustomWheelPosFL = Vector(58,30,-9), + CustomWheelPosFR = Vector(58,-30,-9), + CustomWheelPosRL = Vector(-58,30,-9), + CustomWheelPosRR = Vector(-58,-30,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-7,-17,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-17,-11), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-30,17,-11), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-30,-17,-11), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-95,17,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-95,-17,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 21000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 8, + RearConstant = 21000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 35, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 25, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-70,-30,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 35, + + AirFriction = -35, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/esperanto_idle.wav', + BrakeSqueal = true, + + snd_low = 'octoteam/vehicles/esperanto_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/esperanto_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/esperanto_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/esperanto_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/esperanto_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/moonbeam_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(21.156, 14.869, 16.181), ang = Angle(-0.0, -90.0, 62.8) }, + Radio = { pos = Vector(28.309, 0.007, 7.612), ang = Angle(-19.0, 180.0, -0.0) }, + Plates = { + Front = { pos = Vector(95.041, -0.011, -6.311), ang = Angle(7.0, 0, 0) }, + Back = { pos = Vector(-93.244, -0.002, 14.595), ang = Angle(-8, 180, 0) }, + }, + Mirrors = { + top = { + pos = Vector(14.677, -0.018, 29.757), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(28.433, 35.467, 18.306), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.399, -35.731, 18.051), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_premier', V ) + +local light_table = { + L_HeadLampPos = Vector(81,26,6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(81,26,6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-92,25.5,15.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-92,-25.5,15.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(81,26,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(81,-26,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(27,22,18), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(81,26,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(81,-26,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26.5,22,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(92,20.5,-6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(92,-20.5,-6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-92,25.5,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-92,-25.5,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-92,25.5,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-92,-25.5,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-93,21.5,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-93,-21.5,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(93.9, -10.2, -6.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(93.9, 10.2, -6.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(91.5, -9.1, 3.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.03 + }, + { + pos = Vector(91.5, 9.1, 3.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.025 + }, + { + pos = Vector(-71.0, -23.4, 22.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-71.0, 23.4, 22.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(16.4, -20.7, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(16.4, -16, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.034 + }, + { + pos = Vector(16.4, -12, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.033 + }, + { + pos = Vector(16.4, -8, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.032 + }, + { + pos = Vector(16.4, 20.7, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(16.4, 16, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(16.4, 12, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(16.4, 8, 30.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(29.5, -34.4, 15.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(29.5, 34.4, 15.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(77,29.5,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-93,26.5,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(27,19,19), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(77,-29.5,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-93,-26.5,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(27,13.5,19), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [10] = '', + [11] = '' + }, + Brake = { + [9] = '', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = '' + }, + Reverse = { + [9] = '', + [10] = '', + [11] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + Brake_Reverse = { + [9] = '', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/premier/premier_lights_on', + [10] = 'models/gta4/vehicles/premier/premier_lights_on', + [11] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/premier/premier_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_premier', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pres.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pres.lua new file mode 100644 index 0000000..08776dc --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pres.lua @@ -0,0 +1,467 @@ +local V = { + Name = 'Presidente', + Model = 'models/octoteam/vehicles/pres.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2200.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_pres', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[2] = {REN.GTA4ColorTable(0,6,0)} + -- CarCols[3] = {REN.GTA4ColorTable(0,133,12)} + -- CarCols[4] = {REN.GTA4ColorTable(4,133,12)} + -- CarCols[5] = {REN.GTA4ColorTable(6,97,12)} + -- CarCols[6] = {REN.GTA4ColorTable(10,133,12)} + -- CarCols[7] = {REN.GTA4ColorTable(21,133,12)} + -- CarCols[8] = {REN.GTA4ColorTable(23,133,12)} + -- CarCols[9] = {REN.GTA4ColorTable(25,133,12)} + -- CarCols[10] = {REN.GTA4ColorTable(33,133,35)} + -- CarCols[11] = {REN.GTA4ColorTable(34,97,34)} + -- CarCols[12] = {REN.GTA4ColorTable(47,133,35)} + -- CarCols[13] = {REN.GTA4ColorTable(49,133,63)} + -- CarCols[14] = {REN.GTA4ColorTable(52,133,56)} + -- CarCols[15] = {REN.GTA4ColorTable(54,133,55)} + -- CarCols[16] = {REN.GTA4ColorTable(65,133,63)} + -- CarCols[17] = {REN.GTA4ColorTable(67,133,118)} + -- CarCols[18] = {REN.GTA4ColorTable(70,133,65)} + -- CarCols[19] = {REN.GTA4ColorTable(98,133,90)} + -- CarCols[20] = {REN.GTA4ColorTable(16,133,76)} + -- CarCols[21] = {REN.GTA4ColorTable(9,1,91)} + -- CarCols[22] = {REN.GTA4ColorTable(15,133,93)} + -- CarCols[23] = {REN.GTA4ColorTable(19,1,93)} + -- CarCols[24] = {REN.GTA4ColorTable(13,133,80)} + -- ent:SetProxyColors(CarCols[math.random(2,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pres_wheel.mdl', + + CustomWheelPosFL = Vector(60,30,-14), + CustomWheelPosFR = Vector(60,-30,-14), + CustomWheelPosRL = Vector(-60,30,-14), + CustomWheelPosRR = Vector(-60,-30,-14), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 34, + + SeatOffset = Vector(-5,-18,18), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-17,-12), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-38,17,-12), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-38,-17,-12), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-102,22.5,-11), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-102,19,-11), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-102,-22.5,-11), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-102,-19,-11), + ang = Angle(-80,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 30000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 30000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 83, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 24, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 145.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-73,-31,18), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pres', V ) + +local V2 = {} +V2.Name = 'Korean Mob Presidente' +V2.Model = 'models/octoteam/vehicles/pres.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,20) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('011' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,1,75)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(10,10,10), +} +V2.Members.Supercharged = true +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pres2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(82,28,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(82,-28,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-97,28,10), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-97,-28,10), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(82,28,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(82,-28,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(29,25,13), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(85,23,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(85,-23,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29,25,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(91,24,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,-24,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-97,28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-97,28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,0,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,25,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-95,28,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(30,21,15), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(91,-25,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-95,-28,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(30,15,15), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [9] = '', + [11] = '', + [12] = '', + [13] = '', + }, + Brake = { + [10] = '', + [9] = 'models/gta4/vehicles/pres/presidente_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = '', + }, + Reverse = { + [10] = '', + [9] = '', + [11] = '', + [12] = '', + [13] = 'models/gta4/vehicles/pres/presidente_lights_on', + }, + Brake_Reverse = { + [10] = '', + [9] = 'models/gta4/vehicles/pres/presidente_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = 'models/gta4/vehicles/pres/presidente_lights_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = '', + [11] = '', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = '', + }, + Brake = { + [10] = '', + [9] = 'models/gta4/vehicles/pres/presidente_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = '', + [11] = '', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = 'models/gta4/vehicles/pres/presidente_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = 'models/gta4/vehicles/pres/presidente_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = 'models/gta4/vehicles/pres/presidente_lights_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/pres/presidente_lights_on', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = 'models/gta4/vehicles/pres/presidente_lights_on', + [11] = 'models/gta4/vehicles/pres/presidente_lights_on', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = '', + [11] = 'models/gta4/vehicles/pres/presidente_lights_on', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = 'models/gta4/vehicles/pres/presidente_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/pres/presidente_lights_on', + [9] = 'models/gta4/vehicles/pres/presidente_lights_on', + [11] = 'models/gta4/vehicles/pres/presidente_lights_on', + [12] = 'models/gta4/vehicles/pres/presidente_lights_on', + [13] = 'models/gta4/vehicles/pres/presidente_lights_on', + }, + }, + turnsignals = { + left = { + [4] = 'models/gta4/vehicles/pres/presidente_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/pres/presidente_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_pres', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_primo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_primo.lua new file mode 100644 index 0000000..9eff6cd --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_primo.lua @@ -0,0 +1,463 @@ +local V = { + Name = 'Primo', + Model = 'models/octoteam/vehicles/primo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_primo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..'000' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(0,0,0,127)} + -- CarCols[2] = {REN.GTA4ColorTable2(0,0,0,133)} + -- CarCols[3] = {REN.GTA4ColorTable2(0,1,12,133)} + -- CarCols[4] = {REN.GTA4ColorTable2(1,4,12,133)} + -- CarCols[5] = {REN.GTA4ColorTable2(16,17,12,133)} + -- CarCols[6] = {REN.GTA4ColorTable2(17,17,12,133)} + -- CarCols[7] = {REN.GTA4ColorTable2(21,21,12,133)} + -- CarCols[8] = {REN.GTA4ColorTable2(22,23,12,133)} + -- CarCols[9] = {REN.GTA4ColorTable2(31,34,30,133)} + -- CarCols[10] = {REN.GTA4ColorTable2(33,33,32,133)} + -- CarCols[11] = {REN.GTA4ColorTable2(36,36,32,133)} + -- CarCols[12] = {REN.GTA4ColorTable2(52,52,55,133)} + -- CarCols[13] = {REN.GTA4ColorTable2(57,52,12,133)} + -- CarCols[14] = {REN.GTA4ColorTable2(62,55,12,133)} + -- CarCols[15] = {REN.GTA4ColorTable2(70,69,63,133)} + -- CarCols[16] = {REN.GTA4ColorTable2(97,93,90,133)} + -- CarCols[17] = {REN.GTA4ColorTable2(54,54,55,133)} + -- CarCols[18] = {REN.GTA4ColorTable2(67,67,118,133)} + -- CarCols[19] = {REN.GTA4ColorTable2(70,70,65,133)} + -- CarCols[20] = {REN.GTA4ColorTable2(98,98,90,133)} + -- CarCols[21] = {REN.GTA4ColorTable2(16,16,76,133)} + -- CarCols[22] = {REN.GTA4ColorTable2(9,9,91,133)} + -- CarCols[23] = {REN.GTA4ColorTable2(15,15,93,133)} + -- CarCols[24] = {REN.GTA4ColorTable2(19,19,93,133)} + -- CarCols[25] = {REN.GTA4ColorTable2(13,13,80,133)} + -- CarCols[26] = {REN.GTA4ColorTable2(12,12,80,127)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/primo_wheel.mdl', + + CustomWheelPosFL = Vector(62,32,-11), + CustomWheelPosFR = Vector(62,-32,-11), + CustomWheelPosRL = Vector(-61,32,-11), + CustomWheelPosRR = Vector(-61,-32,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(0,-18,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(13,-18,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-35,18,-13), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-35,-18,-13), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-107,23,-10.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-107,-23,-10.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 23, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 140.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,32,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/merit_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_primo', V ) + +local V2 = {} +V2.Name = 'Spanish Lords Primo' +V2.Model = 'models/octoteam/vehicles/primo.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('02111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(34,34,28,127)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(215,142,16), +} +V2.Members.Supercharged = true +list.Set('simfphys_vehicles', 'sim_fphys_gta4_primo2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(97,24,4.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(97,-24,4.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-107,23,10.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-107,-23,10.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(97,24,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-24,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(35,19.3,15.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(97,24,4.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(97,-24,4.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(34,19.3,14.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(101,22,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(101,-22,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-107,23,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,-23,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-107,23,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,-23,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,0,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-108,15,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,200,200,150), + }, + { + pos = Vector(-108,-15,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,200,200,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(84,35,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-94,35,-0.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-107,26,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,200,200,150), + }, + +--[[ { + pos = Vector(35.4,20,16.6), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(84,-35,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-94,-35,-0.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-107,-26,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,200,200,150), + }, + +--[[ { + pos = Vector(35.4,18.5,16.6), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [12] = '', + [14] = '', + }, + Brake = { + [10] = '', + [11] = 'models/gta4/vehicles/primo/primo_lights_on', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = '', + }, + Reverse = { + [10] = '', + [11] = '', + [12] = '', + [14] = 'models/gta4/vehicles/primo/primo_lights_on', + }, + Brake_Reverse = { + [10] = '', + [11] = 'models/gta4/vehicles/primo/primo_lights_on', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = 'models/gta4/vehicles/primo/primo_lights_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = 'models/gta4/vehicles/primo/primo_lights_on', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = 'models/gta4/vehicles/primo/primo_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = 'models/gta4/vehicles/primo/primo_lights_on', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = 'models/gta4/vehicles/primo/primo_lights_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = 'models/gta4/vehicles/primo/primo_lights_on', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = '', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = 'models/gta4/vehicles/primo/primo_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/primo/primo_lights_on', + [11] = 'models/gta4/vehicles/primo/primo_lights_on', + [12] = 'models/gta4/vehicles/primo/primo_lights_on', + [14] = 'models/gta4/vehicles/primo/primo_lights_on', + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/primo/primo_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/primo/primo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_primo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pstockade.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pstockade.lua new file mode 100644 index 0000000..05cefff --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_pstockade.lua @@ -0,0 +1,523 @@ +local V = { + Name = 'Police Stockade', + Model = 'models/octoteam/vehicles/pstockade.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 6500.0, + + EnginePos = Vector(102,0,34), + + LightsTable = 'gta4_pstockade', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + MaxHealth = 5000, + IsArmored = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pstockade_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/pstockade_wheel_r.mdl', + + CustomWheelPosFL = Vector(93,44,-4), + CustomWheelPosFR = Vector(93,-44,-4), + CustomWheelPosRL = Vector(-93,44,-4), + CustomWheelPosRR = Vector(-93,-44,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 35, + + SeatOffset = Vector(20,-30,70), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(30,-29,30), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-131,-40,30), + ang = Angle(0,0,0) + }, + { + pos = Vector(-105,-40,30), + ang = Angle(0,0,0) + }, + { + pos = Vector(-131,40,30), + ang = Angle(0,180,0) + }, + { + pos = Vector(-105,40,30), + ang = Angle(0,180,0) + }, + }, + ExhaustPositions = { + { + pos = Vector(-56.1,-46.4,-11.3), + ang = Angle(-90,90,0), + }, + { + pos = Vector(-52.1,-46.4,-11.3), + ang = Angle(-90,90,0), + }, + }, + + FrontHeight = 18, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 18, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 148, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 120.0, + PowerbandStart = 1700, + PowerbandEnd = 4000, + Turbocharged = true, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-10.1,50.6,6.7), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 160, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stockade_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.12, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_pstockade', V ) + +local light_table = { + L_HeadLampPos = Vector(128,41,19), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(128,-41,19), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-148.7,42.6,33.4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-148.7,-42.6,33.4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(128,41,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(128,-41,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(58,37,55), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(128,41,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(128,-41,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(58,38,55), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-148.7,42.6,33.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-148.7,-42.6,33.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-148.7,42.6,78.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-148.7,-42.6,78.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + ems_sounds = {'GTA4_SIREN_WAIL','GTA4_SIREN_YELP','GTA4_SIREN_WARNING'}, + ems_sprites = { + { + pos = Vector(70.7,36,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,24,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,12,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,0,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,-12,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,-24,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(70.7,-36,92.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-148.7,42.6,23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(60,35,57), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-148.7,-42.6,23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(60,22,57), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [5] = '' + }, + Brake = { + [11] = '', + [5] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/stockade/detail2_on', + [5] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/stockade/detail2_on', + [5] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/stockade/detail2_on', + [5] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/stockade/detail2_on', + [5] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + turnsignals = { + left = { + [2] = 'models/gta4/vehicles/stockade/detail2_on' + }, + right = { + [3] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_pstockade', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rancher.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rancher.lua new file mode 100644 index 0000000..48d0828 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rancher.lua @@ -0,0 +1,388 @@ +local V = { + Name = 'Rancher', + Model = 'models/octoteam/vehicles/rancher.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2000, + Trunk = { + nil, + {100, 1, 1}, + }, + + EnginePos = Vector(70,0,15), + + LightsTable = 'gta4_rancher', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,3)..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,2)} + -- CarCols[2] = {REN.GTA4ColorTable(0,4,8)} + -- CarCols[3] = {REN.GTA4ColorTable(0,3,3)} + -- CarCols[4] = {REN.GTA4ColorTable(1,24,75)} + -- CarCols[5] = {REN.GTA4ColorTable(11,5,6)} + -- CarCols[6] = {REN.GTA4ColorTable(23,24,75)} + -- CarCols[7] = {REN.GTA4ColorTable(30,30,39)} + -- CarCols[8] = {REN.GTA4ColorTable(31,36,72)} + -- CarCols[9] = {REN.GTA4ColorTable(48,49,48)} + -- CarCols[10] = {REN.GTA4ColorTable(52,55,61)} + -- CarCols[11] = {REN.GTA4ColorTable(57,61,61)} + -- CarCols[12] = {REN.GTA4ColorTable(71,73,85)} + -- CarCols[13] = {REN.GTA4ColorTable(76,73,85)} + -- CarCols[14] = {REN.GTA4ColorTable(77,5,85)} + -- CarCols[15] = {REN.GTA4ColorTable(102,90,80)} + -- CarCols[16] = {REN.GTA4ColorTable(106,90,80)} + -- CarCols[17] = {REN.GTA4ColorTable(109,110,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/rancher_wheel.mdl', + + CustomWheelPosFL = Vector(65,35,-10), + CustomWheelPosFR = Vector(65,-35,-10), + CustomWheelPosRL = Vector(-51,35,-10), + CustomWheelPosRR = Vector(-51,-35,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-20,33), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-18,0), + ang = Angle(0,-90,10), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-93,29,-7), + ang = Angle(-110,0,0), + }, + { + pos = Vector(-93,24.6,-7), + ang = Angle(-110,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 37000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 8, + RearConstant = 37000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 60, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 42, + PowerbandStart = 1700, + PowerbandEnd = 4600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-20,40,0), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 60, + + AirFriction = -60, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/rancher_idle.wav', + + snd_low = 'octoteam/vehicles/rancher_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/rancher_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/rancher_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/rancher_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/rancher_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/rancher_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(24.509, 18.662, 23.368), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(28.869, 0.714, 18.205), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(96.441, 0.050, 0.976), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-93.884, -18.597, 11.189), ang = Angle(-6.3, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(15.540, -0.001, 43.671), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(18.865, 43.970, 30.316), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(18.995, -43.810, 30.073), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_rancher', V ) + +local light_table = { + L_HeadLampPos = Vector(91,32,16), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(91,-32,16), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-92,39,6), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-92,-39,6), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(91,32,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,50), + }, + { + pos = Vector(91,-32,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,50), + }, + }, + + Headlamp_sprites = { + {pos = Vector(91,32,16),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-32,16),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-92,39,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-92,-39,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-92,39,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-92,-39,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-92,39,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-92,-39,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,32,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,39,14.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,22.5,22), + material = 'gta4/dash_left', + size = 1, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(91,-32,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,-39,14.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,16.3,22), + material = 'gta4/dash_right', + size = 1, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [11] = '', + [10] = '', + }, + Brake = { + [4] = '', + [11] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [10] = '', + }, + Reverse = { + [4] = '', + [11] = '', + [10] = 'models/gta4/vehicles/rancher/rancher_lights_on', + }, + Brake_Reverse = { + [4] = '', + [11] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [10] = 'models/gta4/vehicles/rancher/rancher_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = '', + [10] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = '', + [10] = 'models/gta4/vehicles/rancher/rancher_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [10] = 'models/gta4/vehicles/rancher/rancher_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = '', + [10] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = '', + [10] = 'models/gta4/vehicles/rancher/rancher_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [11] = 'models/gta4/vehicles/rancher/rancher_lights_on', + [10] = 'models/gta4/vehicles/rancher/rancher_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/rancher/rancher_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/rancher/rancher_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_rancher', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rebla.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rebla.lua new file mode 100644 index 0000000..bb489a0 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rebla.lua @@ -0,0 +1,446 @@ +local V = { + Name = 'Rebla', + Model = 'models/octoteam/vehicles/rebla.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 1550, + Trunk = { 45 }, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_rebla', + + OnSpawn = function(ent) + ent.Tuned = math.random(0,1) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..ent.Tuned..ent.Tuned..ent.Tuned..math.random(0,1)..ent.Tuned ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,3)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[3] = {REN.GTA4ColorTable(0,0,103)} + -- CarCols[4] = {REN.GTA4ColorTable(3,3,103)} + -- CarCols[5] = {REN.GTA4ColorTable(1,1,79)} + -- CarCols[6] = {REN.GTA4ColorTable(3,3,73)} + -- CarCols[7] = {REN.GTA4ColorTable(4,4,82)} + -- CarCols[8] = {REN.GTA4ColorTable(6,6,84)} + -- CarCols[9] = {REN.GTA4ColorTable(11,11,86)} + -- CarCols[10] = {REN.GTA4ColorTable(16,16,92)} + -- CarCols[11] = {REN.GTA4ColorTable(23,23,25)} + -- CarCols[12] = {REN.GTA4ColorTable(34,34,28)} + -- CarCols[13] = {REN.GTA4ColorTable(36,36,27)} + -- CarCols[14] = {REN.GTA4ColorTable(47,47,91)} + -- CarCols[15] = {REN.GTA4ColorTable(52,52,53)} + -- CarCols[16] = {REN.GTA4ColorTable(53,53,51)} + -- CarCols[17] = {REN.GTA4ColorTable(64,64,65)} + -- CarCols[18] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[19] = {REN.GTA4ColorTable(70,70,64)} + -- CarCols[20] = {REN.GTA4ColorTable(73,73,58)} + -- CarCols[21] = {REN.GTA4ColorTable(76,76,58)} + -- CarCols[22] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[23] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[24] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[25] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[26] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/rebla_wheel.mdl', + + CustomWheelPosFL = Vector(56,29,-10), + CustomWheelPosFR = Vector(56,-29,-10), + CustomWheelPosRL = Vector(-57,29,-10), + CustomWheelPosRR = Vector(-57,-29,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-16,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(2,-17,-7), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-33,17,-7), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-33,-17,-7), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-88,18.5,-15), + ang = Angle(-120,0,0), + OnBodyGroups = { + [5] = {0}, + } + }, + { + pos = Vector(-88,-18.5,-15), + ang = Angle(-120,0,0), + OnBodyGroups = { + [5] = {0}, + } + }, + { + pos = Vector(-96,17.5,-16), + ang = Angle(-110,0,0), + OnBodyGroups = { + [5] = {1}, + } + }, + { + pos = Vector(-96,-17.5,-16), + ang = Angle(-110,0,0), + OnBodyGroups = { + [5] = {1}, + } + }, + }, + + FrontHeight = 8, + FrontConstant = 30000, + FrontDamping = 900, + FrontRelativeDamping = 900, + + RearHeight = 8, + RearConstant = 30000, + RearDamping = 900, + RearRelativeDamping = 900, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 550, + + TurnSpeed = 4, + + MaxGrip = 46, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 38, + PowerbandStart = 1200, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-68,34,19), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + AirFriction = -40, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/perennial_idle.wav', + + snd_low = 'octoteam/vehicles/perennial_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/perennial_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/perennial_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/perennial_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/perennial_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/rebla_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(27.0, 16.158, 18.0), ang = Angle(-0.0, -90.0, 71.1) }, + Radio = { pos = Vector(28.647, 0.000, 16.742), ang = Angle(-21.1, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(90.657, 0.001, -11.279), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-90.493, -0.001, 9.418), ang = Angle(-16.8, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(9.069, -0.001, 35.410), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(20.691, 36.915, 24.521), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(20.427, -36.883, 24.539), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_rebla', V ) + +local light_table = { + L_HeadLampPos = Vector(78,25.5,8.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(78,-25.5,8.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-89,25,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-89,-25,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(78,25.5,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(78,-25.5,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(82,20,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(227,242,255,255), + }, + { + pos = Vector(82,-20,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(28,15.5,18.5), + material = 'gta4/dash_lowbeam', + size = 1, + color = Color(255,57,50,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(82,20,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(82,-20,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(28,17,18.5), + material = 'gta4/dash_highbeam', + size = 1, + color = Color(255,57,50,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-89,25,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-89,-25,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-87,30,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-87,-30,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-90,24,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-90,-24,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(88,20,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-88,29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(28,18,18.5), + material = 'gta4/dash_left', + size = 1, + color = Color(255,57,50,255), + },]] + }, + Right = { + { + pos = Vector(88,-20,-2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-88,-29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(28,14.5,18.5), + material = 'gta4/dash_right', + size = 1, + color = Color(255,57,50,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [10] = '', + [11] = '' + }, + Brake = { + [4] = '', + [10] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [11] = '' + }, + Reverse = { + [4] = '', + [10] = '', + [11] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + Brake_Reverse = { + [4] = '', + [10] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [11] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = '', + [11] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [11] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [11] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = '', + [11] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [11] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = '', + [11] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [10] = 'models/gta4/vehicles/rebla/rebla_lights_on', + [11] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/rebla/rebla_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_rebla', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ripley.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ripley.lua new file mode 100644 index 0000000..88f9e56 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ripley.lua @@ -0,0 +1,330 @@ +local V = { + Name = 'Ripley', + Model = 'models/octoteam/vehicles/ripley.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + FLEX = { + Trailers = { + inputPos = Vector(199,0,-25), + inputType = 'ballsocket', + outputPos = Vector(-167,0,-25), + outputType = 'ballsocket' + } + }, + + Members = { + Mass = 9500.0, + + EnginePos = Vector(0,0,0), + + LightsTable = 'gta4_ripley', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,113,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/ripley_wheel.mdl', + + CustomWheelPosFL = Vector(68,41,-27), + CustomWheelPosFR = Vector(68,-41,-27), + CustomWheelPosRL = Vector(-68,41,-27), + CustomWheelPosRR = Vector(-68,-41,-27), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 45, + + SeatOffset = Vector(130,-38,35), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(137,-38,-20), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 5, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 5, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 3, + + MaxGrip = 150, + Efficiency = 1, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 3500, + PeakTorque = 70.0, + PowerbandStart = 1300, + PowerbandEnd = 3000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(0,58,-20), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 130, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/firetruk_idle.wav', + + snd_low = 'octoteam/vehicles/firetruk_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/firetruk_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/firetruk_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/firetruk_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/firetruk_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'BUS_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.16, + Gears = {-0.3,0,0.1,0.25,0.35} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_ripley', V ) + +local light_table = { + L_HeadLampPos = Vector(193,27,-24), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(193,-27,-24), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-157,46,-7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-157,-46,-7), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(193,27,-24), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(193,-27,-24), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(169,50,-1), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(193,27,-24),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(193,-27,-24),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(169,49,-1), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-157,46,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-157,-46,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-157,46,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-157,-46,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-157,40,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-157,-40,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(193,51,-19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-157,53,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(171,48,2), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(193,-51,-19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-157,-53,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(171,42,2), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [9] = '', + [12] = '' + }, + Brake = { + [8] = '', + [9] = 'models/gta4/vehicles/ripley/detail2_on', + [12] = '' + }, + Reverse = { + [8] = '', + [9] = '', + [12] = 'models/gta4/vehicles/ripley/detail2_on' + }, + Brake_Reverse = { + [8] = '', + [9] = 'models/gta4/vehicles/ripley/detail2_on', + [12] = 'models/gta4/vehicles/ripley/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/ripley/detail2_on', + [9] = 'models/gta4/vehicles/ripley/detail2_on', + [12] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/ripley/detail2_on', + [9] = 'models/gta4/vehicles/ripley/detail2_on', + [12] = 'models/gta4/vehicles/ripley/detail2_on' + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/ripley/detail2_on', + [9] = 'models/gta4/vehicles/ripley/detail2_on', + [12] = '' + }, + Reverse = { + [8] = 'models/gta4/vehicles/ripley/detail2_on', + [9] = 'models/gta4/vehicles/ripley/detail2_on', + [12] = 'models/gta4/vehicles/ripley/detail2_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/ripley/detail2_on' + }, + right = { + [11] = 'models/gta4/vehicles/ripley/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_ripley', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rom.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rom.lua new file mode 100644 index 0000000..d79d8aa --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_rom.lua @@ -0,0 +1,366 @@ +local V = { + Name = 'Roman\'s Taxi', + Model = 'models/octoteam/vehicles/rom.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,5), + + LightsTable = 'gta4_rom', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,104,8)} + -- CarCols[2] = {REN.GTA4ColorTable(26,26,8)} + -- CarCols[3] = {REN.GTA4ColorTable(34,34,33)} + -- CarCols[9] = {REN.GTA4ColorTable(19,19,12)} + -- CarCols[7] = {REN.GTA4ColorTable(21,21,12)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/esperanto_wheel.mdl', + + CustomWheelPosFL = Vector(60,33,-10), + CustomWheelPosFR = Vector(60,-33,-10), + CustomWheelPosRL = Vector(-60,33,-10), + CustomWheelPosRR = Vector(-60,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-18,20), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-18,-12), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-36,18,-12), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-36,-18,-12), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-100,28.5,-11.7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-100,-28.5,-11.7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 81, + Efficiency = 0.6, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 140.0, + PowerbandStart = 1800, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,36,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/esperanto_idle.wav', + + snd_low = 'octoteam/vehicles/esperanto_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/esperanto_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/esperanto_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/esperanto_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/esperanto_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/patriot_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_rom', V ) + +local light_table = { + L_HeadLampPos = Vector(93,26,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(93,-26,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101,33,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101,-33,4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(93,26,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(93,-26,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,18,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(94,-18,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(23,18,15), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(94,18,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(94,-18,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,26,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-26,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,18,14.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-101,33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-33,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-101,13,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-101,-13,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(94,18,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,19,15), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-101,21,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(94,-18,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,17,15), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-101,-21,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [7] = '', + [10] = '' + }, + Brake = { + [9] = '', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = '' + }, + Reverse = { + [9] = '', + [7] = '', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + Brake_Reverse = { + [9] = '', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = '', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [7] = 'models/gta4/vehicles/esperanto/esperanto_lights_on', + [10] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/esperanto/esperanto_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_rom', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_romero.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_romero.lua new file mode 100644 index 0000000..62834bd --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_romero.lua @@ -0,0 +1,348 @@ +local V = { + Name = 'Romero', + Model = 'models/octoteam/vehicles/romero.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2800.0, + + EnginePos = Vector(80,0,10), + + LightsTable = 'gta4_romero', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,4)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/romero_wheel.mdl', + + CustomWheelPosFL = Vector(76,32,-11), + CustomWheelPosFR = Vector(76,-32,-11), + CustomWheelPosRL = Vector(-77,32,-11), + CustomWheelPosRR = Vector(-77,-32,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 33, + + SeatOffset = Vector(10,-19.5,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(25,-20,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-118,23.5,-13), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 30000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 30000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 78, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 120.0, + PowerbandStart = 2000, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-58,39,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/admiral_idle.wav', + + snd_low = 'octoteam/vehicles/admiral_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/admiral_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/admiral_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/admiral_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/admiral_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_romero', V ) + +local light_table = { + L_HeadLampPos = Vector(105,27,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(105,-27,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-121,33,11), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-121,-33,11), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(105,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(105,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(41.4,20.9,16.8), + material = 'gta4/dash_lowbeam', + size = 0.5, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(105,27,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(105,-27,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(41.2,20.9,16.2), + material = 'gta4/dash_highbeam', + size = 0.5, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-121,33,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-121,-33,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-121,32,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-121,-32,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-125,10,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-125,-10,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(105,28,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108.5,39,-0.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(41.8,21.8,18.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(105,-28,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108.5,-39,-0.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(41.8,20,18.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [7] = '', + [8] = '' + }, + Brake = { + [3] = '', + [7] = 'models/gta4/vehicles/washington/romero_lights_on', + [8] = '' + }, + Reverse = { + [3] = '', + [7] = '', + [8] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + Brake_Reverse = { + [3] = '', + [7] = 'models/gta4/vehicles/washington/romero_lights_on', + [8] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = '', + [8] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = 'models/gta4/vehicles/washington/romero_lights_on', + [8] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = '', + [8] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = 'models/gta4/vehicles/washington/romero_lights_on', + [8] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = '', + [8] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = 'models/gta4/vehicles/washington/romero_lights_on', + [8] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = '', + [8] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/washington/romero_lights_on', + [7] = 'models/gta4/vehicles/washington/romero_lights_on', + [8] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/washington/romero_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_romero', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ruiner.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ruiner.lua new file mode 100644 index 0000000..539332c --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_ruiner.lua @@ -0,0 +1,445 @@ +local V = { + Name = 'Ruiner', + Model = 'models/octoteam/vehicles/ruiner.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1400, + Trunk = { + 25, + {30, 3, 0}, + }, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_ruiner', + + OnSpawn = function(ent) + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/ruiner_wheel.mdl', + + CustomWheelPosFL = Vector(58,29,-18), + CustomWheelPosFR = Vector(58,-29,-18), + CustomWheelPosRL = Vector(-55,29,-18), + CustomWheelPosRR = Vector(-55,-29,-18), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-20,-17,10), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-10,-17,-23), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-100,22.3,-16.6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-100,18,-16.6), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 27000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 7, + RearConstant = 27000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4, + CounterSteeringMul = 0.8, + + MaxGrip = 46, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 28, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 49, + PowerbandStart = 1200, + PowerbandEnd = 5600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-70,34,5), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/faction_idle.wav', + + snd_low = 'octoteam/vehicles/faction_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/faction_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/faction_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/faction_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/faction_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/ruiner_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(11.294, 16.815, 2.816), ang = Angle(0.0, -90.0, 90.0) }, + Radio = { pos = Vector(23.087, 1.655, 0.963), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(102.531, -0.004, -15.657), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-100.392, -0.005, -10.817), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(-3.732, 0.002, 18.203), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(15.149, 37.626, 8.769), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(15.401, -37.765, 8.911), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_ruiner', V ) + +local light_table = { + L_HeadLampPos = Vector(88,21,-5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(88,-21,-5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-96,18,1), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-96,-18,1), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(88,21,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(88,-21,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(11,26.5,2), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(99,247,247,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(88,21,-5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(88,-21,-5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(11,26.5,3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(99,247,247,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-96,25,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,18,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,11,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + -- + { + pos = Vector(-96,-11,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-18,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-25,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-96,25,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,18,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,11,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + -- + { + pos = Vector(-96,-11,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-18,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-25,3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-96,11,-1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-96,-11,-1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(101,21,-11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-96,25,-1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + { + pos = Vector(-95,32,-1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + { + pos = Vector(-95,32,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + +--[[ { + pos = Vector(11,18,5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(99,247,247,255), + },]] + }, + Right = { + { + pos = Vector(101,-21,-11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-96,-25,-1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + { + pos = Vector(-95,-32,-1.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + },{ + pos = Vector(-95,-32,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + +--[[ { + pos = Vector(11,15,5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(99,247,247,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [14] = '', + [5] = '', + }, + Brake = { + [9] = '', + [14] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [5] = '', + }, + Reverse = { + [9] = '', + [14] = '', + [5] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + }, + Brake_Reverse = { + [9] = '', + [14] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [5] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = '', + [5] = '', + }, + Brake = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [5] = '', + }, + Reverse = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = '', + [5] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [5] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = '', + [5] = '', + }, + Brake = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [5] = '', + }, + Reverse = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = '', + [5] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [14] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + [5] = 'models/gta4/vehicles/ruiner/ruiner_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/ruiner/ruiner_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/ruiner/ruiner_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_ruiner', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabre.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabre.lua new file mode 100644 index 0000000..d3c7bb3 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabre.lua @@ -0,0 +1,407 @@ +local V = { + Name = 'Sabre', + Model = 'models/octoteam/vehicles/sabre.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1600, + Trunk = { 35 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_sabre', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(3,1,8)} + -- CarCols[2] = {REN.GTA4ColorTable(4,1,8)} + -- CarCols[3] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[4] = {REN.GTA4ColorTable(20,1,12)} + -- CarCols[5] = {REN.GTA4ColorTable(22,6,12)} + -- CarCols[6] = {REN.GTA4ColorTable(31,34,31)} + -- CarCols[7] = {REN.GTA4ColorTable(34,34,34)} + -- CarCols[8] = {REN.GTA4ColorTable(39,39,39)} + -- CarCols[9] = {REN.GTA4ColorTable(49,49,50)} + -- CarCols[10] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[11] = {REN.GTA4ColorTable(57,52,50)} + -- CarCols[12] = {REN.GTA4ColorTable(68,64,63)} + -- CarCols[13] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[14] = {REN.GTA4ColorTable(72,72,72)} + -- CarCols[15] = {REN.GTA4ColorTable(95,1,90)} + -- CarCols[16] = {REN.GTA4ColorTable(98,98,98)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/sabre_wheel.mdl', + + CustomWheelPosFL = Vector(58,31,-7), + CustomWheelPosFR = Vector(58,-31,-7), + CustomWheelPosRL = Vector(-58,31,-7), + CustomWheelPosRR = Vector(-58,-31,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-13,-17,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-17,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-93,27,-10), + ang = Angle(-100,-45,0), + }, + { + pos = Vector(-93,-27,-10), + ang = Angle(-100,45,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 30000, + FrontDamping = 850, + FrontRelativeDamping = 850, + + RearHeight = 8, + RearConstant = 30000, + RearDamping = 850, + RearRelativeDamping = 850, + + FastSteeringAngle = 12, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + CounterSteeringMul = 0.83, + + MaxGrip = 44, + Efficiency = 0.92, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 50, + PowerbandStart = 1200, + PowerbandEnd = 6800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.2, + + FuelFillPos = Vector(-62,35,14), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 60, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/faction_idle.wav', + + snd_low = 'octoteam/vehicles/faction_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/faction_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/faction_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/faction_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/faction_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sabre_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.35, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(18.1, 16.908, 16.9), ang = Angle(-0.0, -90.0, 77.3) }, + Radio = { pos = Vector(24.662, 0.008, 12.452), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(103.517, 22.292, -3.713), ang = Angle(-0.0, 5.3, 0.0) }, + Back = { pos = Vector(-102.160, -0.010, 7.326), ang = Angle(-20.6, -180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(5.634, 0.012, 30.896), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(15.105, 36.209, 22.602), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(16.176, -35.791, 21.916), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sabre', V ) + +local light_table = { + L_HeadLampPos = Vector(95,28,7.7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-28,7.7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-99,30,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-99,-30,14), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(95,28,7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-28,7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,18.6,7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-18.6,7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(18.6,24.8,18), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,28,7.7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-28,7.7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,18.6,7.7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-18.6,7.7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(18.1,24.8,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-101,30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-99,30,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-30,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-103,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-103,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(94,33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + +--[[ { + pos = Vector(18.6,17.6,19.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(94,-33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,-33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + +--[[ { + pos = Vector(18.6,16.9,19.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [12] = '', + [4] = '', + }, + Brake = { + [3] = '', + [12] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [4] = '', + }, + Reverse = { + [3] = '', + [12] = '', + [4] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + Brake_Reverse = { + [3] = '', + [12] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [4] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = '', + [4] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [4] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = '', + [4] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [4] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = '', + [4] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [4] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = '', + [4] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [12] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [4] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/sabre/carlito_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/sabre/carlito_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_sabre', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabre2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabre2.lua new file mode 100644 index 0000000..81bf397 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabre2.lua @@ -0,0 +1,383 @@ +local V = { + Name = 'Sabre (Beater)', + Model = 'models/octoteam/vehicles/sabre2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_sabre2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,1)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(3,1,8)} + -- CarCols[2] = {REN.GTA4ColorTable(4,1,8)} + -- CarCols[3] = {REN.GTA4ColorTable(10,3,8)} + -- CarCols[4] = {REN.GTA4ColorTable(11,11,12)} + -- CarCols[5] = {REN.GTA4ColorTable(20,1,12)} + -- CarCols[6] = {REN.GTA4ColorTable(22,6,12)} + -- CarCols[7] = {REN.GTA4ColorTable(28,0,4)} + -- CarCols[8] = {REN.GTA4ColorTable(31,34,31)} + -- CarCols[9] = {REN.GTA4ColorTable(34,34,34)} + -- CarCols[10] = {REN.GTA4ColorTable(39,39,39)} + -- CarCols[11] = {REN.GTA4ColorTable(49,49,50)} + -- CarCols[12] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[13] = {REN.GTA4ColorTable(57,52,50)} + -- CarCols[14] = {REN.GTA4ColorTable(68,64,63)} + -- CarCols[15] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[16] = {REN.GTA4ColorTable(72,72,72)} + -- CarCols[17] = {REN.GTA4ColorTable(95,1,90)} + -- CarCols[18] = {REN.GTA4ColorTable(98,98,98)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + REN.GTA4BeaterInit(ent) + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Beater(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/sabre2_wheel.mdl', + + CustomWheelPosFL = Vector(58,31,-7), + CustomWheelPosFR = Vector(58,-31,-7), + CustomWheelPosRL = Vector(-58,31,-7), + CustomWheelPosRR = Vector(-58,-31,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-13,-17,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-17,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-94,22,-11), + ang = Angle(-90,-10,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-93.2,-26.9,-13.6), + ang = Angle(-100,35,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-92.7,26.8,-15.3), + ang = Angle(-100,-25,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-33.1,29,-12.7), + ang = Angle(-90,-80,0), + OnBodyGroups = { + [1] = {2}, + } + }, + { + pos = Vector(-36.9,29,-12.7), + ang = Angle(-90,-75,0), + OnBodyGroups = { + [1] = {2}, + } + }, + }, + + FrontHeight = 9, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 11, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 72, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 140.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-62,35,14), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/faction_idle.wav', + + snd_low = 'octoteam/vehicles/faction_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/faction_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/faction_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/faction_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/faction_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sabre2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.5,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sabre2', V ) + +local light_table = { + L_HeadLampPos = Vector(95,28,7.7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-28,7.7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-99,30,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-99,-30,14), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(95,28,7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-28,7.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(18.6,24.8,18), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,28,7.7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-28,7.7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(18.1,24.8,17), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-101,30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-99,30,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-30,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-103,27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-103,-27,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(94,33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + }, + Right = { + { + pos = Vector(94,-33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-102,-33,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,100,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [9] = '', + [6] = '', + }, + Brake = { + [3] = '', + [9] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [6] = '', + }, + Reverse = { + [3] = '', + [9] = '', + [6] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + Brake_Reverse = { + [3] = '', + [9] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [6] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = '', + [6] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [6] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = '', + [6] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [6] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = '', + [6] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [6] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = '', + [6] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [9] = 'models/gta4/vehicles/sabre/carlito_lights_on', + [6] = 'models/gta4/vehicles/sabre/carlito_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/sabre/carlito_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/sabre/carlito_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_sabre2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabregt.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabregt.lua new file mode 100644 index 0000000..ccb250e --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sabregt.lua @@ -0,0 +1,392 @@ +local V = { + Name = 'Sabre GT', + Model = 'models/octoteam/vehicles/sabregt.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1550, + Trunk = { 30 }, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_sabregt', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,49)} + -- CarCols[2] = {REN.GTA4ColorTable(38,38,85)} + -- CarCols[3] = {REN.GTA4ColorTable(0,97,102)} + -- CarCols[4] = {REN.GTA4ColorTable(3,0,12)} + -- CarCols[5] = {REN.GTA4ColorTable(6,6,4)} + -- CarCols[6] = {REN.GTA4ColorTable(11,4,12)} + -- CarCols[7] = {REN.GTA4ColorTable(13,13,12)} + -- CarCols[8] = {REN.GTA4ColorTable(16,95,90)} + -- CarCols[9] = {REN.GTA4ColorTable(24,1,12)} + -- CarCols[10] = {REN.GTA4ColorTable(21,21,12)} + -- CarCols[11] = {REN.GTA4ColorTable(23,23,2)} + -- CarCols[12] = {REN.GTA4ColorTable(31,31,27)} + -- CarCols[13] = {REN.GTA4ColorTable(32,113,34)} + -- CarCols[14] = {REN.GTA4ColorTable(34,12,34)} + -- CarCols[15] = {REN.GTA4ColorTable(49,13,41)} + -- CarCols[16] = {REN.GTA4ColorTable(52,0,59)} + -- CarCols[17] = {REN.GTA4ColorTable(62,13,69)} + -- CarCols[18] = {REN.GTA4ColorTable(76,4,76)} + -- CarCols[19] = {REN.GTA4ColorTable(82,85,76)} + -- CarCols[20] = {REN.GTA4ColorTable(87,8,76)} + -- CarCols[21] = {REN.GTA4ColorTable(89,0,4)} + -- CarCols[22] = {REN.GTA4ColorTable(92,95,90)} + -- CarCols[23] = {REN.GTA4ColorTable(95,95,2)} + -- CarCols[24] = {REN.GTA4ColorTable(106,103,2)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/sabregt_wheel.mdl', + + CustomWheelPosFL = Vector(60,32,-9), + CustomWheelPosFR = Vector(60,-32,-9), + CustomWheelPosRL = Vector(-59,32,-9), + CustomWheelPosRR = Vector(-59,-32,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-15,-20,18), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-18,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-110.5,22.9,-5.4), + ang = Angle(-90,-10,0), + }, + { + pos = Vector(-110.5,-22.9,-5.4), + ang = Angle(-90,10,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 29000, + FrontDamping = 850, + FrontRelativeDamping = 850, + + RearHeight = 7, + RearConstant = 29000, + RearDamping = 850, + RearRelativeDamping = 850, + + FastSteeringAngle = 12, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4.5, + CounterSteeringMul = 0.83, + + MaxGrip = 44, + Efficiency = 0.92, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 58, + PowerbandStart = 1200, + PowerbandEnd = 6800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.25, + + FuelFillPos = Vector(-58,-38,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 60, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/dukes_idle.wav', + + snd_low = 'octoteam/vehicles/vigero_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/vigero_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/vigero_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/vigero_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/vigero_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sabre_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.35, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(11.600, 18.997, 12.176), ang = Angle(-0.0, -90.0, 66.6) }, + Radio = { pos = Vector(14.656, 2.860, 8.776), ang = Angle(11.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(99.402, 0.011, -6.936), ang = Angle(10.5, -0.0, 0.0) }, + Back = { pos = Vector(-110.567, -0.000, -1.501), ang = Angle(-0.3, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(7.644, -0.000, 27.300), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(16.751, 37.706, 16.879), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(16.516, -37.297, 19.299), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sabregt', V ) + +local light_table = { + L_HeadLampPos = Vector(92,31.4,4.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(92,-31.4,4.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-111,22.8,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-111,-22.8,5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(92,31.4,4.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(92,-31.4,4.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(93,24.4,4.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(93,-24.4,4.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(12,28,13), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(92,31.4,4.6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,-31.4,4.6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,24.4,4.6),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(93,-24.4,4.6),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(11,28,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-111,22.8,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-111,-22.8,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-111,22.8,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-111,-22.8,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-111,27,2.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-111,22.8,2.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-111,-27,2.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-111,-22.8,2.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95,27.9,-4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + { + pos = Vector(-111,27,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(12,20.5,13), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(95,-27.9,-4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + { + pos = Vector(-111,-27,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(12,17,13), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [10] = '' + }, + Reverse = { + [5] = '', + [10] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on', + [10] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on', + [10] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on', + [10] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on', + [10] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/sabre/sabreturbo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_sabregt', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_schafter.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_schafter.lua new file mode 100644 index 0000000..e8b45a0 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_schafter.lua @@ -0,0 +1,455 @@ +local V = { + Name = 'Schafter', + Model = 'models/octoteam/vehicles/schafter.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1650, + Trunk = { 30 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_schafter', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(13,133,80)} + -- CarCols[2] = {REN.GTA4ColorTable(70,133,8)} + -- CarCols[3] = {REN.GTA4ColorTable(16,133,8)} + -- CarCols[4] = {REN.GTA4ColorTable(22,133,8)} + -- CarCols[5] = {REN.GTA4ColorTable(26,133,18)} + -- CarCols[6] = {REN.GTA4ColorTable(34,133,34)} + -- CarCols[7] = {REN.GTA4ColorTable(43,133,43)} + -- CarCols[8] = {REN.GTA4ColorTable(54,133,54)} + -- CarCols[9] = {REN.GTA4ColorTable(57,133,57)} + -- CarCols[10] = {REN.GTA4ColorTable(61,133,61)} + -- CarCols[11] = {REN.GTA4ColorTable(65,133,65)} + -- CarCols[12] = {REN.GTA4ColorTable(68,133,68)} + -- CarCols[13] = {REN.GTA4ColorTable(77,133,77)} + -- CarCols[14] = {REN.GTA4ColorTable(104,133,103)} + -- CarCols[15] = {REN.GTA4ColorTable(106,133,103)} + -- CarCols[16] = {REN.GTA4ColorTable(108,133,108)} + -- CarCols[17] = {REN.GTA4ColorTable(15,133,93)} + -- CarCols[18] = {REN.GTA4ColorTable(19,1,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/schafter_wheel.mdl', + + CustomWheelPosFL = Vector(65,31,-9), + CustomWheelPosFR = Vector(65,-31,-9), + CustomWheelPosRL = Vector(-65,31,-9), + CustomWheelPosRR = Vector(-65,-31,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-18,21), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-18,-10), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-38,18,-10), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-38,-18,-10), + ang = Angle(0,-90,15) + }, + }, + ExhaustPositions = { + { + pos = Vector(-112,24.5,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-112,19.5,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-112,-19.5,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-112,-24.5,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 32500, + FrontDamping = 900, + FrontRelativeDamping = 900, + + RearHeight = 8, + RearConstant = 32500, + RearDamping = 900, + RearRelativeDamping = 900, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4, + + MaxGrip = 52, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 45, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-82,36,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/buffalo_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(22.103, 18.573, 16.122), ang = Angle(0.0, -90.0, 76.2) }, + Radio = { pos = Vector(27.702, -0.498, 12.245), ang = Angle(-26.3, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(101.497, -0.003, -6.675), ang = Angle(5.8, -0.0, -0.0) }, + Back = { pos = Vector(-111.733, 0.001, 13.583), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(6.748, -0.006, 31.592), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(20.680, 35.212, 21.968), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(19.761, -35.295, 22.149), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_schafter', V ) + +local V2 = {} +V2.Name = 'Russian Mafia Schafter' +V2.Model = 'models/octoteam/vehicles/schafter.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,20) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,1,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(10,10,10), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_schafter2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(89,27,6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(89,-27,6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-109,27,11), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-109,-27,11), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(89,27,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(89,-27,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(25,21.5,16), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,22,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-22,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(25,16,16), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-109,27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-109,26,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-26,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-111,23,8.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-111,-23,8.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,31,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-109,27,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,21,20), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(86,-31,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-109,-27,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,16,20), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [6] = '', + [9] = '', + [12] = '', + }, + Brake = { + [7] = '', + [6] = '', + [9] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [12] = '', + }, + Reverse = { + [7] = '', + [6] = '', + [9] = '', + [12] = 'models/gta4/vehicles/schafter/schafter_lights_on', + }, + Brake_Reverse = { + [7] = '', + [6] = '', + [9] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [12] = 'models/gta4/vehicles/schafter/schafter_lights_on', + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = '', + [9] = '', + [12] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = '', + [9] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [12] = '', + }, + Reverse = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = '', + [9] = '', + [12] = 'models/gta4/vehicles/schafter/schafter_lights_on', + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = '', + [9] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [12] = 'models/gta4/vehicles/schafter/schafter_lights_on', + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [9] = '', + [12] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [9] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [12] = '', + }, + Reverse = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/schafter/schafter_lights_on', + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [6] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [9] = 'models/gta4/vehicles/schafter/schafter_lights_on', + [12] = 'models/gta4/vehicles/schafter/schafter_lights_on', + }, + }, + turnsignals = { + left = { + [14] = 'models/gta4/vehicles/schafter/schafter_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/schafter/schafter_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_schafter', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sentinel.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sentinel.lua new file mode 100644 index 0000000..8e84611 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sentinel.lua @@ -0,0 +1,381 @@ +local V = { + Name = 'Sentinel', + Model = 'models/octoteam/vehicles/sentinel.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Хетчбеки', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Хетчбеки', + + Members = { + Mass = 1600.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_sentinel', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,1,50)} + -- CarCols[2] = {REN.GTA4ColorTable(0,1,1)} + -- CarCols[3] = {REN.GTA4ColorTable(0,133,1)} + -- CarCols[4] = {REN.GTA4ColorTable(7,1,12)} + -- CarCols[5] = {REN.GTA4ColorTable(12,1,12)} + -- CarCols[6] = {REN.GTA4ColorTable(19,133,19)} + -- CarCols[7] = {REN.GTA4ColorTable(34,1,45)} + -- CarCols[8] = {REN.GTA4ColorTable(31,133,30)} + -- CarCols[9] = {REN.GTA4ColorTable(38,133,34)} + -- CarCols[10] = {REN.GTA4ColorTable(52,133,56)} + -- CarCols[11] = {REN.GTA4ColorTable(68,133,103)} + -- CarCols[12] = {REN.GTA4ColorTable(71,133,103)} + -- CarCols[13] = {REN.GTA4ColorTable(77,133,77)} + -- CarCols[14] = {REN.GTA4ColorTable(85,133,77)} + -- CarCols[15] = {REN.GTA4ColorTable(106,103,90)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/sentinel_wheel.mdl', + + CustomWheelPosFL = Vector(60,30,-15), + CustomWheelPosFR = Vector(60,-30,-15), + CustomWheelPosRL = Vector(-55,30,-15), + CustomWheelPosRR = Vector(-55,-30,-15), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-13,-15,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-2,-15,-19), + ang = Angle(0,-90,15), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-92,18.5,-16.6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-92,-18.5,-16.6), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 26, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 150.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-68,31,9), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/sultan_idle.wav', + + snd_low = 'octoteam/vehicles/sultan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/sultan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/sultan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/sultan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/sultan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sultanrs_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.2, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sentinel', V ) + +local light_table = { + L_HeadLampPos = Vector(84,21,-1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(84,-21,-1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-92,23,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-92,-23,5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(84,21,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(84,-21,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(22,20,9.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(84,21,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(84,-21,-1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(22,19.4,9.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(88,21,-14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(88,-21,-14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-92,23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-92,-23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-92,23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-92,-23,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-93,20,1.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-93,-20,1.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(79,29,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,27,1.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(22,16,12.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(79,-29,-1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,-27,1.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(22,14,12.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [8] = '', + [3] = '', + }, + Brake = { + [7] = '', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = '', + }, + Reverse = { + [7] = '', + [8] = '', + [3] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + }, + Brake_Reverse = { + [7] = '', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = '', + }, + Reverse = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = '', + }, + Reverse = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + }, + Brake_Reverse = { + [7] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [8] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + [3] = 'models/gta4/vehicles/sentinel/sentinel_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/sentinel/sentinel_lights_on' + }, + right = { + [4] = 'models/gta4/vehicles/sentinel/sentinel_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_sentinel', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_solair.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_solair.lua new file mode 100644 index 0000000..2484692 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_solair.lua @@ -0,0 +1,410 @@ +local V = { + Name = 'Solair', + Model = 'models/octoteam/vehicles/solair.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2100.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_solair', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(72,72,1)} + -- CarCols[2] = {REN.GTA4ColorTable(30,30,1)} + -- CarCols[3] = {REN.GTA4ColorTable(34,34,1)} + -- CarCols[4] = {REN.GTA4ColorTable(37,37,1)} + -- CarCols[5] = {REN.GTA4ColorTable(43,43,1)} + -- CarCols[6] = {REN.GTA4ColorTable(52,52,1)} + -- CarCols[7] = {REN.GTA4ColorTable(54,54,1)} + -- CarCols[8] = {REN.GTA4ColorTable(55,55,1)} + -- CarCols[9] = {REN.GTA4ColorTable(52,52,1)} + -- CarCols[10] = {REN.GTA4ColorTable(65,65,1)} + -- CarCols[11] = {REN.GTA4ColorTable(69,69,1)} + -- CarCols[12] = {REN.GTA4ColorTable(70,70,1)} + -- CarCols[13] = {REN.GTA4ColorTable(72,72,1)} + -- CarCols[14] = {REN.GTA4ColorTable(75,75,1)} + -- CarCols[15] = {REN.GTA4ColorTable(84,84,1)} + -- CarCols[16] = {REN.GTA4ColorTable(88,88,1)} + -- CarCols[17] = {REN.GTA4ColorTable(90,90,1)} + -- CarCols[18] = {REN.GTA4ColorTable(106,106,1)} + -- CarCols[19] = {REN.GTA4ColorTable(119,119,1)} + -- CarCols[20] = {REN.GTA4ColorTable(111,111,1)} + -- CarCols[21] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[22] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[23] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/solair_wheel.mdl', + + CustomWheelPosFL = Vector(60,31,-4), + CustomWheelPosFR = Vector(60,-31,-4), + CustomWheelPosRL = Vector(-60,31,-4), + CustomWheelPosRR = Vector(-60,-31,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-7,-19,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(6,-18,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-40,18,-5), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-40,-18,-5), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-98,-21,-4.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 28000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 28000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 18, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 130.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-75,-35,17.5), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/lokus_idle.wav', + + snd_low = 'octoteam/vehicles/lokus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/lokus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/lokus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/lokus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/lokus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/solair_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.15, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_solair', V ) + +local light_table = { + L_HeadLampPos = Vector(92,24,9.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(92,-24,9.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-103,16,15.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-103,-16,15.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(92,24,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(92,-24,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(29,11,20), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(92,24,9.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,-24,9.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29,11,21), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(96,26,-2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(96,-26,-2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-103,16,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,-16,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-102,18,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-103,0,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102,-18,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-102,21,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-102,-21,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(90,27.9,9.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-100,30,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100,31,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.4,22.5,23), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(90,-27.9,9.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-100,-30,20), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100,-31,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.4,16,23), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [3] = '', + [11] = '', + }, + Brake = { + [6] = '', + [3] = 'models/gta4/vehicles/solair/solair_lights_on', + [11] = '', + }, + Reverse = { + [6] = '', + [3] = '', + [11] = 'models/gta4/vehicles/solair/solair_lights_on', + }, + Brake_Reverse = { + [6] = '', + [3] = 'models/gta4/vehicles/solair/solair_lights_on', + [11] = 'models/gta4/vehicles/solair/solair_lights_on', + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = '', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = 'models/gta4/vehicles/solair/solair_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = '', + [11] = 'models/gta4/vehicles/solair/solair_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = 'models/gta4/vehicles/solair/solair_lights_on', + [11] = 'models/gta4/vehicles/solair/solair_lights_on', + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = '', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = 'models/gta4/vehicles/solair/solair_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = '', + [11] = 'models/gta4/vehicles/solair/solair_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/solair/solair_lights_on', + [3] = 'models/gta4/vehicles/solair/solair_lights_on', + [11] = 'models/gta4/vehicles/solair/solair_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/solair/solair_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/solair/solair_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_solair', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_speedo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_speedo.lua new file mode 100644 index 0000000..4d00c3d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_speedo.lua @@ -0,0 +1,368 @@ +local V = { + Name = 'Speedo', + Model = 'models/octoteam/vehicles/speedo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(80,0,10), + + LightsTable = 'gta4_speedo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,11)) + -- ent:SetBodyGroups('0'..math.random(0,4) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(9,9,9)} + -- CarCols[2] = {REN.GTA4ColorTable(11,11,11)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,2)} + -- CarCols[4] = {REN.GTA4ColorTable(13,13,13)} + -- CarCols[5] = {REN.GTA4ColorTable(23,23,3)} + -- CarCols[6] = {REN.GTA4ColorTable(68,68,68)} + -- CarCols[7] = {REN.GTA4ColorTable(31,31,31)} + -- CarCols[8] = {REN.GTA4ColorTable(36,36,36)} + -- CarCols[9] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[10] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[11] = {REN.GTA4ColorTable(55,55,55)} + -- CarCols[12] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[13] = {REN.GTA4ColorTable(64,64,64)} + -- CarCols[14] = {REN.GTA4ColorTable(71,71,71)} + -- CarCols[15] = {REN.GTA4ColorTable(77,77,77)} + -- CarCols[16] = {REN.GTA4ColorTable(90,90,90)} + -- CarCols[17] = {REN.GTA4ColorTable(104,104,104)} + -- CarCols[18] = {REN.GTA4ColorTable(106,106,104)} + -- CarCols[19] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[20] = {REN.GTA4ColorTable(4,4,4)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/speedo_wheel.mdl', + + CustomWheelPosFL = Vector(70,36,-19), + CustomWheelPosFR = Vector(70,-36,-19), + CustomWheelPosRL = Vector(-69,36,-19), + CustomWheelPosRR = Vector(-69,-36,-19), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(18,-21,30), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(23,-20,-3), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-102.9,36.8,-18.4), + ang = Angle(-100,-50,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 500, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 500, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 85, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 125.0, + PowerbandStart = 2200, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-40,41,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/speedo_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_speedo', V ) + +local light_table = { + L_HeadLampPos = Vector(97,29,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(97,-29,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-109,36,19), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-109,-36,19), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(97,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(97,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(51,24,21), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(97,29,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(97,-29,5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(51,25,21), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-109,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-109,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-110,36,13.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-110,-36,13.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(97,36,-0.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108,35,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(52,22,22), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(97,-36,-0.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-108,-35,25), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(52,18,22), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [3] = '', + [11] = '', + }, + Brake = { + [10] = '', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = '', + }, + Reverse = { + [10] = '', + [3] = '', + [11] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + Brake_Reverse = { + [10] = '', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [3] = 'models/gta4/vehicles/speedo/speedo_lights_on', + [11] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/speedo/speedo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_speedo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stallion.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stallion.lua new file mode 100644 index 0000000..36d8470 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stallion.lua @@ -0,0 +1,275 @@ +local V = { + Name = 'Stallion', + Model = 'models/octoteam/vehicles/stallion.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_stallion', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,124)} + -- CarCols[3] = {REN.GTA4ColorTable(33,0,28)} + -- CarCols[4] = {REN.GTA4ColorTable(34,0,35)} + -- CarCols[5] = {REN.GTA4ColorTable(38,0,44)} + -- CarCols[6] = {REN.GTA4ColorTable(49,0,66)} + -- CarCols[7] = {REN.GTA4ColorTable(53,0,51)} + -- CarCols[8] = {REN.GTA4ColorTable(56,0,51)} + -- CarCols[9] = {REN.GTA4ColorTable(58,0,106)} + -- CarCols[10] = {REN.GTA4ColorTable(59,0,51)} + -- CarCols[11] = {REN.GTA4ColorTable(62,0,71)} + -- CarCols[12] = {REN.GTA4ColorTable(64,0,71)} + -- CarCols[13] = {REN.GTA4ColorTable(69,0,74)} + -- CarCols[14] = {REN.GTA4ColorTable(74,0,74)} + -- CarCols[15] = {REN.GTA4ColorTable(76,0,74)} + -- CarCols[16] = {REN.GTA4ColorTable(84,0,74)} + -- CarCols[17] = {REN.GTA4ColorTable(106,0,106)} + -- CarCols[18] = {REN.GTA4ColorTable(21,0,75)} + -- CarCols[19] = {REN.GTA4ColorTable(16,0,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/stallion_wheel.mdl', + + CustomWheelPosFL = Vector(62,34,-8), + CustomWheelPosFR = Vector(62,-34,-8), + CustomWheelPosRL = Vector(-61,34,-8), + CustomWheelPosRR = Vector(-61,-34,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-19,-19,23), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-5,-17,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-109,14,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 13, + FrontConstant = 18000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 13, + RearConstant = 18000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 73, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 15, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 140.0, + PowerbandStart = 1500, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-60,39,18), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/vigero_idle.wav', + + snd_low = 'octoteam/vehicles/vigero_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/vigero_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/vigero_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/vigero_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/vigero_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stallion_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.20, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_stallion', V ) + +local light_table = { + L_HeadLampPos = Vector(100,35,8), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(100,-35,8), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-114,29,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-114,-29,4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(100,35,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(100,-35,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(100,35,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(100,-35,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-114,29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-114,-29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-114,29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-114,-29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-114,17,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(-114,-17,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [7] = '', + }, + Brake = { + [11] = '', + [7] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/stallion/stallion_lights_on', + [7] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + Brake = { + [11] = 'models/gta4/vehicles/stallion/stallion_lights_on', + [7] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/stallion/stallion_lights_on', + [7] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + Brake = { + [11] = 'models/gta4/vehicles/stallion/stallion_lights_on', + [7] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + }, + turnsignals = { + left = { + [6] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + right = { + [8] = 'models/gta4/vehicles/stallion/stallion_lights_on', + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_stallion', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_steed.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_steed.lua new file mode 100644 index 0000000..0196d3e --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_steed.lua @@ -0,0 +1,352 @@ +local V = { + Name = 'Steed', + Model = 'models/octoteam/vehicles/steed.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 5500.0, + + EnginePos = Vector(110,0,20), + + LightsTable = 'gta4_steed', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,11)) + -- ent:SetBodyGroups('0'..math.random(0,6) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,113,113)} + -- CarCols[2] = {REN.GTA4ColorTable(116,1,115)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/steed_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/steed_wheel_r.mdl', + + CustomWheelPosFL = Vector(102,34,-5), + CustomWheelPosFR = Vector(102,-34,-5), + CustomWheelPosRL = Vector(-104,37,-5), + CustomWheelPosRR = Vector(-104,-37,-5), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 15.3, + RearWheelRadius = 15.3, + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(37,-20,53), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(50,-22,10), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-52,16,-8), + ang = Angle(-120,-25,0), + }, + }, + + FrontHeight = 18, + FrontConstant = 50000, + FrontDamping = 3000, + FrontRelativeDamping = 700, + + RearHeight = 18, + RearConstant = 50000, + RearDamping = 3000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 103, + Efficiency = 1, + GripOffset = 0, + BrakePower = 45, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 5000, + PeakTorque = 100.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(85,41,8), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/burrito_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.11, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_steed', V ) + +local light_table = { + L_HeadLampPos = Vector(132,28,17), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(132,-28,17), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-169,33,-3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-169,-33,-3), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(132,28,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(132,-28,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(77.5,24,38.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(132,28,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(132,-28,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(77.5,25,38.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-169,33,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-169,-33,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-169,25,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-169,-25,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-179,37,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-179,-37,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(130,37,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-169,39,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(79,26,41), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(130,-37,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-169,-39,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(79,18,41), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [10] = '', + [9] = '', + }, + Brake = { + [5] = '', + [10] = 'models/gta4/vehicles/steed/steed_lights_on', + [9] = '', + }, + Reverse = { + [5] = '', + [10] = '', + [9] = 'models/gta4/vehicles/steed/steed_lights_on', + }, + Brake_Reverse = { + [5] = '', + [10] = 'models/gta4/vehicles/steed/steed_lights_on', + [9] = 'models/gta4/vehicles/steed/steed_lights_on', + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = '', + [9] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = 'models/gta4/vehicles/steed/steed_lights_on', + [9] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = '', + [9] = 'models/gta4/vehicles/steed/steed_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = 'models/gta4/vehicles/steed/steed_lights_on', + [9] = 'models/gta4/vehicles/steed/steed_lights_on', + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = '', + [9] = '', + }, + Brake = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = 'models/gta4/vehicles/steed/steed_lights_on', + [9] = '', + }, + Reverse = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = '', + [9] = 'models/gta4/vehicles/steed/steed_lights_on', + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/steed/steed_lights_on', + [10] = 'models/gta4/vehicles/steed/steed_lights_on', + [9] = 'models/gta4/vehicles/steed/steed_lights_on', + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/steed/steed_lights_on' + }, + right = { + [4] = 'models/gta4/vehicles/steed/steed_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_steed', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stockade.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stockade.lua new file mode 100644 index 0000000..6235cfa --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stockade.lua @@ -0,0 +1,558 @@ +local V = { + Name = 'Stockade', + Model = 'models/octoteam/vehicles/stockade.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 5000, + Trunk = { 100 }, + + EnginePos = Vector(102,0,34), + + LightsTable = 'gta4_stockade', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + MaxHealth = 5000, + IsArmored = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelModel = 'models/octoteam/vehicles/stockade_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/stockade_wheel_r.mdl', + + CustomWheelPosFL = Vector(93,44,-4), + CustomWheelPosFR = Vector(93,-44,-4), + CustomWheelPosRL = Vector(-93,44,-4), + CustomWheelPosRR = Vector(-93,-44,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 35, + + SeatOffset = Vector(20,-30,70), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(30,-29,30), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-131,-40,30), + ang = Angle(0,0,0), + noMirrors = true, + }, + { + pos = Vector(-105,-40,30), + ang = Angle(0,0,0), + noMirrors = true, + }, + { + pos = Vector(-131,40,30), + ang = Angle(0,180,0), + noMirrors = true, + }, + { + pos = Vector(-105,40,30), + ang = Angle(0,180,0), + noMirrors = true, + }, + { + pos = Vector(-160,-20,-6), + ang = Angle(0,90,0), + noMirrors = true, + }, + { + pos = Vector(-160,20,-6), + ang = Angle(0,90,0), + noMirrors = true, + }, + }, + ExhaustPositions = { + { + pos = Vector(-56.1,-46.4,-11.3), + ang = Angle(-90,90,0), + }, + { + pos = Vector(-52.1,-46.4,-11.3), + ang = Angle(-90,90,0), + }, + }, + + StrengthenSuspension = true, + + FrontHeight = 10, + FrontConstant = 35000, + FrontDamping = 4000, + FrontRelativeDamping = 4000, + + RearHeight = 10, + RearConstant = 35000, + RearDamping = 4000, + RearRelativeDamping = 4000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = true, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 75, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-10.1,50.6,6.7), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stockade_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + Dash = { pos = Vector(56.072, 29.429, 53.880), ang = Angle(0.0, -90.0, 71.9) }, + Radio = { pos = Vector(60.643, 3.031, 55.760), ang = Angle(-25.4, 156.2, 3.7) }, + Plates = { + Front = { pos = Vector(140.41, 0.014, 4.973), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-146.770, 18.756, 7.788), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + left = { + pos = Vector(53.704, 57.466, 67.447), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(53.932, -57.128, 69.094), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_stockade', V ) + +local light_table = { + L_HeadLampPos = Vector(128,41,19), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(128,-41,19), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-148.7,42.6,33.4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-148.7,-42.6,33.4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(128,41,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(128,-41,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(58,37,55), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(128,41,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(128,-41,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(58,38,55), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-148.7,42.6,33.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255, 145, 0), + }, + { + pos = Vector(-148.7,-42.6,33.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255, 145, 0), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-148.7,42.6,78.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255, 145, 0), + }, + { + pos = Vector(-148.7,-42.6,78.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255, 145, 0), + }, + }, + + ems_sounds = {'GTA4_SIREN_WAIL','GTA4_SIREN_YELP','GTA4_SIREN_WARNING'}, + ems_sprites = { + { + pos = Vector(72.5,36.25,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255, 145, 0), + Color(255, 145, 0), + Color(255, 145, 0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + }, + Speed = 0.035 + }, + { + pos = Vector(72.5,24,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(72.5,9.5,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255, 145, 0), + Color(255, 145, 0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(255, 145, 0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(72.5,0,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255, 145, 0, 50), + Color(255, 145, 0, 100), + -- + Color(255, 145, 0, 150), + Color(255, 145, 0, 255), + Color(255, 145, 0, 150), + -- + Color(255, 145, 0, 100), + Color(255, 145, 0, 50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(72.5,-9.5,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255, 145, 0), + Color(255, 145, 0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(255, 145, 0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(72.5,-24,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,255,255,50), + Color(255,255,255,100), + -- + Color(255,255,255,150), + Color(255,255,255,255), + Color(255,255,255,150), + -- + Color(255,255,255,100), + Color(255,255,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(72.5,-36.25,89.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255, 145, 0), + Color(255, 145, 0), + Color(255, 145, 0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-148.7,42.6,23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(60,35,57), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-148.7,-42.6,23.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(60,22,57), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [11] = '' + }, + Brake = { + [5] = '', + [11] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/stockade/detail2_on', + [11] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/stockade/detail2_on', + [11] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/stockade/detail2_on', + [11] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/stockade/detail2_on', + [11] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/stockade/detail2_on' + }, + right = { + [9] = 'models/gta4/vehicles/stockade/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_stockade', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stratum.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stratum.lua new file mode 100644 index 0000000..e45988a --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stratum.lua @@ -0,0 +1,388 @@ +local V = { + Name = 'Stratum', + Model = 'models/octoteam/vehicles/stratum.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1900.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_stratum', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1)..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,6,0)} + -- CarCols[2] = {REN.GTA4ColorTable(0,112,12)} + -- CarCols[3] = {REN.GTA4ColorTable(4,112,12)} + -- CarCols[4] = {REN.GTA4ColorTable(6,97,12)} + -- CarCols[5] = {REN.GTA4ColorTable(10,112,12)} + -- CarCols[6] = {REN.GTA4ColorTable(21,112,12)} + -- CarCols[7] = {REN.GTA4ColorTable(23,112,12)} + -- CarCols[8] = {REN.GTA4ColorTable(25,112,12)} + -- CarCols[9] = {REN.GTA4ColorTable(33,112,35)} + -- CarCols[10] = {REN.GTA4ColorTable(34,97,34)} + -- CarCols[11] = {REN.GTA4ColorTable(47,112,35)} + -- CarCols[12] = {REN.GTA4ColorTable(49,112,63)} + -- CarCols[13] = {REN.GTA4ColorTable(52,112,56)} + -- CarCols[14] = {REN.GTA4ColorTable(54,112,55)} + -- CarCols[15] = {REN.GTA4ColorTable(65,112,63)} + -- CarCols[16] = {REN.GTA4ColorTable(67,112,118)} + -- CarCols[17] = {REN.GTA4ColorTable(70,112,65)} + -- CarCols[18] = {REN.GTA4ColorTable(98,112,90)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/stratum_wheel.mdl', + + CustomWheelPosFL = Vector(59,33,-4), + CustomWheelPosFR = Vector(59,-33,-4), + CustomWheelPosRL = Vector(-59,33,-4), + CustomWheelPosRR = Vector(-59,-33,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-11,-16,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-16,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-30,16,-5), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-30,-16,-5), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-103,22,-5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 77, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 135.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-74,-37,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/lokus_idle.wav', + + snd_low = 'octoteam/vehicles/lokus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/lokus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/lokus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/lokus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/lokus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/stratum_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_stratum', V ) + +local light_table = { + L_HeadLampPos = Vector(94,23,11), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(94,-23,11), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101,32,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101,-32,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(94,23,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(94,-23,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(19,15,20), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(94,23,11),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(94,-23,11),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(19,14,20), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(96,26,-2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(96,-26,-2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-101,32,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101,-32,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-104,17,14.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-104,-17,14.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-104,24,11.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-104,-24,11.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(88,33,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-101,32,11.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(19,21,22), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(88,-33,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + { + pos = Vector(-101,-32,11.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(19,12,22), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [3] = '', + [11] = '', + }, + Brake = { + [6] = '', + [3] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [11] = '', + }, + Reverse = { + [6] = '', + [3] = '', + [11] = 'models/gta4/vehicles/stratum/stratum_lights_on', + }, + Brake_Reverse = { + [6] = '', + [3] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [11] = 'models/gta4/vehicles/stratum/stratum_lights_on', + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = '', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = '', + [11] = 'models/gta4/vehicles/stratum/stratum_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [11] = 'models/gta4/vehicles/stratum/stratum_lights_on', + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = '', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = '', + [11] = 'models/gta4/vehicles/stratum/stratum_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [3] = 'models/gta4/vehicles/stratum/stratum_lights_on', + [11] = 'models/gta4/vehicles/stratum/stratum_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/stratum/stratum_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/stratum/stratum_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_stratum', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stretch.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stretch.lua new file mode 100644 index 0000000..b79b876 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_stretch.lua @@ -0,0 +1,391 @@ +local V = { + Name = 'Stretch', + Model = 'models/octoteam/vehicles/stretch.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(123,0,4), + + LightsTable = 'gta4_stretch', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,12)} + -- CarCols[3] = {REN.GTA4ColorTable(113,113,113)} + -- CarCols[4] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[5] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[6] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[7] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[8] = {REN.GTA4ColorTable(13,13,80)} + -- CarCols[9] = {REN.GTA4ColorTable(0,0,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/stretch_wheel.mdl', + + CustomWheelPosFL = Vector(118,35.35,-16), + CustomWheelPosFR = Vector(118,-35.35,-16), + CustomWheelPosRL = Vector(-118,35.35,-16), + CustomWheelPosRR = Vector(-118,-35.35,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(45,-19.5,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(57,-20,-14), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-89,20,-14), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-89,-20,-14), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-170.4,18.7,-13.6), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-170.4,23,-13.6), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 73, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 22, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 140.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-144,-39,10), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/admiral_idle.wav', + + snd_low = 'octoteam/vehicles/admiral_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/admiral_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/admiral_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/admiral_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/admiral_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_stretch', V ) + +local light_table = { + L_HeadLampPos = Vector(154,26,3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(154,-26,3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-167,29,8), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-167,-29,8), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(154,26,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(154,-26,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(76.6,26.2,12.6), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(154,26,3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(154,-26,3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(76.8,26.2,13.7), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-167,29,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-169,16,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-167,-29,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-169,-16,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-171,16,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-169,29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-171,-16,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-169,-29,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-168.8,22.8,6.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-168.8,-22.8,6.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(148.2,35.5,3.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-164.6,37.4,6.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(77.4,22,16), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(148.2,-35.5,3.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-164.6,-37.4,6.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(77.4,16.3,16), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [7] = '', + [10] = '' + }, + Brake = { + [3] = '', + [7] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = '' + }, + Reverse = { + [3] = '', + [7] = '', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + Brake_Reverse = { + [3] = '', + [7] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = '', + [10] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = '', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = '', + [10] = '' + }, + Brake = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = '' + }, + Reverse = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = '', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [7] = 'models/gta4/vehicles/admiral/limo_lights_glass_on', + [10] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + right = { + [11] = 'models/gta4/vehicles/admiral/limo_lights_glass_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_stretch', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sultan.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sultan.lua new file mode 100644 index 0000000..6ca19c1 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sultan.lua @@ -0,0 +1,417 @@ +local V = { + Name = 'Sultan', + Model = 'models/octoteam/vehicles/sultan.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1400.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_sultan', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(1,2)..'000' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(13,0,80)} + -- CarCols[2] = {REN.GTA4ColorTable(0,1,1)} + -- CarCols[3] = {REN.GTA4ColorTable(3,1,1)} + -- CarCols[4] = {REN.GTA4ColorTable(10,1,1)} + -- CarCols[5] = {REN.GTA4ColorTable(25,1,1)} + -- CarCols[6] = {REN.GTA4ColorTable(37,1,1)} + -- CarCols[7] = {REN.GTA4ColorTable(53,6,1)} + -- CarCols[8] = {REN.GTA4ColorTable(61,1,1)} + -- CarCols[9] = {REN.GTA4ColorTable(74,0,83)} + -- CarCols[10] = {REN.GTA4ColorTable(92,1,1)} + -- CarCols[11] = {REN.GTA4ColorTable(6,0,12)} + -- CarCols[12] = {REN.GTA4ColorTable(10,0,12)} + -- CarCols[13] = {REN.GTA4ColorTable(21,0,12)} + -- CarCols[14] = {REN.GTA4ColorTable(23,0,12)} + -- CarCols[15] = {REN.GTA4ColorTable(25,0,12)} + -- CarCols[16] = {REN.GTA4ColorTable(33,0,35)} + -- CarCols[17] = {REN.GTA4ColorTable(34,0,34)} + -- CarCols[18] = {REN.GTA4ColorTable(47,0,35)} + -- CarCols[19] = {REN.GTA4ColorTable(49,0,63)} + -- CarCols[20] = {REN.GTA4ColorTable(52,0,56)} + -- CarCols[21] = {REN.GTA4ColorTable(54,0,55)} + -- CarCols[22] = {REN.GTA4ColorTable(65,0,63)} + -- CarCols[23] = {REN.GTA4ColorTable(67,0,118)} + -- CarCols[24] = {REN.GTA4ColorTable(70,0,65)} + -- CarCols[25] = {REN.GTA4ColorTable(98,0,90)} + -- CarCols[26] = {REN.GTA4ColorTable(16,0,76)} + -- CarCols[27] = {REN.GTA4ColorTable(9,0,91 )} + -- CarCols[28] = {REN.GTA4ColorTable(15,0,93)} + -- CarCols[29] = {REN.GTA4ColorTable(19,0,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/sultan_wheel.mdl', + + CustomWheelPosFL = Vector(56,30,-3), + CustomWheelPosFR = Vector(56,-30,-3), + CustomWheelPosRL = Vector(-56,30,-3), + CustomWheelPosRR = Vector(-56,-30,-3), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-15,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-15,-8), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,15,-8), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-15,-8), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-91,21,-5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 145.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-70,33,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 0.25, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/sultan_idle.wav', + + snd_low = 'octoteam/vehicles/sultan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/sultan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/sultan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/sultan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/sultan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/emperor_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.22, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sultan', V ) + +local V2 = {} +V2.Name = 'Korean Mob Sultan TT' +V2.Model = 'models/octoteam/vehicles/sultan.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,0) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Седаны' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('00111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(75,1,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(122,117,96), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sultan2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(76,29,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(76,-29,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-94,26,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-94,-26,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(76,29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(76,-29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(21,20,19.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(81,22,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(81,-22,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(21,19.2,19.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-94,26,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-94,-26,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-96,18,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-18,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-95,25,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-95,-25,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-93,27,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(21,15.5,22.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-93,-27,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(21,14.8,22.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [12] = '', + [4] = '', + [8] = '' + }, + Brake = { + [9] = '', + [12] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [4] = '', + [8] = '' + }, + Reverse = { + [9] = '', + [12] = '', + [4] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [8] = '' + }, + Brake_Reverse = { + [9] = '', + [12] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [4] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [8] = '' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = '', + [4] = '', + [8] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [4] = '', + [8] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = '', + [4] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [8] = '' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [4] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [8] = '' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = '', + [4] = '', + [8] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + Brake = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [4] = '', + [8] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + Reverse = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = '', + [4] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [8] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [12] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [4] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [8] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_sultan', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sultanrs.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sultanrs.lua new file mode 100644 index 0000000..143372a --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_sultanrs.lua @@ -0,0 +1,394 @@ +local V = { + Name = 'Sultan RS', + Model = 'models/octoteam/vehicles/sultanrs.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1600, + Trunk = { 35 }, + + Backfire = true, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_sultanrs', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(0,2,1,0)} + -- CarCols[2] = {REN.GTA4ColorTable2(0,0,1,0)} + -- CarCols[3] = {REN.GTA4ColorTable2(1,0,2,0)} + -- CarCols[4] = {REN.GTA4ColorTable2(5,0,1,5)} + -- CarCols[5] = {REN.GTA4ColorTable2(12,0,1,0)} + -- CarCols[6] = {REN.GTA4ColorTable2(21,74,112,21)} + -- CarCols[7] = {REN.GTA4ColorTable2(29,0,30,0)} + -- CarCols[8] = {REN.GTA4ColorTable2(33,112,34,33)} + -- CarCols[9] = {REN.GTA4ColorTable2(69,0,59,69)} + -- CarCols[10] = {REN.GTA4ColorTable2(69,90,56,69)} + -- CarCols[11] = {REN.GTA4ColorTable2(83,0,59,83)} + -- CarCols[12] = {REN.GTA4ColorTable2(85,2,88,85)} + -- CarCols[13] = {REN.GTA4ColorTable2(89,0,89,0)} + -- CarCols[14] = {REN.GTA4ColorTable2(102,0,101,0)} + -- CarCols[15] = {REN.GTA4ColorTable2(124,0,124,124)} + -- CarCols[16] = {REN.GTA4ColorTable2(126,0,126,0)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/sultanrs_wheel.mdl', + + CustomWheelPosFL = Vector(56,30,-3), + CustomWheelPosFR = Vector(56,-30,-3), + CustomWheelPosRL = Vector(-56,30,-3), + CustomWheelPosRR = Vector(-56,-30,-3), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(8,0,-1), + + CustomSteerAngle = 40, + + SeatOffset = Vector(-10,-15,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-15,-8), + ang = Angle(0,-90,15), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(14,33,-8.5), + ang = Angle(-90,-40,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 42000, + FrontDamping = 1200, + FrontRelativeDamping = 1200, + + RearHeight = 4, + RearConstant = 42000, + RearDamping = 1200, + RearRelativeDamping = 1200, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 7, + CounterSteeringMul = 0.85, + + MaxGrip = 66, + Efficiency = 1, + GripOffset = 4, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 8000, + PeakTorque = 78, + PowerbandStart = 1200, + PowerbandEnd = 6800, + Turbocharged = true, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-70,33,20), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + AirFriction = -150, + PowerBias = 0.8, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/sultan_idle.wav', + + snd_low = 'octoteam/vehicles/sultan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/sultan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/sultan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/sultan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/sultan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sultanrs_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.12,0.27,0.38,0.45,0.6}, + + Dash = { pos = Vector(20.354, 15.223, 19.3), ang = Angle(-0.0, -90.0, 69.9) }, + Radio = { pos = Vector(27.531, -1.430, 17.989), ang = Angle(-22.6, 180.0, -0.0) }, + Plates = { + Front = { pos = Vector(91.715, 0.001, -3.949), ang = Angle(10.8, 0.0, -0.0) }, + Back = { pos = Vector(-96.149, 0.007, 14.360), ang = Angle(-10.5, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(6.622, -0.033, 33.191), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(17.672, 33.804, 23.593), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(17.355, -33.862, 23.431), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_sultanrs', V ) + +local light_table = { + L_HeadLampPos = Vector(76,29,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(76,-29,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-94,26,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-94,-26,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(76,29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(76,-29,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(21,20,19.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(81,22,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(81,-22,8),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(21,19.2,19.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-94,26,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-94,-26,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-96,18,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-18,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-95,25,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-95,-25,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-93,27,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(21,15.5,22.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-93,-27,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(21,14.8,22.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [15] = '', + [5] = '', + [9] = '' + }, + Brake = { + [10] = '', + [15] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [5] = '', + [9] = '' + }, + Reverse = { + [10] = '', + [15] = '', + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [9] = '' + }, + Brake_Reverse = { + [10] = '', + [15] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [9] = '' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = '', + [5] = '', + [9] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [5] = '', + [9] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = '', + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [9] = '' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [9] = '' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = '', + [5] = '', + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + Brake = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [5] = '', + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + Reverse = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = '', + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [15] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [5] = 'models/gta4/vehicles/sultan/sultan_lights_on', + [9] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/sultan/sultan_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_sultanrs', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_supergt.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_supergt.lua new file mode 100644 index 0000000..6f24f04 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_supergt.lua @@ -0,0 +1,375 @@ +local V = { + Name = 'SuperGT', + Model = 'models/octoteam/vehicles/supergt.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_supergt', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,50)} + -- CarCols[2] = {REN.GTA4ColorTable(3,3,2)} + -- CarCols[3] = {REN.GTA4ColorTable(7,7,5)} + -- CarCols[4] = {REN.GTA4ColorTable(13,13,13)} + -- CarCols[5] = {REN.GTA4ColorTable(34,34,30)} + -- CarCols[6] = {REN.GTA4ColorTable(33,33,29)} + -- CarCols[7] = {REN.GTA4ColorTable(0,0,96)} + -- CarCols[8] = {REN.GTA4ColorTable(40,40,41)} + -- CarCols[9] = {REN.GTA4ColorTable(57,57,55)} + -- CarCols[10] = {REN.GTA4ColorTable(70,70,71)} + -- CarCols[11] = {REN.GTA4ColorTable(72,72,73)} + -- CarCols[12] = {REN.GTA4ColorTable(77,77,75)} + -- CarCols[13] = {REN.GTA4ColorTable(88,88,50)} + -- CarCols[14] = {REN.GTA4ColorTable(104,104,107)} + -- CarCols[15] = {REN.GTA4ColorTable(121,121,123)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/supergt_wheel.mdl', + + CustomWheelPosFL = Vector(58,33,-11), + CustomWheelPosFR = Vector(58,-33,-11), + CustomWheelPosRL = Vector(-58,33,-11), + CustomWheelPosRR = Vector(-58,-33,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-15,-18,13), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-3,-18,-18), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-94,17.2,-5.1), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-94,12.6,-5.1), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-94,-17.2,-5.1), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-94,-12.6,-5.1), + ang = Angle(-80,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 102, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 155.0, + PowerbandStart = 1500, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-66,-36,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/infernus_idle.wav', + + snd_low = 'octoteam/vehicles/infernus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/infernus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/infernus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/infernus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/infernus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.23, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1,1.25} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_supergt', V ) + +local light_table = { + L_HeadLampPos = Vector(75,33.5,4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(75,-33.5,4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-93,21.5,11), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-93,-21.5,11), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(75,33.5,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(75,-33.5,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + +--[[ { + pos = Vector(19.7,16.9,11.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(75,33.5,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(75,-33.5,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(19.7,16.1,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-93,21.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-93,-21.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-91,27.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-91,-27.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-93,21.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-93,-21.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(85,30.5,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-91,27.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(20,20,13), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(85,-30.5,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-91,-27.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(20,16,13), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [9] = '', + [11] = '' + }, + Brake = { + [10] = '', + [9] = 'models/gta4/vehicles/supergt/supergtlights_on', + [11] = '' + }, + Reverse = { + [10] = '', + [9] = '', + [11] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + Brake_Reverse = { + [10] = '', + [9] = 'models/gta4/vehicles/supergt/supergtlights_on', + [11] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = '', + [11] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = 'models/gta4/vehicles/supergt/supergtlights_on', + [11] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = '', + [11] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = 'models/gta4/vehicles/supergt/supergtlights_on', + [11] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = '', + [11] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = 'models/gta4/vehicles/supergt/supergtlights_on', + [11] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = '', + [11] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/supergt/supergtlights_on', + [9] = 'models/gta4/vehicles/supergt/supergtlights_on', + [11] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + right = { + [7] = 'models/gta4/vehicles/supergt/supergtlights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_supergt', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_taxi.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_taxi.lua new file mode 100644 index 0000000..16320c4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_taxi.lua @@ -0,0 +1,377 @@ +local V = { + Name = 'Taxi', + Model = 'models/octoteam/vehicles/taxi.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 1650, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_taxi', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/police_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(60,35,-13), + CustomWheelPosFR = Vector(60,-35,-13), + CustomWheelPosRL = Vector(-60,35,-13), + CustomWheelPosRR = Vector(-60,-35,-13), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-19.5,16), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-20,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-32,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-32,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-114.6,22.3,-14.3), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 6, + FrontConstant = 36000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 6, + RearConstant = 36000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4.5, + + MaxGrip = 56, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 46, + PowerbandStart = 1200, + PowerbandEnd = 6600, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.2, + + FuelFillPos = Vector(-82.4,-37,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + AirFriction = -75, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/stainer_idle.wav', + + snd_low = 'octoteam/vehicles/stainer_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stainer_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stainer_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stainer_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stainer_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/taxi2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(18.956, 19.036, 13.307), ang = Angle(-0.0, -90.0, 80.3) }, + Radio = { pos = Vector(26.232, -0.010, 8.225), ang = Angle(-19.8, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(104.138, -0.031, -7.778), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-114.153, 0.025, 3.693), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(10.393, 0.005, 26.879), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(19.971, 39.204, 17.188), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(21.423, -39.155, 17.184), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_taxi', V ) + +local light_table = { + L_HeadLampPos = Vector(98,25,2.3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(98,-25,2.3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-114,30,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-114,-30,5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(98,25,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-25,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(23,19,11.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(98,25,2.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-25,2.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,18,11.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-114,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-114,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-114,21.7,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-114,-21.7,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(93.8,34.6,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,20,11.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-114,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(93.8,-34.6,2.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,17,11.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-114,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [12] = '', + [10] = '', + [9] = '' + }, + Brake = { + [12] = '', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = '' + }, + Reverse = { + [12] = '', + [10] = '', + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [12] = '', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + on_lowbeam = { + Base = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = '' + }, + Brake = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = '' + }, + Reverse = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + on_highbeam = { + Base = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = '' + }, + Brake = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = '' + }, + Reverse = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + Brake_Reverse = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on', + [10] = 'models/gta4/vehicles/stainer/noose_lights_on', + [9] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + right = { + [12] = 'models/gta4/vehicles/stainer/noose_lights_on' + }, + }, + }, +} +list.Set('simfphys_lights', 'gta4_taxi', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_taxi2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_taxi2.lua new file mode 100644 index 0000000..32b7235 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_taxi2.lua @@ -0,0 +1,1262 @@ +local V = { + Name = 'Taxi', + Model = 'models/octoteam/vehicles/taxi2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 1450, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_taxi2', + + OnSpawn = function(ent) + -- ent:SetBodyGroups('0'..math.random(0,4)..math.random(0,1)..math.random(0,1)..math.random(0,1) ) + + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(89,89,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/taxi2_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(65,32,-16), + CustomWheelPosFR = Vector(65,-32,-16), + CustomWheelPosRL = Vector(-59,32,-16), + CustomWheelPosRR = Vector(-59,-32,-16), + CustomWheelAngleOffset = Angle(0,90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(0,-19.5,15), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-20,-18), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-27,20,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-20,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-104.4,21.3,-15.2), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 28000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 7, + RearConstant = 28000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 39, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-82.4,-34.3,9.8), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + AirFriction = -45, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = .95, + snd_idle = 'octoteam/vehicles/merit_idle.wav', + + snd_low = 'octoteam/vehicles/merit_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/merit_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/merit_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/merit_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/merit_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/taxi2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(30.752, 18.121, 11.628), ang = Angle(-0.0, -90.0, 70.6) }, + Radio = { pos = Vector(33.077, 1.293, 2.084), ang = Angle(-21.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(105.428, 13.770, -11.570), ang = Angle(9.1, 12.8, -0.0) }, + Back = { pos = Vector(-102.606, -0.002, 6.751), ang = Angle(-9.2, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(18.062, 0.002, 25.635), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(28.599, 40.558, 12.955), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.430, -40.797, 12.255), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_taxi2', V ) + +local light_table = { + L_HeadLampPos = Vector(95.5,29.2,-1.6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95.5,-29.2,-1.6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-101.7,28,5.9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-101.7,-28,5.9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(95.5,29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(95.5,-29.2,-1.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(30.6,24.7,10.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(97.7,23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(97.7,-23.7,-2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(30.6,24.7,9.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(98.7,29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(98.7,-29.4,-13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-101.7,28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101.7,-28,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-102.7,20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-102.7,-20.7,5.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-102.2,21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-102.2,-21.2,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + { + pos = Vector(100.7, 15.2, 0.0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(100.7, -15.2, 0.0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 35, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.034 + }, + { + pos = Vector(-9.9, -10.1, 34.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 45, + Colors = { + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-9.9, 10.1, 34.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 45, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(255, 145, 0), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(102.4, 11.1, -2.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(255, 145, 0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(102.4, -11.1, -2.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-101.9, 8.6, 6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-101.9, -8.6, 6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-12.2, 33.4, 13.0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.033 + }, + { + pos = Vector(-12.2, -33.4, 13.0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(29.6, -38.8, 9.0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(29.6, 38.8, 9.0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(15.9, 22.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(15.9, 18.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(15.9, 14.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(15.9, 10.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(15.9, -22.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(15.9, -18.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(15.9, -14.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(15.9, -10.7, 27.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-43.0, 22.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-43.0, 18.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-43.0, 14.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-43.0, 10.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + { + pos = Vector(-43.0, -22.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.035 + }, + { + pos = Vector(-43.0, -18.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.034 + }, + { + pos = Vector(-43.0, -14.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.033 + }, + { + pos = Vector(-43.0, -10.9, 28.2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + Colors = { + Color(0,0,255,150), + Color(0,0,0,0), + Color(0,0,255,150), + -- + Color(0,0,255,255), + Color(0,0,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,255,50), + Color(0,0,255,255), + }, + Speed = 0.032 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(92.4,33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,20.8,12.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(92.4,-33.4,-1.1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-100.8,-29.6,10.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(31.3,15.5,12.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [10] = '', + [9] = '', + [12] = '' + }, + Brake = { + [11] = '', + [10] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [12] = '' + }, + Reverse = { + [11] = '', + [10] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '' + }, + Brake_Reverse = { + [11] = '', + [10] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = '', + [9] = '', + [12] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [12] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = '' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = '', + [9] = '', + [12] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = '', + [12] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Reverse = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = '', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/merit/police2_lights_on', + [10] = 'models/gta4/vehicles/merit/police2_lights_on', + [9] = 'models/gta4/vehicles/merit/police2_lights_on', + [12] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/merit/police2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_taxi2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_trash.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_trash.lua new file mode 100644 index 0000000..3988572 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_trash.lua @@ -0,0 +1,331 @@ +local V = { + Name = 'Trashmaster', + Model = 'models/octoteam/vehicles/trash.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 6500.0, + + EnginePos = Vector(150,0,30), + + LightsTable = 'gta4_trash', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,113,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/trash_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/trash_wheel_r.mdl', + + CustomWheelPosFL = Vector(110,44,-7), + CustomWheelPosFR = Vector(110,-44,-7), + CustomWheelPosML = Vector(-51,40,-7), + CustomWheelPosMR = Vector(-51,-40,-7), + CustomWheelPosRL = Vector(-111,40,-7), + CustomWheelPosRR = Vector(-111,-40,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 40, + + SeatOffset = Vector(128,-29,70), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(140,-30,15), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(112,-53,112), + ang = Angle(0,0,0), + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 20, + FrontConstant = 50000, + FrontDamping = 2000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 50000, + RearDamping = 2000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 85, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4500, + PeakTorque = 100.0, + PowerbandStart = 1700, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(34,41,18), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 150, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/phantom_idle.wav', + + snd_low = 'octoteam/vehicles/phantom_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/phantom_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/phantom_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/phantom_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/phantom_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'TRUCK_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.11, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1,1.25} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_trash', V ) + +local light_table = { + L_HeadLampPos = Vector(179,40,19), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(179,-40,19), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-152,34.3,98.6), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-152,-34.3,98.6), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(179,40,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(179,-40,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(159.4,39,39.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(179,32,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(179,-32,19),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(159.4,38,39.3), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-152,41.3,98.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-152,34.3,98.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-152,27.3,98.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-152,-41.3,98.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-152,-34.3,98.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + { + pos = Vector(-152,-27.3,98.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-208,31,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-208,-31,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(179,46.5,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + { + pos = Vector(-208,41,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(162,33.5,42.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(179,-46.5,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + { + pos = Vector(-208,-41,-12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(162,27.3,42.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [6] = '', + [5] = '', + }, + Brake = { + [7] = '', + [6] = '', + [5] = 'models/gta4/vehicles/trash/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/trash/detail2_on', + [6] = '', + [5] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/trash/detail2_on', + [6] = '', + [5] = 'models/gta4/vehicles/trash/detail2_on', + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/trash/detail2_on', + [6] = 'models/gta4/vehicles/trash/detail2_on', + [5] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/trash/detail2_on', + [6] = 'models/gta4/vehicles/trash/detail2_on', + [5] = 'models/gta4/vehicles/trash/detail2_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/trash/detail2_on' + }, + right = { + [9] = 'models/gta4/vehicles/trash/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_trash', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_turismo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_turismo.lua new file mode 100644 index 0000000..a9cebfc --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_turismo.lua @@ -0,0 +1,365 @@ +local V = { + Name = 'Turismo', + Model = 'models/octoteam/vehicles/turismo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1500.0, + + EnginePos = Vector(-40,0,0), + + LightsTable = 'gta4_turismo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[2] = {REN.GTA4ColorTable(35,35,127)} + -- CarCols[3] = {REN.GTA4ColorTable(1,1,3)} + -- CarCols[4] = {REN.GTA4ColorTable(17,17,34)} + -- CarCols[5] = {REN.GTA4ColorTable(21,21,21)} + -- CarCols[6] = {REN.GTA4ColorTable(31,31,33)} + -- CarCols[7] = {REN.GTA4ColorTable(38,38,30)} + -- CarCols[8] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[9] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[10] = {REN.GTA4ColorTable(72,72,63)} + -- CarCols[11] = {REN.GTA4ColorTable(81,81,63)} + -- CarCols[12] = {REN.GTA4ColorTable(89,89,89)} + -- CarCols[13] = {REN.GTA4ColorTable(95,95,90)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/turismo_wheel.mdl', + + CustomWheelPosFL = Vector(54,33,-9), + CustomWheelPosFR = Vector(54,-33,-9), + CustomWheelPosRL = Vector(-54,33,-9), + CustomWheelPosRR = Vector(-54,-33,-9), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-7,-18,13), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-18,-18), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-87,1.5,-8.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-87,-1.5,-8.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 102, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 34, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 160.0, + PowerbandStart = 1500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-66,-36,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/turismo_idle.wav', + + snd_low = 'octoteam/vehicles/turismo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/turismo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/turismo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/turismo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/turismo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.23, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_turismo', V ) + +local light_table = { + L_HeadLampPos = Vector(78,32,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(78,-32,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-80,29,10), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-80,-29,10), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(78,32,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(78,-32,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + +--[[ { + pos = Vector(28.7,17.9,9.9), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(79,25,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(79,-25,4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(28.7,18.9,9.9), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-80,29,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-80,-29,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-82,19,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-82,-19,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-83,15,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-83,-15,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-81,25,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.25,20.6,11.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-81,-25,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(29.25,16,11.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [6] = '', + [11] = '', + [10] = '' + }, + Brake = { + [5] = '', + [6] = '', + [11] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [10] = '' + }, + Reverse = { + [5] = '', + [6] = '', + [11] = '', + [10] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + Brake_Reverse = { + [5] = '', + [6] = '', + [11] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [10] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = '', + [11] = '', + [10] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = '', + [11] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [10] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = '', + [11] = '', + [10] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = '', + [11] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [10] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [11] = '', + [10] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [11] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [10] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [11] = '', + [10] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [6] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [11] = 'models/gta4/vehicles/turismo/turismo_lights_on', + [10] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + right = { + [13] = 'models/gta4/vehicles/turismo/turismo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_turismo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_uranus.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_uranus.lua new file mode 100644 index 0000000..e45e0fe --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_uranus.lua @@ -0,0 +1,418 @@ +local V = { + Name = 'Uranus', + Model = 'models/octoteam/vehicles/uranus.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Хетчбеки', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Хетчбеки', + + Members = { + Mass = 1300.0, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_uranus', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(13,1,13)} + -- CarCols[2] = {REN.GTA4ColorTable(0,133,0)} + -- CarCols[3] = {REN.GTA4ColorTable(72,133,1)} + -- CarCols[4] = {REN.GTA4ColorTable(54,133,1)} + -- CarCols[5] = {REN.GTA4ColorTable(55,133,1)} + -- CarCols[6] = {REN.GTA4ColorTable(58,133,1)} + -- CarCols[7] = {REN.GTA4ColorTable(65,133,1)} + -- CarCols[8] = {REN.GTA4ColorTable(72,133,1)} + -- CarCols[9] = {REN.GTA4ColorTable(75,133,1)} + -- CarCols[10] = {REN.GTA4ColorTable(84,133,1)} + -- CarCols[11] = {REN.GTA4ColorTable(90,112,1)} + -- CarCols[12] = {REN.GTA4ColorTable(119,112,1)} + -- CarCols[13] = {REN.GTA4ColorTable(2,133,63)} + -- CarCols[14] = {REN.GTA4ColorTable(21,133,72)} + -- CarCols[15] = {REN.GTA4ColorTable(22,133,72)} + -- CarCols[16] = {REN.GTA4ColorTable(13,133,91)} + -- CarCols[17] = {REN.GTA4ColorTable(19,133,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/uranus_wheel.mdl', + + CustomWheelPosFL = Vector(56,28,-5), + CustomWheelPosFR = Vector(56,-28,-5), + CustomWheelPosRL = Vector(-56,28,-5), + CustomWheelPosRR = Vector(-56,-28,-5), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0.02,-2.4), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-16,-14,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-5,-14,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-92,-20,-8), + ang = Angle(-90,0,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-92,19.5,-8), + ang = Angle(-90,0,0), + OnBodyGroups = { + [1] = {1}, + } + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 23, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6500, + PeakTorque = 130.0, + PowerbandStart = 1700, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-74,31,13), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 65, + + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/esperanto_idle.wav', + + snd_low = 'octoteam/vehicles/esperanto_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/esperanto_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/esperanto_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/esperanto_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/esperanto_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/uranus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.5,0,0.15,0.35,0.5,0.75} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_uranus', V ) + +local V2 = {} +V2.Name = 'Russian Mafia Uranus' +V2.Model = 'models/octoteam/vehicles/uranus.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Хетчбеки' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01111' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(13,1,13)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(75,75,75), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_uranus2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(87,20,6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(87,-20,6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-93,21,8), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-93,-21,8), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(87,20,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(87,-20,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(14.5,15.5,16), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(87,20,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(87,-20,6),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(14.5,13.7,16), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(91,23.5,0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(91,-23.5,0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-93,21,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-93,-21,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-93,13,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-93,-13,8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 90, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-93,18,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-93,-18,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(90,28.5,0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-93,24,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(16,16.5,19.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(90,-28.5,0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-93,-24,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(16,13,19.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [7] = '', + [11] = '', + }, + Brake = { + [10] = '', + [7] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [11] = '', + }, + Reverse = { + [10] = '', + [7] = '', + [11] = 'models/gta4/vehicles/uranus/uranus_lights_on', + }, + Brake_Reverse = { + [10] = '', + [7] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [11] = 'models/gta4/vehicles/uranus/uranus_lights_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = '', + [11] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [11] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = '', + [11] = 'models/gta4/vehicles/uranus/uranus_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [11] = 'models/gta4/vehicles/uranus/uranus_lights_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = '', + [11] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [11] = '', + }, + Reverse = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = '', + [11] = 'models/gta4/vehicles/uranus/uranus_lights_on', + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [7] = 'models/gta4/vehicles/uranus/uranus_lights_on', + [11] = 'models/gta4/vehicles/uranus/uranus_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/uranus/uranus_lights_on' + }, + right = { + [3] = 'models/gta4/vehicles/uranus/uranus_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_uranus', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vigero.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vigero.lua new file mode 100644 index 0000000..918b5a4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vigero.lua @@ -0,0 +1,386 @@ +local V = { + Name = 'Vigero', + Model = 'models/octoteam/vehicles/vigero.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1450, + Trunk = { 30 }, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_vigero', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(9,9,1)} + -- CarCols[2] = {REN.GTA4ColorTable(13,13,1)} + -- CarCols[3] = {REN.GTA4ColorTable(17,17,1)} + -- CarCols[4] = {REN.GTA4ColorTable(25,25,1)} + -- CarCols[5] = {REN.GTA4ColorTable(30,30,1)} + -- CarCols[6] = {REN.GTA4ColorTable(37,37,1)} + -- CarCols[7] = {REN.GTA4ColorTable(40,40,1)} + -- CarCols[8] = {REN.GTA4ColorTable(54,54,1)} + -- CarCols[9] = {REN.GTA4ColorTable(57,57,1)} + -- CarCols[10] = {REN.GTA4ColorTable(65,65,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/vigero_wheel.mdl', + + CustomWheelPosFL = Vector(60,32,-16), + CustomWheelPosFR = Vector(60,-32,-16), + CustomWheelPosRL = Vector(-60,32,-16), + CustomWheelPosRR = Vector(-60,-32,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-18,-17,12), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-5,-17,-20), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-96,15,-17), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-96,-15,-17), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 26000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 8, + RearConstant = 26000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 4, + + MaxGrip = 35, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 28, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 7000, + PeakTorque = 44, + PowerbandStart = 1200, + PowerbandEnd = 6800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-68,37,3), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + AirFriction = -50, + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/vigero_idle.wav', + + snd_low = 'octoteam/vehicles/vigero_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/vigero_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/vigero_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/vigero_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/vigero_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/vigero_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.35, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(13.282, 16.775, 4.307), ang = Angle(0.0, -90.0, 68.9) }, + Radio = { pos = Vector(18.391, 0.957, 1.129), ang = Angle(-17.8, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(89.608, -0.016, -15.341), ang = Angle(6.3, 0.0, -0.0) }, + Back = { pos = Vector(-98.307, 0.000, -9.121), ang = Angle(-0.8, 180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(7.009, 0.477, 19.400), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(46.534, 31.608, 11.712), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(46.149, -32.543, 12.628), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_vigero', V ) + +local light_table = { + L_HeadLampPos = Vector(90,28,-4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(90,-28,-4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-99,26,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-99,-26,4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(90,28,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(90,-28,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(13.8,16.8,5.4), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(90,20,-4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-20,-4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(13.8,16.2,5.4), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-99,26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-99,26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-99,27,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-99,-27,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-99,32,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(13.8,17.5,5.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-99,-32,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(13.8,15.5,5.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [12] = '', + [11] = '', + [8] = '', + [4] = '', + }, + Brake = { + [12] = '', + [11] = '', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Reverse = { + [12] = '', + [11] = '', + [8] = '', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + Brake_Reverse = { + [12] = '', + [11] = '', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + }, + on_lowbeam = { + Base = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Brake = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Reverse = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + Brake_Reverse = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = '', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + }, + on_highbeam = { + Base = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Brake = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Reverse = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + Brake_Reverse = { + [12] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [11] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/vigero/vigero_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_vigero', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vigero2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vigero2.lua new file mode 100644 index 0000000..eb6bd09 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vigero2.lua @@ -0,0 +1,335 @@ +local V = { + Name = 'Vigero (Beater)', + Model = 'models/octoteam/vehicles/vigero2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_vigero2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,1)) + -- ent:SetBodyGroups('0'..math.random(0,2) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(9,9,1)} + -- CarCols[2] = {REN.GTA4ColorTable(13,13,1)} + -- CarCols[3] = {REN.GTA4ColorTable(17,17,1)} + -- CarCols[4] = {REN.GTA4ColorTable(25,25,1)} + -- CarCols[5] = {REN.GTA4ColorTable(30,30,1)} + -- CarCols[6] = {REN.GTA4ColorTable(37,37,1)} + -- CarCols[7] = {REN.GTA4ColorTable(40,40,1)} + -- CarCols[8] = {REN.GTA4ColorTable(54,54,1)} + -- CarCols[9] = {REN.GTA4ColorTable(57,57,1)} + -- CarCols[10] = {REN.GTA4ColorTable(65,65,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + REN.GTA4BeaterInit(ent) + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Beater(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/vigero2_wheel.mdl', + + CustomWheelPosFL = Vector(60,32,-16), + CustomWheelPosFR = Vector(60,-32,-16), + CustomWheelPosRL = Vector(-60,32,-16), + CustomWheelPosRR = Vector(-60,-32,-16), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-18,-17,12), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-5,-17,-20), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-90.1,20.9,-19.2), + ang = Angle(-100,-20,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-94,-17.5,-18.8), + ang = Angle(-100,20,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-90.1,20.9,-19.2), + ang = Angle(-100,-20,0), + OnBodyGroups = { + [1] = {2}, + } + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 73, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6500, + PeakTorque = 130.0, + PowerbandStart = 1500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-68,37,3), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/vigero_idle.wav', + + snd_low = 'octoteam/vehicles/vigero_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/vigero_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/vigero_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/vigero_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/vigero_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/vigero2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_vigero2', V ) + +local light_table = { + L_HeadLampPos = Vector(90.4,27.5,-4.8), + L_HeadLampAng = Angle(25,-15,0), + R_HeadLampPos = Vector(90,-28,-4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-99,26,4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-99,-26,4), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(90.4,27.5,-4.8), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(90,-28,-4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(90,28,-4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-28,-4),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-99,26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-99,26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-99,-26,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-99,27,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-99,-27,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-99,32,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + }, + Right = { + { + pos = Vector(-99,-32,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [6] = '', + [4] = '', + }, + Brake = { + [9] = '', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Reverse = { + [9] = '', + [6] = '', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + Brake_Reverse = { + [9] = '', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Brake = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Reverse = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Brake = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = '', + }, + Reverse = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [6] = 'models/gta4/vehicles/vigero/vigero_lights_on', + [4] = 'models/gta4/vehicles/vigero/vigero_lights_on', + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/vigero/vigero_lights_on' + }, + right = { + [7] = 'models/gta4/vehicles/vigero/vigero_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_vigero2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vincent.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vincent.lua new file mode 100644 index 0000000..1228635 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_vincent.lua @@ -0,0 +1,422 @@ +local V = { + Name = 'Vincent', + Model = 'models/octoteam/vehicles/vincent.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1600, + Trunk = { 30 }, + + EnginePos = Vector(65,0,5), + + LightsTable = 'gta4_vincent', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(121,133,1)} + -- CarCols[2] = {REN.GTA4ColorTable(8,133,1)} + -- CarCols[3] = {REN.GTA4ColorTable(10,133,1)} + -- CarCols[4] = {REN.GTA4ColorTable(17,133,1)} + -- CarCols[5] = {REN.GTA4ColorTable(24,133,1)} + -- CarCols[6] = {REN.GTA4ColorTable(36,133,1)} + -- CarCols[7] = {REN.GTA4ColorTable(37,133,1)} + -- CarCols[8] = {REN.GTA4ColorTable(75,133,1)} + -- CarCols[9] = {REN.GTA4ColorTable(55,133,1)} + -- CarCols[10] = {REN.GTA4ColorTable(75,133,1)} + -- CarCols[11] = {REN.GTA4ColorTable(0,93,34)} + -- CarCols[12] = {REN.GTA4ColorTable(0,1,1)} + -- CarCols[13] = {REN.GTA4ColorTable(87,133,83)} + -- CarCols[14] = {REN.GTA4ColorTable(52,133,83)} + -- CarCols[15] = {REN.GTA4ColorTable(39,133,34)} + -- CarCols[16] = {REN.GTA4ColorTable(1,133,1)} + -- CarCols[17] = {REN.GTA4ColorTable(7,133,7)} + -- CarCols[18] = {REN.GTA4ColorTable(31,93,29)} + -- CarCols[19] = {REN.GTA4ColorTable(16,133,76)} + -- CarCols[20] = {REN.GTA4ColorTable(9,1,91)} + -- CarCols[21] = {REN.GTA4ColorTable(15,133,93)} + -- CarCols[22] = {REN.GTA4ColorTable(19,1,93)} + -- CarCols[23] = {REN.GTA4ColorTable(13,133,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/vincent_wheel.mdl', + + CustomWheelPosFL = Vector(55,29,-8), + CustomWheelPosFR = Vector(55,-29,-8), + CustomWheelPosRL = Vector(-55,29,-8), + CustomWheelPosRR = Vector(-55,-29,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 13.4, + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-14,18), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(5,-14,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-27,14,-13), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-27,-14,-13), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-97,19.5,-8.3), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 7, + FrontConstant = 32500, + FrontDamping = 900, + FrontRelativeDamping = 900, + + RearHeight = 7, + RearConstant = 32500, + RearDamping = 900, + RearRelativeDamping = 900, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4, + CounterSteeringMul = 0.7, + + MaxGrip = 56, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 32, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6200, + PeakTorque = 52, + PowerbandStart = 1200, + PowerbandEnd = 5800, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-76,-30,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 45, + + PowerBias = -0.25, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/feroci_idle.wav', + + snd_low = 'octoteam/vehicles/feroci_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/feroci_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/feroci_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/feroci_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/feroci_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.12,0.19,0.28,0.36,0.48}, + + Dash = { pos = Vector(23.525, 14.011, 14.204), ang = Angle(-0.0, -90.0, 76.4) }, + Radio = { pos = Vector(27.050, -0.207, 8.469), ang = Angle(-17.2, 163.0, -1.3) }, + Plates = { + Front = { pos = Vector(95.796, 0.022, -7.263), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-97.833, -0.004, 9.375), ang = Angle(-5.4, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(6.741, 0.007, 27.305), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(20.948, 32.583, 17.375), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(21.052, -33.590, 17.176), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_vincent', V ) + +local light_table = { + L_HeadLampPos = Vector(83,24,2.4), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(83,-24,2.4), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-97,23.5,9.4), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-97,-23.5,9.4), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(83,24,2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(83,-24,2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26,21,13.4), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(83,24,2.4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(83,-24,2.4),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(25,21,12.4), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(90,20.5,-8.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(90,-20.5,-8.6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-97,23.5,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-23.5,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-97,23.5,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-23.5,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,12.5,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-12.5,9.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(80,31,2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,21.5,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,17,13.4), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(80,-31,2.4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-97,-21.5,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,13,13.4), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [7] = '', + [11] = '', + }, + Brake = { + [6] = '', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = '', + }, + Reverse = { + [6] = '', + [7] = '', + [11] = 'models/gta4/vehicles/vincent/vincent_lights_on', + }, + Brake_Reverse = { + [6] = '', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = 'models/gta4/vehicles/vincent/vincent_lights_on', + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = 'models/gta4/vehicles/vincent/vincent_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = 'models/gta4/vehicles/vincent/vincent_lights_on', + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = 'models/gta4/vehicles/vincent/vincent_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [7] = 'models/gta4/vehicles/vincent/vincent_lights_on', + [11] = 'models/gta4/vehicles/vincent/vincent_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/vincent/vincent_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/vincent/vincent_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_vincent', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_virgo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_virgo.lua new file mode 100644 index 0000000..2a43e19 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_virgo.lua @@ -0,0 +1,359 @@ +local V = { + Name = 'Virgo', + Model = 'models/octoteam/vehicles/virgo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1600.0, + + EnginePos = Vector(60,0,20), + + LightsTable = 'gta4_virgo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(4,4,1)} + -- CarCols[2] = {REN.GTA4ColorTable(9,9,1)} + -- CarCols[3] = {REN.GTA4ColorTable(16,16,1)} + -- CarCols[4] = {REN.GTA4ColorTable(19,19,1)} + -- CarCols[5] = {REN.GTA4ColorTable(28,28,1)} + -- CarCols[6] = {REN.GTA4ColorTable(30,30,1)} + -- CarCols[7] = {REN.GTA4ColorTable(37,37,1)} + -- CarCols[8] = {REN.GTA4ColorTable(36,36,1)} + -- CarCols[9] = {REN.GTA4ColorTable(46,46,1)} + -- CarCols[10] = {REN.GTA4ColorTable(54,54,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/virgo_wheel.mdl', + + CustomWheelPosFL = Vector(56,32,1), + CustomWheelPosFR = Vector(56,-32,1), + CustomWheelPosRL = Vector(-61,32,1), + CustomWheelPosRR = Vector(-61,-32,1), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-20,-17,28), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-7,-17,0), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-108,-28,-1), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 22500, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 22500, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 72, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 125.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-78,-37,23), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 0.9, + snd_idle = 'octoteam/vehicles/merit_idle.wav', + + snd_low = 'octoteam/vehicles/merit_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/merit_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/merit_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/merit_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/merit_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/buccaneer_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.5,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_virgo', V ) + +local light_table = { + L_HeadLampPos = Vector(90,28,14), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(90,-28,14), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-108,21,16), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-108,-21,16), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(90,28,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(90,-28,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(90,19,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(90,-19,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(14,26,25), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(90,28,14),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-28,14),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,19,14),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(90,-19,14),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(14,26,24), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-108,21,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-21,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-108,8,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-108,-8,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95,35,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(14,18.2,25), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-108,21,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(95,-35,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(14,17.5,25), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-108,-21,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [10] = '', + [12] = '', + }, + Brake = { + [4] = '', + [10] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [12] = '', + }, + Reverse = { + [4] = '', + [10] = '', + [12] = 'models/gta4/vehicles/virgo/virgo_lights_on', + }, + Brake_Reverse = { + [4] = '', + [10] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [12] = 'models/gta4/vehicles/virgo/virgo_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = '', + [12] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [12] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = '', + [12] = 'models/gta4/vehicles/virgo/virgo_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [12] = 'models/gta4/vehicles/virgo/virgo_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = '', + [12] = '', + }, + Brake = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [12] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = '', + [12] = 'models/gta4/vehicles/virgo/virgo_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [10] = 'models/gta4/vehicles/virgo/virgo_lights_on', + [12] = 'models/gta4/vehicles/virgo/virgo_lights_on', + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/virgo/virgo_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/virgo/virgo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_virgo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_voodoo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_voodoo.lua new file mode 100644 index 0000000..5233f5b --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_voodoo.lua @@ -0,0 +1,422 @@ +local V = { + Name = 'Voodoo', + Model = 'models/octoteam/vehicles/voodoo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 2100.0, + + EnginePos = Vector(65,0,5), + + LightsTable = 'gta4_voodoo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('00'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(0,0,29,90)} + -- CarCols[2] = {REN.GTA4ColorTable2(113,74,113,113)} + -- CarCols[5] = {REN.GTA4ColorTable2(1,133,8,113)} + -- CarCols[4] = {REN.GTA4ColorTable2(5,3,113,113)} + -- CarCols[5] = {REN.GTA4ColorTable2(0,28,90,127)} + -- CarCols[6] = {REN.GTA4ColorTable2(31,24,90,113)} + -- CarCols[7] = {REN.GTA4ColorTable2(34,32,124,113)} + -- CarCols[8] = {REN.GTA4ColorTable2(9,59,113,113)} + -- CarCols[9] = {REN.GTA4ColorTable2(102,93,113,113)} + -- CarCols[10] = {REN.GTA4ColorTable2(109,114,113,113)} + -- CarCols[11] = {REN.GTA4ColorTable2(68,86,113,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/voodoo_wheel.mdl', + + CustomWheelPosFL = Vector(65,34,-10), + CustomWheelPosFR = Vector(65,-34,-10), + CustomWheelPosRL = Vector(-64,34,-10), + CustomWheelPosRR = Vector(-64,-34,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-19,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-19,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-104,40,-15), + ang = Angle(-110,-65,0), + OnBodyGroups = { + [1] = {0}, + } + }, + { + pos = Vector(-23,44,-16), + ang = Angle(-80,-80,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-23,43,-13), + ang = Angle(-80,-80,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-23,-44,-16), + ang = Angle(-80,80,0), + OnBodyGroups = { + [1] = {1}, + } + }, + { + pos = Vector(-23,-43,-13), + ang = Angle(-80,80,0), + OnBodyGroups = { + [1] = {1}, + } + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 120.0, + PowerbandStart = 1700, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-62,41,9), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/patriot_idle.wav', + + snd_low = 'octoteam/vehicles/patriot_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/patriot_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/patriot_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/patriot_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/patriot_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/merit_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.11, + Gears = {-0.5,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_voodoo', V ) + +local V2 = {} +V2.Name = 'Jamaican Voodoo' +V2.Model = 'models/octoteam/vehicles/voodoo.mdl' +V2.Class = 'gmod_sent_vehicle_fphysics_base' +V2.Category = 'Доброград - Особые' +V2.SpawnOffset = Vector(0,0,10) +V2.SpawnAngleOffset = 90 +V2.NAKGame = 'Доброград' +V2.NAKType = 'Маслкары' + +local V2Members = {} +for k,v in pairs(V.Members) do + V2Members[k] = v +end +V2.Members = V2Members +V2.Members.OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('011' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(0,59,113,127)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 +end +V2.Members.ModelInfo = { + WheelColor = Color(215,142,16), +}, +list.Set('simfphys_vehicles', 'sim_fphys_gta4_voodoo2', V2 ) + +local light_table = { + L_HeadLampPos = Vector(95,34,6), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(95,-34,6), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-120,27,3), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-120,-27,3), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(95,34,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(95,-34,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,26,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,-26,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(25,29,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(95,34,6),size = 40,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(95,-34,6),size = 40,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,26,6),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,-26,6),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(25,28,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-120,27,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-120,-27,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-120,17,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-120,-17,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-118,16,-9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-118,-16,-9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-120,36,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(25,23,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-120,-36,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(25,17,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [4] = '', + [13] = '', + }, + Brake = { + [3] = '', + [4] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [13] = '', + }, + Reverse = { + [3] = '', + [4] = '', + [13] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + }, + Brake_Reverse = { + [3] = '', + [4] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [13] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = '', + [13] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [13] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = '', + [13] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [13] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = '', + [13] = '', + }, + Brake = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [13] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = '', + [13] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + }, + Brake_Reverse = { + [3] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [4] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + [13] = 'models/gta4/vehicles/voodoo/voodoo_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/voodoo/voodoo_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/voodoo/voodoo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_voodoo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_washington.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_washington.lua new file mode 100644 index 0000000..0c059e0 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_washington.lua @@ -0,0 +1,388 @@ +local V = { + Name = 'Washington', + Model = 'models/octoteam/vehicles/washington.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1750.0, + + EnginePos = Vector(70,0,5), + + LightsTable = 'gta4_washington', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,12)} + -- CarCols[2] = {REN.GTA4ColorTable(4,4,12)} + -- CarCols[3] = {REN.GTA4ColorTable(6,6,12)} + -- CarCols[4] = {REN.GTA4ColorTable(10,10,12)} + -- CarCols[5] = {REN.GTA4ColorTable(21,21,12)} + -- CarCols[6] = {REN.GTA4ColorTable(23,23,12)} + -- CarCols[7] = {REN.GTA4ColorTable(25,25,12)} + -- CarCols[8] = {REN.GTA4ColorTable(33,33,35)} + -- CarCols[9] = {REN.GTA4ColorTable(37,37,32)} + -- CarCols[10] = {REN.GTA4ColorTable(49,49,63)} + -- CarCols[11] = {REN.GTA4ColorTable(52,52,56)} + -- CarCols[12] = {REN.GTA4ColorTable(54,54,55)} + -- CarCols[13] = {REN.GTA4ColorTable(65,65,63)} + -- CarCols[14] = {REN.GTA4ColorTable(67,67,118)} + -- CarCols[15] = {REN.GTA4ColorTable(70,70,65)} + -- CarCols[16] = {REN.GTA4ColorTable(98,98,90)} + -- CarCols[17] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[18] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[19] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[20] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[21] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/washington_wheel.mdl', + + CustomWheelPosFL = Vector(68,33,-11), + CustomWheelPosFR = Vector(68,-33,-11), + CustomWheelPosRL = Vector(-68,33,-11), + CustomWheelPosRR = Vector(-68,-33,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 33, + + SeatOffset = Vector(-14,-19.5,22), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-20,-10), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-50,20,-10), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-50,-20,-10), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-118,25,-11), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-118,21.5,-11), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-118,-21.5,-11), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-118,-25,-11), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 73, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 22, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 135.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-81,39,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/admiral_idle.wav', + + snd_low = 'octoteam/vehicles/admiral_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/admiral_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/admiral_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/admiral_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/admiral_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.13, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_washington', V ) + +local light_table = { + L_HeadLampPos = Vector(100,26,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(100,-26,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-113,34,5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-113,-34,5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(100,26,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(100,-26,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(23,25,18), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(100,26,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(100,-26,7),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(23,16,18), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-113,34,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-113,-34,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-111,34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-111,-34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-114,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-114,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(100,28,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-112,30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,24,18), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(100,-28,4), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-112,-30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(23,17,18), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [4] = '', + [12] = '' + }, + Brake = { + [10] = '', + [4] = 'models/gta4/vehicles/washington/washington_lights_on', + [12] = '' + }, + Reverse = { + [10] = '', + [4] = '', + [12] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + Brake_Reverse = { + [10] = '', + [4] = 'models/gta4/vehicles/washington/washington_lights_on', + [12] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = '', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = 'models/gta4/vehicles/washington/washington_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = '', + [12] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = 'models/gta4/vehicles/washington/washington_lights_on', + [12] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = '', + [12] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = 'models/gta4/vehicles/washington/washington_lights_on', + [12] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = '', + [12] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/washington/washington_lights_on', + [4] = 'models/gta4/vehicles/washington/washington_lights_on', + [12] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/washington/washington_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_washington', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_willard.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_willard.lua new file mode 100644 index 0000000..24aa6db --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_willard.lua @@ -0,0 +1,391 @@ +local V = { + Name = 'Willard', + Model = 'models/octoteam/vehicles/willard.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1600.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_willard', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(72,72,1)} + -- CarCols[2] = {REN.GTA4ColorTable(30,30,1)} + -- CarCols[3] = {REN.GTA4ColorTable(34,34,1)} + -- CarCols[4] = {REN.GTA4ColorTable(37,37,1)} + -- CarCols[5] = {REN.GTA4ColorTable(43,43,1)} + -- CarCols[6] = {REN.GTA4ColorTable(52,52,1)} + -- CarCols[7] = {REN.GTA4ColorTable(54,54,1)} + -- CarCols[8] = {REN.GTA4ColorTable(55,55,1)} + -- CarCols[9] = {REN.GTA4ColorTable(52,52,1)} + -- CarCols[10] = {REN.GTA4ColorTable(65,65,1)} + -- CarCols[11] = {REN.GTA4ColorTable(69,69,1)} + -- CarCols[12] = {REN.GTA4ColorTable(70,70,1)} + -- CarCols[13] = {REN.GTA4ColorTable(72,72,1)} + -- CarCols[14] = {REN.GTA4ColorTable(75,75,1)} + -- CarCols[15] = {REN.GTA4ColorTable(84,84,1)} + -- CarCols[16] = {REN.GTA4ColorTable(88,88,1)} + -- CarCols[17] = {REN.GTA4ColorTable(90,90,1)} + -- CarCols[18] = {REN.GTA4ColorTable(106,106,1)} + -- CarCols[19] = {REN.GTA4ColorTable(119,119,1)} + -- CarCols[20] = {REN.GTA4ColorTable(111,111,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/willard_wheel.mdl', + + CustomWheelPosFL = Vector(61,29,-4), + CustomWheelPosFR = Vector(61,-29,-4), + CustomWheelPosRL = Vector(-61,29,-4), + CustomWheelPosRR = Vector(-61,-29,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-11,-17,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(4,-17,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-34,17,-5), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-34,-17,-5), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-99,18.5,-3.7), + ang = Angle(-130,0,0), + }, + { + pos = Vector(-99,-18.5,-3.7), + ang = Angle(-130,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 78, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 15, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 130.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-79,34,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 75, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/marbella_idle.wav', + + snd_low = 'octoteam/vehicles/marbella_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/marbella_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/marbella_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/marbella_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/marbella_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/willard_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_willard', V ) + +local light_table = { + L_HeadLampPos = Vector(98,26,10.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(98,-26,10.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-100.5,18,13), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-100.5,-18,13), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(98,26,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-26,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,18,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(98,-18,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(22,11,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(98,26,10.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-26,10.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,18,10.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(98,-18,10.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(22,11,24), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-100.5,18,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-100.5,-18,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-101.5,20,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-101.5,-20,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-100.5,10,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-100.5,-10,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(104,26,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,255,255,150), + }, + { + pos = Vector(-100.5,28,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(23,20,26), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(104,-26,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,255,255,150), + }, + { + pos = Vector(-100.5,-28,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(23,11,26), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [3] = '', + [9] = '' + }, + Brake = { + [11] = '', + [3] = 'models/gta4/vehicles/willard/willard_lights_on', + [9] = '' + }, + Reverse = { + [11] = '', + [3] = '', + [9] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + Brake_Reverse = { + [11] = '', + [3] = 'models/gta4/vehicles/willard/willard_lights_on', + [9] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = '', + [9] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = 'models/gta4/vehicles/willard/willard_lights_on', + [9] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = '', + [9] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = 'models/gta4/vehicles/willard/willard_lights_on', + [9] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + }, + on_highbeam = { + Base = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = '', + [9] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = 'models/gta4/vehicles/willard/willard_lights_on', + [9] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = '', + [9] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/willard/willard_lights_on', + [3] = 'models/gta4/vehicles/willard/willard_lights_on', + [9] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + }, + turnsignals = { + left = { + [8] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/willard/willard_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_willard', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_yankee.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_yankee.lua new file mode 100644 index 0000000..2563b00 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_gta4_yankee.lua @@ -0,0 +1,308 @@ +local V = { + Name = 'Yankee', + Model = 'models/octoteam/vehicles/yankee.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 4000, + Trunk = { 600 }, + + EnginePos = Vector(110,0,20), + + LightsTable = 'gta4_yankee', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,11)) + -- ent:SetBodyGroups('0'..math.random(0,6) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(15,112,1)} + -- CarCols[2] = {REN.GTA4ColorTable(26,78,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/yankee_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/yankee_wheel_r.mdl', + + CustomWheelPosFL = Vector(110,45,-20), + CustomWheelPosFR = Vector(110,-45,-20), + CustomWheelPosRL = Vector(-111,46,-20), + CustomWheelPosRR = Vector(-111,-46,-20), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 21.5, + RearWheelRadius = 21.5, + + CustomMassCenter = Vector(0,0,15), + + CustomSteerAngle = 40, + + SeatOffset = Vector(30,-24,55), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(43,-23,15), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-74,16,-17), + ang = Angle(-120,-25,0), + }, + }, + + StrengthenSuspension = true, + + FrontHeight = 12, + FrontConstant = 25000, + FrontDamping = 3000, + FrontRelativeDamping = 3000, + + RearHeight = 12, + RearConstant = 25000, + RearDamping = 3000, + RearRelativeDamping = 3000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 70, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 70, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 2, + + FuelFillPos = Vector(-38,50,0), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/benson_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.3, + Gears = {-0.1,0,0.1,0.16,0.23,0.32,0.42}, + + CanAttachPackages = true, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_gta4_yankee', V ) + +local light_table = { + L_HeadLampPos = Vector(142,42,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(142,-42,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-193,28,-7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-193,-28,-7), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(142,42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(142,-42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(72,28,40), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(142,42,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(142,-42,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(72,27,40), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-193,28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-193,-28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-193,28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-193,-28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(136,43,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-193,29,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(73.5,30,42.5), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(136,-43,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-193,-29,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(73.5,20,42.5), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [9] = '', + }, + Brake = { + [4] = '', + [9] = 'models/gta4/vehicles/yankee/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [9] = 'models/gta4/vehicles/yankee/detail2_on', + }, + Brake = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [9] = 'models/gta4/vehicles/yankee/detail2_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [9] = 'models/gta4/vehicles/yankee/detail2_on', + }, + Brake = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [9] = 'models/gta4/vehicles/yankee/detail2_on', + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/yankee/detail2_on' + }, + right = { + [10] = 'models/gta4/vehicles/yankee/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_yankee', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_apc.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_apc.lua new file mode 100644 index 0000000..19629e1 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_apc.lua @@ -0,0 +1,318 @@ +local V = { + Name = 'APC', + Model = 'models/octoteam/vehicles/apc.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 6500.0, + + EnginePos = Vector(-80,0,40), + + LightsTable = 'gta4_apc', + + OnSpawn = function(ent) + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + MaxHealth = 5000, + IsArmored = true, + + CustomWheelModel = 'models/octoteam/vehicles/apc_wheel.mdl', + + CustomWheelPosFL = Vector(64,45,-5), + CustomWheelPosFR = Vector(64,-45,-5), + CustomWheelPosRL = Vector(-64,45,-5), + CustomWheelPosRR = Vector(-64,-45,-5), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,35), + + CustomSteerAngle = 35, + + SeatOffset = Vector(11,-15,40), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(11,-15,8), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-96,14,52), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-96,-14,52), + ang = Angle(-90,0,0), + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 18, + FrontConstant = 50000, + FrontDamping = 5000, + FrontRelativeDamping = 500, + + RearHeight = 18, + RearConstant = 50000, + RearDamping = 5000, + RearRelativeDamping = 500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 300, + + TurnSpeed = 3, + + MaxGrip = 160, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 60, + BulletProofTires = true, + + IdleRPM = 700, + LimitRPM = 4000, + PeakTorque = 80.0, + PowerbandStart = 1600, + PowerbandEnd = 3500, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-97,43,22), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 200, + + PowerBias = 0, + + EngineSoundPreset = 0, + + Sound_Idle = 'octoteam/vehicles/apc_idle.wav', + Sound_IdlePitch = 1, + + Sound_Mid = 'octoteam/vehicles/apc_low.wav', + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 40, + Sound_MidFadeOutRate = 0.3, + + Sound_High = 'octoteam/vehicles/apc_high.wav', + Sound_HighPitch = 1.3, + Sound_HighVolume = 2, + Sound_HighFadeInRPMpercent = 40, + Sound_HighFadeInRate = 0.3, + + Sound_Throttle = 'octoteam/vehicles/apc_throttle.wav', + Sound_ThrottlePitch = 1, + Sound_ThrottleVolume = 5, + + snd_horn = 'FIRETRUK_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.12, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_apc', V ) + +local light_table = { + L_HeadLampPos = Vector(115,33,34), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(115,-33,34), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-108,37,45), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-108,-37,45), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(115,33,34), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(115,-33,34), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(115,33,34),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(115,-33,34),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-108,37,45), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-37,45), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-108,37,45), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-108,-37,45), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + ems_sounds = {'common/null.wav'}, + ems_sprites = { + --rotary lights + { + pos = Vector(-37,29,58), + material = 'octoteam/sprites/lights/gta4_corona', + size = 100, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(255,135,0,20), + Color(255,135,0,60), + Color(255,135,0,100), + Color(255,135,0,140), + Color(255,135,0,180), + Color(255,135,0,220), + Color(255,135,0,255), + Color(255,135,0,180), + Color(255,135,0,100), + Color(255,135,0,20), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-37,29,58), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(255,135,0,20), + Color(255,135,0,60), + Color(255,135,0,100), + Color(255,135,0,140), + Color(255,135,0,180), + Color(255,135,0,220), + Color(255,135,0,255), + Color(255,135,0,180), + Color(255,135,0,100), + Color(255,135,0,20), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + }, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-108,41,41), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + }, + Right = { + { + pos = Vector(-108,-41,41), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/apc/detail2_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/apc/detail2_on', + }, + }, + turnsignals = { + left = { + [5] = 'models/gta4/vehicles/apc/detail2_on' + }, + right = { + [6] = 'models/gta4/vehicles/apc/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_apc', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_avan.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_avan.lua new file mode 100644 index 0000000..cf38bc8 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_avan.lua @@ -0,0 +1,292 @@ +local V = { + Name = 'Brickade', + Model = 'models/octoteam/vehicles/avan.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,30), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 6500.0, + + EnginePos = Vector(0,0,0), + + LightsTable = 'gta4_avan', + + OnSpawn = function(ent) + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + MaxHealth = 6000, + IsArmored = true, + + CustomWheelModel = 'models/octoteam/vehicles/avan_wheel.mdl', + + CustomWheelPosFL = Vector(73,45,-20), + CustomWheelPosFR = Vector(73,-45,-20), + CustomWheelPosRL = Vector(-73,45,-20), + CustomWheelPosRR = Vector(-73,-45,-20), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,35), + + CustomSteerAngle = 35, + + SeatOffset = Vector(81,-28,60), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(83,-28,28), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + + StrengthenedSuspension = true, + + FrontHeight = 18, + FrontConstant = 50000, + FrontDamping = 5000, + FrontRelativeDamping = 500, + + RearHeight = 18, + RearConstant = 50000, + RearDamping = 5000, + RearRelativeDamping = 500, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 400, + + TurnSpeed = 3, + + MaxGrip = 160, + Efficiency = 0.8, + GripOffset = 0, + BrakePower = 60, + BulletProofTires = true, + + IdleRPM = 700, + LimitRPM = 4000, + PeakTorque = 100.0, + PowerbandStart = 1600, + PowerbandEnd = 3500, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(20,55,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 200, + + PowerBias = 0, + + EngineSoundPreset = 0, + + Sound_Idle = 'octoteam/vehicles/avan_idle.wav', + Sound_IdlePitch = 0.95, + + Sound_Mid = 'octoteam/vehicles/avan_low.wav', + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 40, + Sound_MidFadeOutRate = 0.3, + + Sound_High = 'octoteam/vehicles/avan_high.wav', + Sound_HighPitch = 1.2, + Sound_HighVolume = 2, + Sound_HighFadeInRPMpercent = 40, + Sound_HighFadeInRate = 0.3, + + Sound_Throttle = 'octoteam/vehicles/avan_throttle.wav', + Sound_ThrottlePitch = 1, + Sound_ThrottleVolume = 5, + + snd_horn = 'octoteam/vehicles/horns/buffalo_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_avan', V ) + +local light_table = { + L_HeadLampPos = Vector(140,47,1), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(140,-47,1), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-130,42.5,64.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-130,-42.5,64.5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(140,47,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(140,-47,1), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(115,22.5,55.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(141,35,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(141,-35,1),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(115,21.5,55.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-130,42.5,64.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-130,-42.5,64.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-130,42.5,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-130,-42.5,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-130,42.5,19.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(115,33.5,55.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(-130,-42.5,19.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(115,23.5,55.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [8] = '', + }, + Brake = { + [10] = '', + [11] = '', + [8] = 'models/gta4/vehicles/avan/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/avan/detail2_on', + [11] = '', + [8] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/avan/detail2_on', + [11] = '', + [8] = 'models/gta4/vehicles/avan/detail2_on', + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/avan/detail2_on', + [11] = 'models/gta4/vehicles/avan/detail2_on', + [8] = '', + }, + Brake = { + [10] = 'models/gta4/vehicles/avan/detail2_on', + [11] = 'models/gta4/vehicles/avan/detail2_on', + [8] = 'models/gta4/vehicles/avan/detail2_on', + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/avan/detail2_on' + }, + right = { + [7] = 'models/gta4/vehicles/avan/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_avan', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_buffalo.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_buffalo.lua new file mode 100644 index 0000000..1ad64e0 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_buffalo.lua @@ -0,0 +1,432 @@ +local V = { + Name = 'Buffalo', + Model = 'models/octoteam/vehicles/buffalo.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1500, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + Backfire = true, + + LightsTable = 'gta4_buffalo', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,3)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,103)} + -- CarCols[3] = {REN.GTA4ColorTable(3,3,103)} + -- CarCols[4] = {REN.GTA4ColorTable(1,1,79)} + -- CarCols[5] = {REN.GTA4ColorTable(3,3,73)} + -- CarCols[6] = {REN.GTA4ColorTable(4,4,82)} + -- CarCols[7] = {REN.GTA4ColorTable(6,6,84)} + -- CarCols[8] = {REN.GTA4ColorTable(11,11,86)} + -- CarCols[9] = {REN.GTA4ColorTable(16,16,92)} + -- CarCols[10] = {REN.GTA4ColorTable(23,23,25)} + -- CarCols[11] = {REN.GTA4ColorTable(34,34,28)} + -- CarCols[12] = {REN.GTA4ColorTable(36,36,27)} + -- CarCols[13] = {REN.GTA4ColorTable(47,47,91)} + -- CarCols[14] = {REN.GTA4ColorTable(52,52,53)} + -- CarCols[15] = {REN.GTA4ColorTable(53,53,51)} + -- CarCols[16] = {REN.GTA4ColorTable(64,64,65)} + -- CarCols[17] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[18] = {REN.GTA4ColorTable(70,70,64)} + -- CarCols[19] = {REN.GTA4ColorTable(73,73,58)} + -- CarCols[20] = {REN.GTA4ColorTable(76,76,58)} + -- CarCols[21] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[22] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[23] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[24] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[25] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/buffalo_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(67,32,-12), + CustomWheelPosFR = Vector(67,-32,-12), + CustomWheelPosRL = Vector(-61,32,-12), + CustomWheelPosRR = Vector(-61,-32,-12), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-17,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-30,18,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-30,-18,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-102,22,-14), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-102,-22,-14), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 5.5, + FrontConstant = 33000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 5.5, + RearConstant = 33000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 20, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 5.5, + CounterSteeringMul = 0.85, + + MaxGrip = 62, + Efficiency = 1.1, + GripOffset = 0, + BrakePower = 42, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6500, + PeakTorque = 68, + PowerbandStart = 1200, + PowerbandEnd = 6200, + Turbocharged = true, + Supercharged = true, + DoNotStall = false, + PowerBoost = 1.3, + + FuelFillPos = Vector(-61,37,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/buffalo2_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo2_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo2_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo2_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo2_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo2_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sultanrs_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.45, + Gears = {-0.12,0,0.12,0.18,0.24,0.32,0.42}, + + Dash = { pos = Vector(22.469, 17.686, 13.903), ang = Angle(-0.0, -90.0, 77.0) }, + Radio = { pos = Vector(28.962, 0.010, 4.947), ang = Angle(-19.6, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(103.097, -0.016, -12.006), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-99.164, 0.008, 7.328), ang = Angle(-22.1, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(15.675, 0.030, 25.598), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(29.291, 36.604, 16.617), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.574, -37.075, 16.650), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_buffalo', V ) + +local light_table = { + L_HeadLampPos = Vector(91,29,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(91,-29,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-94,35,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-94,-35,9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(91,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26.5,17,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,29,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-29,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26.5,18,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,30,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-30,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-94,35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-94,-35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-96,30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,30,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-30,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95,31,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,31,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.5,19,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(95,-31,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,-31,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.5,16,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + [11] = '', + [5] = '' + }, + Brake = { + [9] = '', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [5] = '' + }, + Reverse = { + [9] = '', + [11] = '', + [5] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [9] = '', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [5] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [5] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [5] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [5] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [5] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [5] = '' + }, + Brake = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [5] = '' + }, + Reverse = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [5] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [5] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_buffalo', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_bullet.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_bullet.lua new file mode 100644 index 0000000..157981d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_bullet.lua @@ -0,0 +1,362 @@ +local V = { + Name = 'Bullet GT', + Model = 'models/octoteam/vehicles/bullet.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1500.0, + + Backfire = true, + + EnginePos = Vector(-46,0,10), + + LightsTable = 'gta4_bullet', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(34,1,28)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,2)} + -- CarCols[3] = {REN.GTA4ColorTable(76,0,75)} + -- CarCols[4] = {REN.GTA4ColorTable(132,0,133)} + -- CarCols[5] = {REN.GTA4ColorTable(120,120,133)} + -- CarCols[6] = {REN.GTA4ColorTable(20,20,4)} + -- CarCols[7] = {REN.GTA4ColorTable(0,0,84)} + -- CarCols[8] = {REN.GTA4ColorTable(74,74,128)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/bullet_wheel.mdl', + + CustomWheelPosFL = Vector(52,34,-4), + CustomWheelPosFR = Vector(52,-34,-4), + CustomWheelPosRL = Vector(-62,34,-4), + CustomWheelPosRR = Vector(-62,-34,-4), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 30, + + SeatOffset = Vector(-16,-20,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-5,-20,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-92,15,4.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-92,11.6,4.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-92,-15,4.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-92,-11.6,4.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 106, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 34, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 163.0, + PowerbandStart = 1500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-18,29,19), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 0.65, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/bullet_idle.wav', + + snd_low = 'octoteam/vehicles/bullet_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/bullet_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/bullet_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/bullet_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/bullet_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.235, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_bullet', V ) + +local light_table = { + L_HeadLampPos = Vector(78,25.5,9.3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(78,-25.5,9.3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-95,26.5,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-95,-26.5,14), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(78,25.5,9.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(78,-25.5,9.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(78,21.5,9.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(78,-21.5,9.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(18,20.7,15), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(78,25.5,9.3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(78,-25.5,9.3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(78,21.5,9.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(78,-21.5,9.3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(18.3,20,15.7), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-95,26.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-95,-26.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-95,19.3,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-95,-19.3,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-95,19.3,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-95,-19.3,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(78,28.5,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,26.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(18.5,22.15,17.1), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(78,-28.5,10.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,-26.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(18.5,17.5,17), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [11] = '', + [8] = '', + [12] = '' + }, + Brake = { + [11] = '', + [8] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [12] = '' + }, + Reverse = { + [11] = '', + [8] = '', + [12] = 'models/gta4/vehicles/bullet/bullet_lights_on' + }, + Brake_Reverse = { + [11] = '', + [8] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [12] = 'models/gta4/vehicles/bullet/bullet_lights_on' + }, + }, + on_lowbeam = { + Base = { + [11] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [8] = '', + [12] = '' + }, + Brake = { + [11] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [8] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [12] = '' + }, + Reverse = { + [11] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [8] = '', + [12] = 'models/gta4/vehicles/bullet/bullet_lights_on' + }, + Brake_Reverse = { + [11] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [8] = 'models/gta4/vehicles/bullet/bullet_lights_on', + [12] = 'models/gta4/vehicles/bullet/bullet_lights_on' + }, + }, + turnsignals = { + left = { + [13] = 'models/gta4/vehicles/bullet/bullet_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/bullet/bullet_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_bullet', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_caddy.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_caddy.lua new file mode 100644 index 0000000..fb50a78 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_caddy.lua @@ -0,0 +1,172 @@ +local V = { + Name = 'Caddy', + Model = 'models/octoteam/vehicles/caddy.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Сервис', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 1000.0, + + EnginePos = Vector(-20,0,10), + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(112,112,112)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4EngStop(ent, 0) + + ent.GearChanged = 0 + + ent.StartEngine = function(ent, bIgnoreSettings) + if not ent:EngineActive() then + if not bIgnoreSettings then + ent.CurrentGear = 2 + end + + if not ent.IsInWater then + ent.GTA4Ignition = CreateSound(ent, 'octoteam/vehicles/START_CLICK.wav' ) + ent.GTA4Ignition:Play() + + if !ent:CanStart() then return end + + ent.EngineRPM = ent:GetEngineData().IdleRPM + ent:SetEngineActive(true) + else + if ent:GetDoNotStall() then + ent.GTA4Ignition = CreateSound(ent, 'octoteam/vehicles/START_CLICK.wav' ) + ent.GTA4Ignition:Play() + + if !ent:CanStart() then return end + + ent.EngineRPM = ent:GetEngineData().IdleRPM + ent:SetEngineActive(true) + end + end + end + end + end, + + OnTick = function(ent) + REN.GTA4GearSounds(ent) + REN.GTA4Braking(ent) + + ent.WheelSND = CreateSound(ent, 'octoteam/vehicles/GOLF_KART_WHEEL_LOOP.wav' ) + ent.WheelSND:PlayEx(0,100) + + ent.WheelSND:ChangeVolume(math.Clamp(math.abs(ent.ForwardSpeed/200),0,0.6), 0 ) + ent.WheelSND:ChangePitch(math.Clamp(math.abs(ent.ForwardSpeed/5),25,100), 0 ) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + + ent.WheelSND:Stop() + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/caddy_wheel.mdl', + + CustomWheelPosFL = Vector(40,22,-3), + CustomWheelPosFR = Vector(40,-22,-3), + CustomWheelPosRL = Vector(-40,22,-3), + CustomWheelPosRR = Vector(-40,-22,-3), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 10, + RearWheelRadius = 10, + + CustomMassCenter = Vector(0,0,2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-25,-12,38), + SeatPitch = 15, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-10,-10,10), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + + FrontHeight = 10, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 350, + + TurnSpeed = 3, + + MaxGrip = 65, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 10, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 140.0, + PowerbandStart = 2000, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = true, + + FuelFillPos = Vector(-28,24,16), + FuelType = FUELTYPE_ELECTRIC, + FuelTankSize = 45, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = 'octoteam/vehicles/GOLF_KART_IDLE.wav', + Sound_IdlePitch = 0.9, + + Sound_Mid = 'octoteam/vehicles/GOLF_KART_REVS_OFF.wav', + Sound_MidPitch = 1, + Sound_MidVolume = 0.3, + Sound_MidFadeOutRPMpercent = 50, + Sound_MidFadeOutRate = 0.3, + + Sound_High = 'octoteam/vehicles/GOLF_KART_MAIN_LOOP.wav', + Sound_HighPitch = 1, + Sound_HighVolume = 0.5, + Sound_HighFadeInRPMpercent = 50, + Sound_HighFadeInRate = 0.3, + + Sound_Throttle = 'common/null.wav', + Sound_ThrottlePitch = 1, + Sound_ThrottleVolume = 1, + + snd_horn = 'octoteam/vehicles/horns/airtug_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.30, + Gears = {-0.3,0,0.3} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_caddy', V ) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_caddy2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_caddy2.lua new file mode 100644 index 0000000..00f88fe --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_caddy2.lua @@ -0,0 +1,175 @@ +local V = { + Name = 'Caged Caddy', + Model = 'models/octoteam/vehicles/caddy.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Особые', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Сервис', + + Members = { + Mass = 1000.0, + + EnginePos = Vector(-20,0,10), + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + ent:SetBodyGroups('01' ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(112,112,112)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4EngStop(ent, 0) + + ent.GearChanged = 0 + + ent.StartEngine = function(ent, bIgnoreSettings) + if not ent:EngineActive() then + if not bIgnoreSettings then + ent.CurrentGear = 2 + end + + if not ent.IsInWater then + ent.GTA4Ignition = CreateSound(ent, 'octoteam/vehicles/START_CLICK.wav' ) + ent.GTA4Ignition:Play() + + if !ent:CanStart() then return end + + ent.EngineRPM = ent:GetEngineData().IdleRPM + ent:SetEngineActive(true) + else + if ent:GetDoNotStall() then + ent.GTA4Ignition = CreateSound(ent, 'octoteam/vehicles/START_CLICK.wav' ) + ent.GTA4Ignition:Play() + + if !ent:CanStart() then return end + + ent.EngineRPM = ent:GetEngineData().IdleRPM + ent:SetEngineActive(true) + end + end + end + end + end, + + OnTick = function(ent) + REN.GTA4GearSounds(ent) + REN.GTA4Braking(ent) + + ent.WheelSND = CreateSound(ent, 'octoteam/vehicles/GOLF_KART_WHEEL_LOOP.wav' ) + ent.WheelSND:PlayEx(0,100) + + ent.WheelSND:ChangeVolume(math.Clamp(math.abs(ent.ForwardSpeed/200),0,0.6), 0 ) + ent.WheelSND:ChangePitch(math.Clamp(math.abs(ent.ForwardSpeed/5),25,100), 0 ) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + + ent.WheelSND:Stop() + end, + + IsArmored = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/caddy_wheel.mdl', + + CustomWheelPosFL = Vector(40,22,-3), + CustomWheelPosFR = Vector(40,-22,-3), + CustomWheelPosRL = Vector(-40,22,-3), + CustomWheelPosRR = Vector(-40,-22,-3), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 10, + RearWheelRadius = 10, + + CustomMassCenter = Vector(0,0,2), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-25,-12,38), + SeatPitch = 15, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-10,-10,10), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + + FrontHeight = 10, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 350, + + TurnSpeed = 3, + + MaxGrip = 65, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 10, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 140.0, + PowerbandStart = 2000, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = false, + DoNotStall = true, + + FuelFillPos = Vector(-28,24,16), + FuelType = FUELTYPE_ELECTRIC, + FuelTankSize = 45, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = 'octoteam/vehicles/GOLF_KART_IDLE.wav', + Sound_IdlePitch = 0.9, + + Sound_Mid = 'octoteam/vehicles/GOLF_KART_REVS_OFF.wav', + Sound_MidPitch = 1, + Sound_MidVolume = 0.3, + Sound_MidFadeOutRPMpercent = 50, + Sound_MidFadeOutRate = 0.3, + + Sound_High = 'octoteam/vehicles/GOLF_KART_MAIN_LOOP.wav', + Sound_HighPitch = 1, + Sound_HighVolume = 0.5, + Sound_HighFadeInRPMpercent = 50, + Sound_HighFadeInRate = 0.3, + + Sound_Throttle = 'common/null.wav', + Sound_ThrottlePitch = 1, + Sound_ThrottleVolume = 1, + + snd_horn = 'octoteam/vehicles/horns/airtug_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.30, + Gears = {-0.3,0,0.3} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_caddy2', V ) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_f620.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_f620.lua new file mode 100644 index 0000000..3868f3d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_f620.lua @@ -0,0 +1,390 @@ +local V = { + Name = 'F620', + Model = 'models/octoteam/vehicles/f620.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Спортивные', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Спортивные', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_f620', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(31,31,89)} + -- CarCols[2] = {REN.GTA4ColorTable(7,7,8)} + -- CarCols[3] = {REN.GTA4ColorTable(0,0,7)} + -- CarCols[4] = {REN.GTA4ColorTable(24,24,4)} + -- CarCols[5] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[6] = {REN.GTA4ColorTable(69,69,65)} + -- CarCols[7] = {REN.GTA4ColorTable(74,74,72)} + -- CarCols[8] = {REN.GTA4ColorTable(85,85,82)} + -- CarCols[9] = {REN.GTA4ColorTable(113,113,133)} + -- CarCols[10] = {REN.GTA4ColorTable(89,89,113)} + -- CarCols[11] = {REN.GTA4ColorTable(83,83,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/f620_wheel.mdl', + + CustomWheelPosFL = Vector(58,33,-11), + CustomWheelPosFR = Vector(58,-33,-11), + CustomWheelPosRL = Vector(-58,33,-11), + CustomWheelPosRR = Vector(-58,-33,-11), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-15,-18,13), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-3,-18,-18), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-93,16,-10), + ang = Angle(-100,0,0), + }, + { + pos = Vector(-93,-16,-10), + ang = Angle(-100,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 20000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 20000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 102, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 155.0, + PowerbandStart = 1500, + PowerbandEnd = 4000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-66,-36,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/infernus_idle.wav', + + snd_low = 'octoteam/vehicles/infernus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/infernus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/infernus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/infernus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/infernus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/infernus_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.23, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1,1.25} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_f620', V ) + +local light_table = { + L_HeadLampPos = Vector(77,34,3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(77,-34,3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-91,28,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-91,-28,9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(77,34,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(77,-34,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,100), + }, + { + pos = Vector(80,27.5,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,100), + }, + { + pos = Vector(80,-27.5,3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,100), + }, + +--[[ { + pos = Vector(19.7,16.9,11.3), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(77,34,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(77,-34,3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(80,27.5,3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(80,-27.5,3),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(19.7,16.1,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(89,25,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(89,-25,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-91,28,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-91,-28,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-93,18,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-93,-18,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-93,19,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-93,-19,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(86,24,0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-91,27,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(20,20,13), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(86,-24,0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-91,-27,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(20,16,13), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [8] = '', + [12] = '' + }, + Brake = { + [4] = '', + [8] = 'models/gta4/vehicles/f620/f620_on', + [12] = '' + }, + Reverse = { + [4] = '', + [8] = '', + [12] = 'models/gta4/vehicles/f620/f620_on' + }, + Brake_Reverse = { + [4] = '', + [8] = 'models/gta4/vehicles/f620/f620_on', + [12] = 'models/gta4/vehicles/f620/f620_on' + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = '', + [12] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = 'models/gta4/vehicles/f620/f620_on', + [12] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = '', + [12] = 'models/gta4/vehicles/f620/f620_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = 'models/gta4/vehicles/f620/f620_on', + [12] = 'models/gta4/vehicles/f620/f620_on' + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = '', + [12] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = 'models/gta4/vehicles/f620/f620_on', + [12] = '' + }, + Reverse = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = '', + [12] = 'models/gta4/vehicles/f620/f620_on' + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/f620/f620_on', + [8] = 'models/gta4/vehicles/f620/f620_on', + [12] = 'models/gta4/vehicles/f620/f620_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/f620/f620_on' + }, + right = { + [13] = 'models/gta4/vehicles/f620/f620_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_f620', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_limo2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_limo2.lua new file mode 100644 index 0000000..871a73f --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_limo2.lua @@ -0,0 +1,411 @@ +local V = { + Name = 'Stretch E', + Model = 'models/octoteam/vehicles/limo2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_limo2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + -- ent:SetBodyGroups('0'..math.random(0,1) ) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,8)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,12)} + -- CarCols[3] = {REN.GTA4ColorTable(113,113,113)} + -- CarCols[4] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[5] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[6] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[7] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[8] = {REN.GTA4ColorTable(13,13,80)} + -- CarCols[9] = {REN.GTA4ColorTable(0,0,1)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/limo2_wheel.mdl', + + CustomWheelPosFL = Vector(85,33,-10), + CustomWheelPosFR = Vector(85,-33,-10), + CustomWheelPosRL = Vector(-85,33,-10), + CustomWheelPosRR = Vector(-85,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(10,-19,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(20,-19,-8), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-62,19,-8), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-62,-19,-8), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-22,19,-8), + ang = Angle(0,90,15) + }, + { + pos = Vector(-22,-19,-8), + ang = Angle(0,90,15) + }, + }, + ExhaustPositions = { + { + pos = Vector(-132,19,-9.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-133,14,-9.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-132,-19,-9.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-133,-14,-9.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 25000, + FrontDamping = 1000, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 25000, + RearDamping = 1000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 83, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 22, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 140.0, + PowerbandStart = 2500, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-100,-33.5,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/limo2_idle.wav', + + snd_low = 'octoteam/vehicles/limo2_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/limo2_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/limo2_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/limo2_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/limo2_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_limo2', V ) + +local light_table = { + L_HeadLampPos = Vector(107,31,8.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(107,-31,8.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-126,30,14.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-126,-30,14.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(107,31,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(107,-31,8.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(45,19.5,21), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(111,26,7.2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(111,-26,7.2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(45,18,21), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(115,27,-7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(115,-27,-7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-126,30,14.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-126,-30,14.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-129,22,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-129,-22,13.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-130,18,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-130,-18,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(104,34,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-125,31,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(45,21.5,23), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(104,-34,9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-125,-31,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(45,16,23), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [6] = '', + [10] = '', + [8] = '', + [12] = '', + }, + Brake = { + [6] = '', + [10] = '', + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = '', + }, + Reverse = { + [6] = '', + [10] = '', + [8] = '', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [6] = '', + [10] = '', + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + on_lowbeam = { + Base = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [8] = '', + [12] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [8] = '', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + on_highbeam = { + Base = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [8] = '', + [12] = '', + }, + Brake = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = '', + }, + Reverse = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [8] = '', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [6] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/schafter2/limo2_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/schafter2/limo2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_limo2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_police3.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_police3.lua new file mode 100644 index 0000000..e3d44ba --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_police3.lua @@ -0,0 +1,726 @@ +local V = { + Name = 'Police Buffalo', + Model = 'models/octoteam/vehicles/police3.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 1500, + Trunk = { 30 }, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_police3', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(113,74,113)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/police3_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(67,32,-12), + CustomWheelPosFR = Vector(67,-32,-12), + CustomWheelPosRL = Vector(-61,32,-12), + CustomWheelPosRR = Vector(-61,-32,-12), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-5,-17,17), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(8,-17,-15), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-30,18,-15), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-30,-18,-15), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-102,22,-14), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-102,-22,-14), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 5.5, + FrontConstant = 33000, + FrontDamping = 1000, + FrontRelativeDamping = 1000, + + RearHeight = 5.5, + RearConstant = 33000, + RearDamping = 1000, + RearRelativeDamping = 1000, + + FastSteeringAngle = 20, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 5.5, + CounterSteeringMul = 0.85, + + MaxGrip = 62, + Efficiency = 1.1, + GripOffset = 0, + BrakePower = 42, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6500, + PeakTorque = 68, + PowerbandStart = 1200, + PowerbandEnd = 6200, + Turbocharged = true, + Supercharged = true, + DoNotStall = false, + PowerBoost = 1.3, + + FuelFillPos = Vector(-61,37,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 50, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1.05, + snd_idle = 'octoteam/vehicles/buffalo_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/police/warning.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.45, + Gears = {-0.12,0,0.12,0.18,0.24,0.32,0.42}, + + Dash = { pos = Vector(22.469, 17.686, 13.903), ang = Angle(-0.0, -90.0, 77.0) }, + Radio = { pos = Vector(28.962, 0.010, 4.947), ang = Angle(-19.6, -180.0, 0.0) }, + Plates = { + Front = { pos = Vector(103.097, -0.016, -12.006), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-99.164, 0.008, 7.328), ang = Angle(-22.1, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(15.675, 0.030, 25.598), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(29.291, 36.604, 16.617), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(28.574, -37.075, 16.650), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_police3', V ) + +local colOff = Color(0,0,0, 0) +local emsCenter = Vector(-4, 0, 35) + +local light_table = { + L_HeadLampPos = Vector(91,29,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(91,-29,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-94,35,9), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-94,-35,9), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(91,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26.5,17,11), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,29,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-29,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26.5,18,11), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,30,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-30,-10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-94,35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-94,-35,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-96,30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-96,-30,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,30,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-30,6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'octoteam/vehicles/police/siren1.wav','octoteam/vehicles/police/siren2.wav','octoteam/vehicles/police/siren3.wav'}, + ems_sprites = { + -- + -- FRONT + -- + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(255,0,0, 20), colOff, Color(255,0,0, 20), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 0, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 220, + Colors = { + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + Color(255,255,255, 20), colOff, Color(255,255,255, 20), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + }, + Speed = 0.07 + }, + + -- + -- REAR + -- + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + Color(255,0,0, 20), colOff, Color(255,0,0, 20), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,25,25, 255), colOff, Color(255,25,25, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, -3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 220, + Colors = { + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, + Color(255,255,255, 30), colOff, Color(255,255,255, 30), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 3, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 18, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 8.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 19.5, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-7.5, 14, 0), + material = 'sprites/light_ignorez', + size = 220, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 20), colOff, Color(0,0,255, 20), colOff, + }, + Speed = 0.07 + }, + + -- + -- SIDE + -- + { + pos = emsCenter + Vector(5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, -25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, -24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,0,0, 255), colOff, Color(255,0,0, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(0, 25, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = emsCenter + Vector(-5, 24, 0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(0,0,255, 255), colOff, Color(0,0,255, 255), colOff, + }, + Speed = 0.07 + }, + + -- + -- HEAD/TAIL LIGHTS + -- + { + pos = Vector(97, 30, -10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(97, -30, -10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-97, 30, 6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, colOff, colOff, colOff, colOff, + }, + Speed = 0.07 + }, + { + pos = Vector(-97, -30, 6), + material = 'octoteam/sprites/lights/gta4_corona', + size = 22, + Colors = { + colOff, colOff, colOff, colOff, Color(255,255,255, 255), colOff, Color(255,255,255, 255), colOff, + }, + Speed = 0.07 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(95,31,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,31,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.5,19,11), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(95,-31,-5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-95,-31,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26.5,16,11), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [10] = '', + [11] = '', + [13] = '' + }, + Brake = { + [10] = '', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [13] = '' + }, + Reverse = { + [10] = '', + [11] = '', + [13] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [10] = '', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [13] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [13] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [13] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [13] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [13] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + on_highbeam = { + Base = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [13] = '' + }, + Brake = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [13] = '' + }, + Reverse = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = '', + [13] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + Brake_Reverse = { + [10] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [11] = 'models/gta4/vehicles/buffalo/buffalo_lights_on', + [13] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/buffalo/buffalo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_police3', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_police4.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_police4.lua new file mode 100644 index 0000000..0ff7400 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_police4.lua @@ -0,0 +1,629 @@ +local V = { + Name = 'Police Stinger', + Model = 'models/octoteam/vehicles/police4.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 2200.0, + + EnginePos = Vector(70,0,0), + + Backfire = true, + + LightsTable = 'gta4_police4', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(69,69,35)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + REN.GTA4Bullhorn(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/police4_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(60,30,-14), + CustomWheelPosFR = Vector(60,-30,-14), + CustomWheelPosRL = Vector(-60,30,-14), + CustomWheelPosRR = Vector(-60,-30,-14), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 34, + + SeatOffset = Vector(-5,-18,18), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-17,-12), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-38,17,-12), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-38,-17,-12), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-100,21,-18), + ang = Angle(-80,0,0), + }, + { + pos = Vector(-100,-21,-18), + ang = Angle(-80,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 30000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 30000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 101, + Efficiency = 0.75, + GripOffset = 0, + BrakePower = 34, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 160.0, + PowerbandStart = 1700, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-73,-31,18), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 90, + + PowerBias = 0.65, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/buffalo2_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo2_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo2_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo2_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo2_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo2_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.25, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_police4', V ) + +local light_table = { + L_HeadLampPos = Vector(87,25,2), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(87,-25,2), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-97,28,10), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-97,-28,10), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(87,25,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(87,-25,2), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(90,25,-14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + { + pos = Vector(90,-25,-14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,50), + }, + +--[[ { + pos = Vector(29,25,13), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(87,25,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(87,-25,2),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(29,25,12), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(91,24,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(91,-24,-12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-97,28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-97,28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,-28,10), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-97,0,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-97,29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-97,-29,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + ems_sounds = {'GTA4_SIREN_WAIL','GTA4_SIREN_YELP','GTA4_SIREN_WARNING'}, + ems_sprites = { + { + pos = Vector(-13,-16,35.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(100,100,255,150), + Color(100,100,255,255), + Color(100,100,255,150), + -- + Color(100,100,255,100), + Color(100,100,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(100,100,255,50), + Color(100,100,255,100), + }, + Speed = 0.035 + }, + { + pos = Vector(-13,-5,35.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-13,5,35.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(100,100,255,50), + Color(100,100,255,100), + -- + Color(100,100,255,150), + Color(100,100,255,255), + Color(100,100,255,150), + -- + Color(100,100,255,100), + Color(100,100,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-13,16,35.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(93,15,-14.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(255,0,0,50), + Color(255,0,0,100), + -- + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(93,-15,-14.3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(100,100,255,50), + Color(100,100,255,100), + -- + Color(100,100,255,150), + Color(100,100,255,255), + Color(100,100,255,150), + -- + Color(100,100,255,100), + Color(100,100,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + { + pos = Vector(-65,20.5,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 80, + Colors = { + Color(255,0,0,150), + Color(255,0,0,255), + Color(255,0,0,150), + -- + Color(255,0,0,100), + Color(255,0,0,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(255,0,0,50), + Color(255,0,0,100), + }, + Speed = 0.035 + }, + { + pos = Vector(-65,-20.5,22), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + Colors = { + Color(0,0,0,0), + Color(100,100,255,50), + Color(100,100,255,100), + -- + Color(100,100,255,150), + Color(100,100,255,255), + Color(100,100,255,150), + -- + Color(100,100,255,100), + Color(100,100,255,50), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + Color(0,0,0,0), + -- + Color(0,0,0,0), + Color(0,0,0,0), + }, + Speed = 0.035 + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(83,29,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,50), + }, + { + pos = Vector(-95,28,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(30,21,15), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(83,-29,4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,50), + }, + { + pos = Vector(-95,-28,16), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + +--[[ { + pos = Vector(30,15,15), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [13] = '', + [6] = '', + [14] = '', + [11] = '', + }, + Brake = { + [13] = '', + [6] = 'models/gta4/vehicles/pres/cts_lights_on', + [14] = 'models/gta4/vehicles/pres/cts_lights_on', + [11] = '', + }, + Reverse = { + [13] = '', + [6] = '', + [14] = '', + [11] = 'models/gta4/vehicles/pres/cts_lights_on', + }, + Brake_Reverse = { + [13] = '', + [6] = 'models/gta4/vehicles/pres/cts_lights_on', + [14] = 'models/gta4/vehicles/pres/cts_lights_on', + [11] = 'models/gta4/vehicles/pres/cts_lights_on', + }, + }, + on_lowbeam = { + Base = { + [13] = 'models/gta4/vehicles/pres/cts_lights_on', + [6] = 'models/gta4/vehicles/pres/cts_lights_on', + [14] = '', + [11] = '', + }, + Brake = { + [13] = 'models/gta4/vehicles/pres/cts_lights_on', + [6] = 'models/gta4/vehicles/pres/cts_lights_on', + [14] = 'models/gta4/vehicles/pres/cts_lights_on', + [11] = '', + }, + Reverse = { + [13] = 'models/gta4/vehicles/pres/cts_lights_on', + [6] = 'models/gta4/vehicles/pres/cts_lights_on', + [14] = '', + [11] = 'models/gta4/vehicles/pres/cts_lights_on', + }, + Brake_Reverse = { + [13] = 'models/gta4/vehicles/pres/cts_lights_on', + [6] = 'models/gta4/vehicles/pres/cts_lights_on', + [14] = 'models/gta4/vehicles/pres/cts_lights_on', + [11] = 'models/gta4/vehicles/pres/cts_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/pres/cts_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/pres/cts_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_police4', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_schafter2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_schafter2.lua new file mode 100644 index 0000000..18cc222 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_schafter2.lua @@ -0,0 +1,404 @@ +local V = { + Name = 'Schafter 2nd Gen Custom', + Model = 'models/octoteam/vehicles/schafter2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_schafter2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[2] = {REN.GTA4ColorTable(21,1,24)} + -- CarCols[3] = {REN.GTA4ColorTable(4,0,24)} + -- CarCols[4] = {REN.GTA4ColorTable(24,0,24)} + -- CarCols[5] = {REN.GTA4ColorTable(24,24,24)} + -- CarCols[6] = {REN.GTA4ColorTable(3,3,24)} + -- CarCols[7] = {REN.GTA4ColorTable(7,7,24)} + -- CarCols[8] = {REN.GTA4ColorTable(0,0,29)} + -- CarCols[9] = {REN.GTA4ColorTable(30,30,29)} + -- CarCols[10] = {REN.GTA4ColorTable(34,34,29)} + -- CarCols[11] = {REN.GTA4ColorTable(49,49,63)} + -- CarCols[12] = {REN.GTA4ColorTable(52,52,63)} + -- CarCols[13] = {REN.GTA4ColorTable(69,69,59)} + -- CarCols[14] = {REN.GTA4ColorTable(74,74,77)} + -- CarCols[15] = {REN.GTA4ColorTable(77,77,77)} + -- CarCols[16] = {REN.GTA4ColorTable(85,85,83)} + -- CarCols[17] = {REN.GTA4ColorTable(89,0,89)} + -- CarCols[18] = {REN.GTA4ColorTable(97,97,97)} + -- CarCols[19] = {REN.GTA4ColorTable(32,0,131)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/schafter2_wheel.mdl', + + CustomWheelPosFL = Vector(65,33,-10), + CustomWheelPosFR = Vector(65,-33,-10), + CustomWheelPosRL = Vector(-65,33,-10), + CustomWheelPosRR = Vector(-65,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-19,21), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-19,-10), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-42,19,-10), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-42,-19,-10), + ang = Angle(0,-90,15) + }, + }, + ExhaustPositions = { + { + pos = Vector(-112,16.5,-13), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-112,-16.5,-13), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 93, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 27, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 141.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-80,-33.5,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/buffalo_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_schafter2', V ) + +local light_table = { + L_HeadLampPos = Vector(87,31,6.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(87,-31,6.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-106,30,12.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-106,-30,12.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(87,31,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(87,-31,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(25,19.5,19), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,26,5.2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-26,5.2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(25,18,19), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,27,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-27,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-106,30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-106,-30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-109,22,11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-22,11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-110,18,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-110,-18,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(84,34,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-105,31,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,21.5,21), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(84,-34,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-105,-31,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,16,21), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [13] = '', + [10] = '', + [5] = '', + }, + Brake = { + [8] = '', + [13] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [5] = '', + }, + Reverse = { + [8] = '', + [13] = '', + [10] = '', + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [8] = '', + [13] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = '', + [10] = '', + [5] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [5] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = '', + [10] = '', + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [5] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [5] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [13] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on' + }, + right = { + [4] = 'models/gta4/vehicles/schafter2/limo2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_schafter2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_schafter3.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_schafter3.lua new file mode 100644 index 0000000..9f6d6f1 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_schafter3.lua @@ -0,0 +1,412 @@ +local V = { + Name = 'Schafter 2nd Gen', + Model = 'models/octoteam/vehicles/schafter3.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(70,0,0), + + LightsTable = 'gta4_schafter3', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,1,1)} + -- CarCols[2] = {REN.GTA4ColorTable(70,133,8)} + -- CarCols[3] = {REN.GTA4ColorTable(16,133,8)} + -- CarCols[4] = {REN.GTA4ColorTable(22,133,8)} + -- CarCols[5] = {REN.GTA4ColorTable(26,133,18)} + -- CarCols[6] = {REN.GTA4ColorTable(34,133,34)} + -- CarCols[7] = {REN.GTA4ColorTable(43,133,43)} + -- CarCols[8] = {REN.GTA4ColorTable(54,133,54)} + -- CarCols[9] = {REN.GTA4ColorTable(57,133,57)} + -- CarCols[10] = {REN.GTA4ColorTable(61,133,61)} + -- CarCols[11] = {REN.GTA4ColorTable(65,133,65)} + -- CarCols[12] = {REN.GTA4ColorTable(68,133,68)} + -- CarCols[13] = {REN.GTA4ColorTable(77,133,77)} + -- CarCols[14] = {REN.GTA4ColorTable(104,133,103)} + -- CarCols[15] = {REN.GTA4ColorTable(106,133,103)} + -- CarCols[16] = {REN.GTA4ColorTable(108,133,108)} + -- CarCols[17] = {REN.GTA4ColorTable(15,133,93)} + -- CarCols[18] = {REN.GTA4ColorTable(19,1,93)} + -- CarCols[19] = {REN.GTA4ColorTable(13,133,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/schafter3_wheel.mdl', + + CustomWheelPosFL = Vector(65,33,-10), + CustomWheelPosFR = Vector(65,-33,-10), + CustomWheelPosRL = Vector(-65,33,-10), + CustomWheelPosRR = Vector(-65,-33,-10), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-19,21), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-19,-10), + ang = Angle(0,-90,15), + hasRadio = true + }, + { + pos = Vector(-42,19,-10), + ang = Angle(0,-90,15) + }, + { + pos = Vector(-42,-19,-10), + ang = Angle(0,-90,15) + }, + }, + ExhaustPositions = { + { + pos = Vector(-112,19,-11.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-113,14,-11.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-112,-19,-11.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-113,-14,-11.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 93, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 27, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 141.0, + PowerbandStart = 1700, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = true, + DoNotStall = false, + + FuelFillPos = Vector(-80,-33.5,17), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 90, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/buffalo_idle.wav', + + snd_low = 'octoteam/vehicles/buffalo_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/buffalo_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/buffalo_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/buffalo_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/buffalo_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.17, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_schafter3', V ) + +local light_table = { + L_HeadLampPos = Vector(87,31,6.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(87,-31,6.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-106,30,12.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-106,-30,12.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(87,31,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(87,-31,6.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(25,19.5,19), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(91,26,5.2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(91,-26,5.2),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(25,18,19), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(95,27,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(95,-27,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-106,30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-106,-30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-109,22,11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-109,-22,11.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-110,18,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-110,-18,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(84,34,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-105,31,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,21.5,21), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(84,-34,7.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-105,-31,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,16,21), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [9] = '', + [10] = '', + [12] = '', + }, + Brake = { + [8] = '', + [9] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = '', + }, + Reverse = { + [8] = '', + [9] = '', + [10] = '', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [8] = '', + [9] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = '', + [10] = '', + [12] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = '', + [10] = '', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = '', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [12] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = '', + }, + Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = '', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + Brake_Reverse = { + [8] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [9] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [10] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + [12] = 'models/gta4/vehicles/schafter2/limo2_lights_on', + }, + }, + turnsignals = { + left = { + [3] = 'models/gta4/vehicles/schafter2/limo2_lights_on' + }, + right = { + [5] = 'models/gta4/vehicles/schafter2/limo2_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_schafter3', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_serrano.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_serrano.lua new file mode 100644 index 0000000..d2497a4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_serrano.lua @@ -0,0 +1,406 @@ +local V = { + Name = 'Serrano', + Model = 'models/octoteam/vehicles/serrano.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 3000.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_serrano', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,56)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,11)} + -- CarCols[3] = {REN.GTA4ColorTable(3,3,11)} + -- CarCols[4] = {REN.GTA4ColorTable(14,14,11)} + -- CarCols[5] = {REN.GTA4ColorTable(19,19,14)} + -- CarCols[6] = {REN.GTA4ColorTable(42,42,30)} + -- CarCols[7] = {REN.GTA4ColorTable(45,45,27)} + -- CarCols[8] = {REN.GTA4ColorTable(57,57,126)} + -- CarCols[9] = {REN.GTA4ColorTable(54,54,50)} + -- CarCols[10] = {REN.GTA4ColorTable(52,52,83)} + -- CarCols[11] = {REN.GTA4ColorTable(70,70,80)} + -- CarCols[12] = {REN.GTA4ColorTable(82,82,63)} + -- CarCols[13] = {REN.GTA4ColorTable(85,85,63)} + -- CarCols[14] = {REN.GTA4ColorTable(90,90,91)} + -- CarCols[15] = {REN.GTA4ColorTable(94,94,94)} + -- CarCols[16] = {REN.GTA4ColorTable(104,104,106)} + -- CarCols[17] = {REN.GTA4ColorTable(16,16,76)} + -- CarCols[18] = {REN.GTA4ColorTable(9,9,91)} + -- CarCols[19] = {REN.GTA4ColorTable(15,15,93)} + -- CarCols[20] = {REN.GTA4ColorTable(19,19,93)} + -- CarCols[21] = {REN.GTA4ColorTable(13,13,80)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/serrano_wheel.mdl', + + CustomWheelPosFL = Vector(61,34,-6), + CustomWheelPosFR = Vector(61,-34,-6), + CustomWheelPosRL = Vector(-61,34,-6), + CustomWheelPosRR = Vector(-61,-34,-6), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-19,31), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-19,-2), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-40,19,-2), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-40,-19,-2), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-98,20,-8), + ang = Angle(-110,0,0), + }, + { + pos = Vector(-98,-20,-8), + ang = Angle(-110,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 32000, + FrontDamping = 1250, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 1250, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 145.0, + PowerbandStart = 2000, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-73,-36,24), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/serrano_idle.wav', + + snd_low = 'octoteam/vehicles/serrano_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/serrano_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/serrano_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/serrano_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/serrano_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.21, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_serrano', V ) + +local light_table = { + L_HeadLampPos = Vector(83,33.5,17), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(83,-33.5,17), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-93,22,17), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-93,-22,17), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(83,33.5,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(83,-33.5,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(24.6,19,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(85.5,28,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(85.5,-28,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(24.6,17,23), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(92,27.2,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(92,-27.2,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-95,31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-95,-31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-95,31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-95,-31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-96,31,17.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-96,-31,17.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,32,27), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,20.5,25.8), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(91,-30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,-32,27), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,15.4,25.8), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [6] = '', + [12] = '', + [9] = '' + }, + Brake = { + [5] = '', + [6] = '', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Reverse = { + [5] = '', + [6] = '', + [12] = '', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + Brake_Reverse = { + [5] = '', + [6] = '', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_serrano', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_serrano2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_serrano2.lua new file mode 100644 index 0000000..216537e --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_serrano2.lua @@ -0,0 +1,435 @@ +local V = { + Name = 'Serrano Custom', + Model = 'models/octoteam/vehicles/serrano2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 3000.0, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_serrano2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + ent.CarCol = math.random(1,14) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,1)} + -- CarCols[2] = {REN.GTA4ColorTable(0,0,82)} + -- CarCols[3] = {REN.GTA4ColorTable(0,0,133)} + -- CarCols[4] = {REN.GTA4ColorTable(38,0,34)} + -- CarCols[5] = {REN.GTA4ColorTable(4,0,4)} + -- CarCols[6] = {REN.GTA4ColorTable(24,24,24)} + -- CarCols[7] = {REN.GTA4ColorTable(31,0,28)} + -- CarCols[8] = {REN.GTA4ColorTable(34,34,27)} + -- CarCols[9] = {REN.GTA4ColorTable(37,37,28)} + -- CarCols[10] = {REN.GTA4ColorTable(89,0,89)} + -- CarCols[11] = {REN.GTA4ColorTable(2,2,133)} + -- CarCols[12] = {REN.GTA4ColorTable(6,6,133)} + -- CarCols[13] = {REN.GTA4ColorTable(11,11,89)} + -- CarCols[14] = {REN.GTA4ColorTable(21,21,128)} + ent:SetProxyColors(CarCols[ent.CarCol] ) + + for i = 1,table.Count(ent.Wheels) do --this is a horrible way of doing this, yandere dev tier coding + if ent.Wheels != nil then + if ent.CarCol == 1 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 2 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(133)) + elseif ent.CarCol == 3 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(133)) + elseif ent.CarCol == 4 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 5 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 6 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 7 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 8 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(133)) + elseif ent.CarCol == 9 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(133)) + elseif ent.CarCol == 10 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 11 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(133)) + elseif ent.CarCol == 12 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 13 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(0)) + elseif ent.CarCol == 14 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(133)) + end + end + end + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/serrano2_wheel.mdl', + + CustomWheelPosFL = Vector(61,34,-6), + CustomWheelPosFR = Vector(61,-34,-6), + CustomWheelPosRL = Vector(-61,34,-6), + CustomWheelPosRR = Vector(-61,-34,-6), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-8,-19,31), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-19,-2), + ang = Angle(0,-90,10), + hasRadio = true + }, + { + pos = Vector(-40,19,-2), + ang = Angle(0,-90,10) + }, + { + pos = Vector(-40,-19,-2), + ang = Angle(0,-90,10) + }, + }, + ExhaustPositions = { + { + pos = Vector(-98,20,-8), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-98,-20,-8), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 12, + FrontConstant = 32000, + FrontDamping = 1250, + FrontRelativeDamping = 350, + + RearHeight = 12, + RearConstant = 32000, + RearDamping = 1250, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3, + + MaxGrip = 80, + Efficiency = 0.7, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5500, + PeakTorque = 145.0, + PowerbandStart = 2000, + PowerbandEnd = 5000, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-73,-36,24), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/serrano_idle.wav', + + snd_low = 'octoteam/vehicles/serrano_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/serrano_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/serrano_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/serrano_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/serrano_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/huntley_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.21, + Gears = {-0.4,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_serrano2', V ) + +local light_table = { + L_HeadLampPos = Vector(83,33.5,17), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(83,-33.5,17), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-93,22,17), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-93,-22,17), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(83,33.5,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(83,-33.5,17), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(24.6,19,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(85.5,28,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(85.5,-28,17),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(24.6,17,23), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(76,240,255,255), + },]] + }, + + FogLight_sprites = { + { + pos = Vector(92,27.2,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(92,-27.2,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + }, + Rearlight_sprites = { + { + pos = Vector(-95,31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-95,-31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-95,31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-95,-31,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-96,31,17.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-96,-31,17.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(91,30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,32,27), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,20.5,25.8), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(91,-30,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-92,-32,27), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(25,15.4,25.8), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [5] = '', + [6] = '', + [13] = '', + [9] = '' + }, + Brake = { + [5] = '', + [6] = '', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Reverse = { + [5] = '', + [6] = '', + [13] = '', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + Brake_Reverse = { + [5] = '', + [6] = '', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + on_lowbeam = { + Base = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = '', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + on_highbeam = { + Base = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Brake = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = '' + }, + Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + Brake_Reverse = { + [5] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [6] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [13] = 'models/gta4/vehicles/serrano/serrano_lights_on', + [9] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + turnsignals = { + left = { + [12] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/serrano/serrano_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_serrano2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_superd.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_superd.lua new file mode 100644 index 0000000..ee6a05d --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_superd.lua @@ -0,0 +1,368 @@ +local V = { + Name = 'Super Diamond', + Model = 'models/octoteam/vehicles/superd.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2000, + Trunk = { 45 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_superd', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,112)} + -- CarCols[2] = {REN.GTA4ColorTable(3,3,3)} + -- CarCols[3] = {REN.GTA4ColorTable(11,11,72)} + -- CarCols[4] = {REN.GTA4ColorTable(23,23,23)} + -- CarCols[5] = {REN.GTA4ColorTable(34,34,32)} + -- CarCols[6] = {REN.GTA4ColorTable(40,40,42)} + -- CarCols[7] = {REN.GTA4ColorTable(52,52,50)} + -- CarCols[8] = {REN.GTA4ColorTable(69,69,63)} + -- CarCols[9] = {REN.GTA4ColorTable(77,77,79)} + -- CarCols[10] = {REN.GTA4ColorTable(86,86,86)} + -- CarCols[11] = {REN.GTA4ColorTable(95,95,93)} + -- CarCols[12] = {REN.GTA4ColorTable(102,102,97)} + -- CarCols[13] = {REN.GTA4ColorTable(120,120,120)} + -- CarCols[14] = {REN.GTA4ColorTable(122,122,120)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/superd_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(75,33,-7), + CustomWheelPosFR = Vector(75,-33,-7), + CustomWheelPosRL = Vector(-75,33,-7), + CustomWheelPosRR = Vector(-75,-33,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-19,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(2,-19,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-43,19,-5), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-43,-19,-5), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-127,22,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-127,-22,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8.5, + FrontConstant = 30000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 8.5, + RearConstant = 30000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4.5, + + MaxGrip = 58, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 52, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.15, + + FuelFillPos = Vector(-98,-38,18), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/diamond_idle.wav', + + snd_low = 'octoteam/vehicles/diamond_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/diamond_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/diamond_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/diamond_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/diamond_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(25.980, 19.777, 25.373), ang = Angle(-0.0, -90.0, 76.3) }, + Radio = { pos = Vector(27.466, 0.016, 17.587), ang = Angle(-23.0, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(106.875, -0.019, -5.275), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-128.161, 0.038, 13.874), ang = Angle(-4.6, -180.0, -0.0) }, + }, + Mirrors = { + top = { + pos = Vector(6.961, 0.022, 39.514), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(23.831, 38.496, 26.883), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(24.132, -38.313, 26.299), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_superd', V ) + +local light_table = { + L_HeadLampPos = Vector(102,29.5,12.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(102,-29.5,12.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-125,31.5,12.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-125,-31.5,12.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(102,29.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(102,-29.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(102,23.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(102,-23.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(26,20.5,21.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(102,29.5,12.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-29.5,12.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,23.5,12.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-23.5,12.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(26,18.5,21.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-125,31.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-125,-31.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-125.5,26,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-125.5,-26,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(104,25.5,6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-126,28,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,22.5,21.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(104,-25.5,6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-126,-28,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(26,16.5,21.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [7] = '', + [5] = '', + }, + Brake = { + [7] = '', + [5] = 'models/gta4/vehicles/superd/diamond_lights_on', + }, + }, + on_lowbeam = { + Base = { + [7] = 'models/gta4/vehicles/superd/diamond_lights_on', + [5] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/superd/diamond_lights_on', + [5] = 'models/gta4/vehicles/superd/diamond_lights_on', + }, + }, + on_highbeam = { + Base = { + [7] = 'models/gta4/vehicles/superd/diamond_lights_on', + [5] = '', + }, + Brake = { + [7] = 'models/gta4/vehicles/superd/diamond_lights_on', + [5] = 'models/gta4/vehicles/superd/diamond_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/superd/diamond_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/superd/diamond_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_superd', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_superd2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_superd2.lua new file mode 100644 index 0000000..f885963 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_superd2.lua @@ -0,0 +1,349 @@ +local V = { + Name = 'Super Drop Diamond', + Model = 'models/octoteam/vehicles/superd2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Особые', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 2000, + Trunk = { 45 }, + + EnginePos = Vector(70,0,10), + + LightsTable = 'gta4_superd2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(134,136,134)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/superd2_wheel.mdl', + + ModelInfo = { + WheelColor = Color(10,10,10), + }, + + CustomWheelPosFL = Vector(75,33,-7), + CustomWheelPosFR = Vector(75,-33,-7), + CustomWheelPosRL = Vector(-75,33,-7), + CustomWheelPosRR = Vector(-75,-33,-7), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,0), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-22,-19,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-10,-19,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-43,19,-5), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-43,-19,-5), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-127,22,-7), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-127,-22,-7), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8.5, + FrontConstant = 30000, + FrontDamping = 1100, + FrontRelativeDamping = 1100, + + RearHeight = 8.5, + RearConstant = 30000, + RearDamping = 1100, + RearRelativeDamping = 1100, + + FastSteeringAngle = 15, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 4.5, + + MaxGrip = 58, + Efficiency = 0.9, + GripOffset = 0, + BrakePower = 35, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 52, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + PowerBoost = 1.15, + + FuelFillPos = Vector(-98,-38,18), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/diamond_idle.wav', + + snd_low = 'octoteam/vehicles/diamond_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/diamond_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/diamond_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/diamond_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/diamond_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/admiral_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(14.205, 19.777, 24.012), ang = Angle(-0.0, -90.0, 76.3) }, + Radio = { pos = Vector(27.466, 0.016, 17.587), ang = Angle(-23.0, -180.0, -0.0) }, + Plates = { + Front = { pos = Vector(106.875, -0.019, -5.275), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-128.161, 0.038, 13.874), ang = Angle(-4.6, -180.0, -0.0) }, + }, + Mirrors = { + left = { + pos = Vector(12.522, 40.232, 25.299), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(12.994, -40.403, 25.315), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_superd2', V ) + +local light_table = { + L_HeadLampPos = Vector(102,29.5,12.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(102,-29.5,12.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-125,31.5,12.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-125,-31.5,12.5), + R_RearLampAng = Angle(25,180,0), + + ModernLights = true, + + Headlight_sprites = { + { + pos = Vector(102,29.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(102,-29.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(227,242,255,255), + }, + { + pos = Vector(102,23.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + { + pos = Vector(102,-23.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(227,242,255,255), + }, + +--[[ { + pos = Vector(14,20.5,21.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(102,29.5,12.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-29.5,12.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,23.5,12.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(102,-23.5,12.5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(14,18.5,21.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-125,31.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-125,-31.5,12.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-125.5,26,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-125.5,-26,13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(104,25.5,6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-126,28,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(14,22.5,21.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(104,-25.5,6.9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-126,-28,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(14,16.5,21.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [4] = '', + }, + Brake = { + [8] = '', + [4] = 'models/gta4/vehicles/superd/diamond_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/superd/diamond_lights_on', + [4] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/superd/diamond_lights_on', + [4] = 'models/gta4/vehicles/superd/diamond_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/superd/diamond_lights_on', + [4] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/superd/diamond_lights_on', + [4] = 'models/gta4/vehicles/superd/diamond_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/superd/diamond_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/superd/diamond_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_superd2', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_tampa.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_tampa.lua new file mode 100644 index 0000000..cc11165 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tbogt_tampa.lua @@ -0,0 +1,385 @@ +local V = { + Name = 'Tampa', + Model = 'models/octoteam/vehicles/tampa.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Маслкары', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Маслкары', + + Members = { + Mass = 1700.0, + + EnginePos = Vector(60,0,0), + + LightsTable = 'gta4_tampa', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,49)} + -- CarCols[2] = {REN.GTA4ColorTable(38,38,85)} + -- CarCols[3] = {REN.GTA4ColorTable(0,97,102)} + -- CarCols[4] = {REN.GTA4ColorTable(3,0,12)} + -- CarCols[5] = {REN.GTA4ColorTable(6,6,4)} + -- CarCols[6] = {REN.GTA4ColorTable(11,4,12)} + -- CarCols[7] = {REN.GTA4ColorTable(13,13,12)} + -- CarCols[8] = {REN.GTA4ColorTable(16,95,90)} + -- CarCols[9] = {REN.GTA4ColorTable(24,1,12)} + -- CarCols[10] = {REN.GTA4ColorTable(21,21,12)} + -- CarCols[11] = {REN.GTA4ColorTable(23,23,2)} + -- CarCols[12] = {REN.GTA4ColorTable(31,31,27)} + -- CarCols[13] = {REN.GTA4ColorTable(32,113,34)} + -- CarCols[14] = {REN.GTA4ColorTable(34,12,34)} + -- CarCols[15] = {REN.GTA4ColorTable(49,13,41)} + -- CarCols[16] = {REN.GTA4ColorTable(52,0,59)} + -- CarCols[17] = {REN.GTA4ColorTable(62,13,69)} + -- CarCols[18] = {REN.GTA4ColorTable(76,4,76)} + -- CarCols[19] = {REN.GTA4ColorTable(82,85,76)} + -- CarCols[20] = {REN.GTA4ColorTable(87,8,76)} + -- CarCols[21] = {REN.GTA4ColorTable(89,0,4)} + -- CarCols[22] = {REN.GTA4ColorTable(92,95,90)} + -- CarCols[23] = {REN.GTA4ColorTable(95,95,2)} + -- CarCols[24] = {REN.GTA4ColorTable(106,103,2)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/tampa_wheel.mdl', + + CustomWheelPosFL = Vector(60,33,-8), + CustomWheelPosFR = Vector(60,-33,-8), + CustomWheelPosRL = Vector(-59,33,-8), + CustomWheelPosRR = Vector(-59,-33,-8), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-17,-18,18), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-4,-18,-13), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-107,20.5,-3.5), + ang = Angle(-90,-5,0), + }, + { + pos = Vector(-107,-20.5,-3.5), + ang = Angle(-90,5,0), + }, + }, + + FrontHeight = 13, + FrontConstant = 18000, + FrontDamping = 750, + FrontRelativeDamping = 350, + + RearHeight = 13, + RearConstant = 18000, + RearDamping = 750, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 87, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 23, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 145.0, + PowerbandStart = 1500, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-58,-38,15), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/dukes_idle.wav', + + snd_low = 'octoteam/vehicles/vigero_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/vigero_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/vigero_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/vigero_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/vigero_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/sabre_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.22, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tbogt_tampa', V ) + +local light_table = { + L_HeadLampPos = Vector(84,30,5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(84,-30,5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-105.5,27,11), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-105.5,-27,11), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(84,30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(84,-30,5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(14,12,14), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(88,21.5,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(88,-21.5,5),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(13,12,13), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-105.5,27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105.5,-27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-105.5,21,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105.5,-21,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-105.5,21,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-105.5,-21,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(89,27,-6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + { + pos = Vector(-105.5,27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(14,19,14), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(89,-27,-6.7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + { + pos = Vector(-105.5,-27,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(14,16,14), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [14] = '', + [15] = '', + [13] = '', + [12] = '' + }, + Brake = { + [14] = '', + [15] = '', + [13] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [12] = '' + }, + Reverse = { + [14] = '', + [15] = '', + [13] = '', + [12] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + Brake_Reverse = { + [14] = '', + [15] = '', + [13] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [12] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + }, + on_lowbeam = { + Base = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = '', + [13] = '', + [12] = '' + }, + Brake = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = '', + [13] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [12] = '' + }, + Reverse = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = '', + [13] = '', + [12] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + Brake_Reverse = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = '', + [13] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [12] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + }, + on_highbeam = { + Base = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [13] = '', + [12] = '' + }, + Brake = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [13] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [12] = '' + }, + Reverse = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [13] = '', + [12] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + Brake_Reverse = { + [14] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [15] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [13] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on', + [12] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + right = { + [6] = 'models/gta4/vehicles/tampa/sabreturbo_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_tampa', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_gburrito.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_gburrito.lua new file mode 100644 index 0000000..8802611 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_gburrito.lua @@ -0,0 +1,328 @@ +local V = { + Name = 'Gang Burrito', + Model = 'models/octoteam/vehicles/gburrito.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Особые', + SpawnOffset = Vector(0,0,20), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(90,0,10), + + LightsTable = 'gta4_gburrito', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(1,2,0)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/gburrito_wheel.mdl', + + CustomWheelPosFL = Vector(80,40,-25), + CustomWheelPosFR = Vector(80,-40,-25), + CustomWheelPosRL = Vector(-80,40,-25), + CustomWheelPosRR = Vector(-80,-40,-25), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 30, + + SeatOffset = Vector(30,-27,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(32,-25,-5), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-48,48,-26), + ang = Angle(-90,-70,0), + }, + { + pos = Vector(-53,48,-26), + ang = Angle(-90,-70,0), + }, + { + pos = Vector(-48,-48,-26), + ang = Angle(-90,70,0), + }, + { + pos = Vector(-53,-48,-26), + ang = Angle(-90,70,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 500, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 500, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 95, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 20, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 130.0, + PowerbandStart = 2200, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-50,48,-10), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 80, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/burrito_idle.wav', + + snd_low = 'octoteam/vehicles/burrito_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/burrito_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/burrito_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/burrito_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/burrito_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/burrito_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.16, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_gburrito', V ) + +local light_table = { + L_HeadLampPos = Vector(104,36,7), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(104,-36,7), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-113,36,19), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-113,-36,19), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(104,36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(104,-36,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(57,20,23), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(105,27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(105,-27,7),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(57,21,23), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-113,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-113,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-113,36.3,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-113,-36.3,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(102,43,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(102,43,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(57,31,23), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeLeft = { + { + pos = Vector(-113,36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(102,-43,7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(102,-43,-0.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, +--[[ { + pos = Vector(57,30,23), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + TurnBrakeRight = { + { + pos = Vector(-113,-36,19), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [3] = '', + [12] = '', + [10] = '', + }, + Reverse = { + [3] = '', + [12] = '', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + on_lowbeam = { + Base = { + [3] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [12] = '', + [10] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [12] = '', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + on_highbeam = { + Base = { + [3] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [12] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [10] = '', + }, + Reverse = { + [3] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [12] = 'models/gta4/vehicles/burrito/burrito_lights_on', + [10] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + turnsignals = { + left = { + [11] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + right = { + [8] = 'models/gta4/vehicles/burrito/burrito_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_gburrito', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_pbus.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_pbus.lua new file mode 100644 index 0000000..4d709f4 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_pbus.lua @@ -0,0 +1,429 @@ +local V = { + Name = 'Prison Bus', + Model = 'models/octoteam/vehicles/pbus.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Службы', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Службы', + + Members = { + Mass = 7500.0, + + EnginePos = Vector(110,0,0), + + LightsTable = 'gta4_pbus', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(65,66,65)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 1, 3) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + IsArmored = true, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/pbus_wheel.mdl', + + CustomWheelPosFL = Vector(97,43,-32), + CustomWheelPosFR = Vector(97,-43,-32), + CustomWheelPosRL = Vector(-97,43,-32), + CustomWheelPosRR = Vector(-97,-43,-32), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,25), + + CustomSteerAngle = 35, + + SeatOffset = Vector(29,-18,40), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(0,-33,3), + ang = Angle(0,-90,0), + hasRadio = true + }, + { + pos = Vector(0,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-38,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-38,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-75,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-75,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-112,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-112,33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-149,-33,3), + ang = Angle(0,-90,0) + }, + { + pos = Vector(-149,33,3), + ang = Angle(0,-90,0) + }, + }, + ExhaustPositions = { + { + pos = Vector(-57,-46,-32), + ang = Angle(-100,90,0), + }, + { + pos = Vector(-53,-46,-32), + ang = Angle(-100,90,0), + }, + }, + + StrengthenSuspension = true, + + FrontHeight = 10, + FrontConstant = 35000, + FrontDamping = 4000, + FrontRelativeDamping = 4000, + + RearHeight = 10, + RearConstant = 35000, + RearDamping = 4000, + RearRelativeDamping = 4000, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 40, + BulletProofTires = true, + + IdleRPM = 800, + LimitRPM = 4500, + PeakTorque = 75, + PowerbandStart = 1700, + PowerbandEnd = 4300, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-59,-50,-17), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 160, + + AirFriction = -60, + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/bus_idle.wav', + + snd_low = 'octoteam/vehicles/bus_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/bus_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/bus_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/bus_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/bus_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'BUS_HORN', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.16, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1}, + + Dash = { pos = Vector(71.261, 18.322, 25.553), ang = Angle(0.0, -90.0, 79.6) }, + Radio = { pos = Vector(66.316, -10.450, 24.861), ang = Angle(0.0, 90.0, 0.0) }, + Plates = { + Front = { pos = Vector(139.954, 0, -26.187), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-201.927, 0, -22.796), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(70.085, -11.008, 47.725), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(58.821, 62.320, 45.287), + w = 1 / 6, + ratio = 3 / 6, + }, + right = { + pos = Vector(58.821, -62.320, 45.287), + w = 1 / 6, + ratio = 3 / 6, + }, + }, + + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_pbus', V ) + +local light_table = { + L_HeadLampPos = Vector(134,39.5,-4.5), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(134,-39.5,-4.5), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-202,42,11), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-202,-42,11), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(134,39.5,-4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(134,-39.5,-4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(72,21.3,21.5), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(134,39.5,-4.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(134,-39.5,-4.5),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(72,22,21.5), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-202,42,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-202,-42,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-202,31.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-202,-31.5,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-201,23,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-201,-23,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(134,39.5,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-201,43.3,-4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-201,43.3,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(71,21.3,20.5), + material = 'gta4/dash_left', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(134,-39.5,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-201,-43.3,-4.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-201,-43.3,-9.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(71,13.5,20.5), + material = 'gta4/dash_right', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [5] = '', + [10] = '' + }, + Brake = { + [4] = '', + [5] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [10] = '', + }, + Reverse = { + [4] = '', + [5] = '', + [10] = 'models/gta4/vehicles/pbus/pbus_lights_on', + }, + Brake_Reverse = { + [4] = '', + [5] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [10] = 'models/gta4/vehicles/pbus/pbus_lights_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = '', + [10] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = '', + [10] = 'models/gta4/vehicles/pbus/pbus_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [10] = 'models/gta4/vehicles/pbus/pbus_lights_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = '', + [10] = '' + }, + Brake = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [10] = '', + }, + Reverse = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = '', + [10] = 'models/gta4/vehicles/pbus/pbus_lights_on', + }, + Brake_Reverse = { + [4] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [5] = 'models/gta4/vehicles/pbus/pbus_lights_on', + [10] = 'models/gta4/vehicles/pbus/pbus_lights_on', + }, + }, + turnsignals = { + left = { + [7] = 'models/gta4/vehicles/pbus/pbus_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/pbus/pbus_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_pbus', light_table) diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_regina.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_regina.lua new file mode 100644 index 0000000..3879126 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_regina.lua @@ -0,0 +1,369 @@ +local V = { + Name = 'Regina', + Model = 'models/octoteam/vehicles/regina.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Седаны', + SpawnOffset = Vector(0,0,0), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Седаны', + + Members = { + Mass = 1500, + Trunk = { 80 }, + + EnginePos = Vector(60,0,10), + + LightsTable = 'gta4_regina', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,0)} + -- CarCols[2] = {REN.GTA4ColorTable(28,1,0)} + -- CarCols[3] = {REN.GTA4ColorTable(44,1,0)} + -- CarCols[4] = {REN.GTA4ColorTable(51,1,0)} + -- CarCols[5] = {REN.GTA4ColorTable(57,1,0)} + -- CarCols[6] = {REN.GTA4ColorTable(67,1,0)} + -- CarCols[7] = {REN.GTA4ColorTable(128,1,0)} + -- CarCols[8] = {REN.GTA4ColorTable(131,1,0)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + -- REN.GTA4BeaterInit(ent) + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 0) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + -- REN.GTA4Beater(ent) + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/regina_wheel.mdl', + + CustomWheelPosFL = Vector(64,36,-2), + CustomWheelPosFR = Vector(64,-36,-2), + CustomWheelPosRL = Vector(-64,36,-2), + CustomWheelPosRR = Vector(-64,-36,-2), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,5), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-10,-21,25), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(3,-20,-7), + ang = Angle(0,-90,20), + hasRadio = true + }, + { + pos = Vector(-40,20,-7), + ang = Angle(0,-90,20) + }, + { + pos = Vector(-40,-20,-7), + ang = Angle(0,-90,20) + }, + }, + ExhaustPositions = { + { + pos = Vector(-118,29,-3.5), + ang = Angle(-90,0,0), + }, + { + pos = Vector(-118,-29,-3.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 8, + FrontConstant = 28000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 8, + RearConstant = 28000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 500, + + TurnSpeed = 3.5, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 39, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-80,-40,20), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 60, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/rancher_idle.wav', + + snd_low = 'octoteam/vehicles/rancher_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/rancher_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/rancher_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/rancher_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/rancher_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/rancher_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(16.855, 20.243, 19.761), ang = Angle(-0.0, -90.0, 68.5) }, + Radio = { pos = Vector(22.396, -0.005, 11.531), ang = Angle(0.0, 180.0, 0.0) }, + Plates = { + Front = { pos = Vector(102.422, -0.004, -0.803), ang = Angle(20.6, -0.0, -0.0) }, + Back = { pos = Vector(-122.795, -0.011, 1.970), ang = Angle(0.0, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(5.165, -0.033, 35.563), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(10.784, 43.757, 23.194), + w = 1 / 5, + ratio = 4.5 / 3, + }, + right = { + pos = Vector(12.086, -43.591, 23.623), + w = 1 / 5, + ratio = 4.5 / 3, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_regina', V ) + +local light_table = { + L_HeadLampPos = Vector(96,34,12), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(96,-34,12), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-119,36,12), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-119,-36,12), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(96,34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,-34,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,27.5,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + { + pos = Vector(96,-27.5,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(96,34,12),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,-34,12),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,27.5,12),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(96,-27.5,12),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-119,36,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-119,-36,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-102,21,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + { + pos = Vector(-102,-21,15.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 30, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(99,36,2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + }, + TurnBrakeLeft = { + { + pos = Vector(-119,36,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Right = { + { + pos = Vector(99,-36,2.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,250), + }, + }, + TurnBrakeRight = { + { + pos = Vector(-119,-36,12), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [13] = '', + [12] = '', + [10] = '', + }, + Brake = { + [13] = '', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = '', + }, + Reverse = { + [13] = '', + [12] = '', + [10] = 'models/gta4/vehicles/regina/regina_lights_on', + }, + Brake_Reverse = { + [13] = '', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = 'models/gta4/vehicles/regina/regina_lights_on', + }, + }, + on_lowbeam = { + Base = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = '', + }, + Brake = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = '', + }, + Reverse = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = 'models/gta4/vehicles/regina/regina_lights_on', + }, + Brake_Reverse = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = 'models/gta4/vehicles/regina/regina_lights_on', + }, + }, + on_highbeam = { + Base = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = '', + }, + Brake = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = '', + }, + Reverse = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = 'models/gta4/vehicles/regina/regina_lights_on', + }, + Brake_Reverse = { + [13] = 'models/gta4/vehicles/regina/regina_lights_on', + [12] = 'models/gta4/vehicles/regina/regina_lights_on', + [10] = 'models/gta4/vehicles/regina/regina_lights_on', + }, + }, + turnsignals = { + left = { + [14] = 'models/gta4/vehicles/regina/regina_lights_on' + }, + right = { + [11] = 'models/gta4/vehicles/regina/regina_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_regina', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_rhapsody.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_rhapsody.lua new file mode 100644 index 0000000..fe6fb03 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_rhapsody.lua @@ -0,0 +1,362 @@ +local V = { + Name = 'Rhapsody', + Model = 'models/octoteam/vehicles/rhapsody.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Хетчбеки', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Хетчбеки', + + Members = { + Mass = 1250, + Trunk = { 40 }, + + EnginePos = Vector(55,0,10), + + LightsTable = 'gta4_rhapsody', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,33)} + -- CarCols[2] = {REN.GTA4ColorTable(7,7,8)} + -- CarCols[3] = {REN.GTA4ColorTable(10,10,8)} + -- CarCols[4] = {REN.GTA4ColorTable(23,23,8)} + -- CarCols[5] = {REN.GTA4ColorTable(57,57,56)} + -- CarCols[6] = {REN.GTA4ColorTable(62,62,62)} + -- CarCols[7] = {REN.GTA4ColorTable(66,66,66)} + -- CarCols[8] = {REN.GTA4ColorTable(72,72,72)} + -- CarCols[9] = {REN.GTA4ColorTable(78,78,78)} + -- CarCols[10] = {REN.GTA4ColorTable(95,95,95)} + -- CarCols[11] = {REN.GTA4ColorTable(104,104,104)} + -- CarCols[12] = {REN.GTA4ColorTable(107,107,107)} + -- CarCols[13] = {REN.GTA4ColorTable(2,2,63)} + -- CarCols[14] = {REN.GTA4ColorTable(21,21,72)} + -- CarCols[15] = {REN.GTA4ColorTable(22,22,72)} + -- CarCols[16] = {REN.GTA4ColorTable(13,11,91)} + -- CarCols[17] = {REN.GTA4ColorTable(19,19,93)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 1) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/rhapsody_wheel.mdl', + + CustomWheelPosFL = Vector(54,32,1), + CustomWheelPosFR = Vector(54,-32,1), + CustomWheelPosRL = Vector(-53,32,1), + CustomWheelPosRR = Vector(-53,-32,1), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0.02,-2.4), + + CustomSteerAngle = 35, + + SeatOffset = Vector(-15,-17,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(-1,-17,-5), + ang = Angle(0,-90,20), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-84,-19,-0.5), + ang = Angle(-90,0,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 22000, + FrontDamping = 800, + FrontRelativeDamping = 800, + + RearHeight = 10, + RearConstant = 22000, + RearDamping = 800, + RearRelativeDamping = 800, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3.5, + CounterSteeringMul = 0.8, + + MaxGrip = 45, + Efficiency = 0.85, + GripOffset = 0, + BrakePower = 25, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 6000, + PeakTorque = 45, + PowerbandStart = 1200, + PowerbandEnd = 5500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-54,-36.5,19), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 35, + + AirFriction = -50, + PowerBias = -1, + + EngineSoundPreset = -1, + + snd_pitch = 1.1, + snd_idle = 'octoteam/vehicles/blista_idle.wav', + + snd_low = 'octoteam/vehicles/blista_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/blista_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/blista_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/blista_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/blista_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/blista_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.3, + Gears = {-0.12,0,0.1,0.17,0.25,0.34,0.45}, + + Dash = { pos = Vector(15.752, 17.290, 21.695), ang = Angle(-0.0, -90.0, 82.0) }, + Radio = { pos = Vector(20.884, -0.310, 17.569), ang = Angle(13.0, 180.0, -0.0) }, + Plates = { + Front = { pos = Vector(84.035, 0.005, 2.049), ang = Angle(0.0, 0.0, 0.0) }, + Back = { pos = Vector(-90.521, 0.000, 3.615), ang = Angle(3.3, 180.0, 0.0) }, + }, + Mirrors = { + top = { + pos = Vector(5.641, 0.001, 39.931), + ang = Angle(10, 0, 0), + w = 1 / 3, + ratio = 3.5 / 1, + }, + left = { + pos = Vector(13.081, 37.777, 24.480), + w = 1 / 6, + ratio = 1, + }, + right = { + pos = Vector(12.839, -37.898, 24.809), + w = 1 / 6, + ratio = 1, + }, + }, + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_rhapsody', V ) + +local light_table = { + L_HeadLampPos = Vector(80,28,11), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(80,-28,11), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-86,18.5,14), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-86,-18.5,14), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(80,28,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(80,-28,11), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(80,28,11),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(80,-28,11),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-86,18.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,-18.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-86,18.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-86,-18.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + Reverselight_sprites = { + { + pos = Vector(-86,23.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + { + pos = Vector(-86,-23.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,255,255,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(78,25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-83,29.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + }, + Right = { + { + pos = Vector(78,-25,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-83,-29.5,14), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,35,0,150), + }, + }, + }, + + SubMaterials = { + off = { + Base = { + [14] = '', + [8] = '', + [13] = '', + }, + Brake = { + [14] = '', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = '', + }, + Reverse = { + [14] = '', + [8] = '', + [13] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + }, + Brake_Reverse = { + [14] = '', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + }, + }, + on_lowbeam = { + Base = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = '', + }, + Brake = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = '', + }, + Reverse = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + }, + Brake_Reverse = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + }, + }, + on_highbeam = { + Base = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = '', + }, + Brake = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = '', + }, + Reverse = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + }, + Brake_Reverse = { + [14] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [8] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + [13] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on' + }, + right = { + [9] = 'models/gta4/vehicles/rhapsody/rhapsody_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_rhapsody', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_slamvan.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_slamvan.lua new file mode 100644 index 0000000..b04b3f2 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_slamvan.lua @@ -0,0 +1,295 @@ +local V = { + Name = 'Slamvan', + Model = 'models/octoteam/vehicles/slamvan.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(70,0,10), + + BackFire = true, + + LightsTable = 'gta4_slamvan', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + ent.CarCol = math.random(1,4) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(0,0,2)} + -- CarCols[2] = {REN.GTA4ColorTable(1,1,1)} + -- CarCols[3] = {REN.GTA4ColorTable(11,4,10)} + -- CarCols[4] = {REN.GTA4ColorTable(30,30,10)} + ent:SetProxyColors(CarCols[ent.CarCol] ) + + for i = 1,table.Count(ent.Wheels) do + if ent.Wheels != nil then + if ent.CarCol == 1 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(35)) + elseif ent.CarCol == 2 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(1)) + elseif ent.CarCol == 3 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(3)) + elseif ent.CarCol == 4 then + ent.Wheels[i]:SetColor(REN.GTA4ColorTable(3)) + end + end + end + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/slamvan_wheel.mdl', + + CustomWheelPosFL = Vector(60,37,-15), + CustomWheelPosFR = Vector(60,-37,-15), + CustomWheelPosRL = Vector(-60,37,-15), + CustomWheelPosRR = Vector(-60,-37,-15), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(3,-17,27), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-17,-5), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-35,36,-19), + ang = Angle(-90,-80,0), + }, + { + pos = Vector(-31.5,36,-19), + ang = Angle(-90,-80,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 500, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 500, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 100, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 19, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 125.0, + PowerbandStart = 2200, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-25,-38,-11), + FuelType = FUELTYPE_PETROL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/slamvan_idle.wav', + + snd_low = 'octoteam/vehicles/slamvan_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/slamvan_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/slamvan_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/slamvan_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/slamvan_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/slamvan_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.18, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_slamvan', V ) + +local light_table = { + L_HeadLampPos = Vector(92,33,-3), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(92,-33,-3), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-105.5,26.5,-1.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-105.5,-26.5,-1.5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(92,33,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(92,-33,-3), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(92,33,-3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,-33,-3),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-105.5,26.5,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105.5,-26.5,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-105.5,15,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-105.5,-15,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-105.5,20,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(33.5,20.4,19.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,0,0,255), + },]] + }, + Right = { + { + pos = Vector(-105.5,-20,-1.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(33.5,14.8,19.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,0,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [8] = '', + [5] = '', + }, + Brake = { + [8] = '', + [5] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + }, + on_lowbeam = { + Base = { + [8] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + [5] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + [5] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + }, + on_highbeam = { + Base = { + [8] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + [5] = '', + }, + Brake = { + [8] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + [5] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + }, + turnsignals = { + left = { + [9] = 'models/gta4/vehicles/slamvan/slamvan_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/slamvan/slamvan_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_slamvan', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_towtruck.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_towtruck.lua new file mode 100644 index 0000000..65d85ce --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_towtruck.lua @@ -0,0 +1,290 @@ +local V = { + Name = 'Tow Truck', + Model = 'models/octoteam/vehicles/towtruck.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Большие', + SpawnOffset = Vector(0,0,10), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Большие', + + Members = { + Mass = 2500.0, + + EnginePos = Vector(70,0,10), + + BackFire = true, + + LightsTable = 'gta4_towtruck', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable(40,40,39)} + -- CarCols[2] = {REN.GTA4ColorTable(52,52,53)} + -- CarCols[3] = {REN.GTA4ColorTable(97,97,97)} + -- CarCols[4] = {REN.GTA4ColorTable(90,90,90)} + -- CarCols[5] = {REN.GTA4ColorTable(52,52,52)} + -- CarCols[6] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[7] = {REN.GTA4ColorTable(36,36,36)} + -- CarCols[8] = {REN.GTA4ColorTable(41,41,41)} + -- CarCols[9] = {REN.GTA4ColorTable(48,48,48)} + -- CarCols[10] = {REN.GTA4ColorTable(55,55,55)} + -- CarCols[11] = {REN.GTA4ColorTable(57,57,57)} + -- CarCols[12] = {REN.GTA4ColorTable(64,64,64)} + -- CarCols[13] = {REN.GTA4ColorTable(71,71,71)} + -- CarCols[14] = {REN.GTA4ColorTable(77,77,77)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 0, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 0, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/slamvan_wheel.mdl', + + CustomWheelPosFL = Vector(60,37,-15), + CustomWheelPosFR = Vector(60,-37,-15), + CustomWheelPosRL = Vector(-60,37,-15), + CustomWheelPosRR = Vector(-60,-37,-15), + CustomWheelAngleOffset = Angle(0,-90,0), + + CustomMassCenter = Vector(0,0,10), + + CustomSteerAngle = 35, + + SeatOffset = Vector(3,-17,29), + SeatPitch = 0, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(7,-17,-3), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-35,36,-19), + ang = Angle(-90,-80,0), + }, + { + pos = Vector(-31.5,36,-19), + ang = Angle(-90,-80,0), + }, + }, + + FrontHeight = 10, + FrontConstant = 25000, + FrontDamping = 500, + FrontRelativeDamping = 350, + + RearHeight = 10, + RearConstant = 25000, + RearDamping = 500, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 600, + + TurnSpeed = 3, + + MaxGrip = 90, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 19, + BulletProofTires = false, + + IdleRPM = 800, + LimitRPM = 5000, + PeakTorque = 125.0, + PowerbandStart = 2200, + PowerbandEnd = 4500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-24,-31,-6), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 70, + + PowerBias = 1, + + EngineSoundPreset = 0, + + Sound_Idle = 'octoteam/vehicles/towtruck_idle.wav', + Sound_IdlePitch = 0.85, + + Sound_Mid = 'octoteam/vehicles/towtruck_low.wav', + Sound_MidPitch = 1, + Sound_MidVolume = 1, + Sound_MidFadeOutRPMpercent = 60, + Sound_MidFadeOutRate = 0.3, + + Sound_High = 'octoteam/vehicles/towtruck_high.wav', + Sound_HighPitch = 1.3, + Sound_HighVolume = 2, + Sound_HighFadeInRPMpercent = 60, + Sound_HighFadeInRate = 0.3, + + Sound_Throttle = 'octoteam/vehicles/towtruck_throttle.wav', + Sound_ThrottlePitch = 1, + Sound_ThrottleVolume = 5, + + snd_horn = 'octoteam/vehicles/horns/vigero2_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/WASTE_GATE.wav', + + DifferentialGear = 0.14, + Gears = {-0.3,0,0.15,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_towtruck', V ) + +local light_table = { + L_HeadLampPos = Vector(92,33,0), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(92,-33,0), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-107,35.5,-3.5), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-107,-35.5,-3.5), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(92,33,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(92,-33,0), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + }, + + Headlamp_sprites = { + {pos = Vector(92,33,0),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(92,-33,0),size = 60,material = 'octoteam/sprites/lights/gta4_corona'}, + }, + + Rearlight_sprites = { + { + pos = Vector(-107,35.5,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,-35.5,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-107,35.5,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-107,-35.5,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(-107,30.5,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(33.5,20.4,21.3), + material = 'gta4/dash_left', + size = 0.75, + color = Color(255,0,0,255), + },]] + }, + Right = { + { + pos = Vector(-107,30.5,-3.5), + material = 'octoteam/sprites/lights/gta4_corona', + size = 20, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(33.5,14.8,21.3), + material = 'gta4/dash_right', + size = 0.75, + color = Color(255,0,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [9] = '', + }, + Brake = { + [9] = '', + }, + }, + on_lowbeam = { + Base = { + [9] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + Brake = { + [9] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + }, + on_highbeam = { + Base = { + [9] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + Brake = { + [9] = 'models/gta4/vehicles/slamvan/slamvan_lights_on', + }, + }, + turnsignals = { + left = { + [5] = 'models/gta4/vehicles/slamvan/slamvan_lights_on' + }, + right = { + [10] = 'models/gta4/vehicles/slamvan/slamvan_lights_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_towtruck', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_yankee2.lua b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_yankee2.lua new file mode 100644 index 0000000..1149cdd --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/autorun/simfphys_tlad_yankee2.lua @@ -0,0 +1,300 @@ +local V = { + Name = 'Yankee (Flatbed)', + Model = 'models/octoteam/vehicles/yankee2.mdl', + Class = 'gmod_sent_vehicle_fphysics_base', + Category = 'Доброград - Индустриальные', + SpawnOffset = Vector(0,0,40), + SpawnAngleOffset = 90, + NAKGame = 'Доброград', + NAKType = 'Индустриальные', + + Members = { + Mass = 4000.0, + + EnginePos = Vector(110,0,20), + + LightsTable = 'gta4_yankee2', + + OnSpawn = function(ent) + -- ent:SetSkin(math.random(0,2)) + + if (ProxyColor ) then + -- local CarCols = {} + -- CarCols[1] = {REN.GTA4ColorTable2(1,2,8,5)} + -- ent:SetProxyColors(CarCols[math.random(1,table.Count(CarCols))] ) + end + + REN.GTA4SimfphysInit(ent, 1, 1) --name of car 'ent', ignition type 0-Standard Car 1-Truck 2-Moped 3-Bike, has shutdown noise? 0/1 + end, + + OnTick = function(ent) + REN.GTA4SimfphysOnTick(ent, 1, 0, 2) --name of car 'ent', Has reversing beep? 0/1, Uses big rig brakes? 0/1 Handbrake type? 0-Standard 1-Sporty 2-Truck + end, + + OnDelete = function(ent) + REN.GTA4Delete(ent) --MUST call on EVERY car that uses gta 4 functions + end, + + CustomWheels = true, + CustomSuspensionTravel = 1.5, + + CustomWheelModel = 'models/octoteam/vehicles/yankee2_wheel.mdl', + CustomWheelModel_R = 'models/octoteam/vehicles/yankee2_wheel_r.mdl', + + CustomWheelPosFL = Vector(110,45,-15), + CustomWheelPosFR = Vector(110,-45,-15), + CustomWheelPosRL = Vector(-111,46,-15), + CustomWheelPosRR = Vector(-111,-46,-15), + CustomWheelAngleOffset = Angle(0,-90,0), + + FrontWheelRadius = 21.5, + RearWheelRadius = 21.5, + + CustomMassCenter = Vector(0,0,30), + + CustomSteerAngle = 35, + + SeatOffset = Vector(30,-24,55), + SeatPitch = 10, + SeatYaw = 90, + + PassengerSeats = { + { + pos = Vector(43,-23,15), + ang = Angle(0,-90,0), + hasRadio = true + }, + }, + ExhaustPositions = { + { + pos = Vector(-74,16,-17), + ang = Angle(-120,-25,0), + }, + }, + + FrontHeight = 18, + FrontConstant = 40000, + FrontDamping = 1000, + FrontRelativeDamping = 700, + + RearHeight = 18, + RearConstant = 40000, + RearDamping = 1000, + RearRelativeDamping = 350, + + FastSteeringAngle = 10, + SteeringFadeFastSpeed = 700, + + TurnSpeed = 3, + + MaxGrip = 103, + Efficiency = 0.65, + GripOffset = 0, + BrakePower = 30, + BulletProofTires = false, + + IdleRPM = 700, + LimitRPM = 4000, + PeakTorque = 130.0, + PowerbandStart = 1500, + PowerbandEnd = 3500, + Turbocharged = false, + Supercharged = false, + DoNotStall = false, + + FuelFillPos = Vector(-38,50,0), + FuelType = FUELTYPE_DIESEL, + FuelTankSize = 100, + + PowerBias = 0, + + EngineSoundPreset = -1, + + snd_pitch = 1, + snd_idle = 'octoteam/vehicles/stockade_idle.wav', + + snd_low = 'octoteam/vehicles/stockade_revdown_loop.wav', + snd_low_revdown = 'octoteam/vehicles/stockade_revdown.wav', + snd_low_pitch = 1, + + snd_mid = 'octoteam/vehicles/stockade_gear_loop.wav', + snd_mid_gearup = 'octoteam/vehicles/stockade_gear.wav', + snd_mid_geardown = 'octoteam/vehicles/stockade_shiftdown.wav', + snd_mid_pitch = 1, + + snd_horn = 'octoteam/vehicles/horns/benson_horn.wav', + snd_bloweron = 'common/null.wav', + snd_bloweroff = 'octoteam/vehicles/shared/TURBO_2.wav', + snd_spool = 'octoteam/vehicles/shared/TURBO_3.wav', + snd_blowoff = 'octoteam/vehicles/shared/DUMP_VALVE.wav', + + DifferentialGear = 0.18, + Gears = {-0.4,0,0.2,0.35,0.5,0.75,1} + } +} +list.Set('simfphys_vehicles', 'sim_fphys_tlad_yankee2', V ) + +local light_table = { + L_HeadLampPos = Vector(142,42,9), + L_HeadLampAng = Angle(13,0,0), + R_HeadLampPos = Vector(142,-42,9), + R_HeadLampAng = Angle(5,0,0), + + L_RearLampPos = Vector(-181,28,-7), + L_RearLampAng = Angle(25,180,0), + R_RearLampPos = Vector(-181,-28,-7), + R_RearLampAng = Angle(25,180,0), + + Headlight_sprites = { + { + pos = Vector(142,42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + { + pos = Vector(142,-42,9), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,238,200,150), + }, + +--[[ { + pos = Vector(72,28,40), + material = 'gta4/dash_lowbeam', + size = 0.75, + color = Color(0,255,0,255), + },]] + }, + + Headlamp_sprites = { + {pos = Vector(142,42,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + {pos = Vector(142,-42,9),size = 80,material = 'octoteam/sprites/lights/gta4_corona'}, + +--[[ { + pos = Vector(72,27,40), + material = 'gta4/dash_highbeam', + size = 0.75, + color = Color(0,0,255,255), + },]] + }, + + Rearlight_sprites = { + { + pos = Vector(-181,28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + { + pos = Vector(-181,-28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 60, + color = Color(255,0,0,150), + }, + }, + Brakelight_sprites = { + { + pos = Vector(-181,28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + { + pos = Vector(-181,-28,-7), + material = 'octoteam/sprites/lights/gta4_corona', + size = 70, + color = Color(255,0,0,150), + }, + }, + + DelayOn = 0, + DelayOff = 0, + + Turnsignal_sprites = { + Left = { + { + pos = Vector(136,43,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-181,29,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(73.5,30,42.5), + material = 'gta4/dash_left', + size = 1, + color = Color(0,255,0,255), + },]] + }, + Right = { + { + pos = Vector(136,-43,23), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + { + pos = Vector(-181,-29,-13), + material = 'octoteam/sprites/lights/gta4_corona', + size = 40, + color = Color(255,135,0,150), + }, + +--[[ { + pos = Vector(73.5,20,42.5), + material = 'gta4/dash_right', + size = 1, + color = Color(0,255,0,255), + },]] + }, + }, + + SubMaterials = { + off = { + Base = { + [4] = '', + [11] = '', + }, + Brake = { + [4] = '', + [11] = 'models/gta4/vehicles/yankee/detail2_on', + }, + }, + on_lowbeam = { + Base = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [11] = 'models/gta4/vehicles/yankee/detail2_on', + }, + Brake = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [11] = 'models/gta4/vehicles/yankee/detail2_on', + }, + }, + on_highbeam = { + Base = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [11] = 'models/gta4/vehicles/yankee/detail2_on', + }, + Brake = { + [4] = 'models/gta4/vehicles/yankee/detail2_on', + [11] = 'models/gta4/vehicles/yankee/detail2_on', + }, + }, + turnsignals = { + left = { + [10] = 'models/gta4/vehicles/yankee/detail2_on' + }, + right = { + [8] = 'models/gta4/vehicles/yankee/detail2_on' + }, + }, + } +} +list.Set('simfphys_lights', 'gta4_yankee2', light_table) \ No newline at end of file diff --git a/garrysmod/addons/feature-gta4cars/lua/effects/gta4firetruk_hose.lua b/garrysmod/addons/feature-gta4cars/lua/effects/gta4firetruk_hose.lua new file mode 100644 index 0000000..1daa1bd --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/effects/gta4firetruk_hose.lua @@ -0,0 +1,77 @@ + +function EFFECT:Init(data ) + + self.Entity = data:GetEntity() + self.Origin = data:GetOrigin() + self.Attachment = data:GetAttachment() + self.Forward = data:GetNormal() + self.Scale = data:GetScale() + -- print('1') + if (!IsValid(self.Entity ) ) then return end + -- print('2') + + + self.Angle = self.Forward:Angle() + self.Position = self:GetTracerShootPos(self.Origin, self.Entity, self.Attachment ) + + -- if (self.Position == self.Origin ) then + -- local att = self.Player:GetAttachment(self.Player:LookupAttachment('anim_attachment_RH' ) ) + -- if (att ) then self.Position = att.Pos + att.Ang:Forward() * -2 end + -- end + + local teh_effect = ParticleEmitter(self.Entity:GetPos(), true ) + if (!teh_effect ) then return end + + for i = 1, 2 * self.Scale do + local particle = teh_effect:Add('effects/splash2', self.Position ) + if (particle ) then + local Spread = 0.08 + particle:SetVelocity(self.Entity:GetVelocity() + (Vector(math.sin(math.Rand(0, 360 ) ) * math.Rand(-Spread, Spread ), math.cos(math.Rand(0, 360 ) ) * math.Rand(-Spread, Spread ), math.sin(math.random() ) * math.Rand(-Spread, Spread ) ) + self.Forward ) * 800 ) + + local ang = self.Angle + if (i / 2 == math.floor(i / 2 ) ) then ang = (self.Forward * -2 ):Angle() end + particle:SetAngles(ang ) + particle:SetDieTime(4 ) + particle:SetGravity(Vector(0,0,-400) ) + particle:SetColor(230, 230, 230 ) + particle:SetStartAlpha(255 ) + particle:SetEndAlpha(0 ) + particle:SetStartSize(8 ) + particle:SetEndSize(0 ) + particle:SetCollide(1) + particle:SetCollideCallback(function(particleC, HitPos, normal ) + + particleC:SetAngleVelocity(Angle(0, 0, 0 ) ) + particleC:SetVelocity(Vector(0, 0, 0 ) ) + particleC:SetPos(HitPos + normal * 0.1 ) + particleC:SetGravity(Vector(0, 0, 0 ) ) + + for id, prop in pairs(ents.FindInSphere(HitPos, 15 ) ) do + net.Start('testwaterthiss' ) + net.WriteEntity(prop ) + net.SendToServer() + end + + local angles = normal:Angle() + angles:RotateAroundAxis(normal, particleC:GetAngles().y ) + particleC:SetAngles(angles ) + + particleC:SetLifeTime(0 ) + particleC:SetDieTime(10 ) + particleC:SetStartSize(8 ) + particleC:SetEndSize(0 ) + particleC:SetStartAlpha(128 ) + particleC:SetEndAlpha(0 ) + end ) + end + end + + teh_effect:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-gta4cars/lua/simfphys_weapons/gta4firetruk_cannon.lua b/garrysmod/addons/feature-gta4cars/lua/simfphys_weapons/gta4firetruk_cannon.lua new file mode 100644 index 0000000..d609c41 --- /dev/null +++ b/garrysmod/addons/feature-gta4cars/lua/simfphys_weapons/gta4firetruk_cannon.lua @@ -0,0 +1,141 @@ +-- this script is called SEVERSIDE ONLY. + +function simfphys.weapon:ValidClasses() + + local classes = { + --'sim_fphys_jeep', + 'sim_fphys_gta4_firetruk', -- all classes listed in this table will be using this weapon + } + + return classes +end + +function simfphys.weapon:Initialize(vehicle ) -- 'vehicle' is the 'gmod_sent_vehicle_fphysics_base' entity. + -- this function is called once the weapon is initialized + local pod = vehicle:GetDriverSeat() + simfphys.RegisterCamera(pod, Vector(0,-10,6), Vector(22,60,40)) + -- print('shit') +end + +function simfphys.weapon:AimWeapon(ply, vehicle, pod ) + local Aimang = ply:EyeAngles() + local AimRate = 300 + + local Angles = vehicle:WorldToLocalAngles(Aimang ) // - Angle(0,0,0) + + vehicle.sm_pp_yaw = vehicle.sm_pp_yaw and math.ApproachAngle(vehicle.sm_pp_yaw, Angles.y, AimRate * FrameTime() ) or 0 + vehicle.sm_pp_pitch = vehicle.sm_pp_pitch and math.ApproachAngle(vehicle.sm_pp_pitch, Angles.p, AimRate * FrameTime() ) or 0 + + local TargetAng = Angle(vehicle.sm_pp_pitch,vehicle.sm_pp_yaw,0) + TargetAng:Normalize() + + vehicle:SetPoseParameter('cannon_yaw', TargetAng.y ) + vehicle:SetPoseParameter('cannon_pitch', -TargetAng.p+5 ) +end + +function simfphys.weapon:Think(vehicle ) + -- this function is called on tick + + local ply = vehicle:GetDriver() + if not IsValid(ply ) then return end + + local fire = ply:KeyDown(IN_ATTACK ) + local fire2 = ply:KeyDown(IN_ATTACK2 ) + local weaponselect = vehicle.SelectedWeapon or 1 + + + local Angles = self:AimWeapon(ply, vehicle, pod ) + + if fire2 then + self:SecondaryAttack(vehicle, ply, Angles ) + end + + if vehicle.OldFire2 ~= fire2 then + vehicle.OldFire2 = fire2 + if fire2 and weaponselect == 1 then + self:SetNextSecondaryFire(vehicle, CurTime() + 0.3 ) + vehicle.wpn = CreateSound(vehicle, 'octoteam/vehicles/firetruk_spray.wav' ) + vehicle.wpn:PlayEx(0,0) + vehicle.wpn:ChangePitch(100, 0.2 ) + vehicle.wpn:ChangeVolume(0.8, 0.5 ) + vehicle:CallOnRemove('stop_fire2_sounds', function(vehicle ) + if vehicle.wpn then + vehicle.wpn:Stop() + end + end) + else + if vehicle.wpn then + vehicle.wpn:Stop() + vehicle.wpn = nil + end + end + end + +end + + +function simfphys.weapon:SecondaryAttack(vehicle, ply ) + -- if not self:CanSecondaryAttack(vehicle ) then return end + + vehicle.wOldPos = vehicle.wOldPos or vehicle:GetPos() + local deltapos = vehicle:GetPos() - vehicle.wOldPos + vehicle.wOldPos = vehicle:GetPos() + + + local AttachmentID = vehicle:LookupAttachment('cannon.muzzle' ) + local Attachment = vehicle:GetAttachment(AttachmentID ) + + local shootOrigin = Attachment.Pos + deltapos * engine.TickInterval() + local shootDirection = Attachment.Ang:Up() + + local effectdata = EffectData() + effectdata:SetAttachment(AttachmentID ) + effectdata:SetEntity(vehicle ) + effectdata:SetOrigin(shootOrigin ) + effectdata:SetNormal(shootDirection ) + effectdata:SetScale(4 ) + util.Effect('gta4firetruk_hose', effectdata ) + self:SetNextSecondaryFire(vehicle, 0.5 ) +end +util.AddNetworkString('testwaterthiss' ) +net.Receive('testwaterthiss', function(len, pl ) + + local prop = net.ReadEntity() + + if !IsValid(prop) then return end + + if (math.random(0, 1000 ) > 800 ) then + local retval = hook.Call('ExtinguisherDoExtinguish', nil, prop ) + if (retval == false ) then return end + + if (prop:IsOnFire() ) then prop:Extinguish() end + + local class = prop:GetClass() + if (string.find(class, 'ent_minecraft_torch' ) and prop:GetWorking() ) then + prop:SetWorking(false ) + elseif (string.find(class, 'env_fire' ) ) then -- Gas Can support. Should work in ravenholm too. + prop:Fire('Extinguish' ) + end + end + -- print('shisihsih') +end ) + + + +function simfphys.weapon:CanPrimaryAttack(vehicle ) + vehicle.NextShoot = vehicle.NextShoot or 0 + return vehicle.NextShoot < CurTime() +end + +function simfphys.weapon:SetNextPrimaryFire(vehicle, time ) + vehicle.NextShoot = time +end + +function simfphys.weapon:CanSecondaryAttack(vehicle ) + vehicle.NextShoot2 = vehicle.NextShoot2 or 0 + return vehicle.NextShoot2 < CurTime() +end + +function simfphys.weapon:SetNextSecondaryFire(vehicle, time ) + vehicle.NextShoot2 = time +end \ No newline at end of file diff --git a/garrysmod/addons/feature-jobs/lua/autorun/zzz_dbg-jobs.lua b/garrysmod/addons/feature-jobs/lua/autorun/zzz_dbg-jobs.lua new file mode 100644 index 0000000..fc533f5 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/autorun/zzz_dbg-jobs.lua @@ -0,0 +1,12 @@ +dbgJobs = dbgJobs or {} + +octolib.shared('dbg-jobs/sh_hooks') + +octolib.server('dbg-jobs/sv_jobs') +octolib.server('dbg-jobs/sv_network') + +octolib.client('dbg-jobs/vgui_main') +octolib.client('dbg-jobs/vgui_job_button') +octolib.client('dbg-jobs/vgui_job_overlay') +octolib.client('dbg-jobs/cl_jobs') +octolib.client('dbg-jobs/cl_editor') diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/cl_editor.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/cl_editor.lua new file mode 100644 index 0000000..fbbd18f --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/cl_editor.lua @@ -0,0 +1,95 @@ +concommand.Add('dbg_jobs_editor', function() + netstream.Start('dbg-jobs.editMap') +end) + +local icon = octolib.icons.silk16('location_pin', 'smooth mips') +local pointTypes = { + pack = { + name = 'Точки доставки груза', + model = 'models/props_junk/wood_crate002a.mdl', + serialize = function(data) return { data.pos, data.ang } end, + deserialize = function(data) return { pos = data[1], ang = data[2] } end, + }, + home = { + name = 'Точки доставки на дом', + model = 'models/hunter/blocks/cube075x075x075.mdl', + serialize = function(data) return data.pos end, + deserialize = function(data) return { pos = data } end, + }, +} + +netstream.Hook('dbg-jobs.editMap', function(data) + local showIcons = {} + hook.Add('HUDPaint', 'dbg-jobs.editMap', function() + if not octolib.flyEditor.active then + return hook.Remove('HUDPaint', 'dbg-jobs.editMap') + end + + for _, movable in pairs(octolib.flyEditor.movables) do + local parentCsEnt = movable.csent:GetParent() + local parent = parentCsEnt and parentCsEnt.movable and parentCsEnt.movable.id + if parent and showIcons[parent] then + local pos = movable.csent:GetPos():ToScreen() + surface.SetMaterial(icon) + surface.SetDrawColor(255,255,255, 255) + surface.DrawTexturedRect(pos.x - 8, pos.y - 8, 16, 16) + end + end + end) + + local props = octolib.table.map(pointTypes, function(v) + return { + name = v.name, + model = v.model, + } + end) + + octolib.flyEditor.start({ + props = props, + noclip = true, + maxDist = 0, + buttons = { + {'Отображать сквозь карту', octolib.icons.silk32('eye'), function() + octolib.menu( + octolib.table.mapSequential(pointTypes, function(v, k) + return {v.name, showIcons[k] and octolib.icons.silk16('tick') or octolib.icons.silk16('cross'), function() + showIcons[k] = not showIcons[k] + end} + end) + ):Open() + end} + }, + canCreate = octolib.table.mapSequential(pointTypes, function(v, k) + return {v.name, { + model = v.model, + parent = k, + name = 'Новая точка', + }} + end), + }, function(movables) + local toSend = {} + for _, movable in pairs(movables) do + local typeID = movable.parent + local typeData = pointTypes[typeID] + if typeData then + local t = toSend[typeID] or {} + t[#t + 1] = typeData.serialize(movable) + toSend[typeID] = t + end + end + + netstream.Heavy('dbg-jobs.editMap', toSend) + end) + + timer.Simple(0, function() + for key, typeData in pairs(pointTypes) do + for _, item in ipairs(data[key] or {}) do + octolib.flyEditor.addMovable(table.Merge({ + parent = key, + name = 'Точка', + model = typeData.model, + }, typeData.deserialize(item))) + end + end + end) +end) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/cl_jobs.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/cl_jobs.lua new file mode 100644 index 0000000..1e43601 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/cl_jobs.lua @@ -0,0 +1,27 @@ +surface.CreateFont('dbg-jobs.title', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +hook.Add('octogui.f4-tabs', 'dbg-jobs', function() + + octogui.addToF4({ + order = 12.3, + id = 'jobs', + name = 'Задания', + icon = Material('octoteam/icons/case_brief.png'), + build = function(f) + f:SetSize(400, 600) + f:DockPadding(0, 19, 0, 0) + + local jobs = f:Add 'dbg_jobs_main' + jobs:Dock(FILL) + end, + show = function(f, st) + netstream.Start('dbg-jobs.subscribe', st) + end, + }) + +end) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/craft.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/craft.lua new file mode 100644 index 0000000..888e51c --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/craft.lua @@ -0,0 +1,103 @@ +octolib.server('craft_jewelry') + +local jewelryItems = { + { 4500, 'Золотое кольцо с гравировкой' }, + { 20000, 'Золотое кольцо с алмазом' }, + { 15000, 'Золотое кольцо с рубином' }, + { 17000, 'Золотое кольцо с сапфиром' }, + { 15000, 'Серебряное кольцо с сапфиром' }, + { 50000, 'Золотой браслет с рубинами' }, + { 43000, 'Золотое ожерелье с рубинами' }, + { 45000, 'Серьги с алмазом и жемчужиной' }, +} + +local orderTypes = { + { + name = 'Покупка украшений', + icon = octolib.icons.silk32('diamond'), + getOrderData = function() + local totalMoney = 0 + local queries = {} + local amount = math.random(1, 3) + for _, item in RandomPairs(jewelryItems) do + local price, name = unpack(item) + totalMoney = totalMoney + price + queries[#queries + 1] = { class = 'souvenir', name = name } + + if #queries >= amount then break end + end + + return { + timeout = octolib.time.toSeconds(30, 'minutes'), + reward = DarkRP.formatMoney(totalMoney), + money = totalMoney, + text = table.concat(octolib.table.mapSequential(queries, function(item) + return '— ' .. item.name + end), '\n'), + volume = 20, + queries = queries, + } + end, + }, +} + +local job = {} +job.chance = 0.2 + +function job.publish() + local orderType = table.Random(orderTypes) + if not orderType then return end + + local orderData = orderType.getOrderData() + if not orderData or #orderData.queries < 1 then return end + + return { + name = orderType.name, + icon = orderType.icon, + desc = 'Требуется доставить к почтовому ящику:\n' .. orderData.text, + reward = orderData.reward, + deposit = math.floor(orderData.money / 40) * 10, + timeout = orderData.timeout, + + money = orderData.money, + queries = orderData.queries, + } +end + +function job.start(ply, publishData) + octoinv.expectShipment(ply, publishData.id, function(cont) + local items = cont:FindItemsByQueryList(publishData.queries) + if not items then return end + + for _, item in ipairs(items) do + item:Remove() + end + + dbgJobs.finishJob(publishData.id, true) + return true + end) + + return {} +end + +function job.finish(startData, isSuccessful) + local ply = startData.ply + if not IsValid(ply) then return end + + octoinv.removeShipment(ply, startData.publishData.id) + ply:ClearMarkers('job:' .. startData.publishData.id) + + if isSuccessful then + local totalReward = startData.publishData.money + startData.publishData.deposit + + ply:Notify(('Задание "%s" выполнено! На твой счет перечислено %s'):format( + startData.publishData.name, + DarkRP.formatMoney(totalReward) + )) + ply:BankAdd(totalReward) + else + ply:Notify('warning', ('Задание "%s" провалено'):format(startData.publishData.name)) + end +end + +dbgJobs.registerType('craft', job) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/craft_jewelry.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/craft_jewelry.lua new file mode 100644 index 0000000..51263df --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/craft_jewelry.lua @@ -0,0 +1,266 @@ +local function createItem(id, name, icon, mass, volume) + octoinv.registerItem(id, { + name = name, + icon = icon, + mass = mass, + volume = volume, + }) +end + +createItem('jewelry_chain_gold', 'Золотая цепь', octolib.icons.color('chain_gold'), 0.1, 0.1) -- 1100Р +createItem('jewelry_chain_silver', 'Серебряная цепь', octolib.icons.color('chain_silver'), 0.1, 0.1) -- 700P +createItem('jewelry_ring_gold', 'Золотое кольцо', octolib.icons.color('ring_gold'), 0.1, 0.1) -- 1100P +createItem('jewelry_ring_silver', 'Серебряное кольцо', octolib.icons.color('ring_silver'), 0.1, 0.1) -- 700P +createItem('jewelry_pearl', 'Жемчужина', octolib.icons.color('bubble'), 0.05, 0.05) +createItem('jewelry_diamond', 'Алмаз', octolib.icons.color('diamond'), 0.05, 0.05) +createItem('jewelry_ruby', 'Рубин', octolib.icons.color('ruby'), 0.05, 0.05) +createItem('jewelry_topaz', 'Топаз', octolib.icons.color('topaz'), 0.05, 0.05) +createItem('jewelry_emerald', 'Изумруд', octolib.icons.color('emerald'), 0.05, 0.05) +createItem('jewelry_sapphire', 'Сапфир', octolib.icons.color('sapphire'), 0.05, 0.05) + +octoinv.addShopCat('jewelry', { name = 'Драгоценности', icon = octolib.icons.color('diamond') }) +octoinv.addShopItem('jewelry_pearl', { cat = 'jewelry', price = 4500 }) +octoinv.addShopItem('jewelry_diamond', { cat = 'jewelry', price = 15000 }) +octoinv.addShopItem('jewelry_ruby', { cat = 'jewelry', price = 11000 }) +octoinv.addShopItem('jewelry_sapphire', { cat = 'jewelry', price = 13500 }) +octoinv.addShopItem('jewelry_topaz', { cat = 'jewelry', price = 12000 }) +octoinv.addShopItem('jewelry_emerald', { cat = 'jewelry', price = 14500 }) + +octoinv.registerMarketCatFromShop('jewelry') +octoinv.registerMarketItem('jewelry_chain_gold', { parent = 'cat_jewelry' }) +octoinv.registerMarketItem('jewelry_chain_silver', { parent = 'cat_jewelry' }) +octoinv.registerMarketItem('jewelry_ring_gold', { parent = 'cat_jewelry' }) +octoinv.registerMarketItem('jewelry_ring_silver', { parent = 'cat_jewelry' }) + +-- +-- WORKBENCH +-- + +local benchSounds = { + 'physics/metal/metal_box_strain1.wav', + 'physics/metal/metal_box_strain2.wav', + 'physics/metal/metal_box_strain3.wav', + 'physics/metal/metal_box_strain4.wav', + 'physics/metal/metal_canister_impact_soft1.wav', + 'physics/metal/metal_canister_impact_soft2.wav', + 'physics/metal/metal_canister_impact_soft3.wav', +} + +octoinv.registerCraft('jewelry_ring', { + name = 'Золотое кольцо с гравировкой', + icon = octolib.icons.color('one_ring'), + conts = {'workbench'}, + sound = benchSounds, + time = 15, + tools = { + {'tool_caliper', 1}, + {'tool_hammer', 1}, + }, + ings = { + {'jewelry_ring_gold', 2}, + {'craft_nail', 1}, + }, + finish = { + {'souvenir', { + name = 'Золотое кольцо с гравировкой', + icon = octolib.icons.color('one_ring'), + mass = 0.15, + volume = 0.1, + }}, + } +}) + +octoinv.registerCraft('jewelry_ring_diamond', { + name = 'Золотое кольцо с алмазом', + icon = octolib.icons.color('diamond_ring'), + conts = {'workbench'}, + sound = benchSounds, + time = 15, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_ring_gold', 1}, + {'jewelry_diamond', 1}, + }, + finish = { + {'souvenir', { + name = 'Золотое кольцо с алмазом', + icon = octolib.icons.color('diamond_ring'), + mass = 0.15, + volume = 0.1, + }}, + } +}) + +octoinv.registerCraft('jewelry_ring_ruby', { + name = 'Золотое кольцо с рубином', + icon = octolib.icons.color('ring_ruby'), + conts = {'workbench'}, + sound = benchSounds, + time = 15, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_ring_gold', 1}, + {'jewelry_ruby', 1}, + }, + finish = { + {'souvenir', { + name = 'Золотое кольцо с рубином', + icon = octolib.icons.color('ring_ruby'), + mass = 0.15, + volume = 0.1, + }}, + } +}) + +octoinv.registerCraft('jewelry_ring_sapphire', { + name = 'Золотое кольцо с сапфиром', + icon = octolib.icons.color('ring_sapphire'), + conts = {'workbench'}, + sound = benchSounds, + time = 15, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_ring_gold', 1}, + {'jewelry_sapphire', 1}, + }, + finish = { + {'souvenir', { + name = 'Золотое кольцо с сапфиром', + icon = octolib.icons.color('ring_sapphire'), + mass = 0.15, + volume = 0.1, + }}, + } +}) + +octoinv.registerCraft('jewelry_ring_sapphire_silver', { + name = 'Серебряное кольцо с сапфиром', + icon = octolib.icons.color('ring_sapphire'), + conts = {'workbench'}, + sound = benchSounds, + time = 15, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_ring_silver', 1}, + {'jewelry_sapphire', 1}, + }, + finish = { + {'souvenir', { + name = 'Серебряное кольцо с сапфиром', + icon = octolib.icons.color('ring_sapphire'), + mass = 0.15, + volume = 0.1, + }}, + } +}) + +octoinv.registerCraft('jewelry_bracelet', { + name = 'Золотой браслет с рубинами', + icon = octolib.icons.color('bracelet'), + conts = {'workbench'}, + sound = benchSounds, + time = 15, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_chain_gold', 2}, + {'jewelry_ruby', 4}, + }, + finish = { + {'souvenir', { + name = 'Золотой браслет с рубинами', + icon = octolib.icons.color('bracelet'), + mass = 0.2, + volume = 0.15, + }}, + } +}) + +octoinv.registerCraft('jewelry_necklace', { + name = 'Золотое ожерелье с рубинами', + icon = octolib.icons.color('necklace'), + conts = {'workbench'}, + sound = benchSounds, + time = 20, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_chain_gold', 2}, + {'jewelry_ring_gold', 3}, + {'jewelry_ruby', 3}, + }, + finish = { + {'souvenir', { + name = 'Золотое ожерелье с рубинами', + icon = octolib.icons.color('necklace'), + mass = 0.4, + volume = 0.3, + }}, + } +}) + +octoinv.registerCraft('jewelry_earrings', { + name = 'Серьги с алмазом и жемчужиной', + icon = octolib.icons.color('earrings'), + conts = {'workbench'}, + sound = benchSounds, + time = 20, + tools = { + {'tool_caliper', 1}, + }, + ings = { + {'jewelry_ring_silver', 2}, + {'jewelry_pearl', 2}, + {'jewelry_diamond', 2}, + }, + finish = { + {'souvenir', { + name = 'Серьги с алмазом и жемчужиной', + icon = octolib.icons.color('earrings'), + mass = 0.4, + volume = 0.3, + }}, + } +}) + +-- +-- MACHINE +-- +createItem('jewelry_bp_chain', 'Цепь', octolib.icons.color('microsd'), 0.02, 0.02) +createItem('jewelry_bp_ring', 'Кольцо', octolib.icons.color('microsd'), 0.02, 0.02) +octoinv.addShopItem('jewelry_bp_chain', { cat = 'machinebp', price = 15 }) +octoinv.addShopItem('jewelry_bp_ring', { cat = 'machinebp', price = 15 }) + +octoinv.registerProcess('machine', { + time = 15, + ins = {machine = {{'ingot_gold', 1}}, machine_tray = {{'jewelry_bp_chain', 1}}}, + out = {machine = {{'jewelry_chain_gold', 5}}}, +}, 'jewelry_chain_gold') + +octoinv.registerProcess('machine', { + time = 15, + ins = {machine = {{'ingot_silver', 1}}, machine_tray = {{'jewelry_bp_chain', 1}}}, + out = {machine = {{'jewelry_chain_silver', 5}}}, +}, 'jewelry_chain_silver') + +octoinv.registerProcess('machine', { + time = 15, + ins = {machine = {{'ingot_gold', 1}}, machine_tray = {{'jewelry_bp_ring', 1}}}, + out = {machine = {{'jewelry_ring_gold', 5}}}, +}, 'jewelry_ring_gold') + +octoinv.registerProcess('machine', { + time = 15, + ins = {machine = {{'ingot_silver', 1}}, machine_tray = {{'jewelry_bp_ring', 1}}}, + out = {machine = {{'jewelry_ring_silver', 5}}}, +}, 'jewelry_ring_silver') diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/home.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/home.lua new file mode 100644 index 0000000..2d61e11 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/home.lua @@ -0,0 +1,227 @@ +local foodRequests = { + { 700, L.omelet }, + { 800, L.hotdog }, + { 950, L.ceasar }, + { 1280, L.mushroom_soup }, + { 1350, L.bolognese }, + { 1500, L.zitti }, + { 1550, L.wrap }, + { 1750, L.borsch }, + { 1900, L.pizza_pepperoni }, + { 1900, L.pizza_margarita }, + { 2250, L.pizza_volcano }, + { 2350, L.lasagna }, + { 3200, 'Запеченный окунь' }, + { 3200, 'Запеченный карп' }, + { 3200, 'Форель под медом' }, + { 3200, 'Щука в сливочном соусе' }, + + { 700, L.tea }, + { 700, L.cacao }, + { 800, L.cappuccino }, + { 900, L.espresso }, +} + +local function getBoxData(volume) + if volume <= 10 then + return 10, 'models/props/cs_office/cardboard_box02.mdl' + elseif volume <= 50 then + return 50, 'models/props/cs_office/cardboard_box03.mdl' + else + return math.ceil(volume / 10) * 10, 'models/props/cs_office/cardboard_box01.mdl' + end +end + +local orderTypes = { + { + -- name = 'Продукты на дом', + -- icon = octolib.icons.silk32('apple'), + -- getOrderData = function() + -- if not foodShopItems then + -- foodShopItems = octolib.table.map(octoinv.shopItems, function(item) + -- if item.cat == 'ings' then return item end + -- end) + -- end + + -- local totalPrice = 0 + -- local totalVolume = 0 + -- local items = {} + -- local kindsAmount = math.random(2, 10) + -- for _ = 1, kindsAmount do + -- local itemData, itemID = table.Random(foodShopItems) + -- local amount = math.random(1, 10) + -- items[itemID] = (items[itemID] or 0) + amount + -- totalPrice = totalPrice + itemData.price * amount + -- totalVolume = totalVolume + octoinv.getItemData('volume', itemID) * amount + -- end + + -- local bonusMoney = math.ceil(totalPrice * 0.05) * 10 -- 50% rounded up to 10s + + -- return { + -- timeout = octolib.time.toSeconds(15, 'minutes'), + -- reward = DarkRP.formatMoney(bonusMoney), + -- money = totalPrice + bonusMoney, + -- text = table.concat(octolib.table.mapSequential(items, function(amount, itemID) + -- return '— ' .. amount .. 'х ' .. octoinv.getItemData('name', itemID) + -- end), '\n') .. '\nСтоимость продуктов будет компенсирована', + -- volume = totalVolume, + -- queries = octolib.table.mapSequential(items, function(amount, itemID) + -- return { class = itemID, amount = { _gte = amount }} + -- end), + -- } + -- end, + -- }, { + name = 'Доставка еды', + icon = octolib.icons.silk32('hamburger'), + getOrderData = function() + local totalMoney = 0 + local queries = {} + local amount = math.random(1, 4) + for _, item in RandomPairs(foodRequests) do + local price, name = unpack(item) + totalMoney = totalMoney + price + queries[#queries + 1] = { class = 'food', name = name, part = { _exists = false }} + + if #queries >= amount then break end + end + + return { + timeout = octolib.time.toSeconds(20, 'minutes'), + reward = DarkRP.formatMoney(totalMoney), + money = totalMoney, + text = table.concat(octolib.table.mapSequential(queries, function(item) + return '— ' .. item.name + end), '\n'), + volume = 20, + queries = queries, + } + end, + }, +} + +local job = {} + +function job.publish() + local orderType = table.Random(orderTypes) + if not orderType then return end + + local orderData = orderType.getOrderData() + if not orderData or #orderData.queries < 1 then return end + + local mapConfig = dbgJobs.mapConfig and dbgJobs.mapConfig.home + if not mapConfig then return end + + local existingContainersPositions = octolib.table.mapSequential(ents.FindByClass('dbg_jobs_container'), function(ent) + return ent:GetPos() + end) + + local spawnPos + for _, pos in RandomPairs(mapConfig) do + -- we don't want any other containers near spawn + local ok = true + for _, theirPos in ipairs(existingContainersPositions) do + if theirPos:DistToSqr(pos) < 40000 then + ok = false + break + end + end + + if ok then + spawnPos = pos + break + end + end + if not spawnPos then return end + + local volume, model = getBoxData(orderData.volume) + local cont = ents.Create 'dbg_jobs_container' + cont:SetModel(model) + cont:SetPos(spawnPos) + cont:SetAngles(Angle(0, math.random() * 360, 0)) + cont:Spawn() + cont:Activate() + + local phys = cont:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + timer.Simple(3, function() + if not IsValid(phys) then return end + phys:EnableMotion(false) + end) + end + + local estateTo = dbgEstates.getNearest(spawnPos) + local addressTo = estateTo and estateTo.name or '???' + + local desc = ('Требуется доставить к %s:\n%s'):format(addressTo, orderData.text) + + return { + name = orderType.name, + icon = orderType.icon, + desc = desc, + reward = orderData.reward, + deposit = math.floor(orderData.money / 40) * 10, + timeout = orderData.timeout, + + money = orderData.money, + cont = cont, + volume = volume, + queries = orderData.queries, + } +end + +function job.cancel(publishData) + local cont = publishData.cont + if IsValid(cont) then cont:Remove() end +end + +function job.start(ply, publishData) + if not IsValid(publishData.cont) then + ply:Notify('Что-то случилось с контейнером, задание отменено') + dbgJobs.removeAvailable(publishData.id, true) + return + end + + local cont = publishData.cont + ply:AddMarker({ + id = 'job:' .. publishData.id, + txt = 'Получатель', + pos = cont:WorldSpaceCenter(), + col = Color(255,92,38), + des = {'time', { octolib.time.toSeconds(2, 'hours') }}, + icon = octolib.icons.silk16('arrow_down'), + }) + cont:SetDeliveryData(publishData.id, publishData.volume, function(cont) + for _, query in ipairs(publishData.queries) do + if not cont:FindItem(query) then return end + end + + return true + end) + + return {} +end + +function job.finish(startData, isSuccessful) + local cont = startData.publishData.cont + if IsValid(cont) then cont:Remove() end + + local ply = startData.ply + if not IsValid(ply) then return end + + ply:ClearMarkers('job:' .. startData.publishData.id) + + if isSuccessful then + local totalReward = startData.publishData.money + startData.publishData.deposit + + ply:Notify(('Задание "%s" выполнено! На твой счет перечислено %s'):format( + startData.publishData.name, + DarkRP.formatMoney(totalReward) + )) + ply:BankAdd(totalReward) + else + ply:Notify('warning', ('Задание "%s" провалено'):format(startData.publishData.name)) + end +end + +dbgJobs.registerType('home', job) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/pack.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/pack.lua new file mode 100644 index 0000000..6e8bacb --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/jobs/pack.lua @@ -0,0 +1,214 @@ +local packages = { + { + text = 'коробку', + money = 1000, + variants = { + { model = 'models/physrbox/cardboard_taped.mdl', mass = 15 }, + { model = 'models/physrbox/taped_big.mdl', mass = 15 }, + { model = 'models/physrbox/taped_medium.mdl', mass = 10 }, + { model = 'models/physrbox/taped_white.mdl', mass = 10 }, + }, + }, { + text = 'мешок', + money = 2500, + variants = { + { model = 'models/props_shops/prop_bakery_floursack_01_a.mdl', mass = 40 }, + { model = 'models/props_shops/prop_bakery_floursack_02_a.mdl', mass = 40 }, + { model = 'models/props_shops/prop_bakery_floursack_02_b.mdl', mass = 35 }, + { model = 'models/props_shops/prop_bakery_floursack_02_c.mdl', mass = 30 }, + }, + }, { + text = 'телевизор', + money = 2500, + variants = { + { model = 'models/gmod_tower/suitetv.mdl', mass = 50 }, + { model = 'models/mark2580/gtav/mp_apa_low/lobby/tv_03.mdl', mass = 40 }, + { model = 'models/sal/ammunation/tv2.mdl', mass = 40 }, + { model = 'models/sal/ammunation/tv.mdl', mass = 35 }, + }, + }, { + text = 'кресло', + money = 3500, + variants = { + { model = 'models/mark2580/gtav/mp_apa_mid/hall/v_res_mp_stripchair_high.mdl', mass = 80 }, + { model = 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_stn_chairarm_23_high.mdl', mass = 75 }, + { model = 'models/props_interiors/sofa_chair02.mdl', mass = 75 }, + { model = 'models/Highrise/lobby_chair_01.mdl', mass = 100 }, + }, + }, { + text = 'шкаф', + money = 5000, + variants = { + { model = 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_storageunit_high.mdl', mass = 125 }, + { model = 'models/mark2580/gtav/mp_apa_low/bedroom/brm_cabinet.mdl', mass = 120 }, + { model = 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_sideboardm02_high.mdl', mass = 140 }, + { model = 'models/props_c17/furnituredresser001a.mdl', mass = 155 }, + { model = 'models/props/interior/dresser_wardrobe01.mdl', mass = 170 }, + }, + }, { + text = 'диван', + money = 6000, + variants = { + { model = 'models/mark2580/gtav/tequilala_map/basement/club_officesofa.mdl', mass = 150 }, + { model = 'models/mark2580/gtav/mp_apa_low/lobby/sofa1.mdl', mass = 160 }, + { model = 'models/props_interiors/sofa01.mdl', mass = 175 }, + { model = 'models/props_interiors/sofa02.mdl', mass = 170 }, + { model = 'models/props_c17/furniturecouch002a.mdl', mass = 140 }, + { model = 'models/sims/gm_sofa.mdl', mass = 135 }, + }, + }, +} + +local job = {} + +function job.publish() + local packageData = table.Random(packages) + if not packageData then return end + + local modelData = table.Random(packageData.variants) + if not modelData then return end + + local mapConfig = dbgJobs.mapConfig and dbgJobs.mapConfig.pack + if not mapConfig then return end + + local existingPackagesPositions = octolib.table.mapSequential(ents.FindByClass('dbg_jobs_package'), function(ent) + return ent:GetPos() + end) + + local spawnData, targetPos + for _, place in RandomPairs(mapConfig) do + if not targetPos then + targetPos = place[1] + continue + end + + -- we don't want any other packages near spawn + local pos = unpack(place) + local ok = true + for _, theirPos in ipairs(existingPackagesPositions) do + if theirPos:DistToSqr(pos) < 90000 then + ok = false + break + end + end + + if ok then + spawnData = place + break + end + end + if not spawnData then return end + + targetPos = util.TraceLine({ + start = targetPos, + endpos = targetPos + Vector(0, 0, -300), + collisiongroup = COLLISION_GROUP_WORLD, + }).HitPos or targetPos + + local prop = ents.Create 'dbg_jobs_package' + prop:SetModel(modelData.model) + prop:SetSkin(modelData.skin or 0) + prop:SetBodyGroups(modelData.bodygroups or '') + prop:Spawn() + prop:Activate() + prop:SetPos(spawnData[1] + Vector(0, 0, -prop:OBBMins().z)) + prop:SetAngles(spawnData[2]) + + local phys = prop:GetPhysicsObject() + if IsValid(phys) then + phys:SetMass(modelData.mass * 3) + phys:Wake() + end + + local estateFrom = dbgEstates.getNearest(spawnData[1]) + local addressFrom = estateFrom and estateFrom.name or '???' + local estateTo = dbgEstates.getNearest(targetPos) + local addressTo = estateTo and estateTo.name or '???' + + local desc = ('Требуется доставить %s массой %d кг\n%s → %s'):format(packageData.text, modelData.mass, addressFrom, addressTo) + desc = desc .. ('\nОбъем груза: %dл'):format(prop:GetVolumeLiters()) + if modelData.mass > 100 then + desc = desc .. '\nРекомендуется использовать грузовой автомобиль' + end + + local money = packageData.money + -- from -25% to +25% based on distance + + math.Round(packageData.money / 40 * (math.Clamp(spawnData[1]:Distance(targetPos), 0, 10000) / 10000 - 0.5)) * 10 + -- from -10% to +10% as a random bonus + + math.floor(packageData.money / 100) * math.random(-10, 10) + + return { + name = ('Перевозка груза (%s кг)'):format(modelData.mass), + icon = octolib.icons.silk32('lorry_go'), + desc = desc, + reward = DarkRP.formatMoney(money), + deposit = math.floor(money / 40) * 10, + timeout = octolib.time.toSeconds(10, 'minutes'), + + money = money, + prop = prop, + targetPos = targetPos, + } +end + +function job.cancel(publishData) + local prop = publishData.prop + if IsValid(prop) then prop:Remove() end +end + +function job.start(ply, publishData) + if not IsValid(publishData.prop) then + ply:Notify('Что-то случилось с грузом, задание отменено') + dbgJobs.removeAvailable(publishData.id, true) + return + end + + local markerID = 'job:' .. publishData.id + local prop = publishData.prop + ply:AddMarker({ + id = markerID .. '.1', + txt = 'Груз', + pos = prop:WorldSpaceCenter(), + col = Color(255,92,38), + des = {'dist', { 100 }}, + icon = octolib.icons.silk16('box_closed'), + }) + + local targetPos = publishData.targetPos + ply:AddMarker({ + id = markerID .. '.2', + txt = 'Получатель', + pos = targetPos, + col = Color(255,92,38), + des = {'time', { octolib.time.toSeconds(2, 'hours') }}, + icon = octolib.icons.silk16('arrow_down'), + }) + prop:SetDeliveryData(publishData.id, ply, targetPos) + + return {} +end + +function job.finish(startData, isSuccessful) + local prop = startData.publishData.prop + if IsValid(prop) then prop:Remove() end + + local ply = startData.ply + if not IsValid(ply) then return end + + ply:ClearMarkers('job:' .. startData.publishData.id .. '.1') + ply:ClearMarkers('job:' .. startData.publishData.id .. '.2') + + if isSuccessful then + local totalReward = startData.publishData.money + startData.publishData.deposit + + ply:Notify(('Задание "%s" выполнено! На твой счет перечислено %s'):format( + startData.publishData.name, + DarkRP.formatMoney(totalReward) + )) + ply:BankAdd(totalReward) + else + ply:Notify('warning', ('Задание "%s" провалено'):format(startData.publishData.name)) + end +end + +dbgJobs.registerType('pack', job) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/sh_hooks.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/sh_hooks.lua new file mode 100644 index 0000000..52f0392 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/sh_hooks.lua @@ -0,0 +1,21 @@ +hook.Add('car-dealer.populateTags', 'dbg-jobs.trucks', function(id, data, tags) + if list.Get('simfphys_vehicles')[data.simfphysID].Members.CanAttachPackages then + tags[#tags + 1] = carDealer.tags.truck + end +end) + +local cookForbidden = octolib.array.toKeys({ 'Доставка еды' }) + +hook.Add('dbg-jobs.canTake', 'dbg-jobs.restrict', function(ply, publishData) + if ply:IsGhost() then + return false, 'Мертвые не могут этого делать' + end + + if ply:GetNetVar('dbg-police.job', '') ~= '' then + return false, 'Полицейские не могут принимать заказы' + end + + if ply:isCook() and cookForbidden[publishData.name] then + return false, 'Повар готовит еду, а не доставляет' + end +end) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/sv_jobs.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/sv_jobs.lua new file mode 100644 index 0000000..6a52532 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/sv_jobs.lua @@ -0,0 +1,143 @@ +dbgJobs.registered = dbgJobs.registered or {} +dbgJobs.available = dbgJobs.available or {} +dbgJobs.active = dbgJobs.active or {} +dbgJobs.subscribers = dbgJobs.subscribers or {} + +file.CreateDir('dbg-jobs') +dbgJobs.mapConfig = util.JSONToTable(file.Read('dbg-jobs/' .. game.GetMap() .. '.json') or '{}') or {} + +local maxJobs = 10 +local addJobChance = 1 +local addJobInterval = octolib.time.toSeconds(2, 'minutes') +local jobLifeTime = octolib.time.toSeconds(30, 'minutes') + +function dbgJobs.registerType(id, jobData) + if not isfunction(jobData.publish) then error('publish is required to register a job') end + if not isfunction(jobData.start) then error('start is required to register a job') end + if not isfunction(jobData.finish) then error('finish is required to register a job') end + + dbgJobs.registered[id] = jobData +end + +function dbgJobs.syncAvailable(receivers) + netstream.Start(receivers, 'dbg-jobs.syncAvailable', dbgJobs.available) +end + +function dbgJobs.syncActive(ply) + netstream.Start(ply, 'dbg-jobs.syncActive', ply.activeJobs) +end + +function dbgJobs.getSubscribers() + return table.GetKeys(dbgJobs.subscribers) +end + +function dbgJobs.addAvailable(publishData) + if not publishData.jobType then error('jobType is required to add a job') end + + local id = octolib.string.uuid() + publishData.id = id + dbgJobs.available[id] = publishData + + netstream.Start(dbgJobs.getSubscribers(), 'dbg-jobs.addAvailable', publishData) + + timer.Create('dbg-jobs.cancel:' .. id, jobLifeTime, 1, function() + dbgJobs.removeAvailable(id, true) + end) +end + +function dbgJobs.addRandom() + local pool = octolib.table.mapSequential(dbgJobs.registered, function(job, jobType) return { job.chance or 1, jobType } end) + local jobType = octolib.array.randomWeighted(pool) + if not jobType then return end + + local jobJdata = dbgJobs.registered[jobType] + local publishData = jobJdata.publish() + if not publishData then return end + + publishData.jobType = jobType + + dbgJobs.addAvailable(publishData) +end + +function dbgJobs.removeAvailable(id, cancel) + timer.Remove('dbg-jobs.timeout:' .. id) + + local publishData = dbgJobs.available[id] + if not publishData then return end + + local jobData = dbgJobs.registered[publishData.jobType] + if not jobData then return end + + if cancel and jobData.cancel then jobData.cancel(publishData) end + dbgJobs.available[id] = nil + + netstream.Start(dbgJobs.getSubscribers(), 'dbg-jobs.removeAvailable', id) +end + +function dbgJobs.assignJob(id, ply) + local publishData = dbgJobs.available[id] + if not publishData then return end + + local jobData = dbgJobs.registered[publishData.jobType] + if not jobData then return end + + local startData = jobData.start(ply, publishData) + if not startData then return end + + startData.id = id + startData.ply = ply + startData.jobType = publishData.jobType + startData.startedAt = CurTime() + startData.publishData = publishData + + ply.activeJobs = ply.activeJobs or {} + ply.activeJobs[id] = startData + dbgJobs.active[id] = startData + dbgJobs.removeAvailable(id) + dbgJobs.syncActive(ply) + + local timeout = startData.timeout or publishData.timeout + if timeout then + timer.Create('dbg-jobs.timeout:' .. id, timeout, 1, function() + dbgJobs.finishJob(id, false) + end) + end + + ply:Notify(publishData.desc) + + return startData +end + +function dbgJobs.finishJob(id, isSuccessful) + timer.Remove('dbg-jobs.timeout:' .. id) + + local startData = dbgJobs.active[id] + if not startData then return end + + local jobData = dbgJobs.registered[startData.jobType] + if not jobData then return end + + local ply = startData.ply + jobData.finish(startData, isSuccessful) + dbgJobs.active[id] = nil + + if IsValid(ply) then + startData.ply.activeJobs[id] = nil + dbgJobs.syncActive(ply) + end +end + +function dbgJobs.saveMapConfig(data) + if data then dbgJobs.mapConfig = data end + file.Write('dbg-jobs/' .. game.GetMap() .. '.json', util.TableToJSON(dbgJobs.mapConfig)) +end + +timer.Create('dbg-jobs.tryAddAvailable', addJobInterval, 0, function() + if table.Count(dbgJobs.available) >= maxJobs or math.random() > addJobChance then return end + dbgJobs.addRandom() +end) + +local files = file.Find('dbg-jobs/jobs/*.lua', 'LUA') +for _, v in ipairs(files) do + octolib.server('dbg-jobs/jobs/' .. string.StripExtension(v)) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/sv_network.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/sv_network.lua new file mode 100644 index 0000000..cc482c9 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/sv_network.lua @@ -0,0 +1,51 @@ +netstream.Hook('dbg-jobs.subscribe', function(ply, subscribe) + dbgJobs.subscribers[ply] = subscribe and true or nil + + if subscribe then + dbgJobs.syncAvailable(ply) + dbgJobs.syncActive(ply) + end +end) + +netstream.Listen('dbg-jobs.take', function(reply, ply, jobID) + local publishData = dbgJobs.available[jobID or ''] + if not publishData then return reply('Заказ не существует. Возможно, его уже взяли') end + + if publishData.deposit and not ply:BankHas(publishData.deposit) then + return reply('У тебя не хватает денег для принятия этого заказа') + end + + local can, why = hook.Run('dbg-jobs.canTake', ply, publishData) + if can == false then + return reply(why or 'Ты не можешь принять этот заказ') + end + + local startData = dbgJobs.assignJob(jobID, ply) + if not startData then + return reply('Не получилось принять заказ') + end + + if publishData.deposit then + ply:BankAdd(-publishData.deposit) + end + + reply() +end) + +netstream.Hook('dbg-jobs.cancel', function(ply, jobID) + local startData = dbgJobs.active[jobID or ''] + if not startData or startData.ply ~= ply then return end + + dbgJobs.finishJob(jobID, false) + ply:Notify('Заказ отменен') +end) + +netstream.Hook('dbg-jobs.editMap', function(ply, data) + if not ply:IsSuperAdmin() then return end + + if data then + dbgJobs.saveMapConfig(data) + else + netstream.Heavy(ply, 'dbg-jobs.editMap', dbgJobs.mapConfig) + end +end) diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_job_button.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_job_button.lua new file mode 100644 index 0000000..e19599d --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_job_button.lua @@ -0,0 +1,80 @@ +local colHover = Color(0,0,0, 30) + +local function iconAndLabel(parent, iconPath, text, iconWidth, iconMargin) + local icon = parent:Add 'DImage' + icon:Dock(LEFT) + icon:SetWide(iconWidth or 16) + icon:DockMargin(0, 0, iconMargin or 4, 0) + icon:SetImage(iconPath) + + local label = parent:Add 'DLabel' + label:Dock(FILL) + label:SetContentAlignment(4) + label:SetText(text) + + return icon, label +end + +local PANEL = {} + +function PANEL:Init() + self:Dock(TOP) + self:SetTall(48) + + self.icon = self:Add 'DImage' + self.icon:Dock(LEFT) + self.icon:DockMargin(8, 8, 8, 8) + self.icon:SetWide(32) + + self.title = self:Add 'DLabel' + self.title:Dock(FILL) + self.title:SetFont('dbg-jobs.title') + self.title:SetText('...') + + local right = self:Add 'DPanel' + right:Dock(RIGHT) + right:SetWide(70) + right:SetPaintBackground(false) + right:SetMouseInputEnabled(false) + + self.time = right:Add 'DPanel' + self.time:Dock(TOP) + self.time:SetTall(16) + self.time:DockMargin(0, 6, 0, 4) + self.time:SetPaintBackground(false) + self.time.icon, self.time.label = iconAndLabel(self.time, 'icon16/time.png', '...') + + self.reward = right:Add 'DPanel' + self.reward:Dock(TOP) + self.reward:SetTall(16) + self.reward:SetPaintBackground(false) + self.reward.icon, self.reward.label = iconAndLabel(self.reward, 'icon16/money.png', '...') + + self:SetText('') +end + +function PANEL:SetJob(mainPanel, publishData, startData) + local time = publishData.timeout and (math.floor(publishData.timeout / 60) .. ' мин') or '???' + + self.mainPanel = mainPanel + self.publishData = publishData + self.startData = startData + self.icon:SetImage(publishData.icon) + self.title:SetText(publishData.name) + self.time.label:SetText(time) + self.reward.label:SetText(publishData.reward) +end + +function PANEL:DoClick() + local overlay = octolib.overlay(self:GetParent():GetParent():GetParent(), 'dbg_jobs_overlay') + overlay:SetJob(self.mainPanel, self.publishData, self.startData) + self.mainPanel.overlay = overlay +end + +function PANEL:Paint(w, h) + if self.Hovered then + draw.RoundedBox(4, 0, 0, w, h, colHover) + end +end + +vgui.Register('dbg_jobs_button', PANEL, 'DButton') diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_job_overlay.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_job_overlay.lua new file mode 100644 index 0000000..5e18747 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_job_overlay.lua @@ -0,0 +1,78 @@ +local PANEL = {} + +function PANEL:Init() + self:SetSize(350, 300) + self:DockPadding(5, 5, 5, 5) + + self.title = self:Add 'DLabel' + self.title:Dock(TOP) + self.title:SetTall(40) + self.title:SetContentAlignment(5) + self.title:SetFont('dbg-jobs.title') + self.title:SetText('...') + + self.desc = self:Add 'DMarkup' + self.desc:Dock(TOP) + self.desc:DockMargin(5, 0, 5, 0) + self.desc:SetText('...') + + self.accept = octolib.button(self, 'Принять заказ', function() + self:AcceptJob() + end) + self.accept:Dock(BOTTOM) + self.accept:SetTall(30) +end + +function PANEL:SetJob(mainPanel, publishData, startData) + self.mainPanel = mainPanel + self.publishData = publishData + self.startData = startData + + self.title:SetText(publishData.name) + + local desc = publishData.desc .. '\n' + if publishData.timeout then desc = desc .. '\nОграничение по времени: ' .. math.floor(publishData.timeout / 60) .. ' мин' end + if publishData.deposit then + desc = desc .. '\nЗалог за взятие заказа: ' .. DarkRP.formatMoney(publishData.deposit) + end + + if startData and startData.ply == LocalPlayer() then + self.accept:SetText('Отказаться') + elseif publishData.deposit then + self.accept:SetText(('Принять заказ (%s)'):format(DarkRP.formatMoney(publishData.deposit))) + else + self.accept:SetText('Принять заказ') + end + desc = desc .. '\nПлата за выполнение: ' .. publishData.reward + + self.desc:SetText(desc) +end + +function PANEL:AcceptJob() + if not self.publishData then return end + + if self.startData and self.startData.ply == LocalPlayer() then + Derma_Query('Ты хочешь отказаться от выполнения?\nЗалог не будет возвращен', 'Отмена заказа', 'Да', function() + netstream.Start('dbg-jobs.cancel', self.publishData.id) + self.mainPanel.overlay:Remove() + end, 'Отмена') + else + Derma_Query('Ты хочешь принять этот заказ?\nПри его невыполнении залог не будет возвращен', 'Принятие заказа', 'Да', function() + self.accept:SetEnabled(false) + self.accept:SetText('Загрузка...') + + netstream.Request('dbg-jobs.take', self.publishData.id):Then(function(err) + if not err then + self.mainPanel:SwitchToName('Принятые') + self.mainPanel.overlay:Remove() + else + Derma_Message(err, 'Ошибка', 'Понятно') + self.accept:SetEnabled(true) + self:SetJob(self.mainPanel, self.publishData, self.startData) + end + end) + end, 'Отмена') + end +end + +vgui.Register('dbg_jobs_overlay', PANEL, 'DPanel') diff --git a/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_main.lua b/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_main.lua new file mode 100644 index 0000000..7465feb --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/dbg-jobs/vgui_main.lua @@ -0,0 +1,75 @@ +local instance + +local PANEL = {} + +function PANEL:Init() + -- ALL JOBS + self.available = vgui.Create 'DPanel' + self.available:SetPaintBackground(false) + self:AddSheet('Доступные', self.available, octolib.icons.silk16('world')) + + -- MY JOBS + self.active = vgui.Create 'DPanel' + self.active:SetPaintBackground(false) + self:AddSheet('Принятые', self.active, octolib.icons.silk16('tick')) + + instance = self +end + +function PANEL:AddAvailable(publishData) + if hook.Run('dbg-jobs.canTake', LocalPlayer(), publishData) == false then return end + + local button = self.available:Add 'dbg_jobs_button' + button:SetJob(self, publishData) +end + +function PANEL:RemoveAvailable(jobID) + for _, button in ipairs(self.available:GetChildren()) do + if button.publishData.id == jobID then + button:Remove() + end + end +end + +function PANEL:SetAvailable(publishDatas) + self.available:Clear() + + for _, job in pairs(publishDatas) do + self:AddAvailable(job) + end +end + +function PANEL:AddActive(job) + local button = self.active:Add 'dbg_jobs_button' + button:SetJob(self, job.publishData, job) +end + +function PANEL:SetActive(data) + self.active:Clear() + + for _, job in pairs(data or {}) do + self:AddActive(job) + end +end + +netstream.Hook('dbg-jobs.syncAvailable', function(publishDatas) + if not IsValid(instance) then return end + instance:SetAvailable(publishDatas) +end) + +netstream.Hook('dbg-jobs.syncActive', function(jobs) + if not IsValid(instance) then return end + instance:SetActive(jobs) +end) + +netstream.Hook('dbg-jobs.addAvailable', function(publishData) + if not IsValid(instance) then return end + instance:AddAvailable(publishData) +end) + +netstream.Hook('dbg-jobs.removeAvailable', function(jobID) + if not IsValid(instance) then return end + instance:RemoveAvailable(jobID) +end) + +vgui.Register('dbg_jobs_main', PANEL, 'DPropertySheet') diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/cl_init.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/cl_init.lua new file mode 100644 index 0000000..cc74e3f --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/cl_init.lua @@ -0,0 +1,5 @@ +include('shared.lua') + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/init.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/init.lua new file mode 100644 index 0000000..e22ead0 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/init.lua @@ -0,0 +1,40 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) +end + +function ENT:Think() + if self.jobID and self.check(self.mainCont) then + dbgJobs.finishJob(self.jobID, true) + return + end + + self:NextThink(CurTime() + 2) + return true +end + +function ENT:Use(ply) + ply:OpenInventory(self.inv) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) +end + +function ENT:SetDeliveryData(jobID, volume, check) + if self.inv then + self.inv:Remove() + end + + local inv = self:CreateInventory() + self.mainCont = inv:AddContainer('main', { + icon = octolib.icons.color('box3_drop'), + name = 'Получатель', + volume = volume, + }) + + self.jobID = jobID + self.check = check +end diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/shared.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/shared.lua new file mode 100644 index 0000000..bd2b522 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_container/shared.lua @@ -0,0 +1,5 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' +ENT.PrintName = 'Контейнер доставки' +ENT.Category = 'Доброград' +ENT.Author = 'octoteam' diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/cl_init.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/cl_init.lua new file mode 100644 index 0000000..77cecd2 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/cl_init.lua @@ -0,0 +1,90 @@ +include('shared.lua') + +-- CreateMaterial('dbg_jobs_door1', 'VertexLitGeneric', { +-- ['$basetexture'] = 'models/props_wasteland/cargo_container02', +-- ['$model'] = 1, +-- }) +-- local matrix1 = Matrix() +-- matrix1:Scale(Vector(1 / 4.34, 1 / 2.19, 1)) +-- matrix1:Translate(Vector(0.015, 1.17, 0)) +-- Material('!dbg_jobs_door1'):SetMatrix('$basetexturetransform', matrix1) + +-- CreateMaterial('dbg_jobs_door2', 'VertexLitGeneric', { +-- ['$basetexture'] = 'models/props_wasteland/cargo_container02', +-- ['$model'] = 1, +-- }) +-- local matrix2 = Matrix() +-- matrix2:Scale(Vector(1 / 4.34, 1 / 2.19, 1)) +-- matrix2:Translate(Vector(0, 1.17, 0)) +-- Material('!dbg_jobs_door2'):SetMatrix('$basetexturetransform', matrix2) + +-- local positions = { +-- open = Vector(0, -190.5, 120), +-- closed = Vector(0, -190.5, 3.2), +-- } + +function ENT:Initialize() + -- self:NetworkVarNotify('Open', self.OnOpenChanged) +end + +function ENT:OnRemove() + -- self:RemoveDoors() +end + +function ENT:Draw() + self:DrawModel() + + -- if self.doorAnim then + -- local startTime, endTime, startPos, endPos = unpack(self.doorAnim) + + -- if not IsValid(self.door) then + -- self.doorAnim = nil + -- return + -- end + + -- local ct = CurTime() + -- if ct > endTime then + -- self.door:SetLocalPos(endPos) + -- self.doorAnim = nil + -- return + -- end + + -- local fraction = octolib.tween.easing.inOutQuad(math.TimeFraction(startTime, endTime, ct), 0, 1, 1) + -- self.door:SetLocalPos(LerpVector(fraction, startPos, endPos)) + -- end +end + +-- function ENT:OnOpenChanged(name, old, new) +-- print(name, old, new, IsValid(self.door)) +-- if old == new or not IsValid(self.door) then return end +-- self.doorAnim = { CurTime(), CurTime() + 5, self.door:GetLocalPos(), new and positions.open or positions.closed } +-- PrintTable(self.doorAnim) +-- end + +-- function ENT:CreateDoors() +-- if not IsValid(self.door) then self:RemoveDoors() end + +-- local door = ClientsideModel('models/hunter/plates/plate1x1.mdl') +-- self.door = door +-- door:SetParent(self) +-- door:SetLocalPos(self:GetOpen() and positions.open or positions.closed) +-- door:SetLocalAngles(Angle(90, -90, 0)) +-- door:SetSize(Vector(2.62, 1.34, 1.5)) +-- door:SetMaterial('!dbg_jobs_door1') +-- end + +-- function ENT:RemoveDoors() +-- if not IsValid(self.door) then return end +-- self.door:Remove() +-- end + +-- hook.Add('NotifyShouldTransmit', 'dbg_jobs_drop', function(ent, state) +-- if ent:GetClass() ~= 'dbg_jobs_drop' then return end +-- if state then +-- timer.Simple(0, function() +-- ent:CreateDoors() +-- end) +-- else +-- ent:RemoveDoors() +-- end +-- end) diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/init.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/init.lua new file mode 100644 index 0000000..0b75663 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/init.lua @@ -0,0 +1,29 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +function ENT:Initialize() + self:SetModel(self.Model) + self:SetSkin(self.Skin) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) +end + +function ENT:Use(ply) + self:SetOpen(not self:GetOpen()) +end + +function ENT:SetDeliveryData(jobID, check) + self.jobID = jobID + self.check = check +end + +function ENT:Think() + if self.jobID and self.check(ents.FindInSphere(self:GetPos(), 40)) then + dbgJobs.finishJob(self.jobID, true) + end + + self:NextThink(CurTime() + 2) + return true +end diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/shared.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/shared.lua new file mode 100644 index 0000000..7da7fca --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_drop/shared.lua @@ -0,0 +1,14 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' +ENT.PrintName = 'Точка приема работы' +ENT.Category = 'Доброград' +ENT.Author = 'octoteam' +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.Model = 'models/props_wasteland/cargo_container01b.mdl' +ENT.Skin = 1 + +-- function ENT:SetupDataTables() +-- self:NetworkVar('Bool', 0, 'Open') +-- end diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/cl_init.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/cl_init.lua new file mode 100644 index 0000000..ce24a23 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/cl_init.lua @@ -0,0 +1,7 @@ +include('shared.lua') + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/init.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/init.lua new file mode 100644 index 0000000..d2a9663 --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/init.lua @@ -0,0 +1,106 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) +end + +function ENT:AttachToCar(car, cont) + car.package, self.car = self, car + cont.volume = cont.volume - self:GetVolumeLiters() + cont:ResetSpaceMass() + cont:QueueSync() + self.oldPos = car:WorldToLocal(self:GetPos()) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + self:SetPos(car:GetPos()) + self.trunkWeld = constraint.Weld(self, car, 0, 0, 0, true, false) + self:SetNoDraw(true) + self:DrawShadow(false) +end + +function ENT:DetachFromCar() + local car = self.car + if not IsValid(car) then return false end + if not car.inv then return false end + local cont = car.inv:GetContainer('trunk') + if not cont then return false end + if IsValid(self.trunkWeld) then self.trunkWeld:Remove() end + self:SetCollisionGroup(COLLISION_GROUP_NONE) + self:SetPos(car:LocalToWorld(self.oldPos) + Vector(0,0,1)) + car.package, self.car, self.oldPos = nil + self:SetNoDraw(false) + self:DrawShadow(true) + cont.volume = cont.volume + self:GetVolumeLiters() + cont:ResetSpaceMass() + cont:QueueSync() + return true +end + +-- 0.0018868524251996 is a shitty coefficient to convert source volume units to liters +-- calced manually, using cola cans, assuming its' volume is 0.33l +function ENT:GetVolumeLiters() + return math.ceil(self:GetPhysicsObject():GetVolume() * 0.0018868524251996) +end + +function ENT:Think() + if not self.jobID then return end + + if self:GetPos():DistToSqr(self.targetPos) <= self.targetDist then + dbgJobs.finishJob(self.jobID, true) + return + end + + self:NextThink(CurTime() + 2) + return true +end + +function ENT:Use(ply) + if self.ply ~= ply then return ply:Notify('Этот груз ожидает курьера') end + + if IsValid(self.weld) then + self.weld:Remove() + ply:Notify('Предмет откреплен') + return + end + + local bottom = self:NearestPoint(self:GetPos() - Vector(0, 0, 300)) + local traceData = { + start = bottom, + endpos = bottom - Vector(0,0,4), + filter = self, + } + local car = util.TraceLine(traceData).Entity + if not IsValid(car) or not car.IsSimfphyscar then return ply:Notify('Предмет можно закрепить, если положить его на автомобиль') end + if not list.Get('simfphys_vehicles')[car.VehicleName].Members.CanAttachPackages then + return ply:Notify('warning', 'Не получается закрепить предмет на этом автомобиле') + end + + self.weld = constraint.Weld(self, car, 0, 0, 5000, true, false) + ply:Notify('Предмет закреплен на автомобиле') +end + +function ENT:SetDeliveryData(jobID, ply, targetPos) + self.jobID = jobID + self.ply = ply + self.targetPos = targetPos + self.targetDist = (self:GetModelRadius() * 1.2) ^ 2 +end + +hook.Add('dbg-hands.canDrag', 'dbg_jobs_package', function(ply, ent, trace) + local ent = trace.Entity + if ent:GetClass() ~= 'dbg_jobs_package' then return end + + local owner = ent.ply + if owner == ply or IsValid(owner) and owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true) then return end + + return false, 'Этот груз ожидает курьера' +end) + +hook.Add('EntityRemoved', 'dbg-jobs.detach', function(ent) + if ent:GetClass() == 'gmod_sent_vehicle_fphysics_base' and ent.package then + ent.package:DetachFromCar() + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/shared.lua b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/shared.lua new file mode 100644 index 0000000..e34c3af --- /dev/null +++ b/garrysmod/addons/feature-jobs/lua/entities/dbg_jobs_package/shared.lua @@ -0,0 +1,5 @@ +ENT.Type = 'anim' +ENT.Base = 'base_entity' +ENT.PrintName = 'Груз' +ENT.Category = 'Доброград' +ENT.Author = 'octoteam' diff --git a/garrysmod/addons/feature-map/lua/autorun/octomap.lua b/garrysmod/addons/feature-map/lua/autorun/octomap.lua new file mode 100644 index 0000000..e8bd0e9 --- /dev/null +++ b/garrysmod/addons/feature-map/lua/autorun/octomap.lua @@ -0,0 +1 @@ +octolib.shared('octomap/octomap') diff --git a/garrysmod/addons/feature-map/lua/cmenu/items/markers.lua b/garrysmod/addons/feature-map/lua/cmenu/items/markers.lua new file mode 100644 index 0000000..33af3a4 --- /dev/null +++ b/garrysmod/addons/feature-map/lua/cmenu/items/markers.lua @@ -0,0 +1,15 @@ +local callsMarkers = { 'call', 'police', 'cpSupport', 'cpPanicBtn', 'camera' } +octogui.cmenu.registerItem('other', 'markers', { + text = L.markers, + icon = octolib.icons.silk16('map'), + options = { + { text = L.markers_clear, icon = octolib.icons.silk16('map_delete'), action = function() octolib.markers.clear() end }, + { + text = L.markers_clear_police, + icon = octolib.icons.silk16('map_delete'), + action = function() + for _, id in ipairs(callsMarkers) do octolib.markers.clear(id) end + end, + }, + }, +}) diff --git a/garrysmod/addons/feature-map/lua/octomap/cl_octomap.lua b/garrysmod/addons/feature-map/lua/octomap/cl_octomap.lua new file mode 100644 index 0000000..1fc5d04 --- /dev/null +++ b/garrysmod/addons/feature-map/lua/octomap/cl_octomap.lua @@ -0,0 +1,67 @@ +octomap.material = Material('octoteam/icons/clock.png') + +for k, v in pairs({ + url = 'goEUmT0.jpg', + addX = 80, addY = -22, + relX = 0.072, relY = -0.072, + mapW = 2560, mapH = 2560, + scale = 0.5, + tgtScale = 0.5, + scaleMin = 0.2097152, scaleMax = 1, + offX = 0, offY = 0, + tgtOffX = 0, tgtOffY = 0, + cx = 0, cy = 0, + bgCol = Color(198, 234, 146), + tgtSpeed = 20, + allowPan = true, + paddingL = 0, + paddingR = 0, + paddingT = 0, + paddingB = 0, +}) do + octomap.config[k] = octomap.config[k] or v +end + +local config = octomap.config + +function octomap.reloadMainMaterial() + + local pathFile = 'imgscreen/' .. config.url + local pathImg = '../data/' .. pathFile + + if file.Exists(pathFile, 'DATA') then + octomap.material = Material(pathImg) + return + end + + http.Fetch(octolib.imgurImage(config.url), function(content) + file.Write(pathFile, content) + local matName = pathImg:gsub('%.png', '') + RunConsoleCommand('mat_reloadmaterial', matName) + octomap.material = Material(pathImg) + end) + +end + +function octomap.worldToMap(x, y, z) + + if isvector(x) then x, y, z = x.x, x.y, x.z end + return x * config.relX + config.addX, y * config.relY + config.addY, z + +end + +function octomap.mapToWorld(x, y, z) + + if isvector(x) then x, y, z = x.x, x.y, x.z end + return (x - config.addX) / config.relX, (y - config.addY) / config.relY, z + +end + +hook.Add('octolib.imgur.loaded', 'octomap', octomap.reloadMainMaterial) +if octolib and octolib.imgurLoaded and octolib.imgurLoaded() then + octomap.reloadMainMaterial() +end + +if config.updateMap then + timer.Create('octomap.update', 1, 0, config.updateMap) +end diff --git a/garrysmod/addons/feature-map/lua/octomap/meta_marker.lua b/garrysmod/addons/feature-map/lua/octomap/meta_marker.lua new file mode 100644 index 0000000..7d67858 --- /dev/null +++ b/garrysmod/addons/feature-map/lua/octomap/meta_marker.lua @@ -0,0 +1,228 @@ +octomap.markers = octomap.markers or {} +octomap.clickableMarkers = octomap.clickableMarkers or {} +octomap.nextMarkerID = octomap.nextMarkerID or 1 + +octomap.metaMarker = octomap.metaMarker or {} + +local config = octomap.config +local Marker = octomap.metaMarker +Marker.__index = Marker + +local defaults = { + icon = 'icon16/house.png', + iconOffset = {0, 0}, + iconSize = 16, +} + +local icons = { + layerUp = Material('octoteam/icons-16/bullet_up.png'), + layerDown = Material('octoteam/icons-16/bullet_down.png'), +} + +function octomap.getMarker(id) + + return octomap.markers[id] + +end + +function octomap.createMarker(id) + + if not id then + id = octomap.nextMarkerID + octomap.nextMarkerID = octomap.nextMarkerID + 1 + end + + octomap.markers[id] = octomap.markers[id] or {} + + local marker = octomap.markers[id] + marker.id = id + marker.thinkAfter = 0 + setmetatable(marker, Marker) + + marker:SetPos(0,0,0) + marker:SetIcon(defaults.icon) + marker:SetIconOffset(unpack(defaults.iconOffset)) + marker:SetIconSize(defaults.iconSize) + + return marker + +end + +function Marker:Paint(x, y, map) + + local permanent = self.permanent + if permanent then + surface.DisableClipping(true) + x = math.Clamp(x, 0, map:GetWide()) + y = math.Clamp(y, 0, map:GetTall()) + end + + local otherLayer = self.layer and self.layer ~= config.mapLayer + if otherLayer then surface.SetAlphaMultiplier(0.35) end + + local iconX, iconY = x + self.iconOffX - self.iconHalfSize, y + self.iconOffY - self.iconHalfSize + surface.SetMaterial(self.icon) + if self.color then + surface.SetDrawColor(self.color) + else + surface.SetDrawColor(255,255,255, 255) + end + surface.DrawTexturedRect(iconX, iconY, self.iconSize, self.iconSize) + surface.SetAlphaMultiplier(1) + + if otherLayer then + surface.SetMaterial(self.layer > config.mapLayer and icons.layerUp or icons.layerDown) + surface.SetDrawColor(255,255,255, 200) + surface.DrawTexturedRect(iconX + self.iconSize - 14, iconY + self.iconSize - 14, self.iconSize, self.iconSize) + end + + if permanent then + surface.DisableClipping(false) + end + +end + +function Marker:SetIcon(path) + + self.iconPath = path or defaults.icon + self.icon = Material(self.iconPath) + return self + +end + +function Marker:SetIconSize(size) + + self.iconSize = size or defaults.iconSize + self.iconHalfSize = self.iconSize / 2 + return self + +end + +function Marker:SetIconOffset(x, y) + + self.iconOffX = x or defaults.iconOffset[1] + self.iconOffY = y or defaults.iconOffset[2] + + return self + +end + +function Marker:Remove() + + if self.clickable then self:SetClickable(false) end + self.removed = true + octomap.markers[self.id] = nil + + if self.sidebarData then + hook.Run('octomap.addedToSidebar', self) + end + +end + +function Marker:SetID(id) + + local m = octomap.getMarker(id) + if m then m:Remove() end + + self.id = id + octomap.markers[id] = self + + return self + +end + +function Marker:SetPos(x, y, z) + + x, y = x or 0, y or 0 + self.x, self.y, self.z = octomap.worldToMap(x, y, z) + self.cachedWorldX, self.cachedWorldY = nil + if config.getMapLayer then + self.layer = config.getMapLayer(self.z or 0) + end + + return self + +end + +function Marker:GetPos() + + if not self.cachedWorldX then + self.cachedWorldX, self.cachedWorldY = octomap.mapToWorld(self.x, self.y) + end + + return self.cachedWorldX, self.cachedWorldY + +end + +function Marker:SetMapPos(x, y, z) + + x, y = x or 0, y or 0 + self.x, self.y = x, y + self.z = z or self.z + self.cachedWorldX, self.cachedWorldY = nil + if config.getMapLayer then + self.layer = config.getMapLayer(self.z or 0) + end + + return self + +end + +function Marker:GetMapPos(x, y) + + return self.x, self.y + +end + +function Marker:SetColor(col) + + self.color = col + return self + +end + +function Marker:SetClickable(val) + + if val and not self.clickable then + if not table.HasValue(octomap.clickableMarkers, self) then + octomap.clickableMarkers[#octomap.clickableMarkers + 1] = self + self.clickable = true + end + elseif not val and self.clickable then + table.RemoveByValue(octomap.clickableMarkers, self) + self.clickable = nil + end + + return self + +end + +function Marker:SetPermanent(val) + + self.permanent = val or nil + return self + +end + +function Marker:SetVisible(val) + + self.hidden = val == false or nil + return self + +end + +function Marker:AddToSidebar(name, id) + + self.sidebarData = { + name = name, + id = id, + } + + hook.Run('octomap.addedToSidebar', self) + return self + +end + +-- to be overridden +function Marker:LeftClick(map) end +function Marker:RightClick(map) end diff --git a/garrysmod/addons/feature-map/lua/octomap/octomap.lua b/garrysmod/addons/feature-map/lua/octomap/octomap.lua new file mode 100644 index 0000000..1388a8a --- /dev/null +++ b/garrysmod/addons/feature-map/lua/octomap/octomap.lua @@ -0,0 +1,8 @@ +octomap = octomap or {} + +octolib.shared('config/octomap') + +octolib.client('cl_octomap') + +octolib.client('meta_marker') +octolib.client('vgui_map') diff --git a/garrysmod/addons/feature-map/lua/octomap/vgui_map.lua b/garrysmod/addons/feature-map/lua/octomap/vgui_map.lua new file mode 100644 index 0000000..dfeaf43 --- /dev/null +++ b/garrysmod/addons/feature-map/lua/octomap/vgui_map.lua @@ -0,0 +1,248 @@ +local config = octomap.config + +local PANEL = {} + +function PANEL:Init() + + self:Dock(FILL) + for k, v in pairs(config) do self[k] = v end + +end + +function PANEL:SetOptions(opts) + + for k, v in pairs(opts or {}) do self[k] = v end + +end + +function PANEL:Paint(w, h) + + self:UpdateAnimation() + + local scale = self.scale + local centerOffX, centerOffY = math.Round(self.cx + self.offX * scale), math.Round(self.cy + self.offY * scale) + + local mw, mh = config.mapW * scale, config.mapH * scale + local mx, my = centerOffX - math.Round(mw / 2), centerOffY - math.Round(mh / 2) + draw.NoTexture() + surface.SetDrawColor(config.bgCol) + surface.DrawRect(0, 0, w, h) + surface.SetMaterial(octomap.material) + surface.SetDrawColor(255,255,255, 255) + surface.DrawTexturedRect(mx, my, mw, mh) + + for id, marker in pairs(octomap.markers) do + local x, y = centerOffX + math.Round(marker.x * scale), centerOffY + math.Round(marker.y * scale) + marker:Paint(x, y, self) + end + +end + +function PANEL:Think() + + if self.panning then + if not self.allowPan then self.panning = nil end + + local newX, newY = gui.MousePos() + local dx, dy = newX - self.lastMX, newY - self.lastMY + if dx ~= 0 then + self.offX = self.offX + math.Round(dx / self.scale) + self.tgtOffX = self.offX + self.lastMX = newX + self:AlignToBounds() + end + if dy ~= 0 then + self.offY = self.offY + math.Round(dy / self.scale) + self.tgtOffY = self.offY + self.lastMY = newY + self:AlignToBounds() + end + end + + local ct = CurTime() + for id, marker in pairs(octomap.markers) do + if marker.Think and ct > marker.thinkAfter then + marker.thinkAfter = marker:Think() or ct + end + end + +end + +function PANEL:UpdateAnimation() + + if self.tgtScale ~= self.scale then + self.scale = octolib.math.lerp(self.scale, self.tgtScale, FrameTime() * self.tgtSpeed, 0.001) + end + + if self.tgtOffX ~= self.offX or self.tgtOffY ~= self.offY then + local pos = octolib.math.lerpVector(Vector(self.offX, self.offY, 0), Vector(self.tgtOffX, self.tgtOffY, 0), FrameTime() * self.tgtSpeed, 1) + self.offX, self.offY = pos.x, pos.y + end + +end + +function PANEL:AlignToBounds() + + local scale = self.tgtScale + local bx = config.mapW * scale - (self.cx * 2 - self.paddingL - self.paddingR) + local by = config.mapH * scale - (self.cy * 2 - self.paddingT - self.paddingB) + + if bx <= 0 then + self.tgtOffX = (self.paddingL - self.paddingR) / 2 / scale + else + local half = bx / 2 + local hpl, hpr = self.paddingL / 2, self.paddingR / 2 + local bl, br = (half + hpl - hpr) / scale, -(half + hpr - hpl) / scale + if self.tgtOffX < br then self.tgtOffX = br end + if self.tgtOffX > bl then self.tgtOffX = bl end + end + + if by <= 0 then + self.tgtOffY = (self.paddingT - self.paddingB) / 2 / scale + else + local half = by / 2 + local hpt, hpb = self.paddingT / 2, self.paddingB / 2 + local bt, bb = (half + hpt - hpb) / scale, -(half + hpb - hpt) / scale + if self.tgtOffY < bb then self.tgtOffY = bb end + if self.tgtOffY > bt then self.tgtOffY = bt end + end + +end + +function PANEL:OnMousePressed(key) + + local cx, cy = self:LocalCursorPos() + local mx, my = self:FromPanelToMap(cx, cy) + self.pressX, self.pressY = cx, cy + + if key == MOUSE_LEFT then + if self.allowPan then + self.panning = true + self.lastMX, self.lastMY = gui.MousePos() + + hook.Add('PlayerButtonUp', 'mapPan', function(ply, but) + if but == MOUSE_LEFT then + if IsValid(self) then self.panning = false end + hook.Remove('PlayerButtonUp', 'mapPan') + end + end) + end + end + + -- if key == MOUSE_RIGHT then + -- local m = octomap.getMarker('customTarget') + -- if m then + -- local x, y = m:GetMapPos() + -- if math.abs(x - mx) + math.abs(y - my) < 100 * self.scale then + -- m:Remove() + -- return + -- end + -- end + + -- local m = octomap.createMarker('customTarget') + -- :SetMapPos(mx, my) + -- :SetIcon('octoteam/icons-16/location_pin.png') + -- :SetIconOffset(0, -7) + -- :SetClickable(true) + + -- function m:LeftClick() + -- self + -- :SetIcon('octoteam/icons-16/tick.png') + -- :SetIconOffset() + -- end + -- end + +end + +function PANEL:OnMouseReleased(key) + + local cx, cy = self:LocalCursorPos() + if math.abs(cx - self.pressX) < 5 and math.abs(cy - self.pressY) < 5 then + for i, marker in ipairs(octomap.clickableMarkers) do + local x, y = self:FromMapToPanel(marker:GetMapPos()) + local radius = marker.iconHalfSize + 2 + if math.abs(cx - x) < radius and math.abs(cy - y) < radius then + if key == MOUSE_LEFT then + marker:LeftClick(self) + elseif key == MOUSE_RIGHT then + marker:RightClick(self) + end + end + end + end + + self.panning = false + +end + +function PANEL:OnMouseWheeled(d) + + if not self.allowPan then return end + + local x, y = self:FromPanelToMap(self:LocalCursorPos()) + self:Zoom(d, x, y) + +end + +function PANEL:PerformLayout() + + self.cx = self:GetWide() / 2 + self.cy = self:GetTall() / 2 + self:AlignToBounds() + +end + +function PANEL:Zoom(d, x, y) + + d = d or 1 + if not x or not y then + x, y = self:FromPanelToMap(self:LocalCursorPos()) + end + + local scaleOld = self.tgtScale + self.tgtScale = math.Clamp(self.tgtScale * (d > 0 and 1.25 or 0.8), self.scaleMin, self.scaleMax) + + local ch = math.abs(1 - scaleOld / self.tgtScale) * octolib.math.sign(d) + self.tgtOffX = octolib.math.lerpUnclamped(self.tgtOffX, -x, ch) + self.tgtOffY = octolib.math.lerpUnclamped(self.tgtOffY, -y, ch) + + self:AlignToBounds() + +end + +function PANEL:GoTo(x, y, scale) + + self.tgtSpeed = 10 + timer.Create('octomap.tgtSpeedReset', 1, 1, function() + if not IsValid(self) then return end + self.tgtSpeed = config.tgtSpeed + end) + + self.tgtOffX = -x + (self.paddingL - self.paddingR) / 2 + self.tgtOffY = -y + (self.paddingT - self.paddingB) / 2 + self.tgtScale = scale or self.tgtScale + self:AlignToBounds() + +end + +function PANEL:FromPanelToMap(x, y) + + x, y = x or 0, y or 0 + return (x - self.cx) / self.scale - self.offX, (y - self.cy) / self.scale - self.offY + +end + +function PANEL:FromMapToPanel(x, y) + + x, y = x or 0, y or 0 + return (x + self.offX) * self.scale + self.cx, (y + self.offY) * self.scale + self.cy + +end + +function PANEL:GetViewCenter() + + return self.cx + (self.paddingL - self.paddingR) / 2, self.cy + (self.paddingT - self.paddingB) / 2 + +end + +vgui.Register('octomap', PANEL, 'DPanel') diff --git a/garrysmod/addons/feature-orgdescs/lua/autorun/orgs.lua b/garrysmod/addons/feature-orgdescs/lua/autorun/orgs.lua new file mode 100644 index 0000000..c855b5f --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/autorun/orgs.lua @@ -0,0 +1,11 @@ +simpleOrgs = simpleOrgs or {} +octolib.shared('config/groups') + +octolib.client('orgs/client') + +octolib.server('orgs/server') +octolib.server('orgs/ext_player_sv') + +octolib.client('orgs/cl_multirank') +octolib.shared('orgs/ext_player_sh') +octolib.server('orgs/sv_multirank') diff --git a/garrysmod/addons/feature-orgdescs/lua/cmenu/items/org_clothes.lua b/garrysmod/addons/feature-orgdescs/lua/cmenu/items/org_clothes.lua new file mode 100644 index 0000000..7dfac8e --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/cmenu/items/org_clothes.lua @@ -0,0 +1,41 @@ +local function getClothes(mdl, clothesData) + for prefix, clothes in pairs(clothesData) do + if string.StartWith(mdl, prefix) then return clothes end + end +end + +local function reloadClothes() + for id, org in pairs(simpleOrgs.orgs) do + if not org.clothes then continue end + octogui.cmenu.registerItem('clothes', id, { + text = 'Форма', + icon = octolib.icons.silk16(org.clothes.icon), + check = function(ply) + return ply:getJobTable().command == org.team and getClothes(ply:GetModel(), org.clothes) ~= nil + end, + build = function(sm) + local ply = LocalPlayer() + local clothes = getClothes(ply:GetModel(), org.clothes) + if not clothes then return end + for clID, data in pairs(clothes) do + local bg, addsm, addpmo = ply:GetBodygroup(data.bodygroup) + if data.sm then + addsm, addpmo = sm:AddSubMenu(data.sm) + addpmo:SetIcon(octolib.icons.silk16(data.icon)) + end + for bgID, v in pairs(data.vals) do + local sm2 = addsm or sm + if bg ~= bgID then + sm2:AddOption(v[1], function() + netstream.Start('dbg-orgs.clothes', clID, bgID) + octochat.say(v[3]) + end):SetIcon(octolib.icons.silk16(v[2])) + end + end + end + end, + }) + end +end + +hook.Add('simple-orgs.load', 'simple-orgs.org_clothes', reloadClothes) \ No newline at end of file diff --git a/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/cl_init.lua b/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/cl_init.lua new file mode 100644 index 0000000..777909f --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/cl_init.lua @@ -0,0 +1,110 @@ +include 'shared.lua' + +netstream.Hook('simple-orgs.guest', function(id, url, flyer) + + local org = simpleOrgs.orgs[id] + if not org then return end + + local fr = vgui.Create('DFrame') + fr:SetSize(500, 64) + fr:SetTitle(org.title) + fr:SetPos((ScrW() - 500) / 2, (ScrH() - 575) / 2) + fr:MakePopup() + fr:SetAlpha(0) + fr:SizeTo(500, 575, 2, 0, 0.3) + fr:AlphaTo(255, 1, 0) + + local scrollPan = fr:Add('DScrollPanel') + scrollPan:Dock(FILL) + local presenation = scrollPan:Add('DImage') + presenation:Dock(TOP) + presenation:SetKeepAspect(true) + octolib.getImgurMaterial(flyer, function(mat) + if not IsValid(presenation) then return end + presenation:SetMaterial(mat) + presenation:SetTall(mat:Height()) + end, true) + + local btn = fr:Add('DButton') + btn:Dock(BOTTOM) + btn:DockMargin(0, 5, 15, 0) + btn:SetFont('f4.normal') + btn:SetTall(30) + btn:SetText(url and url ~= '' and 'Подать заявку' or 'Заявки временно не принимаются') + btn:SetEnabled(url and url ~= '') + if btn:IsEnabled() then + function btn:DoClick() + octoesc.OpenURL(url) + end + end +end) + +local function chooseByData(combo, data) + for k, v in pairs(combo.Data) do + if v == data then + combo:ChooseOptionID(k) + return true + end + end + return false +end + +local function synchronizeOption(customizer, val) + if not customizer then return false end + if customizer[1] then return chooseByData(customizer[2], val) + elseif val == 0 or val == customizer[2].okVal then + customizer[2]:SetValue(val ~= 0) + return true + else return false end +end + +netstream.Hook('simple-orgs.activeWindow', function(ent, mdlData) + + local ply = LocalPlayer() + + local pnl = octolib.models.selector({mdlData}, function(index, skin, bgs, mats) + if not index then return end + netstream.Start('simple-orgs.getAmmo', ent, skin, bgs, mats) + end) + pnl.layoutPan:Remove() + pnl.submitBtn:SetText('Переодеться и пополнить боезапас') + + local handOverBtn = octolib.button(pnl.mpWrap, 'Сдать экипировку', function() + netstream.Start('simple-orgs.handOver', ent) + pnl:Close() + end) + handOverBtn:Dock(BOTTOM) + handOverBtn:DockMargin(0, 2, 0, 0) + handOverBtn:SetTall(25) + + pnl.submitBtn:SetParent() + pnl.submitBtn:SetParent(pnl.mpWrap) + + local params = pnl.params + params:ChangeModel(1, mdlData) + if params.skinCustomizer then + chooseByData(params.skinCustomizer, ply:GetSkin()) + end + + local modelEnt = pnl.modelPreview.Entity + + local bgCustomizers = params.bgCustomizers + for _, v in ipairs(ply:GetBodyGroups()) do + local id, val = v.id, ply:GetBodygroup(v.id) + if not synchronizeOption(bgCustomizers[id], val) then modelEnt:SetBodygroup(id, val) end + end + + local matCustomizers = params.matCustomizers + for id in ipairs(ply:GetMaterials()) do + local val = ply:GetSubMaterial(id) + if not synchronizeOption(matCustomizers[id], val) then modelEnt:SetSubMaterial(id, val) end + end + +end) + +function ENT:Draw() + self:DrawModel() + local title = self:GetNetVar('title') + if not title then return end + render.DrawBubble(self, Vector(0, 0.45, 8), Angle(0, 180, 90), title, 200, 200) +end diff --git a/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/init.lua b/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/init.lua new file mode 100644 index 0000000..5b1715b --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/init.lua @@ -0,0 +1,163 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +duplicator.RegisterEntityClass('ent_dbg_org_board', function(ply, data) + local ent = duplicator.GenericDuplicatorFunction(ply, data) + ent:SetOrgID(ent.orgId) + return ent +end, 'Data', 'orgId') + +function ENT:Initialize() + + self:SetModel('models/props/cs_office/offcorkboarda.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + +end + +function ENT:SetOrgID(id) + self.orgId = id + local title = simpleOrgs and simpleOrgs.orgs and simpleOrgs.orgs[self.orgId] and simpleOrgs.orgs[self.orgId].shortTitle or nil + if title == '' then title = nil end + self:SetNetVar('title', title) +end + +local function equip(ply, mdlData, skin, bgs, mats) + local ent = ply.pendingOrgEnt + ply.pendingOrgEnt = nil + if not mdlData then return ply:SetNetVar('activeRank', nil) end + if not IsValid(ent) then return end + if not octolib.use.check(ply, ent) then return end + if not ent.orgId or not simpleOrgs.orgs[ent.orgId] then return end + local org = simpleOrgs.orgs[ent.orgId] + if not ply:IsOrgMember(ent.orgId) then return end + + ply:SaveCitizen() + local job, jobID = DarkRP.getJobByCommand(org.team) + ply:changeTeam(jobID, true, true) + if org.police then + ply:SetNetVar('dbg-police.job', job.command) + end + ply:SetClothes(nil) + ply:SetModel(mdlData.model) + ply:SetSkin(skin) + for _, v in ipairs(ply:GetBodyGroups()) do + ply:SetBodygroup(v.id, bgs[v.id] or 0) + end + if mats then + for k, v in pairs(mats) do + ply:SetSubMaterial(k, v) + end + end + + ply.currentOrg = ent.orgId + ply.currentOrgModel = mdlData + + ply.prevArmor = ply:Armor() + ply:SetArmor(job.armor or 0) + ply:SelectWeapon('dbg_hands') + if job.hasTalkie and org.talkieFreq then + ply:ConnectTalkie(org.talkieFreq) + ply:SyncTalkieChannels() + end + + hook.Run('simple-orgs.getEquip', ply) + +end + +function ENT:Use(ply) + if not IsValid(ply) then return end + if not self.orgId or not simpleOrgs.orgs[self.orgId] then return end + local org = simpleOrgs.orgs[self.orgId] + if org.secret then return end + + if not ply:IsOrgMember(self.orgId) then + if ply:getJobTable().command == self.team then + ply:RestoreCitizen() + end + if not org.flyer or org.flyer == '' then + return ply:Notify('error', 'Для этого нужно состоять в организации "' .. org.name .. '"') + end + netstream.Start(ply, 'simple-orgs.guest', self.orgId, org.url, org.flyer) + return + end + + + if ply:getJobTable().command == org.team then + netstream.Start(ply, 'simple-orgs.activeWindow', self, ply.currentOrgModel) + elseif ply.dbgPolice_citizenData then + ply:Notify('Ты не можешь сейчас занять эту должность') + else + if next(ply.Buffs) then -- if he has any drug buff + return ply:Notify('warning', 'Чтобы заступить на службу, нужна чистая голова! Приведи себя в порядок и возвращайся позже') + end + local mdls = hook.Run('simple-orgs.overrideModels', ply, self.orgId, org.mdls, self) + if mdls and not mdls[1] then return end + ply.pendingOrgEnt = self + ply:SelectModel(mdls or org.mdls, equip) + end +end + +netstream.Hook('simple-orgs.getAmmo', function(ply, ent, userSkin, userBgs, userMats) + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_org_board' then return end + if not octolib.use.check(ply, ent) then return end + if not ent.orgId or not simpleOrgs.orgs[ent.orgId] then return end + local org = simpleOrgs.orgs[ent.orgId] + if not ply:IsOrgMember(ent.orgId) then return end + if ply:getJobTable().command ~= org.team then return end + + local wep = ply:GetWeapon('med_kit') + if IsValid(wep) then wep:SetClip1(200) end + + local job = DarkRP.getJobByCommand(org.team) + for k,v in pairs(job.ammo or {}) do + ply:SetAmmo(math.max(ply:GetAmmoCount(k), v), k) + end + ply:SetArmor(job.armor or 0) + + local mdlData = ply.currentOrgModel + if mdlData then + local skin, bgs, mats = octolib.models.getValidSelection(mdlData, userSkin, userBgs, userMats) + ply:SetSkin(skin) + for _, v in ipairs(ply:GetBodyGroups()) do + ply:SetBodygroup(v.id, bgs[v.id] or 0) + end + for k, v in pairs(mats) do + ply:SetSubMaterial(k, v) + end + end + ply:EmitSound('npc/combine_soldier/gear5.wav', 65) + + hook.Run('simple-orgs.getAmmo', ply, ent.orgId) +end) + +netstream.Hook('simple-orgs.handOver', function(ply, ent) + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_org_board' then return end + if not octolib.use.check(ply, ent) then return end + if not ent.orgId or not simpleOrgs.orgs[ent.orgId] then return end + local org = simpleOrgs.orgs[ent.orgId] + if not ply:IsOrgMember(ent.orgId) then return end + if ply:getJobTable().command ~= org.team then return end + + if not ply.dbgPolice_citizenData then + ply:Notify('warning', L.error_character) + return + end + ply:SetArmor(ply.prevArmor or 0) + ply:SetNetVar('dbg-police.job', nil) + + for i, _ in ipairs(ply:GetMaterials()) do + if ply:GetSubMaterial(i) then + ply:SetSubMaterial(i, nil) + end + end + + ply:RestoreCitizen() + ply.currentOrg, ply.currentOrgModel = nil + + hook.Run('simple-orgs.handOver', ply) + +end) diff --git a/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/shared.lua b/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/shared.lua new file mode 100644 index 0000000..8ae14a2 --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/entities/ent_dbg_org_board/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'Доска огранизации' +ENT.Category = L.dobrograd +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/feature-orgdescs/lua/orgs/cl_multirank.lua b/garrysmod/addons/feature-orgdescs/lua/orgs/cl_multirank.lua new file mode 100644 index 0000000..6d045ed --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/orgs/cl_multirank.lua @@ -0,0 +1,180 @@ +local function rankName(orgID, rankID) + return simpleOrgs.orgs[orgID].ranks[rankID].shortName +end + +local function fancyRanks(orgID, tbl) + return string.Implode(', ', octolib.table.mapSequential(tbl, function(v) + return rankName(orgID, v) + end)) +end + +local function sizeToContents(self) + self:SizeToContentsY(0) +end + +function simpleOrgs.openMultiRankEditor(orgID, members, url, flyer) + + local org = simpleOrgs.orgs[orgID] + if not (org and org.multirank) then return end + + local fr = vgui.Create 'DFrame' + fr:SetSize(350, 500) + fr:SetTitle(org.name) + fr:Center() + fr:MakePopup() + fr:SetSizable(true) + fr:SetMinHeight(350) + fr:SetMinWidth(250) + + local lst = fr:Add 'DListView' + lst:Dock(FILL) + lst:AddColumn('Ник в стиме') + lst:AddColumn('Ранг(и)') + + local lines = {} + local saved + + local function update(sid, ranks) + if IsValid(lines[sid]) then + lines[sid].ranks = ranks + lines[sid]:SetValue(2, fancyRanks(orgID, ranks)) + else + steamworks.RequestPlayerInfo(util.SteamIDTo64(sid), function(name) + local line = lst:AddLine(name, fancyRanks(orgID, ranks)) + line.sid, line.ranks = sid, ranks + lines[sid] = line + end) + end + saved = false + end + + for sid, ranks in pairs(members) do update(sid, ranks) end + saved = true + + local function editRanks(sid) + + if not octolib.string.isSteamID(sid) then + return octolib.notify.show('warning', sid, ' не является SteamID') + end + + local o = octolib.overlay(fr, 'DPanel') + o:SetSize(fr:GetWide() - 100, fr:GetTall() - 100) + local curRanks = IsValid(lines[sid]) and lines[sid].ranks or {} + + local lbl = octolib.label(o, ('Ранги игрока %s\nНажми по рангу в списке ниже, чтобы снять его'):format(sid)) + lbl:DockMargin(5, 5, 5, 5) + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl.PerformLayout = sizeToContents + steamworks.RequestPlayerInfo(util.SteamIDTo64(sid), function(name) + if IsValid(lbl) then lbl:SetText(('Ранги игрока %s (%s)\nНажми по рангу в списке ниже, чтобы снять его'):format(sid, name)) end + end) + + local ranksLst = o:Add 'DListView' + ranksLst:Dock(FILL) + ranksLst:AddColumn('Ранг') + ranksLst:SetSortable(false) + + for _, v in ipairs(curRanks) do + ranksLst:AddLine(rankName(orgID, v)).rank = v + end + + curRanks = octolib.array.toKeys(curRanks) + local function apply() + local newRanks = table.GetKeys(curRanks) + table.sort(newRanks, function(a, b) + return org._rankOrder[a] < org._rankOrder[b] + end) + o:Remove() + if not newRanks[1] then + if IsValid(lines[sid]) then + lst:RemoveLine(lines[sid]:GetID()) + lines[sid] = nil + end + return + end + update(sid, newRanks) + editRanks(sid) + end + + function ranksLst:OnRowSelected(_, line) + curRanks[line.rank] = nil + apply() + end + + local add = octolib.button(o, 'Добавить', function() + local options = {} + for _, v in ipairs(org.rankOrder) do + if not curRanks[v] then + options[#options + 1] = {rankName(orgID, v), nil, function() + curRanks[v] = true + apply() + end} + end + end + octolib.menu(options):Open() + end) + add:Dock(BOTTOM) + + end + + function lst:OnRowRightClick(lID, line) + local menu = DermaMenu() + menu:AddOption('Изменить', function() + editRanks(line.sid) + end):SetIcon(octolib.icons.silk16('pencil')) + menu:AddOption('Удалить', function() + lines[line.sid] = nil + lst:RemoveLine(lID) + saved = false + end):SetIcon(octolib.icons.silk16('cross')) + menu:Open() + end + function lst:DoDoubleClick(_, line) + editRanks(line.sid) + end + + local urlPan, flyerPan + + local saveBtn = octolib.button(fr, 'Сохранить', function() + local result = {} + for sid, v in pairs(lines) do + result[sid] = v.ranks + end + netstream.Start('simple-orgs.editor.save', orgID, result, urlPan:GetValue(), flyerPan:GetValue()) + saved = true + end) + saveBtn:Dock(BOTTOM) + + local oldClose = fr.Close + function fr:Close() + if saved then return oldClose(self) end + octolib.confirmDialog(self, 'Сохранить изменения в составе?', function(save) + if save then saveBtn:DoClick() end + oldClose(self) + end) + end + + octolib.button(fr, 'Добавить/Изменить', + octolib.fStringRequest('Добавление/Изменение участника', 'Укажи SteamID игрока, чьи ранги необходимо обновить', LocalPlayer():SteamID(), editRanks) + ):Dock(BOTTOM) + + urlPan = octolib.textEntry(fr) + urlPan:Dock(BOTTOM) + urlPan:SetValue(url or '') + urlPan:SetUpdateOnType(true) + urlPan:SetPlaceholderText('Если оставить пустым, кнопка будет неактивна') + + octolib.label(fr, 'Ссылка на подачу заявки (можно не указывать)'):Dock(BOTTOM) + + flyerPan = octolib.textEntry(fr) + flyerPan:Dock(BOTTOM) + flyerPan:SetValue(flyer and flyer ~= '' and ('https://i.imgur.com/'..flyer) or '') + flyerPan:SetUpdateOnType(true) + flyerPan:SetPlaceholderText('Если оставить пустым, флаера не будет') + + octolib.label(fr, 'Ссылка на флаер на Imgur (должен быть шириной 475)'):Dock(BOTTOM) + + return fr + +end diff --git a/garrysmod/addons/feature-orgdescs/lua/orgs/client.lua b/garrysmod/addons/feature-orgdescs/lua/orgs/client.lua new file mode 100644 index 0000000..fcc129d --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/orgs/client.lua @@ -0,0 +1,116 @@ +local function listPanel(parent, initial, onSave) + + local cache = initial or {} + + local list = parent:Add('DListView') + list:Dock(FILL) + list:AddColumn('SteamID') + list:AddColumn('Ник в стиме') + + function list:Rebuild() + list:Clear() + + for i, v in ipairs(cache) do + local line = list:AddLine(v) + line.id = i + + steamworks.RequestPlayerInfo(util.SteamIDTo64(v), function(name) + if IsValid(line) then line:SetColumnText(2, name) end + end) + end + end + + function list:OnRowRightClick(id, line) + local sid = line:GetColumnText(1) + local menu = DermaMenu() + + menu:AddOption('Скопировать SteamID', function() + SetClipboardText(sid) + end):SetIcon('icon16/page_copy.png') + + menu:AddOption('Открыть профиль', function() + gui.ActivateGameUI() + octoesc.OpenURL('https://steamcommunity.com/profiles/' .. util.SteamIDTo64(sid)) + end):SetIcon('icon16/report_user.png') + + menu:AddOption('Удалить', function() + table.remove(cache, table.KeyFromValue(cache, line:GetValue(1))) + list:Rebuild() + end):SetIcon('icon16/delete.png') + + menu:Open() + end + + octolib.button(parent, 'Сохранить', function(self) + onSave(cache) + self:SetEnabled(false) + timer.Simple(2, function() + if IsValid(self) then self:SetEnabled(true) end + end) + end):Dock(BOTTOM) + + octolib.button(parent, 'Добавить', octolib.fStringRequest('Добавить', 'Укажи SteamID', LocalPlayer():SteamID(), function(sid) + sid = string.Trim(string.upper(sid)) + if not octolib.string.isSteamID(sid) then + return octolib.notify.show('warning', 'Это не SteamID') + end + + if table.HasValue(cache, sid) then + return octolib.notify.show('warning', 'Этот игрок уже добавлен в список') + end + + table.insert(cache, sid) + list:Rebuild() + end)):Dock(BOTTOM) + + list:Rebuild() + return list +end + +netstream.Hook('simple-orgs.editor.open', function(id, members, url, flyer, owners) + + local frMembers + if not simpleOrgs.orgs[id].multirank then + + frMembers = vgui.Create('DFrame') + frMembers:SetTitle(id .. ': Редактор участников') + frMembers:SetSize(300, 350) + frMembers:Center() + frMembers:SetSizable(true) + frMembers:SetMinimumSize(300, 350) + frMembers:MakePopup() + local urlPan, flyerPan + listPanel(frMembers, members, function(data) + netstream.Start('simple-orgs.editor.save', id, data, urlPan:GetValue(), flyerPan:GetValue()) + end) + urlPan = octolib.textEntry(frMembers) + urlPan:Dock(BOTTOM) + urlPan:SetValue(url or '') + urlPan:SetUpdateOnType(true) + urlPan:SetPlaceholderText('В данный момент заявки не принимаются') + octolib.label(frMembers, 'Ссылка на подачу заявки (можно не указывать)'):Dock(BOTTOM) + + flyerPan = octolib.textEntry(frMembers) + flyerPan:Dock(BOTTOM) + flyerPan:SetValue(flyer and flyer ~= '' and ('https://i.imgur.com/'..flyer) or '') + flyerPan:SetUpdateOnType(true) + flyerPan:SetPlaceholderText('Если оставить пустым, флаера не будет') + octolib.label(frMembers, 'Ссылка на флаер на Imgur (должен быть шириной 475)'):Dock(BOTTOM) + else frMembers = simpleOrgs.openMultiRankEditor(id, members, url, flyer) end + + if not owners then return end + local x, y = frMembers:GetPos() + frMembers:SetPos(x - 180, y) + + local frOwners = vgui.Create('DFrame') + frOwners:SetTitle(id .. ': Редактор владельцев') + frOwners:SetSize(300, 350) + frOwners:SetPos(x + 180, y) + frOwners:SetSizable(true) + frOwners:SetMinimumSize(300, 350) + frOwners:MakePopup() + listPanel(frOwners, owners, function(data) + netstream.Start('simple-orgs.editor.saveOwners', id, data) + end) + +end) diff --git a/garrysmod/addons/feature-orgdescs/lua/orgs/ext_player_sh.lua b/garrysmod/addons/feature-orgdescs/lua/orgs/ext_player_sh.lua new file mode 100644 index 0000000..3824156 --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/orgs/ext_player_sh.lua @@ -0,0 +1,12 @@ +local plyMeta = FindMetaTable 'Player' + +function plyMeta:GetActiveRank(orgID) + if not orgID then return unpack(self:GetNetVar('activeRank', {})) end + if self:GetNetVar('activeRank', {})[1] == orgID then + return self:GetNetVar('activeRank')[2] + end +end + +function plyMeta:GetActiveOrg() + return self:GetNetVar('activeRank', {})[1] +end diff --git a/garrysmod/addons/feature-orgdescs/lua/orgs/ext_player_sv.lua b/garrysmod/addons/feature-orgdescs/lua/orgs/ext_player_sv.lua new file mode 100644 index 0000000..bd16252 --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/orgs/ext_player_sv.lua @@ -0,0 +1,84 @@ +local meta = FindMetaTable 'Player' +function meta:SaveCitizen() + + local weapons = {} + for _,wep in ipairs(self:GetWeapons()) do + if not (GAMEMODE.Config.DisallowDrop[wep:GetClass()] or self:jobHasWeapon(wep:GetClass())) then + table.insert(weapons, {wep:GetClass(), wep:Clip1()}) + end + end + + local bg = {} + for _,v in ipairs(self:GetBodyGroups()) do + bg[v.id] = self:GetBodygroup(v.id) + end + self.dbgPolice_citizenData = { + j = self:getJobTable().command, + mdl = self:GetModel(), + bg = bg, + sk = self:GetSkin(), + a = self:Armor(), + p = self:GetAmmoCount('pistol'), + smg = self:GetAmmoCount('SMG1'), + lc = self:GetNetVar('HasGunlicense'), + cl = self:GetNetVar('customClothes'), + wps = weapons, + } + +end + +function meta:RestoreCitizen() + + if not self.dbgPolice_citizenData or not self.dbgPolice_citizenData.mdl then return end + + local _, job = DarkRP.getJobByCommand(self.dbgPolice_citizenData.j) + self:changeTeam(job, true, true) + self:SetModel(self.dbgPolice_citizenData.mdl) + self:SetSkin(self.dbgPolice_citizenData.sk) + for k, v in pairs(self.dbgPolice_citizenData.bg) do self:SetBodygroup(k, v) end + self:SetAmmo(self.dbgPolice_citizenData.p, 'pistol') + self:SetAmmo(self.dbgPolice_citizenData.smg, 'SMG1') + self:SetArmor(self.dbgPolice_citizenData.a) + self:SetNetVar('HasGunlicense', self.dbgPolice_citizenData.lc) + for k, v in pairs(self.dbgPolice_citizenData.wps) do + local wep = self:Give(v[1], true) + if IsValid(wep) then wep:SetClip1(v[2]) end + end + self:SetClothes(self.dbgPolice_citizenData.cl) + self.dbgPolice_citizenData = nil + self:SelectWeapon('dbg_hands') + + if self:HasTalkie() and not self:CanListenToChannel(self:GetFrequency(), true) then + self:DisconnectTalkie() + end + +end + +function meta:IsOrgMember(orgID) + local is = hook.Run('simple-orgs.isMember', self, orgID) + if is ~= nil then return is end + local org = simpleOrgs.orgs[orgID] + local sid = self:SteamID() + return org and org.members and (org.members[sid] ~= nil or table.HasValue(org.members, sid)) +end + +function meta:OpenOrgEditor(orgID) + local ok, msg = hook.Run('simple-orgs.overrideManagement', self, orgID) + if ok ~= nil then return ok, msg end + if not orgID or not simpleOrgs.orgs[orgID] then + return false, 'Organization not found' + end + local org = simpleOrgs.orgs[orgID] + if not (self:query('DBG: Редактировать организации') or table.HasValue(org.owners, self:SteamID())) then + return false, 'Access denied' + end + netstream.Start(self, 'simple-orgs.editor.open', orgID, org.members, org.url, org.flyer, self:query('DBG: Редактировать организации') and org.owners or nil) +end + +function meta:GetOrgRanks(orgID) + local org = simpleOrgs.orgs[orgID] + if not org then return {} end + local sid = self:SteamID() + if not org.multirank then return table.HasValue(org.owners, sid) and { 'owner' } or table.HasValue(org.members, sid) and { 'member' } or {} end + return org.members[sid] or {} +end diff --git a/garrysmod/addons/feature-orgdescs/lua/orgs/server.lua b/garrysmod/addons/feature-orgdescs/lua/orgs/server.lua new file mode 100644 index 0000000..5f966ae --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/orgs/server.lua @@ -0,0 +1,264 @@ +local function updatePlayers() + local members = {} + for k, v in pairs(simpleOrgs.orgs) do + members[k] = table.IsSequential(v.members) and octolib.array.toKeys(v.members) or table.GetKeys(v.members) + end + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + local sid, orgs = ply:SteamID(), {} + for k, v in pairs(members) do + if v[sid] then orgs[k] = true end + end + ply:SetLocalVar('dbg-orgs.member', orgs) + end) +end + +local function reloadOrgs() + octolib.db:RunQuery('SELECT * FROM `dbg_orgs`', function(q, st, res) + if not istable(res) then return end + + local members, owners, urls, flyers = {}, {}, {}, {} + for _,v in ipairs(res) do + members[v.id] = pon.decode(v.members) + owners[v.id] = pon.decode(v.owners) + urls[v.id] = v.url or '' + flyers[v.id] = v.flyer or '' + end + + for k,v in pairs(simpleOrgs.orgs) do + v.members = simpleOrgs.decompressMembers(members[k] or {}) + table.Empty(v.owners) + for _, owner in ipairs(owners[k] or {}) do + v.owners[#v.owners + 1] = owner + end + v.url = urls[k] or '' + v.flyer = flyers[k] or '' + end + updatePlayers() + end) +end + +hook.Add('octolib.event:reloadOrgs', 'dbg-orgs', reloadOrgs) + +hook.Add('octolib.db.init', 'dbg-orgs', function() + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS `dbg_orgs` ( + `id` VARCHAR(10) NOT NULL, + `members` TEXT NOT NULL, + `owners` TEXT NOT NULL, + `url` VARCHAR(128) NOT NULL DEFAULT '', + `flyer` VARCHAR(128) NOT NULL DEFAULT '', + PRIMARY KEY (`id`) + ) COLLATE='utf8_general_ci' + ]], function(q, st, res) + local vals = '' + for k,v in pairs(simpleOrgs.orgs) do + if #vals ~= 0 then vals = vals .. ',' end + vals = vals .. ('(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\')'):format( + octolib.db:escape(k), + octolib.db:escape(pon.encode(v.members)), + octolib.db:escape(pon.encode(v.owners)), + octolib.db:escape(v.url), + octolib.db:escape(v.flyer)) + end + octolib.db:RunQuery([[INSERT IGNORE INTO `dbg_orgs` VALUES ]] .. vals, reloadOrgs) + end) +end) + +local function saveOrg(id) + local org = id and simpleOrgs.orgs[id] + if not org then return end + local members = simpleOrgs.compressMembers(id) + octolib.db:PrepareQuery([[UPDATE `dbg_orgs` SET `members` = ?, `url` = ?, `flyer` = ? WHERE `id` = ?]], {pon.encode(members), org.url, org.flyer, id}, function(q, st, res) + octolib.sendCmdToOthers('reloadOrgs') + updatePlayers() + end) +end +simpleOrgs.saveOrg = saveOrg + + +function simpleOrgs.compressMembers(membersOrID) + membersOrID = membersOrID or {} + local members = membersOrID + if not istable(membersOrID) then members = simpleOrgs.orgs[membersOrID] and simpleOrgs.orgs[membersOrID].members end + if not istable(members) then return {} end + if table.IsSequential(members) then return octolib.table.mapSequential(members, octolib.string.compressSteamID) end + local result = {} + for k, v in pairs(members) do + result[octolib.string.compressSteamID(k)] = v + end + return result +end + +function simpleOrgs.decompressMembers(membersOrID) + membersOrID = membersOrID or {} + if not istable(membersOrID) then return simpleOrgs.orgs[membersOrID] and table.Copy(simpleOrgs.orgs[membersOrID].members) or {} end + if table.IsSequential(membersOrID) then return octolib.table.mapSequential(membersOrID, octolib.string.decompressSteamID) end + local result = {} + for k, v in pairs(membersOrID) do + result[octolib.string.decompressSteamID(k)] = v + end + return result +end + +local allowedFlyerExts = octolib.array.toKeys {'.jpg', '.png'} +netstream.Hook('simple-orgs.editor.save', function(ply, orgId, data, url, flyer) + if not (istable(data) and isstring(url) and isstring(flyer) and orgId and simpleOrgs.orgs[orgId]) then + return ply:Notify('Организация не найдена') + end + if not table.HasValue(simpleOrgs.orgs[orgId].owners, ply:SteamID()) and not ply:IsSuperAdmin() then + return ply:Notify('Доступ запрещен') + end + + simpleOrgs.orgs[orgId].members = data + simpleOrgs.orgs[orgId].url = url + + if flyer ~= '' then + if flyer:sub(1, 20) ~= 'https://i.imgur.com/' then + return ply:Notify('warning', 'Можно использовать только ссылки с Imgur') + end + if not allowedFlyerExts[flyer:sub(-4)] then + return ply:Notify('warning', 'Можно использовать только ссылки в формате .jpg или .png') + end + flyer = flyer:sub(21) + end + simpleOrgs.orgs[orgId].flyer = flyer + + saveOrg(orgId) + ply:Notify('Информация об организации обновлена') +end) + +netstream.Hook('simple-orgs.editor.saveOwners', function(ply, orgId, data) + if not (istable(data) and orgId and simpleOrgs.orgs[orgId]) then + return ply:Notify('Организация не найдена') + end + local org = simpleOrgs.orgs[orgId] + if not ply:query('DBG: Редактировать организации') then + return ply:Notify('Доступ запрещен') + end + org.owners = data + octolib.db:PrepareQuery([[UPDATE `dbg_orgs` SET `owners` = ? WHERE `id` = ?]], {pon.encode(data), orgId}, function(q, st, res) + octolib.sendCmdToOthers('reloadOrgs') + end) + ply:Notify('Информация об организации обновлена') +end) + +concommand.Add('dbg_orgs_edit', function(ply, cmd, args) + local orgId = args[1] + if not orgId then + return ply:PrintMessage(HUD_PRINTCONSOLE, 'dbg_orgs_edit ID') + end + local _, msg = ply:OpenOrgEditor(orgId) + if msg then ply:PrintMessage(HUD_PRINTCONSOLE, msg) end +end) + +hook.Add('PlayerFinishedLoading', 'simple-orgs', function(ply) + local orgs = {} + for k in pairs(simpleOrgs.orgs) do + if ply:IsOrgMember(k) then orgs[k] = true end + end + ply:SetLocalVar('dbg-orgs.member', orgs) +end) + +hook.Add('playerCanChangeTeam', 'simple-orgs.multirank', function(ply, t, force) + if force then return end + local org = RPExtraTeams[t].orgID + if org then org = simpleOrgs.orgs[org] end + if org then + return false, 'Это абстрактная профессия, которая используется участниками организации ' .. org.name .. '. Ты не можешь ее взять' + end +end) + +CFG.use.ent_dbg_org_board = { + function(ply, ent) + if not ent.orgId then return end + return 'Изучить', 'octoteam/icons/search.png', function(ply, ent) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, + function(ply, ent) + if not ent.orgId then return end + + local org = simpleOrgs.orgs[ent.orgId] + if not org or (not table.HasValue(org.owners, ply:SteamID()) and not ply:query('DBG: Редактировать организации')) then return end + + return 'Управление', 'octoteam/icons/group3.png', function(ply, ent) + local ok, msg = ply:OpenOrgEditor(ent.orgId) + if msg then ply:Notify(ok and 'hint' or 'warning', msg) end + end + end, + function(ply, ent) + if not ply:IsSuperAdmin() then return end + return 'Настроить', 'octoteam/icons/keypad.png', function(ply, ent) + octolib.request.send(ply, { + { + name = 'Идентификатор организации', + desc = 'Организации создаются через разработчиков', + type = 'comboBox', + opts = octolib.table.mapSequential(table.GetKeys(simpleOrgs.orgs), function(v) return {simpleOrgs.orgs[v].name, v, ent.orgId == v} end), + required = true, + } + }, function(data) + ent:SetOrgID(data[1]) + end) + end + end, +} + +local function simpleOrgAccess(ply, ent) + + local data = dbgEstates.getData(ent:GetEstateID()) + if not data or not data.owners then return end + for _,v in ipairs(data.owners) do + if string.StartWith(v, 'g:') then + if v == 'g:police' then + for k, v in pairs(simpleOrgs.orgs) do + if v.police and ply:IsOrgMember(k) then + return true + end + end + else + local gID = v:sub(3) + if ply:IsOrgMember(gID) then + return true + end + end + end + end + +end +hook.Add('canKeysLock', 'dbg-orgs.doors', simpleOrgAccess) +hook.Add('canKeysUnlock', 'dbg-orgs.doors', simpleOrgAccess) + +hook.Add('dbg-winter.isWarm', 'dbg-orgs', function(ply) + if ply.dbgPolice_citizenData then + return true + end +end) + +hook.Add('PlayerDisconnected', 'dbg-orgs.customClothes', function(ply) + local clothes = ply.dbgPolice_citizenData and ply.dbgPolice_citizenData.cl + if clothes then + ply:SetDBVar('customClothes', clothes) + end +end) + +local function getClothes(mdl, clothesData) + for prefix, clothes in pairs(clothesData) do + if string.StartWith(mdl, prefix) then return clothes end + end +end + +netstream.Hook('dbg-orgs.clothes', function(ply, clID, value) + + if not (clID and value) then return end + local org = ply.currentOrg and simpleOrgs.orgs[ply.currentOrg] + if not (org and org.clothes) then return end + + local clothes = getClothes(ply:GetModel(), org.clothes) + if not (clothes and clothes[clID]) then return end + local clothing = clothes[clID] + if clothing.vals[value] then + ply:SetBodygroup(clothing.bodygroup, value) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-orgdescs/lua/orgs/sv_multirank.lua b/garrysmod/addons/feature-orgdescs/lua/orgs/sv_multirank.lua new file mode 100644 index 0000000..28d4954 --- /dev/null +++ b/garrysmod/addons/feature-orgdescs/lua/orgs/sv_multirank.lua @@ -0,0 +1,148 @@ +local function getModels(orgID, rank, originMdls) + + local org = simpleOrgs.orgs[orgID] + if not (org and org.ranks[rank]) then return {} end + if not org.ranks[rank] then return {} end + local rankData = org.ranks[rank] + + local mdls = table.Copy(rankData.mdls) + for _, v in ipairs(mdls) do + v.name = v.name:format(rankData.shortName) + v.requiredSkin = v.everyday and rankData.everydaySkin or rankData.skin or v.requiredSkin or 0 + v.requiredMats = v.everyday and rankData.everydayMats or rankData.requiredMats or v.requiredMats or nil + end + + return mdls +end + +hook.Add('simple-orgs.overrideModels', 'simple-orgs.multirank', function(ply, orgID, originMdls, ent) + local org = simpleOrgs.orgs[orgID] + if not org.multirank then return end + + -- trying to determine current rank + local ranks = ply:GetOrgRanks(orgID) + if not ranks[1] then return {} end + if not ranks[2] then + ply:SetNetVar('activeRank', { orgID, ranks[1] }) + return getModels(orgID, ranks[1], originMdls) + end + if ply.tempOrg == orgID and ply.tempOrgRank then + local mdls = getModels(orgID, ply.tempOrgRank, originMdls) + ply.tempOrg, ply.tempOrgRank = nil + return mdls + end + + -- asking about preferred rank + octolib.request.send(ply, {rank = { + name = 'Ранг', + desc = 'Тебе разрешено использовать несколько рангов. Выбери тот, который соответствует твоему текущему персонажу', + type = 'comboBox', + opts = octolib.table.mapSequential(ranks, function(v) return org.ranks[v] and {org.ranks[v].shortName, v} or nil end), + required = true, + }}, function(data) + if not data or not org.ranks[data.rank or ''] then return end + if IsValid(ent) then + ply:SetNetVar('activeRank', { orgID, data.rank }) + ply.tempOrg, ply.tempOrgRank = orgID, data.rank + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end) + return {} + +end) + +local function class(v) return istable(v) and v[1] or v end + +local function parseOldTeam(ply, old, new) + local oldOrgID = RPExtraTeams[old].orgID + if not oldOrgID then return end + local oldOrg = simpleOrgs.orgs[oldOrgID] + if not (oldOrg and oldOrg.multirank) then return end + + if new ~= TEAM_ADMIN and ply:GetActiveOrg() == oldOrgID then ply:SetNetVar('activeRank', nil) end + local hMask = ply:GetNetVar('hMask') + if hMask and hMask[1] == 'gasmask' and not ply:CanUnmask() then + ply:SetNetVar('hMask', nil) + end + ply:SetNetVar('customJob', nil) + ply.orgWeps = nil +end + +local function parseNewTeam(ply, old, new) + local newOrgID = RPExtraTeams[new].orgID + if not newOrgID then return end + local newOrg = simpleOrgs.orgs[newOrgID] + if not (newOrg and newOrg.multirank) then return end + + local rank = ply:GetActiveRank(newOrgID) + if not rank or not newOrg.ranks[rank] then return end + local rankData = newOrg.ranks[rank] + + timer.Simple(0, function() + + if rankData.weps then + ply.orgWeps = octolib.table.mapSequential(rankData.weps, class) + for _, v in ipairs(rankData.weps) do + if istable(v) then + local wep = ply:Give(v[1]) + if IsValid(wep) then + wep.WorldModel = v[2] + wep:Initialize() + local clip1 = wep:GetMaxClip1() + if clip1 then + wep:SetClip1(clip1) + end + end + else + local wep = ply:Give(v) + if IsValid(wep) then + local clip1 = wep:GetMaxClip1() + if clip1 then + wep:SetClip1(clip1) + end + end + end + end + end + + if rankData.excludeWeps then + for _, v in ipairs(rankData.excludeWeps) do + ply:StripWeapon(v) + end + end + + if rankData.ammo then + for _, v in ipairs(rankData.ammo) do + ply:SetAmmo(v[2], v[1]) + end + end + + local armor + if isfunction(rankData.armor) then + armor = rankData.armor(ply) + else armor = rankData.armor end + if armor then + ply:SetArmor(armor) + end + + end) + + ply:SetNetVar('customJob', {rankData.name, rankData.icon}) + +end + +hook.Add('OnPlayerChangedTeam', 'simple-orgs.multirank', function(ply, old, new) + parseOldTeam(ply, old, new) + parseNewTeam(ply, old, new) +end) + +hook.Add('simple-orgs.getAmmo', 'simple-orgs.multirank', function(ply, orgID) + local org = simpleOrgs.orgs[orgID] + if not org.multirank then return end + local rank = ply:GetActiveRank(orgID) + if not (rank and org.ranks[rank]) then return end + + for _, v in ipairs(org.ranks[rank].ammo or {}) do + ply:SetAmmo(math.max(ply:GetAmmoCount(v[1]), v[2]), v[1]) + end +end) diff --git a/garrysmod/addons/feature-peepholes/lua/autorun/client/cl_peepholes.lua b/garrysmod/addons/feature-peepholes/lua/autorun/client/cl_peepholes.lua new file mode 100644 index 0000000..d7ac7fa --- /dev/null +++ b/garrysmod/addons/feature-peepholes/lua/autorun/client/cl_peepholes.lua @@ -0,0 +1,81 @@ +local fisheye = CreateClientConVar('cl_dbg_fisheyeonpeepholes', '1', true) +local hint = false + +local x, y, w, h, r, seg, poly = ScrW() / 2, ScrH() / 2, 128, 128, ScrH() / 2.5, 46, {} +poly[#poly+1] = {x = x, y = y, u = 0.5, v = 0.5} +for i = 0, seg do + local a = math.rad((i/seg) * -360) + poly[#poly+1] = {x = x + math.sin(a) * r, y = y + math.cos(a) * r, u = math.sin(a) / 2 + 0.5, v = math.cos(a) / 2 + 0.5} +end + +local rt = GetRenderTarget("peepholes", ScrW(), ScrH()) +local busy, view = false + +local function updateRender() + + busy = true + render.PushRenderTarget(rt) + local ang = LocalPlayer():EyeAngles() + render.RenderView(view) + render.PopRenderTarget() + busy = false + +end + +netstream.Hook('dbg-doors.peephole', function(nview) + view = nview + if view then view.w, view.h = ScrW(), ScrH() end +end) + +hook.Add('PreDrawEffects', 'dbg-doors.peephole', function() + + if busy or not view then return end + + updateRender() + + -- prepare + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilTestMask(255) + render.SetStencilWriteMask(255) + render.SetStencilReferenceValue(42) + + -- make mask + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilZFailOperation(STENCIL_KEEP) + cam.Start2D() + surface.SetDrawColor(255, 255, 255) + draw.NoTexture() + surface.DrawPoly(poly) + cam.End2D() + + -- draw view + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilFailOperation(STENCIL_ZERO) + render.SetStencilZFailOperation(STENCIL_ZERO) + render.DrawTextureToScreen(rt) + + -- darken screen + render.SetStencilCompareFunction(STENCIL_NOTEQUAL) + cam.Start2D() + draw.RoundedBox(0, 0, 0, ScrW(), ScrH(), color_black) + cam.End2D() + render.SetStencilEnable(false) + +end) + +hook.Add('RenderScreenspaceEffects', 'dbg-doors.peepholes.overlay', function() + if not fisheye:GetBool() or not view then return end + DrawMaterialOverlay('models/props_c17/fisheyelens', -0.05) +end) + +hook.Add('HUDPaint', 'dbg-doors.peepholes.tutorial', function() + + if view then + hint = true + draw.DrawText('Нажми E, чтобы перестать смотреть в глазок', 'CloseCaption_Normal', ScrW() / 2, ScrH() - 48, color_white, TEXT_ALIGN_CENTER) + elseif hint then hook.Remove('HUDPaint', 'dbg-doors.peepholes.tutorial') end + +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-peepholes/lua/autorun/server/sv_peepholes.lua b/garrysmod/addons/feature-peepholes/lua/autorun/server/sv_peepholes.lua new file mode 100644 index 0000000..4597f60 --- /dev/null +++ b/garrysmod/addons/feature-peepholes/lua/autorun/server/sv_peepholes.lua @@ -0,0 +1,161 @@ +peepholes = peepholes or {} + +local function getDoorWidth(door) + local mins, maxs = door:GetModelBounds() + local dx, dy = maxs.x - mins.x, maxs.y - mins.y + return math.abs(dx) > math.abs(dy) and dx or dy +end + +-- 1 = in front of door, 2 = behind the door +local function getRelativePosition(ply, door) + return ply:GetAimVector():Dot(door:GetForward()) <= 0 and 1 or 2 +end + +-- 1 = forward, -1 = backward +local function calculatePos(door, fw) + return Vector(0.3 * fw, getDoorWidth(door) / 2 - 2, 16) +end + +-- true = in front of door, false = behind door +local function calculateAngle(door, fw) + return Angle(0, fw and 0 or 180, 0) +end + +local function stopViewing(ply) + + ply.peephole = nil + netstream.Start(ply, 'dbg-doors.peephole', nil) + timer.Simple(0.5, function() ply:Freeze(false) end) + +end + +function peepholes.setup(ply, door) + + local dir = getRelativePosition(ply, door) + if not door.peephole then door.peephole = {} end + if door.peephole[dir] then return false, 'На этой стороне двери уже установлен глазок' end + + local pos = calculatePos(door, dir == 1 and 1 or -1) + if not pos then return false, 'Не получится поставить глазок на движущуюся дверь' end + + local ph = ents.Create('prop_dynamic') + ph:SetModel('models/maxofs2d/camera.mdl') + ph:SetParent(door) + ph:SetModelScale(0.4) + ph:SetLocalPos(pos) + ph:SetLocalAngles(calculateAngle(door, dir == 1)) + ph:Spawn() + door:DeleteOnRemove(ph) + door.peephole[dir] = ph + + return true, 'Глазок установлен' + +end + +function peepholes.hasPeephole(door) + return door.peephole ~= nil +end + +function peepholes.canAccess(ply, door) + local dir = getRelativePosition(ply, door) + return door.peephole and door.peephole[dir] and IsValid(door.peephole[dir]) +end + +function peepholes.view(ply, door) + + if not peepholes.canAccess(ply, door) then return end + if ply.peephole then return end + + local dir = getRelativePosition(ply, door) + local ent = door.peephole[dir] + local pos, ang = Vector(ent:GetPos()), Angle(ent:GetAngles()) + pos = pos - ent:GetForward() * 10 + ang:RotateAroundAxis(Vector(0,0,1), 180) + netstream.Start(ply, 'dbg-doors.peephole', { + origin = pos, + angles = ang, + x = 0, y = 0, + fov = 150, + }) + + ply.peephole = ent + ply:Freeze(true) + +end + +function peepholes.remove(door, dir) + + if not door.peephole or not door.peephole[dir] then return end + local ent = door.peephole[dir] + for _,pl in ipairs(player.GetAll()) do + if pl.peephole == ent then stopViewing(pl) end + end + ent:Remove() + door.peephole[dir] = nil + if not door.peephole[1] and not door.peephole[2] then door.peephole = nil end + +end + + +hook.Add('Move', 'dbg-doors.peephole.quit1', function(ply, data) + if ply.peephole and data:GetVelocity() ~= Vector() then + stopViewing(ply) + end +end) + +hook.Add('dbg-doors.unowned', 'dbg-doors.peephole.remove', function(ent) + if ent.peephole then + peepholes.remove(ent, 1) + peepholes.remove(ent, 2) + end +end) + +local exitBtns = {KEY_E, KEY_LCONTROL, KEY_SPACE} + +hook.Add('PlayerButtonDown', 'dbg-doors.peepholes', function(ply, btn) + if ply.peephole and table.HasValue(exitBtns, btn) then stopViewing(ply) end +end) + +local classes = {'prop_door_rotating'} +octoinv.registerItem('peephole', { + name = 'Глазок', + icon = 'octoteam/icons/bubble.png', + mass = 0.08, + volume = 0.1, + desc = 'Загляни за дверь, не выходя из дома!', + use = { + function(ply, item) + return L.set, 'octoteam/icons/wrench.png', function(ply, item) + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) or not table.HasValue(classes, ent:GetClass()) then ply:Notify('warning', 'Нужно смотреть на дверь') return end + if ent:GetPlayerOwner() ~= ply:SteamID() then ply:Notify('warning', 'Глазок можно установить только на свою дверь') return end + if ply:HasItem('tool_screwer') < 1 then ply:Notify('warning', 'Для установки нужна отвертка') return end + + ply:DelayedAction('peephole_mount', 'Установка', { + time = 5, + check = function() return octolib.use.check(ply, ent) and ply:HasItem('tool_screwer') >= 1 and ply:HasItem('peephole') >= 1 end, + succ = function() + if ent:GetPlayerOwner() ~= ply:SteamID() then ply:Notify('warning', 'Глазок можно установить только на свою дверь') return end + if ply:HasItem('tool_screwer') < 1 then ply:Notify('warning', 'Для установки нужна отвертка') return end + + local result, msg = peepholes.setup(ply, ent) + if result then ply:TakeItem('peephole', 1) end + + ply:Notify(result and 'rp' or 'warning', msg) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:EmitSound('ambient/machines/pneumatic_drill_' .. math.random(1, 4) .. '.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, + }, +}) + +octoinv.addShopItem('peephole', { + cat = 'security', price = 150 +}) diff --git a/garrysmod/addons/feature-phone/lua/autorun/phone.lua b/garrysmod/addons/feature-phone/lua/autorun/phone.lua new file mode 100644 index 0000000..242a48a --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/autorun/phone.lua @@ -0,0 +1 @@ +octolib.module('phone') \ No newline at end of file diff --git a/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/cl_init.lua b/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/cl_init.lua new file mode 100644 index 0000000..8b9944c --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/cl_init.lua @@ -0,0 +1,14 @@ +include 'shared.lua' + +local titlePosAngByModels = { + ['models/props_equipment/phone_booth.mdl'] = {Vector(16.6, 0, 74), Angle(0, 90, 90)}, + -- ['models/props/cs_office/phone.mdl'] = {Vector(-5, 0, 5), Angle(0, 90, 90)}, +} + +function ENT:Draw() + self:DrawModel() + local posang = titlePosAngByModels[self:GetModel()] + if posang then + render.DrawBubble(self, posang[1], posang[2], L.phone, 200, 200) + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/init.lua b/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/init.lua new file mode 100644 index 0000000..6d1beac --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/init.lua @@ -0,0 +1,26 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + +end + +function ENT:GetNick() + if not IsValid(self.owner) then return '(стационарный)' end + return self.owner:Nick() +end + +function ENT:Use(ply) + + if not self.off then + netstream.Start(ply, 'dbg-phone.open', true, self) + end + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/shared.lua b/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/shared.lua new file mode 100644 index 0000000..61ede62 --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/entities/ent_dbg_phone/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = L.phone +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.Model = 'models/props_equipment/phone_booth.mdl' \ No newline at end of file diff --git a/garrysmod/addons/feature-phone/lua/phone/client.lua b/garrysmod/addons/feature-phone/lua/phone/client.lua new file mode 100644 index 0000000..df04577 --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/phone/client.lua @@ -0,0 +1,313 @@ +function req(title, desc, func) + return function() + Derma_StringRequest(title, desc, '', func, nil, L.ok, L.cancel) + end +end + +local function sms() + local text, tgtName, check = '', '' + local f = vgui.Create 'DFrame' + f:SetSize(400, 135) + f:SetTitle(L.send_sms) + f:Center() + f:MakePopup() + f:SetBackgroundBlur(true) + + local b = f:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(30) + b:SetText(L.send) + b:SetEnabled(false) + function b:DoClick() + netstream.Start('chat', ('/sms "%s" %s'):format(tgtName, text)) + f:Remove() + end + + local function check() + b:SetEnabled(tgtName ~= '' and string.Trim(text) ~= '') + end + + local c = f:Add 'DComboBox' + c:Dock(TOP) + c:SetTall(30) + c:DockMargin(0, 0, 0, 5) + c:SetValue(L.recipient) + for i, v in ipairs(player.GetAll()) do if v ~= LocalPlayer() then c:AddChoice(v:Name(), v:Name()) end end + function c:OnSelect(i, val, data) + tgtName = data + check() + end + + local e = f:Add 'DTextEntry' + e:Dock(TOP) + e:SetTall(20) + e:DockMargin(5, 5, 5, 10) + e:SetUpdateOnType(true) + e:SetPlaceholderText(L.text_msg) + e.PaintOffset = 5 + function e:OnValueChange(val) + text = val + check() + end +end + +local function anyOfTeam(func) + for _, v in ipairs(player.GetAll()) do + if func(v) then return true end + end + return false +end +local function doCommand(start) + return function(s) + octochat.say(start .. (s and (' ' .. s) or '')) + end +end + +netstream.Hook('dbg-phone.open', function(center, ent) + + gui.EnableScreenClicker(true) + + local menu = DermaMenu() + local pm, smo = menu:AddSubMenu(L.call_hint) + + pm:AddOption(L.ems_hint, req(L.call_ems, 'На вызов могут отреагировать полицейские, медики и спасатели.\n' .. L.desc_sit_and_place, + function(s) netstream.Start('dbg-phone.cr', ent, s) end)):SetIcon(octolib.icons.silk16('asterisk_yellow')) + + if anyOfTeam(DarkRP.isMedic) then + pm:AddOption(L.medic, req(L.call_medic, 'На вызов могут отреагировать медики и фармацевты.\n' .. L.desc_sit_and_place, + doCommand('/callmed'))):SetIcon(octolib.icons.silk16('user_medical')) + end + if anyOfTeam(DarkRP.isFirefighter) then + pm:AddOption('Спасателей', req('Вызов спасателей', L.desc_sit_and_place, doCommand('/callfire'))):SetIcon(octolib.icons.silk16('user_firefighter')) + end + if anyOfTeam(DarkRP.isMech) then + pm:AddOption(L.mechanic2, req(L.call_mech, L.desc_sit_and_place, doCommand('/callmech'))):SetIcon(octolib.icons.silk16('car')) + end + if anyOfTeam(DarkRP.isWorker) then + pm:AddOption('Городского рабочего', req('Вызов городского рабочего', L.desc_sit_and_place, doCommand('/callworker'))):SetIcon(octolib.icons.silk16('wrench')) + end + if anyOfTeam(DarkRP.isTaxist) then + pm:AddOption('Такси', req('Вызов такси', 'Опиши местоположение и место назначения', doCommand('/calltaxi'))):SetIcon(octolib.icons.silk16('car_taxi')) + end + + smo:SetIcon(octolib.icons.silk16('phone_handset')) + menu:AddOption('Проверить баланс', function() netstream.Start('chat', '/getbank') end):SetIcon(octolib.icons.silk16('money')) + menu:AddOption(L.send_sms, sms):SetIcon(octolib.icons.silk16('oms_new_text_message')) + menu:AddOption(L.make_advert, req(L.make_advert, L.text_advert, + function(s) netstream.Start('chat', '/ad ' .. s) end)):SetIcon(octolib.icons.silk16('advertising')) + menu:AddOption(L.make_order, function() F4:OpenWindow('shop') end):SetIcon(octolib.icons.silk16('cart_add')) + menu:Open() + + if center then menu:Center() end + + gui.EnableScreenClicker(false) + +end) + +local phone = phone or {} + +local phoneAnim = { + default = { + Bip01_L_Hand = { ang = Angle(15, 10, -35) }, + Bip01_L_Forearm = { ang = Angle(0, -100, -40) }, + Bip01_L_Clavicle = { ang = Angle(0, 0, 0) }, + Bip01_L_UpperArm = { ang = Angle(-20, -30, 0) }, + + Bip01_L_Finger0 = { ang = Angle(-5, -28, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, -20, 0) }, + Bip01_L_Finger1 = { ang = Angle(5, -18, -30) }, + Bip01_L_Finger12 = { ang = Angle(0, -10, 0) }, + Bip01_L_Finger2 = { ang = Angle(10, -18, -30) }, + Bip01_L_Finger22 = { ang = Angle(0, -10, 0) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + fov = 0.55, + }, + + sitting = { + Bip01_L_Hand = { ang = Angle(-10, -45, -105) }, + Bip01_L_Forearm = { ang = Angle(0, -22, -60) }, + Bip01_L_Clavicle = { ang = Angle(0, 0, 0) }, + Bip01_L_UpperArm = { ang = Angle(-10, -30, 0) }, + + Bip01_L_Finger0 = { ang = Angle(-15, -10, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, -20, 0) }, + Bip01_L_Finger1 = { ang = Angle(20, -18, -30) }, + Bip01_L_Finger12 = { ang = Angle(0, -10, 0) }, + Bip01_L_Finger2 = { ang = Angle(15, -12, -10) }, + Bip01_L_Finger22 = { ang = Angle(0, -10, 0) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + fov = 0.3, + }, + + crouching = { + Bip01_L_Hand = { ang = Angle(6, -30, -20) }, + Bip01_L_Forearm = { ang = Angle(0, 0, -40) }, + Bip01_L_Clavicle = { ang = Angle(5, 20, 0) }, + Bip01_L_UpperArm = { ang = Angle(30, -65, -5) }, + + Bip01_L_Finger0 = { ang = Angle(-30, 0, 0) }, + Bip01_L_Finger02 = { ang = Angle(0, -15, 0) }, + Bip01_L_Finger1 = { ang = Angle(17, 23, 0) }, + Bip01_L_Finger12 = { ang = Angle(-10, 25, 0) }, + Bip01_L_Finger2 = { ang = Angle(15, 20, -10) }, + Bip01_L_Finger22 = { ang = Angle(0, 80, 30) }, + + Bip01_Head1 = { ang = Angle(0,-10,0) }, + fov = 0.2, + }, +} + +local phonePos = { + pos = Vector(1.4, 0, 1), + ang = Angle(-240, -10, 80) +} + +hook.Add('UpdateAnimation', 'dbg-phone.UpdateAnimation', function(ply, vel) + + if not IsValid(ply) then return end + if ply:InVehicle() then return end + if ply:GetModel() == 'models/error.mdl' then return end + ply.PhoneAnimWeight = math.Approach( + ply.PhoneAnimWeight or 0, + (ply:IsUsingPhone() and ply:OnGround()) and (1 - vel:LengthSqr() / 50000) or 0, + FrameTime() * 5 + ) + + local weight = ply.PhoneAnimWeight or 0 + if weight > 0 then + local anim = phone.getProperAnim(ply) + for bone, data in pairs(anim) do + if bone ~= 'fov' then + local weapon = ply:GetActiveWeapon() + if IsValid(weapon) and weapon:GetClass():find('octo', 1, false) and (bone == 'Bip01_R_Forearm' or bone == 'Bip01_R_Hand') then continue end + local id = ply:LookupBone('ValveBiped.' .. bone) + if not id then continue end + if data.pos then ply:ManipulateBonePosition(id, data.pos * weight) end + if data.ang then ply:ManipulateBoneAngles(id, data.ang * weight) end + end + end + + local state = (IsValid(ply:GetVehicle()) and 'sitting') or 'normal' + + if not IsValid(ply.Phone) then + phone.createDummy(ply) + elseif ply.lastState ~= state then + phone.removeDummy(ply) + ply.lastState = state + return + end + + else + if IsValid(ply.Phone) then phone.removeDummy(ply) end + end + +end) + +function phone.getProperAnim(ply) + local veh = ply:GetVehicle() + local weapon = ply:GetActiveWeapon() + + if IsValid(veh) then + return phoneAnim.sitting + elseif ply:Crouching() then + return phoneAnim.crouching + else + return phoneAnim.default + end + +end + +function phone.createDummy(ply) + + if IsValid(ply) and not IsValid(ply.Phone) then + local attID = ply:LookupAttachment('anim_attachment_LH') + + local phone_m = octolib.createDummy('models/lt_c/tech/cellphone.mdl') + phone_m:SetParent(ply, attID) + phone_m:SetLocalPos(phonePos.pos) + phone_m:SetLocalAngles(phonePos.ang) + phone_m:SetModelScale(ply:GetModelScale()) + phone_m:SetSkin(5) + + ply.Phone = phone_m + end + +end + +function phone.removeDummy(ply) + + if IsValid(ply) and IsValid(ply.Phone) then + ply.Phone:Remove() + ply.Phone = nil + for bone,data in pairs(phone.getProperAnim(ply)) do + if bone ~= 'fov' then + local id = ply:LookupBone('ValveBiped.' .. bone) + if not id then continue end + if data.pos then ply:ManipulateBonePosition(id, Vector()) end + if data.ang then ply:ManipulateBoneAngles(id, Angle()) end + end + end + end +end + + +octolib.func.loop(function(done) + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if IsValid(ply) and IsValid(ply.Phone) and (not ply:Alive() or ply:GetNoDraw() or not ply:IsUsingPhone()) then + phone.removeDummy(ply) + end + end):Then(done) +end) + +local usingPhone = false +local function stopTyping(ply) + if usingPhone then + usingPhone = false + end +end + +hook.Add('KeyDown', 'dbg-phone.noJump', function(ply, key) + if (key == IN_WALK) and IsFirstTimePredicted() and usingPhone then + stopTyping(ply) + phone.removeDummy(ply) + end +end) + +local cmds = {'/sms '} +hook.Add('ChatTextChanged', 'dbg-phone', function(txt) + local ply = LocalPlayer() + if not IsValid(ply) then return end + if ply:GetVelocity():LengthSqr() > 0 then return end + + local showPhone + for _, cmd in ipairs(cmds) do + if txt:StartWith(cmd) then + showPhone = true + break + else + showPhone = false + end + end + + if showPhone then + if txt:sub(6) then netstream.Start('dbg-phone.typingSMS') end + if not usingPhone then + netstream.Start('dbg-phone.updateTypeStatus', true) + usingPhone = true + end + elseif usingPhone then + netstream.Start('dbg-phone.updateTypeStatus', false) + stopTyping(ply) + end +end) + +netstream.Hook('dbg-phone.remove', function(data) + if data and data:IsPlayer() and IsValid(data.Phone) then + phone.removeDummy(data) + if data == LocalPlayer() then + stopTyping(data) + end + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-phone/lua/phone/server.lua b/garrysmod/addons/feature-phone/lua/phone/server.lua new file mode 100644 index 0000000..1681567 --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/phone/server.lua @@ -0,0 +1,119 @@ +netstream.Hook('dbg-phone.cr', function(ply, ent, s) + if (ply.nextEMSRequest or 0) > CurTime() then return end + local name = ply:Nick() + if IsValid(ent) then + if ent:GetClass() ~= 'ent_dbg_phone' or ply:AtStationaryPhone() ~= ent then return end + name = ent:GetNick() + elseif not ply:HasMobilePhone() then return end + DarkRP.callEMS(ply, name, tostring(s)) +end) + +local ply = FindMetaTable 'Player' + +function ply:HasMobilePhone() + return self.inv and self:FindItem({class = 'phone', on = true}) ~= nil +end + +function ply:AtStationaryPhone() + local ent = octolib.use.getTrace(self).Entity + if IsValid(ent) and ent:GetClass() == 'ent_dbg_phone' and not ent.off then + return ent + end +end + +function ply:HasPhone() + return not self:IsGhost() and (self:HasMobilePhone() or self:AtStationaryPhone()) +end + +function ply:SendSMS(...) + if not self:HasMobilePhone() then return end + + octochat.talkTo(self, ...) + + local phone = self:FindItem({class = 'phone', on = true}) + if phone:GetData('notification') then + self:EmitSound('phone/phone_notification.wav', 38) + end + + if phone:GetData('vibration') then + self:EmitSound('phone/phone_vibration.wav', 25) + end +end + +hook.Add('octochat.canExecute', 'dbg-phone', function(ply, cmd) + + if octochat.commands[cmd].phone and not ply:HasPhone() then + return false, L.need_phone + end + +end) + +hook.Add('octoinv.shop.order-override', 'dbg-phone', function(ply) + + if not ply:HasPhone() then + return false, L.need_phone + end + +end) + +local function updateTypeStatus(ply, state, sounds) + + if not ply:Alive() or ply:IsGhost() or not ply:HasMobilePhone() then return end + if ply:GetNetVar('sgMuted') then return end + + if state then + local weapon = ply:GetActiveWeapon() + + if IsValid(weapon) and weapon:GetHoldType() ~= 'normal' and not ply:Crouching() and not ply:InVehicle() then + if weapon:GetHoldType() ~= 'revolver' then return end + ply.last_wep = {wep = weapon:GetClass(), hold = weapon:GetHoldType()} + weapon:SetHoldType('normal') + end + + ply:SetNetVar('UsingPhone', true) + else + ply:SetNetVar('UsingPhone', false) + if ply:Alive() then + local weapon = ply:GetActiveWeapon() + local last_wep = ply.last_wep + if last_wep then + local wep, hold = ply.last_wep.wep, ply.last_wep.hold + + if IsValid(weapon) and wep and hold and weapon:GetClass() == wep then + weapon:SetHoldType(hold or 'normal') + end + end + end + ply.last_wep = {} + end +end + +netstream.Hook('dbg-phone.updateTypeStatus', function(ply, state) + updateTypeStatus(ply, state) +end) + +octolib.func.loop(function(done) + octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + + if not IsValid(ply) then return end + if not ply:HasMobilePhone() or not ply:GetNetVar('UsingPhone') then return end + + local weapon = ply:GetActiveWeapon() + if not IsValid(weapon) then return end + + if (weapon:GetHoldType() ~= 'normal' or weapon:GetHoldType() ~= 'passive') and not ply:Crouching() and not ply:InVehicle() then + ply.last_wep = {wep = weapon:GetClass(), hold = weapon:GetHoldType()} + weapon:SetHoldType('normal') + end + + end):Then(done) +end) + +netstream.Hook('dbg-phone.typingSMS', function(ply) + if not IsValid(ply) then return end + + local phone = ply.inv and ply:FindItem({class = 'phone', on = true, notification = true}) + if phone then + ply:EmitSound('type.mp3', 50) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-phone/lua/phone/shared.lua b/garrysmod/addons/feature-phone/lua/phone/shared.lua new file mode 100644 index 0000000..cbe7102 --- /dev/null +++ b/garrysmod/addons/feature-phone/lua/phone/shared.lua @@ -0,0 +1,5 @@ +local meta = FindMetaTable 'Player' + +function meta:IsUsingPhone() + return self:GetNetVar('UsingPhone', false) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-playertest/lua/autorun/playertest.lua b/garrysmod/addons/feature-playertest/lua/autorun/playertest.lua new file mode 100644 index 0000000..16b9d32 --- /dev/null +++ b/garrysmod/addons/feature-playertest/lua/autorun/playertest.lua @@ -0,0 +1,5 @@ +octolib.server('playertest/sv_test') +octolib.client('playertest/cl_test') + +octolib.server('editor/sv_editor') +octolib.client('editor/cl_editor') \ No newline at end of file diff --git a/garrysmod/addons/feature-playertest/lua/editor/cl_editor.lua b/garrysmod/addons/feature-playertest/lua/editor/cl_editor.lua new file mode 100644 index 0000000..9bf3150 --- /dev/null +++ b/garrysmod/addons/feature-playertest/lua/editor/cl_editor.lua @@ -0,0 +1,355 @@ +local defaultAnswers = {{'Вариант 1'}, {'Вариант 2'}, {'Вариант 3'}, {'Вариант 4'}} +local defaultQuestion = { question = 'Текст вопроса', data = defaultAnswers } +local hintFormat = 'Чтобы пройти тест, понадобится ответить правильно хотя бы на %d вопрос%s из %d.' + +local function buildQuestion(fr, pnl, line, q) + + local lbl = pnl:Add 'DLabelEditable' + lbl:DockMargin(0, 0, 0, 15) + lbl:Dock(TOP) + lbl:SetAutoStretchVertical(true) + lbl:SetAutoStretch(true) + lbl:SetWrap(true) + lbl:SetMouseInputEnabled(true) + lbl:SetKeyboardInputEnabled(true) + lbl:SetText(q.question) + lbl:SetFont('dbg-test.small') + function lbl:OnLabelTextChanged(txt) + if q.question ~= txt then + fr:OnContentChange() + end + q.question = txt + line:SetColumnText(1, txt) + end + + for i, ans in ipairs(q.data) do + local cbp = pnl:Add 'DPanel' + cbp:Dock(TOP) + cbp:DockMargin(0, 0, 0, 5) + cbp:SetTall(30) + cbp:SetPaintBackground(false) + + local isCorrect = ans[2] + + local cb = cbp:Add 'DButton' + cb:DockMargin(0, 0, 10, 0) + cb:Dock(LEFT) + cb:SetWide(24) + cb:SetText('') + cb:SetPaintBackground(false) + function cb:DoClick() + isCorrect = not isCorrect + self:UpdateValue() + ans[2] = isCorrect or nil + fr:OnContentChange() + end + function cb:UpdateValue() + self:SetIcon(isCorrect and 'icon16/tick.png' or 'icon16/cross.png') + end + cb:UpdateValue() + + local cbl = cbp:Add 'DLabelEditable' + cbl:Dock(FILL) + cbl:SetContentAlignment(4) + cbl:SetWrap(true) + cbl:SetMouseInputEnabled(true) + cbl:SetKeyboardInputEnabled(true) + cbl:SetText(ans[1]) + function cbl:OnLabelTextChanged(txt) + if ans[1] ~= txt then + fr:OnContentChange() + end + ans[1] = txt + end + + end + +end + +local function buildCategory(fr, pnl, catID, questions) + local leftWrap = pnl:Add 'DPanel' + leftWrap:Dock(FILL) + leftWrap:DockMargin(0, 0, 5, 0) + + local l = leftWrap:Add 'DListView' + l:Dock(FILL) + l:AddColumn('Название') + l:SetHideHeaders(true) + l:SetMultiSelect(false) + pnl.lst = l + + for _, v in ipairs(questions) do + local line = l:AddLine(v.question) + line.question = v + end + + local testPnl = pnl:Add 'DPanel' + testPnl:Dock(RIGHT) + testPnl:SetWide(400) + testPnl:SetPaintBackground(false) + testPnl:DockPadding(5, 5, 5, 5) + + function l:OnClickLine(line, clear) + if not clear then return end + if line.lastClick then + local fTimeDistance = SysTime() - line.lastClick + if fTimeDistance < 0.3 then + return self:DoDoubleClick(line:GetID(), line) + end + + end + line.lastClick = SysTime() + end + + function l:DoDoubleClick(_, line) + self:ClearSelection() + line:SetSelected(true) + testPnl:Clear() + buildQuestion(fr, testPnl, line, line.question) + end + + function l:OnRowRightClick(lineID, line) + local menu = DermaMenu() + menu:AddOption('Редактировать', function() self:DoDoubleClick(lineID, line) end):SetIcon(octolib.icons.silk16('pencil')) + menu:AddOption('Удалить', function() + self:RemoveLine(lineID) + table.RemoveByValue(fr.cache[catID], line.question) + if #fr.cache[catID] < 1 then + fr.cache[catID] = nil + pnl:DoRemove() + end + fr:OnContentChange() + end):SetIcon(octolib.icons.silk16('cross')) + menu:Open() + end + + local createBtn = leftWrap:Add 'DButton' + createBtn:Dock(BOTTOM) + createBtn:SetText('Создать') + createBtn.DoClick = octolib.fStringRequest('Новый вопрос', 'Введи вопрос', 'Текст вопроса', function(str) + local data = { question = str, data = table.Copy(defaultAnswers) } + local line = l:AddLine(str) + line.question = data + l:ClearSelection() + l:SelectItem(line) + fr.cache[catID][#fr.cache[catID] + 1] = data + fr:OnContentChange() + end) +end + +local function drawBlur(self, w, h) + draw.RoundedBox(0, 0, 0, w, h, CFG.skinColors.bg60) +end +netstream.Hook('dbg-test.edit', function(data, questionsAmount, required) + if IsValid(dbgTest.editFr) then + dbgTest.editFr:Remove() + end + + local fr = vgui.Create 'DFrame' + fr:SetSize(600, 500) + fr:SetTitle('Редактор теста') + fr:Center() + fr:MakePopup() + + local cache = table.Copy(data) + fr.cache = cache + fr.saved = true + local qaPnl = octolib.slider(fr, 'Количество пачек вопросов в тесте:', 1, math.huge, 0) + qaPnl:SetValue(questionsAmount) + local totalPnl, reqPnl + function qaPnl:UpdateData() + local maxBatches = math.huge + for _, cat in pairs(cache) do + if next(cat) then + maxBatches = math.min(maxBatches, table.Count(cat)) + end + end + self:SetMax(maxBatches) + local val = self:GetValue() + self:SetValue(self:GetMax()) -- update slider pos + self:SetValue(math.min(val, self:GetMax())) + if totalPnl then totalPnl:UpdateData() end + end + qaPnl:UpdateData() + function qaPnl:OnValueChanged() + reqPnl:UpdateData() + end + + reqPnl = octolib.slider(fr, 'Проходной балл:', 0, math.Round(qaPnl:GetValue()), 0) + reqPnl:SetValue(required) + function reqPnl:UpdateData() + local batches = 0 + for _, cat in pairs(cache) do + if next(cat) then + batches = batches + 1 + end + end + self:SetMax(batches * math.floor(qaPnl:GetValue())) + local val = self:GetValue() + self:SetValue(self:GetMax()) -- update slider pos + self:SetValue(math.min(val, self:GetMax())) + totalPnl:UpdateData() + end + function reqPnl:OnValueChanged() + totalPnl:UpdateData() + if fr.OnChange then fr:OnChange() end -- right after creation, it's not neccessary to enable save button + end + + totalPnl = octolib.label(fr, '') + totalPnl:DockMargin(0, 0, 0, 20) + totalPnl:SetContentAlignment(5) + function totalPnl:UpdateData() + self:SetText(hintFormat:format(reqPnl:GetValue(), octolib.string.formatCount(reqPnl:GetValue(), '', 'а', 'ов'), reqPnl:GetMax())) + end + reqPnl:UpdateData() + + + local catsCont = fr:Add 'DPropertySheet' + catsCont:Dock(FILL) + + local btmCont = fr:Add 'DPanel' + btmCont:Dock(BOTTOM) + btmCont:SetTall(30) + + local saveBtn = btmCont:Add 'DButton' + saveBtn:Dock(FILL) + saveBtn:SetText('Сохранено') + saveBtn:SetEnabled(false) + function saveBtn:DoClick() + netstream.Start('dbg-test.edit', fr.cache, math.Round(qaPnl:GetValue()), math.Round(reqPnl:GetValue())) + fr.saved = true + self:SetEnabled(false) + self:SetText('Сохранено') + end + + local test = btmCont:Add 'DButton' + test:Dock(LEFT) + test:DockMargin(0, 0, 5, 0) + test:SetText('Пример теста') + test:SetWide(185) + + function test:DoClick() + local w = vgui.Create 'DPanel' + dbgTest.frame = w + w:Dock(FILL) + w:DockPadding(30, 20, 30, 20) + w:MakePopup() + self:SetEnabled(false) + + local required = math.Round(reqPnl:GetValue()) + local correct + local function onError(err) + ErrorNoHaltWithStack(err) + w:Remove() + if self:IsValid() then + self:SetEnabled(true) + end + end + netstream.Request('dbg-test.sample', cache, math.Round(qaPnl:GetValue())):Then(function(data) + correct = data.answers + dbgTest.create(data.questions, required, function() + dbgTest.remove() + if self:IsValid() then + self:SetEnabled(true) + end + end, function() + octolib.func.chain({ + function(nxt) + Derma_Query('Отправляя ответы, ты подтверждаешь, что...\n• Знаешь правила сервера;\n• Осознаешь последствия возможного их нарушения', 'Последнее уточнение', 'ОК', nxt, 'Отмена') + end, + function(nxt) + dbgTest.remove() + netstream.Request('dbg-test.sampleScore', dbgTest.answers, correct):Then(nxt):Catch(onError) + end, + function(nxt, data) + local scores = octolib.array.map(data.scores, function(score) + return ('%s%% - %s из %s - %s'):format(math.Round(score[2] / score[3] * 100), score[2], score[3], score[1]) + end) + Derma_Message( + ('%s\nИтого: %.1f/%d (тест %sпройден)'):format(table.concat(scores, '\n'), data.total, required, data.total < required and 'не ' or ''), + 'Результат', + 'OK') + if self:IsValid() then + self:SetEnabled(true) + end + end, + }) + end) + end):Catch(onError) + w.Paint = drawBlur + end + + function fr:OnChange() + self.saved = false + saveBtn:SetEnabled(true) + saveBtn:SetText('Сохранить') + end + + function fr:OnContentChange() + self:OnChange() + qaPnl:UpdateData() + reqPnl:UpdateData() + end + + local function removeTab(self) + local items = catsCont:GetItems() + for i, tab in ipairs(items) do + if tab.Panel == self then + table.remove(items, i) + table.RemoveByValue(catsCont.tabScroller.Panels, tab.Tab) + tab.Tab:Remove() + catsCont.tabScroller:InvalidateLayout(true) + if items[i] then + catsCont:SetActiveTab(items[i].Tab) + end + break + end + end + self:Remove() + end + for cat, v in pairs(cache) do + local pnl = catsCont:Add 'DPanel' + pnl.DoRemove = removeTab + buildCategory(fr, pnl, cat, v) + catsCont:AddSheet(cat, pnl) + end + + local newCat = catsCont:Add 'DPanel' + local createCatBtn = newCat:Add 'DButton' + createCatBtn:Dock(FILL) + createCatBtn:SetText('Нажми, чтобы создать категорию вопросов') + function createCatBtn:Paint(w, h) + derma.SkinHook('Paint', 'Panel', self, w, h) + end + createCatBtn:SetContentAlignment(5) + createCatBtn:SetFont('dbg-test.medium') + local sheet = catsCont:AddSheet('', newCat, octolib.icons.silk16('plus')) + + function createCatBtn:DoClick() + Derma_StringRequest('Новая категория', 'Укажи название категории', 'Категория ' .. (table.Count(cache)+1), function(str) + if cache[str] then return end + table.remove(catsCont.Items) + table.remove(catsCont.tabScroller.Panels) + sheet.Tab:Remove() + local pnl = catsCont:Add 'DPanel' + pnl.DoRemove = removeTab + cache[str] = {table.Copy(defaultQuestion)} + buildCategory(fr, pnl, str, cache[str]) + catsCont:SetActiveTab(catsCont:AddSheet(str, pnl).Tab) + sheet = catsCont:AddSheet('', newCat, octolib.icons.silk16('plus')) + fr:OnContentChange() + end) + end + + local oClose = fr.Close + function fr:Close() + if not self.saved then + octolib.confirmDialog(self, 'Сохранить изменения?', function(b) + self.saved = true + if b then saveBtn:DoClick() end + oClose(self) + end) + else oClose(self) end + end + +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-playertest/lua/editor/sv_editor.lua b/garrysmod/addons/feature-playertest/lua/editor/sv_editor.lua new file mode 100644 index 0000000..27f4c5c --- /dev/null +++ b/garrysmod/addons/feature-playertest/lua/editor/sv_editor.lua @@ -0,0 +1,116 @@ +hook.Add('octolib.db.init', 'dbg-test.editor', function() + octolib.db:RunQuery([[CREATE TABLE IF NOT EXISTS `test_questions` ( + `category` TINYTEXT NOT NULL, + `question` TEXT NOT NULL, + `data` MEDIUMTEXT NOT NULL + )]], dbgTest.updateQuestions) +end) + +function dbgTest.updateQuestions() + dbgTest.questions = {} + octolib.db:RunQuery([[SELECT * FROM `test_questions`]], function(q, st, res) + if istable(res) then + local questions = {} + for _, v in ipairs(res) do + if not questions[v.category] then questions[v.category] = {} end + questions[v.category][#questions[v.category] + 1] = v + v.data = util.JSONToTable(v.data) + v.category = nil + end + dbgTest.questions = questions + end + end) +end + +local function updateLogs() + file.CreateDir('test_fallback') + local logs = file.Find('test_fallback/*.dat', 'DATA') + local ct = os.time() + for _, v in ipairs(logs) do + local fl = 'test_fallback/' .. v + if ct - file.Time(fl, 'DATA') > 10 * 24 * 60 * 60 then -- 10 days + file.Delete(fl) + end + end + file.Write('test_fallback/' .. octolib.string.uuid() .. '.dat', pon.encode(dbgTest.questions)) +end + +netstream.Listen('dbg-test.sample', function(res, ply, questions, maxInBatch) + if not (ply:query('DBG: Редактировать тест') and istable(questions) and isnumber(maxInBatch)) then + return + end + local us, them = dbgTest.generate(questions, maxInBatch) + res({ + questions = them, + answers = us, + }) +end) + +netstream.Listen('dbg-test.sampleScore', function(res, ply, answers, correct) + if not (ply:query('DBG: Редактировать тест') and istable(answers) and istable(correct)) then + return + end + local total, scores = dbgTest.calcScore(answers, correct) + res({ + total = total, + scores = scores, + }) +end) + +netstream.Hook('dbg-test.edit', function(ply, data, maxInBatch, required) + if not ply:query('DBG: Редактировать тест') then + return + end + + local questions = {} + local amount = 0 + for catID, catSrc in pairs(data) do + if not next(catSrc) then return end + maxInBatch = math.min(maxInBatch, #catSrc) + amount = amount + 1 + for _, v in ipairs(catSrc) do + local question = { question = v.question, data = {}, category = catID } + for i = 1, 4 do + question.data[i] = { v.data[i][1], tobool(v.data[i][2]) or nil } + end + questions[#questions + 1] = question + end + end + required = math.min(required, maxInBatch * amount) + if required <= 0 or maxInBatch <= 0 then return end + + octolib.func.chain({ + function(nxt) + octolib.db:RunQuery([[TRUNCATE TABLE `test_questions`]], nxt) + end, + function(nxt) + local str = table.concat(octolib.array.map(questions, function(q) + return ([[('%s','%s','%s')]]):format(octolib.db:escape(q.question), octolib.db:escape(q.category), octolib.db:escape(util.TableToJSON(q.data))) + end), ',') + octolib.db:RunQuery([[INSERT INTO `test_questions` (`question`, `category`, `data`) VALUES ]] .. str, nxt) + end, + function(_, q, st, res) + updateLogs() + if not st then + if IsValid(ply) then ply:Notify('warning', 'Произошла ошибка при сохранении') end + else + octolib.vars.set('dbgTest.maxInBatch', maxInBatch) + octolib.vars.set('dbgTest.required', required) + octolib.sendCmdToOthers('updateTest') + dbgTest.updateQuestions() + ply:Notify('hint', 'Сохранено успешно') + hook.Run('dbg-test.edit', ply) + end + end, + }) +end) + +octolib.vars.init('dbgTest.maxInBatch', 4) +octolib.vars.init('dbgTest.required', 5) + +concommand.Add('dbg_test_edit', function(ply) + if not IsValid(ply) or not ply:query('DBG: Редактировать тест') then + return + end + netstream.Heavy(ply, 'dbg-test.edit', dbgTest.questions, octolib.vars.get('dbgTest.maxInBatch'), octolib.vars.get('dbgTest.required')) +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-playertest/lua/playertest/cl_test.lua b/garrysmod/addons/feature-playertest/lua/playertest/cl_test.lua new file mode 100644 index 0000000..cc3119a --- /dev/null +++ b/garrysmod/addons/feature-playertest/lua/playertest/cl_test.lua @@ -0,0 +1,771 @@ +if dbgTest then dbgTest.remove() end + +local test = {} +dbgTest = test + +local cols = CFG.skinColors + +surface.CreateFont('dbg-test.icons', { + font = 'FontAwesome', + extended = true, + size = 24, + weight = 400, +}) + +surface.CreateFont('dbg-test.title', { + font = 'Calibri', + extended = true, + size = 52, + weight = 350, +}) + +surface.CreateFont('dbg-test.medium', { + font = 'Calibri', + extended = true, + size = 36, + weight = 350, +}) + +surface.CreateFont('dbg-test.normal', { + font = 'Calibri', + extended = true, + size = 24, + weight = 350, +}) + +surface.CreateFont('dbg-test.small', { + font = 'Calibri', + extended = true, + size = 21, + weight = 350, +}) + +local servers = { + ['37.230.137.242:27017'] = { + name = 'Центральный Доброград', + desc = [[Самый развитый район, построенный в период бурного экономического развития города. Он размещается возле исторического района и имеет специфическую архитектуру, сочетая высотные здания и живописную загородную часть, в которую входит шахта, озеро, ранчо, элитный жилой район, мотель и трейлерный парк. Является социально-экономическим центром всего города, из-за чего и происходит его название + +Благодаря просторной карте автомобильная инфраструктура развита, а слоты для игроков увеличены]], + }, + ['37.230.137.242:27018'] = { + name = 'Новый Доброград', + desc = [[С ходом времени Доброград стал расширяться, и у него появился пригород. Новый Доброград – современный район города, расположившийся на обширной территории. Здесь есть районный центр, озеро с загородными коттеджами, а также промышленная зона + +Благодаря просторной карте автомобильная инфраструктура развита, а слоты для игроков увеличены]], + }, +-- ['37.230.137.242:27019'] = { +-- name = 'Новый Доброград #2', +-- desc = [[Изначально здесь было шахтерское поселение, находящееся вблизи Доброграда, но спустя время из-за бурного роста оно вошло в состав города как новый район. Богатая природными ресурсами, здешняя местность дает мощный толчок развитию различной промышленности + +-- Благодаря просторной карте автомобильная инфраструктура развита, а слоты для игроков увеличены. Несмотря на слегка более лояльный тест и отношения к правилам, здесь требуется следовать понятиям ролевой игры]], +-- }, + default = { + name = 'Тестовый Доброград', + desc = [[Самая новая часть города отдана в руки чудаковатых инженеров, любящих экспериментальные инновации. Здесь редко можно встретить других людей, потому что из-за постоянных изменений опасность для жизни и спокойствия в буквальном смысле поджидает на каждом углу + +На этом сервере стоят последние и почти всегда недоделанные обновления, в связи с чем многие вещи еще работают не совсем, как надо. Вход выполняется по вайтлисту, а прогресс не переносится на остальные сервера]], + }, +} + +local setupInfo = {} +local nl = string.char(10) +function test.welcomeScreen(attempts, hint, showMsg) + + if not isfunction(octolib.label) then + timer.Simple(0.1, function() test.welcomeScreen(attempts, hint, showMsg) end) + return + end + + test.remove() + + test.hint = hint + test.attempts = attempts + + local w = vgui.Create 'DPanel' + test.frame = w + w:Dock(LEFT) + w:DockPadding(30, 20, 30, 20) + w:SetWide(600) + w:MakePopup() + function w:Paint(w, h) + draw.RoundedBox(0, 0, 0, w, h, ColorAlpha(cols.bg_d, 250)) + end + + timer.Simple(0.2, test.enableFlyover) -- TODO: + w.OnRemove = test.disableFlyover + + local server = servers[game.GetIPAddress()] or servers.default + + local cont = w:Add 'DScrollPanel' + cont:Dock(FILL) + + local title = octolib.label(cont, server.name) + title:Dock(TOP) + title:SetContentAlignment(5) + title:SetTall(50) + title:SetFont('dbg-test.title') + + local subtitle = octolib.label(cont, ('%s игрок%s'):format(player.GetCount(), octolib.string.formatCount(player.GetCount(), '', 'а', 'ов'))) + subtitle:Dock(TOP) + subtitle:SetContentAlignment(8) + subtitle:SetTall(60) + subtitle:SetFont('dbg-test.medium') + setupInfo[game.GetIPAddress()] = function(info) + if not IsValid(subtitle) or not info.players then return end + subtitle:SetText(('%s игрок%s'):format(#info.players, octolib.string.formatCount(info.players, '', 'а', 'ов'))) + end + timer.Create('dbg-test.updatePlayerCount', 1, 0, function() + if not IsValid(subtitle) then + return timer.Remove('dbg-test.updatePlayerCount') + end + + subtitle:SetText(('%s игрок%s'):format(player.GetCount(), octolib.string.formatCount(player.GetCount(), '', 'а', 'ов'))) + end) + + local desc = cont:Add 'DMarkup' + desc:Dock(TOP) + desc:SetText('' .. server.desc .. '') + + local play = octolib.button(cont, 'Начать игру', function(self) + if not test.attempts then + netstream.Start('dbg-test.answer') + self:SetEnabled(false) + self:SetText('Загрузка...') + elseif test.attempts > 0 then + Derma_Query(test.hint, 'Нужно пройти тест', 'Начать', function() + netstream.Start('dbg-test.start') + end, 'Назад') + elseif test.attempts == 0 then + Derma_Message(test.hint, 'Эх...', 'Понятно') + end + end) + play:SetFont('dbg-test.medium') + play:SetTall(45) + play:DockMargin(150, 30, 150, 0) + + local bp = cont:Add 'DPanel' + bp:SetPaintBackground(false) + bp:Dock(TOP) + bp:SetTall(30) + bp:DockMargin(150, 10, 150, 0) + + local links = octolib.button(bp, 'Ссылки', function() + local m = DermaMenu() + m:AddOption(L.rules_server, function() octoesc.OpenURL('https://wiki.octothorp.team/dobrograd/rules') end) + m:AddOption(L.forum, function() octoesc.OpenURL('https://forum.octothorp.team') end) + m:AddOption(L.wiki, function() octoesc.OpenURL('https://wiki.octothorp.team') end) + m:AddOption(L.we_in_steam, function() octoesc.OpenURL('https://steamcommunity.com/groups/octothorp-team') end) + m:AddOption(L.we_in_vk, function() octoesc.OpenURL('https://vk.com/octoteam') end) + m:AddOption(L.our_site, function() octoesc.OpenURL('https://www.octothorp.team') end) + m:AddOption(L.write_us_in_vk, function() octoesc.OpenURL('https://vk.me/octoteam') end) + m:Open() + end) + links:SetWide(115) + links:Dock(LEFT) + + local quit = octolib.button(bp, 'Отключиться', function() + Derma_Query('Ты уверен? Это отсоединит тебя от текущей игры', 'Выйти с сервера', + 'Уйти', function() LocalPlayer():ConCommand('disconnect') end, + 'Остаться') + end) + quit:SetWide(115) + quit:Dock(RIGHT) + + for address, otherServer in pairs(servers) do + if otherServer ~= server and address ~= 'default' then + local card = w:Add 'DPanel' + card:Dock(BOTTOM) + card:DockMargin(0, 10, 0, 0) + card:DockPadding(15, 10, 15, 10) + card:SetTall(80) + + local title = octolib.label(card, otherServer.name) + title:SetFont('dbg-test.medium') + title:SetTall(30) + + local progress = card:Add 'DProgressLabel' + progress:Dock(BOTTOM) + progress:SetText('Загрузка...') + + local playerCountText = 'Загрузка...' + local serverStatus + setupInfo[address] = function(status) + if not IsValid(title) then return end + if status then + status.players = status.players or 0 + playerCountText = ('%s игрок%s'):format(#status.players, octolib.string.formatCount(#status.players, '', 'а', 'ов')) + serverStatus = status + progress:SetText(playerCountText .. ' - ' .. status.map) + progress:SetFraction(math.min(#status.players / status.maxplayers, 1)) + else + playerCountText = 'Сервер не отвечает' + serverStatus = 'Не в сети' + progress:SetText(playerCountText) + progress:SetFraction(0) + end + end + + local join = card:Add 'DButton' + join:SetText('') + function join:Paint(w, h) + if self.Depressed then return draw.RoundedBox(4, 0, 0, w, h, cols.hvr) end + if self.Hovered then return draw.RoundedBox(4, 0, 0, w, h, ColorAlpha(color_white, 5)) end + end + function join:PerformLayout() + self:SetSize(self:GetParent():GetSize()) + end + function join:DoClick() + local popup = octolib.overlay(w, 'DPanel') + popup:SetSize(570, 450) + popup:DockPadding(15, 10, 15, 10) + + local title = octolib.label(popup, otherServer.name) + title:Dock(TOP) + title:SetContentAlignment(5) + title:SetTall(50) + title:SetFont('dbg-test.title') + + local subtitle = octolib.label(popup, playerCountText) + subtitle:Dock(TOP) + subtitle:SetContentAlignment(8) + subtitle:SetTall(60) + subtitle:SetFont('dbg-test.medium') + + local desc = popup:Add 'DMarkup' + desc:Dock(TOP) + desc:SetText('' .. otherServer.desc .. '') + + local play = octolib.button(popup, 'Подключиться', function() + Derma_Query('Это отключит тебя от текущего сервера', 'Ты уверен?', + 'Продолжить', function() LocalPlayer():ConCommand('connect ' .. address) end, + 'Отмена') + end) + play:SetFont('dbg-test.medium') + play:SetTall(45) + play:DockMargin(150, 30, 150, 0) + + if serverStatus then + if isstring(serverStatus) then + play:SetText(serverStatus) + play:SetEnabled(false) + elseif #serverStatus.players >= serverStatus.maxplayers then + play:SetText('Сервер полон') + play:SetEnabled(false) + end + end + end + end + end + + local othersLabel = octolib.label(w, 'Другие сервера') + othersLabel:Dock(BOTTOM) + othersLabel:SetContentAlignment(5) + othersLabel:SetTall(25) + othersLabel:SetFont('dbg-test.normal') + + local function fetch() + octoservices:get('/servers/status'):Then(function(res) + for _, group in ipairs(res.data or {}) do + for _, server in ipairs(group.servers or {}) do + local setup = setupInfo[server.ip] + if setup then setup(server.status) end + end + end + end) + end + if octoservices then + fetch() + else + hook.Add('octoservices.init', 'octolib.test', fetch) + end + + if showMsg then + Derma_Message(test.hint, 'Эх...', 'Понятно') + end + +end +netstream.Hook('dbg-test.welcomeScreen', test.welcomeScreen) + +function test.intro(toPass) + local window = vgui.Create 'DFrame' + window:SetTitle('Памятка') + window:SetDraggable(false) + window:ShowCloseButton(false) + window:SetBackgroundBlur(true) + window:SetDrawOnTop(true) + window:SetSize(555, 450) + + local timeLeft = 30 + local lbl = window:Add 'DPanel' + lbl:Dock(TOP) + lbl:DockMargin(5, 0, 5, 0) + local txt = L.test_intro1:gsub('*%*(.-)*%*', '%1'):format(10) + local mp = markup.Parse(txt, 475) + function lbl:Paint(w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(255,243,224)) + draw.RoundedBoxEx(8, 0, 0, 55, h, Color(255,183,77), true, false, true) + draw.SimpleText(utf8.char(0xf071), 'dbg-test.icons', 28, h/2, Color(255,234,202), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + mp:Draw(60, 5) + end + + local description = window:Add 'DLabel' + description:Dock(TOP) + description:DockMargin(10, 10, 10, 10) + description:SetWrap(true) + description:SetAutoStretchVertical(true) + description:SetText(L.test_intro2:format(toPass)) + + local btnPanel = window:Add 'DPanel' + btnPanel:Dock(TOP) + btnPanel:SetTall(21) + btnPanel:SetPaintBackground(false) + + local btn = btnPanel:Add 'DButton' + btn:SetText('Начать (' .. timeLeft .. ')') + btn:SizeToContents() + btn:SetTall(21) + btn:SetWide(btn:GetWide() + 20) + btn.DoClick = function() window:Close() end + btn:SetDisabled(true) + function btn:Think() + local newX = (self:GetParent():GetWide() - self:GetWide()) / 2 + if newX ~= self:GetX() then + self:SetX(newX) + self.Think = nil + end + end + timer.Create('dbg-test.note.begin', 1, 0, function() + if not btn:IsValid() then + return timer.Remove('dbg-test.note.begin') + end + timeLeft = timeLeft - 1 + if timeLeft <= 0 then + btn:SetDisabled(false) + btn:SetText('Начать') + timer.Remove('dbg-test.note.begin') + else + btn:SetText('Начать (' .. timeLeft .. ')') + end + end) + + local w, h = mp:Size() + lbl:SetTall(h + 10) + window:MakePopup() + window:DoModal() + window:Center() + function window:PerformLayout(w, h) + self:SizeToChildren(false, true) + self:SetY((ScrH()-self:GetTall())/2) + end +end + +function test.create(quiz, toPass, onCancel, onSubmit) + + test.frame:Clear() + test.frame.Paint = octolib.func.zero + test.frame:SetWide(ScrW()) + + local f = test.frame:Add 'DPanel' + f:DockPadding(10, 10, 10, 10) + f:SetSize(400, 450) + f:Center() + function f:PerformLayout() + self:Center() + end + + local answers = {} + + local qp = f:Add 'DPanel' + qp:Dock(FILL) + qp:SetPaintBackground(false) + + local butFinish, butNext, butPrev + curQID = 0 + local function changeQuestion(delta) + qp:Clear() + curQID = curQID + delta + local q = quiz[curQID] + + local lbl = qp:Add 'DLabel' + lbl:DockMargin(0, 0, 0, 15) + lbl:Dock(TOP) + lbl:SetAutoStretchVertical(true) + lbl:SetWrap(true) + lbl:SetText(('Вопрос %s/%s:' .. nl .. '%s'):format(curQID, #quiz, q[1])) + lbl:SetFont('dbg-test.small') + + answers[curQID] = answers[curQID] or {} + for i = 2, #q do + local curAnswers = answers[curQID] + curAnswers[i-1] = curAnswers[i-1] or false + + local cbp = qp:Add 'DPanel' + cbp:Dock(TOP) + cbp:DockMargin(0, 0, 0, 5) + cbp:SetTall(30) + cbp:SetPaintBackground(false) + + local cb = cbp:Add 'DButton' + cb:DockMargin(0, 0, 10, 0) + cb:Dock(LEFT) + cb:SetWide(24) + cb:SetText('') + cb:SetIcon(val and 'icon16/tick.png' or 'icon16/cross.png') + cb:SetPaintBackground(false) + function cb:DoClick() curAnswers[i-1] = not curAnswers[i-1] self:UpdateValue() end + function cb:UpdateValue() self:SetIcon(curAnswers[i-1] and 'icon16/tick.png' or 'icon16/cross.png') end + curAnswers[i-1] = answers[curQID][i-1] + cb:UpdateValue() + + local cbl = cbp:Add 'DLabel' + cbl:Dock(FILL) + cbl:SetContentAlignment(4) + cbl:SetWrap(true) + cbl:SetText(q[i]) + cbl:SetMouseInputEnabled(true) + function cbl:DoClick() cb:DoClick() end + end + + butNext:SetEnabled(curQID ~= #quiz) + butPrev:SetEnabled(curQID ~= 1) + + if curQID == #quiz then + butFinish:SetEnabled(true) + end + end + + local bp1 = f:Add 'DPanel' + bp1:Dock(BOTTOM) + bp1:DockMargin(0, 5, 0, 0) + bp1:SetTall(30) + bp1:SetPaintBackground(false) + + butFinish = octolib.button(bp1, L.answer_hint, onSubmit or octolib.func.zero) + butFinish:Dock(RIGHT) + butFinish:SetWide(100) + butFinish:SetEnabled(false) + + local butExit = octolib.button(bp1, 'Выйти', onCancel or octolib.func.zero) + butExit:Dock(LEFT) + butExit:SetWide(83) + + local butRules = octolib.button(bp1, 'Правила', function() + octoesc.OpenURL('https://wiki.octothorp.team/dobrograd/rules') + end) + butRules:Dock(LEFT) + butRules:DockMargin(5, 0, 5, 0) + butRules:SetWide(100) + + local butRules = octolib.button(bp1, 'РП-гайд', function() + octoesc.OpenURL('https://wiki.octothorp.team/ru/dobrograd/info/roleplay') + end) + butRules:Dock(FILL) + butRules:DockMargin(0, 0, 5, 0) + butRules:SetWide() + + local bp2 = f:Add 'DPanel' + bp2:Dock(BOTTOM) + bp2:DockMargin(0, 10, 0, 0) + bp2:SetTall(30) + bp2:SetPaintBackground(false) + + butNext = octolib.button(bp2, '>>>', function() changeQuestion(1) end) + butNext:Dock(RIGHT) + butNext:SetWide(188) + butNext:DockMargin(5, 0, 0, 0) + + butPrev = octolib.button(bp2, '<<<', function() changeQuestion(-1) end) + butPrev:Dock(FILL) + + test.answers = answers + changeQuestion(1) + + test.intro(toPass) + +end + +function test.results(resultsTbl, add) + local window = vgui.Create 'DFrame' + window:SetTitle(L.test_success2) + window:SetDraggable(false) + window:ShowCloseButton(false) + window:SetBackgroundBlur(true) + window:SetDrawOnTop(true) + + local lbl = window:Add 'DPanel' + lbl:Dock(FILL) + lbl:DockMargin(5, 0, 5, 0) + local txt = L.test_success:format(table.concat(resultsTbl, nl)) .. (add or '') + local mp = markup.Parse(txt, ScrW() - 100) + function lbl:Paint(w, h) + mp:Draw(0, 0) + end + + local btnPanel = window:Add 'DPanel' + btnPanel:SetTall(30) + btnPanel:SetPaintBackground(false) + + local btn = btnPanel:Add 'DButton' + btn:SetText(L.test_success3) + btn:SizeToContents() + btn:SetTall(20) + btn:SetWide(btn:GetWide() + 20) + btn.DoClick = function() window:Close() end + + btnPanel:SetWide(btn:GetWide() + 10) + + local w, h = mp:Size() + window:SetSize(w + 20, h + 25 + 45 + 10) + btnPanel:Dock(BOTTOM) + window:Center() + btn:SetPos((w + 20 - btn:GetWide()) / 2 - 5, 5) + + lbl:StretchToParent(5, 5, 5, 5) + + window:MakePopup() + window:DoModal() +end + +function test.start(quiz, toPass) + test.create(quiz, toPass, function() + Derma_Query('Ты уверен? Это отсоединит тебя от текущей игры' .. nl .. 'Твоя попытка будет потеряна', 'Выйти с сервера', + 'Уйти', function() LocalPlayer():ConCommand('disconnect') end, + 'Остаться') + end, function(self) + self:SetEnabled(false) + self:SetText(L.loading) + netstream.Start('dbg-test.answer', test.answers) + end) +end +netstream.Hook('dbg-test.start', test.start) + +function test.remove() + + if IsValid(test.frame) then + test.frame:Remove() + end + +end + +netstream.Hook('dbg-test.answer', function(results) + test.remove() + if results then + Derma_Query('Отправляя ответы, ты подтверждаешь, что...\n• Знаешь правила сервера;\n• Осознаешь последствия возможного их нарушения', 'Последнее уточнение', 'ОК', function() + local total = 0 + for i, v in ipairs(results) do + results[i] = ('%s%% - %s из %s - %s'):format(math.Round(v[2] / v[3] * 100), v[2], v[3], v[1]) + total = total + v[2] / v[3] + end + results[#results + 1] = 'Набрано баллов за тест: ' .. total + test.results(results) + end) + end +end) + +-- +-- FLYOVER +-- + +local config = { + rp_eastcoast = { + { + start = { Vector(-921, -206, 92), Angle(18, 0, 0), 80 }, + finish = { Vector(967, -84, 92), Angle(14, -21, 0), 80 }, + time = 20, + }, + { + start = { Vector(777, -3231, 70), Angle(3, 23, 0), 80 }, + finish = { Vector(1334, -3157, 35), Angle(-1, -12, 0), 54 }, + time = 20, + }, + { + start = { Vector(4239, -1503, -20), Angle(-11, 141, 0), 55 }, + finish = { Vector(4739, -1501, -20), Angle(-11, 59, 0), 60 }, + time = 20, + }, + { + start = { Vector(-2900, 828, 190), Angle(-18, -35, 0), 70 }, + finish = { Vector(-2131, 832, -116), Angle(4, 40, 0), 80 }, + time = 20, + }, + { + start = { Vector(-3530, 2863, 66), Angle(0, -89, 0), 40 }, + finish = { Vector(-3522, 2407, 68), Angle(0, -89, 0), 80 }, + time = 20, + }, + }, + rp_evocity_dbg = { + { + start = { Vector(-9562, -11912, 136), Angle(-9, -162, 0), 80 }, + finish = { Vector(-9569, -12473, 136), Angle(-3, -122, 0), 60 }, + time = 20, + }, + { + start = { Vector(-5750, -5584, 1050), Angle(90, -90, 0), 80 }, + finish = { Vector(-5750, -8876, 1050), Angle(90, -90, 0), 80 }, + time = 20, + }, + { + start = { Vector(3635, 5023, 127), Angle(-1, 162, 0), 80 }, + finish = { Vector(3658, 5710, 132), Angle(-1, 106, 0), 60 }, + time = 20, + }, + { + start = { Vector(-6864, 13700, 232), Angle(0, -170, 0), 80 }, + finish = { Vector(-6302, 13700, 232), Angle(-5, -100, 0), 40 }, + time = 20, + }, + { + start = { Vector(658, 4162, 141), Angle(18, -0, 0), 80 }, + finish = { Vector(1219, 4157, 99), Angle(-18, -0, 0), 60 }, + time = 20, + }, + }, + rp_truenorth = { + { + start = { Vector(770, 14699, 180), Angle(-10, -120, 0), 57 }, + finish = { Vector(900, 13600, 180), Angle(-10, -160, 0), 57 }, + time = 20, + }, + { + start = { Vector(10582, -10243, 5492), Angle(0, 110, 0), 60 }, + finish = { Vector(10549, -11242, 5491), Angle(1, 168, 0), 60 }, + time = 20, + }, + { + start = { Vector(5600, 4450, 100), Angle(-19, -100, 0), 96 }, + finish = { Vector(5600, 5500, 100), Angle(-11, -100, 0), 40 }, + time = 20, + }, + { + start = { Vector(10304, 7721, 28), Angle(-10, 23, 0), 60 }, + finish = { Vector(10272, 7019, 28), Angle(-5, -62, 0), 40 }, + time = 20, + }, + { + start = { Vector(4903, -2187, 4195), Angle(2, -154, 0), 55 }, + finish = { Vector(6487, -2200, 4561), Angle(-2, -119, 0), 55 }, + time = 20, + }, + }, + rp_riverden = { + { + start = { Vector(-11383, 10269, 42), Angle(-9, 85, 0), 57 }, + finish = { Vector(-12079, 10332, 46), Angle(-9, 62, 0), 42 }, + time = 20, + }, + { + start = { Vector(-11546, 4250, -131), Angle(6, -26, 0), 68 }, + finish = { Vector(-11650, 3954, -176), Angle(-3, 31, -0), 55 }, + time = 20, + }, + { + start = { Vector(-5261, 7272, 886), Angle(89, -180, 0), 105 }, + finish = { Vector(-5262, 10115, 886), Angle(89, -180, 0), 105 }, + time = 20, + }, + { + start = { Vector(6130, 1668, -171), Angle(-6, 5, 0), 65 }, + finish = { Vector(8166, 1489, -174), Angle(0, 7, 0), 60 }, + time = 20, + }, + { + start = { Vector(9336, -2742, 694), Angle(-1, -74, 0), 70 }, + finish = { Vector(9585, -3655, 698), Angle(-3, -94, 0), 65 }, + time = 20, + }, + }, + default = {{ + start = { Vector(), Angle(), 60 }, + finish = { Vector(), Angle(), 60 }, + time = 60, + }} +} + +function dbgTest.enableFlyover() + + local mapData + for map, data in pairs(config) do if game.GetMap():find(map) then mapData = data break end end + if not mapData then mapData = config.default end + + local curPartID, startTime, finishTime = 0, 0, 0 + local nextRequestPos = 0 + hook.Add('CalcView', 'dbg-flyover', function(ply, pos, ang, fov) + local ct = RealTime() + local curPart = mapData[curPartID] + if ct > finishTime then + curPartID = curPartID + 1 + if curPartID > #mapData then curPartID = 1 end + + curPart = mapData[curPartID] + finishTime = ct + curPart.time + startTime = ct + + nextRequestPos = 0 + end + + local posStart, angStart, fovStart = unpack(curPart.start) + local posFinish, angFinish, fovFinish = unpack(curPart.finish) + local st = (finishTime - ct) / curPart.time -- lerp backwards to simplify math + pos = LerpVector(st, posFinish, posStart) + ang = LerpAngle(st, angFinish, angStart) + fov = Lerp(st, fovFinish, fovStart) + + if ct >= nextRequestPos then + netstream.Start('dbg-flyover.requestPos', pos) + nextRequestPos = ct + 3 + end + + return { + origin = pos, + angles = ang, + fov = fov, + znear = 5, + } + end, -5) + + hook.Add('ShouldDrawLocalPlayer', 'dbg-flyover', function() + return true + end, -5) + + local colBG = CFG.skinColors.bg + hook.Add('RenderScreenspaceEffects', 'dbg-flyover', function() + + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -0.15, + ['$pp_colour_contrast'] = 1.5, + ['$pp_colour_colour'] = 0.3, + } + + DrawColorModify(colMod) + + local ct = RealTime() + local al = 100 + math.min(math.max(ct - finishTime + 2.1, startTime - ct + 2.1, 0) / 2, 1) * 155 + + draw.NoTexture() + surface.SetDrawColor(colBG.r, colBG.g, colBG.b, al) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + + return true -- prevent other effects + + end, -5) + + local hide = octolib.array.toKeys { 'CHudGMod', 'CHudCrosshair', 'CHudMenu' } + hook.Add('HUDShouldDraw', 'dbg-flyover', function(name) + if hide[name] then return false end + end, -5) + +end + +function dbgTest.disableFlyover() + hook.Remove('CalcView', 'dbg-flyover') + hook.Remove('RenderScreenspaceEffects', 'dbg-flyover') + hook.Remove('ShouldDrawLocalPlayer', 'dbg-flyover') + hook.Remove('HUDShouldDraw', 'dbg-flyover') +end diff --git a/garrysmod/addons/feature-playertest/lua/playertest/sv_test.lua b/garrysmod/addons/feature-playertest/lua/playertest/sv_test.lua new file mode 100644 index 0000000..fc8c70d --- /dev/null +++ b/garrysmod/addons/feature-playertest/lua/playertest/sv_test.lua @@ -0,0 +1,319 @@ +local test = dbgTest or {} +dbgTest = test + +test.config = { + id = 'dbg3', + attempts = 2, +} + +local dbvar = 'quiz_' .. test.config.id + +local storageApi = octolib.api({ + url = 'https://dbgtest.octo.gg/api', + headers = { ['Authorization'] = CFG.keys.test }, +}) + +local function tomorrow() + + local dateTime = os.date('*t', os.time()) + dateTime.hour = 0 + dateTime.min = 0 + dateTime.sec = 0 + return os.time(dateTime) + 60 * 60 * 24 + +end + +function test.getAttempts(ply) + + local data = ply:GetDBVar(dbvar) or {} + return data[1] or test.config.attempts + +end + +function test.takeAttempt(ply) + + local data = ply:GetDBVar(dbvar) or {} + ply:SetDBVar(dbvar, { + (data[1] or test.config.attempts) - 1, + tomorrow(), + }) + +end + +local function playTime(time) + local h, m, s = + math.floor(time / 60 / 60), + math.floor(time / 60) % 60, + math.floor(time) % 60 + return string.format('%02i:%02i:%02i', h, m, s) +end + +function test.saveAttempt(ply, questions, answers, total) + local length = CurTime() - ply.dbg_playertest.start + local ptime = playTime(ply:GetTimeTotal()) + + local quiz = {} + for qID, q in ipairs(ply.dbg_playertest.quiz) do + local ans = {q.question} + for i = 1, #q.data do + ans[#ans + 1] = { q.data[i][1], answers[qID][i] and 1 or 0, q.data[i][2] and 1 or 0 } + end + quiz[#quiz + 1] = ans + end + local sid, playerinfo = ply:SteamID() + local req = octolib.vars.get('dbgTest.required') + octolib.func.chain({ + function(nxt) + octolib.getSteamData({ util.SteamIDTo64(ply:SteamID()) }, nxt) + end, + function(nxt, result) + playerinfo = result[1] + storageApi:post('/post', { + user = { + name = playerinfo.name, + sid64 = playerinfo.steamid64, + }, + total = total, + req = req, + quiz = quiz, + }):Then(nxt):Catch(ErrorNoHalt) + end, + function(nxt, response) + local embed = { + title = playerinfo.name, + url = 'https://steamcommunity.com/profiles/' .. playerinfo.steamid64, + description = 'Игрок ' .. (total >= req and 'прошел' or 'провалил') .. ' тест\nhttps://dbgtest.octo.gg/' .. response.data.key, + color = total >= req and 0x00ff00 or 0xff0000, + thumbnail = { url = playerinfo.avatar }, + fields = { + { + name = 'SteamID', + value = sid, + }, { + name = 'Набрано баллов', + value = total..' / '..#questions..' ('..total..' / '..req..')', + }, { + name = 'Наигранное время', + value = ptime, + }, { + name = 'Время прохождения', + value = octolib.time.formatDuration(length), + }, + }, + footer = { + text = 'Ссылка с результатами действительна в течение 10 дней', + } + } + + if CFG.webhooks.unban then + octoservices:post('/discord/webhook/' .. CFG.webhooks.unban, { + embeds = { embed }, + }) + end + + end, + }) + +end + +function test.reset(steamID) + + octolib.setDBVar(steamID, dbvar, nil) + +end + +function test.generate(questions, maxInBatch) + questions = questions or test.questions + maxInBatch = maxInBatch or octolib.vars.get('dbgTest.maxInBatch') + + local quiz = {} + local forUs, forThem = {}, {} + + -- create quiz, shuffling questions and answers + local src = table.Copy(questions) + for catID, cat in pairs(src) do + octolib.array.shuffle(cat) + for i = 1, math.min(maxInBatch, #cat) do + octolib.array.shuffle(cat[i].data) + quiz[#quiz + 1] = cat[i] + end + end + octolib.array.shuffle(quiz) + + for qID, q in ipairs(quiz) do + forThem[qID] = {q.question} + forUs[qID] = q + for i = 1, #q.data do + forThem[qID][i+1] = q.data[i][1] -- send them questions only + end + end + + return forUs, forThem + +end + +function test.spawned(ply) + + local ip = ply:IPAddress():gsub('%:.+', '') + local ips = ply:GetDBVar('ips') or {} + if ip ~= '172.18.0.1' and not table.HasValue(ips, ip) then + ips[#ips + 1] = ip + while #ips > 3 do table.remove(ips, 1) end + + ply:SetDBVar('ips', ips) + end + + if CFG.requireLauncher and not ply:GetNetVar('launcherActivated') then + return + else + ply:SetNetVar('launcherActivated', true) + end + + if CFG.dev and not CFG.testEnabled then + netstream.Start(ply, 'dbg-test.welcomeScreen') + return + end -- no test for dev + + ply:Freeze(true) + ply:SetNoDraw(true) + ply:SetNotSolid(true) + + local data = ply:GetDBVar(dbvar) or {} + if data == true then + netstream.Start(ply, 'dbg-test.welcomeScreen') + else + if data[2] and os.time() > data[2] then + ply:SetDBVar(dbvar, { test.config.attempts, tomorrow() }) + end + ply.dbg_playertest = {} + test.welcome(ply) + end + +end +hook.Add('PlayerFinishedLoading', 'dbg-test', test.spawned) + +function test.welcome(ply, showMsg) + + local attempts = test.getAttempts(ply) + local msg = (attempts == 0 and L.msgTryTomorrow or attempts == test.config.attempts and L.msgWelcome or L.msgTryAgain):format(attempts) + netstream.Start(ply, 'dbg-test.welcomeScreen', attempts, msg, showMsg) + +end + +function test.start(ply) + + if ply.dbg_playertest and CurTime() < (ply.dbg_playertest.nextAttempt or 0) then + ply:Notify('warning', 'Ты уже проходишь тест! Если он не появился, подожди немного') + return + end + + local attemptsLeft = test.getAttempts(ply) + if attemptsLeft <= 0 then + netstream.Start(ply, 'dbg-test.welcomeScreen', 0, L.msgTryTomorrow, true) + return + end + test.takeAttempt(ply) + + local forUs, forThem = test.generate() + netstream.Start(ply, 'dbg-test.start', forThem, octolib.vars.get('dbgTest.required')) + + ply.dbg_playertest = ply.dbg_playertest or {} + ply.dbg_playertest.start = CurTime() + ply.dbg_playertest.nextAttempt = CurTime() + 20 + ply.dbg_playertest.quiz = forUs + +end +netstream.Hook('dbg-test.start', test.start) + +function test.calcScore(answers, correct) + local total, scores = 0, {} + for qID, q in ipairs(correct) do + local right, should = 0, 0 + for i = 1, #q.data do + if q.data[i][2] then + should = should + 1 + if answers[qID][i] then + right = right + 1 + end + elseif answers[qID][i] then + should = should + 1 + end + end + scores[qID] = {q.question, right, should} + total = total + (right / should) + end + return total, scores +end + +function test.answer(ply, answers) + + if not ply.dbg_playertest or not ply.dbg_playertest.quiz then + return test.pass(ply) + end + + if test.getAttempts(ply) < 0 then + return test.catchExploit(ply) + end + + local total, scores = test.calcScore(answers, ply.dbg_playertest.quiz) + test.saveAttempt(ply, ply.dbg_playertest.quiz, answers, total) + if total >= octolib.vars.get('dbgTest.required') then + test.pass(ply, true, scores) + else + test.welcome(ply, true) + end + +end +netstream.Hook('dbg-test.answer', test.answer) + +function test.catchExploit(ply) + + ply.dbg_playertest.expAtt = (ply.dbg_playertest.expAtt or 0) + 1 + ply:Notify('warning', L.exploits_not_here2) + + if ply.dbg_playertest.expAtt >= 3 then + ply:Kick(L.exploits_not_here) + end + +end + +function test.pass(ply, justPassed, data) + + if ply.passedTest then + return ply:addExploitAttempt() + end + + if justPassed then + ply:SetDBVar(dbvar, true) + end + + ply.dbg_playertest = nil + ply.passedTest = true + netstream.Start(ply, 'dbg-test.answer', data) + + ply:Spawn() + ply:Freeze(false) + ply:SetNoDraw(false) + ply:SetNotSolid(false) + ply:SetMoveType(MOVETYPE_WALK) + + hook.Run('dbg-test.complete', ply, justPassed, data) +end + +netstream.Hook('dbg-flyover.requestPos', function(ply, pos) + if ply.passedTest then return end + ply:SetPos(pos) + ply:Freeze(true) + ply:SetNoDraw(true) + ply:SetNotSolid(true) + ply:SetMoveType(MOVETYPE_NOCLIP) + -- timer.Create('resetPVS_' .. ply:SteamID(), 5, 1, function() + -- if not IsValid(ply) then return end + -- ply.extendPVS = nil + -- end) +end) + +-- hook.Add('SetupPlayerVisibility', 'dbg-flyover', function(ply) +-- if not ply.extendPVS then return end +-- AddOriginToPVS(ply.extendPVS) +-- end) diff --git a/garrysmod/addons/feature-police/lua/autorun/police.lua b/garrysmod/addons/feature-police/lua/autorun/police.lua new file mode 100644 index 0000000..6f00d7d --- /dev/null +++ b/garrysmod/addons/feature-police/lua/autorun/police.lua @@ -0,0 +1,4 @@ +dbgPolice = dbgPolice or {} +octolib.server('police/jailpos') +octolib.server('police/use') +octolib.shared('police/k9') diff --git a/garrysmod/addons/feature-police/lua/cmenu/categories/departments.lua b/garrysmod/addons/feature-police/lua/cmenu/categories/departments.lua new file mode 100644 index 0000000..ac03f03 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/cmenu/categories/departments.lua @@ -0,0 +1 @@ +octogui.cmenu.registerCategory('departments') diff --git a/garrysmod/addons/feature-police/lua/cmenu/categories/mayor.lua b/garrysmod/addons/feature-police/lua/cmenu/categories/mayor.lua new file mode 100644 index 0000000..69aaff8 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/cmenu/categories/mayor.lua @@ -0,0 +1 @@ +octogui.cmenu.registerCategory('mayor') diff --git a/garrysmod/addons/feature-police/lua/cmenu/items/mayor.lua b/garrysmod/addons/feature-police/lua/cmenu/items/mayor.lua new file mode 100644 index 0000000..45292c1 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/cmenu/items/mayor.lua @@ -0,0 +1,153 @@ +local rtl = function(self) + self:SetFontInternal('DermaDefault') + self:SetUnderlineFont('dbg_window_underline') + local ct = CurTime() + if ct < (self.nextLayout or 0) then return end + self.nextLayout = ct + FrameTime()*2 + self:SetToFullHeight() +end +local omw = function(self, delta) + self:GetParent():GetParent():OnMouseWheeled(delta) + return true +end + +surface.CreateFont('dbg_laws_title', { + font = system.IsOSX() and 'Helvetica' or 'Tahoma', + size = 18, + antialias = true, + extended = true, +}) + +local as = function(_, name, val) + if name == 'TextClicked' then + gui.ActivateGameUI() + octoesc.OpenURL(val) + end +end + +local function showLaws() + + local f = vgui.Create 'DFrame' + f:SetSize(300, 300) + f:SetTitle('Законы города') + f:SetSizable(true) + f:SetMinWidth(300) + f:AlignTop(5) + f:AlignLeft(5) + + local scr = f:Add 'DScrollPanel' + scr:Dock(FILL) + + local btn = scr:Add 'DButton' + btn:Dock(TOP) + btn:SetText('Основные законы города') + btn:SetTall(36) + function btn:DoClick() + gui.ActivateGameUI() + octoesc.OpenURL('https://wiki.octothorp.team/ru/dobrograd/legislation') + end + + local title = scr:Add 'DLabel' + title:Dock(TOP) + title:DockMargin(0, 10, 0, 5) + title:SetContentAlignment(5) + title:SetFont('dbg_laws_title') + title:SetText('Правки от мэра') + + for i, v in ipairs(DarkRP.getLaws()) do + local splitter = scr:Add 'DPanel' + splitter:Dock(TOP) + splitter:DockMargin(0, 5, 0, 5) + splitter:SetTall(1) + + local lbl = scr:Add 'RichText' + lbl:Dock(TOP) + v = octolib.string.splitByUrl(('10.%i. %s'):format(i, v)) + lbl:InsertColorChange(255, 255, 255, 255) + for _, token in ipairs(v) do + if istable(token) then + lbl:InsertClickableTextStart(token[1]) + lbl:InsertColorChange(0, 130, 255, 255) + lbl:AppendText(token[1]) + lbl:InsertColorChange(255, 255, 255, 255) + lbl:InsertClickableTextEnd() + else + lbl:AppendText(token) + end + end + lbl.PerformLayout = rtl + lbl.ActionSignal = as + lbl.OnMouseWheeled = omw + lbl:SetVerticalScrollbarEnabled(false) + end + +end + +octogui.cmenu.registerItem('mayor', 'laws_show', { + text = L.show_laws, + icon = octolib.icons.silk16('scripts_text'), + action = showLaws, +}) + +octogui.cmenu.registerItem('mayor', 'mayor_actions', { + text = L.mayor, + check = DarkRP.isMayor, + icon = octolib.icons.silk16('star'), + options = { + { + text = 'Изменить законы', + icon = octolib.icons.silk16('script_edit'), + options = { + { + text = L.addlaw, + icon = octolib.icons.silk16('script_add'), + action = octolib.fStringRequest(L.addlaw, L.addlaw_hint, '', function(s) + octochat.say('/addlaw', s) + end, nil, L.ok, L.cancel), + }, { + text = L.removelaw, + icon = octolib.icons.silk16('script_delete'), + build = function(sm) + for k,v in ipairs(DarkRP.getLaws()) do + sm:AddOption(v, function() + octochat.say('/removelaw', k) + end) + end + end, + }, { + text = L.resetclaws, + icon = octolib.icons.silk16('script_torn'), + say = '/resetlaws', + }, + } + }, { + text = L.rename_city, + icon = octolib.icons.silk16('textfield_rename'), + action = octolib.fStringRequest(L.rename_city, L.rename_city_hint, '', function(s) + octochat.say('/renamecity', s) + end, nil, L.ok, L.cancel), + }, { + text = function() + return netvars.GetNetVar('lockdown') and L.c_language_unlockdown or L.c_language_lockdown + end, + icon = function() + return octolib.icons.silk16(netvars.GetNetVar('lockdown') and 'bell_delete' or 'bell') + end, + action = function() + if netvars.GetNetVar('lockdown') then + return octochat.say('/unlockdown') + end + + Derma_StringRequest(L.lockdown, L.lockdown_hint, '', function(s) + octochat.say('/lockdown', string.Trim(s)) + end, nil, L.ok, L.cancel) + end, + }, { + text = L.broadcast, + icon = octolib.icons.silk16('radio_modern'), + action = octolib.fStringRequest(L.broadcast_hint, L.broadcast_write_text, '', function(s) + octochat.say('/broadcast', s) + end, nil, L.ok, L.cancel) + }, + }, +}) diff --git a/garrysmod/addons/feature-police/lua/cmenu/items/police.lua b/garrysmod/addons/feature-police/lua/cmenu/items/police.lua new file mode 100644 index 0000000..4a88d2c --- /dev/null +++ b/garrysmod/addons/feature-police/lua/cmenu/items/police.lua @@ -0,0 +1,131 @@ +local function nameAsc(a, b) + return a:GetName() < b:GetName() +end + +local function getSortedPlayers() + local plys = player.GetAll() + table.sort(plys, nameAsc) + return plys +end + +octogui.cmenu.registerItem('departments', 'police', { + text = L.police, + check = DarkRP.isCop, + icon = octolib.icons.silk16('set_security_question'), + options = { + { + text = 'Розыск', + icon = octolib.icons.silk16('flag_flyaway_blue'), + options = { + { + text = L.c_language_wanted, + icon = octolib.icons.silk16('flag_flyaway_red'), + build = function(sm) + local me = LocalPlayer() + for _, v in ipairs(getSortedPlayers()) do + if v == me or v:isWanted() then continue end + + sm:AddOption(v:Name(), octolib.fStringRequest(L.make_wanted, L.reason_wanted, '', function(s) + octochat.say('/wanted', v:UserID(), s) + end, nil, L.ok, L.cancel)):SetColor(v:getJobTable().color) + end + end, + }, { + text = L.c_language_unwanted, + icon = octolib.icons.silk16('flag_flyaway_green'), + build = function(sm) + for _, v in ipairs(getSortedPlayers()) do + if not v:isWanted() then continue end + + sm:AddOption(v:Name(), octolib.fStringRequest(L.c_language_unwanted, L.c_language_unwanted_description, '', function(a) + octochat.say('/unwanted', v:UserID(), a) + end, nil, L.ok, L.cancel)):SetColor(v:getJobTable().color) + end + end, + } + }, + }, { + text = 'Лицензия', + icon = octolib.icons.silk16('page'), + check = function(ply) + local mayor, chief, serg, cop + for _,v in ipairs(player.GetAll()) do + if v:isMayor() or v:GetActiveRank('gov') == 'worker' then + mayor = true + break + elseif v:isChief() then + chief = true + elseif v:getJobTable().command == 'cop2' then + serg = true + elseif v:isCP() then cop = true end + end + if mayor then + return ply:isMayor() or ply:GetActiveRank('gov') == 'worker' + elseif chief then + return ply:isChief() + elseif serg then + return ply:getJobTable().command == 'cop2' + elseif cop then + return ply:isCP() + else return ply:IsAdmin() end + end, + options = { + { + text = 'Выдать', + icon = octolib.icons.silk16('page_add'), + action = octolib.fStringRequest(L.license_give, L.license_hint, L.gun, function(s) + if string.Trim(s) == '' then return octolib.notify.show('warning', L.need_hint_license) end + octochat.say('/givelicense', s) + end, nil, L.ok, L.cancel), + }, { + text = 'Забрать', + icon = octolib.icons.silk16('page_delete'), + say = '/takelicense', + }, + } + }, { + text = 'Проверить автомобиль', + icon = octolib.icons.silk16('car_add'), + say = '/carcheck', + }, { + text = L.request_backup, + icon = octolib.icons.silk16('shield_add'), + action = octolib.fStringRequest(L.request_backup, L.request_backup_query, '', function(s) + octochat.say('/callhelp', s) + end, nil, L.ok, L.cancel), + }, { + text = L.show_codes, + icon = octolib.icons.silk16('document_inspector'), + action = function() + octogui.cmenu.window(L.codes_hint, LocalPlayer():Team() == TEAM_DPD and L.dpd_codes_help or L.codes_help) + end, + } + }, +}) + +octogui.cmenu.registerItem('departments', 'gr', { + text = L.speaker, + check = DarkRP.isGov, + icon = octolib.icons.silk16('events'), + action = octolib.fStringRequest(L.speaker_say, L.write_text, '', function(s) + octochat.say('/gr', s) + end, nil, L.ok, L.cancel), +}) + +octogui.cmenu.registerItem('departments', 'panicbtn', { + text = L.panic_button_press, + check = DarkRP.isGov, + icon = octolib.icons.silk16('alarm_bell'), + say = '/panicbutton', +}) + +local function canUse(ply) + return DarkRP.isTaxist(ply) or ply:Team() == TEAM_ALPHA +end + +octogui.cmenu.registerItem('departments', 'alphabtn', { + text = L.panic_button_press, + check = canUse, + icon = octolib.icons.silk16('alarm_bell'), + say = '/alphabutton', +}) \ No newline at end of file diff --git a/garrysmod/addons/feature-police/lua/cmenu/properties/police.lua b/garrysmod/addons/feature-police/lua/cmenu/properties/police.lua new file mode 100644 index 0000000..b4869e7 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/cmenu/properties/police.lua @@ -0,0 +1,114 @@ +properties.Add('wanted', { + MenuLabel = L.c_language_wanted, + Order = 4, + MenuIcon = 'icon16/flag_red.png', + Filter = function(self, ent, ply) + return IsValid(ent) and ent:IsPlayer() and not ent:isWanted() and GAMEMODE.CivilProtection[ply:Team()] + end, + Action = function(self, ent) + Derma_StringRequest(L.c_language_wanted, L.c_language_wanted_description, nil, function(a) + octochat.say('/wanted', ent:UserID(), a) + end) + end +}) + +properties.Add('unwanted', { + MenuLabel = L.c_language_unwanted, + Order = 5, + MenuIcon = 'icon16/flag_green.png', + Filter = function(self, ent, ply) + return IsValid(ent) and ent:IsPlayer() and ent:isWanted() and ply:isCP() + end, + Action = function(self, ent) + Derma_StringRequest(L.c_language_unwanted, L.c_language_unwanted_description, nil, function(a) + octochat.say('unwanted', ent:UserID(), a) + end) + end +}) + +-- properties.Add('warrant', { +-- MenuLabel = L.c_language_warrant, +-- Order = 6, +-- MenuIcon = 'icon16/door_in.png', + +-- Filter = function(self, ent, ply) +-- return IsValid(ent) and ent:IsPlayer() and ply:isCP() +-- end, +-- Action = function(self, ent) +-- Derma_StringRequest(L.c_language_warrant, L.c_language_warrant_description, nil, function(a) +-- octochat.say('/warrant', ent:UserID(), a) +-- end) +-- end +-- }) + +local function isInCharge(ply) + + local mayor, chief, serg, cop + for _,v in ipairs(player.GetAll()) do + if v:isMayor() or v:GetActiveRank('gov') == 'worker' then + mayor = true + break + elseif v:isChief() then + chief = true + elseif v:getJobTable().command == 'cop2' then + serg = true + elseif v:isCP() then cop = true end + end + + if mayor then + if not (ply:isMayor() or ply:GetActiveRank('gov') == 'worker') then return false end + elseif chief then + if not ply:isChief() then return false end + elseif serg then + if ply:getJobTable().command ~= 'cop2' then return false end + elseif cop then + if not ply:isCP() then return false end + elseif not ply:IsAdmin() then + return false + end + + return true + +end + +properties.Add('givelicense', { + MenuLabel = L.c_language_give_license, + Order = 7, + MenuIcon = 'icon16/page_add.png', + Filter = function(self, ent, ply) + return IsValid(ent) and ent:IsPlayer() and not ent:GetNetVar('HasGunlicense') and isInCharge(ply) + end, + Action = function(self, ent) + Derma_StringRequest(L.license_give, L.license_hint, L.gun, function(s) + if string.Trim(s) == '' then return octolib.notify.show('warning', L.need_hint_license) end + octochat.say('/givelicense', s) + end, nil, L.issue, L.cancel) + end, +}) + + +properties.Add('takelicense', { + MenuLabel = L.license_withdraw, + Order = 7, + MenuIcon = 'icon16/page_delete.png', + Filter = function(self, ent, ply) + return IsValid(ent) and ent:IsPlayer() and ent:GetNetVar('HasGunlicense') and isInCharge(ply) + end, + Action = function(self, ent) + octochat.say('/takelicense') + end, +}) + +properties.Add('warrantbyprop', { + MenuLabel = L.c_language_warrant_prop, + Order = 8, + MenuIcon = 'icon16/door_in.png', + Filter = function(self, ent, ply) + return IsValid(ent) and IsValid(ent:CPPIGetOwner()) and ent:CPPIGetOwner():IsPlayer() and ply:isCP() + end, + Action = function(self, ent) + Derma_StringRequest(L.c_language_warrant, L.c_language_warrant_description, nil, function(a) + octochat.say('/warrant', ent:CPPIGetOwner():UserID(), a) + end) + end +}) diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/cl_init.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/cl_init.lua new file mode 100644 index 0000000..323c7af --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/cl_init.lua @@ -0,0 +1,189 @@ +include('shared.lua') + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + self:DrawModel() + render.DrawBubble(self, Vector(0, 0.45, 7), Angle(0, 180, 90), L.work_in_police, 200, 200) +end + +net.Receive('dbg-police.menu', function(len) + + local ply = LocalPlayer() + + local f = vgui.Create 'DFrame' + f:SetSize(400, 600) + f:SetTitle(L.work_in_police) + f:DockPadding(8,28,8,8) + f:Center() + f:MakePopup() + + do -- apply + local l = vgui.Create 'DLabel' + l:SetParent(f) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.get_job) + l:SetFont('f4.normal') + + local p = vgui.Create 'DPanel' + p:SetParent(f) + p:Dock(TOP) + p:DockPadding(5, 5, 5, 5) + p:SetTall(125) + + local l2 = vgui.Create 'DLabel' + l2:SetParent(p) + l2:Dock(TOP) + l2:DockMargin(2,0,2,5) + l2:SetTall(45) + l2:SetWrap(true) + l2:SetText(L.job_police) + + local c = vgui.Create 'DComboBox' + c:SetParent(p) + c:Dock(TOP) + c:DockMargin(0,0,0,5) + c:SetValue(L.career_objective) + c:SetTall(30) + c:SetSortItems(false) + for i, job in ipairs(RPExtraTeams) do + if job.police and not job.noPBoard then + c:AddChoice(job.name, job.command) + end + end + + local b = vgui.Create 'DButton' + b:SetParent(p) + b:Dock(TOP) + b:DockMargin(0,0,0,5) + b:SetText(L.application_send) + b:SetTall(30) + b:SetEnabled(false) + local function apply(self, d, text) + net.Start('dbg-police.apply') + net.WriteString(d) + if text then net.WriteString(text) end + net.SendToServer() + + self:SetText(L.application_sent) + self:SetEnabled(false) + c:SetEnabled(false) + end + + function b:DoClick() + local v, d = c:GetSelected() + if d == 'chief' or d == 'mayor' then + local stored = octolib.vars.get('dbg-police.speech.' .. d) or '' + Derma_StringRequest('Предвыборная речь', + 'Ты собираешься избираться на очень важную должность.\nВозможно, избирателям будет проще сформировать свое мнение о тебе,\nесли ты напишешь предвыборную речь', stored, + function(text) + octolib.vars.set('dbg-police.speech.' .. d, string.Trim(text)) + apply(self, d, text) + end, nil, 'Отправить', 'Отмена') + else + apply(self, d) + end + end + function c:OnSelect(i, val, data) + b:SetEnabled(true) + end + end + + if ply:GetNetVar('dbg-police.job', '') ~= '' then -- quit + local l = vgui.Create 'DLabel' + l:SetParent(f) + l:Dock(TOP) + l:DockMargin(5,10,5,0) + l:SetTall(30) + l:SetText(L.retire) + l:SetFont('f4.normal') + + local p = vgui.Create 'DPanel' + p:SetParent(f) + p:Dock(TOP) + p:DockPadding(5, 5, 5, 5) + p:SetTall(90) + + local l2 = vgui.Create 'DLabel' + l2:SetParent(p) + l2:Dock(TOP) + l2:DockMargin(2,0,2,5) + l2:SetTall(45) + l2:SetWrap(true) + l2:SetText(L.retire_hint) + + local b = vgui.Create 'DButton' + b:SetParent(p) + b:Dock(TOP) + b:DockMargin(0,0,0,5) + b:SetText(L.retire) + b:SetTall(30) + function b:DoClick() + net.Start('dbg-police.quit') + net.SendToServer() + + f:Remove() + end + end + + if ply:GetNetVar('dbg-police.job', '') == 'chief' or ply:GetNetVar('dbg-police.job', '') == 'mayor' then -- fire + local l = vgui.Create 'DLabel' + l:SetParent(f) + l:Dock(TOP) + l:DockMargin(5,10,5,0) + l:SetTall(30) + l:SetText(L.control) + l:SetFont('f4.normal') + + local p = vgui.Create 'DPanel' + p:SetParent(f) + p:Dock(TOP) + p:DockPadding(5, 5, 5, 5) + p:SetTall(125) + + local l2 = vgui.Create 'DLabel' + l2:SetParent(p) + l2:Dock(TOP) + l2:DockMargin(2,0,2,5) + l2:SetTall(45) + l2:SetWrap(true) + l2:SetText(L.you_executive_position) + + local c = vgui.Create 'DComboBox' + c:SetParent(p) + c:Dock(TOP) + c:DockMargin(0,0,0,5) + c:SetValue(L.choose_worker) + c:SetTall(30) + for i, target in ipairs(player.GetAll()) do + if target ~= ply and target:GetNetVar('dbg-police.job', '') ~= '' and target:Team() ~= TEAM_DPD then + local job = DarkRP.getJobByCommand(target:GetNetVar('dbg-police.job', '')) + c:AddChoice(target:Name() .. ' (' .. (job and job.name or L.unknown) .. ')', target) + end + end + + local b = vgui.Create 'DButton' + b:SetParent(p) + b:Dock(TOP) + b:DockMargin(0,0,0,5) + b:SetText(L.demote_player) + b:SetTall(30) + b:SetEnabled(false) + function b:DoClick() + net.Start('dbg-police.fire') + net.WriteEntity(self.target) + net.SendToServer() + f:Remove() + end + + function c:OnSelect(i, val, data) + b.target = data + b:SetEnabled(true) + end + end + +end) diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/init.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/init.lua new file mode 100644 index 0000000..11469aa --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/init.lua @@ -0,0 +1,412 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/props/cs_office/offcorkboarda.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + +end + +local applyCooldown = 10 * 60 -- 10 minutes + +local activeVotes = {} +local demotedPlys = {} + +util.AddNetworkString 'dbg-police.menu' +util.AddNetworkString 'dbg-police.apply' +util.AddNetworkString 'dbg-police.quit' +util.AddNetworkString 'dbg-police.fire' +function ENT:Use( ply ) + + if not IsValid(ply) then return end + + if os.time() < ply:GetDBVar('lastDeath', 0) + applyCooldown then + ply:Notify('warning', 'После смерти персонажа работу в полиции можно получить только через 10 минут') + return + end + if ply.currentOrg then + ply:Notify('warning', 'Сначала нужно сдать форму организации') + return + end + + local job = ply:getJobTable() + if job.canJoinPolice and not job.canJoinPolice(ply) then + ply:Notify('warning', select(2, job.canJoinPolice(ply))) + return + end + + net.Start('dbg-police.menu') + net.Send(ply) + +end + +hook.Add('PlayerDeath', 'dbg-police.death', function(ply) + + ply:SetDBVar('lastDeath', os.time()) + timer.Create('resetLastDeath_' .. ply:SteamID(), applyCooldown, 1, function() + if not IsValid(ply) then return end + ply:SetDBVar('lastDeath', nil) + end) + +end) + +local function makeJob(ply, job) + + if not ply or not istable(job) then return end + + if ply.dbgPolice_citizenData then + ply:RestoreCitizen() + end + + ply:SetNetVar('dbg-police.job', job.command) + ply:Notify(L.now_you:format(job.name)) + +end + +local function loseJob(ply) + + ply:SetNetVar('dbg-police.job', nil) + local sID = ply:SteamID() + demotedPlys[sID] = true + timer.Simple(1800, function() demotedPlys[sID] = nil end) + +end + +local inCopLimit = octolib.array.toKeys {'medcop', 'cop', 'cop2', 'chief', 'dpd'} +local avoidLimit = octolib.array.toKeys {'chief', 'mayor'} +local function inPoliceLimits() + local limit = math.Round(player.GetCount() * 0.178) + if limit <= 0 then limit = 1 end + + for _,v in ipairs(player.GetAll()) do + if inCopLimit[v:GetNetVar('dbg-police.job', '')] then + limit = limit - 1 + end + end + + return limit > 0 +end + +local function startVote(ply, job, text) + + local steamID = ply:SteamID() + + for voteSID in pairs(activeVotes) do + local votePly = player.GetBySteamID(voteSID) + if IsValid(votePly) then + return ply:Notify('warning', L.already_vote_hint) + else activeVotes[voteSID] = nil end + end + + if CurTime() < (ply.dbgPolice_nextVote or 0) then + ply:Notify('warning', L.need_wait_demote) + return + end + + if job.command == 'chief' or job.command == 'mayor' then + for i, v in ipairs(player.GetAll()) do + if v:GetNetVar('dbg-police.job') == job.command then + ply:Notify('warning', L.already_work .. v:Name()) + return + end + end + end + + if player.GetCount() <= 1 then + ply:Notify(L.win_vote_one) + makeJob(ply, job) + return + end + + local voters = {} + for _, v in ipairs(player.GetAll()) do + if v ~= ply and v:GetKarma() > -50 and not v:getJobTable().hobo then + voters[#voters + 1] = v + end + end + + activeVotes[steamID] = octolib.questions.start({ + text = L.want_job_hint:format(ply:Name(), job.name), + recipients = voters, + spectators = ply, + time = 30, + onFinish = function(result) + activeVotes[steamID] = nil + if not IsValid(ply) then return end + + if result > 0 then + octolib.notify.sendAll(L.won_in_vote:format(ply:Name())) + makeJob(ply, job) + else + octolib.notify.sendAll(L.lose_in_vote:format(ply:Name())) + ply.dbgPolice_nextVote = CurTime() + 1800 + end + end, + }) + ply:Notify(L.vote_started_hint) + + ply.dbgPolice_nextVote = CurTime() + 180 + + return true +end + +local function sendApplication(ply, job, boss) + + local steamID = ply:SteamID() + + if activeVotes[steamID] then + ply:Notify('warning', L.already_sent_vote) + return + end + + activeVotes[steamID] = octolib.questions.start({ + text = L.want_job_hint:format(ply:Name(), job.name), + recipients = boss, + left = 'Принять', + right = 'Отклонить', + time = 180, + sound = 'Town.d1_town_02_elevbell1', + onFinish = function(result) + activeVotes[steamID] = nil + if not IsValid(ply) then return end + if result > 0 then + ply:Notify(L.your_request_approved) + makeJob(ply, job) + if IsValid(boss) then boss:Notify(L.request_approved) end + elseif result < 0 then + ply:Notify(L.your_request_rejected) + if IsValid(boss) then boss:Notify(L.request_rejected) end + ply.dbgPolice_nextApply = CurTime() + 1800 + else + ply:Notify('Твою заявку пропустили :(') + end + end, + }) + + ply:Notify(L.application_sent_hint) + ply.dbgPolice_nextApply = CurTime() + 180 + +end + +net.Receive('dbg-police.apply', function(len, ply) + + local job, jobID = net.ReadString() + if CurTime() < (ply.dbgPolice_nextApply or 0) then + ply:Notify('warning', L.wait_boy) + return + end + ply.dbgPolice_nextApply = CurTime() + 1 + + if ply:isWanted() then + ply:Notify('warning', L.you_wanted) + return + end + + if ply:GetKarma() <= -50 then + ply:Notify('warning', L.bad_reputation) + return + end + + local time = ply:CheckPoliceDenied() + if time == true then + return ply:Notify('warning', 'Ты не можешь играть за полицейские профессии') + elseif time then + return ply:Notify('warning', 'Ты сможешь играть за полицейские профессии ' .. octolib.time.formatIn(time)) + end + + if ply:IsHandcuffed() then + ply:Notify('warning', L.error_cuffs) + return + end + + local curJob = ply:getJobTable() + if curJob.hobo then + ply:Notify('warning', L.hobo_cant_work) + return + end + + if curJob.canJoinPolice and not curJob.canJoinPolice(ply) then + ply:Notify(select(2, curJob.canJoinPolice(ply))) + return + end + + if demotedPlys[ply:SteamID()] then + ply:Notify('warning', L.wait_after_demote) + return + end + + if not inPoliceLimits() then + if not avoidLimit[job] then + ply:Notify('warning', 'В городе сейчас много полиции, попробуй позже!') + return + end + end + + job, jobID = DarkRP.getJobByCommand(job) + if not job or not job.police or job.noPBoard then + ply:Notify('warning', L.job_doesnt_exist2) + return + end + + if isfunction(job.customCheck) then + local allow, reason = job.customCheck(ply) + if not allow then + reason = reason or job.customCheckFailMsg or L.job_denied + ply:Notify('warning', reason) + return + end + end + + if jobID == TEAM_CHIEF or jobID == TEAM_MAYOR then + local text = net.ReadString() + if startVote(ply, job, text and string.Trim(text) or nil) then + if (text and text ~= '') and activeVotes[steamID] then + octochat.talkTo(nil, octochat.textColors.rp, '[Предвыборная речь] ', ply:Name(), ': ', Color(250,250,200), text) + end + end + else + if team.NumPlayers(TEAM_CHIEF) > 0 then + sendApplication(ply, job, team.GetPlayers(TEAM_CHIEF)[1]) + elseif team.NumPlayers(TEAM_MAYOR) > 0 then + sendApplication(ply, job, team.GetPlayers(TEAM_MAYOR)[1]) + else + for i, v in ipairs(player.GetAll()) do + if v:GetNetVar('dbg-police.job') == 'mayor' or v:GetNetVar('dbg-police.job') == 'chief' then + sendApplication(ply, job, v) + return + end + end + + ply:Notify(L.automatic_accepted) + makeJob(ply, job) + end + end + +end) + +net.Receive('dbg-police.quit', function(len, ply) + + if ply:GetNetVar('dbg-police.job', '') == '' then + ply:Notify('warning', L.you_not_work_in_police) + return + end + + if IsValid(ply.tazeragdoll) then + ply:Notify('warning', L.error_tazer) + return + end + + loseJob(ply) + + ply:Notify(L.you_retired) + + if ply.dbgPolice_citizenData then + ply:RestoreCitizen() + end + +end) + +net.Receive('dbg-police.fire', function(len, ply) + + local target = net.ReadEntity() + + if CurTime() < (ply.dbgPolice_nextFire or 0) then + ply:Notify('warning', L.wait_boy) + return + end + ply.dbgPolice_nextFire = CurTime() + 1 + + if not IsValid(target) or not target:IsPlayer() then + return ply:addExploitAttempt() + end + + if ply:GetNetVar('dbg-police.job') ~= 'mayor' and ply:GetNetVar('dbg-police.job') ~= 'chief' then + ply:Notify('warning', L.you_cant_demote) + return + end + + if target:GetNetVar('dbg-police.job') == 'mayor' then + ply:Notify('warning', L.you_cant_demote_up) + return + end + + if target:GetNetVar('dbg-police.job', '') == '' then + ply:Notify('warning', L.he_not_work_in_police) + return + end + + if IsValid(target.tazeragdoll) then + target:Notify('warning', L.him_dont_stay) + return + end + + loseJob(target) + target:Notify(L.police_demoted_you:format(ply:Name())) + ply:Notify(L.police_demoted_by_you:format(target:Name())) + + if target.dbgPolice_citizenData then + target:RestoreCitizen() + end + +end) + +hook.Add('PlayerDisconnected', 'dbg-police', function(ply) + + local steamID = ply:SteamID() + if activeVotes[steamID] then + octolib.questions.finish(activeVotes[steamID], true) + activeVotes[steamID] = nil + end + +end) + +local function loseJobOnSpawn(ply) + + if ply:GetNetVar('dbg-police.job', '') ~= '' then + ply:Notify(L.death_lose_job) + loseJob(ply) + end + ply.dbgPolice_citizenData = nil + +end +hook.Add('dbg-char.spawn', 'dbg-police', loseJobOnSpawn) + +local function keysAccess(ply, ent) + + local data = dbgEstates.getData(ent:GetEstateID()) + if not data or not data.owners then return end + for _,v in ipairs(data.owners) do + if v == 'g:police' then + local job = DarkRP.getJobByCommand(ply:GetNetVar('dbg-police.job', '')) + return job and job.police or nil + end + end + +end +hook.Add('canKeysLock', 'dbg-police', keysAccess) +hook.Add('canKeysUnlock', 'dbg-police', keysAccess) + +local function loseJobOnDemote(demoter, ply, reason) + + loseJob(ply) + if ply.dbgPolice_citizenData then + ply:RestoreCitizen() + end + +end +hook.Add('onPlayerDemoted', 'dbg-police', loseJobOnDemote) + +hook.Add('dbg-winter.isWarm', 'dbg-police', function(ply) + if ply:isCP() then + return true + end +end) diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/shared.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/shared.lua new file mode 100644 index 0000000..f5eb9ba --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policeboard/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.work_in_police +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/cl_init.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/cl_init.lua new file mode 100644 index 0000000..d266aba --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/cl_init.lua @@ -0,0 +1,209 @@ +include('shared.lua') + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + self:DrawModel() + render.DrawBubble(self, Vector(8.6, 0, 24.5), Angle(0, 90, 90), L.equipment, 200, 200) +end + +local skins = { + medcop = 0, + cop = 0, + cop2 = 1, + chief = 2, +} + +net.Receive('dbg-police.equip', function(len) + + local ply = LocalPlayer() + + local ent = net.ReadEntity() + local equip = net.ReadBool() + local job = ply:GetNetVar('dbg-police.job', '') + + if equip then + local mdls = net.ReadTable() + local f = vgui.Create 'DFrame' + f:SetSize(260, 520) + f:SetTitle(L.equipment) + f:DockPadding(8,30,8,8) + f:Center() + f:MakePopup() + + local p = vgui.Create 'DPanel' + p:SetParent(f) + p:Dock(TOP) + p:SetTall(350) + if job == 'mayor' then + p.model = ply:GetModel() + p.skin = ply:GetSkin() + p.choice = 1 + p.hat = true + else + p.model = mdls[1][1] + p.skin = mdls[1][2] + p.choice = 1 + p.hat = true + end + + local m = vgui.Create 'DModelPanel' + m:SetParent(p) + m:Dock(FILL) + m:SetCamPos(Vector(55,0,66)) + m:SetLookAng(Angle(10,180,0)) + m:SetFOV(25) + m:SetCursor('none') + function m:LayoutEntity( mdl ) + mdl:SetAngles( Angle( 0, 15, 0) ) + mdl.GetPlayerColor = function() return ply:GetPlayerColor() end + return + end + function m:UpdateData() + self:SetModel(p.model) + if job ~= 'mayor' then + self.Entity:SetSkin(skins[job]) + self.Entity:SetBodygroup(4, p.hat and (job == 'chief' and 2 or 1) or 0) + else + self.Entity:SetSkin(p.skin) + end + end + m:UpdateData() + + local p2 = vgui.Create 'DPanel' + p2:SetParent(f) + p2:Dock(FILL) + p2:DockMargin(0,5,0,0) + p2:SetPaintBackground(false) + + local l = vgui.Create 'DLabel' + l:SetParent(p2) + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetTall(30) + l:SetText(L.appearance) + l:SetFont('f4.normal') + + local h = p2:Add 'DCheckBoxLabel' + h:Dock(TOP) + h:DockMargin(0,5,0,5) + h:SetText(L.hat) + h:SetValue(1) + function h:OnChange(val) + if val then + p.hat = true + m.Entity:SetBodygroup(4, job == 'chief' and 2 or 1) + else + p.hat = false + m.Entity:SetBodygroup(4, 0) + end + end + + local c = vgui.Create 'DComboBox' + c:SetParent(p2) + c:Dock(TOP) + c:DockMargin(0,0,0,5) + c:SetTall(30) + c:SetSortItems(false) + + if job == 'mayor' then + c:SetEnabled(false) + else + function c:OnSelect(i, v, data) + p.model = data[1] + p.skin = data[2] + p.choice = i + m:UpdateData() + end + end + + for i, v in ipairs(mdls) do + c:AddChoice(L.appearance2 .. i, v, i == 1) + end + + local b = vgui.Create 'DButton' + b:SetParent(p2) + b:Dock(TOP) + b:DockMargin(0,5,0,0) + b:SetTall(30) + b:SetText(L.get_equipment) + function b:DoClick() + net.Start('dbg-police.equip') + net.WriteEntity(ent) + net.WriteBool(true) + net.WriteUInt(p.choice, 8) + net.WriteBool(p.hat) + net.SendToServer() + f:Remove() + end + else + local f = vgui.Create 'DFrame' + f:SetSize(260, 465) + f:SetTitle(L.equipment) + f:DockPadding(8,30,8,8) + f:Center() + f:MakePopup() + + local p = vgui.Create 'DPanel' + p:SetParent(f) + p:Dock(TOP) + p:SetTall(350) + p.model = ply:GetModel() + p.skin = ply:GetSkin() + p.bg = ply:GetBodygroup(4) + + local m = vgui.Create 'DModelPanel' + m:SetParent(p) + m:Dock(FILL) + m:SetCamPos(Vector(55,0,66)) + m:SetLookAng(Angle(10,180,0)) + m:SetFOV(25) + m:SetCursor('none') + m:SetModel(ply:GetModel()) + m.Entity:SetSkin(ply:GetSkin()) + timer.Simple(0, function() m.Entity:SetBodygroup(4, p.bg) end) + + function m:LayoutEntity( mdl ) + mdl:SetAngles( Angle( 0, 15, 0) ) + mdl.GetPlayerColor = function() return ply:GetPlayerColor() end + return + end + + local p2 = vgui.Create 'DPanel' + p2:SetParent(f) + p2:Dock(FILL) + p2:DockMargin(0,5,0,0) + p2:SetPaintBackground(false) + + local b1 = vgui.Create 'DButton' + b1:SetParent(p2) + b1:Dock(TOP) + b1:DockMargin(0,5,0,0) + b1:SetTall(30) + b1:SetText(L.get_ammo) + function b1:DoClick() + net.Start('dbg-police.equip') + net.WriteEntity(ent) + net.WriteBool(true) + net.SendToServer() + f:Remove() + end + + local b2 = vgui.Create 'DButton' + b2:SetParent(p2) + b2:Dock(TOP) + b2:DockMargin(0,5,0,0) + b2:SetTall(30) + b2:SetText(L.hand_over_equipment) + function b2:DoClick() + net.Start('dbg-police.equip') + net.WriteEntity(ent) + net.WriteBool(false) + net.SendToServer() + f:Remove() + end + end + +end) diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/init.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/init.lua new file mode 100644 index 0000000..d094ef6 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/init.lua @@ -0,0 +1,204 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +local GM = GM or GAMEMODE + +function ENT:Initialize() + + self:SetModel('models/props_c17/lockers001a.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + +end + +local skins = { + medcop = 0, + cop = 0, + cop2 = 1, + chief = 2, +} + +local mdls = { + male = { + { 'models/humans/dpdsl/male_01_01.mdl' }, + { 'models/humans/dpdsl/male_02_01.mdl' }, + { 'models/humans/dpdsl/male_03_01.mdl' }, + { 'models/humans/dpdsl/male_04_01.mdl' }, + { 'models/humans/dpdsl/male_05_01.mdl' }, + { 'models/humans/dpdsl/male_06_01.mdl' }, + { 'models/humans/dpdsl/male_07_01.mdl' }, + { 'models/humans/dpdsl/male_08_01.mdl' }, + { 'models/humans/dpdsl/male_09_01.mdl' }, + }, + female = { + { 'models/humans/dpdfem/female_01.mdl' }, + { 'models/humans/dpdfem/female_02.mdl' }, + { 'models/humans/dpdfem/female_03.mdl' }, + { 'models/humans/dpdfem/female_04.mdl' }, + { 'models/humans/dpdfem/female_06.mdl' }, + { 'models/humans/dpdfem/female_07.mdl' }, + } +} + +util.AddNetworkString 'dbg-police.equip' +function ENT:Use( ply ) + + if not IsValid(ply) then return end + + if ply.currentOrg then + return ply:Notify('warning', 'Сначала нужно сдать форму организации') + end + + if ply.armorItem then + if ply.armorItem.amount == ply:Armor() then + return ply:Notify('warning', 'Нужно снять свой бронежилет') + else + ply.armorItem = nil + end + end + + local job = ply:GetNetVar('dbg-police.job', '') + job = DarkRP.getJobByCommand(job) + + if not job or not job.police then + return ply:Notify('warning', L.this_locker_only_for_police) + end + + if next(ply.Buffs) then -- if he has any drug buff + return ply:Notify('warning', 'Чтобы заступить на службу, нужна чистая голова! Приведи себя в порядок и возвращайся позже') + end + + local models = table.Copy(mdls[ply:GetModel():find('female') and 'female' or 'male']) + local skin = skins[job.command] or 0 + for i, v in ipairs(models) do + v[2] = skin + end + + net.Start('dbg-police.equip') + net.WriteEntity(self) + if ply:getJobTable().police then + net.WriteBool(false) + else + net.WriteBool(true) + net.WriteTable(models) + end + net.Send(ply) + + ply.dbgPolice_policeModels = models + +end + +net.Receive('dbg-police.equip', function(len, ply) + + local ent = net.ReadEntity() + local equip = net.ReadBool() + + if ply.dbgPolice_nextEquip and CurTime() < ply.dbgPolice_nextEquip then + ply:Notify('warning', L.wait_boy) + return + end + ply.dbgPolice_nextEquip = CurTime() + 1 + + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_policelocker' or ply:GetPos():DistToSqr(ent:GetPos()) > 90000 then + return ply:addExploitAttempt() + end + + local pJob, pJobID = DarkRP.getJobByCommand(ply:GetNetVar('dbg-police.job', '')) + local job = ply:getJobTable() + + if not pJob then + ply:Notify('warning', L.error_job) + return + end + + if ply:IsHandcuffed() then + ply:Notify('warning', L.error_cuffs) + return + end + + if IsValid(ply.tazeragdoll) then + ply:Notify('warning', L.error_tazer) + return + end + + local time = ply:CheckPoliceDenied() + if time == true then + return ply:Notify('warning', 'Ты не можешь играть за полицейские профессии') + elseif time then + return ply:Notify('warning', 'Ты сможешь играть за полицейские профессии ' .. octolib.time.formatIn(time)) + end + + local veh = carDealer.getCurVeh(ply) + if equip then + if job.police then + local curWep = ply:GetActiveWeapon() + if IsValid(curWep) and curWep.civil then + return ply:Notify('Ты не можешь заряжать личное оружие из служебного запаса') + end + local medkit = ply:GetWeapon('med_kit') + if IsValid(medkit) then medkit:SetClip1(200) end + ply:SetAmmo(180, 'pistol') + ply:SetAmmo(360, 'SMG1') + ply:SetArmor(job.armor or 0) + -- ply:EmitSound('') + else + if IsValid(veh) then + return ply:Notify('Сначала нужно загнать гражданский автомобиль') + end + + local mdlChoice = net.ReadUInt(8) + local hat = net.ReadBool() + local mdlData = ply.dbgPolice_policeModels and ply.dbgPolice_policeModels[mdlChoice] + if not mdlData then return end + + ply:SelectWeapon('dbg_hands') + + ply:SaveCitizen() + if isfunction(pJob.customCheck) then + local allow, reason = pJob.customCheck(ply) + if not allow then + reason = reason or pJob.customCheckFailMsg or L.job_denied + ply:Notify('warning', reason) + return + end + end + + ply:SetClothes(nil) + ply:changeTeam(pJobID, true, true) + if pJobID ~= TEAM_MAYOR then + ply:SetModel(mdlData[1]) + local cmd = pJob.command + ply:SetSkin(skins[cmd]) + ply:SetBodygroup(4, hat and (cmd == 'chief' and 2 or 1) or 0) + end + local medkit = ply:GetWeapon('med_kit') + if IsValid(medkit) then medkit:SetClip1(200) end + ply:SetAmmo(180, 'pistol') + ply:SetAmmo(360, 'SMG1') + ply:SetArmor(pJob.armor or 0) + ply:SelectWeapon('dbg_hands') + + ply:ConnectTalkie('ems') + end + else + if not job.police then return ply:addExploitAttempt() end + if not ply.dbgPolice_citizenData then + ply:Notify('warning', L.error_character) + return + end + + if IsValid(veh) then + return ply:Notify('Сначала нужно загнать служебный автомобиль') + end + + ply:RestoreCitizen() + end + +end) diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/shared.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/shared.lua new file mode 100644 index 0000000..6c89207 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_policelocker/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.policelocker +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/cl_init.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/cl_init.lua new file mode 100644 index 0000000..7b401a6 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/cl_init.lua @@ -0,0 +1,5 @@ +include 'shared.lua' + +function ENT:Draw() + self:DrawModel() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/init.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/init.lua new file mode 100644 index 0000000..d397946 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/init.lua @@ -0,0 +1,29 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/notakid/spikestrips/spikestrip.mdl') + self:SetMaterial('models/debug/debugwhite') + self:SetColor(Color(36, 36, 36, 255)) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + self:SetTrigger(true) + +end + +function ENT:StartTouch(otherEnt) + + if not IsValid(otherEnt) or otherEnt:GetClass() ~= 'gmod_sent_vehicle_fphysics_wheel' then return end + + otherEnt:TakeDamage(math.random(0, 3)) + + timer.Simple(2, function() + if not IsValid(self) then return end + self:Remove() + end) + +end diff --git a/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/shared.lua b/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/shared.lua new file mode 100644 index 0000000..7a4bf74 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/entities/ent_dbg_spike_strips/shared.lua @@ -0,0 +1,5 @@ +ENT.Type = 'anim' +ENT.PrintName = 'Полицейские шипы' +ENT.Category = 'Доброград' +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/feature-police/lua/police/jailpos.lua b/garrysmod/addons/feature-police/lua/police/jailpos.lua new file mode 100644 index 0000000..8cd0820 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/police/jailpos.lua @@ -0,0 +1,47 @@ +local jailPos = {} +local nextJail = 0 + +local function refreshJails() + + octolib.getDBVar('jailpos', game.GetMap(), {}):Then(function(data) + jailPos = data + end) + +end + +hook.Add('octolib.db.init', 'dbg-police.jailPos', function() + timer.Simple(5, refreshJails) +end) + +hook.Add('octolib.event:dbg-police.refreshJails', 'dbg-police.refreshJails', function(data) + if data[1] == game.GetMap() then refreshJails() end +end) + +local function save() + octolib.setDBVar('jailpos', game.GetMap(), jailPos):Then(function() + octolib.sendCmdToOthers('dbg-police.refreshJails', {game.GetMap()}) + end) +end + +function dbgPolice.addJailPos(pos) + if not isvector(pos) then return end + jailPos[#jailPos + 1] = {pos.x, pos.y, pos.z} + save() +end + +function dbgPolice.clearJailPos() + jailPos = {} + save() +end + +function dbgPolice.nextJailPos() + nextJail = nextJail + 1 + if nextJail > #jailPos then nextJail = 1 end + local pos = jailPos[nextJail] + if not pos then return end + return Vector(pos[1], pos[2], pos[3]) +end + +function dbgPolice.haveJailPos() + return jailPos[1] ~= nil +end diff --git a/garrysmod/addons/feature-police/lua/police/k9.lua b/garrysmod/addons/feature-police/lua/police/k9.lua new file mode 100644 index 0000000..1b84257 --- /dev/null +++ b/garrysmod/addons/feature-police/lua/police/k9.lua @@ -0,0 +1,17 @@ +local k9Whitelist = octolib.array.toKeys { '/me', '/it', '/pit', '/looc', '/ooc', '/pm', '//it', '/roll', '!invisible', '/spawn', '/spectate', '/admintell', '/admintellall' } + +hook.Add('octochat.canExecute', 'dbg-police.k9', function(ply, cmd) + if not k9Whitelist[cmd] and ply:getJobTable().notHuman then return false, 'Ты не человек...' end +end) + +hook.Add('PlayerSay', 'dbg-police.k9', function(ply) + if ply:getJobTable().notHuman then + ply:Notify('warning', 'Ты не человек...') + return '' + end +end, 1) + +hook.Add('octolib.shouldOpenMenu', 'dbg-police.k9', function() + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'dbg_dog' then return false end +end) diff --git a/garrysmod/addons/feature-police/lua/police/use.lua b/garrysmod/addons/feature-police/lua/police/use.lua new file mode 100644 index 0000000..a6fe71b --- /dev/null +++ b/garrysmod/addons/feature-police/lua/police/use.lua @@ -0,0 +1,24 @@ +CFG.use.ent_dbg_spike_strips = { + function(ply, ent) + if not (ply:Team() == TEAM_DPD or ply:Team() == TEAM_WCSO) then return end + return 'Поднять', 'octoteam/icons/arrow_up2.png', function(ply, ent) + ply:DelayedAction('grabspike', 'Подбор шипов', { + time = 1, + check = function() return octolib.use.check(ply, ent) end, + succ = function() + if not IsValid(ply) or not IsValid(ent) then return end + + ply:AddItem('spike_strips', {expire = ent.expire or os.time() + 7200}) + ent:Remove() + end, + }, { + time = 1.5, + inst = true, + action = function() + ent:EmitSound('physics/rubber/rubber_tire_strain'..math.random(1,3)..'.wav', 65) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0,1)) + end, + }) + end + end, +} diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/cat_dobrograd.lua b/garrysmod/addons/feature-radio/lua/autorun/client/cat_dobrograd.lua new file mode 100644 index 0000000..f1a6bb6 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/cat_dobrograd.lua @@ -0,0 +1,45 @@ +octoradio = octoradio or {} + +local function loadStations(lst, curOffset, data) + if not IsValid(lst) then return end + + for _,v in ipairs(data) do + lst:AddStation(v.name, v.place, v.country, v.id) + end + + if #data == 25 then + octoradio.btnNext:SetEnabled(true) + function octoradio.btnNext:DoClick() + lst:Clear() + netstream.Request('octoradio.getByPlace', {'Dobrograd MI', 'United States', curOffset + 25}):Then(function(data) + loadStations(lst, curOffset + 25, data) + end):Catch(print) + end + else octoradio.btnNext:SetEnabled(false) end + + if curOffset > 0 then + octoradio.btnPrev:SetEnabled(true) + function octoradio.btnPrev:DoClick() + lst:Clear() + netstream.Request('octoradio.getByPlace', {'Dobrograd MI', 'United States', math.max(curOffset - 25, 0)}):Then(function(data) + loadStations(lst, math.max(curOffset - 25, 0), data) + end):Catch(print) + end + else octoradio.btnPrev:SetEnabled(false) end + + lst:InvalidateChildren() +end + +function octoradio.populateDobrograd(pan) + + local lst = octoradio.createList(pan) + netstream.Request('octoradio.getByPlace', {'Dobrograd MI', 'United States', 0}):Then(function(data) + loadStations(lst, 0, data) + end) + + local submit = octolib.button(pan, 'Добавить свою радиостанцию', function() + octoesc.OpenURL('https://octo.gg/dbg-radio') + end) + submit:Dock(BOTTOM) + +end diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/cat_favorite.lua b/garrysmod/addons/feature-radio/lua/autorun/client/cat_favorite.lua new file mode 100644 index 0000000..a8e6d85 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/cat_favorite.lua @@ -0,0 +1,88 @@ +octoradio = octoradio or {} + +-- load favs +octoradio.favs = {} +local f = file.Read('octoradio_favs2.dat') +if f then octoradio.favs = pon.decode(f) or {} end + +function octoradio.findFavById(id) + for i,v in ipairs(octoradio.favs) do + if v == id then return i end + end +end + +function octoradio.favSave() + file.Write('octoradio_favs2.dat', pon.encode(octoradio.favs)) +end + +local lst + +local function loadStations(curOffset, data) + if not IsValid(lst) then return end + + for _,v in ipairs(data) do + lst:AddStation(v.title, v.placeName, v.country, v.id) + end + + if #data == 25 then + octoradio.btnNext:SetEnabled(true) + function octoradio.btnNext:DoClick() + lst:Clear() + local nxt = {} + for i = curOffset + 25, math.min(#octoradio.favs, curOffset + 50) do + nxt[#nxt + 1] = octoradio.favs[i] + end + netstream.Request('octoradio.getById', nxt):Then(function(data) + loadStations(curOffset + 25, data) + end):Catch(print) + end + else octoradio.btnNext:SetEnabled(false) end + if curOffset > 1 then + octoradio.btnPrev:SetEnabled(true) + function octoradio.btnPrev:DoClick() + lst:Clear() + local nxt = {} + for i = math.max(curOffset - 25, 1), curOffset - 1 do + nxt[#nxt + 1] = octoradio.favs[i] + end + netstream.Request('octoradio.getById', nxt):Then(function(data) + loadStations(math.max(curOffset - 25, 1), data) + end):Catch(print) + end + else octoradio.btnPrev:SetEnabled(false) end + + lst:InvalidateChildren() +end + +local function updateTab() + lst:Clear() + local tbl = {} + for i = 1, math.min(25, #octoradio.favs) do + tbl[i] = octoradio.favs[i] + end + netstream.Request('octoradio.getById', tbl):Then(function(data) + loadStations(1, data) + end) +end +function octoradio.populateFavorite(pan) + lst = octoradio.createList(pan) + updateTab() +end + +function octoradio.favAdd(id) + if not table.HasValue(octoradio.favs, id) then + octoradio.favs[#octoradio.favs + 1] = id + updateTab() + octoradio.favSave() + end +end + +function octoradio.favRemove(id) + local favID = octoradio.findFavById(id) + if favID then + table.remove(octoradio.favs, favID) + updateTab() + octoradio.favSave() + end +end + diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/cat_search.lua b/garrysmod/addons/feature-radio/lua/autorun/client/cat_search.lua new file mode 100644 index 0000000..14d7981 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/cat_search.lua @@ -0,0 +1,91 @@ +octoradio = octoradio or {} +octoradio.search = octoradio.search or {} + +local function loadSearch(lst, query, place, country, curOffset, data) + if not IsValid(lst) then return end + + for _,v in ipairs(data) do + lst:AddStation(v.title, v.placeName, v.country, v.id) + end + + octoradio.search.returnBtn:SetVisible(true) + octoradio.search.returnBtn.DoClick = octoradio.search.mainPage + + if #data == 25 then + octoradio.btnNext:SetEnabled(true) + function octoradio.btnNext:DoClick() + lst:Clear() + netstream.Request('octoradio.search', {query, place, country, curOffset + 25}):Then(function(data) + loadSearch(lst, query, place, country, curOffset + 25, data) + end):Catch(print) + end + else octoradio.btnNext:SetEnabled(false) end + + if curOffset > 0 then + octoradio.btnPrev:SetEnabled(true) + function octoradio.btnPrev:DoClick() + lst:Clear() + netstream.Request('octoradio.search', {query, place, country, math.max(curOffset - 25, 0)}):Then(function(data) + loadSearch(lst, query, place, country, math.max(curOffset - 25, 0), data) + end):Catch(print) + end + else octoradio.btnPrev:SetEnabled(false) end + + lst:InvalidateChildren() +end + +function octoradio.populateSearch(pan) + + octoradio.search = {} + octoradio.search.pan = pan + local searchPan = pan:Add('DPanel') + searchPan:Dock(TOP) + searchPan:SetPaintBackground(false) + searchPan:SetTall(32) + local returnBtn = searchPan:Add('DImageButton') + returnBtn:Dock(LEFT) + returnBtn:DockMargin(4, 8, 4, 8) + returnBtn:SetWide(16) + returnBtn:SetImage('octoteam/icons-16/arrow_left.png') + octoradio.search.returnBtn = returnBtn + local searchEntry = octolib.textEntry(searchPan) + searchEntry:Dock(FILL) + searchEntry:SetPlaceholderText('Поиск радиостанций в мире') + octoradio.search.entry = searchEntry + local searchBtn = searchEntry:Add('DImageButton') + searchBtn:Dock(RIGHT) + searchBtn:DockMargin(0, 6, 8, 6) + searchBtn:SetWide(16) + searchBtn:SetImage('octoteam/icons-16/zoom.png') + function searchBtn:DoClick() + searchEntry:OnValueChange(searchEntry:GetValue()) + end + + function searchEntry:OnValueChange(val) + octoradio.search.lst:Clear() + netstream.Request('octoradio.search', {val, octoradio.search.place, octoradio.search.country, 0}):Then(function(data) + loadSearch(octoradio.search.lst, val, octoradio.search.place, octoradio.search.country, 0, data) + end):Catch(print) + end + + local lst = octoradio.createList(pan) + octoradio.search.lst = lst + + function octoradio.search.mainPage() + lst:Clear() + octoradio.search.place, octoradio.search.country = nil + returnBtn:SetVisible(false) + searchPan:InvalidateLayout() + searchEntry:SetPlaceholderText('Поиск радиостанций в мире') + local countries = netvars.GetNetVar('octoradio.countries', {}) + for k,v in SortedPairsByValue(countries, true) do + lst:AddCountry(k, v) + end + lst:InvalidateChildren() + end + + octoradio.btnPrev:SetEnabled(false) + octoradio.btnNext:SetEnabled(false) + octoradio.search.mainPage() + +end diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/cat_stations.lua b/garrysmod/addons/feature-radio/lua/autorun/client/cat_stations.lua new file mode 100644 index 0000000..4e26224 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/cat_stations.lua @@ -0,0 +1,40 @@ +octoradio = octoradio or {} + +local function loadStations(lst, curPage, data) + if not IsValid(lst) then return end + + for _,v in ipairs(data) do + lst:AddStation(v.title, v.place, v.country, v.id) + end + + if #data == 25 then + octoradio.btnNext:SetEnabled(true) + function octoradio.btnNext:DoClick() + lst:Clear() + netstream.Request('octoradio.getStations', octoradio.ent, curPage + 1):Then(function(data) + loadStations(lst, curPage + 1, data) + end):Catch(print) + end + else octoradio.btnNext:SetEnabled(false) end + + if curPage > 1 then + octoradio.btnPrev:SetEnabled(true) + function octoradio.btnPrev:DoClick() + lst:Clear() + netstream.Request('octoradio.getStations', octoradio.ent, curPage - 1):Then(function(data) + loadStations(lst, curPage - 1, data) + end):Catch(print) + end + else octoradio.btnPrev:SetEnabled(false) end + + lst:InvalidateChildren() +end + +function octoradio.populateStations(pan) + + local lst = octoradio.createList(pan) + netstream.Request('octoradio.getStations', octoradio.ent, 1):Then(function(data) + loadStations(lst, 1, data) + end) + +end diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/cat_top.lua b/garrysmod/addons/feature-radio/lua/autorun/client/cat_top.lua new file mode 100644 index 0000000..18a44ab --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/cat_top.lua @@ -0,0 +1,34 @@ +octoradio = octoradio or {} + +local api = octolib.api({ + url = 'http://radio.garden/api/ara/content', + headers = {}, +}) + +function octoradio.populateTop(pan) + + local lst = octoradio.createList(pan) + netstream.Request('octoradio.top10'):Then(function(data) + if not IsValid(lst) then return end + local added = {} + for _,v in ipairs(data) do + lst:AddStation(v.title, v.place, v.country, v.id):SetColumnText(3, tostring(v.score)) + added[v.id] = true + end + api:get('/static-pages/our-favorites'):Then(function(res) + if not IsValid(lst) or not res or not res.data or not res.data.data or not res.data.data.content then return end + for _,resLst in ipairs(res.data.data.content) do + if resLst.itemsType == 'channel' then + for _,item in ipairs(resLst.items) do + local id = item.href:gsub('.+%/', '') + if not added[id] then + lst:AddStation(item.title, item.subtitle:gsub(', .+', ''), item.subtitle:gsub('.+, ', ''), id) + added[id] = true + end + end + end + end + end) + end):Catch(print) + +end diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/octo_radio.lua b/garrysmod/addons/feature-radio/lua/autorun/client/octo_radio.lua new file mode 100644 index 0000000..09b0272 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/octo_radio.lua @@ -0,0 +1,98 @@ +octoradio = octoradio or {} + +netstream.Hook('dbg-radio.control', function(ent, whitelisted, id, title, place, country) + + if not IsValid(ent) then return end + local dist, vol = ent:GetDistance(), ent:GetVolume() + + if IsValid(octoradio.pnl) then + octoradio.pnl:Close() + end + + octoradio.ent, octoradio.whitelisted, octoradio.dist, octoradio.vol, octoradio.id = ent, whitelisted, dist, vol * 100, id + octoradio.curTitle, octoradio.curPlace, octoradio.curCountry = title, place, country + + local f = vgui.Create 'DFrame' + f:SetSize(350, 550) + f:SetTitle(L.radio) + f:Center() + f:MakePopup() + octoradio.pnl = f + local panel = f:Add('DPanel') + panel:Dock(FILL) + panel:SetPaintBackground(false) + + local tabs = panel:Add('DPropertySheet') + tabs:Dock(FILL) + + local search, top, dobrograd, favs, stations + if not whitelisted then + + search = tabs:Add('DPanel') + tabs:AddSheet('Поиск', search, octolib.icons.silk16('compass')) + + top = tabs:Add('DPanel') + tabs:AddSheet('Популярно', top, octolib.icons.silk16('crown_gold')) + + dobrograd = tabs:Add('DPanel') + tabs:AddSheet('Сообщество', dobrograd, octolib.icons.silk16('group')) + + favs = tabs:Add('DPanel') + tabs:AddSheet('Избранное', favs, octolib.icons.silk16('star')) + + else + + stations = tabs:Add('DPanel') + tabs:AddSheet('Станции', stations, octolib.icons.silk16('transmit')) + + end + + local btmPan = panel:Add('DPanel') + btmPan:Dock(BOTTOM) + btmPan:DockMargin(115, 0, 115, 0) + btmPan:SetTall(32) + btmPan:SetPaintBackground(false) + + local btnPrev = btmPan:Add('DImageButton') + btnPrev:Dock(LEFT) + btnPrev:DockMargin(0, 0, 5, 0) + btnPrev:SetWide(32) + btnPrev:SetImage(octolib.icons.silk32('control_rewind_blue')) + octoradio.btnPrev = btnPrev + + local btnInfo = btmPan:Add('DImageButton') + btnInfo:Dock(LEFT) + btnInfo:DockMargin(0, 0, 5, 0) + btnInfo:SetWide(32) + btnInfo:SetImage(octolib.icons.silk32('information')) + btnInfo:SetEnabled(octoradio.id) + btnInfo.DoClick = octoradio.displayCurStation + octoradio.btnInfo = btnInfo + + local btnNext = btmPan:Add('DImageButton') + btnNext:Dock(LEFT) + btnNext:SetWide(32) + btnNext:SetImage(octolib.icons.silk32('control_fastforward_blue')) + octoradio.btnNext = btnNext + + if not whitelisted then + octoradio.populateSearch(search) + octoradio.populateTop(top) + octoradio.populateDobrograd(dobrograd) + octoradio.populateFavorite(favs) + else + octoradio.populateStations(stations) + end +end) + +netstream.Hook('dbg-radio.curStUpdate', function(ent, id, title, place, country) + if not IsValid(octoradio.pnl) then return end + if octoradio.ent ~= ent then return end + octoradio.ent, octoradio.dist, octoradio.vol, octoradio.id = ent, ent:GetDistance(), ent:GetVolume() * 100, id + octoradio.curTitle, octoradio.curPlace, octoradio.curCountry = title, place, country + + octoradio.btnInfo:SetEnabled(octoradio.id) + if IsValid(octoradio.curStFrame) then + octoradio.curStFrame:Update() + end +end) diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/sound.lua b/garrysmod/addons/feature-radio/lua/autorun/client/sound.lua new file mode 100644 index 0000000..34e247e --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/sound.lua @@ -0,0 +1,49 @@ +hook.Add('octolib.netVarUpdate', 'dbg-radio', function(idx, var, val) + if not isnumber(idx) then return end + local ent = Entity(idx) + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_radio' then return end + + if var == 'stream' then + if val then + ent:StartStream(val) + if IsValid(ent:GetParent()) and ent:GetParent():GetClass() == 'gmod_sent_vehicle_fphysics_base' then + local veh = LocalPlayer():GetVehicle() + ent:Set2D(IsValid(veh) and veh.vehiclebase == ent:GetParent()) + end + else ent:StopStream() end + return + end + + if not val then return end + if var == 'volume' then ent:SetVolume(val) + elseif var == 'dist' then ent:SetDistance(val) end + +end) + +hook.Add('NotifyShouldTransmit', 'dbg-radio', function(ent, should) + if not should or ent:GetClass() ~= 'ent_dbg_radio' or not ent.GetStreamURL then return end + local url = ent:GetStreamURL() + if not url then return end + if ent.stream.url ~= url then + ent:StartStream(url) + end +end) + +hook.Add('VehicleChanged', 'dbg-radio', function(ply, new, old) + + if ply ~= LocalPlayer() then return end + local seat = IsValid(new) and new or old + if not IsValid(seat) or not IsValid(seat.vehiclebase) then return end + if new.vehiclebase == old.vehiclebase then return end + + local car = seat.vehiclebase + local radio + for _,v in ipairs(car:GetChildren()) do + if v:GetClass() == 'ent_dbg_radio' then + radio = v + break + end + end + if radio then radio:Set2D(IsValid(new)) end + +end) diff --git a/garrysmod/addons/feature-radio/lua/autorun/client/vgui_utils.lua b/garrysmod/addons/feature-radio/lua/autorun/client/vgui_utils.lua new file mode 100644 index 0000000..4f6c0c3 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/client/vgui_utils.lua @@ -0,0 +1,340 @@ +local function loadPlaces(lst, country, curOffset, data) + if not IsValid(lst) then return end + + octoradio.search.entry:SetPlaceholderText('Поиск радиостанций в ' .. country) + for _,v in ipairs(data) do + if v.type == 'place' then + lst:AddPlace(v.name, v.country, v.cnt) + else + lst:AddStation(v.name, v.place, v.country, v.id) + end + end + + octoradio.search.returnBtn:SetVisible(true) + octoradio.search.returnBtn.DoClick = octoradio.search.mainPage + + if #data == 25 then + octoradio.btnNext:SetEnabled(true) + function octoradio.btnNext:DoClick() + lst:Clear() + netstream.Request('octoradio.getByCountry', {country, curOffset + 25}):Then(function(data) + loadPlaces(lst, country, curOffset + 25, data) + end):Catch(print) + end + else octoradio.btnNext:SetEnabled(false) end + + if curOffset > 0 then + octoradio.btnPrev:SetEnabled(true) + function octoradio.btnPrev:DoClick() + lst:Clear() + netstream.Request('octoradio.getByCountry', {country, math.max(curOffset - 25, 0)}):Then(function(data) + loadPlaces(lst, country, math.max(curOffset - 25, 0), data) + end):Catch(print) + end + else octoradio.btnPrev:SetEnabled(false) end + + lst:InvalidateChildren() +end + +local function loadStations(lst, place, country, curOffset, data) + if not IsValid(lst) then return end + + octoradio.search.entry:SetPlaceholderText('Поиск радиостанций в ' .. place .. ', ' .. country) + for _,v in ipairs(data) do + lst:AddStation(v.name, v.place, v.country, v.id) + end + + octoradio.search.returnBtn:SetVisible(true) + octoradio.search.returnBtn.DoClick = function() + lst:Clear() + netstream.Request('octoradio.getByCountry', {country, 0}):Then(function(data) + loadPlaces(lst, country, 0, data) + end):Catch(print) + end + + if #data == 25 then + octoradio.btnNext:SetEnabled(true) + function octoradio.btnNext:DoClick() + lst:Clear() + netstream.Request('octoradio.getByPlace', {place, country, curOffset + 25}):Then(function(data) + loadStations(lst, place, country, curOffset + 25, data) + end):Catch(print) + end + else octoradio.btnNext:SetEnabled(false) end + + if curOffset > 0 then + octoradio.btnPrev:SetEnabled(true) + function octoradio.btnPrev:DoClick() + lst:Clear() + netstream.Request('octoradio.getByPlace', {place, country, math.max(curOffset - 25, 0)}):Then(function(data) + loadStations(lst, place, country, math.max(curOffset - 25, 0), data) + end):Catch(print) + end + else octoradio.btnPrev:SetEnabled(false) end + + lst:InvalidateChildren() +end + +local function rebuildStFrame() + local fr = octolib.overlay(octoradio.pnl, 'DPanel') + fr:SetSize(300, 200) + + local titleCont = fr:Add('DPanel') + titleCont:Dock(TOP) + titleCont:DockMargin(5, 0, 5, 0) + titleCont:SetTall(50) + titleCont:SetPaintBackground(false) + + local playBtn = titleCont:Add('DImageButton') + playBtn:Dock(RIGHT) + playBtn:SetWide(32) + playBtn:DockMargin(5, 9, 0, 9) + + local favBtn = titleCont:Add('DImageButton') + favBtn:Dock(RIGHT) + favBtn:SetWide(32) + favBtn:DockMargin(10, 9, 0, 9) + function favBtn:DoClick() + if not octoradio.id then return end + if not table.HasValue(octoradio.favs, octoradio.id) then + octoradio.favAdd(octoradio.id) + else octoradio.favRemove(octoradio.id) end + self:SetImage('octoteam/icons-32/' .. (table.HasValue(octoradio.favs, octoradio.id) and '' or 'draw_') .. 'star.png') + end + + local titleTextCont = titleCont:Add('DPanel') + titleTextCont:Dock(FILL) + titleTextCont:SetPaintBackground(false) + + local titleLabel = titleTextCont:Add('DLabel') + titleLabel:Dock(TOP) + titleLabel:SetTall(30) + titleLabel:SetContentAlignment(1) + titleLabel:SetFont('f4.normal') + local subtitleCont = titleTextCont:Add('DPanel') + subtitleCont:Dock(FILL) + subtitleCont:SetPaintBackground(false) + subtitleCont:SetContentAlignment(7) + subtitleCont:SetTall(20) + local icon = subtitleCont:Add('DImage') + icon:Dock(LEFT) + icon:SetWide(16) + icon:DockMargin(0, 2, 5, 2) + local subtitleLabel = subtitleCont:Add('DLabel') + subtitleLabel:Dock(FILL) + + local idLbl = octolib.button(fr, 'ID: ######## (нажми, чтобы скопировать)', octolib.func.debounceStart(function(self) + SetClipboardText(octoradio.id or '') + local txt = self:GetText() + self:SetText('Скопировано!') + timer.Simple(2, function() + if IsValid(self) then + self:SetText(txt) + end + end) + end, 2)) + idLbl:DockMargin(5, 5, 5, 5) + + local moreCountry, morePlace + if not octoradio.whitelisted then + + moreCountry = octolib.button(fr, 'Другие радиостанции в стране ', function() + local lst = octoradio.search.lst + if not IsValid(lst) then return end + lst:Clear() + netstream.Request('octoradio.getByCountry', {octoradio.curCountry, 0}):Then(function(data) + fr:Remove() + loadPlaces(lst, octoradio.curCountry, 0, data) + end):Catch(print) + end) + moreCountry:DockMargin(5, 5, 5, 0) + morePlace = octolib.button(fr, 'Другие радиостанции в городе ', function() + local lst = octoradio.search.lst + if not IsValid(lst) then return end + lst:Clear() + netstream.Request('octoradio.getByPlace', {octoradio.curPlace, octoradio.curCountry, 0}):Then(function(data) + fr:Remove() + loadStations(lst, octoradio.curPlace, octoradio.curCountry, 0, data) + end):Catch(print) + end) + morePlace:DockMargin(5, 0, 5, 0) + + else + fr:SetTall(fr:GetTall() - 55) + end + + local dist = octolib.slider(fr, 'Слышимость', 200, 1500, 0) + dist:DockMargin(5, 0, 5, 0) + dist:SetValue(octoradio.dist or 600) + + local parent = octoradio.ent:GetParent() + if IsValid(parent) and parent:IsVehicle() then + local val = dist:GetValue() + dist:SetMinMax(val, val) + dist:SetValue(val) + dist:SetEnabled(false) + else + dist.OnValueChanged = octolib.func.debounce(function(self, val) + val = math.Round(val) + netstream.Start('dbg-radio.soundControl', octoradio.ent, val, octoradio.vol) + octoradio.dist = val + end, 1) + end + + local vol = octolib.slider(fr, 'Громкость', 0, 100, 0) + vol:DockMargin(5, 0, 5, 0) + vol:SetValue(octoradio.vol or 20) + vol.OnValueChanged = octolib.func.debounce(function(self, val) + val = math.Round(val) + netstream.Start('dbg-radio.soundControl', octoradio.ent, octoradio.dist, val) + octoradio.vol = val + end, 1) + + function playBtn:DoClick() + netstream.Start('dbg-radio.toggle', octoradio.ent) + self:SetEnabled(false) + end + + function fr:Update() + icon:SetImage(octoradio.getFlag(octoradio.curCountry)) + favBtn:SetImage('octoteam/icons-32/' .. (table.HasValue(octoradio.favs, octoradio.id) and '' or 'draw_') .. 'star.png') + playBtn:SetImage('octoteam/icons-32/control_' .. (octoradio.ent:GetNetVar('playing') and 'stop' or 'play') .. '_blue.png') + playBtn:SetEnabled(true) + idLbl:SetText('ID: ' .. octoradio.id .. ' (нажми, чтобы скопировать)') + titleLabel:SetText(octoradio.curTitle) + subtitleLabel:SetText(octoradio.curPlace .. ', ' .. octoradio.curCountry) + if moreCountry then + moreCountry:SetText('Другие радиостанции в стране ' .. octoradio.curCountry) + end + if morePlace then + morePlace:SetText('Другие радиостанции в городе ' .. octoradio.curPlace) + end + end + octoradio.curStFrame = fr +end + +local cols = {'yellow', 'red', 'purple', 'pink', 'orange', 'green', 'blue'} +local cMapping = { + ['Åland Islands'] = 'aland_islands', + ['Bailiwick of Guernsey'] = 'guernsey', + ['Bailiwick of Jersey'] = 'jersey', + ['Bosnia and Herzegovina'] = 'bosnia', + ['Cabo Verde'] = 'cape_verde', + ['Collectivity of Saint Martin'] = 'france', + ['Curaçao'] = 'curacao', + ['Czechia'] = 'czech_republic', + ['Côte d\'Ivoire'] = 'cote_divoire', + ['Democratic Republic of the Congo'] = 'congo_democratic_republic', + ['Ecuador'] = 'equador', + ['Federated States of Micronesia'] = 'micronesia', + ['French Guiana'] = 'france', + ['Guadeloupe'] = 'france', + ['Guiné-Bissau'] = 'guinea_bissau', + ['Maldives'] = 'maledives', + ['Mayotte'] = 'france', + ['Myanmar (Burma)'] = 'burma', + ['New Calédonia'] = 'france', + ['North Macedonia'] = 'macedonia', + ['Paraguay'] = 'paraquay', + ['Republic of North Macedonia'] = 'macedonia', + ['Republic of the Congo'] = 'congo_republic', + ['Réunion'] = 'france', + ['Saint Barthélemy'] = 'france', + ['Saint Vincent and the Grenadines'] = 'saint_vincent_and_grenadines', + ['Saint-Pierre et Miquelon'] = 'saint_pierre_and_miquelon', + ['Serbia'] = 'serbia_montenegro', + ['Tahiti'] = 'french_polynesia', + ['The Bahamas'] = 'bahamas', + ['The Gambia'] = 'gambia', + ['U.S. Virgin Islands'] = 'virgin_islands', + ['United Kingdom'] = 'great_britain', + ['United States'] = 'usa', + ['Uruguay'] = 'uruquay', +} +local flagFormat = '%sflag_%s.png' +function octoradio.getFlag(country, prefix) + prefix = prefix or 'octoteam/icons-16/' + local iconPath = flagFormat:format(prefix, string.lower(country):gsub(' ', '_')) + if not file.Exists('materials/' .. iconPath, 'GAME') then + return flagFormat:format(prefix, cMapping[country] or ('flyaway_' .. cols[math.random(#cols)])) + else return iconPath end +end + +function octoradio.createList(pan) + local lst = pan:Add('DListView') + lst:Dock(FILL) + lst:SetDataHeight(16) + lst:AddColumn('icon'):SetFixedWidth(16) + lst:AddColumn('name') + lst:AddColumn('cnt'):SetFixedWidth(40) + lst:SetHideHeaders(true) + lst:SetMultiSelect(false) + + local function addEntry(country, title, cnt, onopen) + local icon = vgui.Create('DImage') + icon:SetImage(octoradio.getFlag(country)) + local line = lst:AddLine(icon, title, cnt or '') + line.onOpen = onopen + return line + end + + function lst:AddCountry(name, stCount) + return addEntry(name, name, stCount, function() + self:Clear() + octoradio.search.country, octoradio.search.place = name + netstream.Request('octoradio.getByCountry', {name, 0}):Then(function(data) + loadPlaces(self, name, 0, data) + end):Catch(print) + end) + end + + function lst:AddPlace(name, country, stCount) + return addEntry(country, name .. ', ' .. country, stCount, function() + self:Clear() + octoradio.search.place, octoradio.search.country = name, country + netstream.Request('octoradio.getByPlace', {name, country, 0}):Then(function(data) + loadStations(self, name, country, 0, data) + end) + end) + end + + function lst:AddStation(title, place, country, id) + local line = addEntry(country, title, nil, function() + netstream.Start('dbg-radio.control', octoradio.ent, id) + end) + line.id = id + return line + end + + function lst:DoDoubleClick(_, line) + if line.onOpen then line.onOpen() end + end + + function lst:OnRowRightClick(_, line) + if not line.id then return end + local menu = DermaMenu() + local fav = table.HasValue(octoradio.favs, line.id) + menu:AddOption(fav and 'Удалить из избранного' or 'Добавить в избранное', function() + if fav then octoradio.favRemove(line.id) else octoradio.favAdd(line.id) end + end):SetIcon('octoteam/icons-16/' .. (fav and 'draw_' or '') .. 'star.png') + if octoradio.id ~= line.id then + menu:AddOption('Переключиться', function() + netstream.Start('dbg-radio.control', octoradio.ent, line.id) + end):SetIcon('octoteam/icons-16/control_play_blue.png') + end + menu:AddOption('Скопировать ID', function() + SetClipboardText(line.id) + end):SetIcon('octoteam/icons-16/page_white_copy.png') + menu:Open() + end + + return lst +end + +function octoradio.displayCurStation() + if IsValid(octoradio.curStFrame) then + octoradio.curStFrame:Remove() + end + rebuildStFrame() + octoradio.curStFrame:Update() +end diff --git a/garrysmod/addons/feature-radio/lua/autorun/server/octo_radio.lua b/garrysmod/addons/feature-radio/lua/autorun/server/octo_radio.lua new file mode 100644 index 0000000..4f1ce1d --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/autorun/server/octo_radio.lua @@ -0,0 +1,392 @@ +octoradio = octoradio or {} + +local api = octolib.api({ + url = 'http://radio.garden/api/ara/content', + headers = {}, +}) + +local updater, q = {}, {} +local refreshPeriod = 24 * 60 * 60 + +hook.Add('octolib.db.init', 'octoradio', function() + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS `dbg_radio` ( + `id` VARCHAR(10) NOT NULL, + `title` TINYTEXT NOT NULL, + `placeId` VARCHAR(10) NOT NULL, + `placeName` TINYTEXT NOT NULL, + `country` TINYTEXT NOT NULL, + `playbackUrl` TINYTEXT NOT NULL, + PRIMARY KEY (`id`) + ) + ]], updater.loadAll) +end) + +function updater.loadAll() + octolib.getDBVar('octoradio', 'updData', {}):Then(function(updData) + + timer.Simple(3, updater.checkUpdating) + local start = os.time() + if start - (updData.last or 0) <= refreshPeriod then return end + octolib.msg('Radio: Begin updating stations') + updData.last, updData.updating = start, true + octolib.setDBVar('octoradio', 'updData', updData) + + api:get('/places'):Then(function(res) + + if res and res.data and res.data.data and res.data.data.list then + local places = res.data.data.list + local nxt, sz, nextPercent = 1, #places, 0 + + octolib.func.loop(function(again) + if not places[nxt] then + octolib.msg('Radio: Saving...') + updater.save():Then(function() + octolib.msg('Radio: Done! (%ss)', os.time() - start) + updData.updating = false + octolib.setDBVar('octoradio', 'updData', updData) + end):Catch(print) + return + end + nxt = nxt + 1 + + local percent = nxt / sz + if percent >= nextPercent then + octolib.msg('Radio: Updating stations progress %s%%', math.floor(percent * 100)) + nextPercent = math.floor(percent * 10 + 1) / 10 + end + + updater.loadPlace(places[nxt-1].id, places[nxt-1].title, places[nxt-1].country):Then(again):Catch(print) + end) + + else + octolib.msg('Radio: Couldn\'t load radio stations!') + end + + end) + + end) +end + +function updater.checkUpdating() + octolib.func.loop(function(again) + octolib.getDBVar('octoradio', 'updData', {}):Then(function(data) + if data.updating then timer.Simple(3, again) + else hook.Run('octoradio.updated') end + end) + end) +end + +function updater.loadPlace(placeId, placeName, country) + return util.Promise(function(resolve) + api:get('/page/' .. placeId .. '/channels'):Then(function(res) + if res and res.data and res.data.data and res.data.data.content and res.data.data.content[1] and res.data.data.content[1].items then + local stations = res.data.data.content[1].items + local nxt = 1 + octolib.func.loop(function(again) + if not stations[nxt] then resolve() return end + local st = stations[nxt] + local id = st.href:gsub('.+%/', '') + if id ~= '' then + updater.queueStation(id, st.title, placeId, placeName, country, 'http://radio.garden/api/ara/content/listen/' .. id .. '/channel.mp3') + end + nxt = nxt + 1 + again() + end) + else + octolib.msg('Radio: No stations found for %s, %s', placeName, country) + resolve() + end + end):Catch(resolve) + end) +end + +function updater.queueStation(...) + local data = octolib.table.map({...}, function(v) + return octolib.db:escape(v) + end) + q[#q + 1] = ('(\'%s\',\'%s\',\'%s\',\'%s\',\'%s\',\'%s\')'):format(unpack(data)) +end + +function updater.save() + return util.Promise(function(resolve, rej) + octolib.db:RunQuery([[DELETE FROM `dbg_radio` WHERE `id` NOT LIKE '##%']], function() + local qSize = #q + local iMax = math.ceil(qSize / 10000) + for i = 1, iMax do + local values = table.concat(q, ',', (i-1) * 10000 + 1, math.min(i * 10000, qSize)) + local query = [[REPLACE INTO `dbg_radio` (`id`, `title`, `placeId`, `placeName`, `country`, `playbackUrl`) VALUES ]] .. values + if i == iMax then + octolib.db:RunQuery(query, function() resolve() end) + else + octolib.db:RunQuery(query) + end + end + end) + end) +end + +octoradio.countries = octoradio.countries or {} +octoradio.places = octoradio.places or {} + +hook.Add('octoradio.updated', 'octoradio.getStats', function() + octolib.db:RunQuery([[SELECT `placeName` AS `place`, country, COUNT(`id`) AS `cnt` FROM `dbg_radio` GROUP BY `placeId`, `placeName`, `country`]], function(q, st, res) + if not istable(res) then return end + octoradio.countries, octoradio.places = {}, {} + for _,v in ipairs(res) do + local country = v.country + octoradio.countries[country] = (octoradio.countries[country] or 0) + v.cnt + local data = octoradio.places[country] or {} + data[#data + 1] = {place = v.place, cnt = v.cnt} + octoradio.places[country] = data + end + for _,v in pairs(octoradio.places) do + table.SortByMember(v, 'cnt', false) + end + netvars.SetNetVar('octoradio.countries', octoradio.countries) + end) +end) + +-- methods +function octoradio.getById(ids, done) + if #ids == 0 then return done(ids) end + local limit = math.min(25, #ids) + local str = '\'' .. octolib.db:escape(ids[1]) + for i = 2, limit do + str = str .. '\',\'' .. octolib.db:escape(ids[i]) + end + str = str .. '\'' + octolib.db:RunQuery([[ + SELECT * FROM `dbg_radio` WHERE `id` IN (]] .. str .. [[) LIMIT ]] .. limit + , function(q, st, res) + done(res or {}) + end) +end + +function octoradio.search(query, place, country, offset, done) + offset = math.max(tonumber(offset) or 0, 0) + query = octolib.db:escape(utf8.sub(string.Trim(query), 1, 128)) + local q = [[SELECT * FROM `dbg_radio` WHERE (`id`='%s' OR `title` LIKE '%%%s%%')]] + if country then + q = q .. [[ AND `country`=']] .. octolib.db:escape(country) .. '\'' + end + if place then + q = q .. [[ AND `placeName`=']] .. octolib.db:escape(place) .. '\'' + end + q = q .. [[ LIMIT 25 OFFSET ]] .. offset + octolib.db:PrepareQuery(string.format(q, query, query), {offset or 0}, function(q, st, res) + done(res) + end) +end + +function octoradio.getByCountry(country, offset, done) + local ans, limit = {}, 25 + offset = tonumber(offset) or 0 + if octoradio.places[country] and octoradio.places[country][1 + offset] then -- include places into search results + local to = math.min(offset + limit, #octoradio.places[country]) + for i = 1 + offset, to do + offset, limit = offset - 1, limit - 1 + local v = octoradio.places[country][i] + ans[#ans + 1] = { + type = 'place', + name = v.place, + country = country, + cnt = v.cnt, + } + end + end + if limit <= 0 then return done(ans) end + offset = math.max(offset, 0) + octolib.db:PrepareQuery([[ + SELECT * FROM `dbg_radio` WHERE `country`=? LIMIT ]] .. limit .. [[ OFFSET ]] .. offset + , {country}, function(q, st, res) + if istable(res) then + for _,v in ipairs(res) do + ans[#ans + 1] = { + type = 'station', + id = v.id, + name = v.title, + place = v.placeName, + country = v.country, + } + end + end + done(ans) + end) +end + +function octoradio.getByPlace(place, country, offset, done) + offset = math.max(tonumber(offset) or 0, 0) + octolib.db:PrepareQuery([[ + SELECT * FROM `dbg_radio` WHERE `placeName`=? AND `country`=? LIMIT 25 OFFSET ]] .. offset, {place, country}, function(q, st, res) + local ans = {} + res = istable(res) and res + if not istable(res) then return done({}) end + for _,v in ipairs(res) do + ans[#ans + 1] = { + type = 'station', + id = v.id, + name = v.title, + place = v.placeName, + country = v.country, + } + end + done(ans) + end) +end + +function octoradio.top10() + local count, data = {}, {} + for _,v in ipairs(ents.FindByClass('ent_dbg_radio')) do + if not v.curID then continue end + count[v.curID] = (count[v.curID] or 0) + 1 + data[v.curID] = data[v.curID] or { + id = v.curID, + title = v.curTitle, + place = v.curPlace, + country = v.curCountry, + } + end + local ans = {} + for k,v in SortedPairsByValue(count, true) do + data[k].score = v + ans[#ans + 1] = data[k] + if #ans >= 10 then break end + end + return ans +end + +netstream.Listen('octoradio.getById', function(reply, _, ids) + if istable(ids) then octoradio.getById(ids, reply) end +end) + +netstream.Listen('octoradio.getByCountry', function(reply, _, data) + if not istable(data) or not isstring(data[1]) or (data[2] and not isnumber(data[2])) then return end + octoradio.getByCountry(data[1], data[2], reply) +end) + +netstream.Listen('octoradio.top10', function(reply) + reply(octoradio.top10()) +end) + +netstream.Listen('octoradio.getByPlace', function(reply, _, data) + if not istable(data) or not isstring(data[1]) or not isstring(data[2]) or (data[3] and not isnumber(data[3])) then return end + octoradio.getByPlace(data[1], data[2], data[3], reply) +end) + +netstream.Listen('octoradio.search', function(reply, _, data) + if not istable(data) + or not isstring(data[1]) + or (data[2] and not isstring(data[2])) + or (data[3] and not isstring(data[3])) + or (data[4] and not isnumber(data[4])) + then return end + octoradio.search(data[1], data[2], data[3], data[4], reply) +end) + +netstream.Listen('octoradio.getStations', function(reply, _, ent, page) + + if not (IsValid(ent) and ent:GetClass() == 'ent_dbg_radio' and ent.whitelisted) then return end + if not (isnumber(page) and octolib.math.inRange(page, 1, math.ceil(#ent.stationsWhitelist / 25))) then return end + print(ent, page) + + octoradio.getById(octolib.array.page(ent.stationsWhitelist, 25, page), function(stations) + reply(octolib.table.mapSequential(stations, function(st) + local allowed, mods = ent:GetStationModifies(st.id) + if not allowed then return nil end + if istable(mods) then return table.Merge(st, mods) end + return st + end)) + end) + +end) + +-- +-- GAME STUFF +-- + +local function updateCloseLook(ent, name, desc) + ent:SetNetVar('dbgLook', { + name = name or '', + desc = desc or '', + time = 1, + }) +end + +local function updateRadio(ent) + netstream.Start(nil, 'dbg-radio.curStUpdate', ent, ent.curID, ent.curTitle, ent.curPlace, ent.curCountry) +end + +netstream.Hook('dbg-radio.toggle', function(ply, ent) + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_radio' then return end + local plyPos = ply:GetShootPos() + local eee = IsValid(ent:GetParent()) and ent:GetParent() or ent + if plyPos:DistToSqr(eee:NearestPoint(plyPos)) > CFG.useDistSqr then return end + if not ent.curURL then return end + + if not ent:GetNetVar('playing') then + updateCloseLook(ent, ent.curTitle, ent.curID) + ent:SetStreamURL(ent.curURL) + else + updateCloseLook(ent) + ent:SetStreamURL() + end + ent:SetNetVar('playing', not ent:GetNetVar('playing') or nil) + updateRadio(ent) +end) + +netstream.Hook('dbg-radio.soundControl', function(ply, ent, dist, vol) + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_radio' then return end + local plyPos = ply:GetShootPos() + local eee = IsValid(ent:GetParent()) and ent:GetParent() or ent + if plyPos:DistToSqr(eee:NearestPoint(plyPos)) > CFG.useDistSqr then return end + + ent:SetVolume(vol) + ent:SetDistance(ent.carRadio and 600 or dist) + + updateRadio(ent) +end) + +netstream.Hook('dbg-radio.control', function(ply, ent, id) + if not IsValid(ent) or ent:GetClass() ~= 'ent_dbg_radio' then return end + local plyPos = ply:GetShootPos() + local eee = IsValid(ent:GetParent()) and ent:GetParent() or ent + if plyPos:DistToSqr(eee:NearestPoint(plyPos)) > CFG.useDistSqr then return end + local allowed, mods = ent:GetStationModifies(id) + if not allowed then return end + + updateCloseLook(ent) + if not id or string.Trim(id) == '' then + ent:SetStreamURL() + ent:SetNetVar('playing', false) + updateRadio(ent) + return + end + + octoradio.getById({id}, function(ans) + ans = istable(ans) and ans[1] + if not istable(ans) or not IsValid(ent) then return end + if mods then ans = table.Merge(ans, mods) end + ent:SetStreamURL(ans.playbackUrl) + ent.curURL = ans.playbackUrl + ent.curID = ans.id + ent.curTitle, ent.curPlace, ent.curCountry = ans.title, ans.placeName, ans.country + updateCloseLook(ent, ans.title or '', ans.id or '') + ent:SetNetVar('playing', true) + updateRadio(ent) + end) +end) + +netstream.Hook('dbg-radio.openCar', function(ply) + + local seat = ply:GetVehicle() + local car = IsValid(seat) and seat:GetParent() + if not IsValid(car) or not car.GetDriverSeat then return end + if car:GetDriverSeat() ~= seat and not simfphys.GetSeatProperty(seat, 'hasRadio') then return end + + local r = car.Radio + if IsValid(r) then + netstream.Start(ply, 'dbg-radio.control', r, r.whitelisted, r.curID, r.curTitle, r.curPlace, r.curCountry) + end + +end) diff --git a/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/cl_init.lua b/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/cl_init.lua new file mode 100644 index 0000000..d14ec89 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/cl_init.lua @@ -0,0 +1,39 @@ +include('shared.lua') + +function ENT:Initialize() + self.stream = octolib.audio.stream() + self.stream:SetParent(self) +end + +function ENT:Draw() + self:DrawModel() +end + +function ENT:SetVolume(vol) + self.stream:SetVolume(vol) +end + +function ENT:SetDistance(dist) + self.stream:SetDistance(dist) +end + +function ENT:StopStream() + self.stream:Stop() +end + +function ENT:StartStream(url) + self.stream:SetURL(url) +end + +function ENT:Set2D(enable) + if enable then + self.stream:SetParent() + self.stream:SetPos() + else + self.stream:SetParent(self) + end +end + +function ENT:OnRemove() + self.stream:Remove() +end diff --git a/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/init.lua b/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/init.lua new file mode 100644 index 0000000..0ae8513 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/init.lua @@ -0,0 +1,87 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +function ENT:Initialize() + + self:SetModel(self.Model) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.whitelisted = false + +end + +function ENT:UpdateDuplicatorData() + self.radioData = { + whitelisted = self.whitelisted, + allowedStations = self.allowedStations, + stationsWhitelist = self.stationsWhitelist, + volume = self:GetVolume(), + dist = self:GetDistance(), + stream = self:GetStreamURL(), + } +end + +function ENT:SetVolume(vol) + vol = vol or 0.2 + if vol > 1 then vol = vol / 100 end + self:SetNetVar('volume', math.Clamp(vol or 0.2, 0, 1)) + self:UpdateDuplicatorData() +end + +function ENT:SetDistance(dist) + dist = math.Clamp(dist or 600, 200, 1500) + self:SetNetVar('distSqr', dist * dist) + self:SetNetVar('dist', dist) + self:UpdateDuplicatorData() +end + +function ENT:SetStreamURL(url) + if url == '' then url = nil end + self:SetNetVar('stream', url) + self:UpdateDuplicatorData() +end + +function ENT:SetStationsWhitelist(whitelist) + if whitelist and not istable(whitelist) then return end + if not whitelist then + self.whitelisted, self.allowedStations, self.stationsWhitelist = false + self:UpdateDuplicatorData() + return + end + self.whitelisted, self.allowedStations, self.stationsWhitelist = true, {}, {} + for _, v in ipairs(whitelist) do + self.allowedStations[v.id] = v + self.stationsWhitelist[#self.stationsWhitelist + 1] = v.id + end + self:UpdateDuplicatorData() +end + +-- returns bool (is station allowed to listen to) and table (overrides of station properties, optional) +function ENT:GetStationModifies(id) + if not self.allowedStations then return true end + if not self.allowedStations[id] then return false end + return true, self.allowedStations[id] +end + +duplicator.RegisterEntityClass('ent_dbg_radio', function(ply, data) + + if IsValid(ply) and not ply:IsAdmin() then return false end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + data = data.radioData or {} + ent.whitelisted = data.whitelisted + ent.allowedStations = data.allowedStations + ent.stationsWhitelist = data.stationsWhitelist + ent:SetVolume(data.volume) + ent:SetDistance(data.dist) + ent:SetStreamURL(data.stream) + ent.duplicated = true + ent:GetPhysicsObject():EnableMotion(false) + ent.static = true + ent.pinned = true + + return ent + +end, 'Data', 'radioData') diff --git a/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/shared.lua b/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/shared.lua new file mode 100644 index 0000000..f8bb0e6 --- /dev/null +++ b/garrysmod/addons/feature-radio/lua/entities/ent_dbg_radio/shared.lua @@ -0,0 +1,21 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'Радио' +ENT.Category = L.dobrograd +ENT.Author = 'Wani4ka' +ENT.Contact = '4wk@wani4ka.ru' +ENT.Spawnable = true + +ENT.Model = 'models/props/cs_office/radio.mdl' + +function ENT:GetVolume() + return self:GetNetVar('volume', 0.2) +end + +function ENT:GetDistance() + return self:GetNetVar('dist', 600) +end + +function ENT:GetStreamURL() + return self:GetNetVar('stream') +end diff --git a/garrysmod/addons/feature-sit/lua/autorun/client/sit.lua b/garrysmod/addons/feature-sit/lua/autorun/client/sit.lua new file mode 100644 index 0000000..f4cd644 --- /dev/null +++ b/garrysmod/addons/feature-sit/lua/autorun/client/sit.lua @@ -0,0 +1 @@ +CreateClientConVar("sitting_disallow_on_me","1",true,true) diff --git a/garrysmod/addons/feature-sit/lua/autorun/server/sit.lua b/garrysmod/addons/feature-sit/lua/autorun/server/sit.lua new file mode 100644 index 0000000..6077fcf --- /dev/null +++ b/garrysmod/addons/feature-sit/lua/autorun/server/sit.lua @@ -0,0 +1,675 @@ +--Oh my god I can sit anywhere! by Xerasin-- +local NextUse = setmetatable({},{__mode='k'}) +--[[local SitOnEnts = CreateConVar("sitting_can_sit_on_ents","1",{FCVAR_NOTIFY}) +local PlayerEnts = CreateConVar("sitting_can_sit_on_player_ents","1",{FCVAR_NOTIFY}) +local PlayerOtherEnts = CreateConVar("sitting_can_sit_on_other_player_ents","1",{FCVAR_NOTIFY})]] +local SitOnEntsMode = CreateConVar("sitting_ent_mode","3", {FCVAR_NOTIFY}) +--[[ + 0 - Can't sit on any ents + 1 - Can't sit on any player ents + 2 - Can only sit on your own ents + 3 - Any +]] +local SittingOnPlayer = CreateConVar("sitting_can_sit_on_players","1",{FCVAR_NOTIFY}) +local SittingOnPlayer2 = CreateConVar("sitting_can_sit_on_player_ent","1",{FCVAR_NOTIFY}) +local PlayerDamageOnSeats = CreateConVar("sitting_can_damage_players_sitting","0",{FCVAR_NOTIFY}) +local AllowWeaponsInSeat = CreateConVar("sitting_allow_weapons_in_seat","0",{FCVAR_NOTIFY}) +local AdminOnly = CreateConVar("sitting_admin_only","0",{FCVAR_NOTIFY}) +local META = FindMetaTable("Player") +local EMETA = FindMetaTable("Entity") + +local function ShouldAlwaysSit(ply) + if not ms then return end + if not ms.GetTheaterPlayers then return end + if not ms.GetTheaterPlayers() then return end + return ms.GetTheaterPlayers()[ply] +end + + +local disabledClasses = octolib.array.toKeys({'prop_ragdoll', 'player'}) +local forcedClasses = octolib.array.toKeys({'prop_dynamic'}) + +local sittingPlayers = {} + +local function Sit(ply, pos, ang, parent, parentbone, func, exit) + if hook.Run('dbg-sit.allow', ply, parent, pos, ang) == false then return end + if IsValid(parent) and not forcedClasses[parent:GetClass()] and IsValid(parent:GetPhysicsObject()) and parent:GetPhysicsObject():IsMotionEnabled() then return end + if ply:GetVelocity():Length() > 0 or ply:KeyDown( IN_DUCK ) or + ply:KeyDown( IN_WALK ) or !ply:IsOnGround() then return end + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == "weapon_physgun" then return end + + local up = ply:GetAngles():Up() + local tr = util.QuickTrace( pos + up*20, up*37, ply ) + if !tr.Hit then + ply:ExitVehicle() + local vehicle = ents.Create("prop_vehicle_prisoner_pod") + vehicle:SetAngles(ang) + pos = pos + vehicle:GetUp()*(10 + 8 * ply:GetModelScale()) + vehicle:SetPos(pos) + vehicle:SetThirdPersonMode( true ) + + vehicle.playerdynseat=true + vehicle.oldpos = ply:GetPos() + vehicle.oldang = ply:EyeAngles() + + vehicle:SetModel("models/nova/airboat_seat.mdl") -- DO NOT CHANGE OR CRASHES WILL HAPPEN + + vehicle:SetKeyValue("vehiclescript", "scripts/vehicles/prisoner_pod.txt") + vehicle:SetKeyValue("limitview","0") + vehicle:Spawn() + vehicle:Activate() + + -- Let's try not to crash + vehicle:SetMoveType(MOVETYPE_PUSH) + vehicle:GetPhysicsObject():Sleep() + vehicle:SetCollisionGroup(COLLISION_GROUP_NONE) + + vehicle:SetNotSolid(true) + vehicle:GetPhysicsObject():Sleep() + vehicle:GetPhysicsObject():EnableGravity(false) + vehicle:GetPhysicsObject():EnableMotion(false) + vehicle:GetPhysicsObject():EnableCollisions(false) + vehicle:GetPhysicsObject():SetMass(1) + + -- Visibles + vehicle:DrawShadow(false) + vehicle:SetColor(Color(0,0,0,0)) + vehicle:SetRenderMode(RENDERMODE_TRANSALPHA) + vehicle:SetNoDraw(true) + + vehicle.VehicleName = "Airboat Seat" + vehicle.ClassOverride = "prop_vehicle_prisoner_pod" + + if parent and parent:IsValid() then + local r = math.rad(ang.yaw+90) + vehicle.plyposhack = vehicle:WorldToLocal(pos + Vector(math.cos(r)*2,math.sin(r)*2,2)) + + vehicle:SetParent(parent) + vehicle.parent=parent + + else + vehicle.OnWorld = true + end + + ply:SetAllowWeaponsInVehicle(true) + + ply:EnterVehicle(vehicle) + sittingPlayers[#sittingPlayers + 1] = ply + + if PlayerDamageOnSeats:GetBool() then + ply:SetCollisionGroup(COLLISION_GROUP_WEAPON) + end + --print("VEHICLE",vehicle,"<-",ply,"PARENT:",vehicle:GetParent(),"D:",vehicle:GetPos():Distance(ply:GetPos())) + + vehicle.removeonexit = true + vehicle.exit = exit + --print("enter vehicle",ply,vehicle) + + local ang = vehicle:GetAngles() + ply:SetEyeAngles(Angle(0,90,0)) + if func then + func(ply) + end + + -- used in hook + if parent then + vehicle.parent.exitOnUnfroze = true + vehicle.parent.seats = vehicle.parent.seats or {} + table.insert( vehicle.parent.seats, vehicle ) + end + + vehicle:SetNetVar( "saw", true ) + + return vehicle + else + return nil + end +end + +hook.Add( "CanExitVehicle", "CustomExit",function( veh, ply ) + if veh:GetNetVar( "saw" ) then return false end +end) +hook.Add( "Think", "CustomExit", function() + local toRem = {} + for i = #sittingPlayers, 1, -1 do + local ply = sittingPlayers[i] + if not IsValid(ply) or not IsValid(ply:GetVehicle()) or not ply:GetVehicle():GetNetVar('saw') then + toRem[#toRem + 1] = ply + table.remove(sittingPlayers, i) + continue + end + + local veh = ply:GetVehicle() + if ply:KeyDown(IN_JUMP) or veh.prevPos and veh.prevPos ~= veh:GetPos() then + veh.oldang = ply:EyeAngles() + toRem[#toRem + 1] = ply + table.remove(sittingPlayers, i) + continue + else veh.prevPos = veh:GetPos() end + + if ply:KeyDown( IN_USE ) then + if ply.sitUsed then return end + + local tr = octolib.use.getTrace(ply) + if tr.Hit and IsValid(tr.Entity) and hook.Run('PlayerUse', ply, tr.Entity) ~= false then + tr.Entity:Use(ply, ply, 1, 0) + end + + ply.sitUsed = true + else + ply.sitUsed = false + end + end + for _, v in ipairs(toRem) do + if IsValid(v) and IsValid(v:GetVehicle()) and v:GetVehicle():GetNetVar('saw') then + v:ExitVehicle() + end + end +end) + +local function dropDriver( ent ) + for i,seat in ipairs( ent.seats ) do + if IsValid(seat) and IsValid(seat:GetDriver()) then + seat:GetDriver():ExitVehicle() + end + end + + table.remove( ent.seats, i ) +end + +hook.Add( "PhysgunPickup", "DisableSitOnPickup", function( ply, ent ) + if ent.exitOnUnfroze then dropDriver( ent ) end +end) + +hook.Add( "OnPhysgunPickup", "DisableSitOnPickup", function( ply, ent ) + if ent.exitOnUnfroze then dropDriver( ent ) end +end) + +hook.Add( "PlayerUnfrozeObject", "ExitOnUnfroze", function( ply, ent, physobj ) + if ent.exitOnUnfroze then dropDriver( ent ) end +end) + +--[[ +-- WHY DOES THIS NOT CRAHSH -- +local vehicle = ents.Create("prop_vehicle_prisoner_pod") + vehicle:SetPos(here) + vehicle:SetModel("models/nova/airboat_seat.mdl") -- BECAUSE SIMPLE COLLISION MODEL D: + vehicle:SetKeyValue("vehiclescript", "scripts/vehicles/prisoner_pod.txt") + vehicle:Spawn() + vehicle:Activate() + vehicle:SetMoveType(MOVETYPE_PUSH) + vehicle:Phys():Sleep() + vehicle:SetCollisionGroup(COLLISION_GROUP_NONE) + vehicle:SetNotSolid(true) + + vehicle:Phys():EnableGravity(false) + vehicle:Phys():EnableMotion(false) + vehicle:Phys():EnableCollisions(false) + vehicle:Phys():SetMass(1) +local this=this + vehicle:SetParent(this) + --vehicle:SetLocalPos(Vector(0,0,20)) + me:EnterVehicle(vehicle)--]] + +local d=function(a,b) return math.abs(a-b) end + +local SittingOnPlayerPoses = +{ + + { + Pos = Vector(-33,13,7), + Ang = Angle(0,90,90), + FindAng = 90, + }, + { + Pos = Vector(33,13,7), + Ang = Angle(0,270,90), + Func = function(ply) + if(not ply:LookupBone("ValveBiped.Bip01_R_Thigh")) then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,90,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,90,0)) + end, + OnExitFunc = function(ply) + if(not ply:LookupBone("ValveBiped.Bip01_R_Thigh")) then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,0,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,0,0)) + end, + FindAng = 270, + }, + { + Pos = Vector(0, 16, -15), + Ang = Angle(0, 180, 0), + Func = function(ply) + if(not ply:LookupBone("ValveBiped.Bip01_R_Thigh")) then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(45,0,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(-45,0,0)) + end, + OnExitFunc = function(ply) + if(not ply:LookupBone("ValveBiped.Bip01_R_Thigh")) then return end + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,0,0)) + ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,0,0)) + end, + FindAng = 0, + }, + { + Pos = Vector(0, 8, -18), + Ang = Angle(0, 0, 0), + FindAng = 180, + }, + +} + +local lookup={} +for k,v in pairs(SittingOnPlayerPoses) do + table.insert(lookup,{v.FindAng,v}) + table.insert(lookup,{v.FindAng+360,v}) + table.insert(lookup,{v.FindAng-360,v}) +end + +local function FindPose(this,me) + local avec=me:GetAimVector() + avec.z=0 + avec:Normalize() + local evec=this:GetRight() + evec.z=0 + evec:Normalize() + local derp=avec:Dot(evec) + + local avec=me:GetAimVector() + avec.z=0 + avec:Normalize() + local evec=this:GetForward() + evec.z=0 + evec:Normalize() + local herp=avec:Dot(evec) + local v=Vector(derp,herp,0) + local a=v:Angle() + + local ang=a.y + assert(ang>=0) + assert(ang<=360) + ang=ang+90+180 + ang=ang%360 + + table.sort(lookup,function(aa,bb) + return d(ang,aa[1]) 100 then return end + local sitting_disallow_on_me = false + if SittingOnPlayer:GetBool() then + for k,v in pairs(ents.FindInSphere(EyeTrace.HitPos, 5)) do + local safe=256 -- maxplayers engine supports anyway + while IsValid(v.SittingOnMe) and safe>0 do + safe=safe - 1 + v=v.SittingOnMe + end + if(v:GetClass() == "prop_vehicle_prisoner_pod" + and v:GetModel() ~= "models/vehicles/prisoner_pod_inner.mdl" + and v:GetDriver() + and v:GetDriver():IsValid() + and not v.PlayerSitOnPlayer + ) then + if v:GetDriver():GetInfoNum("sitting_disallow_on_me",0)~=0 then + return + end + + if sitting_disallow_on_me then + ply:ChatPrint(L.cant_sit_on_players) + return + end + + local pose = FindPose(v,ply) -- SittingOnPlayerPoses[math.random(1, #SittingOnPlayerPoses)] + local pos = v:GetDriver():GetPos() + if(v.plyposhack) then + pos = v:LocalToWorld(v.plyposhack) + end + local vec,ang = LocalToWorld(pose.Pos, pose.Ang, pos, v:GetAngles()) + if v:GetParent() == ply then return end + local ent = Sit(ply, vec, ang, v, 0, pose.Func, pose.OnExitFunc) + --print("sit",ply,ent,v:GetDriver(),v) + if IsValid(ent) then + ent.PlayerOnPlayer = true + v.SittingOnMe = ent + end + return ent + end + end + else + for k,v in pairs(ents.FindInSphere(EyeTrace.HitPos, 5)) do + if(v.removeonexit) then + return + end + end + end + + if(not EyeTrace.HitWorld and SitOnEntsMode:GetInt() == 0) then return end + if(not EyeTrace.HitWorld and blacklist[string.lower(EyeTrace.Entity:GetClass())]) then return end + if(not EyeTrace.HitWorld and EyeTrace.Entity:GetModel() and model_blacklist[string.lower(EyeTrace.Entity:GetModel())]) then return end + if(EMETA.CPPIGetOwner) then + --print(SitOnEntsMode:GetInt()) + if(SitOnEntsMode:GetInt() >= 1) then + if(SitOnEntsMode:GetInt() == 1) then + if(not EyeTrace.HitWorld) then + local owner = EyeTrace.Entity:CPPIGetOwner() + if(owner ~= nil and owner:IsValid() and owner:IsPlayer()) then + return + end + end + end + if(SitOnEntsMode:GetInt() == 2) then + if(not EyeTrace.HitWorld) then + local owner = EyeTrace.Entity:CPPIGetOwner() + if(owner ~= nil and owner:IsValid() and owner:IsPlayer() and owner ~= ply) then + return + end + end + end + end + end + local ang = EyeTrace.HitNormal:Angle() + Angle(-270, 0, 0) + if(math.abs(ang.pitch) <= 15) then + local ang = Angle() + local filter = player.GetAll() + local dists = {} + local distsang = {} + local ang_smallest_hori = nil + local smallest_hori = 90000 + for I=0,360,15 do + local rad = math.rad(I) + local dir = Vector(math.cos(rad), math.sin(rad), 0) + local trace = util.QuickTrace(EyeTrace.HitPos + dir*20 + Vector(0,0,5), Vector(0,0,-15000), filter) + trace.HorizontalTrace = util.QuickTrace(EyeTrace.HitPos + Vector(0,0,5), (dir) * 1000, filter) + trace.Distance = trace.StartPos:Distance(trace.HitPos) + trace.Distance2 = trace.HorizontalTrace.StartPos:Distance(trace.HorizontalTrace.HitPos) + trace.ang = I + + if((not trace.Hit or trace.Distance > 14) and (not trace.HorizontalTrace.Hit or trace.Distance2 > 20)) then + table.insert(dists,trace) + + end + if(trace.Distance2 < smallest_hori and (not trace.HorizontalTrace.Hit or trace.Distance2 > 3)) then + smallest_hori = trace.Distance2 + ang_smallest_hori = I + end + distsang[I] = trace + end + local infront = ((ang_smallest_hori or 0) + 180) % 360 + + if(ang_smallest_hori and distsang[infront].Hit and distsang[infront].Distance > 14 and smallest_hori <= 16) then + local hori = distsang[ang_smallest_hori].HorizontalTrace + ang.yaw = (hori.HitNormal:Angle().yaw - 90) + local ent = nil + if not EyeTrace.HitWorld then + ent = EyeTrace.Entity + if ent:IsPlayer() and not SittingOnPlayer2:GetBool() then return end + + if ent:IsPlayer() and ent:GetInfoNum("sitting_disallow_on_me",0)==1 then + return + end + if sitting_disallow_on_me then + ply:ChatPrint(L.cant_sit_on_players) + return + end + end + local vehicle = Sit(ply, EyeTrace.HitPos-Vector(0,0,20), ang, ent, EyeTrace.PhysicsBone or 0) + --print("sit3",ply,"->",vehicle,ply:GetPos():Distance(EyeTrace.Entity:GetPos())) + return vehicle + else + table.sort(dists, function(a,b) return b.Distance < a.Distance end) + local wants = {} + local eyeang = ply:EyeAngles() + Angle(0,180,0) + for I=1,#dists do + local trace = dists[I] + local behind = distsang[(trace.ang + 180) % 360] + if behind.Distance2 > 3 then + local cost = 0 + if(trace.ang % 90 ~= 0) then cost = cost + 12 end + --[[if(ShouldAlwaysSit(ply)) then + if(trace.ang ~= 180) then cost = cost + 100 end + end]] + if(math.abs(eyeang.yaw - trace.ang) > 12) then + cost = cost + 30 + end + local tbl = { + cost = cost, + ang = trace.ang, + } + table.insert(wants, tbl) + end + end + table.sort(wants,function(a,b) return b.cost > a.cost end) + if(#wants == 0) then return end + ang.yaw = (wants[1].ang - 90) + local ent = nil + if not EyeTrace.HitWorld then + ent = EyeTrace.Entity + if ent:IsPlayer() and not SittingOnPlayer2:GetBool() then return end + if ent:IsPlayer() and IsValid(ent:GetVehicle()) and ent:GetVehicle():GetParent() == ply then return end + + if ent:IsPlayer() and ent:GetInfoNum("sitting_disallow_on_me",0)==1 then + return + end + if sitting_disallow_on_me then + ply:ChatPrint(L.cant_sit_on_players) + return + end + end + local vehicle = Sit(ply, EyeTrace.HitPos - Vector(0,0,20), ang, ent, EyeTrace.PhysicsBone or 0) + + return vehicle + end + + end + +end + + +local function sitcmd(ply) + if ply:InVehicle() then return end + if AdminOnly:GetBool() then + if not ply:IsAdmin() then return end + end + local now=CurTime() + + local nextuse = NextUse[ply] or now + + if nextuse>now then + --ply:ChatPrint("Can not sit again that fast") + return + end + + -- do want to prevent player getting off right after getting in but how :C + if ply:Sit() then + --ply:ChatPrint("You sat down") + nextuse=now + 1 + end + + NextUse[ply] = nextuse + 0.1 + +end + + +-- hook.Add("CanExitVehicle","noinstaleave",function(veh,ply) + +-- if not veh.playerdynseat then return end + +-- local now=CurTime() + +-- local nextuse = NextUse[ply] or now + +-- if nextuse > now then +-- --ply:ChatPrint("Can not leave just yet") +-- return false +-- --else +-- --ply:ChatPrint("You stopped sitting") +-- end + +-- veh.oldang = ply:EyeAngles() +-- end) + +concommand.Add("sit",function(ply, cmd, args) + sitcmd(ply) +end) + +hook.Add("KeyPress","seats_use",function(ply,key) + if key ~= IN_USE then return end + + local walk = ply:KeyDown( IN_SPEED ) or ShouldAlwaysSit(ply) + if not walk then return end + + sitcmd(ply) + +end) + + +hook.Add("PlayerLeaveVehicle","Remove_Seat",function(ply,self) + if(self.removeonexit and self:GetClass()=="prop_vehicle_prisoner_pod") then + NextUse[ply] = CurTime() + 1 + if(self.exit) then + self.exit(ply) + end + + if ply then + ply:SetPos(self.oldpos) + ply:SetEyeAngles(self.oldang) + if ply.UnStuck then + --[[timer.Simple(0,function() + ply:UnStuck() + end)]] + end + end + + self:Remove() + end +end) + + +hook.Add("AllowPlayerPickup","Nopickupwithalt",function(ply) + if(ply:KeyDown(IN_SPEED)) then + return false + end +end) + +hook.Add("PlayerDeath","SitSeat",function(pl) + for k,v in ipairs(player.GetAll()) do + local veh = v:GetVehicle() + if veh:IsValid() and veh.playerdynseat and veh:GetParent()==pl then + veh:Remove() + end + end +end) + +hook.Add("PlayerEnteredVehicle","unsits",function(pl,veh) + for k,v in ipairs(player.GetAll()) do + if v~=pl and v:InVehicle() and v:GetVehicle():IsValid() and v:GetVehicle():GetParent()==pl then + v:ExitVehicle() + end + end + + DropEntityIfHeld( veh ) + + if veh:GetParent():IsValid() then + DropEntityIfHeld( veh:GetParent() ) + end + +end) + +hook.Add("EntityRemoved","Sitting_EntityRemoved",function(ent) + for k,v in ipairs(ents.FindByClass("prop_vehicle_prisoner_pod")) do + if(v:GetParent() == ent) then + if IsValid(v:GetDriver()) then + v:GetDriver():ExitVehicle() + v:Remove() + end + end + end +end) + +timer.Create("RemoveSeats",15,0,function() + for k,v in pairs(ents.FindByClass("prop_vehicle_prisoner_pod")) do + if(v.removeonexit and (v:GetDriver() == nil or not v:GetDriver():IsValid() or v:GetDriver():GetVehicle() ~= v --[[???]])) then + v:Remove() + end + end +end) + +hook.Add("InitPostEntity", "SAW_CompatFix", function() + if hook.GetTable()["CanExitVehicle"]["PAS_ExitVehicle"] and PM_SendPassengers then + local function IsSCarSeat( seat ) + if IsValid(seat) and seat.IsScarSeat and seat.IsScarSeat == true then + return true + end + return false + end + hook.Add("CanExitVehicle", "PAS_ExitVehicle", function( veh, ply ) + if !IsSCarSeat( veh ) and not veh.playerdynseat and veh.vehicle then + // L+R + if ply:VisibleVec( veh:LocalToWorld(Vector(80, 0, 5) )) then + ply:ExitVehicle() + ply:SetPos( veh:LocalToWorld(Vector(75, 0, 5) )) + if veh:GetClass() == "prop_vehicle_prisoner_pod" && !(ply == veh.vehicle:GetDriver()) then PM_SendPassengers( veh.vehicle:GetDriver() ) end + return false + end + + if ply:VisibleVec( veh:LocalToWorld(Vector(-80, 0, 5) )) then + ply:ExitVehicle() + ply:SetPos( veh:LocalToWorld(Vector(-75, 0, 5) )) + if veh:GetClass() == "prop_vehicle_prisoner_pod" && !(ply == veh.vehicle:GetDriver()) then PM_SendPassengers( veh.vehicle:GetDriver() ) end + return false + end + end + --return false --//YOU SHOULDNT RETURN HERE! THIS WILL OVERRIDE THE HOOKS FOR ALL OTHER MOUNTED ADDONS + end) + end +end) + +--[[_duplicatorCopy = _duplicatorCopy or duplicator.Copy +function duplicator.Copy(...) + local tab = _duplicatorCopy(...) + if(tab and tab.Entities and tab.Constraints) then + for k,v in pairs(tab.Entities) do + if(v.removeonexit) then + tab.Entities[k] = nil + end + end + for k,v in pairs(tab.Constraints) do + if(v.Entity) then + if (v.Entity[1] and v.Entity[1].Entity and v.Entity[1].Entity.removeonexit) or (v.Entity[2] and v.Entity[2].Entity and v.Entity[2].Entity.removeonexit) then + tab.Constraints[k] = nil + end + end + end + end + return tab +end]] diff --git a/garrysmod/addons/feature-sit/lua/autorun/server/unstuck.lua b/garrysmod/addons/feature-sit/lua/autorun/server/unstuck.lua new file mode 100644 index 0000000..ae79f55 --- /dev/null +++ b/garrysmod/addons/feature-sit/lua/autorun/server/unstuck.lua @@ -0,0 +1,122 @@ +local ply = nil + +-- WeHateGarbage +local t = {start=nil,endpos=nil,mask=MASK_PLAYERSOLID,filter=nil} +local function PlayerNotStuck() + + t.start = ply:GetPos() + t.endpos = t.start + t.filter = ply + + return util.TraceEntity(t,ply).StartSolid == false + +end + +local NewPos = nil +local function FindPassableSpace( direction, step ) + + local i = 0 + while ( i < 100 ) do + local origin = ply:GetPos() + + --origin = VectorMA( origin, step, direction ) + origin = origin + step * direction + + ply:SetPos( origin ) + if ( PlayerNotStuck( ply ) ) then + NewPos = ply:GetPos() + return true + end + i = i + 1 + end + return false +end + +/* + Purpose: Unstucks player , + Note: Very expensive to call, you have been warned! +*/ +local function UnstuckPlayer( pl ) + ply = pl + + NewPos = ply:GetPos() + local OldPos = NewPos + + if ( !PlayerNotStuck( ply ) ) then + + local angle = ply:GetAngles() + + local forward = angle:Forward() + local right = angle:Right() + local up = angle:Up() + + local SearchScale = 3 -- Increase and it will unstuck you from even harder places but with lost accuracy. Please, don't try higher values than 12 + if ( !FindPassableSpace( forward, SearchScale ) ) + then + if ( !FindPassableSpace( right, SearchScale ) ) + then + if ( !FindPassableSpace( right, -SearchScale ) ) // left + then + if ( !FindPassableSpace( forward, -SearchScale ) ) // back + then + if ( !FindPassableSpace( up, -SearchScale ) ) // down + then + if ( !FindPassableSpace( up, SearchScale ) ) // up + then + + -- spam spam spam + + --Msg( "Can't find the world for player "..tostring(ply).."\n" ) + + return false + + end + end + end + end + end + end + + if OldPos == NewPos then + print("Unstuck: Shouldnothappen") + return true -- Not stuck? + else + ply:SetPos( NewPos ) + if SERVER and ply and ply:IsValid() and ply:GetPhysicsObject():IsValid() then + if ply:IsPlayer() then + ply:SetVelocity(vector_origin) + end + ply:GetPhysicsObject():SetVelocity(vector_origin) -- For some reason setting origin MAY apply some velocity so we're resetting it here. + end + return true + end + + end + + +end + + + + +--------------------- +-- Helper functions +--------------------- +local meta = FindMetaTable"Player" + +if meta.UnStuck then + --ErrorNoHalt"Player:UnStuck implemented by other addon?" +else + + + /* Unstucks a player + + returns: + true: Unstucked + false: Could not UnStuck + else: Not stuck + */ + function meta:UnStuck() + return UnstuckPlayer(self) + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-sit/lua/autorun/ugh.lua b/garrysmod/addons/feature-sit/lua/autorun/ugh.lua new file mode 100644 index 0000000..64419fd --- /dev/null +++ b/garrysmod/addons/feature-sit/lua/autorun/ugh.lua @@ -0,0 +1,23 @@ +if SERVER then + AddCSLuaFile() + return +end + +local last = false +local lsit = 0 +hook.Remove("Think","Sitting_AltUse") +hook.Add("Think","Sitting_CtrlUse",function() + if IsValid( LocalPlayer() ) and IsValid( LocalPlayer():GetVehicle() ) then + LocalPlayer():GetVehicle():SetThirdPersonMode( false ) + end + + -- if(last and !input.IsKeyDown(KEY_E)) then + -- if input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) then + -- if lsit + 1 < CurTime() then + -- RunConsoleCommand("sit") + -- lsit = CurTime() + -- end + -- end + -- end + -- last = input.IsKeyDown(KEY_E) +end) diff --git a/garrysmod/addons/feature-stormfox/lua/autorun/stormfox2.lua b/garrysmod/addons/feature-stormfox/lua/autorun/stormfox2.lua new file mode 100644 index 0000000..9b2b88b --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/autorun/stormfox2.lua @@ -0,0 +1,208 @@ +-- Check for StormFox 1 +if StormFox and StormFox.Version < 2 or file.Exists("autorun/stormfox_autorun.lua", "LUA") then + error("StormFox 1 detected. StormFox 2 can't run.") + return +end +-- While sv_skyname is fixed, I still want this to be first. +if SERVER then + local function checkVar(conname, default) + local con = GetConVar(conname) + if not con then return default end + return con:GetBool() + end + hook.Add("stormfox2.postfunction", "stormfox2.skynameinit", function() + local enable = checkVar("sf_enable", true) + local enablesky = checkVar('sf_enable_skybox', true) + local skybox2d = not checkVar("sf_use_2dskybox", false) + if enable and enablesky and skybox2d then + RunConsoleCommand("sv_skyname", "painted") + end + end) +end + +--[[------------------------------------------------------------------------- +StormFox 2.0 +---------------------------------------------------------------------------]] +StormFox2 = {} +StormFox2.WorkShopVersion = false--game.IsDedicated() +StormFox2.WorkShopURL = "https://steamcommunity.com/sharedfiles/filedetails/?id=2447774443" +-- StormFox's Version number + StormFox2.Version = 2.46 + StormFox2.Loaded = false + +--[[----------------------------------------------------------------- + Prints a message in the console. +---------------------------------------------------------------------------]] + local env_color = SERVER and Color(138,223,255) or Color(230,217,111) + function StormFox2.Msg(...) + local a = {...} + table.insert(a, 1, env_color) + local t = {} + local last = 0 + for _, v in ipairs(a) do + local cur = 0 + if type(v) == "string" then + cur = 1 + elseif type(v) == "table" then + cur = 2 + end + if last == cur then + if cur == 1 then + t[#t] = t[#t] .. " " .. v + break + elseif cur == 2 then + t[#t] = v + break + end + end + last = cur + table.insert(t,v) + end + MsgC(Color(155,155,255),"[StormFox2] ",unpack( t )) + MsgN() + end +--[[----------------------------------------------------------------- + Prints a warning in the console. Can also cause an error. +---------------------------------------------------------------------------]] + local red = Color(255,75,75) + function StormFox2.Warning( sMessage, bError ) + MsgC(Color(155,155,255),"[StormFox2]",red," [WARNING] ",env_color,sMessage,"\n") + if bError then + error(sMessage) + end + end +StormFox2.Msg("Version: V" .. StormFox2.Version .. ".") + +-- Local functions + local function HandleFile(str) + local path = str + if string.find(str,"/") then + path = string.GetFileFromFilename(str) + end + if string.sub(path,0,4) == "old_" then + StormFox2.Warning("Exclude old file: " .. str) + return false + end + local _type = string.sub(path,0,3) + if SERVER then + if _type == "cl_" or _type == "sh_" then + AddCSLuaFile(str) + end + if _type ~= "cl_" then + return include(str) + end + elseif _type ~= "sv_" then + return include(str) + end + end + local function HandleFolder(str) + for _,fil in ipairs(file.Find(str .. "/*.lua","LUA")) do + HandleFile(str .. "/" .. fil) + end + end + +-- File function that creates folders + local function fileWriteWError( filename, data ) + local filObj = file.Open(filename, "wb", "DATA") + if not filObj then return false end + filObj:Write( data ) + filObj:Close() + return true + end + function StormFox2.FileWrite( filename, data ) + local a = string.Explode("/", filename) + -- Create folders + if #a > 0 then + local path = "" + for i = 1, #a - 1 do + path = path .. (i > 1 and "/" or "") .. a[i] + if not file.Exists(path, "DATA") then + file.CreateDir(path) + end + end + end + -- Create file + if fileWriteWError(filename, data) then return true end + StormFox2.Warning("Unable to write file [" .. filename .. "]. Game has no access!") + end + +-- Resources + if SERVER then + local _,folder = file.Find("resource/localization/*", "GAME") + for _,lan in ipairs(folder) do + if file.Exists("resource/localization/" .. lan .. "/stormfox.properties", "GAME") then + resource.AddSingleFile("resource/localization/" .. lan .. "/stormfox.properties") + --print("Added","resource/localization/" .. lan .. "/StormFox2.properties") + elseif lan == "en" then + StormFox2.Warning("Missing language file: resource/localization/en/stormfox.properties") + end + end + end + +-- Network Strings + StormFox2.Net = {} + StormFox2.Net.Settings = "SF_S" -- Handles Settings + StormFox2.Net.Time = "SF_T" -- Handles Settings + StormFox2.Net.LightStyle = "SF_L" -- Handles Lights + StormFox2.Net.Shadows = "SF_H" -- Handles shadows + StormFox2.Net.Thunder = "SF_U" -- Handles Thunder + StormFox2.Net.Network = "SF_N" -- Handles Data + StormFox2.Net.Terrain = "SF_A" -- Handles Terrain + StormFox2.Net.Tool = "SF_O" -- Handles the SF tool + StormFox2.Net.Weather = "SF_W" -- Handles the SF tool + StormFox2.Net.Permission = "SF_P" -- Handles the SF tool + StormFox2.Net.Texture = "SF_Q" -- Handles the SF tool + if SERVER then + for _, str in pairs( StormFox2.Net ) do + util.AddNetworkString( str ) + end + end + +-- Load lib. Libaries are where base functions like temperature, wind, terrain and map data are created. + HandleFolder("stormfox2/lib") + -- Check if map-data has loaded + if not SF_BSPDATALOADED then + StormFox2.Warning("unable to load mapdata!", true) + end + hook.Run("stormfox2.postlib") -- Gets called after libary is done. + +-- Load framework. Framework is where higer functions are created from the base. Like time. + HandleFolder("stormfox2/framework") + hook.Run("stormfox2.postframework") -- Gets called after framework is done. + +-- Load functions. Functions are parts of StormFox that aren't utilized by anything else. Like clouds. + HandleFolder("stormfox2/functions") + hook.Run("stormfox2.postfunction") -- Gets called after functions is done. + +-- Finish up + HandleFolder("stormfox2") -- No idea what should be here. + timer.Simple(0,function() + --[[------------------------------------------------------------------------- + Allows addons to initialize their functions before calling StormFox2.PostInit. + ---------------------------------------------------------------------------]] + hook.Run("stormfox2.preinit") -- For libary files + --[[------------------------------------------------------------------------- + Gets called when StormFox is done loading. + ---------------------------------------------------------------------------]] + hook.Run("stormfox2.postinit") + StormFox2.Loaded = true + if CLIENT then + hook.Run( "StormFox2.PostEntityScan" ) + end + end) + +-- Hack to stop cleanupmap deleting things. + STORMFOX_CLEANUPMAP = STORMFOX_CLEANUPMAP or game.CleanUpMap + -- + function game.CleanUpMap( dontSendToClients, ExtraFilters ) + ExtraFilters = ExtraFilters or {} + table.insert(ExtraFilters,"light_environment") + table.insert(ExtraFilters,"env_fog_controller") + table.insert(ExtraFilters,"shadow_control") + table.insert(ExtraFilters,"env_tonemap_controller") + table.insert(ExtraFilters,"env_wind") + table.insert(ExtraFilters,"env_skypaint") + table.insert(ExtraFilters,"sf_soundscape") + table.insert(ExtraFilters,"stormfox_streetlight_invisible") + STORMFOX_CLEANUPMAP(dontSendToClients,ExtraFilters) + end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/cmenu/items/time.lua b/garrysmod/addons/feature-stormfox/lua/cmenu/items/time.lua new file mode 100644 index 0000000..52fcf61 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/cmenu/items/time.lua @@ -0,0 +1,5 @@ +octogui.cmenu.registerItem('rp', 'time', { + text = L.see_time, + icon = octolib.icons.silk16('time'), + say = '/time', +}) diff --git a/garrysmod/addons/feature-stormfox/lua/entities/env_atmosphere.lua b/garrysmod/addons/feature-stormfox/lua/entities/env_atmosphere.lua new file mode 100644 index 0000000..6cd1be3 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/env_atmosphere.lua @@ -0,0 +1,134 @@ +--[[------------------------------------------------------------------------- +Changes the weather for players near it. +---------------------------------------------------------------------------]] +AddCSLuaFile() + +DEFINE_BASECLASS( "base_anim" ) + +ENT.PrintName = "SF Atmo-Sphere" +ENT.Author = "Nak" +ENT.Information = "Changes the weather for players near it." +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = true +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/maxofs2d/hover_basic.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetUseType( ONOFF_USE ) + end +end + +function ENT:SetupDataTables() + local weathers = {} + if CLIENT then + local s = language.GetPhrase("#none") + weathers[string.upper(s[1]) .. string.sub(s, 2)] = "none" + else + weathers["None"] = "one" + end + for _, str in ipairs( StormFox2.Weather.GetAll() ) do + if str == "BlueMoon" then continue end -- Shhh + weathers[StormFox2.Weather.Get(str).Name] = str + end + self:NetworkVar( "String", 0, "WeatherName", { KeyName = "Weather", Edit = { type = "Combo", order = 1, values = weathers } } ) + self:NetworkVar( "Float", 0, "Percent", { KeyName = "Percent", Edit = { type = "Float", order = 2, min = 0, max = 1 } } ) + self:NetworkVar( "Float", 1, "Temperature", { KeyName = "Temperature(C)", Edit = { type = "Int", order = 3, min = -20, max = 30 } } ) + self:NetworkVar( "Int", 0, "Range", { KeyName = "Range", Edit = { type = "Int", order = 4, min = 250, max = 5000 } } ) + if SERVER then + self:SetRange( 250 ) + self:SetPercent( 0.5 ) + end +end + +function ENT:CanProperty(_, str) + if str == "skin" then return false + elseif str == "drive" then return false + elseif str == "collision" then return false + elseif str == "persist" then return true + end + return true +end + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + +if SERVER then + function ENT:Think() + self:AddEFlags( EFL_FORCE_CHECK_TRANSMIT ) + end + function ENT:SetWeather( str, amount, range, temp ) + self:SetWeatherName( str ) + self:SetPercent( amount ) + if range then + self:SetRange( range ) + end + if temp then + self:SetTemperature( temp ) + end + end +else + local v1 = Vector(1,1,1) + local col1, col2 = Color(255,0,0,55),Color(0,255,0,255) + function ENT:Draw() + local w,p = self:GetWeatherName(),self:GetPercent() + local r = self:GetRange() + local we = StormFox2.Weather.Get(w) + if IsValid( we ) then + local c = CurTime() + local p = self:GetPos() + local np = p + Vector(0,0,math.sin( 3 * c)) + local in_v = StormFox2.util.RenderPos():Distance( p ) < r + self:SetRenderBounds( v1 * -r, v1 ) + self:SetRenderOrigin( np ) + render.MaterialOverrideByIndex(0,Material("stormfox2/entities/env_weatherball_on")) + render.MaterialOverrideByIndex(1,Material("stormfox2/entities/env_weatherball_sphere")) + self:DrawModel() + render.MaterialOverrideByIndex() + self:SetRenderOrigin( ) + if not in_v then + render.SetColorMaterial() + render.DrawSphere( p, r, 30, 30, col1) + end + -- (nTime, nTemp, nWind, bThunder,nFraction) + local symbol = we.GetIcon( StormFox2.Time.Get(), self:GetTemperature() or StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), self:GetPercent() ) + render.SetMaterial( symbol ) + render.DrawSprite( np , 8, 8, color_white) + self:SetRenderAngles( Angle(0,c * 40 % 360,0) ) + else + self:DrawModel() + end + end + + hook.Add("Think", "StormFox2.Weather.EController", function() + if not StormFox2 or StormFox2.Version < 2 then return end + if not StormFox2.Weather or not StormFox2.Weather.RemoveLocal then return end + local t = {} + for _, ent in ipairs( ents.FindByClass("env_atmosphere") ) do + local p = ent:GetPos() + local r = ent:GetRange() + local dis = StormFox2.util.RenderPos():Distance( p ) + local in_v = StormFox2.util.RenderPos():Distance( p ) < r + if in_v and IsValid(StormFox2.Weather.Get(ent:GetWeatherName() or "")) then table.insert(t, {ent, dis}) end + end + if #t < 1 then + StormFox2.Weather.RemoveLocal() + return + end + table.sort(t,function(a,b) return a[2] < b[2] end) + local ent = t[1][1] + if ent:GetPercent() <= 0 then + StormFox2.Weather.RemoveLocal() + else + StormFox2.Weather.SetLocal( ent:GetWeatherName(), ent:GetPercent(), 4, ent:GetTemperature()) + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/gmod_wire_expression2/core/custom/cl_stormfox2.lua b/garrysmod/addons/feature-stormfox/lua/entities/gmod_wire_expression2/core/custom/cl_stormfox2.lua new file mode 100644 index 0000000..1b7d71a --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/gmod_wire_expression2/core/custom/cl_stormfox2.lua @@ -0,0 +1,20 @@ +-- StormFox2 E2 extension +-- Created by Nak + +-- Time +E2Helper.Descriptions["sfTime()"] = "Returns the time as a number between 0 and 1439" +E2Helper.Descriptions["isNight()"] = "Is it night?" +E2Helper.Descriptions["isDay()"] = "Is it day?" +E2Helper.Descriptions["sfTimeDisplay()"] = "Returns the time as a 24h string" +E2Helper.Descriptions["sfTimeDisplay12h()"] = "Returns the time as a 12h string" + +-- Weather +E2Helper.Descriptions["isRaining()"] = "Is it raining?" +E2Helper.Descriptions["isSnowing()"] = "Is it snowing?" +E2Helper.Descriptions["isThundering()"] = "Is it thundering?" +E2Helper.Descriptions["getWeather()"] = "Returns the description of the current weahter" +E2Helper.Descriptions["getWeatherPercent()"] = "Returns the weather amount between [0-1]" + +-- Wind +E2Helper.Descriptions["getWind()"] = "Returns the wind in m/s" +E2Helper.Descriptions["getWindBeaufort()"] = "Returns the wind in Beaufort scale" \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/gmod_wire_expression2/core/custom/stormfox2.lua b/garrysmod/addons/feature-stormfox/lua/entities/gmod_wire_expression2/core/custom/stormfox2.lua new file mode 100644 index 0000000..c5f4314 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/gmod_wire_expression2/core/custom/stormfox2.lua @@ -0,0 +1,68 @@ +-- StormFox2 E2 extension +-- By Nak + +E2Lib.RegisterExtension("stormfox2", true, "Lets E2 chips use StormFox functions") + +__e2setcost( 3 ) + +-- Time + e2function number sfTime() + return StormFox2.Time.Get() + end + e2function number isNight() + return StormFox2.Time.IsNight() and 1 or 0 + end + e2function number isDay() + return StormFox2.Time.IsDay() and 1 or 0 + end + + __e2setcost( 15 ) + e2function string sfTimeDisplay() + return StormFox2.Time.TimeToString(nil) + end + + e2function string sfTimeDisplay12h() + return StormFox2.Time.TimeToString(nil,true) + end + +-- Weather + __e2setcost( 7 ) + local function isRaining() + return StormFox2.Weather.GetCurrent().Name == "Rain" + end + local function isCold() + return StormFox2.Temperature.Get() <= -2 + end + + e2function number isRaining() + if isCold() then return 0 end + return isRaining() and 1 or 0 + end + + e2function number isSnowing() + if not isCold() then return 0 end + return isRaining() and 1 or 0 + end + + e2function number isThundering() + return StormFox2.Thunder.IsThundering() and 1 or 0 + end + + __e2setcost( 10 ) + e2function string getWeather() + return StormFox2.Weather.GetDescription() + end + + e2function number getWeatherPercent() + return StormFox2.Weather.GetPercent() + end +-- Wind + __e2setcost( 3 ) + e2function number getWind() + return StormFox2.Wind.GetForce() + end + + __e2setcost( 10 ) + e2function number getWindBeaufort() + return StormFox2.Wind.GetBeaufort() + end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_clock.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_clock.lua new file mode 100644 index 0000000..1b6e3a5 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_clock.lua @@ -0,0 +1,98 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Clock" +ENT.Author = "Nak" +ENT.Purpose = "A working clock" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/props_trainstation/trainstation_clock001.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + end + self.RenderMode = 1 + self:SetRenderMode(RENDERMODE_TRANSALPHA) + if WireAddon and SERVER then + self.Outputs = Wire_CreateOutputs(self, { + "Clock_24 [STRING]", + "Clock_12 [STRING]", + "Clock_raw" + }) + Wire_TriggerOutput(self, "Clock_raw", StormFox2.Time.Get(true)) + Wire_TriggerOutput(self, "Clock_24", StormFox2.Time.TimeToString(nil)) + Wire_TriggerOutput(self, "Clock_12", StormFox2.Time.TimeToString(nil,true)) + end +end + +if SERVER then + if WireAddon then + local function SetWire(self,data,value) + if self.Outputs[data].Value != value then + Wire_TriggerOutput(self, data, value) + end + end + function ENT:Think() + if not WireAddon then return end + if (self._l or 0) > SysTime() then return end + self._l = SysTime() + 1 + SetWire(self, "Clock_raw", StormFox2.Time.Get(true)) + SetWire(self, "Clock_24", StormFox2.Time.TimeToString(nil)) + SetWire(self, "Clock_12", StormFox2.Time.TimeToString(nil,true)) + end + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 16 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y + 180,0)) + ent:Spawn() + ent:Activate() + return ent + end +else + local mat = Material("vgui/circle") + local mat2 = Material("vgui/dashed_line") + local mat3 = Material("glass/offwndwb") + local mat4 = Material("stormfox2/entities/clock_material") + local sf = Material("stormfox/SF.png") + function ENT:Draw() + render.MaterialOverrideByIndex( 0, mat3 ) + render.MaterialOverrideByIndex( 1, mat4 ) + self:DrawModel() + render.MaterialOverrideByIndex( ) + if ( halo.RenderedEntity() == self ) then return end + if not StormFox2 then return end + if not StormFox2.Time then return end + + local a = self:GetAngles() + local t = StormFox2.Time.Get() + local h = math.floor(t / 60) -- 0 - 24 + local m = t - h * 60 -- 0 - 60 + + cam.Start3D2D(self:GetPos(),self:LocalToWorldAngles(Angle(0,90,90)),0.2) + surface.SetMaterial(mat) + surface.SetDrawColor(0,0,0) + surface.DrawTexturedRect(-10,-10,20,20) + + surface.SetMaterial(mat2) + -- hour arm + local ang = h * 30 + m / 2 + 90 + surface.DrawTexturedRectRotated(0,0,140,4,-ang) + + -- min arm + local ang = m * 6 + 90 + surface.DrawTexturedRectRotated(0,0,200,4,-ang) + cam.End3D2D() + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_candle.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_candle.lua new file mode 100644 index 0000000..d08e8a6 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_candle.lua @@ -0,0 +1,80 @@ + +-- Only add this entity, if the server has CSGO +if SERVER and not IsMounted("csgo") then return end +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Candle Cluster" +ENT.Author = "Nak" +ENT.Purpose = "A cursed candle" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +local ran_tab = { + Model("models/props/de_aztec/hr_aztec/aztec_lighting/aztec_lighting_candle_cluster_01_unlit.mdl"), + Model("models/props/de_aztec/hr_aztec/aztec_lighting/aztec_lighting_candle_cluster_02_unlit.mdl"), +} + +function ENT:Lit() + if self._lit then return end + self._lit = true + self:SetModel( string.gsub(self:GetModel(), "unlit.mdl$", "lit.mdl") ) +end + +local snd_tab = { + Sound("player/halloween/ghost_swish_c_01.wav"), + Sound("player/halloween/ghost_swish_c_02.wav"), + Sound("player/halloween/ghost_swish_c_03.wav"), + Sound("player/halloween/ghost_swish_c_04.wav") +} + +function ENT:UnLit() + if not self._lit then return end + self._lit = false + self:SetModel( string.gsub(self:GetModel(), "_lit.mdl$", "_unlit.mdl") ) + self:EmitSound((table.Random(snd_tab)), 50, math.random(75, 125), 0.5) +end + +function ENT:Initialize() + self._lit = false + if SERVER then + self:SetModel( (table.Random(ran_tab)) ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + end + self.RenderMode = 1 + self:SetRenderMode(RENDERMODE_TRANSALPHA) +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 0.1 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y + 180,0)) + ent:Spawn() + ent:Activate() + return ent + end + function ENT:Think() + self:NextThink( CurTime() + 7 ) + if StormFox2.Time.IsNight() and self:WaterLevel() < 1 then + self:Lit() + else + self:UnLit() + end + return true + end +else + function ENT:Draw() + self:DrawModel() + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_candle_small.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_candle_small.lua new file mode 100644 index 0000000..fe49a45 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_candle_small.lua @@ -0,0 +1,81 @@ + +-- Only add this entity, if the server has CSGO +if SERVER and not IsMounted("csgo") then return end +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Small Candle" +ENT.Author = "Nak" +ENT.Purpose = "A cursed candle" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +local ran_tab = { + Model("models/props/de_aztec/hr_aztec/aztec_lighting/aztec_lighting_candle_01_unlit.mdl"), + Model("models/props/de_aztec/hr_aztec/aztec_lighting/aztec_lighting_candle_02_unlit.mdl"), + Model("models/props/de_aztec/hr_aztec/aztec_lighting/aztec_lighting_candle_03_unlit.mdl"), +} + +function ENT:Lit() + if self._lit then return end + self._lit = true + self:SetModel( string.gsub(self:GetModel(), "unlit.mdl$", "lit.mdl") ) +end + +local snd_tab = { + Sound("player/halloween/ghost_swish_c_01.wav"), + Sound("player/halloween/ghost_swish_c_02.wav"), + Sound("player/halloween/ghost_swish_c_03.wav"), + Sound("player/halloween/ghost_swish_c_04.wav") +} + +function ENT:UnLit() + if not self._lit then return end + self._lit = false + self:SetModel( string.gsub(self:GetModel(), "_lit.mdl$", "_unlit.mdl") ) + self:EmitSound((table.Random(snd_tab)), 50, math.random(75, 125), 0.5) +end + +function ENT:Initialize() + self._lit = false + if SERVER then + self:SetModel( (table.Random(ran_tab)) ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + end + self.RenderMode = 1 + self:SetRenderMode(RENDERMODE_TRANSALPHA) +end + +if SERVER then + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 0.1 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y + 180,0)) + ent:Spawn() + ent:Activate() + return ent + end + function ENT:Think() + self:NextThink( CurTime() + 7 ) + if StormFox2.Time.IsNight() and self:WaterLevel() < 1 then + self:Lit() + else + self:UnLit() + end + return true + end +else + function ENT:Draw() + self:DrawModel() + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_lantern.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_lantern.lua new file mode 100644 index 0000000..00b52df --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_lantern.lua @@ -0,0 +1,149 @@ + +-- Only add this entity, if the server has CSGO +if SERVER and not IsMounted("csgo") then return end +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Large Lantern" +ENT.Author = "Nak" +ENT.Purpose = "A lantern from CSGO" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:Lit() + if self._lit then return end + self._lit = true + self:SetNWBool("lit", true) +end + +function ENT:UnLit() + if not self._lit then return end + self._lit = false + self:SetNWBool("lit", false) +end + +function ENT:Initialize() + self._lit = false + if SERVER then + self:SetModel( "models/props/de_dust/hr_dust/dust_lights/dust_ornate_lantern_01.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + self:SetUseType( SIMPLE_USE ) + self.ltype = 1 + else + self._part = {} + end + self.RenderMode = 1 + self:SetRenderMode(RENDERMODE_TRANSALPHA) +end + + +if SERVER then + local function sendMsg( act, msg ) + if type(act) ~= "Player" then return end + act:PrintMessage( HUD_PRINTTALK, "Lamp: " .. msg ) + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 0.1 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y + 180,0)) + ent:Spawn() + ent:Activate() + return ent + end + function ENT:Use( act ) + self.ltype = self.ltype + 1 + if self.ltype > 2 then + self.ltype = 0 + end + if self.ltype == 0 then + sendMsg( act, "Always off" ) + self:UnLit() + elseif self.ltype == 1 then + sendMsg( act, "On at night" ) + else + sendMsg( act, "Always on" ) + self:Lit() + end + end + function ENT:Think() + self:NextThink( CurTime() + 7 ) + if self.ltype == 0 or self:WaterLevel() > 0 then + self:UnLit() + elseif self.ltype == 2 then + self:Lit() + elseif StormFox2.Time.IsNight() then + self:Lit() + else + self:UnLit() + end + return true + end +else + local ran,rand,max = math.random,math.Rand,math.max + local t = { + (Material( "sprites/flamelet1" )), + (Material( "sprites/flamelet2" )), + (Material( "sprites/flamelet3" )) + } + local lit_mat = Material("stormfox2/models/dust_ornate_lantern_lit") + local c = Color(255,255,255) + local function GetDis(ent) + local lp = LocalPlayer() + if not lp then return 0 end + return lp:GetPos():DistToSqr(ent:GetPos()) + end + function ENT:Think() + if not self:GetNWBool("lit", false) or GetDis(self) > 4500000 then + self:SetNextClientThink(CurTime() + 1) + return true + end + for i = 0, 6 do + self._part[i] = math.random(55, 155) / 255 + end + local nThink = CurTime() + math.Rand(0.1,0.6) + local dlight = DynamicLight( self:EntIndex() ) + if ( dlight ) then + local ml = StormFox2.Map.GetLightRaw() + dlight.pos = self:LocalToWorld(Vector(rand(-0.6,0.6), rand(-0.6,0.6), -20)) + dlight.r = 255 + dlight.g = 255 + dlight.b = 155 + dlight.brightness = 2 - ml / 200 + dlight.Decay = 0 + dlight.Size = 256 * 1.5 + dlight.DieTime = nThink + 0.5 + end + self:SetNextClientThink(nThink) + return true + end + function ENT:Draw() + render.SetColorModulation(1,1,1) + if not self:GetNWBool("lit", false) then + self:DrawModel() + return + end + render.SuppressEngineLighting(true) + render.SetLightingOrigin( self:GetPos() ) + render.ResetModelLighting( 0,0,0 ) + for i = 0, 6 do + local l = self._part[i] or 0 + render.SetModelLighting( i, l,l,l ) + end + render.MaterialOverride(lit_mat) + self:DrawModel() + render.MaterialOverride() + render.SuppressEngineLighting(false) + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_lantern2.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_lantern2.lua new file mode 100644 index 0000000..c87723f --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_cs_lantern2.lua @@ -0,0 +1,149 @@ + +-- Only add this entity, if the server has CSGO +if SERVER and not IsMounted("csgo") then return end +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Lantern" +ENT.Author = "Nak" +ENT.Purpose = "A lantern from CSGO" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:Lit() + if self._lit then return end + self._lit = true + self:SetNWBool("lit", true) +end + +function ENT:UnLit() + if not self._lit then return end + self._lit = false + self:SetNWBool("lit", false) +end + +function ENT:Initialize() + self._lit = false + if SERVER then + self:SetModel( "models/props/de_dust/hr_dust/dust_lights/dust_ornate_lantern_02.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + self:SetUseType( SIMPLE_USE ) + self.ltype = 1 + else + self._part = {} + end + self.RenderMode = 1 + self:SetRenderMode(RENDERMODE_TRANSALPHA) +end + + +if SERVER then + local function sendMsg( act, msg ) + if type(act) ~= "Player" then return end + act:PrintMessage( HUD_PRINTTALK, "Lamp: " .. msg ) + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 0.1 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y + 180,0)) + ent:Spawn() + ent:Activate() + return ent + end + function ENT:Use( act ) + self.ltype = self.ltype + 1 + if self.ltype > 2 then + self.ltype = 0 + end + if self.ltype == 0 then + sendMsg( act, "Always off" ) + self:UnLit() + elseif self.ltype == 1 then + sendMsg( act, "On at night" ) + else + sendMsg( act, "Always on" ) + self:Lit() + end + end + function ENT:Think() + self:NextThink( CurTime() + 7 ) + if self.ltype == 0 or self:WaterLevel() > 0 then + self:UnLit() + elseif self.ltype == 2 then + self:Lit() + elseif StormFox2.Time.IsNight() then + self:Lit() + else + self:UnLit() + end + return true + end +else + local ran,rand,max = math.random,math.Rand,math.max + local t = { + (Material( "sprites/flamelet1" )), + (Material( "sprites/flamelet2" )), + (Material( "sprites/flamelet3" )) + } + local lit_mat = Material("stormfox2/models/dust_ornate_lantern_lit") + local c = Color(255,255,255) + local function GetDis(ent) + local lp = LocalPlayer() + if not lp then return 0 end + return lp:GetPos():DistToSqr(ent:GetPos()) + end + function ENT:Think() + if not self:GetNWBool("lit", false) or GetDis(self) > 4500000 then + self:SetNextClientThink(CurTime() + 1) + return true + end + for i = 0, 6 do + self._part[i] = math.random(55, 155) / 255 + end + local nThink = CurTime() + math.Rand(0.1,0.6) + local dlight = DynamicLight( self:EntIndex() ) + if ( dlight ) then + local ml = StormFox2.Map.GetLightRaw() + dlight.pos = self:LocalToWorld(Vector(rand(-0.6,0.6), rand(-0.6,0.6), 5)) + dlight.r = 255 + dlight.g = 255 + dlight.b = 155 + dlight.brightness = 2 - ml / 200 + dlight.Decay = 0 + dlight.Size = 256 * 1.5 + dlight.DieTime = nThink + 0.5 + end + self:SetNextClientThink(nThink) + return true + end + function ENT:Draw() + render.SetColorModulation(1,1,1) + if not self:GetNWBool("lit", false) then + self:DrawModel() + return + end + render.SuppressEngineLighting(true) + render.SetLightingOrigin( self:GetPos() ) + render.ResetModelLighting( 0,0,0 ) + for i = 0, 6 do + local l = self._part[i] or 0 + render.SetModelLighting( i, l,l,l ) + end + render.MaterialOverride(lit_mat) + self:DrawModel() + render.MaterialOverride() + render.SuppressEngineLighting(false) + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_digitalclock.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_digitalclock.lua new file mode 100644 index 0000000..8e016e8 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_digitalclock.lua @@ -0,0 +1,129 @@ +AddCSLuaFile() +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Digital Clock" +ENT.Author = "Nak" +ENT.Purpose = "A working clock" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +if SERVER then + function ENT:Initialize() + self:SetModel( "models/hunter/blocks/cube025x025x025.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self.mode = 0 + self:SetUseType( SIMPLE_USE ) + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 16 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y,0)) + ent:Spawn() + ent:Activate() + return ent + end + function ENT:Use( activator ) + self.mode = (self.mode + 1) % 3 + self:SetNWInt("mode", self.mode) + self:EmitSound("buttons/button24.wav") + end +else + function ENT:Initialize() + self.ClockBase = ClientsideModel("models/maxofs2d/hover_plate.mdl",RENDERGROUP_TRANSLUCENT) + self.ClockBase:SetPos(self:LocalToWorld(Vector(0,0,-6.2))) + self.ClockBase:SetAngles(self:GetAngles()) + self.ClockBase:SetParent(self) + self.ClockBase:SetNoDraw(true) + end + function ENT:OnRemove() + SafeRemoveEntity(self.ClockBase) + end + local cos,sin,rad,round = math.cos,math.sin,math.rad,math.Round + local mat = Material("phoenix_storms/glass") + local matBase = Material("effects/splashwake1") + local matBeam = Material( "effects/lamp_beam" ) + function ENT:Draw() + -- Render glasscube + render.SetBlend(0.5) + render.MaterialOverride(mat) + self:DrawModel() + render.MaterialOverride() + render.SetBlend(1) + -- Render clockbase + if not IsValid(self.ClockBase) then + self.ClockBase = ClientsideModel("models/maxofs2d/hover_plate.mdl",RENDERGROUP_TRANSLUCENT) + self.ClockBase:SetParent(self) + self.ClockBase:SetNoDraw(true) + end + self.ClockBase:DrawModel() + if not IsValid(self.ClockBase:GetParent()) then + self.ClockBase:SetParent(self) + self.ClockBase:SetPos(self:LocalToWorld(Vector(0,0,-6.2))) + self.ClockBase:SetAngles(self:GetAngles()) + end + -- Render checks + if ( halo.RenderedEntity() == self ) then return end + if not StormFox2 then return end + if not StormFox2.Time then return end + if not StormFox2.Weather then return end + -- Draw holo-light + local a = self:GetAngles() + local f = math.random(78,80) + local r = math.random(10) + local col = Color(155 - r,155 - r,255) + cam.Start3D2D(self:LocalToWorld(Vector(0,0,-4.6)),self:GetAngles(),0.1) + surface.SetDrawColor(col) + surface.SetMaterial(matBase) + surface.DrawTexturedRectRotated(0,0,100,100,SysTime() * 10) + surface.DrawTexturedRectRotated(0,0,100,100,SysTime() * -12) + cam.End3D2D() + + render.SetMaterial(matBeam) + render.DrawBeam( self:LocalToWorld(Vector(0,0,-5)), self:LocalToWorld(Vector(0,0,5)), 18 - math.random(1), 0, 0.9, col ) + + -- Draw Display + cam.Start3D2D(self:GetPos(),Angle(180,EyeAngles().y + 90,-a.p -90),0.07) + local _showWeather = CurTime() % 14 < 7 + local mode = self:GetNWInt("mode", 0) + if mode~= 0 then + col.a = 55 + draw.RoundedBox(30, 30,-40, 30, 15, col) + col.a = 255 + draw.RoundedBox(30, 15 + mode * 15,-40, 15, 15, col) + end + if _showWeather and mode ~= 1 or mode == 2 then + surface.SetDrawColor(col) + surface.SetMaterial(StormFox2.Weather.GetIcon()) + surface.SetTextColor(col) + surface.SetFont("SkyFox-DigitalClock") + local temp = round(StormFox2.Temperature.GetDisplay() ,1) .. StormFox2.Temperature.GetDisplaySymbol() + + text_length = surface.GetTextSize(temp) + surface.SetTextPos(-text_length / 2,-30) + surface.DrawText(temp) + surface.DrawTexturedRect(-60,-60,40,40) + else + surface.SetTextColor(col) + surface.SetFont("SkyFox-DigitalClock") + local text = StormFox2.Time.GetDisplay() + local text_length = surface.GetTextSize(text) + surface.SetTextPos(-text_length / 2,-30) + surface.DrawText(text) + end + cam.End3D2D() + end +end + + diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/cl_init.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/cl_init.lua new file mode 100644 index 0000000..d3367eb --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/cl_init.lua @@ -0,0 +1,65 @@ +include("shared.lua") + +hook.Add( "PhysgunPickup", "StormFox2.MapIce.DisallowPickup", function( ply, ent ) + if ent:GetClass() == "stormfox_mapice" then return false end +end ) + +-- nature/dirtfloor005 +-- wood/woodburnt001 +local ice = Material("stormfox2/effects/ice_water") + +local RenderSkyBoxIce = false +local function BuildPhysics( self ) + if not STORMFOX_WATERMESHCOLLISON then return end + self.phyis = true + if not self:PhysicsInitMultiConvex(STORMFOX_WATERMESHCOLLISON) then + StormFox2.Warning("Unable to create ice physics") + end + local phys = self:GetPhysicsObject() + self:SetMoveType( MOVETYPE_NONE ) + self:SetMaterial( "stormfox2/effects/ice_water" ) + if ( IsValid( phys ) ) then + phys:EnableMotion( false ); + phys:AddGameFlag( FVPHYSICS_CONSTRAINT_STATIC ) + phys:SetMass(4000) + phys:EnableDrag(false) + end + self:EnableCustomCollisions( true ); + self:SetSolid( SOLID_VPHYSICS ) + self:AddFlags( FL_WORLDBRUSH ) +end + +function ENT:Initialize() + if StormFox2.Environment._SETMapIce then + StormFox2.Environment._SETMapIce( true ) + end + if not STORMFOX_WATERMESHCOLLISON then return end + BuildPhysics( self ) + self:SetRenderBoundsWS(StormFox2.Map.MinSize(),StormFox2.Map.MaxSize()) +end + +function ENT:OnRemove( ) + if #ents.FindByClass("stormfox_mapice") > 1 then return end + if StormFox2.Environment._SETMapIce then + StormFox2.Environment._SETMapIce( false ) + end +end + +function ENT:Think() + if self.phyis or not STORMFOX_WATERMESHCOLLISON then return end + BuildPhysics( self ) +end + +hook.Add("PreDrawTranslucentRenderables","StormFox2.Client.RenderSkyWater",function(a,b) + if not StormFox2 or not StormFox2.Environment or not StormFox2.Environment.HasMapIce() or not StormFox2.Map.GetLight then return end + if not STORMFOX_WATERMESH_SKYBOX then return end -- Invalid mesh. + local n = (50 + (StormFox2.Map.GetLight() or 100)) / 200 + ice:SetVector("$color", Vector(n,n,n)) + render.SetMaterial(ice) + if b then + -- Render skybox-water + STORMFOX_WATERMESH_SKYBOX:Draw() + else + STORMFOX_WATERMESH:Draw() + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/init.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/init.lua new file mode 100644 index 0000000..0670c63 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/init.lua @@ -0,0 +1,190 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +local ice = Material("stormfox2/effects/ice_water") + +local Props = {} +hook.Add( "PhysgunPickup", "StormFox2.MapIce.DisallowPickup", function( ply, ent ) + if ent:GetClass() == "stormfox_mapice" then return false end + if Props[ent] then return false end +end ) +hook.Add("CanPlayerUnfreeze", "StormFox2.MapIce.DisallowUnfreeze", function( ply, ent ) + if Props[ent] then + return false + end +end) +hook.Add("CanPlayerEnterVehicle", "StormFox2.MapIce.DisallowDriver", function(ply, veh) + if Props[veh] then return false end +end) + +hook.Add( "ShouldCollide", "StormFox2.MapIce.Collisions", function( ent1, ent2 ) + if not Props[ent1] and not Props[ent2] then return end + if not ent1:IsValid() or not ent2:IsValid() then return end + if ent1:GetClass() == "stormfox_mapice" or ent2:GetClass() == "stormfox_mapice" then + return false + end +end ) + +-- Freeze Ent +local v0 = Vector(0,0,0) +local function FreezeEntity( ent ) + if type(ent) == "Vehicle" then + ent:EnableEngine( false ) + local objects = ent:GetPhysicsObjectCount() + for i = 0, objects - 1 do + local physobject = ent:GetPhysicsObjectNum( i ) + physobject:EnableMotion( false ) + physobject:Sleep() + physobject:SetVelocity( v0 ) + end + ent:SetMoveType( 0 ) + local driver = ent:GetDriver() + if driver:IsValid() and driver.ExitVehicle then + driver:ExitVehicle() + end + end + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:SetVelocity( v0 ) + phys:EnableMotion( false ) + end +end + +-- Unfreeze Ent +local function UnFreezeEntity( ent, enableMotion, mVT ) + if type(ent) == "Vehicle" then + ent:EnableEngine( true ) + local objects = ent:GetPhysicsObjectCount() + ent:SetMoveType(mVT or 6) + for i = 0, objects - 1 do + local physobject = ent:GetPhysicsObjectNum( i ) + physobject:EnableMotion( true ) + physobject:Wake() + end + end + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:EnableMotion( enableMotion ) + phys:Wake() + end +end + +-- Freeze + Block +local function FreezeProp( ent ) + if type(ent) == "Player" then return end + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + Props[ent] = {phys:HasGameFlag(FVPHYSICS_NO_PLAYER_PICKUP), phys:IsMotionEnabled(), ent:GetMoveType()} + end + FreezeEntity( ent ) +end + +local function UnfreezeProp( ent ) + if not ent:IsValid() then return end + if not Props[ent] then return end + UnFreezeEntity( ent, Props[ent][2], Props[ent][3] ) + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + if not Props[ent][1] then + phys:ClearGameFlag( FVPHYSICS_NO_PLAYER_PICKUP ) + end + end + Props[ent] = nil +end + +function ENT:Initialize() + self.SpawnTime = CurTime() + if #ents.FindByClass("stormfox_mapice") > 1 or not STORMFOX_WATERMESHCOLLISON then + StormFox2.Warning("Can't spawn mapice, missing collision mesh!") + self:Remove() + return + end + self:SetCustomCollisionCheck( true ) + self:SetTrigger( true ) + + -- Freeze props and stuff + for i,e in ipairs( ents.GetAll() ) do + if not e:IsValid() then continue end + if e:WaterLevel() < 1 then continue end + if e:WaterLevel() < 3 then + FreezeProp(e) + else -- Water level 3. Check if under water. + local c = e:GetPos() + e:OBBCenter() + local s = math.max(e:OBBMaxs().x,e:OBBMaxs().y,e:OBBMaxs().z, -e:OBBMins().x, -e:OBBMins().y,-e:OBBMins().z ) + if bit.band( util.PointContents( c + Vector(0,0,s) ), CONTENTS_WATER ) ~= CONTENTS_WATER then -- Near surface + FreezeProp(e) + end + end + + end + self:CollisionRulesChanged() + + self:SetMaterial( "stormfox2/effects/ice_water" ) + self:SetPos(Vector(0,0,0)) + self:PhysicsInitMultiConvex(STORMFOX_WATERMESHCOLLISON) + --self:GetPhysicsObjectNum(0):SetMaterial('ice') People report this breaking ice sadly. + local phys = self:GetPhysicsObject() + self:SetMoveType( MOVETYPE_NONE ) + if ( IsValid( phys ) ) then + phys:EnableMotion( false ); + phys:AddGameFlag( FVPHYSICS_CONSTRAINT_STATIC ) + phys:SetMass(4000) + phys:EnableDrag(false) + end + self:EnableCustomCollisions( true ); + self:AddFlags( FL_WORLDBRUSH ) + self:SetSolid( SOLID_VPHYSICS ) + self:AddEFlags(EFL_IN_SKYBOX) + self:AddEFlags(EFL_NO_PHYSCANNON_INTERACTION) + self:SetKeyValue("gmod_allowphysgun", 0) + + self:AddEFlags( EFL_NO_DAMAGE_FORCES ) + + -- Try and unstuck players. + for i,v in ipairs( player.GetAll() ) do + if v:WaterLevel() == 1 then + v:SetPos(v:GetPos() + Vector(0,0,20)) + elseif v:WaterLevel() == 2 then + v:SetPos(v:GetPos() + Vector(0,0,40)) + end + end +end + +function ENT:StartTouch( ent ) + if CurTime() > self.SpawnTime + 0.1 then return end + if Props[ ent ] then return end + FreezeProp( ent ) +end + +function ENT:OnRemove() + for ent,_ in pairs( Props ) do + UnfreezeProp( ent ) + end + self:CollisionRulesChanged() +end + +-- Ice can't burn and players take dmg under ice +local nt = 0 +function ENT:Think() + if nt < CurTime() then + nt = CurTime() + 2 + local dmg = DamageInfo() + dmg:SetDamageType( DMG_DROWN ) + dmg:SetDamage(10) + dmg:SetAttacker( self ) + dmg:SetInflictor( Entity(0) ) + for i,v in ipairs( player.GetAll() ) do + if v:WaterLevel() < 2 then continue end + dmg:SetDamage((v:WaterLevel() - 1 ) * 5) + v:TakeDamageInfo(dmg) + end + end + if not self:IsOnFire() then return end + self:Extinguish() +end + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/shared.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/shared.lua new file mode 100644 index 0000000..b991eed --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_mapice/shared.lua @@ -0,0 +1,27 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" + +ENT.PrintName = "Freezing Water" +ENT.Author = "Nak" +ENT.Purpose = "Ice" +ENT.Instructions = "Is cold" +ENT.Category = "StormFox2" + +ENT.Editable = false +ENT.Spawnable = false + +function ENT:GravGunPunt() + return false +end +function ENT:GravGunPickupAllowed() + return false +end +function ENT:CanProperty() + return false +end +function ENT:CanTool( ply, tab, str ) + return str == "creator" +end +function ENT:CanDrive() + return false +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/cl_init.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/cl_init.lua new file mode 100644 index 0000000..a8dfe1c --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/cl_init.lua @@ -0,0 +1,85 @@ +include("shared.lua") + +function ENT:Initialize() + self.PixVis = util.GetPixelVisibleHandle() +end + +local ran,rand,max = math.random,math.Rand,math.max +local function createFlame(self) + if not self.Emitter or not IsValid(self.Emitter) then -- Recreate missing emitter + self.Emitter = ParticleEmitter(self:GetPos(),false) + end + local t = table.Random({"sprites/flamelet1","sprites/flamelet2","sprites/flamelet3"}) + local p = self.Emitter:Add(t,self:LocalToWorld(Vector(0,0, 7))) + p:SetDieTime(rand(0.5,0.9)) + p:SetStartSize(ran(1,2) / 2) + p:SetGravity(Vector(0,0,10)) + p:SetEndAlpha(0) + p:SetStartAlpha(200) + p:SetVelocity(Vector(0,0,1) + self:GetVelocity() / 3 ) + p:SetRoll(ran(360)) +end + +local broken_glass = Material("models/effects/vol_light001") +local on_mat = Material("stormfox2/models/oillamp_on") +function ENT:Draw() + render.SetColorModulation(1,1,1) -- Override entity color. + if not self:GetNWBool("broken",false) then + if self:IsOn() then + render.MaterialOverrideByIndex(1,on_mat) + end + self:DrawModel() + else + render.MaterialOverrideByIndex(0,broken_glass) + self:DrawModel() + render.MaterialOverrideByIndex() + end + render.MaterialOverrideByIndex() +end + +local function GetDis(ent) + if (ent.time_dis or 0) > CurTime() then return ent.time_dis_v or 0 end + ent.time_dis = CurTime() + 1 + if not LocalPlayer() then return 0 end + ent.time_dis_v = LocalPlayer():GetShootPos():DistToSqr(ent:GetPos()) + return ent.time_dis_v +end +function ENT:Think() + if GetDis(self) > 4500000 then return end + + if (self.nextFlame or 0) > CurTime() then return end + local ml = StormFox2.Map.GetLightRaw() + --if ml > 18 then return end + if self:WaterLevel() > 0 then return end + if self:GetNWBool("broken",false) then return end + if not self:IsOn() then return end + -- Wind + self.nextFlame = CurTime() + (ran(5,10) / 200) + createFlame(self) + if (self.t2 or 0) <= CurTime() then + self.t2 = CurTime() + rand(0.2,0.5) + local dlight = DynamicLight( self:EntIndex() ) + if ( dlight ) then + local h,s,l = ColorToHSL( self:GetColor() ) + l = math.Clamp(rand(l - 0.2, l + 0.2), 0, 1) + local c = HSVToColor(h,s,l) + dlight.pos = self:LocalToWorld(Vector(rand(-0.6,0.6), rand(-0.6,0.6), 10)) + dlight.r = c.r + dlight.g = c.g + dlight.b = c.b + dlight.brightness = 1 - ml / 200 + dlight.Decay = 0 + dlight.Size = 256 * 1.5 + dlight.DieTime = self.t2 + 0.5 + end + end +end + +function ENT:OnRemove( ) + if not IsValid(self.Emitter) then return end + self.Emitter:Finish() +end + +function ENT:DrawTranslucent() + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/init.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/init.lua new file mode 100644 index 0000000..d27acdd --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/init.lua @@ -0,0 +1,109 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel( "models/stormfox2/oillamp.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + --self:SetMoveType( MOVETYPE_NONE ) + + self.RenderMode = 1 + + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetColor(Color(255,185,0)) + self.on = false + self.lastT = SysTime() + 7 + self.hp = 10 + self.respawn = -1 + self:SetKeyValue("fademindist", 2000) + self:SetKeyValue("fademaxdist", 2000) + self:SetNWInt("on",1) + self:SetUseType( SIMPLE_USE ) + self.on = 1 +end + +function ENT:OnTakeDamage(cmd) + if self.hp <= 0 then return end + self.hp = (self.hp or 0) - cmd:GetDamage() + if self.hp > 0 then return end + if WireAddon then + Wire_TriggerOutput(self, "IsBroken", 1) + end + self:EmitSound("physics/glass/glass_largesheet_break1.wav") + self:SetNWBool("broken",true) + self.respawn = CurTime() + 30 + for i = 1,5 do + local effectdata = EffectData() + effectdata:SetOrigin( self:LocalToWorld(Vector(0,0,7)) ) + effectdata:SetNormal(self:GetAngles():Up()) + util.Effect("GlassImpact",effectdata) + end +end + +function ENT:SpawnFunction( ply, tr, ClassName ) + + if ( not tr.Hit ) then return end + + local SpawnPos = tr.HitPos + tr.HitNormal * 0.1 + + local ent = ents.Create( ClassName ) + local ang = (ply:GetPos() - SpawnPos):Angle().y + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ang,0)) + ent:Spawn() + ent:Activate() + return ent + +end + +-- Tells clients to render flame or not +function ENT:SetOn(boolean) + if self.on == boolean then return end + self:SetNWInt("on",boolean and 1 or 0) + self.on = boolean + if boolean then + self:EmitSound("ambient/fire/mtov_flame2.wav", 50, 110, 0.2) + else + self:EmitSound("ambient/atmosphere/hole_hit4.wav", 50, 50,0.4) + end +end + +local function sendMsg( act, msg ) + if type(act) ~= "Player" then return end + act:PrintMessage( HUD_PRINTTALK, "Lamp: " .. msg ) +end + +function ENT:Use( act ) + if self:WaterLevel() < 1 and not self:IsOn() then + self:SetOn(true) + sendMsg( act, "Always on" ) + elseif self:IsOn() then + self:SetOn(false) + sendMsg( act, "On at night" ) + end +end + +local function respawnThink( self ) + if self.respawn < 0 then return end + if self.respawn > CurTime() then return end + self.respawn = -1 + self.hp = 10 + self:SetNWBool("broken",false) +end + +function ENT:Think() + self:NextThink( CurTime() + 7 ) + -- Respawn + respawnThink(self) + if self:IsOn() then return end -- On 24h + local on = (StormFox2.Time.IsNight() and self:WaterLevel() < 1) and 1 or 0 + if self:GetNWInt("on") == on then return end + self:SetNWInt("on",on ) + if on == 1 then + self:EmitSound("ambient/fire/mtov_flame2.wav", 50, 110, 0.2) + else + self:EmitSound("ambient/atmosphere/hole_hit4.wav", 50, 50,0.4) + end + return true +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/shared.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/shared.lua new file mode 100644 index 0000000..993227b --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_oil_lamp/shared.lua @@ -0,0 +1,24 @@ +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Oil Lamp" +ENT.Author = "Nak" +ENT.Purpose = "An old lamp with a moden sun-sensor." +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.WireDebugName = "Oil Lamp" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false --true + +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:IsOn() + if SERVER then + return self.on + else + return self:GetNWInt("on",0) > 0 + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_streetlight_invisible.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_streetlight_invisible.lua new file mode 100644 index 0000000..801e413 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_streetlight_invisible.lua @@ -0,0 +1,379 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Invisible streetlight" +ENT.Author = "Nak" +ENT.Purpose = "Light area up" +ENT.Instructions = "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = false +ENT.AdminOnly = true + +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +local RENDER_DISTANCE = 3500 ^ 2 + +hook.Add("EntityKeyValue", "StormFox2.SLightinvis", function(ent, key, val) + if not IsValid( ent ) then return end + if ent:GetClass() ~= "stormfox_streetlight_invisible" then return end + key = key:lower() + if ent._launch then + if key == "lighttype" then + ent:SetLightType( tonumber( val ) or 0 ) + elseif key == "lightcolour" then + ent:SetLightColor( Vector( val ) or Vector(1,1,1) ) + elseif key == "lightcolor" then + ent:SetLightColor( Vector( val ) or Vector(1,1,1) ) + elseif key == "lightbrightness" then + ent:SetLightBrightness( tonumber( val ) or 0 ) + end + else + ent._launchoption = ent._launchoption or {} + ent._launchoption[key] = val + end +end) + +function ENT:Initialize() + if SERVER then + self._launch = true + self:SetModel( "models/hunter/blocks/cube025x025x025.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_VPHYSICS ) + + self:AddFlags( FL_WORLDBRUSH ) + self:AddEFlags(EFL_NO_PHYSCANNON_INTERACTION) + self:SetKeyValue("gmod_allowphysgun", 0) + self:AddEFlags( EFL_NO_DAMAGE_FORCES ) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + + self:DrawShadow(false) + self:SetLightColor(Vector(1,1,1)) + self:SetLightBrightness(1) + + if self._launchoption then + if self._launchoption["lighttype"] then + self:SetLightType( tonumber( self._launchoption["lighttype"] ) ) + end + local col = self._launchoption["lightcolor"] or self._launchoption["lightcolour"] + if col then + self:SetLightColor( Vector( col ) or Vector(1,1,1) ) + end + if self._launchoption["lightbrightness"] then + self:SetLightBrightness( tonumber( self._launchoption["lightbrightness"] ) ) + end + end + end +end + +hook.Add( "PhysgunPickup", "StormFox2.StreetLight.DisallowPickup", function( ply, ent ) + if ent and ent:GetClass() == "stormfox_streetlight_invisible" then return false end +end ) +hook.Add("CanPlayerUnfreeze", "StormFox2.StreetLight.DisallowUnfreeze", function( ply, ent ) + if ent and ent:GetClass() == "stormfox_streetlight_invisible" then return false end +end) + +local INVALID = 0 +local POINTLIGHT= 1 +local SPOTLIGHT = 2 +local FAKESPOT = 3 +local options = { + ["2D Sprite"] = POINTLIGHT, + ["2D Beam"] = FAKESPOT, + ["ProjectedTexture"] = SPOTLIGHT +} +function ENT:SetupDataTables() + self:NetworkVar( "Int", 0, "LightType", { KeyName = "LightType", Edit = { type = "Combo", values = options } }) + self:NetworkVar( "Vector", 0, "LightColor",{ KeyName = "LightColor", Edit = { type = "VectorColor" } } ) + self:NetworkVar( "Float", 0, "LightBrightness",{ KeyName = "Brightness", Edit = { type = "Float", min = 0, max = 10 } } ) +end + +function ENT:CanProperty(_, str) + if str == "skin" then return false + elseif str == "drive" then return false + elseif str == "collision" then return false + elseif str == "persist" then return true + end + return true +end + +if CLIENT then + local base_mat = Material("stormfox2/entities/streetlight_invis") + local base_mat2 = Material("stormfox2/entities/streetlight_invis_point") + local base_mat3 = Material("stormfox2/entities/streetlight_invis_beam") + local equip_tool = false + local d_tab = {} + hook.Add("PostRender", "StormFox2.StreetLights.Update", function() + d_tab = {} + equip_tool = false + local wep = LocalPlayer():GetActiveWeapon() + if not wep or not IsValid(wep) then return end + if wep:GetClass() ~= "sf2_tool" then return end + equip_tool = true + d_tab = ents.FindByClass("stormfox_streetlight_invisible") + end) + hook.Add("PreDrawHalos", "StormFox2.StreetLights.Halo", function() + if not equip_tool then return end + if not d_tab or #d_tab < 1 then return end + halo.Add( d_tab, color_white, 2, 2, 1, true,true ) + end) + + function ENT:DrawSelfCheck() + if not equip_tool then return end + render.SetMaterial(base_mat) + render.DrawBox(self:GetPos(), self:GetAngles(), self:OBBMins(), self:OBBMaxs(), color_white) + if self:GetLightType() == POINTLIGHT then + render.SetMaterial(base_mat2) + else + render.SetMaterial(base_mat3) + end + local s = self:OBBMaxs().x + local f = self:GetAngles():Up() + render.DrawQuadEasy(self:GetPos() + -f * s * 1.005, -f, s * 2, s * 2, color_white, 0) + end + + -- We use a variable to tell if we should render any lights nearby. Since it can be is costly. + local m_render = false + local sorter = function(a,b) + return a[2] < b[2] + end + + local Max_PointLight = 10 + local Max_SpotLight = 4 + local Max_ShadowLight = 2 + local Max_Fake = 40 + + _STORMFOX2_PTLIGHT = _STORMFOX2_PTLIGHT or { + ents = {}, + used = 0 + } + -- Handles and returns a project texture. + -- Returns the projected texture and ID + local function RequestLight() + _STORMFOX2_PTLIGHT.used = _STORMFOX2_PTLIGHT.used + 1 + if _STORMFOX2_PTLIGHT.used > Max_SpotLight then return end -- Max 4 + local pfent = _STORMFOX2_PTLIGHT.ents[_STORMFOX2_PTLIGHT.used] + if IsValid(pfent) then + pfent:SetEnableShadows( _STORMFOX2_PTLIGHT.used < Max_ShadowLight ) + return pfent, _STORMFOX2_PTLIGHT.used + end + _STORMFOX2_PTLIGHT.ents[_STORMFOX2_PTLIGHT.used] = ProjectedTexture() + pfent = _STORMFOX2_PTLIGHT.ents[_STORMFOX2_PTLIGHT.used] + if not IsValid(fent) then return end + pfent:SetTexture( "effects/flashlight001" ) + pfent:SetBrightness(2) + pfent:SetEnableShadows( _STORMFOX2_PTLIGHT.used < Max_ShadowLight ) + return pfent, _STORMFOX2_PTLIGHT.used + end + local cost = {} + local row = 0 + hook.Add("PostDrawTranslucentRenderables", "StormFox2.Streetlights", function(a, b, c) + if a or b or c or not (m_render or _STORMFOX2_PTLIGHT.ents[1]) then return end + -- Check the light-range + local b_on = StormFox2.Map.GetLightRaw() < 20 + if row < 6 and b_on then -- On + row = math.min(row + FrameTime() * 0.75, 6) + elseif row > 0 and not b_on then + row = math.max(row - FrameTime() * 0.95, 0) + end + if row <= 0 and not _STORMFOX2_PTLIGHT.ents[1] then return end -- No lights are on. No need to calculate things + local view = StormFox2.util.GetCalcView() + local rp = view.pos + view.ang:Forward() * 250 + -- Sort the lights. So we get the closest first + local t = {} + local nID = math.Round(row, 0) + for k, v in ipairs(ents.FindByClass("stormfox_streetlight_invisible")) do + if v:EntIndex() % 6 >= row then continue end + table.insert(t, {v,math.max(0, v:GetPos():DistToSqr(rp) - (v._tDis2 or 0))}) + end + table.sort(t, sorter) + -- Setup cost calculation + cost[1] = Max_PointLight + cost[2] = Max_SpotLight + cost[3] = Max_Fake + local vAng = view.ang + local vNorm = vAng:Forward() + _STORMFOX2_PTLIGHT.used = 0 + -- Draw lights + local fD = math.Clamp((1 - (StormFox2.Fog.GetDistance() / 6000)),0, 1) + for _, tab in ipairs(t) do + local n = 1 - (tab[2] / RENDER_DISTANCE) + if n <= 0 then continue end + tab[1]:DrawLight(n * (0.85 + 0.45 * fD),view.pos,vAng,vNorm) + end + for i = 4, 1, -1 do + if not IsValid(_STORMFOX2_PTLIGHT.ents[i]) then continue end + if _STORMFOX2_PTLIGHT.used < i then + _STORMFOX2_PTLIGHT.ents[i]:Remove() + _STORMFOX2_PTLIGHT.ents[i] = nil + else + break + end + end + m_render = false + end) + function ENT:DrawTranslucent() + if not m_render then -- Wait until some lights is in render + local rp = StormFox2.util.RenderPos() + local d = rp:DistToSqr(self:GetPos()) + if d > RENDER_DISTANCE then return end + end + self:DrawSelfCheck() -- Draw if tool is out + m_render = true + end + local m_lamp = Material("stormfox2/effects/light_beam") + local col = Color(255,255,255,155) + function ENT:TraceDown() + if self._tPos then return self._tPos, self._tDis, self._norm end + local norm = -self:GetAngles():Up() + local tr = util.TraceLine({ + start = self:GetPos() + norm * 100, + endpos = self:GetPos() + norm * 1000, + filter = self, + mask = MASK_SOLID_BRUSHONLY + }) + self._tPos = tr.HitPos or self:GetPos() + self._tDis = self._tPos:Distance(self:GetPos()) + self._tDis2 = self._tDis^2 + self._norm = norm + return self._tPos, self._tDis, norm + end + + local m_spot = Material('stormfox2/effects/spotlight') + local m_flash = Material('stormfox2/effects/flashlight_a') + function ENT:DrawPoint(dist, add, size, ignoreB) + size = (size or 80) * (not ignoreB and self:GetLightBrightness() or 1) + local v = self:GetLightColor() + col.a = 255 * dist + col.r = v.x * 255 + col.g = v.y * 255 + col.b = v.z * 255 + + render.SetMaterial(m_spot) + if add then + render.DrawSprite(self:GetPos() + add, size, size, col) + else + render.DrawSprite(self:GetPos(), size, size, col) + end + end + local c_flash = Color(255,255,255,255) + function ENT:DrawSpot(dist, fake) + dist = (dist - .5) * 2 + if dist <= 0 then return end + if not fake then + local pEnt, id = RequestLight() + if not IsValid(pEnt) then return end + local v = self:GetLightColor() + local bright = math.Round(dist * 2, 1) * self:GetLightBrightness() + if self._lastB == bright and (self._lastID or -1) == id and self._r == v.x and self._g == v.y and self._b == v.z then return end + self._lastB = bright + self._r = v.x + self._g = v.y + self._b = v.z + pEnt:SetBrightness(bright) + local pos, dis, norm = self:TraceDown() + local up = self:GetAngles():Up() + local aDown = -up:Angle() + pEnt:SetPos(self:GetPos() + norm * 20) + pEnt:SetAngles(norm:Angle()) + pEnt:SetFarZ( dis * 1.2 ) + pEnt:SetTexture( "effects/flashlight001" ) + pEnt:SetColor(Color(v.x * 255, v.y * 255, v.z * 255)) + pEnt:Update() + self._lastID = id + else + local pos, dis = self:TraceDown() + local up = self:GetAngles():Up() + c_flash.a = math.min(255, 15 * dist * self:GetLightBrightness()) + local v = self:GetLightColor() + if c_flash.a <= 0 then return end + local s = (dis - 20) * 1.6 + render.SetMaterial(m_flash) + render.DrawQuadEasy(pos + up, up, s, s, c_flash, 0) + end + end + -- Dist goes from 1 to 0. Where 0 is RENDER_DISTANCE units away. + function ENT:DrawBeam(dist, vPos) + local vNorm2 = (vPos - self:GetPos()):Angle():Forward() + local pos, dis = self:TraceDown() + local up = self:GetAngles():Up() + local dot = up:Dot(vNorm2) + local abs_dot = math.abs(dot) + if abs_dot < 0.9 then + local a = dist * 255 * math.Clamp(0.9 - abs_dot,0,1) + col.a = math.Clamp(a * self:GetLightBrightness(), 0, 255) + local v = self:GetLightColor() + col.r = v.x * 255 + col.g = v.y * 255 + col.b = v.z * 255 + render.SetMaterial(m_lamp) + --local m = (pos - self:GetPos()):GetNormalized() + --render.StartBeam( 3 ) + -- render.AddBeam( self:GetPos(), dis * 0.8, 0, col ) + -- render.AddBeam( self:GetPos() + m * dis / 8,dis * 0.8, 0.1, col ) + -- render.AddBeam( pos , dis * 0.8, 0.99, col ) + --render.EndBeam() + render.DrawBeam(self:GetPos(),pos , dis * 0.8, 0, 0.99, col) + end + if dot < 0 then + self:DrawPoint(dist * -dot, vNorm2 * 15 - up * 2, dis / 3, true) + end + end + function ENT:DrawLight(dist,vPos,vAng,vNorm) + dist = math.min(dist, 1) + local lType = self:GetLightType() + if lType == 0 then return end -- Invalid + local fake = false + if cost[lType] <= 0 then-- Ignore + if lType == SPOTLIGHT then + fake = true + end + end + cost[lType] = cost[lType] - 1 + if lType == POINTLIGHT then + self:DrawPoint(dist) + elseif lType == FAKESPOT then + self:DrawBeam(dist,vPos,vNorm) + elseif lType == SPOTLIGHT then + self:DrawBeam(dist,vPos,vNorm) + self:DrawSpot(dist, fake) + end + end +else -- Save + local file_location = "stormfox2/streetlights/" .. game.GetMap() .. ".json" + hook.Add( "ShutDown", "StormFox2.Streetlights.Save", function() + local tab = {} + for k, ent in ipairs(ents.FindByClass("stormfox_streetlight_invisible")) do + if ent:CreatedByMap() then continue end + table.insert(tab, { + ent:GetLightType(), + ent:GetPos(), + ent:GetAngles(), + ent:GetLightColor(), + ent:GetLightBrightness() + }) + end + local out = util.TableToJSON( tab ) + StormFox2.FileWrite(file_location, out) + end) + hook.Add( "InitPostEntity", "StormFox2.Streetlights.Load", function() + local fil = file.Read(file_location, "DATA" ) + if not fil then return end + local tab = util.JSONToTable( fil ) + if ( !tab ) then return end + for k,v in ipairs(tab) do + local ent = ents.Create("stormfox_streetlight_invisible") + ent:SetLightType(v[1]) + ent:SetPos(v[2]) + ent:SetAngles(v[3]) + ent:Spawn() + if not v[4] then v[4] = Vector(1,1,1) end + ent:SetLightColor(Vector(v[4].x, v[4].y, v[4].z)) + ent:SetLightBrightness(v[5] or 1) + end + end ) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/entities/stormfox_weekweather.lua b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_weekweather.lua new file mode 100644 index 0000000..7922632 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/entities/stormfox_weekweather.lua @@ -0,0 +1,106 @@ +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" + +ENT.PrintName = "Weekly display" +ENT.Author = "Nak" +ENT.Purpose = "Weekly weatherdisplay" +ENT.Instructions= "Place it somewhere" +ENT.Category = "StormFox2" + +ENT.Editable = true +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:Initialize() + if SERVER then + self:SetModel( "models/props_phx/rt_screen.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) -- Make us work with physics, + self:SetMoveType( MOVETYPE_VPHYSICS ) -- after all, gmod is a physics + self:SetSolid( SOLID_VPHYSICS ) -- Toolbox + self:SetUseType( SIMPLE_USE ) + self:SetNWBool("hourly", true ) + end +end + +if SERVER then + function ENT:Use() + self:SetNWBool("hourly", not self:GetNWBool("hourly", true ) ) + self:EmitSound("buttons/button14.wav") + end + function ENT:SpawnFunction( ply, tr, ClassName ) + if ( !tr.Hit ) then return end + local SpawnPos = tr.HitPos + tr.HitNormal * 16 + local ent = ents.Create( ClassName ) + ent:SetPos( SpawnPos ) + ent:SetAngles(Angle(0,ply:EyeAngles().y + 180,0)) + ent:Spawn() + ent:Activate() + return ent + end +else + local function niceName(sName) + sName = string.Replace(sName, "_", " ") + local str = "" + for s in string.gmatch(sName, "[^%s]+") do + str = str .. string.upper(s[1]) .. string.sub(s, 2) .. " " + end + return string.TrimRight(str, " ") + end + local mat = Material("vgui/loading-rotate") + local function DrawDisabled(w,h,s) + surface.SetMaterial(mat) + surface.SetDrawColor(color_white) + surface.DrawTexturedRectRotated(w / 2, h / 2, 50, 50, SysTime() * 300 % 360) + surface.SetFont("SF_Menu_H2") + local t = niceName(language.GetPhrase("addons.preset_disabled")) .. " " .. s + local tw,th = surface.GetTextSize( t ) + surface.SetTextColor(color_white) + surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2 - 40) + surface.DrawText(t) + end + local function DrawLoading(w,h) + surface.SetMaterial(mat) + surface.SetDrawColor(color_white) + surface.DrawTexturedRectRotated(w / 2, h / 2, 50, 50, SysTime() * 300 % 360) + surface.SetFont("SF_Menu_H2") + local t = niceName(language.GetPhrase("loading")) + local tw,th = surface.GetTextSize( t ) + surface.SetTextColor(color_white) + surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2 - 40) + surface.DrawText(t .. " " .. string.rep(".", CurTime() % 4)) + end + local w,h = 564,325 + local sc_mat = Material("stormfox2/entities/weather_display") + local sc_RT = GetRenderTarget( "stormfox2_weekweather", w,h ) + local l_update = 0 + local function UpdateRTTexture() + if l_update > CurTime() then return end + l_update = CurTime() + 0.5 + sc_mat:SetTexture("$basetexture", sc_RT) + render.PushRenderTarget( sc_RT ) + render.Clear(0, 0, 0, 255, true, true) + cam.Start2D() + StormFox2.WeatherGen.DrawForecast(w,h, true) + cam.End2D() + render.PopRenderTarget() + end + local r_update = false + function ENT:Draw() + render.MaterialOverrideByIndex(1, sc_mat) + self:DrawModel() + if self:GetPos():DistToSqr(LocalPlayer():GetPos()) > 350000 then return end + render.MaterialOverrideByIndex() + r_update = true + end + -- Updating it inside ENT:Draw causes problems + hook.Add("PostDrawOpaqueRenderables", "StormFox2.Entity.weekweather", function() + if not r_update then return end + r_update = false + UpdateRTTexture() + end) +end + diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_clientquality.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_clientquality.lua new file mode 100644 index 0000000..bac482f --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_clientquality.lua @@ -0,0 +1,53 @@ +--[[------------------------------------------------------------------------- +This scripts job is to sort out the computers and potatoes. +---------------------------------------------------------------------------]] +StormFox2.Client = StormFox2.Client or {} +StormFox2.Setting.AddCL("quality_ultra",false) +StormFox2.Setting.AddCL("quality_target",80,nil,nil, 0, 300) + +local conDetect = 1 +local t_num = {1, 1, 1, 1, 1, 1} +local i = 1 +local q_num = 1 +-- Calculate the avageFPS for the client and make a value we can use. + local bi,buffer = 0,0 + local avagefps = 1 / RealFrameTime() + timer.Create("StormFox2.Client.PotatoSupport",0.25,0,function() + if not system.HasFocus() then -- The player tabbed out. + bi,buffer = 0,0 + return + end + if bi < 10 then + buffer = buffer + 1 / RealFrameTime() + bi = bi + 1 + else + avagefps = buffer / bi + bi,buffer = 0,0 + local q = StormFox2.Setting.GetCache("quality_ultra",false) + local delta_fps = avagefps - StormFox2.Setting.GetCache("quality_target",80) + local delta = math.Clamp(delta_fps / 8,-3,3) + conDetect = math.Clamp(math.Round(conDetect + delta, 1),0,q and 20 or 7) + table.insert(t_num, conDetect) + table.remove(t_num, 1) + + local a = 0 + for _,v in ipairs( t_num ) do + a = a + v + end + q_num = (q_num + (a / #t_num)) / 2 + end + end) +--[[----------------------------------------------------------------- +Returns a number based on the clients FPS. +7 is the max without the user enabling 'sf_quality_ultra', where it then goes up to 20. +---------------------------------------------------------------------------]] +function StormFox2.Client.GetQualityNumber() + if not system.HasFocus() then + return 1, 1 / RealFrameTime() + end + -- Players have complained not seeing the particles when their FPS is low + --if game.SinglePlayer() then I've now had multiplayer complaints. + q_num = math.max(0.5, q_num) + --end + return q_num, avagefps +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_envioment.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_envioment.lua new file mode 100644 index 0000000..92d40a5 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_envioment.lua @@ -0,0 +1,1250 @@ +--[[------------------------------------------------------------------------- +Handles enviroments by scanning the mapfile. +---------------------------------------------------------------------------]] +StormFox2.Environment = StormFox2.Environment or {} +local INVALID_VERTS = 0 +local WATER_VERTS = 1 +local GLASS_VERTS = 2 +local GLASS_ROOF_VERTS = 3 +local METAL_ROOF_VERTS = 4 + +StormFox2.Setting.AddCL("window_enable",render.SupportsPixelShaders_2_0()) +StormFox2.Setting.AddCL("window_distance",800, nil, nil, 0, 4000) +StormFox2.Setting.AddSV("enable_ice",not game.IsDedicated()) +StormFox2.Setting.AddSV("enable_wateroverlay",true, nil, "Effects") + +--[[------------------------------------------------------------------------- +Adds a window model for SF to use. +---------------------------------------------------------------------------]] +local mdl = {} +--[[------------------------------------------------------------------------- +Adds a window model to the list of valid window-models. +These windows must have glass in them. +---------------------------------------------------------------------------]] +function StormFox2.Environment.AddWindowModel(sModel,vMin, vMax) + if not vMin or not vMax then + vMin, vMax = StormFox2.util.GetModelSize(sModel) + end + mdl[sModel] = {vMin, vMax} +end +local function GetWindowModel(sModel) + if mdl[sModel] then return mdl[sModel][1],mdl[sModel][2] end + local min,max = StormFox2.util.GetModelSize(sModel) + mdl[sModel] = {min,max} + if math.abs(max.x - min.x) < math.abs(max.y - min.y) then + local x = min.x + (max.x - min.x) / 2 + mdl[sModel][1].x = x + mdl[sModel][2].x = x + else + local y = min.y + (max.y - min.y) / 2 + mdl[sModel][1].y = y + mdl[sModel][2].y = y + end + return mdl[sModel][1],mdl[sModel][2] +end +local function IsWinModel(sModel) + return mdl[sModel] and true +end + +-- CS:GO and l4d2 seems to be the only one with window-props +if IsMounted("csgo") or IsMounted("l4d2") then + StormFox2.Environment.AddWindowModel("models/props/cs_militia/militiawindow02_breakable.mdl") + StormFox2.Environment.AddWindowModel("models/props/cs_militia/wndw01.mdl") + StormFox2.Environment.AddWindowModel("models/props_windows/window_farmhouse_big.mdl") + StormFox2.Environment.AddWindowModel("models/props_windows/window_farmhouse_small.mdl",Vector(-26,-0.8,-31.8),Vector(26,-0.8,34)) + StormFox2.Environment.AddWindowModel("models/props_windows/window_industrial.mdl") + StormFox2.Environment.AddWindowModel("models/props_windows/window_urban_sash_48_88_full.mdl",Vector(-21.9,0,2),Vector(21.9,0,85)) + StormFox2.Environment.AddWindowModel("models/props_windows/window_urban_sash_48_88_full.mdl",Vector(-21.9,0,2),Vector(21.9,0,85)) + StormFox2.Environment.AddWindowModel("models/props/de_house/window_48x36.mdl", Vector(-22.2,4.8,-16),Vector(21.8,4.8,16)) -- ~4 sides + StormFox2.Environment.AddWindowModel("models/props/de_house/window_54x44.mdl", Vector(-25,4.8,7), Vector(25,4.8,47)) + StormFox2.Environment.AddWindowModel("models/props/de_house/window_54x76.mdl", Vector(-25,4.8,7.9),Vector(25,4.8,80.1)) + StormFox2.Environment.AddWindowModel("models/props/de_inferno/windowbreakable_02.mdl") + StormFox2.Environment.AddWindowModel("models/props/cs_militia/militiawindow02_breakable.mdl", Vector(-55, 0, -35.8), Vector(56, 0, 35.8)) +end + +--[[------------------------------------------------------------------------- +Localize +---------------------------------------------------------------------------]] +local round = math.Round +local util_TraceLine = util.TraceLine +local table_sort = table.sort +local LocalPlayer = LocalPlayer +--[[------------------------------------------------------------------------- +Make a few SurfaceInfo functions. +---------------------------------------------------------------------------]] + local meta = FindMetaTable("SurfaceInfo") + -- Support caching stuff + local surf_caching = {} + meta.__index = function(a,b) + return meta[b] or surf_caching[a] and surf_caching[a][b] + end + meta.__newindex = function(a,b,c) + if not surf_caching[a] then surf_caching[a] = {} end + surf_caching[a][b] = c + end + hook.Add("EntityRemoved", "ClearSurfaceInfo", function(ent) + for _,surf in ipairs(ent:GetBrushSurfaces() or {}) do + surf_caching[surf] = nil + end + end) + function meta:IsValid( ) + if self.b_invalid ~= nil then return self.b_invalid end + local b = #(self:GetVertices()) > 0 + self.b_invalid = b + return self.b_invalid + end + function meta:GetVerticesNoParallel( ) + if not self:IsValid() then return {} end + if self.v_vertNP then return table.Copy(self.v_vertNP) end + self.v_vertNP = {} + local verts = self:GetVertices() + for i,cv in ipairs(verts) do + local pv,nv = verts[i - 1] or verts[#verts], verts[i + 1] or verts[1] + local cP = ( cv - pv ):Cross( nv - pv ) + if cP.x == 0 and cP.y == 0 and cP.z == 0 then continue end -- parallel vector. + table.insert(self.v_vertNP, cv) + end + return table.Copy(self.v_vertNP) + end + function meta:GetCenter( ) + if not self:IsValid() then return end + if self.v_cent then return self.v_cent end + local verts = self:GetVertices() + if #verts < 2 then + self.v_cent = verts[1] + return self.v_cent + end + local vmax,vmin = verts[1],verts[1] + for i = 2,#verts do + vmax[1] = math.max(vmax[1],verts[i][1]) + vmax[2] = math.max(vmax[2],verts[i][2]) + vmax[3] = math.max(vmax[3],verts[i][3]) + vmin[1] = math.min(vmin[1],verts[i][1]) + vmin[2] = math.min(vmin[2],verts[i][2]) + vmin[3] = math.min(vmin[3],verts[i][3]) + end + self.v_cent = vmin + (vmax - vmin) / 2 + return self.v_cent + end + function meta:GetNormal( ) + if not self:IsValid() then return end + if self.v_norm then return self.v_norm end + local p = self:GetVertices() + if #p < 3 then return end -- Invalid brush. (Yes this happens) + local c = p[1] + local s = Vector(0,0,0) + for i = 2,#p do + s = s + ( p[i] - c ):Cross( (p[i + 1] or p[1]) - c ) + if s.x ~= 0 and s.y ~= 0 and s.z ~= 0 then -- Check if this isn't a parallel vector. + break -- Got a valid norm + end + end + self.v_norm = s:GetNormalized() + return self.v_norm + end + function meta:GetAngles( ) + if not self:IsValid() then return end + if self.a_ang then return self.a_ang end + self.a_ang = self:GetNormal():Angle() + return self.a_ang + end + function meta:GetPerimeter( ) + if not self:IsValid() then return end + if self.n_peri then return self.n_peri end + local p = self:GetVertices() + local n = 0 + for i = 1,#p do + n = n + p[i]:Distance(p[i + 1] or p[1]) + end + self.n_peri = n + return self.n_peri + end + function meta:GetArea( ) + if not self:IsValid() then return end + if self.n_area then return self.n_area end + local p = self:GetVertices() + local n = #p + if n < 3 then -- Invalid shape + self.n_area = 0 + return 0 + --elseif n == 3 then -- Triangle, but cost more? + -- local a,b,c = p[1]:Distance(p[2]),p[2]:Distance(p[3]),p[3]:Distance(p[1]) + -- local s = (a + b + c) / 2 + -- t_t[self] = sqrt( s * (s - a) * (s - b) * (s - c) ) + -- return t_t[self] + else -- Any shape + local a = Vector(0,0,0) + for i,pc in ipairs(p) do + local pn = p[i + 1] or p[1] + a = a + pc:Cross(pn) + end + a = a / 2 + self.n_area = a:Distance(Vector(0,0,0)) + return self.n_area + end + end +--[[------------------------------------------------------------------------- +Make some adv SurfaceInfo functions. +---------------------------------------------------------------------------]] + function meta:GetUVVerts() -- Creates UV-data out from the shape. + if not self:IsValid() then return end + if self.t_uv then return table.Copy(self.t_uv) end + local t = self:GetVerticesNoParallel() + local a = self:GetNormal():Angle() + local c = self:GetCenter() + local vmin,vmax + for i,v in ipairs(t) do + t[i] = (t[i] - c) + t[i]:Rotate(a) + if not vmin then + vmin = Vector(t[i].x,t[i].y,t[i].z) + vmax = Vector(t[i].x,t[i].y,t[i].z) + else + for ii = 1,3 do + vmin[ii] = math.min(vmin[ii],t[i][ii]) + vmax[ii] = math.max(vmax[ii],t[i][ii]) + end + end + end + local y_r = vmax.z - vmin.z + local x_r,x_r2 = vmax.x - vmin.x,vmax.y - vmin.y + local min_x = vmin.x + local i2 = 1 + if x_r2 > x_r then + x_r = x_r2 + i2 = 2 + min_x = vmin.y + end + local new_t = {} + for i = 1,#t do + table.insert(new_t, {u = (t[i][i2] - min_x) / x_r,v = (t[i].z - vmin.z) / y_r}) + end + self.t_uv = new_t + return table.Copy(self.t_uv) + end + function meta:GetMesh() -- Generates a mesh-table for the surfaceinfo. + if not self:IsValid() then return end + if self.t_mesh then return table.Copy(self.t_mesh) end + local verts = self:GetVerticesNoParallel() + if #verts < 3 then + self.b_invalid = false + return + end + local n = self:GetNormal() + -- Calc the height and width + local h_max,h_min = verts[1].z,verts[1].z + for i = 2,#verts do + local h = verts[i].z + h_max = math.max(h_max,h) + h_min = math.min(h_min,h) + end + local uvt = self:GetUVVerts() + local t = {} + for i = 1,3 do + table.insert(t, {pos = verts[i], u = uvt[i].u,v = uvt[i].v, normal = n}) + end + for i = 4,#verts do + table.insert(t, {pos = verts[1], u = uvt[1].u,v = uvt[1].v, normal = n}) + table.insert(t, {pos = verts[i - 1], u = uvt[i - 1].u,v = uvt[i - 1].v, normal = n}) + table.insert(t, {pos = verts[i], u = uvt[i].u,v = uvt[i].v, normal = n}) + end + self.t_mesh = t + return table.Copy(self.t_mesh) + end + function meta:GetMinSide() + if not self:IsValid() then return end + if self.n_midi then return self.n_midi end + local mi,ma + local p = self:GetVertices() + for i = 1,#p do + if not mi then + mi = p[i]:Distance(p[i + 1] or p[1]) + ma = mi + else + mi = math.min(mi,p[i]:Distance(p[i + 1] or p[1])) + ma = math.max(ma,p[i]:Distance(p[i + 1] or p[1])) + end + end + self.n_midi = mi + self.n_madi = ma + return mi + end + function meta:GetMaxSide() + if not self:IsValid() then return end + if self.n_madi then return self.n_madi end + self:GetMinSide() + return self.n_madi + end +--[[------------------------------------------------------------------------- +Generate meshes and env-points out from the map-data. +---------------------------------------------------------------------------]] + -- Local functions + local round = math.Round + local function IsRoofAngle( Ang ) + return Ang:Forward():Dot(Vector(0,0,1)) > 0.3 + end + local function DecodeFlag(num) + local nocull = false + local transparent = false + for i = 31,1,-1 do + local n = 2 ^ (i - 1) + if num >= n then + if n == 8192 then -- MATERIAL_VAR_NOCULL + nocull = true + elseif n == 2097152 then -- MATERIAL_VAR_TRANSLUCENT + transparent = true + elseif n == 4 then -- MATERIAL_VAR_NO_DRAW + return false + end + num = num - n + end + end + return nocull,transparent + end + local function IsMaterialEmpty( t ) + return t.HitTexture == "TOOLS/TOOLSINVISIBLE" or t.HitTexture == "**empty**" or t.HitTexture == "TOOLS/TOOLSNODRAW" + end + local up = Vector( 0, 0, -16000) + local function PlyTrace( ent ) + local m,ma = ent:OBBMins(), ent:OBBMaxs() + m.z = 0 + ma.z = 5 + local from = ent:GetPos() + local lastResult + for i = 1, 3 do + local tr = util.TraceHull({ + start = from, + endpos = from + up, + filter = ent, + mins = m, + maxs = ma, + collisiongroup = COLLISION_GROUP_PLAYER + }) + if not IsMaterialEmpty(tr) then return tr end + from = tr.HitPos + tr.Normal + lastResult = lastResult or tr + end + return lastResult + end + local nocull = false + local function IsTransparent( iMat ) + local k = iMat:GetKeyValues() + local flags = k["$flags"] + -- Decode flags + local b,transparent = DecodeFlag(flags) + nocull = b + return transparent + end + local function fuzzeVectors(t,n) + local t2 = {} + for i,v in ipairs(t) do + t2[i] = v + t2[i].x = math.Round(t2[i].x, n or 1) + t2[i].y = math.Round(t2[i].y, n or 1) + t2[i].z = math.Round(t2[i].z, n or 1) + end + return t2 + end + local function FindMatchPointer(data_tab,var) + for var2,t in pairs(data_tab) do + if var.x == var2.x and var.y == var2.y and var.z == var2.z then + return var2 + end + end + return + end + local function VertsMatch(t1,t2) -- Returns true if 2 or more vectors match match. + local n = 0 + for i = 1,#t1 do + if table.HasValue(t2, t1[i]) then n = n + 1 end + if n >= 2 then return true end + end + return false + end + local t = {["mask"] = MASK_SOLID_BRUSHONLY} + local function MatchTrace(from,to,norm, filter) + if norm and (to - from):Dot(norm) > 0 then return false end -- Check if the window "faces" away from the player + t.start = from + t.endpos = norm and to + norm * 10 or to + t.filter = filter or StormFox2.util.ViewEntity() + local tr = util_TraceLine( t ) + return not tr.Hit, tr + end + local function EasyTrace(from,to, mask) + t.start = from + t.endpos = to + t.filter = StormFox2.util.ViewEntity() + t.mask = mask or MASK_SOLID_BRUSHONLY + return not util_TraceLine( t ).Hit + end + local function AdvTrace(from,to, mask) + t.start = from + t.endpos = to + t.filter = StormFox2.util.ViewEntity() + t.mask = mask or MASK_SOLID_BRUSHONLY + return util_TraceLine( t ) + end + local function UnderSky(pos, mask) -- Checks if a position is under the sky. + t.start = pos + t.endpos = pos + Vector(0,0,262144) + t.filter = StormFox2.util.ViewEntity() + t.mask = mask + local r = util_TraceLine( t ) + return r.HitSky and r.HitPos + end + local t2 = {["mask"] = MASK_SHOT} + local function SkyLine(pos,norm, n, nMaxDistance, filter, sky_mask) -- Shoots tracers out and returns a pos, if the sky is above. + t2.start = pos + norm + t2.endpos = pos + norm * (nMaxDistance or 262144) + t2.filter = filter or StormFox2.util.ViewEntity() + t2.mask = MASK_SHOT + local r = util_TraceLine( t2 ) + if r.HitSky then return r.HitPos end + local d = r.HitPos:Distance(pos) + if d < 50 then + return UnderSky(pos, sky_mask or MASK_SOLID_BRUSHONLY) and pos + end + -- Check from start to center + local i = math.min(d / 50, n) - 2 + local dis = d / 2 / i + for i2 = 1,i do + local v = pos + norm * (dis * i2) + local p = UnderSky(v, sky_mask or MASK_SOLID_BRUSHONLY) + if p then return v end + end + -- Cehck center and hit + for i = 1,2 do + local v = pos + norm * (d / 2.1 * i) + local p = UnderSky(v, sky_mask or MASK_SOLID_BRUSHONLY) + if p then return v end + end + end + local function UnderSky2(pos) -- Checks if a position is under the sky. But for entities. + t2.start = pos + t2.endpos = pos + Vector(0,0,262144) + t2.filter = LocalPlayer() + local r = util_TraceLine( t2 ) + return r.HitSky and r.HitPos + end + local m_C = {} -- Cache the materials basetexture + local function IsWindowMaterial( mat ) + if m_C[mat] ~= nil then return m_C[mat] end + local tex = ((mat:GetTexture("$basetexture") or mat):GetName() or "error"):lower() + m_C[mat] = (string.find(tex, "glass", 1, true) or string.find(tex, "window", 1, true)) and IsTransparent(mat) + return m_C[mat] + end + -- Generates a "tree" of matching surfaceinfos + local function FindMatches(tSurfaces, tSurface) + local tGroup = {} + for i,tSurface2 in ipairs(tSurfaces) do + if VertsMatch(tSurface[2],tSurface2[2]) then + local surf = table.remove(tSurfaces,i) + table.insert(tGroup, surf) + for _,v in ipairs(FindMatches(tSurfaces, surf)) do + table.insert(tGroup,v) + end + end + end + table.insert(tGroup, tSurface) + return tGroup + end + local function FindMerges(tSurfaces,tGroups) + for i = 1,#tSurfaces do + if #tSurfaces < 1 then return end + local surf = table.remove(tSurfaces,1) + local group = {} + for _,t in ipairs(FindMatches(tSurfaces, surf)) do + table.insert(group, t[1]) + end + table.insert(tGroups,group) + end + end + -- New surface functions + local function SurfaceInfo_FacingOutside( eEnt, SurfaceInfo, NoCull ) -- returns true if the surfaceinfo is facing the outside + if SurfaceInfo.b_OutSide ~= nil then return SurfaceInfo.b_OutSide end + SurfaceInfo.b_OutSide = false + local n = SurfaceInfo:GetNormal() + local p = SurfaceInfo:GetCenter() + eEnt:GetPos() + local hP = SkyLine(p + n * -5,-n, 10, 180, eEnt) + if hP then + SurfaceInfo.b_OutSide = true + SurfaceInfo.v_OutSide = hP + elseif NoCull then + hP = SkyLine(p + n * 5,n, 10, 180, eEnt) + if hP then + SurfaceInfo.b_OutSide = true + SurfaceInfo.v_OutSide = hP + end + end + return SurfaceInfo.b_OutSide + end + local function SurfaceInfo_GetOutSide( SurfaceInfo ) -- returns a position that is under the sky. + if not SurfaceInfo.v_OutSide then return end + return Vector(SurfaceInfo.v_OutSide[1],SurfaceInfo.v_OutSide[2],SurfaceInfo.v_OutSide[3]) + end + local function SurfaceInfo_GetType( eEnt, SurfaceInfo ) + if #SurfaceInfo:GetVertices() < 3 then return INVALID_VERTS end + if SurfaceInfo:IsWater() then -- Water + return WATER_VERTS + elseif not SurfaceInfo:IsNoDraw() then -- We got a surfacebrush + -- Get texture + local iM = SurfaceInfo:GetMaterial() + local tex = ((iM:GetTexture("$basetexture") or iM):GetName() or "error"):lower() + -- Check + if IsWindowMaterial(iM) and SurfaceInfo_FacingOutside( eEnt, SurfaceInfo ) then -- Glass + if IsRoofAngle( SurfaceInfo:GetAngles() ) then -- Roof + return GLASS_ROOF_VERTS, nocull + else + return GLASS_VERTS, nocull + end + elseif (string.find(tex, "metal", 1, true) or string.find(tex, "sheet", 1, true)) and SurfaceInfo:GetMinSide() > 20 then + if IsRoofAngle( SurfaceInfo:GetAngles() ) then -- Roof + return METAL_ROOF_VERTS + end + end + end + return INVALID_VERTS + end + -- Window "entities" + local window_meta = {} + window_meta.__index = window_meta + window_meta.__tostring = function(self) return "SF_WindowRef[" .. tostring(self[1]) .. "]" end + local function CreateWindowRef(ent,center) + local t = {ent,center} + setmetatable(t, window_meta) + return t + end + function window_meta:IsAlive() + if not IsValid(self[1]) then return false end + return not (self[1]:GetMaxHealth() > 0 and self[1]:Health() <= 0) + end + function window_meta:GetCenter() + if self.mesh then return self[1]:GetPos() + self[1]:OBBCenter() end + local pos = self[2] + local a = self[1]:GetAngles() + return self[1]:GetPos() + pos.y * a:Forward() + a:Up() * pos.z + a:Right() * pos.x + end + function window_meta:Draw() + local ent = self[1] + if not self:IsAlive() then return end + local n = ent:GetAngles():Forward() + if self.mesh then + mesh.Begin( MATERIAL_TRIANGLES, 7 ) + for i,vert in ipairs(self.mesh) do + local vP = ent:LocalToWorld(vert.pos) + mesh.Position( vP ) + mesh.Normal( n ) + mesh.Color(255,255,255,255) + mesh.TexCoord(0, vert.u, -vert.v) + mesh.AdvanceVertex() + end + mesh.End() + elseif self.w and self.h then + local c = self:GetCenter() + render.DrawQuadEasy( c, n, self.w, -self.h, color_white, ent:GetAngles().r ) + end + end + + STORMFOX_WINDOWMESHES = STORMFOX_WINDOWMESHES or {} + STORMFOX_WATERMESHBUILD = {} + STORMFOX_WATERMESHCOLLISON = {} + STORMFOX_WATERMESHBUILD_SKYBOX = {} + -- In case of reload, we delete the old meshes + for i,v in pairs(STORMFOX_WINDOWMESHES) do + v:Destroy() + STORMFOX_WINDOWMESHES[i] = nil + end + + local glass_mapmesh = {} -- Map glass_surfaces = {MeshUV, MeshUV_Scale, RefractMeshUV, RefractMeshUV_Scale} + local glass_dynamic = {} -- {} + local puddle_mapmesh + local surfaceinfos = {} + + local norm_mat = Material("stormfox2/effects/window/win") + local refact_mat = Material("stormfox2/effects/window/win_refract") + local puddle_mat = Material("stormfox2/effects/rain_puddle") + + local ice = Material("stormfox/effects/ice_water") + local ice_size = 500 + local vec_ex = Vector(0,0,1) + + -- Scans for nearby window entities. + local function scan_dynamic() + glass_dynamic = {} + if not StormFox2.Setting.GetCache("window_enable", true) then return end + local view = StormFox2.util.GetCalcView() + local tEnts = ents.FindInSphere(view.pos, StormFox2.Setting.GetCache("window_distance",800)) + for i,ent in ipairs(tEnts) do + local c = ent:GetClass() + if c == "prop_dynamic" or c == "prop_physics" then -- Models + local c = ent:GetPos() + ent:OBBCenter() + local n = ent:GetAngles():Forward() + local t = (ent:OBBMaxs() - ent:OBBMins()).x + 1 + if IsWinModel(ent:GetModel()) and (SkyLine(c + n * t,n, 7, 180, ent) or SkyLine(c + -n * t,-n, 7, 180, ent) ) then + local min,max = GetWindowModel(ent:GetModel()) + local c = min + (max - min) / 2 + local w,h = max:Distance(Vector(min.x,min.y,max.z)),max.z - min.z + local window = CreateWindowRef(ent,c) + window.w = w + window.h = h + table.insert(glass_dynamic, window) + end + elseif c:sub(0,14) == "func_breakable" or c == "func_brush" then -- Brushes + local surfs = ent:GetBrushSurfaces() + if #surfs < 1 then + continue + elseif #surfs < 2 then -- Properly a nocull + if IsWindowMaterial(surfs[1]:GetMaterial()) and SurfaceInfo_FacingOutside(ent, surfs[1], true) then + local window = CreateWindowRef(ent,surfs[1]:GetCenter()) + window.mesh = surfs[1]:GetMesh() + table.insert(glass_dynamic, window) + end + else + local window + local t = {} + for _,surf in ipairs(surfs) do + if surf:GetMinSide() > 10 and IsWindowMaterial(surf:GetMaterial()) and SurfaceInfo_FacingOutside(ent, surf) then + if not window then + window = CreateWindowRef(ent,surf:GetCenter()) + end + -- Add mesh + for i,v in ipairs(surf:GetMesh()) do + table.insert(t, v) + end + end + end + if not window then continue end + window.mesh = t + table.insert(glass_dynamic, window) + end + end + end + end + hook.Add("PostCleanupMap", "StormFox2.environment.onclean", scan_dynamic) + local scan = function() -- Locates all surfaceinfos we need. + StormFox2.Msg("Scanning surfaces ..") + surfaceinfos = {} + local puddle_surfaceinfos = {} + local temp_glass = {} -- [mat_string][Normal][ID] = surface + -- Scan all brushsurfaces and grab the glass/windows, water and metal. Put them in a table with matching normal. + for i,v in ipairs( game.GetWorld():GetBrushSurfaces() ) do + if not v then continue end + if not v:IsValid() then continue end + if not v:IsWater() and v:GetNormal():Dot(Vector(0,0,1)) < -0.999 and #v:GetVertices() == 4 then -- Can be a puddle + local min,max = v:GetMinSide(),v:GetMaxSide() + if v:GetMinSide() > 64 and math.abs(min / max) > 0.7 and UnderSky(v:GetCenter()) then + table.insert(puddle_surfaceinfos, v) + end + end + local v_type = SurfaceInfo_GetType( game.GetWorld(), v ) + if v_type == INVALID_VERTS then continue end -- Invalid or doesn't have a type. + if not surfaceinfos[v_type] then surfaceinfos[v_type] = {} end + table.insert(surfaceinfos[v_type], {v, v:GetCenter()} ) + if v_type ~= GLASS_VERTS and v_type ~= GLASS_ROOF_VERTS then continue end -- If it isn't glass. We're done. + -- Make the normal bit fuzzy. + local N = v:GetNormal() + local N_Fuzzy = Vector(N.x,N.y,N.z) + N_Fuzzy[1] = round(N_Fuzzy[1],1) + N_Fuzzy[2] = round(N_Fuzzy[2],1) + N_Fuzzy[3] = round(N_Fuzzy[3],1) + -- We use insertmatch, since vectors are pointers when used as keys. + local Point_N = FindMatchPointer(temp_glass, N_Fuzzy) + if Point_N then + table.insert(temp_glass[Point_N],{v, fuzzeVectors(v:GetVertices())}) + else + temp_glass[N_Fuzzy] = {{v, fuzzeVectors(v:GetVertices())}} + end + end + -- We now have temp_glass[v_type][tex_string][Normal][i] = {surface_info, verts} + -- Now we need to group surfaces together. + local temp_group = {} + for N,list in pairs(temp_glass) do + if #list == 1 then -- Only one surface of this type on the map. + table.insert(temp_group, {list[1][1]}) + else + FindMerges(list,temp_group) + end + end + temp_glass = nil + coroutine.yield() -- Wait a bit + -- Generate a mesh for each group + StormFox2.Msg("Generating glass-mesh data ..") + local glass_planes = {{},{}} + for group_i,t_group in ipairs(temp_group) do -- For each group. + local mesh = {} + -- Add all triangles together + local a = t_group[1]:GetAngles() + local max_height,min_height,max_width,min_width + local max_vec,min_vec + for i,surf in ipairs(t_group) do + if not surf then continue end + for _,t in ipairs(surf:GetMesh() or {}) do + local vec_r = Vector(t.pos[1],t.pos[2],t.pos[3]) + vec_r:Rotate(-a) -- x = nil, y = w, z = h + t.vec_r = vec_r + t.pos = t.pos + table.insert(mesh, t) + local h,w = vec_r[3], vec_r[2] + max_height = math.max(max_height or h,h) + min_height = math.min(min_height or h,h) + max_width = math.max(max_width or w,w) + min_width = math.min(min_width or w,w) + if not max_vec then + max_vec = Vector(t.pos[1],t.pos[2],t.pos[3]) + min_vec = Vector(t.pos[1],t.pos[2],t.pos[3]) + else + max_vec[1] = math.max(max_vec[1],t.pos[1]) + max_vec[2] = math.max(max_vec[2],t.pos[2]) + max_vec[3] = math.max(max_vec[3],t.pos[3]) + min_vec[1] = math.min(min_vec[1],t.pos[1]) + min_vec[2] = math.min(min_vec[2],t.pos[2]) + min_vec[3] = math.min(min_vec[3],t.pos[3]) + end + end + --[[ + if GetNocull(surf) and false then + local m = surf:GetMesh() + for i = #m,1,-1 do + local t = table.Copy(m[i]) + local vec_r = Vector(t.pos[1],t.pos[2],t.pos[3]) + vec_r:Rotate(-n:Angle()) -- x = nil, y = w, z = h + t.vec_r = vec_r + t.normal = -t.normal + table.insert(mesh, t) + end + end]] + end + local width_range = max_width - min_width + local height_range = max_height - min_height + + -- Adjust the UV + for i,v in ipairs(mesh) do + local h,w = v.vec_r[3] - min_height, v.vec_r[2] - min_width + mesh[i].u = w / width_range + mesh[i].v = 1 - (h / height_range) + end + -- Mesh with UV + for _,v in ipairs(mesh) do + table.insert(glass_planes[1] , table.Copy(v)) + end + -- Mesh with UV-Scale + for i,v in ipairs(mesh) do + mesh[i].u = mesh[i].u * width_range / 64 + group_i / 7 + mesh[i].v = mesh[i].v * height_range / 64 + group_i / 14 + end + for _,v in ipairs(mesh) do + table.insert(glass_planes[2] , table.Copy(v)) + end + end + temp_group = nil + coroutine.yield() -- Wait a bit + -- Add static models + StormFox2.Msg("Including window models ..") + for _,data in pairs(StormFox2.Map.StaticProps()) do + if not surfaceinfos[GLASS_VERTS] then surfaceinfos[GLASS_VERTS] = {} end + if not data.PropType or not IsWinModel(data.PropType) then continue end + local min,max = GetWindowModel(data.PropType) + + local scale = data.UniformScale or data.Scale or 1 + local n = data.Angles:Forward() + local t1 = {pos = Vector(min.x,min.y,min.z) * scale, u = 0, v = 1, normal = n} + local t2 = {pos = Vector(min.x,min.y,max.z) * scale, u = 0, v = 0, normal = n} + local t3 = {pos = Vector(max.x,max.y,max.z) * scale, u = 1, v = 0, normal = n} + local t4 = {pos = Vector(max.x,max.y,min.z) * scale, u = 1, v = 1, normal = n} + local w = Vector(max.x,max.y,min.z):Distance(min) + -- Rotate + t1.pos:Rotate(data.Angles) + t2.pos:Rotate(data.Angles) + t3.pos:Rotate(data.Angles) + t4.pos:Rotate(data.Angles) + -- Apply origin + t1.pos = t1.pos + data.Origin + t2.pos = t2.pos + data.Origin + t3.pos = t3.pos + data.Origin + t4.pos = t4.pos + data.Origin + local mesh = {t1,t2,t3,t1,t3,t4} + -- UV Mesh + for _,t in ipairs(mesh) do + table.insert(glass_planes[1] , t) + end + -- 64x64 Mesh + for _,t in ipairs(mesh) do + t.u = t.u * w / 64 + t.v = 1 - (t.pos.z / 64) + table.insert(glass_planes[2] , t) + end + table.insert(surfaceinfos[GLASS_VERTS], {nil, data.Origin + t1.pos + (t1.pos - t3.pos) / 2} ) + end + coroutine.yield() -- Wait a bit + -- We now got 4 map-wide meshes. + StormFox2.Msg("Generating glass-meshs [" .. #glass_planes[1] .. "] ..") + -- Refract mesh + local obj = Mesh(refact_mat) + obj:BuildFromTriangles(glass_planes[1]) + table.insert(STORMFOX_WINDOWMESHES, obj) + + local obj2 = Mesh(refact_mat) + obj2:BuildFromTriangles(glass_planes[2]) + table.insert(STORMFOX_WINDOWMESHES, obj2) + + coroutine.yield() -- Wait a bit + + -- Normal Mesh + local obj3 = Mesh(norm_mat) + obj3:BuildFromTriangles(glass_planes[1]) + table.insert(STORMFOX_WINDOWMESHES, obj3) + + local obj4 = Mesh(norm_mat) + obj4:BuildFromTriangles(glass_planes[2]) + table.insert(STORMFOX_WINDOWMESHES, obj4) + + glass_planes = nil + glass_mapmesh = {obj,obj2,obj3,obj4} + -- Puddle + obj,obj2,obj3,obj4 = nil,nil,nil,nil + StormFox2.Msg("Generating puddle-meshs [" .. #puddle_surfaceinfos .. "] ..") + local mesh = {} + for i,v in ipairs(puddle_surfaceinfos) do + local r = i % 6 + if r > 3 then continue end + for a,t in ipairs(v:GetMesh()) do + if r == 1 then + t.u = -t.u + elseif r == 2 then + t.u = -t.u + t.v = -t.v + elseif r == 3 then + t.v = -t.v + end + table.insert(mesh, t) + end + end + coroutine.yield() -- Wait a bit + puddle_mapmesh = Mesh(puddle_mat) + puddle_mapmesh:BuildFromTriangles(mesh) + table.insert(STORMFOX_WINDOWMESHES, puddle_mapmesh) + -- Generate water mesh + if surfaceinfos[WATER_VERTS] then + StormFox2.Msg("Generating ice-mesh [" .. #surfaceinfos[WATER_VERTS] .. "] ") + local mesh = {} + STORMFOX_WATERMESH = Mesh(ice) + STORMFOX_WATERMESH_SKYBOX = Mesh(ice) + local c = Color(255,255,255) + local t = Vector(0,1,0) + local u = {0,1,0,-1} + for i,v in ipairs(surfaceinfos[WATER_VERTS]) do + local t = v[1]:GetVertices() + if StormFox2.Map.IsInside( v[2] ) then + for i = 1,3 do + local vec = t[i] + table.insert(STORMFOX_WATERMESHBUILD, { + pos = vec + vec_ex, + u = vec.x / ice_size, + v = vec.y / ice_size, + normal = vector_up, + tangent = t, + userdata = u, + color = c}) + --table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex}) + end + for i = 4,20 do + if #t < i then continue end + local vec = t[1] + table.insert(STORMFOX_WATERMESHBUILD, {pos = vec + vec_ex, + u = vec.x / ice_size, + v = vec.y / ice_size, + normal = vector_up, + tangent = t, + userdata = u, + color = c}) + -- table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex}) + local vec = t[i - 1] + table.insert(STORMFOX_WATERMESHBUILD, {pos = vec + vec_ex, + u = vec.x / ice_size, + v = vec.y / ice_size, + normal = vector_up, + tangent = t, + userdata = u, + color = c}) + -- table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex}) + local vec = t[i] + table.insert(STORMFOX_WATERMESHBUILD, {pos = vec + vec_ex, + u = vec.x / ice_size, + v = vec.y / ice_size, + normal = vector_up, + tangent = t, + userdata = u, + color = c}) + -- table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex}) + end + if #t >= 3 then + local t2 = {} + if #t == 3 then + for i = 1,3 do + table.insert(t2, t[i] + vec_ex) + table.insert(t2, t[i] - vec_ex) + end + table.insert(t2, t[3] + vec_ex) + table.insert(t2, t[3] - vec_ex) + table.insert(STORMFOX_WATERMESHCOLLISON, t2) + elseif #t == 4 then + for i = 1,4 do + table.insert(t2, t[i] + vec_ex) + table.insert(t2, t[i] - vec_ex) + end + table.insert(STORMFOX_WATERMESHCOLLISON, t2) + else + for i = 1,#t do + table.insert(t2, t[i] + vec_ex) + table.insert(t2, t[i] - vec_ex) + end + table.insert(STORMFOX_WATERMESHCOLLISON, t2) + end + end + elseif CLIENT then + local s = StormFox2.Map.GetSkyboxScale() or 1 + for i = 1,3 do + local vec = t[i] + table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)}) + end + for i = 4,20 do + if #t < i then continue end + local vec = t[1] + table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)}) + local vec = t[i - 1] + table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)}) + local vec = t[i] + table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)}) + end + end + end + -- Build the water + STORMFOX_WATERMESH:BuildFromTriangles(STORMFOX_WATERMESHBUILD) + STORMFOX_WATERMESH_SKYBOX:BuildFromTriangles(STORMFOX_WATERMESHBUILD_SKYBOX) + end + -- Add window-entities + StormFox2.Msg("Locating window entities ..") + coroutine.yield() -- Wait a bit + scan_dynamic() + coroutine.yield(true) + end + + local cor_scan = coroutine.wrap(scan) + local function StartGenerating() + timer.Create("SF_ENV_SCAN", 0.2, 0, function() + if cor_scan() then + cor_scan = nil + timer.Remove("SF_ENV_SCAN") + StormFox2.Msg("Meshes completed.") + end + end) + hook.Remove("StormFox2.InitPostEntity", "StormFox_ENV_SCAN") + end + hook.Add("StormFox2.InitPostEntity", "StormFox_ENV_SCAN", StartGenerating) +--[[------------------------------------------------------------------------- +Renders glass-meshes. +---------------------------------------------------------------------------]] + local TEX_SIZE = 512 + local RT_Win = GetRenderTarget( "SF_Win", TEX_SIZE, TEX_SIZE ) + local RT_Win64 = GetRenderTarget( "SF_Win_64", TEX_SIZE, TEX_SIZE ) + local RT_Win_Ref = GetRenderTarget( "SF_Win_R", TEX_SIZE, TEX_SIZE ) + local RT_Win64_Ref = GetRenderTarget( "SF_Win_64_R", TEX_SIZE, TEX_SIZE ) + + -- Returns the current window-renders + local function GetRenderFunctions() + if not StormFox2.Weather or not StormFox2.Terrain or not StormFox2.Terrain.GetCurrent then return end + local cT = StormFox2.Terrain.GetCurrent() or {} + local cW = StormFox2.Weather.GetCurrent() + return cW._RenderWindow or cT.windRender, cW._RenderWindowRefract or cT.windRenderRef, cW._RenderWindow64x64 or cT.windRender64, cW._RenderWindowRefract64x64 or cT.windRenderRef64 + end + + local close_window_ents = {} -- glass_dynamic + local Win,Win64,Win_Ref,Win64_Ref = Material("stormfox2/effects/window/win"),Material("stormfox2/effects/window/win_64"),Material("stormfox2/effects/window/win_refract"),Material("stormfox2/effects/window/win_refract_64") + local function Mat_Update(rt_tex,func) + render.PushRenderTarget(rt_tex) + render.Clear( 0, 0, 0, 0 ) + render.ClearDepth() + -- render.PushFilterMag( 0 ) + -- render.PushFilterMin( 0 ) + cam.Start2D() + surface.SetDrawColor(color_white) + func(TEX_SIZE,TEX_SIZE) + cam.End2D() + -- render.PopFilterMag() + -- render.PopFilterMin() + render.PopRenderTarget() + end + + -- Update material + hook.Add("Think", "StormFox2.Environment.UpdateRTWIndow", function() + if not StormFox2.Terrain then return end + if not StormFox2.Setting.GetCache("window_enable", true) then return end + local windRender, windRenderRef, windRender64, windRender64Ref = GetRenderFunctions() + -- Refract materials + if windRenderRef then + Win_Ref:SetTexture( "$normalmap", RT_Win_Ref ) + Mat_Update(RT_Win_Ref, windRenderRef) + end + if windRender64Ref then + Win64_Ref:SetTexture( "$normalmap", RT_Win64_Ref ) + Mat_Update(RT_Win64_Ref, windRender64Ref) + end + -- Regular materials + if windRender then + Win:SetTexture( "$basetexture", RT_Win ) + Mat_Update(RT_Win, windRender) + end + if windRender64 then + Win64:SetTexture( "$basetexture", RT_Win64 ) + Mat_Update(RT_Win64, windRender64) + end + end) + + local function DrawCloseWindows() + for i,v in ipairs(close_window_ents) do + v:Draw() + end + end + + hook.Add("PreDrawTranslucentRenderables", "StormFox2.Environment.RenderWindow", function(a,b) + if (b or not StormFox2.Terrain) then return end + if puddle_mapmesh then + --render.SetMaterial(puddle_mat) + --puddle_mapmesh:Draw() + end + if #glass_mapmesh < 4 then return end + if not StormFox2.Setting.GetCache("window_enable", true) then return end + local windRender, windRenderRef, windRender64, windRender64Ref = GetRenderFunctions() + -- Refract materials + if windRenderRef then + render.SetMaterial(Win_Ref) + glass_mapmesh[1]:Draw() + DrawCloseWindows() + end + if windRender64Ref then + render.SetMaterial(Win64_Ref) + glass_mapmesh[2]:Draw() + DrawCloseWindows() + end + -- Regular materials + if windRender then + render.SetMaterial(Win) + glass_mapmesh[3]:Draw() + DrawCloseWindows() + end + if windRender64 then + render.SetMaterial(Win64) + glass_mapmesh[4]:Draw() + DrawCloseWindows() + end + end) +--[[------------------------------------------------------------------------- +Handles the location for the client. +Will check if they're outside, in wind(downfall), near glass, near outside, indoors and roof. +---------------------------------------------------------------------------]] +-- Nearest stuff +local nearest_window, nearest_outside +-- Roof +local roof_type, roof_pos -- Roof_type is hit_type enums from lib/sh_downfall.lua +-- Bools +local is_inside, is_inwind, in_water + +local z_distance = 0 + +local viewPos +local function sort_func(a, b) + return a[2]:DistToSqr(viewPos) < b[2]:DistToSqr(viewPos) +end +local update_windows_tick = 0 +local function env_corotinefunction() + local view = StormFox2.util.GetCalcView() + viewPos = view.pos + -- If we're in water, locate the z_position + if bit.band( util.PointContents( viewPos ), CONTENTS_WATER ) == CONTENTS_WATER then + local t = AdvTrace(viewPos,viewPos + Vector(0,0,1000), MASK_WATER) + if t.FractionLeftSolid then + in_water = viewPos.z + 1000 * t.FractionLeftSolid + else + in_water = (viewPos + Vector(0,0,1000)).z + end + else + in_water = nil + end + -- Update close windows + update_windows_tick = update_windows_tick + 1 + if update_windows_tick >= 10 then + scan_dynamic() + update_windows_tick = 0 + end + -- Update close windows + close_window_ents = {} + for _,v in ipairs(glass_dynamic) do -- ent,c + if not v:IsAlive() then continue end + if viewPos:Distance(v:GetCenter()) > 10000 then continue end + table.insert(close_window_ents, v) + end + -- is inside + is_inwind = StormFox2.Wind.IsEntityInWind(LocalPlayer(),true) + if not is_inwind then + local veh = LocalPlayer():GetVehicle() + if IsValid(veh) then + is_inwind = StormFox2.Wind.IsEntityInWind(veh,true) + end + end + is_inside = not (is_inwind or UnderSky2(viewPos)) + -- ZDis + local tr = PlyTrace( StormFox2.util.ViewEntity(), Vector( 0, 0, -16000)) + if not tr.Hit then + z_distance = 16000 + else + z_distance = tr.Fraction * 16000 + end + -- Locate nearest outside / window + if is_inside and not in_water then + -- Nearest window + if surfaceinfos[GLASS_VERTS] then + nearest_window = nil + -- Sort windows + table_sort(surfaceinfos[GLASS_VERTS],sort_func) + -- Check brushes + for i = 1,10 do + if not surfaceinfos[GLASS_VERTS][i] then break end + local surfinfo = surfaceinfos[GLASS_VERTS][i][1] + local winpos = surfaceinfos[GLASS_VERTS][i][2] + local v, tr = MatchTrace(view.pos,winpos,surfinfo and surfinfo:GetNormal()) + if v then -- Check window normal and trace + nearest_window = winpos + break + end + end + -- Check ents + local curDist = nearest_window and nearest_window:DistToSqr(view.pos) + for i = 1,#close_window_ents do + if not close_window_ents[i]:IsAlive() then continue end -- Check if it is alive + local dis = close_window_ents[i]:GetCenter():Distance(view.pos) + if curDist and dis > curDist then continue end + local win = close_window_ents[i] + local n = view.ang:Forward() + --debugoverlay.Cross(win:GetCenter()- n * 15, 15, 1, color_white) + local v, tr = MatchTrace(view.pos,win:GetCenter() - n * 15) + if not v then continue end + curDist = dis + nearest_window = win:GetCenter() + end + end + coroutine.yield() + -- Nearest outside + local oldpos = nearest_outside + -- Scan forward the player, then the players view (IF they look down at an angle towards the outside) + -- SkyLine(pos,norm, n, nMaxDistance, filter, sky_mask) + nearest_outside = SkyLine(view.pos,Angle(0,view.ang.y,0):Forward(), 7, 500, nil, MASK_SHOT) or SkyLine(view.pos,view.ang:Forward(), 7, 500, nil, MASK_SHOT) + -- If we don't hit anything, scan around the player + if not nearest_outside then + local lines = math.min(StormFox2.Client.GetQualityNumber() * 2,10) + local r = 360 / lines + for i = 1,lines do + nearest_outside = SkyLine(view.pos,Angle(0,r * i,0):Forward(), 5, 600,nil, MASK_SHOT) + if nearest_outside then break end + end + end + -- If the oldpos is still valid. Check to see if the currentpos is closer. + if oldpos and EasyTrace(view.pos,oldpos) then -- If old pos and we still see old pos + if nearest_outside then + nearest_outside = ( oldpos:DistToSqr(view.pos) < nearest_outside:DistToSqr(view.pos) ) and oldpos or nearest_outside + else + nearest_outside = oldpos + end + end + coroutine.yield() + -- Roof pos + roof_pos, roof_type = StormFox2.DownFall.CheckDrop(viewPos, Vector(0,0,-1), 3, StormFox2.util.ViewEntity()) + --debugoverlay.Cross(roof_pos, 15, 1, color_white, true) + end + coroutine.yield() +end + +local env_corotine = coroutine.wrap(function() + while true do + env_corotinefunction() + end +end) +local outsideFade = 0 +timer.Create("stormfox2.enviroment.think", 0.25, 0, function() + if not StormFox2.Loaded or not _STORMFOX_POSTENTITY then return end + if not StormFox2.Setting.GetCache("window_enable", true) then return end + env_corotine() + if is_inside then + outsideFade = math.Approach(outsideFade, 0, FrameTime() * 20) + else + outsideFade = math.Approach(outsideFade, 1, FrameTime() * 3.2) + end +end) + +--[[------------------------------------------------------------------------- +Returns the clients enviroment and locations. +---------------------------------------------------------------------------]] +function StormFox2.Environment.Get() + local t = {} + t.outside = not is_inside + t.in_water = in_water + t.z_distance = z_distance + if is_inside and not in_water then + t.nearest_window = is_inside and nearest_window + t.nearest_outside = is_inside and nearest_outside + if roof_pos then + t.roof_z = roof_pos.z + t.roof_type = roof_type + end + end + return t +end + +function StormFox2.Environment.GetOutSideFade() + return outsideFade +end + +--[[------------------------------------------------------------------------- +Returns the clients height over ground. +---------------------------------------------------------------------------]] +function StormFox2.Environment.GetZHeight( bForceUpdate ) + local tr = PlyTrace( StormFox2.util.ViewEntity()) + if not tr.Hit then + z_distance = 16000 + else + z_distance = tr.Fraction * 16000 + end + return z_distance +end + --[[NOTES: + - Make breakable windows have this too. + - Make a weather seed generator, and use it for puddles and clouds (better). + ]] + +--[[ +StartGenerating() +timer.Create("StormFox2.enviroment.think", 0.25, 0, function() + env_corotine() + --print(coroutine.resume(env_corotine)) +end)]] + +--[[------------------------------------------------------------------------- +Ice sheet on the map +---------------------------------------------------------------------------]] +local b = #ents.FindByClass("stormfox_mapice") > 0 +function StormFox2.Environment.HasMapIce() + return b +end +function StormFox2.Environment._SETMapIce(s) + b = s +end +function StormFox2.Environment.DrawWaterOverlay(bSkyBox) + if not StormFox2.Setting.GetCache("enable_wateroverlay", true) or not StormFox2.Setting.SFEnabled() then return end + if not STORMFOX_WATERMESH_SKYBOX then return false end -- Invalid mesh. + if StormFox2.Environment.HasMapIce() then return false end -- Ice is on the map + if bSkyBox then + STORMFOX_WATERMESH_SKYBOX:Draw() + else + STORMFOX_WATERMESH:Draw() + end + return true +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_heavens.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_heavens.lua new file mode 100644 index 0000000..5c34782 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/cl_heavens.lua @@ -0,0 +1,236 @@ +-- Fix overlapping tables + StormFox2.Time = StormFox2.Time or {} +local clamp,max,min = math.Clamp,math.max,math.min +StormFox2.Sun = StormFox2.Sun or {} +StormFox2.Moon = StormFox2.Moon or {} +StormFox2.Sky = StormFox2.Sky or {} +local sunVisible + +-- Pipe Dawg + --[[------------------------------------------------------------------- + Returns true if the sun is visible for the client. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.GetVisibility() + return sunVisible + end + -- Pixel are a bit crazy if called twice + hook.Add("Think","StormFox2.Sun.PixUpdater",function() + if not StormFox2.Loaded then return end + if not _STORMFOX_SUNPIX then -- Create pixel + _STORMFOX_SUNPIX = util.GetPixelVisibleHandle() + else + sunVisible = util.PixelVisible( LocalPlayer():GetPos() + StormFox2.Sun.GetAngle():Forward() * 4096, StormFox2.Mixer.Get("sun_size",30), _STORMFOX_SUNPIX ) + end + end) + +-- Sun overwrite + SF_OLD_SUNINFO = SF_OLD_SUNINFO or util.GetSunInfo() -- Just in case + -- + function util.GetSunInfo() + if not StormFox2.Loaded or not sunVisible then -- In case we mess up + if SF_OLD_SUNINFO then + return SF_OLD_SUNINFO() + else + return {} + end + end + local tab = { + ["direction"] = StormFox2.Sun.GetAngle():Forward(), + ["obstruction"] = sunVisible * (StormFox2.Sun.GetColor().a / 255)} + return tab + end + +-- SkyRender + --[[------------------------------------------------------------------------- + Render layers + StarRender = Stars + SunRender + BlockStarRender (Will allow you to block out stars/sun) + Moon + CloudBox (Like a skybox, just with transparency. Will fade between states) + CloudLayer (Moving clouds) + ---------------------------------------------------------------------------]] + hook.Add("PostDraw2DSkyBox", "StormFox2.SkyBoxRender", function() + if not StormFox2 then return end + if not StormFox2.Loaded then return end + -- Just to be sure. I hate errors in render hooks. + if not StormFox2.util then return end + if not StormFox2.Sun then return end + if not StormFox2.Moon then return end + if not StormFox2.Moon.GetAngle then return end + if not StormFox2.Setting.SFEnabled() then return end + local c_pos = StormFox2.util.RenderPos() + local sky = StormFox2.Setting.GetCache("enable_skybox", true) + local use_2d = StormFox2.Setting.GetCache("use_2dskybox",false) + cam.Start3D( Vector( 0, 0, 0 ), EyeAngles() ,nil,nil,nil,nil,nil,1,32000) -- 2d maps fix + render.OverrideDepthEnable( false,false ) + render.SuppressEngineLighting(true) + render.SetLightingMode( 2 ) + if not use_2d or not sky then + hook.Run("StormFox2.2DSkybox.StarRender", c_pos) + -- hook.Run("StormFox2.2DSkybox.BlockStarRender",c_pos) + hook.Run("StormFox2.2DSkybox.SunRender", c_pos) -- No need to block, shrink the sun. + + hook.Run("StormFox2.2DSkybox.Moon", c_pos) + end + hook.Run("StormFox2.2DSkybox.CloudBox", c_pos) + hook.Run("StormFox2.2DSkybox.CloudLayer", c_pos) + hook.Run("StormFox2.2DSkybox.PostCloudLayer",c_pos) + hook.Run("StormFox2.2DSkybox.FogLayer", c_pos) + render.SuppressEngineLighting(false) + render.SetLightingMode( 0 ) + render.OverrideDepthEnable( false, false ) + cam.End3D() + + render.SetColorMaterial() + end) + +-- Render Sun + local sunMat = Material("stormfox2/effects/moon/moon_glow") + hook.Add("StormFox2.2DSkybox.SunRender","StormFox2.RenderSun",function(c_pos) + local SunN = -StormFox2.Sun.GetAngle():Forward() + local s_size = StormFox2.Sun.GetSize() + local c_c = StormFox2.Sun.GetColor() or color_white + local c = Color(c_c.r,c_c.g,c_c.b,c_c.a) + render.SetMaterial(sunMat) + render.DrawQuadEasy( SunN * -200, SunN, s_size, s_size, c, 0 ) + end) + +-- Render moon + -- Setup params and vars + local CurrentMoonTexture = Material("stormfox2/effects/moon/rt_moon") + local Mask_25 = Material("stormfox2/effects/moon/25.png") + local Mask_0 = Material("stormfox2/effects/moon/0.png") + local Mask_50 = Material("stormfox2/effects/moon/50.png") + local Mask_75 = Material("stormfox2/effects/moon/75.png") + local texscale = 512 + local RTMoonTexture = GetRenderTargetEx( "StormFox_RTMoon", texscale, texscale, 1, MATERIAL_RT_DEPTH_NONE, 2, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGBA8888) + -- Functions to update the moon phase + local lastRotation = -1 + local lastCurrentPhase = -1 + local lastMoonMat + local function RenderMoonPhase(rotation,currentPhase) + + --currentPhase = SF_MOON_FIRST_QUARTER - 0.01 + if currentPhase == SF_MOON_NEW then return end -- New moon. No need to render. + -- Check if there is a need to re-render + local moonMat = StormFox2.Mixer.Get("moonTexture",lastMoonMat) + if type(moonMat) ~= "string" then return end -- Something went wrong. Lets wait. + if lastCurrentPhase == currentPhase and lastMoonMat and lastMoonMat == moonMat then + -- Already rendered + return true + end + lastCurrentPhase = currentPhase + lastMoonMat = moonMat + moonMat = Material(moonMat) + render.PushRenderTarget( RTMoonTexture ) + render.OverrideAlphaWriteEnable( true, true ) + + render.ClearDepth() + render.Clear( 0, 0, 0, 0 ) + cam.Start2D() + -- Render moon + surface.SetDrawColor(color_white) + surface.SetMaterial(moonMat) + surface.DrawTexturedRectUV(0,0,texscale,texscale,-0.01,-0.01,1.01,1.01) + -- Mask Start + -- render.OverrideBlendFunc( true, BLEND_ZERO, BLEND_SRC_ALPHA, BLEND_DST_ALPHA, BLEND_ZERO ) + render.OverrideBlend(true, BLEND_ZERO, BLEND_SRC_ALPHA,0,BLEND_DST_ALPHA, BLEND_ZERO,0) + -- Render mask + surface.SetDrawColor(color_white) + -- New to first q; 0 to 50% + if currentPhase < SF_MOON_FIRST_QUARTER then + local s = 7 - 3.5 * currentPhase + surface.SetMaterial(Mask_25) + surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation) + if currentPhase >= SF_MOON_WAXIN_CRESCENT then + -- Ex step + local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation)) + surface.SetMaterial(Mask_0) + surface.DrawTexturedRectRotated(texscale / 2 + x * (-texscale * 0.51),texscale / 2 + y * (-texscale * 0.51),texscale * 1,texscale,rotation) + end + elseif currentPhase == SF_MOON_FIRST_QUARTER then -- 50% + surface.SetMaterial(Mask_50) + surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale,texscale,rotation) + elseif currentPhase < SF_MOON_FULL then -- 50% to 100% + local s = (currentPhase - SF_MOON_FIRST_QUARTER) * 3 + surface.SetMaterial(Mask_75) + surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation + 180) + local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation)) + surface.SetMaterial(Mask_0) + if s < 0.2 then + surface.DrawTexturedRectRotated(texscale / 2 + x * (-texscale * 0.5),texscale / 2 + y * (-texscale * 0.51),texscale * 1,texscale,rotation + 180) + elseif s < 1 then + surface.DrawTexturedRectRotated(texscale / 2 + x * (-texscale * 0.5),texscale / 2 + y * (-texscale * 0.51),texscale * 0.9,texscale,rotation + 180) + end + elseif currentPhase == SF_MOON_FULL then + -- FULL MOON + elseif currentPhase < SF_MOON_LAST_QUARTER then + local s = 12 - (currentPhase - SF_MOON_FIRST_QUARTER) * 3 + surface.SetMaterial(Mask_75) + surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation) + local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation)) + surface.SetMaterial(Mask_0) + if s < 0.05 then + surface.DrawTexturedRectRotated(texscale / 2 + x * (texscale * 0.5),texscale / 2 + y * (texscale * 0.51),texscale * 1,texscale,rotation) + elseif s < 1 then + surface.DrawTexturedRectRotated(texscale / 2 + x * (texscale * 0.5),texscale / 2 + y * (texscale * 0.51),texscale * 0.9,texscale,rotation) + end + elseif currentPhase == SF_MOON_LAST_QUARTER then + surface.SetMaterial(Mask_50) + surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale,texscale,rotation + 180) + elseif currentPhase < SF_MOON_WANING_CRESCENT + 1 then + local s = (currentPhase - (SF_MOON_WANING_CRESCENT - 1)) * 3.5 + surface.SetMaterial(Mask_25) + surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation + 180) + if currentPhase >= SF_MOON_WAXIN_CRESCENT then + -- Ex step + local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation)) + surface.SetMaterial(Mask_0) + surface.DrawTexturedRectRotated(texscale / 2 + x * (texscale * 0.51),texscale / 2 + y * (texscale * 0.51),texscale,texscale,rotation) + end + end + -- Mask End + render.OverrideBlend(false) + render.OverrideAlphaWriteEnable( false ) + cam.End2D() + render.OverrideAlphaWriteEnable( false ) + render.PopRenderTarget() + CurrentMoonTexture:SetTexture("$basetexture",RTMoonTexture) + end + hook.Add("StormFox2.2DSkybox.Moon","StormFox2.RenderMoon",function(c_pos) + local phase = StormFox2.Moon.GetPhase() + if phase <= 0 then return end + local moonScale = StormFox2.Mixer.Get("moonSize",20) + local moonAng = StormFox2.Moon.GetAngle() + local N = moonAng:Forward() + local NN = -N + local sa = moonAng.y + -- Render texture + -- currentYaw + RenderMoonPhase( ((moonAng.p < 270 and moonAng.p > 90) and 180 or 0),phase) + local c = StormFox2.Mixer.Get("moonColor",Color(205,205,205)) + local a = StormFox2.Mixer.Get("skyVisibility",100) * 2 + -- Dark moonarea + -- PrintTable(CurrentMoonTexture:GetKeyValues()) + render.SetMaterial( CurrentMoonTexture ) + local aa = max(0,(3.125 * a) - 57.5) + render.DrawQuadEasy( N * 200, NN, moonScale , moonScale, Color(c.r,c.g,c.b, aa ), sa ) + end) + +if true then return end +-- Render Sky +local scale = 256 * 1.5 +local galixmat = Material("stormfox2/effects/nightsky3") +local c = Color(255,255,255) +hook.Add("StormFox2.2DSkybox.StarRender", "StormFox2.2DSkyBox.NS", function(c_pos) + render.SetMaterial( galixmat ) + c.a = StormFox2.Mixer.Get("starFade",100) * 2.55 + c.a = 255 + local p = (0.001) * StormFox2.Time.GetSpeed_RAW() + local ang = Angle((RealTime() * p) % 360,0,0) + local n = ang:Forward() * 256 +-- render.DrawQuadEasy(n, -n, scale * 4, scale, c, (ang.p < 270 and ang.p > 90) and 30 or 30 + 180) +-- render.DrawSphere(Vector(0,0,0), -10, 30, 30, c) + +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_date.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_date.lua new file mode 100644 index 0000000..c019994 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_date.lua @@ -0,0 +1,147 @@ +--[[ + Date + + SV StormFox2.Date.SetYearDay( nDay ) Sets the yearday. + SH StormFox2.Date.GetYearDay() Gets the yearday. + SH StormFox2.Date.GetWeekDay( bNumbers ) Returns the weekday. Returns a number if bNumbers is true. + SH StormFox2.Date.GetMonth( bNumbers ) Returns the month. Returns a number if bNumbers is true. + SH StormFox2.Date.GetShortMonth() Returns the month in a 3-letter string. + Sh StormFox2.Date.GetDay() Returns the day within the month. + Sh StormFox2.Date.Get( bNumbers ) Returns the date in string format. MM/DD or DD/MM depending on location and settings. Returns in numbers if bNumbers is true. +]] + +StormFox2.Setting.AddSV("real_time",false) +StormFox2.Date = {} + +if SERVER then + -- Sets the yearday. + function StormFox2.Date.SetYearDay( nDay ) + StormFox2.Network.Set("day", nDay % 365) + end +end + +-- Returns the day within the year. [0 - 364] +function StormFox2.Date.GetYearDay() + return StormFox2.Data.Get("day",0) +end + +local day, month, weekday = -1,-1,-1 +local function calcDate( nDay ) + local t = string.Explode("-", os.date( "%d-%m-%w", nDay * 86400 ), false) + return tonumber(t[1]),tonumber(t[2]),tonumber(t[3]) +end +hook.Add("StormFox2.data.change", "StormFox2.date.update", function(sKey, nDay) + if sKey ~= "day" then return end + day,month,weekday = calcDate( nDay ) +end) +do + local t = { + [0] = "Sunday", + [1] = "Monday", + [2] = "Tuesday", + [3] = "Wednesday", + [4] = "Thursday", + [5] = "Friday", + [6] = "Saturday" + } + -- Returns the weekday ["Monday" - "Sunday"] + function StormFox2.Date.GetWeekDay( bNumbers ) + if bNumbers then + return weekday + end + return t[ weekday ] or "Unknown" + end +end +do + local t = { + [1] = "January", + [2] = "February", + [3] = "March", + [4] = "April", + [5] = "May", + [6] = "June", + [7] = "July", + [8] = "August", + [9] = "September", + [10] = "October", + [11] = "November", + [12] = "December" + } + -- Returns the month ["January" - "December"]. + function StormFox2.Date.GetMonth( bNumbers ) + if bNumbers then + return month + end + return t[ month ] or "Unknown" + end +end +-- Returns the month in short ["Jan" - "Dec"] +function StormFox2.Date.GetShortMonth() + return string.sub(StormFox2.Date.GetMonth(),0,3) +end + +-- Returns the day of the month +function StormFox2.Date.GetDay() + return day +end + +-- Returns the date in string "day / month" + +local country = system.GetCountry() or "UK" +local crazy_countries = {"AS", "BT", "CN", "FM", "GU", "HU", "JP", "KP", "KR", "LT", "MH", "MN", "MP", "TW", "UM", "US", "VI"} +local default = table.HasValue(crazy_countries, country) +if CLIENT then + StormFox2.Setting.AddCL("use_monthday",default,"Display MM/DD instead of DD/MM.") +end + +local tOrdinal = {"st", "nd", "rd"} +local function ordinal(n) + local digit = tonumber(string.sub(n, -1)) + local two_dig = tonumber(string.sub(n,-2)) + if digit > 0 and digit <= 3 and two_dig ~= 11 and two_dig ~= 12 and two_dig ~= 13 then + return n .. tOrdinal[digit] + else + return n .. "th" + end +end + +function StormFox2.Date.Get( bNumbers ) + local m = StormFox2.Date.GetMonth( bNumbers ) + local d = StormFox2.Date.GetDay() + if bNumbers and m < 10 then + m = "0" .. m + elseif not bNumbers then + d = ordinal(d) + end + local rev + if CLIENT then + rev = StormFox2.Setting.GetCache("use_monthday",default) + else + rev = default + end + local e = bNumbers and " / " or " " + + if not rev then + return d .. e .. m + else + return m .. e .. d + end +end + +if SERVER then + -- Sets the starting day. + if StormFox2.Setting.Get("real_time", false) then + StormFox2.Network.Set("day", tonumber(os.date("%j"))) + else + StormFox2.Network.Set("day", cookie.GetNumber("sf_date", math.random(0,364))) + end + -- Saves the day for next start. + hook.Add("ShutDown","StormFox2.Day.Save",function() + cookie.Set("sf_date",StormFox2.Date.GetYearDay()) + end) + -- Sets the day to the current day, if real_time gets switched on. + StormFox2.Setting.Callback("real_time",function(switch) + if not switch then return end + StormFox2.Network.Set("day", os.date("%j")) + end,"sf_convar_data") +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_defaultgamemodes.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_defaultgamemodes.lua new file mode 100644 index 0000000..dcc3431 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_defaultgamemodes.lua @@ -0,0 +1,79 @@ + +StormFox2.Setting.AddSV("random_round_weather",true,nil,"Weather") + +local gamemodes = {"terrortown"} +local isRGame = table.HasValue(gamemodes, engine.ActiveGamemode()) + +local nightBlock = false + +local function SelectRandom() + -- Temp + local tmin,tmax = StormFox2.Setting.Get("min_temp",-10), StormFox2.Setting.Get("max_temp",20) + StormFox2.Temperature.Set( math.random(tmin, tmax) ) + -- Wind + StormFox2.Wind.SetForce( math.random(1, 20)) + StormFox2.Wind.SetYaw( math.random(360)) + -- Select random weather + local w_name + local w_p = math.Rand(0.4, 0.9) + if math.random(0,10) > 5 then + w_name = table.Random(StormFox2.Weather.GetAllSpawnable()) + elseif math.random(1, 2) > 1 then + w_name = "Cloud" + else + w_name = "Clear" + end + local w_t = StormFox2.Weather.Get(w_name) + if w_t.thunder and w_t.thunder(w_p) then + StormFox2.Thunder.SetEnabled( true, w_t.thunder(w_p), math.random(1,3) * 60 ) + else + StormFox2.Thunder.SetEnabled( false ) + end + -- Set random time + local start = StormFox2.Setting.Get("start_time",-1) or -1 + if start < 0 then + if nightBlock then + StormFox2.Time.Set( math.random(500, 900 ) ) + w_p = math.Rand(0.4, 0.75) -- Reroll + else + StormFox2.Time.Set( math.random(60, 1080) ) + end + end + StormFox2.Weather.Set( w_name, w_p ) +end + +hook.Add("StormFox2.Settings.PGL", "StormFox2.DefaultGamemodeSettings", function() + local GM = gmod.GetGamemode() + if not StormFox2.Setting.Get("random_round_weather", true) then return end + if not isRGame and not GM.OnPreRoundStart then return end + if not GM.SF2_Settings then + GM.SF2_Settings = { + ["auto_weather"] = 0, + ["hide_forecast"] = 1, + ["openweathermap_enabled"] = 0, + ["time_speed"] = 1, + ["maplight_auto"] = 1 + } + -- These gamemodes are quick-roundbased. 2~6 mins or so. Block the exspensive light-changes. + if not StormFox2.Ent.light_environments then + GM.SF2_Settings["allow_weather_lightchange"] = 0 + nightBlock = true + end + end + if GM.PreRoundStart then + _SFGMPRERS = _SFGMPRERS or GM.PreRoundStart + function GM.PreRoundStart( ... ) + _SFGMPRERS( ... ) + if not StormFox2.Setting.Get("random_round_weather") then return end + SelectRandom() + end + end +end) + +-- Random TTT round +if SERVER then + hook.Add("TTTPrepareRound", "StormFox2.TTT", function() + if not StormFox2.Setting.Get("random_round_weather") then return end + SelectRandom() + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_heavens.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_heavens.lua new file mode 100644 index 0000000..7d18383 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_heavens.lua @@ -0,0 +1,299 @@ +--[[------------------------------------------------------------------------- + StormFox2.Sun.SetTimeUp(nTime) Sets how long the sun is on the sky. + StormFox2.Sun.IsUp() Returns true if the sun is on the sky. + + + StormFox2.Moon.SetTimeUp(nTime) Sets how long the moon is on the sky. + +---------------------------------------------------------------------------]] +local clamp = math.Clamp + +StormFox2.Sun = StormFox2.Sun or {} +StormFox2.Moon = StormFox2.Moon or {} +StormFox2.Sky = StormFox2.Sky or {} + SF_SKY_DAY = 0 + SF_SKY_SUNRISE = 1 + SF_SKY_SUNSET = 2 + SF_SKY_CEVIL = 3 + SF_SKY_BLUE_HOUR = 4 + SF_SKY_NAUTICAL = 5 + SF_SKY_ASTRONOMICAL = 6 + SF_SKY_NIGHT = 7 + +StormFox2.Setting.AddSV("sunyaw",88,nil, "Effects", 0, 360) +StormFox2.Setting.AddSV("moonlock",true,nil,"Effects") +local phase = StormFox2.Setting.AddSV("moonphase",true,nil,"Effects") + +StormFox2.Setting.AddSV("enable_skybox",true,nil, "Effect") +StormFox2.Setting.AddSV("use_2dskybox",false,nil, "Effects") +StormFox2.Setting.AddSV("overwrite_2dskybox","",nil, "Effects") + +if CLIENT then -- From another file + StormFox2.Setting.AddSV("darken_2dskybox", false, nil, "Effect") +end + +-- Sun and Sun functions + --[[------------------------------------------------------------------------- + Returns the time when the sun rises. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.GetSunRise() + return StormFox2.Setting.Get("sunrise") + end + --[[------------------------------------------------------------------------- + Returns the time when the sun sets. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.GetSunSet() + return StormFox2.Setting.Get("sunset") + end + --[[ + Returns the time when sun is at its higest + ]] + function StormFox2.Sun.GetSunAtHigest() + return (StormFox2.Sun.GetSunRise() + StormFox2.Sun.GetSunSet()) / 2 + end + --[[------------------------------------------------------------------------- + Returns the sun-yaw. (Normal 90) + ---------------------------------------------------------------------------]] + function StormFox2.Sun.GetYaw() + return StormFox2.Setting.Get("sunyaw") + end + --[[------------------------------------------------------------------------- + Returns true if the sun is on the sky. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.IsUp(nTime) + return StormFox2.Time.IsBetween(StormFox2.Sun.GetSunRise(), StormFox2.Sun.GetSunSet(),nTime) + end + --[[------------------------------------------------------------------------- + Returns the sun-size. (Normal 30) + ---------------------------------------------------------------------------]] + function StormFox2.Sun.GetSize() + return StormFox2.Mixer.Get("sun_size",30) + end + --[[------------------------------------------------------------------------- + Returns the sun-color. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.GetColor() + return StormFox2.Mixer.Get("sun_color",Color(255,255,255)) + end + local sunVisible = 0 + --[[------------------------------------------------------------------------- + Returns the sunangle for the current or given time. + ---------------------------------------------------------------------------]] + local function GetSunPitch(nTime) + local p = StormFox2.Time.GetCycleTime() * 360 + return p + end + function StormFox2.Sun.GetAngle(nTime) + local a = Angle(-GetSunPitch(nTime),StormFox2.Sun.GetYaw(),0) + return a + end + +-- We need a sun-stamp. We can't go by time. + local sunOffset = 5 -- Sunset needs to be pushed + local stamp = { + [0] = {SF_SKY_SUNRISE,6,"SunRise"}, -- 6 + [6] = {SF_SKY_DAY, 168,"Day"}, -- 180 - 6 + [174 + sunOffset] = {SF_SKY_SUNSET,6,"SunSet"}, -- 174 + 6 + [180 + sunOffset] = {SF_SKY_CEVIL,4,"Cevil"}, -- 4 + [184 + sunOffset] = {SF_SKY_BLUE_HOUR,2,"Blue Hour"}, -- 6 + [186 + sunOffset] = {SF_SKY_NAUTICAL,6,"Nautical"}, -- 12 + [192 + sunOffset] = {SF_SKY_ASTRONOMICAL,6,"Astronomical"}, -- 18 + [198 + sunOffset] = {SF_SKY_NIGHT,168,"Night"}, -- 144 + [342] = {SF_SKY_ASTRONOMICAL,6,"Astronomical"}, -- 18 + [348] = {SF_SKY_NAUTICAL,6,"Nautical"}, -- 12 + [354] = {SF_SKY_BLUE_HOUR,2,"Blue Hour"}, -- 6 + [356] = {SF_SKY_CEVIL,4,"Cevil"}, -- 4 + [360] = {SF_SKY_SUNRISE,6,"SunRise"}, + [370] = {SF_SKY_SUNRISE,6,"SunRise"}, -- 6 + } + -- Make an array of keys + local stamp_arr = table.GetKeys(stamp) + table.sort(stamp_arr, function(a,b) return a < b end) + -- Fix calculating second argument + for id, pitch in pairs(stamp_arr) do + local n_pitch = stamp_arr[id + 1] or stamp_arr[1] + local ad = math.AngleDifference(n_pitch, pitch) + if ad == 0 then + ad = stamp[n_pitch][2] + end + stamp[pitch][2] = ad + end + -- Calculate the sunsize + local lC,lCV = -1,-1 + local function GetsunSize() + if lC > CurTime() then return lCV end + lC = CurTime() + 2 + local x = StormFox2.Sun.GetSize() + lCV = (-0.00019702 * x^2 + 0.149631 * x - 0.0429803) / 2 + return lCV + end + -- Returns: Stamp-ptch, Sun-pitch, Stamp-pitch + local function GetStamp(nTime,nOffsetDegree) + local sunSize = GetsunSize() + local p = ( GetSunPitch(nTime) + (nOffsetDegree or 0) ) % 360 + -- Offset the sunsize + if p > 90 and p < 270 then -- Sunrise + p = (p - sunSize) % 360 + else -- Sunset + p = (p + sunSize) % 360 + end + -- Locate the sunstamp by angle + local c_pitch, id = -1 + for n, pitch in pairs(stamp_arr) do + if p >= pitch and c_pitch < pitch then + id = n + c_pitch = pitch + end + end + return stamp_arr[id], p, stamp_arr[id + 1] or stamp_arr[1] + end + --[[------------------------------------------------------------------------- + Returns the sun-stamp. + First argument: + 0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour + 4 = nautical, 5 = astronomical, 6 = night + + Second argument: + Pitch + + Second argument + Next stamp + 0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour + 4 = nautical, 5 = astronomical, 6 = night + ---------------------------------------------------------------------------]] + local function GetStamp(nTime,nOffsetDegree) + local sunSize = GetsunSize() + local p = ( GetSunPitch(nTime) + (nOffsetDegree or 0) ) % 360 + -- Offset the sunsize + if p > 90 and p < 270 then -- Sunrise + p = (p - sunSize) % 360 + else -- Sunset + p = (p + sunSize) % 360 + end + -- Locate the sunstamp by angle + local c_pitch, id = -1 + for n, pitch in pairs(stamp_arr) do + if p >= pitch and c_pitch < pitch then + id = n + c_pitch = pitch + end + end + if not id then + return SF_SKY_DAY, p, SF_SKY_CEVIL + end + return stamp_arr[id],p,stamp_arr[id + 1] or stamp_arr[1] + end + --[[------------------------------------------------------------------------- + Returns the sun-stamp. + First argument: + 0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour + 4 = nautical, 5 = astronomical, 6 = night + + Second argument: + Percent used of the current stamp + + Third argument + Next stamp + 0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour + 4 = nautical, 5 = astronomical, 6 = night + + Forth argument + Stamps pitch length + ---------------------------------------------------------------------------]] + local nP = 0 + function StormFox2.Sky.GetStamp(nTime,nOffsetDegree) + local c_stamp,p,n_stamp = GetStamp(nTime,nOffsetDegree) -- p is current angle + local per = (p - c_stamp) / (n_stamp - c_stamp) + return stamp[c_stamp][1], per, stamp[n_stamp][1],stamp[c_stamp][2] -- 1 = Stamp, 2 = Type of stamp + end + -- Returns the last stamp + local lastStamp = 0 + function StormFox2.Sky.GetLastStamp() + return lastStamp + end + -- Sky hook. Used to update the sky colors and other things. + local nextStamp = -1 + hook.Add("StormFox2.Time.Changed","StormFox2.Sky.UpdateStamp",function() + nextStamp = -1 + end) + timer.Create("StormFox2.Sky.Stamp", 1, 0, function() + --local c_t = CurTime() + --if c_t < nextStamp then return end + local stamp,n_t = StormFox2.Sky.GetStamp(nil,6) -- Look 6 degrees into the furture so we can lerp the colors. + --nextStamp = c_t + (n_t * SunDelta) / StormFox2.Time.GetSpeed() + --[[------------------------------------------------------------------------- + This hook gets called when the sky-stamp changes. This is used to change the sky-colors and other things. + First argument: + 0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour + 4 = nautical, 5 = astronomical, 6 = night + + Second argument: + The lerp-time to change the variable. + ---------------------------------------------------------------------------]] + if lastStamp == stamp then return end -- Don't call it twice. + lastStamp = stamp + local delta = 180 / (StormFox2.Setting.Get("sunset") - StormFox2.Setting.Get("sunrise")) + hook.Run("StormFox2.Sky.StampChange", stamp, 6 / math.max(1, delta) ) + end) +-- Moon and its functions + --[[------------------------------------------------------------------------- + Moon phases + ---------------------------------------------------------------------------]] + SF_MOON_NEW = 0 + SF_MOON_WAXIN_CRESCENT = 1 + SF_MOON_FIRST_QUARTER = 2 + SF_MOON_WAXING_GIBBOUS = 3 + SF_MOON_FULL = 4 + SF_MOON_WANING_GIBBOUS = 5 + SF_MOON_LAST_QUARTER = 6 + SF_MOON_WANING_CRESCENT = 7 + --[[------------------------------------------------------------------------- + Returns the moon phase for the current day + ---------------------------------------------------------------------------]] + function StormFox2.Moon.GetPhase() + if not phase:GetValue() then return SF_MOON_FULL end + return StormFox2.Data.Get("moon_phase",SF_MOON_FULL) + end + --[[------------------------------------------------------------------------- + Returns the moon phase name + ---------------------------------------------------------------------------]] + function StormFox2.Moon.GetPhaseName(nTime) + local n = StormFox2.Moon.GetPhase(nTime) + if n == SF_MOON_NEW then return "New Moon" end + if n == SF_MOON_WAXIN_CRESCENT then return "Waxin Crescent" end + if n == SF_MOON_FIRST_QUARTER then return "First Quarter" end + if n == SF_MOON_WAXING_GIBBOUS then return "Waxing Gibbous" end + if n == SF_MOON_FULL then return "Full Moon" end + if n == SF_MOON_WANING_GIBBOUS then return "Waning Gibbous" end + if n == SF_MOON_LAST_QUARTER then return "Last Quarter" end + if n == SF_MOON_WANING_CRESCENT then return "Waning Crescent" end + end + + --[[------------------------------------------------------------------------- + Returns the angle for the moon. First argument can also be a certain time. + ---------------------------------------------------------------------------]] + local tf = 0 + local a = 7 / 7.4 + function StormFox2.Moon.GetAngle(nTime) + local p = 180 + StormFox2.Time.GetCycleTime() * 360 + if StormFox2.Setting.Get("moonlock",false) then + return Angle(-p % 360, StormFox2.Sun.GetYaw(),0) + end + --if true then return Angle(200,StormFox2.Data.Get("sun_yaw",90),0) end + local rDay = StormFox2.Date.GetYearDay() + p = p + ( StormFox2.Moon.GetPhase() - 4 ) * 45 + return Angle(-p % 360,StormFox2.Sun.GetYaw(),0) + end + -- It might take a bit for the server to tell us the day changed. + hook.Add("StormFox2.data.change", "StormFox2.moon.datefix", function(sKey, nDay) + if sKey ~= "day" then return end + tf = 0 + end) + --[[------------------------------------------------------------------------- + Returns true if the moon is up. + ---------------------------------------------------------------------------]] + function StormFox2.Moon.IsUp() + local t = StormFox2.Moon.GetAngle().p + local s = StormFox2.Mixer.Get("moonSize",20) / 6.9 + return t > 180 - s or t < s + end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_maplight.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_maplight.lua new file mode 100644 index 0000000..612e841 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_maplight.lua @@ -0,0 +1,750 @@ +StormFox2.Setting.AddSV("maplight_min",0,mil, "Effects", 0, 100) +StormFox2.Setting.AddSV("maplight_max",80,nil, "Effects", 0, 100) +StormFox2.Setting.AddSV("maplight_smooth",true,nil, "Effects",0,1) + +StormFox2.Setting.AddSV("maplight_auto", true, nil, "Effects") +StormFox2.Setting.AddSV("maplight_lightenv", false,nil, "Effects") +StormFox2.Setting.AddSV("maplight_colormod", false,nil, "Effects") +StormFox2.Setting.AddSV("maplight_dynamic", false,nil, "Effects") +StormFox2.Setting.AddSV("maplight_lightstyle", false,nil, "Effects") + +--[[----------------------------------------------------------------]]-- +StormFox2.Setting.AddSV("maplight_updaterate",game.SinglePlayer() and 6 or 3,nil, "Effects") + + +StormFox2.Setting.AddSV("overwrite_extra_darkness",-1,nil, "Effects", -1, 1) +StormFox2.Setting.SetType( "overwrite_extra_darkness", "special_float") + +StormFox2.Setting.AddSV("allow_weather_lightchange",true,nil, "Weather") + +if CLIENT then + StormFox2.Setting.AddCL("extra_darkness",render.SupportsPixelShaders_2_0(),nil,"Effects",0,1) + StormFox2.Setting.AddCL("extra_darkness_amount",0.75,nil, "Effects",0,1) + StormFox2.Setting.SetType( "extra_darkness_amount", "float" ) +end + +-- Converts a lightlvl to char +local function convertToBZ( nNum ) -- From b to z + local byte = math.Round(6 * nNum / 25 + 98) + return string.char(byte) +end +local function convertToAZ( nNum ) + return string.char(97 + nNum / 4) +end + +local SetLightStyle, SetLightEnv +if SERVER then + -- Sets the lightstyle + local function _SetLightStyle( char ) + engine.LightStyle(0,char) + net.Start(StormFox2.Net.LightStyle) + net.WriteUInt(string.byte(char), 7) + net.Broadcast() + end + local var = 'm' + -- Making it a timer, gives other scripts time to overwrite it. + SetLightStyle = function( char ) + if char == 'a' then char = 'b' end -- 'a' will break all light on the map + if char == var then return end + var = char + if timer.Exists("sf_lightstyleset") then return end + timer.Create( "sf_lightstyleset", 1, 1, function() + _SetLightStyle( var ) + end) + end + local oldLight = 'm' + SetLightEnv = function( char) + if not StormFox2.Ent.light_environments then return end + if char == oldLight then return end + oldLight = char + for _,light in ipairs(StormFox2.Ent.light_environments) do -- Doesn't lag + if not IsValid(light) then continue end + light:Fire("FadeToPattern", char ,0) + if char == "a" then + light:Fire("TurnOff","",0) + else + light:Fire("TurnOn","",0) + end + light:Activate() + end + end +else + local last_sv + net.Receive(StormFox2.Net.LightStyle, function(len) + local c_var = net.ReadUInt(7) + if last_sv and last_sv == c_var then return end -- No need + last_sv = c_var + timer.Simple(1, function() + render.RedownloadAllLightmaps( true, true ) + --MsgC(color_white,"Redownload ligthmap [" .. last_sv .. "]\n") + end) + end) +end + +--[[ Diffrent types of maplight options. + available = Return true to indicate this option is available. No function = always. + on = When you switch it on + off = When you switch it off + change = When the lightvl changes. Secondary is when the smoothness ends. +]] + +local mapLights = {} +local e_light_env = 0 +local e_lightstyle = 1 +local e_colormod = 2 +local e_lightdynamic = 3 + +local lastSetting = {} +-- light_environment (SV) Fast, but not all maps have it + mapLights[e_light_env] = {} + mapLights[e_light_env]["available"] = function() + return StormFox2.Ent.light_environments and true or false + end + if SERVER then + mapLights[e_light_env]["on"] = function(lightLvl) + SetLightEnv( convertToAZ(lightLvl) ) + end + mapLights[e_light_env]["off"] = function(lightLvl) + if lastSetting[e_lightdynamic] then + SetLightEnv('b') + else + SetLightEnv('m') + end + end + mapLights[e_light_env]["change"] = mapLights[e_light_env]["on"] + end + +-- light_style (SV) Laggy on large maps + mapLights[e_lightstyle] = {} + if SERVER then + mapLights[e_lightstyle]["on"] = function(lightLvl) + SetLightStyle( convertToBZ(lightLvl) ) + end + mapLights[e_lightstyle]["off"] = function(lightLvl) + SetLightStyle('m') + timer.Remove( "sf_lightstyle" ) + end + local nextSet + mapLights[e_lightstyle]["change"] = function(lightLvl, full) -- We make this a 30sec timer, since lightstyle is so slow and laggy. + if not full then return end -- Ignore 'smoothness' light + if timer.Exists("sf_lightstyle") then + nextSet = convertToBZ(lightLvl) + else + SetLightStyle(convertToBZ(lightLvl)) + timer.Create("sf_lightstyle", 5, 1, function() + if not nextSet then return end + SetLightStyle(nextSet) + nextSet = nil + end) + end + end + end + +-- ColorMod (CL) A fast alternative + mapLights[e_colormod] = {} + local cmod_on + if CLIENT then + local fardetarget = 0 + mapLights[e_colormod]["on"] = function(lightLvl) + cmod_on = (1 - (lightLvl / 80)) * 0.7 + fardetarget = 0 + end + mapLights[e_colormod]["off"] = function(lightLvl) + cmod_on = nil + end + mapLights[e_colormod]["change"] = function(lightLvl) + cmod_on = (1 - (lightLvl / 80)) * 0.7 + end + local tab = { + [ "$pp_colour_addr" ] = -0.09, + [ "$pp_colour_addg" ] = -0.1, + [ "$pp_colour_addb" ] = -0.05, + [ "$pp_colour_brightness" ] = 0, + [ "$pp_colour_contrast" ] = 1, + [ "$pp_colour_colour" ] = 1, + [ "$pp_colour_mulr" ] = 0, + [ "$pp_colour_mulg" ] = 0, + [ "$pp_colour_mulb" ] = 0 + } + + hook.Add( "RenderScreenspaceEffects", "StormFox2.MapLightCMod", function() + if not cmod_on then return end + local darkness = cmod_on + local env = StormFox2.Environment.Get() + if not env.outside then + if not env.nearest_outside then + darkness = 0 + else + local dis = 1 - ( env.nearest_outside:DistToSqr(StormFox2.util.RenderPos() or EyePos()) / 90000 ) + dis = math.Clamp(dis, 0, 1) + darkness = darkness * 0.2 + darkness * 0.8 * dis + end + end + fardetarget = math.Approach(fardetarget, darkness, FrameTime() * 0.5) + local r_var = fardetarget + local tL = math.min(255,StormFox2.Thunder.GetLight() or 0) / 255 + if tL > 0 then + r_var = r_var * (1 - tL) + end + tab[ "$pp_colour_addr" ] = -0.09 * r_var + tab[ "$pp_colour_addg" ] = -0.1 * r_var + tab[ "$pp_colour_addb" ] = -0.05 * r_var + tab[ "$pp_colour_brightness" ] = 0 -r_var * 0.15 + tab[ "$pp_colour_colour" ] = 1 - r_var * 0.5 -- We're not good at seeing colors in the dark. + tab[ "$pp_colour_contrast" ] = 1 + r_var * 0.08 -- Lower the contrast, however; Bright things are still bright + DrawColorModify( tab ) + end) + end + +-- Dynamic Light + local dsize = 3000 + local dlengh = 9000 - 30 + mapLights[e_lightdynamic] = {} + local dLight = -1 + local function tick() + if not SF_SUN_PROTEX then return end + local vis = dLight + + -- While the best option is to delete the project-texture. + -- Sadly, doing so would allow another mod to override it, as they're limited. + if vis < 0 then return end + local sunang = StormFox2.Sun.GetAngle() -- Angle towards sun + local p = sunang.p % 360 + local tL = math.min(255,StormFox2.Thunder.GetLight() or 0) + if tL > 0 then + p = 270 + sunang.p = p + vis = tL / 2 + else + if p > 350 then -- 15 before + SF_SUN_PROTEX:SetBrightness(0) + SF_SUN_PROTEX:Update() + return + elseif p > 345 then + vis = vis * math.Clamp(-p / 5 + 70, 0, 1) + elseif p <= 190 then + SF_SUN_PROTEX:SetBrightness(0) + SF_SUN_PROTEX:Update() + return + elseif p <= 195 then + vis = vis * math.Clamp(p / 5 - 38, 0, 1) + end + end + local sunnorm = sunang:Forward() -- Norm of sun + local viewpos = StormFox2.util.RenderPos() -- Point of render + + local pos = viewpos + sunnorm * dlengh + pos.x = math.Round(pos.x / 50) * 50 + pos.y = math.Round(pos.y / 50) * 50 + + SF_SUN_PROTEX:SetPos(pos) + if math.Round(sunang.p) == 0 then -- All source light gets a bit, glitchy at 0 or 180 pitch + SF_SUN_PROTEX:SetAngles(Angle(179,sunang.y,0)) + else + SF_SUN_PROTEX:SetAngles(Angle(sunang.p + 180,sunang.y,0)) + end + SF_SUN_PROTEX:SetBrightness(vis * 2) + SF_SUN_PROTEX:Update() + end + mapLights[e_lightdynamic]["on"] = function(lightLvl) + if SERVER then + SetLightStyle( 'b' ) + SetLightEnv('b') + StormFox2.Shadows.SetDisabled( true ) + else + RunConsoleCommand("r_flashlightdepthres", 8192) + dLight = lightLvl + if IsValid(SF_SUN_PROTEX) then + SF_SUN_PROTEX:Remove() + end + SF_SUN_PROTEX = ProjectedTexture() + SF_SUN_PROTEX:SetTexture("stormfox2/effects/dynamic_light") + SF_SUN_PROTEX:SetOrthographic( true , dsize, dsize, dsize, dsize) + SF_SUN_PROTEX:SetNearZ(0) + SF_SUN_PROTEX:SetFarZ( 12000 ) + SF_SUN_PROTEX:SetQuadraticAttenuation( 0 ) + SF_SUN_PROTEX:SetShadowDepthBias(0.000005) + SF_SUN_PROTEX:SetShadowFilter(0.05) -- Meed tp blur the shadows a bit. + SF_SUN_PROTEX:SetShadowSlopeScaleDepthBias(2) + SF_SUN_PROTEX:SetEnableShadows(true) + hook.Add("Think", "StormFox2.MapLightDynamic", tick) + end + end + mapLights[e_lightdynamic]["off"] = function(lightLvl) + if SERVER then + SetLightStyle( 'm' ) + SetLightEnv('m') + StormFox2.Shadows.SetDisabled( false ) + else + dLight = 0 + if SF_SUN_PROTEX then + SF_SUN_PROTEX:Remove() + SF_SUN_PROTEX = nil + end + hook.Remove("Think", "StormFox2.MapLightDynamic") + end + end + if CLIENT then + mapLights[e_lightdynamic]["change"] = function(lightlvl) + dLight = lightlvl + end + end +-- MapLight functions + local function EnableMapLight(str, lightlvl) + if not mapLights[str] then + error("Unknown light") + end + if not mapLights[str]["on"] then return end + mapLights[str]["on"](lightlvl) + end + local function DisableMapLight(str, lightlvl) + if not mapLights[str] then + error("Unknown light") + end + if not mapLights[str]["off"] then return end + mapLights[str]["off"](lightlvl) + end + local function ChangeMapLight(str, lightlvl, full) + if not mapLights[str] then + error("Unknown light") + end + if not mapLights[str]["change"] then return end + mapLights[str]["change"](lightlvl, full) + end +-- Function that will remember and enable / disable a setting. +local function checkSetting(e_type, bool, lightlvl) + if bool and lastSetting[e_type] then return end + if not bool and not lastSetting[e_type] then return end + if bool then + EnableMapLight(e_type, lightlvl) + else + DisableMapLight(e_type, lightlvl) + end + lastSetting[e_type] = bool +end +-- Called when one of the settings change +local function SettingMapLight( lightlvl ) + -- Stop all light-settings when SF gets turned off. + if not StormFox2.Setting.GetCache("enable", true) then + checkSetting(e_lightstyle, false, lightlvl) + checkSetting(e_colormod, false, lightlvl) + checkSetting(e_lightdynamic,false, lightlvl) + checkSetting(e_light_env, false, lightlvl) + return + end + -- Choose e_light_env or e_colormod + if StormFox2.Setting.Get("maplight_auto") then + checkSetting(e_lightdynamic,false, lightlvl) + checkSetting(e_lightstyle, false, lightlvl) + if StormFox2.Ent.light_environments then + checkSetting(e_colormod, false, lightlvl) + checkSetting(e_light_env, true, lightlvl) + else + checkSetting(e_light_env, false, lightlvl) + checkSetting(e_colormod, true, lightlvl) + end + else + -- Can be enabled for all + checkSetting(e_colormod, StormFox2.Setting.Get("maplight_colormod", false), lightlvl) + -- Choose dynamic or lightstyle + if StormFox2.Setting.Get("maplight_dynamic", false) then + checkSetting(e_lightstyle, false, lightlvl) + checkSetting(e_light_env, false, lightlvl) + checkSetting(e_lightdynamic,true, lightlvl) + else + checkSetting(e_lightdynamic,false, lightlvl) + checkSetting(e_lightstyle, StormFox2.Setting.Get("maplight_lightstyle", false),lightlvl) + checkSetting(e_light_env, StormFox2.Setting.Get("maplight_lightenv", false), lightlvl) + end + end +end +-- Called when lightlvl has changed +local function ChangedMapLight( lightlvl, isSmoothLight) + local LastUpdate = not isSmoothLight + if StormFox2.Setting.GetCache("maplight_auto", true) then + if StormFox2.Ent.light_environments then + ChangeMapLight(e_light_env, lightlvl, LastUpdate) + return true + else + ChangeMapLight(e_colormod, lightlvl, LastUpdate) + end + else + local a = false + if StormFox2.Setting.GetCache("maplight_dynamic", false) then + ChangeMapLight(e_lightdynamic, lightlvl, LastUpdate) + a = true + end + if StormFox2.Setting.GetCache("maplight_lightstyle", false) then + ChangeMapLight(e_lightstyle, lightlvl, LastUpdate) + a = true + end + if StormFox2.Setting.GetCache("maplight_lightenv", false) then + ChangeMapLight(e_light_env, lightlvl, LastUpdate) + a = true + end + if StormFox2.Setting.GetCache("maplight_colormod", false) then + ChangeMapLight(e_colormod, lightlvl, LastUpdate) + end + return a + end +end + +-- Sets the detail-light +local SetDetailLight +if CLIENT then + -- Detail MapLight + -- Use default detail material (Just in case) + local detailstr = {["detail/detailsprites"] = true} + -- Find map detail from BSP + local mE = StormFox2.Map.Entities()[1] + if mE and mE["detailmaterial"] then + detailstr[mE["detailmaterial"]] = true + end + -- Add EP2 by default + local ep2m = Material("detail/detailsprites_ep2") + if ep2m and not ep2m:IsError() then + detailstr["detail/detailsprites_ep2"] = true + end + local detail = {} + for k,v in pairs(detailstr) do + table.insert(detail, (Material(k))) + end + SetDetailLight = function(lightAmount) + lightAmount = math.Clamp(lightAmount / 100, 0, 1) + local v = Vector(lightAmount,lightAmount,lightAmount) + for i, m in ipairs(detail) do + m:SetVector("$color",v) + end + end +end + +-- Returns light-variables +local f_mapLight = StormFox2.Setting.GetCache("maplight_max",80) +local f_mapLightRaw = 100 +local c_last_char = 'm' +function StormFox2.Map.GetLight() + return f_mapLight +end +function StormFox2.Map.GetLightRaw() -- Ignores lightamount-settings + return f_mapLightRaw +end +function StormFox2.Map.GetLightChar() + return c_last_char +end + +local function getMaxLight(curLight) + if curLight <= 0 then return 0 end + local n = StormFox2.Setting.GetCache("maplight_max",80) + if n <= 0 then return 0 end + return math.Clamp(curLight / n, 0, 1) * 100 +end + +-- On launch. Setup light +local init = false +do + local chicken, egg = false, false + local function tryInit() + if not chicken or not egg then return end + SettingMapLight(f_mapLight) + hook.Run("StormFox2.lightsystem.new", f_mapLight, getMaxLight(f_mapLight)) + if CLIENT then SetDetailLight(f_mapLight) end + init = true + end + hook.Add("StormFox2.PostEntityScan", "stormfox2.lightsystem.init", function() + chicken = true + tryInit() + end) + hook.Add("stormfox2.postinit", "stormfox2.lightsystem.init2", function() + egg = true + tryInit() + end) +end +-- On settings change +for _, conv in ipairs({"enable","maplight_auto", "maplight_lightenv", "maplight_colormod", "maplight_dynamic", "maplight_lightstyle"}) do + StormFox2.Setting.Callback(conv,function(var) + SettingMapLight(f_mapLight) + end, conv .. "MLCheck") +end + +-- Allows us to use SetLight, without removing the lerp. +local lil = true +local function SetLightInternal(f, isSmoothLight) + if f < 0 then f = 0 elseif + f > 100 then f = 100 end + if f_mapLight == f and lil == isSmoothLight then return end -- Ignore + f_mapLight = f + lil = isSmoothLight + c_last_char = convertToAZ(f) + if not init then return end + -- 2D Skybox + if SERVER then + local str = StormFox2.Setting.GetCache("overwrite_2dskybox","") + local use_2d = StormFox2.Setting.GetCache("use_2dskybox",false) + if use_2d and str ~= "painted" then + StormFox2.Map.Set2DSkyBoxDarkness( f * 0.009 + 0.1, true ) + end + end + -- SetMapLight + ChangedMapLight(f, isSmoothLight) + if CLIENT then SetDetailLight(f) end + -- Tell scripts to update + hook.Run("StormFox2.lightsystem.new", f, getMaxLight(f)) +end + +local t = {} +function StormFox2.Map.SetLight( f, ignore_lightstyle ) + t = {} -- Remove light lerping + SetLightInternal(f, ignore_lightstyle) +end + +--[[ Lerp light + People complain if we use lightStyle too much (Even with settings), so I've removed lerp from maps without light_environment. +]] +-- Lerps the light towards the goal. Make "not_final" true if you're calling it rapidly. +function StormFox2.Map.SetLightLerp(f, nLerpTime, isSmooth ) + local smooth = StormFox2.Setting.GetCache("maplight_smooth",true) + local num = StormFox2.Setting.GetCache("maplight_updaterate", 3) + -- No lights to smooth and/or setting is off + local _5sec = 0.08 * StormFox2.Time.GetSpeed_RAW() + t = {} + if not smooth or nLerpTime <= _5sec or not f_mapLight or num <= 1 then + SetLightInternal( f ) + return + end + -- Are we trying to lerp towards current value? + if f_mapLight and f_mapLight == f then + return + end + -- Start lerping .. + -- We make a time-list of said values. + local st = StormFox2.Time.Get() -- Start Time + local st_lerpt = nLerpTime / num -- Each "step"'s time + -- Too fast of a light change. Can bug out. + if st_lerpt < 5 then -- Set the each step to min 5 seconds. + st_lerpt = 5 + num = math.floor(nLerpTime / 5) + if num <= 1 then -- Only change once. + SetLightInternal( f ) + return + end + end + local st_lerp = math.abs(f_mapLight - f) / num -- Each "step"'s value + -- from: f_mapLight + -- to: f + for i = 0, num - 1 do + table.insert(t, { + (st + (i * st_lerpt)) % 1440, -- Time when applied + math.floor(math.Approach(f_mapLight, f, st_lerp * (i + 1))),-- The light value + i ~= num - 1 or isSmooth -- Isn't last + }) + end + --print("From:",f_mapLight, "TO:", f, "step:",st_lerp, "nums:",num) + --StormFox2.Map.SetLight( math.Approach(f_mapLight, f, n), true ) +end +timer.Create("StormFox2.lightupdate", 1, 0, function() + if #t <= 0 then return end + local n = t[1] + local time = StormFox2.Time.Get() + if n[1] > time or math.abs(time - n[1]) > 720 then return end -- Wait. + -- Trigger + local v = table.remove(t, 1) + SetLightInternal( v[2], v[3] ) -- Set the light, and lightsystel if last. + --print("SetLight", v[2], v[3]) +end) +if SERVER then + -- Control light + hook.Add("StormFox2.weather.postchange", "StormFox2.weather.setlight", function( sName ,nPercentage, nDelta ) + local night, day + if StormFox2.Setting.GetCache("allow_weather_lightchange") then + night,day = StormFox2.Data.GetFinal("mapNightLight", 0), StormFox2.Data.GetFinal("mapDayLight",100) -- Maplight + else + local c = StormFox2.Weather.Get("Clear") + night,day = c:Get("mapNightLight",0), c:Get("mapDayLight",80) -- Maplight + end + local minlight,maxlight = StormFox2.Setting.GetCache("maplight_min",0),StormFox2.Setting.GetCache("maplight_max",80) -- Settings + local smooth = StormFox2.Setting.GetCache("maplight_smooth",game.SinglePlayer()) + -- Calc maplight + local isSmooth = false + local stamp, mapLight = StormFox2.Sky.GetLastStamp() + if stamp >= SF_SKY_CEVIL then + mapLight = night + elseif stamp <= SF_SKY_DAY then + mapLight = day + else + local delta = math.abs( SF_SKY_DAY - SF_SKY_CEVIL ) + local f = StormFox2.Sky.GetLastStamp() / delta + if smooth then + mapLight = Lerp((f + 0.5) / 2, day, night) + isSmooth = true + elseif f <= 0.5 then + mapLight = day + else + mapLight = night + end + end + f_mapLightRaw = mapLight + -- Apply settings + local newLight = minlight + mapLight * (maxlight - minlight) / 100 + local sec = 15 * StormFox2.Time.GetSpeed_RAW() + StormFox2.Map.SetLightLerp(newLight, math.min(sec, nDelta or sec), isSmooth ) + end) + + -- Min and maxlight hotupdate + local function hotUpdate() + local night, day + if StormFox2.Setting.GetCache("allow_weather_lightchange") then + night,day = StormFox2.Data.GetFinal("mapNightLight", 0), StormFox2.Data.GetFinal("mapDayLight",100) -- Maplight + else + local c = StormFox2.Weather.Get("Clear") + night,day = c:Get("mapNightLight",0), c:Get("mapDayLight",80) -- Maplight + end + local minlight,maxlight = StormFox2.Setting.GetCache("maplight_min",0),StormFox2.Setting.GetCache("maplight_max",80) -- Settings + local smooth = StormFox2.Setting.GetCache("maplight_smooth",game.SinglePlayer()) + -- Calc maplight + local isSmooth = false + local stamp, mapLight = StormFox2.Sky.GetLastStamp() + if stamp >= SF_SKY_CEVIL then + mapLight = night + elseif stamp <= SF_SKY_DAY then + mapLight = day + else + local delta = math.abs( SF_SKY_DAY - SF_SKY_CEVIL ) + local f = StormFox2.Sky.GetLastStamp() / delta + if smooth then + mapLight = Lerp((f + 0.5) / 2, day, night) + isSmooth = true + elseif f <= 0.5 then + mapLight = day + else + mapLight = night + end + end + f_mapLightRaw = mapLight + -- Apply settings + local newLight = minlight + mapLight * (maxlight - minlight) / 100 + StormFox2.Map.SetLight( newLight ) + end + StormFox2.Setting.Callback("maplight_min", hotUpdate) + StormFox2.Setting.Callback("maplight_max", hotUpdate) + +else -- Fake darkness. Since some maps are bright + + hook.Add("StormFox2.weather.postchange", "StormFox2.weather.setlight", function( sName ,nPercentage, nDelta ) + if not StormFox2.Map or not StormFox2.Map.SetLightLerp then return end + local minlight,maxlight = StormFox2.Setting.GetCache("maplight_min",0),StormFox2.Setting.GetCache("maplight_max",80) -- Settings + local smooth = StormFox2.Setting.GetCache("maplight_smooth",game.SinglePlayer()) + local night, day + if StormFox2.Setting.GetCache("allow_weather_lightchange") then + night,day = StormFox2.Data.GetFinal("mapNightLight", 0), StormFox2.Data.GetFinal("mapDayLight",100) -- Maplight + else + local c = StormFox2.Weather.Get("Clear") + night,day = c:Get("mapNightLight",0), c:Get("mapDayLight",80) -- Maplight + end + -- Calc maplight + local isSmooth = false + local stamp, mapLight = StormFox2.Sky.GetLastStamp() + if stamp >= SF_SKY_CEVIL then + mapLight = night + elseif stamp <= SF_SKY_DAY then + mapLight = day + else + local delta = math.abs( SF_SKY_DAY - SF_SKY_CEVIL ) + local f = StormFox2.Sky.GetLastStamp() / delta + if smooth then + mapLight = Lerp((f + 0.5) / 2, day, night) + isSmooth = true + elseif f <= 0.5 then + mapLight = day + else + mapLight = night + end + end + f_mapLightRaw = mapLight + -- Apply settings + local newLight = minlight + mapLight * (maxlight - minlight) / 100 + local sec = 15 * StormFox2.Time.GetSpeed_RAW() + StormFox2.Map.SetLightLerp(newLight, math.min(sec, nDelta or sec), isSmooth ) + end) + + local function exp(n) + return n * n + end + local mat_screen = Material( "stormfox2/shader/pp_dark" ) + local mat_ColorMod = Material( "stormfox2/shader/color" ) + mat_ColorMod:SetTexture( "$fbtexture", render.GetScreenEffectTexture() ) + local texMM = GetRenderTargetEx( "_SF_DARK", -1, -1, RT_SIZE_FULL_FRAME_BUFFER, MATERIAL_RT_DEPTH_NONE, 0, 0, IMAGE_FORMAT_RGB888 ) + + -- Renders pp_dark + local function UpdateStencil( darkness ) + if not render.SupportsPixelShaders_2_0() then return end -- How old is the GPU!? + render.UpdateScreenEffectTexture() + render.PushRenderTarget(texMM) + render.Clear( 255 * darkness, 255 * darkness, 255 * darkness, 255 * darkness ) + render.ClearDepth() + render.OverrideBlend( true, BLEND_ONE_MINUS_SRC_COLOR, BLEND_ONE_MINUS_SRC_COLOR, 0, BLEND_ONE, BLEND_ZERO, BLENDFUNC_ADD ) + render.SetMaterial(mat_ColorMod) + render.DrawScreenQuad() + render.OverrideBlend( false ) + render.PopRenderTarget() + mat_screen:SetTexture( "$basetexture", texMM ) + end + local fade = 0 + hook.Add("RenderScreenspaceEffects","StormFox2.Light.MapMat",function() + if not StormFox2.Map.GetLightRaw then return end + if not StormFox2.Setting.SFEnabled() then return end + -- How old is the GPU!? + if not render.SupportsPixelShaders_2_0() then return end + local a = 1 - StormFox2.Map.GetLightRaw() / 100 + if a <= 0 then -- Too bright + fade = 0 + return + end + -- Check settings + local scale = StormFox2.Setting.GetCache("overwrite_extra_darkness",-1) + if scale == 0 then return end -- Force off. + if scale < 0 then + if not StormFox2.Setting.GetCache("extra_darkness",true) then return end + scale = StormFox2.Setting.GetCache("extra_darkness_amount",1) + end + if scale <= 0 then return end + -- Calc the "fade" between outside and inside + local t = StormFox2.Environment.Get() + if t.outside then + fade = math.min(2, fade + FrameTime()) + elseif t.nearest_outside then + -- Calc dot + local view = StormFox2.util.GetCalcView() + if not view then return end + local d = view.pos:DistToSqr(t.nearest_outside) + if d < 15000 then + fade = math.min(2, fade + FrameTime()) + elseif d > 40000 then -- Too far away + fade = math.max(0, fade - FrameTime()) + else + local v1 = view.ang:Forward() + local v2 = (t.nearest_outside - view.pos):GetNormalized() + if v1:Dot(v2) < 0.6 then -- You're looking away + fade = math.max(0, fade - FrameTime()) + else -- You're looking at it + fade = math.min(2, fade + FrameTime()) + end + end + else + fade = math.max(0, fade - FrameTime()) + end + if fade <= 0 then return end + -- Render + UpdateStencil(exp(a * scale * math.min(1, fade))) + render.SetMaterial(mat_screen) + local w,h = ScrW(),ScrH() + render.OverrideBlend( true, 0, BLEND_ONE_MINUS_SRC_COLOR, 2, BLEND_ONE, BLEND_ZERO, BLENDFUNC_ADD ) + render.DrawScreenQuadEx(0,0,w,h) + render.OverrideBlend( false ) + end) +end + +--[[ + TODO: + 1) Add option to limit the angle of the shadows. + 2) Shadow color to match skies? +]] \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_mixer.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_mixer.lua new file mode 100644 index 0000000..80ab6a0 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_mixer.lua @@ -0,0 +1,103 @@ + +-- Weather mixed updates the variables live, unlike the Data. +-- This can't be set tho. +StormFox2.Mixer = {} + +-- Local function +local function isColor(t) + return t.r and t.g and t.b and true or false +end +local function Blender(nFraction, vFrom, vTo) -- Will it blend? + -- Nils should be false, if one of them is a boolean + if type(vFrom) == "nil" and type(vTo) == "boolean" then + vFrom = false + end + if type(vTo) == "nil" and type(vFrom) == "boolean" then + vTo = false + end + -- If the same value, then return it + if vTo == vFrom then return vTo end + -- In case of two diffrent variables. + if type(vFrom) ~= type(vTo) then + StormFox2.Warning("Mixer called with values of two different types[" .. type(vFrom) .. "," .. type(vTo) .. "]") + debug.Trace() + return vFrom + elseif type(vTo) == "string" or type(vTo) == "IMaterial" or type(vTo) == "boolean" then -- String, material or bool. Return vTo. + return vTo + elseif type(vTo) == "number" then -- Number + return Lerp(nFraction, vFrom, vTo) + elseif type(vTo) == "table" and isColor(vTo) then -- Color + local r = Lerp( nFraction, vFrom.r or 255, vTo.r ) + local g = Lerp( nFraction, vFrom.g or 255, vTo.g ) + local b = Lerp( nFraction, vFrom.b or 255, vTo.b ) + local a = Lerp( nFraction, vFrom.a or 255, vTo.a ) + return Color( r, g, b, a ) + end + --StormFox2.Warning("ERROR: Unsupported mix value type[" .. type(vTo) .. "]. Returning original value") + --debug.Trace() + return vFrom +end + +local cache = {} +local cStamp,nStamp,nStampFraction = 0,0,0 +local function GetVar( wWeather, sKey ) + local v1 = wWeather:Get(sKey, cStamp) + if cStamp == nStamp or nStampFraction <= 0 then + return v1 + end + local v2 = wWeather:Get(sKey, nStamp) + local v = Blender(nStampFraction, v1, v2) + return v +end + +StormFox2.Mixer.Blender = Blender + +function StormFox2.Mixer.Get( sKey, zDefault, cP ) + if cache[sKey] ~= nil then return cache[sKey] end + if not StormFox2.Weather then return zDefault end + local cW = StormFox2.Weather.GetCurrent() + if not cW or cW.Name == "Clear" then return GetVar(cW, sKey) or zDefault end + cP = cP or StormFox2.Weather.GetPercent() + if cP >= 1 then + cache[sKey] = GetVar(cW, sKey) + return cache[sKey] or zDefault + end + local clearW = StormFox2.Weather.Get( "Clear" ) + local var1 = GetVar(clearW, sKey) + local var2 = GetVar(cW, sKey) + cache[sKey] = Blender(cP, var1, var2) + return cache[sKey] or zDefault +end + +StormFox2.Mixer.Blender = Blender + +--[[t.Function = {} + t.Static = {} + t.Dynamic = {} + t.SunStamp = {} +]] + +-- Resets the values after a few frames. This is calculated live and should be cached. +local max_frames = 4 +local i = 0 +local percent = 0 +hook.Add("Think", "StormFox2.mixerreset", function() + i = i + 1 + if i < max_frames then return end + i = 0 + cache = {} + -- Current Stamp + local nTime = StormFox2.Time.Get() + local stamp, percent, next_stamp, pitch_length = StormFox2.Sky.GetStamp(nTime, nil, true) -- cpercent goes from 0 - 1 + local pitch_left = pitch_length * (1 - percent) + local forward = 6 + if pitch_left >= 6 then -- Only look 6 degrees in the furture. + cStamp = stamp + nStamp = stamp + nStampFraction = 0 + else + cStamp = stamp + nStamp = next_stamp + nStampFraction = 1 - ( pitch_left / (math.min(pitch_length, 6)) ) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_shadowcontrol.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_shadowcontrol.lua new file mode 100644 index 0000000..373e3c1 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_shadowcontrol.lua @@ -0,0 +1,120 @@ + +StormFox2.Setting.AddSV("modifyshadows",true,nil, "Effects") +StormFox2.Setting.AddSV("modifyshadows_rate",game.IsDedicated() and 2 or 0.25,nil, "Effects", 0, 10) +StormFox2.Setting.SetType("modifyshadows_rate", "float") +if CLIENT then + net.Receive(StormFox2.Net.Shadows, function() + timer.Simple(0.2, function() + for k, ent in ipairs(ents.GetAll()) do + ent:MarkShadowAsDirty() + ent:PhysWake() + end + end) + end) +end +if CLIENT then return end +StormFox2.Shadows = {} + +--[[------------------------------------------------------------------------- +Shadow controls +---------------------------------------------------------------------------]] +local lastP = -1 +function StormFox2.Shadows.SetAngle( nPitch ) + if not StormFox2.Ent.shadow_controls then return end + local nPitch = (nPitch + 180) % 360 + if nPitch == lastP then return end + lastP = nPitch + local str = nPitch .. " " .. StormFox2.Sun.GetYaw() .. " " .. 0 .. " " + for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do + ent:Fire( "SetAngles" , str , 0 ) + end + net.Start(StormFox2.Net.Shadows) + net.Broadcast() +end +function StormFox2.Shadows.SetColor( cColor ) + if not StormFox2.Ent.shadow_controls then return end + local s = cColor.r .. " " .. cColor.g .. " " .. cColor.b + for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do + ent:SetKeyValue( "color", s ) + end +end +function StormFox2.Shadows.SetDistance( dis ) + if not StormFox2.Ent.shadow_controls then return end + for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do + ent:SetKeyValue( "SetDistance", dis ) + end +end +function StormFox2.Shadows.SetDisabled( bool ) + if not StormFox2.Ent.shadow_controls then return end + for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do + ent:SetKeyValue( "SetShadowsDisabled", bool and 1 or 0 ) + end +end + +-- Simple function to set the light +local n +local function SetDarkness(l) + if n and n == l then return end + n = l + local c = 255 - 68 * n + StormFox2.Shadows.SetColor( Color(c,c,c) ) +end + +local l = 0 +local function shadowTick() + -- Limit update rate + if l >= CurTime() then return end + local rate = StormFox2.Setting.GetCache("modifyshadows_rate", 2) + l = CurTime() + rate + + local sP = StormFox2.Sun.GetAngle().p % 360 + --360 -> 180 Day + local c = math.abs(math.AngleDifference(sP, 270)) -- 0 - 180. Above 90 is night. + if c > 90 then -- Night + StormFox2.Shadows.SetAngle( 270 ) + else + StormFox2.Shadows.SetAngle( sP ) + end + if c < 80 then + SetDarkness(1) + elseif c < 85 then + SetDarkness(17 - 0.2*c) + elseif c < 95 then + SetDarkness(0) + elseif c < 100 then + SetDarkness(0.1*c-9.5) + else + SetDarkness(0) + end +end + +local function enable() + if not StormFox2.Ent.shadow_controls then return end + hook.Add("Think", "StormFox2.shadow.rate", shadowTick) +end +local function disable() + hook.Remove("Think", "StormFox2.shadow.rate") + if not StormFox2.Ent.shadow_controls then return end + local a = StormFox2.Map.FindClass('shadow_control') + if not a or #a < 1 then + StormFox2.Shadows.SetAngle( 270 ) + StormFox2.Shadows.SetColor( Color(187, 187, 187) ) + else + local p = (a[1]["angles"] or Angle(90,0,0)).p + 180 + StormFox2.Shadows.SetAngle( p ) + local c = string.Explode(" ", a[1]["color"] or "187 187 187") + StormFox2.Shadows.SetColor( Color(c[1],c[2],c[3]) ) + end +end + +if StormFox2.Setting.Get("modifyshadows", true) then + enable() +end +StormFox2.Setting.Callback("modifyshadows",function(b) + if b then + enable() + else + disable() + end +end) + diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_temperature.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_temperature.lua new file mode 100644 index 0000000..eaeebb0 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_temperature.lua @@ -0,0 +1,228 @@ +-- Holds temperature and wind +--[[------------------------------------------------------------------------- +Temperature is not universal. A few contries cling on to fahrenheit, +so we need another function to display the temperature correctly. + +StormFox runs on celsius, but can convert the temperature to whatever you wish. + +Clients have to use these functions: + StormFox2.Temperature.GetDisplay() -- Returns the temperature in what their setting is set to. + StormFox2.Temperature.GetDisplaySymbol() -- Returns the temperature symbol for their setting. + StormFox2.Temperature.GetDisplayDefault() -- Returns the default temperature setting for their country. + StormFox2.Temperature.GetDisplayType() -- Returns the temperature setting clients have set to. + +Fun facts: + At -90C, we need specialized air or our brain "forgets" to breathe. + You'll be unconscious in an hour in 10C water. Dead in 3. + At -180C oxygen will liquidfy. + Coldest recorded temp on Earth is -90C + +---------------------------------------------------------------------------]] +StormFox2.Temperature = {} +local convert_from,convert_to = {},{} +local p1,p2,p3,p4,p5 = 3 / 2, 33 / 100, 4 / 5, 21 / 40,240 / 54 + convert_to["fahrenheit"] = function(nC) return nC * 1.8 + 32 end + convert_to["kelvin"] = function(nC) return nC + 273.15 end + convert_to["rankine"] = function(nC) return (nC + 273.15) * 1.8 end + convert_to["delisle"] = function(nC) return (100 - nC) * p1 end + convert_to["newton"] = function(nC) return nC * p2 end + convert_to["réaumur"] = function(nC) return nC * p3 end + convert_to["rømer"] = function(nC) return nC * p4 + 7.5 end + convert_to["wedgwood"] = function(nC) return (nC - 580.8) * p5 end + convert_to["gas_mark"] = function(nC) + if nC >= 135 then + return 1 + (nC - 135) / 13.9 + else + return 1 / ((135 - nC) / 13.9 * 2) + end + end + convert_to["banana"] = function(nC) -- 380 kJ of energy in an average size banana. Takes about 344,49kJ to heat up an avage room by 10c. 1 banana = 1.1c + return 10 * (nC - 10) / 11 + end +local p1,p2,p3,p4,p5,p6 = 5 / 9, 2 / 3, 100 / 33, 5 / 4, 40 / 21,54 / 240 + convert_from["fahrenheit"] = function(nF) return (nF - 32) / 1.8 end + convert_from["kelvin"] = function(nK) return nK - 273.15 end + convert_from["rankine"] = function(nR) return (nR - 491.67) * p1 end + convert_from["delisle"] = function(nD) return 100 - nD * p2 end + convert_from["newton"] = function(nN) return nN * p3 end + convert_from["réaumur"] = function(nR) return nR * p4 end + convert_from["rømer"] = function(nR) return (nR - 7.5) * p5 end + convert_from["wedgwood"] = function(nW) return (nW * p6) + 580.8 end + convert_from["gas_mark"] = function(nG) + if nG >= 1 then + return 0.1 * (139 * nG + 1211) + else + return 135 - 6.95 / nG + end + end + convert_from["banana"] = function(nB) + return 1.1 * nB + 10 + end +local symbol = { + ["celsius"] = "°C", + ["fahrenheit"] = "°F", + ["rankine"] = "°R", + ["delisle"] = "°D", + ["newton"] = "°N", + ["réaumur"] = "°Ré", + ["rømer"] = "°Rø", + ["wedgwood"] = "°W", + ["gas_mark"] = "°G", + ["banana"] = "°B", + ["kelvin"] = "°K" +} +--[[------------------------------------------------------------------ +Returns the current temperature. Valid temperatures: + - celsius : default + - fahrenheit + - kelvin + - rankine + - delisle + - newton + - réaumur + - rømer +---------------------------------------------------------------------------]] +local tempOverwrite +function StormFox2.Temperature.Get(sType) + local n = tempOverwrite or StormFox2.Data.Get( "Temp", 20 ) + if not sType or sType == "celsius" then return n end + if not convert_to[sType] then + StormFox2.Warning("Invalid temperature type [" .. tostring(sType) .. "].", true) + end + return convert_to[sType](n) +end +--[[----------------------------------------------------------------- +Returns the list of valid temperatures. + - celsius : default + - fahrenheit + - kelvin + - rankine + - delisle + - newton + - réaumur + - rømer +---------------------------------------------------------------------------]] +function StormFox2.Temperature.GetTypes() + local t = table.GetKeys(convert_to) + table.insert(t,"celsius") + return t +end +--[[----------------------------------------------------------------- +Converts temperature between two types +Valid temperatures: + - celsius : default + - fahrenheit + - kelvin + - rankine + - delisle + - newton + - réaumur + - rømer + - wedgwood +---------------------------------------------------------------------------]] +function StormFox2.Temperature.Convert(sTypeFrom,sTypeTo,nNumber) + if sTypeFrom and sTypeFrom ~= "celsius" then + if not convert_from[sTypeFrom] then + error("Invalid temperature type [" .. sTypeFrom .. "].") + end + nNumber = convert_from[sTypeFrom](nNumber) + end + if sTypeTo and sTypeTo ~= "celsius" then + if not convert_to[sTypeTo] then + error("Invalid temperature type [" .. sTypeTo .. "].") + end + nNumber = convert_to[sTypeTo](nNumber) + end + return nNumber +end + +if SERVER then + --[[------------------------------------------------------------------------- + Sets the temperature in ceilsius. Second argument is the smooth-time in seconds. + ---------------------------------------------------------------------------]] + function StormFox2.Temperature.Set(nCelsius,nLerpTime) + if nCelsius < -273.15 then -- ( In space, there are 270.45 C ) + nCelsius = -273.15 + end + StormFox2.Network.Set("Temp",nCelsius,nLerpTime or 2 * StormFox2.Time.GetSpeed_RAW()) + end +else + local country = system.GetCountry() or "UK" + local fahrenheit_countries = {"BS","PW","BZ","KY","FM","MH","US","PR","VI","GU"} + --[[Bahamas, Palau, Belize, the Cayman Islands, the Federated States of Micronesia, the Marshall Islands, + and the United States and its territories such as Puerto Rico, the U.S. Virgin Islands, and Guam. + ]] + local default_temp = table.HasValue(fahrenheit_countries, country) and "fahrenheit" or "celsius" + local temp_type = default_temp + --[[------------------------------------------------------------------ + Sets the display temperature. Returns true if given a valid temperature-type. + Valid temperatures: + - celsius : default + - fahrenheit + - kelvin + - rankine + - delisle + - newton + - réaumur + - rømer + ---------------------------------------------------------------------------]] + function StormFox2.Temperature.SetDisplayType(sType) + StormFox2.Setting.Set("display_temperature",convert_to[sType] and sType or "celsius") + if convert_to[sType] then + return true + end + return sType == "celsius" + end + --[[----------------------------------------------------------------- + Returns the display temperature type. + ---------------------------------------------------------------------------]] + function StormFox2.Temperature.GetDisplayType() + return temp_type + end + --[[------------------------------------------------------------------ + Returns the display temperature. + ---------------------------------------------------------------------------]] + function StormFox2.Temperature.GetDisplay(nCelcius) + if nCelcius then + return StormFox2.Temperature.Convert(nil,temp_type,nCelcius) + end + return StormFox2.Temperature.Get(temp_type) + end + --[[------------------------------------------------------------------ + Returns the display temperature symbol. ("°C", "°F" ..) + ---------------------------------------------------------------------------]] + function StormFox2.Temperature.GetDisplaySymbol() + return symbol[temp_type] or "°C" + end + --[[------------------------------------------------------------------ + Returns the default temperature, based on client-country. + ---------------------------------------------------------------------------]] + function StormFox2.Temperature.GetDisplayDefault() + return default_temp + end + -- Load the temperature settings. + -- Setup setting + StormFox2.Setting.AddCL("display_temperature",default_temp) + local t = {} + for k, v in pairs(symbol) do + if t[k] then continue end + t[k] = string.upper(k[1]) .. string.sub(k, 2) .. " " .. v + end + StormFox2.Setting.SetType( "display_temperature", t, {"celsius", "fahrenheit", "kelvin"} ) + StormFox2.Setting.Callback("display_temperature",function(sType) + temp_type = convert_to[sType] and sType or "celsius" + end,"StormFox2.temp.type") + -- Load setting + local sType = StormFox2.Setting.Get("display_temperature",default_temp) + temp_type = convert_to[sType] and sType or "celsius" + + hook.Remove("stormfox2.postlib", "StormFox2.TemperatureSettings") + + --[[ + Local temp + ]] + function StormFox2.Temperature.SetLocal( n ) + tempOverwrite = n + end + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_thunder.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_thunder.lua new file mode 100644 index 0000000..164b3b7 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_thunder.lua @@ -0,0 +1,497 @@ + +StormFox2.Thunder = {} + +function StormFox2.Thunder.IsThundering() + return StormFox2.Data.Get("nThunder", 0) > 0 +end + +function StormFox2.Thunder.GetActivity() -- The amount of strikes pr minute + return StormFox2.Data.Get("nThunder", 0) +end + +if SERVER then + local THUNDER_MAKE_SKYBOX = 0 + local THUNDER_TRACE_ERROR = 1 + local THUNDER_SUCCESS = 2 + + local function ETHull(pos,pos2,size,mask) + local t = util.TraceHull( { + start = pos, + endpos = pos2, + maxs = Vector(size,size,4), + mins = Vector(-size,-size,0), + mask = mask + } ) + t.HitPos = t.HitPos or pos + pos2 + return t + end + -- Damages an entity + local function StrikeDamageEnt( ent ) + if not ent or not IsValid(ent) then return end + local effectdata = EffectData() + effectdata:SetOrigin( ent:GetPos() ) + effectdata:SetEntity(ent) + effectdata:SetMagnitude(2) + effectdata:SetScale(3) + for i = 1,100 do + util.Effect( "TeslaHitboxes", effectdata, true, true ) + end + local ctd = DamageInfo() + ctd:IsDamageType(DMG_SHOCK) + ctd:SetDamage(math.Rand(90,200)) + local vr = VectorRand() + vr.z = math.abs(vr.z) + ctd:SetDamageForce(vr * 1000) + ctd:SetInflictor(game.GetWorld()) + ctd:SetAttacker(game.GetWorld()) + ent:TakeDamageInfo(ctd) + end + -- Damanges an area + local function StrikeDamagePos( vPos ) + local b_InWater = bit.band( util.PointContents( vPos ), CONTENTS_WATER ) == CONTENTS_WATER + local t = {} + for _,ent in ipairs(ents.FindInSphere(vPos,750)) do + -- It hit in water, and you're nearby + if b_InWater and ent:WaterLevel() > 0 then + StrikeDamageEnt(ent) + table.insert(t, ent) + elseif ent:GetPos():Distance( vPos ) < 150 then + StrikeDamageEnt(ent) + table.insert(t, ent) + end + end + hook.Run("StormFox2.Thunder.OnStrike", vPos, t) + end + -- Make STrike + local Sway = 100 + + -- Traces a lightningstrike from sky to ground. + + local BrushZ = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000) + + local function MakeStrikeDown( vPos, nTraceSize ) + if not nTraceSize then nTraceSize = 512 end + -- Find the sky position at the area + local SkyPos = StormFox2.DownFall.FindSky( vPos, vector_up, 8 ) + if not SkyPos then -- Unable to find sky above. Get the higest point + SkyPos = Vector(vPos.x, vPos.y, StormFox2.Map.MaxSize().z) + SkyPos = StormFox2.DownFall.FindSky( SkyPos, vector_up, 1 ) or SkyPos + end + --debugoverlay.Box(SkyPos, Vector(1,1,1) * -20, Vector(1,1,1) * 20, 15, Color(255,0,0)) + -- Find the strike distance (Some maps are suuuper tall) + local tr = ETHull(SkyPos - vector_up * 10, Vector( vPos.x, vPos.y, StormFox2.Map.MinSize().z), 256, MASK_SOLID_BRUSHONLY ) + if tr.AllSolid or tr.Fraction == 0 then -- This is inside solid and should instead be done in the skybox. + return THUNDER_MAKE_SKYBOX, SkyPos + end + SkyPos.z = math.min( SkyPos.z, tr.HitPos.z + 8000 ) + local line = {} + table.insert(line,pos) + -- Create a line down + local olddir = Vector(0,0,-1) + local m_dis = math.max(1, ( SkyPos.z - StormFox2.Map.MinSize().z ) / 20000) + local pos = SkyPos - vector_up * 5 + local _n = nTraceSize / 40 + for i = 20, 1, -1 do + -- Random sway + local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)):Forward() + olddir + randir:Normalize() + randir.z = -math.abs(randir.z) + olddir = randir + local pos2 = pos + randir * math.Rand(800, 1000) * m_dis + local m_dis = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000) + local tr = ETHull(pos, pos2, nTraceSize - i * _n ) + --debugoverlay.Line(pos, pos2, 10) + if not tr.Hit then + table.insert(line, pos2) + pos = pos2 + else + if tr.HitSky then -- We hit the side of the skybox. Go to other way + olddir = -olddir + else + table.insert(line, tr.HitPos) + return THUNDER_SUCCESS, line, tr + end + end + end + return line, tr + end + + -- Creates a lightniingstrike up ( Useful when forcing a lightning strike to hit something ) + local function MakeStrikeUp( vPos ) + local SkyPos = StormFox2.DownFall.FindSky( vPos, vector_up, 8 ) + if not SkyPos then -- Unable to find sky above. Get the higest point + SkyPos = Vector(vPos.x, vPos.y, StormFox2.Map.MaxSize().z) + SkyPos = StormFox2.DownFall.FindSky( SkyPos, vector_up, 1 ) or SkyPos + end + local olddir = Vector(0,0,-1) + local pos = vPos + local m_dis = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000) + local list = {pos} + for i = 1, 20 do + local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)):Forward() + olddir + randir:Normalize() + randir.z = -math.abs(randir.z) + olddir = randir + local pos2 = pos + -randir * math.Rand(800, 1000) * m_dis + if pos2.z >= SkyPos.z then + table.insert(list, Vector(pos2.x, pos2.y, SkyPos.z)) + break + else + pos = pos2 + table.insert(list, pos) + end + end + return table.Reverse(list) + end + + local function LightFluff(tList, b_InSkybox ) + local n_Length = math.Rand(.4,0.7) + local n_Light = math.random(200, 250) + + net.Start(StormFox2.Net.Thunder) + net.WriteBool( true ) + net.WriteUInt(#tList, 5) + for i = 1, #tList do + net.WriteVector(tList[i]) + end + net.WriteBool( b_InSkybox ) + net.WriteFloat(n_Length) + net.WriteUInt(n_Light,8) + net.Broadcast() + end + + -- Creates a lightningstrike at a given position. + function StormFox2.Thunder.CreateAt( pos ) + local t_Var, tList, tr + local b_InSkybox = false + local vMapMin = StormFox2.Map.MinSize() + local vMapMax = StormFox2.Map.MaxSize() + if not pos then + pos = Vector( math.Rand(vMapMin.x, vMapMax.x), math.Rand(vMapMin.y, vMapMax.y), math.Rand(vMapMax.z, vMapMin.z / 2) ) + end + local bInside = pos.x >= vMapMin.x and pos.x <= vMapMax.x and vMapMin.y and pos.y <= vMapMax.y + if bInside then + t_Var, tList, tr = MakeStrikeDown( pos ) + if t_Var == THUNDER_MAKE_SKYBOX then + bInside = false + end + end + if not bInside then -- Outside the map + tList = MakeStrikeUp( Vector(pos.x, pos.y, StormFox2.Map.MinSize().z) ) + b_InSkybox = true + end + if not tList then return false end -- Unable to create lightning strike here. + local hPos = tr and tr.HitPos or tList[#tList] + if not hPos then return end + if tr and IsValid( tr.Entity ) then + table.insert(tList, tr.Entity:GetPos() + tr.Entity:OBBCenter()) + hPos = tr.Entity:GetPos() + tr.Entity:OBBCenter() + end + StrikeDamagePos( hPos ) + LightFluff(tList, b_InSkybox ) + return true, tr and IsValid( tr.Entity ) and tr.Entity + end + -- Creates a lightning strike to hit the given position / entity + function StormFox2.Thunder.Strike( zPosOrEnt, bRangeDamage ) + -- Strike the entity + if not bRangeDamage and zPosOrEnt.Health then + local ent = zPosOrEnt + timer.Simple(0.4, function() + StrikeDamageEnt( ent ) + end) + end + if zPosOrEnt.GetPos then + if zPosOrEnt.OBBCenter then + zPosOrEnt = zPosOrEnt:GetPos() + zPosOrEnt:OBBCenter() + else + zPosOrEnt = zPosOrEnt:GetPos() + end + end + if bRangeDamage then + timer.Simple(0.4, function() StrikeDamagePos( zPosOrEnt ) end) + end + local b_InSkybox = not StormFox2.Map.IsInside( zPosOrEnt ) + --if b_InSkybox then + -- zPosOrEnt = StormFox2.Map.WorldtoSkybox( zPosOrEnt ) + --end + + local tList = MakeStrikeUp( zPosOrEnt ) + LightFluff(tList, false ) + return true + end + + function StormFox2.Thunder.Rumble( pos, bLight ) + if not pos then + local vMapMin = StormFox2.Map.MinSize() + local vMapMax = StormFox2.Map.MaxSize() + pos = Vector( math.Rand(vMapMin.x, vMapMax.x), math.Rand(vMapMin.y, vMapMax.y), math.Rand(vMapMax.z, vMapMin.z / 2) ) + end + local n_Length = bLight and math.Rand(.4,0.7) or 0 + local n_Light = bLight and math.random(150, 250) or 0 + net.Start( StormFox2.Net.Thunder ) + net.WriteBool( false ) + net.WriteVector( pos ) + net.WriteFloat(n_Length) + net.WriteUInt(n_Light,8) + net.Broadcast() + end + + -- Enables thunder and makes them spawn at random, until set off or another weather gets selected + local b = false + local n = math.max(StormFox2.Map.MaxSize().x, StormFox2.Map.MaxSize().y, -StormFox2.Map.MinSize().x,-StormFox2.Map.MinSize().y) + if StormFox2.Map.Has3DSkybox() then + n = n * 1.5 + end + + do + local n = 0 + function StormFox2.Thunder.SetEnabled( bEnable, nActivityPrMinute, nTimeAmount ) + n = 0 + if bEnable then + StormFox2.Network.Set("nThunder", nActivityPrMinute) + if nTimeAmount then + StormFox2.Network.Set("nThunder", 0, nTimeAmount) + end + else + StormFox2.Network.Set("nThunder", 0) + end + end + hook.Add("Think", "StormFox2.thunder.activity", function() + if not StormFox2.Thunder.IsThundering() then return end + if n >= CurTime() then return end + local a = StormFox2.Thunder.GetActivity() + n = CurTime() + math.random(50, 60) / a + -- Strike or rumble + if math.random(1,3) < 3 then + StormFox2.Thunder.CreateAt() + else + StormFox2.Thunder.Rumble( nil, math.random(10) > 1 ) + end + end) + end +else + lightningStrikes = lightningStrikes or {} + local _Light, _Stop, _Length = 0,0,0 + function StormFox2.Thunder.GetLight() + if _Light <= 0 then return 0 end + if _Stop < CurTime() then + _Light = 0 + return 0 + end + local t = (_Stop - CurTime()) / _Length + local c = math.abs(math.sin( t * math.pi )) + local l = _Light * c + return math.random(l, l * 0.5) -- Flicker a bit + end + + -- 0 - 2000 + local CloseStrikes = {"sound/stormfox2/amb/thunder_strike.ogg"} + -- 2000 - 20000 + local MediumStrikes = {"sound/stormfox2/amb/thunder_strike.ogg", "sound/stormfox2/amb/thunder_strike2.ogg"} + -- 20000 + + local FarStrikes = {} + + if IsMounted("csgo") or IsMounted("left4dead2") then + table.insert(FarStrikes, "ambient/weather/thunderstorm/lightning_strike_1.wav") + table.insert(FarStrikes, "ambient/weather/thunderstorm/lightning_strike_4.wav") + end + if #FarStrikes < 1 then + table.insert(FarStrikes, "sound/stormfox2/amb/thunder_strike2.ogg") + end + + local snd_buffer = {} + local function StrikeEffect( pos, n_Length ) + local dlight = DynamicLight( 1 ) + if ( dlight ) then + dlight.pos = pos + dlight.r = 255 + dlight.g = 255 + dlight.b = 255 + dlight.brightness = 6 + dlight.Decay = 3000 / n_Length + dlight.Size = 256 * 8 + dlight.DieTime = CurTime() + n_Length + end + local effectdata = EffectData() + local s = math.random(5, 8) + effectdata:SetOrigin( pos + vector_up * 4 ) + effectdata:SetMagnitude( s / 2 ) + effectdata:SetNormal(vector_up) + effectdata:SetRadius( 8 ) + util.Effect( "Sparks", effectdata, true, true ) + end + + local function PlayStrike( vPos, nViewDis, viewPos ) + local snd = "" + if nViewDis <= 2000 then + snd = table.Random(CloseStrikes) + elseif nViewDis <= 15000 then + snd = table.Random(MediumStrikes) + else + snd = table.Random(FarStrikes) + end + if string.sub(snd, 0, 6 ) == "sound/" or string.sub(snd,-4) == ".ogg" then + sound.PlayFile( snd, "3dnoplay", function( station, errCode, errStr ) + if ( IsValid( station ) ) then + station:Set3DFadeDistance( 0, 10 ) + station:SetVolume( 1) + station:SetPos(vPos) + station:Play() + end + end) + else + surface.PlaySound( snd ) + end + end + --[[ + Sound moves about 343 meters pr second + 52.49 hU = 1 meter ~ 18.004 hU pr second + ]] + local b = true + local function SndThink() + if #snd_buffer < 1 then + hook.Remove("Think","StormFox2.Thunder.SndDis") + b = false + return + end + local r = {} + local view = StormFox2.util.ViewEntity():GetPos() + local c = CurTime() - 0.2 + for k,v in ipairs( snd_buffer ) do + local travel = (c - v[2]) * 18004 + local vDis = view:Distance( v[1] ) + if vDis - travel < 0 then + table.insert(r, k) + PlayStrike( v[1], vDis, view ) + end + end + for i = #r, 1, -1 do + table.remove(snd_buffer, r[i]) + end + end + hook.Add("Think","StormFox2.Thunder.SndDis", SndThink) + + local function Strike( tList, b_InSkybox, n_Length, n_Light ) + table.insert(lightningStrikes, {CurTime() + n_Length, n_Length, b_InSkybox, tList, true}) + local pos = tList[#tList][1] + sound.Play("ambient/energy/weld" .. math.random(1,2) .. ".wav", pos) + if not b then + hook.Add("Think","StormFox2.Thunder.SndDis", SndThink) + end + table.insert(snd_buffer, {pos, CurTime()}) + local c = CurTime() + _Light = 255 + _Length = .7 + _Stop = math.max(c + _Length, _Stop) + end + + local function Rumble( vPos, n_Length, n_Light ) + -- Thunder is at 120dB + local c = CurTime() + _Light = n_Light + _Length = n_Length + _Stop = math.max(c + n_Length, _Stop) + sound.Play("ambient/atmosphere/thunder" .. math.random(3,4) .. ".wav", StormFox2.util.ViewEntity():GetPos(), 150) + + end + local Sway = 120 + net.Receive( StormFox2.Net.Thunder, function(len) + local b_Strike = net.ReadBool() + if b_Strike then + local tList = {} + local old + local n = net.ReadUInt(5) + for i = 1, n do + local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)) + local new = net.ReadVector() + if old then + randir = randir:Forward() + (new - old):Angle():Forward() * 2 + else + randir = randir:Forward() + end + old = new + tList[i] = {new,math.Rand(1.2,1.5),randir,math.random(0,1)} + --debugoverlay.Sphere(new, 15, 15, Color(255,255,255), true) + end + local b_InSkybox = net.ReadBool() + Strike(tList, b_InSkybox, net.ReadFloat(), net.ReadUInt(8)) + else + local vPos = net.ReadVector() + Rumble(vPos, net.ReadFloat(), net.ReadUInt(8) ) + end + end) + + -- Render Strikes + local tex = {(Material("stormfox2/effects/lightning"))} + local texend = {(Material("stormfox2/effects/lightning_end")),(Material("stormfox2/effects/lightning_end2"))} + for k, v in ipairs( texend ) do + v:SetFloat("$nofog",1) + end + local t = 0 + hook.Add("PostDrawOpaqueRenderables","StormFox2.Render.Lightning",function(a,sky) + if a or #lightningStrikes < 1 then return end + if sky then return end -- Doesn't work yet + local r = {} + local c = CurTime() + local col = Color( 255, 255, 255, 255) + for k, v in ipairs( lightningStrikes ) do + -- Render world or skybox + --if v[3] ~= sky then continue end + -- Remove if dead + if v[1] < c then + table.insert(r, k) + continue + end + + local life = 1 - ( v[1] - c ) / v[2] -- 0 - 1 + if life < 0.6 then + col.a = 425 * life + else + col.a = 637.5 * (1 - life) + end + local fuzzy = life < 0.6 + local i = 0.6 / #v[4] + if v[5] and not fuzzy then + StrikeEffect( v[4][#v[4]][1] , life ) + lightningStrikes[k][5] = false + end + -- Render beams + render.SetMaterial(tex[1]) + render.StartBeam(#v[4]) + local l = math.Rand(0.4, 0.8) + for k2, v2 in ipairs( v[4] ) do + if life < k2 * i then break end + local tp = 0.1 + render.AddBeam( v2[1], 400, (l * (k2 - 1)) % 1, col ) + end + render.EndBeam() + + -- Render strikes + if fuzzy then + for k2, v2 in ipairs( v[4] ) do + if life < k2 * i or k2 + 3 >= #v[4] then break end + local n2 = life * 2- k2 * 0.04 + local vec = v2[1] + local tp = 1 / #v[4] * k2 + local n = k2 % #texend + 1 + render.SetMaterial(texend[n]) + local w,h = texend[n]:Width() * n2,texend[n]:Height() * n2 + render.DrawBeam( vec, vec + v2[3] * h * v2[2], w * v2[2], 1 - n2, 1, col ) + end + else + local v1 = v[4][1][1] + local v2 = v[4][#v[4]][1] + local vc = (v1 + v2) / 2 + vc.z = v2.z + local vc2 = Vector(vc.x,vc.y,v1.z) + local a = math.max(0, 1 - life - 0.2) + col.a = a * 1275 + render.SetMaterial(Material("stormfox2/effects/lightning_light")) + render.DrawBeam(vc, vc2, 24400, 0.3 , 0.7, col) + end + end + for i = #r, 1, -1 do + table.remove(lightningStrikes, r[i]) + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_timemaster.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_timemaster.lua new file mode 100644 index 0000000..8b7ad8e --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_timemaster.lua @@ -0,0 +1,648 @@ +--[[------------------------------------------------------------------------- + My god, look at the time. + + StormFox2.Time.GetTime(bNearestSecond) [nTime] -- Returns the time number. Between 0 and 1440 + StormFox2.Time.TimeToString(nTime = current time,bUse12Hour) [sTime] -- Returns the time as a string. + StormFox2.Time.IsDay() [bIsDay] -- Returns true if its day + StormFox2.Time.IsNight() [bIsNight] -- Returns true if its night + StormFox2.Time.GetStamp(nTime = current time) [nTimeStamp] -- Returns a timestamp. + + SERVER + StormFox2.Time.Set(nTime or string) -- Sets the time. Also supports a string "12:00" or "5:00 AM". + StormFox2.Time.SetSpeed(nSpeed) -- Sets the timespeed. + + Hooks: + StormFox2.Time.Set -- Called when the time gets set. + StormFox2.Time.NewStamp NewStamp OldStamp -- Callend when the time matches a new stamp + + BASE_TIME is CurTime + StartTime +---------------------------------------------------------------------------]] +local floor,ceil,random = math.floor, math.ceil, math.random +StormFox2.Time = StormFox2.Time or {} +-- Settings + local s_start = StormFox2.Setting.AddSV("start_time",-1,nil, "Time", -1, 1440) -- Sets the starttime + local s_real = StormFox2.Setting.AddSV("real_time",false,nil, "Time") -- Sets the startime to match OS + local s_random = StormFox2.Setting.AddSV("random_time",false,nil,"Time") -- Makes the time random + local s_continue = StormFox2.Setting.AddSV("continue_time",true,nil,"Time"):SetRadioDefault() -- Make the time continue from last + + s_start:SetRadioAll( s_real, s_random, s_continue ) + + StormFox2.Setting.SetType("start_time","Time_toggle") + + local day_length = StormFox2.Setting.AddSV("day_length", 12,nil,"Time",-1, 24 * 60 * 7 ) + :SetMenuType("special_float") + + local night_length = StormFox2.Setting.AddSV("night_length", 12,nil,"Time",-1, 24 * 60 * 7 ) + :SetMenuType("special_float") + + local sun_rise = StormFox2.Setting.AddSV("sunrise",360,nil, "Time", 0, 1440) + StormFox2.Setting.SetType("sunrise", "Time") + local sun_set = StormFox2.Setting.AddSV("sunset",1080,nil, "Time", 0, 1440) + StormFox2.Setting.SetType("sunset", "Time") + + --[[ + Pause + day_length = <= 0 + night_length = <= 0 + Only day + day_length = > 0 + night_length = < 0 + Only night + day_length = < 0 + night_length = >= 0 + ]] + + + --[[ ---- EDIT NOTE ---- + x Instead of using Settings directly in UpdateMath. MAke UpdateMath use arguments instead. + + These settings are send from the server to client on SetTime or join-data. + + When changing settings on the server, wait a few ticks to set them. Sometimes there are multiple settings being changed at the same time. + Best to wait a bit. + + StartTime also got removed .. need to fix that. + ]] + + -- Returns the total time in minutes for a day + local BASE_TIME = 0 + --[[ + Calculates the regular time + cycleTime - The total time it takes for a day to pass + + Enums to keep me sane + - finishTime = The finished number between 0 and 1440. This is the ingame time + - cycleTime = The total seconds it takes for a day to pass + - dayTime = The total seconds it takes for "day-light" to pass + - nightTime = The total seconds it takes for a night to pass + - sunTime = The total of ingame the sun is up + - nightTime = The total of ingame the sun is down + - cyclePercentDay= The percent of the day, being day-light (Only with day and night on) + ]] + -- Math Box to set and get time + local Get, Set, UpdateMath, isInDay, isDay, dayLength, nightLength, netWriteData + local GetCache, IsDayCache, CycleCache, FinsihToCycle, GetCycleRaw + local CR + do + local SF_PAUSE = 0 + local SF_NORMAL = 1 + local SF_DAYONLY = 2 + local SF_NIGHTONLY = 3 + + local cycleLength + local curType -- The time-type + -- Returns the percent from the given time ( 0 - 1440) between starttime and endtime + -- Also loops around if from is higer than to + local function lerp1440( time, from, to ) + if from < to then + return ( time - from ) / ( to - from ) + elseif time >= from then + return ( time - from ) / ( 1440 - from + to ) + else + local ex = 1440 - from + return ( time + ex ) / ( to + ex ) + end + end + local sunTimeUp, nightTimeUp, sunSet, sunRise + function isInDay( finishTime ) + if not sunRise then return true end -- Not loaded yet + if sunRise < sunSet then + return finishTime >= sunRise and finishTime <= sunSet + end + return (finishTime >= sunRise and finishTime <= 1440 ) or finishTime <= sunSet + end + -- Splits cycletime into dayPercent and nightPercent + local function CycleToPercent( cycleTime ) + if cycleTime <= dayLength then -- It is day + return cycleTime / dayLength, nil + else -- It is night + return nil, (cycleTime - dayLength) / nightLength + end + end + -- Takes dayPercent or nightPercent and convert it to cycletime + local function PercentToCycle( dayPercent, nightPercent ) + if dayPercent then + return dayPercent * dayLength + else + return dayLength + nightLength * nightPercent + end + end + -- returns percent of the day that has passed at the given time + local function finishDayToPercent( finishTime ) + return lerp1440( finishTime, sunRise, sunSet ) + end + -- returns percent of the night that has passed at the given time + local function finishNightToPercent( finishTime ) + return lerp1440( finishTime, sunSet, sunRise ) + end + -- Takes the ingame 0-1440 and converts it to the cycle-area + function FinsihToCycle( finishTime ) + if isInDay( finishTime ) then -- If day + return finishDayToPercent( finishTime ) * dayLength + else + return dayLength + finishNightToPercent( finishTime ) * nightLength + end + end + local function CycleToFinish( cycle ) + if cycle <= dayLength then -- Day time + local percent = cycle / dayLength + return ( sunRise + sunTimeUp * percent ) % 1440 + else -- NightTime + local percent = ( cycle - dayLength ) / nightLength + return ( sunSet + nightTimeUp * percent ) % 1440 + end + end + -- Get + local function TimeFromSettings( ) + -- The seconds passed in the day + local chunk = ((CurTime() - BASE_TIME) % cycleLength) + return CycleToFinish( chunk ) + end + local function TimeFromSettings_DAY( ) + local p_chunk = ((CurTime() - BASE_TIME) % cycleLength) / cycleLength + return (sunRise + p_chunk * sunTimeUp) % 1440 + end + local function TimeFromSettings_NIGHT( ) + local p_chunk = ((CurTime() - BASE_TIME) % cycleLength) / cycleLength + return (sunSet + p_chunk * nightTimeUp) % 1440 + end + local function TimeFromPause() + return BASE_TIME + end + -- Is cheaper than converting things around + function isDay() + if not cycleLength then return true end -- Not loaded yet + if curType == SF_NIGHTONLY then + return false + elseif curType == SF_DAYONLY then + return true + else + local l = (CurTime() - BASE_TIME) % cycleLength + return l <= dayLength + end + end + function Get() + if s_real:GetValue() then + GetCache =( CurTime() / 60 - BASE_TIME ) % 1440 + IsDayCache = isInDay( GetCache ) + return GetCache + end + if not cycleLength then return 720 end -- Not loaded yet + local num + if not curType or curType == SF_NORMAL then + num = TimeFromSettings( ) + GetCache = num + IsDayCache = isDay() + elseif curType == SF_PAUSE then + num = TimeFromPause( ) + GetCache = num + IsDayCache = isInDay( num ) + elseif curType == SF_DAYONLY then + num = TimeFromSettings_DAY( ) + GetCache = num + IsDayCache = true + else + num = TimeFromSettings_NIGHT( ) + GetCache = num + IsDayCache = false + end + + return num + end + function Set( snTime ) + --print("SET TIME", snTime) + if s_real:GetValue() then + BASE_TIME = CurTime() / 60 - snTime + elseif not curType or curType == SF_NORMAL then + BASE_TIME = CurTime() - FinsihToCycle( snTime ) + elseif curType == SF_PAUSE then + BASE_TIME = snTime + elseif curType == SF_DAYONLY then + local p = math.Clamp(lerp1440( snTime, sunRise, sunSet ), 0, 1) + BASE_TIME = CurTime() - p * dayLength + else + local p = math.Clamp(lerp1440( snTime, sunSet, sunRise ), 0, 1) + BASE_TIME = CurTime() - p * nightLength + end + GetCache = nil -- Delete time cache + -- Gets called when the user changes the time, or time variables. Tells scripts to recalculate things. + if not StormFox2.Loaded then return end + hook.Run("StormFox2.Time.Changed") + end + function UpdateMath(nsTime, blockSetTime) + --print("MATH UPDATE", nsTime, blockSetTime) + local nsTime = nsTime or cycleLength and Get() + sunSet = sun_set:GetValue() + sunRise = sun_rise:GetValue() + dayLength = day_length:GetValue() * 60 + nightLength = night_length:GetValue() * 60 + --print(sunSet) + --print(sunRise) + --print(dayLength) + --print(nightLength) + if s_real:GetValue() then -- Real time + cycleLength = 60 * 60 * 24 + curType = SF_NORMAL + elseif dayLength <= 0 and nightLength <= 0 or sunSet == sunRise then -- Pause type + curType = SF_PAUSE + cycleLength = 0 + elseif nightLength <= 0 then -- Day only + cycleLength = dayLength + curType = SF_DAYONLY + elseif dayLength <= 0 then -- Night only + cycleLength = nightLength + curType = SF_NIGHTONLY + else + cycleLength = dayLength + nightLength + curType = SF_NORMAL + end + if sunRise < sunSet then + sunTimeUp = sunSet - sunRise + else + sunTimeUp = (1440 - sunRise) + sunSet + end + nightTimeUp = 1440 - sunTimeUp + if not nsTime or blockSetTime then return end -- No valid time currently + Set( nsTime ) + if SERVER then + net.Start( StormFox2.Net.Time ) + net.WriteString( tostring( BASE_TIME ) ) + net.Broadcast() + end + end + local function GetDayPercent() + if not IsDayCache then return -1 end + local chunk = ((CurTime() - BASE_TIME) % cycleLength) + return chunk / dayLength + end + local function GetNightPercent() + if IsDayCache then return -1 end + local chunk = ((CurTime() - BASE_TIME) % cycleLength) + return (chunk - dayLength) / nightLength + end + function GetCycleRaw() + if not cycleLength then return 0 end -- Not loaded, or pause on launch + if CR then return CR end + CR = ((CurTime() - BASE_TIME) % cycleLength) + return CR + end + -- Returns how far the day has progressed 0 = sunRise, 0.5 = sunSet, 1 = sunRise + function StormFox2.Time.GetCycleTime() + if CycleCache then return CycleCache end + if s_real:GetValue() then + local t = Get() + if isInDay( t ) then + CycleCache = lerp1440( t, sunRise, sunSet ) / 2 + else + CycleCache = 0.5 + lerp1440( t, sunSet, sunRise ) / 2 + end + return CycleCache + end + if curType == SF_PAUSE then -- When paused, use the time to calculate + if isInDay( BASE_TIME ) then + CycleCache = lerp1440( BASE_TIME, sunRise, sunSet ) / 2 + else + CycleCache = 0.5 + lerp1440( BASE_TIME, sunSet, sunRise ) / 2 + end + return math.Clamp(CycleCache, 0, 1) + end + if IsDayCache then + CycleCache = GetDayPercent() / 2 + return math.Clamp(CycleCache, 0, 1) + else + CycleCache = GetNightPercent() / 2 + 0.5 + return math.Clamp(CycleCache, 0, 1) + end + end + end + -- Cache clear. Wait 4 frames to update the time-cache, calculating it for every function is too costly. + do + local i = 0 + hook.Add("Think", "StormFox2.Time.ClearCache", function() + CR = nil + i = i + 1 + if i >= 2 then + i = 0 + GetCache = nil + CycleCache = nil + end + end) + end + + -- In most cases, multiple settings will update at the same time. Wait a second. + local function updateTimeSettings( ) + if timer.Exists("SF_SETTIME") then return end + timer.Create("SF_SETTIME", 0.2, 1, function() + UpdateMath( nil, CLIENT ) -- If we're the client, then don't update the BASE_TIME + end) + end + -- If any of the settings change, update the math behind it. This will also fix time and update clients if done on server. + day_length:AddCallback( updateTimeSettings,"SF_TIMEUPDATE") + night_length:AddCallback( updateTimeSettings,"SF_TIMEUPDATE") + sun_rise:AddCallback( updateTimeSettings,"SF_TIMEUPDATE") + sun_set:AddCallback( updateTimeSettings,"SF_TIMEUPDATE") + s_real:AddCallback( updateTimeSettings,"SF_TIMEUPDATE") + + -- Make real-time change day and night length + if SERVER then + s_real:AddCallback( function( b ) + if not b then return end + local dt = string.Explode(":",os.date("%H:%M:%S")) + nsTime = tonumber(dt[1]) * 60 + tonumber(dt[2]) + tonumber(dt[3]) / 60 + StormFox2.Time.Set(nsTime) + end,"SF_REALTIME_S") + end + + + -- Update the math within Get and Set. Will also try and adjust the time + if SERVER then -- Server controls the time + local start_time = cookie.GetNumber("sf2_lasttime",-1) + if s_continue:GetValue() and start_time >= 0 then + -- Continue time from last + else + if s_start:GetValue() >= 0 then -- Start time is on + start_time = s_start:GetValue() + elseif s_real:GetValue() then -- Real time + local dt = string.Explode(":",os.date("%H:%M:%S")) + start_time = tonumber(dt[1]) * 60 + tonumber(dt[2]) + tonumber(dt[3]) / 60 + else -- if s_random:GetValue() or start_time < 0 then Make it random if all options are invalid + start_time = math.Rand(0, 1400) + end + end + UpdateMath( start_time, false ) + -- Only server can set the time + function StormFox2.Time.Set( nsTime ) + if nsTime and type( nsTime ) == "string" then + nsTime = StormFox2.Time.StringToTime(nsTime) + end + if not nsTime then return false end + Set( nsTime ) + net.Start( StormFox2.Net.Time ) + net.WriteString( tostring( BASE_TIME ) ) -- Sending the current time might add a delay to clients. Better to send the new base. + net.Broadcast() + return true + end + -- Tell new clients the settings + hook.Add("StormFox2.data.initspawn", "StormFox2.Time.SendOnJoin", function( ply ) + net.Start( StormFox2.Net.Time ) + net.WriteString( tostring( BASE_TIME ) ) + net.Send( ply ) + end) + else + UpdateMath( 720, true ) -- Set the starting time to 720. We don't know any settings yet. + net.Receive( StormFox2.Net.Time, function(len) + BASE_TIME = tonumber( net.ReadString() ) or 0 + end) + end + + function StormFox2.Time.Get( bNearestSecond ) + if bNearestSecond then + return math.floor(GetCache and GetCache or Get()) + end + return GetCache and GetCache or Get() + end + + -- How many seconds + function StormFox2.Time.GetSpeed_RAW() + if not nightLength or StormFox2.Time.IsPaused() then return 0 end + if IsDayCache then + return 1 / dayLength + end + return 1 / nightLength + end + + function StormFox2.Time.GetSpeed() + return StormFox2.Time.GetSpeed_RAW() * 60 + end + +-- Be able to load time + local function thinkingBox(sVar) -- Converts string to something useful + local h,m = string.match(sVar,"(%d?%d):?(%d?%d)") + local ampm = string.match(sVar,"[ampAMP]+") or "" + if not h or not m then return end + if #ampm > 0 then + if tonumber(h) > 12 then ampm = "" end + end + if #ampm < 1 then ampm = "" end + return h .. ":" .. m .. " " .. ampm + end + --[[------------------------------------------------------------------------- + Returns the given time as a number. Supports both "13:00" and "1:00 PM" + ---------------------------------------------------------------------------]] + function StormFox2.Time.StringToTime(sTime) + sTime = sTime or StormFox2.Time.Get() + str = thinkingBox(sTime) + if not str then return end + local a = string.Explode( ":", str ) + if #a < 2 then return end + local h,m = string.match(a[1],"%d+"),string.match(a[2],"%d+") + local ex = string.match(a[2]:lower(),"[amp]+") + if not h or not m then return end + h,m = tonumber(h),tonumber(m) + if ex then + -- 12clock to 24clock + if ex == "am" and h == 12 then + h = h - 12 + end + if h < 12 and ex == "pm" then + h = h + 12 + end + end + return ( h * 60 + m ) % 1440 + end + + --[[------------------------------------------------------------------------- + A syncronised number used by the client to calculate the time. Use instead StormFox2.Time.Get + ---------------------------------------------------------------------------]] + function StormFox2.Time.GetBASE_TIME() + return BASE_TIME + end + + --[[------------------------------------------------------------------------- + Returns the given or current time in a string format. + ---------------------------------------------------------------------------]] + function StormFox2.Time.TimeToString(nTime,bUse12Hour) + if not nTime then nTime = StormFox2.Time.Get(true) end + local h = floor(nTime / 60) + local m = floor(nTime % 60 ) + if not bUse12Hour then return h .. ":" .. (m < 10 and "0" or "") .. m end + local e = "PM" + if h < 12 or h == 0 then + e = "AM" + end + if h == 0 then + h = 12 + elseif h > 12 then + h = h - 12 + end + return h .. ":" .. (m < 10 and "0" or "") .. m .. " " .. e + end +-- Easy functions + --[[------------------------------------------------------------------------- + Returns true if the current or given time is doing the day. + ---------------------------------------------------------------------------]] + function StormFox2.Time.IsDay( nsTime ) + if not nsTime then -- Cheaper and faster than to convert things around. + return IsDayCache + end + return isInDay( nsTime ) + end + --[[------------------------------------------------------------------------- + Returns true if the current or given time is doing the night. + ---------------------------------------------------------------------------]] + function StormFox2.Time.IsNight(nTime) + return not StormFox2.Time.IsDay(nTime) + end + --[[------------------------------------------------------------------------- + Returns true if the current or given time is between FromTime to ToTime. + E.g Dinner = StormFox2.Time.IsBetween(700,740) + ---------------------------------------------------------------------------]] + function StormFox2.Time.IsBetween(nFromTime,nToTime,nCurrentTime) + if not nCurrentTime then nCurrentTime = StormFox2.Time.Get() end + if nFromTime > nToTime then + return nCurrentTime >= nFromTime or nCurrentTime <= nToTime + end + return nFromTime <= nCurrentTime and nToTime >= nCurrentTime + end + --[[------------------------------------------------------------------------- + Returns the time between Time and Time2 in numbers. + ---------------------------------------------------------------------------]] + function StormFox2.Time.DeltaTime(nTime,nTime2) + if nTime2 >= nTime then return nTime2 - nTime end + return (1440 - nTime) + nTime2 + end +-- Time stamp + --[[ + Simple hour, minute, second and AM / PM + ]] + function StormFox2.Time.GetHours( nTime, b12Hour ) + if not nTime then nTime = StormFox2.Time.Get() end + if not b12Hour then return floor( nTime / 60 ) end + local h = floor( nTime / 60 ) + if h == 0 then + h = 12 + elseif h > 12 then + h = h - 12 + end + return h + end + + function StormFox2.Time.GetMinutes( nTime ) + if not nTime then nTime = StormFox2.Time.Get() end + return floor( nTime % 60 ) + end + + function StormFox2.Time.GetSeconds( nTime ) + if not nTime then nTime = StormFox2.Time.Get() end + return floor( nTime % 1 ) * 60 + end + + function StormFox2.Time.GetAMPM( nTime ) + if not nTime then nTime = StormFox2.Time.Get() end + local h = floor( nTime / 60 ) + if h < 12 or h == 0 then + return "AM" + end + return "PM" + end + --[[ + Allows to pause and resume time + ]] + local lastT + -- Returns true if the time is paused, second argument is nil or a table of the old settings from StormFox2.Time.Pause() + function StormFox2.Time.IsPaused() + local dl = day_length:GetValue() + local nl = night_length:GetValue() + return dl <= 0 and nl <= 0, lastT + end + if SERVER then + function StormFox2.Time.Pause() + local dl = day_length:GetValue() + local nl = night_length:GetValue() + if dl <= 0 and nl <= 0 then return end -- Already paused time + lastT = { dl, nl } + day_length:SetValue( 0 ) + night_length:SetValue( 0 ) + end + function StormFox2.Time.Resume() + if not StormFox2.Time.IsPaused() then return end + if lastT then + day_length:SetValue( lastT[1] ) + night_length:SetValue( lastT[2] ) + lastT = nil + else + day_length:SetValue( 12 ) + night_length:SetValue( 12 ) + end + end + end + --[[ + Returns the seconds until we reached the given time. + Note to lisen for the hook: "StormFox2.Time.Changed". In case an admin changes the time / time-settings. + ]] + function StormFox2.Time.SecondsUntil( nTime ) + if StormFox2.Time.IsPaused() then return -1 end + local c_cycleTime = GetCycleRaw() -- Seconds past sunrise + local t_cycleTime = FinsihToCycle( nTime ) -- Seconds past sunrise to said time + return ( t_cycleTime - c_cycleTime ) % ( dayLength + nightLength ) + end + +-- Default Time Display +if CLIENT then + -- 12h countries + local country = system.GetCountry() or "GB" + local h12_countries = {"GB","IE","US","CA","AU","NZ","IN","PK","BD","MY","MT","EG","MX","PH"} + --[[United Kingdom, Republic of Ireland, the United States, Canada (sorry Quebec), + Australia, New Zealand, India, Pakistan, Bangladesh, Malaysia, Malta, Egypt, Mexico and the former American colony of the Philippines + ]] + local default_12 = table.HasValue(h12_countries, country) + StormFox2.Setting.AddCL("12h_display",default_12,"Changes how time is displayed.","Time") + StormFox2.Setting.SetType( "12h_display", { + [false] = "24h clock", + [true] = "12h clock" + } ) + --[[------------------------------------------------------------------------- + Returns the time in a string, matching the players setting. + ---------------------------------------------------------------------------]] + function StormFox2.Time.GetDisplay(nTime) + local use_12 = StormFox2.Setting.GetCache("12h_display",default_12) + return StormFox2.Time.TimeToString(nTime,use_12) + end + + -- In case the date changes, call the next-day hook + hook.Add("StormFox2.data.change","StormFox2.Date.NextDay", function(sKey, zVar, nDelta) + if sKey == "day" then + hook.Run("StormFox2.Time.NextDay") + end + end) +else + local nextDay = -1 + local _b = false + hook.Add("Think", "StormFox2.Time.NextDayCheck", function() + if nextDay <= CurTime() then -- Calculate next day + local sec = StormFox2.Time.SecondsUntil( 1440 ) + if sec == -1 then -- Time is paused, will never be next day + nextDay = CurTime() + 500 + else + nextDay = CurTime() + sec + if _b then + hook.Run("StormFox2.Time.NextDay") + end + _b = true + end + end + end) + + -- The time and or timespeed changed. Recalculate when the day changes + hook.Add("StormFox2.Time.Changed", "StormFox2.Time.NextDayCalc", function() + nextDay = -1 + _b = false + end) + + -- We use the date-functions to increase the day + hook.Add("StormFox2.Time.NextDay", "StormFox2.Data.NextDay", function() + local nDay = StormFox2.Date.GetYearDay() + 1 + StormFox2.Date.SetYearDay( nDay ) + end) + +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_weather_handle.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_weather_handle.lua new file mode 100644 index 0000000..18cfb86 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sh_weather_handle.lua @@ -0,0 +1,380 @@ +-- Updates the weather for given players + +local lastSet = 0 +local CurrentWeather +local CurrentPercent = 1 + +local function isColor(t) + return t.r and t.g and t.b and true or false +end +local function Blender(nFraction, vFrom, vTo) -- Will it blend? + -- Nils should be false, if one of them is a boolean + if type(vFrom) == "nil" and type(vTo) == "boolean" then + vFrom = false + end + if type(vTo) == "nil" and type(vFrom) == "boolean" then + vTo = false + end + -- If the same value, then return it + if vTo == vFrom then return vTo end + -- In case of two diffrent variables. + if type(vFrom) ~= type(vTo) then + StormFox2.Warning("Mixer called with values of two different types[" .. type(vFrom) .. "," .. type(vTo) .. "]") + debug.Trace() + return vFrom + elseif type(vTo) == "string" or type(vTo) == "IMaterial" or type(vTo) == "boolean" then -- String, material or bool. Return vTo. + return vTo + elseif type(vTo) == "number" then -- Number + return Lerp(nFraction, vFrom, vTo) + elseif type(vTo) == "table" and isColor(vTo) then -- Color + local r = Lerp( nFraction, vFrom.r or 255, vTo.r ) + local g = Lerp( nFraction, vFrom.g or 255, vTo.g ) + local b = Lerp( nFraction, vFrom.b or 255, vTo.b ) + local a = Lerp( nFraction, vFrom.a or 255, vTo.a ) + return Color( r, g, b, a ) + end + --StormFox2.Warning("ERROR: Unsupported mix value type[" .. type(vTo) .. "]. Returning original value") + --debug.Trace() + return vFrom +end + +local function IsSame(sName, nPercentage, nDelta) + if CurrentPercent ~= nPercentage then return false end + if not CurrentWeather then return false end + return CurrentWeather.Name == sName +end + +local function ApplyWeather(sName, nPercentage, nDelta) + hook.Run("StormFox2.weather.prechange", sName ,nPercentage ) + if nDelta and nDelta <= 0 then + nDelta = nil + elseif nDelta then + local sp = StormFox2.Time.GetSpeed_RAW() + if sp > 0 then + nDelta = nDelta / sp + end + end + if CurrentWeather and CurrentWeather.OnChange then + CurrentWeather:OnChange( sName, nPercentage, nDelta ) + end + local clear = StormFox2.Weather.Get( "Clear" ) + CurrentWeather = StormFox2.Weather.Get( sName ) + CurrentPercent = nPercentage + local stamp = StormFox2.Sky.GetLastStamp() + + if sName == "Clear" then + nPercentage = 1 + end + if nPercentage >= 1 then + for _,key in ipairs( StormFox2.Weather.GetKeys() ) do + local v = CurrentWeather:Get( key, stamp ) + if type(v) == "table" and not (v.r and v.g and v.b) then + StormFox2.Data.Set(key, v) + else + StormFox2.Data.Set(key, v, nDelta) + end + end + elseif nPercentage <= 0 then + for _,key in ipairs( StormFox2.Weather.GetKeys() ) do + StormFox2.Data.Set(key, clear:Get( key, stamp ), nDelta) + end + else -- Mixing bin + for _,key in ipairs( StormFox2.Weather.GetKeys() ) do + local var2,b_nomix = CurrentWeather:Get( key, stamp ) + local d = nDelta + if type(var2) == "table" and not (var2.r and var2.g and var2.b) then + d = nil + end + if b_nomix then + StormFox2.Data.Set(key, var2, d) + else + local var1 = clear:Get( key, stamp ) + if var2 and not var1 then -- This is not a default variable + if type(var2) == "number" then + var1 = 0 + end + end + if not var1 and var2 then -- THis is not a default varable + StormFox2.Data.Set(key, var2, d) + elseif var1 and var2 then + StormFox2.Data.Set(key, Blender(nPercentage, var1, var2), d) + end + end + end + end + if CurrentWeather.Init then + CurrentWeather.Init() + end + if CurrentWeather.Tick10 then + CurrentWeather.Tick10() + end + hook.Run("StormFox2.weather.postchange", sName ,nPercentage, nDelta ) +end + +hook.Add("StormFox2.Sky.StampChange","StormFox2.Weather.Stamp",function(_,nLerpTime) + ApplyWeather(CurrentWeather and CurrentWeather.Name or "Clear", CurrentPercent, nLerpTime) +end) + +function StormFox2.Weather.GetCurrent() + return CurrentWeather or StormFox2.Weather.Get( "Clear" ) +end + +function StormFox2.Weather.GetPercent() + return StormFox2.Data.Get("w_Percentage",CurrentPercent) +end + +function StormFox2.Weather.GetFinishPercent() + return CurrentPercent +end + +function StormFox2.Weather.GetDescription() + local c = StormFox2.Weather.GetCurrent() + if not c.GetName then + return c.Name + end + local a,b = c:GetName(StormFox2.Time.Get(), StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), StormFox2.Weather.GetPercent()) + return a,b or a +end + +local errM = Material("error") +function StormFox2.Weather.GetIcon() + local c = StormFox2.Weather.GetCurrent() + if not c.GetIcon then + return errM + end + return c.GetIcon(StormFox2.Time.Get(), StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), StormFox2.Weather.GetPercent()) +end + +local SF_UPDATE_WEATHER = 0 +local SF_INIT_WEATHER = 1 + +if SERVER then + local l_data + function StormFox2.Weather.Set( sName, nPercentage, nDelta ) + if not StormFox2.Setting.GetCache("enable", true) then return end -- Just in case + if nDelta and l_data and nDelta == l_data then + if IsSame(sName, nPercentage) then return false end + end + l_data = nDelta + -- Default vals + if not nDelta then + nDelta = 4 + end + if not nPercentage then + nPercentage = 1 + end + -- Unknown weathers gets replaced with 'Clear' + if not StormFox2.Weather.Get( sName ) then + StormFox2.Warning("Unknown weather: " .. tostring(sName)) + sName = "Clear" + end + -- In case we set the weather to clear, change it so it is the current weather at 0 instead + if sName == "Clear" and CurrentWeather and nDelta > 0 then + nPercentage = 0 + sName = CurrentWeather.Name + elseif sName == "Clear" then + nPercentage = 1 + end + lastSet = CurTime() + net.Start( StormFox2.Net.Weather ) + net.WriteBit(SF_UPDATE_WEATHER) + net.WriteUInt( math.max(0, StormFox2.Data.GetLerpEnd( "w_Percentage" )), 32) + net.WriteFloat(nPercentage) + net.WriteString(sName) + net.WriteFloat(CurTime() + nDelta) + net.Broadcast() + ApplyWeather(sName, nPercentage, nDelta) + if sName == "Clear" then + nPercentage = 0 + end + StormFox2.Data.Set("w_Percentage",nPercentage,nDelta) + return true + end + net.Receive( StormFox2.Net.Weather, function(len, ply) -- OI, what weather? + local lerpEnd = StormFox2.Data.GetLerpEnd( "w_Percentage" ) + net.Start( StormFox2.Net.Weather ) + net.WriteBit(SF_INIT_WEATHER) + net.WriteUInt( math.max(0, StormFox2.Data.GetLerpEnd( "w_Percentage" )), 32) + net.WriteFloat( CurrentPercent ) + net.WriteString( StormFox2.Weather.GetCurrent().Name ) + net.WriteFloat( StormFox2.Data.Get("w_Percentage",CurrentPercent) ) + net.Send(ply) + end) + -- Handles the terrain logic + timer.Create("StormFox2.terrain.updater", 4, 0, function() + local cW = StormFox2.Weather.GetCurrent() + local cT = StormFox2.Terrain.GetCurrent() + + if not cW then return end -- No weather!? + local terrain = cW:Get("Terrain") + if not cT and not terrain then return end -- No terrain detected + if cT and terrain and cT == terrain then return end -- Same terrain detected + if terrain then -- Switch terraintype out. This can't be the same as the other + StormFox2.Terrain.Set(terrain.Name) + elseif not terrain and not cT.lock then -- This terrain doesn't have a lock. Reset terrain + StormFox2.Terrain.Reset() + elseif not terrain and cT.lock then -- Check the lock of cT and see if we can reset + if cT:lock() then -- Lock tells us we can reset the terrain + StormFox2.Terrain.Reset() + end + end + end) + local tS = CurTime() + -- In case no weather was set + timer.Simple(8, function() + -- Clear up weather when it reaches 0 + timer.Create("StormFox2.weather.clear",1,0,function() + if not CurrentWeather then return end + if CurrentWeather.Name == "Clear" then return end + local p = StormFox2.Weather.GetPercent() + if p <= 0 then + StormFox2.Weather.Set("Clear", 1, 0) + end + end) + if CurrentWeather then return end + StormFox2.Weather.Set("Clear", 1, 0) + end) +else + local hasLocalWeather = false + local svWeather + local function SetW( sName, nPercentage, nDelta ) + -- Block same weather + if IsSame(sName, nPercentage) then return false end + ApplyWeather(sName, nPercentage, nDelta) + if sName == "Clear" then + nPercentage = 0 + end + StormFox2.Data.Set("w_Percentage",nPercentage,nDelta) + end + function StormFox2.Weather.SetLocal( sName, nPercentage, nDelta, nTemperature) + -- If nil then remove the local weather + if not sName then + return StormFox2.Weather.RemoveLocal() + end + -- Unknown weathers gets replaced with 'Clear' + if not StormFox2.Weather.Get( sName ) then + StormFox2.Warning("Unknown weather: " .. tostring(sName)) + sName = "Clear" + end + if not hasLocalWeather then + svWeather = {StormFox2.Weather.GetCurrent().Name, StormFox2.Weather.GetFinishPercent(), StormFox2.Temperature.Get()} + end + StormFox2.Temperature.SetLocal(nTemperature) + -- Block same weather + SetW(sName, nPercentage, nDelta) + hasLocalWeather = true + end + function StormFox2.Weather.RemoveLocal() + if not hasLocalWeather then return end + SetW(svWeather[1], svWeather[2], 4) + StormFox2.Temperature.SetLocal(nil) + svWeather = nil + hasLocalWeather = false + end + net.Receive( StormFox2.Net.Weather, function(len) + local flag = net.ReadBit() == SF_UPDATE_WEATHER + local wTarget = net.ReadUInt(32) + local nPercentage = net.ReadFloat() + local sName = net.ReadString() + if flag then + local nDelta = net.ReadFloat() - CurTime() + -- Calculate the time since server set this + if not hasLocalWeather then + SetW(sName, nPercentage, nDelta) + else + svWeather[1] = sName + svWeather[2] = nPercentage + end + else + local current = net.ReadFloat() + if not hasLocalWeather then + local secondsLeft = wTarget - CurTime() + if secondsLeft <= 0 then + SetW(sName, nPercentage, 0) + else + SetW(sName, current, 0) + SetW(sName, nPercentage, secondsLeft) + end + else + svWeather[1] = sName + svWeather[2] = nPercentage + end + end + end) + -- Ask the server what weather we have + hook.Add("StormFox2.InitPostEntity", "StormFox2.terrain", function() + net.Start( StormFox2.Net.Weather ) + net.SendToServer() + end) +end + +hook.Add("Think", "StormFox2.Weather.Think", function() + if not CurrentWeather then return end + if not CurrentWeather.Think then return end + if not StormFox2.Setting.SFEnabled() then return end + CurrentWeather:Think() +end) + +timer.Create("StormFox2.Weather.tickslow", 1, 0, function() + if not CurrentWeather then return end + if not CurrentWeather.TickSlow then return end + CurrentWeather.TickSlow() +end) + +hook.Add("StormFox2.weather.postchange", "StormFox2.weather.slowtickinit", function() + if not CurrentWeather then return end + if not CurrentWeather.TickSlow then return end + CurrentWeather.TickSlow() +end) + +if CLIENT then + local c_tab = {"PostDrawTranslucentRenderables", "PreDrawTranslucentRenderables", "HUDPaint"} + for i,v in ipairs(c_tab) do + hook.Add(v, "StormFox2.Weather." .. v, function(...) + if not CurrentWeather then return end + if not CurrentWeather[v] then return end + CurrentWeather[v](...) + end) + end +end + +-- Some functions to make it easier. +function StormFox2.Weather.IsRaining() + local wT = StormFox2.Weather.GetCurrent() + if wT.Inherit == "Rain" then return true end + if wT.Name ~= "Rain" then return false end + return StormFox2.Temperature.Get() > -2 or false +end + +function StormFox2.Weather.IsSnowing() + local wT = StormFox2.Weather.GetCurrent() + if wT.Name ~= "Rain" then return false end + return StormFox2.Temperature.Get() <= -2 or false +end + +function StormFox2.Weather.GetRainAmount() + if not StormFox2.Weather.IsRaining() then return 0 end + return StormFox2.Weather.GetPercent() +end + +function StormFox2.Weather.HasDownfall() + local wT = StormFox2.Weather.GetCurrent() + if wT.Inherit == "Rain" then return true end + return wT.Name == "Rain" +end + +-- Downfall +function StormFox2.DownFall.IsEntityHit(eEnt, bDont_cache) + if not StormFox2.Weather.HasDownfall() then return false end + return (StormFox2.Wind.IsEntityInWind(eEnt,bDont_cache)) +end + +function StormFox2.DownFall.IsPointHit(vPos) + if not StormFox2.Weather.HasDownfall() then return false end + local t = util.TraceLine( { + start = vPos, + endpos = vPos + -StormFox2.Wind.GetNorm() * 262144, + mask = StormFox2.DownFall.Mask + } ) + return t.HitSky +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_2dskybox.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_2dskybox.lua new file mode 100644 index 0000000..bd446cb --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_2dskybox.lua @@ -0,0 +1,72 @@ + + +StormFox2.Setting.AddSV("darken_2dskybox", false, nil, "Effect") + +local convar = GetConVar("sv_skyname") +local mat_2dBox = "skybox/" .. convar:GetString() +local last_f = 1 +local function OnChange( str ) + StormFox2.Map.Set2DSkyBoxDarkness( last_f ) +end + +cvars.RemoveChangeCallback("sv_skyname", "sf_skynamehook") +cvars.AddChangeCallback( "sv_skyname", OnChange, "sf_skynamehook" ) + +local t = {"bk", "dn", "ft", "lf", "rt", "up"} +function StormFox2.Map.Set2DSkyBoxDarkness( f, bRemember, bDark ) + if bRemember then + last_f = f + end + local sky = convar:GetString() + if sky == "painted" then return end + if bDark == nil then + bDark = StormFox2.Setting.GetCache("darken_2dskybox", false) + end + if not StormFox2.Setting.GetCache("enable_skybox", true) or not StormFox2.Setting.SFEnabled() or not bDark then + f = 1 + end + mat_2dBox = "skybox/" .. sky + local vec = Vector( f, f, f) + + for k,v in ipairs( t ) do + local m = Material(mat_2dBox .. v) + if m:IsError() then continue end + m:SetVector("$color", vec) + m:SetInt("$nofog", 1) + m:SetInt("$ignorez", 1) + end +end + +StormFox2.Setting.Callback("darken_2dskybox", function(vVar) + StormFox2.Map.Set2DSkyBoxDarkness( last_f, false, vVar ) +end, "darken_2dskybox") + +local function SkyThink(b, str) + if not StormFox2.Setting.GetCache("enable_skybox", true) or not StormFox2.Setting.SFEnabled() then return end + if b == nil then + b = StormFox2.Setting.GetCache("use_2dskybox", false) + end + if not b then + return RunConsoleCommand("sv_skyname", "painted") + end + local s = str or StormFox2.Setting.GetCache("overwrite_2dskybox", "") + if s == "" then + local lS = 0 + if StormFox2.Sky and StormFox2.Sky.GetLastStamp then -- Something happen + lS = StormFox2.Sky.GetLastStamp() + end + local sky_options = StormFox2.Weather.GetCurrent():Get("skyBox", lS) + s = (table.Random(sky_options)) + else + StormFox2.Map.Set2DSkyBoxDarkness( last_f ) + end + RunConsoleCommand("sv_skyname", s) +end + +StormFox2.Setting.Callback("use_2dskybox",SkyThink,"2dskybox_enable") +StormFox2.Setting.Callback("overwrite_2dskybox",function(str) SkyThink(nil, str) end,"2dskybox_enable2") + +hook.Add("StormFox2.weather.postchange", "StormFox2.weather.set2dsky", function( _ ) + if not StormFox2.Setting.GetCache("use_2dskybox", false) then return end + SkyThink() +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_envioment.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_envioment.lua new file mode 100644 index 0000000..a0e9fcf --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_envioment.lua @@ -0,0 +1,329 @@ +local INVALID_VERTS = 0 +local WATER_VERTS = 1 + +StormFox2.Setting.AddSV("enable_ice",not game.IsDedicated()) +StormFox2.Setting.AddSV("enable_wateroverlay",true, nil, "Effects") + +--[[------------------------------------------------------------------------- +Localize +---------------------------------------------------------------------------]] +local round = math.Round +local util_TraceLine = util.TraceLine +local table_sort = table.sort +local LocalPlayer = LocalPlayer +--[[------------------------------------------------------------------------- +Make a few SurfaceInfo functions. +---------------------------------------------------------------------------]] + local meta = FindMetaTable("SurfaceInfo") + -- Support caching stuff + local surf_caching = {} + meta.__index = function(a,b) + return meta[b] or surf_caching[a] and surf_caching[a][b] + end + meta.__newindex = function(a,b,c) + if not surf_caching[a] then surf_caching[a] = {} end + surf_caching[a][b] = c + end + hook.Add("EntityRemoved", "ClearSurfaceInfo", function(ent) + for _,surf in ipairs(ent:GetBrushSurfaces() or {}) do + surf_caching[surf] = nil + end + end) + function meta:IsValid( ) + if self.b_invalid ~= nil then return self.b_invalid end + local b = #(self:GetVertices()) > 0 + self.b_invalid = b + return self.b_invalid + end + function meta:GetVerticesNoParallel( ) + if not self:IsValid() then return {} end + if self.v_vertNP then return table.Copy(self.v_vertNP) end + self.v_vertNP = {} + local verts = self:GetVertices() + for i,cv in ipairs(verts) do + local pv,nv = verts[i - 1] or verts[#verts], verts[i + 1] or verts[1] + local cP = ( cv - pv ):Cross( nv - pv ) + if cP.x == 0 and cP.y == 0 and cP.z == 0 then continue end -- parallel vector. + table.insert(self.v_vertNP, cv) + end + return table.Copy(self.v_vertNP) + end + function meta:GetCenter( ) + if not self:IsValid() then return end + if self.v_cent then return self.v_cent end + local verts = self:GetVertices() + if #verts < 2 then + self.v_cent = verts[1] + return self.v_cent + end + local vmax,vmin = verts[1],verts[1] + for i = 2,#verts do + vmax[1] = math.max(vmax[1],verts[i][1]) + vmax[2] = math.max(vmax[2],verts[i][2]) + vmax[3] = math.max(vmax[3],verts[i][3]) + vmin[1] = math.min(vmin[1],verts[i][1]) + vmin[2] = math.min(vmin[2],verts[i][2]) + vmin[3] = math.min(vmin[3],verts[i][3]) + end + self.v_cent = vmin + (vmax - vmin) / 2 + return self.v_cent + end + function meta:GetNormal( ) + if not self:IsValid() then return end + if self.v_norm then return self.v_norm end + local p = self:GetVertices() + if #p < 3 then return end -- Invalid brush. (Yes this happens) + local c = p[1] + local s = Vector(0,0,0) + for i = 2,#p do + s = s + ( p[i] - c ):Cross( (p[i + 1] or p[1]) - c ) + if s.x ~= 0 and s.y ~= 0 and s.z ~= 0 then -- Check if this isn't a parallel vector. + break -- Got a valid norm + end + end + self.v_norm = s:GetNormalized() + return self.v_norm + end + function meta:GetAngles( ) + if not self:IsValid() then return end + if self.a_ang then return self.a_ang end + self.a_ang = self:GetNormal():Angle() + return self.a_ang + end + function meta:GetPerimeter( ) + if not self:IsValid() then return end + if self.n_peri then return self.n_peri end + local p = self:GetVertices() + local n = 0 + for i = 1,#p do + n = n + p[i]:Distance(p[i + 1] or p[1]) + end + self.n_peri = n + return self.n_peri + end + function meta:GetArea( ) + if not self:IsValid() then return end + if self.n_area then return self.n_area end + local p = self:GetVertices() + local n = #p + if n < 3 then -- Invalid shape + self.n_area = 0 + return 0 + --elseif n == 3 then -- Triangle, but cost more? + -- local a,b,c = p[1]:Distance(p[2]),p[2]:Distance(p[3]),p[3]:Distance(p[1]) + -- local s = (a + b + c) / 2 + -- t_t[self] = sqrt( s * (s - a) * (s - b) * (s - c) ) + -- return t_t[self] + else -- Any shape + local a = Vector(0,0,0) + for i,pc in ipairs(p) do + local pn = p[i + 1] or p[1] + a = a + pc:Cross(pn) + end + a = a / 2 + self.n_area = a:Distance(Vector(0,0,0)) + return self.n_area + end + end +--[[------------------------------------------------------------------------- +Make some adv SurfaceInfo functions. +---------------------------------------------------------------------------]] + function meta:GetUVVerts() -- Creates UV-data out from the shape. + if not self:IsValid() then return end + if self.t_uv then return table.Copy(self.t_uv) end + local t = self:GetVerticesNoParallel() + local a = self:GetNormal():Angle() + local c = self:GetCenter() + local vmin,vmax + for i,v in ipairs(t) do + t[i] = (t[i] - c) + t[i]:Rotate(a) + if not vmin then + vmin = Vector(t[i].x,t[i].y,t[i].z) + vmax = Vector(t[i].x,t[i].y,t[i].z) + else + for ii = 1,3 do + vmin[ii] = math.min(vmin[ii],t[i][ii]) + vmax[ii] = math.max(vmax[ii],t[i][ii]) + end + end + end + local y_r = vmax.z - vmin.z + local x_r,x_r2 = vmax.x - vmin.x,vmax.y - vmin.y + local min_x = vmin.x + local i2 = 1 + if x_r2 > x_r then + x_r = x_r2 + i2 = 2 + min_x = vmin.y + end + local new_t = {} + for i = 1,#t do + table.insert(new_t, {u = (t[i][i2] - min_x) / x_r,v = (t[i].z - vmin.z) / y_r}) + end + self.t_uv = new_t + return table.Copy(self.t_uv) + end + function meta:GetMesh() -- Generates a mesh-table for the surfaceinfo. + if not self:IsValid() then return end + if self.t_mesh then return table.Copy(self.t_mesh) end + local verts = self:GetVerticesNoParallel() + local n = self:GetNormal() + -- Calc the height and width + local h_max,h_min = verts[1].z,verts[1].z + for i = 2,#verts do + local h = verts[i].z + h_max = math.max(h_max,h) + h_min = math.min(h_min,h) + end + local uvt = self:GetUVVerts() + local t = {} + for i = 1,3 do + table.insert(t, {pos = verts[i], u = uvt[i].u,v = uvt[i].v, normal = n}) + end + for i = 4,#verts do + table.insert(t, {pos = verts[1], u = uvt[1].u,v = uvt[1].v, normal = n}) + table.insert(t, {pos = verts[i - 1], u = uvt[i - 1].u,v = uvt[i - 1].v, normal = n}) + table.insert(t, {pos = verts[i], u = uvt[i].u,v = uvt[i].v, normal = n}) + end + self.t_mesh = t + return table.Copy(self.t_mesh) + end + function meta:GetMinSide() + if not self:IsValid() then return end + if self.n_midi then return self.n_midi end + local mi,ma + local p = self:GetVertices() + for i = 1,#p do + if not mi then + mi = p[i]:Distance(p[i + 1] or p[1]) + ma = mi + else + mi = math.min(mi,p[i]:Distance(p[i + 1] or p[1])) + ma = math.max(ma,p[i]:Distance(p[i + 1] or p[1])) + end + end + self.n_midi = mi + self.n_madi = ma + return mi + end + function meta:GetMaxSide() + if not self:IsValid() then return end + if self.n_madi then return self.n_madi end + self:GetMinSide() + return self.n_madi + end +--[[------------------------------------------------------------------------- +Generate meshes and env-points out from the map-data. +---------------------------------------------------------------------------]] +-- New surface functions + local function SurfaceInfo_GetType( eEnt, SurfaceInfo ) + if #SurfaceInfo:GetVertices() < 3 then return INVALID_VERTS end + if SurfaceInfo:IsWater() then -- Water + return WATER_VERTS + end + return INVALID_VERTS + end + + +local ice = Material("stormfox2/effects/ice_water") +local ice_size = 500 +local vec_ex = Vector(0,0,1) + +STORMFOX_WATERMESHCOLLISON = {} + +local scan = function() -- Locates all surfaceinfos we need. + StormFox2.Msg("Scanning surfaces ..") + surfaceinfos = {} + -- Scan all brushsurfaces and grab the glass/windows, water and metal. Put them in a table with matching normal. + for i,v in ipairs( game.GetWorld():GetBrushSurfaces() ) do + if not v then continue end + if not v:IsValid() then continue end + if not v:IsWater() then continue end + local v_type = SurfaceInfo_GetType( game.GetWorld(), v ) + if v_type == INVALID_VERTS then continue end -- Invalid or doesn't have a type. + if not surfaceinfos[v_type] then surfaceinfos[v_type] = {} end + table.insert(surfaceinfos[v_type], {v, v:GetCenter()} ) + end + coroutine.yield() + -- Generate water mesh + if surfaceinfos[WATER_VERTS] then + StormFox2.Msg("Generating ice-mesh [" .. #surfaceinfos[WATER_VERTS] .. "] ") + local mesh = {} + for i,v in ipairs(surfaceinfos[WATER_VERTS]) do + if StormFox2.Map.IsInside( v[2] ) then + local t = v[1]:GetVertices() + if #t >= 3 then + local t2 = {} + if #t == 3 then + for i = 1,3 do + table.insert(t2, t[i] + vec_ex) + table.insert(t2, t[i] - vec_ex) + end + table.insert(t2, t[3] + vec_ex) + table.insert(t2, t[3] - vec_ex) + table.insert(STORMFOX_WATERMESHCOLLISON, t2) + elseif #t == 4 then + for i = 1,4 do + table.insert(t2, t[i] + vec_ex) + table.insert(t2, t[i] - vec_ex) + end + table.insert(STORMFOX_WATERMESHCOLLISON, t2) + else + for i = 1,#t do + table.insert(t2, t[i] + vec_ex) + table.insert(t2, t[i] - vec_ex) + end + table.insert(STORMFOX_WATERMESHCOLLISON, t2) + end + end + end + end + end + coroutine.yield(true) +end + +local cor_scan = coroutine.wrap(scan) +local function StartGenerating() + timer.Create("SF_ENV_SCAN", 0.2, 0, function() + if cor_scan() then + cor_scan = nil + timer.Remove("SF_ENV_SCAN") + StormFox2.Msg("Meshes completed.") + end + end) + hook.Remove("StormFox2.InitPostEntity", "StormFox_ENV_SCAN") +end +hook.Add("StormFox2.InitPostEntity", "StormFox_ENV_SCAN", StartGenerating) + +local bIce = false +local function SpawnIce() + for k,v in ipairs(ents.FindByClass("stormfox_mapice")) do + v:Remove() + end + local e = ents.Create("stormfox_mapice") + e:SetPos(Vector(0,0,0)) + e:Spawn() + bIce = true +end + +local function RemoveIce() + bIce = false + for k,v in ipairs(ents.FindByClass("stormfox_mapice")) do + v:Remove() + end +end + +timer.Create("stormfox2.spawnice", 8, 0, function() + if not StormFox2.Setting.GetCache("enable_ice") then + if bIce then + RemoveIce() + end + return + end + if bIce and StormFox2.Temperature.Get() > -1 then + RemoveIce() + elseif not bIce and StormFox2.Temperature.Get() <= -8 then + SpawnIce() + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_heavens.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_heavens.lua new file mode 100644 index 0000000..9141c6d --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/framework/sv_heavens.lua @@ -0,0 +1,95 @@ +--[[------------------------------------------------------------------------- + StormFox2.Sun.SetTimeUp(nTime) Sets how long the sun is on the sky. + StormFox2.Sun.IsUp() Returns true if the sun is on the sky. + + + StormFox2.Moon.SetTimeUp(nTime) Sets how long the moon is on the sky. + +---------------------------------------------------------------------------]] +local clamp = math.Clamp + +StormFox2.Sun = StormFox2.Sun or {} +StormFox2.Moon = StormFox2.Moon or {} +StormFox2.Sky = StormFox2.Sky or {} + +-- SunRise and SunSet + --[[------------------------------------------------------------------------- + Sets the time for sunrise. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.SetSunRise(nTime) + StormFox2.Setting.Set("sunrise", nTime) + end + --[[------------------------------------------------------------------------- + Sets the tiem for sunsets. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.SetSunSet(nTime) + StormFox2.Setting.Set("sunset", nTime) + end + --[[------------------------------------------------------------------------- + Sets the sunyaw. This will also affect the moon. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.SetYaw(nYaw) + StormFox2.Setting.Set("sunyaw",nYaw) + end + --[[------------------------------------------------------------------------- + Sets the sunsize. (Normal is 30) + ---------------------------------------------------------------------------]] + function StormFox2.Sun.SetSize(n) + StormFox2.Network.Set("sun_size",n) + end + --[[------------------------------------------------------------------------- + Sets the suncolor. + ---------------------------------------------------------------------------]] + function StormFox2.Sun.SetColor(cCol) + StormFox2.Network.Set("sun_color",cCol) + end + +-- Moon + --[[------------------------------------------------------------------------- + Sets the moon phase, and increases it once pr day + ---------------------------------------------------------------------------]] + hook.Add("StormFox2.Time.NextDay","StormFox2.MoonPhase",function() + StormFox2.Moon.SetPhase( StormFox2.Moon.GetPhase() + 1 ) + end) + + function StormFox2.Moon.SetPhase( moon_phase ) + StormFox2.Network.Set("moon_phase",moon_phase % 8) + end + + StormFox2.Moon.SetPhase( math.random(0, 7) ) + +-- Skybox +local function SkyTick(b) + b = b and StormFox2.Setting.SFEnabled() + if b then -- Reenable skybox + local _2d = StormFox2.Setting.GetCache("use_2dskybox", false) + if not _2d then + RunConsoleCommand("sv_skyname", "painted") + else + local sky_over = StormFox2.Setting.GetCache("overwrite_2dskybox", "") + if sky_over == "" then + sky_over = StormFox2.Weather.GetCurrent():Get("skyBox",StormFox2.Sky.GetLastStamp()) or "skybox/sky_day02_06_hdrbk" + if type(sky_over) == "table" then + sky_over = table.Random(sky_over) + end + end + RunConsoleCommand("sv_skyname", sky_over) + end + else -- Disable skybox + local map_ent = StormFox2.Map.Entities()[1] + if not map_ent then + StormFox2.Warning("No map-entity?") + RunConsoleCommand("sv_skyname", "skybox/sky_day02_06_hdrbk") + return + end + local sky_name = map_ent["skyname"] or "skybox/sky_day02_06_hdrbk" + StormFox2.Map.Set2DSkyBoxDarkness( 1 ) + RunConsoleCommand("sv_skyname", sky_name) + end +end +local func = function(b) + SkyTick(StormFox2.Setting.GetCache("enable_skybox", true)) +end +StormFox2.Setting.Callback("enable", func, "disable_heavens") +StormFox2.Setting.Callback("clenable", func, "disable_heavenscl") +StormFox2.Setting.Callback("enable_skybox",SkyTick,"enable_skybox_call") \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_clouds.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_clouds.lua new file mode 100644 index 0000000..1f8c661 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_clouds.lua @@ -0,0 +1,266 @@ +--[[------------------------------------------------------------------------- +Render clouds +---------------------------------------------------------------------------]] +local cos,sin,rad = math.cos,math.sin,math.rad +local max,min,clamp,ceil,abs = math.max,math.min,math.Clamp,math.ceil,math.abs +local z_level = -.8 +local eye_mult = -.0001 + +-- Generate dome mesh + local Render_Dome = Mesh() + local top_height = 20 + local sc = 20 + + local stage = 0 + local e_r = rad(45) + local t_s = 1 + local function UVMulti(uv,mul) + return (uv - 0.5) * mul + 0.5 + end + mesh.Begin( Render_Dome, MATERIAL_TRIANGLES, 24 ) + for i = 1,8 do + local yaw = rad(45 * i) + -- Generate the top + -- L + local c,s = cos(yaw),sin(yaw) + local L = {Vector(c * sc,s * sc,0.1 * -sc),(1 + c) / 2 * t_s,(1 + s) / 2 * t_s} + mesh.Position(L[1]) + mesh.TexCoord( stage, L[2], L[3]) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + -- R + local c,s = cos(yaw + e_r),sin(yaw + e_r) + local R = {Vector(c * sc,s * sc,0.1 * -sc),(1 + c) / 2 * t_s, (1 + s) / 2 * t_s} + mesh.Position(R[1]) + mesh.TexCoord( stage, R[2],R[3] ) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + -- T + mesh.Position(Vector(0,0,0.1 * top_height)) + mesh.TexCoord( stage, 0.5 * t_s,0.5 * t_s ) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + + -- Generate side1 + mesh.Position(L[1]) + mesh.TexCoord( stage, L[2], L[3]) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + + local R2 = {R[1] * 1.4 - Vector(0,0,4),UVMulti(R[2],1.4),UVMulti(R[3],1.4)} + mesh.Position(R2[1]) + mesh.TexCoord( stage, R2[2],R2[3] ) + mesh.Color(255,255,255,0) + mesh.AdvanceVertex() + + mesh.Position(R[1]) + mesh.TexCoord( stage, R[2],R[3] ) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + + -- Generate side 2 + mesh.Position(L[1]) + mesh.TexCoord( stage, L[2], L[3]) + mesh.Color(255,255,255,255) + mesh.AdvanceVertex() + + mesh.Position(L[1] * 1.4 - Vector(0,0,4)) + mesh.TexCoord( stage, UVMulti(L[2], 1.4), UVMulti(L[3],1.4)) + mesh.Color(255,255,255,0) + mesh.AdvanceVertex() + + mesh.Position(R2[1]) + mesh.TexCoord( stage, R2[2],R2[3] ) + mesh.Color(255,255,255,0) + mesh.AdvanceVertex() + end + mesh.End() +-- Local functions + local matrix = Matrix() + local function RenderDome(pos,mat,alpha) + matrix:Identity() + matrix:Translate( vector_origin + pos ) + --mat:SetAlpha(alpha) + cam.PushModelMatrix(matrix) + render.SetBlend(alpha / 255) + render.SetMaterial(mat) + Render_Dome:Draw() + render.SetBlend(1) + cam.PopModelMatrix() + end + local lastRT + local function RTRender(RT,blend) + lastRT = RT + render.PushRenderTarget( RT ) + render.ClearDepth() + render.Clear( 0, 0, 0, 0 ) + cam.Start2D() + if not blend then return end + render.OverrideAlphaWriteEnable( true, true ) + end + local function RTMask(srcBlend,destBlend,srcBlendAlpha,destBlendAlpha) + local srcBlend = srcBlend or BLEND_ZERO + local destBlend = destBlend or BLEND_SRC_ALPHA -- + local blendFunc = 0 -- The blend mode used for drawing the color layer + local srcBlendAlpha = srcBlendAlpha or BLEND_DST_ALPHA -- Determines how a rendered texture's final alpha should be calculated. + local destBlendAlpha = destBlendAlpha or BLEND_ZERO -- + local blendFuncAlpha = 0 -- + render.OverrideBlend( true, srcBlend, destBlend, blendFunc, srcBlendAlpha, destBlendAlpha, blendFuncAlpha) + end + local function RTEnd(Mat_Output) + render.OverrideBlend( false ) + render.OverrideAlphaWriteEnable( false ) + cam.End2D() + render.PopRenderTarget() + -- Apply changes + Mat_Output:SetTexture("$basetexture",lastRT) + end + local function DrawTextureRectWindow(w,h,o_x,o_y) -- Render function that supports fractions (surface libary is whole numbers only) + if o_x < 0 then o_x = o_x + w end + if o_y < 0 then o_y = o_y + h end + o_x = o_x % w + o_y = o_y % h + local m = Matrix() + m:Identity() + m:Translate(Vector(o_x % w,o_y % h)) + cam.PushModelMatrix(m) + surface.DrawTexturedRect(0,0,w,h) + surface.DrawTexturedRect(-w,0,w,h) + surface.DrawTexturedRect(0,-h,w,h) + surface.DrawTexturedRect(-w,-h,w,h) + cam.PopModelMatrix() + end +-- Load materials + -- Side clouds + local side_clouds = {} + for _,fil in ipairs(file.Find("materials/stormfox2/effects/clouds/side_cloud*.png","GAME")) do + local png = Material("stormfox2/effects/clouds/" .. fil,"nocull noclamp alphatest") + png:SetInt("$flags",2099250) + table.insert(side_clouds,{png,png:GetInt("$realwidth") / png:GetInt("$realheight")}) + end + -- Top clouds + local layers = 4 + local sky_mats = {} + local offset = {} + local params = {} + params[ "$basetexture" ] = "" + params[ "$translucent" ] = 0 + params[ "$vertexalpha" ] = 1 + params[ "$vertexcolor" ] = 1 + params[ "$nofog" ] = 1 + params[ "$nolod" ] = 1 + params[ "$nomip" ] = 1 + params["$additive"] = 0 + for i = 1,layers do + sky_mats[i] = CreateMaterial("StormFox_RTSKY" .. i,"UnlitGeneric",params) + end + local cloudbig = Material("stormfox2/effects/clouds/clouds_big.png","nocull noclamp smooth") + -- 8240 + cloudbig:SetInt("$flags",2099250) + cloudbig:SetFloat("$nocull",1) + cloudbig:SetFloat("$nocull",1) + cloudbig:SetFloat("$additive",0) + local sky_rts = {} + local texscale = 512 + for i = 1,layers do + sky_rts[i] = GetRenderTargetEx( "StormFox_Sky" .. i, texscale, texscale, 1, MATERIAL_RT_DEPTH_NONE, 2, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGBA8888) + offset[i] = {i * 99,i * 33} + end + local function safeCall(...) + hook.Run("StormFox2.2DSkybox.CloudLayerRender", ...) + end + local function UpdateCloudMaterial(layer,cloud_alpha) + local blend = true + local d_seed = layer * 33 + render.PushFilterMag( TEXFILTER.ANISOTROPIC ) + render.PushFilterMin( TEXFILTER.ANISOTROPIC ) + -- Start RT render + RTRender(sky_rts[layer],blend) + -- Render RT texture + surface.SetMaterial(cloudbig) + surface.SetDrawColor(Color(255,255,255,cloud_alpha)) + --surface.DrawTexturedRect(0,0,texscale,texscale) + DrawTextureRectWindow(texscale,texscale,offset[layer][1] + d_seed,offset[layer][2] + d_seed) + -- If we error in here, gmod will crash. + local b, reason = pcall(safeCall, texscale, texscale, layer) + if not b then ErrorNoHalt(reason) end + -- Mask RT tex + -- RTMask() + -- surface.SetDrawColor(Color(255,255,255,255 - cloud_alpha)) + -- surface.SetMaterial(cloudbig) + -- DrawTextureRectWindow(texscale,texscale,offset[layer][1] + d_seed,offset[layer][2] + d_seed) + + -- End RT tex + RTEnd(sky_mats[layer]) + render.PopFilterMag() + render.PopFilterMin() + end + local col = Color(255,0,0,175) + local v = Vector(0,0,-20) + local function RenderCloud(mat_id,yaw,s_size,alpha, pos) + local mat = side_clouds[mat_id] + if not mat then return end + render.SetMaterial(mat[1]) + local pitch = 0.11 * s_size + local n = Angle(pitch,yaw,0):Forward() + col.a = math.max(175 * alpha, 255) + render.DrawQuadEasy( n * -200 + pos + v, n, s_size * mat[2] , s_size, col, 180 ) + end + local function LerpColor(f, col1, col2) + return Color( Lerp(f, col1.r, col2.r), Lerp(f, col1.g, col2.g), Lerp(f, col1.b, col2.b) ) + end +-- Cloud movement + hook.Add("PreRender","StormFox2.Client.CloudMove",function() + local w_ang = rad(StormFox2.Wind.GetYaw()) + local w_force = max(StormFox2.Wind.GetForce(),0.1) * 0.08 * RealFrameTime() + local x_w,y_w = cos(w_ang) * w_force,sin(w_ang) * w_force + for i = 1,layers do + local ri = (layers - i + 1) + local x,y = offset[i][1],offset[i][2] + offset[i] = {x + x_w * ri ,y + y_w * ri} + end + end) + +hook.Add("StormFox2.2DSkybox.CloudLayer","StormFox2.Client.Clouds",function(eye) + if not StormFox2 then return end + if not StormFox2.Mixer then return end + local cl_amd = StormFox2.Mixer.Get("clouds",0) + --if cl_amd <= 0 then return end + -- Update material-color + local c = StormFox2.Mixer.Get("bottomColor") or Color(204, 255, 255) + -- Render sideclouds + local vec = Vector(c.r,c.g,c.b) / 255 + for k,v in ipairs(side_clouds) do + v[1]:SetVector("$color",vec) + end + local cloud_speed = StormFox2.Time.GetSpeed_RAW() * 0.1 + local sideclouds = 10 * cl_amd + for i = 1,sideclouds do + local a = 1 + if i < sideclouds and i == math.floor(sideclouds) then + a = sideclouds - math.floor(sideclouds) + end + local row = math.floor(i / 3) + local m_id = i % #side_clouds + 1 + local y_start = (i % 3) * 120 + row * 33 + local size = (3 + i % 5) * 24 + RenderCloud(m_id,y_start + i + SysTime() * cloud_speed, size, a, eye * eye_mult * 10 * i / 10 ) + end + -- Render top clouds + local up = Vector(0,0,1) + local n = max(0,min(math.ceil(layers * cl_amd),layers)) + local thunder = 0 + if StormFox2.Thunder then + thunder = min(255,StormFox2.Thunder.GetLight() or 0) / 25 + end + for i = 1,n do + local ri = n - i + layers + local cloud_amplifier = 1 + .4 * (1 - (i / n)) + if i == 1 then + cloud_amplifier = cloud_amplifier + thunder + end + UpdateCloudMaterial(i,255) + sky_mats[i]:SetVector("$color",Vector(min(c.r * cloud_amplifier,255),min(c.g * cloud_amplifier,255),min(c.b * cloud_amplifier,255)) / 255 ) + RenderDome(up * (z_level + 0.4 * ri) + eye * eye_mult,sky_mats[i],255) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_effects.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_effects.lua new file mode 100644 index 0000000..dfc6534 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_effects.lua @@ -0,0 +1,146 @@ + +-- Breath Efect +do + local threshold = 2 -- IRL it is 7.2C, but i think the community would like to tie it closer to snow. + StormFox2.Setting.AddCL("enable_breath", true) + local m_mats = {(Material("particle/smokesprites_0001")),(Material("particle/smokesprites_0002")),(Material("particle/smokesprites_0003"))} + local function GetMouth( ply ) + local att = ply:LookupAttachment("mouth") + if att <= 0 then return end -- No mouth? + return ply:GetAttachment(att) + end + + local function DoEffect(ply, size) + if not _STORMFOX_PEM then return end -- Just in case + local pos, ang + local e = 1 + if ply ~= StormFox2.util.ViewEntity() then -- "Someone else" + local att = GetMouth( ply ) + if not att then return end + pos = att.Pos + ang = att.Ang + else -- Our viewpoint + -- Check the view + local view = StormFox2.util.GetCalcView() + if view.drawviewer then -- Third person + local att = GetMouth( ply ) + if not att then return end + pos = att.Pos + ang = att.Ang + else + e = 2 + ang = Angle(-view.ang.p,view.ang.y,0) + pos = view.pos + ang:Forward() * 3 + ang:Up() * -2 + end + end + local l = StormFox2.Map.GetLight() / 100 + local p = _STORMFOX_PEM["2D"]:Add(table.Random(m_mats),pos) + p:SetStartSize(1) + p:SetEndSize(size) + p:SetStartAlpha(math.min(255, 15 + math.random(55,135) * l * e)) + p:SetEndAlpha(0) + p:SetLifeTime(0) + p:SetGravity(Vector(0,0,4)) + p:SetDieTime(1) + p:SetLighting(false) + p:SetRoll(math.random(360)) + p:SetRollDelta(math.Rand(-0.5,0.5)) + p:SetVelocity(ang:Forward() * 2 + ply:GetVelocity() / 5) + end + -- Runs the effect on the player and returns next time it should be called. + local function CheckEffect(ply) + if not IsValid( ply) then return end + if not ply:Alive() then return end + if ply:WaterLevel() >= 3 then return end + if ply:InVehicle() then + local e = ply:GetVehicle() + if not IsValid( e ) then return end + if e:GetClass() ~= "prop_vehicle_jeep" then return end -- An open vehicle + end + if not StormFox2.Wind.IsEntityInWind(ply) then return end + local len = ply:GetVelocity():Length() + local t = math.Clamp(1.5 - (len / 100),0.2,1.5) + DoEffect(ply,5 + (len / 100)) + return math.Rand(t,t * 2) + end + -- The most optiomal way is to check within the renderhook. + local function RunEffect(ply) + if not StormFox2.Setting.GetCache("enable_breath") then return end + if not StormFox2.Setting.SFEnabled() then return end + if StormFox2.Temperature.Get() > threshold then return end -- Breaht is visible at 7.2C or below + if (ply._sfbreath or 0) >= CurTime() then return end + local cE = CheckEffect( ply ) + if not cE then + ply._sfbreath = CurTime() + 1 + return + end + ply._sfbreath = CurTime() + cE + end + hook.Add("PostPlayerDraw", "StormFox2.Effect.Breath", RunEffect) + -- We also need to check when the player is in first person. + timer.Create("StormFox2.Effect.BreathT", 1, 0, function() + local LP = LocalPlayer( ) + if not IsValid( LP ) then return end + RunEffect( LP ) + end) +end + +-- Depth Filter +local W,H = ScrW(), ScrH() +local depth_r = GetRenderTarget("SF_DepthFilter", W,H, true) +local depthLayer = Material( "stormfox2/shader/depth_layer" ) +local a = 0 +local l +hook.Add("StormFox2.weather.postchange", "StormFox2.DepthFilter.Reset", function(b) + if l and l == b then return end + l = b + a = 0 +end) + +-- Depth doesn't work on all versions +local t = { + ["unknown"] = false, -- Doesn't support the effect. + ["chromium"] = true, + ["dev"] = true, + ["prerelease"] = false, -- Doesn't support the effect. + ["x86-64"] = true +} + +local p = false +local function Patch() + if t[BRANCH] or p then return end + p = true + depthLayer:SetUndefined("$detail") + depthLayer:SetUndefined("$detailblendmode") + StormFox2.Warning("This version doesn't support depth-filter depth!") +end +hook.Add( "StormFox2.DepthFilterRender", "StormFox2.DepthFilter", function() + if not render.SupportsPixelShaders_2_0() then return end + local dFr = StormFox2.Weather.GetCurrent().DepthFilter + -- Calculate Alpha + if not dFr then + a = 0 + return + end + if StormFox2.Environment.Get().outside then + a = math.Approach(a, 1, FrameTime() * .8) + else + a = math.Approach(a, 0, FrameTime() * 5) -- Quick fadeaway + end + if a <= 0 then return end + Patch() + render.UpdateScreenEffectTexture() + render.UpdateFullScreenDepthTexture() + -- Render RT + render.PushRenderTarget( depth_r ) + render.Clear( 0,0,0,0, true, true) + cam.Start2D() + dFr(W,H, a) + cam.End2D() + render.PopRenderTarget() + render.CopyRenderTargetToTexture( render.GetFullScreenDepthTexture() ) + depthLayer:SetTexture("$basetexture", depth_r) + depthLayer:SetFloat("$alpha", a) + render.SetMaterial( depthLayer ) + render.DrawScreenQuad() +end ) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_localization.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_localization.lua new file mode 100644 index 0000000..fb8bb2e --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_localization.lua @@ -0,0 +1,251 @@ +-- This is a localization backup, in case the file didn't transferre. +if not file.Exists("resource/localization/en/stormfox.properties", "GAME") then return end +local str = [[ + #StormFox2.weather = weather + # https://github.com/Facepunch/garrysmod/blob/master/garrysmod/resource/localization/en/community.properties Check this first + # weather, day, night, sun, lightning, snow, cloud + + #Misc + sf_auto=Auto + sf_customization=Customization + sf_window_effects=Window Effects + footprints=footprints + temperature=temperature + fog=Fog + + #Weather + sf_weather.clear=Clear + sf_weather.clear.windy=Windy + sf_weather.cloud=Cloudy + sf_weather.cloud.thunder=Thunder + sf_weather.cloud.storm=Storm + sf_weather.rain=Raining + sf_weather.rain.sleet=Sleet + sf_weather.rain.snow=Snowing + sf_weather.rain.thunder=Thunder + sf_weather.rain.storm=Storm + sf_weather.fog.low=Haze + sf_weather.fog.medium=Fog + sf_weather.fog.high=Thick Fog + sf_weather.lava=Lava + sf_weather.fallout=Nuclear Fallout + + #Tool + sf_tool.name=StormFox2 Tool + sf_tool.desc=Allows you to edit StormFox2 map settings. + sf_tool.surface_editor=Surface Editor + sf_tool.surface_editor.desc=Allows you to edit surface-types. + sf_tool.light_editor=Light Editor + sf_tool.light_editor.desc=Allows you to add/remove sf-lights. + sf_enable_breath=Enables breath + sf_enable_breath.desc=Makes players breath visible in cold. + + #Settings + sf_enable=Enable StormFox + sf_enable.desc=Enable / Disable StormFox 2 + sf_clenable=Enable StormFox + sf_clenable.desc=Enable / Disable StormFox 2. Requires sf_allow_csenable. + sf_allow_csenable=Allow Clients to disable StormFox 2 + sf_allow_csenable.desc=Enabling this will allow clients to disable StormFox 2. + + sf_mthreadwarning=These settings can boost your FPS:\n%s\nWarning\: This Might crash on some older AMD CPUs! + sf_holdc=Hold C + sf_weatherpercent=Weather Amount + sf_setang=Set Angle + sf_setang.desc=Sets the wind-angle to your view. + sf_setwind=Sets the windspeed in m/s + sf_wcontoller=SF Controller + sf_map.light_environment.check=This map support fast lightchanges. + sf_map.light_environment.problem=This map will cause lagspikes for clients, when the light changes. + sf_map.env_wind.none=This map doesn't support windgusts. + sf_map.logic_relay.check=This map has custom day/night relays. + sf_map.logic_relay.none=This map doens't have custom day/night relays. + sf_windmove_players=Affect players + sf_windmove_players.desc=Affect player movment in strong wind. + sf_windmove_foliate=Affect Foliate + sf_windmove_foliate.desc=Foliate moves with the wind. + sf_windmove_props=Affect Props + sf_windmove_props.desc=Props will move with the wind. This can cause lag! + sf_windmove_props_break=Damage Props + sf_windmove_props_break.desc=Props will take damage in the wind. + sf_windmove_props_makedebris=Change CollisionGroup + sf_windmove_props_makedebris.desc=Will make props change collisiongroup, reducing lag. + sf_windmove_props_unfreeze=Unfreeze props. + sf_windmove_props_unfreeze.desc=Unfreeze props being moved by the wind. + sf_windmove_props_unweld=Unweld props. + sf_windmove_props_unweld.desc=Unweld props being moved by the wind. + sf_windmove_props_max=Max props being moved. + sf_windmove_props_max.desc=Max amount of props moving. This can cause lag! + + sf_enable_fogz=Overwrite farZ fog + sf_enable_fogz.desc=Overwrites the maps farZ fog. This might look bad on some maps. + sf_enable_ice=Enable ice + sf_enable_ice.desc=Creates ice over water. + sf_overwrite_fogdistance=Default fog-distance. + sf_overwrite_fogdistance.desc=Overwrites the default fog-distance. + sf_hide_forecast=Hide Forecast + sf_hide_forecast.desc=Stops clients from updating the forecast. + sf_allow_weather_lightchange=Allow weather maplight + sf_allow_weather_lightchange.desc=Allows the weather to modify the maplight. + sf_addnight_temp=Add Night Temperature + sf_addnight_temp.desc=The amount the temperature drops doing night. + sf_apply_settings=Apply settings. + sf_reset_settings=Reset settings. + sf_enable_skybox=Enable Skybox + sf_enable_skybox.desc=Allows StormFox to use the skybox. + sf_use_2dskybox=Use 2D Skybox + sf_use_2dskybox.desc=Makes StormFox use 2D skyboxes instead. + sf_overwrite_2dskybox=Overwrite 2D skybox + sf_overwrite_2dskybox.desc=Overwrites the 2D skybox with another. + sf_darken_2dskybox=Darken 2D Skybox + sf_darken_2dskybox.desc=Match the skybox brightness with the map. + sf_random_round_weather=Random weather each round. + sf_random_round_weather.desc=Gamemodes like TTT will have random weathers between each round. + sf_quickselect=Quick Select. + sf_quickselect.desc=Quick select time settings. + sf_depthfilter=Depth Filter + sf_depthfilter.desc=Render 2D weather-effects on clients screen. + + + #Details + sf_quality_target=FPS Target + sf_quality_target.desc=Adjusts the quality to reach targeted FPS. + sf_quality_ultra=Ultra Quality + sf_quality_ultra.desc=Allows for more effects. + sf_12h_display=Time Display + sf_12h_display.disc=Choose 12-hour or 24-hour clock. + sf_display_temperature=Temperature Units + sf_display_temperature.desc=Choose a temperature unit. + sf_use_monthday=Date Display + sf_use_monthday.disc=Choose MM/DD or DD/MM. + + #Wind + sf_wind=Wind + sf_winddescription.calm=Calm + sf_winddescription.light_air=Light Air + sf_winddescription.light_breeze=Light Breeze + sf_winddescription.gentle_breeze=Gentle Breeze + sf_winddescription.moderate_breeze=Moderate Breeze + sf_winddescription.fresh_breeze=Fresh Breeze + sf_winddescription.strong_breeze=Strong Breeze + sf_winddescription.near_gale=Near Gale + sf_winddescription.gale=Gale + sf_winddescription.strong_gale=Strong Gale + sf_winddescription.storm=Storm + sf_winddescription.violent_storm=Violent Storm + #Hurricane is also known as Category 1 + sf_winddescription.hurricane=Hurricane + sf_winddescription.cat2=Category 2 + sf_winddescription.cat3=Category 3 + sf_winddescription.cat4=Category 4 + sf_winddescription.cat5=Category 5 + + #Time + sf_continue_time=Continuous Time + sf_continue_time.desc=Continue time from last time. + sf_real_time=Real time + sf_real_time.desc=Use the servers OS Time. + sf_start_time=Start time + sf_start_time.desc=Sets the start time. + sf_random_time=Random time + sf_random_time.desc=Sets the time randomly on server-launch. + sf_day_length=Day Length + sf_day_length.desc=How long the day is in minutes. + sf_night_length=Night Length + sf_night_length.desc=How long the night is in minutes. + + #Sun + sf_sunrise=SunRise + sf_sunrise.desc=Sets the time the sun rises. + sf_sunset=SunSet + sf_sunset.desc=Sets the time the sun sets. + sf_sunyaw=SunYaw + sf_sunyaw.desc=Sets the yaw for the sun. + #Moon + sf_moonsize=Moon Size + sf_moonsize.desc=The default moon size. + sf_moonphase=Moon Phases + sf_moonphase.desc=Enable Moon Phases. + sf_moonlock=Moon Lock + sf_moonlock.desc=Locks the moon to the sun's rotation. + + + + #'Maplight + sf_maplight_max=Max Maplight + sf_maplight_max.desc=The max lightlevel. You can adjust this if the map is too bright/dark. + sf_maplight_min=Min Maplight + sf_maplight_min.desc=The min lightlevel. You can adjust this if the map is too bright/dark. + + sf_maplight_smooth=Maplight Lerp. + sf_maplight_smooth.desc=Enables smooth light transitions. + sf_maplight_updaterate=Maplight UpdateRate + sf_maplight_updaterate.desc=The max amount of times StormFox will update the maplight doing transitions. Will cause lag on large maps! + + sf_maplight_auto.desc=Select the best/fastes option for the map. + sf_maplight_lightenv.desc=Enable light_environment. + sf_maplight_colormod.desc=Enable colormod. + sf_maplight_dynamic.desc=Enable dynamic light/shadows. + sf_maplight_lightstyle.desc=Enable lightstyle. + + sf_modifyshadows=Modify shadows + sf_modifyshadows.desc=Modify default shadows to follow the sun. + sf_modifyshadows_rate=Modify shadow rate + sf_modifyshadows_rate.desc=The seconds between each shadow-update. + + sf_extra_lightsupport=Extra Lightsupport + sf_extra_lightsupport.desc=Utilize engine.LightStyle to change the map-light. This can cause lag-spikes, but required on certain maps. + + #Effects + sf_enable_fog=Enable Fog + sf_enable_fog.desc=Allow StormFox to edit the fog. + sf_allow_fog_change=Allow clients to toggle fog. + sf_allow_fog_change.desc=Enabling this will allow clients to toggle fog. + sf_footprint_enabled=Enable Footprints + sf_footprint_enabled.desc=Enable footprint effects. + sf_footprint_playeronly=Player Footprints Only. + sf_footprint_playeronly.desc=Only players make footprints. + sf_footprint_distance=Footprint Render Distance + sf_footprint_distance.desc=Max render distance for footprints. + sf_footprint_max=Max Footprints + sf_footprint_max.desc=Max amount of footprints + + sf_edit_tonemap=Enable tonemap + sf_edit_tonemap.desc=Allow StormFox to edit the tonemap. + sf_enable_wateroverlay=Render water overlay + sf_enable_wateroverlay.desc=Enables water-overlay for weather-types. + + sf_extra_darkness=Extra Darkness + sf_extra_darkness.desc=Adds a darkness-filter to make bright maps darker. + sf_extra_darkness_amount=Extra Darkness Amount + sf_extra_darkness_amount.desc=Scales the darkness-filter. + + sf_overwrite_extra_darkness=Overwrite Extra Darkness + sf_overwrite_extra_darkness.desc=Overwrites the players sf_extra_darkness. + sf_footprint_enablelogic=Enables Serverside Footprints + sf_footprint_enablelogic.desc=Enables server-side footprints. + + sf_window_enable=Enable window effects + sf_window_enable.desc=Enables window weather effects. + sf_window_distance=Window Render Distance + sf_window_distance.desc=The render distance for breakable windows. + sf_override_foliagesway=Override Foliagesway + sf_override_foliagesway.desc=Overrides and applies foliagesway to most foliage on launch. + + #Weather + sf_auto_weather=Auto weather + sf_auto_weather.desc=Automatically change weather over time. + sf_max_weathers_prweek=Max Weathers Pr Week + sf_max_weathers_prweek.desc=Max amount of weathers pr week. + sf_temp_range=Temperature range + sf_temp_range.desc=The min and max temperature. + sf_temp_acc=Temperature change. + sf_temp_acc.desc=The max temperature changes pr day. +]] + +for k, v in ipairs( string.Explode("\n", str)) do + if string.match(v, "%s-#") then continue end + local a,b = string.match(v, "%s*(.+)=(.+)") + if not a or not b then continue end + language.Add(a, b) +end diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_menu.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_menu.lua new file mode 100644 index 0000000..7d03382 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_menu.lua @@ -0,0 +1,363 @@ +StormFox2.Menu = StormFox2.Menu or {} + +local function niceName(sName) + if sName[1] == "#" then + sName = sName:sub(2) + end + sName = string.Replace(sName, "_", " ") + local str = "" + for s in string.gmatch(sName, "[^%s]+") do + str = str .. string.upper(s[1]) .. string.sub(s, 2) .. " " + end + return string.TrimRight(str, " ") +end + +local function wrapText(sText, wide) + wide = wide - 10 + local tw,th = surface.GetTextSize(language.GetPhrase(sText)) + local lines,b = 1, false + local s = "" + for w in string.gmatch(sText, "[^%s,]+") do + local tt = s .. (b and " " or "") .. w + if surface.GetTextSize(tt) >= wide then + s = s .. "\n" .. w + lines = lines + 1 + else + s = tt + end + b = true + end + return s, lines +end + +local m = Material("gui/gradient") +local function paintKnob(self,x, y) -- Skin doesn't have x or y pos + local skin = self:GetSkin() + if ( self:GetDisabled() ) then return skin.tex.Input.Slider.H.Disabled( x, y, 15, 15 ) end + if ( self.Depressed ) then + return skin.tex.Input.Slider.H.Down( x, y, 15, 15 ) + end + if ( self.Hovered ) then + return skin.tex.Input.Slider.H.Hover( x, y, 15, 15 ) + end + skin.tex.Input.Slider.H.Normal( x, y, 15, 15 ) +end +local notchesColor = Color(0,0,0,100) + +-- Tips +local function AddTip( self, text ) + if IsValid( self.tTip ) then return end + self.tTip = vgui.Create("DTooltip") + self.tTip:SetText( text ) + self.tTip.TargetPanel = self + self.tTip:PositionTooltip() +end +local function RemoveTip( self ) + if IsValid( self.tTip ) then + self.tTip:Remove() + end + self.tTip = nil +end + +local tabs = { + [1] = {"Start","#start",(Material("stormfox2/hud/menu/dashboard.png")),function(board) + board:AddTitle("#information") + local dash = vgui.Create("DPanel", board) + dash.Paint = empty + dash:Dock(TOP) + dash:SetTall(100) + local fps, qu, sup, mth + -- FPS + fps = StormFox2.Menu.FPSRing() + dash:Add(fps) + -- Quality + qu = StormFox2.Menu.QualityRing() + dash:Add(qu) + -- Support GPU + sup = StormFox2.Menu.SupportRing() + dash:Add(sup) + -- MThread + mth = StormFox2.Menu.MThreadRing() + dash:Add(mth) + function dash:PerformLayout(w, h) + local a = w / 5 + local x = -fps:GetTall() / 2 + fps:SetPos(x + a, h - fps:GetTall()) + qu:SetPos(x + a*2, h - qu:GetTall()) + sup:SetPos(x + a*3, h - sup:GetTall()) + mth:SetPos(x + a*4, h - sup:GetTall()) + end + + local l = vgui.Create( "DLabel", board ) + l:Dock(TOP) + l:DockMargin(16, 0, 0, 0) + -- l:SetColor(color_black) + l:SetFont("DermaDefaultBold") + l:SetText( "#sf_" .. "quality_target" ) + l:SetAutoStretchVertical(true) + + local l = vgui.Create("DLabel", board) + l:Dock(TOP) + l:DockMargin(16, 0, 0, 0) + l:SetText(language.GetPhrase("#sf_" .. "quality_target.desc")) + l:SetMultiline(true) + l:SetWrap(true) + l:SetAutoStretchVertical(true) + + local FPSTarget = StormFox2.Menu.FPSTarget() + board:Add(FPSTarget) + FPSTarget:DockMargin(16, 0, 0, 0) + + -- EnableDisable + local p = board:AddSetting("clenable") + --local qs = board:AddSetting("quality_target") + board:AddSetting("quality_ultra") + board:AddTitle("#sf_customization") + local l = vgui.Create("DPanel", board) + l:DockMargin(10,0,0,0) + l:SetTall(24) + l:Dock(TOP) + function l:Paint(w,h) + local md = StormFox2.Setting.GetCache("use_monthday",false) and os.date( "%m/%d/%Y" ) or os.date( "%d/%m/%Y" ) + local dt = StormFox2.Setting.GetCache("display_temperature") + local hs = string.Explode(":", os.date( "%H:%M") or "17:23") + local n = hs[1] * 60 + hs[2] + local str = niceName(language.GetPhrase("#time")) .. ": " .. StormFox2.Time.GetDisplay(n) .. " " .. md + str = str .. " " .. niceName(language.GetPhrase("#temperature")) .. ": " .. math.Round(StormFox2.Temperature.Convert(nil,dt,22), 1) .. StormFox2.Temperature.GetDisplaySymbol() + draw.DrawText(str, "DermaDefaultBold", 5, 0, color_black, TEXT_ALIGN_LEFT) + end + board:AddSetting("12h_display") + board:AddSetting("use_monthday") + board:AddSetting("display_temperature") + end}, + [2] = {"Effects","#effects",(Material("stormfox2/hud/menu/settings.png")),function(board) + board:AddTitle(language.GetPhrase("#effects")) + local fog = board:AddSetting("enable_fog") + board:AddSetting("extra_darkness") + board:AddSetting("extra_darkness_amount") + board:AddSetting("enable_breath") + board:AddTitle(language.GetPhrase("#footprints")) + board:AddSetting("footprint_enabled") + board:AddSetting("footprint_playeronly") + board:AddSetting("footprint_distance") + board:AddSetting("footprint_max") + board:AddTitle(language.GetPhrase("#sf_window_effects")) + board:AddSetting("window_enable") + board:AddSetting("window_distance") + + fog:SetDisabled(not StormFox2.Setting.GetCache("allow_fog_change")) + StormFox2.Setting.Callback("allow_fog_change",function(vVar,_,_, self) + fog:SetDisabled(not vVar) + end,fog) + end}, + [3] = {"Misc","#misc",(Material("stormfox2/hud/menu/other.png")),function(board) + board:AddTitle("SF2 " .. language.GetPhrase("spawnmenu.utilities.settings")) + local panel = board:AddSetting("mapfile_cl") + panel:SetTitle("#makepersistent") + panel:SetDescription(language.GetPhrase("#persistent_mode") .. " data\\stormfox2\\cl_settings\\" .. game.GetMap() .. ".json") + end}, + [4] = {"DLC","DLC",(Material("stormfox2/hud/menu/dlc.png")), function(board) + hook.Run("stormfox2.menu.dlc", board) + end} +} + +local colors = CFG.skinColors + +function StormFox2.Menu.FPSRing() + local fps = vgui.Create 'SF_Setting_Ring' + fps:SetText('FPS: ') + fps:SetSize(90, 90) + function fps:Think() + if (self.u_t or 0) > SysTime() then return end + if not system.HasFocus() then return end + self.u_t = SysTime() + 1 + local t = StormFox2.Setting.GetCache('quality_target', 144) + local _, avgFPS = StormFox2.Client.GetQualityNumber() + self:SetValue(avgFPS / t) + self:SetText('FPS: ' .. math.floor(avgFPS)) + end + fps:SetColor(colors.g) + fps:SetTooltip('Соотношение текущего FPS к желаемому') + return fps +end + +function StormFox2.Menu.QualityRing() + local qu = vgui.Create 'SF_Setting_Ring' + qu:SetText('Эффекты') + qu:SetSize(90, 90) + function qu:Think() + if (self.u_t or 0) > SysTime() then return end + if not system.HasFocus() then return end + self.u_t = SysTime() + 1 + local max_q = StormFox2.Setting.GetCache('quality_ultra',false) and 20 or 7 + local q = StormFox2.Client.GetQualityNumber() + local f = q / max_q + self:SetValue(f) + self:SetText(('Эффекты\n%d%%'):format(math.floor(f * 100))) + end + qu:SetColor(colors.g) + return qu +end + +function StormFox2.Menu.SupportRing() + local sup = vgui.Create 'SF_Setting_Ring' + sup:SetText('Поддержка') + sup:SetSize(90, 90) + function sup:Think() + if (self.u_t or 0) > SysTime() then return end + if not system.HasFocus() then return end + self.u_t = SysTime() + 5 + local t = { + { render.SupportsPixelShaders_1_4(), '#problem.no_ps14' }, + { render.SupportsVertexShaders_2_0(), '#problem.no_vs20' }, + { render.SupportsPixelShaders_2_0(), '#problem.no_ps20' }, + { render.SupportsHDR(), '#problem.no_hdr' }, + } + local s = {} + for _, v in ipairs(t) do + if not v[1] then s[#s + 1] = v[2] end + end + self:SetTooltip(s[1] and table.concat(s, '\n') or 'Тут все в порядке!') + local ok = #t - #s + self:SetValue(ok / #t) + self:SetText(('Поддержка\n%d/%d'):format(ok, #t)) + end + sup:SetColor(colors.g) + return sup +end + +local mThreadBestSettings = { + ['cl_threaded_bone_setup'] = 1, + ['cl_threaded_client_leaf_system'] = 1, + ['r_threaded_client_shadow_manager'] = 1, + ['r_threaded_particles'] = 1, + ['r_threaded_renderables'] = 1, + ['r_queued_ropes'] = 1, + ['studio_queue_mode'] = 1, + ['gmod_mcore_test'] = 1, + ['mat_queue_mode'] = 2, +} +function StormFox2.Menu.MThreadRing() + local mth = vgui.Create 'SF_Setting_Ring' + mth:SetText('MThread') + mth:SetSize(90, 90) + function mth:Think() + if (self.u_t or 0) > SysTime() then return end + if not system.HasFocus() then return end + self.u_t = SysTime() + 5 + local s = {} + for k, v in pairs(mThreadBestSettings) do + if GetConVar(k):GetInt() ~= v then + s[#s + 1] = k .. ' ' .. v + end + end + local n = table.Count(mThreadBestSettings) + local ok = n - #s + local f = ok / n + self:SetValue(f) + self:SetText(('MThread\n%d/%d'):format(ok, n)) + local val + if f < 1 then + val = ('Следующие настройки могут увеличить производительность:\n\n%s\n\nВНИМАНИЕ:\nЭти настройки могут приводить к вылетам на некоторых старых процессорах AMD!'):format(table.concat(s, '\n')) + else val = 'Тут все в порядке!' end + self:SetTooltip(val) + end + mth:SetColor(colors.g) + return mth +end + +function StormFox2.Menu.FPSTarget() + local FPSTarget = vgui.Create 'SF_Setting' + FPSTarget:SetSetting 'quality_target' + + local obj = StormFox2.Setting.GetObject 'quality_target' + local slider = FPSTarget:Add 'DButton' + local text = FPSTarget:Add 'DTextEntry' + FPSTarget:MoveDescription(340) + slider:SetPos(5,15) + slider:SetSize( 300, 25 ) + slider:SetText('') + text:SetPos(304, 19) + text:SetSize(40, 20) + local hot = Color(255,0,0,205) + -- Text set + function text:OnEnter(str) + str = str:lower() + if str == language.GetPhrase("#all"):lower() or str == "all" then + str = 0 + else + str = tonumber(str) or 0 + end + obj:SetValue(str) + end + -- Slider skin functions + function slider:GetNotchColor() + return notchesColor + end + function slider:GetNotches() + return math.floor(self:GetWide() / 21) + end + -- Slider paint + function slider:Paint( w, h ) + local var = self._OvR or StormFox2.Setting.GetCache('quality_target', 144) + local cV = 300 - var + local skin = self:GetSkin() + skin:PaintNumSlider(self,w,h) + -- "warm" + surface.SetMaterial(m) + surface.SetDrawColor(hot) + local wi = w / 300 * 260 + surface.DrawTexturedRectUV(wi - 7, 4, w - wi, h - 6, 1,0,0,1) + paintKnob(self, 1 + (w - 16) / 300 * cV,-0.5) + end + function slider:UpdateText( var ) + if var > 0 then + text:SetText(var) + else + text:SetText(niceName(language.GetPhrase("#all"))) + end + end + -- Slider think + function slider:Think() + if self:IsDown() then + self._down = true + self._OvR = math.Clamp(1 - (self:LocalCursorPos() - 6) / (self:GetWide() - 12), 0, 1) * 300 + if self._OvR < 40 then + AddTip(self, 'Играть с низким FPS может быть некомфортно!') + else + RemoveTip(self) + end + self:UpdateText(math.Round(self._OvR, 0)) + else + if not text:IsEditing() then + self:UpdateText(math.Round(obj:GetValue(), 0)) + end + self._OvR = nil + RemoveTip( self ) + if self._down then + self._down = nil + local var = math.Clamp(1 - (self:LocalCursorPos() - 6) / (self:GetWide() - 12), 0, 1) * 300 + obj:SetValue( math.Round(var, 0) ) + end + end + end + slider:UpdateText(math.Round(obj:GetValue(), 0)) + + FPSTarget:Dock(TOP) + + return FPSTarget +end + +function StormFox2.Menu.Open() + if _SFMENU and IsValid(_SFMENU) then + _SFMENU:Remove() + _SFMENU = nil + end + if not LocalPlayer():IsSuperAdmin() then return end + local p = vgui.Create("SF_Menu") + _SFMENU = p + p:SetTitle("StormFox " .. niceName(language.GetPhrase("#client")) .. " ".. language.GetPhrase("#spawnmenu.utilities.settings")) + p:CreateLayout(tabs, StormFox2.Setting.GetAllClient()) + p:SetCookie("sf2_lastmenucl") + _SFMENU:MakePopup() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_menu_sv.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_menu_sv.lua new file mode 100644 index 0000000..fbe7a6f --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_menu_sv.lua @@ -0,0 +1,1159 @@ +StormFox2.Menu = StormFox2.Menu or {} + +local mapPoint = 0 +local maxMapPoint = 60 + 12 + 7 + 7 + 12 +if StormFox2.Ent.light_environments then -- Without this, the map will lag doing lightchanges + mapPoint = mapPoint + 60 +end +if StormFox2.Ent.env_winds then -- Allows windgust and a bit more "alive" wind + mapPoint = mapPoint + 12 +end +if StormFox2.Ent.shadow_controls then -- Allows modifying shadows + mapPoint = mapPoint + 7 +end +if StormFox2.Ent.env_tonemap_controllers then -- Allows to "tone down" the exposure of light + mapPoint = mapPoint + 7 +end +local hlR = StormFox2.Map.HasLogicRelay +local hasMapLogic = (hlR("dusk") or hlR("night_events")) and (hlR("dawn") or hlR("day_events")) +if hasMapLogic then + mapPoint = mapPoint + 12 +end + +local mapPointList = { + {"light_environment", StormFox2.Ent.light_environments, ["check"] ="#sf_map.light_environment.check",["problem"] = "#sf_map.light_environment.problem"}, + {"env_wind", StormFox2.Ent.env_winds, ["none"] ="#sf_map.env_wind.none"}, + {"shadow_control", StormFox2.Ent.shadow_controls}, + {"env_tonemap_controllers", StormFox2.Ent.env_tonemap_controllers}, + {"logic_relay", hasMapLogic, ["check"] = "#sf_map.logic_relay.check", ["none"] = "#sf_map.logic_relay.none"} +} + +local function niceName(sName) + if sName[1] == "#" then + sName = sName:sub(2) + end + sName = string.Replace(sName, "_", " ") + local str = "" + for s in string.gmatch(sName, "[^%s]+") do + str = str .. string.upper(s[1]) .. string.sub(s, 2) .. " " + end + return string.TrimRight(str, " ") +end + +local function CheckSetAPI(self, str ) + http.Fetch("http://api.openweathermap.org/data/2.5/weather?lat=52.6139095&lon=-2.0059601&appid=" .. str, function(body, len, head, code) + if code == 401 then -- Most likly an invalid API-Key. + self:SetPlaceholderText("INVALID CODE") + else + self:SetPlaceholderText("********************************") + StormFox2.Permission.RequestSetting( "sf_openweathermap_key", str ) + end + self:SetText("") + end) +end + +local function ResetPromt( paste ) + LocalPlayer():EmitSound("buttons/combine_button7.wav") + local f = vgui.Create("DFrame") + f:SetTitle("#addons.warning") + f:MakePopup() + f:SetSize(210, 85) + f:Center() + local l = vgui.Create("DLabel",f) + l:SetText("#addons.cannotundo") + l:Dock(TOP) + local n = vgui.Create("DButton", f) + n:SetText("#addons.cancel") + n:SetSize(100, 22) + local y = vgui.Create("DButton", f) + y:SetText("#addons.confirm") + y:SetSize(100, 22) + n:SetPos(105,58) + y:SetPos(5,58) + function n.DoClick() + f:Remove() + end + y.paste = paste + function y.DoClick(self) + LocalPlayer():EmitSound("buttons/button6.wav") + local s = StormFox2.Setting.GetCVSDefault() + self.paste:SetText(s) + StormFox2.Permission.RequestSetting("cvslist", s) + f:Remove() + chat.AddText(Color(155,155,255),"[StormFox2] ", color_white, "Would be best to restart the server.") + end +end + +local function ModifyMaterial( tex, arg ) -- -2 = Remove, -1 = Ignore, 0 = Ground, 1 = Roof + net.Start(StormFox2.Net.Texture) + net.WriteString( tex ) + net.WriteInt(arg, 3) + net.SendToServer() +end + +local function AddOption( panel, c_option, mat ) + local o_1 = niceName(language.GetPhrase("none")) + local o0 = niceName(language.GetPhrase("dirt")) + local o1 = niceName(language.GetPhrase("rooftop")) + local r = niceName(language.GetPhrase("remove")) + local b = vgui.Create("DComboBox", dList) + b.tex = mat + b:SetSortItems(false) + b:SetText(c_option) + b:AddChoice( o0, 0 ) + b:AddChoice( o1, 1 ) + b:AddChoice( o_1, -1 ) + b:AddChoice( r, -2 ) + function b:OnSelect(_, _, var) + ModifyMaterial(mat, var) + end + return b +end + +local x_mat = Material("gui/cross.png") +local function OpenMaterialPromt() + if SF_MPromt then SF_MPromt:Remove() end + SF_MPromt = vgui.Create("DFrame") + SF_MPromt:SetTitle(language.GetPhrase("#sf_tool.surface_editor")) + SF_MPromt:SetSize(400,300) + SF_MPromt:Center() + SF_MPromt:MakePopup() + + local bottom = vgui.Create("DPanel", SF_MPromt) + bottom:Dock(BOTTOM) + bottom:SetTall(24) + + local dT = vgui.Create("DTextEntry", bottom) + dT:Dock(FILL) + dT:SetPlaceholderText(niceName(language.GetPhrase("sf_tool.surface_editor.entertext"))) + + local add = vgui.Create("DButton", bottom) + add:SetText(language.GetPhrase("preset.addnew")) + add:Dock(RIGHT) + add:SetSize(120,30) + function add.DoClick() + local tex = string.Trim(dT:GetText() or "") + if #tex < 1 then return end + ModifyMaterial(tex, 0) + end + local function refresh() + local dList = vgui.Create("DListView",SF_MPromt) + dList:Dock(FILL) + dList:AddColumn( niceName(language.GetPhrase("#texture")) ) + dList:AddColumn( niceName("type") ) + for text, _t in pairs(StormFox2.Data.Get("texture_modification")) do + local t = "NULL" + if _t == -1 then + t = language.GetPhrase("none") + elseif _t == 0 then -- Ground + t = language.GetPhrase("dirt") + elseif _t == 1 then -- Roof + t = language.GetPhrase("rooftop") + end + local b = AddOption(dList, niceName(t), text) + local line = dList:AddLine( text, b ) + end + function dList:OnRowRightClick(id, pnl) + local menu = DermaMenu() + menu:AddOption( niceName(language.GetPhrase("copy")), function() + SetClipboardText( pnl:GetColumnText(1) ) + end) + menu:Open() + end + end + refresh() + hook.Add("StormFox2.data.change", SF_MPromt, refresh) + --dList:SizeToContents() +end +StormFox2.Menu.OpenMaterialPromt = OpenMaterialPromt +local c = Color(0,255,0) +local s = { {"up",-vector_up}, + {"dn",vector_up}, + {"bk",Vector(0,-1,0)}, + {"ft",Vector(0,1,0)}, + {"lf",Vector(1,0,0)}, + {"rt",Vector(-1,0,0)}, +} +local v1 = Vector(1,1,1) +local function ButtonRender( self, w, h ) + local me = StormFox2.Setting.GetCache("overwrite_2dskybox", "") == self.Name + draw.RoundedBox(3, 0, 0, w, h, me and c or color_black) + draw.RoundedBox(3, 1, 1, w - 2, h - 2, color_white) + + if self:IsHovered() and self.Name then + surface.SetDrawColor(color_black) + surface.DrawRect(1, 1, w - 2, w - 2) + local a = Angle(0,CurTime() * 30,0) + local x, y = self:LocalToScreen( 0, 0 ) + -- Find clip + local curparent = self + local leftx, topy = self:LocalToScreen( 1, 1 ) + local rightx, bottomy = self:LocalToScreen( self:GetWide() - 1, self:GetTall() - 21 ) + while ( curparent:GetParent() != nil ) do + curparent = curparent:GetParent() + + local x1, y1 = curparent:LocalToScreen( 0, 0 ) + local x2, y2 = curparent:LocalToScreen( curparent:GetWide(), curparent:GetTall() ) + + leftx = math.max( leftx, x1 ) + topy = math.max( topy, y1 ) + rightx = math.min( rightx, x2 ) + bottomy = math.min( bottomy, y2 ) + previous = curparent + end + + render.SetScissorRect( leftx, topy, rightx, bottomy, true ) + cam.Start3D( -a:Forward() * 100, a, 70, x, y, w, h, 5, 4096 ) + render.SuppressEngineLighting( true ) + for i = 1, 6 do + local mat = Material("skybox/" .. self.Name .. s[i][1]) + render.SetMaterial(mat) + local vt = mat:GetVector("$color") or v1 + mat:SetVector("$color", v1) + render.DrawQuadEasy(s[i][2] * -50, s[i][2] * 100, 100, 100, color_white,(i <= 2) and 0 or 180) + mat:SetVector("$color", vt) + end + render.SuppressEngineLighting( false ) + cam.End3D() + render.SetScissorRect( 0, 0, 0, 0, false ) + else + local vt = self.Mat:GetVector("$color") or v1 + self.Mat:SetVector("$color", v1) + surface.SetMaterial( self.Mat ) + surface.DrawTexturedRect(1, 1, w - 2, w - 2) + self.Mat:SetVector("$color", vt) + end + + draw.DrawText(self.Tex, "DermaDefault", w / 2, h - 18, color_black, TEXT_ALIGN_CENTER) +end + +local col = {Color(230,230,230), color_white} +local function Open2DSkybox() + if _SFMENU_SKYBOX2D then + _SFMENU_SKYBOX2D:Remove() + end + local f = vgui.Create("DFrame") + _SFMENU_SKYBOX2D = f + function f:Paint(w,h) + surface.SetDrawColor( col[2] ) + surface.DrawRect(0,0,w,h) + surface.SetDrawColor( col[1] ) + if self.p_left then + surface.DrawRect(0,0,self.p_left:GetWide(),24) + else + surface.DrawRect(0,0,w,24) + end + + surface.SetDrawColor(55,55,55,255) + surface.DrawRect(0, 0, w, 24) + + local t = self._title or "Window" + surface.SetFont("DermaDefault") + local tw,th = surface.GetTextSize( t ) + surface.SetTextColor(color_white) + surface.SetTextPos(5, 12 - th / 2) + surface.DrawText(t) + end + local s = "2D " .. niceName(language.GetPhrase("#skybox")) + f._title = "StormFox 2 - " .. s + f:SetTitle("") + f:MakePopup() + f:SetSize(800, 520) + f:Center() + local b = vgui.Create("DScrollPanel", f) + b:Dock(FILL) + local grid = vgui.Create( "DGrid", b ) + function f:PerformLayout(w,h) + local c = (800 - 24) / 5 + grid:SetCols( 5 ) + grid:SetColWide( c ) + grid:SetRowHeight( 175 ) + end + --grid:SetCols( 5 ) + --grid:SetColWide( 36 ) + + local list = {} + -- List + local t = {} + for k, v in ipairs( file.Find("materials/skybox/*.vmt","GAME") ) do + if not string.match(v, "[bdflru][kntfp]%.vmt") then continue end + local s = string.sub(v, 0, #v - 6) + if Material( "skybox/" .. v ):IsError() then continue end + t[s] = (t[s] or 0) + 1 + end + -- Validate + local nt = {} + for mat, n in pairs( t ) do + if mat == "painted" then continue end + if n < 6 then continue end + table.insert(nt, mat) + end + -- Sort + table.sort( nt, function(a, b) return a < b end ) + -- Default + local but = vgui.Create( "DButton" ) + but:SetText( "" ) + but:SetSize( 150, 170 ) + but.Mat = Material("stormfox2/hud/settings.png") + but.Tex = niceName(language.GetPhrase("#sf_auto")) + but.Paint = ButtonRender + function but.DoClick(self) + StormFox2.Setting.Set("overwrite_2dskybox", "") + end + grid:AddItem( but ) + + for _, mat in pairs( nt ) do + local but = vgui.Create( "DButton" ) + but:SetText( "" ) + but:SetSize( 150, 170 ) + but.Mat = Material("skybox/" .. mat .. "up") + but.Tex = niceName(mat) + but.Name = mat + but.Paint = ButtonRender + function but.DoClick(self) + StormFox2.Setting.Set("overwrite_2dskybox", self.Name) + end + grid:AddItem( but ) + end +end +local m_check = Material("icon16/accept.png") +local m_warni = Material("icon16/bullet_error.png") +local m_none = Material("icon16/cancel.png") +local m_bu = Material("gui/workshop_rocket.png") +local c_bu = Color(155,155,155) + +local col_on = Color(55,205,55,205) +local col_off = Color(55,55,55,205) +local col_dark = Color(0,0,0,205) +local function wButton(self, w, h) + local b = self.SettingTab.pr_week > 0 + derma.SkinHook( "Paint", "Button", self, w, h ) + surface.SetMaterial( self.WeatherObj.GetSymbol( 0, 20 ) ) + surface.SetDrawColor( b and col_on or col_off ) + local s = math.min(w / 2, h / 2) + surface.DrawTexturedRectRotated(s, (h - 20) / 2, s, s, 0) + surface.SetDrawColor( col_dark ) + surface.DrawRect(0, h - 20, w, 20) + --PrintTable(self.WeatherObj) + local text = self.WeatherObj:GetName(720, 20, 0, false, 1) + text = language.GetPhrase(text) or text + surface.SetFont("DermaDefault") + if surface.GetTextSize(text) > w * 0.8 then + text = text:sub(0,10) .. ".." + end + draw.DrawText(text, "DermaDefault", w / 2, h - 18, color_white, TEXT_ALIGN_CENTER) +end + +-- local lastWClick +local function DoClickWButton( self ) + if lastWClick then + lastWClick:Remove() + lastWClick = nil + end + lastWClick = vgui.Create("DFrame") + lastWClick:SetSize( 230, 360 ) + lastWClick:Center() + lastWClick:MakePopup() + lastWClick:SetTitle( self.WeatherObj:GetName(720, 20, 0, false, 1) ) + + local v = vgui.Create("DLabel", lastWClick) + v:SetText("#sf_max_weathers_prweek") + v:Dock(TOP) + -- Maxs pr week + local nNum = vgui.Create("DNumberWang", lastWClick) + nNum:Dock(TOP) + nNum:SetValue( self.SettingTab.pr_week ) + -- Amount + local v = vgui.Create("DLabel", lastWClick) + v:SetText("#sf_weatherpercent") + v:Dock(TOP) + local aMin = vgui.Create("DNumSlider", lastWClick) + aMin:Dock(TOP) + aMin:SetMinMax(0, 1) + aMin:SetText(niceName("#minimum")) + aMin:DockMargin(5, 0, 5, 0) + aMin:SetValue( self.SettingTab.amount_min ) + local aMax = vgui.Create("DNumSlider", lastWClick) + aMax:Dock(TOP) + aMax:SetMinMax(0, 1) + aMax:SetText(niceName("#maximum")) + aMax:DockMargin(5, -5, 5, 0) + aMax:SetValue( self.SettingTab.amount_max ) + + -- Day Length + local v = vgui.Create("DLabel", lastWClick) + v:SetText("#sf_day_length") + v:Dock(TOP) + local lMin = vgui.Create("DNumSlider", lastWClick) + lMin:Dock(TOP) + lMin:SetMinMax(180, 1440) + lMin:SetText(niceName("#minimum")) + lMin:DockMargin(5, 0, 5, 0) + lMin:SetValue( self.SettingTab.length_min ) + lMin.TextArea:SetEditable( false ) + function lMin.TextArea:Think() + self:SetText( StormFox2.Time.TimeToString( tonumber( lMin:GetValue() ) ) .. "h" ) + end + + local lMax = vgui.Create("DNumSlider", lastWClick) + lMax:Dock(TOP) + lMax:SetMinMax(180, 1440) + lMax:SetText(niceName("#maximum")) + lMax:DockMargin(5, -5, 5, 0) + lMax:SetValue( self.SettingTab.length_max ) + lMax.TextArea:SetEditable( false ) + function lMax.TextArea:Think() + self:SetText( StormFox2.Time.TimeToString( tonumber( lMax:GetValue() ) ) .. "h" ) + end + -- Start Time + local v = vgui.Create("DLabel", lastWClick) + v:SetText("#sf_start_time") + v:Dock(TOP) + + local _12 = StormFox2.Setting.GetCache("12h_display") + local sMin = vgui.Create("DNumSlider", lastWClick) + sMin:Dock(TOP) + sMin:SetMinMax(180, 1440) + sMin:SetText(niceName("#minimum")) + sMin:DockMargin(5, 0, 5, 0) + sMin:SetValue( self.SettingTab.start_min ) + sMin.TextArea:SetEditable( false ) + function sMin.TextArea:Think() + self:SetText( StormFox2.Time.TimeToString( tonumber( sMin:GetValue() ) , _12) ) + end + + local sMax = vgui.Create("DNumSlider", lastWClick) + sMax:Dock(TOP) + sMax:SetMinMax(180, 1440) + sMax:SetText(niceName("#maximum")) + sMax:DockMargin(5, -5, 5, 0) + sMax:SetValue( self.SettingTab.start_max ) + sMax.TextArea:SetEditable( false ) + function sMax.TextArea:Think() + self:SetText( StormFox2.Time.TimeToString( tonumber( sMax:GetValue() ), _12 ) ) + end + -- Thunder + local bThunder = vgui.Create("DCheckBoxLabel", lastWClick) + bThunder:SetText("#sf_weather.rain.thunder") + bThunder:Dock(TOP) + bThunder:SetChecked(self.SettingTab.thunder) + -- Set + local set = vgui.Create("DButton", lastWClick) + set:Dock(BOTTOM) + set:SetText(niceName( "#save_options" )) + local setting = self.obj + function set:DoClick() + local tab = {} + tab.amount_min = tonumber ( aMin:GetValue() ) + tab.amount_max = tonumber ( aMax:GetValue() ) + tab.start_min = tonumber ( sMin:GetValue() ) + tab.start_max = tonumber ( sMax:GetValue() ) + tab.length_min = tonumber ( lMin:GetValue() ) + tab.length_max = tonumber ( lMax:GetValue() ) + tab.thunder = bThunder:GetChecked() + tab.pr_week = tonumber ( nNum:GetValue() ) + local data = StormFox2.WeatherGen.ConvertTabToSetting( tab ) + setting:SetValue( data ) + if lastWClick then + lastWClick:Remove() + lastWClick = nil + end + end +end + + +-- Day Night +local quick_time = { + {"sf_default" , StormFox2.Setting.GetObject("day_length"):GetDefault(), StormFox2.Setting.GetObject("night_length"):GetDefault(), "icon16/package.png"}, + {"sf_stoptime" , 0 , 0 , "icon16/clock_pause.png" }, + --{"sf_real_time" , 12 * 60 , 12 * 60 , "icon16/clock_link.png"}, Most likely kiiinda useless. Since we got sunset and sunrise. + {"sf_alwaysday" , 12 , -1 , "icon16/weather_sun.png"}, + {"sf_alwaysnight" , -1 , 12 , "icon16/drink.png"}, + {"GTA" , 24 , 24 , "icon16/cd.png" }, -- 48 mins for a full day + {"Far Cry 6" , 30 , 30 , "icon16/cd.png" }, -- 30 for each + {"The Forest" , 24 , 12 , "icon16/cd.png" }, -- 30 for each + {"Factorio" , 6.95+2.5 , 2 + 2.5 , "icon16/cd.png" }, -- ½ of Dusk and dawn is split 1.5 + 3.5 = 5 = 2.5 for each + {"Terraria" , 15 , 9 , "icon16/cd.png" } -- 15 mins doing the day, 9 doing the night. +} +local tabs = { + [1] = {"Start","#start",(Material("stormfox2/hud/menu/dashboard.png")),function(board) + board:AddTitle(language.GetPhrase("#map") .. " " .. language.GetPhrase("#support")) + local dash = vgui.Create("DPanel", board) + + dash.Paint = empty + dash:Dock(TOP) + dash:SetTall(100) + + local p = vgui.Create("SF_Setting_Ring", dash) + p:SetText(math.Round(mapPoint / maxMapPoint, 3) * 100 .. "%") + p:SetSize(74, 74) + p:SetPos(24,10) + p:SetValue(mapPoint / maxMapPoint) + local x, y = 0,0 + for k,v in ipairs(mapPointList) do + local p = vgui.Create("DPanel", dash) + p.Paint = function() end + p:SetSize(150, 20) + local l = vgui.Create("DLabel", p) + local d = vgui.Create("DImage", p) + d:SetSize(16, 16) + if v[2] then + d:SetMaterial(m_check) + if v.check then + d:SetToolTip(v.check) + p:SetToolTip(v.check) + end + elseif v.problem then + d:SetMaterial(m_warni) + d:SetToolTip(v.problem) + p:SetToolTip(v.problem) + else + d:SetMaterial(m_none) + if v.none then + d:SetToolTip(v.none) + p:SetToolTip(v.none) + end + end + + l:SetFont('SF_Menu_H2') + l:SetText(v[1]) + l:SizeToContents() + l:SetDark( true ) + p:SetPos(124 + x * 180,14 + y * 25) + l:SetPos( 20 ) + d:SetPos(0,0) + y= y + 1 + if y > 2 then + x = x + 1 + y = 0 + end + end + board:AddSetting("enable") + board:AddSetting("allow_csenable") + + board:AddTitle(language.GetPhrase("#weather")) + + local w_panel = vgui.Create("DPanel", board) + w_panel:SetTall(260) + w_panel:DockMargin(15,0,15,0) + w_panel:Dock(TOP) + + local weather_board = vgui.Create("SF_WeatherMap", w_panel) + weather_board:Dock(FILL) + function weather_board:Paint(w,h) + local x, y = self:LocalToScreen() + StormFox2.WeatherGen.DrawForecast(w,h,true,x, y) + end + + hook.Add("StormFox2.Time.NextDay", weather_board, function() + if not StormFox2.Setting.GetCache("sf_hide_forecast", false) then return end -- Everyone gets informed + net.Start("StormFox2.weekweather") + net.SendToServer() + end) + + local wmap_board = vgui.Create("DPanel", w_panel) + wmap_board:Dock(FILL) + local wmap = vgui.Create("SF_WorldMap", wmap_board) + wmap:Dock(FILL) + + local b = not StormFox2.Setting.Get("openweathermap_enabled", false) + wmap_board:SetDisabled(b) + if b then + wmap_board:Hide() + end + StormFox2.Setting.Callback("openweathermap_enabled",function(vVar,_,_, self) + wmap_board:SetDisabled(not vVar) + if vVar then + wmap_board:Show() + else + wmap_board:Hide() + end + end,wmap) + + -- Shhhh + local xp = vgui.Create("DPanel", board) + xp:SetWide(20) + xp:SetTall(20) + xp:Dock(TOP) + xp.Paint = empty + local xpp = vgui.Create("DImageButton", xp) + xpp:Hide() + xpp:Dock(RIGHT) + xpp:SetWide(20) + xpp:SetImage("icon16/control_play_blue.png") + -- If "XP" in search, then enable xxp + function xp:Think() + local p = board:GetParent():GetParent().p_left.sp.searchbar + if not p then return end + if p:GetText():lower() == "xp" then + xpp:Show() + else + xpp:Hide() + end + end + -- If click then music + local t = {"oNXzMBA9VU4","-U3sP-KbrGk"} + local n,n2 = 1, true + function xpp:DoClick() + if xpp._sn then + xpp._sn:Remove() + xpp._sn = nil + xpp:SetImage('icon16/control_play_blue.png') + else + n = (n + 1)%2 + xpp._sn = vgui.Create("DHTML", xp) + local r = "" + if n == 1 and n2 then + r = "&start=136" + n2 = not n2 + end + xpp._sn:SetHTML([[]]) + xpp._sn:SetPos(0,19) + xpp:SetImage('icon16/control_stop_blue.png') + end + end + end}, + [2] = {"Time","#time",(Material("stormfox2/hud/menu/clock.png")),function(board) + board:AddTitle("#time") + board:AddSetting("continue_time") + board:AddSetting("real_time") + board:AddSetting("random_time") + board:AddSetting("start_time") + + + -- Quick select + local quick = vgui.Create("SF_Setting",board) + local tab = {quick} + quick:Dock(TOP) + quick:SetTitle("sf_quickselect") + quick:SetDescription("sf_quickselect.desc") + do + local cbox = vgui.Create("DComboBox", quick) + cbox:SetPos(14,16) + local length = 70 + cbox:SetText("#options") + cbox:SetSortItems(false) + surface.SetFont("DermaDefault") + for _, var in ipairs(quick_time) do + local game = var[1] + local panel = cbox:AddChoice(game, {var[2], var[3]}, false, var[4]) + length = math.max(length, surface.GetTextSize(game) + 70) + end + cbox:SetWide(length) + function cbox:OnSelect( index, val, data ) + local day, night = data[1], data[2] + StormFox2.Setting.Set("day_length", day) + StormFox2.Setting.Set("night_length", night) + end + quick:MoveDescription( length + 14, 6 ) + end + local time = vgui.Create("DPanel",board) + time:Dock(TOP) + function time:Paint(w,h) + surface.SetTextColor(color_black) + surface.SetTextPos(40,00) + surface.SetFont("SF_Menu_H2") + surface.DrawText(StormFox2.Time.GetDisplay()) + end + table.insert(tab, board:AddSetting("day_length"):SetMax( 60 ) ) + table.insert(tab, board:AddSetting("night_length"):SetMax( 60 ) ) + if StormFox2.Setting.Get("real_time") then + for _, v in ipairs( tab ) do + v:SetDisabled( true ) + end + end + StormFox2.Setting.Callback("real_time",function(var) + for _, v in ipairs( tab ) do + v:SetDisabled( var ) + end + end,quick) + --board:AddSetting("time_speed") + --board:AddSetting("nighttime_multiplier") + board:AddTitle("#sun") + board:AddSetting("sunrise") + board:AddSetting("sunset") + board:AddSetting("sunyaw") + board:AddTitle("#moon") + board:AddSetting("moonlock") + board:AddSetting("moonphase") + board:AddSetting("moonsize") + end}, + [3] = {"Weather","#weather",(Material("stormfox2/hud/menu/weather.png")),function(board) + board:AddTitle("#weather") + board:AddSetting("auto_weather") + board:AddSetting("allow_weather_lightchange") + board:AddSetting("random_round_weather") + board:AddSetting("hide_forecast") + -- board:AddSetting("addnight_temp") + -- board:AddSetting("max_weathers_prweek") + board:AddSetting("max_temp"):SetMin(-10) + board:AddSetting("min_temp"):SetMin(-10):SetMax(30) + local p = vgui.Create("DScrollPanel", board) + p:Dock(TOP) + p:SetTall( 164 ) + p.buttons = {} + p:DockMargin(24, 5, 24, 5) + + local t = StormFox2.Weather.GetAll() + local h = { + ["Clear"] = "!", + ["Rain"] = '"', + ["Cloud"] = "#", + ["Fog"] = "$" + } + table.sort(t, function(a, b) + if h[a] then a = h[a] end + if h[b] then b = h[b] end + return a < b end + ) + p:SetTall( 82 * math.ceil( #t / 6) ) + for i, sName in ipairs( t ) do + local obj = StormFox2.Setting.GetObject("wgen_" .. sName) + board:MarkUsed("wgen_" .. sName) + if not obj then StormFox2.Warning("Unable to locate settings for [" .. sName .. "]") continue end + local b = vgui.Create("DButton", p) + b.obj = obj + table.insert(p.buttons,b) + b:SetSize( 72, 64 + 16 ) + b:SetText("") + b.Paint = wButton + b.WeatherObj = StormFox2.Weather.Get( sName ) + b.SettingTab = StormFox2.WeatherGen.ConvertSettingToTab( obj:GetValue() ) + b.DoClick = DoClickWButton + obj:AddCallback(function(str) + b.SettingTab = StormFox2.WeatherGen.ConvertSettingToTab( str ) + end, b) + end + + function p:PerformLayout(w,h) + local x, y = 0, 0 + for k, v in ipairs( self.buttons ) do + v:SetPos(x,y) + x = x + ( 72 + 2 ) + if x > w - 72 then + y = y + ( 64 + 16 + 2 ) + x = 0 + end + end + end + + board:AddTitle("OpenWeatherMap API") + local apiboard = vgui.Create("DPanel", board) + apiboard:SetTall(54) + apiboard:Dock(TOP) + function apiboard.Paint() end + -- Website + local web_button = vgui.Create("DImageButton", apiboard) + web_button:SetImage("stormfox2/hud/openweather.png") + web_button:SetSize(100,42) + function apiboard:PerformLayout(width, height) + web_button:SetPos( width - 160, 5) + end + function web_button:Paint(w,h) + local s = 40 + surface.SetDrawColor(c_bu) + surface.SetMaterial(m_bu) + DisableClipping(true) + surface.DrawTexturedRectUV(-10, -10, w + 20, h + 20, 0.2,0.1,0.8,.9) + DisableClipping(false) + end + function web_button.DoClick() + gui.OpenURL("https://openweathermap.org/api") + end + + local l_t = vgui.Create("DLabel", apiboard) + l_t:SetPos( 15,3) + l_t:SetDark(true) + l_t:SetText("API: ") + local api_key = vgui.Create("DTextEntry", apiboard) + api_key:SetPos(40,3) + api_key:SetWide(200) + function api_key:OnEnter( str ) + CheckSetAPI( self, str ) + end + local b = StormFox2.Setting.Get("openweathermap_enabled", false) + if b then + api_key:SetPlaceholderText("********************************") + else + api_key:SetPlaceholderText("API KEY") + end + local lon, lat, city = vgui.Create("DTextEntry", apiboard), vgui.Create("DTextEntry", apiboard), vgui.Create("DTextEntry", apiboard) + lon:SetDrawLanguageID( false ) + lat:SetDrawLanguageID( false ) + city:SetDrawLanguageID( false ) + api_key:SetDrawLanguageID( false ) + lon:SetNumeric(true) + lat:SetNumeric(true) + -- Lon + local l_t = vgui.Create("DLabel", apiboard) + l_t:SetPos( 15, 30) + l_t:SetDark(true) + l_t:SetText("lat: ") + lon:SetPos( 40, 30) + lon:SetText(StormFox2.Setting.Get("openweathermap_lat","lat")) + -- Lat + local l_t = vgui.Create("DLabel", apiboard) + l_t:SetPos( 115, 30) + l_t:SetDark(true) + l_t:SetText("lon: ") + lat:SetPos( 140, 30) + lat:SetText(StormFox2.Setting.Get("openweathermap_lon","lon")) + local l_t = vgui.Create("DLabel", apiboard) + l_t:SetPos( 215, 30) + l_t:SetDark(true) + l_t:SetText("/") + -- City + local l_t = vgui.Create("DLabel", apiboard) + l_t:SetPos( 228, 30) + l_t:SetDark(true) + l_t:SetText("#searchbar_placeholder") + city:SetPos( 264, 30) + city:SetPlaceholderText(niceName(language.GetPhrase("#city"))) + if not b then + lon:SetDisabled( true ) + lat:SetDisabled( true ) + city:SetDisabled( true ) + end + local apienable = vgui.Create("DCheckBox", apiboard) + StormFox2.Setting.Callback("openweathermap_enabled",function(b,_,_, self) + if b then + api_key:SetText("********************************") + else + api_key:SetText("API KEY") + end + lon:SetDisabled(not b) + city:SetDisabled(not b) + lat:SetDisabled(not b) + apienable:SetChecked( b ) + end,apiboard) + + StormFox2.Setting.Callback("openweathermap_lon",function(str,_,_, self) + lon:SetText(str) + end,lon) + StormFox2.Setting.Callback("openweathermap_lat",function(str,_,_, self) + lat:SetText(str) + end,lat) + apienable:SetChecked( b ) + function apienable:OnChange(b) + StormFox2.Setting.Set("openweathermap_enabled", b) + end + apienable:SetPos(248, 5) + -- Lat. Lon + function lat:OnEnter( str ) + StormFox2.Permission.RequestSetting("sf_openweathermap_real_lat", str) + end + function lon:OnEnter( str ) + StormFox2.Permission.RequestSetting("sf_openweathermap_real_lon", str) + end + function city:OnEnter( str ) + StormFox2.Permission.RequestSetting("sf_openweathermap_real_city", str) + end + board:MarkUsed("openweathermap_enabled") + board:MarkUsed("openweathermap_lat") + board:MarkUsed("openweathermap_lon") + board:MarkUsed("openweathermap_city") + board:MarkUsed("openweathermap_key") + board:MarkUsed("openweathermap_location") + + board:AddTitle("#sf_wind") + board:AddSetting("windmove_players") + board:AddSetting("windmove_foliate") + board:AddSetting("windmove_props") + board:AddSetting("override_foliagesway") + board:AddSetting("windmove_props_break") + board:AddSetting("windmove_props_makedebris") + board:AddSetting("windmove_props_unfreeze") + board:AddSetting("windmove_props_unweld") + board:AddSetting("windmove_props_max") + + + end}, + [4] = {"Effects","#effects",(Material("stormfox2/hud/menu/settings.png")),function(board) + board:AddTitle(language.GetPhrase("#map") .. language.GetPhrase("#light")) + board:AddSetting("maplight_smooth") + board:AddSetting("edit_tonemap") + local l = vgui.Create("DLabel",board) + l:DockMargin(15,0,0,0) + l:Dock(TOP) + l:SetText(niceName(language.GetPhrase("#light") .. " " .. language.GetPhrase("#options"))) + l:SetDark(true) + l:SetFont("DermaDefaultBold") + do + local auto = board:AddSetting("maplight_auto"):HideTitle() + local lenv = board:AddSetting("maplight_lightenv"):HideTitle() + local colormod = board:AddSetting("maplight_colormod"):HideTitle() + local dynamic = board:AddSetting("maplight_dynamic"):HideTitle() + local lightstyle= board:AddSetting("maplight_lightstyle"):HideTitle() + + local c_auto = StormFox2.Setting.GetObject('maplight_auto') + local c_lenv = StormFox2.Setting.GetObject('maplight_lightenv') + local c_colo = StormFox2.Setting.GetObject('maplight_colormod') + local c_dyna = StormFox2.Setting.GetObject('maplight_dynamic') + local c_ligh = StormFox2.Setting.GetObject('maplight_lightstyle') + local warning = vgui.Create("DImage", lightstyle) + function l:Think() + if c_auto:GetValue() then + lenv:SetDisabled(true) + colormod:SetDisabled(true) + dynamic:SetDisabled(true) + lightstyle:SetDisabled(true) + else + colormod:SetDisabled(false) -- Always an option + lenv:SetDisabled(false) + dynamic:SetDisabled(false) + lightstyle:SetDisabled(false) + if c_dyna:GetValue() then + lenv:SetDisabled(true) + lightstyle:SetDisabled(true) + end + end + end + warning:SetSize(16,16) + warning:SetImage('icon16/error.png') + warning:SetPos(230,0) + warning:SetToolTip('#frame_blend_pp.desc2') + lightstyle:SetToolTip('#frame_blend_pp.desc2') + end + board:AddSetting("maplight_min") + board:AddSetting("maplight_max") + board:AddSetting("maplight_updaterate") + board:AddTitle(niceName("#shadow")) + board:AddSetting("modifyshadows") + board:AddSetting("modifyshadows_rate") + board:AddTitle(niceName("#skybox")) + local skyobj = board:AddSetting("enable_skybox") + local t = {} + table.insert(t, board:AddSetting("use_2dskybox")) + table.insert(t, board:AddSetting("darken_2dskybox")) + table.insert(t, board:AddSetting("overwrite_2dskybox")) + local skybox_select = vgui.Create("DPanel", board) + skybox_select.Paint = empty + skybox_select:SetTall( 24 ) + skybox_select:Dock(TOP) + skybox_select:DockMargin(20,0,0,0) + local button = vgui.Create("DButton", skybox_select) + table.insert(t,button) + button:SetWide(150) + local s = language.GetPhrase("#skybox") .. " " .. language.GetPhrase("#spawnmenu.search") + button:SetText(niceName(s)) + button.DoClick = Open2DSkybox + + board:AddTitle("#effects_pp") + board:AddSetting("overwrite_extra_darkness") + board:AddSetting("depthfilter") + board:AddSetting("enable_ice") + board:AddSetting("enable_wateroverlay") + board:AddSetting("footprint_enablelogic") + + board:AddTitle("surface") + do -- Material box + local p = vgui.Create("DPanel", board) + p:SetTall(30) + p:Dock(TOP) + p.Paint = empty + local b = vgui.Create("DButton", p) + b:SetText(language.GetPhrase("#sf_tool.surface_editor")) + b:SetWide(150) + b.DoClick = OpenMaterialPromt + + local b2 = vgui.Create("DButton", p) + b2:SetWide(150) + + function p:PerformLayout(w,h) + local c = 40 / w + b:SetPos(c * w,0) + b2:SetPos(c * w + 150 + 50,0) + b2:SetText(niceName(language.GetPhrase("#materials"))) + b2.DoClick = function() + RunConsoleCommand("+mat_texture_list") + end + end + end + + board:AddTitle("fog") + board:AddSetting("enable_svfog", nil, "sf_enable_fog") + board:AddSetting("allow_fog_change") + board:AddSetting("enable_fogz") + board:AddSetting("overwrite_fogdistance") + + local function en_skybox( var, var2 ) + if var2 == nil then + var2 = StormFox2.Setting.GetCache("use_2dskybox", false) + end + for i, v in ipairs( t ) do + if i > 1 and not var2 then + var = true + end + v:SetDisabled( var ) + end + end + + en_skybox( not StormFox2.Setting.GetCache("enable_skybox", true) ) + + StormFox2.Setting.Callback("use_2dskybox",function(vVar,_,_, self) + en_skybox( not StormFox2.Setting.GetCache("enable_skybox", true), vVar ) + end,skyobj) + + StormFox2.Setting.Callback("enable_skybox",function(vVar,_,_, self) + en_skybox( not vVar ) + end,skyobj) + end}, + [5] = {"Misc","#misc",(Material("stormfox2/hud/menu/other.png")),function(board) + board:AddTitle("SF2 " .. language.GetPhrase("spawnmenu.utilities.settings")) + local panel = board:AddSetting("mapfile") + panel:SetTitle("#makepersistent") + panel:SetDescription(language.GetPhrase("#persistent_mode") .. " data\\stormfox2\\sv_settings\\" .. game.GetMap() .. ".json") + -- + board:AddTitle("CVS" .. " " .. niceName(language.GetPhrase("#spawnmenu.utilities.settings"))) + local cvs = vgui.Create("DPanel", board) + cvs:SetTall(240) + cvs:Dock(TOP) + cvs.Paint = function() end + surface.SetFont("SF_Display_H3") + local length = 0 + + local copy = vgui.Create("DButton", cvs) + length = surface.GetTextSize(language.GetPhrase("#spawnmenu.menu.copy")) + copy:SetText(language.GetPhrase("#spawnmenu.menu.copy")) + + local insert = vgui.Create("DButton", cvs) + insert:SetText(niceName(language.GetPhrase("#ugc_upload.update"))) + length = math.max(length, surface.GetTextSize(language.GetPhrase("#ugc_upload.update"))) + + local setsetting = vgui.Create("DButton", cvs) + setsetting:SetText(language.GetPhrase("#sf_apply_settings")) + length = math.max(length, surface.GetTextSize(language.GetPhrase("#sf_apply_settings"))) + + local reset = vgui.Create("DButton", cvs) + reset:SetText("") + length = math.max(length, surface.GetTextSize(language.GetPhrase("#sf_reset_settings"))) + local c = Color(255,55,55) + local c2 = Color(255,255,255,15) + local t = language.GetPhrase("#sf_reset_settings") + function reset:Paint(w,h) + draw.RoundedBox(3, 0, 0, w, h, color_black) + draw.RoundedBox(3, 1, 1, w - 2, h - 2, c) + if self:IsHovered() then + draw.RoundedBox(3, 1, 1, w - 2, h - 2, c2) + end + draw.DrawText(t, "SF_Display_H3", w / 2, h / 5, color_white, TEXT_ALIGN_CENTER) + end + + copy:SetFont( "SF_Display_H3" ) + copy:SetSize( length + 10, 24) + insert:SetFont( "SF_Display_H3" ) + insert:SetSize(length + 10, 24) + setsetting:SetFont( "SF_Display_H3" ) + setsetting:SetSize(length + 10, 24) + reset:SetSize(length + 10, 24) + copy:SetPos( 20, 20) + insert:SetPos( 20, 60) + setsetting:SetPos( 20, 100) + reset:SetPos( 20, 140) + local paste = vgui.Create("DTextEntry", cvs) + paste:SetDrawLanguageID( false ) + function copy.DoClick() + SetClipboardText( paste:GetText() ) + end + function insert.DoClick() + paste:SetText( StormFox2.Setting.GetCVS() ) + end + function setsetting.DoClick() + StormFox2.Permission.RequestSetting("cvslist", paste:GetText()) + end + function reset.DoClick() + ResetPromt( paste ) + end + paste:SetPos( length + 50 , 20) + paste:SetMultiline( true ) + local c = length + 80 + function cvs:PerformLayout(w, h) + paste:SetSize( w - c , h - 40) + end + paste:SetText( StormFox2.Setting.GetCVS() ) + end}, + [6] = {"DLC","DLC",(Material("stormfox2/hud/menu/dlc.png")), function(board) + hook.Run("stormfox2.svmenu.dlc", board) + end}, + [7] = {"Changelog", niceName(language.GetPhrase("#changelog")),(Material("stormfox2/hud/menu/other.png")),function(board) + local p = vgui.Create("DHTML", board) + board.PerformLayout = function(self,w,h) + p:SetTall(h) + p:SetWide(w) + end + p:SetPos(0,0) + + p:OpenURL('https://steamcommunity.com/sharedfiles/filedetails/changelog/2447774443') + end} +} + +local col = {Color(230,230,230), color_white} +local col_dis = Color(0,0,0,55) +local col_dis2 = Color(0,0,0,55) +local bh_col = Color(55,55,55,55) + +local icon = Material("icon16/zoom.png") +local icon_c = Color(255,255,255,180) + +local s = 20 +local side_button = function(self, w, h) + if self:IsHovered() and not self:GetDisabled() then + surface.SetDrawColor(bh_col) + surface.DrawRect(0,0,w,h) + end + + surface.SetDrawColor(self:GetDisabled() and col_dis or color_black) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(24 - s / 2, (h - s) / 2, s,s) + + local t = self.text or "ERROR" + surface.SetFont("DermaDefault") + local tw,th = surface.GetTextSize( t ) + surface.SetTextColor(self:GetDisabled() and col_dis2 or color_black) + surface.SetTextPos(48, h / 2 - th / 2) + surface.DrawText(t) +end + +local function empty() end +local function switch(sName, tab) + sName = string.lower(sName) + local pnl + for k, v in pairs(tab) do + if k == sName then + v:Show() + pnl = v + else + v:Hide() + end + end + if not IsValid(pnl) or pnl:GetDisabled() then + pnl = tab["start"] + if IsValid(pnl) then + pnl:Show() + end + end + cookie.Set("sf2_lastmenusv", sName) + return pnl +end + +local t_mat = "icon16/font.png" +local s_mat = "icon16/cog.png" +local n_vc = Color(55,255,55) +function StormFox2.Menu._OpenSV() + if not StormFox2.Loaded then return end + if _SFMENU and IsValid(_SFMENU) then + _SFMENU:Remove() + _SFMENU = nil + end + local p = vgui.Create("SF_Menu") + _SFMENU = p + p:SetTitle("StormFox " .. niceName(language.GetPhrase("#spawnmenu.utilities.server_settings"))) + p:CreateLayout(tabs, StormFox2.Setting.GetAllServer()) + p:SetCookie("sf2_lastmenusv") + _SFMENU:MakePopup() +end +function StormFox2.Menu.OpenSV() + net.Start("StormFox2.menu") + net.WriteBool(true) + net.SendToServer() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_sky.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_sky.lua new file mode 100644 index 0000000..fab54be --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_sky.lua @@ -0,0 +1,154 @@ +--[[------------------------------------------------------------------------- +We overwrite the sky variables. Its much better to handle it clientside. +---------------------------------------------------------------------------]] +-- Override skypaint- Since its set by each tick. + local g_SkyPaint_tab = {} + function g_SkyPaint_tab.IsValid() return true end + local g_datacache = {} + function g_SkyPaint_tab:GetNetworkVars() + return g_datacache + end + -- Setup data + local function AddDataCache(name,defaultdata) + g_datacache[name] = defaultdata + g_SkyPaint_tab["Get" .. name] = function() + return g_datacache[name] + end + g_SkyPaint_tab["Set" .. name] = function(self,var) + g_datacache[name] = var or defaultdata + end + return g_SkyPaint_tab["Set" .. name] + end + _STORMFOX_TOPCOLOROR = AddDataCache("TopColor", Vector( 0.2, 0.5, 1.0 ) ) + AddDataCache("BottomColor", Vector( 0.8, 1.0, 1.0 ) ) + AddDataCache("FadeBias", 1 ) + + AddDataCache("SunNormal", Vector( 0.4, 0.0, 0.01 ) ) + AddDataCache("SunColor", Vector( 0.2, 0.1, 0.0 ) ) + AddDataCache("SunSize", 2.0 ) + + AddDataCache("DuskColor", Vector( 1.0, 0.2, 0.0 ) ) + AddDataCache("DuskScale", 1 ) + AddDataCache("DuskIntensity", 1 ) + + AddDataCache("DrawStars", true ) + AddDataCache("StarLayers", 1 ) + AddDataCache("StarSpeed", 0.01 ) + AddDataCache("StarScale", 0.5 ) + AddDataCache("StarFade", 1.5 ) + AddDataCache("StarTexture", "skybox/starfield" ) + + AddDataCache("HDRScale", 0.66 ) + + -- Override the skypaint directly + local SkyPaintEnt + if #ents.FindByClass("env_skypaint") > 0 then + SkyPaintEnt = ents.FindByClass("env_skypaint")[1] + end + hook.Add("Think","StormFox2.sky.paintFix",function() + if not IsValid(g_SkyPaint) then return end + -- Disable skybox and reset entity + if not StormFox2.Setting.GetCache("enable_skybox", true) or not StormFox2.Setting.SFEnabled() then + if SkyPaintEnt and type(g_SkyPaint) ~= "Entity" then + g_SkyPaint = SkyPaintEnt + end + return + end + if type(g_SkyPaint) ~= "Entity" then + return + end + if g_SkyPaint:GetClass() == "env_skypaint" then + -- We'll hande it from here + SkyPaintEnt = g_SkyPaint + g_SkyPaint = g_SkyPaint_tab + end + end) +-- Local functions + local min,max,abs,app = math.min,math.max,math.abs,math.Approach + local function ColVec(col,div) + if not div then + return Vector(col.r,col.g,col.b) + end + return Vector(col.r / div,col.g / div,col.b / div) + end +-- Read and set the skydata + hook.Add("Think","StormFox2.sky.think",function() + if not IsValid(g_SkyPaint) then return end + if not StormFox2.Time then return end + if not StormFox2.Mixer then return end + if not StormFox2.Setting.SFEnabled() then return end + if not StormFox2.Setting.GetCache("enable_skybox", true) then return end + if StormFox2.Setting.GetCache("use_2dskybox",false,nil, "Effects") then return end + -- Top color + Thunder + local fogAm + if StormFox2.Fog then + fogAm = StormFox2.Fog.GetAmount() + end + local thunder = 0 + if StormFox2.Thunder then + local cl_amd = StormFox2.Mixer.Get("clouds",0) or 0 + thunder = min(255,StormFox2.Thunder.GetLight() or 0) * 0.1 + (cl_amd * .9) + end + local t_data = StormFox2.Mixer.Get("topColor") or Color( 51, 127.5, 255 ) + local t_color = Color(max(thunder,t_data.r),max(thunder,t_data.g),max(thunder,t_data.b)) + local b_color = StormFox2.Mixer.Get("bottomColor") or Color(204, 255, 255) + if fogAm and fogAm > 0.75 then + t_color = StormFox2.Mixer.Blender((fogAm - .75) * 3, t_color, StormFox2.Fog.GetColor()) + end + g_SkyPaint:SetTopColor(ColVec(t_color,255)) + g_SkyPaint:SetBottomColor(ColVec(b_color,255)) + g_SkyPaint:SetFadeBias(StormFox2.Mixer.Get("fadeBias",0.2)) + g_SkyPaint:SetDuskColor(ColVec(StormFox2.Mixer.Get("duskColor",color_white) or color_white,255)) + g_SkyPaint:SetDuskIntensity(StormFox2.Mixer.Get("duskIntensity",1.94)) + g_SkyPaint:SetDuskScale(StormFox2.Mixer.Get("duskScale",0.29)) + + -- Stars + local n = StormFox2.Mixer.Get("starFade",100) * 0.015 + if n <= 0 then + g_SkyPaint:SetDrawStars(false) + g_SkyPaint:SetStarFade(0) + else + g_SkyPaint:SetDrawStars(true) + g_SkyPaint:SetStarSpeed((StormFox2.Mixer.Get("starSpeed") or 0.001) * StormFox2.Time.GetSpeed_RAW()) + g_SkyPaint:SetStarFade(n) + g_SkyPaint:SetStarScale(StormFox2.Mixer.Get("starScale") or 0.5) + g_SkyPaint:SetStarTexture(StormFox2.Mixer.Get("starTexture","skybox/starfield")) + end + -- SunSize + local s_size = StormFox2.Mixer.Get("sunSize",2) * (StormFox2.Mixer.Get("skyVisibility",100) / 100) + g_SkyPaint:SetSunSize(s_size / 10) + + if StormFox2.Sun and StormFox2.Sun.GetAngle then + g_SkyPaint:SetSunNormal(StormFox2.Sun.GetAngle():Forward()) + end + g_SkyPaint:SetHDRScale(StormFox2.Mixer.Get("HDRScale",0.7)) + end) + +-- Debug + if true then return end + local x = 0 + local x2 = 0 + local function drawVal(text, val) + if type(val) == "table" then + val = val.r .. " " .. val.g .. " " .. val.b + end + draw.DrawText(text .. ": " .. tostring(val), "DermaDefault", x2, x * 20, color_white, TEXT_ALIGN_LEFT) + x = x + 1 + end + hook.Add("HUDPaint", "SF_DEBUG.Sky", function() + local t_color = StormFox2.Mixer.Get("topColor") or Color( 51, 127.5, 255 ) + local b_color = StormFox2.Mixer.Get("bottomColor") or Color(204, 255, 255) + x = 1 + x2 = 10 + drawVal("StormFox2 Debug","") + x2 = 20 + drawVal("TopColor",t_color) + drawVal("BottomColor",t_color) + drawVal("fadeBias",StormFox2.Mixer.Get("fadeBias",0.2)) + drawVal("duskIntensity",StormFox2.Mixer.Get("duskIntensity",1.94)) + drawVal("duskScale",StormFox2.Mixer.Get("duskScale",0.29)) + drawVal("starFade",StormFox2.Mixer.Get("starFade",100)) + drawVal("starScale",StormFox2.Mixer.Get("starScale",0.5)) + drawVal("starSpeed",StormFox2.Mixer.Get("starSpeed",0.001)) + drawVal("starTexture",StormFox2.Mixer.Get("starTexture","skybox/starfield")) + end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_spook.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_spook.lua new file mode 100644 index 0000000..c887240 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_spook.lua @@ -0,0 +1,23 @@ + +local s = string.Explode("-", os.date("%m-%d")) +if s[1] ~= "10" then return end +if tonumber(s[2]) < 20 then return end + +-- Layers are large by design. (Kinda like a fish lense). Center is "large". +local mat = Material("hud/killicons/default") +local c = Color(255,255,255,0) +local dist = 80 -- 80 to 60 +local size = 32 -- 64 to 32 +hook.Add("StormFox2.2DSkybox.CloudLayerRender", "StormFox2.IAmNotHere", function(w, h, layer) + local d = StormFox2.Date.GetYearDay() + if d % 2 == 1 then return end + if layer ~= 1 then return end + local rotate = d * 33 % 360 + local p = StormFox2.Weather.GetPercent() + c.a = math.min(105, (p - 0.1) * 1000) + if c.a <= 0 then return end + local x, y, ang = math.cos(math.rad(rotate)) * dist, math.sin(math.rad(rotate)) * dist, t + surface.SetDrawColor(c) + surface.SetMaterial(mat) + surface.DrawTexturedRectRotated(w / 2 + x,h / 2 + y, size,size, 90 - rotate) +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_weather_gen.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_weather_gen.lua new file mode 100644 index 0000000..fdb6a40 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/cl_weather_gen.lua @@ -0,0 +1,418 @@ + +StormFox2.WeatherGen = StormFox2.WeatherGen or {} +-- Settings +local autoWeather = StormFox2.Setting.AddSV("auto_weather",true,nil, "Weather") +local hideForecast = StormFox2.Setting.AddSV("hide_forecast",false,nil, "Weather") + +-- OpenWeatherMap + --[[ + Some of these settings are fake, allow the client to set the settings, but never retrive the secured ones. + So you can't see the correct location or api_key and variables won't be networked to the clients. + ]] + + StormFox2.Setting.AddSV("openweathermap_key", "", nil,"Weather") -- Fake'ish setting + StormFox2.Setting.AddSV("openweathermap_location", "", nil,"Weather") -- Fake'ish setting + StormFox2.Setting.AddSV("openweathermap_city","",nil,"Weather") -- Fake'ish setting + +local OWEnabled = StormFox2.Setting.AddSV("openweathermap_enabled",false,nil,"Weather") + StormFox2.Setting.AddSV("openweathermap_lat",52,nil,"Weather",-180,180) -- Unpercise setting + StormFox2.Setting.AddSV("openweathermap_lon",-2,nil,"Weather",-90,90) -- Unpercise setting + +-- Generator + StormFox2.Setting.AddSV("min_temp",-10,nil,"Weather") + StormFox2.Setting.AddSV("max_temp",20,nil, "Weather") + + local function toStr( num ) + local c = tostring( num ) + return string.rep("0", 4 - #c) .. c + end + local default + local function SplitSetting( str ) + if #str< 20 then return default end -- Invalid, use default + local tab = {} + local min = math.min(100, string.byte(str, 1,1) - 33 ) / 100 + local max = math.min(100, string.byte(str, 2,2) - 33 ) / 100 + tab.amount_min = math.min(min, max) + tab.amount_max = math.max(min, max) + + local min = math.min(1440,tonumber( string.sub(str, 3, 6) ) or 0) + local max = math.min(1440,tonumber( string.sub(str, 7, 10) ) or 0) + tab.start_min = math.min(min, max) + tab.start_max = math.max(min, max) + + local min = tonumber( string.sub(str, 11, 14) ) or 0 + local max = tonumber( string.sub(str, 15, 18) ) or 0 + + tab.length_min = math.min(min, max) + tab.length_max = math.max(min, max) + + tab.thunder = string.sub(str, 19, 19) == "1" + tab.pr_week = tonumber( string.sub(str, 20) ) or 0 + return tab + end + local function CombineSetting( tab ) + local c =string.char( 33 + (tab.amount_min or 0) * 100 ) + c = c .. string.char( 33 + (tab.amount_max or 0) * 100 ) + + c = c .. toStr(math.Clamp( math.Round( tab.start_min or 0), 0, 1440 ) ) + c = c .. toStr(math.Clamp( math.Round( tab.start_max or 0), 0, 1440 ) ) + + c = c .. toStr(math.Clamp( math.Round( tab.length_min or 360 ), 180, 9999) ) + c = c .. toStr(math.Clamp( math.Round( tab.length_max or 360 ), 180, 9999) ) + + c = c .. (tab.thunder and "1" or "0") + + c = c .. tostring( tab.pr_week or 2 ) + return c + end + local default_setting = {} + default_setting["Rain"] = CombineSetting({ + ["amount_min"] = 0.4, + ["amount_max"] = 0.9, + ["start_min"] = 300, + ["start_max"] = 1200, + ["length_min"] = 360, + ["length_max"] = 1200, + ["pr_week"] = 3 + }) + default_setting["Cloud"] = CombineSetting({ + ["amount_min"] = 0.2, + ["amount_max"] = 0.7, + ["start_min"] = 300, + ["start_max"] = 1200, + ["length_min"] = 360, + ["length_max"] = 1200, + ["pr_week"] = 3 + }) + default_setting["Clear"] = CombineSetting({ + ["amount_min"] = 1, + ["amount_max"] = 1, + ["start_min"] = 0, + ["start_max"] = 1440, + ["length_min"] = 360, + ["length_max"] = 1440, + ["pr_week"] = 7 + }) + -- Morning fog + default_setting["Fog"] = CombineSetting({ + ["amount_min"] = 0.4, + ["amount_max"] = 0.8, + ["start_min"] = 360, + ["start_max"] = 560, + ["length_min"] = 160, + ["length_max"] = 360, + ["pr_week"] = 1 + }) + default = CombineSetting({ + ["amount_min"] = 0.4, + ["amount_max"] = 0.9, + ["start_min"] = 300, + ["start_max"] = 1200, + ["length_min"] = 300, + ["length_max"] = 1200, + ["pr_week"] = 0 + }) + StormFox2.WeatherGen.ConvertSettingToTab = SplitSetting + StormFox2.WeatherGen.ConvertTabToSetting = CombineSetting + + if StormFox2.Weather and StormFox2.Weather.Loaded then + for _, sName in ipairs( StormFox2.Weather.GetAll() ) do + local str = default_setting[sName] or default + StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather") + end + else + hook.Add("stormfox2.postloadweather", "StormFox2.WeatherGen.Load", function() + for _, sName in ipairs( StormFox2.Weather.GetAll() ) do + local str = default_setting[sName] or default + StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather") + end + end) + end + +-- API + +local days = 2 +local days_length = days * 1440 +local hours_8 = 60 * 8 + +forecast = forecast or {} +local nul_icon = Material("gui/noicon.png") +-- Is it rain or inherits from rain? +local function isWTRain(wT) + if wT.Name == "Rain" then return true end + if wT.Inherit == "Rain" then return true end + return false +end + +local function fkey( x, a, b ) + return (x - a) / (b - a) +end + +-- The tables are already in order. +local function findNext( tab, time ) -- First one is time + for i, tab in ipairs( tab ) do + if time > tab[1] then continue end + return i + end + return 0 +end + +local function calcPoint( tab, time ) + local i = findNext( tab, time ) + local _next = tab[i] + local _first = tab[i - 1] + local _procent = 1 + if not _first then + _first = _next + else + _procent = fkey(time, _first[1], _next[1]) + end + return _procent, _first, _next +end + +net.Receive("StormFox2.weekweather", function(len) + forecast = {} + forecast.unix_time = net.ReadBool() + forecast.temperature = net.ReadTable() + forecast.weather = net.ReadTable() + forecast.wind = net.ReadTable() + forecast.windyaw = net.ReadTable() + for _, v in pairs( forecast.temperature ) do + if not forecast._minTemp then + forecast._minTemp = v[2] + else + forecast._minTemp = math.min(forecast._minTemp, v[2]) + end + if not forecast._maxTemp then + forecast._maxTemp = v[2] + else + forecast._maxTemp = math.max(forecast._maxTemp, v[2]) + end + end + if not forecast._minTemp then return end -- Invalid forecast + -- Make sure there is at least 10C between + local f = 10 - math.abs(forecast._minTemp - forecast._maxTemp) + if f > 0 then + forecast._minTemp = forecast._minTemp - f / 2 + forecast._maxTemp = forecast._maxTemp + f / 2 + end + -- Calculate / make a table for each 4 hours + forecast._ticks = {} + local lastW + for i = 0, days_length + 1440, hours_8 do + local _first = findNext( forecast.weather, i - hours_8 / 2 ) + local _last = findNext( forecast.weather, i + hours_8 / 2 ) or _first + if not _first then continue end --???? + local m = 0 + local w_type = { + ["fAmount"] = 0, + ["sName"] = "Clear" + } + for i = _first, _last do + local w_data = forecast.weather[i] + if not w_data then continue end + if w_data[2].fAmount == 0 or w_data[2].fAmount < m then continue end + m = w_data[2].fAmount + w_type = w_data[2] + end + local _tempP, _tempFirst, _tempNext = calcPoint( forecast.temperature, i ) + if _tempNext then + --local _tempP = fkey( i, ) + forecast._ticks[i] = { + ["fAmount"] = w_type.fAmount, + ["sName"] = w_type.sName, + ["nTemp"] = Lerp(_tempP, _tempFirst[2], _tempNext[2]), + ["bThunder"] = w_type.bThunder or nil + } + end + end + --PrintTable(forecast) + hook.Run("StormFox2.WeatherGen.ForcastUpdate") +end) + +function StormFox2.WeatherGen.GetForecast() + return forecast +end + +function StormFox2.WeatherGen.IsUnixTime() + return forecast.unix_time or false +end + + +-- Render forcast + +local bg = Color(26,41,72, 255) +local rc = Color(155,155,155,4) +local ca = Color(255,255,255,12) +local tempBG = Color(255,255,255,15) +local sorter = function(a,b) return a[1] < b[1] end + +local m_box = Material("vgui/arrow") +local m_c = Material("gui/gradient_up") +local function DrawTemperature( x, y, w, h, t_list, min_temp, max_temp, bExpensive, offX, offY ) + surface.SetDrawColor(rc) + surface.DrawRect(x, y, w, h) + surface.SetDrawColor(ca) + surface.DrawLine(x, y + h, x + w, y + h) + render.SetScissorRect( x + offX , y - 25 + offY, x + w + offX, y + h + offY, true ) + + local temp_p = fkey(0, max_temp, min_temp) + local tempdiff = max_temp - min_temp + + local yT = h / tempdiff + local div = 10 + if tempdiff < 25 then + div = 10 + elseif tempdiff < 75 then + div = 20 + elseif tempdiff < 150 then + div = 100 + elseif tempdiff < 300 then + div = 200 + elseif tempdiff < 500 then + div = 300 + else + div = 1000 + end + local s = math.ceil(min_temp / div) * div + local counts = (max_temp - s) / div + for temp = s, max_temp, div do + local tOff = temp - min_temp + local ly = y + h - (tOff * yT) + if temp == 0 then + surface.SetDrawColor(color_white) + surface.SetMaterial(m_box) + surface.DrawTexturedRectUV(x, ly, w, 1, 0, 0.5, w / 1 * 0.3, 0.6) + else + surface.SetDrawColor(ca) + surface.DrawLine(x, ly, x + w, ly) + end + end + + local curTim = StormFox2.Time.Get() + local oldX, oldY, oldP + surface.SetTextColor(color_white) + surface.SetFont("SF_Display_H3") + for i, data in ipairs( t_list ) do + if not data then break end + local time_p = (data[1] - curTim) / days_length + local temp_p = data[2] + local pointx = time_p * w + x + local pointy = y + h - (temp_p * h) + if oldX then + if oldX > x + w then break end + surface.SetDrawColor(color_white) + surface.DrawLine(pointx, pointy, oldX, oldY) + if bExpensive then + local triangle = { + { x = oldX , y = oldY , u = 0,v = oldP}, + { x = pointx, y = pointy, u = 1,v = temp_p}, + { x = pointx, y = y + h , u = 1,v = 0}, + { x = oldX , y = y + h , u = 0,v = 0}, + } + surface.SetMaterial(m_c) + surface.SetDrawColor(tempBG) + surface.DrawPoly( triangle ) + end + surface.SetTextPos(pointx - 5, pointy - 14) + local temp = min_temp + temp_p * tempdiff + temp = math.Round(StormFox2.Temperature.GetDisplay(temp), 1) .. StormFox2.Temperature.GetDisplaySymbol() + surface.DrawText(temp) + end + oldX = pointx + oldY = pointy + oldP = temp_p + end + render.SetScissorRect(0,0,0,0,false) + --PrintTable(t_list) +end + +local lM = Material("vgui/loading-rotate") +local lL = Material("stormfox2/logo.png") +local function DrawDisabled( str, w, h ) + draw.DrawText(str, "SF_Display_H", w / 2, h / 4, color_white, TEXT_ALIGN_CENTER) + surface.SetDrawColor(color_white) + surface.SetMaterial(lL) + surface.DrawTexturedRectRotated(w / 2, h / 3 * 2, 64, 64, 0) + surface.SetMaterial(lM) + surface.DrawTexturedRectRotated(w / 2, h / 3 * 2, 128, 128, (CurTime() * 100)% 360) +end +function StormFox2.WeatherGen.DrawForecast(w,h,bExpensive, offX, offY) + offX = offX or 0 + offY = offY or 0 + local y = 0 + surface.SetDrawColor(bg) + surface.DrawRect(0,0,w,h) + -- Check if enabled, else render disable message + if not autoWeather:GetValue() then + local s = language.GetPhrase("sf_auto_weather") or "sf_auto_weather" + local d = language.GetPhrase("#addons.preset_disabled") or "Disabled" + s = s.. ": " .. string.match(d, "%w+") + DrawDisabled( s, w, h ) + return + end + if hideForecast:GetValue() then + local s = language.GetPhrase("sf_hide_forecast") or "sf_hide_forecast" + local d = language.GetPhrase("#addons.preset_enabled") or "Enabled" + s = s.. ": " .. string.match(d, "%w+") + DrawDisabled( s, w, h ) + return + end + if not forecast or not forecast._minTemp then + local c = string.rep(".", CurTime() % 3 + 1) + DrawDisabled( "No data yet" .. c, w, h ) + return + end + local unix = StormFox2.WeatherGen.IsUnixTime() + local curTim = StormFox2.Time.Get() + -- Draw Temperature + -- Convert it into a list of temperature w procent + local c_temp = StormFox2.Temperature.Get() + local min_temp = math.min(c_temp, forecast._minTemp) + local max_temp = math.max(c_temp, forecast._maxTemp) + local abs = math.abs(max_temp - min_temp) * 0.1 + min_temp = min_temp - abs + max_temp = max_temp + abs + + local t = {} + t[1] = { curTim, fkey( c_temp, min_temp, max_temp ) } + for i, data in ipairs( forecast.temperature ) do + local time = data[1] + if time <= curTim then continue end -- Ignore anything before + table.insert(t, {time, fkey( data[2], min_temp, max_temp ) } ) + end + DrawTemperature( w * 0.05, h * 0.5 ,w * 0.9, h * 0.4,t, min_temp, max_temp, bExpensive, offX, offY) + -- Draw current weahter + surface.SetDrawColor(color_white) + surface.SetMaterial(StormFox2.Weather.GetIcon()) + surface.SetFont("SF_Display_H") + local tex = StormFox2.Weather.GetDescription() + local tw, th = surface.GetTextSize(tex) + local wide = tw + 48 + surface.DrawTexturedRect(w / 2 - 48,h * 0.05, 40,40) + draw.DrawText(tex, "SF_Display_H", w / 2 , h * 0.07, color_white, TEXT_ALIGN_LEFT) + -- Draw DayIcons + local c = math.ceil(curTim / hours_8) * hours_8 + surface.SetDrawColor(color_white) + for i = c, days_length + c - 420, hours_8 do + -- Render Time + local t_stamp = StormFox2.Time.GetDisplay( i % 1440 ) + local delt = i - curTim + local x = math.ceil(w * 0.9 / days_length * delt) + draw.DrawText(t_stamp, "SF_Display_H3", x , h * 0.9, color_white) + -- Render icon + local day = forecast._ticks[i] + local s = w / 12 + if day then + local w_type = StormFox2.Weather.Get(day.sName) + if not w_type then + surface.SetMaterial(nul_icon) + else + surface.SetMaterial(w_type.GetIcon( i % 1440, day.nTemp, day.nWind or 0, day.bThunder or false, day.fAmount or 0) ) + surface.DrawTexturedRect(x, h * 0.25, s, s) + local name = w_type:GetName(i % 1440, day.nTemp, day.nWind or 0, day.bThunder or false, day.fAmount or 0) + draw.DrawText(name, "SF_Display_H2", x + s / 2, h * 0.25 + s, color_white, TEXT_ALIGN_CENTER) + end + end + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_concommands.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_concommands.lua new file mode 100644 index 0000000..e560f38 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_concommands.lua @@ -0,0 +1,179 @@ +-- All concommands for stormfox 2 + +local function SendMsg( ply, message ) + message = "[SF2]: " .. message + if not ply then print( message ) return end + ply:PrintMessage(HUD_PRINTTALK, message) +end + +-- Menu commands +if CLIENT then + -- Server menu + concommand.Add('stormfox2_svmenu', StormFox2.Menu.OpenSV, nil, "Opens SF serverside menu") + + -- Client menu + concommand.Add('stormfox2_menu', StormFox2.Menu.Open, nil, "Opens SF clientside menu") + + -- Controller + concommand.Add('stormfox2_controller', StormFox2.Menu.OpenController, nil, "Opens SF controller menu") +else -- Console only + concommand.Add("stormfox2_settings_reset", function( ply, cmd, args, argStr ) + if ply and IsValid(ply) and not ply:IsListenServerHost() then return end -- Nope, console only + StormFox2.Setting.Reset() + end) +end + +-- Weather +concommand.Add("stormfox2_setweather", function(ply, _, arg, _) + if CLIENT then return end + -- Check if valid weather + if #arg < 1 then + SendMsg(ply, "Weather can't be nil") + return + end + local s = string.upper(string.sub(arg[1],0,1)) .. string.lower(string.sub(arg[1], 2)) + if not StormFox2.Weather.Get(s) then + SendMsg(ply, "Invalid weather [" .. s .. "]") + return + end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + StormFox2.Weather.Set( s, tonumber( arg[2] or "1" ) or 1) + end) +end) + +concommand.Add("stormfox2_setthunder", function(ply, _, _, argS) + if CLIENT then return end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + local n = tonumber(argS) or (StormFox2.Thunder.IsThundering() and 6 or 0) + StormFox2.Thunder.SetEnabled( n > 0, n ) + end) +end) + +-- Time and Date +concommand.Add("stormfox2_settime", function(ply, _, _, argS) + if CLIENT then return end + -- Check if valid + if not argS or string.len(argS) < 1 then + SendMsg(ply, "You need to type an input! Use formats like 'stormfox2_settime 19:00' or 'stormfox2_settime 7:00 PM'") + return + end + local tN = StormFox2.Time.StringToTime(argS) + if not tN then + SendMsg(ply, "Invalid input! Use formats like '19:00' or '7:00 PM'") + return + end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + StormFox2.Time.Set( argS ) + end) +end) + +concommand.Add("stormfox2_setyearday", function(ply, _, _, argStr) + if CLIENT then return end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + StormFox2.Date.SetYearDay( tonumber(argStr) or 0 ) + end) +end) + +concommand.Add("stormfox2_setwind", function(ply, _, _, argStr) + if CLIENT then return end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + StormFox2.Wind.SetForce( tonumber(argStr) or 0 ) + end) +end) + +concommand.Add("stormfox2_setwindangle", function(ply, _, _, argStr) + if CLIENT then return end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + StormFox2.Wind.SetYaw( tonumber(argStr) or 0 ) + end) +end) + +concommand.Add("stormfox2_settemperature", function(ply, _, _, argStr) + if CLIENT then return end + local temp = tonumber( string.match(argStr, "%d+") or "0" ) or 0 + if string.match(argStr, "[fF]") then + temp = StormFox2.Temperature.Convert("fahrenheit","celsius",temp) or temp + end + StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function() + StormFox2.Temperature.Set( temp ) + end) +end) + +local function SetSetting( arg, arg2, ply ) + if not arg or arg == "" then + SendMsg( ply, "You need to indecate a setting: stormfox2_setting [Setting] [Value]") + return + end + local obj = StormFox2.Setting.GetObject(arg) + if not obj then + SendMsg( ply, "Invalid setting: \"" .. tostring( arg ) .. "\"!") + return + end + if not arg2 then + SendMsg( ply, "You need a value for the setting!") + return + end + obj:SetValue( arg2 ) + SendMsg( ply, tostring( arg ) .. " = " .. tostring( arg2 )) +end + +local function AutoComplete(cmd, args) + args = string.TrimLeft(args) + local a = string.Explode(" ", args or "") + if #a < 2 then + local options = {} + for _, sName in pairs( StormFox2.Setting.GetAllServer() ) do + if string.find(string.lower(sName),string.lower(a[1]), nil, true) then + table.insert(options, "stormfox2_setting " .. sName) + end + end + if #options < 1 then + return {"stormfox2_setting [No Setting Found!]"} + elseif #options < 2 and "stormfox2_setting " .. args == options[1] then + local obj = StormFox2.Setting.GetObject(a[1]) + if not obj then + return {"stormfox2_setting [Invalid Setting!]"} + end + return {"stormfox2_setting " .. a[1] .. " [" .. obj.type .. "]"} + end + return options + elseif not a[1] or string.TrimLeft(a[1]) == "" then + return {"stormfox2_setting [Setting] [Value]"} + else + local obj = StormFox2.Setting.GetObject(a[1]) + if not obj then + return {"stormfox2_setting [Invalid Setting!]"} + else + return {"stormfox2_setting " .. a[1] .. " [" .. obj.type .. "]"} + end + end +end + +concommand.Add("stormfox2_setting", function(ply, _, _, argStr) + if CLIENT then return end + StormFox2.Permission.EditAccess(ply,"StormFox Settings", function() + local a = string.Explode(" ", argStr, false) + SetSetting(a[1],a[2]) + end) +end, AutoComplete) + +-- Forces the settings to save. +concommand.Add("stormfox2_settings_save", function(ply, _, _, _) + if CLIENT then return end + StormFox2.Permission.EditAccess(ply,"StormFox Settings", function() + SendMsg( ply, "Force saved settings to data/" .. StormFox2.Setting.GetSaveFile()) + StormFox2.Setting.ForceSave() + end) +end) + +-- Debug commands +if true then return end +concommand.Add("stormfox2_debug_spawnice", function(ply) + if ply and not ply:IsListenServerHost() then return end + SpawnIce() +end, nil, nil) + +concommand.Add("stormfox2_debug_removeice", function(ply) + if ply and not ply:IsListenServerHost() then return end + RemoveIce() +end, nil, nil) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_controller.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_controller.lua new file mode 100644 index 0000000..80b47ec --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_controller.lua @@ -0,0 +1,636 @@ + +-- Weather functions +StormFox2.Menu = StormFox2.Menu or {} +local SF_SETWEATHER = 0 +local SF_SETTEMP = 1 +local SF_SETWIND_A = 2 +local SF_SETWIND_F = 3 +local SF_SETTIME = 4 +local SF_SETTIME_S = 5 +local SF_THUNDER = 6 +local SF_YEARDAY = 7 + +if SERVER then + -- Gets called from sh_permission.lua + function StormFox2.Menu.SetWeatherData(ply, uID, var) + if uID == SF_SETWEATHER and type(var) == "table" then + if type(var[1]) ~= "string" or type(var[2])~= "number" then return end + StormFox2.Weather.Set( var[1], var[2] ) + elseif uID == SF_SETTEMP and type(var) == "number" then + StormFox2.Temperature.Set( var ) + elseif uID == SF_SETWIND_F and type(var) == "number" then + StormFox2.Wind.SetForce( var, 3 ) + elseif uID == SF_SETWIND_A and type(var) == "number" then + StormFox2.Wind.SetYaw( var, 3 ) + elseif uID == SF_SETTIME and type(var) == "number" then + StormFox2.Time.Set( var ) + elseif uID == SF_SETTIME_S and type(var) == "number" then + if not StormFox2.Time.IsPaused() then + StormFox2.Time.Pause() + else + StormFox2.Time.Resume() + end + elseif uID == SF_THUNDER and type(var) == "boolean" then + StormFox2.Thunder.SetEnabled(var, 6) + elseif uID == SF_YEARDAY and type(var) == "number" then + StormFox2.Date.SetYearDay( var ) + end + end + return +end + +-- Send a request to change the weather +local function SetWeather( uID, var ) + net.Start( StormFox2.Net.Permission ) + net.WriteUInt(1, 1) -- SF_SERVEREDIT + net.WriteUInt(uID, 4) + net.WriteType(var) + net.SendToServer() +end + +-- Menu +local t_col = Color(67,73,83) +local h_col = Color(84,90,103) +local b_col = Color(51,56,62) +local n = 0.7 +local p_col = Color(51 * n,56 * n,62 * n) +local rad,cos,sin = math.rad, math.cos, math.sin + +local grad = Material("gui/gradient_down") +local function DrawButton(self,w,h) + local hov = self:IsHovered() + local down = self:IsDown() or self._DEPRESSED + surface.SetDrawColor(b_col) + surface.DrawRect(0,0,w,h) + if self._DISABLED then + elseif down then + surface.SetDrawColor(p_col) + elseif hov then + surface.SetDrawColor(h_col) + else + surface.SetDrawColor(t_col) + end + surface.SetMaterial(grad) + surface.DrawTexturedRect(0,0,w,h) + surface.SetDrawColor(p_col) + surface.DrawOutlinedRect(0,0,w,h) +end + +local bg_color = Color(27,27,27) +local side_color = Color(44,48,54) + +local function OpenMenu( self ) + local menu = vgui.Create("DNumberWang") + menu.m_numMin = nil + function menu:SetDraggable() end + local sx = 50 - self:GetWide() + local sy = 24 - self:GetTall() + menu:MakePopup() + menu:SetDraggable(true) + local x, y = self:LocalToScreen(-sx / 2,-sy / 2) + menu:SetPos( x,y ) + menu:RequestFocus() + menu:SetSize(50,24) + menu.m_bIsMenuComponent = true + RegisterDermaMenuForClose( menu ) + function menu:GetDeleteSelf() return true end + menu:SetValue( self:GetVal() ) + menu.b = self + function menu:OnEnter( str ) + CloseDermaMenus() + if not str then return end + self.b.p:OnMenu( tonumber( str ) ) + end +end + +local color_gray = Color(155,155,155) +local function SliderNumber(self) + local p = vgui.Create("DPanel", self) + p:SetTall(18) + p._ta = 30 + function p:Paint() end + function p:SetVal(n) self.val = n end + function p:GetVal() return self.val or 0 end + p._aimval = nil + AccessorFunc(p, "_min", "Min", FORCE_NUMBER) + AccessorFunc(p, "_max", "Max", FORCE_NUMBER) + p:SetMax(1) + p:SetMin(0) + function p:GetAP() + return (self._aimval - self:GetMin() ) / ( self:GetMax() - self:GetMin() ) + end + function p:GetP() + return (self:GetVal() - self:GetMin() ) / ( self:GetMax() - self:GetMin() ) + end + function p:SetP(f) + p:SetVal( -f * self:GetMin() + f * self:GetMax() + self:GetMin() ) + end + local slider = vgui.Create("DButton", p) + local button = vgui.Create("DButton", p) + button:SetText("") + button.p = p + slider:SetText("") + function button:SetVal( n ) p:SetVal(n) end + function button:GetVal() return p:GetVal() end + function button:DoClick() + OpenMenu(self) + end + function p:OnMenu( val ) + if not val then return end + self:SetVal( val ) + self:OnVal( val ) + end + function p:DrawText( num ) return num end + function button:Paint(w,h) + if not self:IsEnabled() then return end + surface.SetDrawColor(0, 0, 0, 155) + surface.DrawRect(0, 0, w, h) + local s = p:DrawText( p:GetVal() ) + draw.DrawText(s, "DermaDefault", w / 2, 2, color_white, TEXT_ALIGN_CENTER) + end + function slider:Paint(w,h) + local v = math.Clamp(p:GetP(), 0, 1) + local a = p._aimval and math.Clamp(p:GetAP(), 0, 1) + local pos = w * v + -- Background + draw.RoundedBox(30, 0, h / 2 - 3, w, 4, color_black) + -- White + draw.RoundedBox(30, 0, h / 2 - 3, pos, 4, color_white) + if a and v ~= a then + local pos2= w * a + local mi = math.min(pos, pos2) + draw.RoundedBox(30, mi, h / 2 - 3, math.abs(pos - pos2),4, color_gray) + draw.RoundedBox(30, pos2 - 1, 0, 3, h, color_gray) + end + draw.RoundedBox(30, pos - 1, 0, 3, h, color_white) + end + function p:PerformLayout(w, h) + button:SetPos(w - self._ta,0) + button:SetSize(self._ta, h) + if self._ta > 0 then + slider:SetSize(w - self._ta - 5,18) + else + slider:SetSize(w,18) + end + slider:SetPos(0, h / 2 - 9) + end + function slider:OnDepressed() + self._update = true + end + function slider:OnReleased() + self._update = false + local x,y = self:LocalCursorPos() + local f = math.Round(math.Clamp(x / self:GetWide(), 0, 1), 2) + p:SetP( f ) + p:OnVal( p:GetVal() ) + end + function slider:Think() + if p.Think2 then + p:Think2() + end + if not self._update then return end + local x,y = self:LocalCursorPos() + local f = math.Round(math.Clamp(x / self:GetWide(), 0, 1), 2) + p:SetP( f ) + end + function p:SetTextSize( num) + self._ta = num + if num <= 0 then + button:SetEnabled(false) + else + button:SetEnabled(true) + end + self:InvalidateLayout() + end + function p:OnVal( val ) end + p:SetVal(0.6) + return p +end + +local bottom_size = 24 +local col_ba = Color(0,0,0,155) +local col_dis = Color(125,125,125,125) +local m_cir = Material("stormfox2/hud/hudring2.png") +local m_thunder = Material("stormfox2/hud/w_cloudy_thunder.png") +local padding = 15 +local padding_y = 5 + +local function addW( w_select, v, p ) + local b = vgui.Create("DButton",w_select) + b:SetSize(32,32) + b:SetText("") + b:DockMargin(0,0,0,0) + w_select:AddPanel(b) + b.weather = v + b:SetToolTip(v) + function b:OnCursorEntered() + local w = StormFox2.Weather.Get(self.weather) + if not IsValid(w) then return end -- Something bad happen + b:SetToolTip(w:GetName(StormFox2.Time.Get(), StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), p:GetVal() / 100)) + end + function b:Paint(w,h) + DrawButton(self,w,h) + local weather = StormFox2.Weather.Get(b.weather) + local mat = weather.GetSymbol and weather.GetSymbol(_,StormFox2.Temperature.Get()) + if mat then + surface.SetDrawColor(255,255,255) + surface.SetMaterial(mat) + surface.DrawTexturedRect(5,5,w - 10,h - 10) + end + end + function b:DoClick() + SetWeather(SF_SETWEATHER, {self.weather, p:GetVal() / 100}) + end +end + +local function versionGet() + if not StormFox2.Version then return "?" end + return string.format("%.2f", StormFox2.Version) +end + +local function Init(self) + self:SetSize(180,432) + self:SetPos(math.min(ScrW() * 0.8, ScrW() - 180), ScrH() / 2 - 200) + self:SetTitle("") + self.btnMaxim:SetVisible( false ) + self.btnMinim:SetVisible( false ) + function self:Paint(w,h) + surface.SetDrawColor(side_color) + surface.DrawRect(0,0,w,h) + -- Top + local t = "StormFox " .. versionGet() + surface.SetDrawColor(p_col) + surface.DrawRect(0,0,w,24) + + surface.SetFont("SF2.W_Button") + local tw,th = surface.GetTextSize(t) + surface.SetTextColor(color_white) + surface.SetTextPos(10,th / 2 - 2) + surface.DrawText(t) + end + self:DockMargin(0,24,0,0) + self:DockPadding(0,24,0,0) + -- Weather + local m_weather = vgui.Create("DPanel", self) + m_weather:SetTall(70) + m_weather:Dock(TOP) + m_weather.Paint = function() end + self.weather = m_weather + local w_button = vgui.Create("DLabel", m_weather) + w_button:SetText("") + w_button:SetTall(28) + function w_button:Paint(w,h) + local t = "Погода" + surface.SetTextColor(color_white) + surface.SetFont("SF2.W_Button") + local tw,th = surface.GetTextSize(t) + surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2) + surface.DrawText(t) + end + local w_select = vgui.Create("DHorizontalScroller", m_weather) + w_select:SetOverlap( -4 ) + w_select.num = 0 + -- Percent & W + local p = SliderNumber( self ) + p:SetToolTip('Интенсивность') + p:SetTextSize(40) + if StormFox2.Weather.GetCurrent() == StormFox2.Weather.Get('Clear') then + p:SetVal(85) + else + p:SetVal(math.Round(math.Clamp(StormFox2.Weather.GetPercent() * 100, 0, 100), 2)) + end + function p:OnVal(x) + SetWeather(SF_SETWEATHER, {StormFox2.Weather.GetCurrent().Name, x / 100}) + end + function m_weather:PerformLayout(w, h) + w_button:SetWide(w * 0.7) + w_button:SetPos(w * 0.15,5) + -- Calc the wide + local wide = w_select.num * (32 - w_select.m_iOverlap) + -- If weathers won't fit, make it default size and pos + if wide >= w * 0.9 then + w_select:SetSize(w * 0.9,32) + w_select:SetPos(w * 0.05, 32) + else -- Calc calculate the middle + w_select:SetSize(wide,32) + w_select:SetPos(w * 0.05 + (w * 0.9 - wide) / 2 , 32) + end + end + local t = StormFox2.Weather.GetAll() + addW(w_select, "Clear", p) + for k,v in ipairs(t) do + if v == "Clear" then continue end + addW(w_select, v, p) + w_select.num = w_select.num + 1 + end + p:SetMin(1) + p:SetMax(100) + p:Dock(TOP) + p:DockMargin(padding,0,padding,padding_y) + function p:DrawText( s ) + return s .. "%" + end + -- Thunder + local tP = vgui.Create("DPanel", self) + tP:Dock(TOP) + tP:SetTall(32) + tP.Paint = empty + local thunder = vgui.Create("DButton", tP) + thunder:NoClipping( true ) + thunder:SetSize(33, 32) + thunder:SetText('') + function tP:PerformLayout(w, h) + thunder:SetPos(w / 2 - 16,0) + end + function thunder:Paint(w,h) + local cW = StormFox2.Weather.GetCurrent() + local hasThunder = cW.Name ~= "Clear" + self._DEPRESSED = StormFox2.Thunder.IsThundering() + self._DISABLED = not hasThunder and not self._DEPRESSED + self:SetTooltip(self._DEPRESSED and 'Выключить грозу' or 'Включить грозу') + DrawButton(self,w,h) + if not self._DISABLED then + surface.SetDrawColor(color_white) + else + surface.SetDrawColor(col_dis) + end + surface.SetMaterial(m_thunder) + surface.DrawTexturedRect(5,5,w - 10,h - 10) + end + function thunder:DoClick() + local cW = StormFox2.Weather.GetCurrent() + local hasThunder = cW.Name ~= "Clear" + local isth = StormFox2.Thunder.IsThundering() + if not isth and not hasThunder then + return + end + SetWeather(SF_THUNDER, not isth) + end + -- Temperature + local t = vgui.Create("DPanel", self) + t:SetTall(30) + t:Dock(TOP) + t:DockMargin(padding,padding_y,padding,0) + local text = 'Температура' + t.text = string.upper(text[1]) .. string.sub(text, 2) + function t:Paint(w,h) + surface.SetFont("SF2.W_Button") + local tw,th = surface.GetTextSize(self.text) + surface.SetTextColor(color_white) + surface.SetTextPos(w / 2 - tw / 2,th / 2 - 2) + surface.DrawText(self.text) + end + local tempslider = SliderNumber(self) + local function Conv( n ) return math.Round(StormFox2.Temperature.Convert(nil,StormFox2.Temperature.GetDisplayType(),n), 1) end + tempslider:DockMargin(padding,0,padding,padding_y) + tempslider:Dock(TOP) + tempslider:SetMin(Conv(-20)) + tempslider:SetMax(Conv(40)) + tempslider:SetTextSize(40) + function tempslider:OnVal( num ) + num = math.Round(StormFox2.Temperature.Convert(StormFox2.Temperature.GetDisplayType(),nil,num), 1) + SetWeather(SF_SETTEMP, num) + end + function tempslider:DrawText( n ) + return n .. StormFox2.Temperature.GetDisplaySymbol() + end + tempslider:SetVal( math.Round(StormFox2.Temperature.GetDisplay(),1) ) + function tempslider:Think() + tempslider._aimval = math.Round(StormFox2.Temperature.GetDisplay(StormFox2.Data.GetFinal( "Temp", 20 )),1) + tempslider:SetVal( math.Round(StormFox2.Temperature.GetDisplay(),1) ) + end + -- Wind Ang + local t = vgui.Create("DPanel", self) + t:DockMargin(padding,padding_y,padding,0) + t:SetTall(30) + t:Dock(TOP) + local text = 'Ветер' + t.text = string.upper(text[1]) .. string.sub(text, 2) + function t:Paint(w,h) + surface.SetFont("SF2.W_Button") + local tw,th = surface.GetTextSize(self.text) + surface.SetTextColor(color_white) + surface.SetTextPos(w / 2 - tw / 2,th / 2 - 2) + surface.DrawText(self.text) + end + local b = vgui.Create("DPanel", self) + function b:Paint() end + b:SetSize(80,80) + b:Dock(TOP) + local w_ang = vgui.Create("DButton", b) + w_ang:SetToolTip('Установить направление ветра по взгляду') + w_ang:SetText("") + function b:PerformLayout(w, h) + w_ang:SetSize(h,h) + w_ang:SetPos(w / 2 - h / 2) + end + function w_ang:Paint( w, h ) + render.PushFilterMag(TEXFILTER.ANISOTROPIC) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + surface.SetDrawColor(col_ba) + surface.SetMaterial(m_cir) + surface.DrawTexturedRect(0,0,w,h) + + local windang = EyeAngles().y - (StormFox2.Wind.GetYaw() or 0) + local wind = StormFox2.Wind.GetForce() or 0 + local t = {{x = w / 2,y = h / 2, u=0.5,v=0.5}} + local l = math.Clamp(wind,0,70) / 3 + if l < 1 then + surface.SetDrawColor(155,255,155) + l = 2 + else + surface.SetDrawColor(155,155,255) + end + local nn = 90 - l * 5 + for i = 0,l - 1 do + local c,s = cos(rad(i * 10 + windang + nn)),sin(rad(i * 10 + windang + nn)) + local x = c * w / 2 + w / 2 + local y = s * h / 2 + h / 2 + table.insert(t,{x = x,y = y, u = (c + 1) / 2, v = (s + 1) / 2}) + end + local c,s = cos(rad(l * 10 + windang + nn)),sin(rad(l * 10 + windang + nn)) + local x = c * w / 2 + w / 2 + local y = s * h / 2 + h / 2 + table.insert(t,{x = x,y = y, u=(c + 1) / 2,v = (s + 1) / 2}) + --draw.NoTexture() + surface.DrawPoly(t) + surface.SetFont("DermaDefault") + local t = 'Угол' + local tw,th = surface.GetTextSize(t) + surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2) + surface.DrawText(t) + render.PopFilterMag() + render.PopFilterMin() + end + function w_ang:DoClick() + SetWeather(SF_SETWIND_A, (EyeAngles().y + 180) % 360) + end + -- Wind + local p = vgui.Create("DPanel", self) + p:SetTall(22) + p:Dock(TOP) + p:DockMargin(padding,padding_y,padding,0) + function p:Paint(w,h) + local f = math.Round(StormFox2.Wind.GetForce() or 0, 1) + local bf,desc = StormFox2.Wind.GetBeaufort(f) + local text = f .."м/с: " .. language.GetPhrase(desc) + surface.SetFont("DermaDefault") + surface.SetTextColor(color_white) + local tw,th = surface.GetTextSize(text) + surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2) + surface.DrawText(text) + end + local windslide = SliderNumber(self) + windslide:SetToolTip('Установить скорость ветра в м/с') + windslide:Dock(TOP) + windslide:DockMargin(padding,0,padding,0) + windslide:SetMin(0) + windslide:SetMax(70) + windslide:SetTextSize(0) + windslide:SetVal( StormFox2.Wind.GetForce() or 0 ) + function windslide:OnVal( num ) + SetWeather(SF_SETWIND_F, num) + end + function windslide:Think2() + windslide._aimval = StormFox2.Data.GetFinal( "Wind", 0 ) + windslide:SetVal( StormFox2.Wind.GetForce() or 0 ) + end + -- Time + local p = vgui.Create("DPanel", self) + p:SetTall(40) + p.Paint = function() end + local t = vgui.Create("SF_TIME", p) + function t:Think() + self:SetValue( StormFox2.Time.Get() ) + end + function t:OnNewValue( var ) + SetWeather( SF_SETTIME, var ) + end + p:Dock(TOP) + local pause = vgui.Create("DButton", p) + pause.state = 1 + pause:SetSize(30, 30) + function p:PerformLayout(w, h) + pause:SetPos(10,10) + t:SetPos( 42 ,10) + t:SetWide( w - 20 - 27 ) + end + local a = StormFox2.Setting.GetObject("day_length") + local b = StormFox2.Setting.GetObject("night_length") + + local r = Material("gui/point.png") + local z = Material("gui/workshop_rocket.png") + function pause:Think() + if StormFox2.Time.IsPaused() then + self.state = 0 -- pause + else + self.state = 1 -- running + end + end + pause:SetText("") + --pause.Paint = DrawButton + --t.bg.Paint = DrawButton + -- + --t.ampm.Paint = DrawButton + --function t.ampm:UpdateColours() + -- self:SetTextStyleColor( color_white ) + --end + --t.hour.color = color_white + --t.min.color = color_white + + local c = Color(0,0,0,225) + function pause:PaintOver(w,h) + local s = 15 + if self.state == 0 then + surface.SetMaterial(r) + surface.SetDrawColor(c) + surface.DrawTexturedRectRotated(w / 2 + 2,h / 2,w - s,h - s, 90) + else + surface.SetMaterial(z) + surface.SetDrawColor(c) + surface.DrawTexturedRectRotated(w / 2 - 5,h / 2,w - s * 1.1,h, 0) + surface.DrawTexturedRectRotated(w / 2 + 5,h / 2,w - s * 1.1,h, 0) + end + end + function pause:DoClick() + SetWeather(SF_SETTIME_S, 0) + end + pause:SetPos(20 ,10) +end + +-- Caht status +local openChat = false +hook.Add("StartChat","StormFox2.Controller.Disable",function() + openChat = true +end) +hook.Add("FinishChat","StormFox2.Controller.Enable",function() + openChat = false +end) + +local mat = Material("gui/workshop_rocket.png") +local c = Color(55,55,55) +function StormFox2.Menu._OpenController() + if _SF_CONTROLLER then + _SF_CONTROLLER:Remove() + end + if spawnmenu and spawnmenu.SetActiveControlPanel then + spawnmenu.SetActiveControlPanel(nil) + end + local p = vgui.Create("DFrame") + + if not p then return end + _SF_CONTROLLER = p + Init(p) + local settings = vgui.Create("DButton", p) + settings:SetSize(31, 24) + settings:SetPos(p:GetWide() - 31 * 2 - 4) + settings:SetIcon('icon16/cog_edit.png') + settings:SetText("") + settings:SetToolTip("Серверные настройки") + function settings:DoClick() + surface.PlaySound("buttons/button14.wav") + RunConsoleCommand("stormfox2_svmenu") + end + function settings:Paint() end + function p:PaintOver(w,h) + if self.enabled then return end + local x,y = 0, h / 2 + surface.SetMaterial(mat) + surface.SetDrawColor(HSLToColor(240, 0.3,0.5 + sin(CurTime() * 1.5) / 10)) + surface.DrawTexturedRectUV(0,h * 0.4,w,h * 0.2,0.2,-0.2,0.8,1) + draw.DrawText('Зажми C', "SF2.W_Button", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER) + end + function p:Think() + local x,y = self:LocalCursorPos(0,0) + local inside = x > 0 and x < self:GetWide() and y > 0 and y < self:GetTall() + if not self.enabled and input.IsKeyDown(KEY_C) and not openChat and not gui.IsConsoleVisible() then + self.enabled = true + self.btnClose:SetDisabled( false ) + self:MakePopup() + self:SetSelected() + elseif self.enabled then + if input.IsKeyDown(KEY_C) then return end -- If KEY is down, don't disable + if self:HasHierarchicalFocus() and not self:HasFocus() then return end -- Typing in something. Don't disable. + if inside then return end -- Mouse is inside controller. Don't disable yet. + self.enabled = false + self.btnClose:SetDisabled( true ) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + end + end + return _SF_CONTROLLER +end + +function StormFox2.Menu.OpenController() + net.Start("StormFox2.menu") + net.WriteBool(false) + net.SendToServer() +end + +function StormFox2.Menu.CloseController() + if _SF_CONTROLLER then + _SF_CONTROLLER:Remove() + end +end + +concommand.Add('stormfox2_controller', StormFox2.Menu.OpenController, nil, "Opens SF controller menu") \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_fog.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_fog.lua new file mode 100644 index 0000000..b129097 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_fog.lua @@ -0,0 +1,256 @@ +--[[------------------------------------------------------------------------- +Use the map-data to set a minimum and maximum fogdistance +---------------------------------------------------------------------------]] +StormFox2.Setting.AddSV("enable_svfog",true,nil, "Effects") +if CLIENT then StormFox2.Setting.AddCL("enable_fog",true, "sf_enable_fog") end +StormFox2.Setting.AddSV("enable_fogz",false,nil, "Effects") +StormFox2.Setting.AddSV("overwrite_fogdistance",-1,nil, "Effects", -1, 800000) +StormFox2.Setting.SetType("overwrite_fogdistance","special_float") +StormFox2.Setting.AddSV("allow_fog_change",engine.ActiveGamemode() == "sandbox",nil, "Effects") + +StormFox2.Fog = {} +-- Local functions + local function fogEnabledCheck() + if not StormFox2.Setting.SFEnabled() then return false end + if not StormFox2.Setting.GetCache("enable_svfog", true) then return false end + if not StormFox2.Setting.GetCache("allow_fog_change", false) then return true end + return StormFox2.Setting.GetCache("enable_fog", true) + end + local _fS, _fE, _fD = 0,400000,1 + local function fogStart( f ) + _fS = f + end + local function fogEnd( f ) + _fE = f + end + local function fogDensity( f ) + _fD = f + end + local function getFogFill() + if _fS >= 0 then return 0 end + return -_fS / (_fE - _fS) * _fD * 0.1 + end + -- Makes it so fog isn't linear + local e = 2.71828 + local function fogCalc(b, a, p) + if a == b then return a end + p = e^(-8.40871*p) + local d = b - a + return a + d * p + end +-- Returns the start of fog. +function StormFox2.Fog.GetStart() + return math.max(0, _fS) +end +-- Returns the end of fog. +function StormFox2.Fog.GetEnd() + return _fE +end +-- Locate / Calculate the default fog-data + local map_distance, map_farZ = -1, -1 + local tab = StormFox2.Map.FindClass("env_fog_controller") + if #tab < 1 then + map_distance = 400000 + else + -- Set a minimum + map_distance = 6000 + for _, data in ipairs(tab) do + map_farZ = math.max(map_farZ, data.farz) + -- Calculate fog-brightness. We can use this to scale the map-distance up to match the fog. + local col_brightness = 1 + local density = (data.fogmaxdensity or 1) + if data.fogcolor then + local fcol = string.Explode(" ", data.fogcolor) + col_brightness = (0.2126 * fcol[1] + 0.7152 * fcol[2] + 0.0722 * fcol[3]) / 255 + end + density = density * col_brightness + map_distance = math.max(((data.fogend or 6000) / density), map_distance) + end + -- It is important we don't overshoot farZ + if map_farZ > 0 then + map_distance = math.min(map_distance, map_farZ) + end + end +-- Returns the fog-amount. 0 - 1 +function StormFox2.Fog.GetAmount() + return 1 - _fE / map_distance +end +-- Returns the fog-distance ( Same as StormFox2.Fog.GetEnd() ) +function StormFox2.Fog.GetDistance() + return _fE or map_distance +end + +-- Returns the default fog-distance for clear weather. + local function getDefaultDistance() + local ov = StormFox2.Setting.GetCache("overwrite_fogdistance",-1) + if ov > -1 then + return ov + end + return map_distance + end +-- Returns the fog-distance. + local function getAimDistance(bFinal) + local cW = StormFox2.Weather.GetCurrent() + if cW.Name == "Clear" then + return getDefaultDistance() + end + local perc = bFinal and StormFox2.Weather.GetFinishPercent() or StormFox2.Weather.GetPercent() + local a = cW:Get('fogDistance') + if not a or perc <= 0 then return getDefaultDistance() end + if perc >= 1 then return a end + return fogCalc(getDefaultDistance(), a, perc) + end + +if SERVER then + local loaded, data, f_FogZ = true + -- Sets the fogZ distance. + function StormFox2.Fog.SetZ(num, nTimer) + timer.Remove( "sf_fog_timer" ) + if nTimer then + timer.Create("sf_fog_timer", nTimer, 1, function() + StormFox2.Fog.SetZ(num) + end) + return + end + f_FogZ = num + if not loaded then + data = num + return + end + if not num then num = map_farZ end + for k,v in ipairs( StormFox2.Ent.env_fog_controllers or {} ) do + if not IsValid(v) then continue end + v:SetKeyValue("farz", num) + end + end + -- Returns the fogz distance. + function StormFox2.Fog.GetZ() + if not StormFox2.Setting.Get("enable_fogz", false) then return map_farZ end + return f_FogZ or (StormFox2.Fog.GetDistance() + 100) + end + hook.Add("StormFox2.PostEntityScan", "StormFox2.Fog.Initz", function() + loaded = true + if data then + StormFox2.Fog.SetZ(data) + data = nil + end + end) + hook.Add("StormFox2.weather.postchange", "StormFox2.Fog.Updater", function( sName ,nPercentage, nDelta ) + local old_fE = _fE or map_distance + _fE = getAimDistance(true) + if _fE > 3000 then + _fS = 0 + else + _fS = _fE - 3000 + end + -- Check fogZ distance + if not StormFox2.Setting.Get("enable_fogz", false) then return end + if old_fE > _fE then -- The fog shriks + StormFox2.Fog.SetZ(_fE * 2 + 100, nDelta) + elseif old_fE < _fE then -- The fog grows + StormFox2.Fog.SetZ(_fE * 2 + 100) + end + end) + timer.Create("StormFox2.Fog.SVUpdate", 2, 0, function() + local cWD = StormFox2.Weather.GetCurrent().Dynamic or {} + if cWD.fogDistance then return end + _fE = getAimDistance(true) + end) + -- Returns the fog-color. + function StormFox2.Fog.GetColor() + return StormFox2.Mixer.Get("fogColor", StormFox2.Mixer.Get("bottomColor",color_white) ) or color_white + end + return +end +----- Fog render and clientside ----- +-- Fog logic and default render + -- Returns the "distance" to outside + local f_outside = 0 + local f_indoor = -1 + local f_lastDist = map_distance + + local function outSideVar() + local env = StormFox2.Environment.Get() + if env.outside then + return f_outside + end + if not env.nearest_outside then + return f_indoor + end + local dis = StormFox2.util.RenderPos():Distance(env.nearest_outside) / 300 + if dis > 1 then + return f_indoor + end + return dis + end + hook.Add("Think", "StormFox2.Fog.Updater", function() + -- Figure out the fogdistance we should have + local f_envfar = outSideVar() + local fog_dis = getAimDistance() + local fog_indoor = StormFox2.Mixer.Get("fogIndoorDistance",3000) + if f_envfar == f_indoor then -- Indoors + fog_dis = math.max(fog_dis, fog_indoor) + elseif f_envfar ~= f_outside then + fog_dis = Lerp(f_envfar + 0.1, fog_dis, fog_indoor) + end + _fE = math.Approach(_fE, fog_dis, math.max(10, _fE) * FrameTime()) + if _fE > 3000 then + _fS = 0 + else + _fS = _fE - 3000 + end + end) + + local f_Col = color_white + local SkyFog = function(scale) + if _fD <= 0 then return end + if not scale then scale = 1 end + if not fogEnabledCheck() then return end + f_Col = StormFox2.Mixer.Get("fogColor", StormFox2.Mixer.Get("bottomColor") ) + -- Apply fog + local tD = StormFox2.Thunder.GetLight() / 2055 + render.FogMode( 1 ) + render.FogStart( StormFox2.Fog.GetStart() * scale ) + render.FogEnd( StormFox2.Fog.GetEnd() * scale ) + render.FogMaxDensity( (_fD - tD) * 0.999 ) + render.FogColor( f_Col.r,f_Col.g,f_Col.b ) + return true + end + hook.Add("SetupSkyboxFog","StormFox2.Sky.Fog",SkyFog) + hook.Add("SetupWorldFog","StormFox2.Sky.WorldFog",SkyFog) + -- Returns the fog-color. + function StormFox2.Fog.GetColor() + return f_Col or color_white + end + +-- Additional Fog render +local m_fog = Material('stormfox2/effects/fog_sphere') +local l_fogz = 0 +hook.Add("StormFox2.2DSkybox.FogLayer", "StormFox2.Fog.RSky", function( viewPos ) + if not fogEnabledCheck() then return end + local v = Vector(math.cos( viewPos.y ), math.sin( viewPos.y ), 0) + m_fog:SetVector("$color", Vector(f_Col.r,f_Col.g,f_Col.b) / 255) + m_fog:SetFloat("$alpha", math.Clamp(5000 / _fE, 0, 1)) + render.SetMaterial( m_fog ) + local tH = math.min(StormFox2.Environment.GetZHeight(), 2100) + if tH ~= l_fogz then + local delta = math.abs(l_fogz, tH) / 2 + l_fogz = math.Approach( l_fogz, tH, math.max(delta, 10) * 5 * FrameTime() ) + end + local h = 2000 + 6000 * StormFox2.Fog.GetAmount() + render.DrawSphere( Vector(0,0,h - l_fogz * 4) , -30000, 30, 30, color_white) +end) + +local mat = Material("color") +local v1 = Vector(1,1,1) +hook.Add("PostDrawOpaqueRenderables", "StormFox2.Sky.FogPDE", function() + if _fS >= 0 or _fD <= 0 then return end + if not fogEnabledCheck() then return end + local a = getFogFill() + mat:SetVector("$color",Vector(f_Col.r / 255,f_Col.g / 255,f_Col.b / 255)) + mat:SetFloat("$alpha",a) + render.SetMaterial(mat) + render.DrawScreenQuad() + mat:SetVector("$color",v1) + mat:SetFloat("$alpha",1) +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_footprints.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_footprints.lua new file mode 100644 index 0000000..ca72c23 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_footprints.lua @@ -0,0 +1,285 @@ +--[[------------------------------------------------------------------------- +Footsteps and logic. +- Overrides default footstepsounds with terrain-sounds +---------------------------------------------------------------------------]] +local NetL = {"npc_zombie", "npc_poisonzombie", "npc_vortigaunt", "npc_antlion", "npc_fastzombie"} -- These entites only play sounds serverside and needs to be networked. +local BL = {"npc_hunter"} -- Tehse entities should not get their sound replaced +local find = string.find +local bAlwaysFootstep = false -- This is set to true on cold maps +local defaultSnowName = "snow.step" +local defaultSnowSnd = { + "stormfox/footstep/footstep_snow0.ogg", + "stormfox/footstep/footstep_snow1.ogg", + "stormfox/footstep/footstep_snow2.ogg", + "stormfox/footstep/footstep_snow3.ogg", + "stormfox/footstep/footstep_snow4.ogg", + "stormfox/footstep/footstep_snow5.ogg", + "stormfox/footstep/footstep_snow6.ogg", + "stormfox/footstep/footstep_snow7.ogg", + "stormfox/footstep/footstep_snow8.ogg", + "stormfox/footstep/footstep_snow9.ogg" +} + +if SERVER then + util.AddNetworkString("StormFox2.feetfix") +end + +-- We use this to cache the last foot for the players. + local lastFoot = {} + hook.Add("PlayerFootstep", "StormFox2.lastfootprint", function(ply, pos, foot, sound, volume, filter, ...) + lastFoot[ply] = foot + end) +-- Local functions + --local noSpam = {} + local cache = {} + -- Returns the foot from sounddata + local function GetFootstep(tab) + local ent = tab.Entity + if not ent or not IsValid(ent) then return end + if not ent:IsPlayer() and not ent:IsNPC() and not ent:IsNextBot() then return end + --if (noSpam[ent] or 0) > CurTime() then return end + -- Check to see if it is a footstep + local OriginalSnd = tab.OriginalSoundName:lower() + local foot = -1 + if cache[OriginalSnd] then + foot = cache[OriginalSnd] + elseif string.match(OriginalSnd, "npc_antlionguard.farstep") or string.match(OriginalSnd, "npc_antlionguard.nearstep") then + foot = lastFoot[ent] or -1 + elseif find(OriginalSnd, "stepleft",1,true) or find(OriginalSnd, "gallopleft",1,true) then + foot = 0 + cache[OriginalSnd] = 0 + elseif find(OriginalSnd, "stepright",1,true) or find(OriginalSnd, "gallopright",1,true) then + foot = 1 + cache[OriginalSnd] = 1 + elseif find(OriginalSnd, ".footstep",1,true) or find(tab.SoundName:lower(),"^player/footsteps",1) then + foot = lastFoot[ent] or -1 + else -- Invalid + return + end + -- No footstep spam + --noSpam[ent] = CurTime() + 0.01 + return foot + end + -- TraceHull for the given entity + local function EntTraceTexture(ent,pos) -- Returns the texture the entity is "on" + local mt = ent:GetMoveType() + if mt < 2 or mt > 3 then return end -- Not walking. + local filter = ent + if ent.GetViewEntity then + filter = ent:GetViewEntity() + end + local t = util.TraceHull( { + start = pos + Vector(0,0,30), + endpos = pos + Vector(0,0,-60), + maxs = ent:OBBMaxs(), + mins = ent:OBBMins(), + collisiongroup = ent:GetCollisionGroup(), + filter = filter + } ) + if not t.Hit then return end -- flying + if t.Entity and IsValid(t.Entity) and t.HitNonWorld and t.HitTexture == "**studio**" then + return + end + return t.HitTexture + end + -- Returns true if the entity is on replaced texture. + local function IsOnReplacedTex(ent,snd,pos) + local sTexture = EntTraceTexture(ent,pos) + if not sTexture then return false,"nil" end + local mat = Material(sTexture) + if not mat then return false, sTexture end + if mat:IsError() and (ent:IsNPC() or string.find(snd,"grass") or string.find(snd,"dirt")) then -- Used by maps + return true, sTexture + end + if StormFox2.Terrain.HasMaterialChanged(mat) then return true, sTexture end + return false,sTexture + end +-- Footstep overwrite and logic + hook.Add("EntityEmitSound", "StormFox2.footstep.detecter", function(data) + if not StormFox2.Terrain then return end + local cT = StormFox2.Terrain.GetCurrent() + if not cT then return end + -- Only enable if we edit or need footsteps. + if not (bAlwaysFootstep or (cT and cT.footstepLisen)) then return end + -- Check if the server has disabled the footprint logic on their side. + if SERVER and not game.SinglePlayer() and not StormFox2.Setting.GetCache("footprint_enablelogic",true) then return end + -- Check if it is a footstep sound of some sort. + local foot = GetFootstep(data) -- Returns [-1 = invalid, 0 = left, 1 = right] + if not foot then return end + -- Checks to see if the texturem the entity stands on, have been replaced. + local bReplace, sTex = IsOnReplacedTex(data.Entity,data.SoundName:lower(),data.Pos or data.Entity:GetPos()) + -- Overwrite the sound if needed. + local changed + if bReplace and cT.footprintSnds then + if cT.footprintSnds[2] then + data.OriginalSoundName = cT.footprintSnds[2] .. (foot == 0 and "left" or "right") + end + if not cT.footprintSnds[1] then + data.SoundName = "ambient/_period.wav" + else + data.SoundName = table.Random(cT.footprintSnds[1]) + data.OriginalSoundName = data.SoundName + end + changed = true + end + -- Call footstep hook + hook.Run("StormFox2.terrain.footstep", data.Entity, foot, data.SoundName, sTex, bReplace ) + -- Singleplayer and server-sounds fix + if SERVER and (game.SinglePlayer() or table.HasValue(NetL, data.Entity:GetClass())) then + net.Start("StormFox2.feetfix",true) + net.WriteEntity(data.Entity) + net.WriteInt(foot or 1,2) + net.WriteString(data.SoundName) + net.WriteString(sTex) + net.WriteBool(bReplace) + net.Broadcast() + end + -- Call terrain function + if cT.footstepFunc then + cT.footstepFunc(data.Entity, foot, data.SoundName, sTex, bReplace) + end + return changed + end) + -- Singleplayer and entity fix + if CLIENT then + net.Receive("StormFox2.feetfix",function() + local cT = StormFox2.Terrain.GetCurrent() + if not cT then return end + local ent = net.ReadEntity() + if not IsValid(ent) then return end + local foot = net.ReadInt(2) + local sndName = net.ReadString() + local sTex = net.ReadString() + local bReplace = net.ReadBool() + if cT.footstepFunc then + cT.footstepFunc(ent, foot, sndName, sTex, bReplace) + end + hook.Run("StormFox2.terrain.footstep", ent, foot, sndName, sTex, bReplace) + end) + end +--[[------------------------------------------------------------------------- +Footprint render +---------------------------------------------------------------------------]] +if CLIENT then + local sin,cos,rad,clamp,ceil,min = math.sin,math.cos,math.rad,math.Clamp,math.ceil,math.min + local prints = {} + local footstep_maxlife = 30 + local function ET(pos,pos2,mask,filter) + local t = util.TraceLine( { + start = pos, + endpos = pos + pos2, + mask = mask, + filter = filter + } ) + if not t then -- tracer failed, this should not happen. Create a fake result. + local t = {} + t.HitPos = pos + pos2 + return t + end + t.HitPos = t.HitPos or (pos + pos2) + return t + end + local function AddPrint(ent,foot) + -- Foot calc + local velspeed = ent:GetVelocity():Length() + local y = rad(ent:GetAngles().y) + local fy = y + rad((foot * 2 - 1) * -90) + local l = 5 * ent:GetModelScale() + local ex = Vector(cos(fy) * l + cos(y) * l,sin(fy) * l + sin(y) * l,0) + local pos = ent:GetPos() + ex + -- Find impact + local tr = ET(pos + Vector(0,0,20),Vector(0,0,-40),MASK_SOLID_BRUSHONLY,ent) + if not tr.Hit then return end -- In space? + -- If no bone_angle then angle math + local normal = -tr.HitNormal + -- CalcAng + local yawoff + if ent:IsPlayer() then + yawoff = normal:Angle().y - ent:EyeAngles().y + 180 + else + yawoff = normal:Angle().y - ent:GetAngles().y + 180 + end + table.insert(prints,{tr.HitPos, normal,foot,ent:GetModelScale() or 1,CurTime() + footstep_maxlife,clamp(velspeed / 300,1,2),yawoff}) + -- pos, normal,foot,scale, life, lengh, yawoff + end + -- Footprint logic + local BL = {"npc_hunter","monster_bigmomma","npc_vortigaunt","npc_dog","npc_fastzombie","npc_stalker"} -- Blacklist footprints + local function CanPrint(ent) + local c = ent:GetClass() + for i,v in ipairs(BL) do + if find(c, v,1,true) then return false end + end + if find(ent:GetModel(),"_torso",1,true) then return false end + return true + end + hook.Add("StormFox2.terrain.footstep", "StormFox2.terrain.makefootprint", function(ent, foot, sSnd, sTexture, bReplace ) + if foot < 0 then return end -- Invalid foot + if not bReplace and bAlwaysFootstep then -- This is a cold map, check for snow + if not find(sTexture:lower(),"snow",1,true) then return end + elseif bReplace then -- This is terrain + local cT = StormFox2.Terrain.GetCurrent() + if not cT then return end + if not cT.footprints then return end + else -- Invalid + return + end + if not CanPrint(ent) then return end + if not StormFox2.Setting.GetCache("footprint_enabled",true) then return end + if StormFox2.Setting.GetCache("footprint_playeronly",false) and not ent:IsPlayer() then return end + local n_max = StormFox2.Setting.GetCache("footprint_max",200) + if #prints > n_max then + table.remove(prints, 1) + end + AddPrint(ent,foot) + end) + -- Footprint render + local mat = {Material("stormfox2/effects/foot_hq.png"),Material("stormfox2/effects/foot_hql.png"),Material("stormfox2/effects/foot_m.png"),Material("stormfox2/effects/foot_s.png")} + local function getMat(q,foot) + if q == 1 then + if foot == 0 then + return mat[2] + else + return mat[1] + end + end + return mat[q + 1] + end + local DrawQuadEasy = render.DrawQuadEasy + local bC = Color(0,0,0,255) + hook.Add("PreDrawOpaqueRenderables","StormFox2.Terrain.Footprints",function() + if not StormFox2.Setting.GetCache("footprint_enabled",true) then return end + if #prints < 1 then return end + local lp = StormFox2.util.RenderPos() + local del = {} + local footstep_dis = StormFox2.Setting.GetCache("footprint_distance",2000,"The renderdistance for footprints") ^ 2 + for k,v in pairs(prints) do + local pos,normal,foot,scale,life,lengh,yawoff = v[1],v[2],v[3],v[4],v[5],v[6],v[7] + local blend = life - CurTime() + if blend <= 0 then + table.insert(del,k) + else + local q = min(ceil(lp:DistToSqr(pos) / footstep_dis),4) + if q >= 4 then continue end + render.SetMaterial(getMat(q,foot)) + if foot == 0 and q > 1 then + DrawQuadEasy( pos + Vector(0,0,q / 3 + 1), normal, 6 * scale, 10 * scale * lengh, bC, yawoff ) + else + DrawQuadEasy( pos + Vector(0,0,q / 3), normal, -6 * scale, 10 * scale * lengh, bC, yawoff ) + end + end + end + for i = #del,1,-1 do + table.remove(prints,del[i]) + end + end) +end + +-- If the map is cold or has snow, always check for footsteps. + bAlwaysFootstep = StormFox2.Map.IsCold() or StormFox2.Map.HasSnow() -- This is a cold map. + if CLIENT then + StormFox2.Setting.AddCL("footprint_enabled",true) -- Add footprint setting + StormFox2.Setting.AddCL("footprint_max",200) -- Add footprint setting + StormFox2.Setting.AddCL("footprint_distance",2000) -- Add footprint setting + StormFox2.Setting.AddCL("footprint_playeronly",false) -- Add footprint setting + end + StormFox2.Setting.AddSV("footprint_enablelogic",true) diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_tonemapcontroller.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_tonemapcontroller.lua new file mode 100644 index 0000000..67bd20a --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_tonemapcontroller.lua @@ -0,0 +1,98 @@ + +StormFox2.Setting.AddSV("edit_tonemap", false, nil, "Effects") + +if CLIENT then return end +StormFox2.ToneMap = {} +--SetBloomScale +-- On load data +local function LoadSettings() + -- Locate tonemap + local t = StormFox2.Map.FindClass("env_tonemap_controller") + if #t < 1 then return end -- Unable to locate tonemap within BSP + local targetname = t[1].targetname + if not targetname then return end -- This tonemap can't have any settings + -- Search for logic_auto + local tab = {} + for k, v in ipairs( StormFox2.Map.FindClass("logic_auto") ) do + if not v.onmapspawn then continue end -- No setting? + if not string.match(v.onmapspawn, "^" .. targetname .. ",") then continue end -- This targets tonemap. + for s in string.gmatch(v.raw, '"OnMapSpawn"%s?"' .. targetname .. ',(.-)"') do + local t = string.Explode(",", s) + tab[t[1]] = t[2] + end + end + return tab +end + +local DefaultSettings = LoadSettings() +local ent +local changed = false +hook.Add("StormFox2.PostEntityScan", "StormFox2.ToneMapFind", function() + ent = StormFox2.Ent.env_tonemap_controller and StormFox2.Ent.env_tonemap_controller[1] +end) + +do + local last = 1 + function StormFox2.ToneMap.SetBloomScale( num ) + if not ent or not DefaultSettings or not DefaultSettings.SetBloomScale then return end + if last == num then return end + ent:Fire("SetBloomScale",DefaultSettings.SetBloomScale * num) + changed = true + last = num + end +end + +do + local last = 1 + function StormFox2.ToneMap.SetExposureScale( num ) + if not ent or not DefaultSettings then return end + if last == num then return end + ent:Fire("SetAutoExposureMax",(DefaultSettings.SetAutoExposureMax or 1) * num) + ent:Fire("SetAutoExposureMin",(DefaultSettings.SetAutoExposureMin or 0) * num) + changed = true + last = num + end +end + +do + local last = 1 + function StormFox2.ToneMap.SetTonemapRateScale( num ) + if not ent or not DefaultSettings then return end + if last == num then return end + ent:Fire("SetTonemapRate",(DefaultSettings.SetTonemapRate or 0.1) * num) + changed = true + last = num + end +end + +function StormFox2.ToneMap.Reset() + if not changed or not ent then return end + changed = false + StormFox2.ToneMap.SetBloomScale( 1 ) + StormFox2.ToneMap.SetExposureScale( 1 ) + StormFox2.ToneMap.SetTonemapRateScale( 1 ) +end + +local function getMaxLight() + local c = StormFox2.Weather.Get("Clear") + return c:Get("mapDayLight",80) +end + +local function ToneMapUpdate( lightlvlraw ) + if not StormFox2.Setting.SFEnabled() or not StormFox2.Setting.GetCache("edit_tonemap", true) then + StormFox2.ToneMap.Reset() + else + StormFox2.ToneMap.SetExposureScale( lightlvlraw / 100 ) + end +end + +local last_Raw = 100 +-- Toggle tonemap with setting +StormFox2.Setting.Callback("edit_tonemap",function() + ToneMapUpdate(last_Raw) +end,"sf_edit_tonemap") +-- Save the last raw-lightlvl and update the tonemap +hook.Add("StormFox2.lightsystem.new", "StormFox2.ToneMap-Controller", function(lightlvl, lightlvl_raw) + last_Raw = lightlvl_raw + ToneMapUpdate(lightlvl_raw) +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_tweaks.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_tweaks.lua new file mode 100644 index 0000000..35af6e3 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_tweaks.lua @@ -0,0 +1,192 @@ + +-- Delete old skybox brushes +if SERVER then + hook.Add( "InitPostEntity", "DeleteBrushNEntity", function() + for i, ent in ipairs( ents.GetAll() ) do + if not IsValid(ent) then continue end + if ent:GetClass() == "func_brush" and (ent:GetName() or "") == "daynight_brush" then + SafeRemoveEntity(ent) + elseif ent:CreatedByMap() and (ent:GetModel() or "") == "models/props/de_port/clouds.mdl" then + ent:SetNoDraw( true ) + end + end + end ) +end + +-- Foliage overwrite +StormFox2.Setting.AddSV("override_foliagesway",true,nil, "Effects") +if StormFox2.Setting.Get("override_foliagesway", true) and CLIENT then + --[[ + Foliage_type: + -2 - No treesway + -1 - Tree trunk + 0 - Tree / w branches andor leaves + 1 - Branches / Leaves + 2 - Ground Plant + Bendyness multiplier: + 1 - default + mat_height: + 0 - height + WaveBonus_speed: + + ]] + + local default_foliage = {} + default_foliage["models/msc/e_leaves"] = {1} + default_foliage["models/msc/e_leaves2"] = {1} + default_foliage["models/msc/e_bark3"] = {-1} + + default_foliage["models/trees/japanese_tree_bark_02"] = {-1, 0.5} + default_foliage["models/trees/japanese_tree_round_02"] = {1} + default_foliage["models/trees/japanese_tree_round_03"] = {1} + default_foliage["models/trees/japanese_tree_round_05"] = {1} + + default_foliage["models/props_foliage/tree_deciduous_01a_leaves2"] = {1} + default_foliage["models/msc/e_bigbush3"] = {2,4} + default_foliage["models/props_coalmine/foliage1"] = {2} + default_foliage["models/props_foliage/mall_trees_branches03"] = {2} + default_foliage["models/props_foliage/tree_deciduous_01a_branches"] = {2} + default_foliage["models/props_foliage/bramble01a"] = {2,0.4} + default_foliage["models/props_foliage/leaves_bushes"] = {2} + default_foliage["models/props_foliage/leaves"] = {2} + default_foliage["models/props_foliage/cane_field01"] = {2,nil,0.3} + --default_foliage["models/props_foliage/cattails"] = {2} Not working + --default_foliage["models/props_foliage/trees_farm01"] = {-1,0.8,0.02,1.5} Doesn't look good on some trees + default_foliage["models/props_foliage/cedar01_mip0"] = {0,0.4,0.02,3} + default_foliage["models/props_foliage/coldstream_cedar_bark"] = {-1} + default_foliage["models/props_foliage/coldstream_cedar_branches"] = {0} + default_foliage["models/props_foliage/urban_trees_branches03"] = {0} + default_foliage["models/props_foliage/bush"] = {2} + default_foliage["models/props_foliage/corn_plant01"] = {1,3.4} + default_foliage["models/props_foliage/detail_clusters"] = {2} + default_foliage["models/cliffs/ferns01"] = {0,2,nil,2} + default_foliage["models/props_foliage/rocks_vegetation"] = {0,4,nil,1,2} + default_foliage["models/props_foliage/flower_barrel"] = {0,3,0.07,2} + default_foliage["models/props_foliage/flower_barrel_dead"] = {0,1,0.07,2} + default_foliage["models/props_foliage/flower_barrel_dead"] = {0,1,0.07,2} + default_foliage["models/props/de_inferno/flower_barrel"] = {0,3,0.02,2} + default_foliage["models/props_foliage/grass_01"] = {2,0.5} + default_foliage["models/props_foliage/grass_02"] = {2,0.5} + default_foliage["models/props_foliage/grass_clusters"] = {2} + default_foliage["models/props_foliage/urban_trees_branches02_mip0"] = {-1} + default_foliage["models/props_foliage/hedge_128"] = {2,0.8} + default_foliage["models/props_foliage/foliage1"] = {2} + default_foliage["models/props_foliage/hr_f/hr_medium_tree_color"] = {-1} + default_foliage["models/props_foliage/ivy01"] = {2,0.1} + default_foliage["models/props_foliage/mall_trees_branches01"] = {0,1,nil,2} + default_foliage["models/props_foliage/mall_trees_barks01"] = {-1,1,nil,4} + default_foliage["models/props_foliage/mall_trees_branches02"] = {-1,1,nil,4} + --default_foliage["models/props_foliage/oak_tree01"] = {} + default_foliage["models/props_foliage/potted_plants"] = {0,4,0.055} + default_foliage["models/props_foliage/shrub_03"] = {2} + default_foliage["models/props_foliage/shrub_03_skin2"] = {2} + default_foliage["models/props_foliage/swamp_vegetation01"] = {-1,0.005,0.2} + default_foliage["models/props_foliage/swamp_branches"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_trees_branches01_large"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_trees_barks_large"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_trees_barks"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_trees_branches01"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_trees_barks_still"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_trees_barks_generic"] = {0,0.005,0.2,10} + default_foliage["models/props_foliage/swamp_shrubwall01"] = {2} + default_foliage["models/props_foliage/swamp_trees_branches01_alphatest"] = {0,0.05} + default_foliage["models/props_foliage/swamp_trees_branches01_still"] = {0,0.05} + default_foliage["models/props_foliage/branch_city"] = {-1} + default_foliage["models/props_foliage/arbre01"] = {-1,0.4,0.04,2} + default_foliage["models/props_foliage/arbre01_b"] = {-1,0.05,nil,2} + default_foliage["models/props_foliage/tree_deciduous_01a-lod.mdl"] = {} + default_foliage["models/props_foliage/tree_deciduous_01a_lod"] = {-1} + default_foliage["models/props_foliage/tree_pine_01_branches"] = {-2} -- Looks bad. Remove. + default_foliage["models/props_foliage/pine_tree_large"] = {-1,0.8} + default_foliage["models/props_foliage/pine_tree_large_snow"] = {-1,0.8} + default_foliage["models/props_foliage/branches_farm01"] = {-1,0.2,0.8} + default_foliage["models/props_foliage/urban_trees_branches03_small"] = {2,0.8} + default_foliage["models/props_foliage/urban_trees_barks01_medium"] = {-1} + default_foliage["models/props_foliage/urban_trees_branches03_medium"] = {0,2} + default_foliage["models/props_foliage/urban_trees_barks01_medium"] = {-1,2,0.2} + default_foliage["models/props_foliage/urban_trees_branches02_small"] = {2} + default_foliage["models/props_foliage/urban_trees_barks01_clusters"] = {-1,0.2,0.2} + default_foliage["models/props_foliage/urban_trees_branches01_clusters"] = {0,0.2,0.2} + default_foliage["models/props_foliage/urban_trees_barks01"] = {-1,0.2} + default_foliage["models/props_foliage/urban_trees_barks01_dry"] = {2,nil,10} + default_foliage["models/props_foliage/leaves_large_vines"] = {0} + default_foliage["models/props_foliage/vines01"] = {2,0.3} + default_foliage["models/map_detail/foliage/foliage_01"] = {2,0.5} + default_foliage["models/map_detail/foliage/detailsprites_01"] = {2} + default_foliage["models/nita/ph_resortmadness/pg_jungle_plant"] = {0,1.2} + default_foliage["models/nita/ph_resortmadness/plant_03"] = {-1,0.3} + default_foliage["models/nita/ph_resortmadness/leaf_8"] = {0,2} + default_foliage["models/nita/ph_resortmadness/fern_2"] = {0,2} + default_foliage["models/nita/ph_resortmadness/tx_plant_02"] = {0,4,nil,4} + default_foliage["models/nita/ph_resortmadness/tx_plant_04"] = {0,4,nil,4} + default_foliage["models/nita/ph_resortmadness/orchid"] = {0,4,nil,4} + default_foliage["models/props_foliage/ah_foliage_sheet001"] = {2,0.4} + default_foliage["models/props_foliage/ah_apple_bark001"] = {2,0.4} + + default_foliage["statua/nature/furcard1"] = {2,0.1} + default_foliage["models/statua/shared/furcard1"] = {2,0.1} + local max = math.max + local function SetFoliageData(texture,foliage_type,bendyness,mat_height,wave_speed) + if not texture then return end + if not wave_speed then wave_speed = 0 end + if not bendyness then bendyness = 1 end + if not mat_height then mat_height = 0 end + local mat = Material(texture) + + if mat:IsError() then return end -- This client don't know what the material this is + -- Enable / Disable the material + if foliage_type < -1 then + mat:SetInt("$treeSway",0) + return + end + mat:SetInt("$treeSway",1) -- 0 is no sway, 1 is classic tree sway, 2 is an alternate, radial tree sway effect. + -- 'Default' settings + mat:SetFloat("$treeswayspeed",2) -- The treesway speed + mat:SetFloat("$treeswayspeedlerpstart",1000) -- Sway starttime + -- Default varables I don't know what do or doesn't have much to do with cl_tree_sway_dir + mat:SetFloat("$treeswayscrumblefalloffexp",3) + mat:SetFloat("$treeswayspeedhighwindmultiplier",0.2) + mat:SetFloat("$treeswaystartradius",0) + mat:SetFloat("$treeswayscrumblefrequency",6.6) + mat:SetFloat("$treeswayspeedlerpend",2500 * bendyness) + -- Special varables + if foliage_type == -1 then --Trunk + mat:SetFloat("$treeSwayStartHeight",mat_height) -- When it starts to sway + mat:SetFloat("$treeswayheight",max(700 - bendyness * 100,0)) -- << How far up before XY starts to matter + mat:SetFloat("$treeswayradius",max(110 - bendyness * 10,0)) -- ? + mat:SetFloat("$treeswayscrumblespeed",3 + (wave_speed or 0)) -- ? + mat:SetFloat("$treeswayscrumblestrength",0.1 * bendyness) -- "Strechyness" + mat:SetFloat("$treeswaystrength",0) -- "Strechyness" + elseif foliage_type == 0 then -- Trees + mat:SetFloat("$treeSwayStartHeight",mat_height) -- When it starts to sway + mat:SetFloat("$treeswayheight",max(700 - bendyness * 100,0)) -- << How far up before XY starts to matter + mat:SetFloat("$treeswayradius",max(110 - bendyness * 10,0)) -- ? + mat:SetFloat("$treeswayscrumblespeed",3 + (wave_speed or 0) ) -- ? + mat:SetFloat("$treeswayscrumblestrength",0.1 * bendyness) -- "Strechyness" + mat:SetFloat("$treeswaystrength",0) -- ? + elseif foliage_type == 1 then -- Leaves + mat:SetFloat("$treeSwayStartHeight",0.5 + mat_height / 2) + mat:SetFloat("$treeswayheight",8) + mat:SetFloat("$treeswayradius",1) + mat:SetFloat("$treeswayscrumblespeed",1 + (wave_speed or 0)) + mat:SetFloat("$treeswayscrumblestrength",0.1) + mat:SetFloat("$treeswaystrength",0.06 * bendyness) + else + mat:SetFloat("$treeSwayStartHeight",0.1 + mat_height / 10) + mat:SetFloat("$treeswayheight",8) + mat:SetFloat("$treeswayradius",1) + mat:SetFloat("$treeswayscrumblespeed",wave_speed or 0) + mat:SetFloat("$treeswayscrumblestrength",0) + mat:SetFloat("$treeswaystrength",0.05 * bendyness) + end + end + + for texture,data in pairs(default_foliage) do + if not data or #data < 1 then continue end + if data[1] < -1 then + SetFoliageData(texture,-2) + else + SetFoliageData(texture,unpack(data)) + end + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_vfire.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_vfire.lua new file mode 100644 index 0000000..b75d83f --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sh_vfire.lua @@ -0,0 +1,30 @@ +--[[------------------------------------------------------------------------- +vFire support :D +---------------------------------------------------------------------------]] + +local vFireList = {} +hook.Add("vFireOnCalculateWind","vFire - StormFox Handshake",function(vFireEnt) + local outside = StormFox2.Wind.IsEntityInWind(vFireEnt) + if outside then + vFireList[vFireEnt] = true + return StormFox2.Wind.GetVector() / 20 + end +end) +if CLIENT then return end +local ran = math.random +timer.Create("vFire - StormFox Rain",2,0,function() + local r = StormFox2.Weather.GetRainAmount() + if r <= 0 then table.Empty(vFireList) return end + for ent,_ in pairs(vFireList) do + if IsValid(ent) then + ent:SoftExtinguish(r * ran(130,160)) + end + end + table.Empty(vFireList) +end) + +timer.Simple(2,function() + if not vFireInstalled then return end + StormFox2.Msg("Gee, vFire, what do you want to do tonight?") + hook.Call("vFire - StormFox Handeshake") +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_mapio.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_mapio.lua new file mode 100644 index 0000000..36d1c48 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_mapio.lua @@ -0,0 +1,122 @@ +--[[------------------------------------------------------------------------- +Light entities: ( env_projectedtexture , light_dynamic, light, light_spot ) +Requirements: + - Named "night" or "1" or "day". + - Not have the name "indoor". + +logic_relays support and map lights. + dusk / night_events + dawn / day_events + weather_ Called when a weathertype gets applied + weather_onchange Called when a weathertype changes + weather__off Called when a weathertype gets removed + +---------------------------------------------------------------------------]] + +local night_lights = {{}, {}, {}, {}, {}, {}} +local relays = {} +local switch +-- local functions +local function setELight( ent, bTurnOn ) + local sOnOff = bTurnOn and "TurnOn" or "TurnOff" + ent:Fire( sOnOff ) +end +local function setLights( bTurnOn ) + if timer.Exists("StormFox2.mi.lights") then + timer.Remove("StormFox2.mi.lights") + end + local i = 1 + timer.Create("StormFox2.mi.lights", 0.5, 6, function() + for _,ent in ipairs(night_lights[i] or {}) do + if not IsValid(ent) then continue end + setELight(ent, bTurnOn) + end + i = i + 1 + end) +end +local function SetRelay(fMapLight) + local lights_on = fMapLight < 20 + if switch ~= nil and lights_on == switch then return end -- Nothing changed + if lights_on then + StormFox2.Map.CallLogicRelay("night_events") + setLights( true ) + else + StormFox2.Map.CallLogicRelay("day_events") + setLights( false ) + end + switch = lights_on +end + +local includeNames = { + ["1"] = true, + ["streetlight"] = true, + ["streetlights"] = true +} + +local includeSearch = { + ["night"] = true, + ["day"] = true, +-- ["lake"] = true, Used indoors .. for some reason + ["outdoor"] = true +} + +local excludeSearch = { + ["indoor"] = true, + ["ind_"] = true, + ["apt_"] = true +} + +local function Search(name, tab) + for _, str in ipairs( tab ) do + if string.format(name, str) then return true end + end + return false +end +local t = {"env_projectedtexture", "light_dynamic", "light", "light_spot"} +hook.Add("StormFox2.InitPostEntity", "StormFox2.lightioinit", function() + -- Locate lights on the map + for i,ent in ipairs( ents.GetAll() ) do + local c = ent:GetClass() + if not table.HasValue(t, c) then continue end + local name = ent:GetName() + -- Unnamed entities + if not name then + if c == "light_spot" then + table.insert(night_lights[ 1 + i % 6 ],ent) + end + continue + end + name = name:lower() + -- Check for include + if includeNames[name] then + table.insert(night_lights[ 1 + i % 6 ],ent) + continue + end + -- Check exclude + if Search(name, excludeSearch) then + continue + end + -- Check include + if not Search(name, includeSearch) then + continue + end + table.insert(night_lights[ 1 + i % 6 ],ent) + end + -- Update on launch + timer.Simple(5, function() + SetRelay(StormFox2.Map.GetLight()) + end) +end) +-- Call day and night relays +hook.Add("StormFox2.lightsystem.new", "StormFox2.mapinteractions.light", SetRelay) + +-- StormFox2.Map.w_CallLogicRelay( name ) + +hook.Add("StormFox2.weather.postchange", "StormFox2.mapinteractions" , function( sName ,nPercentage ) + local c_weather = StormFox2.Weather.GetCurrent() + local relay = c_weather.Name + if c_weather.LogicRelay then + relay = c_weather.LogicRelay() or relay + end + StormFox2.Map.w_CallLogicRelay( string.lower(relay) ) +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_menu.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_menu.lua new file mode 100644 index 0000000..3b9a290 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_menu.lua @@ -0,0 +1,7 @@ + + +net.Receive(StormFox2.Net.Texture, function(len, ply) + StormFox2.Permission.EditAccess(ply,"StormFox Settings", function() + StormFox2.Map.ModifyMaterialType( net.ReadString(), net.ReadInt( 3 )) + end) +end) diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_weather_gen.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_weather_gen.lua new file mode 100644 index 0000000..18c8e26 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/functions/sv_weather_gen.lua @@ -0,0 +1,778 @@ + +StormFox2.WeatherGen = StormFox2.WeatherGen or {} +-- Settings +local auto_weather = StormFox2.Setting.AddSV("auto_weather",true,nil, "Weather") +local hide_forecast = StormFox2.Setting.AddSV("hide_forecast",false,nil, "Weather") +util.AddNetworkString( "StormFox2.weekweather" ) + +local forecast = {} -- The forecast table +local function SetForecast( tab, unix_time ) + forecast = tab + forecast.unix_time = unix_time + if not hide_forecast then return end + net.Start("StormFox2.weekweather") + net.WriteBool( unix_time ) + net.WriteTable( tab.temperature or {} ) + net.WriteTable( tab.weather or {} ) + net.WriteTable( tab.wind or {} ) + net.WriteTable( tab.windyaw or {} ) + net.Broadcast() +end +function StormFox2.WeatherGen.GetForecast() + return forecast +end +function StormFox2.WeatherGen.IsUnixTime() + return forecast.unix_time or false +end + +-- Open Weather API Settings + local api_MaxCalls = 59 + + local KEY_INVALID = 0 + local KEY_UNKNOWN = 1 + local KEY_VALID = 2 + + local KEY_STATUS = KEY_UNKNOWN + + local API_ENABLE = StormFox2.Setting.AddSV("openweathermap_enabled",false,nil,"Weather") + StormFox2.Setting.AddSV("openweathermap_lat",52,nil,"Weather",-180,180) -- Fake setting + StormFox2.Setting.AddSV("openweathermap_lon",-2,nil,"Weather",-90,90) -- Fake setting + + if SERVER then + CreateConVar("sf_openweathermap_key", "", {FCVAR_ARCHIVE, FCVAR_PROTECTED}, "Sets the API key") + CreateConVar("sf_openweathermap_real_lat","52.613909" , {FCVAR_ARCHIVE, FCVAR_PROTECTED}, "The real LAT for the API") + CreateConVar("sf_openweathermap_real_lon","-2.005960" , {FCVAR_ARCHIVE, FCVAR_PROTECTED}, "The real LON for the API") + end + local key = StormFox2.Setting.AddSV("openweathermap_key", "", nil,"Weather") + local location = StormFox2.Setting.AddSV("openweathermap_location", "", nil,"Weather") + local city = StormFox2.Setting.AddSV("openweathermap_city","",nil,"Weather") -- Fake setting + + -- Keep them secret and never network them. + key.isSecret = true + location.isSecret = true + city.isSecret = true + local function onSuccessF( body, len, head, code ) + KEY_STATUS = KEY_VALID + if not auto_weather:GetValue() then return end + -- Most likly an invalid API-Key. + local t = util.JSONToTable(body) or {} + if code == 401 then + KEY_STATUS = KEY_INVALID + StormFox2.Warning(t.message or "API returned 401") + StormFox2.Setting.Set("openweathermap_enabled", false) + return + end + if t.cod == "404" then return end -- Not found + local timeZone = t.timezone and tonumber(t.timezone) or 0 + -- We can set the sunrise and sunset + if t.sys and t.sys.sunrise and t.sys.sunset then + local sunrise = os.date("!%H:%M",t.sys.sunrise + timeZone) + local sunset = os.date("!%H:%M",t.sys.sunset + timeZone) + StormFox2.Setting.Set("sunrise",StormFox2.Time.StringToTime(sunrise)) + StormFox2.Setting.Set("sunset",StormFox2.Time.StringToTime(sunset)) + end + if t.main then + -- Temperature + local temp = StormFox2.Temperature.Convert("kelvin",nil,tonumber( t.main.temp or t.main.temp_min or t.main.temp_max )) + StormFox2.Temperature.Set( temp, 1 ) + -- Weather + local cloudyness = ( t.clouds and t.clouds.all or 0 ) / 110 + local rain = 0 + if t.rain then + rain = math.max( t.rain["1h"] or 0, t.rain["3h"] or 0, 2) / 8 + elseif t.snow then + rain = math.max( t.snow["1h"] or 0, t.snow["3h"] or 0, 2) / 8 + end + if rain > 0 then + StormFox2.Weather.Set("Rain", math.Round(rain * .7 + 0.2,2)) + elseif cloudyness >= 0.1 then + StormFox2.Weather.Set("Cloud", math.Round(cloudyness, 2)) + else + StormFox2.Weather.Set("Clear", 1) + end + -- Thunder + local b_thunder = false + if t.weather and t.weather[1] and t.weather[1].id and (rain > 0 or cloudyness >= 0.3) then + local id = t.weather[1].id + b_thunder = ( id >= 200 and id <= 202 ) or ( id >= 210 and id <= 212 ) or ( id >= 230 and id <= 232 ) or id == 212 + end + StormFox2.Thunder.SetEnabled(b_thunder, id == 212 and 12 or 6) -- 212 is heavy thunderstorm + -- Wind + StormFox2.Wind.SetForce( t.wind and t.wind.speed or 0 ) + StormFox2.Wind.SetYaw( t.wind and t.wind.deg or 0 ) + end + end + local n_NextAllowedCall = 0 + local b_BlockNextW = false + local function UpdateLiveWeather( api_key ) + if b_BlockNextW then return end + if KEY_STATUS == KEY_INVALID then return end + if n_NextAllowedCall >= CurTime() then + return StormFox2.Warning("API can't be called that often!") + end + n_NextAllowedCall = CurTime() + (60 / api_MaxCalls) + local lat = GetConVar("sf_openweathermap_real_lat"):GetString() + local lon = GetConVar("sf_openweathermap_real_lon"):GetString() + local api_key = api_key or GetConVar("sf_openweathermap_key"):GetString() + http.Fetch("http://api.openweathermap.org/data/2.5/weather?lat=" .. lat .. "&lon=" .. lon .. "&appid=" .. api_key, onSuccessF) + end + + local function SetCity( sCityName, callBack ) + if KEY_STATUS == KEY_INVALID then return end + if n_NextAllowedCall >= CurTime() then + return StormFox2.Warning("API can't be called that often!") + end + n_NextAllowedCall = CurTime() + (60 / api_MaxCalls) + http.Fetch("http://api.openweathermap.org/data/2.5/weather?q=" .. sCityName .. "&appid=" .. GetConVar("sf_openweathermap_key"):GetString(), function( body, len, head, code ) + -- Most likly an invalid API-Key. + local t = util.JSONToTable(body) or {} + if code == 401 then + KEY_STATUS = KEY_INVALID + StormFox2.Warning(t.message or "API returned 401") + StormFox2.Setting.Set("openweathermap_enabled", false) + return + end + if t.cod == 404 or not t.coord then -- City not found + if callBack then callBack( false ) end + return + end + b_BlockNextW = true -- Stop the setting from updating the weather again + StormFox2.Setting.Set("openweathermap_location", t.coord.lat .. "," .. t.coord.lon) + b_BlockNextW = false + onSuccessF( body, len, head, code ) + if callBack then callBack( true ) end + end) + end + --- Update Value + key:AddCallback( function( sString ) + RunConsoleCommand( "sf_openweathermap_key", sString ) + key.value = "" -- Silent set it again + KEY_STATUS = KEY_UNKNOWN + UpdateLiveWeather( sString ) -- Try and set the weather + end) + location:AddCallback( function( sString ) + local a = string.Explode(",", sString) + local lat, lon = tonumber(a[1]), tonumber(a[1]) + RunConsoleCommand( "sf_openweathermap_real_lat", lat ) + RunConsoleCommand( "sf_openweathermap_real_lon", lon ) + StormFox2.Setting.Set("openweathermap_lat",math.Round(lat)) -- Fake settings + StormFox2.Setting.Set("openweathermap_lon",math.Round(lon)) -- Fake settings + location.value = "" -- Silent set it again + UpdateLiveWeather() -- Set the weather to the given location + end) + city:AddCallback( function( cityName ) + if cityName == "" then return end + SetCity( cityName ) + city.value = "" -- Silent set it again + end) + + -- Enable and disable API + local status = false + local function EnableAPI() + if status then return end + timer.Create("SF_WGEN_API", 5 * 60, 0, function() + if not auto_weather:GetValue() then return end + if not status then return end + UpdateLiveWeather() + end) + end + local function DisableAPI() + if not status then return end + timer.Destroy("SF_WGEN_API") + end + local function IsUsingAPI() + return status + end + +-- Weather Gen Settings + local max_days_generate = 7 + local min_temp = StormFox2.Setting.AddSV("min_temp",-10,nil,"Weather",-273.15) + local max_temp = StormFox2.Setting.AddSV("max_temp",20,nil, "Weather") + local max_wind = StormFox2.Setting.AddSV("max_wind",50,nil, "Weather") + local function toStr( num ) + local c = tostring( num ) + return string.rep("0", 4 - #c) .. c + end + local default + local function SplitSetting( str ) + if #str< 20 then return default end -- Invalid, use default + local tab = {} + local min = math.min(100, string.byte(str, 1,1) - 33 ) / 100 + local max = math.min(100, string.byte(str, 2,2) - 33 ) / 100 + tab.amount_min = math.min(min, max) + tab.amount_max = math.max(min, max) + + local min = math.min(1440,tonumber( string.sub(str, 3, 6) ) or 0) + local max = math.min(1440,tonumber( string.sub(str, 7, 10) ) or 0) + tab.start_min = math.min(min, max) + tab.start_max = math.max(min, max) + + local min = tonumber( string.sub(str, 11, 14) ) or 0 + local max = tonumber( string.sub(str, 15, 18) ) or 0 + + tab.length_min = math.min(min, max) + tab.length_max = math.max(min, max) + + tab.thunder = string.sub(str, 19, 19) == "1" + tab.pr_week = tonumber( string.sub(str, 20) ) or 0 + return tab + end + local function CombineSetting( tab ) + local c =string.char( 33 + (tab.amount_min or 0) * 100 ) + c = c .. string.char( 33 + (tab.amount_max or 0) * 100 ) + + c = c .. toStr(math.Clamp( math.Round( tab.start_min or 0), 0, 1440 ) ) + c = c .. toStr(math.Clamp( math.Round( tab.start_max or 0), 0, 1440 ) ) + + c = c .. toStr(math.Clamp( math.Round( tab.length_min or 360 ), 180, 9999) ) + c = c .. toStr(math.Clamp( math.Round( tab.length_max or 360 ), 180, 9999) ) + + c = c .. (tab.thunder and "1" or "0") + + c = c .. tostring( tab.pr_week or 2 ) + return c + end + local default_setting = {} + default_setting["Rain"] = CombineSetting({ + ["amount_min"] = 0.4, + ["amount_max"] = 0.9, + ["start_min"] = 300, + ["start_max"] = 1200, + ["length_min"] = 360, + ["length_max"] = 1200, + ["thunder"] = true, + ["pr_week"] = 3 + }) + default_setting["Cloud"] = CombineSetting({ + ["amount_min"] = 0.2, + ["amount_max"] = 0.7, + ["start_min"] = 300, + ["start_max"] = 1200, + ["length_min"] = 360, + ["length_max"] = 1200, + ["pr_week"] = 3 + }) + default_setting["Clear"] = CombineSetting({ + ["amount_min"] = 1, + ["amount_max"] = 1, + ["start_min"] = 0, + ["start_max"] = 1440, + ["length_min"] = 360, + ["length_max"] = 1440, + ["pr_week"] = 7 + }) + -- Morning fog + -- default_setting["Fog"] = CombineSetting({ + -- ["amount_min"] = 0.4, + -- ["amount_max"] = 0.8, + -- ["start_min"] = 360, + -- ["start_max"] = 560, + -- ["length_min"] = 160, + -- ["length_max"] = 360, + -- ["pr_week"] = 1 + -- }) + default = CombineSetting({ + ["amount_min"] = 0.4, + ["amount_max"] = 0.9, + ["start_min"] = 300, + ["start_max"] = 1200, + ["length_min"] = 300, + ["length_max"] = 1200, + ["pr_week"] = 0 + }) + -- Create settings for weather-types. + local weather_setting = {} + local OnWeatherSettingChange + local function call( newVar, oldVar, sName ) + sName = string.match(sName, "wgen_(.+)") or sName + weather_setting[sName] = SplitSetting( newVar ) + OnWeatherSettingChange() + end + hook.Add("stormfox2.postloadweather", "StormFox2.WeatherGen.Load", function() + for _, sName in ipairs( StormFox2.Weather.GetAll() ) do + local str = default_setting[sName] or default + local obj = StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather") + obj:AddCallback( call ) + weather_setting[sName] = SplitSetting( obj:GetValue() ) + end + end) + for _, sName in ipairs( StormFox2.Weather.GetAll() ) do + local str = default_setting[sName] or default + local obj = StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather") + obj:AddCallback( call, "updateWSetting" ) + weather_setting[sName] = SplitSetting( obj:GetValue() ) + end + local function SetWeatherSetting(sName, tab) + if CLIENT then return end + StormFox2.Setting.SetValue("wgen_" .. sName, CombineSetting(tab)) + end + -- Returns the lowest key that is higer than the inputed key. + -- Second return is the higest key that is lower than the inputed key. + local function getClosestKey( tab, key) + local s, ls + for k, v in ipairs( table.GetKeys(tab) ) do + if v < key then + if not ls or ls < v then + ls = v + end + else + if not s or s > v then + s = v + end + end + end + return s, ls or 0 + end + local generator = {} -- Holds all the days + local day = {} + day.__index = day + local function CreateDay() + local t = {} + t._temperature = {} + t._wind = {} + t._windyaw = {} + t._weather = {} + setmetatable(t, day) + table.insert(generator, t) + if #generator > max_days_generate then + table.remove(generator, 1) + end + return t + end + local lastTemp + function day:SetTemperature( nTime, nCelcius ) + -- I got no clue why it tries to set values outside, but clamp it here just in case. + nCelcius = math.Clamp(nCelcius, min_temp:GetValue(), max_temp:GetValue()) + self._temperature[nTime] = nCelcius + lastTemp = nCelcius + return self + end + function day:GetTemperature( nTime ) + return self._temperature[nTime] + end + local lastWind, lastWindYaw + local lastLastWind + function day:SetWind( nTime, nWind, nWindYaw ) + self._wind[nTime] = nWind + self._windyaw[nTime] = nWindYaw + lastLastWind = lastWind + lastWind = nWind + lastWindYaw = nWindYaw + return self + end + function day:GetWind( nTime ) + return self._wind[nTime], self._windyaw[nTime] + end + function day:SetWeather( sName, nStart, nDuration, nAmount, nThunder ) + self._weather[nStart] = { + ["sName"] = sName, + ["nStart"] = nStart, + ["nDuration"] = nDuration, + ["fAmount"] = nAmount, + ["nThunder"] = nThunder } + self._last = math.max(self._last or 0, nStart + nDuration) + end + function day:GetWeather( nTime ) + return self._weather[nTime] + end + function day:GetWeathers() + return self._weather + end + function day:GetLastWeather() + return self._weather[self._last] + end + local function GetLastDay() + return generator[#generator] + end + local weatherWeekCount = {} + local function CanGenerateWeather( sName ) + local count = weatherWeekCount[ sName ] or 0 + local setting = weather_setting[ sName ] + if not setting then return false end -- Invalid weahter? Ignore this. + local pr_week = setting.pr_week or 0 + if pr_week <= 0 then return false end -- Disabled + if pr_week < 1 then -- Floats between 0 and 1 is random. + pr_week = math.random(0, 1 / pr_week) <= 1 and 1 or 0 + end + if count >= pr_week then return false end -- This weahter is reached max for this week + return true + end + local function SortList() + local t = {} + for _, sName in pairs(StormFox2.Weather.GetAll()) do + t[sName] = weatherWeekCount[sName] or 0 + end + return table.SortByKey(t, true) + end + local function UpdateWeekCount() + weatherWeekCount = {} + for k, day in ipairs( generator ) do + for nTime, weather in pairs( day:GetWeathers() ) do + local sName = weather.sName + weatherWeekCount[sName] = (weatherWeekCount[sName] or 0) + 1 + end + end + end + local atemp = math.random(-4, 4) + local nextWeatherOverflow = 0 + local function GenerateDay() + local newDay = CreateDay() + UpdateWeekCount() + -- Handle temperature + do + local mi, ma = min_temp:GetValue(), max_temp:GetValue() + local ltemp = lastTemp or math.random(mi, ma) + local aftemp = -math.min(ltemp - mi, 12) -- The closer the temperature is to minimum, the lower min + local ahtemp = math.min(ma - ltemp, 12) -- The closer the temperature is to maximum, the lower max + atemp = atemp + math.Rand(-4, 4) -- How much the temperature goes up or down + atemp = math.Clamp(atemp, aftemp, ahtemp) + -- UnZero + local tempBoost = 7 - math.abs( ltemp + atemp ) + if tempBoost > 0 then + if atemp >= 0 then + atemp = math.max(atemp + tempBoost, tempBoost) + else + atemp = math.min(atemp - tempBoost, -tempBoost) + end + end + -- Spikes + if math.random(10) > 8 then + -- Create a spike + if ltemp + atemp >= 0 then + atemp = mi / 2 + else + atemp = ma / 2 + end + end + -- New temp + local newMidTemp = math.Round(math.Clamp(ltemp + atemp, mi + 4, ma), 1) + -- Make the new temperature + + local h = StormFox2.Sun.GetSunRise() + local sunHigest = math.random(h - 180, h + 180) + local sunDown = StormFox2.Sun.GetSunSet() - 180 + newDay:SetTemperature( sunHigest, newMidTemp ) + newDay:SetTemperature( sunDown, math.max(newMidTemp - math.random(7), mi) ) + end + -- Handle wind + local newWind + do + --lastWind, lastWindYaw + if not lastWind then + lastWind = math.random(5) + lastWindYaw = math.random(360) + end + local buff = math.abs(atemp) - 4 -- Wind tent to increase the more temp changes. Also add a small negative modifier + if math.random(1, 50) >= 49 and buff > 4 then -- Sudden Storm + buff = buff + 10 + end + local addforce = math.random(buff / 2, buff - math.abs(lastLastWind or 0)) + newWind = math.min(max_wind:GetValue(), math.max(0, lastWind + addforce)) + local yawChange = math.min(40, lastWind + addforce * 15) + newDay:SetWind( math.random(180, 1080), newWind, ( lastWindYaw + math.random(-yawChange, yawChange) ) % 360 ) + end + -- Handle weather + local i = 3 -- Only generates 2 types of weathers pr day at max + local _last = nextWeatherOverflow -- The next empty-time of the day + for _, sName in ipairs( SortList() ) do + if _last >= 1440 then continue end -- This day is full of weathers. Ignore. + if sName == "Clear" and math.random(0, newWind) < newWind * 0.8 then -- Roll a dice between 0 and windForce. If dice is below 80%, try and find another weahter instead. + if atemp > 0 then -- Warm weather tent to clear up the weather + break + elseif atemp < 0 then -- Colder weather will form weather + continue + end + end + -- Check if weather is enabled, and we haven't reached max. + if not CanGenerateWeather( sName ) then continue end + local setting = weather_setting[sName] + local minS, maxS = setting.start_min, setting.start_max + local minL, maxL = setting.length_min, setting.length_max + if _last >= maxS then continue end -- This weather can't be generated this late. + i = i - 1 + if i <= 0 then break end + local start_time = math.random(math.max(minS, _last), maxS) + local length_time = math.random(minL, maxL) + local amount = math.Rand(setting.amount_min, setting.amount_max) + local nThunder + if setting.thunder and amount > 0.5 and math.random(0, 10) > 7 then + nThunder = math.random(4,8) + end + newDay:SetWeather( sName, start_time, length_time, amount, nThunder ) + _last = start_time + length_time + end + nextWeatherOverflow = math.max(0, _last - 1440) + end + local function GenerateWeek() + for i = 1, max_days_generate do + GenerateDay() + end + end + local enable = false + local function IsUsingWeatherGen() + return enable + end + local function TimeToIndex( tab ) + local c = table.GetKeys( tab ) + local a = {} + table.sort(c) + for i = 1, #c do + a[i] = {c[i], tab[c[i]]} + end + return a + end + local wGenList = {} + local function PushDayToList() -- Merges the 7 days into one long line. Its more stable this way + -- empty forecast + wGenList = {} + wGenList._temperature = {} + wGenList._wind = {} + wGenList._windyaw = {} + wGenList._weather = {} + local lastWType = "Clear" + local lastWTime = -100 + for i = 1, 4 do + local f = {} + local day = generator[i] + for nTime, var in pairs( day._temperature ) do + wGenList._temperature[ nTime + (i - 1) * 1440 ] = var + end + for nTime, var in pairs( day._wind ) do + local nn = nTime + (i - 1) * 1440 + wGenList._wind[ nn ] = var + wGenList._windyaw[ nn ] = day._windyaw[ nTime ] + end + for nTime, var in pairs( day._weather ) do + if var.sName == "Clear" then -- Ignore clear weathers. They're default. + lastWType = "Clear" + continue + end + local nTimeStart = nTime + (i - 1) * 1440 + local nTimeMax = nTimeStart + math.Round(math.random(var.nDuration / 4, var.nDuration / 2), 1) + local nTimeMaxEnd = nTimeStart + var.nDuration * 0.75 + local nTimeEnd = nTimeStart + var.nDuration + + local wObj = StormFox2.Weather.Get(var.sName) + local t = { + ["sName"] = var.sName, + ["fAmount"] = math.Round(var.fAmount, 2), + ["bThunder"] = var.nThunder + } + local useCloud = wObj.Inherit == "Cloud" and math.random(1, 10) >= 5 + local startWType = useCloud and "Cloud" or var.sName + if lastWType == var.sName and lastWTime == nTimeStart then -- In case we had the same weather type before, remove the "fading out" part + wGenList._weather[ lastWTime ] = nil + startWType = var.sName + else + wGenList._weather[ nTimeStart ] = { + ["sName"] = startWType, + ["fAmount"] = 0 + } + end + wGenList._weather[ nTimeMax ] = { + ["sName"] = startWType, + ["fAmount"] = math.Round(var.fAmount, 2) + } + wGenList._weather[ nTimeMaxEnd ] = t + wGenList._weather[ nTimeEnd ] = { + ["sName"] = var.sName, + ["fAmount"] = 0 + } + lastWType = var.sName + lastWTime = var.nTimeEnd + end + end + -- Push it into an index + wGenList.weather = TimeToIndex( wGenList._weather ) + wGenList.temperature = TimeToIndex( wGenList._temperature ) + wGenList.wind = TimeToIndex( wGenList._wind ) + wGenList.windyaw = TimeToIndex( wGenList._windyaw ) + if not IsUsingWeatherGen() then return end -- Don't update the forecast. But keep the weather in mind in case it gets enabled. + SetForecast( wGenList ) + end + -- In case settings change, update weekweather + local function ClearAndRedo() + timer.Simple(1, function() + weatherWeekCount = {} + generator = {} + weather_index = 0 + wind_index = 0 + temp_index = 0 + -- Generate new Day + GenerateWeek() + -- Make WGen + PushDayToList() + end) + end + min_temp:AddCallback( ClearAndRedo, "weekWeather" ) + max_temp:AddCallback( ClearAndRedo, "weekWeather" ) + max_wind:AddCallback( ClearAndRedo, "weekWeather" ) + OnWeatherSettingChange = ClearAndRedo + + local lastWeather, lastWind, lastTemp = -1, -1 , -1 + + local function fkey( x, a, b ) + return (x - a) / (b - a) + end + + local function findNext( tab, time ) -- First one is time + for i, v in ipairs( tab ) do + if time > v[1] then continue end + return i + end + return 0 + end + + local weather_index = 0 + local wind_index = 0 + local temp_index = 0 + + local function EnableWGenerator(forceCall) + if enable and not forceCall then return end + enable = true + GenerateWeek() -- Generate a week + PushDayToList() -- Push said list to w-table. + -- We need to set the start-weather + -- Set the start temperature + local curTime = math.ceil(StormFox2.Time.Get()) + local t_index = findNext( wGenList.temperature, curTime ) + if t_index > 0 then + local procentStart = 1 + local _start = wGenList.temperature[t_index - 1] + local _end = wGenList.temperature[t_index] + if _start then + procentStart = fkey( curTime, _start[1], _end[1] ) + end + local temp = Lerp( procentStart, (_start or _end)[2], _end[2] ) + StormFox2.Temperature.Set( math.Round(temp, 2), 0 ) + end + + -- Set the start wind + local wind_index = findNext( wGenList.wind, curTime ) + if wind_index > 0 then + local procentStart = 1 + local _start = wGenList.wind[t_index - 1] + local _end = wGenList.wind[t_index] + if _start then + procentStart = fkey( curTime, _start[1], _end[1] ) + end + local wind = Lerp( procentStart, (_start or _end)[2], _end[2] ) + StormFox2.Wind.SetForce( math.Round(wind, 2), 0 ) + + local _start = wGenList.windyaw[t_index - 1] + local _end = wGenList.windyaw[t_index] + local windyaw = Lerp( procentStart, (_start or _end)[2], _end[2] ) + StormFox2.Wind.SetYaw( math.Round(windyaw, 2), 0 ) + end + + -- Set the start weather + local weather_index = findNext( wGenList.weather, curTime ) + if weather_index > 0 then + local procentStart = 1 + local _start = wGenList.weather[weather_index - 1] + local _end = wGenList.weather[weather_index] + if _start then + procentStart = fkey( curTime, _start[1], _end[1] ) + else + _start = _end + end + local isClear = _end[2].sName == "Clear" or _end[2].fAmount == 0 + local w_type = ( isClear and _start[2] or _end[2] ).sName + local w_procent = ( isClear and _start[2] or _end[2] ).fAmount + StormFox2.Weather.Set( w_type, w_procent * procentStart ) + if _end[2].bThunder then + StormFox2.Thunder.SetEnabled(true, _end[2].bThunder) + else + StormFox2.Thunder.SetEnabled(false, 0) + end + end + -- Create a timer to modify the weather, checks every 1.5 seconds + timer.Create("SF_WGEN_DEF", 0.5, 0, function() + local cT = StormFox2.Time.Get() + local e_index = findNext( wGenList.weather, cT ) + local i_index = findNext( wGenList.wind, cT ) + local t_index = findNext( wGenList.temperature, cT ) + if weather_index~= e_index then + weather_index = e_index + local w_data = wGenList.weather[e_index] + if w_data then + local delta = StormFox2.Time.SecondsUntil(w_data[1]) + StormFox2.Weather.Set(w_data[2].sName, w_data[2].fAmount, delta ) + if w_data[2].bThunder then + StormFox2.Thunder.SetEnabled(true, w_data[2].bThunder) + else + StormFox2.Thunder.SetEnabled(false, 0) + end + end + end + if wind_index~= i_index then + wind_index = i_index + local i_data = wGenList.wind[i_index] + local y_data = wGenList.windyaw[i_index] + + if i_data then + local secs = StormFox2.Time.SecondsUntil(i_data[1]) + StormFox2.Wind.SetForce(i_data[2], secs) + if y_data then + StormFox2.Wind.SetYaw( y_data[2], secs) + end + end + end + if temp_index~= t_index then + temp_index = t_index + local t_data = wGenList.temperature[t_index] + if t_data then + local delta = StormFox2.Time.SecondsUntil(t_data[1]) + StormFox2.Temperature.Set(t_data[2], delta ) + end + end + + end) + end + local function DisableWGenerator() + if not enable then return end + enable = false + timer.Destroy("SF_WGEN_DEF") + end + hook.Add("StormFox2.Time.NextDay", "StormFox2.WGen.ND", function() + if not enable then return end + -- Generate new Day + GenerateDay() + -- Make WGen + PushDayToList() + weather_index = 0 + wind_index = 0 + temp_index = 0 + end) +-- Logic + local function NewWGenSetting() + if not auto_weather:GetValue() then -- Auto weather is off. Make sure API is off too + DisableAPI() + DisableWGenerator() + return + end + if API_ENABLE:GetValue() == true then + EnableAPI() + DisableWGenerator() + else + DisableAPI() + EnableWGenerator() + end + end + API_ENABLE:AddCallback( NewWGenSetting, "API_Enable") + auto_weather:AddCallback( NewWGenSetting, "WGEN_Enable") + hook.Add("stormfox2.postinit", "WGenInit", NewWGenSetting) + + + NewWGenSetting() + hook.Add("StormFox2.data.initspawn", "StormFox2.Weather.SendForcast",function( ply ) + if not hide_forecast then return end + if not forecast then return end -- ? + net.Start("StormFox2.weekweather") + net.WriteBool( forecast.unix_time ) + net.WriteTable( forecast.temperature or {} ) + net.WriteTable( forecast.weather or {} ) + net.WriteTable( forecast.wind or {} ) + net.WriteTable( forecast.windyaw or {} ) + net.Broadcast() + end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_ambiencesnd.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_ambiencesnd.lua new file mode 100644 index 0000000..83f6497 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_ambiencesnd.lua @@ -0,0 +1,247 @@ +SF_AMB_SND = SF_AMB_SND or {} +SF_AMB_CHANNEL = SF_AMB_CHANNEL or {} -- [snd]{station, target_vol, current_vol} + +StormFox2.Ambience = {} + +--[[ + - Outside Constant + - Near Outside 3D + - Near Window By Distance to nearest + - Roof By Distance to nearest + - Glass Roof (Like window) By Distance to nearest + - Metal Roof By Distance to nearest +]] + +--[[ Enums +SF_AMB_CONSTANT = 0 +SF_AMB_DISTANCE = 1 +SF_AMB_FAKE3D = 2 -- Pans the sound towards the point +SF_AMB_USE3D = 3 +]] + +SF_AMB_OUTSIDE = 0 -- CONSTANT VOLUME +SF_AMB_NEAR_OUTSIDE = 1 -- DISTANCE VOLUME +SF_AMB_WINDOW = 2 -- DISTANCE VOLUME +SF_AMB_UNDER_WATER = 3 -- CONSTANT VOLUME +SF_AMB_UNDER_WATER_Z = 4 -- Z-DISTANCE VOLUME (The distance to surface) +SF_AMB_ROOF_ANY = 5 -- Z-DISTANCE (SF_AMB_ROOF_CONCRETE and SF_AMB_ROOF_GROUND will be ignored) +SF_AMB_ROOF_GLASS = 6 -- Z-DISTANCE +SF_AMB_ROOF_METAL = 7 -- Z-DISTANCE +SF_AMB_ROOF_WOOD = 8 -- Z-DISTANCE +SF_AMB_ROOF_CONCRETE = 9 -- Z-DISTANCE +SF_AMB_ROOF_GROUND = 10-- Z-DISTANCE (Default roof) +SF_AMB_ROOF_WATER = 11-- Z-DISTANCE + +-- Smooth the volume of SF_AMB_CHANNEL +hook.Add("Think", "StormFox2.Ambiences.Smooth", function() + for snd,t in pairs( SF_AMB_CHANNEL ) do + if not IsValid( t[1] ) then -- In case something goes wrong. Delete the channel + SF_AMB_CHANNEL[snd] = nil + continue + end + -- Calc the new volume + local c_vol = t[3] + local newvol = math.Approach( c_vol, t[2], FrameTime() ) + if c_vol == newvol then continue end + if newvol <= 0 then + -- Stop the sound and remove channel + t[1]:Stop() + SF_AMB_CHANNEL[snd] = nil + else + if system.HasFocus() then -- We don't want sound playing when gmod is unfocused. + t[1]:SetVolume( newvol ) + else + t[1]:SetVolume( 0 ) + end + SF_AMB_CHANNEL[snd][3] = newvol + end + end +end) + +local AMB_LOAD = {} +-- Handles the sound-channel. +local function RequestChannel( snd ) + if AMB_LOAD[snd] then return end -- Already loading, or error + if SF_AMB_CHANNEL[snd] then return end -- Already loaded + AMB_LOAD[snd] = true + sound.PlayFile( snd, "noblock noplay", function( station, errCode, errStr ) + if ( IsValid( station ) ) then + SF_AMB_CHANNEL[snd] = {station, 0.1, 0} + station:SetVolume( 0 ) + station:EnableLooping( true ) + station:Play() + AMB_LOAD[snd] = nil -- Allow it to be loaded again + else + if errCode == 1 then + StormFox2.Warning("Sound Error! [1] Memory error.") + elseif errCode == 2 then + StormFox2.Warning("Sound Error! [2] Unable to locate or open: " .. snd .. ".") + else + StormFox2.Warning("Sound Error! [" .. errCode .. "] " .. errStr .. ".") + end + end + end) +end + +local snd_meta = {} +snd_meta.__index = snd_meta +-- Creates an ambience soudn +function StormFox2.Ambience.CreateAmbienceSnd( snd, SF_AMB_TYPE, vol_scale, min, max, playrate ) + local t = {} + t.snd = "sound/" .. snd + t.m_vol = vol_scale or 1 + t.min = min or 60 + t.max = max or 300 + t.SF_AMB_TYPE = SF_AMB_TYPE or SF_AMB_OUTSIDE + t.playbackrate = playrate or 1 + setmetatable( t , snd_meta ) + return t +end +-- Returns the channels +function StormFox2.Ambience.DebugList() + return SF_AMB_CHANNEL +end +-- Sets the scale of the sound +function snd_meta:SetVolume( num ) + self.m_vol = math.Clamp(num, 0, 2) -- Just in case +end +-- Doesn't work on sounds with SF_AMB_OUTSIDE or SF_AMB_UNDER_WATER +function snd_meta:SetFadeDistance( min, max ) + self.min = min + self.max = max +end +-- Set playback rate. +function snd_meta:SetPlaybackRate( n ) + self.playbackrate = n or 1 +end +-- Adds ambience for weather +hook.Add("stormfox2.preloadweather", "StormFox2.Amb.Create", function( w_meta ) + function w_meta:AddAmbience( amb_object ) + if not self.ambience_tab then self.ambience_tab = {} end + table.insert(self.ambience_tab, amb_object) + end + function w_meta:ClearAmbience() + self.ambience_tab = {} + end + hook.Remove("stormfox2.preloadweather", "StormFox2.Amb.Create") +end) +-- Applies the ambience sound +local function check(SF_AMB_TYPE, env) + if SF_AMB_TYPE == SF_AMB_NEAR_OUTSIDE and env.nearest_outside then return env.nearest_outside end + if SF_AMB_TYPE == SF_AMB_WINDOW and env.nearest_window then return env.nearest_window end +end + +local p_br = {} +-- Forces a sound to play +local fP +function StormFox2.Ambience.ForcePlay( snd, nVolume, playbackSpeed ) + if string.sub(snd, 0, 6) ~= "sound/" then + snd = "sound/" .. snd + end + fP[snd] = nVolume + p_br[snd] = playbackSpeed or 1 +end +hook.Add("Think", "StormFox2.Ambiences.Logic", function() + if not StormFox2 or not StormFox2.Weather or not StormFox2.Weather.GetCurrent then return end + local c = StormFox2.Weather.GetCurrent() + local v_pos = StormFox2.util.GetCalcView().pos + local env = StormFox2.Environment.Get() + -- Set all target volume to 0 + for _,t2 in pairs( SF_AMB_CHANNEL ) do + t2[2] = 0 + end + -- Generate a list of all sounds the client should hear. And set the the volume + local t = {} + if c.ambience_tab and StormFox2.Setting.SFEnabled() then + for _,amb_object in ipairs( c.ambience_tab ) do + local c_vol = t[amb_object.snd] or 0 + -- WATER + if env.in_water then -- All sounds gets ignored in water. Exp SF_AMB_INWATER + if amb_object.SF_AMB_TYPE == SF_AMB_INWATER then + if c_vol > amb_object.m_vol then + continue + end + c_vol = amb_object.m_vol + elseif env.outside and amb_object.SF_AMB_TYPE == SF_AMB_UNDER_WATER_Z then + local dis = env.in_water - v_pos.z + local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol + if c_vol > vol then + continue + end + c_vol = vol + end + -- OUTSIDE + elseif amb_object.SF_AMB_TYPE == SF_AMB_OUTSIDE and env.outside then + if c_vol > amb_object.m_vol then + continue + end + c_vol = amb_object.m_vol -- Outside is a constant volume + -- ROOFS + elseif amb_object.SF_AMB_TYPE >= SF_AMB_ROOF_ANY and amb_object.SF_AMB_TYPE <= SF_AMB_ROOF_WATER then + if amb_object.SF_AMB_TYPE == SF_AMB_ROOF_ANY and env.roof_z then + if env.roof_type ~= SF_AMB_ROOF_CONCRETE and env.roof_type ~= SF_AMB_ROOF_GROUND then + local dis = env.roof_z - v_pos.z + local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol + if c_vol > vol then + continue + end + c_vol = vol + end + elseif env.roof_z and env.roof_type then + if amb_object.SF_AMB_TYPE == SF_AMB_ROOF_GROUND and env.roof_type == SF_DOWNFALL_HIT_GROUND then + elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_GLASS and env.roof_type == SF_DOWNFALL_HIT_GLASS then + elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_METAL and env.roof_type == SF_DOWNFALL_HIT_METAL then + elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_WOOD and env.roof_type == SF_DOWNFALL_HIT_WOOD then + elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_CONCRETE and env.roof_type == SF_DOWNFALL_HIT_CONCRETE then + elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_WATER and env.roof_type == SF_DOWNFALL_HIT_WATER then + else + continue + end + local dis = env.roof_z - v_pos.z + local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol + if c_vol > vol then + continue + end + c_vol = vol + end + else + local pos = check( amb_object.SF_AMB_TYPE, env ) + if not pos then continue end + local dis = pos:Distance( v_pos ) + --if amb_object.SF_AMB_TYPE == SF_AMB_WINDOW and env.nearest_outside then + -- dis = math.max(dis, 250 - env.nearest_outside:Distance(v_pos)) + + --end + if dis > amb_object.max then continue end -- Too far away + local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol + if vol <= 0 then continue end -- Vol too low + if c_vol > vol then + continue + end + c_vol = vol + end + if c_vol > 0 then + t[amb_object.snd] = c_vol + p_br[amb_object.snd] = amb_object.playbackrate + end + end + end + fP = t + hook.Run("StormFox2.Ambiences.OnSound") + -- Set the target volume + for snd, vol in pairs( t ) do + if not SF_AMB_CHANNEL[snd] then -- Request to create the sound channel + RequestChannel( snd ) + else + SF_AMB_CHANNEL[snd][2] = vol -- Set the target volume + if IsValid( SF_AMB_CHANNEL[snd][1] ) then + if SF_AMB_CHANNEL[snd][1]:GetState() == 0 then -- Somehow stopped + SF_AMB_CHANNEL[snd][1]:Play() + end + if SF_AMB_CHANNEL[snd][1]:GetPlaybackRate() ~= p_br[snd] then + SF_AMB_CHANNEL[snd][1]:SetPlaybackRate(p_br[snd]) + end + end + end + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_defaultparticles.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_defaultparticles.lua new file mode 100644 index 0000000..f18477c --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_defaultparticles.lua @@ -0,0 +1,223 @@ + +-- Rain and show particles are a bit large. So we init them here + +if not StormFox2.Misc then StormFox2.Misc = {} end +local m_snow = Material("particle/snow") +local m_snow_multi = Material("stormfox2/effects/snow-multi.png") +local m_rain = Material("stormfox2/effects/raindrop.png") +local m_rain_medium = Material("stormfox2/effects/raindrop2.png") +local m_rain_multi = Material("particle/particle_smokegrenade") +local rainsplash_w = Material("effects/splashwake3") +local rainsplash = Material("stormfox2/effects/rain_splash") +local m_noise = Material("particle/particle_noisesphere") +local m_fog = Material("particle/smokesprites_0014") + +-- Hit particles +local function MakeRing( vPos, vNormal, L ) + local p = StormFox2.DownFall.AddParticle( rainsplash_w, vPos, true ) + p:SetAngles(vNormal:Angle()) + p:SetStartSize(8) + p:SetEndSize(40) + p:SetDieTime(1) + p:SetEndAlpha(0) + p:SetStartAlpha(math.min(255,25 + math.random(7,10) + L * 0.9)) +end +local function MakeSplash( vPos, vNormal, L, Part ) + local p = StormFox2.DownFall.AddParticle( rainsplash, vPos, false ) + p:SetAngles(vNormal:Angle()) + local _,s = Part:GetSize() + p:SetStartSize(s / 10) + p:SetEndSize(s / 2.5) + p:SetDieTime(0.15) + p:SetEndAlpha(0) + p:SetStartAlpha(math.min(105, 30 + L * 0.9)) +end +local function MakeSnowflake( vPos, vNormal, L, Part ) + local p = StormFox2.DownFall.AddParticle( m_snow, vPos - vNormal, false ) + p:SetAngles(vNormal:Angle()) + p:SetStartSize(math.min(2,Part:GetSize())) + p:SetEndSize(0) + p:SetDieTime(5) + p:SetEndAlpha(0) + p:SetStartAlpha(math.min(255, 10 + L)) +end + +local pT = function(self) + local n = math.min(15, StormFox2.Weather.GetLuminance() / 2) + if self:GetLifeTime() < self:GetDieTime() * .25 then + self:SetStartAlpha(0) + self:SetEndAlpha( n * 8 ) + elseif self:GetLifeTime() < self:GetDieTime() * .5 then + self:SetStartAlpha(n) + self:SetEndAlpha( n ) + else + self:SetStartAlpha(n * 2) + self:SetEndAlpha( 0 ) + end + self:SetNextThink( CurTime() ) +end + +local LM = 0 +local vector_zero = Vector(0,0,0) +local function MakeMist( vPos, L, Part) + if LM > CurTime() then return end + --LM = CurTime() + 0.1 + local w = StormFox2.Wind.GetVector() + local v = Vector(w.x * 8 + math.Rand(-10, 10), w.y * 8 + math.Rand(-10, 10) ,math.Rand(0, 10)) + local ss = math.Rand(75,180) + local es = math.Rand(75,180) + local p = StormFox2.DownFall.AddParticle( m_rain_multi, vPos + Vector(0,0,math.max(es,ss) / 2), false ) + p:SetAirResistance(0) + p:SetNextThink( CurTime() ) + p:SetDieTime( math.random(10, 15)) + p:SetRoll( math.Rand(0,360) ) + p:SetStartSize(ss) + p:SetEndSize(es) + p:SetEndAlpha(0) + p:SetStartAlpha(0) + p:SetThinkFunction(pT) + p:SetVelocity(v) + p:SetCollide( true ) + p:SetCollideCallback( function( part ) --This is an in-line function + part:SetVelocity(vector_zero) + p:SetLifeTime(25) + end ) +end + +-- Make big cloud particles size shared, to fix size hitting + +local init = function() + local fog_template = StormFox2.DownFall.CreateTemplate(m_fog, false) + StormFox2.Misc.fog_template = fog_template + --fog_template:SetSpeed(0.1) + fog_template:SetSize(250, 250) + function fog_template:OnHit( vPos, vNormal, nHitType, zPart ) + if math.random(3) > 1 then return end -- 33% chance to spawn a splash + local L = StormFox2.Weather.GetLuminance() - 10 + if nHitType == SF_DOWNFALL_HIT_WATER then + MakeRing( vPos, vNormal, L ) + elseif nHitType == SF_DOWNFALL_HIT_GLASS then + MakeSplash( vPos, vNormal, L, zPart ) + else -- if nHitType == SF_DOWNFALL_HIT_GROUND then + MakeSplash( vPos, vNormal, L, zPart ) + end + end + + local rain_template = StormFox2.DownFall.CreateTemplate(m_rain, true) + local rain_template_medium = StormFox2.DownFall.CreateTemplate(m_rain_medium,true) + local rain_template_multi = StormFox2.DownFall.CreateTemplate(m_rain_multi, true) + local snow_template = StormFox2.DownFall.CreateTemplate(m_snow, false, false) + local snow_template_multi = StormFox2.DownFall.CreateTemplate(m_snow_multi, true) + local fog_template = StormFox2.DownFall.CreateTemplate(m_rain, true) + StormFox2.Misc.rain_template = rain_template + StormFox2.Misc.rain_template_multi = rain_template_multi + StormFox2.Misc.rain_template_medium = rain_template_medium + StormFox2.Misc.snow_template = snow_template + StormFox2.Misc.snow_template_multi = snow_template_multi + StormFox2.Misc.fog_template = fog_template + + --rain_template_multi + rain_template_medium:SetFadeIn( true ) + rain_template_medium:SetSize(20,40) + rain_template_medium:SetRenderHeight(800) + rain_template_medium:SetAlpha(20) + + --rain_template_multi + rain_template_multi:SetFadeIn( true ) + rain_template_multi:SetSize(150, 600) + rain_template_multi:SetRandomAngle(0.15) + rain_template_multi:SetSpeed( 0.5 ) + + snow_template:SetRandomAngle(0.4) + snow_template:SetSpeed( 1 * 0.15) + snow_template:SetSize(5,5) + snow_template_multi:SetFadeIn( true ) + snow_template_multi:SetSize(300,300) + + --snow_template_multi:SetRenderHeight( 600 ) + snow_template_multi:SetRandomAngle(0.3) + + -- Think functions: + function rain_template_multi:Think() + local P = StormFox2.Weather.GetPercent() + local fC = StormFox2.Fog.GetColor() + local L = math.min(StormFox2.Weather.GetLuminance(), 100) + local TL = StormFox2.Thunder.GetLight() / 2 + local speed = 0.162 * P + 0.324 + StormFox2.Misc.rain_template_multi:SetColor( Color(fC.r + TL + 15, fC.g + TL + 15, fC.b + TL + 15) ) + StormFox2.Misc.rain_template_multi:SetAlpha( math.min(255, math.max(0, (P - 0.5) * 525 )) ) + StormFox2.Misc.rain_template_multi:SetSpeed( speed ) + end + + -- Particle Explosion + -- Make "rain" explosion at rain particles + function rain_template:OnExplosion( vExPos, nDisPercent, iRange, iMagnetide ) + local e_ang = (self:GetPos() - vExPos):Angle():Forward() + local boost = nDisPercent * 5 + local p = StormFox2.DownFall.AddParticle( "effects/splash1", vExPos + e_ang * iRange *nDisPercent , false ) + p:SetStartSize(math.random(32, 20)) + p:SetEndSize(5) + p:SetDieTime(2.5) + p:SetEndAlpha(0) + p:SetStartAlpha(6) + p:SetGravity( physenv.GetGravity() * 2 ) + p:SetVelocity( e_ang * iMagnetide * boost) + p:SetAirResistance(3) + p:SetCollide(true) + p:SetRoll(math.random(360)) + p:SetCollideCallback(function( part ) + part:SetDieTime(0) + end) + end + rain_template_multi.OnExplosion = rain_template.OnExplosion + + -- Particle Hit + function snow_template:OnHit( vPos, vNormal, nHitType, zPart ) + if math.random(3) > 1 then return end -- 33% chance to spawn a splash + local L = StormFox2.Weather.GetLuminance() - 10 + if nHitType == SF_DOWNFALL_HIT_WATER then + MakeRing( vPos, vNormal, L ) + else -- if nHitType == SF_DOWNFALL_HIT_GROUND then + MakeSnowflake( vPos, vNormal, L, zPart ) + end + end + function rain_template:OnHit( vPos, vNormal, nHitType, zPart ) + if math.random(3) > 1 then return end -- 33% chance to spawn a splash + local L = StormFox2.Weather.GetLuminance() - 10 + if nHitType == SF_DOWNFALL_HIT_WATER then + MakeRing( vPos, vNormal, L ) + elseif nHitType == SF_DOWNFALL_HIT_GLASS then + MakeSplash( vPos, vNormal, L, zPart ) + else -- if nHitType == SF_DOWNFALL_HIT_GROUND then + MakeSplash( vPos, vNormal, L, zPart ) + end + end + function rain_template_multi:OnHit( vPos, vNormal, nHitType, zPart) + local L = StormFox2.Weather.GetLuminance() - 10 + if math.random(1,3)> 2 then return end + MakeMist( vPos, L, zPart) + end + local i = 0 + function snow_template_multi:OnHit( vPos, vNormal, nHitType, zPart) + if i < 10 then + i = i + 1 + return + end + i = 0 + local L = StormFox2.Weather.GetLuminance() - 10 + MakeMist( vPos, L, zPart) + end + + fog_template:SetSize(512,512) + fog_template:SetSpeed(5) + fog_template:SetAlpha(0) + function fog_template:OnHit( vPos, vNormal, nHitType, zPart) + local L = StormFox2.Weather.GetLuminance() - 10 + MakeMist( vPos, L, zPart) + end +end + +hook.Add("stormfox2.postlib", "stormfox2.loadParticles", init) +if StormFox2.DownFall and StormFox2.DownFall.CreateTemplate then + init() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_entities.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_entities.lua new file mode 100644 index 0000000..3e10909 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_entities.lua @@ -0,0 +1,26 @@ + +-- Returns an explosion from x position +net.Receive("StormFox2.entity.explosion", function(len) + local pos = net.ReadVector() + local iRadiusOverride = net.ReadInt(16) + local iMagnitude = net.ReadUInt(16) + hook.Run("StormFox2.Entitys.OnExplosion", pos, iRadiusOverride, iMagnitude) +end) + +StormFox2.Ent = {} +hook.Add("stormfox2.postlib", "stormfox2.c_ENT",function() + StormFox2.Ent.env_skypaints = true -- Always + StormFox2.Ent.env_fog_controllers = true -- Always + StormFox2.Ent.light_environments = false -- Special + for k,v in ipairs( StormFox2.Map.FindClass("light_environment") ) do + if v.targetname then + StormFox2.Ent.light_environments = true + break + end + end + StormFox2.Ent.shadow_controls = #StormFox2.Map.FindClass("shadow_control") > 0 + StormFox2.Ent.env_tonemap_controllers = #StormFox2.Map.FindClass("env_tonemap_controller") > 0 + StormFox2.Ent.env_winds = #StormFox2.Map.FindClass("env_wind") > 0 + StormFox2.Ent.env_tonemap_controller = #StormFox2.Map.FindClass("env_tonemap_controller") > 0 + hook.Remove("stormfox2.postlib", "stormfox2.c_ENT") +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_fonts.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_fonts.lua new file mode 100644 index 0000000..bd2efc5 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_fonts.lua @@ -0,0 +1,120 @@ +surface.CreateFont( "SF_Display_H", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = true, + size = 30, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "SF_Display_H2", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = true, + size = 20, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "SF_Display_H3", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = true, + size = 14, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "SF_Menu_H2", { + font = "coolvetica", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 20, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont( "SkyFox-DigitalClock", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 50, + weight = 500, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, +} ) + +surface.CreateFont("SF2.W_Button", { + font = "Tahoma", + size = 15, + weight = 1500, +}) + +-- Tool +surface.CreateFont( "sf_tool_large", { + font = "Verdana", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 30, + weight = 700, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = true, +} ) + +surface.CreateFont( "sf_tool_small", { + font = "Verdana", + size = 17, + weight = 1000 +} ) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_vgui.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_vgui.lua new file mode 100644 index 0000000..6a29556 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/cl_vgui.lua @@ -0,0 +1,2015 @@ + + +-- Local settings +local bg_color = Color(255,255,255) +local slider_col = Color(91,156,214) +local b_alpha = Color(0,0,0,60) +local c_alpha = Color(0,0,0,20) + + +-- Add SF Setting vgui +local function empty() end +local function wrapText(sText, wide) + wide = wide - 10 + local tw,th = surface.GetTextSize(language.GetPhrase(sText)) + local lines,b = 1, false + local tab = {""} + for w in string.gmatch(sText, "[^%s,]+") do + local tt = tab[#tab] .. (b and " " or "") .. w + if surface.GetTextSize(tt) >= wide then + table.insert(tab, w) + lines = lines + 1 + else + tab[#tab] = tt + end + b = true + end + return tab, lines +end +local function niceName(sName) + sName = string.Replace(sName, "_", " ") + local str = "" + for s in string.gmatch(sName, "[^%s]+") do + str = str .. string.upper(s[1]) .. string.sub(s, 2) .. " " + end + return string.TrimRight(str, " ") +end +-- function to overwrite the textentity, and display the current temp setting. +local function makeTempDisplay( panel ) + panel._DrawTextEntryText = panel.DrawTextEntryText + function panel:DrawTextEntryText( ... ) + local s = self:GetText() + self:SetText(s .. StormFox2.Temperature.GetDisplaySymbol()) + self:_DrawTextEntryText( ... ) + self:SetText(s) + end +end + +-- Functions to convert the temperature to the clients settings +local function valToTemp( val ) + return StormFox2.Temperature.GetDisplay(val) .. StormFox2.Temperature.GetDisplaySymbol() +end + +local function tempToVal( val ) + return StormFox2.Temperature.Convert(StormFox2.Temperature.GetDisplayType(),"celsius",val) +end + +local function PaintOver(self, w, h) + --surface.SetDrawColor(color_black) + --surface.DrawOutlinedRect(0,0,w,h) + if not self.dMark then return end + local b = self.dMark - CurTime() + if b <= 0 then + self.dMark = nil + return + end + local a = math.sin(b * math.pi * 2 ) + surface.SetDrawColor(0,0,0, a * 100) + surface.DrawRect(0,0,w,h) +end + +local function FindPercent( x, a, b ) + return (x - a) / (b - a) +end + +local function FindPercentClam( x, a, b ) + return math.Clamp(FindPercent( x, a, b ), 0, 1) +end + + + +local vgui_Create +do + local function overrideConVar( PANEL ) + function PANEL:SetConVar( sName ) + self._sfobj = StormFox2.Setting.GetObject( sName ) + end + function PANEL:ConVarChanged( strNewValue ) + if not self._sfobj then return end + self._sfobj:SetFromString( strNewValue ) + end + -- Todo: Think only every 0.1 seconds? + function PANEL:ConVarStringThink() + if not self._sfobj then return end + if self.m_LV ~= nil and self._sfobj:GetValue() == self.m_LV then return end + self.m_LV = self._sfobj:GetValue() + self:SetValue( self._sfobj:GetString() ) + end + function PANEL:ConVarNumberThink() + if not self._sfobj then return end + if self.m_NV ~= nil and self._sfobj:GetValue() == self.m_NV then return end + self.m_NV = self._sfobj:GetValue() + self:SetValue( tonumber(num) ) + end + end + local function overrideAll( PANEL ) + if not IsValid( PANEL ) then return end + if PANEL.ConVarChanged then + overrideConVar(PANEL) + end + for _, v in ipairs( PANEL:GetChildren() ) do + overrideAll( v ) + end + end + function vgui_Create( str_type, self ) + local PANEL = vgui.Create( str_type, self ) + -- Override convar functions + overrideAll(PANEL) + return PANEL + end +end + +-- Title +do + local PANEL = {} + PANEL.PaintOver = PaintOver + derma.DefineControl( "SFTitle", "", PANEL, "DLabel" ) +end +-- Slider +do + local cir_size = 14 + local barSize = 6 + local shadowSize = 18 + local sh = Material("vgui/slider") + local barSpace = 7 + + local PANEL = {} + AccessorFunc(PANEL, "min", "Min") + AccessorFunc(PANEL, "max", "Max") + AccessorFunc(PANEL, "value", "Value") + AccessorFunc(PANEL, "float", "Float") + -- Make sure we set a flag, if edited + function PANEL:SetMin( v ) + self._set = true + self.min = v + end + function PANEL:SetMax( v ) + self._set = true + self.max = v + end + function PANEL:Init() + self:SetValue( -1 ) + self:SetMin(0) + self:SetMax(1) + self._set = false -- Reset flag + self:SetCursor( "hand" ) + self._flagOff = false + end + function PANEL:GetValueFromPercent( p ) + local range = self:GetMax() - self:GetMin() + return self:GetMin() + range * p + end + function PANEL:GetPercentFromValue( var ) + return FindPercentClam( var, self:GetMin(), self:GetMax() ) + end + function PANEL:GetPercentFromPoint( xPos ) + local bar_width = self:GetWide() - barSpace * 2 + local range = self:GetMax() - self:GetMin() + return math.Clamp((xPos - barSpace) / bar_width, 0, 1) + end + function PANEL:GetPointFromPercent( p ) + local bar_width = self:GetWide() - barSpace * 2 + return barSpace + bar_width * p + end + local m_cir = Material("vgui/circle") + + -- Tells the slider that -1, is used to indecate it can be turned off + function PANEL:SetFlagOff( bool ) + self._flagOff = bool + self:SetMin(0) + end + + function PANEL:SetSetting( sName, sType, Description ) + self._sfobj = StormFox2.Setting.GetObject( sName ) + if not self._sfobj then return end + -- If we haven't edited the min / max, then use the convars setting. If not then make a range. + local min = self._set and self:GetMin() or self._sfobj:GetMin() or 0 + local max = self._set and self:GetMax() or self._sfobj:GetMax() or self._sfobj:GetDefault() or 1 + if self._flagOff then + self:SetMin(0) + else + self:SetMin(min) + end + self:SetMax(max) + local target = math.Clamp(self._sfobj:GetValue(), min, max) + self:SetValue(target) + end + + function PANEL:Think() + if not self._sfobj then return end + local range = self:GetMax() - self:GetMin() + local target = self._sfobj:GetValue() + if self._flagOff and target <= 0 then + target = 0 + end + local delta = math.abs(self:GetValue() - target) + self:SetValue( math.Approach(self:GetValue(), target, FrameTime() * math.max(range * 0.1, delta * 10)) ) + end + + function PANEL:OnMousePressed( keyCode ) + if keyCode ~= MOUSE_LEFT then return end + self._down = true + self:MouseCapture( true ) + end + + function PANEL:OnMouseReleased( keyCode ) + if keyCode == MOUSE_RIGHT then + local menu = DermaMenu(false, self) + menu:AddOption( "Reset", function() + self._sfobj:Revert() + end):SetIcon( "icon16/package.png" ) + menu:Open() + return true + elseif keyCode == MOUSE_LEFT then + if not self._down then return end + self._down = false + self:MouseCapture( false ) + if not self._sfobj then return end + + local val = self:GetValueFromPercent(self:GetPercentFromPoint( self:CursorPos() )) + if not self:GetFloat() then + val = math.Round(val, 0) + else + val = math.Round(val, 3) + end + self._sfobj:SetValue(val) + self:SetValue(val) + end + end + function PANEL:Paint(w, h) + local bar_width = w - barSpace * 2 + local bar_y = math.Round(h / 2) + local val + local min, max = self:GetMin(), self:GetMax() + if self._flagOff and self:GetValue() < 0 then + val = 0 + elseif self._down then + val = self:GetValueFromPercent(self:GetPercentFromPoint( self:CursorPos() )) + if not self:GetFloat() then + self._downvalue = math.Clamp(math.Round(val), min, max) + else + self._downvalue = math.Clamp(math.Round(val,3), min, max) + end + elseif self:GetValue() < 0 and false then -- Unset + local p = ((math.sin(SysTime() * 3) + 1) / 2) + val = min + p * max + else + val = self:GetValue() + end + local percentage = FindPercentClam( val , self:GetMin() or 0, max or 1 ) + if percentage < 1 then + draw.RoundedBox(30, barSpace, bar_y - barSize / 2, bar_width ,barSize, b_alpha) + end + if percentage > 0 then + draw.RoundedBox(30, barSpace, bar_y - barSize / 2, bar_width * percentage, barSize, slider_col) + end + + local cir_x = self:GetPointFromPercent( percentage ) + surface.SetMaterial(sh) + surface.SetDrawColor(c_alpha) + surface.DrawTexturedRectRotated(cir_x, bar_y, shadowSize, shadowSize,0) + surface.DrawTexturedRectRotated(cir_x, bar_y, shadowSize + 2, shadowSize + 2,0) + surface.SetMaterial(m_cir) + if self._down then + surface.SetDrawColor(slider_col) + else + surface.SetDrawColor(bg_color) + end + surface.DrawTexturedRectRotated(cir_x, bar_y, cir_size, cir_size,0) + end +derma.DefineControl( "SF_Slider", "", PANEL, "DPanel" ) + +-- DoubleSlider + local PANEL = {} + AccessorFunc(PANEL, "min", "Min") + AccessorFunc(PANEL, "max", "Max") + AccessorFunc(PANEL, "value", "MinValue") + AccessorFunc(PANEL, "value2", "MaxValue") + AccessorFunc(PANEL, "float", "Float") + function PANEL:Init() + self:SetMinValue( 0 ) + self:SetMaxValue( 0 ) + self:SetMin(0) + self:SetMax(1) + self:SetCursor( "hand" ) + end + local m_cir = Material("vgui/circle") + + function PANEL:SetSettingMin( sName, sType, Description ) + self._sfobj = StormFox2.Setting.GetObject( sName ) + if not self._sfobj then return end + local min = self._sfobj:GetMin() or self:GetMin() or 0 + self:SetMin(min) + self:SetMax(math.max(min, self:GetMax() or 1)) + end + + function PANEL:SetSettingMax( sName, sType, Description ) + self._sfobj2 = StormFox2.Setting.GetObject( sName ) + if not self._sfobj2 then return end + local max = self._sfobj:GetMax() or self:GetMax() or 1 + self:SetMax(max) + self:SetMin(max, self:GetMax() or 1) + end + + function PANEL:Think() + if not self._sfobj then return end + if not self._sfobj2 then return end + local range = self:GetMax() - self:GetMin() + -- Min + local delta = math.abs(self:GetMinValue() - self._sfobj:GetValue()) + self:SetMinValue( math.Approach(self:GetMinValue(), self._sfobj:GetValue(), FrameTime() * math.max(range * 0.1, delta * 10)) ) + -- Max + local delta = math.abs(self:GetMaxValue() - self._sfobj2:GetValue()) + self:SetMaxValue( math.Approach(self:GetMaxValue(), self._sfobj2:GetValue(), FrameTime() * math.max(range * 0.1, delta * 10)) ) + end + + local cir_size = 14 + local barSize = 6 + local shadowSize = 18 + local sh = Material("vgui/slider") + local barSpace = 7 + function PANEL:OnMousePressed( keyCode ) + if keyCode ~= MOUSE_LEFT then return end + -- Select the closest type + local cur_p = self:GetPercentFromPoint( self:CursorPos() ) + local percentage = FindPercentClam( (self:GetMinValue() + self:GetMaxValue()) / 2 , self:GetMin() or 0, self:GetMax() or 1 ) -- Center + if cur_p < percentage then + self._down = 1 -- Min + else + self._down = 2 -- Max + end + self:MouseCapture( true ) + end + function PANEL:OnMouseReleased( keyCode ) + if keyCode ~= MOUSE_LEFT then return end + if not self._down then return end + + self:MouseCapture( false ) + if not self._sfobj then return end + + local val = self:GetValueFromPercent(self:GetPercentFromPoint( self:CursorPos() )) + if not self:GetFloat() then + val = math.Round(val, 0) + else + val = math.Round(val, 3) + end + if self._down == 0 then + self._sfobj:SetValue(val) + self:SetMinValue(val) + else + self._sfobj2:SetValue(val) + self:SetMaxValue(val) + end + self._down = false + end + function PANEL:Paint(w, h) + local bar_width = w - barSpace * 2 + local bar_y = math.Round(h / 2) + local valMin, valMax = self:GetMinValue(), self:GetMaxValue() + if self._down then + local val = self:GetValueFromPercent(self:GetPercentFromPoint( self:CursorPos() )) + if not self:GetFloat() then + self._downvalue = math.Clamp(math.Round(val), self:GetMin(), self:GetMax()) + else + self._downvalue = math.Clamp(math.Round(val,3), self:GetMin(), self:GetMax()) + end + if self._down == 1 then + valMin = val + else + valMax = val + end + end + local minP = FindPercentClam( valMin , self:GetMin() or 0, self:GetMax() or 1 ) + local maxP = FindPercentClam( valMax , self:GetMin() or 0, self:GetMax() or 1 ) + + -- BG bar + draw.RoundedBox(30, barSpace, bar_y - barSize / 2, bar_width ,barSize, b_alpha) + + local cir_x = self:GetPointFromPercent( minP ) + local cir_x2 = self:GetPointFromPercent( maxP ) + + draw.RoundedBox(30, cir_x, bar_y - barSize / 2, cir_x2 - cir_x, barSize, slider_col) + + -- Min + surface.SetMaterial(sh) + surface.SetDrawColor(c_alpha) + surface.DrawTexturedRectRotated(cir_x, bar_y, shadowSize, shadowSize,0) + surface.DrawTexturedRectRotated(cir_x, bar_y, shadowSize + 2, shadowSize + 2,0) + surface.SetMaterial(m_cir) + if self._down then + surface.SetDrawColor(slider_col) + else + surface.SetDrawColor(bg_color) + end + surface.DrawTexturedRectRotated(cir_x, bar_y, cir_size, cir_size,0) + -- Max + surface.SetMaterial(sh) + surface.SetDrawColor(c_alpha) + surface.DrawTexturedRectRotated(cir_x2, bar_y, shadowSize, shadowSize,0) + surface.DrawTexturedRectRotated(cir_x2, bar_y, shadowSize + 2, shadowSize + 2,0) + surface.SetMaterial(m_cir) + if self._down then + surface.SetDrawColor(slider_col) + else + surface.SetDrawColor(bg_color) + end + surface.DrawTexturedRectRotated(cir_x2, bar_y, cir_size, cir_size,0) + end + derma.DefineControl( "SF_DoubleSlider", "", PANEL, "SF_Slider" ) +end +-- Description Box +do + local lineHeight + local function getLineHeight() + if lineHeight then return lineHeight end + surface.SetFont("DermaDefault") + local ws,hs = surface.GetTextSize("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + lineHeight = hs + return hs + end + local PANEL = {} + function PANEL:Init() + self.t_text = {} + self.text = "No description" + end + function PANEL:SetText( str ) + self.text = str + surface.SetFont("DermaDefault") + local t_text, lines = wrapText(str, self:GetWide()) + self:SetTall(lines * getLineHeight()) + self.t_text = t_text + end + function PANEL:PerformLayout(w, h) + surface.SetFont("DermaDefault") + local t_text, lines = wrapText(self.text, w) + self:SetTall(lines * getLineHeight()) + self.t_text = t_text + end + function PANEL:GetTextLines() + if not self.t_text then return 1 end + return math.max(1, #self.t_text) + end + function PANEL:Paint(w, h) + surface.SetDrawColor(color_white) + surface.DrawRect(0,0,w,h) + surface.SetFont("DermaDefault") + surface.SetTextColor(color_black) + for y, str in ipairs( self.t_text ) do + surface.SetTextPos(0, (y - 1) * getLineHeight() ) + surface.DrawText( str ) + end + end + derma.DefineControl( "SF2_TextBox", "", PANEL, "DPanel" ) +end +-- Setting Tab +do + local PANEL = {} + PANEL.PaintOver = PaintOver + --PANEL.Paint = empty + function PANEL:Init() + self:DockMargin(5,0,10,0) + local label_name = vgui_Create( "DLabel", self ) + label_name:SetText("Unknown Setting") + label_name:SetColor(color_black) + label_name:SetFont("DermaDefaultBold") + label_name:SizeToContents() + label_name:Dock(TOP) + self.label_name = label_name + local description = vgui_Create( "SF2_TextBox", self ) + description:SetText("No description.") + description:SetPos(25, label_name:GetTall() + 2) + self.description = description + self._SetSetting = self.SetSetting + self:DockMargin(15,0,15,5) + end + function PANEL:SetTitle( str ) + self.label_name:SetText( language.GetPhrase(str) or str ) + end + function PANEL:SetDescription( str ) + self.description:SetText( language.GetPhrase(str) or str ) + end + function PANEL:PerformLayout(w, h) + local maxH = 0 + if IsValid(self.description) then + local x = self.description:GetPos() + self.description:SetWide(w - x) + end + for _, v in ipairs( self:GetChildren() ) do + local x, y = v:GetPos() + maxH = math.max(maxH, y + v:GetTall()) + end + if h == maxH then return end + self:SetTall(maxH + 5) + end + function PANEL:HideTitle( bool ) + if bool == false then + self.label_name:Show() + for _, v in ipairs( self:GetChildren() ) do + local x, y = v:GetPos() + v:SetPos(x, y + self.label_name:GetTall()) + end + else + self.label_name:Hide() + for _, v in ipairs( self:GetChildren() ) do + local x, y = v:GetPos() + v:SetPos(x, y - self.label_name:GetTall()) + end + end + self:InvalidateLayout() + return self + end + PANEL.Paint = empty + function PANEL:SetSetting( sName, sType, Description ) + sType = sType or StormFox2.Setting.GetType( sName ) + local obj = StormFox2.Setting.GetObject( sName ) + local tName = Description and "#" .. Description or (obj and "sf_" .. obj:GetName()) or "Unknown" + self.label_name:SetText( language.GetPhrase(tName) or tName ) + self.description:SetText( language.GetPhrase(tName .. ".desc") or "Unknown" ) + for _, v in ipairs( self:GetChildren() ) do + if not v.SetSetting then continue end + v:SetSetting( sName, sType, Description ) + end + end + function PANEL:MoveDescription( x, y ) + if y then + self.description:SetPos( math.max(25, 10 + x), self.label_name:GetTall() + math.max(2, y) ) + else + local x2, y2 = self.description:GetPos() + self.description:SetPos( math.max(25, 10 + x), y2 ) + end + end + derma.DefineControl( "SF_Setting", "", PANEL, "DPanel" ) +end + +-- Boolean setting +do + local PANEL = {} + function PANEL:Init() + self.check = vgui_Create("DCheckBox", self) + self.check:SetPos(5,14) + function self.check:DoClick() -- If we're a radio-type. Never turn off. + if not self._sfobj then return end + if self:GetChecked() and self._block then return end + self._sfobj:SetValue( not self:GetChecked() ) + end + function self.check:DragMousePress( keyCode ) + if keyCode ~= MOUSE_RIGHT then return end + local menu = DermaMenu(false, self) + menu:AddOption( "Reset", function() + self._sfobj:Revert() + end):SetIcon( "icon16/package.png" ) + menu:Open() + end + function self.check:Think() + if not self._sfobj then return end + self:SetChecked(self._sfobj:GetValue()) + end + end + function PANEL:SetSetting( sName, sType, Description ) + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + self.check._sfobj = StormFox2.Setting.GetObject(sName) + self.check._block = self.check._sfobj:IsRadio() + + end + derma.DefineControl( "SF_Setting_Bool", "", PANEL, "SF_Setting" ) +end + +-- String setting +do + local PANEL = {} + function PANEL:Init() + self.text = vgui_Create("DTextEntry", self) + self.text:SetPos(5,14) + self.text:SetWide(120) + self:MoveDescription(120) + end + function PANEL:SetSetting( sName, sType, Description ) + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + self.text:SetConVar( sName ) + return self + end + derma.DefineControl( "SF_Setting_String", "", PANEL, "SF_Setting" ) +end + +-- Float setting +local barLength = 220 + 50 +do + local PANEL = {} + function PANEL:Init() + self.text = vgui_Create("DTextEntry", self) + self.text:SetNumeric(true) + self.text:SetDrawLanguageID( false ) + self.slider = vgui_Create("SF_Slider", self) + self.slider:SetPos(5,14) + self.slider:SetWide(barLength) + self.slider:SetFloat(true) + self.text:SetPos(barLength + 8,18) + self.text:SetSize(55,18) + self:MoveDescription(barLength + 60, 6) + self._isTemp = false + end + function PANEL:SetMin( v ) + self.slider:SetMin( v ) + return self + end + function PANEL:SetMax( v ) + self.slider:SetMax( v ) + return self + end + function PANEL:SetTemperature(bool) + self._isTemp = bool + return self + end + function PANEL:Think() + if not self._sfobj then return end + if self.slider._down and self.slider._downvalue then + local text + if self._isTemp then + text = self.slider._downvalue and valToTemp(self.slider._downvalue) or "?" + else + text = self.slider._downvalue + end + self.text:SetText(text) + elseif not self.text:IsEditing() then + local text + if self._flagoff and self._sfobj:GetValue() <= (self._sfobj:GetMin() or -1) then + text = "" + elseif self._isTemp then + text = valToTemp(self._sfobj:GetValue()) + else + text = self._sfobj:GetValue() + end + self.text:SetText(text) + end + end + function PANEL:SetSetting( sName, sType, Description ) + local _sfobj = StormFox2.Setting.GetObject( sName ) + self._sfobj = _sfobj + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + --self.text:SetConVar( sName ) + function self.text:OnEnter( val ) + val = string.match(val, "[%d%.-]+") or val + if _sfobj:GetMin() then + val = math.max(_sfobj:GetMin(), val) + end + if _sfobj:GetMax() then + val = math.min(_sfobj:GetMax(), val) + end + _sfobj:SetValue(val) + self:SetText(tostring(val)) + end + return self + end + derma.DefineControl( "SF_Setting_Float", "", PANEL, "SF_Setting" ) +end +-- Int setting +do + local PANEL = {} + function PANEL:Init() + self.text = vgui_Create("DTextEntry", self) + self.text:SetNumeric(true) + self.text:SetDrawLanguageID( false ) + self.slider = vgui_Create("SF_Slider", self) + self.slider:SetPos(5,14) + self.slider:SetWide(barLength) + self.text:SetPos(barLength + 8,18) + self.text:SetSize(55,18) + self:MoveDescription(barLength + 60, 6) + self._isTemp = false + end + function PANEL:SetMin( v ) + self.slider:SetMin( v ) + return self + end + function PANEL:SetMax( v ) + self.slider:SetMax( v ) + return self + end + function PANEL:SetTemperature(bool) + self._isTemp = bool + return self + end + function PANEL:Think() + if not self._sfobj then return end + if self.slider._down and self.slider._downvalue then + local text + if self._isTemp then + text = self.slider._downvalue and valToTemp(self.slider._downvalue) or "?" + else + text = self.slider._downvalue + end + self.text:SetText(text) + elseif not self.text:IsEditing() then + local text + if self._isTemp then + text = valToTemp(self._sfobj:GetValue()) + else + text = self._sfobj:GetValue() + end + self.text:SetText(text) + end + end + function PANEL:SetSetting( sName, sType, Description ) + local _sfobj = StormFox2.Setting.GetObject( sName ) + self._sfobj = _sfobj + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + --self.text:SetConVar( sName ) + function self.text:OnEnter( val ) + val = string.match(val, "[%d%.-]+") or val + local clamp = tonumber( val ) or 0 + if _sfobj:GetMax() then + clamp = math.min(clamp, _sfobj:GetMax()) + end + if _sfobj:GetMin() then + clamp = math.max(clamp, _sfobj:GetMin()) + end + _sfobj:SetValue(clamp) + self:SetText(tostring(clamp)) + end + return self + end + derma.DefineControl( "SF_Setting_Int", "", PANEL, "SF_Setting" ) +end +-- Table / Enums +do + local sortNum = function(a,b) + if type(a) == "boolean" then + return a or b + end + return a>b + end + local sortStr = function(a,b) return a 0 or self:IsHovered() then return end + surface.SetMaterial(mat) + surface.SetDrawColor(color_white) + surface.DrawTexturedRect(w / 2 - 8, h / 2 - 8, 16, 16) + end + function self.check:DoClick() -- If we're a radio-type. Never turn off. + if self:GetChecked() and self._block then return end + self:Toggle() + end + self:MoveDescription(barLength + 60, 6) + end + function PANEL:SetMin( v ) + self.slider:SetMin( v ) + return self + end + function PANEL:SetMax( v ) + self.slider:SetMax( v ) + return self + end + function PANEL:Think() + if not self._sfobj then return end + OldPanel["Think"](self) + local min = self._sfobj:GetMin() or -1 + self.check:SetChecked(self._sfobj:GetValue() > min) + end + + function PANEL:SetSetting( sName, sType, Description ) + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + local _sfobj = StormFox2.Setting.GetObject( sName ) + self._sfobj = _sfobj + --self.text:SetConVar( sName ) + self.check._block = _sfobj:IsRadio() + function self.text:OnEnter( val ) + val = tonumber( string.match(val, "[%d%.-]+") or val ) or 0 + if _sfobj:GetMin() then + val = math.max(_sfobj:GetMin(), val) + end + if _sfobj:GetMax() then + val = math.min(_sfobj:GetMax(), val) + end + _sfobj:SetValue(val) + self:SetText(tostring(val)) + end + function self.check:DoClick() + local min = _sfobj:GetMin() or -1 + local b = _sfobj:GetValue() > 0 + if b then + _sfobj:SetValue(min) + else + local n = math.max(_sfobj:GetDefault(), 1) + _sfobj:SetValue(n) + end + end + return self + end + derma.DefineControl( "SF_Setting_FloatSpecial", "", PANEL, "SF_Setting_Float" ) +end +-- Time Object + local PANEL = {} + local function OnMousePressed( self, mcode ) + if ( mcode == MOUSE_LEFT ) then + self:OnGetFocus() + else + local menu = DermaMenu(false, self.base) + local obj = StormFox2.Setting.GetObject("12h_display") + local text = (obj:GetValue() and "24-" or "12-") .. language.GetPhrase("clock") + menu:AddOption( text, function() + obj:SetValue( not obj:GetValue()) + end):SetIcon( "icon16/clock.png" ) + return true + end + end + local function NewPaint(self, w, h, off) + surface.SetFont("SF2_TimeSet") + local tex = (self:GetValue() or "") + local tw,th = surface.GetTextSize(tex) + + surface.SetTextColor(self.color or color_black) + surface.SetTextPos(w / 2 - tw / 2,h / 2 - th / 2) + + if self:HasFocus() then + local e = (math.Round(SysTime() % 1) == 1 and "_" or "") + surface.DrawText( tex .. e ) + else + surface.DrawText( tex ) + end + end + + function PANEL:BGThink() + if self.lastDisplay ~= cDis then + self:InvalidateLayout() + self.lastDisplay = cDis + end + local _12 = StormFox2.Setting.Get("12h_display") + if not self.hour:IsEditing() and not self.hour._SETONLOST then + local h = StormFox2.Time.GetHours( self.value, _12 ) + if h < 10 then h = "0" .. h end + self.hour:SetText(h) + end + if not self.min:IsEditing() then + local m = StormFox2.Time.GetMinutes( self.value ) + if m < 10 then m = "0" .. m end + self.min:SetText(m) + end + if _12 then + self.ampm:SetText( StormFox2.Time.GetAMPM() ) + end + end + + function PANEL:Init() + self.bg = vgui_Create("DPanel",self) + function self.bg:IsSelected() return false end + function self.bg:GetToggle() return false end + local hour = vgui.Create("DTextEntry", self.bg) + self.hour = hour + local min = vgui.Create("DTextEntry", self.bg) + self.min = min + self.ampm = vgui.Create("DButton", self) + function self.bg:Paint( w, h ) + local cDis = StormFox2.Setting.Get("12h_display") + self.Hovered = hour.Hovered or min.Hovered + self.Depressed= hour:IsEditing() or min:IsEditing() + derma.SkinHook( "Paint", "Button", self, w, h ) + surface.SetFont("SF2_TimeSet") + local tw,th = surface.GetTextSize(":") + + surface.SetTextColor(self.color or color_black) + surface.SetTextPos(w / 2 - tw / 2,h / 2 - th / 2 - 2) + surface.DrawText(":") + end + function self.bg:IsHovered() + return hour:IsHovered() or min:IsHovered() + end + function self.bg:IsDown() + return hour:IsEditing() or min:IsEditing() + end + function self.hour.OnGetFocus(self) + local tex = self._SETONFOC or "" + self:SetValue(tex) + self:SetCaretPos(#tex) + self._SETONFOC = nil + end + self.min.OnGetFocus = self.hour.OnGetFocus + function self.min:OnLoseFocus() + if hour._SETONLOST then -- hour got unset variables + self.base:_SetNewVar( ) + hour._SETONLOST = nil + end + end + + self.bg:SetPos( 2,0 ) + self.hour:SetPos(0,0) + self.hour:SetWide(30) + self.hour:SetTall(30) + self.hour.Paint = NewPaint + self.hour.OnMousePressed = OnMousePressed + self.hour.base = self + self.min:SetPos(32,0) + self.min:SetWide(30) + self.min:SetTall(30) + self.min.Paint = NewPaint + self.min.OnMousePressed = OnMousePressed + self.min.base = self + + self.ampm:SetPos(66 + 2,0) + self.ampm:SetWide(34) + self.ampm:SetTall(30) + self.ampm:SetFont("SF2_TimeSetAM") + self.ampm.base = self + if not StormFox2.Setting.Get("12h_display") then + self.ampm:Hide() + self.lastDisplay = false + else + self.lastDisplay = true + self.ampm:SetText("AM") + end + self.hour:SetDrawLanguageID( false ) + self.min:SetDrawLanguageID( false ) + self.hour:SetNumeric(true) + self.min:SetNumeric(true) + self:SetValue( 0 ) + self:SetTall( 30 ) + -- Logic + function self.hour:OnChange( str ) + if #self:GetValue() <= 2 then return end + self._SETONLOST = self:GetValue():sub(0,2) + min._SETONFOC = self:GetValue():sub(3) + self:KillFocus() + min:RequestFocus() + self:SetText(self._SETONLOST ) + end + function self.hour:OnEnter() + self.base:_SetNewVar() + end + self.min.OnEnter = self.hour.OnEnter + function self.ampm:DoClick() + self:SetText( self:GetText() == "AM" and "PM" or "AM") + self.base:_SetNewVar() + end + end + function PANEL:SetValue( num ) + self.value = num + end + function PANEL:GetValue() + return self.value + end + function PANEL:_SetNewVar() + local str = self.hour:GetText() .. ":" .. self.min:GetText() + if StormFox2.Setting.Get("12h_display") then + str = str .. " " .. self.ampm:GetText() + end + self.value = StormFox2.Time.StringToTime( str ) + self:OnNewValue( self.value ) + end + PANEL.OnNewValue = function() end + PANEL.Paint = PANEL.BGThink + function PANEL:PerformLayout(pw, ph) + local w = (pw - 8) / 2 + if StormFox2.Setting.Get("12h_display") then + w = (pw - 8) / 3 + self.ampm:Show( ) + else + self.ampm:Hide( true ) + end + self.hour:SetWide(w) + self.min:SetWide(w) + self.ampm:SetWide(w) + + self.bg:SetWide(w * 2) + self.bg:SetHeight( ph ) + + --self.hour:SetPos(0,0) + self.min:SetPos(w, 0) + self.ampm:SetPos(w * 2 + 6, 0) + self.hour:SetHeight( ph ) + self.min:SetHeight( ph ) + self.ampm:SetHeight( ph ) + end + derma.DefineControl( "SF_TIME", "", PANEL, "DPanel" ) +-- Time +do + local PANEL = {} + surface.CreateFont("SF2_TimeSet", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 26, + weight = 50, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, + } ) + surface.CreateFont("SF2_TimeSetAM", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + extended = false, + size = 22, + weight = 50, + blursize = 0, + scanlines = 0, + antialias = true, + underline = false, + italic = false, + strikeout = false, + symbol = false, + rotary = false, + shadow = false, + additive = false, + outline = false, + } ) + local function NewPaint(self, w, h, off) + surface.SetFont("SF2_TimeSet") + local tw,th = surface.GetTextSize("ABCDEFGHIJ") + + surface.SetTextColor(color_black) + surface.SetTextPos(2,h / 2 - th / 2) + local tex = (self:GetValue() or "") + local e = (math.Round(SysTime() % 1) == 1 and "_" or "") + if self:HasFocus() and #tex < 2 then + surface.DrawText( tex .. e ) + else + surface.DrawText( tex ) + end + if self:HasFocus() and #tex == 2 then + local tw = surface.GetTextSize(tex:sub(0,1)) + surface.SetTextPos(2 + tw,h / 2 - th / 2) + surface.DrawText( e ) + end + end + local function OnMousePressed( self, mcode ) + if ( mcode == MOUSE_LEFT ) then + self:OnGetFocus() + else + local menu = DermaMenu(false, self) + local obj = StormFox2.Setting.GetObject("12h_display") + local text = (obj:GetValue() and "24-" or "12-") .. language.GetPhrase("clock") + menu:AddOption( text, function() + obj:SetValue( not obj:GetValue()) + end):SetIcon( "icon16/clock.png" ) + menu:AddOption( "Reset", function() + self._sfobj:Revert() + end):SetIcon( "icon16/package.png" ) + menu:Open() + return true + end + end + function PANEL:Init() + local b = vgui_Create("DPanel", self) -- All options + b.Paint = empty + local bg = vgui_Create("DPanel", b) -- Background + self.hour = vgui.Create("DTextEntry", b) + self.min = vgui.Create("DTextEntry", b) + self.ampm = vgui.Create("DButton", b) + self.bg = bg + self.b = b + self.bar_ex = 0 + + b:SetPos(6,14) + b:SetSize(66 + 34 + 2, 30) + bg:SetSize(66, 30) + function bg:IsSelected() return false end + function bg:GetToggle() return false end + function bg:IsDown() + return hour:IsEditing() or min:IsEditing() + end + local hour, min = self.hour, self.min + do + + function bg:Paint( w, h ) + self.Hovered = hour.Hovered or min.Hovered + self.Depressed= hour:IsEditing() or min:IsEditing() + derma.SkinHook( "Paint", "Button", self, w, h ) + surface.SetFont("SF2_TimeSet") + local tw,th = surface.GetTextSize(":") + + surface.SetTextColor(color_black) + surface.SetTextPos(w / 2 - tw / 2,h / 2 - th / 2 - 2) + surface.DrawText(":") + end + end + + self.hour:SetPos(2,0) + self.hour:SetWide(30) + self.hour:SetTall(30) + + self.min:SetPos(34,0) + self.min:SetWide(30) + self.min:SetTall(30) + + self.ampm:SetPos(66 + 2,0) + self.ampm:SetWide(34) + self.ampm:SetTall(30) + self.ampm:SetFont("SF2_TimeSetAM") + function self.ampm:UpdateColours( skin ) + if ( !self:IsEnabled() ) then return self:SetTextStyleColor( skin.Colours.Button.Disabled ) end + if ( self:IsDown() || self.m_bSelected ) then return self:SetTextStyleColor( skin.Colours.Button.Down ) end + if ( self.Hovered ) then return self:SetTextStyleColor( skin.Colours.Button.Hover ) end + return self:SetTextStyleColor( color_black ) + end + + self.hour:SetNumeric(true) + self.hour:SetDrawLanguageID( false ) + self.hour.Paint = NewPaint + self.hour:SetText("00") + local panel = self + function self.hour.OnGetFocus(self) + self:SetValue("") + end + function self.hour:OnEnter() + local num = tonumber( self:GetValue() or "" ) or 0 + local lim = StormFox2.Setting.Get("12h_display") and 12 or 24 + num = math.Clamp(num, 0, lim) + if num < 10 then + self:SetText("0" .. num) + else + self:SetText(num) + end + panel:OnNewValue() + end + function self.hour:OnChange() + if string.find(self:GetValue(), "-") then + self:SetText(string.Replace(self:GetValue(), "-", "")) + end + if #self:GetValue() >= 3 then -- Can't enter more numbers + self:FocusPrevious() + self:OnEnter() + elseif #self:GetValue() == 2 then + --self:OnEnter() + self:KillFocus() + min:RequestFocus() + min._setonlosefocus = true + end + end + function self.hour.OnLoseFocus( self ) + if #self:GetText() < 2 then return end + -- We lost focus and the player has entered two numbers in. Should be set. Not everyone hits enter + self:OnEnter() + end + self.min:SetNumeric(true) + self.min:SetDrawLanguageID( false ) + self.min.Paint = NewPaint + self.min:SetText("00") + function self.min.OnGetFocus(self) + self._oldVar = self:GetValue() + self:SetValue("") + end + function self.min:OnEnter() + -- Hour value has changed, and this is empty. We then use the last value instead. + if #self:GetText() < 1 and self._setonlosefocus and self._oldVar then + self:SetValue( self._oldVar ) + end + self._setonlosefocus = nil + local num = tonumber( self:GetValue() or "" ) or 0 + num = math.Clamp(num, 0, 59) + if num < 10 then + self:SetText("0" .. num) + else + self:SetText(num) + end + panel:OnNewValue( num ) + end + function self.min:OnChange() + if string.find(self:GetValue(), "-") then + self:SetText(string.Replace(self:GetValue(), "-", "")) + end + if #self:GetValue() >= 2 then -- Can't enter more numbers + self:FocusPrevious() + self:OnEnter() + end + end + function self.min.OnLoseFocus( self ) + -- We lost focus and the player has entered two numbers in. Should be set + if #self:GetText() < 2 and not (self._oldVar and self._setonlosefocus) then return end + -- We have a set-on-leave flag + if self._setonlosefocus and self._oldVar then + self:SetValue(self._oldVar) + end + self._setonlosefocus = nil + self:OnEnter() + + end + function self.ampm:DoClick() + if self:GetText() == "AM" then + self:SetText("PM") + else + self:SetText("AM") + end + panel:OnNewValue() + end + + hour.OnMousePressed = OnMousePressed + min.OnMousePressed = OnMousePressed + + self:MoveDescription(105 + self.bar_ex,4) + end + + function PANEL:OnNewValue() + local str = tonumber(self.hour:GetText()) .. ":" .. tonumber(self.min:GetText()) + if StormFox2.Setting.Get("12h_display") then + str = str .. self.ampm:GetText() + end + self._sfobj:SetValue( StormFox2.Time.StringToTime(str) ) + end + + function PANEL:Think() + var = var or 0 + if not self._sfobj then return end + local _12 = StormFox2.Setting.Get("12h_display") + local var = self._sfobj:GetValue() + if var < 0 then + self.b:SetDisabled( true ) + self.hour:SetText("00") + self.min:SetText("00") + self.ampm:SetText("AM") + if _12 then + self.ampm:SetText(StormFox2.Time.GetAMPM( self._sfobj:GetValue() )) + if not self.ampm:IsVisible() then + self.ampm:Show() + self:MoveDescription(105 + self.bar_ex,4) + end + else + if self.ampm:IsVisible() then + self.ampm:Hide() + self:MoveDescription(70 + self.bar_ex,4) + end + end + else + self.b:SetDisabled( false ) + if not self.hour:IsEditing() then + local h = StormFox2.Time.GetHours( self._sfobj:GetValue(), _12 ) + if h < 10 then + h = "0" .. h + end + self.hour:SetText(h) + end + if not self.min:IsEditing() then + local h = StormFox2.Time.GetMinutes( self._sfobj:GetValue(), _12 ) + if h < 10 then + h = "0" .. h + end + self.min:SetText(h) + end + if _12 then + self.ampm:SetText(StormFox2.Time.GetAMPM( self._sfobj:GetValue() )) + if not self.ampm:IsVisible() then + self.ampm:Show() + self:MoveDescription(105 + self.bar_ex,4) + end + else + if self.ampm:IsVisible() then + self.ampm:Hide() + self:MoveDescription(70 + self.bar_ex,4) + end + end + end + end + + function PANEL:SetSetting( sName, sType, Description ) + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + self._sfobj = StormFox2.Setting.GetObject( sName ) + self.min._sfobj = self._sfobj + self.hour._sfobj = self._sfobj + --self.check:SetConVar( sName ) + return self + end + derma.DefineControl( "SF_Setting_Time", "", PANEL, "SF_Setting" ) +end +-- Time Toggle +do + local OldPanel = vgui.GetControlTable("SF_Setting_Time") + local PANEL = {} + function PANEL:Init() + self.check = vgui_Create("DCheckBox", self) + self.check:SetPos(5,20) + self.b:SetPos(24,14) + self:MoveDescription(105 + 18,4) + self.bar_ex = 18 + end + function PANEL:Think() + OldPanel.Think(self) + if not self._sfobj then return end + local b = self._sfobj:GetValue() >= 0 + self.check:SetChecked(b) + end + function PANEL:SetSetting( sName, sType, Description ) + OldPanel.SetSetting( self, sName, sType, Description ) + local _sfobj = StormFox2.Setting.GetObject( sName ) + self.check._block = _sfobj:IsRadio() + function self.check:DoClick() + if self:GetChecked() and self._block then return end + local b = _sfobj:GetValue() > 0 + if b then + _sfobj:SetValue(-1) + else + local default = _sfobj:GetDefault() + if default < 0 then default = 720 end + _sfobj:SetValue(default) + end + end + return self + end + derma.DefineControl( "SF_Setting_TimeToggle", "", PANEL, "SF_Setting_Time" ) +end +-- DoubleInt setting +do + local PANEL = {} + function PANEL:Init() + self.text = vgui_Create("DTextEntry", self) + self.text:SetNumeric(true) + self.text:SetDrawLanguageID( false ) + self.slider = vgui_Create("SF_DoubleSlider", self) + self.slider:SetPos(5,14) + self.slider:SetWide(barLength) + self.text:SetPos(barLength + 8,18) + self.text:SetSize(55,18) + self:MoveDescription(barLength + 60, 6) + self._isTemp = false + end + function PANEL:SetTemperature(bool) + self._isTemp = bool + end + function PANEL:Think() + if not self._sfobj then return end + if self.slider._down and self.slider._downvalue then + local text + if self._isTemp then + text = self.slider._downvalue and valToTemp(self.slider._downvalue) or "?" + else + text = self.slider._downvalue + end + self.text:SetText(text) + elseif not self.text:IsEditing() then + local text + if self._isTemp then + text = valToTemp(self._sfobj:GetValue()) + else + text = self._sfobj:GetValue() + end + self.text:SetText(text) + end + end + function PANEL:SetSettingMin( sName, sType, Description ) + local _sfobj = StormFox2.Setting.GetObject( sName ) + self._sfobj = _sfobj + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + self.slider:SetSettingMin( sName, sType, Description ) + --self.text:SetConVar( sName ) + function self.text:OnEnter( val ) + val = string.match(val, "[%d%.]+") or val + local clamp = math.Clamp(tonumber( val ), _sfobj:GetMin(), _sfobj:GetMax()) + _sfobj:SetValue(clamp) + self:SetText(tostring(clamp)) + end + end + function PANEL:SetSettingMax( sName, sType, Description ) + local _sfobj2 = StormFox2.Setting.GetObject( sName ) + self._sfobj2 = _sfobj + self:_SetSetting( sName, sType, Description ) -- Set title and textbox + self.slider:SetSettingMax( sName, sType, Description ) + --self.text:SetConVar( sName ) + end + function PANEL:SetMin( var ) + self.slider:SetMin( var ) + end + function PANEL:SetMax( var ) + self.slider:SetMax( var ) + end + derma.DefineControl( "SF_Setting_Double", "", PANEL, "SF_Setting" ) +end + +-- Hud Ring +do + local PANEL = {} + local m_ring = Material("stormfox2/hud/hudring.png") + local cCol1 = Color(55,55,55,55) + local cCol2 = Color(55,55,255,105) + local seg = 40 + function PANEL:SetColor(r,g,b,a) + self.cCol2 = (r and g and b) and Color(r,g,b,a or 105) or r + return self + end + function PANEL:Init() + self._val = 1 + self._off = 0.05 + self.cCol1 = cCol1 + self.cCol2 = cCol2 + end + function PANEL:SetValue(nNum) + self._val = math.Clamp(nNum, 0, 1) + self:RebuildPoly() + return self + end + function PANEL:SetText(sText) + self._text = sText + return self + end + function PANEL:RebuildPoly() + local pw, ph = self:GetWide(), self:GetTall() + local x,y = pw / 2, ph / 2 + local polyMul = 1.2 + local radius = math.min(pw, ph) / 2 * polyMul + local mul2 = 2 / polyMul + + -- Generate poly + self._poly = {} + local seg = 40 + table.insert( self._poly, { x = x, y = y, u = 0.5, v = 0.5 } ) + local n = self._val * seg + for i = 0, n do + local a = math.rad( ( i / seg ) * -360 ) + table.insert( self._poly, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / mul2 + 0.5, v = math.cos( a ) / mul2 + 0.5 } ) + end + local a = math.rad( ( n / seg ) * -360 ) + table.insert( self._poly, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / mul2 + 0.5, v = math.cos( a ) / mul2 + 0.5 } ) + table.insert( self._poly, { x = x, y = y, u = 0.5, v = 0.5 } ) + end + function PANEL:PerformLayout(pw, ph) + self:RebuildPoly() + end + function PANEL:Paint(w,h) + if self._text then + local td = string.Explode("\n",self._text) + for k,v in ipairs(td) do + local n = k - 1 + draw.SimpleText(v, "DermaDefault", w / 2, h / 2 + n * 12 - (#td - 1) * 6, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + else + draw.SimpleText(self._val, "DermaDefault", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + if not self._poly then return end + surface.SetMaterial(m_ring) + surface.SetDrawColor(self.cCol1) + surface.DrawTexturedRect(self._x or 0,self._y or 0,self._w or w,self._h or h) + surface.SetDrawColor(self.cCol2) + surface.DrawPoly(self._poly) + end + derma.DefineControl( "SF_Setting_Ring", "", PANEL, "DPanel" ) +end + +-- Old + -- Ring + do + local PANEL = {} + derma.DefineControl( "SF_HudRing", "", PANEL, "DPanel" ) + end + -- World Display + do + local PANEL = {} + derma.DefineControl( "SF_WorldMap", "", PANEL, "DPanel" ) + end + + -- Day Display + do + local PANEL = {} + derma.DefineControl( "SF_WeatherHour", "", PANEL, "DPanel" ) + end + + -- Weather Display + do + local PANEL = {} + derma.DefineControl( "SF_WeatherMap", "", PANEL, "DPanel" ) + end +-- Menu +do + local function empty() end + local function switch(sName, tab, buttons, sCookie) + sName = string.lower(sName) + local pnl + for k, v in pairs(tab) do + if k == sName then + v:Show() + pnl = v + else + v:Hide() + end + end + if not IsValid(pnl) or pnl:GetDisabled() then + pnl = tab["start"] + sName = "start" + if IsValid(pnl) then + pnl:Show() + end + end + for k,v in pairs(buttons) do + v._selected = k == sName + end + if sCookie then + cookie.Set(sCookie, sName) + end + return pnl + end + local function addSetting(sName, pPanel, _type, _desc) + local setting + if type(_type) == "table" then + setting = vgui_Create("SF_Setting_Enum", pPanel) + elseif _type == "boolean" or _type == "bool" then + setting = vgui_Create("SF_Setting_Bool", pPanel) + elseif _type == "float" then + setting = vgui_Create("SF_Setting_Float", pPanel) + elseif _type == "special_float" then + setting = vgui_Create("SF_Setting_FloatSpecial", pPanel) + elseif _type == "number" then + setting = vgui_Create("SF_Setting_Int", pPanel) + elseif _type == "time" then + setting = vgui_Create("SF_Setting_Time", pPanel) + elseif _type == "string" then + setting = vgui_Create("SF_Setting_String", pPanel) + elseif _type == "time_toggle" then + setting = vgui_Create("SF_Setting_TimeToggle", pPanel) + elseif _type == "temp" or _type == "temperature" then + setting = vgui_Create("SF_Setting_Float", pPanel) + setting:SetTemperature( true ) + elseif _type == "double_number" then + setting = vgui_Create("SF_DDSliderNum", pPanel) + setting:SetSetting(sName[1],sName[2], _type, _desc) + return setting + else + StormFox2.Warning("Unknown Setting Variable: " .. sName .. " [" .. tostring(_type) .. "]") + return + end + if not setting then + StormFox2.Warning("Unknown Setting Variable: " .. sName .. " [" .. tostring(_type) .. "]") + return + end + --local setting = _type == "boolean" and vgui_Create("SFConVar_Bool", board) or vgui_Create("SFConVar", board) + setting:SetSetting(sName, _type, _desc) + return setting + end + local col = {Color(230,230,230), color_white} + local col_dis = Color(0,0,0,55) + local col_dis2 = Color(0,0,0,55) + local bh_col = Color(55,55,55,55) + local sb_col = Color(91,155,213) + + local icon = Material("icon16/zoom.png") + local icon_c = Color(255,255,255,180) + + local s = 20 + local side_button = function(self, w, h) + if self:IsHovered() and not self:GetDisabled() then + surface.SetDrawColor(bh_col) + surface.DrawRect(0,0,w,h) + end + + surface.SetDrawColor(self:GetDisabled() and col_dis or color_black) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(24 - s / 2, (h - s) / 2, s,s) + + local t = self.text or "ERROR" + surface.SetFont("DermaDefault") + local tw,th = surface.GetTextSize( t ) + surface.SetTextColor(self:GetDisabled() and col_dis2 or color_black) + surface.SetTextPos(48, h / 2 - th / 2) + surface.DrawText(t) + + if self._selected then + surface.SetDrawColor(sb_col) + surface.DrawRect(4,4,4,h - 8) + end + end + + local t_mat = "icon16/font.png" + local s_mat = "icon16/cog.png" + local s = 60 + local n_vc = Color(55,255,55) + local n_bm = Material("vgui/notices/undo") + + local PANEL = {} + function PANEL:Init() + -- BG + self:SetTitle("") + function self:SetTitle( str ) + self._title = str + end + self:SetSize(64 * 11, 500) + self:Center() + self:DockPadding(0,24,0,0) + function self:Paint(w, h) + surface.SetDrawColor( col[2] ) + surface.DrawRect(0,0,w,h) + surface.SetDrawColor( col[1] ) + if self.p_left then + surface.DrawRect(0,0,self.p_left:GetWide(),24) + else + surface.DrawRect(0,0,w,24) + end + + surface.SetDrawColor(55,55,55,255) + surface.DrawRect(0, 0, w, 24) + + local t = self._title or "Window" + surface.SetFont("DermaDefault") + local tw,th = surface.GetTextSize( t ) + surface.SetTextColor(color_white) + surface.SetTextPos(5, 12 - th / 2) + surface.DrawText(t) + end + -- Left panel + local p_left = vgui_Create("DPanel", self) + self.p_left = p_left + p_left:SetWide(180) + p_left:Dock( LEFT ) + p_left:DockPadding(0,0,0,0) + function p_left:Paint(w, h) + surface.SetDrawColor( col[1] ) + surface.DrawRect(0,0,w,h) + end + -- Right panel + local p_right = vgui_Create("DPanel", self) + self.p_right = p_right + p_right:Dock( FILL ) + p_right.Paint = empty + p_right.sub = {} + end + function PANEL:CreateLayout( tabs, setting_list ) + local p = self + local p_right = self.p_right + local p_left = self.p_left + p_right.sub = {} + + for k,v in ipairs( tabs ) do + local p = vgui_Create("DScrollPanel", self.p_right) + p:Dock(FILL) + self.p_right.sub[string.lower(v[1])] = p + p:Hide() + end + p_right.sub["start"]._isstart = true + + p_left.buttons = {} + -- Add Start + local b = vgui_Create("DButton", p_left) + b:Dock(TOP) + b:SetTall(40) + b:SetText("") + b.icon = tabs[1][3] + b.text = niceName( language.GetPhrase(tabs[1][2]) ) + b.Paint = side_button + p_left.buttons["start"] = b + function b:DoClick() + switch("start", p_right.sub, p_left.buttons, p._cookie) + end + + -- Add search bar + local sp = vgui_Create("DPanel", p_left) + p_left.sp = sp + local search_tab = {} + sp:Dock(TOP) + sp:SetTall(40) + function sp:Paint() end + sp.searchbar = vgui_Create("DTextEntry",sp) + sp.searchbar:SetText("") + sp.searchbar:Dock(TOP) + sp.searchbar:SetHeight(20) + sp.searchbar:DockMargin(4,10,4,0) + function sp.searchbar:OptionSelected( sName, page, vguiObj ) + local board = switch(page, p_right.sub, p_left.buttons, p._cookie) + if vguiObj then + vguiObj.dMark = 2 + CurTime() + if board then + board:ScrollToChild(vguiObj) + end + timer.Simple(1, function() vguiObj:RequestFocus() end ) + end + self:SetText("") + self:KillFocus() + end + function sp.searchbar:OnChange( ) + local val = self:GetText() + local tab = {} + if self.result and IsValid(self.result) then + self.result:Remove() + end + local OnlyTitles = val == "" + self.result = vgui_Create("DMenu") + function self.result:OptionSelected( pnl, text ) + if not search_tab[pnl.sName] then return end + sp.searchbar:OptionSelected( text, search_tab[pnl.sName][1], search_tab[pnl.sName][2] ) + end + for sName, v in pairs( search_tab ) do + local phrase = language.GetPhrase( sName ) + if string.find(string.lower(phrase),string.lower(val), nil, true) then + if OnlyTitles and v[3]~=t_mat then continue end + table.insert(tab, {sName, v[1], v[2]}) + local op = self.result:AddOption( phrase ) + if v[3] then + op:SetIcon(v[3]) + end + op.sName = sName + end + end + local x,y = self:LocalToScreen(0,0) + self.result:Open(x, y + self:GetTall()) + end + function sp.searchbar:OnGetFocus() + self:OnChange( ) + end + function sp.searchbar:OnLoseFocus() + if not self.result then return end + self.result:Remove() + self.result = nil + end + function sp.searchbar:Paint(w,h) + surface.SetDrawColor(color_white) + surface.DrawRect(0,0,w,h) + surface.SetDrawColor(color_black) + surface.DrawOutlinedRect(0,0,w,h) + local text = self:GetText() + if self:IsEditing() then + if math.Round(SysTime() * 2) % 2 == 0 then + text = text .. "|" + end + elseif #text < 1 then + text = "#searchbar_placeholer" + end + surface.SetMaterial(icon) + surface.SetDrawColor(icon_c) + local s = 4 + surface.DrawTexturedRect(w - h + s / 2, s / 2, h - s, h - s) + draw.DrawText(text, "DermaDefault", 5, 2, color_black, TEXT_ALIGN_BOTTOM) + end + for i = 2, #tabs do + v = tabs[i] + local b = vgui_Create("DButton", p_left) + b:Dock(TOP) + b:SetTall(40) + b:SetText("") + b.icon = v[3] + b.text = niceName( language.GetPhrase(tabs[i][2]) ) + b.sTab = v[1] + b.Paint = side_button + p_left.buttons[string.lower(tabs[i][1])] = b + function b:DoClick() + switch(self.sTab, p_right.sub,p_left.buttons, p._cookie) + end + end + + -- Move changelog down + if p_left.buttons["changelog"] then + p_left.buttons["changelog"]:Dock(BOTTOM) + p_left.buttons["changelog"]:SetZPos(1) + end + + -- Add workshop button + local b = vgui_Create("DButton", p_left) + b:Dock(BOTTOM) + b:SetTall(40) + b:SetText("") + b.icon = tabs[1][3] + b.text = "Workshop" + b.Paint = side_button + b.r = 180 + if StormFox2.NewVersion() then + function b:PaintOver(w,h) + surface.SetDrawColor(color_white) + surface.SetMaterial(n_bm) + if self:IsHovered() then + local dif = math.abs(math.AngleDifference(self.r, 0)) + 5 + self.r = (self.r + math.max(0.01,dif) * FrameTime() * 5) % 360 + else + self.r = 180 + end + surface.DrawTexturedRectRotated(w - 20, h / 2, 20, 20, self.r) + --surface.DrawRect( - w,0,w,h - 1) + draw.DrawText( StormFox2.NewVersion() , "SF_Menu_H2", w - 34 , h / 2 - 8, color_black, TEXT_ALIGN_RIGHT) + end + end + p_left.buttons["workshop"] = b + function b:DoClick() + gui.OpenURL( StormFox2.WorkShopURL ) + end + if not StormFox2.WorkShopURL then + b:SetDisabled(true) + end + + do + local b = vgui_Create("DButton", p_left) + b:Dock(BOTTOM) + b:SetTall(40) + b:SetText("") + b.icon = Material("stormfox2/discord.png") + b.text = "Discord" + b.Paint = side_button + b.r = 180 + p_left.buttons["discord"] = b + function b:DoClick() + gui.OpenURL( "https://discord.gg/mefXXt4u9E" ) + end + end + + local used = {} + function p:AddSetting( sName, group, _type, sDesc ) + if not group then + group = select(2, StormFox2.Setting.GetType(sName)) + end + if not group then + group = "misc" + elseif not p_right.sub[group] then + group = "misc" + end + local board = p_right.sub[group] + return board:AddSetting( sName, _type, sDesc ) + end + function p:MarkUsed( sName ) + used[sName] = true + end + for sBName, pnl in pairs(p_right.sub) do + pnl.sBName = string.lower(sBName) + pnl._other = false + function pnl:AddSetting( sName, _type, sDesc ) + if self._other then + self:AddTitle("#other", true) + pnl._other = false + end + local mul = type( sName ) == "table" + if not _type then + _type = StormFox2.Setting.GetType(mul and sName[1] or sName) + end + local setting = addSetting(sName, self, _type, sDesc) + if not setting then return end + if not mul then + if not search_tab["sf_" .. sName] or not self._isstart then + search_tab["sf_" .. sName] = {self.sBName, setting, s_mat} + end + used[sName] = true + else + for k,v in ipairs(sName) do + if not search_tab["sf_" .. v] or not self._isstart then + search_tab["sf_" .. v] = {self.sBName, setting, s_mat} + end + used[v] = true + end + end + --local setting = _type == "boolean" and vgui_Create("SFConVar_Bool", board) or vgui_Create("SFConVar", board) + setting:Dock(TOP) + self:AddItem(setting) + return setting + end + function pnl:AddTitle( sName, bIgnoreSearch ) + local dL = vgui_Create("SFTitle", self) + local text = niceName( language.GetPhrase(sName) ) + dL:SetText( text ) + dL:SetDark(true) + dL:SetFont("SF_Menu_H2") + dL:SizeToContents() + dL:Dock(TOP) + dL:DockMargin(5,self._title and 20 or 0,0,0) + self._title = true + self:AddItem(dL) + if (not search_tab[text] or not self._isstart) and #sName > 0 and not bIgnoreSearch then + search_tab[text] = {self.sBName, dL, t_mat} + end + return dL + end + function pnl:MarkUsed( sName ) + p:MarkUsed( sName ) + end + end + -- Make the layout + for _,v in ipairs(tabs) do + if not v[4] then continue end + local b = p_right.sub[string.lower(v[1])] + if not b then continue end + v[4]( b ) + end + + -- Add all other settings + for sBName, pnl in pairs(p_right.sub) do + pnl._other = true + end + for _, sName in ipairs( setting_list ) do + if used[sName] then continue end + p:AddSetting( sName ) + end + -- If there are empty setting-pages, remove them + for sBName, pnl in pairs(p_right.sub) do + local n = #pnl:GetChildren()[1]:GetChildren() + if n > 0 then continue end + local b = self.p_left.buttons[sBName] + if IsValid(b) then + b:SetDisabled(true) + end + local p = self.p_right.sub[string.lower(sBName)] + if IsValid(p) then + p:SetDisabled(true) + end + end + -- Add space at bottom + for sBName, pnl in pairs(p_right.sub) do + pnl.bottom = pnl:AddTitle("") + end + end + function PANEL:SetCookie( sCookie ) + self._cookie = sCookie + local selected = cookie.GetString(sCookie, "start") or "start" + if not self.p_right.sub[selected] then -- Unknown page, set it to "start" + selected = "start" + end + switch(selected, self.p_right.sub,self.p_left.buttons, sCookie) + end + function PANEL:Select( str_page ) + switch(str_page, self.p_right.sub,self.p_left.buttons, self._cookie) + end + derma.DefineControl( "SF_Menu", "", PANEL, "DFrame" ) +end + +--StormFox2.Menu.OpenController() + +-- Test vgui objects +if true then return end + +if SF_MENU_TEST then SF_MENU_TEST:Remove() end +SF_MENU_TEST = vgui.Create("DFrame") +SF_MENU_TEST:MakePopup() +SF_MENU_TEST:SetSize(600,500) +SF_MENU_TEST:SetPos(320,ScrH() / 2 - 100) + +local function Add(_type, _setting) + local setting = vgui.Create(_type, SF_MENU_TEST) + setting:SetSetting(_setting) + setting:SetSize(50,20) + setting:Dock(TOP) + return setting +end +Add("SF_Setting_Bool", "footprint_enabled") +Add("SF_Setting_String", "footprint_enabled") +Add("SF_Setting_Float", "overwrite_extra_darkness") +Add("SF_Setting_Int", "window_distance"):SetTemperature(true) +Add("SF_Setting_FloatSpecial", "window_distance") +Add("SF_Setting_Enum", "12h_display") +Add("SF_Setting_Time", "sunrise") +Add("SF_Setting_TimeToggle", "sunrise") diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_cami.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_cami.lua new file mode 100644 index 0000000..40136ee --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_cami.lua @@ -0,0 +1,528 @@ +--[[ +CAMI - Common Admin Mod Interface. +Makes admin mods intercompatible and provides an abstract privilege interface +for third party addons. + +IMPORTANT: This is a draft script. It is very much WIP. + +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 = 20150902.1 + +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 usergroupName1 + 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. + Note: this function does NOT return an immediate result! + The result is in the callback! + + 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) + 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. + 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: + None, the answer is given in the callback function in order to allow + for the admin mod to perform e.g. a database lookup. +]] +-- 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) + hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly, + privilegeName, callback, targetPly, extraInfoTbl) +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 pairs(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 \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_data.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_data.lua new file mode 100644 index 0000000..4f948d2 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_data.lua @@ -0,0 +1,198 @@ + +--[[ + Unlike SF1, this doesn't support networking. + + If timespeed changes, and some lerpvalues like temperature has been applied, we need to keep it syncronised. + That is why we now use Time value instead of CurTime + + Data.Set( sKey, zVar, nDelta ) Sets the data. Supports lerping if given delta. + Data.Get( sKey, zDefault ) Returns the data. Returns zDefault if nil. + Data.GetFinal( zKey, zDefault) Returns the data without calculating the lerp. + Data.IsLerping( sKey ) Returns true if the data is currently lerping. + + Hooks: + - StormFox2.data.change sKey zVar Called when data changed or started lerping. + - StormFox2.data.lerpstart sKey zVar Called when data started lerping + - StormFox2.data.lerpend sKey zVar Called when data stopped lerping (This will only be called if we check for the variable) +]] +StormFox2.Data = {} + +StormFox_DATA = {} -- Var +StormFox_AIMDATA = {} -- Var, start, end + +--[[TODO: There are still problems with nil varables. +]] + +-- Returns the final data. This will never lerp. +function StormFox2.Data.GetFinal( sKey, zDefault ) + if StormFox_AIMDATA[sKey] then + if StormFox_AIMDATA[sKey][1] ~= nil then + return StormFox_AIMDATA[sKey][1] + else + return zDefault + end + end + if StormFox_DATA[sKey] ~= nil then + return StormFox_DATA[sKey] + end + return zDefault +end + +local lerpCache = {} +local function calcFraction(start_cur, end_cur) + local n = CurTime() + if n >= end_cur then return 1 end + local d = end_cur - start_cur + return (n - start_cur) / d +end +do + local function isColor(t) + if type(t) ~= "table" then return false end + return t.r and t.g and t.b and true or false + end + local function LerpVar(fraction, from, to) + local t = type(from) + if t ~= type(to) then + --StormFox2.Warning("Can't lerp " .. type(from) .. " to " .. type(to) .. "!") + return to + end + if t == "number" then + return Lerp(fraction, from, to) + elseif t == "string" then + return fraction > .5 and to or from + elseif isColor(from) then + local r = Lerp(fraction, from.r, to.r) + local g = Lerp(fraction, from.g, to.g) + local b = Lerp(fraction, from.b, to.b) + local a = Lerp(fraction, from.a, to.a) + return Color(r,g,b,a) + elseif t == "vector" then + return LerpVector(fraction, from, to) + elseif t == "angle" then + return LerpAngle(fraction, from, to) + elseif t == "boolean" then + if fraction > .5 then + return to + else + return from + end + else + --print("UNKNOWN", t,"TO",to) + end + end + + -- Returns data + function StormFox2.Data.Get( sKey, zDefault ) + -- Check if lerping + local var1 = StormFox_DATA[sKey] + if not StormFox_AIMDATA[sKey] then + if var1 ~= nil then + return var1 + else + return zDefault + end + end + -- Check cache and return + if lerpCache[sKey] ~= nil then return lerpCache[sKey] end + -- Calc + local fraction = calcFraction(StormFox_AIMDATA[sKey][2],StormFox_AIMDATA[sKey][3]) + local var2 = StormFox_AIMDATA[sKey][1] + if fraction <= 0 then + return var1 + elseif fraction < 1 then + lerpCache[sKey] = LerpVar( fraction, var1, var2 ) + if not lerpCache[sKey] then + --print("DATA",sKey, zDefault) + --print(debug.traceback()) + end + return lerpCache[sKey] or zDefault + else -- Fraction end + StormFox_DATA[sKey] = var2 + StormFox_AIMDATA[sKey] = nil + hook.Run("StormFox2.data.lerpend",sKey,var2) + return var2 or zDefault + end + end + local n = 0 + -- Reset cache after 4 frames + hook.Add("Think", "StormFox2.resetdatalerp", function() + n = n + 1 + if n < 4 then return end + n = 0 + lerpCache = {} + end) +end + +-- Sets data. Will lerp if given delta. +function StormFox2.Data.Set( sKey, zVar, nDelta ) + -- Check if vars are the same + if StormFox_DATA[sKey] ~= nil and not StormFox_AIMDATA[sKey] then + if StormFox_DATA[sKey] == zVar then return end + end + -- If time is paused, there shouldn't be any lerping + if StormFox2.Time and StormFox2.Time.IsPaused and StormFox2.Time.IsPaused() then + nDelta = 0 + end + -- Delete old cache + lerpCache[sKey] = nil + -- Set to nil + if not zVar and zVar == nil then + StormFox_DATA[sKey] = nil + StormFox_AIMDATA[sKey] = nil + return + end + -- If delta is 0 or below. (Or no prev data). Set it. + if not nDelta or nDelta <= 0 or StormFox_DATA[sKey] == nil or StormFox2.Time.GetSpeed_RAW() <= 0 then + StormFox_AIMDATA[sKey] = nil + StormFox_DATA[sKey] = zVar + hook.Run("StormFox2.data.change",sKey,zVar) + return + end + -- Get the current lerping value and set that as a start + if StormFox_AIMDATA[sKey] then + StormFox_DATA[sKey] = StormFox2.Data.Get( sKey ) + end + StormFox_AIMDATA[sKey] = {zVar, CurTime(), CurTime() + nDelta, StormFox2.Time.GetSpeed_RAW()} + hook.Run("StormFox2.data.lerpstart",sKey,zVar, nDelta) + hook.Run("StormFox2.data.change", sKey, zVar, nDelta) +end + +function StormFox2.Data.IsLerping( sKey ) + if not StormFox_AIMDATA[sKey] then return false end + -- Check and see if we're done lerping + local fraction = calcFraction(StormFox_AIMDATA[sKey][2],StormFox_AIMDATA[sKey][3]) + if fraction < 1 then + return true + end + -- We're done lerping. + StormFox_DATA[sKey] = StormFox2.Data.GetFinal( sKey ) + StormFox_AIMDATA[sKey] = nil + lerpCache[sKey] = nil + hook.Run("StormFox2.data.lerpend",sKey,zVar) + return true +end + +function StormFox2.Data.GetLerpEnd( sKey ) + if not StormFox_AIMDATA[sKey] then return 0 end + return StormFox_AIMDATA[sKey][3] +end + +-- If time changes, we need to update the lerp values +hook.Add("StormFox2.Time.Changed", "StormFox2.datatimefix", function() + local nT = StormFox2.Time.GetSpeed_RAW() + local c = CurTime() + if nT <= 0.001 then return end + for k,v in pairs( StormFox_AIMDATA ) do + if not v[4] or v[4] == nT then continue end + local now_value = StormFox2.Data.Get( k ) + if not StormFox_AIMDATA[k] then continue end -- After checking the value, it is now gone. + if now_value then + StormFox_DATA[k] = now_value + end + local delta_timeamount = (v[3] - c) -- Time left + local delta_time = v[4] / nT -- Time multiplication + StormFox_AIMDATA[k][2] = c + StormFox_AIMDATA[k][3] = c + delta_timeamount * delta_time + StormFox_AIMDATA[k][4] = nT + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_downfall.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_downfall.lua new file mode 100644 index 0000000..b603878 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_downfall.lua @@ -0,0 +1,908 @@ +--[[------------------------------------------------------------------------- + downfall_meta:GetNextParticle() + +---------------------------------------------------------------------------]] +local max,min,t_insert,abs,clamp = math.max,math.min,table.insert,math.abs,math.Clamp + +-- Particle emitters +if CLIENT then + _STORMFOX_PEM = _STORMFOX_PEM or {} + local tab = { + __index = function(self, b) + if b == "3D" then + if IsValid(self._PEM) then return self._PEM end + self._PEM = ParticleEmitter(Vector(0,0,0),true) + self._PEM:SetNoDraw(true) + return self._PEM + elseif b == "2D" then + if IsValid(self._PEM2D) then return self._PEM2D end + self._PEM2D = ParticleEmitter(Vector(0,0,0)) + self._PEM2D:SetNoDraw(true) + return self._PEM2D + end + end + } + setmetatable(_STORMFOX_PEM, tab) + timer.Create("StormFox2.ParticleFlush", 60 * 2, 0, function() + if _STORMFOX_PEM._PEM2D then + _STORMFOX_PEM._PEM2D:Finish() + _STORMFOX_PEM._PEM2D = nil + end + if _STORMFOX_PEM._PEM then + _STORMFOX_PEM._PEM:Finish() + _STORMFOX_PEM._PEM = nil + end + end) +end + +-- Downfall mask +local mask = bit.bor( CONTENTS_SOLID, CONTENTS_MOVEABLE, CONTENTS_MONSTER, CONTENTS_WINDOW, CONTENTS_DEBRIS, CONTENTS_HITBOX, CONTENTS_WATER, CONTENTS_SLIME ) +local util_TraceHull,bit_band,Vector,IsValid = util.TraceHull,bit.band,Vector,IsValid + +StormFox2.DownFall = {} +StormFox2.DownFall.Mask = mask + +SF_DOWNFALL_HIT_NIL = -1 +SF_DOWNFALL_HIT_GROUND = 0 +SF_DOWNFALL_HIT_WATER = 1 +SF_DOWNFALL_HIT_GLASS = 2 +SF_DOWNFALL_HIT_CONCRETE = 3 +SF_DOWNFALL_HIT_WOOD = 4 +SF_DOWNFALL_HIT_METAL = 5 + +local con = GetConVar("sv_gravity") +-- Returns the gravity +local function GLGravity() + if con then + return con:GetInt() / 600 + else -- Err + return 1 + end +end + +-- This will return the gravity on the server. +StormFox2.DownFall.GetGravity = GLGravity +-- game.GetTimeScale() + +-- Traces +do + local t = { + start = Vector(0,0,0), + endpos = Vector(0,0,0), + maxs = Vector(1,1,4), + mins = Vector(-1,-1,0), + mask = mask + } + local function GetViewEntity() + if SERVER then return end + return LocalPlayer():GetViewEntity() or LocalPlayer() + end + -- MaterialScanner + local c_t = {} + -- Errors + c_t["**displacement**"] = "default" + c_t["**studio**"] = "default" + c_t["default_silent"] = "default" + c_t["floatingstandable"] = "default" + c_t["item"] = "default" + c_t["ladder"] = "default" + c_t["no_decal"] = "default" + c_t["player"] = "default" + c_t["player_control_clip"] = "default" + -- Concrete / Rock / Ground + c_t["boulder"] = "concrete" + c_t["concrete_block"] = "concrete" + c_t["gravel"] = "concrete" + c_t["rock"] = "concrete" + c_t["brick"] = "concrete" + c_t["baserock"] = "concrete" + c_t["dirt"] = "concrete" + c_t["grass"] = "concrete" + c_t["gravel"] = "concrete" + c_t["mud"] = "concrete" + c_t["quicksand"] = "concrete" + c_t["slipperyslime"] = "concrete" + c_t["sand"] = "concrete" + c_t["antlionsand"] = "concrete" + -- Metal + c_t["canister"] = "metal" + c_t["chain"] = "metal" + c_t["chainlink"] = "metal" + c_t["paintcan"] = "metal" + c_t["popcan"] = "metal" + c_t["roller"] = "metal" + -- Wood + c_t["roller"] = "wood" + c_t["roller"] = "metal" + c_t["roller"] = "metal" + c_t["roller"] = "metal" + c_t["roller"] = "metal" + + -- Convert surfaceprops + local function ConvertSurfaceProp( sp ) + sp = sp:lower() + if c_t[sp] then + return c_t[sp] + end + -- Guess + if string.find( sp, "window", 1, true) or string.find( sp, "glass", 1, true) then + return "glass" + end + if string.find( sp, "wood",1,true) then + return "wood" + end + if string.find( sp, "metal",1,true) then + return "metal" + end + if string.find( sp, "concrete",1,true) then + return "concrete" + end + if string.find( sp, "water",1,true) then + return "water" + end + return "default" + end + local m_t = {} + local function SurfacePropIDToHIT( id ) + if not id then return end + if id < 0 then return SF_DOWNFALL_HIT_GROUND end + if m_t[ id ] then return m_t[ id ] end + -- ConvertSurfaceProp + local name = util.GetSurfacePropName( id ) + name = ConvertSurfaceProp( name ) + if name == "default" then + m_t[ id ] = SF_DOWNFALL_HIT_GROUND + elseif name == "metal" then + m_t[ id ] = SF_DOWNFALL_HIT_METAL + elseif name == "water" then + m_t[ id ] = SF_DOWNFALL_HIT_WATER + elseif name == "wood" then + m_t[ id ] = SF_DOWNFALL_HIT_WOOD + elseif name == "glass" then + m_t[ id ] = SF_DOWNFALL_HIT_GLASS + elseif name == "concrete" then + m_t[ id ] = SF_DOWNFALL_HIT_CONCRETE + else + m_t[ id ] = SF_DOWNFALL_HIT_GROUND + end + return m_t[ id ] + end + local function MaterialToHIT( str ) + if m_t[ str ] then return m_t[ str ] end + local sp = Material( str ):GetKeyValues()["$surfaceprop"] + if sp and #sp > 0 then + sp = ConvertSurfaceProp( sp ) + else + sp = ConvertSurfaceProp( str ) + end + if sp == "default" then + m_t[ str ] = SF_DOWNFALL_HIT_GROUND + elseif sp == "metal" then + m_t[ str ] = SF_DOWNFALL_HIT_METAL + elseif sp == "water" then + m_t[ str ] = SF_DOWNFALL_HIT_WATER + elseif sp == "wood" then + m_t[ str ] = SF_DOWNFALL_HIT_WOOD + elseif sp == "glass" then + m_t[ str ] = SF_DOWNFALL_HIT_GLASS + elseif sp == "concrete" then + m_t[ str ] = SF_DOWNFALL_HIT_CONCRETE + else + m_t[ str ] = SF_DOWNFALL_HIT_GROUND + end + return m_t[ str ] + end + local function IsMaterialEmpty( t ) + return t.HitTexture == "TOOLS/TOOLSINVISIBLE" or t.HitTexture == "**empty**" or t.HitTexture == "TOOLS/TOOLSNODRAW" + end + -- Returns a raindrop pos from a sky position. #2 is hittype: -1 = no hit, 0 = ground, 1 = water, 2 = glass. + local function TraceDown(pos, norm, nRadius, filter) + nRadius = nRadius or 1 + if not pos or not norm then return end + t.start = pos + t.endpos = pos + norm * 262144 + t.maxs.x = nRadius + t.maxs.y = nRadius + t.mins.x = -nRadius + t.mins.y = -nRadius + t.filter = filter or GetViewEntity() + local tr = util_TraceHull(t) + if tr and (tr.AllSolid or tr.StartSolid or tr.HitSky) then + return + elseif nRadius > 5 and tr.Fraction * -norm.z < 0.0005 then -- About 150 hammer-units + -- print("Dis: " .. 262144 * tr.Fraction * -norm.z) + return + end + if not tr or not tr.Hit then + return tr.HitPos, SF_DOWNFALL_HIT_NIL + elseif not IsValid(tr.Entity) then + return tr.HitPos, SurfacePropIDToHIT( tr.SurfaceProps or -1 ) or MaterialToHIT( tr.HitTexture ) or SF_DOWNFALL_HIT_GROUND + else + local mat = tr.Entity:GetMaterial():lower() + local mod = tr.Entity:GetModel():lower() + return tr.HitPos, MaterialToHIT( #mat > 0 and mat or mod ) + end + return tr.HitPos, SF_DOWNFALL_HIT_GROUND, tr.HitNormal + end + StormFox2.DownFall.TraceDown = TraceDown + + -- Returns the skypos. If it didn't find the sky it will return last position as #2 + local function FindSky(vFrom, vNormal, nTries) + local last,lastFakeSky + for i = 1,nTries do + local t = util.TraceLine( { + start = vFrom, + endpos = vFrom + vNormal * 262144, + mask = MASK_SOLID_BRUSHONLY + } ) + if not t.Hit then break end -- Just empty void from this point on + -- Check if we're in the void + if t.HitPos.z > 32768 then break end -- Max map-size is 32768^2 + --if t.HitTexture == "TOOLS/TOOLSINVISIBLE" then return end + -- We found the sky! + if t.HitSky then + -- In case there is the tiniest gab between skybox and the last brush, ignore it. + if t.StartSolid then + local zDis = (t.HitPos.z - vFrom.z ) * (t.Fraction - t.FractionLeftSolid) + if zDis < 1 then + return nil, t.HitPos + end + end + return t.HitPos + end + -- Check for fake sky. Some maps don't have a brush called "skybox" .. for some reason. + if IsMaterialEmpty(t) and not t.HitSky then + -- Check if far away + lastFakeSky = lastFakeSky or t.HitPos + else + lastFakeSky = nil + end + last = t.HitPos + vFrom = t.HitPos + vNormal + end + if lastFakeSky then + return lastFakeSky + end + return nil, last + end + StormFox2.DownFall.FindSky = FindSky + + -- Locates the skybox above vFrom and returns a raindrop pos. #2 is hittype: -2 No sky, -1 = no hit/invald, 0 = ground, 1 = water, 2 = glass, #3 is hitnormal + function StormFox2.DownFall.CheckDrop(vFrom, vNorm, nRadius, filter) + t.mask = mask + local sky,_ = FindSky(vFrom, -vNorm, 7) + if not sky then return vFrom, -2 end -- Unable to find a skybox above this position + return TraceDown(sky + vNorm * (nRadius * 4), vNorm * 262144, nRadius, filter) + end + + -- Does the same as StormFox2.DownFall.CheckDrop, but will cache + local t_cache = {} + local t_cache_hit = {} + local c_i = 0 + function StormFox2.DownFall.CheckDropCache( ... ) + local pos,n = StormFox2.DownFall.CheckDrop( ... ) + if pos and n > -1 then + c_i = (c_i % 10) + 1 + t_cache[c_i] = pos + t_cache_hit[c_i] = pos + return pos,n + end + if #t_cache < 1 then return pos,n end + local n = math.random(1, #t_cache) + return t_cache[n],t_cache_hit[n] + end + + local cos,sin,rad = math.cos, math.sin, math.rad + -- Calculates and locates a downfall-drop infront/nearby of the client. + -- #1 = Hit Position, #2 hitType, #3 The offset from view, #4 hitNormal + local vZ = Vector(0,0,0) + function StormFox2.DownFall.CalculateDrop( nDis, nSize, nTries, vNorm, ignoreVel, nMaxDistance, tTemplate ) + vNorm = vNorm or StormFox2.Wind.GetNorm() + local view = StormFox2.util.GetCalcView() + local v_vel = StormFox2.util.ViewEntity():GetVelocity() or vZ + local v_pos = (view.pos and Vector(view.pos.x, view.pos.y, view.pos.z) or vZ) + Vector(vNorm.x,vNorm.y,0) * -tTemplate:GetSpeed() * -200 + if not ignoreVel then + v_pos = v_pos + Vector(v_vel.x,v_vel.y,0) / 2 + end + for i = 1, nTries do + -- Get a random angle + local d = math.Rand(nDis / 200,4) + local deg = math.random(d * 45) + if math.random() > 0.5 then + deg = -deg + end + -- Calculate the offset + local yaw = rad(view.ang.y + deg) + local offset = v_pos + Vector(cos(yaw),sin(yaw)) * nDis + local pos, n, hitNorm = StormFox2.DownFall.CheckDrop( offset, vNorm, nSize) + if pos and n > -2 and pos:DistToSqr(v_pos) < 11000000 then -- TODO: Why does this happen? Position shouldn't be that waaaay away. + local bRandomAge = not ignoreVel and nDis > nMaxDistance - v_vel:Length2D() + return pos,n,offset, hitNorm, bRandomAge + end + end + end +end + +if CLIENT then + local render_SetMaterial, render_DrawBeam, render_DrawSprite = render.SetMaterial, render.DrawBeam, render.DrawSprite + + -- Creats a regular particle and returns it + function StormFox2.DownFall.AddParticle( sMaterial, vPos, bUse3D ) + if bUse3D then + return _STORMFOX_PEM["3D"]:Add( sMaterial, vPos ) + end + return _STORMFOX_PEM["2D"]:Add( sMaterial, vPos ) + end + + -- Particle Template. Particles "copy" these values when they spawn. + local pt_meta = {} + pt_meta.__index = pt_meta + debug.getregistry()["SFParticleTemplate"] = pt_meta + pt_meta.MetaName = "ParticleTemplate" + pt_meta.g = 1 + pt_meta.r_H = 400 -- Default render height + pt_meta.r_H2 = 800 -- Default max render height (This is used to kill particles) + AccessorFunc(pt_meta, "iMat", "Material") + AccessorFunc(pt_meta, "w", "Width") + AccessorFunc(pt_meta, "h", "Height") + AccessorFunc(pt_meta, "c", "Color") + AccessorFunc(pt_meta, "g", "Speed") + AccessorFunc(pt_meta, "r_H", "RenderHeight") + AccessorFunc(pt_meta, "i_G", "IgnoreGravity") + -- Think function .. This will be called each time SmartTemplate gets called + function pt_meta:Think() end + -- Sets the alpha + function pt_meta:SetAlpha( nAlpha ) + if self.c == color_white then + self.c = Color(255,255,255) + end + self.c.a = nAlpha + end + function pt_meta:GetAlpha() + return self.c.a + end + function pt_meta:SetSize( nWidth, nHeight ) + self.w = nWidth + self.h = nHeight + end + function pt_meta:SetRenderHeight( f ) + self.r_H = f + self.r_H2 = f * 2 + end + function pt_meta:GetSize() + return self.w or 1, self.h or 1 + end + -- On hit (Overwrite it) + function pt_meta:OnHit( vPos, vNormal, nHitType, zPart ) + end + -- On Explosion + function pt_meta:OnExplosion( vExposionPos, nDistance, iRange, iMagnitude) + end + function pt_meta:GetNorm() + return self.vNorm or StormFox2.Wind.GetNorm() or Vector(0,0,-1) + end + function pt_meta:SetNorm( vNorm ) + self.vNorm = vNorm + end + function pt_meta:SetRandomAngle( r_a ) + self._ra = r_a + end + function pt_meta:SetRoll( nRoll ) + self._roll = nRoll + end + function pt_meta:SetFadeIn( b ) + self._bFIn = b + end + function pt_meta:_REM() + local v = self.data or self + v.num = v.num - 1 + end + function StormFox2.DownFall.CreateTemplate(sMaterial, bBeam, bFollow) + local t = {} + setmetatable(t,pt_meta) + t:SetMaterial(sMaterial) + t.c = color_white + t.bBeam = bBeam or false + t.w = 32 + t.h = 32 + t.g = 1 + t.r_H = 400 + t.r_H2 = 800 + t.i_G = false + t.bFollow = bFollow + t.num = 0 + return t + end + -- Particles + local p_meta = {} + debug.getregistry()["SFParticle"] = p_meta + p_meta.__index = function(self, key) + return p_meta[key] or self.data[key] + end + -- Creates a particle from the template. Can return nil if something happen + function pt_meta:CreateParticle( vEndPos, vNorm, hitType, hitNorm, maxDistance ) + local view = StormFox2.util.GetCalcView().pos + local z_view = view.z + local t = {} + t.data = self + t.vNorm = vNorm + t.endpos = vEndPos + t.hitType = hitType or SF_DOWNFALL_HIT_NIL + t.hitNorm = hitNorm or Vector(0,0,-1) + --t.to = CurTime() + setmetatable(t, p_meta) + local cG = self.g + if not t:GetIgnoreGravity() then + cG = self.g * GLGravity() + end + local dir_z = min(t:GetNorm().z,-0.1) -- Winddir should always be down + -- Calc the starting position. + if cG > 0 then -- Start from the sky and down + local l = z_view + (t.r_H or 200) - t.endpos.z + math.Rand(0, t.h) + t.curlength = l * -dir_z + if t.curlength <= 0 then -- Something went wrong + self.h = 0 + return + end + elseif cG < 0 then -- Start from ground and up + local l = max(0, z_view - (t.r_H or 200) - t.endpos.z) -- Ground or below renderheight + t.curlength = l * -dir_z + end + if self.bFollow then + t.endpos = vEndPos - view + t.bFollow = true + end + + -- Check if outside + local p = t:CalcPos() + if maxDistance and p:Distance(Vector(view.x, view.y, p.z)) > maxDistance then + return + end + self.num = self.num + 1 + return t, (t.r_H or 200) * 2 / abs( cG ) -- Secondary is how long we thing it will take for the particle to die. We also want this to be steady. + end + -- Returns the amount of particles spawned + function pt_meta:GetNumber() + return self.num or 0 + end + -- Calculates the current position of the particle + function p_meta:CalcPos() + self.pos = self.endpos - self:GetNorm() * self.curlength + return self.pos + end + -- Returns the current position + function p_meta:GetPos() + if self.pos then return self.pos end + return self:CalcPos() + end + -- Sets the alpha of the particle, but won't overwrite template's color. + function p_meta:SetAlpha( nAlpha ) + if not rawget(self, c) then -- Don't overwrite the template alpha. Create our own color and then modify it. + self.c = Color(self.c.r, self.c.g, self.c.b, nAlpha) + else + self.c.a = nAlpha + end + end + function p_meta:GetHitNormal() + return self.hitNorm or Vector(0,0,-1) + end + -- Sets the "age" of the particle between 0 - 1 + function p_meta:SetAge( f ) + self.curlength = self.curlength * f + end + -- + function p_meta:GetDistance() + if self._distance then + return self._distance + end + --print("ERROR") + end + -- Sets the max-distance from view + function p_meta:SetMaxDistance( f ) + self.mDis2Sqr = f ^ 2 + end + -- Renders the particles + function p_meta:Render(viewPos) + local pos = self:GetPos() + if self.bFollow then + pos = pos + viewPos + end + if self._renh and self._bFIn and self._renh < 0.5 then -- We're fading out + self.cL = Color(self.c.r, self.c.g, self.c.b, self._renh * 2 * self.c.a) + elseif self._bFIn and (self._bFInA or 0) < 1 then -- We're fading in + self._bFInA = min((self._bFInA or 0) + FrameTime(), 1) + self.cL = Color(self.c.r, self.c.g, self.c.b, self._bFInA * self.c.a) + else + self.cL = nil + end + render_SetMaterial(self.iMat) + if self.bBeam then + if self._renh then + if self._renh <= 0 then return end + local sr = 1 - self._renh + local sh = self:GetNorm() * self.h * 0.91 + render_DrawBeam(pos - sh, pos - sh * sr, self.w, 0, self._renh, self.cL or self.c) + else + render_DrawBeam(pos - self:GetNorm() * self.h, pos, self.w, 0, 1, self.cL or self.c) + end + else + render_DrawSprite(pos, self.w, self.h, self.c) + end + end + -- Checks the view + function p_meta:IsInsideView() + return self._iv == nil and true or self._iv + end + + local function IVCheck( view, part ) + local vN = (part:GetPos() - view.pos) + vN:Normalize() + local dot = vN:Dot(view.ang:Forward()) + part._iv = dot > -( view.fov / 90 ) + 1 + end + + --[[ + StormFox2.DownFall.CreateTemplate(sMaterial, bBeam) + Creates a template. This particle-data is shared between all other particles that are made from this. + Do note that you can overwrite this data on each other individual particle as well. + + template:CreateParticle( vPos, startlength ) + Creates a particle from the template. This particle can also be modified using the template functions. + ]] + + -- Moves and kills the particles + local t_sfp = {} + local e_check = 0 + local function ParticleTick() + if #t_sfp < 1 then return end + if e_check > #t_sfp then + e_check = 0 + end + local view = StormFox2.util.GetCalcView() + local viewp = view.pos + local v_vel = StormFox2.util.ViewEntity():GetVelocity() + local v_l = max(v_vel.x,v_vel.y) + v_vel = v_vel / 4 + viewp + local z_view = viewp.z + local fr = FrameTime() * 600 -- * game.GetTimeScale() + local die = {} + local gg = GLGravity() -- Global Gravity + local e_check_n = e_check + 50 + for n,t in ipairs(t_sfp) do + local part = t[2] + -- The length it moves (Could also be negative) + local move = part.g + if not part:GetIgnoreGravity() then + move = part.g * gg + end + if e_check_n < n and part.mDis2Sqr and not part.bFollow then -- Check the max-distance + if (part:GetPos() - v_vel):Length2DSqr() > part.mDis2Sqr + v_l then + part.hitType = SF_DOWNFALL_HIT_NIL + die[#die + 1] = n + continue + end + IVCheck( view, part) + end + part.curlength = part.curlength - move * fr + -- Check if it dies + if move > 0 then + local zp = part:CalcPos().z + if zp < part.endpos.z then + -- Hit ground + if zp < part.endpos.z - part.h or part.h < 10 then + die[#die + 1] = n + else + part._renh = 1 - (part.endpos.z - zp) / (part.h * 0.9) + end + elseif zp < z_view - part.r_H or zp > z_view + part.r_H2 + part.h then + -- Die in air + part.hitType = SF_DOWNFALL_HIT_NIL + die[#die + 1] = n + end + elseif move < 0 then -- It moves up in the sky. Should allways be hittype SF_DOWNFALL_HIT_NIL + if part:CalcPos().z > z_view + part.r_H then + -- Die + die[#die + 1] = n + end + end + end + e_check = e_check + 50 + -- Kill particles + for i = #die, 1, -1 do + local t = table.remove(t_sfp, die[i]) + local part = t[2] + part:_REM() + --print(" Real Death: ", CurTime() - (part.to or 0)) + if part.hitType ~= SF_DOWNFALL_HIT_NIL and part.OnHit then + part:OnHit( part.endpos, part:GetHitNormal(), part.hitType, part ) + end + end + end + -- Renders all particles. t_sfp should be in render-order + local viewpos = Vector(0,0,0) + local function ParticleRender() + local v = StormFox2.util.GetCalcView().pos or EyePos() + viewpos.x = v.x + viewpos.x = v.y + for _,t in ipairs(t_sfp) do + -- render.DrawLine(t[2]:GetPos(), t[2].endpos, color_white, true) + if t[2]:IsInsideView() then + t[2]:Render(viewpos) + end + end + end + + -- Adds a particle. + function StormFox2.DownFall.AddTemplateSimple( tTemplate, vEndPos, hitType, hitNorm, nDistance, vNorm, maxDistance ) + local part = tTemplate:CreateParticle( vEndPos, vNorm, hitType, hitNorm, maxDistance ) + if not part then return end + if not nDistance then + local p = StormFox2.util.GetCalcView().pos + nDistance = Vector(p.x,p.y,vEndPos.z):Distance( vEndPos ) + end + -- Add by distance + local n = #t_sfp + local t = {nDistance, part} + if n < 1 then + t_insert(t_sfp, t) + else + for i=1,n do + if nDistance > t_sfp[i][1] then + t_insert(t_sfp, i, t) + return part + end + end + t_insert(t_sfp, n, t) + end + return part + end + + -- Tries to add a particle. Also has cache build in. + local v_d = Vector(0,0,-1) + function StormFox2.DownFall.AddTemplate( tTemplate, nMaxDistance, nDistance, traceSize, vNorm ) + vNorm = vNorm or StormFox2.Wind.GetNorm() + if tTemplate._ra then -- Random angle + vNorm = Vector(vNorm.x, vNorm.y, vNorm.z) + Vector(math.Rand(-tTemplate._ra, tTemplate._ra),math.Rand(-tTemplate._ra, tTemplate._ra),0) + vNorm:GetNormal() + end + local vEnd, nHitType, vCenter, hitNorm, bRandomAge = StormFox2.DownFall.CalculateDrop( nDistance, traceSize, 1, vNorm, tTemplate.bFollow, nMaxDistance, tTemplate ) + -- pos,n,offset, hitNorm + if not tTemplate.m_cache then tTemplate.m_cache = {} end + if not vEnd then + if tTemplate.m_cache and #tTemplate.m_cache > 0 then + local t = table.remove(tTemplate.m_cache, 1) + vEnd = t[1] + nHitType = t[2] + vCenter = t[3] + hitNorm = t[4] + nMaxDistance = t[5] + vNorm = t[6] + else + return false + end + else + if t_insert(tTemplate.m_cache, {vEnd,nHitType, vCenter, hitNorm, nMaxDistance, vNorm}) > 10 then + table.remove(tTemplate.m_cache,1) + end + end + -- Large particles from an angle looks odd. + if tTemplate.w >= 20 and (vNorm.x ~= 0 or vNorm.y ~= 0) then + local l = 1 - vNorm:Dot(v_d) --* tTemplate.w * 1.1 + vEnd = vEnd + vNorm * -l * 500 + end + local part = StormFox2.DownFall.AddTemplateSimple( tTemplate, vEnd, nHitType, hitNorm, nDistance, vNorm, nMaxDistance and nMaxDistance + 50 ) + if not part then return false end + if bRandomAge or true then + local n = part.h / (part.r_H * 2) + part:SetAge( math.Rand(-n, 1)) + end + if nMaxDistance then + part:SetMaxDistance( nMaxDistance + 50 ) + end + return part + end + + -- Max particles by quality setting. + -- 7 = 1 + -- 0 = 0.1 + local function max_particles() + local qt = StormFox2.Client.GetQualityNumber() + 0.2 + if qt >= 7 then return 1 end + if qt <= 0.2 then return 0.02 end + return qt / 7 + end + + local sm_timer = 0.05 + -- Returns the how many particles it should create pr 0.1 second + function StormFox2.DownFall.CalcTemplateTimer( tTemplate, nAimAmount ) + local speed = abs( tTemplate.g * GLGravity() ) * 600 + -- print("FT",1 / FrameTime()) + -- print("nAimAmount: " .. nAimAmount) + -- print("nAimAmountPrT: " .. nAimAmount * FrameTime()) + -- print("SPEED", speed) + -- + local alive_time = tTemplate.r_H / speed -- How long would it be alive? (Only half the time, since players are usually are on the ground) + local a = nAimAmount / alive_time * sm_timer + return a * max_particles(), alive_time + end + + -- Automaticlly spawns particles and returns a table of them. + -- This will spawn particles 10 times pr second + local emp_t = {} + function StormFox2.DownFall.SmartTemplate( tTemplate, nMinDistance, nMaxDistance, nAimAmount, traceSize, vNorm, fFunc ) + if tTemplate.Think then + tTemplate:Think() + end + local am = tTemplate:GetNumber() + if am >= nAimAmount then return emp_t end + if tTemplate.s_timer and tTemplate.s_timer > CurTime() then return emp_t end + tTemplate.s_timer = CurTime() + sm_timer + local b = min(1, am / nAimAmount) -- Full amount + local n,at = StormFox2.DownFall.CalcTemplateTimer( tTemplate, nAimAmount )-- How many times this need to run pr tick + local t = {} + local _d = math.Rand(nMaxDistance, nMinDistance) + for i = 1, n do + if i%7 == 0 then + _d = math.Rand(nMaxDistance, nMinDistance) + end + local p = StormFox2.DownFall.AddTemplate( tTemplate, nMaxDistance, _d, traceSize or 5, vNorm ) + if p then + p._distance = _d + p:SetAge( 1 + math.Rand(0,at) ) + t_insert(t, p) + end + end + return t + end + + hook.Add("Think","StormFox2.Downfall.Tick", ParticleTick) + hook.Add("PostDrawTranslucentRenderables", "StormFox2.Downfall.Render", function(depth,sky) + if depth or sky then return end -- Don't render in skybox or depth. + -- Render particles on the floor + if _STORMFOX_PEM._PEM2D then + _STORMFOX_PEM._PEM2D:Draw() + end + if _STORMFOX_PEM._PEM then + _STORMFOX_PEM._PEM:Draw() + end + if LocalPlayer():WaterLevel() >= 3 then return end -- Don't render SF particles under wanter. + ParticleRender() -- Render sf particles + end) + + hook.Add("RenderScreenspaceEffects", "StormFox2.Downfall.DepthRender", function() + if render.GetDXLevel() < 95 then return end + if LocalPlayer():WaterLevel() >= 3 then return end -- Don't render SF particles under wanter. + local obj = StormFox2.Setting.GetObject("depthfilter") + if not obj then return end + if not obj:GetValue() then return end + hook.Run("StormFox2.DepthFilterRender") -- Render depthfilter + end) + + function StormFox2.DownFall.DebugList() + return t_sfp + end + + hook.Add("StormFox2.Entitys.OnExplosion", "StormFox2.Downfall.Explosion", function(pos, iRadius, iMagnitude) + for i = #t_sfp, 1, -1 do + local part = t_sfp[i][2] + local dis = part:GetPos():Distance(pos) + if dis * 1.2 > iRadius then continue end -- Adding just a bit more + if part.OnExplosion then + part:OnExplosion(pos, clamp(1 - (dis / iRadius), 0, 1),iRadius, iMagnitude) + end + part:_REM() + table.remove(t_sfp, i) + end + end) +end + +hook.Add("stormfox2.postlib", "AddDepthSetting", function() + StormFox2.Setting.AddSV("depthfilter",true,nil,"Effects") +end) + +-- 2D +if CLIENT and false then + local meta = {} + AccessorFunc(meta, "_x", "X") + AccessorFunc(meta, "_y", "y") + AccessorFunc(meta, "_w", "Width") + AccessorFunc(meta, "_h", "Height") + AccessorFunc(meta, "_mat", "Material") + AccessorFunc(meta, "_a", "Angle") + AccessorFunc(meta, "_l", "Life") + AccessorFunc(meta, "_as", "AngleSpeed") + AccessorFunc(meta, "_c", "Color") + AccessorFunc(meta, "_g", "Weight") -- Will make gravity effect it. + function meta:SetSize( nSize ) + self:SetWidth( mSize ) + self:SetHeight( mSize ) + end + function meta:SetVelocity( nXSpeed, nYSpeed ) + self._vx = nXSpeed + self._vy = nYSpeed + end + function meta:SetFade( nBool ) + self._f = nBool + end + function meta:SetPos( x, y ) + self._x = x + self._y = y + end + function StormFox2.DownFall.CreateScreenParticle(mMat) + local t = {} + -- Mat + t._mat = mMat + -- Pos + t._x = 0 + t._y = 0 + t._w = 32 + t._h = 32 + -- Ang + t._a = 0 + t._as = 0 + -- Vel + t._vx = 0 + t._vy = 0 + -- Grav + t._g = 0 + -- Should fade + t._f = true + -- Col + t._c = color_white + setmetatable(t, meta) + return t + end + function StormFox2.DownFall.AddScreenParticle(obj2D, posx, posy) + local t = {} + -- Pos + t._x = posx + t._y = posy + t._w = 32 + t._h = 32 + -- Ang + t._a = 0 + t._as = 0 + -- Vel + t._vx = 0 + t._vy = 0 + -- Grav + t._g = 0 + -- Should fade + t._f = true + -- Col + t._c = color_white + setmetatable(t, meta) + table.insert(_STORMFOX_SCE2d, {t, CurTime()}) + return t + end + hook.Add("HUDPaintBackground", "StormFox2.Downfall.2D_Rain", function() + if #_STORMFOX_SCE2d < 1 then return end + -- In + if LocalPlayer() and LocalPlayer():WaterLevel() >= 3 then + + end + + local del = {} + local screenGrav = 1 + for id, p in ipairs( _STORMFOX_SCE2d ) do + local o = p[1] + local t = CurTime() - p[4] + o:GetLife() + -- Check if dead + if t <= 0 or p[3] > ScrH() then -- Dead or outside + table.insert(del, id) + continue + end + -- Move + local g = o._g and o._g * screenGrav or 0 + _STORMFOX_SCE2d[id][2] = p[2] + o._vx * FrameTime() + _STORMFOX_SCE2d[id][3] = p[3] + (o._vy + g) * FrameTime() + o:SetAngle( o:GetAngle() + o._ys ) + -- Render + if o._f then + + end + surface.SetDrawColor(o._c.r, o._c.g, o._c.b, o._c.a) + + end + end) +end diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_mapglass.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_mapglass.lua new file mode 100644 index 0000000..f68bd0b --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_mapglass.lua @@ -0,0 +1,858 @@ +--[[------------------------------------------------------------------------- +CoffeeLib BSP @ Nak 2021 + +DO NOT USE THIS WITHOUT MY PERMISSON IN YOUR PROJECT! +If you wish the utilize the same functions, you can download CoffeeLib seperate to your server. + +CoffeeLib also come with more utility functions. + +---------------------------------------------------------------------------]] +-- Check for cache +--StormFox2.FileWrite("stormfox2/cache/" .. game.GetMap() .. ".dat", DATA) +StormFox2.Map = {} +SF_BSPDATA = SF_BSPDATA or {} +local function ReadVector(f) + return Vector( f:ReadFloat(), f:ReadFloat(), f:ReadFloat() ) +end +local CACHE_VERSION = 2 +local CACHE_FIL = "stormfox2/cache/" .. game.GetMap() .. ".dat" +local CRC = tonumber(file.Size( "maps/" .. game.GetMap() .. ".bsp","GAME" )) -- or tonumber(util.CRC(file.Read("maps/" .. game.GetMap() .. ".bsp","GAME"))) +local file = table.Copy(file) +local Vector = Vector +local Color = Color +local table = table.Copy(table) +local string = table.Copy(string) +local util = table.Copy(util) + +-- Enums +local NO_TYPE = -1 +local DIRTGRASS_TYPE = 0 +local ROOF_TYPE = 1 +local ROAD_TYPE = 2 +local PAVEMENT_TYPE = 3 + +-- Generator +local GetBSPData +do + local meta = {} + meta.__index = meta + + -- Local functions + local function ReadLumpHeader( BSP, f ) + local t = {} + if BSP.version ~= 21 or BSP._isL4D2 == false then + t.fileofs = f:ReadLong() + t.filelen = f:ReadLong() + t.version = f:ReadLong() + t.fourCC = f:ReadLong() + elseif BSP._isL4D2 == true then + t.version = f:ReadLong() + t.fileofs = f:ReadLong() + t.filelen = f:ReadLong() + t.fourCC = f:ReadLong() + else -- Try and figure it out + local fileofs = f:ReadLong() -- Version + local filelen = f:ReadLong() -- fileofs + local version = f:ReadLong() -- filelen + t.fourCC = f:ReadLong() + if fileofs <= 8 then + BSP._isL4D2 = true + t.version = fileofs + t.fileofs = filelen + t.filelen = version + else + BSP._isL4D2 = false + t.fileofs = fileofs + t.filelen = filelen + t.version = version + end + end + return t + end + + local function CreateStaticProp(f, version, m, staticSize) + local s = f:Tell() + local obj = {} + -- Version 4 + obj.Origin = ReadVector(f) -- Vector (3 float) 12 bytes + obj.Angles = Angle( f:ReadFloat(),f:ReadFloat(),f:ReadFloat() ) -- Angle (3 float) 12 bytes + -- Version 4 + obj.PropType = m[f:ReadUShort() + 1] -- unsigned short 2 bytes + obj.First_leaf = f:ReadUShort() -- unsigned short 2 bytes + obj.LeafCount = f:ReadUShort() -- unsigned short 2 bytes + obj.Solid = f:ReadByte() -- unsigned char 1 byte + obj.Flags = f:ReadByte() -- unsigned char 1 byte + obj.Skin = f:ReadLong() -- int 4 bytes + obj.FadeMinDist = f:ReadFloat() -- float 4 bytes + obj.FadeMaxDist = f:ReadFloat() -- float 4 bytes + obj.LightingOrigin = ReadVector(f) -- Vector (3 float) 12 bytes + -- 56 bytes used + -- Version 5 + if version >= 5 then + obj.ForcedFadeScale = f:ReadFloat() -- float 4 bytes + end + -- 60 bytes used + -- Version 6 and 7 + if version >= 6 and version <= 7 then + obj.MinDXLevel = f:ReadUShort() -- unsigned short 2 bytes + obj.MaxDXLevel = f:ReadUShort() -- unsigned short 2 bytes + -- Version 8 + elseif version >= 8 then + obj.MinCPULevel = f:ReadByte() -- unsigned char 1 byte + obj.MaxCPULevel = f:ReadByte() -- unsigned char 1 byte + obj.MinGPULevel = f:ReadByte() -- unsigned char 1 byte + obj.MaxGPULevel = f:ReadByte() -- unsigned char 1 byte + end + -- Version 7 + if version >= 7 then -- color32 ( 32-bit color) 4 bytes + obj.DiffuseModulation = Color( f:ReadByte(),f:ReadByte(),f:ReadByte(),f:ReadByte() ) + end + -- Somewhere between here are a lot of troubles. Lets reverse and start from the bottom it to be sure. + local bSkip = 0 + -- Version 11 UniformScale [4 bytes] + if version >= 11 then + f:Seek(s + staticSize - 4) + obj.UniformScale = f:ReadFloat() + bSkip = bSkip + 4 + else + obj.UniformScale = 1 -- Scale is not supported in lower versions + end + -- Version 10+ (Bitflags) FlagsEx [4 bytes] + if version >= 10 then -- unsigned int + f:Seek(s + staticSize - bSkip - 4) + obj.flags = f:ReadULong() + bSkip = bSkip + 4 + end + -- Version 9 and 10 DisableX360 [4 bytes] + if version >= 9 and version <= 10 then + f:Seek(s + staticSize - bSkip - 4) + obj.DisableX360 = f:ReadLong() -- bool (4 bytes) + end + return obj,f:Tell() - s + bSkip + end + + function meta:SeekLump(f, num ) + local h = self.lumpheader[ num + 1 ] + assert(h, "Can't locate lump!") + assert(h.fileofs > 8 or h.filelen < 1, "Invalid bytes in lumpheader!" .. num) + f:Seek(h.fileofs) + return h.filelen + end + + function meta:ReadLump(f, num ) + local len = self:SeekLump(f, num ) + local data = f:Read( len ) + return data + end + + function meta:LZMAReadLump(f, num ) + self:SeekLump(f, num ) + if f:Read(4):lower() ~= "lzma" then + return self:ReadLump(f, num ) + end + local actualSize = f:ReadLong() + local lzmaSize = f:ReadLong() -- lzmaSize + local t = {} + for i = 1,5 do + table.insert(t, f:ReadByte()) + end + local str = f:Read( lzmaSize ) + local buf = file.Open("sf_buffer.txt", "wb", "DATA"); + for _, v in ipairs(t) do + buf:WriteByte(v); + end + buf:WriteULong(actualSize); -- this is actually a 64bit int, but i can't write those with gmod functions + buf:WriteULong(0); -- filling in the unused bytes + buf:Write(str); + buf:Close(); + + return util.Decompress(file.Read("sf_buffer.txt")) + end + + function meta:ReadGameLumpHeader( f ) + if self._gamelump then return self._gamelump end + self._gamelump = {} + local len = self:SeekLump(f, 35 ) + local pos = f:Tell() + for i = 1, math.min(64, f:ReadLong() ) do + self._gamelump[i] = { + id = f:ReadLong(), + flags = f:ReadUShort(), + version = f:ReadUShort(), + fileofs = f:ReadLong(), + filelen = f:ReadLong() + } + end + return self._gamelump + end + + function meta:FindGameLump(f, gLumpID ) + local t = self:ReadGameLumpHeader( f ) + local m + for k, v in ipairs( t ) do + if v.id == gLumpID then + m = k + break + end + end + return t[m] + end + + -- Textures and materials + do + local max_data = 256000 + function meta:GetTextures(f) + local t = {} + local data = self:LZMAReadLump(f, 43) + if #data > max_data then + error("BSP's TexDataStringData is invalid!") + end + for s in string.gmatch( data, "[^%z]+" ) do + table.insert(t, s:lower()) + end + return t + end + end + + local function LoadENTLump( data, tab ) + for s in string.gmatch( data, "%{.-%\n}" ) do + local t = util.KeyValuesToTable("t" .. s) + -- Convert a few things to make it easier + t.origin = util.StringToType(t.origin or "0 0 0","Vector") + t.angles = util.StringToType(t.angles or "0 0 0","Angle") + local c = util.StringToType(t.rendercolor or "255 255 255","Vector") + t.rendercolor = Color(c.x,c.y,c.z) + t.raw = s + table.insert(tab,t) + end + end + + -- Load functions + function GetBSPData() + --print("Loading file") + local mapfile = "maps/"..game.GetMap() .. ".bsp" + local f = file.Open(mapfile,"rb","GAME") + if not f then StormFox2.Warning("Unable to load mapfile!") return {} end + -- Read the header + if f:Read(4) ~= "VBSP" then + f:Close() + error("File not BSP format!") + end + local BSP = {} + setmetatable(BSP, meta) + BSP.version = f:ReadLong() + assert( BSP.version <= 21, "BSP is too new" ) + -- Read Lump Header + BSP.lumpheader = {} + for i = 1, 64 do + BSP.lumpheader[i] = ReadLumpHeader( BSP, f ) + end + -- Ent + do + local data + BSP.Entities = {} + if file.Exists("maps/" .. game.GetMap() .. "_l_0.lmp", "GAME") then + StormFox2.Msg("Reading lmp EntityLump file.") + data = file.Read("maps/" .. game.GetMap() .. "_l_0.lmp", "GAME") + else + data = BSP:LZMAReadLump(f, 0 ) + end + LoadENTLump( data, BSP.Entities ) + end + -- Static prosp + BSP.StaticProps = {} + do + local t = BSP:FindGameLump( f, 1936749168 ) -- 1936749168 == "sprp" + if not t then t = {} end -- No static props? Must be empty or something + -- Read the models + f:Seek( t.fileofs ) + local m = {} + local n = f:ReadLong() -- Number of models + if n > 16384 then + ErrorNoHalt(game.GetMap() .. ".BSP has more than 16384 models!") + else + for i = 1,n do + local model = "" + for i2 = 1,128 do + local c = string.char(f:ReadByte()) + if string.match(c,"[%w_%-%.%/]") then + model = model .. c + end + end + m[i] = model + end + end + -- Read the leafs ( Unused ) + for i = 1, f:ReadLong() do + f:ReadShort() -- Unsigned + end + -- Read static props + local count = f:ReadLong() + if count > 16384 then + ErrorNoHalt(game.GetMap() .. ".BSP has more than 16384 static props!") + else + local endPos = t.filelen + t.fileofs + local staticSize = (endPos - f:Tell()) / count + local staticStart = f:Tell() + local staticUsed = 0 + for i = 0, count - 1 do + -- This is to try and get as much valid data we can. + f:Seek(staticStart + staticSize * i) + local sObj, sizeused = CreateStaticProp(f,t.version, m, staticSize) + staticUsed = sizeused + sObj.Index = table.insert(BSP.StaticProps,sObj) - 1 + end + end + end + -- We calculate the amount of static props within this space. It is more stable. + -- Textures + BSP.TextureArray = BSP:GetTextures(f) + f:Close() + return BSP + end +end + +-- Local functions + local function ValidateCache( fil ) + if not fil then return false end + if fil:Read(3) ~= "SF2" then return false end + if fil:ReadUShort() ~= CACHE_VERSION then return false end + if fil:ReadULong() ~= CRC then return false end + return true + end + + local function WriteData( f, str ) + f:WriteULong(#str) + f:Write(str) + end + + local function ReadData(f) + local n = f:ReadULong() or 0 + if n < 1 then return "" end + return f:Read(n) + end + + local function LoadCache() + if true then return end + if not file.Exists(CACHE_FIL, "DATA") then + return + end + -- Check if valid + local f = file.Open(CACHE_FIL, "rb", "DATA") + if not ValidateCache(f) then + StormFox2.Warning("Map cache is invalid / outdated! Regenerating ..") + f:Close() + return + end + StormFox2.Msg("Loading map cache ..") + local SF_BSPDATA = {} + SF_BSPDATA.version = f:ReadLong() + -- Entities + SF_BSPDATA.Entities = util.JSONToTable(ReadData(f)) or {} + -- Static props + SF_BSPDATA.StaticProps = util.JSONToTable(ReadData(f)) or {} + -- Textures + SF_BSPDATA.TextureArray = util.JSONToTable(ReadData(f)) or {} + SF_BSPDATA._hasPak = f:ReadBool() + + f:Close() + return SF_BSPDATA + end + + local function SaveCache() + if true then return end + if not file.Exists("stormfox2/cache", "DATA") then + file.CreateDir("stormfox2/cache") + end + local f = file.Open(CACHE_FIL, "wb", "DATA") + if not f then Stormfox2.Warning("Unable to save map cache!") end + f:Write("SF2") + f:WriteUShort(CACHE_VERSION) + f:WriteULong(CRC) + f:ReadLong(SF_BSPDATA.version) + -- Ent + WriteData(f, util.TableToJSON(SF_BSPDATA.Entities)) + -- Static + WriteData(f, util.TableToJSON(SF_BSPDATA.StaticProps)) + -- Textures + WriteData(f, util.TableToJSON(SF_BSPDATA.TextureArray)) + f:WriteBool(SF_BSPDATA._hasPak) + StormFox2.Msg("Saved map cache.") + f:Close() + end + + local function LoadMap() + SF_BSPDATA = LoadCache() -- Try and load cache + if SF_BSPDATA then -- Managed to load + SF_BSPDATALOADED = true + else + -- Load map .. + if CoffeeLib and false then + StormFox2.Msg("Generating map cache using CoffeeLib ..") + local BSP = Map.ReadBSP() + SF_BSPDATA = {} + SF_BSPDATA.version = BSP:GetVersion() + SF_BSPDATA.Entities = BSP:GetEntities() + SF_BSPDATA.StaticProps = BSP:GetStaticProps() + SF_BSPDATA.TextureArray = BSP:GetTextures() + SF_BSPDATALOADED = true + SaveCache() + else + StormFox2.Msg("Generating new map cache ..") + SF_BSPDATA = GetBSPData() + SF_BSPDATALOADED = true + if SF_BSPDATA then SaveCache() end + end + end + end +-- Load map +LoadMap() +-- Texture Generator +-- Type Guesser function +local blacklist = {"gravelfloor002b","swift/","sign","indoor","foliage","model","dirtfloor005c","dirtground010","concretefloor027a","swamp","sand","concret"} +local function GetTexType(str) + str = str:lower() + if str == "error" then return NO_TYPE end + for _,bl in ipairs(blacklist) do + if string.find(str,bl,nil,true) then return NO_TYPE end + end + -- Dirt grass and gravel + if string.find(str,"grass") then return DIRTGRASS_TYPE end + if string.find(str,"dirt") then return DIRTGRASS_TYPE end + if string.find(str,"gravel") then return DIRTGRASS_TYPE end + if string.find(str,"ground") then return DIRTGRASS_TYPE end + -- Roof + if string.find(str,"roof") then return ROOF_TYPE end + -- Road + if string.find(str,"road") then return ROAD_TYPE end + if string.find(str,"asphalt") then return ROAD_TYPE end + -- Pavement This is disabled, since it messes most maps up + --if string.find(str,"pavement") or string.find(str,"cobble") or string.find(str,"concretefloor") then return PAVEMENT_TYPE end + return NO_TYPE +end +--[[------------------------------------------------------------------------- +Generates the texture-table used by StormFox2. +---------------------------------------------------------------------------]] +local function GenerateTextureTree() + local tree = {} + -- Load all textures + for _,tex_string in pairs(StormFox2.Map.AllTextures()) do + if tree[tex_string] then continue end + local mat = Material(tex_string) + if not mat then continue end + local tex1,tex2 = mat:GetTexture("$basetexture"),mat:GetTexture("$basetexture2") + if not tex1 and not tex2 then continue end + -- Guess from the textures + if tex1 and not tex1:IsError() then + local t = GetTexType(tex1:GetName()) + if t ~= NO_TYPE then + tree[tex_string] = {} + tree[tex_string][1] = t + end + end + if tex2 and not tex2:IsError() then + local t = GetTexType(tex2:GetName()) + if t ~= NO_TYPE then + tree[tex_string] = tree[tex_string] or {} + tree[tex_string][2] = t + end + end + end + return tree +end +--[[------------------------------------------------------------------------- +Returns a list of map-textures that should be replaced. +NO_TYPE = -1 +DIRTGRASS_TYPE = 0 +ROOF_TYPE = 1 +ROAD_TYPE = 2 +PAVEMENT_TYPE = 3 +---------------------------------------------------------------------------]] +function StormFox2.Map.GetTextureTree() + return SF_TEXTDATA or {} +end + +-- Small StormFox Functions + --[[------------------------------------------------------------------------- + Returns the mapversion. + ---------------------------------------------------------------------------]] + function StormFox2.Map.Version() + return SF_BSPDATA.version or -1 + end + + --[[------------------------------------------------------------------------- + Returns the entities from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.Entities() + return SF_BSPDATA.Entities or {} + end + --[[------------------------------------------------------------------------- + Returns the staticprops from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.StaticProps() + return SF_BSPDATA.StaticProps or {} + end + --[[------------------------------------------------------------------------- + Returns all textures from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.AllTextures() + return SF_BSPDATA.TextureArray or {} + end + --[[------------------------------------------------------------------------- + Returns the filtered textures from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.Textures() + return SF_BSPDATA.Textures or {} + end + --[[------------------------------------------------------------------------- + Returns the filtered textures from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.HasPAK() + return SF_BSPDATA._hasPak or false + end + + --[[------------------------------------------------------------------------- + Gets all entities with the given class from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.FindClass(sClass) + local t = {} + for k,v in pairs(SF_BSPDATA.Entities) do + if v.classname and string.match(v.classname,sClass) then + table.insert(t,v) + end + end + return t + end + --[[------------------------------------------------------------------------- + Gets all entities with the given name from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.FindTargetName(sTargetName) + local t = {} + for k,v in pairs(SF_BSPDATA.Entities) do + if string.match(v.targetname or "",sTargetName) then + table.insert(t,v) + end + end + return t + end + --[[------------------------------------------------------------------------- + Returns the mapdata for the given entity. Will be nil if isn't a map-created entity. + ---------------------------------------------------------------------------]] + function StormFox2.Map.FindEntity(eEnt) + local c = eEnt:GetClass() + local h_id = eEnt:GetKeyValues().hammerid + if not h_id then return end + for k,v in pairs(SF_BSPDATA.Entities) do + if c == v.classname and h_id == v.hammerid then + return v + end + end + return + end + --[[------------------------------------------------------------------------- + Returns the mapdata for the given entity. Will be nil if isn't a map-created entity. + ---------------------------------------------------------------------------]] + function StormFox2.Map.FindEntsInSphere(vPos,nRadius) + local t = {} + nRadius = nRadius^2 + for i,v in ipairs(SF_BSPDATA.Entities) do + if v.origin:DistToSqr(vPos) <= nRadius then + table.insert(t,v) + end + end + return t + end + --[[------------------------------------------------------------------------- + Returns the mapdata for the given entity. Will be nil if isn't a map-created entity. + ---------------------------------------------------------------------------]] + function StormFox2.Map.FindStaticsInSphere(vPos,nRadius) + local t = {} + nRadius = nRadius^2 + for i,v in ipairs(SF_BSPDATA.StaticProps) do + if v.Origin:DistToSqr(vPos) <= nRadius then + table.insert(t,v) + end + end + return t + end + --[[------------------------------------------------------------------------- + Locates an entity with the given hammer_id from the mapfile. + ---------------------------------------------------------------------------]] + function StormFox2.Map.FindHammerid(nHammerID) + for k,v in pairs(ents.GetAll()) do + local h_id = v:GetKeyValues().hammerid + if not h_id then return end + if h_id == nHammerID then + return v + end + end + return + end + -- Map functions + local min,max,sky,sky_scale,has_Sky,map_radius = Vector(0,0,0),Vector(0,0,0),Vector(0,0,0),1,false + --[[------------------------------------------------------------------------- + Returns the maxsize of the map. + ---------------------------------------------------------------------------]] + function StormFox2.Map.MaxSize() + return max + end + --[[------------------------------------------------------------------------- + Returns the minsize of the map. + ---------------------------------------------------------------------------]] + function StormFox2.Map.MinSize() + return min + end + --[[------------------------------------------------------------------------- + Returns the radius of the map. + ---------------------------------------------------------------------------]] + function StormFox2.Map.RadiusSize() + return map_radius + end + --[[------------------------------------------------------------------------- + Clamps the vector to the size of the map. + ---------------------------------------------------------------------------]] + local clamp = math.Clamp + function StormFox2.Map.ClampPos(vec) + vec.x = clamp(vec.x, min.x + 1, max.x - 1) + vec.y = clamp(vec.y, min.y + 1, max.y - 1) + vec.z = clamp(vec.z, min.z + 1, max.z - 1) + return vec + end + --[[------------------------------------------------------------------------- + Returns the true center of the map. Often Vector( 0, 0, 0 ) + ---------------------------------------------------------------------------]] + function StormFox2.Map.GetCenter() + return (StormFox2.Map.MaxSize() + StormFox2.Map.MinSize()) / 2 + end + --[[------------------------------------------------------------------------- + Returns true if the position is within the map + ---------------------------------------------------------------------------]] + function StormFox2.Map.IsInside(vec) + if vec.x > max.x then return false end + if vec.y > max.y then return false end + if vec.z > max.z then return false end + if vec.x < min.x then return false end + if vec.y < min.y then return false end + if vec.z < min.z then return false end + return true + end + --[[------------------------------------------------------------------------- + Returns the skybox-position. + ---------------------------------------------------------------------------]] + function StormFox2.Map.GetSkyboxPos() + return sky + end + --[[------------------------------------------------------------------------- + Returns the skybox-scale. + ---------------------------------------------------------------------------]] + function StormFox2.Map.GetSkyboxScale() + return sky_scale + end + --[[------------------------------------------------------------------------- + Returns true if the map has a 3D skybox. + ---------------------------------------------------------------------------]] + function StormFox2.Map.Has3DSkybox() + return has_Sky + end + --[[------------------------------------------------------------------------- + Converts the given position to skybox. + ---------------------------------------------------------------------------]] + function StormFox2.Map.SkyboxToWorld(vPosition) + return (vPosition - sky) * sky_scale + end + --[[------------------------------------------------------------------------- + Converts the given skybox position to world. + ---------------------------------------------------------------------------]] + function StormFox2.Map.WorldtoSkybox(vPosition) + return (vPosition / sky_scale) + sky + end + --[[------------------------------------------------------------------------- + Checks if the mapfile has the entity-class. + ---------------------------------------------------------------------------]] + local list = {} + function StormFox2.Map.HadClass(sClass) + if list[sClass] ~= nil then return list[sClass] end + list[sClass] = #StormFox2.Map.FindClass(sClass) > 0 + return list[sClass] + end + --[[----------------------------------------------------------------- + Returns true if it is a cold map + ---------------------------------------------------------------------------]] + local bCold = false + function StormFox2.Map.IsCold() + return bCold + end + --[[------------------------------------------------------------------ + Returns true if the map has a snow-texture + ---------------------------------------------------------------------------]] + local bSnow = false + function StormFox2.Map.HasSnow() + return bSnow + end + +--[[------------------------------------------------------------------------- +Controls map relays easier + dusk = night_events + dawn = day_events +---------------------------------------------------------------------------]] +local relay = {} +hook.Add("StormFox2.InitPostEntity", "StormFox2.MapInteractions.Init", function() + -- Locate all logic_relays on the map + for _,ent in ipairs( ents.FindByClass("logic_relay") ) do + local name = ent:GetName() + name = string.match(name, "-(.+)$") or name + if name == "dusk" then name = "night_events" end + if name == "dawn" then name = "day_events" end + if not relay[name] then relay[name] = {} end + table.insert(relay[name], ent) + end +end) +if SERVER then + function StormFox2.Map.CallLogicRelay(sName,b) + if sName == "dusk" then sName = "night_events" end + if sName == "dawn" then sName = "day_events" end + if b ~= nil and b == false then + sName = sName .. "_off" + end + if not relay[sName] then return end + for _, ent in ipairs(relay[sName]) do + if not IsValid(ent) then continue end + ent:Fire( "Trigger", "" ); + end + end + local l_w + function StormFox2.Map.w_CallLogicRelay( name ) + name = string.lower( name ) + if l_w then + if l_w == name then + return + else -- Turn "off" the last logic relay + StormFox2.Map.CallLogicRelay("weather_" .. l_w, false) + end + end + StormFox2.Map.CallLogicRelay("weather_onchange") + l_w = name + StormFox2.Map.CallLogicRelay("weather_" .. name, true) + end + function StormFox2.Map.HasLogicRelay(sName,b) + if sName == "dusk" then sName = "night_events" end + if sName == "dawn" then sName = "day_events" end + if b ~= nil and b == false then + sName = sName .. "_off" + end + return relay[sName] and true or false + end +else -- Clients don't know the relays + local t = {} + for k,v in ipairs(StormFox2.Map.FindClass("logic_relay")) do + local sName = v.targetname + if not sName then break end + if sName == "dusk" then sName = "night_events" end + if sName == "dawn" then sName = "day_events" end + t[sName] = true + end + function StormFox2.Map.HasLogicRelay(sName,b) + if sName == "dusk" then sName = "night_events" end + if sName == "dawn" then sName = "day_events" end + return t[sName] and true or false + end +end +-- Generates the texture-tree +--if not SF_TEXTDATAMAP or table.Count(SF_TEXTDATAMAP) < 1 then + SF_TEXTDATAMAP = GenerateTextureTree() +--end +-- Find some useful variables we can use +if StormFox2.Map.Entities()[1] then + max = util.StringToType( StormFox2.Map.Entities()[1]["world_maxs"], "Vector" ) + min = util.StringToType( StormFox2.Map.Entities()[1]["world_mins"], "Vector" ) + map_radius = math.max(max.x, max.y, max.z, -min.x, -min.y, -min.z) * 1.41 + bCold = StormFox2.Map.Entities()[1]["coldworld"] and true or false +else + StormFox2.Warning("This map doesn't have an entity lump! Might cause some undocumented behaviors.") + -- gm_flatgrass + max = Vector(15360, 15360, -12288) + min = Vector(15360, 15360, -12800) + map_radius = 15360 * 1.41 + bCold = false +end +local sky_cam = StormFox2.Map.FindClass("sky_camera")[1] +if sky_cam then + has_Sky = true + sky = util.StringToType( sky_cam.origin, "Vector" ) + sky_scale = tonumber(sky_cam.scale) or 1 +end +for _,tab in pairs(StormFox2.Map.Textures()) do + if string.find(tab.nameStringTableID:lower(), "snow") then + bSnow = true + break + end +end + +-- Modify the texture tree, if there are changes +--[[ + local INVALID = -2 + local NO_TYPE = -1 + local DIRTGRASS_TYPE = 0 + local ROOF_TYPE = 1 + local ROAD_TYPE = 2 + local PAVEMENT_TYPE = 3 +]] + +local modifyData = {} -- Holds the list of modified materials +-- Gnerates SF_TEXTDATA from SF_TEXTDATAMAP and modifyData +local function GenerateTEXTDATA() + -- Create a copy from the map-data + SF_TEXTDATA = table.Copy( SF_TEXTDATAMAP ) + -- Modify the data + for sMat,v in pairs( modifyData ) do + if v == -1 then + SF_TEXTDATA[sMat] = {-1, -1} + else + if not SF_TEXTDATA[sMat] then + SF_TEXTDATA[sMat] = {} + end + local mat = Material(sMat) + if mat:GetTexture("$basetexture") then + SF_TEXTDATA[sMat][1] = v + end + --if mat:GetTexture("$basetexture2") then Broken + -- SF_TEXTDATA[sMat][2] = v + --end + end + end +end + +if SERVER then + local map_tex_file = "stormfox2/tex_setting/" .. game.GetMap() .. ".txt" + local function SaveMData() + StormFox2.FileWrite(map_tex_file, util.TableToJSON(modifyData)) + end + local function LoadMData() + if file.Exists(map_tex_file, "DATA") then + modifyData = util.JSONToTable( file.Read(map_tex_file, "DATA") ) or {} + end + GenerateTEXTDATA() + end + LoadMData() + hook.Add("stormfox2.postlib", "stormfox2.lib.texturesetting", function() + StormFox2.Network.ForceSet("texture_modification", modifyData) + function StormFox2.Map.ModifyMaterialType( sMat, v ) + if v < -1 then + modifyData[sMat] = nil + else + modifyData[sMat] = v + end + SaveMData() + StormFox2.Network.ForceSet("texture_modification", modifyData) + GenerateTEXTDATA() + end + end) +else + GenerateTEXTDATA() -- We don't know if we'll ever get a list of modified materials. + hook.Add("StormFox2.data.change", "stormfox2.lib.texturesetting", function(key, _) + if key ~= "texture_modification" then return end + modifyData = StormFox2.Data.Get("texture_modification", {}) + GenerateTEXTDATA() + StormFox2.Terrain.Update() + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_network.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_network.lua new file mode 100644 index 0000000..dc19ec7 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_network.lua @@ -0,0 +1,59 @@ + +--[[ + Network.Set( sKey, zVar, nDelta ) Same as Data.Set( sKey, zVar, nDelta ) but networks it to all clients. +]] +StormFox2.Network = {} +StormFox_NETWORK = {} -- Var + +if SERVER then + function StormFox2.Network.Set( sKey, zVar, nDelta ) + StormFox2.Data.Set(sKey, zVar, nDelta) + if StormFox_NETWORK[sKey] == zVar then return end + net.Start(StormFox2.Net.Network) + net.WriteBool(true) + net.WriteString(sKey) + net.WriteType(zVar) + net.WriteUInt(nDelta or 0, 16) + net.Broadcast() + StormFox_NETWORK[sKey] = zVar + end + function StormFox2.Network.ForceSet( sKey, zVar, nDelta ) + StormFox2.Data.Set(sKey, zVar, nDelta) + net.Start(StormFox2.Net.Network) + net.WriteBool(true) + net.WriteString(sKey) + net.WriteType(zVar) + net.WriteUInt(nDelta or 0, 16) + net.Broadcast() + StormFox_NETWORK[sKey] = zVar + end + local tickets = {} + net.Receive(StormFox2.Net.Network, function(len, ply) + if tickets[ply] then return end + tickets[ply] = true + net.Start(StormFox2.Net.Network) + net.WriteBool(false) + net.WriteTable(StormFox_NETWORK) + net.Send(ply) + hook.Run("StormFox2.data.initspawn", ply) + end) +else + net.Receive(StormFox2.Net.Network, function(len) + if net.ReadBool() then + local sKey = net.ReadString() + local zVar = net.ReadType() + local nDelta = net.ReadUInt(16) + StormFox2.Data.Set(sKey, zVar, nDelta) + else + StormFox_NETWORK = net.ReadTable() + for k,v in pairs(StormFox_NETWORK) do + StormFox2.Data.Set(k, v) + end + end + end) + -- Ask the server what data we have + hook.Add("StormFox2.InitPostEntity", "StormFox2.network", function() + net.Start(StormFox2.Net.Network) + net.SendToServer() + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_permission.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_permission.lua new file mode 100644 index 0000000..a3bcc11 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_permission.lua @@ -0,0 +1,145 @@ +StormFox2.Permission = {} + +hook.Add("stormfox2.postlib", "stormfox2.privileges", function() + if not CAMI then return end + CAMI.RegisterPrivilege{ + Name = "StormFox Settings", + MinAccess = "superadmin" + } + -- Permission to edit StormFox weather and time + CAMI.RegisterPrivilege{ + Name = "StormFox WeatherEdit", + MinAccess = "admin" + } +end) + +local SF_SERVEREDIT = 0 +local SF_WEATHEREDIT= 1 + +if SERVER then + util.AddNetworkString("StormFox2.menu") + -- "Fake" settings + local commands = { + ["cvslist"] = function( var ) + StormFox2.Setting.SetCVS( tostring( var ) ) + end + } + net.Receive("StormFox2.menu", function(len, ply) + local req = net.ReadBool() + if ply:IsListenServerHost() or game.SinglePlayer() then + net.Start("StormFox2.menu") + net.WriteBool(req) + net.Send( ply ) + return + end + CAMI.PlayerHasAccess(ply,req and "StormFox Settings" or "StormFox WeatherEdit",function(b) + if not b then return end + net.Start("StormFox2.menu") + net.WriteBool(req) + net.Send( ply ) + end) + end) + local function NoAccess(ply, msg) + if not ply then + MsgC( Color(155,155,255),"[StormFox2] ", color_white, msg ) + MsgN() + return + end + net.Start( StormFox2.Net.Permission ) + net.WriteString(msg) + net.Send(ply) + end + local function plyRequestSetting(ply, convar, var) + if not CAMI then return end + -- Check if its a stormfox setting + local obj = StormFox2.Setting.GetObject( convar ) or commands[ convar ] + if not obj then + if ply then + NoAccess(ply, "Invalid setting: " .. tostring(convar)) + end + return false, "Not SF" + end + -- If singleplayer/host + if game.SinglePlayer() or ply:IsListenServerHost() then + if type(obj) == "function" then + obj( var ) + else + obj:SetValue( var ) + end + return + end + -- Check CAMI + CAMI.PlayerHasAccess(ply,"StormFox Settings",function(b) + if not b then + NoAccess(ply, "You don't have access to edit the settings!") + return + end + if type(obj) == "function" then + obj( var ) + else + obj:SetValue( var ) + end + end) + end + local function plyRequestEdit( ply, tID, var) + if not CAMI then return end + -- If singleplayer/host + if game.SinglePlayer() or ply:IsListenServerHost() then + return StormFox2.Menu.SetWeatherData(ply, tID, var) + end + -- Check CAMI + CAMI.PlayerHasAccess(ply,"StormFox WeatherEdit",function(b) + if not b then + NoAccess(ply, "You don't have access to edit the weather!") + return + end + StormFox2.Menu.SetWeatherData(ply, tID, var) + end) + end + net.Receive( StormFox2.Net.Permission, function(len, ply) + local t = net.ReadUInt(1) + if t == SF_SERVEREDIT then + plyRequestSetting(ply, net.ReadString(), net.ReadType()) + elseif t == SF_WEATHEREDIT then + plyRequestEdit(ply, net.ReadUInt(4), net.ReadType()) + end + end) + + function StormFox2.Permission.EditAccess(ply, sPermission, onSuccess, ...) + if not ply or ply:IsListenServerHost() then -- Console or host + return onSuccess(ply, ... ) + end + local a = {...} + CAMI.PlayerHasAccess(ply,sPermission,function(b) + if not b then + NoAccess(ply, "You don't have access to edit the weather.") + return + end + onSuccess(ply, unpack(a) ) + end) + end +else + net.Receive(StormFox2.Net.Permission, function(len) + local str = net.ReadString() + chat.AddText(Color(155,155,255),"[StormFox2] ", color_white, str) + end) + net.Receive("StormFox2.menu", function(len) + local n = net.ReadBool() + if n then + StormFox2.Menu._OpenSV() + else + StormFox2.Menu._OpenController() + end + end) + local w_list = { + "sf_menu","sf_openweathermap_key", "sf_openweathermap_real_lat", "sf_openweathermap_real_lon", "sf_openweathermap_real_city" + } + function StormFox2.Permission.RequestSetting( convar, var ) + net.Start(StormFox2.Net.Permission) + net.WriteUInt(SF_SERVEREDIT, 1) + net.WriteString( convar ) + net.WriteType(var) + net.SendToServer() + end +end + diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_reset.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_reset.lua new file mode 100644 index 0000000..47a2145 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_reset.lua @@ -0,0 +1,12 @@ + +-- Allows to reset +if _STORMFOX_POSTENTITY then + timer.Simple(2, function() + hook.Run("StormFox2.InitPostEntity") + end) +end + +hook.Add("InitPostEntity", "SF_PostEntity", function() + hook.Run("StormFox2.InitPostEntity") + _STORMFOX_POSTENTITY = true +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_settings.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_settings.lua new file mode 100644 index 0000000..a5f2e26 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_settings.lua @@ -0,0 +1,717 @@ +--[[------------------------------------------------------------------------- +StormFox Settings +Handle settings and convert convars. + + - Hooks: StormFox2.Setting.Change sName, vVarable +---------------------------------------------------------------------------]] +StormFox2.Setting = {} +-- Local functions and var + + local NET_ALLSETTINGS = 0 + local NET_UPDATE = 1 + + local settingCallback = {} + local settings = {} + local cache = {} + + local ValueToString, StringToValue + do + function StringToValue( str, _type ) + _type = _type:lower() + if ( _type == "vector" ) then return Vector( str ) end + if ( _type == "angle" ) then return Angle( str ) end + if ( _type == "float" or _type == "number" ) then return tonumber( str ) end + if ( _type == "int" ) then return math.Round( tonumber( str ) ) end + if ( _type == "bool" or _type == "boolean" ) then return tobool( str ) end + if ( _type == "string" ) then return tostring( str ) end + if ( _type == "entity" ) then return Entity( str ) end + StormFox2.Warning("Unable parse: " .. _type, true) + end + function ValueToString( var, _type ) + _type = (_type or type( var )):lower() + if _type == "vector" or _type == "angle" then return string.format( "%.2f %.2f %.2f", var:Unpack() ) end + if _type == "number" or _type == "float" then return util.NiceFloat( var ) end + if _type == "int" then var = math.Round( var ) end + return tostring( var ) + end + end +-- Load the settings + local mapFile, defaultFile + if SERVER then + mapFile, defaultFile = "stormfox2/sv_settings/" .. game.GetMap() .. ".json", "stormfox2/sv_settings/default.json" + else + mapFile, defaultFile = "stormfox2/cl_settings/" .. game.GetMap() .. ".json", "stormfox2/cl_settings/default.json" + end + local settingsFile = file.Exists(mapFile, "DATA") and mapFile or defaultFile + local fileData = {} + if file.Exists(settingsFile, "DATA") then + fileData = util.JSONToTable( file.Read(settingsFile, "DATA") or "" ) or {} + end + local blockSaveFile = false + local function saveToFile() + if blockSaveFile then return end + local data = {} + for sName, obj in pairs( settings ) do + if CLIENT and obj:IsServer() then continue end -- If you're the client, ignore server settings + if sName == "mapfile" then continue end + if sName == "mapfile_cl" then continue end + if obj:IsDefault() then continue end + data[sName] = obj:GetString() + end + StormFox2.FileWrite( settingsFile, util.TableToJSON(data, true) ) + end + + function StormFox2.Setting.IsUsingMapFile() + return settingsFile == mapFile + end + + function StormFox2.Setting.UseMapFile( bool ) + if bool then + if settingsFile == mapFile then return end + settingsFile = mapFile + -- Settings only save once callback is done. Therefor we force this to save + local ob = blockSaveFile + blockSaveFile = false + saveToFile() -- "Copy" the settings to the file + blockSaveFile = ob + else + if settingsFile == defaultFile then return end + file.Delete(mapFile) + settingsFile = defaultFile + -- Reload the default file + fileData = util.JSONToTable( file.Read(settingsFile, "DATA") or "" ) or {} + blockSaveFile = true + for sName, var in pairs( fileData ) do + local obj = StormFox2.Setting.GetObject( sName ) + if not obj then continue end + local newVar = fileData[sName] and StringToValue(fileData[sName], obj.type) + obj:SetValue( newVar ) + end + blockSaveFile = false + end + end + + function StormFox2.Setting.ForceSave() + blockSaveFile = false + saveToFile() + end + + function StormFox2.Setting.GetSaveFile() + return settingsFile + end + +-- Meta Table +local meta = {} + meta.__index = meta + AccessorFunc(meta, "group", "Group") + AccessorFunc(meta, "desc", "Description") + function meta:GetName() + return self.sName + end + function meta:GetValue() + return self.value + end + function meta:GetDescription() + return self.desc + end + function meta:IsSecret() + return self.isSecret or false + end + function meta:SetValue( var ) + if self:GetValue() == var then return self end -- Ignore + StormFox2.Setting.Set(self:GetName(), var) + return self + end + function meta:GetDefault() + return self.default + end + function meta:IsDefault() + return self:GetValue() == self:GetDefault() + end + function meta:Revert() + self:SetValue( self:GetDefault() ) + end + function meta:IsServer() + return self.server + end + function meta:GetType() + return self.type + end + function meta:SetMenuType( sType, tSortOrter ) + StormFox2.Setting.SetType( self:GetName(), sType, tSortOrter ) + return self + end + function meta:GetMin() + return self.min + end + function meta:GetMax() + return self.max + end + function meta:GetString() + return ValueToString(self:GetValue(), self:GetType()) + end + function meta:IsFuzzyOn() + if not self:GetValue() then return false end + if self.type == "number" then + if self:GetValue() <= ( self:GetMin() or 0 ) then return false end + end + return true + end + function meta:SetFuzzyOn() + if self.type == "boolean" then + self:SetValue( true ) + elseif self.type == "number" then + local lowest = ( self:GetMin() or 0 ) + self:SetValue( lowest + 1 ) + end + return self + end + function meta:SetFuzzyOff() + if self.type == "boolean" then + self:SetValue( false ) + elseif self.type == "number" then + local lowest = ( self:GetMin() or 0 ) + self:SetValue( lowest ) + end + return self + end + function meta:SetFromString( str, type ) + self:SetValue( StringToValue( str, type or self.type ) ) + return self + end + function meta:AddCallback(fFunc,sID) + StormFox2.Setting.Callback(self:GetName(),fFunc,sID) + end + -- Ties the setting to others. Does require all to be booleans or form of toggles + do + local radioTab = {} + local radioTabDefault = {} + local blockLoop = false + local function callBack(vVar, oldVar, sName, id) + if blockLoop then return end -- Another setting made you change. Don't run. + local obj = settings[sName] + if not obj then StormFox2.Warning("Invalid radio-setting!", true) end + local a = radioTab[sName] + if not a then return end + -- Make sure we turned "on" + if not obj:IsFuzzyOn() then -- We got turned off. Make sure at least one is on + if not blockLoop then -- Turned off, and we didn't get called by others + local default = a[1] -- First one in list. This is to ensure self never get set. + for _, other in ipairs( a ) do + if other:IsFuzzyOn() then return end -- One of the others are on. Ignore. + if radioTabDefault[other:GetName()] then -- I'm the default one + default = other + end + end + -- All other settings are off. Try and switch the default on. + if default:GetName() == sName then -- Tell the settings we can't be turned off + return false + else + default:SetFuzzyOn() + end + end + return + end + blockLoop = true + -- Tell the others we turned on, and they have to turn off + for _, other in ipairs( a ) do + if other:GetName() == obj:GetName() then continue end -- Just to make sure we don't loop around + other:SetFuzzyOff() + end + blockLoop = false + end + local callOthers = true + -- Makes all settings turn off, if one of them are turned on. + function meta:SetRadioAll( ... ) + if self:IsServer() and CLIENT then -- Not your job to keep track. + self._radioB = true + for _, other in ipairs( { ... } ) do + other._radioB = true + end + return self + end + local a = {} + -- Make sure the arguments doesn't contain itself and all is from the same realm. + local f = { ... } + for _, other in ipairs( f ) do + if other:GetName() == self:GetName() then continue end -- Don't include self + if other:IsServer() ~= self:IsServer() then -- Make sure same realm + StormFox2.Warning(other:GetName() .. " tried to tie itself to a setting from another realm!") + continue + end + table.insert(a, other) + end + if #a < 1 then StormFox2.Warning(self:GetName() .. " tried to tie itself to nothing!",true) end + -- Tell the other settings to do the same + if callOthers then + callOthers = false + for _, other in ipairs( a ) do + other:SetRadio( self, unpack( a ) ) + end + callOthers = true + end + radioTab[self:GetName()] = a + StormFox2.Setting.Callback(self:GetName(),callBack,"radio_setting") + return self + end + function meta:SetRadioDefault() -- Turn on if all others are off + radioTabDefault[self:GetName()] = true + return self + end + function meta:IsRadio() + return (radioTab[self:GetName()] or self._radioB) and true or false + end + -- Tells these settings to turn off, if this setting is turned on + function meta:SetRadio( ... ) + if self:IsServer() and CLIENT then -- Not your job to keep track. + self._radioB = true + return self + end + local a = {} + -- Make sure the arguments doesn't contain itself and all is from the same realm. + for _, other in ipairs( { ... } ) do + if other:GetName() == self:GetName() then continue end -- Don't include self + if other:IsServer() ~= self:IsServer() then -- Make sure same realm + StormFox2.Warning(other:GetName() .. " tried to tie itself to a setting from another realm!") + continue + end + table.insert(a, other) + end + if #a < 1 then StormFox2.Warning(self:GetName() .. " tried to tie itself to nothing!",true) end + radioTab[self:GetName()] = a + StormFox2.Setting.Callback(self:GetName(),callBack,"radio_setting") + return self + end + end + +local postSettingChace = {} +-- Creates a setting and returns the setting-object +function StormFox2.Setting.AddSV(sName,vDefaultVar,sDescription,sGroup, nMin, nMax) + if settings[sName] then return settings[sName] end -- Already created + local t = {} + setmetatable(t, meta) + t.sName = sName + t.type = type(vDefaultVar) + if SERVER then + if fileData[sName] ~= nil then + t.value = StringToValue(fileData[sName], t.type) + end + if t.value == nil then -- Check convar before setting the setting. + local con = GetConVar("sf_" .. sName) + if con then + t.value = StringToValue(con:GetString(), t.type) + end + end + if t.value == nil then -- If all fails, use the default + t.value = vDefaultVar + end + else + t.value = postSettingChace[sName] or vDefaultVar + end + t.default = vDefaultVar + t.server = true + t.min = nMin + t.max = nMax + t:SetGroup( sGroup ) + t:SetDescription( sDescription ) + settings[sName] = t + return t +end + +-- Creates a setting and returns the setting-object +if CLIENT then + function StormFox2.Setting.AddCL(sName,vDefaultVar,sDescription,sGroup, nMin, nMax) + if settings[sName] then return settings[sName] end -- Already added + local t = {} + setmetatable(t, meta) + t.sName = sName + t.type = type(vDefaultVar) + if CLIENT then + if fileData[sName] ~= nil then + t.value = StringToValue(fileData[sName], t.type) + end + if t.value == nil then + local con = GetConVar("sf_" .. sName) + if con then + t.value = StringToValue(con:GetString(), t.type) + end + end + if t.value == nil then -- If all fails, use the default + t.value = vDefaultVar + end + end + t.default = vDefaultVar + t.server = false + t.min = nMin + t.max = nMax + t:SetGroup( sGroup ) + t:SetDescription( sDescription ) + settings[sName] = t + return t + end +end + +--[[----------------------------------------------------------------- +Tries to onvert to the given defaultvar to match the setting. +---------------------------------------------------------------------------]] +function StormFox2.Setting.StringToType( sName, sString ) + local obj = settings[sName] + if not obj then return sString end -- No idea + return StringToValue( sString, obj.type ) +end + +--[[----------------------------------------------------------------- +Returns a setting and will try to convert to the given defaultvar type. +Secondary will be true, if the setting isn't there. +---------------------------------------------------------------------------]] +function StormFox2.Setting.Get(sName,vDefaultVar) + local obj = settings[sName] + if not obj then return vDefaultVar end + return obj:GetValue() +end +--[[----------------------------------------------------------------- +Returns hte setting object. +---------------------------------------------------------------------------]] +function StormFox2.Setting.GetObject(sName) + return settings[sName] +end +--[[----------------------------------------------------------------- +Sets a StormFox setting +---------------------------------------------------------------------------]] +local w_list = { + "openweathermap_key", "openweathermap_real_lat", "openweathermap_real_lon" +} +--value_type["openweathermap_key"] = "string" +--value_type["openweathermap_real_lat"] = "string" +--value_type["openweathermap_real_lon"] = "string" + +local function CallBack( sName, newVar, oldVar) + if not settingCallback[sName] then return end + for id, fFunc in pairs( settingCallback[sName] ) do + if isstring(id) or IsValid(id) then + fFunc(newVar, oldVar, sName, id) + else -- Invalid + settingCallback[sName][id] = nil + end + end +end + +function StormFox2.Setting.Set(sName,vVar, bDontSave) + -- Special "settings" + if sName == "openweathermap_real_city" then + StormFox2.WeatherGen.APISetCity( vVar ) + return + end + -- Check if valid + local obj = settings[sName] + if not obj then + StormFox2.Warning("Invalid setting: " .. sName .. "!") + return false + end + -- Check the variable + if obj.type ~= type(vVar) then + if type(vVar) == "string" then -- Try and convert it + vVar = StringToValue( vVar, obj.type ) + else + StormFox2.Warning("Invalid variable: " .. sName .. "!") + return false + end + if vVar == nil then return false end -- Failed + end + -- Check min and max + if obj.type == "number" and obj.min then + vVar = math.max(vVar, obj.min) + end + if obj.type == "number" and obj.max then + vVar = math.min(vVar, obj.max) + end + -- Check for duplicates + local oldVar = obj:GetValue() + if oldVar == vVar then return end -- Same value, ignore + -- We need to ask the server to change this setting. This isn't ours + if CLIENT and obj:IsServer() then + if StormFox2.Permission then + StormFox2.Permission.RequestSetting(sName, vVar) + else + StormFox2.Warning("Unable to ask server to change: " .. sName .. "!") + end + return false + end + -- Save the value + -- Make callbacks + local oB = blockSaveFile -- Editing a setting, might change others. Only save after we're done + blockSaveFile = true + local oldVar = obj.value + obj.value = vVar + -- Callback + CallBack(sName, vVar, oldVar) + blockSaveFile = oB -- We're done changing settings, save if we can + if not blockSaveFile and not bDontSave then + if not (sName == "mapfile" or sName == "mapfile_cl") then + saveToFile() + end + end + cache[sName] = nil -- Delete cache + -- Tell all clients about it + if SERVER then + if not obj:IsSecret() then + net.Start( StormFox2.Net.Settings ) + net.WriteUInt(NET_UPDATE, 3) + net.WriteString(sName) + net.WriteType(vVar) + net.Broadcast() + end + end + --[[------------------------------------------------------------------ + Gets called when a StormFox setting changes. + ---------------------------------------------------------------------------]] + hook.Run("StormFox2.Setting.Change",sName,vVar, oldVar) + return true +end +-- Server and Clientside NET +if CLIENT then + net.Receive(StormFox2.Net.Settings,function(len) + local _type = net.ReadUInt(3) + if _type == NET_UPDATE then + local sName = net.ReadString() + local var = net.ReadType() + local obj = settings[sName] + if not obj then + StormFox2.Warning("Server tried to set an unknown setting: " .. sName) + return + end + if not obj:IsServer() then + StormFox2.Warning("Server tried to set a clientside setting: " .. sName) + else + local oldVar = obj.value + obj.value = var + cache[sName] = var + -- Callback + CallBack(sName, var, oldVar) + hook.Run("StormFox2.Setting.Change",sName,obj.value,oldVar) + end + elseif _type == NET_ALLSETTINGS then + local tab = net.ReadTable() -- I'm lazy + for sName, vVar in pairs( tab ) do + local obj = settings[sName] + if not obj then -- So this setting "might" be used later. Cache the setting-value and ignore + postSettingChace[sName] = vVar + -- StormFox2.Warning("Server tried to set an unknown setting: " .. sName) + continue + end + if not obj:IsServer() then -- This is a clientside setting. Nope.AVI + StormFox2.Warning("Server tried to set a clientside setting: " .. sName) + else + local oldVar = obj.value + obj.value = vVar + cache[sName] = vVar + -- Callback + CallBack(sName, vVar, oldVar) + hook.Run("StormFox2.Setting.Change",sName,obj.value,oldVar) + end + end + end + end) +else + hook.Add("StormFox2.data.initspawn", "StormFox2.setting.send", function( ply ) + net.Start( StormFox2.Net.Settings ) + net.WriteUInt(NET_ALLSETTINGS, 3) + for sName, obj in pairs( settings ) do + if not obj then continue end + if obj:IsSecret() then continue end + net.WriteType( sName ) + net.WriteType( obj:GetValue() ) + end + -- End of table + net.WriteType( nil ) + net.Send(ply) + end) +end +--[[----------------------------------------------------------------- +Calls the function when the given setting changes. +fFunc will be called with: vNewVariable, vOldVariable, ConVarName, sID + +Unlike convars, this will also be triggered on the clients too. +Note: Variables get converted automatically +---------------------------------------------------------------------------]] +function StormFox2.Setting.Callback(sName,fFunc,sID) + if not settingCallback[sName] then settingCallback[sName] = {} end + settingCallback[sName][sID or "default"] = fFunc +end + +--hook.Add("StormFox2.Setting.Change", "StormFox2.Setting.Callbacks", function(sName, vVar, oldVar) + --if not settingCallback[sName] then return end + --for id, fFunc in pairs( settingCallback[sName] ) do + -- fFunc(vVar, oldVar, sName, id) + --end +--end) +--[[------------------------------------------------------------------ +Same as StormFox2.Setting.Get, however this will cache the result. +This is faster than looking it up constantly. +---------------------------------------------------------------------------]] +function StormFox2.Setting.GetCache(sName,vDefaultVar) + if cache[sName] then return cache[sName] end + local var = StormFox2.Setting.Get(sName,vDefaultVar) + cache[sName] = var + return var +end + +function StormFox2.Setting.GetDefault(sName) + local obj = settings[sName] + if not obj then return nil end + return obj:GetDefault() +end +function StormFox2.Setting.GetAll() + return table.GetKeys( settings ) +end +function StormFox2.Setting.GetAllServer() + -- Server only has server settings + if SERVER then return StormFox2.Setting.GetAll() end + -- Make list + local t = {} + for sName,obj in pairs(settings) do + if not obj:IsServer() then continue end + table.insert(t, sName) + end + return t +end +if CLIENT then + function StormFox2.Setting.GetAllClient() + local t = {} + for sName,obj in pairs(settings) do + if obj:IsServer() then continue end + table.insert(t, sName) + end + return t + end +end + +-- Returns the valuetype of the setting. This can allow special types like tables .. ect +local type_override = {} +function StormFox2.Setting.GetType( sName ) + if type_override[sName] then return type_override[sName] end + local obj = settings[sName] + if not obj then return end + return obj.type +end +--[[ Type: + - number + - string + - float + - boolean + - A table of options { [value] = "description" } + - special_float + Marks below 0 as "off" + - time + - temp / temperature + - Time_toggle +]] +function StormFox2.Setting.SetType( sName, sType, tSortOrter ) + if type(sType) == "nil" then -- Reset it + StormFox2.Warning("Can't make the setting a nil-type!") + end + if type(sType) == "boolean" then + type_override[sName] = "boolean" + elseif type(sType) == "number" then + type_override[sName] = "number" + elseif type(sType) == "table" then -- A table is a list of options + type_override[sName] = {sType, tSortOrter} + else + if sType == "bool" then + type_override[sName] = "boolean" + else + type_override[sName] = string.lower(sType) + end + end +end + +-- Resets all stormfox settings to default. +if SERVER then + local obj = StormFox2.Setting.AddSV("mapfile", false) + obj:AddCallback(StormFox2.Setting.UseMapFile) + obj:SetValue( StormFox2.Setting.IsUsingMapFile() ) + function StormFox2.Setting.Reset() + blockSaveFile = true + for sName, obj in pairs(setting) do + if sName == "mapfile" then continue end + obj:Revert() + end + blockSaveFile = false + saveToFile() + StormFox2.Warning("All settings were reset to default values. You should restart!") + cache = {} + end +else + StormFox2.Setting.AddSV("mapfile", false) + local obj = StormFox2.Setting.AddCL("mapfile_cl", false) + obj:AddCallback(StormFox2.Setting.UseMapFile) + obj:SetValue( StormFox2.Setting.IsUsingMapFile() ) + function StormFox2.Setting.Reset() + blockSaveFile = true + for _, sName in ipairs(StormFox2.Setting.GetAllClient()) do + if sName == "mapfile_cl" then continue end + local obj = setting[sName] + if not obj then continue end + obj:Revert() + end + blockSaveFile = false + saveToFile() + StormFox2.Warning("All settings were reset to default values. You should rejoin!") + cache = {} + end +end + +-- Gets and sets StormFox server setting +if SERVER then + function StormFox2.Setting.SetCVS( str ) + local t = string.Explode(",", str) + blockSaveFile = true + for i = 1, #t, 2 do + local sName, var = t[i], t[i+1] or nil + if string.len(sName) < 1 or not var then continue end + local obj = StormFox2.Setting.GetObject(sName ) + if not obj then + StormFox2.Warning("Invalid setting: " .. sName .. ".") + continue + else + obj:SetValue(var) + end + end + blockSaveFile = false + saveToFile() + StormFox2.Warning("All settings were updated. You should restart!") + end +end + +local exlist = {"openweathermap_real_lat", "openweathermap_real_lon", "openweathermap_key"} +function StormFox2.Setting.GetCVS() + local c = "" + for sName, obj in pairs(settings) do + if obj:IsSecret() then continue end + if not obj:IsServer() then continue end + c = c .. sName .. "," .. obj:GetString() .. "," + end + return c +end + +function StormFox2.Setting.GetCVSDefault() + local c = "" + for sName, obj in pairs(settings) do + if obj:IsSecret() then continue end -- Justi n case, so people don't share hidden settings + if not obj:IsServer() then continue end + c = c .. sName .. "," .. ValueToString(obj:GetDefault(), obj:GetType()) .. "," + end + return c +end + +-- Disable SF2 +StormFox2.Setting.AddSV("enable", true, nil, "Start") +StormFox2.Setting.AddSV("allow_csenable", engine.ActiveGamemode() == "sandbox", nil, "Start") +if CLIENT then + StormFox2.Setting.AddCL("clenable", true, nil, "Start") +end +function StormFox2.Setting.SFEnabled() + if not StormFox2.Setting.GetCache("enable", true) then return false end + if SERVER or not StormFox2.Setting.GetCache("allow_csenable", false) then return true end + return StormFox2.Setting.GetCache("clenable", true) +end diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_terrain.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_terrain.lua new file mode 100644 index 0000000..b7416b5 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_terrain.lua @@ -0,0 +1,307 @@ + +--[[ + Terrain control. Changes the ground + + Terrain.Create( sName ) Creates a new terrain type and stores it + Terrain.Get( sName ) Returns the terrain. + Terrain.Set( sName ) Sets the terrain. (This should only be done serverside) + Terrain.GetCurrent() Returns the terrain obj or nil. + StormFox2.Terrain.Reset() Resets the terrain to default. + StormFox2.Terrain.HasMaterialChanged( iMaterial ) Returns true if the terrain has changed the material. + + Terrain Meta: + :LockUntil( fFunc ) Makes the terrian + :MakeFootprints( sndList, sndName ) Makes footprints. Allows to overwrite footstep sounds. + :AddTextureSwap( material, basetexture, basetextire2 ) Changes a materials textures. + :RenderWindow( width, height ) A function that renders a window-texure. (Weather will trump this) + :RenderWindowRefract( width, height ) A function that renders a window-texure. (Weather will trump this) + :RenderWindow64x64( width, height ) A function that renders a window-texure. (Weather will trump this) + :RenderWindowRefract64x64( width, height ) A function that renders a window-texure. (Weather will trump this) + :Apply Applies the terrain (This won't reset old terrain) + + Hooks: + StormFox2.terrain.footstep Entity foot[0 = left,1 = right] sTexture bTerrainTexture +]] + +local meta = {} +meta.__index = meta +meta.__tostring = function(self) return "SF_TerrainType[" .. (self.Name or "Unknwon") .. "]" end +meta.__eq = function(self, other) + if type(other) ~= "table" then return false end + if not other.Name then return false end + return other.Name == self.Name +end +debug.getregistry()["SFTerrain"] = meta +local terrains = {} +StormFox2.Terrain = {} +-- Creates a new terrain type and stores it +function StormFox2.Terrain.Create( sName ) + local t = {} + t.Name = sName + setmetatable(t, meta) + terrains[sName] = t + t.swap = {} + return t +end +-- Cur terrain +local CURRENT_TERRAIN +function StormFox2.Terrain.GetCurrent() + return CURRENT_TERRAIN +end +-- Returns the terrain. +function StormFox2.Terrain.Get( sName ) + if not sName then return end + return terrains[sName] +end + +-- Makes the terrain stay until this function returns true or another terrain overwrites. +function meta:LockUntil( fFunc ) + self.lock = fFunc +end +-- Sets the ground texture. e.i; snow +function meta:SetGroundTexture( iTexture, bOnlyGround ) + self.ground = iTexture + self.only_ground = bOnlyGround +end +-- Adds a texture swap. +function meta:AddTextureSwap( mMaterial, basetexture, basetextire2 ) + if type(mMaterial) ~= "IMaterial" then + mMaterial = Material(mMaterial) + end + if not basetexture and not basetextire2 then return end + self.swap[mMaterial] = { basetexture, basetextire2 } +end +-- Makes footprints. Allows to overwrite footstep sounds. +function meta:MakeFootprints( bool, sndList, sndName, OnPrint ) + self.footprints = bool + if sndList or sndName then + self.footprintSnds = {sndList, sndName} + end + self.footstepFunc = OnPrint + self.footstepLisen = bool or sndList or sndName or OnPrint +end + +-- A function that renders a window-texure. (Weather will trump this) +function meta:RenderWindow( fFunc ) + self.windRender = fFunc +end +-- A function that renders a window-texure. (Weather will trump this) +function meta:RenderWindowRefract( fFunc ) + self.windRenderRef = fFunc +end +-- A function that renders a window-texure. (Weather will trump this) +function meta:RenderWindow64x64( fFunc ) + self.windRender64 = fFunc +end +-- A function that renders a window-texure. (Weather will trump this) +function meta:RenderWindowRefract64x64( fFunc ) + self.windRenderRef64 = fFunc +end + +-- Texture handler +_STORMFOX_TEXCHANGES = _STORMFOX_TEXCHANGES or {} -- List of changed materials. +_STORMFOX_TEXORIGINAL = _STORMFOX_TEXORIGINAL or {} -- This is global, just in case. +local footStepLisen = false -- If set to true, will enable footprints. + +local function StringTex(iTex) + if not iTex then return end + if type(iTex) == "string" then return iTex end + return iTex:GetName() +end + +local function HasChanged( self, materialTexture ) + local mat = self:GetName() or "unknown" + local b = materialTexture == "$basetexture2" and 2 or 1 + return _STORMFOX_TEXCHANGES[mat] and _STORMFOX_TEXCHANGES[mat][b] or false +end + +function StormFox2.Terrain.HasMaterialChanged( iMaterial ) + local mat = iMaterial:GetName() or iMaterial + return _STORMFOX_TEXCHANGES[mat] and _STORMFOX_TEXCHANGES[mat] +end + +function StormFox2.Terrain.GetOriginalTexture( iMaterial ) + local mat = iMaterial:GetName() or iMaterial + return _STORMFOX_TEXORIGINAL[mat] and _STORMFOX_TEXORIGINAL[mat][1] +end + +-- We're going to overwrite SetTexture. As some mods might change the default texture. +local mat_meta = FindMetaTable("IMaterial") +STORMFOX_TEX_APPLY = STORMFOX_TEX_APPLY or mat_meta.SetTexture +function mat_meta:SetTexture(materialTexture, texture) + -- Check if it is basetexutre or basetexture2 we're changing. + if materialTexture ~= "$basetexture" and materialTexture ~= "$basetexture2" then + return STORMFOX_TEX_APPLY( self, materialTexture, texture ) + end + -- Overwrite the original texture list. + local mat = self:GetName() or "unknown" + if not _STORMFOX_TEXORIGINAL[mat] then _STORMFOX_TEXORIGINAL[mat] = {} end + if materialTexture == "$basetexture" then + _STORMFOX_TEXORIGINAL[mat][1] = StringTex(texture) + else + _STORMFOX_TEXORIGINAL[mat][2] = StringTex(texture) + end + -- If we havn't changed the texture, allow change. + if not HasChanged(self, materialTexture) then + return STORMFOX_TEX_APPLY( self, materialTexture, texture ) + end +end + +-- Resets the material. Returns false if unable to reset. +local function ResetMaterial( self ) + local mat = self:GetName() or "unknown" + if not _STORMFOX_TEXCHANGES[mat] or not _STORMFOX_TEXORIGINAL[mat] then return false end + if _STORMFOX_TEXCHANGES[mat][1] and _STORMFOX_TEXORIGINAL[mat][1] then + STORMFOX_TEX_APPLY( self, "$basetexture", _STORMFOX_TEXORIGINAL[mat][1] ) + end + if _STORMFOX_TEXCHANGES[mat][2] and _STORMFOX_TEXORIGINAL[mat][2] then + STORMFOX_TEX_APPLY( self, "$basetexture2", _STORMFOX_TEXORIGINAL[mat][2] ) + end + _STORMFOX_TEXCHANGES[mat] = nil + return true +end + +-- Set the material +local function SetMat(self, tex1, tex2) + if not tex1 and not tex2 then return end + local mat = self:GetName() or "unknown" + -- Save the default texture + if not _STORMFOX_TEXORIGINAL[mat] then _STORMFOX_TEXORIGINAL[mat] = {} end + if tex1 and not _STORMFOX_TEXORIGINAL[mat][1] then + _STORMFOX_TEXORIGINAL[mat][1] = StringTex(self:GetTexture("$basetexture")) + end + if tex2 and not _STORMFOX_TEXORIGINAL[mat][2] then + _STORMFOX_TEXORIGINAL[mat][2] = StringTex(self:GetTexture("$basetexture2")) + end + -- Set texture + if tex1 then + if CLIENT then + STORMFOX_TEX_APPLY( self, "$basetexture", tex1 ) + end + if not _STORMFOX_TEXCHANGES[ mat ] then _STORMFOX_TEXCHANGES[ mat ] = {} end + _STORMFOX_TEXCHANGES[ mat ][ 1 ] = true + end + if tex2 then + if CLIENT then + STORMFOX_TEX_APPLY( self, "$basetexture2", tex2 ) + end + if not _STORMFOX_TEXCHANGES[ mat ] then _STORMFOX_TEXCHANGES[ mat ] = {} end + _STORMFOX_TEXCHANGES[ mat ][ 2 ] = true + end +end + +-- Resets the terrain to default. +function StormFox2.Terrain.Reset( bNoUpdate ) + --print("Reset") + if SERVER and not bNoUpdate then + StormFox2.Map.CallLogicRelay("terrain_clear") + end + CURRENT_TERRAIN = nil + if SERVER and not bNoUpdate then + net.Start(StormFox2.Net.Terrain) + net.WriteString( "" ) + net.Broadcast() + end + if next(_STORMFOX_TEXCHANGES) == nil then return end + for tex,_ in pairs( _STORMFOX_TEXCHANGES ) do + local mat = Material( tex ) + if not ResetMaterial( mat ) then + StormFox2.Warning( "Can't reset [" .. tostring( mat ) .. "]." ) + end + end + _STORMFOX_TEXCHANGES = {} +end + +-- Sets the terrain. (This should only be done serverside) +function StormFox2.Terrain.Set( sName ) + -- Apply terrain. + local t = StormFox2.Terrain.Get( sName ) + if not t then + StormFox2.Terrain.Reset() + return false + end + StormFox2.Terrain.Reset( true ) + t:Apply() + if SERVER then + StormFox2.Map.CallLogicRelay( "terrain_" .. string.lower(sName) ) + end + return true +end + +-- Reapplies the current terrain +function StormFox2.Terrain.Update() + local terrain = StormFox2.Terrain.GetCurrent() + if not terrain then return end + StormFox2.Terrain.Reset( true ) + terrain:Apply() +end + +local NO_TYPE = -1 +local DIRTGRASS_TYPE = 0 +local ROOF_TYPE = 1 +local ROAD_TYPE = 2 +local PAVEMENT_TYPE = 3 + +local function checkType(n, bOnlkyG) + if not n then return false end + if n == 0 then return true end + if n == 1 and not bOnlkyG then return true end + return false +end + +-- Applies the terrain (This won't reset old terrain) +function meta:Apply() + CURRENT_TERRAIN = self + if SERVER then + net.Start(StormFox2.Net.Terrain) + net.WriteString( CURRENT_TERRAIN.Name ) + net.Broadcast() + end + -- Swap materials + if self.swap then + for mat,tab in pairs( self.swap ) do + SetMat( mat, tab[1], tab[2] ) + end + end + -- Set ground + if self.ground then + for materialName,tab in pairs( StormFox2.Map.GetTextureTree() ) do + local mat = Material( materialName ) + local a,b = checkType(tab[1], self.only_ground), checkType(tab[2], self.only_ground) + SetMat( mat, a and self.ground,b and self.ground) + end + end + footStepLisen = self.footprints or self.footprintSnds +end + +-- NET +if SERVER then + net.Receive(StormFox2.Net.Terrain, function(len, ply) -- OI, what terrain? + net.Start(StormFox2.Net.Terrain) + net.WriteString( CURRENT_TERRAIN and CURRENT_TERRAIN.Name or "" ) + net.Send(ply) + end) +else + net.Receive(StormFox2.Net.Terrain, function(len) + local sName = net.ReadString() + local b = StormFox2.Terrain.Set( sName ) + --"Terrain Recived: ", sName, b) + + end) + -- Ask the server + hook.Add("StormFox2.InitPostEntity", "StormFox2.terrain.init", function() + timer.Simple(1, function() + net.Start(StormFox2.Net.Terrain) + net.WriteBit(1) + net.SendToServer() + end) + end) +end + +-- A bit ballsy, but lets try. +hook.Add("ShutDown","StormFox2.Terrain.Clear",function() + StormFox2.Msg("Reloading all changed materials ..") + for k, _ in pairs( _STORMFOX_TEXORIGINAL ) do + RunConsoleCommand("mat_reloadmaterial", k) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_util.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_util.lua new file mode 100644 index 0000000..b79c859 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_util.lua @@ -0,0 +1,80 @@ +--[[------------------------------------------------------------------------- +Useful functions +---------------------------------------------------------------------------]] + +StormFox2.util = {} +local cache = {} +--[[----------------------------------------------------------------- +Returns the OBBMins and OBBMaxs of a model. +---------------------------------------------------------------------------]] +function StormFox2.util.GetModelSize(sModel) + if cache[sModel] then return cache[sModel][1],cache[sModel][2] end + if not file.Exists(sModel,"GAME") then + cache[sModel] = {Vector(0,0,0),Vector(0,0,0)} + return cache[sModel] + end + local f = file.Open(sModel,"r", "GAME") + f:Seek(104) + local hullMin = Vector( f:ReadFloat(),f:ReadFloat(),f:ReadFloat()) + local hullMax = Vector( f:ReadFloat(),f:ReadFloat(),f:ReadFloat()) + f:Close() + cache[sModel] = {hullMin,hullMax} + return hullMin,hullMax +end + +if CLIENT then + --[[----------------------------------------------------------------- + Calcview results + ---------------------------------------------------------------------------]] + local view = {} + view.pos = Vector(0,0,0) + view.ang = Angle(0,0,0) + view.fov = 0 + view.drawviewer = false + local otherPos, otherAng, otherFOV + local a = true + hook.Add("RenderScene", "StormFox2.util.EyeHack", function(pos, ang,fov) + if not a then return end + otherPos, otherAng, otherFOV = pos, ang,fov + a = false + end) + + hook.Add("PostRender", "StormFox2.util.EyeHack", function() + local tab = render.GetViewSetup and render.GetViewSetup() or {} + view.pos = tab.origin or otherPos or EyePos() + view.ang = tab.angles or otherAng or EyeAngles() + view.fov = tab.fov or otherFOV or 90 + view.drawviewer = LocalPlayer():ShouldDrawLocalPlayer() + a = true + end) + --[[----------------------------------------------------------------- + Returns the last calcview result. + ---------------------------------------------------------------------------]] + function StormFox2.util.GetCalcView() + return view + end + --[[----------------------------------------------------------------- + Returns the last camera position. + ---------------------------------------------------------------------------]] + function StormFox2.util.RenderPos() + return view.pos or EyePos() + end + + --[[----------------------------------------------------------------- + Returns the current viewentity + ---------------------------------------------------------------------------]] + local viewEntity + hook.Add("Think", "StormFox2.util.ViewEnt", function() + local lp = LocalPlayer() + if not IsValid(lp) then return end + local p = lp:GetViewEntity() or lp + if p.InVehicle and p:InVehicle() and p == lp then + viewEntity = p:GetVehicle() or p + else + viewEntity = p + end + end) + function StormFox2.util.ViewEntity() + return IsValid(viewEntity) and viewEntity or LocalPlayer() + end +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_version.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_version.lua new file mode 100644 index 0000000..189a77b --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_version.lua @@ -0,0 +1,33 @@ +-- Checks the newest version +if SERVER then + -- Checks the workshop page for version number. + local function RunCheck() + http.Fetch(StormFox2.WorkShopURL, function(code) + local lV = tonumber(string.match(code, "Version:(.-)<")) + if not lV then return end -- Unable to locate last version + if StormFox2.Version >= lV then return end -- Up to date + StormFox2.Msg("Version " .. lV .. " is out!") + StormFox2.Network.Set("stormfox_newv", lV) + cookie.Set("sf_nextv", lV) + end) + end + local function RunLogic() + -- Check if a newer version is out + local lV = cookie.GetNumber("sf_nextv", StormFox2.Version) + if cookie.GetNumber("sf_nextvcheck", 0) > os.time() then + if lV > StormFox2.Version then + StormFox2.Msg("Version " .. lV .. " is out!") + StormFox2.Network.Set("stormfox_newv", lV) + end + else + RunCheck() + cookie.Set("sf_nextvcheck", os.time() + 129600) -- Check in 1½ day + end + end + hook.Add("stormfox2.preinit", "stormfox2.checkversion", RunLogic) +end + +-- Will return a version-number, if a new version is detected +function StormFox2.NewVersion() + return StormFox2.Data.Get("stormfox_newv") +end diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_weather_meta.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_weather_meta.lua new file mode 100644 index 0000000..fdec0d7 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_weather_meta.lua @@ -0,0 +1,212 @@ + +StormFox2.Weather = {} +local Weathers = {} +-- Diffrent stamps on where the sun are. (Remember, SF goes after sunrise/set) + SF_SKY_DAY = 0 + SF_SKY_SUNRISE = 1 + SF_SKY_SUNSET = 2 + SF_SKY_CEVIL = 3 + SF_SKY_BLUE_HOUR = 4 + SF_SKY_NAUTICAL = 5 + SF_SKY_ASTRONOMICAL = 6 + SF_SKY_NIGHT = 7 + +local w_meta = {} +w_meta.__index = w_meta +w_meta.__tostring = function(self) return "SF_WeatherType[" .. (self.Name or "Unknwon") .. "]" end +w_meta.MetaName = "SF-Weather" +debug.getregistry()["SFWeather"] = w_meta + +-- function for the generator. Returns true to allow. Function will be called with (day_temperature, time_start, time_duration, percent) +function w_meta:SetRequire(fFunc) + self.Require = fFunc +end + +function w_meta:SetInit(fFunc) + self.Init = fFunc +end + +function w_meta:SetOnChange(fFunc) + self.OnChange = fFunc +end + +function w_meta:IsValid() + return true +end + +function StormFox2.Weather.Add( sName, sInherit ) + if Weathers[sName] then return Weathers[sName] end + local t = {} + t.ID = table.Count(Weathers) + 1 + t.Name = sName + setmetatable(t, w_meta) + if sName ~= "Clear" then -- Clear shouldn't inherit itself + t.Inherit = sInherit or "Clear" + end + Weathers[sName] = t + t.Function = {} + t.Static = {} + t.Dynamic = {} + t.SunStamp = {} + return t +end + +function StormFox2.Weather.Get( sName ) + return Weathers[sName] +end + +function StormFox2.Weather.GetAll() + return table.GetKeys( Weathers ) +end + +function StormFox2.Weather.GetAllSpawnable() + local t = {} + for w, v in pairs( Weathers ) do + if v.spawnable and w ~= "Clear" then -- clear is default + table.insert(t, w) + end + end + return t +end + +local keys = {} +local l_e,l_c, c_c = -1,0 + +function w_meta:Set(sKey,zVariable, bStatic) + keys[sKey] = true + l_c = CurTime() + if type(zVariable) == "function" then + self.Function[sKey] = zVariable + elseif bStatic then + self.Static[sKey] = zVariable + else + self.Dynamic[sKey] = zVariable + end +end + +local r_list = {"Terrain", "windRender", "windRenderRef", "windRender64", "windRenderRef64"} +function StormFox2.Weather.GetKeys() + if l_c == l_e then + return c_c + end + for k,v in ipairs(r_list) do + keys[v] = nil + end + l_e = l_c + c_c = table.GetKeys(keys) + return c_c +end + +-- This function inserts a variable into a table. Using the STAMP as key. +function w_meta:SetSunStamp(sKey, zVariable, STAMP) + keys[sKey] = true + l_c = CurTime() + if not self.SunStamp[sKey] then self.SunStamp[sKey] = {} end + self.SunStamp[sKey][STAMP] = zVariable +end +-- Returns a copy of all variables with the given sunstamp, to the given sunstamp. +function w_meta:CopySunStamp( from_STAMP, to_STAMP ) + for sKey,v in pairs(self.SunStamp) do + if type(v) ~= "table" then continue end + if not v[from_STAMP] then continue end + self.SunStamp[sKey][to_STAMP] = v[from_STAMP] or nil + end +end + +do + local in_list = {} + --[[ + Returns a variable + - If the variable is a function. It will be called with the current stamp. + - Second argument will tell SF it is static and shouldn't be mixed + ]] + function w_meta:Get(sKey, SUNSTAMP ) + -- Fallback to day-stamp, if Last Steamp is nil- + if not SUNSTAMP then + SUNSTAMP = StormFox2.Sky.GetLastStamp() or SF_SKY_DAY + end + if self.Function[sKey] then + return self.Function[sKey]( SUNSTAMP ) + elseif self.SunStamp[sKey] then + if self.SunStamp[sKey][SUNSTAMP] ~= nil then + return self.SunStamp[sKey][SUNSTAMP] + end + -- This sunstamp isn't set, try and elevate stamp and check + if SUNSTAMP >= SF_SKY_CEVIL then + for i = SF_SKY_CEVIL + 1, SF_SKY_NIGHT do + if self.SunStamp[sKey][i] then + return self.SunStamp[sKey][i] + end + end + else + for i = SF_SKY_CEVIL - 1, SF_SKY_DAY, -1 do + if self.SunStamp[sKey][i] then + return self.SunStamp[sKey][i] + end + end + end + elseif self.Static[sKey] then + return self.Static[sKey], true + elseif self.Dynamic[sKey] then + return self.Dynamic[sKey] + end + if self.Name == "Clear" then return end + -- Check if we inherit + if not self.Inherit then return nil end + if not Weathers[self.Inherit] then return nil end -- Inherit is invalid + if in_list[self.Inherit] == true then -- Loop detected + StormFox2.Warning("WeatherData loop detected! multiple occurences of : " .. self.Inherit) + return + end + in_list[self.Name] = true + local a,b,c,d,e = Weathers[self.Inherit]:Get(sKey, SUNSTAMP) + in_list = {} + return a,b,c,d,e + end +end + +-- Sets the terrain for the weather. This can also be a function that returns a terrain object. +function w_meta:SetTerrain( zTerrain ) + self:Set( "Terrain", zTerrain ) +end + +-- A function that renders a window-texure +function w_meta:RenderWindow( fFunc ) + self._RenderWindow = fFunc +end + +function w_meta:RenderWindowRefract( fFunc ) + self._RenderWindowRefract = fFunc +end + +function w_meta:RenderWindow64x64( fFunc ) + self._RenderWindow64x64 = fFunc +end + +function w_meta:RenderWindowRefract64x64( fFunc ) + self._RenderWindowRefract64x64 = fFunc +end + +--[[------------------------------------------------------------------ + Returns the "lightlevel" of the skybox in a range of 0-255. +---------------------------------------------------------------------------]] +function StormFox2.Weather.GetLuminance() + local Col = StormFox2.Mixer.Get("bottomColor") or Color(255,255,255) + return 0.2126 * Col.r + 0.7152 * Col.g + 0.0722 * Col.b +end + +-- Load the weathers once lib is done. +hook.Add("stormfox2.postlib", "stormfox2.loadweathers", function() + + StormFox2.Setting.AddSV("moonsize",30,nil,"Effects", 5, 500) + + hook.Run("stormfox2.preloadweather", w_meta) + for _,fil in ipairs(file.Find("stormfox2/weathers/*.lua","LUA")) do + if SERVER then + AddCSLuaFile("stormfox2/weathers/" .. fil) + end + pcall(include,"stormfox2/weathers/" .. fil) + end + StormFox2.Weather.Loaded = true + hook.Run("stormfox2.postloadweather") +end) diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_wind.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_wind.lua new file mode 100644 index 0000000..fc8028b --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sh_wind.lua @@ -0,0 +1,545 @@ +StormFox2.Wind = StormFox2.Wind or {} +local min,max,sqrt,abs = math.min,math.max,math.sqrt,math.abs +local function ET(pos,pos2,mask,filter) + local t = util.TraceLine( { + start = pos, + endpos = pos + pos2, + mask = mask, + filter = filter + } ) + t.HitPos = t.HitPos or (pos + pos2) + return t,t.HitSky +end + +-- Settings +hook.Add("stormfox2.postlib", "stormfox2.windSettings",function() + StormFox2.Setting.AddSV("windmove_players",true,nil,"Weather") + StormFox2.Setting.AddSV("windmove_foliate",true,nil,"Weather") + StormFox2.Setting.AddSV("windmove_props",false,nil,"Weather") + StormFox2.Setting.AddSV("windmove_props_break",true,nil,"Weather") + StormFox2.Setting.AddSV("windmove_props_unweld",true,nil,"Weather") + StormFox2.Setting.AddSV("windmove_props_unfreeze",true,nil,"Weather") + StormFox2.Setting.AddSV("windmove_props_max",100,nil,"Weather") + StormFox2.Setting.AddSV("windmove_props_makedebris",true,nil,"Weather") + hook.Remove("stormfox2.postlib", "stormfox2.windSettings") +end) + + +if SERVER then + hook.Add("stormfox2.postlib", "stormfox2.svWindInit",function() + if not StormFox2.Ent.env_winds then return end + for _,ent in ipairs( StormFox2.Ent.env_winds ) do + ent:SetKeyValue('windradius',-1) -- Make global + ent:SetKeyValue('maxgustdelay', 20) + ent:SetKeyValue('mingustdelay', 10) + ent:SetKeyValue('gustduration', 5) + end + hook.Remove("stormfox2.postlib", "stormfox2.svWindInit") + end) + --[[------------------------------------------------------------------------- + Sets the wind force. Second argument is the lerp-time. + ---------------------------------------------------------------------------]] + function StormFox2.Wind.SetForce( nForce, nLerpTime ) + StormFox2.Network.Set( "Wind", nForce, nLerpTime ) + end + --[[------------------------------------------------------------------------- + Sets the wind yaw. Second argument is the lerp-time. + ---------------------------------------------------------------------------]] + function StormFox2.Wind.SetYaw( nYaw, nLerpTime ) + StormFox2.Network.Set( "WindAngle", nYaw, nLerpTime ) + end +end + +--[[------------------------------------------------------------------------- +Returns the wind yaw-direction +---------------------------------------------------------------------------]] +function StormFox2.Wind.GetYaw() + return StormFox2.Data.Get( "WindAngle", 0 ) +end +--[[------------------------------------------------------------------------- +Returns the wind force. +---------------------------------------------------------------------------]] +function StormFox2.Wind.GetForce() + return StormFox2.Data.Get( "Wind", 0 ) +end + +-- Beaufort scale and Saffir–Simpson hurricane scale +local bfs = {} + bfs[0] = "sf_winddescription.calm" + bfs[0.3] = "sf_winddescription.light_air" + bfs[1.6] = "sf_winddescription.light_breeze" + bfs[3.4] = "sf_winddescription.gentle_breeze" + bfs[5.5] = "sf_winddescription.moderate_breeze" + bfs[8] = "sf_winddescription.fresh_breeze" + bfs[10.8] = "sf_winddescription.strong_breeze" + bfs[13.9] = "sf_winddescription.near_gale" + bfs[17.2] = "sf_winddescription.gale" + bfs[20.8] = "sf_winddescription.strong_gale" + bfs[24.5] = "sf_winddescription.storm" + bfs[28.5] = "sf_winddescription.violent_storm" + bfs[32.7] = "sf_winddescription.hurricane" -- Also known as cat 1 + bfs[43] = "sf_winddescription.cat2" + bfs[50] = "sf_winddescription.cat3" + bfs[58] = "sf_winddescription.cat4" + bfs[70] = "sf_winddescription.cat5" + local bfkey = table.GetKeys(bfs) + table.sort(bfkey,function(a,b) return a < b end) +--[[------------------------------------------------------------------------- +Returns the given or current wind in beaufort-scale and sf_winddescription.. +---------------------------------------------------------------------------]] +function StormFox2.Wind.GetBeaufort(ms) + local n = ms or StormFox2.Wind.GetForce() + local Beaufort, Description = 0, "sf_winddescription.calm" + for k,kms in ipairs( bfkey ) do + if kms <= n then + Beaufort, Description = k - 1, bfs[ kms ] + else + break + end + end + return Beaufort, Description +end +-- Spawning env_wind won't work. Therefor we need to use the cl_tree_sway_dir on the client if it's not on the map. + if CLIENT then + local function updateWind() + if StormFox2.Map.HadClass( "env_wind" ) then return end + local nw = math.min(StormFox2.Wind.GetForce() * 0.6, 21) + local ra = math.rad( StormFox2.Data.Get( "WindAngle", 0 ) ) + local wx,wy = math.cos(ra) * nw,math.sin(ra) * nw + RunConsoleCommand("cl_tree_sway_dir",wx,wy) + end + hook.Add("StormFox2.Wind.Change","StormFox2.Wind.CLFix",function(windNorm, wind) + if not StormFox2.Setting.GetCache("windmove_foliate", true) then return end + updateWind() + end) + StormFox2.Setting.Callback("windmove_foliate", function(b) + if b then + updateWind() + else + RunConsoleCommand("cl_tree_sway_dir",0,0) + end + end) + else + local function updateWind(nw, ang) + if not StormFox2.Ent.env_winds then return end + local min = nw * .6 + local max = nw * .8 + local gust = math.min(nw, 5.5) + for _,ent in ipairs( StormFox2.Ent.env_winds ) do + if not IsValid(ent) then continue end + --print(ent, max, min ,gust) + if ang then ent:Fire('SetWindDir', ang) end + ent:SetKeyValue('minwind', min) + ent:SetKeyValue('maxwind', max) + ent:SetKeyValue('gustdirchange', math.max(0, 21 - nw)) + ent:SetKeyValue('maxgust', gust) + ent:SetKeyValue('mingust', gust * .8) + end + end + hook.Add("StormFox2.Wind.Change","StormFox2.Wind.SVFix",function(windNorm, wind) + local nw = StormFox2.Wind.GetForce() * 2 + local ang = StormFox2.Data.Get( "WindAngle", 0 ) + updateWind(nw, ang) + end) + StormFox2.Setting.Callback("windmove_foliate", function(b) + if not StormFox2.Ent.env_winds then return end + local ang = StormFox2.Data.Get( "WindAngle", 0 ) + if not b then + updateWind(0, ang) + return + end + local nw = StormFox2.Wind.GetForce() * 2 + updateWind(nw, ang) + end) + end +--[[------------------------------------------------------------------------- +Calculate and update the wind direction +---------------------------------------------------------------------------]] +local windNorm = Vector(0,0,-1) +local windVec = Vector(0,0,0) +local wind,windAng = 0,-1 +local function calcfunc() + local owind = StormFox2.Data.Get("Wind",0) + local nwind = owind * 0.2 + local nang = StormFox2.Data.Get("WindAngle",0) + if nwind == wind and nang == windAng then return end -- Nothing changed + wind = nwind + windAng = nang + windNorm = Angle( 90 - sqrt(wind) * 10 ,windAng,0):Forward() + windVec = windNorm * wind + windNorm:Normalize() + --[[----------------------------------------------------------------- + Gets called when the wind changes. + ---------------------------------------------------------------------------]] + hook.Run("StormFox2.Wind.Change", windNorm, owind) +end + +-- If the wind-data changes, is changing or is done changing. Reclaculate the wind. +timer.Create("StormFox2.Wind.Update", 1, 0, function() + if not StormFox2.Data.IsLerping("Wind") and not StormFox2.Data.IsLerping("WindAngle") then return end + calcfunc() +end) +local function dataCheck(sKey,sVar) + if sKey ~= "Wind" and sKey ~= "WindAngle" then return end + calcfunc() +end +hook.Add("StormFox2.data.change","StormFox2.Wind.Calc",dataCheck) +hook.Add("StormFox2.data.lerpend", "StormFox2.Wind.Calcfinish", dataCheck) + +--[[------------------------------------------------------------------------- +Returns the wind norm. +---------------------------------------------------------------------------]] +function StormFox2.Wind.GetNorm() + return windNorm +end +--[[------------------------------------------------------------------------- +Returns the wind vector. +---------------------------------------------------------------------------]] +function StormFox2.Wind.GetVector() + return windVec +end +--[[------------------------------------------------------------------------- +Checks if an entity is out in the wind (or rain). Caches the result for 1 second. +---------------------------------------------------------------------------]] +local function IsMaterialEmpty( t ) + return t.HitTexture == "TOOLS/TOOLSINVISIBLE" or t.HitTexture == "**empty**" or t.HitTexture == "TOOLS/TOOLSNODRAW" +end +local function ET_II(pos, vec, mask, filter) -- Ignore invisble brushes 'n stuff' + local lastT + for i = 1, 5 do + local t, a = ET(pos, vec, mask, filter) + if not IsMaterialEmpty(t) and t.Hit then return t, a end + lastT = lastT or t + pos = t.HitPos + end + lastT.HitSky = true + return lastT +end +local max_dis = 32400 +function StormFox2.Wind.IsEntityInWind(eEnt,bDont_cache) + if not IsValid(eEnt) then return end + if not bDont_cache then + if eEnt.sf_wind_var and (eEnt.sf_wind_var[2] or 0) > CurTime() then + return eEnt.sf_wind_var[1],windNorm + else + eEnt.sf_wind_var = {} + end + end + local pos = eEnt:OBBCenter() + eEnt:GetPos() + local tr = ET_II(pos, windNorm * -640000, MASK_SHOT, eEnt) + local hitSky = tr.HitSky + local dis = tr.HitPos:DistToSqr( pos ) + if not hitSky and dis >= max_dis then -- So far away. The wind would had gone around. Check if we're outside. + local tr = ET(pos,Vector(0,0,640000),MASK_SHOT,eEnt) + hitSky = tr.HitSky + end + if not bDont_cache then + eEnt.sf_wind_var[1] = hitSky + eEnt.sf_wind_var[2] = CurTime() + 1 + end + return hitSky,windNorm +end + +-- Wind sounds +if CLIENT then + local windSnd = -1 -- -1 = none, 0 = outside, 0+ Distance to outside + local windGusts = {} + local maxVol = 1.5 + local function AddGuest( snd, vol, duration ) + if windGusts[snd] then return end + if not duration then duration = SoundDuration( snd ) end + windGusts[snd] = {vol * 0.4, CurTime() + duration - 1} + end + + timer.Create("StormFox2.Wind.Snd", 1, 0, function() + windSnd = -1 + if StormFox2.Wind.GetForce() <= 0 then return end + local env = StormFox2.Environment.Get() + if not env or (not env.outside and not env.nearest_outside) then return end + if not env.outside and env.nearest_outside then + local view = StormFox2.util.RenderPos() + windSnd = StormFox2.util.RenderPos():Distance(env.nearest_outside) + else + windSnd = 0 + end + -- Guests + local vM = (400 - windSnd) / 400 + if vM <= 0 then return end + local wForce = StormFox2.Wind.GetForce() + if math.random(50) > 40 then + if wForce > 17 and math.random(1,2) > 1 then + AddGuest("ambient/wind/windgust.wav",math.Rand(0.8, 1) * vM) + elseif wForce > 14 and wForce < 30 then + AddGuest("ambient/wind/wind_med" .. math.random(1,2) .. ".wav", math.min(maxVol, wForce / 30) * vM) + end + end + if wForce > 27 and math.random(50) > 30 then + AddGuest("ambient/wind/windgust_strong.wav",math.min(maxVol, wForce / 30) * vM) + end + end) + + -- Cold "empty" wind: ambience/wind1.wav + -- ambient/wind/wind1.wav + -- ambient/wind/wind_rooftop1.wav + -- ambient/wind/wind1.wav + -- StormFox2.Ambience.ForcePlay + hook.Add("StormFox2.Ambiences.OnSound", "StormFox2.Ambiences.Wind", function() + if windSnd < 0 then return end -- No wind + local wForce = StormFox2.Wind.GetForce() * 0.25 + local vM = (400 - windSnd) / 400 + if vM <= 0 then return end + -- Main loop + StormFox2.Ambience.ForcePlay( "ambient/wind/wind_rooftop1.wav", math.min((wForce - 1) / 35, maxVol) * vM * 0.8, math.min(1.2, 0.9 + wForce / 100) ) + -- Wind gusts + for snd,data in pairs(windGusts) do + if data[2] <= CurTime() then + windGusts[snd] = nil + else + StormFox2.Ambience.ForcePlay( snd, 0.2 * data[1] + math.Rand(0, 0.1) ) + end + end + end) +else + -- Flag models + local flags = {} + local flag_models = {} + flag_models["models/props_fairgrounds/fairgrounds_flagpole01.mdl"] = 90 + flag_models["models/props_street/flagpole_american.mdl"] = 90 + flag_models["models/props_street/flagpole_american_tattered.mdl"] = 90 + flag_models["models/props_street/flagpole.mdl"] = 90 + flag_models["models/mapmodels/flags.mdl"] = 0 + flag_models["models/props/de_cbble/cobble_flagpole.mdl"] = 180 + flag_models["models/props/de_cbble/cobble_flagpole_2.mdl"] = 225 + flag_models["models/props/props_gameplay/capture_flag.mdl"] = 270 + flag_models["models/props_medieval/pendant_flag/pendant_flag.mdl"] = 0 + flag_models["models/props_moon/parts/moon_flag.mdl"] = 0 + local function FlagInit() + -- Check if there are any flags on the map + for _,ent in pairs(ents.GetAll()) do + if not ent:CreatedByMap() then continue end + -- Check the angle + if math.abs(ent:GetAngles():Forward():Dot(Vector(0,0,1))) > 5 then continue end + if not flag_models[ent:GetModel()] then continue end + table.insert(flags,ent) + end + if #flags > 0 then -- Only add the hook if there are flags on the map. + hook.Add("StormFox2.data.change","StormFox2.flagcontroller",function(key,var) + if key == "WindAngle" then + --print("Windang", var) + for _,ent in ipairs(flags) do + if not IsValid(ent) then continue end + local y = flag_models[ent:GetModel()] or 0 + ent:SetAngles(Angle(0,var + y,0)) + end + elseif key == "Wind" then + --print("Wind", var) + for _,ent in ipairs(flags) do + if not IsValid(ent) then continue end + ent:SetPlaybackRate(math.Clamp(var / 7,0.5,10)) + end + end + end) + end + end + hook.Add("StormFox2.PostEntityScan", "StormFox2.Wind.FlagInit", FlagInit) +end + +if CLIENT then return end +-- Wind movment + local function windMove(ply, mv, cmd ) + if not StormFox2.Setting.GetCache("windmove_players") then return end + if( ply:GetMoveType() != MOVETYPE_WALK ) then return end + local wF = (StormFox2.Wind.GetForce() - 15) / 11 + if wF <= 0 then return end + if not StormFox2.Wind.IsEntityInWind(ply) then return end -- Not in wind + -- Calc windforce + local r = math.rad( StormFox2.Wind.GetYaw() - ply:GetAngles().y ) + local fS = math.cos( r ) * wF + local sS = math.sin( r ) * wF + + if mv:GetForwardSpeed() == 0 and mv:GetSideSpeed() == 0 then -- Not moving + --mv:SetSideSpeed( - sS / 10) Annoying + --mv:SetForwardSpeed( - fS / 10) + else + -- GetForwardMove() returns 10000y. We need to figure out the speed first. + local running, walking = mv:KeyDown( IN_SPEED ), mv:KeyDown( IN_WALK ) + local speed = running and ply:GetRunSpeed() or walking and ply:GetSlowWalkSpeed() or ply:GetWalkSpeed() + + local forward = math.Clamp(mv:GetForwardSpeed(), -speed, speed) + local side = math.Clamp(mv:GetSideSpeed(), -speed, speed) + if forward~=0 and side~=0 then + forward = forward * .7 + side = side * .7 + end + -- Now we modify them. We don't want to move back. + if forward > 0 and fS < 0 then + forward = math.max(0, forward / -fS) + elseif forward < 0 and fS > 0 then + forward = math.min(0, forward / fS) + end + if side > 0 and sS > 0 then + side = math.max(0, side / sS) + forward = forward + fS * 20 + elseif side < 0 and sS < 0 then + side = math.min(0, side / -sS) + forward = forward + fS * 20 + end + -- Apply the new speed + mv:SetForwardSpeed( forward ) + cmd:SetForwardMove( forward ) + mv:SetSideSpeed( side ) + cmd:SetSideMove( side ) + end + end + hook.Add("SetupMove", "windtest", windMove) + +-- Wind proppush + local move_list = { + ["rpg_missile"] = true, + ["npc_grenade_frag"] = true, + ["npc_grenade_bugbait"] = true, -- Doesn't work + ["gmod_hands"] = false, + ["gmod_tool"] = false + } + local function CanMoveClass( ent ) + if( IsValid( ent:GetParent()) ) then return end + local class = ent:GetClass() + if( move_list[class] == false ) then return false end + return string.match(class,"^prop_") or string.match(class,"^gmod_") or move_list[class] + end + + local function ApplyWindEffect( ent, wind, windnorm ) + if ent:GetPersistent() then return end + if(wind < 5) then return end + -- Make a toggle + local vol + local phys = ent:GetPhysicsObject() + if(not phys or not IsValid(phys)) then -- No physics + return + end + vol = phys:GetVolume() or 15 + -- Check Move + local windPush = windnorm * 1.37 * vol * .66 + --windnorm * 5.92 * (vol / 50) + local windRequ = phys:GetInertia() + windRequ = max(windRequ.x,windRequ.y) + if max(abs(windPush.x),abs(windPush.y)) < windRequ then -- Can't move + return + end + local class = ent:GetClass() + if( class != "npc_grenade_frag") then + windPush.x = math.Clamp(windPush.x, -5500, 5500) + windPush.y = math.Clamp(windPush.y, -5500, 5500) + end + -- Unfreeze + if(wind > 20) then + if( StormFox2.Setting.GetCache("windmove_props_unfreeze", true) ) then + if not phys:IsMoveable() then + phys:EnableMotion(true) + end + end + end + -- Unweld + if(wind > 30) then + if( StormFox2.Setting.GetCache("windmove_props_unweld", true) ) then + if constraint.FindConstraint( ent, "Weld" ) and math.random(1, 15) < 2 then + ent:EmitSound("physics/wood/wood_box_break" .. math.random(1,2) .. ".wav") + constraint.RemoveConstraints( ent, "Weld" ) + end + end + end + -- Move + phys:Wake() + phys:ApplyForceCenter(Vector(windPush.x, windPush.y, math.max(phys:GetVelocity().z, 0))) + -- Break + if(wind > 40) then + if( StormFox2.Setting.GetCache("windmove_props_break", true) ) then + if not ent:IsVehicle() and (ent._sfnext_dmg or 0) <= CurTime() and ent:GetClass() != "npc_grenade_frag" then + ent._sfnext_dmg = CurTime() + 0.5 + ent:TakeDamage(ent:Health() / 10 + 2,game.GetWorld(),game.GetWorld()) + end + end + end + end + + local move_tab = {} + local current_prop = 0 + local function AddEntity( ent ) + if( ent._sfwindcan or 0 ) > CurTime() then return end + if( StormFox2.Setting.GetCache("windmove_props_max", 50) <= table.Count(move_tab) ) then return end -- Too many props moving atm + move_tab[ ent ] = CurTime() + --ApplyWindEffect( ent, StormFox2.Wind.GetForce(), StormFox2.Wind.GetNorm() ) + end + + hook.Add("OnEntityCreated","StormFox.Wind.PropMove",function(ent) + if( not StormFox2.Setting.GetCache("windmove_props", false) ) then return end + if( not IsValid(ent) ) then return end + if( not CanMoveClass( ent ) ) then return end + AddEntity( ent ) + end) + + local scanID = 0 + local function ScanProps() + local t = ents.GetAll() + if( #t < scanID) then + scanID = 0 + end + for i = scanID, math.min(#t, scanID + 30) do + local ent = t[i] + if(not IsValid( ent )) then break end + if ent:GetPersistent() then continue end + if not CanMoveClass( ent ) then continue end + if move_tab[ ent ] then continue end -- Already added + if not StormFox2.Wind.IsEntityInWind( ent ) then continue end -- Not in wind + AddEntity( ent ) + end + scanID = scanID + 30 + end + + local next_loop = 0 -- We shouldn't run this on think if there arae too few props + hook.Add("Think","StormFox.Wind.EffectProps",function() + if( not StormFox2.Setting.GetCache("windmove_props", false) ) then return end + if( next_loop > CurTime() ) then return end + next_loop = CurTime() + (game.SinglePlayer() and 0.1 or 0.2) + -- Scan on all entities. This would be slow. But we're only looking at 30 entities at a time. + ScanProps() + + local t = table.GetKeys( move_tab ) + if(#t < 1) then return end + local wind = StormFox2.Wind.GetForce() + -- Check if there is wind + if( wind < 10) then + table.Empty( move_tab ) + return + end + if( current_prop > #t) then + current_prop = 1 + end + local wind, c_windnorm = StormFox2.Wind.GetForce(), StormFox2.Wind.GetNorm() + local windnorm = Vector(c_windnorm.x, c_windnorm.y, 0) + local c = CurTime() + for i = current_prop, math.min(#t, current_prop + 30) do + local ent = t[i] + if(not ent) then + break + end + -- Check if valid + if(not IsValid( ent ) or not StormFox2.Wind.IsEntityInWind( ent )) then + move_tab[ent] = nil + continue + end + -- Check if presistence + if ent:GetPersistent() then continue end + -- If the entity has been in the wind for over 10 seconds, try and move on and see if we can pick up something else + if(move_tab[ ent ] < c - 10) then + ent._sfwindcan = c + math.random(20, 30) + if(StormFox2.Setting.GetCache("windmove_props_makedebris", true)) then + ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS ) + end + move_tab[ent] = nil + continue + end + ApplyWindEffect( ent, wind, windnorm ) + end + current_prop = current_prop + 30 + end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_content.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_content.lua new file mode 100644 index 0000000..50766a4 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_content.lua @@ -0,0 +1,27 @@ + +-- Adds SF content +if not StormFox2.WorkShopVersion then + local i = 0 + local function AddDir(dir,dirlen) + if not dirlen then dirlen = dir:len() end + local files, folders = file.Find(dir .. "/*", "GAME") + for _, fdir in ipairs(folders) do + if fdir ~= ".svn" then + AddDir(dir .. "/" .. fdir) + end + end + for k, v in ipairs(files) do + local fil = dir .. "/" .. v + resource.AddFile(fil) + i = i + 1 + end + end + AddDir("materials/stormfox2") + AddDir("sound/stormfox2") + AddDir("models/stormfox2") + StormFox2.Msg("Added " .. i .. " content files.") +-- Add the workshop +else + resource.AddWorkshop(string.match(StormFox2.WorkShopURL, "%d+$")) + StormFox2.Msg("Added content files from workshop.") +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_entities.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_entities.lua new file mode 100644 index 0000000..3a7a7f4 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_entities.lua @@ -0,0 +1,70 @@ +--[[------------------------------------------------------------------------- +Creates or finds map entities + Hook: + StormFox2.PostEntityScan Gets called after StormFox have located the map entities. + Convar: + sf_enable_mapsupport +---------------------------------------------------------------------------]] +StormFox2.Ent = {} +CreateConVar("sf_enable_mapsupport","1",{FCVAR_REPLICATED,FCVAR_ARCHIVE},"StormFox2.setting.mapsupport") +-- Find or creat entities + local function GetOrCreate(str,only_get) + local l = ents.FindByClass(str) + local con = GetConVar("sf_enable_mapsupport") + if #l > 0 then + local s = string.rep(" ",24 - #str) + MsgC( " ", Color(255,255,255), str, s, Color(55,255,55), "Found", Color( 255, 255, 255), "\n" ) + return l + end + if not con:GetBool() or only_get then -- Disabled mapsupport or don't create + local s = string.rep(" ",24 - #str) + MsgC( " ", Color(255,255,255), str, s, Color(255,55,55), "Not found", Color( 255, 255, 255), "\n" ) + return + end + local ent = ents.Create(str) + ent:Spawn(); + ent:Activate(); + ent._sfcreated = true + local s = string.rep(" ",24 - #str) + MsgC( " ", Color(255,255,255), str, s, Color(155,155,255), "Created", Color( 255, 255, 255), "\n" ) + return {ent} + end + -- We need to use this function, as some entities spawn regardless of what the map has. + local function findEntities() + StormFox2.Msg( "Scanning mapentities ..." ) + local tSunlist = ents.FindByClass( "env_sun" ) + for i = 1, #tSunlist do -- Remove any env_suns, there should be only one but who knows + tSunlist[ i ]:Fire( "TurnOff" ) + end + StormFox2.Ent.env_skypaints = GetOrCreate( "env_skypaint" ) + StormFox2.Ent.light_environments = GetOrCreate( "light_environment", true) + StormFox2.Ent.env_fog_controllers = GetOrCreate( "env_fog_controller" ) + StormFox2.Ent.shadow_controls = GetOrCreate( "shadow_control", true ) + StormFox2.Ent.env_tonemap_controllers = GetOrCreate("env_tonemap_controller", true ) + StormFox2.Ent.env_winds = GetOrCreate("env_wind", true ) -- Can't spawn the wind controller without problems + StormFox2.Ent.env_tonemap_controller = GetOrCreate( "env_tonemap_controller", true) + -- Kill TF2 sun + for k,v in ipairs(ents.FindByModel("models/props_skybox/sunnoon.mdl")) do + if v:IsValid() then + v:SetNoDraw( true ) + end + end + --[[------------------------------------------------------------------------- + Gets called when StormFox has handled map-entities. + ---------------------------------------------------------------------------]] + hook.Run( "StormFox2.PostEntityScan" ) + end +-- If this is first run, wait for InitPostEntity. + hook.Add("StormFox2.InitPostEntity","StormFox2.Entities",findEntities) +-- Tell clients about explosions + util.AddNetworkString("StormFox2.entity.explosion") + hook.Add("EntityRemoved","StormFox2.Entitys.Explosion",function(ent) + if ent:GetClass() ~= "env_explosion" then return end + local t = ent:GetKeyValues() + net.Start("StormFox2.entity.explosion") + net.WriteVector(ent:GetPos()) + net.WriteUInt(t.iRadiusOverride or t.iMagnitude, 16) + net.WriteUInt(t.iMagnitude, 16) + net.SendPVS(ent:GetPos()) + hook.Run("StormFox2.Entitys.OnExplosion", ent:GetPos(), t.iRadiusOverride, t.iMagnitude) + end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_perlin.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_perlin.lua new file mode 100644 index 0000000..904104e --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/lib/sv_perlin.lua @@ -0,0 +1,129 @@ +--[[ + Implemented as described here: + http://flafla2.github.io/2014/08/09/perlinnoise.html + + Copied from: https://gist.github.com/SilentSpike/25758d37f8e3872e1636d90ad41fe2ed +]]-- + +local floor,band,clamp,max = math.floor,bit.band,math.Clamp,math.max + +perlin = {} +local p = {} + +-- Hash lookup table as defined by Ken Perlin +-- This is a randomly arranged array of all numbers from 0-255 inclusive +local permutation = {151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 +} +-- p is used to hash unit cube coordinates to [0, 255] +for i = 0,255 do + -- Convert to 0 based index table + p[i] = permutation[i + 1] + -- Repeat the array to avoid buffer overflow in hash function + p[i + 256] = permutation[i + 1] +end +-- Functions +local dot_product = { + [0x0] = function(x,y,z) return x + y end, + [0x1] = function(x,y,z) return -x + y end, + [0x2] = function(x,y,z) return x - y end, + [0x3] = function(x,y,z) return -x - y end, + [0x4] = function(x,y,z) return x + z end, + [0x5] = function(x,y,z) return -x + z end, + [0x6] = function(x,y,z) return x - z end, + [0x7] = function(x,y,z) return -x - z end, + [0x8] = function(x,y,z) return y + z end, + [0x9] = function(x,y,z) return -y + z end, + [0xA] = function(x,y,z) return y - z end, + [0xB] = function(x,y,z) return -y - z end, + [0xC] = function(x,y,z) return y + x end, + [0xD] = function(x,y,z) return -y + z end, + [0xE] = function(x,y,z) return y - x end, + [0xF] = function(x,y,z) return -y - z end +} +local function grad(hash, x, y, z) + return dot_product[band(hash,0xF)](x,y,z) +end +local function fade(t) + return t * t * t * (t * (t * 6 - 15) + 10) +end +local function lerp(t, a, b) + return a + t * (b - a) +end +function perlin.noise(x, y, z, zoom) -- [-1 , 1] + zoom = zoom or 100 + x = x / zoom + y = y and y / zoom or 0 + z = z and z / zoom or 0 + + -- Calculate the "unit cube" that the point asked will be located in + local xi = floor(x) % 256 + local yi = floor(y) % 256 + local zi = floor(z) % 256 + + -- Next we calculate the location (from 0 to 1) in that cube + x = x - floor(x) + y = y - floor(y) + z = z - floor(z) + + -- We also fade the location to smooth the result + local u = fade(x) + local v = fade(y) + local w = fade(z) + + -- Hash all 8 unit cube coordinates surrounding input coordinate + local A = p[xi ] + yi + local AA = p[A ] + zi + local AB = p[A + 1 ] + zi + local AAA = p[ AA ] + local ABA = p[ AB ] + local AAB = p[ AA + 1 ] + local ABB = p[ AB + 1 ] + + local B = p[xi + 1] + yi + local BA = p[B ] + zi + local BB = p[B + 1 ] + zi + local BAA = p[ BA ] + local BBA = p[ BB ] + local BAB = p[ BA + 1 ] + local BBB = p[ BB + 1 ] + + -- Take the weighted average between all 8 unit cube coordinates + return lerp(w, + lerp(v, + lerp(u, + grad(AAA,x,y,z), + grad(BAA,x-1,y,z) + ), + lerp(u, + grad(ABA,x,y-1,z), + grad(BBA,x-1,y-1,z) + ) + ), + lerp(v, + lerp(u, + grad(AAB,x,y,z-1), grad(BAB,x-1,y,z-1) + ), + lerp(u, + grad(ABB,x,y-1,z-1), grad(BBB,x-1,y-1,z-1) + ) + ) + ) +end +function perlin.range(x, y ,z, zoom) -- [0 - 1] + return (1 + perlinnoise(x, y, z, zoom)) / 2 +end +function perlin.rangeSub(x, y, z , zoom, n) + return max(0,(perlin.range(x, y ,z, zoom) - n ) / (1 - n)) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/sh_cwi.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/sh_cwi.lua new file mode 100644 index 0000000..7c68a15 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/sh_cwi.lua @@ -0,0 +1,116 @@ + +--[[ + Commen Weather Interface. + Still WIP! +]] + +local Version = 0.02 +if CWI and CWI.Version > Version then return end + +CWI = {} +CWI.Version = Version +CWI.NotSupported = 12345 +CWI.WeatherMod = "StormFox 2" + +-- Time + if SERVER then + --[[ + Sets the time by using a number between 0 - 1440 + 720 being midday + ]] + function CWI.SetTime( num ) + StormFox2.Time.Set( num ) + end + --[[ + Sets the timespeed + 1 = real time + 60 = 60x real time speed + ]] + CWI.SetTimeSpeed = StormFox2.Time.SetSpeed + end + --[[ + Returns the time as a number between 0 - 1440 + 720 being midday + ]] + CWI.GetTime = StormFox2.Time.Get + --[[ + Returns the timespeed. + ]] + CWI.GetTimeSpeed = StormFox2.Time.GetSpeed + -- Easy day/night variables + CWI.IsDay = function() return StormFox2.Sun.IsUp() end + CWI.IsNight = function() return not StormFox2.Sun.IsUp() end + +-- Weather + --[[ + Sets the weather + ]] + if SERVER then + function CWI.SetWeather( str ) + str = string.upper(str[1]) .. string.sub(str, 2):lower() + StormFox2.Weather.Set( str ) + end + end + -- Returns the current weather + function CWI.GetWeather() + return StormFox2.Weather.GetCurrent().Name:lower() + end + -- Returns a list of all weather-types + CWI.DefaultWeather = "clear" + function CWI.GetWeathers() + local t = {} + for _, str in ipairs( StormFox2.Weather.GetAll() ) do + table.insert(t, StormFox2.Weather.Get(str).Name:lower()) + end + return t + end +-- Downfall + CWI.IsRaining = StormFox2.Weather.IsRaining + CWI.IsSnowing = StormFox2.Weather.IsSnowing + +-- Time Functions + function CWI.GetHours( bUse12Hour ) + if not bUse12Hour then return math.floor( CWI.GetTime() / 60 ) end + local h = math.floor( CWI.GetTime() / 60 ) + local b = ( h < 12 or h == 0 ) and "AM" or "PM" + if h == 0 then + h = 12 + elseif h > 12 then + h = h - 12 + end + return h, b + end + function CWI.GetMinutes() + return math.floor( CWI.GetTime() % 60 ) + end + function CWI.GetSeconds() + return math.floor( CWI.GetTime() % 1 ) * 60 + end + function CWI.TimeToString( bUse12Hour ) + local h, e = CWI.GetHours( bUse12Hour ) + return h .. ":" .. CWI.GetMinutes() .. (e and " " .. e or "") + end + +--[[ + Hook: CWI.NewWeather, weatherName +]] +local lastW = "Unknown" +hook.Add("StormFox2.weather.postchange", "CWI.CallNewWeather", function( sName ) + if lastW == sName then return end + lastW = sName + hook.Run("CWI.NewWeather", sName:lower()) +end) + +--[[ + Hook: CWI.NewDay, dayNumber 0 - 365 +]] +hook.Add("StormFox2.Time.NextDay", "CWI.CallNewDay", function( ) + hook.Run("CWI.NewDay") +end) + +--[[ + Hook: CWI.Init +]] +hook.Add("stormfox2.postinit", "SFCWI.Init", function() + hook.Run("CWI.Init") +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/vgui/cl_newvgui.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/vgui/cl_newvgui.lua new file mode 100644 index 0000000..1a2f38c --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/vgui/cl_newvgui.lua @@ -0,0 +1,142 @@ + +-- SF_TextEntry +-- Supports for temperature conversion and other things +do + local PANEL = {} + AccessorFunc( PANEL, "m_bTemp", "Temperature" ) + AccessorFunc( PANEL, "m_sUnity", "Unit" ) + local function convFromClient( val ) + return StormFox2.Temperature.Convert(StormFox2.Temperature.GetDisplayType(),nil ,tonumber(val)) + end + local function convToClient( val ) + return StormFox2.Temperature.Convert(nil, StormFox2.Temperature.GetDisplayType() ,tonumber(val)) + end + -- Hacky solution to units. + local _oldDTET = vgui.GetControlTable("DTextEntry").DrawTextEntryText + function PANEL:DrawTextEntryText( ... ) + if not self:GetUnit() then + return _oldDTET( self, ... ) + end + local s = self:GetText() + self:SetText(s .. self:GetUnit()) + _oldDTET( self, ... ) + self:SetText(s) + end + -- Converts the text displayed and typed unto the temperature unit. + function PANEL:SetTemperature( b ) + self.m_bTemp = b + if b then + self:SetUnit( StormFox2.Temperature.GetDisplaySymbol() ) + else + self:SetUnit(nil) + end + end + -- Overwrite the values for celcius. + function PANEL:SetValue( strValue ) + if ( vgui.GetKeyboardFocus() == self ) then return end + local CaretPos = self:GetCaretPos() + strValue = self.m_bTemp and convToClient(strValue) or strValue + self:SetText( strValue ) + self:OnValueChange( strValue ) + self:SetCaretPos( CaretPos ) + end + function PANEL:UpdateConvarValue() + self:ConVarChanged( self.m_bTemp and convFromClient(self:GetValue()) or self:GetValue() ) + end + derma.DefineControl( "SF_TextEntry", "SF TextEntry", PANEL, "TextEntry" ) +end + +-- SF_TextBox +-- Wraos text +do + local function wrapText(sText, wide) + wide = wide - 10 + local tw,th = surface.GetTextSize(language.GetPhrase(sText)) + local lines,b = 1, false + local s = "" + for w in string.gmatch(sText, "[^%s,]+") do + local tt = s .. (b and " " or "") .. w + if surface.GetTextSize(tt) >= wide then + s = s .. "\n" .. w + lines = lines + 1 + else + s = tt + end + b = true + end + return s, lines, th + end + local PANEL = {} + function PANEL:PerformLayout(w, h) + local text, lines, th = wrapText(self:GetText(), self:GetWide() - self._b:GetWide()) + self._d:SetText(text) + self._d:SizeToContents() + local nh = math.max( th, lines * th) + if nh > h then + self:SetTall( math.max( th, lines * th) ) + end + end + derma.DefineControl( "SF_TextBox", "SF TextBox", PANEL, "DLabel" ) +end + +-- SF_Slider +-- Doesn't spam convar +do + local function paintKnob(self,x, y) -- Skin doesn't have x or y pos + local skin = self:GetSkin() + if ( self:GetDisabled() ) then return skin.tex.Input.Slider.H.Disabled( x, y, 15, 15 ) end + if ( self.Depressed ) then + return skin.tex.Input.Slider.H.Down( x, y, 15, 15 ) + end + if ( self.Hovered ) then + return skin.tex.Input.Slider.H.Hover( x, y, 15, 15 ) + end + skin.tex.Input.Slider.H.Normal( x, y, 15, 15 ) + end + local PANEL = {} + AccessorFunc( PANEL, "m_max", "Max" ) + AccessorFunc( PANEL, "m_min", "Min" ) + AccessorFunc( PANEL, "m_fDecimal", "Decimals" ) + AccessorFunc( PANEL, "m_bLiveUpdate", "UpdateLive" ) + AccessorFunc( PANEL, "m_fFloatValue", "FloatValue" ) + Derma_Install_Convar_Functions( PANEL ) + + function PANEL:Init() + self:SetMin( 0 ) + self:SetMax( 10 ) + self:SetDecimals( 2 ) + self:SetFloatValue( 1.5 ) + self:SetUpdateLive( false ) + self:SetText("") + end + function PANEL:SetValue( val ) + local val = tonumber( val ) + if ( val == nil ) then return end + if ( val == self:GetFloatValue() ) then return end + val = math.Round(val, self:GetDecimals()) + self:SetFloatValue( val ) + self:OnValueChanged( val ) + self:UpdateConVar() + end + function PANEL:Think() + if ( !self:GetActive() ) then + self:ConVarNumberThink() + end + if self._wdown and not self:IsDown() then + self:ConVarChanged( self:GetFloatValue() ) + self._wdown = false + elseif self:IsDown() and self._knob then + local w_f = math.Clamp((self:LocalCursorPos() - 7) / (self:GetWide() - 15), 0, 1) + local r = self:GetMax() - self:GetMin() + self:SetValue(self:GetMin() + w_f * r) + self._wdown = true + end + end + function PANEL:Paint( w, h ) + derma.SkinHook( "Paint", "Slider", panel, w, h ) + local sw = w - 15 + paintKnob(self,sw * self.m_fSlideX,0) + end + derma.DefineControl("SF_Slider", "A simple slider", PANEL, "DButton") +end + diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/clear.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/clear.lua new file mode 100644 index 0000000..671f1cd --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/clear.lua @@ -0,0 +1,134 @@ + +-- Clear weather. This is the default weather + +local clear = StormFox2.Weather.Add( "Clear" ) + +local windy = 8 + +-- Description +if CLIENT then + function clear:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= windy + if b_windy then + return language.GetPhrase("#sf_weather.clear.windy"), "Windy" + end + return language.GetPhrase("#sf_weather.clear"), "Clear" + end +else + function clear:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= windy + if b_windy then + return "Windy" + end + return "Clear" + end +end +-- Icon +local m1,m2,m3,m4 = (Material("stormfox2/hud/w_clear.png")),(Material("stormfox2/hud/w_clear_night.png")),(Material("stormfox2/hud/w_clear_windy.png")),(Material("stormfox2/hud/w_clear_cold.png")) +function clear.GetSymbol( nTime ) -- What the menu should show + return m1 +end + +function clear.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show + local b_day = StormFox2.Time.IsDay(nTime) + local b_cold = nTemp < -2 + local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= windy + if b_windy then + return m3 + elseif b_cold then + return m4 + elseif b_day then + return m1 + else + return m2 + end +end + +-- Day + clear:SetSunStamp("topColor",Color(91, 127.5, 255), SF_SKY_DAY) + clear:SetSunStamp("bottomColor",Color(204, 255, 255), SF_SKY_DAY) + clear:SetSunStamp("fadeBias",0.2, SF_SKY_DAY) + clear:SetSunStamp("duskColor",Color(255, 255, 255), SF_SKY_DAY) + clear:SetSunStamp("duskIntensity",1.94, SF_SKY_DAY) + clear:SetSunStamp("duskScale",0.29, SF_SKY_DAY) + clear:SetSunStamp("sunSize",20, SF_SKY_DAY) + clear:SetSunStamp("sunColor",Color(255, 255, 255), SF_SKY_DAY) + clear:SetSunStamp("starFade",0, SF_SKY_DAY) + --clear:SetSunStamp("fogDensity",0.8, SF_SKY_DAY) +-- Night + clear:SetSunStamp("topColor",Color(0,0,0), SF_SKY_NIGHT) + clear:SetSunStamp("bottomColor",Color(0, 1.5, 5.25), SF_SKY_NIGHT) + clear:SetSunStamp("fadeBias",0.12, SF_SKY_NIGHT) + clear:SetSunStamp("duskColor",Color(9, 9, 0), SF_SKY_NIGHT) + clear:SetSunStamp("duskIntensity",0, SF_SKY_NIGHT) + clear:SetSunStamp("duskScale",0, SF_SKY_NIGHT) + clear:SetSunStamp("sunSize",0, SF_SKY_NIGHT) + clear:SetSunStamp("starFade",100, SF_SKY_NIGHT) + --clear:SetSunStamp("fogDensity",1, SF_SKY_NIGHT) +-- Sunset + -- Old Color(170, 85, 43) + clear:SetSunStamp("topColor",Color(130.5, 106.25, 149), SF_SKY_SUNSET) + clear:SetSunStamp("bottomColor",Color(204, 98, 5), SF_SKY_SUNSET) + clear:SetSunStamp("fadeBias",1, SF_SKY_SUNSET) + clear:SetSunStamp("duskColor",Color(248, 103, 30), SF_SKY_SUNSET) + clear:SetSunStamp("duskIntensity",3, SF_SKY_SUNSET) + clear:SetSunStamp("duskScale",0.6, SF_SKY_SUNSET) + clear:SetSunStamp("sunSize",15, SF_SKY_SUNSET) + clear:SetSunStamp("sunColor",Color(198, 170, 59), SF_SKY_SUNSET) + clear:SetSunStamp("starFade",30, SF_SKY_SUNSET) + --clear:SetSunStamp("fogDensity",0.8, SF_SKY_SUNSET) +-- Sunrise + clear:SetSunStamp("topColor",Color(130.5, 106.25, 149), SF_SKY_SUNRISE) + clear:SetSunStamp("bottomColor",Color(204, 98, 5), SF_SKY_SUNRISE) + clear:SetSunStamp("fadeBias",1, SF_SKY_SUNRISE) + clear:SetSunStamp("duskColor",Color(248, 103, 30), SF_SKY_SUNRISE) + clear:SetSunStamp("duskIntensity",3, SF_SKY_SUNRISE) + clear:SetSunStamp("duskScale",0.6, SF_SKY_SUNRISE) + clear:SetSunStamp("sunSize",15, SF_SKY_SUNRISE) + clear:SetSunStamp("sunColor",Color(198, 170, 59), SF_SKY_SUNRISE) + clear:SetSunStamp("starFade",30, SF_SKY_SUNRISE) + clear:SetSunStamp("fogDensity",0.8, SF_SKY_SUNRISE) +-- Cevil + clear:CopySunStamp( SF_SKY_NIGHT, SF_SKY_CEVIL ) -- Copy the night sky + clear:SetSunStamp("fadeBias",1, SF_SKY_CEVIL) + clear:SetSunStamp("sunSize",0, SF_SKY_CEVIL) + +-- Default variables. These don't change. + clear:Set("moonColor", Color( 205, 205, 205 )) + local moonSize = StormFox2.Setting.GetObject("moonsize") + clear:Set("moonSize",moonSize:GetValue()) + + moonSize:AddCallback(function(var) + clear:Set("moonSize",moonSize:GetValue()) + end, "SF_moonSizeUpdate") + clear:Set("moonTexture", "stormfox2/effects/moon/moon.png" ) + clear:Set("skyVisibility",100) -- Blocks out the sun/moon + clear:Set("mapDayLight",100) + clear:Set("mapNightLight",0) + clear:Set("clouds",0) + clear:Set("HDRScale",0.7) + + clear:Set("fogDistance", 400000) + clear:Set("fogIndoorDistance", 3000) + --clear:Set("fogEnd",90000) + --clear:Set("fogStart",0) + +-- Static values + clear:Set("starSpeed", 0.001) + clear:Set("starScale", 2.2) + clear:Set("starTexture", "skybox/starfield") + clear:Set("enableThunder") -- Tells the generator that this weather_type can't have thunder. + +-- 2D skyboxes +if SERVER then + local t_day, t_night, t_sunrise, t_sunset + t_day = {"sky_day01_05", "sky_day01_04", "sky_day02_01","sky_day02_03","sky_day02_04","sky_day02_05"} + t_sunrise = {"sky_day01_05", "sky_day01_06", "sky_day01_08"} + t_sunset = {"sky_day02_02", "sky_day02_01"} + t_night = {"sky_day01_09"} + + clear:SetSunStamp("skyBox",t_day, SF_SKY_DAY) + clear:SetSunStamp("skyBox",t_sunrise, SF_SKY_SUNRISE) + clear:SetSunStamp("skyBox",t_sunset, SF_SKY_SUNSET) + clear:SetSunStamp("skyBox",t_night, SF_SKY_NIGHT) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/cloudrain.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/cloudrain.lua new file mode 100644 index 0000000..f78f212 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/cloudrain.lua @@ -0,0 +1,651 @@ + +local clamp,max,min,random = math.Clamp,math.max,math.min,math.random +-- Rain and cloud is nearly the same. +local cloudy = StormFox2.Weather.Add( "Cloud" ) +local rain = StormFox2.Weather.Add( "Rain", "Cloud" ) + -- cloudy.spawnable = true Cloud is not spawnable. Since it is a "default" when it is cloudy + rain.spawnable = true + rain.thunder = function(percent) -- The amount of strikes pr minute + return percent > 0.5 and random(10) > 5 and percent * 3 or 0 + end +-- Cloud icon +do + -- Description + if CLIENT then + function cloudy:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + if StormFox2.Wind.GetBeaufort(nWind) >= 10 then + return language.GetPhrase('sf_weather.cloud.storm') + elseif bThunder then + return language.GetPhrase('sf_weather.cloud.thunder') + else + return language.GetPhrase('sf_weather.cloud') + end + end + else + function cloudy:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + if StormFox2.Wind.GetBeaufort(nWind) >= 10 then + return "Storm" + elseif bThunder then + return "Thunder" + else + return "Cloudy" + end + end + end + -- Icon + local m_def = Material("stormfox2/hud/w_cloudy.png") + local m_night = Material("stormfox2/hud/w_cloudy_night.png") + local m_windy = Material("stormfox2/hud/w_cloudy_windy.png") + local m_thunder = Material("stormfox2/hud/w_cloudy_thunder.png") + function cloudy.GetSymbol( nTime ) -- What the menu should show + return m_def + end + function cloudy.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show + local b_day = StormFox2.Time.IsDay(nTime) + local b_cold = nTemp < -2 + local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= 7 + local b_H = nFraction > 0.5 + if bThunder then + return m_thunder + elseif b_windy then + return m_windy + elseif b_H or b_day then + return m_def + else + return m_night + end + end +end + +-- Rain icon +do + -- Description + if CLIENT then + function rain:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + if StormFox2.Wind.GetBeaufort(nWind) >= 10 then + return language.GetPhrase('sf_weather.cloud.storm'), 'Storm' + elseif bThunder then + return language.GetPhrase('sf_weather.cloud.thunder'), 'Thunder' + elseif nTemp > 0 then + return language.GetPhrase('sf_weather.rain'), 'Raining' + elseif nTemp > -2 then + return language.GetPhrase('sf_weather.rain.sleet'), 'Sleet' + else + return language.GetPhrase('sf_weather.rain.snow'), 'Snowing' + end + end + else + function rain:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + if StormFox2.Wind.GetBeaufort(nWind) >= 10 then + return 'Storm', 'Storm' + elseif bThunder then + return 'Thunder', 'Thunder' + elseif nTemp > 0 then + return 'Raining', 'Raining' + elseif nTemp > -2 then + return 'Sleet', 'Sleet' + else + return 'Snowing', 'Snowing' + end + end + end + -- Icon + local m_def = Material("stormfox2/hud/w_raining.png") + local m_def_light = Material("stormfox2/hud/w_raining_light.png") + local m_thunder = Material("stormfox2/hud/w_raining_thunder.png") + local m_windy = Material("stormfox2/hud/w_raining_windy.png") + local m_snow = Material("stormfox2/hud/w_snowing.png") + local m_sleet = Material("stormfox2/hud/w_sleet.png") + function rain.GetSymbol( nTime, nTemp ) -- What the menu should show + if nTemp < -4 then + return m_snow + end + return m_def + end + function rain.GetIcon( _, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show + local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= 7 + if bThunder then + return m_thunder + elseif b_windy and nTemp > -4 then + return m_windy + elseif nTemp > 0 then + if nFraction > 0.4 then + return m_def + else + return m_def_light + end + elseif nTemp <= -4 then + return m_snow + else + return m_sleet + end + end + function rain.LogicRelay() + if StormFox2.Temperature.Get() < -1 then + return "snow" + end + return "rain" + end +end + +-- Sky and default weather variables +do + -- Day -- + cloudy:SetSunStamp("topColor",Color(3.0, 2.9, 3.5), SF_SKY_DAY) + cloudy:SetSunStamp("bottomColor",Color(20, 25, 25), SF_SKY_DAY) + cloudy:SetSunStamp("duskColor",Color(3, 2.9, 3.5), SF_SKY_DAY) + cloudy:SetSunStamp("duskScale",1, SF_SKY_DAY) + cloudy:SetSunStamp("HDRScale",0.33, SF_SKY_DAY) + -- Night + cloudy:SetSunStamp("topColor",Color(0.4, 0.2, 0.54), SF_SKY_NIGHT) + cloudy:SetSunStamp("bottomColor",Color(2.25, 2.25,2.25),SF_SKY_NIGHT) + --cloudy:SetSunStamp("bottomColor",Color(14.3* 0.5,14.8* 0.5,15.2* 0.5), SF_SKY_NIGHT) + cloudy:SetSunStamp("duskColor",Color(.4, .2, .54), SF_SKY_NIGHT) + cloudy:SetSunStamp("duskScale",0, SF_SKY_NIGHT) + cloudy:SetSunStamp("HDRScale",0.1, SF_SKY_NIGHT) + -- Sunset/rise + cloudy:SetSunStamp("duskScale",0.26, SF_SKY_SUNSET) + cloudy:SetSunStamp("duskScale",0.26, SF_SKY_SUNRISE) + + cloudy:Set("starFade",0) + cloudy:Set("mapDayLight",0.25) + cloudy:Set("skyVisibility",0) + cloudy:Set("clouds",1) + cloudy:Set("enableThunder", true) + cloudy:Set("fogDistance", function() + local wF = StormFox2.Wind.GetForce() + local temp = clamp(StormFox2.Temperature.Get() / 4 + 1,0,1) + if wF <= 0 then return 3000 end + local multi = max(0, 26 - temp * 8) + return max(3000 - multi * wF,0) + end) + + rain:Set("mapDayLight",0) + local cDay, cNight = Color(20, 25, 25), Color(2.25, 2.25,2.25) + local n = 7 + local cDayM, cNightM = Color(40 * 2, 50 * 2, 50 * 2), Color(2.25 * n, 2.25 * n,2.25 * n) + rain:Set("bottomColor",function(nStamp) + local temp = StormFox2.Temperature.Get() + if temp >= 0 then + return nStamp == SF_SKY_DAY and cDay or cNight + elseif temp <= -4 then + return nStamp == SF_SKY_DAY and cDayM or cNightM + end + local cRain = nStamp == SF_SKY_DAY and cDay or cNight + local cSnow = nStamp == SF_SKY_DAY and cDayM or cNightM + return StormFox2.Mixer.Blender(temp / 4 + 1, cSnow, cRain) + end) + --rain:SetSunStamp("fogEnd",800,SF_SKY_DAY) + --rain:SetSunStamp("fogEnd",800,SF_SKY_SUNRISE) + --rain:SetSunStamp("fogEnd",2000,SF_SKY_NIGHT) + --rain:SetSunStamp("fogEnd",2000,SF_SKY_BLUE_HOUR) + --rain:Set("fogDensity",1) + --rain:Set("fogStart",0) + rain:Set("fogDistance", function() + local wF = StormFox2.Wind.GetForce() + local temp = clamp(StormFox2.Temperature.Get() / 4 + 1,0,1) + if wF <= 0 then return 3000 end + local multi = max(0, 26 - temp * 8) + return max(3000 - multi * wF,0) + end) + rain:Set("fogIndoorDistance", 5500) +-- rain:SetSunStamp("fogDistance",2000, SF_SKY_DAY) +-- rain:SetSunStamp("fogDistance",2500, SF_SKY_SUNSET) +-- rain:SetSunStamp("fogDistance",2000, SF_SKY_NIGHT) +-- rain:SetSunStamp("fogDistance",2500, SF_SKY_SUNRISE) + +end +-- Window render +local rain_normal_material = Material("stormfox2/effects/window/rain_normal") +local rain_t = StormFox2.Terrain.Create("rain") +do + local raindrops = {} + local raindrops_mat = {(Material("stormfox2/effects/window/raindrop_normal")),(Material("stormfox2/effects/window/raindrop_normal2")),(Material("stormfox2/effects/window/raindrop_normal3"))} + local s = 2 + rain:RenderWindowRefract64x64(function(w, h) + if StormFox2.Temperature.Get() < -1 then return false end + local QT = StormFox2.Client.GetQualityNumber() + local P = StormFox2.Weather.GetPercent() + -- Base + surface.SetDrawColor(Color(255,255,255,255 * P)) + surface.SetMaterial(rain_normal_material) + local c = (-SysTime() / 1000) % 1 + surface.DrawTexturedRectUV(0,0, w, h, 0, c, s, c + s ) + -- Create raindrop + if #raindrops < math.Clamp(QT * 10, 5 ,65 * P) and random(100) <= 90 then + local s = random(6,10) + local x,y = random(s, w - s * 2), random(s, h * 0.8) + local sp = random(10, 50) + local lif = CurTime() + random(3,5) + local m = table.Random(raindrops_mat) + table.insert(raindrops, {x,y,s,m,sp,lif}) + end + -- Render raindrop + local r = {} + for i,v in ipairs(raindrops) do + local lif = (v[6] - CurTime()) * 10 + local a_n = h - v[2] - v[3] + local a = min(25.5,min(a_n,lif)) * 10 + if a > 0 then + surface.SetMaterial(v[4]) + surface.SetDrawColor(Color(255,255,255,a)) + surface.DrawTexturedRect(v[1],v[2],v[3],v[3]) + v[2] = v[2] + FrameTime() * v[5] + else + table.insert(r, i) + end + end + -- Remove raindrop + for i = #r,1,-1 do + table.remove(raindrops, r[i]) + end + end) + + -- Snow window + local mat = Material("stormfox2/effects/window/snow") + local mat2 = Material("stormfox2/effects/blizzard.png","noclamp") + local mat3 = Material("stormfox2/effects/rainstorm.png","noclamp") + local size = 0.5 + local function RenderWindow(w, h) + if StormFox2.Temperature.Get() > -2 then + local wi = StormFox2.Wind.GetForce() + local P = StormFox2.Weather.GetPercent() + local lum = max(min(25 + StormFox2.Weather.GetLuminance(), 255),150) + if P * wi < 10 then return false end + -- Storm + local c = Color(lum,lum,lum,math.min(255, wi * 3)) + surface.SetDrawColor(c) + surface.SetMaterial(mat3) + for i = 1, math.max(1, wi / 20) do + local cx = CurTime() * -1 % size + local cu = (CurTime() * -(4 + i)) % size + local fx = i / 3 + cx + surface.DrawTexturedRectUV(0,0,w,h, fx, cu, fx + size, size + cu) + end + else + local P = 1 - StormFox2.Weather.GetPercent() + local wi = StormFox2.Wind.GetForce() + local lum = max(min(25 + StormFox2.Weather.GetLuminance(), 255),150) + local c = Color(lum,lum,lum) + local oSF = StormFox2.Environment.GetOutSideFade() + if wi > 5 and oSF < 1 then + c.a = 255 - (oSF * 255) + surface.SetDrawColor(c) + surface.SetMaterial(mat2) + local cu = CurTime() * 3 + for i = 1, wi / 20 do + local sz = (i * 3.333) % 3 + local sx = i * 3 + (cu * 0.2) % sz + local sy = i * 5 + -cu % (sz * 0.5) + surface.DrawTexturedRectUV(0,0,w,h,sx,sy,sx + sz,sy + sz) + end + c.a = 255 + end + surface.SetMaterial(mat) + surface.SetDrawColor(c) + surface.DrawTexturedRect(0,h * 0.12 * P,w,h) + end + end + rain:RenderWindow( RenderWindow ) +end +-- Snow Terrain and footsteps +do + local snow = StormFox2.Terrain.Create("snow") + + -- Make the snow terrain apply, if temp is low + -- rain:SetTerrain( function() + -- if SERVER then + -- StormFox2.Map.w_CallLogicRelay(rain.LogicRelay()) + -- end + -- return StormFox2.Temperature.Get() < -3 and snow or rain_t + -- end) + + -- Make the snow stay, until temp is high or it being replaced. + snow:LockUntil(function() + return StormFox2.Temperature.Get() > -2 + end) + + -- Footprints + snow:MakeFootprints(true,{ + "stormfox2/footstep/footstep_snow0.mp3", + "stormfox2/footstep/footstep_snow1.mp3", + "stormfox2/footstep/footstep_snow2.mp3", + "stormfox2/footstep/footstep_snow3.mp3", + "stormfox2/footstep/footstep_snow4.mp3", + "stormfox2/footstep/footstep_snow5.mp3", + "stormfox2/footstep/footstep_snow6.mp3", + "stormfox2/footstep/footstep_snow7.mp3", + "stormfox2/footstep/footstep_snow8.mp3", + "stormfox2/footstep/footstep_snow9.mp3" + },"snow.step") + + snow:SetGroundTexture("nature/snowfloor001a") + snow:AddTextureSwap("models/buggy/buggy001","stormfox2/textures/buggy001-snow") + snow:AddTextureSwap("models/vehicle/musclecar_col","stormfox2/textures/musclecar_col-snow") + + -- Other snow textures + -- DOD + if IsMounted("dod") then + snow:AddTextureSwap("models/props_foliage/hedge_128", "models/props_foliage/hedgesnow_128") + snow:AddTextureSwap("models/props_fortifications/hedgehog", "models/props_fortifications/hedgehog_snow") + snow:AddTextureSwap("models/props_fortifications/sandbags", "models/props_fortifications/sandbags_snow") + snow:AddTextureSwap("models/props_fortifications/dragonsteeth", "models/props_fortifications/dragonsteeth_snow") + snow:AddTextureSwap("models/props_normandy/logpile", "models/props_normandy/logpile_snow") + snow:AddTextureSwap("models/props_urban/light_fixture01", "models/props_urban/light_fixture01_snow") + snow:AddTextureSwap("models/props_urban/light_streetlight01", "models/props_urban/light_streetlight01_snow") + snow:AddTextureSwap("models/props_urban/light_fixture01_on", "models/props_urban/light_fixture01_snow_on") + snow:AddTextureSwap("models/props_urban/light_streetlight01_on", "models/props_urban/light_streetlight01_snow_on") + end + -- TF2 + if IsMounted("tf") then + snow:AddTextureSwap("models/props_foliage/shrub_03","models/props_foliage/shrub_03_snow") + snow:AddTextureSwap("models/props_swamp/shrub_03","models/props_foliage/shrub_03_snow") + snow:AddTextureSwap("models/props_foliage/shrub_03_skin2","models/props_foliage/shrub_03_snow") + + snow:AddTextureSwap("models/props_foliage/grass_02","models/props_foliage/grass_02_snow") + snow:AddTextureSwap("models/props_foliage/grass_02_dark","models/props_foliage/grass_02_snow") + snow:AddTextureSwap("nature/blendgrassground001","nature/blendgrasstosnow001") + snow:AddTextureSwap("nature/blendgrassground002","nature/blendgrasstosnow001") + snow:AddTextureSwap("nature/blendgrassground007","nature/blendgrasstosnow001") + snow:AddTextureSwap("detail/detailsprites_2fort","detail/detailsprites_viaduct_event") + snow:AddTextureSwap("detail/detailsprites_dustbowl","detail/detailsprites_viaduct_event") + snow:AddTextureSwap("detail/detailsprites_trainyard","detail/detailsprites_viaduct_event") + snow:AddTextureSwap("models/props_farm/tree_leaves001","models/props_farm/tree_branches001") + snow:AddTextureSwap("models/props_foliage/tree_pine01","models/props_foliage/tree_pine01_snow") + for _,v in ipairs({"02","05","06","09","10","10a"}) do + snow:AddTextureSwap("models/props_forest/cliff_wall_" .. v,"models/props_forest/cliff_wall_" .. v .. "_snow") + end + snow:AddTextureSwap("models/props_island/island_tree_leaves02","models/props_island/island_tree_roots01") + snow:AddTextureSwap("models/props_forest/train_stop","models/props_forest/train_stop_snow") + end +end + +-- Rain particles and sound +if CLIENT then + -- Sound + local rain_light = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_light.ogg", SF_AMB_OUTSIDE, 1 ) + local rain_window = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_WINDOW, 0.1 ) + local rain_outside = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_outside.ogg", SF_AMB_NEAR_OUTSIDE, 0.1 ) + --local rain_underwater = StormFox2.Ambience.CreateAmbienceSnd( "", SF_AMB_UNDER_WATER, 0.1 ) Unused + local rain_watersurf = StormFox2.Ambience.CreateAmbienceSnd( "ambient/water/water_run1.wav", SF_AMB_UNDER_WATER_Z, 0.1 ) + local rain_roof_wood = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof.ogg", SF_AMB_ROOF_WOOD, 0.1 ) + local rain_roof_metal = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof_metal.ogg", SF_AMB_ROOF_METAL, 0.1 ) + local rain_glass = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_ROOF_GLASS, 0.1 ) + rain:AddAmbience( rain_light ) + rain:AddAmbience( rain_window ) + rain:AddAmbience( rain_outside ) + rain:AddAmbience( rain_watersurf ) + rain:AddAmbience( rain_roof_wood ) + rain:AddAmbience( rain_roof_metal ) + rain:AddAmbience( rain_glass ) + -- Edit watersurf + rain_watersurf:SetFadeDistance(0,100) + rain_watersurf:SetVolume( 0.05 ) + rain_watersurf:SetPlaybackRate(2) + -- Edit rain_glass + rain_roof_metal:SetFadeDistance(10,400) + rain_glass:SetFadeDistance(10, 400) + rain_window:SetFadeDistance(100, 200) + -- Edit rain_outside + rain_outside:SetFadeDistance(100, 200) + -- Materials + local m_rain = Material("stormfox2/raindrop.png") + local m_rain2 = Material("stormfox2/effects/raindrop-multi2.png") + local m_rain3 = Material("stormfox2/effects/raindrop-multi3.png") + local m_rain_multi = Material("stormfox2/effects/snow-multi.png","noclamp smooth") + local m_snow = Material("particle/snow") + local m_snow1 = Material("stormfox2/effects/snowflake1.png") + local m_snow2 = Material("stormfox2/effects/snowflake2.png") + local m_snow3 = Material("stormfox2/effects/snowflake3.png") + local t_snow = {m_snow1, m_snow2, m_snow3} + local m_snowmulti = Material("stormfox2/effects/snow-multi.png") + local m_snowmulti2 = Material("stormfox2/effects/snow-multi2.png") + + + -- Make the distant rain start higer up. + + -- Update the rain templates every 10th second + function rain.TickSlow() + local W = StormFox2.Wind.GetForce() + local P = StormFox2.Weather.GetPercent() * (0.5 + W / 30) + local L = StormFox2.Weather.GetLuminance() + local T = StormFox2.Temperature.Get() + 2 + local TL = StormFox2.Thunder.GetLight() + + local TP = math.Clamp(T / 4,0,1) + + rain_outside:SetVolume( P * TP ) + rain_light:SetVolume( P * TP ) + rain_window:SetVolume( P * 0.3 * TP ) + rain_roof_wood:SetVolume( P * 0.3 * TP ) + rain_roof_metal:SetVolume( P * 1 * TP ) + rain_glass:SetVolume( P * 0.5 * TP ) + + local P = StormFox2.Weather.GetPercent() + local speed = 0.72 + 0.36 * P + StormFox2.Misc.rain_template:SetSpeed( speed ) + StormFox2.Misc.rain_template_medium:SetSpeed( speed ) + StormFox2.Misc.rain_template_medium:SetAlpha( L / 5) + end + -- Gets called every tick to add rain. + local multi_dis = 1200 + local m2 = Material("particle/particle_smokegrenade1") + local tc = Color(150,150,150) + local snow_col = Color(255,255,255) + function rain.Think() + local P = StormFox2.Weather.GetPercent() + local L = StormFox2.Weather.GetLuminance() + local W = StormFox2.Wind.GetForce() + if StormFox2.DownFall.GetGravity() < 0 then return end -- Rain can't come from the ground. + local T = StormFox2.Temperature.Get() + 2 + if T > 0 or T > random(-3, 0) then -- Spawn rain particles + -- Set alpha + local s = 1.22 + 1.56 * P + StormFox2.Misc.rain_template:SetSize( s , 5.22 + 7.56 * P) + StormFox2.Misc.rain_template:SetColor(tc) + StormFox2.Misc.rain_template:SetAlpha(min(100 + 15 * P + L,255)) + StormFox2.Misc.rain_template_medium:SetAlpha(min(150 + 15 * P + L,255) /3) + StormFox2.Misc.rain_template_multi:SetAlpha( L ) + -- Spawn rain particles + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template, 10, 700, 10 + P * 900, 5, vNorm ) or {} ) do + v:SetSize( 1.22 + 1.56 * P * math.Rand(1,2), 5.22 + 7.56 * P ) + end + -- Spawn distant rain + if P > 0.15 then + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_medium, 250, 700, 10 + P * 300, 250, vNorm ) or {} ) do + local a = random(0,2) + if a > 0 then + if a > 1 then + v:SetMaterial( m_rain2 ) + else + v:SetMaterial(m_rain3 ) + end + v:SetSize( 250, 250 ) + v:SetSpeed( v:GetSpeed() * math.Rand(1,2)) + else + v:SetSize( 1.22 + 1.56 * P * math.Rand(1,3) * 10, 5.22 + 7.56 * P * 10 ) + end + v:SetAlpha(min(15 + 4 * P + L,255) * 0.2) + end + end + if P > (0.5 - W * 0.4) and L > 5 then + local dis = random(900 - W * 100 - P * 500,multi_dis) + local d = max(dis / multi_dis, 0.5) + local s = math.Rand(0.5,1) * max(0.7,P) * 300 * d + --StormFox2.Misc.rain_template_multi:SetAlpha(math.min(15 + 4 * P + L,255) * .2) + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_multi, dis, multi_dis * 2, (90 + P * (250 + W)) / 2, s, vNorm ) or {} ) do + local d = v:GetDistance() + v:SetAlpha(15) + if not d or d < 500 then + v:SetSize( 225, 500 ) + else + v:SetSize( d * .45, d) + end + if random(0,1) == 1 then + v:SetMaterial(m2) + end + end + end + else + -- Spawn snow particles + local force_multi = max(1, (W / 6)) + local snow_distance = min(random(140,500), StormFox2.Fog.GetEnd()) + local d = min(snow_distance / 500, 1) + local snow_size = math.Rand(3,5) * d * max(0.7,P) + local s = math.Rand(3,5) * d * max(0.7,P) + local snow_speed = 0.15 * force_multi + + StormFox2.Misc.snow_template:SetSpeed( snow_speed ) + local n = max(min(L * 3, 255), 150) + snow_col.r = n + snow_col.g = n + snow_col.b = n + StormFox2.Misc.snow_template:SetColor(snow_col) + StormFox2.Misc.snow_template_multi:SetColor(snow_col) + local max_normal = 40 * P * (50 - W) + if StormFox2.Environment.GetOutSideFade() < 0.9 then + max_normal = 40 * P + end + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.snow_template, 20, snow_distance, max_normal, 5, vNorm ) or {} ) do + v:SetSize( s, s ) + v:SetSpeed( math.Rand(1, 2) * snow_speed) + if snow_speed > 0.15 and random(snow_speed)> 0.15 then + v.hitType = SF_DOWNFALL_HIT_NIL + end + end + -- Spawn snow distant + if P > 0.15 then + local max_multi = 10 * (P - 0.15) * (70 - W) + if StormFox2.Environment.GetOutSideFade() < 0.9 then + max_normal = 10 * (P - 0.15) + end + local snow_distance = min(random(300,900), StormFox2.Fog.GetEnd()) + local d = max(snow_distance / 900, 0.5) + local snow_size = math.Rand(0.5,1) * max(0.7,P) * 500 * d + local snow_speed = 0.15 * d * force_multi + StormFox2.Misc.snow_template:SetSpeed( mult_speed ) + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.snow_template_multi, 500, snow_distance / 1, max_multi, s, vNorm ) or {} ) do + v:SetSize( snow_size, snow_size ) + v:SetSpeed( math.Rand(1, 2) * snow_speed) + v:SetRoll( math.Rand(0, 360)) + if random(0,1) == 0 then + v:SetMaterial(m_snowmulti2) + end + end + end + local max_fog = (90 + P * (20 + (W / 80) * 102)) + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_medium, snow_distance, snow_distance * 2 , max_fog, s, vNorm ) or {} ) do + local d = v:GetDistance() + if not d or d < 500 then + v:SetSize( 225, 500 ) + else + v:SetSize( d * .45, d) + end + if random(0,1) >= 0 then + v:SetMaterial(m2) + end + end + end + end + + +-- Render Filter (Screen filter, this is additive) + local blizard = Material("stormfox2/effects/blizzard.png", "noclamp") + local storm = Material("stormfox2/effects/rainstorm.png", "noclamp") + local sx,sy = 0,0 + local rx,ry,rx2,ry2 = 0,0,0,0 + --surface.SetDrawColor( Color(255,255,255,34 * a) ) + --surface.SetMaterial( snowMulti ) + --surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH() ,c2,0 + c,2 + c2,2 + c) + --surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH() ,-c2 + c4,0 + c3,1 - c2 + c4,1 + c3) + local up = Vector(0,0,1) + + local function setMaterialRoll(mat, roll, u, v) + local matrix = Matrix() + local w = mat:Width() + local h = mat:Height() + matrix:SetAngles(Angle(0,roll,0)) + matrix:Translate(Vector(u, v, 0)) + mat:SetMatrix("$basetexturetransform", matrix) + end + function rain.DepthFilter(w, h, a) + a = (a - 0.75) * 4 + if a <= 0 then return end + local windDir = (-StormFox2.Wind.GetNorm()):Angle() + local rainscale = (StormFox2.Temperature.Get() + 2) / 2 + + local ad = math.AngleDifference(StormFox2.Wind.GetYaw() + 180, StormFox2.util.GetCalcView().ang.y) + local ada = math.sin(math.rad(ad)) + -- 0 = directly into the wind + -- 1 = directly to the side of the wind + + -- 0 = not moving at all + -- 1 = max movment + local A = EyeAngles():Forward() + local B = windDir:Forward() + local D = math.abs(A:Dot(B)) + local C = 1 - D + local P = StormFox2.Weather.GetPercent() + local W = math.min(1, StormFox2.Wind.GetForce() / 60) + + local B2 = windDir:Right() + local D2 = (A:Dot(B2)) + + if rainscale > -1 then + local WP = math.min(1, P) -- 0 - 1 Wimdy + local wind_x = ada * -C * 4 * WP + local wind_y = -8 * math.max(0.5, WP) + local roll = (windDir.p - 270) * -D2 * 0.8 + rx = (rx + FrameTime() * wind_x) % 1 + ry = (ry + FrameTime() * wind_y) % 1 + setMaterialRoll(storm, 180 - roll + 3, rx, ry) + surface.SetMaterial( storm ) + surface.SetDrawColor( Color(255,255,255,84 * a * math.max(0.1, W) * WP * math.max(C,0)) ) + local s,s2 = 1.5, 1.8 + surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0,0 + s, 0 + s) + -- surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), rx,ry, rx + s2,ry + s2) + elseif rainscale < 1 then + local WP = math.min(1, W) + local wind_x = ada * -C * 4 * WP + local wind_y = -8 * math.max(0.5, WP) + local roll = (windDir.p - 270) * -ada + sx = (sx + FrameTime() * wind_x) % 1 + sy = (sy + FrameTime() * wind_y) % 1 + setMaterialRoll(blizard, 180 - roll + 14, w / 2, h / 2) + surface.SetDrawColor( Color(255,255,255,144 * a * math.max(WP,0.1) * ((P) * 1.15) ) ) + surface.SetMaterial( blizard ) + surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), sx, sy, 2 + sx, 2 + sy) + surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), -sx, sy, 1 - sx, 1 + sy) + + + -- surface.SetDrawColor( Color(255,255,255,255 * a * WP * D ) ) + -- surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0, 1, 1) + -- surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0, 0.6, 0.6) + end + --print(">",w,h,a) + end + +-- Render water + local debri = Material("stormfox2/effects/terrain/snow_water") + rain.PreDrawTranslucentRenderables = function( a, b) + local f = 5 + StormFox2.Temperature.Get() + if f > 0 then return end + debri:SetFloat("$alpha",StormFox2.Weather.GetPercent() * 0.3 * math.Clamp(-f, 0, 1)) + render.SetMaterial(debri) + StormFox2.Environment.DrawWaterOverlay( b ) + end +end + +-- 2D skyboxes +if SERVER then + local t_day, t_night, t_sunrise, t_sunset + t_day = {"sky_day03_02", "sky_day03_03", "sky_day03_04"} + t_sunrise = {"sky_day01_01"} + t_sunset = {"sky_day01_06"} + t_night = {"sky_day01_09"} + + cloudy:SetSunStamp("skyBox",t_day, SF_SKY_DAY) + cloudy:SetSunStamp("skyBox",t_sunrise, SF_SKY_SUNRISE) + cloudy:SetSunStamp("skyBox",t_sunset, SF_SKY_SUNSET) + cloudy:SetSunStamp("skyBox",t_night, SF_SKY_NIGHT) +end diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/fallout.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/fallout.lua new file mode 100644 index 0000000..4db904e --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/fallout.lua @@ -0,0 +1,188 @@ + +local rad = StormFox2.Weather.Add( "Radioactive", "Rain" ) +if CLIENT then + function rad:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + return language.GetPhrase('sf_weather.fallout'), "Nuclear fallout" + end +else + function rad:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + return "Nuclear fallout", "Nuclear fallout" + end +end + +local m_def = Material("stormfox2/hud/w_fallout.png") +function rad.GetSymbol( nTime ) -- What the menu should show + return m_def +end +function rad.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show + return m_def +end + +-- Day -- + rad:SetSunStamp("topColor",Color(3.0, 102.9, 3.5), SF_SKY_DAY) + rad:SetSunStamp("bottomColor",Color(20, 55, 25), SF_SKY_DAY) + rad:SetSunStamp("duskColor",Color(3, 5.9, 3.5), SF_SKY_DAY) + rad:SetSunStamp("duskScale",1, SF_SKY_DAY) + rad:SetSunStamp("HDRScale",0.33, SF_SKY_DAY) +-- Night + rad:SetSunStamp("topColor",Color(0.4, 20.2, 0.54),SF_SKY_NIGHT) + rad:SetSunStamp("bottomColor",Color(2.25, 25,2.25),SF_SKY_NIGHT) + rad:SetSunStamp("duskColor",Color(.4, 1.2, .54), SF_SKY_NIGHT) + rad:SetSunStamp("duskScale",0, SF_SKY_NIGHT) + rad:SetSunStamp("HDRScale",0.1, SF_SKY_NIGHT) +-- Sunset/rise + rad:SetSunStamp("duskScale",0.26, SF_SKY_SUNSET) + rad:SetSunStamp("duskScale",0.26, SF_SKY_SUNRISE) + +if CLIENT then + -- Snd + local rain_light = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_light.ogg", SF_AMB_OUTSIDE, 1 ) + local rain_window = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_WINDOW, 0.1 ) + local rain_outside = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_outside.ogg", SF_AMB_NEAR_OUTSIDE, 0.1 ) + local rain_watersurf = StormFox2.Ambience.CreateAmbienceSnd( "ambient/water/water_run1.wav", SF_AMB_UNDER_WATER_Z, 0.1 ) + local rain_roof_wood = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof.ogg", SF_AMB_ROOF_WOOD, 0.1 ) + local rain_roof_metal = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof_metal.ogg", SF_AMB_ROOF_METAL, 0.1 ) + local rain_glass = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_ROOF_GLASS, 0.1 ) + rad:AddAmbience( rain_light ) + rad:AddAmbience( rain_window ) + rad:AddAmbience( rain_outside ) + rad:AddAmbience( rain_watersurf ) + rad:AddAmbience( rain_roof_wood ) + rad:AddAmbience( rain_roof_metal ) + rad:AddAmbience( rain_glass ) + -- Edit watersurf + rain_watersurf:SetFadeDistance(0,100) + rain_watersurf:SetVolume( 0.05 ) + rain_watersurf:SetPlaybackRate(2) + -- Edit rain_glass + rain_roof_metal:SetFadeDistance(10,400) + rain_glass:SetFadeDistance(10, 400) + rain_window:SetFadeDistance(100, 200) + -- Edit rain_outside + rain_outside:SetFadeDistance(100, 200) + + local m_rain = Material("stormfox2/raindrop.png") + local m_rain2 = Material("stormfox2/effects/raindrop-multi2.png") + local m_rain3 = Material("stormfox2/effects/raindrop-multi3.png") + local m_rain_multi = Material("stormfox2/effects/snow-multi.png","noclamp smooth") + function rad.TickSlow() + local P = StormFox2.Weather.GetPercent() + local L = StormFox2.Weather.GetLuminance() + + rain_outside:SetVolume( P ) + rain_light:SetVolume( P ) + rain_window:SetVolume( P * 0.3 ) + rain_roof_wood:SetVolume( P * 0.3 ) + rain_roof_metal:SetVolume( P * 1 ) + rain_glass:SetVolume( P * 0.5 ) + + local P = StormFox2.Weather.GetPercent() + local speed = 0.72 + 0.36 * P + StormFox2.Misc.rain_template:SetSpeed( speed ) + StormFox2.Misc.rain_template_medium:SetSpeed( speed ) + StormFox2.Misc.rain_template_medium:SetAlpha( L / 5) + end + local multi_dis = 1200 + local c = Color(150,250,150) + function rad.Think() + local P = StormFox2.Weather.GetPercent() + local L = StormFox2.Weather.GetLuminance() + local W = StormFox2.Wind.GetForce() + if StormFox2.DownFall.GetGravity() < 0 then return end -- Rain can't come from the ground. + + -- Set alpha + local s = 1.22 + 1.56 * P + StormFox2.Misc.rain_template:SetSize( s , 5.22 + 7.56 * P) + StormFox2.Misc.rain_template:SetColor(c) + StormFox2.Misc.rain_template:SetAlpha(math.min(15 + 4 * P + L,255)) + StormFox2.Misc.rain_template_medium:SetAlpha(math.min(15 + 4 * P + L,255) /3) + -- Spawn rain particles + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template, 10, 700, 10 + P * 900, 5, vNorm ) or {} ) do + v:SetSize( 1.22 + 1.56 * P * math.Rand(1,2), 5.22 + 7.56 * P ) + end + -- Spawn distant rain + if P > 0.15 then + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_medium, 250, 700, 10 + P * 500, 250, vNorm ) or {} ) do + v:SetColor(c) + local a = math.random(0,2) + if a > 0 then + if a > 1 then + v:SetMaterial( m_rain2 ) + else + v:SetMaterial(m_rain3 ) + end + v:SetSize( 250, 250 ) + v:SetSpeed( v:GetSpeed() * math.Rand(1,2)) + else + v:SetSize( 1.22 + 15.6 * P * math.Rand(1,3), 5.22 + 75.6 * P ) + end + v:SetAlpha(math.min(15 + 4 * P + L,255) * 0.2) + end + end + if P > (0.5 - W * 0.4) then + local dis = math.random(900 - W * 100 - P * 500,multi_dis) + local d = math.max(dis / multi_dis, 0.5) + local s = math.Rand(0.5,1) * math.max(0.7,P) * 300 * d + --StormFox2.Misc.rain_template_multi:SetAlpha(math.min(15 + 4 * P + L,255) * .2) + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_multi, dis, multi_dis * 2, (90 + P * (250 + W)) / 2, s, vNorm ) or {} ) do + local d = v:GetDistance() + if not d or d < 500 then + v:SetSize( 225, 500 ) + else + v:SetSize( d * .45, d) + end + + if math.random(0,1) == 1 then + v:SetMaterial(m2) + end + end + end + end + -- Render fallout + local debri = Material("stormfox2/effects/terrain/fallout_water") + local function renderD( a, b) + local P = StormFox2.Weather.GetPercent() + debri:SetFloat("$alpha",StormFox2.Weather.GetPercent()) + render.SetMaterial(debri) + StormFox2.Environment.DrawWaterOverlay( b ) + end + rad.PreDrawTranslucentRenderables = renderD + +else + -- Take dmg in rain, slowly + local nt = 0 + function rad.Think() + if nt < CurTime() then + nt = CurTime() + 2 + local dmg = DamageInfo() + dmg:SetDamageType( DMG_RADIATION ) + dmg:SetDamage(10) + dmg:SetAttacker( Entity(0) ) + dmg:SetInflictor( Entity(0) ) + local P = StormFox2.Weather.GetPercent() * 5 + for i,v in ipairs( player.GetAll() ) do + if v:WaterLevel() > 0 then + dmg:SetDamage((v:WaterLevel() ) * P) + elseif StormFox2.Wind.IsEntityInWind(v) then + dmg:SetDamage(P) + else + continue + end + v:TakeDamageInfo(dmg) + v:EmitSound("player/geiger" .. math.random(1,3) .. ".wav") + end + end + end +end + +-- Terrain + local radt = StormFox2.Terrain.Create("radio") + rad:SetTerrain( function(a) return StormFox2.Weather.GetPercent() > 0.5 and radt end ) + radt:SetGroundTexture("nature/toxicslime001a") +-- Footsounds + radt:MakeFootprints(true,{ + "player/footsteps/gravel1.wav", + "player/footsteps/gravel2.wav", + "player/footsteps/gravel3.wav", + "player/footsteps/gravel4.wav" + },"gravel.step") \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/fog.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/fog.lua new file mode 100644 index 0000000..e73fd8f --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/fog.lua @@ -0,0 +1,54 @@ +-- Rain and cloud is nearly the same. +local fog = StormFox2.Weather.Add( "Fog" ) +fog:Set("fogDistance", 150) +fog:Set("fogIndoorDistance", 600) +if CLIENT then + function fog.Think() + local p = StormFox2.Weather.GetPercent() + if p < 0.5 then return end + // tTemplate, nMinDistance, nMaxDistance, nAimAmount, traceSize, vNorm, fFunc ) + local fc = StormFox2.Fog.GetColor() + local c = Color(fc.r,fc.g,fc.b, 0) + for _,v in ipairs( StormFox2.DownFall.SmartTemplate(StormFox2.Misc.fog_template, 200, 900, 45 * p - 15, 250, vNorm ) or {} ) do + v:SetColor( c ) + end + end + + function fog:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + if nFraction < 0.2 then + return language.GetPhrase('#sf_weather.clear'), 'Clear' + elseif nFraction < 0.6 then + return language.GetPhrase('#sf_weather.fog.low'), 'Haze' + elseif nFraction < 0.8 then + return language.GetPhrase('#sf_weather.fog.medium'), 'Fog' + else + return language.GetPhrase('#sf_weather.fog.high'), 'Thick Fog' + end + end +else + function fog:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + if nFraction < 0.2 then + return 'Clear', 'Clear' + elseif nFraction < 0.6 then + return 'Haze', 'Haze' + elseif nFraction < 0.8 then + return 'Fog', 'Fog' + else + return 'Thick Fog', 'Thick Fog' + end + end +end + + + +-- Fog icon +do + -- Icon + local m_def = Material("stormfox2/hud/w_fog.png") + function fog.GetSymbol( nTime ) -- What the menu should show + return m_def + end + function fog.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show + return m_def + end +end diff --git a/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/lava.lua b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/lava.lua new file mode 100644 index 0000000..88aa87c --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/stormfox2/weathers/lava.lua @@ -0,0 +1,88 @@ +local lava = StormFox2.Weather.Add( "Lava", "Cloud" ) +lava:Set("fogDistance", 2000) +lava:Set("fogIndoorDistance", 3000) +lava:Set("mapDayLight",0) +local s = 10 +lava:SetSunStamp("bottomColor",Color(50, 2.5, 2.5), SF_SKY_DAY) +lava:SetSunStamp("bottomColor",Color(2.25, .225,.225),SF_SKY_NIGHT) + +if CLIENT then + function lava:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + return language.GetPhrase('sf_weather.lava') + end +else + function lava:GetName(nTime, nTemp, nWind, bThunder, nFraction ) + return "Lava", "Lava" + end +end +local m_def = Material("stormfox2/hud/w_lava.png") +function lava.GetSymbol( nTime ) -- What the menu should show + return m_def +end +function lava.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show + return m_def +end + +-- Terrain +local t_lava = StormFox2.Terrain.Create("lava") +local a = function() + local P = StormFox2.Weather.GetPercent() + return P > 0.5 and t_lava +end +lava:SetTerrain( a ) +t_lava:SetGroundTexture("stormfox2/effects/terrain/lava_ground", true) + +-- Downfall & Snd +if CLIENT then + local lava_snd = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/lava.ogg", SF_AMB_OUTSIDE, 0.4 ) + local m_lava = Material("stormfox2/effects/lava_particle") + lava:AddAmbience( lava_snd ) + local p_d = Material("stormfox2/effects/lava_particle2") + local lava_particles = StormFox2.DownFall.CreateTemplate(p_d, true) + lava_particles:SetFadeIn( true ) + lava_particles:SetRandomAngle(0.1) + function lava:Think() + local P = StormFox2.Weather.GetPercent() + lava_snd:SetVolume( P * .4 ) + + local l = math.min(255, StormFox2.Weather.GetLuminance() * 7) + lava_particles:SetColor(Color(l,l,l)) + lava_particles:SetSpeed(.3) -- Makes the start position + local dis = math.random(100,1500) + for _,v in ipairs( StormFox2.DownFall.SmartTemplate( lava_particles, 200, dis, P * 800, 5, vNorm ) or {} ) do + local s = math.Rand(10,200) + v:SetSize( s, s ) + v:SetSpeed( math.Rand(.05, .15) ) + if math.random(1,2) == 1 then + v:SetMaterial(m_lava) + end + end + end +-- Render water debri + local debri = Material("stormfox2/effects/terrain/lava_water") + local function renderD( a, b) + render.SetMaterial(debri) + StormFox2.Environment.DrawWaterOverlay( b ) + end + lava.PreDrawTranslucentRenderables = renderD +end + +-- Burn +if SERVER then + t_lava:MakeFootprints( false, nil, nil, function(ent, foot, SoundName, sTex, bReplace) + if not ent or not IsValid(ent) then return end + if not bReplace then return end + if ent.Health and ent:Health() <= 1 then return end + if math.random(1, 10) <= 9 then + local burn = DamageInfo() + burn:SetDamage( math.random(5, 10) ) + burn:SetDamageType(DMG_BURN) + burn:SetInflictor(game.GetWorld()) + burn:SetAttacker(game.GetWorld()) + burn:SetDamagePosition( ent:GetPos() ) + ent:TakeDamageInfo( burn ) + else + ent:Ignite(1, 0) + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/weapons/lightning_gun.lua b/garrysmod/addons/feature-stormfox/lua/weapons/lightning_gun.lua new file mode 100644 index 0000000..89c9db7 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/weapons/lightning_gun.lua @@ -0,0 +1,196 @@ + +--[[------------------------------------------------------------------------- + Point and click +---------------------------------------------------------------------------]] + +AddCSLuaFile() + +SWEP.PrintName = "Mjölnir Gun" +SWEP.Instructions = "The power of Thor!" +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.UseHands = true + +SWEP.ViewModel = Model( "models/weapons/c_pistol.mdl") --"models/weapons/w_eq_tablet.mdl") -- ) +SWEP.WorldModel = Model( "models/weapons/w_pistol.mdl" ) + +-- No ammo + SWEP.Primary.ClipSize = -1 + SWEP.Primary.DefaultClip = -1 + SWEP.Primary.Automatic = false + SWEP.Primary.Ammo = "none" + + SWEP.Secondary.ClipSize = -1 + SWEP.Secondary.DefaultClip = -1 + SWEP.Secondary.Automatic = true + SWEP.Secondary.Ammo = "none" + + SWEP.Slot = 5 + SWEP.SlotPos = 1 + +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.Spawnable = true +SWEP.UseHands = true + +SWEP.Selected = 1 + +if ( SERVER ) then + SWEP.AutoSwitchTo = true + SWEP.AutoSwitchFrom = true +end + +function SWEP:Initialize() + self:SetHoldType( "pistol" ) + self:SendWeaponAnim( 1 ) + self.power = 0 + self._bUsed = true + self._bGlow = -1 +end +function SWEP:HasPower() + return self:GetPower() >= 1 +end +function SWEP:GetPower() + local a = (CurTime() - self.power) / 1.2 + if a >= 1 then return 1 end + return math.max(0, a) +end +function SWEP:UsePower( t ) + self.power = CurTime() + t + self._bUsed = true + timer.Simple(0.5, function() + if not IsValid( self ) then return end + self._bGlow = -1 + self:SendWeaponAnim( ACT_VM_IDLE_LOWERED ) + end) +end + +-- SF commands +local function Strike( self, bAlt ) + if CLIENT then return end + local tr = util.TraceLine( util.GetPlayerTrace( self:GetOwner() ) ) + if not tr.HitPos then return end + if bAlt then + StormFox2.Thunder.CreateAt( tr.HitPos + vector_up * 4 ) + else + StormFox2.Thunder.Strike( tr.HitPos, true ) + end +end +local function Rumble( self ) + if CLIENT then return end + local tr = util.TraceLine( util.GetPlayerTrace( self:GetOwner() ) ) + if not tr.HitPos then return end + StormFox2.Thunder.Rumble(tr.HitPos,true) +end + +function SWEP:CanPrimaryAttack() + return self:HasPower() +end + +function SWEP:PrimaryAttack() + if not IsFirstTimePredicted() then return end + if not self:CanPrimaryAttack() then return end + local Owner = self:GetOwner() + Owner:MuzzleFlash() + self.Weapon:SendWeaponAnim( 186 ) + Owner:SetAnimation( ACT_GLOCK_SHOOTEMPTY ) + self._bGlow = CurTime() + 0.1 + self:UsePower( ( 0.5 + CurTime()%0.5 ) ) + if SERVER then + Strike(self, Owner:KeyDown(IN_SPEED)) + else + --self:EmitSound("weapons/physcannon/energy_disintegrate4.wav", 75, 100, 0.2) + local hitPos = Owner:GetEyeTrace().HitPos or Owner:GetShootPos() + end +end + +function SWEP:SecondaryAttack() + if not IsFirstTimePredicted() then return end + if not self:CanPrimaryAttack() then return end + self:UsePower( 0.25 ) + self.Weapon:SendWeaponAnim( ACT_VM_IDLE_TO_LOWERED ) -- View model animation + if SERVER then + Rumble(self) + else + self:EmitSound("weapons/slam/mine_mode.wav") + end +end + +function SWEP:Think() + if self._bUsed and self:HasPower() then + self:SendWeaponAnim( ACT_VM_IDLE_TO_LOWERED ) + self._bUsed = false + self:EmitSound("weapons/physcannon/superphys_small_zap1.wav", 75, 100, 0.2) + end +end + +function SWEP:Deploy() + self.Weapon:SendWeaponAnim( 172 ) + return true +end + +function SWEP:ShouldDropOnDie() return false end + +if ( SERVER ) then return end -- Only clientside lua after this line +function SWEP:Holster() +end + +SWEP.WepSelectIcon = surface.GetTextureID( "vgui/gmod_camera" ) + +-- Don't draw the weapon info on the weapon selection thing +function SWEP:DrawHUD() end +function SWEP:PrintWeaponInfo( x, y, alpha ) end + +--[[------------------------------------------------------------------------- +function SWEP:CalcView(ply,pos,ang,fov) + --pos = pos + ang:Forward() * -50 + return pos,ang,fov +end +---------------------------------------------------------------------------]] + +-- CL swep rendering +function SWEP:CalcViewModelView( vm, _,_,pos, ang) + --pos = pos + ang:Forward() * 10 + ang:Right() * 30 + ang:Up()*5 + --ang:RotateAroundAxis(ang:Up(),80) + return pos,ang +end + + +function SWEP:DrawWorldModel() + self:DrawModel() + cam.Start3D2D(self:LocalToWorld(Vector(1,-0.4,0.4)),self:LocalToWorldAngles(Angle(0,0,80)),0.1) + local w,h,s = 20,4,1 + surface.SetDrawColor(0,0,0) + surface.DrawRect(0,0,w,h) + surface.SetDrawColor(0,255,0) + surface.DrawRect(s,s,w - s*2,h - s*2) + cam.End3D2D() +end + +local g_mat = Material("sprites/glow04_noz") +function SWEP:PostDrawViewModel(vm,wep,ply) + if not vm then return end + local pos, ang = vm:GetBonePosition(39) + if self._bGlow > -1 then + local time = (self._bGlow - CurTime()) * 4 + if time > 0 then + local t = (1 - time) + render.SetMaterial(g_mat) + render.DrawSprite(pos + ang:Up() * (3 + t * 2.1 ), 8 * t, 8 * t, color_white) + end + end + local pow = wep:GetPower() + ang:RotateAroundAxis(ang:Right(), 90) + cam.Start3D2D(pos - ang:Up() + ang:Forward() * -3,ang,0.1) + local w,h,s = 20,4,1 + local w2 = w * pow + surface.SetDrawColor(0,0,0) + surface.DrawRect(0,0,w,h) + if pow < 1 then + surface.SetDrawColor(255,255 * pow,0) + else + surface.SetDrawColor(55,55,255) + end + surface.DrawRect(s,s,(w2 - s * 2),h - s*2) + cam.End3D2D() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/cl_init.lua b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/cl_init.lua new file mode 100644 index 0000000..fab08c9 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/cl_init.lua @@ -0,0 +1,221 @@ + +--[[------------------------------------------------------------------------- + Point and click +---------------------------------------------------------------------------]] + +include("shared.lua") + + +if ( SERVER ) then + SWEP.AutoSwitchTo = false + SWEP.AutoSwitchFrom = false +end + +function SWEP:PrimaryAttack() + if not IsFirstTimePredicted() then return end + local tool = self:GetTool() + if not tool or not tool.LeftClick then return end + tool.LeftClick(tool, self:GetOwner():GetEyeTrace()) +end + +function SWEP:SecondaryAttack() + if not IsFirstTimePredicted() then return end + local tool = self:GetTool() + if not tool or not tool.RightClick then return end + tool.RightClick(tool, self:GetOwner():GetEyeTrace()) +end + +function SWEP:Holster() + self:RemoveGhost() + return true +end + +function SWEP:OnRemove() + self:RemoveGhost() + return true +end + +local oldTool = -1 +function SWEP:Think() + local tool_id = self:GetToolID() + if tool_id ~= oldTool then + self:RemoveGhost() + oldTool = tool_id + self:SetTool(tool_id) + end +end + +local ghostHalo +function SWEP:SetGhost(mdl, pos, ang) + -- Remove ghost if nil mdl + if not mdl then + if self._ghost and IsValid(self._ghost) then + self._ghost:Remove() + self._ghost = nil + end + return + end + -- Make ghost or set mdl + if not self._ghost or not IsValid(self._ghost) then + self._ghost = ClientsideModel(mdl, RENDERMODE_TRANSCOLOR ) + ghostHalo = nil + elseif self._ghost:GetModel() ~= mdl then + self._ghost:SetModel(mdl) + end + -- Move ghost + if pos then + self._ghost:SetPos(pos) + end + if ang then + self._ghost:SetAngles(ang) + end + return self._ghost +end + +function SWEP:SetGhostHalo(col) + ghostHalo = col +end + +function SWEP:RemoveGhost() + self:SetGhost() +end + +-- Context menu +do + local v = false + hook.Add("OnContextMenuOpen", "StormFox2.Tool.COpen", function() + v = true + end) + hook.Add("OnContextMenuClose", "StormFox2.Tool.CClose", function() + v = false + end) + + function SWEP:IsContextMenuOpen() + return v + end +end + +hook.Add("PreDrawHalos", "StormFox2.GhostHalo", function() + local wep = LocalPlayer():GetActiveWeapon() + if not wep or not IsValid(wep) then return end + if wep:GetClass() ~= "sf2_tool" then return end + if not IsValid(wep._ghost) then return end + if not ghostHalo then return end + halo.Add( {wep._ghost}, ghostHalo, 5, 5, 2 ) +end) + +SWEP.WepSelectIcon = surface.GetTextureID( "vgui/gmod_camera" ) + +-- Don't draw the weapon info on the weapon selection thing +function SWEP:DrawHUD() end +function SWEP:PrintWeaponInfo( x, y, alpha ) end + +--[[------------------------------------------------------------------------- +function SWEP:CalcView(ply,pos,ang,fov) + --pos = pos + ang:Forward() * -50 + return pos,ang,fov +end +---------------------------------------------------------------------------]] + +-- Unstable screen +function SWEP:_GetScreenUN() + return self._unstable or 0.2 +end +function SWEP:_SetScreenUN( n ) + self._unstable = n +end + +-- Render screen +local matScreen = Material( "stormfox2/weapons/sf_tool_screen" ) +local bgMat = Material("stormfox2/logo.png") +local sMat = Material("effects/tvscreen_noise002a") +local rMat = Material("gui/r.png") +do + local ScreenSize = 256 + local RTTexture = GetRenderTarget( "SFToolgunScreen", ScreenSize, ScreenSize ) + function SWEP:RenderToolScreen() + local TEX_SIZE = ScreenSize + -- Set up our view for drawing to the texture + --cam.IgnoreZ(true) + render.PushRenderTarget( RTTexture ) + render.ClearDepth() + render.Clear( 0, 0, 0, 0 ) + cam.Start2D() + -- Draw Screen + local tool = self:GetTool() + if not tool then + surface.SetDrawColor(color_white) + surface.SetMaterial(bgMat) + surface.DrawTexturedRect(TEX_SIZE * 0.1,TEX_SIZE * 0.1,TEX_SIZE * 0.8,TEX_SIZE * 0.8) + surface.SetMaterial(rMat) + if math.Round(CurTime()%2) ~= 0 then + surface.DrawTexturedRect(20,TEX_SIZE - 60,40,40) + end + else + if not tool.NoPrintName then + draw.DrawText(tool.PrintName or "Unknown", "sf_tool_large", TEX_SIZE / 2, 10, color_white, TEXT_ALIGN_CENTER) + end + if tool.ScreenRender then + tool:ScreenRender( TEX_SIZE, TEX_SIZE ) + end + end + -- surface.SetMaterial(sMat) + -- surface.DrawTexturedRect(TEX_SIZE * 0.1,TEX_SIZE * 0.1,TEX_SIZE * 0.8,TEX_SIZE * 0.8) + if self:_GetScreenUN() > 0.15 then + surface.SetDrawColor(color_white) + surface.SetMaterial(sMat) + surface.DrawTexturedRect(0,0,TEX_SIZE * 2,TEX_SIZE * 2) + end + cam.End2D() + render.PopRenderTarget() + + matScreen:SetTexture( "$basetexture", RTTexture ) + matScreen:SetFloat("$shake", self:_GetScreenUN()) + --cam.IgnoreZ(false) + end +end + +local mTool = Material("stormfox2/weapons/sf_tool") + +function SWEP:PreDrawViewModel() + if self:_GetScreenUN() > 0 then + self:_SetScreenUN( math.max(0, self:_GetScreenUN() - FrameTime() * 0.6) ) + end + self:RenderToolScreen() + render.MaterialOverrideByIndex(1,matScreen) + render.MaterialOverrideByIndex(2,mTool) + +end +function SWEP:PostDrawViewModel() + render.MaterialOverrideByIndex() + local tool = self:GetTool() + if not tool then return end + -- Render + if tool.Render then + cam.Start3D() + tool:Render() + cam.End3D() + end +end + +-- CL swep rendering +function SWEP:CalcViewModelView( vm, _,_,pos, ang) +end + +function SWEP:Deploy() + self:_SetScreenUN( 0.2 ) +end + +function SWEP:DrawWorldModel() + local Owner = self:GetOwner() + if IsValid(Owner) and Owner ~= LocalPlayer() then + self:_SetScreenUN( 0 ) + elseif self:_GetScreenUN() < 0.4 then + self:_SetScreenUN( math.min(0.4, self:_GetScreenUN() + FrameTime() * 0.2) ) + end + self:RenderToolScreen() + render.MaterialOverrideByIndex(1,matScreen) + render.MaterialOverrideByIndex(2,mTool) + self:DrawModel() + render.MaterialOverrideByIndex() +end \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/init.lua b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/init.lua new file mode 100644 index 0000000..fc9658d --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/init.lua @@ -0,0 +1,93 @@ + +--[[------------------------------------------------------------------------- + Point and click +---------------------------------------------------------------------------]] + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) + +include("shared.lua") + +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true + +function SWEP:ShouldDropOnDie() return false end + +-- Add tools +local TOOLS = {} + +function SWEP:SwitchTool() + local n = self:GetToolID() + 1 + if n > #self.Tool then + n = 1 + end + self:SetTool( n ) +end + +function SWEP:HasAccessToSettings( onSuccess, ... ) + local a = {...} + local ply = self:GetOwner() + if not IsValid(ply) then return end + CAMI.PlayerHasAccess(ply,"StormFox Settings",function(b) + if not b then + if IsValid(ply) then + ply:EmitSound("ambient/alarms/klaxon1.wav") + end + SafeRemoveEntity(self) + end + onSuccess( unpack( a ) ) + end) +end + +function SWEP:Equip( newOwner ) + if newOwner:GetClass() ~= "player" then + SafeRemoveEntity(self) + else + self:HasAccessToSettings( function() end ) + end +end + +function SWEP:PrimaryAttack() + if not IsFirstTimePredicted() then return end + if ( game.SinglePlayer() ) then self:CallOnClient( "PrimaryAttack" ) end + local tool = self:GetTool() + if not tool or not tool.LeftClick then return end + local Owner = self:GetOwner() + if tool.LeftClick(self, Owner:GetEyeTrace()) then + self:DoShootEffect(Owner:GetEyeTrace(),IsFirstTimePredicted()) + end +end + +function SWEP:SecondaryAttack() + if not IsFirstTimePredicted() then return end + if ( game.SinglePlayer() ) then self:CallOnClient( "SecondaryAttack" ) end + local tool = self:GetTool() + if not tool or not tool.RightClick then return end + local Owner = self:GetOwner() + if tool.RightClick(self, Owner:GetEyeTrace()) then + self:DoShootEffect(Owner:GetEyeTrace(),IsFirstTimePredicted()) + end +end + +function SWEP:Holster() + if not IsFirstTimePredicted() then return end + if ( game.SinglePlayer() ) then self:CallOnClient( "Holster" ) end + return true +end + +function SWEP:Reload() + if not IsFirstTimePredicted() then return end + local Owner = self:GetOwner() + if ( !Owner:KeyPressed( IN_RELOAD ) ) then return end + self:SwitchTool() + Owner:EmitSound("buttons/button14.wav") +end + +function SWEP:Think() +end + +-- Stops players from picking up multiple tools +hook.Add("PlayerCanPickupWeapon", "StormFox2.Tool.Pickup", function(ply, wep) + if (wep:GetClass() ~= "sf2_tool") then return end -- Ignore other weapons + if IsValid(ply:GetWeapon("sf2_tool")) then return false end -- If you already have a tool, don't pick this one up +end) \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/settings/light_editor.lua b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/settings/light_editor.lua new file mode 100644 index 0000000..2138f1c --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/settings/light_editor.lua @@ -0,0 +1,322 @@ +local TOOL = {} +TOOL.RealName = "Light Editor" +TOOL.PrintName = "#sf_tool.light_editor" +TOOL.ToolTip = "#sf_tool.light_editor.desc" +TOOL.NoPrintName = false +-- TOOL.ShootSound = Sound("weapons/irifle/irifle_fire2.wav") + +local SPAWN = 0 +local DELETE = 1 +local SPAWN_ALL = 2 +local DELETE_ALL= 3 + +local INVALID = 0 +local POINTLIGHT= 1 +local SPOTLIGHT = 2 +local FAKESPOT = 3 + +local t_models = {} + t_models['models/props_c17/lamppost03a_off.mdl'] = {Vector(0,94,440), Angle(0,0,0), SPOTLIGHT} + t_models['models/sickness/evolight_01.mdl'] = {Vector(0,-80,314), Angle(0,0,0), SPOTLIGHT} + t_models['models/props_lighting/lightfixture02.mdl'] = {Vector(50,0,-10), Angle(30,0,0), FAKESPOT} + t_models['models/sickness/parkinglotlight.mdl'] = {Vector(0,30,284), Angle(0,0,0), FAKESPOT, Vector(0,-30,284)} + t_models['models/props/de_inferno/light_streetlight.mdl'] = {Vector(0,0,150), Angle(0,0,0), POINTLIGHT} + t_models['models/props/cs_office/light_inset.mdl'] = {Vector(0,0,-3), Angle(0,0,0), POINTLIGHT} + t_models['models/unioncity2/props_street/streetlight.mdl'] = {Vector(0,-108,388), Angle(0,0,0), SPOTLIGHT} + t_models['models/unioncity2/props_lighting/lightpost_double.mdl']={Vector(5,0,358),Angle(0,0,0), SPOTLIGHT, Vector(-75,0,358)} + t_models['models/unioncity2/props_street/telepole01b.mdl'] = {Vector(0,-109,335), Angle(0,0,0), SPOTLIGHT} + t_models['models/unioncity2/props_lighting/lightpost_single.mdl']={Vector(76,0,357),Angle(0,0,0), SPOTLIGHT} + + t_models['models/props_badlands/siloroom_light2.mdl'] = {Vector(0,0,-18), Angle(0,0,0), POINTLIGHT} + t_models['models/props_badlands/siloroom_light2_small.mdl'] = {Vector(0,0,-14), Angle(0,0,0), POINTLIGHT} + t_models['models/props_c17/light_cagelight01_off.mdl'] = {Vector(4,0,-8), Angle(0,0,0), POINTLIGHT} + t_models['models/props_c17/light_cagelight02_off.mdl'] = {Vector(4,0,-8), Angle(0,0,0), POINTLIGHT} + t_models['models/props_c17/light_cagelight02_on.mdl'] = {Vector(4,0,-8), Angle(0,0,0), POINTLIGHT} + t_models['models/props_c17/light_decklight01_off.mdl'] = {Vector(0,0,0), Angle(90,180,0),SPOTLIGHT} + t_models['models/props_c17/light_decklight01_on.mdl'] = {Vector(0,0,0), Angle(90,180,0),SPOTLIGHT} + t_models['models/props_c17/light_domelight01_off.mdl'] = {Vector(0,0,-8), Angle(0,0,0), POINTLIGHT} + t_models['models/props_c17/light_floodlight02_off.mdl'] = {Vector(0,-15,78), Angle(0,275,68),FAKESPOT,Vector(0,15,78), Angle(0,265,68)} + t_models['models/props_c17/light_industrialbell01_on.mdl'] = {Vector(0,0,-8), Angle(0,0,0), FAKESPOT} + t_models['models/props_combine/combine_light001a.mdl'] = {Vector(-6,0,34), Angle(90,0,0), SPOTLIGHT} + t_models['models/props_combine/combine_light001b.mdl'] = {Vector(-12,0,47), Angle(90,0,0), SPOTLIGHT} + t_models['models/props_combine/combine_light002a.mdl'] = {Vector(-9,0,37), Angle(90,0,0), SPOTLIGHT} + t_models['models/props_equipment/light_floodlight.mdl'] = {Vector(0,-12,80), Angle(0,275,68),FAKESPOT,Vector(0,12,80), Angle(0,265,68)} + t_models['models/props_gameplay/security_fence_light01.mdl']= {Vector(0,-68,-11), Angle(0,0,0), SPOTLIGHT} + t_models['models/props_wasteland/lights_industrialcluster01a.mdl']= {Vector(-20,0,374),Angle(52,0,0),SPOTLIGHT, Vector(20,0,374), Angle(-52,0,0)} + t_models['models/props_mvm/construction_light02.mdl'] = {Vector(-30,-25,144), Angle(0,275,68),FAKESPOT,Vector(-30,25,144), Angle(0,265,68)} + t_models['models/props_hydro/construction_light.mdl'] = {Vector(0,-3,-19), Angle(0,0,45), SPOTLIGHT} + t_models['models/props/cs_assault/streetlight.mdl'] = {Vector(50,0,45), Angle(0,0,0), SPOTLIGHT} + +local function IsLightNear(pos) + local t = {} + for k,v in ipairs(ents.FindInSphere(pos, 20)) do + if v:GetClass() == "stormfox_streetlight_invisible" then + return v + end + end +end + +local function SpawnMissingLight(pos, ang, i_type) + if IsLightNear(pos) then return end + local ent = ents.Create("stormfox_streetlight_invisible") + ent:SetPos(pos) + ent:SetAngles(ang) + ent:Spawn() + ent:SetLightType(i_type) + return ent +end + +local function StaticLocal(v, pos, ang) + return LocalToWorld(pos * (v.UniformScale or v.Scale or 1), ang, v.Origin, v.Angles) +end + +local function StaticLightPos(v) + local tab = t_models[v.PropType] + if not tab then return end -- Unknown + local pos, ang = StaticLocal(v, tab[1] * (v.UniformScale or v.Scale), tab[2]) + local spos, ang2 + if tab[4] then + spos, ang2 = StaticLocal(v, tab[4] * (v.UniformScale or v.Scale), tab[5] or tab[2]) + end + return pos, ang, spos, ang2 +end + +local sorter = function(a,b) + return a[5] 3 then default_lighttype = 1 end + end + end +end +return TOOL \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/settings/surface_editor.lua b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/settings/surface_editor.lua new file mode 100644 index 0000000..5fdce89 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/settings/surface_editor.lua @@ -0,0 +1,138 @@ + +local TOOL = {} +TOOL.RealName = "Surface Editor" +TOOL.PrintName = "#sf_tool.surface_editor" +TOOL.ToolTip = "#sf_tool.surface_editor.desc" +TOOL.NoPrintName = false +TOOL.ShootSound = Sound("weapons/irifle/irifle_fire2.wav") + +local mat = Material("stormfox2/weapons/sf_tool_mat") +local function FindTexture( str ) + str = str:lower() + if str == "**displacement**" then return end + if str == "**studio**" then return end + if str:sub(0,5) == "tools" then return end + local mat = Material(str) + --if str:sub(0,5) == "maps/" and false then -- This is a hammer thingy + -- str = mat:GetString( "$basetexture" ) or mat:GetString( "$basetexture2" ) + -- mat = Material(str) + -- if StormFox2.Terrain.HasMaterialChanged(mat) then -- We havve replaced this material. Lets get the basetexture + -- return StormFox2.Terrain.GetOriginalTexture(mat) + -- end + --end + return str +end + +local cross = Material("gui/cross.png") +local c_red = Color(255,55,55) + +local m_roof = Material("stormfox2/hud/tool/texture_roof.png") +local m_ground = Material("stormfox2/hud/tool/texture_ground.png") + +local snd_accept = Sound("buttons/button3.wav") +local snd_deny = Sound("buttons/button2.wav") + +if SERVER then + function TOOL:SendFunc( tex, a ) + if not tex or not a then return end + if type(tex) ~= "string" or type(a) ~= "number" then return end + StormFox2.Map.ModifyMaterialType( tex, a ) + self:EmitSound(snd_accept) + end +else + local function OpenOption( self, sTexture ) + local p = vgui.Create("DFrame") + p:SetTitle(language.GetPhrase("spawnmenu.menu.edit")) + p:SetSize( 50 * 3 + 10, 50 + 24) + p:Center() + p:MakePopup() + -- Roof + local roof = vgui.Create( "DImageButton", p ) + roof:SetSize( 50, 50) + roof:SetImage("stormfox2/hud/tool/texture_roof.png") + roof:Dock(LEFT) + roof.DoClick = function() + self.SendFunc( sTexture, 1 ) + p:Remove() + end + -- Roof + local ground = vgui.Create( "DImageButton", p ) + ground:SetSize( 50, 50) + ground:SetImage("stormfox2/hud/tool/texture_ground.png") + ground:Dock(LEFT) + ground.DoClick = function() + self.SendFunc( sTexture, 0 ) + p:Remove() + end + -- Block + local block = vgui.Create( "DImageButton", p ) + block:SetSize( 50, 50) + block:SetImage("gui/cross.png") + block:Dock(LEFT) + block.DoClick = function() + self.SendFunc( sTexture, -1 ) + p:Remove() + end + end + function TOOL:LeftClick(tr) + local tex = FindTexture(tr.HitTexture) + if not tex then + self:EmitSound(snd_deny) + return + end + OpenOption( self, tex ) + end + function TOOL:RightClick(tr) + local tex = FindTexture(tr.HitTexture) + if not tex then + self:EmitSound(snd_deny) + return + end + self.SendFunc( tex, -2, -2 ) + self:EmitSound(snd_accept) + end +end + +function TOOL:ScreenRender( w, h ) + local tr = LocalPlayer():GetEyeTrace() + local tex = FindTexture(tr.HitTexture) + if tex then + mat:SetTexture("$basetexture", tex) + -- In case this material doesn't have a valid texture, it might be only a material. + if not mat:GetTexture("$basetexture") then + local m = Material(tex) + local tryTex = StormFox2.Terrain.GetOriginalTexture(m) or m:GetTexture("$basetexture") + if tryTex then + mat:SetTexture("$basetexture", tryTex) + end + end + local tData = SF_TEXTDATA[tex] + surface.SetMaterial(mat) + surface.SetDrawColor(color_white) + surface.DrawTexturedRect(w * 0.1,h * 0.2,w * 0.8,h * 0.7) + if tData and tData[1] then + -- Roof type + if tData[1] == -1 then + surface.SetDrawColor(c_red) + surface.SetMaterial(cross) + surface.DrawTexturedRect(w * 0.15,h * 0.65,w * 0.2,h * 0.2) + elseif tData[1] == 0 then + surface.SetDrawColor(color_white) + surface.SetMaterial(m_ground) + surface.DrawTexturedRect(w * 0.15,h * 0.65,w * 0.2,h * 0.2) + elseif tData[1] == 1 then + surface.SetDrawColor(color_white) + surface.SetMaterial(m_roof) + surface.DrawTexturedRect(w * 0.15,h * 0.65,w * 0.2,h * 0.2) + end + end + else + surface.SetDrawColor(c_red) + surface.SetMaterial(cross) + surface.DrawTexturedRect(w * 0.1,h * 0.2,w * 0.8,h * 0.7) + surface.SetDrawColor(color_white) + end + + surface.DrawOutlinedRect(w * 0.1,h * 0.2,w * 0.8,h * 0.7) +end +return TOOL \ No newline at end of file diff --git a/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/shared.lua b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/shared.lua new file mode 100644 index 0000000..69d5487 --- /dev/null +++ b/garrysmod/addons/feature-stormfox/lua/weapons/sf2_tool/shared.lua @@ -0,0 +1,161 @@ + + +SWEP.PrintName = "#sf_tool.name" +SWEP.Author = "Nak" +SWEP.Contact = "" +SWEP.Purpose = "#sf_tool.desc" +SWEP.Instructions = "#sf_tool.desc" + +SWEP.ViewModel = "models/weapons/c_toolgun.mdl" +SWEP.WorldModel = "models/weapons/w_toolgun.mdl" + +SWEP.UseHands = true +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.Slot = 5 +SWEP.SlotPos = 5 + +util.PrecacheModel( SWEP.ViewModel ) +util.PrecacheModel( SWEP.WorldModel ) + +-- Tool meta +local t_meta = {} +-- Proxy allows to push entity functions within TOOL to SWEP. Its a hack, but I'm lazy. + local proxy_key,proxy_self + local function proxy(...) + local self = proxy_self + local func = self[proxy_key] + local a = {...} + -- In case first argument is "self", weplace it with SWEP + if #a > 0 then + if type(a[1]) == "table" and a[1].MetaName and a[1].MetaName == "sftool" then + a[1] = self + end + end + func(unpack(a)) + proxy_key = nil + proxy_self = nil + end + t_meta.__index = function(self, key) + if key == "_swep" then return end + if IsValid(self._swep) and self._swep[key] then + proxy_key = key + proxy_self = self._swep + return proxy + end + end + function t_meta:GetSWEP() + return self._swep + end + +-- Load tools +SWEP.Tool = {} + +for _,fil in ipairs(file.Find("weapons/sf2_tool/settings/*.lua","LUA")) do + if SERVER then + AddCSLuaFile("weapons/sf2_tool/settings/" .. fil) + end + local tool = (include("weapons/sf2_tool/settings/" .. fil)) + tool.MetaName = "sftool" + setmetatable(tool, t_meta) + table.insert(SWEP.Tool, tool) +end + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.CanHolster = true +SWEP.CanDeploy = true + +function SWEP:SetupDataTables() + self:NetworkVar( "Int", 0, "ToolID" ) +end + +function SWEP:SetTool(num) + self._toolobj = nil + if not IsValid(self:GetOwner()) then return end + if SERVER then + self:SetToolID( num ) + end + if num == 0 then return end -- Screen + self._toolobj = table.Copy(self.Tool[num]) + self._toolobj._swep = self + setmetatable(self._toolobj, t_meta) + return self._toolobj +end + +function SWEP:GetTool() + if not IsValid(self:GetOwner()) then return end -- No owner. + if self._toolobj then + return self._toolobj + end + local n = self:GetToolID() + if n == 0 then return end + self:SetTool(self:GetToolID()) + return self._toolobj +end + +function SWEP:DoShootEffect( tr, bFirstTimePredicted ) + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) -- View model animation + local Owner = self:GetOwner() + Owner:SetAnimation( PLAYER_ATTACK1 ) + if ( not bFirstTimePredicted ) then return end + local traceEffect = EffectData() + traceEffect:SetOrigin( tr.HitPos + tr.HitNormal * 4 ) + traceEffect:SetStart( Owner:GetShootPos() ) + traceEffect:SetAttachment( 1 ) + traceEffect:SetEntity( self ) + traceEffect:SetScale(0.2) + traceEffect:SetNormal( tr.HitNormal ) + util.Effect( "ToolTracer", traceEffect ) + util.Effect( "StunstickImpact", traceEffect ) + local tool = self:GetTool() + if not tool or not tool.ShootSound then return end + Owner:EmitSound(tool.ShootSound) +end + +if SERVER then + local function dofunction(ply, wep, tool, data) + StormFox2.Msg(ply:GetName(),color_white," used",tool.RealName or "SF2 Tool.") + tool.SendFunc( wep, unpack( data ) ) + wep:DoShootEffect(ply:GetEyeTrace(),IsFirstTimePredicted()) + end + net.Receive(StormFox2.Net.Tool, function(len, ply) + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then return end + if wep:GetClass() ~= "sf2_tool" then return end + local tool = wep:GetTool() + if not tool or not tool.SendFunc then return end + wep:HasAccessToSettings(dofunction, ply, wep, tool, net.ReadTable() ) + end) +else + function SWEP.SendFunc( ... ) + net.Start(StormFox2.Net.Tool) + net.WriteTable({...}) + net.SendToServer() + end +end + +function SWEP:Initialize() + self:SetHoldType( "revolver" ) + self.Primary = { + ClipSize = -1, + DefaultClip = -1, + Automatic = false, + Ammo = "none" + } + self.Secondary = { + ClipSize = -1, + DefaultClip = -1, + Automatic = false, + Ammo = "none" + } +end \ No newline at end of file diff --git a/garrysmod/addons/feature-talkicon/lua/autorun/talkicon.lua b/garrysmod/addons/feature-talkicon/lua/autorun/talkicon.lua new file mode 100644 index 0000000..c74f141 --- /dev/null +++ b/garrysmod/addons/feature-talkicon/lua/autorun/talkicon.lua @@ -0,0 +1,88 @@ + +CreateConVar('talkicon_computablecolor', 1, FCVAR_ARCHIVE + FCVAR_REPLICATED + FCVAR_SERVER_CAN_EXECUTE, 'Compute color from location brightness.') +CreateConVar('talkicon_showtextchat', 1, FCVAR_ARCHIVE + FCVAR_REPLICATED + FCVAR_SERVER_CAN_EXECUTE, 'Show icon on using text chat.') +CreateConVar('talkicon_ignoreteamchat', 1, FCVAR_ARCHIVE + FCVAR_REPLICATED + FCVAR_SERVER_CAN_EXECUTE, 'Disable over-head icon on using team chat.') + +local rangeStyle = { + -- r, g, b, size + [2250000] = {127, 127, 255, 8}, + [150000] = {255, 178, 102, 4}, + [500000] = {255, 102, 102, 6}, + [10000] = {130, 130, 130, 3}, +} + +local textColors = {} +for cmds, col in pairs({ + [{'/it ', '//it ', '/me ', '/toit ', '/pit '}] = Color(255, 178, 102), + [{'/yell ', '/y ', '/yr ', '/yradio '}] = Color(255, 102, 102, 150), + [{'/whisper ', '/w ', '/wr', '/wradio '}] = Color(130, 130, 130), + [{'/ ', '// ', '/pm ', '/looc ', '/ooc ', '/lradio ', '/lr '}] = Color(43, 123, 167), +}) do + for _, cmd in ipairs(cmds) do + textColors[cmd] = col + end +end + +if (SERVER) then + + RunConsoleCommand('mp_show_voice_icons', '0') + + netstream.Hook('TalkIconColor', function(ply, col) + ply:SetNetVar('TalkIcon', col) + end) + +elseif (CLIENT) then + + hook.Add('ChatTextChanged', 'talkicon', function(txt) + local ply = LocalPlayer() + if not IsValid(ply) then return end + if txt == '' then netstream.Start('TalkIconColor', color_white) end + for cmd in pairs(textColors) do + if txt:StartWith(cmd) then + if cmd == LocalPlayer():GetNetVar('TalkIcon') then return end + netstream.Start('TalkIconColor', cmd) + end + end + end) + + local voice_mat = Material('octoteam/icons-glyph/sound3.png') + local text_mat = Material('octoteam/icons-glyph/bubble_typing.png') + + hook.Add('PostPlayerDraw', 'TalkIcon', function(ply) + if ply == LocalPlayer() and GetViewEntity() == LocalPlayer() then return end + if not ply:Alive() then return end + if not ply:IsSpeaking() and not ply:IsTyping() then return end + + local pos = ply:GetPos() + Vector(0, 0, ply:GetModelRadius() + 7) + + local attachment = ply:GetAttachment(ply:LookupAttachment('eyes')) + if attachment then + pos = ply:GetAttachment(ply:LookupAttachment('eyes')).Pos + Vector(0, 0, 7) + end + + + local color_var = 1 + local computed_color = render.ComputeLighting(ply:GetPos(), Vector(0, 0, 1)) + local max = math.max(computed_color.x, computed_color.y, computed_color.z) + color_var = math.Clamp(max * 1.11, 0, 1) + + if ply:IsSpeaking() then + if GetConVar('cl_dbg_voiceicon'):GetInt() == '1' then + local r, g, b, size = unpack(rangeStyle[ply:GetNetVar('TalkRange', 0)] or rangeStyle[150000]) + render.SetMaterial(voice_mat) + local size = size + ply:VoiceVolume() * 2 + render.DrawSprite(pos, size, size, Color(color_var * r, color_var * g, color_var * b, 150)) + end + else + local col = textColors[ply:GetNetVar('TalkIcon') or ''] or color_white + render.SetMaterial(text_mat) + render.DrawSprite(pos, 4, 4, col) + end + end) + + hook.Add('InitPostEntity', 'RemoveChatBubble', function() + hook.Remove('StartChat', 'StartChatIndicator') + hook.Remove('FinishChat', 'EndChatIndicator') + end) + +end diff --git a/garrysmod/addons/feature-winter/lua/autorun/dbg-winter.lua b/garrysmod/addons/feature-winter/lua/autorun/dbg-winter.lua new file mode 100644 index 0000000..d337acb --- /dev/null +++ b/garrysmod/addons/feature-winter/lua/autorun/dbg-winter.lua @@ -0,0 +1,12 @@ +octolib.server('dbg-winter/sv_winter') +octolib.client('dbg-winter/cl_winter') +octolib.client('dbg-winter/cl_editor') + +hook.Add('Think', 'dbg-winter.init', function() + hook.Remove('Think', 'dbg-winter.init') + + local mapFile = 'dbg-winter/map_' .. game.GetMap() + if file.Exists(mapFile .. '.lua', 'LUA') then + octolib.client(mapFile) + end +end) diff --git a/garrysmod/addons/feature-winter/lua/dbg-winter/cl_editor.lua b/garrysmod/addons/feature-winter/lua/dbg-winter/cl_editor.lua new file mode 100644 index 0000000..11e0697 --- /dev/null +++ b/garrysmod/addons/feature-winter/lua/dbg-winter/cl_editor.lua @@ -0,0 +1,218 @@ +local frame +local ply = LocalPlayer() +local selMat = 'NATURE/SNOWFLOOR001A' +local selColor = Vector(0.5, 0.5, 0.5) + +local backup = {} + +local function toggleOverride(matStr) + + local mat = Material(matStr) + local orig = backup[matStr] + if orig then + -- material is snowed, remove it + if orig.t1 then mat:SetTexture('$basetexture', orig.t1) end + if orig.t2 then mat:SetTexture('$basetexture2', orig.t2) end + if orig.col then mat:SetVector('$color', orig.col) end + backup[matStr] = nil + else + -- raw material, make it snow + local orig = { + t1 = mat:GetTexture('$basetexture'), + t2 = mat:GetTexture('$basetexture2'), + col = mat:GetVector('$color'), + } + + if not orig.t1 or orig.t1:GetName() == 'error' then orig.t1 = nil end + if not orig.t2 or orig.t2:GetName() == 'error' then orig.t2 = nil end + + if orig.t1 then mat:SetTexture('$basetexture', selMat) end + if orig.t2 then mat:SetTexture('$basetexture2', selMat) end + mat:SetVector('$color', selColor) + + backup[matStr] = orig + end + +end + +local function adjustDecal(matPath, alpha) + + local mat = Material(matPath) + if not mat then return octolib.notify.show('Не удалось найти материал') end + + if alpha then + alpha = math.Round(alpha or 1, 2) + backup[matPath] = backup[matPath] or { al = mat:GetFloat('$alpha') or 1 } + mat:SetFloat('$alpha', alpha) + elseif backup[matPath] and backup[matPath].al then + mat:SetFloat('$alpha', backup[matPath].al) + backup[matPath] = nil + end + +end + +local function export() + return octolib.table.map(backup, function(data, matPath) + local mat = Material(matPath) + local out = {} + if data.t1 then out.t1 = mat:GetTexture('$basetexture'):GetName() end + if data.t2 then out.t2 = mat:GetTexture('$basetexture2'):GetName() end + if data.col then out.col = mat:GetVector('$color') end + if data.al then out.al = mat:GetFloat('$alpha') end + return out + end) +end + +local function reset() + for matPath, data in pairs(backup) do + if data.al then + adjustDecal(matPath) + else + toggleOverride(matPath) + end + end + + backup = {} +end + +function ImportTextureOverrides(exported) + reset() + + local oldSnowMat, oldSnowCol = selMat, selColor + for matPath, data in pairs(exported) do + if data.al then + adjustDecal(matPath, data.al) + else + selMat = data.t1 or data.t2 + selColor = data.col or Vector(0.5, 0.5, 0.5) + toggleOverride(matPath) + end + end + selMat, selColor = oldSnowMat, oldSnowCol +end + +hook.Add('octolib.configLoaded', 'dbg-winter', function() + if not CFG.dev then return end + + concommand.Add('texture_editor', function() + + if IsValid(frame) then + frame:Remove() + return + end + + ply = LocalPlayer() + + frame = vgui.Create 'DFrame' + frame:SetSize(200, 162) + frame:SetTitle('Редактор текстур') + frame:AlignLeft(5) + frame:AlignBottom(5) + frame:SetKeyboardInputEnabled(false) + + local function addButton(text, func) + local b = frame:Add 'DButton' + b:Dock(TOP) + b:DockMargin(0, 0, 0, 5) + b:SetTall(25) + b:SetText(text) + b.DoClick = func + + frame:SetTall(frame:GetTall() + 30) + frame:AlignBottom(5) + end + + octolib.button(frame, selMat, function(self) + Derma_StringRequest('Смена текстуры', 'Введи путь к текстуре, которая\nбудет использоваться для замены', selMat, function(s) + selMat = s + self:SetText(selMat) + end) + end) + + local cmSnow = octolib.colorPicker(frame, 'Цвет') + cmSnow:SetWangs(false) + cmSnow:SetTall(80) + cmSnow:SetVector(selColor) + function cmSnow:ValueChanged() + selColor = self:GetVector() + end + + addButton('Редактор текстур: OFF', function(self) + local active = hook.GetTable().PlayerBindPress.texture_editor == nil + self:SetText('Редактор текстур: ' .. (active and 'ON' or 'OFF')) + + if active then + hook.Add('PlayerBindPress', 'texture_editor', function(ply, bind, pressed) + if ply ~= LocalPlayer() or not pressed then return end + + if bind == '+attack' then + local tr = util.TraceLine({ + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * 10000, + filter = ply, + }) + + if not tr.Hit or not tr.HitTexture then return octolib.notify.show('warning', 'Не получилось найти точку попадания') end + if IsValid(tr.Entity) then + for _, v in ipairs(tr.Entity:GetMaterials()) do + toggleOverride(v) + end + return + end + + matStr = tr.HitTexture + if tr.HitTexture:find('**', 1, true) then return octolib.notify.show('warning', 'Этот тип материала не поддерживается, используй mat_crosshair') end + + toggleOverride(tr.HitTexture) + end + + if bind == '+attack2' then + Derma_StringRequest('Заменить текстуру', 'Введи путь к материалу, его можно получить через mat_crosshair', '', toggleOverride) + end + end) + + octolib.notify.show('ЛКМ - вкл/выкл перезапись материала, на который смотришь') + octolib.notify.show('ПКМ - ручной ввод пути материала') + else + hook.Remove('PlayerBindPress', 'texture_editor') + end + end) + + addButton('Видимые материалы', function() + RunConsoleCommand('mat_texture_list', '1') + end) + + addButton('Настроить декаль', function() + octolib.request.open({{ + name = 'Материал', + desc = 'Можно получить через mat_crosshair или mat_texture_list', + type = 'strShort', + }, { + name = 'Непрозрачность', + desc = 'Установи в максимум (1), чтобы вернуть к исходному значению', + type = 'numSlider', + val = 1, min = 0, max = 1, + }}, function(data) + if not data then return end + adjustDecal(data[1], data[2]) + end) + end) + + addButton('Экспорт', function() + SetClipboardText(pon.encode(export())) + octolib.notify.show('Данные скопировные в буфер обмена') + end) + + addButton('Импорт', octolib.fStringRequest('Импорт данных', 'Введи экспортированный код', '', function(s) + local data = pon.decode(s) + if not istable(data) then return octolib.notify.show('warning', 'Не получилось раскодировать данные') end + + ImportTextureOverrides(data) + end)) + + addButton('Сбросить все', reset) + + end) + +end) + diff --git a/garrysmod/addons/feature-winter/lua/dbg-winter/cl_winter.lua b/garrysmod/addons/feature-winter/lua/dbg-winter/cl_winter.lua new file mode 100644 index 0000000..56bc6fe --- /dev/null +++ b/garrysmod/addons/feature-winter/lua/dbg-winter/cl_winter.lua @@ -0,0 +1,16 @@ +hook.Add('PostDrawHUD', 'dbg-winter', function() + local st = LocalPlayer():GetNetVar('frost', 0) / 100 + if isHoldingCamera or st <= 0 then return end + + DrawColorModify({ + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = st * 0.2, + ['$pp_colour_addb'] = st * 0.5, + ['$pp_colour_brightness'] = 0, + ['$pp_colour_contrast'] = 1, + ['$pp_colour_colour'] = 1 - st * 0.5, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + }) +end) diff --git a/garrysmod/addons/feature-winter/lua/dbg-winter/map_rp_eastcoast_v4c.lua b/garrysmod/addons/feature-winter/lua/dbg-winter/map_rp_eastcoast_v4c.lua new file mode 100644 index 0000000..3b2df26 --- /dev/null +++ b/garrysmod/addons/feature-winter/lua/dbg-winter/map_rp_eastcoast_v4c.lua @@ -0,0 +1 @@ +ImportTextureOverrides(pon.decode [[['maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_-704_-768_80;['col;v0.82352942228317,0.82352942228317,0.82352942228317;'t1;'east-snow/stoneplat2_east;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1600_640_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'halloween2015/models/hay/hay_block_skin2_lod02;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)'nature/snowfloor001a;}'decals/prodconcrete04;['al;0.5;}'nature/gravelfloor004a;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'NATURE/GRAVELFLOOR004A;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'de_nuke/nukfloorc_detaila;[(14)0.5;}'METAL/METALSTAIR002A;[(4)v1,1,1;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-3936_2112_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)'east-snow/snow;}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_2752_-1344_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1164_-528_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_1920_-1888_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/ivy03;[(14)Y;}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_1728_288_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'halloween2015/models/hay/hay_block_skin1_lod01;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-928_384_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/de_chateau/cnstrcttarpb_-1600_256_320;[(4)v0.98431372642517,0.98431372642517,0.98431372642517;(5)'east-snow/naves;}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_2080_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_2080_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_3712_-2048_96;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-3072_-320_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-2582_1388_53;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'DE_TIDES/TIDES_GRASS_A;[(4)v0.72156864404678,0.72156864404678,0.72156864404678;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_1024_-128_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/blendconcretegravel001a_5376_-1345_32;['t2;(11)(5)(11)(4)v0.61176472902298,0.61176472902298,0.61176472902298;}'maps/rp_eastcoast_v4c/cs_assault/pavement001_2048_-2560_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'halloween2015/models/hay/hay_block_skin2;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_2752_-1344_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-2352_-1952_96;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_384_-560_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-2934_904_74;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/proddirta;[(4)v0.72156864404678,0.72156864404678,0.72156864404678;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_2048_-2560_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_-3840_1312_64;[(4)v1,1,1;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-576_-528_80;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_2080_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2440_-2924_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_4672_-896_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_5376_-1345_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1711_-1757_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1856_976_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-2720_1856_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/prodconcrete05;[(14)0.5;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-256_-496_32;[(4)v0.73725491762161,0.73725491762161,0.73725491762161;(5)(6)}'maps/rp_eastcoast_v4c/de_chateau/cnstrcttarpb_-1856_976_64;[(4)v0.98431372642517,0.98431372642517,0.98431372642517;(5)(42)}'maps/rp_eastcoast_v4c/concrete/concretefloor039a_-2240_832_-64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_2264_-416_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/stone/stonestair001a_-1600_256_64;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'decals/decalborealispuddle001a;[(14)Y;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2752_-1728_128;[(4)v0.73725491762161,0.73725491762161,0.73725491762161;(5)(6)}'NATURE/DIRTFLOOR012A;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1744_1040_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1104_1792_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_2752_-1344_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor039a_-1104_1792_64;[(4)v1,1,1;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1080_-1072_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_64_-560_96;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'concrete/concretefloor011a;[(4)v1,1,1;(5)(124)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_1920_1344_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'halloween2015/models/hay/hay_block;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2348_-2645_31;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-2240_832_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'CS_HAVANA/CURB04;[(4)v1,1,1;(5)'east-snow/curb_c;}'maps/rp_eastcoast_v4c/cs_assault/pavement001_2048_-3392_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1536_-624_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-3904_832_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'METAL/CITADEL_METALWALL074A;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'maps/rp_eastcoast_v4c/stone/stonestair001a_1280_96_64;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'NATURE/DIRTFLOOR004A;[(4)v0.73725491762161,0.73725491762161,0.73725491762161;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1600_-1152_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_2048_-3392_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-1600_640_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_3751_-3548_45;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/decalmetalgrate019a;[(14)0.75;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_3751_-3548_45;[(4)v0.73725491762161,0.73725491762161,0.73725491762161;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-64_64_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1024_-128_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_-2582_1388_53;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1408_1408_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/building_template/building_trainstation_template001a_1408_448_192;[(4)v0.72156864404678,0.72156864404678,0.72156864404678;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-1408_1408_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_-1408_1408_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor028a_-1104_1792_64;[(4)v0.73725491762161,0.73725491762161,0.73725491762161;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_-2582_1388_53;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'de_nuke/nukfloorc_detailb;[(14)0.5;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-64_704_32;[(4)v0.81960785388947,0.82352942228317,0.82352942228317;(5)(6)}'models/props_foliage/mall_trees_branches01;[(4)v1,1,1;(5)'east-snow/mall_trees_branches01;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_5376_-1729_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2496_192_320;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/building_template/building_trainstation_template001a_1280_96_64;[(4)v0.72156864404678,0.72156864404678,0.72156864404678;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-3936_2112_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-3840_1312_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'PROPS/RUBBERROOF002A;[(4)v0.74901962280273,0.74901962280273,0.74901962280273;(5)(11)}'NATURE/GRAVELFLOOR004A_C17;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1872_-1152_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_192_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2048_-3392_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/de_chateau/cnstrcttarpb_1024_-128_32;[(4)v0.6235294342041,0.6235294342041,0.6235294342041;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_-1600_256_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_1536_-624_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/trashdecal04a;[(14)0.25;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_64_-704_96;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_4992_-896_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-1592_-640_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'de_nuke/nukfloorc_detaild;[(14)0.5;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1600_256_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-704_-768_80;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'concrete/concretefloor009a;[(4)v0.65882354974747,0.65882354974747,0.65882354974747;(5)(11)}'NATURE/DIRTFLOOR006A;[(4)v0.74901962280273,0.74901962280273,0.74901962280273;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_5374_-831_22;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/stone/stonestair001a_64_-560_96;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-928_384_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_4864_-1408_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-1592_-640_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_192_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/trashdecal05a;[(14)0.5;}'maps/rp_eastcoast_v4c/cs_assault/pavement001_1680_-864_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'METAL/METALWALL014A;[(4)v0.74901962280273,0.74901962280273,0.74901962280273;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_1680_-864_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-3776_-128_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1070_-2837_86;[(4)v0.81960785388947,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor019a_1280_96_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/decalmetalgrate011a;[(14)0.75;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-2934_904_74;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_896_-528_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/decalmetalgrate010a;[(14)0.75;}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_1099_-1983_29;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor019a_896_-528_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_-1600_640_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/stone/stonestair001a_-704_-768_80;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'nature/blendgrassgravel001c;[(59)(11)(5)(11)(4)v0.74901962280273,0.74901962280273,0.74901962280273;}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-1600_256_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_-3936_2112_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'CONCRETE/CONCRETEFLOOR009A_CONSTRUCT;[(4)v1,1,1;(5)'east-snow/pavement001a;}'decals/decalkleinerpuddle001a;[(14)Y;}'decals/decaltiremark001a;[(14)0.5;}'maps/rp_eastcoast_v4c/cs_assault/pavement001_192_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'halloween2015/models/hay/hay_block_lod01;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'decals/ivy02;[(14)Y;}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_2048_-3392_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_-1080_-1072_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/stone/stonestair001a_-576_-528_80;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_4672_-896_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-1112_984_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-3936_2112_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_-1112_984_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_-3936_2112_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1112_984_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1631_-2839_160;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_192_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'halloween2015/models/hay/hay_block_skin1;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2080_-128_16;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1422_-2479_160;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_2048_-3392_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor019a_1408_448_192;[(4)v0.65882354974747,0.65882354974747,0.65882354974747;(5)(11)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-1856_976_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/debris_concrete001a;[(14)0.5;}'maps/rp_eastcoast_v4c/de_chateau/cnstrcttarpb_-1632_640_-144;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(42)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_64_-560_96;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_-1112_984_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-1600_-1152_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'CONCRETE/CONCRETESTAIR006A;[(4)v1,1,1;(5)'concrete/concretestair006a;}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-3840_1312_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_-928_384_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_4672_-896_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1920_1344_16;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-2934_904_74;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_551_-1701_160;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'BUILDING_TEMPLATE/BUILDING_TEMPLATE029A;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_1422_-2479_160;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_-1592_-640_32;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_-928_384_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-1600_640_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/decalmetalgrate022a;[(14)0.5;}'PROPS/TARPAPERROOF002A;[(4)v0.74901962280273,0.74901962280273,0.74901962280273;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1920_-1888_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/decalmetalgrate017a;[(14)0.5;}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_2048_-2560_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'models/props_c17/awning001d;[(4)v0.98431372642517,0.98431372642517,0.98431372642517;(5)'east-snow/awning001d;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-352_1280_64;[(4)v0.81960785388947,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor011a_c17_-1600_640_64;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001_1024_-128_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_2080_-128_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/trashdecal01a;[(14)0.5;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1592_-640_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_1024_-128_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_1024_-128_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2752_-1344_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_-1600_256_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_4672_-896_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'CONCRETE/CONCRETEFLOOR009A;[(4)v0.65882354974747,0.65882354974747,0.65882354974747;(5)(11)}'halloween2015/models/hay/hay_block_lod02;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'METAL/METALHULL013A;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/decalmetalgrate014a;[(14)0.75;}'decals/trashdecal02a;[(14)0.5;}'maps/rp_eastcoast_v4c/stone/stonestair001a_3968_-448_192;[(4)v1,1,1;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_3712_-2048_96;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/metal/citadel_tilefloor016a_896_64_256;[(4)v0.6235294342041,0.6235294342041,0.6235294342041;(5)(11)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_-1152_-1432_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/de_chateau/cnstrcttarpb_-1112_984_64;[(4)v0.98431372642517,0.98431372642517,0.98431372642517;(5)(42)}'decals/decalmetalmanhole004a;[(14)0.75;}'maps/rp_eastcoast_v4c/concrete/blendconcretegravel001a_5374_-831_22;[(59)(11)(5)(11)(4)v0.61176472902298,0.61176472902298,0.61176472902298;}'de_nuke/nukfloorc_detailc;[(14)0.5;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1680_-864_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_3536_-664_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'decals/decalmetalgrate001a;[(14)0.5;}'decals/prodconcrete01;[(14)0.5;}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_1728_288_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-2582_1388_53;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_2752_-1344_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/rubble01a;[(14)0.5;}'decals/decalposter014b;[(14)Y;}'decals/decalgraffiti059a;[(14)Y;}'maps/rp_eastcoast_v4c/cs_assault/rightcurb001_2264_-416_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_-3840_1312_64;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'maps/rp_eastcoast_v4c/concrete/concretefloor005a_2264_-416_32;[(4)v0.82352942228317,0.82352942228317,0.82352942228317;(5)(6)}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_4672_-896_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/trashdecal03a;[(14)0.75;}'decals/prodconcrete03;[(14)0.5;}'pretmodels/material__88;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'maps/rp_eastcoast_v4c/de_chateau/cnstrcttarpb_-1600_640_64;[(4)v0.98431372642517,0.98431372642517,0.98431372642517;(5)(42)}'models/props_foliage/branch_city;[(4)v1,1,1;(5)'east-snow/branch_city;}'maps/rp_eastcoast_v4c/cs_assault/pavement001a_1680_-864_32;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'metal/metalgrate008a;[(4)v1,1,1;(5)'east-snow/metalgrate008a;}(337)[(4)v1,1,1;(5)(337)}'maps/rp_eastcoast_v4c/cs_assault/leftcurb001_2048_-2560_16;[(4)v0.68627452850342,0.68627452850342,0.68627452850342;(5)(25)}'decals/decalmetalgrate007a;[(14)0.75;}'halloween2015/models/hay/hay_block_skin1_lod02;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}'"maps/rp_eastcoast_v4c/concrete/blendconcretegravel001a_5376_-1345_32;[(4)v0.72156864404678,0.72156864404678,0.72156864404678;(5)(11)}'halloween2015/models/hay/hay_block_skin2_lod01;[(4)v0.61176472902298,0.61176472902298,0.61176472902298;(5)(11)}}]]) \ No newline at end of file diff --git a/garrysmod/addons/feature-winter/lua/dbg-winter/map_rp_truenorth_v1a.lua b/garrysmod/addons/feature-winter/lua/dbg-winter/map_rp_truenorth_v1a.lua new file mode 100644 index 0000000..1364ab9 --- /dev/null +++ b/garrysmod/addons/feature-winter/lua/dbg-winter/map_rp_truenorth_v1a.lua @@ -0,0 +1 @@ +--ImportTextureOverrides(pon.decode [[['BUILDING_TEMPLATE/ROOF_TEMPLATE001A;['col;v1,1,1;'t1;'nature/snowfloor001a;}'blend/blend_moss_002_oldconcrete_104;['t2;(6)(5)(6)(4)v1,1,1;}'models/props_foliage/reed_001;[(4)v0.64705884456635,0.64705884456635,0.64705884456635;(5)'null;}'models/props_foliage/spruce_001_branches;[(4)v1,1,1;(5)'truenorth-snow/pine_003_branches;}'maps/rp_truenorth_v1a/metal/metalroof006a_6931_2611_81;[(4)v1,1,1;(5)(6)}'BRICK/BRICK_FLOOR_01;[(4)v1,1,1;(5)'truenorth-snow/brick_floor_03;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_-16_11359_209;[(4)v1,1,1;(5)'truenorth-snow/pavement001a;}'models/props_foliage/ah_foliage_sheet001;[(4)v0.5,0.5,0.5;(5)'truenorth-snow/ah_foliage_sheet001;}'models/props_foliage/urban_trees_branches03;[(4)v1,1,1;(5)'truenorth-snow/urban_trees_branches03_mip0;}'nature/blendgraveldirt01;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'REALWORLDTEXTURES2/ROOF/WOOD_ROOF_02;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_15_13684_209;[(4)v1,1,1;(5)(23)}'CONCRETE/CONCRETEFLOOR010A;[(4)v0.5,0.5,0.5;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_2966_10321_201;[(4)v1,1,1;(5)(23)}'ocrp/urban/road04c;[(4)v1,1,1;(5)'truenorth-snow/road03-1;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_12851_10017_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_8073_3242_73;[(4)v1,1,1;(5)(23)}'OCRP/URBAN/ROAD04B;[(4)v1,1,1;(5)'truenorth-snow/road03-2;}'DE_NUKE/NUKE_CEILING_FACILITY_01;[(4)v1,1,1;(5)(6)}'models/props_foliage/pine_003_branches;[(4)v1,1,1;(5)'truenorth-snow/pine_001_branches;}'REALWORLDTEXTURES/NEWER/1/ROOF_1_04;[(4)v1,1,1;(5)(6)}'statua/nature/rockfordgrass2_noprop;[(9)(6)(5)(6)(4)v1,1,1;}'BUILDINGS/ASPH_SHINGLES02_A;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/metal/metalroof006a_2644_4566_73;[(4)v1,1,1;(5)(6)}'BUILDINGS/SHINGLES_EXT_02;[(4)v1,1,1;(5)(6)}'models/props_foliage/hedgerow_01;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)'truenorth-snow/hedgerow_01;}'models/props_foliage/urban_trees_branches03_medium;[(4)v1,1,1;(5)(29)}'nature/cliff01a;[(4)v0.5,0.5,0.5;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_779_13858_201;[(4)v1,1,1;(5)(23)}'nature/blendgrassdirt03;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_3594_2974_81;[(4)v1,1,1;(5)(23)}'DE_TRAIN/TRAIN_GRAVEL_FLOOR_01;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_5071_4204_80;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_5032_3368_145;[(4)v1,1,1;(5)(23)}'CONCRETE/CONCRETE_044;[(4)v1,1,1;(5)'truenorth-snow/sidewalk2;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_7026_2451_281;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/statua/nature/blendgrassdirt01_wvt_patch;[(4)v0.74901962280273,0.73725491762161,0.73725491762161;(5)(6)}'statua/nature/blendgrasssand01;[(9)(6)(5)(6)(4)v0.64705884456635,0.64705884456635,0.64705884456635;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_6846_12535_81;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/metal/metalroof006a_684_3092_73;[(4)v1,1,1;(5)(6)}'CONCRETE/CONCRETEWALL036A;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(6)}'CONCRETE/CONCRETEFLOOR015A;[(4)v1,1,1;(5)(23)}'ocrp/urban/road04a;[(4)v1,1,1;(5)(42)}'models/props_foliage/pine_001_branches;[(4)v1,1,1;(5)(54)}'statua/nature/furcard1;[(4)v0.5,0.5,0.5;(5)'truenorth-snow/furcard1;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_6822_11578_73;[(4)v1,1,1;(5)(23)}'statua/nature/farmblend2;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_-4986_12649_209;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_6167_9893_81;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_2644_4566_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/metal/metalroof006a_3767_2445_80;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_7354_13859_73;[(4)v1,1,1;(5)(23)}'statua/nature/farmblend3;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'models/cliffs/rocks_vegetation;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)(26)}'WOOD/MILFLR002;[(4)v0.54901963472366,0.54901963472366,0.54901963472366;(5)(6)}'OCRP/URBAN/ROAD03-1;[(4)v1,1,1;(5)(42)}'NATURE/FOREST_DIRT_02;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_5425_8984_81;[(4)v1,1,1;(5)(23)}'BRICK/BRICK_FLOOR_03;[(4)v1,1,1;(5)(20)}'statua/nature/blendgrassdirt01;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'models/props_foliage/ash_smallbranch;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)'truenorth-snow/ash_smallbranch;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_7232_10003_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_12008_11510_73;[(4)v1,1,1;(5)(23)}'models/props_foliage/poplar_bigbranch;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)'truenorth-snow/poplar_bigbranch;}'OCRP/URBAN/SIDEWALK2;[(4)v1,1,1;(5)(86)}'detail/detailsprites_ep2;['al;X0;}'models/props_foliage/ash_bigbranch;[(4)v0.85882353782654,0.85882353782654,0.85882353782654;(5)'truenorth-snow/ash_bigbranch;}'BUILDINGS/ASPH_SHINGLES01;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_4567_7385_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_15154_10885_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_12902_13965_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/metal/metalroof006a_6846_12535_81;[(4)v1,1,1;(5)(6)}'statua/nature/blendforest_01;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'nature/blendrockmud01a;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'maps/rp_truenorth_v1a/metal/metalroof006a_3594_2974_81;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/metal/metalroof006a_3367_1955_81;[(4)v1,1,1;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_15185_12516_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/metal/metalroof006a_1919_1851_81;[(4)v1,1,1;(5)(6)}'TILE/TILE_FLOOR_008;[(4)v1,1,1;(5)'truenorth-snow/tile_floor_008;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_6419_2053_81;[(4)v1,1,1;(5)(23)}'blend/blend_asphalt_003_dirt_001;[(9)(6)(5)(6)(4)v1,1,1;}'models/props/de_train/railroadtracklong;[(4)v1,1,1;(5)'truenorth-snow/railroadtracklong;}'AJACKS/AJACKS_GRASS01;[(4)v0.74901962280273,0.73725491762161,0.73725491762161;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_9593_2192_73;[(4)v1,1,1;(5)(23)}'models/props_foliage/juniper_002;[(4)v1,1,1;(5)'truenorth-snow/juniper_002_skin2;}'statua/nature/rockfordgrass1;[(9)(6)(5)(6)(4)v0.74901962280273,0.73725491762161,0.73725491762161;}'METAL/METALWALL016A;[(4)v0.5,0.5,0.5;(5)(6)}'nature/cliff04a;[(4)v0.64705884456635,0.64705884456635,0.64705884456635;(5)(6)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_-3672_7711_209;[(4)v1,1,1;(5)(23)}'models/props_foliage/mall_trees_branches01;[(4)v1,1,1;(5)'truenorth-snow/mall_trees_branches01;}'CONCRETE/CONCRETEFLOOR016A;[(4)v1,1,1;(5)'truenorth-snow/concretefloor016a;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_-3651_9378_209;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_-4355_7829_201;[(4)v1,1,1;(5)(23)}'DE_NUKE/NUKMETWALLAB;[(4)v0.5,0.5,0.5;(5)(6)}'nature/cliff03b;[(4)v0.5,0.5,0.5;(5)(6)}'BUILDINGS/ROOF07;[(4)v1,1,1;(5)(6)}'nature/blendpavedirt01_nodetail;[(9)(6)(5)(6)(4)v1,1,1;}'maps/rp_truenorth_v1a/cs_assault/pavement001a_-5046_10207_209;[(4)v1,1,1;(5)(23)}'statua/nature/blendforest_02;[(9)(6)(5)(225)(4)v0.75,0.75,0.75;}'OCRP/URBAN/ROAD03-3;[(4)v1,1,1;(5)(49)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_2294_11558_201;[(4)v1,1,1;(5)(23)}'OCRP/URBAN/ROAD03-2;[(4)v0.89803922176361,0.89803922176361,0.89803922176361;(5)(49)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_12869_-1587_73;[(4)v0.7843137383461,0.77647060155869,0.77647060155869;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_9119_13649_73;[(4)v1,1,1;(5)(23)}'maps/rp_truenorth_v1a/cs_assault/pavement001a_12371_15110_73;[(4)v1,1,1;(5)(23)}}]]) diff --git a/garrysmod/addons/feature-winter/lua/dbg-winter/sv_winter.lua b/garrysmod/addons/feature-winter/lua/dbg-winter/sv_winter.lua new file mode 100644 index 0000000..9b11f25 --- /dev/null +++ b/garrysmod/addons/feature-winter/lua/dbg-winter/sv_winter.lua @@ -0,0 +1,76 @@ +local function UnderSky2(pos, ply) + local r = util.QuickTrace(pos, Vector(0,0,262144), ply) + return r.HitSky and r.HitPos +end + +local function isWarm(ply) + if StormFox2.Temperature.Get() > 2 then return true end + if ply.warmClothes then return true end + if ply:Team() == TEAM_ADMIN then return true end + if hook.Run('dbg-winter.isWarm', ply) == true then return true end + local veh = ply:GetVehicle() + if IsValid(veh) and veh.fphysSeat and veh:GetParent():EngineActive() then return true end + + return not UnderSky2(ply:GetPos(), ply) +end + +timer.Create('dbg-winter.frost', 10, 0, function() + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + if ply:IsAFK() or ply:IsGhost() then return end + if isWarm(ply) then + ply:SetNetVar('frost', math.max(ply:GetNetVar('frost', 0) - 3, 0)) + else + local new = math.min(ply:GetNetVar('frost', 0) + 1, 100) + ply:SetNetVar('frost', new) + if new == 50 then ply:Notify(L.frost_hint) end + if new == 99 then ply:Notify('warning', L.frost_hint2) end + if new >= 50 and math.random(3) == 1 then ply:EmitSound('ambient/voices/cough'..math.random(1,4)..'.wav', 60) end + if new >= 100 then + local h = ply:Health() + if h > 20 then ply:SetHealth(h - 2) end + end + end + end) +end) + +hook.Add('PlayerSpawn', 'dbg-winter', function(ply) + ply.warmClothes = nil + ply:SetNetVar('frost', 0) +end) + +hook.Add('KeyRelease', 'dbg-winter.snowball', function(ply, key) + if key ~= IN_USE then return end + + local hand = ply:GetInventory():GetContainer('_hand') + if not hand then return end + + local tr = octolib.use.getTrace(ply) + if IsValid(tr.Entity) or tr.MatType ~= MAT_SNOW then return end + + ply:DelayedAction('snowball', 'Лепка снежка', { + time = 2.5, + check = function() + local tr = octolib.use.getTrace(ply) + return not IsValid(tr.Entity) and tr.MatType == MAT_SNOW and ply:GetInventory():GetContainer('_hand') ~= nil + end, + succ = function() + hand:AddItem('throwable', { + name = 'Снежок', + usesLeft = 1, + gc = 'ent_dbg_snowball', + desc = 'Брр... Холодный!', + icon = 'octoteam/icons/snowball.png', + model = 'models/weapons/w_snowball_thrown.mdl', + expire = os.time() + 20, + }) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_DROP + math.random(0, 1)) + ply:EmitSound('player/footsteps/snow' .. math.random(1, 6) .. '.wav') + end, + }) +end) diff --git a/garrysmod/addons/feature-wire/lua/autorun/wire_load.lua b/garrysmod/addons/feature-wire/lua/autorun/wire_load.lua new file mode 100644 index 0000000..904fdfa --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/autorun/wire_load.lua @@ -0,0 +1,138 @@ +--[[ +Copyright 2013 Wiremod Developers +https://github.com/wiremod/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]]-- + +if VERSION < 140403 and VERSION > 5 then + -- VERSION > 5 check added June 2013, to address issues regarding the Steampipe update sometimes setting VERSION to 1. + ErrorNoHalt("WireMod: This branch of wiremod only supports Gmod13+.\n") + return +end + +if SERVER then + -- this file + AddCSLuaFile("autorun/wire_load.lua") + + -- shared includes + AddCSLuaFile("wire/wire_paths.lua") + AddCSLuaFile("wire/wireshared.lua") + AddCSLuaFile("wire/wiregates.lua") + AddCSLuaFile("wire/wiremonitors.lua") + AddCSLuaFile("wire/gpulib.lua") + AddCSLuaFile("wire/cpulib.lua") + AddCSLuaFile("wire/timedpairs.lua") + AddCSLuaFile("wire/default_data_decompressor.lua") + AddCSLuaFile("wire/flir.lua") + AddCSLuaFile("wire/von.lua") + + -- client includes + AddCSLuaFile("wire/client/cl_wirelib.lua") + AddCSLuaFile("wire/client/cl_modelplug.lua") + AddCSLuaFile("wire/client/cl_wire_map_interface.lua") + AddCSLuaFile("wire/client/wiredermaexts.lua") + AddCSLuaFile("wire/client/wiremenus.lua") + AddCSLuaFile("wire/client/wire_expression2_browser.lua") + AddCSLuaFile("wire/client/wire_filebrowser.lua") + AddCSLuaFile("wire/client/wire_listeditor.lua") + AddCSLuaFile("wire/client/wire_soundpropertylist.lua") + AddCSLuaFile("wire/client/e2helper.lua") + AddCSLuaFile("wire/client/e2descriptions.lua") + AddCSLuaFile("wire/client/e2_extension_menu.lua") + AddCSLuaFile("wire/client/gmod_tool_auto.lua") + AddCSLuaFile("wire/client/sound_browser.lua") + AddCSLuaFile("wire/client/thrusterlib.lua") + AddCSLuaFile("wire/client/rendertarget_fix.lua") + AddCSLuaFile("wire/client/customspawnmenu.lua") + + -- text editor + AddCSLuaFile("wire/client/text_editor/texteditor.lua") + AddCSLuaFile("wire/client/text_editor/wire_expression2_editor.lua") + AddCSLuaFile("wire/client/text_editor/modes/e2.lua") + AddCSLuaFile("wire/client/text_editor/modes/zcpu.lua") + + -- HL-ZASM + AddCSLuaFile("wire/client/hlzasm/hc_compiler.lua") + AddCSLuaFile("wire/client/hlzasm/hc_opcodes.lua") + AddCSLuaFile("wire/client/hlzasm/hc_expression.lua") + AddCSLuaFile("wire/client/hlzasm/hc_preprocess.lua") + AddCSLuaFile("wire/client/hlzasm/hc_syntax.lua") + AddCSLuaFile("wire/client/hlzasm/hc_codetree.lua") + AddCSLuaFile("wire/client/hlzasm/hc_optimize.lua") + AddCSLuaFile("wire/client/hlzasm/hc_output.lua") + AddCSLuaFile("wire/client/hlzasm/hc_tokenizer.lua") + + -- ZVM + AddCSLuaFile("wire/zvm/zvm_core.lua") + AddCSLuaFile("wire/zvm/zvm_features.lua") + AddCSLuaFile("wire/zvm/zvm_opcodes.lua") + AddCSLuaFile("wire/zvm/zvm_data.lua") + + if CreateConVar("wire_force_workshop", 1, {FCVAR_ARCHIVE}, "Should Wire force all clients to download the Workshop edition of Wire, for models? (requires restart to disable)"):GetBool() then + resource.AddWorkshop("160250458") + end +end + +-- shared includes +include("wire/wireshared.lua") +include("wire/wire_paths.lua") +include("wire/wiregates.lua") +include("wire/wiremonitors.lua") +include("wire/gpulib.lua") +include("wire/cpulib.lua") +include("wire/timedpairs.lua") +include("wire/default_data_decompressor.lua") +include("wire/flir.lua") +include("wire/von.lua") + +-- server includes +if SERVER then + include("wire/server/wirelib.lua") + include("wire/server/modelplug.lua") + include("wire/server/radiolib.lua") + include("wire/server/debuggerlib.lua") +end + +-- client includes +if CLIENT then + include("wire/client/cl_wirelib.lua") + include("wire/client/cl_modelplug.lua") + include("wire/client/cl_wire_map_interface.lua") + include("wire/client/wiredermaexts.lua") + include("wire/client/wiremenus.lua") + include("wire/client/text_editor/texteditor.lua") + include("wire/client/wire_expression2_browser.lua") + include("wire/client/text_editor/wire_expression2_editor.lua") + include("wire/client/wire_filebrowser.lua") + include("wire/client/wire_listeditor.lua") + include("wire/client/wire_soundpropertylist.lua") + include("wire/client/e2helper.lua") + include("wire/client/e2descriptions.lua") + include("wire/client/e2_extension_menu.lua") + include("wire/client/gmod_tool_auto.lua") + include("wire/client/sound_browser.lua") + include("wire/client/thrusterlib.lua") + include("wire/client/rendertarget_fix.lua") + include("wire/client/hlzasm/hc_compiler.lua") + include("wire/client/customspawnmenu.lua") + +end + +-- Load UWSVN, done here so its definitely after Wire is loaded. +if file.Find("wire/uwsvn_load.lua","LUA")[1] then + if SERVER then AddCSLuaFile( "wire/uwsvn_load.lua" ) end + include("wire/uwsvn_load.lua") +end + +if SERVER then print("Wiremod Version '"..WireLib.GetVersion().."' loaded") end diff --git a/garrysmod/addons/feature-wire/lua/effects/jump_in.lua b/garrysmod/addons/feature-wire/lua/effects/jump_in.lua new file mode 100644 index 0000000..605967d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/effects/jump_in.lua @@ -0,0 +1,60 @@ +EFFECT.Mat = Material( "effects/select_ring" ) + +/*--------------------------------------------------------- + Initializes the effect. The data is a table of data + which was passed from the server. +---------------------------------------------------------*/ +function EFFECT:Init( data ) + + local TargetEntity = data:GetEntity() + + if ( !TargetEntity || !TargetEntity:IsValid() ) then return end + + //local vOffset = TargetEntity:GetPos() + + local Low, High = TargetEntity:WorldSpaceAABB() + local Center = data:GetOrigin() //High - (( High - Low ) * 0.5) + + local NumParticles = TargetEntity:BoundingRadius() + NumParticles = NumParticles * 2 + + NumParticles = math.Clamp( NumParticles, 10, 500 ) + + local emitter = ParticleEmitter( Center ) + + for i=0, NumParticles do + + local vPos = Vector( math.Rand(Low.x,High.x), math.Rand(Low.y,High.y), math.Rand(Low.z,High.z) ) + local vVel = (vPos - Center) * 6 + local particle = emitter:Add( "effects/spark", Center ) + if (particle) then + particle:SetVelocity( vVel ) + particle:SetLifeTime( 0 ) + particle:SetDieTime( math.Rand( 0.1, 0.4 ) ) + particle:SetStartAlpha( 0 ) + particle:SetEndAlpha( math.Rand( 200, 255 ) ) + particle:SetStartSize( 0 ) + particle:SetEndSize( 20 ) + particle:SetRoll( math.Rand(0, 360) ) + particle:SetRollDelta( 0 ) + end + + end + + emitter:Finish() + +end + + +/*--------------------------------------------------------- + THINK +---------------------------------------------------------*/ +function EFFECT:Think( ) + return false +end + +/*--------------------------------------------------------- + Draw the effect +---------------------------------------------------------*/ +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-wire/lua/effects/jump_out.lua b/garrysmod/addons/feature-wire/lua/effects/jump_out.lua new file mode 100644 index 0000000..a1f06cd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/effects/jump_out.lua @@ -0,0 +1,62 @@ + + +EFFECT.Mat = Material( "effects/select_ring" ) + +/*--------------------------------------------------------- + Initializes the effect. The data is a table of data + which was passed from the server. +---------------------------------------------------------*/ +function EFFECT:Init( data ) + + local TargetEntity = data:GetEntity() + + if ( !TargetEntity || !TargetEntity:IsValid() ) then return end + + local vOffset = TargetEntity:GetPos() + + local Low, High = TargetEntity:WorldSpaceAABB() + local Center = data:GetOrigin() //High - (( High - Low ) * 0.5) + + local NumParticles = TargetEntity:BoundingRadius() + NumParticles = NumParticles * 2 + + NumParticles = math.Clamp( NumParticles, 10, 500 ) + + local emitter = ParticleEmitter( vOffset ) + + for i=0, NumParticles do + + local vPos = Vector( math.Rand(Low.x,High.x), math.Rand(Low.y,High.y), math.Rand(Low.z,High.z) ) + local vVel = (Center - vPos) * 6 + local particle = emitter:Add( "effects/spark", vPos ) + if (particle) then + particle:SetVelocity( vVel ) + particle:SetLifeTime( 0 ) + particle:SetDieTime( math.Rand( 0.1, 0.3 ) ) + particle:SetStartAlpha( math.Rand( 200, 255 ) ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 20 ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand(0, 360) ) + particle:SetRollDelta( 0 ) + end + + end + + emitter:Finish() + +end + + +/*--------------------------------------------------------- + THINK +---------------------------------------------------------*/ +function EFFECT:Think( ) + return false +end + +/*--------------------------------------------------------- + Draw the effect +---------------------------------------------------------*/ +function EFFECT:Render() +end diff --git a/garrysmod/addons/feature-wire/lua/effects/thruster_ring.lua b/garrysmod/addons/feature-wire/lua/effects/thruster_ring.lua new file mode 100644 index 0000000..1020f2a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/effects/thruster_ring.lua @@ -0,0 +1,48 @@ +EFFECT.Mat = Material( "effects/select_ring" ) + +function EFFECT:Init( data ) + + local size = 16 + self:SetCollisionBounds( Vector( -size,-size,-size ), Vector( size,size,size ) ) + + local Pos = data:GetOrigin() + data:GetNormal() * 2 + + self:SetPos( Pos ) + self:SetAngles( data:GetNormal():Angle() + Angle( 0.01, 0.01, 0.01 ) ) + + self.Pos = data:GetOrigin() + self.Normal = data:GetNormal() + + self.Speed = 2 + self.Size = 16 + self.Alpha = 255 + self.GrowthRate = data:GetMagnitude() +end + +function EFFECT:Think( ) + + local speed = FrameTime() * self.Speed + + self.Alpha = self.Alpha - 250.0 * speed + self.Size = self.Size + (255 - self.Alpha) * self.GrowthRate + self:SetPos( self:GetPos() + self.Normal * speed * 128 ) + + if (self.Alpha < 0 ) then return false end + if (self.Size < 0) then return false end + return true + +end + +function EFFECT:Render( ) + + if (self.Alpha < 1 ) then return end + + render.SetMaterial( self.Mat ) + + render.DrawQuadEasy( self:GetPos(), + self:GetAngles():Forward(), + self.Size, self.Size, + Color( math.Rand( 10, 100), math.Rand( 100, 220), math.Rand( 240, 255), self.Alpha ) + ) + +end diff --git a/garrysmod/addons/feature-wire/lua/effects/thruster_sperm.lua b/garrysmod/addons/feature-wire/lua/effects/thruster_sperm.lua new file mode 100644 index 0000000..1837307 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/effects/thruster_sperm.lua @@ -0,0 +1,66 @@ + +EFFECT.Mat = Material( "thrusteraddon/sperm" ) + +/*--------------------------------------------------------- + Initializes the effect. The data is a table of data + which was passed from the server. +---------------------------------------------------------*/ +function EFFECT:Init( data ) + + local size = 16 + self:SetCollisionBounds( Vector( -size,-size,-size ), Vector( size,size,size ) ) + + local Pos = data:GetOrigin() + data:GetNormal() * 2 + + self:SetPos( Pos ) + self:SetAngles( data:GetNormal():Angle() + Angle( 0.01, 0.01, 0.01 ) ) + + self.Pos = data:GetOrigin() + self.Normal = data:GetNormal() + + self.Speed = 2 + self.Size = 16 + self.Alpha = 255 + +end + + +/*--------------------------------------------------------- + THINK +---------------------------------------------------------*/ +function EFFECT:Think( ) + + local speed = FrameTime() * self.Speed + + //if (self.Speed > 100) then self.Speed = self.Speed - 1000 * speed end + + //self.Size = self.Size + speed * self.Speed + self.Alpha = self.Alpha - 250.0 * speed + + self:SetPos( self:GetPos() + self.Normal * speed * 128 ) + + if (self.Alpha < 0 ) then return false end + if (self.Size < 0) then return false end + return true + +end + +/*--------------------------------------------------------- + Draw the effect +---------------------------------------------------------*/ +function EFFECT:Render( ) + + if (self.Alpha < 1 ) then return end + + render.SetMaterial( self.Mat ) + local ang = self:GetAngles():Forward() + ang.yaw = ang.yaw + 90 + ang.roll = ang.roll + 90 + + render.DrawQuadEasy( self:GetPos(), + ang, + self.Size, self.Size, + Color( 255,255,255, self.Alpha ) + ) + +end diff --git a/garrysmod/addons/feature-wire/lua/entities/base_wire_entity.lua b/garrysmod/addons/feature-wire/lua/entities/base_wire_entity.lua new file mode 100644 index 0000000..d378045 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/base_wire_entity.lua @@ -0,0 +1,445 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_gmodentity" ) +ENT.Type = "anim" +ENT.PrintName = "Wire Unnamed Ent" +ENT.Purpose = "Base for all wired SEnts" +ENT.RenderGroup = RENDERGROUP_OPAQUE +ENT.Spawnable = false +ENT.AdminOnly = false + +ENT.IsWire = true + +if CLIENT then + local wire_drawoutline = CreateClientConVar("wire_drawoutline", 1, true, false) + + function ENT:Initialize() + self.NextRBUpdate = CurTime() + 0.25 + end + + function ENT:Draw() + self:DoNormalDraw() + Wire_Render(self) + if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then + -- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean + Wire_DrawTracerBeam( self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false ) + end + end + + local WorldTip = { dietime = 0 } + function ENT:AddWorldTip( txt ) + WorldTip.dietime = SysTime() + RealFrameTime() * 4 + WorldTip.ent = self + end + + local edgesize = 18 + + -- makes sure the overlay doesn't go out of the screen & provides several useful sizes and positions for the DrawBody function + function ENT:GetWorldTipPositions( w, h, w_body, h_body, w_footer, h_footer ) + local pos = LocalPlayer():GetEyeTrace().HitPos + local spos = LocalPlayer():GetShootPos() + if pos == spos then -- if the position is right in your face, get a better position + pos = spos + LocalPlayer():GetAimVector() * 5 + end + pos = pos:ToScreen() + + pos.x = math.Round(pos.x) + pos.y = math.Round(pos.y) + + w = math.min( w, ScrW() - 64 ) + h = math.min( h, ScrH() - 64 ) + + local maxx = pos.x - 32 + local maxy = pos.y - 32 + + local minx = maxx - w + local miny = maxy - h + + if minx < 32 then + maxx = 32 + w + minx = 32 + end + + if miny < 32 then + maxy = 32 + h + miny = 32 + end + + local centerx = (maxx+minx)/2 + local centery = (maxy+miny)/2 + + return { min = {x = minx,y = miny}, + max = {x = maxx,y = maxy}, + center = {x = centerx, y = centery}, + size = {w = w, h = h}, + bodysize = {w = w_body, h = h_body }, + footersize = {w = w_footer, h = h_footer}, + edgesize = edgesize + } + end + + -- This is overridable by other wire entities which want to customize the overlay, but generally you shouldn't override it + function ENT:DrawWorldTipOutline( pos ) + draw.NoTexture() + surface.SetDrawColor(Color(25,25,25,200)) + + local poly = { + {x = pos.min.x + edgesize, y = pos.min.y, u = 0, v = 0 }, + {x = pos.max.x, y = pos.min.y, u = 0, v = 0 }, + {x = pos.max.x, y = pos.max.y - edgesize + 0.5, u = 0, v = 0 }, + {x = pos.max.x - edgesize + 0.5, y = pos.max.y, u = 0, v = 0 }, + {x = pos.min.x, y = pos.max.y, u = 0, v = 0 }, + {x = pos.min.x, y = pos.min.y + edgesize, u = 0, v = 0 }, + } + + render.CullMode(MATERIAL_CULLMODE_CCW) + surface.DrawPoly( poly ) + + surface.SetDrawColor(Color(0,0,0,255)) + + for i=1,#poly-1 do + surface.DrawLine( poly[i].x, poly[i].y, poly[i+1].x, poly[i+1].y ) + end + surface.DrawLine( poly[#poly].x, poly[#poly].y, poly[1].x, poly[1].y ) + end + + local function getWireName( ent ) + local name = ent:GetNWString("WireName") + if not name or name == "" then return ent.PrintName else return name end + end + + -- This is overridable by other wire entities which want to customize the overlay + function ENT:GetWorldTipBodySize() + local txt = self:GetOverlayData().txt + if txt == nil or txt == "" then return 0,0 end + return surface.GetTextSize( txt ) + end + + -- This is overridable by other wire entities which want to customize the overlay + function ENT:DrawWorldTipBody( pos ) + local data = self:GetOverlayData() + draw.DrawText( data.txt, "GModWorldtip", pos.center.x, pos.min.y + edgesize/2, Color(255,255,255,255), TEXT_ALIGN_CENTER ) + end + + -- This is overridable by other wire entities which want to customize the overlay + function ENT:DrawWorldTip() + local data = self:GetOverlayData() + if not data then return end + + surface.SetFont( "GModWorldtip" ) + + local txt = data.txt + local class = getWireName( self ) .. " [" .. self:EntIndex() .. "]" + local name = "(" .. self:GetPlayerName() .. ")" + + local w_body, h_body = self:GetWorldTipBodySize() + local w_class, h_class = surface.GetTextSize( class ) + local w_name, h_name = surface.GetTextSize( name ) + + local w_total = txt ~= "" and w_body or 0 + local h_total = txt ~= "" and h_body or 0 + + local w_footer, h_footer = 0, 0 + + local info_requires_multiline = false + if w_total < w_class + w_name - edgesize then + info_requires_multiline = true + + w_footer = math.max(w_total,w_class,w_name) + h_footer = h_class + h_name + edgesize + 8 + + w_total = w_footer + h_total = h_total + h_footer + else + w_footer = math.max(w_total,w_class + 8 + w_name) + h_footer = math.max(h_class,h_name) + edgesize + 8 + + w_total = w_footer + h_total = h_total + h_footer + end + + if h_body == 0 then h_total = h_total - h_body - edgesize end + + local pos = self:GetWorldTipPositions( w_total + edgesize*2,h_total + edgesize, + w_body,h_body, + w_footer,h_footer ) + + self:DrawWorldTipOutline( pos ) + + local offset = pos.min.y + if h_body > 0 then + self:DrawWorldTipBody( pos ) + offset = offset + h_body + edgesize + + surface.SetDrawColor( Color(0,0,0,255) ) + surface.DrawLine( pos.min.x, offset, pos.max.x, offset ) + end + + if info_requires_multiline then + draw.DrawText( class, "GModWorldtip", pos.center.x, offset + 8, Color(255,255,255,255), TEXT_ALIGN_CENTER ) + draw.DrawText( name, "GModWorldtip", pos.center.x, offset + h_class + 16, Color(255,255,255,255), TEXT_ALIGN_CENTER ) + else + draw.DrawText( class, "GModWorldtip", pos.min.x + edgesize, offset + 16, Color(255,255,255,255) ) + draw.DrawText( name, "GModWorldtip", pos.min.x + pos.size.w - w_name - edgesize, offset + 16, Color(255,255,255,255) ) + end + end + + hook.Add("HUDPaint","wire_draw_world_tips",function() + if SysTime() > WorldTip.dietime then return end + + local ent = WorldTip.ent + if not IsValid(ent) then return end + + ent:DrawWorldTip() + end) + + -- Custom better version of this base_gmodentity function + function ENT:BeingLookedAtByLocalPlayer() + local trace = LocalPlayer():GetEyeTrace() + + if trace.Entity ~= self then return false end + if trace.HitPos:Distance(LocalPlayer():GetShootPos()) > 200 then return false end + + return true + end + + local drawInfo = false + hook.Add('Think', 'wire.drawInfo', function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local wep = ply:GetActiveWeapon() + drawInfo = IsValid(wep) and wep:GetClass() == 'gmod_tool' + end) + + function ENT:DoNormalDraw(nohalo, notip) + if not drawInfo then + return self:DrawModel() + end + + local looked_at = self:BeingLookedAtByLocalPlayer() + if not nohalo and wire_drawoutline:GetBool() and looked_at then + if self.RenderGroup == RENDERGROUP_OPAQUE then + self.OldRenderGroup = self.RenderGroup + self.RenderGroup = RENDERGROUP_TRANSLUCENT + end + self:DrawEntityOutline() + self:DrawModel() + else + if self.OldRenderGroup then + self.RenderGroup = self.OldRenderGroup + self.OldRenderGroup = nil + end + self:DrawModel() + end + if not notip and looked_at then + self:AddWorldTip() + end + end + + -- function ENT:Think() + -- if (CurTime() >= (self.NextRBUpdate or 0)) then + -- -- We periodically update the render bounds every 10 seconds - the + -- -- reasons why are mostly anecdotal, but in some circumstances + -- -- entities might 'forget' their renderbounds. Nobody really knows + -- -- if this is still needed or not. + -- self.NextRBUpdate = CurTime() + 10 + -- Wire_UpdateRenderBounds(self) + -- end + -- end + + local halos = {} + local halos_inv = {} + + function ENT:DrawEntityOutline() + if halos_inv[self] then return end + halos[#halos+1] = self + halos_inv[self] = true + end + + hook.Add("PreDrawHalos", "Wiremod_overlay_halos", function() + if #halos == 0 then return end + halo.Add(halos, Color(100,100,255), 3, 3, 1, true, true) + halos = {} + halos_inv = {} + end) + + -------------------------------------------------------------------------------- + -- Overlay getting + -------------------------------------------------------------------------------- + + -- Basic legacy GetOverlayText, is no longer used here but we leave it here in case other addons rely on it. + function ENT:GetOverlayText() + local name = self:GetNWString("WireName") + if name == "" then name = self.PrintName end + local header = "- " .. name .. " -" + + local data = self:GetOverlayData() + if data and data.txt then + return header .. "\n" .. data.txt + else + return header + end + end + + -------------------------------------------------------------------------------- + -- Overlay receiving + -------------------------------------------------------------------------------- + net.Receive( "wire_overlay_data", function( len ) + local ent = net.ReadEntity() + if IsValid( ent ) then + ent.OverlayData = net.ReadTable() + end + end ) +end + +-------------------------------------------------------------------------------- +-- Overlay setting +-------------------------------------------------------------------------------- +-- We want more fine-grained control over everything related to overlays, +-- so we have a custom system here + +-- It allows us to optionally send values rather than entire strings, which saves networking +-- It also allows us to only update overlays when someone is looking at the entity. + +function ENT:SetOverlayText( txt ) + if not self.OverlayData then + self.OverlayData = {} + end + + if txt and #txt > 12000 then + txt = string.sub(txt,1,12000) -- I have tested this and 12000 chars is enough to cover the entire screen at 1920x1080. You're unlikely to need more + end + + self.OverlayData.txt = txt + + if not self.OverlayData_UpdateTime then self.OverlayData_UpdateTime = {} end + self.OverlayData_UpdateTime.time = CurTime() +end + +function ENT:SetOverlayData( data ) + self.OverlayData = data + if self.OverlayData.txt and #self.OverlayData.txt > 12000 then + self.OverlayData.txt = string.sub(self.OverlayData.txt,1,12000) + end + + if not self.OverlayData_UpdateTime then self.OverlayData_UpdateTime = {} end + self.OverlayData_UpdateTime.time = CurTime() +end + +function ENT:GetOverlayData() + return self.OverlayData +end + +if CLIENT then return end -- no more client + +-------------------------------------------------------------------------------- +-- Overlay syncing +-------------------------------------------------------------------------------- + +util.AddNetworkString( "wire_overlay_data" ) + +timer.Create("WireOverlayUpdate", 0.1, 0, function() + for _, ply in ipairs(player.GetAll()) do + local ent = ply:GetEyeTrace().Entity + if IsValid(ent) and ent.IsWire and + ent.OverlayData and + ent.OverlayData_UpdateTime and + ent.OverlayData_UpdateTime.time > (ent.OverlayData_UpdateTime[ply] or 0) then + + ent.OverlayData_UpdateTime[ply] = CurTime() + + net.Start( "wire_overlay_data" ) + net.WriteEntity( ent ) + net.WriteTable( ent.OverlayData ) + net.Send( ply ) + end + end +end) + +-------------------------------------------------------------------------------- +-- Other functions +-------------------------------------------------------------------------------- + +function ENT:Initialize() + BaseClass.Initialize(self) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self.WireDebugName = self.WireDebugName or (self.PrintName and self.PrintName:sub(6)) or self:GetClass():gsub("gmod_wire", "") +end + +function ENT:OnRemove() + WireLib.Remove(self) +end + +function ENT:OnRestore() + WireLib.Restored(self) +end + +function ENT:BuildDupeInfo() + return WireLib.BuildDupeInfo(self) +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + WireLib.ApplyDupeInfo(ply, ent, info, GetEntByID) +end + +function ENT:PreEntityCopy() + duplicator.ClearEntityModifier(self, "WireDupeInfo") + -- build the DupeInfo table and save it as an entity mod + local DupeInfo = self:BuildDupeInfo() + if DupeInfo then + duplicator.StoreEntityModifier(self, "WireDupeInfo", DupeInfo) + end +end + +function ENT:OnEntityCopyTableFinish(dupedata) + -- Called by Garry's duplicator, to modify the table that will be saved about an ent + + -- Remove anything with non-string keys, or util.TableToJSON will crash the game + dupedata.OverlayData_UpdateTime = nil +end + +local function EntityLookup(CreatedEntities) + return function(id, default) + if id == nil then return default end + if id == 0 then return game.GetWorld() end + local ent = CreatedEntities[id] + if IsValid(ent) then return ent else return default end + end +end + +function ENT:OnDuplicated() + self.DuplicationInProgress = true +end + +function ENT:PostEntityPaste(Player,Ent,CreatedEntities) + -- We manually apply the entity mod here rather than using a + -- duplicator.RegisterEntityModifier because we need access to the + -- CreatedEntities table. + if Ent.EntityMods and Ent.EntityMods.WireDupeInfo then + Ent:ApplyDupeInfo(Player, Ent, Ent.EntityMods.WireDupeInfo, EntityLookup(CreatedEntities)) + end + self.DuplicationInProgress = nil +end + +-- Helper function for entities that can be linked +ENT.LINK_STATUS_UNLINKED = 1 +ENT.LINK_STATUS_LINKED = 2 +ENT.LINK_STATUS_INACTIVE = 2 -- alias +ENT.LINK_STATUS_DEACTIVATED = 2 -- alias +ENT.LINK_STATUS_ACTIVE = 3 +ENT.LINK_STATUS_ACTIVATED = 3 -- alias +function ENT:ColorByLinkStatus(status) + local a = self:GetColor().a + + if status == self.LINK_STATUS_UNLINKED then + self:SetColor(Color(255,0,0,a)) + elseif status == self.LINK_STATUS_LINKED then + self:SetColor(Color(255,165,0,a)) + elseif status == self.LINK_STATUS_ACTIVE then + self:SetColor(Color(0,255,0,a)) + else + self:SetColor(Color(255,255,255,a)) + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_addressbus.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_addressbus.lua new file mode 100644 index 0000000..39fce12 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_addressbus.lua @@ -0,0 +1,98 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Address Bus" +ENT.WireDebugName = "AddressBus" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.Outputs = Wire_CreateOutputs(self, {"Memory"}) + self.Inputs = Wire_CreateInputs(self,{"Memory1","Memory2","Memory3","Memory4"}) + self.DataRate = 0 + self.DataBytes = 0 + + self.Memory = {} + self.MemStart = {} + self.MemEnd = {} + for i = 1,4 do + self.Memory[i] = nil + self.MemStart[i] = 0 + self.MemEnd[i] = 0 + end + self:SetOverlayText("Data rate: 0 bps") +end + +function ENT:Setup(Mem1st, Mem2st, Mem3st, Mem4st, Mem1sz, Mem2sz, Mem3sz, Mem4sz) + local starts = {Mem1st,Mem2st,Mem3st,Mem4st} + local sizes = {Mem1sz,Mem2sz,Mem3sz,Mem4sz} + for i = 1,4 do + starts[i] = tonumber(starts[i]) or 0 + sizes[i] = tonumber(sizes[i]) or 0 + + self.MemStart[i] = starts[i] + self.MemEnd[i] = starts[i] + sizes[i] - 1 + self["Mem"..i.."st"] = starts[i] + self["Mem"..i.."sz"] = sizes[i] + end +end + +function ENT:Think() + BaseClass.Think(self) + + self.DataRate = self.DataBytes + self.DataBytes = 0 + + Wire_TriggerOutput(self, "Memory", self.DataRate) + self:SetOverlayText("Data rate: "..math.floor(self.DataRate*2).." bps") + self:NextThink(CurTime()+0.5) + return true +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + for i = 1,4 do + if (Address >= self.MemStart[i]) and (Address <= self.MemEnd[i]) then + if self.Memory[i] then + if self.Memory[i].ReadCell then + self.DataBytes = self.DataBytes + 1 + local val = self.Memory[i]:ReadCell(Address - self.MemStart[i]) + return val or 0 + end + else + return 0 + end + end + end + return nil +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + local res = false + for i = 1,4 do + if (Address >= self.MemStart[i]) and (Address <= self.MemEnd[i]) then + if self.Memory[i] then + if self.Memory[i].WriteCell then + self.Memory[i]:WriteCell(Address - self.MemStart[i], value) + end + end + self.DataBytes = self.DataBytes + 1 + res = true + end + end + return res +end + +function ENT:TriggerInput(iname, value) + for i = 1,4 do + if iname == "Memory"..i then + self.Memory[i] = self.Inputs["Memory"..i].Src + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_addressbus", WireLib.MakeWireEnt, "Data", "Mem1st", "Mem2st", "Mem3st", "Mem4st", "Mem1sz", "Mem2sz", "Mem3sz", "Mem4sz") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_adv_emarker.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_adv_emarker.lua new file mode 100644 index 0000000..1e5caef --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_adv_emarker.lua @@ -0,0 +1,141 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Adv Wire Entity Marker" +ENT.Author = "Divran" +ENT.WireDebugName = "Adv EMarker" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Marks = {} + local outputs = {"Entities [ARRAY]", "Nr"} + for i=3,12 do + outputs[i] = "Entity" .. (i-2) .. " [ENTITY]" + end + self.Inputs = WireLib.CreateInputs( self, { "Entity [ENTITY]", "Add Entity", "Remove Entity", "Clear Entities" } ) + self.Outputs = WireLib.CreateOutputs( self, outputs ) + self:SetOverlayText( "Number of entities linked: 0" ) +end + +function ENT:TriggerInput( name, value ) + if (name == "Entity") then + if IsValid(value) then + self.Target = value + end + elseif (name == "Add Entity") then + if IsValid(self.Target) then + if (value != 0) then + local bool, index = self:CheckEnt( self.Target ) + if (!bool) then + self:LinkEnt( self.Target ) + end + end + end + elseif (name == "Remove Entity") then + if IsValid(self.Target) then + if (value != 0) then + local bool, index = self:CheckEnt( self.Target ) + if (bool) then + self:UnlinkEnt( self.Target ) + end + end + end + elseif (name == "Clear Entities") then + self:ClearEntities() + end +end + +function ENT:UpdateOutputs() + -- Trigger regular outputs + WireLib.TriggerOutput( self, "Entities", self.Marks ) + WireLib.TriggerOutput( self, "Nr", #self.Marks ) + + -- Trigger special outputs + for i=3,12 do + WireLib.TriggerOutput( self, "Entity" .. (i-2), self.Marks[i-2] ) + end + + -- Overlay text + self:SetOverlayText( "Number of entities linked: " .. #self.Marks ) + + -- Yellow lines information + WireLib.SendMarks(self) +end + +function ENT:CheckEnt( ent ) + for index, e in pairs( self.Marks ) do + if (e == ent) then return true, index end + end + return false, 0 +end + +function ENT:LinkEnt( ent ) + if (self:CheckEnt( ent )) then return false end + self.Marks[#self.Marks+1] = ent + ent:CallOnRemove("AdvEMarker.Unlink", function(ent) + self:UnlinkEnt(ent) + end) + self:UpdateOutputs() + return true +end + +function ENT:UnlinkEnt( ent ) + local bool, index = self:CheckEnt( ent ) + if (bool) then + table.remove( self.Marks, index ) + self:UpdateOutputs() + end + return bool +end + +function ENT:ClearEntities() + for i=1,#self.Marks do + if self.Marks[i]:IsValid() then + self.Marks[i]:RemoveCallOnRemove( "AdvEMarker.Unlink" ) + end + end + self.Marks = {} + self:UpdateOutputs() +end + +function ENT:OnRemove() + self:ClearEntities() +end + +duplicator.RegisterEntityClass( "gmod_wire_adv_emarker", WireLib.MakeWireEnt, "Data" ) + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if next(self.Marks) then + local tbl = {} + for index, e in pairs( self.Marks ) do + tbl[index] = e:EntIndex() + end + + info.marks = tbl + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if (info.marks) then + self.Marks = self.Marks or {} + + for index, entid in pairs(info.marks) do + local ent = GetEntByID(entid) + self.Marks[index] = ent + ent:CallOnRemove("AdvEMarker.Unlink", function(ent) + if IsValid(self) then self:UnlinkEnt(ent) end + end) + end + self:UpdateOutputs() + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_adv_input.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_adv_input.lua new file mode 100644 index 0000000..ae21859 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_adv_input.lua @@ -0,0 +1,107 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Advanced Input" +ENT.WireDebugName = "Adv. Input" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self,{"Reset"}) + self.Outputs = Wire_CreateOutputs(self,{"Out"}) +end + +function ENT:Setup(key_more,key_less,toggle,value_min,value_max,value_start,speed) + self.keymore = key_more + self.keyless = key_less + + local pl = self:GetPlayer() + numpad.OnDown( pl, key_more, "WireAdvInput_On", self, 1 ) + numpad.OnUp( pl, key_more, "WireAdvInput_Off", self, 1 ) + numpad.OnDown( pl, key_less, "WireAdvInput_On", self, -1 ) + numpad.OnUp( pl, key_less, "WireAdvInput_Off", self, -1 ) + + self.toggle = (toggle == 1 || toggle == true) + self.value_min = value_min + self.value_max = value_max + self.Value = value_start + self.value_start = value_start + self.speed = speed + self:ShowOutput() + Wire_TriggerOutput(self,"Out",self.Value) +end + +function ENT:TriggerInput(iname, value) + if(iname == "Reset")then + if(value != 0)then + self.Value = self.value_start + self:ShowOutput() + Wire_TriggerOutput(self,"Out",self.Value) + end + end +end + +function ENT:InputActivate(mul) + if (self.toggle) then + return self:Switch( !self.On, mul ) + end + return self:Switch( true, mul ) +end + +function ENT:InputDeactivate( mul ) + if (self.toggle) then return true end + return self:Switch( false, mul ) +end + +function ENT:Switch( on, mul ) + if (!self:IsValid()) then return false end + self.On = on + if(on) then + self.dir = mul + else + self.dir = 0 + end + return true +end + +function ENT:Think() + BaseClass.Think(self) + local timediff = CurTime()-(self.LastThink or 0) + self.LastThink = (self.LastThink or 0)+timediff + if (self.On == true) then + self.Value = self.Value + self.speed * timediff * self.dir + if (self.Value < self.value_min) then + self.Value = self.value_min + elseif (self.Value > self.value_max) then + self.Value = self.value_max + end + self:ShowOutput() + Wire_TriggerOutput(self,"Out",self.Value) + self:NextThink(CurTime()+0.02) + return true + end +end + +function ENT:ShowOutput() + self:SetOverlayText("(" .. self.value_min .. " - " .. self.value_max .. ") = " .. self.Value) +end + +local function On( pl, ent, mul ) + if (!ent:IsValid()) then return false end + pl = player.GetBySteamID(pl) + if not gamemode.Call("PlayerUse", pl, ent) then return end + return ent:InputActivate( mul ) +end + +local function Off( pl, ent, mul ) + if (!ent:IsValid()) then return false end + pl = player.GetBySteamID(pl) + if not gamemode.Call("PlayerUse", pl, ent) then return end + return ent:InputDeactivate( mul ) +end +numpad.Register( "WireAdvInput_On",On) +numpad.Register( "WireAdvInput_Off",Off) + +duplicator.RegisterEntityClass("gmod_wire_adv_input", WireLib.MakeWireEnt, "Data", "keymore", "keyless", "toggle", "value_min", "value_max", "value_start", "speed", "frozen") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_button.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_button.lua new file mode 100644 index 0000000..ccf2dd1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_button.lua @@ -0,0 +1,180 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Button" +ENT.WireDebugName = "Button" + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "On" ) +end + +if CLIENT then + local halo_ent, halo_blur + + function ENT:Initialize() + self.PosePosition = 0.0 + end + + function ENT:Think() + baseclass.Get("gmod_button").UpdateLever(self) + end + + function ENT:Draw() + self:DoNormalDraw(true,false) + if LocalPlayer():GetEyeTrace().Entity == self and EyePos():Distance( self:GetPos() ) < 512 then + if self:GetOn() then + halo_ent = self + halo_blur = 4 + math.sin(CurTime()*20)*2 + else + self:DrawEntityOutline() + end + end + Wire_Render(self) + end + + hook.Add("PreDrawHalos", "Wiremod_button_overlay_halos", function() + if halo_ent then + halo.Add({halo_ent}, Color(255,100,100), halo_blur, halo_blur, 1, true, true) + halo_ent = nil + end + end) + + return -- No more client +end + +ENT.OutputEntID = false +ENT.EntToOutput = NULL + +local anims = { + -- ["model"] = { on_anim, off_anim } + ["models/props/switch001.mdl"] = { 2, 1 }, + ["models/props_combine/combinebutton.mdl"] = { 3, 2 }, + ["models/props_mining/control_lever01.mdl"] = { 1, 4 }, + ["models/props_mining/freightelevatorbutton01.mdl"] = { 1, 2 }, + ["models/props_mining/freightelevatorbutton02.mdl"] = { 1, 2 }, + ["models/props_mining/switch01.mdl"] = { 1, 2 }, + ["models/bull/buttons/rocker_switch.mdl"] = { 1, 2 }, + ["models/bull/buttons/toggle_switch.mdl"] = { 1, 2 }, + ["models/bull/buttons/key_switch.mdl"] = { 1, 2 }, + ["models/props_mining/switch_updown01.mdl"] = { 2, 3 }, +} + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.Outputs = Wire_CreateOutputs(self, { "Out" }) + self.Inputs = Wire_CreateInputs(self, { "Set" }) + local anim = anims[self:GetModel()] + if anim then self:SetSequence(anim[2]) end +end + +function ENT:TriggerInput(iname, value) + if iname == "Set" then + if (self.toggle) then + self:Switch(value ~= 0) + self.PrevUser = nil + self.podpress = nil + end + end +end + +function ENT:Use(ply, caller) + if (not ply:IsPlayer()) then return end + if (self.PrevUser) and (self.PrevUser:IsValid()) then return end + if self.OutputEntID then + self.EntToOutput = ply + end + if (self:GetOn()) then + if (self.toggle) then self:Switch(false) end + + return + end + if IsValid(caller) and caller:GetClass() == "gmod_wire_pod" then + self.podpress = true + end + + self:Switch(true) + self.PrevUser = ply +end + +function ENT:Think() + BaseClass.Think(self) + + if ( self:GetOn() ) then + if (not self.PrevUser) + or (not self.PrevUser:IsValid()) + or (not self.podpress and not self.PrevUser:KeyDown(IN_USE)) + or (self.podpress and not self.PrevUser:KeyDown( IN_ATTACK )) then + if (not self.toggle) then + self:Switch(false) + end + + self.PrevUser = nil + self.podpress = nil + end + + self:NextThink(CurTime()+0.05) + return true + end +end + +function ENT:Setup(toggle, value_off, value_on, description, entityout) + self.toggle = toggle + self.value_off = value_off + self.value_on = value_on + self.entityout = entityout + + if entityout then + WireLib.AdjustSpecialOutputs(self, { "Out", "EntID" , "Entity" }, { "NORMAL", "NORMAL" , "ENTITY" }) + Wire_TriggerOutput(self, "EntID", 0) + Wire_TriggerOutput(self, "Entity", nil) + self.OutputEntID=true + else + Wire_AdjustOutputs(self, { "Out" }) + self.OutputEntID=false + end + + if toggle then + Wire_AdjustInputs(self, { "Set" }) + else + Wire_AdjustInputs(self, {}) + end + self:Switch(self:GetOn()) +end + +function ENT:Switch(on) + if (not self:IsValid()) then return end + + self:SetOn( on ) + + if (on) then + self:ShowOutput(self.value_on) + self.Value = self.value_on + + local anim = anims[self:GetModel()] + if anim then self:SetSequence(anim[1]) end + else + self:ShowOutput(self.value_off) + self.Value = self.value_off + + local anim = anims[self:GetModel()] + if anim then self:SetSequence(anim[2]) end + + if self.OutputEntID then self.EntToOutput = NULL end + end + + Wire_TriggerOutput(self, "Out", self.Value) + if self.OutputEntID then + Wire_TriggerOutput(self, "EntID", self.EntToOutput:EntIndex()) + Wire_TriggerOutput(self, "Entity", self.EntToOutput) + end + return true +end + +function ENT:ShowOutput(value) + self:SetOverlayText( "(" .. self.value_off .. " - " .. self.value_on .. ") = " .. value ) +end + +duplicator.RegisterEntityClass("gmod_wire_button", WireLib.MakeWireEnt, "Data", "toggle", "value_off", "value_on", "description", "entityout" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cameracontroller.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cameracontroller.lua new file mode 100644 index 0000000..6cae073 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cameracontroller.lua @@ -0,0 +1,1089 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Camera Controller" +ENT.WireDebugName = "Camera Controller" + +if CLIENT then + + -------------------------------------------------- + -- Camera controller + -- Clientside + -------------------------------------------------- + + local enabled = false + local self + + local clientprop + + -- Position + local pos = Vector(0,0,0) + local smoothpos = Vector(0,0,0) + + -- Distance & zooming + local distance = 0 + local curdistance = 0 + local oldcurdistance = 0 + local smoothdistance = 0 + + local zoomdistance = 0 + local zoombind = 0 + + -- Angle + local ang = Angle(0,0,0) + local smoothang = Angle(0,0,0) + + local oldeyeang = Angle(0,0,0) + local unroll = false + + -- Options + local ParentLocal = false + local AutoMove = false + local FreeMove = false + local LocalMove = false + local AutoUnclip = false + local AutoUnclip_IgnoreWater = false + local AllowZoom = false + local DrawPlayer = true + local DrawParent = true + + -- View calculations + local resetViewAngles = false + local max = math.max + local abs = math.abs + + local pos_speed_convar = GetConVar( "wire_cam_smooth_amount" ) + + local Parent + local function GetParent() + return Parent, IsValid( Parent ) + end + + local function DoAutoMove( curpos, curang, curdistance, parent, HasParent ) + local pos_speed = pos_speed_convar:GetFloat() + local ang_speed = pos_speed - 2 + + curang = LocalPlayer():EyeAngles() + + if AllowZoom then + if zoombind ~= 0 then + zoomdistance = math.Clamp(zoomdistance + zoombind * FrameTime() * 100 * max((abs(curdistance) + abs(zoomdistance))/10,10),0,16000-curdistance) + zoombind = 0 + end + curdistance = curdistance + zoomdistance + end + + smoothdistance = Lerp( FrameTime() * pos_speed, smoothdistance, curdistance ) + + if HasParent then + if LocalMove then + curpos = parent:LocalToWorld( curpos - curang:Forward() * smoothdistance ) + curang = parent:LocalToWorldAngles( curang ) + else + curpos = parent:LocalToWorld( curpos ) - curang:Forward() * smoothdistance + end + else + curpos = curpos - curang:Forward() * smoothdistance + end + + return curpos, curang + end + + local function DoAutoUnclip( curpos, parent, HasParent ) + local start, endpos + + if not AutoMove then + if HasParent then + start = parent:GetPos() + else + start = self:GetPos() + end + + endpos = curpos + else + if HasParent then + start = parent:LocalToWorld(pos) + else + start = pos + end + + endpos = curpos + end + + local tr = { + start = start, + endpos = endpos, + mask = (AutoUnclip_IgnoreWater and CONTENTS_SOLID or bit.bor(MASK_WATER, CONTENTS_SOLID)), + mins = Vector(-8,-8,-8), + maxs = Vector(8,8,8) + } + + local trace = util.TraceHull( tr ) + + if trace.Hit then + return trace.HitPos + end + + return curpos + end + + hook.Remove("CalcView","wire_camera_controller_calcview") + hook.Add( "CalcView", "wire_camera_controller_calcview", function() + if not enabled then return end + if not IsValid( self ) then enabled = false return end + + local pos_speed = pos_speed_convar:GetFloat() + local ang_speed = pos_speed - 2 + + local curpos = pos + local curang = ang + local curdistance = distance + + local parent, HasParent = GetParent() + + local newview = {} + + -- AutoMove + if AutoMove then + -- only smooth the position, and do it before the automove + smoothpos = LerpVector( FrameTime() * pos_speed, smoothpos, curpos ) + + curpos, curang = DoAutoMove( smoothpos, curang, curdistance, parent, HasParent ) + + if AutoUnclip then + curpos = DoAutoUnclip( curpos, parent, HasParent ) + end + + -- apply view + newview.origin = curpos + newview.angles = curang + elseif HasParent then + -- smooth BEFORE using toWorld + smoothpos = LerpVector( FrameTime() * pos_speed, smoothpos, curpos ) + smoothang = LerpAngle( FrameTime() * ang_speed, smoothang, curang ) + + -- now toworld it + curpos = parent:LocalToWorld( smoothpos ) + curang = parent:LocalToWorldAngles( smoothang ) + + -- now check for auto unclip + if AutoUnclip then + curpos = DoAutoUnclip( curpos, parent, HasParent ) + end + + -- apply view + newview.origin = curpos + newview.angles = curang + else + -- check auto unclip first + if AutoUnclip then + curpos = DoAutoUnclip( curpos, parent, HasParent ) + end + + -- there's no parent, just smooth it + smoothpos = LerpVector( FrameTime() * pos_speed, smoothpos, curpos ) + smoothang = LerpAngle( FrameTime() * ang_speed, smoothang, curang ) + newview.origin = smoothpos + newview.angles = smoothang + end + + newview.drawviewer = DrawPlayer -- this doesn't work (probably because I use SetViewEntity serverside) + return newview + end) + + hook.Add("PlayerBindPress", "wire_camera_controller_zoom", function(ply, bind, pressed) + if enabled and AllowZoom then + if (bind == "invprev") then + zoombind = -1 + return true + elseif (bind == "invnext") then + zoombind = 1 + return true + end + end + end) + + local mouse_sensitvity = GetConVar("sensitivity") + + hook.Remove("InputMouseApply", "wire_camera_controller_input_unlock") + hook.Add("InputMouseApply", "wire_camera_controller_input_unlock", function(cmd, x, y, ang) + if resetViewAngles then + cmd:SetViewAngles( Angle(0, ang.y, 0) ) + resetViewAngles = false + return true + end + + if not enabled then return end + if not FreeMove then return end + if not IsValid( self ) then enabled = false return end + -- feels correct, might not be, but raw values were definitely too fast + local smooth = mouse_sensitvity:GetFloat() * FrameTime() + local matrix = Matrix() + matrix:SetAngles( ang ) + -- could make this a number instead + if unroll then + local parent, hasParent = GetParent() + if hasParent then + local parentMatrix = Matrix() + parentMatrix:SetAngles( parent:GetAngles() ) + local diffAngles = (matrix:GetInverseTR() * parentMatrix):GetAngles() + if math.abs(diffAngles.y) > 90 then diffAngles.r = -diffAngles.r end + matrix:Rotate( Angle( y * smooth, -x * smooth, diffAngles.r * smooth ) ) + else + matrix:Rotate( Angle( y * smooth, -x * smooth, -ang.r * smooth ) ) + end + else + matrix:Rotate( Angle( y * smooth, -x * smooth, 0 ) ) + end + cmd:SetViewAngles( matrix:GetAngles() ) + return true + end) + + -------------------------------------------------- + -- Receiving data from server + -------------------------------------------------- + + local WaitingForID + local function ReadPositions() + -- pos/ang + pos.x = net.ReadFloat() + pos.y = net.ReadFloat() + pos.z = net.ReadFloat() + ang.p = net.ReadFloat() + ang.y = net.ReadFloat() + ang.r = net.ReadFloat() + + unroll = net.ReadBit() ~= 0 + + -- distance + distance = math.Clamp(net.ReadFloat(),-16000,16000) + + -- Parent + WaitingForID = net.ReadInt(32) + + if WaitingForID ~= -1 and IsValid( Entity(WaitingForID) ) then + Parent = Entity(WaitingForID) + WaitingForID = nil + elseif WaitingForID == -1 then + WaitingForID = nil + end + + end + + -- if the camera is parented to an entity that was very recently + -- created, there is a chance it doesn't exist on the client yet, + -- so we use this hook to wait for it to be created + hook.Add( "NetworkEntityCreated", "wire_camera_controller_network_entity", function( ent ) + if WaitingForID and ent:EntIndex() == WaitingForID then + Parent = ent + WaitingForID = nil + end + end) + + net.Receive( "wire_camera_controller_toggle", function( len ) + local enable = net.ReadBit() ~= 0 + local cam = net.ReadEntity() + + if cam ~= self and enabled then return end -- another camera controller is already enabled + + self = cam + + -- make the previous parent visible + -- (this also makes the parent visible when you turn off the cam controller) + local parent, HasParent = GetParent() + if HasParent then + parent:SetNoDraw( false ) + end + + if enable then + ParentLocal = net.ReadBit() ~= 0 + AutoMove = net.ReadBit() ~= 0 + FreeMove = net.ReadBit() ~= 0 + LocalMove = net.ReadBit() ~= 0 + AllowZoom = net.ReadBit() ~= 0 + AutoUnclip = net.ReadBit() ~= 0 + AutoUnclip_IgnoreWater = net.ReadBit() ~= 0 + DrawPlayer = net.ReadBit() ~= 0 + DrawParent = net.ReadBit() ~= 0 + ReadPositions() + + -- Hide the parent if that's what the user wants + local parent, HasParent = GetParent() + if HasParent and not DrawParent then + parent:SetNoDraw( true ) + end + + -- If we switched on, set current positions and angles + if not enabled then + -- Copy them + smoothpos = Vector(pos.x,pos.y,pos.z) + smoothang = Angle(ang.p,ang.y,ang.r) + curdistance = distance + smoothdistance = distance + zoomdistance = 0 + end + else + WaitingForID = nil + resetViewAngles = true + end + + enabled = enable + end) + + net.Receive( "wire_camera_controller_sync", function( len ) + if not enabled or not IsValid( self ) then return end + local cam = net.ReadEntity() + if cam ~= self then return end -- another cam controller is trying to hijack us + + ReadPositions() + end) + + return -- No more client +end + +-------------------------------------------------- +-- Initialize +-------------------------------------------------- + +function ENT:Initialize() + BaseClass.Initialize(self) + self.Outputs = WireLib.CreateOutputs( self, { "On", "HitPos [VECTOR]", "CamPos [VECTOR]", "CamDir [VECTOR]", + "CamAng [ANGLE]", "Trace [RANGER]" } ) + self.Inputs = WireLib.CreateInputs( self, { "Activated", "Direction [VECTOR]", "Angle [ANGLE]", "Position [VECTOR]", + "Distance", "UnRoll", "Parent [ENTITY]", "FilterEntities [ARRAY]", "FLIR", "FOV" } ) + + self.Activated = false -- Whether or not to activate the cam controller for all players sitting in linked vehicles, or as soon as a player sits in a linked vehicle + self.Active = false -- Whether the player is currently being shown the camera view. + self.FOV = nil -- The FOV of the player's view. (By default, do not change the FOV.) + self.FLIR = false -- Whether infrared view is turned on. + + self.Position = Vector(0,0,0) + self.Angle = Angle(0,0,0) + self.Distance = 0 + self.UnRoll = false + + self.Players = {} + self.Vehicles = {} + + self.NextGetContraption = 0 + self.NextUpdateOutputs = 0 + + self:GetContraption() + + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) +end + +-------------------------------------------------- +-- UpdateOverlay +-------------------------------------------------- + +function ENT:UpdateOverlay() + local unclip = self.AutoUnclip and "Yes" or "No" + if self.AutoUnclip_IgnoreWater then unclip = unclip .. " (Ignores water)" end + + self:SetOverlayText( + string.format( "Local Coordinates: %s\nClient side movement: %s\nFree movement: %s\nCL movement local to parent: %s\nClient side zooming: %s\nAuto unclip: %s\nDraw player: %s\nDraw parent: %s\n\nActivated: %s", + self.ParentLocal and "Yes" or "No", + self.AutoMove and "Yes" or "No", + self.FreeMove and "Yes" or "No", + self.LocalMove and "Yes" or "No", + self.AllowZoom and "Yes" or "No", + unclip, + self.DrawPlayer and "Yes" or "No", + self.DrawParent and "Yes" or "No", + self.Activated and "Yes" or "No" + ) + ) +end + +-------------------------------------------------- +-- Setup +-------------------------------------------------- + +function ENT:Setup(ParentLocal,AutoMove,FreeMove,LocalMove,AllowZoom,AutoUnclip,DrawPlayer,AutoUnclip_IgnoreWater,DrawParent) + self.ParentLocal = tobool(ParentLocal) + self.AutoMove = tobool(AutoMove) + self.FreeMove = tobool(FreeMove) + self.LocalMove = tobool(LocalMove) + self.AllowZoom = tobool(AllowZoom) + self.AutoUnclip = tobool(AutoUnclip) + self.AutoUnclip_IgnoreWater = tobool(AutoUnclip_IgnoreWater) + self.DrawPlayer = tobool(DrawPlayer) + self.DrawParent = tobool(DrawParent) + + self:UpdateOverlay() +end + +-------------------------------------------------- +-- Data sending +-------------------------------------------------- + +local function SendPositions( pos, ang, dist, parent, unroll ) + -- pos/ang + net.WriteFloat( pos.x ) + net.WriteFloat( pos.y ) + net.WriteFloat( pos.z ) + net.WriteFloat( ang.p ) + net.WriteFloat( ang.y ) + net.WriteFloat( ang.r ) + + net.WriteBit( unroll ) + + -- distance + net.WriteFloat( dist ) + + -- parent + local id = IsValid( parent ) and parent:EntIndex() or -1 + net.WriteInt( id, 32 ) +end + +util.AddNetworkString( "wire_camera_controller_toggle" ) +function ENT:SyncSettings( ply, active ) + if active == nil then active = self.Active end + if not IsValid(ply) then ply = self.Players end + net.Start( "wire_camera_controller_toggle" ) + net.WriteBit( active ) + net.WriteEntity( self ) + if self.Active then + net.WriteBit( self.ParentLocal ) + net.WriteBit( self.AutoMove ) + net.WriteBit( self.FreeMove ) + net.WriteBit( self.LocalMove ) + net.WriteBit( self.AllowZoom ) + net.WriteBit( self.AutoUnclip ) + net.WriteBit( self.AutoUnclip_IgnoreWater ) + net.WriteBit( self.DrawPlayer ) + net.WriteBit( self.DrawParent ) + SendPositions( self.Position, self.Angle, self.Distance, self.Parent, self.UnRoll ) + end + net.Send( ply ) +end + + +util.AddNetworkString( "wire_camera_controller_sync" ) +function ENT:SyncPositions( ply ) + if not IsValid(ply) then ply = self.Players end + net.Start( "wire_camera_controller_sync" ) + net.WriteEntity( self ) + SendPositions( self.Position, self.Angle, self.Distance, self.Parent, self.UnRoll ) + net.Send( ply ) +end + + +-------------------------------------------------- +-- GetContraption +-- Used in UpdateOutputs to make the traces ignore the contraption +-------------------------------------------------- +function ENT:GetContraption() + if CurTime() < self.NextGetContraption then return end + self.NextGetContraption = CurTime() + 5 + + self.Entities = {} + + local parent = self + if IsValid( self.Parent ) then parent = self.Parent end + + local ents = constraint.GetAllConstrainedEntities( parent ) + for k,v in pairs( ents ) do + self.Entities[#self.Entities+1] = v + end + if self.Inputs.FilterEntities and self.Inputs.FilterEntities.Value then + for k,v in pairs( self.Inputs.FilterEntities.Value ) do + if IsEntity( v ) and IsValid( v ) then + self.Entities[#self.Entities+1] = v + end + end + end +end + +-------------------------------------------------- +-- UpdateOutputs +-------------------------------------------------- +function ENT:UpdateOutputs() + if CurTime() < self.NextUpdateOutputs then return end + self.NextUpdateOutputs = CurTime() + 0.1 + + local ply = self.Players[1] + + if self.Active and IsValid( ply ) then + local parent = self.Parent + local HasParent = IsValid( parent ) + + local pos, ang = self.Position, self.Angle + + local curpos = pos + local curang = ang + + if self.AutoMove then + curang = ply:EyeAngles() + local veh = ply:GetVehicle() + if IsValid( veh ) then curang = veh:WorldToLocalAngles( curang ) end + + local dist = self.Distance + + if HasParent and IsValid( parent ) then + if self.LocalMove then + curpos = parent:LocalToWorld( curpos - curang:Forward() * dist ) + curang = parent:LocalToWorldAngles( curang ) + else + curpos = parent:LocalToWorld( curpos ) - curang:Forward() * dist + end + else + curpos = curpos - curang:Forward() * dist + end + else + if HasParent then + curpos = parent:LocalToWorld( curpos ) + curang = parent:LocalToWorldAngles( curang ) + end + end + + -- AutoUnclip + if self.AutoUnclip then + local start, endpos + + if not self.AutoMove then + if HasParent then + start = parent:GetPos() + else + start = self:GetPos() + end + + endpos = curpos + else + if HasParent then + start = parent:LocalToWorld(pos) + else + start = pos + end + + endpos = curpos + end + + local tr = { + start = start, + endpos = endpos, + mask = (self.AutoUnclip_IgnoreWater and CONTENTS_SOLID or bit.bor(MASK_WATER, CONTENTS_SOLID)), + mins = Vector(-8,-8,-8), + maxs = Vector(8,8,8) + } + + local trace = util.TraceHull( tr ) + + if trace.Hit then + curpos = trace.HitPos + end + end + + local trace = util.TraceLine({start=curpos,endpos=curpos+curang:Forward()*999999999,filter=self.Entities}) + trace.RealStartPos = curpos + local hitPos = trace.HitPos or Vector(0,0,0) + + if self.OldDupe then + WireLib.TriggerOutput(self, "X", hitPos.x) + WireLib.TriggerOutput(self, "Y", hitPos.y) + WireLib.TriggerOutput(self, "Z", hitPos.z) + end + + WireLib.TriggerOutput(self,"HitPos",hitPos) + WireLib.TriggerOutput(self,"CamPos",curpos) + WireLib.TriggerOutput(self,"CamDir",curang:Forward()) + WireLib.TriggerOutput(self,"CamAng",curang) + WireLib.TriggerOutput(self,"Trace",trace) + else + if self.OldDupe then + WireLib.TriggerOutput(self, "X", 0) + WireLib.TriggerOutput(self, "Y", 0) + WireLib.TriggerOutput(self, "Z", 0) + end + + WireLib.TriggerOutput(self,"HitPos", Vector(0,0,0)) + WireLib.TriggerOutput(self,"CamPos",Vector(0,0,0)) + WireLib.TriggerOutput(self,"CamDir",Vector(0,0,0)) + WireLib.TriggerOutput(self,"CamAng",Angle(0,0,0)) + WireLib.TriggerOutput(self,"Trace",nil) + end +end + +-------------------------------------------------- +-- Think +-------------------------------------------------- +function ENT:Think() + BaseClass.Think(self) + + if self.NeedsSyncSettings then + self.NeedsSyncSettings = nil + self:SyncSettings() + end + if self.NeedsSyncPositions then + self.NeedsSyncPositions = nil + self:SyncPositions() + end + + self:GetContraption() + self:UpdateOutputs() + + self:NextThink(CurTime()) + return true +end + +-------------------------------------------------- +-- PVS Hook +-------------------------------------------------- + +hook.Add("SetupPlayerVisibility", "gmod_wire_cameracontroller", function(player) + local cam = player.CamController + if IsValid(cam) then + local pos = cam.Position + if IsValid( cam.Parent ) then pos = cam.Parent:LocalToWorld(pos) end + AddOriginToPVS(pos) + end +end) + +-------------------------------------------------- +-- OnRemove +-------------------------------------------------- + +function ENT:OnRemove() + if IsValid( self.Parent ) then + self.Parent:RemoveCallOnRemove( "wire_camera_controller_remove_parent" ) + end + + self:ClearEntities() +end + +-------------------------------------------------- +-- DisableCam +-------------------------------------------------- + +function ENT:DisableCam( ply ) + if #self.Vehicles == 0 and not ply then -- if the cam controller isn't linked, it controls the owner's view + ply = self:GetPlayer() + end + + self:SetFOV( ply, false ) + self:SetFLIR( ply, false ) + + self:SyncSettings( ply, false ) + + if IsValid( ply ) then + for i=1,#self.Players do + if self.Players[i] == ply then + table.remove( self.Players, i ) + break + end + end + + ply.CamController = nil + else + self.Players = {} + end + + if #self.Players == 0 then + WireLib.TriggerOutput(self, "On", 0) + self.Active = false + self:ColorByLinkStatus(self.LINK_STATUS_LINKED) + end +end + +-------------------------------------------------- +-- EnableCam +-------------------------------------------------- + +function ENT:EnableCam( ply ) + -- if we're in the middle of being pasted, then there may be linked vehicles + -- that we don't know about yet so we just ignore the call. See wiremod/wire#1062 + if self.DuplicationInProgress then return end + + if #self.Vehicles == 0 and not ply then -- if the cam controller isn't linked, it controls the owner's view + ply = self:GetPlayer() + end + + if IsValid( ply ) then + for i=1,#self.Players do + if self.Players[i] == ply then return end -- check if this player is already active + end + + self.Players[#self.Players+1] = ply + ply.CamController = self + + self:SetFOV( ply ) + self:SetFLIR( ply ) + + WireLib.TriggerOutput(self, "On", 1) + self.Active = true + + self:ColorByLinkStatus(self.LINK_STATUS_ACTIVE) + + self:SyncSettings( ply ) + else -- No player specified, activate cam for everyone not already active + local lookup = {} + for i=1,#self.Players do lookup[self.Players[i]] = true end + + for i=#self.Vehicles,1,-1 do + local veh = self.Vehicles[i] + if IsValid( veh ) then + local ply = veh:GetDriver() + if IsValid( ply ) then + if not lookup[ply] then + self:EnableCam( ply ) + end + end + else + self:UnlinkEnt( veh ) + end + end + end +end + +-------------------------------------------------- +-- SetFOV +-------------------------------------------------- + +function ENT:SetFOV( ply, b ) + if b == nil and self.FOV ~= nil then b = true end + if self.FOV == 0 then b = false end + + if IsValid( ply ) then + if b then + if not ply.Wire_Cam_DefaultFOV then + ply.Wire_Cam_DefaultFOV = ply:GetFOV() + end + + if ply:GetFOV() ~= self.FOV then + ply:SetFOV( self.FOV, 0 ) + end + elseif ply.Wire_Cam_DefaultFOV then + if ply:GetFOV() ~= ply.Wire_Cam_DefaultFOV then + ply:SetFOV( ply.Wire_Cam_DefaultFOV, 0 ) + end + ply.Wire_Cam_DefaultFOV = nil + end + else + for i=#self.Players,1,-1 do + local ply = self.Players[i] + if IsValid(ply) then + self:SetFOV( ply, b ) + else + table.remove( self.Players, i ) + end + end + end +end + +-------------------------------------------------- +-- SetFLIR +-------------------------------------------------- + +function ENT:SetFLIR( ply, b ) + if b == nil then b = self.FLIR end + + if IsValid( ply ) then + if b then + FLIR.start( ply ) + else + FLIR.stop( ply ) + end + else + for i=#self.Players,1,-1 do + local ply = self.Players[i] + if IsValid(ply) then + self:SetFLIR( ply, b ) + else + table.remove( self.Players, i ) + end + end + end +end + +-------------------------------------------------- +-- LocalizePositions +-------------------------------------------------- + +function ENT:LocalizePositions(b) + if self.ParentLocal then return end + -- Localize the position if we have a parent + if IsValid( self.Parent ) then + local parent = self.Parent + if b then + self.Position = parent:WorldToLocal( self.Position ) + self.Angle = parent:WorldToLocalAngles( self.Angle ) + else + self.Position = parent:LocalToWorld( self.Position ) + self.Angle = parent:LocalToWorldAngles( self.Angle ) + end + end +end + +-------------------------------------------------- +-- TriggerInput +-------------------------------------------------- + +function ENT:TriggerInput( name, value ) + if name == "Activated" then + self.Activated = value ~= 0 + if value ~= 0 then self:EnableCam() else self:DisableCam() end + self:UpdateOverlay() + elseif name == "Zoom" or name == "FOV" then + self.FOV = math.Clamp( value, 0, 90 ) + if not self.Activated then return end + self:SetFOV() + elseif name == "FLIR" then + self.FLIR = value ~= 0 + if not self.Activated then return end + self:SetFLIR() + else + self:LocalizePositions(false) + + if name == "Parent" then + self.Parent = value + elseif name == "Position" then + self.Position = value + elseif name == "Distance" then + self.Distance = value + elseif name == "UnRoll" then + self.UnRoll = tobool(value) + elseif name == "Direction" then + self.Angle = value:Angle() + elseif name == "Angle" then + self.Angle = value + elseif name == "X" then + self.Position.x = value + elseif name == "Y" then + self.Position.y = value + elseif name == "Z" then + self.Position.z = value + elseif name == "Pitch" then + self.Angle.p = value + elseif name == "Yaw" then + self.Angle.y = value + elseif name == "Roll" then + self.Angle.r = value + end + + self:LocalizePositions(true) + self.NeedsSyncPositions = true + end +end + +-------------------------------------------------- +-- HiSpeed Access +-------------------------------------------------- + +local hispeed_ports = { + -- camera settings + [1] = "Activated", + [2] = "Parent", + [3] = "Zoom", + [4] = "FOV", + [5] = "FLIR", + + -- camera position + [6] = "X", + [7] = "Y", + [8] = "Z", + [9] = "Distance", + + -- camera angle (direction omitted as angle is the same thing) + [10] = "Pitch", + [11] = "Yaw", + [12] = "Roll", + [13] = "UnRoll", + + -- controller settings + [14] = "ParentLocal", + [15] = "AutoMove", + [16] = "FreeMove", + [17] = "LocalMove", + [18] = "AllowZoom", + [19] = "AutoUnclip", + [20] = "AutoUnclip_IgnoreWater", + [21] = "DrawPlayer", + [22] = "DrawParent" +} + +function ENT:WriteCell(address, value) + if not hispeed_ports[address] then return false end + + local key = hispeed_ports[address] + if address < 14 then + if address == 2 then value = Entity( value ) end -- special case: parent entity by entid + self:TriggerInput(key, value) + return true + else + value = tobool( value ) + if self[key] ~= value then + self[key] = value + self.NeedsSyncSettings = true + return true + end + end + + return false +end + +-------------------------------------------------- +-- Enter/exit vehicle hooks +-------------------------------------------------- + +hook.Add("PlayerEnteredVehicle", "gmod_wire_cameracontroller", function(player, vehicle) + if IsValid(vehicle.CamController) and vehicle.CamController.Activated then + vehicle.CamController:EnableCam( player ) + end +end) +hook.Add("PlayerLeaveVehicle", "gmod_wire_cameracontroller", function(player, vehicle) + if IsValid(vehicle.CamController) and vehicle.CamController.Activated then + vehicle.CamController:DisableCam( player ) + end +end) + +-------------------------------------------------- +-- Leave camera manually +-------------------------------------------------- +concommand.Add( "wire_cameracontroller_leave", function(player) + if IsValid(player.CamController) then + player.CamController:DisableCam( player ) + end +end) + +-------------------------------------------------- +-- Linking to vehicles +-------------------------------------------------- + +function ENT:UpdateMarks() + WireLib.SendMarks(self,self.Vehicles) +end + +function ENT:ClearEntities() + self:DisableCam() + + for i=1,#self.Vehicles do + self.Vehicles[i]:RemoveCallOnRemove( "wire_camera_controller_remove_pod" ) + self.Vehicles[i].CamController = nil + end + + self.Vehicles = {} + self:UpdateMarks() + return true +end + +function ENT:LinkEnt(pod) + pod = WireLib.GetClosestRealVehicle(pod,self:GetPos(),self:GetPlayer()) + + if not IsValid(pod) or not pod:IsVehicle() then return false, "Must link to a vehicle" end + for i=1,#self.Vehicles do + if self.Vehicles[i] == pod then + return false + end + end + + pod.CamController = self + pod:CallOnRemove( "wire_camera_controller_remove_pod", function() + self:UnlinkEnt( pod ) + end) + + self.Vehicles[#self.Vehicles+1] = pod + self.Players = {} + + if not self.Active then + if #self.Vehicles > 0 then + self:ColorByLinkStatus(self.LINK_STATUS_LINKED) + else + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) + end + end + + if IsValid( pod:GetDriver() ) then + self:EnableCam( pod:GetDriver() ) + end + + self:UpdateMarks() + return true +end + +function ENT:UnlinkEnt(pod) + local idx = 0 + for i=1,#self.Vehicles do + if self.Vehicles[i] == pod then + idx = i + break + end + end + if idx == 0 then return false end + + if IsValid( pod:GetDriver() ) then + self:DisableCam( pod:GetDriver() ) + end + + pod:RemoveCallOnRemove( "wire_camera_controller_remove_pod" ) + table.remove( self.Vehicles, idx ) + pod.CamController = nil + + if not self.Active then + if #self.Vehicles > 0 then + self:ColorByLinkStatus(self.LINK_STATUS_LINKED) + else + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) + end + end + + self:UpdateMarks() + return true +end + +-------------------------------------------------- +-- Dupe support +-------------------------------------------------- + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) + local veh = {} + for i=1,#self.Vehicles do + veh[i] = self.Vehicles[i]:EntIndex() + end + info.Vehicles = veh + + info.OldDupe = self.OldDupe + + -- Other options are saved using duplicator.RegisterEntityClass + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if info.cam or info.pod or info.OldDupe then -- OLD DUPE DETECTED + if info.cam then + local CamEnt = GetEntByID( info.cam ) + if IsValid( CamEnt ) then CamEnt:Remove() end + end + + if info.pod then + self.Vehicles[1] = GetEntByID( info.pod ) + end + + WireLib.AdjustSpecialInputs( self, { "Activated", "X", "Y", "Z", "Pitch", "Yaw", "Roll", + "Angle [ANGLE]", "Position [VECTOR]", "Distance", "UnRoll", "Direction [VECTOR]", + "Parent [ENTITY]", "FLIR", "FOV" } ) + + WireLib.AdjustSpecialOutputs( self, { "On", "X", "Y", "Z", "HitPos [VECTOR]", + "CamPos [VECTOR]", "CamDir [VECTOR]", "CamAng [ANGLE]", + "Trace [RANGER]" } ) + + self.OldDupe = true + else + local veh = info.Vehicles + if veh then + for i=1,#veh do + self:LinkEnt( GetEntByID( veh[i] ) ) + end + end + end + + timer.Simple( 0.1, function() if IsValid( self ) then self:UpdateMarks() end end ) -- timers solve everything (the entity isn't valid on the client at first, so we wait a bit) +end + +WireLib.AddInputAlias( "Zoom", "FOV" ) +WireLib.AddOutputAlias( "XYZ", "HitPos" ) + +duplicator.RegisterEntityClass("gmod_wire_cameracontroller", WireLib.MakeWireEnt, "Data", "ParentLocal","AutoMove","FreeMove","LocalMove","AllowZoom","AutoUnclip","DrawPlayer","AutoUnclip_IgnoreWater","DrawParent") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_disk.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_disk.lua new file mode 100644 index 0000000..c3c675c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_disk.lua @@ -0,0 +1,105 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire CD Disk" +ENT.WireDebugName = "CD" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.DiskMemory = {} + self.Precision = 1 //1 unit + self.IRadius = 12 //units + + //Use Z axis for Sector address + //Use XY radius for Track address + //Use Z height for Stack address + self:Setup() +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + info.Precision = self.Precision + info.IRadius = self.IRadius + info.Skin = self:GetSkin() + info["DiskMemory"] = {} + + local dataptr = 0 + for k,v in pairs(self.DiskMemory) do + info["DiskMemory"][k] = dataptr + info["DiskData"..dataptr] = {} + for k2,v2 in pairs(self.DiskMemory[k]) do + info["DiskData"..dataptr][k2] = v2 + end + dataptr = dataptr + 1 + end + + return info +end + + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.Precision = info.Precision + self.IRadius = info.IRadius + self:SetSkin(info.Skin or 0) + self.DiskMemory = {} + + for k,v in pairs(info["DiskMemory"]) do + local dataptr = info["DiskMemory"][k] + self.DiskMemory[k] = {} + for k2,v2 in pairs(info["DiskData"..dataptr]) do + self.DiskMemory[k][k2] = v2 + end + end + + self:Setup() +end + +function ENT:Setup(precision, iradius, skin) + local min = self:OBBMins() + local max = self:OBBMaxs() + + if precision then self.Precision = math.floor(math.Clamp(precision,1,64)) end + if iradius then self.IRadius = math.max(iradius,0) end + if skin then self.Skin = skin self:SetSkin(skin) end + + self.StackStartHeight = -min.z + + self.DiskStacks = math.max(1,math.floor((max.z - min.z) / self.Precision)+1) + self.DiskTracks = math.floor(0.5*math.min(max.x - min.x,max.y - min.y) / self.Precision) + + self.DiskSectors = 0 + self.TrackSectors = {} + self.FirstTrack = math.floor((self.IRadius) / self.Precision) + for i=self.FirstTrack,self.DiskTracks-1 do + self.TrackSectors[i] = self.DiskSectors + self.DiskSectors = self.DiskSectors + math.floor(2*3.1415926*i) + 1 + end + + self.DiskVolume = self.DiskSectors*self.DiskStacks + self.BytesPerBlock = 512//*self.Precision + self.DiskSize = self.DiskSectors*self.BytesPerBlock + +// print("Precision: "..(self.Precision)) +// print("H: "..(max.z - min.z)) +// print("R: "..(0.5*((max.x - min.x)^2+(max.y - min.y)^2)^0.5)) +// print("Disk stacks: "..self.DiskStacks) +// print("Disk tracks: "..self.DiskTracks) +// print("Disk sectors total: "..self.DiskSectors) +// print("Disk volume "..self.DiskVolume) + + self:ShowOutput() +end + +function ENT:ShowOutput() + self:SetOverlayText("Effective size (per stack): "..self.DiskSize.." bytes ("..math.floor(self.DiskSize/1024).." kb)\n".. + "Tracks: "..self.DiskTracks.."\nSectors: "..self.DiskSectors.."\nStacks: "..self.DiskStacks) +end + +duplicator.RegisterEntityClass("gmod_wire_cd_disk", WireLib.MakeWireEnt, "Data", "Precision", "IRadius", "Skin") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_lock.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_lock.lua new file mode 100644 index 0000000..e81b2b0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_lock.lua @@ -0,0 +1,121 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire CD Lock" +ENT.WireDebugName = "CD Lock" + +if CLIENT then return end -- No more client + +--Time after losing one disk to search for another +local NEW_DISK_WAIT_TIME = 2 +local DISK_IN_SOCKET_CONSTRAINT_POWER = 5000 +local DISK_IN_ATTACH_RANGE = 16 + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.Const = nil + self.Disk = nil + self.DisableLinking = 0 + + self.Inputs = WireLib.CreateSpecialInputs(self, { "Disable" }, { "NORMAL" }) + self.Outputs = WireLib.CreateSpecialOutputs(self, { "Locked", "DiskEntity" }, { "NORMAL", "ENTITY" }) + + self:NextThink(CurTime() + 0.25) +end + +function ENT:TriggerInput(iname, value) + if (iname == "Disable") then + self.DisableLinking = value + if (value >= 1) and (self.Const) then + self.Const:Remove() + --self.NoCollideConst:Remove() + + self.Const = nil + self.Disk.Lock = nil + self.Disk = nil + --self.NoCollideConst = nil + + WireLib.TriggerOutput(self, "Locked", 0) + WireLib.TriggerOutput(self, "DiskEntity", nil) + self:NextThink(CurTime() + NEW_DISK_WAIT_TIME) + end + end +end + +function ENT:Think() + BaseClass.Think(self) + if self.DoNextThink then + self:NextThink( self.DoNextThink ) + self.DoNextThink = nil + return true + end + + if not IsValid(self.Disk) and self.DisableLinking < 1 then -- if we're not linked + -- Find entities near us + local lockCenter = self:LocalToWorld(Vector(0, 0, 0)) + local local_ents = ents.FindInSphere(lockCenter, DISK_IN_ATTACH_RANGE) + for key, disk in pairs(local_ents) do + -- If we find a disk, try to attach it to us + if (disk:IsValid() and disk:GetClass() == "gmod_wire_cd_disk") then + if (disk.Lock == nil) then + self:AttachDisk(disk) + end + end + end + else + self:NextThink(CurTime() + 1) + return true + end +end + +function ENT:AttachDisk(disk) + --Position disk + + local newpos = self:LocalToWorld(Vector(0, 0, 0)) + local lockAng = self:GetAngles() + disk:SetPos(newpos) + disk:SetAngles(lockAng) + + self.NoCollideConst = constraint.NoCollide(self, disk, 0, 0) + if (not self.NoCollideConst) then + WireLib.TriggerOutput(self, "Locked", 0) + WireLib.TriggerOutput(self, "DiskEntity", nil) + return + end + + --Constrain together + self.Const = constraint.Weld(self, disk, 0, 0, DISK_IN_SOCKET_CONSTRAINT_POWER, true) + if (not self.Const) then + self.NoCollideConst:Remove() + self.NoCollideConst = nil + WireLib.TriggerOutput(self, "Locked", 0) + WireLib.TriggerOutput(self, "DiskEntity", nil) + return + end + + self.Const:CallOnRemove("wire_cd_remove_on_weld",function() + self.Const = nil + self.Disk.Lock = nil + self.Disk = nil + self.NoCollideConst = nil + + WireLib.TriggerOutput(self, "Locked", 0) + WireLib.TriggerOutput(self, "DiskEntity", nil) + + self.DoNextThink = CurTime() + NEW_DISK_WAIT_TIME --Give time before next grabbing a disk. + end) + + --Prepare clearup incase one is removed + disk:DeleteOnRemove(self.Const) + self:DeleteOnRemove(self.Const) + self.Const:DeleteOnRemove(self.NoCollideConst) + + disk.Lock = self + self.Disk = disk + WireLib.TriggerOutput(self, "Locked", 1) + WireLib.TriggerOutput(self, "DiskEntity", disk) +end + +duplicator.RegisterEntityClass("gmod_wire_cd_lock", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_ray.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_ray.lua new file mode 100644 index 0000000..37de7a1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cd_ray.lua @@ -0,0 +1,275 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire CD Ray" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "CD Ray" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self, {"Write","Read","Value","Range"}) + self.Outputs = Wire_CreateOutputs(self, {"Data","Sector","LocalSector","Track","Stack","Address"}) + + self.Command = {} + self.Command[0] = 0 //[W] Write ray on + self.Command[1] = 0 //[W] Read ray on + self.Command[2] = 0 //[R] Current sector (global) + self.Command[3] = 0 //[R] Current sector (on track) + self.Command[4] = 0 //[R] Current track + self.Command[5] = 0 //[R] Current stack + self.Command[6] = 0 //[R] Current address (global) + self.Command[7] = 0 //[R] Current address (in current stack) + + self.Command[8] = 0 //[W] Buffer ready (read or write - pick the ray) + self.Command[9] = 0 //[W] Continious mode + self.Command[10] = 0 //[W] Wait for address mode + self.Command[11] = 0 //[W] Target address (in current stack) + self.Command[12] = 0 //[W] Wait for track§or mode + self.Command[13] = 0 //[W] Target sector + self.Command[14] = 0 //[W] Target track + + self.Command[21] = 0 //[R] Raw disk spin velocity + self.Command[22] = 0 //[R] Raw disk spin angle + self.Command[23] = 0 //[R] Raw distance from disk center + self.Command[24] = 0 //[R] Raw stack index + + self.Command[25] = 0 //[R] Disk precision (Inches Per Block) + self.Command[26] = 0 //[R] Disk sectors (total) + self.Command[27] = 0 //[R] Disk tracks (total) + self.Command[28] = 0 //[R] First track number + self.Command[29] = 0 //[R] Bytes per block + self.Command[30] = 0 //[R] Disk size (per stack) + self.Command[31] = 0 //[R] Disk volume (bytes total) + self.Command[32] = 0 //[R] Disk entity id + + self.WriteBuffer = {} + self.PrevDiskEnt = nil + + self:SetBeamLength(64) +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if (Address >= 0) && (Address < 512) then + if (self.Command[Address]) then + return self.Command[Address] + else + return 0 + end + end + if (Address >= 512) && (Address < 1024) then + if (self.WriteBuffer[Address-512]) then + return self.WriteBuffer[Address-512] + else + return 0 + end + end + return nil +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + if (Address >= 0) && (Address < 512) then + self.Command[Address] = value + if (Address == 8) then + self:DoJob() + end + return true + end + if (Address >= 512) && (Address < 1024) then + if (value ~= 0) then + self.WriteBuffer[Address-512] = value + else + self.WriteBuffer[Address-512] = nil + end + return true + end + return false +end + +function ENT:Setup(Range,DefaultZero) + self.DefaultZero = DefaultZero + if Range then self:SetBeamLength(Range) end +end + +function ENT:TriggerInput(iname, value) + if (iname == "Write") then + self.Command[0] = value + self.Command[8] = 1 + self.Command[9] = 1 + elseif (iname == "Read") then + self.Command[1] = value + self.Command[8] = 1 + self.Command[9] = 1 + elseif (iname == "Value") then + self.Command[8] = 1 + self.Command[9] = 1 + self.WriteBuffer[0] = value + elseif(iname == "Range")then + self:SetBeamLength(math.Clamp(value,0,32000)) + end +end + +function ENT:DoJob() + if (not self.Disk) then return end + local disk = self.Disk + if (self.Command[8] ~= 0) then + local dojob = true + if (self.Command[10] ~= 0) then + if (self.Command[11] ~= self.Command[7]) then dojob = false end + end + if (self.Command[12] ~= 0) then + if (self.Command[13] ~= self.Command[3]) || (self.Command[14] ~= self.Command[4]) then + dojob = false + end + end + + if dojob then + if (self.Command[9] == 0) then self.Command[8] = 0 end + local sector_addr = self.Sector.."."..self.Track.."."..self.Stack//{s=sector,t=track,st=stack} + if (self.Command[0] ~= 0) then //write ray + disk.DiskMemory[sector_addr] = table.Copy(self.WriteBuffer) + else //read ray + self.WriteBuffer = table.Copy(disk.DiskMemory[sector_addr]) or { [0] = 0 } + end + end + end +end + +function ENT:Think() + local vStart = self:GetPos() + local vForward = self:GetUp() + + local trace = {} + trace.start = vStart + trace.endpos = vStart + (vForward * self:GetBeamLength()) + trace.filter = { self } + local trace = util.TraceLine( trace ) + + if ((self.Command[0] ~= 0) or (self.Command[1] ~= 0)) then + if (self.Command[0] == 1) then --write ray (blue) + self:SetColor(Color(0, 0, 255, 255)) + else --read ray (red) + self:SetColor(Color(255, 0, 0, 255)) + end + else + self:SetColor(Color(255, 255, 255, 255)) + end + + if ((trace.Entity) and + (trace.Entity:IsValid()) and + (trace.Entity:GetClass() == "gmod_wire_cd_disk")) then + local pos = trace.HitPos + local disk = trace.Entity + local lpos = disk:WorldToLocal(pos) + + local vel = disk.Entity:GetPhysicsObject():GetAngleVelocity().z + + local r = (lpos.x^2+lpos.y^2)^0.5 //radius + local a = math.fmod(3.1415926+math.atan2(lpos.x,lpos.y),2*3.1415926) //angle + local h = lpos.z-disk.StackStartHeight //stack + + local track = math.floor(r / disk.Precision) + local sector = math.floor(a*track)//*disk.Precision) + local stack = math.floor(h/disk.Precision) + if (disk.DiskStacks == 1) then stack = 0 end + + if (self.PrevDiskEnt ~= disk) then + self.PrevDiskEnt = disk + + self.Command[25] = disk.Precision + self.Command[26] = disk.DiskSectors + self.Command[27] = disk.DiskTracks + self.Command[28] = disk.FirstTrack + self.Command[29] = disk.BytesPerBlock + self.Command[30] = disk.DiskSize + self.Command[31] = disk.DiskVolume + self.Command[32] = disk.Entity:EntIndex() + end + + if ((track >= disk.FirstTrack) and (stack >= 0) and (sector >= 0) and + //(track < disk.DiskTracks) and + (stack < disk.DiskStacks)) then + self.Command[21] = vel //[R] Raw disk spin velocity + self.Command[22] = a //[R] Raw disk spin angle + self.Command[23] = r //[R] Raw distance from disk center + self.Command[24] = h //[R] Raw stack index + + if (not disk.TrackSectors[track]) then disk.TrackSectors[track] = 0 end + + self.Command[2] = disk.DiskSectors*stack+disk.TrackSectors[track]+sector //[R] Current sector (global) + self.Command[3] = sector //[R] Current sector (on track) + self.Command[4] = track //[R] Current track + self.Command[5] = stack //[R] Current stack + self.Command[6] = self.Command[2]*disk.BytesPerBlock //[R] Current address (global) + self.Command[7] = (disk.TrackSectors[track]+sector)*disk.BytesPerBlock //[R] Current address (in current stack) + + if ((self.Command[0] ~= 0) or (self.Command[1] ~= 0)) then + self.Sector = sector + self.Track = track + self.Stack = stack + self.Disk = disk + self:DoJob() + end + else + self.Command[21] = 0 + self.Command[22] = 0 + self.Command[23] = 0 + self.Command[24] = 0 + + self.Command[2] = 0 + self.Command[3] = 0 + self.Command[4] = 0 + self.Command[5] = 0 + self.Command[6] = 0 + self.Command[7] = 0 + end + else + self.PrevDiskEnt = nil + self.Disk = nil + + self.Command[2] = 0 + self.Command[3] = 0 + self.Command[4] = 0 + self.Command[5] = 0 + self.Command[6] = 0 + self.Command[7] = 0 + + self.Command[21] = 0 + self.Command[22] = 0 + self.Command[23] = 0 + self.Command[24] = 0 + self.Command[25] = 0 + self.Command[26] = 0 + self.Command[27] = 0 + self.Command[28] = 0 + self.Command[29] = 0 + self.Command[30] = 0 + self.Command[31] = 0 + self.Command[32] = 0 + end + + //Update output + if (self.WriteBuffer[0]) then + Wire_TriggerOutput(self, "Data",self.WriteBuffer[0]) + else + Wire_TriggerOutput(self, "Data",0) + end + Wire_TriggerOutput(self, "Sector", self.Command[2]) + Wire_TriggerOutput(self, "LocalSector", self.Command[3]) + Wire_TriggerOutput(self, "Track", self.Command[4]) + Wire_TriggerOutput(self, "Stack", self.Command[5]) + Wire_TriggerOutput(self, "Address", self.Command[6]) + + self:NextThink(CurTime()+0.01) + return true +end + +duplicator.RegisterEntityClass("gmod_wire_cd_ray", WireLib.MakeWireEnt, "Data", "Range", "DefaultZero") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_clutch.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_clutch.lua new file mode 100644 index 0000000..26ae096 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_clutch.lua @@ -0,0 +1,285 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Clutch" +ENT.Purpose = "Allows rotational friction to be varied dynamically" +ENT.WireDebugName = "Clutch" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs( self, { "Friction" } ) + --self.Outputs = Wire_CreateOutputs( self, { "Welded" } ) + + self.clutch_friction = 0 + self.clutch_ballsockets = {} -- Table of constraints as keys + + self:UpdateOverlay() +end + +function ENT:UpdateOverlay() + local text = "Friction: " .. tostring( self.clutch_friction ) .. "\n" + + local num_constraints = table.Count( self.clutch_ballsockets ) + if num_constraints > 0 then + text = text .. "Links: " .. tostring( num_constraints ) + else + text = text .. "Unlinked" + end + + self:SetOverlayText( text ) +end + + +--[[------------------------------------------------------- + -- Constraint functions -- + Functions for handling clutch constraints +---------------------------------------------------------]] + +function ENT:ClutchExists( Ent1, Ent2 ) + for k, _ in pairs( self.clutch_ballsockets ) do + if ( Ent1 == k.Ent1 and Ent2 == k.Ent2 ) or + ( Ent1 == k.Ent2 and Ent2 == k.Ent1 ) then + return true + end + end + + return false +end + + +-- Returns an array with each entry as a table containing Ent1, Ent2 +function ENT:GetConstrainedPairs() + local ConstrainedPairs = {} + for k, _ in pairs( self.clutch_ballsockets ) do + if IsValid( k ) then + table.insert( ConstrainedPairs, {Ent1 = k.Ent1, Ent2 = k.Ent2} ) + else + self.clutch_ballsockets[k] = nil + end + end + return ConstrainedPairs +end + + +local function NewBallSocket( Ent1, Ent2, friction ) + if not IsValid( Ent1 ) or not IsValid( Ent2 ) then return false end + + local ballsocket = constraint.AdvBallsocket( Ent1, Ent2, 0, 0, + Vector(0,0,0), Vector(0,0,0), 0, 0, + -180, -180, -180, 180, 180, 180, + friction, friction, friction, 1, 0 ) + + if ballsocket then + -- Prevent ball socket from being affected by dupe/remove functions + ballsocket.Type = "" + end + + return ballsocket +end + + +-- Register a new clutch association with the controller +function ENT:AddClutch( Ent1, Ent2, friction ) + local ballsocket = NewBallSocket( Ent1, Ent2, friction or self.clutch_friction ) + + if ballsocket then + self.clutch_ballsockets[ballsocket] = true + ballsocket:CallOnRemove( "WireClutchRemove", function() + if self.clutch_ballsockets[ballsocket] then + -- The table value is still true so something unknown killed the ballsocket + -- Set the table so that nothing else runs into issues + self.clutch_ballsockets[ballsocket] = nil + self:UpdateOverlay() + -- Wait a frame so nothing bad happens, then rebuild it + timer.Simple(0, function() + if self:IsValid() then + self:AddClutch( Ent1, Ent2, friction ) + end + end) + end + end) + end + + self:UpdateOverlay() + return ballsocket +end + + +-- Remove a new clutch association from the controller +function ENT:RemoveClutch( const ) + self.clutch_ballsockets[const] = nil + + if IsValid( const ) then + const:Remove() + end + + self:UpdateOverlay() +end + + +function ENT:SetClutchFriction( const, friction ) + -- There seems to be no direct way to edit constraint friction, so we must create a new ball socket constraint + self.clutch_ballsockets[const] = nil + + if IsValid( const ) then + local Ent1 = const.Ent1 + local Ent2 = const.Ent2 + + const:Remove() + + local newconst = NewBallSocket( Ent1, Ent2, friction ) + if newconst then + self.clutch_ballsockets[newconst] = true + end + + else + print("Wire Clutch: Attempted to set friction on invalid constraint") + end + + return true +end + + +function ENT:OnRemove() + + for k, v in pairs( self.clutch_ballsockets ) do + + self:RemoveClutch( k ) + + end + +end + + +--[[------------------------------------------------------- + -- Main controller functions -- + Handle controller tables, wire input +---------------------------------------------------------]] +-- Used for setting/restoring entity mass when creating the clutch constraint +local function SaveMass( MassTable, ent ) + if IsValid( ent ) and !MassTable[ent] then + local Phys = ent:GetPhysicsObject() + if IsValid( Phys ) then + MassTable[ent] = Phys:GetMass() + Phys:SetMass(1) + end + end +end + +local function RestoreMass( MassTable ) + for k, v in pairs( MassTable ) do + k:GetPhysicsObject():SetMass( v ) + end +end + + +-- Set friction on all constrained ents, called by input or timer (if delayed) +function ENT:UpdateFriction() + -- Set masses to 1 - this will prevents friction from varying depending on mass + local MassTable = {} + + -- Create a table copy so when we start ammending self.clutch_ballsockets, it won't affect this loop + local clutch_ballsockets = table.Copy( self.clutch_ballsockets ) + + -- Update all registered ball socket constraints + local numconstraints = 0 -- Used to calculate the delay between inputs + + for k, _ in pairs( clutch_ballsockets ) do + if not IsValid( k ) then + self:RemoveClutch( k ) + + else + SaveMass( MassTable, k.Ent1 ) + SaveMass( MassTable, k.Ent2 ) + + self:SetClutchFriction( k, self.clutch_friction ) + numconstraints = numconstraints + 1 + + end + end + + RestoreMass( MassTable ) + self:UpdateOverlay() + + return numconstraints +end + + +-- Called when the clutch input delay timer finishes +local function ClutchDelayEnd( ent ) + ent.ClutchDelay = nil + + if ent.delayed_clutch_friction then + ent:TriggerInput( "Friction", ent.delayed_clutch_friction ) + ent.delayed_clutch_friction = nil + end +end + + +function ENT:TriggerInput( iname, value ) + if iname == "Friction" then + if !self.ClutchDelay then + self.clutch_friction = value + + -- Create a delay to avoid server lag + local numconstraints = self:UpdateFriction() + local maxrate = math.max( GetConVarNumber( "wire_clutch_maxrate", 20 ), 1 ) + local Delay = numconstraints / maxrate + + self.ClutchDelay = true + timer.Create( "wire_clutch_delay_" .. tostring(self:EntIndex()), Delay, 0, function() ClutchDelayEnd(self) end ) + + else + -- This should only happen if an error prevents the ClutchDelayEnd function from being called + if !timer.Exists( "wire_clutch_delay_" .. tostring(self:EntIndex())) then + self.ClutchDelay = false + end + + -- Store new friction value so it can be updated after the delay + self.delayed_clutch_friction = value + + end + end +end + + + +--[[------------------------------------------------------- + -- Adv Duplicator Support -- + Linked entities are stored and recalled by their EntIndexes +---------------------------------------------------------]] +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + info.constrained_pairs = {} + + for k, v in pairs( self:GetConstrainedPairs() ) do + info.constrained_pairs[k] = {} + info.constrained_pairs[k].Ent1 = v.Ent1:EntIndex() or 0 + info.constrained_pairs[k].Ent2 = v.Ent2:EntIndex() or 0 + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + local Ent1, Ent2 + for _, v in pairs( info.constrained_pairs ) do + Ent1 = GetEntByID(v.Ent1) + Ent2 = GetEntByID(v.Ent2, game.GetWorld()) + + if IsValid(Ent1) and + Ent1 ~= Ent2 and + hook.Run( "CanTool", ply, WireLib.dummytrace(Ent1), "ballsocket_adv" ) and + hook.Run( "CanTool", ply, WireLib.dummytrace(Ent2), "ballsocket_adv" ) then + self:AddClutch( Ent1, Ent2 ) + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_clutch", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_colorer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_colorer.lua new file mode 100644 index 0000000..eaefe17 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_colorer.lua @@ -0,0 +1,185 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Colorer" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Colorer" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) +end + +if CLIENT then + + local color_box_size = 64 + function ENT:GetWorldTipBodySize() + -- "Input color:" text + local w_total,h_total = surface.GetTextSize( "Input color:\n255,255,255,255" ) + + -- Color box width + w_total = math.max(w_total,color_box_size) + + -- "Target color:" text + local w,h = surface.GetTextSize( "Target color:\n255,255,255,255" ) + w_total = w_total + 18 + math.max(w,color_box_size) + h_total = math.max(h_total, h) + + -- Color box height + h_total = h_total + 18 + color_box_size + 18/2 + + return w_total, h_total + end + + local white = Color(255,255,255,255) + local black = Color(0,0,0,255) + + local function drawColorBox( color, x, y ) + surface.SetDrawColor( color ) + surface.DrawRect( x, y, color_box_size, color_box_size ) + + local size = color_box_size + + surface.SetDrawColor( black ) + surface.DrawLine( x, y, x + size, y ) + surface.DrawLine( x + size, y, x + size, y + size ) + surface.DrawLine( x + size, y + size, x, y + size ) + surface.DrawLine( x, y + size, x, y ) + end + + function ENT:DrawWorldTipBody( pos ) + -- get colors + local data = self:GetOverlayData() + local inColor = Color(data.r or 255,data.g or 255,data.b or 255,data.a or 255) + + local trace = util.TraceLine( { start = self:GetPos(), endpos = self:GetPos() + self:GetUp() * self:GetBeamLength(), filter = {self} } ) + + local targetColor = Color(255,255,255,255) + if IsValid( trace.Entity ) then + targetColor = trace.Entity:GetColor() + end + + -- "Input color" text + local color_text = string.format("Input color:\n%d,%d,%d,%d",inColor.r,inColor.g,inColor.b,inColor.a) + + local w,h = surface.GetTextSize( color_text ) + draw.DrawText( color_text, "GModWorldtip", pos.min.x + pos.edgesize + w/2, pos.min.y + pos.edgesize, white, TEXT_ALIGN_CENTER ) + + -- "Target color" text + local color_text = string.format("Target color:\n%d,%d,%d,%d",targetColor.r,targetColor.g,targetColor.b,targetColor.a) + local w2,h2 = surface.GetTextSize( color_text ) + draw.DrawText( color_text, "GModWorldtip", pos.max.x - w/2 - pos.edgesize, pos.min.y + pos.edgesize, white, TEXT_ALIGN_CENTER ) + + local h = math.max(h,h2) + + -- Input color box + local x = pos.min.x + pos.edgesize + w/2 - color_box_size/2 + local y = pos.min.y + pos.edgesize * 1.5 + h + drawColorBox( inColor, x, y ) + + -- Target color box + + local x = pos.max.x - pos.edgesize - w/2 - color_box_size/2 + local y = pos.min.y + pos.edgesize * 1.5 + h + drawColorBox( targetColor, x, y ) + + end + + + return -- No more client +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = WireLib.CreateSpecialInputs(self, { "Fire", "R", "G", "B", "A", "RGB" }, {"NORMAL", "NORMAL", "NORMAL", "NORMAL", "NORMAL", "VECTOR"}) + self.Outputs = WireLib.CreateOutputs( self, {"Out"} ) + self.InColor = Color(255, 255, 255, 255) + self:SetBeamLength(2048) + self:ShowOutput() +end + +function ENT:Setup(outColor,Range) + if(outColor)then + self.outColor = outColor + WireLib.AdjustOutputs(self, {"R","G","B","A"}) + else + WireLib.AdjustOutputs(self, {"Out"}) + end + + if Range then self:SetBeamLength(Range) end + self:ShowOutput() +end + +function ENT:TriggerInput(iname, value) + if iname == "Fire" and value ~= 0 then + local vStart = self:GetPos() + local vForward = self:GetUp() + + local trace = util.TraceLine { + start = vStart, + endpos = vStart + (vForward * self:GetBeamLength()), + filter = { self } + } + if not IsValid(trace.Entity) then return end + if not hook.Run( "CanTool", self:GetPlayer(), trace, "colour" ) then return end + + if trace.Entity:IsPlayer() then + trace.Entity:SetColor(Color(self.InColor.r, self.InColor.g, self.InColor.b, 255)) + else + WireLib.SetColor(trace.Entity, Color(self.InColor.r, self.InColor.g, self.InColor.b, self.InColor.a)) + end + elseif iname == "R" then + self.InColor.r = math.Clamp(value, 0, 255) + self:ShowOutput() + elseif iname == "G" then + self.InColor.g = math.Clamp(value, 0, 255) + self:ShowOutput() + elseif iname == "B" then + self.InColor.b = math.Clamp(value, 0, 255) + self:ShowOutput() + elseif iname == "A" then + self.InColor.a = math.Clamp(value, 0, 255) + self:ShowOutput() + elseif iname == "RGB" then + self.InColor = Color( value.x, value.y, value.z, self.InColor.a ) + self:ShowOutput() + end +end + +function ENT:ShowOutput() + self:SetOverlayData( { r = self.InColor.r, + g = self.InColor.g, + b = self.InColor.b, + a = self.InColor.a } ) +end + +function ENT:Think() + BaseClass.Think(self) + if self.outColor then + local vStart = self:GetPos() + local vForward = self:GetUp() + + local trace = {} + trace.start = vStart + trace.endpos = vStart + (vForward * self:GetBeamLength()) + trace.filter = { self } + local trace = util.TraceLine( trace ) + + if !IsValid( trace.Entity ) then + WireLib.TriggerOutput( self, "R", 255 ) + WireLib.TriggerOutput( self, "G", 255 ) + WireLib.TriggerOutput( self, "B", 255 ) + WireLib.TriggerOutput( self, "A", 255 ) + else + local c = trace.Entity:GetColor() + WireLib.TriggerOutput( self, "R", c.r ) + WireLib.TriggerOutput( self, "G", c.g ) + WireLib.TriggerOutput( self, "B", c.b ) + WireLib.TriggerOutput( self, "A", c.a ) + end + end + self:NextThink(CurTime() + 0.1) + return true +end + +duplicator.RegisterEntityClass("gmod_wire_colorer", WireLib.MakeWireEnt, "Data", "outColor", "Range") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/cl_init.lua new file mode 100644 index 0000000..b4769bc --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/cl_init.lua @@ -0,0 +1,470 @@ +include("shared.lua") + +function ENT:Initialize() + self.Memory1 = {} + self.Memory2 = {} + for i = 0, 2047 do + self.Memory1[i] = 0 + end + + -- Caching control: + -- [2020] - Force cache refresh + -- [2021] - Cached blocks size (up to 28, 0 if disabled) + -- + -- Hardware image control: + -- [2019] - Clear viewport defined by 2031-2034 + -- [2022] - Screen ratio (read only) + -- [2023] - Hardware scale + -- [2024] - Rotation (0 - 0*, 1 - 90*, 2 - 180*, 3 - 270*) + -- [2025] - Brightness White + -- [2026] - Brightness B + -- [2027] - Brightness G + -- [2028] - Brightness R + -- [2029] - Vertical scale (1) + -- [2030] - Horizontal scale (1) + -- + -- Shifting control: + -- [2031] - Low shift column + -- [2032] - High shift column + -- [2033] - Low shift row + -- [2034] - High shift row + -- + -- Character output control: + -- [2035] - Charset, always 0 + -- [2036] - Brightness (additive) + -- + -- Control registers: + -- [2037] - Shift cells (number of cells, >0 right, <0 left) + -- [2038] - Shift rows (number of rows, >0 shift up, <0 shift down) + -- [2039] - Hardware Clear Row (Writing clears row) + -- [2040] - Hardware Clear Column (Writing clears column) + -- [2041] - Hardware Clear Screen + -- [2042] - Hardware Background Color (000) + -- + -- Cursor control: + -- [2043] - Cursor Blink Rate (0.50) + -- [2044] - Cursor Size (0.25) + -- [2045] - Cursor Address + -- [2046] - Cursor Enabled + -- + -- [2047] - Clk + + self.Memory1[2022] = 3/4 + self.Memory1[2023] = 0 + self.Memory1[2024] = 0 + self.Memory1[2025] = 1 + self.Memory1[2026] = 1 + self.Memory1[2027] = 1 + self.Memory1[2028] = 1 + self.Memory1[2029] = 1 + self.Memory1[2030] = 1 + self.Memory1[2031] = 0 + self.Memory1[2032] = 29 + self.Memory1[2033] = 0 + self.Memory1[2034] = 17 + self.Memory1[2035] = 0 + self.Memory1[2036] = 0 + + self.Memory1[2042] = 0 + self.Memory1[2043] = 0.5 + self.Memory1[2044] = 0.25 + self.Memory1[2045] = 0 + self.Memory1[2046] = 0 + + for i = 0, 2047 do + self.Memory2[i] = self.Memory1[i] + end + + self.LastClk = false + + self.PrevTime = CurTime() + self.IntTimer = 0 + + self.NeedRefresh = true + self.Flash = false + self.FrameNeedsFlash = false + + self.FramesSinceRedraw = 0 + self.NewClk = true + + self.GPU = WireGPU(self) + + -- Setup caching + GPULib.ClientCacheCallback(self,function(Address,Value) + self:WriteCell(Address,Value) + end) + + WireLib.netRegister(self) +end + +function ENT:OnRemove() + self.GPU:Finalize() + self.NeedRefresh = true +end + +function ENT:ReadCell(Address,value) + Address = math.floor(Address) + if Address < 0 then return nil end + if Address >= 2048 then return nil end + + return self.Memory2[Address] +end + +function ENT:WriteCell(Address,value) + Address = math.floor(Address) + if Address < 0 then return false end + if Address >= 2048 then return false end + + if Address == 2047 then self.NewClk = value ~= 0 end + + if self.NewClk then + self.Memory1[Address] = value -- Vis mem + self.NeedRefresh = true + end + self.Memory2[Address] = value -- Invis mem + + -- 2038 - Shift rows (number of rows, >0 shift down, <0 shift up) + -- 2039 - Hardware Clear Row (Writing clears row) + -- 2040 - Hardware Clear Column (Writing clears column) + -- 2041 - Hardware Clear Screen + + if (Address == 2025) or + (Address == 2026) or + (Address == 2027) or + (Address == 2028) or + (Address == 2036) then + self.NeedRefresh = true + end + + if Address == 2019 then + local low = math.floor(math.Clamp(self.Memory1[2033],0,17)) + local high = math.floor(math.Clamp(self.Memory1[2034],0,17)) + local lowc = math.floor(math.Clamp(self.Memory1[2031],0,29)) + local highc = math.floor(math.Clamp(self.Memory1[2032],0,29)) + for j = low, high do + for i = 2*lowc, 2*highc+1 do + self.Memory1[60*j+i] = 0 + self.Memory2[60*j+i] = 0 + end + end + self.NeedRefresh = true + end + if Address == 2037 then + local delta = math.floor(math.Clamp(math.abs(value),-30,30)) + local low = math.floor(math.Clamp(self.Memory1[2033],0,17)) + local high = math.floor(math.Clamp(self.Memory1[2034],0,17)) + local lowc = math.floor(math.Clamp(self.Memory1[2031],0,29)) + local highc = math.floor(math.Clamp(self.Memory1[2032],0,29)) + if (value > 0) then + for j = low,high do + for i = highc,lowc+delta,-1 do + if (self.NewClk) then + self.Memory1[j*60+i*2] = self.Memory1[j*60+(i-delta)*2] + self.Memory1[j*60+i*2+1] = self.Memory1[j*60+(i-delta)*2+1] + end + self.Memory2[j*60+i*2] = self.Memory2[j*60+(i-delta)*2] + self.Memory2[j*60+i*2+1] = self.Memory2[j*60+(i-delta)*2+1] + end + end + for j = low,high do + for i = lowc, lowc+delta-1 do + if (self.NewClk) then + self.Memory1[j*60+i*2] = 0 + self.Memory1[j*60+i*2+1] = 0 + end + self.Memory2[j*60+i*2] = 0 + self.Memory2[j*60+i*2+1] = 0 + end + end + else + for j = low,high do + for i = lowc,highc-delta do + if (self.NewClk) then + self.Memory1[j*60+i*2] = self.Memory1[j*60+i*2+delta*2] + self.Memory1[j*60+i*2+1] = self.Memory1[j*60+i*2+1+delta*2] + end + self.Memory2[j*60+i*2] = self.Memory2[j*60+i*2+delta*2] + self.Memory2[j*60+i*2+1] = self.Memory2[j*60+i*2+1+delta*2] + end + end + for j = low,high do + for i = highc-delta+1,highc do + if (self.NewClk) then + self.Memory1[j*60+i*2] = 0 + self.Memory1[j*60+i*2+1] = 0 + end + self.Memory2[j*60+i*2] = 0 + self.Memory2[j*60+i*2+1] = 0 + end + end + end + end + if Address == 2038 then + local delta = math.floor(math.Clamp(math.abs(value),-30,30)) + local low = math.floor(math.Clamp(self.Memory1[2033],0,17)) + local high = math.floor(math.Clamp(self.Memory1[2034],0,17)) + local lowc = math.floor(math.Clamp(self.Memory1[2031],0,29)) + local highc = math.floor(math.Clamp(self.Memory1[2032],0,29)) + if (value > 0) then + for j = low, high-delta do + for i = 2*lowc,2*highc+1 do + if (self.NewClk) then + self.Memory1[j*60+i] = self.Memory1[(j+delta)*60+i] + end + self.Memory2[j*60+i] = self.Memory2[(j+delta)*60+i] + end + end + for j = high-delta+1,high do + for i = 2*lowc, 2*highc+1 do + if (self.NewClk) then + self.Memory1[j*60+i] = 0 + end + self.Memory2[j*60+i] = 0 + end + end + else + for j = high,low+delta,-1 do + for i = 2*lowc, 2*highc+1 do + if (self.NewClk) then + self.Memory1[j*60+i] = self.Memory1[(j-delta)*60+i] + end + self.Memory2[j*60+i] = self.Memory2[(j-delta)*60+i] + end + end + for j = low,low+delta-1 do + for i = 2*lowc, 2*highc+1 do + if (self.NewClk) then + self.Memory1[j*60+i] = 0 + end + self.Memory2[j*60+i] = 0 + end + end + end + end + if Address == 2039 then + for i = 0, 59 do + self.Memory1[value*60+i] = 0 + self.Memory2[value*60+i] = 0 + end + self.NeedRefresh = true + end + if Address == 2040 then + for i = 0, 17 do + self.Memory1[i*60+value] = 0 + self.Memory2[i*60+value] = 0 + end + self.NeedRefresh = true + end + if Address == 2041 then + for i = 0, 18*30*2-1 do + self.Memory1[i] = 0 + self.Memory2[i] = 0 + end + self.NeedRefresh = true + end + + if self.LastClk ~= self.NewClk then + self.LastClk = self.NewClk + self.Memory1 = table.Copy(self.Memory2) -- swap the memory if clock changes + self.NeedRefresh = true + end + return true +end + +local specialCharacters = { + [128] = { + { x = 0, y = 1 }, + { x = 1, y = 1 }, + { x = 1, y = 0 }, + }, + [129] = { + { x = 0, y = 1 }, + { x = 0, y = 0 }, + { x = 1, y = 1 }, + }, + [130] = { + { x = 0, y = 1 }, + { x = 1, y = 0 }, + { x = 0, y = 0 }, + }, + [131] = { + { x = 0, y = 0 }, + { x = 1, y = 0 }, + { x = 1, y = 1 }, + }, +} + +function ENT:DrawSpecialCharacter(c,x,y,w,h,r,g,b) + surface.SetDrawColor(r,g,b,255) + surface.SetTexture(0) + + local vertices = specialCharacters[c] + if vertices then + local vertexData = { + { x = vertices[1].x*w+x, y = vertices[1].y*h+y }, + { x = vertices[2].x*w+x, y = vertices[2].y*h+y }, + { x = vertices[3].x*w+x, y = vertices[3].y*h+y }, + } + surface.DrawPoly(vertexData) + end +end + +function ENT:Draw() + self:DrawModel() + + local curtime = CurTime() + local DeltaTime = curtime - self.PrevTime + self.PrevTime = curtime + self.IntTimer = self.IntTimer + DeltaTime + self.FramesSinceRedraw = self.FramesSinceRedraw + 1 + + if self.NeedRefresh == true then + self.FramesSinceRedraw = 0 + self.NeedRefresh = false + self.FrameNeedsFlash = false + + if self.Memory1[2046] >= 1 then self.FrameNeedsFlash = true end + + self.GPU:RenderToGPU(function() + -- Draw terminal here + -- W/H = 16 + local szx = 512/31 + local szy = 512/19 + + local ch = self.Memory1[2042] + + local hb = 28*math.fmod(ch, 10)*self.Memory1[2026]*self.Memory1[2025] + self.Memory1[2036] + local hg = 28*math.fmod(math.floor(ch / 10), 10)*self.Memory1[2027]*self.Memory1[2025] + self.Memory1[2036] + local hr = 28*math.fmod(math.floor(ch / 100),10)*self.Memory1[2028]*self.Memory1[2025] + self.Memory1[2036] + surface.SetDrawColor(hr,hg,hb,255) + surface.DrawRect(0,0,512,512) + + for ty = 0, 17 do + for tx = 0, 29 do + local a = tx + ty*30 + local c1 = self.Memory1[2*a] + local c2 = self.Memory1[2*a+1] + + local cback = math.floor(c2 / 1000) + local cfrnt = c2 - math.floor(c2 / 1000)*1000 + + local fb = math.Clamp(28*math.fmod(cfrnt, 10)*self.Memory1[2026]*self.Memory1[2025] + self.Memory1[2036],0,255) + local fg = math.Clamp(28*math.fmod(math.floor(cfrnt / 10), 10)*self.Memory1[2027]*self.Memory1[2025] + self.Memory1[2036],0,255) + local fr = math.Clamp(28*math.fmod(math.floor(cfrnt / 100),10)*self.Memory1[2028]*self.Memory1[2025] + self.Memory1[2036],0,255) + local bb = math.Clamp(28*math.fmod(cback, 10)*self.Memory1[2026]*self.Memory1[2025] + self.Memory1[2036],0,255) + local bg = math.Clamp(28*math.fmod(math.floor(cback / 10), 10)*self.Memory1[2027]*self.Memory1[2025] + self.Memory1[2036],0,255) + local br = math.Clamp(28*math.fmod(math.floor(cback / 100),10)*self.Memory1[2028]*self.Memory1[2025] + self.Memory1[2036],0,255) + + if (self.Flash == true) and (cback > 999) then + fb,bb = bb,fb + fg,bg = bg,fg + fr,br = br,fr + end + + if cback > 999 then + self.FrameNeedsFlash = true + end + + if c1 >= 2097152 then c1 = 0 end + if c1 < 0 then c1 = 0 end + + if cback ~= 0 then + surface.SetDrawColor(br,bg,bb,255) + surface.DrawRect(tx*szx+szx/2,ty*szy+szy/2,szx*1.2,szy*1.2) + else + surface.SetDrawColor(hr,hg,hb,255) + surface.DrawRect(tx*szx+szx/2,ty*szy+szy/2,szx*1.2,szy*1.2) + end + + if (c1 ~= 0) and (cfrnt ~= 0) then + -- Note: the source engine does not handle unicode characters above 65535 properly. + local utf8 = "" + if c1 <= 127 then + utf8 = string.char (c1) + elseif c1 < 2048 then + utf8 = string.format("%c%c", 192 + math.floor (c1 / 64), 128 + (c1 % 64)) + elseif c1 < 65536 then + utf8 = string.format("%c%c%c", 224 + math.floor (c1 / 4096), 128 + (math.floor (c1 / 64) % 64), 128 + (c1 % 64)) + elseif c1 < 2097152 then + utf8 = string.format("%c%c%c%c", 240 + math.floor (c1 / 262144), 128 + (math.floor (c1 / 4096) % 64), 128 + (math.floor (c1 / 64) % 64), 128 + (c1 % 64)) + end + + if specialCharacters[c1] then + self:DrawSpecialCharacter( + c1, (tx+0.5)*szx, (ty+0.5)*szy, szx, szy, + fr,fg,fb + ) + else + draw.DrawText( + utf8, + "WireGPU_ConsoleFont", + (tx + 0.625) * szx, (ty + 0.75) * szy, + Color(fr,fg,fb,255),0 + ) + end + end + end + end + + if self.Memory1[2045] > 1080 then self.Memory1[2045] = 1080 end + if self.Memory1[2045] < 0 then self.Memory1[2045] = 0 end + if self.Memory1[2044] > 1 then self.Memory1[2044] = 1 end + if self.Memory1[2044] < 0 then self.Memory1[2044] = 0 end + + if self.Memory1[2046] >= 1 then + if self.Flash == true then + local a = math.floor(self.Memory1[2045] / 2) + + local tx = a - math.floor(a / 30)*30 + local ty = math.floor(a / 30) + + local c = self.Memory1[2*a+1] + local cback = 999-math.floor(c / 1000) + local bb = 28*math.fmod(cback,10) + local bg = 28*math.fmod(math.floor(cback / 10),10) + local br = 28*math.fmod(math.floor(cback / 100),10) + + surface.SetDrawColor( + math.Clamp(br*self.Memory1[2028]*self.Memory1[2025],0,255), + math.Clamp(bg*self.Memory1[2027]*self.Memory1[2025],0,255), + math.Clamp(bb*self.Memory1[2026]*self.Memory1[2025],0,255), + 255 + ) + surface.DrawRect( + tx*szx+szx/2, + ty*szy+szy/2+szy*1.2*(1-self.Memory1[2044]), + szx*1.2, + szy*1.2*self.Memory1[2044] + ) + end + end + end) + end + + if self.FrameNeedsFlash == true then + if self.IntTimer < self.Memory1[2043] then + if (self.Flash == false) then + self.NeedRefresh = true + end + self.Flash = true + end + + if self.IntTimer >= self.Memory1[2043] then + if self.Flash == true then + self.NeedRefresh = true + end + self.Flash = false + end + + if self.IntTimer >= self.Memory1[2043]*2 then + self.IntTimer = 0 + end + end + + self.GPU:Render(self.Memory1[2024],self.Memory1[2023]) + Wire_Render(self) +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/init.lua new file mode 100644 index 0000000..0118a20 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/init.lua @@ -0,0 +1,218 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include('shared.lua') + +ENT.WireDebugName = "ConsoleScreen" + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.Inputs = WireLib.CreateInputs(self, { "CharX", "CharY", "Char (ASCII/Unicode)", "CharParam (RGBrgb; White=999)", "Clk", "Reset" }) + self.Outputs = WireLib.CreateOutputs(self, { "Memory" }) + + self.Memory = {} + + for i = 0, 2047 do + self.Memory[i] = 0 + end + + self.CharX = 0 + self.CharY = 0 + self.Char = 0 + self.CharParam = 0 + + self.Memory[2020] = 0 + self.Memory[2021] = 0 + + self.Memory[2022] = 3/4 + self.Memory[2023] = 0 + self.Memory[2024] = 0 + self.Memory[2025] = 1 + self.Memory[2026] = 1 + self.Memory[2027] = 1 + self.Memory[2028] = 1 + self.Memory[2029] = 1 + self.Memory[2030] = 1 + self.Memory[2031] = 0 + self.Memory[2032] = 29 + self.Memory[2033] = 0 + self.Memory[2034] = 17 + self.Memory[2035] = 0 + self.Memory[2036] = 0 + + self.Memory[2042] = 000 + self.Memory[2043] = 0.5 + self.Memory[2044] = 0.25 + self.Memory[2045] = 0 + self.Memory[2046] = 0 + self.Memory[2047] = 1 -- CLK + + self.Cache = GPUCacheManager(self,true) +end + +function ENT:SendPixel() + if (self.Memory[2047] ~= 0) && (self.CharX >= 0) && (self.CharX < 30) && + (self.CharY >= 0) && (self.CharY < 18) then + local pixelno = math.floor(self.CharY)*30+math.floor(self.CharX) + + self:WriteCell(pixelno*2, self.Char) + self:WriteCell(pixelno*2+1, self.CharParam) + end +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if Address < 0 then return nil end + if Address >= 2048 then return nil end + if Address == 2022 then return WireGPU_Monitors[self:GetModel()].RatioX end + + return self.Memory[Address] +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + if Address < 0 then return false end + if Address >= 2048 then return false end + if Address < 2000 then -- text/attribute data + if self.Memory[Address] == value then return true end + else +-- self.Memory[Address] = value + self:ClientWriteCell(Address, value) +-- self.Cache:WriteNow(Address, value) +-- return true + end + + self.Memory[Address] = value + self.Cache:Write(Address,value) + return true +end + +function ENT:Think() + self.Cache:Flush() + self:NextThink(CurTime()+0.1) + return true +end + +function ENT:Retransmit(ply) + self.Cache:Flush() + for address,value in pairs(self.Memory) do + self.Cache:Write(address,value) + end + self.Cache:Flush(ply) +end + +function ENT:TriggerInput(iname, value) + if iname == "CharX" then + self.CharX = value + self:SendPixel() + elseif iname == "CharY" then + self.CharY = value + self:SendPixel() + elseif iname == "Char" then + self.Char = value + self:SendPixel() + elseif iname == "CharParam" then + self.CharParam = value + self:SendPixel() + elseif iname == "Clk" then + self:WriteCell(2047, value) + self:SendPixel() + elseif iname == "Reset" then + self:WriteCell(2041,0) + self:WriteCell(2046,0) + self:WriteCell(2042,0) + end +end + +function ENT:ClientWriteCell(Address, value) + if Address == 2019 then -- Hardware Clear Viewport + local low = math.floor(math.Clamp(self.Memory[2033],0,17)) + local high = math.floor(math.Clamp(self.Memory[2034],0,17)) + local lowc = math.floor(math.Clamp(self.Memory[2031],0,29)) + local highc = math.floor(math.Clamp(self.Memory[2032],0,29)) + for j = low, high do + for i = 2*lowc, 2*highc+1 do + self.Memory[i*60+value] = 0 + end + end + elseif Address == 2037 then -- Shift cells (number of cells, >0 right, <0 left) + local delta = math.floor(math.Clamp(math.abs(value),-30,30)) + local low = math.floor(math.Clamp(self.Memory[2033],0,17)) + local high = math.floor(math.Clamp(self.Memory[2034],0,17)) + local lowc = math.floor(math.Clamp(self.Memory[2031],0,29)) + local highc = math.floor(math.Clamp(self.Memory[2032],0,29)) + if value > 0 then + for j = low,high do + for i = highc,lowc+delta,-1 do + self.Memory[j*60+i*2] = self.Memory[j*60+i*2-delta*2] + self.Memory[j*60+i*2+1] = self.Memory[j*60+i*2+1-delta*2] + end + end + for j = low,high do + for i = lowc, lowc+delta-1 do + self.Memory[j*60+i*2] = 0 + self.Memory[j*60+i*2+1] = 0 + end + end + else + for j = low,high do + for i = lowc,highc-delta do + self.Memory[j*60+i*2] = self.Memory[j*60+i*2+delta*2] + self.Memory[j*60+i*2+1] = self.Memory[j*60+i*2+1+delta*2] + end + end + for j = low,high do + for i = highc-delta+1,highc do + self.Memory[j*60+i*2] = 0 + self.Memory[j*60+i*2+1] = 0 + end + end + end + elseif Address == 2038 then -- Shift rows (number of rows, >0 shift down, <0 shift up) + local delta = math.floor(math.Clamp(math.abs(value),-30,30)) + local low = math.floor(math.Clamp(self.Memory[2033],0,17)) + local high = math.floor(math.Clamp(self.Memory[2034],0,17)) + local lowc = math.floor(math.Clamp(self.Memory[2031],0,29)) + local highc = math.floor(math.Clamp(self.Memory[2032],0,29)) + if value > 0 then + for j = low, high-delta do + for i = 2*lowc, 2*highc+1 do + self.Memory[j*60+i] = self.Memory[(j+delta)*60+i] + end + end + for j = high-delta+1,high do + for i = 2*lowc, 2*highc+1 do + self.Memory[j*60+i] = 0 + end + end + else + for j = high,low+delta,-1 do + for i = 2*lowc, 2*highc+1 do + self.Memory[j*60+i] = self.Memory[(j-delta)*60+i] + end + end + for j = low,low+delta-1 do + for i = 2*lowc, 2*highc+1 do + self.Memory[j*60+i] = 0 + end + end + end + elseif Address == 2039 then -- Hardware Clear Row (Writing clears row) + for i = 0, 59 do + self.Memory[value*60+i] = 0 + end + elseif Address == 2040 then -- Hardware Clear Column (Writing clears column) + for i = 0, 17 do + self.Memory[i*60+value] = 0 + end + elseif Address == 2041 then -- Hardware Clear Screen + for i = 0, 18*30*2-1 do + self.Memory[i] = 0 + end + self.Cache:Reset() + end +end + +duplicator.RegisterEntityClass("gmod_wire_consolescreen", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/shared.lua new file mode 100644 index 0000000..872345b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_consolescreen/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire Console Screen" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cpu.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cpu.lua new file mode 100644 index 0000000..36adaf9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cpu.lua @@ -0,0 +1,288 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire ZCPU" +ENT.Author = "Black Phoenix" +ENT.WireDebugName = "ZCPU" + +if CLIENT then return end -- No more client + +local cpu_max_frequency = 1400000 +local wire_cpu_max_frequency = CreateConVar("wire_cpu_max_frequency", cpu_max_frequency, FCVAR_REPLICATED) + +cvars.AddChangeCallback("wire_cpu_max_frequency",function() + cpu_max_frequency = math.Clamp(math.floor(wire_cpu_max_frequency:GetInt()),1,30000000) +end) + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.Inputs = Wire_CreateInputs(self, { "MemBus", "IOBus", "Frequency", "Clk", "Reset", "Interrupt"}) + self.Outputs = Wire_CreateOutputs(self, { "Error" }) + + -- CPU platform settings + self.Clk = false -- whether the Clk input is on + self.VMStopped = false -- whether the VM has halted itself (e.g. by running off the end of the program) + self.Frequency = 2000 + -- Create virtual machine + self.VM = CPULib.VirtualMachine() + self.VM.SerialNo = CPULib.GenerateSN("CPU") + self.VM:Reset() + + self:SetCPUName() + self:SetMemoryModel("64krom") + self.VM.SignalError = function(VM,errorCode) + Wire_TriggerOutput(self, "Error", errorCode) + end + self.VM.SignalShutdown = function(VM) + self.VMStopped = true + end + self.VM.ExternalWrite = function(VM,Address,Value) + if Address >= 0 then -- Use MemBus + local MemBusSource = self.Inputs.MemBus.Src + if MemBusSource then + if MemBusSource.ReadCell then + local result = MemBusSource:WriteCell(Address-self.VM.RAMSize,Value) + if result then return true + else VM:Interrupt(7,Address) return false + end + else VM:Interrupt(8,Address) return false + end + else VM:Interrupt(7,Address) return false + end + else -- Use IOBus + local IOBusSource = self.Inputs.IOBus.Src + if IOBusSource then + if IOBusSource.ReadCell then + local result = IOBusSource:WriteCell(-Address-1,Value) + if result then return true + else VM:Interrupt(10,-Address-1) return false + end + else VM:Interrupt(8,Address+1) return false + end + else return true + end + end + end + self.VM.ExternalRead = function(VM,Address) + if Address >= 0 then -- Use MemBus + local MemBusSource = self.Inputs.MemBus.Src + if MemBusSource then + if MemBusSource.ReadCell then + local result = MemBusSource:ReadCell(Address-self.VM.RAMSize) + if result then return result + else VM:Interrupt(7,Address) return + end + else VM:Interrupt(8,Address) return + end + else VM:Interrupt(7,Address) return + end + else -- Use IOBus + local IOBusSource = self.Inputs.IOBus.Src + if IOBusSource then + if IOBusSource.ReadCell then + local result = IOBusSource:ReadCell(-Address-1) + if result then return result + else VM:Interrupt(10,-Address-1) return + end + else VM:Interrupt(8,Address+1) return + end + else return 0 + end + end + end + + local oldReset = self.VM.Reset + self.VM.Reset = function(...) + if self.Clk and self.VMStopped then + self:NextThink(CurTime()) + end + self.VMStopped = false + return oldReset(...) + end + + -- Player that debugs the processor + self.DebuggerPlayer = nil +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + return self.VM:ReadCell(Address) +end + +function ENT:WriteCell(Address,Value) + Address = math.floor(Address) + return self.VM:WriteCell(Address,tonumber(Value) or 0) +end + +local memoryModels = { + ["8k"] = { 8192, 0 }, + ["8krom"] = { 8192, 8192 }, + ["32k"] = { 32768, 0 }, + ["32krom"] = { 32768, 32768 }, + ["64krom"] = { 65536, 65536 }, + ["64k"] = { 65536, 0 }, + ["128krom"] = { 131072, 131072 }, + ["128rom"] = { 0, 128 }, + ["128"] = { 128, 128 }, + ["flat"] = { 0, 0 }, +} + +function ENT:SetMemoryModel(model) + self.VM.RAMSize = memoryModels[model][1] or 65536 + self.VM.ROMSize = memoryModels[model][2] or 65536 +end + +-- Execute ZCPU virtual machine +function ENT:Run() + -- Do not run if debugging is active + if self.DebuggerPlayer then return end + + -- Calculate time-related variables + local CurrentTime = CurTime() + local DeltaTime = math.min(1/30,CurrentTime - (self.PreviousTime or 0)) + self.PreviousTime = CurrentTime + + -- Check if need to run till specific instruction + if self.BreakpointInstructions then + self.VM.TimerDT = DeltaTime + self.VM.CPUIF = self + self.VM:Step(8,function(self) + -- self:Emit("VM.IP = "..(self.PrecompileIP or 0)) + -- self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + + self:Dyn_Emit("if (VM.CPUIF.Clk and not VM.CPUIF.VMStopped) and (VM.CPUIF.OnVMStep) then") + self:Dyn_EmitState() + self:Emit("VM.CPUIF.OnVMStep()") + self:Emit("end") + self:Emit("if VM.CPUIF.BreakpointInstructions[VM.IP] then") + self:Dyn_EmitState() + self:Emit("VM.CPUIF.OnBreakpointInstruction(VM.IP)") + self:Emit("VM.CPUIF.VMStopped = true") + self:Emit("VM.TMR = VM.TMR + "..self.PrecompileInstruction) + self:Emit("VM.CODEBYTES = VM.CODEBYTES + "..self.PrecompileBytes) + self:Emit("if true then return end") + self:Emit("end") + self:Emit("if VM.CPUIF.LastInstruction and ((VM.IP > VM.CPUIF.LastInstruction) or VM.CPUIF.ForceLastInstruction) then") + self:Dyn_EmitState() + self:Emit("VM.CPUIF.ForceLastInstruction = nil") + self:Emit("VM.CPUIF.OnLastInstruction()") + self:Emit("VM.CPUIF.VMStopped = true") + self:Emit("VM.TMR = VM.TMR + "..self.PrecompileInstruction) + self:Emit("VM.CODEBYTES = VM.CODEBYTES + "..self.PrecompileBytes) + self:Emit("if true then return end") + self:Emit("end") + end) + self.VM.CPUIF = nil + else + -- How many steps VM must make to keep up with execution + local Cycles = math.max(1,math.floor(self.Frequency*DeltaTime*0.5)) + self.VM.TimerDT = (DeltaTime/Cycles) + + while (Cycles > 0) and (self.Clk) and (not self.VMStopped) and (self.VM.Idle == 0) do + -- Run VM step + local previousTMR = self.VM.TMR + self.VM:Step() + Cycles = Cycles - math.max(1, self.VM.TMR - previousTMR) + end + end + + -- Update VM timer + self.VM.TIMER = self.VM.TIMER + DeltaTime + + -- Reset idle register + self.VM.Idle = 0 +end + +function ENT:Think() + if (not game.SinglePlayer()) and (self.Frequency > cpu_max_frequency) then self.Frequency = cpu_max_frequency end + self:Run() + if self.Clk and not self.VMStopped then self:NextThink(CurTime()) end + return true +end + +-- Write data to RAM and then flash ROM if required +function ENT:FlashData(data) + self.VM:Reset() + for k,v in pairs(data) do + self.VM:WriteCell(k,tonumber(v) or 0) + if (k >= 0) and (k < self.VM.ROMSize) then + self.VM.ROM[k] = tonumber(v) or 0 + end + end +end + +function ENT:SetCPUName(name) + local overlayStr = "" + local a = math.floor(self.VM.SerialNo / 100000) + local b = math.floor(self.VM.SerialNo % 100000) + if name and (name ~= "") then + self:SetOverlayText(string.format("%s\nS/N %05d%05d",name,a,b)) + else + self:SetOverlayText(string.format("Zyelios CPU\nS/N %05d%05d",a,b)) + end + self.CPUName = name +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + info.SerialNo = self.VM.SerialNo + info.InternalRAMSize = self.VM.RAMSize + info.InternalROMSize = self.VM.ROMSize + info.CPUName = self.CPUName + + if self.VM.ROMSize > 0 then + info.Memory = {} + for k,v in pairs(self.VM.ROM) do if v ~= 0 then info.Memory[k] = v end end + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.VM.SerialNo = info.SerialNo or CPULib.GenerateSN("UNK") + self.VM.RAMSize = info.InternalRAMSize or 65536 + self.VM.ROMSize = info.InternalROMSize or 65536 + self:SetCPUName(info.CPUName) + + if info.Memory then--and + --(((info.UseROM) and (info.UseROM == true)) or + -- ((info.InternalROMSize) and (info.InternalROMSize > 0))) then + self.VM.ROM = {} + for k,v in pairs(info.Memory) do self.VM.ROM[k] = tonumber(v) or 0 end + self.VM:Reset() + end +end + +-- Compatibility with old NMI input +WireLib.AddInputAlias( "NMI", "Interrupt" ) + +function ENT:TriggerInput(iname, value) + if iname == "Clk" then + self.Clk = (value >= 1) + if self.Clk then + self.VMStopped = false + self:NextThink(CurTime()) + end + elseif iname == "Frequency" then + if value > 0 then self.Frequency = math.floor(value) end + elseif iname == "Reset" then --VM may be nil + if self.VM.HWDEBUG ~= 0 then + self.VM.DBGSTATE = math.floor(value) + if (value > 0) and (value <= 1.0) then self.VM:Reset() end + else + if value >= 1.0 then self.VM:Reset() end + end + Wire_TriggerOutput(self, "Error", 0) + elseif iname == "Interrupt" then + if (value >= 32) && (value < 256) then + if (self.Clk and not self.VMStopped) then self.VM:ExternalInterrupt(math.floor(value)) end + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_cpu", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_damage_detector.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_damage_detector.lua new file mode 100644 index 0000000..dd734cb --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_damage_detector.lua @@ -0,0 +1,346 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Damage Detector" +ENT.Author = "Jimlad" +ENT.WireDebugName = "Damage Detector" + +if CLIENT then return end -- No more client + +local DEFAULT = {n={},ntypes={},s={},stypes={},size=0} + +-- Global table to keep track of all detectors +local Wire_Damage_Detectors = {} + +-- Damage detection function +local function CheckWireDamageDetectors( ent, inflictor, attacker, amount, dmginfo ) + if amount > 0 then + for k,_ in pairs(Wire_Damage_Detectors) do + local detector = k + if IsValid(detector) then + if detector.on then + if not detector.updated then + detector:UpdateLinkedEnts() + detector.updated = true + detector:NextThink(CurTime()) -- Update link info once per tick per detector at most + end + if detector.key_ents[ent] then + detector:UpdateDamage( dmginfo, ent ) + end + end + else + Wire_Damage_Detectors[k] = nil + end + end + end +end +hook.Add("EntityTakeDamage", "CheckWireDamageDetectors", function( ent, dmginfo ) + if not next(Wire_Damage_Detectors) then return end + local r, e = xpcall( CheckWireDamageDetectors, debug.traceback, ent, dmginfo:GetInflictor(), dmginfo:GetAttacker(), dmginfo:GetDamage(), dmginfo ) + if !r then print( "Wire damage detector error: " .. e ) end +end) + + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = WireLib.CreateSpecialOutputs(self, { "Clk", "Damage", "Attacker", "Victim", "Victims", "Position", "Force", "Type" } , { "NORMAL", "NORMAL", "ENTITY", "ENTITY", "TABLE", "VECTOR", "VECTOR", "STRING" } ) + self.Inputs = WireLib.CreateSpecialInputs(self, { "On", "Entity", "Entities", "Reset" }, { "NORMAL", "ENTITY", "ARRAY", "NORMAL" } ) + + self.on = false + self.updated = false -- Tracks whether constraints were updated that tick + self.hit = false -- Tracks whether detector registered any damage that tick + + self.firsthit_dmginfo = {} -- Stores damage info representing damage during an interval + + self.linked_entities = {} -- numerical array + self.linked_entities_lookup = {} -- lookup table indexed by entities + + self:LinkEnt( self ) + + self.count = 0 + + -- Store output damage info + self.victims = table.Copy(DEFAULT) + WireLib.TriggerOutput( self, "Victims", self.victims ) + self.damage = 0 + + Wire_Damage_Detectors[self] = true +end + +--[[***************************** + How entities are stored: + self.linked_entities_lookup: Table: Lookup table used for linking and unlinking + self.linked_entities Array: Entities the detector is directly linked to. + self.key_ents KeyTable: Entities to check damage on (including constrained ents), only updated when the damage hook is called,.Contains entities as table keys +*****************************]] + +function ENT:OnRemove() + Wire_Damage_Detectors[self] = nil + Wire_Remove(self) + self:ClearEntities() +end + +-- Update overlay +function ENT:ShowOutput() + local text + if self.includeconstrained == 0 then + text = "(Individual Props)\n" + else + text = "(Constrained Props)\n" + end + + if #self.linked_entities == 0 then + text = text .. "Not linked" + else + if #self.linked_entities == 1 and self.linked_entities[1] == self then + text = text .. "Linked to self" + else + text = text .. "Linked to " .. #self.linked_entities .. " entities." + end + end + + self:SetOverlayText( text ) + + self:SetOverlayText(text) +end + +function ENT:Setup( includeconstrained ) + self.includeconstrained = includeconstrained + self:ShowOutput() +end + +function ENT:LinkEnt( ent ) + if self.linked_entities_lookup[ent] then return false end + + self.linked_entities_lookup[ent] = true + self.linked_entities[#self.linked_entities+1] = ent + ent:CallOnRemove( "DDetector.Unlink", function( ent ) + if IsValid( self ) then + self:UnlinkEnt( ent ) + end + end ) + + self:ShowOutput() + WireLib.SendMarks( self, self.linked_entities ) + return true +end + +function ENT:UnlinkEnt( ent ) + if not self.linked_entities_lookup[ent] then return false end + + self.linked_entities_lookup[ent] = nil + + for i=1,#self.linked_entities do + if self.linked_entities[i] == ent then + table.remove( self.linked_entities, i ) + break + end + end + + ent:RemoveCallOnRemove( "DDetector.Unlink" ) + + self:ShowOutput() + WireLib.SendMarks( self, self.linked_entities ) + return true +end + +function ENT:ClearEntities() + for i=1, #self.linked_entities do + if IsValid( self.linked_entities[i] ) then -- generally, all entities should be kept valid automatically by the CallOnRemove functions, but CallOnRemove isn't called for players apparently + self.linked_entities[i]:RemoveCallOnRemove( "DDetector.Unlink" ) + end + end + + self.linked_entities = {} + self.linked_entities_lookup = {} + + self:ShowOutput() + WireLib.SendMarks( self, self.linked_entities ) + return true +end + +function ENT:TriggerInput( iname, value ) + if iname == "On" then + self.on = value ~= 0 + elseif iname == "Entities" then -- Populate linked_entities from "Array" + if value then + self:ClearEntities() + + for _, v in pairs( value ) do + if IsValid( v ) then + self:LinkEnt( v ) + end + end + end + elseif iname == "Entity" then + if IsValid( value )then + self:LinkEnt( value ) + end + elseif iname == "Reset" then + if value then + self.count = 0 + self.firsthit_dmginfo = {} + self.victims = table.Copy(DEFAULT) + self.damage = 0 + self:TriggerOutput() + end + end +end + +function ENT:TriggerOutput() -- Entity outputs won't trigger again until they change + local attacker = self.firsthit_dmginfo[1] + WireLib.TriggerOutput( self, "Attacker", IsValid(attacker) and attacker or NULL) + + local victim = self.firsthit_dmginfo[2] + WireLib.TriggerOutput( self, "Victim", IsValid(victim) and victim or NULL) + + self.victims.size = table.Count(self.victims.s) + WireLib.TriggerOutput( self, "Victims", self.victims or table.Copy(DEFAULT) ) + WireLib.TriggerOutput( self, "Position", self.firsthit_dmginfo[3] or Vector(0,0,0) ) + WireLib.TriggerOutput( self, "Force", self.firsthit_dmginfo[4] or Vector(0,0,0) ) + WireLib.TriggerOutput( self, "Type", self.firsthit_dmginfo[5] or "" ) + WireLib.TriggerOutput( self, "Damage", self.damage or 0 ) + + WireLib.TriggerOutput( self, "Clk", self.count ) +end + +function ENT:UpdateLinkedEnts() -- Check to see if prop is registered by the detector + if #self.linked_entities == 0 then return end + + self.key_ents = {} + + for i=1, #self.linked_entities do -- include linked_entities + local ent = self.linked_entities[i] + if IsValid( ent ) then + if self.includeconstrained == 1 then -- Don't update constrained entities unless we have to + self:UpdateConstrainedEnts( ent ) + end + + self.key_ents[ent] = true + else + self.linked_entities[ent] = nil + end + end +end + +function ENT:UpdateConstrainedEnts( ent ) -- Finds all entities constrained to 'ent' + local ents = constraint.GetAllConstrainedEntities( ent ) + + for _,v in pairs( ents ) do + self.key_ents[v] = true + end +end +local damageTypes = { + [DMG_GENERIC] = "Generic", + [DMG_CRUSH] = "Crush", + [DMG_BULLET] = "Bullet", + [DMG_SLASH] = "Slash", + [DMG_BURN] = "Burn", + [DMG_VEHICLE] = "Vehicle", + [DMG_FALL] = "Fall", + [DMG_BLAST] = "Explosive", + [DMG_CLUB] = "Club", + [DMG_SHOCK] = "Shock", + [DMG_SONIC] = "Sonic", + [DMG_ENERGYBEAM] = "Laser", + [DMG_DROWN] = "Drown", + [DMG_PARALYZE] = "Poison", + [DMG_POISON] = "Poison", + [DMG_NERVEGAS] = "Neurotoxin", + [DMG_RADIATION] = "Radiation", + [DMG_ACID] = "Toxic", + [DMG_PHYSGUN] = "Gravgun", + [DMG_PLASMA] = "Plasma", + [DMG_AIRBOAT] = "AirboatGun", + [DMG_ENERGYBEAM] = "Laser", + [DMG_DIRECT] = "Direct" +} +function ENT:UpdateDamage( dmginfo, ent ) -- Update damage table + local damage = dmginfo:GetDamage() + + if !self.hit then -- Only register the first target's damage info + self.firsthit_dmginfo = { + dmginfo:GetAttacker(), + ent, + dmginfo:GetDamagePosition(), + dmginfo:GetDamageForce() + } + + -- Damage type (handle almost all types) + self.dmgtype = damageTypes[dmginfo:GetDamageType()] or "Other" + + + + self.victims = table.Copy(DEFAULT) + self.firsthit_dmginfo[5] = self.dmgtype + + self.hit = true + end + + if self.dmgtype == "Explosive" then -- Explosives will output the entity that receives the most damage + if self.damage < damage then + self.damage = damage + self.firsthit_dmginfo[2] = ent + end + else + self.damage = self.damage + damage + end + + -- Update victims table (ent, damage) + local entID = tostring(ent:EntIndex()) + self.victims.s[entID] = ( self.victims[entID] or 0 ) + damage + self.victims.stypes[entID] = "n" + + self.count = self.count + 1 + if self.count == math.huge then self.count = 0 end -- This shouldn't ever happen... unless you're really REALLY bored +end + +function ENT:Think() + self.updated = false + self.hit = false + if self.damage > 0 then + self:TriggerOutput() + self.damage = 0 + end + return true +end + +duplicator.RegisterEntityClass("gmod_wire_damage_detector", WireLib.MakeWireEnt, "Data", "includeconstrained") + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if #self.linked_entities > 0 then + info.linked_entities = {} + + for i=1,#self.linked_entities do + if IsValid( self.linked_entities[i] ) then + info.linked_entities[i] = self.linked_entities[i]:EntIndex() + else + self.linked_entities[i] = nil + end + end + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if info.linked_entities then + if type( info.linked_entities ) == "number" then -- old dupe compatibility + self:LinkEnt( GetEntByID( info.linked_entities ) ) + else + for i=1,#info.linked_entities do + self:LinkEnt( GetEntByID( info.linked_entities[i] ) ) + end + end + end + + self:ShowOutput() + -- wait a while after dupe before sending marks, because the entity doesn't exist clientside yet + timer.Simple( 0.1, function() if IsValid( self ) then WireLib.SendMarks( self, self.linked_entities ) end end ) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_satellitedish.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_satellitedish.lua new file mode 100644 index 0000000..d77a670 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_satellitedish.lua @@ -0,0 +1,50 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Data Satellite Dish" +ENT.WireDebugName = "Satellite Dish" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:ShowOutput() +end + +function ENT:LinkEnt( transmitter ) + if not IsValid(transmitter) || transmitter:GetClass() != "gmod_wire_data_transferer" then + return false, "Satellite Dishes can only be linked to Wire Data Transferers!" + end + self.Transmitter = transmitter + self:ShowOutput() + WireLib.SendMarks(self, {transmitter}) + return true +end +function ENT:UnlinkEnt() + self.Transmitter = nil + self:ShowOutput() + WireLib.SendMarks(self, {}) + return true +end + +function ENT:ShowOutput() + self:SetOverlayText( IsValid(self.Transmitter) and "Linked" or "Unlinked" ) +end + +duplicator.RegisterEntityClass("gmod_wire_data_satellitedish", WireLib.MakeWireEnt, "Data") + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if IsValid( self.Transmitter ) then + info.Transmitter = self.Transmitter:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.Transmitter = GetEntByID(info.Transmitter) + self:ShowOutput() +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_store.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_store.lua new file mode 100644 index 0000000..85c9c96 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_store.lua @@ -0,0 +1,15 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Data Storer" +ENT.WireDebugName = "Data Store" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Values = {A=0, B=0, C=0, D=0, E=0, F=0, G=0, H=0} +end + +duplicator.RegisterEntityClass("gmod_wire_data_store", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_transferer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_transferer.lua new file mode 100644 index 0000000..c38c3ae --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_data_transferer.lua @@ -0,0 +1,144 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Data Transferer" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Data Transferer" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self, {"Send","Range","A","B","C","D","E","F","G","H"}) + self.Outputs = Wire_CreateOutputs(self, {"A","B","C","D","E","F","G","H"}) + self.Sending = false + self.Activated = false + self.ActivateTime = 0 + self.DefaultZero = true + self.IgnoreZero = false + self.Values = {}; + self.Values["A"] = 0 + self.Values["B"] = 0 + self.Values["C"] = 0 + self.Values["D"] = 0 + self.Values["E"] = 0 + self.Values["F"] = 0 + self.Values["G"] = 0 + self.Values["H"] = 0 + + self:SetBeamLength(25000) +end + +function ENT:Setup(Range,DefaultZero,IgnoreZero) + self.IgnoreZero = IgnoreZero + self.DefaultZero = DefaultZero + if Range then self:SetBeamLength(Range) end +end + +function ENT:TriggerInput(iname, value) + if(iname == "Send")then + self.Sending = value > 0 + elseif(iname == "Range")then + self:SetBeamLength(math.Clamp(value,0,32000)) + else + self.Values[iname] = value + end +end + +function ENT:Think() + self:NextThink(CurTime()+0.125) + if(self.Activated == false && self.DefaultZero)then + Wire_TriggerOutput(self,"A",0) + Wire_TriggerOutput(self,"B",0) + Wire_TriggerOutput(self,"C",0) + Wire_TriggerOutput(self,"D",0) + Wire_TriggerOutput(self,"E",0) + Wire_TriggerOutput(self,"F",0) + Wire_TriggerOutput(self,"G",0) + Wire_TriggerOutput(self,"H",0) + else + if(CurTime() > self.ActivateTime + 0.5)then + self.Activated = false + end + end + + + local vStart = self:GetPos() + local vForward = self:GetUp() + + local trace = {} + trace.start = vStart + trace.endpos = vStart + (vForward * self:GetBeamLength()) + trace.filter = { self } + local trace = util.TraceLine( trace ) + + local ent = trace.Entity + + if not IsValid(ent) then + self:SetColor(Color(255, 255, 255, 255)) + return true + end + + self:SetColor(Color(0, 255, 0, 255)) + + if ent:GetClass() == "gmod_wire_data_transferer" then + ent:ReceiveValue("A",self.Values.A) + ent:ReceiveValue("B",self.Values.B) + ent:ReceiveValue("C",self.Values.C) + ent:ReceiveValue("D",self.Values.D) + ent:ReceiveValue("E",self.Values.E) + ent:ReceiveValue("F",self.Values.F) + ent:ReceiveValue("G",self.Values.G) + ent:ReceiveValue("H",self.Values.H) + elseif ent:GetClass() == "gmod_wire_data_satellitedish" then + if IsValid(ent.Transmitter) then + ent.Transmitter:ReceiveValue("A",self.Values.A) + ent.Transmitter:ReceiveValue("B",self.Values.B) + ent.Transmitter:ReceiveValue("C",self.Values.C) + ent.Transmitter:ReceiveValue("D",self.Values.D) + ent.Transmitter:ReceiveValue("E",self.Values.E) + ent.Transmitter:ReceiveValue("F",self.Values.F) + ent.Transmitter:ReceiveValue("G",self.Values.G) + ent.Transmitter:ReceiveValue("H",self.Values.H) + else + self:SetColor(Color(255, 0, 0, 255)) + end + elseif ent:GetClass() == "gmod_wire_data_store" then + Wire_TriggerOutput(self,"A",ent.Values.A) + Wire_TriggerOutput(self,"B",ent.Values.B) + Wire_TriggerOutput(self,"C",ent.Values.C) + Wire_TriggerOutput(self,"D",ent.Values.D) + Wire_TriggerOutput(self,"E",ent.Values.E) + Wire_TriggerOutput(self,"F",ent.Values.F) + Wire_TriggerOutput(self,"G",ent.Values.G) + Wire_TriggerOutput(self,"H",ent.Values.H) + if(self.Sending)then + ent.Values.A = self.Inputs["A"].Value + ent.Values.B = self.Inputs["B"].Value + ent.Values.C = self.Inputs["C"].Value + ent.Values.D = self.Inputs["D"].Value + ent.Values.E = self.Inputs["E"].Value + ent.Values.F = self.Inputs["F"].Value + ent.Values.G = self.Inputs["G"].Value + ent.Values.H = self.Inputs["H"].Value + end + else + self:SetColor(Color(255, 255, 255, 255)) + end + return true +end + +function ENT:ReceiveValue(output,value) + self.Activated = true + self.ActivateTime = CurTime() + if value ~= 0 or not self.IgnoreZero then + Wire_TriggerOutput(self,output,value) + end +end + +duplicator.RegisterEntityClass("gmod_wire_data_transferer", WireLib.MakeWireEnt, "Data", "Range", "DefaultZero", "IgnoreZero") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dataplug.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dataplug.lua new file mode 100644 index 0000000..bde0cd6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dataplug.lua @@ -0,0 +1,63 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "gmod_wire_plug" ) +ENT.PrintName = "Wire Plug" +ENT.WireDebugName = "DataPlug" + +function ENT:GetSocketClass() + return "gmod_wire_datasocket" +end + +if CLIENT then + function ENT:DrawEntityOutline() end -- never draw outline + return +end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Memory = nil + + self.Inputs = WireLib.CreateInputs(self, { "Memory" }) + self.Outputs = WireLib.CreateOutputs(self, { "Connected" }) + WireLib.TriggerOutput(self, "Connected", 0) +end + +-- Override some functions from gmod_wire_plug +function ENT:Setup() end +function ENT:ResendValues() WireLib.TriggerOutput(self,"Connected",1) end +function ENT:ResetValues() WireLib.TriggerOutput(self,"Connected",0) end + +function ENT:ReadCell( Address, infloop ) + infloop = infloop or 0 + if infloop > 50 then return end + Address = math.floor(Address) + + if IsValid(self.Socket) and self.Socket.OwnMemory and self.Socket.OwnMemory.ReadCell then + return self.Socket.OwnMemory:ReadCell( Address, infloop + 1 ) + end + return nil +end + +function ENT:WriteCell( Address, value, infloop ) + infloop = infloop or 0 + if infloop > 50 then return end + Address = math.floor(Address) + + if IsValid(self.Socket) and self.Socket.OwnMemory and self.Socket.OwnMemory.WriteCell then + return self.Socket.OwnMemory:WriteCell( Address, value, infloop + 1 ) + end + return false +end + +function ENT:TriggerInput(iname, value, iter) + if (iname == "Memory") then + self.Memory = self.Inputs.Memory.Src + if IsValid(self.Socket) then + self.Socket:SetMemory(self.Memory) + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_dataplug", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dataport.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dataport.lua new file mode 100644 index 0000000..ec3e85a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dataport.lua @@ -0,0 +1,66 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Data Port" +ENT.WireDebugName = "DataPort" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.Outputs = Wire_CreateOutputs(self, { "Port0","Port1","Port2","Port3","Port4","Port5","Port6","Port7" }) + self.Inputs = Wire_CreateInputs(self, { "Port0","Port1","Port2","Port3","Port4","Port5","Port6","Port7" }) + + self.Ports = {} + for i = 0,7 do + self.Ports[i] = 0 + end + self.OutPorts = {} + + self:NextThink(CurTime()) +end + +function ENT:Think() + BaseClass.Think(self) + + for i = 0,7 do + if self.OutPorts[i] then + Wire_TriggerOutput(self, "Port"..i, self.OutPorts[i]) + self.OutPorts[i] = nil + end + end + self:NextThink(CurTime()) + return true -- for NextThink +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if (Address >= 0) && (Address <= 7) then + return self.Ports[Address] + else + return nil + end +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + if (Address >= 0) && (Address <= 7) then + self.OutPorts[Address] = value + return true + else + return false + end +end + +function ENT:TriggerInput(iname, value) + for i = 0,7 do + if iname == ("Port"..i) then + self.Ports[i] = value + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_dataport", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_datarate.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_datarate.lua new file mode 100644 index 0000000..672d0f2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_datarate.lua @@ -0,0 +1,90 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Data Transferrer" +ENT.WireDebugName = "DataTransfer" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self.Outputs = Wire_CreateOutputs(self, {"Output","HiSpeed_DataRate","Wire_DataRate"}) + self.Inputs = Wire_CreateInputs(self,{"Input","Smooth", "Interval"}) + + self.Memory = nil + self.Smooth = 0.1 + self.Interval = 0.25 + + self.WDataRate = 0 + self.WDataBytes = 0 + self.HDataRate = 0 + self.HDataBytes = 0 + + self:SetOverlayText("Hi-Speed data rate: 0 bps\nWire data rate: 0 bps") +end + +function ENT:Think() + BaseClass.Think(self) + + self.WDataRate = (self.WDataRate*(2-self.Smooth) + self.WDataBytes * (1/self.Interval) * (self.Smooth)) / 2 + self.WDataBytes = 0 + + self.HDataRate = (self.HDataRate*(2-self.Smooth) + self.HDataBytes * (1/self.Interval) * (self.Smooth)) / 2 + self.HDataBytes = 0 + + Wire_TriggerOutput(self, "HiSpeed_DataRate", self.HDataRate) + Wire_TriggerOutput(self, "Wire_DataRate", self.WDataRate) + + self:SetOverlayText("Hi-Speed data rate: "..math.floor(self.HDataRate).." bps\nWire data rate: "..math.floor(self.WDataRate).." bps") + self:NextThink(CurTime()+self.Interval) + + return true +end + +function ENT:ReadCell( Address ) + Address = math.floor(Address) + if (self.Memory) then + if (self.Memory.LatchStore && self.Memory.LatchStore[math.floor(Address)]) then + self.HDataBytes = self.HDataBytes + 1 + return self.Memory.LatchStore[math.floor(Address)] + elseif (self.Memory.ReadCell) then + self.HDataBytes = self.HDataBytes + 1 + local val = self.Memory:ReadCell(Address) + if (val) then return val + else return 0 end + end + end + return nil +end + +function ENT:WriteCell( Address, value ) + Address = math.floor(Address) + if (self.Memory) then + if (self.Memory.LatchStore && self.Memory.LatchStore[math.floor(Address)]) then + self.Memory.LatchStore[math.floor(Address)] = value + self.HDataBytes = self.HDataBytes + 1 + return true + elseif (self.Memory.WriteCell) then + local res = self.Memory:WriteCell(Address, value) + self.HDataBytes = self.HDataBytes + 1 + return res + end + end + return false +end + +function ENT:TriggerInput(iname, value) + if (iname == "Input") then + self.Memory = self.Inputs.Input.Src + self.WDataBytes = self.WDataBytes + 1 + Wire_TriggerOutput(self, "Output", value) + elseif (iname == "Smooth") then + self.Smooth = 2*(1-math.Clamp(value,0,1)) + elseif (iname == "Interval") then + self.Interval = math.Clamp(value,0.1,2) + end +end + +duplicator.RegisterEntityClass("gmod_wire_datarate", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_datasocket.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_datasocket.lua new file mode 100644 index 0000000..1c57e09 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_datasocket.lua @@ -0,0 +1,117 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "gmod_wire_socket" ) +ENT.PrintName = "Wire Data Socket" +ENT.WireDebugName = "Socket" + +function ENT:GetPlugClass() + return "gmod_wire_dataplug" +end + +if CLIENT then + -- hook.Add("HUDPaint","Wire_DataSocket_DrawLinkHelperLine",function() + -- local sockets = ents.FindByClass("gmod_wire_datasocket") + -- for k,self in pairs( sockets ) do + -- local Pos, _ = self:GetLinkPos() + + -- local Closest = self:GetClosestPlug() + + -- if IsValid(Closest) and self:CanLink(Closest) and Closest:GetNWBool( "PlayerHolding", false ) and Closest:GetClosestSocket() == self then + -- local plugpos = Closest:GetPos():ToScreen() + -- local socketpos = Pos:ToScreen() + -- surface.SetDrawColor(255,255,100,255) + -- surface.DrawLine(plugpos.x, plugpos.y, socketpos.x, socketpos.y) + -- end + -- end + -- end) + + function ENT:DrawEntityOutline() end -- never draw outline + + return +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = WireLib.CreateInputs(self, { "Memory" }) + self.Outputs = WireLib.CreateOutputs(self, { "Memory" }) + WireLib.TriggerOutput(self, "Memory", 0) + + self.Memory = nil +end + +function ENT:Setup( WeldForce, AttachRange ) + self.WeldForce = WeldForce or 5000 + self.AttachRange = AttachRange or 5 + self:SetNWInt( "AttachRange", self.AttachRange ) +end + +-- Override some functions from gmod_wire_socket +function ENT:ResendValues() + self:SetMemory(self.Plug.Memory) +end +function ENT:ResetValues() + self.Memory = nil --We're now getting no signal + WireLib.TriggerOutput(self, "Memory", 0) +end + +duplicator.RegisterEntityClass( "gmod_wire_datasocket", WireLib.MakeWireEnt, "Data", "WeldForce", "AttachRange" ) + +function ENT:SetMemory(mement) + self.Memory = mement + WireLib.TriggerOutput(self, "Memory", 1) +end + +function ENT:ReadCell( Address, infloop ) + infloop = infloop or 0 + if infloop > 50 then return end + Address = math.floor(Address) + + if (self.Memory) then + if (self.Memory.ReadCell) then + return self.Memory:ReadCell( Address, infloop + 1 ) + else + return nil + end + else + return nil + end +end + +function ENT:WriteCell( Address, value, infloop ) + infloop = infloop or 0 + if infloop > 50 then return end + Address = math.floor(Address) + + if (self.Memory) then + if (self.Memory.WriteCell) then + return self.Memory:WriteCell( Address, value, infloop + 1 ) + else + return false + end + else + return false + end +end + +function ENT:TriggerInput(iname, value, iter) + if (iname == "Memory") then + self.OwnMemory = self.Inputs.Memory.Src + end +end + +-- Override dupeinfo functions from wire plug +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) + + if info.Socket then info.Socket.ArrayInput = nil end -- this input is not used on this entity + + return info +end + +function ENT:GetApplyDupeInfoParams(info) + return info.Socket.WeldForce, info.Socket.AttachRange +end + +duplicator.RegisterEntityClass("gmod_wire_datasocket", WireLib.MakeWireEnt, "Data", "WeldForce", "AttachRange") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_detonator.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_detonator.lua new file mode 100644 index 0000000..6131bb6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_detonator.lua @@ -0,0 +1,73 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Detonator" +ENT.WireDebugName = "Detonator" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs( self, { "Trigger" } ) + self.Trigger = 0 + self.damage = 0 +end + +function ENT:TriggerInput(iname, value) + if iname == "Trigger" then + self:ShowOutput( value ) + end +end + +function ENT:Setup(damage) + self.damage = damage + self:ShowOutput( 0 ) +end + +function ENT:ShowOutput( Trigger ) + if Trigger ~= self.Trigger then + self:SetOverlayText( self.damage .. " = " .. Trigger ) + self.Trigger = Trigger + if Trigger > 0 then + self:DoDamage() + end + end +end + +function ENT:DoDamage() + if self.target and self.target:IsValid() and self.target:Health() > 0 then + if self.target:Health() <= self.damage then + self.target:SetHealth(0) + self.target:Fire( "break", "", 0 ) + self.target:Fire( "kill", "", 0.2 ) + else + self.target:SetHealth( self.target:Health() - self.damage ) + end + end + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "Explosion", effectdata, true, true ) + self:Remove() +end + +-- Dupe info functions added by TheApathetic +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if self.target and self.target:IsValid() then + info.target = self.target:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.target = GetEntByID(info.target) +end + +duplicator.RegisterEntityClass("gmod_wire_detonator", WireLib.MakeWireEnt, "Data", "damage") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dhdd.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dhdd.lua new file mode 100644 index 0000000..f068f36 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dhdd.lua @@ -0,0 +1,145 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Dupeable Hard Drive" +ENT.Author = "Divran" +ENT.WireDebugName = "Dupeable HDD" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.Outputs = WireLib.CreateOutputs( self, { "Memory [ARRAY]", "Size" } ) + self.Inputs = WireLib.CreateInputs( self, { "Data [ARRAY]", "Clear", "AllowWrite" } ) + + self.Memory = {} + self.Size = 0 + self.ROM = false + self.AllowWrite = true + + self:SetOverlayText("DHDD") +end + +-- Read cell +function ENT:ReadCell( Address ) + -- 256 KiB limit + if Address < 0 or Address >= 262144 then return 0 end + Address = math.floor(Address) + + local data = self.Memory[Address or 0] or 0 + return isnumber(data) and data or 0 +end + +-- Write cell +function ENT:WriteCell( Address, value ) + -- 256 KiB limit + if Address < 0 or Address >= 262144 then return false end + Address = math.floor(Address) + + if self.AllowWrite then + self.Memory[Address] = value ~= 0 and value or nil + self.Size = math.max(self.Size, Address + 1) + end + + self.WantsUpdate = true + return true +end + +function ENT:Think() + self.BaseClass.Think( self ) + + --[[ + The workaround using WantsUpdate should not be required. + However, the server crashes (for no reason whatsoever) if you + create a string of the following structure too often + [~11 chars] .. number .. [~3 chars] + (such as "DHDD\nSize: " .. self.Size .." bytes") + No, string.format doesn't help + ]] + if self.WantsUpdate then + self.WantsUpdate = nil + self:ShowOutputs() + end +end + +function ENT:ShowOutputs() + WireLib.TriggerOutput( self, "Memory", self.Memory ) + WireLib.TriggerOutput( self, "Size", self.Size ) + if not self.ROM then + self:SetOverlayText("DHDD\nSize: " .. self.Size .." bytes" ) + else + self:SetOverlayText("ROM\nSize: " .. self.Size .." bytes" ) + end +end + +function ENT:TriggerInput( name, value ) + if (name == "Data") then + if not value then return end -- if the value is invalid, abort + if not IsValid(self.Inputs.Data.Src) then return end -- if the input is not wired to anything, abort + if not self.AllowWrite then return end -- if we don't allow writing, abort + + self.Memory = value + + -- HiSpeed interfaces are 0-based, but Lua arrays are typically 1-based. + -- This gives the right 0-based size if the input is a 0-based or 1-based array: + -- {} ⇒ 0 + -- { 0 = 0 } ⇒ 1 + -- { 1 = 1 }, { 0 = 0, 1 = 1 } ⇒ 2 + local size = table.maxn(value) + if size ~= 0 or value[0] ~= nil then + size = size + 1 + end + self.Size = size + self.WantsUpdate = true + elseif (name == "Clear") then + if value ~= 0 then + self.Memory = {} + self.Size = 0 + self.WantsUpdate = true + end + elseif (name == "AllowWrite") then + self.AllowWrite = value >= 1 + end +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo( self ) or {} + + info.DHDD = {} + info.ROM = self.ROM + local n = 0 + info.DHDD.Memory = {} + for k,v in pairs( self.Memory ) do -- Only save the first 512^2 values + n = n + 1 + if (n > 512*512) then break end + info.DHDD.Memory[k] = v + end + + info.DHDD.AllowWrite = self.AllowWrite + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + if (info.DHDD) then + ent.Memory = (info.DHDD.Memory or {}) + + local size = table.maxn(ent.Memory) + if size ~= 0 or ent.Memory[0] ~= nil then + size = size + 1 + end + self.Size = size + + if info.DHDD.AllowWrite ~= nil then + ent.AllowWrite = info.DHDD.AllowWrite + end + self:ShowOutputs() + end + self.ROM = info.ROM or false + + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) +end + +duplicator.RegisterEntityClass( "gmod_wire_dhdd", WireLib.MakeWireEnt, "Data" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/cl_init.lua new file mode 100644 index 0000000..4e79f49 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/cl_init.lua @@ -0,0 +1,320 @@ +include('shared.lua') + +function ENT:Initialize() + self.Memory1 = {} + self.Memory2 = {} + + self.LastClk = true + self.NewClk = true + self.Memory1[1048575] = 1 + self.Memory2[1048575] = 1 + self.NeedRefresh = true + self.IsClear = true + self.ClearQueued = false + self.RefreshPixels = {} + self.RefreshRows = {} + + self.ScreenWidth = 32 + self.ScreenHeight = 32 + + for i=1,self.ScreenHeight do + self.RefreshRows[i] = i-1 + end + + //0..786431 - RGB data + + //1048569 - Color mode (0: RGBXXX; 1: R G B) + //1048570 - Clear row + //1048571 - Clear column + //1048572 - Screen Height + //1048573 - Screen Width + //1048574 - Hardware Clear Screen + //1048575 - CLK + + self.GPU = WireGPU(self) + + self.buffer = {} + + WireLib.netRegister(self) +end + +function ENT:OnRemove() + self.GPU:Finalize() + self.NeedRefresh = true +end + +local function stringToNumber(index, str, bytes) + local newpos = index+bytes + str = str:sub(index,newpos-1) + local n = 0 + for j=1,bytes do + n = n + str:byte(j)*(256^(j-1)) + end + return n, newpos +end + +local pixelbits = {3, 1, 3, 4, 1} +net.Receive("wire_digitalscreen", function() + local ent = Entity(net.ReadUInt(16)) + + if IsValid(ent) and ent.Memory1 and ent.Memory2 then + local pixelbit = pixelbits[net.ReadUInt(5)] + local len = net.ReadUInt(32) + local datastr = util.Decompress(net.ReadData(len)) + if #datastr>0 then + ent:AddBuffer(datastr,pixelbit) + end + end +end) + +function ENT:AddBuffer(datastr,pixelbit) + self.buffer[#self.buffer+1] = {datastr=datastr,readIndex=1,pixelbit=pixelbit} +end + +function ENT:ProcessBuffer() + if not self.buffer[1] then return end + + local datastr = self.buffer[1].datastr + local readIndex = self.buffer[1].readIndex + local pixelbit = self.buffer[1].pixelbit + + local length + length, readIndex = stringToNumber(readIndex,datastr,3) + if length == 0 then + table.remove( self.buffer, 1 ) + return + end + local address + address, readIndex = stringToNumber(readIndex,datastr,3) + for i = address, address + length - 1 do + if i>=1048500 then + local data + data, readIndex = stringToNumber(readIndex,datastr,2) + self:WriteCell(i, data) + else + local data + data, readIndex = stringToNumber(readIndex,datastr,pixelbit) + self:WriteCell(i, data) + end + end + + self.buffer[1].readIndex = readIndex +end + +function ENT:Think() + if self.buffer[1] ~= nil then + local maxtime = SysTime() + (1/RealFrameTime()) * 0.0001 -- do more depending on client FPS. Higher fps = more work + + while SysTime() < maxtime and self.buffer[1] do + self:ProcessBuffer() + end + end + + self:NextThink(CurTime()+0.1) + return true +end + +function ENT:ReadCell(Address,value) + Address = math.floor(Address) + if Address < 0 then return nil end + if Address >= 1048577 then return nil end + + return self.Memory2[Address] +end + +function ENT:WriteCell(Address,value) + Address = math.floor(Address) + if Address < 0 then return false end + if Address >= 1048577 then return false end + + if Address == 1048575 then + self.NewClk = value ~= 0 + elseif Address < 1048500 then + self.IsClear = false + end + + if (self.NewClk) then + self.Memory1[Address] = value -- visible buffer + self.NeedRefresh = true + if self.Memory1[1048569] == 1 then -- R G B mode + local pixelno = math.floor(Address/3) + if self.RefreshPixels[#self.RefreshPixels] ~= pixelno then + self.RefreshPixels[#self.RefreshPixels+1] = pixelno + end + else -- other modes + self.RefreshPixels[#self.RefreshPixels+1] = Address + end + end + self.Memory2[Address] = value -- invisible buffer + + if Address == 1048574 then -- Hardware Clear Screen + local mem1,mem2 = {},{} + for addr = 1048500,1048575 do + mem1[addr] = self.Memory1[addr] + mem2[addr] = self.Memory2[addr] + end + self.Memory1,self.Memory2 = mem1,mem2 + self.IsClear = true + self.ClearQueued = true + self.NeedRefresh = true + elseif Address == 1048572 then + self.ScreenHeight = value + if not self.IsClear then + self.NeedRefresh = true + for i = 1,self.ScreenHeight do + self.RefreshRows[i] = i-1 + end + end + elseif Address == 1048573 then + self.ScreenWidth = value + if not self.IsClear then + self.NeedRefresh = true + for i = 1,self.ScreenHeight do + self.RefreshRows[i] = i-1 + end + end + end + + if self.LastClk ~= self.NewClk then + -- swap the memory if clock changes + self.LastClk = self.NewClk + self.Memory1 = table.Copy(self.Memory2) + + self.NeedRefresh = true + for i=1,self.ScreenHeight do + self.RefreshRows[i] = i-1 + end + end + return true +end + +local transformcolor = {} +transformcolor[0] = function(c) -- RGBXXX + local crgb = math.floor(c / 1000) + local cgray = c - math.floor(c / 1000)*1000 + + cb = cgray+28*math.fmod(crgb, 10) + cg = cgray+28*math.fmod(math.floor(crgb / 10), 10) + cr = cgray+28*math.fmod(math.floor(crgb / 100), 10) + + return cr, cg, cb +end +transformcolor[2] = function(c) -- 24 bit mode + cb = math.fmod(c, 256) + cg = math.fmod(math.floor(c / 256), 256) + cr = math.fmod(math.floor(c / 65536), 256) + + return cr, cg, cb +end +transformcolor[3] = function(c) -- RRRGGGBBB + cb = math.fmod(c, 1000) + cg = math.fmod(math.floor(c / 1e3), 1000) + cr = math.fmod(math.floor(c / 1e6), 1000) + + return cr, cg, cb +end +transformcolor[4] = function(c) -- XXX + return c, c, c +end + +local floor = math.floor + +function ENT:RedrawPixel(a) + if a >= self.ScreenWidth*self.ScreenHeight then return end + + local cr,cg,cb + + local x = a % self.ScreenWidth + local y = math.floor(a / self.ScreenWidth) + + local colormode = self.Memory1[1048569] or 0 + + if colormode == 1 then + cr = self.Memory1[a*3 ] or 0 + cg = self.Memory1[a*3+1] or 0 + cb = self.Memory1[a*3+2] or 0 + else + local c = self.Memory1[a] or 0 + cr, cg, cb = (transformcolor[colormode] or transformcolor[0])(c) + end + + local xstep = (512/self.ScreenWidth) + local ystep = (512/self.ScreenHeight) + + surface.SetDrawColor(cr,cg,cb,255) + local tx, ty = floor(x*xstep), floor(y*ystep) + surface.DrawRect( tx, ty, floor((x+1)*xstep-tx), floor((y+1)*ystep-ty) ) +end + +function ENT:RedrawRow(y) + local xstep = (512/self.ScreenWidth) + local ystep = (512/self.ScreenHeight) + if y >= self.ScreenHeight then return end + local a = y*self.ScreenWidth + + local colormode = self.Memory1[1048569] or 0 + + for x = 0,self.ScreenWidth-1 do + local cr,cg,cb + + if (colormode == 1) then + cr = self.Memory1[(a+x)*3 ] or 0 + cg = self.Memory1[(a+x)*3+1] or 0 + cb = self.Memory1[(a+x)*3+2] or 0 + else + local c = self.Memory1[a+x] or 0 + cr, cg, cb = (transformcolor[colormode] or transformcolor[0])(c) + end + + surface.SetDrawColor(cr,cg,cb,255) + local tx, ty = floor(x*xstep), floor(y*ystep) + surface.DrawRect( tx, ty, floor((x+1)*xstep-tx), floor((y+1)*ystep-ty) ) + end +end + +function ENT:Draw() + self:DrawModel() + + if self.NeedRefresh then + self.NeedRefresh = false + + self.GPU:RenderToGPU(function() + local pixels = 0 + local idx = 0 + + if self.ClearQueued then + surface.SetDrawColor(0,0,0,255) + surface.DrawRect(0,0, 512,512) + self.ClearQueued = false + end + + if (#self.RefreshRows > 0) then + idx = #self.RefreshRows + while ((idx > 0) and (pixels < 8192)) do + self:RedrawRow(self.RefreshRows[idx]) + self.RefreshRows[idx] = nil + idx = idx - 1 + pixels = pixels + self.ScreenWidth + end + else + idx = #self.RefreshPixels + while ((idx > 0) and (pixels < 8192)) do + self:RedrawPixel(self.RefreshPixels[idx]) + self.RefreshPixels[idx] = nil + idx = idx - 1 + pixels = pixels + 1 + end + end + if idx ~= 0 then + self.NeedRefresh = true + end + end) + end + + self.GPU:Render() + Wire_Render(self) +end + +function ENT:IsTranslucent() + return true +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/init.lua new file mode 100644 index 0000000..32de7e5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/init.lua @@ -0,0 +1,299 @@ +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) +include('shared.lua') + +ENT.WireDebugName = "DigitalScreen" + +function ENT:Initialize() + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.Inputs = Wire_CreateInputs(self, { "PixelX", "PixelY", "PixelG", "Clk", "FillColor", "ClearRow", "ClearCol" }) + self.Outputs = Wire_CreateOutputs(self, { "Memory" }) + + self.Memory = {} + + self.PixelX = 0 + self.PixelY = 0 + self.PixelG = 0 + self.Memory[1048569] = 0 + self.Memory[1048575] = 1 + + self.ScreenWidth = 32 + self.ScreenHeight = 32 + + self.NumOfWrites = 0 + + self.ChangedCellRanges = {} +end + +function ENT:Setup(ScreenWidth, ScreenHeight) + self:WriteCell(1048572, ScreenHeight or 32) + self:WriteCell(1048573, ScreenWidth or 32) +end + +function ENT:SendPixel() + if self.Memory[1048575] == 0 then return end -- why? + if self.PixelX < 0 then return end + if self.PixelY < 0 then return end + if self.PixelX >= self.ScreenWidth then return end + if self.PixelY >= self.ScreenHeight then return end + + local address = self.PixelY*self.ScreenWidth + self.PixelX + self:WriteCell(address, self.PixelG) +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if Address < 0 then return nil end + if Address >= 1048577 then return nil end + + return self.Memory[Address] or 0 +end + +function ENT:MarkCellChanged(Address) + self.NumOfWrites = self.NumOfWrites + 1 + + local lastrange = self.ChangedCellRanges[#self.ChangedCellRanges] + if lastrange then + if Address == lastrange.start + lastrange.length then + -- wrote just after the end of the range, append + lastrange.length = lastrange.length + 1 + elseif Address == lastrange.start - 1 then + -- wrote just before the start of the range, prepend + lastrange.start = lastrange.start - 1 + lastrange.length = lastrange.length + 1 + elseif Address < lastrange.start - 1 or Address > lastrange.start + lastrange.length then + -- wrote outside the range + lastrange = nil + end + end + if not lastrange then + lastrange = { + start = Address, + length = 1 + } + self.ChangedCellRanges[#self.ChangedCellRanges + 1] = lastrange + end +end + +local function numberToString(t, number, bytes) + local str = {} + for j=1,bytes do + str[#str+1] = string.char(number % 256) + number = math.floor(number / 256) + end + t[#t+1] = table.concat(str) +end + +local function buildData(datastr, memory, pixelbit, range, bytesRemaining) + if bytesRemaining < 15 then return 0 end + local lengthIndex = #datastr+1 + datastr[lengthIndex] = "000" + numberToString(datastr,range.start,3) -- Address of range + bytesRemaining = bytesRemaining - 6 + local i, iend = range.start, range.start + range.length + while i0 do + if i>=1048500 then + numberToString(datastr,memory[i],2) + bytesRemaining = bytesRemaining - 2 + else + numberToString(datastr,memory[i],pixelbit) + bytesRemaining = bytesRemaining - pixelbit + end + i = i + 1 + end + local lengthStr = {} + numberToString(lengthStr,i - range.start,3) -- Length of range + datastr[lengthIndex] = lengthStr[1] + range.length = iend - i + range.start = i + + return bytesRemaining +end + +util.AddNetworkString("wire_digitalscreen") + +local pixelbits = {3, 1, 3, 4, 1} --The compressed pixel formats are in bytes +function ENT:FlushCache(ply) + if not next(self.ChangedCellRanges) then return end + + if not ply then + -- If the user is writing a lot of data, activate buffering + if self.NumOfWrites > 4000 then + self.UseBuffering = true + else + self.UseBuffering = nil + end + + if self.UseBuffering then + -- This section allows the data to build up until + -- the user stops writing data, or up to three seconds + if not self.WaitToFlush then + self.WaitToFlush = CurTime() + 3 + return + elseif self.WaitToFlush >= CurTime() then + if self.NumOfWrites > 0 then + return + end + end + end + + self.NumOfWrites = 0 + end + + self.WaitToFlush = nil + + local pixelformat = (math.floor(self.Memory[1048569]) or 0) + 1 + if pixelformat < 1 or pixelformat > #pixelbits then pixelformat = 1 end + local pixelbit = pixelbits[pixelformat] + local bytesRemaining = 32768 + local datastr = {} + + local range = self.ChangedCellRanges[1] + while range and bytesRemaining>0 do + bytesRemaining = buildData(datastr, self.Memory, pixelbit, range, bytesRemaining) + if range.length==0 then + table.remove(self.ChangedCellRanges, 1) + range = self.ChangedCellRanges[1] + end + end + + numberToString(datastr,0,3) + datastr = util.Compress(table.concat(datastr)) + + net.Start("wire_digitalscreen") + net.WriteUInt(self:EntIndex(),16) + net.WriteUInt(pixelformat, 5) + net.WriteUInt(#datastr, 32) + net.WriteData(datastr,#datastr) + + if ply then net.Send(ply) else net.Broadcast() end +end + +function ENT:Retransmit(ply) + self:FlushCache() -- Empty the cache + + self:MarkCellChanged(1048569) -- Colormode + self:MarkCellChanged(1048572) -- Screen Width + self:MarkCellChanged(1048573) -- Screen Height + self:MarkCellChanged(1048575) -- Clk + self:FlushCache(ply) + + local memory = self.Memory + for addr=0, self.ScreenWidth*self.ScreenHeight do + if memory[addr] then + self:MarkCellChanged(addr) + end + end + self:MarkCellChanged(1048575) -- Clk + self:FlushCache(ply) +end + +function ENT:ClearPixel(i) + if self.Memory[1048569] == 1 then + -- R G B mode + self.Memory[i*3] = 0 + self.Memory[i*3+1] = 0 + self.Memory[i*3+2] = 0 + return + end + + -- other modes + self.Memory[i] = 0 +end + +function ENT:ClearCellRange(start, length) + for i = start, start + length - 1 do + self.Memory[i] = 0 + end +end + +function ENT:WriteCell(Address, value) + Address = math.floor (Address) + if Address < 0 then return false end + if Address >= 1048577 then return false end + + if Address < 1048500 then -- RGB data + if self.Memory[Address] == value or + (value == 0 and self.Memory[Address] == nil) then + return true + end + else + if Address == 1048569 then + -- Color mode (0: RGBXXX; 1: R G B; 2: 24 bit RGB; 3: RRRGGGBBB; 4: XXX) + value = math.Clamp(math.floor(value or 0), 0, 9) + elseif Address == 1048570 then -- Clear row + local row = math.Clamp(math.floor(value), 0, self.ScreenHeight-1) + if self.Memory[1048569] == 1 then + self:ClearCellRange(row*self.ScreenWidth*3, self.ScreenWidth*3) + else + self:ClearCellRange(row*self.ScreenWidth, self.ScreenWidth) + end + elseif Address == 1048571 then -- Clear column + local col = math.Clamp(math.floor(value), 0, self.ScreenWidth-1) + for i = col,col+self.ScreenWidth*(self.ScreenHeight-1),self.ScreenWidth do + self:ClearPixel(i) + end + elseif Address == 1048572 then -- Height + self.ScreenHeight = math.Clamp(math.floor(value), 1, 512) + elseif Address == 1048573 then -- Width + self.ScreenWidth = math.Clamp(math.floor(value), 1, 512) + elseif Address == 1048574 then -- Hardware Clear Screen + local mem = {} + for addr = 1048500,1048575 do + mem[addr] = self.Memory[addr] + end + self.Memory = mem + -- clear pixel data from usermessage queue + local i = 1 + while self.ChangedCellRanges[i] ~= nil do + if self.ChangedCellRanges[i].start + self.ChangedCellRanges[i].length < 1048500 then + table.remove(self.ChangedCellRanges, i) + else + i = i + 1 + end + end + elseif Address == 1048575 then -- CLK + -- not needed atm + end + end + + self.Memory[Address] = value + + self:MarkCellChanged(Address) + + return true +end + +function ENT:Think() + self:FlushCache() + self:NextThink(CurTime()+1) + return true +end + +function ENT:TriggerInput(iname, value) + if (iname == "PixelX") then + self.PixelX = math.floor(value) + self:SendPixel() + elseif (iname == "PixelY") then + self.PixelY = math.floor(value) + self:SendPixel() + elseif (iname == "PixelG") then + self.PixelG = math.floor(value) + self:SendPixel() + elseif (iname == "Clk") then + self:WriteCell(1048575, value) + self:SendPixel() + elseif (iname == "FillColor") then + self:WriteCell(1048574,value) + elseif (iname == "ClearCol") then + self:WriteCell(1048571,math.Clamp( value, 0, 31 )) + elseif (iname == "ClearRow") then + self:WriteCell(1048570,math.Clamp( value, 0, 31 )) + end +end + +duplicator.RegisterEntityClass("gmod_wire_digitalscreen", WireLib.MakeWireEnt, "Data", "ScreenWidth", "ScreenHeight") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/shared.lua new file mode 100644 index 0000000..ff762fd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_digitalscreen/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire Digital Screen" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dual_input.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dual_input.lua new file mode 100644 index 0000000..2b16281 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dual_input.lua @@ -0,0 +1,94 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Dual Input" +ENT.WireDebugName = "Dual Input" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = Wire_CreateOutputs(self, { "Out" }) +end + +function ENT:Setup(keygroup, keygroup2, toggle, value_off, value_on, value_on2) + self.keygroup = keygroup + self.keygroup2 = keygroup2 + + local pl = self:GetPlayer() + numpad.OnDown( pl, keygroup, "WireDualInput_On", self, 1 ) + numpad.OnUp( pl, keygroup, "WireDualInput_Off", self, 1 ) + numpad.OnDown( pl, keygroup2, "WireDualInput_On", self, -1 ) + numpad.OnUp( pl, keygroup2, "WireDualInput_Off", self, -1 ) + + self.toggle = (toggle == 1 || toggle == true) + self.value_off = value_off + self.value_on = value_on + self.value_on2 = value_on2 + self.Value = value_off + self.Select = 0 + + self:ShowOutput(self.value_off) + Wire_TriggerOutput(self, "Out", self.value_off) +end + +function ENT:InputActivate( mul ) + if ( self.toggle && self.Select == mul ) then + return self:Switch( !self.On, mul ) + end + + return self:Switch( true, mul ) +end + +function ENT:InputDeactivate( mul ) + if ( self.toggle ) then return true end + + return self:Switch( false, mul ) +end + +function ENT:Switch( on, mul ) + if (!self:IsValid()) then return false end + + self.On = on + self.Select = mul + + if (on && mul == 1) then + self:ShowOutput(self.value_on) + self.Value = self.value_on + elseif (on && mul == -1) then + self:ShowOutput(self.value_on2) + self.Value = self.value_on2 + else + self:ShowOutput(self.value_off) + self.Value = self.value_off + end + + Wire_TriggerOutput(self, "Out", self.Value) + + return true +end + +function ENT:ShowOutput(value) + self:SetOverlayText( "(" .. self.value_on2 .. " - " .. self.value_off .. " - " .. self.value_on .. ") = " .. value ) +end + +local function On( pl, ent, mul ) + if (!ent:IsValid()) then return false end + pl = player.GetBySteamID(pl) + if not gamemode.Call("PlayerUse", pl, ent) then return end + return ent:InputActivate( mul ) +end + +local function Off( pl, ent, mul ) + if (!ent:IsValid()) then return false end + pl = player.GetBySteamID(pl) + if not gamemode.Call("PlayerUse", pl, ent) then return end + return ent:InputDeactivate( mul ) +end + +numpad.Register( "WireDualInput_On", On ) +numpad.Register( "WireDualInput_Off", Off ) + +duplicator.RegisterEntityClass("gmod_wire_dual_input", WireLib.MakeWireEnt, "Data", "keygroup", "keygroup2", "toggle", "value_off", "value_on", "value_on2", "frozen") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dynamic_button.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dynamic_button.lua new file mode 100644 index 0000000..f1b6409 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_dynamic_button.lua @@ -0,0 +1,171 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Dynamic Button" +ENT.WireDebugName = "Dynamic Button" + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "On" ) +end + + +if CLIENT then + local halo_ent, halo_blur + + function ENT:Draw() + self:DoNormalDraw(true,false) + if LocalPlayer():GetEyeTrace().Entity == self and EyePos():Distance( self:GetPos() ) < 512 then + if self:GetOn() then + halo_ent = self + halo_blur = 4 + math.sin(CurTime()*20)*2 + else + self:DrawEntityOutline() + end + end + Wire_Render(self) + end + + hook.Add("PreDrawHalos", "Wiremod_dynbutton_overlay_halos", function() + if halo_ent then + halo.Add({halo_ent}, Color(255,100,100), halo_blur, halo_blur, 1, true, true) + halo_ent = nil + end + end) + + return -- No more client +end + +ENT.OutputEntID = false +ENT.EntToOutput = NULL + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.Outputs = Wire_CreateOutputs(self, { "Out" }) + self.Inputs = Wire_CreateInputs(self, { "Set" }) +end + +function ENT:TriggerInput(iname, value) + if iname == "Set" then + if (self.toggle) then + self:Switch(value ~= 0) + self.PrevUser = nil + self.podpress = nil + end + end +end + +function ENT:Use(ply, caller) + if (not ply:IsPlayer()) then return end + if (self.PrevUser) and (self.PrevUser:IsValid()) then return end + if self.OutputEntID then + self.EntToOutput = ply + end + if (self:GetOn()) then + if (self.toggle) then self:Switch(false) end + + return + end + if IsValid(caller) and caller:GetClass() == "gmod_wire_pod" then + self.podpress = true + end + + self:Switch(true) + self.PrevUser = ply +end + +function ENT:Think() + BaseClass.Think(self) + + if self:GetOn() then + if (not self.PrevUser) + or (not self.PrevUser:IsValid()) + or (not self.podpress and not self.PrevUser:KeyDown(IN_USE)) + or (self.podpress and not self.PrevUser:KeyDown( IN_ATTACK )) then + if (not self.toggle) then + self:Switch(false) + end + + self.PrevUser = nil + self.podpress = nil + end + + self:NextThink(CurTime()+0.05) + return true + end +end + +function ENT:Setup(toggle, value_on, value_off, description, entityout, material_on, material_off, on_r, on_g, on_b, off_r, off_g, off_b ) + self.toggle = toggle + self.value_off = value_off + self.value_on = value_on + self.Value = value_off + self.entityout = entityout + self.material_on = material_on + self.material_off = material_off + self:SetOn( false ) + self.on_r = on_r + self.on_g = on_g + self.on_b = on_b + self.off_r = off_r + self.off_g = off_g + self.off_b = off_b + + self:ShowOutput(self.value_off) + Wire_TriggerOutput(self, "Out", self.value_off) + + self:SetMaterial(self.material_off) + self:SetColor(Color(self.off_r, self.off_g, self.off_b, 255)) + + if entityout then + WireLib.AdjustSpecialOutputs(self, { "Out", "EntID" , "Entity" }, { "NORMAL", "NORMAL" , "ENTITY" }) + Wire_TriggerOutput(self, "EntID", 0) + Wire_TriggerOutput(self, "Entity", nil) + self.OutputEntID=true + else + Wire_AdjustOutputs(self, { "Out" }) + self.OutputEntID=false + end + + if toggle then + Wire_AdjustInputs(self, { "Set" }) + else + Wire_AdjustInputs(self, {}) + end +end + +function ENT:Switch(on) + if (not self:IsValid()) then return end + + self:SetOn( on ) + + if (on) then + self:ShowOutput(self.value_on) + self.Value = self.value_on + if self.material_on ~= "" then self:SetMaterial(self.material_on) end + self:SetColor(Color(self.on_r, self.on_g, self.on_b, 255)) + + else + self:ShowOutput(self.value_off) + self.Value = self.value_off + if self.material_off ~= "" then self:SetMaterial(self.material_off) end + self:SetColor(Color(self.off_r, self.off_g, self.off_b, 255)) + + if self.OutputEntID then self.EntToOutput = NULL end + end + + Wire_TriggerOutput(self, "Out", self.Value) + if self.OutputEntID then + Wire_TriggerOutput(self, "EntID", self.EntToOutput:EntIndex()) + Wire_TriggerOutput(self, "Entity", self.EntToOutput) + end + return true +end + +function ENT:ShowOutput(value) + self:SetOverlayText( "(" .. self.value_off .. " - " .. self.value_on .. ") = " .. value ) +end + +duplicator.RegisterEntityClass("gmod_wire_dynamic_button", WireLib.MakeWireEnt, "Data", "toggle", "value_on", "value_off", "description", "entityout", "material_on", "material_off", "on_r", "on_g", "on_b", "off_r", "off_g", "off_b" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/cl_init.lua new file mode 100644 index 0000000..c5b47fc --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/cl_init.lua @@ -0,0 +1,82 @@ +include('shared.lua') + +ENT.gmod_wire_egp = true + +function ENT:Initialize() + self.GPU = GPULib.WireGPU( self ) + self.GPU.texture_filtering = TEXFILTER.ANISOTROPIC + + self.RenderTable = {} + self:EGP_Update( EGP.HomeScreen ) +end + +function ENT:EGP_Update( Table ) + self.NeedsUpdate = true + self.NextUpdate = Table +end + +function ENT:_EGP_Update( bool ) + self.NeedsUpdate = nil + local Table = self.NextUpdate or self.RenderTable + + if not Table then return end + self.UpdateConstantly = nil + + self.GPU:RenderToGPU( function() + render.Clear( 0, 0, 0, 0, true ) + --render.ClearRenderTarget( 0, 0, 0, 0 ) + + local currentfilter = self.GPU.texture_filtering + + local mat = self:GetEGPMatrix() + + for k,v in pairs( Table ) do + if (v.parent == -1) then self.UpdateConstantly = true end -- Check if an object is parented to the cursor + if (v.parent and v.parent != 0) then + if (!v.IsParented) then EGP:SetParent( self, v.index, v.parent ) end + local _, data = EGP:GetGlobalPos( self, v.index ) + EGP:EditObject( v, data ) + elseif ((!v.parent or v.parent == 0) and v.IsParented) then + EGP:UnParent( self, v.index ) + end + local oldtex = EGP:SetMaterial( v.material ) + + if v.filtering != currentfilter then + render.PopFilterMin() + render.PopFilterMag() + render.PushFilterMag(v.filtering) + render.PushFilterMin(v.filtering) + currentfilter = v.filtering + end + + v:Draw(self, mat) + EGP:FixMaterial( oldtex ) + end + end) +end + +function ENT:GetEGPMatrix() + return Matrix() +end + +function ENT:DrawEntityOutline() end + +function ENT:Draw() + self:DrawModel() + Wire_Render(self) + if self.UpdateConstantly or self.NeedsUpdate then + self:_EGP_Update() + end + + -- check if translucent setting changed + if self.GPU.translucent ~= self:GetTranslucent() then + self.GPU.translucent = self:GetTranslucent() + self:_EGP_Update() + end + + self.GPU:Render() +end + +function ENT:OnRemove() + self.GPU:Finalize() +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/init.lua new file mode 100644 index 0000000..f819a06 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/init.lua @@ -0,0 +1,50 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include('shared.lua') + +ENT.WireDebugName = "E2 Graphics Processor" + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType( SIMPLE_USE ) + + self.RenderTable = {} + + WireLib.CreateOutputs( self, { "User [ENTITY]" } ) + WireLib.CreateWirelinkOutput( nil, self, {true} ) + + self.xScale = { 0, 512 } + self.yScale = { 0, 512 } + self.Scaling = false + + self.TopLeft = false + self.GPU_texture_filtering = TEXFILTER.ANISOTROPIC +end + +function ENT:Use( ply ) + WireLib.TriggerOutput( self, "User", ply ) +end + +function ENT:Think() + WireLib.TriggerOutput( self, "User", nil ) +end + +function ENT:SetEGPOwner( ply ) + self.ply = ply + self.plyID = IsValid(ply) and ply:UniqueID() or "World" +end + +function ENT:GetEGPOwner() + if (!self.ply or !self.ply:IsValid()) then + local ply = player.GetByUniqueID( self.plyID ) + if (ply) then self.ply = ply end + return ply + else + return self.ply + end + return false +end + +function ENT:UpdateTransmitState() return TRANSMIT_ALWAYS end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/framecontrol.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/framecontrol.lua new file mode 100644 index 0000000..2f26602 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/framecontrol.lua @@ -0,0 +1,26 @@ +-------------------------------------------------------- +-- Frame Saving & Loading +-------------------------------------------------------- + +local EGP = EGP + +EGP.Frames = {} + +function EGP:SaveFrame( ply, Ent, index ) + if (!EGP.Frames[ply]) then EGP.Frames[ply] = {} end + EGP.Frames[ply][index] = table.Copy(Ent.RenderTable) +end + +function EGP:LoadFrame( ply, Ent, index ) + if (!EGP.Frames[ply]) then EGP.Frames[ply] = {} return false end + if (SERVER) then + local bool = (EGP.Frames[ply][index] != nil) + if (!bool) then return false end + return true, table.Copy(EGP.Frames[ply][index]) + else + local frame = EGP.Frames[ply][index] + if (!frame) then return false end + Ent.RenderTable = table.Copy(frame) + Ent:EGP_Update() + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/materials.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/materials.lua new file mode 100644 index 0000000..d1d6aac --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/materials.lua @@ -0,0 +1,32 @@ +-------------------------------------------------------- +-- Materials (And fonts) +-------------------------------------------------------- +local EGP = EGP + +-- Valid fonts table +EGP.ValidFonts_Lookup = {} +if (CLIENT) then + + local type = type + local SetMaterial = surface.SetMaterial + local NoTexture = draw.NoTexture + + function EGP:SetMaterial( Mat ) + if type(Mat) == "IMaterial" then + SetMaterial( Mat ) + elseif isentity(Mat) then + if (!Mat:IsValid() or !Mat.GPU or !Mat.GPU.RT) then NoTexture() return end + local OldTex = WireGPU_matScreen:GetTexture("$basetexture") + WireGPU_matScreen:SetTexture("$basetexture", Mat.GPU.RT) + SetMaterial(WireGPU_matScreen) + return OldTex + else + NoTexture() + end + end + + function EGP:FixMaterial( OldTex ) + if (!OldTex) then return end + WireGPU_matScreen:SetTexture("$basetexture", OldTex) + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/objectcontrol.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/objectcontrol.lua new file mode 100644 index 0000000..27fb123 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/objectcontrol.lua @@ -0,0 +1,278 @@ +-------------------------------------------------------- +-- Objects +-------------------------------------------------------- +local EGP = EGP + +EGP.Objects = {} +EGP.Objects.Names = {} +EGP.Objects.Names_Inverted = {} + +-- This object is not used. It's only a base +EGP.Objects.Base = {} +EGP.Objects.Base.ID = 0 +EGP.Objects.Base.x = 0 +EGP.Objects.Base.y = 0 +EGP.Objects.Base.w = 0 +EGP.Objects.Base.h = 0 +EGP.Objects.Base.r = 255 +EGP.Objects.Base.g = 255 +EGP.Objects.Base.b = 255 +EGP.Objects.Base.a = 255 +EGP.Objects.Base.filtering = TEXFILTER.ANISOTROPIC +EGP.Objects.Base.material = "" +if CLIENT then EGP.Objects.Base.material = false end +EGP.Objects.Base.parent = 0 +EGP.Objects.Base.Transmit = function( self ) + EGP:SendPosSize( self ) + EGP:SendColor( self ) + EGP:SendMaterial( self ) + net.WriteUInt(math.Clamp(self.filtering,0,3), 2) + net.WriteInt( self.parent, 16 ) +end +EGP.Objects.Base.Receive = function( self ) + local tbl = {} + EGP:ReceivePosSize( tbl ) + EGP:ReceiveColor( tbl, self ) + EGP:ReceiveMaterial( tbl ) + tbl.filtering = net.ReadUInt(2) + tbl.parent = net.ReadInt(16) + return tbl +end +EGP.Objects.Base.DataStreamInfo = function( self ) + return { x = self.x, y = self.y, w = self.w, h = self.h, r = self.r, g = self.g, b = self.b, a = self.a, material = self.material, filtering = self.filtering, parent = self.parent } +end +function EGP.Objects.Base:Contains(point) + return false +end + +---------------------------- +-- Get Object +---------------------------- + +function EGP:GetObjectByID( ID ) + for _, v in pairs( EGP.Objects ) do + if (v.ID == ID) then return table.Copy( v ) end + end + ErrorNoHalt( "[EGP] Error! Object with ID '" .. ID .. "' does not exist. Please post this bug message in the EGP thread on the wiremod forums.\n" ) +end + +---------------------------- +-- Load all objects +---------------------------- + +function EGP:NewObject( Name ) + if self.Objects[Name] then return self.Objects[Name] end + + -- Create table + self.Objects[Name] = {} + -- Set info + self.Objects[Name].Name = Name + table.Inherit( self.Objects[Name], self.Objects.Base ) + + -- Create lookup table + local ID = table.Count(self.Objects) + self.Objects[Name].ID = ID + self.Objects.Names[Name] = ID + + -- Inverted lookup table + self.Objects.Names_Inverted[ID] = Name + + return self.Objects[Name] +end + +local folder = "entities/gmod_wire_egp/lib/objects/" +local files = file.Find(folder.."*.lua", "LUA") +table.sort( files ) +for _,v in pairs( files ) do + include(folder..v) + if (SERVER) then AddCSLuaFile(folder..v) end +end + +---------------------------- +-- Object existance check +---------------------------- +function EGP:HasObject( Ent, index ) + if not EGP:ValidEGP(Ent) then return false end + if SERVER then index = math.Round(math.Clamp(index or 1, 1, self.ConVars.MaxObjects:GetInt())) end + if not Ent.RenderTable or #Ent.RenderTable == 0 then return false end + for k,v in pairs( Ent.RenderTable ) do + if (v.index == index) then + return true, k, v + end + end + return false +end + +---------------------------- +-- Object order changing +---------------------------- +function EGP:SetOrder( Ent, from, to, dir ) + if not Ent.RenderTable or #Ent.RenderTable == 0 then return false end + dir = dir or 0 + + if Ent.RenderTable[from] then + to = math.Clamp(math.Round(to or 1),1,#Ent.RenderTable) + if SERVER then Ent.RenderTable[from].ChangeOrder = {target=to,dir=dir} end + return true + end + return false +end + +local already_reordered = {} +function EGP:PerformReorder_Ex( Ent, i ) + local obj = Ent.RenderTable[i] + if obj then + -- Check if this object has already been reordered + if already_reordered[obj.index] then + -- if yes, get its new position (or old position if it didn't change) + return already_reordered[obj.index] + end + + -- Set old position (to prevent recursive loops) + already_reordered[obj.index] = i + + if obj.ChangeOrder then + local target = obj.ChangeOrder.target + local dir = obj.ChangeOrder.dir + + local target_idx = 0 + if dir == 0 then + -- target is absolute position + target_idx = target + else + -- target is relative position + local bool, k = self:HasObject( Ent, target ) + if bool then + -- Check for order dependencies + k = self:PerformReorder_Ex( Ent, k ) or k + + target_idx = k + dir + end + end + + if target_idx ~= 0 then + -- Make a copy of the object and insert it at the new position + local copy = table.Copy(obj) + copy.ChangeOrder = nil + table.insert( Ent.RenderTable, target_idx, copy ) + + -- Update already reordered reference to new position + already_reordered[obj.index] = target_idx + + return target_idx + else + return i + end + end + end +end + +function EGP:PerformReorder( Ent ) + -- Reset, just to be sure + already_reordered = {} + + -- First pass, insert objects at their wanted position + for i=1,#Ent.RenderTable do + self:PerformReorder_Ex( Ent, i ) + end + + -- Second pass, remove objects from their original positions + for i=#Ent.RenderTable,1,-1 do + local obj = Ent.RenderTable[i] + if obj.ChangeOrder then + table.remove( Ent.RenderTable, i ) + end + end + + -- Clear some memory + already_reordered = {} +end + +---------------------------- +-- Create / edit objects +---------------------------- + +function EGP:CreateObject( Ent, ObjID, Settings ) + if not self:ValidEGP(Ent) then return false end + + if not self.Objects.Names_Inverted[ObjID] then + ErrorNoHalt("Trying to create nonexistant object! Please report this error to Divran at wiremod.com. ObjID: " .. ObjID .. "\n") + return false + end + + if SERVER then Settings.index = math.Round(math.Clamp(Settings.index or 1, 1, self.ConVars.MaxObjects:GetInt())) end + + local bool, k, v = self:HasObject( Ent, Settings.index ) + if (bool) then -- Already exists. Change settings: + if v.ID ~= ObjID then -- Not the same kind of object, create new + local Obj = self:GetObjectByID( ObjID ) + self:EditObject( Obj, Settings ) + Obj.index = Settings.index + Ent.RenderTable[k] = Obj + return true, Obj + else + return self:EditObject( v, Settings ), v + end + else -- Did not exist. Create: + local Obj = self:GetObjectByID( ObjID ) + self:EditObject( Obj, Settings ) + Obj.index = Settings.index + table.insert( Ent.RenderTable, Obj ) + return true, Obj + end +end + +function EGP:EditObject( Obj, Settings ) + local ret = false + for k,v in pairs( Settings ) do + if (Obj[k] ~= nil and Obj[k] ~= v) then + Obj[k] = v + ret = true + end + end + return ret +end + + + +-------------------------------------------------------- +-- Homescreen +-------------------------------------------------------- + +EGP.HomeScreen = {} + +local mat +if CLIENT then mat = Material else mat = function( str ) return str end end + +-- Create table +local tbl = { + { ID = EGP.Objects.Names["Box"], Settings = { x = 256, y = 256, h = 356, w = 356, material = mat("expression 2/cog"), r = 150, g = 34, b = 34, a = 255 } }, + { ID = EGP.Objects.Names["Text"], Settings = {x = 256, y = 256, text = "EGP 3", font = "WireGPU_ConsoleFont", valign = 1, halign = 1, size = 50, r = 135, g = 135, b = 135, a = 255 } } +} + +--[[ Old homescreen (EGP v2 home screen design contest winner) +local tbl = { + { ID = EGP.Objects.Names["Box"], Settings = { x = 256, y = 256, w = 362, h = 362, material = true, angle = 135, r = 75, g = 75, b = 200, a = 255 } }, + { ID = EGP.Objects.Names["Box"], Settings = { x = 256, y = 256, w = 340, h = 340, material = true, angle = 135, r = 10, g = 10, b = 10, a = 255 } }, + { ID = EGP.Objects.Names["Text"], Settings = { x = 229, y = 28, text = "E", size = 100, fontid = 4, r = 200, g = 50, b = 50, a = 255 } }, + { ID = EGP.Objects.Names["Text"], Settings = { x = 50, y = 200, text = "G", size = 100, fontid = 4, r = 200, g = 50, b = 50, a = 255 } }, + { ID = EGP.Objects.Names["Text"], Settings = { x = 400, y = 200, text = "P", size = 100, fontid = 4, r = 200, g = 50, b = 50, a = 255 } }, + { ID = EGP.Objects.Names["Text"], Settings = { x = 228, y = 375, text = "2", size = 100, fontid = 4, r = 200, g = 50, b = 50, a = 255 } }, + { ID = EGP.Objects.Names["Box"], Settings = { x = 256, y = 256, w = 256, h = 256, material = mat("expression 2/cog"), angle = 45, r = 255, g = 50, b = 50, a = 255 } }, + { ID = EGP.Objects.Names["Box"], Settings = { x = 128, y = 241, w = 256, h = 30, material = true, r = 10, g = 10, b = 10, a = 255 } }, + { ID = EGP.Objects.Names["Box"], Settings = { x = 241, y = 128, w = 30, h = 256, material = true, r = 10, g = 10, b = 10, a = 255 } }, + { ID = EGP.Objects.Names["Circle"], Settings = { x = 256, y = 256, w = 70, h = 70, material = true, r = 255, g = 50, b = 50, a = 255 } }, + { ID = EGP.Objects.Names["Box"], Settings = { x = 256, y = 256, w = 362, h = 362, material = mat("gui/center_gradient"), angle = 135, r = 75, g = 75, b = 200, a = 75 } }, + { ID = EGP.Objects.Names["Box"], Settings = { x = 256, y = 256, w = 362, h = 362, material = mat("gui/center_gradient"), angle = 135, r = 75, g = 75, b = 200, a = 75 } } +} +]] + +-- Convert table +for k,v in pairs( tbl ) do + local obj = EGP:GetObjectByID( v.ID ) + obj.index = k + for k2,v2 in pairs( v.Settings ) do + if obj[k2] ~= nil then obj[k2] = v2 end + end + table.insert( EGP.HomeScreen, obj ) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/parenting.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/parenting.lua new file mode 100644 index 0000000..2d80d8c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/parenting.lua @@ -0,0 +1,289 @@ +-------------------------------------------------------- +-- Parenting functions +-------------------------------------------------------- +local EGP = EGP +EGP.ParentingFuncs = {} + +local function addUV( v, t ) -- Polygon u v fix + if (v.verticesindex) then + local t2 = v[v.verticesindex] + for k,v in ipairs( t ) do + t[k].u = t2[k].u + t[k].v = t2[k].v + end + end +end +EGP.ParentingFuncs.addUV = addUV + +local function makeArray( v, fakepos ) + local ret = {} + if isstring(v.verticesindex) then + if (!fakepos) then + if (!v["_"..v.verticesindex]) then EGP:AddParentIndexes( v ) end + for k,v in ipairs( v["_"..v.verticesindex] ) do + ret[#ret+1] = v.x + ret[#ret+1] = v.y + end + else + for k,v in ipairs( v[v.verticesindex] ) do + ret[#ret+1] = v.x + ret[#ret+1] = v.y + end + end + else + if (!fakepos) then + for k,v2 in ipairs( v.verticesindex ) do + ret[#ret+1] = v["_"..v2[1]] + ret[#ret+1] = v["_"..v2[2]] + end + else + for k,v2 in ipairs( v.verticesindex ) do + ret[#ret+1] = v[v2[1]] + ret[#ret+1] = v[v2[2]] + end + end + end + return ret +end +EGP.ParentingFuncs.makeArray = makeArray + +local function makeTable( v, data ) + local ret = {} + if isstring(v.verticesindex) then + for i=1,#data,2 do + ret[#ret+1] = { x = data[i], y = data[i+1] } + end + else + local n = 1 + for k,v in ipairs( v.verticesindex ) do + ret[v[1]] = data[n] + ret[v[2]] = data[n+1] + n = n + 2 + end + end + return ret +end +EGP.ParentingFuncs.makeTable = makeTable + +local function getCenter( data ) + local centerx, centery = 0, 0 + local n = #data + for i=1, n, 2 do + centerx = centerx + data[i] + centery = centery + data[i+1] + end + return centerx / (n/2), centery / (n/2) +end +EGP.ParentingFuncs.getCenter = getCenter + +-- (returns true if obj has vertices, false if not, followed by the new position data) +function EGP:GetGlobalPos( Ent, index ) + local bool, k, v = self:HasObject( Ent, index ) + if (bool) then + if (v.verticesindex) then -- Object has vertices + if (v.parent and v.parent != 0) then -- Object is parented + if (v.parent == -1) then -- object is parented to the cursor + local xy = {0,0} + if (CLIENT) then + xy = self:EGPCursor( Ent, LocalPlayer() ) + end + local x, y = xy[1], xy[2] + local r = makeArray( v ) + for i=1,#r,2 do + local x_ = r[i] + local y_ = r[i+1] + local vec, ang = LocalToWorld( Vector( x_, y_, 0 ), Angle(), Vector( x, y, 0 ), Angle() ) + r[i] = vec.x + r[i+1] = vec.y + end + local ret = {} + if isstring(v.verticesindex) then + local temp = makeTable( v, r ) + addUV( v, temp ) + ret = { [v.verticesindex] = temp } + else ret = makeTable( v, r ) end + return true, ret + else + local hasVertices, data = self:GetGlobalPos( Ent, v.parent ) + if (hasVertices) then -- obj and parent have vertices + local _, _, prnt = self:HasObject( Ent, v.parent ) + local centerx, centery = getCenter( makeArray( prnt, true ) ) + local temp = makeArray( v ) + for i=1,#temp,2 do + temp[i] = centerx + temp[i] + temp[i+1] = centery + temp[i+1] + end + local ret = {} + if isstring(v.verticesindex) then ret = { [v.verticesindex] = makeTable( v, temp ) } else ret = makeTable( v, temp ) end + return true, ret + else -- obj has vertices, parent does not + local x, y, ang = data.x, data.y, data.angle + local r = makeArray( v ) + for i=1,#r,2 do + local x_ = r[i] + local y_ = r[i+1] + local vec, ang = LocalToWorld( Vector( x_, y_, 0 ), Angle( 0, 0, 0 ), Vector( x, y, 0 ), Angle( 0, -ang, 0 ) ) + r[i] = vec.x + r[i+1] = vec.y + end + local ret = {} + if isstring(v.verticesindex) then + local temp = makeTable( v, r ) + addUV( v, temp ) + ret = { [v.verticesindex] = temp } + else ret = makeTable( v, r ) end + return true, ret + end + end + local ret = {} + if isstring(v.verticesindex) then ret = { [v.verticesindex] = makeTable( v, makeArray( v ) ) } else ret = makeTable( v, makeArray( v ) ) end + return true, ret + end + local ret = {} + if isstring(v.verticesindex) then ret = { [v.verticesindex] = makeTable( v, makeArray( v ) ) } else ret = makeTable( v, makeArray( v ) ) end + return true, ret + else -- Object does not have vertices, parent does not + if (v.parent and v.parent != 0) then -- Object is parented + if (v.parent == -1) then -- Object is parented to the cursor + local xy = {0,0} + if (CLIENT) then + xy = self:EGPCursor( Ent, LocalPlayer() ) + end + local x, y = xy[1], xy[2] + local vec, ang = LocalToWorld( Vector( v._x, v._y, 0 ), Angle( 0, v._angle or 0, 0 ), Vector( x, y, 0 ), Angle() ) + return false, { x = vec.x, y = vec.y, angle = -ang.y } + else + local hasVertices, data = self:GetGlobalPos( Ent, v.parent ) + if (hasVertices) then -- obj does not have vertices, parent does + local _, _, prnt = self:HasObject( Ent, v.parent ) + local centerx, centery = getCenter( makeArray( prnt, true ) ) + return false, { x = (v._x or v.x) + centerx, y = (v._y or v.y) + centery, angle = -(v._angle or v.angle) } + else -- Niether have vertices + local x, y, ang = data.x, data.y, data.angle + local vec, ang = LocalToWorld( Vector( v._x, v._y, 0 ), Angle( 0, v._angle or 0, 0 ), Vector( x, y, 0 ), Angle( 0, -(ang or 0), 0 ) ) + return false, { x = vec.x, y = vec.y, angle = -ang.y } + end + end + end + return false, { x = v.x, y = v.y, angle = v.angle or 0 } + end + end + + return false, {x=0,y=0,angle=0} +end + +-------------------------------------------------------- +-- Parenting functions +-------------------------------------------------------- + +function EGP:AddParentIndexes( v ) + if (v.verticesindex) then + -- Copy original positions + if isstring(v.verticesindex) then + v["_"..v.verticesindex] = table.Copy( v[v.verticesindex] ) + else + for k,v2 in ipairs( v.verticesindex ) do + v["_"..v2[1]] = v[v2[1]] + v["_"..v2[2]] = v[v2[2]] + end + end + else + v._x = v.x + v._y = v.y + v._angle = v.angle + end + v.IsParented = true +end + +local function GetChildren( Ent, Obj ) + local ret = {} + for k,v in ipairs( Ent.RenderTable ) do + if (v.parent == Obj.index) then + ret[#ret+1] = v + end + end + return ret +end + +local function CheckParents( Ent, Obj, parentindex, checked ) + if (Obj.index == parentindex) then + return false + end + if (!checked[Obj.index]) then + checked[Obj.index] = true + local ret = true + for k,v in ipairs( GetChildren( Ent, Obj )) do + if (!CheckParents( Ent, v, parentindex, checked )) then + ret = false + break + end + end + return ret + end + return false +end + +function EGP:SetParent( Ent, index, parentindex ) + local bool, k, v = self:HasObject( Ent, index ) + if (bool) then + if (parentindex == -1) then -- Parent to cursor? + if (self:EditObject( v, { parent = parentindex } )) then return true, v end + else + local bool2, k2, v2 = self:HasObject( Ent, parentindex ) + if (bool2) then + self:AddParentIndexes( v ) + + if (SERVER) then parentindex = math.Clamp(parentindex,1,self.ConVars.MaxObjects:GetInt()) end + + -- If it's already parented to that object + if (v.parent and v.parent == parentindex) then return false end + -- If the user is trying to parent it to itself + if (v.parent and v.parent == v.index) then return false end + -- If the user is trying to create a circle of parents, causing an infinite loop + if (!CheckParents( Ent, v, parentindex, {} )) then return false end + + if (self:EditObject( v, { parent = parentindex } )) then return true, v end + end + end + end +end + +function EGP:RemoveParentIndexes( v, hasVertices ) + if (hasVertices) then + -- Remove original positions + if isstring(v.verticesindex) then + v["_"..v.verticesindex] = nil + else + for k,v2 in ipairs( v.verticesindex ) do + v["_"..v2[1]] = nil + v["_"..v2[2]] = nil + end + end + else + v._x = nil + v._y = nil + v._angle = nil + end + v.IsParented = nil +end + +function EGP:UnParent( Ent, index ) + local bool, k, v = false + if isnumber(index) then + bool, k, v = self:HasObject( Ent, index ) + elseif istable(index) then + bool = true + v = index + index = v.index + end + if (bool) then + local hasVertices, data = self:GetGlobalPos( Ent, index ) + self:RemoveParentIndexes( v, hasVertices ) + + if (!v.parent or v.parent == 0) then return false end + + data.parent = 0 + + if (self:EditObject( v, data, Ent:GetPlayer() )) then return true, v end + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/queuesystem.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/queuesystem.lua new file mode 100644 index 0000000..de58388 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/queuesystem.lua @@ -0,0 +1,148 @@ +-------------------------------------------------------- +-- EGP Queue System +-------------------------------------------------------- +local EGP = EGP + +EGP.Queue = {} + +function EGP:AddQueueObject( Ent, ply, Function, Object ) + if (!self.Queue[ply]) then self.Queue[ply] = {} end + local n = #self.Queue[ply] + if (n > 0) then + local LastItem = self.Queue[ply][n] + if (LastItem.Ent == Ent and LastItem.Action == "Object") then + local found = false + for k,v in ipairs( LastItem.Args[1] ) do + if (v.index == Object.index) then + --self:EditObject( v, Object ) + + if (Object.remove) then -- The object has been removed + table.remove( LastItem.Args[1], k ) + elseif (v.ID != Object.ID) then -- Not the same kind of object, create new + found = true + if (v.OnRemove) then v:OnRemove() end + local Obj = self:GetObjectByID( Object.ID ) + self:EditObject( Obj, Object:DataStreamInfo() ) + Obj.index = v.index + if (Obj.OnCreate) then Obj:OnCreate() end + LastItem.Args[1][k] = Obj + else -- Edit + found = true + self:EditObject( v, Object:DataStreamInfo() ) + end + + break + end + end + if (!found) then + LastItem.Args[1][#LastItem.Args[1]+1] = Object + end + else + self:AddQueue( Ent, ply, Function, "Object", { Object } ) + end + else + self:AddQueue( Ent, ply, Function, "Object", { Object } ) + end +end + +function EGP:AddQueue( Ent, ply, Function, Action, ... ) + if (!self.Queue[ply]) then self.Queue[ply] = {} end + local n = #self.Queue[ply] + if (n > 0) then + local LastItem = self.Queue[ply][n] + if (LastItem.Ent == Ent and LastItem.Action == Action) then -- Same item, no point in sending it again + return + end + end + self.Queue[ply][n+1] = { Action = Action, Function = Function, Ent = Ent, Args = {...} } +end + +function EGP:InsertQueueObjects( Ent, ply, Function, Objects ) + if (!self.Queue[ply]) then self.Queue[ply] = {} end + local n = #self.Queue[ply] + if (n > 0) then + local FirstItem = self.Queue[ply][1] + if (FirstItem.Ent == Ent and FirstItem.Action == "Object") then + local Args = FirstItem.Args + for k,v in ipairs( Objects ) do + table.insert( Args, v ) + end + else + self:InsertQueue( Ent, ply, Function, "Object", Objects ) + end + else + self:InsertQueue( Ent, ply, Function, "Object", Objects ) + end +end + +function EGP:InsertQueue( Ent, ply, Function, Action, ... ) + if (!self.Queue[ply]) then self.Queue[ply] = {} end + table.insert( self.Queue[ply], 1, { Action = Action, Function = Function, Ent = Ent, Args = {...} } ) +end + +function EGP:GetNextItem( ply ) + if (!self.Queue[ply]) then return false end + if (#self.Queue[ply] <= 0) then return false end + return table.remove( self.Queue[ply], 1 ) +end + +local AlreadyChecking = 0 + +function EGP:SendQueueItem( ply ) + local NextAction = self:GetNextItem( ply ) + if (NextAction != false) then + local Func = NextAction.Function + local Ent = NextAction.Ent + local Args = NextAction.Args + if (Args and #Args>0) then + Func( Ent, ply, unpack(Args) ) + else + Func( Ent, ply ) + end + + if (CurTime() != AlreadyChecking) then -- Had to use this hacky way of checking, because the E2 triggered 4 times for some strange reason. If anyone can figure out why, go ahead and tell me. + AlreadyChecking = CurTime() + + -- Check if the queue has no more items for this screen + local Items = self:GetQueueItemsForScreen( ply, Ent ) + if (Items and #Items == 0) then + EGP.RunByEGPQueue = 1 + EGP.RunByEGPQueue_Ent = Ent + EGP.RunByEGPQueue_ply = ply + for k,v in ipairs( ents.FindByClass( "gmod_wire_expression2" ) ) do -- Find all E2s + local context = v.context + if (context) then + local owner = context.player + -- Check if friends, whether or not the E2 is already executing, and if the E2 wants to be triggered by the queue system regarding the screen in question. + if (E2Lib.isFriend( ply, owner ) and context.data and context.data.EGP and context.data.EGP.RunOnEGP and context.data.EGP.RunOnEGP[Ent] == true) then + v:Execute() + end + end + end + EGP.RunByEGPQueue_ply = nil + EGP.RunByEGPQueue_Ent = nil + EGP.RunByEGPQueue = nil + end + end + end +end + +timer.Create("EGP_Queue_Process", 1, 0, function() + local removetab = {} + for ply, tab in pairs(EGP.Queue) do + if !IsValid(ply) then removetab[ply] = true continue end + EGP:SendQueueItem(ply) + end + for ply in pairs(removetab) do EGP.Queue[ply] = nil end +end) + +function EGP:GetQueueItemsForScreen( ply, Ent ) + if (!self.Queue[ply]) then return {} end + local ret = {} + for k,v in ipairs( self.Queue[ply] ) do + if (v.Ent == Ent) then + table.insert( ret, v ) + end + end + return ret +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/textlayouter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/textlayouter.lua new file mode 100644 index 0000000..f181980 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/textlayouter.lua @@ -0,0 +1,335 @@ +//---------------------------------------------------------------------------------------------- +// Purpose: contains functionality for wrapping text to a specific width and expanding spaces +// so the text is aligned on both sides +//---------------------------------------------------------------------------------------------- + + + +local TextWrapIndex = {}; +TextWrapIndex.__index = TextWrapIndex; + +// name of the font to use - this should be the last thing you set +AccessorFunc( TextWrapIndex, "m_FontName", "Font" ); +// the contents of this object +AccessorFunc( TextWrapIndex, "m_Text", "Text" ); +// width and height of the box +AccessorFunc( TextWrapIndex, "w", "Wide" ); +AccessorFunc( TextWrapIndex, "h", "Tall" ); +// should text be cut off if it exceeds the height? +AccessorFunc( TextWrapIndex, "m_LimitHeight", "LimitHeight" ); +// the width of the tab character +AccessorFunc( TextWrapIndex, "m_TabWidth", "TabWidth" ); +// spacing between lines +AccessorFunc( TextWrapIndex, "m_Spacing", "Spacing" ); +// position of the object +AccessorFunc( TextWrapIndex, "x", "OffsetX" ); +AccessorFunc( TextWrapIndex, "y", "OffsetY" ); +// string to be appended when text is cut off - +// either if the word is too long or the height is exceeded +// this length of this text is not taken into account, so it should be something short +AccessorFunc( TextWrapIndex, "m_Cutoff", "CutoffText" ); +AccessorFunc( TextWrapIndex, "m_HCutoff", "HeightCutoffText" ); +// should spaces be expanded to justify the text on both sides? +AccessorFunc( TextWrapIndex, "m_Justify", "Justify" ); +// should the last line in a paragraph also be justified? +AccessorFunc( TextWrapIndex, "m_JustifyLast", "JustifyLast" ); + +//---------------------------------------------------------------------------------------------- +// Purpose: creates a new instance of TextWrap +//---------------------------------------------------------------------------------------------- +--function TextWrap( fontName ) +function EGP:TextLayouter( fontName ) + + local self = setmetatable( {}, TextWrapIndex ); + + // defaults + self:SetText( "" ); + self:SetWide( 0 ); + self:SetTall( 0 ); + self:SetLimitHeight( false ); + self:SetTabWidth( 5 ); + self:SetSpacing( 0 ); + self:SetPos( 0, 0 ); + self:SetCutoffText( '-' ); + self:SetHeightCutoffText( ".." ); + self:SetJustify( true ); + self:SetJustifyLast( false ); + --self:SetWrap( true ); + self:SetFont( fontName or "default" ); + + return self; + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: sets the font and caches some size values +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:SetFont( fontName ) + + self.m_FontName = fontName; + + surface.SetFont( fontName ); + + // cache these + self.SpaceW, self.SpaceH = surface.GetTextSize( ' ' ); + self.CharW, self.CharH = surface.GetTextSize( 'W' ); + self.CutoffW, self.CutoffH = surface.GetTextSize( self:GetCutoffText() ); + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: gets the position +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:GetPos() + + return self:GetOffsetX(), self:GetOffsetY(); + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: sets the position +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:SetPos( x, y ) + + self:SetOffsetX( x ); + self:SetOffsetY( y ); + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: gets the size +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:GetSize() + + return self:GetWide(), self:GetTall(); + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: sets the size +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:SetSize( width, height ) + + self:SetWide( width ); + self:SetTall( height ); + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: gets the minimum width, clamp your stuff to this +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:GetOptimalWidth( ) + + return self.CharW + self.SpaceW; + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: draws the wrapped text +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:Draw() + + if( !self.TextData ) then + + self:Reset(); + + end + + surface.SetFont( self:GetFont() ); + + for i, textData in ipairs( self.TextData ) do + + // no need to recompute every time we just want to move it + surface.SetTextPos( self.x + textData[1], self.y + textData[2] ); + surface.DrawText( textData[3] ); + + end + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: recomputes text alignment and return the net height of the text +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:Reset() + + // SplitWord will trigger an infinite loop if the width of the box is less than the + // max character width + self:SetWide( math.max( self:GetOptimalWidth(), self:GetWide() ) ); + + self.TextData = {}; // { x, y, word } + + surface.SetFont( self:GetFont() ); + + local y = 0; + local numWords = 0; + + for paragraph in self:GetText():gmatch( "[^\n]+" ) do + + y, numWords = self:ComputeParagraph( paragraph, y, numWords ); + + end + + if( numWords == 0 ) then return 0; end + + return y + self:GetSpacing() + self.CharH; + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: justifies words from startI to endI so they fit the width +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:JustifyLine( width, startI, endI ) + + if( !self:GetJustify() ) then return; end + + // TODO: figure out why this needs to be here + if( !self.TextData[ startI ] || !self.TextData[ endI ] ) then return; end + + // calculate the new width of the space character + local x = self.TextData[ startI ][1]; + local spaceW = math.max( self.SpaceW, ( self:GetWide() - width - x ) / ( endI - startI ) ); + + for i = startI, endI do + + self.TextData[i][1] = x; + + x = x + surface.GetTextSize( self.TextData[i][3] ) + spaceW; + + end + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: splits long words and appends the cutoff text when needed +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:SplitWord( word, width, x, y, lineWidth, lastI, numWords ) + + // no need to split, return what we got + if( width <= self:GetWide() ) then + + return false, x, y, lineWidth, lastI, numWords; + + end + + local nextX = ( x == 0 and 0 or ( x + self.SpaceW ) ) + width; + + // split up the word until it can fit + while( nextX > self:GetWide() ) do + + // calculate roughly where we want to cut + local cutoffPos = math.floor( ( width - nextX + self:GetWide() ) / width * #word ); + + // only add it if there's something to cut off, else just move it down + if( cutoffPos > 0 ) then + + // split and append the cutoff text + local part1 = word:sub( 1, cutoffPos )..self:GetCutoffText(); + // start again with this word + word = word:sub( cutoffPos + 1 ); + + table.insert( self.TextData, { x, y, part1 } ); + + //self:JustifyLine( lineWidth, lastI, numWords ); + + numWords = numWords + 1; + lastI = numWords; + width = surface.GetTextSize( word ); + + else + + //self:JustifyLine( lineWidth, lastI, numWords ); + + end + + x = 0; + y = y + self.CharH + self:GetSpacing(); + nextX = width + self.SpaceW; + + end + + // insert the remaining part + table.insert( self.TextData, { x, y, word } ); + + // return the modified values + // this makes me hate Lua for not having some sort of pointer or reference features + return true, x + width + self.SpaceW, y, nextX + self.SpaceW, lastI + 1, numWords + 1; + +end + +//---------------------------------------------------------------------------------------------- +// Purpose: extracts words from this paragraph and splits long words properly +//---------------------------------------------------------------------------------------------- +function TextWrapIndex:ComputeParagraph( line, y, numWords ) + + local x = 0; + local lastI = numWords + 1; + local lineWidth = 0; + local splitWord = false; + local onlyLine = true; + + for word in line:gmatch( "[^ ]+" ) do + + local width = surface.GetTextSize( word ); + + // split up words that won't fit even on the next line + splitWord, x, y, lineWidth, lastI, numWords = self:SplitWord( + word, + width, + x, y, + lineWidth, + lastI, + numWords + ); + + + + // the SplitWord method handles this stuff on its own + if( !splitWord ) then + + // this word exceeds the width, move it down + if( ( x + self.SpaceW + width ) >= self:GetWide() ) then + + if (y + self.CharH > self:GetTall()) then + self.TextData[#self.TextData][3] = string.Left( self.TextData[#self.TextData][3], -3 ) .. self:GetHeightCutoffText() + break + end + + // justify the last line + self:JustifyLine( lineWidth, lastI, numWords ); + + x = 0; + y = y + self.CharH + self:GetSpacing(); + lastI = numWords + 1; + lineWidth = 0; + onlyLine = false; + + end + + + + table.insert( self.TextData, { x, y, word } ); + + x = x + self.SpaceW + width; + lineWidth = lineWidth + width; + numWords = numWords + 1; + splitWord = false; + + else + + onlyLine = false; + + end + + end + + // don't justify the last line if it's not the only one + if( onlyLine ) then + + self:JustifyLine( lineWidth, lastI, numWords ); + + end + + // TODO: this sometimes adds an empty line, figure out why + return y + self.CharH + self:GetSpacing(), numWords; + +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/transmitreceive.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/transmitreceive.lua new file mode 100644 index 0000000..1a6fae1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/transmitreceive.lua @@ -0,0 +1,704 @@ +-------------------------------------------------------- +-- Queue Functions +-------------------------------------------------------- +local EGP = EGP + +if (SERVER) then + util.AddNetworkString( "EGP_Transmit_Data" ) + + ---------------------------- + -- Umsgs per second check + ---------------------------- + EGP.IntervalCheck = {} + + function EGP:PlayerDisconnect( ply ) EGP.IntervalCheck[ply] = nil EGP.Queue[ply] = nil end + hook.Add("PlayerDisconnected","EGP_PlayerDisconnect",function( ply ) EGP:PlayerDisconnect( ply ) end) + + + function EGP:CheckInterval( ply ) + if (!self.IntervalCheck[ply]) then self.IntervalCheck[ply] = { bytes = 0, time = 0 } end + + local maxcount = self.ConVars.MaxPerSec:GetInt() + + local tbl = self.IntervalCheck[ply] + tbl.bytes = math.max(0, tbl.bytes - (CurTime() - tbl.time) * maxcount) + tbl.time = CurTime() + return tbl.bytes < maxcount + end + + ---------------------------- + -- Queue functions + ---------------------------- + + util.AddNetworkString( "ClearScreen" ) + local function ClearScreen( Ent, ply ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, ClearScreen, "ClearScreen" ) + return + end + + if (!EGP.umsg.Start( "EGP_Transmit_Data", ply )) then return end + net.WriteEntity( Ent ) + net.WriteString( "ClearScreen" ) + EGP.umsg.End() + + EGP:SendQueueItem( ply ) + end + + util.AddNetworkString( "SaveFrame" ) + local function SaveFrame( Ent, ply, FrameName ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, SaveFrame, "SaveFrame", FrameName ) + return + end + + util.AddNetworkString( FrameName ) + if (!EGP.umsg.Start( "EGP_Transmit_Data", ply )) then return end + net.WriteEntity( Ent ) + net.WriteString( "SaveFrame" ) + net.WriteEntity( ply ) + net.WriteString( FrameName ) + EGP.umsg.End() + + EGP:SendQueueItem( ply ) + end + + util.AddNetworkString( "LoadFrame" ) + local function LoadFrame( Ent, ply, FrameName ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, LoadFrame, "LoadFrame", FrameName ) + return + end + + local bool, _ = EGP:LoadFrame( ply, Ent, FrameName ) + if (!bool) then return end + + if (!EGP.umsg.Start( "EGP_Transmit_Data", ply )) then return end + net.WriteEntity( Ent ) + net.WriteString( "LoadFrame" ) + net.WriteEntity( ply ) + net.WriteString( FrameName ) + EGP.umsg.End() + + EGP:SendQueueItem( ply ) + end + + -- Extra Add Poly queue item, used by poly objects with a lot of vertices in them + util.AddNetworkString( "AddVertex" ) + local function AddVertex( Ent, ply, index, vertices ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, AddVertex, "AddVertex", index, vertices ) + return + end + + local bool, k, v = EGP:HasObject( Ent, index ) + if (bool) then + if (!EGP.umsg.Start("EGP_Transmit_Data", ply)) then return end + net.WriteEntity( Ent ) + net.WriteString( "AddVertex" ) + net.WriteInt( index, 16 ) + net.WriteUInt( #vertices, 8 ) + for i=1,#vertices do + local vert = vertices[i] + net.WriteInt( vert.x, 16 ) + net.WriteInt( vert.y, 16 ) + if (v.HasUV) then + net.WriteFloat( vert.u or 0 ) + net.WriteFloat( vert.v or 0 ) + end + end + EGP.umsg.End() + end + + EGP:SendQueueItem( ply ) + end + + -- Extra Set Poly queue item, used by poly objects with a lot of vertices in them + util.AddNetworkString( "SetVertex" ) + function EGP._SetVertex( Ent, ply, index, vertices, skiptoadd ) + + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, EGP._SetVertex, "SetVertex", index, vertices, skiptoadd ) + return + end + + local bool, k, v = EGP:HasObject( Ent, index ) + if (bool) then + if (!EGP.umsg.Start("EGP_Transmit_Data", ply)) then return end + net.WriteEntity( Ent ) + net.WriteString( "SetVertex" ) + net.WriteInt( index, 16 ) + net.WriteUInt( #vertices, 8 ) + for i=1,#vertices do + local vert = vertices[i] + net.WriteInt( vert.x, 16 ) + net.WriteInt( vert.y, 16 ) + if (v.HasUV) then + net.WriteFloat( vert.u or 0 ) + net.WriteFloat( vert.v or 0 ) + end + end + EGP.umsg.End() + end + + EGP:SendQueueItem( ply ) + end + + -- Extra Add Text queue item, used by text objects with a lot of text in them + util.AddNetworkString( "AddText" ) + local function AddText( Ent, ply, index, text ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, AddText, "AddText", index, text ) + return + end + + local bool, k, v = EGP:HasObject( Ent, index ) + if (bool) then + if (!EGP.umsg.Start("EGP_Transmit_Data", ply)) then return end + net.WriteEntity( Ent ) + net.WriteString( "AddText" ) + net.WriteInt( index, 16 ) + net.WriteString( text ) + EGP.umsg.End() + end + + EGP:SendQueueItem( ply ) + end + + -- Extra Set Text queue item, used by text objects with a lot of text in them + util.AddNetworkString( "SetText" ) + function EGP._SetText( Ent, ply, index, text ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, EGP._SetText, "SetText", index, text ) + return + end + + local bool, k, v = EGP:HasObject( Ent, index ) + if (bool) then + if (!EGP.umsg.Start("EGP_Transmit_Data", ply)) then return end + net.WriteEntity( Ent ) + net.WriteString( "SetText" ) + net.WriteInt( index, 16 ) + net.WriteString( text ) + EGP.umsg.End() + end + + EGP:SendQueueItem( ply ) + end + + local function SetScale( ent, ply, x, y ) + EGP:SetScale( ent, x, y ) + EGP:SendQueueItem( ply ) + end + + local function MoveTopLeft( ent, ply, bool ) + ent.TopLeft = bool + EGP:SendQueueItem( ply ) + end + + util.AddNetworkString( "EditFiltering" ) + local function EditFiltering( Ent, ply, filtering ) + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueue( Ent, ply, EditFiltering, "EditFiltering", filtering ) + return + end + if (!EGP.umsg.Start("EGP_Transmit_Data", ply)) then return end + net.WriteEntity( Ent ) + net.WriteString( "EditFiltering" ) + net.WriteUInt( filtering, 2 ) + EGP.umsg.End() + + EGP:SendQueueItem( ply ) + end + + util.AddNetworkString( "ReceiveObjects" ) + local function SendObjects( Ent, ply, DataToSend ) + if (!Ent or !Ent:IsValid() or !ply or !ply:IsValid() or !DataToSend) then return end + + -- Check duped + if (Ent.EGP_Duplicated) then + EGP:InsertQueueObjects( Ent, ply, SendObjects, DataToSend ) + return + end + + -- Check interval + if not IsValid(ply) or not ply:IsPlayer() then return end + if (EGP:CheckInterval( ply ) == false) then + EGP:InsertQueueObjects( Ent, ply, SendObjects, DataToSend ) + return + end + + local order_was_changed = false + + if (!EGP.umsg.Start( "EGP_Transmit_Data", ply )) then return end + net.WriteEntity( Ent ) + net.WriteString( "ReceiveObjects" ) + + net.WriteUInt( #DataToSend, 16 ) -- Send estimated number of objects to be sent + for k,v in ipairs( DataToSend ) do + + -- Check if the object doesn't exist serverside anymore (It may have been removed by a command in the queue before this, like egpClear or egpRemove) + --if (!EGP:HasObject( Ent, v.index )) then + -- EGP:CreateObject( Ent, v.ID, v ) + --end + + net.WriteInt( v.index, 16 ) -- Send index of object + + if (v.remove == true) then + net.WriteUInt(0, 8) -- Object is to be removed, send a 0 + local bool, k, v = EGP:HasObject( Ent, v.index ) + if (bool) then + -- Unparent all objects parented to this object + for k2,v2 in pairs( Ent.RenderTable ) do + if (v2.parent and v.index and v2.parent == v.index) then + EGP:UnParent( Ent, v2 ) + end + end + + table.remove( Ent.RenderTable, k ) + end + else + net.WriteUInt(v.ID, 8) -- Else send the ID of the object + + if (Ent.Scaling or Ent.TopLeft) then + v = table.Copy(v) -- Make a copy of the table so it doesn't overwrite the serverside object + end + + -- Scale the positions and size + if (Ent.Scaling) then + EGP:ScaleObject( Ent, v ) + end + + -- Move the object to draw from the top left + if (Ent.TopLeft) then + EGP:MoveTopLeft( Ent, v ) + end + + if v.ChangeOrder then -- We want to change the order of this object, send the index to where we wish to move it + net.WriteInt( v.ChangeOrder.target, 16 ) + net.WriteInt( v.ChangeOrder.dir, 3 ) + order_was_changed = true + else + net.WriteInt( 0, 16 ) -- Don't change order + end + + v:Transmit( Ent, ply ) + end + end + EGP.umsg.End() + + -- Change order now + if order_was_changed then + EGP:PerformReorder( Ent ) + end + + EGP:SendQueueItem( ply ) + end + + ---------------------------- + -- DoAction + ---------------------------- + + function EGP:DoAction( Ent, E2, Action, ... ) + if (Action == "SendObject") then + local Data = {...} + if (!Data[1]) then return end + + if (E2 and E2.entity and E2.entity:IsValid()) then + E2.prf = E2.prf + 30 + end + + self:AddQueueObject( Ent, E2.player, SendObjects, Data[1] ) + elseif (Action == "RemoveObject") then + local Data = {...} + if (!Data[1]) then return end + + if (E2 and E2.entity and E2.entity:IsValid()) then + E2.prf = E2.prf + 20 + end + + for i=1,#Ent.RenderTable do + E2.prf = E2.prf + 0.3 + if Ent.RenderTable[i].index == Data[1] then + table.remove( Ent.RenderTable, i ) + break + end + end + + self:AddQueueObject( Ent, E2.player, SendObjects, { index = Data[1], remove = true } ) + elseif (Action == "ClearScreen") then + if (E2 and E2.entity and E2.entity:IsValid()) then + E2.prf = E2.prf + 20 + end + + // Remove all queued actions for this screen + local queue = self.Queue[E2.player] or {} + local i = 1 + while i<=#queue do + if queue[i].Ent == Ent then + E2.prf = E2.prf + 0.3 + table.remove(queue, i) + else + i = i + 1 + end + end + + Ent.RenderTable = {} + + self:AddQueue( Ent, E2.player, ClearScreen, "ClearScreen" ) + elseif (Action == "SaveFrame") then + local Data = {...} + if (!Data[1]) then return end + + if (E2 and E2.entity and E2.entity:IsValid()) then + E2.prf = E2.prf + 10 + end + + EGP:SaveFrame( E2.player, Ent, Data[1] ) + self:AddQueue( Ent, E2.player, SaveFrame, "SaveFrame", Data[1] ) + elseif (Action == "LoadFrame") then + local Data = {...} + if (!Data[1]) then return end + + if (E2 and E2.entity and E2.entity:IsValid()) then + E2.prf = E2.prf + 10 + end + + local bool, frame = EGP:LoadFrame( E2.player, Ent, Data[1] ) + if (bool) then + Ent.RenderTable = frame + end + + self:AddQueue( Ent, E2.player, LoadFrame, "LoadFrame", Data[1] ) + elseif (Action == "SetScale") then + local Data = {...} + self:AddQueue( Ent, E2.player, SetScale, "SetScale", Data[1], Data[2] ) + elseif (Action == "MoveTopLeft") then + local Data = {...} + self:AddQueue( Ent, E2.player, MoveTopLeft, "MoveTopLeft", Data[1] ) + elseif (Action == "EditFiltering") then + local Data = {...} + self:AddQueue( Ent, E2.player, EditFiltering, "EditFiltering", Data[1] ) + end + end +else -- SERVER/CLIENT + function EGP:Receive( netlen ) + local Ent = net.ReadEntity() + if (!self:ValidEGP( Ent ) or !Ent.RenderTable) then return end + + local Action = net.ReadString() + if (Action == "ClearScreen") then + Ent.RenderTable = {} + Ent:EGP_Update() + elseif (Action == "SaveFrame") then + local ply = net.ReadEntity() + local FrameName = net.ReadString() + EGP:SaveFrame( ply, Ent, FrameName ) + elseif (Action == "LoadFrame") then + local ply = net.ReadEntity() + local FrameName = net.ReadString() + EGP:LoadFrame( ply, Ent, FrameName ) + Ent:EGP_Update() + elseif (Action == "SetText") then + local index = net.ReadInt(16) + local text = net.ReadString() + local bool,k,v = EGP:HasObject( Ent, index ) + if (bool) then + if (EGP:EditObject( v, { text = text } )) then Ent:EGP_Update() end + end + elseif (Action == "AddText") then + local index = net.ReadInt(16) + local text = net.ReadString() + local bool,k,v = EGP:HasObject( Ent, index ) + if (bool) then + if (EGP:EditObject( v, { text = v.text .. text } )) then Ent:EGP_Update() end + end + elseif (Action == "SetVertex") then + local index = net.ReadInt(16) + local bool, k,v = EGP:HasObject( Ent, index ) + if (bool) then + local vertices = {} + + if (v.HasUV) then + local n = 0 + for i=1,net.ReadUInt(8) do + local x, y, u, _v = net.ReadInt(16), net.ReadInt(16), net.ReadFloat(), net.ReadFloat() + vertices[i] = { x=x, y=y, u=u, v=_v } + end + else + local n = 0 + for i=1,net.ReadUInt(8) do + local x, y = net.ReadInt(16), net.ReadInt(16) + vertices[i] = { x=x, y=y } + end + end + + if (EGP:EditObject( v, { vertices = vertices })) then Ent:EGP_Update() end + end + elseif (Action == "AddVertex") then + local index = net.ReadInt(16) + local bool, k, v = EGP:HasObject( Ent, index ) + if (bool) then + local vertices = table.Copy(v.vertices) + + if (v.HasUV) then + local n = 0 + for i=1,net.ReadUInt(8) do + local x, y, u, _v = net.ReadInt(16), net.ReadInt(16), net.ReadFloat(), net.ReadFloat() + vertices[#vertices+1] = { x=x, y=y, u=u, v=_v } + end + else + local n = 0 + for i=1,net.ReadUInt(8) do + local x, y = net.ReadInt(16), net.ReadInt(16) + vertices[#vertices+1] = { x=x, y=y } + end + end + + if (EGP:EditObject( v, { vertices = vertices })) then Ent:EGP_Update() end + end + elseif (Action == "EditFiltering") then + if Ent.GPU then -- Only Screens use GPULib + Ent.GPU.texture_filtering = net.ReadUInt(2) or TEXFILTER.ANISOTROPIC + end + elseif (Action == "ReceiveObjects") then + local order_was_changed = false + + for i=1,net.ReadUInt(16) do + local index = net.ReadInt(16) + if (index == 0) then break end -- In case the umsg had to abort early + + local ID = net.ReadUInt(8) + + if (ID == 0) then -- Remove object + local bool, k, v = EGP:HasObject( Ent, index ) + if (bool) then + if (v.OnRemove) then v:OnRemove() end + + -- Unparent all objects parented to this object + for k2,v2 in pairs( Ent.RenderTable ) do + if (v2.parent and v.index and v2.parent == v.index) then + EGP:UnParent( Ent, v2 ) + end + end + + table.remove( Ent.RenderTable, k ) + end + else + + -- Change Order + local ChangeOrder_To = net.ReadInt(16) + local ChangeOrder_Dir + if ChangeOrder_To ~= 0 then + ChangeOrder_Dir = net.ReadInt(3) + end + + local current_obj + local bool, k, v = self:HasObject( Ent, index ) + if (bool) then -- Object already exists + if (v.ID != ID) then -- Not the same kind of object, create new + if (v.OnRemove) then v:OnRemove() end + local Obj = self:GetObjectByID( ID ) + local data = Obj:Receive() + self:EditObject( Obj, data ) + Obj.index = index + Ent.RenderTable[k] = Obj + if (Obj.OnCreate) then Obj:OnCreate() end + + -- For EGP HUD + if (Obj.res) then Obj.res = nil end + + current_obj = Obj + else -- Edit + self:EditObject( v, v:Receive() ) + + -- If parented, reset the parent indexes + if (v.parent and v.parent != 0) then + EGP:AddParentIndexes( v ) + end + + -- For EGP HUD + if (v.res) then v.res = nil end + + current_obj = v + end + else -- Object does not exist. Create new + local Obj = self:GetObjectByID( ID ) + self:EditObject( Obj, Obj:Receive() ) + Obj.index = index + if (Obj.OnCreate) then Obj:OnCreate() end + Ent.RenderTable[#Ent.RenderTable+1] = Obj--table.insert( Ent.RenderTable, Obj ) + + current_obj = Obj + end + + -- Change Order (later) + if ChangeOrder_To ~= 0 then + order_was_changed = true + current_obj.ChangeOrder = {target=ChangeOrder_To,dir=ChangeOrder_Dir} + end + end + end + + + -- Change order now + if order_was_changed then + self:PerformReorder( Ent ) + end + + Ent:EGP_Update() + end + end + net.Receive( "EGP_Transmit_Data", function(netlen) EGP:Receive( netlen ) end ) + +end + +if (SERVER) then + util.AddNetworkString("EGP_Request_Transmit") + + EGP.DataStream = {} + + concommand.Add("EGP_Request_Reload",function(ply,cmd,args) + if (!EGP.DataStream[ply]) then EGP.DataStream[ply] = {} end + local tbl = EGP.DataStream[ply] + if (!tbl.SingleTime) then tbl.SingleTime = 0 end + if (!tbl.AllTime) then tbl.AllTime = 0 end + if (args[1]) then + if (tbl.SingleTime > CurTime()) then + ply:ChatPrint("[EGP] This command has anti-spam protection. Try again after 10 seconds.") + else + tbl.SingleTime = CurTime() + 10 + ply:ChatPrint("[EGP] Request accepted for single screen. Sending...") + EGP:SendDataStream( ply, args[1] ) + end + else + if (tbl.AllTime > CurTime()) then + ply:ChatPrint("[EGP] This command has anti-spam protection. Try again after 30 seconds.") + else + tbl.AllTime = CurTime() + 30 + ply:ChatPrint("[EGP] Request accepted for all screens. Sending...") + local bool, msg = EGP:SendDataStream( ply, args[1] ) + if (bool == false) then + ply:ChatPrint("[EGP] " .. msg ) + end + end + end + end) + + function EGP:SendDataStream( ply, entid ) + if (!ply or !ply:IsValid()) then return false, "ERROR: Invalid ply." end + local targets + if (entid) then + local tempent = Entity(entid) + if (self:ValidEGP( tempent )) then + targets = { tempent } + else + return false, "ERROR: Invalid screen." + end + end + if (!targets) then + targets = ents.FindByClass("gmod_wire_egp") + table.Add( targets, ents.FindByClass("gmod_wire_egp_hud") ) + table.Add( targets, ents.FindByClass("gmod_wire_egp_emitter") ) + + if (#targets == 0) then return false, "There are no EGP screens on the map." end + end + + local sent + for k,v in ipairs( targets ) do + if (v.RenderTable and #v.RenderTable>0) then + local DataToSend = {} + for k2, v2 in pairs( v.RenderTable ) do + local obj = v2 + + if (v.Scaling or v.TopLeft) then + obj = table.Copy(v2) -- Make a copy of the table so it doesn't overwrite the serverside object + else + obj = v2 + end + + -- Scale the positions and size + if (v.Scaling) then + EGP:ScaleObject( v, obj ) + end + + -- Move the object to draw from the top left + if (v.TopLeft) then + EGP:MoveTopLeft( v, obj ) + end + + DataToSend[#DataToSend+1] = { ID = obj.ID, index = obj.index, Settings = obj:DataStreamInfo() } + end + + timer.Simple( k, function() -- send 1 second apart + net.Start("EGP_Request_Transmit") + net.WriteTable({ + Ent = v, + Objects = DataToSend, + Filtering = v.GPU_texture_filtering, + IsLastScreen = (k == #targets) and #targets or nil -- Doubles as notifying the client that no more data will arrive, and tells them how many did arrive + }) + net.Send(ply) + end) + sent = true + end + end + if not sent then + return false, "None of the screens have any objects drawn on them." + else + return true, #targets + end + end + + local function initspawn(ply) + timer.Simple(10,function() + if (ply and ply:IsValid()) then + local bool, msg = EGP:SendDataStream( ply ) + if (bool == true) then + ply:ChatPrint("[EGP] " .. tostring(msg) .. " EGP Screens found on the server. Sending objects now...") + end + end + end) + end + + hook.Add("PlayerInitialSpawn","EGP_SpawnFunc",initspawn) +else + + function EGP:ReceiveDataStream( decoded ) + local Ent = decoded.Ent + local Objects = decoded.Objects + + if (self:ValidEGP( Ent )) then + Ent.RenderTable = {} + if Ent.GPU then -- Only Screens use GPULib + Ent.GPU.texture_filtering = decoded.Filtering or TEXFILTER.ANISOTROPIC + end + for _,v in pairs( Objects ) do + local Obj = self:GetObjectByID(v.ID) + self:EditObject( Obj, v.Settings ) + -- If parented, reset the parent indexes + if (Obj.parent and Obj.parent != 0) then + self:AddParentIndexes( Obj ) + end + Obj.index = v.index + table.insert( Ent.RenderTable, Obj ) + end + Ent:EGP_Update() + end + + if decoded.IsLastScreen then + LocalPlayer():ChatPrint("[EGP] Received EGP object reload. " .. decoded.IsLastScreen .. " screens' objects were reloaded.") + end + end + net.Receive("EGP_Request_Transmit", function(len,ply) + EGP:ReceiveDataStream(net.ReadTable()) + end) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/umsgsystem.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/umsgsystem.lua new file mode 100644 index 0000000..01c7989 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/umsgsystem.lua @@ -0,0 +1,49 @@ +-------------------------------------------------------- +-- Custom umsg System +-------------------------------------------------------- +local EGP = EGP + +local CurSender +local LastErrorTime = 0 +--[[ Transmit Sizes: + Angle = 12 + Bool = 1 + Char = 1 + Entity = 2 + Float = 4 + Long = 4 + Short = 2 + String = string length + Vector = 12 + VectorNormal = 12 +]] + +EGP.umsg = {} + +function EGP.umsg.Start( name, sender ) + if CurSender then + if (LastErrorTime + 1 < CurTime()) then + ErrorNoHalt("[EGP] Umsg error. It seems another umsg is already sending, but it occured over 1 second ago. Ending umsg.") + EGP.umsg.End() + else + ErrorNoHalt("[EGP] Umsg error. Another umsg is already sending!") + if (LastErrorTime + 2 < CurTime()) then + LastErrorTime = CurTime() + end + return false + end + end + CurSender = sender + + net.Start( name ) + return true +end + +function EGP.umsg.End() + if CurSender then + if not EGP.IntervalCheck[CurSender] then EGP.IntervalCheck[CurSender] = { bytes = 0, time = 0 } end + EGP.IntervalCheck[CurSender].bytes = EGP.IntervalCheck[CurSender].bytes + net.BytesWritten() + end + net.Broadcast() + CurSender = nil +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/usefulfunctions.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/usefulfunctions.lua new file mode 100644 index 0000000..1b07f50 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/egplib/usefulfunctions.lua @@ -0,0 +1,458 @@ +-------------------------------------------------------- +-- e2function Helper functions +-------------------------------------------------------- +local EGP = EGP + +---------------------------- +-- Table IsEmpty +---------------------------- + +function EGP:Table_IsEmpty( tbl ) return next(tbl) == nil end + +---------------------------- +-- SetScale +---------------------------- + +function EGP:SetScale( ent, x, y ) + if not self:ValidEGP(ent) or not x or not y then return end + ent.xScale = { x[1], x[2] } + ent.yScale = { y[1], y[2] } + if x[1] ~= 0 or x[2] ~= 512 or y[1] ~= 0 or y[2] ~= 512 then + ent.Scaling = true + else + ent.Scaling = false + end +end + +-------------------------------------------------------- +-- Scaling functions +-------------------------------------------------------- + +local makeArray +local makeTable +local addUV +hook.Add("Initialize","EGP_WaitForParentingFile",function() + makeArray = EGP.ParentingFuncs.makeArray + makeTable = EGP.ParentingFuncs.makeTable + addUV = EGP.ParentingFuncs.addUV +end) + +function EGP:ScaleObject( ent, v ) + if not self:ValidEGP(ent) then return end + local xScale = ent.xScale + local yScale = ent.yScale + if not xScale or not yScale then return end + + local xMin = xScale[1] + local xMax = xScale[2] + local yMin = yScale[1] + local yMax = yScale[2] + + local xMul = 512/(xMax-xMin) + local yMul = 512/(yMax-yMin) + + if (v.verticesindex) then -- Object has vertices + local r = makeArray( v, true ) + for i=1,#r,2 do + r[i] = (r[i] - xMin) * xMul + r[i+1] = (r[i+1]- yMin) * yMul + end + local settings = makeTable(v, r) + addUV(v, settings) + if isstring(v.verticesindex) then settings = { [v.verticesindex] = settings } end + self:EditObject( v, settings ) + else + if (v.x) then + v.x = (v.x - xMin) * xMul + end + if (v.y) then + v.y = (v.y - yMin) * yMul + end + if (v.w) then + v.w = math.abs(v.w * xMul) + end + if (v.h) then + v.h = math.abs(v.h * yMul) + end + end +end + +-------------------------------------------------------- +-- Draw from top left +-------------------------------------------------------- + +function EGP:MoveTopLeft( ent, v ) + if not self:ValidEGP(ent) then return end + + if (v.CanTopLeft and v.x and v.y and v.w and v.h) then + local vec, ang = LocalToWorld( Vector( v.w/2, v.h/2, 0 ), Angle(0,0,0), Vector( v.x, v.y, 0 ), Angle( 0, -v.angle or 0, 0 ) ) + local t = { x = vec.x, y = vec.y } + if (v.angle) then t.angle = -ang.yaw end + self:EditObject( v, t ) + end +end + +---------------------------- +-- IsDifferent check +---------------------------- +function EGP:IsDifferent( tbl1, tbl2 ) + if self:Table_IsEmpty(tbl1) ~= self:Table_IsEmpty(tbl2) then return true end -- One is empty, the other is not + + for k,v in ipairs( tbl1 ) do + if not tbl2[k] or tbl2[k].ID ~= v.ID then -- Different ID? + return true + else + for k2,v2 in pairs( v ) do + if k2 ~= "BaseClass" then + if tbl2[k][k2] or tbl2[k][k2] ~= v2 then -- Is any setting different? + return true + end + end + end + end + end + + for k, _ in ipairs( tbl2 ) do -- Were any objects removed? + if not tbl1[k] then + return true + end + end + + return false +end + + +---------------------------- +-- IsAllowed check +---------------------------- +function EGP:IsAllowed( E2, Ent ) + if not EGP:ValidEGP(Ent) then return false end + if (E2 and E2.entity and E2.entity:IsValid()) then + local owner = Ent:GetEGPOwner() + if E2.player ~= owner then + return E2Lib.isFriend(E2.player,Ent:GetEGPOwner()) + else + return true + end + end + return false +end + +-------------------------------------------------------- +-- Transmitting / Receiving helper functions +-------------------------------------------------------- +----------------------- +-- Material +----------------------- + +function EGP:SendMaterial( obj ) -- ALWAYS use this when sending material + local mat = obj.material + if isstring(mat) then + net.WriteString( "0" .. mat ) -- 0 for string + elseif isentity(mat) then + net.WriteString( "1" .. mat:EntIndex() ) -- 1 for entity + end +end + +function EGP:ReceiveMaterial( tbl ) -- ALWAYS use this when receiving material + local temp = net.ReadString() + local what, mat = temp:sub(1,1), temp:sub(2) + if what == "0" then + if mat == "" then + tbl.material = false + else + tbl.material = Material(mat) + end + elseif what == "1" then + local num = tonumber(mat) + if not num or not IsValid(Entity(num)) then + tbl.material = false + else + tbl.material = Entity(num) + end + end +end + +----------------------- +-- Other +----------------------- +function EGP:SendPosSize( obj ) + net.WriteInt( obj.w, 16 ) + net.WriteInt( obj.h, 16 ) + net.WriteInt( obj.x, 16 ) + net.WriteInt( obj.y, 16 ) +end + +function EGP:SendColor( obj ) + net.WriteUInt(obj.r, 8) + net.WriteUInt(obj.g, 8) + net.WriteUInt(obj.b, 8) + if (obj.a) then net.WriteUInt( math.Clamp( obj.a, 0, 255 ) , 8) end +end + +function EGP:ReceivePosSize( tbl ) -- Used with SendPosSize + tbl.w = net.ReadInt(16) + tbl.h = net.ReadInt(16) + tbl.x = net.ReadInt(16) + tbl.y = net.ReadInt(16) +end + +function EGP:ReceiveColor( tbl, obj ) -- Used with SendColor + tbl.r = net.ReadUInt(8) + tbl.g = net.ReadUInt(8) + tbl.b = net.ReadUInt(8) + if (obj.a) then tbl.a = net.ReadUInt(8) end +end + +-------------------------------------------------------- +-- Other +-------------------------------------------------------- +function EGP:ValidEGP( Ent ) + return (IsValid( Ent ) and (Ent:GetClass() == "gmod_wire_egp" or Ent:GetClass() == "gmod_wire_egp_hud" or Ent:GetClass() == "gmod_wire_egp_emitter")) +end + + +-- Saving Screen width and height +if (CLIENT) then + usermessage.Hook("EGP_ScrWH_Request",function(um) + RunConsoleCommand("EGP_ScrWH",ScrW(),ScrH()) + end) +else + hook.Add("PlayerInitialSpawn","EGP_ScrHW_Request",function(ply) + timer.Simple(1,function() + if (ply and ply:IsValid() and ply:IsPlayer()) then + umsg.Start("EGP_ScrWH_Request",ply) umsg.End() + end + end) + end) + + EGP.ScrHW = {} + + concommand.Add("EGP_ScrWH",function(ply,cmd,args) + if (args and tonumber(args[1]) and tonumber(args[2])) then + EGP.ScrHW[ply] = { tonumber(args[1]), tonumber(args[2]) } + end + end) +end + +-- Used to check if the cached vertices of egpCircle etc are still valid, and if not update to the current values +function EGP:CacheNeedsUpdate(obj, keys) + if not obj.vert_cache then obj.vert_cache = {} end + local cache = obj.vert_cache + local update = false + for _,k in pairs(keys) do + if cache[k] ~= obj[k] then + update = true + cache[k] = obj[k] + end + end + return update +end + +-- Line drawing helper function +function EGP:DrawLine( x, y, x2, y2, size ) + if (size < 1) then size = 1 end + if (size == 1) then + surface.DrawLine( x, y, x2, y2 ) + else + -- Calculate position + local x3 = (x + x2) / 2 + local y3 = (y + y2) / 2 + + -- calculate height + local w = math.sqrt( (x2-x) ^ 2 + (y2-y) ^ 2 ) + + -- Calculate angle (Thanks to Fizyk) + local angle = math.deg(math.atan2(y-y2,x2-x)) + + -- if the rectangle's less than a pixel wide, nothing will get drawn. + if w < 1 then w = 1 end + + surface.DrawTexturedRectRotated( x3, y3, w, size, angle ) + end +end + +function EGP:DrawPath( vertices, size, closed ) + if size < 1 then size = 1 end + local num = #vertices + + + if size == 1 then -- size 1 => just normal lines + local last = vertices[1] + for i=2, num do + local v = vertices[i] + surface.DrawLine( last.x, last.y, v.x, v.y ) + last = v + end + if closed then + surface.DrawLine( last.x, last.y, vertices[1].x, vertices[1].y ) + end + else + size = size/2 -- simplify calculations + local corners = vertices.outline_cache + if vertices.outline_cache_size ~= size then -- check if the outline was cached already + corners = {} + local lastdir = {x=0, y=0} + if closed then + local x1 = vertices[num].x + local y1 = vertices[num].y + local x2 = vertices[1].x + local y2 = vertices[1].y + local len = math.sqrt( (x2-x1) ^ 2 + (y2-y1) ^ 2 ) + lastdir = {x=(x2-x1)/len, y=(y2-y1)/len} -- initialize lastdir so first segment can be drawn normally + end + for i=1, (closed and num+1 or num) do + local v1 = i==num+1 and vertices[1] or vertices[i] + local x1 = v1.x + local y1 = v1.y + + if not closed and i==num then -- very last segment, just end perpendicular (TODO: maybe move after the loop) + corners[#corners+1] = { r={x=x1-lastdir.y*size, y=y1+lastdir.x*size}, l={x=x1+lastdir.y*size, y=y1-lastdir.x*size}} + else + local v2 = i 0.999 then -- also account for rounding errors, somehow the dot product can be >1, which makes scaling nan + -- direction stays the same, no need for a corner, just skip this point, unless it is the last segment of a closed path (last segment of a open path is handled explicitly above) + if i == num+1 then + corners[#corners+1] = { r={x=x1-dir.y*size, y=y1+dir.x*size}, l={x=x1+dir.y*size, y=y1-dir.x*size} } + end + elseif dot < -0.999 then -- new direction is inverse, just add perpendicular nodes + corners[#corners+1] = { r={x=x1-dir.y*size, y=y1+dir.x*size}, l={x=x1+dir.y*size, y=y1-dir.x*size} } + elseif dir.x*-lastdir.y + dir.y*lastdir.x > 0 then -- right bend, checked by getting the dot product between dir and lastDir:rotate(90) + local offsetx = -lastdir.y*size-lastdir.x*scaling + local offsety = lastdir.x*size-lastdir.y*scaling + if dot < 0 then -- sharp corner, add two points to the outer edge to not have insanely long spikes + corners[#corners+1] = { r={x=x1+offsetx, y=y1+offsety}, l={x=x1+(lastdir.x+lastdir.y)*size, y=y1+(lastdir.y-lastdir.x)*size} } + corners[#corners+1] = { r={x=x1+offsetx, y=y1+offsety}, l={x=x1-(dir.x-dir.y)*size, y=y1-(dir.y+dir.x)*size} } + else + corners[#corners+1] = { r={x=x1+offsetx, y=y1+offsety}, l={x=x1-offsetx, y=y1-offsety} } + end + else -- left bend + local offsetx = lastdir.y*size-lastdir.x*scaling + local offsety = -lastdir.x*size-lastdir.y*scaling + if dot < 0 then + corners[#corners+1] = { l={x=x1+offsetx, y=y1+offsety}, r={x=x1+(lastdir.x-lastdir.y)*size, y=y1+(lastdir.y+lastdir.x)*size} } + corners[#corners+1] = { l={x=x1+offsetx, y=y1+offsety}, r={x=x1-(dir.x+dir.y)*size, y=y1-(dir.y-dir.x)*size} } + else + corners[#corners+1] = { l={x=x1+offsetx, y=y1+offsety}, r={x=x1-offsetx, y=y1-offsety} } + end + end + end + lastdir = dir + end + end + end + vertices.outline_cache = corners + vertices.outline_cache_size = size + end + for i=2, #corners, 2 do + local verts + if i==#corners then -- last corner, only one segment missing + verts = {corners[i].r, corners[i-1].r, corners[i-1].l, corners[i].l} + else -- draw this and next segment as a single polygon + verts = {corners[i].r, corners[i-1].r, corners[i-1].l, corners[i].l, corners[i+1].l, corners[i+1].r} + end + surface.DrawPoly(verts) + end + end +end + +local function ScaleCursor( this, x, y ) + if (this.Scaling) then + local xMin = this.xScale[1] + local xMax = this.xScale[2] + local yMin = this.yScale[1] + local yMax = this.yScale[2] + + x = (x * (xMax-xMin)) / 512 + xMin + y = (y * (yMax-yMin)) / 512 + yMin + end + + return x, y +end + +local function ReturnFailure( this ) + if (this.Scaling) then + return {this.xScale[1]-1,this.yScale[1]-1} + end + return {-1,-1} +end + +function EGP:EGPCursor( this, ply ) + if not EGP:ValidEGP(this) then return {-1,-1} end + if not IsValid(ply) or not ply:IsPlayer() then return ReturnFailure( this ) end + + local Normal, Pos, monitor, Ang + -- If it's an emitter, set custom normal and pos + if (this:GetClass() == "gmod_wire_egp_emitter") then + Normal = this:GetRight() + Pos = this:LocalToWorld( Vector( -64, 0, 135 ) ) + + monitor = { Emitter = true } + else + -- Get monitor screen pos & size + monitor = WireGPU_Monitors[ this:GetModel() ] + + -- Monitor does not have a valid screen point + if not monitor then return {-1,-1} end + + Ang = this:LocalToWorldAngles( monitor.rot ) + Pos = this:LocalToWorld( monitor.offset ) + + Normal = Ang:Up() + end + + local Start = ply:GetShootPos() + local Dir = ply:GetAimVector() + + local A = Normal:Dot(Dir) + + -- If ray is parallel or behind the screen + if (A == 0 or A > 0) then return ReturnFailure( this ) end + + local B = Normal:Dot(Pos-Start) / A + + if (B >= 0) then + if (monitor.Emitter) then + local HitPos = Start + Dir * B + HitPos = this:WorldToLocal( HitPos ) - Vector( -64, 0, 135 ) + local x = HitPos.x*(512/128) + local y = HitPos.z*-(512/128) + x, y = ScaleCursor( this, x, y ) + return {x,y} + else + local HitPos = WorldToLocal( Start + Dir * B, Angle(), Pos, Ang ) + local x = (0.5+HitPos.x/(monitor.RS*512/monitor.RatioX)) * 512 + local y = (0.5-HitPos.y/(monitor.RS*512)) * 512 + if (x < 0 or x > 512 or y < 0 or y > 512) then return ReturnFailure( this ) end -- Aiming off the screen + x, y = ScaleCursor( this, x, y ) + return {x,y} + end + end + + return ReturnFailure( this ) +end + +function EGP.ScreenSpaceToObjectSpace(object, point) + point = { x = point.x - object.x, y = point.y - object.y } + + if object.angle and object.angle ~= 0 then + local theta = math.rad(object.angle) + local cos_theta, sin_theta = math.cos(theta), math.sin(theta) + point.x, point.y = + point.x * cos_theta - point.y * sin_theta, + point.y * cos_theta + point.x * sin_theta + end + + return point +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/init.lua new file mode 100644 index 0000000..6852e36 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/init.lua @@ -0,0 +1,30 @@ +EGP = {} + +-------------------------------------------------------- +-- Include all other files +-------------------------------------------------------- + +function EGP:Initialize() + local Folder = "entities/gmod_wire_egp/lib/egplib/" + local entries = file.Find( Folder .. "*.lua", "LUA") + for _, entry in ipairs( entries ) do + if (SERVER) then + AddCSLuaFile( Folder .. entry ) + end + include( Folder .. entry ) + end +end + +EGP:Initialize() + +local EGP = EGP + +EGP.ConVars = {} +EGP.ConVars.MaxObjects = CreateConVar( "wire_egp_max_objects", 300, { FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE } ) +EGP.ConVars.MaxPerSec = CreateConVar( "wire_egp_max_bytes_per_sec", 10000, { FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE } ) -- Keep between 2500-40000 + +EGP.ConVars.MaxVertices = CreateConVar( "wire_egp_max_poly_vertices", 1024, { FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE } ) + +EGP.ConVars.AllowEmitter = CreateConVar( "wire_egp_allow_emitter", 1, { FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE } ) +EGP.ConVars.AllowHUD = CreateConVar( "wire_egp_allow_hud", 1, { FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE } ) +EGP.ConVars.AllowScreen = CreateConVar( "wire_egp_allow_screen", 1, { FCVAR_NOTIFY, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE } ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/3dtracker.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/3dtracker.lua new file mode 100644 index 0000000..d608e72 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/3dtracker.lua @@ -0,0 +1,94 @@ +-- Author: Divran +local Obj = EGP:NewObject( "3DTracker" ) +Obj.material = nil +Obj.w = nil +Obj.h = nil +Obj.target_x = 0 +Obj.target_y = 0 +Obj.target_z = 0 +Obj.r = nil +Obj.g = nil +Obj.b = nil +Obj.a = nil +Obj.parententity = NULL +Obj.Is3DTracker = true +Obj.angle = 0 + +function Obj:Draw(egp) + if egp.gmod_wire_egp_emitter then + local objectPosition + if self.parententity and self.parententity:IsValid() then + objectPosition = self.parententity:LocalToWorld(Vector(self.target_x,self.target_y,self.target_z)) + else + objectPosition = Vector(self.target_x,self.target_y,self.target_z) + end + + local eyePosition = EyePos() + + local direction = objectPosition-eyePosition + + -- localise the positions + eyePosition = egp:WorldToLocal(eyePosition) + direction = egp:WorldToLocal(direction + egp:GetPos()) + + -- plane/ray intersection: + --[[ + screenPosition = eyePosition+direction*fraction | screenPosition.y = 0 + 0 = eyePosition.y+direction.y*fraction | - eyePosition.y + -eyePosition.y = direction.y*fraction | / direction.y + -eyePosition.y / direction.y = fraction | swap sides + ]] + + local fraction = -eyePosition.y / direction.y + local screenPosition = eyePosition+direction*fraction + + screenPosition = (screenPosition - Vector( -64, 0, 135 )) / 0.25 + + if fraction < 0 then -- hide for fraction < 0 (maybe for > 1 too?) + self.x = math.huge + self.y = math.huge + else + self.x = screenPosition.x + self.y = -screenPosition.z + end + + -- fraction < 0: object-player-screen: player is between object and screen; object is not seen at all when facing the screen + -- fraction 0-1: object-screen-player: screen is between object and player; object is seen behind the screen + -- fraction > 1: screen-object-player: object is between screen and player; object is seen in front of the screen + elseif egp.gmod_wire_egp_hud then + local pos + if self.parententity and self.parententity:IsValid() then + pos = self.parententity:LocalToWorld(Vector(self.target_x,self.target_y,self.target_z)) + else + pos = Vector(self.target_x,self.target_y,self.target_z) + end + + local pos = pos:ToScreen() + self.x = pos.x + self.y = pos.y + elseif egp.gmod_wire_egp then + end +end + +function Obj:Transmit() + net.WriteFloat( self.target_x ) + net.WriteFloat( self.target_y ) + net.WriteFloat( self.target_z ) + net.WriteEntity( self.parententity ) + net.WriteInt((self.angle%360)*64, 16) +end + +function Obj:Receive() + local tbl = {} + tbl.target_x = net.ReadFloat() + tbl.target_y = net.ReadFloat() + tbl.target_z = net.ReadFloat() + local parententity = net.ReadEntity() + if parententity and parententity:IsValid() then tbl.parententity = parententity end + tbl.angle = net.ReadInt(16)/64 + return tbl +end + +function Obj:DataStreamInfo() + return { target_x = self.target_x, target_y = self.target_y, target_z = self.target_z, parententity = self.parententity } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/box.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/box.lua new file mode 100644 index 0000000..c7898e6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/box.lua @@ -0,0 +1,32 @@ +-- Author: Divran +local Obj = EGP:NewObject( "Box" ) +Obj.angle = 0 +Obj.CanTopLeft = true +Obj.Draw = function( self ) + if (self.a>0) then + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + surface.DrawTexturedRectRotated( self.x, self.y, self.w, self.h, self.angle ) + end +end +Obj.Transmit = function( self ) + net.WriteInt((self.angle%360)*20, 16) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle } ) + return tbl +end +function Obj:Contains(point) + point = EGP.ScreenSpaceToObjectSpace(self, point) + local w, h = self.w / 2, self.h / 2 + return -w <= point.x and point.x <= w and + -h <= point.y and point.y <= h +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/boxoutline.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/boxoutline.lua new file mode 100644 index 0000000..2eac0a8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/boxoutline.lua @@ -0,0 +1,68 @@ +-- Author: Divran +local Obj = EGP:NewObject( "BoxOutline" ) +Obj.size = 1 +Obj.angle = 0 +Obj.CanTopLeft = true +local function rotate( x, y, a ) + local a = a * math.pi / 180 + local _x = math.cos(a) * x - math.sin(a) * y + local _y = math.sin(a) * x + math.cos(a) * y + return _x, _y +end + +Obj.Draw = function( self, egp ) + if (self.a>0 and self.w > 0 and self.h > 0) then + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + + local x, y, w, h, a, s = self.x, self.y, self.w, self.h, self.angle, self.size + + local x1, y1 = rotate( w / 2 - s / 2, 0, -a ) + local x2, y2 = rotate( -w / 2 + s / 2, 0, -a ) + local x3, y3 = rotate( 0, h / 2 - s / 2, -a ) + local x4, y4 = rotate( 0, -h / 2 + s / 2, -a ) + + if egp.gmod_wire_egp_emitter then -- is emitter + if (h - s*2 > 0) then + -- Right + surface.DrawTexturedRectRotated( x + math.ceil(x1), y + math.Round(y1), h - s*2, s, a - 90 ) + -- Left + surface.DrawTexturedRectRotated( x + math.floor(x2), y + math.Round(y2), h - s*2, s, a + 90 ) + end + -- Bottom + surface.DrawTexturedRectRotated( x + math.Round(x3), y + math.floor(y3), w, s, a + 180 ) + -- Top + surface.DrawTexturedRectRotated( x + math.Round(x4), y + math.ceil(y4), w, s, a ) + else -- is not emitter + if (h - s*2 > 0) then + -- Right + surface.DrawTexturedRectRotated( x + math.ceil(x1), y + math.ceil(y1), h - s*2, s, a - 90 ) + -- Left + surface.DrawTexturedRectRotated( x + math.ceil(x2), y + math.ceil(y2), h - s*2, s, a + 90 ) + end + -- Bottom + surface.DrawTexturedRectRotated( x + math.ceil(x3), y + math.ceil(y3), w, s, a + 180 ) + -- Top + surface.DrawTexturedRectRotated( x + math.ceil(x4), y + math.ceil(y4), w, s, a ) + end + end +end + +Obj.Transmit = function( self ) + net.WriteInt( self.size, 16 ) + net.WriteInt( (self.angle%360)*20, 16 ) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.size = net.ReadInt(16) + tbl.angle = net.ReadInt(16)/20 + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + tbl.size = self.size + tbl.angle = self.angle + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + return tbl +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/circle.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/circle.lua new file mode 100644 index 0000000..d6bf625 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/circle.lua @@ -0,0 +1,55 @@ +-- Author: Divran +local Obj = EGP:NewObject( "Circle" ) +Obj.angle = 0 +Obj.fidelity = 180 +local cos, sin, rad, floor = math.cos, math.sin, math.rad, math.floor +Obj.Draw = function( self ) + if (self.a>0 and self.w > 0 and self.h > 0) then + if EGP:CacheNeedsUpdate(self, {"x", "y", "w", "h", "angle", "fidelity"}) then + local vertices = {} + local ang = -rad(self.angle) + local c = cos(ang) + local s = sin(ang) + for i=0,360,floor(360/self.fidelity) do + local radd = rad(i) + local x = cos(radd) + local u = (x+1)/2 + local y = sin(radd) + local v = (y+1)/2 + + local tempx = x * self.w * c - y * self.h * s + self.x + y = x * self.w * s + y * self.h * c + self.y + x = tempx + + vertices[#vertices+1] = { x = x, y = y, u = u, v = v } + end + self.vert_cache.verts = vertices + end + + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + surface.DrawPoly( self.vert_cache.verts ) + end +end +Obj.Transmit = function( self ) + net.WriteInt( (self.angle%360)*20, 16 ) + net.WriteUInt( self.fidelity, 8 ) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + tbl.fidelity = net.ReadUInt(8) + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle, fidelity = self.fidelity } ) + return tbl +end +function Obj:Contains(point) + point = EGP.ScreenSpaceToObjectSpace(self, point) + local x, y = point.x / self.w, point.y / self.h + return x * x + y * y <= 1 +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/circleoutline.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/circleoutline.lua new file mode 100644 index 0000000..c0620b7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/circleoutline.lua @@ -0,0 +1,50 @@ +-- Author: Divran +local Obj = EGP:NewObject( "CircleOutline" ) +Obj.angle = 0 +Obj.size = 1 +Obj.fidelity = 180 +local cos, sin, rad = math.cos, math.sin, math.rad +Obj.Draw = function( self ) + if (self.a>0 and self.w > 0 and self.h > 0) then + if EGP:CacheNeedsUpdate(self, {"x", "y", "w", "h", "angle", "fidelity"}) then + local vertices = {} + local ang = -rad(self.angle) + local c = cos(ang) + local s = sin(ang) + for radd=0, 2*math.pi*(1 - 0.5/self.fidelity), 2*math.pi/self.fidelity do + local x = cos(radd) + local y = sin(radd) + + local tempx = x * self.w * c - y * self.h * s + self.x + y = x * self.w * s + y * self.h * c + self.y + x = tempx + + vertices[#vertices+1] = { x = x, y = y } + end + self.vert_cache.verts = vertices + end + + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + EGP:DrawPath(self.vert_cache.verts, self.size, true) + end +end +Obj.Transmit = function( self ) + net.WriteInt( (self.angle%360)*20, 16 ) + net.WriteInt( self.size, 16 ) + net.WriteUInt(self.fidelity, 8) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + tbl.size = net.ReadInt(16) + tbl.fidelity = net.ReadUInt(8) + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle, size = self.size, fidelity = self.fidelity } ) + return tbl +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/line.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/line.lua new file mode 100644 index 0000000..fe38586 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/line.lua @@ -0,0 +1,39 @@ +-- Author: Divran +local Obj = EGP:NewObject( "Line" ) +Obj.w = nil +Obj.h = nil +Obj.x2 = 0 +Obj.y2 = 0 +Obj.size = 1 +Obj.verticesindex = { { "x", "y" }, { "x2", "y2" } } +Obj.Draw = function( self ) + if (self.a>0) then + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + EGP:DrawLine( self.x, self.y, self.x2, self.y2, self.size ) + end +end +Obj.Transmit = function( self ) + net.WriteInt( self.x, 16 ) + net.WriteInt( self.y, 16 ) + net.WriteInt( self.x2, 16 ) + net.WriteInt( self.y2, 16 ) + net.WriteInt( self.size, 16 ) + net.WriteInt( self.parent, 16 ) + EGP:SendMaterial( self ) + EGP:SendColor( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.x = net.ReadInt(16) + tbl.y = net.ReadInt(16) + tbl.x2 = net.ReadInt(16) + tbl.y2 = net.ReadInt(16) + tbl.size = net.ReadInt(16) + tbl.parent = net.ReadInt(16) + EGP:ReceiveMaterial( tbl ) + EGP:ReceiveColor( tbl, self ) + return tbl +end +Obj.DataStreamInfo = function( self ) + return { x = self.x, y = self.y, x2 = self.x2, y2 = self.y2, r = self.r, g = self.g, b = self.b, a = self.a, size = self.size, parent = self.parent } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/linestrip.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/linestrip.lua new file mode 100644 index 0000000..0747cfd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/linestrip.lua @@ -0,0 +1,43 @@ +-- Author: sk8 (& Divran) +local Obj = EGP:NewObject( "LineStrip" ) +Obj.w = nil +Obj.h = nil +Obj.x = nil +Obj.y = nil +Obj.vertices = {} +Obj.verticesindex = "vertices" +Obj.size = 1 +Obj.Draw = function( self ) + local n = #self.vertices + if (self.a>0 and n>0 and self.size>0) then + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + + EGP:DrawPath(self.vertices, self.size, false) + end +end +Obj.Transmit = function( self, Ent, ply ) + net.WriteUInt( #self.vertices, 16 ) + for i=1,#self.vertices do + net.WriteInt( self.vertices[i].x, 16 ) + net.WriteInt( self.vertices[i].y, 16 ) + end + net.WriteInt(self.parent, 16) + net.WriteInt(self.size, 16) + EGP:SendMaterial( self ) + EGP:SendColor( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.vertices = {} + for i=1,net.ReadUInt(16) do + tbl.vertices[ i ] = { x = net.ReadInt(16), y = net.ReadInt(16) } + end + tbl.parent = net.ReadInt(16) + tbl.size = net.ReadInt(16) + EGP:ReceiveMaterial( tbl ) + EGP:ReceiveColor( tbl, self ) + return tbl +end +Obj.DataStreamInfo = function( self ) + return { vertices = self.vertices, material = self.material, r = self.r, g = self.g, b = self.b, a = self.a, parent = self.parent } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/poly.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/poly.lua new file mode 100644 index 0000000..82d32d8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/poly.lua @@ -0,0 +1,76 @@ +-- Author: Divran +local Obj = EGP:NewObject( "Poly" ) +Obj.w = nil +Obj.h = nil +Obj.x = nil +Obj.y = nil +Obj.vertices = {} +Obj.verticesindex = "vertices" +Obj.HasUV = true + +-- Returns whether c is to the left of the line from a to b. +local function counterclockwise( a, b, c ) + local area = (a.x - c.x) * (b.y - c.y) - (b.x - c.x) * (a.y - c.y) + return area > 0 +end + +Obj.Draw = function( self ) + if (self.a>0 and #self.vertices>2) then + render.CullMode(counterclockwise(unpack(self.vertices)) and MATERIAL_CULLMODE_CCW or MATERIAL_CULLMODE_CW) + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + surface.DrawPoly( self.vertices ) + render.CullMode(MATERIAL_CULLMODE_CCW) + end +end +Obj.Transmit = function( self, Ent, ply ) + if (#self.vertices <= 28) then + net.WriteUInt( #self.vertices, 8 ) + for i=1,#self.vertices do + net.WriteInt( self.vertices[i].x, 16 ) + net.WriteInt( self.vertices[i].y, 16 ) + net.WriteFloat( self.vertices[i].u or 0 ) + net.WriteFloat( self.vertices[i].v or 0 ) + end + else + net.WriteUInt( 0, 8 ) + EGP:InsertQueue( Ent, ply, EGP._SetVertex, "SetVertex", self.index, self.vertices ) + end + net.WriteUInt(math.Clamp(self.filtering,0,3), 2) + net.WriteInt( self.parent, 16 ) + EGP:SendMaterial( self ) + EGP:SendColor( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.vertices = {} + for i = 1, net.ReadUInt(8) do + tbl.vertices[ i ] = { x = net.ReadInt(16), y = net.ReadInt(16), u = net.ReadFloat(), v = net.ReadFloat() } + end + tbl.filtering = net.ReadUInt(2) + tbl.parent = net.ReadInt(16) + EGP:ReceiveMaterial( tbl ) + EGP:ReceiveColor( tbl, self ) + return tbl +end +Obj.DataStreamInfo = function( self ) + return { vertices = self.vertices, material = self.material, r = self.r, g = self.g, b = self.b, a = self.a, filtering = self.filtering, parent = self.parent } +end +function Obj:Contains(point) + if #self.vertices < 3 then return false end + + -- To check whether a point is in the polygon, we check whether it's to the + -- 'inside' side of each edge. (If the polygon is counterclockwise then the + -- inside is the left side; otherwise it's the right side.) This only works + -- for convex polygons, but so does `surface.drawPoly`. + local inside + if counterclockwise(self.vertices[1], self.vertices[2], self.vertices[3]) then + inside = counterclockwise + else + inside = function(a, b, c) return counterclockwise(b, a, c) end + end + + for i = 1, #self.vertices - 1 do + if not inside(self.vertices[i], self.vertices[i + 1], point) then return false end + end + return inside(self.vertices[#self.vertices], self.vertices[1], point) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/polyoutline.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/polyoutline.lua new file mode 100644 index 0000000..d590cd6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/polyoutline.lua @@ -0,0 +1,42 @@ +-- Author: sk8 (& Divran) +local Obj = EGP:NewObject( "PolyOutline" ) +Obj.w = nil +Obj.h = nil +Obj.x = nil +Obj.y = nil +Obj.vertices = {} +Obj.verticesindex = "vertices" +Obj.size = 1 +Obj.Draw = function( self ) + local n = #self.vertices + if (self.a>0 and n>0 and self.size>0) then + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + EGP:DrawPath(self.vertices, self.size, true) + end +end +Obj.Transmit = function( self, Ent, ply ) + net.WriteUInt( #self.vertices, 16 ) + for i=1,#self.vertices do + net.WriteInt( self.vertices[i].x, 16 ) + net.WriteInt( self.vertices[i].y, 16 ) + end + net.WriteInt(self.parent, 16) + net.WriteInt(self.size, 16) + EGP:SendMaterial( self ) + EGP:SendColor( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.vertices = {} + for i=1,net.ReadUInt(16) do + tbl.vertices[ i ] = { x = net.ReadInt(16), y = net.ReadInt(16) } + end + tbl.parent = net.ReadInt(16) + tbl.size = net.ReadInt(16) + EGP:ReceiveMaterial( tbl ) + EGP:ReceiveColor( tbl, self ) + return tbl +end +Obj.DataStreamInfo = function( self ) + return { vertices = self.vertices, material = self.material, r = self.r, g = self.g, b = self.b, a = self.a, parent = self.parent } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/roundedbox.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/roundedbox.lua new file mode 100644 index 0000000..65725f8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/roundedbox.lua @@ -0,0 +1,65 @@ +-- Author: sk8 (& Divran) +local Obj = EGP:NewObject( "RoundedBox" ) +Obj.angle = 0 +Obj.radius = 16 +Obj.CanTopLeft = true +Obj.fidelity = 36 +Obj.Draw = function( self ) + if EGP:CacheNeedsUpdate(self, {"x", "y", "w", "h", "angle", "fidelity", "radius"}) then + local xs,ys , sx,sy = self.x,self.y , self.w, self.h + local polys = {} + local source = { {x=-1,y=-1} , {x=1,y=-1} , {x=1,y=1} , {x=-1,y=1} } + local radius = math.max(0,math.min((math.min(sx,sy)/2), self.radius )) + local div,angle = 360/self.fidelity, -self.angle + for x=1,4 do + for i=0,(self.fidelity+1)/4 do + local srx,sry = source[x].x,source[x].y + local scx,scy = srx*(sx-(radius*2))/2 , sry*(sy-(radius*2))/2 + scx,scy = scx*math.cos(math.rad(angle)) - scy*math.sin(math.rad(angle)), + scx*math.sin(math.rad(angle)) + scy*math.cos(math.rad(angle)) + local a,r = math.rad(div*i+(x*90)), radius + local dir = {x=math.sin(-(a+math.rad(angle))),y=math.cos(-(a+math.rad(angle)))} + local dirUV = {x=math.sin(-a),y=math.cos(-a)} + local ru,rv = (radius/sx),(radius/sy) + local u,v = 0.5 + (dirUV.x*ru) + (srx/2)*(1-(ru*2)), + 0.5 + (dirUV.y*rv) + (sry/2)*(1-(rv*2)) + polys[#polys+1] = {x=xs+scx+(dir.x*r), y=ys+scy+(dir.y*r) , u=u,v=v} + end + end + self.vert_cache.verts = polys + end + + surface.SetDrawColor(self.r,self.g,self.b,self.a) + surface.DrawPoly(self.vert_cache.verts) +end +Obj.Transmit = function( self ) + net.WriteInt((self.angle%360)*20, 16) + net.WriteInt(self.radius, 16) + net.WriteUInt(self.fidelity, 8) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + tbl.radius = net.ReadInt(16) + tbl.fidelity = net.ReadUInt(8) + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle, radius = self.radius, fidelity = self.fidelity } ) + return tbl +end +function Obj:Contains(point) + point = EGP.ScreenSpaceToObjectSpace(self, point) + local w, h = self.w / 2, self.h / 2 + local r = math.min(math.min(w, h), self.radius) + local x, y = math.abs(point.x), math.abs(point.y) + if x > w or y > h then return false end + x, y = x - w + r, y - h + r + if x < 0 or y < 0 then return true end + x, y = x / h, y / h + return x * x + y * y <= 1 +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/roundedboxoutline.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/roundedboxoutline.lua new file mode 100644 index 0000000..5919657 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/roundedboxoutline.lua @@ -0,0 +1,52 @@ +-- Author: sk8 (& Divran) +local Obj = EGP:NewObject( "RoundedBoxOutline" ) +Obj.angle = 0 +Obj.radius = 16 +Obj.size = 1 +Obj.CanTopLeft = true +Obj.fidelity = 36 +Obj.Draw = function( self ) + if EGP:CacheNeedsUpdate(self, {"x", "y", "w", "h", "angle", "fidelity", "radius"}) then + local xs,ys , sx,sy = self.x,self.y , self.w, self.h + local polys = {} + local source = { {x=-1,y=-1} , {x=1,y=-1} , {x=1,y=1} , {x=-1,y=1} } + local radius = math.max(0,math.min((math.min(sx,sy)/2), self.radius )) + local div,angle = 360/self.fidelity, -self.angle + for x=1,4 do + for i=0,(self.fidelity+1)/4 do + local srx,sry = source[x].x,source[x].y + local scx,scy = srx*(sx-(radius*2))/2 , sry*(sy-(radius*2))/2 + scx,scy = scx*math.cos(math.rad(angle)) - scy*math.sin(math.rad(angle)), + scx*math.sin(math.rad(angle)) + scy*math.cos(math.rad(angle)) + local a,r = math.rad(div*i+(x*90)), radius + local dir = {x=math.sin(-(a+math.rad(angle))),y=math.cos(-(a+math.rad(angle)))} + polys[#polys+1] = {x=xs+scx+(dir.x*r), y=ys+scy+(dir.y*r)} + end + end + self.vert_cache.verts = polys + end + surface.SetDrawColor(self.r,self.g,self.b,self.a) + EGP:DrawPath(self.vert_cache.verts, self.size, true) +end +Obj.Transmit = function( self ) + net.WriteInt((self.angle%360)*20, 16) + net.WriteInt(self.radius, 16) + net.WriteInt(self.size, 16) + net.WriteUInt(self.fidelity, 8) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + tbl.radius = net.ReadInt(16) + tbl.size = net.ReadInt(16) + tbl.fidelity = net.ReadUInt(8) + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle, radius = self.radius, fidelity = self.fidelity } ) + return tbl +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/text.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/text.lua new file mode 100644 index 0000000..ab3b1ec --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/text.lua @@ -0,0 +1,125 @@ +-- Author: Divran +local Obj = EGP:NewObject( "Text" ) +Obj.h = nil +Obj.w = nil +Obj.text = "" +Obj.font = "WireGPU_ConsoleFont" +Obj.size = 18 +Obj.valign = 0 +Obj.halign = 0 +Obj.angle = 0 + +local surface_SetTextPos +local surface_DrawText +local surface_SetTextColor +local surface_CreateFont +local surface_SetFont +local surface_GetTextSize +local cam_PushModelMatrix +local cam_PopModelMatrix +local mat +local matAng + +if CLIENT then + surface_SetTextPos = surface.SetTextPos + surface_DrawText = surface.DrawText + surface_SetTextColor = surface.SetTextColor + surface_CreateFont = surface.CreateFont + surface_SetFont = surface.SetFont + surface_GetTextSize = surface.GetTextSize + + + -- Thanks to Wizard for this rotateable text code + cam_PushModelMatrix = cam.PushModelMatrix + cam_PopModelMatrix = cam.PopModelMatrix + mat = Matrix() + matAng = Angle(0, 0, 0) +end + +function Obj:Draw(ent, drawMat) + if (self.text and #self.text>0) then + surface_SetTextColor( self.r, self.g, self.b, self.a ) + + local font = "WireEGP_" .. self.size .. "_" .. self.font + if (!EGP.ValidFonts_Lookup[font]) then + local fontTable = + { + font=self.font, + size = self.size, + weight = 800, + antialias = true, + additive = false + } + surface_CreateFont( font, fontTable ) + EGP.ValidFonts_Lookup[font] = true + end + surface_SetFont( font ) + + if self.angle == 0 then + local w,h + local x, y = self.x, self.y + if (self.halign != 0) then + w,h = surface_GetTextSize( self.text ) + x = x - (w * ((self.halign%10)/2)) + end + if (self.valign) then + if (!h) then _,h = surface_GetTextSize( self.text ) end + y = y - (h * ((self.valign%10)/2)) + end + + surface_SetTextPos( x, y ) + surface_DrawText( self.text ) + else + local w,h + local x, y = 0,0 + if (self.halign != 0) then + w,h = surface_GetTextSize( self.text ) + x = (w * ((self.halign%10)/2)) + end + if (self.valign) then + if (!h) then _,h = surface_GetTextSize( self.text ) end + y = (h * ((self.valign%10)/2)) + end + + mat:Set(drawMat) + + mat:Translate(Vector(self.x, self.y, 0)) + + matAng.y = -self.angle + mat:Rotate(matAng) + + surface_SetTextPos(-x, -y) + cam_PushModelMatrix(mat) + surface_DrawText( self.text ) + cam_PopModelMatrix() + end + end +end +Obj.Transmit = function( self, Ent, ply ) + net.WriteInt( self.x, 16 ) + net.WriteInt( self.y, 16 ) + EGP:InsertQueue( Ent, ply, EGP._SetText, "SetText", self.index, self.text ) + net.WriteString(self.font) + net.WriteUInt(math.Clamp(self.size,0,256), 8) + net.WriteUInt(math.Clamp(self.valign,0,2), 2) + net.WriteUInt(math.Clamp(self.halign,0,2), 2) + net.WriteInt( self.parent, 16 ) + EGP:SendColor( self ) + net.WriteInt((self.angle%360)*20, 16) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.x = net.ReadInt(16) + tbl.y = net.ReadInt(16) + tbl.font = net.ReadString() + tbl.size = net.ReadUInt(8) + tbl.valign = net.ReadUInt(2) + tbl.halign = net.ReadUInt(2) + tbl.parent = net.ReadInt(16) + EGP:ReceiveColor( tbl, self ) + tbl.angle = net.ReadInt(16)/20 + return tbl +end +Obj.DataStreamInfo = function( self ) + return { x = self.x, y = self.y, valign = self.valign, halign = self.halign, size = self.size, r = self.r, g = self.g, b = self.b, a = self.a, text = self.text, font = self.font, parent = self.parent, angle = self.angle } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/textlayout.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/textlayout.lua new file mode 100644 index 0000000..184645b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/textlayout.lua @@ -0,0 +1,120 @@ +-- Author: Divran +local Obj = EGP:NewObject( "TextLayout" ) +Obj.h = 512 +Obj.w = 512 +Obj.text = "" +Obj.font = "WireGPU_ConsoleFont" +Obj.size = 18 +Obj.valign = 0 +Obj.halign = 0 +Obj.angle = 0 + +local cam_PushModelMatrix +local cam_PopModelMatrix +local mat +local matAng + +if CLIENT then + -- Thanks to Wizard for this rotateable text code + cam_PushModelMatrix = cam.PushModelMatrix + cam_PopModelMatrix = cam.PopModelMatrix + mat = Matrix() + matAng = Angle(0, 0, 0) +end + +function Obj:Draw(ent, drawMat) + if (self.text and #self.text>0) then + surface.SetTextColor( self.r, self.g, self.b, self.a ) + + local font = "WireEGP_" .. self.size .. "_" .. self.font + if (!EGP.ValidFonts_Lookup[font]) then + local fontTable = + { + font=self.font, + size = self.size, + weight = 800, + antialias = true, + additive = false + } + surface.CreateFont( font, fontTable ) + EGP.ValidFonts_Lookup[font] = true + end + surface.SetFont( font ) + + --if (!self.layouter) then self.layouter = EGP:MakeTextLayouter() end -- Trying to make my own layouter... + --self.layouter:SetText( self.text, self.x, self.y, self.w, self.h, self.halign, self.valign, (self.fontid != self.oldfontid) ) + --self.layouter:DrawText() + --self.oldfontid = self.fontid + + if (!self.layouter) then self.layouter = MakeTextScreenLayouter() end + + if self.angle == 0 then + self.layouter:DrawText(self.text, self.x, self.y, self.w, self.h, self.halign, self.valign) + else + mat:Set(drawMat) + + mat:Translate(Vector(self.x, self.y, 0)) + + matAng.y = -self.angle + mat:Rotate(matAng) + + cam_PushModelMatrix(mat) + self.layouter:DrawText(self.text, 0, 0, self.w, self.h, self.halign, self.valign) + cam_PopModelMatrix() + end + + --[[ + if (!self.layouter) then + self.layouter = EGP:TextLayouter( font ) + self.layouter:SetJustify( false ) + self.layouter:SetJustifyLast( false ) + self.layouter:SetTabWidth( 4 ) + self.layouter:SetLimitHeight( true ) + self.oldvalues = {} + end + if (self.oldvalues.x != self.x or + self.oldvalues.y != self.y or + self.oldvalues.w != self.w or + self.oldvalues.h != self.h or + self.oldvalues.text != self.text or + self.oldvalues.size != self.size or + self.oldvalues.halign != self.halign or + self.oldvalues.valign != self.valign or + self.oldvalues.fontid != self.fontid) then + self.layouter:SetSize( self.w, self.h ) + self.layouter:SetPos( self.x, self.y ) + self.layouter:SetText( self.text ) + self.layouter:SetFont( font ) + self.layouter:Reset() + self.oldvalues = table.Copy( self ) + end + self.layouter:Draw() + ]] + end +end +Obj.Transmit = function( self, Ent, ply ) + EGP:SendPosSize( self ) + EGP:InsertQueue( Ent, ply, EGP._SetText, "SetText", self.index, self.text ) + net.WriteString(self.font) + net.WriteUInt(math.Clamp(self.size,0,256), 8) + net.WriteUInt(math.Clamp(self.valign,0,2), 2) + net.WriteUInt(math.Clamp(self.halign,0,2), 2) + net.WriteInt( self.parent, 16 ) + EGP:SendColor( self ) + net.WriteInt((self.angle%360)*20, 16) +end +Obj.Receive = function( self ) + local tbl = {} + EGP:ReceivePosSize( tbl ) + tbl.font = net.ReadString(8) + tbl.size = net.ReadUInt(8) + tbl.valign = net.ReadUInt(2) + tbl.halign = net.ReadUInt(2) + tbl.parent = net.ReadInt(16) + EGP:ReceiveColor( tbl, self ) + tbl.angle = net.ReadInt(16)/20 + return tbl +end +Obj.DataStreamInfo = function( self ) + return { x = self.x, y = self.y, w = self.w, h = self.h, valign = self.valign, halign = self.halign, size = self.size, r = self.r, g = self.g, b = self.b, a = self.a, text = self.text, font = self.font, parent = self.parent, angle = self.angle } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/wedge.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/wedge.lua new file mode 100644 index 0000000..ee4322e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/wedge.lua @@ -0,0 +1,63 @@ +-- Author: Divran +local Obj = EGP:NewObject( "Wedge" ) +Obj.angle = 0 +Obj.size = 45 +Obj.fidelity = 180 +local rad, cos, sin = math.rad, math.cos, math.sin +Obj.Draw = function( self ) + if self.a>0 and self.w > 0 and self.h > 0 and self.size ~= 360 then + if EGP:CacheNeedsUpdate(self, {"x", "y", "w", "h", "angle", "fidelity", "size"}) then + local vertices = {} + + vertices[1] = { x = self.x, y = self.y, u = 0, v = 0 } + local ang = -rad(self.angle) + local c = cos(ang) + local s = sin(ang) + for ii=0,self.fidelity do + local i = ii*(360-self.size)/self.fidelity + local radd = rad(i) + local x = cos(radd) + local u = (x+1)/2 + local y = sin(radd) + local v = (y+1)/2 + + --radd = -rad(self.angle) + local tempx = x * self.w * c - y * self.h * s + self.x + y = x * self.w * s + y * self.h * c + self.y + x = tempx + + vertices[ii+2] = { x = x, y = y, u = u, v = v } + end + self.vert_cache.verts = vertices + end + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + surface.DrawPoly( self.vert_cache.verts ) + end +end +Obj.Transmit = function( self ) + net.WriteInt( (self.angle%360)*20, 16 ) + net.WriteInt( (self.size%360)*20, 16 ) + net.WriteUInt(self.fidelity, 8) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + tbl.size = net.ReadInt(16)/20 + tbl.fidelity = net.ReadUInt(8) + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle, size = self.size, fidelity = self.fidelity } ) + return tbl +end +function Obj:Contains(point) + point = EGP.ScreenSpaceToObjectSpace(self, point) + local x, y = point.x / self.w, point.y / self.h + if x * x + y * y > 1 then return false end + local theta = math.deg(math.atan2(y, x)) + return theta <= -self.size or 0 <= theta +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/wedgeoutline.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/wedgeoutline.lua new file mode 100644 index 0000000..963316c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/lib/objects/wedgeoutline.lua @@ -0,0 +1,53 @@ +-- Author: Divran +local Obj = EGP:NewObject( "WedgeOutline" ) +Obj.angle = 0 +Obj.size = 45 +Obj.fidelity = 180 +local rad, cos, sin = math.rad, math.cos, math.sin +Obj.Draw = function( self ) + if (self.a>0 and self.w > 0 and self.h > 0 and self.size ~= 360) then + if EGP:CacheNeedsUpdate(self, {"x", "y", "w", "h", "angle", "fidelity", "size"}) then + local vertices = {} + + vertices[1] = { x = self.x, y = self.y } + local ang = -rad(self.angle) + local c = cos(ang) + local s = sin(ang) + for ii=0,self.fidelity do + local i = ii*(360-self.size)/self.fidelity + local radd = rad(i) + local x = cos(radd) + local y = sin(radd) + local tempx = x * self.w * c - y * self.h * s + self.x + y = x * self.w * s + y * self.h * c + self.y + x = tempx + + vertices[ii+2] = { x = x, y = y } + end + self.vert_cache.verts = vertices + end + + surface.SetDrawColor( self.r, self.g, self.b, self.a ) + EGP:DrawPath(self.vert_cache.verts, 1, true) + end +end +Obj.Transmit = function( self ) + net.WriteInt( (self.angle%360)*20, 16 ) + net.WriteInt( (self.size%360)*20, 16 ) + net.WriteUInt(self.fidelity, 8) + self.BaseClass.Transmit( self ) +end +Obj.Receive = function( self ) + local tbl = {} + tbl.angle = net.ReadInt(16)/20 + tbl.size = net.ReadInt(16)/20 + tbl.fidelity = net.ReadUInt(8) + table.Merge( tbl, self.BaseClass.Receive( self ) ) + return tbl +end +Obj.DataStreamInfo = function( self ) + local tbl = {} + table.Merge( tbl, self.BaseClass.DataStreamInfo( self ) ) + table.Merge( tbl, { angle = self.angle, size = self.size, fidelity = self.fidelity } ) + return tbl +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/shared.lua new file mode 100644 index 0000000..7564f16 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp/shared.lua @@ -0,0 +1,20 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire EGP" +ENT.Author = "Divran" +ENT.Contact = "Divran @ Wiremod" +ENT.Purpose = "Bring Graphic Processing to E2" +ENT.Instructions = "Wirelink To E2" + +ENT.Spawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH + +include("lib/init.lua") +if (SERVER) then AddCSLuaFile("lib/init.lua") end + + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Translucent" ) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_emitter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_emitter.lua new file mode 100644 index 0000000..0426d6a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_emitter.lua @@ -0,0 +1,168 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "gmod_wire_egp" ) +ENT.PrintName = "Wire E2 Graphics Processor Emitter" +ENT.WireDebugName = "E2 Graphics Processor Emitter" +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.gmod_wire_egp_emitter = true + +local DrawOffsetPos = Vector(0, 0, 71) +local DrawOffsetAng = Angle(0, 0, 90) +local DrawScale = 0.25 +local DrawOffsetNoRT = Vector(-64,0,64) + +if SERVER then + + function ENT:Initialize() + BaseClass.Initialize(self) + + self:SetDrawOffsetPos(DrawOffsetPos) + self:SetDrawOffsetAng(DrawOffsetAng) + self:SetDrawScale(DrawScale) + + self.Inputs = WireLib.CreateSpecialInputs(self, { "Scale", "Position", "Angle" }, {"NORMAL", "VECTOR", "ANGLE"}) + end + + function ENT:TriggerInput(iname, value) + if iname == "Scale" then + self:SetDrawScale( math.Clamp(value * 0.25, 0.01, 0.5) ) + + elseif iname == "Position" then + local x = math.Clamp(value.x, -150, 150) + local y = math.Clamp(value.y, -150, 150) + local z = math.Clamp(value.z, -150, 150) + + self:SetDrawOffsetPos(Vector(x, y, z)) + elseif iname == "Angle" then + self:SetDrawOffsetAng(value) + end + end +end + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "DrawScale" ) + self:NetworkVar( "Vector", 0, "DrawOffsetPos" ) + self:NetworkVar( "Angle", 0, "DrawOffsetAng" ) + self:NetworkVar( "Bool", 0, "UseRT" ) +end + +if CLIENT then + function ENT:Initialize() + BaseClass.Initialize(self) + self.GPU.GetInfo = function() + local pos = self:LocalToWorld(self:GetDrawOffsetPos()) + local ang = self:LocalToWorldAngles(self:GetDrawOffsetAng()) + return {RS = self:GetDrawScale(), RatioX = 1, translucent = true}, pos, ang + end + end + + function ENT:DrawEntityOutline() end + + local wire_egp_emitter_drawdist = CreateClientConVar("wire_egp_emitter_drawdist","0",true,false) + local wire_egp_drawemitters = CreateClientConVar("wire_egp_drawemitters", "1") + + function ENT:EGP_Update( Table ) + self.NeedsUpdate = true + self.NextUpdate = Table + + -- the parent checks need to be processed here if we aren't using a GPU + self.UpdateConstantly = nil + if self.GPU == nil then + for k,object in pairs(self.RenderTable) do + if object.parent == -1 or object.Is3DTracker then self.UpdateConstantly = true end -- Check if an object is parented to the cursor (or for 3DTrackers) + if object.parent and object.parent ~= 0 then + if not object.IsParented then EGP:SetParent(self, object.index, object.parent) end + local _, data = EGP:GetGlobalPos(self, object.index) + EGP:EditObject(object, data) + elseif not object.parent or object.parent == 0 and object.IsParented then + EGP:UnParent(self, object.index) + end + end + end + end + + function ENT:DrawNoRT() + if (wire_egp_drawemitters:GetBool() == true and self.RenderTable and #self.RenderTable > 0) then + if (self.UpdateConstantly) then self:EGP_Update() end + local pos = self:LocalToWorld(self:GetDrawOffsetPos() + DrawOffsetNoRT) + local ang = self:LocalToWorldAngles(self:GetDrawOffsetAng()) + local mat = self:GetEGPMatrix() + cam.Start3D2D(pos, ang, self:GetDrawScale()) + local globalfilter = TEXFILTER.ANISOTROPIC -- Emitter uses ANISOTRPOIC (unchangeable) + for i=1,#self.RenderTable do + local object = self.RenderTable[i] + local oldtex = EGP:SetMaterial( object.material ) + if object.filtering ~= globalfilter then + render.PushFilterMag(object.filtering) + render.PushFilterMin(object.filtering) + object:Draw(self, mat) + render.PopFilterMin() + render.PopFilterMag() + else + object:Draw(self, mat) + end + EGP:FixMaterial( oldtex ) + end + cam.End3D2D() + end + end + + -- cam.PushModelMatrix replaces the currently drawn matrix, because cam.Start3D2D + -- pushes a matrix of its own we need to replicate it + function ENT:GetEGPMatrix() + if self.GPU ~= nil then + local mat = Matrix() + local pos = self:LocalToWorld(self:GetDrawOffsetPos() + DrawOffsetNoRT) + mat:SetTranslation(pos) + -- Just using the angle given to cam.Start3D2D doesn't seem to work, it seems to be rotated 180 on the roll + local ang = self:LocalToWorldAngles(self:GetDrawOffsetAng() + Angle(0, 0, 180)) + mat:SetAngles(ang) + local scale = Vector(1, 1, 1) + scale:Mul(self:GetDrawScale()) + mat:SetScale(scale) + return mat + else + return BaseClass.GetEGPMatrix(self) + end + end + + function ENT:Draw() + -- check if the RT should be removed or recreated + local hasGPU = (self.GPU~=nil) + if self:GetUseRT() == false and hasGPU then + self.GPU:FreeRT() -- remove gpu RT + self.GPU = nil + if not self.RenderTable or #self.RenderTable == 0 then -- if the screen is empty + self.RenderTable = table.Copy(EGP.HomeScreen) -- copy home screen + end + self:EGP_Update() + elseif self:GetUseRT() == true and not hasGPU then + local t = self.RenderTable -- save reference + self:Initialize() -- recreate gpu RT + self.RenderTable = t -- restore render table + self:EGP_Update() + end + + if self.GPU then -- if we're rendering on RT, use base EGP's draw function instead + BaseClass.Draw(self) + else + self:DrawModel() + self:DrawNoRT() + Wire_Render(self) + end + end + + function ENT:GetTranslucent() return true end -- emitters are always transparent + + function ENT:Think() + local dist = Vector(1,0,1)*wire_egp_emitter_drawdist:GetInt() + self:SetRenderBounds(Vector(-64,0,0)-dist,Vector(64,0,135)+dist) + end + + + function ENT:OnRemove() + if self.GPU then self.GPU:Finalize() end + end +end + +function ENT:UpdateTransmitState() return TRANSMIT_ALWAYS end \ No newline at end of file diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/cl_init.lua new file mode 100644 index 0000000..4ae1366 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/cl_init.lua @@ -0,0 +1,108 @@ +include('shared.lua') +include("HUDDraw.lua") + +ENT.gmod_wire_egp_hud = true + +-------------------------------------------------------- +-- 0-512 to screen res & back +-------------------------------------------------------- + +local makeArray +local makeTable +if (EGP) then -- If the table has been loaded + makeArray = EGP.ParentingFuncs.makeArray + makeTable = EGP.ParentingFuncs.makeTable +else -- If the table hasn't been loaded + hook.Add("Initialize",function() + makeArray = EGP.ParentingFuncs.makeArray + makeTable = EGP.ParentingFuncs.makeTable + end) +end + +function ENT:GetEGPMatrix() + return Matrix() +end + +function ENT:ScaleObject( bool, v ) + local xMin, xMax, yMin, yMax, _xMul, _yMul + if (bool) then -- 512 -> screen + xMin = 0 + xMax = 512 + yMin = 0 + yMax = 512 + _xMul = ScrW() + _yMul = ScrH() + else -- screen -> 512 + xMin = 0 + xMax = ScrW() + yMin = 0 + yMax = ScrH() + _xMul = 512 + _yMul = 512 + end + + local xMul = _xMul/(xMax-xMin) + local yMul = _yMul/(yMax-yMin) + + if (v.verticesindex) then -- Object has vertices + local r = makeArray( v, true ) + for i=1,#r,2 do + r[i] = (r[i] - xMin) * xMul + r[i+1] = (r[i+1]- yMin) * yMul + end + local settings = {} + if isstring(v.verticesindex) then settings = { [v.verticesindex] = makeTable( v, r ) } else settings = makeTable( v, r ) end + EGP:EditObject( v, settings ) + else + if (v.x) then + v.x = (v.x - xMin) * xMul + end + if (v.y) then + v.y = (v.y - yMin) * yMul + end + if (v.w) then + v.w = v.w * xMul + end + if (v.h) then + v.h = v.h * yMul + end + end + + v.res = bool +end + +function ENT:Initialize() + self.RenderTable = {} + self.Resolution = false -- False = Use screen res. True = 0-512 res. + self.OldResolution = false + + EGP:AddHUDEGP( self ) +end + +function ENT:EGP_Update() + for k,v in pairs( self.RenderTable ) do + if (v.res == nil) then v.res = false end + if (v.res != self.Resolution) then + self:ScaleObject( !v.res, v ) + end + if (v.parent and v.parent != 0) then + if (!v.IsParented) then EGP:SetParent( self, v.index, v.parent ) end + local _, data = EGP:GetGlobalPos( self, v.index ) + EGP:EditObject( v, data ) + elseif (!v.parent or v.parent == 0 and v.IsParented) then + EGP:UnParent( self, v.index ) + end + end + self.OldResolution = self.Resolution +end + +function ENT:DrawEntityOutline() end + +function ENT:Draw() + self.Resolution = self:GetNWBool("Resolution",false) + if (self.Resolution != self.OldResolution) then + self:EGP_Update() + end + self:DrawModel() + Wire_Render(self) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/huddraw.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/huddraw.lua new file mode 100644 index 0000000..b5a9e1a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/huddraw.lua @@ -0,0 +1,181 @@ +hook.Add("Initialize","EGP_HUD_Initialize",function() + if (CLIENT) then + local EGP_HUD_FirstPrint = true + local tbl = {} + + -------------------------------------------------------- + -- Toggle + -------------------------------------------------------- + local function EGP_Use( um ) + local ent = um:ReadEntity() + if (!ent or !ent:IsValid()) then return end + local bool = um:ReadChar() + if (bool == -1) then + ent.On = nil + elseif (bool == 1) then + ent.On = true + elseif (bool == 0) then + if (ent.On == true) then + ent.On = nil + LocalPlayer():ChatPrint("[EGP] EGP HUD Disconnected.") + else + if (!tbl[ent]) then -- strange... this entity should be in the table. Might have gotten removed due to a lagspike. Add it again + EGP:AddHUDEGP( ent ) + end + ent.On = true + if (EGP_HUD_FirstPrint) then + LocalPlayer():ChatPrint("[EGP] EGP HUD Connected. NOTE: Type 'wire_egp_hud_unlink' in console to disconnect yourself from all EGP HUDs.") + EGP_HUD_FirstPrint = nil + else + LocalPlayer():ChatPrint("[EGP] EGP HUD Connected.") + end + end + end + end + usermessage.Hook( "EGP_HUD_Use", EGP_Use ) + + -------------------------------------------------------- + -- Disconnect all HUDs + -------------------------------------------------------- + concommand.Add("wire_egp_hud_unlink",function() + local en = ents.FindByClass("gmod_wire_egp_hud") + LocalPlayer():ChatPrint("[EGP] Disconnected from all EGP HUDs.") + for k,v in ipairs( en ) do + v.On = nil + end + end) + + -------------------------------------------------------- + -- Add / Remove HUD Entities + -------------------------------------------------------- + function EGP:AddHUDEGP( Ent ) + tbl[Ent] = true + end + + function EGP:RemoveHUDEGP( Ent ) + tbl[Ent] = nil + end + + -------------------------------------------------------- + -- Paint + -------------------------------------------------------- + hook.Add("HUDPaint","EGP_HUDPaint",function() + for Ent,_ in pairs( tbl ) do + if (!Ent or !Ent:IsValid()) then + EGP:RemoveHUDEGP( Ent ) + break + else + if (Ent.On == true) then + if (Ent.RenderTable and #Ent.RenderTable > 0) then + local mat = Ent:GetEGPMatrix() + + for _,object in pairs( Ent.RenderTable ) do + local oldtex = EGP:SetMaterial( object.material ) + object:Draw(Ent, mat) + EGP:FixMaterial( oldtex ) + + -- Check for 3DTracker parent + if (object.parent) then + local hasObject, _, parent = EGP:HasObject( Ent, object.parent ) + if (hasObject and parent.Is3DTracker) then + Ent:EGP_Update() + end + end + end + end + end + end + end + end) -- HUDPaint hook + else + local vehiclelinks = {} + + function EGP:LinkHUDToVehicle( hud, vehicle ) + if not hud.LinkedVehicles then hud.LinkedVehicles = {} end + if not hud.Marks then hud.Marks = {} end + + hud.Marks[#hud.Marks+1] = vehicle + hud.LinkedVehicles[vehicle] = true + vehiclelinks[hud] = hud.LinkedVehicles + + vehicle:CallOnRemove( "EGP HUD unlink on remove", function( ent ) + EGP:UnlinkHUDFromVehicle( hud, ent ) + end) + + timer.Simple( 0.1, function() -- timers solve everything (this time, it's the fact that the entity isn't valid on the client after dupe) + WireLib.SendMarks( hud ) + end) + end + + function EGP:UnlinkHUDFromVehicle( hud, vehicle ) + if not vehicle then -- unlink all + if hud.Marks then + for i=1,#hud.Marks do + if hud.Marks[i]:IsValid() then + hud.Marks[i]:RemoveCallOnRemove( "EGP HUD unlink on remove" ) + end + end + end + vehiclelinks[hud] = nil + hud.LinkedVehicles = nil + hud.Marks = nil + else + if vehiclelinks[hud] then + local bool = vehiclelinks[hud][vehicle] + if bool then + if vehicle:IsValid() then + vehicle:RemoveCallOnRemove( "EGP HUD unlink on remove" ) + if vehicle:GetDriver() and vehicle:GetDriver():IsValid() then + umsg.Start( "EGP_HUD_Use", vehicle:GetDriver() ) + umsg.Entity( hud ) + umsg.Char( -1 ) + umsg.End() + end + end + end + + if hud.Marks then + for i=1,#hud.Marks do + if hud.Marks[i] == vehicle then + table.remove( hud.Marks, i ) + break + end + end + end + + hud.LinkedVehicles[vehicle] = nil + if not next( hud.LinkedVehicles ) then + hud.LinkedVehicles = nil + hud.Marks = nil + end + + vehiclelinks[hud] = hud.LinkedVehicles + end + end + + WireLib.SendMarks( hud ) + end + + hook.Add("PlayerEnteredVehicle","EGP_HUD_PlayerEnteredVehicle",function( ply, vehicle ) + for k,v in pairs( vehiclelinks ) do + if v[vehicle] ~= nil then + umsg.Start( "EGP_HUD_Use", ply ) + umsg.Entity( k ) + umsg.Char( 1 ) + umsg.End() + end + end + end) + + hook.Add("PlayerLeaveVehicle","EGP_HUD_PlayerLeaveVehicle",function( ply, vehicle ) + for k,v in pairs( vehiclelinks ) do + if v[vehicle] ~= nil then + umsg.Start( "EGP_HUD_Use", ply ) + umsg.Entity( k ) + umsg.Char( -1 ) + umsg.End() + end + end + end) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/init.lua new file mode 100644 index 0000000..8c1a875 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/init.lua @@ -0,0 +1,105 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +AddCSLuaFile("huddraw.lua") +include("huddraw.lua") + +DEFINE_BASECLASS("base_wire_entity") + +ENT.WireDebugName = "E2 Graphics Processor HUD" + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.RenderTable = {} + + self:SetUseType(SIMPLE_USE) + + self.Inputs = WireLib.CreateInputs( self, { "0 to 512" } ) + WireLib.CreateWirelinkOutput( nil, self, {true} ) + + self.xScale = { 0, 512 } + self.yScale = { 0, 512 } + self.Scaling = false + + self.TopLeft = false +end + +function ENT:TriggerInput( name, value ) + if (name == "0 to 512") then + self:SetNWBool( "Resolution", value != 0 ) + end +end + +function ENT:Use( ply ) + umsg.Start( "EGP_HUD_Use", ply ) umsg.Entity( self ) umsg.End() +end + +function ENT:SetEGPOwner( ply ) + self.ply = ply + self.plyID = ply:UniqueID() +end + +function ENT:GetEGPOwner() + if (!self.ply or !self.ply:IsValid()) then + local ply = player.GetByUniqueID( self.plyID ) + if (ply) then self.ply = ply end + return ply + else + return self.ply + end + return false +end + +function ENT:UpdateTransmitState() return TRANSMIT_ALWAYS end + +function ENT:LinkEnt( ent ) + ent = WireLib.GetClosestRealVehicle(ent,self:GetPos(),self:GetPlayer()) + + if IsValid( ent ) and ent:IsVehicle() then + if self.LinkedVehicles and self.LinkedVehicles[ent] then + return false + end + + EGP:LinkHUDToVehicle( self, ent ) + return true + else + return false, tostring(ent) .. " is invalid or is not a vehicle" + end +end + +function ENT:OnRemove() + EGP:UnlinkHUDFromVehicle( self ) +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + local vehicles = self.LinkedVehicles + if vehicles then + local _vehicles = {} + for k,v in pairs( vehicles ) do + _vehicles[#_vehicles+1] = k:EntIndex() + end + info.egp_hud_vehicles = _vehicles + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + local vehicles = info.egp_hud_vehicles + if vehicles then + for i=1,#vehicles do + local vehicle = GetEntByID( vehicles[i] ) + + if IsValid( vehicle ) then + self:LinkEnt( vehicle ) + end + end + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/shared.lua new file mode 100644 index 0000000..39b24ca --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_egp_hud/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire EGP HUD" +ENT.Author = "Divran" +ENT.Contact = "Divran @ Wiremod" +ENT.Purpose = "EGP Hud" +ENT.Instructions = "WireLink To E2" + +ENT.Spawnable = false diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_emarker.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_emarker.lua new file mode 100644 index 0000000..843f87a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_emarker.lua @@ -0,0 +1,47 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Entity Marker" +ENT.WireDebugName = "EMarker" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = WireLib.CreateSpecialOutputs(self, { "Entity" }, { "ENTITY" }) + self:SetOverlayText( "No Mark selected" ) +end + +function ENT:LinkEMarker(mark) + if mark then self.mark = mark end + if not IsValid(self.mark) then self:SetOverlayText( "No Mark selected" ) return end + self.mark:CallOnRemove("EMarker.UnLink", function(ent) + if IsValid(self) and self.mark == ent then self:UnLinkEMarker() end + end) + Wire_TriggerOutput(self, "Entity", self.mark) + self:SetOverlayText( "Linked - " .. self.mark:GetModel() ) +end + +function ENT:UnLinkEMarker() + self.mark = NULL + Wire_TriggerOutput(self, "Entity", NULL) + self:SetOverlayText( "No Mark selected" ) +end + +duplicator.RegisterEntityClass( "gmod_wire_emarker", WireLib.MakeWireEnt, "Data" ) + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if ( self.mark ) and ( self.mark:IsValid() ) then + info.mark = self.mark:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self:LinkEMarker(GetEntByID(info.mark)) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_exit_point.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_exit_point.lua new file mode 100644 index 0000000..542dae9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_exit_point.lua @@ -0,0 +1,159 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Vehicle Exit Point" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + BaseClass.Initialize(self) + + self.Inputs = WireLib.CreateInputs(self, {"Entity [ENTITY]", "Entities [ARRAY]", "Position [VECTOR]", "Local Position [VECTOR]", "Angle [ANGLE]", "Local Angle [ANGLE]"}) + + self.Position = Vector(0,0,0) + self.Angle = Angle(0,0,0) + self.Entities = {} + self.Global = false + self.GlobalAngle = false + self:AddExitPoint() + + self:ShowOutput() +end + +function ENT:TriggerInput( name, value ) + if (name == "Entity") then + self.Entities = {} + if self:CheckPP(value) then + self:LinkEnt(value) + end + elseif (name == "Entities") then + self.Entities = {} + for _, ent in pairs(value) do + if self:CheckPP(ent) then + self:LinkEnt(ent) + end + end + elseif (name == "Position") then + self.Position = value + self.Global = true + elseif (name == "Local Position") then + self.Position = value + self.Global = false + elseif (name == "Angle") then + self.Angle = value + self.GlobalAngle = true + elseif (name == "Local Angle") then + self.Angle = value + self.GlobalAngle = false + end + self:ShowOutput() +end + +function ENT:ShowOutput() + self:SetOverlayText(string.format("Entities linked: %i\n%sPosition: (%.2f, %.2f, %.2f)", table.Count(self.Entities), self.Global and "" or "Local ", self.Position.x, self.Position.y, self.Position.z)) +end + +function ENT:CheckPP(ent) + -- Check Prop Protection. Most block/allow all of CanTool, but lets check hoverdrive controller specifically, since if they can attach one to your vehicle, they can simulate this anyways + return IsValid(ent) and gamemode.Call("CanTool", self:GetPlayer(), WireLib.dummytrace(ent), "wire_hoverdrivecontroller") +end + + +local ExitPoints = {} +function ENT:AddExitPoint() + ExitPoints[self] = true +end +local function RemoveExitPoint( ent ) + if ExitPoints[ent] then ExitPoints[ent] = nil end +end +hook.Add( "EntityRemoved", "WireExitPoint", RemoveExitPoint ) + + +local ClampDistance = CreateConVar("wire_pod_exit_distance", "1000", FCVAR_ARCHIVE, "The maximum distance an exit point can move a player") +local function MovePlayer( ply, vehicle ) + for epoint, _ in pairs( ExitPoints ) do + if IsValid(epoint) and not epoint.Position:IsZero() and epoint.Entities and epoint.Entities[vehicle] then + if epoint.Global then + local origin = vehicle:GetPos() + local direction = epoint.Position - origin + local direction_distance = direction:Length() + ply:SetPos( origin + direction / direction_distance * math.min(direction_distance, math.max(0, ClampDistance:GetInt())) + Vector(0,0,5) ) -- Add 5z so they don't get stuck in the GPS or whatnot + local ang = ply:EyeAngles() + else + local LocalPosDistance = epoint.Position:Length() + ply:SetPos( vehicle:LocalToWorld( epoint.Position / LocalPosDistance * math.min(LocalPosDistance, math.max(0, ClampDistance:GetInt()))) + Vector(0,0,5) ) + end + + if epoint.GlobalAngle then + ply:SetEyeAngles( Angle( epoint.Angle.p, epoint.Angle.y, 0 ) ) + else + local ang = epoint:LocalToWorldAngles( epoint.Angle ) + ang.r = 0 + ply:SetEyeAngles( ang ) + end + + return + end + end +end +hook.Add("PlayerLeaveVehicle", "WireExitPoint", MovePlayer ) + +function ENT:SendMarks() + local marks = {} + for ent,_ in pairs(self.Entities) do table.insert(marks, ent) end + WireLib.SendMarks(self, marks) +end + +function ENT:LinkEnt( ent ) + ent = WireLib.GetClosestRealVehicle(ent,self:GetPos(),self:GetPlayer()) + + if not IsValid(ent) or not ent:IsVehicle() then return false, "Must link to a vehicle" end + if self.Entities[ent] then return end + self.Entities[ent] = true + ent:CallOnRemove("ExitPoint.Unlink", function(ent) + if IsValid(self) then self:UnlinkEnt(ent) end + end) + + self:SendMarks() + self:ShowOutput() + return true +end + +function ENT:UnlinkEnt( ent ) + if not self.Entities[ent] then return end + self.Entities[ent] = nil + + self:SendMarks() + self:ShowOutput() + return true +end + +function ENT:ClearEntities() + self.Entities = {} + WireLib.SendMarks(self, {}) + self:ShowOutput() +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if next(self.Entities) then + info.marks = {} + for ent, _ in pairs(self.Entities) do + if IsValid(ent) then table.insert(info.marks, ent:EntIndex()) end + end + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if info.marks then + for _, entindex in pairs(info.marks) do + self:LinkEnt(GetEntByID(entindex)) + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_exit_point", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_explosive.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_explosive.lua new file mode 100644 index 0000000..fd4a2d0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_explosive.lua @@ -0,0 +1,274 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Explosive" +ENT.WireDebugName = "Explosive" + +if CLIENT then return end -- No more client + +local wire_explosive_delay = CreateConVar( "wire_explosive_delay", 0.2, FCVAR_ARCHIVE ) +local wire_explosive_range = CreateConVar( "wire_explosive_range", 512, FCVAR_ARCHIVE ) + +function ENT:Initialize() + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + phys:Wake() + end + + self.exploding = false + self.reloading = false + self.NormInfo = "" + self.count = 0 + self.ExplodeTime = 0 + self.ReloadTime = 0 + self.CountTime = 0 + + self.Inputs = Wire_CreateInputs(self, { "Detonate", "ResetHealth" }) + + self:SetMaxHealth(100) + self:SetHealth(100) + self.Outputs = Wire_CreateOutputs(self, { "Health" }) +end + +function ENT:TriggerInput(iname, value) + if (iname == "Detonate") then + if ( !self.exploding && !self.reloading ) then + if ( math.abs(value) == self.key ) then + self:Trigger() + end + end + elseif (iname == "ResetHealth") then + self:ResetHealth() + end +end + +function ENT:Setup( key, damage, delaytime, removeafter, radius, affectother, notaffected, delayreloadtime, maxhealth, bulletproof, explosionproof, fallproof, explodeatzero, resetatexplode, fireeffect, coloreffect, invisibleatzero ) + + self.key = key + self.Damage = math.Clamp( damage, 0, 1500 ) + self.Delaytime = delaytime + self.Removeafter = removeafter + self.Radius = math.Clamp(radius, 1, wire_explosive_range:GetFloat()) + self.Affectother = affectother + self.Notaffected = notaffected + self.Delayreloadtime = delayreloadtime + + self.BulletProof = bulletproof + self.ExplosionProof = explosionproof + self.FallProof = fallproof + + self.ExplodeAtZero = explodeatzero + self.ResetAtExplode = resetatexplode + + self.FireEffect = fireeffect + self.ColorEffect = coloreffect + self.InvisibleAtZero = invisibleatzero + + self:SetMaxHealth(maxhealth) + self:ResetHealth() + + --[[ + self:SetHealth(maxhealth) + Wire_TriggerOutput(self, "Health", maxhealth) + + reset everthing back and try to stop exploding + self.exploding = false + self.reloading = false + self.count = 0 + self:Extinguish() + if (self.ColorEffect) then self:SetColor(Color(255, 255, 255, 255)) end + ]] + + self.NormInfo = "" + if (self.Damage > 0) then self.NormInfo = self.NormInfo.."Damage: "..self.Damage end + if (self.Radius > 0 || self.Delaytime > 0) then self.NormInfo = self.NormInfo.."\n" end + if (self.Radius > 0 ) then self.NormInfo = self.NormInfo.." Rad: "..self.Radius end + if (self.Delaytime > 0) then self.NormInfo = self.NormInfo.." Delay: "..self.Delaytime end + + self:ShowOutput() + + local ttable = { + key = key, + damage = damage, + removeafter = removeafter, + delaytime = delaytime, + radius = radius, + affectother = affectother, + notaffected = notaffected, + delayreloadtime = delayreloadtime, + maxhealth = maxhealth, + bulletproof = bulletproof, + explosionproof = explosionproof, + fallproof = fallproof, + explodeatzero = explodeatzero, + resetatexplode = resetatexplode, + fireeffect = fireeffect, + coloreffect = coloreffect, + invisibleatzero = invisibleatzero + } + table.Merge( self:GetTable(), ttable ) +end + +function ENT:ResetHealth( ) + self:SetHealth( self:GetMaxHealth() ) + Wire_TriggerOutput(self, "Health", self:GetMaxHealth()) + + -- put the fires out and try to stop exploding + self.exploding = false + self.reloading = false + self.count = 0 + self:Extinguish() + + if (self.ColorEffect) then self:SetColor(Color(255, 255, 255, 255)) end + + self:SetNoDraw( false ) + + self:ShowOutput() +end + +function ENT:OnTakeDamage( dmginfo ) + + if ( dmginfo:GetInflictor():GetClass() == "gmod_wire_explosive" && !self.Affectother ) then return end + + if ( !self.Notaffected ) then self:TakePhysicsDamage( dmginfo ) end + + if (dmginfo:IsBulletDamage() && self.BulletProof) || + (dmginfo:IsExplosionDamage() && self.ExplosionProof) || + (dmginfo:IsFallDamage() && self.FallProof) then return end //fix fall damage, it doesn't happen + + if (self:Health() > 0) then //don't need to beat a dead horse + local dammage = dmginfo:GetDamage() + local h = self:Health() - dammage + if (h < 0) then h = 0 end + self:SetHealth(h) + Wire_TriggerOutput(self, "Health", h) + self:ShowOutput() + if (self.ColorEffect) then + local c = h == 0 and 0 or 255 * (h / self:GetMaxHealth()) + self:SetColor(Color(255, c, c, 255)) + end + if (h == 0) then + if (self.ExplodeAtZero) then self:Trigger() end + end + end + +end + +-- Start exploding +function ENT:Trigger() + if ( self.Delaytime > 0 ) then + self.ExplodeTime = CurTime() + self.Delaytime + if (self.FireEffect) then self:Ignite((self.Delaytime + 3),0) end + end + self.exploding = true + // Force reset of counter + self.CountTime = 0 +end + +function ENT:Think() + BaseClass.Think(self) + + if (self.exploding) then + if (self.ExplodeTime < CurTime()) then + self:Explode() + end + elseif (self.reloading) then + if (self.ReloadTime < CurTime()) then + self.reloading = false + if (self.ResetAtExplode) then + self:ResetHealth() + else + self:ShowOutput() + end + end + end + + // Do count check to ensure that + // ShowOutput() is called every second + // when exploding or reloading + if ((self.CountTime or 0) < CurTime()) then + local temptime = 0 + if (self.exploding) then + temptime = self.ExplodeTime + elseif (self.reloading) then + temptime = self.ReloadTime + end + + if (temptime > 0) then + self.count = math.ceil(temptime - CurTime()) + self:ShowOutput() + end + + self.CountTime = CurTime() + 1 + end + + self:NextThink(CurTime() + 0.05) + return true +end + +function ENT:Explode( ) + + if ( !self:IsValid() ) then return end + + self:Extinguish() + + if (!self.exploding) then return end //why are we exploding if we shouldn't be + + local ply = self:GetPlayer() or self + if(not IsValid(ply)) then ply = self end; + + if (self.InvisibleAtZero) then + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetNoDraw( true ) + self:SetColor(Color(255, 255, 255, 0)) + end + + if ( self.Damage > 0 ) then + util.BlastDamage( self, ply, self:GetPos(), self.Radius, self.Damage ) + end + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "Explosion", effectdata, true, true ) + + if ( self.Removeafter ) then + self:Remove() + return + end + + self.exploding = false + + self.reloading = true + self.ReloadTime = CurTime() + math.max(wire_explosive_delay:GetFloat(), self.Delayreloadtime) + // Force reset of counter + self.CountTime = 0 + self:ShowOutput() +end + +-- don't foreget to call this when changes happen +function ENT:ShowOutput( ) + local txt = "" + if (self.reloading && self.Delayreloadtime > 0) then + txt = "Rearming... "..self.count + if (self.ColorEffect && !self.InvisibleAtZero) then + local c = 255 * ((self.Delayreloadtime - self.count) / self.Delayreloadtime) + self:SetColor(Color(255, c, c, 255)) + end + if (self.InvisibleAtZero) then + self:SetNoDraw( false ) + self:SetColor(Color(255, 255, 255, 255 * ((self.Delayreloadtime - self.count) / self.Delayreloadtime))) + self:SetRenderMode(RENDERMODE_TRANSALPHA) + end + elseif (self.exploding) then + txt = "Triggered... "..self.count + else + txt = self.NormInfo.."\nHealth: "..self:Health().."/"..self:GetMaxHealth() + end + self:SetOverlayText(txt) +end + +duplicator.RegisterEntityClass( "gmod_wire_explosive", WireLib.MakeWireEnt, "Data", "key", "damage", "delaytime", "removeafter", "radius", "affectother", "notaffected", "delayreloadtime", "maxhealth", "bulletproof", "explosionproof", "fallproof", "explodeatzero", "resetatexplode", "fireeffect", "coloreffect", "invisibleatzero" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/ast.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/ast.lua new file mode 100644 index 0000000..3972747 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/ast.lua @@ -0,0 +1,104 @@ +-- The E2 source code is parsed into a tree structure, known as an 'abstract +-- syntax tree' or AST. This file provides utilities for operating on nodes of +-- this tree generically. + +AddCSLuaFile() + +E2Lib.AST = {} + +local function genericChildVisitor(node, action) + for i = 3, #node do + local child = node[i] + if istable(child) and child.__instruction then + node[i] = action(child) or child + end + end +end +local childVisitors = {} +function childVisitors.call(node, action) + local arguments = node[4] + for i, argument in ipairs(arguments) do + arguments[i] = action(argument) or argument + end +end +function childVisitors.methodcall(node, action) + local this, arguments = node[4], node[5] + node[4] = action(this) or this + for i, argument in ipairs(arguments) do + arguments[i] = action(argument) or argument + end +end +function childVisitors.stringcall(node, action) + local name, arguments = node[3], node[4] + node[3] = action(name) or name + for i, argument in ipairs(arguments) do + arguments[i] = action(argument) or argument + end +end +function childVisitors.kvtable(node, action) + local entries = node[3] + local additions = {} + for key, value in pairs(entries) do + local newKey, newValue = action(key) or key, action(value) or value + if key ~= newKey then + entries[key] = nil + additions[newKey] = newValue + else + entries[key] = newValue + end + end + for key, value in pairs(additions) do entries[key] = value end +end +childVisitors.kvarray = childVisitors.kvtable +function childVisitors.switch(node, action) + local expression = node[3] + node[3] = action(expression) or expression + for _, case in pairs(node[4]) do + local condition, result = case[1], case[2] + case[1] = condition and action(condition) or condition + case[2] = action(result) or result + end +end + +--- Call `action` on every child of `node`. +-- If it returns a value, then that value will replace the node. +-- For example: +-- E2Lib.AST.visitChildren(node, function(child) +-- print(string.format("Node has a child of type %s", child[1])) +-- -- replace (sub x x) → 0 +-- if child[1] == "sub" and child[3] == child[4] then +-- return { "literal", child[2], 0, "n" } +-- end +-- end) +function E2Lib.AST.visitChildren(node, action) + local visitor = childVisitors[node[1]] or genericChildVisitor + return visitor(node, action) +end + +--- Return a string representation of the tree. +function E2Lib.AST.dump(tree, indentation) + indentation = indentation or "" + local str = indentation .. tree[1] + + local summary = {} + for i = 3, #tree do + local v = tree[i] + if isstring(v) then + table.insert(summary, string.format("%q", v)) + elseif isnumber(v) then + table.insert(summary, tostring(v)) + end + end + if next(summary) then + str = str .. " [" .. table.concat(summary, ", ") .. "]" + end + + str = str .. " (" .. tree[2][1] .. ":" .. tree[2][2] .. ")" + local childIndentation = indentation .. "| " + + E2Lib.AST.visitChildren(tree, function(child) + str = str .. "\n" .. E2Lib.AST.dump(child, childIndentation) + end) + + return str +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/compiler.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/compiler.lua new file mode 100644 index 0000000..4eecdd7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/compiler.lua @@ -0,0 +1,956 @@ +--[[ + Expression 2 Compiler for Garry's Mod + Andreas "Syranide" Svensson, me@syranide.com +]] + +AddCSLuaFile() + +E2Lib.Compiler = {} +local Compiler = E2Lib.Compiler +Compiler.__index = Compiler + +function Compiler.Execute(...) + -- instantiate Compiler + local instance = setmetatable({}, Compiler) + + -- and pcall the new instance's Process method. + return xpcall(Compiler.Process, E2Lib.errorHandler, instance, ...) +end + +function Compiler:Error(message, instr) + error(message .. " at line " .. instr[2][1] .. ", char " .. instr[2][2], 0) +end + +function Compiler:Process(root, inputs, outputs, persist, delta, includes) -- Took params out becuase it isnt used. + self.context = {} + + self:InitScope() -- Creates global scope! + + self.inputs = inputs + self.outputs = outputs + self.persist = persist + self.includes = includes or {} + self.prfcounter = 0 + self.prfcounters = {} + self.tvars = {} + self.funcs = {} + self.dvars = {} + self.funcs_ret = {} + self.EnclosingFunctions = { --[[ { ReturnType: string } ]] } + + for name, v in pairs(inputs) do + self:SetGlobalVariableType(name, wire_expression_types[v][1], { nil, { 0, 0 } }) + end + + for name, v in pairs(outputs) do + self:SetGlobalVariableType(name, wire_expression_types[v][1], { nil, { 0, 0 } }) + end + + for name, v in pairs(persist) do + self:SetGlobalVariableType(name, wire_expression_types[v][1], { nil, { 0, 0 } }) + end + + for name, v in pairs(delta) do + self.dvars[name] = v + end + + self:PushScope() + + local script = Compiler["Instr" .. string.upper(root[1])](self, root) + + self:PopScope() + + return script, self +end + +function tps_pretty(tps) + if not tps or #tps == 0 then return "void" end + if type(tps) == "string" then tps = { tps } end + local ttt = {} + for i = 1, #tps do + local _, typenames = E2Lib.splitType(tps[i]) + for j = 1, #typenames do table.insert(ttt, typenames[j]) end + end + return table.concat(ttt, ", ") +end + +local function op_find(name) + return E2Lib.optable_inv[name] or "unknown?!" +end + +--[[ + Scopes: Rusketh +]] -- +function Compiler:InitScope() + self.Scopes = {} + self.ScopeID = 0 + self.Scopes[0] = self.GlobalScope or {} --for creating new enviroments + self.Scope = self.Scopes[0] + self.GlobalScope = self.Scope +end + +function Compiler:PushScope(Scope) + self.ScopeID = self.ScopeID + 1 + self.Scope = Scope or {} + self.Scopes[self.ScopeID] = self.Scope +end + +function Compiler:PopScope() + self.ScopeID = self.ScopeID - 1 + self.Scope = self.Scopes[self.ScopeID] + self.Scopes[self.ScopeID] = self.Scope + return table.remove(self.Scopes, self.ScopeID + 1) +end + +function Compiler:SaveScopes() + return { self.Scopes, self.ScopeID, self.Scope } +end + +function Compiler:LoadScopes(Scopes) + self.Scopes = Scopes[1] + self.ScopeID = Scopes[2] + self.Scope = Scopes[3] +end + +function Compiler:SetLocalVariableType(name, type, instance) + local typ = self.Scope[name] + if typ and typ ~= type then + self:Error("Variable (" .. E2Lib.limitString(name, 10) .. ") of type [" .. tps_pretty({ typ }) .. "] cannot be assigned value of type [" .. tps_pretty({ type }) .. "]", instance) + end + + self.Scope[name] = type + return self.ScopeID +end + +function Compiler:SetGlobalVariableType(name, type, instance) + for i = self.ScopeID, 0, -1 do + local typ = self.Scopes[i][name] + if typ and typ ~= type then + self:Error("Variable (" .. E2Lib.limitString(name, 10) .. ") of type [" .. tps_pretty({ typ }) .. "] cannot be assigned value of type [" .. tps_pretty({ type }) .. "]", instance) + elseif typ then + return i + end + end + + self.GlobalScope[name] = type + return 0 +end + +function Compiler:GetVariableType(instance, name) + for i = self.ScopeID, 0, -1 do + local type = self.Scopes[i][name] + if type then + return type, i + end + end + + self:Error("Variable (" .. E2Lib.limitString(name, 10) .. ") does not exist", instance) + return nil +end + +-- --------------------------------------------------------------------------- + +function Compiler:EvaluateStatement(args, index) + local name = string.upper(args[index + 2][1]) + local ex, tp = Compiler["Instr" .. name](self, args[index + 2]) + -- ex.TraceBack = args[index + 2] + ex.TraceName = name + return ex, tp +end + +function Compiler:Evaluate(args, index) + local ex, tp = self:EvaluateStatement(args, index) + + if tp == "" then + self:Error("Function has no return value (void), cannot be part of expression or assigned", args[index + 2]) + end + + return ex, tp +end + +function Compiler:HasOperator(instr, name, tps) + local pars = table.concat(tps) + local a = wire_expression2_funcs["op:" .. name .. "(" .. pars .. ")"] + return a and true or false +end + +function Compiler:GetOperator(instr, name, tps) + local pars = table.concat(tps) + local a = wire_expression2_funcs["op:" .. name .. "(" .. pars .. ")"] + if not a then + self:Error("No such operator: " .. op_find(name) .. "(" .. tps_pretty(tps) .. ")", instr) + return + end + + self.prfcounter = self.prfcounter + (a[4] or 3) + + return { a[3], a[2], a[1] } +end + + +function Compiler:UDFunction(Sig) + if self.funcs_ret and self.funcs_ret[Sig] then + return { + Sig, self.funcs_ret[Sig], + function(self, args) + if self.funcs and self.funcs[Sig] then + return self.funcs[Sig](self, args) + elseif self.funcs_ret and self.funcs_ret[Sig] then + -- This only occurs if a function's definition isn't executed before the function is called + -- Would probably only accidentally come about when pasting an E2 that has function definitions in + -- if(first()) instead of if(first() || duped()) + error("UDFunction: " .. Sig .. " undefined at runtime!", -1) + -- return wire_expression_types2[self.funcs_ret[Sig]][2] -- This would return the default value for the type, probably better to error though + end + end, + 20 + } + end +end + + +function Compiler:GetFunction(instr, Name, Args) + local Params = table.concat(Args) + local Func = wire_expression2_funcs[Name .. "(" .. Params .. ")"] + + if not Func then + Func = self:UDFunction(Name .. "(" .. Params .. ")") + end + + if not Func then + for I = #Params, 0, -1 do + Func = wire_expression2_funcs[Name .. "(" .. Params:sub(1, I) .. "...)"] + if Func then break end + end + end + + if not Func then + self:Error("No such function: " .. Name .. "(" .. tps_pretty(Args) .. ")", instr) + return + end + + self.prfcounter = self.prfcounter + (Func[4] or 20) + + return { Func[3], Func[2], Func[1] } +end + + +function Compiler:GetMethod(instr, Name, Meta, Args) + local Params = Meta .. ":" .. table.concat(Args) + local Func = wire_expression2_funcs[Name .. "(" .. Params .. ")"] + + if not Func then + Func = self:UDFunction(Name .. "(" .. Params .. ")") + end + + if not Func then + for I = #Params, #Meta + 1, -1 do + Func = wire_expression2_funcs[Name .. "(" .. Params:sub(1, I) .. "...)"] + if Func then break end + end + end + + if not Func then + self:Error("No such function: " .. tps_pretty({ Meta }) .. ":" .. Name .. "(" .. tps_pretty(Args) .. ")", instr) + return + end + + self.prfcounter = self.prfcounter + (Func[4] or 20) + + return { Func[3], Func[2], Func[1] } +end + +function Compiler:PushPrfCounter() + self.prfcounters[#self.prfcounters + 1] = self.prfcounter + self.prfcounter = 0 +end + +function Compiler:PopPrfCounter() + local prfcounter = self.prfcounter + self.prfcounter = self.prfcounters[#self.prfcounters] + self.prfcounters[#self.prfcounters] = nil + return prfcounter +end + +-- ------------------------------------------------------------------------ + +function Compiler:InstrSEQ(args) + -- args = { "seq", trace, subexpressions... } + self:PushPrfCounter() + + local stmts = { self:GetOperator(args, "seq", {})[1], 0 } + + for i = 1, #args - 2 do + stmts[#stmts + 1] = self:EvaluateStatement(args, i) + end + + stmts[2] = self:PopPrfCounter() + + return stmts +end + +function Compiler:InstrBRK(args) + -- args = { "brk", trace } + return { self:GetOperator(args, "brk", {})[1] } +end + +function Compiler:InstrCNT(args) + -- args = { "cnt", trace } + return { self:GetOperator(args, "cnt", {})[1] } +end + +function Compiler:InstrFOR(args) + -- args = { "for", trace, variable name, start expression, stop expression, step expression or nil, loop body } + local var = args[3] + + local estart, tp1 = self:Evaluate(args, 2) + local estop, tp2 = self:Evaluate(args, 3) + + local estep, tp3 + if args[6] then + estep, tp3 = self:Evaluate(args, 4) + if tp1 ~= "n" or tp2 ~= "n" or tp3 ~= "n" then self:Error("for(" .. tps_pretty({ tp1 }) .. ", " .. tps_pretty({ tp2 }) .. ", " .. tps_pretty({ tp3 }) .. ") is invalid, only supports indexing by number", args) end + else + if tp1 ~= "n" or tp2 ~= "n" then self:Error("for(" .. tps_pretty({ tp1 }) .. ", " .. tps_pretty({ tp2 }) .. ") is invalid, only supports indexing by number", args) end + end + + self:PushScope() + self:SetLocalVariableType(var, "n", args) + + local stmt = self:EvaluateStatement(args, 5) + self:PopScope() + + return { self:GetOperator(args, "for", {})[1], var, estart, estop, estep, stmt } +end + +function Compiler:InstrWHL(args) + -- args = { "whl", trace, condition expression, loop body } + self:PushScope() + + self:PushPrfCounter() + local cond = self:Evaluate(args, 1) + local prf_cond = self:PopPrfCounter() + + local stmt = self:EvaluateStatement(args, 2) + self:PopScope() + + return { self:GetOperator(args, "whl", {})[1], cond, stmt, prf_cond } +end + + +function Compiler:InstrIF(args) + -- args = { "if", trace, condition expression, true case body, false case body } + self:PushPrfCounter() + local ex1, tp1 = self:Evaluate(args, 1) + local prf_cond = self:PopPrfCounter() + + self:PushScope() + local st1 = self:EvaluateStatement(args, 2) + self:PopScope() + + self:PushScope() + local st2 = self:EvaluateStatement(args, 3) + self:PopScope() + + local rtis = self:GetOperator(args, "is", { tp1 }) + local rtif = self:GetOperator(args, "if", { rtis[2] }) + return { rtif[1], prf_cond, { rtis[1], ex1 }, st1, st2 } +end + +function Compiler:InstrDEF(args) + -- args = { "def", trace, primary expression, fallback expression } + local ex1, tp1 = self:Evaluate(args, 1) + + self:PushPrfCounter() + local ex2, tp2 = self:Evaluate(args, 2) + local prf_ex2 = self:PopPrfCounter() + + local rtis = self:GetOperator(args, "is", { tp1 }) + local rtif = self:GetOperator(args, "def", { rtis[2] }) + local rtdat = self:GetOperator(args, "dat", {}) + + if tp1 ~= tp2 then + self:Error("Different types (" .. tps_pretty({ tp1 }) .. ", " .. tps_pretty({ tp2 }) .. ") returned in default conditional", args) + end + + return { rtif[1], { rtis[1], { rtdat[1], nil } }, ex1, ex2, prf_ex2 }, tp1 +end + +function Compiler:InstrCND(args) + -- args = { "cnd", trace, conditional expression, true expression, false expression } + local ex1, tp1 = self:Evaluate(args, 1) + + self:PushPrfCounter() + local ex2, tp2 = self:Evaluate(args, 2) + local prf_ex2 = self:PopPrfCounter() + + self:PushPrfCounter() + local ex3, tp3 = self:Evaluate(args, 3) + local prf_ex3 = self:PopPrfCounter() + + local rtis = self:GetOperator(args, "is", { tp1 }) + local rtif = self:GetOperator(args, "cnd", { rtis[2] }) + + if tp2 ~= tp3 then + self:Error("Different types (" .. tps_pretty({ tp2 }) .. ", " .. tps_pretty({ tp3 }) .. ") returned in conditional", args) + end + + return { rtif[1], { rtis[1], ex1 }, ex2, ex3, prf_ex2, prf_ex3 }, tp2 +end + + +function Compiler:InstrCALL(args) + -- args = { "call", trace, function name, { argument expressions... } } + local exprs = { false } + + local tps = {} + for i = 1, #args[4] do + local ex, tp = self:Evaluate(args[4], i - 2) + tps[#tps + 1] = tp + exprs[#exprs + 1] = ex + end + + local rt = self:GetFunction(args, args[3], tps) + exprs[1] = rt[1] + exprs[#exprs + 1] = tps + + return exprs, rt[2] +end + +function Compiler:InstrSTRINGCALL(args) + -- args = { "stringcall", trace, function name expression, { argument expressions... }, return type } + local exprs = { false } + + local fexp, ftp = self:Evaluate(args, 1) + + if ftp ~= "s" then + self:Error("User function is not string-type", args) + end + + local tps = {} + for i = 1, #args[4] do + local ex, tp = self:Evaluate(args[4], i - 2) + tps[#tps + 1] = tp + exprs[#exprs + 1] = ex + end + + exprs[#exprs + 1] = tps + + local rtsfun = self:GetOperator(args, "stringcall", {})[1] + + local typeids_str = table.concat(tps, "") + + return { rtsfun, fexp, exprs, tps, typeids_str, args[5] }, args[5] +end + +function Compiler:InstrMETHODCALL(args) + -- args = { "methodcall", trace, method name, object expression, { argument expressions... } } + local exprs = { false } + + local tps = {} + + local ex, tp = self:Evaluate(args, 2) + exprs[#exprs + 1] = ex + + for i = 1, #args[5] do + local ex, tp = self:Evaluate(args[5], i - 2) + tps[#tps + 1] = tp + exprs[#exprs + 1] = ex + end + + local rt = self:GetMethod(args, args[3], tp, tps) + exprs[1] = rt[1] + exprs[#exprs + 1] = tps + + return exprs, rt[2] +end + +function Compiler:InstrASS(args) + -- args = { "ass", trace, variable name, assigned expression } + local op = args[3] + local ex, tp = self:Evaluate(args, 2) + local ScopeID = self:SetGlobalVariableType(op, tp, args) + local rt = self:GetOperator(args, "ass", { tp }) + + if ScopeID == 0 and self.dvars[op] then + local stmts = { self:GetOperator(args, "seq", {})[1], 0 } + stmts[3] = { self:GetOperator(args, "ass", { tp })[1], "$" .. op, { self:GetOperator(args, "var", {})[1], op, ScopeID }, ScopeID } + stmts[4] = { rt[1], op, ex, ScopeID } + return stmts, tp + else + return { rt[1], op, ex, ScopeID }, tp + end +end + +function Compiler:InstrASSL(args) + -- args = { "assl", trace, variable name, assigned expression } + local op = args[3] + local ex, tp = self:Evaluate(args, 2) + local ScopeID = self:SetLocalVariableType(op, tp, args) + local rt = self:GetOperator(args, "ass", { tp }) + + if ScopeID == 0 then + self:Error("Invalid use of 'local' inside the global scope.", args) + end -- Just to make code look neater. + + return { rt[1], op, ex, ScopeID }, tp +end + +function Compiler:InstrGET(args) + -- args = { "get", trace, object expression, field expression, return type or nil } + local ex, tp = self:Evaluate(args, 1) + local ex1, tp1 = self:Evaluate(args, 2) + local tp2 = args[5] + + if tp2 == nil then + if not self:HasOperator(args, "idx", { tp, tp1 }) then + self:Error("No such operator: get " .. tps_pretty({ tp }) .. "[" .. tps_pretty({ tp1 }) .. "]", args) + end + + local rt = self:GetOperator(args, "idx", { tp, tp1 }) + return { rt[1], ex, ex1 }, rt[2] + + + else + if not self:HasOperator(args, "idx", { tp2, "=", tp, tp1 }) then + self:Error("No such operator: get " .. tps_pretty({ tp }) .. "[" .. tps_pretty({ tp1, tp2 }) .. "]", args) + end + + local rt = self:GetOperator(args, "idx", { tp2, "=", tp, tp1 }) + return { rt[1], ex, ex1 }, tp2 + end +end + +function Compiler:InstrSET(args) + -- args = { "set", trace, object expression, field expression, value expression, value type or nil } + local ex, tp = self:Evaluate(args, 1) + local ex1, tp1 = self:Evaluate(args, 2) + local ex2, tp2 = self:Evaluate(args, 3) + + if args[6] == nil then + if not self:HasOperator(args, "idx", { tp, tp1, tp2 }) then + self:Error("No such operator: set " .. tps_pretty({ tp }) .. "[" .. tps_pretty({ tp1 }) .. "]=" .. tps_pretty({ tp2 }), args) + end + + local rt = self:GetOperator(args, "idx", { tp, tp1, tp2 }) + + return { rt[1], ex, ex1, ex2, nil }, rt[2] + else + if tp2 ~= args[6] then + self:Error("Indexing type mismatch, specified [" .. tps_pretty({ args[6] }) .. "] but value is [" .. tps_pretty({ tp2 }) .. "]", args) + end + + if not self:HasOperator(args, "idx", { tp2, "=", tp, tp1, tp2 }) then + self:Error("No such operator: set " .. tps_pretty({ tp }) .. "[" .. tps_pretty({ tp1, tp2 }) .. "]", args) + end + local rt = self:GetOperator(args, "idx", { tp2, "=", tp, tp1, tp2 }) + + return { rt[1], ex, ex1, ex2 }, tp2 + end +end + + +-- generic code for all binary non-boolean operators +for _, operator in ipairs({ "add", "sub", "mul", "div", "mod", "exp", "eq", "neq", "geq", "leq", "gth", "lth", "band", "band", "bor", "bxor", "bshl", "bshr" }) do + Compiler["Instr" .. operator:upper()] = function(self, args) + -- args = { operator, trace, left expression, right expression } + local ex1, tp1 = self:Evaluate(args, 1) + local ex2, tp2 = self:Evaluate(args, 2) + local rt = self:GetOperator(args, operator, { tp1, tp2 }) + return { rt[1], ex1, ex2 }, rt[2] + end +end + +function Compiler:InstrINC(args) + -- args = { "inc", trace, variable name } + local op = args[3] + local tp, ScopeID = self:GetVariableType(args, op) + local rt = self:GetOperator(args, "inc", { tp }) + + if ScopeID == 0 and self.dvars[op] then + local stmts = { self:GetOperator(args, "seq", {})[1], 0 } + stmts[3] = { self:GetOperator(args, "ass", { tp })[1], "$" .. op, { self:GetOperator(args, "var", {})[1], op, ScopeID }, ScopeID } + stmts[4] = { rt[1], op, ScopeID } + return stmts + else + return { rt[1], op, ScopeID } + end +end + +function Compiler:InstrDEC(args) + -- args = { "dec", trace, variable name } + local op = args[3] + local tp, ScopeID = self:GetVariableType(args, op) + local rt = self:GetOperator(args, "dec", { tp }) + + if ScopeID == 0 and self.dvars[op] then + local stmts = { self:GetOperator(args, "seq", {})[1], 0 } + stmts[3] = { self:GetOperator(args, "ass", { tp })[1], "$" .. op, { self:GetOperator(args, "var", {})[1], op, ScopeID }, ScopeID } + stmts[4] = { rt[1], op, ScopeID } + return stmts + else + return { rt[1], op, ScopeID } + end +end + +function Compiler:InstrNEG(args) + -- args = { "neg", trace, expression } + local ex1, tp1 = self:Evaluate(args, 1) + local rt = self:GetOperator(args, "neg", { tp1 }) + return { rt[1], ex1 }, rt[2] +end + + +function Compiler:InstrNOT(args) + -- args = { "not", trace, expression } + local ex1, tp1 = self:Evaluate(args, 1) + local rt1is = self:GetOperator(args, "is", { tp1 }) + local rt = self:GetOperator(args, "not", { rt1is[2] }) + return { rt[1], { rt1is[1], ex1 } }, rt[2] +end + +function Compiler:InstrAND(args) + -- args = { "and", trace, left expression, right expression } + local ex1, tp1 = self:Evaluate(args, 1) + local ex2, tp2 = self:Evaluate(args, 2) + local rt1is = self:GetOperator(args, "is", { tp1 }) + local rt2is = self:GetOperator(args, "is", { tp2 }) + local rt = self:GetOperator(args, "and", { rt1is[2], rt2is[2] }) + return { rt[1], { rt1is[1], ex1 }, { rt2is[1], ex2 } }, rt[2] +end + +function Compiler:InstrOR(args) + -- args = { "or", trace, left expression, right expression } + local ex1, tp1 = self:Evaluate(args, 1) + local ex2, tp2 = self:Evaluate(args, 2) + local rt1is = self:GetOperator(args, "is", { tp1 }) + local rt2is = self:GetOperator(args, "is", { tp2 }) + local rt = self:GetOperator(args, "or", { rt1is[2], rt2is[2] }) + return { rt[1], { rt1is[1], ex1 }, { rt2is[1], ex2 } }, rt[2] +end + + +function Compiler:InstrTRG(args) + -- args = { "trg", trace, variable name } + local op = args[3] + if not self.inputs[op] then + self:Error("Triggered operator (~" .. E2Lib.limitString(op, 10) .. ") can only be used on inputs", args) + end + local rt = self:GetOperator(args, "trg", {}) + return { rt[1], op }, rt[2] +end + +function Compiler:InstrDLT(args) + -- args = { "dlt", trace, variable name } + local op = args[3] + local tp, ScopeID = self:GetVariableType(args, op) + + if ScopeID ~= 0 or not self.dvars[op] then + self:Error("Delta operator ($" .. E2Lib.limitString(op, 10) .. ") cannot be used on temporary variables", args) + end + + self.dvars[op] = true + local rt = self:GetOperator(args, "sub", { tp, tp }) + local rtvar = self:GetOperator(args, "var", {}) + return { rt[1], { rtvar[1], op, ScopeID }, { rtvar[1], "$" .. op, ScopeID } }, rt[2] +end + +function Compiler:InstrIWC(args) + -- args = { "iwc", trace, variable name } + local op = args[3] + + if self.inputs[op] then + local rt = self:GetOperator(args, "iwc", {}) + return { rt[1], op }, rt[2] + elseif self.outputs[op] then + local rt = self:GetOperator(args, "owc", {}) + return { rt[1], op }, rt[2] + else + self:Error("Connected operator (->" .. E2Lib.limitString(op, 10) .. ") can only be used on inputs or outputs", args) + end +end +function Compiler:InstrLITERAL(args) + -- args = { "literal", trace, value, value type } + self.prfcounter = self.prfcounter + 0.5 + local value = args[3] + return { function() return value end }, args[4] +end + +function Compiler:InstrVAR(args) + -- args = { "var", trace, variable name } + self.prfcounter = self.prfcounter + 1.0 + local tp, ScopeID = self:GetVariableType(args, args[3]) + local name = args[3] + + return {function(self) + return self.Scopes[ScopeID][name] + end}, tp +end + +function Compiler:InstrFEA(args) + -- args = { "fea", trace, key variable name, key type, value variable name, value type, table expression, loop body } + local keyvar, keytype, valvar, valtype = args[3], args[4], args[5], args[6] + local tableexpr, tabletp = self:Evaluate(args, 5) + + local op + + if keytype then + op = self:GetOperator(args, "fea", {keytype, valtype, tabletp}) + else + -- If no key type is specified, fallback to old behavior + + -- The type of the keys iterated over depends on what's being iterated over (ie. tabletp). + -- The 'table' returned by tableexpr can be a table, an array, a gtable, or others in future. + -- If the type has an indexing operator that takes strings, then we iterate over strings, + -- otherwise we iterator over numbers. + + if self:HasOperator(args, "fea", {"s", valtype, tabletp}) then + op = self:GetOperator(args, "fea", {"s", valtype, tabletp}) + keytype = "s" + elseif self:HasOperator(args, "fea", {"n", valtype, tabletp}) then + op = self:GetOperator(args, "fea", {"n", valtype, tabletp}) + keytype = "n" + else + self:Error("Type '" .. tps_pretty(tabletp) .. "' has no valid default foreach operator", args) + end + end + + self:PushScope() + + self:SetLocalVariableType(keyvar, keytype, args) + self:SetLocalVariableType(valvar, valtype, args) + + local stmt = self:EvaluateStatement(args, 6) + + self:PopScope() + + return {op[1], keyvar, valvar, tableexpr, stmt} +end + + +function Compiler:InstrFUNCTION(args) + -- args = { "function", trace, signature, return type, object type, { { parameter name, parameter type }... }, function body } + local Sig, Return, methodType, Args = args[3], args[4], args[5], args[6] + Return = Return or "" + + local OldScopes = self:SaveScopes() + self:InitScope() -- Create a new Scope Enviroment + self:PushScope() + + for _, D in pairs(Args) do + local Name, Type = D[1], wire_expression_types[D[2]][1] + self:SetLocalVariableType(Name, Type, args) + end + + if self.funcs_ret[Sig] and self.funcs_ret[Sig] ~= Return then + local TP = tps_pretty(self.funcs_ret[Sig]) + self:Error("Function " .. Sig .. " must be given return type " .. TP, args) + end + + self.funcs_ret[Sig] = Return + + table.insert(self.EnclosingFunctions, { ReturnType = Return }) + + local Stmt = self:EvaluateStatement(args, 5) -- Offset of -2 + + table.remove(self.EnclosingFunctions) + + self:PopScope() + self:LoadScopes(OldScopes) -- Reload the old enviroment + + self.prfcounter = self.prfcounter + 40 + + -- This is the function that will be bound to to the function name, ie. the + -- one that's called at runtime when code calls the function + local function body(self, runtimeArgs) + -- runtimeArgs = { body, parameterExpression1, ..., parameterExpressionN, parameterTypes } + -- we need to evaluate the arguments before switching to the new scope + local parameterValues = {} + for parameterIndex = 2, #Args + 1 do + local parameterExpression = runtimeArgs[parameterIndex] + local parameterValue = parameterExpression[1](self, parameterExpression) + parameterValues[parameterIndex - 1] = parameterValue + end + + local OldScopes = self:SaveScopes() + self:InitScope() + self:PushScope() + + for parameterIndex = 1, #Args do + local parameterName = Args[parameterIndex][1] + local parameterValue = parameterValues[parameterIndex] + self.Scope[parameterName] = parameterValue + end + + self.func_rv = nil + local ok, msg = pcall(Stmt[1],self,Stmt) + + self:PopScope() + self:LoadScopes(OldScopes) + + -- a "C stack overflow" error will probably just confuse E2 users more than a "tick quota" error. + if not ok and msg:find( "C stack overflow" ) then error( "tick quota exceeded", -1 ) end + + if not ok and msg == "return" then return self.func_rv end + + if not ok then error(msg,0) end + + if Return ~= "" then + local argNames = {} + local offset = methodType == "" and 0 or 1 + + for k, v in ipairs(Args) do + argNames[k - offset] = v[1] + end + + error("Function " .. E2Lib.generate_signature(Sig, nil, argNames) .. + " executed and didn't return a value - expecting a value of type " .. + E2Lib.typeName(Return), 0) + end + end + + return { self:GetOperator(args, "function", {})[1], Sig, body } +end + +function Compiler:InstrRETURN(args) + -- args = { "return", trace, return expression or nil } + local enclosingFunction = self.EnclosingFunctions[#self.EnclosingFunctions] + if enclosingFunction == nil then + self:Error("Return may not exist outside of a function", args) + end + + local expectedType = assert(enclosingFunction.ReturnType) + local value, actualType + if args[3] then + value, actualType = self:Evaluate(args, 1) + else + actualType = "" + end + + if actualType ~= expectedType then + self:Error("Return type mismatch: " .. tps_pretty(expectedType) .. " expected, got " .. tps_pretty(actualType), args) + end + + return { self:GetOperator(args, "return", {})[1], value, actualType } +end + +function Compiler:InstrKVTABLE(args) + -- args = { "kvtable", trace, { key expression = value expression... } } + local s = {} + local stypes = {} + + local exprs = args[3] + for k, v in pairs(exprs) do + local key, type = self["Instr" .. string.upper(k[1])](self, k) + if type == "s" or type == "n" then + local value, type = self["Instr" .. string.upper(v[1])](self, v) + s[key] = value + stypes[key] = type + else + self:Error("String or number expected, got " .. tps_pretty(type), k) + end + end + + return { self:GetOperator(args, "kvtable", {})[1], s, stypes }, "t" +end + +function Compiler:InstrKVARRAY(args) + -- args = { "kvarray", trace, { key expression = value expression... } } + local values = {} + local types = {} + + local exprs = args[3] + for k, v in pairs(exprs) do + local key, type = self["Instr" .. string.upper(k[1])](self, k) + if type == "n" then + local value, type = self["Instr" .. string.upper(v[1])](self, v) + values[key] = value + types[key] = type + else + self:Error("Number expected, got " .. tps_pretty(type), k) + end + end + + return { self:GetOperator(args, "kvarray", {})[1], values, types }, "r" +end + +function Compiler:InstrSWITCH(args) + -- args = { "switch", trace, value expression, { { case expression or nil, body }... } } + -- up to one case can have a nil case expression, this is the default case + self:PushPrfCounter() + local value, type = Compiler["Instr" .. string.upper(args[3][1])](self, args[3]) -- This is the value we are passing though the switch statment + local prf_cond = self:PopPrfCounter() + + self:PushScope() + + local cases = {} + local Cases = args[4] + local default + for i = 1, #Cases do + local case, block, prf_eq, eq = Cases[i][1], Cases[i][2], 0, nil + if case then -- The default will not have one + self.ScopeID = self.ScopeID - 1 -- For the case statments we pop the scope back + self:PushPrfCounter() + local ex, tp = Compiler["Instr" .. string.upper(case[1])](self, case) --This is the value we are checking against + prf_eq = self:PopPrfCounter() -- We add some pref + self.ScopeID = self.ScopeID + 1 + if tp == "" then -- There is no value + self:Error("Function has no return value (void), cannot be part of expression or assigned", args) + elseif tp ~= type then -- Value types do not match. + self:Error("Case missmatch can not compare " .. tps_pretty(type) .. " with " .. tps_pretty(tp), args) + end + eq = { self:GetOperator(args, "eq", { type, tp })[1], value, ex } -- This is the equals operator to check if values match + else + default=i + end + local stmts = Compiler["Instr" .. string.upper(block[1])](self, block) -- This is statments that are run when Values match + cases[i] = { eq, stmts, prf_eq } + end + + self:PopScope() + + local rtswitch = self:GetOperator(args, "switch", {}) + return { rtswitch[1], prf_cond, cases, default } +end + +function Compiler:InstrINCLU(args) + -- args = { "inclu", trace, filename } + local file = args[3] + local include = self.includes[file] + + if not include or not include[1] then + self:Error("Problem including file '" .. file .. "'", args) + end + + if not include[2] then + + include[2] = true -- Tempory value to prvent E2 compiling itself when itself. (INFINATE LOOOP!) + + local OldScopes = self:SaveScopes() + self:InitScope() -- Create a new Scope Enviroment + self:PushScope() + + local root = include[1] + local status, script = pcall(Compiler["Instr" .. string.upper(root[1])], self, root) + + if not status then + if script:find("C stack overflow") then script = "Include depth to deep" end + + if not self.IncludeError then + -- Otherwise Errors messages will be wrapped inside other error messages! + self.IncludeError = true + self:Error("include '" .. file .. "' -> " .. script, args) + else + error(script, 0) + end + end + + include[2] = script + + self:PopScope() + self:LoadScopes(OldScopes) -- Reload the old enviroment + end + + + return { self:GetOperator(args, "include", {})[1], file } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/optimizer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/optimizer.lua new file mode 100644 index 0000000..116abb6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/optimizer.lua @@ -0,0 +1,179 @@ +--[[ +An optimizer for E2 abstract syntax trees, as produced by the parser and +consumed by the compiler. + +Currently it only performs some simple peephole optimizations and constant +propagation. Ideally, we'd do type inference as much as possible before +optimizing, which would give us more useful information throughout. +--]] + +E2Lib.Optimizer = {} +local Optimizer = E2Lib.Optimizer +Optimizer.__index = Optimizer + +local optimizerDebug = CreateConVar("wire_expression2_optimizer_debug", 0, + "Print an E2's abstract syntax tree after optimization" +) + +function Optimizer.Execute(root) + local ok, result = xpcall(Optimizer.Process, E2Lib.errorHandler, root) + if ok and optimizerDebug:GetBool() then + print(E2Lib.AST.dump(result)) + end + return ok, result +end + +Optimizer.Passes = {} + +function Optimizer.Process(tree) + E2Lib.AST.visitChildren(tree, Optimizer.Process) + + for _, pass in ipairs(Optimizer.Passes) do + local action = pass[tree[1]] + if action then + tree = assert(action(tree)) + end + end + tree.__instruction = true + return tree +end + +local constantPropagation = {} + +local function evaluateBinary(instruction) + -- this is a little sneaky: we use the operators previously registered with getOperator + -- to do compile-time evaluation, even though it really wasn't designed for it. + local op = wire_expression2_funcs["op:" .. instruction[1] .. "(" .. instruction[3][4] .. instruction[4][4] .. ")"] + local x, y = instruction[3][3], instruction[4][3] + + local value = op[3]({prf = 0}, {nil, {function() return x end}, {function() return y end}}) + local type = op[2] + return {"literal", instruction[2], value, type} +end + +local function evaluateUnary(instruction) + local op = wire_expression2_funcs["op:" .. instruction[1] .. "(" .. instruction[3][4] .. ")"] + local x = instruction[3][3] + + local value = op[3]({prf = 0}, {nil, {function() return x end}}) + local type = op[2] + return {"literal", instruction[2], value, type} +end + +for _, operator in pairs({ "add", "sub", "mul", "div", "mod", "exp", "eq", "neq", "geq", "leq", + "gth", "lth", "band", "band", "bor", "bxor", "bshl", "bshr" }) do + constantPropagation[operator] = function(instruction) + if instruction[3][1] ~= "literal" or instruction[4][1] ~= "literal" then return instruction end + return evaluateBinary(instruction) + end +end + +function constantPropagation.neg(instruction) + if instruction[3][1] ~= "literal" then return instruction end + return evaluateUnary(instruction) +end + +constantPropagation["not"] = function(instruction) + if instruction[3][1] ~= "literal" then return instruction end + instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]}) + return evaluateUnary(instruction) +end + +for _, operator in pairs({ "and", "or" }) do + constantPropagation[operator] = function(instruction) + if instruction[3][1] ~= "literal" or instruction[4][1] ~= "literal" then return instruction end + instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]}) + instruction[4] = evaluateUnary({"is", instruction[2], instruction[4]}) + return evaluateBinary(instruction) + end +end + +table.insert(Optimizer.Passes, constantPropagation) + + +local peephole = {} +function peephole.add(instruction) + -- (add 0 x) → x + if instruction[3][1] == "literal" and instruction[3][3] == 0 then return instruction[4] end + -- (add x 0) → x + if instruction[4][1] == "literal" and instruction[4][3] == 0 then return instruction[3] end + -- (add (neg x) (neg y)) → (neg (add x y)) + if instruction[3][1] == "neg" and instruction[4][1] == "neg" then + return {"neg", instruction[2], {"add", instruction[2], instruction[3][3], instruction[4][3], + __instruction = true}} + end + -- (add x (neg y)) → (sub x y) + if instruction[4][1] == "neg" then + return {"sub", instruction[2], instruction[3], instruction[4][3]} + end + -- (add (neg x) y) → (sub y x) + if instruction[3][1] == "neg" then + return {"sub", instruction[2], instruction[4], instruction[3][3]} + end + return instruction +end + +function peephole.sub(instruction) + -- (sub 0 x) → (neg x) + if instruction[3][1] == "literal" and instruction[3][3] == 0 then + return {"neg", instruction[2], instruction[4]} + end + -- (sub x 0) → x + if instruction[4][1] == "literal" and instruction[4][3] == 0 then return instruction[3] end + -- (sub (neg x) (neg y)) → (sub y x) + if instruction[3][1] == "neg" and instruction[4][1] == "neg" then + return {"sub", instruction[2], instruction[4][3], instruction[3][3]} + end + -- (sub x (neg y) → (add x y)) + if instruction[4][1] == "neg" then + return {"add", instruction[2], instruction[3], instruction[4][3]} + end + -- (sub (neg x) y) → (neg (add x y)) + if instruction[3][1] == "neg" then + return {"neg", instruction[2], {"add", instruction[2], instruction[3][3], instruction[4], + __instruction = true }} + end + return instruction +end + +function peephole.mul(instruction) + if instruction[4][1] == "literal" and instruction[3][1] ~= "literal" then + instruction[3], instruction[4] = instruction[4], instruction[3] + end + -- (mul 1 x) → x + if instruction[3][1] == "literal" and instruction[3][3] == 1 then return instruction[4] end + -- (mul -1 x) → (neg x) + if instruction[3][1] == "literal" and instruction[3][3] == -1 then + return {"neg", instruction[2], instruction[4]} + end + return instruction +end + +function peephole.neg(instruction) + -- (neg (neg x)) → x + if instruction[3][1] == "neg" then return instruction[3][3] end + return instruction +end + +peephole["if"] = function(instruction) + -- (if 1 x y) → x + -- (if 0 x y) → y + if instruction[3][1] == "literal" then + instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]}) + if instruction[3][3] == 1 then return instruction[4] end + if instruction[3][3] == 0 then return instruction[5] end + assert(false, "unreachable: `is` evaluation didn't return a boolean") + end + return instruction +end + +function peephole.whl(instruction) + -- (while 0 x) → (seq) + if instruction[3][1] == "literal" then + instruction[3] = evaluateUnary({"is", instruction[2], instruction[3]}) + if instruction[3][3] == 0 then return {"seq", instruction[2]} end + end + return instruction +end + +table.insert(Optimizer.Passes, peephole) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/parser.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/parser.lua new file mode 100644 index 0000000..ec8b772 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/parser.lua @@ -0,0 +1,1511 @@ +--[[ + Expression 2 Parser for Garry's Mod + Andreas "Syranide" Svensson, me@syranide.com +]] + +AddCSLuaFile() + +--[[ + +The following is a description of the E2 language as a parsing +expression grammar. Note that the parser does all its semantic analysis +while parsing, forbidding certain things which this grammar allows. + +* ε is the end-of-file +* E? matches zero or one occurrences of T (and will always match one if possible) +* E* matches zero or more occurrences of T (and will always match as many as possible) +* E F matches E (and then whitespace) and then F +* E / F tries matching E, if it fails it matches F (from the start location) +* &E matches E, but does not consume any input. +* !E matches everything except E, and does not consume any input. + +Root ← Stmts + +Stmts ← Stmt1 (("," / " ") Stmt1)* ε + +Stmt1 ← ("if" Cond Block IfElseIf)? Stmt2 +Stmt2 ← ("while" Cond Block)? Stmt3 +Stmt3 ← ("for" "(" Var "=" Expr1 "," Expr1 ("," Expr1)? ")" Block)? Stmt4 +Stmt4 ← ("foreach" "(" Var "," Var ":" Fun "=" Expr1 ")" Block)? Stmt5 +Stmt5 ← ("break" / "continue")? Stmt6 +Stmt6 ← (Var ("++" / "--"))? Stmt7 +Stmt7 ← (Var ("+=" / "-=" / "*=" / "/="))? Stmt8 +Stmt8 ← "local"? (Var (&"[" Index ("=" Stmt8)? / "=" Stmt8))? Stmt9 +Stmt9 ← ("switch" "(" Expr1 ")" "{" SwitchBlock)? Stmt10 +Stmt10 ← (FunctionStmt / ReturnStmt)? Stmt11 +Stmt11 ← ("#include" String)? Expr1 + +FunctionStmt ← "function" FunctionHead "(" FunctionArgs Block +FunctionHead ← (Type Type ":" Fun / Type ":" Fun / Type Fun / Fun) +FunctionArgs ← (FunctionArg ("," FunctionArg)*)? ")" +FunctionArg ← Var (":" Type)? + +ReturnStmt ← "return" ("void" / &"}" / Expr1) +IfElseIf ← "elseif" Cond Block IfElseIf / IfElse +IfElse ← "else" Block +Cond ← "(" Expr1 ")" +Block ← "{" (Stmt1 (("," / " ") Stmt1)*)? "}" +SwitchBlock ← (("case" Expr1 / "default") CaseBlock)* "}" +CaseBlock ← (Stmt1 (("," / " ") Stmt1)*)? &("case" / "default" / "}") + +Expr1 ← !(Var "=") !(Var "+=") !(Var "-=") !(Var "*=") !(Var "/=") Expr2 +Expr2 ← Expr3 (("?" Expr1 ":" Expr1) / ("?:" Expr1))? +Expr3 ← Expr4 ("|" Expr4)* +Expr4 ← Expr5 ("&" Expr5)* +Expr5 ← Expr6 ("||" Expr6)* +Expr6 ← Expr7 ("&&" Expr7)* +Expr7 ← Expr8 ("^^" Expr8)* +Expr8 ← Expr9 (("==" / "!=") Expr9)* +Expr9 ← Expr10 ((">" / "<" / ">=" / "<=") Expr10)* +Expr10 ← Expr11 (("<<" / ">>") Expr11)* +Expr11 ← Expr12 (("+" / "-") Expr12)* +Expr12 ← Expr13 (("*" / "/" / "%") Expr13)* +Expr13 ← Expr14 ("^" Expr14)* +Expr14 ← ("+" / "-" / "!") Expr15 +Expr15 ← Expr16 (MethodCallExpr / TableIndexExpr)? +Expr16 ← "(" Expr1 ")" / FunctionCallExpr / Expr17 +Expr17 ← Number / String / "~" Var / "$" Var / "->" Var / Expr18 +Expr18 ← !(Var "++") !(Var "--") Expr19 +Expr19 ← Var + +MethodCallExpr ← ":" Fun "(" (Expr1 ("," Expr1)*)? ")" +TableIndexExpr ← "[" Expr1 ("," Type)? "]" + +FunctionCallExpr ← Fun "(" KeyValueList? ")" +KeyValueList ← (KeyValue ("," KeyValue))* +KeyValue = Expr1 ("=" Expr1)? + +]] +-- ---------------------------------------------------------------------------------- + +E2Lib.Parser = {} +local Parser = E2Lib.Parser +Parser.__index = Parser + +local parserDebug = CreateConVar("wire_expression2_parser_debug", 0, + "Print an E2's abstract syntax tree after parsing" +) + +function Parser.Execute(...) + -- instantiate Parser + local instance = setmetatable({}, Parser) + + -- and pcall the new instance's Process method. + return xpcall(Parser.Process, E2Lib.errorHandler, instance, ...) +end + +function Parser:Error(message, token) + if token then + error(message .. " at line " .. token[4] .. ", char " .. token[5], 0) + else + error(message .. " at line " .. self.token[4] .. ", char " .. self.token[5], 0) + end +end + +function Parser:Process(tokens, params) + self.tokens = tokens + self.index = 0 + self.count = #tokens + self.delta = {} + self.includes = {} + + self:NextToken() + local tree = self:Root() + if parserDebug:GetBool() then + print(E2Lib.AST.dump(tree)) + end + return tree, self.delta, self.includes +end + +-- --------------------------------------------------------------------- + +function Parser:GetToken() + return self.token +end + +function Parser:GetTokenData() + return self.token[2] +end + +function Parser:GetTokenTrace() + return { self.token[4], self.token[5] } +end + + +function Parser:Instruction(trace, name, ...) + return { __instruction = true, name, trace, ... } +end + + +function Parser:HasTokens() + return self.readtoken ~= nil +end + +function Parser:NextToken() + if self.index <= self.count then + if self.index > 0 then + self.token = self.readtoken + else + self.token = { "", "", false, 1, 1 } + end + + self.index = self.index + 1 + self.readtoken = self.tokens[self.index] + else + self.readtoken = nil + end +end + +function Parser:TrackBack() + self.index = self.index - 2 + self:NextToken() +end + + +function Parser:AcceptRoamingToken(name) + local token = self.readtoken + if not token or token[1] ~= name then return false end + + self:NextToken() + return true +end + +function Parser:AcceptTailingToken(name) + local token = self.readtoken + if not token or token[3] then return false end + + return self:AcceptRoamingToken(name) +end + +function Parser:AcceptLeadingToken(name) + local token = self.tokens[self.index + 1] + if not token or token[3] then return false end + + return self:AcceptRoamingToken(name) +end + + +function Parser:RecurseLeft(func, tbl) + local expr = func(self) + local hit = true + + while hit do + hit = false + for i = 1, #tbl do + if self:AcceptRoamingToken(tbl[i]) then + local trace = self:GetTokenTrace() + + hit = true + expr = self:Instruction(trace, tbl[i], expr, func(self)) + break + end + end + end + + return expr +end + +-- -------------------------------------------------------------------------- + +local loopdepth + +function Parser:Root() + loopdepth = 0 + return self:Stmts() +end + + +function Parser:Stmts() + local trace = self:GetTokenTrace() + local stmts = self:Instruction(trace, "seq") + + if not self:HasTokens() then return stmts end + + while true do + if self:AcceptRoamingToken("com") then + self:Error("Statement separator (,) must not appear multiple times") + end + + stmts[#stmts + 1] = self:Stmt1() + + if not self:HasTokens() then break end + + if not self:AcceptRoamingToken("com") then + if self.readtoken[3] == false then + self:Error("Statements must be separated by comma (,) or whitespace") + end + end + end + + return stmts +end + + +function Parser:Stmt1() + if self:AcceptRoamingToken("if") then + local trace = self:GetTokenTrace() + return self:Instruction(trace, "if", self:Cond(), self:Block("if condition"), self:IfElseIf()) + end + + return self:Stmt2() +end + +function Parser:Stmt2() + if self:AcceptRoamingToken("whl") then + local trace = self:GetTokenTrace() + loopdepth = loopdepth + 1 + local whl = self:Instruction(trace, "whl", self:Cond(), self:Block("while condition")) + loopdepth = loopdepth - 1 + return whl + end + + return self:Stmt3() +end + +function Parser:Stmt3() + if self:AcceptRoamingToken("for") then + local trace = self:GetTokenTrace() + loopdepth = loopdepth + 1 + + if not self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis (() must appear before condition") + end + + if not self:AcceptRoamingToken("var") then + self:Error("Variable expected for the numeric index") + end + + local var = self:GetTokenData() + + if not self:AcceptRoamingToken("ass") then + self:Error("Assignment operator (=) expected to preceed variable") + end + + local estart = self:Expr1() + + if not self:AcceptRoamingToken("com") then + self:Error("Comma (,) expected after start value") + end + + local estop = self:Expr1() + + local estep + if self:AcceptRoamingToken("com") then + estep = self:Expr1() + end + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close condition") + end + + local sfor = self:Instruction(trace, "for", var, estart, estop, estep, self:Block("for statement")) + + loopdepth = loopdepth - 1 + return sfor + end + + return self:Stmt4() +end + +function Parser:Stmt4() + if self:AcceptRoamingToken("fea") then + local trace = self:GetTokenTrace() + loopdepth = loopdepth + 1 + + if not self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis missing (() after foreach statement") + end + + if not self:AcceptRoamingToken("var") then + self:Error("Variable expected to hold the key") + end + local keyvar = self:GetTokenData() + + local keytype + + if self:AcceptRoamingToken("col") then + if not self:AcceptRoamingToken("fun") then + self:Error("Type expected after colon") + end + + keytype = self:GetTokenData() + if keytype == "number" then keytype = "normal" end + + if wire_expression_types[string.upper(keytype)] == nil then + self:Error("Unknown type: " .. keytype) + end + + keytype = wire_expression_types[string.upper(keytype)][1] + end + + if not self:AcceptRoamingToken("com") then + self:Error("Comma (,) expected after key variable") + end + + if not self:AcceptRoamingToken("var") then + self:Error("Variable expected to hold the value") + end + local valvar = self:GetTokenData() + + if not self:AcceptRoamingToken("col") then + self:Error("Colon (:) expected to separate type from variable") + end + + if not self:AcceptRoamingToken("fun") and not self:AcceptRoamingToken("udf") then + self:Error("Type expected after colon") + end + local valtype = self:GetTokenData() + if valtype == "number" then valtype = "normal" end + if wire_expression_types[string.upper(valtype)] == nil then + self:Error("Unknown type: " .. valtype) + end + valtype = wire_expression_types[string.upper(valtype)][1] + + if not self:AcceptRoamingToken("ass") then + self:Error("Equals sign (=) expected after value type to specify table") + end + + local tableexpr = self:Expr1() + + if not self:AcceptRoamingToken("rpa") then + self:Error("Missing right parenthesis after foreach statement") + end + + local sfea = self:Instruction(trace, "fea", keyvar, keytype, valvar, valtype, tableexpr, self:Block("foreach statement")) + loopdepth = loopdepth - 1 + return sfea + end + + return self:Stmt5() +end + +function Parser:Stmt5() + if self:AcceptRoamingToken("brk") then + if loopdepth > 0 then + local trace = self:GetTokenTrace() + return self:Instruction(trace, "brk") + else + self:Error("Break may not exist outside of a loop") + end + elseif self:AcceptRoamingToken("cnt") then + if loopdepth > 0 then + local trace = self:GetTokenTrace() + return self:Instruction(trace, "cnt") + else + self:Error("Continue may not exist outside of a loop") + end + end + + return self:Stmt6() +end + +function Parser:Stmt6() + if self:AcceptRoamingToken("var") then + local trace = self:GetTokenTrace() + local var = self:GetTokenData() + + if self:AcceptTailingToken("inc") then + return self:Instruction(trace, "inc", var) + elseif self:AcceptRoamingToken("inc") then + self:Error("Increment operator (++) must not be preceded by whitespace") + end + + if self:AcceptTailingToken("dec") then + return self:Instruction(trace, "dec", var) + elseif self:AcceptRoamingToken("dec") then + self:Error("Decrement operator (--) must not be preceded by whitespace") + end + + self:TrackBack() + end + + return self:Stmt7() +end + +function Parser:Stmt7() + if self:AcceptRoamingToken("var") then + local trace = self:GetTokenTrace() + local var = self:GetTokenData() + + if self:AcceptRoamingToken("aadd") then + return self:Instruction(trace, "ass", var, self:Instruction(trace, "add", self:Instruction(trace, "var", var), self:Expr1())) + elseif self:AcceptRoamingToken("asub") then + return self:Instruction(trace, "ass", var, self:Instruction(trace, "sub", self:Instruction(trace, "var", var), self:Expr1())) + elseif self:AcceptRoamingToken("amul") then + return self:Instruction(trace, "ass", var, self:Instruction(trace, "mul", self:Instruction(trace, "var", var), self:Expr1())) + elseif self:AcceptRoamingToken("adiv") then + return self:Instruction(trace, "ass", var, self:Instruction(trace, "div", self:Instruction(trace, "var", var), self:Expr1())) + end + + self:TrackBack() + end + + return self:Stmt8() +end + +function Parser:Index() + if self:AcceptTailingToken("lsb") then + local trace = self:GetTokenTrace() + local exp = self:Expr1() + + if self:AcceptRoamingToken("com") then + if not self:AcceptRoamingToken("fun") then + self:Error("Indexing operator ([]) requires a lower case type [X,t]") + end + + local typename = self:GetTokenData() + if typename == "number" then typename = "normal" end + local type = wire_expression_types[string.upper(typename)] + + if not self:AcceptRoamingToken("rsb") then + self:Error("Right square bracket (]) missing, to close indexing operator [X,t]") + end + + if not type then + self:Error("Indexing operator ([]) does not support the type [" .. typename .. "]") + end + + return { exp, type[1], trace }, self:Index() + + elseif self:AcceptTailingToken("rsb") then + return { exp, nil, trace } + + else + self:Error("Indexing operator ([]) must not be preceded by whitespace") + end + end +end + + +function Parser:Stmt8(parentLocalized) + local localized + if self:AcceptRoamingToken("loc") then + if parentLocalized ~= nil then self:Error("Assignment can't contain roaming local operator") end + localized = true + end + + if self:AcceptRoamingToken("var") then + local tbpos = self.index + local trace = self:GetTokenTrace() + local var = self:GetTokenData() + + if self:AcceptTailingToken("lsb") then + self:TrackBack() + local indexs = { self:Index() } + + if self:AcceptRoamingToken("ass") then + if localized or parentLocalized then + self:Error("Invalid operator (local).") + end + + local total = #indexs + local inst = self:Instruction(trace, "var", var) + + for i = 1, total do -- Yep, All this took me 2 hours to figure out! + local key, type, trace = indexs[i][1], indexs[i][2], indexs[i][3] + if i == total then + inst = self:Instruction(trace, "set", inst, key, self:Stmt8(false), type) + else + inst = self:Instruction(trace, "get", inst, key, type) + end + end -- Example Result: set( get( get(Var,1,table) ,1,table) ,3,"hello",string) + return inst + end + + elseif self:AcceptRoamingToken("ass") then + if localized or parentLocalized then + return self:Instruction(trace, "assl", var, self:Stmt8(true)) + else + return self:Instruction(trace, "ass", var, self:Stmt8(false)) + end + elseif localized then + self:Error("Invalid operator (local) must be used for variable declaration.") + end + + self.index = tbpos - 2 + self:NextToken() + elseif localized then + self:Error("Invalid operator (local) must be used for variable declaration.") + end + + return self:Stmt9() +end + +function Parser:Stmt9() + if self:AcceptRoamingToken("swh") then + local trace = self:GetTokenTrace() + + if not self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis (() expected before switch condition") + end + + local expr = self:Expr1() + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) expected after switch condition") + end + + if not self:AcceptRoamingToken("lcb") then + self:Error("Left curly bracket ({) expected after switch condition") + end + + loopdepth = loopdepth + 1 + local cases, default = self:SwitchBlock() + loopdepth = loopdepth - 1 + + return self:Instruction(trace, "switch", expr, cases, default) + end + + return self:Stmt10() +end + +function Parser:Stmt10() + if self:AcceptRoamingToken("func") then + + local Trace = self:GetTokenTrace() + + + local Name, Return, Type + local NameToken, ReturnToken, TypeToken + local Args, Temp = {}, {} + + + -- Errors are handeled after line 49, both 'fun' and 'var' tokens are used for accurate error reports. + if self:AcceptRoamingToken("fun") or self:AcceptRoamingToken("var") or self:AcceptRoamingToken("void") then --get the name + Name = self:GetTokenData() + NameToken = self.token -- Copy the current token for error reporting + + -- We check if the previous token was actualy the return not the name + if self:AcceptRoamingToken("fun") or self:AcceptRoamingToken("var") or self:AcceptRoamingToken("void") then + Return = Name + ReturnToken = NameToken + + Name = self:GetTokenData() + NameToken = self.token + end + + -- We check if the name token is actualy the type + if self:AcceptRoamingToken("col") then + if self:AcceptRoamingToken("fun") or self:AcceptRoamingToken("var") then + Type = Name + TypeToken = NameToken + + Name = self:GetTokenData() + NameToken = self.token + else + self:Error("Function name must appear after colon (:)") + end + end + end + + + if Return and Return ~= "void" then -- Check the retun value + + if Return ~= Return:lower() then + self:Error("Function return type must be lowercased", ReturnToken) + end + + if Return == "number" then Return = "normal" end + + Return = Return:upper() + + if not wire_expression_types[Return] then + self:Error("Invalid return argument '" .. E2Lib.limitString(Return:lower(), 10) .. "'", ReturnToken) + end + + Return = wire_expression_types[Return][1] + + else + Return = "" + end + + if Type then -- check the Type + + if Type ~= Type:lower() then self:Error("Function object type must be full lowercase", TypeToken) end + + if Type == "number" then Type = "normal" end + + if Type == "void" then self:Error("Void can not be used as function object type", TypeToken) end + + Type = Type:upper() + + if not wire_expression_types[Type] then + self:Error("Invalid data type '" .. E2Lib.limitString(Type:lower(), 10) .. "'", TypeToken) + end + + Temp["This"] = true + + Args[1] = { "This", Type } + else + Type = "" + end + + if not Name then self:Error("Function name must follow function declaration") end + + if Name[1] ~= Name[1]:lower() then self:Error("Function name must start with a lower case letter", NameToken) end + + + if not self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis (() must appear after function name") + end + + self:FunctionArgs(Temp, Args) + + local Sig = Name .. "(" + for I = 1, #Args do + local Arg = Args[I] + Sig = Sig .. wire_expression_types[Arg[2]][1] + if I == 1 and Arg[1] == "This" and Type ~= '' then + Sig = Sig .. ":" + end + end + Sig = Sig .. ")" + + if wire_expression2_funcs[Sig] then self:Error("Function '" .. Sig .. "' already exists") end + + local Inst = self:Instruction(Trace, "function", Sig, Return, Type, Args, self:Block("function declaration")) + + return Inst + + -- Return Statment + elseif self:AcceptRoamingToken("ret") then + + local Trace = self:GetTokenTrace() + + if self:AcceptRoamingToken("void") or (self.readtoken[1] and self.readtoken[1] == "rcb") then + return self:Instruction(Trace, "return") + end + + return self:Instruction(Trace, "return", self:Expr1()) + + -- Void Missplacement + elseif self:AcceptRoamingToken("void") then + self:Error("Void may only exist after return") + end + + return self:Stmt11() +end + +function Parser:Stmt11() + if self:AcceptRoamingToken("inclu") then + + local Trace = self:GetTokenTrace() + + -- if not self:AcceptRoamingToken("lpa") then + -- self:Error("Left parenthesis (() must appear after include") + -- end + + if not self:AcceptRoamingToken("str") then + self:Error("include path (string) expected after include") + end + + local Path = self:GetTokenData() + + -- if not self:AcceptRoamingToken("rpa") then + -- self:Error("Right parenthesis ()) must appear after include path") + -- end + + self.includes[#self.includes + 1] = Path + + return self:Instruction(Trace, "inclu", Path) + end + + return self:Expr1() +end + +function Parser:FunctionArgs(Temp, Args) + if self:HasTokens() and not self:AcceptRoamingToken("rpa") then + while true do + + if self:AcceptRoamingToken("com") then self:Error("Argument separator (,) must not appear multiple times") end + + if self:AcceptRoamingToken("var") or self:AcceptRoamingToken("fun") then + self:FunctionArg(Temp, Args) + elseif self:AcceptRoamingToken("lsb") then + self:FunctionArgList(Temp, Args) + end + + if self:AcceptRoamingToken("rpa") then + break + + elseif not self:AcceptRoamingToken("com") then + self:NextToken() + self:Error("Right parenthesis ()) expected after function arguments") + end + end + end +end + +function Parser:FunctionArg(Temp, Args) + local Type = "normal" + + local Name = self:GetTokenData() + + if not Name then self:Error("Variable required") end + + if Name[1] ~= Name[1]:upper() then self:Error("Variable must start with uppercased letter") end + + if Temp[Name] then self:Error("Variable '" .. Name .. "' is already used as an argument,") end + + if self:AcceptRoamingToken("col") then + if self:AcceptRoamingToken("fun") or self:AcceptRoamingToken("var") then + Type = self:GetTokenData() + else + self:Error("Type expected after colon (:)") + end + end + + if Type ~= Type:lower() then self:Error("Type must be lowercased") end + + if Type == "number" then Type = "normal" end + + Type = Type:upper() + + if not wire_expression_types[Type] then + self:Error("Invalid type specified") + end + + + Temp[Name] = true + Args[#Args + 1] = { Name, Type } +end + +function Parser:FunctionArgList(Temp, Args) + + if self:HasTokens() then + + local Vars = {} + while true do + if self:AcceptRoamingToken("fun") or self:AcceptRoamingToken("var") then + local Name = self:GetTokenData() + + if not Name then self:Error("Variable required") end + + if Name[1] ~= Name[1]:upper() then self:Error("Variable must start with uppercased letter") end + + if Temp[Name] then self:Error("Variable '" .. Name .. "' is already used as an argument") end + + Temp[Name] = true + Vars[#Vars + 1] = Name + elseif self:AcceptRoamingToken("rsb") then + break + + else -- if !self:HasTokens() then + self:NextToken() + self:Error("Right square bracket (]) expected at end of argument list") + end + end + + if #Vars == 0 then + self:TrackBack() + self:TrackBack() + self:Error("Variables expected in variable list") + end + + local Type = "normal" + + if self:AcceptRoamingToken("col") then + if self:AcceptRoamingToken("fun") or self:AcceptRoamingToken("var") then + Type = self:GetTokenData() + else + self:Error("Type expected after colon (:)") + end + end + + if Type ~= Type:lower() then self:Error("Type must be lowercased") end + + if Type == "number" then Type = "normal" end + + Type = Type:upper() + + if not wire_expression_types[Type] then + self:Error("Invalid type specified") + end + + for I = 1, #Vars do + Args[#Args + 1] = { Vars[I], Type } + end + + else + self:Error("Variable expected after left square bracket ([) in argument list") + end +end + +function Parser:IfElseIf() + if self:AcceptRoamingToken("eif") then + local trace = self:GetTokenTrace() + return self:Instruction(trace, "if", self:Cond(), self:Block("elseif condition"), self:IfElseIf()) + end + + return self:IfElse() +end + +function Parser:IfElse() + if self:AcceptRoamingToken("els") then + return self:Block("else") + end + + local trace = self:GetTokenTrace() + return self:Instruction(trace, "seq") +end + +function Parser:Cond() + if not self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis (() expected before condition") + end + + local expr = self:Expr1() + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close condition") + end + + return expr +end + + +function Parser:Block(block_type) + local trace = self:GetTokenTrace() + local stmts = self:Instruction(trace, "seq") + + if not self:AcceptRoamingToken("lcb") then + self:Error("Left curly bracket ({) expected after " .. (block_type or "condition")) + end + + local token = self:GetToken() + + if self:AcceptRoamingToken("rcb") then + return stmts + end + + if self:HasTokens() then + while true do + if self:AcceptRoamingToken("com") then + self:Error("Statement separator (,) must not appear multiple times") + elseif self:AcceptRoamingToken("rcb") then + self:Error("Statement separator (,) must be suceeded by statement") + end + + stmts[#stmts + 1] = self:Stmt1() + + if self:AcceptRoamingToken("rcb") then + return stmts + end + + if not self:AcceptRoamingToken("com") then + if not self:HasTokens() then break end + + if self.readtoken[3] == false then + self:Error("Statements must be separated by comma (,) or whitespace") + end + end + end + end + + self:Error("Right curly bracket (}) missing, to close switch block", token) +end + +function Parser:SwitchBlock() -- Shhh this is a secret. Do not tell anybody about this, Rusketh! + local cases = {} + local default + + if self:HasTokens() and not self:AcceptRoamingToken("rpa") then + + if not self:AcceptRoamingToken("case") and not self:AcceptRoamingToken("default") then + self:Error("Case Operator (case) expected in case block.", self:GetToken()) + end + + self:TrackBack() + + while true do + + if self:AcceptRoamingToken("case") then + local expr = self:Expr1() + + if not self:AcceptRoamingToken("com") then + self:Error("Comma (,) expected after case condition") + end + + cases[#cases + 1] = { expr, self:CaseBlock() } + + elseif self:AcceptRoamingToken("default") then + + if default then + self:Error("Only one default case (default:) may exist.") + end + + if not self:AcceptRoamingToken("com") then + self:Error("Comma (,) expected after default case") + end + + default = true + cases[#cases + 1] = { nil, self:CaseBlock() } + + else + break + end + end + end + + if not self:AcceptRoamingToken("rcb") then + self:Error("Right curly bracket (}) missing, to close statement block", self:GetToken()) + end + + return cases +end + +function Parser:CaseBlock() -- Shhh this is a secret. Do not tell anybody about this, Rusketh! + if self:HasTokens() then + local stmts = self:Instruction(self:GetTokenTrace(), "seq") + + if self:HasTokens() then + while true do + + if self:AcceptRoamingToken("case") or self:AcceptRoamingToken("default") or self:AcceptRoamingToken("rcb") then + self:TrackBack() + return stmts + elseif self:AcceptRoamingToken("com") then + self:Error("Statement separator (,) must not appear multiple times") + elseif self:AcceptRoamingToken("rcb") then + self:Error("Statement separator (,) must be suceeded by statement") + end + + stmts[#stmts + 1] = self:Stmt1() + + if not self:AcceptRoamingToken("com") then + if not self:HasTokens() then break end + + if self.readtoken[3] == false then + self:Error("Statements must be separated by comma (,) or whitespace") + end + end + end + end + else + self:Error("Case block is missing after case declaration.") + end +end + +function Parser:Expr1() + self.exprtoken = self:GetToken() + + if self:AcceptRoamingToken("var") then + if self:AcceptRoamingToken("ass") then + self:Error("Assignment operator (=) must not be part of equation") + end + + if self:AcceptRoamingToken("aadd") then + self:Error("Additive assignment operator (+=) must not be part of equation") + elseif self:AcceptRoamingToken("asub") then + self:Error("Subtractive assignment operator (-=) must not be part of equation") + elseif self:AcceptRoamingToken("amul") then + self:Error("Multiplicative assignment operator (*=) must not be part of equation") + elseif self:AcceptRoamingToken("adiv") then + self:Error("Divisive assignment operator (/=) must not be part of equation") + end + + self:TrackBack() + end + + return self:Expr2() +end + +function Parser:Expr2() + local expr = self:Expr3() + + if self:AcceptRoamingToken("qsm") then + local trace = self:GetTokenTrace() + local exprtrue = self:Expr1() + + if not self:AcceptRoamingToken("col") then -- perhaps we want to make sure there is space around this (method bug) + self:Error("Conditional operator (:) must appear after expression to complete conditional", self:GetToken()) + end + + return self:Instruction(trace, "cnd", expr, exprtrue, self:Expr1()) + end + + if self:AcceptRoamingToken("def") then + local trace = self:GetTokenTrace() + + return self:Instruction(trace, "def", expr, self:Expr1()) + end + + return expr +end + +function Parser:Expr3() + return self:RecurseLeft(self.Expr4, { "or" }) +end + +function Parser:Expr4() + return self:RecurseLeft(self.Expr5, { "and" }) +end + +function Parser:Expr5() + return self:RecurseLeft(self.Expr6, { "bor" }) +end + +function Parser:Expr6() + return self:RecurseLeft(self.Expr7, { "band" }) +end + +function Parser:Expr7() + return self:RecurseLeft(self.Expr8, { "bxor" }) +end + +function Parser:Expr8() + return self:RecurseLeft(self.Expr9, { "eq", "neq" }) +end + +function Parser:Expr9() + return self:RecurseLeft(self.Expr10, { "gth", "lth", "geq", "leq" }) +end + +function Parser:Expr10() + return self:RecurseLeft(self.Expr11, { "bshr", "bshl" }) +end + +function Parser:Expr11() + return self:RecurseLeft(self.Expr12, { "add", "sub" }) +end + +function Parser:Expr12() + return self:RecurseLeft(self.Expr13, { "mul", "div", "mod" }) +end + +function Parser:Expr13() + return self:RecurseLeft(self.Expr14, { "exp" }) +end + +function Parser:Expr14() + if self:AcceptLeadingToken("add") then + return self:Expr15() + elseif self:AcceptRoamingToken("add") then + self:Error("Identity operator (+) must not be succeeded by whitespace") + end + + if self:AcceptLeadingToken("sub") then + local trace = self:GetTokenTrace() + return self:Instruction(trace, "neg", self:Expr15()) + elseif self:AcceptRoamingToken("sub") then + self:Error("Negation operator (-) must not be succeeded by whitespace") + end + + if self:AcceptLeadingToken("not") then + local trace = self:GetTokenTrace() + return self:Instruction(trace, "not", self:Expr14()) + elseif self:AcceptRoamingToken("not") then + self:Error("Logical not operator (!) must not be succeeded by whitespace") + end + + return self:Expr15() +end + +function Parser:Expr15() + local expr = self:Expr16() + + while true do + if self:AcceptTailingToken("col") then + if not self:AcceptTailingToken("fun") then + if self:AcceptRoamingToken("fun") then + self:Error("Method operator (:) must not be preceded by whitespace") + else + self:Error("Method operator (:) must be followed by method name") + end + end + + local trace = self:GetTokenTrace() + local fun = self:GetTokenData() + + if not self:AcceptTailingToken("lpa") then + if self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis (() must not be preceded by whitespace") + else + self:Error("Left parenthesis (() must appear after method name") + end + end + + local token = self:GetToken() + + if self:AcceptRoamingToken("rpa") then + expr = self:Instruction(trace, "methodcall", fun, expr, {}) + else + local exprs = { self:Expr1() } + + while self:AcceptRoamingToken("com") do + exprs[#exprs + 1] = self:Expr1() + end + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close method argument list", token) + end + + expr = self:Instruction(trace, "methodcall", fun, expr, exprs) + end + --elseif self:AcceptRoamingToken("col") then + -- self:Error("Method operator (:) must not be preceded by whitespace") + elseif self:AcceptTailingToken("lsb") then + local trace = self:GetTokenTrace() + + if self:AcceptRoamingToken("rsb") then + self:Error("Indexing operator ([]) requires an index [X]") + end + + local aexpr = self:Expr1() + if self:AcceptRoamingToken("com") then + if not self:AcceptRoamingToken("fun") then + self:Error("Indexing operator ([]) requires a lower case type [X,t]") + end + + local longtp = self:GetTokenData() + + if not self:AcceptRoamingToken("rsb") then + self:Error("Right square bracket (]) missing, to close indexing operator [X,t]") + end + + if longtp == "number" then longtp = "normal" end + if wire_expression_types[string.upper(longtp)] == nil then + self:Error("Indexing operator ([]) does not support the type [" .. longtp .. "]") + end + + local tp = wire_expression_types[string.upper(longtp)][1] + expr = self:Instruction(trace, "get", expr, aexpr, tp) + elseif self:AcceptRoamingToken("rsb") then + expr = self:Instruction(trace, "get", expr, aexpr) + else + self:Error("Indexing operator ([]) needs to be closed with comma (,) or right square bracket (])") + end + elseif self:AcceptRoamingToken("lsb") then + self:Error("Indexing operator ([]) must not be preceded by whitespace") + elseif self:AcceptTailingToken("lpa") then + local trace = self:GetTokenTrace() + + local token = self:GetToken() + local exprs + + if self:AcceptRoamingToken("rpa") then + exprs = {} + else + exprs = { self:Expr1() } + + while self:AcceptRoamingToken("com") do + exprs[#exprs + 1] = self:Expr1() + end + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close function argument list", token) + end + end + + if self:AcceptRoamingToken("lsb") then + if not self:AcceptRoamingToken("fun") then + self:Error("Return type operator ([]) requires a lower case type [type]") + end + + local longtp = self:GetTokenData() + + if not self:AcceptRoamingToken("rsb") then + self:Error("Right square bracket (]) missing, to close return type operator [type]") + end + + if longtp == "number" then longtp = "normal" end + if wire_expression_types[string.upper(longtp)] == nil then + self:Error("Return type operator ([]) does not support the type [" .. longtp .. "]") + end + + local stype = wire_expression_types[string.upper(longtp)][1] + + expr = self:Instruction(trace, "stringcall", expr, exprs, stype) + else + expr = self:Instruction(trace, "stringcall", expr, exprs, "") + end + else + break + end + end + + return expr +end + +function Parser:Expr16() + if self:AcceptRoamingToken("lpa") then + local token = self:GetToken() + + local expr = self:Expr1() + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close grouped equation", token) + end + + return expr + end + + if self:AcceptRoamingToken("fun") then + local trace = self:GetTokenTrace() + local fun = self:GetTokenData() + + if not self:AcceptTailingToken("lpa") then + if self:AcceptRoamingToken("lpa") then + self:Error("Left parenthesis (() must not be preceded by whitespace") + else + self:Error("Left parenthesis (() must appear after function name, variables must start with uppercase letter,") + end + end + + local token = self:GetToken() + + if self:AcceptRoamingToken("rpa") then + return self:Instruction(trace, "call", fun, {}) + else + + local exprs = {} + + -- Special case for "table( str=val, str=val, str=val, ... )" (or array) + if fun == "table" or fun == "array" then + local kvtable = false + + local key = self:Expr1() + + if self:AcceptRoamingToken("ass") then + if self:AcceptRoamingToken("rpa") then + self:Error("Expression expected, got right paranthesis ())", self:GetToken()) + end + + exprs[key] = self:Expr1() + + kvtable = true + else -- If it isn't a "table( str=val, ...)", then it's a "table( val,val,val,... )" + exprs = { key } + end + + if kvtable then + while self:AcceptRoamingToken("com") do + local key = self:Expr1() + local token = self:GetToken() + + if self:AcceptRoamingToken("ass") then + if self:AcceptRoamingToken("rpa") then + self:Error("Expression expected, got right paranthesis ())", self:GetToken()) + end + + exprs[key] = self:Expr1() + else + self:Error("Assignment operator (=) missing, to complete expression", token) + end + end + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close function argument list", self:GetToken()) + end + + return self:Instruction(trace, "kv" .. fun, exprs) + end + else + exprs = { self:Expr1() } + end + + while self:AcceptRoamingToken("com") do + exprs[#exprs + 1] = self:Expr1() + end + + if not self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) missing, to close function argument list", token) + end + + return self:Instruction(trace, "call", fun, exprs) + end + end + + return self:Expr17() +end + +function Parser:Expr17() + if self:AcceptRoamingToken("num") then + local trace = self:GetTokenTrace() + local tokendata = self:GetTokenData() + if isnumber(tokendata) then + return self:Instruction(trace, "literal", tokendata, "n") + end + local num, suffix = tokendata:match("^([-+e0-9.]*)(.*)$") + num = assert(tonumber(num), "unparseable numeric literal") + local value, type + if suffix == "" then + value, type = num, "n" + elseif suffix == "i" then + value, type = {0, num}, "c" + elseif suffix == "j" then + value, type = {0, 0, num, 0}, "q" + elseif suffix == "k" then + value, type = {0, 0, 0, num}, "q" + else + assert(false, "unrecognized numeric suffix " .. suffix) + end + return self:Instruction(trace, "literal", value, type) + end + + if self:AcceptRoamingToken("str") then + local trace = self:GetTokenTrace() + local str = self:GetTokenData() + return self:Instruction(trace, "literal", str, "s") + end + + if self:AcceptRoamingToken("trg") then + local trace = self:GetTokenTrace() + + if not self:AcceptTailingToken("var") then + if self:AcceptRoamingToken("var") then + self:Error("Triggered operator (~) must not be succeeded by whitespace") + else + self:Error("Triggered operator (~) must be preceded by variable") + end + end + + local var = self:GetTokenData() + return self:Instruction(trace, "trg", var) + end + + if self:AcceptRoamingToken("dlt") then + local trace = self:GetTokenTrace() + + if not self:AcceptTailingToken("var") then + if self:AcceptRoamingToken("var") then + self:Error("Delta operator ($) must not be succeeded by whitespace") + else + self:Error("Delta operator ($) must be preceded by variable") + end + end + + local var = self:GetTokenData() + self.delta[var] = true + + return self:Instruction(trace, "dlt", var) + end + + if self:AcceptRoamingToken("imp") then + local trace = self:GetTokenTrace() + + if not self:AcceptTailingToken("var") then + if self:AcceptRoamingToken("var") then + self:Error("Connected operator (->) must not be succeeded by whitespace") + else + self:Error("Connected operator (->) must be preceded by variable") + end + end + + local var = self:GetTokenData() + + return self:Instruction(trace, "iwc", var) + end + + return self:Expr18() +end + +function Parser:Expr18() + if self:AcceptRoamingToken("var") then + if self:AcceptTailingToken("inc") then + self:Error("Increment operator (++) must not be part of equation") + elseif self:AcceptRoamingToken("inc") then + self:Error("Increment operator (++) must not be preceded by whitespace") + end + + if self:AcceptTailingToken("dec") then + self:Error("Decrement operator (--) must not be part of equation") + elseif self:AcceptRoamingToken("dec") then + self:Error("Decrement operator (--) must not be preceded by whitespace") + end + + self:TrackBack() + end + + return self:Expr19() +end + +function Parser:Expr19() + if self:AcceptRoamingToken("var") then + local trace = self:GetTokenTrace() + local var = self:GetTokenData() + return self:Instruction(trace, "var", var) + end + + return self:ExprError() +end + +function Parser:ExprError() + if self:HasTokens() then + if self:AcceptRoamingToken("add") then + self:Error("Addition operator (+) must be preceded by equation or value") + elseif self:AcceptRoamingToken("sub") then -- can't occur (unary minus) + self:Error("Subtraction operator (-) must be preceded by equation or value") + elseif self:AcceptRoamingToken("mul") then + self:Error("Multiplication operator (*) must be preceded by equation or value") + elseif self:AcceptRoamingToken("div") then + self:Error("Division operator (/) must be preceded by equation or value") + elseif self:AcceptRoamingToken("mod") then + self:Error("Modulo operator (%) must be preceded by equation or value") + elseif self:AcceptRoamingToken("exp") then + self:Error("Exponentiation operator (^) must be preceded by equation or value") + + elseif self:AcceptRoamingToken("ass") then + self:Error("Assignment operator (=) must be preceded by variable") + elseif self:AcceptRoamingToken("aadd") then + self:Error("Additive assignment operator (+=) must be preceded by variable") + elseif self:AcceptRoamingToken("asub") then + self:Error("Subtractive assignment operator (-=) must be preceded by variable") + elseif self:AcceptRoamingToken("amul") then + self:Error("Multiplicative assignment operator (*=) must be preceded by variable") + elseif self:AcceptRoamingToken("adiv") then + self:Error("Divisive assignment operator (/=) must be preceded by variable") + + elseif self:AcceptRoamingToken("and") then + self:Error("Logical and operator (&) must be preceded by equation or value") + elseif self:AcceptRoamingToken("or") then + self:Error("Logical or operator (|) must be preceded by equation or value") + + elseif self:AcceptRoamingToken("eq") then + self:Error("Equality operator (==) must be preceded by equation or value") + elseif self:AcceptRoamingToken("neq") then + self:Error("Inequality operator (!=) must be preceded by equation or value") + elseif self:AcceptRoamingToken("gth") then + self:Error("Greater than or equal to operator (>=) must be preceded by equation or value") + elseif self:AcceptRoamingToken("lth") then + self:Error("Less than or equal to operator (<=) must be preceded by equation or value") + elseif self:AcceptRoamingToken("geq") then + self:Error("Greater than operator (>) must be preceded by equation or value") + elseif self:AcceptRoamingToken("leq") then + self:Error("Less than operator (<) must be preceded by equation or value") + + elseif self:AcceptRoamingToken("inc") then + self:Error("Increment operator (++) must be preceded by variable") + elseif self:AcceptRoamingToken("dec") then + self:Error("Decrement operator (--) must be preceded by variable") + + elseif self:AcceptRoamingToken("rpa") then + self:Error("Right parenthesis ()) without matching left parenthesis") + elseif self:AcceptRoamingToken("lcb") then + self:Error("Left curly bracket ({) must be part of an if/while/for-statement block") + elseif self:AcceptRoamingToken("rcb") then + self:Error("Right curly bracket (}) without matching left curly bracket") + elseif self:AcceptRoamingToken("lsb") then + self:Error("Left square bracket ([) must be preceded by variable") + elseif self:AcceptRoamingToken("rsb") then + self:Error("Right square bracket (]) without matching left square bracket") + + elseif self:AcceptRoamingToken("com") then + self:Error("Comma (,) not expected here, missing an argument?") + elseif self:AcceptRoamingToken("col") then + self:Error("Method operator (:) must not be preceded by whitespace") + + elseif self:AcceptRoamingToken("if") then + self:Error("If keyword (if) must not appear inside an equation") + elseif self:AcceptRoamingToken("eif") then + self:Error("Else-if keyword (elseif) must be part of an if-statement") + elseif self:AcceptRoamingToken("els") then + self:Error("Else keyword (else) must be part of an if-statement") + + else + self:Error("Unexpected token found (" .. self.readtoken[1] .. ")") + end + else + self:Error("Further input required at end of code, incomplete expression", self.exprtoken) + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/preprocessor.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/preprocessor.lua new file mode 100644 index 0000000..afb120a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/preprocessor.lua @@ -0,0 +1,424 @@ +--[[ + Expression 2 Pre-Processor for Garry's Mod + Andreas "Syranide" Svensson, me@syranide.com +]] + +AddCSLuaFile() + +E2Lib.PreProcessor = {} +local PreProcessor = E2Lib.PreProcessor +PreProcessor.__index = PreProcessor + +function PreProcessor.Execute(...) + -- instantiate PreProcessor + local instance = setmetatable({}, PreProcessor) + + -- and pcall the new instance's Process method. + return xpcall(instance.Process, E2Lib.errorHandler, instance, ...) +end + +function PreProcessor:Error(message, column) + error(message .. " at line " .. self.readline .. ", char " .. (column or 1), 0) +end + +local type_map = { + v4 = "xv4", + v2 = "xv2", + m4 = "xm4", + m2 = "xm2", + rd = "xrd", + wl = "xwl", + number = "n", +} +local function gettype(tp) + tp = tp:Trim():lower() + local up = tp:upper() + return type_map[tp] or (wire_expression_types[up] and wire_expression_types[up][1]) or tp +end + +function PreProcessor:HandlePPCommand(comment) + local command, args = comment:match("^([^ ]*) ?(.*)$") + local handler = self["PP_" .. command] + if handler then return handler(self, args) end +end + +function PreProcessor:FindComments(line) + local ret, count, pos, found = {}, 0, 1 + repeat + found = line:find('[#"\\]', pos) + if found then -- We found something + local char = line:sub(found, found) + if char == "#" then -- We found a comment + local before = line:sub(found - 1, found - 1) + if before == "]" then -- We found an ending + count = count + 1 + ret[count] = { type = "end", pos = found - 1 } + pos = found + 1 + else + local after = line:sub(found + 1, found + 1) + if after == "[" then -- We found a start + count = count + 1 + ret[count] = { type = "start", pos = found } + pos = found + 2 + else -- We found a normal comment + count = count + 1 + ret[count] = { type = "normal", pos = found } + pos = found + 1 + end + end + elseif char == '"' then -- We found a string + count = count + 1 + ret[count] = { type = "string", pos = found } + pos = found + 1 + elseif char == '\\' then -- We found an escape character + pos = found + 2 -- Skip the escape character and the character following it + end + end + until (not found) + return ret, count +end + +function PreProcessor:RemoveComments(line) + + local comments, num = self:FindComments(line) -- Find all comments and strings on this line + + if num == 0 and self.blockcomment then + return "" + end + + local prev_disabled, ret, lastpos = self:Disabled(), "", 1 + + for i = 1, num do + local type = comments[i].type + if type == "string" and not self.blockcomment then -- Is it a string? + self.multilinestring = not self.multilinestring + elseif not self.multilinestring then -- Else it's a comment if we're not inside a multiline string + if self.blockcomment then -- Time to look for a ]# + if type == "end" then -- We found one + local pos = comments[i].pos + ret = ret .. (" "):rep(pos - lastpos + 4) -- Replace the stuff in between with spaces + lastpos = pos + 2 + self.blockcomment = nil -- We're no longer in a block comment + end + else -- Time to look for a #[ + if type == "start" then -- We found one + local pos = comments[i].pos + ret = ret .. line:sub(lastpos, pos - 1) + lastpos = pos + 2 + self.blockcomment = true -- We're now inside a block comment + elseif type == "normal" then -- We found a # instead + local pos = comments[i].pos + if line:sub(pos + 1, pos + 7) == "include" then + ret = ret .. line:sub(lastpos) + else + ret = ret .. line:sub(lastpos, pos - 1) + self:HandlePPCommand(line:sub(pos + 1)) + end + + lastpos = -1 + break -- Don't care what comes after + end + end + end + end + + if prev_disabled then + return "" + elseif lastpos ~= -1 and not self.blockcomment then + return ret .. line:sub(lastpos, -1) + else + return ret + end +end + +function PreProcessor:ParseDirectives(line) + if self.multilinestring then return line end + + -- parse directive + local directive, value = line:match("^@([^ ]*) ?(.*)$") + + -- not a directive? + if not directive then + -- flag as "in code", if that is the case + if string.Trim(line) ~= "" then + self.incode = true + end + -- don't handle as a directive. + return line + end + + local col = directive:find("[A-Z]") + if col then self:Error("Directive (@" .. E2Lib.limitString(directive, 10) .. ") must be lowercase", col + 1) end + if self.incode then self:Error("Directive (@" .. E2Lib.limitString(directive, 10) .. ") must appear before code") end + + -- evaluate directive + if directive == "name" then + if not self.ignorestuff then + if self.directives.name == nil then + self.directives.name = value + else + self:Error("Directive (@name) must not be specified twice") + end + end + elseif directive == "model" then + if not self.ignorestuff then + if self.directives.model == nil then + self.directives.model = value + else + self:Error("Directive (@model) must not be specified twice") + end + end + elseif directive == "inputs" then + local retval, columns = self:ParsePorts(value, #directive + 2) + + for i, key in ipairs(retval[1]) do + if self.directives.inputs[3][key] then + self:Error("Directive (@input) contains multiple definitions of the same variable", columns[i]) + else + local index = #self.directives.inputs[1] + 1 + self.directives.inputs[1][index] = key + self.directives.inputs[2][index] = retval[2][i] + self.directives.inputs[3][key] = retval[2][i] + end + end + elseif directive == "outputs" then + local retval, columns = self:ParsePorts(value, #directive + 2) + + for i, key in ipairs(retval[1]) do + if self.directives.outputs[3][key] then + self:Error("Directive (@output) contains multiple definitions of the same variable", columns[i]) + else + local index = #self.directives.outputs[1] + 1 + self.directives.outputs[1][index] = key + self.directives.outputs[2][index] = retval[2][i] + self.directives.outputs[3][key] = retval[2][i] + end + end + elseif directive == "persist" then + local retval, columns = self:ParsePorts(value, #directive + 2) + + for i, key in ipairs(retval[1]) do + if self.directives.persist[3][key] then + self:Error("Directive (@persist) contains multiple definitions of the same variable", columns[i]) + else + local index = #self.directives.persist[1] + 1 + self.directives.persist[1][index] = key + self.directives.persist[2][index] = retval[2][i] + self.directives.persist[3][key] = retval[2][i] + end + end + elseif directive == "trigger" then + local trimmed = string.Trim(value) + if trimmed == "all" then + if self.directives.trigger[1] ~= nil then + self:Error("Directive (@trigger) conflicts with previous directives") + end + self.directives.trigger[1] = true + elseif trimmed == "none" then + if self.directives.trigger[1] ~= nil then + self:Error("Directive (@trigger) conflicts with previous directives") + end + self.directives.trigger[1] = false + elseif trimmed ~= "" then + if self.directives.trigger[1] ~= nil and #self.directives.trigger[2] == 0 then + self:Error("Directive (@trigger) conflicts with previous directives") + end + + self.directives.trigger[1] = false + local retval, columns = self:ParsePorts(value, #directive + 2) + + for i, key in ipairs(retval[1]) do + if self.directives.trigger[2][key] then + self:Error("Directive (@trigger) contains multiple definitions of the same variable", columns[i]) + else + self.directives.trigger[2][key] = true + end + end + end + elseif directive == "autoupdate" then + if CLIENT then return "" end + if not IsValid( self.ent ) or not self.ent.duped or not self.ent.filepath or self.ent.filepath == "" then return "" end + WireLib.Expression2Upload( self.ent:GetPlayer(), self.ent, self.ent.filepath ) + else + self:Error("Unknown directive found (@" .. E2Lib.limitString(directive, 10) .. ")", 2) + end + + -- remove line from output + return "" +end + +function PreProcessor:Process(buffer, directives, ent) + -- entity is needed for autoupdate + self.ent = ent + self.ifdefStack = {} + + local lines = string.Explode("\n", buffer) + + if not directives then + self.directives = { + name = nil, + model = nil, + inputs = { {}, {}, {} }, + outputs = { {}, {}, {} }, + persist = { {}, {}, {} }, + delta = { {}, {}, {} }, + trigger = { nil, {} }, + } + else + self.directives = directives + self.ignorestuff = true + end + + for i, line in ipairs(lines) do + self.readline = i + line = string.TrimRight(line) + + line = self:RemoveComments(line) + line = self:ParseDirectives(line) + + lines[i] = line + end + + if self.directives.trigger[1] == nil then self.directives.trigger[1] = true end + if not self.directives.name then self.directives.name = "" end + + return self.directives, string.Implode("\n", lines) +end + +function PreProcessor:ParsePorts(ports, startoffset) + local names = {} + local types = {} + local columns = {} + + -- Preprocess [Foo Bar]:entity into [Foo,Bar]:entity so we don't have to deal with split-up multi-variable definitions in the main loop + ports = ports:gsub("%[.-%]", function(s) + return s:gsub(" ", ",") + end) + + for column, key in ports:gmatch("()([^ ]+)") do + column = startoffset + column + key = key:Trim() + + -------------------------------- variable names -------------------------------- + + -- single-variable definition? + local _, i, namestring = key:find("^([A-Z][A-Za-z0-9_]*)") + if i then + -- yes -> add the variable + names[#names + 1] = namestring + else + -- no -> maybe a multi-variable definition? + _, i, namestring = key:find("^%[([^]]+)%]") + if not i then + -- no -> malformed variable name + self:Error("Variable name (" .. E2Lib.limitString(key, 10) .. ") must start with an uppercase letter", column) + end + -- yes -> add all variables. + for column2, var in namestring:gmatch("()([^,]+)") do + column2 = column + column2 + var = string.Trim(var) + -- skip empty entries + if var ~= "" then + -- error on malformed variable names + if not var:match("^[A-Z]") then self:Error("Variable name (" .. E2Lib.limitString(var, 10) .. ") must start with an uppercase letter", column2) end + local errcol = var:find("[^A-Za-z0-9_]") + if errcol then self:Error("Variable declaration (" .. E2Lib.limitString(var, 10) .. ") contains invalid characters", column2 + errcol - 1) end + -- and finally add the variable. + names[#names + 1] = var + end + end + end + + -------------------------------- variable types -------------------------------- + + local vtype + local character = key:sub(i + 1, i + 1) + if character == ":" then + -- type is specified -> check for validity + vtype = key:sub(i + 2) + + if vtype ~= vtype:lower() then + self:Error("Variable type [" .. E2Lib.limitString(vtype, 10) .. "] must be lowercase", column + i + 1) + end + + if vtype == "number" then vtype = "normal" end + + if not wire_expression_types[vtype:upper()] then + self:Error("Unknown variable type [" .. E2Lib.limitString(vtype, 10) .. "] specified for variable(s) (" .. E2Lib.limitString(namestring, 10) .. ")", column + i + 1) + end + elseif character == "" then + -- type is not specified -> default to NORMAL + vtype = "NORMAL" + else + -- invalid -> raise an error + self:Error("Variable declaration (" .. E2Lib.limitString(key, 10) .. ") contains invalid characters", column + i) + end + + -- fill in the missing types + for i = #types + 1, #names do + types[i] = vtype:upper() + columns[i] = column + end + end + + return { names, types }, columns +end + +function PreProcessor:Disabled() + return self.ifdefStack[#self.ifdefStack] == false +end + +function PreProcessor:GetFunction(args, type) + local thistype, colon, name, argtypes = args:match("([^:]-)(:?)([^:(]+)%(([^)]*)%)") + if not thistype or (thistype ~= "") ~= (colon ~= "") then self:Error("Malformed " .. type .. " argument " .. args) end + + thistype = gettype(thistype) + + local tps = {thistype .. colon} + for _, argtype in ipairs(string.Explode(",", argtypes)) do + argtype = gettype(argtype) + table.insert(tps, argtype) + end + local pars = table.concat(tps) + return wire_expression2_funcs[name .. "(" .. pars .. ")"] +end + +function PreProcessor:PP_ifdef(args) + local func = self:GetFunction(args, "#ifdef") + + if self:Disabled() then + table.insert(self.ifdefStack, false) + else + table.insert(self.ifdefStack, func ~= nil) + end +end + +function PreProcessor:PP_ifndef(args) + local func = self:GetFunction(args, "#ifndef") + + if self:Disabled() then + table.insert(self.ifdefStack, false) + else + table.insert(self.ifdefStack, func == nil) + end +end + +function PreProcessor:PP_else(args) + local state = table.remove(self.ifdefStack) + if state == nil then self:Error("Found #else outside #ifdef/#ifndef block") end + + if args:Trim() ~= "" then self:Error("Must not pass an argument to #else") end + + if self:Disabled() then + table.insert(self.ifdefStack, false) + else + table.insert(self.ifdefStack, not state) + end +end + +function PreProcessor:PP_endif(args) + local state = table.remove(self.ifdefStack) + if state == nil then self:Error("Found #endif outside #ifdef/#ifndef block") end + + if args:Trim() ~= "" then self:Error("Must not pass an argument to #endif") end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/tokenizer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/tokenizer.lua new file mode 100644 index 0000000..e025988 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/base/tokenizer.lua @@ -0,0 +1,283 @@ +--[[ + Expression 2 Tokenizer for Garry's Mod + Andreas "Syranide" Svensson, me@syranide.com +]] + +AddCSLuaFile() + +E2Lib.Tokenizer = {} +local Tokenizer = E2Lib.Tokenizer +Tokenizer.__index = Tokenizer + +function Tokenizer.Execute(...) + -- instantiate Tokenizer + local instance = setmetatable({}, Tokenizer) + + -- and pcall the new instance's Process method. + return xpcall(Tokenizer.Process, E2Lib.errorHandler, instance, ...) +end + +function Tokenizer:Error(message, offset) + error(message .. " at line " .. self.tokenline .. ", char " .. (self.tokenchar + (offset or 0)), 0) +end + +function Tokenizer:Process(buffer, params) + self.buffer = buffer + self.length = buffer:len() + self.position = 0 + + self:SkipCharacter() + + local tokens = {} + local tokenname, tokendata, tokenspace + self.tokendata = "" + + while self.character do + tokenspace = self:NextPattern("%s+") and true or false + + if not self.character then break end + + self.tokenline = self.readline + self.tokenchar = self.readchar + self.tokendata = "" + + tokenname, tokendata = self:NextSymbol() + + if tokenname == nil then + tokenname, tokendata = self:NextOperator() + + if tokenname == nil then + self:Error("Unknown character found (" .. self.character .. ")") + end + end + + tokens[#tokens + 1] = { tokenname, tokendata, tokenspace, self.tokenline, self.tokenchar } + end + + return tokens +end + +-- --------------------------------------------------------------------------------------- + +function Tokenizer:SkipCharacter() + if self.position < self.length then + if self.position > 0 then + if self.character == "\n" then + self.readline = self.readline + 1 + self.readchar = 1 + else + self.readchar = self.readchar + 1 + end + else + self.readline = 1 + self.readchar = 1 + end + + self.position = self.position + 1 + self.character = self.buffer:sub(self.position, self.position) + else + self.character = nil + end +end + +function Tokenizer:NextCharacter() + self.tokendata = self.tokendata .. self.character + self:SkipCharacter() +end + +-- Returns true on success, nothing if it fails. +function Tokenizer:NextPattern(pattern) + if not self.character then return false end + local startpos, endpos, text = self.buffer:find(pattern, self.position) + + if startpos ~= self.position then return false end + local buf = self.buffer:sub(startpos, endpos) + if not text then text = buf end + + self.tokendata = self.tokendata .. text + + + self.position = endpos + 1 + if self.position <= self.length then + self.character = self.buffer:sub(self.position, self.position) + else + self.character = nil + end + + buf = string.Explode("\n", buf) + if #buf > 1 then + self.readline = self.readline + #buf - 1 + self.readchar = #buf[#buf] + 1 + else + self.readchar = self.readchar + #buf[#buf] + end + return true +end + +function Tokenizer:NextSymbol() + local tokenname + + if self:NextPattern("^0x[0-9A-F]+") then + -- Hexadecimal number literal + tokenname = "num" + self.tokendata = tonumber(self.tokendata) or self:Error("Invalid number format (" .. E2Lib.limitString(self.tokendata, 10) .. ")") + elseif self:NextPattern("^0b[0-1]+") then + -- Binary number literal + tokenname = "num" + self.tokendata = tonumber(self.tokendata:sub(3), 2) or self:Error("Invalid number format (" .. E2Lib.limitString(self.tokendata, 10) .. ")") + elseif self:NextPattern("^[0-9]+%.?[0-9]*") then + -- real/imaginary/quaternion number literals + local errorpos = self.tokendata:match("^0()[0-9]") or self.tokendata:find("%.$") + if self:NextPattern("^[eE][+-]?[0-9][0-9]*") then + errorpos = errorpos or self.tokendata:match("[eE][+-]?()0[0-9]") + end + + self:NextPattern("^[ijk]") + if self:NextPattern("^[a-zA-Z_]") then + errorpos = errorpos or self.tokendata:len() + end + + if errorpos then + self:Error("Invalid number format (" .. E2Lib.limitString(self.tokendata, 10) .. ")", errorpos - 1) + end + + tokenname = "num" + + elseif self:NextPattern("^[a-z#][a-zA-Z0-9_]*") then + -- keywords/functions + if self.tokendata == "if" then + tokenname = "if" + elseif self.tokendata == "elseif" then + tokenname = "eif" + elseif self.tokendata == "else" then + tokenname = "els" + elseif self.tokendata == "local" then + tokenname = "loc" + elseif self.tokendata == "while" then + tokenname = "whl" + elseif self.tokendata == "for" then + tokenname = "for" + elseif self.tokendata == "break" then + tokenname = "brk" + elseif self.tokendata == "continue" then + tokenname = "cnt" + elseif self.tokendata == "switch" then + tokenname = "swh" + elseif self.tokendata == "case" then + tokenname = "case" + elseif self.tokendata == "default" then + tokenname = "default" + elseif self.tokendata == "foreach" then + tokenname = "fea" + elseif self.tokendata == "function" then + tokenname = "func" + elseif self.tokendata == "return" then + tokenname = "ret" + elseif self.tokendata == "void" then + tokenname = "void" + elseif self.tokendata == "#include" then + tokenname = "inclu" + elseif self.tokendata:match("^[ijk]$") and self.character ~= "(" then + tokenname, self.tokendata = "num", "1" .. self.tokendata + else + tokenname = "fun" + end + + elseif self:NextPattern("^[A-Z][a-zA-Z0-9_]*") then + -- variables + tokenname = "var" + + elseif self.character == "_" then + -- constants + self:NextCharacter() + self:NextPattern("^[A-Z0-9_]*") + + local value = wire_expression2_constants[self.tokendata] + + if isnumber(value) then + tokenname = "num" + self.tokendata = value + elseif isstring(value) then + tokenname = "str" + self.tokendata = value + else + self:Error("Constant (" .. self.tokendata .. ") has invalid data type (" .. type(value) .. ")") + end + + elseif self.character == "\"" then + -- strings + + -- skip opening quotation mark + self:SkipCharacter() + + -- loop until the closing quotation mark + while self.character ~= "\"" do + -- check for line/file endings + if not self.character then + self:Error("Unterminated string (\"" .. E2Lib.limitString(self.tokendata, 10):gsub("\n", "") .. ")") + end + + if self.character == "\\" then + self:SkipCharacter() + if self.character == "a" then + self.tokendata = self.tokendata .. "\a" + self:SkipCharacter() + elseif self.character == "b" then + self.tokendata = self.tokendata .. "\b" + self:SkipCharacter() + elseif self.character == "f" then + self.tokendata = self.tokendata .. "\f" + self:SkipCharacter() + elseif self.character == "n" then + self.tokendata = self.tokendata .. "\n" + self:SkipCharacter() + elseif self.character == "r" then + self.tokendata = self.tokendata .. "\r" + self:SkipCharacter() + elseif self.character == "t" then + self.tokendata = self.tokendata .. "\t" + self:SkipCharacter() + elseif self.character == "v" then + self.tokendata = self.tokendata .. "\v" + self:SkipCharacter() + else + self:NextCharacter() + end + else + self:NextCharacter() + end + end + -- skip closing quotation mark + self:SkipCharacter() + + tokenname = "str" + + else + -- nothing + return + end + + return tokenname, self.tokendata +end + +function Tokenizer:NextOperator() + local op = E2Lib.optable[self.character] + + if not op then return end + + while true do + self:NextCharacter() + + -- Check for the end of the string. + if not self.character then return op[1] end + + -- Check whether we are at a leaf and can't descend any further. + if not op[2] then return op[1] end + + -- Check whether we are at a node with no matching branches. + if not op[2][self.character] then return op[1] end + + -- branch + op = op[2][self.character] + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/cl_init.lua new file mode 100644 index 0000000..192a737 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/cl_init.lua @@ -0,0 +1,206 @@ +include('shared.lua') + +local function Include(e2, directives, includes, scripts) + if scripts[e2] then + return + end + + local code = file.Read("expression2/" .. e2 .. ".txt") + -- removed CLIENT as this is client file + -- local code + -- if CLIENT the code = file.Read("expression2/" .. e2 .. ".txt") + + if not code then + return false, "Could not find include '" .. e2 .. ".txt'" + end + + local status, err, buffer = E2Lib.PreProcessor.Execute(code, directives) + if not status then + return "include '" .. e2 .. "' -> " .. err + end + + local status, tokens = E2Lib.Tokenizer.Execute(buffer) + if not status then + return "include '" .. e2 .. "' -> " .. tokens + end + + local status, tree, dvars, files = E2Lib.Parser.Execute(tokens) + if not status then + return "include '" .. e2 .. "' -> " .. tree + end + + includes[e2] = code + + scripts[e2] = { tree } + + for i = 1, #files do + local error = Include(files[i], directives, includes, scripts) + if error then return error end + end +end + +function wire_expression2_validate(buffer) + -- removed CLIENT as this is client file + -- if CLIENT and + if not e2_function_data_received then return "Loading extensions. Please try again in a few seconds..." end + + -- invoke preprocessor + local status, directives, buffer = E2Lib.PreProcessor.Execute(buffer) + if not status then return directives end + + -- decompose directives + local inports, outports, persists = directives.inputs, directives.outputs, directives.persist + -- removed CLIENT as this is client file + -- if CLIENT then + RunConsoleCommand("wire_expression2_scriptmodel", directives.model or "") + -- end + + -- invoke tokenizer (=lexer) + local status, tokens = E2Lib.Tokenizer.Execute(buffer) + if not status then return tokens end + + -- invoke parser + local status, tree, dvars, files = E2Lib.Parser.Execute(tokens) + if not status then return tree end + + -- prepare includes + local includes, scripts = {}, {} + for i = 1, #files do + local error = Include(files[i], directives, includes, scripts) + if error then return error end + end + + -- invoke compiler + local status, script, instance = E2Lib.Compiler.Execute(tree, inports[3], outports[3], persists[3], dvars, scripts) + if not status then return script end + + return nil, includes +end + +-- string.GetTextSize shits itself if the string is both wide and tall, +-- so we have to explode it around \n and add the sizes together +-- since it works fine for strings that are wide but not tall +local function wtfgarry( str ) + local w, h = 0, 0 + local expl = string.Explode( "\n", str ) + for i=1,#expl do + local _w, _h = surface.GetTextSize( expl[i] ) + w = math.max(w,_w) + h = h + _h + end + return w, h +end + +local h_of_lower = 100 -- height of the lower section (the prfbench/percent bar section) +function ENT:GetWorldTipBodySize() + local data = self:GetOverlayData() + if not data then return 100, 20 end + + local w_total,h_total = wtfgarry( data.txt ) + h_total = h_total + 18 + + local prfbench = data.prfbench + local prfcount = data.prfcount + local timebench = data.timebench + + local e2_hardquota = GetConVar("wire_expression2_quotahard"):GetInt() + local e2_softquota = GetConVar("wire_expression2_quotasoft"):GetInt() + + -- ops text + local hardtext = (prfcount / e2_hardquota > 0.33) and "(+" .. tostring(math.Round(prfcount / e2_hardquota * 100)) .. "%)" or "" + local str = string.format("%i ops, %i%% %s", prfbench, prfbench / e2_softquota * 100, hardtext) + + h_of_lower = 0 + local w,h = surface.GetTextSize( str ) + w_total = math.max(w_total,w) + h_total = h_total + h + 18 + h_of_lower = h_of_lower + h + 18 + + -- cpu time text + local str = string.format("cpu time: %ius", timebench*1000000) + + local w,h = surface.GetTextSize( str ) + w_total = math.max(w_total,w) + h_total = h_total + h + 20 + h_of_lower = h_of_lower + h + 20 + 18 + + return w_total, math.min(h_total,ScrH() - (h_of_lower + 32*2)) +end + +function ENT:DrawWorldTipBody( pos ) + local data = self:GetOverlayData() + if not data then return end + + local txt = data.txt + local err = data.error -- this isn't used (yet), might do something with it later + + local white = Color(255,255,255,255) + local black = Color(0,0,0,255) + + local w_total, yoffset = 0, pos.min.y + + ------------------- + -- Name + ------------------- + local w,h = wtfgarry( txt ) + h = h + pos.edgesize + h = math.min(h,pos.size.h - (h_of_lower+pos.footersize.h)) + + render.SetScissorRect( pos.min.x + 16, pos.min.y, pos.max.x - 16, pos.min.y + h, true ) + draw.DrawText( txt, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset + 9, white, TEXT_ALIGN_CENTER ) + render.SetScissorRect( 0, 0, ScrW(), ScrH(), false ) + + w_total = math.max( w_total, w ) + yoffset = yoffset + h + + surface.SetDrawColor( black ) + surface.DrawLine( pos.min.x, yoffset, pos.max.x, yoffset ) + + ------------------- + -- prfcount/benchmarking/etc + ------------------- + local prfbench = data.prfbench + local prfcount = data.prfcount + local timebench = data.timebench + + local e2_hardquota = GetConVar("wire_expression2_quotahard"):GetInt() + local e2_softquota = GetConVar("wire_expression2_quotasoft"):GetInt() + + -- fancy percent bar + local w = pos.size.w - pos.edgesize * 2 + + -- ops text + local hardtext = (prfcount / e2_hardquota > 0.33) and "(+" .. tostring(math.Round(prfcount / e2_hardquota * 100)) .. "%)" or "" + local str = string.format("%i ops, %i%% %s", prfbench, prfbench / e2_softquota * 100, hardtext) + draw.DrawText( str, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset + 9, white, TEXT_ALIGN_CENTER ) + + local _,h = surface.GetTextSize( str ) + yoffset = yoffset + h + pos.edgesize + + -- fancy percent bar + + local softquota_width = w * 0.7 + local quota_width = softquota_width * math.min(prfbench/e2_softquota,1) + (w - softquota_width + 1) * (prfcount/e2_hardquota) + + local y = yoffset + surface.SetDrawColor( Color(0,170,0,255) ) + surface.DrawRect( pos.min.x + pos.edgesize, y, softquota_width, 20 ) + + surface.SetDrawColor( Color(170,0,0,255) ) + surface.DrawRect( pos.min.x + pos.edgesize + softquota_width - 1, y, w - softquota_width + 2, 20 ) + + surface.SetDrawColor( Color(0,0,0,200) ) + surface.DrawRect( pos.min.x + pos.edgesize, y, quota_width, 20 ) + + surface.SetDrawColor( black ) + surface.DrawLine( pos.min.x + pos.edgesize, y, pos.min.x + pos.edgesize + w, y ) + surface.DrawLine( pos.min.x + pos.edgesize + w, y, pos.min.x + pos.edgesize + w, y + 20 ) + surface.DrawLine( pos.min.x + pos.edgesize + w, y + 20, pos.min.x + pos.edgesize, y + 20 ) + surface.DrawLine( pos.min.x + pos.edgesize, y + 20, pos.min.x + pos.edgesize, y ) + + yoffset = yoffset + 20 + + -- cpu time text + local str = string.format("cpu time: %ius", timebench*1000000) + draw.DrawText( str, "GModWorldtip", pos.min.x + pos.size.w/2, yoffset + 9, white, TEXT_ALIGN_CENTER ) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/angle.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/angle.lua new file mode 100644 index 0000000..fc95e60 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/angle.lua @@ -0,0 +1,404 @@ +/******************************************************************************\ +Angle support +\******************************************************************************/ + +registerType("angle", "a", { 0, 0, 0 }, + function(self, input) return { input.p or input[1], input.y or input[2], input.r or input[3] } end, + function(self, output) return Angle(output[1], output[2], output[3]) end, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 3 then error("Return value does not have exactly 3 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 3 + end +) + +local pi = math.pi +local floor, ceil = math.floor, math.ceil + +/******************************************************************************/ + +__e2setcost(1) -- approximated + +e2function angle ang() + return { 0, 0, 0 } +end + +__e2setcost(2) + +e2function angle ang(rv1) + return { rv1, rv1, rv1 } +end + +e2function angle ang(rv1, rv2, rv3) + return { rv1, rv2, rv3 } +end + +// Convert Vector -> Angle +e2function angle ang(vector rv1) + return {rv1[1],rv1[2],rv1[3]} +end + +/******************************************************************************/ + +registerOperator("ass", "a", "a", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ + +e2function number operator_is(angle rv1) + if rv1[1] != 0 || rv1[2] != 0 || rv1[3] != 0 + then return 1 else return 0 end +end + +__e2setcost(3) + +e2function number operator==(angle rv1, angle rv2) + if rv1[1] - rv2[1] <= delta && rv2[1] - rv1[1] <= delta && + rv1[2] - rv2[2] <= delta && rv2[2] - rv1[2] <= delta && + rv1[3] - rv2[3] <= delta && rv2[3] - rv1[3] <= delta + then return 1 else return 0 end +end + +e2function number operator!=(angle rv1, angle rv2) + if rv1[1] - rv2[1] > delta || rv2[1] - rv1[1] > delta || + rv1[2] - rv2[2] > delta || rv2[2] - rv1[2] > delta || + rv1[3] - rv2[3] > delta || rv2[3] - rv1[3] > delta + then return 1 else return 0 end +end + +e2function number operator>=(angle rv1, angle rv2) + if rv2[1] - rv1[1] <= delta && + rv2[2] - rv1[2] <= delta && + rv2[3] - rv1[3] <= delta + then return 1 else return 0 end +end + +e2function number operator<=(angle rv1, angle rv2) + if rv1[1] - rv2[1] <= delta && + rv1[2] - rv2[2] <= delta && + rv1[3] - rv2[3] <= delta + then return 1 else return 0 end +end + +e2function number operator>(angle rv1, angle rv2) + if rv1[1] - rv2[1] > delta && + rv1[2] - rv2[2] > delta && + rv1[3] - rv2[3] > delta + then return 1 else return 0 end +end + +e2function number operator<(angle rv1, angle rv2) + if rv2[1] - rv1[1] > delta && + rv2[2] - rv1[2] > delta && + rv2[3] - rv1[3] > delta + then return 1 else return 0 end +end + +/******************************************************************************/ + +__e2setcost(2) + +e2function angle operator_neg(angle rv1) + return { -rv1[1], -rv1[2], -rv1[3] } +end + +e2function angle operator+(rv1, angle rv2) + return { rv1 + rv2[1], rv1 + rv2[2], rv1 + rv2[3] } +end + +e2function angle operator+(angle rv1, rv2) + return { rv1[1] + rv2, rv1[2] + rv2, rv1[3] + rv2 } +end + +e2function angle operator+(angle rv1, angle rv2) + return { rv1[1] + rv2[1], rv1[2] + rv2[2], rv1[3] + rv2[3] } +end + +e2function angle operator-(rv1, angle rv2) + return { rv1 - rv2[1], rv1 - rv2[2], rv1 - rv2[3] } +end + +e2function angle operator-(angle rv1, rv2) + return { rv1[1] - rv2, rv1[2] - rv2, rv1[3] - rv2 } +end + +e2function angle operator-(angle rv1, angle rv2) + return { rv1[1] - rv2[1], rv1[2] - rv2[2], rv1[3] - rv2[3] } +end + +e2function angle operator*(angle rv1, angle rv2) + return { rv1[1] * rv2[1], rv1[2] * rv2[2], rv1[3] * rv2[3] } +end + +e2function angle operator*(rv1, angle rv2) + return { rv1 * rv2[1], rv1 * rv2[2], rv1 * rv2[3] } +end + +e2function angle operator*(angle rv1, rv2) + return { rv1[1] * rv2, rv1[2] * rv2, rv1[3] * rv2 } +end + +e2function angle operator/(rv1, angle rv2) + return { rv1 / rv2[1], rv1 / rv2[2], rv1 / rv2[3] } +end + +e2function angle operator/(angle rv1, rv2) + return { rv1[1] / rv2, rv1[2] / rv2, rv1[3] / rv2 } +end + +e2function angle operator/(angle rv1, angle rv2) + return { rv1[1] / rv2[1], rv1[2] / rv2[2], rv1[3] / rv2[3] } +end + +e2function number angle:operator[](index) + return this[floor(math.Clamp(index, 1, 3) + 0.5)] +end + +e2function number angle:operator[](index, value) + this[floor(math.Clamp(index, 1, 3) + 0.5)] = value + return value +end + +/******************************************************************************/ + +__e2setcost(5) + +e2function angle angnorm(angle rv1) + return {(rv1[1] + 180) % 360 - 180,(rv1[2] + 180) % 360 - 180,(rv1[3] + 180) % 360 - 180} +end + +e2function number angnorm(rv1) + return (rv1 + 180) % 360 - 180 +end + +__e2setcost(1) + +e2function number angle:pitch() + return this[1] +end + +e2function number angle:yaw() + return this[2] +end + +e2function number angle:roll() + return this[3] +end + +__e2setcost(2) + +// SET methods that returns angles +e2function angle angle:setPitch(rv2) + return { rv2, this[2], this[3] } +end + +e2function angle angle:setYaw(rv2) + return { this[1], rv2, this[3] } +end + +e2function angle angle:setRoll(rv2) + return { this[1], this[2], rv2 } +end + +/******************************************************************************/ + +__e2setcost(5) + +e2function angle round(angle rv1) + return { + floor(rv1[1] + 0.5), + floor(rv1[2] + 0.5), + floor(rv1[3] + 0.5) + } +end + +e2function angle round(angle rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf + 0.5) / shf, + floor(rv1[2] * shf + 0.5) / shf, + floor(rv1[3] * shf + 0.5) / shf + } +end + +e2function angle ceil(angle rv1) + return { + ceil(rv1[1]), + ceil(rv1[2]), + ceil(rv1[3]) + } +end + +e2function angle ceil(angle rv1, decimals) + local shf = 10 ^ decimals + return { + ceil(rv1[1] * shf) / shf, + ceil(rv1[2] * shf) / shf, + ceil(rv1[3] * shf) / shf + } +end + +e2function angle floor(angle rv1) + return { + floor(rv1[1]), + floor(rv1[2]), + floor(rv1[3]) + } +end + +e2function angle floor(angle rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf) / shf, + floor(rv1[2] * shf) / shf, + floor(rv1[3] * shf) / shf + } +end + +// Performs modulo on p,y,r separately +e2function angle mod(angle rv1, rv2) + local p,y,r + if rv1[1] >= 0 then + p = rv1[1] % rv2 + else p = rv1[1] % -rv2 end + if rv1[2] >= 0 then + y = rv1[2] % rv2 + else y = rv1[2] % -rv2 end + if rv1[3] >= 0 then + r = rv1[3] % rv2 + else r = rv1[3] % -rv2 end + return {p, y, r} +end + +// Modulo where divisors are defined as an angle +e2function angle mod(angle rv1, angle rv2) + local p,y,r + if rv1[1] >= 0 then + p = rv1[1] % rv2[1] + else p = rv1[1] % -rv2[1] end + if rv1[2] >= 0 then + y = rv1[2] % rv2[2] + else y = rv1[2] % -rv2[2] end + if rv1[3] >= 0 then + y = rv1[3] % rv2[3] + else y = rv1[3] % -rv2[3] end + return {p, y, r} +end + +// Clamp each p,y,r separately +e2function angle clamp(angle rv1, rv2, rv3) + local p,y,r + + if rv1[1] < rv2 then p = rv2 + elseif rv1[1] > rv3 then p = rv3 + else p = rv1[1] end + + if rv1[2] < rv2 then y = rv2 + elseif rv1[2] > rv3 then y = rv3 + else y = rv1[2] end + + if rv1[3] < rv2 then r = rv2 + elseif rv1[3] > rv3 then r = rv3 + else r = rv1[3] end + + return {p, y, r} +end + +// Clamp according to limits defined by two min/max angles +e2function angle clamp(angle rv1, angle rv2, angle rv3) + local p,y,r + + if rv1[1] < rv2[1] then p = rv2[1] + elseif rv1[1] > rv3[1] then p = rv3[1] + else p = rv1[1] end + + if rv1[2] < rv2[2] then y = rv2[2] + elseif rv1[2] > rv3[2] then y = rv3[2] + else y = rv1[2] end + + if rv1[3] < rv2[3] then r = rv2[3] + elseif rv1[3] > rv3[3] then r = rv3[3] + else r = rv1[3] end + + return {p, y, r} +end + +// Mix two angles by a given proportion (between 0 and 1) +e2function angle mix(angle rv1, angle rv2, rv3) + local p = rv1[1] * rv3 + rv2[1] * (1-rv3) + local y = rv1[2] * rv3 + rv2[2] * (1-rv3) + local r = rv1[3] * rv3 + rv2[3] * (1-rv3) + return {p, y, r} +end + +__e2setcost(2) + +// Circular shift function: shiftr( p,y,r ) = ( r,p,y ) +e2function angle shiftR(angle rv1) + return {rv1[3], rv1[1], rv1[2]} +end + +e2function angle shiftL(angle rv1) + return {rv1[2], rv1[3], rv1[1]} +end + +__e2setcost(5) + +// Returns 1 if the angle lies between (or is equal to) the min/max angles +e2function normal inrange(angle rv1, angle rv2, angle rv3) + if rv1[1] < rv2[1] then return 0 end + if rv1[2] < rv2[2] then return 0 end + if rv1[3] < rv2[3] then return 0 end + + if rv1[1] > rv3[1] then return 0 end + if rv1[2] > rv3[2] then return 0 end + if rv1[3] > rv3[3] then return 0 end + + return 1 +end + +// Rotate an angle around a vector by the given number of degrees +e2function angle angle:rotateAroundAxis(vector axis, degrees) + local ang = Angle(this[1], this[2], this[3]) + local vec = Vector(axis[1], axis[2], axis[3]):GetNormal() + + ang:RotateAroundAxis(vec, degrees) + return {ang.p, ang.y, ang.r} +end + +// Convert the magnitude of the angle to radians +e2function angle toRad(angle rv1) + return {rv1[1] * pi / 180, rv1[2] * pi / 180, rv1[3] * pi / 180} +end + +// Convert the magnitude of the angle to degrees +e2function angle toDeg(angle rv1) + return {rv1[1] * 180 / pi, rv1[2] * 180 / pi, rv1[3] * 180 / pi} +end + +/******************************************************************************/ + +e2function vector angle:forward() + return Angle(this[1], this[2], this[3]):Forward() +end + +e2function vector angle:right() + return Angle(this[1], this[2], this[3]):Right() +end + +e2function vector angle:up() + return Angle(this[1], this[2], this[3]):Up() +end + +e2function string toString(angle a) + return ("[%s,%s,%s]"):format(a[1],a[2],a[3]) +end + +e2function string angle:toString() = e2function string toString(angle a) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/array.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/array.lua new file mode 100644 index 0000000..de743d1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/array.lua @@ -0,0 +1,589 @@ +-------------------------------------------------------------------------------- +-- Array Support +-- Original author: Unknown (But the Wiki mentions Erkle) +-- Rewritten by Divran at 2010-12-21 +-------------------------------------------------------------------------------- + +-------------------------------------------------------------------------------- +-- Helper functions and constants +-------------------------------------------------------------------------------- + +local table_insert = table.insert +local table_remove = table.remove +local floor = math.floor + +local blocked_types = { + ["t"] = true, + ["r"] = true, + ["xgt"] = true +} + +-- Fix return values +local function fixdef( val ) + return istable(val) and table.Copy(val) or val +end + +-- Uppercases the first letter +local function upperfirst( word ) + return word:Left(1):upper() .. word:Right(-2):lower() +end + +-------------------------------------------------------------------------------- +-- Type +-------------------------------------------------------------------------------- + +registerType("array", "r", {}, + function(self, input) + local ret = {} + self.prf = self.prf + #input / 3 + for k,v in pairs(input) do ret[k] = v end + return ret + end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + end, + function(v) + return !istable(v) + end +) + +-------------------------------------------------------------------------------- +-- Array(...) +-- Constructs and returns an array with the given values as elements. If you specify values that are not supported by the array data type, they are skipped +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function array array(...) + local ret = {...} + if (#ret == 0) then return {} end -- This is in place of the old "array()" function (now deleted because array(...) overwrote it) + for k,v in pairs( ret ) do + self.prf = self.prf + 1/3 + if (blocked_types[typeids[k]]) then ret[k] = nil end + end + return ret +end + +registerOperator( "kvarray", "", "r", function( self, args ) + local ret = {} + + local values = args[2] + local types = args[3] + + for k,v in pairs( values ) do + if not blocked_types[types[k]] then + local key = k[1]( self, k ) + local value = v[1]( self, v ) + + ret[key] = value + + self.prf = self.prf + 1/3 + end + end + + return ret +end) + +-------------------------------------------------------------------------------- +-- = operator +-------------------------------------------------------------------------------- +registerOperator("ass", "r", "r", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + local Scope = self.Scopes[scope] + if !Scope.lookup then Scope.lookup = {} end + local lookup = Scope.lookup + + --remove old lookup entry + if (lookup[rhs]) then lookup[rhs][lhs] = nil end + + --add new + if (!lookup[rhs]) then + lookup[rhs] = {} + end + lookup[rhs][lhs] = true + + Scope[lhs] = rhs + Scope.vclk[lhs] = true + return rhs +end) + + + +-------------------------------------------------------------------------------- +-- IS operator +-------------------------------------------------------------------------------- +e2function number operator_is(array arr) + return istable(arr) and 1 or 0 +end + +-------------------------------------------------------------------------------- +-- Looped functions and operators +-------------------------------------------------------------------------------- +registerCallback( "postinit", function() + local getf, setf + for k,v in pairs( wire_expression_types ) do + local name = k:lower() + if (name == "normal") then name = "number" end + local nameupperfirst = upperfirst( name ) + local id = v[1] + local default = v[2] + local typecheck = v[6] + + if (!blocked_types[id]) then -- blocked check start + + -------------------------------------------------------------------------------- + -- Get functions + -- value = R[N,type], and value = R:(N) + -------------------------------------------------------------------------------- + __e2setcost(5) + + local function getter( self, array, index, doremove ) + if (!array or !index) then return fixdef( default ) end -- Make sure array and index are value + local ret + if (doremove) then + ret = table_remove( array, index ) + self.GlobalScope.vclk[array] = true + else + ret = array[floor(index)] + end + if (typecheck and typecheck( ret )) then return fixdef( default ) end -- If typecheck returns true, the type is wrong. + return ret + end + + registerOperator("idx", id.."=rn", id, function(self,args) + local op1, op2 = args[2], args[3] + local array, index = op1[1](self,op1), op2[1](self,op2) + return getter( self, array, index ) + end) + + registerFunction( name, "r:n", id, function(self,args) + local op1, op2 = args[2], args[3] + local array, index = op1[1](self,op1), op2[1](self,op2) + return getter( self, array, index ) + end) + + -------------------------------------------------------------------------------- + -- Set functions + -- R[N,type] = value, and R:set(N,value) + -------------------------------------------------------------------------------- + + local function setter( self, array, index, value, doinsert ) + if (!array or !index) then return fixdef( default ) end -- Make sure array and index are valid + if (typecheck and typecheck( value )) then return fixdef( default ) end -- If typecheck returns true, the type is wrong. + if (doinsert) then + if index > 2^31 or index < 0 then return fixdef( default ) end -- too large, possibility of crashing gmod + table_insert( array, index, value ) + else + array[floor(index)] = value + end + self.GlobalScope.vclk[array] = true + return value + end + + registerOperator("idx", id.."=rn"..id, id, function(self,args) + local op1, op2, op3 = args[2], args[3], args[4] + local array, index, value = op1[1](self,op1), op2[1](self,op2), op3[1](self,op3) + return setter( self, array, index, value ) + end) + + registerFunction("set" .. nameupperfirst, "r:n"..id, id, function(self,args) + local op1, op2, op3 = args[2], args[3], args[4] + local array, index, value = op1[1](self,op1), op2[1](self,op2), op3[1](self,op3) + return setter( self, array, index, value ) + end) + + + -------------------------------------------------------------------------------- + -- Push functions + -- Inserts the value at the end of the array + -------------------------------------------------------------------------------- + __e2setcost(7) + + registerFunction( "push" .. nameupperfirst, "r:" .. id, id, function(self,args) + local op1, op2 = args[2], args[3] + local array, value = op1[1](self,op1), op2[1](self,op2) + return setter( self, array, #array + 1, value ) + end) + + -------------------------------------------------------------------------------- + -- Insert functions + -- Inserts the value at the specified index. Subsequent values are moved up to compensate. + -------------------------------------------------------------------------------- + registerFunction( "insert" .. nameupperfirst, "r:n" .. id, id, function( self, args ) + local op1, op2, op3 = args[2], args[3], args[4] + local array, index, value = op1[1](self,op1), op2[1](self,op2), op3[1](self,op3) + return setter( self, array, index, value, true ) + end) + + -------------------------------------------------------------------------------- + -- Pop functions + -- Removes and returns the last value in the array. + -------------------------------------------------------------------------------- + registerFunction( "pop" .. nameupperfirst, "r:", id, function(self,args) + local op1 = args[2] + local array = op1[1](self,op1) + if (!array) then return fixdef( default ) end + return getter( self, array, #array, true ) + end) + + -------------------------------------------------------------------------------- + -- Unshift functions + -- Inserts the value at the beginning of the array. Subsequent values are moved up to compensate. + -------------------------------------------------------------------------------- + registerFunction( "unshift" .. nameupperfirst, "r:" .. id, id, function(self,args) + local op1, op2 = args[2], args[3] + local array, value = op1[1](self,op1), op2[1](self,op2) + return setter( self, array, 1, value, true ) + end) + + -------------------------------------------------------------------------------- + -- Shift functions + -- Removes and returns the first value of the array. Subsequent values are moved down to compensate. + -------------------------------------------------------------------------------- + registerFunction( "shift" .. nameupperfirst, "r:", id, function(self,args) + local op1 = args[2] + local array = op1[1](self,op1) + if (!array) then return fixdef( default ) end + return getter( self, array, 1, true ) + end) + + -------------------------------------------------------------------------------- + -- Remove functions + -- Removes and returns the specified value of the array. Subsequent values are moved down to compensate. + -------------------------------------------------------------------------------- + registerFunction( "remove" .. nameupperfirst, "r:n", id, function(self,args) + local op1, op2 = args[2], args[3] + local array, index = op1[1](self,op1), op2[1](self,op2) + if (!array or !index) then return fixdef( default ) end + return getter( self, array, index, true ) + end) + + -------------------------------------------------------------------------------- + -- Foreach operators + -------------------------------------------------------------------------------- + __e2setcost(0) + + registerOperator("fea", "n" .. id .. "r", "", function(self, args) + local keyname, valname = args[2], args[3] + + local tbl = args[4] + tbl = tbl[1](self, tbl) + + local statement = args[5] + + for key, value in pairs(tbl) do + if not typecheck(value) then + self:PushScope() + + self.prf = self.prf + 3 + + self.Scope.vclk[keyname] = true + self.Scope.vclk[valname] = true + + self.Scope[keyname] = key + self.Scope[valname] = value + + local ok, msg = pcall(statement[1], self, statement) + + if not ok then + if msg == "break" then self:PopScope() break + elseif msg ~= "continue" then self:PopScope() error(msg, 0) end + end + + self:PopScope() + end + end + end) + + end -- blocked check end + end +end) + +-------------------------------------------------------------------------------- +-- Pop +-- Removes the last entry in the array and returns 1 if removed +-------------------------------------------------------------------------------- +__e2setcost(2) +e2function number array:pop() + local result = table_remove( this ) and 1 or 0 + self.GlobalScope.vclk[this] = true + return result +end + +-------------------------------------------------------------------------------- +-- Shift +-- Removes the first element of the array; all other entries will move down one address and returns 1 if removed +-------------------------------------------------------------------------------- +__e2setcost(3) +e2function number array:shift() + local result = table_remove( this, 1 ) and 1 or 0 + self.GlobalScope.vclk[this] = true + return result +end + +-------------------------------------------------------------------------------- +-- Remove +-- Removes the specified entry, moving subsequent entries down to compensate and returns 1 if removed +-------------------------------------------------------------------------------- +__e2setcost(2) +e2function number array:remove( index ) + local result = table_remove( this, index ) and 1 or 0 + self.GlobalScope.vclk[this] = true + return result +end + +-------------------------------------------------------------------------------- +-- Force remove +-- Force removes the specified entry, without moving subsequent entries down and returns 1 if removed +-- Does not shift larger indexes down to fill the hole +-------------------------------------------------------------------------------- +e2function number array:unset( index ) + if this[index] == nil then return 0 end + this[index] = nil + self.GlobalScope.vclk[this] = true + return 1 +end + +-------------------------------------------------------------------------------- +-- Exists +-- Returns 1 if any value exists at the specified index, 0 if not +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:exists( index ) + return this[index] != nil and 1 or 0 +end + +-------------------------------------------------------------------------------- +-- Count +-- Returns the number of entries in the array +-------------------------------------------------------------------------------- +__e2setcost(3) +e2function number array:count() + return #this +end + +-------------------------------------------------------------------------------- +-- Clear +-- Empties the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function void array:clear() + self.prf = self.prf + #this / 3 + table.Empty(this) +end + +-------------------------------------------------------------------------------- +-- Clone +-- Returns a copy of the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function array array:clone() + local ret = {} + self.prf = self.prf + #this / 3 + for k,v in pairs(this) do + ret[k] = v + end + return ret +end + +-------------------------------------------------------------------------------- +-- Sum +-- Returns the sum of all numerical values in the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:sum() + local ret = 0 + local indexes = 0 + for k,v in pairs( this ) do + indexes = indexes + 1 + ret = ret + (tonumber(v) or 0) + end + self.prf = self.prf + indexes / 2 + return ret +end + +-------------------------------------------------------------------------------- +-- Average +-- Returns the average of all numerical values in the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:average() + local ret = 0 + local indexes = 0 + for k,v in pairs( this ) do + indexes = indexes + 1 + ret = ret + (tonumber(v) or 0) + end + self.prf = self.prf + indexes / 2 + return ret / indexes +end + +-------------------------------------------------------------------------------- +-- Min +-- Returns the smallest value in the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:min() + local num + local indexes = 0 + for k,v in pairs( this ) do + indexes = indexes + 1 + local val = tonumber(v) or 0 + if (num == nil or val < num) then + num = val + end + end + self.prf = self.prf + indexes / 2 + return num or 0 +end + +-------------------------------------------------------------------------------- +-- MinIndex +-- Returns the index of the smallest value in the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:minIndex() + local num = nil + local index = nil + local indexes = 0 + for k,v in pairs( this ) do + indexes = indexes + 1 + local val = tonumber(v) or 0 + if (num == nil or val < num) then + num = val + index = k + end + end + self.prf = self.prf + indexes / 2 + return index or 0 +end + +-------------------------------------------------------------------------------- +-- Max +-- Returns the largest value in the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:max() + local num + local indexes = 0 + for k,v in pairs( this ) do + indexes = indexes + 1 + local val = tonumber(v) or 0 + if (num == nil or val > num) then + num = val + end + end + self.prf = self.prf + indexes / 2 + return num or 0 +end + +-------------------------------------------------------------------------------- +-- MaxIndex +-- Returns the index of the largest value in the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function number array:maxIndex() + local num = nil + local index = nil + local indexes = 0 + for k,v in pairs( this ) do + indexes = indexes + 1 + local val = tonumber(v) or 0 + if (num == nil or val > num) then + num = val + index = k + end + end + self.prf = self.prf + indexes / 2 + return index or 0 +end + +-------------------------------------------------------------------------------- +-- Concat +-- Concatenates the values of the array +-------------------------------------------------------------------------------- +__e2setcost(1) +local luaconcat = table.concat +local clamp = math.Clamp +local function concat( tab, delimeter, startindex, endindex ) + local ret = {} + local len = #tab + + startindex = startindex or 1 + if startindex > len then return "" end + + endindex = clamp(endindex or len, startindex, len) + + for i=startindex, endindex do + ret[#ret+1] = tostring(tab[i]) + end + return luaconcat( ret, delimeter ) +end + +e2function string array:concat() + self.prf = self.prf + #this/3 + return concat(this) +end +e2function string array:concat(string delimiter) + self.prf = self.prf + #this/3 + return concat(this,delimiter) +end +e2function string array:concat(string delimiter, startindex) + self.prf = self.prf + #this/3 + return concat(this,delimiter,startindex) +end +e2function string array:concat(string delimiter, startindex, endindex) + self.prf = self.prf + #this/3 + return concat(this,delimiter,startindex,endindex) +end +e2function string array:concat(startindex) + self.prf = self.prf + #this/3 + return concat(this,"",startindex,endindex) +end +e2function string array:concat(startindex,endindex) + self.prf = self.prf + #this/3 + return concat(this,"",startindex,endindex) +end + +-------------------------------------------------------------------------------- +-- Id +-- Returns a string identifier representing the array +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function string array:id() + return tostring(this) +end + +-------------------------------------------------------------------------------- +-- Add +-- Add the contents of the specified array to the end of 'this' +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function array array:add( array other ) + if (!next(this) and !next(other)) then return {} end -- Both of them are empty + local ret = {} + for i=1,#this do + ret[i] = this[i] + end + for i=1,#other do + ret[#ret+1] = other[i] + end + self.prf = self.prf + #ret / 3 + return ret +end + +-------------------------------------------------------------------------------- +-- Merge +-- Merges the two tables. Identical indexes will be overwritten by 'other' +-------------------------------------------------------------------------------- +__e2setcost(1) +e2function array array:merge( array other ) + if (!next(this) and !next(other)) then return {} end -- Both of them are empty + local ret = {} + for i=1,math.max(#this,#other) do + ret[i] = other[i] or this[i] + end + self.prf = self.prf + #ret / 3 + return ret +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/bitwise.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/bitwise.lua new file mode 100644 index 0000000..42ad448 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/bitwise.lua @@ -0,0 +1,47 @@ +-- By asiekierka, 2009 -- +--Non-luabit XOR by TomyLobo-- + +__e2setcost(2) + +e2function number bAnd(a, b) + return bit.band(a, b) +end +e2function number bOr(a, b) + return bit.bor(a, b) +end +e2function number bXor(a, b) + return bit.bxor(a, b) +end +e2function number bShr(a, b) + return bit.rshift(a, b) +end +e2function number bShl(a, b) + return bit.lshift(a, b) +end +e2function number bNot(n) + return bit.bnot(n) +end +e2function number bNot(n,bits) + if bits >= 32 || bits < 1 then + return (-1)-n + else + return (math.pow(2,bits)-1)-n + end +end + + +e2function number operator_band( a, b ) + return bit.band(a, b) +end +e2function number operator_bor( a, b ) + return bit.bor(a, b) +end +e2function number operator_bxor( a, b ) + return bit.bxor(a, b) +end +e2function number operator_bshr( a, b ) + return bit.rshift(a, b) +end +e2function number operator_bshl( a, b ) + return bit.lshift(a, b) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/bone.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/bone.lua new file mode 100644 index 0000000..02fc241 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/bone.lua @@ -0,0 +1,429 @@ +local isOwner = E2Lib.isOwner +local IsValid = IsValid +registerCallback("e2lib_replace_function", function(funcname, func, oldfunc) + if funcname == "isOwner" then + isOwner = func + elseif funcname == "IsValid" then + IsValid = func + end +end) + +local bone2entity = {} +local bone2index = {} +local entity2bone = {} + +hook.Add("EntityRemoved", "wire_expression2_bone", function(ent) + if not entity2bone[ent] then return end + for index,bone in pairs(entity2bone[ent]) do + bone2entity[bone] = nil + bone2index[bone] = nil + end + entity2bone[ent] = nil +end) + +-- faster access to some math library functions +local abs = math.abs +local atan2 = math.atan2 +local sqrt = math.sqrt +local asin = math.asin +local Clamp = math.Clamp + +local rad2deg = 180 / math.pi + +function getBone(entity, index) + if not entity2bone[entity] then entity2bone[entity] = {} end + local bone = entity2bone[entity][index] + if not bone then + bone = entity:GetPhysicsObjectNum(index) + entity2bone[entity][index] = bone + end + if not bone then return nil end + if not bone:IsValid() then return nil end + + bone2entity[bone] = entity + bone2index[bone] = index + + return bone +end +E2Lib.getBone = getBone + +local function removeBone(bone) + bone2entity[bone] = nil + bone2index[bone] = nil +end + +-- checks whether the bone is valid. if yes, returns the bone's entity and bone index; otherwise, returns nil. +local function isValidBone(b) + if type(b) ~= "PhysObj" or not IsValid(b) then return nil, 0 end + local ent = bone2entity[b] + if not IsValid(ent) then + removeBone(b) + return nil, 0 + end + return ent, bone2index[b] +end +E2Lib.isValidBone = isValidBone + +--[[************************************************************************]]-- + +registerType("bone", "b", nil, + nil, + nil, + function(retval) + if retval == nil then return end + if type(retval) ~= "PhysObj" then error("Return value is neither nil nor a PhysObj, but a "..type(retval).."!",0) end + if not bone2entity[retval] then error("Return value is not a registered bone!",0) end + end, + function(b) + return not isValidBone(b) + end +) + +--[[************************************************************************]]-- + +__e2setcost(1) + +--- if (B) +e2function number operator_is(bone b) + if not isValidBone(b) then return 0 else return 1 end +end + +--- B = B +registerOperator("ass", "b", "b", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +--- B == B +e2function number operator==(bone lhs, bone rhs) + if lhs == rhs then return 1 else return 0 end +end + +--- B != B +e2function number operator!=(bone lhs, bone rhs) + if lhs ~= rhs then return 1 else return 0 end +end + +--[[************************************************************************]]-- +__e2setcost(3) + +--- Returns 's th bone. +e2function bone entity:bone(index) + if not IsValid(this) then return nil end + if index < 0 then return nil end + if index >= this:GetPhysicsObjectCount() then return nil end + return getBone(this, index) +end + +--- Returns an array containing all of 's bones. This array's first element has the index 0! +e2function array entity:bones() + if not IsValid(this) then return {} end + local ret = {} + local maxn = this:GetPhysicsObjectCount()-1 + for i = 0,maxn do + ret[i] = getBone(this, i) + end + return ret +end + +--- Returns 's number of bones. +e2function number entity:boneCount() + if not IsValid(this) then return 0 end + return this:GetPhysicsObjectCount() +end + +__e2setcost(1) + +--- Returns an invalid bone. +e2function bone nobone() + return nil +end + +--- Returns the entity belongs to +e2function entity bone:entity() + return isValidBone(this) +end + +--- Returns 's index in the entity it belongs to. Returns -1 if the bone is invalid or an error occured. +e2function number bone:index() + if not isValidBone(this) then return -1 end + --[[local ent = this:GetEntity() + if not IsValid(ent) then return -1 end + local maxn = ent:GetPhysicsObjectCount()-1 + for i = 0,maxn do + if this == ent:GetPhysicsObjectNum(i) then return i end + end + return -1]] + return bone2index[this] or -1 +end + +--[[************************************************************************]]-- + +--- Returns 's position. +e2function vector bone:pos() + if not isValidBone(this) then return {0, 0, 0} end + return this:GetPos() +end + +--- Returns a vector describing 's forward direction. +e2function vector bone:forward() + if not isValidBone(this) then return {0, 0, 0} end + return this:LocalToWorld(Vector(1,0,0))-this:GetPos() +end + +--- Returns a vector describing 's right direction. +e2function vector bone:right() + if not isValidBone(this) then return {0, 0, 0} end + return this:LocalToWorld(Vector(0,-1,0))-this:GetPos() -- the y coordinate in local coords is left, not right. hence -1 +end + +--- Returns a vector describing 's up direction. +e2function vector bone:up() + if not isValidBone(this) then return {0, 0, 0} end + return this:LocalToWorld(Vector(0,0,1))-this:GetPos() +end + +--- Returns 's velocity. +e2function vector bone:vel() + if not isValidBone(this) then return {0, 0, 0} end + return this:GetVelocity() +end + +--- Returns 's velocity in local coordinates. +e2function vector bone:velL() + if not isValidBone(this) then return {0, 0, 0} end + return this:WorldtoLocal(this:GetVelocity() + this:GetPos()) +end + +--[[************************************************************************]]-- + +--- Transforms from local coordinates (as seen from ) to world coordinates. +e2function vector bone:toWorld(vector pos) + if not isValidBone(this) then return {0, 0, 0} end + return this:LocalToWorld(Vector(pos[1],pos[2],pos[3])) +end + +--- Transforms from world coordinates to local coordinates (as seen from ). +e2function vector bone:toLocal(vector pos) + if not isValidBone(this) then return {0, 0, 0} end + return this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) +end + +--[[************************************************************************]]-- + +--- Returns 's angular velocity. +e2function angle bone:angVel() + if not isValidBone(this) then return {0, 0, 0} end + local vec = this:GetAngleVelocity() + return { vec.y, vec.z, vec.x } +end + +--- Returns a vector describing rotation axis, magnitude and sense given as the vector's direction, magnitude and orientation. +e2function vector bone:angVelVector() + if not isValidBone(this) then return {0, 0, 0} end + return this:GetAngleVelocity() +end + +--- Returns 's pitch, yaw and roll angles. +e2function angle bone:angles() + if not isValidBone(this) then return {0, 0, 0} end + local ang = this:GetAngles() + return { ang.p, ang.y, ang.r } +end + +--[[************************************************************************]]-- + +--- Returns the bearing (yaw) from to . +e2function number bone:bearing(vector pos) + if not isValidBone(this) then return 0 end + + pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) + + return rad2deg*-atan2(pos.y, pos.x) +end + +--- Returns the elevation (pitch) from to . +e2function number bone:elevation(vector pos) + if not isValidBone(this) then return 0 end + pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) + + local len = pos:Length() + if len < delta then return 0 end + return rad2deg*asin(pos.z / len) +end + +--- Returns the elevation (pitch) and bearing (yaw) from to +e2function angle bone:heading(vector pos) + if not isValidBone(this) then return {0, 0, 0} end + + pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) + + -- bearing + local bearing = rad2deg*-atan2(pos.y, pos.x) + + -- elevation + local len = pos:Length()--sqrt(x*x + y*y + z*z) + if len < delta then return { 0, bearing, 0 } end + local elevation = rad2deg*asin(pos.z / len) + + return { elevation, bearing, 0 } +end + +--- Returns 's mass. +e2function number bone:mass() + if not isValidBone(this) then return 0 end + return this:GetMass() +end + +--- Returns 's Center of Mass. +e2function vector bone:massCenter() + if not isValidBone(this) then return {0, 0, 0} end + return this:LocalToWorld(this:GetMassCenter()) +end + +--- Returns 's Center of Mass in local coordinates. +e2function vector bone:massCenterL() + if not isValidBone(this) then return {0, 0, 0} end + return this:GetMassCenter() +end + +--- Sets 's mass (between 0.001 and 50,000) +e2function void bone:setMass(mass) + local ent = isValidBone(this) + if not ent then return end + if not isOwner(self, ent) then return end + mass = Clamp(mass, 0.001, 50000) + this:SetMass(mass) +end + +--- Gets the principal components of 's inertia tensor in the form vec(Ixx, Iyy, Izz) +e2function vector bone:inertia() + if not isValidBone(this) then return {0, 0, 0} end + return this:GetInertia() +end + +--[[************************************************************************]]-- +__e2setcost(30) + +local clamp = WireLib.clampForce + +--- Applies force to according to 's direction and magnitude +e2function void bone:applyForce(vector force) + local ent = isValidBone(this) + if not ent then return end + if not isOwner(self, ent) then return end + force = clamp(force) + this:ApplyForceCenter(Vector(force[1], force[2], force[3])) +end + +--- Applies force to according to from the location of +e2function void bone:applyOffsetForce(vector force, vector pos) + local ent = isValidBone(this) + if not ent then return end + if not isOwner(self, ent) then return end + force = clamp(force) + pos = clamp(pos) + this:ApplyForceOffset(Vector(force[1], force[2], force[3]), Vector(pos[1], pos[2], pos[3])) +end + +--- Applies torque to according to +e2function void bone:applyAngForce(angle angForce) + local ent = isValidBone(this) + if not ent then return end + if not isOwner(self, ent) then return end + + if angForce[1] == 0 and angForce[2] == 0 and angForce[3] == 0 then return end + angForce = clamp(angForce) + + -- assign vectors + local pos = this:GetPos() + local forward = this:LocalToWorld(Vector(1,0,0)) - pos + local left = this:LocalToWorld(Vector(0,1,0)) - pos -- the y coordinate in local coords is left, not right + local up = this:LocalToWorld(Vector(0,0,1)) - pos + + -- apply pitch force + if angForce[1] ~= 0 then + local pitch = up * (angForce[1] * 0.5) + this:ApplyForceOffset( forward, pitch ) + this:ApplyForceOffset( forward * -1, pitch * -1 ) + end + + -- apply yaw force + if angForce[2] ~= 0 then + local yaw = forward * (angForce[2] * 0.5) + this:ApplyForceOffset( left, yaw ) + this:ApplyForceOffset( left * -1, yaw * -1 ) + end + + -- apply roll force + if angForce[3] ~= 0 then + local roll = left * (angForce[3] * 0.5) + this:ApplyForceOffset( up, roll ) + this:ApplyForceOffset( up * -1, roll * -1 ) + end +end + +--- Applies torque according to the axis, magnitude and sense given by the vector's direction, magnitude and orientation. +e2function void bone:applyTorque(vector torque) + local ent = isValidBone(this) + if not ent then return end + if not isOwner(self, ent) then return end + local phys = this + + if torque[1] == 0 and torque[2] == 0 and torque[3] == 0 then return end + torque = clamp(torque) + + local tq = Vector(torque[1], torque[2], torque[3]) + local torqueamount = tq:Length() + + -- Convert torque from local to world axis + tq = phys:LocalToWorld( tq ) - phys:GetPos() + + -- Find two vectors perpendicular to the torque axis + local off + if abs(tq.x) > torqueamount * 0.1 or abs(tq.z) > torqueamount * 0.1 then + off = Vector(-tq.z, 0, tq.x) + else + off = Vector(-tq.y, tq.x, 0) + end + off = off:GetNormal() * torqueamount * 0.5 + + local dir = ( tq:Cross(off) ):GetNormal() + + dir = clamp(dir) + off = clamp(off) + + phys:ApplyForceOffset( dir, off ) + phys:ApplyForceOffset( dir * -1, off * -1 ) +end + +--[[************************************************************************]]-- +__e2setcost(2) +--- Returns 1 if is frozen, 0 otherwise +e2function number bone:isFrozen() + if not isValidBone(this) then return end + if this:IsMoveable() then return 0 else return 1 end +end + +-- helper function for invert(T) in table.lua +function e2_tostring_bone(b) + local ent = isValidBone(b) + if not ent then return "(null)" end + return string.format("%s:bone(%d)", tostring(ent), bone2index[b]) +end + +--- Returns formatted as a string. Returns "(null)" for invalid bones. +e2function string toString(bone b) + local ent = isValidBone(b) + if not ent then return "(null)" end + return string.format("%s:bone(%d)", tostring(ent), bone2index[b]) +end + +WireLib.registerDebuggerFormat("BONE", e2_tostring_bone) + +--[[************************************************************************]]-- + +-- TODO: constraints diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/chat.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/chat.lua new file mode 100644 index 0000000..204d2f8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/chat.lua @@ -0,0 +1,145 @@ +-- Original author: ZeikJT +-- Modified by Gwahir and TomyLobo + +local IsValid = IsValid + +local TextList = { + last = { "", 0, nil } +} +local ChatAlert = {} +local chipHideChat = false + +--[[************************************************************************]]-- + +registerCallback("destruct",function(self) + ChatAlert[self.entity] = nil +end) + +hook.Add("PlayerSay","Exp2TextReceiving", function(ply, text, teamchat) + local entry = { text, CurTime(), ply, teamchat } + TextList[ply:EntIndex()] = entry + TextList.last = entry + + chipHideChat = false + local hideCurrent = false + for e,_ in pairs(ChatAlert) do + if IsValid(e) then + chipHideChat = nil + e.context.data.runByChat = entry + e:Execute() + e.context.data.runByChat = nil + --if chipHideChat ~= nil and ply == e.player then + if chipHideChat and ply == e.player then + hideCurrent = chipHideChat + end + else + ChatAlert[e] = nil + end + end + + if hideCurrent then return "" end +end) + +hook.Add("EntityRemoved","Exp2ChatPlayerDisconnect", function(ply) + TextList[ply:EntIndex()] = nil +end) + +--[[************************************************************************]]-- +__e2setcost(3) + +--- If == 0, the chip will no longer run on chat events, otherwise it makes this chip execute when someone chats. Only needs to be called once, not in every execution. +e2function void runOnChat(activate) + if activate ~= 0 then + ChatAlert[self.entity] = true + else + ChatAlert[self.entity] = nil + end +end + +--- Returns 1 if the chip is being executed because of a chat event. Returns 0 otherwise. +e2function number chatClk() + return self.data.runByChat and 1 or 0 +end + +--- Returns 1 if the chip is being executed because of a chat event by player . Returns 0 otherwise. +e2function number chatClk(entity ply) + if not IsValid(ply) then return 0 end + local cause = self.data.runByChat + return cause and cause[3] == ply and 1 or 0 +end + +--- If != 0, hide the chat message that is currently being processed. +e2function void hideChat(hide) + chipHideChat = hide ~= 0 +end + +--[[************************************************************************]]-- + +--- Returns the last player to speak. +e2function entity lastSpoke() + local entry = TextList.last + if not entry then return nil end + + local ply = entry[3] + if not IsValid(ply) then return nil end + if not ply:IsPlayer() then return nil end + + return ply +end + +--- Returns the last message in the chat log. +e2function string lastSaid() + local entry = TextList.last + if not entry then return "" end + + return entry[1] +end + +--- Returns the time the last message was sent. +e2function number lastSaidWhen() + local entry = TextList.last + if not entry then return 0 end + + return entry[2] +end + +--- Returns 1 if the last message was sent in the team chat, 0 otherwise. +e2function number lastSaidTeam() + local entry = TextList.last + if not entry then return 0 end + + return entry[4] and 1 or 0 +end + +--- Returns what the player last said. +e2function string entity:lastSaid() + if not IsValid(this) then return "" end + if not this:IsPlayer() then return "" end + + local entry = TextList[this:EntIndex()] + if not entry then return "" end + + return entry[1] +end + +--- Returns when the given player last said something. +e2function number entity:lastSaidWhen() + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + + local entry = TextList[this:EntIndex()] + if not entry then return 0 end + + return entry[2] +end + +--- Returns 1 if the last message was sent in the team chat, 0 otherwise. +e2function number entity:lastSaidTeam() + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + + local entry = TextList[this:EntIndex()] + if not entry then return 0 end + + return entry[4] and 1 or 0 +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_console.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_console.lua new file mode 100644 index 0000000..974f678 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_console.lua @@ -0,0 +1,20 @@ +local convars = { + wire_expression2_concmd = 0, + wire_expression2_concmd_whitelist = "", +} + +local function CreateCVars() + for name,default in pairs(convars) do + local current_cvar = CreateClientConVar(name, default, true, true) + local value = current_cvar:GetString() or default + RunConsoleCommand(name, value) + end +end + +if CanRunConsoleCommand() then + CreateCVars() +else + hook.Add("Initialize", "wire_expression2_console", function() + CreateCVars() + end) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_debug.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_debug.lua new file mode 100644 index 0000000..f3ca154 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_debug.lua @@ -0,0 +1,25 @@ +CreateClientConVar( "wire_expression2_print_max", 15, true, true ) +CreateClientConVar( "wire_expression2_print_delay", 0.3, true, true ) + +local chips = {} + +hook.Add("EntityRemoved", "wire_expression2_printColor", function(ent) + chips[ent] = nil +end) + +net.Receive("wire_expression2_printColor", function( len, ply ) + local chip = net.ReadEntity() + local console = net.ReadBool() + if chip and not chips[chip] then + chips[chip] = true + -- printColorDriver is used for the first time on us by this chip + chat.AddText(Color(255,0,0),"While in somone's seat/car/whatever, printColorDriver can be used to 100% realistically fake people talking, including admins.") + chat.AddText(Color(255,0,0),"Don't trust a word you hear while in a seat after seeing this message!") + end + + if console then + MsgC(unpack(net.ReadTable())) + else + chat.AddText(unpack(net.ReadTable())) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_files.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_files.lua new file mode 100644 index 0000000..016db00 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_files.lua @@ -0,0 +1,149 @@ +--[[ + File Extension + By: Dan (McLovin) +]]-- + +local cv_max_transfer_size = CreateConVar( "wire_expression2_file_max_size", "300", { FCVAR_REPLICATED, FCVAR_ARCHIVE } ) //in kib + +local upload_buffer = {} +local download_buffer = {} + +local upload_chunk_size = 20000 //Our overhead is pretty small so lets send it in moderate sized pieces, no need to max out the buffer + +local allowed_directories = { //prefix with >(allowed directory)/file.txt for files outside of e2files/ directory + ["e2files"] = "e2files", + ["e2shared"] = "expression2/e2shared", + ["cpushared"] = "cpuchip/e2shared", + ["gpushared"] = "gpuchip/e2shared", + ["spushared"] = "spuchip/e2shared", + ["dupeshared"] = "adv_duplicator/e2shared" +} + +for _,dir in pairs( allowed_directories ) do + if not file.IsDir( dir, "DATA" ) then file.CreateDir( dir ) end +end + +local function process_filepath( filepath ) + if string.find( filepath, "..", 1, true ) then + return "e2files/", "noname.txt" + end + + local fullpath = "" + + if string.Left( filepath, 1 ) == ">" then + local diresc = string.find( filepath, "/" ) + + if diresc then + local extdir = string.sub( filepath, 2, diresc - 1 ) + local dir = (allowed_directories[extdir] or "e2files") .. "/" + + fullpath = dir .. string.sub( filepath, diresc + 1, string.len( filepath ) ) + else + fullpath = "e2files/" .. filepath + end + else + fullpath = "e2files/" .. filepath + end + + return string.GetPathFromFilename( fullpath ) or "e2files/", string.GetFileFromFilename( fullpath ) or "noname.txt" +end + +/* --- File Read --- */ + +local function upload_callback() + if !upload_buffer or !upload_buffer.data then return end + + local chunk_size = math.Clamp( string.len( upload_buffer.data ), 0, upload_chunk_size ) + + net.Start("wire_expression2_file_chunk") + net.WriteUInt(chunk_size, 32) + net.WriteData(upload_buffer.data, chunk_size) + net.SendToServer() + upload_buffer.data = string.sub( upload_buffer.data, chunk_size + 1, string.len( upload_buffer.data ) ) + + if upload_buffer.chunk >= upload_buffer.chunks then + net.Start("wire_expression2_file_finish") net.SendToServer() + timer.Remove( "wire_expression2_file_upload" ) + return + end + + upload_buffer.chunk = upload_buffer.chunk + 1 +end + +net.Receive("wire_expression2_request_file_sp", function(netlen) + local fpath,fname = process_filepath(net.ReadString()) + RunConsoleCommand("wire_expression2_file_singleplayer", fpath .. fname) +end) + +net.Receive("wire_expression2_request_file", function(netlen) + local fpath,fname = process_filepath(net.ReadString()) + local fullpath = fpath .. fname + + if file.Exists( fullpath,"DATA" ) and file.Size( fullpath, "DATA" ) <= (cv_max_transfer_size:GetInt() * 1024) then + local filedata = file.Read( fullpath,"DATA" ) or "" + + local encoded = E2Lib.encode( filedata ) + + upload_buffer = { + chunk = 1, + chunks = math.ceil( string.len( encoded ) / upload_chunk_size ), + data = encoded + } + + net.Start("wire_expression2_file_begin") + net.WriteUInt(string.len(filedata), 32) + net.SendToServer() + + timer.Create( "wire_expression2_file_upload", 1/60, upload_buffer.chunks, upload_callback ) + else + net.Start("wire_expression2_file_begin") + net.WriteUInt(0, 32) // 404 file not found, send len of 0 + net.SendToServer() + end +end ) + +/* --- File Write --- */ +net.Receive("wire_expression2_file_download_begin", function( netlen ) + local fpath,fname = process_filepath( net.ReadString() ) + if string.GetExtensionFromFilename( string.lower(fname) ) != "txt" then return end + if not file.Exists(fpath, "DATA") then file.CreateDir(fpath) end + download_buffer = { + name = fpath .. fname, + data = "" + } +end ) + +net.Receive("wire_expression2_file_download_chunk", function( netlen ) + if not download_buffer.name then return end + local len = net.ReadUInt(32) + download_buffer.data = (download_buffer.data or "") .. net.ReadData(len) +end ) + +net.Receive("wire_expresison2_file_download_finish", function( netlen ) + if not download_buffer.name then return end + + if net.ReadBit() ~= 0 then + file.Append( download_buffer.name, download_buffer.data ) + else + file.Write( download_buffer.name, download_buffer.data ) + end +end ) + +/* --- File List --- */ + +net.Receive( "wire_expression2_request_list", function( netlen ) + local dir = process_filepath(net.ReadString()) + + net.Start("wire_expression2_file_list") + local files, folders = file.Find( dir .. "*","DATA" ) + net.WriteUInt(#files + #folders, 16) + for _,fop in pairs(files) do + if string.GetExtensionFromFilename( fop ) == "txt" then + net.WriteString(E2Lib.encode( fop )) + end + end + for _,fop in pairs(folders) do + net.WriteString(E2Lib.encode( fop.."/" )) + end + net.SendToServer() +end ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_hologram.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_hologram.lua new file mode 100644 index 0000000..8800a5f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_hologram.lua @@ -0,0 +1,24 @@ +local function WireHologramsShowOwners() + for _,ent in pairs( ents.FindByClass( "gmod_wire_hologram" ) ) do + local id = ent:GetNWInt( "ownerid" ) + + for _,ply in pairs( player.GetAll() ) do + if ply:UserID() == id then + local vec = ent:GetPos():ToScreen() + + draw.DrawText( ply:Name() .. "\n" .. ply:SteamID(), "DermaDefault", vec.x, vec.y, Color(255,0,0,255), 1 ) + break + end + end + end +end + +local display_owners = false +concommand.Add( "wire_holograms_display_owners", function() + display_owners = !display_owners + if display_owners then + hook.Add( "HUDPaint", "wire_holograms_showowners", WireHologramsShowOwners) + else + hook.Remove("HUDPaint", "wire_holograms_showowners") + end +end ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_player.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_player.lua new file mode 100644 index 0000000..68b6ec7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/cl_player.lua @@ -0,0 +1,23 @@ +local function SendFriendStatus() + local friends = {} + for _,ply in ipairs(player.GetHumans()) do + if ply:GetFriendStatus() == "friend" then + table.insert(friends, ply:EntIndex()) + end + end + RunConsoleCommand("wire_expression2_friend_status", table.concat(friends, ",")) +end + +if CanRunConsoleCommand() then + SendFriendStatus() +end +hook.Add("OnEntityCreated", "wire_expression2_extension_player", function(ent) + if not IsValid(ent) then return end + if not ent:IsPlayer() then return end + + SendFriendStatus() +end) + +-- isTyping +hook.Add("StartChat","E2_IsTyping_Start",function() RunConsoleCommand("E2_StartChat") end) +hook.Add("FinishChat","E2_IsTyping_Finish",function() RunConsoleCommand("E2_FinishChat") end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/color.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/color.lua new file mode 100644 index 0000000..a1664d9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/color.lua @@ -0,0 +1,244 @@ +/******************************************************************************\ + Color support +\******************************************************************************/ + +local Clamp = math.Clamp +local floor = math.floor +local Round = math.Round + +local function RGBClamp(r,g,b) + return Clamp(r,0,255),Clamp(g,0,255),Clamp(b,0,255) +end + +/******************************************************************************/ + +__e2setcost(2) + +e2function vector entity:getColor() + if !IsValid(this) then return {0,0,0} end + + local c = this:GetColor() + return { c.r, c.g, c.b } +end + +e2function vector4 entity:getColor4() + if not IsValid(this) then return {0,0,0,0} end + local c = this:GetColor() + return {c.r,c.g,c.b,c.a} +end + +e2function number entity:getAlpha() + return IsValid(this) and this:GetColor().a or 0 +end + +e2function void entity:setColor(r,g,b) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + + WireLib.SetColor(this, Color(r, g, b, this:GetColor().a)) +end + +e2function void entity:setColor(r,g,b,a) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + + WireLib.SetColor(this, Color(r, g, b, a)) +end + +e2function void entity:setColor(vector c) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + + WireLib.SetColor(this, Color(c[1], c[2], c[3], this:GetColor().a)) +end + +e2function void entity:setColor(vector c, a) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + + WireLib.SetColor(this, Color(c[1], c[2], c[3], a)) +end + +e2function void entity:setColor(vector4 c) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + + WireLib.SetColor(this, Color(c[1], c[2], c[3], c[4])) +end + +e2function void entity:setAlpha(a) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + + if this:IsPlayer() then return end + + local c = this:GetColor() + WireLib.SetColor(this, Color(c.r, c.g, c.b, a)) +end + +e2function void entity:setRenderMode(mode) + if !IsValid(this) then return end + if !isOwner(self, this) then return end + if this:IsPlayer() then return end + + this:SetRenderMode(mode) + duplicator.StoreEntityModifier(this, "colour", { RenderMode = mode }) +end + +e2function vector entity:getPlayerColor() + if not IsValid(this) then return {0, 0, 0} end + if not this:IsPlayer() then return {0, 0, 0} end + + local c = this:GetPlayerColor() + + return { RGBClamp(Round(c.r * 255), Round(c.g * 255), Round(c.b * 255)) } +end + +e2function vector entity:getWeaponColor() + if not IsValid(this) then return {0, 0, 0} end + if not this:IsPlayer() then return {0, 0, 0} end + + local c = this:GetWeaponColor() + + return { RGBClamp(Round(c.r * 255), Round(c.g * 255), Round(c.b * 255)) } +end + +--- HSV + +--- Converts from the [http://en.wikipedia.org/wiki/HSV_color_space HSV color space] to the [http://en.wikipedia.org/wiki/RGB_color_space RGB color space] +e2function vector hsv2rgb(vector hsv) + local col = HSVToColor(math.Clamp(hsv[1] % 360, 0, 360), hsv[2], hsv[3]) + return { col.r, col.g, col.b } +end + +e2function vector hsv2rgb(h, s, v) + local col = HSVToColor(math.Clamp(h % 360, 0, 360), s, v) + return { col.r, col.g, col.b } +end + +--- Converts from the [http://en.wikipedia.org/wiki/RGB_color_space RGB color space] to the [http://en.wikipedia.org/wiki/HSV_color_space HSV color space] +e2function vector rgb2hsv(vector rgb) + return { ColorToHSV(Color(rgb[1], rgb[2], rgb[3])) } +end + +e2function vector rgb2hsv(r, g, b) + return { ColorToHSV(Color(r, g, b)) } +end + +--- HSL + +local function Convert_hue2rgb(p, q, t) + if t < 0 then t = t + 1 end + if t > 1 then t = t - 1 end + if t < 1/6 then return p + (q - p) * 6 * t end + if t < 1/2 then return q end + if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end + return p +end + +local function Convert_hsl2rgb(h, s, l) + local r = 0 + local g = 0 + local b = 0 + + if s == 0 then + r = l + g = l + b = l + else + local q = l + s - l * s + if l < 0.5 then q = l * (1 + s) end + local p = 2 * l - q + r = Convert_hue2rgb(p, q, h + 1/3) + g = Convert_hue2rgb(p, q, h) + b = Convert_hue2rgb(p, q, h - 1/3) + end + + return floor(r * 255), floor(g * 255), floor(b * 255) +end + +local function Convert_rgb2hsl(r, g, b) + r = r / 255 + g = g / 255 + b = b / 255 + local max = math.max(r, g, b) + local min = math.min(r, g, b) + local h = (max + min) / 2 + local s = h + local l = h + + if max == min then + h = 0 + s = 0 + else + local d = max - min + s = d / (max + min) + if l > 0.5 then s = d / (2 - max - min) end + if max == r then + if g < b then + h = (g - b) / d + 6 + else + h = (g - b) / d + 0 + end + elseif max == g then + h = (b - r) / d + 2 + elseif max == b then + h = (r - g) / d + 4 + end + h = h / 6 + end + + return h, s, l +end + +--- Converts HSL color space to RGB color space +e2function vector hsl2rgb(vector hsl) + return { RGBClamp(Convert_hsl2rgb(hsl[1] / 360, hsl[2], hsl[3])) } +end + +e2function vector hsl2rgb(h, s, l) + return { RGBClamp(Convert_hsl2rgb(h / 360, s, l)) } +end + +--- Converts RGB color space to HSL color space +e2function vector rgb2hsl(vector rgb) + local h,s,l = Convert_rgb2hsl(RGBClamp(rgb[1], rgb[2], rgb[3])) + return { floor(h * 360), s, l } +end + +e2function vector rgb2hsl(r, g, b) + local h,s,l = Convert_rgb2hsl(RGBClamp(r, g, b)) + return { floor(h * 360), s, l } +end + +--- DIGI + +local converters = {} +converters[0] = function(r, g, b) + local r = Clamp(floor(r/28),0,9) + local g = Clamp(floor(g/28),0,9) + local b = Clamp(floor(b/28),0,9) + + return r*100000+g*10000+b*1000 +end +converters[1] = false +converters[2] = function(r, g, b) + return floor(r)*65536+floor(g)*256+floor(b) +end +converters[3] = function(r, g, b) + return floor(r)*1000000+floor(g)*1000+floor(b) +end + +--- Converts an RGB vector to a number in digital screen format. Specifies a mode, either 0, 2 or 3, corresponding to Digital Screen color modes. +e2function number rgb2digi(vector rgb, mode) + local conv = converters[mode] + if not conv then return 0 end + return conv(rgb[1], rgb[2], rgb[3]) +end + +--- Converts the RGB color (,,) to a number in digital screen format. Specifies a mode, either 0, 2 or 3, corresponding to Digital Screen color modes. +e2function number rgb2digi(r, g, b, mode) + local conv = converters[mode] + if not conv then return 0 end + return conv(r, g, b) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/compat.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/compat.lua new file mode 100644 index 0000000..3bcacdd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/compat.lua @@ -0,0 +1,125 @@ +-- Functions in this file are retained purely for backwards-compatibility. They should not be used in new code and might be removed at any time. + +e2function string number:teamName() + local str = team.GetName(this) + if not str then return "" end + return str +end + +e2function number number:teamScore() + return team.GetScore(this) +end + +e2function number number:teamPlayers() + return team.NumPlayers(this) +end + +e2function number number:teamDeaths() + return team.TotalDeaths(this) +end + +e2function number number:teamFrags() + return team.TotalFrags(this) +end + +e2function void setColor(r, g, b) + self.entity:SetColor(Color(math.Clamp(r, 0, 255), math.Clamp(g, 0, 255), math.Clamp(b, 0, 255), 255)) +end + +__e2setcost(30) -- temporary + +local clamp = WireLib.clampForce + +e2function void applyForce(vector force) + force = clamp(force) + local phys = self.entity:GetPhysicsObject() + phys:ApplyForceCenter(Vector(force[1],force[2],force[3])) +end + +e2function void applyOffsetForce(vector force, vector position) + force = clamp(force) + position = clamp(position) + local phys = self.entity:GetPhysicsObject() + phys:ApplyForceOffset(Vector(force[1],force[2],force[3]), Vector(position[1],position[2],position[3])) +end + +e2function void applyAngForce(angle angForce) + if angForce[1] == 0 and angForce[2] == 0 and angForce[3] == 0 then return end + angForce = clamp(angForce) + + local ent = self.entity + local phys = ent:GetPhysicsObject() + + -- assign vectors + local up = ent:GetUp() + local left = ent:GetRight() * -1 + local forward = ent:GetForward() + + -- apply pitch force + if angForce[1] ~= 0 then + local pitch = up * (angForce[1] * 0.5) + phys:ApplyForceOffset( forward, pitch ) + phys:ApplyForceOffset( forward * -1, pitch * -1 ) + end + + -- apply yaw force + if angForce[2] ~= 0 then + local yaw = forward * (angForce[2] * 0.5) + phys:ApplyForceOffset( left, yaw ) + phys:ApplyForceOffset( left * -1, yaw * -1 ) + end + + -- apply roll force + if angForce[3] ~= 0 then + local roll = left * (angForce[3] * 0.5) + phys:ApplyForceOffset( up, roll ) + phys:ApplyForceOffset( up * -1, roll * -1 ) + end +end + +e2function void applyTorque(vector torque) + if torque[1] == 0 and torque[2] == 0 and torque[3] == 0 then return end + torque = clamp(torque) + + local phys = self.entity:GetPhysicsObject() + + local tq = Vector(torque[1], torque[2], torque[3]) + local torqueamount = tq:Length() + + -- Convert torque from local to world axis + tq = phys:LocalToWorld( tq ) - phys:GetPos() + + -- Find two vectors perpendicular to the torque axis + local off + if math.abs(tq.x) > torqueamount * 0.1 or math.abs(tq.z) > torqueamount * 0.1 then + off = Vector(-tq.z, 0, tq.x) + else + off = Vector(-tq.y, tq.x, 0) + end + off = off:GetNormal() * torqueamount * 0.5 + + local dir = ( tq:Cross(off) ):GetNormal() + + dir = clamp(dir) + off = clamp(off) + + phys:ApplyForceOffset( dir, off ) + phys:ApplyForceOffset( dir * -1, off * -1 ) +end + +__e2setcost(10) + +e2function number entity:height() + --[[ Old code (UGLYYYY) + if(!IsValid(this)) then return 0 end + if(this:IsPlayer() or this:IsNPC()) then + local pos = this:GetPos() + local up = this:GetUp() + return this:NearestPoint(Vector(pos.x+up.x*100,pos.y+up.y*100,pos.z+up.z*100)).z-this:NearestPoint(Vector(pos.x-up.x*100,pos.y-up.y*100,pos.z-up.z*100)).z + else return 0 end + ]] + + -- New code (Same as E:boxSize():z()) + if(!IsValid(this)) then return 0 end + return (this:OBBMaxs() - this:OBBMins()).z +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/complex.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/complex.lua new file mode 100644 index 0000000..dfe2577 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/complex.lua @@ -0,0 +1,459 @@ +/******************************************************************************\ + Complex numbers support +\******************************************************************************/ + +-- faster access to some math library functions +local abs = math.abs +local Round = math.Round +local sqrt = math.sqrt +local exp = math.exp +local log = math.log +local sin = math.sin +local cos = math.cos +local sinh = math.sinh +local cosh = math.cosh +local acos = math.acos +local atan2 = math.atan2 + +local function format(value) + local dbginfo + + if abs(value[1]) < delta then + if abs(value[2]) < delta then + dbginfo = "0" + else + dbginfo = Round(value[2]*1000)/1000 .. "i" + end + else + if value[2] > delta then + dbginfo = Round(value[1]*1000)/1000 .. "+" .. Round(value[2]*1000)/1000 .. "i" + elseif abs(value[2]) <= delta then + dbginfo = Round(value[1]*1000)/1000 + elseif value[2] < -delta then + dbginfo = Round(value[1]*1000)/1000 .. Round(value[2]*1000)/1000 .. "i" + end + end + return dbginfo +end +WireLib.registerDebuggerFormat("COMPLEX", format) + +/******************************************************************************/ + +__e2setcost(2) + +registerType("complex", "c", { 0, 0 }, + function(self, input) return { input[1], input[2] } end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 2 then error("Return value does not have exactly 2 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 2 + end +) + +/******************************************************************************/ + +__e2setcost(4) + +local function cexp(x,y) + return {exp(x)*cos(y), exp(x)*sin(y)} +end + +local function clog(x,y) + local r,i,l + + l = x*x+y*y + + if l < delta then return {-1e+100, 0} end + + r = log(sqrt(l)) + + local c,s + c = x/sqrt(l) + + i = acos(c) + if y<0 then i = -i end + + return {r, i} +end + +local function cdiv(a,b) + local l=b[1]*b[1]+b[2]*b[2] + + return {(a[1]*b[1]+a[2]*b[2])/l, (a[2]*b[1]-a[1]*b[2])/l} +end + +/******************************************************************************/ + +__e2setcost(2) + +registerOperator("ass", "c", "c", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + self.Scopes[scope][lhs] = rhs + self.Scopes[scope].vclk[lhs] = true + return rhs +end) + +e2function number operator_is(complex z) + if (z[1]==0) && (z[2]==0) then return 0 else return 1 end +end + +e2function number operator==(complex lhs, complex rhs) + if abs(lhs[1]-rhs[1])<=delta && + abs(lhs[2]-rhs[2])<=delta then + return 1 + else return 0 end +end + +e2function number operator==(complex lhs, number rhs) + if abs(lhs[1]-rhs)<=delta && + abs(lhs[2])<=delta then + return 1 + else return 0 end +end + +e2function number operator==(number lhs, complex rhs) + if abs(lhs-rhs[1])<=delta && + abs(rhs[2])<=delta then + return 1 + else return 0 end +end + +e2function number operator!=(complex lhs, complex rhs) + if abs(lhs[1]-rhs[1])>delta || + abs(lhs[2]-rhs[2])>delta then + return 1 + else return 0 end +end + +e2function number operator!=(complex lhs, number rhs) + if abs(lhs[1]-rhs)>delta || + abs(lhs[2])>delta then + return 1 + else return 0 end +end + +e2function number operator!=(number lhs, complex rhs) + if abs(lhs-rhs[1])>delta || + abs(rhs[2])>delta then + return 1 + else return 0 end +end + +/******************************************************************************/ + +e2function complex operator_neg(complex z) + return {-z[1], -z[2]} +end + +e2function complex operator+(complex lhs, complex rhs) + return {lhs[1]+rhs[1], lhs[2]+rhs[2]} +end + +e2function complex operator+(number lhs, complex rhs) + return {lhs+rhs[1], rhs[2]} +end + +e2function complex operator+(complex lhs, number rhs) + return {lhs[1]+rhs, lhs[2]} +end + +e2function complex operator-(complex lhs, complex rhs) + return {lhs[1]-rhs[1], lhs[2]-rhs[2]} +end + +e2function complex operator-(number lhs, complex rhs) + return {lhs-rhs[1], -rhs[2]} +end + +e2function complex operator-(complex lhs, number rhs) + return {lhs[1]-rhs, lhs[2]} +end + +e2function complex operator*(complex lhs, complex rhs) + return {lhs[1]*rhs[1]-lhs[2]*rhs[2], lhs[2]*rhs[1]+lhs[1]*rhs[2]} +end + +e2function complex operator*(number lhs, complex rhs) + return {lhs*rhs[1], lhs*rhs[2]} +end + +e2function complex operator*(complex lhs, number rhs) + return {lhs[1]*rhs, lhs[2]*rhs} +end + +e2function complex operator/(complex lhs, complex rhs) + local z = rhs[1]*rhs[1] + rhs[2]*rhs[2] + return {(lhs[1]*rhs[1]+lhs[2]*rhs[2])/z, (lhs[2]*rhs[1]-lhs[1]*rhs[2])/z} +end + +e2function complex operator/(number lhs, complex rhs) + local z = rhs[1]*rhs[1] + rhs[2]*rhs[2] + return {lhs*rhs[1]/z, -lhs*rhs[2]/z} +end + +e2function complex operator/(complex lhs, number rhs) + return {lhs[1]/rhs, lhs[2]/rhs} +end + +e2function complex operator^(complex lhs, complex rhs) + local l = clog(lhs[1], lhs[2]) + local e = {rhs[1]*l[1] - rhs[2]*l[2], rhs[1]*l[2] + rhs[2]*l[1]} + return cexp(e[1], e[2]) +end + +e2function complex operator^(complex lhs, number rhs) + local l = clog(lhs[1], lhs[2]) + return cexp(rhs*l[1], rhs*l[2]) +end + +/******************************** constructors ********************************/ + +--- Returns complex zero +e2function complex comp() + return {0, 0} +end + +--- Converts a real number to complex (returns complex number with real part and imaginary part 0) +e2function complex comp(a) + return {a, 0} +end + +--- Returns +*i +e2function complex comp(a, b) + return {a, b} +end + +--- Returns the imaginary unit i +e2function complex i() + return {0, 1} +end + +--- Returns *i +e2function complex i(b) + return {0, b} +end + +/****************************** helper functions ******************************/ + +--- Returns the absolute value of +e2function number abs(complex z) + return sqrt(z[1]*z[1] + z[2]*z[2]) +end + +--- Returns the argument of +e2function number arg(complex z) + local l = z[1]*z[1]+z[2]*z[2] + if l==0 then return 0 end + local c = z[1]/sqrt(l) + local p = acos(c) + if z[2]<0 then p = -p end + return p +end + +--- Returns the conjugate of +e2function complex conj(complex z) + return {z[1], -z[2]} +end + +--- Returns the real part of +e2function number real(complex z) + return z[1] +end + +--- Returns the imaginary part of +e2function number imag(complex z) + return z[2] +end + +/***************************** exp and logarithms *****************************/ + +--- Raises Euler's constant e to the power of +e2function complex exp(complex z) + return cexp(z[1], z[2]) +end + +--- Calculates the natural logarithm of +e2function complex log(complex z) + return clog(z[1], z[2]) +end + +--- Calculates the logarithm of to a complex base +e2function complex log(complex base, complex z) + return cdiv(clog(z),clog(base)) +end + +--- Calculates the logarithm of to a real base +e2function complex log(number base, complex z) + local l=clog(z) + return {l[1]/log(base), l[2]/log(base)} +end + +--- Calculates the logarithm of to base 2 +e2function complex log2(complex z) + local l=clog(z) + return {l[1]/log(2), l[2]/log(2)} +end + +--- Calculates the logarithm of to base 10 +e2function complex log10(complex z) + local l=clog(z) + return {l[1]/log(10), l[2]/log(10)} +end + +/******************************************************************************/ + +--- Calculates the square root of +e2function complex sqrt(complex z) + local l = clog(z[1], z[2]) + return cexp(0.5*l[1], 0.5*l[2]) +end + +--- Calculates the complex square root of the real number +e2function complex csqrt(n) + if n<0 then + return {0, sqrt(-n)} + else + return {sqrt(n), 0} + end +end + +/******************* trigonometric and hyperbolic functions *******************/ + +__e2setcost(3) + +--- Calculates the sine of +e2function complex sin(complex z) + return {sin(z[1])*cosh(z[2]), sinh(z[2])*cos(z[1])} +end + +--- Calculates the cosine of +e2function complex cos(complex z) + return {cos(z[1])*cosh(z[2]), -sin(z[1])*sinh(z[2])} +end + +--- Calculates the hyperbolic sine of +e2function complex sinh(complex z) + return {sinh(z[1])*cos(z[2]), sin(z[2])*cosh(z[1])} +end + +--- Calculates the hyperbolic cosine of +e2function complex cosh(complex z) + return {cosh(z[1])*cos(z[2]), sinh(z[1])*sin(z[2])} +end + +--- Calculates the tangent of +e2function complex tan(complex z) + local s,c + s = {sin(z[1])*cosh(z[2]), sinh(z[2])*cos(z[1])} + c = {cos(z[1])*cosh(z[2]), -sin(z[1])*sinh(z[2])} + return cdiv(s,c) +end + +--- Calculates the cotangent of +e2function complex cot(complex z) + local s,c + s = {sin(z[1])*cosh(z[2]), sinh(z[2])*cos(z[1])} + c = {cos(z[1])*cosh(z[2]), -sin(z[1])*sinh(z[2])} + return cdiv(c,s) +end + +__e2setcost(5) + +--- Calculates the inverse sine of +e2function complex asin(complex z) + local log1mz2 = clog(1-z[1]*z[1]+z[2]*z[2], 2*z[1]*z[2]) + local rt = cexp(log1mz2[1]*0.5,log1mz2[2]*0.5) + local flog = clog(rt[1]-z[2], z[1]+rt[2]) + return {flog[2], -flog[1]} +end + +--- Calculates the inverse cosine of +e2function complex acos(complex z) + local logz2m1 = clog(z[1]*z[1]-z[2]*z[2]-1, 2*z[1]*z[2]) + local rt = cexp(logz2m1[1]*0.5,logz2m1[2]*0.5) + local flog = clog(z[1]+rt[1], z[2]+rt[2]) + return {flog[2], -flog[1]} +end + +--- Calculates the inverse tangent of +e2function complex atan(complex z) + local frac = cdiv({-z[1],1-z[2]},{z[1],1+z[2]}) + local logfrac = clog(frac[1], frac[2]) + local rt = cexp(logfrac[1]*0.5,logfrac[2]*0.5) + local flog = clog(rt[1], rt[2]) + return {flog[2], -flog[1]} +end + +__e2setcost(2) + +--- Calculates the principle value of +e2function number atan2(complex z) + return atan2(z[2], z[1]) +end + +-- ******************** hyperbolic functions *********************** -- + +__e2setcost(4) + +--- Calculates the hyperbolic tangent of +e2function complex tanh(complex z) + local s,c + s = {sinh(z[1])*cos(z[2]), sin(z[2])*cosh(z[1])} + c = {cosh(z[1])*cos(z[2]), sinh(z[1])*sin(z[2])} + return cdiv(s,c) +end + +--- Calculates the hyperbolic cotangent of +e2function complex coth(complex z) + local s,c + s = {sinh(z[1])*cos(z[2]), sin(z[2])*cosh(z[1])} + c = {cosh(z[1])*cos(z[2]), sinh(z[1])*sin(z[2])} + return cdiv(c,s) +end + +__e2setcost(3) + +--- Calculates the secant of +e2function complex sec(complex z) + local c + c = {cos(z[1])*cosh(z[2]), -sin(z[1])*sinh(z[2])} + return cdiv({1,0},c) +end + +--- Calculates the cosecant of +e2function complex csc(complex z) + local s + s = {sin(z[1])*cosh(z[2]), sinh(z[2])*cos(z[1])} + return cdiv({1,0},s) +end + +--- Calculates the hyperbolic secant of +e2function complex sech(complex z) + local c + c = {cosh(z[1])*cos(z[2]), sinh(z[1])*sin(z[2])} + return cdiv({1,0},c) +end + +--- Calculates the hyperbolic cosecant of +e2function complex csch(complex z) + local s + s = {sinh(z[1])*cos(z[2]), sin(z[2])*cosh(z[1])} + return cdiv({1,0},s) +end + +/******************************************************************************/ + +__e2setcost(15) + +--- Formats as a string. +e2function string toString(complex z) + return format(z) +end +e2function string complex:toString() + return format(this) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/console.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/console.lua new file mode 100644 index 0000000..302218d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/console.lua @@ -0,0 +1,91 @@ +/******************************************************************************\ + Console support +\******************************************************************************/ + +E2Lib.RegisterExtension("console", true, "Lets E2 chips run concommands and retrieve convars") + +local function tokenizeAndGetCommands(str) + -- Tokenize! + local tokens = {} + local curtoken = {} + local escaped = false + for i=1, #str do + local char = string.sub(str, i, i) + if (escaped and char ~= "\"") or string.match(char, "[%w+-]") then + curtoken[#curtoken + 1] = char + else + if #curtoken>0 then tokens[#tokens + 1] = table.concat(curtoken) curtoken = {} end + if char == "\"" then + escaped = not escaped + elseif char ~= " " then + tokens[#tokens + 1] = char + end + end + end + if #curtoken>0 then tokens[#tokens+1] = table.concat(curtoken) end + + -- Get table of commands used + local commands = {tokens[1] or ""} + for i=1, #tokens do + if tokens[i]==";" then + commands[#commands + 1] = tokens[i+1] or "" + end + end + + return commands +end + +local function validConCmd(self, command) + local ply = self.player + if not ply:IsValid() then return false end + if ply:GetInfoNum("wire_expression2_concmd", 0) == 0 then return false end + -- Validating the concmd length to ensure that it won't crash the server. 512 is the max + if #command >= 512 then return false end + + local whitelist = (ply:GetInfo("wire_expression2_concmd_whitelist") or ""):Trim() + if whitelist == "" then return true end + + local whitelistTbl = {} + for k, v in pairs(string.Split(whitelist, ",")) do whitelistTbl[v] = true end + + local commands = tokenizeAndGetCommands(command) + for _, command in pairs(commands) do + if not whitelistTbl[command] then + return false + end + end + return true +end + + +__e2setcost(5) + +e2function number concmd(string command) + if not validConCmd(self, command) then return 0 end + self.player:ConCommand(command:gsub("%%", "%%%%")) + return 1 +end + +e2function string convar(string cvar) + if not validConCmd(self, cvar) then return "" end + local ret = self.player:GetInfo(cvar) + if not ret then return "" end + return ret +end + +e2function number convarnum(string cvar) + if not validConCmd(self, cvar) then return 0 end + local ret = self.player:GetInfoNum(cvar, 0) + if not ret then return 0 end + return ret +end + +e2function number maxOfType(string typename) + if typename == "wire_holograms" then return GetConVarNumber("wire_holograms_max") or 0 end + return GetConVarNumber("sbox_max"..typename) or 0 +end + +e2function number playerDamage() + local ret = GetConVarNumber("sbox_playershurtplayers") or 0 + return ret ~= 0 and 1 or 0 +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/constraint.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/constraint.lua new file mode 100644 index 0000000..7d36572 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/constraint.lua @@ -0,0 +1,315 @@ +----------------------------- +----Helper Functions-- +----------------------------- + +-- caps("heLlO") == "Hello" +local function caps(text) + local capstext = text:sub(1,1):upper() .. text:sub(2):lower() + if capstext == "Nocollide" then return "NoCollide" end + if capstext == "Advballsocket" then return "AdvBallsocket" end + return capstext +end + +-- Returns con.Ent1 or con.Ent2, whichever is not equivalent to ent. Optionally subscripts con with num beforehand. +local function ent1or2(ent,con,num) + if not con then return nil end + if num then + con = con[num] + if not con then return nil end + end + if con.Ent1==ent then return con.Ent2 end + return con.Ent1 +end + +--[[ + -- buildFilters + + Special keywords for filtering include: + 'All' + 'Constraints'/'Constraint' + 'Parented'/'Parents'/'Parent' + 'Wires'/'Wire' + or any constraint type names such as 'Weld', 'Axis', etc + + Prefixing any of the keywords (except 'All') with '-' or '!' will negate the filter + Examples + entity():getConnectedEntities("all") => entity():getConnectedEntities() => entity():getConnectedEntities("") => all three are the same + entity():getConnectedEntities("all","-axis") => get all entities except those constrained via axis + entity():getConnectedEntities("weld","parented") => get welded or parented entities + entity():getConnectedEntities("all","-constraint") => is the same as entity():getConnectedEntities("parented") + entity():getConnectedEntities("-weld") => Invalid. Trying to use a negated keyword without also using 'All' or 'Constraints' won't work. + (if an entity is attached to your contraption in multiple ways and one of them match the filter, then it will be returned.) + + Keywords are not case sensitive. +]] +local function buildFilter(filters) + local filter_lookup = {} + + if #filters == 0 or (#filters == 1 and filters[1] == "") then -- No filters given, same as "All" + filter_lookup.Constraints = true + filter_lookup.Parented = true + filter_lookup.Wires = true + else + for i=1,#filters do + local filter = filters[i] + if type(filter) == "string" then + local bool = true + if string.sub(filter,1,1) == "-" or string.sub(filter,1,1) == "!" then -- check for negation + bool = false + filter = string.sub(filter,2) + end + + filter = caps(filter) + + -- correct potential mistakes + if filter == "Constraint" then filter = "Constraints" + elseif filter == "Parent" or filter == "Parents" then filter = "Parented" + elseif filter == "Wire" then filter = "Wires" end + + if filter == "All" then + if bool then -- "all" can't be negated + filter_lookup.Constraints = true + filter_lookup.Parented = true + filter_lookup.Wires = true + end + else + filter_lookup[filter] = bool + end + end + end + end + + return filter_lookup +end + +local function checkFilter(constraintType,filter_lookup) + if filter_lookup.Constraints -- check if we allow all constraints + and not (filter_lookup[constraintType] == false) -- but also if this specific constraint hasn't been negated + then return true end + + return filter_lookup[constraintType] == true -- check if this specific constraint has been added to the filter +end + +-- Custom version of constraint.GetTable, which is faster than garry's and supports filtering +local function constraint_GetTable(ent,filter_lookup) + if not ent.Constraints then return {} end + + local result = {} + + for _, con in pairs( ent.Constraints ) do + if IsValid(con) then + if filter_lookup and not checkFilter(con.Type,filter_lookup) then continue end + + result[#result+1] = con + end + end + + return result +end + +-- Alias, due to the fact that the custom version of GetTable already supports filtering +local constraint_FindConstraints = constraint_GetTable + +-- Custom version of constraint.FindConstraint, which is faster than garry's +local function constraint_FindConstraint(ent,filter_lookup) + if not ent.Constraints then return {} end + + for _, con in pairs( ent.Constraints ) do + if IsValid(con) then + if filter_lookup and not checkFilter(con.Type,filter_lookup) then continue end + + return con + end + end +end + +local getConnectedEntities + +-- small helper function for getConnectedEntities +local function getConnectedEx(e, filter_lookup, result, already_added) + if IsValid(e) and not already_added[e] then + getConnectedEntities(e, filter_lookup, result, already_added) + end +end + + +-- custom version of constraint.GetAllConstrainedEntities, but is faster than garry's +-- and supports filtering and also parented entities +getConnectedEntities = function(ent, filter_lookup, result, already_added) + result = result or {} + already_added = already_added or {} + + result[#result+1] = ent + already_added[ent] = true + + if filter_lookup then + if filter_lookup.Parented then -- add parented entities + getConnectedEx(ent:GetParent(),filter_lookup, result, already_added) + for _, e in pairs(ent:GetChildren()) do + getConnectedEx( e, filter_lookup, result, already_added ) + end + end + + if filter_lookup.Wires then -- add wired entities + for _, i in pairs(ent.Inputs or {}) do + getConnectedEx( i.Src, filter_lookup, result, already_added ) + end + + for _, o in pairs(ent.Outputs or {}) do + getConnectedEx( o.Src, filter_lookup, result, already_added ) + end + end + end + + for _, con in pairs( ent.Constraints or {} ) do -- add constrained entities + if IsValid(con) then + if filter_lookup and not checkFilter(con.Type,filter_lookup) then -- skip if it doesn't match the filter + continue + end + + for i=1, 6 do + getConnectedEx( con["Ent"..i], filter_lookup, result, already_added ) + end + end + end + + return result +end + +--****************************************************************************** +__e2setcost(20) + +--- Returns an '''array''' containing all entities directly or indirectly constrained to , except itself. +e2function array entity:getConstraints() + if not IsValid(this) then return {} end + if not constraint.HasConstraints(this) then return {} end + + local result = getConnectedEntities(this) + table.remove(result,1) -- is always in the first position + self.prf = self.prf + #result * 30 + + return result +end + +--[[ + Returns an '''array''' constaining all entities directly or indirectly connected to + supports filtering, see buildFilter above +]] +e2function array entity:getConnectedEntities(...) + if not IsValid(this) then return {} end + local result = getConnectedEntities(this,buildFilter({...})) + self.prf = self.prf + #result * 30 + return result +end +e2function array entity:getConnectedEntities(array filters) + if not IsValid(this) then return {} end + local result = getConnectedEntities(this,buildFilter(filters)) + self.prf = self.prf + #result * 30 + return result +end + +__e2setcost(5) + +--- Returns the number of constraints on . +e2function number entity:hasConstraints() + if not IsValid(this) then return 0 end + + return #constraint_GetTable(this) +end + +--- Returns the number of constraints of type on . +e2function number entity:hasConstraints(string constraintType) + if not IsValid(this) then return 0 end + + return #constraint_GetTable(this,buildFilter({constraintType})) +end + +--- Returns 1 if is constrained to anything, 0 otherwise. +e2function number entity:isConstrained() + if not IsValid(this) then return 0 end + if not constraint.HasConstraints(this) then return 0 end + + return 1 +end + +--- Returns the first entity was welded to. +e2function entity entity:isWeldedTo() + if not IsValid(this) then return nil end + if not constraint.HasConstraints(this) then return nil end + + local filter = {Weld=true} -- create filter directly, no need to call buildFilter here, since it's static + return ent1or2(this,constraint_FindConstraint(this, filter)) +end + +--- Returns the th entity was welded to. +e2function entity entity:isWeldedTo(index) + if not IsValid(this) then return nil end + if not constraint.HasConstraints(this) then return nil end + + local filter = {Weld=true} -- create filter directly, no need to call buildFilter here, since it's static + return ent1or2(this,constraint_FindConstraints(this, filter), math.floor(index)) +end + +--- Returns the first entity was constrained to. +e2function entity entity:isConstrainedTo() + if not IsValid(this) then return nil end + if not constraint.HasConstraints(this) then return nil end + + return ent1or2(this,constraint_GetTable(this),1) +end + +--- Returns the th entity was constrained to. +e2function entity entity:isConstrainedTo(index) + if not IsValid(this) then return nil end + if not constraint.HasConstraints(this) then return nil end + + return ent1or2(this,constraint_GetTable(this), math.floor(index)) +end + +--- Returns the first entity was constrained to with the given constraint type . +e2function entity entity:isConstrainedTo(string constraintType) + if not IsValid(this) then return nil end + if not constraint.HasConstraints(this) then return nil end + + return ent1or2(this,constraint_FindConstraint(this, buildFilter({constraintType}))) +end + +--- Returns the th entity was constrained to with the given constraint type . +e2function entity entity:isConstrainedTo(string constraintType, index) + if not IsValid(this) then return nil end + if not constraint.HasConstraints(this) then return nil end + + return ent1or2(this,constraint_FindConstraints(this, buildFilter({constraintType})), math.floor(index)) +end + +--- Returns the '''entity''' is parented to. +e2function entity entity:parent() + if not IsValid(this) then return nil end + return this:GetParent() +end + +--- Returns the '''bone''' is parented to. +e2function bone entity:parentBone() + if not IsValid(this) then return nil end + + local ent = this:GetParent() + if not IsValid(ent) then return nil end + local bonenum = this:GetParentPhysNum() + return getBone(ent, bonenum) +end + +__e2setcost(20) + +--- Returns an '''array''' containing all the children of the entity - that is, every entity whose parent is this entity. +e2function array entity:children() + if not IsValid(this) then return {} end + + local keytable = this:GetChildren() + local array = {} + local i = 1 + for _, ent in pairs(keytable) do + array[i] = ent + i = i + 1 + end + return array +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/core.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/core.lua new file mode 100644 index 0000000..de6097a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/core.lua @@ -0,0 +1,518 @@ +-------------------------------------------------------------------------------- +-- Core language support +-------------------------------------------------------------------------------- + +local delta = wire_expression2_delta + +__e2setcost(1) -- approximation + +registerOperator("dat", "", "", function(self, args) + return istable(args[2]) and table.Copy(args[2]) or args[2] +end) + +__e2setcost(2) -- approximation + +registerOperator("var", "", "", function(self, args) + local op1, scope = args[2], args[3] + local val = self.Scopes[scope][op1] + return val +end) + +-------------------------------------------------------------------------------- + +__e2setcost(0) + +registerOperator("seq", "", "", function(self, args) + self.prf = self.prf + args[2] + if self.prf > e2_tickquota then error("perf", 0) end + + local n = #args + if n == 2 then return end + + for i=3,n-1 do + local op = args[i] + op[1](self, op) + end + + local op = args[n] + return op[1](self, op) +end) + +-------------------------------------------------------------------------------- + +__e2setcost(0) -- approximation + +registerOperator("whl", "", "", function(self, args) + local op1, op2 = args[2], args[3] + + self.prf = self.prf + args[4] + 3 + while op1[1](self, op1) ~= 0 do + self:PushScope() + local ok, msg = pcall(op2[1], self, op2) + if not ok then + if msg == "break" then self:PopScope() break + elseif msg ~= "continue" then self:PopScope() error(msg, 0) end + end + + self.prf = self.prf + args[4] + 3 + self:PopScope() + end +end) + +registerOperator("for", "", "", function(self, args) + local var, op1, op2, op3, op4 = args[2], args[3], args[4], args[5], args[6] + + local rstart, rend, rstep + rstart = op1[1](self, op1) + rend = op2[1](self, op2) + local rdiff = rend - rstart + local rdelta = delta + + if op3 then + rstep = op3[1](self, op3) + + if rdiff > -delta then + if rstep < delta and rstep > -delta then return end + elseif rdiff < delta then + if rstep > -delta then return end + else + return + end + + if rstep < 0 then + rdelta = -delta + end + else + if rdiff > -delta then + rstep = 1 + else + return + end + end + + self.prf = self.prf + 3 + for I=rstart,rend+rdelta,rstep do + self:PushScope() + self.Scope[var] = I + self.Scope.vclk[var] = true + + local ok, msg = pcall(op4[1], self, op4) + if not ok then + if msg == "break" then self:PopScope() break + elseif msg ~= "continue" then self:PopScope() error(msg, 0) end + end + + self.prf = self.prf + 3 + self:PopScope() + end + +end) + +__e2setcost(2) -- approximation + +registerOperator("brk", "", "", function(self, args) + error("break", 0) +end) + +registerOperator("cnt", "", "", function(self, args) + error("continue", 0) +end) + +-------------------------------------------------------------------------------- + +__e2setcost(3) -- approximation + +registerOperator("if", "n", "", function(self, args) + local op1 = args[3] + self.prf = self.prf + args[2] + + local ok, result + + if op1[1](self, op1) ~= 0 then + self:PushScope() + local op2 = args[4] + ok, result = pcall(op2[1],self, op2) + else + self:PushScope() -- for else statments, elseif staments will run the if opp again + local op3 = args[5] + ok, result = pcall(op3[1],self, op3) + end + + self:PopScope() + if not ok then + error(result,0) + end +end) + +registerOperator("def", "n", "", function(self, args) + local op1 = args[2] + local op2 = args[3] + local rv2 = op2[1](self, op2) + + -- sets the argument for the DAT-operator + op1[2][2] = rv2 + local rv1 = op1[1](self, op1) + + if rv1 ~= 0 then + return rv2 + else + self.prf = self.prf + args[5] + local op3 = args[4] + return op3[1](self, op3) + end +end) + +registerOperator("cnd", "n", "", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 ~= 0 then + self.prf = self.prf + args[5] + local op2 = args[3] + return op2[1](self, op2) + else + self.prf = self.prf + args[6] + local op3 = args[4] + return op3[1](self, op3) + end +end) + +-------------------------------------------------------------------------------- + +__e2setcost(1) -- approximation + +registerOperator("trg", "", "n", function(self, args) + local op1 = args[2] + return self.triggerinput == op1 and 1 or 0 +end) + + +registerOperator("iwc", "", "n", function(self, args) + local op1 = args[2] + return IsValid(self.entity.Inputs[op1].Src) and 1 or 0 +end) + +registerOperator("owc","","n",function(self,args) + local op1 = args[2] + local tbl = self.entity.Outputs[op1].Connected + local ret = #tbl + for i=1,ret do if (not IsValid(tbl[i].Entity)) then ret = ret - 1 end end + return ret +end) + +-------------------------------------------------------------------------------- + +__e2setcost(0) -- cascaded + +registerOperator("is", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1 ~= 0 and 1 or 0 +end) + +__e2setcost(1) -- approximation + +registerOperator("not", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1 == 0 and 1 or 0 +end) + +registerOperator("and", "nn", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 == 0 then return 0 end + + local op2 = args[3] + local rv2 = op2[1](self, op2) + return rv2 ~= 0 and 1 or 0 +end) + +registerOperator("or", "nn", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 ~= 0 then return 1 end + + local op2 = args[3] + local rv2 = op2[1](self, op2) + return rv2 ~= 0 and 1 or 0 +end) + +-------------------------------------------------------------------------------- + +__e2setcost(1) -- approximation + +e2function number first() + return self.entity.first and 1 or 0 +end + +e2function number duped() + return self.entity.duped and 1 or 0 +end + +e2function number inputClk() + return self.triggerinput and 1 or 0 +end + +e2function string inputClkName() + return self.triggerinput or "" +end + +-- This MUST be the first destruct hook! +registerCallback("destruct", function(self) + local entity = self.entity + if entity.error then return end + if not entity.script then return end + if not self.data.runOnLast then return end + + self.resetting = false + self.data.runOnLast = false + + self.data.last = true + entity:Execute() + self.data.last = false +end) + +--- Returns 1 if it is being called on the last execution of the expression gate before it is removed or reset. This execution must be requested with the runOnLast(1) command. +e2function number last() + return self.data.last and 1 or 0 +end + +-- dupefinished() +-- Made by Divran + +local function dupefinished( TimedPasteData, TimedPasteDataCurrent ) + for k,v in pairs( TimedPasteData[TimedPasteDataCurrent].CreatedEntities ) do + if (isentity(v) and v:IsValid() and v:GetClass() == "gmod_wire_expression2") then + v.dupefinished = true + v:Execute() + v.dupefinished = nil + end + end +end +hook.Add("AdvDupe_FinishPasting", "E2_dupefinished", dupefinished ) + +e2function number dupefinished() + return self.entity.dupefinished and 1 or 0 +end + +--- Returns 1 if this is the last() execution and caused by the entity being removed. +e2function number removing() + return self.entity.removing and 1 or 0 +end + +--- If != 0, the chip will run once when it is removed, setting the last() flag when it does. +e2function void runOnLast(activate) + if self.data.last then return end + self.data.runOnLast = activate ~= 0 +end + +-------------------------------------------------------------------------------- + +__e2setcost(2) -- approximation + +e2function void exit() + error("exit", 0) +end + +e2function void error( string reason ) + error(reason, 2) +end + +e2function void assert(condition) + if not condition then error("assert failed", 2) end +end + +e2function void assert(condition, string reason) + if not condition then error(reason, 2) end +end + +-------------------------------------------------------------------------------- + +__e2setcost(100) -- approximation + +e2function void reset() + if self.data.last or self.entity.first then error("exit", 0) end + + if self.entity.last_reset and self.entity.last_reset == CurTime() then + error("Attempted to reset the E2 twice in the same tick!", 2) + end + self.entity.last_reset = CurTime() + + self.data.reset = true + error("exit", 0) +end + +-- wrapping this in a postinit hook to make sure this is the last postexecute hook in the list +registerCallback("postinit", function() + -- handle reset() + registerCallback("postexecute", function(self) + if self.data.reset then + self.entity:Reset() + self.data.reset = false + + -- do not execute any other postexecute hooks after this one. + error("cancelhook", 0) + end + end) +end) + +-------------------------------------------------------------------------------- + +local floor = math.floor +local ceil = math.ceil +local round = math.Round + +__e2setcost(1) -- approximation + +e2function number ops() + return round(self.prfbench) +end + +e2function number entity:ops() + if not IsValid(this) or this:GetClass() ~= "gmod_wire_expression2" or not this.context then return 0 end + return round(this.context.prfbench) +end + +e2function number opcounter() + return ceil(self.prf + self.prfcount) +end + +e2function number cpuUsage() + return self.timebench +end + +e2function number entity:cpuUsage() + if not IsValid(this) or this:GetClass() ~= "gmod_wire_expression2" or not this.context then return 0 end + return this.context.timebench +end + +--- If used as a while loop condition, stabilizes the expression around hardquota used. +e2function number perf() + if self.prf >= e2_tickquota*0.95-200 then return 0 end + if self.prf + self.prfcount >= e2_hardquota then return 0 end + if self.prf >= e2_softquota*2 then return 0 end + return 1 +end + +e2function number perf(number n) + n = math.Clamp(n, 0, 100) + if self.prf >= e2_tickquota*n*0.01 then return 0 end + if self.prf + self.prfcount >= e2_hardquota * n * 0.01 then return 0 end + if n == 100 then + if self.prf >= e2_softquota * 2 then return 0 end + else + if self.prf >= e2_softquota * n * 0.01 then return 0 end + end + return 1 +end + +e2function number minquota() + if self.prf < e2_softquota then + return floor(e2_softquota - self.prf) + else + return 0 + end +end + +e2function number maxquota() + if self.prf < e2_tickquota then + local tickquota = e2_tickquota - self.prf + local hardquota = e2_hardquota - self.prfcount - self.prf + e2_softquota + + if hardquota < tickquota then + return floor(hardquota) + else + return floor(tickquota) + end + else + return 0 + end +end + +e2function number softQuota() + return e2_softquota +end + +e2function number hardQuota() + return e2_hardquota +end + +e2function number timeQuota() + return e2_timequota +end + +__e2setcost(nil) + +registerCallback("postinit", function() + -- Returns the Nth value given after the index, the type's zero element otherwise. If you mix types, all non-matching arguments will be regarded as the 2nd argument's type's zero element. + for name,id,zero in pairs_map(wire_expression_types, unpack) do + registerFunction("select", "n"..id.."...", id, function(self, args) + local index = args[2] + index = index[1](self, index) + + index = math.Clamp(math.floor(index), 1, #args-3) + + if index ~= 1 and args[#args][index+1] ~= id then return zero end + local value = args[index+2] + value = value[1](self, value) + return value + end, 5, { "index", "argument1" }) + end +end) + +-------------------------------------------------------------------------------- + +__e2setcost(3) -- approximation + +registerOperator("switch", "", "", function(self, args) + local cases, startcase = args[3], args[4] + + self:PushScope() + + for i=1, #cases do -- We figure out what we can run. + local case = cases[i] + local op1 = case[1] + + self.prf = self.prf + case[3] + if self.prf > e2_tickquota then error("perf", 0) end + + if (op1 and op1[1](self, op1) == 1) then -- Equals operator + startcase = i + break + end + end + + if startcase then + for i=startcase, #cases do + local stmts = cases[i][2] + local ok, msg = pcall(stmts[1], self, stmts) + if not ok then + if msg == "break" then + break + elseif msg ~= "continue" then + self:PopScope() + error(msg, 0) + end + end + end + end + + self:PopScope() +end) + +registerOperator("include", "", "", function(self, args) + local Include = self.includes[ args[2] ] + + if Include and Include[2] then + local Script = Include[2] + + local OldScopes = self:SaveScopes() + self:InitScope() -- Create a new Scope Enviroment + self:PushScope() + + Script[1](self, Script) + + self:PopScope() + self:LoadScopes(OldScopes) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom.lua new file mode 100644 index 0000000..be987c0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom.lua @@ -0,0 +1,3 @@ +/******************************************************************************\ + User defined support +\******************************************************************************/ diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_constraintcore.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_constraintcore.lua new file mode 100644 index 0000000..4eaa2fe --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_constraintcore.lua @@ -0,0 +1,32 @@ +language.Add("Undone_e2_axis", "Undone E2 Axis") +language.Add("Undone_e2_ballsocket", "Undone E2 Ballsocket") +language.Add("Undone_e2_winch", "Undone E2 Winch") +language.Add("Undone_e2_hydraulic", "Undone E2 Hydraulic") +language.Add("Undone_e2_rope", "Undone E2 Rope") +language.Add("Undone_e2_slider", "Undone E2 Slider") +language.Add("Undone_e2_nocollide", "Undone E2 Nocollide") +language.Add("Undone_e2_weld", "Undone E2 Weld") +E2Helper.Descriptions["enableConstraintUndo"] = "If 0, suppresses creation of undo entries for constraints" +E2Helper.Descriptions["axis(evev)"] = "Creates an axis constraint between two entities at vectors local to each entity" +E2Helper.Descriptions["axis(evevn)"] = "Creates an axis constraint between two entities at vectors local to each entity with friction" +E2Helper.Descriptions["axis(evevnv)"] = "Creates an axis constraint between two entities at vectors local to each entity with friction and local rotation axis" +E2Helper.Descriptions["ballsocket"] = "Creates a ballsocket constraint between two entities at a vector local to ent1" +E2Helper.Descriptions["ballsocket(evevvv)"] = "Creates an AdvBallsocket constraint between two entities at a vector local to ent1, using the specified mins, maxs, and frictions" +E2Helper.Descriptions["ballsocket(evevvvn)"] = "Creates an AdvBallsocket constraint between two entities at a vector local to ent1, using the specified mins, maxs, frictions, rotateonly" +E2Helper.Descriptions["weldAng"] = "Creates an angular weld constraint (angles are fixed, position is free) between two entities at a vector local to ent1" +E2Helper.Descriptions["winch"] = "Creates a winch constraint with a referenceid, between two entities, at vectors local to each" +E2Helper.Descriptions["hydraulic(nevevn)"] = "Creates a hydraulic constraint with a referenceid, between two entities, at vectors local to each" +E2Helper.Descriptions["hydraulic(nevevnnsnn)"] = "Creates a hydraulic constraint with a referenceid, between two entities, at vectors local to each, with constant, damping, and stretch only" +E2Helper.Descriptions["hydraulic(nevevnnnsnn)"] = "Creates a hydraulic constraint with a referenceid, between two entities, at vectors local to each, with constant, damping, relative damping and stretch only" +E2Helper.Descriptions["rope(nevev)"] = "Creates a rope constraint with a referenceid, between two entities, at vectors local to each" +E2Helper.Descriptions["rope(nevevnns)"] = "Creates a rope constraint with a referenceid, between two entities, at vectors local to each with add length, width, and material" +E2Helper.Descriptions["rope(nevevnnsn)"] = "Creates a rope constraint with a referenceid, between two entities, at vectors local to each with add length, width, material, and rigidity" +E2Helper.Descriptions["setLength(e:nn)"] = "Sets the length of a winch/hydraulic/rope stored in this entity at a referenceid" +E2Helper.Descriptions["slider"] = "Creates a slider constraint between two entities at vectors local to each entity" +E2Helper.Descriptions["noCollide"] = "Creates a nocollide constraint between two entities" +E2Helper.Descriptions["noCollideAll"] = "Nocollides an entity to all entities/players, just like the tool's right-click" +E2Helper.Descriptions["weld"] = "Creates a weld constraint between two entities" +E2Helper.Descriptions["constraintBreak(e:)"] = "Breaks all constraints of all types on an entity" +E2Helper.Descriptions["constraintBreak(e:e)"] = "Breaks all constraints between two entities" +E2Helper.Descriptions["constraintBreak(e:s)"] = "Breaks all constraints of a type (weld, axis, nocollide, ballsocket) on an entity" +E2Helper.Descriptions["constraintBreak(e:se)"] = "Breaks a constraint of a type (weld, axis, nocollide, ballsocket) between two entities" diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua new file mode 100644 index 0000000..0b8d219 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_prop.lua @@ -0,0 +1,48 @@ +language.Add("Undone_e2_spawned_prop", "Undone E2 Spawned Prop") +language.Add("Undone_e2_spawned_seat", "Undone E2 Spawned Seat") +E2Helper.Descriptions["propManipulate(e:vannn)"] = "Allows to do any single prop core function in one term (position, rotation, freeze, gravity, notsolid)" +E2Helper.Descriptions["propSpawn(sn)"] = "Use the model string or a template entity to spawn a prop. You can set the position and/or the rotation as well. The last number indicates frozen/unfrozen." +E2Helper.Descriptions["propSpawn(en)"] = "Entity template, Frozen Spawns a prop with the model of the template entity. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["propSpawn(svn)"] = "Model path, Position, Frozen Spawns a prop with the model denoted by the string filepath at the position denoted by the vector. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["propSpawn(evn)"] = "Entity template, Position, Frozen Spawns a prop with the model of the template entity at the position denoted by the vector. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["propSpawn(san)"] = "Model path, Rotation, Frozen Spawns a prop with the model denoted by the string filepath and rotated to the angle given. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["propSpawn(ean)"] = "Rotation, Frozen Spawns a prop with the model of the template entity and rotated to the angle given. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["propSpawn(svan)"] = "Model path, Position, Rotation, Frozen Spawns a prop with the model denoted by the string file path, at the position denoted by the vector, and rotated to the angle given. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["propSpawn(evan)"] = "Position, Rotation, Frozen Spawns a prop with the model of the template entity, at the position denoted by the vector, and rotated to the angle given. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["seatSpawn(sn)"] = "Model path, Frozen Spawns a prop with the model denoted by the string filepath. If frozen is 0, then it will spawn unfrozen." +E2Helper.Descriptions["seatSpawn(svan)"] = E2Helper.Descriptions["seatSpawn(sn)"] +E2Helper.Descriptions["propSpawnEffect(n)"] = "Set to 1 to enable prop spawn effect, 0 to disable." +E2Helper.Descriptions["propDelete(e:)"] = "Deletes the specified prop." +E2Helper.Descriptions["propDelete(t:)"] = "Deletes all the props in the given table, returns the amount of props deleted." +E2Helper.Descriptions["propDelete(r:)"] = "Deletes all the props in the given array, returns the amount of props deleted." +E2Helper.Descriptions["propFreeze(e:n)"] = "Passing 0 unfreezes the entity, everything else freezes it." +E2Helper.Descriptions["propNotSolid(e:n)"] = "Passing 0 makes the entity solid, everything else makes it non-solid." +E2Helper.Descriptions["propGravity(e:n)"] = "Passing 0 makes the entity weightless, everything else makes it weighty." +E2Helper.Descriptions["propMakePersistent(e:n)"] = "Setting to 1 will make the prop persistent." +E2Helper.Descriptions["propPhysicalMaterial(e:s)"] = "Changes the surface material of a prop (eg. wood, metal, ... See Material_surface_properties )." +E2Helper.Descriptions["propPhysicalMaterial(e:)"] = "Returns the surface material of a prop." +E2Helper.Descriptions["setPos(e:v)"] = "Sets the position of an entity." +E2Helper.Descriptions["reposition(e:v)"] = "Deprecated. Kept for backwards-compatibility." +E2Helper.Descriptions["setAng(e:a)"] = "Set the rotation of an entity." +E2Helper.Descriptions["rerotate(e:a)"] = "Deprecated. Kept for backwards-compatibility." +E2Helper.Descriptions["parentTo(e:e)"] = "Parents one entity to another." +E2Helper.Descriptions["parentTo(e:)"] = E2Helper.Descriptions["parentTo(e:e)"] +E2Helper.Descriptions["deparent(e:)"] = "Unparents an entity, so it moves freely again." +E2Helper.Descriptions["propBreak(e:)"] = "Breaks/Explodes breakable/explodable props (Useful for Mines)." +E2Helper.Descriptions["propCanCreate()"] = "Returns 1 when propSpawn() will successfully spawn a prop until the limit is reached." +E2Helper.Descriptions["propDrag(e:n)"] = "Passing 0 makes the entity not be affected by drag" +E2Helper.Descriptions["propInertia(e:n)"] = "Sets the directional inertia" +E2Helper.Descriptions["propInertia(e:v)"] = E2Helper.Descriptions["propInertia(e:n)"] +E2Helper.Descriptions["propDraw(e:n)"] = "Passing 0 disables rendering for the entity (makes it really invisible)" +E2Helper.Descriptions["propShadow(e:n)"] = "Passing 0 disables rendering for the entity's shadow" +E2Helper.Descriptions["propSetBuoyancy(e:n)"] = "Sets the prop's buoyancy ratio from 0 to 1" +E2Helper.Descriptions["propSetFriction(e:n)"] = "Sets prop's friction coefficient (default is 1)" +E2Helper.Descriptions["propGetFriction(e:)"] = "Gets prop's friction coefficient" +E2Helper.Descriptions["propSetElasticity(e:n)"] = "Sets prop's elasticity coefficient (default is 1)" +E2Helper.Descriptions["propGetElasticity(e:)"] = "Gets prop's elasticity coefficient" +E2Helper.Descriptions["propSpawnUndo(n)"] = "Set to 0 to force prop removal on E2 shutdown, and suppress Undo entries for props." +E2Helper.Descriptions["propDeleteAll()"] = "Removes all entities spawned by this E2" +E2Helper.Descriptions["propStatic(e:n)"] = "Sets to 1 to make the entity static (disables movement, physgun, unfreeze, drive...) or 0 to cancel." +E2Helper.Descriptions["propSetVelocity(e:v)"] = "Sets the velocity of the prop for the next iteration" +E2Helper.Descriptions["propSetVelocityInstant(e:v)"] = "Sets the initial velocity of the prop" +E2Helper.Descriptions["use(e:)"] = "Simulates a player pressing their use key on the entity." diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_remoteupload.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_remoteupload.lua new file mode 100644 index 0000000..e8a1612 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_remoteupload.lua @@ -0,0 +1,9 @@ +usermessage.Hook("e2_remoteupload_request", function(um) + local target = um:ReadEntity() + local filepath = um:ReadString() + + if target and target:IsValid() and filepath and file.Exists("expression2/" .. filepath, "DATA") then + local str = file.Read("expression2/" .. filepath) + WireLib.Expression2Upload(target, str, "expression2/" .. filepath) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_wiring.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_wiring.lua new file mode 100644 index 0000000..659cde5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/cl_wiring.lua @@ -0,0 +1,6 @@ +E2Helper.Descriptions["createWire(e:essnvs)"] = "Creates a wire from specified input of first entity to the output of second entity, with specified wire width, color and material" +E2Helper.Descriptions["createWire(e:ess)"] = "Creates a wire from specified input of first entity to the output of second entity" +E2Helper.Descriptions["deleteWire(e:s)"] = "Unwires the specified input of the entity" +E2Helper.Descriptions["getWireInputs(e:)"] = "Returns array of all inputs of the entity" +E2Helper.Descriptions["getWireOutputs(e:)"] = "Returns array of all outputs of the entity" +E2Helper.Descriptions["wirelink(e:)"] = "Returns entity's wirelink" diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/constraintcore.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/constraintcore.lua new file mode 100644 index 0000000..fd93b27 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/constraintcore.lua @@ -0,0 +1,376 @@ +E2Lib.RegisterExtension("constraintcore", false, "Allows the creation and manipulation of constraints between entities.") + +registerCallback("construct", function(self) + self.data.constraintUndos = true +end) + +__e2setcost(1) +e2function void enableConstraintUndo(state) + self.data.constraintUndos = state ~= 0 +end + +local function checkEnts(self, ent1, ent2) + if !ent1 || (!ent1:IsValid() && !ent1:IsWorld()) || !ent2 || (!ent2:IsValid() && !ent2:IsWorld()) || ent1 == ent2 then return false end + if !isOwner(self, ent1) || !isOwner(self, ent2) || ent1:IsPlayer() || ent2:IsPlayer() then return false end + return true +end +local function addundo(self, prop, message) + self.player:AddCleanup( "constraints", prop ) + if self.data.constraintUndos then + undo.Create("e2_"..message) + undo.AddEntity( prop ) + undo.SetPlayer( self.player ) + undo.Finish() + end +end +local function caps(text) + local capstext = text:sub(1,1):upper() .. text:sub(2):lower() + if capstext == "Nocollide" then return "NoCollide" end + if capstext == "Advballsocket" then return "AdvBallsocket" end + return capstext +end + +// All vectors are LOCAL positions relative to their corresponding entities +__e2setcost(30) + +--- Creates an axis between and at vector positions local to each ent. +e2function void axis(entity ent1, vector v1, entity ent2, vector v2) + if not checkEnts(self, ent1, ent2) then return end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + addundo(self, constraint.Axis(ent1, ent2, 0, 0, vec1, vec2, 0, 0, 0, 0), "axis") +end + +--- Creates an axis between and at vector positions local to each ent, with friction. +e2function void axis(entity ent1, vector v1, entity ent2, vector v2, friction) + if not checkEnts(self, ent1, ent2) then return end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + addundo(self, constraint.Axis(ent1, ent2, 0, 0, vec1, vec2, 0, 0, friction, 0), "axis") +end + +--- Creates an axis between and at vector positions local to each ent, with friction and rotation axis. +e2function void axis(entity ent1, vector v1, entity ent2, vector v2, friction, vector localaxis) + if not checkEnts(self, ent1, ent2) then return end + local vec1, vec2, laxis = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]), Vector(localaxis[1], localaxis[2], localaxis[3]) + addundo(self, constraint.Axis(ent1, ent2, 0, 0, vec1, vec2, 0, 0, friction, 0, laxis), "axis") +end + +--- Creates a ballsocket between and at , which is local to +e2function void ballsocket(entity ent1, vector v, entity ent2) + if !checkEnts(self, ent1, ent2) then return end + local vec = Vector(v[1], v[2], v[3]) + addundo(self, constraint.Ballsocket(ent1, ent2, 0, 0, vec, 0, 0, 0), "ballsocket") +end + +--- Creates a ballsocket between and at , which is local to , with friction +e2function void ballsocket(entity ent1, vector v, entity ent2, friction) + if !checkEnts(self, ent1, ent2) then return end + local vec = Vector(v[1], v[2], v[3]) + addundo(self, constraint.AdvBallsocket(ent1, ent2, 0, 0, Vector(), vec, 0, 0, -180, -180, -180, 180, 180, 180, friction, friction, friction, 0, 0), "ballsocket") +end + +--- Creates an adv ballsocket between and at , which is local to , with many settings +e2function void ballsocket(entity ent1, vector v, entity ent2, vector mins, vector maxs, vector frictions) + if !checkEnts(self, ent1, ent2) then return end + local vec = Vector(v[1], v[2], v[3]) + addundo(self, constraint.AdvBallsocket(ent1, ent2, 0, 0, Vector(), vec, 0, 0, mins[1], mins[2], mins[3], maxs[1], maxs[2], maxs[3], frictions[1], frictions[2], frictions[3], 0, 0), "ballsocket") +end + +--- Creates an adv ballsocket between and at , which is local to , with many settings +e2function void ballsocket(entity ent1, vector v, entity ent2, vector mins, vector maxs, vector frictions, rotateonly) + if !checkEnts(self, ent1, ent2) then return end + local vec = Vector(v[1], v[2], v[3]) + addundo(self, constraint.AdvBallsocket(ent1, ent2, 0, 0, Vector(), vec, 0, 0, mins[1], mins[2], mins[3], maxs[1], maxs[2], maxs[3], frictions[1], frictions[2], frictions[3], rotateonly, 0), "ballsocket") +end + +--- Creates an angular weld (angles are fixed, position isn't) between and at , which is local to +e2function void weldAng(entity ent1, vector v, entity ent2) + if !checkEnts(self, ent1, ent2) then return end + local vec = Vector(v[1], v[2], v[3]) + addundo(self, constraint.AdvBallsocket(ent1, ent2, 0, 0, Vector(), vec, 0, 0, 0, -0, 0, 0, 0, 0, 0, 0, 0, 1, 0), "ballsocket") +end + + +local function CalcElasticConsts(Phys1, Phys2, Ent1, Ent2) + local minMass + if Ent1:IsWorld() then + minMass = Phys2:GetMass() + elseif Ent2:IsWorld() then + minMass = Phys1:GetMass() + else + minMass = math.min( Phys1:GetMass(), Phys2:GetMass() ) + end + + local const = minMass * 100 + local damp = const * 0.2 + + return const, damp +end + +// Note: Winch is just a rename of Hydraulic with the last parameter True. +--- Makes a winch constraint (stored at index ) between and , at vectors local to their respective ents, with width. +e2function void winch(index, entity ent1, vector v1, entity ent2, vector v2, width) + if !checkEnts(self, ent1, ent2) then return end + if !ent1.data then ent1.data = {} end + if !ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1],v1[2],v1[3]), Vector(v2[1],v2[2],v2[3]) + if width < 0 || width > 50 then width = 1 end + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + local constant, dampen = CalcElasticConsts( ent1:GetPhysicsObject(), ent2:GetPhysicsObject(), ent1, ent2 ) + ent1.data.Ropes[index] = constraint.Elastic( ent1, ent2, 0, 0, vec1, vec2, constant, dampen, 0, "cable/cable2", width, true ) + addundo(self, ent1.data.Ropes[index], "winch") +end + +--- Makes a hydraulic constraint (stored at index ) between and , at vectors local to their respective ents, with width. +e2function void hydraulic(index, entity ent1, vector v1, entity ent2, vector v2, width) + if !checkEnts(self, ent1, ent2) then return end + if !ent1.data then ent1.data = {} end + if !ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1],v1[2],v1[3]), Vector(v2[1],v2[2],v2[3]) + if width < 0 || width > 50 then width = 1 end + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + local constant, dampen = CalcElasticConsts( ent1:GetPhysicsObject(), ent2:GetPhysicsObject(), ent1, ent2 ) + ent1.data.Ropes[index] = constraint.Elastic( ent1, ent2, 0, 0, vec1, vec2, constant, dampen, 0, "cable/cable2", width, false ) + addundo(self, ent1.data.Ropes[index], "hydraulic") +end + +--- Makes a hydraulic constraint (stored at index ) between and , at vectors local to their respective ents, constant and damping, with width, material, and stretch only option. +e2function void hydraulic(index, entity ent1, vector v1, entity ent2, vector v2, constant, damping, string mat, width, stretch) + if not checkEnts(self, ent1, ent2) then return end + if not ent1.data then ent1.data = {} end + if not ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1],v1[2],v1[3]), Vector(v2[1],v2[2],v2[3]) + if width < 0 or width > 50 then width = 1 end + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + ent1.data.Ropes[index] = constraint.Elastic( ent1, ent2, 0, 0, vec1, vec2, constant, damping, 0, mat, width, tobool(stretch) ) + addundo(self, ent1.data.Ropes[index], "hydraulic") +end + +--- Makes a hydraulic constraint (stored at index ) between and , at vectors local to their respective ents, constant, damping and relative damping, with width, material, and stretch only option. +e2function void hydraulic(index, entity ent1, vector v1, entity ent2, vector v2, constant, damping, rdamping, string mat, width, stretch) + if not checkEnts(self, ent1, ent2) then return end + if not ent1.data then ent1.data = {} end + if not ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1],v1[2],v1[3]), Vector(v2[1],v2[2],v2[3]) + if width < 0 or width > 50 then width = 1 end + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + ent1.data.Ropes[index] = constraint.Elastic( ent1, ent2, 0, 0, vec1, vec2, constant, damping, rdamping, mat, width, tobool(stretch) ) + addundo(self, ent1.data.Ropes[index], "hydraulic") +end + +--- Creates a rope between and at vector positions local to each ent. +e2function void rope(index, entity ent1, vector v1, entity ent2, vector v2) + if not checkEnts(self, ent1, ent2) then return end + if not ent1.data then ent1.data = {} end + if not ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + local length = (ent1:LocalToWorld(vec1) - ent2:LocalToWorld(vec2)):Length() + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + ent1.data.Ropes[index] = constraint.Rope( ent1, ent2, 0, 0, vec1, vec2, length, 0, 0, 1, "cable/rope", false ) + addundo(self, ent1.data.Ropes[index], "rope") +end + +--- Creates a rope between and at vector positions local to each ent, with additional length, width, and material. +e2function void rope(index, entity ent1, vector v1, entity ent2, vector v2, addlength, width, string mat) + if not checkEnts(self, ent1, ent2) then return end + if not ent1.data then ent1.data = {} end + if not ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + local length = (ent1:LocalToWorld(vec1) - ent2:LocalToWorld(vec2)):Length() + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + ent1.data.Ropes[index] = constraint.Rope( ent1, ent2, 0, 0, vec1, vec2, length, addlength, 0, width, mat, false ) + addundo(self, ent1.data.Ropes[index], "rope") +end + +--- Creates a rope between and at vector positions local to each ent, with additional length, width, and material. +e2function void rope(index, entity ent1, vector v1, entity ent2, vector v2, addlength, width, string mat, rigid ) + if not checkEnts(self, ent1, ent2) then return end + if not ent1.data then ent1.data = {} end + if not ent1.data.Ropes then ent1.data.Ropes = {} end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + local length = (ent1:LocalToWorld(vec1) - ent2:LocalToWorld(vec2)):Length() + + if IsValid(ent1.data.Ropes[index]) then + ent1.data.Ropes[index]:Remove() + end + + ent1.data.Ropes[index] = constraint.Rope( ent1, ent2, 0, 0, vec1, vec2, length, addlength, 0, width, mat, tobool(rigid) ) + addundo(self, ent1.data.Ropes[index], "rope") +end + +__e2setcost(5) + +--- Sets a rope/hydraulic/winch stored at index inside (the first entity) to be long. +e2function void entity:setLength(index, length) + if not IsValid(this) then return end + if not isOwner(self, this) then return false end + if length < 0 then length = 0 end + if this.data.Ropes then + local con = this.data.Ropes[index] + if IsValid(con) then + if con.Type == "Rope" then + con:SetKeyValue("addlength", length) + else + con:Fire("SetSpringLength", length, 0) + end + end + end +end + +--- Sets a hydraulic/winch stored at index inside (the first entity) to be constant. +e2function void entity:setConstant(index, constant) + if not IsValid(this) then return end + if not isOwner(self, this) then return false end + if constant < 0 then constant = 0 end + if this.data.Ropes then + local con = this.data.Ropes[index] + if IsValid(con) then + con:Fire("SetSpringConstant", constant, 0) + end + end +end + +--- Sets a hydraulic/winch stored at index inside (the first entity) to be constant and damping. +e2function void entity:setConstant(index, constant, damping) + if not IsValid(this) then return end + if not isOwner(self, this) then return false end + if constant < 0 then constant = 0 end + if damping < 0 then damping = 0 end + if this.data.Ropes then + local con = this.data.Ropes[index] + if IsValid(con) then + con:Fire("SetSpringConstant", constant, 0) + con:Fire("SetSpringDamping", damping, 0) + end + end +end + +--- Sets a hydraulic/winch stored at index inside (the first entity) to be damping. +e2function void entity:setDamping(index, damping) + if not IsValid(this) then return end + if not isOwner(self, this) then return false end + if damping < 0 then damping = 0 end + if this.data.Ropes then + local con = this.data.Ropes[index] + if IsValid(con) then + con:Fire("SetSpringDamping", damping, 0) + end + end +end + +__e2setcost(30) + +--- Creates a slider between and at vector positions local to each ent. +e2function void slider(entity ent1, vector v1, entity ent2, vector v2) + if !checkEnts(self, ent1, ent2) then return end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + addundo(self, constraint.Slider(ent1, ent2, 0, 0, vec1, vec2, 1), "slider") +end + +--- Creates a slider between and at vector positions local to each ent, with width. +e2function void slider(entity ent1, vector v1, entity ent2, vector v2, width) + if !checkEnts(self, ent1, ent2) then return end + local vec1, vec2 = Vector(v1[1], v1[2], v1[3]), Vector(v2[1], v2[2], v2[3]) + addundo(self, constraint.Slider(ent1, ent2, 0, 0, vec1, vec2, width), "slider") +end + +--- Nocollides to +e2function void noCollide(entity ent1, entity ent2) + if !checkEnts(self, ent1, ent2) then return end + addundo(self, constraint.NoCollide(ent1, ent2, 0, 0), "nocollide") +end + +--- Nocollides to entities/players, just like Right Click of No-Collide Stool +e2function void noCollideAll(entity ent, state) + if !IsValid(ent) then return end + if !isOwner(self, ent) then return false end + if state != 0 then + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + else + ent:SetCollisionGroup( COLLISION_GROUP_NONE ) + end +end + +--- Welds to +e2function void weld(entity ent1, entity ent2) + if !checkEnts(self, ent1, ent2) then return end + addundo(self, constraint.Weld(ent1, ent2, 0, 0, 0, true), "weld") +end + +__e2setcost(5) + +--- Breaks EVERY CONSTRAINT on +e2function void entity:constraintBreak() + if !IsValid(this) then return end + if !isOwner(self, this) then return false end + constraint.RemoveAll(this) +end + +--- Breaks all constraints between and +e2function void entity:constraintBreak(entity ent2) + if !checkEnts(self, this, ent2) then return end + local consts = this.Constraints + local consts2 = ent2.Constraints + if !consts then + if !consts2 then return end + consts = consts2 + end + for _,v in pairs( consts ) do + if IsValid(v) then + local CTab = v:GetTable() + if ( CTab.Ent1 == this && CTab.Ent2 == ent2 ) || ( CTab.Ent1 == ent2 && CTab.Ent2 == this ) then + v:Remove() + end + end + end +end + +--- Breaks all constraints of type on +e2function void entity:constraintBreak(string type) + if !IsValid(this) then return end + if !isOwner(self, this) then return false end + constraint.RemoveConstraints(this, caps(type)) +end + +--- Breaks a constraint of type between and +e2function void entity:constraintBreak(string type, entity ent2) + if !checkEnts(self, this, ent2) then return end + local consts = this.Constraints + local consts2 = ent2.Constraints + if !consts then + if !consts2 then return end + consts = consts2 + end + for _,v in pairs( consts ) do + if IsValid(v) then + local CTab = v:GetTable() + if CTab.Type == caps(type) && ( CTab.Ent1 == this && CTab.Ent2 == ent2 ) || ( CTab.Ent1 == ent2 && CTab.Ent2 == this ) then + v:Remove() + break + end + end + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/effects.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/effects.lua new file mode 100644 index 0000000..aebeb56 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/effects.lua @@ -0,0 +1,188 @@ +E2Lib.RegisterExtension("effects", false, "Allows E2s to play arbitrary effects.") + +local wire_expression2_effect_burst_max = CreateConVar( "wire_expression2_effect_burst_max", 4, {FCVAR_ARCHIVE} ) +local wire_expression2_effect_burst_rate = CreateConVar( "wire_expression2_effect_burst_rate", 0.1, {FCVAR_ARCHIVE} ) + +-- Use hook E2CanEffect to blacklist/whitelist effects +local effect_blacklist = { + dof_node = true +} + +local function isAllowed( self ) + local data = self.data + + if data.effect_burst == 0 then return false end + + data.effect_burst = data.effect_burst - 1 + + local timerid = "E2_effect_burst_count_" .. self.entity:EntIndex() + if not timer.Exists( timerid ) then + timer.Create( timerid, wire_expression2_effect_burst_rate:GetFloat(), 0, function() + if not IsValid( self.entity ) then + timer.Remove( timerid ) + return + end + + data.effect_burst = data.effect_burst + 1 + if data.effect_burst == wire_expression2_effect_burst_max:GetInt() then + timer.Remove( timerid ) + end + end) + end + + return true +end + +registerType("effect", "xef", nil, + nil, + nil, + function(retval) + if retval == nil then return end + local _type = type(retval) + if _type~="CEffectData" then error("Return value is neither nil nor a CEffectData, but a "..type(retval).."!",0) end + end, + function(v) + return type(v)~="CEffectData" + end +) + +__e2setcost(1) + +registerOperator("ass", "xef", "xef", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + self.Scopes[scope][lhs] = rhs + self.Scopes[scope].vclk[lhs] = true + return rhs +end) + +e2function effect effect() + return EffectData() +end + +e2function effect effect:setOrigin(vector pos) + if not this then return end + + this:SetOrigin(Vector( pos[1], pos[2], pos[3] )) + return this +end + +e2function effect effect:setStart(vector pos) + if not this then return end + + this:SetStart(Vector( pos[1], pos[2], pos[3] )) + return this +end + +e2function effect effect:setMagnitude(number mag) + if not this then return end + + this:SetMagnitude(mag) + return this +end + +e2function effect effect:setAngles(angle ang) + if not this then return end + + this:SetAngles( Angle( ang[1] ,ang[2] ,ang[3] )) + return this +end + +e2function effect effect:setScale(number scale) + if not this then return end + + this:SetScale(scale) + return this +end + +e2function effect effect:setEntity(entity ent) + if not this then return end + if not IsValid(ent) then return end + + this:SetEntity(ent) + return this +end + +e2function effect effect:setNormal(vector norm) + if not this then return end + + this:SetNormal(Vector( norm[1], norm[2], norm[3] )) + return this +end + +e2function effect effect:setSurfaceProp(number prop) + if not this then return end + + this:SetSurfaceProp(prop) + return this +end + +e2function effect effect:setRadius(number radius) + if not this then return end + + this:SetRadius(radius) + return this +end + +e2function effect effect:setMaterialIndex(number index) + if not this then return end + + this:SetMaterialIndex(index) + return this +end + +e2function effect effect:setHitBox(number index) + if not this then return end + + this:SetHitBox(index) + return this +end + +e2function effect effect:setFlags(number flags) + if not this then return end + + this:SetFlags(flags) + return this +end + +e2function effect effect:setEntIndex(number index) + if not this then return end + + this:SetEntIndex(index) + return this +end + +e2function effect effect:setDamageType(number index) + if not this then return end + + this:SetDamageType(index) + return this +end + +e2function effect effect:setColor(number index) + if not this then return end + index = math.Clamp(index,0,255) + this:SetColor(index) + return this +end + +e2function effect effect:setAttachment(number index) + if not this then return end + + this:SetAttachment(index) + return this +end + +e2function void effect:play(string name) + if not this then return end + if not isAllowed(self) then return end + if effect_blacklist[name] then return end + if hook.Run( "Expression2_CanEffect", name:lower(), self ) == false then return end + + util.Effect(name,this) +end + +registerCallback("construct", function(self) + self.data.effect_burst = wire_expression2_effect_burst_max:GetInt() +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/prop.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/prop.lua new file mode 100644 index 0000000..82978ad --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/prop.lua @@ -0,0 +1,509 @@ +/******************************************************************************\ +Prop Core by MrFaul started by ZeikJT +report any wishes, issues to Mr.Faul@gmx.de (GER or ENG pls) +\******************************************************************************/ + +E2Lib.RegisterExtension("propcore", false, "Allows E2 chips to create and manipulate props", "Can be used to teleport props to arbitrary locations, including other player's faces") +PropCore = {} +local sbox_E2_maxProps = CreateConVar( "sbox_E2_maxProps", "-1", FCVAR_ARCHIVE ) +local sbox_E2_maxPropsPerSecond = CreateConVar( "sbox_E2_maxPropsPerSecond", "4", FCVAR_ARCHIVE ) +local sbox_E2_PropCore = CreateConVar( "sbox_E2_PropCore", "2", FCVAR_ARCHIVE ) -- 2: Players can affect their own props, 1: Only admins, 0: Disabled + +local E2totalspawnedprops = 0 +local E2tempSpawnedProps = 0 +local TimeStamp = 0 +local playerMeta = FindMetaTable("Player") + +local function TempReset() + if (CurTime()>= TimeStamp) then + E2tempSpawnedProps = 0 + TimeStamp = CurTime()+1 + end +end +hook.Add("Think","TempReset",TempReset) + +function PropCore.WithinPropcoreLimits() + return (sbox_E2_maxProps:GetInt() <= 0 or E2totalspawnedprops 0 then phys:EnableMotion( false ) end + end + + self.player:AddCleanup( cleanupCategory, prop ) + + if self.data.propSpawnUndo then + undo.Create( undoCategory ) + undo.AddEntity( prop ) + undo.SetPlayer( self.player ) + undo.Finish( undoName .. " (" .. model .. ")" ) + end + + prop:CallOnRemove( "wire_expression2_propcore_remove", + function( prop ) + self.data.spawnedProps[ prop ] = nil + E2totalspawnedprops = E2totalspawnedprops - 1 + end + ) + + self.data.spawnedProps[ prop ] = self.data.propSpawnUndo + E2totalspawnedprops = E2totalspawnedprops + 1 + E2tempSpawnedProps = E2tempSpawnedProps + 1 + + return prop +end + +function PropCore.PhysManipulate(this, pos, rot, freeze, gravity, notsolid) + local phys = this:GetPhysicsObject() + if IsValid( phys ) then + if pos ~= nil then WireLib.setPos( phys, Vector( pos[1],pos[2],pos[3] ) ) end + if rot ~= nil then WireLib.setAng( phys, Angle( rot[1],rot[2],rot[3] ) ) end + if freeze ~= nil and this:GetUnFreezable() ~= true then phys:EnableMotion( freeze == 0 ) end + if gravity ~= nil then phys:EnableGravity( gravity ~= 0 ) end + if notsolid ~= nil then this:SetSolid( notsolid ~= 0 and SOLID_NONE or SOLID_VPHYSICS ) end + phys:Wake() + end +end + +-------------------------------------------------------------------------------- + +__e2setcost(40) +e2function entity propSpawn(string model, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + return PropCore.CreateProp(self,model,self.entity:GetPos()+self.entity:GetUp()*25,self.entity:GetAngles(),frozen) +end + +e2function entity propSpawn(entity template, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + if not IsValid(template) then return NULL end + return PropCore.CreateProp(self,template:GetModel(),self.entity:GetPos()+self.entity:GetUp()*25,self.entity:GetAngles(),frozen) +end + +e2function entity propSpawn(string model, vector pos, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + return PropCore.CreateProp(self,model,Vector(pos[1],pos[2],pos[3]),self.entity:GetAngles(),frozen) +end + +e2function entity propSpawn(entity template, vector pos, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + if not IsValid(template) then return NULL end + return PropCore.CreateProp(self,template:GetModel(),Vector(pos[1],pos[2],pos[3]),self.entity:GetAngles(),frozen) +end + +e2function entity propSpawn(string model, angle rot, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + return PropCore.CreateProp(self,model,self.entity:GetPos()+self.entity:GetUp()*25,Angle(rot[1],rot[2],rot[3]),frozen) +end + +e2function entity propSpawn(entity template, angle rot, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + if not IsValid(template) then return NULL end + return PropCore.CreateProp(self,template:GetModel(),self.entity:GetPos()+self.entity:GetUp()*25,Angle(rot[1],rot[2],rot[3]),frozen) +end + +e2function entity propSpawn(string model, vector pos, angle rot, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + return PropCore.CreateProp(self,model,Vector(pos[1],pos[2],pos[3]),Angle(rot[1],rot[2],rot[3]),frozen) +end + +e2function entity propSpawn(entity template, vector pos, angle rot, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + if not IsValid(template) then return NULL end + return PropCore.CreateProp(self,template:GetModel(),Vector(pos[1],pos[2],pos[3]),Angle(rot[1],rot[2],rot[3]),frozen) +end + +-------------------------------------------------------------------------------- + +__e2setcost(60) +e2function entity seatSpawn(string model, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + if model=="" then model = "models/nova/airboat_seat.mdl" end + return PropCore.CreateProp(self,model,self.entity:GetPos()+self.entity:GetUp()*25,self.entity:GetAngles(),frozen,true) +end + +e2function entity seatSpawn(string model, vector pos, angle rot, number frozen) + if not PropCore.ValidAction(self, nil, "spawn") then return NULL end + if model=="" then model = "models/nova/airboat_seat.mdl" end + return PropCore.CreateProp(self,model,Vector(pos[1],pos[2],pos[3]),Angle(rot[1],rot[2],rot[3]),frozen,true) +end + +-------------------------------------------------------------------------------- + +__e2setcost(10) +e2function void entity:propDelete() + if not PropCore.ValidAction(self, this, "delete") then return end + this:Remove() +end + +e2function void entity:propBreak() + if not PropCore.ValidAction(self, this, "break") then return end + this:Fire("break",1,0) +end + +e2function void entity:use() + if not PropCore.ValidAction(self, this, "use") then return end + + local ply = self.player + if not IsValid(ply) then return end -- if the owner isn't connected to the server, do nothing + + if not hook.Run( "PlayerUse", ply, this ) then return end + if this.Use then + this:Use(ply,ply,USE_ON,0) + else + this:Fire("use","1",0) + end +end + +__e2setcost(30) +local function removeAllIn( self, tbl ) + local count = 0 + for k,v in pairs( tbl ) do + if (IsValid(v) and isOwner(self,v) and !v:IsPlayer()) then + count = count + 1 + v:Remove() + end + end + return count +end + +e2function number table:propDelete() + if not PropCore.ValidAction(self, nil, "Tdelete") then return 0 end + + local count = removeAllIn( self, this.s ) + count = count + removeAllIn( self, this.n ) + + self.prf = self.prf + count + + return count +end + +e2function number array:propDelete() + if not PropCore.ValidAction(self, nil, "Tdelete") then return 0 end + + local count = removeAllIn( self, this ) + + self.prf = self.prf + count + + return count +end + +e2function void propDeleteAll() + for ent in pairs( self.data.spawnedProps ) do + if IsValid( ent ) then + ent:Remove() + end + end + self.data.spawnedProps = {} +end + + +__e2setcost(10) + +-------------------------------------------------------------------------------- +e2function void entity:propManipulate(vector pos, angle rot, number freeze, number gravity, number notsolid) + if not PropCore.ValidAction(self, this, "manipulate") then return end + PropCore.PhysManipulate(this, pos, rot, freeze, gravity, notsolid) +end + +e2function void entity:propFreeze(number freeze) + if not PropCore.ValidAction(self, this, "freeze") then return end + PropCore.PhysManipulate(this, nil, nil, freeze, nil, nil) +end + +e2function void entity:propNotSolid(number notsolid) + if not PropCore.ValidAction(self, this, "solid") then return end + PropCore.PhysManipulate(this, nil, nil, nil, nil, notsolid) +end + +--- Makes not render at all +e2function void entity:propDraw(number drawEnable) + if not PropCore.ValidAction(self, this, "draw") then return end + this:SetNoDraw( drawEnable == 0 ) +end + +--- Makes 's shadow not render at all +e2function void entity:propShadow(number shadowEnable) + if not PropCore.ValidAction(self, this, "shadow") then return end + this:DrawShadow( shadowEnable ~= 0 ) +end + +e2function void entity:propGravity(number gravity) + if not PropCore.ValidAction(self, this, "gravity") then return end + PropCore.PhysManipulate(this, nil, nil, nil, gravity, nil) +end + +e2function void entity:propDrag( number drag ) + if not PropCore.ValidAction(self, this, "drag") then return end + local phys = this:GetPhysicsObject() + if IsValid( phys ) then + phys:EnableDrag( drag ~= 0 ) + end +end + +e2function void entity:propInertia( vector inertia ) + if not PropCore.ValidAction(self, this, "inertia") then return end + if Vector( inertia[1], inertia[2], inertia[3] ):IsZero() then return end + local phys = this:GetPhysicsObject() + if IsValid( phys ) then + phys:SetInertia(Vector(inertia[1], inertia[2], inertia[3])) + end +end + +e2function void entity:propSetBuoyancy(number buoyancy) + if not PropCore.ValidAction(self, this, "buoyancy") then return end + local phys = this:GetPhysicsObject() + if IsValid( phys ) then + phys:SetBuoyancyRatio( math.Clamp(buoyancy, 0, 1) ) + end +end + +e2function void entity:propSetFriction(number friction) + if not PropCore.ValidAction(self, this, "friction") then return end + this:SetFriction( math.Clamp(friction, -1000, 1000) ) +end + +e2function number entity:propGetFriction() + if not PropCore.ValidAction(self, this, "friction") then return 0 end + return this:GetFriction() +end + +e2function void entity:propSetElasticity(number elasticity) + if not PropCore.ValidAction(self, this, "elasticity") then return end + this:SetElasticity( math.Clamp(elasticity, -1000, 1000) ) +end + +e2function number entity:propGetElasticity() + if not PropCore.ValidAction(self, this, "elasticity") then return 0 end + return this:GetElasticity() +end + +e2function void entity:propMakePersistent(number persistent) + if not PropCore.ValidAction(self, this, "persist") then return end + if GetConVarString("sbox_persist") == "0" then return end + if not gamemode.Call("CanProperty", self.player, "persist", this) then return end + this:SetPersistent(persistent ~= 0) +end + +e2function void entity:propPhysicalMaterial(string physprop) + if not PropCore.ValidAction(self, this, "physprop") then return end + construct.SetPhysProp(self.player, this, 0, nil, {nil, Material = physprop}) +end + +e2function string entity:propPhysicalMaterial() + if not PropCore.ValidAction(self, this, "physprop") then return "" end + local phys = this:GetPhysicsObject() + if IsValid(phys) then return phys:GetMaterial() or "" end + return "" +end + +e2function void entity:propSetVelocity(vector velocity) + if not PropCore.ValidAction(self, this, "velocitynxt") then return end + local phys = this:GetPhysicsObject() + if IsValid( phys ) then + phys:SetVelocity(Vector(velocity[1], velocity[2], velocity[3])) + end +end + +e2function void entity:propSetVelocityInstant(vector velocity) + if not PropCore.ValidAction(self, this, "velocityins") then return end + local phys = this:GetPhysicsObject() + if IsValid( phys ) then + phys:SetVelocityInstantaneous(Vector(velocity[1], velocity[2], velocity[3])) + end +end + +hook.Add( "CanDrive", "checkPropStaticE2", function( ply, ent ) if ent.propStaticE2 ~= nil then return false end end ) +e2function void entity:propStatic( number static ) + if not PropCore.ValidAction( self, this, "static" ) then return end + if static ~= 0 and this.propStaticE2 == nil then + local phys = this:GetPhysicsObject() + this.propStaticE2 = phys:IsMotionEnabled() + this.PhysgunDisabled = true + this:SetUnFreezable( true ) + phys:EnableMotion( false ) + elseif this.propStaticE2 ~= nil then + this.PhysgunDisabled = false + this:SetUnFreezable( false ) + if this.propStaticE2 == true then + local phys = this:GetPhysicsObject() + phys:Wake() + phys:EnableMotion( true ) + end + this.propStaticE2 = nil + end +end + +-------------------------------------------------------------------------------- + +__e2setcost(20) +e2function void entity:setPos(vector pos) + if not PropCore.ValidAction(self, this, "pos") then return end + PropCore.PhysManipulate(this, pos, nil, nil, nil, nil) +end + +e2function void entity:reposition(vector pos) = e2function void entity:setPos(vector pos) + +e2function void entity:setAng(angle rot) + if not PropCore.ValidAction(self, this, "ang") then return end + PropCore.PhysManipulate(this, nil, rot, nil, nil, nil) +end + +e2function void entity:rerotate(angle rot) = e2function void entity:setAng(angle rot) + +-------------------------------------------------------------------------------- + +local function parent_check( child, parent ) + while IsValid( parent ) do + if (child == parent) then + return false + end + parent = parent:GetParent() + end + return true +end + +local function parent_antispam( child ) + if (child.E2_propcore_antispam or 0) > CurTime() then + return false + end + + child.E2_propcore_antispam = CurTime() + 0.06 + return true +end + +e2function void entity:parentTo(entity target) + if not PropCore.ValidAction(self, this, "parent") then return end + if not IsValid(target) then return nil end + if(!isOwner(self, target)) then return end + if not parent_antispam( this ) then return end + if this == target then return end + if (!parent_check( this, target )) then return end + this:SetParent(target) +end + +__e2setcost(5) +e2function void entity:deparent() + if not PropCore.ValidAction(self, this, "deparent") then return end + this:SetParent( nil ) +end +e2function void entity:parentTo() = e2function void entity:deparent() + +__e2setcost(1) + +e2function void propSpawnEffect(number on) + self.data.propSpawnEffect = on ~= 0 +end + +e2function void propSpawnUndo(number on) + self.data.propSpawnUndo = on ~= 0 +end + +e2function number propCanCreate() + if PropCore.WithinPropcoreLimits() then return 1 end + return 0 +end + +registerCallback("construct", + function(self) + self.data.propSpawnEffect = true + self.data.propSpawnUndo = true + self.data.spawnedProps = {} + end +) + +registerCallback("destruct", + function(self) + for ent, undo in pairs( self.data.spawnedProps ) do + if undo == false and IsValid( ent ) then + ent:Remove() + end + end + end +) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/readme.txt b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/readme.txt new file mode 100644 index 0000000..91489d3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/readme.txt @@ -0,0 +1,9 @@ +Put your custom expression2 extensions in here. +They will be automatically loaded at runtime. + +For each file, there can be a client part, which must be prefixed with "cl_". +This part will then be transferred to and loaded on the client. + +Example: +If there is a file named "foo.lua" it will be loaded on the server. +If, in addition to that, there is a file named "cl_foo.lua", it will be transferred to and loaded on the client. diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/remoteupload.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/remoteupload.lua new file mode 100644 index 0000000..51508a1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/remoteupload.lua @@ -0,0 +1,82 @@ +E2Lib.RegisterExtension("remoteupload", false, "Allows the E2 to remotely change the source code of other E2s.") + +local antispam = {} + +local function check(ply) + if antispam[ply] and antispam[ply] > CurTime() then + return false + else + antispam[ply] = CurTime() + 1 + return true + end +end + +umsg.PoolString("e2_remoteupload_request") + +__e2setcost(1000) +e2function void entity:remoteUpload( string filepath ) + if not this or not this:IsValid() or this:GetClass() ~= "gmod_wire_expression2" then return end + if E2Lib.getOwner( self,this ) ~= self.player then return end + if not check(self.player) then return end + + umsg.Start( "e2_remoteupload_request", self.player ) + umsg.Entity( this ) + umsg.String( filepath ) + umsg.End() +end + +__e2setcost(250) +e2function void entity:remoteSetCode( string code ) + if not this or not this:IsValid() or this:GetClass() ~= "gmod_wire_expression2" then return end + if E2Lib.getOwner( self,this ) ~= self.player then return end + if not check(self.player) then return end + + timer.Simple( 0, function() + this:Setup( code, {}, nil, nil, "remoteSetCode" ) + end ) +end + +e2function void entity:remoteSetCode( string main, table includes ) + if not this or not this:IsValid() or this:GetClass() ~= "gmod_wire_expression2" then return end + if E2Lib.getOwner( self,this ) ~= self.player then return end + if not check(self.player) then return end + + local luatable = {} + + for k,v in pairs( includes.s ) do + self.prf = self.prf + 0.3 + if includes.stypes[k] == "s" then + luatable[k] = v + else + error( "Non-string value given to remoteSetCode", 2 ) + end + end + + timer.Simple( 0, function() + this:Setup( main, luatable, nil, nil, "remoteSetCode" ) + end ) +end + +__e2setcost(20) + +e2function string getCode() + local main, _ = self.entity:GetCode() + return main +end + +e2function table getCodeIncludes() + local _, includes = self.entity:GetCode() + local e2table = {n={},ntypes={},s={},stypes={},size=0} + local size = 0 + + for k,v in pairs( includes ) do + size = size + 1 + e2table.s[k] = v + e2table.stypes[k] = "s" + end + + self.prf = self.prf + size * 0.3 + e2table.size = size + + return e2table +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/wiring.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/wiring.lua new file mode 100644 index 0000000..7a5ed85 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/custom/wiring.lua @@ -0,0 +1,108 @@ +-- Originally by Jeremydeath, updated by Nebual + Natrim's wirelink +E2Lib.RegisterExtension("wiring", false, "Allows the creation and deletion of wires between entities and getting wirelinks.") + +__e2setcost(30) + +--- Creates an invisible wire between the input of and the output of +e2function number entity:createWire(entity ent2, string inputname, string outputname) + if not IsValid(this) or not IsValid(ent2) then return 0 end + if not isOwner(self, this) or not isOwner(self, ent2) then return 0 end + + if not this.Inputs or not ent2.Outputs then return 0 end + if inputname == "" or outputname == "" then return 0 end + if not this.Inputs[inputname] or not ent2.Outputs[outputname] then return 0 end + if this.Inputs[inputname].Src then + local CheckInput = this.Inputs[inputname] + if CheckInput.SrcId == outputname and CheckInput.Src == ent2 then return 0 end -- Already wired + end + + local trigger = self.entity.trigger + self.entity.trigger = { false, {} } -- So the wire creation doesn't execute the E2 immediately because an input changed + WireLib.Link_Start(self.player:UniqueID(), this, this:WorldToLocal(this:GetPos()), inputname, "cable/rope", Vector(255,255,255), 0) + WireLib.Link_End(self.player:UniqueID(), ent2, ent2:WorldToLocal(ent2:GetPos()), outputname, self.player) + self.entity.trigger = trigger + + return 1 +end + +local ValidWireMat = {"cable/rope", "cable/cable2", "cable/xbeam", "cable/redlaser", "cable/blue_elec", "cable/physbeam", "cable/hydra", "arrowire/arrowire", "arrowire/arrowire2"} +--- Creates a wire between the input of and the output of , using the , , +e2function number entity:createWire(entity ent2, string inputname, string outputname, width, vector color, string mat) + if not IsValid(this) or not IsValid(ent2) then return 0 end + if not isOwner(self, this) or not isOwner(self, ent2) then return 0 end + + if not this.Inputs or not ent2.Outputs then return 0 end + if inputname == "" or outputname == "" then return 0 end + if not this.Inputs[inputname] or not ent2.Outputs[outputname] then return 0 end + if this.Inputs[inputname].Src then + local CheckInput = this.Inputs[inputname] + if CheckInput.SrcId == outputname and CheckInput.Src == ent2 then return 0 end -- Already wired + end + + if(!table.HasValue(ValidWireMat,mat)) then + if(table.HasValue(ValidWireMat,"cable/"..mat)) then + mat = "cable/"..mat + elseif(table.HasValue(ValidWireMat,"arrowire/"..mat)) then + mat = "arrowire/"..mat + else + return 0 + end + end + + local trigger = self.entity.trigger + self.entity.trigger = { false, {} } -- So the wire creation doesn't execute the E2 immediately because an input changed + WireLib.Link_Start(self.player:UniqueID(), this, this:WorldToLocal(this:GetPos()), inputname, mat, Vector(color[1],color[2],color[3]), width or 1) + WireLib.Link_End(self.player:UniqueID(), ent2, ent2:WorldToLocal(ent2:GetPos()), outputname, self.player) + self.entity.trigger = trigger + + return 1 +end + +--- Deletes wire leading to 's input +e2function number entity:deleteWire(string inputname) + if not IsValid(this) or not isOwner(self, this) or inputname == "" then return 0 end + + if not this.Inputs or not this.Inputs[inputname] or not this.Inputs[inputname].Src then return 0 end + local trigger = self.entity.trigger + self.entity.trigger = { false, {} } -- So the wire deletion doesn't execute the E2 immediately because an input zero'd + WireLib.Link_Clear(this, inputname) + self.entity.trigger = trigger + return 1 +end + +__e2setcost(10) +--- Returns an array of 's wire input names +e2function array entity:getWireInputs() + if not IsValid(this) or not isOwner(self, this) or not this.Inputs then return {} end + local ret = {} + for k,v in pairs(this.Inputs) do + if k ~= "" then + table.insert(ret, k) + end + end + return ret +end + +--- Returns an array of 's wire output names +e2function array entity:getWireOutputs() + if not IsValid(this) or not isOwner(self, this) or not this.Outputs then return {} end + local ret = {} + for k,v in pairs(this.Outputs) do + if k ~= "" then + table.insert(ret, k) + end + end + return ret +end + +__e2setcost(5) + +--- Returns 's entity wirelink +e2function wirelink entity:wirelink() + if not IsValid(this) then return nil end + if not isOwner(self, this) then return nil end + if not this.extended then + WireLib.CreateWirelinkOutput( self.player, this, {true} ) + end + return this +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/datasignal.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/datasignal.lua new file mode 100644 index 0000000..b94751c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/datasignal.lua @@ -0,0 +1,431 @@ +--[[ +dataSignal optimized +Made by Divran + +dataSignals are a combination of signals and gvars. +Instead of using one to trigger the E2, and the other +to send the data, dataSignals can both trigger the E2 AND +send the data at the same time. + +Have fun! +]] + +local groups = {} +local queue = {} + +local isOwner = E2Lib.isOwner +local isFriend = E2Lib.isFriend +local getHash = E2Lib.getHash +local copy = table.Copy +local remove = table.remove +local c = math.Clamp +local f = math.floor + +--------------------------------------------- +-- Lua helper functions +--------------------------------------------- + + +--------------------------------------------- +-- IsAllowed +-- Check if an E2 is allowed to send a signal to another E2 +--------------------------------------------- +--[[ + When sending: + Scope = 0 -> Only send to E2s you own. + Scope = 1 -> Send to E2s you own and to people who have you in their PP friends list. + Scope = 2 -> Send to everyone. + + When receiving: + Scope = 0 -> Only receive from E2s you own. + Scope = 1 -> Receive from E2s you own and from people who have you in their PP friends list. + Scope = 2 -> Receive from everyone. +]] + +local function isOwner( froment, toent ) -- we need a more strict isOwner than E2's default implementation, which also checks isFriend + return froment.player == toent.player +end + +local function IsAllowed( froment, toent, fromscope, signaltype ) + if not froment or not froment:IsValid() or froment:GetClass() ~= "gmod_wire_expression2" then return false end + if not toent or not toent:IsValid() or toent:GetClass() ~= "gmod_wire_expression2" then return false end + + if signaltype ~= "direct" and froment == toent then return false end -- Can't send to the same E2 (if it's a group signal) + + local toscope = toent.context.data.datasignal.scope + + if (fromscope == 2 and toscope == 2) or isOwner(froment, toent) then -- both scopes are 2 or the receiver E2 is yours + return true + elseif fromscope == 2 and toscope == 1 and isFriend( toent.player, froment.player ) then -- if sending to everyone, and receiving from only friends, check if receiver is friend with sender + return true + elseif fromscope == 1 then -- send only to friends + if toscope == 2 then -- receiving from all, check only if sender is friend with receiver + return isFriend( froment.player, toent.player ) + elseif toscope == 1 then -- receiving from friends, check both if sender is friend with receiver and if receiver is friend with sender + return isFriend( froment.player, toent.player ) and + isFriend( toent.player, froment.player ) + end + end + + return false -- Any other outcome is false +end + +--------------------------------------------- +-- processQueue +--------------------------------------------- + +local function processQueue() + if #queue == 0 then return end + + local temp = queue + local size = #queue + queue = {} + + for i=1,#temp do + local currentsignal = temp[i] + + if not currentsignal.from or not currentsignal.from:IsValid() then continue end + if not currentsignal.to or not currentsignal.to:IsValid() then continue end + + currentsignal.to.context.data.currentsignal = currentsignal + currentsignal.to:Execute() + currentsignal.to.context.data.currentsignal = nil + end + + if next(queue) ~= nil then + timer.Simple( 0, processQueue ) + end +end + +registerCallback("postexecute",function(self) + if self.entity.removing then + processQueue() + else + timer.Simple( 0, processQueue ) + end +end) + +--------------------------------------------- +-- addQueue +-- Add an item to the queue +--------------------------------------------- +local function addQueue( item ) + queue[#queue+1] = item +end + + +--------------------------------------------- +-- sendSignalToE2 +-- Check if any signals are in the queue, waiting to be sent +-- Returns true on success, false on failure +--------------------------------------------- +local function sendSignalToE2( from, fromscope, to, signalname, groupname, var, vartype, signaltype ) + signaltype = signaltype or "direct" -- default to "direct" + + if not IsAllowed( from, to, fromscope, signaltype ) then return false end + + from.context.prf = from.context.prf + 80 + + local item = { + from = from, + fromscope = fromscope, + to = to, + name = signalname, + groupname = groupname, + var = var, + vartype = vartype, + hash = getHash( from.context, from.buffer ), + } + + addQueue( item ) + return true +end + +--------------------------------------------- +-- sendSignalToGroup +-- Sends a signal to every valid target in a group +-- Returns false if any one (or all) fails, else true +--------------------------------------------- +local function sendSignalToGroup( from, fromscope, signalname, groupname, var, vartype ) + local group = groups[groupname] + if not group then return 0 end + + local ret = true + for e2,_ in pairs( group ) do + if not sendSignalToE2( from, fromscope, e2, signalname, groupname, var, vartype, "group" ) then + ret = false + end + end + + return ret +end + +--------------------------------------------- +-- probeGroup +-- Returns an array of E2s that would receive the signal if it was sent to the specified target +--------------------------------------------- +local function probeGroup( from, fromscope, groupname ) + local ret = {} + + if groups[groupname] then + for e2,_ in pairs( groups[groupname] ) do + if IsAllowed( from, e2, fromscope, "group" ) then + ret[#ret+1] = e2 + end + end + end + + return ret +end + +--------------------------------------------- +-- joinGroup +-- Make an e2 join a group +--------------------------------------------- +local function joinGroup( self, groupname ) + -- Is the E2 already in that group? + local grps = self.data.datasignal.groups + for i=1,#grps do + if grps[i] == groupname then return end + end + + -- Else add it + grps[#grps+1] = groupname + + -- If that group does not exist, create it + if not groups[groupname] then + groups[groupname] = {} + end + + -- Add the E2 to that group + groups[groupname][self.entity] = true +end + +--------------------------------------------- +-- leaveGroup +-- Make an e2 leave a group +--------------------------------------------- +local function leaveGroup( self, groupname ) + -- Is the E2 in that group? + local grps = self.data.datasignal.groups + local found + for i=1,#grps do + if grps[i] == groupname then found = i break end + end + if not found then return end + + -- Else remove it + remove( grps, found ) + + -- Remove the E2 from the group + groups[groupname][self.entity] = nil + + -- If there are no more E2s in this group, remove it + if (next(groups[groupname]) == nil) then + groups[groupname] = nil + end +end + +-- Upperfirst, used by the E2 functions below +local function upperfirst( word ) + return word:Left(1):upper() .. word:Right(-2):lower() +end + +--------------------------------------------- +-- E2 functions +--------------------------------------------- + +local non_allowed_types = { xgt = true } -- If anyone can think of any other types that should never be allowed, enter them here. + +local function fixdefault( var ) + return istable(var) and copy(var) or var +end + +registerCallback("postinit",function() + -- Add support for EVERY SINGLE type. Yeah!! + for k,v in pairs( wire_expression_types ) do + if not non_allowed_types[v[1]] then + + if (k == "NORMAL") then k = "NUMBER" end + k = string.lower(k) + + __e2setcost(10) + + -- Send a signal directly to another E2 + registerFunction("dsSendDirect","se"..v[1],"n",function(self,args) + local op1, op2, op3 = args[2], args[3], args[4] + local signalname, to, var = op1[1](self, op1),op2[1](self, op2),op3[1](self,op3) + return sendSignalToE2( self.entity, 2, to, signalname, "", var, k ) and 1 or 0 + end) + + __e2setcost(15) + + registerFunction("dsSendDirect","sr"..v[1],"n",function(self,args) + local op1, op2, op3 = args[2], args[3], args[4] + local signalname, array, var = op1[1](self, op1),op2[1](self, op2),op3[1](self,op3) + local ret = 1 + for i=1,#array do + if not sendSignalToE2( self.entity, 2, array[i], signalname, "", var, k ) then + ret = 0 + end + end + return ret + end) + + __e2setcost(20) + + -- Send a ds to the group in the E2s scope + registerFunction("dsSend","ss"..v[1],"n",function(self,args) + local op1, op2, op3 = args[2], args[3], args[4] + local signalname, groupname, var = op1[1](self, op1),op2[1](self, op2),op3[1](self,op3) + + return sendSignalToGroup( self.entity, self.data.datasignal.scope, signalname, groupname, var, k ) and 1 or 0 + end) + + -- Send a ds to the group in scope + registerFunction("dsSend","ssn"..v[1],"n",function(self,args) + local op1, op2, op3, op4 = args[2], args[3], args[4], args[5] + local signalname, groupname, scope, var = op1[1](self, op1),op2[1](self, op2),op3[1](self,op3),op4[1](self,op4) + + return sendSignalToGroup( self.entity, scope, signalname, groupname, var, k ) and 1 or 0 + end) + + __e2setcost(5) + + -- Get variable + registerFunction("dsGet" .. upperfirst( k ), "", v[1], function(self,args) + if not self.data.currentsignal or self.data.currentsignal.vartype ~= k then return fixdefault(v[2]) end + return self.data.currentsignal.var + end) + + end -- allowed check + end -- loop +end) -- postinit + +__e2setcost(10) + +-- Leave all groups +e2function void dsClearGroups() + for i=1,#self.data.datasignal.groups do + local name = self.data.datasignal.groups[i] + if (groups[name]) then + if (groups[name][self.entity] == true) then + groups[name][self.entity] = nil + end + if (next(groups[name]) == nil) then + groups[name] = nil + end + end + end + self.data.datasignal.groups = {} +end + +-- Join group +e2function void dsJoinGroup( string groupname ) + joinGroup( self, groupname ) +end + +-- Leave group +e2function void dsLeaveGroup( string groupname ) + leaveGroup( self, groupname ) +end + +__e2setcost(5) + +-- Get all groups in an array +e2function array dsGetGroups() + return self.data.datasignal.groups or {} +end + +-- 0 = only you, 1 = only pp friends, 2 = everyone +e2function void dsSetScope( number scope ) + self.data.datasignal.scope = c(f(scope),0,2) +end + +-- Get current scope +e2function number dsGetScope() + return self.data.datasignal.scope +end + +__e2setcost(1) + +-- Check if the current execution was caused by ANY datasignal +e2function number dsClk() + return self.data.currentsignal ~= nil and 1 or 0 +end + +-- Check if the current execution was caused by a datasignal named +e2function number dsClk( string name ) + if not self.data.currentsignal then return 0 end + return self.data.currentsignal.name == name and 1 or 0 +end + +-- Returns the name of the current signal +e2function string dsClkName() + if not self.data.currentsignal then return "" end + return self.data.currentsignal.name +end + +-- Get the type of the current data +e2function string dsGetType() + if not self.data.currentsignal then return "" end + return self.data.currentsignal.vartype +end + +-- Get the E2 that sent the signal +e2function entity dsGetSender() + if not self.data.currentsignal then return end + return self.data.currentsignal.from +end + +-- Get the group which the signal was sent to +e2function string dsGetGroup() + if not self.data.currentsignal then return "" end + return self.data.currentsignal.groupname +end + +-- Get the hash of the sending E2 +e2function number dsGetHash() + if not self.data.currentsignal then return "" end + return self.data.currentsignal.hash +end + +__e2setcost(20) + +-- Get all E2s which would have received a signal if you had sent it to this group and the E2s scope +e2function array dsProbe( string groupname ) + return probeGroup( self.entity, self.data.datasignal.scope, groupname ) +end + +-- Get all E2s which would have received a signal if you had sent it to this group and scope +e2function array dsProbe( string groupname, number scope ) + return probeGroup( self.entity, c(f(scope),0,2), groupname ) +end + + +--------------------------------------------- +-- Construct & Destruct +--------------------------------------------- + +-- When an E2 is removed, clear it from the groups table +registerCallback("destruct",function(self) + if (self.data.datasignal.groups) then + if (#self.data.datasignal.groups > 0) then + for k,v in pairs( self.data.datasignal.groups ) do + if (groups[v]) then + groups[v][self.entity] = nil + if (next(groups[v]) == nil) then + groups[v] = nil + end + end + end + end + end +end) + +-- When an E2 is spawned, set its group and scope to the defaults +registerCallback("construct",function(self) + self.data.datasignal = {} + self.data.datasignal.groups = {} + self.data.datasignal.scope = 0 +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/debug.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/debug.lua new file mode 100644 index 0000000..06f5405 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/debug.lua @@ -0,0 +1,403 @@ +-- import some e2lib and math functions +local IsValid = IsValid +local isOwner = E2Lib.isOwner +local Clamp = math.Clamp +local seq = table.IsSequential + +/******************************************************************************/ + +local function checkOwner(self) + return IsValid(self.player); +end + +/******************************************************************************/ + +-- default delay for printing messages, adds one "charge" after this delay +local defaultPrintDelay = 0.3 +-- the amount of "charges" a player has by default +local defaultMaxPrints = 15 + +-- Contains the amount of "charges" a player has, i.e. the amount of print-statements can be executed before +-- the messages being omitted. The defaultPrintDelay is the time required to add one additional charge to the +-- player's account. The defaultMaxPrints variable are the charges the player starts with. +local printDelays = {} + +-- Returns the table containing the player's charges or creatis if it it does not yet exist +-- @param ply player to get the table from, not validated +-- @param maxCharges amount of charges to set it the table has to be created +-- @param chargesDelay delay until a new charge is given, set it the table has to be created +local function getDelaysOrCreate(ply, maxCharges, chargesDelay) + local printDelay = printDelays[ply] + + if not printDelay then + -- if the player does not have an entry yet, add it + printDelay = { numCharges = maxCharges, lastTime = CurTime() } + printDelays[ply] = printDelay + end + + return printDelay +end + +-- Returns whether or not a player has "charges" for printing a message +-- Additionally adds all new charges the player might have +-- @param ply player to check, not validated +local function canPrint(ply) + -- update the console variables just in case + local maxCharges = ply:GetInfoNum("wire_expression2_print_max", defaultMaxPrints) + local chargesDelay = ply:GetInfoNum("wire_expression2_print_delay", defaultPrintDelay) + + local printDelay = getDelaysOrCreate(ply, maxCharges, chargesDelay) + + local currentTime = CurTime() + if printDelay.numCharges < maxCharges then + -- check if the player "deserves" new charges + local timePassed = (currentTime - printDelay.lastTime) + local chargesToAdd = math.floor(timePassed / chargesDelay) + printDelay.numCharges = printDelay.numCharges + chargesToAdd + -- add "semi" charges the player might already have + printDelay.lastTime = (currentTime - (timePassed % chargesDelay)) + end + -- we should clamp his charges for safety + if printDelay.numCharges > maxCharges then + printDelay.numCharges = maxCharges + -- remove the "semi" charges, otherwise the player has too many + printDelay.lastTime = currentTime + end + + return printDelay and printDelay.numCharges > 0 +end + +-- Returns whether or not a player can currently print a message or if it will be omitted by the antispam +-- Additionally removes one charge from the player's account +-- @param ply player to check, is not validated +local function checkDelay(ply) + -- update the console variables just in case + local maxCharges = ply:GetInfoNum("wire_expression2_print_max", defaultMaxPrints) + local chargesDelay = ply:GetInfoNum("wire_expression2_print_delay", defaultPrintDelay) + + local printDelay = getDelaysOrCreate(ply, maxCharges, chargesDelay) + + if not canPrint(ply) then + return false + else + printDelay.numCharges = printDelay.numCharges - 1 + return true + end +end + +hook.Add("PlayerDisconnected", "e2_print_delays_player_dc", function(ply) printDelays[ply] = nil end) + +/******************************************************************************/ + +-- Returns whether or not the next print-message will be printed or omitted by antispam +e2function number playerCanPrint() + if not checkOwner(self) then return end + return (canPrint(self.player) and 1 or 0) +end + +local function SpecialCase( arg ) + if istable(arg) then + if (arg.isfunction) then + return "function " .. arg[3] .. " = (" .. arg[2] .. ")" + elseif (seq(arg)) then -- A table with only numerical indexes + local str = "[" + for k,v in ipairs( arg ) do + if istable(v) then + if (k != #arg) then + str = str .. SpecialCase( v ) .. "," + else + str = str .. SpecialCase( v ) .. "]" + end + else + if (k != #arg) then + str = str .. tostring(v) .. "," + else + str = str .. tostring(v) .. "]" + end + end + end + return str + else -- Else it's a table with string indexes (which this function can't handle) + return "[table]" + end + end +end + +-- Prints <...> like lua's print(...), except to the chat area +e2function void print(...) + if not checkOwner(self) then return end + if not checkDelay( self.player ) then return end + local args = {...} + if #args>0 then + local text = "" + for k,v in ipairs( args ) do + text = text .. (SpecialCase( v ) or tostring(v)) .. "\t" + end + if (text and #text>0) then + self.player:ChatPrint(string.Left(text,249)) -- Should we switch to net messages? We probably don't want to print more than 249 chars at once anyway + end + end +end + +--- Posts to the chat area. (deprecated due to print(...)) +--e2 function void print(string text) +-- self.player:ChatPrint(text) +--end + +--- Posts a string to the chat of 's driver. Returns 1 if the text was printed, 0 if not. +e2function number entity:printDriver(string text) + if not IsValid(this) then return 0 end + if not this:IsVehicle() then return 0 end + if not isOwner(self, this) then return 0 end + if text:find('"', 1, true) then return 0 end + + local driver = this:GetDriver() + if not IsValid(driver) then return 0 end + + if not checkDelay( self.player ) then return 0 end + + driver:ChatPrint(text) + return 1 +end + +/******************************************************************************/ + +--- Displays a hint popup with message for seconds ( being clamped between 0.7 and 7). +e2function void hint(string text, duration) + if not IsValid(self.player) then return end + if not checkDelay( self.player ) then return end + WireLib.AddNotify(self.player, text, NOTIFY_GENERIC, Clamp(duration,0.7,7)) +end + +--- Displays a hint popup to the driver of vehicle E, with message for seconds ( being clamped between 0.7 and 7). Same return value as printDriver. +e2function number entity:hintDriver(string text, duration) + if not IsValid(this) then return 0 end + if not this:IsVehicle() then return 0 end + if not isOwner(self, this) then return 0 end + + local driver = this:GetDriver() + if not IsValid(driver) then return 0 end + + if not checkDelay( self.player ) then return 0 end + + WireLib.AddNotify(driver, text, NOTIFY_GENERIC, Clamp(duration,0.7,7)) + return 1 +end + +/******************************************************************************/ + +local valid_print_types = {} +for _,cname in ipairs({ "HUD_PRINTCENTER", "HUD_PRINTCONSOLE", "HUD_PRINTNOTIFY", "HUD_PRINTTALK" }) do + local value = _G[cname] + valid_print_types[value] = true + E2Lib.registerConstant(cname, value) +end + +--- Same as print(), but can make the text show up in different places. can be one of the following: _HUD_PRINTCENTER, _HUD_PRINTCONSOLE, _HUD_PRINTNOTIFY, _HUD_PRINTTALK. +e2function void print(print_type, string text) + if (not checkOwner(self)) then return; end + if not valid_print_types[print_type] then return end + if not checkDelay( self.player ) then return end + + self.player:PrintMessage(print_type, text) +end + +--- Same as E:printDriver(), but can make the text show up in different places. can be one of the following: _HUD_PRINTCENTER, _HUD_PRINTCONSOLE, _HUD_PRINTNOTIFY, _HUD_PRINTTALK. +e2function number entity:printDriver(print_type, string text) + if not IsValid(this) then return 0 end + if not this:IsVehicle() then return 0 end + if not isOwner(self, this) then return 0 end + if not valid_print_types[print_type] then return 0 end + if text:find('"', 1, true) then return 0 end + + local driver = this:GetDriver() + if not IsValid(driver) then return 0 end + + if not checkDelay( self.player ) then return 0 end + + driver:PrintMessage(print_type, text) + return 1 +end + +/******************************************************************************/ + +-- helper stuff for printTable +local PrintTableToString +do + local msgbuf = {} + local function Msg(s) + table.insert(msgbuf, s) + end + + -- From: https://raw.githubusercontent.com/garrynewman/garrysmod/ced7ae207d60af3f77779b30630cce91029e1981/garrysmod/lua/includes/util.lua + local PrintTable + PrintTable = function( t, indent, done ) + + done = done or {} + indent = indent or 0 + local keys = table.GetKeys( t ) + + table.sort( keys, function( a, b ) + if ( isnumber( a ) && isnumber( b ) ) then return a < b end + return tostring( a ) < tostring( b ) + end ) + + for i = 1, #keys do + local key = keys[ i ] + local value = t[ key ] + Msg( string.rep( "\t", indent ) ) + + if ( istable( value ) && !done[ value ] ) then + + done[ value ] = true + Msg( tostring( key ) .. ":" .. "\n" ) + PrintTable ( value, indent + 2, done ) + done[ value ] = nil + + else + + Msg( tostring( key ) .. "\t=\t" ) + Msg( tostring( value ) .. "\n" ) + + end + + end + + end + PrintTableToString = function(...) + msgbuf = {} + PrintTable(...) + return table.concat(msgbuf) + end +end + +--- Prints an array like the lua function [[G.PrintTable|PrintTable]] does, except to the chat area. +e2function void printTable(array arr) + if not checkOwner(self) then return end + if not checkDelay( self.player ) then return end + + for _,line in ipairs(string.Explode("\n",PrintTableToString(arr))) do + self.player:ChatPrint(line) + end +end + +-- The printTable(T) function is in table.lua because it uses a local function + +/******************************************************************************/ + +__e2setcost(100) + +util.AddNetworkString("wire_expression2_printColor") + +local printColor_typeids = { + n = tostring, + s = tostring, + v = function(v) return Color(v[1],v[2],v[3]) end, + xv4 = function(v) return Color(v[1],v[2],v[3],v[4]) end, + e = function(e) return IsValid(e) and e:IsPlayer() and e or "" end, +} + +local function printColorVarArg(chip, ply, console, typeids, ...) + if not IsValid(ply) then return end + if not checkDelay(ply) then return end + local send_array = { ... } + + for i,tp in ipairs(typeids) do + if printColor_typeids[tp] then + send_array[i] = printColor_typeids[tp](send_array[i]) + else + send_array[i] = "" + end + end + + net.Start("wire_expression2_printColor") + net.WriteEntity(chip) + net.WriteBool(console) + net.WriteTable(send_array) + net.Send(ply) +end + +local printColor_types = { + number = tostring, + string = tostring, + Vector = function(v) return Color(v[1],v[2],v[3]) end, + table = function(tbl) + for i,v in pairs(tbl) do + if !isnumber(i) then return "" end + if !isnumber(v) then return "" end + if i < 1 or i > 4 then return "" end + end + return Color(tbl[1] or 0, tbl[2] or 0,tbl[3] or 0,tbl[4]) + end, + Player = function(e) return IsValid(e) and e:IsPlayer() and e or "" end, +} + +local function printColorArray(chip, ply, console, arr) + if (not IsValid(ply)) then return; end + if not checkDelay( ply ) then return end + + local send_array = {} + + for i,tp in ipairs_map(arr,type) do + if printColor_types[tp] then + send_array[i] = printColor_types[tp](arr[i]) + else + send_array[i] = "" + end + end + + net.Start("wire_expression2_printColor") + net.WriteEntity(chip) + net.WriteBool(console) + net.WriteTable(send_array) + net.Send(ply) +end + + +--- Works like [[chat.AddText]](...). Parameters can be any amount and combination of numbers, strings, player entities, color vectors (both 3D and 4D). +e2function void printColor(...) + printColorVarArg(nil, self.player, false, typeids, ...) +end + +--- Like printColor(...), except taking an array containing all the parameters. +e2function void printColor(array arr) + printColorArray(nil, self.player, false, arr) +end + +--- Works like MsgC(...). Parameters can be any amount and combination of numbers, strings, player entities, color vectors (both 3D and 4D). +e2function void printColorC(...) + printColorVarArg(nil, self.player, true, typeids, ...) +end + +--- Like printColorC(...), except taking an array containing all the parameters. +e2function void printColorC(array arr) + printColorArray(nil, self.player, true, arr) +end + +--- Like printColor(...), except printing in 's driver's chat area instead of yours. +e2function void entity:printColorDriver(...) + if not IsValid(this) then return end + if not this:IsVehicle() then return end + if not isOwner(self, this) then return end + + local driver = this:GetDriver() + if not IsValid(driver) then return end + + if not checkDelay( self.player ) then return end + + printColorVarArg(self.entity, driver, false, typeids, ...) +end + +--- Like printColor(R), except printing in 's driver's chat area instead of yours. +e2function void entity:printColorDriver(array arr) + if not IsValid(this) then return end + if not this:IsVehicle() then return end + if not isOwner(self, this) then return end + + local driver = this:GetDriver() + if not IsValid(driver) then return end + + if not checkDelay( self.player ) then return end + + printColorArray(self.entity, driver, false, arr) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/e2doc.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/e2doc.lua new file mode 100644 index 0000000..6c4b8b3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/e2doc.lua @@ -0,0 +1,151 @@ +local eliminate_varname_conflicts = true + +if not e2_parse_args then include("extpp.lua") end + +local readfile = readfile or function(filename) + return file.Read("entities/gmod_wire_expression2/core/" .. filename, "LUA") +end +local writefile = writefile or function(filename, contents) + print("--- Writing to file 'data/e2doc/" .. filename .. "' ---") + return file.Write("e2doc/" .. filename, contents) +end +local p_typename = "[a-z][a-z0-9]*" +local p_typeid = "[a-z][a-z0-9]?[a-z0-9]?[a-z0-9]?[a-z0-9]?" +local p_argname = "[a-zA-Z][a-zA-Z0-9]*" +local p_funcname = "[a-z][a-zA-Z0-9]*" +local p_func_operator = "[-a-zA-Z0-9+*/%%^=!><&|$_]*" + +local function ltrim(s) + return string.match(s, "^%s*(.-)$") +end + +local function rtrim(s) + return string.match(s, "^(.-)%s*$") +end + +local function trim(s) + return string.match(s, "^%s*(.-)%s*$") +end + +local mess_with_args + +function mess_with_args(args, desc, thistype) + local args_referenced = string.match(desc, "<" .. p_argname .. ">") + local argtable, ellipses = e2_parse_args(args) + local indices = {} + if thistype ~= "" then indices[string.upper(e2_get_typeid(thistype))] = 2 end + args = '' + for i, name in ipairs(argtable.argnames) do + local typeid = string.upper(argtable.typeids[i]) + + local index = "" + if args_referenced then + index = indices[typeid] + if index then + indices[typeid] = index + 1 + else + index = "" + indices[typeid] = 2 + end + end + local newname = typeid .. "" .. index .. "" + if index == "" then newname = typeid end + + desc = desc:gsub("<" .. name .. ">", "''" .. newname .. "''") + if i ~= 1 then args = args .. "," end + args = args .. newname + end + if ellipses then + if #argtable.argnames ~= 0 then args = args .. "," end + args = args .. "..." + end + return args, desc +end + +local function e2doc(filename, outfile) + if not outfile then + outfile = string.match(filename, "^.*%.") .. "txt" + end + local current = {} + local output = { '====Commands====\n:{|style="background:#E6E6FA"\n!align="left" width="150"| Function\n!align="left" width="60"| Returns\n!align="left" width="1000"| Description\n' } + local insert_line = true + for line in string.gmatch(readfile(filename), "%s*(.-)%s*\n") do + if line:sub(1, 3) == "---" then + if line:match("[^-%s]") then table.insert(current, ltrim(line:sub(4))) end + elseif line:sub(1, 3) == "///" then + table.insert(current, ltrim(line:sub(4))) + elseif line:sub(1, 12) == "--[[********" or line:sub(1, 9) == "/********" then + if line:find("^%-%-%[%[%*%*%*%*%*%*%*%*+%]%]%-%-$") or line:find("^/%*%*%*%*%*%*%*%*+/$") then + insert_line = true + end + elseif line:sub(1, 10) == "e2function" then + local ret, thistype, colon, name, args = line:match("e2function%s+(" .. p_typename .. ")%s+([a-z0-9]-)%s*(:?)%s*(" .. p_func_operator .. ")%(([^)]*)%)") + if thistype ~= "" and colon == "" then error("E2doc syntax error: Function names may not start with a number.", 0) end + if thistype == "" and colon ~= "" then error("E2doc syntax error: No type for 'this' given.", 0) end + if thistype:sub(1, 1):find("[0-9]") then error("E2doc syntax error: Type names may not start with a number.", 0) end + + desc = table.concat(current, "
") + current = {} + + if name:sub(1, 8) ~= "operator" and not desc:match("@nodoc") then + if insert_line then + table.insert(output, '|-\n| bgcolor="SteelBlue" | || bgcolor="SteelBlue" | || bgcolor="SteelBlue" | \n') + insert_line = false + end + args, desc = mess_with_args(args, desc, thistype) + + if ret == "void" then + ret = "" + else + ret = string.upper(e2_get_typeid(ret)) + end + + if thistype ~= "" then + thistype = string.upper(e2_get_typeid(thistype)) + desc = desc:gsub("", "''" .. thistype .. "''") + thistype = thistype .. ":" + end + table.insert(output, string.format("|-\n|%s%s(%s) || %s || ", thistype, name, args, ret)) + --desc = desc:gsub("<([^<>]+)>", "''%1''") + table.insert(output, desc) + table.insert(output, "\n") + end + end + end -- for line + output = table.concat(output) .. "|}\n" + print(output) + writefile(outfile, output) +end + +-- Add a client-side "e2doc" console command +if SERVER then + AddCSLuaFile() + e2doc = nil +elseif CLIENT then + concommand.Add("e2doc", + function(player, command, args) + if not file.IsDir("e2doc", "DATA") then file.CreateDir("e2doc") end + if not file.IsDir("e2doc/custom", "DATA") then file.CreateDir("e2doc/custom") end + + local path = string.match(args[2] or args[1], "^%s*(.+)/") + if path and not file.IsDir("e2doc/" .. path, "DATA") then file.CreateDir("e2doc/" .. path) end + + e2doc(args[1], args[2]) + end, + function(commandName, args) -- autocomplete function + args = string.match(args, "^%s*(.-)%s*$") + local path = string.match(args, "^%s*(.+/)") or "" + local files = file.Find("entities/gmod_wire_expression2/core/" .. args .. "*", "LUA") + local ret = {} + for _, v in ipairs(files) do + if string.sub(v, 1, 1) ~= "." then + if file.IsDir("entities/gmod_wire_expression2/core/" .. path .. v, "LUA") then + table.insert(ret, "e2doc " .. path .. v .. "/") + else + table.insert(ret, "e2doc " .. path .. v) + end + end + end + return ret + end) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/e2lib.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/e2lib.lua new file mode 100644 index 0000000..0971f8c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/e2lib.lua @@ -0,0 +1,726 @@ +AddCSLuaFile() + +E2Lib = {} + +local type = type +local function checkargtype(argn, value, argtype) + if type(value) ~= argtype then error(string.format("bad argument #%d to 'E2Lib.%s' (%s expected, got %s)", argn, debug.getinfo(2, "n").name, argtype, type(value)), 2) end +end + +-- -------------------------- Helper functions ----------------------------- +local unpack = unpack +local IsValid = IsValid + +-- This functions should not be used in functions that tend to be used very often, as it is slower than getting the arguments manually. +function E2Lib.getArguments(self, args) + local ret = {} + for i = 2, #args[7] + 1 do + ret[i - 1] = args[i][1](self, args[i]) + end + return unpack(ret) +end + +-- Backwards compatibility +E2Lib.isnan = WireLib.isnan +E2Lib.clampPos = WireLib.clampPos +E2Lib.setPos = WireLib.setPos +E2Lib.setAng = WireLib.setAng + +function E2Lib.setMaterial(ent, material) + material = WireLib.IsValidMaterial(material) + ent:SetMaterial(material) + duplicator.StoreEntityModifier(ent, "material", { MaterialOverride = material }) +end + +function E2Lib.setSubMaterial(ent, index, material) + index = math.Clamp(index, 0, 255) + material = WireLib.IsValidMaterial(material) + ent:SetSubMaterial(index, material) + duplicator.StoreEntityModifier(ent, "submaterial", { ["SubMaterialOverride_"..index] = material }) +end + +-- getHash +-- Returns a hash for the given string + +-- local str_byte = string.byte +-- local str_sub = string.sub +local util_CRC = util.CRC +local tonumber = tonumber +function E2Lib.getHash(self, data) + --[[ + -- Thanks to emspike for this code + self.prf = self.prf + #data + local a, b = 1, 0 + for i = 1, #data do + a = (a + str_byte(str_sub(data,i,i))) % 65521 + b = (b + a) % 65521 + end + return b << 16 | a + -- but we're going to use Garry's function, since it's most likely done in C++, so it's probably faster. + -- For some reason, Garry's util.CRC returns a string... but it's always a number, so tonumbering it should work. + -- I'm making it default to -1 if it for some reason throws a letter in there, breaking tonumber. + ]] -- + + if self then self.prf = self.prf + #data / 10 end + return tonumber(util_CRC(data)) or -1 +end + +-- -------------------------- signature generation ----------------------------- + +function E2Lib.typeName(typeid) + if typeid == "" then return "void" end + if typeid == "n" then return "number" end + + local tp = wire_expression_types2[typeid] + if not tp then error("Type ID '" .. typeid .. "' not found", 2) end + + local typename = tp[1]:lower() + return typename or "unknown" +end + +function E2Lib.splitType(args) + local ret = {} + local thistype + local i = 1 + while i <= #args do + local letter = args:sub(i, i) + if letter == ":" then + if #ret ~= 1 then error("Misplaced ':' in args", 2) end + thistype = ret[1] + ret = {} + elseif letter == "." then + if args:sub(i) ~= "..." then error("Misplaced '.' in args", 2) end + table.insert(ret, "...") + i = i + 2 + elseif letter == "=" then + if #ret ~= 1 then error("Misplaced '=' in args", 2) end + ret = {} + else + local typeid = letter + if letter == "x" then + typeid = args:sub(i, i + 2) + i = i + 2 + end + table.insert(ret, E2Lib.typeName(typeid)) + end + i = i + 1 + end + return thistype, ret +end + +-- given a function signature like "setNumber(xwl:sn)" and an optional return typeid, generates a nice, readable signature +function E2Lib.generate_signature(signature, rets, argnames) + local funcname, args = string.match(signature, "([^(]+)%(([^)]*)%)") + if not funcname then error("malformed signature") end + + local thistype, args = E2Lib.splitType(args) + + if argnames then + for i = 1, #args do + if argnames[i] then args[i] = args[i] .. " " .. argnames[i] end + end + end + local new_signature = string.format("%s(%s)", funcname, table.concat(args, ",")) + if thistype then new_signature = thistype .. ":" .. new_signature end + + return (not rets or rets == "") and (new_signature) or (E2Lib.typeName(rets) .. "=" .. new_signature) +end + +-- ------------------------ various entity checkers ---------------------------- + +-- replaces an E2Lib function (ex.: isOwner) and notifies plugins +function E2Lib.replace_function(funcname, func) + checkargtype(1, funcname, "string") + checkargtype(2, func, "function") + + local oldfunc = E2Lib[funcname] + if not isfunction(oldfunc) then error("No E2Lib function by the name " .. funcname .. " found.", 2) end + E2Lib[funcname] = func + wire_expression2_CallHook("e2lib_replace_function", funcname, func, oldfunc) +end + +function E2Lib.validPhysics(entity) + if IsValid(entity) then + if entity:IsWorld() then return false end + if entity:GetMoveType() ~= MOVETYPE_VPHYSICS then return false end + return entity:GetPhysicsObject():IsValid() + end + return false +end + +-- This function gets wrapped when CPPI is detected, see very end of this file +function E2Lib.getOwner(self, entity) + if entity == nil then return end + if entity == self.entity or entity == self.player then return self.player end + if entity.GetPlayer then + local ply = entity:GetPlayer() + if IsValid(ply) then return ply end + end + + local OnDieFunctions = entity.OnDieFunctions + if OnDieFunctions then + if OnDieFunctions.GetCountUpdate then + if OnDieFunctions.GetCountUpdate.Args then + if OnDieFunctions.GetCountUpdate.Args[1] then return OnDieFunctions.GetCountUpdate.Args[1] end + end + end + if OnDieFunctions.undo1 then + if OnDieFunctions.undo1.Args then + if OnDieFunctions.undo1.Args[2] then return OnDieFunctions.undo1.Args[2] end + end + end + end + + if entity.GetOwner then + local ply = entity:GetOwner() + if IsValid(ply) then return ply end + end + + return nil +end + +function E2Lib.abuse(ply) + ply:Kick("Be good and don't abuse -- sincerely yours, the E2") + error("abuse", 0) +end + +-- This function gets replaced when CPPI is detected, see very end of this file +function E2Lib.isFriend(owner, player) + return owner == player +end + +function E2Lib.isOwner(self, entity) + if game.SinglePlayer() then return true end + local player = self.player + local owner = E2Lib.getOwner(self, entity) + if not IsValid(owner) then return false end + + return E2Lib.isFriend(owner, player) +end + +local isOwner = E2Lib.isOwner + +-- Checks whether the player is the chip's owner or in a pod owned by the chip's owner. Assumes that ply is really a player. +function E2Lib.canModifyPlayer(self, ply) + if ply == self.player then return true end + + if not IsValid(ply) then return false end + if not ply:IsPlayer() then return false end + + local vehicle = ply:GetVehicle() + if not IsValid(vehicle) then return false end + return isOwner(self, vehicle) +end + +-- ------------------------ type guessing ------------------------------------------ + +local type_lookup = { + number = "n", + string = "s", + Vector = "v", + PhysObj = "b", +} +local table_length_lookup = { + [2] = "xv2", + [3] = "v", + [4] = "xv4", + [9] = "m", + [16] = "xm4", +} + +function E2Lib.guess_type(value) + local vtype = type(value) + if type_lookup[vtype] then return type_lookup[vtype] end + if IsValid(value) then return "e" end + if value.EntIndex then return "e" end + if vtype == "table" then + if table_length_lookup[#value] then return table_length_lookup[#value] end + if value.HitPos then return "xrd" end + end + + for typeid, v in pairs(wire_expression_types2) do + if v[5] then + local ok = pcall(v[5], value) + if ok then return typeid end + end + end + + -- TODO: more type guessing here + + return "" -- empty string = unknown type, for now. +end + +-- Types that cannot possibly be guessed correctly: +-- angle (will be reported as vector) +-- matrix2 (will be reported as vector4) +-- wirelink (will be reported as entity) +-- complex (will be reported as vector2) +-- quaternion (will be reported as vector4) +-- all kinds of nil stuff + +-- ------------------------ list filtering ------------------------------------------------- + +function E2Lib.filterList(list, criterion) + local index = 1 + -- print("-- filterList: "..#list.." entries --") + + while index <= #list do + if not criterion(list[index]) then + -- MsgC(Color(128,128,128), "- "..tostring(list[index]).."\n") + list[index] = list[#list] + table.remove(list) + else + -- print(string.format("+%3d %s", index, tostring(list[index]))) + index = index + 1 + end + end + + -- print("--------") + return list +end + +-- ----------------------------- compiler stuf --------------------------------- + +-- A function suitable for use as xpcall's error handler. If the error is +-- generated by Compiler:Error, Parser:Error, etc., then the string will be a +-- usable error message. If not, then it's an error not caused by an error in +-- user code, and so we dump a stack trace to the console to help debug it. +function E2Lib.errorHandler(message) + if string.match(message, " at line ") then return message end + + print("Internal error - please report to https://github.com/wiremod/wire/issues") + print(message) + debug.Trace() + return "Internal error, see console for more details" +end + +E2Lib.optable_inv = { + add = "+", + sub = "-", + mul = "*", + div = "/", + mod = "%", + exp = "^", + ass = "=", + aadd = "+=", + asub = "-=", + amul = "*=", + adiv = "/=", + inc = "++", + dec = "--", + eq = "==", + neq = "!=", + lth = "<", + geq = ">=", + leq = "<=", + gth = ">", + band = "&&", + bor = "||", + bxor = "^^", + bshr = ">>", + bshl = "<<", + ["not"] = "!", + ["and"] = "&", + ["or"] = "|", + qsm = "?", + col = ":", + def = "?:", + com = ",", + lpa = "(", + rpa = ")", + lcb = "{", + rcb = "}", + lsb = "[", + rsb = "]", + dlt = "$", + trg = "~", + imp = "->", +} + +E2Lib.optable = {} +for token, op in pairs(E2Lib.optable_inv) do + local current = E2Lib.optable + for i = 1, #op do + local c = op:sub(i, i) + local nxt = current[c] + if not nxt then + nxt = {} + current[c] = nxt + end + + if i == #op then + nxt[1] = token + else + if not nxt[2] then + nxt[2] = {} + end + + current = nxt[2] + end + end +end + +function E2Lib.printops() + local op_order = { ["+"] = 1, ["-"] = 2, ["*"] = 3, ["/"] = 4, ["%"] = 5, ["^"] = 6, ["="] = 7, ["!"] = 8, [">"] = 9, ["<"] = 10, ["&"] = 11, ["|"] = 12, ["?"] = 13, [":"] = 14, [","] = 15, ["("] = 16, [")"] = 17, ["{"] = 18, ["}"] = 19, ["["] = 20, ["]"] = 21, ["$"] = 22, ["~"] = 23 } + print("E2Lib.optable = {") + for k, v in pairs_sortkeys(E2Lib.optable, function(a, b) return (op_order[a] or math.huge) < (op_order[b] or math.huge) end) do + local tblstring = table.ToString(v) + tblstring = tblstring:gsub(",}", "}") + tblstring = tblstring:gsub("{(.)=", " {[\"%1\"] = ") + tblstring = tblstring:gsub(",(.)=", ", [\"%1\"] = ") + print(string.format("\t[%q] = %s,", k, tblstring)) + end + print("}") +end + +-- ------------------------------ string stuff --------------------------------- + +-- limits the given string to the given length and adds "..." to the end if too long. +function E2Lib.limitString(text, length) + checkargtype(1, text, "string") + checkargtype(2, length, "number") + + if #text <= length then + return text + else + return string.sub(text, 1, length) .. "..." + end +end + +do + local enctbl = {} + local dectbl = {} + + do + -- generate encode/decode lookup tables + -- local valid_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 +-*/#^!?~=@&|.,:(){}[]<>" -- list of "normal" chars that can be transferred without problems + local invalid_chars = "'\"\n\\%" + local hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' } + + + for i = 1, #invalid_chars do + local char = invalid_chars:sub(i, i) + enctbl[char] = true + end + for byte = 1, 255 do + dectbl[hex[(byte - byte % 16) / 16 + 1] .. hex[byte % 16 + 1]] = string.char(byte) + if enctbl[string.char(byte)] then + enctbl[string.char(byte)] = "%" .. hex[(byte - byte % 16) / 16 + 1] .. hex[byte % 16 + 1] + else + enctbl[string.char(byte)] = string.char(byte) + end + end + + --for i = 1, #valid_chars do + -- local char = valid_chars:sub(i, i) + -- enctbl[char] = char + --end + end + + -- escapes special characters + function E2Lib.encode(str) + return str:gsub(".", enctbl) + end + + -- decodes escaped characters + function E2Lib.decode(encoded) + return encoded:gsub("%%(..)", dectbl) + end +end + +-- ------------------------------- extensions ---------------------------------- + +do + -- Shared stuff, defined later. + + local extensions, printExtensions, conCommandSetExtensionStatus + + function E2Lib.GetExtensions() + return extensions.list + end + + function E2Lib.GetExtensionStatus(name) + name = name:Trim():lower() + return extensions.status[name] + end + + function E2Lib.GetExtensionDocumentation(name) + return extensions.documentation[name] or {} + end + + if SERVER then -- serverside stuff + + util.AddNetworkString( "wire_expression2_server_send_extensions_list" ) + util.AddNetworkString( "wire_expression2_client_request_print_extensions" ) + util.AddNetworkString( "wire_expression2_client_request_set_extension_status" ) + + function wire_expression2_PreLoadExtensions() + hook.Run( "Expression2_PreLoadExtensions" ) + extensions = { status = {}, list = {}, prettyList = {}, documentation = {} } + local list = sql.Query( "SELECT * FROM wire_expression2_extensions" ) + if list then + for i = 1, #list do + local row = list[ i ] + E2Lib.SetExtensionStatus( row.name, row.enabled ) + end + else + sql.Query( "CREATE TABLE wire_expression2_extensions ( name VARCHAR(32) PRIMARY KEY, enabled BOOLEAN )" ) + end + extensions.save = true + end + + function E2Lib.RegisterExtension(name, default, description, warning) + name = name:Trim():lower() + if extensions.status[ name ] == nil then + E2Lib.SetExtensionStatus( name, default ) + end + extensions.list[ #extensions.list + 1 ] = name + + if description or warning then + extensions.documentation[name] = { Description = description, Warning = warning } + end + + -- This line shouldn't be modified because it tells the parser that this extension is disabled, + -- thus making its functions not available in the E2 Editor (see function e2_include_pass2 in extloader.lua). + assert( extensions.status[ name ], "EXTENSION_DISABLED" ) + end + + function E2Lib.SetExtensionStatus( name, status ) + name = name:Trim():lower() + status = tobool( status ) + extensions.status[ name ] = status + if extensions.save then + sql.Query( "REPLACE INTO wire_expression2_extensions ( name, enabled ) VALUES ( " .. sql.SQLStr( name ) .. ", " .. ( status and 1 or 0 ) .. " )" ) + end + end + + -- After using E2Lib.SetExtensionStatus in an external script, this function should be called. + -- Its purpose is to update the clientside autocomplete list for the concommands. + function E2Lib.UpdateClientsideExtensionsList( ply ) + net.Start( "wire_expression2_server_send_extensions_list" ) + net.WriteTable(extensions) + if IsValid( ply ) then + net.Send( ply ) + else + net.Broadcast() + end + end + + local function buildPrettyList() + local function padLeft( str, len ) return (" "):rep( len - #str ) .. str end + local function padRight( str, len ) return str .. (" "):rep( len - #str ) end + local function padCenter( str, len ) return padRight( padLeft( str, math.floor( (len + #str) / 2 ) ), len ) end + + local list, column1, column2, columnsWidth = extensions.list, {}, {}, 0 + for i = 1, #list do + local name = list[ i ] + if #name > columnsWidth then columnsWidth = #name end + if extensions.status[ name ] == true then column1[ #column1 + 1 ] = name else column2[ #column2 + 1 ] = name end + end + local mainTitle, column1Title, column2Title = "E2 EXTENSIONS", "ENABLED", "DISABLED" + local maxWidth, maxRows = math.max( columnsWidth * 2, #column1Title + #column2Title, #mainTitle - 3 ), math.max( #column1, #column2 ) + if maxWidth % 2 ~= 0 then maxWidth = maxWidth + 1 end + columnsWidth = maxWidth / 2 + maxWidth = maxWidth + 3 + local delimiter = " +-" .. ("-"):rep( columnsWidth ) .. "-+-" .. ("-"):rep( columnsWidth ) .. "-+" + + list = + { + " +-" .. ("-"):rep( maxWidth ) .. "-+", + " | " .. padCenter( mainTitle, maxWidth ) .. " |", + delimiter, + " | " .. padCenter( column1Title, columnsWidth ) .. " | " .. padCenter( column2Title, columnsWidth ) .. " |", + delimiter, + } + for i = 1, maxRows do list[ #list + 1 ] = " | " .. padRight( column1[ i ] or "", columnsWidth ) .. " | " .. padRight( column2[ i ] or "", columnsWidth ) .. " |" end + list[ #list + 1 ] = delimiter + + extensions.prettyList = list + end + + function printExtensions( ply, str ) + if IsValid( ply ) then + if str then ply:PrintMessage( 2, str ) end + for i = 1, #extensions.prettyList do ply:PrintMessage( 2, extensions.prettyList[ i ] ) end + else + if str then print( str ) end + for i = 1, #extensions.prettyList do print( extensions.prettyList[ i ] ) end + end + end + + function conCommandSetExtensionStatus( ply, cmd, args ) + if IsValid( ply ) and not ply:IsSuperAdmin() and not game.SinglePlayer() then + ply:PrintMessage( 2, "Sorry " .. ply:Name() .. ", you don't have access to this command." ) + return + end + local name = args[ 1 ] + if name then + name = name:Trim():lower() + if extensions.status[ name ] ~= nil then + local status = tobool( cmd:find( "enable" ) ) + if extensions.status[ name ] == status then + local str = "Extension '" .. name .. "' is already " .. ( status and "enabled" or "disabled" ) .. "." + if IsValid( ply ) then ply:PrintMessage( 2, str ) else print( str ) end + else + E2Lib.SetExtensionStatus( name, status ) + E2Lib.UpdateClientsideExtensionsList() + local str = "E2 Extension '" .. name .. "' has been " .. ( status and "enabled" or "disabled" ) + if not game.SinglePlayer() and IsValid( ply ) then MsgN( str .. " by " .. ply:Name() .. " (" .. ply:SteamID() .. ")." ) end + local canReloadNow = #player.GetAll() == 0 + if canReloadNow then str = str .. ". Expression 2 will be reloaded now." else str = str .. ". Expression 2 will be reloaded in 10 seconds." end + if IsValid( ply ) then ply:PrintMessage( 2, str ) else print( str ) end + if canReloadNow then wire_expression2_reload( ply ) else timer.Create( "E2_AutoReloadTimer", 10, 1, function() wire_expression2_reload( ply ) end ) end + end + else printExtensions( ply, "Unknown extension '" .. name .. "'. Here is a list of available extensions:" ) end + else printExtensions( ply, "Usage: '" .. cmd .. " '. Here is a list of available extensions:" ) end + end + + net.Receive( "wire_expression2_client_request_print_extensions", + function( _, ply ) + printExtensions( ply ) + end + ) + + net.Receive( "wire_expression2_client_request_set_extension_status", + function( _, ply ) + conCommandSetExtensionStatus( ply, net.ReadString(), net.ReadTable() ) + end + ) + + hook.Add( "PlayerInitialSpawn", "wire_expression2_updateClientsideExtensions", E2Lib.UpdateClientsideExtensionsList ) + + function wire_expression2_PostLoadExtensions() + table.sort( extensions.list, function( a, b ) return a < b end ) + E2Lib.UpdateClientsideExtensionsList() + buildPrettyList() + if not wire_expression2_is_reload then -- only print once on startup, not on each reload. + printExtensions() + end + hook.Run( "Expression2_PostLoadExtensions" ) + end + + else -- clientside stuff + + extensions = { status = {}, list = {} } + + function printExtensions() + net.Start( "wire_expression2_client_request_print_extensions" ) + net.SendToServer() + end + + function conCommandSetExtensionStatus( _, cmd, args ) + net.Start( "wire_expression2_client_request_set_extension_status" ) + net.WriteString( cmd ) + net.WriteTable( args ) + net.SendToServer() + end + + net.Receive( "wire_expression2_server_send_extensions_list", function() + extensions = net.ReadTable() + end) + + end + + -- shared stuff + + local function makeAutoCompleteList( cmd, args ) + args = args:Trim():lower() + local status, list, tbl, j = tobool( cmd:find( "enable" ) ), extensions.list, {}, 1 + for i = 1, #list do + local name = list[ i ] + if extensions.status[ name ] ~= status and name:find( args ) then + tbl[ j ] = cmd .. " " .. name + j = j + 1 + end + end + return tbl + end + + concommand.Add( "wire_expression2_extension_enable", conCommandSetExtensionStatus, makeAutoCompleteList ) + concommand.Add( "wire_expression2_extension_disable", conCommandSetExtensionStatus, makeAutoCompleteList ) + concommand.Add( "wire_expression2_extensions", function( ply ) printExtensions( ply ) end ) + +end + +-- ------------------------ clientside reload command -------------------------- + +do + if SERVER then + + util.AddNetworkString( "wire_expression2_client_request_reload" ) + net.Receive( "wire_expression2_client_request_reload", + function( n, ply ) + wire_expression2_reload( ply ) + end + ) + + else + + local function wire_expression2_reload() + net.Start( "wire_expression2_client_request_reload" ) + net.SendToServer() + end + + concommand.Add( "wire_expression2_reload", wire_expression2_reload ) + + end + +end + +-- ------------------------------ compatibility -------------------------------- + +-- Some functions need to be global for backwards-compatibility. +local makeglobal = { + ["validPhysics"] = true, + ["getOwner"] = true, + ["isOwner"] = true, +} + +-- Put all these functions into the global scope. +for funcname, _ in pairs(makeglobal) do + _G[funcname] = E2Lib[funcname] +end + +hook.Add("InitPostEntity", "e2lib", function() +-- If changed, put them into the global scope again. + registerCallback("e2lib_replace_function", function(funcname, func, oldfunc) + if makeglobal[funcname] then + _G[funcname] = func + end + if funcname == "IsValid" then IsValid = func + elseif funcname == "isOwner" then isOwner = func + end + end) + + -- check for a CPPI compliant plugin + if SERVER and CPPI then + if debug.getregistry().Player.CPPIGetFriends then + E2Lib.replace_function("isFriend", function(owner, player) + if owner == nil then return false end + if owner == player then return true end + + local friends = owner:CPPIGetFriends() + if not istable(friends) then return end + + for _, friend in pairs(friends) do + if player == friend then return true end + end + + return false + end) + end + + if debug.getregistry().Entity.CPPIGetOwner then + local _getOwner = E2Lib.getOwner + E2Lib.replace_function("getOwner", function(self, entity) + if not IsValid(entity) then return end + if entity == self.entity or entity == self.player then return self.player end + + local owner = entity:CPPIGetOwner() + if IsValid(owner) then return owner end + + return _getOwner(self, entity) + end) + end + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/egpfunctions.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/egpfunctions.lua new file mode 100644 index 0000000..b13b8dc --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/egpfunctions.lua @@ -0,0 +1,1295 @@ +local function Update(self,this) + self.data.EGP.UpdatesNeeded[this] = true +end + +-------------------------------------------------------- +-- Frames +-------------------------------------------------------- +------------- +-- Save +------------- + +__e2setcost(15) + +e2function void wirelink:egpSaveFrame( string index ) + if (!EGP:ValidEGP( this )) then return end + if (!index or index == "") then return end + local bool, frame = EGP:LoadFrame( self.player, nil, index ) + if (bool) then + if (!EGP:IsDifferent( this.RenderTable, frame )) then return end + end + EGP:DoAction( this, self, "SaveFrame", index ) + Update(self,this) +end + +e2function void wirelink:egpSaveFrame( index ) + if (!EGP:ValidEGP( this )) then return end + if (!index) then return end + local bool, frame = EGP:LoadFrame( self.player, nil, tostring(index) ) + if (bool) then + if (!EGP:IsDifferent( this.RenderTable, frame )) then return end + end + EGP:DoAction( this, self, "SaveFrame", tostring(index) ) + Update(self,this) +end + +------------- +-- Load +------------- + +__e2setcost(15) + +e2function void wirelink:egpLoadFrame( string index ) + if (!EGP:IsAllowed( self, this )) then return end + if (!index or index == "") then return end + local bool, frame = EGP:LoadFrame( self.player, nil, index ) + if (bool) then + if (EGP:IsDifferent( this.RenderTable, frame )) then + EGP:DoAction( this, self, "LoadFrame", index ) + Update(self,this) + end + end +end + +e2function void wirelink:egpLoadFrame( number index ) + if (!EGP:IsAllowed( self, this )) then return end + if (!index) then return end + local bool, frame = EGP:LoadFrame( self.player, nil, tostring(index) ) + if (bool) then + if (EGP:IsDifferent( this.RenderTable, frame )) then + EGP:DoAction( this, self, "LoadFrame", tostring(index) ) + Update(self,this) + end + end +end + +-------------------------------------------------------- +-- Order +-------------------------------------------------------- + +e2function void wirelink:egpOrder( number index, number order ) + if (!EGP:IsAllowed( self, this )) then return end + if (index == order) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + local bool2 = EGP:SetOrder( this, k, order ) + if (bool2) then + EGP:DoAction( this, self, "SendObject", v ) + Update(self,this) + end + end +end + +e2function number wirelink:egpOrder( number index ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + return k + end + return -1 +end + +e2function void wirelink:egpOrderAbove( number index, number abovethis ) + if not EGP:IsAllowed( self, this ) then return end + local bool, k, v = EGP:HasObject( this, index ) + if bool then + local bool2, k2, v2 = EGP:HasObject( this, abovethis ) + if bool2 then + local bool3 = EGP:SetOrder( this, k, abovethis, 1 ) + if bool3 then + EGP:DoAction( this, self, "SendObject", v ) + Update(self,this) + end + end + end +end + +e2function void wirelink:egpOrderBelow( number index, number belowthis ) + if not EGP:IsAllowed( self, this ) then return end + local bool, k, v = EGP:HasObject( this, index ) + if bool then + local bool2, k2, v2 = EGP:HasObject( this, belowthis ) + if bool2 then + local bool3 = EGP:SetOrder( this, k, belowthis, -1 ) + if bool3 then + EGP:DoAction( this, self, "SendObject", v ) + Update(self,this) + end + end + end +end + +__e2setcost(15) + +-------------------------------------------------------- +-- Box +-------------------------------------------------------- +e2function void wirelink:egpBox( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Box"], { index = index, w = size[1], h = size[2], x = pos[1], y = pos[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- BoxOutline +-------------------------------------------------------- +e2function void wirelink:egpBoxOutline( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["BoxOutline"], { index = index, w = size[1], h = size[2], x = pos[1], y = pos[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- RoundedBox +-------------------------------------------------------- +e2function void wirelink:egpRoundedBox( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["RoundedBox"], { index = index, w = size[1], h = size[2], x = pos[1], y = pos[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +e2function void wirelink:egpRadius( number index, number radius ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { radius = radius } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +-------------------------------------------------------- +-- RoundedBoxOutline +-------------------------------------------------------- +e2function void wirelink:egpRoundedBoxOutline( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["RoundedBoxOutline"], { index = index, w = size[1], h = size[2], x = pos[1], y = pos[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- Text +-------------------------------------------------------- +e2function void wirelink:egpText( number index, string text, vector2 pos ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Text"], { index = index, text = text, x = pos[1], y = pos[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +e2function void wirelink:egpTextLayout( number index, string text, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["TextLayout"], { index = index, text = text, x = pos[1], y = pos[2], w = size[1], h = size[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +__e2setcost(10) + +---------------------------- +-- Set Text +---------------------------- +e2function void wirelink:egpSetText( number index, string text ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { text = text } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +---------------------------- +-- Alignment +---------------------------- +e2function void wirelink:egpAlign( number index, number halign ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { halign = math.Clamp(halign,0,2) } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpAlign( number index, number halign, number valign ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { valign = math.Clamp(valign,0,2), halign = math.Clamp(halign,0,2) } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +---------------------------- +-- Filtering +---------------------------- +e2function void wirelink:egpFiltering( number index, number filtering ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { filtering = math.Clamp(filtering,0,3) } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpGlobalFiltering( number filtering ) + if (!EGP:IsAllowed( self, this )) then return end + if this:GetClass() == "gmod_wire_egp" then -- Only Screens use GPULib and can use global filtering + EGP:DoAction( this, self, "EditFiltering", math.Clamp(filtering, 0, 3) ) + end +end + +for _,cname in ipairs({ "NONE", "POINT", "LINEAR", "ANISOTROPIC" }) do + local value = TEXFILTER[cname] + if value < 0 or value > 3 then + print("WARNING: TEXFILTER."..cname.."="..value.." out of expected range (0-3). Please adjust code to udpdated values. Skipping...") + -- Update clamp for both filtering functions above as well as write/readUInt(filtering,2) in egp baseclass+poly netcode. + else + E2Lib.registerConstant("TEXFILTER_"..cname, value) + end +end + +---------------------------- +-- Font +---------------------------- +e2function void wirelink:egpFont( number index, string font ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { font = font } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpFont( number index, string font, number size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { font = font, size = size } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +-------------------------------------------------------- +-- Poly +-------------------------------------------------------- + +__e2setcost(20) + +local function maxvertices() return EGP.ConVars.MaxVertices:GetInt() end + +e2function void wirelink:egpPoly( number index, ... ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + local args = {...} + if (#args<3) then return end -- No less than 3 + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if (typeids[k] == "xv2" or typeids[k] == "xv4") then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (typeids[k] == "xv4") then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Poly"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +e2function void wirelink:egpPoly( number index, array args ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + if (#args<3) then return end -- No less than 3 + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if istable(v) and (#v == 2 or #v == 4) then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (#v == 4) then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Poly"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- PolyOutline +-------------------------------------------------------- + +e2function void wirelink:egpPolyOutline( number index, ... ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + local args = {...} + if (#args<3) then return end -- No less than 3 + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if (typeids[k] == "xv2" or typeids[k] == "xv4") then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (typeids[k] == "xv4") then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["PolyOutline"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +e2function void wirelink:egpPolyOutline( number index, array args ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + if (#args<3) then return end -- No less than 3 + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if istable(v) and (#v == 2 or #v == 4) then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (#v == 4) then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["PolyOutline"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +e2function void wirelink:egpAddVertices( number index, array args ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + if (#args<3) then return end -- No less than 3 + + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if istable(v) and (#v == 2 or #v == 4) then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (#v == 4) then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + if (EGP:EditObject( v, { vertices = vertices } )) then + EGP:InsertQueue( this, self.player, EGP._SetVertex, "SetVertex", index, vertices, true ) + Update(self,this) + end + end +end + +-------------------------------------------------------- +-- egpLineStrip (PolyOutline without the final connecting line) +-------------------------------------------------------- + +e2function void wirelink:egpLineStrip( number index, ... ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + local args = {...} + if (#args<3) then return end -- No less than 3 + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if (typeids[k] == "xv2" or typeids[k] == "xv4") then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (typeids[k] == "xv4") then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["LineStrip"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +e2function void wirelink:egpLineStrip( number index, array args ) + if (!EGP:IsAllowed( self, this )) then return end + if (!EGP:ValidEGP( this )) then return end + if (#args<3) then return end -- No less than 3 + + local max = maxvertices() + + -- Each arg must be a vec2 or vec4 + local vertices = {} + for k,v in ipairs( args ) do + if istable(v) and (#v == 2 or #v == 4) then + n = #vertices + if (n > max) then break end + vertices[n+1] = { x = v[1], y = v[2] } + if (#v == 4) then + vertices[n+1].u = v[3] + vertices[n+1].v = v[4] + end + end + end + + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["LineStrip"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +__e2setcost(15) + +-------------------------------------------------------- +-- Line +-------------------------------------------------------- +e2function void wirelink:egpLine( number index, vector2 pos1, vector2 pos2 ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Line"], { index = index, x = pos1[1], y = pos1[2], x2 = pos2[1], y2 = pos2[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- Circle +-------------------------------------------------------- +e2function void wirelink:egpCircle( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Circle"], { index = index, x = pos[1], y = pos[2], w = size[1], h = size[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- Circle Outline +-------------------------------------------------------- +e2function void wirelink:egpCircleOutline( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["CircleOutline"], { index = index, x = pos[1], y = pos[2], w = size[1], h = size[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- Triangle +-------------------------------------------------------- +e2function void wirelink:egpTriangle( number index, vector2 v1, vector2 v2, vector2 v3 ) + if (!EGP:IsAllowed( self, this )) then return end + local vertices = { { x = v1[1], y = v1[2] }, { x = v2[1], y = v2[2] }, { x = v3[1], y = v3[2] } } + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Poly"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- Triangle Outline +-------------------------------------------------------- +e2function void wirelink:egpTriangleOutline( number index, vector2 v1, vector2 v2, vector2 v3 ) + if (!EGP:IsAllowed( self, this )) then return end + local vertices = { { x = v1[1], y = v1[2] }, { x = v2[1], y = v2[2] }, { x = v3[1], y = v3[2] } } + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["PolyOutline"], { index = index, vertices = vertices }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +-------------------------------------------------------- +-- Wedge +-------------------------------------------------------- +e2function void wirelink:egpWedge( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Wedge"], { index = index, x = pos[1], y = pos[2], w = size[1], h = size[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +--[[ I'm sticking to my policy of not spamming pointless functions. +e2function void wirelink:egpWedge( number index, vector2 pos, vector2 size, number angle, number mouthsize ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["Wedge"], { index = index, x = pos[1], y = pos[2], w = size[1], h = size[2], size = mouthsize, angle = angle }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end +]] + +-------------------------------------------------------- +-- Wedge Outline +-------------------------------------------------------- +e2function void wirelink:egpWedgeOutline( number index, vector2 pos, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["WedgeOutline"], { index = index, x = pos[1], y = pos[2], w = size[1], h = size[2] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +--[[ I'm sticking to my policy of not spamming pointless functions. +e2function void wirelink:egpWedgeOutline( number index, vector2 pos, vector2 size, number angle, number mouthsize ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["WedgeOutline"], { index = index, x = pos[1], y = pos[2], w = size[1], h = size[2], size = mouthsize, angle = angle }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end +]] + +-------------------------------------------------------- +-- 3DHolder +-------------------------------------------------------- +e2function void wirelink:egp3DTracker( number index, vector pos ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, obj = EGP:CreateObject( this, EGP.Objects.Names["3DTracker"], { index = index, target_x = pos[1], target_y = pos[2], target_z = pos[3] }, self.player ) + if (bool) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end +end + +__e2setcost(10) + +e2function void wirelink:egpPos( number index, vector pos ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { target_x = pos[1], target_y = pos[2], target_z = pos[3] } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +-------------------------------------------------------- +-- Set functions +-------------------------------------------------------- + +__e2setcost(10) + +---------------------------- +-- Size +---------------------------- +e2function void wirelink:egpSize( number index, vector2 size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { w = size[1], h = size[2] } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpSize( number index, number size ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { size = size } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +---------------------------- +-- Position +---------------------------- +e2function void wirelink:egpPos( number index, vector2 pos ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { x = pos[1], y = pos[2] } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +---------------------------- +-- Angle +---------------------------- + +e2function void wirelink:egpAngle( number index, number angle ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { angle = angle } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +------------- +-- Position & Angle +------------- + +e2function void wirelink:egpAngle( number index, vector2 worldpos, vector2 axispos, number angle ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.x and v.y) then + + local vec, ang = LocalToWorld(Vector(axispos[1],axispos[2],0), Angle(0,0,0), Vector(worldpos[1],worldpos[2],0), Angle(0,-angle,0)) + + local x = vec.x + local y = vec.y + + angle = -ang.yaw + + local t = { x = x, y = y } + if (v.angle) then t.angle = angle end + + if (EGP:EditObject( v, t )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end + end +end + +---------------------------- +-- Color +---------------------------- +e2function void wirelink:egpColor( number index, vector4 color ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { r = color[1], g = color[2], b = color[3], a = color[4] } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpColor( number index, vector color ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { r = color[1], g = color[2], b = color[3] } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpColor( number index, r,g,b,a ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { r = r, g = g, b = b, a = a } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpAlpha( number index, number a ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { a = a } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + + +---------------------------- +-- Material +---------------------------- +e2function void wirelink:egpMaterial( number index, string material ) + if (!EGP:IsAllowed( self, this )) then return end + material = WireLib.IsValidMaterial(material) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { material = material } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function void wirelink:egpMaterialFromScreen( number index, entity gpu ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool and gpu and gpu:IsValid()) then + if (EGP:EditObject( v, { material = gpu } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +---------------------------- +-- Fidelity (number of corners for circles and wedges) +---------------------------- +e2function void wirelink:egpFidelity( number index, number fidelity ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (EGP:EditObject( v, { fidelity = math.Clamp(fidelity,3,180) } )) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end + end +end + +e2function number wirelink:egpFidelity( number index ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.fidelity) then + return v.fidelity + end + end + return -1 +end + +---------------------------- +-- Parenting +---------------------------- +e2function void wirelink:egpParent( number index, number parentindex ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, v = EGP:SetParent( this, index, parentindex ) + if (bool) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end +end + +-- Entity parenting (only for 3Dtracker - does nothing for any other object) +e2function void wirelink:egpParent( number index, entity parent ) + if not parent or not parent:IsValid() then return end + if (!EGP:IsAllowed( self, this )) then return end + + local bool, k, v = EGP:HasObject( this, index ) + if bool and v.Is3DTracker then + if v.parententity == parent then return end -- Already parented to that + v.parententity = parent + + EGP:DoAction( this, self, "SendObject", v ) + Update(self,this) + end +end + +-- Returns the entity a tracker is parented to +e2function entity wirelink:egpTrackerParent( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if bool and v.Is3DTracker then + return (v.parententity and v.parententity:IsValid()) and v.parententity or nil + end +end + +e2function void wirelink:egpParentToCursor( number index ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, v = EGP:SetParent( this, index, -1 ) + if (bool) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end +end + +e2function void wirelink:egpUnParent( number index ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, v = EGP:UnParent( this, index ) + if (bool) then EGP:DoAction( this, self, "SendObject", v ) Update(self,this) end +end + +e2function number wirelink:egpParent( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.parent) then + return v.parent + end + end + return -1 +end + +-------------------------------------------------------- +-- Clear & Remove +-------------------------------------------------------- +e2function void wirelink:egpClear() + if (!EGP:IsAllowed( self, this )) then return end + if (EGP:ValidEGP( this )) then + EGP:DoAction( this, self, "ClearScreen" ) + Update(self,this) + end +end + +e2function void wirelink:egpRemove( number index ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + EGP:DoAction( this, self, "RemoveObject", index ) + Update(self,this) + end +end + +-------------------------------------------------------- +-- Get functions +-------------------------------------------------------- + +__e2setcost(5) + +e2function vector2 wirelink:egpPos( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.x and v.y) then + return {v.x, v.y} + end + end + return {-1,-1} +end + +__e2setcost(20) +e2function vector wirelink:egpGlobalPos( number index ) + local hasvertices, posang = EGP:GetGlobalPos( this, index ) + if (!hasvertices) then + return { posang.x, posang.y, posang.angle } + end + return { 0,0,0 } +end + +e2function array wirelink:egpGlobalVertices( number index ) + ErrorNoHalt = override + local hasvertices, data = EGP:GetGlobalPos( this, index ) + ErrorNoHalt = olderror + if (hasvertices) then + if (data.vertices) then + local ret = {} + for i=1,#data.vertices do + local v = data.vertices[i] + ret[i] = {v.x,v.y} + self.prf = self.prf + 0.1 + end + return ret + elseif (data.x and data.y and data.x2 and data.y2 and data.x3 and data.y3) then + return {{data.x,data.y},{data.x2,data.y2},{data.x3,data.y3}} + elseif (data.x and data.y and data.x2 and data.y2) then + return {{data.x,data.y},{data.x2,data.y2}} + end + end + return { 0,0,0 } +end + +__e2setcost(5) + +e2function vector2 wirelink:egpSize( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.w and v.h) then + return {v.w, v.h} + end + end + return {-1,-1} +end + +e2function number wirelink:egpSizeNum( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.size) then + return v.size + end + end + return -1 +end + +e2function vector4 wirelink:egpColor4( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.r and v.g and v.b and v.a) then + return {v.r,v.g,v.b,v.a} + end + end + return {-1,-1,-1,-1} +end + +e2function vector wirelink:egpColor( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.r and v.g and v.b) then + return {v.r,v.g,v.b} + end + end + return {-1,-1,-1} +end + +e2function number wirelink:egpAlpha( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.a) then + return v.a + end + end + return -1 +end + +e2function number wirelink:egpAngle( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.angle) then + return v.angle + end + end + return -1 +end + +e2function string wirelink:egpMaterial( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.material) then + return v.material + end + end + return "" +end + +e2function number wirelink:egpRadius( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.radius) then + return v.radius + end + end + return -1 +end + +__e2setcost(10) + +e2function array wirelink:egpVertices( number index ) + local bool, k, v = EGP:HasObject( this, index ) + if (bool) then + if (v.vertices) then + local ret = {} + for k2,v2 in ipairs( v.vertices ) do + ret[k2] = {v2.x,v2.y} + end + return ret + elseif (v.x and v.y and v.x2 and v.y2 and v.x3 and v.y3) then + return {{v.x,v.y},{v.x2,v.y2},{v.x3,v.y3}} + elseif (v.x and v.y and v.x2 and v.y2) then + return {{v.x,v.y},{v.x2,v.y2}} + end + end + return {} +end + +-------------------------------------------------------- +-- Indexes +-------------------------------------------------------- +__e2setcost(1) +e2function array wirelink:egpObjectIndexes() + if not EGP:ValidEGP(this) then return {} end + if not this.RenderTable or #this.RenderTable == 0 then return {} end + local indexes = {} + for _, v in pairs(this.RenderTable) do + indexes[#indexes + 1] = v.index + end + self.prf = self.prf + #indexes/3 + return indexes +end + +-------------------------------------------------------- +-- Object Type +-------------------------------------------------------- +__e2setcost(1) + +e2function array wirelink:egpObjectTypes() + if not EGP:ValidEGP(this) then return {} end + if not this.RenderTable or #this.RenderTable == 0 then return {} end + local objs = {} + for _, v in pairs(this.RenderTable) do + objs[v.index] = EGP.Objects.Names_Inverted[v.ID] or "" + end + self.prf = self.prf + #this.RenderTable/3 + return objs +end + +__e2setcost(10) + +e2function string wirelink:egpObjectType(number index) + local bool, _, v = EGP:HasObject(this, index) + if bool then + return EGP.Objects.Names_Inverted[v.ID] or "" + end + return "" +end + +-------------------------------------------------------- +-- Additional Functions +-------------------------------------------------------- + +__e2setcost(15) + +e2function void wirelink:egpCopy( index, fromindex ) + if (!EGP:IsAllowed( self, this )) then return end + local bool, k, v = EGP:HasObject( this, fromindex ) + if (bool) then + local copy = table.Copy( v ) + copy.index = index + local bool2, obj = EGP:CreateObject( this, v.ID, copy, self.player ) + if (bool2) then EGP:DoAction( this, self, "SendObject", obj ) Update(self,this) end + end +end + +__e2setcost(20) + +e2function vector2 wirelink:egpCursor( entity ply ) + return EGP:EGPCursor( this, ply ) +end + +__e2setcost(10) + +e2function vector2 egpScrSize( entity ply ) + if (!ply or !ply:IsValid() or !ply:IsPlayer() or !EGP.ScrHW[ply]) then return {-1,-1} end + return EGP.ScrHW[ply] +end + +e2function number egpScrW( entity ply ) + if (!ply or !ply:IsValid() or !ply:IsPlayer() or !EGP.ScrHW[ply]) then return -1 end + return EGP.ScrHW[ply][1] +end + +e2function number egpScrH( entity ply ) + if (!ply or !ply:IsValid() or !ply:IsPlayer() or !EGP.ScrHW[ply]) then return -1 end + return EGP.ScrHW[ply][2] +end + +__e2setcost(15) + +e2function number wirelink:egpHasObject( index ) + local bool, _, _ = EGP:HasObject( this, index ) + return bool and 1 or 0 +end + +--- Returns 1 if the object with specified index contains the specified point. +e2function number wirelink:egpObjectContainsPoint(number index, vector2 point) + local _, _, object = EGP:HasObject(this, index) + return object and object:Contains({x = point[1], y = point[2]}) and 1 or 0 +end + +__e2setcost(10) + +local function errorcheck( x, y ) + local xMul = x[2]-x[1] + local yMul = y[2]-y[1] + if (xMul == 0 or yMul == 0) then error("Invalid EGP scale") end +end + +e2function void wirelink:egpScale( vector2 xScale, vector2 yScale ) + if (!EGP:IsAllowed( self, this )) then return end + errorcheck(xScale,yScale) + EGP:DoAction( this, self, "SetScale", xScale, yScale ) +end + +e2function void wirelink:egpResolution( vector2 topleft, vector2 bottomright ) + if (!EGP:IsAllowed( self, this )) then return end + local xScale = { topleft[1], bottomright[1] } + local yScale = { topleft[2], bottomright[2] } + errorcheck(xScale,yScale) + EGP:DoAction( this, self, "SetScale", xScale, yScale ) +end + +e2function vector2 wirelink:egpOrigin() + if (!EGP:IsAllowed( self, this )) then return end + local xOrigin = this.xScale[1] + (this.xScale[2] - this.xScale[1])/2 + local yOrigin = this.yScale[1] + (this.yScale[2] - this.yScale[1])/2 + return { xOrigin, yOrigin } + --return EGP:DoAction( this, self, "GetOrigin" ) +end + +e2function vector2 wirelink:egpSize() + if (!EGP:IsAllowed( self, this )) then return end + local width = math.abs(this.xScale[1] - this.xScale[2]) + local height = math.abs(this.yScale[1] - this.yScale[2]) + return { width, height } + --return EGP:DoAction( this, self, "GetScreenSize" ) +end + +e2function void wirelink:egpDrawTopLeft( number onoff ) + if (!EGP:IsAllowed( self, this )) then return end + local bool = true + if (onoff == 0) then bool = false end + EGP:DoAction( this, self, "MoveTopLeft", bool ) +end + +-- this code has some wtf strange things +local function ScalePoint( this, x, y ) + local xMin = this.xScale[1] + local xMax = this.xScale[2] + local yMin = this.yScale[1] + local yMax = this.yScale[2] + + x = ((x - xMin) * 512) / (xMax - xMin) - xMax + y = ((y - yMin) * 512) / (yMax - yMin) - yMax + + return x,y +end + + +__e2setcost(20) +e2function vector wirelink:egpToWorld( vector2 pos ) + if not EGP:ValidEGP( this ) then return Vector(0,0,0) end + + local class = this:GetClass() + if class == "gmod_wire_egp_emitter" then + local x,y = pos[1]*0.25,pos[2]*0.25 -- 0.25 because the scale of the 3D2D is 0.25. + if this.Scaling then + x,y = ScalePoint(this,x,y) + end + return this:LocalToWorld( Vector(-64,0,135) + Vector(x,0,-y) ) + elseif class == "gmod_wire_egp" then + local monitor = WireGPU_Monitors[this:GetModel()] + if not monitor then return Vector(0,0,0) end + + local x,y = pos[1],pos[2] + + if this.Scaling then + x,y = ScalePoint( this, x, y ) + else + x,y = x-256,y-256 + end + + x = x * monitor.RS / monitor.RatioX + y = y * monitor.RS + + local vec = Vector(x,-y,0) + vec:Rotate(monitor.rot) + return this:LocalToWorld(vec+monitor.offset) + end + + return Vector(0,0,0) +end + +local antispam = {} +__e2setcost(25) +e2function void wirelink:egpHudToggle() + if not EGP:ValidEGP( this ) then return end + if antispam[self.player] and antispam[self.player] > CurTime() then return end + antispam[self.player] = CurTime() + 0.1 + umsg.Start( "EGP_HUD_Use", self.player ) umsg.Entity( this ) umsg.End() +end + +-------------------------------------------------------- +-- Useful functions +-------------------------------------------------------- + +----------------------------- +-- ConVars +----------------------------- + +__e2setcost(10) + +e2function number wirelink:egpNumObjects() + if (!EGP:ValidEGP( this )) then return -1 end + return #this.RenderTable +end + +e2function number egpMaxObjects() + return EGP.ConVars.MaxObjects:GetInt() +end + +e2function number egpMaxUmsgPerSecond() + return EGP.ConVars.MaxPerSec:GetInt() +end + +e2function number egpBytesLeft() + local maxcount = EGP.ConVars.MaxPerSec:GetInt() + local tbl = EGP.IntervalCheck[self.player] + tbl.bytes = math.max(0, tbl.bytes - (CurTime() - tbl.time) * maxcount) + tbl.time = CurTime() + return maxcount - tbl.bytes +end + +__e2setcost(5) + +e2function number egpCanSendUmsg() + return (EGP:CheckInterval( self.player ) and 1 or 0) +end + +----------------------------- +-- Queue system +----------------------------- + +e2function number egpClearQueue() + if (EGP.Queue[self.player]) then + EGP.Queue[self.player] = {} + return 1 + end + return 0 +end + +--[[ currently does not work +e2 function number wirelink:egpClearQueue() + if (!EGP:ValidEGP( this )) then return end + if (EGP.Queue[self.player]) then + EGP:StopQueueTimer( self.player ) + EGP.Queue[self.player].DONTADDMORE = true + local removetable = {} + for k,v in ipairs( EGP.Queue[self.player] ) do + if (v.Ent == this) then + table.insert( removetable, k ) + return 1 + end + end + for k,v in ipairs( removetable ) do + table.remove( EGP.Queue[self.player], v ) + end + EGP:SendQueueItem( self.player ) + EGP:StartQueueTimer( self.player ) + timer.Simple(1,function() EGP.Queue[self.player].DONTADDMORE = nil end) + end + return 0 +end +]] + +__e2setcost(10) + +-- Returns the amount of items in your queue +e2function number egpQueue() + if (EGP.Queue[self.player]) then + return #EGP.Queue[self.player] + end + return 0 +end + +-- Choose whether or not to make this E2 run when the queue has finished sending all items for +e2function void wirelink:egpRunOnQueue( yesno ) + if (!EGP:ValidEGP( this )) then return end + local bool = false + if (yesno != 0) then bool = true end + self.data.EGP.RunOnEGP[this] = bool +end + +-- Returns 1 if the current execution was caused by the EGP queue system OR if the EGP queue system finished in the current execution +e2function number egpQueueClk() + if (EGP.RunByEGPQueue) then + return 1 + end + return 0 +end + +-- Returns 1 if the current execution was caused by the EGP queue system regarding the entity OR if the EGP queue system finished in the current execution +e2function number egpQueueClk( wirelink screen ) + if (EGP.RunByEGPQueue and EGP.RunByEGPQueue_Ent == screen) then + return 1 + end + return 0 +end + +-- Returns 1 if the current execution was caused by the EGP queue system regarding the entity OR if the EGP queue system finished in the current execution +e2function number egpQueueClk( entity screen ) + if (EGP.RunByEGPQueue and EGP.RunByEGPQueue_Ent == screen) then + return 1 + end + return 0 +end + +-- Returns the screen which the queue finished sending items for +e2function entity egpQueueScreen() + if (EGP.RunByEGPQueue) then + return EGP.RunByEGPQueue_Ent + end +end + +-- Same as above, except returns wirelink +e2function wirelink egpQueueScreenWirelink() + if (EGP.RunByEGPQueue) then + return EGP.RunByEGPQueue_Ent + end +end + +-- Returns the player which ordered the current items to be sent (This is usually yourself, but if you're sharing pp with someone it might be them. Good way to check if someone is fucking with your screens) +e2function entity egpQueuePlayer() + if (EGP.RunByEGPQueue) then + return EGP.RunByEGPQueue_ply + end +end + +-- Returns 1 if the current execution was caused by the EGP queue system and the player was the player whom ordered the item to be sent (This is usually yourself, but if you're sharing pp with someone it might be them.) +e2function number egpQueueClkPly( entity ply ) + if (EGP.RunByEGPQueue and EGP.RunByEGPQueue_ply == ply) then + return 1 + end + return 0 +end + +-------------------------------------------------------- +-- Callbacks +-------------------------------------------------------- + +registerCallback("postexecute",function(self) + for k,v in pairs( self.data.EGP.UpdatesNeeded ) do + if IsValid(k) then + EGP:SendQueueItem( self.player ) + end + self.data.EGP.UpdatesNeeded[k] = nil + end +end) + +registerCallback("construct",function(self) + self.data.EGP = {} + self.data.EGP.RunOnEGP = {} + self.data.EGP.UpdatesNeeded = {} +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/entity.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/entity.lua new file mode 100644 index 0000000..ed4ab6d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/entity.lua @@ -0,0 +1,934 @@ +/******************************************************************************\ + Entity support +\******************************************************************************/ + +registerType("entity", "e", nil, + nil, + function(self,output) return output or NULL end, + function(retval) + if IsValid(retval) then return end + if retval == nil then return end + if not retval.EntIndex then error("Return value is neither nil nor an Entity, but a "..type(retval).."!",0) end + end, + function(v) + return not isentity(v) + end +) + +/******************************************************************************/ + +-- import some e2lib functions +local validPhysics = E2Lib.validPhysics +local getOwner = E2Lib.getOwner +local isOwner = E2Lib.isOwner + +local sun = ents.FindByClass("env_sun")[1] -- used for sunDirection() + +hook.Add("InitPostEntity","sunent",function() + sun = ents.FindByClass("env_sun")[1] + timer.Simple(0,function() -- make sure we have a sun first + hook.Remove("InitPostEntity","sunent") + end ) -- then remove this. we don't need it anymore. +end ) + +registerCallback("e2lib_replace_function", function(funcname, func, oldfunc) + if funcname == "isOwner" then + isOwner = func + elseif funcname == "getOwner" then + getOwner = func + elseif funcname == "IsValid" then + IsValid = func + elseif funcname == "validPhysics" then + validPhysics = func + end +end) + +-- faster access to some math library functions +local abs = math.abs +local atan2 = math.atan2 +local sqrt = math.sqrt +local asin = math.asin +local Clamp = math.Clamp + +local rad2deg = 180 / math.pi + +/******************************************************************************/ + +local function checkOwner(self) + return IsValid(self.player) +end + +/******************************************************************************/ +// Functions using operators + +__e2setcost(5) -- temporary + +registerOperator("ass", "e", "e", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ + +e2function number operator_is(entity ent) + if IsValid(ent) then return 1 else return 0 end +end + +e2function number operator==(entity lhs, entity rhs) + if lhs == rhs then return 1 else return 0 end +end + +e2function number operator!=(entity lhs, entity rhs) + if lhs ~= rhs then return 1 else return 0 end +end + +/******************************************************************************/ + +e2function entity entity(id) + local ent = ents.GetByIndex(id) + if not IsValid(ent) then return nil end + return ent +end + +e2function number entity:id() + if not IsValid(this) then return 0 end + return this:EntIndex() +end + +e2function number entity:creationID() + if not IsValid(this) then return 0 end + return this:GetCreationID() +end + +e2function number entity:creationTime() + if not IsValid(this) then return 0 end + return this:GetCreationTime() +end + +/******************************************************************************/ +// Functions getting string + +e2function entity noentity() + return NULL +end + +e2function entity world() + return game.GetWorld() +end + +e2function string entity:name() + if(not IsValid(this)) then return "" end + return this:GetName() or "" +end + +e2function string entity:type() + if not IsValid(this) then return "" end + return this:GetClass() +end + +e2function string entity:model() + if not IsValid(this) then return "" end + return this:GetModel() or "" +end + +e2function entity entity:owner() + if not IsValid(this) then return nil end + return getOwner(self, this) +end + +__e2setcost(20) + +e2function table entity:keyvalues() + local ret = {n={},ntypes={},s={},stypes={},size=0} -- default table + if not IsValid(this) then return ret end + local keyvalues = this:GetKeyValues() + local size = 0 + for k,v in pairs( keyvalues ) do + size = size + 1 + ret.s[k] = v + ret.stypes[k] = string.lower(type(v)[1]) -- i swear there's a more elegant solution to this but whatever. + end + ret.size = size + return ret +end + +__e2setcost(5) -- temporary + +/******************************************************************************/ +// Functions getting vector +e2function vector entity:pos() + if not IsValid(this) then return {0,0,0} end + return this:GetPos() +end + +e2function vector entity:forward() + if not IsValid(this) then return {0,0,0} end + return this:GetForward() +end + +e2function vector entity:right() + if not IsValid(this) then return {0,0,0} end + return this:GetRight() +end + +e2function vector entity:up() + if not IsValid(this) then return {0,0,0} end + return this:GetUp() +end + +e2function vector entity:vel() + if not IsValid(this) then return {0,0,0} end + return this:GetVelocity() +end + +e2function vector entity:velL() + if not IsValid(this) then return {0,0,0} end + return this:WorldToLocal(this:GetVelocity() + this:GetPos()) +end + +e2function angle entity:angVel() + if not validPhysics(this) then return {0,0,0} end + local phys = this:GetPhysicsObject() + local vec = phys:GetAngleVelocity() + return { vec.y, vec.z, vec.x } +end + +--- Returns a vector describing rotation axis, magnitude and sense given as the vector's direction, magnitude and orientation. +e2function vector entity:angVelVector() + if not validPhysics(this) then return { 0, 0, 0 } end + local phys = this:GetPhysicsObject() + return phys:GetAngleVelocity() +end + +--- Specific to env_sun because Source is dum. Use this to trace towards the sun or something. +e2function vector sunDirection() + if not IsValid(sun) then return { 0, 0, 0 } end + return sun:GetKeyValues().sun_dir +end + +/******************************************************************************/ +// Functions using vector getting vector + +__e2setcost(15) + +e2function vector entity:toWorld(vector localPosition) + if not IsValid(this) then return {0,0,0} end + return this:LocalToWorld(Vector(localPosition[1],localPosition[2],localPosition[3])) +end + +e2function vector entity:toLocal(vector worldPosition) + if not IsValid(this) then return {0,0,0} end + return this:WorldToLocal(Vector(worldPosition[1],worldPosition[2],worldPosition[3])) +end + +e2function vector entity:toWorldAxis(vector localAxis) + if not IsValid(this) then return {0,0,0} end + return this:LocalToWorld(Vector(localAxis[1],localAxis[2],localAxis[3]))-this:GetPos() +end + +e2function vector entity:toLocalAxis(vector worldAxis) + if not IsValid(this) then return {0,0,0} end + return this:WorldToLocal(Vector(worldAxis[1],worldAxis[2],worldAxis[3])+this:GetPos()) +end + +--- Transforms from an angle local to to a world angle. +e2function angle entity:toWorld(angle localAngle) + if not IsValid(this) then return { 0, 0, 0 } end + local worldAngle = this:LocalToWorldAngles(Angle(localAngle[1],localAngle[2],localAngle[3])) + return { worldAngle.p, worldAngle.y, worldAngle.r } +end + +--- Transforms from a world angle to an angle local to . +e2function angle entity:toLocal(angle worldAngle) + if not IsValid(this) then return { 0, 0, 0 } end + local localAngle = this:WorldToLocalAngles(Angle(worldAngle[1],worldAngle[2],worldAngle[3])) + return { localAngle.p, localAngle.y, localAngle.r } +end + +/******************************************************************************/ +// Functions getting number + +__e2setcost(5) + +e2function number entity:health() + if not IsValid(this) then return 0 end + return this:Health() +end + +e2function number entity:maxHealth() + if not IsValid(this) then return 0 end + return this:GetMaxHealth() +end + +e2function number entity:radius() + if not IsValid(this) then return 0 end + return this:BoundingRadius() +end + +// original bearing & elevation thanks to Gwahir +--- Returns the bearing (yaw) from to + +__e2setcost(15) + +e2function number entity:bearing(vector pos) + if not IsValid(this) then return 0 end + + pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) + + return rad2deg*-atan2(pos.y, pos.x) +end + +--- Returns the elevation (pitch) from to +e2function number entity:elevation(vector pos) + if not IsValid(this) then return 0 end + + pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) + + local len = pos:Length() + if len < delta then return 0 end + return rad2deg*asin(pos.z / len) +end + +--- Returns the elevation (pitch) and bearing (yaw) from to +e2function angle entity:heading(vector pos) + if not IsValid(this) then return { 0, 0, 0 } end + + pos = this:WorldToLocal(Vector(pos[1],pos[2],pos[3])) + + -- bearing + local bearing = rad2deg*-atan2(pos.y, pos.x) + + -- elevation + local len = pos:Length()--sqrt(x*x + y*y + z*z) + if len < delta then return { 0, bearing, 0 } end + local elevation = rad2deg*asin(pos.z / len) + + return { elevation, bearing, 0 } +end + +__e2setcost(10) + +e2function number entity:mass() + if not validPhysics(this) then return 0 end + local phys = this:GetPhysicsObject() + return phys:GetMass() +end + +e2function vector entity:massCenter() + if not validPhysics(this) then return {0,0,0} end + local phys = this:GetPhysicsObject() + return this:LocalToWorld(phys:GetMassCenter()) +end + +e2function vector entity:massCenterL() + if not validPhysics(this) then return {0,0,0} end + local phys = this:GetPhysicsObject() + return phys:GetMassCenter() +end + +e2function void setMass(mass) + if not validPhysics(self.entity) then return end + if WireLib.isnan( mass ) then mass = 50000 end + local mass = Clamp(mass, 0.001, 50000) + local phys = self.entity:GetPhysicsObject() + phys:SetMass(mass) + duplicator.StoreEntityModifier(self.entity, "mass", { Mass = mass }) +end + +e2function void entity:setMass(mass) + if not validPhysics(this) then return end + if not isOwner(self, this) then return end + if this:IsPlayer() then return end + if WireLib.isnan( mass ) then mass = 50000 end + local mass = Clamp(mass, 0.001, 50000) + local phys = this:GetPhysicsObject() + phys:SetMass(mass) + duplicator.StoreEntityModifier(this, "mass", { Mass = mass }) +end + +e2function number entity:volume() + if not validPhysics(this) then return 0 end + local phys = this:GetPhysicsObject() + return phys:GetVolume() or 0 +end + +e2function number entity:surfaceArea() + if not validPhysics(this) then return 0 end + local phys = this:GetPhysicsObject() + return phys:GetSurfaceArea() or 0 +end + +e2function number entity:stress() + if not validPhysics(this) then return 0 end + local phys = this:GetPhysicsObject() + return phys:GetStress() or 0 +end + +/******************************************************************************/ +// Functions getting boolean/number +e2function number entity:isPlayer() + if not IsValid(this) then return 0 end + if this:IsPlayer() then return 1 else return 0 end +end + +e2function number entity:isNPC() + if not IsValid(this) then return 0 end + if this:IsNPC() then return 1 else return 0 end +end + +e2function number entity:isVehicle() + if not IsValid(this) then return 0 end + if this:IsVehicle() then return 1 else return 0 end +end + +e2function number entity:isWorld() + if not isentity(this) then return 0 end + if this:IsWorld() then return 1 else return 0 end +end + +e2function number entity:isOnGround() + if not IsValid(this) then return 0 end + if this:IsOnGround() then return 1 else return 0 end +end + +e2function number entity:isUnderWater() + if not IsValid(this) then return 0 end + if this:WaterLevel() > 0 then return 1 else return 0 end +end + +e2function number entity:isValid() + return IsValid(this) and 1 or 0 +end + +--- Returns 1 if has valid physics. Note: Players do not. +e2function number entity:isValidPhysics() + return E2Lib.validPhysics(this) and 1 or 0 +end + +/******************************************************************************/ +// Functions getting angles + +e2function angle entity:angles() + if not IsValid(this) then return {0,0,0} end + local ang = this:GetAngles() + return {ang.p,ang.y,ang.r} +end + +/******************************************************************************/ + +e2function string entity:getMaterial() + if not IsValid(this) then return "" end + return this:GetMaterial() or "" +end + +e2function string entity:getSubMaterial(index) + if not IsValid(this) then return "" end + return this:GetSubMaterial(index-1) or "" +end + +__e2setcost(20) + +e2function array entity:getMaterials() + if not IsValid(this) then return {} end + return this:GetMaterials() +end + +__e2setcost(10) + +e2function void entity:setMaterial(string material) + if not IsValid(this) then return end + if not isOwner(self, this) then return end + E2Lib.setMaterial(this, material) +end + +e2function void entity:setSubMaterial(index, string material) + if not IsValid(this) then return end + if not isOwner(self, this) then return end + E2Lib.setSubMaterial(this, index-1, material) +end + +--- Gets 's current skin number. +e2function number entity:getSkin() + if IsValid(this) then return this:GetSkin() end + return 0 +end + +--- Sets 's skin number. +e2function void entity:setSkin(skinIndex) + if IsValid(this) and not this:IsPlayer() + and this:SkinCount() > 0 and skinIndex < this:SkinCount() + and gamemode.Call("CanProperty", self.player, "skin", this) then + this:SetSkin(skinIndex) + end +end + +--- Gets 's number of skins. +e2function number entity:getSkinCount() + if IsValid(this) then return this:SkinCount() end + return 0 +end + +--- Sets 's bodygroup. +e2function void entity:setBodygroup(bgrp_id, bgrp_subid) + if not IsValid(this) then return end + if not isOwner(self, this) then return end + this:SetBodygroup(bgrp_id, bgrp_subid) +end + +--- Gets 's bodygroup number. +e2function number entity:getBodygroup(bgrp_id) + if IsValid(this) then return this:GetBodygroup(bgrp_id) end + return 0 +end +--- Gets 's bodygroup count. +e2function number entity:getBodygroups(bgrp_id) + if IsValid(this) then return this:GetBodygroupCount(bgrp_id) end + return 0 +end + +/******************************************************************************/ + +e2function number entity:isPlayerHolding() + if not IsValid(this) then return 0 end + if this:IsPlayerHolding() then return 1 else return 0 end +end + +e2function number entity:isOnFire() + if not IsValid(this) then return 0 end + if this:IsOnFire() then return 1 else return 0 end +end + +e2function number entity:isWeapon() + if not IsValid(this) then return 0 end + if this:IsWeapon() then return 1 else return 0 end +end + +e2function number entity:isFrozen() + if not validPhysics(this) then return 0 end + local phys = this:GetPhysicsObject() + if phys:IsMoveable() then return 0 else return 1 end +end + +/******************************************************************************/ + +__e2setcost(30) -- temporary + +local clamp = WireLib.clampForce + +e2function void entity:applyForce(vector force) + if not validPhysics(this) then return nil end + if not isOwner(self, this) then return nil end + + force = clamp(force) + + local phys = this:GetPhysicsObject() + phys:ApplyForceCenter(Vector(force[1],force[2],force[3])) +end + +e2function void entity:applyOffsetForce(vector force, vector position) + if not validPhysics(this) then return nil end + if not isOwner(self, this) then return nil end + + force = clamp(force) + position = clamp(position) + + local phys = this:GetPhysicsObject() + phys:ApplyForceOffset(Vector(force[1],force[2],force[3]), Vector(position[1],position[2],position[3])) +end + +e2function void entity:applyAngForce(angle angForce) + if not validPhysics(this) then return nil end + if not isOwner(self, this) then return nil end + + if angForce[1] == 0 and angForce[2] == 0 and angForce[3] == 0 then return end + angForce = clamp(angForce) + + local phys = this:GetPhysicsObject() + + -- assign vectors + local up = this:GetUp() + local left = this:GetRight() * -1 + local forward = this:GetForward() + + -- apply pitch force + if angForce[1] ~= 0 then + local pitch = up * (angForce[1] * 0.5) + phys:ApplyForceOffset( forward, pitch ) + phys:ApplyForceOffset( forward * -1, pitch * -1 ) + end + + -- apply yaw force + if angForce[2] ~= 0 then + local yaw = forward * (angForce[2] * 0.5) + phys:ApplyForceOffset( left, yaw ) + phys:ApplyForceOffset( left * -1, yaw * -1 ) + end + + -- apply roll force + if angForce[3] ~= 0 then + local roll = left * (angForce[3] * 0.5) + phys:ApplyForceOffset( up, roll ) + phys:ApplyForceOffset( up * -1, roll * -1 ) + end +end + +--- Applies torque according to a local torque vector, with magnitude and sense given by the vector's direction, magnitude and orientation. +e2function void entity:applyTorque(vector torque) + if not IsValid(this) then return end + if not isOwner(self, this) then return end + + if torque[1] == 0 and torque[2] == 0 and torque[3] == 0 then return end + torque = clamp(torque) + + local phys = this:GetPhysicsObject() + + local tq = Vector(torque[1], torque[2], torque[3]) + local torqueamount = tq:Length() + + -- Convert torque from local to world axis + tq = phys:LocalToWorld( tq ) - phys:GetPos() + + -- Find two vectors perpendicular to the torque axis + local off + if abs(tq.x) > torqueamount * 0.1 or abs(tq.z) > torqueamount * 0.1 then + off = Vector(-tq.z, 0, tq.x) + else + off = Vector(-tq.y, tq.x, 0) + end + off = off:GetNormal() * torqueamount * 0.5 + + local dir = ( tq:Cross(off) ):GetNormal() + + dir = clamp(dir) + off = clamp(off) + + phys:ApplyForceOffset( dir, off ) + phys:ApplyForceOffset( dir * -1, off * -1 ) +end + +e2function vector entity:inertia() + if not validPhysics(this) then return {0,0,0} end + return this:GetPhysicsObject():GetInertia() +end + + +/******************************************************************************/ + +__e2setcost(10) -- temporary + +e2function void entity:lockPod(lock) + if not IsValid(this) or not this:IsVehicle() then return end + if not isOwner(self, this) then return end + if lock ~= 0 then + this:Fire("Lock", "", 0) + else + this:Fire("Unlock", "", 0) + end +end + +e2function void entity:killPod() + if not IsValid(this) or not this:IsVehicle() then return end + if not isOwner(self, this) then return end + local ply = this:GetDriver() + if IsValid(ply) then ply:Kill() end +end + +e2function void entity:ejectPod() + if not IsValid(this) or not this:IsVehicle() then return end + if not isOwner(self, this) then return end + local ply = this:GetDriver() + if IsValid(ply) then ply:ExitVehicle() end +end + +e2function void entity:podStripWeapons() + if not IsValid(this) or not this:IsVehicle() then return end + if not isOwner(self, this) then return end + local ply = this:GetDriver() + if IsValid(ply) and next(ply:GetWeapons()) ~= nil then + ply:StripWeapons() + ply:ChatPrint("Your weapons have been stripped!") + end +end + +/******************************************************************************/ + +__e2setcost(10) + +e2function vector entity:boxSize() + if not IsValid(this) then return {0,0,0} end + return this:OBBMaxs() - this:OBBMins() +end + +e2function vector entity:boxCenter() + if not IsValid(this) then return {0,0,0} end + return this:OBBCenter() +end + +-- Same as using E:toWorld(E:boxCenter()) in E2, but since Lua runs faster, this is more efficient. +e2function vector entity:boxCenterW() + if not IsValid(this) then return {0,0,0} end + return this:LocalToWorld(this:OBBCenter()) +end + +e2function vector entity:boxMax() + if not IsValid(this) then return {0,0,0} end + return this:OBBMaxs() +end + +e2function vector entity:boxMin() + if not IsValid(this) then return {0,0,0} end + return this:OBBMins() +end + + +/******************************************************************************/ + +-- Returns the entity's (min) axis-aligned bounding box +e2function vector entity:aabbMin() + if not IsValid(this) or not IsValid(this:GetPhysicsObject()) then return {0,0,0} end + local ret, _ = this:GetPhysicsObject():GetAABB() + return ret or {0,0,0} +end + +-- Returns the entity's (max) axis-aligned bounding box +e2function vector entity:aabbMax() + if not IsValid(this) or not IsValid(this:GetPhysicsObject()) then return {0,0,0} end + local _, ret = this:GetPhysicsObject():GetAABB() + return ret or {0,0,0} +end + +-- Returns the entity's axis-aligned bounding box size +e2function vector entity:aabbSize() + if not IsValid(this) or not IsValid(this:GetPhysicsObject()) then return {0,0,0} end + local ret, ret2 = this:GetPhysicsObject():GetAABB() + ret = ret or Vector(0,0,0) + ret2 = ret2 or Vector(0,0,0) + return ret2 - ret +end + + +/******************************************************************************/ + +-- Returns the rotated entity's min world-axis-aligned bounding box corner +e2function vector entity:aabbWorldMin() + if not IsValid(this) then return {0,0,0} end + local ret, _ = this:WorldSpaceAABB() + return ret or {0,0,0} +end + +-- Returns the rotated entity's max world-axis-aligned bounding box corner +e2function vector entity:aabbWorldMax() + if not IsValid(this) then return {0,0,0} end + local _, ret = this:WorldSpaceAABB() + return ret or {0,0,0} +end + +-- Returns the rotated entity's world-axis-aligned bounding box size +e2function vector entity:aabbWorldSize() + if not IsValid(this) then return {0,0,0} end + local ret, ret2 = this:WorldSpaceAABB() + ret = ret or Vector(0,0,0) + ret2 = ret2 or Vector(0,0,0) + return ret2 - ret +end +/******************************************************************************/ + +__e2setcost(5) + +e2function entity entity:driver() + if not IsValid(this) or not this:IsVehicle() then return nil end + return this:GetDriver() +end + +e2function entity entity:passenger() + if not IsValid(this) or not this:IsVehicle() then return nil end + return this:GetPassenger(0) +end + +--- Returns formatted as a string. Returns "(null)" for invalid entities. +e2function string toString(entity ent) + if not IsValid(ent) then return "(null)" end + return tostring(ent) +end + +e2function string entity:toString() = e2function string toString(entity ent) + +/******************************************************************************/ + +local SetTrails = duplicator.EntityModifiers.trail + +--- Removes the trail from . +e2function void entity:removeTrails() + if not checkOwner(self) then return end + if not IsValid(this) then return end + if not isOwner(self, this) then return end + + SetTrails(self.player, this, nil) +end + +local function composedata(startSize, endSize, length, material, color, alpha) + if string.find(material, '"', 1, true) then return nil end + + endSize = math.Clamp( endSize, 0, 128 ) + startSize = math.Clamp( startSize, 0, 128 ) + + return { + Color = Color( color[1], color[2], color[3], alpha ), + Length = length, + StartSize = startSize, + EndSize = endSize, + Material = material, + } +end + +__e2setcost(30) + +--- StartSize, EndSize, Length, Material, Color (RGB), Alpha +--- Adds a trail to with the specified attributes. +e2function void entity:setTrails(startSize, endSize, length, string material, vector color, alpha) + if not checkOwner(self) then return end + if not IsValid(this) then return end + if not isOwner(self, this) then return end + + local Data = composedata(startSize, endSize, length, material, color, alpha) + if not Data then return end + + SetTrails(self.player, this, Data) +end + + +--- StartSize, EndSize, Length, Material, Color (RGB), Alpha, AttachmentID, Additive +--- Adds a trail to with the specified attributes. +e2function void entity:setTrails(startSize, endSize, length, string material, vector color, alpha, attachmentID, additive) + if not checkOwner(self) then return end + if not IsValid(this) then return end + if not isOwner(self, this) then return end + + local Data = composedata(startSize, endSize, length, material, color, alpha) + if not Data then return end + + Data.AttachmentID = attachmentID + Data.Additive = additive ~= 0 + + SetTrails(self.player, this, Data) +end + +/******************************************************************************/ + +__e2setcost( 15 ) + +--- Returns 's attachment ID associated with +e2function number entity:lookupAttachment(string attachmentName) + if not IsValid(this) then return 0 end + return this:LookupAttachment(attachmentName) +end + +--- Returns 's attachment position associated with +e2function vector entity:attachmentPos(attachmentID) + if not IsValid(this) then return { 0, 0, 0 } end + local attachment = this:GetAttachment(attachmentID) + if not attachment then return { 0, 0, 0 } end + return attachment.Pos +end + +--- Returns 's attachment angle associated with +e2function angle entity:attachmentAng(attachmentID) + if not IsValid(this) then return { 0, 0, 0 } end + local attachment = this:GetAttachment(attachmentID) + if not attachment then return { 0, 0, 0 } end + local ang = attachment.Ang + return { ang.p, ang.y, ang.r } +end + +--- Same as :attachmentPos(entity:lookupAttachment()) +e2function vector entity:attachmentPos(string attachmentName) + if not IsValid(this) then return { 0, 0, 0 } end + local attachment = this:GetAttachment(this:LookupAttachment(attachmentName)) + if not attachment then return { 0, 0, 0 } end + return attachment.Pos +end + +--- Same as :attachmentAng(entity:lookupAttachment()) +e2function angle entity:attachmentAng(string attachmentName) + if not IsValid(this) then return { 0, 0, 0 } end + local attachment = this:GetAttachment(this:LookupAttachment(attachmentName)) + if not attachment then return { 0, 0, 0 } end + local ang = attachment.Ang + return { ang.p, ang.y, ang.r } +end + +__e2setcost(20) + +-- Returns a table containing all attachments for +e2function array entity:attachments() + if not IsValid(this) then return {} end + local tmp = {} + local atc = this:GetAttachments() + for i=1, #atc do + tmp[i] = atc[i].name + end + return tmp +end + +/******************************************************************************/ + +__e2setcost(15) + +e2function vector entity:nearestPoint( vector point ) + if not IsValid(this) then return {0,0,0} end + return this:NearestPoint( Vector(point[1],point[2],point[3]) ) +end + +/******************************************************************************/ + +local function upperfirst( word ) + return word:Left(1):upper() .. word:Right(-2):lower() +end + +local function fixdef( def ) + return istable(def) and table.Copy(def) or def +end + +local non_allowed_types = { + xgt = true, + t = true, + r = true, + } + +registerCallback("postinit",function() + for k,v in pairs( wire_expression_types ) do + if not non_allowed_types[v[1]] then + if k == "NORMAL" then k = "NUMBER" end + k = upperfirst(k) + + __e2setcost(5) + + local function getf( self, args ) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if not IsValid(rv1) or not rv2 then return fixdef( v[2] ) end + local id = self.uid + if not rv1["EVar_"..id] then return fixdef( v[2] ) end + return rv1["EVar_"..id][rv2] or fixdef( v[2] ) + end + + local function setf( self, args ) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + local id = self.uid + if not IsValid(rv1) or not rv2 or not rv3 then return end + if not rv1["EVar_"..id] then + rv1["EVar_"..id] = {} + end + rv1["EVar_"..id][rv2] = rv3 + return rv3 + end + + registerOperator("idx", v[1].."=es", v[1], getf) + registerOperator("idx", v[1].."=es"..v[1], v[1], setf) + end -- allowed check + end -- loop +end) -- postinit diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/extloader.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/extloader.lua new file mode 100644 index 0000000..bdf17d7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/extloader.lua @@ -0,0 +1,174 @@ +--[[ + Loading extensions +]] + +wire_expression2_PreLoadExtensions() + +-- Save E2's metatable for wire_expression2_reload +if ENT then + + local wire_expression2_ENT = ENT + + function wire_expression2_reload(ply, cmd, args) + if IsValid( ply ) and not ply:IsSuperAdmin() and not game.SinglePlayer() then + ply:PrintMessage( 2, "Sorry " .. ply:Name() .. ", you don't have access to this command." ) + return + end + + local function _Msg( str ) + if IsValid( ply ) then ply:PrintMessage( 2, str ) end + if not game.SinglePlayer() then MsgN( str ) end + end + + timer.Destroy( "E2_AutoReloadTimer" ) + + _Msg( "Calling destructors for all Expression 2 chips." ) + local chips = ents.FindByClass( "gmod_wire_expression2" ) + for _, chip in ipairs( chips ) do + if not chip.error then + chip:PCallHook( "destruct" ) + end + chip.script = nil + end + + _Msg( "Reloading Expression 2 extensions." ) + ENT = wire_expression2_ENT + wire_expression2_is_reload = true + include( "entities/gmod_wire_expression2/core/extloader.lua" ) + wire_expression2_is_reload = nil + ENT = nil + + _Msg( "Calling constructors for all Expression 2 chips." ) + wire_expression2_prepare_functiondata() + if not args or args[1] ~= "nosend" then + for _, p in ipairs( player.GetAll() ) do + if IsValid( p ) then wire_expression2_sendfunctions( p ) end + end + end + for _, chip in ipairs( chips ) do + pcall( chip.OnRestore, chip ) + end + + _Msg( "Done reloading Expression 2 extensions." ) + end + + concommand.Add( "wire_expression2_reload", wire_expression2_reload ) + +end + +wire_expression2_reset_extensions() + +include("extpp.lua") + +local included_files + +local function e2_include_init() + e2_extpp_init() + included_files = {} +end + +-- parses typename/typeid associations from a file and stores info about the file for later use by e2_include_finalize/e2_include_pass2 +local function e2_include(name) + local path, filename = string.match(name, "^(.-/?)([^/]*)$") + + local luaname = "entities/gmod_wire_expression2/core/" .. name + local contents = file.Read(luaname, "LUA") or "" + e2_extpp_pass1(contents) + table.insert(included_files, { name, luaname, contents }) +end + +-- parses and executes an extension +local function e2_include_pass2(name, luaname, contents) + local preprocessedSource = e2_extpp_pass2(contents) + + if not preprocessedSource then return include(name) end + + local func = CompileString(preprocessedSource, luaname) + + local ok, err = pcall(func) + if not ok then -- an error occured while executing + if not err:find( "EXTENSION_DISABLED" ) then + error(err, 0) + end + return + end + + __e2setcost(nil) -- Reset ops cost at the end of each file +end + +local function e2_include_finalize() + for _, info in ipairs(included_files) do + local ok, message = pcall(e2_include_pass2, unpack(info)) + if not ok then + WireLib.ErrorNoHalt(string.format("There was an error loading " .. + "the %s extension. Please report this to its developer.\n%s\n", + info[1], message)) + end + end + included_files = nil + e2_include = nil +end + +-- end preprocessor stuff + +e2_include_init() + +e2_include("core.lua") +e2_include("array.lua") +e2_include("number.lua") +e2_include("vector.lua") +e2_include("string.lua") +e2_include("angle.lua") +e2_include("entity.lua") +e2_include("player.lua") +e2_include("timer.lua") +e2_include("selfaware.lua") +e2_include("unitconv.lua") +e2_include("wirelink.lua") +e2_include("console.lua") +e2_include("find.lua") +e2_include("files.lua") +e2_include("cl_files.lua") +e2_include("globalvars.lua") +e2_include("ranger.lua") +e2_include("sound.lua") +e2_include("color.lua") +e2_include("serverinfo.lua") +e2_include("chat.lua") +e2_include("constraint.lua") +e2_include("weapon.lua") +e2_include("gametick.lua") +e2_include("npc.lua") +e2_include("matrix.lua") +e2_include("vector2.lua") +e2_include("signal.lua") +e2_include("bone.lua") +e2_include("table.lua") +e2_include("serialization.lua") +e2_include("hologram.lua") +e2_include("complex.lua") +e2_include("bitwise.lua") +e2_include("quaternion.lua") +e2_include("debug.lua") +e2_include("http.lua") +e2_include("compat.lua") +e2_include("custom.lua") +e2_include("datasignal.lua") +e2_include("egpfunctions.lua") +e2_include("functions.lua") +e2_include("strfunc.lua") +e2_include("steamidconv.lua") + +-- Load serverside files here, they need additional parsing +do + local list = file.Find("entities/gmod_wire_expression2/core/custom/*.lua", "LUA") + for _, filename in pairs(list) do + if filename:sub(1, 3) ~= "cl_" then + e2_include("custom/" .. filename) + end + end +end + +e2_include_finalize() +wire_expression2_CallHook("postinit") +wire_expression2_PostLoadExtensions() diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/extpp.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/extpp.lua new file mode 100644 index 0000000..b01e72b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/extpp.lua @@ -0,0 +1,399 @@ +AddCSLuaFile() + +-- some constants -- + +local p_typename = "[a-z][a-z0-9]*" +local p_typeid = "[a-z][a-z0-9]?[a-z0-9]?[a-z0-9]?[a-z0-9]?" +local p_argname = "[a-zA-Z][a-zA-Z0-9_]*" +local p_funcname = "[a-z][a-zA-Z0-9]*" +local p_func_operator = "[-a-zA-Z0-9+*/%%^=!><&|$_%[%]]*" + +local OPTYPE_FUNCTION +local OPTYPE_NORMAL = 0 +local OPTYPE_DONT_FETCH_FIRST = 1 +local OPTYPE_ASSIGN = 2 +local OPTYPE_APPEND_RET = 3 + +local optable = { + ["operator+"] = "add", + ["operator++"] = { "inc", OPTYPE_DONT_FETCH_FIRST }, + ["operator-"] = "sub", + ["operator--"] = { "dec", OPTYPE_DONT_FETCH_FIRST }, + ["operator*"] = "mul", + ["operator/"] = "div", + ["operator%"] = "mod", + ["operator^"] = "exp", + ["operator="] = { "ass", OPTYPE_ASSIGN }, + ["operator=="] = "eq", + ["operator!"] = "not", + ["operator!="] = "neq", + ["operator>"] = "gth", + ["operator>="] = "geq", + ["operator<"] = "lth", + ["operator<="] = "leq", + ["operator&"] = "and", + ["operator&&"] = "and", + ["operator|"] = "or", + ["operator||"] = "or", + ["operator[]"] = "idx", -- typeless op[] + ["operator[T]"] = { "idx", OPTYPE_APPEND_RET }, -- typed op[] + + ["operator_is"] = "is", + ["operator_neg"] = "neg", + ["operator_band"] = "band", + ["operator_bor"] = "bor", + ["operator_bxor"] = "bxor", + ["operator_bshl"] = "bshl", + ["operator_bshr"] = "bshr", +} + +-- This is an array for types that were parsed from all E2 extensions. +local preparsed_types + +-- This function initialized extpp's dynamic fields +function e2_extpp_init() + -- We initialize the array of preparsed types with an alias "number" for "normal". + preparsed_types = { ["NUMBER"] = "n" } +end + +-- This function checks whether its argument is a valid type id. +local function is_valid_typeid(typeid) + return (typeid:match("^[a-wy-zA-WY-Z]$") or typeid:match("^[xX][a-wy-zA-WY-Z0-9][a-wy-zA-WY-Z0-9]$")) and true +end + +-- Returns the typeid associated with the given typename +function e2_get_typeid(typename) + local n = string.upper(typename) + + -- was the type registered with E-2? + if wire_expression_types[n] then return wire_expression_types[n][1] end + + -- was the name found when looking for registerType lines? + if preparsed_types[n] then return preparsed_types[n] end + + -- is the type name a valid typeid? use the type name as the typeid + if is_valid_typeid(typename) then return typename end + return nil +end + +-- parses an argument list +function e2_parse_args(args) + local ellipses = false + local argtable = { typeids = {}, argnames = {} } + if args:find("%S") == nil then return argtable end -- no arguments + local function handle_arg(arg) + -- ellipses before this argument? raise error + if ellipses then error("PP syntax error: Ellipses (...) must be the last argument.", 0) end + -- is this argument an ellipsis? + if string.match(arg, "^%s*%.%.%.%s*$") then + -- signal ellipses-ness + ellipses = true + return false + end + + -- assume a type name was given and split up the argument into type name and argument name. + local typename, argname = string.match(arg, "^%s*(" .. p_typename .. ")%s+(" .. p_argname .. ")%s*$") + + -- the assumption failed + if not typename then + -- try looking for a argument name only and defaulting the type name to "number" + argname = string.match(arg, "^%s*(" .. p_argname .. ")%s*$") + typename = "number" + end + + -- this failed as well? give up and print an error + if not argname then error("PP syntax error: Invalid function parameter syntax.", 0) end + + local typeid = e2_get_typeid(typename) + + if not typeid then error("PP syntax error: Invalid parameter type '" .. typename .. "' for argument '" .. argname .. "'.", 0) end + + table.insert(argtable.typeids, typeid) + table.insert(argtable.argnames, argname) + return false + end + + -- find the argument before the first comma + local firstarg = args:find(",") or (args:len() + 1) + firstarg = args:sub(1, firstarg - 1) + -- handle it + handle_arg(firstarg) + + -- find and handle the remaining arguments. + args:gsub(",([^,]*)", handle_arg) + return argtable, ellipses +end + +local function mangle(name, arg_typeids, op_type) + if op_type then name = "operator_" .. name end + local ret = "e2_" .. name + if arg_typeids == "" then return ret end + return ret .. "_" .. arg_typeids:gsub("[:=]", "_") +end + +local function linenumber(s, i) + local c = 1 + local line = 0 + while c and c < i do + line = line + 1 + c = s:find("\n", c + 1, true) + end + return line +end + +-- returns a name and a register function for the given name +-- also optionally returns a flag signaling how to treat the operator in question. +local function handleop(name) + local operator = optable[name] + + if operator then + local op_type = OPTYPE_NORMAL + + -- special treatment is needed for some operators. + if istable(operator) then operator, op_type = unpack(operator) end + + -- return everything. + return operator, "registerOperator", op_type + elseif name:find("^" .. p_funcname .. "$") then + return name, "registerFunction", OPTYPE_FUNCTION + else + error("PP syntax error: Invalid character in function name.", 0) + end +end + +local function makestringtable(tbl, i, j) + if #tbl == 0 then return "{}" end + if not i then i = 1 + elseif i < 0 then i = #tbl + i + 1 + elseif i < 1 then i = 1 + elseif i > #tbl then i = #tbl + end + + if not j then j = #tbl + elseif j < 0 then j = #tbl + j + 1 + elseif j < 1 then j = 1 + elseif j > #tbl then j = #tbl + end + + --return string.format("{"..string.rep("%q,", math.max(0,j-i+1)).."}", unpack(tbl, i, j)) + local ok, ret = pcall(string.format, "{" .. string.rep("%q,", math.max(0, j - i + 1)) .. "}", unpack(tbl, i, j)) + if not ok then + print(i, j, #tbl, "{" .. string.rep("%q,", math.max(0, j - i + 1)) .. "}") + error(ret) + end + return ret +end + +function e2_extpp_pass1(contents) + -- look for registerType lines and fill preparsed_types with them + for typename, typeid in string.gmatch("\n" .. contents, '%WregisterType%(%s*"(' .. p_typename .. ')"%s*,%s*"(' .. p_typeid .. ')"') do + preparsed_types[string.upper(typename)] = typeid + end +end + +function e2_extpp_pass2(contents) + -- We add some stuff to both ends of the string so we can look for %W (non-word characters) at the ends of the patterns. + contents = "\nlocal tempcosts,registeredfunctions={},{}" .. contents .. "\n " + + -- this is a list of pieces that make up the final code + local output = {} + -- this is a list of registerFunction lines that will be put at the end of the file. + local function_register = {} + -- We start from position 2, since char #1 is always the \n we added earlier + local lastpos = 2 + + local aliaspos, aliasdata = nil, nil + + -- This flag helps determine whether the preprocessor changed, so we can tell the environment about it. + local changed = false + for h_begin, ret, thistype, colon, name, args, whitespace, equals, h_end in contents:gmatch("()e2function%s+(" .. p_typename .. ")%s+([a-z0-9]-)%s*(:?)%s*(" .. p_func_operator .. ")%(([^)]*)%)(%s*)(=?)()") do + changed = true + + local function handle_function() + if contents:sub(h_begin - 1, h_begin - 1):match("%w") then return end + local aliasflag = nil + if equals == "" then + if aliaspos then + if contents:sub(aliaspos, h_begin - 1):find("%S") then error("PP syntax error: Malformed alias definition.", 0) end + -- right hand side of an alias assignment + aliasflag = 2 + aliaspos = nil + end + else + if aliaspos then error("PP syntax error: Malformed alias definition.", 0) end + -- left hand side of an alias assignment + aliasflag = 1 + aliaspos = h_end + end + + -- check for some obvious errors + if thistype ~= "" and colon == "" then error("PP syntax error: Function names may not start with a number.", 0) end + if thistype == "" and colon ~= "" then error("PP syntax error: No type for 'this' given.", 0) end + if thistype:match("^[0-9]") then error("PP syntax error: Type names may not start with a number.", 0) end + + -- append everything since the last function to the output. + table.insert(output, contents:sub(lastpos, h_begin - 1)) + -- advance lastpos to the end of the function header + lastpos = h_end + + -- this table contains the arguments in the following form: + -- argtable.argname[n] = "" + -- argtable.typeids[n] = "" + local argtable, ellipses = e2_parse_args(args) + + -- take care of operators: give them a different name and register function + -- op_type is nil if we register a function and a number if it as operator + local name, regfn, op_type = handleop(name) + + -- return type (void means "returns nothing", i.e. "" in registerFunctionese) + local ret_typeid = (ret == "void") and "" or e2_get_typeid(ret) -- ret_typeid = (ret == "void") ? "" : e2_get_typeid(ret) + + -- return type not found => throw an error + if not ret_typeid then error("PP syntax error: Invalid return type: '" .. ret .. "'", 0) end + + -- if "typename:" was found in front of the function name + if thistype ~= "" then + -- evaluate the type name + local this_typeid = e2_get_typeid(thistype) + + -- the type was not found? + if this_typeid == nil then + -- is the type name a valid typeid? + if is_valid_typeid(thistype) then + -- use the type name as the typeid + this_typeid = thistype + else + -- type is not found and not a valid typeid => error + error("PP syntax error: Invalid type for 'this': '" .. thistype .. "'", 0) + end + end + + -- prepend a "this" argument to the list, with the parsed type + if op_type then + -- allow pseudo-member-operators. example: e2function matrix:operator*(factor) + table.insert(argtable.typeids, 1, this_typeid) + else + table.insert(argtable.typeids, 1, this_typeid .. ":") + end + table.insert(argtable.argnames, 1, "this") + end -- if thistype ~= "" + + -- add a sub-table for flagging arguments as "no opfetch" + argtable.no_opfetch = {} + + if op_type == OPTYPE_ASSIGN then -- assignment + -- the assignment operator is registered with only argument typeid, hence we need a special case. + -- we need to make sure the two types match: + if argtable.typeids[1] ~= argtable.typeids[2] then error("PP syntax error: operator= needs two arguments of the same type.", 0) end + + -- remove the typeid of one of the arguments from the list + argtable.typeids[1] = "" + + -- mark the argument as "no opfetch" + argtable.no_opfetch[1] = true + elseif op_type == OPTYPE_DONT_FETCH_FIRST then -- delta/increment/decrement + -- mark the argument as "no opfetch" + argtable.no_opfetch[1] = true + elseif op_type == OPTYPE_APPEND_RET then + table.insert(argtable.typeids, 1, ret_typeid .. "=") + end + + -- -- prepare some variables needed to generate the function header and the registerFunction line -- -- + + -- concatenated typeids. example: "s:nn" + local arg_typeids = table.concat(argtable.typeids) + + -- generate a mangled name, which serves as the function's Lua name + local mangled_name = mangle(name, arg_typeids, op_type) + + if aliasflag then + if aliasflag == 1 then + -- left hand side of an alias definition + aliasdata = { regfn, name, arg_typeids, ret_typeid } + elseif aliasflag == 2 then + -- right hand side of an alias definition + regfn, name, arg_typeids, ret_typeid = unpack(aliasdata) + table.insert(function_register, ('if registeredfunctions.%s then %s(%q, %q, %q, registeredfunctions.%s, tempcosts[%q], %s) end\n'):format(mangled_name, regfn, name, arg_typeids, ret_typeid, mangled_name, mangled_name, makestringtable(argtable.argnames, (thistype ~= "") and 2 or 1))) + end + else + -- save tempcost + table.insert(output, string.format("tempcosts[%q]=__e2getcost() ", mangled_name)) + if ellipses then + -- generate a registerFunction line + table.insert(function_register, string.format('if registeredfunctions.%s then %s(%q, %q, %q, registeredfunctions.%s, tempcosts[%q], %s) end\n', mangled_name, regfn, name, arg_typeids .. "...", ret_typeid, mangled_name, mangled_name, makestringtable(argtable.argnames, (thistype ~= "") and 2 or 1))) + + -- generate a new function header and append it to the output + table.insert(output, 'function registeredfunctions.' .. mangled_name .. '(self, args, typeids, ...)') + table.insert(output, " if not typeids then") + table.insert(output, " local arr,typeids,source_typeids,tmp={},{},args[#args]") + table.insert(output, " for i=" .. (2 + #argtable.typeids) .. ",#args-1 do") + table.insert(output, " tmp=args[i]") + table.insert(output, " arr[#arr+1]=tmp[1](self,tmp)") + if thistype ~= "" then + -- offset to compensate the absence of this's typeid + table.insert(output, " typeids[#typeids+1]=source_typeids[i-2]") + else + table.insert(output, " typeids[#typeids+1]=source_typeids[i-1]") + end + table.insert(output, " end") + table.insert(output, " return registeredfunctions." .. mangled_name .. "(self,args,typeids,unpack(arr))") + table.insert(output, " end") + else + -- generate a registerFunction line + table.insert(function_register, string.format('if registeredfunctions.%s then %s(%q, %q, %q, registeredfunctions.%s, tempcosts[%q], %s) end\n', mangled_name, regfn, name, arg_typeids, ret_typeid, mangled_name, mangled_name, makestringtable(argtable.argnames, (thistype ~= "") and 2 or 1))) + + -- generate a new function header and append it to the output + table.insert(output, 'function registeredfunctions.' .. mangled_name .. '(self, args)') + end + + -- if the function has arguments, insert argument fetch code + if #argtable.argnames ~= 0 then + local argfetch, opfetch_l, opfetch_r = '', '', '' + for i, name in ipairs(argtable.argnames) do + if not argtable.no_opfetch[i] then + -- generate opfetch code if not flagged as "no opfetch" + opfetch_l = string.format('%s%s, ', opfetch_l, name) + opfetch_r = string.format('%s%s[1](self, %s), ', opfetch_r, name, name) + end + argfetch = string.format('%sargs[%d], ', argfetch, i + 1) + end + + -- remove the trailing commas + argfetch = argfetch:sub(1, -3) + opfetch_l = opfetch_l:sub(1, -3) + opfetch_r = opfetch_r:sub(1, -3) + + -- fetch the rvs from the args + table.insert(output, string.format(' local %s = %s %s = %s', + table.concat(argtable.argnames, ', '), + argfetch, + opfetch_l, + opfetch_r)) + end -- if #argtable.argnames ~= 0 + end -- if aliasflag + table.insert(output, whitespace) + end + + -- function handle_function() + + -- use pcall, so we can add line numbers to all errors + local ok, msg = pcall(handle_function) + if not ok then + if msg:sub(1, 2) == "PP" then + error(":" .. linenumber(contents, h_begin) .. ": " .. msg, 0) + else + error(": PP internal error: " .. msg, 0) + end + end + end -- for contents:gmatch(e2function) + + + -- did the preprocessor change anything? + if changed then + -- yes => sweep everything together into a neat pile of hopefully valid lua code + return table.concat(output) .. contents:sub(lastpos, -6) .. table.concat(function_register) + else + -- no => tell the environment about it, so it can include() the source file instead. + return false + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/files.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/files.lua new file mode 100644 index 0000000..9dedfa5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/files.lua @@ -0,0 +1,469 @@ +--[[ + File Extension + By: Dan (McLovin) +]]-- + +local cv_transfer_delay = CreateConVar( "wire_expression2_file_delay", "5", { FCVAR_ARCHIVE } ) +local cv_max_transfer_size = CreateConVar( "wire_expression2_file_max_size", "300", { FCVAR_REPLICATED, FCVAR_ARCHIVE } ) -- in kib + +local download_chunk_size = 20000 -- Our overhead is pretty small so lets send it in moderate sized pieces, no need to max out the buffer + +E2Lib.RegisterExtension( "file", true, "Allows reading and writing of files in the player's local data directory." ) + +local FILE_UNKNOWN = 0 +local FILE_OK = 1 +local FILE_TIMEOUT = 2 +local FILE_404 = 3 +local FILE_TRANSFER_ERROR = 4 + +E2Lib.registerConstant( "FILE_UNKNOWN", FILE_UNKNOWN ) +E2Lib.registerConstant( "FILE_OK", FILE_OK ) +E2Lib.registerConstant( "FILE_TIMEOUT", FILE_TIMEOUT ) +E2Lib.registerConstant( "FILE_404", FILE_404 ) +E2Lib.registerConstant( "FILE_TRANSFER_ERROR", FILE_TRANSFER_ERROR ) + +local delays = {} +local uploads = {} +local downloads = {} +local lists = {} +local run_on = { + file = { + run = 0, + name = "", + ents = {}, + status = FILE_UNKNOWN + }, + list = { + run = 0, + dir = "", + ents = {} + } +} + +local function file_canUpload( ply ) + local pfile = uploads[ply] + local pdel = (delays[ply] or {}).upload + + if (pfile and (pfile.uploading or pfile.sp_wait)) or + (pdel and not ply:IsListenServerHost() and (CurTime() - pdel) < cv_transfer_delay:GetInt()) then return false end + + return true +end + +util.AddNetworkString("wire_expression2_request_file_sp") +util.AddNetworkString("wire_expression2_request_file") +local function file_Upload( ply, entity, filename ) + if !file_canUpload( ply ) or !IsValid( entity ) or !IsValid( ply ) or !ply:IsPlayer() or string.Right( filename, 4 ) != ".txt" then return false end + + uploads[ply] = { + name = filename, + uploading = false, --don't halt other uploads incase file does not exist + uploaded = false, + data = "", + ent = entity, + sp_wait = ply:IsListenServerHost() + } + net.Start(uploads[ply].sp_wait and "wire_expression2_request_file_sp" or "wire_expression2_request_file") + net.WriteString( filename ) + net.Send(ply) + + delays[ply].upload = CurTime() +end + + +local function file_canDownload( ply ) + local pfile = downloads[ply] + local pdel = (delays[ply] or {}).download + + if (pfile and pfile.downloading) or + (pdel and (CurTime() - pdel) < cv_transfer_delay:GetInt()) then return false end + + return true +end + +local function file_Download( ply, filename, data, append ) + if !file_canDownload( ply ) or !IsValid( ply ) or !ply:IsPlayer() or string.Right( filename, 4 ) != ".txt" then return false end + if string.len( data ) > (cv_max_transfer_size:GetInt() * 1024) then return false end + + -- if we're trying to append an empty string then we don't need to queue up + -- a download to consider the operation completed + if append and string.len(data) == 0 then return true end + + downloads[ply] = { + name = filename, + data = data, + started = false, + downloading = true, + downloaded = false, + append = append + } +end + + +local function file_canList( ply ) + local plist = lists[ply] + local pdel = (delays[ply] or {}).list + + if (plist and plist.uploading) or + (pdel and (CurTime() - pdel) < cv_transfer_delay:GetInt()) then return false end + + return true +end + +util.AddNetworkString("wire_expression2_request_list") +local function file_List( ply, entity, dir ) + if !file_canList( ply ) or !IsValid( ply ) or !ply:IsPlayer() then return false end + + lists[ply] = { + dir = dir, + data = {}, + uploading = true, + uploaded = false, + ent = entity + } + net.Start("wire_expression2_request_list") + net.WriteString( dir or "" ) + net.Send(ply) + + delays[ply].list = CurTime() +end + +--- File loading --- + +__e2setcost( 20 ) + +e2function void fileLoad( string filename ) + file_Upload( self.player, self.entity, filename ) +end + +__e2setcost( 5 ) + +e2function number fileCanLoad() + return file_canUpload( self.player ) and 1 or 0 +end + +e2function number fileLoaded() + local pfile = uploads[self.player] + + return (!pfile.uploading and pfile.uploaded) and 1 or 0 +end + +e2function number fileLoading() + local pfile = uploads[self.player] + + return pfile.uploading and 1 or 0 +end + +e2function number fileStatus() + return run_on.file.status or FILE_UNKNOWN +end + +--- File reading/writing --- + +e2function string fileName() + local pfile = uploads[self.player] + + if pfile.uploaded and !pfile.uploading then + return pfile.name + end + + return "" +end + +__e2setcost( 10 ) + +e2function string fileRead() + local pfile = uploads[self.player] + + return (pfile.uploaded and !pfile.uploading) and pfile.data or "" +end + +__e2setcost( 5 ) + +e2function number fileMaxSize() + return cv_max_transfer_size:GetInt() +end + +e2function number fileCanWrite() + return file_canDownload( self.player ) and 1 or 0 +end + +__e2setcost( 20 ) + +e2function void fileWrite( string filename, string data ) + file_Download( self.player, filename, data, false ) +end + +e2function void fileAppend( string filename, string data ) + file_Download( self.player, filename, data, true ) +end + +--- File Listing --- + +__e2setcost( 20 ) + +e2function void fileList( string dir ) + file_List( self.player, self.entity, dir ) +end + +__e2setcost( 5 ) + +e2function number fileCanList() + return file_canList( self.player ) and 1 or 0 +end + +e2function number fileLoadedList() + local plist = lists[self.player] + + return (!plist.uploading and plist.uploaded) and 1 or 0 +end + +e2function number fileLoadingList() + local plist = lists[self.player] + + return plist.uploading and 1 or 0 +end + +e2function array fileReadList() + local plist = lists[self.player] + + return (plist.uploaded and !plist.uploading and plist.data) and plist.data or {} +end + +--- runOnFile event --- + +__e2setcost( 5 ) + +e2function void runOnFile( active ) + run_on.file.ents[self.entity] = (active != 0) +end + +e2function number fileClk() + return self.data.runOnFile and 1 or 0 +end + +e2function number fileClk( string filename ) + return (self.data.runOnFile and run_on.file.name == filename) and 1 or 0 +end + +-- runOnList event --- + +__e2setcost( 5 ) + +e2function void runOnList( active ) + run_on.list.ents[self.entity] = (active != 0) +end + +e2function number fileListClk() + return self.data.runOnFileList and 1 or 0 +end + +e2function number fileListClk( string dir ) + return (self.data.runOnFileList and run_on.list.dir == dir) and 1 or 0 +end + +--- Hooks 'n' Shit --- + +registerCallback( "construct", function( self ) + uploads[self.player] = uploads[self.player] or { + uploading = false, + uploaded = false + } + downloads[self.player] = downloads[self.player] or { + downloading = false, + downloaded = false + } + lists[self.player] = lists[self.player] or { + uploading = false, + uploaded = false + } + delays[self.player] = delays[self.player] or { + upload = 0, + download = 0, + list = 0 + } +end ) + +--- Downloading --- +util.AddNetworkString("wire_expression2_file_download_begin") +util.AddNetworkString("wire_expression2_file_download_chunk") +util.AddNetworkString("wire_expresison2_file_download_finish") +timer.Create("wire_expression2_flush_file_buffer", 0.2, 0, function() + for ply,fdata in pairs( downloads ) do + if IsValid( ply ) and ply:IsPlayer() and fdata.downloading then + if !fdata.started then + net.Start("wire_expression2_file_download_begin") + net.WriteString(fdata.name or "") + net.Send(ply) + + fdata.started = true + end + + local strlen = math.Clamp( string.len( fdata.data ), 0, download_chunk_size ) + + if strlen > 0 then + net.Start("wire_expression2_file_download_chunk") + net.WriteUInt(strlen, 32) + net.WriteData(fdata.data, strlen) + net.Send(ply) + + fdata.data = string.sub( fdata.data, strlen + 1 ) + end + + if string.len( fdata.data ) < 1 then + net.Start("wire_expresison2_file_download_finish") + net.WriteBit(fdata.append or false) + net.Send(ply) + + fdata.downloaded = true + fdata.downloading = false + end + end + end +end) + +--- Uploading --- + +local function file_execute( ent, filename, status ) + if !IsValid( ent ) or !run_on.file.ents[ent] then return end + + run_on.file.run = 1 + run_on.file.name = filename + run_on.file.status = status + + ent.context.data.runOnFile = true + ent:Execute() + ent.context.data.runOnFile = nil + + run_on.file.run = 0 + run_on.file.name = "" + run_on.file.status = FILE_UNKNOWN +end + +util.AddNetworkString("wire_expression2_file_begin") +net.Receive("wire_expression2_file_begin", function(netlen, ply) + local pfile = uploads[ply] + if !pfile then return end + + local len = net.ReadUInt(32) + + if len == 0 then -- file not found + file_execute( pfile.ent, pfile.name, FILE_404 ) + return + end + if (len / 1024) > cv_max_transfer_size:GetInt() then return end + + pfile.buffer = "" + pfile.len = len + pfile.uploading = true + pfile.uploaded = false + + timer.Create( "wire_expression2_file_check_timeout_" .. ply:EntIndex(), 5, 1, function() + local pfile = uploads[ply] + if !pfile then return end + pfile.uploading = false + pfile.uploaded = false + file_execute( pfile.ent, pfile.name, FILE_TIMEOUT ) + end) +end ) + +util.AddNetworkString("wire_expression2_file_chunk") +net.Receive("wire_expression2_file_chunk", function(netlen, ply) + local pfile = uploads[ply] + if !pfile or !pfile.buffer then return end + if !pfile.uploading then + file_execute( pfile.ent, pfile.name, FILE_TRANSFER_ERROR ) + end + + local len = net.ReadUInt(32) + pfile.buffer = pfile.buffer .. net.ReadData(len) + + local timername = "wire_expression2_file_check_timeout_" .. ply:EntIndex() + if timer.Exists( timername ) then + timer.Create( timername, 5, 1, function() + local pfile = uploads[ply] + if !pfile then return end + pfile.uploading = false + pfile.uploaded = false + file_execute( pfile.ent, pfile.name, FILE_TIMEOUT ) + end) + end +end ) + +util.AddNetworkString("wire_expression2_file_finish") +net.Receive("wire_expression2_file_finish", function(netlen, ply) + local timername = "wire_expression2_file_check_timeout_" .. ply:EntIndex() + + if timer.Exists( timername ) then + timer.Remove( timername ) + end + + local pfile = uploads[ply] + if !pfile then return end + + pfile.uploading = false + pfile.data = E2Lib.decode( pfile.buffer ) + pfile.buffer = "" + + if string.len( pfile.data ) != pfile.len then -- transfer error + pfile.data = "" + file_execute( pfile.ent, pfile.name, FILE_TRANSFER_ERROR ) + return + end + pfile.uploaded = true + + file_execute( pfile.ent, pfile.name, FILE_OK ) +end ) + +concommand.Add("wire_expression2_file_singleplayer", function(ply, cmd, args) + if not ply:IsListenServerHost() then ply:Kick("Do not use wire_expression2_file_singleplayer in multiplayer, unless you're the host!") end + local pfile = uploads[ply] + if !pfile then return end + + local path = args[1] + if not file.Exists(path, "DATA") then + pfile.sp_wait = false + file_execute( pfile.ent, pfile.name, FILE_404 ) + return + end + + local timername = "wire_expression2_file_check_timeout_" .. ply:EntIndex() + + if timer.Exists(timername) then timer.Remove(timername) end + + + pfile.uploading = false + pfile.data = file.Read(path) + pfile.buffer = "" + pfile.uploaded = true + pfile.sp_wait = false + + file_execute(pfile.ent, pfile.name, FILE_OK) +end) + +--- Listing --- +util.AddNetworkString("wire_expression2_file_list") +net.Receive("wire_expression2_file_list", function(netlen, ply) + local plist = lists[ply] + if !plist then return end + + local timername = "wire_expression2_filelist_check_timeout_" .. ply:EntIndex() + if timer.Exists( timername ) then timer.Remove( timername ) end + + for i=1, net.ReadUInt(16) do + table.insert( plist.data, ( E2Lib.decode( net.ReadString() ) ) ) + end + + plist.uploaded = true + plist.uploading = false + + run_on.list.run = 1 + run_on.list.dir = plist.dir + + plist.ent.context.data.runOnFileList = true + plist.ent:Execute() + plist.ent.context.data.runOnFileList = nil + + run_on.list.run = 0 + run_on.list.dir = "" +end ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/find.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/find.lua new file mode 100644 index 0000000..064e50f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/find.lua @@ -0,0 +1,1025 @@ +E2Lib.RegisterExtension("find", true, "Allows an E2 to search for entities matching a filter.") + +local function table_IsEmpty(t) return not next(t) end + +local filterList = E2Lib.filterList + +local function replace_match(a,b) + return string.match( string.Replace(a,"-","__"), string.Replace(b,"-","__") ) +end + +-- -- some generic filter criteria -- -- + +local function filter_all() return true end +local function filter_none() return false end + +local forbidden_classes = { + --[[ + ["info_apc_missile_hint"] = true, + ["info_camera_link"] = true, + ["info_constraint_anchor"] = true, + ["info_hint"] = true, + ["info_intermission"] = true, + ["info_ladder_dismount"] = true, + ["info_landmark"] = true, + ["info_lighting"] = true, + ["info_mass_center"] = true, + ["info_no_dynamic_shadow"] = true, + ["info_node"] = true, + ["info_node_air"] = true, + ["info_node_air_hint"] = true, + ["info_node_climb"] = true, + ["info_node_hint"] = true, + ["info_node_link"] = true, + ["info_node_link_controller"] = true, + ["info_npc_spawn_destination"] = true, + ["info_null"] = true, + ["info_overlay"] = true, + ["info_particle_system"] = true, + ["info_projecteddecal"] = true, + ["info_snipertarget"] = true, + ["info_target"] = true, + ["info_target_gunshipcrash"] = true, + ["info_teleport_destination"] = true, + ["info_teleporter_countdown"] = true, + ]] + ["info_player_allies"] = true, + ["info_player_axis"] = true, + ["info_player_combine"] = true, + ["info_player_counterterrorist"] = true, + ["info_player_deathmatch"] = true, + ["info_player_logo"] = true, + ["info_player_rebel"] = true, + ["info_player_start"] = true, + ["info_player_terrorist"] = true, + ["info_player_blu"] = true, + ["info_player_red"] = true, + ["prop_dynamic"] = true, + ["physgun_beam"] = true, + ["player_manager"] = true, + ["predicted_viewmodel"] = true, + ["gmod_ghost"] = true, +} +local function filter_default(self) + local chip = self.entity + return function(ent) + if forbidden_classes[ent:GetClass()] then return false end + + if ent == chip then return false end + return true + end +end + +-- -- some filter criterion generators -- -- + +-- Generates a filter that filters out everything not in a lookup table. +local function filter_in_lookup(lookup) + if table_IsEmpty(lookup) then return filter_none end + + return function(ent) + return lookup[ent] + end +end + +-- Generates a filter that filters out everything in a lookup table. +local function filter_not_in_lookup(lookup) + if table_IsEmpty(lookup) then return filter_all end + + return function(ent) + return not lookup[ent] + end +end + +-- Generates a filter that filters out everything not in a lookup table. +local function filter_function_result_in_lookup(lookup, func) + if table_IsEmpty(lookup) then return filter_none end + + return function(ent) + return lookup[func(ent)] + end +end + +-- Generates a filter that filters out everything in a lookup table. +local function filter_function_result_not_in_lookup(lookup, func) + if table_IsEmpty(lookup) then return filter_all end + + return function(ent) + return not lookup[func(ent)] + end +end + +-- checks if binary_predicate(func(ent), key) matches for any of the keys in the lookup table. Returns false if it does. +local function filter_binary_predicate_match_none(lookup, func, binary_predicate) + if table_IsEmpty(lookup) then return filter_all end + + return function(a) + a = func(a) + for b,_ in pairs(lookup) do + if binary_predicate(a, b) then return false end + end + return true + end +end + +-- checks if binary_predicate(func(ent), key) matches for any of the keys in the lookup table. Returns true if it does. +local function filter_binary_predicate_match_one(lookup, func, binary_predicate) + if table_IsEmpty(lookup) then return filter_none end + + return function(a) + a = func(a) + for b,_ in pairs(lookup) do + if binary_predicate(a, b) then return true end + end + return false + end +end + + +-- -- filter criterion combiners -- -- + +local _filter_and = { + [0] = function() return filter_all end, + function(f1) return f1 end, + function(f1,f2) return function(v) return f1(v) and f2(v) end end, + function(f1,f2,f3) return function(v) return f1(v) and f2(v) and f3(v) end end, + function(f1,f2,f3,f4) return function(v) return f1(v) and f2(v) and f3(v) and f4(v) end end, + function(f1,f2,f3,f4,f5) return function(v) return f1(v) and f2(v) and f3(v) and f4(v) and f5(v) end end, + function(f1,f2,f3,f4,f5,f6) return function(v) return f1(v) and f2(v) and f3(v) and f4(v) and f5(v) and f6(v) end end, + function(f1,f2,f3,f4,f5,f6,f7) return function(v) return f1(v) and f2(v) and f3(v) and f4(v) and f5(v) and f6(v) and f7(v) end end, +} + +-- Usage: filter = filter_and(filter1, filter2, filter3) +local function filter_and(...) + local args = {...} + + -- filter out all filter_all entries + filterList(args, function(f) + if f == filter_none then + args = { filter_none } -- If a filter_none is in the list, we can discard all other filters. + end + return f ~= filter_all + end) + + local combiner = _filter_and[#args] + if not combiner then return nil end -- TODO: write generic combiner + return combiner(unpack(args)) +end + +local _filter_or = { + [0] = function() return filter_none end, + function(f1) return f1 end, + function(f1,f2) return function(v) return f1(v) or f2(v) end end, + function(f1,f2,f3) return function(v) return f1(v) or f2(v) or f3(v) end end, + function(f1,f2,f3,f4) return function(v) return f1(v) or f2(v) or f3(v) or f4(v) end end, + function(f1,f2,f3,f4,f5) return function(v) return f1(v) or f2(v) or f3(v) or f4(v) or f5(v) end end, + function(f1,f2,f3,f4,f5,f6) return function(v) return f1(v) or f2(v) or f3(v) or f4(v) or f5(v) or f6(v) end end, + function(f1,f2,f3,f4,f5,f6,f7) return function(v) return f1(v) or f2(v) or f3(v) or f4(v) or f5(v) or f6(v) or f7(v) end end, +} + +-- Usage: filter = filter_or(filter1, filter2, filter3) +local function filter_or(...) + local args = {...} + + -- filter out all filter_none entries + filterList(args, function(f) + if f == filter_all then + args = { filter_all } -- If a filter_all is in the list, we can discard all other filters. + end + return f ~= filter_none + end) + + local combiner = _filter_or[#args] + if not combiner then return nil end -- TODO: write generic combiner + return combiner(unpack(args)) +end + +local function invalidate_filters(self) + -- Update the filters the next time they are used. + self.data.findfilter = nil +end + +-- This function should be called after the black- or whitelists have changed. +local function update_filters(self) + -- Do not update again until the filters are invalidated the next time. + + local find = self.data.find + + --------------------- + -- blacklist -- + --------------------- + + -- blacklist for single entities + local bl_entity_filter = filter_not_in_lookup(find.bl_entity) + -- blacklist for a player's props + local bl_owner_filter = filter_function_result_not_in_lookup(find.bl_owner, function(ent) return getOwner(self,ent) end) + + -- blacklist for models + local bl_model_filter = filter_binary_predicate_match_none(find.bl_model, function(ent) return string.lower(ent:GetModel() or "") end, replace_match) + -- blacklist for classes + local bl_class_filter = filter_binary_predicate_match_none(find.bl_class, function(ent) return string.lower(ent:GetClass()) end, replace_match) + + -- combine all blacklist filters (done further down) + --local filter_blacklist = filter_and(bl_entity_filter, bl_owner_filter, bl_model_filter, bl_class_filter) + + --------------------- + -- whitelist -- + --------------------- + + local filter_whitelist = filter_all + + -- if not all whitelists are empty, use the whitelists. + local whiteListInUse = not (table_IsEmpty(find.wl_entity) and table_IsEmpty(find.wl_owner) and table_IsEmpty(find.wl_model) and table_IsEmpty(find.wl_class)) + + if whiteListInUse then + -- blacklist for single entities + local wl_entity_filter = filter_in_lookup(find.wl_entity) + -- blacklist for a player's props + local wl_owner_filter = filter_function_result_in_lookup(find.wl_owner, function(ent) return getOwner(self,ent) end) + + -- blacklist for models + local wl_model_filter = filter_binary_predicate_match_one(find.wl_model, function(ent) return string.lower(ent:GetModel() or "") end, replace_match) + -- blacklist for classes + local wl_class_filter = filter_binary_predicate_match_one(find.wl_class, function(ent) return string.lower(ent:GetClass()) end, replace_match) + + -- combine all whitelist filters + filter_whitelist = filter_or(wl_entity_filter, wl_owner_filter, wl_model_filter, wl_class_filter) + end + --------------------- + + -- finally combine all filters + --self.data.findfilter = filter_and(find.filter_default, filter_blacklist, filter_whitelist) + self.data.findfilter = filter_and(find.filter_default, bl_entity_filter, bl_owner_filter, bl_model_filter, bl_class_filter, filter_whitelist) +end + +local function applyFindList(self, findlist) + local findfilter = self.data.findfilter + if not findfilter then + update_filters(self) + findfilter = self.data.findfilter + end + filterList(findlist, findfilter) + + self.data.findlist = findlist + return #findlist +end + +--[[************************************************************************]]-- + + +local _findrate = CreateConVar("wire_expression2_find_rate", 0.05,{FCVAR_ARCHIVE,FCVAR_NOTIFY}) +local _maxfinds = CreateConVar("wire_expression2_find_max",10,{FCVAR_ARCHIVE,FCVAR_NOTIFY}) +local function findrate() return _findrate:GetFloat() end +local function maxfinds() return _maxfinds:GetInt() end + +local chiplist = {} + +registerCallback("construct", function(self) + self.data.find = { + filter_default = filter_default(self), + bl_entity = {}, + bl_owner = {}, + bl_model = {}, + bl_class = {}, + + wl_entity = {}, + wl_owner = {}, + wl_model = {}, + wl_class = {}, + } + invalidate_filters(self) + self.data.findnext = 0 + self.data.findlist = {} + self.data.findcount = maxfinds() + chiplist[self.data] = true +end) + +registerCallback("destruct", function(self) + chiplist[self.data] = nil +end) + +hook.Add("EntityRemoved", "wire_expression2_find_EntityRemoved", function(ent) + for chip,_ in pairs(chiplist) do + local find = chip.find + find.bl_entity[ent] = nil + find.bl_owner[ent] = nil + find.wl_entity[ent] = nil + find.wl_owner[ent] = nil + + filterList(chip.findlist, function(v) return ent ~= v end) + end +end) + + +--[[************************************************************************]]-- + +function query_blocked(self, update) + if (update) then + if (self.data.findcount > 0) then + self.data.findcount = self.data.findcount - 1 + return false + else + return true + end + end + return (self.data.findcount < 1) +end + +-- Adds to the available find calls +local delay = 0 +local function addcount() + if (delay > CurTime()) then return end + delay = CurTime() + findrate() + + for v,_ in pairs( chiplist ) do + if (v and v.findcount and v.findcount < maxfinds()) then + v.findcount = v.findcount + 1 + end + end +end +hook.Add("Think","Wire_Expression2_Find_AddCount",addcount) + +__e2setcost(2) + +--- Returns the minimum delay between entity find events on a chip +e2function number findUpdateRate() + return findrate() +end + +-- Returns the maximum number of finds per E2 +e2function number findMax() + return maxfinds() +end + +-- Returns the remaining available find calls +e2function number findCount() + return self.data.findcount +end + +--[[ This function wasn't used +--- Returns the minimum delay between entity find events per player +e2 function number findPlayerUpdateRate() + return wire_exp2_playerFindRate:GetFloat() +end +]] + +--- Returns 1 if find functions can be used, 0 otherwise. +e2function number findCanQuery() + return query_blocked(self) and 0 or 1 +end + +--[[************************************************************************]]-- +__e2setcost(30) + +--- Finds entities in a sphere around V with a radius of N, returns the number found after filtering +e2function number findInSphere(vector center, radius) + if query_blocked(self, 1) then return 0 end + center = Vector(center[1], center[2], center[3]) + + return applyFindList(self,ents.FindInSphere(center, radius)) +end + +--- Like findInSphere but with a [[http://mathworld.wolfram.com/SphericalCone.html Spherical cone]], arguments are for position, direction, length, and degrees (works now) +e2function number findInCone(vector position, vector direction, length, degrees) + if query_blocked(self, 4) then return 0 end + + position = Vector(position[1], position[2], position[3]) + direction = Vector(direction[1], direction[2], direction[3]):GetNormalized() + + local findlist = ents.FindInSphere(position, length) + + local cosDegrees = math.cos(math.rad(degrees)) + local Dot = direction.Dot + + -- update filter and apply it, together with the cone filter. This is an optimization over applying the two filters in separate passes + if not self.data.findfilter then update_filters(self) end + filterList(findlist, filter_and( + self.data.findfilter, + function(ent) + return Dot(direction, (ent:GetPos() - position):GetNormalized()) > cosDegrees + end + )) + + self.data.findlist = findlist + return #findlist +end + +--- Like findInSphere but with a globally aligned box, the arguments are the diagonal corners of the box +e2function number findInBox(vector min, vector max) + if query_blocked(self, 1) then return 0 end + min = Vector(min[1], min[2], min[3]) + max = Vector(max[1], max[2], max[3]) + return applyFindList(self, ents.FindInBox(min, max)) +end + +--- Find all entities with the given name +e2function number findByName(string name) + if query_blocked(self, 1) then return 0 end + return applyFindList(self, ents.FindByName(name)) +end + +--- Find all entities with the given model +e2function number findByModel(string model) + if query_blocked(self, 1) then return 0 end + return applyFindList(self, ents.FindByModel(model)) +end + +--- Find all entities with the given class +e2function number findByClass(string class) + if query_blocked(self, 1) then return 0 end + return applyFindList(self, ents.FindByClass(class)) +end + +--[[************************************************************************]]-- + +local function findPlayer(name) + name = string.lower(name) + return filterList(player.GetAll(), function(ent) return string.find(string.lower(ent:GetName()), name,1,true) end)[1] +end + +--- Returns the player with the given name, this is an exception to the rule +e2function entity findPlayerByName(string name) + if query_blocked(self, 1) then return nil end + return findPlayer(name) +end + +--- Returns the player with the given SteamID +e2function entity findPlayerBySteamID(string id) + if query_blocked(self, 1) then return NULL end + return player.GetBySteamID(id) or NULL +end + +--- Returns the player with the given SteamID64 +e2function entity findPlayerBySteamID64(string id) + if query_blocked(self, 1) then return NULL end + return player.GetBySteamID64(id) or NULL +end + +--[[************************************************************************]]-- +__e2setcost(10) + +--- Exclude all entities from from future finds +e2function void findExcludeEntities(array arr) + local bl_entity = self.data.find.bl_entity + local IsValid = IsValid + + for _,ent in ipairs(arr) do + if not IsValid(ent) then return end + bl_entity[ent] = true + end + invalidate_filters(self) +end + +--- Exclude from future finds +e2function void findExcludeEntity(entity ent) + if not IsValid(ent) then return end + self.data.find.bl_entity[ent] = true + invalidate_filters(self) +end + +--- Exclude this player from future finds (put it on the entity blacklist) +e2function void findExcludePlayer(entity ply) = e2function void findExcludeEntity(entity ent) + +--- Exclude this player from future finds (put it on the entity blacklist) +e2function void findExcludePlayer(string name) + local ply = findPlayer(name) + if not ply then return end + self.data.find.bl_entity[ply] = true + invalidate_filters(self) +end + +--- Exclude entities owned by this player from future finds +e2function void findExcludePlayerProps(entity ply) + if not IsValid(ply) then return end + self.data.find.bl_owner[ply] = true + invalidate_filters(self) +end + +--- Exclude entities owned by this player from future finds +e2function void findExcludePlayerProps(string name) + local ply = findPlayer(name) + if not ply then return end + e2_findExcludePlayerProps_e(self, { nil, { function() return ply end } }) +end + +--- Exclude entities with this model (or partial model name) from future finds +e2function void findExcludeModel(string model) + self.data.find.bl_model[string.lower(model)] = true + invalidate_filters(self) +end + +--- Exclude entities with this class (or partial class name) from future finds +e2function void findExcludeClass(string class) + self.data.find.bl_class[string.lower(class)] = true + invalidate_filters(self) +end + +--[[************************************************************************]]-- + +--- Remove all entities from from the blacklist +e2function void findAllowEntities(array arr) + local bl_entity = self.data.find.bl_entity + local IsValid = IsValid + + for _,ent in ipairs(arr) do + if not IsValid(ent) then return end + bl_entity[ent] = nil + end + invalidate_filters(self) +end + +--- Remove from the blacklist +e2function void findAllowEntity(entity ent) + if not IsValid(ent) then return end + self.data.find.bl_entity[ent] = nil + invalidate_filters(self) +end + +--- Remove this player from the entity blacklist +e2function void findAllowPlayer(entity ply) = e2function void findAllowEntity(entity ent) + +--- Remove this player from the entity blacklist +e2function void findAllowPlayer(string name) + local ply = findPlayer(name) + if not ply then return end + self.data.find.bl_entity[ply] = nil + invalidate_filters(self) +end + +--- Remove entities owned by this player from the blacklist +e2function void findAllowPlayerProps(entity ply) + if not IsValid(ply) then return end + self.data.find.bl_owner[ply] = nil + invalidate_filters(self) +end + +--- Remove entities owned by this player from the blacklist +e2function void findAllowPlayerProps(string name) + local ply = findPlayer(name) + if not ply then return end + e2_findAllowPlayerProps_e(self, { nil, { function() return ply end } }) +end + +--- Remove entities with this model (or partial model name) from the blacklist +e2function void findAllowModel(string model) + self.data.find.bl_model[string.lower(model)] = nil + invalidate_filters(self) +end + +--- Remove entities with this class (or partial class name) from the blacklist +e2function void findAllowClass(string class) + self.data.find.bl_class[string.lower(class)] = nil + invalidate_filters(self) +end + +--[[************************************************************************]]-- + +--- Include all entities from in future finds, and remove others not in the whitelist +e2function void findIncludeEntities(array arr) + local wl_entity = self.data.find.wl_entity + local IsValid = IsValid + + for _,ent in ipairs(arr) do + if not IsValid(ent) then return end + wl_entity[ent] = true + end + invalidate_filters(self) +end + +--- Include in future finds, and remove others not in the whitelist +e2function void findIncludeEntity(entity ent) + if not IsValid(ent) then return end + self.data.find.wl_entity[ent] = true + invalidate_filters(self) +end + +--- Include this player in future finds, and remove other entities not in the entity whitelist +e2function void findIncludePlayer(entity ply) = e2function void findIncludeEntity(entity ent) + +--- Include this player in future finds, and remove other entities not in the entity whitelist +e2function void findIncludePlayer(string name) + local ply = findPlayer(name) + if not ply then return end + self.data.find.wl_entity[ply] = true + invalidate_filters(self) +end + +--- Include entities owned by this player from future finds, and remove others not in the whitelist +e2function void findIncludePlayerProps(entity ply) + if not IsValid(ply) then return end + self.data.find.wl_owner[ply] = true + invalidate_filters(self) +end + +--- Include entities owned by this player from future finds, and remove others not in the whitelist +e2function void findIncludePlayerProps(string name) + local ply = findPlayer(name) + if not ply then return end + e2_findIncludePlayerProps_e(self, { nil, { function() return ply end } }) +end + +--- Include entities with this model (or partial model name) in future finds, and remove others not in the whitelist +e2function void findIncludeModel(string model) + self.data.find.wl_model[string.lower(model)] = true + invalidate_filters(self) +end + +--- Include entities with this class (or partial class name) in future finds, and remove others not in the whitelist +e2function void findIncludeClass(string class) + self.data.find.wl_class[string.lower(class)] = true + invalidate_filters(self) +end + +--[[************************************************************************]]-- + +--- Remove all entities from from the whitelist +e2function void findDisallowEntities(array arr) + local wl_entity = self.data.find.wl_entity + local IsValid = IsValid + + for _,ent in ipairs(arr) do + if not IsValid(ent) then return end + wl_entity[ent] = nil + end + invalidate_filters(self) +end + +--- Remove from the whitelist +e2function void findDisallowEntity(entity ent) + if not IsValid(ent) then return end + self.data.find.wl_entity[ent] = nil + invalidate_filters(self) +end + +--- Remove this player from the entity whitelist +e2function void findDisallowPlayer(entity ply) = e2function void findDisallowEntity(entity ent) + +--- Remove this player from the entity whitelist +e2function void findDisallowPlayer(string name) + local ply = findPlayer(name) + if not ply then return end + self.data.find.wl_entity[ply] = nil + invalidate_filters(self) +end + +--- Remove entities owned by this player from the whitelist +e2function void findDisallowPlayerProps(entity ply) + if not IsValid(ply) then return end + self.data.find.wl_owner[ply] = nil + invalidate_filters(self) +end + +--- Remove entities owned by this player from the whitelist +e2function void findDisallowPlayerProps(string name) + local ply = findPlayer(name) + if not ply then return end + e2_findDisallowPlayerProps_e(self, { nil, { function() return ply end } }) +end + +--- Remove entities with this model (or partial model name) from the whitelist +e2function void findDisallowModel(string model) + self.data.find.wl_model[string.lower(model)] = nil + invalidate_filters(self) +end + +--- Remove entities with this class (or partial class name) from the whitelist +e2function void findDisallowClass(string class) + self.data.find.wl_class[string.lower(class)] = nil + invalidate_filters(self) +end + +--[[************************************************************************]]-- + +--- Clear all entries from the entire blacklist +e2function void findClearBlackList() + local find = self.data.find + find.bl_entity = {} + find.bl_owner = {} + find.bl_model = {} + find.bl_class = {} + + invalidate_filters(self) +end + +--- Clear all entries from the entity blacklist +e2function void findClearBlackEntityList() + self.data.find.bl_entity = {} + invalidate_filters(self) +end + +--- Clear all entries from the prop owner blacklist +e2function void findClearBlackPlayerPropList() + self.data.find.bl_owner = {} + invalidate_filters(self) +end + +--- Clear all entries from the model blacklist +e2function void findClearBlackModelList() + self.data.find.bl_model = {} + invalidate_filters(self) +end + +--- Clear all entries from the class blacklist +e2function void findClearBlackClassList() + self.data.find.bl_class = {} + invalidate_filters(self) +end + +--- Clear all entries from the entire whitelist +e2function void findClearWhiteList() + local find = self.data.find + find.wl_entity = {} + find.wl_owner = {} + find.wl_model = {} + find.wl_class = {} + + invalidate_filters(self) +end + +--- Clear all entries from the player whitelist +e2function void findClearWhiteEntityList() + self.data.find.wl_entity = {} + invalidate_filters(self) +end + +--- Clear all entries from the prop owner whitelist +e2function void findClearWhitePlayerPropList() + self.data.find.wl_owner = {} + invalidate_filters(self) +end + +--- Clear all entries from the model whitelist +e2function void findClearWhiteModelList() + self.data.find.wl_model = {} + invalidate_filters(self) +end + +--- Clear all entries from the class whitelist +e2function void findClearWhiteClassList() + self.data.find.wl_class = {} + invalidate_filters(self) +end + +--[[************************************************************************]]-- +__e2setcost(2) + +--- Returns the indexed entity from the previous find event (valid parameters are 1 to the number of entities found) +e2function entity findResult(index) + return self.data.findlist[index] +end + +--- Returns the closest entity to the given point from the previous find event +e2function entity findClosest(vector position) + local closest = nil + local dist = math.huge + self.prf = self.prf + #self.data.findlist * 10 + for _,ent in pairs(self.data.findlist) do + if IsValid(ent) then + local pos = ent:GetPos() + local xd, yd, zd = pos.x-position[1], pos.y-position[2], pos.z-position[3] + local curdist = xd*xd + yd*yd + zd*zd + if curdist < dist then + closest = ent + dist = curdist + end + end + end + return closest +end + +--- Formats the query as an array, R:entity(Index) to get a entity, R:string to get a description including the name and entity id. +e2function array findToArray() + local tmp = {} + for k,v in ipairs(self.data.findlist) do + tmp[k] = v + end + self.prf = self.prf + #tmp / 3 + return tmp +end + +--- Equivalent to findResult(1) +e2function entity find() + return self.data.findlist[1] +end + +--[[************************************************************************]]-- +__e2setcost(10) + +--- Sorts the entities from the last find event, index 1 is the closest to point V, returns the number of entities in the list +e2function number findSortByDistance(vector position) + position = Vector(position[1], position[2], position[3]) + local findlist = self.data.findlist + self.prf = self.prf + #findlist * 12 + + local d = {} + for i=1, #findlist do + local v = findlist[i] + if v:IsValid() then + d[v] = (position - v:GetPos()):LengthSqr() + else + d[v] = math.huge + end + end + table.sort(findlist, function(a, b) return d[a] < d[b] end) + return #findlist +end + +--[[************************************************************************]]-- +__e2setcost(5) + +local function applyClip(self, filter) + local findlist = self.data.findlist + self.prf = self.prf + #findlist * 5 + + filterList(findlist, filter) + + return #findlist +end + +--- Filters the list of entities by removing all entities that are NOT of this class +e2function number findClipToClass(string class) + class = string.lower(class) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return replace_match(string.lower(ent:GetClass()), class) + end) +end + +--- Filters the list of entities by removing all entities that are of this class +e2function number findClipFromClass(string class) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return not replace_match(string.lower(ent:GetClass()), class) + end) +end + +--- Filters the list of entities by removing all entities that do NOT have this model +e2function number findClipToModel(string model) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return replace_match(string.lower(ent:GetModel() or ""), model) + end) +end + +--- Filters the list of entities by removing all entities that do have this model +e2function number findClipFromModel(string model) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return not replace_match(string.lower(ent:GetModel() or ""), model) + end) +end + +--- Filters the list of entities by removing all entities that do NOT have this name +e2function number findClipToName(string name) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return replace_match(string.lower(ent:GetName()), name) + end) +end + +--- Filters the list of entities by removing all entities that do have this name +e2function number findClipFromName(string name) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return not replace_match(string.lower(ent:GetName()), name) + end) +end + +--- Filters the list of entities by removing all entities NOT within the specified sphere (center, radius) +e2function number findClipToSphere(vector center, radius) + center = Vector(center[1], center[2], center[3]) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return center:Distance(ent:GetPos()) <= radius + end) +end + +--- Filters the list of entities by removing all entities within the specified sphere (center, radius) +e2function number findClipFromSphere(vector center, radius) + center = Vector(center[1], center[2], center[3]) + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return center:Distance(ent:GetPos()) > radius + end) +end + +--- Filters the list of entities by removing all entities NOT on the positive side of the defined plane. (Plane origin, vector perpendicular to the plane) You can define any convex hull using this. +e2function number findClipToRegion(vector origin, vector perpendicular) + origin = Vector(origin[1], origin[2], origin[3]) + perpendicular = Vector(perpendicular[1], perpendicular[2], perpendicular[3]) + + local perpdot = perpendicular:Dot(origin) + + return applyClip(self, function(ent) + if !IsValid(ent) then return false end + return perpdot < perpendicular:Dot(ent:GetPos()) + end) +end + +-- inrange used in findClip*Box (below) +local function inrange( vec1, vecmin, vecmax ) + if (vec1.x < vecmin.x) then return false end + if (vec1.y < vecmin.y) then return false end + if (vec1.z < vecmin.z) then return false end + + if (vec1.x > vecmax.x) then return false end + if (vec1.y > vecmax.y) then return false end + if (vec1.z > vecmax.z) then return false end + + return true +end + +-- If vecmin is greater than vecmax, flip it +local function sanitize( vecmin, vecmax ) + for I=1, 3 do + if (vecmin[I] > vecmax[I]) then + local temp = vecmin[I] + vecmin[I] = vecmax[I] + vecmax[I] = temp + end + end + return vecmin, vecmax +end + +-- Filters the list of entities by removing all entities within the specified box +e2function number findClipFromBox( vector min, vector max ) + + min, max = sanitize( min, max ) + + min = Vector(min[1], min[2], min[3]) + max = Vector(max[1], max[2], max[3]) + + return applyClip( self, function(ent) + return !inrange(ent:GetPos(),min,max) + end) +end + +-- Filters the list of entities by removing all entities not within the specified box +e2function number findClipToBox( vector min, vector max ) + + min, max = sanitize( min, max ) + + min = Vector(min[1], min[2], min[3]) + max = Vector(max[1], max[2], max[3]) + + return applyClip( self, function(ent) + return inrange(ent:GetPos(),min,max) + end) +end + +-- Filters the list of entities by removing all entities equal to this entity +e2function number findClipFromEntity( entity ent ) + if !IsValid( ent ) then return -1 end + return applyClip( self, function( ent2 ) + if !IsValid(ent2) then return false end + return ent != ent2 + end) +end + +-- Filters the list of entities by removing all entities equal to one of these entities +e2function number findClipFromEntities( array entities ) + local lookup = {} + self.prf = self.prf + #entities / 3 + for k,v in ipairs( entities ) do lookup[v] = true end + return applyClip( self, function( ent ) + if !IsValid(ent) then return false end + return !lookup[ent] + end) +end + +-- Filters the list of entities by removing all entities not equal to this entity +e2function number findClipToEntity( entity ent ) + if !IsValid( ent ) then return -1 end + return applyClip( self, function( ent2 ) + if !IsValid(ent2) then return false end + return ent == ent2 + end) +end + +-- Filters the list of entities by removing all entities not equal to one of these entities +e2function number findClipToEntities( array entities ) + local lookup = {} + self.prf = self.prf + #entities / 3 + for k,v in ipairs( entities ) do lookup[v] = true end + return applyClip( self, function( ent ) + if !IsValid(ent) then return false end + return lookup[ent] + end) +end + +-- Filters the list of entities by removing all props not owned by this player +e2function number findClipToPlayerProps( entity ply ) + if not IsValid(ply) then return -1 end + return applyClip( self, function( ent ) + if not IsValid(ent) then return false end + return getOwner(self,ent) == ply + end) +end + +-- Filters the list of entities by removing all props owned by this player +e2function number findClipFromPlayerProps( entity ply ) + if not IsValid(ply) then return -1 end + return applyClip( self, function( ent ) + if not IsValid(ent) then return false end + return getOwner(self,ent) ~= ply + end) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/functions.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/functions.lua new file mode 100644 index 0000000..1d7dd0d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/functions.lua @@ -0,0 +1,25 @@ +--[[============================================================ + E2 Function System + By Rusketh + General Operators +============================================================]]-- + +__e2setcost(1) + +registerOperator("function", "", "", function(self, args) + local sig, body = args[2], args[3] + self.funcs[sig] = body + self.strfunc_cache = {} +end) + +__e2setcost(2) + +registerOperator("return", "", "", function(self, args) + if args[2] then + local op = args[2] + local rv = op[1](self, op) + self.func_rv = rv + end + + error("return",0) +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/gametick.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/gametick.lua new file mode 100644 index 0000000..a03b62d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/gametick.lua @@ -0,0 +1,49 @@ +/******************************************************************************\ + Game tick callback support +\******************************************************************************/ + +local registered_chips = {} +local tickrun = 0 + +registerCallback("destruct",function(self) + registered_chips[self.entity] = nil +end) + +__e2setcost(1) + +--- If != 0 the expression will execute once every game tick +e2function void runOnTick(activate) + if activate ~= 0 then + registered_chips[self.entity] = true + else + registered_chips[self.entity] = nil + end +end + +--- Returns 1 if the current execution was caused by "runOnTick" +e2function number tickClk() + return self.data.tickrun and 1 or 0 +end + +local function Expression2TickClock() + local ents = {} + + -- this additional step is needed because we cant modify registered_chips while it is being iterated. + local i = 1 + for entity,_ in pairs(registered_chips) do + if entity:IsValid() then + ents[i] = entity + i = i + 1 + end + end + + for _,entity in ipairs(ents) do + entity.context.data.tickrun = true + entity:Execute() + entity.context.data.tickrun = nil + end +end +hook.Add("Think", "Expression2TickClock", Expression2TickClock) +timer.Create("Expression2TickClock", 5, 0, function() + hook.Add("Think", "Expression2TickClock", Expression2TickClock) +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/globalvars.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/globalvars.lua new file mode 100644 index 0000000..7e9f05f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/globalvars.lua @@ -0,0 +1,467 @@ +--[[ +gvars v2 +Made by Divran +]] + +local gvars = {} +gvars.shared = {} +gvars.safe = {} -- Safe from hacking using gTableSafe + +------------------------------------------------------------------------------------------------ +-- GVARS V2 +------------------------------------------------------------------------------------------------ +------------------------------------------------ +-- Type +------------------------------------------------ +registerType( "gtable", "xgt", {}, + function(self) self.entity:Error("You may not input a gtable.") end, + function(self) self.entity:Error("You may not output a gtable.") end, + function(retval) + if !istable(retval) then error("Return value is not a gtable, but a "..type(retval).."!",0) end + end, + function(v) + return !istable(v) + end +) + +__e2setcost(1) + +registerOperator("ass", "xgt", "xgt", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + local Scope = self.Scopes[scope] + if !Scope.lookup then Scope.lookup = {} end + local lookup = Scope.lookup + + -- remove old lookup entry + if lookup[rhs] then lookup[rhs][lhs] = nil end + + -- add new lookup entry + local lookup_entry = lookup[rhs] + if not lookup_entry then + lookup_entry = {} + lookup[rhs] = lookup_entry + end + lookup_entry[lhs] = true + + --Scope.vars[lhs] = rhs + Scope[lhs] = rhs + Scope.vclk[lhs] = true + return rhs +end) + +e2function number operator_is( gtable tbl ) + return istable(tbl) and 1 or 0 +end + +------------------------------------------------ +-- gTable +------------------------------------------------ +__e2setcost(1) + +e2function gtable gTable( string groupname ) + if (!gvars[self.uid][groupname]) then gvars[self.uid][groupname] = {} end + return gvars[self.uid][groupname] +end + +e2function gtable gTable( string groupname, number shared ) + if shared == 0 then + if (!gvars[self.uid][groupname]) then gvars[self.uid][groupname] = {} end + return gvars[self.uid][groupname] + else + if (!gvars.shared[groupname]) then gvars.shared[groupname] = {} end + return gvars.shared[groupname] + end +end + +local getHash = E2Lib.getHash +e2function gtable gTableSafe( number shared ) + local hash = getHash( self, self.entity.buffer ) + if shared == 0 then + if not gvars[self.uid][hash] then gvars[self.uid][hash] = {} end + return gvars[self.uid][hash] + else + if not gvars.safe[hash] then gvars.safe[hash] = {} end + return gvars.safe[hash] + end +end + +__e2setcost(5) + +-- Clear the non-shared table +e2function void gRemoveAll() + for k,v in pairs( gvars[self.uid] ) do + self.prf = self.prf + 0.3 + + for k2,v2 in pairs( v ) do + self.prf = self.prf + 0.3 + v[k2] = nil + end + + gvars[self.uid][k] = nil + end +end + +e2function void gtable:clear() + for k,v in pairs( this ) do + this[k] = nil + self.prf = self.prf + 0.3 + end +end + +e2function number gtable:count() + local ret = table.Count( this ) + self.prf = self.prf + ret / 3 + return ret +end + +local string_sub = string.sub +e2function table gtable:toTable() + local ret = {n={},ntypes={},s={},stypes={},size=0,istable=true,depth=0} + + for k,v in pairs( this ) do + local typeid, index = string_sub( k, 1,1 ), string_sub( k, 2 ) + if typeid == "x" then + typeid = string_sub( k, 1,3 ) + index = string_sub( k, 4 ) + end + + ret.s[index] = v + ret.stypes[index] = typeid + ret.size = ret.size + 1 + end + + self.prf = self.prf + ret.size / 3 + + return ret +end + +------------------------------------------------ +-- Get/Set functions +------------------------------------------------ +-- Upperfirst, used by the E2 functions below +local function upperfirst( word ) + return word:Left(1):upper() .. word:Right(-2):lower() +end + +local non_allowed_types = { -- If anyone can think of any other types that should never be allowed, enter them here. + xgt = true, +} + +registerCallback("postinit",function() + for k,v in pairs( wire_expression_types ) do + if (!non_allowed_types[v[1]]) then + if (k == "NORMAL") then k = "NUMBER" end + k = upperfirst(k) + + __e2setcost(5) + + -- Table[index,type] functions + local function getf( self, args ) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if isnumber(rv2) then rv2 = tostring(rv2) end + local val = rv1[v[1]..rv2] + if (val) then -- If the var exists + return val -- return it + end + local default = v[2] + if istable(default) then default = table.Copy(default) end + return default + end + local function setf( self, args ) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + if isnumber(rv2) then rv2 = tostring(rv2) end + rv1[v[1]..rv2] = rv3 + return rv3 + end + + registerOperator("idx", v[1].."=xgts", v[1], getf) -- G[S,type] + registerOperator("idx", v[1].."=xgts"..v[1], v[1], setf) -- G[S,type] + registerOperator("idx", v[1].."=xgtn", v[1], getf) -- G[N,type] (same as G[N:toString(),type]) + registerOperator("idx", v[1].."=xgtn"..v[1], v[1], setf) -- G[N,type] (same as G[N:toString(),type]) + ------ + + --gRemove* -- Remove the variable at the specified index and return it + registerFunction("remove"..k,"xgt:s",v[1],function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self,op1), op2[1](self,op2) + local val = rv1[v[1]..rv2] + if (val) then + rv1[v[1]..rv2] = nil + return val + end + local default = v[2] + if istable(default) then default = table.Copy(default) end + return default + end) + + -- gRemoveAll*() - Remove all variables of a type in the player's non-shared table + registerFunction("gRemoveAll"..k.."s","","",function(self,args) + for k2,v2 in pairs( gvars[self.uid] ) do + for k3, v3 in pairs( v2 ) do + self.prf = self.prf + 0.3 + if (string.Left(k3,#v[1]) == v[1]) then + gvars[self.uid][k2][k3] = nil + --v3 = nil + end + end + end + end) + + -- gRemoveAll*(S) - Remove all variables of a type in the player's non-shared table in the specified group + registerFunction("gRemoveAll"..k.."s","s","",function(self,args) + local op1 = args[2] + local rv1 = op1[1](self,op1) + if (gvars[self.uid][rv1]) then + for k2,v2 in pairs( gvars[self.uid][rv1] ) do + self.prf = self.prf + 0.3 + if (string.Left(k2,#v[1]) == v[1]) then + gvars[self.uid][rv1][k2] = nil + --v2 = nil + end + end + end + end) + + -------------------------------------------------------------------------------- + -- gTable converts all numeric indexes to strings, so we can only support iterating string keys + -------------------------------------------------------------------------------- + __e2setcost(1) + + registerOperator("fea", "s" .. v[1] .. "xgt", "", function(self, args) + local keyname, valname = args[2], args[3] + + local tbl = args[4] + tbl = tbl[1](self, tbl) + + local statement = args[5] + local len = #v[1] + + for key, value in pairs(tbl) do + if key:sub(1, len) == v[1] then + self:PushScope() + + self.prf = self.prf + 3 + + self.Scope.vclk[keyname] = true + self.Scope.vclk[valname] = true + + self.Scope[keyname] = key:sub(len + 1) + self.Scope[valname] = value + + local ok, msg = pcall(statement[1], self, statement) + + if not ok then + if msg == "break" then self:PopScope() break + elseif msg ~= "continue" then self:PopScope() error(msg, 0) end + end + + self:PopScope() + end + end + end) + + end -- allowed check + end -- loop +end) -- postinit + + +------------------------------------------------------------------------------------------------ +-- ALL BELOW FUNCTIONS ARE DEPRECATED (Only for compability) +------------------------------------------------------------------------------------------------ +------------------------------------------------ +-- Group management +------------------------------------------------ +__e2setcost(1) + +e2function void gSetGroup( string groupname ) + self.data.gvars.group = groupname +end + +e2function string gGetGroup() + return self.data.gvars.group or "" +end + +e2function void gShare( number share ) + self.data.gvars.shared = math.Clamp(share,0,1) +end + +e2function number gGetShare() + return self.data.gvars.shared or 0 +end + +e2function void gResetGroup() + self.data.gvars.group = "default" +end + +------------------------------------------------ +-- Get/Set functions +------------------------------------------------ +registerCallback("postinit",function() + + local types = {} + + types.Str = { "s", wire_expression_types.STRING } + types.Num = { "n", wire_expression_types.NORMAL } + types.Ent = { "e", wire_expression_types.ENTITY } + types.Vec = { "v", wire_expression_types.VECTOR } + types.Ang = { "a", wire_expression_types.ANGLE } + + local function GetVar( Group, Tbl, Index, Type ) + if (Tbl[Group]) then + local val = Tbl[Group][Type..Index] + if (val) then -- If the var exists + return val -- return it + end + end + end + + for k,v in pairs( types ) do + + __e2setcost(8) + + -- gSet*(S,*) + registerFunction("gSet"..k,"s"..v[1],"",function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1),op2[1](self, op2) + if (self.data.gvars.shared == 1) then + if (!gvars.shared[self.data.gvars.group]) then gvars.shared[self.data.gvars.group] = {} end + gvars.shared[self.data.gvars.group][v[1]..rv1] = rv2 + else + if (!gvars[self.uid][self.data.gvars.group]) then gvars[self.uid][self.data.gvars.group] = {} end + gvars[self.uid][self.data.gvars.group][v[1]..rv1] = rv2 + end + end) + + -- gGet*(S) + registerFunction("gGet"..k,"s",v[1],function(self,args) + local op1 = args[2] + local rv1 = op1[1](self,op1) + if (self.data.gvars.shared == 1) then + local ret = GetVar(self.data.gvars.group,gvars.shared,rv1,v[1]) + if (ret) then return ret end + local default = v[2][2] + if istable(default) then default = table.Copy(default) end + return default + else + local ret = GetVar(self.data.gvars.group,gvars[self.uid],rv1,v[1]) + if (ret) then return ret end + local default = v[2][2] + if istable(default) then default = table.Copy(default) end + return default + end + end) + + -- gSet*(N,*) (same as gSet*(N:toString(),*) + registerFunction("gSet"..k,"n"..v[1],"",function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1),op2[1](self, op2) + if (self.data.gvars.shared == 1) then + if (!gvars.shared[self.data.gvars.group]) then gvars.shared[self.data.gvars.group] = {} end + gvars.shared[self.data.gvars.group][v[1]..tostring(rv1)] = rv2 + else + if (!gvars[self.uid][self.data.gvars.group]) then gvars[self.uid][self.data.gvars.group] = {} end + gvars[self.uid][self.data.gvars.group][v[1]..tostring(rv1)] = rv2 + end + end) + + -- gGet*(N) (same as gGet*(N:toString())) + registerFunction("gGet"..k,"n",v[1],function(self,args) + local op1 = args[2] + local rv1 = tostring(op1[1](self,op1)) + if (self.data.gvars.shared == 1) then + local ret = GetVar(self.data.gvars.group,gvars.shared,rv1,v[1]) + if (ret) then return ret end + local default = v[2][2] + if istable(default) then default = table.Copy(default) end + return default + else + local ret = GetVar(self.data.gvars.group,gvars[self.uid],rv1,v[1]) + if (ret) then return ret end + local default = v[2][2] + if istable(default) then default = table.Copy(default) end + return default + end + end) + + -- gDelete*(S) + registerFunction("gDelete"..k,"s",v[1],function(self,args) + local op1 = args[2] + local rv1 = op1[1](self,op1) + if (self.data.gvars.shared == 1) then + if (gvars.shared[self.data.gvars.group]) then + if (gvars.shared[self.data.gvars.group][v[1]..rv1]) then + local val = gvars.shared[self.data.gvars.group][v[1]..rv1] + gvars.shared[self.data.gvars.group][v[1]..rv1] = nil + return val + end + end + else + if (gvars[self.uid][self.data.gvars.group]) then + if (gvars[self.uid][self.data.gvars.group][v[1]..rv1]) then + local val = gvars[self.uid][self.data.gvars.group][v[1]..rv1] + gvars[self.uid][self.data.gvars.group][v[1]..rv1] = nil + return val + end + end + end + local default = v[2][2] + if istable(default) then default = table.Copy(default) end + return default + end) + + -- gDelete*(N) (same as gDelete*(N:toString())) + registerFunction("gDelete"..k,"n",v[1],function(self,args) + local op1 = args[2] + local rv1 = tostring(op1[1](self,op1)) + if (self.data.gvars.shared == 1) then + if (gvars.shared[self.data.gvars.group]) then + if (gvars.shared[self.data.gvars.group][v[1]..rv1]) then + local val = gvars.shared[self.data.gvars.group][v[1]..rv1] + gvars.shared[self.data.gvars.group][v[1]..rv1] = nil + return val + end + end + else + if (gvars[self.uid][self.data.gvars.group]) then + if (gvars[self.uid][self.data.gvars.group][v[1]..rv1]) then + local val = gvars[self.uid][self.data.gvars.group][v[1]..rv1] + gvars[self.uid][self.data.gvars.group][v[1]..rv1] = nil + return val + end + end + end + local default = v[2][2] + if istable(default) then default = table.Copy(default) end + return default + end) + + __e2setcost(5) + + -- gDeleteAll*() + registerFunction("gDeleteAll"..k,"","",function(self,args) + for k,v in pairs( gvars[self.uid][self.data.gvars.group] ) do + self.prf = self.prf + 0.3 + if (string.Left(k,#v[1]) == v[1]) then + v = nil + end + end + end) + end + +end) +------------------------------------------------------------------------------------------------ +-- ALL ABOVE FUNCTIONS ARE DEPRECATED (Only for compability) +------------------------------------------------------------------------------------------------ +------------------------------------------------ +-- Construct/Destruct +------------------------------------------------ +registerCallback("construct",function(self) + self.data.gvars = {} + self.data.gvars.group = "default" + self.data.gvars.shared = 0 + if (!gvars[self.uid]) then gvars[self.uid] = {} end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/hologram.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/hologram.lua new file mode 100644 index 0000000..51c69f7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/hologram.lua @@ -0,0 +1,1346 @@ +E2Lib.RegisterExtension( "holo", true, "Allows E2 to create and manipulate non-solid models." ) + +-- ----------------------------------------------------------------------------- + +local function checkOwner(self) + return IsValid(self.player) +end + +-- ----------------------------------------------------------------------------- + +local wire_holograms_max = CreateConVar( "wire_holograms_max", "250", {FCVAR_ARCHIVE} ) +local wire_holograms_spawn_amount = CreateConVar( "wire_holograms_spawn_amount", "15", {FCVAR_ARCHIVE} ) -- This limit resets once a second +local wire_holograms_burst_amount = CreateConVar( "wire_holograms_burst_amount", "80", {FCVAR_ARCHIVE} ) -- This limit goes down first, resets every burst_delay +local wire_holograms_burst_delay = CreateConVar( "wire_holograms_burst_delay", "10", {FCVAR_ARCHIVE} ) +local wire_holograms_max_clips = CreateConVar( "wire_holograms_max_clips", "5", {FCVAR_ARCHIVE} ) -- Don't set higher than 16 without editing net.Start("wire_holograms_clip") +local wire_holograms_modelany = CreateConVar( "wire_holograms_modelany", "0", {FCVAR_ARCHIVE}, + "1: Allow holograms to use models besides the official hologram models." .. + "2: Allow holograms to additionally use models not present on the server." ) +local wire_holograms_size_max = CreateConVar( "wire_holograms_size_max", "50", {FCVAR_ARCHIVE} ) +util.AddNetworkString("wire_holograms_set_visible") +util.AddNetworkString("wire_holograms_clip") +util.AddNetworkString("wire_holograms_set_scale") +util.AddNetworkString("wire_holograms_set_bone_scale") +util.AddNetworkString("wire_holograms_set_player_color") + + +-- context = chip.context = self +-- uid = context.uid = self.uid = chip.uid = player:UniqueID() +-- Holo = { ent = prop, scale = scale, e2owner = context } +-- E2HoloRepo[uid][-index] = Holo <-- global holos +-- E2HoloRepo[uid][Holo] = Holo <-- local holos +-- context.data.holos[index] = Holo <-- local holos + +local E2HoloRepo = {} +local PlayerAmount = {} +local BlockList = {} + +local ModelList = { + ["cone"] = "cone", + ["cplane"] = "cplane", + ["cube"] = "cube", + ["cylinder"] = "cylinder", + ["hq_cone"] = "hq_cone", + ["hq_cylinder"] = "hq_cylinder", + ["hq_dome"] = "hq_dome", + ["hq_hdome"] = "hq_hdome", + ["hq_hdome_thick"] = "hq_hdome_thick", + ["hq_hdome_thin"] = "hq_hdome_thin", + ["hq_icosphere"] = "hq_icosphere", + ["hq_sphere"] = "hq_sphere", + ["hq_torus"] = "hq_torus", + ["hq_torus_thick"] = "hq_torus_thick", + ["hq_torus_thin"] = "hq_torus_thin", + ["hq_torus_oldsize"] = "hq_torus_oldsize", + ["hq_tube"] = "hq_tube", + ["hq_tube_thick"] = "hq_tube_thick", + ["hq_tube_thin"] = "hq_tube_thin", + ["hq_stube"] = "hq_stube", + ["hq_stube_thick"] = "hq_stube_thick", + ["hq_stube_thin"] = "hq_stube_thin", + ["icosphere"] = "icosphere", + ["icosphere2"] = "icosphere2", + ["icosphere3"] = "icosphere3", + ["plane"] = "plane", + ["prism"] = "prism", + ["pyramid"] = "pyramid", + ["sphere"] = "sphere", + ["sphere2"] = "sphere2", + ["sphere3"] = "sphere3", + ["tetra"] = "tetra", + ["torus"] = "torus", + ["torus2"] = "torus2", + ["torus3"] = "torus3", + + ["rcube"] = "rcube", + ["rcube_thick"] = "rcube_thick", + ["rcube_thin"] = "rcube_thin", + ["hq_rcube"] = "hq_rcube", + ["hq_rcube_thick"] = "hq_rcube_thick", + ["hq_rcube_thin"] = "hq_rcube_thin", + ["rcylinder"] = "rcylinder", + ["rcylinder_thick"] = "rcylinder_thick", + ["rcylinder_thin"] = "rcylinder_thin", + ["hq_rcylinder"] = "hq_rcylinder", + ["hq_rcylinder_thick"]= "hq_rcylinder_thick", + ["hq_rcylinder_thin"] = "hq_rcylinder_thin", + ["hq_cubinder"] = "hq_cubinder", + ["hexagon"] = "hexagon", + ["octagon"] = "octagon", + ["right_prism"] = "right_prism", + + -- Removed models with their replacements + + ["dome"] = "hq_dome", + ["dome2"] = "hq_hdome", + ["hqcone"] = "hq_cone", + ["hqcylinder"] = "hq_cylinder", + ["hqcylinder2"] = "hq_cylinder", + ["hqicosphere"] = "hq_icosphere", + ["hqicosphere2"] = "hq_icosphere", + ["hqsphere"] = "hq_sphere", + ["hqsphere2"] = "hq_sphere", + ["hqtorus"] = "hq_torus_oldsize", + ["hqtorus2"] = "hq_torus_oldsize", + + -- HQ models with their short names + + ["hqhdome"] = "hq_hdome", + ["hqhdome2"] = "hq_hdome_thin", + ["hqhdome3"] = "hq_hdome_thick", + ["hqtorus3"] = "hq_torus_thick", + ["hqtube"] = "hq_tube", + ["hqtube2"] = "hq_tube_thin", + ["hqtube3"] = "hq_tube_thick", + ["hqstube"] = "hq_stube", + ["hqstube2"] = "hq_stube_thin", + ["hqstube3"] = "hq_stube_thick", + ["hqrcube"] = "hq_rcube", + ["hqrcube2"] = "hq_rcube_thick", + ["hqrcube3"] = "hq_rcube_thin", + ["hqrcylinder"] = "hq_rcylinder", + ["hqrcylinder2"] = "hq_rcylinder_thin", + ["hqrcylinder3"] = "hq_rcylinder_thick", + ["hqcubinder"] = "hq_cubinder" +} + +-- Return the absolute model path +local function modelPath(name) + return "models/holograms/" .. name .. ".mdl" +end + +local added = {} +local pathLookup = {} + +for _,v in pairs( ModelList ) do + if not added[v] then + local path = modelPath(v) + + pathLookup[path] = true + util.PrecacheModel(path) + -- resource.AddSingleFile( "models/holograms/" .. v .. ".mdl" ) + + added[v] = true + end +end + +local function GetModel(self, model, skin) + skin = skin or 0 + + if ModelList[model] then + model = modelPath(ModelList[model]) + + -- If this model isn't already the absolute path of a default model, and only default models are allowed + elseif not pathLookup[model] and wire_holograms_modelany:GetInt() == 0 then + return false + end + + if wire_holograms_modelany:GetInt() ~= 2 and not WireLib.CanModel(self.player, model, skin) then + -- Check if the model is at least valid + if not WireLib.CanModel(self.player, model, 0) then + return false + end + + -- The model was valid however the skin was not. Go to default skin + skin = 0 + end + + return model, skin +end + +-- ----------------------------------------------------------------------------- + +local scale_queue = {} +local bone_scale_queue = {} +local clip_queue = {} +local vis_queue = {} +local player_color_queue = {} + +local function add_queue( queue, ply, data ) + local plyqueue = queue[ply] + if not plyqueue then + plyqueue = {} + queue[ply] = plyqueue + end + if #plyqueue==wire_holograms_max:GetInt() then return end + plyqueue[#plyqueue+1] = data +end + +-- call to remove all queued items for a specific hologram +local function remove_from_queues( holo_ent ) + local function remove_from_queue( queue ) + for _, plyqueue in pairs( queue ) do + for i=#plyqueue,1,-1 do -- iterate backwards to allow removing + local Holo = plyqueue[i][1] -- the hologram is always at idx 1 + if Holo.ent == holo_ent then + table.remove( plyqueue, i ) -- remove it from the queue + end + end + end + end + + remove_from_queue( scale_queue ) + remove_from_queue( bone_scale_queue ) + remove_from_queue( clip_queue ) + remove_from_queue( vis_queue ) + remove_from_queue( player_color_queue ) +end + +local function remove_holo( Holo ) + if IsValid(Holo.ent) then + remove_from_queues( Holo.ent ) + Holo.ent:Remove() + end +end + +local function flush_scale_queue(queue, recipient) + if not queue then queue = scale_queue end + if not next(queue) then return end + + net.Start("wire_holograms_set_scale") + for _, plyqueue in pairs(queue) do + for _,Holo,scale in ipairs_map(plyqueue, unpack) do + net.WriteUInt(Holo.ent:EntIndex(), 16) + net.WriteFloat(scale.x) + net.WriteFloat(scale.y) + net.WriteFloat(scale.z) + end + end + net.WriteUInt(0, 16) + if recipient then net.Send(recipient) else net.Broadcast() end +end + +local function flush_bone_scale_queue(queue, recipient) + if not queue then queue = bone_scale_queue end + if not next(queue) then return end + + net.Start("wire_holograms_set_bone_scale") + for _, plyqueue in pairs(queue) do + for _,Holo,bone,scale in ipairs_map(plyqueue, unpack) do + net.WriteUInt(Holo.ent:EntIndex(), 16) + net.WriteUInt(bone + 1, 16) -- using +1 to be able reset holo bones scale with -1 and not use signed int + net.WriteFloat(scale.x) + net.WriteFloat(scale.y) + net.WriteFloat(scale.z) + end + end + net.WriteUInt(0, 16) + net.WriteUInt(0, 16) + if recipient then net.Send(recipient) else net.Broadcast() end +end + +local function flush_clip_queue(queue, recipient) + if not queue then queue = clip_queue end + if not next(queue) then return end + + net.Start("wire_holograms_clip") + for _, plyqueue in pairs(queue) do + for _,Holo,clip in ipairs_map(plyqueue, unpack) do + if clip and clip.index then + net.WriteUInt(Holo.ent:EntIndex(), 16) + net.WriteUInt(clip.index, 4) -- 4: absolute highest wire_holograms_max_clips is thus 16 + if clip.enabled ~= nil then + net.WriteBit(true) + net.WriteBit(clip.enabled) + elseif clip.origin and clip.normal and clip.localentid then + net.WriteBit(false) + net.WriteVector(clip.origin) + net.WriteFloat(clip.normal.x) net.WriteFloat(clip.normal.y) net.WriteFloat(clip.normal.z) + net.WriteUInt(clip.localentid, 16) + end + end + end + end + net.WriteUInt(0, 16) + if recipient then net.Send(recipient) else net.Broadcast() end +end + +local function flush_vis_queue() + if not next(vis_queue) then return end + + for ply,tbl in pairs( vis_queue ) do + if IsValid( ply ) and #tbl > 0 then + net.Start("wire_holograms_set_visible") + for _,Holo,visible in ipairs_map(tbl, unpack) do + net.WriteUInt(Holo.ent:EntIndex(), 16) + net.WriteBit(visible) + end + net.WriteUInt(0, 16) + net.Send(ply) + end + end +end + +local function flush_player_color_queue() + if not next(player_color_queue) then return end + + net.Start("wire_holograms_set_player_color") + for _, plyqueue in pairs(player_color_queue) do + for _,Holo,color in ipairs_map(plyqueue, unpack) do + net.WriteUInt(Holo.ent:EntIndex(), 16) + net.WriteVector(color) + end + end + net.WriteUInt(0, 16) + net.Broadcast() +end + +registerCallback("postexecute", function(self) + flush_scale_queue() + flush_bone_scale_queue() + flush_clip_queue() + flush_vis_queue() + flush_player_color_queue() + + scale_queue = {} + bone_scale_queue = {} + clip_queue = {} + vis_queue = {} + player_color_queue = {} +end) + +local function rescale(Holo, scale, bone) + local maxval = wire_holograms_size_max:GetInt() + local minval = -maxval + + if scale then + local x = math.Clamp( scale[1], minval, maxval ) + local y = math.Clamp( scale[2], minval, maxval ) + local z = math.Clamp( scale[3], minval, maxval ) + local scale = Vector(x, y, z) + + if Holo.scale ~= scale then + add_queue( scale_queue, Holo.e2owner, { Holo, scale } ) + Holo.scale = scale + end + end + + if bone then + Holo.bone_scale = Holo.bone_scale or {} + if #bone == 2 then + local bidx, b_scale = bone[1], bone[2] + local x = math.Clamp( b_scale[1], minval, maxval ) + local y = math.Clamp( b_scale[2], minval, maxval ) + local z = math.Clamp( b_scale[3], minval, maxval ) + local scale = Vector(x, y, z) + + add_queue( bone_scale_queue, Holo.e2owner, { Holo, bidx, scale } ) + Holo.bone_scale[bidx] = scale + else -- reset holo bone scale + add_queue( bone_scale_queue, Holo.e2owner, { Holo, -1, Vector(0,0,0) } ) + Holo.bone_scale = {} + end + end +end + +local function check_clip(Holo, idx) + Holo.clips = Holo.clips or {} + + if idx > 0 and idx <= wire_holograms_max_clips:GetInt() then + Holo.clips[idx] = Holo.clips[idx] or {} + local clip = Holo.clips[idx] + + clip.enabled = clip.enabled or false + clip.origin = clip.origin or Vector(0,0,0) + clip.normal = clip.normal or Vector(0,0,0) + clip.localentid = clip.localentid or 0 + + return clip + end + + return nil +end + +local function enable_clip(Holo, idx, enabled) + local clip = check_clip(Holo, idx) + + if clip and clip.enabled ~= enabled then + clip.enabled = enabled + + add_queue( clip_queue, Holo.e2owner, { Holo, + { + index = idx, + enabled = enabled + }} + ) + end +end + +local function set_clip(Holo, idx, origin, normal, localentid) + local clip = check_clip(Holo, idx) + + if clip and (clip.origin ~= origin or clip.normal ~= normal or clip.localentid ~= localentid) then + clip.origin = origin + clip.normal = normal + clip.localentid = localentid + + add_queue( clip_queue, Holo.e2owner, { Holo, + { + index = idx, + origin = origin, + normal = normal, + localentid = localentid + }} + ) + end +end + +local function set_visible(Holo, players, visible) + if not Holo.visible then Holo.visible = {} end + visible = (visible == 1) or (visible == true) + for _,ply in pairs( players ) do + if IsValid( ply ) and ply:IsPlayer() and Holo.visible[ply] ~= visible then + Holo.visible[ply] = visible + add_queue( vis_queue, ply, { Holo, visible } ) + end + end +end + +local function reset_clholo(Holo, scale) + if Holo.clips then + for cidx, clip in pairs(Holo.clips) do + if clip.enabled then + add_queue(clip_queue, Holo.e2owner, { Holo, + { + index = cidx, + enabled = false + }} + ) + end + end + Holo.clips = {} + end + rescale(Holo, scale, {}) + if Holo.visible then + for ply, state in pairs(Holo.visible) do + if not state then + add_queue(vis_queue, ply, { Holo, true }) + end + end + Holo.visible = {} + end +end + +local function set_player_color(Holo, color) + add_queue(player_color_queue, Holo.e2owner, { Holo, color }) +end + +hook.Add( "PlayerInitialSpawn", "wire_holograms_set_vars", function(ply) + local s_queue = {} + local b_s_queue = {} + local c_queue = {} + + for pl_uid,rep in pairs( E2HoloRepo ) do + for k,Holo in pairs( rep ) do + if Holo and IsValid(Holo.ent) then + local clips = Holo.clips + local scale = Holo.scale + local bone_scales = Holo.bone_scale + + table.insert(s_queue, { Holo, scale }) + + if bone_scales and next(bone_scales) ~= nil then + for bidx,b_scale in pairs(bone_scales) do + table.insert(b_s_queue, { Holo, bidx, b_scale }) + end + end + + if clips and next(clips) ~= nil then + for cidx,clip in pairs(clips) do + if clip.enabled then + table.insert(c_queue, { + Holo, + { + index = cidx, + enabled = clip.enabled + } + } ) + end + + if clip.origin and clip.normal and clip.localentid then + table.insert(c_queue, { + Holo, + { + index = cidx, + origin = clip.origin, + normal = clip.normal, + localentid = clip.localentid + } + } ) + end + end + end + end + end + end + + flush_scale_queue({[ply] = s_queue}, ply) + flush_bone_scale_queue({[ply] = b_s_queue}, ply) + flush_clip_queue({[ply] = c_queue}, ply) +end) + +-- ----------------------------------------------------------------------------- + +local function MakeHolo(Player, Pos, Ang, model) + local prop = ents.Create( "gmod_wire_hologram" ) + WireLib.setPos(prop, Pos) + WireLib.setAng(prop, Ang) + prop:SetModel(model) + prop:SetPlayer(Player) + prop:SetNWInt("ownerid", Player:UserID()) + + return prop +end + +-- Returns the hologram with the given index or nil if it doesn't exist. +local function CheckIndex(self, index) + index = index - index % 1 + local Holo + if index<0 then + Holo = E2HoloRepo[self.uid][-index] + else + Holo = self.data.holos[index] + end + if not Holo or not IsValid(Holo.ent) then return nil end + return Holo +end + +-- Sets the given index to the given hologram. +local function SetIndex(self, index, Holo) + index = index - index % 1 + local rep = E2HoloRepo[self.uid] + if index<0 then + rep[-index] = Holo + else + local holos = self.data.holos + if holos[index] then rep[holos[index]] = nil end + holos[index] = Holo + if Holo then rep[Holo] = Holo end + end +end + +local function CreateHolo(self, index, pos, scale, ang, color, model) + if not pos then pos = self.entity:GetPos() end + if not scale then scale = Vector(1,1,1) end + if not ang then ang = self.entity:GetAngles() end + + model = GetModel(self, model or "cube") or "models/holograms/cube.mdl" + + local Holo = CheckIndex(self, index) + if not Holo then + Holo = {} + SetIndex(self, index, Holo) + end + + local prop + + if IsValid(Holo.ent) then + prop = Holo.ent + WireLib.setPos(prop, pos) + WireLib.setAng(prop, ang) + prop:SetModel( model ) + else + prop = MakeHolo(self.player, pos, ang, model, {}, {}) + prop:Activate() + prop:Spawn() + prop:SetSolid(SOLID_NONE) + prop:SetMoveType(MOVETYPE_NONE) + PlayerAmount[self.uid] = PlayerAmount[self.uid]+1 + Holo.ent = prop + Holo.e2owner = self + + prop:CallOnRemove( "holo_cleanup", function( ent, self, index ) --Give the player more holograms if we get removed + local Holo = CheckIndex( self, index ) + if not Holo then return end + + PlayerAmount[self.uid] = PlayerAmount[self.uid] - 1 + SetIndex( self, index, nil ) + end, self, index ) + end + + if not IsValid(prop) then return nil end + + if color then WireLib.SetColor(Holo.ent, Color(color[1],color[2],color[3],color[4] or 255)) end + + reset_clholo(Holo, scale) -- Reset scale, clips, and visible status + + return prop +end + +-- ----------------------------------------------------------------------------- + +local function CheckSpawnTimer( self, readonly ) + local holo = self.data.holo + if CurTime() >= holo.nextSpawn then + holo.nextSpawn = CurTime()+1 + if CurTime() >= holo.nextBurst then + holo.remainingSpawns = wire_holograms_burst_amount:GetInt() + elseif holo.remainingSpawns < wire_holograms_spawn_amount:GetInt() then + holo.remainingSpawns = wire_holograms_spawn_amount:GetInt() + end + end + + if CurTime() >= holo.nextBurst then + holo.nextBurst = CurTime() + wire_holograms_burst_delay:GetInt() + end + + if holo.remainingSpawns > 0 then + if not readonly then + holo.remainingSpawns = holo.remainingSpawns - 1 + end + return true + else + return false + end +end + +-- Removes all holograms from the given chip. +local function clearholos(self) + -- delete local holos + for index,Holo in pairs(self.data.holos) do remove_holo(Holo) end + + -- delete global holos owned by this chip + local rep = E2HoloRepo[self.uid] + if not rep then return end + for index,Holo in ipairs(rep) do + if Holo.e2owner == self then remove_holo(Holo) end + end +end + +local function clearholos_all(ply_uid) + if ply_uid == nil then + for pl_uid, rep in pairs(E2HoloRepo) do clearholos_all(pl_uid) end + return + end + + for k,Holo in pairs(E2HoloRepo[ply_uid]) do + if Holo and IsValid(Holo.ent) then + Holo.ent:RemoveCallOnRemove( "holo_cleanup" ) + remove_holo(Holo) + end + end + + E2HoloRepo[ply_uid] = {} + PlayerAmount[ply_uid] = 0 +end + +-- ----------------------------------------------------------------------------- + +__e2setcost(30) -- temporary + + + +e2function entity holoCreate(index, vector position, vector scale, angle ang, vector color, string model) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1], position[2], position[3]) + ang = Angle(ang[1], ang[2], ang[3]) + local ret = CreateHolo(self, index, position, scale, ang, color, model) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index, vector position, vector scale, angle ang, vector4 color, string model) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1], position[2], position[3]) + ang = Angle(ang[1], ang[2], ang[3]) + local ret = CreateHolo(self, index, position, scale, ang, color, model) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index, vector position, vector scale, angle ang, vector color) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1], position[2], position[3]) + ang = Angle(ang[1], ang[2], ang[3]) + local ret = CreateHolo(self, index, position, scale, ang, color) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index, vector position, vector scale, angle ang, vector4 color) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1], position[2], position[3]) + ang = Angle(ang[1], ang[2], ang[3]) + local ret = CreateHolo(self, index, position, scale, ang, color) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index, vector position, vector scale, angle ang) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1], position[2], position[3]) + ang = Angle(ang[1], ang[2], ang[3]) + local ret = CreateHolo(self, index, position, scale, ang) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index, vector position, vector scale) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1],position[2],position[3]) + local ret = CreateHolo(self, index, position, scale) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index, vector position) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + position = Vector(position[1],position[2],position[3]) + local ret = CreateHolo(self, index, position) + if IsValid(ret) then return ret end +end + +e2function entity holoCreate(index) + if not checkOwner(self) then return end + if BlockList[self.player:SteamID()] == true or CheckSpawnTimer( self ) == false then return end + local Holo = CheckIndex(self, index) + if not Holo and PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then return end + + local ret = CreateHolo(self, index) + if IsValid(ret) then return ret end +end + +__e2setcost(20) +e2function void holoDelete(index) + local Holo = CheckIndex(self, index) + if not Holo then return end + + remove_holo(Holo) +end + +e2function void holoDeleteAll() + clearholos(self) +end + +e2function void holoDeleteAll( all ) + if all > 0 then + clearholos_all( self.uid ) + else + clearholos( self ) + end +end + +e2function void holoReset(index, string model, vector scale, vector color, string material) + model = GetModel(self, model) + if not model then return end + local Holo = CheckIndex(self, index) + if not Holo then return end + + Holo.ent:SetModel(model) + WireLib.SetColor(Holo.ent, Color(color[1],color[2],color[3],255)) + E2Lib.setMaterial(Holo.ent, material) + + remove_from_queues( Holo.ent ) + reset_clholo(Holo, scale) -- Reset scale, clips, and visible status +end + +__e2setcost(2) + +e2function number holoCanCreate() + if (not checkOwner(self)) then return 0 end + + if CheckSpawnTimer(self, true) == false or PlayerAmount[self.uid] >= wire_holograms_max:GetInt() then + return 0 + end + + return 1 +end + +e2function number holoRemainingSpawns() + CheckSpawnTimer(self, true) + return self.data.holo.remainingSpawns +end + +e2function number holoAmount() + return PlayerAmount[self.uid] +end + +e2function number holoMaxAmount() + return wire_holograms_max:GetInt() +end + +-- ----------------------------------------------------------------------------- + +__e2setcost(15) -- temporary + +e2function void holoScale(index, vector scale) + local Holo = CheckIndex(self, index) + if not Holo then return end + + rescale(Holo, scale) +end + +e2function vector holoScale(index) + local Holo = CheckIndex(self, index) + if not Holo then return {0,0,0} end + + return Holo.scale or {0,0,0} -- TODO: maybe {1,1,1}? +end + +e2function void holoScaleUnits(index, vector size) + local Holo = CheckIndex(self, index) + if not Holo then return end + + local propsize = Holo.ent:OBBMaxs()-Holo.ent:OBBMins() + + local x = size[1] / propsize.x + local y = size[2] / propsize.y + local z = size[3] / propsize.z + + rescale(Holo, Vector(x, y, z)) +end + +e2function vector holoScaleUnits(index) + local Holo = CheckIndex(self, index) + if not Holo then return {0,0,0} end + + local scale = Holo.scale or {0,0,0} -- TODO: maybe {1,1,1}? + + local propsize = Holo.ent:OBBMaxs()-Holo.ent:OBBMins() + + return Vector(scale[1] * propsize.x, scale[2] * propsize.y, scale[3] * propsize.z) +end + + +e2function void holoBoneScale(index, boneindex, vector scale) + local Holo = CheckIndex(self, index) + if not Holo then return end + + rescale(Holo, nil, {boneindex, scale}) +end + +e2function void holoBoneScale(index, string bone, vector scale) + local Holo = CheckIndex(self, index) + if not Holo then return end + local boneindex = Holo.ent:LookupBone(bone) + if boneindex == nil then return end + + rescale(Holo, nil, {boneindex, scale}) +end + +e2function vector holoBoneScale(index, boneindex) + local Holo = CheckIndex(self, index) + if not Holo then return {0,0,0} end + return Holo.bone_scale[boneindex] or {0, 0, 0} +end + +e2function vector holoBoneScale(index, string bone) + local Holo = CheckIndex(self, index) + if not Holo then return {0,0,0} end + local boneindex = Holo.ent:LookupBone(bone) + if boneindex == nil then return {0,0,0} end + return Holo.bone_scale[boneindex] or {0, 0, 0} +end +__e2setcost(1) +e2function number holoClipsAvailable() + return wire_holograms_max_clips:GetInt() +end + +__e2setcost(15) +e2function void holoClipEnabled(index, enabled) -- Clip at first index + local Holo = CheckIndex(self, index) + if not Holo then return end + + if enabled == 1 then + enable_clip(Holo, 1, true) + elseif enabled == 0 then + enable_clip(Holo, 1, false) + end +end + +e2function void holoClipEnabled(index, clipidx, enabled) + local Holo = CheckIndex(self, index) + if not Holo then return end + + if enabled == 1 then + enable_clip(Holo, clipidx, true) + elseif enabled == 0 then + enable_clip(Holo, clipidx, false) + end +end + +e2function void holoClip(index, vector origin, vector normal, isglobal) -- Clip at first index + local Holo = CheckIndex(self, index) + if not Holo then return end + + set_clip(Holo, 1, Vector(origin[1], origin[2], origin[3]), Vector(normal[1], normal[2], normal[3]), isglobal ~= 0 and 0 or Holo.ent:EntIndex()) +end + +e2function void holoClip(index, clipidx, vector origin, vector normal, isglobal) + local Holo = CheckIndex(self, index) + if not Holo then return end + + set_clip(Holo, clipidx, Vector(origin[1], origin[2], origin[3]), Vector(normal[1], normal[2], normal[3]), isglobal ~= 0 and 0 or Holo.ent:EntIndex()) +end + +e2function void holoClip(index, vector origin, vector normal, entity localent) -- Clip at first index + local Holo = CheckIndex(self, index) + if not Holo then return end + + set_clip(Holo, 1, Vector(origin[1], origin[2], origin[3]), Vector(normal[1], normal[2], normal[3]), localent:EntIndex()) +end + +e2function void holoClip(index, clipidx, vector origin, vector normal, entity localent) + local Holo = CheckIndex(self, index) + if not Holo then return end + + set_clip(Holo, clipidx, Vector(origin[1], origin[2], origin[3]), Vector(normal[1], normal[2], normal[3]), localent:EntIndex()) +end + +e2function void holoPos(index, vector position) + local Holo = CheckIndex(self, index) + if not Holo then return end + + WireLib.setPos(Holo.ent, Vector(position[1],position[2],position[3])) +end + +e2function void holoAng(index, angle ang) + local Holo = CheckIndex(self, index) + if not Holo then return end + + WireLib.setAng(Holo.ent, Angle(ang[1],ang[2],ang[3])) +end + +-- ----------------------------------------------------------------------------- + +e2function void holoColor(index, vector color) + local Holo = CheckIndex(self, index) + if not Holo then return end + + WireLib.SetColor(Holo.ent, Color(color[1],color[2],color[3],Holo.ent:GetColor().a)) +end + +e2function void holoColor(index, vector4 color) + local Holo = CheckIndex(self, index) + if not Holo then return end + + WireLib.SetColor(Holo.ent, Color(color[1],color[2],color[3],color[4])) +end + +e2function void holoColor(index, vector color, alpha) + local Holo = CheckIndex(self, index) + if not Holo then return end + + WireLib.SetColor(Holo.ent, Color(color[1],color[2],color[3],alpha)) +end + +e2function void holoAlpha(index, alpha) + local Holo = CheckIndex(self, index) + if not Holo then return end + + local c = Holo.ent:GetColor() + c.a = alpha + WireLib.SetColor(Holo.ent, c) +end + +__e2setcost(10) +e2function void holoShadow(index, has_shadow) + local Holo = CheckIndex(self, index) + if not Holo then return end + + Holo.ent:DrawShadow( has_shadow ~= 0 ) +end + +e2function void holoDisableShading( index, disable ) + local Holo = CheckIndex(self, index) + if not Holo then return end + + Holo.ent:SetNWBool( "disable_shading", disable == 1 ) +end + +-- ----------------------------------------------------------------------------- + +e2function array holoModelList() + local mlist = {} + + for k,_ in pairs( ModelList ) do + mlist[#mlist + 1] = k + end + + return mlist +end + +__e2setcost(1) +e2function number holoModelAny() + return wire_holograms_modelany:GetInt() +end + +__e2setcost(10) +e2function void holoModel(index, string model) + local Holo = CheckIndex(self, index) + if not Holo then return end + + model = GetModel(self, model) + if not model then return end + + Holo.ent:SetModel(model) +end + +e2function void holoModel(index, string model, skin) + local Holo = CheckIndex(self, index) + if not Holo then return end + + skin = skin - skin % 1 + + model, skin = GetModel(self, model, skin) + if not model then return end + + Holo.ent:SetModel(model) + Holo.ent:SetSkin(skin) +end + +e2function void holoSkin(index, skin) + local Holo = CheckIndex(self, index) + if not Holo then return end + + skin = skin - skin % 1 + local _, skin = GetModel(self, Holo.ent:GetModel(), skin) + + Holo.ent:SetSkin(skin) +end + +e2function void holoMaterial(index, string material) + local Holo = CheckIndex(self, index) + if not Holo then return end + E2Lib.setMaterial(Holo.ent, material) +end + +e2function void holoPlayerColor(index, vector color) + local Holo = CheckIndex(self, index) + if not Holo then return end + + local r = color[1]/255 + local g = color[2]/255 + local b = color[3]/255 + + set_player_color(Holo, Vector(r, g, b)) +end + +e2function void holoRenderFX(index, effect) + local Holo = CheckIndex(self, index) + if not Holo then return end + + effect = effect - effect % 1 + Holo.ent:SetKeyValue("renderfx",effect) +end + +e2function void holoBodygroup(index, bgrp_id, bgrp_subid) + local Holo = CheckIndex(self, index) + if not Holo then return end + + Holo.ent:SetBodygroup(bgrp_id, bgrp_subid) +end + +e2function number holoBodygroups(index, bgrp_id) + local Holo = CheckIndex(self, index) + if not Holo then return end + + return Holo.ent:GetBodygroupCount(bgrp_id) +end + +-- ----------------------------------------------------------------------------- + +e2function void holoVisible(index, entity ply, visible) + local Holo = CheckIndex(self, index) + if not Holo or not IsValid( ply ) or not ply:IsPlayer() then return end + + set_visible(Holo, { ply }, visible) +end + +e2function void holoVisible(index, array players, visible) + local Holo = CheckIndex(self, index) + if not Holo then return end + + set_visible(Holo, players, visible) +end + +-- ----------------------------------------------------------------------------- +local function Parent_Hologram(holo, ent, attachment) + if ent:GetParent() and ent:GetParent():IsValid() and ent:GetParent() == holo.ent then return end + + holo.ent:SetParent(ent) + + if attachment ~= nil then + holo.ent:Fire("SetParentAttachmentMaintainOffset", attachment, 0.01) + end +end + +-- Check for recursive parenting +local function Check_Parents(child, parent) + while IsValid(parent:GetParent()) do + parent = parent:GetParent() + if parent == child then + return false + end + end + + return true +end + +__e2setcost(40) +e2function void holoParent(index, otherindex) + local Holo = CheckIndex(self, index) + if not Holo then return end + + local Holo2 = CheckIndex(self, otherindex) + if not Holo2 then return end + + if not Check_Parents(Holo.ent, Holo2.ent) then return end + + Parent_Hologram(Holo, Holo2.ent, nil) +end + +e2function void holoParent(index, entity ent) + if not IsValid(ent) then return end + local Holo = CheckIndex(self, index) + if not Holo then return end + + if not Check_Parents(Holo.ent, ent) then return end + + Parent_Hologram(Holo, ent, nil) +end + +e2function void holoParentAttachment(index, entity ent, string attachmentName) + if not IsValid(ent) then return end + local Holo = CheckIndex(self, index) + if not Holo then return end + + Parent_Hologram(Holo, ent, attachmentName) +end + +e2function void holoUnparent(index) + local Holo = CheckIndex(self, index) + if not Holo then return end + + Holo.ent:SetParent(nil) + Holo.ent:SetParentPhysNum(0) +end + +-- ----------------------------------------------------------------------------- + +__e2setcost(2) +e2function entity holoEntity(index) + local Holo = CheckIndex(self, index) + if Holo and IsValid(Holo.ent) then return Holo.ent end +end + +__e2setcost(30) +--- Gets the hologram index of the given entity, if any. Returns 0 on failure. +e2function number holoIndex(entity ent) + if not IsValid(ent) then return 0 end + if ent:GetClass() ~= "gmod_wire_hologram" then return 0 end + + -- check local holos + for k,Holo in pairs(self.data.holos) do + if(ent == Holo.ent) then return k end + end + + -- check global holos + for k,Holo in pairs(E2HoloRepo[self.uid]) do + if isnumber(k) and ent == Holo.ent then return -k end + end + return 0 +end + +-- ----------------------------------------------------------------------------- + +registerCallback("construct", function(self) + if not E2HoloRepo[self.uid] then + E2HoloRepo[self.uid] = {} + PlayerAmount[self.uid] = 0 + end + --self.data.HoloEffect = false + self.data.holos = {} + self.data.holo = { + nextSpawn = CurTime()+1, + nextBurst = CurTime()+wire_holograms_burst_delay:GetInt(), + remainingSpawns = wire_holograms_burst_amount:GetInt() + } +end) + +registerCallback("destruct", function(self) + clearholos(self) +end) + +-- ----------------------------------------------------------------------------- + +local function ConsoleMessage(ply, text) + if ply:IsValid() then + ply:PrintMessage( HUD_PRINTCONSOLE, text ) + else + print(text) + end +end + +concommand.Add( "wire_holograms_remove_all", function( ply, com, args ) + if ply:IsValid() and not ply:IsAdmin() then return end + + clearholos_all() +end ) + +concommand.Add( "wire_holograms_block", function( ply, com, args ) + if ply:IsValid() and not ply:IsAdmin() then return end + + if not args[1] then + ConsoleMessage( ply, "Command requires a player's name (or part of their name)" ) + ConsoleMessage( ply, "Usage: wire_holograms_block [name]" ) + return + end + + local name = args[1]:lower() + local players = E2Lib.filterList(player.GetAll(), function(ent) return ent:GetName():lower():match(name) end) + + if #players == 1 then + local v = players[1] + if BlockList[v:SteamID()] == true then + ConsoleMessage( ply, v:GetName() .. " is already in the holograms blocklist!" ) + else + local uid = v:UniqueID() + if E2HoloRepo[uid] then + clearholos_all(uid) + end + BlockList[v:SteamID()] = true + for _,p in ipairs( player.GetAll() ) do + p:PrintMessage( HUD_PRINTTALK, "(ADMIN) " .. v:GetName() .. " added to holograms blocklist" ) + end + end + elseif #players > 1 then + ConsoleMessage( ply, "More than one player matches that name!" ) + else + ConsoleMessage( ply, "No player names found with " .. args[1] ) + end +end ) + +concommand.Add( "wire_holograms_unblock", function( ply, com, args ) + if ply:IsValid() and not ply:IsAdmin() then return end + + if not args[1] then + ConsoleMessage( ply, "Command requires a player's name (or part of their name)" ) + ConsoleMessage( ply, "Usage: wire_holograms_unblock [name]" ) + return + end + + local name = args[1]:lower() + local players = E2Lib.filterList(player.GetAll(), function(ent) return ent:GetName():lower():match(name) end) + + if #players == 1 then + local v = players[1] + if BlockList[v:SteamID()] == true then + BlockList[v:SteamID()] = nil + for _,player in ipairs( player.GetAll() ) do + player:PrintMessage( HUD_PRINTTALK, "(ADMIN) " .. v:GetName() .. " removed from holograms blocklist" ) + end + else + ConsoleMessage( ply, v:GetName() .. " is not in the holograms blocklist!" ) + end + elseif #players > 1 then + ConsoleMessage( ply, "More than one player matches that name!" ) + else + ConsoleMessage( ply, "No player names found with " .. args[1] ) + end +end ) + +concommand.Add( "wire_holograms_block_id", function( ply, com, args ) + if ply:IsValid() and not ply:IsAdmin() then return end + + local steamID = table.concat(args) + + if not steamID:match("STEAM_[0-9]:[0-9]:[0-9]+") then + ConsoleMessage( ply, "Invalid SteamID format" ) + ConsoleMessage( ply, "Usage: wire_holograms_block_id STEAM_X:X:XXXXXX" ) + return + end + + if BlockList[steamID] == true then + ConsoleMessage( ply, steamID .. " is already in the holograms blocklist!" ) + else + BlockList[steamID] = true + for _,player in ipairs( player.GetAll() ) do + player:PrintMessage( HUD_PRINTTALK, "(ADMIN) " .. steamID .. " added to holograms blocklist" ) + end + local uid + for _,v in pairs( player.GetAll() ) do + if v:SteamID() == steamID then + uid = v:UniqueID() + if (E2HoloRepo[uid]) then + clearholos_all(uid) + return + end + end + end + end +end ) + +concommand.Add( "wire_holograms_unblock_id", function( ply, com, args ) + if ply:IsValid() and not ply:IsAdmin() then return end + + local steamID = table.concat(args) + + if not steamID:match("STEAM_[0-9]:[0-9]:[0-9]+") then + ConsoleMessage( ply, "Invalid SteamID format" ) + ConsoleMessage( ply, "Usage: wire_holograms_unblock_id STEAM_X:X:XXXXXX" ) + return + end + + if BlockList[steamID] == true then + BlockList[steamID] = nil + for _,player in ipairs( player.GetAll() ) do + player:PrintMessage( HUD_PRINTTALK, "(ADMIN) " .. steamID .. " removed from holograms blocklist" ) + end + else + ConsoleMessage( ply, steamID .. " is not in the holograms blocklist!" ) + end +end ) + +-- ----------------------------------------------------------------------------- + +wire_holograms = {} -- This global table is used to share certain functions and variables with UWSVN +wire_holograms.CheckIndex = CheckIndex + +registerCallback( "postinit", function() + timer.Simple( 1, function() + wire_holograms = nil + end ) +end ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/http.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/http.lua new file mode 100644 index 0000000..8f6bc6a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/http.lua @@ -0,0 +1,125 @@ +/* + Simple HTTP Extension + (McLovin) +*/ + +E2Lib.RegisterExtension( "http", false, "Lets E2 chips make web requests to the real internet.", "Allows any E2 to make your server make arbitrary HTTP GET requests to any site. It can use this to make HTTP requests to any IP address inside your local network." ) + +local cvar_delay = CreateConVar( "wire_expression2_http_delay", "3", FCVAR_ARCHIVE ) +local cvar_timeout = CreateConVar( "wire_expression2_http_timeout", "15", FCVAR_ARCHIVE ) + +local requests = {} +local run_on = { + ents = {} +} + +local function player_can_request( ply ) + local preq = requests[ply] + + return !preq or + (preq.in_progress and preq.t_start and (CurTime() - preq.t_start) >= cvar_timeout:GetFloat()) or + (!preq.in_progress and preq.t_end and (CurTime() - preq.t_end) >= cvar_delay:GetFloat()) +end + +__e2setcost( 20 ) + +e2function void httpRequest( string url ) + local ply = self.player + if !player_can_request( ply ) or url == "" then return end + + requests[ply] = { + in_progress = true, + t_start = CurTime(), + t_end = 0, + url = url + } + + http.Fetch(url, function( contents, size, headers, code ) + if !IsValid( ply ) or !ply:IsPlayer() or !requests[ply] then return end + + local preq = requests[ply] + + preq.t_end = CurTime() + preq.in_progress = false + preq.data = contents or "" + preq.success = 1 + + local ent = self.entity + if IsValid(ent) and run_on.ents[ent] then + self.data.httpClk = preq + ent:Execute() + self.data.httpClk = nil + end + + end, function( err ) + if !IsValid( ply ) or !ply:IsPlayer() or !requests[ply] then return end + + local preq = requests[ply] + + preq.t_end = CurTime() + preq.in_progress = false + preq.data = "" + preq.success = 0 + + local ent = self.entity + if IsValid(ent) and run_on.ents[ent] then + self.data.httpClk = preq + ent:Execute() + self.data.httpClk = nil + end + end) +end + +__e2setcost( 5 ) + +e2function number httpCanRequest() + return ( player_can_request( self.player ) ) and 1 or 0 +end + +e2function number httpClk() + return self.data.httpClk and 1 or 0 +end + +e2function string httpData() + local preq = requests[self.player] + + return preq and preq.data or "" +end + +e2function number httpSuccess() + local preq = requests[self.player] + + return preq and preq.success or 0 +end + +e2function string httpRequestUrl() + local preq = requests[self.player] + + return preq and preq.url or "" +end + +e2function string httpUrlEncode(string data) + local ndata = string.gsub( data, "[^%w _~%.%-]", function( str ) + local nstr = string.format( "%X", string.byte( str ) ) + + return "%" .. ( ( string.len( nstr ) == 1 ) and "0" or "" ) .. nstr + end ) + + return string.gsub( ndata, " ", "+" ) +end + +e2function string httpUrlDecode(string data) + local ndata = string.gsub( data, "+", " " ) + + return string.gsub( ndata, "(%%%x%x)", function( str ) + return string.char( tonumber( string.Right( str, 2 ), 16 ) ) + end ) +end + +e2function void runOnHTTP( number rohttp ) + run_on.ents[self.entity] = rohttp ~= 0 and true or nil +end + +registerCallback( "destruct", function( self ) + run_on.ents[self.entity] = nil +end ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/init.lua new file mode 100644 index 0000000..a3ac931 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/init.lua @@ -0,0 +1,338 @@ +AddCSLuaFile() + +--[[ + Expression 2 for Garry's Mod + Andreas "Syranide" Svensson, me@syranide.com +]] + +wire_expression2_delta = 0.0000001000000 +delta = wire_expression2_delta + +-- functions to type-check function return values. + +local wire_expression2_debug = CreateConVar("wire_expression2_debug", 0, 0) + +if SERVER then + cvars.AddChangeCallback("wire_expression2_debug", function(CVar, PreviousValue, NewValue) + if (PreviousValue) == NewValue then return end + wire_expression2_reload() + end) +end + +--- This function ensures that the given function shows up by the given name in stack traces. +--- It does so by eval'ing a generated block of code which invokes the actual function. +--- Tail recursion optimization is specifically avoided by introducing a local variable in the generated code block. +local function namefunc(func, name) + -- Filter the name + name = name:gsub("[^A-Za-z_0-9]", "_") + + -- RunString doesn't have a return value, so we need to go via a global variable + wire_expression2_namefunc = func + RunString(([[ + local %s = wire_expression2_namefunc + function wire_expression2_namefunc(...) + local ret = %s(...) + return ret + end + ]]):format(name, name)) + local ret = wire_expression2_namefunc + wire_expression2_namefunc = nil + + -- Now ret contains the wrapped function and we can just return it. + return ret +end + +-- Installs a typecheck in a function identified by the given signature. +local function makecheck(signature) + if signature == "op:seq()" then return end + local name = signature:match("^([^(]*)") + local entry = wire_expression2_funcs[signature] + local oldfunc, signature, rets, func, cost = entry.oldfunc, unpack(entry) + + if oldfunc then return end + oldfunc = namefunc(func, "e2_" .. name) + + function func(...) + local retval = oldfunc(...) + + local checker = wire_expression_types2[rets][5] + if not checker then return retval end + + local ok, msg = pcall(checker, retval) + if ok then return retval end + debug.Trace() + local full_signature = E2Lib.generate_signature(signature, rets) + error(string.format("Type check for function %q failed: %s\n", full_signature, msg), 0) + + return retval + end + + entry[3] = func + entry.oldfunc = oldfunc +end + +-- ---------------------------------------------------------------------- + +function wire_expression2_reset_extensions() + wire_expression_callbacks = { + construct = {}, + destruct = {}, + preexecute = {}, + postexecute = {}, + } + + wire_expression_types = {} + wire_expression_types2 = { + [""] = { + [5] = function(retval) if retval ~= nil then error("Return value of void function is not nil.", 0) end end + } + } + wire_expression2_funcs = {} + wire_expression2_funclist = {} + if CLIENT then wire_expression2_funclist_lowercase = {} end + wire_expression2_constants = {} +end + +local function isValidTypeId(id) + return #id == (string.sub(id, 1, 1) == "x" and 3 or 1) +end + +-- additional args: , , +function registerType(name, id, def, ...) + if not isValidTypeId(id) then + -- this type ID format is relied on in various places including + -- E2Lib.splitType, and malformed type IDs cause confusing and subtle + -- errors. Catch this early and blame the caller. + error(string.format("malformed type ID '%s' - type IDs must be one " .. + "character long, or three characters long starting with an x", id), 2) + end + + wire_expression_types[string.upper(name)] = { id, def, ... } + wire_expression_types2[id] = { string.upper(name), def, ... } + if not WireLib.DT[string.upper(name)] then + WireLib.DT[string.upper(name)] = { Zero = def } + end +end + +function wire_expression2_CallHook(hookname, ...) + if not wire_expression_callbacks[hookname] then return end + local ret_array = {} + local errors = {} + local ok, ret + for i, callback in ipairs(wire_expression_callbacks[hookname]) do + ok, ret = pcall(callback, ...) + if not ok then + if ret == "cancelhook" then break end + table.insert(errors, "\n" .. ret) + ret_array = nil + else + if ret_array then table.insert(ret_array, ret or false) end + end + end + if not ret_array then error("Error(s) occured while executing '" .. hookname .. "' hook:" .. table.concat(errors), 0) end + return ret_array +end + +function registerCallback(event, callback) + if not wire_expression_callbacks[event] then wire_expression_callbacks[event] = {} end + table.insert(wire_expression_callbacks[event], callback) +end + +local tempcost + +function __e2setcost(cost) + tempcost = cost +end + +function __e2getcost() + return tempcost +end + +function registerOperator(name, pars, rets, func, cost, argnames) + local signature = "op:" .. name .. "(" .. pars .. ")" + + wire_expression2_funcs[signature] = { signature, rets, func, cost or tempcost, argnames = argnames } + if wire_expression2_debug:GetBool() then makecheck(signature) end +end + +function registerFunction(name, pars, rets, func, cost, argnames) + local signature = name .. "(" .. pars .. ")" + + wire_expression2_funcs[signature] = { signature, rets, func, cost or tempcost, argnames = argnames } + wire_expression2_funclist[name] = true + if wire_expression2_debug:GetBool() then makecheck(signature) end +end + +function E2Lib.registerConstant(name, value, literal) + if name:sub(1, 1) ~= "_" then name = "_" .. name end + if not value and not literal then value = _G[name] end + + wire_expression2_constants[name] = value +end + +-- --------------------------------------------------------------- + +-- Load clientside files here +-- Serverside files are instead loaded in extloader.lua, because they need additional parsing +do + local function loadFiles( extra, list ) + for _, filename in pairs(list) do + if SERVER then AddCSLuaFile("entities/gmod_wire_expression2/core/" .. extra .. filename) + else include("entities/gmod_wire_expression2/core/" .. extra .. filename) end + end + end + + loadFiles("custom/",file.Find("entities/gmod_wire_expression2/core/custom/cl_*.lua", "LUA")) + loadFiles("",file.Find("entities/gmod_wire_expression2/core/cl_*.lua", "LUA")) +end + +if SERVER then + util.AddNetworkString("e2_functiondata_start") + util.AddNetworkString("e2_functiondata_chunk") + + -- Serverside files are loaded in extloader + include("extloader.lua") + + -- -- Transfer E2 function info to the client for validation and syntax highlighting purposes -- -- + + do + local miscdata = {} -- Will contain {E2 types info, constants}, this whole table is under 1kb + local functiondata = {} -- Will contain {functionname = {returntype, cost, argnames}, this will be between 50-100kb + + -- Fills out the above two tables + function wire_expression2_prepare_functiondata() + miscdata = { {}, wire_expression2_constants } + functiondata = {} + for typename, v in pairs(wire_expression_types) do + miscdata[1][typename] = v[1] -- typeid (s) + end + + for signature, v in pairs(wire_expression2_funcs) do + functiondata[signature] = { v[2], v[4], v.argnames } -- ret (s), cost (n), argnames (t) + end + end + + wire_expression2_prepare_functiondata() + + -- Send everything + local targets = {} + local function sendData(target) + if IsValid(target) and target:IsPlayer() and targets[target] == nil then + targets[target] = "start" + net.Start("e2_functiondata_start") + net.WriteTable(miscdata[1]) + net.WriteTable(miscdata[2]) + net.Send(target) + end + end + + hook.Add("Think", "wire_expression2_sendfunctions_think", function() + for k, signature in pairs(targets) do + if not k:IsValid() or not k:IsPlayer() then + targets[k] = nil + else + net.Start("e2_functiondata_chunk") + if signature == "start" then signature = nil end -- We want to start with next(functiondata, nil) but can't store nil in a table + local tab + while net.BytesWritten() < 64000 do + signature, tab = next(functiondata, signature) + if not signature then break end + net.WriteString(signature) -- The function signature ["holoAlpha(nn)"] + net.WriteString(tab[1]) -- The function's return type ["s"] + net.WriteUInt(tab[2] or 0, 16) -- The function's cost [5] + net.WriteTable(tab[3] or {}) -- The function's argnames table (if a table isn't set, it'll just send a 1 byte blank table) + end + net.WriteString("") -- Needed to break out of the receiving for loop without affecting the final completion bit boolean + net.WriteBit(signature == nil) -- If we're at the end of the table, next will return nil, thus sending a true here + targets[k] = signature -- If nil, this'll remove the entry. Otherwise, this'll set a new next(t,k) starting point + net.Send(k) + end + end + end) + + local antispam = {} + function wire_expression2_sendfunctions(ply, isconcmd) + if isconcmd and not game.SinglePlayer() then + if not antispam[ply] then antispam[ply] = 0 end + if antispam[ply] > CurTime() then + ply:PrintMessage(HUD_PRINTCONSOLE, "This command has a 60 second anti spam protection. Try again in " .. math.Round(antispam[ply] - CurTime()) .. " seconds.") + return + end + antispam[ply] = CurTime() + 60 + end + sendData(ply) + end + + -- add a console command the user can use to re-request the function info, in case of errors or updates + concommand.Add("wire_expression2_sendfunctions", wire_expression2_sendfunctions) + + if game.SinglePlayer() then + -- If single player, send everything immediately + hook.Add("PlayerInitialSpawn", "wire_expression2_sendfunctions", sendData) + end + end + +elseif CLIENT then + + e2_function_data_received = nil + -- -- Receive E2 function info from the server for validation and syntax highlighting purposes -- -- + + wire_expression2_reset_extensions() + + local function insertData(functiondata) + -- functions + for signature, tab in pairs(functiondata) do + local fname = signature:match("^([^(:]+)%(") + if fname then + wire_expression2_funclist[fname] = true + wire_expression2_funclist_lowercase[fname:lower()] = fname + end + if not next(tab[3]) then tab[3] = nil end -- If the function has no argnames table, the server will just send a blank table + wire_expression2_funcs[signature] = { signature, tab[1], false, tab[2], argnames = tab[3] } + end + + e2_function_data_received = true + + if wire_expression2_editor then + wire_expression2_editor:Validate(false) + + -- Update highlighting on all tabs + for i = 1, wire_expression2_editor:GetNumTabs() do + wire_expression2_editor:GetEditor(i).PaintRows = {} + end + end + end + + local function insertMiscData(types, constants) + wire_expression2_reset_extensions() + + -- types + for typename, typeid in pairs(types) do + wire_expression_types[typename] = { typeid } + wire_expression_types2[typeid] = { typename } + end + + -- constants + wire_expression2_constants = constants + end + + local buffer = {} + net.Receive("e2_functiondata_start", function(len) + buffer = {} + insertMiscData(net.ReadTable(), net.ReadTable()) + end) + + net.Receive("e2_functiondata_chunk", function(len) + while true do + local signature = net.ReadString() + if signature == "" then break end -- We've reached the end of the packet + buffer[signature] = { net.ReadString(), net.ReadUInt(16), net.ReadTable() } -- ret, cost, argnames + end + + if net.ReadBit() == 1 then + insertData(buffer) -- We've received the last packet! + end + end) +end + +include("e2doc.lua") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/matrix.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/matrix.lua new file mode 100644 index 0000000..55a2b2f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/matrix.lua @@ -0,0 +1,1529 @@ +/******************************************************************************\ + Matrix support +\******************************************************************************/ + +local delta = wire_expression2_delta + +local function clone(a) + local b = {} + for k,v in ipairs(a) do + b[k] = v + end + return b +end + + +/******************************************************************************\ + 2x2 Matrices +\******************************************************************************/ + +registerType("matrix2", "xm2", { 0, 0, + 0, 0 }, + function(self, input) + local ret = {} + for k,v in pairs(input) do ret[k] = v end + return ret + end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 4 then error("Return value does not have exactly 4 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 4 + end +) + +/******************************************************************************/ +// Common functions - explicit matrix solvers + +local function det2(a) + return ( a[1] * a[4] - a[3] * a[2] ) +end + +local function inverse2(a) + local det = det2(a) + if det == 0 then return { 0, 0, + 0, 0 } + end + return { a[4]/det, -a[2]/det, + -a[3]/det, a[1]/det } +end + +/******************************************************************************/ + +__e2setcost(1) -- approximated + +e2function matrix2 matrix2() + return { 0, 0, + 0, 0 } +end + +__e2setcost(5) -- temporary + +e2function matrix2 matrix2(vector2 rv1, vector2 rv2) + return { rv1[1], rv2[1], + rv1[2], rv2[2] } +end + +e2function matrix2 rowMatrix2(vector2 rv1, vector2 rv2) + return { rv1[1], rv1[2], + rv2[1], rv2[2] } +end + +e2function matrix2 matrix2(rv1, rv2, rv3, rv4) + return { rv1, rv2, + rv3, rv4 } +end + +e2function matrix2 matrix2(matrix rv1) + return { rv1[1], rv2[2], + rv1[4], rv2[5] } +end + +e2function matrix2 identity2() + return { 1, 0, + 0, 1 } +end + +/******************************************************************************/ + +registerOperator("ass", "xm2", "xm2", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ +// Comparison + +e2function number operator_is(matrix2 rv1) + if rv1[1] > delta || -rv1[1] > delta || + rv1[2] > delta || -rv1[2] > delta || + rv1[3] > delta || -rv1[3] > delta || + rv1[4] > delta || -rv1[4] > delta + then return 1 else return 0 end +end + +e2function number operator==(matrix2 rv1, matrix2 rv2) + if rv1[1] - rv2[1] <= delta && rv2[1] - rv1[1] <= delta && + rv1[2] - rv2[2] <= delta && rv2[2] - rv1[2] <= delta && + rv1[3] - rv2[3] <= delta && rv2[3] - rv1[3] <= delta && + rv1[4] - rv2[4] <= delta && rv2[4] - rv1[4] <= delta + then return 1 else return 0 end +end + +e2function number operator!=(matrix2 rv1, matrix2 rv2) + if rv1[1] - rv2[1] > delta && rv2[1] - rv1[1] > delta && + rv1[2] - rv2[2] > delta && rv2[2] - rv1[2] > delta && + rv1[3] - rv2[3] > delta && rv2[3] - rv1[3] > delta && + rv1[4] - rv2[4] > delta && rv2[4] - rv1[4] > delta + then return 1 else return 0 end +end + +/******************************************************************************/ +// Basic operations + +e2function matrix2 operator_neg(matrix2 rv1) + return { -rv1[1], -rv1[2], + -rv1[3], -rv1[4] } +end + +e2function matrix2 operator+(matrix2 rv1, matrix2 rv2) + return { rv1[1] + rv2[1], rv1[2] + rv2[2], + rv1[3] + rv2[3], rv1[4] + rv2[4] } +end + +e2function matrix2 operator-(matrix2 rv1, matrix2 rv2) + return { rv1[1] - rv2[1], rv1[2] - rv2[2], + rv1[3] - rv2[3], rv1[4] - rv2[4] } +end + +e2function matrix2 operator*(rv1, matrix2 rv2) + return { rv1 * rv2[1], rv1 * rv2[2], + rv1 * rv2[3], rv1 * rv2[4] } +end + +e2function matrix2 operator*(matrix2 rv1, rv2) + return { rv1[1] * rv2, rv1[2] * rv2, + rv1[3] * rv2, rv1[4] * rv2 } +end + +e2function vector2 operator*(matrix2 rv1, vector2 rv2) + return { rv1[1] * rv2[1] + rv1[2] * rv2[2], + rv1[3] * rv2[1] + rv1[4] * rv2[2] } +end + +e2function matrix2 operator*(matrix2 rv1, matrix2 rv2) + return { rv1[1] * rv2[1] + rv1[2] * rv2[3], + rv1[1] * rv2[2] + rv1[2] * rv2[4], + rv1[3] * rv2[1] + rv1[4] * rv2[3], + rv1[3] * rv2[2] + rv1[4] * rv2[4] } +end + +e2function matrix2 operator/(matrix2 rv1, rv2) + return { rv1[1] / rv2, rv1[2] / rv2, + rv1[3] / rv2, rv1[4] / rv2 } +end + +e2function matrix2 operator^(matrix2 rv1, rv2) + + if rv2 == -1 then return ( inverse2(rv1) ) + + elseif rv2 == 0 then return { 1, 0, + 0, 1 } + + elseif rv2 == 1 then return rv1 + + elseif rv2 == 2 then + return { rv1[1] * rv1[1] + rv1[2] * rv1[3], + rv1[1] * rv1[2] + rv1[2] * rv1[4], + rv1[3] * rv1[1] + rv1[4] * rv1[3], + rv1[3] * rv1[2] + rv1[4] * rv1[4] } + + else return { 0, 0, + 0, 0 } + end +end + +/******************************************************************************/ +// Row/column/element manipulation + +e2function vector2 matrix2:row(rv2) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 2 then k = 2 + else k = rv2 - rv2 % 1 end + + local x = this[k * 2 - 1] + local y = this[k * 2] + return { x, y } +end + +e2function vector2 matrix2:column(rv2) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 2 then k = 2 + else k = rv2 - rv2 % 1 end + + local x = this[k] + local y = this[k + 2] + return { x, y } +end + +e2function matrix2 matrix2:setRow(rv2, rv3, rv4) + local k + + if rv2 < 1 then k = 2 + elseif rv2 > 2 then k = 4 + else k = (rv2 - rv2 % 1)*2 end + + local a = clone(this) + a[k - 1] = rv3 + a[k] = rv4 + return a +end + +e2function matrix2 matrix2:setRow(rv2, vector2 rv3) + local k + + if rv2 < 1 then k = 2 + elseif rv2 > 2 then k = 4 + else k = (rv2 - rv2 % 1)*2 end + + local a = clone(this) + a[k - 1] = rv3[1] + a[k] = rv3[2] + return a +end + + +e2function matrix2 matrix2:setColumn(rv2, rv3, rv4) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 2 then k = 2 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k] = rv3 + a[k + 2] = rv4 + return a +end + +e2function matrix2 matrix2:setColumn(rv2, vector2 rv3) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 2 then k = 2 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k] = rv3[1] + a[k + 2] = rv3[2] + return a +end + +e2function matrix2 matrix2:swapRows() + + this = { this[3], this[4], + this[1], this[2] } + return this +end + +e2function matrix2 matrix2:swapColumns() + + this = { this[2], this[1], + this[4], this[3] } + return this +end + +e2function number matrix2:element(rv2, rv3) + local i, j + + if rv2 < 1 then i = 1 + elseif rv2 > 2 then i = 2 + else i = rv2 - rv2 % 1 end + if rv3 < 1 then j = 1 + elseif rv3 > 2 then j = 2 + else j = rv3 - rv3 % 1 end + + local k = i + (j - 1) * 2 + return this[k] +end + +e2function matrix2 matrix2:setElement(rv2, rv3, rv4) + local i, j + + if rv2 < 1 then i = 1 + elseif rv2 > 2 then i = 2 + else i = rv2 - rv2 % 1 end + if rv3 < 1 then j = 1 + elseif rv3 > 2 then j = 2 + else j = rv3 - rv3 % 1 end + + local a = clone(this) + a[i + (j - 1) * 2] = rv4 + return a +end + +e2function matrix2 matrix2:swapElements(rv2, rv3, rv4, rv5) + local i1, j1, i2, j2 + + if rv2 < 1 then i1 = 1 + elseif rv2 > 3 then i1 = 3 + else i1 = rv2 - rv2 % 1 end + + if rv3 < 1 then j1 = 1 + elseif rv3 > 3 then j1 = 3 + else j1 = rv3 - rv3 % 1 end + + if rv4 < 1 then i2 = 1 + elseif rv4 > 3 then i2 = 3 + else i2 = rv4 - rv4 % 1 end + + if rv5 < 1 then j2 = 1 + elseif rv5 > 3 then j2 = 3 + else j2 = rv5 - rv5 % 1 end + + local k1 = i1 + (j1 - 1) * 2 + local k2 = i2 + (j2 - 1) * 2 + local a = clone(this) + a[k1], a[k2] = this[k2], this[k1] + return a +end + +/******************************************************************************/ +// Useful matrix maths functions + +e2function vector2 diagonal(matrix2 rv1) + return { rv1[1], rv1[4] } +end + +e2function number trace(matrix2 rv1) + return ( rv1[1] + rv[4] ) +end + +e2function number det(matrix2 rv1) + return ( det2(rv1) ) +end + +e2function matrix2 transpose(matrix2 rv1) + return { rv1[1], rv1[3], + rv1[2], rv1[4] } +end + +e2function matrix2 adj(matrix2 rv1) + return { rv1[4], -rv1[2], + -rv1[3], rv1[1] } +end + + +/******************************************************************************\ + 3x3 Matrices +\******************************************************************************/ + +registerType("matrix", "m", { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 }, + function(self, input) + local ret = {} + for k,v in pairs(input) do ret[k] = v end + return ret + end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 9 then error("Return value does not have exactly 9 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 9 + end +) + +/******************************************************************************/ +// Common functions - matrix solvers + +/* +-- Useful functions - may be used in the future? These have been written explicitly in the relevant commands for now. + +local function transpose3(a) + return { a[1], a[4], a[7], + a[2], a[5], a[8], + a[3], a[6], a[9] } +end + +local function adj3(a) + return { a[5] * a[9] - a[8] * a[6], a[8] * a[3] - a[2] * a[9], a[2] * a[6] - a[5] * a[3], + a[7] * a[6] - a[4] * a[9], a[1] * a[9] - a[7] * a[3], a[4] * a[3] - a[1] * a[6], + a[4] * a[8] - a[7] * a[5], a[7] * a[2] - a[1] * a[8], a[1] * a[5] - a[4] * a[2] } +end +*/ + +local function det3(a) + return ( a[1] * (a[5] * a[9] - a[8] * a[6]) - + a[2] * (a[4] * a[9] - a[7] * a[6]) + + a[3] * (a[4] * a[8] - a[7] * a[5]) ) +end + +local function inverse3(a) + local det = det3(a) + if det == 0 then return { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 } + end + return { (a[5] * a[9] - a[8] * a[6])/det, (a[8] * a[3] - a[2] * a[9])/det, (a[2] * a[6] - a[5] * a[3])/det, + (a[7] * a[6] - a[4] * a[9])/det, (a[1] * a[9] - a[7] * a[3])/det, (a[4] * a[3] - a[1] * a[6])/det, + (a[4] * a[8] - a[7] * a[5])/det, (a[7] * a[2] - a[1] * a[8])/det, (a[1] * a[5] - a[4] * a[2])/det } +end + + +/******************************************************************************/ + +__e2setcost(1) -- approximated + +e2function matrix matrix() + return { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 } +end + +__e2setcost(5) -- temporary + +e2function matrix matrix(vector rv1, vector rv2, vector rv3) + return { rv1[1], rv2[1], rv3[1], + rv1[2], rv2[2], rv3[2], + rv1[3], rv2[3], rv3[3] } +end + +e2function matrix rowMatrix(vector rv1, vector rv2, vector rv3) + return { rv1[1], rv1[2], rv1[3], + rv2[1], rv2[2], rv2[3], + rv3[1], rv3[2], rv3[3],} +end + +e2function matrix matrix(rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9) + return { rv1, rv2, rv3, + rv4, rv5, rv6, + rv7, rv8, rv9 } +end + +e2function matrix matrix(matrix2 rv1) + return { rv1[1], rv1[2], 0, + rv1[3], rv1[4], 0, + 0, 0, 0 } +end + +e2function matrix identity() + return { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 } +end + +/******************************************************************************/ + +registerOperator("ass", "m", "m", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ +// Comparison + +e2function number operator_is(matrix rv1) + if rv1[1] > delta || -rv1[1] > delta || + rv1[2] > delta || -rv1[2] > delta || + rv1[3] > delta || -rv1[3] > delta || + rv1[4] > delta || -rv1[4] > delta || + rv1[5] > delta || -rv1[5] > delta || + rv1[6] > delta || -rv1[6] > delta || + rv1[7] > delta || -rv1[7] > delta || + rv1[8] > delta || -rv1[8] > delta || + rv1[9] > delta || -rv1[9] > delta + then return 1 else return 0 end +end + +e2function number operator==(matrix rv1, matrix rv2) + if rv1[1] - rv2[1] <= delta && rv2[1] - rv1[1] <= delta && + rv1[2] - rv2[2] <= delta && rv2[2] - rv1[2] <= delta && + rv1[3] - rv2[3] <= delta && rv2[3] - rv1[3] <= delta && + rv1[4] - rv2[4] <= delta && rv2[4] - rv1[4] <= delta && + rv1[5] - rv2[5] <= delta && rv2[5] - rv1[5] <= delta && + rv1[6] - rv2[6] <= delta && rv2[6] - rv1[6] <= delta && + rv1[7] - rv2[7] <= delta && rv2[7] - rv1[7] <= delta && + rv1[8] - rv2[8] <= delta && rv2[8] - rv1[8] <= delta && + rv1[9] - rv2[9] <= delta && rv2[9] - rv1[9] <= delta + then return 1 else return 0 end +end + +e2function number operator!=(matrix rv1, matrix rv2) + if rv1[1] - rv2[1] > delta && rv2[1] - rv1[1] > delta && + rv1[2] - rv2[2] > delta && rv2[2] - rv1[2] > delta && + rv1[3] - rv2[3] > delta && rv2[3] - rv1[3] > delta && + rv1[4] - rv2[4] > delta && rv2[4] - rv1[4] > delta && + rv1[5] - rv2[5] > delta && rv2[5] - rv1[5] > delta && + rv1[6] - rv2[6] > delta && rv2[6] - rv1[6] > delta && + rv1[7] - rv2[7] > delta && rv2[7] - rv1[7] > delta && + rv1[8] - rv2[8] > delta && rv2[8] - rv1[8] > delta && + rv1[9] - rv2[9] > delta && rv2[9] - rv1[9] > delta + then return 1 else return 0 end +end + +/******************************************************************************/ +// Basic operations + +e2function matrix operator_neg(matrix rv1) + return { -rv1[1], -rv1[2], -rv1[3], + -rv1[4], -rv1[5], -rv1[6], + -rv1[7], -rv1[8], -rv1[9] } +end + +e2function matrix operator+(matrix rv1, matrix rv2) + return { rv1[1] + rv2[1], rv1[2] + rv2[2], rv1[3] + rv2[3], + rv1[4] + rv2[4], rv1[5] + rv2[5], rv1[6] + rv2[6], + rv1[7] + rv2[7], rv1[8] + rv2[8], rv1[9] + rv2[9] } +end + +e2function matrix operator-(matrix rv1, matrix rv2) + return { rv1[1] - rv2[1], rv1[2] - rv2[2], rv1[3] - rv2[3], + rv1[4] - rv2[4], rv1[5] - rv2[5], rv1[6] - rv2[6], + rv1[7] - rv2[7], rv1[8] - rv2[8], rv1[9] - rv2[9] } +end + +e2function matrix operator*(rv1, matrix rv2) + return { rv1 * rv2[1], rv1 * rv2[2], rv1 * rv2[3], + rv1 * rv2[4], rv1 * rv2[5], rv1 * rv2[6], + rv1 * rv2[7], rv1 * rv2[8], rv1 * rv2[9] } +end + +e2function matrix operator*(matrix rv1, rv2) + return { rv1[1] * rv2, rv1[2] * rv2, rv1[3] * rv2, + rv1[4] * rv2, rv1[5] * rv2, rv1[6] * rv2, + rv1[7] * rv2, rv1[8] * rv2, rv1[9] * rv2 } +end + +e2function vector operator*(matrix rv1, vector rv2) + return { rv1[1] * rv2[1] + rv1[2] * rv2[2] + rv1[3] * rv2[3], + rv1[4] * rv2[1] + rv1[5] * rv2[2] + rv1[6] * rv2[3], + rv1[7] * rv2[1] + rv1[8] * rv2[2] + rv1[9] * rv2[3] } +end + +e2function matrix operator*(matrix rv1, matrix rv2) + return { rv1[1] * rv2[1] + rv1[2] * rv2[4] + rv1[3] * rv2[7], + rv1[1] * rv2[2] + rv1[2] * rv2[5] + rv1[3] * rv2[8], + rv1[1] * rv2[3] + rv1[2] * rv2[6] + rv1[3] * rv2[9], + rv1[4] * rv2[1] + rv1[5] * rv2[4] + rv1[6] * rv2[7], + rv1[4] * rv2[2] + rv1[5] * rv2[5] + rv1[6] * rv2[8], + rv1[4] * rv2[3] + rv1[5] * rv2[6] + rv1[6] * rv2[9], + rv1[7] * rv2[1] + rv1[8] * rv2[4] + rv1[9] * rv2[7], + rv1[7] * rv2[2] + rv1[8] * rv2[5] + rv1[9] * rv2[8], + rv1[7] * rv2[3] + rv1[8] * rv2[6] + rv1[9] * rv2[9] } +end + +e2function matrix operator/(matrix rv1, rv2) + return { rv1[1] / rv2, rv1[2] / rv2, rv1[3] / rv2, + rv1[4] / rv2, rv1[5] / rv2, rv1[6] / rv2, + rv1[7] / rv2, rv1[8] / rv2, rv1[9] / rv2 } +end + +e2function matrix operator^(matrix rv1, rv2) + + if rv2 == -1 then return ( inverse3(rv1) ) + + elseif rv2 == 0 then return { 1, 0, 0, + 0, 1, 0, + 0, 0, 1 } + + elseif rv2 == 1 then return rv1 + + elseif rv2 == 2 then + return { rv1[1] * rv1[1] + rv1[2] * rv1[4] + rv1[3] * rv1[7], + rv1[1] * rv1[2] + rv1[2] * rv1[5] + rv1[3] * rv1[8], + rv1[1] * rv1[3] + rv1[2] * rv1[6] + rv1[3] * rv1[9], + rv1[4] * rv1[1] + rv1[5] * rv1[4] + rv1[6] * rv1[7], + rv1[4] * rv1[2] + rv1[5] * rv1[5] + rv1[6] * rv1[8], + rv1[4] * rv1[3] + rv1[5] * rv1[6] + rv1[6] * rv1[9], + rv1[7] * rv1[1] + rv1[8] * rv1[4] + rv1[9] * rv1[7], + rv1[7] * rv1[2] + rv1[8] * rv1[5] + rv1[9] * rv1[8], + rv1[7] * rv1[3] + rv1[8] * rv1[6] + rv1[9] * rv1[9] } + + else return { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 } + end +end + +/******************************************************************************/ +// Row/column/element manipulation + +e2function vector matrix:row(rv2) + local k + + if rv2 < 1 then k = 3 + elseif rv2 > 3 then k = 9 + else k = (rv2 - rv2 % 1)*3 end + + local x = this[k - 2] + local y = this[k - 1] + local z = this[k] + return { x, y, z } +end + +e2function vector matrix:column(rv2) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 3 then k = 3 + else k = rv2 - rv2 % 1 end + + local x = this[k] + local y = this[k + 3] + local z = this[k + 6] + return { x, y, z } +end + +e2function matrix matrix:setRow(rv2, rv3, rv4, rv5) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 3 then k = 3 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k * 3 - 2] = rv3 + a[k * 3 - 1] = rv4 + a[k * 3] = rv5 + return a +end + +e2function matrix matrix:setRow(rv2, vector rv3) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 3 then k = 3 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k * 3 - 2] = rv3[1] + a[k * 3 - 1] = rv3[2] + a[k * 3] = rv3[3] + return a +end + +e2function matrix matrix:setColumn(rv2, rv3, rv4, rv5) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 3 then k = 3 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k] = rv3 + a[k + 3] = rv4 + a[k + 6] = rv5 + return a +end + +e2function matrix matrix:setColumn(rv2, vector rv3) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 3 then k = 3 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k] = rv3[1] + a[k + 3] = rv3[2] + a[k + 6] = rv3[3] + return a +end + +e2function matrix matrix:swapRows(rv2, rv3) + local r1, r2 + + if rv2 < 1 then r1 = 1 + elseif rv2 > 3 then r1 = 3 + else r1 = rv2 - rv2 % 1 end + if rv3 < 1 then r2 = 1 + elseif rv3 > 3 then r2 = 3 + else r2 = rv3 - rv3 % 1 end + + if r1 == r2 then return this + elseif (r1 == 1 && r2 == 2) || (r1 == 2 && r2 == 1) then + this = { this[4], this[5], this[6], + this[1], this[2], this[3], + this[7], this[8], this[9] } + elseif (r1 == 2 && r2 == 3) || (r1 == 3 && r2 == 2) then + this = { this[1], this[2], this[3], + this[7], this[8], this[9], + this[4], this[5], this[6] } + elseif (r1 == 1 && r2 == 3) || (r1 == 3 && r2 == 1) then + this = { this[7], this[8], this[9], + this[4], this[5], this[6], + this[1], this[2], this[3] } + end + return this +end + +e2function matrix matrix:swapColumns(rv2, rv3) + local r1, r2 + + if rv2 < 1 then r1 = 1 + elseif rv2 > 3 then r1 = 3 + else r1 = rv2 - rv2 % 1 end + if rv3 < 1 then r2 = 1 + elseif rv3 > 3 then r2 = 3 + else r2 = rv3 - rv3 % 1 end + + if r1 == r2 then return this + elseif (r1 == 1 && r2 == 2) || (r1 == 2 && r2 == 1) then + this = { this[2], this[1], this[3], + this[5], this[4], this[6], + this[8], this[7], this[9] } + elseif (r1 == 2 && r2 == 3) || (r1 == 3 && r2 == 2) then + this = { this[1], this[3], this[2], + this[4], this[6], this[5], + this[7], this[9], this[8] } + elseif (r1 == 1 && r2 == 3) || (r1 == 3 && r2 == 1) then + this = { this[3], this[2], this[1], + this[6], this[5], this[4], + this[9], this[8], this[7] } + end + return this +end + +e2function number matrix:element(rv2, rv3) + local i, j + + if rv2 < 1 then i = 1 + elseif rv2 > 3 then i = 3 + else i = rv2 - rv2 % 1 end + if rv3 < 1 then j = 1 + elseif rv3 > 3 then j = 3 + else j = rv3 - rv3 % 1 end + + local k = i + (j - 1) * 3 + return this[k] +end + +e2function matrix matrix:setElement(rv2, rv3, rv4) + local i, j + + if rv2 < 1 then i = 1 + elseif rv2 > 3 then i = 3 + else i = rv2 - rv2 % 1 end + if rv3 < 1 then j = 1 + elseif rv3 > 3 then j = 3 + else j = rv3 - rv3 % 1 end + + local a = clone(this) + a[i + (j - 1) * 3] = rv4 + return a +end + +e2function matrix matrix:swapElements(rv2, rv3, rv4, rv5) + local i1, j1, i2, j2 + + if rv2 < 1 then i1 = 1 + elseif rv2 > 3 then i1 = 3 + else i1 = rv2 - rv2 % 1 end + + if rv3 < 1 then j1 = 1 + elseif rv3 > 3 then j1 = 3 + else j1 = rv3 - rv3 % 1 end + + if rv4 < 1 then i2 = 1 + elseif rv4 > 3 then i2 = 3 + else i2 = rv4 - rv4 % 1 end + + if rv5 < 1 then j2 = 1 + elseif rv5 > 3 then j2 = 3 + else j2 = rv5 - rv5 % 1 end + + local k1 = i1 + (j1 - 1) * 3 + local k2 = i2 + (j2 - 1) * 3 + local a = clone(this) + a[k1], a[k2] = this[k2], this[k1] + return a +end + +e2function matrix matrix:setDiagonal(vector rv2) + return { rv2[1], this[4], this[7], + this[2], rv2[2], this[8], + this[3], this[6], rv2[3] } +end + +e2function matrix matrix:setDiagonal(rv2, rv3, rv4) + return { rv2, this[4], this[7], + this[2], rv3, this[8], + this[3], this[6], rv4 } +end + +/******************************************************************************/ +// Useful matrix maths functions + +e2function vector diagonal(matrix rv1) + return { rv1[1], rv1[5], rv1[9] } +end + +e2function number trace(matrix rv1) + return ( rv1[1] + rv1[5] + rv1[9] ) +end + +e2function number det(matrix rv1) + return ( det3(rv1) ) +end + +e2function matrix transpose(matrix rv1) + return { rv1[1], rv1[4], rv1[7], + rv1[2], rv1[5], rv1[8], + rv1[3], rv1[6], rv1[9] } +end + +e2function matrix adj(matrix rv1) + return { rv1[5] * rv1[9] - rv1[8] * rv1[6], rv1[8] * rv1[3] - rv1[2] * rv1[9], rv1[2] * rv1[6] - rv1[5] * rv1[3], + rv1[7] * rv1[6] - rv1[4] * rv1[9], rv1[1] * rv1[9] - rv1[7] * rv1[3], rv1[4] * rv1[3] - rv1[1] * rv1[6], + rv1[4] * rv1[8] - rv1[7] * rv1[5], rv1[7] * rv1[2] - rv1[1] * rv1[8], rv1[1] * rv1[5] - rv1[4] * rv1[2] } +end + +/******************************************************************************/ +// Extra functions + +e2function matrix matrix(entity rv1) + if(!IsValid(rv1)) then + return { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 } + end + local factor = 10000 + local pos = rv1:GetPos() + local x = rv1:LocalToWorld(Vector(factor,0,0)) - pos + local y = rv1:LocalToWorld(Vector(0,factor,0)) - pos + local z = rv1:LocalToWorld(Vector(0,0,factor)) - pos + return { x.x/factor, y.x/factor, z.x/factor, + x.y/factor, y.y/factor, z.y/factor, + x.z/factor, y.z/factor, z.z/factor } +end + +e2function vector matrix:x() + return { this[1], this[4], this[7] } +end + +e2function vector matrix:y() + return { this[2], this[5], this[8] } +end + +e2function vector matrix:z() + return { this[3], this[6], this[9] } +end + +// Returns a 3x3 reference frame matrix as described by the angle . Multiplying by this matrix will be the same as rotating by the given angle. +e2function matrix matrix(angle ang) + ang = Angle(ang[1], ang[2], ang[3]) + local x = ang:Forward() + local y = ang:Right() * -1 + local z = ang:Up() + return { + x.x, y.x, z.x, + x.y, y.y, z.y, + x.z, y.z, z.z + } +end + +// Converts a rotation matrix to angle form (assumes matrix is orthogonal) +local rad2deg = 180 / math.pi + +e2function angle matrix:toAngle() + local pitch = math.asin( -this[7] ) * rad2deg + local yaw = math.atan2( this[4], this[1] ) * rad2deg + local roll = math.atan2( this[8], this[9] ) * rad2deg + return { pitch, yaw, roll } +end + +// Create a rotation matrix in the format (v,n) where v is the axis direction vector and n is degrees (right-handed rotation) +e2function matrix mRotation(vector rv1, rv2) + + local vec + local len = (rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3]) ^ 0.5 + if len == 1 then vec = rv1 + elseif len > delta then vec = { rv1[1] / len, rv1[2] / len, rv1[3] / len } + else return { 0, 0, 0, + 0, 0, 0, + 0, 0, 0 } + end + + local vec2 = { vec[1] * vec[1], vec[2] * vec[2], vec[3] * vec[3] } + local a = rv2 * 3.14159265 / 180 + local cos = math.cos(a) + local sin = math.sin(a) + local cosmin = 1 - cos + return { vec2[1] + (1 - vec2[1]) * cos, + vec[1] * vec[2] * cosmin - vec[3] * sin, + vec[1] * vec[3] * cosmin + vec[2] * sin, + vec[1] * vec[2] * cosmin + vec[3] * sin, + vec2[2] + (1 - vec2[2]) * cos, + vec[2] * vec[3] * cosmin - vec[1] * sin, + vec[1] * vec[3] * cosmin - vec[2] * sin, + vec[2] * vec[3] * cosmin + vec[1] * sin, + vec2[3] + (1 - vec2[3]) * cos } +end + +/******************************************************************************\ + 4x4 Matrices +\******************************************************************************/ + +registerType("matrix4", "xm4", { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 }, + function(self, input) + local ret = {} + for k,v in pairs(input) do ret[k] = v end + return ret + end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 16 then error("Return value does not have exactly 16 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 16 + end +) + +/******************************************************************************/ + +__e2setcost(1) -- approximated + +e2function matrix4 matrix4() + return { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } +end + +__e2setcost(5) -- temporary + +e2function matrix4 matrix4(vector4 rv1, vector4 rv2, vector4 rv3, vector4 rv4) + return { rv1[1], rv2[1], rv3[1], rv4[1], + rv1[2], rv2[2], rv3[2], rv4[2], + rv1[3], rv2[3], rv3[3], rv4[3], + rv1[4], rv2[4], rv3[4], rv4[4] } +end + +e2function matrix4 rowMatrix4(vector4 rv1, vector4 rv2, vector4 rv3, vector4 rv4) + return { rv1[1], rv1[2], rv1[3], rv1[4], + rv2[1], rv2[2], rv2[3], rv2[4], + rv3[1], rv3[2], rv3[3], rv3[4], + rv4[1], rv4[2], rv4[3], rv4[4] } +end + +e2function matrix4 matrix4(rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9, rv10, rv11, rv12, rv13, rv14, rv15, rv16) + return { rv1, rv2, rv3, rv4, + rv5, rv6, rv7, rv8, + rv9, rv10, rv11, rv12, + rv13, rv14, rv15, rv16 } +end + +e2function matrix4 matrix4(matrix2 rv1) + return { rv1[1], rv1[2], 0, 0, + rv1[3], rv1[4], 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } +end + +e2function matrix4 matrix4(matrix2 rv1, matrix2 rv2, matrix2 rv3, matrix2 rv4) + return { rv1[1], rv1[2], rv2[1], rv2[2], + rv1[3], rv1[4], rv2[3], rv2[4], + rv3[1], rv3[2], rv4[1], rv4[2], + rv3[3], rv3[4], rv4[3], rv4[4] } +end + +e2function matrix4 matrix4(matrix rv1) + return { rv1[1], rv1[2], rv1[3], 0, + rv1[4], rv1[5], rv1[6], 0, + rv1[7], rv1[8], rv1[9], 0, + 0, 0, 0, 0 } +end + +e2function matrix4 identity4() + return { 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 } +end + +/******************************************************************************/ + +registerOperator("ass", "xm4", "xm4", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ +// Comparison + +e2function number operator_is(matrix4 rv1) + if rv1[1] > delta || -rv1[1] > delta || + rv1[2] > delta || -rv1[2] > delta || + rv1[3] > delta || -rv1[3] > delta || + rv1[4] > delta || -rv1[4] > delta || + rv1[5] > delta || -rv1[5] > delta || + rv1[6] > delta || -rv1[6] > delta || + rv1[7] > delta || -rv1[7] > delta || + rv1[8] > delta || -rv1[8] > delta || + rv1[9] > delta || -rv1[9] > delta || + rv1[10] > delta || -rv1[10] > delta || + rv1[11] > delta || -rv1[11] > delta || + rv1[12] > delta || -rv1[12] > delta || + rv1[13] > delta || -rv1[13] > delta || + rv1[14] > delta || -rv1[14] > delta || + rv1[15] > delta || -rv1[15] > delta || + rv1[16] > delta || -rv1[16] > delta + then return 1 else return 0 end +end + +e2function number operator==(matrix4 rv1, matrix4 rv2) + if rv1[1] - rv2[1] <= delta && rv2[1] - rv1[1] <= delta && + rv1[2] - rv2[2] <= delta && rv2[2] - rv1[2] <= delta && + rv1[3] - rv2[3] <= delta && rv2[3] - rv1[3] <= delta && + rv1[4] - rv2[4] <= delta && rv2[4] - rv1[4] <= delta && + rv1[5] - rv2[5] <= delta && rv2[5] - rv1[5] <= delta && + rv1[6] - rv2[6] <= delta && rv2[6] - rv1[6] <= delta && + rv1[7] - rv2[7] <= delta && rv2[7] - rv1[7] <= delta && + rv1[8] - rv2[8] <= delta && rv2[8] - rv1[8] <= delta && + rv1[9] - rv2[9] <= delta && rv2[9] - rv1[9] <= delta && + rv1[10] - rv2[10] <= delta && rv2[10] - rv1[10] <= delta && + rv1[11] - rv2[11] <= delta && rv2[11] - rv1[11] <= delta && + rv1[12] - rv2[12] <= delta && rv2[12] - rv1[12] <= delta && + rv1[13] - rv2[13] <= delta && rv2[13] - rv1[13] <= delta && + rv1[14] - rv2[14] <= delta && rv2[14] - rv1[14] <= delta && + rv1[15] - rv2[15] <= delta && rv2[15] - rv1[15] <= delta && + rv1[16] - rv2[16] <= delta && rv2[16] - rv1[16] <= delta + then return 1 else return 0 end +end + +e2function number operator!=(matrix4 rv1, matrix4 rv2) + if rv1[1] - rv2[1] > delta && rv2[1] - rv1[1] > delta && + rv1[2] - rv2[2] > delta && rv2[2] - rv1[2] > delta && + rv1[3] - rv2[3] > delta && rv2[3] - rv1[3] > delta && + rv1[4] - rv2[4] > delta && rv2[4] - rv1[4] > delta && + rv1[5] - rv2[5] > delta && rv2[5] - rv1[5] > delta && + rv1[6] - rv2[6] > delta && rv2[6] - rv1[6] > delta && + rv1[7] - rv2[7] > delta && rv2[7] - rv1[7] > delta && + rv1[8] - rv2[8] > delta && rv2[8] - rv1[8] > delta && + rv1[9] - rv2[9] > delta && rv2[9] - rv1[9] > delta && + rv1[10] - rv2[10] > delta && rv2[10] - rv1[10] > delta && + rv1[11] - rv2[11] > delta && rv2[11] - rv1[11] > delta && + rv1[12] - rv2[12] > delta && rv2[12] - rv1[12] > delta && + rv1[13] - rv2[13] > delta && rv2[13] - rv1[13] > delta && + rv1[14] - rv2[14] > delta && rv2[14] - rv1[14] > delta && + rv1[15] - rv2[15] > delta && rv2[15] - rv1[15] > delta && + rv1[16] - rv2[16] > delta && rv2[16] - rv1[16] > delta + then return 1 else return 0 end +end + +/******************************************************************************/ +// Basic operations + +e2function matrix4 operator_neg(matrix4 rv1) + return { -rv1[1], -rv1[2], -rv1[3], -rv1[4], + -rv1[5], -rv1[6], -rv1[7], -rv1[8], + -rv1[9], -rv1[10], -rv1[11], -rv1[12], + -rv1[13], -rv1[14], -rv1[15], -rv1[16] } +end + +e2function matrix4 operator+(matrix4 rv1, matrix4 rv2) + return { rv1[1] + rv2[1], rv1[2] + rv2[2], rv1[3] + rv2[3], rv1[4] + rv2[4], + rv1[5] + rv2[5], rv1[6] + rv2[6], rv1[7] + rv2[7], rv1[8] + rv2[8], + rv1[9] + rv2[9], rv1[10] + rv2[10], rv1[11] + rv2[11], rv1[12] + rv2[12], + rv1[13] + rv2[13], rv1[14] + rv2[14], rv1[15] + rv2[15], rv1[16] + rv2[16] } +end + +e2function matrix4 operator-(matrix4 rv1, matrix4 rv2) + return { rv1[1] - rv2[1], rv1[2] - rv2[2], rv1[3] - rv2[3], rv1[4] - rv2[4], + rv1[5] - rv2[5], rv1[6] - rv2[6], rv1[7] - rv2[7], rv1[8] - rv2[8], + rv1[9] - rv2[9], rv1[10] - rv2[10], rv1[11] - rv2[11], rv1[12] - rv2[12], + rv1[13] - rv2[13], rv1[14] - rv2[14], rv1[15] - rv2[15], rv1[16] - rv2[16] } +end + +e2function matrix4 operator*(rv1, matrix4 rv2) + return { rv1 * rv2[1], rv1 * rv2[2], rv1 * rv2[3], rv1 * rv2[4], + rv1 * rv2[5], rv1 * rv2[6], rv1 * rv2[7], rv1 * rv2[8], + rv1 * rv2[9], rv1 * rv2[10], rv1 * rv2[11], rv1 * rv2[12], + rv1 * rv2[13], rv1 * rv2[14], rv1 * rv2[15], rv1 * rv2[16] } +end + +e2function matrix4 operator*(matrix4 rv1, rv2) + return { rv1[1] * rv2, rv1[2] * rv2, rv1[3] * rv2, rv1[4] * rv2, + rv1[5] * rv2, rv1[6] * rv2, rv1[7] * rv2, rv1[8] * rv2, + rv1[9] * rv2, rv1[10] * rv2, rv1[11] * rv2, rv1[12] * rv2, + rv1[13] * rv2, rv1[14] * rv2, rv1[15] * rv2, rv1[16] * rv2 } +end + +e2function vector4 operator*(matrix4 rv1, vector4 rv2) + return { rv1[1] * rv2[1] + rv1[2] * rv2[2] + rv1[3] * rv2[3] + rv1[4] * rv2[4], + rv1[5] * rv2[1] + rv1[6] * rv2[2] + rv1[7] * rv2[3] + rv1[8] * rv2[4], + rv1[9] * rv2[1] + rv1[10] * rv2[2] + rv1[11] * rv2[3] + rv1[12] * rv2[4], + rv1[13] * rv2[1] + rv1[14] * rv2[2] + rv1[15] * rv2[3] + rv1[16] * rv2[4] } +end + +e2function matrix4 operator*(matrix4 lhs, matrix4 rhs) + return { + lhs[ 1] * rhs[ 1] + lhs[ 2] * rhs[ 5] + lhs[ 3] * rhs[ 9] + lhs[ 4] * rhs[13], + lhs[ 1] * rhs[ 2] + lhs[ 2] * rhs[ 6] + lhs[ 3] * rhs[10] + lhs[ 4] * rhs[14], + lhs[ 1] * rhs[ 3] + lhs[ 2] * rhs[ 7] + lhs[ 3] * rhs[11] + lhs[ 4] * rhs[15], + lhs[ 1] * rhs[ 4] + lhs[ 2] * rhs[ 8] + lhs[ 3] * rhs[12] + lhs[ 4] * rhs[16], + lhs[ 5] * rhs[ 1] + lhs[ 6] * rhs[ 5] + lhs[ 7] * rhs[ 9] + lhs[ 8] * rhs[13], + lhs[ 5] * rhs[ 2] + lhs[ 6] * rhs[ 6] + lhs[ 7] * rhs[10] + lhs[ 8] * rhs[14], + lhs[ 5] * rhs[ 3] + lhs[ 6] * rhs[ 7] + lhs[ 7] * rhs[11] + lhs[ 8] * rhs[15], + lhs[ 5] * rhs[ 4] + lhs[ 6] * rhs[ 8] + lhs[ 7] * rhs[12] + lhs[ 8] * rhs[16], + lhs[ 9] * rhs[ 1] + lhs[10] * rhs[ 5] + lhs[11] * rhs[ 9] + lhs[12] * rhs[13], + lhs[ 9] * rhs[ 2] + lhs[10] * rhs[ 6] + lhs[11] * rhs[10] + lhs[12] * rhs[14], + lhs[ 9] * rhs[ 3] + lhs[10] * rhs[ 7] + lhs[11] * rhs[11] + lhs[12] * rhs[15], + lhs[ 9] * rhs[ 4] + lhs[10] * rhs[ 8] + lhs[11] * rhs[12] + lhs[12] * rhs[16], + lhs[13] * rhs[ 1] + lhs[14] * rhs[ 5] + lhs[15] * rhs[ 9] + lhs[16] * rhs[13], + lhs[13] * rhs[ 2] + lhs[14] * rhs[ 6] + lhs[15] * rhs[10] + lhs[16] * rhs[14], + lhs[13] * rhs[ 3] + lhs[14] * rhs[ 7] + lhs[15] * rhs[11] + lhs[16] * rhs[15], + lhs[13] * rhs[ 4] + lhs[14] * rhs[ 8] + lhs[15] * rhs[12] + lhs[16] * rhs[16] + } +end + +e2function matrix4 operator/(matrix4 rv1, rv2) + return { rv1[1] / rv2, rv1[2] / rv2, rv1[3] / rv2, rv1[4] / rv2, + rv1[5] / rv2, rv1[6] / rv2, rv1[7] / rv2, rv1[8] / rv2, + rv1[9] / rv2, rv1[10] / rv2, rv1[11] / rv2, rv1[12] / rv2, + rv1[13] / rv2, rv1[14] / rv2, rv1[15] / rv2, rv1[16] / rv2 } +end + +e2function matrix4 operator^(matrix4 lhs, rhs) + + //if rhs == -1 then return ( inverse4(lhs) ) + + if rhs == 0 then return { 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 } + + elseif rhs == 1 then return lhs + + elseif rhs == 2 then + return { + lhs[ 1] * lhs[ 1] + lhs[ 2] * lhs[ 5] + lhs[ 3] * lhs[ 9] + lhs[ 4] * lhs[13], + lhs[ 1] * lhs[ 2] + lhs[ 2] * lhs[ 6] + lhs[ 3] * lhs[10] + lhs[ 4] * lhs[14], + lhs[ 1] * lhs[ 3] + lhs[ 2] * lhs[ 7] + lhs[ 3] * lhs[11] + lhs[ 4] * lhs[15], + lhs[ 1] * lhs[ 4] + lhs[ 2] * lhs[ 8] + lhs[ 3] * lhs[12] + lhs[ 4] * lhs[16], + lhs[ 5] * lhs[ 1] + lhs[ 6] * lhs[ 5] + lhs[ 7] * lhs[ 9] + lhs[ 8] * lhs[13], + lhs[ 5] * lhs[ 2] + lhs[ 6] * lhs[ 6] + lhs[ 7] * lhs[10] + lhs[ 8] * lhs[14], + lhs[ 5] * lhs[ 3] + lhs[ 6] * lhs[ 7] + lhs[ 7] * lhs[11] + lhs[ 8] * lhs[15], + lhs[ 5] * lhs[ 4] + lhs[ 6] * lhs[ 8] + lhs[ 7] * lhs[12] + lhs[ 8] * lhs[16], + lhs[ 9] * lhs[ 1] + lhs[10] * lhs[ 5] + lhs[11] * lhs[ 9] + lhs[12] * lhs[13], + lhs[ 9] * lhs[ 2] + lhs[10] * lhs[ 6] + lhs[11] * lhs[10] + lhs[12] * lhs[14], + lhs[ 9] * lhs[ 3] + lhs[10] * lhs[ 7] + lhs[11] * lhs[11] + lhs[12] * lhs[15], + lhs[ 9] * lhs[ 4] + lhs[10] * lhs[ 8] + lhs[11] * lhs[12] + lhs[12] * lhs[16], + lhs[13] * lhs[ 1] + lhs[14] * lhs[ 5] + lhs[15] * lhs[ 9] + lhs[16] * lhs[13], + lhs[13] * lhs[ 2] + lhs[14] * lhs[ 6] + lhs[15] * lhs[10] + lhs[16] * lhs[14], + lhs[13] * lhs[ 3] + lhs[14] * lhs[ 7] + lhs[15] * lhs[11] + lhs[16] * lhs[15], + lhs[13] * lhs[ 4] + lhs[14] * lhs[ 8] + lhs[15] * lhs[12] + lhs[16] * lhs[16] + } + + else return { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } + end +end + +/******************************************************************************/ +// Row/column/element manipulation + +e2function vector4 matrix4:row(rv2) + local k + + if rv2 < 1 then k = 4 + elseif rv2 > 4 then k = 16 + else k = (rv2 - rv2 % 1)*4 end + + local x = this[k - 3] + local y = this[k - 2] + local z = this[k - 1] + local w = this[k] + return { x, y, z, w } +end + +e2function vector4 matrix4:column(rv2) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 4 then k = 4 + else k = rv2 - rv2 % 1 end + + local x = this[k] + local y = this[k + 4] + local z = this[k + 8] + local w = this[k + 12] + return { x, y, z, w } +end + +e2function matrix4 matrix4:setRow(rv2, rv3, rv4, rv5, rv6) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 4 then k = 4 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k * 4 - 3] = rv3 + a[k * 4 - 2] = rv4 + a[k * 4 - 1] = rv5 + a[k * 4] = rv6 + return a +end + +e2function matrix4 matrix4:setRow(rv2, vector4 rv3) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 4 then k = 4 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k * 4 - 3] = rv3[1] + a[k * 4 - 2] = rv3[2] + a[k * 4 - 1] = rv3[3] + a[k * 4] = rv3[4] + return a +end + +e2function matrix4 matrix4:setColumn(rv2, rv3, rv4, rv5, rv6) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 4 then k = 4 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k] = rv3 + a[k + 4] = rv4 + a[k + 8] = rv5 + a[k + 12] = rv6 + return a +end + +e2function matrix4 matrix4:setColumn(rv2, vector4 rv3) + local k + + if rv2 < 1 then k = 1 + elseif rv2 > 4 then k = 4 + else k = rv2 - rv2 % 1 end + + local a = clone(this) + a[k] = rv3[1] + a[k + 4] = rv3[2] + a[k + 8] = rv3[3] + a[k + 12] = rv3[4] + return a +end + +e2function matrix matrix:swapRows(rv2, rv3) + local r1, r2 + + if rv2 < 1 then r1 = 1 + elseif rv2 > 4 then r1 = 4 + else r1 = rv2 - rv2 % 1 end + if rv3 < 1 then r2 = 1 + elseif rv3 > 4 then r2 = 4 + else r2 = rv3 - rv3 % 1 end + + if r1 == r2 then return this + elseif (r1 == 1 && r2 == 2) || (r1 == 2 && r2 == 1) then + this = { this[5], this[6], this[7], this[8], + this[1], this[2], this[3], this[4], + this[9], this[10], this[11], this[12], + this[13], this[14], this[15], this[16] } + elseif (r1 == 2 && r2 == 3) || (r1 == 3 && r2 == 2) then + this = { this[1], this[2], this[3], this[4], + this[9], this[10], this[11], this[12], + this[5], this[6], this[7], this[8], + this[13], this[14], this[15], this[16] } + elseif (r1 == 3 && r2 == 4) || (r1 == 4 && r2 == 3) then + this = { this[1], this[2], this[3], this[4], + this[5], this[6], this[7], this[8], + this[13], this[14], this[15], this[16], + this[9], this[10], this[11], this[12] } + elseif (r1 == 1 && r2 == 3) || (r1 == 3 && r2 == 1) then + this = { this[9], this[10], this[11], this[12], + this[5], this[6], this[7], this[8], + this[1], this[2], this[3], this[4], + this[13], this[14], this[15], this[16] } + elseif (r1 == 2 && r2 == 4) || (r1 == 4 && r2 == 2) then + this = { this[1], this[2], this[3], this[4], + this[13], this[14], this[15], this[16], + this[9], this[10], this[11], this[12], + this[5], this[6], this[7], this[8] } + elseif (r1 == 1 && r2 == 4) || (r1 == 4 && r2 == 1) then + this = { this[13], this[14], this[15], this[16], + this[5], this[6], this[7], this[8], + this[9], this[10], this[11], this[12], + this[1], this[2], this[3], this[4] } + end + return this +end + +e2function matrix4 matrix4:swapColumns(rv2, rv3) + local r1, r2 + + if rv2 < 1 then r1 = 1 + elseif rv2 > 4 then r1 = 4 + else r1 = rv2 - rv2 % 1 end + if rv3 < 1 then r2 = 1 + elseif rv3 > 4 then r2 = 4 + else r2 = rv3 - rv3 % 1 end + + if r1 == r2 then return this + elseif (r1 == 1 && r2 == 2) || (r1 == 2 && r2 == 1) then + this = { this[2], this[1], this[3], this[4], + this[6], this[5], this[7], this[8], + this[10], this[9], this[11], this[12], + this[14], this[13], this[15], this[16] } + elseif (r1 == 2 && r2 == 3) || (r1 == 3 && r2 == 2) then + this = { this[1], this[3], this[2], this[4], + this[5], this[7], this[6], this[8], + this[9], this[11], this[10], this[12], + this[13], this[15], this[14], this[16] } + elseif (r1 == 3 && r2 == 4) || (r1 == 4 && r2 == 3) then + this = { this[1], this[2], this[4], this[3], + this[5], this[6], this[8], this[7], + this[9], this[10], this[12], this[11], + this[13], this[14], this[16], this[15] } + elseif (r1 == 1 && r2 == 3) || (r1 == 3 && r2 == 1) then + this = { this[3], this[2], this[1], this[4], + this[7], this[6], this[5], this[8], + this[11], this[10], this[9], this[12], + this[15], this[14], this[13], this[16] } + elseif (r1 == 2 && r2 == 4) || (r1 == 4 && r2 == 2) then + this = { this[1], this[4], this[3], this[2], + this[5], this[8], this[7], this[6], + this[9], this[12], this[11], this[10], + this[13], this[16], this[15], this[14] } + elseif (r1 == 1 && r2 == 4) || (r1 == 4 && r2 == 1) then + this = { this[4], this[2], this[3], this[1], + this[8], this[6], this[7], this[5], + this[12], this[10], this[11], this[9], + this[16], this[14], this[15], this[13] } + end + return this +end + +e2function number matrix4:element(rv2, rv3) + local i, j + + if rv2 < 1 then i = 1 + elseif rv2 > 4 then i = 4 + else i = rv2 - rv2 % 1 end + if rv3 < 1 then j = 1 + elseif rv3 > 4 then j = 4 + else j = rv3 - rv3 % 1 end + + local k = i + (j - 1) * 4 + return this[k] +end + +e2function matrix4 matrix4:setElement(rv2, rv3, rv4) + local i, j + + if rv2 < 1 then i = 1 + elseif rv2 > 4 then i = 4 + else i = rv2 - rv2 % 1 end + if rv3 < 1 then j = 1 + elseif rv3 > 4 then j = 4 + else j = rv3 - rv3 % 1 end + + local a = clone(this) + a[i + (j - 1) * 4] = rv4 + return a +end + +e2function matrix4 matrix4:swapElements(rv2, rv3, rv4, rv5) + local i1, j1, i2, j2 + + if rv2 < 1 then i1 = 1 + elseif rv2 > 4 then i1 = 4 + else i1 = rv2 - rv2 % 1 end + + if rv3 < 1 then j1 = 1 + elseif rv3 > 4 then j1 = 4 + else j1 = rv3 - rv3 % 1 end + + if rv4 < 1 then i2 = 1 + elseif rv4 > 4 then i2 = 4 + else i2 = rv4 - rv4 % 1 end + + if rv5 < 1 then j2 = 1 + elseif rv5 > 4 then j2 = 4 + else j2 = rv5 - rv5 % 1 end + + local k1 = i1 + (j1 - 1) * 4 + local k2 = i2 + (j2 - 1) * 4 + local a = clone(this) + a[k1], a[k2] = this[k2], this[k1] + return a +end + +e2function matrix4 matrix4:setDiagonal(vector4 rv2) + return { rv2[1], this[2], this[3], this[4], + this[5], rv2[2], this[7], this[8], + this[9], this[10], rv2[3], this[12], + this[13], this[14], this[15], rv2[4] } +end + +e2function matrix4 matrix4:setDiagonal(rv2, rv3, rv4, rv5) + return { rv2, this[2], this[3], this[4], + this[5], rv3, this[7], this[8], + this[9], this[10], rv4, this[12], + this[13], this[14], this[15], rv5 } +end + +/******************************************************************************/ +// Useful matrix maths functions + +e2function vector4 diagonal(matrix4 rv1) + return { rv1[1], rv1[6], rv1[11], rv1[16] } +end + +e2function number trace(matrix4 rv1) + return ( rv1[1] + rv1[6] + rv1[11] + rv1[16] ) +end + +e2function matrix4 transpose(matrix4 rv1) + return { rv1[1], rv1[5], rv1[9], rv1[13], + rv1[2], rv1[6], rv1[10], rv1[14], + rv1[3], rv1[7], rv1[11], rv1[15], + rv1[4], rv1[8], rv1[12], rv1[16] } +end + +// find the inverse for a standard affine transformation matix +e2function matrix4 inverseA(matrix4 rv1) + local t1 = rv1[1] * rv1[4] + rv1[5] * rv1[8] + rv1[9] * rv1[12] + local t2 = rv1[2] * rv1[4] + rv1[6] * rv1[8] + rv1[10] * rv1[12] + local t3 = rv1[3] * rv1[4] + rv1[7] * rv1[8] + rv1[11] * rv1[12] + return { rv1[1], rv1[5], rv1[9], -t1, + rv1[2], rv1[6], rv1[10], -t2, + rv1[3], rv1[7], rv1[11], -t3, + 0, 0, 0, 1 } +end + +/******************************************************************************/ +// Extra functions + +e2function matrix4 matrix4(entity rv1) + if(!IsValid(rv1)) then + return { 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 } + end + local factor = 10000 + local pos = rv1:GetPos() + local x = rv1:LocalToWorld(Vector(factor,0,0)) - pos + local y = rv1:LocalToWorld(Vector(0,factor,0)) - pos + local z = rv1:LocalToWorld(Vector(0,0,factor)) - pos + return { x.x/factor, y.x/factor, z.x/factor, pos.x, + x.y/factor, y.y/factor, z.y/factor, pos.y, + x.z/factor, y.z/factor, z.z/factor, pos.z, + 0, 0, 0, 1 } +end + +e2function vector matrix4:x() + return { this[1], this[5], this[9] } +end + +e2function vector matrix4:y() + return { this[2], this[6], this[10] } +end + +e2function vector matrix4:z() + return { this[3], this[7], this[11] } +end + +e2function vector matrix4:pos() + return { this[4], this[8], this[12] } +end + +--- Returns a 4x4 reference frame matrix as described by the angle . Multiplying by this matrix will be the same as rotating by the given angle. +e2function matrix4 matrix4(angle ang) + ang = Angle(ang[1], ang[2], ang[3]) + local x = ang:Forward() + local y = ang:Right() * -1 + local z = ang:Up() + return { + x.x, y.x, z.x, 0, + x.y, y.y, z.y, 0, + x.z, y.z, z.z, 0, + 0, 0, 0, 1 + } +end + +--- Returns a 4x4 reference frame matrix as described by the angle and the position . Multiplying by this matrix will be the same as rotating by the given angle and offsetting by the given vector. +e2function matrix4 matrix4(angle ang, vector pos) + ang = Angle(ang[1], ang[2], ang[3]) + local x = ang:Forward() + local y = ang:Right() * -1 + local z = ang:Up() + return { + x.x, y.x, z.x, pos[1], + x.y, y.y, z.y, pos[2], + x.z, y.z, z.z, pos[3], + 0, 0, 0, 1 + } +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/npc.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/npc.lua new file mode 100644 index 0000000..33dabf3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/npc.lua @@ -0,0 +1,154 @@ +/******************************************************************************\ + NPC control and such +\******************************************************************************/ + +E2Lib.RegisterExtension("npc", true, "Allows controlling of NPCs.", "NPCs can be given weapons and ordered to hate other players.") + +__e2setcost(5) -- temporary + +local function validNPC(entity) + return IsValid(entity) && entity:IsNPC() +end + +e2function void entity:npcGoWalk(vector rv2) + if !validNPC(this) || !isOwner(self,this) then return end + this:SetLastPosition( Vector(rv2[1], rv2[2], rv2[3]) ) + this:SetSchedule( SCHED_FORCED_GO ) +end + +e2function void entity:npcGoRun(vector rv2) + if !validNPC(this) || !isOwner(self,this) then return end + this:SetLastPosition( Vector(rv2[1], rv2[2], rv2[3]) ) + this:SetSchedule( SCHED_FORCED_GO_RUN ) +end + +e2function void entity:npcAttack() + if !validNPC(this) || !isOwner(self,this) then return end + this:SetSchedule( SCHED_MELEE_ATTACK1 ) +end + +e2function void entity:npcShoot() + if !validNPC(this) || !isOwner(self,this) then return end +-- if !this:HasCondition( 6 ) then return end -- COND_NO_WEAPON. See http://maurits.tv/data/garrysmod/wiki/wiki.garrysmod.com/index4389.html + this:SetSchedule( SCHED_RANGE_ATTACK1 ) +end + +e2function void entity:npcFace(vector rv2) + if !validNPC(this) || !isOwner(self,this) then return end + local Vec = Vector(rv2[1], rv2[2], rv2[3]) - self.entity:GetPos() + local ang = Vec:Angle() + this:SetAngles( Angle(0,ang.y,0) ) +end + +e2function void entity:npcGiveWeapon() + if !validNPC(this) || !isOwner(self,this) then return end + + local weapon = this:GetActiveWeapon() + if (weapon:IsValid()) then + if (weapon:GetClass() == "weapon_smg1") then return end + weapon:Remove() + end + + this:Give( "ai_weapon_smg1" ) +end + +e2function void entity:npcGiveWeapon(string rv2) + if !validNPC(this) || !isOwner(self,this) then return end + + local weapon = this:GetActiveWeapon() + if (weapon:IsValid()) then + if (weapon:GetClass() == "weapon_" .. rv2) then return end + weapon:Remove() + end + + this:Give( "ai_weapon_" .. rv2 ) +end + +e2function void entity:npcStop() + if !validNPC(this) || !isOwner(self,this) then return end + this:SetSchedule( SCHED_NONE ) +end + +e2function entity entity:npcGetTarget() + if !validNPC(this) or !isOwner(self, this) then return end + return this:GetEnemy() +end + +e2function void entity:npcSetTarget(entity ent) + if !(IsValid(ent) and (ent:IsNPC() or ent:IsPlayer())) or !validNPC(this) or !isOwner(self, this) then return end + this:SetEnemy(ent) +end + +//--Relationship functions--// + +// Disposition: 0 - Error, 1 - hate, 2 - fear, 3 - like, 4 - neutral + +local function NpcDisp(string) + if(string == "hate") then return 1 end + if(string == "fear") then return 2 end + if(string == "like") then return 3 end + if(string == "neutral") then return 4 end + return 0 +end + +local function DispToString(number) + if(number == 1) then return "hate" end + if(number == 2) then return "fear" end + if(number == 3) then return "like" end + if(number == 4) then return "neutral" end + return 0 +end + +local function NpcDispString(string) + if(string == "hate") then return "D_HT" end + if(string == "fear") then return "D_FR" end + if(string == "like") then return "D_LI" end + if(string == "neutral") then return "D_NU" end + return "D_ER" +end + +e2function void entity:npcRelationship(entity rv2, string rv3, rv4) + if !validNPC(this) || !IsValid(rv2) || !isOwner(self,this) then return end + local entity = this + local target = rv2 + local disp = NpcDisp(rv3) + local prior = rv4 + if disp == 0 then return end + entity:AddEntityRelationship( target, disp, prior ) +end + +e2function void entity:npcRelationship(string rv2, string rv3, rv4) + if !validNPC(this) || !isOwner(self,this) then return end + local entity = this + local target = rv2 + local disp = NpcDispString(rv3) + local prior = math.floor( rv4 / 10 ) + local input = target.." "..disp.." "..tostring(prior) + if disp == "D_ER" then return end + entity:AddRelationship( input ) +end + +e2function number entity:npcRelationshipByOwner(entity rv2, string rv3, rv4) + if !validNPC(this) || !IsValid(rv2) || !isOwner(self,this) then return 0 end + local entity = this + local owner = rv2 + local disp = NpcDisp(rv3) + local prior = rv4 + if disp == 0 then return 0 end + local Table = ents.FindByClass("npc_*") + + for i=1,#Table do + if(isOwner(self, Table[i])) then entity:AddEntityRelationship( Table[i], disp, prior ) end + end + + return #Table +end + +e2function string entity:npcDisp(entity rv2) + if !validNPC(this) || !IsValid(rv2) || !isOwner(self,this) then return "" end + local entity = this + local target = rv2 + local disp = entity:Disposition( target ) + if disp == 0 then return "" end + return DispToString(disp) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/number.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/number.lua new file mode 100644 index 0000000..6e73ab5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/number.lua @@ -0,0 +1,680 @@ +-- these upvalues (locals in an enclosing scope) are faster to access than globals. +local delta = wire_expression2_delta + +local math = math +local random = math.random +local pi = math.pi +local inf = math.huge + +local exp = math.exp +local frexp = math.frexp +local log = math.log +local log10 = math.log10 +local sqrt = math.sqrt + +local floor = math.floor +local ceil = math.ceil +local Round = math.Round + +local sin = math.sin +local cos = math.cos +local tan = math.tan + +local acos = math.acos +local asin = math.asin +local atan = math.atan +local atan2 = math.atan2 + +local sinh = math.sinh +local cosh = math.cosh +local tanh = math.tanh + + +--[[************************************************************************]]-- +-- Numeric support +--[[************************************************************************]]-- + +registerType("normal", "n", 0, + nil, + nil, + function(retval) + if !isnumber(retval) then error("Return value is not a number, but a "..type(retval).."!",0) end + end, + function(v) + return !isnumber(v) + end +) + +E2Lib.registerConstant("PI", pi) +E2Lib.registerConstant("E", exp(1)) +E2Lib.registerConstant("PHI", (1+sqrt(5))/2) + +--[[************************************************************************]]-- + +__e2setcost(2) + +registerOperator("ass", "n", "n", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +__e2setcost(1.5) + +registerOperator("inc", "n", "", function(self, args) + local op1, scope = args[2], args[3] + self.Scopes[scope][op1] = self.Scopes[scope][op1] + 1 + self.Scopes[scope].vclk[op1] = true +end) + +registerOperator("dec", "n", "", function(self, args) + local op1, scope = args[2], args[3] + self.Scopes[scope][op1] = self.Scopes[scope][op1] - 1 + self.Scopes[scope].vclk[op1] = true +end) + +--[[************************************************************************]]-- + +__e2setcost(1.5) + +registerOperator("eq", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rvd = op1[1](self, op1) - op2[1](self, op2) + if rvd <= delta && -rvd <= delta + then return 1 else return 0 end +end) + +registerOperator("neq", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rvd = op1[1](self, op1) - op2[1](self, op2) + if rvd > delta || -rvd > delta + then return 1 else return 0 end +end) + +__e2setcost(1.25) + +registerOperator("geq", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rvd = op1[1](self, op1) - op2[1](self, op2) + if -rvd <= delta + then return 1 else return 0 end +end) + +registerOperator("leq", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + + local rvd = op1[1](self, op1) - op2[1](self, op2) + if rvd <= delta + then return 1 else return 0 end +end) + +registerOperator("gth", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rvd = op1[1](self, op1) - op2[1](self, op2) + if rvd > delta + then return 1 else return 0 end +end) + +registerOperator("lth", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rvd = op1[1](self, op1) - op2[1](self, op2) + if -rvd > delta + then return 1 else return 0 end +end) + +--[[************************************************************************]]-- + +__e2setcost(0.5) -- approximation + +registerOperator("neg", "n", "n", function(self, args) + local op1 = args[2] + return -op1[1](self, op1) +end) + +__e2setcost(1) + +registerOperator("add", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + return op1[1](self, op1) + op2[1](self, op2) +end) + +registerOperator("sub", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + return op1[1](self, op1) - op2[1](self, op2) +end) + +registerOperator("mul", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + return op1[1](self, op1) * op2[1](self, op2) +end) + +registerOperator("div", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + return op1[1](self, op1) / op2[1](self, op2) +end) + +registerOperator("exp", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + return op1[1](self, op1) ^ op2[1](self, op2) +end) + +registerOperator("mod", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + return op1[1](self, op1) % op2[1](self, op2) +end) + +--[[************************************************************************]]-- +-- TODO: select, average +-- TODO: is the shifting correct for rounding arbitrary decimals? + +__e2setcost(1) + +registerFunction("min", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1 < rv2 then return rv1 else return rv2 end +end) + +registerFunction("min", "nnn", "n", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + local val + if rv1 < rv2 then val = rv1 else val = rv2 end + if rv3 < val then return rv3 else return val end +end) + +registerFunction("min", "nnnn", "n", function(self, args) + local op1, op2, op3, op4 = args[2], args[3], args[4], args[5] + local rv1, rv2, rv3, rv4 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3), op4[1](self, op4) + local val + if rv1 < rv2 then val = rv1 else val = rv2 end + if rv3 < val then val = rv3 end + if rv4 < val then return rv4 else return val end +end) + +registerFunction("max", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1 > rv2 then return rv1 else return rv2 end +end) + +registerFunction("max", "nnn", "n", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + local val + if rv1 > rv2 then val = rv1 else val = rv2 end + if rv3 > val then return rv3 else return val end +end) + +registerFunction("max", "nnnn", "n", function(self, args) + local op1, op2, op3, op4 = args[2], args[3], args[4], args[5] + local rv1, rv2, rv3, rv4 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3), op4[1](self, op4) + local val + if rv1 > rv2 then val = rv1 else val = rv2 end + if rv3 > val then val = rv3 end + if rv4 > val then return rv4 else return val end +end) + +--[[************************************************************************]]-- + +__e2setcost(2) -- approximation + +--- Returns true (1) if given value is a finite number; otherwise false (0). +e2function number isfinite(value) + return (value > -inf and value < inf) and 1 or 0 +end + +--- Returns 1 if given value is a positive infinity or -1 if given value is a negative infinity; otherwise 0. +e2function number isinf(value) + if value == inf then return 1 end + if value == -inf then return -1 end + return 0 +end + +--- Returns true (1) if given value is not a number (NaN); otherwise false (0). +e2function number isnan(value) + return (value ~= value) and 1 or 0 +end + +--[[************************************************************************]]-- + +__e2setcost(2) -- approximation + +e2function number abs(value) + if value >= 0 then return value else return -value end +end + +--- rounds towards +inf +e2function number ceil(rv1) + return ceil(rv1) +end + +e2function number ceil(value, decimals) + local shf = 10 ^ floor(decimals + 0.5) + return ceil(value * shf) / shf +end + +--- rounds towards -inf +e2function number floor(rv1) + return floor(rv1) +end + +e2function number floor(value, decimals) + local shf = 10 ^ floor(decimals + 0.5) + return floor(value * shf) / shf +end + +--- rounds to the nearest integer +e2function number round(rv1) + return floor(rv1 + 0.5) +end + +e2function number round(value, decimals) + local shf = 10 ^ floor(decimals + 0.5) + return floor(value * shf + 0.5) / shf +end + +--- rounds towards zero +e2function number int(rv1) + if rv1 >= 0 then return floor(rv1) else return ceil(rv1) end +end + +--- returns the fractional part. (frac(-1.5) == 0.5 & frac(3.2) == 0.2) +e2function number frac(rv1) + if rv1 >= 0 then return rv1 % 1 else return rv1 % -1 end +end + +-- TODO: what happens with negative modulo? +registerFunction("mod", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1 >= 0 then return rv1 % rv2 else return rv1 % -rv2 end +end) + +-- TODO: change to a more suitable name? (cyclic modulo?) +-- add helpers for wrap90 wrap180, wrap90r wrap180r? or pointless? +-- wrap90(Pitch), wrap(Pitch, 90) +-- should be added... + +registerFunction("wrap", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return (rv1 + rv2) % (rv2 * 2) - rv2 +end) + +registerFunction("clamp", "nnn", "n", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + if rv1 < rv2 then return rv2 elseif rv1 > rv3 then return rv3 else return rv1 end +end) + +--- Returns 1 if is in the interval [; ], 0 otherwise. +e2function number inrange(value, min, max) + if value < min then return 0 end + if value > max then return 0 end + return 1 +end + +registerFunction("sign", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 > delta then return 1 + elseif rv1 < -delta then return -1 + else return 0 end +end) + +--[[************************************************************************]]-- + +__e2setcost(2) -- approximation + +registerFunction("random", "", "n", function(self, args) + return random() +end) + +registerFunction("random", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return random() * rv1 +end) + +registerFunction("random", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1 + random() * (rv2 - rv1) +end) + +registerFunction("randint", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return random(rv1) +end) + +registerFunction("randint", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local temp = rv1 + if (rv1 > rv2) then rv1 = rv2 rv2 = temp end + return random(rv1, rv2) +end) + +--[[************************************************************************]]-- + +__e2setcost(2) -- approximation + +registerFunction("sqrt", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1 ^ (1 / 2) +end) + +registerFunction("cbrt", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1 ^ (1 / 3) +end) + +registerFunction("root", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1 ^ (1 / rv2) +end) + +local const_e = exp(1) +registerFunction("e", "", "n", function(self, args) + return const_e +end) + +registerFunction("exp", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return exp(rv1) +end) + +e2function vector2 frexp(x) + local mantissa, exponent = frexp(x) + return { mantissa, exponent } +end + +registerFunction("ln", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return log(rv1) +end) + +local const_log2 = log(2) +registerFunction("log2", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return log(rv1) / const_log2 +end) + +registerFunction("log10", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return log10(rv1) +end) + +registerFunction("log", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return log(rv1) / log(rv2) +end) + +--[[************************************************************************]]-- + +__e2setcost(2) -- approximation + +local deg2rad = pi / 180 +local rad2deg = 180 / pi + +registerFunction("inf", "", "n", function(self, args) + return inf +end) + +registerFunction("pi", "", "n", function(self, args) + return pi +end) + +registerFunction("toRad", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1 * deg2rad +end) + +registerFunction("toDeg", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1 * rad2deg +end) + +registerFunction("acos", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return acos(rv1) * rad2deg +end) + +registerFunction("asin", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return asin(rv1) * rad2deg +end) + +registerFunction("atan", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return atan(rv1) * rad2deg +end) + +registerFunction("atan", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return atan2(rv1, rv2) * rad2deg +end) + +registerFunction("cos", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return cos(rv1 * deg2rad) +end) + +registerFunction("sec", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/cos(rv1 * deg2rad) +end) + +registerFunction("sin", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return sin(rv1 * deg2rad) +end) + +registerFunction("csc", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/sin(rv1 * deg2rad) +end) + +registerFunction("tan", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return tan(rv1 * deg2rad) +end) + +registerFunction("cot", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/tan(rv1 * deg2rad) +end) + +registerFunction("cosh", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return cosh(rv1) +end) + +registerFunction("sech", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/cosh(rv1) +end) + +registerFunction("sinh", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return sinh(rv1) +end) + +registerFunction("csch", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/sinh(rv1) +end) + +registerFunction("tanh", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return tanh(rv1) +end) + +registerFunction("coth", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/tanh(rv1) +end) + +registerFunction("acosr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return acos(rv1) +end) + +registerFunction("asinr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return asin(rv1) +end) + +registerFunction("atanr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return atan(rv1) +end) + +registerFunction("atanr", "nn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return atan2(rv1, rv2) +end) + +registerFunction("cosr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return cos(rv1) +end) + +registerFunction("secr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/cos(rv1) +end) + +registerFunction("sinr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return sin(rv1) +end) + +registerFunction("cscr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/sin(rv1) +end) + +registerFunction("tanr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return tan(rv1) +end) + +registerFunction("cotr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/tan(rv1) +end) + +registerFunction("coshr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return cosh(rv1) +end) + +registerFunction("sechr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/cosh(rv1) +end) + +registerFunction("sinhr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return sinh(rv1) +end) + +registerFunction("cschr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/sinh(rv1) +end) + +registerFunction("tanhr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return tanh(rv1) +end) + +registerFunction("cothr", "n", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return 1/tanh(rv1) +end) + +--[[************************************************************************]]-- + +__e2setcost(15) -- approximation + +e2function string toString(number number) + return tostring(number) +end + +e2function string number:toString() + return tostring(this) +end + +__e2setcost(25) -- approximation + +local function tobase(number, base, self) + local ret = "" + if base < 2 or base > 36 or number == 0 then return "0" end + if base == 10 then return tostring(number) end + local chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + local loops = 0 + while number > 0 do + loops = loops + 1 + number, d = math.floor(number/base),(number%base)+1 + ret = string.sub(chars,d,d)..ret + if (loops > 32000) then break end + end + self.prf = self.prf + loops + return ret +end + +e2function string toString(number number, number base) + return tobase(number, base, self) +end + +e2function string number:toString(number base) + return tobase(this, base, self) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/player.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/player.lua new file mode 100644 index 0000000..b50f26b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/player.lua @@ -0,0 +1,833 @@ +--[[---------------------------------------------------------------------------- + Player-Entity support +------------------------------------------------------------------------------]] + +local IsValid = IsValid +local isOwner = E2Lib.isOwner + + +local spawnAlert = {} +local runBySpawn = 0 +local lastJoined = NULL + +local leaveAlert = {} +local runByLeave = 0 +local lastLeft = NULL + +registerCallback("e2lib_replace_function", function(funcname, func, oldfunc) + if funcname == "isOwner" then + isOwner = func + elseif funcname == "IsValid" then + IsValid = func + end +end) + +-------------------------------------------------------------------------------- + +__e2setcost(5) -- temporary + +e2function number entity:isAdmin() + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + if this:IsAdmin() then return 1 else return 0 end +end + +e2function number entity:isSuperAdmin() + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + if this:IsSuperAdmin() then return 1 else return 0 end +end + +-------------------------------------------------------------------------------- + +__e2setcost(8) + +e2function vector entity:shootPos() + if(not IsValid(this)) then return {0,0,0} end + if(this:IsPlayer() or this:IsNPC()) then + return this:GetShootPos() + else return {0,0,0} end +end + +e2function vector entity:eye() + if (not IsValid(this)) then return {0,0,0} end + if (this:IsPlayer()) then + return this:GetAimVector() + else + return this:GetForward() + end +end + +--- Returns a local angle describing player 's view angles. +e2function angle entity:eyeAngles() + if not IsValid(this) then return { 0, 0, 0} end + local ang = this:EyeAngles() + return { ang.p, ang.y, ang.r } +end + +-------------------------------------------------------------------------------- + +__e2setcost(5) + +e2function string entity:steamID() + if(not IsValid(this)) then return "" end + if(not this:IsPlayer()) then return "" end + return this:SteamID() +end + +e2function string entity:steamID64() + return IsValid(this) and this:IsPlayer() and this:SteamID64() or "" +end + +e2function number entity:armor() + if(not IsValid(this)) then return 0 end + if(this:IsPlayer() or this:IsNPC()) then return this:Armor() else return 0 end +end + +-------------------------------------------------------------------------------- + +__e2setcost(5) + +e2function number entity:isCrouch() + if(not IsValid(this)) then return 0 end + if(this:IsPlayer() and this:Crouching()) then return 1 else return 0 end +end + +e2function number entity:isAlive() + if(not IsValid(this)) then return 0 end + if(this:IsPlayer() and this:Alive()) then return 1 end + if(this:IsNPC() and this:Health() > 0) then return 1 end + return 0 +end + +-- returns 1 if players has flashlight on or 0 if not +e2function number entity:isFlashlightOn() + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + if this:FlashlightIsOn() then return 1 else return 0 end +end + +-------------------------------------------------------------------------------- + +e2function number entity:frags() + if(not IsValid(this)) then return 0 end + if(this:IsPlayer()) then return this:Frags() else return 0 end +end + +e2function number entity:deaths() + if(not this or not this:IsValid()) then return 0 end + if(this:IsPlayer()) then return this:Deaths() else return 0 end +end + +-------------------------------------------------------------------------------- + +e2function number entity:team() + if(not IsValid(this)) then return 0 end + if(this:IsPlayer()) then return this:Team() else return 0 end +end + +e2function string teamName(rv1) + local str = team.GetName(rv1) + if str == nil then return "" end + return str +end + +e2function number teamScore(rv1) + return team.GetScore(rv1) +end + +e2function number teamPlayers(rv1) + return team.NumPlayers(rv1) +end + +e2function number teamDeaths(rv1) + return team.TotalDeaths(rv1) +end + +e2function number teamFrags(rv1) + return team.TotalFrags(rv1) +end + +e2function vector teamColor(index) + local col = team.GetColor(index) + return { col.r, col.g, col.b } +end + +__e2setcost(10) + +e2function array teams() + local team_indexes = {} + for index,_ in pairs(team.GetAllTeams()) do + team_indexes[#team_indexes+1] = index + end + table.sort(team_indexes) + return team_indexes +end + +-------------------------------------------------------------------------------- +__e2setcost(2) + +e2function number entity:keyForward() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_FORWARD)) and 1 or 0 +end + +e2function number entity:keyLeft() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_MOVELEFT)) and 1 or 0 +end + +e2function number entity:keyBack() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_BACK)) and 1 or 0 +end + +e2function number entity:keyRight() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_MOVERIGHT)) and 1 or 0 +end + +e2function number entity:keyJump() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_JUMP)) and 1 or 0 +end + +e2function number entity:keyAttack1() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_ATTACK)) and 1 or 0 +end + +e2function number entity:keyAttack2() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_ATTACK2)) and 1 or 0 +end + +e2function number entity:keyUse() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_USE)) and 1 or 0 +end + +e2function number entity:keyReload() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_RELOAD)) and 1 or 0 +end + +e2function number entity:keyZoom() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_ZOOM)) and 1 or 0 +end + +e2function number entity:keyWalk() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_WALK)) and 1 or 0 +end + +e2function number entity:keySprint() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_SPEED)) and 1 or 0 +end + +e2function number entity:keyDuck() + if not IsValid(this) or not this:IsPlayer() then return 0 end + return this:KeyDown(IN_DUCK) and 1 or this:GetInfoNum("gmod_vehicle_viewmode", 0) +end + +e2function number entity:keyLeftTurn() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_LEFT)) and 1 or 0 +end + +e2function number entity:keyRightTurn() + return (IsValid(this) and this:IsPlayer() and this:KeyDown(IN_RIGHT)) and 1 or 0 +end + +e2function number entity:keyPressed(string char) + if not IsValid(this) or not this:IsPlayer() then return 0 end + if this.keystate then + local key = _G["KEY_" .. string.upper(char)] or "no_key" + if this.keystate[key] then return 1 end + + key = _G[string.match(string.upper(char),"^(MOUSE_.+)$") or ""] or "no_key" + if this.keystate[key] then return 1 end + end + + return 0 +end + + +local KeyAlert = {} + +local keys_lookup = {} +local number_of_keys = 0 +local sub = string.sub +local lower = string.lower +for k,v in pairs( _G ) do + if sub(k,1,4) == "KEY_" then + keys_lookup[v] = lower(sub(k,5)) + number_of_keys = number_of_keys + 1 + end + + if sub(k,1,3) == "IN_" then + number_of_keys = number_of_keys + 1 + end +end + +-- Manually input the mouse buttons because they're a bit weird +keys_lookup[107] = "mouse_left" +keys_lookup[108] = "mouse_right" +keys_lookup[109] = "mouse_middle" +keys_lookup[110] = "mouse_4" +keys_lookup[111] = "mouse_5" +keys_lookup[112] = "mouse_wheel_up" +keys_lookup[113] = "mouse_wheel_down" +number_of_keys = number_of_keys + 7 + +-- add three more for flashlight "impulse 100" and next/prev weapon binds +number_of_keys = number_of_keys + 3 + +registerCallback("destruct",function(self) + KeyAlert[self.entity] = nil + --Used futher below. Didn't want to create more then one of these per file + spawnAlert[self.entity] = nil + leaveAlert[self.entity] = nil +end) + +local function UpdateKeys(ply, bind, key, state) + local uid = ply:UniqueID() + + local keystate = { + runByKey = ply, + KeyWasReleased = not state, + pressedKey = keys_lookup[key] or "", + pressedBind = bind or "" + } + + for chip, plys in pairs(KeyAlert) do + if IsValid(chip) then + local filter = plys[uid] + if (isbool(filter) and filter == true) or + (istable(filter) and (filter[keystate.pressedKey] == true or filter[keystate.pressedBind] == true)) then + + chip.context.data.runOnKeys = keystate + chip:Execute() + chip.context.data.runOnKeys = nil + end + else + KeyAlert[chip] = nil + end + end +end + +local bindsPressed = {} +local function triggerKey(ply,bind,key,state) + -- delay these 1 tick with timers, otherwise ply:keyPressed(str) doesn't work properly, in case old E2s uses that function + -- It is recommended to use keyClkPressed() instead to get which key was pressed. + timer.Simple(0,function() + if not IsValid(ply) then return end -- if the player disconnected during this time, abort + UpdateKeys(ply,bind,key,state) + end) +end + +hook.Add("PlayerBindDown", "Exp2KeyReceivingDown", function(player, binding, button) + triggerKey(player,binding,button,true) +end) + +hook.Add("PlayerBindUp", "Exp2KeyReceivingUp", function(player, binding, button) + triggerKey(player,binding,button,false) +end) + +local function toggleRunOnKeys(self,ply,on,filter) + if not IsValid(ply) or not ply:IsPlayer() then return end + + local ent = self.entity + local uid = ply:UniqueID() + + if on ~= 0 then + if not KeyAlert[ent] then KeyAlert[ent] = {} end + + if filter == nil or (istable(filter) and next(filter) == nil) then + -- if no filter was specified (or an empty array) then allow all keys + filter = true + elseif istable(filter) then + -- invert the filter + local inverted = {} + for i=1,math.min(number_of_keys,#filter) do + inverted[filter[i]] = true + end + filter = inverted + end + + KeyAlert[ent][uid] = filter + elseif KeyAlert[ent] then + KeyAlert[ent][uid] = nil + if next(KeyAlert[ent]) == nil then + KeyAlert[ent] = nil + end + end +end + +__e2setcost(20) + +--- Makes the chip run on key events from the specified player (can be used on multiple players) +e2function void runOnKeys(entity ply, on) + toggleRunOnKeys(self, ply, on) +end + +e2function void runOnKeys(entity ply, on, array filter) + toggleRunOnKeys(self, ply, on, filter) +end + +e2function void runOnKeys(array plys, on) + for i=1,#plys do + toggleRunOnKeys(self, plys[i], on) + end +end + +e2function void runOnKeys(array plys, on, array filter) + for i=1,#plys do + toggleRunOnKeys(self, plys[i], on, filter) + end +end + +__e2setcost(1) + +--- Returns user if the chip is being executed because of a key event. +e2function entity keyClk() + if not self.data.runOnKeys then return nil end + return self.data.runOnKeys.runByKey +end + +--- Returns 1 or -1 if the chip is being executed because of a key event by player +--- depending of whether the key was just pressed or released +e2function number keyClk(entity ply) + if not self.data.runOnKeys then return 0 end + if not IsValid(ply) then return 0 end + local runby = self.data.runOnKeys.runByKey + if not ply == runby then return 0 end + return self.data.runOnKeys.KeyWasReleased and -1 or 1 +end + +-- Returns the key which caused the keyClk event to trigger +e2function string keyClkPressed() + if not self.data.runOnKeys then return "" end + return self.data.runOnKeys.pressedKey +end + +-- Returns the bind which caused the keyClk event to trigger (if any) +e2function string keyClkPressedBind() + if not self.data.runOnKeys then return "" end + return self.data.runOnKeys.pressedBind +end + +-- Use Support -- + +__e2setcost(50) +--- Makes the chip "Use"able +e2function void runOnUse(value) + if value != 0 then + self.entity:SetUseType( SIMPLE_USE ) + self.entity.Use = function(selfEnt,activator) + self.data.runByUse = activator + selfEnt:Execute() + self.data.runByUse = NULL + end + else + self.entity.Use = nil + end +end + +__e2setcost(1) +--- Returns the entity who is using the chip +e2function entity useClk() + return self.data.runByUse or NULL +end + + +-- isTyping +local plys = {} +concommand.Add("E2_StartChat",function(ply,cmd,args) plys[ply] = true end) +concommand.Add("E2_FinishChat",function(ply,cmd,args) plys[ply] = nil end) +hook.Add("PlayerDisconnected","E2_istyping",function(ply) plys[ply] = nil end) + +e2function number entity:isTyping() + return plys[this] and 1 or 0 +end + +-------------------------------------------------------------------------------- + +__e2setcost(2) +local Trusts + +if CPPI and debug.getregistry().Player.CPPIGetFriends then + + function Trusts(ply, whom) + if ply == whom then return true end + local friends = ply:CPPIGetFriends() + if not istable(friends) then return false end + for _,friend in pairs(friends) do + if whom == friend then return true end + end + return false + end + + e2function array entity:friends() + if not IsValid(this) then return {} end + if not this:IsPlayer() then return {} end + if not Trusts(this, self.player) then return {} end + + local ret = this:CPPIGetFriends() + if not istable(ret) then return {} end + return ret + end + + e2function number entity:trusts(entity whom) + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + if not Trusts(this, self.player) then return 0 end + + return Trusts(this, whom) and 1 or 0 + end + +else + + function Trusts(ply, whom) + return ply == whom + end + + e2function array entity:friends() + return {} + end + + e2function number entity:trusts(entity whom) + return whom == this and 1 or 0 + end + +end + + +local steamfriends = {} + +concommand.Add("wire_expression2_friend_status", function(ply, command, args) + local friends = {} + + for index in args[1]:gmatch("[^,]+") do + local n = tonumber(index) + if not n then return end + table.insert(friends, Entity(n)) + end + + steamfriends[ply:EntIndex()] = friends +end) + +hook.Add("EntityRemoved", "wire_expression2_friend_status", function(ply) + steamfriends[ply:EntIndex()] = nil +end) + +__e2setcost(15) + +--- Returns an array containing 's steam friends currently on the server +e2function array entity:steamFriends() + if not IsValid(this) then return {} end + if not this:IsPlayer() then return {} end + if not Trusts(this, self.player) then return {} end + + return steamfriends[this:EntIndex()] or {} +end + +--- Returns 1 if and are steam friends, 0 otherwise. +e2function number entity:isSteamFriend(entity friend) + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + if not Trusts(this, self.player) then return 0 end + + local friends = steamfriends[this:EntIndex()] + if not friends then return 0 end + + return table.HasValue(friends, friend) and 1 or 0 +end + +-------------------------------------------------------------------------------- + +__e2setcost(5) + +e2function number entity:ping() + if not IsValid(this) then return 0 end + if(this:IsPlayer()) then return this:Ping() else return 0 end +end + +e2function number entity:timeConnected() + if not IsValid(this) then return 0 end + if(this:IsPlayer()) then return this:TimeConnected() else return 0 end +end + +e2function entity entity:vehicle() + if not IsValid(this) then return nil end + if not this:IsPlayer() then return nil end + return this:GetVehicle() +end + +e2function number entity:inVehicle() + if not IsValid(this) then return 0 end + if(this:IsPlayer() and this:InVehicle()) then return 1 else return 0 end +end + +--- Returns 1 if the player is in noclip mode, 0 if not. +e2function number entity:inNoclip() + if not IsValid(this) or this:GetMoveType() ~= MOVETYPE_NOCLIP then return 0 end + return 1 +end + +e2function number entity:inGodMode() + return IsValid(this) and this:IsPlayer() and this:HasGodMode() and 1 or 0 +end + +-------------------------------------------------------------------------------- + +local player = player + +__e2setcost(10) + +e2function array players() + return player.GetAll() +end + +e2function array playersAdmins() + local Admins = {} + for _,ply in ipairs(player.GetAll()) do + if (ply:IsAdmin()) then + table.insert(Admins,ply) + end + end + return Admins +end + +e2function array playersSuperAdmins() + local Admins = {} + for _,ply in ipairs(player.GetAll()) do + if (ply:IsSuperAdmin()) then + table.insert(Admins,ply) + end + end + return Admins +end + +-------------------------------------------------------------------------------- + +e2function entity entity:aimEntity() + if not IsValid(this) then return nil end + if not this:IsPlayer() then return nil end + + local ent = this:GetEyeTraceNoCursor().Entity + if not ent:IsValid() then return nil end + return ent +end + +e2function vector entity:aimPos() + if not IsValid(this) then return {0,0,0} end + if not this:IsPlayer() then return {0,0,0} end + + return this:GetEyeTraceNoCursor().HitPos +end + +e2function vector entity:aimNormal() + if not IsValid(this) then return {0,0,0} end + if not this:IsPlayer() then return {0,0,0} end + + return this:GetEyeTraceNoCursor().HitNormal +end + +--- Returns the bone the player is currently aiming at. +e2function bone entity:aimBone() + if not IsValid(this) then return nil end + if not this:IsPlayer() then return nil end + + local trace = this:GetEyeTraceNoCursor() + local ent = trace.Entity + if not IsValid(ent) then return nil end + return getBone(ent, trace.PhysicsBone) +end + +--[[--------------------------------------------------------------------------------------------]]-- + +hook.Add("PlayerInitialSpawn","Exp2RunOnJoin", function(ply) + lastJoined = ply + for e,_ in pairs(spawnAlert) do + if IsValid(e) then + e.context.data.runBySpawn = true + e:Execute() + e.context.data.runBySpawn = nil + else + spawnAlert[e] = nil + end + end +end) + +hook.Add("PlayerDisconnected","Exp2RunOnLeave", function(ply) + lastLeft = ply + for e,_ in pairs(leaveAlert) do + if IsValid(e) then + e.context.data.runByLeave = true + e:Execute() + e.context.data.runByLeave = nil + else + leaveAlert[e] = nil + end + end +end) + +__e2setcost(3) + +e2function void runOnPlayerConnect(activate) + if activate ~= 0 then + spawnAlert[self.entity] = true + else + spawnAlert[self.entity] = nil + end +end + +e2function number playerConnectClk() + return self.data.runBySpawn and 1 or 0 +end + +e2function entity lastConnectedPlayer() + return lastJoined +end + +e2function void runOnPlayerDisconnect(activate) + if activate ~= 0 then + leaveAlert[self.entity] = true + else + leaveAlert[self.entity] = nil + end +end + +e2function number playerDisconnectClk() + return self.data.runByLeave and 1 or 0 +end + +e2function entity lastDisconnectedPlayer() + return lastLeft +end + +----- Deaths+Spawns, Dev: Vurv, 12/28/19 ----- +local DeathAlert = {} -- table of e2s that have runOnDeath(1) +local RespawnAlert = {} +local DeathList = { last = {} } +local RespawnList = { last = {} } + +hook.Add("PlayerDeath","Exp2PlayerDetDead",function(victim,inflictor,attacker) + local entry = {} -- default table + entry.victim = victim + entry.inflictor = inflictor + entry.timestamp = CurTime() + entry.attacker = attacker + DeathList[victim:EntIndex()] = entry -- victim's death is saved in victims death list. + DeathList.last = entry -- the most recent death's table is stored here for later use. + for ex,_ in pairs(DeathAlert) do -- loops over all chips in deathalert, ignores key. + if IsValid(ex) then + ex.context.data.runByDeath = entry + ex:Execute() + ex.context.data.runByDeath = nil + end + end +end) + +hook.Add("PlayerSpawn","Exp2PlayerDetRespn",function(player,transition) + local entry = {} + entry.ply = player + entry.timestamp = CurTime() + RespawnList[player:EntIndex()] = entry + RespawnList.last = entry + for ex,_ in pairs(RespawnAlert) do + if IsValid(ex) then + ex.context.data.runByRespawned = entry + ex:Execute() + ex.context.data.runByRespawned = nil + end + end +end) + +__e2setcost(5) + +--- If active is 0, the chip will no longer run on death. +e2function void runOnDeath(number activate) + if activate ~= 0 then + DeathAlert[self.entity] = true + else + DeathAlert[self.entity] = nil + end +end + +--If ran by death, (defined in e2 data), gives 1 or 0 (ternary) +e2function number deathClk() + return self.data.runByDeath and 1 or 0 +end + +e2function number lastDeathTime() -- returns when the last death happened + local Timestamp = DeathList.last.timestamp + if not IsValid(Timestamp) then return 0 end + return Timestamp -- Checks if num is valid, if so then returns the timestamp from the table, else returns 0. +end + +e2function number lastDeathTime(entity ply) -- returns when the player provided last died + if not IsValid(ply) then return NULL end + if not ply:IsPlayer() then return NULL end + local Timestamp = DeathList[ply:EntIndex()].timestamp + if not IsValid(Timestamp) then return 0 end + return Timestamp -- Checks if num is valid, if so then returns the timestamp from the table, else returns 0. (also checks if ply is player and valid) +end + +e2function entity lastDeathVictim() -- Gives Death Victim + local Victim = DeathList.last.victim + if not IsValid(Victim) then return NULL end + return Victim +end + +e2function entity lastDeathInflictor() + local Inflictor = DeathList.last.inflictor + if not IsValid(Inflictor) then return NULL end + return Inflictor +end + +e2function entity lastDeathInflictor(entity ply) + if not IsValid(ply) then return NULL end + if not ply:IsPlayer() then return NULL end + local Inflictor = DeathList[ply:EntIndex()].inflictor + if not IsValid(Inflictor) then return NULL end + return Inflictor +end + +e2function entity lastDeathAttacker() + local Attacker = DeathList.last.attacker + if not IsValid(Attacker) then return NULL end + return Attacker +end + +e2function entity lastDeathAttacker(entity ply) + if not IsValid(ply) then return NULL end + if not ply:IsPlayer() then return NULL end + local Attacker = DeathList[ply:EntIndex()].attacker + if not IsValid(Attacker) then return NULL end + return Attacker +end + +-- Spawn Functions +e2function number spawnClk() + return self.data.runByRespawned and 1 or 0 +end + +e2function void runOnSpawn(number activate) + if activate ~= 0 then + RespawnAlert[self.entity] = true + else + RespawnAlert[self.entity] = nil + end +end + +e2function number lastSpawnTime() + local Timestamp = RespawnList.last.timestamp + if not IsValid(Timestamp) then return 0 end + return Timestamp +end + +e2function number lastSpawnTime(entity ply) -- returns the last time player provided spawned. + if not IsValid(ply) then return 0 end + if not ply:IsPlayer() then return 0 end + local Timestamp = SpawnList[ply:EntIndex()].timestamp + if not IsValid(Timestamp) then return 0 end + return Timestamp +end + +e2function entity lastSpawnedPlayer() + local Ply = RespawnList.last.ply + if not IsValid(Ply) then return NULL end + return Ply +end +--******************************************-- diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/quaternion.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/quaternion.lua new file mode 100644 index 0000000..d309f5e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/quaternion.lua @@ -0,0 +1,754 @@ +/******************************************************************************\ + Quaternion support +\******************************************************************************/ + +// TODO: implement more! + +-- faster access to some math library functions +local abs = math.abs +local Round = math.Round +local sqrt = math.sqrt +local exp = math.exp +local log = math.log +local sin = math.sin +local cos = math.cos +local sinh = math.sinh +local cosh = math.cosh +local acos = math.acos + +local deg2rad = math.pi/180 +local rad2deg = 180/math.pi + +registerType("quaternion", "q", { 0, 0, 0, 0 }, + nil, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 4 then error("Return value does not have exactly 4 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 4 + end +) + +local function format(value) + local r,i,j,k,dbginfo + + r = "" + i = "" + j = "" + k = "" + + if abs(value[1]) > 0.0005 then + r = Round(value[1]*1000)/1000 + end + dbginfo = r + if abs(value[2]) > 0.0005 then + i = tostring(Round(value[2]*1000)/1000) + if string.sub(i,1,1)!="-" and dbginfo != "" then i = "+"..i end + i = i .. "i" + end + dbginfo = dbginfo .. i + if abs(value[3]) > 0.0005 then + j = tostring(Round(value[3]*1000)/1000) + if string.sub(j,1,1)!="-" and dbginfo != "" then j = "+"..j end + j = j .. "j" + end + dbginfo = dbginfo .. j + if abs(value[4]) > 0.0005 then + k = tostring(Round(value[4]*1000)/1000) + if string.sub(k,1,1)!="-" and dbginfo != "" then k = "+"..k end + k = k .. "k" + end + dbginfo = dbginfo .. k + if dbginfo == "" then dbginfo = "0" end + return dbginfo +end + +WireLib.registerDebuggerFormat("QUATERNION", format) + +/****************************** Helper functions ******************************/ + +local function qmul(lhs, rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + return { + lhs1 * rhs1 - lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, + lhs1 * rhs2 + lhs2 * rhs1 + lhs3 * rhs4 - lhs4 * rhs3, + lhs1 * rhs3 + lhs3 * rhs1 + lhs4 * rhs2 - lhs2 * rhs4, + lhs1 * rhs4 + lhs4 * rhs1 + lhs2 * rhs3 - lhs3 * rhs2 + } +end + +local function qexp(q) + local m = sqrt(q[2]*q[2] + q[3]*q[3] + q[4]*q[4]) + local u + if m ~= 0 then + u = { q[2]*sin(m)/m, q[3]*sin(m)/m, q[4]*sin(m)/m } + else + u = { 0, 0, 0 } + end + local r = exp(q[1]) + return { r*cos(m), r*u[1], r*u[2], r*u[3] } +end + +local function qlog(q) + local l = sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4]) + if l == 0 then return { -1e+100, 0, 0, 0 } end + local u = { q[1]/l, q[2]/l, q[3]/l, q[4]/l } + local a = acos(u[1]) + local m = sqrt(u[2]*u[2] + u[3]*u[3] + u[4]*u[4]) + if abs(m) > delta then + return { log(l), a*u[2]/m, a*u[3]/m, a*u[4]/m } + else + return { log(l), 0, 0, 0 } --when m is 0, u[2], u[3] and u[4] are 0 too + end +end + +local function qDot(q1, q2) + return q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3] + q1[4]*q2[4] +end + +local function qGetNormalized(q) + local len = sqrt(q[1]^2 + q[2]^2 + q[3]^2 + q[4]^2) + return {q[1]/len, q[2]/len, q[3]/len, q[4]/len} +end + +local function qNormalize(q) + local len = sqrt(q[1]^2 + q[2]^2 + q[3]^2 + q[4]^2) + q[1] = q[1]/len + q[2] = q[2]/len + q[3] = q[3]/len + q[4] = q[4]/len +end + +/******************************************************************************/ + +__e2setcost(1) + +--- Creates a zero quaternion +e2function quaternion quat() + return { 0, 0, 0, 0 } +end + +--- Creates a quaternion with real part equal to +e2function quaternion quat(real) + return { real, 0, 0, 0 } +end + +--- Creates a quaternion with real and "i" parts equal to +e2function quaternion quat(complex c) + return { c[1], c[2], 0, 0 } +end + +--- Converts a vector to a quaternion (returns .x*i + .y*j + .z*k) +e2function quaternion quat(vector imag) + return { 0, imag[1], imag[2], imag[3] } +end + +--- Returns +i+j+k +e2function quaternion quat(real, i, j, k) + return { real, i, j, k } +end + +__e2setcost(6) + +--- Converts to a quaternion +e2function quaternion quat(angle ang) + local p, y, r = ang[1], ang[2], ang[3] + p = p*deg2rad*0.5 + y = y*deg2rad*0.5 + r = r*deg2rad*0.5 + local qr = {cos(r), sin(r), 0, 0} + local qp = {cos(p), 0, sin(p), 0} + local qy = {cos(y), 0, 0, sin(y)} + return qmul(qy,qmul(qp,qr)) +end + +__e2setcost(15) + +--- Creates a quaternion given forward () and up () vectors +e2function quaternion quat(vector forward, vector up) + local x = Vector(forward[1], forward[2], forward[3]) + local z = Vector(up[1], up[2], up[3]) + local y = z:Cross(x):GetNormalized() --up x forward = left + + local ang = x:Angle() + if ang.p > 180 then ang.p = ang.p - 360 end + if ang.y > 180 then ang.y = ang.y - 360 end + + local yyaw = Vector(0,1,0) + yyaw:Rotate(Angle(0,ang.y,0)) + + local roll = acos(math.Clamp(y:Dot(yyaw), -1, 1))*rad2deg + + local dot = y.z + if dot < 0 then roll = -roll end + + local p, y, r = ang.p, ang.y, roll + p = p*deg2rad*0.5 + y = y*deg2rad*0.5 + r = r*deg2rad*0.5 + local qr = {cos(r), sin(r), 0, 0} + local qp = {cos(p), 0, sin(p), 0} + local qy = {cos(y), 0, 0, sin(y)} + return qmul(qy,qmul(qp,qr)) +end + +--- Converts angle of to a quaternion +e2function quaternion quat(entity ent) + if(!IsValid(ent)) then + return { 0, 0, 0, 0 } + end + local ang = ent:GetAngles() + local p, y, r = ang.p, ang.y, ang.r + p = p*deg2rad*0.5 + y = y*deg2rad*0.5 + r = r*deg2rad*0.5 + local qr = {cos(r), sin(r), 0, 0} + local qp = {cos(p), 0, sin(p), 0} + local qy = {cos(y), 0, 0, sin(y)} + return qmul(qy,qmul(qp,qr)) +end + +__e2setcost(1) + +--- Returns quaternion i +e2function quaternion qi() + return {0, 1, 0, 0} +end + +--- Returns quaternion *i +e2function quaternion qi(n) + return {0, n, 0, 0} +end + +--- Returns j +e2function quaternion qj() + return {0, 0, 1, 0} +end + +--- Returns *j +e2function quaternion qj(n) + return {0, 0, n, 0} +end + +--- Returns k +e2function quaternion qk() + return {0, 0, 0, 1} +end + +--- Returns *k +e2function quaternion qk(n) + return {0, 0, 0, n} +end + +/******************************************************************************/ + +__e2setcost(2) + +registerOperator("ass", "q", "q", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + self.Scopes[scope][lhs] = rhs + self.Scopes[scope].vclk[lhs] = true + return rhs +end) + +/******************************************************************************/ +// TODO: define division as multiplication with (1/x), or is it not useful? + +__e2setcost(4) + +e2function quaternion operator_neg(quaternion q) + return { -q[1], -q[2], -q[3], -q[4] } +end + +e2function quaternion operator+(quaternion lhs, quaternion rhs) + return { lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3] + rhs[3], lhs[4] + rhs[4] } +end + +e2function quaternion operator+(number lhs, quaternion rhs) + return { lhs + rhs[1], rhs[2], rhs[3], rhs[4] } +end + +e2function quaternion operator+(quaternion lhs, number rhs) + return { lhs[1] + rhs, lhs[2], lhs[3], lhs[4] } +end + +e2function quaternion operator+(complex lhs, quaternion rhs) + return { lhs[1] + rhs[1], lhs[2] + rhs[2], rhs[3], rhs[4] } +end + +e2function quaternion operator+(quaternion lhs, complex rhs) + return { lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3], lhs[4] } +end + +e2function quaternion operator-(quaternion lhs, quaternion rhs) + return { lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3], lhs[4] - rhs[4] } +end + +e2function quaternion operator-(number lhs, quaternion rhs) + return { lhs - rhs[1], -rhs[2], -rhs[3], -rhs[4] } +end + +e2function quaternion operator-(quaternion lhs, number rhs) + return { lhs[1] - rhs, lhs[2], lhs[3], lhs[4] } +end + +e2function quaternion operator-(complex lhs, quaternion rhs) + return { lhs[1] - rhs[1], lhs[2] - rhs[2], -rhs[3], -rhs[4] } +end + +e2function quaternion operator-(quaternion lhs, complex rhs) + return { lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3], lhs[4] } +end + +e2function quaternion operator*(lhs, quaternion rhs) + return { lhs * rhs[1], lhs * rhs[2], lhs * rhs[3], lhs * rhs[4] } +end + +e2function quaternion operator*(quaternion lhs, rhs) + return { lhs[1] * rhs, lhs[2] * rhs, lhs[3] * rhs, lhs[4] * rhs } +end + +__e2setcost(6) + +e2function quaternion operator*(complex lhs, quaternion rhs) + local lhs1, lhs2 = lhs[1], lhs[2] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + return { + lhs1 * rhs1 - lhs2 * rhs2, + lhs1 * rhs2 + lhs2 * rhs1, + lhs1 * rhs3 - lhs2 * rhs4, + lhs1 * rhs4 + lhs2 * rhs3 + } +end + +e2function quaternion operator*(quaternion lhs, complex rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs1, rhs2 = rhs[1], rhs[2] + return { + lhs1 * rhs1 - lhs2 * rhs2, + lhs1 * rhs2 + lhs2 * rhs1, + lhs3 * rhs1 + lhs4 * rhs2, + lhs4 * rhs1 - lhs3 * rhs2 + } +end + +__e2setcost(9) + +e2function quaternion operator*(quaternion lhs, quaternion rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + return { + lhs1 * rhs1 - lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, + lhs1 * rhs2 + lhs2 * rhs1 + lhs3 * rhs4 - lhs4 * rhs3, + lhs1 * rhs3 + lhs3 * rhs1 + lhs4 * rhs2 - lhs2 * rhs4, + lhs1 * rhs4 + lhs4 * rhs1 + lhs2 * rhs3 - lhs3 * rhs2 + } +end + +e2function quaternion operator*(quaternion lhs, vector rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3] + return { + -lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, + lhs1 * rhs2 + lhs3 * rhs4 - lhs4 * rhs3, + lhs1 * rhs3 + lhs4 * rhs2 - lhs2 * rhs4, + lhs1 * rhs4 + lhs2 * rhs3 - lhs3 * rhs2 + } +end + +e2function quaternion operator*(vector lhs, quaternion rhs) + local lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + return { + -lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, + lhs2 * rhs1 + lhs3 * rhs4 - lhs4 * rhs3, + lhs3 * rhs1 + lhs4 * rhs2 - lhs2 * rhs4, + lhs4 * rhs1 + lhs2 * rhs3 - lhs3 * rhs2 + } +end + +e2function quaternion operator/(quaternion lhs, number rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + return { + lhs1/rhs, + lhs2/rhs, + lhs3/rhs, + lhs4/rhs + } +end + +e2function quaternion operator/(number lhs, quaternion rhs) + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + local l = rhs1*rhs1 + rhs2*rhs2 + rhs3*rhs3 + rhs4*rhs4 + return { + ( lhs * rhs1)/l, + (-lhs * rhs2)/l, + (-lhs * rhs3)/l, + (-lhs * rhs4)/l + } +end + +e2function quaternion operator/(quaternion lhs, complex rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs1, rhs2 = rhs[1], rhs[2] + local l = rhs1*rhs1 + rhs2*rhs2 + return { + ( lhs1 * rhs1 + lhs2 * rhs2)/l, + (-lhs1 * rhs2 + lhs2 * rhs1)/l, + ( lhs3 * rhs1 - lhs4 * rhs2)/l, + ( lhs4 * rhs1 + lhs3 * rhs2)/l + } +end + +e2function quaternion operator/(complex lhs, quaternion rhs) + local lhs1, lhs2 = lhs[1], lhs[2] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + local l = rhs1*rhs1 + rhs2*rhs2 + rhs3*rhs3 + rhs4*rhs4 + return { + ( lhs1 * rhs1 + lhs2 * rhs2)/l, + (-lhs1 * rhs2 + lhs2 * rhs1)/l, + (-lhs1 * rhs3 + lhs2 * rhs4)/l, + (-lhs1 * rhs4 - lhs2 * rhs3)/l + } +end + +__e2setcost(10) +e2function quaternion operator/(quaternion lhs, quaternion rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + local l = rhs1*rhs1 + rhs2*rhs2 + rhs3*rhs3 + rhs4*rhs4 + return { + ( lhs1 * rhs1 + lhs2 * rhs2 + lhs3 * rhs3 + lhs4 * rhs4)/l, + (-lhs1 * rhs2 + lhs2 * rhs1 - lhs3 * rhs4 + lhs4 * rhs3)/l, + (-lhs1 * rhs3 + lhs3 * rhs1 - lhs4 * rhs2 + lhs2 * rhs4)/l, + (-lhs1 * rhs4 + lhs4 * rhs1 - lhs2 * rhs3 + lhs3 * rhs2)/l + } +end + +__e2setcost(4) + +e2function quaternion operator^(number lhs, quaternion rhs) + if lhs == 0 then return { 0, 0, 0, 0 } end + local l = log(lhs) + return qexp({ l*rhs[1], l*rhs[2], l*rhs[3], l*rhs[4] }) +end + +e2function quaternion operator^(quaternion lhs, number rhs) + local l = qlog(lhs) + return qexp({ l[1]*rhs, l[2]*rhs, l[3]*rhs, l[4]*rhs }) +end + + +e2function number quaternion:operator[](index) + index = math.Round(math.Clamp(index,1,4)) + return this[index] +end + +e2function number quaternion:operator[](index, value) + index = math.Round(math.Clamp(index,1,4)) + this[index] = value + return value +end + +/******************************************************************************/ + +__e2setcost(6) + +e2function number operator==(quaternion lhs, quaternion rhs) + local rvd1, rvd2, rvd3, rvd4 = lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3], lhs[4] - rhs[4] + if rvd1 <= delta && rvd1 >= -delta && + rvd2 <= delta && rvd2 >= -delta && + rvd3 <= delta && rvd3 >= -delta && + rvd4 <= delta && rvd4 >= -delta + then return 1 else return 0 end +end + +e2function number operator!=(quaternion lhs, quaternion rhs) + local rvd1, rvd2, rvd3, rvd4 = lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3], lhs[4] - rhs[4] + if rvd1 > delta || rvd1 < -delta || + rvd2 > delta || rvd2 < -delta || + rvd3 > delta || rvd3 < -delta || + rvd4 > delta || rvd4 < -delta + then return 1 else return 0 end +end + +/******************************************************************************/ + +__e2setcost(4) + +--- Returns absolute value of +e2function number abs(quaternion q) + return sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4]) +end + +--- Returns the conjugate of +e2function quaternion conj(quaternion q) + return {q[1], -q[2], -q[3], -q[4]} +end + +--- Returns the inverse of +e2function quaternion inv(quaternion q) + local l = q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4] + if l == 0 then return {0,0,0,0} end + return { q[1]/l, -q[2]/l, -q[3]/l, -q[4]/l } +end + +__e2setcost(1) + +--- Returns the real component of the quaternion +e2function number quaternion:real() + return this[1] +end + +--- Returns the i component of the quaternion +e2function number quaternion:i() + return this[2] +end + +--- Returns the j component of the quaternion +e2function number quaternion:j() + return this[3] +end + +--- Returns the k component of the quaternion +e2function number quaternion:k() + return this[4] +end + +/******************************************************************************/ + +__e2setcost(7) + +--- Raises Euler's constant e to the power +e2function quaternion exp(quaternion q) + return qexp(q) +end + +--- Calculates natural logarithm of +e2function quaternion log(quaternion q) + return qlog(q) +end + +__e2setcost(2) + +--- Changes quaternion so that the represented rotation is by an angle between 0 and 180 degrees (by coder0xff) +e2function quaternion qMod(quaternion q) + if q[1]<0 then return {-q[1], -q[2], -q[3], -q[4]} else return {q[1], q[2], q[3], q[4]} end +end + +__e2setcost(13) + +--- Performs spherical linear interpolation between and . Returns for =0, for =1 +--- Derived from c++ source on https://en.wikipedia.org/wiki/Slerp +e2function quaternion slerp(quaternion q0, quaternion q1, number t) + local dot = qDot(q0, q1) + + if dot < 0 then + q1 = {-q1[1], -q1[2], -q1[3], -q1[4]} + dot = -dot + end + + -- Really small theta, transcendental functions approximate to linear + if dot > 0.9995 then + local lerped = { + q0[1] + t*(q1[1] - q0[1]), + q0[2] + t*(q1[2] - q0[2]), + q0[3] + t*(q1[3] - q0[3]), + q0[4] + t*(q1[4] - q0[4]), + } + qNormalize(lerped) + return lerped + end + + local theta_0 = acos(dot) + local theta = theta_0*t + local sin_theta = sin(theta) + local sin_theta_0 = sin(theta_0) + + local s0 = cos(theta) - dot * sin_theta / sin_theta_0 + local s1 = sin_theta / sin_theta_0 + + local slerped = { + q0[1]*s0 + q1[1]*s1, + q0[2]*s0 + q1[2]*s1, + q0[3]*s0 + q1[3]*s1, + q0[4]*s0 + q1[4]*s1, + } + qNormalize(slerped) + return slerped +end + +--- Performs normalized linear interpolation between and . Returns normalized for =0, normalized for =1 +e2function quaternion nlerp(quaternion q0, quaternion q1, number t) + local t1 = 1 - t + local q2 + if qDot(q0, q1) < 0 then + q2 = { q0[1] * t1 - q1[1] * t, q0[2] * t1 - q1[2] * t, q0[3] * t1 - q1[3] * t, q0[4] * t1 - q1[4] * t } + else + q2 = { q0[1] * t1 + q1[1] * t, q0[2] * t1 + q1[2] * t, q0[3] * t1 + q1[3] * t, q0[4] * t1 + q1[4] * t } + end + + qNormalize(q2) + return q2 +end + +/******************************************************************************/ +__e2setcost(7) + +--- Returns vector pointing forward for +e2function vector quaternion:forward() + local this1, this2, this3, this4 = this[1], this[2], this[3], this[4] + local t2, t3, t4 = this2 * 2, this3 * 2, this4 * 2 + return { + this1 * this1 + this2 * this2 - this3 * this3 - this4 * this4, + t3 * this2 + t4 * this1, + t4 * this2 - t3 * this1 + } +end + +--- Returns vector pointing right for +e2function vector quaternion:right() + local this1, this2, this3, this4 = this[1], this[2], this[3], this[4] + local t2, t3, t4 = this2 * 2, this3 * 2, this4 * 2 + return { + t4 * this1 - t2 * this3, + this2 * this2 - this1 * this1 + this4 * this4 - this3 * this3, + - t2 * this1 - t3 * this4 + } +end + +--- Returns vector pointing up for +e2function vector quaternion:up() + local this1, this2, this3, this4 = this[1], this[2], this[3], this[4] + local t2, t3, t4 = this2 * 2, this3 * 2, this4 * 2 + return { + t3 * this1 + t2 * this4, + t3 * this4 - t2 * this1, + this1 * this1 - this2 * this2 - this3 * this3 + this4 * this4 + } +end + +/******************************************************************************/ +__e2setcost(9) + +--- Returns quaternion for rotation about axis by angle +e2function quaternion qRotation(vector axis, ang) + local ax = Vector(axis[1], axis[2], axis[3]) + ax:Normalize() + local ang2 = ang*deg2rad*0.5 + return { cos(ang2), ax.x*sin(ang2), ax.y*sin(ang2), ax.z*sin(ang2) } +end + +--- Construct a quaternion from the rotation vector . Vector direction is axis of rotation, magnitude is angle in degress (by coder0xff) +e2function quaternion qRotation(vector rv1) + local angSquared = rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + if angSquared == 0 then return { 1, 0, 0, 0 } end + local len = sqrt(angSquared) + local ang = (len + 180) % 360 - 180 + local ang2 = ang*deg2rad*0.5 + local sang2len = sin(ang2) / len + return { cos(ang2), rv1[1] * sang2len , rv1[2] * sang2len, rv1[3] * sang2len } +end + +--- Returns the angle of rotation in degrees (by coder0xff) +e2function number rotationAngle(quaternion q) + local l2 = q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4] + if l2 == 0 then return 0 end + local l = sqrt(l2) + local ang = 2*acos(math.Clamp(q[1]/l, -1, 1))*rad2deg //this returns angle from 0 to 360 + if ang > 180 then ang = ang - 360 end //make it -180 - 180 + return ang +end + +--- Returns the axis of rotation (by coder0xff) +e2function vector rotationAxis(quaternion q) + local m2 = q[2] * q[2] + q[3] * q[3] + q[4] * q[4] + if m2 == 0 then return { 0, 0, 1 } end + local m = sqrt(m2) + return { q[2] / m, q[3] / m, q[4] / m} +end + +--- Returns the rotation vector - rotation axis where magnitude is the angle of rotation in degress (by coder0xff) +e2function vector rotationVector(quaternion q) + local l2 = q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4] + local m2 = math.max( q[2]*q[2] + q[3]*q[3] + q[4]*q[4], 0 ) + if l2 == 0 or m2 == 0 then return { 0, 0, 0 } end + local s = 2 * acos( math.Clamp( q[1] / sqrt(l2), -1, 1 ) ) * rad2deg + if s > 180 then s = s - 360 end + s = s / sqrt(m2) + return { q[2] * s, q[3] * s, q[4] * s } +end + +/******************************************************************************/ +__e2setcost(3) + +--- Converts to a vector by dropping the real component +e2function vector vec(quaternion q) + return { q[2], q[3], q[4] } +end + +__e2setcost(15) + +--- Converts to a transformation matrix +e2function matrix matrix(quaternion q) + local w,x,y,z = q[1],q[2],q[3],q[4] + return { + 1 - 2*y*y - 2*z*z , 2*x*y - 2*z*w , 2*x*z + 2*y*w, + 2*x*y + 2*z*w , 1 - 2*x*x - 2*z*z , 2*y*z - 2*x*w, + 2*x*z - 2*y*w , 2*y*z + 2*x*w , 1 - 2*x*x - 2*y*y + } +end + +--- Returns angle represented by +e2function angle quaternion:toAngle() + local l = sqrt(this[1]*this[1]+this[2]*this[2]+this[3]*this[3]+this[4]*this[4]) + if l == 0 then return {0,0,0} end + local q1, q2, q3, q4 = this[1]/l, this[2]/l, this[3]/l, this[4]/l + + local x = Vector(q1*q1 + q2*q2 - q3*q3 - q4*q4, + 2*q3*q2 + 2*q4*q1, + 2*q4*q2 - 2*q3*q1) + + local y = Vector(2*q2*q3 - 2*q4*q1, + q1*q1 - q2*q2 + q3*q3 - q4*q4, + 2*q2*q1 + 2*q3*q4) + + local ang = x:Angle() + if ang.p > 180 then ang.p = ang.p - 360 end + if ang.y > 180 then ang.y = ang.y - 360 end + + local yyaw = Vector(0,1,0) + yyaw:Rotate(Angle(0,ang.y,0)) + + local roll = acos(math.Clamp(y:Dot(yyaw), -1, 1))*rad2deg + + local dot = q2*q1 + q3*q4 + if dot < 0 then roll = -roll end + + return {ang.p, ang.y, roll} +end + +--- Returns new normalized quaternion +e2function quaternion quaternion:normalized() + return qGetNormalized(this) +end + +--- Returns dot product of two quaternion +e2function number quaternion:dot(quaternion q1) + return qDot(this, q1) +end + +/******************************************************************************/ +--- Formats as a string. +e2function string toString(quaternion q) + return format(q) +end + +e2function string quaternion:toString() + return format(this) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/ranger.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/ranger.lua new file mode 100644 index 0000000..5463b2a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/ranger.lua @@ -0,0 +1,651 @@ +/******************************************************************************\ + Expression 2 built-in ranger/tracing extension +\******************************************************************************/ + +-- Register the type up here before the Extension Registration so that the Wire Ranger still works +registerType("ranger", "xrd", nil, + nil, + nil, + function(retval) + if retval == nil then return end + if !istable(retval) then error("Return value is neither nil nor a table, but a "..type(retval).."!",0) end + end, + function(v) + return !istable(v) or not v.HitPos + end +) + +__e2setcost(1) -- temporary + +--- RD = RD +registerOperator("ass", "xrd", "xrd", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + self.Scopes[scope][lhs] = rhs + self.Scopes[scope].vclk[lhs] = true + return rhs +end) + +e2function number operator_is(ranger walker) + if walker then return 1 else return 0 end +end + +/******************************************************************************/ + +E2Lib.RegisterExtension("ranger", true, "Lets E2 chips trace rays and check for collisions.") + +------------------- +-- Main function -- +------------------- + + + + +local function ResetRanger(self) + local data = self.data + data.rangerdefaultzero = false + data.rangerignoreworld = false + data.rangerwater = false + data.rangerentities = true + data.rangerwhitelistmode = false + data.rangerfilter = { self.entity } + data.rangerfilter_lookup = table.MakeNonIterable{ [self.entity] = true } +end + +local function IsErrorVector(pos) + if pos.x ~= pos.x or pos.x == math.huge or pos.x == -math.huge then return true end + if pos.y ~= pos.y or pos.y == math.huge or pos.y == -math.huge then return true end + if pos.z ~= pos.z or pos.z == math.huge or pos.z == -math.huge then return true end + return false +end + +local function entitiesAndWaterTrace( tracedata, tracefunc ) + tracedata.ignoreworld = true + local trace1 = tracefunc() + tracedata.ignoreworld = nil + tracedata.mask = MASK_WATER + local trace2 = tracefunc() + if not trace1.Hit then return trace2 end + if not trace2.Hit then return trace1 end + return trace1.fraction < trace2.fraction and trace1 or trace2 +end + +local function getFilter(invert,inputfilter,tracedat,self) -- activate if whitelistmode is on, used later + + if invert then + tracedat.filter = {self.entity} + local foundEnts = ents.FindAlongRay( tracedat.start , tracedat.endpos, tracedat.mins, tracedat.maxs ) + -- set real filter to everything we MIGHT hit for now + + for _,found in ipairs(foundEnts) do + for _,ent in ipairs(inputfilter) do + if ent==found then + goto nextEnt + end + end + tracedat.filter[#tracedat.filter + 1] = found + ::nextEnt:: + end + + self.prf = self.prf + #tracedat.filter*2 -- add 2 ops for every potential entity hit, tells how expensive the find was + else + tracedat.filter = inputfilter + end + +end + +local function ranger(self, rangertype, range, p1, p2, hulltype, mins, maxs, traceEntity ) + local data = self.data + local chip = self.entity + + local whitelistmode = data.rangerwhitelistmode + local defaultzero = data.rangerdefaultzero + local ignoreworld = data.rangerignoreworld + local water = data.rangerwater + local entities = data.rangerentities + + local filter = data.rangerfilter + local finalfilter = nil + + if not data.rangerpersist then ResetRanger(self) end + + -- begin building tracedata structure + local tracedata = {} + if entities then + if ignoreworld then + if water then + tracedata.entitiesandwater = true + else + tracedata.ignoreworld = true + end + else + if water then + tracedata.mask = -1 + else + --No flags needed + end + end + else + if ignoreworld then + if water then + tracedata.mask = MASK_WATER + else + tracedata.mask = 0 + end + else + if water then + tracedata.mask = bit.bor(MASK_WATER, CONTENTS_SOLID) + else + tracedata.mask = MASK_NPCWORLDSTATIC + end + end + end + + -- calculate startpos and endpos + if rangertype == 2 then + tracedata.start = Vector( p1[1], p1[2], p1[3] ) + tracedata.endpos = Vector( p2[1], p2[2], p2[3] ) + elseif rangertype == 3 then + tracedata.start = Vector( p1[1], p1[2], p1[3] ) + tracedata.endpos = tracedata.start + Vector( p2[1], p2[2], p2[3] ):GetNormalized()*range + else + tracedata.start = chip:GetPos() + + if rangertype == 1 && (p1!=0 || p2!=0) then + p1 = math.rad(p1) + p2 = math.rad(p2+270) + local zoff = -math.cos(p1)*range + local yoff = math.sin(p1)*range + local xoff = math.cos(p2)*zoff + zoff = math.sin(p2)*zoff + tracedata.endpos = chip:LocalToWorld(Vector(xoff,yoff,zoff)) + elseif rangertype == 0 && (p1!=0 || p2!=0) then + local skew = Vector(p2, -p1, 1) + tracedata.endpos = chip:LocalToWorld(skew:GetNormalized()*range) + else + tracedata.endpos = tracedata.start + chip:GetUp()*range + end + end + + if IsErrorVector(tracedata.start) or IsErrorVector(tracedata.endpos) then return end + + -- EDIT: give rangerFilter() different behaviors with rangerwhitelistmode() + + + + --------------------------------------------------------------------------------------- + local trace + if IsValid(traceEntity) then + + getFilter(whitelistmode,filter,tracedata,self) -- condensed all the rangerWhitelist stuff down into one function, should be more "readable" now + + if tracedata.entitiesandwater then + + trace = entitiesAndWaterTrace( tracedata, function() + return util.TraceEntity( tracedata, traceEntity ) + end ) + else + trace = util.TraceEntity( tracedata, traceEntity ) + end + elseif (hulltype) then + if (hulltype == 1) then + local s = Vector(mins[1], mins[2], mins[3]) + tracedata.mins = s/2*-1 + tracedata.maxs = s/2 + elseif (hulltype == 2) then + local s1 = Vector(mins[1], mins[2], mins[3]) + local s2 = Vector(maxs[1], maxs[2], maxs[3]) + tracedata.mins = s1 + tracedata.maxs = s2 + end + + if not entities then -- unfortunately we have to add tons of ops if this happens + -- If we didn't, it would be possible to crash servers with it. + tracedata.mins = WireLib.clampPos( tracedata.mins ) + tracedata.maxs = WireLib.clampPos( tracedata.maxs ) + self.prf = self.prf + tracedata.mins:Distance(tracedata.maxs) * 0.5 + end + + if IsErrorVector(tracedata.mins) or IsErrorVector(tracedata.maxs) then return end + -- If max is less than min it'll cause a hang + OrderVectors(tracedata.mins, tracedata.maxs) + + getFilter(whitelistmode,filter,tracedata,self) + + if whitelistmode then + tracedata.filter[#tracedata.filter] = chip + end + + if tracedata.entitiesandwater then + trace = entitiesAndWaterTrace( tracedata, function() + return util.TraceHull( tracedata ) + end ) + else + trace = util.TraceHull( tracedata ) + end + else + + getFilter(whitelistmode,filter,tracedata,self) + + if tracedata.entitiesandwater then + trace = entitiesAndWaterTrace( tracedata, function() + return util.TraceLine( tracedata ) + end ) + else + trace = util.TraceLine( tracedata ) + end + end + --------------------------------------------------------------------------------------- + + -- handle some ranger settings + if defaultzero and not trace.Hit then + trace.Fraction = 0 + trace.HitPos = tracedata.start + end + + trace.RealStartPos = tracedata.start + + return trace +end + +/******************************************************************************/ + +__e2setcost(1) -- temporary + +--- Passing 0 (the default) resets all ranger flags and filters every execution and after calling ranger/rangerOffset. Passing anything else will make the flags and filters persist until they're changed again. +e2function void rangerPersist(persist) + self.data.rangerpersist = persist ~= 0 +end + +--- Resets all ranger flags and filters. +e2function void rangerReset() + ResetRanger(self) +end + +local flaglookup = { + i = "rangerignoreworld", + w = "rangerwater", + e = "rangerentities", + z = "rangerdefaultzero", + v = "rangerwhitelistmode", +} + +--- Returns the ranger flags as a string. +e2function string rangerFlags() + local ret = "" + for char,field in pairs(flaglookup) do + if self.data[field] then ret = ret .. char end + end + return ret +end + +--- Sets the ranger flags. can be any combination of I=ignore world, W=hit water, E=hit entities and Z=default to zero. +e2function void rangerFlags(string flags) + flags = flags:lower() + for char,field in pairs(flaglookup) do + self.data[field] = flags:find(char) and true or false + end +end + +--- Default is 0, if any other value is given it will hit water +e2function void rangerHitWater(hitwater) + self.data.rangerwater = hitwater ~= 0 +end + +--- Default is 1, if any value other than 0 is is given, it will hit entities +e2function void rangerHitEntities(hitentities) + self.data.rangerentities = hitentities ~= 0 +end + +--- Default is 0, if any other value is given it will ignore world +e2function void rangerIgnoreWorld(ignoreworld) + self.data.rangerignoreworld = ignoreworld ~= 0 +end + +--- If given any value other than 0 it will default the distance data to zero when nothing is hit +e2function void rangerDefaultZero(defaultzero) + self.data.rangerdefaultzero = defaultzero ~= 0 +end + +--- Default is 0, rangerFilter() behaves like a blacklist. 1 makes rangerFilter() behave like a whitelistmode. +e2function void rangerWhitelist(whitelistmode) + self.data.rangerwhitelistmode = whitelistmode ~= 0 +end + +__e2setcost(10) + +--- Feed entities you don't... or maybe only want to hit +e2function void rangerFilter(entity ent) + if IsValid(ent) and not self.data.rangerfilter_lookup[ent] then + local n = #self.data.rangerfilter+1 + self.data.rangerfilter[n] = ent + self.data.rangerfilter_lookup[ent] = true + end +end + +__e2setcost(1) + +--- Feed entities you don't... or maybe only want to hit +e2function void rangerFilter(array filter) + local rangerfilter = self.data.rangerfilter + local n = #rangerfilter + for _,ent in ipairs(filter) do + if IsValid(ent) and not self.data.rangerfilter_lookup[ent] then + n = n + 1 + rangerfilter[n] = ent + self.data.rangerfilter_lookup[ent] = true + end + end + self.prf = self.prf + #filter * 10 +end + +/******************************************************************************/ + +e2function ranger noranger() + return nil +end + +__e2setcost(20) -- temporary + +--- Equivalent to rangerOffset(16384, :shootPos(), :eye()), but faster (causing less lag) +e2function ranger entity:eyeTrace() + if not IsValid(this) then return nil end + if not this:IsPlayer() then return nil end + local ret = this:GetEyeTraceNoCursor() + ret.RealStartPos = this:GetShootPos() + return ret +end + +e2function ranger entity:eyeTraceCursor() + if not IsValid(this) or not this:IsPlayer() then return nil end + local ret = this:GetEyeTrace() + ret.RealStartPos = this:GetShootPos() + return ret +end + +--- You input max range, it returns ranger data +e2function ranger ranger(distance) + return ranger(self, 0, distance, 0, 0) -- type 0, no skew +end + +--- Same as above with added inputs for X and Y skew +e2function ranger ranger(distance, xskew, yskew) + return ranger(self, 0, distance, xskew, yskew) -- type 0, with skew +end + +-- Same as ranger(distance) but for another entity +e2function ranger ranger(entity ent, distance) + if not IsValid( ent ) then return nil end + if not self.data.rangerfilter_lookup[ent] then + self.data.rangerfilter[#self.data.rangerfilter+1] = ent + self.data.rangerfilter_lookup[ent] = true + end + return ranger(self,3,distance,ent:GetPos(),ent:GetUp()) +end + +--- You input the distance, x-angle and y-angle (both in degrees) it returns ranger data +e2function ranger rangerAngle(distance, xangle, yangle) + return ranger(self, 1, distance, xangle, yangle) -- type 1, with angles +end + +--- You input two vector points, it returns ranger data +e2function ranger rangerOffset(vector from, vector to) + return ranger(self, 2, 0, from, to) -- type 2, from one point to another +end + +--- You input the range, a position vector, and a direction vector and it returns ranger data +e2function ranger rangerOffset(distance, vector from, vector direction) + return ranger(self, 3, distance, from, direction) -- type 3, from one position into a specific direction, in a specific range +end + +/******************************************************************************/ + +__e2setcost(2) -- temporary + +--- Returns the distance from the rangerdata input, else depends on rangerDefault +e2function number ranger:distance() + if not this then return 0 end + + local startpos + if (this.StartSolid) then + startpos = this.RealStartPos + else + startpos = this.StartPos + end + + --if this.StartSolid then return this.StartPos:Distance(this.HitPos)*(1/(1-this.FractionLeftSolid)-1) end + return startpos:Distance(this.HitPos) +end + +--- Returns the position of the input ranger data trace IF it hit anything, else returns vec(0,0,0) +e2function vector ranger:position() + if not this then return { 0, 0, 0 } end + if this.StartSolid then return this.StartPos end + return this.HitPos +end + +-- Returns the position of the input ranger data trace IF it it anything, else returns vec(0,0,0). +-- NOTE: This function works like Lua's trace, while the above "position" function returns the same as positionLeftSolid IF it was created inside the world. +e2function vector ranger:pos() + if not this then return {0,0,0} end + return this.HitPos +end + +--- Returns the entity of the input ranger data trace IF it hit an entity, else returns nil +e2function entity ranger:entity() + if not this then return nil end + return this.Entity +end + +--- Returns the bone of the input ranger data trace IF it hit an entity, else returns nil +e2function bone ranger:bone() + if not this then return nil end + + local ent = this.Entity + if not IsValid(ent) then return nil end + return getBone(ent, this.PhysicsBone) +end + +--- Returns 1 if the input ranger data hit anything and 0 if it didn't +e2function number ranger:hit() + if not this then return 0 end + if this.Hit then return 1 else return 0 end +end + +--- Outputs a normalized vector perpendicular to the surface the ranger is pointed at. +e2function vector ranger:hitNormal() + if not this then return { 0, 0, 0 } end + return this.HitNormal +end + +-- Returns a number between 0 and 1, ie R:distance()/maxdistance +e2function number ranger:fraction() + if not this then return 0 end + return this.Fraction +end + +-- Returns 1 if the ranger hit the world, else 0 +e2function number ranger:hitWorld() + if not this then return 0 end + return this.HitWorld and 1 or 0 +end + +-- Returns 1 if the ranger hit the skybox, else 0 +e2function number ranger:hitSky() + if not this then return 0 end + return this.HitSky and 1 or 0 +end + +-- Returns the position at which the trace left the world if it was started inside the world +e2function vector ranger:positionLeftSolid() + if not this then return { 0,0,0 } end + return this.StartPos +end + +e2function number ranger:distanceLeftSolid() + if not this then return 0 end + return this.RealStartPos:Distance(this.StartPos) +end + +-- Returns a number between 0 and 1 +e2function number ranger:fractionLeftSolid() + if not this then return 0 end + return this.FractionLeftSolid +end + +-- Returns 1 if the trace started inside the world, else 0 +e2function number ranger:startSolid() + if not this then return 0 end + return this.StartSolid and 1 or 0 +end + +local mat_enums = {} +local hitgroup_enums = {} +for k,v in pairs( _G ) do + if (k:sub(1,4) == "MAT_") then + mat_enums[v] = k:sub(5):lower() + elseif (k:sub(1,9) == "HITGROUP_") then + hitgroup_enums[v] = k:sub(10):lower() + end +end + +-- Returns the material type (ie "contrete", "dirt", "flesh", etc) +e2function string ranger:matType() + if not this then return "" end + if not this.MatType then return "" end + return mat_enums[this.MatType] or "" +end + +-- Returns the hit group if the trace hit a player (ie "chest", "stomach", "head", "leftarm", etc) +e2function string ranger:hitGroup() + if not this then return "" end + if not this.HitGroup then return "" end + return hitgroup_enums[this.HitGroup] or "" +end + +-- Returns the texture that the trace hits +e2function string ranger:hitTexture() + if not this then return "" end + return this.HitTexture or "" +end + +-- Helper table used for toTable +local ids = { + ["FractionLeftSolid"] = "n", + ["HitNonWorld"] = "n", + ["Fraction"] = "n", + ["Entity"] = "e", + ["HitNoDraw"] = "n", + ["HitSky"] = "n", + ["HitPos"] = "v", + ["StartSolid"] = "n", + ["HitWorld"] = "n", + ["HitGroup"] = "n", + ["HitNormal"] = "v", + ["HitBox"] = "n", + ["Normal"] = "v", + ["Hit"] = "n", + ["MatType"] = "n", + ["StartPos"] = "v", + ["PhysicsBone"] = "n", + ["WorldToLocal"] = "v", + ["RealStartPos"] = "v", + ["HitTexture"] = "s", + ["HitBoxBone"] = "n" +} + +local DEFAULT = {n={},ntypes={},s={},stypes={},size=0} + +-- Converts the ranger into a table. This allows you to manually get any and all raw data from the trace. +e2function table ranger:toTable() + if not this then return {} end + local ret = table.Copy(DEFAULT) + local size = 0 + for k,v in pairs( this ) do + if (ids[k]) then + if isbool(v) then v = v and 1 or 0 end + ret.s[k] = v + ret.stypes[k] = ids[k] + size = size + 1 + end + end + ret.size = size + return ret +end + +/******************************************************************************/ +-- Hull traces + +__e2setcost(20) + +-- distance, size +e2function ranger rangerHull( number distance, vector size ) + return ranger( self, 0, distance, 0, 0, 1, size ) +end + +-- distance, mins, maxs +e2function ranger rangerHull(distance, vector mins, vector maxs) + return ranger(self, 0, distance, 0, 0, 2, mins, maxs ) +end + +-- distance, xskew, yskew, size +e2function ranger rangerHull(distance, xskew, yskew, vector size) + return ranger(self, 0, distance, xskew, yskew, 1, size) +end + +-- distance, xskew, yskew, mins, maxs +e2function ranger rangerHull(distance, xskew, yskew, vector mins, vector maxs) + return ranger(self, 0, distance, xskew, yskew, 2, mins, maxs) +end + +-- distance, xangle, yangle, size +e2function ranger rangerAngleHull(distance, xangle, yangle, vector size) + return ranger(self, 1, distance, xangle, yangle, 1, size) +end + +-- distance, xangle, yangle, mins, maxs +e2function ranger rangerAngleHull(distance, xangle, yangle, vector mins, vector maxs) + return ranger(self, 1, distance, xangle, yangle, 2, mins, maxs) +end + +-- startpos, endpos, size +e2function ranger rangerOffsetHull( vector startpos, vector endpos, vector size ) + return ranger( self, 2, 0, startpos, endpos, 1, size ) +end + +-- startpos, endpos, mins, maxs +e2function ranger rangerOffsetHull( vector startpos, vector endpos, vector mins, vector maxs ) + return ranger( self, 2, 0, startpos, endpos, 2, mins, maxs ) +end + +-- distance, startpos, direction, size +e2function ranger rangerOffsetHull( number distance, vector startpos, vector direction, vector size ) + return ranger( self, 3, distance, startpos, direction, 1, size ) +end + +-- distance, startpos, direction mins, maxs +e2function ranger rangerOffsetHull( number distance, vector startpos, vector direction, vector mins, vector maxs ) + return ranger( self, 3, distance, startpos, direction, 2, mins, maxs ) +end + +-- Use util.TraceEntity for collison box trace +e2function ranger rangerOffsetHull(entity ent, vector from, vector to) + if IsValid(ent) and !ent:IsWorld() then + return ranger(self, 2, 0, from, to, 0, 0, 0, ent) + else + return nil + end +end + +/******************************************************************************/ + +registerCallback("construct", function(self) + self.data.rangerpersist = false +end) + +registerCallback("preexecute", function(self) + if not self.data.rangerpersist then + ResetRanger(self) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/selfaware.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/selfaware.lua new file mode 100644 index 0000000..fa286d0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/selfaware.lua @@ -0,0 +1,246 @@ +/******************************************************************************\ + Selfaware support +\******************************************************************************/ + +__e2setcost(1) -- temporary + +e2function entity entity() + return self.entity +end + +e2function entity owner() + return self.player +end + +__e2setcost(nil) -- temporary + +e2function void selfDestruct() + self.entity:Remove() +end + +e2function void selfDestructAll() + for k,v in pairs(constraint.GetAllConstrainedEntities(self.entity)) do + if(getOwner(self,v)==self.player) then + v:Remove() + end + end + //constraint.RemoveAll(self.entity) + self.entity:Remove() +end + +/******************************************************************************/ +-- i/o functions + +__e2setcost(10) + +-- Returns an array of all entities wired to the output +e2function array ioOutputEntities( string output ) + local ret = {} + if (self.entity.Outputs[output]) then + local tbl = self.entity.Outputs[output].Connected + for i=1,#tbl do if (IsValid(tbl[i].Entity)) then ret[#ret+1] = tbl[i].Entity end end + self.prf = self.prf + #ret + end + return ret +end + +-- Returns the entity the input is wired to +e2function entity ioInputEntity( string input ) + if (self.entity.Inputs[input] and self.entity.Inputs[input].Src and IsValid(self.entity.Inputs[input].Src)) then return self.entity.Inputs[input].Src end +end + +local function setOutput( self, args, Type ) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self,op1), op2[1](self,op2) + if (self.entity.Outputs[rv1] and self.entity.Outputs[rv1].Type == Type) then + self.GlobalScope[rv1] = rv2 + self.GlobalScope.vclk[rv1] = true + end +end + +local function getInput( self, args, default, Type ) + local op1 = args[2] + local rv1 = op1[1](self,op1) + if istable(default) then default = table.Copy(default) end + if (self.entity.Inputs[rv1] and self.entity.Inputs[rv1].Type == Type) then + return self.GlobalScope[rv1] or default + end + return default +end + +local excluded_types = { + xgt = true, +} + +local function upperfirst( word ) + return word:Left(1):upper() .. word:Right(-2):lower() +end + +__e2setcost(5) + +registerCallback("postinit",function() + for k,v in pairs( wire_expression_types ) do + local short = v[1] + if (!excluded_types[short]) then + registerFunction("ioSetOutput","s"..short,""..short,function(self,args) return setOutput(self,args,k) end) + registerFunction("ioGetInput"..upperfirst(k == "NORMAL" and "NUMBER" or k),"s",short,function(self,args) return getInput(self,args,v[2],k) end) + end + end +end) + +/******************************************************************************/ +-- Name functions + +-- Set the name of the E2 itself +e2function void setName( string name ) + local e = self.entity + if( #name > 12000 ) then + name = string.sub( name, 1, 12000 ) + end + if (e.name == name) then return end + if (name == "generic" or name == "") then + name = "generic" + e.WireDebugName = "Expression 2" + else + e.WireDebugName = "E2 - " .. name + end + e.name = name + e:SetNWString( "name", e.name ) + e:SetOverlayText(name) +end + +-- Set the name of a entity (component name if not E2) +e2function void entity:setName( string name ) + if not IsValid(this) or E2Lib.getOwner(self, this) ~= self.player then return end + if( #name > 12000 ) then + name = string.sub( name, 1, 12000 ) + end + if this:GetClass() == "gmod_wire_expression2" then + if this.name == name then return end + if name == "generic" or name == "" then + name = "generic" + this.WireDebugName = "Expression 2" + else + this.WireDebugName = "E2 - " .. name + end + this.name = name + this:SetNWString( "name", this.name ) + this:SetOverlayText(name) + else + if this.wireName == name or string.find(name, "[\n\r\"]") ~= nil then return end + this.wireName = name + this:SetNWString("WireName", name) + duplicator.StoreEntityModifier(this, "WireName", { name = name }) + end +end + +-- Get the name of another E2 or compatible entity or component name of wiremod components +e2function string entity:getName() + if not IsValid(this) then return "" end + if this.GetGateName then + return this:GetGateName() or "" + end + return this:GetNWString("WireName", this.PrintName) or "" +end + + +/******************************************************************************/ + +registerCallback("construct", function(self) + self.data.changed = {} +end) + +__e2setcost(1) + +-- This is the prototype for everything that can be compared using the == operator +e2function number changed(value) + local chg = self.data.changed + + if value == chg[args] then return 0 end + + chg[args] = value + return 1 +end + +-- vectors can be of gmod type Vector, so we need to treat them separately +e2function number changed(vector value) + local chg = self.data.changed + + local this_chg = chg[args] + if not this_chg then + chg[args] = value + return 1 + end + if this_chg + and value[1] == this_chg[1] + and value[2] == this_chg[2] + and value[3] == this_chg[3] + then return 0 end + + chg[args] = value + return 1 +end + +-- This is the prototype for all table types. +e2function number changed(angle value) + local chg = self.data.changed + + local this_chg = chg[args] + if not this_chg then + chg[args] = value + return 1 + end + for i,v in pairs(value) do + if v ~= this_chg[i] then + chg[args] = value + return 1 + end + end + return 0 +end + +local excluded_types = { + n = true, + v = true, + a = true, + [""] = true, + + r = true, + t = true, +} +local comparable_types = { + s = true, + e = true, + xwl = true, + b = true, +} + +registerCallback("postinit", function() + -- generate this function for all types + for typeid,_ in pairs(wire_expression_types2) do + if not excluded_types[typeid] then + if comparable_types[typeid] then + registerFunction("changed", typeid, "n", registeredfunctions.e2_changed_n) + else + registerFunction("changed", typeid, "n", registeredfunctions.e2_changed_a) + end + end + end +end) + +/******************************************************************************/ + +__e2setcost( 5 ) + +local getHash = E2Lib.getHash +e2function number hash() + return getHash( self, self.entity.original ) +end + +e2function number hashNoComments() + return getHash( self, self.entity.buffer ) +end + +e2function number hash( string str ) + return getHash( self, str ) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/serialization.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/serialization.lua new file mode 100644 index 0000000..b5cf73c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/serialization.lua @@ -0,0 +1,561 @@ +E2Lib.RegisterExtension("serialization", true, "Adds functions to serialize data structures into a string and back again.") + +-- GLON output validation +local DEFAULT = {n={},ntypes={},s={},stypes={},size=0} + +--[[ +wire_expression2_glon = {} +wire_expression2_glon.history = {} +wire_expression2_glon.players = {} + +local function logGlonCall( self, glonString, ret, safeGlonObject ) + local logEntry = + { + Expression2 = + { + Name = self.entity.name, + Owner = self.entity.player, + OwnerID = self.entity.player:IsValid() and self.entity.player:SteamID() or "[Unknown]", + OwnerName = self.entity.player:IsValid() and self.entity.player:Name() or "[Unknown]", + }, + GLON = glonString, + GLONOutput = ret, + SafeOutput = safeGlonObject, + Timestamp = os.date("%c") + } + + wire_expression2_glon.history[#wire_expression2_glon.history + 1] = logEntry + + if self.entity.player:IsValid() then + wire_expression2_glon.players[self.entity.player] = wire_expression2_glon.players[self.entity.player] or {} + wire_expression2_glon.players[self.entity.player][#wire_expression2_glon.players[self.entity.player] + 1] = logEntry + end +end +]] + +local antispam_lookup = {} +local function antispam( self ) + if antispam_lookup[self.uid] and antispam_lookup[self.uid] > CurTime() then + return false + end + + antispam_lookup[self.uid] = CurTime() + 0.5 + return true +end + +-- this conversions table is used by luaTypeToWireTypeid +local conversions = { + -- convert boolean to number + boolean = function( v ) return "normal", v and 1 or 0 end, + + -- these probably won't happen, but just in case + Player = function( v ) return "Entity" end, + NPC = function( v ) return "Entity" end, +} + +-- converts a lua variable's type to a wire typeid +-- also returns v. v may have been modified in the process (converting boolean to number, for example) +local function luaTypeToWireTypeid( v ) + local typename = type( v ) + + if conversions[typename] then + local new_val + typename, new_val = conversions[typename]( v ) + if new_val ~= nil then + v = new_val + end + end + + -- special check for number + if typename == "number" then typename = "normal" end + + -- convert full type name to typeid + return wire_expression_types[ string.upper( typename ) ][1], v +end + +local forbiddenTypes = { + ["xgt"] = true, + ["xwl"] = true +} + +local typeSanitizers + +local function sanitizeGlonOutput ( self, glonOutputObject, objectType, safeGlonObjectMap ) + self.prf = self.prf + 1 + + if not objectType then return nil end + if forbiddenTypes[objectType] then return nil end + if not wire_expression_types2[objectType] and not objecType == "external_t" then return nil end + + safeGlonObjectMap = safeGlonObjectMap or { + r = {}, + t = {} + } + + if not typeSanitizers[objectType] then + if wire_expression_types2[objectType][6](glonOutputObject) then return nil end -- failed type validity check + return glonOutputObject + end + + return typeSanitizers[objectType] ( self, glonOutputObject, safeGlonObjectMap ) +end + +typeSanitizers = { + ["r"] = function ( self, glonOutputObject, safeGlonObjectMap ) + if safeGlonObjectMap["r"][glonOutputObject] then + return safeGlonObjectMap["r"][glonOutputObject] + end + + local safeArray = {} + if not glonOutputObject then return safeArray end + safeGlonObjectMap["r"][glonOutputObject] = safeArray + + if !istable(glonOutputObject) then return safeArray end + + for k, v in pairs(glonOutputObject) do + if type (k) == "number" then + safeArray[k] = v + end + end + + return safeArray + end, + ["t"] = function ( self, glonOutputObject, safeGlonObjectMap ) + if safeGlonObjectMap["t"][glonOutputObject] then + return safeGlonObjectMap["t"][glonOutputObject] + end + + local safeTable = table.Copy(DEFAULT) + if not glonOutputObject then return safeTable end + safeGlonObjectMap["t"][glonOutputObject] = safeTable + + if !istable(glonOutputObject) then return safeTable end + + if istable(glonOutputObject.s) and istable(glonOutputObject.stypes) then + for k, v in pairs(glonOutputObject.s) do + local objectType = glonOutputObject.stypes[k] + local safeObject = sanitizeGlonOutput( self, v, objectType, safeGlonObjectMap ) + if safeObject then + safeTable.s[tostring(k)] = safeObject + safeTable.stypes[tostring(k)] = objectType + safeTable.size = safeTable.size + 1 + end + end + end + + if istable(glonOutputObject.n) and istable(glonOutputObject.ntypes) then + for k, v in pairs(glonOutputObject.n) do + if isnumber(k) then + local objectType = glonOutputObject.ntypes[k] + local safeObject = sanitizeGlonOutput( self, v, objectType, safeGlonObjectMap ) + if safeObject then + safeTable.n[k] = safeObject + safeTable.ntypes[k] = objectType + safeTable.size = safeTable.size + 1 + end + end + end + end + + return safeTable + end, + ["v"] = function ( self, glonOutputObject, safeGlonObjectMap ) + if not glonOutputObject then return table.Copy(wire_expression_types2["v"][2]) end + if isvector(glonOutputObject) then return { glonOutputObject.x, glonOutputObject.y, glonOutputObject.z } end + if !istable(glonOutputObject) then return table.Copy(wire_expression_types2["v"][2]) end + + local safeValue = {} + for i = 1, 3 do + safeValue[i] = tonumber(glonOutputObject[i]) or wire_expression_types2["v"][2][i] + end + + return safeValue + end, + ["external_t"] = function ( self, glonOutputObject, safeGlonObjectMap ) + if safeGlonObjectMap["t"][glonOutputObject] then + return safeGlonObjectMap["t"][glonOutputObject] + end + + local safeTable = {} + if not glonOutputObject then return safeTable end + safeGlonObjectMap["t"][glonOutputObject] = safeTable + + if !istable(glonOutputObject) then return safeTable end + + for k, v in pairs(glonOutputObject) do + local objectType, v = luaTypeToWireTypeid( v ) + if objectType == "t" then objectType = "external_t" end + + local safeObject = sanitizeGlonOutput( self, v, objectType, safeGlonObjectMap ) + if safeObject then + safeTable[k] = safeObject + end + end + + return safeTable + end, +} + +-- Default sanitizer for types that are arrays of numbers +local numericArrayDataTypes = +{ + ["a"] = 3, + ["c"] = 2, + ["m"] = 9, + ["q"] = 4, + ["xm2"] = 4, + ["xm4"] = 16, + ["xv2"] = 2, + ["xv4"] = 4 +} + +for objectType, arrayLength in pairs(numericArrayDataTypes) do + typeSanitizers[objectType] = function ( self, glonOutputObject, sanitizedGlonObjectMap ) + if !istable(glonOutputObject) then return table.Copy(wire_expression_types2[objectType][2]) end + + local safeValue = {} + for i = 1, arrayLength do + safeValue[i] = tonumber(glonOutputObject[i]) or wire_expression_types2[objectType][2][i] + end + + return safeValue + end +end + +-- Attempt to load glon +if not glon and file.Exists( 'includes/modules/glon.lua', 'LUA' ) then + pcall(require,"glon") +end + +-- If glon STILL doesn't exist, don't load any of these functions +if glon then + local last_glon_error = "" + + __e2setcost(10) + + --- Encodes into a string, using [[GLON]]. + e2function string glonEncode(array data) + local ok, ret = pcall(glon.encode, data) + if not ok then + last_glon_error = ret + ErrorNoHalt("glon.encode error: "..ret) + return "" + end + + if ret then + self.prf = self.prf + #ret / 2 + end + + return ret or "" + end + + --- Decodes into an array, using [[GLON]]. + e2function array glonDecode(string data) + if not data or data == "" then return {} end + + self.prf = self.prf + #data / 2 + + local ok, ret = pcall(glon.decode, data) + + if not ok then + last_glon_error = ret + ErrorNoHalt("glon.decode error: "..ret) + return {} + end + + local safeArray = sanitizeGlonOutput( self, ret, "r" ) + -- logGlonCall( self, data, ret, safeArray ) + return safeArray or {} + end + + e2function string glonError() + return last_glon_error or "" + end + + hook.Add("InitPostEntity", "wire_expression2_glonfix", function() + -- Fixing other people's bugs... + for i = 1,20 do + local name, encode_types = debug.getupvalue(glon.Write, i) + if name == "encode_types" then + for _,tp in ipairs({"NPC","Vehicle","Weapon"}) do + if not encode_types[tp] then encode_types[tp] = encode_types.Entity end + end + break + end + end + end) + + --------------------------------------------------------------------------- + -- table glon + --------------------------------------------------------------------------- + + __e2setcost(15) + + --- Encodes into a string, using [[GLON]]. + e2function string glonEncode(table data) = e2function string glonEncode(array data) + + __e2setcost(25) + + -- decodes a glon string and returns an table + e2function table glonDecodeTable(string data) + if not data or data == "" then return table.Copy(DEFAULT) end + + self.prf = self.prf + #data / 2 + + local ok, ret = pcall(glon.decode, data) + if not ok then + last_glon_error = ret + ErrorNoHalt("glon.decode error: "..ret) + return table.Copy(DEFAULT) + end + + local safeTable = sanitizeGlonOutput( self, ret, "t" ) + return safeTable or table.Copy(DEFAULT) + end +end + +--------------------------------------------------------------------------- +-- von +--------------------------------------------------------------------------- +local last_von_error + +__e2setcost(10) + +--- Encodes into a string, using [[von]]. +e2function string vonEncode(array data) + local ok, ret = pcall(WireLib.von.serialize, data) + if not ok then + last_von_error = ret + if not antispam(self) then return "" end + WireLib.ClientError("von.encode error: "..ret, self.player) + return "" + end + + if ret then + self.prf = self.prf + #ret / 2 + end + + return ret or "" +end + +--- Decodes into an array, using [[von]]. +e2function array vonDecode(string data) + if not data or data == "" then return {} end + + self.prf = self.prf + #data / 2 + + local ok, ret = pcall(WireLib.von.deserialize, data) + + if not ok then + last_von_error = ret + if not antispam(self) then return {} end + WireLib.ClientError("von.decode error: "..ret, self.player) + return {} + end + + local safeArray = sanitizeGlonOutput( self, ret, "r" ) + return safeArray or {} +end + +__e2setcost(1) +e2function string vonError() + return last_von_error or "" +end + +__e2setcost(15) + +--- Encodes into a string, using [[von]]. +e2function string vonEncode(table data) = e2function string vonEncode(array data) + +__e2setcost(25) + +-- decodes a glon string and returns an table +e2function table vonDecodeTable(string data) + if not data or data == "" then return table.Copy(DEFAULT) end + + self.prf = self.prf + #data / 2 + + local ok, ret = pcall(WireLib.von.deserialize, data) + if not ok then + last_von_error = ret + if not antispam(self) then return table.Copy(DEFAULT) end + WireLib.ClientError("von.decode error: "..ret, self.player) + return table.Copy(DEFAULT) + end + + local safeTable = sanitizeGlonOutput( self, ret, "t" ) + return safeTable or table.Copy(DEFAULT) +end + +--------------------------------------------------------------------------- +-- json +--------------------------------------------------------------------------- + +local last_json_error + +-- this encodes the table into json +local function jsonEncode( self, data, prettyprint ) + local ok, ret = pcall(util.TableToJSON, data, prettyprint ~= 0) + if not ok then + last_json_error = ret + if not antispam(self) then return "" end + WireLib.ClientError("jsonEncode error: "..ret, self.player) + return "" + end + + if ret then + self.prf = self.prf + #ret / 2 + end + + return ret or "" +end + +-- this decodes the json string into a table +local function jsonDecode( self, data, tp ) + if not data or data == "" then return {} end + + self.prf = self.prf + #data / 2 + + local ok, ret = pcall(util.JSONToTable, data) + + if not ok then + last_json_error = ret + if not antispam(self) then return {} end + WireLib.ClientError("jsonDecode error: "..ret, self.player) + return {} + end + + local safeArray = sanitizeGlonOutput( self, ret, tp ) + return safeArray or {} +end + +__e2setcost(1) +e2function string jsonError() + return last_json_error or "" +end + +__e2setcost(50) + +-- arrays don't store their values' types, so there are many ambiguous types. +-- This function removes all values that are ambiguous +-- (basically only keeps numbers and strings) +local function jsonEncode_arrays( array ) + local luatable = {} + + for k,v in pairs( array ) do + local tp = type(v) + + if tp == "number" then + luatable[k] = v + elseif tp == "string" then + luatable[k] = v + end + end + + return luatable +end + +-- this function converts an E2 table into a Lua table (drops arrays) +local function jsonEncode_recurse( self, data, tp, copied_tables ) + local luatable = {} + copied_tables[data] = luatable + + local e2types = wire_expression_types2 + + for k,v in pairs( data.n ) do + self.prf = self.prf + 0.3 + + if data.ntypes[k] == "r" then + v = jsonEncode_arrays( v ) + elseif data.ntypes[k] == "t" then + if copied_tables[v] then + v = copied_tables[v] + else + v = jsonEncode_recurse( self, v, data.ntypes[k], copied_tables ) + end + + -- convert from E2 type to Lua type + elseif e2types[data.ntypes[k]] and e2types[data.ntypes[k]][4] then + v = e2types[data.ntypes[k]][4]( self, v ) + end + + luatable[k] = v + end + + for k,v in pairs( data.s ) do + self.prf = self.prf + 0.3 + + if data.stypes[k] == "r" then + v = jsonEncode_arrays( v ) + elseif data.stypes[k] == "t" then + if copied_tables[v] then + v = copied_tables[v] + else + v = jsonEncode_recurse( self, v, data.stypes[k], copied_tables ) + end + + -- convert from E2 type to Lua type + elseif e2types[data.stypes[k]] and e2types[data.stypes[k]][4] then + v = e2types[data.stypes[k]][4]( self, v ) + end + + luatable[k] = v + end + + return luatable +end + +local function jsonEncode_start( self, data, prettyprint ) + local copied_tables = {} + local luatable = jsonEncode_recurse( self, data, "external_t", copied_tables ) + return jsonEncode( self, luatable, prettyprint ) +end + +-- Used to encode an E2 table to a Lua table, so that it can be used by external resources properly. +e2function string jsonEncode( table data ) return jsonEncode_start( self, data, 0 ) end +e2function string jsonEncode( table data, prettyprint ) return jsonEncode_start( self, data, prettyprint ) end + +-- this function converts a lua table into an E2 table +local function jsonDecode_recurse( self, luatable, copied_tables ) + local e2table = table.Copy(DEFAULT) + + local wire_expression_types = wire_expression_types + + for k,v in pairs( luatable ) do + local typeid, v = luaTypeToWireTypeid( v ) + + -- if it's a table, recurse through it and convert all the tables it contains + if typeid == "t" then + local val = v + v = table.Copy(DEFAULT) + + if copied_tables[val] then + v = copied_tables[val] + else + v = jsonDecode_recurse( self, val, copied_tables ) + end + end + + if type(k) == "number" then + e2table.ntypes[k] = typeid + e2table.n[k] = v + e2table.size = e2table.size + 1 + elseif type(k) == "string" then + e2table.stypes[k] = typeid + e2table.s[k] = v + e2table.size = e2table.size + 1 + end + end + + self.prf = self.prf + 0.3 * e2table.size + + return e2table +end + +e2function table jsonDecode( string data ) + local luatable = jsonDecode( self, data, "external_t" ) + local copied_tables = {} + return jsonDecode_recurse( self, luatable, copied_tables ) +end + +__e2setcost(nil) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/serverinfo.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/serverinfo.lua new file mode 100644 index 0000000..08ecf3c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/serverinfo.lua @@ -0,0 +1,80 @@ +/******************************************************************************\ + Server Information +\******************************************************************************/ + +__e2setcost(1) + +e2function string map() + return game.GetMap() +end + +local hostname = GetConVar("hostname") +e2function string hostname() + return hostname:GetString() +end + +e2function string hostip() + return game.GetIPAddress() +end + +local sv_lan = GetConVar("sv_lan") +e2function number isLan() + return sv_lan:GetBool() and 1 or 0 +end + +e2function string gamemode() + return gmod.GetGamemode().Name +end + +e2function string serverUUID() + return WireLib.GetServerUUID() +end + +e2function number isSinglePlayer() + return game.SinglePlayer() and 1 or 0 +end + +e2function number isDedicated() + return game.IsDedicated() and 1 or 0 +end + +e2function number numPlayers() + return player.GetCount() +end + +e2function number maxPlayers() + return game.MaxPlayers() +end + +local sv_gravity = GetConVar("sv_gravity") +e2function number gravity() + return sv_gravity:GetFloat() +end + +e2function vector propGravity() + return physenv.GetGravity() +end + +e2function number airDensity() + return physenv.GetAirDensity() +end + +e2function number maxFrictionMass() + return physenv.GetPerformanceSettings()["MaxFrictionMass"] +end + +e2function number minFrictionMass() + return physenv.GetPerformanceSettings()["MinFrictionMass"] +end + +e2function number speedLimit() + return physenv.GetPerformanceSettings()["MaxVelocity"] +end + +e2function number angSpeedLimit() + return physenv.GetPerformanceSettings()["MaxAngularVelocity"] +end + +e2function number tickInterval() + return engine.TickInterval() +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/signal.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/signal.lua new file mode 100644 index 0000000..88d8c05 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/signal.lua @@ -0,0 +1,274 @@ +--[[ +Original idea: Gwahir +Original implementation: Gwahir +Rewrite that made it work: TomyLobo +Minor rewrite that made it work even if the player disconnected: Lexi +]] + +-- holds the currently registered signal handlers. Format: +-- scopes[uniqueID|1|2][group][name][context] = true|nil +local scopes = WireLib.containers.autocleanup:new(3) + +-- holds the currently queued signals. Format: +-- signal_queue[group][name][receiverid] = { group, name, scope, sender, senderid } +local signal_queue = WireLib.containers.autocleanup:new(2) + +-- holds the current signal data. Format: +-- currentSignal = nil|{ group, name, scope, sender, senderid } + +--[[************************************************************************]]-- + +-- executes a chip's code +local function triggerSignal(receiverid, signaldata) + local receiver = Entity(receiverid) + if not IsValid(receiver) or not receiver.Execute then return end + receiver.context.data.currentSignal = signaldata + receiver:Execute() + receiver.context.data.currentSignal = nil + if (signaldata.sender and signaldata.sender:IsValid()) then signaldata.sender.context.prf = signaldata.sender.context.prf + 80 end +end + +local timerRunning = false + +local function checkSignals() + timerRunning = false + + -- make a copy of the signal queue so we can add new signals safely while iterating through the existing array + local queue = signal_queue + + -- clear the signal queue + signal_queue = WireLib.containers.autocleanup:new(2) + + -- loop through all queued signal groups + for group,signals in pairs_ac(queue) do + -- loop through all queued signals in the group + for name,receivers in pairs_ac(signals) do + -- ... and all receivers + for receiverid,signaldata in pairs_ac(receivers) do + -- and trigger all signals on the queue + triggerSignal(receiverid, signaldata) + end + end + end + +end + +-- queues a chip's code for execution after at most +local function postSignal(receiverid, group, name, scope, sender, senderid) + -- don't send the signal back to the sender + if senderid == receiverid then return end + + -- add the given signal to the queue + signal_queue[group][name][receiverid] = { group, name, scope, sender, senderid } + + -- set a timer if it isnt already set + if not timerRunning then + timerRunning = true + timer.Simple(0.01, checkSignals) + end +end + +-- Sends the given signal group/name combination to everyone listening +-- Note: filter_player is a UNIQUE ID! +local function broadcastSignal(group, name, scope, sender, filter_player) + + local sender_player = sender.uid + + -- scope 0 => read from scopes[sender.uid] + -- scope 1/2 => read from scopes[scope] + local contexts = scopes[scope == 0 and sender_player or scope][group][name] + + -- there was no signal registered for the selected scope/group/name combination. + if not contexts then return end + + local senderid = sender:EntIndex() + + for receiverid,_ in pairs_ac(contexts) do + local receiver_player = Entity(receiverid).uid + if (not filter_player or receiver_player == filter_player) and (scope ~= 2 or receiver_player ~= sender_player) then + postSignal(receiverid, group, name, scope, sender, senderid) + end + end +end + +--local function table_IsEmpty(t) return not pairs(t)(t) end +local function table_IsEmpty(t) return not next(t) end + +local function setGroup(self, group) + -- set the current group to the new group + self.data.signalgroup = group +end + +--[[************************************************************************]]-- + +__e2setcost(5) + +--- Sets the E-2's current signal group to , this is applied during runOnSignal, signalSend, and signalSetOnRemove calls, so call it first. +e2function void signalSetGroup(string group) + setGroup(self, group) +end + +--- Gets the E-2's current signal group +e2function string signalGetGroup() + return self.data.signalgroup +end + +__e2setcost(5) + +--- If == 0 the chip will no longer run on this signal, otherwise it makes this chip execute when signal is sent by someone in scope . +e2function void runOnSignal(string name, scope, activate) + -- sanitize inputs + --if scope >= 3 or scope < 0 then return end + scope = math.Clamp(math.floor(scope), 0, 2) + + -- process inputs + activate = activate ~= 0 or nil + if scope == 0 then scope = self.uid end + + -- (un-)register signal + scopes[scope][self.data.signalgroup][name][self.entity:EntIndex()] = activate +end + +--[[************************************************************************]]-- + +__e2setcost(1) + +--- Returns 1 if the chip was executed because of any signal, regardless of name, group or scope. Returns 0 otherwise. +e2function number signalClk() + if not self.data.currentSignal then return 0 end + return self.data.currentSignal and 1 or 0 +end + +--- Returns 1 if the chip was executed because the signal was sent, regardless of group or scope. Returns 0 otherwise. +e2function number signalClk(string name) + if not self.data.currentSignal then return 0 end + local currentSignal = self.data.currentSignal + return (currentSignal and currentSignal[2] == name) and 1 or 0 +end + +--- Returns 1 if the chip was executed because the signal was sent to the scope , regardless of group. Returns 0 otherwise. +e2function number signalClk(string name, scope) + if not self.data.currentSignal then return 0 end + local currentSignal = self.data.currentSignal + return (currentSignal and currentSignal[2] == name and currentSignal[3] == scope) and 1 or 0 +end + +--- Returns 1 if the chip was executed because the signal was sent in the group , regardless of scope. Returns 0 otherwise. +e2function number signalClk(string group, string name) + if not self.data.currentSignal then return 0 end + local currentSignal = self.data.currentSignal + return (currentSignal and currentSignal[1] == group and currentSignal[2] == name) and 1 or 0 +end + +--- Returns 1 if the chip was executed because the signal was sent in the group to the scope . Returns 0 otherwise. +e2function number signalClk(string group, string name, scope) + if not self.data.currentSignal then return 0 end + local currentSignal = self.data.currentSignal + return (currentSignal and currentSignal[1] == group and currentSignal[2] == name and currentSignal[3] == scope) and 1 or 0 +end + +__e2setcost(4) + +--- Returns the name of the received signal. +e2function string signalName() + if not self.data.currentSignal then return "" end + return self.data.currentSignal[2] +end + +--- Returns the group name of the received signal. +e2function string signalGroup() + if not self.data.currentSignal then return "" end + return self.data.currentSignal[1] +end + +--- Returns the entity of the chip that sent the signal. +e2function entity signalSender() + if not self.data.currentSignal then return nil end + return self.data.currentSignal[4] +end + +--- Returns the entity ID of the chip that sent the signal. Useful if the entity doesn't exist anymore. +e2function number signalSenderId() + if not self.data.currentSignal then return 0 end + return self.data.currentSignal[5] +end + +--[[************************************************************************]]-- + +__e2setcost(10) + +--- Sets the signal that the chip sends when it is removed from the world. +e2function void signalSetOnRemove(string name, scope) + self.data.removeSignal = { self.data.signalgroup, name, scope, self.entity } +end + +--- Clears the signal that the chip sends when it is removed from the world. +e2function void signalClearOnRemove() + self.data.removeSignal = nil +end + +__e2setcost(20) + +--- Sends signal to scope . Additional calls to this function with the same signal will overwrite the old call until the signal is issued. +e2function void signalSend(string name, scope) + broadcastSignal(self.data.signalgroup, name, scope, self.entity) +end + +__e2setcost(10) + +--- Sends signal S to the given chip. Multiple calls for different chips do not overwrite each other. +e2function void signalSendDirect(string name, entity receiver) + if not IsValid(receiver) then return end + + local receiverid = receiver:EntIndex() + + -- filter out non-E2 entities + if not receiver.context then return end + + -- dont send back to ourselves + if receiver.context == self then return end + + local group = self.data.signalgroup + + -- check whether the target entity accepts signals from the "anyone" scope. + if not scopes[1][group][name][receiverid] then return end + + -- send the signal + postSignal(receiverid, group, name, 1, self.entity, self.entity:EntIndex()) +end + +__e2setcost(20) + +--- sends signal S to chips owned by the given player, multiple calls for different players do not overwrite each other +e2function void signalSendToPlayer(string name, entity player) + if not IsValid(player) then return end + broadcastSignal(self.data.signalgroup, name, 1, self.entity, player) +end + +--[[************************************************************************]]-- + +registerCallback("construct",function(self) + -- set a default group + setGroup(self, "default") +end) + +registerCallback("destruct",function(self) + -- loop through all scopes, ... + for scope,groups in pairs_ac(scopes) do + -- ... all groups ... + for group, signals in pairs_ac(groups) do + -- ... and all signals ... + for name, contexts in pairs_ac(signals) do + -- to remove all signals the chip registered for. + contexts[self] = nil + end + end + end + + -- broadcast the on-remove signal, if one was registered + if self.data.removeSignal then broadcastSignal(unpack(self.data.removeSignal)) end + + -- clean up (not actually necessary since the context is destroyed anyway) + self.data.signalgroup = nil + self.data.removeSignal = nil +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/sound.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/sound.lua new file mode 100644 index 0000000..90f1dd5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/sound.lua @@ -0,0 +1,224 @@ +/******************************************************************************\ + Built-in Sound support v1.18 +\******************************************************************************/ + +E2Lib.RegisterExtension("sound", true, "Allows E2s to play sounds.", "Sounds can be played out of arbitrary entities, including other players.") + +local wire_expression2_maxsounds = CreateConVar( "wire_expression2_maxsounds", 16, {FCVAR_ARCHIVE} ) +local wire_expression2_sound_burst_max = CreateConVar( "wire_expression2_sound_burst_max", 8, {FCVAR_ARCHIVE} ) +local wire_expression2_sound_burst_rate = CreateConVar( "wire_expression2_sound_burst_rate", 0.1, {FCVAR_ARCHIVE} ) + +--------------------------------------------------------------- +-- Helper functions +--------------------------------------------------------------- + +local function isAllowed( self ) + local data = self.data.sound_data + local count = data.count + if count == wire_expression2_maxsounds:GetInt() then return false end + + if data.burst == 0 then return false end + + data.burst = data.burst - 1 + + local timerid = "E2_sound_burst_count_" .. self.entity:EntIndex() + if not timer.Exists( timerid ) then + timer.Create( timerid, wire_expression2_sound_burst_rate:GetFloat(), 0, function() + if not IsValid( self.entity ) then + timer.Remove( timerid ) + return + end + + data.burst = data.burst + 1 + if data.burst == wire_expression2_sound_burst_max:GetInt() then + timer.Remove( timerid ) + end + end) + end + + return true +end + +local function getSound( self, index ) + if isnumber( index ) then index = math.floor( index ) end + return self.data.sound_data.sounds[index] +end + +local function soundStop(self, index, fade) + local sound = getSound( self, index ) + if not sound then return end + + fade = math.abs( fade ) + + if fade == 0 then + sound:Stop() + + if isnumber( index ) then index = math.floor( index ) end + self.data.sound_data.sounds[index] = nil + + self.data.sound_data.count = self.data.sound_data.count - 1 + else + sound:FadeOut( fade ) + + timer.Simple( fade, function() soundStop( self, index, 0 ) end) + end + + timer.Remove( "E2_sound_stop_" .. self.entity:EntIndex() .. "_" .. index ) +end + +local function soundCreate(self, entity, index, time, path, fade) + if path:match('["?]') then return end + local data = self.data.sound_data + if not isAllowed( self ) then return end + + path = path:Trim() + path = path:gsub( "\\", "/" ) + if isnumber( index ) then index = math.floor( index ) end + + local timerid = "E2_sound_stop_" .. self.entity:EntIndex() .. "_" .. index + + local sound = getSound( self, index ) + if sound then + sound:Stop() + timer.Remove( timerid ) + else + data.count = data.count + 1 + end + + local filter = RecipientFilter() + filter:AddAllPlayers() + local sound = CreateSound( entity, path, filter ) + data.sounds[index] = sound + sound:Play() + + entity:CallOnRemove( "E2_stopsound", function() + soundStop( self, index, 0 ) + end ) + + if time == 0 and fade == 0 then return end + time = math.abs( time ) + + timer.Create( timerid, time, 1, function() + if not self or not IsValid( self.entity ) or not IsValid( entity ) then return end + + soundStop( self, index, fade ) + end) +end + +local function soundPurge( self ) + local sound_data = self.data.sound_data + if sound_data.sounds then + for k,v in pairs( sound_data.sounds ) do + v:Stop() + timer.Remove( "E2_sound_stop_" .. self.entity:EntIndex() .. "_" .. k ) + end + end + + sound_data.sounds = {} + sound_data.count = 0 +end + +--------------------------------------------------------------- +-- Play functions +--------------------------------------------------------------- + +__e2setcost(25) + +e2function void soundPlay( index, duration, string path ) + soundCreate(self,self.entity,index,duration,path,0) +end + +e2function void entity:soundPlay( index, duration, string path) + if not IsValid(this) or not isOwner(self, this) then return end + soundCreate(self,this,index,duration,path,0) +end + +e2function void soundPlay( index, duration, string path, fade ) + soundCreate(self,self.entity,index,duration,path,fade) +end + +e2function void entity:soundPlay( index, duration, string path, fade ) + if not IsValid(this) or not isOwner(self, this) then return end + soundCreate(self,this,index,duration,path,fade) +end + +e2function void soundPlay( string index, duration, string path ) = e2function void soundPlay( index, duration, string path ) +e2function void entity:soundPlay( string index, duration, string path ) = e2function void entity:soundPlay( index, duration, string path ) +e2function void soundPlay( string index, duration, string path, fade ) = e2function void soundPlay( index, duration, string path, fade ) +e2function void entity:soundPlay( string index, duration, string path, fade ) = e2function void entity:soundPlay( index, duration, string path, fade ) + +--------------------------------------------------------------- +-- Modifier functions +--------------------------------------------------------------- + +__e2setcost(5) + +e2function void soundStop( index ) + soundStop(self, index, 0) +end + +e2function void soundStop( index, fadetime ) + soundStop(self, index, fadetime) +end + +e2function void soundVolume( index, volume ) + local sound = getSound( self, index ) + if not sound then return end + + sound:ChangeVolume( math.Clamp( volume, 0, 1 ), 0 ) +end + +e2function void soundVolume( index, volume, fadetime ) + local sound = getSound( self, index ) + if not sound then return end + + sound:ChangeVolume( math.Clamp( volume, 0, 1 ), math.abs( fadetime ) ) +end + + +e2function void soundPitch( index, pitch ) + local sound = getSound( self, index ) + if not sound then return end + + sound:ChangePitch( math.Clamp( pitch, 0, 255 ), 0 ) +end + +e2function void soundPitch( index, pitch, fadetime ) + local sound = getSound( self, index ) + if not sound then return end + + sound:ChangePitch( math.Clamp( pitch, 0, 255 ), math.abs( fadetime ) ) +end + + +e2function void soundStop( string index ) = e2function void soundStop( index ) +e2function void soundStop( string index, fadetime ) = e2function void soundStop( index, fadetime ) +e2function void soundVolume( string index, volume ) = e2function void soundVolume( index, volume ) +e2function void soundVolume( string index, volume, fadetime ) = e2function void soundVolume( index, volume, fadetime ) +e2function void soundPitch( string index, pitch ) = e2function void soundPitch( index, pitch ) +e2function void soundPitch( string index, pitch, fadetime ) = e2function void soundPitch( index, pitch, fadetime ) + +--------------------------------------------------------------- +-- Other +--------------------------------------------------------------- + +e2function void soundPurge() + soundPurge( self ) +end + +e2function number soundDuration(string sound) + return SoundDuration(sound) or 0 +end + +--------------------------------------------------------------- + +registerCallback("construct", function(self) + self.data.sound_data = {} + self.data.sound_data.burst = wire_expression2_sound_burst_max:GetInt() + self.data.sound_data.sounds = {} + self.data.sound_data.count = 0 +end) + +registerCallback("destruct", function(self) + soundPurge( self ) +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/steamidconv.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/steamidconv.lua new file mode 100644 index 0000000..cafa1ba --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/steamidconv.lua @@ -0,0 +1,15 @@ +local util = util +local util_SteamIDFrom64 = util.SteamIDFrom64 +local util_SteamIDTo64 = util.SteamIDTo64 + +__e2setcost(5) -- approximated + +--- Given a 64bit SteamID will return a STEAM_0 style Steam ID. +e2function string steamIDFrom64(string community_id) + return util_SteamIDFrom64(community_id) +end + +--- Given a STEAM_0 style Steam ID will return a 64bit Steam ID. +e2function string steamIDTo64(string id) + return util_SteamIDTo64(id) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/strfunc.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/strfunc.lua new file mode 100644 index 0000000..1fda019 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/strfunc.lua @@ -0,0 +1,101 @@ +local function nicename( word ) + local ret = word:lower() + if ret == "normal" then return "number" end + return ret +end + +local function checkFuncName( self, funcname ) + if self.funcs[funcname] then + return self.funcs[funcname], self.funcs_ret[funcname] + elseif wire_expression2_funcs[funcname] then + return wire_expression2_funcs[funcname][3], wire_expression2_funcs[funcname][2] + end +end + +registerCallback("construct", function(self) self.strfunc_cache = {} end) + +local insert = table.insert +local concat = table.concat +local function findFunc( self, funcname, typeids, typeids_str ) + local func, func_return_type, vararg + + self.prf = self.prf + 40 + + local str = funcname .. "(" .. typeids_str .. ")" + for i=1,#self.strfunc_cache do + local t = self.strfunc_cache[i] + if t[1] == str then + return t[2], t[3], t[4] + end + end + + self.prf = self.prf + 40 + + if #typeids > 0 then + if not func then + func, func_return_type = checkFuncName( self, str ) + end + + if not func then + func, func_return_type = checkFuncName( self, funcname .. "(" .. typeids[1] .. ":" .. concat(typeids,"",2) .. ")" ) + end + + if not func then + for i=#typeids,1,-1 do + func, func_return_type = checkFuncName( self, funcname .. "(" .. concat(typeids,"",1,i) .. "...)" ) + if func then vararg = true break end + end + + if not func then + func, func_return_type = checkFuncName( self, funcname .. "(...)" ) + if func then vararg = true end + end + end + + if not func then + for i=#typeids,2,-1 do + func, func_return_type = checkFuncName( self, funcname .. "(" .. typeids[1] .. ":" .. concat(typeids,"",2,i) .. "...)" ) + if func then vararg = true break end + end + + if not func then + func, func_return_type = checkFuncName( self, funcname .. "(" .. typeids[1] .. ":...)" ) + if func then vararg = true end + end + end + else + func, func_return_type = checkFuncName( self, funcname .. "()" ) + end + + if func then + local t = { str, func, func_return_type, vararg } + insert( self.strfunc_cache, 1, t ) + if #self.strfunc_cache == 21 then self.strfunc_cache[21] = nil end + end + + return func, func_return_type +end + +__e2setcost(20) + +registerOperator( "stringcall", "", "", function(self, args) + local op1, funcargs, typeids, typeids_str, returntype = args[2], args[3], args[4], args[5], args[6] + local funcname = op1[1](self,op1) + + local func, func_return_type = findFunc( self, funcname, typeids, typeids_str ) + + if not func then error( "No such function: " .. funcname .. "(" .. tps_pretty( typeids_str ) .. ")", 0 ) end + + if returntype ~= "" and func_return_type ~= returntype then + error( "Mismatching return types. Got " .. nicename(wire_expression_types2[returntype][1]) .. ", expected " .. nicename(wire_expression_types2[func_return_type][1] ), 0 ) + end + + self.prf = self.prf + 40 + + if returntype ~= "" then + local ret = func( self, funcargs ) + return ret + else + func( self, funcargs ) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/string.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/string.lua new file mode 100644 index 0000000..c837d46 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/string.lua @@ -0,0 +1,584 @@ +/******************************************************************************\ + String support +\******************************************************************************/ + +// TODO: is string.left() faster than s:left()? +// TODO: is string.sub faster than both left and right? +// TODO: these return bad results when used with negative numbers! +// TODO: benchmarks! + +local string = string -- optimization + +/******************************************************************************/ + +registerType("string", "s", "", + nil, + nil, + function(retval) + if !isstring(retval) then error("Return value is not a string, but a "..type(retval).."!",0) end + end, + function(v) + return !isstring(v) + end +) + +/******************************************************************************/ + +__e2setcost(3) -- temporary + +registerOperator("ass", "s", "s", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ + +registerOperator("is", "s", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + + return rv1 ~= "" and 1 or 0 +end) + +registerOperator("eq", "ss", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + return rv1 == rv2 and 1 or 0 +end) + +registerOperator("neq", "ss", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + return rv1 ~= rv2 and 1 or 0 +end) + +registerOperator("geq", "ss", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + math.min(#rv1, #rv2) / 10 + + return rv1 >= rv2 and 1 or 0 +end) + +registerOperator("leq", "ss", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + math.min(#rv1, #rv2) / 10 + + return rv1 <= rv2 and 1 or 0 +end) + +registerOperator("gth", "ss", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + math.min(#rv1, #rv2) / 10 + + return rv1 > rv2 and 1 or 0 +end) + +registerOperator("lth", "ss", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + math.min(#rv1, #rv2) / 10 + + return rv1 < rv2 and 1 or 0 +end) + +/******************************************************************************/ + +__e2setcost(10) -- temporary + +registerOperator("add", "ss", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv1*0.01 + #rv2*0.01 + + return rv1 .. rv2 +end) + +/******************************************************************************/ + +registerOperator("add", "sn", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv1*0.01 + + return rv1 .. tostring(rv2) +end) + +registerOperator("add", "ns", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv2*0.01 + + return tostring(rv1) .. rv2 +end) + +/******************************************************************************/ + +registerOperator("add", "sv", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv1*0.01 + + return ("%s[%s,%s,%s]"):format( rv1, rv2[1], rv2[2], rv2[3] ) +end) + +registerOperator("add", "vs", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv2*0.01 + + return ("[%s,%s,%s]%s"):format( rv1[1],rv1[2],rv1[3],rv2) +end) + +/******************************************************************************/ + +registerOperator("add", "sa", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv1*0.01 + + return ("%s[%s,%s,%s]"):format( rv1,rv2[1],rv2[2],rv2[3] ) +end) + +registerOperator("add", "as", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + + self.prf = self.prf + #rv2*0.01 + + return ("[%s,%s,%s]%s"):format( rv1[1],rv1[2],rv1[3],rv2) +end) + +/******************************************************************************/ + +__e2setcost(20) -- temporary + +e2function number string:toNumber() + local ret = tonumber(this) + if ret == nil then return 0 end + return ret +end + +e2function number string:toNumber(number base) + local ret = tonumber(this, base) + if ret == nil then return 0 end + return ret +end + +local string_char = string.char +local string_byte = string.byte +local string_len = string.len +local utf8_char = utf8.char +local utf8_byte = utf8.codepoint + +registerFunction("toChar", "n", "s", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 < 0 then return "" end + if rv1 > 255 then return "" end + return string_char(rv1) +end) + +registerFunction("toByte", "s", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 == "" then return -1 end + return string_byte(rv1) +end) + +registerFunction("toByte", "sn", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv2 < 1 || rv2 > string_len(rv1) then return -1 end + return string_byte(rv1, rv2) +end) + +local math_floor = math.floor + +registerFunction("toUnicodeChar", "n", "s", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + + -- upper limit used to be 2097152, new limit acquired using pcall and a for loop + -- above this limit, the function causes a lua error + if rv1 < 1 or rv1 > 1114112 then return "" end + + return utf8_char(rv1) +end) + +registerFunction("toUnicodeByte", "s", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1 == "" then return -1 end + + return utf8_byte(rv1) +end) + +/******************************************************************************/ + +registerFunction("index", "s:n", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1:sub(rv2, rv2) +end) + +registerFunction("left", "s:n", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1:Left(rv2) +end) + +registerFunction("right", "s:n", "s", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1:Right(rv2) +end) + +registerFunction("sub", "s:nn", "s", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + return rv1:sub(rv2, rv3) +end) + +e2function string string:sub(start) + return this:sub(start) +end + +e2function string string:operator[](index) + return this:sub(index,index) +end + +registerFunction("upper", "s:", "s", function(self, args) + local op1 = args[2], args[3] + local rv1 = op1[1](self, op1) + return rv1:upper() +end) + +registerFunction("lower", "s:", "s", function(self, args) + local op1 = args[2], args[3] + local rv1 = op1[1](self, op1) + return rv1:lower() +end) + +registerFunction("length", "s:", "n", function(self, args) + local op1 = args[2], args[3] + local rv1 = op1[1](self, op1) + return rv1:len() +end) + +registerFunction("unicodeLength", "s:", "n", function(self, args) + local op1 = args[2], args[3] + local rv1 = op1[1](self, op1) + -- the string.gsub method is inconsistent with how writeUnicodeString and toUnicodeByte handles badly-formed sequences. + -- local _, length = string.gsub (rv1, "[^\128-\191]", "") + local length = 0 + local i = 1 + while i <= #rv1 do + local byte = string_byte (rv1, i) + if byte >= 240 then + i = i + 4 + elseif byte >= 224 then + i = i + 3 + elseif byte >= 192 then + i = i + 2 + else + i = i + 1 + end + length = length + 1 + end + self.prf = self.prf + length * 0.1 + return length +end) + +/******************************************************************************/ + +registerFunction("repeat", "s:n", "s", function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), math.abs(op2[1](self, op2)) + self.prf = self.prf + #rv1 * rv2 * 0.01 + return rv1:rep(rv2) +end) + +registerFunction("trim", "s:", "s", function(self,args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return string.Trim(rv1) +end) + +registerFunction("trimLeft", "s:", "s", function(self,args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1:match( "^ *(.-)$") +end) + +registerFunction("trimRight", "s:", "s", function(self,args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1:TrimRight() +end) + +/******************************************************************************/ + + +local sub = string.sub +local gsub = string.gsub +local find = string.find + +--- Returns the 1st occurrence of the string , returns 0 if not found. Prints malformed string errors to the chat area. +e2function number string:findRE(string pattern) + local OK, Ret = pcall(string.find, this, pattern) + if not OK then + self.player:ChatPrint(Ret) + return 0 + else + return Ret or 0 + end +end + +--- Returns the 1st occurrence of the string starting at and going to the end of the string, returns 0 if not found. Prints malformed string errors to the chat area. +e2function number string:findRE(string pattern, start) + local OK, Ret = pcall(find, this, pattern, start) + if not OK then + self.player:ChatPrint(Ret) + return 0 + else + return Ret or 0 + end +end + +--- Returns the 1st occurrence of the string , returns 0 if not found. Does not use LUA patterns. +e2function number string:find(string needle) + return this:find( needle, 1, true) or 0 +end + +--- Returns the 1st occurrence of the string starting at and going to the end of the string, returns 0 if not found. Does not use LUA patterns. +e2function number string:find(string needle, start) + return this:find( needle, start, true) or 0 +end + +--- Finds and replaces every occurrence of with without regular expressions +e2function string string:replace(string needle, string new) + if needle == "" then return this end + return this:Replace( needle, new) +end + +--- Finds and replaces every occurrence of with using regular expressions. Prints malformed string errors to the chat area. +e2function string string:replaceRE(string pattern, string new) + local OK, NewStr = pcall(gsub, this, pattern, new) + if not OK then + self.player:ChatPrint(NewStr) + return "" + else + return NewStr or "" + end +end + +__e2setcost(5) + +--- Splits the string into an array, along the boundaries formed by the string . See also [[string.Explode]] +local string_Explode = string.Explode +e2function array string:explode(string delim) + local ret = string_Explode( delim, this ) + self.prf = self.prf + #ret * 0.3 + #this * 0.1 + return ret +end + +e2function array string:explodeRE( string delim ) + local ret = string_Explode( delim, this, true ) + self.prf = self.prf + #ret * 0.3 + #this * 0.1 + return ret +end + +__e2setcost(10) + +--- Returns a reversed version of +e2function string string:reverse() + return this:reverse() +end + +/******************************************************************************/ +local string_format = string.format +local gmatch = string.gmatch +local Right = string.Right + +--- Formats a values exactly like Lua's [http://www.lua.org/manual/5.1/manual.html#pdf-string.format string.format]. Any number and type of parameter can be passed through the "...". Prints errors to the chat area. +e2function string format(string fmt, ...) + -- TODO: call toString for table-based types + local ok, ret = pcall(string_format, fmt, ...) + if not ok then + self.player:ChatPrint(ret) + return "" + end + return ret +end + +/******************************************************************************/ +-- string.match wrappers by Jeremydeath, 2009-08-30 +local string_match = string.match +local table_remove = table.remove + +--- runs [[string.match]](, ) and returns the sub-captures as an array. Prints malformed pattern errors to the chat area. +e2function array string:match(string pattern) + local args = {pcall(string_match, this, pattern)} + if not args[1] then + self.player:ChatPrint(args[2] or "Unknown error in str:match") + return {} + else + table_remove( args, 1 ) -- Remove "OK" boolean + return args or {} + end +end + +--- runs [[string.match]](, , ) and returns the sub-captures as an array. Prints malformed pattern errors to the chat area. +e2function array string:match(string pattern, position) + local args = {pcall(string_match, this, pattern, position)} + if not args[1] then + self.player:ChatPrint(args[2] or "Unknown error in str:match") + return {} + else + table_remove( args, 1 ) -- Remove "OK" boolean + return args or {} + end +end + +local table_Copy = table.Copy + +-- Helper function for gmatch (below) +-- (By Divran) +local DEFAULT = {n={},ntypes={},s={},stypes={},size=0,istable=true,depth=0} +local function gmatch( self, this, pattern ) + local ret = table_Copy( DEFAULT ) + local num = 0 + local iter = this:gmatch( pattern ) + local v + while true do + v = {iter()} + if (!v or #v==0) then break end + num = num + 1 + ret.n[num] = v + ret.ntypes[num] = "r" + end + self.prf = self.prf + num + ret.size = num + return ret +end + +--- runs [[string.gmatch]](, ) and returns the captures in an array in a table. Prints malformed pattern errors to the chat area. +-- (By Divran) +e2function table string:gmatch(string pattern) + local OK, ret = pcall( gmatch, self, this, pattern ) + if (!OK) then + self.player:ChatPrint( ret or "Unknown error in str:gmatch" ) + return table_Copy( DEFAULT ) + else + return ret + end +end + +--- runs [[string.gmatch]](, , ) and returns the captures in an array in a table. Prints malformed pattern errors to the chat area. +-- (By Divran) +e2function table string:gmatch(string pattern, position) + this = this:Right( -position-1 ) + local OK, ret = pcall( gmatch, self, this, pattern ) + if (!OK) then + self.player:ChatPrint( ret or "Unknown error in str:gmatch" ) + return table_Copy( DEFAULT ) + else + return ret + end +end + +--- runs [[string.match]](, ) and returns the first match or an empty string if the match failed. Prints malformed pattern errors to the chat area. +e2function string string:matchFirst(string pattern) + local OK, Ret = pcall(string_match, this, pattern) + if not OK then + self.player:ChatPrint(Ret) + return "" + else + return Ret or "" + end +end + +--- runs [[string.match]](, , ) and returns the first match or an empty string if the match failed. Prints malformed pattern errors to the chat area. +e2function string string:matchFirst(string pattern, position) + local OK, Ret = pcall(string_match, this, pattern, position) + if not OK then + self.player:ChatPrint(Ret) + return "" + else + return Ret or "" + end +end + +/******************************************************************************/ +local unpack = unpack +local isnumber = isnumber +local utf8_len = utf8.len + +local function ToUnicodeChar(self, args) + local count = #args + if count == 0 then return "" end + local codepoints = {} + for i = 1, count do + local value = args[i] + if isnumber(value) then + value = math_floor(value) + if 0 <= value and value <= 0x10FFFF then + codepoints[#codepoints + 1] = value + end + end + end + self.prf = self.prf + count * 0.001 + return utf8_char(unpack(codepoints)) +end + +__e2setcost(1) + +--- Returns the UTF-8 string from the given Unicode code-points. +e2function string toUnicodeChar(...) + return ToUnicodeChar(self, { ... }) +end + +--- Returns the UTF-8 string from the given Unicode code-points. +e2function string toUnicodeChar(array args) + return ToUnicodeChar(self, args) +end + +--- Returns the Unicode code-points from the given UTF-8 string. +e2function array string:toUnicodeByte(number startPos, number endPos) + if #this == 0 then return {} end + local codepoints = { pcall(utf8_byte, this, startPos, endPos) } + local ok = table.remove(codepoints, 1) + if not ok then return {} end + self.prf = self.prf + #codepoints * 0.001 + return codepoints +end + +--- Returns the length of the given UTF-8 string. +e2function number string:unicodeLength(number startPos, number endPos) + if #this == 0 then return 0 end + local ok, length = pcall(utf8_len, this, startPos, endPos) + if ok and isnumber(length) then + self.prf = self.prf + length * 0.001 + return length + end + self.prf = self.prf + #this * 0.001 + return -1 +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/table.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/table.lua new file mode 100644 index 0000000..b654031 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/table.lua @@ -0,0 +1,1275 @@ +---------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Formerly known as "mtable", this extension has now (15-11-2010) replaced the old table extension. +-- Made by Divran +---------------------------------------------------------------------------------------------------------------------------------------------------------------- +local function IsEmpty( t ) return !next(t) end +local rep = string.rep +local tostring = tostring +local table = table +local type = type + +local opcost = 1/3 -- cost of looping through table multiplier + +-- All different table types in E2 +local tbls = { + r = true, + t = true, + xgt = true, +} + +-- Types not allowed in tables +local blocked_types = { + xgt = true, +} + +-------------------------------------------------------------------------------- + +local function checkOwner(self) + return IsValid(self.player); +end + + +-------------------------------------------------------------------------------- +-- Type defining +-------------------------------------------------------------------------------- + +local DEFAULT = {n={},ntypes={},s={},stypes={},size=0} + +registerType("table", "t", table.Copy(DEFAULT), + function(self, input) + if input.size == 0 then + return table.Copy(DEFAULT) + end + return input + end, + nil, + function(retval) + if not istable(retval) then error("Return value is not a table, but a "..type(retval).."!", 0) end + end, + function(v) + return not istable(v) + end +) + +local formatPort = WireLib.Debugger.formatPort +local function temp( ret, tbl, k, v, orientvertical, isnum ) + local id + if (isnum) then + id = tbl.ntypes[k] + else + id = tbl.stypes[k] + end + + if (isnum) then + ret = ret .. k .. "=" + else + ret = ret .. '"' .. k .. '"' .. "=" + end + + local longtype = wire_expression_types2[id][1] + + if (tbls[id] == true) then + if (id == "xgt") then + ret = ret .. "wtf how did this get here" + else + if (id == "r") then + ret = ret .. "Array with " .. #v .. " elements" + elseif (id == "t") then + ret = ret .. "Table with " .. v.size .. " elements" + else + ret = ret .. "Error! Should never get down here!" + end + end + else + ret = ret .. formatPort[longtype]( v, orientvertical ) + end + + if (orientvertical) then + ret = ret .. "\n" + else + ret = ret .. ", " + end + + return ret +end + +WireLib.registerDebuggerFormat( "table", function( value, orientvertical ) + if not value.n or not value.s then return "{}" end + local ret = "" + local n = 0 + for k2,v2 in pairs( value.n ) do + n = n + 1 + if (n > 7) then break end + ret = temp( ret, value, k2, v2, orientvertical, true ) + end + for k3, v3 in pairs( value.s ) do + n = n + 1 + if (n > 7) then break end + ret = temp( ret, value, k3, v3, orientvertical, false ) + end + ret = ret:Left(-3) + return "{" .. ret .. "}" +end) + +-------------------------------------------------------------------------------- +-- Helper functions +-------------------------------------------------------------------------------- + +-- Fix default values +local function fixdef( def ) + return istable(def) and table.Copy(def) or def +end + +-- Uppercases the first letter +local function upperfirst( word ) + return word:Left(1):upper() .. word:Right(-2):lower() +end + +---------------------------------- +-- tostrings +---------------------------------- + +-- for invert(R) and printTable and T:toString() +local tostrings = { + number=tostring, + string=tostring, + Entity=tostring, + Weapon=tostring, + Player=tostring, + Vehicle=tostring, + NPC=tostring, + PhysObj=e2_tostring_bone, + ["nil"] = function() return "(null)" end +} + +function tostrings.table(t) + return "["..table.concat(t, ",").."]" +end + +function tostrings.Vector2(v) + return ("[%s,%s]"):format(v[1],v[2]) +end + +function tostrings.Vector(v) + return ("[%s,%s,%s]"):format(v[1],v[2],v[3]) +end + +function tostrings.Vector4(v) + return ("[%s,%s,%s,%s]"):format(v[1],v[2],v[3],v[4]) +end + +-- for invert(T) +local tostring_typeid = { + c = formatPort.COMPLEX, + b = tostring, + e = tostring, + xwl = tostring, + xrd = tostring, + n = tostring, + q = formatPort.QUATERNION, + s = function(s) return s end, + r = tostring, + xm4 = tostrings.table, + t = tostring, + v = tostrings.Vector, + m = tostrings.table, + xv2 = tostrings.Vector2, + xm2 = tostrings.table, + a = tostrings.Vector, + xv4 = tostrings.Vector4, +} + +local function checkAbort( ret, cost, abortafter ) + if abortafter and cost > abortafter then + if ret[#ret] ~= "\n- Aborted to prevent lag -" then + ret[#ret+1] = "\n- Aborted to prevent lag -" + end + return true + end + + return false +end + +local function normal_table_tostring( tbl, indenting, abortafter, cost ) + local ret = {} + local cost = cost or 0 + for k,v in pairs( tbl ) do + if tostrings[type(v)] then + ret[#ret+1] = rep("\t",indenting) .. k .. "\t=\t" .. tostrings[type(v)]( v ) .. "\n" + cost = cost + 1 + else + ret[#ret+1] = rep("\t",indenting) .. k .. "\t=\t" .. tostring(v) .. "\n" + cost = cost + 1 + end + + if checkAbort( ret, cost, abortafter ) then return table.concat(ret), cost end + end + return table.concat(ret), cost +end + +local table_tostring + +local function var_tostring( k, v, typeid, indenting, printed, abortafter, cost ) + local ret = "" + local cost = (cost or 0) + 1 + if (typeid == "t" and not printed[v]) then -- If it's a table + printed[v] = true + ret = rep("\t",indenting) .. k .. ":\n" + local ret2, cost2 = table_tostring( v, indenting + 2, printed, abortafter, cost ) + ret = ret .. ret2 + cost = cost + cost2 + elseif typeid == "r" and not printed[v] then -- if it's an array + printed[v] = true + ret = rep("\t",indenting) .. k .. ":\n" + local ret2, cost2 = normal_table_tostring( v, indenting + 2, abortafter, cost ) + ret = ret .. ret2 + cost = cost2 + elseif tostring_typeid[typeid] then -- if it's a type defined in this table + ret = rep("\t",indenting) .. k .. "\t=\t" .. tostring_typeid[typeid]( v ) .. "\n" + else -- if it's anything else + ret = rep("\t",indenting) .. k .. "\t=\t" .. tostring(v) .. "\n" + end + return ret, cost +end + +table_tostring = function( tbl, indenting, printed, abortafter, cost ) + local ret = {} + local cost = cost or 0 + for k,v in pairs( tbl.n ) do + if checkAbort( ret, cost, abortafter ) then return table.concat(ret), cost end + local ret2, cost2 = var_tostring( k, v, tbl.ntypes[k], indenting, printed, abortafter, cost ) + ret[#ret+1] = ret2 + cost = cost2 + end + for k,v in pairs( tbl.s ) do + if checkAbort( ret, cost, abortafter ) then return table.concat(ret), cost end + local ret2, cost2 = var_tostring( k, v, tbl.stypes[k], indenting, printed, abortafter, cost ) + ret[#ret+1] = ret2 + cost = cost2 + end + return table.concat(ret), cost +end + +-------------------------------------------------------------------------------- +-- Operators +-------------------------------------------------------------------------------- + +__e2setcost(5) + +registerOperator("ass", "t", "t", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + + local Scope = self.Scopes[scope] + if !Scope.lookup then Scope.lookup = {} end + + local lookup = Scope.lookup + if (lookup[rhs]) then lookup[rhs][lhs] = nil end + if (!lookup[rhs]) then lookup[rhs] = {} end + lookup[rhs][lhs] = true + + Scope[lhs] = rhs + Scope.vclk[lhs] = true + return rhs +end) + +__e2setcost(1) + +e2function number operator_is( table tbl ) + return (tbl.size > 0) and 1 or 0 +end + +e2function number operator==( table rv1, table rv2 ) + return (rv1 == rv2) and 1 or 0 +end + +e2function number operator!=( table rv1, table rv2 ) + return (rv1 ~= rv2) and 1 or 0 +end + +__e2setcost(nil) + +registerOperator( "kvtable", "", "t", function( self, args ) + local ret = table.Copy( DEFAULT ) + + + local types = args[3] + + local s, stypes, n, ntypes = {}, {}, {}, {} + + local size = 0 + for k,v in pairs( args[2] ) do + if not blocked_types[types[k]] then + local key = k[1]( self, k ) + + if isstring(key) then + s[key] = v[1]( self, v ) + stypes[key] = types[k] + elseif isnumber(key) then + n[key] = v[1]( self, v ) + ntypes[key] = types[k] + end + size = size + 1 + end + end + + self.prf = self.prf + size * opcost + ret.size = size + ret.s = s + ret.stypes = stypes + ret.n = n + ret.ntypes = ntypes + return ret +end) + +-------------------------------------------------------------------------------- +-- Common functions +-------------------------------------------------------------------------------- + +__e2setcost(1) + +-- Creates a table +e2function table table(...) + local tbl = {...} + if (#tbl == 0) then return table.Copy(DEFAULT) end + local ret = table.Copy(DEFAULT) + local size = 0 + for k,v in ipairs( tbl ) do + if (!blocked_types[typeids[k]]) then + size = size + 1 + ret.n[k] = v + ret.ntypes[k] = typeids[k] + end + end + ret.size = size + self.prf = self.prf + size * opcost + return ret +end + +__e2setcost(1) + +-- Erases everything in the table +e2function void table:clear() + self.prf = self.prf + this.size * opcost + table.Empty( this.n ) + this.ntypes = {} + table.Empty( this.s ) + this.stypes = {} + this.size = 0 + return this +end + +__e2setcost(1) + +-- Returns the number of elements in the table +e2function number table:count() + return this.size +end + +__e2setcost(3) +-- Returns the number of elements in the array-part of the table +e2function number table:ncount() + return #this.n +end + +__e2setcost(1) +-- Returns 1 if any value exists at the specified index, else 0 +e2function number table:exists( index ) + return this.n[index] != nil and 1 or 0 +end +e2function number table:exists( string index ) + return this.s[index] != nil and 1 or 0 +end + +__e2setcost(5) + +e2function void printTable( table tbl ) + if (not checkOwner(self)) then return; end + if (tbl.size > 200) then + self.player:ChatPrint("Table has more than 200 ("..tbl.size..") elements. PrintTable cancelled to prevent lag") + return + end + local printed = { [tbl] = true } + local ret, cost = table_tostring( tbl, 0, printed, 200 ) + self.prf = self.prf + cost + for str in string.gmatch( ret, "[^\n]+" ) do + if #str > 250 then + self.prf = self.prf + 100 + self.player:ChatPrint("PrintTable attempted to print too much. PrintTable was cancelled to prevent lag") + return + end + self.player:ChatPrint( str ) + end +end + +__e2setcost(5) + +-- Flip the numbers and strings of the table +e2function table table:flip() + local ret = table.Copy(DEFAULT) + for k,v in pairs( this.n ) do + if (this.ntypes[k] == "s") then + ret.s[v] = k + ret.stypes[v] = "n" + end + end + for k,v in pairs( this.s ) do + if (this.stypes[k] == "n") then + ret.n[v] = k + ret.ntypes[v] = "s" + end + end + self.prf = self.prf + this.size * opcost + return ret +end + +-- Returns an table with the typesids of both the array- and table-parts +e2function table table:typeids() + local ret = table.Copy(DEFAULT) + ret.n = table.Copy(this.ntypes) + for k,v in pairs( ret.n ) do + ret.ntypes[k] = "s" + end + ret.s = table.Copy( this.stypes ) + for k,v in pairs( ret.s ) do + ret.stypes[k] = "s" + end + ret.size = this.size + self.prf = self.prf + this.size * opcost + return ret +end + +-- Removes the specified entry from the array-part and returns 1 if removed +e2function number table:remove( number index ) + if (#this.n == 0) then return 0 end + if (!this.n[index]) then return 0 end + if index < 1 then -- table.remove doesn't work if the index is below 1 + this.n[index] = nil + this.ntypes[index] = nil + else + table.remove( this.n, index ) + table.remove( this.ntypes, index ) + end + this.size = this.size - 1 + self.GlobalScope.vclk[this] = true + return 1 +end + +-- Force removes the specified entry from the table-part, without moving subsequent entries down and returns 1 if removed +e2function number table:remove( string index ) + if (IsEmpty(this.s)) then return 0 end + if (!this.s[index]) then return 0 end + this.s[index] = nil + this.stypes[index] = nil + this.size = this.size - 1 + self.GlobalScope.vclk[this] = true + return 1 +end + +-------------------------------------------------------------------------------- +-- Force remove +-- Force removes the specified entry from the array-part, without moving subsequent entries down and returns 1 if removed +-- Does not shift larger indexes down to fill the hole +-------------------------------------------------------------------------------- +e2function number table:unset( index ) + if this.n[index] == nil then return 0 end + this.n[index] = nil + this.ntypes[index] = nil + this.size = this.size - 1 + self.GlobalScope.vclk[this] = true + return 1 +end + +-- Force remove for strings is an alias to table:remove(string) +e2function number table:unset( string index ) = e2function number table:remove( string index ) + +-- Removes all variables not of the type +e2function table table:clipToTypeid( string typeid ) + local ret = table.Copy(DEFAULT) + for k,v in pairs( this.n ) do + if (this.ntypes[k] == typeid) then + local n = #ret.n+1 + if istable(v) then + ret.n[n] = table.Copy(v) + else + ret.n[n] = v + end + ret.ntypes[n] = this.ntypes[k] + ret.size = ret.size + 1 + end + end + for k,v in pairs( this.s ) do + if (this.stypes[k] == typeid) then + if istable(v) then + ret.s[k] = table.Copy(v) + else + ret.s[k] = v + end + ret.stypes[k] = this.stypes[k] + ret.size = ret.size + 1 + end + end + self.prf = self.prf + this.size * opcost + return ret +end + +-- Removes all variables of the type +e2function table table:clipFromTypeid( string typeid ) + local ret = table.Copy(DEFAULT) + for k,v in pairs( this.n ) do + if (this.ntypes[k] != typeid) then + if istable(v) then + ret.n[k] = table.Copy(v) + else + ret.n[k] = v + end + ret.ntypes[k] = this.ntypes[k] + ret.size = ret.size + 1 + end + end + for k,v in pairs( this.s ) do + if (this.stypes[k] != typeid) then + if istable(v) then + ret.s[k] = table.Copy(v) + else + ret.s[k] = v + end + ret.stypes[k] = this.stypes[k] + ret.size = ret.size + 1 + end + end + self.prf = self.prf + this.size * opcost + return ret +end + +__e2setcost(10) + +local function clone(self, tbl, lookup) + local copy = {} + + lookup = lookup or {} + lookup[tbl] = copy + + for k, v in pairs(tbl) do + if type(v) == "table" then + if lookup[v] then + self.prf = self.prf + opcost -- simple assign operation + copy[k] = lookup[v] + else + self.prf = self.prf + opcost * 3 -- creating new table + copy[k] = clone(self, v, lookup) + end + else + self.prf = self.prf + opcost -- simple assign operation + copy[k] = v + end + end + + return copy +end + +e2function table table:clone() + return clone(self, this) +end + +__e2setcost(1) + +e2function string table:id() + return tostring(this) +end + +__e2setcost(5) + +-- Formats the table as a human readable string +e2function string table:toString() + local printed = { [this] = true } + local ret, cost = table_tostring( this, 0, printed, 400 ) + self.prf = self.prf + cost * opcost + return ret +end + +-- Adds rv2 to the end of 'this' (adds numerical indexes to the end of the array-part, and only inserts string indexes that don't exist on rv1) +e2function table table:add( table rv2 ) + local ret = table.Copy(this) + local cost = this.size + local size = this.size + + local count = #ret.n + for k,v in pairs( rv2.n ) do + cost = cost + 1 + local id = rv2.ntypes[k] + if (!blocked_types[id]) then + count = count + 1 + size = size + 1 + ret.n[count] = v + ret.ntypes[count] = id + end + end + + for k,v in pairs( rv2.s ) do + cost = cost + 1 + if (!ret.s[k]) then + local id = rv2.stypes[k] + if (!blocked_types[id]) then + size = size + 1 + ret.s[k] = v + ret.stypes[k] = id + end + end + end + + self.prf = self.prf + cost * opcost + ret.size = size + return ret +end + +-- Merges rv2 with 'this' (both numerical and string indexes are overwritten) +e2function table table:merge( table rv2 ) + local ret = table.Copy(this) + local cost = this.size + local size = this.size + + for k,v in pairs( rv2.n ) do + cost = cost + 1 + local id = rv2.ntypes[k] + if (!blocked_types[id]) then + if (!ret.n[k]) then size = size + 1 end + ret.n[k] = v + ret.ntypes[k] = id + end + end + + for k,v in pairs( rv2.s ) do + cost = cost + 1 + local id = rv2.stypes[k] + if (!blocked_types[id]) then + if (!ret.s[k]) then size = size + 1 end + ret.s[k] = v + ret.stypes[k] = id + end + end + + self.prf = self.prf + cost * opcost + ret.size = size + return ret +end + +-- Removes all variables from 'this' which have keys which exist in rv2 +e2function table table:difference( table rv2 ) + local ret = table.Copy(DEFAULT) + local cost = 0 + local size = 0 + + for k,v in pairs( this.n ) do + cost = cost + 1 + if (!rv2.n[k]) then + size = size + 1 + ret.n[size] = v + ret.ntypes[size] = this.ntypes[k] + end + end + + for k,v in pairs( this.s ) do + cost = cost + 1 + if (!rv2.s[k]) then + size = size + 1 + ret.s[k] = v + ret.stypes[k] = this.stypes[k] + end + end + self.prf = self.prf + cost * opcost + ret.size = size + + return ret +end + +-- Removes all variables from 'this' which don't have keys which exist in rv2 +e2function table table:intersect( table rv2 ) + local ret = table.Copy(DEFAULT) + local cost = 0 + local size = 0 + + for k,v in pairs( this.n ) do + cost = cost + 1 + if (rv2.n[k]) then + size = size + 1 + ret.n[size] = v + ret.ntypes[size] = this.ntypes[k] + end + end + + for k,v in pairs( this.s ) do + cost = cost + 1 + if (rv2.s[k]) then + size = size + 1 + ret.s[k] = v + ret.stypes[k] = this.stypes[k] + end + end + self.prf = self.prf + cost * opcost + ret.size = size + + return ret +end + +-------------------------------------------------------------------------------- +-- Array-part-only functions +-------------------------------------------------------------------------------- + +__e2setcost(3) + +-- Removes the last entry in the array-part and returns 1 if removed +e2function number table:pop() + local n = #this.n + if (n == 0) then return 0 end + this.n[n] = nil + this.ntypes[n] = nil + this.size = this.size - 1 + self.GlobalScope.vclk[this] = true + return 1 +end + +-- Deletes the first element of the table; all other entries will move down one address and returns 1 if removed +e2function number table:shift() + local result = table.remove( this.n, 1 ) and 1 or 0 + table.remove( this.ntypes, 1 ) + this.size = this.size - 1 + self.GlobalScope.vclk[this] = true + return result +end + +__e2setcost(5) + +-- Returns the smallest number in the array-part +e2function number table:min() + if (IsEmpty(this.n)) then return 0 end + local smallest = nil + local cost = 0 + for k,v in pairs( this.n ) do + cost = cost + 1 + if (this.ntypes[k] == "n") then + if (smallest == nil or v < smallest) then + smallest = v + end + end + end + self.prf = self.prf + cost * opcost + return smallest or 0 +end + +-- Returns the largest number in the array-part +e2function number table:max() + if (IsEmpty(this.n)) then return 0 end + local largest = nil + local cost = 0 + for k,v in pairs( this.n ) do + cost = cost + 1 + if (this.ntypes[k] == "n") then + if (largest == nil or v > largest) then + largest = v + end + end + end + self.prf = self.prf + cost * opcost + return largest or 0 +end + +-- Returns the index of the largest number in the array-part +e2function number table:maxIndex() + if (IsEmpty(this.n)) then return 0 end + local largest = nil + local index = 0 + local cost = 0 + for k,v in pairs( this.n ) do + cost = cost + 1 + if (this.ntypes[k] == "n") then + if (largest == nil or v > largest) then + largest = v + index = k + end + end + end + self.prf = self.prf + cost * opcost + return index +end + +-- Returns the index of the smallest number in the array-part +e2function number table:minIndex() + if (IsEmpty(this.n)) then return 0 end + local smallest = nil + local index = 0 + local cost = 0 + for k,v in pairs( this.n ) do + cost = cost + 1 + if (this.ntypes[k] == "n") then + if (smallest == nil or v < smallest) then + smallest = v + index = k + end + end + end + self.prf = self.prf + cost * opcost + return index +end + +-- Returns the types of the variables in the array-part +e2function array table:typeidsArray() + if (IsEmpty(this.n)) then return {} end + self.prf = self.prf + table.Count(this.ntypes) * opcost + return table.Copy(this.ntypes) +end + +-- Converts the table into an array +e2function array table:toArray() + if (IsEmpty(this.n)) then return {} end + local ret = {} + local cost = 0 + for k,v in pairs( this.n ) do + cost = cost + 1 + local id = this.ntypes[k] + if (tbls[id] != true) then + ret[k] = v + end + end + self.prf = self.prf + cost * opcost + return ret +end + +__e2setcost(20) + +-- Returns the find in the array part of an table +e2function table findToTable() + local ret = table.Copy(DEFAULT) + for k,v in ipairs( self.data.findlist ) do + ret.n[k] = v + ret.ntypes[k] = "e" + ret.size = k + end + return ret +end + +__e2setcost(1) +local luaconcat = table.concat +local clamp = math.Clamp +local function concat( tab, delimeter, startindex, endindex ) + local ret = {} + local len = #tab + + startindex = startindex or 1 + if startindex > len then return "" end + + endindex = clamp(endindex or len, startindex, len) + + for i=startindex, endindex do + ret[#ret+1] = tostring(tab[i]) + end + return luaconcat( ret, delimeter ) +end + +e2function string table:concat() + self.prf = self.prf + #this * opcost + return concat(this.n) +end +e2function string table:concat(string delimiter) + self.prf = self.prf + #this * opcost + return concat(this.n,delimiter) +end +e2function string table:concat(string delimiter, startindex) + self.prf = self.prf + #this * opcost + return concat(this.n,delimiter,startindex) +end +e2function string table:concat(string delimiter, startindex, endindex) + self.prf = self.prf + #this * opcost + return concat(this.n,delimiter,startindex,endindex) +end +e2function string table:concat(startindex) + self.prf = self.prf + #this * opcost + return concat(this.n,"",startindex,endindex) +end +e2function string table:concat(startindex,endindex) + self.prf = self.prf + #this * opcost + return concat(this.n,"",startindex,endindex) +end + +-------------------------------------------------------------------------------- +-- Table-part-only functions +-------------------------------------------------------------------------------- + +__e2setcost(5) + +-------------------------------------------------------------------------------- +-- Backwards compatibility functions (invert & co.) +-------------------------------------------------------------------------------- + +--- Returns a lookup table for . Usage: Index = T:number(toString(Value)). +--- Don't overuse this function, as it can become expensive for arrays with > 10 entries! +e2function table invert(array arr) + local ret = table.Copy(DEFAULT) + local c = 0 + local size = 0 + for i,v in ipairs(arr) do + c = c + 1 + local tostring_this = tostrings[type(v)] + if tostring_this then + ret.s[tostring_this(v)] = i + ret.stypes[tostring_this(v)] = "n" + size = size + 1 + elseif (checkOwner(self)) then + self.player:ChatPrint("E2: invert(R): Invalid type ("..type(v)..") in array. Ignored.") + end + end + ret.size = size + self.prf = self.prf + c * opcost + return ret +end + +--- Returns a lookup table for . Usage: Key = T:string(toString(Value)). +--- Don't overuse this function, as it can become expensive for tables with > 10 entries! +e2function table invert(table tbl) + local ret = table.Copy(DEFAULT) + local c = 0 + local size = 0 + for i,v in pairs(tbl.n) do + c = c + 1 + local typeid = tbl.ntypes[i] + local tostring_this = tostring_typeid[typeid] + if tostring_this then + ret.s[tostring_this(v)] = i + ret.stypes[tostring_this(v)] = "n" + size = size + 1 + elseif (checkOwner(self)) then + self.player:ChatPrint("E2: invert(T): Invalid type ("..typeid..") in table. Ignored.") + end + end + for i,v in pairs(tbl.s) do + c = c + 1 + local typeid = tbl.stypes[i] + local tostring_this = tostring_typeid[typeid] + if tostring_this then + ret.s[tostring_this(v)] = i + ret.stypes[tostring_this(v)] = "s" + size = size + 1 + elseif (checkOwner(self)) then + self.player:ChatPrint("E2: invert(T): Invalid type ("..typeid..") in table. Ignored.") + end + end + self.prf = self.prf + c * opcost + ret.size = size + return ret +end + +e2function array table:keys() + local ret = {} + local c = 0 + for index,value in pairs(this.n) do + c = c + 1 + ret[#ret+1] = index + end + for index,value in pairs(this.s) do + c = c + 1 + ret[#ret+1] = index + end + self.prf = self.prf + c * opcost + return ret +end + +e2function array table:values() + local ret = {} + local c = 0 + for index,value in pairs(this.n) do + c = c + 1 + if (!tbls[this.ntypes[index]]) then + ret[#ret+1] = value + end + end + for index,value in pairs(this.s) do + c = c + 1 + if (!tbls[this.stypes[index]]) then + ret[#ret+1] = value + end + end + self.prf = self.prf + c * opcost + return ret +end + +-------------------------------------------------------------------------------- +-- Looped functions +-------------------------------------------------------------------------------- + +registerCallback( "postinit", function() + local getf, setf + for k,v in pairs( wire_expression_types ) do + local name = k + local id = v[1] + + if (!blocked_types[id]) then -- blocked check start + + -------------------------------------------------------------------------------- + -- Set/Get functions, t[index,type] syntax + -------------------------------------------------------------------------------- + + __e2setcost(5) + + -- Getters + registerOperator("idx", id.."=ts" , id, function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if (!rv1.s[rv2] or rv1.stypes[rv2] != id) then return fixdef(v[2]) end + if (v[6] and v[6](rv1.s[rv2])) then return fixdef(v[2]) end -- Type check + return rv1.s[rv2] + end) + + registerOperator("idx", id.."=tn" , id, function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if (!rv1.n[rv2] or rv1.ntypes[rv2] != id) then return fixdef(v[2]) end + if (v[6] and v[6](rv1.n[rv2])) then return fixdef(v[2]) end -- Type check + return rv1.n[rv2] + end) + + -- Setters + registerOperator("idx", id.."=ts"..id , id, function( self, args ) + local op1, op2, op3, scope = args[2], args[3], args[4], args[5] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + if (rv1.s[rv2] == nil and rv3 ~= nil) then rv1.size = rv1.size + 1 + elseif (rv1.n[rv2] ~= nil and rv3 == nil) then rv1.size = rv1.size - 1 end + rv1.s[rv2] = rv3 + rv1.stypes[rv2] = id + self.GlobalScope.vclk[rv1] = true + return rv3 + end) + + registerOperator("idx", id.."=tn"..id, id, function(self,args) + local op1, op2, op3, scope = args[2], args[3], args[4], args[5] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + if (rv1.n[rv2] == nil and rv3 ~= nil) then rv1.size = rv1.size + 1 + elseif (rv1.n[rv2] ~= nil and rv3 == nil) then rv1.size = rv1.size - 1 end + rv1.n[rv2] = rv3 + rv1.ntypes[rv2] = id + self.GlobalScope.vclk[rv1] = true + return rv3 + end) + + + -------------------------------------------------------------------------------- + -- Remove functions + -------------------------------------------------------------------------------- + + __e2setcost(8) + + local function removefunc( self, rv1, rv2, numidx ) + if (!rv1 or !rv2) then return fixdef(v[2]) end + if (numidx) then + if (!rv1.n[rv2] or rv1.ntypes[rv2] != id) then return fixdef(v[2]) end + local ret = rv1.n[rv2] + if rv2 < 1 then -- table.remove doesn't work if the index is below 1 + rv1.n[rv2] = nil + rv1.ntypes[rv2] = nil + else + table.remove( rv1.n, rv2 ) + table.remove( rv1.ntypes, rv2 ) + end + rv1.size = rv1.size - 1 + self.GlobalScope.vclk[rv1] = true + return ret + else + if (!rv1.s[rv2] or rv1.stypes[rv2] != id) then return fixdef(v[2]) end + local ret = rv1.s[rv2] + rv1.s[rv2] = nil + rv1.stypes[rv2] = nil + self.GlobalScope.vclk[rv1] = true + rv1.size = rv1.size - 1 + return ret + end + end + + name = upperfirst( name ) + if (name == "Normal") then name = "Number" end + registerFunction("remove"..name,"t:s",id,function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return removefunc( self, rv1, rv2) + end) + registerFunction("remove"..name,"t:n",id,function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return removefunc(self, rv1, rv2, true) + end) + + + -------------------------------------------------------------------------------- + -- Array functions + -------------------------------------------------------------------------------- + __e2setcost(10) + + -- Push a variable into the table (into the array part) + registerFunction( "push"..name,"t:"..id,"",function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv2 == nil then return end + local n = #rv1.n+1 + rv1.size = rv1.size + 1 + rv1.n[n] = rv2 + rv1.ntypes[n] = id + self.GlobalScope.vclk[rv1] = true + return rv2 + end) + + registerFunction( "pop"..name,"t:",id,function(self,args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return removefunc(self, rv1, #rv1.n, true) + end) + + registerFunction( "insert"..name,"t:n"..id,"",function(self,args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self,op3) + if rv3 == nil then return end + if rv2 < 0 then return end + if rv2 > 2^31 then return end -- too large, possibility of crashing gmod + rv1.size = rv1.size + 1 + table.insert( rv1.n, rv2, rv3 ) + table.insert( rv1.ntypes, rv2, id ) + self.GlobalScope.vclk[rv1] = true + return rv3 + end) + + registerFunction( "unshift"..name,"t:"..id,"",function(self,args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv2 == nil then return end + rv1.size = rv1.size + 1 + table.insert( rv1.n, 1, rv2 ) + table.insert( rv1.ntypes, 1, id ) + self.GlobalScope.vclk[rv1] = true + return rv2 + end) + + -------------------------------------------------------------------------------- + -- Foreach operators + -------------------------------------------------------------------------------- + __e2setcost(nil) + + registerOperator("fea", "s" .. id .. "t", "", function(self, args) + local keyname, valname = args[2], args[3] + + local tbl = args[4] + tbl = tbl[1](self, tbl) + + local statement = args[5] + + for key, value in pairs(tbl.s) do + if tbl.stypes[key] == id then + self:PushScope() + + self.prf = self.prf + 3 + + self.Scope.vclk[keyname] = true + self.Scope.vclk[valname] = true + + self.Scope[keyname] = key + self.Scope[valname] = value + + local ok, msg = pcall(statement[1], self, statement) + + if not ok then + if msg == "break" then self:PopScope() break + elseif msg ~= "continue" then self:PopScope() error(msg, 0) end + end + + self:PopScope() + end + end + end) + + registerOperator("fea", "n" .. id .. "t", "", function(self, args) + local keyname, valname = args[2], args[3] + + local tbl = args[4] + tbl = tbl[1](self, tbl) + + local statement = args[5] + + for key, value in pairs(tbl.n) do + if tbl.ntypes[key] == id then + self:PushScope() + + self.prf = self.prf + 3 + + self.Scope.vclk[keyname] = true + self.Scope.vclk[valname] = true + + self.Scope[keyname] = key + self.Scope[valname] = value + + local ok, msg = pcall(statement[1], self, statement) + + if not ok then + if msg == "break" then self:PopScope() break + elseif msg ~= "continue" then self:PopScope() error(msg, 0) end + end + + self:PopScope() + end + end + end) + + end -- blocked check end + + end +end) + +-------------------------------------------------------------------------------- +-- "lookup" stuff copied from the old table.lua file +-------------------------------------------------------------------------------- + +-- these postexecute and construct hooks handle changes to both tables and arrays. +registerCallback("postexecute", function(self) + local Scope = self.GlobalScope + local vclk, lookup = Scope.vclk, Scope.lookup + + -- Go through all registered values of the types table and array. + for value,varnames in pairs(lookup) do + local clk = vclk[value] + + local still_assigned = false + -- For each value, go through the variables they're assigned to and trigger them. + for varname,_ in pairs(varnames) do + if value == Scope[varname] then + -- The value is still assigned to the variable? => trigger it. + if clk then vclk[varname] = true end + still_assigned = true + else + -- The value is no longer assigned to the variable? => remove the lookup table entry. + varnames[varname] = nil + end + end + + -- if the value is no longer assigned to anything, remove all references to it. + if not still_assigned then + lookup[value] = nil + end + -- If the value has no more variable names associated, remove the value's place in the lookup table. + if IsEmpty(varnames) then lookup[value] = nil end + end +end) + +local tbls = { + ARRAY = true, + TABLE = true, +} + +registerCallback("construct", function(self) + local Scope = self.GlobalScope + Scope.lookup = {} + + for k,v in pairs( Scope ) do + if k != "lookup" then + local datatype = self.entity.outports[3][k] + if (tbls[datatype]) then + if (!Scope.lookup[v]) then Scope.lookup[v] = {} end + Scope.lookup[v][k] = true + end + end + end +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/timer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/timer.lua new file mode 100644 index 0000000..2ac153e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/timer.lua @@ -0,0 +1,209 @@ +/******************************************************************************\ + Timer support +\******************************************************************************/ + +local timerid = 0 + +local function Execute(self, name) + self.data.timer.runner = name + + self.data['timer'].timers[name] = nil + + if(self.entity and self.entity.Execute) then + self.entity:Execute() + end + + if !self.data['timer'].timers[name] then + timer.Remove("e2_" .. self.data['timer'].timerid .. "_" .. name) + end + + self.data.timer.runner = nil +end + +local function AddTimer(self, name, delay) + if delay < 10 then delay = 10 end + + local timerName = "e2_" .. self.data.timer.timerid .. "_" .. name + + if self.data.timer.runner == name and timer.Exists(timerName) then + timer.Adjust(timerName, delay / 1000, 2, function() + Execute(self, name) + end) + timer.Start(timerName) + elseif !self.data['timer'].timers[name] then + timer.Create(timerName, delay / 1000, 2, function() + Execute(self, name) + end) + end + + self.data['timer'].timers[name] = true +end + +local function RemoveTimer(self, name) + if self.data['timer'].timers[name] then + timer.Remove("e2_" .. self.data['timer'].timerid .. "_" .. name) + self.data['timer'].timers[name] = nil + end +end + +/******************************************************************************/ + +registerCallback("construct", function(self) + self.data['timer'] = {} + self.data['timer'].timerid = timerid + self.data['timer'].timers = {} + + timerid = timerid + 1 +end) + +registerCallback("destruct", function(self) + for name,_ in pairs(self.data['timer'].timers) do + RemoveTimer(self, name) + end +end) + +/******************************************************************************/ + +__e2setcost(20) + +e2function void interval(rv1) + AddTimer(self, "interval", rv1) +end + +e2function void timer(string rv1, rv2) + AddTimer(self, rv1, rv2) +end + +__e2setcost(5) + +e2function void stoptimer(string rv1) + RemoveTimer(self, rv1) +end + +__e2setcost(1) +e2function number clk() + if self.data.timer.runner == "interval" + then return 1 else return 0 end +end + +e2function number clk(string rv1) + if self.data.timer.runner == rv1 + then return 1 else return 0 end +end + +e2function string clkName() + return self.data.timer.runner or "" +end + +e2function array getTimers() + local ret = {} + local i = 0 + for name,_ in pairs( self.data.timer.timers ) do + i = i + 1 + ret[i] = name + end + self.prf = self.prf + i * 5 + return ret +end + +e2function void stopAllTimers() + for name,_ in pairs(self.data.timer.timers) do + self.prf = self.prf + 5 + RemoveTimer(self,name) + end +end + +/******************************************************************************/ + +e2function number curtime() + return CurTime() +end + +e2function number realtime() + return RealTime() +end + +e2function number systime() + return SysTime() +end + +----------------------------------------------------------------------------------- + +local function luaDateToE2Table( time, utc ) + local ret = {n={},ntypes={},s={},stypes={},size=0} + local time = os.date((utc and "!" or "") .. "*t",time) + + if not time then return ret end -- this happens if you give it a negative time + + for k,v in pairs( time ) do + if k == "isdst" then + ret.s.isdst = (v and 1 or 0) + ret.stypes.isdst = "n" + else + ret.s[k] = v + ret.stypes[k] = "n" + end + + ret.size = ret.size + 1 + end + + return ret +end +__e2setcost(10) +-- Returns the server's current time formatted neatly in a table +e2function table date() + return luaDateToE2Table() +end + +-- Returns the specified time formatted neatly in a table +e2function table date( time ) + return luaDateToE2Table(time) +end + +-- Returns the server's current time formatted neatly in a table using UTC +e2function table dateUTC() + return luaDateToE2Table(nil,true) +end + +-- Returns the specified time formatted neatly in a table using UTC +e2function table dateUTC( time ) + return luaDateToE2Table(time,true) +end + +-- This function has a strange and slightly misleading name, but changing it might break older E2s, so I'm leaving it +-- It's essentially the same as the date function above +e2function number time(string component) + local ostime = os.date("!*t") + local ret = ostime[component] + + return tonumber(ret) or ret and 1 or 0 -- the later parts account for invalid components and isdst +end + + +----------------------------------------------------------------------------------- + +__e2setcost(2) +-- Returns the time in seconds +e2function number time() + return os.time() +end + +-- Attempts to construct the time from the data in the given table (same as lua's os.time) +-- The table structure must be the same as in the above date functions +-- If any values are missing or of the wrong type, that value is ignored (it will be nil) +local validkeys = {hour = true, min = true, day = true, sec = true, yday = true, wday = true, month = true, year = true, isdst = true} +e2function number time(table data) + local args = {} + + for k,v in pairs( data.s ) do + if data.stypes[k] ~= "n" or not validkeys[k] then continue end + + if k == "isdst" then + args.isdst = (v == 1) + else + args[k] = v + end + end + + return os.time( args ) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/unitconv.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/unitconv.lua new file mode 100644 index 0000000..e002ba4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/unitconv.lua @@ -0,0 +1,133 @@ +/******************************************************************************\ + Unit conversion +\******************************************************************************/ + +/* + u - source unit + A Source Unit is 0.75 Inch long, more info here: + http://developer.valvesoftware.com/wiki/Dimensions#Map_Grid_Units:_quick_reference + + mm - millimeter + cm - centimeter + dm - decimeter + m - meter + km - kilometer + in - inch + ft - foot + yd - yard + mi - mile + nmi - nautical mile + + g - gram + kg - kilogram + t - tonne + oz - ounce + lb - pound +*/ + +local speed = { + ["u/s"] = 1 / 0.75, + ["u/m"] = 60 * (1 / 0.75), + ["u/h"] = 3600 * (1 / 0.75), + + ["mm/s"] = 25.4, + ["cm/s"] = 2.54, + ["dm/s"] = 0.254, + ["m/s"] = 0.0254, + ["km/s"] = 0.0000254, + ["in/s"] = 1, + ["ft/s"] = 1 / 12, + ["yd/s"] = 1 / 36, + ["mi/s"] = 1 / 63360, + ["nmi/s"] = 127 / 9260000, + + ["mm/m"] = 60 * 25.4, + ["cm/m"] = 60 * 2.54, + ["dm/m"] = 60 * 0.254, + ["m/m"] = 60 * 0.0254, + ["km/m"] = 60 * 0.0000254, + ["in/m"] = 60, + ["ft/m"] = 60 / 12, + ["yd/m"] = 60 / 36, + ["mi/m"] = 60 / 63360, + ["nmi/m"] = 60 * 127 / 9260000, + + ["mm/h"] = 3600 * 25.4, + ["cm/h"] = 3600 * 2.54, + ["dm/h"] = 3600 * 0.254, + ["m/h"] = 3600 * 0.0254, + ["km/h"] = 3600 * 0.0000254, + ["in/h"] = 3600, + ["ft/h"] = 3600 / 12, + ["yd/h"] = 3600 / 36, + ["mi/h"] = 3600 / 63360, + ["nmi/h"] = 3600 * 127 / 9260000, + + ["mph"] = 3600 / 63360, + ["knots"] = 3600 * 127 / 9260000, + ["mach"] = 0.0254 / 295, +} + +local length = { + ["u"] = 1 / 0.75, + + ["mm"] = 25.4, + ["cm"] = 2.54, + ["dm"] = 0.254, + ["m"] = 0.0254, + ["km"] = 0.0000254, + ["in"] = 1, + ["ft"] = 1 / 12, + ["yd"] = 1 / 36, + ["mi"] = 1 / 63360, + ["nmi"] = 127 / 9260000, +} + +local weight = { + ["g"] = 1000, + ["kg"] = 1, + ["t"] = 0.001, + ["oz"] = 1 / 0.028349523125, + ["lb"] = 1 / 0.45359237, +} + +__e2setcost(2) -- approximated + +e2function number toUnit(string rv1, rv2) + + if speed[rv1] then + return (rv2 * 0.75) * speed[rv1] + elseif length[rv1] then + return (rv2 * 0.75) * length[rv1] + elseif weight[rv1] then + return rv2 * weight[rv1] + end + + return -1 +end + +e2function number fromUnit(string rv1, rv2) + + if speed[rv1] then + return (rv2 / 0.75) / speed[rv1] + elseif length[rv1] then + return (rv2 / 0.75) / length[rv1] + elseif weight[rv1] then + return rv2 / weight[rv1] + end + + return -1 +end + +e2function number convertUnit(string rv1, string rv2, rv3) + + if speed[rv1] and speed[rv2] then + return rv3 * (speed[rv2] / speed[rv1]) + elseif length[rv1] and length[rv2] then + return rv3 * (length[rv2] / length[rv1]) + elseif weight[rv1] and weight[rv2] then + return rv3 * (weight[rv2] / weight[rv1]) + end + + return -1 +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/vector.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/vector.lua new file mode 100644 index 0000000..6e2f2d7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/vector.lua @@ -0,0 +1,732 @@ +-------------------------------------------------------------------------------- +-- Vector support -- +-------------------------------------------------------------------------------- + +local delta = wire_expression2_delta + +local random = math.random +local Vector = Vector +local sqrt = math.sqrt +local floor = math.floor +local ceil = math.ceil +local pi = math.pi +local atan2 = math.atan2 +local asin = math.asin +local rad2deg = 180 / pi +local deg2rad = pi / 180 + +-- TODO: add reflect? +-- TODO: add absdotproduct? +-- TODO: add helper for angle and dotproduct? (just strange?) + +-------------------------------------------------------------------------------- + +registerType("vector", "v", { 0, 0, 0 }, + nil, + function(self, output) return Vector(output[1], output[2], output[3]) end, + function(retval) + if isvector(retval) then return end + if not istable(retval) then error("Return value is neither a Vector nor a table, but a "..type(retval).."!",0) end + if #retval ~= 3 then error("Return value does not have exactly 3 entries!",0) end + end, + function(v) + return not isvector(v) and (not istable(v) or #v ~= 3) + end +) + +-------------------------------------------------------------------------------- + +__e2setcost(1) -- approximated + +e2function vector vec() + return { 0, 0, 0 } +end + +__e2setcost(2) + +e2function vector vec(x) + return { x, x, x } +end + +e2function vector vec(x, y, z) + return { x, y, z } +end + +e2function vector vec(vector2 v2) + return { v2[1], v2[2], 0 } +end + +e2function vector vec(vector2 v2, z) + return { v2[1], v2[2], z } +end + +e2function vector vec(vector4 v4) + return { v4[1], v4[2], v4[3] } +end + +--- Convert Angle -> Vector +e2function vector vec(angle ang) + return { ang[1], ang[2], ang[3] } +end + +-------------------------------------------------------------------------------- + +registerOperator("ass", "v", "v", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +-------------------------------------------------------------------------------- + +e2function number vector:operator_is() + if this[1] > delta or -this[1] > delta or + this[2] > delta or -this[2] > delta or + this[3] > delta or -this[3] > delta + then return 1 else return 0 end +end + +e2function number vector:operator==( vector other ) + if this[1] - other[1] <= delta and other[1] - this[1] <= delta and + this[2] - other[2] <= delta and other[2] - this[2] <= delta and + this[3] - other[3] <= delta and other[3] - this[3] <= delta + then return 1 else return 0 end +end + +e2function number vector:operator!=( vector other ) + if this[1] - other[1] > delta or other[1] - this[1] > delta or + this[2] - other[2] > delta or other[2] - this[2] > delta or + this[3] - other[3] > delta or other[3] - this[3] > delta + then return 1 else return 0 end +end + +-------------------------------------------------------------------------------- + +e2function vector vector:operator_neg() + return { -this[1], -this[2], -this[3] } +end + +e2function vector operator+(lhs, vector rhs) + return { lhs + rhs[1], lhs + rhs[2], lhs + rhs[3] } +end + +e2function vector operator+(vector lhs, rhs) + return { lhs[1] + rhs, lhs[2] + rhs, lhs[3] + rhs } +end + +e2function vector operator+(vector lhs, vector rhs) + return { lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3] + rhs[3] } +end + +e2function vector operator-(lhs, vector rhs) + return { lhs - rhs[1], lhs - rhs[2], lhs - rhs[3] } +end + +e2function vector operator-(vector lhs, rhs) + return { lhs[1] - rhs, lhs[2] - rhs, lhs[3] - rhs } +end + +e2function vector operator-(vector lhs, vector rhs) + return { lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3] } +end + +e2function vector operator*(lhs, vector rhs) + return { lhs * rhs[1], lhs * rhs[2], lhs * rhs[3] } +end + +e2function vector operator*(vector lhs, rhs) + return { lhs[1] * rhs, lhs[2] * rhs, lhs[3] * rhs } +end + +e2function vector operator*(vector lhs, vector rhs) + return { lhs[1] * rhs[1], lhs[2] * rhs[2], lhs[3] * rhs[3] } +end + +e2function vector operator/(lhs, vector rhs) + return { lhs / rhs[1], lhs / rhs[2], lhs / rhs[3] } +end + +e2function vector operator/(vector lhs, rhs) + return { lhs[1] / rhs, lhs[2] / rhs, lhs[3] / rhs } +end + +e2function vector operator/(vector lhs, vector rhs) + return { lhs[1] / rhs[1], lhs[2] / rhs[2], lhs[3] / rhs[3] } +end + +e2function number vector:operator[](index) + return this[floor(math.Clamp(index, 1, 3) + 0.5)] +end + +e2function number vector:operator[](index, value) + this[floor(math.Clamp(index, 1, 3) + 0.5)] = value + return value +end + +-------------------------------------------------------------------------------- + +__e2setcost(10) -- temporary + +--- Returns a uniformly distributed, random, normalized direction vector. +e2function vector randvec() + local s,a, x,y + + --[[ + This is a variant of the algorithm for computing a random point + on the unit sphere; the algorithm is suggested in Knuth, v2, + 3rd ed, p136; and attributed to Robert E Knop, CACM, 13 (1970), + 326. + ]] + -- translated to lua from http://mhda.asiaa.sinica.edu.tw/mhda/apps/gsl-1.6/randist/sphere.c + + -- Begin with the polar method for getting x,y inside a unit circle + repeat + x = random() * 2 - 1 + y = random() * 2 - 1 + s = x*x + y*y + until s <= 1.0 + + a = 2 * sqrt(1 - s) -- factor to adjust x,y so that x^2+y^2 is equal to 1-z^2 + return Vector(x*a, y*a, s * 2 - 1) -- z uniformly distributed from -1 to 1 + + --[[ + -- This variant saves 2 multiplications per loop, woo. But it's not readable and not verified, thus commented out. + -- I will also not add a cheaper non-uniform variant, as that can easily be derived from the other randvec functions and V:normalize(). + -- Begin with the polar method for getting x,y inside a (strangely skewed) unit circle + repeat + x = random() + y = random() + s = x*(x-1) + y*(y-1) + until s <= -0.25 + + a = sqrt(-16 - s*64) -- factor to adjust x,y so that x^2+y^2 is equal to 1-z^2 + return Vector((x-0.5)*a, (y-0.5)*a, s * 8 + 3) -- z uniformly distributed from -1 to 1 + ]] +end + +__e2setcost(5) + +--- Returns a random vector with its components between and +e2function vector randvec( normal min, normal max) + local range = max-min + return Vector(min+random()*range, min+random()*range, min+random()*range) +end + +--- Returns a random vector between and +e2function vector randvec(vector min, vector max) + local minx, miny, minz = min[1], min[2], min[3] + return Vector(minx+random()*(max[1]-minx), miny+random()*(max[2]-miny), minz+random()*(max[3]-minz)) +end + +-------------------------------------------------------------------------------- + +__e2setcost(5) + +e2function number vector:length() + return (this[1] * this[1] + this[2] * this[2] + this[3] * this[3]) ^ 0.5 +end + +e2function number vector:length2() + return this[1] * this[1] + this[2] * this[2] + this[3] * this[3] +end + +e2function number vector:distance(vector other) + local dx, dy, dz = this[1] - other[1], this[2] - other[2], this[3] - other[3] + return (dx * dx + dy * dy + dz * dz) ^ 0.5 +end + +e2function number vector:distance2( vector other ) + local dx, dy, dz = this[1] - other[1], this[2] - other[2], this[3] - other[3] + return dx * dx + dy * dy + dz * dz +end + +e2function vector vector:normalized() + local len = (this[1] * this[1] + this[2] * this[2] + this[3] * this[3]) ^ 0.5 + if len > delta then + return { this[1] / len, this[2] / len, this[3] / len } + else + return { 0, 0, 0 } + end +end + +e2function number vector:dot( vector other ) + return this[1] * other[1] + this[2] * other[2] + this[3] * other[3] +end + +e2function vector vector:cross( vector other ) + return { + this[2] * other[3] - this[3] * other[2], + this[3] * other[1] - this[1] * other[3], + this[1] * other[2] - this[2] * other[1], + } +end + +__e2setcost(10) + +--- returns the outer product (tensor product) of two vectors +e2function matrix vector:outerProduct( vector other ) + return { + this[1] * this[1], this[1] * other[2], this[1] * other[3], + this[2] * this[1], this[2] * other[2], this[2] * other[3], + this[3] * this[1], this[3] * other[2], this[3] * other[3], + } +end + +__e2setcost(15) +e2function vector vector:rotateAroundAxis(vector axis, degrees) + local ca, sa = math.cos(degrees*deg2rad), math.sin(degrees*deg2rad) + local x,y,z = axis[1], axis[2], axis[3] + local length = (x*x+y*y+z*z)^0.5 + x,y,z = x/length, y/length, z/length + + return {(ca + (x^2)*(1-ca)) * this[1] + (x*y*(1-ca) - z*sa) * this[2] + (x*z*(1-ca) + y*sa) * this[3], + (y*x*(1-ca) + z*sa) * this[1] + (ca + (y^2)*(1-ca)) * this[2] + (y*z*(1-ca) - x*sa) * this[3], + (z*x*(1-ca) - y*sa) * this[1] + (z*y*(1-ca) + x*sa) * this[2] + (ca + (z^2)*(1-ca)) * this[3]} +end + +__e2setcost(5) + +e2function vector vector:rotate( angle ang ) + local v = Vector(this[1], this[2], this[3]) + v:Rotate(Angle(ang[1], ang[2], ang[3])) + return v +end + +e2function vector vector:rotate( normal pitch, normal yaw, normal roll ) + local v = Vector(this[1], this[2], this[3]) + v:Rotate(Angle(pitch, yaw, roll)) + return v +end + +e2function vector2 vector:dehomogenized() + local w = this[3] + if w == 0 then return { this[1], this[2] } end + return { this[1]/w, this[2]/w } +end + +e2function vector positive(vector rv1) + return { + rv1[1] >= 0 and rv1[1] or -rv1[1], + rv1[2] >= 0 and rv1[2] or -rv1[2], + rv1[3] >= 0 and rv1[3] or -rv1[3], + } +end + +__e2setcost(3) + +--- Convert the magnitude of the vector to radians +e2function vector toRad(vector rv1) + return Vector(rv1[1] * deg2rad, rv1[2] * deg2rad, rv1[3] * deg2rad) +end + +--- Convert the magnitude of the vector to degrees +e2function vector toDeg(vector rv1) + return Vector(rv1[1] * rad2deg, rv1[2] * rad2deg, rv1[3] * rad2deg) +end + +-------------------------------------------------------------------------------- + +__e2setcost(5) + +--- Returns a vector in the same direction as , with a length clamped between (min) and (max) +e2function vector clamp(vector Input, Min, Max) + if Min < 0 then Min = 0 end + local x,y,z = Input[1], Input[2], Input[3] + local length = x*x+y*y+z*z + if length < Min*Min then + length = Min*(length ^ -0.5) -- Min*(length ^ -0.5) <=> Min/sqrt(length) + elseif length > Max*Max then + length = Max*(length ^ -0.5) -- Max*(length ^ -0.5) <=> Max/sqrt(length) + else + return Input + end + + return { x*length, y*length, z*length } +end + +-------------------------------------------------------------------------------- + +__e2setcost(1) + +e2function number vector:x() + return this[1] +end + +e2function number vector:y() + return this[2] +end + +e2function number vector:z() + return this[3] +end + +__e2setcost(2) + +--- SET method that returns a new vector with x replaced +e2function vector vector:setX(x) + return { x, this[2], this[3] } +end + +--- SET method that returns a new vector with y replaced +e2function vector vector:setY(y) + return { this[1], y, this[3] } +end + +--- SET method that returns a new vector with z replaced +e2function vector vector:setZ(z) + return { this[1], this[2], z } +end + +-------------------------------------------------------------------------------- + +__e2setcost(6) + +e2function vector round(vector rv1) + return { + floor(rv1[1] + 0.5), + floor(rv1[2] + 0.5), + floor(rv1[3] + 0.5) + } +end + +e2function vector round(vector rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf + 0.5) / shf, + floor(rv1[2] * shf + 0.5) / shf, + floor(rv1[3] * shf + 0.5) / shf + } +end + +e2function vector ceil( vector rv1 ) + return { + ceil(rv1[1]), + ceil(rv1[2]), + ceil(rv1[3]) + } +end + +e2function vector ceil(vector rv1, decimals) + local shf = 10 ^ decimals + return { + ceil(rv1[1] * shf) / shf, + ceil(rv1[2] * shf) / shf, + ceil(rv1[3] * shf) / shf + } +end + +e2function vector floor(vector rv1) + return { + floor(rv1[1]), + floor(rv1[2]), + floor(rv1[3]) + } +end + +e2function vector floor(vector rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf) / shf, + floor(rv1[2] * shf) / shf, + floor(rv1[3] * shf) / shf + } +end + +__e2setcost(10) + +--- min/max based on vector length - returns shortest/longest vector +e2function vector min(vector rv1, vector rv2) + local length1 = ( rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] ) ^ 0.5 + local length2 = ( rv2[1] * rv2[1] + rv2[2] * rv2[2] + rv2[3] * rv2[3] ) ^ 0.5 + if length1 < length2 then return rv1 else return rv2 end +end + +e2function vector max(vector rv1, vector rv2) + local length1 = ( rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] ) ^ 0.5 + local length2 = ( rv2[1] * rv2[1] + rv2[2] * rv2[2] + rv2[3] * rv2[3] ) ^ 0.5 + if length1 > length2 then return rv1 else return rv2 end +end + +--- component-wise min/max +e2function vector maxVec(vector rv1, vector rv2) + return { + rv1[1] > rv2[1] and rv1[1] or rv2[1], + rv1[2] > rv2[2] and rv1[2] or rv2[2], + rv1[3] > rv2[3] and rv1[3] or rv2[3], + } +end + +e2function vector minVec(vector rv1, vector rv2) + return { + rv1[1] < rv2[1] and rv1[1] or rv2[1], + rv1[2] < rv2[2] and rv1[2] or rv2[2], + rv1[3] < rv2[3] and rv1[3] or rv2[3], + } +end + +--- Performs modulo on x,y,z separately +e2function vector mod(vector rv1, rv2) + return { + rv1[1] >= 0 and rv1[1] % rv2 or rv1[1] % -rv2, + rv1[2] >= 0 and rv1[2] % rv2 or rv1[2] % -rv2, + rv1[3] >= 0 and rv1[3] % rv2 or rv1[3] % -rv2, + } +end + +--- Modulo where divisors are defined as a vector +e2function vector mod(vector rv1, vector rv2) + return { + rv1[1] >= 0 and rv1[1] % rv2[1] or rv1[1] % -rv2[1], + rv1[2] >= 0 and rv1[2] % rv2[2] or rv1[2] % -rv2[2], + rv1[3] >= 0 and rv1[3] % rv2[3] or rv1[3] % -rv2[3], + } +end + +--- Clamp according to limits defined by two min/max vectors +e2function vector clamp(vector value, vector min, vector max) + local x,y,z + + if value[1] < min[1] then x = min[1] + elseif value[1] > max[1] then x = max[1] + else x = value[1] end + + if value[2] < min[2] then y = min[2] + elseif value[2] > max[2] then y = max[2] + else y = value[2] end + + if value[3] < min[3] then z = min[3] + elseif value[3] > max[3] then z = max[3] + else z = value[3] end + + return {x, y, z} +end + +--- Mix two vectors by a given proportion (between 0 and 1) +e2function vector mix(vector vec1, vector vec2, ratio) + return { + vec1[1] * ratio + vec2[1] * (1-ratio), + vec1[2] * ratio + vec2[2] * (1-ratio), + vec1[3] * ratio + vec2[3] * (1-ratio) + } +end + +e2function vector bezier(vector startVec, vector control, vector endVec, ratio) + return { + (1-ratio)^2 * startVec[1] + (2 * (1-ratio) * ratio * control[1]) + ratio^2 * endVec[1], + (1-ratio)^2 * startVec[2] + (2 * (1-ratio) * ratio * control[2]) + ratio^2 * endVec[2], + (1-ratio)^2 * startVec[3] + (2 * (1-ratio) * ratio * control[3]) + ratio^2 * endVec[3] + } +end + +__e2setcost(2) + +--- Circular shift function: shiftR(vec(x,y,z)) = vec(z,x,y) +e2function vector shiftR(vector vec) + return { vec[3], vec[1], vec[2] } +end + +--- Circular shift function: shiftL(vec(x,y,z)) = vec(y,z,x) +e2function vector shiftL(vector vec) + return { vec[2], vec[3], vec[1] } +end + +__e2setcost(5) + +--- Returns 1 if the vector lies between (or is equal to) the min/max vectors +e2function number inrange(vector vec, vector min, vector max) + if vec[1] < min[1] then return 0 end + if vec[2] < min[2] then return 0 end + if vec[3] < min[3] then return 0 end + + if vec[1] > max[1] then return 0 end + if vec[2] > max[2] then return 0 end + if vec[3] > max[3] then return 0 end + + return 1 +end + +-------------------------------------------------------------------------------- + +__e2setcost(3) + +e2function angle vector:toAngle() + local angle = Vector(this[1], this[2], this[3]):Angle() + return { angle.p, angle.y, angle.r } +end + +e2function angle vector:toAngle(vector up) + local angle = Vector(this[1], this[2], this[3]):AngleEx(Vector(up[1], up[2], up[3])) + return { angle.p, angle.y, angle.r } +end + +-------------------------------------------------------------------------------- + +local contents = {} +for k,v in pairs(_G) do + if (k:sub(1,9) == "CONTENTS_") then + contents[v] = k:sub(10):lower() + end +end + +local cachemeta = {} + +local cache_parts_array = setmetatable({ [0] = {} }, cachemeta) +local cache_lookup_table = setmetatable({ [0] = { empty = true } }, cachemeta) +local cache_concatenated_parts = setmetatable({ [0] = "empty" }, cachemeta) + +local function generateContents( n ) + local parts_array, lookup_table = {}, {} + local ret = {} + + for i = 0,30 do + if bit.band(n, (2^i)) ~= 0 then + local name = contents[2^i] + lookup_table[name] = true + parts_array[#parts_array+1] = name + end + end + + concatenated_parts = table.concat(parts_array, ",") + + cache_parts_array[n] = parts_array + cache_lookup_table[n] = lookup_table + cache_concatenated_parts[n] = concatenated_parts + return concatenated_parts +end + +function cachemeta:__index(n) + generateContents(n) + return rawget(self, n) +end + +__e2setcost( 20 ) + +e2function number pointHasContent( vector point, string has ) + local cont = cache_lookup_table[util.PointContents(Vector(point[1], point[2], point[3]))] + + has = has:gsub(" ", "_"):lower() + + for m in has:gmatch("([^,]+),?") do + if cont[m] then return 1 end + end + + return 0 +end + +__e2setcost( 15 ) + +e2function string pointContents( vector point ) + return cache_concatenated_parts[util.PointContents( Vector(point[1],point[2],point[3]))] +end + +e2function array pointContentsArray( vector point ) + return cache_parts_array[util.PointContents( Vector(point[1],point[2],point[3]))] +end + +-------------------------------------------------------------------------------- + +__e2setcost(15) + +--- Converts a local position/angle to a world position/angle and returns the position +e2function vector toWorld( vector localpos, angle localang, vector worldpos, angle worldang ) + local localpos = Vector(localpos[1],localpos[2],localpos[3]) + local localang = Angle(localang[1],localang[2],localang[3]) + local worldpos = Vector(worldpos[1],worldpos[2],worldpos[3]) + local worldang = Angle(worldang[1],worldang[2],worldang[3]) + return LocalToWorld(localpos,localang,worldpos,worldang) +end + +--- Converts a local position/angle to a world position/angle and returns the angle +e2function angle toWorldAng( vector localpos, angle localang, vector worldpos, angle worldang ) + local localpos = Vector(localpos[1],localpos[2],localpos[3]) + local localang = Angle(localang[1],localang[2],localang[3]) + local worldpos = Vector(worldpos[1],worldpos[2],worldpos[3]) + local worldang = Angle(worldang[1],worldang[2],worldang[3]) + local pos, ang = LocalToWorld(localpos,localang,worldpos,worldang) + return {ang.p,ang.y,ang.r} +end + +--- Converts a local position/angle to a world position/angle and returns both in an array +e2function array toWorldPosAng( vector localpos, angle localang, vector worldpos, angle worldang ) + local localpos = Vector(localpos[1],localpos[2],localpos[3]) + local localang = Angle(localang[1],localang[2],localang[3]) + local worldpos = Vector(worldpos[1],worldpos[2],worldpos[3]) + local worldang = Angle(worldang[1],worldang[2],worldang[3]) + local pos, ang = LocalToWorld(localpos,localang,worldpos,worldang) + return {pos, {ang.p,ang.y,ang.r}} +end + +--- Converts a world position/angle to a local position/angle and returns the position +e2function vector toLocal( vector localpos, angle localang, vector worldpos, angle worldang ) + local localpos = Vector(localpos[1],localpos[2],localpos[3]) + local localang = Angle(localang[1],localang[2],localang[3]) + local worldpos = Vector(worldpos[1],worldpos[2],worldpos[3]) + local worldang = Angle(worldang[1],worldang[2],worldang[3]) + return WorldToLocal(localpos,localang,worldpos,worldang) +end + +--- Converts a world position/angle to a local position/angle and returns the angle +e2function angle toLocalAng( vector localpos, angle localang, vector worldpos, angle worldang ) + local localpos = Vector(localpos[1],localpos[2],localpos[3]) + local localang = Angle(localang[1],localang[2],localang[3]) + local worldpos = Vector(worldpos[1],worldpos[2],worldpos[3]) + local worldang = Angle(worldang[1],worldang[2],worldang[3]) + local vec, ang = WorldToLocal(localpos,localang,worldpos,worldang) + return {ang.p,ang.y,ang.r} +end + +--- Converts a world position/angle to a local position/angle and returns both in an array +e2function array toLocalPosAng( vector localpos, angle localang, vector worldpos, angle worldang ) + local localpos = Vector(localpos[1],localpos[2],localpos[3]) + local localang = Angle(localang[1],localang[2],localang[3]) + local worldpos = Vector(worldpos[1],worldpos[2],worldpos[3]) + local worldang = Angle(worldang[1],worldang[2],worldang[3]) + local pos, ang = WorldToLocal(localpos,localang,worldpos,worldang) + return {pos, {ang.p,ang.y,ang.r}} +end + +-------------------------------------------------------------------------------- +-- Credits to Wizard of Ass for bearing(v,a,v) and elevation(v,a,v) + +e2function number bearing(vector originpos,angle originangle, vector pos) + pos = WorldToLocal(Vector(pos[1],pos[2],pos[3]),Angle(0,0,0),Vector(originpos[1],originpos[2],originpos[3]),Angle(originangle[1],originangle[2],originangle[3])) + return rad2deg*-atan2(pos.y, pos.x) +end + +e2function number elevation(vector originpos,angle originangle, vector pos) + pos = WorldToLocal(Vector(pos[1],pos[2],pos[3]),Angle(0,0,0),Vector(originpos[1],originpos[2],originpos[3]),Angle(originangle[1],originangle[2],originangle[3])) + local len = pos:Length() + if (len < delta) then return 0 end + return rad2deg*asin(pos.z / len) +end + +e2function angle heading(vector originpos,angle originangle, vector pos) + pos = WorldToLocal(Vector(pos[1],pos[2],pos[3]),Angle(0,0,0),Vector(originpos[1],originpos[2],originpos[3]),Angle(originangle[1],originangle[2],originangle[3])) + + local bearing = rad2deg*-atan2(pos.y, pos.x) + + local len = pos:Length() + if (len < delta) then return { 0, bearing, 0 } end + return { rad2deg*asin(pos.z / len), bearing, 0 } +end + +-------------------------------------------------------------------------------- + +__e2setcost( 10 ) + + +e2function number vector:isInWorld() + if util.IsInWorld(Vector(this[1], this[2], this[3])) then return 1 else return 0 end +end + +__e2setcost( 5 ) + +--- Gets the vector nicely formatted as a string "[X,Y,Z]" +e2function string toString(vector v) + return ("[%s,%s,%s]"):format(v[1],v[2],v[3]) +end + +--- Gets the vector nicely formatted as a string "[X,Y,Z]" +e2function string vector:toString() = e2function string toString(vector v) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/vector2.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/vector2.lua new file mode 100644 index 0000000..f50c599 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/vector2.lua @@ -0,0 +1,1112 @@ +/******************************************************************************\ + 2D Vector support +\******************************************************************************/ + +local delta = wire_expression2_delta + +local floor = math.floor +local ceil = math.ceil +local random = math.random +local pi = math.pi + +/******************************************************************************/ + +registerType("vector2", "xv2", { 0, 0 }, + function(self, input) return { input[1], input[2] } end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 2 then error("Return value does not have exactly 2 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 2 + end +) + +/******************************************************************************/ + +__e2setcost(1) -- approximated + +registerFunction("vec2", "", "xv2", function(self, args) + return { 0, 0 } +end) + +__e2setcost(2) + +registerFunction("vec2", "nn", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1, rv2 } +end) + +registerFunction("vec2", "n", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1, rv1 } +end) + +registerFunction("vec2", "v", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1[1], rv1[2] } +end) + +registerFunction("vec2", "xv4", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1[1], rv1[2] } +end) + +/******************************************************************************/ + +registerOperator("ass", "xv2", "xv2", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ + +registerOperator("is", "xv2", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1[1] > delta || -rv1[1] > delta || + rv1[2] > delta || -rv1[2] > delta + then return 1 else return 0 end +end) + +registerOperator("eq", "xv2xv2", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1[1] - rv2[1] <= delta && rv2[1] - rv1[1] <= delta && + rv1[2] - rv2[2] <= delta && rv2[2] - rv1[2] <= delta + then return 1 else return 0 end +end) + +registerOperator("neq", "xv2xv2", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1[1] - rv2[1] > delta || rv2[1] - rv1[1] > delta || + rv1[2] - rv2[2] > delta || rv2[2] - rv1[2] > delta + then return 1 else return 0 end +end) + +/******************************************************************************/ + +registerOperator("neg", "xv2", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { -rv1[1], -rv1[2] } +end) + +registerOperator("add", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] + rv2[1], rv1[2] + rv2[2] } +end) + +registerOperator("sub", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] - rv2[1], rv1[2] - rv2[2] } +end) + +registerOperator("mul", "nxv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1 * rv2[1], rv1 * rv2[2] } +end) + +registerOperator("mul", "xv2n", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] * rv2, rv1[2] * rv2 } +end) + +registerOperator("mul", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] * rv2[1], rv1[2] * rv2[2] } +end) + +registerOperator("div", "nxv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1 / rv2[1], rv1 / rv2[2] } +end) + +registerOperator("div", "xv2n", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] / rv2, rv1[2] / rv2 } +end) + +registerOperator("div", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] / rv2[1], rv1[2] / rv2[2] } +end) + +e2function number vector2:operator[](index) + return this[floor(math.Clamp(index, 1, 2) + 0.5)] +end + +e2function number vector2:operator[](index, value) + this[floor(math.Clamp(index, 1, 2) + 0.5)] = value + return value +end + +/******************************************************************************/ + +__e2setcost(3) + +registerFunction("length", "xv2:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return (rv1[1] * rv1[1] + rv1[2] * rv1[2] ) ^ 0.5 +end) + +registerFunction("length2", "xv2:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[1] * rv1[1] + rv1[2] * rv1[2] +end) + +registerFunction("distance", "xv2:xv2", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local rvd1, rvd2 = rv1[1] - rv2[1], rv1[2] - rv2[2] + return (rvd1 * rvd1 + rvd2 * rvd2 ) ^ 0.5 +end) + +registerFunction("distance2", "xv2:xv2", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local rvd1, rvd2 = rv1[1] - rv2[1], rv1[2] - rv2[2] + return rvd1 * rvd1 + rvd2 * rvd2 +end) + +registerFunction("normalized", "xv2:", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + local len = (rv1[1] * rv1[1] + rv1[2] * rv1[2] ) ^ 0.5 + if len > delta then + return { rv1[1] / len, rv1[2] / len } + else + return { 0, 0 } + end +end) + +registerFunction("dot", "xv2:xv2", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1[1] * rv2[1] + rv1[2] * rv2[2] +end) + +registerFunction("cross", "xv2:xv2", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1[1] * rv2[2] - rv1[2] * rv2[1] +end) + +-- returns the outer product (tensor product) of two vectors +registerFunction("outerProduct", "xv2:xv2", "xm2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] * rv1[1], rv1[1] * rv2[2], + rv1[2] * rv1[1], rv1[2] * rv2[2] } +end) + +registerFunction("rotate", "xv2:n", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local a = rv2 * pi / 180 + local x = math.cos(a) * rv1[1] - math.sin(a) * rv1[2] + local y = math.sin(a) * rv1[1] + math.cos(a) * rv1[2] + return { x, y } +end) + +registerFunction("positive", "xv2", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + local x, y + if rv1[1] >= 0 then x = rv1[1] else x = -rv1[1] end + if rv1[2] >= 0 then y = rv1[2] else y = -rv1[2] end + return { x, y } +end) + +__e2setcost(2) + +// Convert the magnitude of the vector to radians +e2function vector2 toRad(vector2 xv2) + return {xv2[1] * pi / 180, xv2[2] * pi / 180} +end + +// Convert the magnitude of the vector to degrees +e2function vector2 toDeg(vector2 xv2) + return {xv2[1] * 180 / pi, xv2[2] * 180 / pi} +end + +/******************************************************************************/ + +__e2setcost(3) + +--- Returns a vector in the same direction as , with a length clamped between (min) and (max) +e2function vector2 clamp(vector2 Input, Min, Max) + if Min < 0 then Min = 0 end + local x,y = Input[1], Input[2] + local length = x*x+y*y + if length < Min*Min then + length = Min*(length ^ -0.5) -- Min*(length ^ -0.5) <=> Min/sqrt(length) + elseif length > Max*Max then + length = Max*(length ^ -0.5) -- Max*(length ^ -0.5) <=> Max/sqrt(length) + else + return Input + end + + return { x*length, y*length } +end + +/******************************************************************************/ + +__e2setcost(1) + +registerFunction("x", "xv2:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[1] +end) + +registerFunction("y", "xv2:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[2] +end) + +// SET methods that returns vectors - you shouldn't need these for 2D vectors, but I've added them anyway for consistency +// NOTE: does not change the original vector! +registerFunction("setX", "xv2:n", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv2, rv1[2] } +end) + +registerFunction("setY", "xv2:n", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1], rv2 } +end) + +/******************************************************************************/ + +__e2setcost(4) + +e2function vector2 round(vector2 rv1) + return { + floor(rv1[1] + 0.5), + floor(rv1[2] + 0.5) + } +end + +e2function vector2 round(vector2 rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf + 0.5) / shf, + floor(rv1[2] * shf + 0.5) / shf + } +end + +e2function vector2 ceil( vector2 rv1 ) + return { + ceil(rv1[1]), + ceil(rv1[2]) + } +end + +e2function vector2 ceil(vector2 rv1, decimals) + local shf = 10 ^ decimals + return { + ceil(rv1[1] * shf) / shf, + ceil(rv1[2] * shf) / shf + } +end + +e2function vector2 floor(vector2 rv1) + return { + floor(rv1[1]), + floor(rv1[2]) + } +end + +e2function vector2 floor(vector2 rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf) / shf, + floor(rv1[2] * shf) / shf + } +end + +// min/max based on vector length - returns shortest/longest vector +registerFunction("min", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local length1 = ( rv1[1] * rv1[1] + rv1[2] * rv1[2] ) ^ 0.5 + local length2 = ( rv2[1] * rv2[1] + rv2[2] * rv2[2] ) ^ 0.5 + if length1 < length2 then return rv1 else return rv2 end +end) + +registerFunction("max", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local length1 = ( rv1[1] * rv1[1] + rv1[2] * rv1[2] ) ^ 0.5 + local length2 = ( rv2[1] * rv2[1] + rv2[2] * rv2[2] ) ^ 0.5 + if length1 > length2 then return rv1 else return rv2 end +end) + +// component-wise min/max +registerFunction("maxVec", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x, y + if rv1[1] > rv2[1] then x = rv1[1] else x = rv2[1] end + if rv1[2] > rv2[2] then y = rv1[2] else y = rv2[2] end + return {x, y} +end) + +registerFunction("minVec", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x, y + if rv1[1] < rv2[1] then x = rv1[1] else x = rv2[1] end + if rv1[2] < rv2[2] then y = rv1[2] else y = rv2[2] end + return {x, y} +end) + +// Performs modulo on x,y separately +registerFunction("mod", "xv2n", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x, y + + if rv1[1] >= 0 then + x = rv1[1] % rv2 + else x = rv1[1] % -rv2 end + + if rv1[2] >= 0 then + y = rv1[2] % rv2 + else y = rv1[2] % -rv2 end + + return { x, y } +end) + +// Modulo where divisors are defined as a vector +registerFunction("mod", "xv2xv2", "xv2", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x, y + + if rv1[1] >= 0 then + x = rv1[1] % rv2[1] + else x = rv1[1] % -rv2[1] end + + if rv1[2] >= 0 then + y = rv1[2] % rv2[2] + else y = rv1[2] % -rv2[2] end + + return { x, y } +end) + +// Clamp according to limits defined by two min/max vectors +registerFunction("clamp", "xv2xv2xv2", "xv2", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + local x, y + + if rv1[1] < rv2[1] then x = rv2[1] + elseif rv1[1] > rv3[1] then x = rv3[1] + else x = rv1[1] end + + if rv1[2] < rv2[2] then y = rv2[2] + elseif rv1[2] > rv3[2] then y = rv3[2] + else y = rv1[2] end + + return { x, y } +end) + +// Mix two vectors by a given proportion (between 0 and 1) +registerFunction("mix", "xv2xv2n", "xv2", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + + local x = rv1[1] * rv3 + rv2[1] * (1-rv3) + local y = rv1[2] * rv3 + rv2[2] * (1-rv3) + return { x, y } +end) + +e2function vector2 bezier(vector2 startVec, vector2 control, vector2 endVec, ratio) + return { + (1-ratio)^2 * startVec[1] + (2 * (1-ratio) * ratio * control[1]) + ratio^2 * endVec[1], + (1-ratio)^2 * startVec[2] + (2 * (1-ratio) * ratio * control[2]) + ratio^2 * endVec[2] + } +end + +__e2setcost(2) + +// swap x/y +registerFunction("shift", "xv2", "xv2", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1[2], rv1[1] } +end) + +// Returns 1 if the vector lies between (or is equal to) the min/max vectors +registerFunction("inrange", "xv2xv2xv2", "n", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + + if rv1[1] < rv2[1] then return 0 end + if rv1[2] < rv2[2] then return 0 end + if rv1[1] > rv3[1] then return 0 end + if rv1[2] > rv3[2] then return 0 end + + return 1 +end) + +/******************************************************************************/ + +registerFunction("toAngle", "xv2:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + local angle = math.atan2( rv1[2], rv1[1] ) * 180 / pi + --if (angle < 0) then angle = angle + 180 end + return angle +end) + +__e2setcost(5) + +e2function string toString(vector2 v) + return ("[%s,%s]"):format(v[1],v[2]) +end + +e2function string vector2:toString() = e2function string toString(vector2 v) + +-- register a formatter for the debugger +WireLib.registerDebuggerFormat("VECTOR2", function(value) + return string.format("(%.2f, %.2f)", value[1], value[2]) +end) + +/******************************************************************************/ + +__e2setcost(5) + +-- Returns a random vector2 between -1 and 1 +e2function vector2 randvec2() + local randomang = random() * pi * 2 + return { math.cos( randomang ), math.sin( randomang ) } +end + +-- Returns a random vector2 between min and max +e2function vector2 randvec2(min,max) + return { min+random()*(max-min), min+random()*(max-min) } +end + +-- Returns a random vector2 between vec2 min and vec2 max +e2function vector2 randvec2( vector2 min, vector2 max ) + return { min[1]+random()*(max[1]-min[1]), min[2]+random()*(max[2]-min[2]) } +end + +/******************************************************************************\ + 4D Vector support +\******************************************************************************/ + +//NOTE: These are purely cartesian 4D vectors, so "w" denotes the 4th coordinate rather than a scaling factor as with an homogeneous coordinate system + +/******************************************************************************/ + +registerType("vector4", "xv4", { 0, 0, 0, 0 }, + function(self, input) return { input[1], input[2], input[3], input[4] } end, + nil, + function(retval) + if !istable(retval) then error("Return value is not a table, but a "..type(retval).."!",0) end + if #retval ~= 4 then error("Return value does not have exactly 4 entries!",0) end + end, + function(v) + return !istable(v) or #v ~= 4 + end +) + +/******************************************************************************/ + +__e2setcost(1) -- approximated + +registerFunction("vec4", "", "xv4", function(self, args) + return { 0, 0, 0, 0 } +end) + +__e2setcost(4) + +registerFunction("vec4", "n", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1, rv1, rv1, rv1 } +end) + +registerFunction("vec4", "nnnn", "xv4", function(self, args) + local op1, op2, op3, op4 = args[2], args[3], args[4], args[5] + local rv1, rv2, rv3, rv4 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3), op4[1](self, op4) + return { rv1, rv2, rv3, rv4 } +end) + +registerFunction("vec4", "xv2", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1[1], rv1[2], 0, 0 } +end) + +registerFunction("vec4", "xv2nn", "xv4", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + return { rv1[1], rv1[2], rv2, rv3 } +end) + +registerFunction("vec4", "xv2xv2", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1], rv1[2], rv2[1], rv2[2] } +end) + +registerFunction("vec4", "v", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { rv1[1], rv1[2], rv1[3], 0 } +end) + +registerFunction("vec4", "vn", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1], rv1[2], rv1[3], rv2 } +end) + +/******************************************************************************/ + +registerOperator("ass", "xv4", "xv4", function(self, args) + local op1, op2, scope = args[2], args[3], args[4] + local rv2 = op2[1](self, op2) + self.Scopes[scope][op1] = rv2 + self.Scopes[scope].vclk[op1] = true + return rv2 +end) + +/******************************************************************************/ + +registerOperator("is", "xv4", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + if rv1[1] > delta || -rv1[1] > delta || + rv1[2] > delta || -rv1[2] > delta || + rv1[3] > delta || -rv1[3] > delta || + rv1[4] > delta || -rv1[4] > delta + then return 1 else return 0 end +end) + +registerOperator("eq", "xv4xv4", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1[1] - rv2[1] <= delta && rv2[1] - rv1[1] <= delta && + rv1[2] - rv2[2] <= delta && rv2[2] - rv1[2] <= delta && + rv1[3] - rv2[3] <= delta && rv2[3] - rv1[3] <= delta && + rv1[4] - rv2[4] <= delta && rv2[4] - rv1[4] <= delta + then return 1 else return 0 end +end) + +registerOperator("neq", "xv4xv4", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + if rv1[1] - rv2[1] > delta || rv2[1] - rv1[1] > delta || + rv1[2] - rv2[2] > delta || rv2[2] - rv1[2] > delta || + rv1[3] - rv2[3] > delta || rv2[3] - rv1[3] > delta || + rv1[4] - rv2[4] > delta || rv2[4] - rv1[4] > delta + then return 1 else return 0 end +end) + +/******************************************************************************/ + +registerOperator("neg", "xv4", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return { -rv1[1], -rv1[2], -rv1[3], -rv1[4] } +end) + +registerOperator("add", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] + rv2[1], rv1[2] + rv2[2], rv1[3] + rv2[3], rv1[4] + rv2[4] } +end) + +registerOperator("sub", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] - rv2[1], rv1[2] - rv2[2], rv1[3] - rv2[3], rv1[4] - rv2[4] } +end) + +registerOperator("mul", "nxv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1 * rv2[1], rv1 * rv2[2], rv1 * rv2[3], rv1 * rv2[4] } +end) + +registerOperator("mul", "xv4n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] * rv2, rv1[2] * rv2, rv1[3] * rv2, rv1[4] * rv2 } +end) + +registerOperator("mul", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] * rv2[1], rv1[2] * rv2[2], rv1[3] * rv2[3], rv1[4] * rv2[4] } +end) + +registerOperator("div", "nxv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1 / rv2[1], rv1 / rv2[2], rv1 / rv2[3], rv1 / rv2[4] } +end) + +registerOperator("div", "xv4n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] / rv2, rv1[2] / rv2, rv1[3] / rv2, rv1[4] / rv2 } +end) + +registerOperator("div", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] / rv2[1], rv1[2] / rv2[2], rv1[3] / rv2[3], rv1[4] / rv2[4] } +end) + +e2function number vector4:operator[](index) + return this[floor(math.Clamp(index, 1, 4) + 0.5)] +end + +e2function number vector4:operator[](index, value) + this[floor(math.Clamp(index, 1, 4) + 0.5)] = value + return value +end + +/******************************************************************************/ + +__e2setcost(7) + +registerFunction("length", "xv4:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return (rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + rv1[4] * rv1[4]) ^ 0.5 +end) + +registerFunction("length2", "xv4:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + rv1[4] * rv1[4] +end) + +registerFunction("distance", "xv4:xv4", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local rvd1, rvd2, rvd3, rvd4 = rv1[1] - rv2[1], rv1[2] - rv2[2], rv1[3] - rv2[3], rv1[4] - rv2[4] + return (rvd1 * rvd1 + rvd2 * rvd2 + rvd3 * rvd3 + rvd4 * rvd4) ^ 0.5 +end) + +registerFunction("distance2", "xv4:xv4", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local rvd1, rvd2, rvd3 = rv1[1] - rv2[1], rv1[2] - rv2[2], rv1[3] - rv2[3], rv1[4] - rv2[4] + return rvd1 * rvd1 + rvd2 * rvd2 + rvd3 * rvd3 + rvd4 * rvd4 +end) + +registerFunction("dot", "xv4:xv4", "n", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return rv1[1] * rv2[1] + rv1[2] * rv2[2] + rv1[3] * rv2[3] + rv1[4] * rv2[4] +end) + +__e2setcost(15) + +-- returns the outer product (tensor product) of two vectors +registerFunction("outerProduct", "xv4:xv4", "xm4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1] * rv1[1], rv1[1] * rv2[2], rv1[1] * rv2[3], rv1[1] * rv2[4], + rv1[2] * rv1[1], rv1[2] * rv2[2], rv1[2] * rv2[3], rv1[2] * rv2[4], + rv1[3] * rv1[1], rv1[3] * rv2[2], rv1[3] * rv2[3], rv1[3] * rv2[4], + rv1[4] * rv1[1], rv1[4] * rv2[2], rv1[4] * rv2[3], rv1[4] * rv2[4] } +end) + +__e2setcost(7) + +registerFunction("normalized", "xv4:", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + local len = (rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + rv1[4] * rv1[4]) ^ 0.5 + if len > delta then + return { rv1[1] / len, rv1[2] / len, rv1[3] / len, rv1[4] / len } + else + return { 0, 0, 0, 0 } + end +end) + +__e2setcost(3) + +registerFunction("dehomogenized", "xv4:", "v", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + local w = rv1[4] + if w == 0 then return { rv1[1], rv1[2], rv1[3] } end + return { rv1[1]/w, rv1[2]/w, rv1[3]/w } +end) + +__e2setcost(4) + +registerFunction("positive", "xv4", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + local x, y, z, w + if rv1[1] >= 0 then x = rv1[1] else x = -rv1[1] end + if rv1[2] >= 0 then y = rv1[2] else y = -rv1[2] end + if rv1[3] >= 0 then z = rv1[3] else z = -rv1[3] end + if rv1[4] >= 0 then w = rv1[4] else w = -rv1[4] end + return { x, y, z, w } +end) + +/******************************************************************************/ + +__e2setcost(2) + +registerFunction("x", "xv4:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[1] +end) + +registerFunction("y", "xv4:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[2] +end) + +registerFunction("z", "xv4:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[3] +end) + +registerFunction("w", "xv4:", "n", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return rv1[4] +end) + +__e2setcost(3) + +// SET methods that returns vectors +// NOTE: does not change the original vector! +registerFunction("setX", "xv4:n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv2, rv1[2], rv1[3], rv1[4] } +end) + +registerFunction("setY", "xv4:n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1], rv2, rv1[3], rv1[4] } +end) + +registerFunction("setZ", "xv4:n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1], rv1[2], rv2, rv1[4] } +end) + +registerFunction("setW", "xv4:n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + return { rv1[1], rv1[2], rv1[3], rv2 } +end) + +/******************************************************************************/ + +__e2setcost(8) + +e2function vector4 round(vector4 rv1) + return { + floor(rv1[1] + 0.5), + floor(rv1[2] + 0.5), + floor(rv1[3] + 0.5), + floor(rv1[4] + 0.5) + } +end + +e2function vector4 round(vector4 rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf + 0.5) / shf, + floor(rv1[2] * shf + 0.5) / shf, + floor(rv1[3] * shf + 0.5) / shf, + floor(rv1[4] * shf + 0.5) / shf + } +end + +e2function vector4 ceil( vector4 rv1 ) + return { + ceil(rv1[1]), + ceil(rv1[2]), + ceil(rv1[3]), + ceil(rv1[4]), + } +end + +e2function vector4 ceil(vector4 rv1, decimals) + local shf = 10 ^ decimals + return { + ceil(rv1[1] * shf) / shf, + ceil(rv1[2] * shf) / shf, + ceil(rv1[3] * shf) / shf, + ceil(rv1[4] * shf) / shf + } +end + +e2function vector4 floor(vector4 rv1) + return { + floor(rv1[1]), + floor(rv1[2]), + floor(rv1[3]), + floor(rv1[4]) + } +end + +e2function vector4 floor(vector4 rv1, decimals) + local shf = 10 ^ decimals + return { + floor(rv1[1] * shf) / shf, + floor(rv1[2] * shf) / shf, + floor(rv1[3] * shf) / shf, + floor(rv1[4] * shf) / shf + } +end + +__e2setcost(13) + +// min/max based on vector length - returns shortest/longest vector +registerFunction("min", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local length1 = ( rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + rv1[4] * rv1[4] ) ^ 0.5 + local length2 = ( rv2[1] * rv2[1] + rv2[2] * rv2[2] + rv2[3] * rv2[3] + rv2[4] * rv2[4] ) ^ 0.5 + if length1 < length2 then return rv1 else return rv2 end +end) + +registerFunction("max", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local length1 = ( rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + rv1[4] * rv1[4] ) ^ 0.5 + local length2 = ( rv2[1] * rv2[1] + rv2[2] * rv2[2] + rv2[3] * rv2[3] + rv2[4] * rv2[4] ) ^ 0.5 + if length1 > length2 then return rv1 else return rv2 end +end) + +// component-wise min/max +registerFunction("maxVec", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x, y, z, w + if rv1[1] > rv2[1] then x = rv1[1] else x = rv2[1] end + if rv1[2] > rv2[2] then y = rv1[2] else y = rv2[2] end + if rv1[3] > rv2[3] then z = rv1[3] else z = rv2[3] end + if rv1[4] > rv2[4] then w = rv1[4] else w = rv2[4] end + return {x, y, z, w} +end) + +registerFunction("minVec", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x, y, z, w + if rv1[1] < rv2[1] then x = rv1[1] else x = rv2[1] end + if rv1[2] < rv2[2] then y = rv1[2] else y = rv2[2] end + if rv1[3] < rv2[3] then z = rv1[3] else z = rv2[3] end + if rv1[4] < rv2[4] then w = rv1[4] else w = rv2[4] end + return {x, y, z, w} +end) + +// Performs modulo on x,y,z separately +registerFunction("mod", "xv4n", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x,y,z,w + if rv1[1] >= 0 then + x = rv1[1] % rv2 + else x = rv1[1] % -rv2 end + if rv1[2] >= 0 then + y = rv1[2] % rv2 + else y = rv1[2] % -rv2 end + if rv1[3] >= 0 then + z = rv1[3] % rv2 + else z = rv1[3] % -rv2 end + if rv1[4] >= 0 then + w = rv1[4] % rv2 + else w = rv1[4] % -rv2 end + return {x, y, z, w} +end) + +// Modulo where divisors are defined as a vector +registerFunction("mod", "xv4xv4", "xv4", function(self, args) + local op1, op2 = args[2], args[3] + local rv1, rv2 = op1[1](self, op1), op2[1](self, op2) + local x,y,z,w + if rv1[1] >= 0 then + x = rv1[1] % rv2[1] + else x = rv1[1] % -rv2[1] end + if rv1[2] >= 0 then + y = rv1[2] % rv2[2] + else y = rv1[2] % -rv2[2] end + if rv1[3] >= 0 then + z = rv1[3] % rv2[3] + else z = rv1[3] % -rv2[3] end + if rv1[4] >= 0 then + w = rv1[4] % rv2[3] + else w = rv1[4] % -rv2[3] end + return {x, y, z, w} +end) + +// Clamp according to limits defined by two min/max vectors +registerFunction("clamp", "xv4xv4xv4", "xv4", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + local x,y,z,w + + if rv1[1] < rv2[1] then x = rv2[1] + elseif rv1[1] > rv3[1] then x = rv3[1] + else x = rv1[1] end + + if rv1[2] < rv2[2] then y = rv2[2] + elseif rv1[2] > rv3[2] then y = rv3[2] + else y = rv1[2] end + + if rv1[3] < rv2[3] then z = rv2[3] + elseif rv1[3] > rv3[3] then z = rv3[3] + else z = rv1[3] end + + if rv1[4] < rv2[4] then w = rv2[4] + elseif rv1[4] > rv3[4] then w = rv3[4] + else w = rv1[4] end + + return {x, y, z, w} +end) + +--- Returns a vector in the same direction as , with a length clamped between (min) and (max) +e2function vector4 clamp(vector4 Input, Min, Max) + if Min < 0 then Min = 0 end + local x,y,z,w = Input[1], Input[2], Input[3], Input[4] + local length = x*x+y*y+z*z+w*w + if length < Min*Min then + length = Min*(length ^ -0.5) -- Min*(length ^ -0.5) <=> Min/sqrt(length) + elseif length > Max*Max then + length = Max*(length ^ -0.5) -- Max*(length ^ -0.5) <=> Max/sqrt(length) + else + return Input + end + + return { x*length, y*length, z*length, w*length } +end + +// Mix two vectors by a given proportion (between 0 and 1) +registerFunction("mix", "xv4xv4n", "xv4", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + + local x = rv1[1] * rv3 + rv2[1] * (1-rv3) + local y = rv1[2] * rv3 + rv2[2] * (1-rv3) + local z = rv1[3] * rv3 + rv2[3] * (1-rv3) + local w = rv1[4] * rv3 + rv2[4] * (1-rv3) + return {x, y, z, w} +end) + +__e2setcost(4) + +// Circular shift function: shiftR( x,y,z,w ) = ( w,x,y,z ) +registerFunction("shiftR", "xv4", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return {rv1[4], rv1[1], rv1[2], rv1[3]} +end) + +registerFunction("shiftL", "xv4", "xv4", function(self, args) + local op1 = args[2] + local rv1 = op1[1](self, op1) + return {rv1[2], rv1[3], rv1[4], rv1[1]} +end) + +// Returns 1 if the vector lies between (or is equal to) the min/max vectors +registerFunction("inrange", "xv4xv4xv4", "n", function(self, args) + local op1, op2, op3 = args[2], args[3], args[4] + local rv1, rv2, rv3 = op1[1](self, op1), op2[1](self, op2), op3[1](self, op3) + + if rv1[1] < rv2[1] then return 0 end + if rv1[2] < rv2[2] then return 0 end + if rv1[3] < rv2[3] then return 0 end + if rv1[4] < rv2[4] then return 0 end + + if rv1[1] > rv3[1] then return 0 end + if rv1[2] > rv3[2] then return 0 end + if rv1[3] > rv3[3] then return 0 end + if rv1[4] > rv3[4] then return 0 end + + return 1 +end) + +__e2setcost(5) + +// Convert the magnitude of the vector to radians +e2function vector4 toRad(vector4 xv4) + return {xv4[1] * pi / 180, xv4[2] * pi / 180, xv4[3] * pi / 180, xv4[4] * pi / 180} +end + +// Convert the magnitude of the vector to degrees +e2function vector4 toDeg(vector4 xv4) + return {xv4[1] * 180 / pi, xv4[2] * 180 / pi, xv4[3] * 180 / pi, xv4[4] * 180 / pi} +end + +/******************************************************************************/ + +__e2setcost(7) + +-- Returns a random vector4 between -1 and 1 +e2function vector4 randvec4() + local vec = { random()*2-1, random()*2-1, random()*2-1, random()*2-1 } + local length = ( vec[1]^2+vec[2]^2+vec[3]^2+vec[4]^2 ) ^ 0.5 -- x ^ 0.5 <=> math.sqrt( x ) + return { vec[1] / length, vec[2] / length, vec[3] / length, vec[4] / length } +end + +-- Returns a random vector4 between min and max +e2function vector4 randvec4(min,max) + return { min+random()*(max-min), min+random()*(max-min), min+random()*(max-min), min+random()*(max-min) } +end + +-- Returns a random vector4 between vec4 min and vec4 max +e2function vector4 randvec4( vector4 min, vector4 max ) + local minx, miny, minz, minw = min[1], min[2], min[3], min[4] + return { minx+random()*(max[1]-minx), miny+random()*(max[2]-miny), minz+random()*(max[2]-minz), minw+random()*(max[2]-minw) } +end + +/******************************************************************************/ + +e2function string toString(vector4 v) + return ("[%s,%s,%s,%s]"):format(v[1],v[2],v[3],v[4]) +end +e2function string vector4:toString() = e2function string toString(vector4 v) + +-- register a formatter for the debugger +WireLib.registerDebuggerFormat("VECTOR4", function(value) + return string.format("(%.2f, %.2f, %.2f, %.2f)", value[1], value[2], value[3], value[4]) +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/weapon.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/weapon.lua new file mode 100644 index 0000000..57090d5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/weapon.lua @@ -0,0 +1,80 @@ +/******************************************************************************\ + Player-weapon support +\******************************************************************************/ + +__e2setcost(2) -- temporary + +e2function entity entity:weapon() + if not IsValid(this) then return nil end + if not this:IsPlayer() and not this:IsNPC() then return nil end + + return this:GetActiveWeapon() +end + + +e2function entity entity:weapon(string weaponclassname) + if not IsValid(this) then return nil end + if not this:IsPlayer() and not this:IsNPC() then return nil end + + return this:GetWeapon(weaponclassname) +end + +e2function array entity:weapons() + if not IsValid(this) then return {} end + if not this:IsPlayer() then return {} end + local ret = {} + for k,v in pairs(this:GetWeapons()) do + ret[#ret + 1] = v + end + return ret +end + +e2function string entity:primaryAmmoType() + if not IsValid(this) then return "" end + if not this:IsWeapon() then return "" end + + local ammoId = this:GetPrimaryAmmoType() + + return game.GetAmmoName(ammoId) or "" +end + +e2function string entity:secondaryAmmoType() + if not IsValid(this) then return "" end + if not this:IsWeapon() then return "" end + + local ammoId = this:GetSecondaryAmmoType() + + return game.GetAmmoName(ammoId) or "" +end + +e2function number entity:ammoCount(string ammo_type) + if not IsValid(this) then return 0 end + if not this:IsPlayer() then return 0 end + + return this:GetAmmoCount(ammo_type) +end + +e2function number entity:clip1() + if not IsValid(this) then return 0 end + if not this:IsWeapon() then return 0 end + + return this:Clip1() +end + +e2function number entity:clip2() + if not IsValid(this) then return 0 end + if not this:IsWeapon() then return 0 end + + return this:Clip2() +end + +e2function string entity:tool() + if not IsValid(this) then return "" end + if not this:IsPlayer() then return "" end + + local weapon = this:GetActiveWeapon() + if not IsValid(weapon) then return "" end + if weapon:GetClass() ~= "gmod_tool" then return "" end + + return weapon.Mode +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/wirelink.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/wirelink.lua new file mode 100644 index 0000000..9907cc9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/core/wirelink.lua @@ -0,0 +1,675 @@ +/******************************************************************************\ + Wire link support +\******************************************************************************/ + +local floor = math.floor +local Clamp = math.Clamp + +registerCallback("construct", function(self) + self.triggercache = {} +end) + +registerCallback("postexecute", function(self) + for _,ent,portname,value in pairs_map(self.triggercache, unpack) do + WireLib.TriggerInput(ent, portname, value) + end + + self.triggercache = {} +end) + +local function TriggerInput(self,ent, portname, value, typename) + if not ent.Inputs[portname] then return value end + if ent.Inputs[portname].Type ~= typename then return value end + + self.triggercache[ent:EntIndex().."__"..portname] = { ent, portname, value } + + return value +end + +local function validWirelink(self, ent) + if not IsValid(ent) then return false end + if not ent.extended then return false end + if not isOwner(self, ent) then return false end + return true +end + +local function mapOutputAlias(ent, portname) + if ent.OutputAliases and ent.OutputAliases[portname] then + return ent.OutputAliases[portname] + end + + return portname +end + +local function mapInputAlias(ent, portname) + if ent.InputAliases and ent.InputAliases[portname] then + return ent.InputAliases[portname] + end + + return portname +end + +/******************************************************************************/ + +local function WriteStringZero(entity, address, string) + if not entity:WriteCell(address+#string, 0) then return 0 end + + for index = 1,#string do + local byte = string.byte(string,index) + if not entity:WriteCell(address+index-1, byte) then return 0 end + end + return address+#string+1 +end + +local function ReadStringZero(entity, address) + local byte + local tbl = {} + for index = address,address+16384 do + byte = entity:ReadCell(index, byte) + if not byte then return "" end + if byte < 1 then break end + if byte >= 256 then byte = 32 end + table.insert(tbl,string.char(floor(byte))) + end + return table.concat(tbl) +end + +local wa_lookup -- lookup table for tables that were already serialized. +local function WriteArray(entity, address, data) + -- check if enough space is available + if not entity:WriteCell(address+#data-1, 0) then return 0 end + + -- write the trailing 0 byte. + entity:WriteCell(address+#data, 0) + local free_address = address+#data+1 + + for index, value in ipairs(data) do + local tp = type(value) + if tp == "number" then + if not entity:WriteCell(address+index-1, value) then return 0 end + elseif tp == "string" then + if not entity:WriteCell(address+index-1, free_address) then return 0 end + free_address = WriteStringZero(entity, free_address, value) + if free_address == 0 then return 0 end + elseif tp == "table" then + if wa_lookup[value] then + if not entity:WriteCell(address+index-1, wa_lookup[value]) then return 0 end + else + wa_lookup[value] = free_address + if not entity:WriteCell(address+index-1, free_address) then return 0 end + free_address = WriteArray(entity, free_address, value) + end + elseif tp == "Vector" then + if not entity:WriteCell(address+index-1, free_address) then return 0 end + free_address = WriteArray(entity, free_address, { value[1], value[2], value[3] }) + end + end + return free_address +end + +local function writeArraySimple(entity, address, data) + local written = 0 + for index, value in pairs(data) do + if type(value) == "number" then + if not entity:WriteCell(address + index - 1, value) then return 0 end + written = written + 1 + end + end + return written +end + +/******************************************************************************/ + +registerType("wirelink", "xwl", nil, + nil, + nil, + function(retval) + if IsValid(retval) then return end + if retval == nil then return end + if not retval.EntIndex then error("Return value is neither nil nor an Entity (and thus not a wirelink), but a "..type(retval).."!",0) end + end, + function(v) + return not IsValid(v) + end +) + +/******************************************************************************/ + +__e2setcost(2) -- temporary + +registerOperator("ass", "xwl", "xwl", function(self, args) + local lhs, op2, scope = args[2], args[3], args[4] + local rhs = op2[1](self, op2) + self.Scopes[scope][lhs] = rhs + self.Scopes[scope].vclk[lhs] = true + return rhs +end) + +/******************************************************************************/ + +e2function number operator_is(wirelink value) + if not validWirelink(self, value) then return 0 end + return 1 +end + +e2function number operator==(wirelink lhs, wirelink rhs) + if lhs == rhs then return 1 else return 0 end +end + +e2function number operator!=(wirelink lhs, wirelink rhs) + if lhs ~= rhs then return 1 else return 0 end +end + +/******************************************************************************/ + +e2function number wirelink:isHiSpeed() + if not validWirelink(self, this) then return 0 end + if this.WriteCell or this.ReadCell then return 1 else return 0 end +end + +e2function entity wirelink:entity() + return this +end + +/******************************************************************************/ + +e2function number wirelink:hasInput(string portname) + if not validWirelink(self, this) then return 0 end + + if not this.Inputs[portname] then return 0 end + return 1 +end + +e2function number wirelink:hasOutput(string portname) + if not validWirelink(self, this) then return 0 end + + if not this.Outputs[portname] then return 0 end + return 1 +end + +/******************************************************************************/ + +registerCallback("postinit", function() + + local getf, setf + -- generate getters and setters for all types + for typename, v in pairs( wire_expression_types ) do + local id = v[1] + local zero = v[2] + local input_serializer = v[3] + local output_serializer = v[4] + local fname = typename == "NORMAL" and "NUMBER" or typename + + -- for T:number() etc + local getter = fname:lower() + + -- for T:setNumber() etc + local setter = "set"..fname:sub(1,1):upper()..fname:sub(2):lower() + + if input_serializer then + if istable(zero) and not next(zero) then + -- table/array + function getf(self, args) + local this, portname = args[2], args[3] + this, portname = this[1](self, this), portname[1](self, portname) + + if not validWirelink(self, this) then return {} end + + portname = mapOutputAlias(this, portname) + + if not this.Outputs[portname] then return {} end + if this.Outputs[portname].Type ~= typename then return {} end + + return input_serializer(self, this.Outputs[portname].Value) + end + else + -- all other types with input serializers + function getf(self, args) + local this, portname = args[2], args[3] + this, portname = this[1](self, this), portname[1](self, portname) + + if not validWirelink(self, this) then return input_serializer(self, zero) end + + portname = mapOutputAlias(this, portname) + + if not this.Outputs[portname] then return input_serializer(self, zero) end + if this.Outputs[portname].Type ~= typename then return input_serializer(self, zero) end + + return input_serializer(self, this.Outputs[portname].Value) + end + end + else + -- all types without an input serializer + -- a check for {} is not needed here, since array and table both have input serializers and are thus handled in the above branch. + function getf(self, args) + local this, portname = args[2], args[3] + this, portname = this[1](self, this), portname[1](self, portname) + + if not validWirelink(self, this) then return zero end + + portname = mapOutputAlias(this, portname) + + if not this.Outputs[portname] then return zero end + if this.Outputs[portname].Type ~= typename then return zero end + + return this.Outputs[portname].Value + end + end + + if output_serializer then + function setf(self, args) + local this, portname, value = args[2], args[3], args[4] + this, portname, value = this[1](self, this), portname[1](self, portname), value[1](self, value) + + if not validWirelink(self, this) then return value end + if not this.Inputs then return value end + + portname = mapInputAlias(this, portname) + + TriggerInput(self, this, portname, output_serializer(self, value), typename) + return value + end + else + function setf(self, args) + local this, portname, value = args[2], args[3], args[4] + this, portname, value = this[1](self, this), portname[1](self, portname), value[1](self, value) + + if not validWirelink(self, this) then return value end + if not this.Inputs then return value end + + portname = mapInputAlias(this, portname) + + TriggerInput(self, this, portname, value, typename) + return value + end + end + + registerFunction(getter, "xwl:s", id, getf, 5) + registerOperator("idx", id.."=xwls", id, getf, 5) + registerFunction(setter, "xwl:s"..id, id, setf, 5) + registerOperator("idx", id.."=xwls"..id, id, setf, 5) + end +end) + +__e2setcost(15) -- temporary + +e2function void wirelink:setXyz(vector value) + if not validWirelink(self, this) then return end + + TriggerInput(self, this, "X", value[1], "NORMAL") + TriggerInput(self, this, "Y", value[2], "NORMAL") + TriggerInput(self, this, "Z", value[3], "NORMAL") +end + +e2function vector wirelink:xyz() + if not validWirelink(self, this) then return { 0, 0, 0 } end + + local x, y, z = this.Outputs["X"], this.Outputs["Y"], this.Outputs["Z"] + + if not x or not y or not z then return { 0, 0, 0 } end + if x.Type ~= "NORMAL" or y.Type ~= "NORMAL" or z.Type ~= "NORMAL" then return { 0, 0, 0 } end + return { x.Value, y.Value, z.Value } +end + +/******************************************************************************/ + +__e2setcost(5) -- temporary + +--- Return E2 wirelink -- and create it if none created yet +e2function wirelink wirelink() + if not self.entity.extended then + WireLib.CreateWirelinkOutput( self.player, self.entity, {true} ) + end + return self.entity +end + +__e2setcost(1) + +--- Return an invalid wirelink +e2function wirelink nowirelink() + return nil +end + +/******************************************************************************/ +-- XWL:inputs/outputs/inputType/outputType by jeremydeath + +__e2setcost(15) -- temporary + +--- Returns an array of all the inputs that has without their types. Returns an empty array if it has none +e2function array wirelink:inputs() + if not validWirelink(self, this) then return {} end + if(!this.Inputs) then return {} end + + local InputNames = {} + for k,v in pairs_sortvalues(this.Inputs, WireLib.PortComparator) do + table.insert(InputNames,k) + end + return InputNames +end + +--- Returns an array of all the outputs that has without their types. Returns an empty array if it has none +e2function array wirelink:outputs() + if not validWirelink(self, this) then return {} end + if(!this.Outputs) then return {} end + + local OutputNames = {} + for k,v in pairs_sortvalues(this.Outputs, WireLib.PortComparator) do + table.insert(OutputNames,k) + end + return OutputNames +end + +--- Returns the type of input that is in lowercase. ( "NORMAL" is changed to "number" ) +e2function string wirelink:inputType(string Input) + if not validWirelink(self, this) then return "" end + if(!this.Inputs or !this.Inputs[Input]) then return "" end + + local Type = this.Inputs[Input].Type or "" + if Type == "NORMAL" then Type = "number" end + return string.lower(Type) +end + +--- Returns the type of output that is in lowercase. ( "NORMAL" is changed to "number" ) +e2function string wirelink:outputType(string Output) + if not validWirelink(self, this) then return "" end + if(!this.Outputs or !this.Outputs[Output]) then return "" end + + local Type = this.Outputs[Output].Type or "" + if Type == "NORMAL" then Type = "number" end + return string.lower(Type) +end + +/******************************************************************************/ + +__e2setcost(5) -- temporary + +e2function number wirelink:writeCell(address, value) + if not validWirelink(self, this) then return 0 end + + if not this.WriteCell then return 0 end + if this:WriteCell(address, value) then return 1 else return 0 end +end + +e2function number wirelink:readCell(address) + if not validWirelink(self, this) then return 0 end + + if not this.ReadCell then return 0 end + return this:ReadCell(address) or 0 +end + +e2function array wirelink:readArray(start, size) + if size < 0 then return {} end + if !validWirelink(self, this) or !this.ReadCell then return {} end + + self.prf = self.prf + size + + local ret = {} + + for i = 1, size do + ret[i] = this:ReadCell(start + (i - 1)) + end + + return ret +end + +e2function number wirelink:operator[](address, value) + if not validWirelink(self, this) then return value end + + if not this.WriteCell then return value end + this:WriteCell(address, value) + return value +end +e2function number wirelink:operator[](address) = e2function number wirelink:readCell(address) + +/******************************************************************************/ + +__e2setcost(20) -- temporary + +--- XWL[N,vector]=V +e2function vector wirelink:operator[T](address, vector value) + if not validWirelink(self, this) then return value end + + if not this.WriteCell then return value end + this:WriteCell(address, value[1]) + this:WriteCell(address+1, value[2]) + this:WriteCell(address+2, value[3]) + return value +end + +--- V=XWL[N,vector] +e2function vector wirelink:operator[T](address) + if not validWirelink(self, this) then return { 0, 0, 0 } end + + if not this.ReadCell then return 0 end + return { + this:ReadCell(address) or 0, + this:ReadCell(address+1) or 0, + this:ReadCell(address+2) or 0, + } +end + +--- XWL[N,string]=S +e2function string wirelink:operator[T](address, string value) + if not validWirelink(self, this) or not this.WriteCell then return "" end + WriteStringZero(this, address, value) + return value +end + +--- S=XWL[N,string] +e2function string wirelink:operator[T](address) + if not validWirelink(self, this) or not this.ReadCell then return "" end + return ReadStringZero(this, address) +end + +/******************************************************************************/ + +__e2setcost(20) -- temporary + +local function conv(vec) + local r = Clamp(floor(vec[1]/28),0,9) + local g = Clamp(floor(vec[2]/28),0,9) + local b = Clamp(floor(vec[3]/28),0,9) + + return floor(r)*100+floor(g)*10+floor(b) +end + +local function WriteString(self, entity, string, X, Y, textcolor, bgcolor, Flash) + if not validWirelink(self, entity) or not entity.WriteCell then return end + + if !isnumber(textcolor)then textcolor = conv(textcolor) end + if !isnumber(bgcolor) then bgcolor = conv(bgcolor) end + + textcolor = Clamp(floor(textcolor), 0, 999) + bgcolor = Clamp(floor(bgcolor), 0, 999) + Flash = Flash ~= 0 and 1 or 0 + local Params = Flash*1000000 + bgcolor*1000 + textcolor + + local Xorig = X + for i = 1,#string do + local Byte = string.byte(string,i) + if Byte == 10 then + Y = Y+1 + X = Xorig -- shouldn't this be 0 as well? would be more consistent. + else + if X >= 30 then + X = 0 + Y = Y + 1 + end + local Address = 2*(Y*30+(X)) + X = X + 1 + if Address>=1080 or Address<0 then return end + entity:WriteCell(Address, Byte) + entity:WriteCell(Address+1, Params) + end + end +end + +e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash) + WriteString(self, this,text,x,y,textcolor,bgcolor,flash) +end + + +e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor) + WriteString(self, this,text,x,y,textcolor,bgcolor,0) +end + + +e2function void wirelink:writeString(string text, x, y, textcolor) + WriteString(self, this,text,x,y,textcolor,0,0) +end + +e2function void wirelink:writeString(string text, x, y) + WriteString(self, this,text,x,y,999,0,0) +end + +e2function void wirelink:writeString(string text, x, y, textcolor, vector bgcolor, flash) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash) +e2function void wirelink:writeString(string text, x, y, vector textcolor, bgcolor, flash) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash) +e2function void wirelink:writeString(string text, x, y, vector textcolor, vector bgcolor, flash) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash) + +e2function void wirelink:writeString(string text, x, y, textcolor, vector bgcolor) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor) +e2function void wirelink:writeString(string text, x, y, vector textcolor, bgcolor) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor) +e2function void wirelink:writeString(string text, x, y, vector textcolor, vector bgcolor) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor) + +e2function void wirelink:writeString(string text, x, y, vector textcolor) = e2function void wirelink:writeString(string text, x, y, textcolor) + +-- Unicode strings +local function WriteUnicodeString(self, entity, string, X, Y, textcolor, bgcolor, Flash) + if not validWirelink(self, entity) or not entity.WriteCell then return end + + if !isnumber(textcolor)then textcolor = conv(textcolor) end + if !isnumber(bgcolor) then bgcolor = conv(bgcolor) end + + textcolor = Clamp(floor(textcolor), 0, 999) + bgcolor = Clamp(floor(bgcolor), 0, 999) + Flash = Flash ~= 0 and 1 or 0 + local Params = Flash*1000000 + bgcolor*1000 + textcolor + + local Xorig = X + local i = 1 + while i <= #string do + local Byte = string.byte(string,i) + if Byte == 10 then + Y = Y+1 + X = Xorig -- shouldn't this be 0 as well? would be more consistent. + else + if Byte >= 128 then + if Byte >= 240 then + -- 4 byte sequence (unsupported by engine, but it should only occupy one character on the console screen) + if i + 3 > #string then + Byte = 0 + else + Byte = (Byte % 8) * 262144 + Byte = Byte + (string.byte (string, i + 1) % 64) * 4096 + Byte = Byte + (string.byte (string, i + 2) % 64) * 64 + Byte = Byte + (string.byte (string, i + 3) % 64) + end + i = i + 3 + elseif Byte >= 224 then + -- 3 byte sequence + if i + 2 > #string then + Byte = 0 + else + Byte = (Byte % 16) * 4096 + Byte = Byte + (string.byte (string, i + 1) % 64) * 64 + Byte = Byte + (string.byte (string, i + 2) % 64) + end + i = i + 2 + elseif Byte >= 192 then + -- 2 byte sequence + if i + 1 > #string then + Byte = 0 + else + Byte = (Byte % 32) * 64 + Byte = Byte + (string.byte (string, i + 1) % 64) + end + i = i + 1 + else + -- invalid sequence + Byte = 0 + end + end + if X >= 30 then + X = 0 + Y = Y + 1 + end + local Address = 2*(Y*30+(X)) + X = X + 1 + if Address>=1080 or Address<0 then return end + entity:WriteCell(Address, Byte) + entity:WriteCell(Address+1, Params) + end + i = i + 1 + end +end + +e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash) + WriteUnicodeString(self,this,text,x,y,textcolor,bgcolor,flash) +end + + +e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor) + WriteUnicodeString(self,this,text,x,y,textcolor,bgcolor,0) +end + + +e2function void wirelink:writeUnicodeString(string text, x, y, textcolor) + WriteUnicodeString(self,this,text,x,y,textcolor,0,0) +end + +e2function void wirelink:writeUnicodeString(string text, x, y) + WriteUnicodeString(self,this,text,x,y,999,0,0) +end + +e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, vector bgcolor, flash) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash) +e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor, bgcolor, flash) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash) +e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor, vector bgcolor, flash) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash) + +e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, vector bgcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor) +e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor, bgcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor) +e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor, vector bgcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor) + +e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor) + +/******************************************************************************/ + +--- Writes a null-terminated string to the given address. Returns the next free address or 0 on failure. +e2function number wirelink:writeString(address, string data) + if not validWirelink(self, this) or not this.WriteCell then return 0 end + return WriteStringZero(this, address, data) +end + +--- Reads a null-terminated string from the given address. Returns an empty string on failure. +e2function string wirelink:readString(address) + if not validWirelink(self, this) or not this.ReadCell then return "" end + return ReadStringZero(this, address) +end + +/******************************************************************************/ + +--- Writes an array's elements into a piece of memory. Strings and sub-tables (angles, vectors, matrices) are written as pointers to the actual data. Strings are written null-terminated. +e2function number wirelink:writeArray(address, array data) + if not validWirelink(self, this) or not this.WriteCell then return 0 end + wa_lookup = {} + local ret = WriteArray(this,address,data) + wa_lookup = nil + return ret +end + +e2function number wirelink:writeTable(address, table data ) + if not validWirelink(self, this) or not this.WriteCell then return 0 end + wa_lookup = {} + local ret = WriteArray(this,address,data.n) + wa_lookup = nil + return ret +end + +--- Writes only an array's numeric elements into a piece of memory, without null termination, returns number of elements written +e2function number wirelink:writeArraySimple(address, array data) + if not validWirelink(self, this) or not this.WriteCell then return 0 end + return writeArraySimple(this, address, data) +end + +e2function number wirelink:writeTableSimple(address, table data) + if not validWirelink(self, this) or not this.WriteCell then return 0 end + return writeArraySimple(this, address, data.n) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/init.lua new file mode 100644 index 0000000..5e191d4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/init.lua @@ -0,0 +1,679 @@ +AddCSLuaFile('cl_init.lua') +AddCSLuaFile('shared.lua') +include('shared.lua') + +DEFINE_BASECLASS("base_wire_entity") + +-- This makes E2s not save using garry's workshop save +-- Until someone can find the cause of the crashes, leave this in here +local old = gmsave.ShouldSaveEntity +function gmsave.ShouldSaveEntity( ent, ... ) + if ent:GetClass() == "gmod_wire_expression2" then return false end + return old( ent, ... ) +end + +e2_softquota = nil +e2_hardquota = nil +e2_tickquota = nil +e2_timequota = nil + +do + local wire_expression2_unlimited = GetConVar("wire_expression2_unlimited") + local wire_expression2_quotasoft = GetConVar("wire_expression2_quotasoft") + local wire_expression2_quotahard = GetConVar("wire_expression2_quotahard") + local wire_expression2_quotatick = GetConVar("wire_expression2_quotatick") + local wire_expression2_quotatime = GetConVar("wire_expression2_quotatime") + + local function updateQuotas() + if wire_expression2_unlimited:GetBool() then + e2_softquota = 1000000 + e2_hardquota = 1000000 + e2_tickquota = 100000 + e2_timequota = -1 + else + e2_softquota = wire_expression2_quotasoft:GetInt() + e2_hardquota = wire_expression2_quotahard:GetInt() + e2_tickquota = wire_expression2_quotatick:GetInt() + e2_timequota = wire_expression2_quotatime:GetInt()*0.001 + end + end + cvars.AddChangeCallback("wire_expression2_unlimited", updateQuotas) + cvars.AddChangeCallback("wire_expression2_quotasoft", updateQuotas) + cvars.AddChangeCallback("wire_expression2_quotahard", updateQuotas) + cvars.AddChangeCallback("wire_expression2_quotatick", updateQuotas) + cvars.AddChangeCallback("wire_expression2_quotatime", updateQuotas) + updateQuotas() +end + +local function copytype(var) + return istable(var) and table.Copy(var) or var +end + + +local ScopeManager = {} +ScopeManager.__index = ScopeManager + +function ScopeManager:InitScope() + self.Scopes = {} + self.ScopeID = 0 + self.Scopes[0] = self.GlobalScope or { vclk = {} } -- for creating new enviroments + self.Scope = self.Scopes[0] + self.GlobalScope = self.Scope +end + +function ScopeManager:PushScope() + self.Scope = { vclk = {} } + self.ScopeID = self.ScopeID + 1 + self.Scopes[self.ScopeID] = self.Scope +end + +function ScopeManager:PopScope() + self.ScopeID = self.ScopeID - 1 + self.Scope = self.Scopes[self.ScopeID] + self.Scopes[self.ScopeID] = self.Scope + return table.remove(self.Scopes, self.ScopeID + 1) +end + +function ScopeManager:SaveScopes() + return { self.Scopes, self.ScopeID, self.Scope } +end + +function ScopeManager:LoadScopes(Scopes) + self.Scopes = Scopes[1] + self.ScopeID = Scopes[2] + self.Scope = Scopes[3] +end + +function ENT:UpdateOverlay(clear) + if clear then + self:SetOverlayData( { + txt = "(none)", + error = self.error, + prfbench = 0, + prfcount = 0, + timebench = 0 + }) + else + self:SetOverlayData( { + txt = self.name, -- name/error + error = self.error, -- error bool + prfbench = self.context.prfbench, + prfcount = self.context.prfcount, + timebench = self.context.timebench + }) + end +end + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.Inputs = WireLib.CreateInputs(self, {}) + self.Outputs = WireLib.CreateOutputs(self, {}) + + self:UpdateOverlay(true) + self:SetColor(Color(255, 0, 0, self:GetColor().a)) +end + +function ENT:OnRestore() + self:Setup(self.original, self.inc_files, nil, true) +end + +local SysTime = SysTime + +function ENT:Execute() + if self.error then return end + if self.context.resetting then return end + + for k, v in pairs(self.tvars) do + self.GlobalScope[k] = copytype(wire_expression_types2[v][2]) + end + + self:PCallHook('preexecute') + + self.context:PushScope() + + local bench = SysTime() + + local ok, msg = pcall(self.script[1], self.context, self.script) + if not ok then + if msg == "exit" then + elseif msg == "perf" then + self:Error("Expression 2 (" .. self.name .. "): tick quota exceeded", "tick quota exceeded") + else + self:Error("Expression 2 (" .. self.name .. "): " .. msg, "script error") + end + end + + self.context.time = self.context.time + (SysTime() - bench) + + self.context:PopScope() + + self.first = false -- if hooks call execute + self.duped = false -- if hooks call execute + self.context.triggerinput = nil -- if hooks call execute + + self:PCallHook('postexecute') + + self:TriggerOutputs() + + for k, v in pairs(self.inports[3]) do + if self.GlobalScope[k] then + if wire_expression_types[self.Inputs[k].Type][3] then + self.GlobalScope[k] = wire_expression_types[self.Inputs[k].Type][3](self.context, self.Inputs[k].Value) + else + self.GlobalScope[k] = self.Inputs[k].Value + end + end + end + + self.GlobalScope.vclk = {} + for k, v in pairs(self.globvars) do + self.GlobalScope[k] = copytype(wire_expression_types2[v][2]) + end + + if self.context.prfcount + self.context.prf - e2_softquota > e2_hardquota then + self:Error("Expression 2 (" .. self.name .. "): tick quota exceeded", "hard quota exceeded") + end + + if self.error then self:PCallHook('destruct') end +end + +function ENT:Think() + BaseClass.Think(self) + self:NextThink(CurTime()+0.030303) + + if self.context and not self.error then + self.context.prfbench = self.context.prfbench * 0.95 + self.context.prf * 0.05 + self.context.prfcount = self.context.prfcount + self.context.prf - e2_softquota + self.context.timebench = self.context.timebench * 0.95 + self.context.time * 0.05 -- Average it over the last 20 ticks + + if e2_timequota > 0 and self.context.timebench > e2_timequota then + self:Error("Expression 2 (" .. self.name .. "): time quota exceeded", "time quota exceeded") + self:PCallHook('destruct') + end + + if self.context.prfcount < 0 then self.context.prfcount = 0 end + + self:UpdateOverlay() + + self.context.prf = 0 + self.context.time = 0 + end + + return true +end + +local CallHook = wire_expression2_CallHook +function ENT:CallHook(hookname, ...) + if not self.context then return end + return CallHook(hookname, self.context, ...) +end + +function ENT:OnRemove() + if not self.error and not self.removing then -- make sure destruct hooks aren't called twice (once on error, once on remove) + self.removing = true + self:PCallHook('destruct') + end +end + +function ENT:PCallHook(...) + local ok, ret = pcall(self.CallHook, self, ...) + if ok then + return ret + else + self:Error("Expression 2 (" .. self.name .. "): " .. ret) + end +end + +function ENT:Error(message, overlaytext) + self:SetOverlayText(self.name .. "\n(" .. (overlaytext or "script error") .. ")") + self:SetColor(Color(255, 0, 0, self:GetColor().a)) + + self.error = true + -- ErrorNoHalt(message .. "\n") + WireLib.ClientError(message, self.player) +end + +function ENT:CompileCode(buffer, files, filepath) + self.original = buffer + if filepath then -- filepath may have already been set from the dupe function + self.filepath = filepath + end + + local status, directives, buffer = E2Lib.PreProcessor.Execute(buffer,nil,self) + if not status then self:Error(directives) return end + self.buffer = buffer + self.error = false + + self.name = directives.name + if directives.name == "" then + self.name = "generic" + self.WireDebugName = "Expression 2" + else + self.WireDebugName = "E2 - " .. self.name + end + self:SetNWString("name", self.name) + + self.directives = directives + self.inports = directives.inputs + self.outports = directives.outputs + self.persists = directives.persist + self.trigger = directives.trigger + + local status, tokens = E2Lib.Tokenizer.Execute(self.buffer) + if not status then self:Error(tokens) return end + + local status, tree, dvars = E2Lib.Parser.Execute(tokens) + if not status then self:Error(tree) return end + + if not self:PrepareIncludes(files) then return end + + status,tree = E2Lib.Optimizer.Execute(tree) + if not status then self:Error(tree) return end + + local status, script, inst = E2Lib.Compiler.Execute(tree, self.inports[3], self.outports[3], self.persists[3], dvars, self.includes) + if not status then self:Error(script) return end + + self.script = script + self.dvars = inst.dvars + self.tvars = inst.tvars + self.funcs = inst.funcs + self.funcs_ret = inst.funcs_ret + self.globvars = inst.GlobalScope + + self:ResetContext() +end + +function ENT:GetGateName() + return self.name +end + +function ENT:GetCode() + return self.original, self.inc_files +end + +function ENT:PrepareIncludes(files) + + self.inc_files = files + + self.includes = {} + + for file, buffer in pairs(files) do + local status, directives, buffer = E2Lib.PreProcessor.Execute(buffer, self.directives) + if not status then + self:Error("(" .. file .. ")" .. directives) + return + end + + local status, tokens = E2Lib.Tokenizer.Execute(buffer) + if not status then + self:Error("(" .. file .. ")" .. tokens) + return + end + + local status, tree, dvars = E2Lib.Parser.Execute(tokens) + if not status then + self:Error("(" .. file .. ")" .. tree) + return + end + + status, tree = E2Lib.Optimizer.Execute(tree) + if not status then + self:Error("(" .. file .. ")" .. tree) + return + end + + self.includes[file] = { tree } + end + + return true +end + +function ENT:ResetContext() + local context = { + data = {}, + vclk = {}, -- Used only by arrays and tables! + funcs = self.funcs, + funcs_ret = self.funcs_ret, + entity = self, + player = self.player, + uid = self.uid, + prf = 0, + prfcount = 0, + prfbench = 0, + time = 0, + timebench = 0, + includes = self.includes + } + + setmetatable(context, ScopeManager) + context:InitScope() + + self.context = context + self.GlobalScope = context.GlobalScope + self._vars = self.GlobalScope -- Dupevars + + self.Inputs = WireLib.AdjustSpecialInputs(self, self.inports[1], self.inports[2]) + self.Outputs = WireLib.AdjustSpecialOutputs(self, self.outports[1], self.outports[2]) + + if self.extended then -- It was extended before the adjustment, recreate the wirelink + WireLib.CreateWirelinkOutput( self.player, self, {true} ) + end + + self._original = string.Replace(string.Replace(self.original, "\"", string.char(163)), "\n", string.char(128)) + + self._name = self.name + self._inputs = { {}, {} } + self._outputs = { {}, {} } + + for k, v in pairs(self.inports[3]) do + self._inputs[1][#self._inputs[1] + 1] = k + self._inputs[2][#self._inputs[2] + 1] = v + self.GlobalScope[k] = copytype(wire_expression_types[v][2]) + self.globvars[k] = nil + end + + for k, v in pairs(self.outports[3]) do + self._outputs[1][#self._outputs[1] + 1] = k + self._outputs[2][#self._outputs[2] + 1] = v + self.GlobalScope[k] = copytype(wire_expression_types[v][2]) + self.GlobalScope.vclk[k] = true + self.globvars[k] = nil + end + + for k, v in pairs(self.persists[3]) do + self.GlobalScope[k] = copytype(wire_expression_types[v][2]) + self.globvars[k] = nil + end + + for k, v in pairs(self.globvars) do + self.GlobalScope[k] = copytype(wire_expression_types2[v][2]) + end + + for k, v in pairs(self.Inputs) do + if wire_expression_types[v.Type][3] then + self.GlobalScope[k] = wire_expression_types[v.Type][3](self.context, v.Value) + else + self.GlobalScope[k] = v.Value + end + end + + for k, _ in pairs(self.dvars) do + self.GlobalScope["$" .. k] = self.GlobalScope[k] + end + + self.error = false +end + +function ENT:IsCodeDifferent(buffer, includes) + -- First check the main file + if self.original ~= buffer then return true end + + -- First compare one way + for k, v in pairs(self.inc_files) do + if includes[k] ~= v then return true end + end + + -- Then compare the other way, too + for k, v in pairs(includes) do + if self.inc_files[k] ~= v then return true end + end + + -- All code is identical. + return false +end + +function ENT:Setup(buffer, includes, restore, forcecompile, filepath) + if self.script then + self:PCallHook('destruct') + end + + self.uid = IsValid(self.player) and self.player:UniqueID() or "World" + self:SetColor(Color(255, 255, 255, self:GetColor().a)) + + if forcecompile or self:IsCodeDifferent(buffer, includes) then + self:CompileCode(buffer, includes, filepath) + if self.error then + self._original = string.Replace(string.Replace(self.original, "\"", string.char(163)), "\n", string.char(128)) + self._name = self.name + self._inputs = { {}, {} } + self._outputs = { {}, {} } + end + else + self:ResetContext() + end + + self:SetOverlayText(self.name) + + local ok, msg = pcall(self.CallHook, self, 'construct') + if not ok then + Msg("Construct hook(s) failed, executing destruct hooks...\n") + local ok2, msg2 = pcall(self.CallHook, self, 'destruct') + if ok2 then + self:Error(msg .. "\nDestruct hooks succeeded.") + else + self:Error(msg .. "\n" .. msg2) + end + return + end + + self.duped = false + + if not restore then + self.first = true + self:Execute() + self:Think() + end + + self:NextThink(CurTime()) +end + +function ENT:Reset() + -- prevent E2 from executing anything + self.context.resetting = true + + -- reset the chip in the next tick + timer.Simple(0, function() if IsValid(self) then self:Setup(self.original, self.inc_files) end end) +end + +function ENT:TriggerInput(key, value) + if self.error then return end + if key and self.inports and self.inports[3][key] then + local t = self.inports[3][key] + + self.GlobalScope["$" .. key] = self.GlobalScope[key] + if wire_expression_types[t][3] then + self.GlobalScope[key] = wire_expression_types[t][3](self.context, value) + else + self.GlobalScope[key] = value + end + + self.context.triggerinput = key + if self.trigger[1] or self.trigger[2][key] then self:Execute() end + self.context.triggerinput = nil + end +end + +function ENT:TriggerOutputs() + for key, t in pairs(self.outports[3]) do + if self.GlobalScope.vclk[key] or self.first then + if wire_expression_types[t][4] then + WireLib.TriggerOutput(self, key, wire_expression_types[t][4](self.context, self.GlobalScope[key])) + else + WireLib.TriggerOutput(self, key, self.GlobalScope[key]) + end + end + end +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID, GetConstByID) + self:Setup(self.buffer, self.inc_files, true) + + if not self.error then + for k, v in pairs(self.dupevars) do + self.GlobalScope[k] = v + end -- Rusketh Broke this :( + -- table.Merge(self.context.vars, self.dupevars) + self.dupevars = nil + + self.duped = true + self:Execute() + self:Think() + self.duped = false + end + + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID, GetConstByID) +end + +-- -------------------------------- Transfer ---------------------------------- + +--[[ + Player Disconnection Magic +--]] +local cvar = CreateConVar("wire_expression2_pause_on_disconnect", 0, 0, "Decides if chips should pause execution on their owner's disconnect.\n0 = no, 1 = yes, 2 = non-admins only.") +-- This is a global function so it can be overwritten for greater control over whose chips are frozenated +function wire_expression2_ShouldFreezeChip(ply) + return not ply:IsAdmin() +end + +-- It uses EntityRemoved because PlayerDisconnected doesn't catch all disconnects. +hook.Add("EntityRemoved", "Wire_Expression2_Player_Disconnected", function(ent) + if (not (ent and ent:IsPlayer())) then + return + end + local ret = cvar:GetInt() + if (ret == 0 or (ret == 2 and not wire_expression2_ShouldFreezeChip(ent))) then + return + end + for _, v in ipairs(ents.FindByClass("gmod_wire_expression2")) do + if (v.player == ent) then + v:SetOverlayText(v.name .. "\n(Owner disconnected.)") + local oldColor = v:GetColor() + v:SetColor(Color(255, 0, 0, v:GetColor().a)) + v.disconnectPaused = oldColor + v.error = true + end + end +end) + +hook.Add("PlayerAuthed", "Wire_Expression2_Player_Authed", function(ply, sid, uid) + local c + for _, ent in ipairs(ents.FindByClass("gmod_wire_expression2")) do + if (ent.uid == uid) then + ent.context.player = ply + ent.player = ply + ent:SetNWEntity("player", ply) + if (ent.disconnectPaused) then + c = ent.disconnectPaused + ent:SetColor(Color(c[1], c[2], c[3], c[4])) + ent:SetRenderMode(ent:GetColor().a == 255 and RENDERMODE_NORMAL or RENDERMODE_TRANSALPHA) + ent.error = false + ent.disconnectPaused = false + ent:SetOverlayText(ent.name) + end + end + end +end) + +function MakeWireExpression2(player, Pos, Ang, model, buffer, name, inputs, outputs, vars, inc_files, filepath) + if not player then player = game.GetWorld() end -- For Garry's Map Saver + if IsValid(player) and not player:CheckLimit("wire_expressions") then return false end + if not WireLib.CanModel(player, model) then return false end + + local self = ents.Create("gmod_wire_expression2") + if not self:IsValid() then return false end + + if buffer then self.duped = true end + + self:SetModel(model) + self:SetAngles(Ang) + self:SetPos(Pos) + self:Spawn() + self:SetPlayer(player) + self.player = player + self:SetNWEntity("player", player) + + if isstring( buffer ) then -- if someone dupes an E2 with compile errors, then all these values will be invalid + buffer = string.Replace(string.Replace(buffer, string.char(163), "\""), string.char(128), "\n") + self.buffer = buffer + self:SetOverlayText(name) + + self.inc_files = inc_files or {} + + self.Inputs = WireLib.AdjustSpecialInputs(self, inputs[1], inputs[2]) + self.Outputs = WireLib.AdjustSpecialOutputs(self, outputs[1], outputs[2]) + + self.dupevars = vars + + self.filepath = filepath + else + self.buffer = "error(\"You tried to dupe an E2 with compile errors!\")\n#Unfortunately, no code can be saved when duping an E2 with compile errors.\n#Fix your errors and try again." + + self.inc_files = {} + self.dupevars = {} + + self.name = "generic" + end + + if IsValid(player) then + player:AddCount("wire_expressions", self) + player:AddCleanup("wire_expressions", self) + end + return self +end +duplicator.RegisterEntityClass("gmod_wire_expression2", MakeWireExpression2, "Pos", "Ang", "Model", "_original", "_name", "_inputs", "_outputs", "_vars", "inc_files", "filepath") + +-------------------------------------------------- +-- Emergency shutdown (beta testing so far) +-------------------------------------------------- +local average_ram = 0 +local enable = CreateConVar( + "wire_expression2_ram_emergency_shutdown_enable", "0", {FCVAR_ARCHIVE}, + "Enable/disable the emergency shutdown feature." ) + +local average_halt_multiplier = CreateConVar( + "wire_expression2_ram_emergency_shutdown_spike", "4", {FCVAR_ARCHIVE}, + "if (current_ram > average_ram * spike_convar) then shut down all E2s" ) + +local halt_max_amount = CreateConVar( + "wire_expression2_ram_emergency_shutdown_total", "512", {FCVAR_ARCHIVE}, + "This is in kilobytes, if (current_ram > total_convar) then shut down all E2s" ) + +local function enableEmergencyShutdown() + hook.Remove( "Think", "wire_expression2_emergency_shutdown" ) -- remove old hook + if enable:GetBool() then + hook.Add( "Think", "wire_expression2_emergency_shutdown", function() + local current_ram = collectgarbage("count") + if average_ram == 0 then -- set up initial value + average_ram = current_ram + else + -- calculate average + average_ram = average_ram * 0.95 + current_ram * 0.05 + + if current_ram > average_ram * average_halt_multiplier:GetFloat() or -- if the current ram spikes + current_ram > halt_max_amount:GetInt() * 1000 then -- or if the current ram goes over a set limit + + local e2s = ents.FindByClass("gmod_wire_expression2") -- find all E2s and halt them + for _,v in pairs( e2s ) do + if not v.error then + -- immediately clear any memory the E2 may be holding + v:PCallHook("destruct") + v:ResetContext() + v:PCallHook("construct") + + -- Notify the user why we shut down + v:Error( "High server RAM usage detected! Emergency E2 shutdown!" ) + end + end + collectgarbage() -- collect the garbage now + average_ram = collectgarbage("count") -- reset average ram when we're done + end + end + end) + end +end + +enableEmergencyShutdown() +cvars.AddChangeCallback( "wire_expression2_ram_emergency_shutdown_enable", enableEmergencyShutdown ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/shared.lua new file mode 100644 index 0000000..65a227f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_expression2/shared.lua @@ -0,0 +1,26 @@ +DEFINE_BASECLASS("base_wire_entity") + +ENT.PrintName = "Wire Expression 2" +ENT.Author = "Syranide" +ENT.Contact = "me@syranide.com" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.WireDebugName = "Expression 2" + +CreateConVar("wire_expression2_unlimited", "0", {FCVAR_REPLICATED}) +CreateConVar("wire_expression2_quotasoft", "10000", {FCVAR_REPLICATED}) +CreateConVar("wire_expression2_quotahard", "100000", {FCVAR_REPLICATED}) +CreateConVar("wire_expression2_quotatick", "25000", {FCVAR_REPLICATED}) +CreateConVar("wire_expression2_quotatime", "-1", {FCVAR_REPLICATED}, "Time in (ms) the e2 can consume before killing (-1 is infinite)") + +include("core/e2lib.lua") +include("base/ast.lua") +include("base/preprocessor.lua") +include("base/tokenizer.lua") +include("base/parser.lua") +if SERVER then + include("base/optimizer.lua") +end +include("base/compiler.lua") +include('core/init.lua') diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_extbus.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_extbus.lua new file mode 100644 index 0000000..9bd4b8e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_extbus.lua @@ -0,0 +1,178 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Extended Bus" +ENT.WireDebugName = "Extended Bus" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS) + self:SetMoveType( MOVETYPE_VPHYSICS) + self:SetSolid( SOLID_VPHYSICS) + self:SetUseType( SIMPLE_USE) + self.Outputs = Wire_CreateOutputs(self, {"Memory"}) + self.Inputs = Wire_CreateInputs(self,{"Memory1","Memory2","Memory3","Memory4","Memory5","Memory6","Memory7","Memory8"}) + + self.DataRate = 0 + self.DataBytes = 0 + self.PerformRecursiveScan = 1 + self.ControlDataSize = 32 + self.ControlData = {} + + self.Memory = {} + self.MemStart = {} + self.MemEnd = {} + for i = 1,8 do + self.Memory[i] = nil + self.MemStart[i] = 0 + self.MemEnd[i] = 0 + end + self:SetOverlayText("Data rate: 0 bps") +end + +function ENT:Think() + BaseClass.Think(self) + + self.DataRate = self.DataBytes + self.DataBytes = 0 + + Wire_TriggerOutput(self, "Memory", self.DataRate) + self:SetOverlayText("Data rate: "..math.floor(self.DataRate*2).." bps") + self:NextThink(CurTime()+0.5) +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if (Address >= 0) and (Address < self.ControlDataSize) then + if Address < 16 then + if Address % 2 == 0 then + return self.MemStart[Address/2+1] + else + return self.MemEnd[(Address-1)/2+1] + end + elseif Address == 16 then + return self.ControlDataSize + elseif Address == 18 then + return self.DataRate + elseif Address == 20 then + return self.PerformRecursiveScan + elseif Address >= 32 then + return self.ControlData[Address-31] or 0 + end + return 0 + else + for i = 1,8 do + if (Address-self.ControlDataSize >= self.MemStart[i]) and + (Address-self.ControlDataSize <= self.MemEnd[i]) then + if self.Memory[i] then + if self.Memory[i].ReadCell then + self.DataBytes = self.DataBytes + 1 + local val = self.Memory[i]:ReadCell(Address-self.ControlDataSize-self.MemStart[i]) + return val or 0 + end + else + return 0 + end + end + end + end + return nil +end + +local recursiveCounter = 0 +function ENT:GetDeviceInfo(deviceEnt) + local deviceType = CPULib.GetDeviceType(deviceEnt:GetClass()) + if deviceEnt.Socket then + if deviceEnt.Socket.Inputs.Memory.Src then + self:GetDeviceInfo(deviceEnt.Socket.Inputs.Memory.Src) + else + table.insert(self.ControlData,14) + end + return + elseif deviceEnt.Plug then + if deviceEnt.Plug.Inputs.Memory.Src then + self:GetDeviceInfo(deviceEnt.Plug.Inputs.Memory.Src) + else + table.insert(self.ControlData,13) + end + return + end + + table.insert(self.ControlData,deviceType) + + if self.PerformRecursiveScan >= 1 then + recursiveCounter = recursiveCounter + 1 + if recursiveCounter < 256 then + if (deviceEnt:GetClass() == "gmod_wire_addressbus") or + (deviceEnt:GetClass() == "gmod_wire_extbus") then + for i = 1,8 do + if deviceEnt.Memory[i] then + self:GetDeviceInfo(deviceEnt.Memory[i]) + else + table.insert(self.ControlData,0) + end + end + end + end + end +end + +function ENT:WriteCell(Address, Value) + Address = math.floor(Address) + if (Address >= 0) and (Address < self.ControlDataSize) then + -- [0..15] Address bus settings + -- [16] Control data area size + -- [17] Write to request device info + -- [18] Data transfer rate + -- [19] Override returned device type (0: no override) + -- [20] Perform recursive scan + -- [32..] Device types + if Address < 16 then + if Address % 2 == 0 then + self.MemStart[Address/2+1] = math.floor(Value) + else + self.MemEnd[(Address-1)/2+1] = math.floor(Value) + end + elseif Address == 16 then + self.ControlDataSize = math.max(32,math.floor(Value)) + elseif Address == 17 then + recursiveCounter = 0 + self.ControlData = {} + for i = 1,8 do + if self.Memory[i] then + self:GetDeviceInfo(self.Memory[i]) + else + table.insert(self.ControlData,0) + end + end + elseif Address == 20 then + self.PerformRecursiveScan = Value + end + return true + else + local res = false + for i = 1,8 do + if (Address-self.ControlDataSize >= self.MemStart[i]) and + (Address-self.ControlDataSize <= self.MemEnd[i]) then + if self.Memory[i] then + if self.Memory[i].WriteCell then + self.Memory[i]:WriteCell(Address-self.ControlDataSize-self.MemStart[i], Value) + end + end + self.DataBytes = self.DataBytes + 1 + res = true + end + end + return res + end +end + +function ENT:TriggerInput(iname, value) + for i = 1,8 do + if iname == "Memory"..i then + self.Memory[i] = self.Inputs["Memory"..i].Src + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_extbus", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_eyepod.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_eyepod.lua new file mode 100644 index 0000000..656a308 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_eyepod.lua @@ -0,0 +1,359 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Eye Pod" +ENT.Purpose = "To control the player's view in a pod and output their mouse movements" +ENT.WireDebugName = "Eye Pod" + +if CLIENT then + local enabled = false + local rotate90 = false + local freezePitch = true + local freezeYaw = true + + local previousEnabled = false + + usermessage.Hook("UpdateEyePodState", function(um) + if not um then return end + + local eyeAng = um:ReadAngle() + enabled = um:ReadBool() + rotate90 = um:ReadBool() + freezePitch = um:ReadBool() and eyeAng.p + freezeYaw = um:ReadBool() and eyeAng.y + end) + + hook.Add("CreateMove", "WireEyePodEyeControl", function(ucmd) + if enabled then + currentAng = ucmd:GetViewAngles() + + if freezePitch then + currentAng.p = freezePitch + end + + if freezeYaw then + currentAng.y = freezeYaw + end + + currentAng.r = 0 + + ucmd:SetViewAngles(currentAng) + previousEnabled = true + elseif previousEnabled then + if rotate90 then + ucmd:SetViewAngles(Angle(0,90,0)) + else + ucmd:SetViewAngles(Angle(0,0,0)) + end + previousEnabled = false + end + end) + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + self:DrawShadow(false) + + -- Set wire I/O + self.Inputs = WireLib.CreateSpecialInputs(self, { "Enable", "SetPitch", "SetYaw", "SetViewAngle", "UnfreezePitch", "UnfreezeYaw" }, { "NORMAL", "NORMAL", "NORMAL", "ANGLE", "NORMAL", "NORMAL" }) + self.Outputs = WireLib.CreateSpecialOutputs(self, { "X", "Y", "XY" }, { "NORMAL", "NORMAL", "VECTOR2" }) + + -- Initialize values + self.driver = nil + self.X = 0 + self.Y = 0 + self.enabled = false + self.pod = nil + self.eyeAng = Angle(0, 0, 0) + self.rotate90 = false + self.DefaultToZero = 1 + self.ShowRateOfChange = 0 + self.LastUpdateTime = CurTime() + + -- clamps + self.ClampXMin = 0 + self.ClampXMax = 0 + self.ClampYMin = 0 + self.ClampYMax = 0 + self.ClampX = 0 + self.ClampY = 0 + + self.freezePitch = true + self.freezeYaw = true + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) +end + +function ENT:UpdateOverlay() + self:SetOverlayText( + string.format( "Default to Zero: %s\nCumulative: %s\nMin: %s,%s\nMax: %s,%s\n%s\n\nActivated: %s%s", + (self.DefaultToZero == 1) and "Yes" or "No", + (self.ShowRateOfChange == 0) and "Yes" or "No", + self.ClampXMin, self.ClampYMin, + self.ClampXMax, self.ClampYMax, + IsValid( self.pod ) and "Linked to: " .. self.pod:GetModel() or "Not linked", + self.enabled and "Yes" or "No", + (self.enabled == true and IsValid( self.driver )) and "\nIn use by: " .. self.driver:Nick() or "" + ) + ) +end + +function ENT:Setup(DefaultToZero, RateOfChange, ClampXMin, ClampXMax, ClampYMin, ClampYMax, ClampX, ClampY) + self.DefaultToZero = DefaultToZero + self.ShowRateOfChange = RateOfChange + self.ClampXMin = ClampXMin + self.ClampXMax = ClampXMax + self.ClampYMin = ClampYMin + self.ClampYMax = ClampYMax + self.ClampX = ClampX + self.ClampY = ClampY + + self:UpdateOverlay() +end + +local Rotate90ModelList = { + ["models/props_c17/furniturechair001a.mdl"] = true, + ["models/airboat.mdl"] = true, + ["models/props_c17/chair_office01a.mdl"] = true, + ["models/nova/chair_office02.mdl"] = true, + ["models/nova/chair_office01.mdl"] = true, + ["models/props_combine/breenchair.mdl"] = true, + ["models/nova/chair_wood01.mdl"] = true, + ["models/nova/airboat_seat.mdl"] = true, + ["models/nova/chair_plastic01.mdl"] = true, + ["models/nova/jeep_seat.mdl"] = true, + ["models/props_phx/carseat.mdl"] = true, + ["models/props_phx/carseat2.mdl"] = true, + ["models/props_phx/carseat3.mdl"] = true, + ["models/buggy.mdl"] = true, + ["models/vehicle.mdl"] = true +} + +-- Old function alias +function ENT:PodLink(vehicle) return self:LinkEnt(vehicle) end + +function ENT:LinkEnt(vehicle) + vehicle = WireLib.GetClosestRealVehicle(vehicle,self:GetPos(),self:GetPlayer()) + + if not IsValid(vehicle) or not vehicle:IsVehicle() then + if IsValid(self.pod) then + self.pod.AttachedWireEyePod = nil + end + self.pod = nil + self:UpdateOverlay() + return false, "Must link to a vehicle" + end + self.pod = vehicle + vehicle:CallOnRemove("wire_eyepod_remove",function() + self:UnlinkEnt(vehicle) + end) + + self.rotate90 = false + self.eyeAng = Angle(0, 0, 0) + if IsValid(vehicle) and vehicle:IsVehicle() then + if Rotate90ModelList[string.lower(vehicle:GetModel())] then + self.rotate90 = true + self.eyeAng = Angle(0, 90, 0) + end + end + + vehicle.AttachedWireEyePod = self + self:UpdateOverlay() + WireLib.SendMarks(self,{vehicle}) + self:ColorByLinkStatus(IsValid(vehicle) and self.LINK_STATUS_LINKED or self.LINK_STATUS_UNLINKED) + return true +end + +function ENT:UnlinkEnt() + if IsValid(self.pod) then + self.pod.AttachedWireEyePod = nil + self.pod:RemoveCallOnRemove("wire_eyepod_remove") + end + self.pod = nil + if IsValid(self.driver) then + self:updateEyePodState(false) + self.driver = nil + end + WireLib.SendMarks(self,{}) + self:UpdateOverlay() + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) + return true +end + +function ENT:updateEyePodState(enabled) + self:ColorByLinkStatus(enabled and self.LINK_STATUS_ACTIVE or self.LINK_STATUS_LINKED) + umsg.Start("UpdateEyePodState", self.driver) + umsg.Angle(self.eyeAng) + umsg.Bool(enabled) + umsg.Bool(self.rotate90) + umsg.Bool(self.freezePitch) + umsg.Bool(self.freezeYaw) + umsg.End() +end + +hook.Add("PlayerEnteredVehicle","gmod_wire_eyepod_entervehicle",function(ply,vehicle) + local eyepod = vehicle.AttachedWireEyePod + if eyepod ~= nil then + if IsValid(eyepod) then + eyepod.driver = vehicle:GetDriver() + eyepod:updateEyePodState(eyepod.enabled) + eyepod:UpdateOverlay() + else + vehicle.AttachedWireEyePod = nil + end + end +end) + +hook.Add("PlayerLeaveVehicle","gmod_wire_eyepod_leavevehicle",function(ply,vehicle) + local eyepod = vehicle.AttachedWireEyePod + if eyepod ~= nil then + if IsValid(eyepod) then + eyepod:updateEyePodState(false) + eyepod.driver = nil + if (eyepod.X ~= 0 or eyepod.Y ~= 0) and eyepod.DefaultToZero == 1 then + eyepod.X = 0 + eyepod.Y = 0 + WireLib.TriggerOutput( eyepod, "X", 0 ) + WireLib.TriggerOutput( eyepod, "Y", 0 ) + WireLib.TriggerOutput( eyepod, "XY", {0,0} ) + end + eyepod:UpdateOverlay() + else + vehicle.AttachedWireEyePod = nil + end + end +end) + +function ENT:OnRemove() + self:UnlinkEnt() +end + +local function AngNorm(Ang) + return (Ang + 180) % 360 - 180 +end +local function AngNorm90(Ang) + return (Ang + 90) % 180 - 90 +end + +function ENT:TriggerInput(iname, value) + -- Change variables to reflect input + if iname == "Enable" then + self.enabled = value ~= 0 + + if self.enabled == false and self.DefaultToZero == 1 and (self.X ~= 0 or self.Y ~= 0) then + self.X = 0 + self.Y = 0 + WireLib.TriggerOutput( self, "X", 0 ) + WireLib.TriggerOutput( self, "Y", 0 ) + WireLib.TriggerOutput( self, "XY", {0,0} ) + end + + self:UpdateOverlay() + elseif iname == "SetPitch" then + self.eyeAng = Angle(AngNorm90(value), self.eyeAng.y, self.eyeAng.r) + elseif iname == "SetYaw" then + if self.rotate90 == true then + self.eyeAng = Angle(AngNorm90(self.eyeAng.p), AngNorm(value+90), self.eyeAng.r) + else + self.eyeAng = Angle(AngNorm90(self.eyeAng.p), AngNorm(value), self.eyeAng.r) + end + elseif iname == "SetViewAngle" then + if self.rotate90 == true then + self.eyeAng = Angle(AngNorm90(value.p), AngNorm(value.y+90), 0) + else + self.eyeAng = Angle(AngNorm90(value.p), AngNorm(value.y), 0) + end + elseif iname == "UnfreezePitch" then + self.freezePitch = value == 0 + elseif iname == "UnfreezeYaw" then + self.freezeYaw = value == 0 + end + + if IsValid(self.pod) and IsValid(self.driver) then + self:updateEyePodState(self.enabled) + end +end + +hook.Add("SetupMove", "WireEyePodMouseControl", function(ply, movedata) + --is the player in a vehicle? + if not ply then return end + if not ply:InVehicle() then return end + + local vehicle = ply:GetVehicle() + if not IsValid(vehicle) then return end + + --get the EyePod + local eyePod = vehicle.AttachedWireEyePod + + --is the vehicle linked to an EyePod? + if not IsValid(eyePod) then return end + + if eyePod.enabled then + + local cmd = ply:GetCurrentCommand() + + local oldX = eyePod.X + local oldY = eyePod.Y + + --reset the output so it is not cumualative if you want the rate of change + if eyePod.ShowRateOfChange == 1 then + eyePod.X = 0 + eyePod.Y = 0 + end + + --update the cumualative output + eyePod.X = cmd:GetMouseX()/10 + eyePod.X + eyePod.Y = -cmd:GetMouseY()/10 + eyePod.Y + + --clamp the output + if eyePod.ClampX == 1 then + eyePod.X = math.Clamp(eyePod.X, eyePod.ClampXMin, eyePod.ClampXMax) + end + if eyePod.ClampY == 1 then + eyePod.Y = math.Clamp(eyePod.Y, eyePod.ClampYMin, eyePod.ClampYMax) + end + + if oldX ~= eyePod.X or oldY ~= eyePod.Y then + -- Update outputs + WireLib.TriggerOutput(eyePod, "X", eyePod.X) + WireLib.TriggerOutput(eyePod, "Y", eyePod.Y) + + local XY_Vec = {eyePod.X, eyePod.Y} + WireLib.TriggerOutput(eyePod, "XY", XY_Vec) + end + + --reset the mouse + cmd:SetMouseX(0) + cmd:SetMouseY(0) + + end +end) + +-- Advanced Duplicator Support +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if IsValid(self.pod) then + info.pod = self.pod:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self:PodLink(GetEntByID(info.pod)) +end + +duplicator.RegisterEntityClass("gmod_wire_eyepod", WireLib.MakeWireEnt, "Data", "DefaultToZero", "ShowRateOfChange" , "ClampXMin" , "ClampXMax" , "ClampYMin" , "ClampYMax" , "ClampX", "ClampY") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_forcer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_forcer.lua new file mode 100644 index 0000000..a6d7481 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_forcer.lua @@ -0,0 +1,124 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Forcer" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Forcer" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) + self:NetworkVar( "Bool", 0, "ShowBeam" ) + self:NetworkVar( "Bool", 1, "BeamHighlight" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Force = 0 + self.OffsetForce = 0 + self.Velocity = 0 + + self.Inputs = WireLib.CreateInputs( self, { "Force", "OffsetForce", "Velocity", "Length" } ) + + self:Setup(0, 100, true, false) +end + +function ENT:Setup( Force, Length, ShowBeam, Reaction ) + self.ForceMul = Force or 1 + self.Reaction = Reaction or false + if Length then self:SetBeamLength(Length) end + if ShowBeam ~= nil then self:SetShowBeam(ShowBeam) end + self:ShowOutput() +end + +function ENT:TriggerInput( name, value ) + if (name == "Force") then + self.Force = value + self:SetBeamHighlight(value != 0) + self:ShowOutput() + elseif (name == "OffsetForce") then + self.OffsetForce = value + self:SetBeamHighlight(value != 0) + self:ShowOutput() + elseif (name == "Velocity") then + self.Velocity = math.Clamp(value,-100000,100000) + self:SetBeamHighlight(value != 0) + self:ShowOutput() + elseif (name == "Length") then + self:SetBeamLength(math.Round(value)) + self:ShowOutput() + end +end + +local clamp = WireLib.clampForce + +function ENT:Think() + if self.Force == 0 and self.OffsetForce == 0 and self.Velocity == 0 then return end + + local Forward = self:GetUp() + local BeamOrigin = self:GetPos() + Forward * self:OBBMaxs().z + + local trace = util.TraceLine { + start = BeamOrigin, + endpos = BeamOrigin + self:GetBeamLength() * Forward, + filter = self + } + + if not IsValid(trace.Entity) then return end + if IsValid(self:GetPlayer()) and not gamemode.Call( "GravGunPunt", self:GetPlayer(), trace.Entity ) then return end + + if trace.Entity:GetMoveType() == MOVETYPE_VPHYSICS then + local phys = trace.Entity:GetPhysicsObject() + if not IsValid(phys) then return end + + local force = clamp(Forward * self.Force * self.ForceMul) + local offsetForce = clamp(Forward * self.OffsetForce * self.ForceMul) + local velocity = clamp(Forward * self.Velocity) + + if self.Force ~= 0 then phys:ApplyForceCenter( force ) end + if self.OffsetForce ~= 0 then phys:ApplyForceOffset( offsetForce, trace.HitPos ) end + if self.Velocity ~= 0 then phys:SetVelocityInstantaneous( velocity ) end + else + local velocity = clamp(Forward * self.Velocity) + if self.Velocity ~= 0 then trace.Entity:SetVelocity( velocity ) end + end + + if self.Reaction and IsValid(self:GetPhysicsObject()) and (self.Force + self.OffsetForce ~= 0) then + local reactionForce = clamp(Forward * -(self.Force + self.OffsetForce) * self.ForceMul) + self:GetPhysicsObject():ApplyForceCenter( reactionForce ) + end + + self:NextThink( CurTime() ) + return true +end + +function ENT:ShowOutput() + self:SetOverlayText( + "Center Force = "..math.Round(self.ForceMul * self.Force).. + "\nOffset Force = "..math.Round(self.ForceMul * self.OffsetForce).. + "\nVelocity = "..math.Round(self.Velocity).. + "\nLength = " .. math.Round(self:GetBeamLength()) + ) +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + info.ForceMul = self.ForceMul + info.Reaction = self.Reaction + return info +end + + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + self:Setup( info.ForceMul, info.Length, info.ShowBeam, info.Reaction ) + + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) +end + +--Moves old "A" input to new "Force" input for older saves +WireLib.AddInputAlias( "A", "Force" ) + +duplicator.RegisterEntityClass("gmod_wire_forcer", WireLib.MakeWireEnt, "Data", "Force", "Length", "ShowBeam", "Reaction") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_freezer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_freezer.lua new file mode 100644 index 0000000..584f745 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_freezer.lua @@ -0,0 +1,148 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Freezer" +ENT.WireDebugName = "Freezer" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.State = false + self.CollisionState = 0 + self.Marks = {} + self.Inputs = WireLib.CreateInputs(self, {"Activate", "Disable Collisions"}) + self:UpdateOutputs() +end + +function ENT:TriggerInput(name, value) + if name == "Activate" then + self.State = value ~= 0 + for _, ent in pairs(self.Marks) do + if IsValid(ent) and IsValid(ent:GetPhysicsObject()) then + if self.State then + -- Garry's Mod provides an OnPhysgunFreeze hook, which will + -- unfreeze the object if prop protection allows it... + gamemode.Call("OnPhysgunFreeze", self, ent:GetPhysicsObject(), ent, self:GetPlayer()) + else + -- ...and a CanPlayerUnfreeze hook, which will return whether + -- prop protection allows it, but won't unfreeze do the unfreezing. + if not gamemode.Call("CanPlayerUnfreeze", self:GetPlayer(), ent, ent:GetPhysicsObject()) then return end + ent:GetPhysicsObject():EnableMotion(true) + ent:GetPhysicsObject():Wake() + end + end + end + elseif name == "Disable Collisions" then + self.CollisionState = math.Clamp(math.Round(value), 0, 4) + for _, ent in pairs(self.Marks) do + if IsValid(ent) and IsValid(ent:GetPhysicsObject()) and gamemode.Call("CanTool", self:GetPlayer(), WireLib.dummytrace(ent), "nocollide") then + if self.CollisionState == 0 then + ent:SetCollisionGroup( COLLISION_GROUP_NONE ) + ent:GetPhysicsObject():EnableCollisions(true) + elseif self.CollisionState == 1 then + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + ent:GetPhysicsObject():EnableCollisions(true) + elseif self.CollisionState == 2 then + ent:SetCollisionGroup( COLLISION_GROUP_NONE ) + ent:GetPhysicsObject():EnableCollisions(false) + elseif self.CollisionState == 3 then + ent:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + ent:GetPhysicsObject():EnableCollisions(true) + elseif self.CollisionState == 4 then + ent:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + ent:GetPhysicsObject():EnableCollisions(false) + end + end + end + end + self:UpdateOverlay() +end + +local collisionDescriptions = { + [0] = "Normal Collisions", + [1] = "Disabled prop/player Collisions", + [2] = "Disabled prop/world Collisions", + [3] = "Disabled player Collisions", + [4] = "Disabled prop/world/player Collisions" +} + +function ENT:UpdateOverlay() + self:SetOverlayText( + (self.State and "Frozen" or "Unfrozen") .. "\n" .. + collisionDescriptions[self.CollisionState] .. "\n" .. + "Linked Entities: " .. #self.Marks) +end +function ENT:UpdateOutputs() + self:UpdateOverlay() + + WireLib.SendMarks(self) -- Stool's yellow lines +end + +function ENT:CheckEnt( ent ) + if IsValid(ent) then + for index, e in pairs( self.Marks ) do + if (e == ent) then return true, index end + end + end + return false, 0 +end + +function ENT:LinkEnt( ent ) + if (self:CheckEnt( ent )) then return false end + self.Marks[#self.Marks+1] = ent + ent:CallOnRemove("AdvEMarker.Unlink", function(ent) + if IsValid(self) then self:UnlinkEnt(ent) end + end) + self:UpdateOutputs() + return true +end + +function ENT:UnlinkEnt( ent ) + local bool, index = self:CheckEnt( ent ) + if (bool) then + table.remove( self.Marks, index ) + self:UpdateOutputs() + end + return bool +end + +function ENT:ClearEntities() + self.Marks = {} + self:UpdateOutputs() +end + +duplicator.RegisterEntityClass( "gmod_wire_freezer", WireLib.MakeWireEnt, "Data" ) + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if next(self.Marks) then + local tbl = {} + for index, e in pairs( self.Marks ) do + tbl[index] = e:EntIndex() + end + + info.marks = tbl + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if info.Ent1 then + -- Old wire-extras dupe support + table.insert(self.Marks, GetEntByID(info.Ent1)) + end + if info.marks then + for index, entindex in pairs(info.marks) do + self.Marks[index] = GetEntByID(entindex) + end + end + self:TriggerInput("Disable Collisions", self.Inputs["Disable Collisions"].Value) + self:UpdateOutputs() +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_friendslist.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_friendslist.lua new file mode 100644 index 0000000..e4151a3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_friendslist.lua @@ -0,0 +1,140 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Friends List" +ENT.WireDebugName = "Friends List" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + BaseClass.Initialize( self ) + + WireLib.CreateInputs( self, {"CheckEntity [ENTITY]", "CheckSteamID [STRING]", "CheckEntityID"} ) + WireLib.CreateOutputs( self, {"Checked", "Friends [ARRAY]", "AmountConnected", "AmountTotal"} ) + + self.friends_lookup = {} + self.steamids = {} + self.steamids_lookup = {} + self.save_on_entity = false + self:UpdateOutputs() +end + +function ENT:Setup( save_on_entity, friends_steamids ) + self.save_on_entity = false + self:UpdateFriendslist( friends_steamids ) + self.save_on_entity = save_on_entity or false + self:UpdateOutputs() +end + +function ENT:UpdateFriendslist( friends_steamids ) + if self.save_on_entity then return end + + self.friends_lookup = {} + + self.steamids = table.Copy(friends_steamids) + self.steamids_lookup = {} + for i=1,#friends_steamids do + self.steamids_lookup[friends_steamids[i]] = true + end + + local plys = player.GetHumans() + for i=1,#plys do + self:Connected( plys[i] ) + end + + self:UpdateOutputs() +end + +function ENT:Connected( ply ) + local steamid = ply:SteamID() + + -- already added + if self.friends_lookup[ply] then return end + + if self.steamids_lookup[ply:SteamID()] then + self.friends_lookup[ply] = true + self:UpdateOutputs() + end +end + +function ENT:Disconnected( ply ) + local steamid = ply:SteamID() + + -- not already added + if not self.friends_lookup[ply] then return end + + if self.steamids_lookup[ply:SteamID()] then + self.friends_lookup[ply] = nil + self:UpdateOutputs() + end +end + +hook.Add( "PlayerInitialSpawn", "wire_friendslist_connect", function( ply ) + local friendslists = ents.FindByClass( "gmod_wire_friendslist" ) + for i=1,#friendslists do friendslists[i]:Connected( ply ) end +end) +hook.Add( "PlayerDisconnected", "wire_friendslist_disconnect", function( ply ) + local friendslists = ents.FindByClass( "gmod_wire_friendslist" ) + for i=1,#friendslists do friendslists[i]:Disconnected( ply ) end +end) + +function ENT:TriggerInput( name, value ) + local ply + + if name == "CheckEntity" then + ply = value + elseif name == "CheckSteamID" then + ply = player.GetBySteamID( value ) + elseif name == "CheckEntityID" then + ply = Entity(value) + end + + if not IsValid( ply ) then return end + + if self.friends_lookup[ply] then + WireLib.TriggerOutput( self, "Checked", 1 ) + else + WireLib.TriggerOutput( self, "Checked", -1 ) + end + + timer.Remove( "wire_friendslist_" .. self:EntIndex() ) + timer.Create( "wire_friendslist_" .. self:EntIndex(), 0.1, 1, function() + if IsValid( self ) then + WireLib.TriggerOutput( self, "Checked", 0 ) + end + end) +end + +function ENT:UpdateOutputs() + local str = {} + + str[#str+1] = "Saved on entity: " .. (self.save_on_entity and "Yes" or "No") .. "\n" + str[#str+1] = #self.steamids .. " total friends" + str[#str+1] = "\nConnected:" + + local not_connected = {} + local connected = {} + + for i=1, #self.steamids do + local steamid = self.steamids[i] + local ply = player.GetBySteamID( steamid ) + + if IsValid( ply ) then + str[#str+1] = ply:Nick() .. " (" .. steamid .. ")" + connected[#connected+1] = ply + else + not_connected[#not_connected+1] = steamid + end + end + + table.insert( str, 2, #connected .. " connected friends" ) + + WireLib.TriggerOutput( self, "Friends", connected ) + WireLib.TriggerOutput( self, "AmountConnected", #connected ) + WireLib.TriggerOutput( self, "AmountTotal", #self.steamids ) + + local str = table.concat( str, "\n" ) + if #not_connected > 0 then str = str .. "\n\nNot connected:\n" .. table.concat( not_connected, "\n" ) end + self:SetOverlayText( str ) +end + +duplicator.RegisterEntityClass("gmod_wire_friendslist", WireLib.MakeWireEnt, "Data", "save_on_entity", "steamids") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_fx_emitter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_fx_emitter.lua new file mode 100644 index 0000000..cadee8c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_fx_emitter.lua @@ -0,0 +1,135 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire FX Emitter" +ENT.WireDebugName = "FX Emitter" + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "On" ) + self:NetworkVar( "Int", 0, "Effect" ) + self:NetworkVar( "Float", 0, "Delay" ) + self:NetworkVar( "Vector", 0, "FXDir" ) +end + +function ENT:GetFXPos() + return self:GetPos() +end + +-- Effect registration + +ENT.Effects = {} +ENT.fxcount = 0 +local fx_emitter = ENT + +ComboBox_Wire_FX_Emitter_Options = {} + +function AddFXEmitterEffect(name, func, nicename) + fx_emitter.fxcount = fx_emitter.fxcount+1 + // Maintain a global reference for these effects + ComboBox_Wire_FX_Emitter_Options[name] = fx_emitter.fxcount + if CLIENT then + fx_emitter.Effects[fx_emitter.fxcount] = func + language.Add( "wire_fx_emitter_"..name, nicename ) + end +end + +-- Modular effect adding.. stuff +include( "wire/fx_emitter_default.lua" ) + + +if CLIENT then + ENT.Delay = 0.05 + + function ENT:Draw() + // Don't draw if we are in camera mode + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + if ( wep:IsValid() ) then + local weapon_name = wep:GetClass() + if ( weapon_name == "gmod_camera" ) then return end + end + + BaseClass.Draw( self ) + end + + function ENT:Think() + if not self:GetOn() then return end + + if ( self.Delay > CurTime() ) then return end + self.Delay = CurTime() + self:GetDelay() + + local Effect = self:GetEffect() + + // Missing effect... replace it if possible :/ + if ( !self.Effects[ Effect ] ) then if ( self.Effects[1] ) then Effect = 1 else return end end + + local Angle = self:GetAngles() + + local FXDir = self:GetFXDir() + if FXDir and not FXDir:IsZero() then Angle = FXDir:Angle() else self:GetUp():Angle() end + + local FXPos = self:GetFXPos() + if not FXPos or FXDir:IsZero() then FXPos=self:GetPos() + Angle:Forward() * 12 end + + local b, e = pcall( self.Effects[Effect], FXPos, Angle ) + + if (!b) then + // Report the error + Print(self.Effects) + Print(FXPos) + Print(Angle) + Msg("Error in Emitter "..tostring(Effect).."\n -> "..tostring(e).."\n") + + // Remove the naughty function + self.Effects[ Effect ] = nil + end + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:SetModel( "models/props_lab/tpplug.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:DrawShadow( false ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + self.Inputs = WireLib.CreateInputs(self, {"On", "Effect", "Delay", "Direction [VECTOR]"}) +end + +function ENT:Setup(delay, effect) + if delay then self:SetDelay(delay) end + if effect then self:SetEffect(effect) end +end + +function ENT:TriggerInput( inputname, value, iter ) + if inputname == "Direction" then + self:SetFXDir(value:GetNormal()) + elseif inputname == "Effect" then + self:SetEffect(math.Clamp(value - value % 1, 1, self.fxcount)) + elseif inputname == "On" then + self:SetOn(value ~= 0) + elseif inputname == "Delay" then + self:SetDelay(math.Clamp(value, 0.05, 20)) + --elseif (inputname == "Position") then -- removed for excessive mingability + -- self:SetFXPos(value) + end +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + -- Old dupes stored this info here rather than as RegisterEntityClass vars + if info.Effect then self:SetEffect(info.Effect) end + if info.Delay then self:SetDelay(info.Delay) end +end + +duplicator.RegisterEntityClass("gmod_wire_fx_emitter", WireLib.MakeWireEnt, "Data", "delay", "effect" ) +-- Note: delay and effect are here for backwards compatibility, they're now stored in the DataTable diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gate.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gate.lua new file mode 100644 index 0000000..0dacf5c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gate.lua @@ -0,0 +1,216 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Gate" +ENT.WireDebugName = "Gate" + +if CLIENT then return end -- No more client + +local Wire_EnableGateInputValues = CreateConVar("Wire_EnableGateInputValues", 1, FCVAR_ARCHIVE) + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = {} + self.Outputs = {} +end + +function ENT:Setup( action, noclip ) + local gate = GateActions[action] + if not gate then return end + if GateActions[action].is_banned then return end + + self.Updating = true + + self.action = action + + self.WireDebugName = gate.name + + WireLib.AdjustSpecialInputs(self, gate.inputs, gate.inputtypes ) + if (gate.outputs) then + WireLib.AdjustSpecialOutputs(self, gate.outputs, gate.outputtypes) + else + --Wire_AdjustOutputs(self, { "Out" }) + WireLib.AdjustSpecialOutputs(self, { "Out" }, gate.outputtypes) + end + + if (gate.reset) then + gate.reset(self) + end + + local ReadCell = gate.ReadCell + if ReadCell then + function self:ReadCell(Address) + return ReadCell(gate,self,Address) + end + else + self.ReadCell = nil + end + + local WriteCell = gate.WriteCell + if WriteCell then + function self:WriteCell(Address,value) + return WriteCell(gate,self,Address,value) + end + else + self.WriteCell = nil + end + + if (noclip) then + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + self.noclip = noclip + + self.Action = gate + self.PrevValue = nil + + --self.Action.inputtypes = self.Action.inputtypes or {} + + self.Updating = nil + + self:CalcOutput() + self:ShowOutput() +end + + +function ENT:OnInputWireLink(iname, itype, src, oname, otype) + if (self.Action) and (self.Action.OnInputWireLink) then + self.Action.OnInputWireLink(self, iname, itype, src, oname, otype) + end +end + +function ENT:OnOutputWireLink(oname, otype, dst, iname, itype) + if (self.Action) and (self.Action.OnOutputWireLink) then + self.Action.OnOutputWireLink(self, oname, otype, dst, iname, itype) + end +end + +function ENT:TriggerInput(iname, value, iter) + if self.Updating then return end + if (self.Action) and (not self.Action.timed) then + self:CalcOutput(iter) + self:ShowOutput() + end +end + +function ENT:Think() + BaseClass.Think(self) + + if (self.Action) and (self.Action.timed) then + self:CalcOutput() + self:ShowOutput() + + self:NextThink(CurTime()+0.02) + return true + end +end + + +function ENT:CalcOutput(iter) + if (self.Action) and (self.Action.output) then + if (self.Action.outputs) then + local result = { self.Action.output(self, unpack(self:GetActionInputs(), 1, #self.Action.inputs)) } + + for k,v in ipairs(self.Action.outputs) do + Wire_TriggerOutput(self, v, result[k] or WireLib.DT[ self.Outputs[v].Type ].Zero, iter) + end + else + local value = self.Action.output(self, unpack(self:GetActionInputs(), 1, #self.Action.inputs)) or WireLib.DT[ self.Outputs.Out.Type ].Zero + + Wire_TriggerOutput(self, "Out", value, iter) + end + end +end + +function ENT:ShowOutput() + local txt + + if (self.Action) then + txt = (self.Action.name or "No Name") + if (self.Action.label) then + txt = txt.."\n"..self.Action.label(self:GetActionOutputs(), unpack(self:GetActionInputs(Wire_EnableGateInputValues:GetBool()), 1, #self.Action.inputs)) + end + else + txt = "Invalid gate!" + end + + self:SetOverlayText(txt) +end + + +function ENT:OnRestore() + self.Action = GateActions[self.action] + + BaseClass.OnRestore(self) +end + + +function ENT:GetActionInputs(as_names) + local Args = {} + + if (self.Action.compact_inputs) then + -- If a gate has compact inputs (like Arithmetic - Add), nil inputs are truncated so {0, nil, nil, 5, nil, 1} becomes {0, 5, 1} + for k,v in ipairs(self.Action.inputs) do + local input = self.Inputs[v] + if (not input) then + ErrorNoHalt("Wire Gate ("..self.action..") error: Missing input! ("..k..","..v..")\n") + return {} + end + + if IsValid(input.Src) then + if (as_names) then + table.insert(Args, input.Src.WireName or input.Src.WireDebugName or v) + else + table.insert(Args, input.Value) + end + end + end + + while (#Args < self.Action.compact_inputs) do + if (as_names) then + table.insert(Args, self.Action.inputs[#Args+1] or "*Not enough inputs*") + else + --table.insert( Args, WireLib.DT[ (self.Action.inputtypes[#Args+1] or "NORMAL") ].Zero ) + table.insert( Args, WireLib.DT[ self.Inputs[ self.Action.inputs[#Args+1] ].Type ].Zero ) + end + end + else + for k,v in ipairs(self.Action.inputs) do + local input = self.Inputs[v] + if (not input) then + ErrorNoHalt("Wire Gate ("..self.action..") error: Missing input! ("..k..","..v..")\n") + return {} + end + + if (as_names) then + Args[k] = IsValid(input.Src) and (input.Src.WireName or input.Src.WireDebugName) or v + else + Args[k] = IsValid(input.Src) and input.Value or WireLib.DT[ self.Inputs[v].Type ].Zero + end + end + end + + return Args +end + +function ENT:GetActionOutputs() + if (self.Action.outputs) then + local result = {} + for _,v in ipairs(self.Action.outputs) do + result[v] = self.Outputs[v].Value or WireLib.DT[ self.Outputs[v].Type ].Zero + end + + return result + end + + return self.Outputs.Out.Value or WireLib.DT[ self.Outputs.Out.Type ].Zero +end + +function WireLib.MakeWireGate(pl, Pos, Ang, model, action, noclip, frozen, nocollide) + if not GateActions[action] then return end + if GateActions[action].is_banned then return end + + return WireLib.MakeWireEnt(pl, { Class = "gmod_wire_gate", Pos=Pos, Angle=Ang, Model=model }, action, noclip) +end +duplicator.RegisterEntityClass("gmod_wire_gate", WireLib.MakeWireGate, "Pos", "Ang", "Model", "action", "noclip") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gimbal.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gimbal.lua new file mode 100644 index 0000000..7346082 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gimbal.lua @@ -0,0 +1,90 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Gimbal" +ENT.WireDebugName = "Gimbal" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:GetPhysicsObject():EnableGravity(false) + + self.Inputs = WireLib.CreateInputs(self,{"On", "X", "Y", "Z", "Target [VECTOR]", "Direction [VECTOR]", "Angle [ANGLE]", "AngleOffset [ANGLE]"}) + + self.XYZ = Vector() + self.TargetAngOffset = Matrix() + self.TargetAngOffset:SetAngles(Angle(90,0,0)) +end + +function ENT:TriggerInput(name,value) + if name == "On" then + self.On = value ~= 0 + else + self.TargetPos = nil + self.TargetDir = nil + self.TargetAng = nil + + if name == "X" then + self.XYZ.x = value + self.TargetPos = self.XYZ + elseif name == "Y" then + self.XYZ.y = value + self.TargetPos = self.XYZ + elseif name == "Z" then + self.XYZ.z = value + self.TargetPos = self.XYZ + elseif name == "Target" then + self.XYZ = Vector(value.x, value.y, value.z) + self.TargetPos = self.XYZ + elseif name == "Direction" then + self.TargetDir = value + elseif name == "Angle" then + self.TargetAng = value + elseif name == "AngleOffset" then + self.TargetAngOffset = Matrix() + self.TargetAngOffset:SetAngles(value) + end + end + self:ShowOutput() + return true +end + + +function ENT:Think() + if self.On then + local ang + if self.TargetPos then + ang = (self.TargetPos - self:GetPos()):Angle() + elseif self.TargetDir then + ang = self.TargetDir:Angle() + elseif self.TargetAng then + ang = self.TargetAng + end + if ang then + local m = Matrix() + m:SetAngles(ang) + m = m * self.TargetAngOffset + self:SetAngles(m:GetAngles()) + end + -- TODO: Put an option in the CPanel for Angle(90,0,0), and other useful directions + self:GetPhysicsObject():Wake() + end + self:NextThink(CurTime()) + return true +end + +function ENT:ShowOutput() + if not self.On then + self:SetOverlayText("Off") + elseif self.TargetPos then + self:SetOverlayText(string.format("Aiming towards (%.2f, %.2f, %.2f)", self.XYZ.x, self.XYZ.y, self.XYZ.z)) + elseif self.TargetDir then + self:SetOverlayText(string.format("Aiming (%.4f, %.4f, %.4f)", self.TargetDir.x, self.TargetDir.y, self.TargetDir.z)) + elseif self.TargetAng then + self:SetOverlayText(string.format("Aiming (%.1f, %.1f, %.1f)", self.TargetAng.pitch, self.TargetAng.yaw, self.TargetAng.roll)) + end +end + +duplicator.RegisterEntityClass("gmod_wire_gimbal", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gps.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gps.lua new file mode 100644 index 0000000..aa8dda6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gps.lua @@ -0,0 +1,112 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire GPS" +ENT.WireDebugName = "GPS" + +if CLIENT then + function ENT:Think() + BaseClass.Think(self) + + local pos = self:GetPos() + if (COLOSSAL_SANDBOX) then pos = pos * 6.25 end + local txt = string.format( "Position = %0.3f, %0.3f, %0.3f", math.Round(pos.x,3),math.Round(pos.y,3),math.Round(pos.z,3) ) + + self:SetOverlayText( txt ) + + self:NextThink(CurTime()+0.04) + return true + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.storedpositions = {}; + self.arrayindex = 0; + + self.Inputs = Wire_CreateInputs(self, { "Store/Save Pos", "Next", "Remove Save Position"}) + self.Outputs = WireLib.CreateSpecialOutputs( self, { "X", "Y", "Z", "Vector", "Recall X", "Recall Y", "Recall Z", "Recall Vector", "Current Memory"}, { "NORMAL", "NORMAL", "NORMAL", "VECTOR", "NORMAL", "NORMAL", "NORMAL", "VECTOR", "NORMAL"}) +end + +function ENT:Setup() + self.Value = 0 + self.PrevOutput = nil + + //self:ShowOutput(0, 0, 0) + Wire_TriggerOutput(self, "X", 0) + Wire_TriggerOutput(self, "Y", 0) + Wire_TriggerOutput(self, "Z", 0) + Wire_TriggerOutput(self, "Vector", Vector(0,0,0)) + Wire_TriggerOutput(self, "Recall X", 0) + Wire_TriggerOutput(self, "Recall Y", 0) + Wire_TriggerOutput(self, "Recall Z", 0) + Wire_TriggerOutput(self, "Recall Vector", Vector(0,0,0)) + Wire_TriggerOutput(self, "Current Memory", 0) +end + +function ENT:Think() + BaseClass.Think(self) + + local pos = self:GetPos() + if (COLOSSAL_SANDBOX) then pos = pos * 6.25 end + + Wire_TriggerOutput(self, "X", pos.x) + Wire_TriggerOutput(self, "Y", pos.y) + Wire_TriggerOutput(self, "Z", pos.z) + Wire_TriggerOutput(self, "Vector", pos) + Wire_TriggerOutput(self, "Current Memory", self.arrayindex) + if self.arrayindex > 0 then + Wire_TriggerOutput(self, "Recall X", self.storedpositions[self.arrayindex].x) + Wire_TriggerOutput(self, "Recall Y", self.storedpositions[self.arrayindex].y) + Wire_TriggerOutput(self, "Recall Z", self.storedpositions[self.arrayindex].z) + Wire_TriggerOutput(self, "Recall Vector", self.storedpositions[self.arrayindex]) + else + Wire_TriggerOutput(self, "Recall X", 0) + Wire_TriggerOutput(self, "Recall Y", 0) + Wire_TriggerOutput(self, "Recall Z", 0) + Wire_TriggerOutput(self, "Recall Vector", Vector(0,0,0)) + end + + self:NextThink(CurTime()+0.04) + return true +end + +function ENT:TriggerInput(iname, value) + if (iname == "Store/Save Pos") then + if (value ~= 0) then + local curpos = self:GetPos() + table.insert(self.storedpositions, curpos) + self.arrayindex = self.arrayindex+1 + end + elseif (iname == "Next") then + if (value ~= 0) then + if # self.storedpositions > 0 then + if not (self.arrayindex >= # self.storedpositions) then + self.arrayindex = self.arrayindex+1; + else + self.arrayindex = 1; --loop back + end + end + end + elseif (iname == "Remove Save Position") then + if (value ~= 0) then + if self.arrayindex ~= 0 then + table.remove(self.storedpositions, self.arrayindex) + end + if (self.arrayindex == 1) and (# self.storedpositions == 0) then + self.arrayindex = 0 + end + if (self.arrayindex == (# self.storedpositions+1)) then + self.arrayindex = self.arrayindex-1 + end + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_gps", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/cl_gpuvm.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/cl_gpuvm.lua new file mode 100644 index 0000000..2355d5b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/cl_gpuvm.lua @@ -0,0 +1,2348 @@ +-------------------------------------------------------------------------------- +-- Override virtual machine functions and features +-------------------------------------------------------------------------------- +local VM = {} + +surface.CreateFont( "WireGPU_ErrorFont",{ + font="Coolvetica", + size = 26, + weight = 200, + antialias = true, + additive = false +}) + +function ENT:OverrideVM() + -- Store VM calls that will be overriden + self.VM.BaseReset = self.VM.Reset + + -- Add additional VM functionality + for k,v in pairs(VM) do + if k == "OpcodeTable" then + for k2,v2 in pairs(v) do + self.VM.OpcodeTable[k2] = v2 + end + else + self.VM[k] = v + end + end + + self.VM.ErrorText = {} + self.VM.ErrorText[2] = "Program ended unexpectedly" + self.VM.ErrorText[3] = "Arithmetic division by zero" + self.VM.ErrorText[4] = "Unknown instruction detected" + self.VM.ErrorText[5] = "Internal GPU error" + self.VM.ErrorText[6] = "Stack violation error" + self.VM.ErrorText[7] = "Memory I/O fault" + self.VM.ErrorText[13] = "General fault" + self.VM.ErrorText[15] = "Address space violation" + self.VM.ErrorText[16] = "Pants integrity violation" + self.VM.ErrorText[17] = "Frame instruction limit" + self.VM.ErrorText[23] = "Error reading string data" + + self.VM.Interrupt = function(self,interruptNo,interruptParameter,isExternal,cascadeInterrupt) + if self.ASYNC == 1 then + if self.EntryPoint5 > 0 then + self.IP = self.EntryPoint5 + self.LADD = interruptParameter or self.XEIP + self.LINT = interruptNo + else + -- Shutdown asynchronous thread + self.Memory[65528] = 0 + end + else + if self.EntryPoint3 > 0 then + self.IP = self.EntryPoint3 + self.LADD = interruptParameter or self.XEIP + self.LINT = interruptNo + else + if (interruptNo == 2) and (self.XEIP == 0) then self.INTR = 1 return end + + if self.RenderEnable == 1 then + surface.SetTexture(0) + surface.SetDrawColor(0,0,0,120) + surface.DrawRect(0,0,self.ScreenWidth,self.ScreenHeight) + + draw.DrawText("Error in the instruction stream","WireGPU_ErrorFont",48,16,Color(255,255,255,255)) + draw.DrawText((self.ErrorText[interruptNo] or "Unknown error").." (#"..interruptNo..")","WireGPU_ErrorFont",16,16+32*2,Color(255,255,255,255)) + draw.DrawText("Parameter: "..(interruptParameter or 0),"WireGPU_ErrorFont",16,16+32*3,Color(255,255,255,255)) + draw.DrawText("Address: "..self.XEIP,"WireGPU_ErrorFont",16,16+32*4,Color(255,255,255,255)) + + local errorPosition = CPULib.Debugger.PositionByPointer[self.XEIP] + if errorPosition then + local posText = HCOMP:formatPrefix(errorPosition.Line,errorPosition.Col,errorPosition.File) + + draw.DrawText("Debugging data present (may be invalid):","WireGPU_ErrorFont",16,16+32*6,Color(255,255,255,255)) + draw.DrawText("Error at "..posText,"WireGPU_ErrorFont",16,16+32*7,Color(255,255,255,255)) + draw.DrawText("Line: ","WireGPU_ErrorFont",16,16+32*9,Color(255,255,255,255)) + end + end + self.INTR = 1 + end + end + end + + -- Override ports + self.VM.WritePort = function(VM,Port,Value) + VM:WriteCell(63488+Port,Value) + end + self.VM.ReadPort = function(VM,Port) + return VM:ReadCell(63488+Port) + end + + -- Override writecell + self.VM.BaseWriteCell = self.VM.WriteCell + self.VM.WriteCell = function(VM,Address,Value) + VM:BaseWriteCell(Address,Value) + if (Address >= 65536) and (Address <= 131071) then + if VM.MemBusCount < 8 then + VM.MemBusCount = VM.MemBusCount + 1 + VM.MemBusBuffer[Address] = Value + end + elseif Address == 65534 then + VM:HardReset() + elseif Address == 65530 then + VM.ROM = {} + elseif Address == 65529 then + VM.AsyncState = {} + end + end + + -- Add internal registers + self.VM.InternalRegister[128] = "EntryPoint0" + self.VM.InternalRegister[129] = "EntryPoint1" + self.VM.InternalRegister[130] = "EntryPoint2" + self.VM.InternalRegister[131] = "EntryPoint3" + self.VM.InternalRegister[132] = "EntryPoint4" + self.VM.InternalRegister[133] = "EntryPoint5" + self.VM.InternalRegister[134] = "EntryPoint6" + self.VM.InternalRegister[135] = "EntryPoint7" + self.VM.InternalRegister[136] = "CoordinatePipe" + self.VM.InternalRegister[137] = "VertexPipe" + self.VM.InternalRegister[138] = "VertexBufZSort" + self.VM.InternalRegister[139] = "VertexLighting" + self.VM.InternalRegister[140] = "VertexBufEnabled" + self.VM.InternalRegister[141] = "VertexCulling" + self.VM.InternalRegister[142] = "DistanceCulling" + self.VM.InternalRegister[143] = "Font" + self.VM.InternalRegister[144] = "FontSize" + self.VM.InternalRegister[145] = "WordWrapMode" + self.VM.InternalRegister[146] = "ASYNC" + self.VM.InternalRegister[147] = "INIT" + + -- Remove internal registers + self.VM.InternalRegister[24] = nil --IDTR + self.VM.InternalRegister[32] = nil --IF + self.VM.InternalRegister[33] = nil --PF + self.VM.InternalRegister[34] = nil --EF + self.VM.InternalRegister[45] = nil --BusLock + self.VM.InternalRegister[46] = nil --IDLE + self.VM.InternalRegister[47] = nil --INTR + self.VM.InternalRegister[52] = nil --NIDT + + -- Remove some instructions + self.VM.OperandCount[16] = nil --RD + self.VM.OperandCount[17] = nil --WD + self.VM.OperandCount[28] = nil --SPG + self.VM.OperandCount[29] = nil --CPG + self.VM.OperandCount[37] = nil --HALT + self.VM.OperandCount[41] = nil --IRET + self.VM.OperandCount[42] = nil --STI + self.VM.OperandCount[43] = nil --CLI + self.VM.OperandCount[44] = nil --STP + self.VM.OperandCount[45] = nil --CLP + self.VM.OperandCount[46] = nil --STD + self.VM.OperandCount[48] = nil --STEF + self.VM.OperandCount[49] = nil --CLEF + self.VM.OperandCount[70] = nil --EXTINT + self.VM.OperandCount[95] = nil --ERPG + self.VM.OperandCount[96] = nil --WRPG + self.VM.OperandCount[97] = nil --RDPG + self.VM.OperandCount[99] = nil --LIDTR + self.VM.OperandCount[100] = nil --STATESTORE + self.VM.OperandCount[109] = nil --STATERESTORE + self.VM.OperandCount[110] = nil --EXTRET + self.VM.OperandCount[113] = nil --RLADD + self.VM.OperandCount[116] = nil --STD2 + self.VM.OperandCount[118] = nil --STM + self.VM.OperandCount[119] = nil --CLM + self.VM.OperandCount[122] = nil --SPP + self.VM.OperandCount[123] = nil --CPP + self.VM.OperandCount[124] = nil --SRL + self.VM.OperandCount[125] = nil --GRL + self.VM.OperandCount[131] = nil --SMAP + self.VM.OperandCount[132] = nil --GMAP + + -- Add some extra lookups + self.VM.FontName = {} + self.VM.FontName[0] = "Lucida Console" + self.VM.FontName[1] = "Courier New" + self.VM.FontName[2] = "Trebuchet" + self.VM.FontName[3] = "Arial" + self.VM.FontName[4] = "Times New Roman" + self.VM.FontName[5] = "Coolvetica" + self.VM.FontName[6] = "Akbar" + self.VM.FontName[7] = "csd" + + -- Add text layouter + self.VM.Layouter = MakeTextScreenLayouter() + self.VM.Entity = self +end + + + +-------------------------------------------------------------------------------- +-- Switches to a font, creating it if it does not exist +-------------------------------------------------------------------------------- +local fontcache = {} +function VM:SetFont() + local name, size = self.FontName[self.Font], self.FontSize + if not fontcache[name] or not fontcache[name][size] then + if not fontcache[name] then fontcache[name] = {} end + + surface.CreateFont("WireGPU_"..name..size, { + font = name, + size = size, + weight = 800, + antialias = true, + additive = false, + }) + fontcache[name][size] = true + end + + surface.SetFont("WireGPU_"..name..size) +end + + + + +-------------------------------------------------------------------------------- +-- Reset state each GPU frame +-------------------------------------------------------------------------------- +function VM:Reset() + -- Reset VM + self.IP = 0 -- Instruction pointer + + self.EAX = 0 -- General purpose registers + self.EBX = 0 + self.ECX = 0 + self.EDX = 0 + self.ESI = 0 + self.EDI = 0 + self.ESP = 32767 + self.EBP = 0 + + self.CS = 0 -- Segment pointer registers + self.SS = 0 + self.DS = 0 + self.ES = 0 + self.GS = 0 + self.FS = 0 + self.KS = 0 + self.LS = 0 + + -- Extended registers + for reg=0,31 do self["R"..reg] = 0 end + + self.ESZ = 32768 -- Stack size register + self.CMPR = 0 -- Compare register + self.XEIP = 0 -- Current instruction address register + self.LADD = 0 -- Last interrupt parameter + self.LINT = 0 -- Last interrupt number + self.BPREC = 48 -- Binary precision for integer emulation mode (default: 48) + self.IPREC = 48 -- Integer precision (48 - floating point mode, 8, 16, 32, 64 - integer mode) + self.VMODE = 2 -- Vector mode (2D, 3D) + self.INTR = 0 -- Handling an interrupt + self.BlockStart = 0 -- Start of the block + self.BlockSize = 0 -- Size of the block + + -- Reset internal GPU registers + -- [131072]..[2097151] - Extended GPU memory (2MB GPU) + -- [131072]..[1048576] - Extended GPU memory (1MB GPU) + -- [131072]..[524287] - Extended GPU memory (512K GPU) + -- [131072]..[262143] - Extended GPU memory (256K GPU) + -- No extended memory (128K GPU) + -- [65536]..[131071] - MemBus mapped memory (read/write) + -- No extra memory beyond 65536 (64K GPU) + -- + -- Hardware control registers: + -- [65535] - CLK + -- [65534] - RESET + -- [65533] - HARDWARE CLEAR + -- [65532] - Vertex mode (render vertex instead of RT) + -- [65531] - HALT + -- [65530] - RAM_RESET + -- [65529] - Async thread reset + -- [65528] - Async thread clk + -- [65527] - Async thread frequency + -- [65526] - Player index (0 to 31) + -- + -- Image control: + -- [65525] - Horizontal image scale + -- [65524] - Vertical image scale + -- [65523] - Hardware scale + -- [65522] - Rotation (0 - 0*, 1 - 90*, 2 - 180*, 3 - 270*) + -- [65521] - Sprite/texture size + -- [65520] - Pointer to texture data + -- [65519] - Size of texture data + -- [65518] - Raster quality + -- [65517] - Texture buffer (1: sprite buffer, 0: front buffer) + -- + -- Vertex pipe controls: + -- [65515] - Image width (800) + -- [65514] - Image height (600) + -- [65513] - Real screen ratio + -- [65512] - Parameter list address (for dwritefmt) + -- + -- Cursor control: + -- [65505] - Cursor X (0..1) + -- [65504] - Cursor Y (0..1) + -- [65503] - Cursor visible + -- [65502] - Cursor buttons (bits) + -- + -- Brightness control: + -- [65495] - Brightness W + -- [65494] - Brightness R + -- [65493] - Brightness G + -- [65492] - Brightness B + -- [65491] - Contrast W + -- [65490] - Contrast R + -- [65489] - Contrast G + -- [65488] - Contrast B + -- + -- Rendering settings + -- [65485] - Circle quality (3..128) + -- [65484] - Offset Point X + -- [65483] - Offset Point Y + -- [65482] - Rotation (rad) + -- [65481] - Scale + -- [65480] - Center point X + -- [65479] - Center point Y + -- [65478] - Circle start (rad) + -- [65477] - Circle end (rad) + -- [65476] - Line width (1) + -- [65475] - Scale X + -- [65474] - Scale Y + -- [65473] - Font horizontal align + -- [65472] - ZOffset + -- [65471] - Font vertical align + -- [65470] - Culling distance + -- [65469] - Culling mode (0: front, 1: back) + -- [65468] - Single-side lighting (1: front, -1: back) + -- [65467] - Memory offset of vertex data (non-zero means poly ops take indexes into this array) + -- [65466] - Texture rotation (rad) + -- [65465] - Texture scale + -- [65464] - Texture center point U + -- [65463] - Texture center point V + -- [65462] - Texture offset U + -- [65461] - Texture offset V + -- + -- Misc: + -- [64512] - Last register + -- [63488]..[64511] - External ports + + self.Memory[65535] = 1 + self.Memory[65534] = 0 +--self.Memory[65533] = 1 (value persists over reset) +--self.Memory[65532] = 0 +--self.Memory[65531] = 0 (value persists over reset) +--self.Memory[65530] = 0 +--self.Memory[65529] = 0 +--self.Memory[65528] = 0 +--self.Memory[65527] = 0 + self.Memory[65526] = (LocalPlayer():UserID() % 32) + ---------------------- + self.Memory[65525] = 1 + self.Memory[65524] = 1 + self.Memory[65523] = 0 + self.Memory[65522] = 0 + self.Memory[65521] = 512 + self.Memory[65520] = 0 + self.Memory[65519] = 0 + self.Memory[65518] = 0 + self.Memory[65517] = 1 + ---------------------- + self.Memory[65515] = 800 + self.Memory[65514] = 600 +--self.Memory[65513] = 0 (set elsewhere) + self.Memory[65512] = 0 + ---------------------- +--self.Memory[65505] = 0 (set elsewhere) +--self.Memory[65504] = 0 (set elsewhere) + self.Memory[65503] = 0 +--self.Memory[65502] = 0 + ---------------------- + self.Memory[65495] = 1 + self.Memory[65494] = 1 + self.Memory[65493] = 1 + self.Memory[65492] = 1 + self.Memory[65491] = 0 + self.Memory[65490] = 0 + self.Memory[65489] = 0 + self.Memory[65488] = 0 + ---------------------- + self.Memory[65485] = 32 + self.Memory[65484] = 0 + self.Memory[65483] = 0 + self.Memory[65482] = 0 + self.Memory[65481] = 1 + self.Memory[65480] = 0 + self.Memory[65479] = 0 + self.Memory[65478] = 0 + self.Memory[65477] = 6.28318530717 + self.Memory[65476] = 1 + self.Memory[65475] = 1 + self.Memory[65474] = 1 + self.Memory[65473] = 0 + self.Memory[65472] = 0 + self.Memory[65471] = 0 + self.Memory[65470] = 0 + self.Memory[65469] = 0 + self.Memory[65468] = 0 + self.Memory[65467] = 0 + self.Memory[65466] = 0 + self.Memory[65465] = 1 + self.Memory[65464] = 0.5 + self.Memory[65463] = 0.5 + self.Memory[65462] = 0 + self.Memory[65461] = 0 + + -- Coordinate pipe + -- 0 - direct (0..512 or 0..1024 range) + -- 1 - mapped to screen + -- 2 - mapped to 0..1 range + -- 3 - mapped to -1..1 range + self.CoordinatePipe = 0 + + -- Vertex pipes: + -- 0 - XY mapping + -- 1 - YZ mapping + -- 2 - XZ mapping + -- 3 - XYZ projective mapping + -- 4 - XY mapping + matrix + -- 5 - XYZ projective mapping + matrix + self.VertexPipe = 0 + + -- Flags that can be ddisable/ddenable-d + -- 0 VERTEX_ZSORT Enable or disable ZSorting in vertex buffer (sorted on flush) + self.VertexBufZSort = 0 + -- 1 VERTEX_LIGHTING Enable or disable vertex lighting + self.VertexLighting = 0 + -- 2 VERTEX_BUFFER Enable or disable vertex buffer + self.VertexBufEnabled = 0 + -- 3 VERTEX_CULLING Enable or disable culling on faces + self.VertexCulling = 0 + -- 4 VERTEX_DCULLING Enable or disable distance culling + self.DistanceCulling = 0 + -- 5 VERTEX_TEXTURING Enable texturing from sprite buffer + self.VertexTexturing = 0 + + -- Font layouter related + self.Font = 0 + self.FontSize = 12 + self.TextBox = { x = 512, y = 512, z = 0, w = 0 } + self.WordWrapMode = 0 + + -- Current color + self.Color = {x = 0, y = 0, z = 0, w = 255} + self.Material = nil + self.Texture = 0 + + -- Model transform matrix + self.ModelMatrix = self.ModelMatrix or {} + self.ModelMatrix[0] = 1 self.ModelMatrix[1] = 0 self.ModelMatrix[2] = 0 self.ModelMatrix[3] = 0 + self.ModelMatrix[4] = 0 self.ModelMatrix[5] = 1 self.ModelMatrix[6] = 0 self.ModelMatrix[7] = 0 + self.ModelMatrix[8] = 0 self.ModelMatrix[9] = 0 self.ModelMatrix[10] = 1 self.ModelMatrix[11] = 0 + self.ModelMatrix[12] = 0 self.ModelMatrix[13] = 0 self.ModelMatrix[14] = 0 self.ModelMatrix[15] = 1 + + --View transform matrix + self.ProjectionMatrix = self.ProjectionMatrix or {} + self.ProjectionMatrix[0] = 1 self.ProjectionMatrix[1] = 0 self.ProjectionMatrix[2] = 0 self.ProjectionMatrix[3] = 0 + self.ProjectionMatrix[4] = 0 self.ProjectionMatrix[5] = 1 self.ProjectionMatrix[6] = 0 self.ProjectionMatrix[7] = 0 + self.ProjectionMatrix[8] = 0 self.ProjectionMatrix[9] = 0 self.ProjectionMatrix[10] = 1 self.ProjectionMatrix[11] = 0 + self.ProjectionMatrix[12] = 0 self.ProjectionMatrix[13] = 0 self.ProjectionMatrix[14] = 0 self.ProjectionMatrix[15] = 1 + + -- Reset buffers: + self.StringCache = {} + self.VertexBuffer = {} + self.Lights = {} +end + + + +-------------------------------------------------------------------------------- +-- Save asynchronous thread state +-------------------------------------------------------------------------------- +local asyncPreservedVariables = { + "IP","EAX","EBX","ECX","EDX","ESI","EDI","ESP","EBP","CS","SS","DS","ES","GS", + "FS","KS","LS","ESZ","CMPR","XEIP","LADD","LINT","BPREC","IPREC","VMODE","INTR", + "BlockStart","BlockSize","CoordinatePipe","VertexPipe","VertexBufZSort","VertexLighting", + "VertexBufEnabled","VertexCulling","DistanceCulling","VertexTexturing","Font", + "FontSize","WordWrapMode","Material","Texture","VertexBuffer","Lights", +} +for reg=0,31 do table.insert(asyncPreservedVariables,"R"..reg) end + +local asyncPreservedMemory = { + 65525,65524,65523,65522,65521,65520,65519,65518,65517, + 65515,65514,65512,65503,65495,65494,65493,65492,65491, + 65490,65489,65488,65485,65484,65483,65482,65481,65480, + 65479,65478,65477,65476,65475,65474,65473,65472,65471, + 65470,65469,65468,65467,65466,65465,65464,65463,65462, + 65461 +} + +function VM:SaveAsyncThread_Util() + for _,var in pairs(asyncPreservedVariables) do self.AsyncState[var] = self[var] end + for _,mem in pairs(asyncPreservedMemory) do self.AsyncState[mem] = self.Memory[mem] end + + self.AsyncState.TextBox = { x = self.TextBox.x, y = self.TextBox.y, z = self.TextBox.z, w = self.TextBox.w } + self.AsyncState.Color = { x = self.Color.x, y = self.Color.y, z = self.Color.z, w = self.Color.w } + self.AsyncState.ModelMatrix = {} + self.AsyncState.ProjectionMatrix = {} + for k,v in pairs(self.ModelMatrix) do self.AsyncState.ModelMatrix[k] = v end + for k,v in pairs(self.ProjectionMatrix) do self.AsyncState.ProjectionMatrix[k] = v end +end + +function VM:SaveAsyncThread() + if not self.AsyncState then + self.AsyncState = {} + self:Reset() + + self:SaveAsyncThread_Util() + self.AsyncState.IP = self.EntryPoint4 + return + end + + self:SaveAsyncThread_Util() +end + + + +-------------------------------------------------------------------------------- +-- Restore asynchronous thread state +-------------------------------------------------------------------------------- +function VM:RestoreAsyncThread_Util() + for _,var in pairs(asyncPreservedVariables) do self[var] = self.AsyncState[var] end + for _,mem in pairs(asyncPreservedMemory) do self.Memory[mem] = self.AsyncState[mem] end + + self.TextBox = { x = self.AsyncState.TextBox.x, y = self.AsyncState.TextBox.y, z = self.AsyncState.TextBox.z, w = self.AsyncState.TextBox.w } + self.Color = { x = self.AsyncState.Color.x, y = self.AsyncState.Color.y, z = self.AsyncState.Color.z, w = self.AsyncState.Color.w } + self.ModelMatrix = {} + self.ProjectionMatrix = {} + for k,v in pairs(self.AsyncState.ModelMatrix) do self.ModelMatrix[k] = v end + for k,v in pairs(self.AsyncState.ProjectionMatrix) do self.ProjectionMatrix[k] = v end +end + +function VM:RestoreAsyncThread() + if not self.AsyncState then + self.AsyncState = {} + self:Reset() + + self:SaveAsyncThread_Util() + self.AsyncState.IP = self.EntryPoint4 + end + + self:RestoreAsyncThread_Util() +end + + + +-------------------------------------------------------------------------------- +-- Reset GPU state and clear all persisting registers +-------------------------------------------------------------------------------- +function VM:HardReset() + self:Reset() + + -- Reset registers that usually persist over normal reset + self.Memory[65533] = 1 + self.Memory[65532] = 0 + self.Memory[65531] = 0 + self.Memory[65535] = 1 + + self.Memory[65529] = 0 + self.Memory[65528] = 0 + self.Memory[65527] = 60000 + + self.Memory[65502] = 0 + + -- Entrypoints to special calls + -- 0 DRAW Called when screen is being drawn + -- 1 INIT Called when screen is hard reset + -- 2 USE Called when screen is used + -- 3 ERROR Called when GPU error has occured + -- 4 ASYNC Asynchronous thread entrypoint + self.EntryPoint0 = 0 + self.EntryPoint1 = self.EntryPoint1 or 0 + self.EntryPoint2 = 0 + self.EntryPoint3 = 0 + self.EntryPoint4 = 0 + self.EntryPoint5 = 0 + self.EntryPoint6 = 0 + self.EntryPoint7 = 0 + + -- Is running asynchronous thread + self.ASYNC = 0 + + -- Has initialized already + self.INIT = 0 + + -- Reset async thread + self.AsyncState = nil +end + + + +-------------------------------------------------------------------------------- +-- Compute UV +-------------------------------------------------------------------------------- +function VM:ComputeTextureUV(vertex,u,v) + local texturesOnSide = math.floor(512/self.Memory[65521]) + local textureX = (1/texturesOnSide) * (self.Texture % texturesOnSide) + local textureY = (1/texturesOnSide) * math.floor(self.Texture / texturesOnSide) + + local uvStep = (1/512) + local du,dv = u,v + + if (self.Memory[65466] ~= 0) or (self.Memory[65465] ~= 1) then + local cu,cv = self.Memory[65464],self.Memory[65463] + local tu,tv = u-cu,v-cv + local angle,scale = self.Memory[65466],self.Memory[65465] + du = scale*(tu*math.cos(angle) - tv*math.sin(angle)) + cu + self.Memory[65462] + dv = scale*(tv*math.cos(angle) + tu*math.sin(angle)) + cv + self.Memory[65461] + end + + vertex.u = textureX+(1/texturesOnSide)*du*(1-2*uvStep)+uvStep + vertex.v = textureY+(1/texturesOnSide)*dv*(1-2*uvStep)+uvStep +end + + + +-------------------------------------------------------------------------------- +-- Transform coordinates through coordinate pipe +-------------------------------------------------------------------------------- +function VM:CoordinateTransform(x,y) + -- Transformed coordinates + local tX = x + local tY = y + + -- Is rotation/scale register set + if (self.Memory[65482] ~= 0) or (self.Memory[65481] ~= 1) then + -- Centerpoint of rotation + local cX = self.Memory[65480] + local cY = self.Memory[65479] + + -- Calculate normalized direction to rotated point + local vD = math.sqrt((x-cX)^2+(y-cY)^2) + 1e-7 + local vX = x / vD + local vY = y / vD + + -- Calculate angle of rotation for the point + local A + if self.RAMSize == 65536 then A = math.atan2(vX,vY) -- Old GPU + else A = math.atan2(vY,vX) + end + + -- Rotate point by a certain angle + A = A + self.Memory[65482] + + -- Generate new coordinates + tX = cX + math.cos(A) * vD * self.Memory[65481] * self.Memory[65475] + tY = cY + math.sin(A) * vD * self.Memory[65481] * self.Memory[65474] + end + + -- Apply DMOVE offset + tX = tX + self.Memory[65484] + tY = tY + self.Memory[65483] + + if self.CoordinatePipe == 0 then + tX = self.ScreenWidth*(tX/512) + tY = self.ScreenHeight*(tY/512) + elseif self.CoordinatePipe == 1 then + tX = self.ScreenWidth*tX/self.Memory[65515] + tY = self.ScreenHeight*tY/self.Memory[65514] + elseif self.CoordinatePipe == 2 then + tX = tX*self.ScreenWidth + tY = tY*self.ScreenHeight + elseif self.CoordinatePipe == 3 then + tX = 0.5*self.ScreenWidth*(1+tX) + tY = 0.5*self.ScreenHeight*(1+tY) + elseif self.CoordinatePipe == 4 then + tX = 0.5*self.ScreenWidth+tX + tY = 0.5*self.ScreenHeight+tY + end + + -- Apply raster quality transform + local transformedCoordinate = { x = tX, y = tY} + local rasterQuality = self.Memory[65518] + + if rasterQuality > 0 then + local W,H = self.ScreenWidth/2,self.ScreenHeight/2 + transformedCoordinate.x = (tX-W)*(1-(rasterQuality/W))+W + transformedCoordinate.y = (tY-H)*(1-(rasterQuality/H))+H + end + + return transformedCoordinate +end + + + + +-------------------------------------------------------------------------------- +-- Transform coordinate via vertex pipe +-------------------------------------------------------------------------------- +function VM:VertexTransform(inVertex,toScreen) + -- Make sure the coordinate is complete + local vertex = inVertex or {} + vertex.x = vertex.x or 0 + vertex.y = vertex.y or 0 + vertex.z = vertex.z or 0 + vertex.w = vertex.w or 1 + vertex.u = vertex.u or 0 + vertex.v = vertex.v or 0 + + -- Create the resulting coordinate + local resultVertex = { + x = vertex.x, + y = vertex.y, + z = vertex.z, + w = vertex.w, + u = vertex.u, + v = vertex.v } + + -- Transformed world coordinates + local worldVertex = { + x = 0, + y = 0, + z = 0, + w = 1, + u = vertex.u, + v = vertex.v } + + -- Add Z offset to input coordinate + vertex.z = vertex.z + self.Memory[65472] + + -- Do the transformation + if self.VertexPipe == 0 then -- XY plane + local resultCoordinate = self:CoordinateTransform(vertex.x,vertex.y) + resultVertex.x = resultCoordinate.x + resultVertex.y = resultCoordinate.y + elseif self.VertexPipe == 1 then -- YZ plane + local resultCoordinate = self:CoordinateTransform(vertex.y,vertex.z) + resultVertex.x = resultCoordinate.x + resultVertex.y = resultCoordinate.y + elseif self.VertexPipe == 2 then -- XZ plane + local resultCoordinate = self:CoordinateTransform(vertex.x,vertex.z) + resultVertex.x = resultCoordinate.x + resultVertex.y = resultCoordinate.y + elseif self.VertexPipe == 3 then -- Perspective transform + local tX = (vertex.x + self.Memory[65512])/(vertex.z + self.Memory[65512]) + local tY = (vertex.y + self.Memory[65512])/(vertex.z + self.Memory[65512]) + + local resultCoordinate = self:CoordinateTransform(tX,tY) + resultVertex.x = resultCoordinate.x + resultVertex.y = resultCoordinate.y + elseif self.VertexPipe == 4 then -- 2D matrix + local tX = self.ModelMatrix[0*4+0] * vertex.x + + self.ModelMatrix[0*4+1] * vertex.y + + self.ModelMatrix[0*4+2] * 0 + + self.ModelMatrix[0*4+3] * 1 + + local tY = self.ModelMatrix[1*4+0] * vertex.x + + self.ModelMatrix[1*4+1] * vertex.y + + self.ModelMatrix[1*4+2] * 0 + + self.ModelMatrix[1*4+3] * 1 + + local resultCoordinate = self:CoordinateTransform(tX,tY) + resultVertex.x = resultCoordinate.x + resultVertex.y = resultCoordinate.y + elseif self.VertexPipe == 5 then -- 3D matrix + local world + if not toScreen then + -- Transform into world coordinates + world = {} + + for i=0,3 do + world[i] = self.ModelMatrix[i*4+0] * vertex.x + + self.ModelMatrix[i*4+1] * vertex.y + + self.ModelMatrix[i*4+2] * vertex.z + + self.ModelMatrix[i*4+3] * vertex.w + end + + worldVertex.x = world[0] + worldVertex.y = world[1] + worldVertex.z = world[2] + worldVertex.w = world[3] + else + worldVertex = vertex + world = {} + world[0] = vertex.x + world[1] = vertex.y + world[2] = vertex.z + world[3] = vertex.w + end + + -- Transform into screen coordinates + local screen = {} + + for i=0,3 do + screen[i] = self.ProjectionMatrix[i*4+0] * world[0] + + self.ProjectionMatrix[i*4+1] * world[1] + + self.ProjectionMatrix[i*4+2] * world[2] + + self.ProjectionMatrix[i*4+3] * world[3] + end + + -- Project to screen + if screen[3] == 0 then screen[3] = 1 end + for i=0,3 do screen[i] = screen[i] / screen[3] end + + -- Transform coordinates + local resultCoordinate = self:CoordinateTransform(screen[0],screen[1]) + resultVertex.x = resultCoordinate.x + resultVertex.y = resultCoordinate.y + resultVertex.z = screen[2] + resultVertex.w = screen[3] + end + + return resultVertex,worldVertex +end + + + + +-------------------------------------------------------------------------------- +-- Transform color +-------------------------------------------------------------------------------- +function VM:ColorTransform(color) + color.x = color.x * self.Memory[65495] * self.Memory[65494] + color.y = color.y * self.Memory[65495] * self.Memory[65493] + color.z = color.z * self.Memory[65495] * self.Memory[65492] + return color +end + + + + +-------------------------------------------------------------------------------- +-- Read a string by offset +-------------------------------------------------------------------------------- +function VM:ReadString(address) + local charString = "" + local charCount = 0 + local currentChar = 255 + + while currentChar ~= 0 do + currentChar = self:ReadCell(address + charCount) + -- Reading failed + if not currentChar then + return + elseif currentChar > 0 and currentChar < 255 then + charString = charString .. string.char(currentChar) + elseif currentChar ~= 0 then + self:Interrupt(23,currentChar) + return "" + end + + charCount = charCount + 1 + if charCount > 8192 then + self:Interrupt(23,0) + return "" + end + end + return charString +end + + + + +-------------------------------------------------------------------------------- +-- Get text size (by sk89q) +-------------------------------------------------------------------------------- +function VM:TextSize(text) + self:SetFont() + + if self.WordWrapMode == 1 then + return self.Layouter:GetTextSize(text, self.TextBox.x, self.TextBox.y) + else + return surface.GetTextSize(text) + end +end + + + + +-------------------------------------------------------------------------------- +-- Output font to screen (word wrap update by sk89q) +-------------------------------------------------------------------------------- +function VM:FontWrite(posaddr,text) + -- Read position + local vertex = {} + vertex.x = self:ReadCell(posaddr+0) + vertex.y = self:ReadCell(posaddr+1) + vertex = self:VertexTransform(vertex) + + self:SetFont() + + -- Draw text + if self.RenderEnable == 1 then + if self.WordWrapMode == 1 then + surface.SetTextColor(self.Color.x,self.Color.y,self.Color.z,self.Color.w) + self.Layouter:DrawText(tostring(text), vertex.x, vertex.y, self.TextBox.x, + self.TextBox.y, self.Memory[65473], self.Memory[65471]) + else + draw.DrawText(text,"WireGPU_"..self.FontName[self.Font]..self.FontSize, + vertex.x,vertex.y,Color(self.Color.x,self.Color.y,self.Color.z,self.Color.w), + self.Memory[65473]) + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Draw line between two points +-------------------------------------------------------------------------------- +function VM:DrawLine(point1,point2,drawNow) + -- Line centerpoint + local cX = (point1.x + point2.x) / 2 + local cY = (point1.y + point2.y) / 2 + + -- Line width + local W = self.Memory[65476] + + -- Line length and angle + local L = math.sqrt((point1.x-point2.x)^2+(point1.y-point2.y)^2) + 1e-7 + local dX = (point2.x-point1.x) / L + local dY = (point2.y-point1.y) / L + local A = math.atan2(dY,dX) + local dA = math.atan2(W,L/2) + + -- Generate vertexes + local vertexBuffer = { {}, {}, {}, {} } + + vertexBuffer[1].x = cX - 0.5 * L * math.cos(A-dA) + vertexBuffer[1].y = cY - 0.5 * L * math.sin(A-dA) + vertexBuffer[1].u = 0 + vertexBuffer[1].v = 0 + + vertexBuffer[2].x = cX + 0.5 * L * math.cos(A+dA) + vertexBuffer[2].y = cY + 0.5 * L * math.sin(A+dA) + vertexBuffer[2].u = 1 + vertexBuffer[2].v = 1 + + vertexBuffer[3].x = cX + 0.5 * L * math.cos(A-dA) + vertexBuffer[3].y = cY + 0.5 * L * math.sin(A-dA) + vertexBuffer[3].u = 0 + vertexBuffer[3].v = 1 + + vertexBuffer[4].x = cX - 0.5 * L * math.cos(A+dA) + vertexBuffer[4].y = cY - 0.5 * L * math.sin(A+dA) + vertexBuffer[4].u = 1 + vertexBuffer[4].v = 0 + + -- Draw vertexes + if drawNow then + surface.DrawPoly(vertexBuffer) + else + self:DrawToBuffer(vertexBuffer) + end +end + + + + +-------------------------------------------------------------------------------- +-- Flush vertex buffer. Based on code by Nick +-------------------------------------------------------------------------------- +local function triangleSortFunction(triA,triB) + local z1 = (triA.vertex[1].z + triA.vertex[2].z + triA.vertex[3].z) / 3 + local z2 = (triB.vertex[1].z + triB.vertex[2].z + triB.vertex[3].z) / 3 + + return z1 < z2 +end + +function VM:FlushBuffer() + -- Projected vertex data: + -- vertexData.transformedVertex [SCREEN SPACE] + -- Vertex data in world space: + -- vertexData.vertex [WORLD SPACE] + -- Triangle color: + -- vertexData.color + -- + -- Light positions: [WORLD SPACE] + + if self.VertexBufEnabled == 1 then + -- Do not flush color-only buffer + if (#self.VertexBuffer == 1) and (not self.VertexBuffer[1].vertex) then + self.VertexBuffer = {} + return + end + + -- Sort triangles by distance + if self.VertexBufZSort == 1 then + table.sort(self.VertexBuffer,triangleSortFunction) + end + + -- Render each triangle + for vertexID,vertexData in ipairs(self.VertexBuffer) do + -- Should this polygon be culled + local cullVertex = false + + -- Generate output + local resultTriangle + local resultTriangle2 + local resultColor = { + x = vertexData.color.x, + y = vertexData.color.y, + z = vertexData.color.z, + w = vertexData.color.w, + } + local resultMaterial = vertexData.material + if vertexData.rt then + WireGPU_matBuffer:SetTexture("$basetexture", vertexData.rt) + resultMaterial = WireGPU_matBuffer + end + + + if vertexData.vertex then + resultTriangle = {} + + resultTriangle[1] = {} + resultTriangle[1].x = vertexData.transformedVertex[1].x + resultTriangle[1].y = vertexData.transformedVertex[1].y + resultTriangle[1].u = vertexData.transformedVertex[1].u + resultTriangle[1].v = vertexData.transformedVertex[1].v + + resultTriangle[2] = {} + resultTriangle[2].x = vertexData.transformedVertex[2].x + resultTriangle[2].y = vertexData.transformedVertex[2].y + resultTriangle[2].u = vertexData.transformedVertex[2].u + resultTriangle[2].v = vertexData.transformedVertex[2].v + + resultTriangle[3] = {} + resultTriangle[3].x = vertexData.transformedVertex[3].x + resultTriangle[3].y = vertexData.transformedVertex[3].y + resultTriangle[3].u = vertexData.transformedVertex[3].u + resultTriangle[3].v = vertexData.transformedVertex[3].v + + -- Additional processing + if (self.VertexCulling == 1) or (self.VertexLighting == 1) then + -- Get vertices (world space) + local v1 = vertexData.vertex[1] + local v2 = vertexData.vertex[2] + local v3 = vertexData.vertex[3] + + -- Compute barycenter (world space) + local vpos = { + x = (v1.x+v2.x) * 1/3, + y = (v1.y+v2.y) * 1/3, + z = (v1.z+v2.z) * 1/3 + } + + -- Compute normal (world space) + local x1 = v2.x - v1.x + local y1 = v2.y - v1.y + local z1 = v2.z - v1.z + + local x2 = v3.x - v1.x + local y2 = v3.y - v1.y + local z2 = v3.z - v1.z + + local normal = { + x = y1*z2-y2*z1, + y = z1*x2-z2*x1, + z = x1*y2-x2*y1 + } + + -- Normalize it + local d = (normal.x^2 + normal.y^2 + normal.z^2)^(1/2)+1e-7 + normal.x = normal.x / d + normal.y = normal.y / d + normal.z = normal.z / d + + -- Perform culling + if self.VertexCulling == 1 then + if self.Memory[65469] == 0 then + cullVertex = (normal.x*v1.x + normal.y*v1.y + normal.z*v1.z) <= 0 + else + cullVertex = (normal.x*v1.x + normal.y*v1.y + normal.z*v1.z) >= 0 + end + end + + -- Perform vertex lighting + if (self.VertexLighting == 1) and (not cullVertex) then + -- Extra color generated by lights + local lightColor = { x = 0, y = 0, z = 0, w = 255} + + -- Apply all lights (world space calculations) + for i=0,7 do + if self.Lights[i] then + local lightPosition = { + x = self.Lights[i].Position.x, + y = self.Lights[i].Position.y, + z = self.Lights[i].Position.z + } + local lightLength = (lightPosition.x^2+lightPosition.y^2+lightPosition.z^2)^(1/2)+1e-7 + lightPosition.x = lightPosition.x / lightLength + lightPosition.y = lightPosition.y / lightLength + lightPosition.z = lightPosition.z / lightLength + + local lightDot + if self.Memory[65468] == 0 then + lightDot = math.abs(lightPosition.x*normal.x + + lightPosition.y*normal.y + + lightPosition.z*normal.z)*self.Lights[i].Color.w + else + lightDot = math.max(0,self.Memory[65468]*(lightPosition.x*normal.x + + lightPosition.y*normal.y + + lightPosition.z*normal.z))*self.Lights[i].Color.w + + end + + lightColor.x = math.min(lightColor.x + self.Lights[i].Color.x * lightDot,255) + lightColor.y = math.min(lightColor.y + self.Lights[i].Color.y * lightDot,255) + lightColor.z = math.min(lightColor.z + self.Lights[i].Color.z * lightDot,255) + end + end + + -- Modulate object color with light color + resultColor.x = (1/255) * resultColor.x * lightColor.x + resultColor.y = (1/255) * resultColor.y * lightColor.y + resultColor.z = (1/255) * resultColor.z * lightColor.z + end + + -- Perform distance culling + if (self.DistanceCulling == 1) and (not cullVertex) then + local Infront = {} + local Behind = {} + + local frontCullDistance = self.Memory[65470] + local K = -frontCullDistance + + -- Generate list of vertices which go behind the camera + if v1.z - K >= 0 + then Behind [#Behind + 1] = v1 + else Infront[#Infront + 1] = v1 + end + + if v2.z - K >= 0 + then Behind [#Behind + 1] = v2 + else Infront[#Infront + 1] = v2 + end + + if v3.z - K >= 0 + then Behind [#Behind + 1] = v3 + else Infront[#Infront + 1] = v3 + end + + if #Behind == 1 then + local Point1 = Infront[1] + local Point2 = Infront[2] + local Point3 = Behind[1] + local Point4 = {} + + local D1 = { + x = Point3.x - Point1.x, + y = Point3.y - Point1.y, + z = Point3.z - Point1.z, + } + local D2 = { + x = Point3.x - Point2.x, + y = Point3.y - Point2.y, + z = Point3.z - Point2.z, + } + + local T1 = D1.z + local T2 = D2.z + + if (T1 ~= 0) and (T2 ~= 0) then + local S1 = (K - Point1.z)/T1 + local S2 = (K - Point2.z)/T2 + + -- Calculate the new UV values + Point4.u = Point2.u + S2 * (Point3.u - Point2.u) + Point4.v = Point2.v + S2 * (Point3.v - Point2.v) + + Point3.u = Point1.u + S1 * (Point3.u - Point1.u) + Point3.v = Point1.v + S1 * (Point3.v - Point1.v) + + -- Calculate new coordinates + Point3.x = Point1.x + S1 * D1.x + Point3.y = Point1.y + S1 * D1.y + Point3.z = Point1.z + S1 * D1.z + + Point4.x = Point2.x + S2 * D2.x + Point4.y = Point2.y + S2 * D2.y + Point4.z = Point2.z + S2 * D2.z + + -- Transform the points (from world space to screen space) + local P1t = self:VertexTransform(Point1,true) + local P2t = self:VertexTransform(Point2,true) + local P3t = self:VertexTransform(Point3,true) + local P4t = self:VertexTransform(Point4,true) + + resultTriangle[1] = P1t + resultTriangle[2] = P2t + resultTriangle[3] = P3t + + resultTriangle2 = {} + resultTriangle2[1] = P2t + resultTriangle2[2] = P3t + resultTriangle2[3] = P4t + end + elseif #Behind == 2 then + local Point1 = Infront[1] + local Point2 = Behind[1] + local Point3 = Behind[2] + + local D1 = { + x = Point2.x - Point1.x, + y = Point2.y - Point1.y, + z = Point2.z - Point1.z, + } + local D2 = { + x = Point3.x - Point1.x, + y = Point3.y - Point1.y, + z = Point3.z - Point1.z, + } + + local T1 = D1.z + local T2 = D2.z + + if (T1 ~= 0) and (T2 ~= 0) then + local S1 = (K - Point1.z)/T1 + local S2 = (K - Point1.z)/T2 + + --Calculate the new UV values + Point2.u = Point1.u + S1 * (Point2.u - Point1.u) + Point2.v = Point1.v + S1 * (Point2.v - Point1.v) + + Point3.u = Point1.u + S2 * (Point3.u - Point1.u) + Point3.v = Point1.v + S2 * (Point3.v - Point1.v) + + -- Calculate new coordinates + Point2.x = Point1.x + S1 * D1.x + Point2.y = Point1.y + S1 * D1.y + Point2.z = Point1.z + S1 * D1.z + + Point3.x = Point1.x + S2 * D2.x + Point3.y = Point1.y + S2 * D2.y + Point3.z = Point1.z + S2 * D2.z + + -- Transform the points (from world space to screen space) + local P1t = self:VertexTransform(Point1,true) + local P2t = self:VertexTransform(Point2,true) + local P3t = self:VertexTransform(Point3,true) + + resultTriangle[1] = P1t + resultTriangle[2] = P2t + resultTriangle[3] = P3t + end + elseif #Behind == 3 then + cullVertex = true + end + end + end -- End additional processing + end + + + if not cullVertex then + -- self:FixDrawDirection(DrawInfo) + if self.RenderEnable == 1 then + if resultMaterial then + surface.SetMaterial(resultMaterial) + resultTriangle = { + [1] = resultTriangle[3], + [2] = resultTriangle[2], + [3] = resultTriangle[1], + } + else + surface.SetTexture(0) + end + surface.SetDrawColor(resultColor.x,resultColor.y,resultColor.z,resultColor.w) + if vertexData.wireframe then + if resultTriangle then + for i=1,#resultTriangle do + local point1 = resultTriangle[i] + local point2 = resultTriangle[i+1] + if not point2 then point2 = resultTriangle[1] end + self:DrawLine(point1,point2,true) + end + end + + if resultTriangle2 then + for i=1,#resultTriangle2 do + local point1 = resultTriangle2[i] + local point2 = resultTriangle2[i+1] + if not point2 then point2 = resultTriangle2[1] end + self:DrawLine(point1,point2,true) + end + end + else + if resultTriangle then surface.DrawPoly(resultTriangle) end + if resultTriangle2 then surface.DrawPoly(resultTriangle2) end + end + end + end + end + + self.VertexBuffer = {} + end +end + + + + +-------------------------------------------------------------------------------- +-- Set current color +-------------------------------------------------------------------------------- +function VM:SetColor(color) + if self.VertexBufEnabled == 1 then + if #self.VertexBuffer > 0 then + self.VertexBuffer[#self.VertexBuffer].color = self:ColorTransform(color) + else + self.VertexBuffer[1] = { + color = self:ColorTransform(color), + } + end + end + + self.Color = self:ColorTransform(color) +end + + + + +-------------------------------------------------------------------------------- +-- Set current material +-------------------------------------------------------------------------------- +function VM:SetMaterial(material) + if self.VertexBufEnabled == 1 then + if #self.VertexBuffer > 0 then + self.VertexBuffer[#self.VertexBuffer].material = material + else + self.VertexBuffer[1] = { + material = material, + } + end + end + + self.Material = material +end + + + +-------------------------------------------------------------------------------- +-- Bind rendering state (color, texture) +-------------------------------------------------------------------------------- +function VM:BindState() + surface.SetDrawColor(self.Color.x,self.Color.y,self.Color.z,self.Color.w) + if self.VertexTexturing == 1 then + if self.Memory[65517] == 1 then + --[@entities\gmod_wire_gpu\cl_gpuvm.lua:1276] bad argument #2 to 'SetTexture' (ITexture expected, got nil) + self.Entity:AssertSpriteBufferExists() + if self.Entity.SpriteGPU.RT then + WireGPU_matBuffer:SetTexture("$basetexture", self.Entity.SpriteGPU.RT) + end + else + if self.Entity.GPU.RT then + WireGPU_matBuffer:SetTexture("$basetexture", self.Entity.GPU.RT) + end + end + surface.SetMaterial(WireGPU_matBuffer) + else + if self.Material then + surface.SetMaterial(self.Material) + else + surface.SetTexture(0) + end + end +end + + + +-------------------------------------------------------------------------------- +-- Draw a buffer (or add it to vertex buffer) +-------------------------------------------------------------------------------- +function VM:DrawToBuffer(vertexData,isWireframe) + if self.VertexBufEnabled == 1 then + -- Add new entry + if (not self.VertexBuffer[#self.VertexBuffer]) or self.VertexBuffer[#self.VertexBuffer].vertex then + self.VertexBuffer[#self.VertexBuffer+1] = { + color = self.Color, + material = self.Material, + vertex = {}, + transformedVertex = {}, + wireframe = isWireframe, + } + else + self.VertexBuffer[#self.VertexBuffer].vertex = {} + self.VertexBuffer[#self.VertexBuffer].transformedVertex = {} + self.VertexBuffer[#self.VertexBuffer].wireframe = isWireframe + end + + -- Add RT material if required + if self.VertexTexturing == 1 then + if self.Memory[65517] == 1 then + self.Entity:AssertSpriteBufferExists() + self.VertexBuffer[#self.VertexBuffer].rt = self.Entity.SpriteGPU.RT + else + self.VertexBuffer[#self.VertexBuffer].rt = self.Entity.GPU.RT + end + end + + + -- Add all vertices + for _,vertex in ipairs(vertexData) do + local screenVertex,worldVertex = self:VertexTransform(vertex) + table.insert(self.VertexBuffer[#self.VertexBuffer].vertex,worldVertex) + table.insert(self.VertexBuffer[#self.VertexBuffer].transformedVertex,screenVertex) + end + else + local resultPoly = {} + + -- Transform vertices + for _,vertex in ipairs(vertexData) do + local screenVertex,worldVertex = self:VertexTransform(vertex) + table.insert(resultPoly,screenVertex) + end + + -- Draw + if self.RenderEnable == 1 then + self:BindState() + if isWireframe then + for i=1,#resultPoly do + local point1 = resultPoly[i] + local point2 = resultPoly[i+1] + if not point2 then point2 = resultPoly[1] end + self:DrawLine(point1,point2,true) + end + else + surface.DrawPoly(resultPoly) + end + end + end +end + + + + + + + + + +-------------------------------------------------------------------------------- +-- GPU instruction set implementation +-------------------------------------------------------------------------------- +VM.OpcodeTable = {} +VM.OpcodeTable[98] = function(self) --TIMER + self:Dyn_Emit("if VM.ASYNC == 1 then") + self:Dyn_EmitOperand(1,"(VM.TIMER+"..(self.PrecompileInstruction or 0).."*VM.TimerDT)",true) + self:Dyn_Emit("else") + self:Dyn_EmitOperand(1,"VM.TIMER",true) + self:Dyn_Emit("end") +end +VM.OpcodeTable[111] = function(self) --IDLE + self:Dyn_Emit("VM.INTR = 1") + self:Dyn_EmitBreak() + self.PrecompileBreak = true +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[200] = function(self) --DRECT_TEST + self:Dyn_Emit("if VM.RenderEnable == 1 then") + self:Dyn_Emit("$L W = VM.ScreenWidth") + self:Dyn_Emit("$L H = VM.ScreenHeight") + + self:Dyn_Emit("surface.SetTexture(0)") + self:Dyn_Emit("surface.SetDrawColor(200,200,200,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*0,0,W*0.125,H*0.80)") + self:Dyn_Emit("surface.SetDrawColor(200,200,000,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*1,0,W*0.125,H*0.80)") + self:Dyn_Emit("surface.SetDrawColor(000,200,200,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*2,0,W*0.125,H*0.80)") + self:Dyn_Emit("surface.SetDrawColor(000,200,000,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*3,0,W*0.125,H*0.80)") + self:Dyn_Emit("surface.SetDrawColor(200,000,200,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*4,0,W*0.125,H*0.80)") + self:Dyn_Emit("surface.SetDrawColor(200,000,000,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*5,0,W*0.125,H*0.80)") + self:Dyn_Emit("surface.SetDrawColor(000,000,200,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*6,0,W*0.125,H*0.80)") + + self:Dyn_Emit("for gray=0,7 do") + self:Dyn_Emit("surface.SetDrawColor(31*gray,31*gray,31*gray,255)") + self:Dyn_Emit("surface.DrawRect(W*0.125*gray,H*0.80,W*0.125,H*0.20)") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[201] = function(self) --DEXIT + self:Dyn_Emit("VM.INTR = 1") + self:Dyn_EmitBreak() + self.PrecompileBreak = true +end +VM.OpcodeTable[202] = function(self) --DCLR + self:Dyn_Emit("if VM.RenderEnable == 1 then") + self:Dyn_Emit("surface.SetTexture(0)") + self:Dyn_Emit("surface.SetDrawColor(0,0,0,255)") + self:Dyn_Emit("surface.DrawRect(0,0,VM.ScreenWidth,VM.ScreenHeight)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[203] = function(self) --DCLRTEX + self:Dyn_Emit("if VM.RenderEnable == 1 then") + self:Dyn_Emit("VM:BindState()") + self:Dyn_Emit("surface.SetDrawColor(255,255,255,255)") + self:Dyn_Emit("surface.DrawTexturedRect(0,0,VM.ScreenWidth,VM.ScreenHeight)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[204] = function(self) --DVXFLUSH + self:Dyn_Emit("VM:FlushBuffer()") +end +VM.OpcodeTable[205] = function(self) --DVXCLEAR + self:Dyn_Emit("VM.VertexBuffer = {}") +end +VM.OpcodeTable[206] = function(self) --DSETBUF_VX + self:Dyn_Emit("VM.Entity:SetRendertarget()") + self:Dyn_Emit("VM.LastBuffer = 2") +end +VM.OpcodeTable[207] = function(self) --DSETBUF_SPR + self:Dyn_Emit("VM.Entity:SetRendertarget(1)") + self:Dyn_Emit("VM.LastBuffer = 1") +end +VM.OpcodeTable[208] = function(self) --DSETBUF_FBO + self:Dyn_Emit("VM.Entity:SetRendertarget(0)") + self:Dyn_Emit("VM.LastBuffer = 0") +end +VM.OpcodeTable[209] = function(self) --DSWAP + self:Dyn_Emit("if VM.RenderEnable == 1 then") + self:Dyn_Emit("VM.Entity:AssertSpriteBufferExists()") + self:Dyn_Emit("if VM.Entity.SpriteGPU.RT and VM.Entity.GPU.RT then") + self:Dyn_Emit("render.CopyTexture(VM.Entity.SpriteGPU.RT,VM.Entity.GPU.RT)") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[210] = function(self) --DVXPIPE + self:Dyn_Emit("VM.VertexPipe = $1") +end +VM.OpcodeTable[211] = function(self) --DCVXPIPE + self:Dyn_Emit("VM.CoordinatePipe = $1") +end +VM.OpcodeTable[212] = function(self) --DENABLE + self:Dyn_Emit("$L IDX = $1") + self:Dyn_Emit("if IDX == 0 then VM.VertexBufEnabled = 1 end") + self:Dyn_Emit("if IDX == 1 then VM.VertexBufZSort = 1 end") + self:Dyn_Emit("if IDX == 2 then VM.VertexLighting = 1 end") + self:Dyn_Emit("if IDX == 3 then VM.VertexCulling = 1 end") + self:Dyn_Emit("if IDX == 4 then VM.DistanceCulling = 1 end") + self:Dyn_Emit("if IDX == 5 then VM.VertexTexturing = 1 end") +end +VM.OpcodeTable[213] = function(self) --DDISABLE + self:Dyn_Emit("$L IDX = $1") + self:Dyn_Emit("if IDX == 0 then VM.VertexBufEnabled = 0 end") + self:Dyn_Emit("if IDX == 1 then VM.VertexBufZSort = 0 end") + self:Dyn_Emit("if IDX == 2 then VM.VertexLighting = 0 end") + self:Dyn_Emit("if IDX == 3 then VM.VertexCulling = 0 end") + self:Dyn_Emit("if IDX == 4 then VM.DistanceCulling = 0 end") + self:Dyn_Emit("if IDX == 5 then VM.VertexTexturing = 0 end") +end +VM.OpcodeTable[214] = function(self) --DCLRSCR + self:Dyn_Emit("if VM.RenderEnable == 1 then") + self:Dyn_Emit("VM:SetColor(VM:ReadVector4f($1))") + self:Dyn_Emit("VM:BindState()") + self:Dyn_Emit("surface.SetTexture(0)") + self:Dyn_Emit("surface.DrawRect(0,0,VM.ScreenWidth,VM.ScreenHeight)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[215] = function(self) --DCOLOR + self:Dyn_Emit("VM:SetColor(VM:ReadVector4f($1))") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[216] = function(self) --DTEXTURE + self:Dyn_Emit("VM.Texture = $1") +end +VM.OpcodeTable[217] = function(self) --DSETFONT + self:Dyn_Emit("VM.Font = math.Clamp(math.floor($1),0,7)") + end +VM.OpcodeTable[218] = function(self) --DSETSIZE + self:Dyn_Emit("VM.FontSize = math.floor(math.max(4,math.min($1,200)))") +end +VM.OpcodeTable[219] = function(self) --DMOVE + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("if ADDR == 0 then") + self:Dyn_Emit("VM:WriteCell(65484,0)") + self:Dyn_Emit("VM:WriteCell(65483,0)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_Emit("VM:WriteCell(65484,VM:ReadCell(ADDR+0))") + self:Dyn_Emit("VM:WriteCell(65483,VM:ReadCell(ADDR+1))") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[220] = function(self) --DVXDATA_2F + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L VDATA = VM:ReadCell(65467)") + self:Dyn_Emit("for IDX=1,math.min(128,$2) do") + self:Dyn_Emit("if VDATA > 0 then") + self:Dyn_Emit("$L VIDX = VM:ReadCell(ADDR+IDX-1)") + self:Dyn_Emit("VD[IDX] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX*2+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX*2+1),") + self:Dyn_Emit("}") + self:Dyn_Emit("else") + self:Dyn_Emit("VD[IDX] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*2+1),") + self:Dyn_Emit("}") + self:Dyn_Emit("end") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[IDX],VD[IDX].x/512,VD[IDX].y/512)") + self:Dyn_Emit("end") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD)") +end +VM.OpcodeTable[221] = function(self) --DVXDATA_2F_TEX + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L VDATA = VM:ReadCell(65467)") + self:Dyn_Emit("for IDX=1,math.min(128,$2) do") + self:Dyn_Emit("if VDATA > 0 then") + self:Dyn_Emit("$L VIDX = VM:ReadCell(ADDR+IDX-1)") + self:Dyn_Emit("VD[IDX] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX*4+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX*4+1),") + self:Dyn_Emit("}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[IDX],VM:ReadCell(VDATA+VIDX*4+2),VM:ReadCell(VDATA+VIDX*4+3))") + self:Dyn_Emit("else") + self:Dyn_Emit("VD[IDX] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*4+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*4+1),") + self:Dyn_Emit("}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[IDX],VM:ReadCell(ADDR+(IDX-1)*4+2),VM:ReadCell(ADDR+(IDX-1)*4+3))") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD)") +end +VM.OpcodeTable[222] = function(self) --DVXDATA_3F + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L VDATA = VM:ReadCell(65467)") + self:Dyn_Emit("for IDX=1,math.min(128,$2) do") + self:Dyn_Emit("if VDATA > 0 then") + self:Dyn_Emit("$L VIDX1 = VM:ReadCell(ADDR+(IDX-1)*3+0)") + self:Dyn_Emit("$L VIDX2 = VM:ReadCell(ADDR+(IDX-1)*3+1)") + self:Dyn_Emit("$L VIDX3 = VM:ReadCell(ADDR+(IDX-1)*3+2)") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX1*3+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX1*3+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX1*3+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX2*3+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX2*3+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX2*3+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX3*3+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX3*3+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX3*3+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("else") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*9+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*9+1),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*9+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*9+3),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*9+4),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*9+5),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*9+6),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*9+7),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*9+8),") + self:Dyn_Emit("}") + self:Dyn_Emit("end") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],1,1)") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[223] = function(self) --DVXDATA_3F_TEX + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L VDATA = VM:ReadCell(65467)") + self:Dyn_Emit("for IDX=1,math.min(128,$2) do") + self:Dyn_Emit("if VDATA > 0 then") + self:Dyn_Emit("$L VIDX1 = VM:ReadCell(ADDR+(IDX-1)*3+0)") + self:Dyn_Emit("$L VIDX2 = VM:ReadCell(ADDR+(IDX-1)*3+1)") + self:Dyn_Emit("$L VIDX3 = VM:ReadCell(ADDR+(IDX-1)*3+2)") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX1*5+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX1*5+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX1*5+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX2*5+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX2*5+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX2*5+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX3*5+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX3*5+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX3*5+2),") + self:Dyn_Emit("}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],VM:ReadCell(VDATA+VIDX1*5+3),VM:ReadCell(VDATA+VIDX1*5+4))") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],VM:ReadCell(VDATA+VIDX2*5+3),VM:ReadCell(VDATA+VIDX2*5+4))") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],VM:ReadCell(VDATA+VIDX3*5+3),VM:ReadCell(VDATA+VIDX3*5+4))") + self:Dyn_Emit("else") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*15+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*15+1),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*15+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*15+5),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*15+6),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*15+7),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*15+10),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*15+11),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*15+12),") + self:Dyn_Emit("}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],VM:ReadCell(ADDR+(IDX-1)*15+ 3),VM:ReadCell(ADDR+(IDX-1)*15+ 4))") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],VM:ReadCell(ADDR+(IDX-1)*15+ 8),VM:ReadCell(ADDR+(IDX-1)*15+ 9))") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],VM:ReadCell(ADDR+(IDX-1)*15+13),VM:ReadCell(ADDR+(IDX-1)*15+14))") + self:Dyn_Emit("end") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[224] = function(self) --DVXDATA_3F_WF + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L VDATA = VM:ReadCell(65467)") + self:Dyn_Emit("for IDX=1,math.min(128,$2) do") + self:Dyn_Emit("if VDATA > 0 then") + self:Dyn_Emit("$L VIDX1 = VM:ReadCell(ADDR+(IDX-1)*3+0)") + self:Dyn_Emit("$L VIDX2 = VM:ReadCell(ADDR+(IDX-1)*3+1)") + self:Dyn_Emit("$L VIDX3 = VM:ReadCell(ADDR+(IDX-1)*3+2)") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX1*3+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX1*3+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX1*3+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX2*3+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX2*3+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX2*3+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(VDATA+VIDX3*3+0),") + self:Dyn_Emit(" y = VM:ReadCell(VDATA+VIDX3*3+1),") + self:Dyn_Emit(" z = VM:ReadCell(VDATA+VIDX3*3+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("else") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*9+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*9+1),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*9+2),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*9+3),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*9+4),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*9+5),") + self:Dyn_Emit("}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR+(IDX-1)*9+6),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR+(IDX-1)*9+7),") + self:Dyn_Emit(" z = VM:ReadCell(ADDR+(IDX-1)*9+8),") + self:Dyn_Emit("}") + self:Dyn_Emit("end") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD,true)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[225] = function(self) --DRECT + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR1 = $1") + self:Dyn_Emit("$L ADDR2 = $2") + + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR2+1)}") + self:Dyn_Emit("VD[4] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR2+1)}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],1,1)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[4],0,1)") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD)") +end +VM.OpcodeTable[226] = function(self) --DCIRCLE + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L R = $2") + self:Dyn_Emit("$L SIDES = math.max(3,math.min(64,VM:ReadCell(65485)))") + self:Dyn_Emit("$L START = VM:ReadCell(65478)") + self:Dyn_Emit("$L END = VM:ReadCell(65477)") + self:Dyn_Emit("$L STEP = (END-START)/SIDES") + + self:Dyn_Emit("$L VEC = VM:ReadVector2f($1)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("for IDX=1,SIDES do") + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VEC.x + R*math.sin(START+STEP*(IDX+0)),") + self:Dyn_Emit(" y = VEC.y + R*math.cos(START+STEP*(IDX+0))}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VEC.x,") + self:Dyn_Emit(" y = VEC.y}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VEC.x + R*math.sin(START+STEP*(IDX+1)),") + self:Dyn_Emit(" y = VEC.y + R*math.cos(START+STEP*(IDX+1))}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],1,1)") + + self:Dyn_Emit("VM:DrawToBuffer(VD)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[227] = function(self) --DLINE + self:Dyn_Emit("VM:DrawLine(VM:ReadVector2f($1),VM:ReadVector2f($2))") +end +VM.OpcodeTable[228] = function(self) --DRECTWH + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR1 = $1") + self:Dyn_Emit("$L ADDR2 = $2") + + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0)+VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0)+VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)+VM:ReadCell(ADDR2+1)}") + self:Dyn_Emit("VD[4] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)+VM:ReadCell(ADDR2+1)}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],1,1)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[4],0,1)") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawToBuffer(VD)") +end +VM.OpcodeTable[229] = function(self) --DORECT + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR1 = $1") + self:Dyn_Emit("$L ADDR2 = $2") + + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR2+1)}") + self:Dyn_Emit("VD[4] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR2+1)}") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawLine(VD[1],VD[2])") + self:Dyn_Emit("VM:DrawLine(VD[2],VD[3])") + self:Dyn_Emit("VM:DrawLine(VD[3],VD[4])") + self:Dyn_Emit("VM:DrawLine(VD[4],VD[1])") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[230] = function(self) --DTRANSFORM2F + self:Dyn_Emit("$L VEC = VM:ReadVector2f($2)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VEC = VM:VertexTransform(VEC)") + self:Dyn_Emit("VM:WriteVector2f($1,VEC)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[231] = function(self) --DTRANSFORM3F + self:Dyn_Emit("$L VEC = VM:ReadVector3f($2)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VEC = VM:VertexTransform(VEC)") + self:Dyn_Emit("VM:WriteVector3f($1,VEC)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[232] = function(self) --DSCRSIZE + self:Dyn_Emit("VM:WriteCell(65515,$1)") + self:Dyn_Emit("VM:WriteCell(65514,$2)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[233] = function(self) --DROTATESCALE + self:Dyn_Emit("VM:WriteCell(65482,$1)") + self:Dyn_Emit("VM:WriteCell(65481,$2)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[234] = function(self) --DORECTWH + self:Dyn_Emit("$L VD = {}") + self:Dyn_Emit("$L ADDR1 = $1") + self:Dyn_Emit("$L ADDR2 = $2") + + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0)+VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0)+VM:ReadCell(ADDR2+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)+VM:ReadCell(ADDR2+1)}") + self:Dyn_Emit("VD[4] = {") + self:Dyn_Emit(" x = VM:ReadCell(ADDR1+0),") + self:Dyn_Emit(" y = VM:ReadCell(ADDR1+1)+VM:ReadCell(ADDR2+1)}") + + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:DrawLine(VD[1],VD[2])") + self:Dyn_Emit("VM:DrawLine(VD[2],VD[3])") + self:Dyn_Emit("VM:DrawLine(VD[3],VD[4])") + self:Dyn_Emit("VM:DrawLine(VD[4],VD[1])") +end +VM.OpcodeTable[235] = function(self) --DCULLMODE + self:Dyn_Emit("VM:WriteCell(65469,$1)") + self:Dyn_Emit("VM:WriteCell(65468,$2)") +end +VM.OpcodeTable[236] = function(self) --DARRAY + +end +VM.OpcodeTable[237] = function(self) --DDTERMINAL + +end +VM.OpcodeTable[238] = function(self) --DPIXEL + self:Dyn_Emit("$L COLOR = VM:ColorTransform(VM:ReadVector4f($2))") + self:Dyn_Emit("$L POS = VM:ReadVector2f($1)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("surface.SetTexture(0)") + self:Dyn_Emit("surface.SetDrawColor(COLOR.x,COLOR.y,COLOR.z,COLOR.w)") + self:Dyn_Emit("surface.DrawRect(math.floor(POS.x),math.floor(POS.y),1,1)") +end +VM.OpcodeTable[239] = function(self) --RESERVED + +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[240] = function(self) --DWRITE + self:Dyn_Emit("$L TEXT = VM:ReadString($2)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:FontWrite($1,TEXT)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[241] = function(self) --DWRITEI + self:Dyn_Emit("VM:FontWrite($1,math.floor($2))") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[242] = function(self) --DWRITEF + self:Dyn_Emit("VM:FontWrite($1,$2)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[243] = function(self) --DENTRYPOINT + self:Dyn_Emit("$L IDX = $1") + self:Dyn_Emit("if IDX == 0 then VM.EntryPoint0 = $2 end") + self:Dyn_Emit("if IDX == 1 then VM.EntryPoint1 = $2 end") + self:Dyn_Emit("if IDX == 2 then VM.EntryPoint2 = $2 end") + self:Dyn_Emit("if IDX == 3 then VM.EntryPoint3 = $2 end") + self:Dyn_Emit("if IDX == 4 then VM.EntryPoint4 = $2 end") +end +VM.OpcodeTable[244] = function(self) --DSETLIGHT + self:Dyn_Emit("$L IDX = math.floor($1)") + self:Dyn_Emit("$L ADDR = $2") + self:Dyn_Emit("if (IDX < 0) or (IDX > 7) then") + self:Dyn_EmitInterrupt("19","0") + self:Dyn_Emit("else") + self:Dyn_Emit("VM.Lights[IDX] = {") + self:Dyn_Emit(" Position = VM:ReadVector4f(ADDR+0),") + self:Dyn_Emit(" Color = VM:ReadVector4f(ADDR+4)}") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[245] = function(self) --DGETLIGHT + self:Dyn_Emit("$L IDX = math.floor($1)") + self:Dyn_Emit("$L ADDR = $2") + self:Dyn_Emit("if (IDX < 0) or (IDX > 7) then") + self:Dyn_EmitInterrupt("19","0") + self:Dyn_Emit("else") + self:Dyn_Emit("if VM.Lights[IDX] then") + self:Dyn_Emit("VM:WriteVector4f(ADDR+0,VM.Lights[IDX].Position)") + self:Dyn_Emit("VM:WriteVector4f(ADDR+4,VM.Lights[IDX].Color)") + self:Dyn_Emit("else") + self:Dyn_Emit("VM:WriteVector4f(ADDR+0,0)") + self:Dyn_Emit("VM:WriteVector4f(ADDR+4,0)") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[246] = function(self) --DWRITEFMT string.format( + self:Dyn_Emit("$L text = VM:ReadString($2)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L ptr = $2 + #text + 1") + self:Dyn_Emit("$L ptr2 = VM.Memory[65512] or 0") + self:Dyn_Emit("if ptr2 ~= 0 then ptr = ptr2 end") + + self:Dyn_Emit("local finaltext = \"\"") + + self:Dyn_Emit("local inparam = false") + self:Dyn_Emit("local lengthmod = nil") + + self:Dyn_Emit("while (text ~= \"\") do") + self:Dyn_Emit("local chr = string.sub(text,1,1)") + self:Dyn_Emit("text = string.sub(text,2,65536)") + + self:Dyn_Emit("if (inparam == false) then") + self:Dyn_Emit("if (chr == \"%\") then") + self:Dyn_Emit("inparam = true") + self:Dyn_Emit("else") + self:Dyn_Emit("finaltext = finaltext .. chr") + self:Dyn_Emit("end") + self:Dyn_Emit("else") + self:Dyn_Emit("if (chr == \".\") then") + self:Dyn_Emit("chr = string.sub(text,1,1)") + self:Dyn_Emit("text = string.sub(text,2,65536)") + + self:Dyn_Emit("if (tonumber(chr)) then") + self:Dyn_Emit("lengthmod = tonumber(chr)") + self:Dyn_Emit("end") + self:Dyn_Emit("elseif (chr == \"i\") or (chr == \"d\") then") + self:Dyn_Emit("if (lengthmod) then") + self:Dyn_Emit("local digits = 0") + self:Dyn_Emit("local num = math.floor(VM:ReadCell(ptr))") + self:Dyn_Emit("local temp = num") + self:Dyn_Emit("while (temp > 0) do") + self:Dyn_Emit("digits = digits + 1") + self:Dyn_Emit("temp = math.floor(temp / 10)") + self:Dyn_Emit("end") + self:Dyn_Emit("if (num == 0) then") + self:Dyn_Emit("digits = 1") + self:Dyn_Emit("end") + + self:Dyn_Emit("local fnum = tostring(num)") + self:Dyn_Emit("while (digits < lengthmod) do") + self:Dyn_Emit("digits = digits + 1") + self:Dyn_Emit("fnum = \"0\"..fnum") + self:Dyn_Emit("end") + + self:Dyn_Emit("finaltext = finaltext ..fnum") + self:Dyn_Emit("else") + self:Dyn_Emit("finaltext = finaltext .. math.floor(VM:ReadCell(ptr))") + self:Dyn_Emit("end") + self:Dyn_Emit("ptr = ptr + 1") + self:Dyn_Emit("inparam = false") + self:Dyn_Emit("lengthmod = nil") + self:Dyn_Emit("elseif (chr == \"f\") then") + self:Dyn_Emit("finaltext = finaltext .. VM:ReadCell(ptr)") + self:Dyn_Emit("ptr = ptr + 1") + self:Dyn_Emit("inparam = false") + self:Dyn_Emit("lengthmod = nil") + self:Dyn_Emit("elseif (chr == \"s\") then") + self:Dyn_Emit("local addr = VM:ReadCell(ptr)") + self:Dyn_Emit("local str = VM:ReadString(addr)") + self:Dyn_Emit("finaltext = finaltext .. str") + self:Dyn_Emit("ptr = ptr + 1") + self:Dyn_Emit("inparam = false") + self:Dyn_Emit("lengthmod = nil") + self:Dyn_Emit("elseif (chr == \"t\") then") + self:Dyn_Emit("while (string.len(finaltext) % (lengthmod or 6) != 0) do") + self:Dyn_Emit("finaltext = finaltext..\" \"") + self:Dyn_Emit("end") + self:Dyn_Emit("inparam = false") + self:Dyn_Emit("lengthmod = nil") + self:Dyn_Emit("elseif (chr == \"%\") then") + self:Dyn_Emit("finaltext = finaltext .. \"%\"") + self:Dyn_Emit("inparam = false") + self:Dyn_Emit("lengthmod = nil") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + + self:Dyn_Emit("VM:FontWrite($1,finaltext)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[247] = function(self) --DWRITEFIX + self:Dyn_Emit("$L TEXT = $2") + self:Dyn_Emit("if TEXT == math.floor(TEXT) then TEXT = TEXT .. \"0\" end") + self:Dyn_Emit("VM:FontWrite($1,TEXT)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[248] = function(self) --DTEXTWIDTH + self:Dyn_Emit("$L TEXT = VM:ReadString($2)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L W,H = VM:TextSize(TEXT)") + self:Dyn_EmitOperand("W") +end +VM.OpcodeTable[249] = function(self) --DTEXTHEIGHT + self:Dyn_Emit("$L TEXT = VM:ReadString($2)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L W,H = VM:TextSize(TEXT)") + self:Dyn_EmitOperand("H") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[271] = function(self) --MLOADPROJ + self:Dyn_Emit("VM.ProjectionMatrix = VM:ReadMatrix($1)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[272] = function(self) --MREAD + self:Dyn_Emit("VM:WriteMatrix($1,VM.ModelMatrix)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[274] = function(self) --DT + self:Dyn_EmitOperand("VM.TimerDT") +end +VM.OpcodeTable[276] = function(self) --DSHADE + self:Dyn_Emit("$L SHADE = $1") + self:Dyn_Emit("VM.Color.x = VM.Color.x*SHADE") + self:Dyn_Emit("VM.Color.y = VM.Color.y*SHADE") + self:Dyn_Emit("VM.Color.z = VM.Color.z*SHADE") + self:Dyn_Emit("VM:SetColor(VM.Color)") +end +VM.OpcodeTable[277] = function(self) --DSETWIDTH + self:Dyn_Emit("VM:WriteCell(65476,$1)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[278] = function(self) --MLOAD + self:Dyn_Emit("VM.ModelMatrix = VM:ReadMatrix($1)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[279] = function(self) --DSHADENORM + self:Dyn_Emit("$L SHADE = $1") + self:Dyn_Emit("VM.Color.x = math.Clamp(VM.Color.x*SHADE,0,255)") + self:Dyn_Emit("VM.Color.y = math.Clamp(VM.Color.y*SHADE,0,255)") + self:Dyn_Emit("VM.Color.z = math.Clamp(VM.Color.z*SHADE,0,255)") + self:Dyn_Emit("VM:SetColor(VM.Color)") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[280] = function(self) --DDFRAME + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L V1 = VM:ReadVector2f(ADDR+0)") -- X,Y + self:Dyn_Emit("$L V2 = VM:ReadVector2f(ADDR+2)") -- W,H + self:Dyn_Emit("$L V3 = VM:ReadVector4f(ADDR+4)") -- C1,C2,C3,BorderSize + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L CSHADOW = VM:ReadVector3f(V3.x)") + self:Dyn_Emit("$L CHIGHLIGHT = VM:ReadVector3f(V3.y)") + self:Dyn_Emit("$L CFACE = VM:ReadVector3f(V3.z)") + + -- Shadow rectangle + self:Dyn_Emit("$L VD1 = {}") + self:Dyn_Emit("VD1[1] = {") + self:Dyn_Emit(" x = V3.w + V1.x,") + self:Dyn_Emit(" y = V3.w + V1.y}") + self:Dyn_Emit("VD1[2] = {") + self:Dyn_Emit(" x = V3.w + V1.x + V2.x,") + self:Dyn_Emit(" y = V3.w + V1.y}") + self:Dyn_Emit("VD1[3] = {") + self:Dyn_Emit(" x = V3.w + V1.x + V2.x,") + self:Dyn_Emit(" y = V3.w + V1.y + V2.y}") + self:Dyn_Emit("VD1[4] = {") + self:Dyn_Emit(" x = V3.w + V1.x,") + self:Dyn_Emit(" y = V3.w + V1.y + V2.y}") + + -- Highlight rectangle + self:Dyn_Emit("$L VD2 = {}") + self:Dyn_Emit("VD2[1] = {") + self:Dyn_Emit(" x = -V3.w + V1.x,") + self:Dyn_Emit(" y = -V3.w + V1.y}") + self:Dyn_Emit("VD2[2] = {") + self:Dyn_Emit(" x = -V3.w + V1.x + V2.x,") + self:Dyn_Emit(" y = -V3.w + V1.y}") + self:Dyn_Emit("VD2[3] = {") + self:Dyn_Emit(" x = -V3.w + V1.x + V2.x,") + self:Dyn_Emit(" y = -V3.w + V1.y + V2.y}") + self:Dyn_Emit("VD2[4] = {") + self:Dyn_Emit(" x = -V3.w + V1.x,") + self:Dyn_Emit(" y = -V3.w + V1.y + V2.y}") + + -- Face rectangle + self:Dyn_Emit("$L VD3 = {}") + self:Dyn_Emit("VD3[1] = {") + self:Dyn_Emit(" x = V1.x,") + self:Dyn_Emit(" y = V1.y}") + self:Dyn_Emit("VD3[2] = {") + self:Dyn_Emit(" x = V1.x + V2.x,") + self:Dyn_Emit(" y = V1.y}") + self:Dyn_Emit("VD3[3] = {") + self:Dyn_Emit(" x = V1.x + V2.x,") + self:Dyn_Emit(" y = V1.y + V2.y}") + self:Dyn_Emit("VD3[4] = {") + self:Dyn_Emit(" x = V1.x,") + self:Dyn_Emit(" y = V1.y + V2.y}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD1[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD1[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD1[3],1,1)") + self:Dyn_Emit("VM:ComputeTextureUV(VD1[4],0,1)") + + self:Dyn_Emit("VM:ComputeTextureUV(VD2[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD2[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD2[3],1,1)") + self:Dyn_Emit("VM:ComputeTextureUV(VD2[4],0,1)") + + self:Dyn_Emit("VM:ComputeTextureUV(VD3[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD3[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD3[3],1,1)") + self:Dyn_Emit("VM:ComputeTextureUV(VD3[4],0,1)") + + self:Dyn_Emit("VM:SetColor(CSHADOW)") + self:Dyn_Emit("VM:DrawToBuffer(VD1)") + self:Dyn_Emit("VM:SetColor(CHIGHLIGHT)") + self:Dyn_Emit("VM:DrawToBuffer(VD2)") + self:Dyn_Emit("VM:SetColor(CFACE)") + self:Dyn_Emit("VM:DrawToBuffer(VD3)") +end +VM.OpcodeTable[283] = function(self) --DRASTER + self:Dyn_Emit("VM:WriteCell(65518,$1)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[284] = function(self) --DDTERRAIN + self:Dyn_Emit("$L ADDR = $1") + self:Dyn_Emit("$L W = VM:ReadCell(ADDR+0)") -- Total width/height of the terrain + self:Dyn_Emit("$L H = VM:ReadCell(ADDR+1)") + self:Dyn_Emit("$L R = math.Clamp(math.floor(VM:ReadCell(ADDR+2)),0,16)") -- Visibility radius + self:Dyn_Emit("$L U = VM:ReadCell(ADDR+3)") -- Point around which terrain must be drawn + self:Dyn_Emit("$L V = VM:ReadCell(ADDR+4)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L VD = {}") + + -- Terrain size + self:Dyn_Emit("$L MinX = math.Clamp(math.floor(W/2 + U - R),1,W-1)") + self:Dyn_Emit("$L MinY = math.Clamp(math.floor(H/2 + V - R),1,H-1)") + self:Dyn_Emit("$L MaxX = math.Clamp(math.floor(W/2 + U + R),1,W-1)") + self:Dyn_Emit("$L MaxY = math.Clamp(math.floor(H/2 + V + R),1,H-1)") + + -- Draw terrain + self:Dyn_Emit("for X=MinX,MaxX do") + self:Dyn_Emit("for Y=MinY,MaxY do") + self:Dyn_Emit("$L XPOS = X - W/2 - U - 0.5") + self:Dyn_Emit("$L YPOS = Y - H/2 - U - 0.5") + + self:Dyn_Emit("if (X > 0) and (X <= W-1) and (Y > 0) and (Y <= H-1) and (XPOS^2+YPOS^2 <= R^2) then") + self:Dyn_Emit("$L Z1 = VM:ReadCell(ADDR+16+(Y-1)*W+(X-1))") + self:Dyn_Emit("$L Z2 = VM:ReadCell(ADDR+16+(Y-1)*W+(X-0))") + self:Dyn_Emit("$L Z3 = VM:ReadCell(ADDR+16+(Y-0)*W+(X-0))") + self:Dyn_Emit("$L Z4 = VM:ReadCell(ADDR+16+(Y-0)*W+(X-1))") + + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = XPOS+1,") + self:Dyn_Emit(" y = YPOS+1,") + self:Dyn_Emit(" z = Z3}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = XPOS+1,") + self:Dyn_Emit(" y = YPOS,") + self:Dyn_Emit(" z = Z2}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = XPOS,") + self:Dyn_Emit(" y = YPOS,") + self:Dyn_Emit(" z = Z1}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],1,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],1,1)") + self:Dyn_Emit("VM:DrawToBuffer(VD)") + + self:Dyn_Emit("VD[1] = {") + self:Dyn_Emit(" x = XPOS,") + self:Dyn_Emit(" y = YPOS,") + self:Dyn_Emit(" z = Z1}") + self:Dyn_Emit("VD[2] = {") + self:Dyn_Emit(" x = XPOS,") + self:Dyn_Emit(" y = YPOS+1,") + self:Dyn_Emit(" z = Z4}") + self:Dyn_Emit("VD[3] = {") + self:Dyn_Emit(" x = XPOS+1,") + self:Dyn_Emit(" y = YPOS+1,") + self:Dyn_Emit(" z = Z3}") + + self:Dyn_Emit("VM:ComputeTextureUV(VD[1],0,0)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[2],0,1)") + self:Dyn_Emit("VM:ComputeTextureUV(VD[3],1,1)") + self:Dyn_Emit("VM:DrawToBuffer(VD)") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[288] = function(self) --DSETTEXTBOX + self:Dyn_Emit("VM.Textbox = VM:ReadVector2f($1)") + self:Dyn_EmitInterruptCheck() +end +VM.OpcodeTable[289] = function(self) --DSETTEXTWRAP + self:Dyn_Emit("VM.WordWrapMode = $1") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[294] = function(self) --DMULDT + self:Dyn_EmitOperand("$2*VM.TimerDT") +end +VM.OpcodeTable[297] = function(self) --DMULDT + self:Dyn_EmitOperand("$2*VM.TimerDT") +end +VM.OpcodeTable[298] = function(self) --DBEGIN + self:Dyn_Emit("VM.Entity:SetRendertarget(1)") + self:Dyn_Emit("VM.LastBuffer = 1") +end +VM.OpcodeTable[299] = function(self) --DEND + self:Dyn_Emit("VM:FlushBuffer()") + self:Dyn_Emit("VM.Entity:AssertSpriteBufferExists()") + self:Dyn_Emit("if VM.Entity.SpriteGPU.RT and VM.Entity.GPU.RT then") + self:Dyn_Emit("render.CopyTexture(VM.Entity.SpriteGPU.RT,VM.Entity.GPU.RT)") + self:Dyn_Emit("end") + self:Dyn_Emit("VM.Entity:SetRendertarget()") + self:Dyn_Emit("VM.LastBuffer = 2") +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[303] = function(self) --DXTEXTURE + self:Dyn_Emit("$L PTR = $1") + self:Dyn_Emit("if PTR > 0 then") + self:Dyn_Emit("$L NAME = VM:ReadString($1)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:SetMaterial(GPULib.Material(NAME))") + self:Dyn_Emit("else") + self:Dyn_Emit("VM:SetMaterial(nil)") + self:Dyn_Emit("end") +end + + + + + + +-------------------------------------------------------------------------------- +--ENT._VM = {} +--for k,v in pairs(VM) do ENT._VM[k] = v end +--VM = nil diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/cl_init.lua new file mode 100644 index 0000000..7749fdf --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/cl_init.lua @@ -0,0 +1,446 @@ +include("cl_gpuvm.lua") +include("shared.lua") + +local Monitors = {} +local MonitorLookup = {} +local HUDLookup = {} + +-------------------------------------------------------------------------------- +-- Update monitors certain GPU is linked to +-------------------------------------------------------------------------------- +local function recalculateMonitorLookup() + MonitorLookup = {} + HUDLookup = {} + for gpuIdx,linkedGPUs in pairs(Monitors) do + for _,linkedGPUIdx in pairs(linkedGPUs) do + local linkedEnt = ents.GetByIndex(linkedGPUIdx) + if linkedEnt and linkedEnt:IsValid() then + if linkedEnt:IsPlayer() then + HUDLookup[linkedGPUIdx] = gpuIdx + else + MonitorLookup[linkedGPUIdx] = gpuIdx + end + end + end + end +end + +local function GPU_MonitorState(um) + -- Read monitors for this GPU + local gpuIdx = um:ReadLong() + Monitors[gpuIdx] = {} + + -- Fetch all monitors + local count = um:ReadShort() + for i=1,count do + Monitors[gpuIdx][i] = um:ReadLong() + end + + -- Recalculate small lookup table for monitor system + recalculateMonitorLookup() +end +usermessage.Hook("wire_gpu_monitorstate", GPU_MonitorState) + + +-------------------------------------------------------------------------------- +-- Update GPU features/memory model +-------------------------------------------------------------------------------- +local function GPU_MemoryModel(um) + local GPU = ents.GetByIndex(um:ReadLong()) + if not GPU then return end + if not GPU:IsValid() then return end + + if GPU.VM then + GPU.VM.ROMSize = um:ReadLong() + GPU.VM.SerialNo = um:ReadFloat() + GPU.VM.RAMSize = GPU.VM.ROMSize + else + GPU.ROMSize = um:ReadLong() + GPU.SerialNo = um:ReadFloat() + end + GPU.ChipType = um:ReadShort() +end +usermessage.Hook("wire_gpu_memorymodel", GPU_MemoryModel) + +local wire_gpu_frameratio = CreateClientConVar("wire_gpu_frameratio",4) + +function ENT:Initialize() + -- Create virtual machine + self.VM = CPULib.VirtualMachine() + self.VM.SerialNo = CPULib.GenerateSN("GPU") + self.VM.RAMSize = 65536 + self.VM.ROMSize = 65536 + self.VM.PCAP = 0 + self.VM.RQCAP = 0 + self.VM.CPUVER = 1.0 -- Beta GPU by default + self.VM.CPUTYPE = 1 -- ZGPU + self.ChipType = 0 + + -- Hard-reset VM and override it + self:OverrideVM() + self.VM:HardReset() + + -- Special variables + self.VM.CurrentBuffer = 2 + self.VM.LastBuffer = 2 + self.VM.RenderEnable = 0 + self.VM.VertexMode = 0 + self.VM.MemBusBuffer = {} + self.VM.MemBusCount = 0 + + -- Create GPU + self.GPU = WireGPU(self) + self.In3D2D = false + self.In2D = false + + -- Setup caching + GPULib.ClientCacheCallback(self,function(Address,Value) + self.VM:WriteCell(Address,Value) + self.VM.ROM[Address] = Value + end) + + -- Draw outlines in chip mode + local tempDrawOutline = self.DrawEntityOutline + self.DrawEntityOutline = function(self) if self.ChipType ~= 0 then tempDrawOutline(self) end end +end + +-- Assert that sprite buffer exists and is available for any operations on it +function ENT:AssertSpriteBufferExists() + if not self.SpriteGPU then self.SpriteGPU = WireGPU(self) end +end + + + + +-------------------------------------------------------------------------------- +-- Entity deleted +function ENT:OnRemove() + GPULib.ClientCacheCallback(self,nil) + self.GPU:Finalize() + if self.SpriteGPU then self.SpriteGPU:Finalize() end +end + + + + +-------------------------------------------------------------------------------- +-- Run GPU execution (isAsync: should be running async thread) +function ENT:Run(isAsync) + -- How many steps VM must make to keep up with execution + local Cycles + if isAsync then + -- Limit frequency + self.VM.Memory[65527] = math.Clamp(self.VM.Memory[65527],1,1200000) + + -- Calculate timing + Cycles = math.max(1,math.floor(self.VM.Memory[65527]*self.DeltaTime*0.5)) + self.VM.TimerDT = self.DeltaTime/Cycles + self.VM.TIMER = self.CurrentTime + self.VM.ASYNC = 1 + else + Cycles = 50000 + self.VM:Reset() + self.VM.TimerDT = self.DeltaTime + self.VM.TIMER = self.CurrentTime + + if self.VM.INIT == 0 then + self.VM.IP = self.VM.EntryPoint1 + self.VM.INIT = 1 + else + self.VM.IP = self.VM.EntryPoint0 + end + + self.VM.ASYNC = 0 + end + + -- Run until interrupt, or if async thread then until async thread stops existing + while (Cycles > 0) and (self.VM.INTR == 0) do -- and (not (isAsync and (self.VM.Entrypoint4 == 0))) + local previousTMR = self.VM.TMR + self.VM:Step() + Cycles = Cycles - (self.VM.TMR - previousTMR) + + if (self.VM.ASYNC == 0) and (Cycles < 0) then self.VM:Interrupt(17,0) end + end + + -- Reset INTR register for async thread + if self.VM.ASYNC == 1 then self.VM.INTR = 0 end +end + + + + +-------------------------------------------------------------------------------- +-- Request rendering to rendertarget +function ENT:SetRendertarget(ID) + if ID == 1 then self:AssertSpriteBufferExists() end + + if not ID then -- Restore state + if self.In2D == true then self.In2D = false cam.End2D() end + if self.ScreenRTSet then + render.SetViewPort(0,0,self.ScreenRTWidth,self.ScreenRTHeight) + render.SetRenderTarget(self.ScreenRT) + + self.ScreenRTSet = nil + self.ScreenRT = nil + + self.VM.ScreenWidth = self.VM.VertexScreenWidth or 512 + self.VM.ScreenHeight = self.VM.VertexScreenHeight or 512 + end + if self.VertexCamSettings and (not self.In3D2D) then + cam.Start3D2D(self.VertexCamSettings[1],self.VertexCamSettings[2],self.VertexCamSettings[3]) + self.In3D2D = true + end + self.VM.CurrentBuffer = 2 + if self.VM.VertexMode == 0 then self.VM.RenderEnable = 0 end + else + -- Remember screen RT if this is the first switch + local noRT = true + if not self.ScreenRTSet then + self.ScreenRT = render.GetRenderTarget() + self.ScreenRTWidth = ScrW() + self.ScreenRTHeight = ScrH() + self.ScreenRTSet = true + noRT = false + end + + -- Bind correct rendertarget + local newRT + if ID == 0 + then newRT = self.GPU.RT + else newRT = self.SpriteGPU.RT + end + + if not newRT then return end + + -- Start drawing to the RT + if self.In2D == true then self.In2D = false cam.End2D() end + -- Get out of the 2D3D camera if its set + if self.In3D2D == true then self.In3D2D = false cam.End3D2D() end + + render.SetRenderTarget(newRT) + render.SetViewPort(0,0,512,512) + cam.Start2D() + self.In2D = true + + -- RT size + self.VM.ScreenWidth = 512 + self.VM.ScreenHeight = 512 + self.VM.CurrentBuffer = ID + + if self.VM.VertexMode == 0 then self.VM.RenderEnable = 1 end + end +end + + + + +-------------------------------------------------------------------------------- +-- Render GPU to rendertarget +function ENT:RenderGPU() + self.VM.VertexMode = 0 + self:SetRendertarget(0) + + if self.VM:ReadCell(65531) == 0 then -- Halt register + if self.VM:ReadCell(65533) == 1 then -- Hardware clear + surface.SetDrawColor(0,0,0,255) + surface.DrawRect(0,0,self.VM.ScreenWidth,self.VM.ScreenHeight) + end + if self.VM:ReadCell(65535) == 1 then -- Clk + self:Run(false) + end + end + + -- Restore screen rendertarget + self:SetRendertarget() +end + +-- Render GPU to world +function ENT:RenderVertex(width,height) + self.VM.VertexMode = 1 + self.VM.RenderEnable = 1 + self:SetRendertarget() + + self.VM.ScreenWidth = width or 512 + self.VM.ScreenHeight = height or 512 + self.VM.VertexScreenWidth = self.VM.ScreenWidth + self.VM.VertexScreenHeight = self.VM.ScreenHeight + + if self.VM:ReadCell(65531) == 0 then -- Halt register + if self.VM:ReadCell(65533) == 1 then -- Hardware clear + surface.SetDrawColor(0,0,0,255) + surface.DrawRect(0,0,self.VM.ScreenWidth,self.VM.ScreenHeight) + end + if self.VM:ReadCell(65535) == 1 then -- Clk + self:Run(false) + end + end + + self.VM.VertexScreenWidth = nil + self.VM.VertexScreenHeight = nil + self.VM.VertexMode = 0 + self:SetRendertarget() +end + +-- Process misc GPU stuff +function ENT:RenderMisc(pos, ang, resolution, aspect, monitor) + self.VM:WriteCell(65513, aspect) + local ply = LocalPlayer() + local trace = ply:GetEyeTraceNoCursor() + if (trace.Entity and trace.Entity:IsValid() and trace.Entity == self) then + local dist = trace.Normal:Dot(trace.HitNormal)*trace.Fraction*(-16384) + dist = math.max(dist, trace.Fraction*16384-self:BoundingRadius()) + + if (dist < 256) then + local pos = WorldToLocal( trace.HitPos, Angle(), pos, ang ) + local x = 0.5+pos.x/(monitor.RS*(512/monitor.RatioX)) + local y = 0.5-pos.y/(monitor.RS*512) + + local cursorOffset = 0 + if self.VM:ReadCell(65532) == 1 then -- Check for vertex mode to counter the faulty offset + cursorOffset = 0.5 + end + + self.VM:WriteCell(65505,x - cursorOffset) + self.VM:WriteCell(65504,y - cursorOffset) + + if (self.VM:ReadCell(65503) == 1) then + surface.SetDrawColor(255,255,255,255) + surface.SetTexture(surface.GetTextureID("gui/arrow")) + x = math.Clamp(x,0 + cursorOffset, 1 + cursorOffset) + y = math.Clamp(y,0 + cursorOffset, 1 + cursorOffset) + surface.DrawTexturedRectRotated(-256*aspect+x*512*aspect+10,-256+y*512+12,32,32,45) + end + end + end +end + + + +-------------------------------------------------------------------------------- +-- Entity drawing function +function ENT:Draw() + -- Calculate time-related variables + self.CurrentTime = CurTime() + self.DeltaTime = math.min(1/30,self.CurrentTime - (self.PreviousTime or 0)) + self.PreviousTime = self.CurrentTime + + -- Draw GPU itself + self:DrawModel() + + -- Draw image from another GPU + local videoSource = MonitorLookup[self:EntIndex()] + if videoSource then + videoGPU = ents.GetByIndex(videoSource) + if videoGPU and videoGPU:IsValid() and videoGPU.GPU then + videoGPU.GPU.Entity = self + videoGPU.GPU:Render( + videoGPU.VM:ReadCell(65522), videoGPU.VM:ReadCell(65523)-videoGPU.VM:ReadCell(65518)/512, -- rotation, scale + 512*math.Clamp(videoGPU.VM:ReadCell(65525),0,1), 512*math.Clamp(videoGPU.VM:ReadCell(65524),0,1) + ) + videoGPU.GPU.Entity = videoGPU + end + Wire_Render(self) + return + end + + if self.DeltaTime > 0 then + -- Run the per-frame GPU thread + if self.VM.Memory[65532] == 0 then + local FrameRate = wire_gpu_frameratio:GetFloat() or 4 + self.FramesSinceRedraw = (self.FramesSinceRedraw or 0) + 1 + self.FrameInstructions = 0 + if self.FramesSinceRedraw >= FrameRate then + self.FramesSinceRedraw = 0 + self:RenderGPU() + end + end + + -- Run asynchronous thread + if self.VM.Memory[65528] == 1 then + self.VM.VertexMode = 0 + if self.VM.LastBuffer < 2 + then self:SetRendertarget(self.VM.LastBuffer) + else self:SetRendertarget() + end + + self.VM:RestoreAsyncThread() + self:Run(true) + self.VM:SaveAsyncThread() + + self:SetRendertarget() + end + end + + -- Draw GPU to world + if self.ChipType == 0 then -- Not a microchip + if self.VM.Memory[65532] == 0 then + self.GPU:Render( + self.VM:ReadCell(65522), self.VM:ReadCell(65523)-self.VM:ReadCell(65518)/512, -- rotation, scale + 512*math.Clamp(self.VM:ReadCell(65525),0,1), 512*math.Clamp(self.VM:ReadCell(65524),0,1), -- width, height + function(pos, ang, resolution, aspect, monitor) -- postrenderfunction + self:RenderMisc(pos, ang, resolution, aspect, monitor) + end + ) + else + -- Custom render to world + local monitor, pos, ang = self.GPU:GetInfo() + + --pos = pos + ang:Up()*zoffset + pos = pos - ang:Right()*(monitor.y2-monitor.y1)/2 + pos = pos - ang:Forward()*(monitor.x2-monitor.x1)/2 + + local width,height = 512*math.Clamp(self.VM.Memory[65525],0,1), + 512*math.Clamp(self.VM.Memory[65524],0,1) + + local h = width and width*monitor.RatioX or height or 512 + local w = width or h/monitor.RatioX + local x = -w/2 + local y = -h/2 + + local res = monitor.RS*512/h + self.VertexCamSettings = { pos, ang, res } + cam.Start3D2D(pos, ang, res) + self.In3D2D = true + local ok, err = xpcall(function() + self:RenderVertex(512,512*monitor.RatioX) + self:RenderMisc(pos, ang, res, 1/monitor.RatioX, monitor) + end, debug.traceback) + if not ok then WireLib.ErrorNoHalt(err) end + if self.In3D2D then self.In3D2D = false cam.End3D2D() end + self.VertexCamSettings = nil + end + end + + Wire_Render(self) +end + + + +-------------------------------------------------------------------------------- +-- Think function +function ENT:Think() + for k,v in pairs(self.VM.MemBusBuffer) do + RunConsoleCommand("wgm", self:EntIndex(), k, v) + end + self.VM.MemBusBuffer = {} + self.VM.MemBusCount = 0 +end + + + + +-------------------------------------------------------------------------------- +-- HUD drawing function +local function GPU_DrawHUD() + local videoSource = HUDLookup[LocalPlayer():EntIndex()] + if videoSource then + local videoGPU = ents.GetByIndex(videoSource) + if videoGPU and videoGPU:IsValid() and videoGPU.RenderVertex then + local screenWidth = ScrW() + local screenHeight = ScrH() + + videoGPU:RenderVertex(screenWidth,screenHeight) + end + end +end +hook.Add("HUDPaint","wire_gpu_drawhud",GPU_DrawHUD) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/init.lua new file mode 100644 index 0000000..c5938de --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/init.lua @@ -0,0 +1,390 @@ +-- Load shared/clientside stuff +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("cl_gpuvm.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +DEFINE_BASECLASS("base_wire_entity") + +ENT.WireDebugName = "ZGPU" + + +-------------------------------------------------------------------------------- +function ENT:Initialize() + -- Physics properties + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + -- Inputs/outputs + self.Inputs = Wire_CreateInputs(self, { "Clk", "Reset", "IOBus", "MemBus", "VideoOut" }) + self.Outputs = Wire_CreateOutputs(self, { "Memory" }) + + -- Setup platform settings + self.Clk = 1 + self.MemBusScanAddress = 65536 + self.SerialNo = CPULib.GenerateSN("GPU") + self:SetMemoryModel("64k",true) + + -- Create serverside memory and cache + self.Memory = {} + self.Cache = GPUCacheManager(self) + + -- Connected monitors + self.Monitors = { } + self:UpdateClientMonitorState() +end + +function ENT:UpdateClientMonitorState() + umsg.Start("wire_gpu_monitorstate") + umsg.Long(self:EntIndex()) + umsg.Short(#self.Monitors) + for idx=1,#self.Monitors do + umsg.Long(self.Monitors[idx]) + end + umsg.End() +end +-------------------------------------------------------------------------------- +-- Set processor +-------------------------------------------------------------------------------- +function ENT:SetMemoryModel(model,initial) + if model then + for i=6,11 do + if model == (2^i).."k" then + self.RAMSize = (2^i)*1024 + self.ChipType = 0 + elseif model == (2^i).."kc" then + self.RAMSize = (2^i)*1024 + self.ChipType = 1 + end + end + end + + if not initial then + timer.Create("wire_gpu_modelupdate_"..math.floor(math.random()*1000000),0.1+math.random()*0.3,1, + function() + umsg.Start("wire_gpu_memorymodel") + umsg.Long(self:EntIndex()) + umsg.Long (self.RAMSize) + umsg.Float(self.SerialNo) + umsg.Short(self.ChipType) + umsg.End() + end) + end +end + + +-------------------------------------------------------------------------------- +-- Resend all GPU cache to newly spawned player +-------------------------------------------------------------------------------- +function ENT:ResendCache(player) + timer.Create("wire_gpu_resendtimer_"..math.floor(math.random()*1000000),0.4+math.random()*1.2,1, + function() + self.Cache:Flush() + for address,value in pairs(self.Memory) do + self:WriteCell(address,value,player) + end + self.Cache:Flush(player) + + self:WriteCell(65534,1,player) -- Reset GPU + self:WriteCell(65535,self.Clk,player) -- Update Clk + end) +end + +local function GPU_PlayerRespawn(player) + for _,Entity in pairs(ents.FindByClass("gmod_wire_gpu")) do + Entity:ResendCache(player) + end +end +hook.Add("PlayerInitialSpawn", "GPUPlayerRespawn", GPU_PlayerRespawn) +concommand.Add("wire_gpu_resendcache", GPU_PlayerRespawn) + + + +-------------------------------------------------------------------------------- +-- Checks if address is valid +-------------------------------------------------------------------------------- +local function isValidAddress(n) + return n and (math.floor(n) == n) and (n >= -140737488355327) and (n <= 140737488355328) +end + + + +-------------------------------------------------------------------------------- +-- Read cell from GPU memory +-------------------------------------------------------------------------------- +function ENT:ReadCell(Address) + Address = math.floor(Address) + -- Check if address is valid + if not isValidAddress(Address) then + self:Interrupt(15,Address) + return + end + + if (Address < 0) or (Address >= self.RAMSize) then + return nil + else + if self.Memory[Address] then + return self.Memory[Address] + else + return 0 + end + end +end + + +-------------------------------------------------------------------------------- +-- Write cell to GPU memory +-------------------------------------------------------------------------------- +function ENT:WriteCell(Address, Value, Player) + Address = math.floor(Address) + if (Address < 0) or (Address >= self.RAMSize) then + return false + else + if (Address ~= 65535) and (Address ~= 65534) and (Address ~= 65502) then + -- Write to internal memory + self.Memory[Address] = Value + + -- Add address to cache if cache is not big enough yet + self.Cache:Write(Address,Value,Player) + return true + else + self.Cache:Flush(Player) + self.Cache:WriteNow(Address,Value,Player) + end + return true + end +end + + + +-------------------------------------------------------------------------------- +-- Use key support +-------------------------------------------------------------------------------- +function ENT:Use(player) +-- +end + + +-------------------------------------------------------------------------------- +-- Write advanced dupe +-------------------------------------------------------------------------------- +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + info.SerialNo = self.SerialNo + info.RAMSize = self.RAMSize + info.ChipType = self.ChipType + info.Memory = {} + + for address = 0,self.RAMSize-1 do + if self.Memory[address] and (self.Memory[address] ~= 0) then info.Memory[address] = self.Memory[address] end + end + + return info +end + + +-------------------------------------------------------------------------------- +-- Read from advanced dupe +-------------------------------------------------------------------------------- +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.SerialNo = info.SerialNo or 999999 + self.RAMSize = info.RAMSize or 65536 + self.ChipType = info.ChipType or 0 + self.Memory = {} + + for address = 0,self.RAMSize-1 do + if info.Memory[address] then self.Memory[address] = tonumber(info.Memory[address]) or 0 end + end + + self:SetMemoryModel() + self:ResendCache(nil) +end + + +-------------------------------------------------------------------------------- +-- Handle external input +-------------------------------------------------------------------------------- +function ENT:TriggerInput(iname, value) + if iname == "Clk" then + self.Clk = (value >= 1 and 1 or 0) + self:WriteCell(65535,self.Clk) + elseif iname == "Reset" then + if value >= 1.0 then self:WriteCell(65534,1) end + end +end + + +-------------------------------------------------------------------------------- +-- Find out all monitors connected to the GPU +-------------------------------------------------------------------------------- +function ENT:QueryMonitors(entity) + self.QueryRecurseCounter = self.QueryRecurseCounter + 1 + if self.QueryRecurseCounter > 128 then return end + if (not entity) or (not entity:IsValid()) then return end + + if entity:GetClass() == "gmod_wire_gpu" then -- VideoOut connected to a GPU + table.insert(self.QueryResult,entity:EntIndex()) + elseif entity.Socket then -- VideoOut connected to a plug + self:QueryMonitors(entity.Socket.Inputs.Memory.Src) + elseif entity.Plug then -- VideoOut connected to a socket + self:QueryMonitors(entity.Plug.Inputs.Memory.Src) + elseif entity.Ply and entity.Ply:IsValid() then -- VideoOut connected to pod + table.insert(self.QueryResult,entity.Ply:EntIndex()) + elseif entity:GetClass() == "gmod_wire_addressbus" then -- VideoOut connected to address bus + self:QueryMonitors(entity.Inputs.Memory1.Src) + self:QueryMonitors(entity.Inputs.Memory2.Src) + self:QueryMonitors(entity.Inputs.Memory3.Src) + self:QueryMonitors(entity.Inputs.Memory4.Src) + elseif entity:GetClass() == "gmod_wire_extbus" then -- VideoOut connected to ext bus + self:QueryMonitors(entity.Inputs.Memory1.Src) + self:QueryMonitors(entity.Inputs.Memory2.Src) + self:QueryMonitors(entity.Inputs.Memory3.Src) + self:QueryMonitors(entity.Inputs.Memory4.Src) + self:QueryMonitors(entity.Inputs.Memory5.Src) + self:QueryMonitors(entity.Inputs.Memory6.Src) + self:QueryMonitors(entity.Inputs.Memory7.Src) + self:QueryMonitors(entity.Inputs.Memory8.Src) + end +end + + +-------------------------------------------------------------------------------- +-- Update cache and external connections +-------------------------------------------------------------------------------- +function ENT:Think() + -- Update IOBus + if self.Inputs.IOBus.Src then + -- Was there any update in that that would require flushing + local DataUpdated = false + + -- Update any cells that must be updated + for port = 0,1023 do + if self.Inputs.IOBus.Src.ReadCell then + local var = self.Inputs.IOBus.Src:ReadCell(port) + if var then + if self:ReadCell(port+63488) ~= var then + self:WriteCell(port+63488,var) + DataUpdated = true + end + end + end + end + + -- Flush updated data + if DataUpdated then self.Cache:Flush() end + end + + -- Update MemBus + if self.Inputs.MemBus.Src then + for address=self.MemBusScanAddress,self.MemBusScanAddress+1023 do + local var = self.Inputs.MemBus.Src:ReadCell(address-65536) + if var then + if self:ReadCell(address) ~= var then + self:WriteCell(address,var) + end + end + end + self.MemBusScanAddress = self.MemBusScanAddress + 1024 + if self.MemBusScanAddress >= 131072 then + self.MemBusScanAddress = 65536 + end + end + + -- Flush any data in cache + self.Cache:Flush() + + -- Update video output, and send any changes to client + if self.Inputs.VideoOut.Src then + self.QueryRecurseCounter = 0 + self.QueryResult = { } + self:QueryMonitors(self.Inputs.VideoOut.Src) + + -- Check if monitors setup has changed + local monitorsChanged = false + for k,v in pairs(self.QueryResult) do + if self.Monitors[k] ~= v then + monitorsChanged = true + break + end + end + + if not monitorsChanged then + for k,v in pairs(self.Monitors) do + if self.QueryResult[k] ~= v then + monitorsChanged = true + break + end + end + end + + if #self.QueryResult ~= #self.Monitors then monitorsChanged = true end + + if monitorsChanged then + self.Monitors = self.QueryResult + end + + -- Send update to all clients + if monitorsChanged then + self:UpdateClientMonitorState() + end + end + + -- Update serverside cursor + local model = self:GetModel() + local monitor = WireGPU_Monitors[model] + local ang = self:LocalToWorldAngles(monitor.rot) + local pos = self:LocalToWorld(monitor.offset) + + for _,player in pairs(player.GetAll()) do + local trace = player:GetEyeTraceNoCursor() + local ent = trace.Entity + if ent:IsValid() then + local dist = trace.Normal:Dot(trace.HitNormal)*trace.Fraction*(-16384) + dist = math.max(dist, trace.Fraction*16384-ent:BoundingRadius()) + + if dist < 64 and ent == self then + if player:KeyDown(IN_ATTACK) or player:KeyDown(IN_USE) then + self:WriteCell(65502,1) + end + local cpos = WorldToLocal(trace.HitPos, Angle(), pos, ang) + local cx = 0.5+cpos.x/(monitor.RS*(512/monitor.RatioX)) + local cy = 0.5-cpos.y/(monitor.RS*(512)) + + self.Memory[65505] = cx + self.Memory[65504] = cy + end + end + end + + self:NextThink(CurTime()+0.05) + return true +end + + +-------------------------------------------------------------------------------- +-- GPU-to-MemBus support +-------------------------------------------------------------------------------- +concommand.Add("wgm", function(player, command, args) + -- Find the referenced GPU + local GPU = ents.GetByIndex(args[1]) + if not GPU then return end + if not GPU:IsValid() then return end + + -- Must be a valid GPU, and belong to the caller +-- if GPU.player ~= player then return end + + -- Write on membus + local Address = tonumber(args[2]) or 0 + local Value = tonumber(args[3]) or 0 + + -- Perform external write + if GPU.Inputs.MemBus.Src then + GPU.Inputs.MemBus.Src:WriteCell(Address-65536,Value) + end +end) + +duplicator.RegisterEntityClass("gmod_wire_gpu", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/shared.lua new file mode 100644 index 0000000..63b736e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpu/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire ZGPU" +ENT.Author = "Black Phoenix" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false + +ENT.RenderGroup = RENDERGROUP_BOTH diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpulib_controller.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpulib_controller.lua new file mode 100644 index 0000000..3908a88 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gpulib_controller.lua @@ -0,0 +1,88 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire GPULib Controller" +ENT.WireDebugName = "GPULib Controller" + +if CLIENT then return end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + WireLib.CreateInputs(self, { "Target [ENTITY]" }) + WireLib.CreateOutputs(self, { "Screen [ENTITY]", "Target [ENTITY]", "LocalPosition [VECTOR]", "LocalAngle [ANGLE]", "Resolution" }) +end + +function ENT:Setup() +end + +function ENT:SetScreen(screen) + self.screen = screen + WireLib.TriggerOutput(self, "Screen", screen) + if IsValid(self.target) then + GPULib.switchscreen(self.screen, self.target) + end + self:UpdateTarget() +end + +function ENT:Think() + self:UpdateTarget() +end + +function ENT:UpdateTarget() + local target = self.screen and self.screen.GPUEntity + if not IsValid(target) then target = self.screen end + + if self.target ~= target then + self.target = target + if IsValid(target) then + WireLib.TriggerOutput(self, "Target", target) + local monitor, pos, ang = GPULib.GPU.GetInfo({ Entity = target }) -- TODO: think of a cleaner way + WireLib.TriggerOutput(self, "LocalPosition", monitor.offset) + WireLib.TriggerOutput(self, "LocalAngle", monitor.rot) + WireLib.TriggerOutput(self, "Resolution", monitor.RS) + else + WireLib.TriggerOutput(self, "Target", NULL) + WireLib.TriggerOutput(self, "LocalPosition", Vector(0,0,0)) + WireLib.TriggerOutput(self, "LocalAngle", Angle(0,0,0)) + WireLib.TriggerOutput(self, "Resolution", 0) + end + end +end + +function ENT:TriggerInput(iname, value) + if iname == "Target" and self.screen and self.screen:IsValid() then + if not IsValid(value) then value = self.screen end + GPULib.switchscreen(self.screen, value) + self:UpdateTarget() + end +end + +duplicator.RegisterEntityClass("gmod_wire_gpulib_controller", WireLib.MakeWireEnt, "Data") + +function ENT:LinkEnt(screen) + if not IsValid(screen) then return false, "Invalid entity" end + self:SetScreen(screen) + return true +end +function ENT:UnlinkEnt() + self:SetScreen(nil) + return true +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if IsValid(self.screen) then + info.screen = self.screen:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self:SetScreen(GetEntByID(info.screen)) + self:UpdateTarget() +end + diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_grabber.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_grabber.lua new file mode 100644 index 0000000..c0e9836 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_grabber.lua @@ -0,0 +1,191 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Grabber" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Grabber" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self, { "Grab","Strength","Range" }) + self.Outputs = Wire_CreateOutputs(self, {"Holding", "Grabbed Entity [ENTITY]"}) + self.WeldStrength = 0 + self.Weld = nil + self.WeldEntity = nil + self.ExtraProp = nil + self.ExtraPropWeld = nil + self:GetPhysicsObject():SetMass(10) + + self:Setup(100, true) +end + +function ENT:OnRemove() + if self.Weld then + self:ResetGrab() + end + Wire_Remove(self) +end + +function ENT:Setup(Range, Gravity) + if Range then self:SetBeamLength(Range) end + self.Gravity = Gravity +end + +function ENT:LinkEnt( prop ) + if not IsValid(prop) then return false, "Not a valid entity!" end + self.ExtraProp = prop + WireLib.SendMarks(self, {prop}) + return true +end +function ENT:UnlinkEnt() + if IsValid(self.ExtraPropWeld) then + self.ExtraPropWeld:Remove() + self.ExtraPropWeld = nil + end + self.ExtraProp = nil + WireLib.SendMarks(self, {}) + return true +end + +function ENT:ResetGrab() + if IsValid(self.Weld) then + self.Weld:Remove() + if IsValid(self.WeldEntity) and IsValid(self.WeldEntity:GetPhysicsObject()) and self.Gravity then + self.WeldEntity:GetPhysicsObject():EnableGravity(true) + end + end + if IsValid(self.ExtraPropWeld) then + self.ExtraPropWeld:Remove() + end + + self.Weld = nil + self.WeldEntity = nil + self.ExtraPropWeld = nil + + self:SetColor(Color(255, 255, 255, self:GetColor().a)) + Wire_TriggerOutput(self, "Holding", 0) + Wire_TriggerOutput(self, "Grabbed Entity", self.WeldEntity) +end + +function ENT:CanGrab(trace) + if not trace.Entity or not isentity(trace.Entity) then return false end + if (not trace.Entity:IsValid() and not trace.Entity:IsWorld()) or trace.Entity:IsPlayer() then return false end + -- If there's no physics object then we can't constraint it! + if not util.IsValidPhysicsObject(trace.Entity, trace.PhysicsBone) then return false end + + if not gamemode.Call( "CanTool", self:GetPlayer(), trace, "weld" ) then return false end + + return true +end + +function ENT:TriggerInput(iname, value) + if iname == "Grab" then + if value ~= 0 and self.Weld == nil then + local vStart = self:GetPos() + local vForward = self:GetUp() + + local filter = ents.FindByClass( "gmod_wire_spawner" ) -- for prop spawning contraptions that grab spawned props + table.insert( filter, self ) + + local trace = util.TraceLine { + start = vStart, + endpos = vStart + (vForward * self:GetBeamLength()), + filter = filter + } + if not self:CanGrab(trace) then return end + + -- Weld them! + local const = constraint.Weld(self, trace.Entity, 0, 0, self.WeldStrength) + if const then + const.Type = "" --prevents the duplicator from making this weld + end + + local const2 + --Msg("+Weld1\n") + if self.ExtraProp then + if self.ExtraProp:IsValid() then + const2 = constraint.Weld(self.ExtraProp, trace.Entity, 0, 0, self.WeldStrength) + if const2 then + const2.Type = "" --prevents the duplicator from making this weld + end + --Msg("+Weld2\n") + end + end + if self.Gravity then + trace.Entity:GetPhysicsObject():EnableGravity(false) + end + + self.WeldEntity = trace.Entity + self.Weld = const + self.ExtraPropWeld = const2 + + self:SetColor(Color(255, 0, 0, self:GetColor().a)) + Wire_TriggerOutput(self, "Holding", 1) + Wire_TriggerOutput(self, "Grabbed Entity", self.WeldEntity) + elseif value == 0 then + if self.Weld ~= nil or self.ExtraPropWeld ~= nil then + self:ResetGrab() + end + end + elseif iname == "Strength" then + self.WeldStrength = math.max(value,0) + elseif iname == "Range" then + self:SetBeamLength(math.Clamp(value,0,32000)) + end +end + +--duplicator support (TAD2020) +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if self.WeldEntity and self.WeldEntity:IsValid() then + info.WeldEntity = self.WeldEntity:EntIndex() + end + + if self.ExtraProp and self.ExtraProp:IsValid() then + info.ExtraProp = self.ExtraProp:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.WeldEntity = GetEntByID(info.WeldEntity) + + self.ExtraProp = GetEntByID(info.ExtraProp) + + if self.WeldEntity and self.Inputs.Grab.Value ~= 0 then + + if not self.Weld and self.trace~=nil then + self.Weld = constraint.Weld(self, self.trace, 0, 0, self.WeldStrength) + self.Weld.Type = "" --prevents the duplicator from making this weld + end + + if IsValid(self.ExtraProp) then + self.ExtraPropWeld = constraint.Weld(self.ExtraProp, self.WeldEntity, 0, 0, self.WeldStrength) + self.ExtraPropWeld.Type = "" --prevents the duplicator from making this weld + end + + if self.Gravity then + self.WeldEntity:GetPhysicsObject():EnableGravity(false) + end + if self.Weld then + self:SetColor(Color(255, 0, 0, self:GetColor().a)) + Wire_TriggerOutput(self, "Holding", 1) + Wire_TriggerOutput(self, "Grabbed Entity", self.WeldEntity) + else + self:ResetGrab() + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_grabber", WireLib.MakeWireEnt, "Data", "Range", "Gravity") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_graphics_tablet.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_graphics_tablet.lua new file mode 100644 index 0000000..8d51765 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_graphics_tablet.lua @@ -0,0 +1,182 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Graphics Tablet" +ENT.WireDebugName = "Graphics Tablet" +ENT.Author = "greenarrow" +ENT.Editable = true + +ENT.workingDistance = 64 + +local SCREEN_CURSOR = false -- (0, 0) is top left, (1, 1) is bottom right +local GRAPH_CURSOR = true -- (0, 0) is center, (1, 1) is top right + +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "DrawBackground", { KeyName = "DrawBackground", + Edit = { type = "Boolean", title = "#Tool_wire_graphics_tablet_mode", order = 1 } }) + self:NetworkVar("Bool", 1, "CursorMode", { KeyName = "CursorMode", + Edit = { type = "Boolean", title = "#Tool_wire_graphics_tablet_draw_background", order = 2 } }) +end + +if CLIENT then + function ENT:Initialize() + self.GPU = WireGPU(self, true) + end + + function ENT:OnRemove() + self.GPU:Finalize() + end + + local function cut_rect(x1,y1,w1,h1,x2,y2,w2,h2) + local x,y = x1>x2 and x1 or x2, y1>y2 and y1 or y2 + local right1,bottom1,right2,bottom2 = x1+w1,y1+h1, x2+w2,y2+h2 + local w,h = (right1= 0 and cy >= 0 and cx <= 1 and cy <= 1 then + surface.SetDrawColor(255, 255, 255, 255) + --surface.SetTexture(surface.GetTextureID("gui/arrow")) + --surface.DrawTexturedRectRotated(x+cx*w+11,y+cy*h+11,32,32,45) + + local curSize = 16 + local curWidth = 2 + local midX, midY = x+cx*w,y+cy*h + + local x1,y1,w1,h1 = cut_rect(midX - curSize, midY - curWidth, curSize * 2, curWidth * 2,x,y,w,h) + local x2,y2,w2,h2 = cut_rect(midX - curWidth, midY - curSize, curWidth * 2, curSize * 2,x,y,w,h) + surface.DrawRect(x1,y1,w1,h1) + surface.DrawRect(x2,y2,w2,h2) + end + end + end + end, draw_background and nil or 0.1) + Wire_Render(self) + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Outputs = Wire_CreateOutputs(self, { "X", "Y", "Use", "OnScreen" }) + + Wire_TriggerOutput(self, "X", 0) + Wire_TriggerOutput(self, "Y", 0) + Wire_TriggerOutput(self, "Use", 0) + Wire_TriggerOutput(self, "OnScreen", 0) + + self.lastOnscreen = 0 + self.lastX = 0 + self.lastY = 0 + self.lastClick = 0 +end + +function ENT:Think() + BaseClass.Think(self) + local onScreen = 0 + local clickActive = 0 + + local GPUEntity = self.GPUEntity or self + local model = GPUEntity:GetModel() + local monitor = WireGPU_Monitors[model] + local ang = GPUEntity:LocalToWorldAngles(monitor.rot) + local pos = GPUEntity:LocalToWorld(monitor.offset) + local h = 512 + local w = h/monitor.RatioX + local x = -w/2 + local y = -h/2 + + for _,ply in pairs(player.GetAll()) do + local trace = ply:GetEyeTraceNoCursor() + local ent = trace.Entity + if ent:IsValid() then + local dist = trace.Normal:Dot(trace.HitNormal)*trace.Fraction*-16384 + dist = math.max(dist, trace.Fraction*16384-ent:BoundingRadius()) + + if dist < 64 and ent == GPUEntity then + if ply:KeyDown(IN_ATTACK) or ply:KeyDown(IN_USE) then + clickActive = 1 + end + local cpos = WorldToLocal(trace.HitPos, Angle(), pos, ang) + + local cx = 0.5+cpos.x/(monitor.RS*w) + local cy = 0.5-cpos.y/(monitor.RS*h) + + if (cx >= 0 and cy >= 0 and cx <= 1 and cy <= 1) then + onScreen = 1 + if (cx ~= self.lastX or cy ~= self.lastY) then + self.lastX = cx + self.lastY = cy + if self:GetCursorMode() == GRAPH_CURSOR then + cx = cx * 2 - 1 + cy = -(cy * 2 - 1) + end + Wire_TriggerOutput(self, "X", cx) + Wire_TriggerOutput(self, "Y", cy) + self:ShowOutput(cx, cy, clickActive, 1) + end + end + end + end + end + + if (onScreen ~= self.lastOnScreen) then + Wire_TriggerOutput(self, "OnScreen", onScreen) + self:ShowOutput(self.lastX, self.lastY, self.lastClick, onScreen) + self.lastOnScreen = onScreen + end + + if (clickActive ~= self.lastClick) then + Wire_TriggerOutput(self, "Use", clickActive) + self:ShowOutput(self.lastX, self.lastY, clickActive, self.lastOnScreen) + self.lastClick = clickActive + end + + self:NextThink(CurTime()+0.08) + return true +end + +function ENT:ShowOutput(cx, cy, activeval, osval) + self:SetOverlayText(string.format("X = %f, Y = %f, Use = %d, On Screen = %d\n", cx, cy, activeval, osval)) +end + +function ENT:OnRestore() + BaseClass.OnRestore(self) + Wire_AdjustOutputs(self, { "X", "Y", "Use", "OnScreen" }) +end + +-- only needed for legacy dupes +function ENT:Setup(gmode, draw_background) + if gmode ~= nil then self:SetCursorMode(gmode) end + if draw_background ~= nil then self:SetDrawBackground(draw_background) end +end + +duplicator.RegisterEntityClass("gmod_wire_graphics_tablet", WireLib.MakeWireEnt, "Data", "gmode", "draw_background") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gyroscope.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gyroscope.lua new file mode 100644 index 0000000..3c3de24 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_gyroscope.lua @@ -0,0 +1,79 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Gyroscope" +ENT.WireDebugName = "Gyroscope" + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Out180" ) +end + +if CLIENT then + --handle overlay text client side instead (TAD2020) + function ENT:Think() + BaseClass.Think(self) + + if self:GetModel() == "models/bull/various/gyroscope.mdl" then + + local lineOfNodes = self:WorldToLocal( ( Vector(0,0,1):Cross( self:GetUp() ) ):GetNormal( ) + self:GetPos() ) + + self:SetPoseParameter( "rot_yaw" , math.deg( math.atan2( lineOfNodes[2] , lineOfNodes[1] ) ) ) + self:SetPoseParameter( "rot_roll" , -math.deg( math.acos( self:GetUp():DotProduct( Vector(0,0,1) ) ) or 0 ) ) + end + + local ang = self:GetAngles() + if (ang.p < 0 && !self:GetOut180()) then ang.p = ang.p + 360 end + if (ang.y < 0 && !self:GetOut180()) then ang.y = ang.y + 360 end + if (ang.r < 0 && !self:GetOut180()) then ang.r = ang.r + 360 + elseif (ang.r > 180 && self:GetOut180()) then ang.r = ang.r - 360 end + self:ShowOutput(ang.p, ang.y, ang.r) + + self:NextThink(CurTime()+0.04) + return true + end + + function ENT:ShowOutput(p, y, r) + self:SetOverlayText(string.format("Angles = %.3f, %.3f, %.3f", p, y, r)) + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = WireLib.CreateSpecialOutputs(self, { "Pitch", "Yaw", "Roll", "Angle" }, {"NORMAL", "NORMAL", "NORMAL", "ANGLE"}) +end + +function ENT:Setup( out180 ) + if out180 ~= nil then self:SetOut180(out180) end + + Wire_TriggerOutput(self, "Pitch", 0) + Wire_TriggerOutput(self, "Yaw", 0) + Wire_TriggerOutput(self, "Roll", 0) + WireLib.TriggerOutput(self, "Angle", Angle( 0, 0, 0 )) +end + +function ENT:Think() + BaseClass.Think(self) + + local ang = self:GetAngles() + if (ang.p < 0 && !self:GetOut180()) then ang.p = ang.p + 360 end + if (ang.y < 0 && !self:GetOut180()) then ang.y = ang.y + 360 end + if (ang.r < 0 && !self:GetOut180()) then ang.r = ang.r + 360 + elseif (ang.r > 180 && self:GetOut180()) then ang.r = ang.r - 360 end + Wire_TriggerOutput(self, "Pitch", ang.p) + Wire_TriggerOutput(self, "Yaw", ang.y) + Wire_TriggerOutput(self, "Roll", ang.r) + Wire_TriggerOutput(self, "Angle", Angle( ang.p, ang.y, ang.r )) + --now handled client side (TAD2020) + --self:ShowOutput(ang.p, ang.y, ang.r) + + self:NextThink(CurTime()+0.04) + return true +end + +duplicator.RegisterEntityClass("gmod_wire_gyroscope", WireLib.MakeWireEnt, "Data", "out180") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hdd.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hdd.lua new file mode 100644 index 0000000..ae2b47e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hdd.lua @@ -0,0 +1,328 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Flash EEPROM" +ENT.WireDebugName = "WireHDD" + +if CLIENT then return end -- No more client + +function ENT:OnRemove() + for k,v in pairs(self.CacheUpdated) do + file.Write(self:GetStructName(k),self:MakeFloatTable(self.Cache[k])) + end +end + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.Outputs = Wire_CreateOutputs(self, { "Data", "Capacity", "DriveID" }) + self.Inputs = Wire_CreateInputs(self, { "Clk", "AddrRead", "AddrWrite", "Data" }) + + self.Clk = 0 + self.AWrite = 0 + self.ARead = 0 + self.Data = 0 + self.Out = 0 + + -- Flash type + -- 0: compatibility 16 values per block mode + -- 1: 128 values per block mode + self.FlashType = 0 + self.BlockSize = 16 + + -- Hard drive id/folder id: + self.DriveID = 0 + self.PrevDriveID = nil + + -- Hard drive capicacity (loaded from hdd) + self.DriveCap = 0 + self.MaxAddress = 0 + + -- Current cache (array of blocks) + self.Cache = {} + self.CacheUpdated = {} + + -- Owners STEAMID + self.Owner_SteamID = "SINGLEPLAYER" + self:NextThink(CurTime()+1.0) +end + +function ENT:Setup(DriveID, DriveCap) + self.DriveID = DriveID + self.DriveCap = DriveCap + self:UpdateCap() + self:SetOverlayText(self.DriveCap.."kb".."\nWriteAddr:"..self.AWrite.." Data:"..self.Data.." Clock:"..self.Clk.."\nReadAddr:"..self.ARead.." = ".. self.Out) + Wire_TriggerOutput(self, "DriveID", self.DriveID) +end + +function ENT:GetStructName(name) + return "WireFlash/"..(self.Owner_SteamID or "UNKNOWN").."/HDD"..self.DriveID.."/"..name..".txt" +end + +function ENT:GetCap() + -- If hard drive exists + if file.Exists(self:GetStructName("drive"),"DATA") then + -- Read format data + local formatData = file.Read(self:GetStructName("drive"),"DATA") + + if tonumber(formatData) then + self.DriveCap = tonumber(formatData) + self.FlashType = 0 + self.BlockSize = 16 + self.MaxAddress = self.DriveCap * 1024 + else + local formatInfo = string.Explode("\n",formatData) + if (formatInfo[1] == "FLASH1") then + self.DriveCap = tonumber(formatInfo[2]) or 0 + self.MaxAddress = tonumber(formatInfo[3]) or (self.DriveCap * 1024) + self.FlashType = 1 + self.BlockSize = 32 + else + file.Write(self:GetStructName("drive"),"FLASH1\n"..self.DriveCap.."\n"..self.MaxAddress) + end + end + else + file.Write(self:GetStructName("drive"),"FLASH1\n"..self.DriveCap.."\n"..self.MaxAddress) + self.FlashType = 1 + self.BlockSize = 32 + self.MaxAddress = 0 + end + + --Can't have cap bigger than 256 in MP + if (not game.SinglePlayer()) and (self.DriveCap > 256) then + self.DriveCap = 256 + end + + Wire_TriggerOutput(self, "Capacity", self.DriveCap) +end + +function ENT:UpdateCap() + --Can't have cap bigger than 256 in MP + if (not game.SinglePlayer()) and (self.DriveCap > 256) then + self.DriveCap = 256 + end + if self.FlashType == 0 then + file.Write(self:GetStructName("drive"),self.DriveCap) + else + file.Write(self:GetStructName("drive"),"FLASH1\n"..self.DriveCap.."\n"..self.MaxAddress) + end + + self:GetCap() +end + +function ENT:GetFloatTable(Text) + local text = Text + local tbl = {} + local ptr = 0 + while (string.len(text) > 0) do + local value = string.sub(text, 1, 24) + text = string.sub(text, 25) + tbl[ptr] = tonumber(value) + ptr = ptr + 1 + end + return tbl +end + +function ENT:MakeFloatTable(Table) + local text = "" + for i=0,#Table-1 do + --Clamp size to 24 chars + local floatstr = string.sub(tostring(Table[i]),1,24) + --Make a string, and append missing spaces + floatstr = floatstr .. string.rep(" ",24-string.len(floatstr)) + + text = text..floatstr + end + + return text +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + --DriveID should be > 0, and less than 4 in MP + if ((self.DriveID < 0) || (!game.SinglePlayer() && (self.DriveID >= 4))) then + return nil + end + + local player = self:GetPlayer() + if player:IsValid() then + local steamid = player:SteamID() + steamid = string.gsub(steamid, ":", "_") + if (steamid ~= "UNKNOWN") then + self.Owner_SteamID = steamid + else + self.Owner_SteamID = "SINGLEPLAYER" + end + + -- If drive has changed, change cap + if self.DriveID ~= self.PrevDriveID then + self:GetCap() + self.PrevDriveID = self.DriveID + end + + -- Check if address is valid + if (Address < self.DriveCap * 1024) and (Address >= 0) then + -- Compute address + local block = math.floor(Address / self.BlockSize) + local blockaddress = math.floor(Address) % self.BlockSize + + -- Check if this address is cached for read + if self.Cache[block] then + return self.Cache[block][blockaddress] or 0 + end + + -- If sector isn't created yet, return 0 + if not file.Exists(self:GetStructName(block),"DATA") then + self.Cache[block] = {} + self.CacheUpdated[block] = true + for i=0,self.BlockSize-1 do + self.Cache[block][i] = 0 + end + return 0 + end + + -- Read the block + local blockdata = self:GetFloatTable(file.Read(self:GetStructName(block))) + self.Cache[block] = {} + for i=0,self.BlockSize-1 do + self.Cache[block][i] = blockdata[i] or 0 + end + return self.Cache[block][blockaddress] + else + return nil + end + else + return nil + end +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + --DriveID should be > 0, and less than 4 in MP + if ((self.DriveID < 0) || (!game.SinglePlayer() && (self.DriveID >= 4))) then + return false + end + + local player = self:GetPlayer() + if (player:IsValid()) then + local steamid = player:SteamID() + steamid = string.gsub(steamid, ":", "_") + if (steamid ~= "UNKNOWN") then + self.Owner_SteamID = steamid + else + self.Owner_SteamID = "SINGLEPLAYER" + end + + -- If drive has changed, change cap + if self.DriveID ~= self.PrevDriveID then + self:GetCap() + self.PrevDriveID = self.DriveID + end + + -- Check if address is valid + if (Address < self.DriveCap * 1024) and (Address >= 0) then + -- Compute address + local block = math.floor(Address / self.BlockSize) + local blockaddress = math.floor(Address) % self.BlockSize + + -- Check if this address is cached + if self.Cache[block] then + self.CacheUpdated[block] = true + self.Cache[block][blockaddress] = value + if Address > self.MaxAddress then + self.MaxAddress = Address + end + return true + end + + -- If sector isn't created yet, cache it + if not file.Exists(self:GetStructName(block),"DATA") then + self.Cache[block] = {} + self.CacheUpdated[block] = true + for i=0,self.BlockSize-1 do + self.Cache[block][i] = 0 + end + self.Cache[block][blockaddress] = value + if Address > self.MaxAddress then + self.MaxAddress = Address + end + return true + end + + -- Read the block + local blockdata = self:GetFloatTable(file.Read(self:GetStructName(block))) + self.Cache[block] = {} + for i=0,self.BlockSize-1 do + self.Cache[block][i] = blockdata[i] or 0 + end + self.CacheUpdated[block] = true + self.Cache[block][blockaddress] = value + if Address > self.MaxAddress then + self.MaxAddress = Address + end + return true + else + return false + end + else + return false + end +end + +function ENT:Think() + local cachedBlockIndex = next(self.CacheUpdated) + if cachedBlockIndex then + self.CacheUpdated[cachedBlockIndex] = nil + file.CreateDir(string.GetPathFromFilename(self:GetStructName(cachedBlockIndex))) + file.Write(self:GetStructName(cachedBlockIndex),self:MakeFloatTable(self.Cache[cachedBlockIndex])) + self:UpdateCap() + end + self:NextThink(CurTime()+0.25) + return true +end + +function ENT:TriggerInput(iname, value) + if (iname == "Clk") then + self.Clk = value + if (self.Clk >= 1) then + self:WriteCell(self.AWrite, self.Data) + if (self.ARead == self.AWrite) then + local val = self:ReadCell(self.ARead) + if (val) then + Wire_TriggerOutput(self, "Data", val) + self.Out = val + end + end + end + elseif (iname == "AddrRead") then + self.ARead = value + local val = self:ReadCell(value) + if (val) then + Wire_TriggerOutput(self, "Data", val) + self.Out = val + end + elseif (iname == "AddrWrite") then + self.AWrite = value + if (self.Clk >= 1) then + self:WriteCell(self.AWrite, self.Data) + end + elseif (iname == "Data") then + self.Data = value + if (self.Clk >= 1) then + self:WriteCell(self.AWrite, self.Data) + if (self.ARead == self.AWrite) then + local val = self:ReadCell(self.ARead) + if (val) then + Wire_TriggerOutput(self, "Data", val) + self.Out = val + end + end + end + end + + self:SetOverlayText(self.DriveCap.."kb".."\nWriteAddr:"..self.AWrite.." Data:"..self.Data.." Clock:"..self.Clk.."\nReadAddr:"..self.ARead.." = ".. self.Out) +end + +duplicator.RegisterEntityClass("gmod_wire_hdd", WireLib.MakeWireEnt, "Data", "DriveID", "DriveCap") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_holoemitter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_holoemitter.lua new file mode 100644 index 0000000..5199bdb --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_holoemitter.lua @@ -0,0 +1,417 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Holographic Emitter" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Holographic Emitter" + +if CLIENT then + local cvar = CreateClientConVar("cl_wire_holoemitter_maxfadetime",5,true,false) -- "cl_" in the cvar name isn't very neat... probably too late to change it now, though. + local keeplatest = CreateClientConVar("wire_holoemitter_keeplatestdot", "0", true, false) + + -- Materials + local matbeam = Material( "tripmine_laser" ) + local matpoint = Material( "sprites/gmdm_pickups/light" ) + + function ENT:Initialize() + self.Points = {} + self.RBound = Vector(1024,1024,1024) + end + + function ENT:AddPoint( Pos, Local, Color, DieTime, LineBeam, GroundBeam, Size ) + if Local ~= nil and Color ~= nil and DieTime ~= nil and LineBeam ~= nil and GroundBeam ~= nil and Size ~= nil then + + local point = {} + point.Pos = Pos + point.Local = Local + point.Color = Color + point.LineBeam = LineBeam + point.GroundBeam = GroundBeam + point.Size = Size + + if DieTime ~= 0 then + point.DieTime = CurTime() + DieTime + end + + point.SpawnTime = CurTime() + self.Points[#self.Points+1] = point + end + end + + net.Receive("WireHoloEmitterData", function(netlen) + local ent = net.ReadEntity() + if not IsValid(ent) then return end + local syncinterval = net.ReadFloat() + local count = net.ReadUInt(16) + for i=1, count do + local pos = net.ReadVector() + local lcl = net.ReadBit() ~= 0 + local color = Color(net.ReadUInt(8),net.ReadUInt(8),net.ReadUInt(8)) + local dietime = net.ReadUInt(16)/100 + local linebeam = net.ReadBit() ~= 0 + local groundbeam = net.ReadBit() ~= 0 + local size = net.ReadUInt(16)/100 + timer.Simple(i/count*syncinterval,function() + if IsValid(ent) then + ent:AddPoint(pos, lcl, color, dietime, linebeam, groundbeam, size) + end + end) + end + end) + + function ENT:Think() + self:NextThink( CurTime() ) + + if (self:GetNWBool( "Clear", false ) == true) then + self.Points = {} + return true + end + + if not next(self.Points) then return true end + + -- To make it visible across the entire map + local p = LocalPlayer():GetPos() + self:SetRenderBoundsWS( p - self.RBound, p + self.RBound ) + + local cvarnum = cvar:GetFloat() + + for k=#self.Points,1,-1 do + local v = self.Points[k] + + if k == #self.Points and keeplatest:GetBool() then continue end -- Check keep latest convar + + if v.DieTime then + v.Color.a = 255-(CurTime()-v.SpawnTime)/(v.DieTime-v.SpawnTime)*255 -- Set alpha + + if v.DieTime < CurTime() then -- If the point's time has passed, remove it + table.remove( self.Points, k ) + + if self.Points[k-1] then self.Points[k-1].LineBeam = false end -- Don't draw a line to this point anymore + end + end + + if cvarnum ~= 0 and v.SpawnTime + cvarnum < CurTime() then -- If the clientside time limit is shorter than the DieTime + table.remove( self.Points, k ) + + if self.Points[k-1] then self.Points[k-1].LineBeam = false end -- Don't draw a line to this point anymore + end + end + + return true + end + + function ENT:Draw() + BaseClass.Draw(self) + + local ent = self:GetNWEntity( "Link", false ) + if not IsValid(ent) then ent = self end + + local forcelocal = false + if ent:GetClass() == "gmod_wire_hologrid" then + local temp = ent:GetNWEntity( "reference", false ) + if IsValid(temp) then + ent = temp + forcelocal = true + end + end + + local selfpos = ent:GetPos() + + local n = #self.Points + + if (n == 0 or self:GetNWBool("Active",true) == false) then return end + + for k=1, n do + local v = self.Points[k] + local Pos = v.Pos + + if (v.Local or forcelocal) then + Pos = ent:LocalToWorld( Pos ) + end + + if (v.GroundBeam) then + render.SetMaterial( matbeam ) + render.DrawBeam( + selfpos, + Pos, + v.Size, + 0,1, + v.Color + ) + end + + if (v.LineBeam and k < n) then + render.SetMaterial( matbeam ) + + local NextPoint = self.Points[k+1] + local NextPos = NextPoint.Pos + if (NextPoint.Local or forcelocal) then + NextPos = ent:LocalToWorld( NextPos ) + end + + render.DrawBeam( + NextPos, + Pos, + v.Size * 2, + 0, 1, + v.Color + ) + end + + render.SetMaterial( matpoint ) + render.DrawSprite( + Pos, + v.Size, v.Size, + v.Color + ) + end + end + + return -- No more client +end + +-- Server + +local cvar = CreateConVar("wire_holoemitter_interval",0.3,{FCVAR_ARCHIVE,FCVAR_NOTIFY}) + +function ENT:Initialize( ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:DrawShadow( false ) + + self:SetNWBool( "Clear", false ) + self:SetNWBool( "Active", true ) + + self.bools = {} + self.bools.Local = true + self.bools.LineBeam = true + self.bools.GroundBeam = true + + self.Inputs = WireLib.CreateInputs( self, { "Pos [VECTOR]", "X" , "Y", "Z", "Local", "Color [VECTOR]", "FadeTime", "LineBeam", "GroundBeam", "Size", "Clear", "Active" } ) + self.Outputs = WireLib.CreateOutputs( self, { "Memory" } ) -- Compatibility for older hispeed devices (such as gpu/cpu) + + self.Points = {} + + self.Data = {} + self.Data.Pos = Vector(0,0,0) + self.Data.Local = false + self.Data.Color = Vector(255,255,255) + self.Data.FadeTime = 1 + self.Data.LineBeam = false + self.Data.GroundBeam = false + self.Data.Size = 1 + + self:SetOverlayText( "Holo Emitter" ) +end + +function ENT:AddPoint() + self.Points[#self.Points+1] = { + Pos = Vector(self.Data.Pos.x,self.Data.Pos.y,self.Data.Pos.z), + Local = self.Data.Local, + Color = Vector(self.Data.Color.x,self.Data.Color.y,self.Data.Color.z), + FadeTime = self.Data.FadeTime, + LineBeam = self.Data.LineBeam, + GroundBeam = self.Data.GroundBeam, + Size = self.Data.Size, + } +end + +function ENT:TriggerInput( name, value ) + if (name == "X") then -- X + if (self.Data.Pos.x != value) then + self.Data.Pos.x = value + self:AddPoint() + end + elseif (name == "Y") then -- Y + if (self.Data.Pos.y != value) then + self.Data.Pos.y = value + self:AddPoint() + end + elseif (name == "Z") then -- Z + if (self.Data.Pos.z != value) then + self.Data.Pos.z = value + self:AddPoint() + end + elseif (name == "Pos") then -- XYZ + if (self.Data.Pos != value) then + self.Data.Pos = value + self:AddPoint() + end + else + -- Clear & Active + if (name == "Clear" or name == "Active") then + self:SetNWBool(name,!(value == 0 and true) or false) + else + -- Other data + if (self.bools[name]) then value = !(value == 0 and true) or false end + self.Data[name] = value + end + end +end + +-- Hispeed info +-- 0 = Draw (when changed, draws point) (if used in readcell, returns whether or not you are allowed to draw any more this interval) +-- 1 = X +-- 2 = Y +-- 3 = Z +-- 4 = Local (1/0) +-- 5 = R +-- 6 = G +-- 7 = B +-- 8 = FadeTime +-- 9 = LineBeam +-- 10 = GroundBeam +-- 11 = Size +-- 12 = Clear (removes all dots when = 1) +-- 13 = Active + +function ENT:ReadCell( Address ) + Address = math.floor(Address) + if (Address == 0) then + return 1 + elseif (Address == 1) then + return self.Data.Pos.x + elseif (Address == 2) then + return self.Data.Pos.y + elseif (Address == 3) then + return self.Data.Pos.z + elseif (Address == 4) then + return (self.Data.Local and 1 or 0) + elseif (Address == 5) then + return self.Data.Color.x + elseif (Address == 6) then + return self.Data.Color.y + elseif (Address == 7) then + return self.Data.Color.z + elseif (Address == 8) then + return self.Data.FadeTime + elseif (Address == 9) then + return (self.Data.LineBeam and 1 or 0) + elseif (Address == 10) then + return (self.Data.GroundBeam and 1 or 0) + elseif (Address == 11) then + return self.Data.Size + elseif (Address == 12) then + return (self:GetNWBool("Clear",false) and 1 or 0) + elseif (Address == 13) then + return (self:GetNWBool("Active",true) and 1 or 0) + end +end + +function ENT:WriteCell( Address, value ) + Address = math.floor(Address) + if (Address == 0) then + self:AddPoint() + return true + elseif (Address == 1) then + self.Data.Pos.x = value + return true + elseif (Address == 2) then + self.Data.Pos.y = value + return true + elseif (Address == 3) then + self.Data.Pos.z = value + return true + elseif (Address == 4) then + self.Data.Local = value ~= 0 + return true + elseif (Address == 5) then + self.Data.Color.x = value + return true + elseif (Address == 6) then + self.Data.Color.y = value + return true + elseif (Address == 7) then + self.Data.Color.z = value + return true + elseif (Address == 8) then + self.Data.FadeTime = value + return true + elseif (Address == 9) then + self.Data.LineBeam = !(value == 0 and true) or false + return true + elseif (Address == 10) then + self.Data.GroundBeam = !(value == 0 and true) or false + return true + elseif (Address == 11) then + self.Data.Size = value + return true + elseif (Address == 12) then + self:SetNWBool( "Clear", !(value == 0 and true) or false ) + return true + elseif (Address == 13) then + self:SetNWBool( "Active", !(value == 0 and true) or false ) + return true + end + return false +end + +function ENT:Link( ent ) + if IsValid(ent) and ent:GetClass() == "gmod_wire_hologrid" then -- Remove "Local" input if linking to a hologrid + WireLib.AdjustInputs( self, { "Pos [VECTOR]", "X" , "Y", "Z", "Color [VECTOR]", "FadeTime", "LineBeam", "GroundBeam", "Size", "Clear", "Active" } ) + else + local old = self:GetNWEntity( "Link" ) + if IsValid(old) and old:GetClass() == "gmod_wire_hologrid" then -- Put the "Local" input back + WireLib.AdjustInputs( self, { "Pos [VECTOR]", "X" , "Y", "Z", "Local", "Color [VECTOR]", "FadeTime", "LineBeam", "GroundBeam", "Size", "Clear", "Active" } ) + end + end + + self:SetNWEntity( "Link", ent ) +end + +function ENT:UnLink() + local old = self:GetNWEntity( "Link" ) + if IsValid(old) and old:GetClass() == "gmod_wire_hologrid" then -- Put the "Local" input back + WireLib.AdjustInputs( self, { "Pos [VECTOR]", "X" , "Y", "Z", "Local", "Color [VECTOR]", "FadeTime", "LineBeam", "GroundBeam", "Size", "Clear", "Active" } ) + end + + self:SetNWEntity( "Link", NULL ) +end + +util.AddNetworkString("WireHoloEmitterData") +function ENT:Think() + self:NextThink( CurTime() + cvar:GetFloat() ) + if not next(self.Points) then return true end + net.Start("WireHoloEmitterData") + net.WriteEntity(self) + net.WriteFloat(cvar:GetFloat()) -- send sync interval + net.WriteUInt(#self.Points, 16) -- send nr of points + for _,v in pairs( self.Points ) do -- send each point + net.WriteVector(v.Pos) + net.WriteBit(v.Local) + net.WriteUInt(v.Color.x,8) + net.WriteUInt(v.Color.y,8) + net.WriteUInt(v.Color.z,8) + net.WriteUInt(math.Clamp(v.FadeTime,0,100)*100,16) + net.WriteBit(v.LineBeam) + net.WriteBit(v.GroundBeam) + net.WriteUInt(math.Clamp(v.Size,0,100)*100, 16) + end + net.Broadcast() + + self.Points = {} + return true +end + +duplicator.RegisterEntityClass("gmod_wire_holoemitter", WireLib.MakeWireEnt, "Data") + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + local link = self:GetNWEntity("Link",false) + if (link) then + info.holoemitter_link = link:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self:Link(GetEntByID(info.holoemitter_link)) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hologram.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hologram.lua new file mode 100644 index 0000000..5d805f7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hologram.lua @@ -0,0 +1,393 @@ +AddCSLuaFile() +DEFINE_BASECLASS("base_anim") -- NOTE: Not base_wire_entity! Simpler than that +ENT.PrintName = "Wire Hologram" +ENT.RenderGroup = RENDERGROUP_OPAQUE +ENT.DisableDuplicator = true + +function ENT:SetPlayer(ply) + self:SetVar("Founder", ply) + self:SetVar("FounderIndex", ply:UniqueID()) + + self:SetNWString("FounderName", ply:Nick()) +end + +function ENT:GetPlayer() + return self:GetVar("Founder", NULL) +end + +if CLIENT then + local blocked = {} + local scale_buffer = {} + local bone_scale_buffer = {} + local clip_buffer = {} + local vis_buffer = {} + local player_color_buffer = {} + + function ENT:Initialize() + self.bone_scale = {} + self:DoScale() + local ownerid = self:GetNWInt("ownerid") + self.blocked = blocked[ownerid] or false + + self.clips = {} + self:DoClip() + self:DoVisible() + self:DoPlayerColor() + end + + hook.Add("PlayerBindPress", "wire_hologram_scale_setup", function() -- For initial spawn + for _, ent in pairs(ents.FindByClass("gmod_wire_hologram")) do + if ent:IsValid() and ent.DoScale then + ent:DoScale() + ent:DoClip() + ent:DoVisible() + ent:DoPlayerColor() + end + end + hook.Remove("PlayerBindPress", "wire_hologram_scale_setup") + end) + + function ENT:SetupClipping() + if next(self.clips) then + self.oldClipState = render.EnableClipping(true) + + for _, clip in pairs(self.clips) do + if clip.enabled and clip.normal and clip.origin then + local norm = clip.normal + local origin = clip.origin + + if clip.localentid then + local localent = Entity(clip.localentid) + if localent:IsValid() then + norm = localent:LocalToWorld(norm) - localent:GetPos() + origin = localent:LocalToWorld(origin) + end + end + + render.PushCustomClipPlane(norm, norm:Dot(origin)) + end + end + end + end + + function ENT:FinishClipping() + if next(self.clips) then + for _, clip in pairs(self.clips) do + render.PopCustomClipPlane() + end + + render.EnableClipping(self.oldClipState) + end + end + + function ENT:Draw() + if self.blocked or self.notvisible then return end + + if self:GetColor().a ~= 255 then + self.RenderGroup = RENDERGROUP_BOTH + else + self.RenderGroup = RENDERGROUP_OPAQUE + end + + self:SetupClipping() + + if self:GetNWBool("disable_shading") then + render.SuppressEngineLighting(true) + self:DrawModel() + render.SuppressEngineLighting(false) + else + self:DrawModel() + end + + self:FinishClipping() + end + + -- ----------------------------------------------------------------------------- + + function ENT:DoClip() + local eidx = self:EntIndex() + + if clip_buffer[eidx] ~= nil then + table.Merge(self.clips, clip_buffer[eidx]) + clip_buffer[eidx] = nil + end + end + + local function CheckClip(eidx, cidx) + clip_buffer[eidx] = clip_buffer[eidx] or {} + clip_buffer[eidx][cidx] = clip_buffer[eidx][cidx] or {} + + return clip_buffer[eidx][cidx] + end + + local function SetClipEnabled(eidx, cidx, enabled) + local clip = CheckClip(eidx, cidx) + + clip.enabled = enabled + end + + local function SetClip(eidx, cidx, origin, norm, localentid) + local clip = CheckClip(eidx, cidx) + + clip.normal = norm + clip.origin = origin + + if localentid ~= 0 then + clip.localentid = localentid + else + clip.localentid = nil + end + end + + net.Receive("wire_holograms_clip", function(netlen) + local entid = net.ReadUInt(16) + + while entid ~= 0 do + local clipid = net.ReadUInt(4) + + if net.ReadBit() ~= 0 then + SetClipEnabled(entid, clipid, net.ReadBit() ~= 0) + else + SetClip(entid, clipid, net.ReadVector(), Vector(net.ReadFloat(), net.ReadFloat(), net.ReadFloat()), net.ReadUInt(16)) + end + local ent = Entity(entid) + if ent and ent.DoClip then + ent:DoClip() + end + entid = net.ReadUInt(16) + end + end) + + -- ----------------------------------------------------------------------------- + + local function SetScale(entindex, scale) + scale_buffer[entindex] = scale + + local ent = Entity(entindex) + + if ent and ent.DoScale then + ent:DoScale() + end + end + + local function SetBoneScale(entindex, bindex, scale) + if bone_scale_buffer[entindex] == nil then bone_scale_buffer[entindex] = {} end + + if bindex == -1 then + bone_scale_buffer[entindex] = nil + else + bone_scale_buffer[entindex][bindex] = scale + end + + local ent = Entity(entindex) + + if ent and ent.DoScale then + if bindex == -1 then ent.bone_scale = {} end -- reset bone scale + ent:DoScale() + end + end + + function ENT:DoScale() + local eidx = self:EntIndex() + + if scale_buffer[eidx] ~= nil then + self.scale = scale_buffer[eidx] + scale_buffer[eidx] = nil + end + + if bone_scale_buffer[eidx] ~= nil then + for b, s in pairs(bone_scale_buffer[eidx]) do + self.bone_scale[b] = s + end + bone_scale_buffer[eidx] = {} + end + + local scale = self.scale or Vector(1, 1, 1) + + if self.EnableMatrix then + local mat = Matrix() + mat:Scale(Vector(scale.x, scale.y, scale.z)) + self:EnableMatrix("RenderMultiply", mat) + else + -- Some entities, like ragdolls, cannot be resized with EnableMatrix, so lets average the three components to get a float + self:SetModelScale((scale.x + scale.y + scale.z) / 3, 0) + end + + if table.Count( self.bone_scale ) > 0 then + local count = self:GetBoneCount() or -1 + + for i = count, 0, -1 do + local bone_scale = self.bone_scale[i] or Vector(1,1,1) + self:ManipulateBoneScale(i, bone_scale) // Note: Using ManipulateBoneScale currently causes RenderBounds to be reset every frame! + end + end + + local propmax = self:OBBMaxs() + local propmin = self:OBBMins() + self:SetRenderBounds(Vector(scale.x * propmin.x, scale.y * propmin.y, scale.z * propmin.z),Vector(scale.x * propmax.x, scale.y * propmax.y, scale.z * propmax.z)) + end + + net.Receive("wire_holograms_set_scale", function(netlen) + local index = net.ReadUInt(16) + + while index ~= 0 do + SetScale(index, Vector(net.ReadFloat(), net.ReadFloat(), net.ReadFloat())) + index = net.ReadUInt(16) + end + end) + + net.Receive("wire_holograms_set_bone_scale", function(netlen) + local index = net.ReadUInt(16) + local bindex = net.ReadUInt(16) - 1 -- using -1 to get negative -1 for reset + + while index ~= 0 do + SetBoneScale(index, bindex, Vector(net.ReadFloat(), net.ReadFloat(), net.ReadFloat())) + index = net.ReadUInt(16) + bindex = net.ReadUInt(16) - 1 + end + end) + + -- ----------------------------------------------------------------------------- + + function ENT:DoVisible() + local eidx = self:EntIndex() + + if vis_buffer[eidx] ~= nil then + self.notvisible = vis_buffer[eidx] + vis_buffer[eidx] = nil + end + end + + net.Receive("wire_holograms_set_visible", function(netlen) + local index = net.ReadUInt(16) + + while index ~= 0 do + + local ent = Entity(index) + if ent and ent.DoVisible then + ent.notvisible = net.ReadBit() == 0 + else + vis_buffer[index] = net.ReadBit() == 0 + end + + index = net.ReadUInt(16) + end + end) + + -- ----------------------------------------------------------------------------- + + local function SetPlayerColor(entindex, color) + local ent = Entity(entindex) + -- For reference, here's why this works: + -- https://github.com/garrynewman/garrysmod/blob/master/garrysmod/lua/matproxy/player_color.lua + function ent:GetPlayerColor() + return color + end + end + + function ENT:DoPlayerColor() + local eidx = self:EntIndex() + if player_color_buffer[eidx] ~= nil then + SetPlayerColor(eidx, player_color_buffer[eidx]) + player_color_buffer[eidx] = nil + end + + + end + + net.Receive("wire_holograms_set_player_color", function(netlen) + local index = net.ReadUInt(16) + + while index ~= 0 do + local ent = Entity(index) + if IsValid(ent) and ent.DoPlayerColor then + SetPlayerColor(index, net.ReadVector()) + else + player_color_buffer[index] = net.ReadVector() + end + + index = net.ReadUInt(16) + end + end) + + -- ----------------------------------------------------------------------------- + + concommand.Add("wire_holograms_block_client", + function(ply, command, args) + local toblock + for _, ply in ipairs(player.GetAll()) do + if ply:Name() == args[1] then + toblock = ply + break + end + end + if not toblock then error("Player not found") end + + local id = toblock:UserID() + blocked[id] = true + for _, ent in ipairs(ents.FindByClass("gmod_wire_hologram")) do + if ent:GetNWInt("ownerid") == id then + ent.blocked = true + end + end + end, + function() + local names = {} + for _, ply in ipairs(player.GetAll()) do + table.insert(names, "wire_holograms_block_client \"" .. ply:Name() .. "\"") + end + table.sort(names) + return names + end) + + concommand.Add("wire_holograms_unblock_client", + function(ply, command, args) + local toblock + for _, ply in ipairs(player.GetAll()) do + if ply:Name() == args[1] then + toblock = ply + break + end + end + if not toblock then error("Player not found") end + + local id = toblock:UserID() + blocked[id] = nil + for _, ent in ipairs(ents.FindByClass("gmod_wire_hologram")) do + if ent:GetNWInt("ownerid") == id then + ent.blocked = false + end + end + end, + function() + local names = {} + for _, ply in ipairs(player.GetAll()) do + if blocked[ply:UserID()] then + table.insert(names, "wire_holograms_unblock_client \"" .. ply:Name() .. "\"") + end + end + table.sort(names) + return names + end) + + -- Severe lagspikes can detach the source entity from its lua, so we need to reapply things when its reattached + hook.Add("NetworkEntityCreated", "wire_hologram_rescale", function(ent) + if ent.scale and ent.DoScale then + -- ent.scale isn't present on newly created holograms, only old ones that've been hit by a lagspike + ent:DoScale() + ent:DoClip() + ent:DoVisible() + ent:DoPlayerColor() + end + end) + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:SetSolid(SOLID_NONE) + self:SetMoveType(MOVETYPE_NONE) + self:DrawShadow(false) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hologrid.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hologrid.lua new file mode 100644 index 0000000..7907757 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hologrid.lua @@ -0,0 +1,74 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Holo Grid" +ENT.Author = "Chad 'Jinto'" +ENT.WireDebugName = "Holo Grid" + +if CLIENT then return end -- No more client + +function ENT:Initialize( ) + self:PhysicsInit( SOLID_VPHYSICS ); + self:SetMoveType( MOVETYPE_VPHYSICS ); + self:SetSolid( SOLID_VPHYSICS ); + self:SetUseType(SIMPLE_USE) + + self:Setup(false) + + -- create inputs. + self.Inputs = WireLib.CreateSpecialInputs(self, { "UseGPS", "Reference" }, { "NORMAL", "ENTITY" }) + self.reference = self +end + +function ENT:Setup(UseGPS) + if UseGPS then + self.usesgps = true + self:SetNWEntity( "reference", ents.GetByIndex(-1) ) + self:SetOverlayText( "(GPS)" ) + else + self.usesgps = false + self:SetNWEntity( "reference", self.reference ) + self:SetOverlayText( "(Local)" ) + end +end + +function ENT:TriggerInput( inputname, value ) + -- store values. + if inputname == "UseGPS" then + self:Setup(value ~= 0) + elseif inputname == "Reference" then + if IsValid(value) then + self.reference = value + else + self.reference = self + end + self:Setup(self.usesgps) + end +end + +function ENT:Use( activator, caller ) + if caller:IsPlayer() then self:Setup(not self.usesgps) end +end + +duplicator.RegisterEntityClass("gmod_wire_hologrid", WireLib.MakeWireEnt, "Data", "usegps") + + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + info.hologrid_usegps = self.usesgps and 1 or 0 + + if IsValid(self.reference) then + info.reference = self.reference:EntIndex() + else + info.reference = nil + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.reference = GetEntByID(info.reference, self) + self:Setup(info.hologrid_usegps ~= 0) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hoverball.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hoverball.lua new file mode 100644 index 0000000..07d35a4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hoverball.lua @@ -0,0 +1,238 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Hoverball" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Hoverball" + +-- Shared +function ENT:IsOn() return self:GetNWBool( "On", false ) end + +if CLIENT then + -- Clientside GetZTarget + function ENT:GetZTarget() return self:GetNWFloat( "ZTarget", 0 ) end + + local drawhoverballs = CreateConVar( "cl_drawhoverballs", "1" ) + local glowmat = Material( "sprites/light_glow02_add" ) + + function ENT:DrawTranslucent() + if not drawhoverballs:GetBool() then return end + + if self:IsOn() then + local Pos = self:GetPos() + local vDiff = (Pos - LocalPlayer():EyePos()):GetNormalized() + + local color = Color( 70, 180, 255, 255 ) -- Color( 40, 50, 200, 255 ) + render.SetMaterial( glowmat ) + + -- Draw central glow + render.DrawSprite( Pos - vDiff * 2, 22, 22, color ) + + -- Draw glow based on distance from target + local Distance = math.Clamp( math.abs( ( self:GetZTarget() - Pos.z ) * math.sin( RealTime() * 20 ) ) * 0.05, 0, 1 ) + color.r = color.r * Distance + color.g = color.g * Distance + color.b = color.b * Distance + + render.DrawSprite( Pos + vDiff * 4, 48, 48, color ) + render.DrawSprite( Pos + vDiff * 4, 52, 52, color ) + end + end + + return -- No more client +end + +-- Getters/setters +function ENT:GetZTarget() return self.ztarget end +function ENT:SetZTarget( z ) + self.ztarget = z + self:SetNWFloat( "ZTarget", z ) +end +function ENT:GetZVelocity() return self.zvelocity end +function ENT:SetZVelocity( z ) + self.zvelocity = z * FrameTime() * 5000 + + if z ~= 0 then + local phys = self:GetPhysicsObject() + if IsValid( phys ) then + phys:Wake() + end + end +end +function ENT:GetSpeed() return self.speed end +function ENT:SetSpeed( s ) + if not game.SinglePlayer() then + s = math.Clamp( s, 0, 10 ) + end + + self.speed = s +end +function ENT:SetOn( h ) self:SetNWBool( "On", h ) end + +function ENT:GetAirResistance() return self.resistance end +function ENT:SetAirResistance( r ) self.resistance = r end +function ENT:GetSpeed() return self.speed end +function ENT:SetSpeed( s ) self.speed = s end +function ENT:SetStrength( s ) + self.strength = s + local phys = self:GetPhysicsObject() + if ( phys:IsValid() ) then + phys:SetMass( 150 * s ) + end +end +function ENT:GetStrength() return self.strength end + +-- Initialize +function ENT:Initialize() + self:PhysicsInitSphere( 6, "metal_bouncy" ) + self:StartMotionController() + + self:SetZVelocity( 0 ) + self:SetZTarget( self:GetPos().z ) + + self:SetSpeed( 1 ) + self:SetStrength( 1 ) + self:SetAirResistance( 1 ) + self:SetZTarget( self:GetPos().z ) -- reset target position + + self.Inputs = WireLib.CreateInputs( self, { "On", "ZVelocity", "ZTarget" } ) + self.Outputs = WireLib.CreateOutputs( self, { "Position [VECTOR]", "X", "Y", "Z", "Distance" } ) +end + +WireLib.AddInputAlias( "A: ZVelocity", "ZVelocity" ) +WireLib.AddInputAlias( "B: HoverMode", "On" ) +WireLib.AddInputAlias( "C: SetZTarget", "ZTarget" ) +WireLib.AddOutputAlias( "A: Zpos", "Z" ) +WireLib.AddOutputAlias( "B: Xpos", "X" ) +WireLib.AddOutputAlias( "C: Ypos", "Y" ) + +-- Setup +function ENT:Setup(speed, resistance, strength, starton) + self:SetSpeed( speed ) + self:SetStrength( strength ) + self:SetAirResistance( resistance ) + + if starton then self:Enable() else self:Disable() end + self.starton = starton +end + +-- TriggerInput +function ENT:TriggerInput( name, value ) + if name == "On" then + value = value ~= 0 + if value ~= self:IsOn() then + if value then self:Enable() else self:Disable() end + end + elseif name == "ZVelocity" then + self:SetZVelocity( value ) + elseif name == "ZTarget" then + self:SetZTarget( value ) + end +end + +-- Enable/Disable +function ENT:Enable() + self:SetOn( true ) + self:SetStrength( self.strength ) -- Reset weight to user specified value + local phys = self:GetPhysicsObject() + if IsValid( phys ) then + phys:EnableGravity( false ) + phys:Wake() + end +end + +function ENT:Disable() + self:SetOn( false ) + local phys = self:GetPhysicsObject() + if IsValid( phys ) then + phys:SetMass( 1 ) -- less dead weight when off + phys:EnableGravity( true ) + end +end + +function ENT:Think() + BaseClass.Think( self ) + + local on = self:IsOn() and "\nActivated" or "\nDeactivated" + + local pos = self:GetPos() + local Distance = self:GetZTarget() - pos.z + self:SetOverlayText( string.format( "Speed: %i\nResistance: %.2f\nStrength: %.2f\nDistance to ZTarget: %.2f%s", self:GetSpeed(), self:GetAirResistance(), self:GetStrength(), Distance, on ) ) + + WireLib.TriggerOutput( self, "Position", pos ) + WireLib.TriggerOutput( self, "X", pos.x ) + WireLib.TriggerOutput( self, "Y", pos.y ) + WireLib.TriggerOutput( self, "Z", pos.z ) + WireLib.TriggerOutput( self, "Distance", Distance ) +end + +function ENT:PhysicsSimulate( phys, deltatime ) + if (self:IsOn()) then + local Pos = phys:GetPos() + + if ( self:GetZVelocity() != 0 ) then + self:SetZTarget( self:GetZTarget() + (self:GetZVelocity() * deltatime * self:GetSpeed()) ) + end + + phys:Wake() + + local Vel = phys:GetVelocity() + local Distance = self:GetZTarget() - Pos.z + local AirResistance = self:GetAirResistance() + + if ( Distance == 0 ) then return end + + local Exponent = Distance^2 + + if ( Distance < 0 ) then + Exponent = Exponent * -1 + end + + Exponent = Exponent * deltatime * 300 + + local physVel = phys:GetVelocity() + local zVel = physVel.z + + Exponent = Exponent - (zVel * deltatime * 600 * ( AirResistance + 1 ) ) + -- The higher you make this 300 the less it will flop about + -- I'm thinking it should actually be relative to any objects we're connected to + -- Since it seems to flop more and more the heavier the object + + Exponent = math.Clamp( Exponent, -5000, 5000 ) + + local Linear = Vector(0,0,0) + local Angular = Vector(0,0,0) + + Linear.z = Exponent + + if AirResistance > 0 then + Linear.y = physVel.y * -AirResistance + Linear.x = physVel.x * -AirResistance + end + + return Angular, Linear, SIM_GLOBAL_ACCELERATION + else + return SIM_GLOBAL_FORCE + end +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + info.OnState = self:IsOn() and 1 or 0 -- convert to 1/0 for simple old dupe compatibility + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if info and info.OnState and info.OnState == 1 then + self:Enable() + end +end + +function ENT:OnRestore() + self.ZVelocity = 0 + + BaseClass.OnRestore(self) +end + +duplicator.RegisterEntityClass("gmod_wire_hoverball", WireLib.MakeWireEnt, "Data", "speed", "resistance", "strength", "starton") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/cl_init.lua new file mode 100644 index 0000000..116e952 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/cl_init.lua @@ -0,0 +1,284 @@ +include('shared.lua') + +local hudindicators = {} +-- Default HUD x/y +local hudx = 22 +local hudy = 200 +local nextupdate = 0 +-- Text Height Constant +local dtextheight = draw.GetFontHeight("Default") +-- So we don't need to calculate this every frame w/ Percent Bar style +local pbarheight = dtextheight + 16 +-- Y Offset constants +local offsety = {32, 32, 32, 92 + dtextheight, 60 + dtextheight} +-- Texture IDs for Full/Semi-Circle styles +local fullcircletexid = surface.GetTextureID("hudindicator/hi_fullcircle") +local semicircletexid = surface.GetTextureID("hudindicator/hi_semicircle") + +-- Function to check if a registered HUD Indicator: +-- A) belongs to someone other than the calling LocalPlayer() +-- B) is not registered as pod-only +function ENT:ClientCheckRegister() + local ply = LocalPlayer() + local plyuid = ply:UniqueID() + return ply ~= self:GetPlayer() and not self:GetNWBool(plyuid) +end + +-- Used by STool for unregister control panel +-- Only allowed to unregister HUD Indicators that aren't yours +-- and for those that aren't pod-only registers +function HUDIndicator_GetCurrentRegistered() + local registered = {} + for eindex,_ in pairs(hudindicators) do + local ent = ents.GetByIndex(eindex) + if IsValid(ent) then + if (ent:CheckClientRegister()) then + local entry = {} + entry.EIndex = eindex + entry.Description = hudindicators[eindex].Description + table.insert(registered, entry) + end + end + end + + return registered +end + +local function DrawHUDIndicators() + if not IsValid(LocalPlayer()) or not LocalPlayer():Alive() then return end + + local currenty = hudy + + -- Now draw HUD Indicators + for _, index in ipairs(table.MakeSortedKeys(hudindicators)) do + if (hudindicators[index]) then -- Is this necessary? + local ent = Entity(index) + + if IsValid(ent) then + local indinfo = hudindicators[index] + if not indinfo.HideHUD and indinfo.Ready then + local txt = indinfo.FullText or "" + + if (indinfo.Style == 0) then -- Basic + draw.WordBox(8, hudx, currenty, txt, "Default", Color(50, 50, 75, 192), Color(255, 255, 255, 255)) + elseif (indinfo.Style == 1) then -- Gradient + draw.WordBox(8, hudx, currenty, txt, "Default", indinfo.DisplayColor, indinfo.TextColor) + elseif (indinfo.Style == 2) then -- Percent Bar + --surface.SetFont("Default") + --local pbarwidth, h = surface.GetTextSize(txt) + --pbarwidth = math.max(pbarwidth + 16, 100) -- The extra 16 pixels is a "buffer" to make it look better + local startx = hudx + --local w1 = math.floor(indinfo.Factor * pbarwidth) + --local w2 = math.ceil(pbarwidth - w1) + local pbarwidth = indinfo.BoxWidth + local w1 = indinfo.W1 + local w2 = indinfo.W2 + if (indinfo.Factor > 0) then -- Draw only if we have a factor + local BColor = indinfo.BColor + surface.SetDrawColor(BColor.r, BColor.g, BColor.b, 160) + surface.DrawRect(startx, currenty, w1, pbarheight) + startx = w1 + hudx + end + + if (indinfo.Factor < 1) then + local AColor = indinfo.AColor + surface.SetDrawColor(AColor.r, AColor.g, AColor.b, 160) + surface.DrawRect(startx, currenty, w2, pbarheight) + end + + -- Center the description (+ value if applicable) on the percent bar + draw.SimpleText(txt, "Default", hudx + (pbarwidth / 2), currenty + (pbarheight / 2), Color(255, 255, 255, 255), 1, 1) + elseif (indinfo.Style == 3) then -- Full Circle Gauge + draw.RoundedBox(8, hudx, currenty, indinfo.BoxWidth, 88 + dtextheight, Color(50, 50, 75, 192)) + + surface.SetTexture(fullcircletexid) + surface.DrawTexturedRect(hudx + 8, currenty + 8, 64, 64) + + local startx = hudx + 40 + local starty = currenty + 40 + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawLine(startx, starty, startx + indinfo.LineX, starty + indinfo.LineY) + + -- Now the text + draw.SimpleText(txt, "Default", hudx + (indinfo.BoxWidth / 2), currenty + 72 + (pbarheight / 2), Color(255, 255, 255, 255), 1, 1) + elseif (indinfo.Style == 4) then -- Semi-Circle Gauge + draw.RoundedBox(8, hudx, currenty, indinfo.BoxWidth, 56 + dtextheight, Color(50, 50, 75, 192)) + + surface.SetTexture(semicircletexid) + surface.DrawTexturedRect(hudx + 8, currenty + 8, 64, 32) + + local startx = hudx + 40 + local starty = currenty + 39 + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawLine(startx, starty, startx + indinfo.LineX, starty + indinfo.LineY) + + -- Now the text + draw.SimpleText(txt, "Default", hudx + (indinfo.BoxWidth / 2), currenty + 40 + (pbarheight / 2), Color(255, 255, 255, 255), 1, 1) + end + + -- Go to next "line" + currenty = currenty + offsety[indinfo.Style + 1] + end + else + -- Clear this from the table so we don't check again + hudindicators[index] = nil + end + end + end +end +hook.Add("HUDPaint", "DrawHUDIndicators", DrawHUDIndicators) + +local function HUDFormatDescription( eindex ) + -- This is placed here so we don't have to update + -- the description more often than is necessary + local indinfo = hudindicators[eindex] + if (indinfo.ShowValue == 0) then -- No Value + hudindicators[eindex].FullText = indinfo.Description + elseif (indinfo.ShowValue == 1) then -- Percent + hudindicators[eindex].FullText = indinfo.Description.." ("..string.format("%.1f", ((indinfo.Factor or 0) * 100)).."%)" + elseif (indinfo.ShowValue == 2) then -- Value + -- Round to up to 2 places + hudindicators[eindex].FullText = indinfo.Description.." ("..string.format("%g", math.Round((indinfo.Value or 0) * 100) / 100)..")" + end + + -- Do any extra processing for certain HUD styles + -- so we aren't calculating this every frame + surface.SetFont("Default") + local textwidth, _ = surface.GetTextSize(hudindicators[eindex].FullText or "") + + if (indinfo.Style == 1) then -- Gradient + local ent = ents.GetByIndex(eindex) + if IsValid(ent) then + local c = ent:GetColor() + c.a = 160 + hudindicators[eindex].DisplayColor = c + + local textcolor = Color(255, 255, 255, 255) + if (c.r >= 192 and c.g >= 192 and c.b >= 192) then + -- Draw dark text for very bright Indicator colors + textcolor = Color(32, 32, 32, 255) + end + + hudindicators[eindex].TextColor = textcolor + end + elseif (indinfo.Style == 2) then -- Percent Bar + local pbarwidth = math.max(textwidth + 16, 100) -- The extra 16 pixels is a "buffer" to make it look better + hudindicators[eindex].BoxWidth = pbarwidth + hudindicators[eindex].W1 = math.floor((indinfo.Factor or 0) * pbarwidth) + hudindicators[eindex].W2 = math.ceil(pbarwidth - hudindicators[eindex].W1) + elseif (indinfo.Style == 3) then -- Full Circle Gauge + local ang = math.rad(math.fmod((indinfo.Factor or 0) * 360 + (indinfo.FullCircleAngle or 0), 360)) + hudindicators[eindex].LineX = math.cos(ang) * 32 + hudindicators[eindex].LineY = math.sin(ang) * 32 + hudindicators[eindex].BoxWidth = math.max(textwidth + 16, 80) + elseif (indinfo.Style == 4) then -- Semi-Circle Gauge + local ang = math.rad((indinfo.Factor or 0) * 180 + 180) + hudindicators[eindex].LineX = math.cos(ang) * 32 + hudindicators[eindex].LineY = math.sin(ang) * 32 + hudindicators[eindex].BoxWidth = math.max(textwidth + 16, 80) + end +end + +-- Function to ensure that the respective table index is created before any elements are added or modified +-- The HUDIndicatorRegister umsg is *supposed* to arrive (and be processed) before all the others, +-- but for some reason (probably net lag or whatever) it isn't (TheApathetic) +local function CheckHITableElement(eindex) + if not hudindicators[eindex] then + hudindicators[eindex] = {} + end +end + +-- UserMessage stuff +local function HUDIndicatorRegister( um ) + local eindex = um:ReadShort() + CheckHITableElement(eindex) + + hudindicators[eindex].Description = um:ReadString() + hudindicators[eindex].ShowValue = um:ReadShort() + local tempstyle = um:ReadShort() + if hudindicators[eindex].Style ~= tempstyle then + hudindicators[eindex].Ready = false -- Make sure that everything's ready first before drawing + end + hudindicators[eindex].Style = tempstyle + + if not hudindicators[eindex].Factor then -- First-time register + hudindicators[eindex].Factor = 0 + hudindicators[eindex].Value = 0 + hudindicators[eindex].HideHUD = false + hudindicators[eindex].BoxWidth = 100 + end + HUDFormatDescription( eindex ) +end +usermessage.Hook("HUDIndicatorRegister", HUDIndicatorRegister) + +local function HUDIndicatorUnRegister( um ) + local eindex = um:ReadShort() + hudindicators[eindex] = nil +end +usermessage.Hook("HUDIndicatorUnRegister", HUDIndicatorUnRegister) + +local function HUDIndicatorFactor( um ) + local eindex = um:ReadShort() + CheckHITableElement(eindex) + + hudindicators[eindex].Factor = um:ReadFloat() + hudindicators[eindex].Value = um:ReadFloat() + HUDFormatDescription( eindex ) +end +usermessage.Hook("HUDIndicatorFactor", HUDIndicatorFactor) + +local function HUDIndicatorHideHUD( um ) + local eindex = um:ReadShort() + CheckHITableElement(eindex) + + hudindicators[eindex].HideHUD = um:ReadBool() +end +usermessage.Hook("HUDIndicatorHideHUD", HUDIndicatorHideHUD) + +local function HUDIndicatorStylePercent( um ) + local eindex = um:ReadShort() + local ainfo = string.Explode("|", um:ReadString()) + local binfo = string.Explode("|", um:ReadString()) + CheckHITableElement(eindex) + + hudindicators[eindex].AColor = { r = ainfo[1], g = ainfo[2], b = ainfo[3]} + hudindicators[eindex].BColor = { r = binfo[1], g = binfo[2], b = binfo[3]} +end +usermessage.Hook("HUDIndicatorStylePercent", HUDIndicatorStylePercent) + +local function HUDIndicatorStyleFullCircle( um ) + local eindex = um:ReadShort() + CheckHITableElement(eindex) + + hudindicators[eindex].FullCircleAngle = um:ReadFloat() + HUDFormatDescription( eindex ) -- So the gauge updates with FullCircleAngle factored in +end +usermessage.Hook("HUDIndicatorStyleFullCircle", HUDIndicatorStyleFullCircle) + +-- Check for updates every 1/5 seconds +local function HUDIndicatorCheck() + if (CurTime() < nextupdate) then return end + + nextupdate = CurTime() + 0.20 + -- Keep x/y within range (the 50 and 100 are arbitrary and may change) + hudx = math.Clamp(GetConVarNumber("wire_hudindicator_hudx") or 22, 0, ScrW() - 50) + hudy = math.Clamp(GetConVarNumber("wire_hudindicator_hudy") or 200, 0, ScrH() - 100) + + -- Now check readiness + for eindex,indinfo in pairs(hudindicators) do + if not indinfo.Ready then + if (indinfo.Style == 0) then -- Basic + hudindicators[eindex].Ready = true -- Don't need to do any additional checks + elseif (indinfo.Style == 1) then -- Gradient + hudindicators[eindex].Ready = (indinfo.DisplayColor and indinfo.TextColor) + elseif (indinfo.Style == 2) then -- Percent Bar + hudindicators[eindex].Ready = (indinfo.BoxWidth and indinfo.W1 and indinfo.W2 and indinfo.AColor and indinfo.BColor) + elseif (indinfo.Style == 3) then -- Full Circle Gauge + hudindicators[eindex].Ready = (indinfo.BoxWidth and indinfo.LineX and indinfo.LineY and indinfo.FullCircleAngle) + elseif (indinfo.Style == 4) then -- Semi-Circle Gauge + hudindicators[eindex].Ready = (indinfo.BoxWidth and indinfo.LineX and indinfo.LineY) + end + end + end +end +hook.Add("Think", "WireHUDIndicatorCVarCheck", HUDIndicatorCheck) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/init.lua new file mode 100644 index 0000000..3e7127b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/init.lua @@ -0,0 +1,385 @@ + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) + +include('shared.lua') + +DEFINE_BASECLASS("base_wire_entity") + +ENT.WireDebugName = "HUD Indicator" + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.A = 0 + self.AR = 0 + self.AG = 0 + self.AB = 0 + self.AA = 0 + self.B = 0 + self.BR = 0 + self.BG = 0 + self.BB = 0 + self.BA = 0 + + -- List of players who have hooked this indicator + self.RegisteredPlayers = {} + self.PrefixText = "(Hud) Color = " + + self.Inputs = Wire_CreateInputs(self, { "A", "HideHUD" }) +end + +function ENT:Setup(a, ar, ag, ab, aa, b, br, bg, bb, ba, material, showinhud, huddesc, hudaddname, hudshowvalue, hudstyle, allowhook, fullcircleangle) + self.A = a or 0 + self.AR = ar or 255 + self.AG = ag or 0 + self.AB = ab or 0 + self.AA = aa or 255 + self.B = b or 1 + self.BR = br or 0 + self.BG = bg or 255 + self.BB = bb or 0 + self.BA = ba or 255 + self:SetMaterial(material) + + local ttable = { + a = a, + ar = ar, + ag = ag, + ab = ab, + aa = aa, + b = b, + br = br, + bg = bg, + bb = bb, + ba = ba, + material = material, + showinhud = showinhud, + huddesc = huddesc, + hudaddname = hudaddname, + hudshowvalue = hudshowvalue, + hudstyle = hudstyle, + allowhook = allowhook, + fullcircleangle = fullcircleangle + } + table.Merge(self:GetTable(), ttable ) + + self:HUDSetup(showinhud, huddesc, hudaddname, hudshowvalue, hudstyle, allowhook, fullcircleangle) +end + +-- For HUD Indicators +function ENT:HUDSetup(showinhud, huddesc, hudaddname, hudshowvalue, hudstyle, allowhook, fullcircleangle) + local ply = self:GetPlayer() + -- If user updates with the STool to take indicator off of HUD + if not showinhud and self.ShowInHUD then + self:UnRegisterPlayer(ply) + + -- Adjust inputs back to normal + --Wire_AdjustInputs(self, { "A" }) + elseif (showinhud) then + -- Basic style is useless without a value + -- to show so set a default if necessary + if hudstyle == 0 and hudshowvalue == 0 then + hudshowvalue = 1 + end + + if not self:CheckRegister(ply) then + -- First-time register + -- Updating this player is handled further down + self:RegisterPlayer(ply, true) + end + + -- Add name if desired + if (hudaddname) then + self:SetNWString("WireName", huddesc) + elseif (self:GetNWString("WireName") == huddesc) then + -- Only remove it if the HUD Description was there + -- because there might be another name on it + self:SetNWString("WireName", "") + end + + -- Adjust inputs accordingly + --[[ if (!self.Inputs.HideHUD) then + Wire_AdjustInputs(self, { "A", "HideHUD" }) + self:TriggerInput("HideHUD", 0) + self.PrevHideHUD = false + end ]] + end + + self.ShowInHUD = showinhud + self.HUDDesc = huddesc + self.HUDAddName = hudaddname + self.HUDShowValue = hudshowvalue + self.HUDStyle = hudstyle + self.AllowHook = allowhook + self.FullCircleAngle = fullcircleangle + + -- To tell if you can hook a HUD Indicator at a glance + if (allowhook) then + self.PrefixText = "(Hud) Color = " + else + self.PrefixText = "(Hud - Locked) Color = " + end + + -- Update all registered players with this info + for _, v in pairs(self.RegisteredPlayers) do + self:RegisterPlayer(v.ply, v.hookhidehud) + end + + -- Only trigger this input on the + -- first time that Setup() is called + if not self.HasBeenSetup then + self:TriggerInput("A", self.A) + self:TriggerInput("HideHUD", 0) + self.PrevHideHUD = false + self.HasBeenSetup = true + end +end + +-- This is called from RegisterPlayer to send any style-specific info +function ENT:SetupHUDStyle(hudstyle, rplayer) + -- 0 (Basic) and 1 (Gradient) don't require any extra info + local pl = rplayer or self:GetPlayer() + -- Allow for hooked players + --if (rplayer) then pl = rplayer end + + if (hudstyle == 2) then -- Percent Bar + -- Send as string (there should be a way to send colors) + local ainfo = self.AR.."|"..self.AG.."|"..self.AB + local binfo = self.BR.."|"..self.BG.."|"..self.BB + umsg.Start("HUDIndicatorStylePercent", pl) + umsg.Short(self:EntIndex()) + umsg.String(ainfo) + umsg.String(binfo) + umsg.End() + elseif (hudstyle == 3) then -- Full Circle Gauge + umsg.Start("HUDIndicatorStyleFullCircle", pl) + umsg.Short(self:EntIndex()) + umsg.Float(self.FullCircleAngle) + umsg.End() + end +end + +-- Hook this player to the HUD Indicator +function ENT:RegisterPlayer(ply, hookhidehud, podonly) + local plyuid = ply:UniqueID() + local eindex = self:EntIndex() + + -- If player is already registered, this will send an update + -- The podonly is used for players who are registered only because they are in a linked pod + if not self.RegisteredPlayers[plyuid] then + self.RegisteredPlayers[plyuid] = { ply = ply, hookhidehud = hookhidehud, podonly = podonly } + -- This is used to check for pod-only status in ClientCheckRegister() + self:SetNWBool( plyuid, util.tobool(podonly) ) + end + + umsg.Start("HUDIndicatorRegister", ply) + umsg.Short(eindex) + umsg.String(self.HUDDesc or "") + umsg.Short(self.HUDShowValue) + umsg.Short(self.HUDStyle) + umsg.End() + self:SetupHUDStyle(self.HUDStyle, ply) + + -- Trigger inputs to fully add this player to the list + -- Force factor to update + self.PrevOutput = nil + self:TriggerInput("A", self.Inputs.A.Value) + if (hookhidehud) then + self:TriggerInput("HideHUD", self.Inputs.HideHUD.Value) + end +end + +function ENT:UnRegisterPlayer(ply) + umsg.Start("HUDIndicatorUnRegister", ply) + umsg.Short(self:EntIndex()) + umsg.End() + self.RegisteredPlayers[ply:UniqueID()] = nil +end + +-- Is this player registered? +function ENT:CheckRegister(ply) + return self.RegisteredPlayers[ply:UniqueID()] ~= nil +end + +-- Is this player registered only because he is in a linked pod? +function ENT:CheckPodOnly(ply) + if not ply or not ply:IsValid() then return false end + local plyuid = ply:UniqueID() + return self.RegisteredPlayers[plyuid] ~= nil and self.RegisteredPlayers[plyuid].podonly +end + +function ENT:TriggerInput(iname, value) + if (iname == "A") then + local factor = math.Clamp((value-self.A)/(self.B-self.A), 0, 1) + self:ShowOutput(factor, value) + + local r = math.Clamp((self.BR-self.AR)*factor+self.AR, 0, 255) + local g = math.Clamp((self.BG-self.AG)*factor+self.AG, 0, 255) + local b = math.Clamp((self.BB-self.AB)*factor+self.AB, 0, 255) + local a = math.Clamp((self.BA-self.AA)*factor+self.AA, 0, 255) + self:SetColor(Color(r, g, b, a)) + elseif (iname == "HideHUD") then + if (self.PrevHideHUD == (value > 0)) then return end + + self.PrevHideHUD = (value > 0) + -- Value has updated, so send information + self:SendHUDInfo(self.PrevHideHUD) + end +end + +function ENT:ShowOutput(factor, value) + if (factor ~= self.PrevOutput) then + self:SetOverlayText( self.PrefixText .. string.format("%.1f", (factor * 100)) .. "%" ) + self.PrevOutput = factor + + local rf = RecipientFilter() + local pl = self:GetPlayer() + + -- RecipientFilter will contain all registered players + for index,rplayer in pairs(self.RegisteredPlayers) do + if (rplayer.ply and rplayer.ply:IsValid()) then + if rplayer.ply ~= pl or (self.ShowInHUD or self.PodPly == pl) then + rf:AddPlayer(rplayer.ply) + end + else + self.RegisteredPlayers[index] = nil + end + end + + umsg.Start("HUDIndicatorFactor", rf) + umsg.Short(self:EntIndex()) + -- Send both to ensure that all styles work properly + umsg.Float(factor) + umsg.Float(value) + umsg.End() + end +end + +function ENT:SendHUDInfo(hidehud) + -- Sends information to player + local pl = self:GetPlayer() + + for index,rplayer in pairs(self.RegisteredPlayers) do + if (rplayer.ply) then + if rplayer.ply ~= pl or (self.ShowInHUD or self.PodPly == pl) then + umsg.Start("HUDIndicatorHideHUD", rplayer.ply) + umsg.Short(self:EntIndex()) + -- Check player's preference + if (rplayer.hookhidehud) then + umsg.Bool(hidehud) + else + umsg.Bool(false) + end + umsg.End() + end + else + self.RegisteredPlayers[index] = nil + end + end +end + +-- Despite everything being named "pod", any vehicle will work +function ENT:LinkVehicle(pod) + if not IsValid(pod) or not string.find(pod:GetClass(), "prop_vehicle_") then return false end + + local ply = nil + -- Check if a player is in pod first + for _, v in pairs(player.GetAll()) do + if (v:GetVehicle() == pod) then + ply = v + break + end + end + + if ply and not self:CheckRegister(ply) then + -- Register as "only in pod" if not registered before + self:RegisterPlayer(ply, false, true) + + -- Force factor to update + self.PrevOutput = nil + self:TriggerInput("A", self.Inputs.A.Value) + end + self.Pod = pod + self.PodPly = ply + + return true +end + +function ENT:UnLinkVehicle() + local ply = self.PodPly + + if ply and self:CheckPodOnly(ply) then + -- Only unregister if player is registered only because he is in a linked pod + self:UnRegisterPlayer(ply) + end + self.Pod = nil + self.PodPly = nil +end + +function ENT:Think() + BaseClass.Think(self) + + if IsValid(self.Pod) then + local ply = nil + + if not IsValid(self.PodPly) or self.PodPly:GetVehicle() ~= self.Pod then + for _, v in pairs(player.GetAll()) do + if (v:GetVehicle() == self.Pod) then + ply = v + break + end + end + else + ply = self.PodPly + end + + -- Has the player changed? + if ply ~= self.PodPly then + if self.PodPly and self:CheckPodOnly(self.PodPly) then -- Don't send umsg if player disconnected or is registered otherwise + self:UnRegisterPlayer(self.PodPly) + end + + self.PodPly = ply + + if self.PodPly and not self:CheckRegister(self.PodPly) then + self:RegisterPlayer(self.PodPly, false, true) + + -- Force factor to update + self.PrevOutput = nil + self:TriggerInput("A", self.Inputs.A.Value) + end + end + else + -- If we deleted this pod and there was a player in it + if self.PodPly and self:CheckPodOnly(self.PodPly) then + self:UnRegisterPlayer(self.PodPly) + end + self.PodPly = nil + end + + self:NextThink(CurTime() + 0.1) + return true +end + +-- Advanced Duplicator Support +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if (self.Pod) and (self.Pod:IsValid()) then + info.pod = self.Pod:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.Pod = GetEntByID(info.pod) +end + +duplicator.RegisterEntityClass("gmod_wire_hudindicator", WireLib.MakeWireEnt, "Data", "a", "ar", "ag", "ab", "aa", "b", "br", + "bg", "bb", "ba", "material", "showinhud", "huddesc", "hudaddname", "hudshowvalue", "hudstyle", "allowhook", "fullcircleangle") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/shared.lua new file mode 100644 index 0000000..d8508ce --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hudindicator/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire HUD Indicator" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hydraulic.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hydraulic.lua new file mode 100644 index 0000000..8168789 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_hydraulic.lua @@ -0,0 +1,217 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Hydraulic Controller" +ENT.WireDebugName = "Hydraulic" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + BaseClass.Initialize(self) + self.Inputs = WireLib.CreateInputs( self, { "Length", "In", "Out", "Constant", "Damping" } ) + self.Outputs = WireLib.CreateOutputs( self, { "Length", "Target Length", "Constant", "Damping" } ) + self.TargetLength = 0 + self.current_constant = 0 + self.current_damping = 0 + self.direction = 0 + self.last_time = CurTime() +end + +function ENT:GetWPos( ent, phys, lpos ) + if ent:EntIndex() == 0 then + return lpos + end + + if IsValid( phys ) then + return phys:LocalToWorld( lpos ) + else + return ent:LocalToWorld( lpos ) + end +end + +function ENT:GetDistance() + local CTable = self.constraint:GetTable() + local p1 = self:GetWPos( CTable.Ent1, CTable.Phys1 or CTable.Ent1:GetPhysicsObject(), CTable.LPos1 ) + local p2 = self:GetWPos( CTable.Ent2, CTable.Phys2 or CTable.Ent2:GetPhysicsObject(), CTable.LPos2 ) + return p1:Distance(p2) +end + +function ENT:Think() + BaseClass.Think( self ) + if not IsValid(self.constraint) then return end + + local deltaTime = CurTime() - self.last_time + self.last_time = CurTime() + + if self.direction ~= 0 then + self:SetLength(math.max(self.TargetLength + (self.constraint:GetTable().speed * self.direction * deltaTime), 1)) + end + + self:UpdateOutputs( true ) + self:NextThink(CurTime()+0.05) + return true +end + +function ENT:UpdateOutputs( OnlyLength ) + local curLength = self:GetDistance() + WireLib.TriggerOutput( self, "Length", curLength ) + WireLib.TriggerOutput( self, "Target Length", self.TargetLength ) + if not OnlyLength then + WireLib.TriggerOutput( self, "Length", self.TargetLength ) + WireLib.TriggerOutput( self, "Constant", self.current_constant ) + WireLib.TriggerOutput( self, "Damping", self.current_damping ) + end + + self:SetOverlayText(string.format("%s length: %.2f\nConstant: %i\nDamping: %i", (self.constraint.stretchonly and "Winch" or "Hydraulic"), curLength, self.current_constant, self.current_damping)) +end + +function ENT:SetConstraint( c ) + self.constraint = c + + if self.current_constant ~= 0 or (self.Inputs and self.Inputs.Constant.Src) then + self:TriggerInput("Constant", self.Inputs.Constant.Value) + else + self.current_constant = self.constraint:GetKeyValues().constant + end + + if self.current_damping ~= 0 or (self.Inputs and self.Inputs.Damping.Src) then + self:TriggerInput("Damping", self.Inputs.Damping.Value) + else + self.current_damping = self.constraint:GetKeyValues().damping + end + + self:SetLength(self:GetDistance()) + + self:UpdateOutputs() +end + +function ENT:SetRope( r ) + self.rope = r +end + +function ENT:SetLength(value) + self.TargetLength = value + self.constraint:Fire("SetSpringLength", value, 0) + if IsValid(self.rope) then self.rope:Fire("SetLength", value, 0) end +end + +function ENT:TriggerInput(iname, value) + if not IsValid(self.constraint) then return end + if (iname == "Length") then + self:SetLength(math.max(value,1)) + elseif (iname == "In") then + self.direction = -value + elseif (iname == "Out") then + self.direction = value + elseif (iname == "Constant") then + if value == 0 then + self.current_constant, _ = WireLib.CalcElasticConsts(self.constraint.Ent1, self.constraint.Ent2) + else + self.current_constant = value + end + self.constraint:Fire("SetSpringConstant",self.current_constant) + timer.Simple( 0.1, function() if IsValid(self) then self:UpdateOutputs() end end) -- Needs to be delayed because ent:Fire doesn't update that fast. + elseif (iname == "Damping") then + if value == 0 then + _, self.current_damping = WireLib.CalcElasticConsts(self.constraint.Ent1, self.constraint.Ent2) + else + self.current_damping = value + end + self.constraint:Fire("SetSpringDamping",self.current_damping) + timer.Simple( 0.1, function() if IsValid(self) then self:UpdateOutputs() end end) + end +end + + +-- needed for the constraint to find the controller after being duplicator pasted +local WireHydraulicTracking = {} + +function MakeWireHydraulicController( pl, Pos, Ang, model, MyEntId, const, rope ) + local controller = WireLib.MakeWireEnt(pl, {Class = "gmod_wire_hydraulic", Pos=Pos, Angle=Ang, Model=model}) + if not IsValid(controller) then return end + + if not const then + WireHydraulicTracking[ MyEntId ] = controller + else + controller.MyId = controller:EntIndex() + const.MyCrtl = controller:EntIndex() + controller:SetConstraint( const ) + controller:DeleteOnRemove( const ) + end + + if rope then + controller:SetRope( rope ) + controller:DeleteOnRemove( rope ) + end + + return controller +end +duplicator.RegisterEntityClass("gmod_wire_hydraulic", MakeWireHydraulicController, "Pos", "Ang", "Model", "MyId" ) +duplicator.RegisterEntityClass("gmod_wire_winch_controller", MakeWireHydraulicController, "Pos", "Ang", "Model", "MyId") +scripted_ents.Alias("gmod_wire_winch_controller", "gmod_wire_hydraulic") + +function MakeWireHydraulic( pl, Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, width, material, speed, fixed, stretchonly, MyCrtl ) + if not constraint.CanConstrain(Ent1, Bone1) then return false end + if not constraint.CanConstrain(Ent2, Bone2) then return false end + + local Phys1 = Ent1:GetPhysicsObjectNum( Bone1 ) + local Phys2 = Ent2:GetPhysicsObjectNum( Bone2) + local WPos1 = Phys1:LocalToWorld( LPos1 ) + local WPos2 = Phys2:LocalToWorld( LPos2 ) + + if ( Phys1 == Phys2 ) then return false end + + local constant, dampen = WireLib.CalcElasticConsts(Ent1, Ent2) + + local const, rope = constraint.Elastic( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, constant, dampen, 0, material, width, stretchonly ) + if not const then return nil, rope end + + if fixed == 1 then + local slider = constraint.Slider( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0 ) + slider:SetTable( {} ) + const:DeleteOnRemove( slider ) + end + + local ctable = { + Type = "WireHydraulic", + pl = pl, + Ent1 = Ent1, + Ent2 = Ent2, + Bone1 = Bone1, + Bone2 = Bone2, + LPos1 = LPos1, + LPos2 = LPos2, + width = width, + material = material, + speed = speed, + fixed = fixed, + stretchonly = stretchonly + } + const:SetTable( ctable ) + + if MyCrtl then + local controller = WireHydraulicTracking[ MyCrtl ] + + const.MyCrtl = controller:EntIndex() + controller.MyId = controller:EntIndex() + + controller:SetConstraint( const ) + controller:DeleteOnRemove( const ) + if (rope) then + controller:SetRope( rope ) + controller:DeleteOnRemove( rope ) + end + + Ent1:DeleteOnRemove( controller ) + Ent2:DeleteOnRemove( controller ) + const:DeleteOnRemove( controller ) + end + + return const, rope +end +duplicator.RegisterConstraint("WireHydraulic", MakeWireHydraulic, "pl", "Ent1", "Ent2", "Bone1", "Bone2", "LPos1", "LPos2", "width", "material", "speed", "fixed", "stretchonly", "MyCrtl") + +-- Backwards compatibility with old dupes of Winches, which were just Hydraulics with strechonly = true +local function WinchToHydraulic(pl, Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, width, fwd_speed, bwd_speed, material, MyCrtl) + return MakeWireHydraulic(pl, Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, width, material, fwd_speed, 0, true, MyCrtl) +end +duplicator.RegisterConstraint("WireWinch", WinchToHydraulic, "pl", "Ent1", "Ent2", "Bone1", "Bone2", "LPos1", "LPos2", "width", "fwd_speed", "bwd_speed", "material", "MyCrtl") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_igniter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_igniter.lua new file mode 100644 index 0000000..f64072a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_igniter.lua @@ -0,0 +1,70 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Igniter" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Igniter" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self, { "A", "Length", "Extinguish" }) + self.IgniteLength = 10 + self:Setup(false, 2048) +end + +function ENT:Setup(trgply,Range) + self.TargetPlayers = trgply + if Range then self:SetBeamLength(Range) end +end + +function ENT:DoTrace() + local vStart = self:GetPos() + local vForward = self:GetUp() + + local trace = {} + trace.start = vStart + trace.endpos = vStart + (vForward * self:GetBeamLength()) + trace.filter = { self } + local trace = util.TraceLine( trace ) + + local cvarTargetPlayers = GetConVarNumber('sbox_wire_igniters_allowtrgply') > 0 + + if not IsValid(trace.Entity) then return false end + if not gamemode.Call("CanProperty", self:GetPlayer(), "ignite", trace.Entity) then return false end + if (trace.Entity:IsPlayer() and (not self.TargetPlayers or not cvarTargetPlayers)) then return false end + if (trace.Entity:IsWorld()) then return false end + + return trace.Entity +end + +function ENT:TriggerInput(iname, value) + if (iname == "A") then + if (value ~= 0) then + local target = self:DoTrace() + + if target == false then return false end + + target:Extinguish() + target:Ignite( self.IgniteLength, 0 ) + end + elseif (iname == "Length") then + self.IgniteLength = math.min(value,GetConVarNumber("sbox_wire_igniters_maxlen")) + elseif (iname == "Extinguish") then + if value ~= 0 then + local target = self:DoTrace() + + if target == false then return false end + + target:Extinguish() + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_igniter", WireLib.MakeWireEnt, "Data", "TargetPlayers", "Range") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_indicator.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_indicator.lua new file mode 100644 index 0000000..c698df5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_indicator.lua @@ -0,0 +1,229 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Indicator" +ENT.WireDebugName = "Indicator" +ENT.RenderGroup = RENDERGROUP_BOTH + +-- Helper functions +function ENT:GetFactorFromValue( value ) + return math.Clamp((value-self.a)/(self.b-self.a), 0, 1) +end + +function ENT:GetColorFromValue( value ) + local factor = self:GetFactorFromValue( value, self ) + local r = math.Clamp((self.br-self.ar)*factor+self.ar, 0, 255) + local g = math.Clamp((self.bg-self.ag)*factor+self.ag, 0, 255) + local b = math.Clamp((self.bb-self.ab)*factor+self.ab, 0, 255) + local a = math.Clamp((self.ba-self.aa)*factor+self.aa, 0, 255) + return Color(r,g,b,a), factor +end + +if CLIENT then + local color_box_size = 64 + function ENT:GetWorldTipBodySize() + return 400,80 + end + + local white = Color(255,255,255,255) + local black = Color(0,0,0,255) + + local function drawSquare( x,y,w,h ) + surface.SetDrawColor( black ) + surface.DrawLine( x, y, x + w, y ) + surface.DrawLine( x + w, y, x + w, y + h ) + surface.DrawLine( x + w, y + h, x, y + h ) + surface.DrawLine( x, y + h, x, y ) + end + + local function drawColorSlider( x, y, w, h, self ) + if self.a == self.b then -- no infinite loops! + draw.DrawText( "Can't draw color bar because A == B", + "GModWorldtip", x + w / 2, y + h / 2, white, TEXT_ALIGN_CENTER ) + + return + end + + local diff = self.b - self.a + local len = math.abs(self.b) - math.abs(self.a) + local step = diff / 50 + + local find_selected = nil + + for i=self.a,self.b - step/2, step do + local color, factor = self:GetColorFromValue( i ) + local pos_x = math.floor(x + (factor * w)) + + -- we're not stepping over every single possible value here, + -- so we have to check if we're close-ish to the user's selected value + if not find_selected then + if diff >= 0 and i >= self.value then + find_selected = i + elseif diff < 0 and i < self.value then + find_selected = i + end + end + + surface.SetDrawColor( color ) + surface.DrawRect( pos_x, y, math.ceil(w/50), h ) + end + + -- if the user has set the value to this exactly, then + -- there's a possibility that the above check couldn't detect it + if self.value == self.b then find_selected = self.b end + + -- draw the outline of the color slider + drawSquare( x,y,w,h ) + + -- draw the small box showing the current selected color + if find_selected then + find_selected = math.Clamp(find_selected,math.min(self.a,self.b)+step/2,math.max(self.a,self.b)-step/2) + local factor = self:GetFactorFromValue( find_selected ) + local pos_x = math.floor(x + (factor * w)) + drawSquare(pos_x - step / 2,y-h*0.15,math.ceil(w/50),h*1.4) + end + end + + function ENT:DrawWorldTipBody( pos ) + -- Get colors + local data = self:GetOverlayData() + + -- Merge the data onto the entity itself. + -- This allows us to use the same references as serverside + for k,v in pairs( data ) do self[k] = v end + + -- A + local color_text = string.format("A color: %d,%d,%d,%d\nA value: %d",self.ar,self.ag,self.ab,self.aa,self.a) + draw.DrawText( color_text, "GModWorldtip", pos.min.x + pos.edgesize, pos.min.y + pos.edgesize, white, TEXT_ALIGN_LEFT ) + + -- B + local color_text = string.format("B color: %d,%d,%d,%d\nB value: %d",self.br,self.bg,self.bb,self.ba,self.b) + draw.DrawText( color_text, "GModWorldtip", pos.max.x - pos.edgesize, pos.min.y + pos.edgesize, white, TEXT_ALIGN_RIGHT ) + + -- Percent + local factor = math.Clamp((self.value-self.a)/(self.b-self.a), 0, 1) + local color_text = string.format("%s (%d%%)",math.Round(self.value,2),factor*100) + local w,h = surface.GetTextSize(color_text) + draw.DrawText( color_text, "GModWorldtip", pos.center.x + 40, pos.min.y + pos.edgesize + h, white, TEXT_ALIGN_RIGHT ) + + -- Slider + drawColorSlider( pos.min.x + pos.edgesize, pos.min.y + pos.edgesize + 46, 401, 16, self ) + end + + return +end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + -- Preferably we would switch to storing these as colors, + -- but it's not really worth breaking all old dupes + self.a = 0 + self.ar = 0 + self.ag = 0 + self.ab = 0 + self.aa = 0 + self.b = 0 + self.br = 0 + self.bg = 0 + self.bb = 0 + self.ba = 0 + + self.Inputs = WireLib.CreateInputs(self, { "A" }) + self:SetRenderMode( RENDERMODE_TRANSALPHA ) +end + +function ENT:Setup(a, ar, ag, ab, aa, b, br, bg, bb, ba) + self.a = a or 0 + self.ar = ar or 255 + self.ag = ag or 0 + self.ab = ab or 0 + self.aa = aa or 255 + self.b = b or 1 + self.br = br or 0 + self.bg = bg or 255 + self.bb = bb or 0 + self.ba = ba or 255 + + self:TriggerInput("A", self.a) +end + +function ENT:TriggerInput(iname, value) + if iname == "A" then + self:ShowOutput(value) + local color = self:GetColorFromValue( value ) + self:SetColor(color) + end +end + +function ENT:ShowOutput(value) + self:SetOverlayData({ + a = self.a, + b = self.b, + ar = self.ar, + ag = self.ag, + ab = self.ab, + aa = self.aa, + br = self.br, + bg = self.bg, + bb = self.bb, + ba = self.ba, + value = value + }) +end + +duplicator.RegisterEntityClass("gmod_wire_indicator", WireLib.MakeWireEnt, "Data", "a", "ar", "ag", "ab", "aa", "b", "br", "bg", "bb", "ba") + +function MakeWire7Seg( pl, Pos, Ang, Model, a, ar, ag, ab, aa, b, br, bg, bb, ba) + if IsValid(pl) and not pl:CheckLimit( "wire_indicators" ) then return false end + + local function MakeWireIndicator(prototype, scale) + local name, angOffset, posOffset = unpack(prototype) + posOffset = Vector(0, posOffset.x, -posOffset.y) + local Pos, Ang = LocalToWorld(posOffset * scale, Angle(), Pos, Ang), Ang + angOffset + local ent = WireLib.MakeWireEnt(pl, + { Class = "gmod_wire_indicator", + Pos = Pos, Angle = Ang, + Model = Model, frozen = frozen, nocollide = nocollide }, + a, ar, ag, ab, aa, b, br, bg, bb, ba ) + if IsValid(ent) then + ent:SetNWString("WireName", name) + duplicator.StoreEntityModifier( ent, "WireName", { name = name } ) + end + return ent + end + + + local prototypes = { + { "G", Angle(0, 0, 0), Vector(0, 0) }, + { "A", Angle(0, 0, 0), Vector(0, 2) }, + { "B", Angle(0, 0, 90), Vector(1, 1) }, + { "C", Angle(0, 0, 90), Vector(1, -1) }, + { "D", Angle(0, 0, 0), Vector(0, -2) }, + { "E", Angle(0, 0, 90), Vector(-1, -1) }, + { "F", Angle(0, 0, 90), Vector(-1, 1) } + } + + local wire_indicators = {} + wire_indicators[1] = MakeWireIndicator( prototypes[1], 0 ) + + -- get the scale (half the long side of the indicator) from the first one + local scale = wire_indicators[1]:OBBMaxs().y + + for i = 2, 7 do + wire_indicators[i] = MakeWireIndicator( prototypes[i], scale ) + if not IsValid( wire_indicators[i] ) then break end + + for y = 1, i-1 do + const = constraint.Weld( wire_indicators[i], wire_indicators[y], 0, 0, 0, true, true ) + end + wire_indicators[i - 1]:DeleteOnRemove( wire_indicators[i] ) --when one is removed, all are. a linked chain + end + + if wire_indicators[7] then + wire_indicators[7]:DeleteOnRemove( wire_indicators[1] ) --loops chain back to first + end + + return wire_indicators +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_input.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_input.lua new file mode 100644 index 0000000..0547a30 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_input.lua @@ -0,0 +1,102 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Numpad Input" +ENT.WireDebugName = "Numpad Input" + +if CLIENT then return end -- No more client + +//As input.GetKeyName is Clientside only, the following list is generated by: +//lua_run_cl local a="{\"" for k=1, KEY_LAST do a=a..input.GetKeyName(k).."\",\"" end print(string.sub(a,0,-3).."}") +local keylist = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","KP_INS","KP_END","KP_DOWNARROW","KP_PGDN","KP_LEFTARROW","KP_5","KP_RIGHTARROW","KP_HOME","KP_UPARROW","KP_PGUP","KP_SLASH","KP_MULTIPLY","KP_MINUS","KP_PLUS","KP_ENTER","KP_DEL","[","]","SEMICOLON","'","`",",",".","/","\\","-","=","ENTER","SPACE","BACKSPACE","TAB","CAPSLOCK","NUMLOCK","ESCAPE","SCROLLLOCK","INS","DEL","HOME","END","PGUP","PGDN","PAUSE","SHIFT","RSHIFT","ALT","RALT","CTRL","RCTRL","LWIN","RWIN","APP","UPARROW","LEFTARROW","DOWNARROW","RIGHTARROW","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","CAPSLOCKTOGGLE","NUMLOCKTOGGLE","SCROLLLOCKTOGGLE"} +local function keyname(keygroup) + return keylist[keygroup] or "" +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + // Used to keep track of numpad.OnUp/Down returns + // Fixes bug where player cannot change numpad key (TheApathetic) + self.OnUpImpulse = nil + self.OnDownImpulse = nil + + self.Outputs = Wire_CreateOutputs(self, { "Out" }) +end + +function ENT:Setup(keygroup, toggle, value_off, value_on) + self.keygroup = keygroup + self.toggle = (toggle == 1 || toggle == true) + self.value_off = value_off + self.value_on = value_on + self.Value = value_off + + if (self.OnUpImpulse) then + numpad.Remove(self.OnUpImpulse) + numpad.Remove(self.OnDownImpulse) + end + + local pl = self:GetPlayer() + self.OnDownImpulse = numpad.OnDown( pl, keygroup, "WireInput_On", self, 1 ) + self.OnUpImpulse = numpad.OnUp( pl, keygroup, "WireInput_Off", self, 1 ) + + + self:ShowOutput(self.value_off) + Wire_TriggerOutput(self, "Out", self.value_off) +end + +function ENT:InputActivate( mul ) + if ( self.toggle ) then + return self:Switch( !self.On, mul ) + end + + return self:Switch( true, mul ) +end + +function ENT:InputDeactivate( mul ) + if ( self.toggle ) then return true end + + return self:Switch( false, mul ) +end + +function ENT:Switch( on, mul ) + if (!self:IsValid()) then return false end + + self.On = on + + if (on) then + self:ShowOutput(self.value_on) + self.Value = self.value_on + else + self:ShowOutput(self.value_off) + self.Value = self.value_off + end + + Wire_TriggerOutput(self, "Out", self.Value) + + return true +end + +function ENT:ShowOutput(value) + self:SetOverlayText( "("..keyname(self.keygroup)..")\n(" .. tostring(self.value_off) .. " - " .. tostring(self.value_on) .. ") = " .. tostring(value) ) +end + +local function On( pl, ent, mul ) + if (!ent:IsValid()) then return false end + pl = player.GetBySteamID(pl) + if not gamemode.Call("PlayerUse", pl, ent) then return end + return ent:InputActivate( mul ) +end + +local function Off( pl, ent, mul ) + if (!ent:IsValid()) then return false end + pl = player.GetBySteamID(pl) + if not gamemode.Call("PlayerUse", pl, ent) then return end + return ent:InputDeactivate( mul ) +end + +numpad.Register( "WireInput_On", On ) +numpad.Register( "WireInput_Off", Off ) + +duplicator.RegisterEntityClass("gmod_wire_input", WireLib.MakeWireEnt, "Data", "keygroup", "toggle", "value_off", "value_on") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/cl_init.lua new file mode 100644 index 0000000..0dd8d57 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/cl_init.lua @@ -0,0 +1,61 @@ +include('shared.lua') +include("remap.lua") -- For stools/keyboard.lua's layout selector + +net.Receive("wire_keyboard_blockinput", function(netlen) + if net.ReadBit() ~= 0 then + hook.Add("PlayerBindPress", "wire_keyboard_blockinput", function(ply, bind, pressed) + -- return true for all keys except the mouse, to block keyboard actions while typing + if bind == "+attack" then return nil end + if bind == "+attack2" then return nil end + + return true + end) + else + hook.Remove("PlayerBindPress", "wire_keyboard_blockinput") + end +end) + +local panel + +local function hideMessage() + if not panel then return end + + panel:Remove() + panel = nil +end + +net.Receive("wire_keyboard_activatemessage", function(netlen) + local on = net.ReadBit() ~= 0 + + hideMessage() + + if not on then return end + + local pod = net.ReadBit() ~= 0 + + local leaveKey = LocalPlayer():GetInfoNum("wire_keyboard_leavekey", KEY_LALT) + local leaveKeyName = string.upper(input.GetKeyName(leaveKey)) + + local text + if pod then + text = "This pod is linked to a Wire Keyboard - press " .. leaveKeyName .. " to leave." + else + text = "Wire Keyboard turned on - press " .. leaveKeyName .. " to leave." + end + + panel = vgui.Create("DShape") -- DPanel is broken for small sizes + panel:SetColor(Color(0, 0, 0, 192)) + panel:SetType("Rect") + + local label = vgui.Create("DLabel", panel) + label:SetText(text) + label:SizeToContents() + + local padding = 3 + label:SetPos(2 * padding, 2 * padding) + panel:SizeToChildren(true, true) + label:SetPos(padding, padding) + + panel:CenterHorizontal() + panel:CenterVertical(0.95) +end) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/init.lua new file mode 100644 index 0000000..e2b8e84 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/init.lua @@ -0,0 +1,437 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +AddCSLuaFile("remap.lua") + +include('shared.lua') +include('remap.lua') + +DEFINE_BASECLASS("base_wire_entity") + +ENT.WireDebugName = "Wired Keyboard" + +local All_Enums = {} -- table containing key -> key enum conversion + +-- Add a few common keys +for i = 48, 57 do -- 0 -> 9 + All_Enums[i] = _G["KEY_" .. string.char(i)] +end +for i = 65, 90 do -- A -> Z + All_Enums[i] = _G["KEY_" .. string.upper(string.char(i))] +end +for i = 97, 122 do -- a -> z + All_Enums[i] = _G["KEY_" .. string.upper(string.char(i))] +end + +local unprintable_chars = {} +unprintable_chars[0] = true +for i=17,20 do unprintable_chars[i] = true end -- arrow keys +for i=127,177 do unprintable_chars[i] = true end -- backspace, numpad, ctrl, alt, shift, break, F1-F12, scroll/num/caps lock, and more + +-- These keys output a numeric code that's different to their ASCII code - they +-- are numpad keys, and using a different code lets contraptions differentiate +-- between eg. pressing 0 on the number row and 0 on the numpad. The codes are +-- defined in remap.lua. +local convertable_chars = { + [128] = "0", + [129] = "1", + [130] = "2", + [131] = "3", + [132] = "4", + [133] = "5", + [134] = "6", + [135] = "7", + [136] = "8", + [137] = "9", + [138] = "/", + [139] = "*", + [140] = "-", + [141] = "+", + [142] = "\n", + [143] = ".", +} + +local function getPrintableChar( key ) + if convertable_chars[key] then return convertable_chars[key] end + if unprintable_chars[key] then return "" end + if key == 13 then key = 10 end -- convert newline '13' into newlne '10' to make it work properly + return utf8.char(key) +end + +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(ONOFF_USE) + + self.Inputs = WireLib.CreateInputs(self, { "Kick", "Reset Output String" }) + self.Outputs = WireLib.CreateOutputs(self, { "Memory", "Output [STRING]", "OutputChar [STRING]", "ActiveKeys [ARRAY]", "User [ENTITY]", "InUse" }) + + self.ActiveKeys = {} -- table containing all currently active keys, used to see when keys are pressed/released + self.Buffer = {} -- array containing all currently active keys, value is ascii + self.BufferLookup = {} -- lookup table mapping enums to buffer positions + self.Buffer[0] = 0 + self.OutputString = "" + + self:TriggerOutputs() +end + +WireLib.AddInputAlias("Kick the bastard out of keyboard", "Kick") + +function ENT:TriggerInput(name, value) + if name == "Kick" then + -- It was kicking at the same time as giving output - added tiny delay fixing that race condition + timer.Simple(0.1, function() + if IsValid(self) then + self.Locked = (value ~= 0) + self:PlayerDetach() + end + end) + elseif name == "Reset Output String" then + self.OutputString = "" + self:TriggerOutputs() + end +end + +function ENT:TriggerOutputs(key) + local str = "" + + -- Output key numerical & char representation + if key ~= nil then + WireLib.TriggerOutput(self, "Memory", key) + WireLib.TriggerOutput(self, "OutputChar", getPrintableChar(key)) + else + WireLib.TriggerOutput(self, "OutputChar", "") + end + + -- Output user + if IsValid( self.ply ) then + WireLib.TriggerOutput(self, "User", self.ply) + WireLib.TriggerOutput(self, "InUse", 1) + str = str .. "In use by: " .. self.ply:Nick() .. "\n" + else + WireLib.TriggerOutput(self, "User", nil) + WireLib.TriggerOutput(self, "InUse", 0) + str = str .. "Not in use\n" + end + + -- Output currently pressed keys + local ActiveKeys_Output, idx = {}, 0 + for key_enum,_ in pairs( self.ActiveKeys ) do + idx = idx + 1 + ActiveKeys_Output[idx] = self:GetRemappedKey(key_enum) + end + WireLib.TriggerOutput(self, "ActiveKeys", ActiveKeys_Output) + + -- Output buffer string + WireLib.TriggerOutput(self, "Output", self.OutputString) + + -- Display options in overlay + str = str .. "Lock player controls: " .. (self.Synchronous and "Yes" or "No") .. "\n" + .. "Automatic buffer clear: " .. (self.AutoBuffer and "Yes" or "No") .. "\n" + .. "Enter key ASCII output: " .. (self.EnterKeyAscii and "10 ('\\n')" or "13 ('\\r')") + + self:SetOverlayText( str ) +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if Address >= 0 and Address < 32 then + return self.Buffer[Address] or 0 + elseif Address >= 32 and Address < 256 then + return self:IsPressedAscii(Address - 32) and 1 or 0 + end + + return 0 +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + if Address == 0 then + self:UnshiftBuffer() -- User wants to remove the first key in the buffer + else + self:RemoveFromBufferByKey(value) + end + + return true +end + +util.AddNetworkString("wire_keyboard_blockinput") +util.AddNetworkString("wire_keyboard_activatemessage") +function ENT:PlayerAttach(ply) + if not IsValid(ply) or IsValid(self.ply) then return end -- If the keyboard is already in use, don't attach the player + + if IsValid(ply.WireKeyboard) then -- If the player is already using a different keyboard + if ply.WireKeyboard == self then return end -- If the keyboard is this keyboard, don't re-attach the player + ply.WireKeyboard:PlayerDetach() -- If it's another keyboard, detach the player from that keyboard first + end + + -- If the keyboard is locked (Kick input is wired to something other than 0), don't attach the player + if self.Locked then return end + + -- Store player + self.ply = ply + + -- Block keyboard input + if self.Synchronous then + net.Start("wire_keyboard_blockinput") + net.WriteBit(true) + net.Send(ply) + end + + net.Start("wire_keyboard_activatemessage") + net.WriteBit(true) + net.WriteBit(IsValid(self.Pod)) + net.Send(ply) + + -- Set the wire keyboard value on the player + ply.WireKeyboard = self + + -- Reset tables + self.BufferLookup = {} + self.ActiveKeys = {} + self.Buffer = {} + self.Buffer[0] = 0 + + self:TriggerOutputs() +end + +function ENT:PlayerDetach() + local ply = self.ply + self.ply = nil + + -- Kick player out of vehicle, if in one + if IsValid(self.Pod) and IsValid(self.Pod:GetDriver()) and self.Pod:GetDriver() == ply then + self.Pod:GetDriver():ExitVehicle() + end + + if IsValid(ply) then + net.Start("wire_keyboard_blockinput") + net.WriteBit(false) + net.Send(ply) + + net.Start("wire_keyboard_activatemessage") + net.WriteBit(false) + net.Send(ply) + + ply.WireKeyboard = nil + end + + self:TriggerOutputs() +end + +function ENT:Use(ply, _, type) + if type ~= USE_OFF then return end + if IsValid(self.Pod) then + ply:ChatPrint("This keyboard is linked to a pod. Please use the pod instead.") + return + end + + self:PlayerAttach(ply) +end + +function ENT:OnRemove() + self:UnlinkEnt() + self:PlayerDetach() + BaseClass.OnRemove(self) +end + +function ENT:LinkEnt(pod) + pod = WireLib.GetClosestRealVehicle(pod,self:GetPos(),self:GetPlayer()) + + if not IsValid(pod) or not pod:IsVehicle() then return false, "Must link to a vehicle" end + if IsValid(self.Pod) then self.Pod.WireKeyboard = nil end + pod.WireKeyboard = self + self.Pod = pod + WireLib.SendMarks(self, {pod}) + return true +end +function ENT:UnlinkEnt() + if IsValid(self.Pod) then + self.Pod.WireKeyboard = nil + end + self.Pod = nil + WireLib.SendMarks(self, {}) + return true +end + +hook.Add("PlayerEnteredVehicle", "Wire_Keyboard_PlayerEnteredVehicle", function(ply, pod) + if IsValid(pod.WireKeyboard) then + pod.WireKeyboard:PlayerAttach(ply) + end +end) + +hook.Add("PlayerLeaveVehicle", "wire_keyboard_PlayerLeaveVehicle", function(ply, pod) + if IsValid(pod.WireKeyboard) and pod.WireKeyboard.ply == ply then + pod.WireKeyboard:PlayerDetach() + end +end) + +function ENT:AppendOutputString(key) + if key == 127 then + local pos = string.match(self.OutputString,"()"..utf8.charpattern.."$") + if pos then + self.OutputString = string.sub(self.OutputString,1,pos-1) + end + else + key = getPrintableChar(key) + self.OutputString = self.OutputString .. key + end + + self:TriggerOutputs() +end + +--local Wire_Keyboard_Remap = Wire_Keyboard_Remap -- Defined in remap.lua +function ENT:GetRemappedKey(key_enum) + if not key_enum or key_enum == 0 or key_enum > KEY_LAST then return 0 end -- Above KEY_LAST are joystick and mouse enums + + local layout = "American" + if IsValid(self.ply) then layout = self.ply:GetInfo("wire_keyboard_layout", "American") end + local current = Wire_Keyboard_Remap[layout] + if not current then return 0 end + + local ret = current.normal[key_enum] + + -- Check if a special key is being held down (such as SHIFT) + for k,v in pairs(self.ActiveKeys) do + if v == true and current[k] and current[k][key_enum] then + ret = current[k][key_enum] + end + end + + if isstring(ret) then ret = utf8.codepoint(ret) end + if not self.EnterKeyAscii and ret == 10 then ret = 13 end + return ret +end + +function ENT:KeyPressed(key_enum) + local key = self:GetRemappedKey(key_enum) + if key == nil or key == 0 then return end + + if not All_Enums[key] then All_Enums[key] = key_enum end + + self.ActiveKeys[key_enum] = true + self:PushBuffer(key, key_enum) + self:AppendOutputString(key) + + self:TriggerOutputs(key) +end + +function ENT:KeyReleased(key_enum) + local key = self:GetRemappedKey(key_enum) + if key == nil or key == 0 then return end + + self.ActiveKeys[key_enum] = nil + + if self.AutoBuffer then + self:RemoveFromBufferByKey(key) + end + + self:TriggerOutputs(0) +end + +function ENT:IsPressedEnum(key_enum) + return self.ActiveKeys[key_enum] +end + +function ENT:IsPressedAscii(key) + local key_enum = All_Enums[key] + if not key_enum then return false end + return self:IsPressedEnum(key_enum) +end + +function ENT:UnshiftBuffer() + self:RemoveFromBufferByPosition(1) +end + +function ENT:PushBuffer(key, key_enum) + self.Buffer[0] = self.Buffer[0] + 1 + self.Buffer[self.Buffer[0]] = key + + if not self.BufferLookup[key_enum] then self.BufferLookup[key_enum] = {} end + local positions = self.BufferLookup[key_enum] + positions[#positions+1] = self.Buffer[0] +end + +function ENT:RemoveFromBufferByPosition(bufferpos) + if self.Buffer[0] <= 0 then return end + table.remove(self.Buffer, bufferpos) + self.Buffer[0] = self.Buffer[0] - 1 + + -- Move all remaining keys down one step + for _, positions in pairs(self.BufferLookup) do + for k,pos in pairs(positions) do + if bufferpos < pos then + positions[k] = positions[k] - 1 + end + end + end +end + +function ENT:RemoveFromBufferByKey(key) + local key_enum = All_Enums[key] + if not key_enum then return false end -- key is invalid + + local positions = self.BufferLookup[key_enum] + if not positions then return false end -- error, shouldn't happen + local bufferpos = table.remove(positions, 1) + if not bufferpos then return false end -- error, shouldn't happen + + self:RemoveFromBufferByPosition(bufferpos) +end + +function ENT:Think() + if not IsValid(self.ply) then + self:NextThink(CurTime() + 0.3) -- Don't need to update as often + return true + end + + local leavekey = self.ply:GetInfoNum("wire_keyboard_leavekey", KEY_LALT) + + -- Remove lifted up keys from our ActiveKeys + for key_enum, _ in pairs(self.ActiveKeys) do + if not self.ply.keystate[key_enum] then + self:KeyReleased(key_enum) + end + end + + -- Check for newly pressed keys and add them to our ActiveKeys + for key_enum, _ in pairs(self.ply.keystate) do + if key_enum == leavekey then + if leavekey ~= KEY_LALT or not self:IsPressedEnum(KEY_LCONTROL) then -- if LCONTROL and LALT are being pressed, then the player is trying to use the "ALT GR" key which is available for some languages + self:PlayerDetach() -- Pressing the leave key quits the keyboard + break + end + end + + if not self:IsPressedEnum(key_enum) then + self:KeyPressed(key_enum) + end + end + + self:NextThink(CurTime()) + return true +end + +function ENT:Setup(autobuffer, sync, enterkeyascii) + self.AutoBuffer = autobuffer + self.Synchronous = sync + self.EnterKeyAscii = enterkeyascii + self:TriggerOutputs() +end + +duplicator.RegisterEntityClass("gmod_wire_keyboard", WireLib.MakeWireEnt, "Data", "AutoBuffer", "Synchronous", "EnterKeyAscii") + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if IsValid(self.Pod) then + info.pod = self.Pod:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self:LinkEnt(GetEntByID(info.pod), true) + if info.autobuffer then self.AutoBuffer = info.autobuffer end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/remap.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/remap.lua new file mode 100644 index 0000000..406de1a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/remap.lua @@ -0,0 +1,472 @@ +Wire_Keyboard_Remap = {} + +---------------------------------------------------------------------- +-- Default - Keys that all layouts use +---------------------------------------------------------------------- + +local Wire_Keyboard_Remap_default = {} +Wire_Keyboard_Remap_default.normal = {} +Wire_Keyboard_Remap_default[KEY_LSHIFT] = {} +Wire_Keyboard_Remap_default[KEY_RSHIFT] = Wire_Keyboard_Remap_default[KEY_LSHIFT] +local remap = Wire_Keyboard_Remap_default.normal +remap[KEY_NONE] = "" +remap[KEY_0] = "0" +remap[KEY_1] = "1" +remap[KEY_2] = "2" +remap[KEY_3] = "3" +remap[KEY_4] = "4" +remap[KEY_5] = "5" +remap[KEY_6] = "6" +remap[KEY_7] = "7" +remap[KEY_8] = "8" +remap[KEY_9] = "9" +remap[KEY_A] = "a" +remap[KEY_B] = "b" +remap[KEY_C] = "c" +remap[KEY_D] = "d" +remap[KEY_E] = "e" +remap[KEY_F] = "f" +remap[KEY_G] = "g" +remap[KEY_H] = "h" +remap[KEY_I] = "i" +remap[KEY_J] = "j" +remap[KEY_K] = "k" +remap[KEY_L] = "l" +remap[KEY_M] = "m" +remap[KEY_N] = "n" +remap[KEY_O] = "o" +remap[KEY_P] = "p" +remap[KEY_Q] = "q" +remap[KEY_R] = "r" +remap[KEY_S] = "s" +remap[KEY_T] = "t" +remap[KEY_U] = "u" +remap[KEY_V] = "v" +remap[KEY_W] = "w" +remap[KEY_X] = "x" +remap[KEY_Y] = "y" +remap[KEY_Z] = "z" +remap[KEY_PAD_0] = 128 +remap[KEY_PAD_1] = 129 +remap[KEY_PAD_2] = 130 +remap[KEY_PAD_3] = 131 +remap[KEY_PAD_4] = 132 +remap[KEY_PAD_5] = 133 +remap[KEY_PAD_6] = 134 +remap[KEY_PAD_7] = 135 +remap[KEY_PAD_8] = 136 +remap[KEY_PAD_9] = 137 +remap[KEY_PAD_DIVIDE] = 138 +remap[KEY_PAD_MULTIPLY] = 139 +remap[KEY_PAD_MINUS] = 140 +remap[KEY_PAD_PLUS] = 141 +remap[KEY_PAD_ENTER] = 142 +remap[KEY_PAD_DECIMAL] = 143 +remap[KEY_ENTER] = 10 +remap[KEY_SPACE] = " " +remap[KEY_BACKSPACE] = 127 +remap[KEY_TAB] = 9 +remap[KEY_CAPSLOCK] = 144 +remap[KEY_NUMLOCK] = 145 +remap[KEY_ESCAPE] = 18 +remap[KEY_SCROLLLOCK] = 146 +remap[KEY_INSERT] = 147 +remap[KEY_DELETE] = 148 +remap[KEY_HOME] = 149 +remap[KEY_END] = 150 +remap[KEY_PAGEUP] = 151 +remap[KEY_PAGEDOWN] = 152 +remap[KEY_BREAK] = 153 +remap[KEY_LSHIFT] = 154 +remap[KEY_RSHIFT] = 155 +remap[KEY_LALT] = 156 +remap[KEY_RALT] = 157 +remap[KEY_LCONTROL] = 158 +remap[KEY_RCONTROL] = 159 +remap[KEY_LWIN] = 160 +remap[KEY_RWIN] = 161 +remap[KEY_APP] = 162 +remap[KEY_UP] = 17 +remap[KEY_LEFT] = 19 +remap[KEY_DOWN] = 18 +remap[KEY_RIGHT] = 20 +remap[KEY_F1] = 163 +remap[KEY_F2] = 164 +remap[KEY_F3] = 165 +remap[KEY_F4] = 166 +remap[KEY_F5] = 167 +remap[KEY_F6] = 168 +remap[KEY_F7] = 169 +remap[KEY_F8] = 170 +remap[KEY_F9] = 171 +remap[KEY_F10] = 172 +remap[KEY_F11] = 173 +remap[KEY_F12] = 174 +remap[KEY_CAPSLOCKTOGGLE] = 175 +remap[KEY_NUMLOCKTOGGLE] = 176 +remap[KEY_SCROLLLOCKTOGGLE] = 177 +--[[ + -- These are unused + remap[KEY_XBUTTON_UP] = 200 + remap[KEY_XBUTTON_DOWN] = 201 + remap[KEY_XBUTTON_LEFT] = 202 + remap[KEY_XBUTTON_RIGHT] = 203 + remap[KEY_XBUTTON_START] = 204 + remap[KEY_XBUTTON_BACK] = 205 + remap[KEY_XBUTTON_STICK1] = 206 + remap[KEY_XBUTTON_STICK2] = 207 + remap[KEY_XBUTTON_A] = 208 + remap[KEY_XBUTTON_B] = 209 + remap[KEY_XBUTTON_X] = 210 + remap[KEY_XBUTTON_Y] = 211 + remap[KEY_XBUTTON_LTRIGGER] = 214 + remap[KEY_XBUTTON_RTRIGGER] = 215 + remap[KEY_XSTICK1_UP] = 216 + remap[KEY_XSTICK1_DOWN] = 217 + remap[KEY_XSTICK1_LEFT] = 218 + remap[KEY_XSTICK1_RIGHT] = 219 + remap[KEY_XSTICK2_UP] = 220 + remap[KEY_XSTICK2_DOWN] = 221 + remap[KEY_XSTICK2_LEFT] = 222 + remap[KEY_XSTICK2_RIGHT] = 223 +]] + +remap = Wire_Keyboard_Remap_default[KEY_LSHIFT] +remap[KEY_A] = "A" +remap[KEY_B] = "B" +remap[KEY_C] = "C" +remap[KEY_D] = "D" +remap[KEY_E] = "E" +remap[KEY_F] = "F" +remap[KEY_G] = "G" +remap[KEY_H] = "H" +remap[KEY_I] = "I" +remap[KEY_J] = "J" +remap[KEY_K] = "K" +remap[KEY_L] = "L" +remap[KEY_M] = "M" +remap[KEY_N] = "N" +remap[KEY_O] = "O" +remap[KEY_P] = "P" +remap[KEY_Q] = "Q" +remap[KEY_R] = "R" +remap[KEY_S] = "S" +remap[KEY_T] = "T" +remap[KEY_U] = "U" +remap[KEY_V] = "V" +remap[KEY_W] = "W" +remap[KEY_X] = "X" +remap[KEY_Y] = "Y" +remap[KEY_Z] = "Z" + +---------------------------------------------------------------------- +-- American +---------------------------------------------------------------------- + +Wire_Keyboard_Remap.American = {} +Wire_Keyboard_Remap.American = table.Copy(Wire_Keyboard_Remap_default) +Wire_Keyboard_Remap.American[KEY_RSHIFT] = Wire_Keyboard_Remap.American[KEY_LSHIFT] + +remap = Wire_Keyboard_Remap.American.normal +remap[KEY_LBRACKET] = "[" +remap[KEY_RBRACKET] = "]" +remap[KEY_SEMICOLON] = ";" +remap[KEY_APOSTROPHE] = "'" +remap[KEY_BACKQUOTE] = "`" +remap[KEY_COMMA] = "," +remap[KEY_PERIOD] = "." +remap[KEY_SLASH] = "/" +remap[KEY_BACKSLASH] = "\\" +remap[KEY_MINUS] = "-" +remap[KEY_EQUAL] = "=" + +remap = Wire_Keyboard_Remap.American[KEY_LSHIFT] +remap[KEY_0] = ")" +remap[KEY_1] = "!" +remap[KEY_2] = "@" +remap[KEY_3] = "#" +remap[KEY_4] = "$" +remap[KEY_5] = "%" +remap[KEY_6] = "^" +remap[KEY_7] = "&" +remap[KEY_8] = "*" +remap[KEY_9] = "(" +remap[KEY_LBRACKET] = "{" +remap[KEY_RBRACKET] = "}" +remap[KEY_SEMICOLON] = ":" +remap[KEY_APOSTROPHE] = '"' +remap[KEY_COMMA] = "<" +remap[KEY_PERIOD] = ">" +remap[KEY_SLASH] = "?" +remap[KEY_BACKSLASH] = "|" +remap[KEY_MINUS] = "_" +remap[KEY_EQUAL] = "+" + +---------------------------------------------------------------------- +-- British +---------------------------------------------------------------------- + +Wire_Keyboard_Remap.British = {} +Wire_Keyboard_Remap.British = table.Copy(Wire_Keyboard_Remap.American) +Wire_Keyboard_Remap.British[KEY_LCONTROL] = {} +Wire_Keyboard_Remap.British[KEY_RSHIFT] = Wire_Keyboard_Remap.British[KEY_LSHIFT] + +remap = Wire_Keyboard_Remap.British.normal +remap[KEY_BACKQUOTE] = "`" +remap[KEY_BACKSLASH] = "#" + +remap = Wire_Keyboard_Remap.British[KEY_LSHIFT] +remap[KEY_2] = '"' +remap[KEY_3] = "£" +remap[KEY_APOSTROPHE] = "@" +remap[KEY_BACKQUOTE] = "¬" +remap[KEY_BACKSLASH] = "~" + +remap = Wire_Keyboard_Remap.British[KEY_LCONTROL] +remap[KEY_4] = "€" +remap[KEY_A] = "á" +remap[KEY_E] = "é" +remap[KEY_I] = "í" +remap[KEY_O] = "ó" +remap[KEY_U] = "ú" + +---------------------------------------------------------------------- +-- Russian/Русский +---------------------------------------------------------------------- + +Wire_Keyboard_Remap.Russian = {} +Wire_Keyboard_Remap.Russian = table.Copy(Wire_Keyboard_Remap_default) +Wire_Keyboard_Remap.Russian[KEY_RSHIFT] = Wire_Keyboard_Remap.Russian[KEY_LSHIFT] + +remap = Wire_Keyboard_Remap.Russian.normal +remap[KEY_LBRACKET] = "х" +remap[KEY_RBRACKET] = "ъ" +remap[KEY_SEMICOLON] = "ж" +remap[KEY_APOSTROPHE] = "э" +remap[KEY_BACKQUOTE] = "ё" +remap[KEY_COMMA] = "б" +remap[KEY_PERIOD] = "ю" +remap[KEY_SLASH] = "." +remap[KEY_BACKSLASH] = "\\" +remap[KEY_MINUS] = "-" +remap[KEY_EQUAL] = "=" + +remap = Wire_Keyboard_Remap.Russian.normal +remap[KEY_A] = "ф" +remap[KEY_B] = "и" +remap[KEY_C] = "с" +remap[KEY_D] = "в" +remap[KEY_E] = "у" +remap[KEY_F] = "а" +remap[KEY_G] = "п" +remap[KEY_H] = "р" +remap[KEY_I] = "ш" +remap[KEY_J] = "о" +remap[KEY_K] = "л" +remap[KEY_L] = "д" +remap[KEY_M] = "ь" +remap[KEY_N] = "т" +remap[KEY_O] = "щ" +remap[KEY_P] = "з" +remap[KEY_Q] = "й" +remap[KEY_R] = "к" +remap[KEY_S] = "ы" +remap[KEY_T] = "е" +remap[KEY_U] = "г" +remap[KEY_V] = "м" +remap[KEY_W] = "ц" +remap[KEY_X] = "ч" +remap[KEY_Y] = "н" +remap[KEY_Z] = "я" + + +remap = Wire_Keyboard_Remap.Russian[KEY_LSHIFT] +remap[KEY_A] = "Ф" +remap[KEY_B] = "И" +remap[KEY_C] = "С" +remap[KEY_D] = "В" +remap[KEY_E] = "У" +remap[KEY_F] = "А" +remap[KEY_G] = "П" +remap[KEY_H] = "Р" +remap[KEY_I] = "Ш" +remap[KEY_J] = "О" +remap[KEY_K] = "Л" +remap[KEY_L] = "Д" +remap[KEY_M] = "Ь" +remap[KEY_N] = "Т" +remap[KEY_O] = "Щ" +remap[KEY_P] = "З" +remap[KEY_Q] = "Й" +remap[KEY_R] = "К" +remap[KEY_S] = "Ы" +remap[KEY_T] = "Е" +remap[KEY_U] = "Г" +remap[KEY_V] = "М" +remap[KEY_W] = "Ц" +remap[KEY_X] = "Ч" +remap[KEY_Y] = "Н" +remap[KEY_Z] = "Я" +remap[KEY_1] = '!' +remap[KEY_2] = '"' +remap[KEY_3] = "№" +remap[KEY_5] = '%' +remap[KEY_4] = ";" +remap[KEY_6] = ":" +remap[KEY_7] = "?" +remap[KEY_8] = "*" +remap[KEY_9] = "(" +remap[KEY_0] = ")" +remap[KEY_MINUS] = "_" +remap[KEY_EQUAL] = "+" +remap[KEY_BACKSLASH] = "/" +remap[KEY_LBRACKET] = "Х" +remap[KEY_RBRACKET] = "Ъ" +remap[KEY_SEMICOLON] = "Ж" +remap[KEY_APOSTROPHE] = "Э" +remap[KEY_BACKQUOTE] = "Ё" +remap[KEY_COMMA] = "Б" +remap[KEY_PERIOD] = "Ю" +remap[KEY_SLASH] = "," + +---------------------------------------------------------------------- +-- Swedish +---------------------------------------------------------------------- + +Wire_Keyboard_Remap.Swedish = {} +Wire_Keyboard_Remap.Swedish = table.Copy(Wire_Keyboard_Remap_default) +Wire_Keyboard_Remap.Swedish[KEY_LCONTROL] = {} -- Should be KEY_RALT, but that didn't work correctly +Wire_Keyboard_Remap.Swedish[KEY_RSHIFT] = Wire_Keyboard_Remap.Swedish[KEY_LSHIFT] + +remap = Wire_Keyboard_Remap.Swedish.normal +remap[KEY_LBRACKET] = "´" +remap[KEY_RBRACKET] = "å" +remap[KEY_BACKQUOTE] = "¨" +remap[KEY_APOSTROPHE] = "ä" +remap[KEY_SEMICOLON] = "ö" +remap[KEY_COMMA] = "," +remap[KEY_PERIOD] = "." +remap[KEY_SLASH] = "'" +remap[KEY_BACKSLASH] = "§" +remap[KEY_MINUS] = "-" +remap[KEY_EQUAL] = "+" + +remap = Wire_Keyboard_Remap.Swedish[KEY_LSHIFT] +remap[KEY_0] = "=" +remap[KEY_1] = "!" +remap[KEY_2] = '"' +remap[KEY_3] = "#" +remap[KEY_4] = "¤" +remap[KEY_5] = "%" +remap[KEY_6] = "&" +remap[KEY_7] = "/" +remap[KEY_8] = "(" +remap[KEY_9] = ")" +remap[KEY_LBRACKET] = "`" +remap[KEY_RBRACKET] = "Å" +remap[KEY_SEMICOLON] = 214 --"Ö" +remap[KEY_BACKQUOTE] = "^" -- doesn't work because garry +remap[KEY_APOSTROPHE] = "Ä" +remap[KEY_COMMA] = ";" +remap[KEY_PERIOD] = ":" +remap[KEY_SLASH] = "*" +remap[KEY_BACKSLASH] = "½" +remap[KEY_MINUS] = "_" +remap[KEY_EQUAL] = "?" + +remap = Wire_Keyboard_Remap.Swedish[KEY_LCONTROL] +remap[KEY_2] = "@" +remap[KEY_3] = "£" +remap[KEY_4] = "$" +remap[KEY_7] = "{" +remap[KEY_8] = "[" +remap[KEY_9] = "]" +remap[KEY_0] = "}" +remap[KEY_EQUAL] = "\\" +remap[KEY_SEMICOLON] = "~" +remap[KEY_E] = "€" + +---------------------------------------------------------------------- +-- Norwegian +---------------------------------------------------------------------- + +Wire_Keyboard_Remap.Norwegian = {} +Wire_Keyboard_Remap.Norwegian = table.Copy(Wire_Keyboard_Remap.Swedish) +Wire_Keyboard_Remap.Norwegian[KEY_RSHIFT] = Wire_Keyboard_Remap.Norwegian[KEY_LSHIFT] + +remap = Wire_Keyboard_Remap.Norwegian.normal +remap[KEY_BACKQUOTE] = "ø" +remap[KEY_APOSTROPHE] = "æ" +remap[KEY_BACKSLASH] = "|" +remap[KEY_LBRACKET] = "\\" + +remap = Wire_Keyboard_Remap.Norwegian[KEY_LSHIFT] +remap[KEY_BACKQUOTE] = "Ø" +remap[KEY_APOSTROPHE] = "Æ" +remap[KEY_BACKSLASH] = "§" + +remap = Wire_Keyboard_Remap.Norwegian[KEY_LCONTROL] +remap[KEY_EQUAL] = nil +remap[KEY_M] = "µ" +remap[KEY_LBRACKET] = "´" + +---------------------------------------------------------------------- +-- German +---------------------------------------------------------------------- + +Wire_Keyboard_Remap.German = {} +Wire_Keyboard_Remap.German = table.Copy(Wire_Keyboard_Remap_default) +Wire_Keyboard_Remap.German[KEY_LCONTROL] = {} -- Should be KEY_RALT, but that didn't work correctly +Wire_Keyboard_Remap.German[KEY_RSHIFT] = Wire_Keyboard_Remap.German[KEY_LSHIFT] + +remap = Wire_Keyboard_Remap.German.normal +remap[KEY_LBRACKET] = "ß" +remap[KEY_RBRACKET] = "´" +remap[KEY_SEMICOLON] = "ü" +remap[KEY_APOSTROPHE] = "ä" +remap[KEY_BACKQUOTE] = "ö" +remap[KEY_COMMA] = "," +remap[KEY_PERIOD] = "." +remap[KEY_SLASH] = "#" +remap[KEY_BACKSLASH] = "^" +remap[KEY_MINUS] = "-" +remap[KEY_EQUAL] = "+" + +remap = Wire_Keyboard_Remap.German[KEY_LSHIFT] +remap[KEY_0] = "=" +remap[KEY_1] = "!" +remap[KEY_2] = '"' +remap[KEY_3] = "§" +remap[KEY_4] = "$" +remap[KEY_5] = "%" +remap[KEY_6] = "&" +remap[KEY_7] = "/" +remap[KEY_8] = "(" +remap[KEY_9] = ")" +remap[KEY_LBRACKET] = "?" +remap[KEY_RBRACKET] = "`" +remap[KEY_SEMICOLON] = "Ü" +remap[KEY_APOSTROPHE] = 'Ä' +remap[KEY_BACKQUOTE] = "Ö" +remap[KEY_COMMA] = ";" +remap[KEY_PERIOD] = ":" +remap[KEY_SLASH] = "'" +remap[KEY_BACKSLASH] = "°" +remap[KEY_MINUS] = "_" +remap[KEY_EQUAL] = "*" + +remap = Wire_Keyboard_Remap.German[KEY_LCONTROL] +remap[KEY_0] = "}" +remap[KEY_2] = '²' +remap[KEY_3] = "³" +remap[KEY_7] = "{" +remap[KEY_8] = "[" +remap[KEY_9] = "]" +remap[KEY_E] = "€" +remap[KEY_M] = "µ" +remap[KEY_Q] = "@" +remap[KEY_LBRACKET] = '\\' +remap[KEY_EQUAL] = "~" +remap[KEY_COMMA] = "<" +remap[KEY_PERIOD] = ">" +remap[KEY_MINUS] = "|" diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/shared.lua new file mode 100644 index 0000000..c0451e9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keyboard/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire Keyboard" +ENT.Author = "Divran" +ENT.Contact = "www.wiremod.com" +ENT.Purpose = "Send key input to the server." +ENT.Instructions = "Click Use on it to activate it." + +ENT.Spawnable = false diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keypad.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keypad.lua new file mode 100644 index 0000000..3c43f11 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_keypad.lua @@ -0,0 +1,229 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Keypad" + +if CLIENT then + local X = -50 + local Y = -100 + local W = 100 + local H = 200 + + local KeyPos = { + {X+5 , Y+100 , 25, 25, -2.2, 3.45, 1.3 , 0 }, -- 1 + {X+37.5, Y+100 , 25, 25, -0.6, 1.85, 1.3 , 0 }, -- 2 + {X+70 , Y+100 , 25, 25, 1.0, 0.25, 1.3 , 0 }, -- 3 + + {X+5 , Y+132.5, 25, 25, -2.2, 3.45, 2.9 , -1.6}, -- 4 + {X+37.5, Y+132.5, 25, 25, -0.6, 1.85, 2.9 , -1.6}, -- 5 + {X+70 , Y+132.5, 25, 25, 1.0, 0.25, 2.9 , -1.6}, -- 6 + + {X+5 , Y+165 , 25, 25, -2.2, 3.45, 4.55, -3.3}, -- 7 + {X+37.5, Y+165 , 25, 25, -0.6, 1.85, 4.55, -3.3}, -- 8 + {X+70 , Y+165 , 25, 25, 1.0, 0.25, 4.55, -3.3}, -- 9 + + {X+5 , Y+ 67.5, 40, 25, -2.2, 4.25, -0.3 , 1.6}, -- abort + {X+55 , Y+ 67.5, 40, 25, 0.3, 1.65, -0.3 , 1.6}, -- ok + } + + local fontdata = { + font = "Trebuchet MS", + weight = 400, + antialias = true, + additive = false + } + fontdata.size = 34 surface.CreateFont( "Trebuchet34", fontdata ) + fontdata.size = 24 surface.CreateFont( "Trebuchet24", fontdata ) + + local highlight_key, highlight_until + function ENT:Draw() + self:DrawModel() + + local Ply = LocalPlayer() + if (Ply:GetShootPos() - self:GetPos()):Length() > 750 then return end + + local pos = self:GetPos() + (self:GetForward() * 1.1) + local ang = self:GetAngles() + local rot = Vector(-90, 90, 0) + + ang:RotateAroundAxis(ang:Right(), rot.x) + ang:RotateAroundAxis(ang:Up(), rot.y) + ang:RotateAroundAxis(ang:Forward(), rot.z) + + cam.Start3D2D(pos, ang, 0.05) + local trace = Ply:GetEyeTrace() + + local pos = self:WorldToLocal(trace.HitPos) + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(X-5, Y-5, W+10, H+10) + + surface.SetDrawColor(50, 75, 50, 255) + surface.DrawRect(X+5, Y+5, 90, 50) + + for k,v in pairs(KeyPos) do + local text = k + local textx = v[1] + 9 + local texty = v[2] + 4 + local x = (pos.y - v[5]) / (v[5] + v[6]) + local y = 1 - (pos.z + v[7]) / (v[7] + v[8]) + local highlight_current_key = highlight_key == k and highlight_until >= CurTime() + + if (k == 10) then + text = "ABORT" + textx = v[1] + 2 + texty = v[2] + 4 + surface.SetDrawColor(150, 25, 25, 255) + elseif (k == 11) then + text = "OK" + textx = v[1] + 12 + texty = v[2] + 5 + surface.SetDrawColor(25, 150, 25, 255) + else + surface.SetDrawColor(150, 150, 150, 255) + end + + if highlight_current_key or (trace.Entity == self and x >= 0 and y >= 0 and x <= 1 and y <= 1) then + if (k <= 9) then + surface.SetDrawColor(200, 200, 200, 255) + elseif (k == 10) then + surface.SetDrawColor(200, 50, 50, 255) + elseif (k == 11) then + surface.SetDrawColor(50, 200, 50, 255) + end + + if Ply:KeyDown(IN_USE) and not Ply.KeyOnce and not highlight_current_key then + net.Start("wire_keypad") + net.WriteEntity(self) + net.WriteUInt(k, 4) + net.SendToServer() + Ply.KeyOnce = true + end + end + surface.DrawRect(v[1], v[2], v[3], v[4]) + draw.DrawText(text, "Trebuchet18", textx, texty, Color(0, 0, 0, 255)) + end + + local Display = self:GetNWString("keypad_display", "") + if Display == "y" then + draw.DrawText("ACCESS", "Trebuchet24", X+17, Y+7, Color(0, 255, 0, 255)) + draw.DrawText("GRANTED","Trebuchet24", X+7, Y+27, Color(0, 255, 0, 255)) + elseif Display == "n" then + draw.DrawText("ACCESS", "Trebuchet24", X+17, Y+7, Color(255, 0, 0, 255)) + draw.DrawText("DENIED", "Trebuchet24", X+19, Y+27, Color(255, 0, 0, 255)) + else + draw.DrawText(Display, "Trebuchet34", X+17, Y+10, Color(255, 255, 255, 255)) + end + cam.End3D2D() + end + + hook.Add("KeyRelease", "Keypad_KeyReleased", function(Ply, key) + Ply.KeyOnce = false + end) + + local binds = { + ["+gm_special 1" ] = 1, + ["+gm_special 2" ] = 2, + ["+gm_special 3" ] = 3, + ["+gm_special 4" ] = 4, + ["+gm_special 5" ] = 5, + ["+gm_special 6" ] = 6, + ["+gm_special 7" ] = 7, + ["+gm_special 8" ] = 8, + ["+gm_special 9" ] = 9, + ["+gm_special 11"] = 11, + ["+gm_special 12"] = 10, + } + + hook.Add("PlayerBindPress", "keypad_PlayerBindPress", function(ply, bind, pressed) + if not pressed then return end + local command = binds[bind] + if not command then return end + + local trace = ply:GetEyeTraceNoCursor() + local ent = trace.Entity + if not IsValid(ent) then return end + + if ent:GetClass() ~= "gmod_wire_keypad" then return end + + net.Start("wire_keypad") + net.WriteEntity(ent) + net.WriteUInt(command, 4) + net.SendToServer() + + highlight_key, highlight_until = command, CurTime()+0.5 + return true + end) + return +end -- No more client + +util.PrecacheSound("buttons/button8.wav") +util.PrecacheSound("buttons/button9.wav") +util.PrecacheSound("buttons/button14.wav") +util.PrecacheSound("buttons/button15.wav") + +function ENT:Initialize() + BaseClass.Initialize(self) + + self.Outputs = WireLib.CreateOutputs(self, {"Valid", "Invalid"}) + + self.CurrentNum = 0 +end + +function ENT:Setup(password, securemode) + self.Password = password + self.Secure = securemode +end + +util.AddNetworkString("wire_keypad") +net.Receive("wire_keypad", function(netlen, ply) + local ent = net.ReadEntity() + if not IsValid(ent) or not ent.Password then return end + + if ent.CurrentNum == -1 then return end -- Display still shows ACCESS from a past success + if (ply:GetShootPos() - ent:GetPos()):Length() > 50 then return end + + local key = net.ReadUInt(4) + + if key == 10 then -- Reset + ent:SetNWString("keypad_display", "") + ent:EmitSound("buttons/button14.wav") + ent.CurrentNum = 0 + elseif key == 11 or ent.CurrentNum > 999 then -- Accept + local access = (ent.Password == util.CRC(ent.CurrentNum)) + if access then + ent:SetNWString("keypad_display", "y") + Wire_TriggerOutput(ent, "Valid", 1) + ent:EmitSound("buttons/button9.wav") + else + ent:SetNWString("keypad_display", "n") + Wire_TriggerOutput(ent, "Invalid", 1) + ent:EmitSound("buttons/button8.wav") + end + + ent.CurrentNum = -1 + timer.Create("wire_keypad_"..ent:EntIndex().."_"..tostring(access), 2, 1, function() + if IsValid(ent) then + ent:SetNWString("keypad_display", "") + ent.CurrentNum = 0 + if access then + Wire_TriggerOutput(ent, "Valid", 0) + else + Wire_TriggerOutput(ent, "Invalid", 0) + end + end + end) + else + ent.CurrentNum = ent.CurrentNum * 10 + key + + if ent.Secure then + ent:SetNWString("keypad_display", string.rep("*", string.len(ent.CurrentNum))) + else + ent:SetNWString("keypad_display", tostring(ent.CurrentNum)) + end + ent:EmitSound("buttons/button15.wav") + end +end) + +duplicator.RegisterEntityClass("sent_keypad", WireLib.MakeWireEnt, "Data", "Pass", "secure") +duplicator.RegisterEntityClass("gmod_wire_keypad", WireLib.MakeWireEnt, "Data", "Password", "Secure") +scripted_ents.Alias("sent_keypad", "gmod_wire_keypad") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_lamp.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_lamp.lua new file mode 100644 index 0000000..052598d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_lamp.lua @@ -0,0 +1,179 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Lamp" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Lamp" + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "On" ) +end + +if CLIENT then + local matLight = Material( "sprites/light_ignorez" ) + local matBeam = Material( "effects/lamp_beam" ) + + function ENT:Initialize() + self.PixVis = util.GetPixelVisibleHandle() + end + + function ENT:DrawTranslucent() + + BaseClass.DrawTranslucent( self ) + + -- No glow if we're not switched on! + if not self:GetOn() then return end + + local LightNrm = self:GetAngles():Forward() + local ViewNormal = self:GetPos() - EyePos() + local Distance = ViewNormal:Length() + ViewNormal:Normalize() + local ViewDot = ViewNormal:Dot( LightNrm * -1 ) + local LightPos = self:GetPos() + LightNrm * 5 + + -- glow sprite + --[[ + render.SetMaterial( matBeam ) + + local BeamDot = BeamDot = 0.25 + + render.StartBeam( 3 ) + render.AddBeam( LightPos + LightNrm * 1, 128, 0.0, Color( r, g, b, 255 * BeamDot) ) + render.AddBeam( LightPos - LightNrm * 100, 128, 0.5, Color( r, g, b, 64 * BeamDot) ) + render.AddBeam( LightPos - LightNrm * 200, 128, 1, Color( r, g, b, 0) ) + render.EndBeam() + --]] + + if ViewDot >= 0 then + + render.SetMaterial( matLight ) + local Visibile = util.PixelVisible( LightPos, 16, self.PixVis ) + + if (!Visibile) then return end + + local Size = math.Clamp( Distance * Visibile * ViewDot * 2, 64, 512 ) + + Distance = math.Clamp( Distance, 32, 800 ) + local Alpha = math.Clamp( (1000 - Distance) * Visibile * ViewDot, 0, 100 ) + local Col = self:GetColor() + Col.a = Alpha + + render.DrawSprite( LightPos, Size, Size, Col, Visibile * ViewDot ) + render.DrawSprite( LightPos, Size*0.4, Size*0.4, Color(255, 255, 255, Alpha), Visibile * ViewDot ) + + end + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + end + + self.Inputs = WireLib.CreateSpecialInputs(self, {"Red", "Green", "Blue", "RGB", "FOV", "Distance", "Brightness", "On", "Texture"}, {"NORMAL", "NORMAL", "NORMAL", "VECTOR", "NORMAL", "NORMAL", "NORMAL", "NORMAL", "STRING"}) +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:TriggerInput(iname, value) + if (iname == "Red") then + self.r = math.Clamp(value,0,255) + elseif (iname == "Green") then + self.g = math.Clamp(value,0,255) + elseif (iname == "Blue") then + self.b = math.Clamp(value,0,255) + elseif (iname == "RGB") then + self.r, self.g, self.b = math.Clamp(value[1],0,255), math.Clamp(value[2],0,255), math.Clamp(value[3],0,255) + elseif (iname == "FOV") then + self.FOV = value + elseif (iname == "Distance") then + self.Dist = value + elseif (iname == "Brightness") then + self.Brightness = math.Clamp(value,0,10) + elseif (iname == "On") then + self:Switch( value ~= 0 ) + elseif (iname == "Texture") then + if value != "" then self.Texture = value else self.Texture = "effects/flashlight001" end + end + self:UpdateLight() +end + +function ENT:Switch( on ) + if on ~= not self.flashlight then return end + self.on = on + + if not on then + SafeRemoveEntity( self.flashlight ) + self.flashlight = nil + self:SetOn( false ) + return + end + + self:SetOn( true ) + + local angForward = self:GetAngles() + + self.flashlight = ents.Create( "env_projectedtexture" ) + + self.flashlight:SetParent( self ) + + -- The local positions are the offsets from parent.. + self.flashlight:SetLocalPos( Vector( 0, 0, 0 ) ) + self.flashlight:SetLocalAngles( Angle(0,0,0) ) + + -- Looks like only one flashlight can have shadows enabled! + self.flashlight:SetKeyValue( "enableshadows", 1 ) + + self.flashlight:SetKeyValue( "farz", self.Dist ) + self.flashlight:SetKeyValue( "nearz", 12 ) + self.flashlight:SetKeyValue( "lightfov", self.FOV ) + + local c = self:GetColor() + local b = self.Brightness + self.flashlight:SetKeyValue( "lightcolor", Format( "%i %i %i 255", c.r * b, c.g * b, c.b * b ) ) + + self.flashlight:Spawn() + + self.flashlight:Input( "SpotlightTexture", NULL, NULL, self.Texture ) +end + +function ENT:UpdateLight() + self:SetColor( Color( self.r, self.g, self.b, self:GetColor().a ) ) + if ( !IsValid( self.flashlight ) ) then return end + + self.flashlight:Input( "SpotlightTexture", NULL, NULL, self.Texture ) + self.flashlight:Input( "FOV", NULL, NULL, tostring( self.FOV ) ) + self.flashlight:SetKeyValue( "farz", self.Dist ) + + local c = self:GetColor() + local b = self.Brightness + self.flashlight:SetKeyValue( "lightcolor", Format( "%i %i %i 255", c.r*b, c.g*b, c.b*b ) ) + + self:SetOverlayText( "Red: " .. c.r .. " Green: " .. c.g .. " Blue: " .. c.b .. "\n" .. + "FoV: " .. self.FOV .. " Distance: " .. self.Dist .. " Brightness: " .. self.Brightness ) +end + +function ENT:Setup( r, g, b, Texture, fov, dist, brightness, on ) + self.r, self.g, self.b = math.Clamp(r or 255,0,255), math.Clamp(g or 255,0,255), math.Clamp(b or 255,0,255) + + self.Texture = Texture or "effects/flashlight001" + self.FOV = fov or 90 + self.Dist = dist or 1024 + self.Brightness = math.Clamp(brightness or 8,0,10) + self.on = on or false + self:Switch( self.on ) + self:UpdateLight() +end + +duplicator.RegisterEntityClass( "gmod_wire_lamp", WireLib.MakeWireEnt, "Data", "r", "g", "b", "Texture", "FOV", "Dist", "Brightness", "on" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_las_receiver.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_las_receiver.lua new file mode 100644 index 0000000..4c3c01f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_las_receiver.lua @@ -0,0 +1,54 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Laser Pointer Receiver" +ENT.WireDebugName = "Laser Receiver" + +if CLIENT then return end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self.Outputs = WireLib.CreateSpecialOutputs(self, {"X", "Y", "Z", "Active", "Pos", "RangerData"}, {"NORMAL", "NORMAL", "NORMAL", "NORMAL", "VECTOR", "RANGER"}) + self.VPos = Vector(0,0,0) + + self:SetOverlayText( "Laser Pointer Receiver" ) +end + +function ENT:GetBeaconPos(sensor) + return self.VPos +end +function ENT:GetBeaconVelocity(sensor) return Vector() end + +function ENT:Use( User, caller ) + if not hook.Run("PlayerGiveSWEP", User, "laserpointer", weapons.Get( "laserpointer" )) then return end + User:PrintMessage(HUD_PRINTTALK, "Hold down your use key for 2 seconds to get and link a Laser Pointer.") + timer.Create("las_receiver_use_"..User:EntIndex(), 2, 1, function() + if not IsValid(User) or not User:IsPlayer() then return end + if not User:KeyDown(IN_USE) then return end + if not User:GetEyeTrace().Entity then return end + + if not IsValid(User:GetWeapon("laserpointer")) then + if not hook.Run("PlayerGiveSWEP", User, "laserpointer", weapons.Get( "laserpointer" )) then return end + User:Give("laserpointer") + end + + User:GetWeapon("laserpointer").Receiver = self + User:PrintMessage(HUD_PRINTTALK, "You are now linked!") + User:SelectWeapon("laserpointer") + end) +end + +local function playerDeath( victim, weapon, killer) + if(victim:HasWeapon("laserPointer"))then + local pointer = victim:GetWeapon("laserPointer") + if(pointer && pointer:IsValid())then + victim.LasReceiver = pointer.Receiver + end + end +end +hook.Add( "PlayerDeath", "laserMemory", playerDeath) + +duplicator.RegisterEntityClass("gmod_wire_las_receiver", WireLib.MakeWireEnt, "Data") +duplicator.RegisterEntityClass("gmod_wire_las_reciever", WireLib.MakeWireEnt, "Data") -- For old dupe support diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_latch.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_latch.lua new file mode 100644 index 0000000..42ea2a1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_latch.lua @@ -0,0 +1,208 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Constraint Latch" +ENT.Purpose = "Controllable weld and nocollide between two selected entities" +ENT.WireDebugName = "Latch" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs( self, { "Activate", "NoCollide", "Strength" } ) + self.Outputs = Wire_CreateOutputs( self, { "Welded" } ) + + -- masks containing all current states + self.nocollide_masks = { + -- Ent1, Ent2 , nocollide between the two + { false, false, true }, -- 1 nocollide between the two + { true , false, false }, -- 2 nocollide Ent1 with all + { false, true , false }, -- 3 nocollide Ent2 with all + { true , true , false } -- 4 nocollide both with all + -- all other values: { false, false, false } + } + + self.nocollide_description = { + "NoCollided", + "Ent1 has collisions disabled", + "Ent2 has collisions disabled", + "All collisions disabled" + } + + self.Nocollide = nil + self:TriggerInput("NoCollide", 0) +end + +-- Run if weld is removed (will run *after* Create_Weld) +local function Weld_Removed( weld, ent ) + if IsValid(ent) then + if !ent.Constraint or ent.Constraint == weld then + ent.Constraint = nil + Wire_TriggerOutput( ent, "Welded", 0 ) + ent:UpdateOverlay() + end + end +end + +function ENT:Remove_Weld() + if self.Constraint then + if self.Constraint:IsValid() then + self.Constraint:Remove() + end + self.Constraint = nil + end +end + +function ENT:Create_Weld() + self:Remove_Weld() + self.Constraint = MakeWireLatch( self.Ent1, self.Ent2, self.Bone1, self.Bone2, self.weld_strength or 0 ) + + if self.Constraint then + self.Constraint:CallOnRemove( "Weld Latch Removed", Weld_Removed, self ) + end +end + +-- This function is called by the STOOL +function ENT:SendVars( Ent1, Ent2, Bone1, Bone2, const ) + self.Ent1 = Ent1 + self.Ent2 = Ent2 + self.Bone1 = Bone1 + self.Bone2 = Bone2 + self.Constraint = const +end + +function ENT:TriggerInput( iname, value ) + if iname == "Activate" then + if value == 0 and self.Constraint then + self:Remove_Weld() + + elseif value ~= 0 and not self.Constraint then + self:Create_Weld() + Wire_TriggerOutput( self, "Welded", 1 ) + end + + elseif iname == "NoCollide" then + self.nocollide_status = value + local mask = self.nocollide_masks[value] or {false, false, false} + + if IsValid( self.Ent1 ) then + local phys = self.Ent1:GetPhysicsObject() + if phys:IsValid() then phys:EnableCollisions(not mask[1]) end + end + + if IsValid( self.Ent2 ) then + local phys = self.Ent2:GetPhysicsObject() + if phys:IsValid() then phys:EnableCollisions(not mask[2]) end + end + + if mask[3] then + if not self.Nocollide then + if self.Ent1 and self.Ent2 then + -- enable NoCollide between the two entities + self.Nocollide = constraint.NoCollide( self.Ent1, self.Ent2, self.Bone1, self.Bone2 ) + end + end + else + if self.Nocollide then + if self.Nocollide:IsValid() then + -- disable NoCollide between the two entities + self.Nocollide:Input("EnableCollisions", nil, nil, nil) + self.Nocollide:Remove() + end + self.Nocollide = nil + end + end + + elseif iname == "Strength" then + local newvalue = math.max( value, 0 ) + if newvalue ~= self.weld_strength then + self.weld_strength = newvalue + + if self.Constraint then + self:Create_Weld() + end + end + + end + + self:UpdateOverlay() +end + +function ENT:OnRemove() + self:TriggerInput("Activate", 0) + self:TriggerInput("NoCollide", 0) +end + +function ENT:UpdateOverlay() + local desc = self.nocollide_description[self.nocollide_status] + if not desc then + if IsValid( self.Constraint ) then + self:SetOverlayText( "Welded" ) + else + self:SetOverlayText( "Deactivated" ) + end + return + end + local text = self.Constraint and "Welded and " or "Not welded but " + text = text .. desc + self:SetOverlayText( text ) +end + +-- duplicator support +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if IsValid( self.Ent1 ) then + info.Ent1 = self.Ent1:EntIndex() + info.Bone1 = self.Bone1 + end + if IsValid( self.Ent2 ) then + info.Ent2 = self.Ent2:EntIndex() + info.Bone2 = self.Bone2 + end + + info.Activate = self.Constraint and 1 or 0 + info.NoCollide = self.nocollide_status + info.weld_strength = self.weld_strength + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.Ent1 = GetEntByID(info.Ent1, game.GetWorld()) + if IsValid(self.Ent1) then + self.Bone1 = info.Bone1 + end + + self.Ent2 = GetEntByID(info.Ent2, game.GetWorld()) + if IsValid(self.Ent2) then + self.Bone2 = info.Bone2 + end + + self:TriggerInput("Strength", info.weld_strength or 0) + self:TriggerInput("Activate", info.Activate) + self:TriggerInput("NoCollide", info.NoCollide) +end + +duplicator.RegisterEntityClass("gmod_wire_latch", WireLib.MakeWireEnt, "Data") + +function MakeWireLatch( Ent1, Ent2, Bone1, Bone2, forcelimit ) + if ( !constraint.CanConstrain( Ent1, Bone1 ) ) then return false end + if ( !constraint.CanConstrain( Ent2, Bone2 ) ) then return false end + + local Phys1 = Ent1:GetPhysicsObjectNum( Bone1 ) + local Phys2 = Ent2:GetPhysicsObjectNum( Bone2 ) + + if ( Phys1 == Phys2 ) then return false end + + local const = constraint.Weld( Ent1, Ent2, Bone1, Bone2, forcelimit or 0 ) + + if !IsValid(const) then return nil end + + const.Type = "" -- prevents the duplicator from copying this weld + + return const +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_lever.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_lever.lua new file mode 100644 index 0000000..e4fc320 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_lever.lua @@ -0,0 +1,282 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Analog Lever" +ENT.WireDebugName = "Lever" + +function ENT:CalcAngle(shootPos, shootDir) + local myPos = self:GetPos() + local right = self:GetRight() + + local planeHitPos = self:WorldToLocal(shootPos + shootDir * ((myPos - shootPos):Dot(right) / shootDir:Dot(right))) + + self.Ang = math.Clamp( math.deg( math.atan2( planeHitPos[1], planeHitPos[3] ) ), -45, 45 ) +end + +if CLIENT then + + function ENT:Initialize() + self.RBMin, self.RBMax = self:GetRenderBounds() + self.RBMin:Add(Vector(-30,0,0)) + self.RBMax:Add(Vector(30,0,60)) + end + + local RenderGroup = ENT.RenderGroup + + function ENT:CreateCSModel() + if self.csmodelLoading or IsValid(self.csmodel) then return end + self.csmodelLoading = true + timer.Simple(0, function() + if not IsValid(self) then return end + self.csmodel = octolib.createDummy("models/props_wasteland/tram_lever01.mdl", RenderGroup) + self.csmodel:SetParent(self) + self.NextRBUpdate = 0 + self.csmodelLoading = false + end) + end + + function ENT:Draw() + if not IsValid(self.csmodel) then + return self:CreateCSModel() + end + + -- If user, calculate clientside, otherwise get server value + self.User = self:GetNWEntity("User",NULL) + if IsValid(self.User) then + self:CalcAngle(self.User:GetShootPos(), self.User:GetAimVector()) + else + self.Ang = self:GetNWFloat("Ang",0) -- get networked ang + end + + local lever_ang = Angle(self.Ang,0,0) + local ang = self:LocalToWorldAngles(lever_ang) + local pos = self:LocalToWorld(lever_ang:Up() * 21) + + render.Model({ + model = self.csmodel:GetModel(), + pos = pos, + angle = ang + }, self.csmodel) + + BaseClass.Draw(self) + end + + function ENT:Think() + if (CurTime() >= (self.NextRBUpdate or 0)) then + self.NextRBUpdate = CurTime() + 10 + self:SetRenderBounds(self.RBMin, self.RBMax) + end + + local isClicking = LocalPlayer():KeyDown(IN_USE) or LocalPlayer():KeyDown(IN_ATTACK) + if isClicking and not self.wasClicking and IsValid(self.csmodel) then + local aimPos = LocalPlayer():GetShootPos() + if aimPos:DistToSqr(self:GetPos())<100^2 then + local rayPos = util.IntersectRayWithOBB( + aimPos, + LocalPlayer():GetAimVector() * 100, + self.csmodel:GetPos(), + self.csmodel:GetAngles(), + self.csmodel:OBBMins() - Vector(2,2,2), + self.csmodel:OBBMaxs() + Vector(2,2,2) + ) + if rayPos then + net.Start("wire_lever_activate") + net.WriteEntity(self) + net.SendToServer() + end + end + end + self.wasClicking = isClicking + + -- Don't call baseclass think or else renderbounds will be overwritten + end +else + util.PrecacheModel( "models/props_wasteland/tram_lever01.mdl" ) + + function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.Ang = 0 + self.Value = 0 + self:Setup(0, 1) + + self.Inputs = WireLib.CreateInputs(self, {"SetValue", "Min", "Max"}) + self.Outputs = WireLib.CreateOutputs(self, {"Value", "Entity [ENTITY]"}) + end + + function ENT:Setup(min, max) + min = min or 0 + max = max or 1 + self.Min = math.min(min, max) + self.Max = math.max(min, max) + end + + function ENT:TriggerInput(iname, value) + if iname == "SetValue" then + self.Ang = (math.Clamp(value, self.Min, self.Max) - self.Min)/(self.Max - self.Min) * 90 - 45 + elseif (iname == "Min") then + self.Min = value + elseif (iname == "Max") then + self.Max = value + end + end + + function ENT:Use( ply ) + if not IsValid(ply) or not ply:IsPlayer() or IsValid(self.User) then return end + self.User = ply + WireLib.TriggerOutput( self, "Entity", ply) + self:SetNWEntity("User",self.User) + end + + util.AddNetworkString("wire_lever_activate") + net.Receive("wire_lever_activate", function(netlen, ply) + local ent = net.ReadEntity() + if not IsValid(ply) or not IsValid(ent) or not ent.Use or ent:GetClass() ~= "gmod_wire_lever" then return end + if IsValid(ent.User) then return end + + if ply:GetShootPos():DistToSqr(ent:GetPos()) < 100^2 then + ent:Use(ply, ply, USE_ON, 1) + end + end) + + function ENT:Think() + BaseClass.Think(self) + + if IsValid(self.User) then + local shootPos = self.User:GetShootPos() + if shootPos:DistToSqr(self:GetPos()) < 100^2 and (self.User:KeyDown(IN_USE) or self.User:KeyDown(IN_ATTACK)) then + local shootDir = self.User:GetAimVector() + self:CalcAngle(shootPos, shootDir) + else + self.User = NULL + WireLib.TriggerOutput( self, "Entity", NULL) + self:SetNWEntity("User",self.User) + end + end + + local oldvalue = self.Value + self.Value = Lerp((self.Ang + 45) / 90, self.Min, self.Max) + if self.Value ~= oldvalue then + WireLib.TriggerOutput(self, "Value", self.Value) + self:ShowOutput() + self:SetNWFloat("Ang",self.Ang) + end + + self:NextThink(CurTime()) + return true + end + + function ENT:ShowOutput() + self:SetOverlayText(string.format("(%.2f - %.2f) = %.2f", self.Min, self.Max, self.Value)) + end + + function ENT:ConvertFromOldLever(base) + -- remove all constraints from self + self:SetParent() + constraint.RemoveAll(self) + + local original_solid = self:GetSolid() + local original_motion = self:GetPhysicsObject():IsMotionEnabled() + + -- remove collisions and freeze to prevent the entity from flying away + self:SetNotSolid(true) + self:GetPhysicsObject():EnableMotion(false) + + -- change model and move into new position + self:SetModel("models/props_wasteland/tram_leverbase01.mdl") + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetPos(base:GetPos()) + self:SetAngles(base:GetAngles()) + + timer.Simple(0,function() -- give the setpos time to be applied + if not IsValid(self) then return end + + -- make copies of welds and nocollides and + -- move the constraints to self instead of base + -- we're only doing welds and nocollides to avoid any strange + -- issues, I think it's good enough :tm: + if base.Constraints then + for _, con in pairs( base.Constraints ) do + local Ent1 = con.Ent1 + local Ent2 = con.Ent2 + local Bone1 = con.Bone1 + local Bone2 = con.Bone2 + + -- Move the target entity from base to self + if Ent1 == base then Ent1 = self + elseif Ent2 == base then Ent2 = self end + + if con.Type == "Weld" then + local ForceLimit = con.forcelimit + local NoCollide = con.nocollide + local DeleteOnBreak = false -- can't be copied easily, so we'll assume it's false to save us the trouble + + constraint.Weld(Ent1,Ent2,Bone1,Bone2,ForceLimit,NoCollide,DeleteOnBreak) + elseif con.Type == "NoCollide" then + constraint.NoCollide(Ent1,Ent2,Bone1,Bone2) + end + end + end + + -- copy parent + self:SetParent(base:GetParent()) + base:Remove() + + -- reset original values + self:SetNotSolid(not original_solid) + self:GetPhysicsObject():EnableMotion(original_motion) + end) + end + + local fix_after_dupe = setmetatable({},{__mode="kv"}) + hook.Add("AdvDupe_FinishPasting","LeverFixOldDupe",function(data) + if next(fix_after_dupe) == nil then return end + + local levers = {} + for __, ent in pairs( data[1].CreatedEntities ) do + if ent:GetClass()=="gmod_wire_lever" then + levers[ent] = true + end + end + -- this hook is also called on garrydupe's paste, thanks to wirelib.lua + for self, base in pairs(fix_after_dupe) do + if base:IsValid() and self:IsValid() then + if levers[self] then + self:ConvertFromOldLever(base) + fix_after_dupe[self] = nil + end + else + if base:IsValid() then base:Remove() end + if self:IsValid() then self:Remove() end + fix_after_dupe[self] = nil + end + end + end) + + function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + info.value = self.Value + return info + end + + function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + -- this is only used to update the entity to the latest version + -- if it's found to be an old dupe + if info.baseent then + local base = GetEntByID(info.baseent) + fix_after_dupe[self] = base + end + if info.value then + self.Value = nil -- So the value is dirty no matter what + self:TriggerInput("SetValue", info.value) + end + + end + + duplicator.RegisterEntityClass("gmod_wire_lever", WireLib.MakeWireEnt, "Data", "Min", "Max" ) + +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_light.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_light.lua new file mode 100644 index 0000000..cb75f4c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_light.lua @@ -0,0 +1,257 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Light" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Light" + +function ENT:SetupDataTables() + self:NetworkVar( "Bool", 0, "Glow" ) + self:NetworkVar( "Float", 0, "Brightness" ) + self:NetworkVar( "Float", 1, "LightSize" ) + self:NetworkVar( "Int", 0, "R" ) + self:NetworkVar( "Int", 1, "G" ) + self:NetworkVar( "Int", 2, "B" ) +end + +if CLIENT then + local matLight = Material( "sprites/light_ignorez" ) + local matBeam = Material( "effects/lamp_beam" ) + + function ENT:Initialize() + self.PixVis = util.GetPixelVisibleHandle() + + --[[ + This is some unused value which is used to activate the wire overlay. + We're not using it because we are using NetworkVars instead, since wire + overlays only update when you look at them, and we want to update the + sprite colors whenever the wire input changes. ]] + self:SetOverlayData({}) + end + + function ENT:GetMyColor() + return Color( self:GetR(), self:GetG(), self:GetB(), 255 ) + end + + function ENT:DrawTranslucent() + local up = self:GetAngles():Up() + + local LightPos = self:GetPos() + render.SetMaterial( matLight ) + + local ViewNormal = self:GetPos() - EyePos() + local Distance = ViewNormal:Length() + ViewNormal:Normalize() + + local Visible = util.PixelVisible( LightPos, 4, self.PixVis ) + + if not Visible or Visible < 0.1 then return end + + local c = self:GetMyColor() + + if self:GetModel() == "models/maxofs2d/light_tubular.mdl" then + render.DrawSprite( LightPos - up * 2, 8, 8, Color(255, 255, 255, 255), Visible ) + render.DrawSprite( LightPos - up * 4, 8, 8, Color(255, 255, 255, 255), Visible ) + render.DrawSprite( LightPos - up * 6, 8, 8, Color(255, 255, 255, 255), Visible ) + render.DrawSprite( LightPos - up * 5, 128, 128, c, Visible ) + else + render.DrawSprite( self:LocalToWorld( self:OBBCenter() ), 128, 128, c, Visible ) + end + end + + local wire_light_block = CreateClientConVar("wire_light_block", 0, false, false) + + function ENT:Think() + if self:GetGlow() and not wire_light_block:GetBool() then + local dlight = DynamicLight(self:EntIndex()) + if dlight then + dlight.Pos = self:GetPos() + + local c = self:GetMyColor() + dlight.r = c.r + dlight.g = c.g + dlight.b = c.b + + dlight.Brightness = self:GetBrightness() + dlight.Decay = self:GetLightSize() * 5 + dlight.Size = self:GetLightSize() + dlight.DieTime = CurTime() + 1 + end + end + end + + local color_box_size = 64 + function ENT:GetWorldTipBodySize() + -- text + local w_total,h_total = surface.GetTextSize( "Color:\n255,255,255,255" ) + + -- Color box width + w_total = math.max(w_total,color_box_size) + + -- Color box height + h_total = h_total + 18 + color_box_size + 18/2 + + return w_total, h_total + end + + local white = Color(255,255,255,255) + local black = Color(0,0,0,255) + + local function drawColorBox( color, x, y ) + surface.SetDrawColor( color ) + surface.DrawRect( x, y, color_box_size, color_box_size ) + + local size = color_box_size + + surface.SetDrawColor( black ) + surface.DrawLine( x, y, x + size, y ) + surface.DrawLine( x + size, y, x + size, y + size ) + surface.DrawLine( x + size, y + size, x, y + size ) + surface.DrawLine( x, y + size, x, y ) + end + + function ENT:DrawWorldTipBody( pos ) + -- get color + local color = self:GetMyColor() + + -- text + local color_text = string.format("Color:\n%d,%d,%d",color.r,color.g,color.b) + + local w,h = surface.GetTextSize( color_text ) + draw.DrawText( color_text, "GModWorldtip", pos.center.x, pos.min.y + pos.edgesize, white, TEXT_ALIGN_CENTER ) + + -- color box + drawColorBox( color, pos.center.x - color_box_size / 2, pos.min.y + pos.edgesize * 1.5 + h ) + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = WireLib.CreateInputs(self, {"Red", "Green", "Blue", "RGB [VECTOR]"}) +end + +function ENT:Directional( On ) + if On then + if IsValid( self.DirectionalComponent ) then return end + + local flashlight = ents.Create( "env_projectedtexture" ) + flashlight:SetParent( self ) + + -- The local positions are the offsets from parent.. + flashlight:SetLocalPos( Vector( 0, 0, 0 ) ) + flashlight:SetLocalAngles( Angle( -90, 0, 0 ) ) + if self:GetModel() == "models/maxofs2d/light_tubular.mdl" then + flashlight:SetLocalAngles( Angle( 90, 0, 0 ) ) + end + + -- Looks like only one flashlight can have shadows enabled! + flashlight:SetKeyValue( "enableshadows", 1 ) + flashlight:SetKeyValue( "farz", 1024 ) + flashlight:SetKeyValue( "nearz", 12 ) + flashlight:SetKeyValue( "lightfov", 90 ) + + local c = self:GetColor() + local b = self.brightness + flashlight:SetKeyValue( "lightcolor", Format( "%i %i %i 255", c.r * b, c.g * b, c.b * b ) ) + + flashlight:Spawn() + flashlight:Input( "SpotlightTexture", NULL, NULL, "effects/flashlight001" ) + + self.DirectionalComponent = flashlight + elseif IsValid( self.DirectionalComponent ) then + self.DirectionalComponent:Remove() + self.DirectionalComponent = nil + end +end + +function ENT:Radiant( On ) + if On then + if IsValid( self.RadiantComponent ) then + self.RadiantComponent:Fire( "TurnOn", "", "0" ) + else + local dynlight = ents.Create( "light_dynamic" ) + dynlight:SetPos( self:GetPos() ) + local dynlightpos = dynlight:GetPos() + Vector( 0, 0, 10 ) + dynlight:SetPos( dynlightpos ) + dynlight:SetKeyValue( "_light", Format( "%i %i %i 255", self.R, self.G, self.B ) ) + dynlight:SetKeyValue( "style", 0 ) + dynlight:SetKeyValue( "distance", 255 ) + dynlight:SetKeyValue( "brightness", 5 ) + dynlight:SetParent( self ) + dynlight:Spawn() + + self.RadiantComponent = dynlight + end + elseif IsValid( self.RadiantComponent ) then + self.RadiantComponent:Fire( "TurnOff", "", "0" ) + end +end + +function ENT:UpdateLight() + self:SetR( self.R ) + self:SetG( self.G ) + self:SetB( self.B ) + + if IsValid( self.DirectionalComponent ) then self.DirectionalComponent:SetKeyValue( "lightcolor", Format( "%i %i %i 255", self.R * self.brightness, self.G * self.brightness, self.B * self.brightness ) ) end + if IsValid( self.RadiantComponent ) then self.RadiantComponent:SetKeyValue( "_light", Format( "%i %i %i 255", self.R, self.G, self.B ) ) end +end + +function ENT:TriggerInput(iname, value) + if (iname == "Red") then + self.R = math.Clamp(value,0,255) + elseif (iname == "Green") then + self.G = math.Clamp(value,0,255) + elseif (iname == "Blue") then + self.B = math.Clamp(value,0,255) + elseif (iname == "RGB") then + self.R, self.G, self.B = math.Clamp(value[1],0,255), math.Clamp(value[2],0,255), math.Clamp(value[3],0,255) + elseif (iname == "GlowBrightness") then + if not game.SinglePlayer() then value = math.Clamp( value, 0, 10 ) end + self.brightness = value + self:SetBrightness( value ) + elseif (iname == "GlowSize") then + if not game.SinglePlayer() then value = math.Clamp( value, 0, 1024 ) end + self.size = value + self:SetLightSize( value ) + end + + self:UpdateLight() +end + +function ENT:Setup(directional, radiant, glow, brightness, size, r, g, b) + self.directional = directional or false + self.radiant = radiant or false + self.glow = glow or false + self.brightness = brightness or 2 + self.size = size or 256 + self.R = r or 255 + self.G = g or 255 + self.B = b or 255 + + if not game.SinglePlayer() then + self.brightness = math.Clamp( self.brightness, 0, 10 ) + self.size = math.Clamp( self.size, 0, 1024 ) + end + + self:Directional( self.directional ) + self:Radiant( self.radiant ) + self:SetGlow( self.glow ) + self:SetBrightness( self.brightness ) + self:SetLightSize( self.size ) + + if self.glow then + WireLib.AdjustInputs(self, {"Red", "Green", "Blue", "RGB [VECTOR]", "GlowBrightness", "GlowSize"}) + else + WireLib.AdjustInputs(self, {"Red", "Green", "Blue", "RGB [VECTOR]"}) + end + + self:UpdateLight() +end + +duplicator.RegisterEntityClass("gmod_wire_light", WireLib.MakeWireEnt, "Data", "directional", "radiant", "glow", "brightness", "size", "R", "G", "B") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_locator.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_locator.lua new file mode 100644 index 0000000..39ecebe --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_locator.lua @@ -0,0 +1,21 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Locator Beacon" +ENT.WireDebugName = "Locator" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) +end + +function ENT:GetBeaconPos(sensor) + return self:GetPos() +end +function ENT:GetBeaconVelocity(sensor) + return self:GetVelocity() +end + +duplicator.RegisterEntityClass("gmod_wire_locator", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_motor.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_motor.lua new file mode 100644 index 0000000..c1b7793 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_motor.lua @@ -0,0 +1,127 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Motor" +ENT.WireDebugName = "Motor" + +if CLIENT then + ENT.RenderGroup = RENDERGROUP_BOTH + return +end + +function ENT:Initialize() + BaseClass.Initialize(self) + self.Inputs = Wire_CreateInputs( self, { "Mul" } ) +end + +function ENT:SetConstraint( c ) + self.constraint = c + self.Mul = 0 + self:ShowOutput() +end + +function ENT:SetAxis( a ) + self.axis = a +end + +function ENT:TriggerInput(iname, value) + if iname == "Mul" then + self.Mul = value + self:ShowOutput() + local Motor = self.constraint + if not IsValid( Motor ) then + return false + end + Motor:Fire( "Scale", value, 0 ) + Motor:Fire( "Activate", "" , 0 ) + end +end + +function ENT:ShowOutput() + if self.constraint and IsValid( self.constraint ) then + + local torque = self.constraint.torque + local current_torque = torque * self.Mul + local forcelimit = self.constraint.forcelimit + local friction = self.constraint.friction + + self:SetOverlayText( string.format( "Current Torque: %s\nTorque: %s\nForce Limit: %s\nHinge Friction: %s", current_torque, torque, forcelimit, friction ) ) + end +end + +--needed for the constraint to find the controller after being duplicator pasted +local WireMotorTracking = {} + +function MakeWireMotorController( pl, Pos, Ang, MyEntId, model, const, axis ) + local controller = WireLib.MakeWireEnt(pl, {Class = "gmod_wire_motor", Pos=Pos, Angle=Ang, Model=model}) + if not IsValid(controller) then return end + + if not const then + WireMotorTracking[ MyEntId ] = controller + else + controller.MyId = controller:EntIndex() + const.MyCrtl = controller:EntIndex() + controller:SetConstraint( const ) + controller:DeleteOnRemove( const ) + end + + if axis then + controller:SetAxis( axis ) + controller:DeleteOnRemove( axis ) + end + + return controller +end +duplicator.RegisterEntityClass("gmod_wire_motor", MakeWireMotorController, "Pos", "Ang", "MyId", "model") + +function MakeWireMotor( pl, Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, friction, torque, nocollide, forcelimit, MyCrtl ) + if not constraint.CanConstrain( Ent1, Bone1 ) then return false end + if not constraint.CanConstrain( Ent2, Bone2 ) then return false end + + local Phys1 = Ent1:GetPhysicsObjectNum( Bone1 ) + local Phys2 = Ent2:GetPhysicsObjectNum( Bone2 ) + local WPos1 = Phys1:LocalToWorld( LPos1 ) + local WPos2 = Phys2:LocalToWorld( LPos2 ) + + if Phys1 == Phys2 then return false end + + local const, axis = constraint.Motor( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, friction, torque, 0, nocollide, 0, pl, forcelimit ) + + if not const then return nil, axis end + + local ctable = { + Type = "WireMotor", + pl = pl, + Ent1 = Ent1, + Ent2 = Ent2, + Bone1 = Bone1, + Bone2 = Bone2, + LPos1 = LPos1, + LPos2 = LPos2, + friction = friction, + torque = torque, + nocollide = nocollide, + forcelimit = forcelimit + } + const:SetTable( ctable ) + + if MyCrtl then + local controller = WireMotorTracking[ MyCrtl ] + + const.MyCrtl = controller:EntIndex() + controller.MyId = controller:EntIndex() + + controller:SetConstraint( const ) + controller:DeleteOnRemove( const ) + if axis then + controller:SetAxis( axis ) + controller:DeleteOnRemove( axis ) + end + + Ent1:DeleteOnRemove( controller ) + Ent2:DeleteOnRemove( controller ) + const:DeleteOnRemove( controller ) + end + + return const, axis +end +duplicator.RegisterConstraint( "WireMotor", MakeWireMotor, "pl", "Ent1", "Ent2", "Bone1", "Bone2", "LPos1", "LPos2", "friction", "torque", "nocollide", "forcelimit", "MyCrtl" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_nailer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_nailer.lua new file mode 100644 index 0000000..1c05e1c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_nailer.lua @@ -0,0 +1,108 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Nailer" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Nailer" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) + self:NetworkVar( "Bool", 0, "ShowBeam" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = WireLib.CreateInputs(self, { "Weld", "Axis", "Ballsocket" }) + self:SetBeamLength(2048) +end + +function ENT:Setup(flim, Range, ShowBeam) + self.Flim = math.Clamp(flim, 0, 10000) + if Range then self:SetBeamLength(Range) end + if ShowBeam ~= nil then self:SetShowBeam(ShowBeam) end + self:ShowOutput() +end + +function ENT:CanNail(trace) + -- Bail if we hit world or a player + if not IsValid(trace.Entity) or trace.Entity:IsPlayer() then return false end + -- If there's no physics object then we can't constraint it! + if not util.IsValidPhysicsObject(trace.Entity, trace.PhysicsBone) then return false end + -- The nailer tool no longer exists, but we ask for permission under its name anyway + if hook.Run( "CanTool", self:GetPlayer(), trace, "nailer" ) == false then return false end + return true +end + +function ENT:TriggerInput(name, value) + if value == 0 then return end + + local up = self:GetUp() + + local trace1 = util.TraceLine( { + start = self:GetPos(), + endpos = self:GetPos() + up * self:GetBeamLength(), + filter = { self } + } ) + + if not self:CanNail( trace1 ) then return end + + local trace2 = util.TraceLine( { + start = trace1.HitPos, + endpos = trace1.HitPos + up * 50, + filter = { trace1.Entity, self } + } ) + + if not self:CanNail( trace2 ) then return end + + if name == "Weld" then + constraint.Weld( trace1.Entity, + trace2.Entity, + trace1.PhysicsBone, + trace2.PhysicsBone, + self.Flim + ) + elseif name == "Axis" then + local phys1 = trace1.Entity:GetPhysicsObject() + local phys2 = trace2.Entity:GetPhysicsObject() + if not IsValid( phys1 ) or not IsValid( phys2 ) then return end + + local LPos1 = phys1:WorldToLocal( trace2.HitPos + trace2.HitNormal ) + local LPos2 = phys2:WorldToLocal( trace2.HitPos ) + + constraint.Axis( trace1.Entity, + trace2.Entity, + trace1.PhysicsBone, + trace2.PhysicsBone, + LPos1, LPos2, + self.Flim + ) + elseif name == "Ballsocket" then + constraint.Ballsocket( trace1.Entity, + trace2.Entity, + trace1.PhysicsBone, + trace2.PhysicsBone, + trace2.Entity:WorldToLocal(trace1.HitPos), + self.Flim + ) + end + + -- effect on weld (tomb332) + local effectdata = EffectData() + effectdata:SetOrigin( trace2.HitPos ) + effectdata:SetNormal( trace1.HitNormal ) + effectdata:SetMagnitude( 5 ) + effectdata:SetScale( 1 ) + effectdata:SetRadius( 10 ) + util.Effect( "Sparks", effectdata, false, true ) +end + +function ENT:ShowOutput() + self:SetOverlayText(string.format( "Range: %s\nForce limit: %s", math.Round(self:GetBeamLength(),2), math.Round(self.Flim,2) )) +end + +WireLib.AddInputAlias( "A", "Weld" ) + +duplicator.RegisterEntityClass("gmod_wire_nailer", WireLib.MakeWireEnt, "Data", "Flim", "Range", "ShowBeam") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_numpad.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_numpad.lua new file mode 100644 index 0000000..e13fbd2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_numpad.lua @@ -0,0 +1,123 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Numpad" +ENT.WireDebugName = "Numpad" + +if CLIENT then return end -- No more client + +local keynames = {"0","1","2","3","4","5","6","7","8","9",".","enter","+","-","*","/"} -- Names as we will display them and for inputs/outputs +local keyenums = {37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 52, 51, 50, 49, 48, 47} -- Same indexes as keynames, values are the corresponding KEY_* enums +local nametoenum = {} +for k,v in ipairs(keynames) do nametoenum[v] = keyenums[k] end -- Indexes are string input/output names, values are the corresponding KEY_* enums + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs(self, keynames) + self.Outputs = Wire_CreateOutputs(self, keynames) + + self.Buffer = {} + for i = 1, #keynames do + self.Buffer[i] = 0 + end +end + +-- These two high speed functions want to access a zero indexed array of what keys are pressed (0-15), our buffer is 1-16 +function ENT:ReadCell( Address ) + Address = math.floor(Address) + if (Address >= 0) && (Address < #keynames) then + return self.Buffer[Address+1] + else + return nil + end +end + +function ENT:WriteCell( Address, value ) + Address = math.floor(Address) + if (Address >= 0) && (Address < #keynames) then + self:TriggerInput(keynames[Address+1], value) + return true + else + return false + end +end + +function ENT:TriggerInput(key, value) + if value ~= 0 then + numpad.Activate( self:GetPlayer(), nametoenum[key], true ) + else + numpad.Deactivate( self:GetPlayer(), nametoenum[key], true ) + end +end + +function ENT:Setup( toggle, value_off, value_on) + self.toggle = toggle + self.value_off = value_off + self.value_on = value_on + + self.impulses = {} + for k,keyenum in ipairs(keyenums) do + table.insert(self.impulses, numpad.OnDown( self:GetPlayer(), keyenum, "WireNumpad_On", self, k )) + table.insert(self.impulses, numpad.OnUp( self:GetPlayer(), keyenum, "WireNumpad_Off", self, k )) + end + + self:ShowOutput() +end + +function ENT:NumpadActivate( key ) + if ( self.toggle ) then + return self:Switch( self.Buffer[ key ] == 0, key ) + end + + return self:Switch( true, key ) +end + +function ENT:NumpadDeactivate( key ) + if ( self.toggle ) then return true end + + return self:Switch( false, key ) +end + +function ENT:Switch( on, key ) + if (!self:IsValid()) then return false end + + self.Buffer[key] = on and 1 or 0 + + self:ShowOutput() + self.Value = on and self.value_on or self.value_off + + Wire_TriggerOutput(self, keynames[key], self.Value) + + return true +end + +function ENT:ShowOutput() + local txt = "" + for k,keyname in ipairs(keynames) do + if (self.Buffer[k] ~= 0) then + txt = txt..", "..keyname + end + end + + self:SetOverlayText( string.sub(txt,2) ) +end + +function ENT:OnRemove() + for _,impulse in ipairs(self.impulses) do + numpad.Remove(impulse) + end +end + +local function On( pl, ent, key ) + return ent:NumpadActivate( key ) +end +numpad.Register( "WireNumpad_On", On ) + +local function Off( pl, ent, key ) + return ent:NumpadDeactivate( key ) +end +numpad.Register( "WireNumpad_Off", Off ) + +duplicator.RegisterEntityClass("gmod_wire_numpad", WireLib.MakeWireEnt, "Data", "toggle", "value_off", "value_on") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_oscilloscope.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_oscilloscope.lua new file mode 100644 index 0000000..a1ba486 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_oscilloscope.lua @@ -0,0 +1,188 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Oscilloscope" +ENT.WireDebugName = "Oscilloscope" + +if CLIENT then + function ENT:Initialize() + self.GPU = WireGPU(self) + + self.Nodes = {} + end + + function ENT:OnRemove() + self.GPU:Finalize() + end + + function ENT:GetNodeList() + return self.Nodes + end + + function ENT:AddNode(x,y) + self.Nodes[#self.Nodes+1] = { + X = x, + Y = y + } + + if #self.Nodes > self:GetNWFloat("Length",50) then + for i=#self.Nodes,self:GetNWFloat("Length",50),-1 do + table.remove(self.Nodes,1) + end + end + end + + net.Receive( "wire_oscilloscope_send_node", function( length ) + local ent = net.ReadEntity() + local x = net.ReadFloat() + local y = net.ReadFloat() + + if IsValid(ent) then + ent:AddNode(x,y) + end + end) + + function ENT:Draw() + self:DrawModel() + + local length = self:GetNWFloat("Length", 50) + local r,g,b = self:GetNWFloat("R"), self:GetNWFloat("G"), self:GetNWFloat("B") + if r == 0 and g == 0 and b == 0 then g = 200 end + + self.GPU:RenderToGPU(function() + surface.SetDrawColor(10,20,5,255) + surface.DrawRect(0,0,512,512) + + local nodes = self:GetNodeList() + for i=1,length do + local i_next = i+1 + if not nodes[i_next] then continue end + + local nx1 = nodes[i].X*256+256 + local ny1 = -nodes[i].Y*256+256 + local nx2 = nodes[i_next].X*256+256 + local ny2 = -nodes[i_next].Y*256+256 + + if ((nx1-nx2)*(nx1-nx2) + (ny1-ny2)*(ny1-ny2) < 256*256) then + local a = math.max(1, 3.75-(3*i)/length)^1.33 + local a2 = math.max(1, a/2) + + for i=-3,3 do + surface.SetDrawColor(r/a, g/a, b/a, 255) + surface.DrawLine(nx1, ny1+i, nx2, ny2+i) + surface.SetDrawColor(r/a, g/a, b/a, 255) + surface.DrawLine(nx1+i, ny1, nx2+i, ny2) + end + + surface.SetDrawColor(r/a2, g/a2, b/a2, 255) + surface.DrawLine(nx1, ny1, nx2, ny2) + end + end + + surface.SetDrawColor(30, 120, 10, 255) + surface.DrawLine(0, 128, 512, 128) + surface.DrawLine(0, 384, 512, 384) + surface.DrawLine(128, 0, 128, 512) + surface.DrawLine(384, 0, 384, 512) + + surface.SetDrawColor(180, 200, 10, 255) + surface.DrawLine(0, 256, 512, 256) + surface.DrawLine(256, 0, 256, 512) + end) + + self.GPU:Render() + Wire_Render(self) + end + + return -- No more client +end + +-- Server + +local wire_oscilloscope_maxlength = CreateConVar("wire_oscilloscope_maxlength", 100, {FCVAR_ARCHIVE}, "Maximum number of nodes") + +util.AddNetworkString( "wire_oscilloscope_send_node" ) +function ENT:SetNextNode(x, y) + net.Start("wire_oscilloscope_send_node") + net.WriteEntity(self) + net.WriteFloat(x) + net.WriteFloat(y) + net.SendPVS( self:GetPos() ) +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = WireLib.CreateInputs(self, { "X", "Y", "R", "G", "B", "Pause", "Length", "Update Frequency" }) +end + +function ENT:Think() + if (self.Inputs.Pause.Value == 0) then + BaseClass.Think(self) + + local x = math.max(-1, math.min(self.Inputs.X.Value or 0, 1)) + local y = math.max(-1, math.min(self.Inputs.Y.Value or 0, 1)) + self:SetNextNode(x, y) + + self:NextThink(CurTime()+(self.updaterate or 0.08)) + return true + end +end + +function ENT:TriggerInput(iname, value) + if iname == "R" then + self:SetNWFloat("R", math.Clamp(value, 0, 255)) + elseif iname == "G" then + self:SetNWFloat("G", math.Clamp(value, 0, 255)) + elseif iname == "B" then + self:SetNWFloat("B", math.Clamp(value, 0, 255)) + elseif iname == "Length" then + if value == 0 then value = 50 end + self:SetNWFloat("Length", math.Clamp(value, 1, wire_oscilloscope_maxlength:GetInt())) + elseif iname == "Update Frequency" then + if value <= 0 then value = 0.08 end + self.updaterate = value + end +end + +--[[ + hi-speed Addresses: + 0: X + 1: Y + 2: R + 3: G + 4: B + 5: Length + 6: Update frequency +]] +local address_lookup = {nil,nil,"R","G","B","Length","Update Frequency"} +function ENT:WriteCell( address, value ) + address = math.floor(address) + address = address + 1 + if address == 1 then + self.Inputs.X.Value = value + elseif address == 2 then + self.Inputs.Y.Value = value + elseif address_lookup[address] then + self:TriggerInput( address_lookup[address], value ) + end +end + +function ENT:ReadCell( address ) + address = math.floor(address) + address = address + 1 + if address == 1 then + return self.Inputs.X.Value + elseif address == 2 then + return self.Inputs.Y.Value + elseif address == 4 then + return self.updaterate + elseif address_lookup[address] then + return self:GetNWFloat( address_lookup[address] ) + end + + return 0 +end + +duplicator.RegisterEntityClass("gmod_wire_oscilloscope", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_output.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_output.lua new file mode 100644 index 0000000..cf8dcba --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_output.lua @@ -0,0 +1,66 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Numpad Output" +ENT.WireDebugName = "Numpad Output" + +if CLIENT then return end -- No more client + +local keylist = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","KP_INS","KP_END","KP_DOWNARROW","KP_PGDN","KP_LEFTARROW","KP_5","KP_RIGHTARROW","KP_HOME","KP_UPARROW","KP_PGUP","KP_SLASH","KP_MULTIPLY","KP_MINUS","KP_PLUS","KP_ENTER","KP_DEL","[","]","SEMICOLON","'","`",",",".","/","\\","-","=","ENTER","SPACE","BACKSPACE","TAB","CAPSLOCK","NUMLOCK","ESCAPE","SCROLLLOCK","INS","DEL","HOME","END","PGUP","PGDN","PAUSE","SHIFT","RSHIFT","ALT","RALT","CTRL","RCTRL","LWIN","RWIN","APP","UPARROW","LEFTARROW","DOWNARROW","RIGHTARROW","F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","CAPSLOCKTOGGLE","NUMLOCKTOGGLE","SCROLLLOCKTOGGLE"} + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:SetOn( false ) + + self.Inputs = Wire_CreateInputs(self, { "A" }) +end + +function ENT:TriggerInput(iname, value) + if (iname == "A") then + if ((value > 0) ~= self:IsOn()) then + self:Switch(not self:IsOn(), self:GetPlayer()) + end + end +end + +function ENT:Switch( on, ply ) + local plyindex = self:GetPlayerIndex() + local key = self:GetKey() + if (not key) then return end + + if (on) then + numpad.Activate( ply, key, true ) + else + numpad.Deactivate( ply, key, true ) + end + + self:SetOn(on) +end + +function ENT:ShowOutput() + if (self.key) then + self:SetOverlayText(keylist[self.key] or "") + end +end + +function ENT:Setup( key ) + if (numpad.GetModifiedKey) then key = numpad.GetModifiedKey(self:GetOwner(), key) end + self.key = key + self:ShowOutput() +end + +function ENT:GetKey() + return self.key +end + +function ENT:SetOn( on ) + self.On = on +end + +function ENT:IsOn() + return self.On +end + +duplicator.RegisterEntityClass("gmod_wire_output", WireLib.MakeWireEnt, "Data", "key") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_pixel.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_pixel.lua new file mode 100644 index 0000000..69bd8ad --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_pixel.lua @@ -0,0 +1,63 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Pixel" +ENT.WireDebugName = "Pixel" + +if CLIENT then + function ENT:Draw( ) + self:DrawModel( ) + end + + return -- No more client +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.R, self.G, self.B = 0, 0, 0 + self.Inputs = WireLib.CreateInputs( self, { "Red", "Green", "Blue", "PackedRGB", "RGB", "Color [VECTOR]" } ) +end + +function ENT:TriggerInput(iname, value) + local R,G,B = self.R, self.G, self.B + if (iname == "Red") then + R = value + elseif (iname == "Green") then + G = value + elseif (iname == "Blue") then + B = value + elseif (iname == "PackedRGB") then + B = value % 256 + G = ( value / 256 ) % 256 + R = ( value / ( 256 * 256 ) ) % 256 + elseif (iname == "RGB") then + local crgb = math.floor( value / 1000 ) + local cgray = value - math.floor( value / 1000 ) * 1000 + local cb = 24 * math.fmod( crgb, 10 ) + local cg = 24 * math.fmod( math.floor( crgb / 10 ), 10 ) + local cr = 24 * math.fmod( math.floor( crgb / 100 ), 10 ) + B = cgray + cb + G = cgray + cg + R = cgray + cr + elseif (iname == "Color") then + R = value.r + G = value.g + B = value.b + end + self:ShowOutput( math.floor( R ), math.floor( G ), math.floor( B ) ) +end + +function ENT:Setup() + self:ShowOutput( 0, 0, 0 ) +end + +function ENT:ShowOutput( R, G, B ) + if ( R ~= self.R or G ~= self.G or B ~= self.B ) then + self.R, self.G, self.B = R, G, B + self:SetColor(Color(R, G, B, 255)) + end +end + +duplicator.RegisterEntityClass("gmod_wire_pixel", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_plug.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_plug.lua new file mode 100644 index 0000000..ea5816c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_plug.lua @@ -0,0 +1,195 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Plug" +ENT.Author = "Divran" +ENT.Purpose = "Links with a socket" +ENT.Instructions = "Move a plug close to a socket to link them, and data will be transferred through the link." +ENT.WireDebugName = "Plug" + +function ENT:GetSocketClass() + return "gmod_wire_socket" +end + +function ENT:GetClosestSocket() + local sockets = ents.FindInSphere( self:GetPos(), 100 ) + + local ClosestDist + local Closest + + for k,v in pairs( sockets ) do + if (v:GetClass() == self:GetSocketClass() and not v:GetNWBool( "Linked", false )) then + local pos, _ = v:GetLinkPos() + local Dist = self:GetPos():Distance( pos ) + if (ClosestDist == nil or ClosestDist > Dist) then + ClosestDist = Dist + Closest = v + end + end + end + + return Closest +end + +if CLIENT then + function ENT:DrawEntityOutline() + if (GetConVar("wire_plug_drawoutline"):GetBool()) then + BaseClass.DrawEntityOutline( self ) + end + end + return -- No more client +end + +------------------------------------------------------------ +-- Helper functions & variables +------------------------------------------------------------ +local LETTERS = { "A", "B", "C", "D", "E", "F", "G", "H" } +local LETTERS_INV = {} +for k,v in pairs( LETTERS ) do + LETTERS_INV[v] = k +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:SetNWBool( "Linked", false ) + + self.Memory = {} +end + +function ENT:Setup( ArrayInput ) + local old = self.ArrayInput + self.ArrayInput = ArrayInput or false + + if not (self.Inputs and self.Outputs and self.ArrayInput == old) then + if (self.ArrayInput) then + self.Inputs = WireLib.CreateInputs( self, { "In [ARRAY]" } ) + self.Outputs = WireLib.CreateOutputs( self, { "Out [ARRAY]" } ) + else + self.Inputs = WireLib.CreateInputs( self, LETTERS ) + self.Outputs = WireLib.CreateOutputs( self, LETTERS ) + end + end + + self:ShowOutput() +end + +function ENT:TriggerInput( name, value ) + if (self.Socket and self.Socket:IsValid()) then + self.Socket:SetValue( name, value ) + end + self:ShowOutput() +end + +function ENT:SetValue( name, value ) + if not (self.Socket and self.Socket:IsValid()) then return end + if (name == "In") then + if (self.ArrayInput) then -- Both have array + WireLib.TriggerOutput( self, "Out", table.Copy( value ) ) + else -- Target has array, this does not + for i=1,#LETTERS do + local val = (value or {})[i] + if isnumber(val) then + WireLib.TriggerOutput( self, LETTERS[i], val ) + end + end + end + else + if (self.ArrayInput) then -- Target does not have array, this does + if (value ~= nil) then + local data = table.Copy( self.Outputs.Out.Value ) + data[LETTERS_INV[name]] = value + WireLib.TriggerOutput( self, "Out", data ) + end + else -- Niether have array + if (value ~= nil) then + WireLib.TriggerOutput( self, name, value ) + end + end + end + self:ShowOutput() +end + +------------------------------------------------------------ +-- WriteCell +-- Hi-speed support +------------------------------------------------------------ +function ENT:WriteCell( Address, Value, WriteToMe ) + Address = math.floor(Address) + if (WriteToMe) then + self.Memory[Address or 1] = Value or 0 + return true + else + if (self.Socket and self.Socket:IsValid()) then + self.Socket:WriteCell( Address, Value, true ) + return true + else + return false + end + end +end + +------------------------------------------------------------ +-- ReadCell +-- Hi-speed support +------------------------------------------------------------ +function ENT:ReadCell( Address ) + Address = math.floor(Address) + return self.Memory[Address or 1] or 0 +end + +function ENT:Think() + BaseClass.Think( self ) + self:SetNWBool( "PlayerHolding", self:IsPlayerHolding() ) +end + +function ENT:ResetValues() + if (self.ArrayInput) then + WireLib.TriggerOutput( self, "Out", {} ) + else + for i=1,#LETTERS do + WireLib.TriggerOutput( self, LETTERS[i], 0 ) + end + end + self.Memory = {} + self:ShowOutput() +end + +------------------------------------------------------------ +-- ResendValues +-- Resends the values when plugging in +------------------------------------------------------------ +function ENT:ResendValues() + if (not self.Socket) then return end + if (self.ArrayInput) then + self.Socket:SetValue( "In", self.Inputs.In.Value ) + else + for i=1,#LETTERS do + self.Socket:SetValue( LETTERS[i], self.Inputs[LETTERS[i]].Value ) + end + end +end + +function ENT:ShowOutput() + local OutText = "Plug [" .. self:EntIndex() .. "]\n" + if (self.ArrayInput) then + OutText = OutText .. "Array input/outputs." + else + OutText = OutText .. "Number input/outputs." + end + if (self.Socket and self.Socket:IsValid()) then + OutText = OutText .. "\nLinked to socket [" .. self.Socket:EntIndex() .. "]" + end + self:SetOverlayText(OutText) +end + +duplicator.RegisterEntityClass( "gmod_wire_plug", WireLib.MakeWireEnt, "Data", "ArrayInput" ) + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + if (info.Plug ~= nil) then + ent:Setup( info.Plug.ArrayInput ) + end + + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_pod.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_pod.lua new file mode 100644 index 0000000..9ee067e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_pod.lua @@ -0,0 +1,603 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Pod Controller" +ENT.WireDebugName = "Pod Controller" +ENT.AllowLockInsideVehicle = CreateConVar( "wire_pod_allowlockinsidevehicle", "0", FCVAR_ARCHIVE, "Allow or disallow people to be locked inside of vehicles" ) + +if CLIENT then + local hideHUD = 0 + local firstTime = true + local HUDHidden = false + local savedHooks = nil + local toolgunHUDFunc = nil + local function blank() end + + usermessage.Hook( "wire pod hud", function( um ) + local vehicle = um:ReadEntity() + if LocalPlayer():InVehicle() and LocalPlayer():GetVehicle() == vehicle then + hideHUD = um:ReadShort() + if hideHUD > 0 and not HUDHidden then + HUDHidden = true + if firstTime then + LocalPlayer():ChatPrint( "The owner of this vehicle has hidden your hud using a pod controller. If it gets stuck this way, use the console command 'wire_pod_hud_show' to forcibly enable it again." ) + firstTime = false + end + --Hide toolgun HUD + local toolgun = LocalPlayer():GetWeapon("gmod_tool") + if IsValid(toolgun) then + toolgunHUDFunc = toolgun.DrawHUD + toolgun.DrawHUD = blank + end + --Hide all HUDPaints except for EGP HUD + local hooks = hook.GetTable()["HUDPaint"] + savedHooks = table.Copy(hooks) + for k in pairs(hooks) do + if hideHUD > 2 or k ~= "EGP_HUDPaint" then + hook.Add( "HUDPaint", k, blank ) + end + end + --Hide other HUD elements + hook.Add( "DrawDeathNotice", "Wire pod DrawDeathNotice", function() return false end) + hook.Add( "HUDDrawTargetID", "Wire pod HUDDrawTargetID", function() return false end) + hook.Add( "HUDShouldDraw", "Wire pod HUDShouldDraw", function( name ) + if hideHUD > 0 then + if LocalPlayer():InVehicle() then + --Allow crosshair (it can be hidden using the other input) and CHudGMod (for the EGP HUDPaint to pass through). Hide the chat if the input is higher than 1 + if name ~= "CHudCrosshair" and name ~= "CHudGMod" and (hideHUD > 1 and name == "CHudChat" or name ~= "CHudChat") then return false end + else + hideHUD = 0 + end + else + --Restore toolgun HUD + local toolgun = LocalPlayer():GetWeapon("gmod_tool") + if IsValid(toolgun) and toolgun.DrawHUD == blank and toolgunHUDFunc ~= nil then + toolgun.DrawHUD = toolgunHUDFunc + end + toolgunHUDFunc = nil + --Restore HUDPaints and other HUD elements + local hooks = hook.GetTable()["HUDPaint"] + for k,v in pairs(hooks) do + if v == blank and savedHooks ~= nil and savedHooks[k] ~= nil then + hook.Add( "HUDPaint", k, savedHooks[k] ) + end + end + savedHooks = nil + + hook.Remove( "HUDShouldDraw", "Wire pod HUDShouldDraw") + hook.Remove( "DrawDeathNotice", "Wire pod DrawDeathNotice") + hook.Remove( "HUDDrawTargetID", "Wire pod HUDDrawTargetID") + HUDHidden = false + end + end) + end + else + hideHUD = 0 + end + end) + + concommand.Add( "wire_pod_hud_show", function(ply,cmd,args) + hideHUD = 0 + end) + + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + local outputs = { + -- Keys + "W", "A", "S", "D", "Mouse1", "Mouse2", + "R", "Space", "Shift", "Zoom", "Alt", "TurnLeftKey", "TurnRightKey", + + -- Clientside keys + "PrevWeapon", "NextWeapon", "Light", + + -- Aim Position + "X", "Y", "Z", "AimPos [VECTOR]", + "Distance", "Bearing", "Elevation", + + -- Other info + "ThirdPerson", "Team", "Health", "Armor", + + -- Active + "Active", + + -- Entity + "Entity [ENTITY]", + + -- Driver + "Driver [ENTITY]" + } + + local inputs = { + "Lock", "Terminate", "Strip weapons", "Eject", + "Disable", "Crosshairs", "Brake", "Allow Buttons", + "Relative", "Damage Health", "Damage Armor", "Hide Player", "Hide HUD", + "Vehicle [ENTITY]" + } + + self.Inputs = WireLib.CreateInputs( self, inputs ) + self.Outputs = WireLib.CreateOutputs( self, outputs ) + + self:SetLocked( false ) + self:SetHidePlayer( false ) + self:SetHideHUD( 0 ) + self.HidePlayerVal = false + self.Crosshairs = false + self.Disable = false + self.AllowButtons = false + self.Relative = false + self.MouseDown = false + + self:SetActivated( false ) + + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) + + self:SetOverlayText( "Pod Controller" ) +end + +-- Accessor funcs for certain functions +function ENT:SetLocked( b ) + if not self:HasPod() or self.Locked == b then return end + + self.Locked = b + self.Pod:Fire( b and "Lock" or "Unlock", "1", 0 ) +end + +function ENT:SetActivated( b ) + if (self.Activated == b) then return end + + self:ColorByLinkStatus(b and self.LINK_STATUS_ACTIVE or self.LINK_STATUS_LINKED) + + self.Activated = b + WireLib.TriggerOutput(self, "Active", b and 1 or 0) +end + +function ENT:HidePlayer( b ) + if not self:HasPly() then return end + + local c = self:GetPly():GetColor() + if b then + self.OldPlyAlpha = c.a + c.a = 0 + else + c.a = self.OldPlyAlpha or 255 + self.OldPlyAlpha = nil + end + self:GetPly():SetColor(c) + self:GetPly():SetRenderMode(c.a ~= 255 and RENDERMODE_TRANSALPHA or RENDERMODE_NORMAL) +end + +function ENT:SetHidePlayer( b ) + if (self.HidePlayer == b) then return end + + self.HidePlayerVal = b + + if (self:HasPly()) then + self:HidePlayer( b ) + end +end + +function ENT:LinkEnt( pod ) + pod = WireLib.GetClosestRealVehicle(pod,self:GetPos(),self:GetPlayer()) + + -- if pod is still not a vehicle even after all of the above, then error out + if not IsValid(pod) or not pod:IsVehicle() then return false, "Must link to a vehicle" end + if hook.Run( "CanTool", self:GetPlayer(), WireLib.dummytrace(pod), "wire_pod" ) == false then return false, "You do not have permission to access this vehicle" end + + self:SetPod( pod ) + WireLib.SendMarks(self, {pod}) + return true +end +function ENT:UnlinkEnt() + if IsValid(self.Pod) then + self.Pod:RemoveCallOnRemove("wire_pod_remove") + end + self.Pod = nil + WireLib.SendMarks(self, {}) + WireLib.TriggerOutput( self, "Entity", NULL ) + self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED) + return true +end +function ENT:OnRemove() + self:UnlinkEnt() +end + +function ENT:HasPod() return (self.Pod and self.Pod:IsValid()) end +function ENT:GetPod() return self.Pod end +function ENT:SetPod( pod ) + if pod and pod:IsValid() and not pod:IsVehicle() then return false end + + if self:HasPly() then + self:PlayerExited(self:GetPly()) + else + self:ColorByLinkStatus(IsValid(pod) and self.LINK_STATUS_LINKED or self.LINK_STATUS_UNLINKED) + end + + self.Pod = pod + WireLib.TriggerOutput( self, "Entity", pod ) + + if not IsValid(pod) then return true end + + pod:CallOnRemove("wire_pod_remove",function() + self:UnlinkEnt(pod) + end) + + if IsValid(pod:GetDriver()) then + self:PlayerEntered(pod:GetDriver()) + end + + return true +end + +function ENT:HasPly() + return (self.Ply and self.Ply:IsValid()) +end +function ENT:GetPly() + return self.Ply +end +function ENT:SetPly( ply ) + if IsValid(ply) and not ply:IsPlayer() then return false end + self.Ply = ply + WireLib.TriggerOutput( self, "Driver", ply ) + return true +end + +function ENT:SetHideHUD( val ) + self.HideHUD = val + + if self:HasPly() and self:HasPod() then -- If we have a player, we SHOULD always have a pod as well, but just in case. + umsg.Start( "wire pod hud", self:GetPly() ) + umsg.Entity( self:GetPod() ) + umsg.Short( self.HideHUD ) + umsg.End() + end +end +function ENT:GetHideHUD() return self.HideHUD end + +local bindingToOutput = { + ["forward"] = "W", + ["moveleft"] = "A", + ["back"] = "S", + ["moveright"] = "D", + ["left"] = "TurnLeftKey", + ["right"] = "TurnRightKey", + + ["jump"] = "Space", + ["speed"] = "Shift", + ["zoom"] = "Zoom", + ["walk"] = "Alt", + + ["attack"] = "Mouse1", + ["attack2"] = "Mouse2", + ["reload"] = "R", + + ["invprev"] = "PrevWeapon", + ["invnext"] = "NextWeapon", + ["impulse 100"] = "Light", +} + +hook.Add("PlayerBindDown", "gmod_wire_pod", function(player, binding) + if not binding then return end + local output = bindingToOutput[binding] + if not output then return end + + for _, pod in pairs(ents.FindByClass("gmod_wire_pod")) do + if pod:GetPly() == player and not pod.Disable then + WireLib.TriggerOutput(pod, output, 1) + end + end +end) + +hook.Add("PlayerBindUp", "gmod_wire_pod", function(player, binding) + if not binding then return end + local output = bindingToOutput[binding] + if not output then return end + + for _, pod in pairs(ents.FindByClass("gmod_wire_pod")) do + if pod:GetPly() == player and not pod.Disable then + WireLib.TriggerOutput(pod, output, 0) + end + end +end) + +-- Helper function for ejecting players using the RC remote +function ENT:RCEject() + self.RC:Off() +end + +function ENT:TriggerInput( name, value ) + if (name == "Lock") then + if (self.RC) then return end + if not self:HasPod() then return end + self:SetLocked( value ~= 0 ) + elseif (name == "Terminate") then + if value == 0 or not self:HasPly() then return end + local ply = self:GetPly() + if (self.RC) then self:RCEject( ply ) end + ply:Kill() + elseif (name == "Strip weapons") then + if value == 0 or not self:HasPly() then return end + local ply = self:GetPly() + if (self.RC) then + ply:ChatPrint( "Your control has been terminated, and your weapons stripped!" ) + self:RCEject( ply ) + else + ply:ChatPrint( "Your weapons have been stripped!" ) + end + ply:StripWeapons() + elseif (name == "Eject") then + if value == 0 or not self:HasPly() then return end + if (self.RC) then + self:RCEject( self:GetPly() ) + else + self:GetPly():ExitVehicle() + end + elseif (name == "Disable") then + self.Disable = value ~= 0 + + if (self.Disable) then + for _, output in pairs( bindingToOutput ) do + WireLib.TriggerOutput( self, output, 0 ) + end + end + elseif (name == "Crosshairs") then + self.Crosshairs = value ~= 0 + if (self:HasPly()) then + if (self.Crosshairs) then + self:GetPly():CrosshairEnable() + else + self:GetPly():CrosshairDisable() + end + end + elseif (name == "Brake") then + if not self:HasPod() then return end + local pod = self:GetPod() + if value ~= 0 then + pod:Fire("TurnOff","1",0) + pod:Fire("HandBrakeOn","1",0) + else + pod:Fire("TurnOn","1",0) + pod:Fire("HandBrakeOff","1",0) + end + elseif (name == "Damage Health") then + if not self:HasPly() or value <= 0 then return end + if (value > 100) then value = 100 end + self:GetPly():TakeDamage( value ) + elseif (name == "Damage Armor") then + if not self:HasPly() or value <= 0 then return end + if (value > 100) then value = 100 end + local dmg = self:GetPly():Armor() - value + if (dmg < 0) then dmg = 0 end + self:GetPly():SetArmor( dmg ) + elseif (name == "Allow Buttons") then + self.AllowButtons = value ~= 0 + elseif (name == "Relative") then + self.Relative = value ~= 0 + elseif (name == "Hide Player") then + self:SetHidePlayer( value ~= 0 ) + elseif (name == "Hide HUD") then + self:SetHideHUD( value ) + elseif (name == "Vehicle") then + if not IsValid(value) then return end -- only link if the input is valid. that way, it won't be unlinked if the wire is disconnected + if value:IsPlayer() then return end + if value:IsNPC() then return end + + self:LinkEnt(value) + end +end + +local function fixupangle(angle) + if angle > 180 then angle = angle - 360 end + if angle < -180 then angle = angle + 360 end + return angle +end + +function ENT:Think() + if (self:HasPly() and self.Activated) then + local ply = self:GetPly() + local pod = self:GetPod() + + -- Tracing + local trace = util.TraceLine( { start = ply:GetShootPos(), endpos = ply:GetShootPos() + ply:GetAimVector() * 9999999999, filter = { ply, pod } } ) + local distance + if (self:HasPod()) then distance = trace.HitPos:Distance( pod:GetPos() ) else distance = trace.HitPos:Distance( ply:GetShootPos() ) end + + if (trace.Hit) then + -- Position + WireLib.TriggerOutput( self, "X", trace.HitPos.x ) + WireLib.TriggerOutput( self, "Y", trace.HitPos.y ) + WireLib.TriggerOutput( self, "Z", trace.HitPos.z ) + WireLib.TriggerOutput( self, "AimPos", trace.HitPos ) + WireLib.TriggerOutput( self, "Distance", distance ) + self.VPos = trace.HitPos + + -- Bearing & Elevation + local angle = ply:GetAimVector():Angle() + + if (self.Relative) then + local originalangle + if (self.RC) then + originalangle = ply.InitialAngle + else + originalangle = pod:GetAngles() + if pod:GetClass() ~= "prop_vehicle_prisoner_pod" then + originalangle.y = originalangle.y + 90 + end + end + WireLib.TriggerOutput( self, "Bearing", fixupangle( angle.y - originalangle.y ) ) + WireLib.TriggerOutput( self, "Elevation", fixupangle( angle.p - originalangle.p ) ) + else + WireLib.TriggerOutput( self, "Bearing", fixupangle( angle.y ) ) + WireLib.TriggerOutput( self, "Elevation", fixupangle( -angle.p ) ) + end + else + WireLib.TriggerOutput( self, "X", 0 ) + WireLib.TriggerOutput( self, "Y", 0 ) + WireLib.TriggerOutput( self, "Z", 0 ) + WireLib.TriggerOutput( self, "AimPos", Vector(0,0,0) ) + WireLib.TriggerOutput( self, "Bearing", 0 ) + WireLib.TriggerOutput( self, "Elevation", 0 ) + self.VPos = Vector(0,0,0) + end + + -- Button pressing + if (self.AllowButtons and distance < 82) then + local button = trace.Entity + if IsValid(button) and (ply:KeyDown( IN_ATTACK ) and not self.MouseDown) and button.Use then + -- Generic support (Buttons, Dynamic Buttons, Levers, EGP screens, etc) + self.MouseDown = true + button:Use(ply, self, USE_ON, 0) + elseif not ply:KeyDown( IN_ATTACK ) and self.MouseDown then + self.MouseDown = false + end + end + + -- Other info + WireLib.TriggerOutput(self, "Team", ply:Team()) + WireLib.TriggerOutput(self, "Health", ply:Health()) + WireLib.TriggerOutput(self, "Armor", ply:Armor()) + if self:HasPod() then WireLib.TriggerOutput(self, "ThirdPerson", pod:GetThirdPersonMode() and 1 or 0) end + + if not ply:IsBot() then WireLib.TriggerOutput(self, "Light", ply.keystate[KEY_F] and 1 or 0) end + end + + self:NextThink( CurTime() ) + return true +end + +function ENT:PlayerEntered( ply, RC ) + if (self:HasPly()) then return end + self:SetPly( ply ) + + self.RC = RC + + if (self.Crosshairs) then + ply:CrosshairEnable() + end + + if self.HideHUD > 0 and self:HasPod() then + timer.Simple(0.1,function() + umsg.Start( "wire pod hud", ply ) + umsg.Entity( self:GetPod() ) + umsg.Short( self.HideHUD ) + umsg.End() + end) + end + + if (self.HidePlayerVal) then + self:HidePlayer( true ) + end + + self:SetActivated( true ) +end + +function ENT:PlayerExited( ply ) + if not self:HasPly() then return end + + self:HidePlayer( false ) + + ply:CrosshairEnable() + + self:SetActivated( false ) + + for _, output in pairs(bindingToOutput) do + WireLib.TriggerOutput( self, output, 0 ) + end + + WireLib.TriggerOutput( self, "X", 0 ) + WireLib.TriggerOutput( self, "Y", 0 ) + WireLib.TriggerOutput( self, "Z", 0 ) + WireLib.TriggerOutput( self, "AimPos", Vector(0,0,0) ) + + WireLib.TriggerOutput( self, "Distance", 0 ) + WireLib.TriggerOutput( self, "Bearing", 0 ) + WireLib.TriggerOutput( self, "Elevation", 0 ) + + WireLib.TriggerOutput( self, "ThirdPerson", 0 ) + WireLib.TriggerOutput( self, "Team", 0 ) + WireLib.TriggerOutput( self, "Health", 0 ) + WireLib.TriggerOutput( self, "Armor", 0 ) + + self:SetPly( nil ) +end + +hook.Add( "PlayerEnteredVehicle", "Wire_Pod_EnterVehicle", function( ply, vehicle ) + for _, v in pairs( ents.FindByClass( "gmod_wire_pod" ) ) do + if (v:HasPod() and v:GetPod() == vehicle) then + v:PlayerEntered( ply ) + end + end +end) + +hook.Add( "PlayerLeaveVehicle", "Wire_Pod_ExitVehicle", function( ply, vehicle ) + for _, v in pairs( ents.FindByClass( "gmod_wire_pod" ) ) do + if (v:HasPod() and v:GetPod() == vehicle) then + v:PlayerExited( ply ) + end + end +end) + +hook.Add("CanExitVehicle","Wire_Pod_CanExitVehicle", function( vehicle, ply ) + for _, v in pairs( ents.FindByClass( "gmod_wire_pod" ) ) do + if (v:HasPod() and v:GetPod() == vehicle) and v.Locked and v.AllowLockInsideVehicle:GetBool() then + return false + end + end +end) + +function ENT:GetBeaconPos(sensor) + return self.VPos +end +function ENT:GetBeaconVelocity(sensor) + return self:HasPod() and self:GetPod():GetVelocity() or self:GetVelocity() +end + +--Duplicator support to save pod link (TAD2020) +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if self:HasPod() and not self.RC then + info.pod = self.Pod:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + local pod = GetEntByID(info.pod) + if IsValid(pod) then + self:LinkEnt(pod) + end +end + +function ENT:Use( User, caller ) + if User ~= self:GetPlayer() then return end + if not hook.Run("PlayerGiveSWEP", User, "remotecontroller", weapons.Get( "remotecontroller" )) then return end + User:PrintMessage(HUD_PRINTTALK, "Hold down your use key for 2 seconds to get and link a Remote Controller.") + timer.Create("pod_use_"..self:EntIndex(), 2, 1, function() + if not IsValid(User) or not User:IsPlayer() then return end + if not User:KeyDown(IN_USE) then return end + if not User:GetEyeTrace().Entity or User:GetEyeTrace().Entity ~= self then return end + + if not IsValid(User:GetWeapon("remotecontroller")) then + if not hook.Run("PlayerGiveSWEP", User, "remotecontroller", weapons.Get( "remotecontroller" )) then return end + User:Give("remotecontroller") + end + + User:GetWeapon("remotecontroller").Linked = self + User:PrintMessage(HUD_PRINTTALK, "You are now linked!") + User:SelectWeapon("remotecontroller") + end) +end + +duplicator.RegisterEntityClass("gmod_wire_pod", WireLib.MakeWireEnt, "Data") +duplicator.RegisterEntityClass("gmod_wire_adv_pod", WireLib.MakeWireEnt, "Data") +scripted_ents.Alias("gmod_wire_adv_pod", "gmod_wire_pod") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_radio.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_radio.lua new file mode 100644 index 0000000..9a39680 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_radio.lua @@ -0,0 +1,139 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Radio" +ENT.WireDebugName = "Radio" + +if CLIENT then return end -- No more client + +local MODEL = Model( "models/props_lab/binderblue.mdl" ) + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = WireLib.CreateInputs(self, { "Channel"}) + self.Outputs = WireLib.CreateOutputs(self, { "ERRORS!!!" }) + + self.Channel = 0 + self.values = 4 + self.RecievedData = {} + for i=0,31 do + self.RecievedData[i] = {} + self.RecievedData[i].Owner = nil + self.RecievedData[i].Data = 0 + end + self.SentData = {} + for i=0,31 do + self.SentData[i] = 0 + end + + Radio_Register(self) + Radio_RecieveData(self) +end + +function ENT:Setup(channel,values,secure) + channel = math.floor(tonumber(channel) or 0) + self.Secure = secure + self.Old = false + if (tonumber(values) == nil) then + values = 4 + self.Old = true + else + values = math.Clamp(math.floor(values),1,32) + end + + self.values = values + local onames = {} + if (self.Old == false) then + for i = 1,self.values do + onames[i] = tostring(i) --without tostring() you kill the debugger. + end + else + onames = {"A","B","C","D"} + end + + WireLib.AdjustOutputs(self,onames) + table.insert(onames,"Channel") + WireLib.AdjustInputs(self,onames) + + self.Channel = channel + Radio_ChangeChannel(self) +end + +function ENT:TriggerInput(iname, value) + if (iname == "Channel") then + self.Channel = math.floor(value) + Radio_ChangeChannel(self) + + elseif (iname != nil && value != nil) then + if (self.Old == true) then + if (iname == "A") then + Radio_SendData(self,self.Channel,0,value) + elseif (iname == "B") then + Radio_SendData(self,self.Channel,1,value) + elseif (iname == "C") then + Radio_SendData(self,self.Channel,2,value) + elseif (iname == "D") then + Radio_SendData(self,self.Channel,3,value) + end + else + Radio_SendData(self,tonumber(iname)-1,value) + end + end + self:ShowOutput() +end + +function ENT:NotifyDataRecieved(subch) + WireLib.TriggerOutput(self,tostring(subch+1),self.RecievedData[subch].Data) +end + +function ENT:ReadCell(Address) + Address = math.floor(Address) + if (Address >= 0) && (Address < self.values) then + return self.RecievedData[Address].Data + else + return nil + end +end + +function ENT:WriteCell(Address, value) + Address = math.floor(Address) + if (Address >= 0) && (Address < self.values) then + Radio_SendData(self,Address,value) + return true + else + return false + end +end + +function ENT:ShowOutput() + if (self.Old == true) then + self:SetOverlayText( "(Channel " .. self.Channel .. ") Transmit A: " .. (self.Inputs.A.Value or 0) .. " B: " .. (self.Inputs.B.Value or 0) .. " C: " .. (self.Inputs.C.Value or 0) .. " D: " .. (self.Inputs.D.Value or 0) .. "\nReceive A: " .. (self.Outputs.A.Value or 0) .. " B: " .. (self.Outputs.B.Value or 0) .. " C: " .. (self.Outputs.C.Value or 0) .. " D: " .. (self.Outputs.D.Value or 0) ) + else + local overlay = "(Channel " .. self.Channel .. ") Transmit" + for i=1,self.values do + overlay = overlay .. " " .. i .. ":" .. + math.Round((self.SentData[i-1])*1000)/1000 + end + overlay = overlay .. "\nReceive" + for i=1,self.values do + overlay = overlay .. " " .. i .. ":" .. + math.Round((self.RecievedData[i-1].Data)*1000)/1000 + end + if (self.Secure == true) then overlay = overlay .. "\nSecured" end + self:SetOverlayText(overlay) + end +end + +function ENT:OnRestore() + BaseClass.OnRestore(self) + Radio_Register(self) +end + +function ENT:OnRemove() + if (!self.Channel) then return end + Radio_Unregister(self) +end + +duplicator.RegisterEntityClass("gmod_wire_radio", WireLib.MakeWireEnt, "Data", "Channel", "values", "Secure") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_ranger.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_ranger.lua new file mode 100644 index 0000000..85d4468 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_ranger.lua @@ -0,0 +1,295 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Ranger" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Ranger" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) + self:NetworkVar( "Bool", 0, "ShowBeam" ) + self:NetworkVar( "Float", 1, "SkewX" ) + self:NetworkVar( "Float", 2, "SkewY" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:StartMotionController() + + self.Inputs = WireLib.CreateInputs(self, { "X", "Y", "SelectValue","Length"}) + self.Outputs = WireLib.CreateOutputs(self, { "Dist" }) + self.hires = false +end + +function ENT:Setup( range, default_zero, show_beam, ignore_world, trace_water, out_dist, out_pos, out_vel, out_ang, out_col, out_val, out_sid, out_uid, out_eid, out_hnrm, hiRes ) + --for duplication + self.default_zero = default_zero + self.show_beam = show_beam + self.ignore_world = ignore_world + self.trace_water = trace_water + self.out_dist = out_dist + self.out_pos = out_pos + self.out_vel = out_vel + self.out_ang = out_ang + self.out_col = out_col + self.out_val = out_val + self.out_sid = out_sid + self.out_uid = out_uid + self.out_eid = out_eid + self.out_hnrm = out_hnrm + self.hires = hiRes + + self.PrevOutput = nil + + if range then self:SetBeamLength(math.min(range, 64000)) end + if show_beam ~= nil then self:SetShowBeam(show_beam) end + + self:SetNWBool("TraceWater", trace_water) + + local onames, otypes = {}, {} + + + local function add(...) + local args = {...} + for i=1,#args,2 do + onames[#onames+1] = args[i] + otypes[#otypes+1] = args[i+1] + end + end + + + if (out_dist) then add("Dist","NORMAL") end + if (out_pos) then + add("Pos", "VECTOR", + "Pos X", "NORMAL", + "Pos Y", "NORMAL", + "Pos Z", "NORMAL") + end + if (out_vel) then + add("Vel","VECTOR", + "Vel X","NORMAL", + "Vel Y","NORMAL", + "Vel Z","NORMAL") + end + if (out_ang) then + add("Ang","ANGLE", + "Ang Pitch","NORMAL", + "Ang Yaw","NORMAL", + "Ang Roll","NORMAL") + end + if (out_col) then + add("Col RGB","VECTOR", + "Col R","NORMAL", + "Col G","NORMAL", + "Col B","NORMAL", + "Col A","NORMAL") + end + if (out_val) then add("Val","NORMAL","ValSize","NORMAL") end + if (out_sid) then add( "SteamID", "STRING" ) end + if (out_uid) then add( "UniqueID","NORMAL" ) end + if (out_eid) then add( "EntID", "NORMAL", "Entity", "ENTITY" ) end + if (out_hnrm) then + add("HitNormal","VECTOR", + "HitNormal X","NORMAL", + "HitNormal Y","NORMAL", + "HitNormal Z","NORMAL") + end + add( "RangerData", "RANGER" ) + WireLib.AdjustSpecialOutputs(self, onames, otypes) + + self:TriggerOutput(0, Vector(0, 0, 0), Vector(0, 0, 0), Angle(0, 0, 0), Color(255, 255, 255, 255),nil,0,0,NULL, Vector(0, 0, 0),nil) + self:ShowOutput(0, Vector(0, 0, 0), Vector(0, 0, 0), Angle(0, 0, 0), Color(255, 255, 255, 255),nil,0,0,NULL, Vector(0, 0, 0),nil) +end + +function ENT:TriggerInput(iname, value) + if (iname == "X") then + self:SetSkewX(value) + elseif (iname == "Y") then + self:SetSkewY(value) + elseif (iname == "Length") then + self:SetBeamLength(math.min(value, 64000)) + end +end + +function ENT:Think() + BaseClass.Think(self) + + local tracedata = {} + tracedata.start = self:GetPos() + if (self.Inputs.X.Value == 0 and self.Inputs.Y.Value == 0) then + tracedata.endpos = tracedata.start + self:GetUp() * self:GetBeamLength() + else + local skew = Vector(self.Inputs.X.Value, self.Inputs.Y.Value, 1) + skew = skew*(self:GetBeamLength()/skew:Length()) + local beam_x = self:GetRight()*skew.x + local beam_y = self:GetForward()*skew.y + local beam_z = self:GetUp()*skew.z + tracedata.endpos = tracedata.start + beam_x + beam_y + beam_z + end + tracedata.filter = { self } + if (self.trace_water) then tracedata.mask = -1 end + local trace = util.TraceLine(tracedata) + trace.RealStartPos = tracedata.start + + local dist = 0 + local pos = Vector(0, 0, 0) + local vel = Vector(0, 0, 0) + local ang = Angle(0, 0, 0) + local col = Color(255, 255, 255, 255) + local ent = NULL + local sid = "" + local uid = 0 + local val = {} + local hnrm = Vector(0,0,0) + + if (trace.Hit) then + dist = trace.Fraction * self:GetBeamLength() + pos = trace.HitPos + hnrm = trace.HitNormal + ent = trace.Entity + + if (ent:IsValid()) then + + vel = ent:GetVelocity() + ang = ent:GetAngles() + col = ent:GetColor() + + if (self.out_sid or self.out_uid) and (ent:IsPlayer()) then + sid = ent:SteamID() or "" + uid = tonumber(ent:UniqueID()) or -1 + end + + if (self.out_val and ent.Outputs) then + local i = 1 + for k,v in pairs(ent.Outputs) do + if (v.Value != nil and type(v.Value) == "number") then + val[i] = v.Value + i = i + 1 + end + end + end + + elseif(self.ignore_world) then + if (trace.HitWorld) then + if (self.default_zero) then + dist = 0 + else + dist = self:GetBeamLength() + end + pos = Vector(0,0,0) + end + end + + else + if (not self.default_zero) then + dist = self:GetBeamLength() + end + end + + if (COLOSSAL_SANDBOX) then + vel = vel * 6.25 + pos = pos * 6.25 + dist = dist * 6.25 + end + + self:TriggerOutput(dist, pos, vel, ang, col, val, sid, uid, ent, hnrm, trace) + self:ShowOutput(dist, pos, vel, ang, col, val, sid, uid, ent, hnrm, trace) + + if (self.hires) then + self:NextThink(CurTime()) + else + self:NextThink(CurTime()+0.04) + end + + return true +end + +local round = math.Round + +function ENT:ShowOutput(dist, pos, vel, ang, col, val, sid, uid, ent, hnrm, trace) + local txt = "Max Range: " .. self:GetBeamLength() + + if (self.out_dist) then txt = txt .. "\nRange = " .. round(dist,3) end + if (self.out_pos) then txt = txt .. string.format("\nPosition = %s, %s, %s", round(pos.x,3), round(pos.y,3), round(pos.z,3)) end + if (self.out_vel) then txt = txt .. string.format("\nVelocity = %s, %s, %s", round(vel.x,3), round(vel.y,3), round(vel.z,3)) end + if (self.out_ang) then txt = txt .. string.format("\nAngles = %s, %s, %s", round(ang.pitch,3), round(ang.yaw,3), round(ang.roll,3)) end + if (self.out_col) then txt = txt .. string.format("\nColor = %s, %s, %s, %s", round(col.r), round(col.g), round(col.b), round(col.a)) end + if (self.out_val) then txt = txt .. string.format("\nValue = %s ValSize = %s", round(self.Outputs["Val"].Value or 0,3), #(val or {}) ) end + if (self.out_sid) then txt = txt .. "\nSteamID = " .. (sid or "") end + if (self.out_uid) then txt = txt .. "\nUniqueID = " .. (uid or 0) end + if (self.out_eid) then txt = txt .. "\nEntID = " .. ent:EntIndex() end + if (self.out_hnrm) then txt = txt .. string.format("\nHitNormal = %s, %s, %s", round(hnrm.x,3), round(hnrm.y,3), round(hnrm.z,3)) end + + self:SetOverlayText(txt) +end + +function ENT:TriggerOutput(dist, pos, vel, ang, col, val, sid, uid, ent, hnrm, trace) + + if (self.out_dist) then + WireLib.TriggerOutput(self, "Dist", dist) + end + + if (self.out_pos) then + WireLib.TriggerOutput(self, "Pos", pos) + WireLib.TriggerOutput(self, "Pos X", pos.x) + WireLib.TriggerOutput(self, "Pos Y", pos.y) + WireLib.TriggerOutput(self, "Pos Z", pos.z) + end + + if (self.out_vel) then + WireLib.TriggerOutput(self, "Vel", vel) + WireLib.TriggerOutput(self, "Vel X", vel.x) + WireLib.TriggerOutput(self, "Vel Y", vel.y) + WireLib.TriggerOutput(self, "Vel Z", vel.z) + end + + if (self.out_ang) then + WireLib.TriggerOutput(self, "Ang", ang) + WireLib.TriggerOutput(self, "Ang Pitch", ang.p) + WireLib.TriggerOutput(self, "Ang Yaw", ang.y) + WireLib.TriggerOutput(self, "Ang Roll", ang.r) + end + + if (self.out_col) then + WireLib.TriggerOutput(self, "Col RGB", Vector(col.r, col.g, col.b)) + WireLib.TriggerOutput(self, "Col R", col.r) + WireLib.TriggerOutput(self, "Col G", col.g) + WireLib.TriggerOutput(self, "Col B", col.b) + WireLib.TriggerOutput(self, "Col A", col.a) + end + + if (self.out_sid) then + WireLib.TriggerOutput(self, "SteamID", sid) + end + + if (self.out_uid) then + WireLib.TriggerOutput(self, "UniqueID", uid) + end + + if (self.out_eid) then + WireLib.TriggerOutput(self, "EntID", ent:EntIndex()) + WireLib.TriggerOutput(self, "Entity", ent) + end + + if (self.out_hnrm and hnrm) then + WireLib.TriggerOutput(self, "HitNormal", hnrm) + WireLib.TriggerOutput(self, "HitNormal X", hnrm.x) + WireLib.TriggerOutput(self, "HitNormal Y", hnrm.y) + WireLib.TriggerOutput(self, "HitNormal Z", hnrm.z) + end + + if (val ~= nil and #val > 0 and self.Inputs.SelectValue.Value <= #val) then + WireLib.TriggerOutput(self, "Val", val[self.Inputs.SelectValue.Value]) + WireLib.TriggerOutput(self, "ValSize", #val) + else + WireLib.TriggerOutput(self, "Val", 0) + WireLib.TriggerOutput(self, "ValSize", 0) + end + WireLib.TriggerOutput(self, "RangerData", trace) + +end + +duplicator.RegisterEntityClass("gmod_wire_ranger", WireLib.MakeWireEnt, "Data", "range", "default_zero", "show_beam", "ignore_world", "trace_water", "out_dist", "out_pos", "out_vel", "out_ang", "out_col", "out_val", "out_sid", "out_uid", "out_eid", "out_hnrm", "hires") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_relay.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_relay.lua new file mode 100644 index 0000000..0bde8ae --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_relay.lua @@ -0,0 +1,163 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Relay" +ENT.WireDebugName = "Relay" +ENT.Author = "tad2020" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + + self.Value = {} //stores current output value + self.Last = {} //stores last input value for each input + + self.Inputs = Wire_CreateInputs(self, { "1A", "2A", "Switch" }) + self.Outputs = Wire_CreateOutputs(self, { "A" }) +end + +function ENT:Setup(keygroup1, keygroup2, keygroup3, keygroup4, keygroup5, keygroupoff, toggle, normclose, poles, throws, nokey) + + local outpoles = {"A", "B", "C", "D", "E", "F", "G", "H"} //output names + + // Clamp + throws = throws > 10 and 10 or throws + poles = poles > #outpoles and #outpoles or poles + + local inputs = {} //wont need this outside setup + + self.outputs = {} //need to rebuild output names + + self.keygroup1 = keygroup1 + self.keygroup2 = keygroup2 + self.keygroup3 = keygroup3 + self.keygroup4 = keygroup4 + self.keygroup5 = keygroup5 + self.keygroupoff = keygroupoff + self.toggle = toggle + self.normclose = normclose or 0 + self.selinput = normclose or 0 + self.poles = poles + self.throws = throws + self.nokey = nokey + + //build inputs and putputs, init all nil values + for p=1, self.poles do + self.outputs[p] = outpoles[p] + self.Value[p] = self.Value[p] or 0 + for t=1, self.throws do + //inputs[ p * self.poles + t ] = t .. outpoles[p] + table.insert(inputs, ( t .. outpoles[p] )) + self.Last[ t .. outpoles[p] ] = self.Last[ t .. outpoles[p] ] or 0 + end + end + //add switch input to end of input list + table.insert(inputs, "Switch") + + Wire_AdjustInputs(self, inputs) + Wire_AdjustOutputs(self, self.outputs) + + //set the switch to its new normal state + self:Switch( normclose ) + + if not nokey then + local pl = self:GetPlayer() + if (keygroupoff) then + numpad.OnDown( pl, keygroupoff, "WireRelay_On", self, 0 ) + numpad.OnUp( pl, keygroupoff, "WireRelay_Off", self, 0 ) + end + if (keygroup1) then + numpad.OnDown( pl, keygroup1, "WireRelay_On", self, 1 ) + numpad.OnUp( pl, keygroup1, "WireRelay_Off", self, 1 ) + end + if (keygroup2) then + numpad.OnDown( pl, keygroup2, "WireRelay_On", self, 2 ) + numpad.OnUp( pl, keygroup2, "WireRelay_Off", self, 2 ) + end + if (keygroup3) then + numpad.OnDown( pl, keygroup3, "WireRelay_On", self, 3 ) + numpad.OnUp( pl, keygroup3, "WireRelay_Off", self, 3 ) + end + if (keygroup4) then + numpad.OnDown( pl, keygroup4, "WireRelay_On", self, 4 ) + numpad.OnUp( pl, keygroup4, "WireRelay_Off", self, 4 ) + end + if (keygroup5) then + numpad.OnDown( pl, keygroup5, "WireRelay_On", self, 5 ) + numpad.OnUp( pl, keygroup5, "WireRelay_Off", self, 5 ) + end + end +end + + +function ENT:TriggerInput(iname, value) + if (iname == "Switch") then + if (math.abs(value) >= 0 && math.abs(value) <= self.throws) then + self:Switch(math.abs(value)) + end + elseif (iname) then + self.Last[iname] = value or 0 + self:Switch(self.selinput) + end +end + + +function ENT:Switch( mul ) + if (!self:IsValid()) then return false end + self.selinput = mul + for p,v in ipairs(self.outputs) do + self.Value[p] = self.Last[ mul .. v ] or 0 + Wire_TriggerOutput(self, v, self.Value[p]) + end + self:ShowOutput() + return true +end + + +function ENT:ShowOutput() + local txt = self.poles .. "P" .. self.throws .. "T " + if (self.selinput == 0) then + txt = txt .. "Sel: off" + else + txt = txt .. "Sel: " .. self.selinput + end + + for p,v in ipairs(self.outputs) do + txt = txt .. "\n" .. v .. ": " .. self.Value[p] + end + + self:SetOverlayText( txt ) +end + + +function ENT:InputActivate( mul ) + if ( self.toggle && self.selinput == mul) then //only toggle for the same key + return self:Switch( self.normclose ) + else + return self:Switch( mul ) + end +end + +function ENT:InputDeactivate( mul ) + if ( self.toggle ) then return true end + return self:Switch( self.normclose ) +end + + +local function On( pl, ent, mul ) + if (!ent:IsValid()) then return false end + return ent:InputActivate( mul ) +end + +local function Off( pl, ent, mul ) + if (!ent:IsValid()) then return false end + return ent:InputDeactivate( mul ) +end + +numpad.Register( "WireRelay_On", On ) +numpad.Register( "WireRelay_Off", Off ) + +duplicator.RegisterEntityClass("gmod_wire_relay", WireLib.MakeWireEnt, "Data", "keygroup1", "keygroup2", "keygroup3", "keygroup4", "keygroup5", "keygroupoff", "toggle", "normclose", "poles", "throws", "nokey") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_screen.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_screen.lua new file mode 100644 index 0000000..5c654e0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_screen.lua @@ -0,0 +1,185 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Screen" +ENT.WireDebugName = "Screen" +ENT.Editable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "SingleValue", { KeyName = "SingleValue", + Edit = { type = "Boolean", title = "#Tool_wire_screen_singlevalue", order = 1 } }) + self:NetworkVar("Bool", 1, "SingleBigFont", { KeyName = "SingleBigFont", + Edit = { type = "Boolean", title = "#Tool_wire_screen_singlebigfont", order = 2 } }) + self:NetworkVar("Bool", 2, "LeftAlign", { KeyName = "LeftAlign", + Edit = { type = "Boolean", title = "#Tool_wire_screen_leftalign", order = 3 } }) + self:NetworkVar("Bool", 3, "Floor", { KeyName = "Floor", + Edit = { type = "Boolean", title = "#Tool_wire_screen_floor", order = 4 } }) + self:NetworkVar("Bool", 4, "FormatNumber", { KeyName = "FormatNumber", + Edit = { type = "Boolean", title = "#Tool_wire_screen_formatnumber", order = 5 } }) + self:NetworkVar("Bool", 5, "FormatTime", { KeyName = "FormatTime", + Edit = { type = "Boolean", title = "#Tool_wire_screen_formattime", order = 6 } }) + self:NetworkVar("String", 0, "TextA", { KeyName = "TextA", + Edit = { type = "Generic", title = "#Tool_wire_screen_texta", order = 7 } }) + self:NetworkVar("String", 1, "TextB", { KeyName = "TextB", + Edit = { type = "Generic", title = "#Tool_wire_screen_textb", order = 8 } }) + + if SERVER then + self:NetworkVarNotify("SingleValue", function(ent, key, old, single) + WireLib.AdjustInputs(self, single and { "A" } or { "A", "B" }) + end) + end +end + +function ENT:SetDisplayA(float) self:SetNWFloat("DisplayA", float) end +function ENT:SetDisplayB(float) self:SetNWFloat("DisplayB", float) end +function ENT:GetDisplayA() return self:GetNWFloat("DisplayA") end +function ENT:GetDisplayB() return self:GetNWFloat("DisplayB") end + +if CLIENT then + function ENT:Initialize() + self.GPU = WireGPU(self, true) + end + + function ENT:OnRemove() + self.GPU:Finalize() + end + + local header_color = Color(100,100,150,255) + local text_color = Color(255,255,255,255) + local background_color = Color(0,0,0,255) + + local large_font = "Trebuchet36" + local small_font = "Trebuchet18" + local value_large_font = "screen_font_single" + local value_small_font = "screen_font" + + local small_height = 20 + local large_height = 40 + + function ENT:DrawNumber( header, value, x,y,w,h ) + local header_height = small_height + local header_font = small_font + local value_font = value_small_font + + if self:GetSingleValue() and self:GetSingleBigFont() then + header_height = large_height + header_font = large_font + value_font = value_large_font + end + + surface.SetDrawColor( header_color ) + surface.DrawRect( x, y, w, header_height ) + + surface.SetFont( header_font ) + surface.SetTextColor( text_color ) + local _w,_h = surface.GetTextSize( header ) + surface.SetTextPos( x + w / 2 - _w / 2, y + 2 ) + surface.DrawText( header, header_font ) + + if self:GetFormatTime() then -- format as time, aka duration - override formatnumber and floor settings + value = WireLib.nicenumber.nicetime( value ) + elseif self:GetFormatNumber() then + if self:GetFloor() then + value = WireLib.nicenumber.format( math.floor( value ), 1 ) + else + value = WireLib.nicenumber.formatDecimal( value ) + end + elseif self:GetFloor() then + value = "" .. math.floor( value ) + else + -- note: loses precision after ~7 decimals, so don't bother displaying more + value = "" .. math.floor( value * 10000000 ) / 10000000 + end + + local align = self:GetLeftAlign() and 0 or 1 + surface.SetFont( value_font ) + local _w,_h = surface.GetTextSize( value ) + surface.SetTextPos( x + (w / 2 - _w / 2) * align, y + header_height ) + surface.DrawText( value ) + end + + function ENT:Draw() + self:DrawModel() + + self.GPU:RenderToWorld(nil, 188, function(x, y, w, h) + + if self:GetSingleValue() then + self:DrawNumber( self:GetTextA(), self:GetDisplayA(), x,y,w,h ) + else + local h = h/2 + self:DrawNumber( self:GetTextA(), self:GetDisplayA(), x,y,w,h ) + self:DrawNumber( self:GetTextB(), self:GetDisplayB(), x,y+h,w,h ) + end + end) + + Wire_Render(self) + end + + function ENT:IsTranslucent() return true end + + local fontData = { + font = "coolvetica", + size = 64, + weight = 400, + antialias = false, + additive = false, + + } + surface.CreateFont("screen_font", fontData ) + fontData.size = 128 + surface.CreateFont("screen_font_single", fontData ) + fontData.size = 36 + surface.CreateFont("Trebuchet36", fontData ) + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = WireLib.CreateInputs(self, { "A", "B" }) + + self.ValueA = 0 + self.ValueB = 0 +end + +function ENT:Think() + if self.ValueA then + self:SetDisplayA( self.ValueA ) + self.ValueA = nil + end + + if self.ValueB then + self:SetDisplayB( self.ValueB ) + self.ValueB = nil + end + + self:NextThink(CurTime() + 0.05) + return true +end + +function ENT:TriggerInput(iname, value) + if (iname == "A") then + self.ValueA = value + elseif (iname == "B") then + self.ValueB = value + end +end + +-- only needed for legacy dupes +function ENT:Setup(SingleValue, SingleBigFont, TextA, TextB, LeftAlign, Floor, FormatNumber, FormatTime) + if type(TextA) == "string" then self:SetTextA(TextA) end + if type(TextB) == "string" then self:SetTextB(TextB) end + if SingleBigFont ~= nil then self:SetSingleBigFont(SingleBigFont) end + if LeftAlign ~= nil then self:SetLeftAlign(LeftAlign) end + if Floor ~= nil then self:SetFloor(Floor) end + if SingleValue ~= nil then self:SetSingleValue(SingleValue) end + if FormatNumber ~= nil then self:SetFormatNumber(FormatNumber) end + if FormatTime ~= nil then self:SetFormatTime(FormatTime) end +end + +duplicator.RegisterEntityClass("gmod_wire_screen", WireLib.MakeWireEnt, "Data", "SingleValue", "SingleBigFont", "TextA", "TextB", "LeftAlign", "Floor", "FormatNumber", "FormatTime") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_sensor.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_sensor.lua new file mode 100644 index 0000000..a8e5136 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_sensor.lua @@ -0,0 +1,213 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Beacon Sensor" +ENT.WireDebugName = "Beacon Sensor" + +if CLIENT then return end -- No more client + +local MODEL = Model( "models/props_lab/huladoll.mdl" ) + +function ENT:Initialize() + self:SetModel( MODEL ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs(self, { "Target" }) + self.Outputs = Wire_CreateOutputs(self, { "Out" }) +end + +function ENT:Setup(xyz_mode, outdist, outbrng, gpscord, direction_vector, direction_normalized, target_velocity, velocity_normalized) + if !xyz_mode and !outdist and !outbrng and !gpscord and !direction_vector and !target_velocity then outdist = true end + + self.xyz_mode = xyz_mode + self.PrevOutput = nil + self.Value = 0 + self.outdist = outdist + self.outbrng = outbrng + self.gpscord = gpscord + self.direction_vector = direction_vector + self.direction_normalized = direction_normalized + self.target_velocity = target_velocity + self.velocity_normalized = velocity_normalized + + + local onames = {} + if (outdist) then + table.insert(onames, "Distance") + end + + if (xyz_mode) then + table.insert(onames, "X") + table.insert(onames, "Y") + table.insert(onames, "Z") + end + if (outbrng) then + table.insert(onames, "Bearing") + table.insert(onames, "Elevation") + end + if (gpscord) then + table.insert(onames, "World_X") + table.insert(onames, "World_Y") + table.insert(onames, "World_Z") + end + if (direction_vector) then + table.insert(onames, "Direction_X") + table.insert(onames, "Direction_Y") + table.insert(onames, "Direction_Z") + end + if (target_velocity) then + table.insert(onames, "Velocity_X") + table.insert(onames, "Velocity_Y") + table.insert(onames, "Velocity_Z") + end + + Wire_AdjustOutputs(self, onames) + self:TriggerOutputs(0, Angle(0, 0, 0),Vector(0, 0, 0),Vector(0, 0, 0),Vector(0, 0, 0),Vector(0,0,0)) + self:ShowOutput() +end + +function ENT:Think() + BaseClass.Think(self) + + if not IsValid(self.ToSense) or not self.ToSense.GetBeaconPos then return end + if (self.Active) then + local dist = 0 + local distc = Vector(0,0,0); + local brng = Angle(0,0,0); + local velo = Vector(0,0,0); + local gpscords = Vector(0,0,0); + local dirvec = Vector(0,0,0); + local MyPos = self:GetPos() + //local BeaconPos = self.Inputs["Target"].Src:GetBeaconPos(self) + local BeaconPos = self.ToSense:GetBeaconPos(self) or MyPos + if (self.outdist) then + dist = (BeaconPos-MyPos):Length() + end + if (self.xyz_mode) then + local DeltaPos = self:WorldToLocal(BeaconPos) + distc = Vector(-DeltaPos.y,DeltaPos.x,DeltaPos.z) + end + if (self.outbrng) then + local DeltaPos = self:WorldToLocal(BeaconPos) + brng = DeltaPos:Angle() + end + if (self.gpscord) then gpscords = BeaconPos end + if (self.direction_vector) then + dirvec = BeaconPos - MyPos; + if(self.direction_normalized) then dirvec:Normalize() end; + end; + if (self.target_velocity) then + velo = self.ToSense:GetBeaconVelocity(self); + if(self.velocity_normalized) then velo:Normalize() end; + end + self:TriggerOutputs(dist, brng, distc, gpscords,dirvec,velo) + self:ShowOutput() + + self:NextThink(CurTime()+0.04) + return true + end +end + +function ENT:ShowOutput() + local txt = "" + if (self.outdist) then + txt = string.format("%s\nDistance = %.3f", txt, self.Outputs.Distance.Value) + end + if (self.xyz_mode) then + txt = string.format("%s\nOffset = %.3f, %.3f, %.3f", txt, self.Outputs.X.Value, self.Outputs.Y.Value, self.Outputs.Z.Value) + end + if (self.outbrng) then + txt = string.format("%s\nBearing = %.3f, %.3f", txt, self.Outputs.Bearing.Value, self.Outputs.Elevation.Value) + end + if (self.gpscord) then + txt = string.format("%s\nWorldPos = %.3f, %.3f, %.3f", txt, self.Outputs.World_X.Value, self.Outputs.World_Y.Value, self.Outputs.World_Z.Value) + end + if (self.direction_vector) then + txt = string.format("%s\nDirectionVector = %.3f, %.3f, %.3f", txt, self.Outputs.Direction_X.Value, self.Outputs.Direction_Y.Value, self.Outputs.Direction_Z.Value) + end + if (self.target_velocity) then + txt = string.format("%s\nTargetVelocity = %.3f, %.3f, %.3f", txt, self.Outputs.Velocity_X.Value, self.Outputs.Velocity_Y.Value, self.Outputs.Velocity_Z.Value) + end + + self:SetOverlayText(string.Right(txt,#txt-1)) -- Cut off the first \n +end + + +function ENT:TriggerOutputs(dist, brng, distc, gpscords,dirvec,velo) + if (self.outdist) then + Wire_TriggerOutput(self, "Distance", dist) + end + if (self.xyz_mode) then + Wire_TriggerOutput(self, "X", distc.x) + Wire_TriggerOutput(self, "Y", distc.y) + Wire_TriggerOutput(self, "Z", distc.z) + end + if (self.gpscord) then + Wire_TriggerOutput(self, "World_X", gpscords.x) + Wire_TriggerOutput(self, "World_Y", gpscords.y) + Wire_TriggerOutput(self, "World_Z", gpscords.z) + end + if (self.outbrng) then + local pitch = brng.p + local yaw = brng.y + + if (pitch > 180) then pitch = pitch - 360 end + if (yaw > 180) then yaw = yaw - 360 end + + Wire_TriggerOutput(self, "Bearing", -yaw) + Wire_TriggerOutput(self, "Elevation", -pitch) + end + if(self.direction_vector) then + Wire_TriggerOutput(self, "Direction_X", dirvec.x) + Wire_TriggerOutput(self, "Direction_Y", dirvec.y) + Wire_TriggerOutput(self, "Direction_Z", dirvec.z) + end + if(self.target_velocity) then + Wire_TriggerOutput(self, "Velocity_X", velo.x) + Wire_TriggerOutput(self, "Velocity_Y", velo.y) + Wire_TriggerOutput(self, "Velocity_Z", velo.z) + end +end + +function ENT:TriggerInput(iname, value) + if (iname == "Target") and ( self.ToSense != self.Inputs.Target.Src ) then + self:LinkEnt(self.Inputs.Target.Src) + end +end + +function ENT:LinkEnt(beacon) + if IsValid(beacon) and beacon.GetBeaconPos then + self.ToSense = beacon + self.Active = true + WireLib.SendMarks(self, {beacon}) + return true + else + self:UnlinkEnt() + return false, "Must link to ent that outputs BeaconPos" + end +end +function ENT:UnlinkEnt(ent) + self.ToSense = nil + self.Active = false + WireLib.SendMarks(self, {}) + return true +end + +duplicator.RegisterEntityClass("gmod_wire_sensor", WireLib.MakeWireEnt, "Data", "xyz_mode", "outdist", "outbrng", "gpscord", "direction_vector", "direction_normalized", "target_velocity", "velocity_normalized") + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if IsValid(self.ToSense) then + info.to_sense = self.ToSense:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self:LinkEnt(GetEntByID(info.to_sense)) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_simple_explosive.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_simple_explosive.lua new file mode 100644 index 0000000..37744a5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_simple_explosive.lua @@ -0,0 +1,93 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Simple Explosive" +ENT.WireDebugName = "Simple Explosive" + +if CLIENT then return end -- No more client + +local wire_explosive_delay = CreateConVar( "wire_explosive_delay", 0.2, FCVAR_ARCHIVE ) +local wire_explosive_range = CreateConVar( "wire_explosive_range", 512, FCVAR_ARCHIVE ) + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + phys:Wake() + end + + self.NormInfo = "" + self.DisabledByTimeUntil = CurTime() + + self.Inputs = Wire_CreateInputs(self, { "Detonate" }) +end + +function ENT:Setup( key, damage, removeafter, radius ) + self.key = key + self.damage = math.Min(damage, 1500) + self.removeafter = removeafter + self.radius = math.Clamp(radius, 1, wire_explosive_range:GetFloat()) + self.Exploded = false + + if (self.damage > 0) then + self.NormInfo = "Damage: " .. math.floor(self.damage) .. "\nRadius: " .. math.floor(self.radius) + else + self.NormInfo = "Radius: " .. math.floor(self.radius) + end + + self:ShowOutput() + +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:TriggerInput(iname, value) + if (iname == "Detonate") then + if (!self.Exploded) and ( math.abs(value) == self.key ) then + self:Explode() + elseif (value == 0) then + self.Exploded = false + end + end +end + +function ENT:Explode( ) + + if ( !self:IsValid() ) then return end + if (self.Exploded) then return end + if self.DisabledByTimeUntil > CurTime() then return end + self.DisabledByTimeUntil = CurTime() + wire_explosive_delay:GetFloat() + + local ply = self:GetPlayer() + if not IsValid(ply) then ply = self end + + if ( self.damage > 0 ) then + util.BlastDamage( self, ply, self:GetPos(), self.radius, self.damage ) + end + + local effectdata = EffectData() + effectdata:SetOrigin( self:GetPos() ) + util.Effect( "Explosion", effectdata, true, true ) + + self.Exploded = true + self:ShowOutput() + + if ( self.removeafter ) then + self:Remove() + return + end +end + +function ENT:ShowOutput( ) + if (self.Exploded) then + self:SetOverlayText("Exploded\n"..self.NormInfo) + else + self:SetOverlayText("Explosive\n"..self.NormInfo) + end +end + +duplicator.RegisterEntityClass( "gmod_wire_simple_explosive", WireLib.MakeWireEnt, "Data", "key", "damage", "removeafter", "radius" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_socket.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_socket.lua new file mode 100644 index 0000000..3fddcc6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_socket.lua @@ -0,0 +1,404 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Socket" +ENT.Purpose = "Links with a plug" +ENT.Instructions = "Move a plug close to a plug to link them, and data will be transferred through the link." +ENT.WireDebugName = "Socket" + +local PositionOffsets = { + ["models/wingf0x/isasocket.mdl"] = Vector(0,0,0), + ["models/wingf0x/altisasocket.mdl"] = Vector(0,0,2.6), + ["models/wingf0x/ethernetsocket.mdl"] = Vector(0,0,0), + ["models/wingf0x/hdmisocket.mdl"] = Vector(0,0,0), + ["models/props_lab/tpplugholder_single.mdl"] = Vector(5, 13, 10), + ["models/bull/various/usb_socket.mdl"] = Vector(8,0,0), + ["models/hammy/pci_slot.mdl"] = Vector(0,0,0), + ["models//hammy/pci_slot.mdl"] = Vector(0,0,0), -- For some reason, GetModel on this model has two / on the client... Bug? +} +local AngleOffsets = { + ["models/wingf0x/isasocket.mdl"] = Angle(0,0,0), + ["models/wingf0x/altisasocket.mdl"] = Angle(0,0,0), + ["models/wingf0x/ethernetsocket.mdl"] = Angle(0,0,0), + ["models/wingf0x/hdmisocket.mdl"] = Angle(0,0,0), + ["models/props_lab/tpplugholder_single.mdl"] = Angle(0,0,0), + ["models/bull/various/usb_socket.mdl"] = Angle(0,0,0), + ["models/hammy/pci_slot.mdl"] = Angle(0,0,0), + ["models//hammy/pci_slot.mdl"] = Angle(0,0,0), -- For some reason, GetModel on this model has two / on the client... Bug? +} +local SocketModels = { + ["models/wingf0x/isasocket.mdl"] = "models/wingf0x/isaplug.mdl", + ["models/wingf0x/altisasocket.mdl"] = "models/wingf0x/isaplug.mdl", + ["models/wingf0x/ethernetsocket.mdl"] = "models/wingf0x/ethernetplug.mdl", + ["models/wingf0x/hdmisocket.mdl"] = "models/wingf0x/hdmiplug.mdl", + ["models/props_lab/tpplugholder_single.mdl"] = "models/props_lab/tpplug.mdl", + ["models/bull/various/usb_socket.mdl"] = "models/bull/various/usb_stick.mdl", + ["models/hammy/pci_slot.mdl"] = "models/hammy/pci_card.mdl", + ["models//hammy/pci_slot.mdl"] = "models//hammy/pci_card.mdl", -- For some reason, GetModel on this model has two / on the client... Bug? +} + +function ENT:GetLinkPos() + return self:LocalToWorld(PositionOffsets[self:GetModel()] or Vector(0,0,0)), self:LocalToWorldAngles(AngleOffsets[self:GetModel()] or Angle(0,0,0)) +end + +function ENT:CanLink( Target ) + if (Target.Socket and Target.Socket:IsValid()) then return false end + if (SocketModels[self:GetModel()] ~= Target:GetModel()) then return false end + return true +end + +function ENT:GetClosestPlug() + local Pos, _ = self:GetLinkPos() + + local plugs = ents.FindInSphere( Pos, (CLIENT and self:GetNWInt( "AttachRange", 5 ) or self.AttachRange) ) + + local ClosestDist + local Closest + + for k,v in pairs( plugs ) do + if (v:GetClass() == self:GetPlugClass() and not v:GetNWBool( "Linked", false )) then + local Dist = v:GetPos():Distance( Pos ) + if (ClosestDist == nil or ClosestDist > Dist) then + ClosestDist = Dist + Closest = v + end + end + end + + return Closest +end + +function ENT:GetPlugClass() + return "gmod_wire_plug" +end + +if CLIENT then + function ENT:DrawEntityOutline() + if (GetConVar("wire_plug_drawoutline"):GetBool()) then + BaseClass.DrawEntityOutline( self ) + end + end + + -- hook.Add("HUDPaint","Wire_Socket_DrawLinkHelperLine",function() + -- local sockets = ents.FindByClass("gmod_wire_socket") + -- for k,self in pairs( sockets ) do + -- local Pos, _ = self:GetLinkPos() + + -- local Closest = self:GetClosestPlug() + + -- if IsValid(Closest) and self:CanLink(Closest) and Closest:GetNWBool( "PlayerHolding", false ) and Closest:GetClosestSocket() == self then + -- local plugpos = Closest:GetPos():ToScreen() + -- local socketpos = Pos:ToScreen() + -- surface.SetDrawColor(255,255,100,255) + -- surface.DrawLine(plugpos.x, plugpos.y, socketpos.x, socketpos.y) + -- end + -- end + -- end) + + return -- No more client +end + + +local NEW_PLUG_WAIT_TIME = 2 +local LETTERS = { "A", "B", "C", "D", "E", "F", "G", "H" } +local LETTERS_INV = {} +for k,v in pairs( LETTERS ) do + LETTERS_INV[v] = k +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:SetNWBool( "Linked", false ) + + self.Memory = {} + + self.DoNextThink = CurTime() + 5 -- wait 5 seconds +end + +function ENT:Setup( ArrayInput, WeldForce, AttachRange ) + local old = self.ArrayInput + self.ArrayInput = ArrayInput or false + + if not (self.Inputs and self.Outputs and self.ArrayInput == old) then + if (self.ArrayInput) then + self.Inputs = WireLib.CreateInputs( self, { "In [ARRAY]" } ) + self.Outputs = WireLib.CreateOutputs( self, { "Out [ARRAY]" } ) + else + self.Inputs = WireLib.CreateInputs( self, LETTERS ) + self.Outputs = WireLib.CreateOutputs( self, LETTERS ) + end + end + + self.WeldForce = WeldForce or 5000 + self.AttachRange = AttachRange or 5 + self:SetNWInt( "AttachRange", self.AttachRange ) + + self:ShowOutput() +end + +function ENT:TriggerInput( name, value ) + if (self.Plug and self.Plug:IsValid()) then + self.Plug:SetValue( name, value ) + end + self:ShowOutput() +end + +function ENT:SetValue( name, value ) + if not (self.Plug and self.Plug:IsValid()) then return end + if (name == "In") then + if (self.ArrayInput) then -- Both have array + WireLib.TriggerOutput( self, "Out", table.Copy( value ) ) + else -- Target has array, this does not + for i=1,#LETTERS do + local val = (value or {})[i] + if isnumber(val) then + WireLib.TriggerOutput( self, LETTERS[i], val ) + end + end + end + else + if (self.ArrayInput) then -- Target does not have array, this does + if (value ~= nil) then + local data = table.Copy( self.Outputs.Out.Value ) + data[LETTERS_INV[name]] = value + WireLib.TriggerOutput( self, "Out", data ) + end + else -- Niether have array + if (value ~= nil) then + WireLib.TriggerOutput( self, name, value ) + end + end + end + self:ShowOutput() +end + +------------------------------------------------------------ +-- WriteCell +-- Hi-speed support +------------------------------------------------------------ +function ENT:WriteCell( Address, Value, WriteToMe ) + Address = math.floor(Address) + if (WriteToMe) then + self.Memory[Address or 1] = Value or 0 + return true + else + if (self.Plug and self.Plug:IsValid()) then + self.Plug:WriteCell( Address, Value, true ) + return true + else + return false + end + end +end + +------------------------------------------------------------ +-- ReadCell +-- Hi-speed support +------------------------------------------------------------ +function ENT:ReadCell( Address ) + Address = math.floor(Address) + return self.Memory[Address or 1] or 0 +end + +function ENT:ResetValues() + if (self.ArrayInput) then + WireLib.TriggerOutput( self, "Out", {} ) + else + for i=1,#LETTERS do + WireLib.TriggerOutput( self, LETTERS[i], 0 ) + end + end + self.Memory = {} + self:ShowOutput() +end + +------------------------------------------------------------ +-- ResendValues +-- Resends the values when plugging in +------------------------------------------------------------ +function ENT:ResendValues() + if (not self.Plug) then return end + if (self.ArrayInput) then + self.Plug:SetValue( "In", self.Inputs.In.Value ) + else + for i=1,#LETTERS do + self.Plug:SetValue( LETTERS[i], self.Inputs[LETTERS[i]].Value ) + end + end +end + +function ENT:OnWeldRemoved() + self.Weld = nil + + self.Plug:SetNWBool( "Linked", false ) + self:SetNWBool( "Linked", false ) + + self.Plug.Socket = nil + self.Plug:ResetValues() + + self.Plug = nil + self:ResetValues() + + self.DoNextThink = CurTime() + NEW_PLUG_WAIT_TIME +end + +function ENT:AttachWeld(weld) + if self.Plug then self.Plug:DeleteOnRemove( weld ) end + self:DeleteOnRemove( weld ) + if self.Weld then self.Weld:RemoveCallOnRemove("wire_socket_remove_on_weld") end + self.Weld = weld + weld:CallOnRemove("wire_socket_remove_on_weld",function() self:OnWeldRemoved() end) +end + +-- helper function +local function FindConstraint( ent, plug ) + if IsValid(ent) then + local welds = constraint.FindConstraints( ent, "Weld" ) + for k,v in pairs( welds ) do + if (v.Ent2 == plug) then + return v.Constraint + end + end + end + if IsValid(plug) then + local welds = constraint.FindConstraints( plug, "Weld" ) + for k,v in pairs( welds ) do + if (v.Ent2 == ent) then + return v.Constraint + end + end + end +end + +------------------------------------------------------------ +-- Think +-- Find nearby plugs and connect to them +------------------------------------------------------------ +function ENT:Think() + BaseClass.Think(self) + if self.DoNextThink then + self:NextThink( self.DoNextThink ) + self.DoNextThink = nil + return true + end + + if not IsValid(self.Plug) then -- currently not linked, check for nearby links + local Pos, Ang = self:GetLinkPos() + local Closest = self:GetClosestPlug() + + if (Closest and Closest:IsValid() and self:CanLink( Closest ) and not Closest:IsPlayerHolding() and Closest:GetClosestSocket() == self) then + self.Plug = Closest + Closest.Socket = self + + -- Move + Closest:SetPos( Pos ) + Closest:SetAngles( Ang ) + + -- Weld + local weld = FindConstraint(self,Closest) + if not weld then + weld = constraint.Weld( self, Closest, 0, 0, self.WeldForce, true ) + end + + if weld and weld:IsValid() then self:AttachWeld(weld) end + + -- Resend all values + Closest:ResendValues() + self:ResendValues() + + Closest:SetNWBool( "Linked", true ) + self:SetNWBool( "Linked", true ) + end + + self:NextThink( CurTime() + 0.05 ) + return true + else + self:NextThink( CurTime() + 1 ) -- while linked, there's no point in running any faster than this + return true + end +end + +function ENT:ShowOutput() + local OutText = "Socket [" .. self:EntIndex() .. "]\n" + if (self.ArrayInput) then + OutText = OutText .. "Array input/outputs." + else + OutText = OutText .. "Number input/outputs." + end + if (self.Plug and self.Plug:IsValid()) then + OutText = OutText .. "\nLinked to plug [" .. self.Plug:EntIndex() .. "]" + end + self:SetOverlayText(OutText) +end + +duplicator.RegisterEntityClass( "gmod_wire_socket", WireLib.MakeWireEnt, "Data", "ArrayInput", "WeldForce", "AttachRange" ) + +------------------------------------------------------------ +-- Adv Duplicator Support +------------------------------------------------------------ +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + info.Socket = {} + info.Socket.ArrayInput = self.ArrayInput + info.Socket.WeldForce = self.WeldForce + info.Socket.AttachRange = self.AttachRange + if self.Plug then + info.Socket.Plug = self.Plug:EntIndex() + else + -- if we don't write -1 here then sockets will somehow remember which plugs they used to be + -- connected to in the past after paste even though that reference no longer exists. I have no clue why + info.Socket.Plug = -1 + end + + return info +end + +function ENT:GetApplyDupeInfoParams(info) + return info.Socket.ArrayInput, info.Socket.WeldForce, info.Socket.AttachRange +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID, GetConstByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + if (info.Socket) then + ent:Setup( self:GetApplyDupeInfoParams(info) ) + if info.Socket.Plug ~= -1 then -- check for the strangely required -1 here (see BuildDupeInfo) + local plug = GetEntByID( info.Socket.Plug ) + if IsValid(plug) then + ent.Plug = plug + plug.Socket = ent + ent.Weld = nil + + plug:SetNWBool( "Linked", true ) + ent:SetNWBool( "Linked", true ) + -- Resend all values + plug:ResendValues() + ent:ResendValues() + + -- Attempt to find connected plug + timer.Simple(0.5,function() + local weld = FindConstraint( ent, plug ) + if not IsValid(weld) then + weld = constraint.Weld( self, plug, 0, 0, self.WeldForce, true ) + end + if IsValid(weld) then + self:AttachWeld(weld) + end + end) + end + end + else -- OLD DUPES COMPATIBILITY + ent:Setup() -- default values + + -- Attempt to find connected plug + timer.Simple(0.5,function() + local weld = FindConstraint( ent ) + if IsValid(weld) then + self:AttachWeld(weld) + end + end) + end -- /OLD DUPES COMPATIBILITY +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_soundemitter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_soundemitter.lua new file mode 100644 index 0000000..2820d2a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_soundemitter.lua @@ -0,0 +1,190 @@ +--[[ + The Wire Sound Emitter emits a sound whose parameters can be tweaked. + See http://wiki.garrysmod.com/page/Category:CSoundPatch +--]] + +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Sound Emitter" +ENT.WireDebugName = "Sound Emitter" + +if CLIENT then return end + +local DefaultSamples = { + "synth/square.wav", + "synth/saw.wav", + "synth/tri.wav", + "synth/sine.wav" +} +for _, str in pairs(DefaultSamples) do util.PrecacheSound(str) end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = WireLib.CreateInputs(self, { "A", "Toggle", "Volume", "Play", "Stop", + "PitchRelative", "Sample", "SampleName [STRING]" }) + self.Outputs = WireLib.CreateOutputs(self, { "Duration", "Property Sound", "Properties [ARRAY]", "Memory" }) + + self.Samples = table.Copy(DefaultSamples) + + self.Active = false + self.Volume = 100 + self.Pitch = 100 + self.sound = self.Samples[1] + -- self.sound is a string, self.SoundObj is a CSoundPatch + + self.NeedsRefresh = true + + hook.Add("PlayerConnect", self:GetClass() .. self:EntIndex(), function() + self.NeedsRefresh = true + end) +end + +function ENT:OnRemove() + hook.Remove("PlayerConnect", self:GetClass() .. self:EntIndex()) + self:StopSounds() + BaseClass.OnRemove(self) +end + +--[[ + readcells: + 0: active + 1: volume + 2: pitchrelative + 3: duration +]] +function ENT:ReadCell(address) + address = math.floor(address) + if address == 0 then + return self.Active and 1 or 0 + elseif address == 1 then + return self.Volume / 100 + elseif address == 2 then + return self.Pitch / 100 + elseif address == 3 then + return self.Outputs.Duration.Value + end +end + + +-- writecells: +local cellsOut = { + [0] = "A", + [1] = "Volume", + [2] = "PitchRelative", + [3] = "Sample", + [4] = "Play", + [5] = "Stop", +} + +function ENT:WriteCell(address, value) + address = math.floor(address) + if cellsOut[address] then + self:TriggerInput(cellsOut[address], value) + return true + else + return false + end +end + +function ENT:TriggerInput(iname, value) + if iname == "Toggle" and value ~= 0 then + self:TriggerInput("A", not self.Active) + elseif iname == "A" then + if value ~= 0 then + self:TriggerInput("Play", 1) + else + self:TriggerInput("Stop", 1) + end + elseif iname == "Play" and value ~= 0 then + -- Property sounds need to be refreshed + -- every time to work probably especially + -- when it has multiple sounds/pitches/volumes. + if self.SoundProperties then + self.NeedsRefresh = true + WireLib.TriggerOutput(self, "Duration", SoundDuration(self.sound)) + end + + self.Active = true + self:StartSounds() + elseif iname == "Stop" and value ~= 0 then + self.Active = false + self:StopSounds() + elseif iname == "Volume" then + self.Volume = math.Clamp(math.floor(value*100), 0.0, 100.0) + elseif iname == "PitchRelative" then + self.Pitch = math.Clamp(math.floor(value*100), 0, 255) + elseif iname == "Sample" then + self:TriggerInput("SampleName", self.Samples[value] or self.Samples[1]) + elseif iname == "SampleName" then + self:SetSound(value) + end + self:UpdateSound() +end + +function ENT:UpdateSound() + if self.NeedsRefresh or self.sound ~= self.ActiveSample then + self.NeedsRefresh = nil + self.SoundObj = CreateSound(self, self.sound) + self.ActiveSample = self.sound + + self.SoundProperties = sound.GetProperties(self.sound) + if self.SoundProperties then + WireLib.TriggerOutput(self, "Duration", SoundDuration(self.sound)) + WireLib.TriggerOutput(self, "Property Sound", 1) + WireLib.TriggerOutput(self, "Properties", self.SoundProperties) + else + WireLib.TriggerOutput(self, "Property Sound", 0) + WireLib.TriggerOutput(self, "Properties", {}) + end + + if self.Active then self:StartSounds() end + end + self.SoundObj:ChangePitch(self.Pitch, 0) + self.SoundObj:ChangeVolume(self.Volume / 100.0, 0) +end + +function ENT:SetSound(soundName) + self:StopSounds() + + if soundName:match('["?]') then return end + parsedsound = soundName:Trim() + util.PrecacheSound(parsedsound) + + self.sound = parsedsound + + self.SoundProperties = sound.GetProperties(self.sound) + if self.SoundProperties then + WireLib.TriggerOutput(self, "Duration", SoundDuration(self.sound)) + WireLib.TriggerOutput(self, "Property Sound", 1) + WireLib.TriggerOutput(self, "Properties", self.SoundProperties) + else + WireLib.TriggerOutput(self, "Property Sound", 0) + WireLib.TriggerOutput(self, "Properties", {}) + end + + self:SetOverlayText( parsedsound:gsub("[/\\]+","/") ) +end + +function ENT:StartSounds() + if self.NeedsRefresh then + self:UpdateSound() + end + if self.SoundObj then + self.SoundObj:Play() + end +end + +function ENT:StopSounds() + if self.SoundObj then + self.SoundObj:Stop() + end +end + +function ENT:Setup(sample) + self:SetSound(sample) +end + +duplicator.RegisterEntityClass("gmod_wire_soundemitter", WireLib.MakeWireEnt, "Data", "sound") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spawner.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spawner.lua new file mode 100644 index 0000000..e7674e9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spawner.lua @@ -0,0 +1,243 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Prop Spawner" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Prop Spawner" + +if CLIENT then return end -- No more client + +local wire_spawner_delay = CreateConVar( "wire_spawner_delay", game.SinglePlayer() and 0 or 0.2 ) + +local GlobalUndoList = {} + +hook.Add("EntityRemoved", "wire_spawner_EntityRemoved", function(ent) + if not GlobalUndoList[ent] then return end + GlobalUndoList[ent]:CheckEnts(ent) + GlobalUndoList[ent] = nil +end) + +local function MakePropNoEffect(...) + local backup = DoPropSpawnedEffect + DoPropSpawnedEffect = function() end + local ret = MakeProp(...) + DoPropSpawnedEffect = backup + return ret +end + +function ENT:Initialize() + + self:SetMoveType( MOVETYPE_NONE ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:DrawShadow( false ) + + self:SetTrigger( true ) -- enables receiving StartTouch/EndTouch calls + self.DisabledByTouch = false + self.DisabledByTimeUntil = CurTime() + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then phys:Wake() end + + self.UndoList = {} + + -- Spawner is "edge-triggered" + self.SpawnLastValue = 0 + self.UndoLastValue = 0 + + -- Made more efficient by updating the overlay text and + -- Wire output only when number of active props changes (TheApathetic) + self.CurrentPropCount = 0 + + -- Add inputs/outputs (TheApathetic) + self.Inputs = WireLib.CreateSpecialInputs(self, { "Spawn", "Undo", "UndoEnt", "SpawnEffect" }, { "NORMAL", "NORMAL", "ENTITY", "NORMAL" }) + self.Outputs = WireLib.CreateSpecialOutputs(self, { "Out", "LastSpawned", "Props" }, { "NORMAL", "ENTITY", "ARRAY" }) + + Wire_TriggerOutput(self, "Props", self.UndoList) +end + +function ENT:Setup( delay, undo_delay, spawn_effect, mat, r, g, b, a, skin ) + self.delay = delay + self.undo_delay = undo_delay + self.spawn_effect = spawn_effect + if r then + self.mat = mat + self.r = r + self.g = g + self.b = b + self.a = a + self.skin = skin + + self:SetRenderMode(3) + self:SetMaterial(mat or "") + self:SetSkin(skin or 0) + self:SetColor(Color(r or 255, g or 255, b or 255, 100)) + end + + self:ShowOutput() +end + +function ENT:StartTouch( ent ) + -- we handle touch blocking in DoSpawn, as otherwise we can get + -- a StartTouch before we've added the prop to our local undo list. +end + +function ENT:EndTouch( ent ) + if ent.PropSpawner == self then self.DisabledByTouch = false end +end + +function ENT:DoSpawn( pl, down ) + + if self.DisabledByTouch or self.DisabledByTimeUntil > CurTime() then return end + + local ent = self + if (not ent:IsValid()) then return end + + local phys = ent:GetPhysicsObject() + if (not phys:IsValid()) then return end + + local Pos = ent:GetPos() + local Ang = ent:GetAngles() + local model = ent:GetModel() + local prop = nil + + if self.spawn_effect ~= 0 then + prop = MakeProp( pl, Pos, Ang, model, {}, {} ) + else + prop = MakePropNoEffect( pl, Pos, Ang, model, {}, {} ) + end + + if not IsValid(prop) then return end + + prop:SetMaterial( ent:GetMaterial() ) + prop:SetColor(Color(self.r, self.g, self.b, self.a)) + prop:SetSkin( ent:GetSkin() or 0 ) + prop.PropSpawner = self + + -- apply the physic's objects properties + local phys2 = prop:GetPhysicsObject() + phys2:SetMass( phys:GetMass() ) -- known issue: while being held with the physgun, the spawner spawns 45k mass props. Could be worked around with a Think hook, but nah... + + if not ent:IsPlayerHolding() then -- minge protection :) + phys2:SetVelocity( phys:GetVelocity() ) + phys2:AddAngleVelocity( phys:GetAngleVelocity() - phys2:GetAngleVelocity() ) -- No SetAngleVelocity, so we must subtract the current angular velocity + end + + local nocollide = constraint.NoCollide( prop, ent, 0, 0 ) + if (nocollide:IsValid()) then prop:DeleteOnRemove( nocollide ) end + + undo.Create("Prop") + undo.AddEntity( prop ) + undo.AddEntity( nocollide ) + undo.SetPlayer( pl ) + undo.Finish() + + -- Check if the player is NULL (ab0mbs) + if IsValid(pl) then + pl:AddCleanup( "props", prop ) + pl:AddCleanup( "props", nocollide ) + end + + table.insert( self.UndoList, 1, prop ) + GlobalUndoList[prop] = self + + Wire_TriggerOutput(self, "LastSpawned", prop) + self.CurrentPropCount = #self.UndoList + Wire_TriggerOutput(self, "Out", self.CurrentPropCount) + Wire_TriggerOutput(self, "Props", self.UndoList) + self:ShowOutput() + + self.DisabledByTouch = true + self.DisabledByTimeUntil = CurTime() + wire_spawner_delay:GetFloat() + + if (self.undo_delay == 0) then return end + + timer.Simple( self.undo_delay, function() if prop:IsValid() then prop:Remove() end end ) + +end + +function ENT:DoUndo( pl ) + if not next(self.UndoList) then return end + + local ent = table.remove(self.UndoList, #self.UndoList) + + if not IsValid(ent) then + return self:DoUndo(pl) + end + + ent:Remove() + WireLib.AddNotify(pl, "Undone Prop", NOTIFY_UNDO, 2 ) +end + +function ENT:DoUndoEnt( pl, ent ) + if not IsValid(ent) then return end + + if GlobalUndoList[ent] ~= self then return end + + ent:Remove() + WireLib.AddNotify(pl, "Undone Prop", NOTIFY_UNDO, 2 ) +end + +function ENT:CheckEnts(removed_entity) + -- Purge list of no longer existing props + for i = #self.UndoList,1,-1 do + local ent = self.UndoList[i] + if not IsValid(ent) or ent == removed_entity then + table.remove(self.UndoList, i) + end + end + + -- Check to see if active prop count has changed + if (#self.UndoList ~= self.CurrentPropCount) then + self.CurrentPropCount = #self.UndoList + Wire_TriggerOutput(self, "Out", self.CurrentPropCount) + Wire_TriggerOutput(self, "Props", self.UndoList) + self:ShowOutput() + end +end + +function ENT:TriggerInput(iname, value) + local pl = self:GetPlayer() + + if (iname == "Spawn") then + -- Spawner is "edge-triggered" (TheApathetic) + local SpawnThisValue = value > 0 + if (SpawnThisValue == self.SpawnLastValue) then return end + self.SpawnLastValue = SpawnThisValue + + if (SpawnThisValue) then + -- Simple copy/paste of old numpad Spawn with a few modifications + if (self.delay == 0) then self:DoSpawn( pl ) return end + + local TimedSpawn = function ( ent, pl ) + if not IsValid(ent) then return end + ent:DoSpawn( pl ) + end + + timer.Simple( self.delay, function() TimedSpawn(self, pl) end ) + end + elseif (iname == "Undo") then + -- Same here + local UndoThisValue = value > 0 + if (UndoThisValue == self.UndoLastValue) then return end + self.UndoLastValue = UndoThisValue + + if (UndoThisValue) then self:DoUndo(pl) end + elseif (iname == "UndoEnt") then + self:DoUndoEnt(pl, value) + elseif (iname == "SpawnEffect") then + self.spawn_effect = value + end +end + +function ENT:ShowOutput() + self:SetOverlayText("Spawn Delay: "..self.delay.."\nUndo Delay: "..self.undo_delay.."\nActive Props: "..self.CurrentPropCount) +end + +function ENT:OnRemove() + -- unregister spawned props from GlobalUndoList + for _,ent in ipairs(self.UndoList) do + GlobalUndoList[ent] = nil + end +end + +duplicator.RegisterEntityClass("gmod_wire_spawner", WireLib.MakeWireEnt, "Data", "delay", "undo_delay", "spawn_effect", "mat", "r", "g", "b", "a", "skin") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_speedometer.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_speedometer.lua new file mode 100644 index 0000000..1960696 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_speedometer.lua @@ -0,0 +1,104 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Speedometer" +ENT.WireDebugName = "Speedo" + +function ENT:GetXYZMode() + return self:GetNWBool( 0 ) +end + +function ENT:GetAngVel() + return self:GetNWBool( 1 ) +end + +function ENT:SetModes( XYZMode, AngVel ) + self:SetNWBool( 0, XYZMode ) + self:SetNWBool( 1, AngVel ) +end + +if CLIENT then + function ENT:Think() + BaseClass.Think(self) + + local txt + if (self:GetXYZMode()) then + local vel = self:WorldToLocal(self:GetVelocity()+self:GetPos()) + txt = "Velocity = " .. math.Round((-vel.y or 0)*1000)/1000 .. "," .. math.Round((vel.x or 0)*1000)/1000 .. "," .. math.Round((vel.z or 0)*1000)/1000 + else + local vel = self:GetVelocity():Length() + txt = "Speed = " .. math.Round((vel or 0)*1000)/1000 + end + + --sadly self:GetPhysicsObject():GetAngleVelocity() does work client side, so read out is unlikely + /*if (self:GetAngVel()) then + local ang = self:GetPhysicsObject():GetAngleVelocity() + txt = txt .. "\nAngVel = P " .. math.Round((ang.y or 0)*1000)/1000 .. ", Y " .. math.Round((ang.z or 0)*1000) /1000 .. ", R " .. math.Round((ang.x or 0)*1000)/1000 + end*/ + + self:SetOverlayText( txt ) + + self:NextThink(CurTime()+0.04) + return true + end + + return -- No more client +end + +local MODEL = Model("models/jaanus/wiretool/wiretool_speed.mdl") + +function ENT:Initialize() + self:SetModel( MODEL ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = Wire_CreateOutputs(self, { "Out", "MPH", "KPH" }) +end + +function ENT:Setup( xyz_mode, AngVel ) + self.z_only = xyz_mode --was renamed but kept for dupesaves + self.XYZMode = xyz_mode + self.AngVel = AngVel + self:SetModes( xyz_mode,AngVel ) + + local outs = {} + if (xyz_mode) then + outs = { "X", "Y", "Z" } + else + outs = { "Out", "MPH", "KPH", } + end + if (AngVel) then + table.Add(outs, {"AngVel_P", "AngVel_Y", "AngVel_R" } ) + end + Wire_AdjustOutputs(self, outs) +end + +function ENT:Think() + BaseClass.Think(self) + + if (self.XYZMode) then + local vel = self:WorldToLocal(self:GetVelocity()+self:GetPos()) + if (COLOSSAL_SANDBOX) then vel = vel * 6.25 end + Wire_TriggerOutput(self, "X", -vel.y) + Wire_TriggerOutput(self, "Y", vel.x) + Wire_TriggerOutput(self, "Z", vel.z) + else + local vel = self:GetVelocity():Length() + if (COLOSSAL_SANDBOX) then vel = vel * 6.25 end + Wire_TriggerOutput(self, "Out", vel) // vel = Source Units / sec, Source Units = Inch * 0.75 , more info here: http://developer.valvesoftware.com/wiki/Dimensions#Map_Grid_Units:_quick_reference + Wire_TriggerOutput(self, "MPH", vel * 3600 / 63360 * 0.75) + Wire_TriggerOutput(self, "KPH", vel * 3600 * 0.0000254 * 0.75) + end + + if (self.AngVel) then + local ang = self:GetPhysicsObject():GetAngleVelocity() + Wire_TriggerOutput(self, "AngVel_P", ang.y) + Wire_TriggerOutput(self, "AngVel_Y", ang.z) + Wire_TriggerOutput(self, "AngVel_R", ang.x) + end + + self:NextThink(CurTime()+0.04) + return true +end + +duplicator.RegisterEntityClass("gmod_wire_speedometer", WireLib.MakeWireEnt, "Data", "z_only", "AngVel") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/cl_init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/cl_init.lua new file mode 100644 index 0000000..b8148b4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/cl_init.lua @@ -0,0 +1,179 @@ +include("cl_spuvm.lua") +include("shared.lua") + + + +-------------------------------------------------------------------------------- +WireSPU_MaxChannels = 32 + +local SoundEmitters = {} +local SoundEmitterLookup = {} +local HUDLookup = {} + + + + +-------------------------------------------------------------------------------- +-- Update sound emitters certain SPU is linked to +-------------------------------------------------------------------------------- +local function recalculateSoundEmitterLookup() + SoundEmitterLookup = {} + HUDLookup = {} + for gpuIdx,linkedSPUs in pairs(SoundEmitters) do + for _,linkedSPUIdx in pairs(linkedSPUs) do + local linkedEnt = ents.GetByIndex(linkedSPUIdx) + if linkedEnt and linkedEnt:IsValid() then + if linkedEnt:IsPlayer() then + HUDLookup[linkedSPUIdx] = gpuIdx + else + SoundEmitterLookup[linkedSPUIdx] = gpuIdx + end + end + end + end +end + +local function SPU_SoundEmitterState(um) + -- Read sound emitters for this SPU + local gpuIdx = um:ReadLong() + SoundEmitters[gpuIdx] = {} + + -- Fetch all sound emitters + local count = um:ReadShort() + for i=1,count do + SoundEmitters[gpuIdx][i] = um:ReadLong() + end + + -- Recalculate small lookup table for sound emitter system + recalculateSoundEmitterLookup() +end +usermessage.Hook("wire_spu_soundstate", SPU_SoundEmitterState) + +local function SPU_SoundSources(um) + local SPU = ents.GetByIndex(um:ReadLong()) + if not SPU then return end + if not SPU:IsValid() then return end + + for i=0,WireSPU_MaxChannels-1 do + SPU.SoundSources[i] = ents.GetByIndex(um:ReadLong()) + SPU.SoundSources[i]:SetNoDraw(true) + SPU.SoundSources[i]:SetModelScale(0,0) + end + + -- Reset VM + SPU.VM:Reset() + SPU.VM.Memory[65535] = 1 + SPU.VM.Memory[65527] = 300000 +end +usermessage.Hook("wire_spu_soundsources", SPU_SoundSources) + + + + +-------------------------------------------------------------------------------- +-- Update SPU features/memory model +-------------------------------------------------------------------------------- +local function SPU_MemoryModel(um) + local SPU = ents.GetByIndex(um:ReadLong()) + if not SPU then return end + if not SPU:IsValid() then return end + + if SPU.VM then + SPU.VM.ROMSize = um:ReadLong() + SPU.VM.SerialNo = um:ReadFloat() + SPU.VM.RAMSize = SPU.VM.ROMSize + else + SPU.ROMSize = um:ReadLong() + SPU.SerialNo = um:ReadFloat() + end + SPU.ChipType = um:ReadShort() +end +usermessage.Hook("wire_spu_memorymodel", SPU_MemoryModel) + + + + +-------------------------------------------------------------------------------- +function ENT:Initialize() + -- Create virtual machine + self.VM = CPULib.VirtualMachine() + self.VM.SerialNo = CPULib.GenerateSN("SPU") + self.VM.RAMSize = 65536 + self.VM.ROMSize = 65536 + self.VM.PCAP = 0 + self.VM.RQCAP = 0 + self.VM.CPUVER = 1.0 -- Beta SPU by default + self.VM.CPUTYPE = 2 -- ZSPU + self.ChipType = 0 + + -- Create fake sound sources + self.SoundSources = {} + + -- Hard-reset VM and override it + self:OverrideVM() + self.VMReset = 0 + + -- Setup caching + GPULib.ClientCacheCallback(self,function(Address,Value) + self.VM:WriteCell(Address,Value) + self.VM.ROM[Address] = Value + end) +end + + + + +-------------------------------------------------------------------------------- +-- Entity deleted +function ENT:OnRemove() + GPULib.ClientCacheCallback(self,nil) + if self.VM.Channel then + for k,v in pairs(self.VM.Channel) do + v.Sound:Stop() + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Run SPU execution +function ENT:Run() + -- Limit frequency + self.VM.Memory[65527] = math.Clamp(self.VM.Memory[65527],1,1200000) + + -- Calculate timing + local Cycles = math.max(1,math.floor(self.VM.Memory[65527]*self.DeltaTime*0.5)) + self.VM.TimerDT = self.DeltaTime/Cycles + self.VM.TIMER = self.CurrentTime + + -- Run until interrupt, or if async thread then until async thread stops existing + while (Cycles > 0) and (self.VM.INTR == 0) do + local previousTMR = self.VM.TMR + self.VM:Step() + Cycles = Cycles - (self.VM.TMR - previousTMR) + end + + -- Reset INTR register for async thread + self.VM.INTR = 0 +end + +-------------------------------------------------------------------------------- +-- Think function +function ENT:Think() + -- Calculate time-related variables + self.CurrentTime = CurTime() + self.DeltaTime = math.min(1/30,self.CurrentTime - (self.PreviousTime or 0)) + self.PreviousTime = self.CurrentTime + + -- Dont run until all sound sources are init + if #self.SoundSources == 0 then return end + + -- Run asynchronous thread + if self.VM.Memory[65535] == 1 then + self:Run() + -- Calculate ADSR + self.VM:CalculateADSR(self.DeltaTime) + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/cl_spuvm.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/cl_spuvm.lua new file mode 100644 index 0000000..cb7a7df --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/cl_spuvm.lua @@ -0,0 +1,455 @@ +-------------------------------------------------------------------------------- +-- Override virtual machine functions and features +-------------------------------------------------------------------------------- +local VM = {} + +function ENT:OverrideVM() + -- Store VM calls that will be overriden + self.VM.BaseReset = self.VM.Reset + + -- Add additional VM functionality + for k,v in pairs(VM) do + if k == "OpcodeTable" then + for k2,v2 in pairs(v) do + self.VM.OpcodeTable[k2] = v2 + end + else + self.VM[k] = v + end + end + + self.VM.Entity = self + + self.VM.Interrupt = function(self,interruptNo,interruptParameter,isExternal,cascadeInterrupt) + self.IP = self.EntryPoint1 + self.LADD = interruptParameter + self.LINT = interruptNo + end + + -- Override ports + self.VM.WritePort = function(VM,Port,Value) + VM:WriteCell(63488+Port,Value) + end + self.VM.ReadPort = function(VM,Port) + return VM:ReadCell(63488+Port) + end + + -- Override writecell + self.VM.BaseWriteCell = self.VM.WriteCell + self.VM.WriteCell = function(VM,Address,Value) + VM:BaseWriteCell(Address,Value) + if Address == 65534 then + VM:Reset() + elseif Address == 65530 then + VM.ROM = {} + end + end + + -- Add internal registers + self.VM.InternalRegister[128] = "EntryPoint0" + self.VM.InternalRegister[129] = "EntryPoint1" + + -- Remove internal registers + self.VM.InternalRegister[24] = nil --IDTR + self.VM.InternalRegister[32] = nil --IF + self.VM.InternalRegister[33] = nil --PF + self.VM.InternalRegister[34] = nil --EF + self.VM.InternalRegister[45] = nil --BusLock + self.VM.InternalRegister[46] = nil --IDLE + self.VM.InternalRegister[47] = nil --INTR + self.VM.InternalRegister[52] = nil --NIDT + + -- Remove some instructions + self.VM.OperandCount[16] = nil --RD + self.VM.OperandCount[17] = nil --WD + self.VM.OperandCount[28] = nil --SPG + self.VM.OperandCount[29] = nil --CPG + self.VM.OperandCount[37] = nil --HALT + self.VM.OperandCount[41] = nil --IRET + self.VM.OperandCount[42] = nil --STI + self.VM.OperandCount[43] = nil --CLI + self.VM.OperandCount[44] = nil --STP + self.VM.OperandCount[45] = nil --CLP + self.VM.OperandCount[46] = nil --STD + self.VM.OperandCount[48] = nil --STEF + self.VM.OperandCount[49] = nil --CLEF + self.VM.OperandCount[70] = nil --EXTINT + self.VM.OperandCount[95] = nil --ERPG + self.VM.OperandCount[96] = nil --WRPG + self.VM.OperandCount[97] = nil --RDPG + self.VM.OperandCount[99] = nil --LIDTR + self.VM.OperandCount[100] = nil --STATESTORE + self.VM.OperandCount[109] = nil --STATERESTORE + self.VM.OperandCount[110] = nil --EXTRET + self.VM.OperandCount[113] = nil --RLADD + self.VM.OperandCount[116] = nil --STD2 + self.VM.OperandCount[118] = nil --STM + self.VM.OperandCount[119] = nil --CLM + self.VM.OperandCount[122] = nil --SPP + self.VM.OperandCount[123] = nil --CPP + self.VM.OperandCount[124] = nil --SRL + self.VM.OperandCount[125] = nil --GRL + self.VM.OperandCount[131] = nil --SMAP + self.VM.OperandCount[132] = nil --GMAP +end + +-------------------------------------------------------------------------------- +-- Reset state each GPU frame +-------------------------------------------------------------------------------- +function VM:Reset() + -- Reset VM + self.IP = 0 -- Instruction pointer + + self.EAX = 0 -- General purpose registers + self.EBX = 0 + self.ECX = 0 + self.EDX = 0 + self.ESI = 0 + self.EDI = 0 + self.ESP = 32767 + self.EBP = 0 + + self.CS = 0 -- Segment pointer registers + self.SS = 0 + self.DS = 0 + self.ES = 0 + self.GS = 0 + self.FS = 0 + self.KS = 0 + self.LS = 0 + + -- Extended registers + for reg=0,31 do self["R"..reg] = 0 end + + self.ESZ = 32768 -- Stack size register + self.CMPR = 0 -- Compare register + self.XEIP = 0 -- Current instruction address register + self.LADD = 0 -- Last interrupt parameter + self.LINT = 0 -- Last interrupt number + self.BPREC = 48 -- Binary precision for integer emulation mode (default: 48) + self.IPREC = 48 -- Integer precision (48 - floating point mode, 8, 16, 32, 64 - integer mode) + self.VMODE = 2 -- Vector mode (2D, 3D) + self.INTR = 0 -- Handling an interrupt + self.BlockStart = 0 -- Start of the block + self.BlockSize = 0 -- Size of the block + + self.EntryPoint0 = 0 + self.EntryPoint1 = 0 + + -- Reset internal SPU registers + -- Hardware control registers: + -- [65535] - CLK + -- [65534] - RESET + -- [65527] - Async thread frequency + + if self.Channel then + for k,v in pairs(self.Channel) do + v.Sound:Stop() + end + end + + self.Waveform = {} + self.Channel = {} + + self.Waveform[0] = WireSPU_GetSound("synth/square.wav") + self.Waveform[1] = WireSPU_GetSound("synth/saw.wav") + self.Waveform[2] = WireSPU_GetSound("synth/tri.wav") + self.Waveform[3] = WireSPU_GetSound("synth/sine.wav") + + for chan=0,3 do + self.Channel[chan] = { + Sound = CreateSound(self.Entity.SoundSources[chan],self.Waveform[chan]), + Volume = 1.0, + Pitch = 100, + ADSR = {x=0, y=0, z=1, w=0}, + ADSRStage = 0, + ADSRTime = 0, + ADSRMode = 1, + ADSRVolume = 0, + } + end +end + +WireSPU_SoundCache = {} +function WireSPU_GetSound(name) + if not WireSPU_SoundCache[name] then + WireSPU_SoundCache[name] = Sound(name) + end + return WireSPU_SoundCache[name] +end + + + +-------------------------------------------------------------------------------- +-- Read a string by offset +-------------------------------------------------------------------------------- +function VM:ReadString(address) + local charString = "" + local charCount = 0 + local currentChar = 255 + + while currentChar ~= 0 do + currentChar = self:ReadCell(address + charCount) + + if (currentChar > 0) and (currentChar < 255) then + charString = charString .. string.char(currentChar) + else + if currentChar ~= 0 then + self:Interrupt(23,currentChar) + return "" + end + end + + charCount = charCount + 1 + if charCount > 8192 then + self:Interrupt(23,0) + return "" + end + end + return charString +end + +-------------------------------------------------------------------------------- +-- Calculate ADSR Envelope +-------------------------------------------------------------------------------- + +function VM:CalculateADSR(deltaTime) + for _, chan in pairs(self.Channel) do + if chan.ADSRStage ~= 0 then + -- break up the ADSR envelope for easier reading + local curTime = deltaTime + chan.ADSRTime + chan.ADSRTime = curTime + local attackTime = chan.ADSR.x / 1000 + local decayTime = chan.ADSR.y / 1000 + local sustainVol = chan.ADSR.z + local releaseTime = chan.ADSR.w / 1000 + local relVolume = chan.ADSRVolume + local maxVolume = chan.Volume + local curVolume = chan.Sound:GetVolume() + + -- ADSR Stages: + -- 0: Idle + -- 1: Attack + -- 2: Decay + -- 3: Sustain + -- 4: Release + + -- Attack + if chan.ADSRStage == 1 then + if curTime >= attackTime then -- Move to Decay + chan.ADSRStage = 2 + curTime = curTime - attackTime + chan.ADSRTime = curTime + else + local mag = curTime/attackTime + local vol = maxVolume * mag + chan.Sound:ChangeVolume(vol) + chan.ADSRVolume = vol + end + end + -- Decay + if chan.ADSRStage == 2 then + if curTime >= decayTime then -- Move to Sustain + if chan.ADSRMode == 0 then + chan.ADSRStage = 4 --Mode 0, no Sustain + else + chan.ADSRStage = 3 --Mode 1, Sustain + end + curTime = curTime - decayTime + chan.ADSRTime = curTime + else + local mag = curTime / decayTime + local vol = (maxVolume - (maxVolume * mag)) + ((maxVolume * sustainVol) * mag) + chan.Sound:ChangeVolume(vol) + chan.ADSRVolume = vol + end + end + -- Sustain + if chan.ADSRStage == 3 then + if curVolume ~= (maxVolume * sustainVol) then + chan.Sound:ChangeVolume(maxVolume * sustainVol) + chan.ADSRVolume = maxVolume * sustainVol + end + end + -- Release + if chan.ADSRStage == 4 then + if (releaseTime ~= 0) and (curTime < releaseTime) then -- The only place we COULD get a divide by zero error! + local mag = curTime / releaseTime + local vol = (maxVolume * relVolume) - ((maxVolume * relVolume) * mag) + chan.Sound:ChangeVolume(vol) + --We don't set ADSRVolume here as ADSRVolume is used to calculate the release curve + elseif curVolume ~=0 then + chan.ADSRStage = 0 + chan.Sound:ChangeVolume(0) + end + end + end + end +end + +-------------------------------------------------------------------------------- +-- SPU instruction set implementation +-------------------------------------------------------------------------------- +VM.OpcodeTable = {} +VM.OpcodeTable[111] = function(self) --IDLE +-- self:Dyn_Emit("VM.INTR = 1") + self:Dyn_EmitBreak() + self.PrecompileBreak = true +end +-------------------------------------------------------------------------------- +VM.OpcodeTable[320] = function(self) --CHRESET + self:Dyn_Emit("$L CHAN = math.floor($1)") + + self:Dyn_Emit("if CHAN == -1 then") + self:Dyn_Emit("for channel=0,WireSPU_MaxChannels-1 do") + self:Dyn_Emit("if VM.Channel[channel] then") + self:Dyn_Emit("VM.Channel[channel].Sound:Stop()") + self:Dyn_Emit("VM.Channel[channel].Pitch = 100") + self:Dyn_Emit("VM.Channel[channel].Volume = 1.0") + self:Dyn_Emit("VM.Channel[channel].ADSR = {x=0, y=0, z=1, w=0}") + self:Dyn_Emit("VM.Channel[channel].ADSRStage = 0") + self:Dyn_Emit("VM.Channel[channel].ADSRTime = 0") + self:Dyn_Emit("VM.Channel[channel].ADSRMode = 1") + self:Dyn_Emit("VM.Channel[channel].ADSRVolume = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_Emit("else") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].Sound:Stop()") + self:Dyn_Emit("VM.Channel[CHAN].Pitch = 100") + self:Dyn_Emit("VM.Channel[CHAN].Volume = 1.0") + self:Dyn_Emit("VM.Channel[CHAN].ADSR = {x=0, y=0, z=1, w=0}") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRMode = 1") + self:Dyn_Emit("VM.Channel[CHAN].ADSRVolume = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[321] = function(self) --CHSTART + self:Dyn_Emit("$L CHAN = math.floor($1)") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].Sound:PlayEx(VM.Channel[CHAN].Volume,VM.Channel[CHAN].Pitch)") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 1") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRVolume = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[322] = function(self) --CHSTOP + self:Dyn_Emit("$L CHAN = math.floor($1)") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].Sound:Stop()") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRVolume = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[323] = function(self) --CHTRIGGER + self:Dyn_Emit("$L CHAN = math.floor($1)") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 1") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRVolume = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[324] = function(self) --CHRELEASE + self:Dyn_Emit("$L CHAN = math.floor($1)") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 4") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end + +-------------------------------------------------------------------------------- +VM.OpcodeTable[330] = function(self) --WSET + self:Dyn_Emit("$L WAVE = math.floor($1)") + self:Dyn_Emit("$L NAME = VM:ReadString($2)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if (WAVE >= 0) and (WAVE < 8192) then") + self:Dyn_Emit("VM.Waveform[WAVE] = WireSPU_GetSound(NAME)") + self:Dyn_Emit("end") +end +VM.OpcodeTable[331] = function(self) --CHWAVE + self:Dyn_Emit("$L CHAN = math.floor($1)") + self:Dyn_Emit("$L WAVE = math.floor($2)") + + self:Dyn_Emit("if (WAVE >= 0) and (WAVE < 8192) and (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Waveform[WAVE] then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("if VM.Channel[CHAN].Sound:IsPlaying() then") + self:Dyn_Emit("VM.Channel[CHAN].Sound:Stop()") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_Emit("VM.Channel[CHAN] = { Sound = CreateSound(VM.Entity.SoundSources[CHAN],VM.Waveform[WAVE]), Pitch = 100, Volume = 1.0 }") + self:Dyn_Emit("VM.Channel[CHAN].ADSR = {x=0, y=0, z=1, w=0}") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRMode = 1") + self:Dyn_Emit("VM.Channel[CHAN].ADSRVolume = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[332] = function(self) --CHLOOP + self:Dyn_Emit("$L CHAN = math.floor($1)") + self:Dyn_Emit("$L X = $2") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].ADSRMode = X") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[333] = function(self) --CHVOLUME + self:Dyn_Emit("$L CHAN = math.floor($1)") + self:Dyn_Emit("$L X = $2") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].Sound:ChangeVolume(math.Clamp(X,0,1),0)") + self:Dyn_Emit("VM.Channel[CHAN].Volume = math.Clamp(X,0,1)") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[334] = function(self) --CHPITCH + self:Dyn_Emit("$L CHAN = math.floor($1)") + self:Dyn_Emit("$L X = $2") + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].Sound:ChangePitch(math.Clamp(X*100,0,255),0)") + self:Dyn_Emit("VM.Channel[CHAN].Pitch = math.Clamp(X*100,0,255)") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end +VM.OpcodeTable[335] = function(self) --CHMODT +end +VM.OpcodeTable[336] = function(self) --CHMODA +end +VM.OpcodeTable[337] = function(self) --CHMODF +end +VM.OpcodeTable[338] = function(self) --CHADSR + self:Dyn_Emit("$L CHAN = math.floor($1)") + self:Dyn_Emit("$L VEC = VM:ReadVector4f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if (CHAN >= 0) and (CHAN < WireSPU_MaxChannels) then") + self:Dyn_Emit("if VM.Channel[CHAN] then") + self:Dyn_Emit("VM.Channel[CHAN].ADSR = VEC") + self:Dyn_Emit("VM.Channel[CHAN].ADSRStage = 0") + self:Dyn_Emit("VM.Channel[CHAN].ADSRTime = 0") + self:Dyn_Emit("end") + self:Dyn_Emit("end") +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/init.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/init.lua new file mode 100644 index 0000000..5ab2a7e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/init.lua @@ -0,0 +1,330 @@ +-- Load shared/clientside stuff +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("cl_spuvm.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +DEFINE_BASECLASS("base_wire_entity") + +ENT.WireDebugName = "ZSPU" + +-------------------------------------------------------------------------------- +WireSPU_MaxChannels = 32 + +-------------------------------------------------------------------------------- +function ENT:Initialize() + -- Physics properties + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + -- Inputs/outputs + self.Inputs = Wire_CreateInputs(self, { "Clk", "Reset", "IOBus", "SoundOut" }) + self.Outputs = Wire_CreateOutputs(self, { "Memory" }) + + -- Setup platform settings + self.Clk = 1 + self.MemBusScanAddress = 65536 + self.SerialNo = CPULib.GenerateSN("SPU") + self:SetMemoryModel("128k",true) + + -- Create serverside memory and cache + self.Memory = {} + self.Cache = GPUCacheManager(self) + + -- Connected sound emitters + self.SoundEmitters = {} + + -- Sound sources + self.SoundSources = {} + for i=0,WireSPU_MaxChannels-1 do + self.SoundSources[i] = ents.Create("prop_physics") + self.SoundSources[i]:SetParent(self) + self.SoundSources[i]:SetModel("models/cheeze/wires/nano_math.mdl") + self.SoundSources[i]:SetPos(self:GetPos()) + self.SoundSources[i]:Spawn() + self.SoundSources[i]:PhysicsDestroy() + end + + timer.Create("wire_spu_soundsources_"..math.floor(math.random()*1000000),0.1+math.random()*0.3,1, + function() + umsg.Start("wire_spu_soundsources") + umsg.Long(self:EntIndex()) + for i=0,WireSPU_MaxChannels-1 do + umsg.Long(self.SoundSources[i]:EntIndex()) + end + umsg.End() + +-- for i=0,WireSPU_MaxChannels-1 do +-- self.SoundSources[i]:SetModelScale(Vector(0)) +-- self.SoundSources[i]:SetNoDraw(true) +-- end + end) +end + +function ENT:OnRemove() + for i=0,WireSPU_MaxChannels-1 do + self.SoundSources[i]:Remove() + end +end + + +-------------------------------------------------------------------------------- +-- Set processor +-------------------------------------------------------------------------------- +function ENT:SetMemoryModel(model,initial) + if model then + for i=6,11 do + if model == (2^i).."k" then + self.RAMSize = (2^i)*1024 + self.ChipType = 0 + elseif model == (2^i).."kc" then + self.RAMSize = (2^i)*1024 + self.ChipType = 1 + end + end + end + + if not initial then + timer.Create("wire_spu_modelupdate_"..math.floor(math.random()*1000000),0.1+math.random()*0.3,1, + function() + umsg.Start("wire_spu_memorymodel") + umsg.Long(self:EntIndex()) + umsg.Long (self.RAMSize) + umsg.Float(self.SerialNo) + umsg.Short(self.ChipType) + umsg.End() + end) + end +end + + +-------------------------------------------------------------------------------- +-- Resend all SPU cache to newly spawned player +-------------------------------------------------------------------------------- +function ENT:ResendCache(player) + timer.Create("wire_spu_resendtimer_"..math.floor(math.random()*1000000),0.4+math.random()*1.2,1, + function() + self.Cache:Flush() + for address,value in pairs(self.Memory) do + self:WriteCell(address,value,player) + end + self.Cache:Flush(player) + + self:WriteCell(65534,1,player) -- Reset SPU + self:WriteCell(65535,self.Clk,player) -- Update Clk + end) +end + +local function SPU_PlayerRespawn(player) + for _,Entity in pairs(ents.FindByClass("gmod_wire_spu")) do + Entity:ResendCache(player) + end +end +hook.Add("PlayerInitialSpawn", "SPUPlayerRespawn", SPU_PlayerRespawn) +concommand.Add("wire_spu_resendcache", SPU_PlayerRespawn) + + +-------------------------------------------------------------------------------- +-- Read cell from SPU memory +-------------------------------------------------------------------------------- +function ENT:ReadCell(Address) + Address = math.floor(Address) + if (Address < 0) or (Address >= self.RAMSize) then + return nil + else + if self.Memory[Address] then + return self.Memory[Address] + else + return 0 + end + end +end + + +-------------------------------------------------------------------------------- +-- Write cell to SPU memory +-------------------------------------------------------------------------------- +function ENT:WriteCell(Address, Value, Player) + Address = math.floor(Address) + if (Address < 0) or (Address >= self.RAMSize) then + return false + else + if (Address ~= 65535) and (Address ~= 65534) then + -- Write to internal memory + self.Memory[Address] = Value + + -- Add address to cache if cache is not big enough yet + self.Cache:Write(Address,Value,Player) + return true + else + self.Cache:Flush(Player) + self.Cache:WriteNow(Address,Value,Player) + end + return true + end +end + + +-------------------------------------------------------------------------------- +-- Write advanced dupe +-------------------------------------------------------------------------------- +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + info.SerialNo = self.SerialNo + info.RAMSize = self.RAMSize + info.ChipType = self.ChipType + info.Memory = {} + + for address = 0,self.RAMSize-1 do + if self.Memory[address] and (self.Memory[address] ~= 0) then info.Memory[address] = self.Memory[address] end + end + + return info +end + + +-------------------------------------------------------------------------------- +-- Read from advanced dupe +-------------------------------------------------------------------------------- +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.SerialNo = info.SerialNo or 999999 + self.RAMSize = info.RAMSize or 65536 + self.ChipType = info.ChipType or 0 + self.Memory = {} + + for address = 0,self.RAMSize-1 do + if info.Memory[address] then self.Memory[address] = tonumber(info.Memory[address]) or 0 end + end + + self:SetMemoryModel() + self:ResendCache(nil) +end + + +-------------------------------------------------------------------------------- +-- Handle external input +-------------------------------------------------------------------------------- +function ENT:TriggerInput(iname, value) + if iname == "Clk" then + self.Clk = (value >= 1 and 1 or 0) + self:WriteCell(65535,self.Clk) + elseif iname == "Reset" then + if value >= 1.0 then self:WriteCell(65534,1) end + end +end + + +-------------------------------------------------------------------------------- +-- Find out all sound emitters connected to the SPU +-------------------------------------------------------------------------------- +function ENT:QuerySoundEmitters(entity) + self.QueryRecurseCounter = self.QueryRecurseCounter + 1 + if self.QueryRecurseCounter > 128 then return end + if (not entity) or (not entity:IsValid()) then return end + + if entity:GetClass() == "gmod_wire_spu" then -- VideoOut connected to a GPU + table.insert(self.QueryResult,entity:EntIndex()) + elseif entity.Socket then -- VideoOut connected to a plug + self:QuerySoundEmitters(entity.Socket.Inputs.Memory.Src) + elseif entity.Plug then -- VideoOut connected to a socket + self:QuerySoundEmitters(entity.Plug.Inputs.Memory.Src) + elseif entity.Ply and entity.Ply:IsValid() then -- VideoOut connected to pod + table.insert(self.QueryResult,entity.Ply:EntIndex()) + elseif entity:GetClass() == "gmod_wire_addressbus" then -- VideoOut connected to address bus + self:QuerySoundEmitters(entity.Inputs.Memory1.Src) + self:QuerySoundEmitters(entity.Inputs.Memory2.Src) + self:QuerySoundEmitters(entity.Inputs.Memory3.Src) + self:QuerySoundEmitters(entity.Inputs.Memory4.Src) + elseif entity:GetClass() == "gmod_wire_extbus" then -- VideoOut connected to ext bus + self:QuerySoundEmitters(entity.Inputs.Memory1.Src) + self:QuerySoundEmitters(entity.Inputs.Memory2.Src) + self:QuerySoundEmitters(entity.Inputs.Memory3.Src) + self:QuerySoundEmitters(entity.Inputs.Memory4.Src) + self:QuerySoundEmitters(entity.Inputs.Memory5.Src) + self:QuerySoundEmitters(entity.Inputs.Memory6.Src) + self:QuerySoundEmitters(entity.Inputs.Memory7.Src) + self:QuerySoundEmitters(entity.Inputs.Memory8.Src) + end +end + + +-------------------------------------------------------------------------------- +-- Update cache and external connections +-------------------------------------------------------------------------------- +function ENT:Think() + -- Update IOBus + if self.Inputs.IOBus.Src then + -- Was there any update in that that would require flushing + local DataUpdated = false + + -- Update any cells that must be updated + for port = 0,1023 do + if self.Inputs.IOBus.Src.ReadCell then + local var = self.Inputs.IOBus.Src:ReadCell(port) + if var then + if self:ReadCell(port+63488) ~= var then + self:WriteCell(port+63488,var) + DataUpdated = true + end + end + end + end + + -- Flush updated data + if DataUpdated then self.Cache:Flush() end + end + + -- Flush any data in cache + self.Cache:Flush() + + -- Update video output, and send any changes to client + if self.Inputs.SoundOut.Src then + self.QueryRecurseCounter = 0 + self.QueryResult = { } + self:QuerySoundEmitters(self.Inputs.SoundOut.Src) + + -- Check if sound emitters setup has changed + local soundEmittersChanged = false + for k,v in pairs(self.QueryResult) do + if self.SoundEmitters[k] ~= v then + soundEmittersChanged = true + break + end + end + + if not soundEmittersChanged then + for k,v in pairs(self.SoundEmitters) do + if self.QueryResult[k] ~= v then + soundEmittersChanged = true + break + end + end + end + + if #self.QueryResult ~= #self.SoundEmitters then soundEmittersChanged = true end + + if soundEmittersChanged then + self.SoundEmitters = self.QueryResult + end + + -- Send update to all clients + if soundEmittersChanged then + umsg.Start("wire_spu_soundstate") + umsg.Long(self:EntIndex()) + umsg.Short(#self.SoundEmitters) + for idx=1,#self.SoundEmitters do + umsg.Long(self.SoundEmitters[idx]) + end + umsg.End() + end + end + + self:NextThink(CurTime()+0.05) + return true +end + +duplicator.RegisterEntityClass("gmod_wire_spu", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/shared.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/shared.lua new file mode 100644 index 0000000..a85189a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_spu/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_wire_entity" + +ENT.PrintName = "Wire ZSPU" +ENT.Author = "Black Phoenix" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_target_finder.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_target_finder.lua new file mode 100644 index 0000000..c415268 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_target_finder.lua @@ -0,0 +1,451 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Target Finder" +ENT.WireDebugName = "Target Finder" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs(self, { "Hold" }) + self.Outputs = WireLib.CreateSpecialOutputs( self, { "Out" }, { "ENTITY" } ) +end + +function ENT:Setup(maxrange, players, npcs, npcname, beacons, hoverballs, thrusters, props, propmodel, vehicles, playername, casesen, rpgs, painttarget, minrange, maxtargets, maxbogeys, notargetowner, entity, notownersstuff, steamname, colorcheck, colortarget, pcolR, pcolG, pcolB, pcolA, checkbuddylist, onbuddylist ) + local ttable = { -- For dupe support + range = maxrange, + players = players, + npcs = npcs, + npcname = npcname, + beacons = beacons, + hoverballs = hoverballs, + thrusters = thrusters, + props = props, + propmodel = propmodel, + vehicles = vehicles, + playername = playername, + steamname = steamname, + colorcheck = colorcheck, + colortarget = colortarget, + pcolR = pcolR, + pcolG = pcolG, + pcolB = pcolB, + pcolA = pcolA, + casesen = casesen, + rpgs = rpgs, + painttarget = painttarget, + minrange = minrange, + maxtargets = maxtargets, + maxbogeys = maxbogeys, + notargetowner = notargetowner, + notownersstuff = notownersstuff, + checkbuddylist = checkbuddylist, + onbuddylist = onbuddylist, + entity = entity, + } + table.Merge( self:GetTable(), ttable ) + + self.MaxRange = maxrange + self.MinRange = minrange or 1 + self.TargetPlayer = players + self.NoTargetOwner = notargetowner + self.NoTargetOwnersStuff = notownersstuff + self.TargetNPC = npcs + self.NPCName = npcname + self.TargetBeacon = beacons + self.TargetHoverballs = hoverballs + self.TargetThrusters = thrusters + self.TargetProps = props + self.PropModel = propmodel + self.TargetVehicles = vehicles + self.PlayerName = playername + self.SteamName = steamname + self.ColorCheck = colorcheck + self.ColorTarget = colortarget + self.PcolR = pcolR + self.PcolG = pcolG + self.PcolB = pcolB + self.PcolA = pcolA + self.CaseSen = casesen + self.TargetRPGs = rpgs + self.EntFil = entity + self.CheckBuddyList = checkbuddylist + self.OnBuddyList = onbuddylist + self.PaintTarget = painttarget + self.MaxTargets = math.floor(math.Clamp((maxtargets or 1), 1, GetConVarNumber("wire_target_finders_maxtargets", 10))) + self.MaxBogeys = math.floor(math.Clamp((maxbogeys or 1), self.MaxTargets, GetConVarNumber("wire_target_finders_maxbogeys", 30))) + + if (self.SelectedTargets) then --unpaint before clearing + for _,ent in pairs(self.SelectedTargets) do + self:TargetPainter(ent, false) + end + end + self.SelectedTargets = {} + self.SelectedTargetsSel = {} + + local AdjOutputs = {} + local AdjOutputsT = {} + for i = 1, self.MaxTargets do + table.insert(AdjOutputs, tostring(i)) + table.insert(AdjOutputsT, "NORMAL") + table.insert(AdjOutputs, tostring(i).."_Ent") + table.insert(AdjOutputsT, "ENTITY") + end + WireLib.AdjustSpecialOutputs(self, AdjOutputs, AdjOutputsT) + + + self.Selector = {} + self.Selector.Next = {} + self.Selector.Prev = {} + self.Selector.Hold = {} + local AdjInputs = {} + for i = 1, self.MaxTargets do + local inputnext = tostring(i).."-NextTarget" + --local inputprev = tostring(i).."-PrevTarget" + local inputhold = tostring(i).."-HoldTarget" + self.Selector.Next[inputnext] = i + --self.Selector.Prev[inputprev] = i + --self.Selector.Hold[inputhold] = i + table.insert(AdjInputs, inputnext) + --table.insert(AdjInputs, inputprev) + table.insert(AdjInputs, inputhold) + end + table.insert(AdjInputs, "Hold") + Wire_AdjustInputs(self, AdjInputs) + + self:ShowOutput(false) +end + +function ENT:TriggerInput(iname, value) + if (value > 0) then + if self.Selector.Next[iname] then + self:SelectorNext(self.Selector.Next[iname]) + --[[elseif self.Selector.Prev[iname] then + self:SelectorPrev(self.Selector.Prev[iname])]] + --[[elseif self.Selector.Hold[iname] then + self:SelectorHold(self.Selector.Hold[iname])]] + end + end +end + + +function ENT:GetBeaconPos(sensor) + local ch = 1 + if (sensor.Inputs) and (sensor.Inputs.Target.SrcId) then + ch = tonumber(sensor.Inputs.Target.SrcId) + end + if self.SelectedTargets[ch] then + if (not self.SelectedTargets[ch]:IsValid()) then + self.SelectedTargets[ch] = nil + Wire_TriggerOutput(self, tostring(ch), 0) + return sensor:GetPos() + end + + return self.SelectedTargets[ch]:GetPos() + end + + return sensor:GetPos() +end + +function ENT:GetBeaconVelocity(sensor) + local ch = 1 + if (sensor.Inputs) and (sensor.Inputs.Target.SrcId) then + ch = tonumber(sensor.Inputs.Target.SrcId) + end + if self.SelectedTargets[ch] then + if (not self.SelectedTargets[ch]:IsValid()) then + self.SelectedTargets[ch] = nil + Wire_TriggerOutput(self, tostring(ch), 0) + return sensor:GetVelocity() + end + return self.SelectedTargets[ch]:GetVelocity() + end + return sensor:GetVelocity() +end + + +function ENT:SelectorNext(ch) + if (self.Bogeys) and (#self.Bogeys > 0) then + if (!self.SelectedTargetsSel[ch]) then self.SelectedTargetsSel[ch] = 1 end + + local sel = self.SelectedTargetsSel[ch] + if (sel > #self.Bogeys) then sel = 1 end + + if (self.SelectedTargets[ch]) and (self.SelectedTargets[ch]:IsValid()) then + + if (self.PaintTarget) then self:TargetPainter(self.SelectedTargets[ch], false) end + table.insert(self.Bogeys, self.SelectedTargets[ch]) --put old target back + self.SelectedTargets[ch] = table.remove(self.Bogeys, sel) --pull next target + if (self.PaintTarget) then self:TargetPainter(self.SelectedTargets[ch], true) end + + else + + self.SelectedTargets[ch] = table.remove(self.Bogeys, sel) --pull next target + if (self.PaintTarget) then self:TargetPainter(self.SelectedTargets[ch], true) end + + end + + self.SelectedTargetsSel[ch] = sel + 1 + self.Inputs[ch.."-HoldTarget"].Value = 1 --put the channel on hold so it wont change in the next scan + Wire_TriggerOutput(self, tostring(ch), 1) + Wire_TriggerOutput(self, tostring(ch).."_Ent", self.SelectedTargets[ch]) + end +end + +--function ENT:SelectorPrev(ch) end --TODO if needed + +function ENT:FindInValue(haystack,needle,case_sensitive) + if !isstring(haystack) or !isstring(needle) then return false end; + if(needle == "") then return true end; + if(case_sensitive) then + if(haystack:find(needle)) then return true end; + else + if(haystack:lower():find(needle:lower())) then return true end; + end + return false +end + +function ENT:FindColor(contact) + if (not self.ColorCheck) then return true end + local col = contact:GetColor() + if (col.r == self.PcolR) and (col.g == self.PcolG) and (col.b == self.PcolB) and (col.a == self.PcolA) then + return self.ColorTarget + else + return !self.ColorTarget + end +end + +function ENT:CheckTheBuddyList(friend) + if not self.CheckBuddyList or not CPPI then return true end + if not IsValid(self:GetPlayer()) then return false end + + for _, v in pairs(self:GetPlayer():CPPIGetFriends()) do + if v == friend then return self.OnBuddyList end + end + return not self.OnBuddyList +end + +function ENT:Think() + BaseClass.Think(self) + + if not (self.Inputs.Hold and self.Inputs.Hold.Value > 0) then + -- Find targets that meet requirements + local mypos = self:GetPos() + local bogeys,dists = {},{} + for _,contact in pairs(ents.FindInSphere(mypos, self.MaxRange or 10)) do + local class = contact:GetClass() + if (not self.NoTargetOwnersStuff or (class == "player") or (WireLib.GetOwner(contact) ~= self:GetPlayer())) and ( + -- NPCs + ((self.TargetNPC) and (contact:IsNPC()) and (self:FindInValue(class,self.NPCName))) or + --Players + ((self.TargetPlayer) and (class == "player") and (!self.NoTargetOwner or self:GetPlayer() != contact) and self:FindInValue(contact:GetName(),self.PlayerName,self.CaseSen) and self:FindInValue(contact:SteamID(),self.SteamName) and self:FindColor(contact) and self:CheckTheBuddyList(contact)) or + --Locators + ((self.TargetBeacon) and (class == "gmod_wire_locator")) or + --RPGs + ((self.TargetRPGs) and (class == "rpg_missile")) or + -- Hoverballs + ((self.TargetHoverballs) and (class == "gmod_hoverball" or class == "gmod_wire_hoverball")) or + -- Thruster + ((self.TargetThrusters) and (class == "gmod_thruster" or class == "gmod_wire_thruster" or class == "gmod_wire_vectorthruster")) or + -- Props + ((self.TargetProps) and (class == "prop_physics") and (self:FindInValue(contact:GetModel(),self.PropModel))) or + -- Vehicles + ((self.TargetVehicles) and (contact:IsVehicle())) or + -- Entity classnames + (self.EntFil ~= "" and self:FindInValue(class,self.EntFil))) + then + local dist = (contact:GetPos() - mypos):Length() + if (dist >= self.MinRange) then + -- put targets in a table index by the distance from the finder + bogeys[dist] = contact + dists[#dists+1] = dist + end + end + end + + -- sort the list of bogeys by key (distance) + self.Bogeys = {} + self.InRange = {} + table.sort(dists) + local k = 1 + for i,d in pairs(dists) do + if !self:IsTargeted(bogeys[d], i) then + self.Bogeys[k] = bogeys[d] + k = k + 1 + if (k > self.MaxBogeys) then break end + end + end + + + -- check that the selected targets are valid + for i = 1, self.MaxTargets do + if (self:IsOnHold(i)) then + self.InRange[i] = true + end + + if (!self.InRange[i]) or (!self.SelectedTargets[i]) or (self.SelectedTargets[i] == nil) or (!self.SelectedTargets[i]:IsValid()) then + if (self.PaintTarget) then self:TargetPainter(self.SelectedTargets[i], false) end + if (#self.Bogeys > 0) then + self.SelectedTargets[i] = table.remove(self.Bogeys, 1) + if (self.PaintTarget) then self:TargetPainter(self.SelectedTargets[i], true) end + Wire_TriggerOutput(self, tostring(i), 1) + Wire_TriggerOutput(self, tostring(i).."_Ent", self.SelectedTargets[i]) + else + self.SelectedTargets[i] = nil + Wire_TriggerOutput(self, tostring(i), 0) + Wire_TriggerOutput(self, tostring(i).."_Ent", NULL) + end + end + end + + end + + -- temp hack + if self.SelectedTargets[1] then + self:ShowOutput(true) + else + self:ShowOutput(false) + end + self:NextThink(CurTime() + 1) + return true +end + +function ENT:IsTargeted(bogey, bogeynum) + for i = 1, self.MaxTargets do + if (self.SelectedTargets[i]) and (self.SelectedTargets[i] == bogey) then + --hold this target + if (self.Inputs[i.."-HoldTarget"]) and (self.Inputs[i.."-HoldTarget"].Value > 0) then + self.InRange[i] = true + return true + end + + --this bogey is not as close as others, untarget it and let it be add back to the list + if (bogeynum > self.MaxTargets) then + self.SelectedTargets[i] = nil + if (self.PaintTarget) then self:TargetPainter(bogey, false) end + return false + end + + self.InRange[i] = true + return true + end + end + return false +end + +function ENT:IsOnHold(ch) + if (self.Inputs[ch.."-HoldTarget"]) and (self.Inputs[ch.."-HoldTarget"].Value > 0) then + return true + end + return false +end + + +function ENT:OnRemove() + BaseClass.OnRemove(self) + + --unpaint all our targets + if (self.PaintTarget) then + for _,ent in pairs(self.SelectedTargets) do + self:TargetPainter(ent, false) + end + end +end + +function ENT:OnRestore() + BaseClass.OnRestore(self) + + self.MaxTargets = self.MaxTargets or 1 +end + +function ENT:TargetPainter( tt, targeted ) + if tt and IsValid(tt) and tt:EntIndex() ~= 0 and hook.Run( "CanTool", self:GetPlayer(), WireLib.dummytrace(tt), "colour" ) then + if (targeted) then + self.OldColor = tt:GetColor() + tt:SetColor(Color(255, 0, 0, 255)) + else + if not self.OldColor then self.OldColor = Color(255,255,255,255) end + + local c = tt:GetColor() + + -- do not change color back if the target color changed in the meantime + if c.r != 255 or c.g != 0 or c.b != 0 or c.a != 255 then + self.OldColor = c + end + + tt:SetColor(self.OldColor) + end + end +end + + +function ENT:ShowOutput(value) + local txt + if (value) then + txt = "Target Acquired" + else + txt = "No Target" + end + + if (self.Inputs.Hold) and (self.Inputs.Hold.Value > 0) then txt = txt .. " - Locked" end + + self:SetOverlayText(txt) +end + + + +-- +-- PropProtection support +-- +-- Uses code from uclip for checking ownership +-- +-- Written by Team Ulysses, http://ulyssesmod.net/ +local hasPropProtection = false -- Chaussette's Prop Protection (preferred over PropSecure) +local propProtectionFn -- Function to call to see if a prop belongs to a player. We have to fetch it from a local so we'll store it here. + +local hasPropSecure = false -- Prop Secure by Conna +local hasProtector = false -- Protector by Conna + +local noProtection = false -- If there's no protection whatsoever, this is flagged. +-- We need this flag because with a protector, we default to _not_ being able to go through things. +-- This flag saves us major memory/bandwidth when there's no protection + +-- We'll check status of protectors in this init +local function init() + local t = hook.GetTable() + local fn + if(t.CanTool) then + if t.CanTool[0] then -- ULib + fn = t.CanTool[0].PropProtection + else + fn = t.CanTool.PropProtection + end + end + + hasPropProtection = isfunction( fn ) + + if hasPropProtection then + -- We're going to get the function we need now. It's local so this is a bit dirty + local gi = debug.getinfo( fn ) + for i=1,gi.nups do + if debug.getupvalue( fn, i ) == "Appartient" then + local junk + junk, propProtectionFn = debug.getupvalue( fn, i ) + break + end + end + end + + hasPropSecure = istable( PropSecure ) + hasProtector = istable( Protector ) + + if not hasPropProtection and not hasPropSecure and not hasProtector then + noProtection = true + end +end +hook.Add( "Initialize", "WireTargetFinderInitialize", init ) + +duplicator.RegisterEntityClass("gmod_wire_target_finder", WireLib.MakeWireEnt, "Data", "range", "players", "npcs", "npcname", "beacons", "hoverballs", "thrusters", "props", "propmodel", "vehicles", "playername", "casesen", "rpgs", "painttarget", "minrange", "maxtargets", "maxbogeys", "notargetowner", "entity", "notownersstuff", "steamname", "colorcheck", "colortarget", "pcolR", "pcolG", "pcolB", "pcolA", "checkbuddylist", "onbuddylist") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_teleporter.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_teleporter.lua new file mode 100644 index 0000000..fe5f94f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_teleporter.lua @@ -0,0 +1,329 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Teleporter" +ENT.WireDebugName = "Teleporter" +ENT.Author = "Divran" + +if CLIENT then return end -- No more client + +CreateConVar("wire_teleporter_cooldown","1",{FCVAR_ARCHIVE,FCVAR_NOTIFY}) + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Jumping = false + self.TargetPos = self:GetPos() + self.TargetAng = self:GetAngles() + self.Entities = {} + self.LocalPos = {} + self.LocalAng = {} + self.LocalVel = {} + self.UseSounds = true + self.UseEffects = true + + self.ClassSpecificActions = { + gmod_wire_hoverball = function( ent, oldpos, newpos ) ent:SetZTarget( newpos.z ) end, + gmod_toggleablehoverball = function( ent, oldpos, newpos ) ent:SetTargetZ( newpos.z ) end, + gmod_hoverball = function( ent, oldpos, newpos ) ent.dt.TargetZ = newpos.z end, + } + + self:ShowOutput() + + self.Inputs = Wire_CreateInputs( self, { "Jump", "TargetPos [VECTOR]", "X", "Y", "Z", "TargetAngle [ANGLE]", "Sound" }) +end + +function ENT:TriggerInput(iname, value) + if (iname == "Jump") then + if (value ~= 0 and not self.Jumping) then + self:Jump(self.UseAngle or self.Inputs.TargetAngle.Src ~= nil) + self.UseAngle = false + end + elseif (iname == "TargetPos") then + self.TargetPos = value + elseif (iname == "X") then + self.TargetPos.x = value + elseif (iname == "Y") then + self.TargetPos.y = value + elseif (iname == "Z") then + self.TargetPos.z = value + elseif (iname == "TargetAngle") then + self.TargetAng = value + -- if the angle is set, we should use it for jumping + -- even if there's nothing connected to the angle wire. + -- otherwise, we can't use wirelink for angles. + self.UseAngle = true + elseif (iname == "Sound") then + self.UseSounds = value ~= 0 + end + self:ShowOutput() +end + +function ENT:ShowOutput() + self:SetOverlayText( "Target Position = " .. tostring(self.TargetPos) .. "\nTarget Angle = " .. tostring(self.TargetAng) .. "\nSounds = " .. (self.UseSounds and "Yes" or "No") .. "\nEffects = " .. (self.UseEffects and "Yes" or "No") ) +end + +function ENT:Jump( withangles ) + -------------------------------------------------------------------- + -- Check for errors + -------------------------------------------------------------------- + + -- Is already teleporting + if (self.Jumping) then + return + end + + -- The target position is outside the world + if (!util.IsInWorld( self.TargetPos )) then + self:EmitSound("buttons/button8.wav") + return + end + + -- The position or angle hasn't changed + if (self:GetPos() == self.TargetPos and self:GetAngles() == self.TargetAng) then + self:EmitSound("buttons/button8.wav") + return + end + + + + -------------------------------------------------------------------- + -- Find other entities + -------------------------------------------------------------------- + + -- Get the localized positions + local ents = constraint.GetAllConstrainedEntities( self ) + + -- If the teleporter is parented, and not constrained, then get the contraption of the parent instead + local val = next(ents) -- the first value of GetAllConstrainedEntities is always 'self', so we skip this value and check the next + if next(ents,val) == nil and IsValid(self:GetParent()) then + ents = constraint.GetAllConstrainedEntities( self:GetParent() ) + end + + -- Check world + self.Entities = {} + self.OtherEntities = {} + for _, ent in pairs( ents ) do + -- Calculate the position after teleport, without actually moving the entity + local pos = self:WorldToLocal( ent:GetPos() ) + pos:Rotate( self.TargetAng ) + pos = pos + self.TargetPos + + local b = util.IsInWorld( pos ) + if not b then -- If an entity will be outside the world after teleporting.. + self:EmitSound("buttons/button8.wav") + return + elseif ent ~= self then -- if the entity is not equal to self + if self:CheckAllowed( ent ) then -- If the entity can be teleported + self.Entities[#self.Entities+1] = ent + else -- If the entity can't be teleported + self.OtherEntities[#self.OtherEntities+1] = ent + end + end + end + + -- All error checking passed + self.Jumping = true + + -------------------------------------------------------------------- + -- Sound and visual effects + -------------------------------------------------------------------- + if self.UseSounds then self:EmitSound("ambient/levels/citadel/weapon_disintegrate2.wav") end -- Starting sound + + if self.UseEffects then + -- Effect out + local effectdata = EffectData() + effectdata:SetEntity( self ) + local Dir = (self.TargetPos - self:GetPos()) + Dir:Normalize() + effectdata:SetOrigin( self:GetPos() + Dir * math.Clamp( self:BoundingRadius() * 5, 180, 4092 ) ) + util.Effect( "jump_out", effectdata, true, true ) + + DoPropSpawnedEffect( self ) + + for _, ent in pairs( ents ) do + -- Effect out + local effectdata = EffectData() + effectdata:SetEntity( ent ) + effectdata:SetOrigin( self:GetPos() + Dir * math.Clamp( ent:BoundingRadius() * 5, 180, 4092 ) ) + util.Effect( "jump_out", effectdata, true, true ) + end + end + + -- Call the next stage after a short time. This small delay is necessary for sounds and effects to work properly. + timer.Simple( 0.05, function() self:Jump_Part2( withangles ) end ) +end + +function ENT:Jump_Part2( withangles ) + local OldPos = self:GetPos() + + -------------------------------------------------------------------- + -- Other entities + -------------------------------------------------------------------- + + -- Save local positions, angles, and velocity + self.LocalPos = {} + self.LocalAng = {} + self.LocalVel = {} + for _, ent in pairs( self.Entities ) do + if (ent:GetPhysicsObjectCount() > 1) then -- Check for bones + local tbl = { Main = self:WorldToLocal( ent:GetPos() ) } + local tbl2 = { Main = self:WorldToLocal( ent:GetVelocity() + ent:GetPos() ) } + + for i=0, ent:GetPhysicsObjectCount()-1 do + local b = ent:GetPhysicsObjectNum( i ) + tbl[i] = ent:WorldToLocal( b:GetPos() ) + + tbl2[i] = ent:WorldToLocal( ent:GetPos() + b:GetVelocity() ) + b:SetVelocity( b:GetVelocity() * -1 ) + end + + -- Save the localized position table + self.LocalPos[ent] = tbl + + -- Save the localized velocity table + self.LocalVel[ent] = tbl2 + else + -- Save the localized position + self.LocalPos[ent] = self:WorldToLocal( ent:GetPos() ) + + -- Save the localized velocity + self.LocalVel[ent] = self:WorldToLocal( ent:GetVelocity() + ent:GetPos() ) + end + + ent:SetVelocity( ent:GetVelocity() * -1 ) + + if withangles then + self.LocalAng[ent] = self:WorldToLocalAngles( ent:GetAngles() ) + end + end + + -------------------------------------------------------------------- + -- The teleporter itself + -------------------------------------------------------------------- + + -- Save old parent and then unparent the teleporter (is restored after teleporting) + -- This prevents an issue that deletes the entire contraption + local parent = self:GetParent() + self:SetParent() + + local oldvel = self:WorldToLocal( self:GetVelocity() + self:GetPos() ) -- Velocity + self:SetPos( self.TargetPos ) -- Position + if withangles then self:SetAngles( self.TargetAng ) end -- Angle + self:GetPhysicsObject():SetVelocity( self:LocalToWorld( oldvel ) - self:GetPos() ) -- Set new velocity + + if self.UseSounds then self:EmitSound("npc/turret_floor/die.wav", 450, 70) end -- Sound + + local Dir = (OldPos - self:GetPos()):GetNormalized() + if self.UseEffects then + -- Effect + effectdata = EffectData() + effectdata:SetEntity( self ) + effectdata:SetOrigin( self:GetPos() + Dir * math.Clamp( self:BoundingRadius() * 5, 180, 4092 ) ) + util.Effect( "jump_in", effectdata, true, true ) + end + + -------------------------------------------------------------------- + -- Other entities + -------------------------------------------------------------------- + + for _, ent in pairs( self.Entities ) do + + local oldPos = ent:GetPos() -- Remember old position + + if withangles then ent:SetAngles( self:LocalToWorldAngles( self.LocalAng[ent] ) ) end -- Angles + + if (ent:GetPhysicsObjectCount() > 1) then -- Check for bones + ent:SetPos( self:LocalToWorld( self.LocalPos[ent].Main ) ) -- Position + + -- Set new velocity + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:SetVelocity( self:LocalToWorld( self.LocalVel[ent].Main ) - ent:GetPos() ) + else + ent:SetVelocity( self:LocalToWorld( self.LocalVel[ent].Main ) ) + end + + for i=0, ent:GetPhysicsObjectCount()-1 do -- For each bone... + local b = ent:GetPhysicsObjectNum( i ) + + b:SetPos( ent:LocalToWorld(self.LocalPos[ent][i]) ) -- Position + b:SetVelocity( ent:LocalToWorld( self.LocalVel[ent][i] ) - ent:GetPos() ) -- Set new velocity + end + + ent:GetPhysicsObject():Wake() + else -- If it doesn't have bones + ent:SetPos( self:LocalToWorld(self.LocalPos[ent]) ) -- Position + + -- Set new velocity + local phys = ent:GetPhysicsObject() + if phys:IsValid() then + phys:SetVelocity( self:LocalToWorld( self.LocalVel[ent] ) - ent:GetPos() ) + else + ent:SetVelocity( self:LocalToWorld( self.LocalVel[ent] ) ) + end + + ent:GetPhysicsObject():Wake() + end + + if self.UseEffects then + -- Effect in + effectdata = EffectData() + effectdata:SetEntity( ent ) + effectdata:SetOrigin( self:GetPos() + Dir * math.Clamp( ent:BoundingRadius() * 5, 180, 4092 ) ) + util.Effect( "jump_in", effectdata, true, true ) + DoPropSpawnedEffect( ent ) + end + + + if self.ClassSpecificActions[ent:GetClass()] then -- Call function specific for this entity class + self.ClassSpecificActions[ent:GetClass()]( ent, oldPos, ent:GetPos() ) + end + end + + if self.UseEffects then + for _, ent in pairs( self.OtherEntities ) do -- Render the effect on all other entities in the contraption + -- Effect in + effectdata = EffectData() + effectdata:SetEntity( ent ) + effectdata:SetOrigin( self:GetPos() + Dir * math.Clamp( ent:BoundingRadius() * 5, 180, 4092 ) ) + util.Effect( "jump_in", effectdata, true, true ) + DoPropSpawnedEffect( ent ) + end + end + + self:SetParent( parent ) -- restore parent + + -- Cooldown - prevent teleporting for a time + timer.Create( + "teleporter_"..self:EntIndex(), -- name + GetConVarNumber( "wire_teleporter_cooldown" ), -- delay + 1, -- nr of runs + function() -- function + if self:IsValid() then + self.Jumping = false + end + end + ) +end + +function ENT:CheckAllowed( e ) + if (e:GetParent():EntIndex() != 0) then return false end + + -- These shouldn't happen, ever, but they're here just to be safe + local c = e:GetClass() + if c == "Player" or c:find("npc_") then return false end + + return true +end + +function ENT:Setup(UseSounds, UseEffects) + self.UseSounds = UseSounds + self.UseEffects = UseEffects + self:ShowOutput() +end + +duplicator.RegisterEntityClass("gmod_wire_hoverdrivecontroler", WireLib.MakeWireEnt, "Data", "UseSounds", "UseEffects" ) +duplicator.RegisterEntityClass("gmod_wire_teleporter", WireLib.MakeWireEnt, "Data", "UseSounds", "UseEffects") +scripted_ents.Alias("gmod_wire_hoverdrivecontroler", "gmod_wire_teleporter") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textentry.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textentry.lua new file mode 100644 index 0000000..9e8a356 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textentry.lua @@ -0,0 +1,273 @@ +-- Author: mitterdoo (with help from Divran) + +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Text Entry (Wire)" +ENT.WireDebugName = "Text Entry" + + +function ENT:SetupDataTables() + self:NetworkVar("Float",0,"Hold") + self:NetworkVar("Bool",0,"DisableUse") +end + +if CLIENT then + local panel + + ---------------------------------------------------- + -- Show the prompt + ---------------------------------------------------- + net.Receive("wire_textentry_show",function() + local self=net.ReadEntity() + if !IsValid(self) then return end + panel = Derma_StringRequest( + "Wire Text Entry", + "Enter text below", + "", + function(text) + net.Start("wire_textentry_action") + net.WriteEntity(self) + net.WriteBool(true) + net.WriteString(text) + net.SendToServer() + end, + function() + net.Start("wire_textentry_action") + net.WriteEntity(self) + net.WriteBool(false) + net.WriteString("") + net.SendToServer() + end, + "Enter","Cancel" + ) + end) + + net.Receive( "wire_textentry_kick", function() + if IsValid( panel ) then + panel:Remove() + end + end) + return +end + +---------------------------------------------------- +-- UpdateOverlay +---------------------------------------------------- +function ENT:UpdateOverlay() + local hold = math.Round(math.max(self:GetHold(),0),1) + local txt = "Hold Length: " .. (hold > 0 and hold or "Forever") + + if self.BlockInput then + txt = txt.."\nBlocking Input" + elseif IsValid(self.User) then + txt = txt.."\nIn use by: " .. self.User:Nick() + end + + if self:GetDisableUse() then + txt = txt .. "\nUse disabled" + end + + self:SetOverlayText(txt) +end + +---------------------------------------------------- +-- Initialize +---------------------------------------------------- +function ENT:Initialize() + self:PhysicsInit(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.Inputs=WireLib.CreateInputs(self,{"Block Input","Prompt"}) + self.Outputs=WireLib.CreateOutputs(self,{"In Use","Text [STRING]","User [ENTITY]"}) + + self.BlockInput=false + self.NextPrompt = 0 + + self:UpdateOverlay() +end + +---------------------------------------------------- +-- Vehicle linking +---------------------------------------------------- +function ENT:TriggerInput(name,value) + if name == "Block Input" then + self.BlockInput = value~=0 + self:UpdateOverlay() + if IsValid( self.User ) then self:Unprompt( true ) end + elseif name == "Prompt" then + if value ~= 0 then self:Prompt() end + end +end + +---------------------------------------------------- +-- Vehicle linking +---------------------------------------------------- +function ENT:UnlinkEnt(ent) + if not IsValid( ent ) then return false, "Invalid entity specified" end + + if IsValid(self.Vehicle) then + self.Vehicle:RemoveCallOnRemove( "wire_textentry_onremove" ) + self.Vehicle.WireTextEntry = nil + end + + self.Vehicle = nil + WireLib.SendMarks( self, {} ) + return true +end + +function ENT:LinkEnt(ent) + if not IsValid( ent ) then return false, "Invalid entity specified" end + if not ent:IsVehicle() then return false, "Entity must be a vehicle" end + + if IsValid( self.Vehicle ) then -- remove old callback + self.Vehicle:RemoveCallOnRemove( "wire_textentry_onremove" ) + self.Vehicle.WireTextEntry = nil + end + + self.Vehicle = ent + self.Vehicle.WireTextEntry = self + + -- add new callback + self.Vehicle:CallOnRemove( "wire_textentry_onremove", function() + self:UnlinkEnt( ent ) + end) + + WireLib.SendMarks( self, { ent } ) + return true +end + +function ENT:ClearEntities() + self:UnlinkEnt(self.Vehicle) +end + +function ENT:OnRemove() + if IsValid( self.Vehicle ) then -- remove callback + self.Vehicle:RemoveCallOnRemove( "wire_textentry_onremove" ) + self.Vehicle.WireTextEntry = nil + end + + self:Unprompt( true ) +end + +---------------------------------------------------- +-- Receiving text from client +---------------------------------------------------- +util.AddNetworkString("wire_textentry_action") +net.Receive("wire_textentry_action",function(len,ply) + local self=net.ReadEntity() + + if not IsValid( self ) or not IsValid( ply ) or ply ~= self.User then return end + + local ok = net.ReadBool() + local text = net.ReadString() + + self:Unprompt() -- in all cases, make text entry available for use again + + if ok and not self.BlockInput then + WireLib.TriggerOutput( self, "Text", text ) + + local timername = "wire_textentry_" .. self:EntIndex() + timer.Remove( timername ) + if math.max(self:GetHold(),0) > 0 then + timer.Create( timername, math.max(self:GetHold(),0), 1, function() + if IsValid( self ) then + WireLib.TriggerOutput( self, "User", nil ) + WireLib.TriggerOutput( self, "Text", "" ) + end + end) + end + end + + self:UpdateOverlay() +end) + +---------------------------------------------------- +-- Prompt +-- Sends prompt to user etc +---------------------------------------------------- +util.AddNetworkString("wire_textentry_show") +function ENT:Prompt( ply ) + if ply then + if CurTime() < self.NextPrompt then return end -- anti spam + self.NextPrompt = CurTime() + 0.1 + + if self.BlockInput or IsValid( self.User ) then + WireLib.AddNotify(ply,"That text entry is not accepting input right now!",NOTIFY_ERROR,5,6) + return + end + + self.User = ply + + WireLib.TriggerOutput( self, "User", ply ) + WireLib.TriggerOutput( self, "In Use", 1 ) + + local timername = "wire_textentry_" .. self:EntIndex() + timer.Remove( timername ) + + net.Start( "wire_textentry_show" ) + net.WriteEntity( self ) + net.Send( ply ) + + self:UpdateOverlay() + elseif IsValid( self.Vehicle ) and IsValid( self.Vehicle:GetDriver() ) then -- linked + self:Prompt( self.Vehicle:GetDriver() ) -- prompt for driver + else -- not linked + self:Prompt( self:GetPlayer() ) -- prompt for owner + end +end + +---------------------------------------------------- +-- Unprompt +-- Unsets user, making the text entry usable by other users +---------------------------------------------------- +util.AddNetworkString("wire_textentry_kick") +function ENT:Unprompt( kickuser ) + if IsValid( self.User ) and kickuser then + net.Start( "wire_textentry_kick" ) net.Send( self.User ) + end + + local timername = "wire_textentry_" .. self:EntIndex() + timer.Remove( timername ) + + self.User = nil + WireLib.TriggerOutput( self, "In Use", 0 ) + self:UpdateOverlay() +end + +---------------------------------------------------- +-- PlayerLeaveVehicle +---------------------------------------------------- +hook.Add( "PlayerLeaveVehicle", "wire_textentry_leave_vehicle", function( ply, vehicle ) + if vehicle.WireTextEntry and IsValid( vehicle.WireTextEntry ) and + IsValid( vehicle.WireTextEntry.User ) and vehicle.WireTextEntry.User == ply then + + vehicle.WireTextEntry:Unprompt( true ) + end +end) + +---------------------------------------------------- +-- Use +---------------------------------------------------- +function ENT:Use(ply) + if self:GetDisableUse() or not IsValid( ply ) then return end + + self:Prompt( ply ) +end + +---------------------------------------------------- +-- Setup +---------------------------------------------------- +function ENT:Setup(hold,disableuse) + hold = tonumber(hold) + if hold then + self:SetHold( math.max( hold, 0 ) ) + end + + disableuse = tobool(disableuse) + if disableuse ~= nil then + self:SetDisableUse( disableuse ) + end + + self:UpdateOverlay() +end +duplicator.RegisterEntityClass("gmod_wire_textentry",WireLib.MakeWireEnt,"Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textreceiver.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textreceiver.lua new file mode 100644 index 0000000..090d429 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textreceiver.lua @@ -0,0 +1,145 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Text Receiver" +ENT.WireDebugName = "Text Receiver" + +if CLIENT then return end -- No more client + +local receivers = {} + +local function RegisterReceiver( ent ) + receivers[ent] = true +end + +local function RemoveReceiver( ent ) + receivers[ent] = nil +end + +hook.Add( "PlayerSay", "Wire Text receiver PlayerSay", function( ply, txt ) + for ent,_ in pairs( receivers ) do + if not ent or not ent:IsValid() then + RemoveReceiver( ent ) + else + ent:PlayerSpoke( ply, txt ) + end + end +end) + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + RegisterReceiver( self ) + + self.Outputs = WireLib.CreateOutputs( self, { "Message [STRING]", "Player [ENTITY]", "Clk" } ) + + self.UseLuaPatterns = false + self.CaseInsensitive = true + self.Matches = {} +end + +function ENT:Setup( UseLuaPatterns, Matches, CaseInsensitive ) + local outputs = { "Message", "Player", "Clk" } + local types = { "STRING", "ENTITY", "NORMAL" } + + if UseLuaPatterns then + outputs[#outputs+1] = "PatternError" + types[#types+1] = "STRING" + end + + if #Matches > 0 then + local txt = "Matches:" + for i=1,#Matches do + outputs[#outputs+1] = "Match " .. i + types[#types+1] = "NORMAL" + txt = txt .. "\n" .. Matches[i] + if UseLuaPatterns then + outputs[#outputs+1] = "Matches " .. i + types[#types+1] = "ARRAY" + end + end + self:SetOverlayText(txt) + end + self.Outputs = WireLib.AdjustSpecialOutputs( self, outputs, types ) + + self:PlayerSpoke( nil, "" ) -- Reset outputs + + self.UseLuaPatterns = UseLuaPatterns + self.Matches = Matches + self.CaseInsensitive = CaseInsensitive +end + +function ENT:OnRemove() + RemoveReceiver( self ) +end + +local string_find = string.find +local string_lower = string.lower +local string_match = string.match + +function ENT:PcallFind( text, match ) + local ok, ret = pcall( string_find, text, match, 1, not self.UseLuaPatterns ) + + if ok == true then + return ret ~= nil + else + return false + end +end + +function ENT:AddError( err, idx ) + self.PatternError = self.PatternError .. err .. " at match nr " .. idx .. "\n" +end + +function ENT:PcallMatch( text, match, idx ) + local ret = { pcall( string_match, text, match ) } + + if ret[1] == true then + table.remove( ret, 1 ) + return ret + else + self:AddError( ret[2], idx ) + return {} + end +end + +function ENT:PlayerSpoke( ply, text ) + WireLib.TriggerOutput( self, "Message", text ) + WireLib.TriggerOutput( self, "Player", ply ) + + WireLib.TriggerOutput( self, "Clk", 1 ) + timer.Simple( 0, function() + if self and self:IsValid() then + WireLib.TriggerOutput( self, "Clk", 0 ) + end + end ) + + if self.CaseInsensitive then text = string_lower(text) end + + if self.UseLuaPatterns then + -- Reset error + self.PatternError = "" + WireLib.TriggerOutput( self, "PatternError", self.PatternError ) + end + + for i=1,#self.Matches do + local match = self.Matches[i] + if self.CaseInsensitive then match = string_lower(match) end + if self:PcallFind( text, match ) then + WireLib.TriggerOutput( self, "Match " .. i, 1 ) + else + WireLib.TriggerOutput( self, "Match " .. i, 0 ) + end + + if self.UseLuaPatterns then + WireLib.TriggerOutput( self, "Matches " .. i, self:PcallMatch( text, match, i ) ) + end + end + + if self.UseLuaPatterns then + WireLib.TriggerOutput( self, "PatternError", string.sub( self.PatternError, 1, -2 ) ) + end +end + +duplicator.RegisterEntityClass("gmod_wire_textreceiver", WireLib.MakeWireEnt, "Data", "UseLuaPatterns", "Matches", "CaseInsensitive" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textscreen.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textscreen.lua new file mode 100644 index 0000000..8ead52b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_textscreen.lua @@ -0,0 +1,299 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Text Screen" +ENT.WireDebugName = "Text Screen" + +function ENT:InitializeShared() + self.text = "" + self.chrPerLine = 5 + self.textJust = 0 + self.valign = 0 + self.tfont = "Arial" + + self.fgcolor = Color(255,255,255) + self.bgcolor = Color(0,0,0) +end + + +if CLIENT then + local Layouter = {} + Layouter.__index = Layouter + + function MakeTextScreenLayouter() + return setmetatable({}, Layouter) + end + + function Layouter:AddString(s) + local width, height = surface.GetTextSize(s) + + local nextx = self.x+width + if nextx > self.x2 then return false end + + table.insert(self.drawlist, { s, self.x, self.y }) + + self.x = nextx + self.LineHeight = math.max(height, self.LineHeight) + + return true + end + + function Layouter:NextLine() + if self.LineHeight == 0 then + self:AddString(" ") + end + + local nexty = self.y+self.LineHeight + + if nexty > self.y2 then return false end + + local offsetx = (self.x2-self.x)*self.halign/2 + + table.insert(self.lines, { offsetx, self.drawlist }) + + self.y = nexty + self:ResetLine() + return true + end + + function Layouter:ResetLine() + self.LineHeight = 0 + self.x = self.x1 + self.drawlist = {} + end + + function Layouter:ResetPage() + self:ResetLine() + self.y = self.y1 + self.lines = {} + end + + -- valign is not supported yet + function Layouter:layout(text, x, y, w, h, halign) + self.x1 = x + self.y1 = y + self.x2 = x+w + self.y2 = y+h + self.halign = halign + + self:ResetPage() + + for line,newlines in text:gmatch("([^\n]*)(\n*)") do + for spaces,word in line:gmatch("( *)([^ ]*)") do + if not self:AddString(spaces..word) then + if not self:NextLine() then return false end + self:AddString(word) + end + end + for i = 1,#newlines do + if not self:NextLine() then return false end + end + end + if not self:NextLine() then return false end + return true + end + + function Layouter:DrawText(text, x, y, w, h, halign, valign) + self:layout(text, x, y, w, h, halign, valign) + + local offsety = (self.y2-self.y)*valign/2 + + for _,offsetx,drawlist in ipairs_map(self.lines,unpack) do + for _,s,x,y in ipairs_map(drawlist,unpack) do + surface.SetTextPos(x+offsetx, y+offsety) + surface.DrawText(s) + end + end + end + + function Layouter:GetTextSize(text, w, h) + self:layout(text, 0, 0, w, h, 2, 0) + + local minoffset = nil + + for _, offsetx, drawlist in ipairs_map(self.lines, unpack) do + if not minoffset then + minoffset = offsetx + else + minoffset = math.min(minoffset, offsetx) + end + end + + return minoffset and self.x2 - minoffset or 0, self.y + end + -------------------------------------------------------------------------------- + + function ENT:Initialize() + self:InitializeShared() + + self.GPU = WireGPU(self) + self.layouter = MakeTextScreenLayouter() + self:CreateFont(self.tfont, self.chrPerLine) + + WireLib.netRegister(self) + end + + function ENT:OnRemove() + self.GPU:Finalize() + self.NeedRefresh = true + end + function ENT:Draw() + self:DrawModel() + + if self.NeedRefresh then + self.NeedRefresh = nil + self.GPU:RenderToGPU(function() + local w = 512 + local h = 512 + + surface.SetDrawColor(self.bgcolor.r, self.bgcolor.g, self.bgcolor.b, 255) + surface.DrawRect(0, 0, w, h) + + surface.SetFont(self.tfont..self.chrPerLine) + surface.SetTextColor(self.fgcolor) + self.layouter:DrawText(self.text, 0, 0, w, h, self.textJust, self.valign) + end) + end + + self.GPU:Render() + --[[ + self.GPU:RenderToWorld(512, nil, function(x, y, w, h) + + surface.SetDrawColor(self.bgcolor.r, self.bgcolor.g, self.bgcolor.b, 255) + surface.DrawRect(x, y, w, h) + + surface.SetFont("textScreenfont"..self.chrPerLine) + surface.SetTextColor(self.fgcolor) + self.layouter:DrawText(self.text, x, y, w, h, self.textJust, self.valign) + end) + ]] + Wire_Render(self) + end + + function ENT:SetText(text) + self.text = text + self.NeedRefresh = true + end + + function ENT:Receive() + if net.ReadBit() ~= 0 then + self.chrPerLine = net.ReadUInt(4) + self.textJust = net.ReadUInt(2) + self.valign = net.ReadUInt(2) + + self.fgcolor = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) + self.bgcolor = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) + self.tfont = net.ReadString() + self:CreateFont(self.tfont, self.chrPerLine) + + self.NeedRefresh = true + else + self:SetText(net.ReadString()) + end + end + + local createdFonts = {} + function ENT:CreateFont(font, chrPerLine) + if createdFonts[font .. chrPerLine] then return end + + local fontData = { + font = font, + size = 380 / chrPerLine, + weight = 400, + antialias = true, + additive = false + } + surface.CreateFont(font .. chrPerLine, fontData) + createdFonts[font .. chrPerLine] = true + self.NeedRefresh = true + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.doSendText = false + self.doSendConfig = false + self.Inputs = WireLib.CreateSpecialInputs(self, { "String", "Font", "FGColor", "BGColor" }, { "STRING", "STRING", "VECTOR", "VECTOR" }) + self:InitializeShared() +end + +function ENT:Setup(DefaultText, chrPerLine, textJust, valign, tfont, fgcolor, bgcolor) + self.fgcolor = fgcolor or Color(255,255,255) + self.bgcolor = bgcolor or Color(0,0,0) + self.chrPerLine = math.Clamp(math.ceil(chrPerLine or 10), 1, 15) + self.textJust = textJust or 1 + self.valign = valign or 0 + self.tfont = tfont or "Arial" + self:SendConfig() + + self:TriggerInput("String", DefaultText or "") +end + +function ENT:TriggerInput(iname, value) + if iname == "String" then + self.text = string.sub(tostring(value), 1, 1024) + self.doSendText = true + elseif iname == "Font" then + self.tfont = tostring(value) + self.doSendConfig = true + elseif iname == "FGColor" then + self.fgcolor = Color(value.x, value.y, value.z) + self.doSendConfig = true + elseif iname == "BGColor" then + self.bgcolor = Color(value.x, value.y, value.z) + self.doSendConfig = true + end +end + +local function formatText(text) + return text:gsub("
", "\n") +end + +function ENT:SendText(ply) + self.doSendText = false + WireLib.netStart(self) + net.WriteBit(false) -- Sending Text + net.WriteString(formatText(self.text)) + WireLib.netEnd(ply) +end + +function ENT:Think() + if self.doSendConfig then + self:SendConfig() + end + if self.doSendText then + self:SendText() + end +end + +function ENT:SendConfig(ply) + self.doSendConfig = false + WireLib.netStart(self) + net.WriteBit(true) -- Sending Config + net.WriteUInt(self.chrPerLine, 4) + net.WriteUInt(self.textJust, 2) + net.WriteUInt(self.valign, 2) + + net.WriteUInt(self.fgcolor.r, 8) + net.WriteUInt(self.fgcolor.g, 8) + net.WriteUInt(self.fgcolor.b, 8) + + net.WriteUInt(self.bgcolor.r, 8) + net.WriteUInt(self.bgcolor.g, 8) + net.WriteUInt(self.bgcolor.b, 8) + net.WriteString(string.sub(self.tfont,0,31)) + WireLib.netEnd(ply) +end + +function ENT:Retransmit(ply) + self:SendText(ply) + self:SendConfig(ply) +end + +duplicator.RegisterEntityClass("gmod_wire_textscreen", WireLib.MakeWireEnt, "Data", "text", "chrPerLine", "textJust", "valign", "tfont", "fgcolor", "bgcolor") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_thruster.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_thruster.lua new file mode 100644 index 0000000..715dd0d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_thruster.lua @@ -0,0 +1,310 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Thruster" +ENT.RenderGroup = RENDERGROUP_BOTH -- TODO: this is only needed when they're active. +ENT.WireDebugName = "Thruster" + +WireLib.ThrusterNetEffects = { + ["fire_smoke"] = true +} + +function ENT:SetEffect( name ) + self:SetNWString( "Effect", name ) + self.neteffect = WireLib.ThrusterNetEffects[ name ] +end +function ENT:GetEffect( name ) + return self:GetNWString( "Effect" ) +end + +function ENT:SetOn( boolon ) + self:SetNWBool( "On", boolon, true ) +end +function ENT:IsOn( name ) + return self:GetNWBool( "On" ) +end + +function ENT:SetOffset( v ) + self:SetNWVector( "Offset", v, true ) +end +function ENT:GetOffset( name ) + return self:GetNWVector( "Offset" ) +end + + +if CLIENT then + function ENT:Initialize() + self.ShouldDraw = 1 + self.EffectAvg = 0 + + local mx, mn = self:GetRenderBounds() + self:SetRenderBounds(mn + Vector(0,0,128), mx, 0) + end + + function ENT:DrawTranslucent() + if self.ShouldDraw == 0 or not self:IsOn() then return end + + local EffectDraw = WireLib.ThrusterEffectDraw[self:GetEffect()] + if EffectDraw then EffectDraw(self) end + end + + function ENT:Think() + BaseClass.Think(self) + + self.ShouldDraw = GetConVarNumber("cl_drawthrusterseffects") + + if self.ShouldDraw == 0 or not self:IsOn() then return end + + local EffectThink = WireLib.ThrusterEffectThink[self:GetEffect()] + if EffectThink then EffectThink(self) end + end + + function ENT:CalcNormal() + return (self:LocalToWorld(self:GetOffset()) - self:GetPos()):GetNormalized() + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + phys:Wake() + end + + local max = self:OBBMaxs() + local min = self:OBBMins() + + self.ThrustOffset = Vector( 0, 0, max.z ) + self.ThrustOffsetR = Vector( 0, 0, min.z ) + self.ForceAngle = self.ThrustOffset:GetNormalized() * -1 + + self:SetForce( 2000 ) + + self.oweffect = "fire" + self.uweffect = "same" + + self:SetOffset( self.ThrustOffset ) + self:StartMotionController() + + self:Switch( false ) + + self.Inputs = Wire_CreateInputs(self, { "A" }) + + self.soundname = Sound( "PhysicsCannister.ThrusterLoop" ) +end + +function ENT:OnRemove() + BaseClass.OnRemove(self) + + if (self.soundname and self.soundname != "") then + self:StopSound(self.soundname) + end +end + +function ENT:SetForce( force, mul ) + if (force) then + self.force = force + self:ShowOutput() + end + mul = mul or 1 + + local phys = self:GetPhysicsObject() + if (!phys:IsValid()) then + Msg("Warning: [",self,"] Physics object isn't valid!\n") + return + end + + // Get the data in worldspace + local ThrusterWorldPos = phys:LocalToWorld( self.ThrustOffset ) + local ThrusterWorldForce = phys:LocalToWorldVector( self.ThrustOffset * -1 ) + + // Calculate the velocity + ThrusterWorldForce = ThrusterWorldForce * self.force * mul * 50 + self.ForceLinear, self.ForceAngle = phys:CalculateVelocityOffset( ThrusterWorldForce, ThrusterWorldPos ); + self.ForceLinear = phys:WorldToLocalVector( self.ForceLinear ) + + if self.neteffect then + -- self.ForceLinear is 0 if the thruster is frozen + self.effectforce = ThrusterWorldForce:Length() + self.updateeffect = true + end + + if ( mul > 0 ) then + self:SetOffset( self.ThrustOffset ) + else + self:SetOffset( self.ThrustOffsetR ) + end +end + +function ENT:SetDatEffect(uwater, owater, uweffect, oweffect) + if self:WaterLevel() > 0 then + if not uwater then + self:SetEffect("none") + return + end + + if uweffect == "same" then + self:SetEffect(oweffect) + return + else + self:SetEffect(uweffect) + return + end + else + if not owater then + self:SetEffect("none") + return + end + self:SetEffect(oweffect) + return + end +end + +function ENT:Setup(force, force_min, force_max, oweffect, uweffect, owater, uwater, bidir, soundname) + self:SetForce(force) + + self:SetDatEffect(uwater, owater, uweffect, oweffect) + + self.oweffect = oweffect + self.uweffect = uweffect + self.force_min = force_min + self.force_max = force_max + self.bidir = bidir + self.owater = owater + self.uwater = uwater + + if (!soundname) then soundname = "" end + + -- Preventing client crashes + local BlockedChars = '["?]' + if ( string.find(soundname, BlockedChars) ) then + self:StopSound( self.SoundName ) + soundname = "" + end + + if (soundname == "") then + self:StopSound( self.soundname ) + end + + self.soundname = Sound( soundname ) + + --self:SetOverlayText( "Thrust = " .. 0 .. "\nMul: " .. math.Round(force*1000)/1000 ) +end + +function ENT:TriggerInput(iname, value) + if (iname == "A") then + if ( (self.bidir) and (math.abs(value) > 0.01) and (math.abs(value) > self.force_min) ) or ( (value > 0.01) and (value > self.force_min) ) then + self:Switch(true, math.min(value, self.force_max)) + else + self:Switch(false, 0) + end + end +end + +function ENT:Think() + if self.neteffect and self.updateeffect then + self.updateeffect = false + self:SetNWFloat("Thrust", self.effectforce) + end + self:NextThink(CurTime()+0.5) +end + +function ENT:PhysicsSimulate( phys, deltatime ) + if (!self:IsOn()) then return SIM_NOTHING end + + if (self:WaterLevel() > 0) then + if (not self.uwater) then + self:SetEffect("none") + return SIM_NOTHING + end + + if (self.uweffect == "same") then + self:SetEffect(self.oweffect) + else + self:SetEffect(self.uweffect) + end + else + if (not self.owater) then + self:SetEffect("none") + return SIM_NOTHING + end + + self:SetEffect(self.oweffect) + end + + local ForceAngle, ForceLinear = self.ForceAngle, self.ForceLinear + + return ForceAngle, ForceLinear, SIM_LOCAL_ACCELERATION +end + +function ENT:Switch( on, mul ) + if (!self:IsValid()) then return false end + + local changed = (self:IsOn() ~= on) + self:SetOn( on ) + + + if (on) then + if (changed) and (self.soundname and self.soundname != "") then + self:StopSound( self.soundname ) + self:EmitSound( self.soundname ) + end + + self.mul = mul + + self:SetForce( nil, mul ) + else + if (self.soundname and self.soundname != "") then + self:StopSound( self.soundname ) + end + + self.mul = 0 + end + self:ShowOutput() + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + phys:Wake() + end + + return true +end + +function ENT:ShowOutput() + self:SetOverlayText(string.format("Force Mul: %.2f\nModel Mul: %.2f\nInput: %.2f\nForce Applied: %.2f", + self.force or 0, + self.ThrustOffset.z, + self.mul or 0, + (self.force or 0) * (self.mul or 0) * self.ThrustOffset.z + )) +end + +function ENT:OnRestore() + local phys = self:GetPhysicsObject() + + if (phys:IsValid()) then + phys:Wake() + end + + local max = self:OBBMaxs() + local min = self:OBBMins() + + self.ThrustOffset = Vector( 0, 0, max.z ) + self.ThrustOffsetR = Vector( 0, 0, min.z ) + self.ForceAngle = self.ThrustOffset:GetNormalized() * -1 + + self:SetOffset( self.ThrustOffset ) + self:StartMotionController() + + BaseClass.OnRestore(self) +end + +duplicator.RegisterEntityClass("gmod_wire_thruster", WireLib.MakeWireEnt, "Data", "force", "force_min", "force_max", "oweffect", "uweffect", "owater", "uwater", "bidir", "soundname") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trail.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trail.lua new file mode 100644 index 0000000..3b2f096 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trail.lua @@ -0,0 +1,50 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Trail" +ENT.WireDebugName = "Trail" +ENT.RenderGroup = RENDERGROUP_BOTH + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self, {"Set", "Length","StartSize","EndSize","R","G","B","A"}) + self.Outputs = Wire_CreateOutputs(self, {}) + + self.Trail = { + Color = Color(255, 255, 255, 255), + Length = 5, + StartSize = 32, + EndSize = 0, + Material = "trails/lol" + } +end + +function ENT:Setup(Trail) + self.Trail = table.Merge(self.Trail, Trail) + self:SetOverlayText( "Trail: " .. Trail.Material ) +end + +function ENT:TriggerInput(iname, value) + if iname == "Set" and value ~= 0 then + duplicator.EntityModifiers.trail(self:GetOwner(), self, self.Trail) + elseif iname == "Length" then + self.Trail.Length = value + elseif iname == "StartSize" then + self.Trail.StartSize = value + elseif iname == "EndSize" then + self.Trail.EndSize = value + elseif iname == "R" then + self.Trail.Color.r = value + elseif iname == "G" then + self.Trail.Color.g = value + elseif iname == "B" then + self.Trail.Color.b = value + elseif iname == "A" then + self.Trail.Color.a = value + end +end + +duplicator.RegisterEntityClass("gmod_wire_trail", WireLib.MakeWireEnt, "Data", "Trail") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trigger.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trigger.lua new file mode 100644 index 0000000..151b5ca --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trigger.lua @@ -0,0 +1,99 @@ +-- Wire Trigger created by mitterdoo +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Trigger" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "Trigger" + +function ENT:SetupDataTables() + self:NetworkVar( "Vector", 0, "TriggerSize" ) + self:NetworkVar( "Vector", 1, "TriggerOffset" ) + self:NetworkVar( "Entity", 0, "TriggerEntity" ) + self:NetworkVar( "Int", 0, "Filter" ) + self:NetworkVar( "Bool", 0, "OwnerOnly" ) +end + +if CLIENT then + function ENT:GetOverlayData() + local size = self:GetTriggerSize() + local offset = self:GetTriggerOffset() + + local txt = "Size: " .. string.format( "(%.2f,%.2f,%.2f)", size.x, size.y, size.z ) .. "\n" + txt = txt .. "Offset: " .. string.format( "(%.2f,%.2f,%.2f)", offset.x, offset.y, offset.z ) .. "\n" + txt = txt .. "Triggered by: " .. ( + self:GetFilter() == 0 and "All Entities" or + self:GetFilter() == 1 and "Only Players" or + self:GetFilter() == 2 and "Only Props" + ) + + return {txt=txt} + end + + return -- No more client +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + local phys = self:GetPhysicsObject() if (phys:IsValid()) then phys:Wake() end + + self.Outputs = WireLib.CreateOutputs(self, { "EntCount", "Entities [ARRAY]" }) +end + +function ENT:Setup( model, filter, owneronly, sizex, sizey, sizez, offsetx, offsety, offsetz ) + + filter = math.Clamp( filter, 0, 2 ) + sizex = math.Clamp( sizex, -1000, 1000 ) + sizey = math.Clamp( sizey, -1000, 1000 ) + sizez = math.Clamp( sizez, -1000, 1000 ) + offsetx = math.Clamp( offsetx, -1000, 1000 ) + offsety = math.Clamp( offsety, -1000, 1000 ) + offsetz = math.Clamp( offsetz, -1000, 1000 ) + self.model = model + self.filter = filter + self.owneronly = owneronly + self.sizex = sizex + self.sizey = sizey + self.sizez = sizez + self.offsetx = offsetx + self.offsety = offsety + self.offsetz = offsetz + + self:SetOwnerOnly( tobool( owneronly ) ) + self:SetModel( model ) + self:SetFilter( filter ) + self:SetTriggerSize( Vector( sizex, sizey, sizez ) ) + self:SetTriggerOffset( Vector( offsetx, offsety, offsetz ) ) + + + local mins = self:GetTriggerSize() / -2 + local maxs = self:GetTriggerSize() / 2 + + local oldtrig = self:GetTriggerEntity() + if IsValid( oldtrig ) then + oldtrig:SetCollisionBounds( mins, maxs ) + oldtrig:SetPos( self:LocalToWorld( self:GetTriggerOffset() ) ) + oldtrig:Reset() + else + local trig = ents.Create( "gmod_wire_trigger_entity" ) + trig:SetPos( self:LocalToWorld( self:GetTriggerOffset() ) ) + trig:SetAngles( self:GetAngles() ) + trig:PhysicsInit( SOLID_BBOX ) + trig:SetMoveType( MOVETYPE_VPHYSICS ) + trig:SetSolid( SOLID_BBOX ) + trig:SetModel( "models/hunter/blocks/cube025x025x025.mdl" ) + trig:SetParent( self ) + trig:Spawn() + + trig:SetCollisionBounds( mins, maxs ) + trig:SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE ) + trig:SetNoDraw( true ) + trig:SetTrigger( true ) + self:SetTriggerEntity( trig ) + trig:SetTriggerEntity( self ) + self:DeleteOnRemove( trig ) + end +end + +duplicator.RegisterEntityClass("gmod_wire_trigger", WireLib.MakeWireEnt, "Data", "model", "filter", "owneronly", "sizex", "sizey", "sizez", "offsetx", "offsety", "offsetz" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trigger_entity.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trigger_entity.lua new file mode 100644 index 0000000..2676d7b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_trigger_entity.lua @@ -0,0 +1,58 @@ +-- Wire Trigger created by mitterdoo +AddCSLuaFile() +ENT.Base = "base_anim" +ENT.Name = "Wire Trigger Entity" +ENT.Author = "mitterdoo" + +function ENT:Initialize() + + if SERVER then + self.EntsInside = {} + end + +end +function ENT:SetupDataTables() + + self:NetworkVar( "Entity", 0, "TriggerEntity" ) + +end + +function ENT:Reset() + self.EntsInside = {} + + local owner = self:GetTriggerEntity() + if not IsValid( owner ) then return end + WireLib.TriggerOutput( owner, "EntCount", 0 ) + WireLib.TriggerOutput( owner, "Entities", self.EntsInside ) +end + +function ENT:StartTouch( ent ) + + local owner = self:GetTriggerEntity() + if not IsValid( owner ) then return end + if ent == owner then return end -- this never happens but just in case... + if owner:GetFilter() == 1 and not ent:IsPlayer() or owner:GetFilter() == 2 and ent:IsPlayer() then return end + local ply = ent:IsPlayer() and ent + if owner:GetOwnerOnly() and ( WireLib.GetOwner( ent ) or ply ) ~= WireLib.GetOwner( owner ) then return end + + self.EntsInside[ #self.EntsInside+1 ] = ent + + WireLib.TriggerOutput( owner, "EntCount", #self.EntsInside ) + WireLib.TriggerOutput( owner, "Entities", self.EntsInside ) + +end +function ENT:EndTouch( ent ) + + local owner = self:GetTriggerEntity() + if not IsValid( owner ) then return end + + for i = 1, #self.EntsInside do + if self.EntsInside[ i ] == ent then + table.remove( self.EntsInside, i ) + end + end + + WireLib.TriggerOutput( owner, "EntCount", #self.EntsInside ) + WireLib.TriggerOutput( owner, "Entities", self.EntsInside ) + +end diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_turret.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_turret.lua new file mode 100644 index 0000000..221fc40 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_turret.lua @@ -0,0 +1,125 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Turret" +ENT.WireDebugName = "Turret" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:DrawShadow( false ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + phys:Wake() + end + + self.Firing = false + self.NextShot = 0 + + self.Inputs = Wire_CreateInputs(self, { "Fire" }) +end + +function ENT:FireShot() + + if ( self.NextShot > CurTime() ) then return end + + self.NextShot = CurTime() + self.delay + + -- Make a sound if you want to. + if self.sound then + self:EmitSound(self.sound) + end + + -- Get the muzzle attachment (this is pretty much always 1) + local Attachment = self:GetAttachment( 1 ) + + -- Get the shot angles and stuff. + local shootOrigin = Attachment.Pos + self:GetVelocity() * engine.TickInterval() + local shootAngles = self:GetAngles() + + -- Shoot a bullet + local bullet = {} + bullet.Num = self.numbullets + bullet.Src = shootOrigin + bullet.Dir = shootAngles:Forward() + bullet.Spread = self.spreadvector + bullet.Tracer = self.tracernum + bullet.TracerName = self.tracer + bullet.Force = self.force + bullet.Damage = self.damage + bullet.Attacker = self:GetPlayer() + self:FireBullets( bullet ) + + -- Make a muzzle flash + local effectdata = EffectData() + effectdata:SetOrigin( shootOrigin ) + effectdata:SetAngles( shootAngles ) + effectdata:SetScale( 1 ) + util.Effect( "MuzzleEffect", effectdata ) + +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:Think() + BaseClass.Think(self) + + if( self.Firing ) then + self:FireShot() + end + + self:NextThink(CurTime()) + return true +end + +function ENT:TriggerInput(iname, value) + if (iname == "Fire") then + self.Firing = value > 0 + end +end + +local ValidTracers = { + ["Tracer"]=true, + ["AR2Tracer"]=true, + ["AirboatGunHeavyTracer"]=true, + ["LaserTracer"]=true, + [""]=true, +} + +function ENT:Setup(delay, damage, force, sound, numbullets, spread, tracer, tracernum) + if not game.SinglePlayer() then + self.delay = math.max(delay,0.05) -- clamp delay if it's not single player + else + self.delay = delay + end + + self.damage = damage + self.force = force + -- Preventing client crashes + if string.find(sound, '["?]') then + self.sound = "" + else + self.sound = sound + end + + if not game.SinglePlayer() then + self.numbullets = math.Clamp( numbullets, 1, 10 ) -- clamp num bullets if it's not single player + else + self.numbullets = numbullets + end + + self.spread = spread -- for duplication + self.spreadvector = Vector(spread,spread,0) + + self.tracer = ValidTracers[string.Trim(tracer)] and string.Trim(tracer) or "" + self.tracernum = tracernum or 1 +end + +duplicator.RegisterEntityClass( "gmod_wire_turret", WireLib.MakeWireEnt, "Data", "delay", "damage", "force", "sound", "numbullets", "spread", "tracer", "tracernum" ) diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_twoway_radio.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_twoway_radio.lua new file mode 100644 index 0000000..edafec4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_twoway_radio.lua @@ -0,0 +1,172 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Two-way Radio" +ENT.WireDebugName = "2W Radio" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs(self, { "A", "B", "C", "D" }) + self.Outputs = Wire_CreateOutputs(self, { "A", "B", "C", "D" }) + + self.PairID = nil + self.Other = nil +end + +function ENT:Setup() + self.PrevOutputA = nil + self.PrevOutputB = nil + self.PrevOutputC = nil + self.PrevOutputD = nil + + self:ShowOutput("update", 1) + Wire_TriggerOutput(self, "A", self.Outputs.A.Value or 0) + Wire_TriggerOutput(self, "B", self.Outputs.B.Value or 0) + Wire_TriggerOutput(self, "C", self.Outputs.C.Value or 0) + Wire_TriggerOutput(self, "D", self.Outputs.D.Value or 0) +end + +function ENT:TriggerInput(iname, value) + if self.Other and self.Other:IsValid() and self.Other.Inputs then + self.Other:ReceiveRadio(iname, value) + self:ShowOutput("update", 1) + end +end + +function ENT:Think() + BaseClass.Think(self) + + if (not self.Other) or (not self.Other:IsValid()) then + self.Other = nil + self.PairID = nil + end +end +function IsRadio(entity) + if IsValid(entity) and entity:GetClass() == "gmod_wire_twoway_radio" then return true end + return false +end +function ENT:ReceiveRadio(iname, value) + if (iname == "A") and (self.Other) and (self.Other:IsValid()) then + Wire_TriggerOutput(self, "A", value) + elseif (iname == "B") and (self.Other) and (self.Other:IsValid()) then + Wire_TriggerOutput(self, "B", value) + elseif (iname == "C") and (self.Other) and (self.Other:IsValid()) then + Wire_TriggerOutput(self, "C", value) + elseif (iname == "D") and (self.Other) and (self.Other:IsValid()) then + Wire_TriggerOutput(self, "D", value) + end + self:ShowOutput(iname, value) +end + +function ENT:RadioLink(other, id) + self.Other = other + self.PairID = id + self.PeerID = id + + self:TriggerInput("A", self.Inputs.A.Value or 0) + self:TriggerInput("B", self.Inputs.B.Value or 0) + self:TriggerInput("C", self.Inputs.C.Value or 0) + self:TriggerInput("D", self.Inputs.D.Value or 0) + self:ShowOutput("update", 1) +end + +function ENT:LinkEnt( other ) + if not IsRadio(other) then return false, "Must link to another Two-Way Radio" end + if other == self then return false, "Cannot link Two-Way Radio to itself" end + -- If it's already linked... + if self.Other then + -- to the same one, return + if self.Other == other then return false end + --to a different one, then tell it to unlink + self.Other.UnlinkEnt() + end + + local id = Radio_GetTwoWayID() + self:RadioLink(other, id) + other:RadioLink(self, id) + WireLib.AddNotify(self:GetPlayer(), "The Radios are now paired. Pair ID is " .. tostring(id) .. ".", NOTIFY_GENERIC, 7) + WireLib.SendMarks(self, {other}) + return true +end +function ENT:UnlinkEnt() + if not IsRadio(self) then return false end + if not IsRadio(self.Other) then return false end + self.Other:RadioLink(nil, nil) + WireLib.SendMarks(self.Other, {}) + self:RadioLink(nil, nil) + WireLib.SendMarks(self, {}) + return true +end + +function ENT:ShowOutput(iname, value) + local changed + if (iname == "A") then + if (value ~= self.PrevOutputA) then + self.PrevOutputA = (value or 0) + changed = 1 + end + elseif (iname == "B") then + if (value ~= self.PrevOutputB) then + self.PrevOutputB = (value or 0) + changed = 1 + end + elseif (iname == "C") then + if (value ~= self.PrevOutputC) then + self.PrevOutputC = (value or 0) + changed = 1 + end + elseif (iname == "D") then + if (value ~= self.PrevOutputD) then + self.PrevOutputD = (value or 0) + changed = 1 + end + elseif (iname == "update") then + changed = 1 + end + if (changed) then + if self.PairID == nil then + self:SetOverlayText( "(Not Paired) Transmit: 0, 0, 0, 0" ) + else + self:SetOverlayText( "(Pair ID: " .. self.PairID .. ")\nTransmit A: " .. (self.Inputs.A.Value or 0) .. " B: " .. (self.Inputs.B.Value or 0) .. " C: " .. (self.Inputs.C.Value or 0) .. " D: " .. (self.Inputs.D.Value or 0) .. "\nReceive A: " .. (self.Outputs.A.Value or 0) .. " B: " .. (self.Outputs.B.Value or 0) .. " C: " .. (self.Outputs.C.Value or 0) .. " D: " .. (self.Outputs.D.Value or 0) ) + end + + end +end + +function ENT:OnRestore() + BaseClass.OnRestore(self) + + Wire_AdjustInputs(self, { "A", "B", "C", "D" }) + Wire_AdjustOutputs(self, { "A", "B", "C", "D" }) +end + +// Dupe info functions added by TheApathetic +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if (self.Other) && (self.Other:IsValid()) then + info.Other = self.Other:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + local other = GetEntByID(info.Other) + if IsValid(other) then + -- A new two-way ID is created upon paste to avoid + -- interference with current two-way radios + -- This works because ApplyDupeInfo is called after + -- all entities are already pasted (TheApathetic) + local id = other.PairID or Radio_GetTwoWayID() + self:RadioLink(other, id) + end +end + +duplicator.RegisterEntityClass("gmod_wire_twoway_radio", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_user.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_user.lua new file mode 100644 index 0000000..6c08c1b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_user.lua @@ -0,0 +1,48 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire User" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.WireDebugName = "User" + +function ENT:SetupDataTables() + self:NetworkVar( "Float", 0, "BeamLength" ) +end + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = WireLib.CreateInputs(self, {"Fire"}) + self:Setup(2048) +end + +function ENT:Setup(Range) + if Range then self:SetBeamLength(Range) end +end +function ENT:TriggerInput(iname, value) + if iname == "Fire" and value ~= 0 then + local vStart = self:GetPos() + + local trace = util.TraceLine( { + start = vStart, + endpos = vStart + (self:GetUp() * self:GetBeamLength()), + filter = { self }, + }) + + if not IsValid(trace.Entity) then return false end + local ply = self:GetPlayer() + if not IsValid(ply) then ply = self end + + if not hook.Run( "PlayerUse", ply, trace.Entity ) then return false end + + if trace.Entity.Use then + trace.Entity:Use(ply,ply,USE_ON,0) + else + trace.Entity:Fire("use","1",0) + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_user", WireLib.MakeWireEnt, "Data", "Range") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_value.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_value.lua new file mode 100644 index 0000000..8498ef8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_value.lua @@ -0,0 +1,155 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Value" +ENT.WireDebugName = "Value" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = WireLib.CreateOutputs(self, { "Out" }) +end + +local types_lookup = { + NORMAL = 0, + ANGLE = Angle(0,0,0), + VECTOR = Vector(0,0,0), + VECTOR2 = {0,0}, + VECTOR4 = {0,0,0,0}, + STRING = "", +} + +function ENT:SetupLegacy( values ) + local new = {} + + for k,v in pairs( values ) do + local tp, val = string.match( v, "^ *([^: ]+) *:(.*)$" ) + tp = string.upper(tp or "NORMAL") + + if types_lookup[tp] then + new[#new+1] = { DataType = tp, Value = val or v } + end + end + + self.LegacyOutputs = true + self:Setup( new ) +end + +local tonumber = tonumber +local parsers = {} +function parsers.NORMAL( val ) + return tonumber(val) +end +function parsers.VECTOR ( val ) + local x,y,z = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) + if tonumber(x) and tonumber(y) and tonumber(y) then + return Vector(tonumber(x),tonumber(y),tonumber(z)) + end +end +function parsers.VECTOR2( val ) + local x, y = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *$" ) + if tonumber(x) and tonumber(y) then return {tonumber(x), tonumber(y)} end +end +function parsers.VECTOR4( val ) + local x, y, z, w = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) + if tonumber(x) and tonumber(y) and tonumber(y) and tonumber(w) then + return {tonumber(x),tonumber(y),tonumber(z),tonumber(w)} + end +end +function parsers.ANGLE( val ) + local p,y,r = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) + if tonumber(p) and tonumber(y) and tonumber(r) then + return Angle(tonumber(p),tonumber(y),tonumber(r)) + end +end +function parsers.STRING( val ) + return string.gsub( tostring( val ), "\\[n0\\]", {["\\\\"] = "\\", ["\\n"] = "\n", ["\\0"] = "\0"} ) +end + +function ENT:ParseValue( value, tp ) + if parsers[tp] then + local ret = parsers[tp]( value ) + if ret then + return ret + else + WireLib.AddNotify( self:GetPlayer(), "Constant Value: Unable to parse value '" .. tostring(value) .. "' as type '" .. tp .. "'.", NOTIFY_ERROR, 5, NOTIFYSOUND_ERROR1 ) + return types_lookup[tp] + end + end +end + +function ENT:Setup( valuesin ) + if not valuesin then return end + + local _, val = next( valuesin ) + if not val then + WireLib.AddNotify( self:GetPlayer(), "Constant Value: No values found!", NOTIFY_ERROR, 5, NOTIFYSOUND_ERROR1 ) + elseif not istable( val ) then -- old dupe + self:SetupLegacy( valuesin ) + else + self.value = valuesin -- Wirelink/Duplicator Info + + local names = {} + local types = {} + local values = {} + local descs = {} + + for k,v in pairs(valuesin) do + v.DataType = string.upper( v.DataType ) + if v.DataType == "NUMBER" then v.DataType = "NORMAL" end + + if types_lookup[string.upper( v.DataType )] ~= nil then + names[k] = tostring( k ) + types[k] = string.upper( v.DataType ) + values[k] = self:ParseValue( v.Value, string.upper( v.DataType ) ) + descs[k] = values[k] ~= nil and v.Value or "*ERROR*" + else + WireLib.AddNotify( self:GetPlayer(), "Constant Value: Invalid type '" .. string.upper( v.DataType ) .. "' specified.", NOTIFY_ERROR, 5, NOTIFYSOUND_ERROR1 ) + names[k] = tostring( k ) + types[k] = "STRING" + values[k] = "INVALID TYPE SPECIFIED" + descs[k] = "*ERROR*" + end + end + + if self.LegacyOutputs then + -- Gmod12 Constant Values will have outputs like Value1, Value2... + -- To avoid breaking old dupes, we'll use those names if we're created from an old dupe + for k,v in pairs(names) do + names[k] = "Value"..v + end + end + + -- this is where storing the values as strings comes in: they are the descriptions for the inputs. + WireLib.AdjustSpecialOutputs(self, names, types, descs ) + + local txt = {} + for k,v in pairs(valuesin) do + txt[#txt+1] = string.format( "%s [%s]: %s",names[k],types[k],descs[k]) + WireLib.TriggerOutput( self, names[k], values[k] ) + end + self:SetOverlayText(table.concat( txt, "\n" )) + + self.types = types + self.values = values + end +end + +function ENT:ReadCell( Address ) + Address = math.floor(Address) + local tp = self.types[Address+1] + -- we can only retrieve numbers here, unfortunately. + -- This is because the ReadCell function assumes that things like vectors and strings store one of their values per cell, + -- which the constant value does not. While this could be worked around, it's just not worth the effort imo, and it'd just be confusing to use + -- If you need to get other types, you'll need to use E2's "Wlk[OutputName,OutputType]" index syntax instead + if tp == "NORMAL" then + return self.values[Address+1] + end + + return 0 +end + +duplicator.RegisterEntityClass("gmod_wire_value", WireLib.MakeWireEnt, "Data", "value") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_vectorthruster.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_vectorthruster.lua new file mode 100644 index 0000000..0edfee3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_vectorthruster.lua @@ -0,0 +1,291 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Vector Thruster" +ENT.RenderGroup = RENDERGROUP_BOTH -- TODO: this is only needed when they're active. +ENT.WireDebugName = "Vector Thruster" + +function ENT:SetEffect( name ) + self:SetNWString( "Effect", name ) + self.neteffect = WireLib.ThrusterNetEffects[ name ] +end +function ENT:GetEffect() + return self:GetNWString( "Effect" ) +end + +function ENT:SetOn( boolon ) + if (self:IsOn() ~= boolon) then + if (boolon) then + if (self.soundname and self.soundname != "") then + self:StopSound( self.soundname ) + self:EmitSound( self.soundname ) + end + else + if (self.soundname and self.soundname != "") then + self:StopSound( self.soundname ) + end + end + self:SetNWBool( "vecon", boolon, true ) + end +end +function ENT:IsOn() + return self:GetNWBool( "vecon" ) +end + +function ENT:SetMode( v ) + self:SetNWInt( "vecmode", v, true ) +end +function ENT:GetMode() + return self:GetNWInt( "vecmode" ) +end + +function ENT:SetOffset( v ) + self:SetNWVector( "Offset", v, true ) +end +function ENT:GetOffset( name ) + return self:GetNWVector( "Offset" ) +end + +function ENT:SetNormal( v ) + self:SetNWVector( "vec", v ) +end +function ENT:GetNormal() + return self:GetNWVector( "vec" ) +end + +if CLIENT then + function ENT:Initialize() + self.ShouldDraw = 1 + self.EffectAvg = 0 + + local mx, mn = self:GetRenderBounds() + self:SetRenderBounds(mn + Vector(0,0,128), mx, 0) + end + + function ENT:DrawTranslucent() + if self.ShouldDraw == 0 or not self:IsOn() then return end + + local EffectDraw = WireLib.ThrusterEffectDraw[self:GetEffect()] + if EffectDraw then EffectDraw(self) end + end + + function ENT:Think() + BaseClass.Think(self) + + self.ShouldDraw = GetConVarNumber("cl_drawthrusterseffects") + + if self.ShouldDraw == 0 or not self:IsOn() then return end + + local EffectThink = WireLib.ThrusterEffectThink[self:GetEffect()] + if EffectThink then EffectThink(self) end + end + + function ENT:CalcNormal() + return self:GetNormal() + end + + return -- No more client +end + +-- Server + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self:DrawShadow( false ) + + self.X = 0 + self.Y = 0 + self.Z = 0 + self.mode = 0 + self.yaw = 0 + self.pitch = 0 + self.mul = 0 + self.force = 0 + self.calcforce = true + + self.ForceLinear = vector_origin + self.ForceAngular = vector_origin + + local max = self:OBBMaxs() + self.ThrustOffset = Vector( 0, 0, max.z ) + + local phys = self:GetPhysicsObject() + if (phys:IsValid()) then + local massCenter = phys:GetMassCenter() + self.ThrustOffset.x = massCenter.x + self.ThrustOffset.y = massCenter.y + phys:Wake() + end + + self.oweffect = "fire" + self.uweffect = "same" + + self:SetOffset(self.ThrustOffset) + self:SetNormal(Vector()) + + self:StartMotionController() + + self.Inputs = Wire_CreateInputs(self, { "Mul" }) + + self.soundname = Sound( "PhysicsCannister.ThrusterLoop" ) +end + +function ENT:OnRemove() + BaseClass.OnRemove(self) + + if (self.soundname) then + self:StopSound(self.soundname) + end +end + +function ENT:CalcForce(phys) + if self.angleinputs then + self.X = math.cos(self.pitch) * math.cos(self.yaw) + self.Y = math.sin(self.pitch) + self.Z = math.cos(self.pitch) * math.sin(self.yaw) + end + + local ThrusterWorldForce = Vector( -self.X, -self.Y, -self.Z ) + + if (self.mode == 0) then + ThrusterWorldForce = phys:LocalToWorldVector( ThrusterWorldForce ) + elseif (self.mode == 2) then + ThrusterWorldForce = phys:LocalToWorldVector( ThrusterWorldForce ) + ThrusterWorldForce.z = -self.Z + end + + local ThrustLen = ThrusterWorldForce:Length() + + if self.lengthismul then + self.mul = ThrustLen + end + + if ThrustLen>0 then + local ThrustNormal = ThrusterWorldForce/ThrustLen + self:SetNormal( -ThrustNormal ) + self.ForceLinear, self.ForceAngular = phys:CalculateVelocityOffset( ThrustNormal * ( math.min( self.force * self.mul, self.force_max ) * 50 ), phys:LocalToWorld( self.ThrustOffset ) ) + else + self:SetNormal( vector_origin ) + self.ForceLinear, self.ForceAngular = vector_origin, vector_origin + end + + if self.neteffect then + self:SetNWFloat("Thrust", self.ForceLinear:Length()) + end +end + +function ENT:Setup(force, force_min, force_max, oweffect, uweffect, owater, uwater, bidir, soundname, mode, angleinputs, lengthismul) + self.mul = 0 + self.force = force + self.oweffect = oweffect + self.uweffect = uweffect + self.force_min = force_min + self.force_max = force_max + self.bidir = bidir + self.owater = owater + self.uwater = uwater + self.angleinputs = angleinputs + self.lengthismul = lengthismul + self.calcforce = true + + -- Preventing client crashes + local BlockedChars = '["?]' + if ( string.find(soundname, BlockedChars) ) then + soundname = "" + end + + if (soundname and soundname == "" and self.soundname and self.soundname != "") then + self:StopSound(self.soundname) + end + + if (soundname) then + self.soundname = Sound(soundname) + end + + self.mode = mode or 0 + self:SetMode( self.mode ) + self:ShowOutput() + + if (angleinputs) then + WireLib.AdjustInputs(self, {"Mul", "Pitch", "Yaw"}) + else + WireLib.AdjustInputs(self, {"Mul", "X", "Y", "Z", "Vector [VECTOR]"}) + end +end + +function ENT:TriggerInput(iname, value) + if (iname == "Mul") then + self.mul = value + elseif (iname == "X") then + self.X = value + elseif (iname == "Y") then + self.Y = value + elseif (iname == "Z") then + self.Z = value + elseif (iname == "Vector") then + self.X = value.x + self.Y = value.y + self.Z = value.z + elseif (iname == "Yaw") then + self.yaw = math.rad( value ) + elseif (iname == "Pitch") then + self.pitch = math.rad( value ) + end + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + self.calcforce = true + if phys:IsMotionEnabled() then + phys:Wake() + else + self:PhysicsSimulate(phys) + end + end +end + +function ENT:PhysicsSimulate( phys, deltatime ) + if self.calcforce then + self:CalcForce(phys) + self:SetOn(self.mul ~= 0 and ( (self.bidir) and (math.abs(self.mul) > 0.01) and (math.abs(self.mul) > self.force_min) ) or ( (self.mul > 0.01) and (self.mul > self.force_min) )) + self:ShowOutput() + end + + if (!self:IsOn()) then return SIM_NOTHING end + if (self:IsPlayerHolding()) then return SIM_NOTHING end + + if (self:WaterLevel() > 0) then + if (not self.uwater) then + self:SetEffect("none") + return SIM_NOTHING + end + + if (self.uweffect == "same") then + self:SetEffect(self.oweffect) + else + self:SetEffect(self.uweffect) + end + else + if (not self.owater) then + self:SetEffect("none") + return SIM_NOTHING + end + + self:SetEffect(self.oweffect) + end + + return self.ForceAngular, self.ForceLinear, SIM_GLOBAL_ACCELERATION +end + +function ENT:ShowOutput() + local mode = self:GetMode() + self:SetOverlayText(string.format("Force Mul: %.2f\nInput: %.2f\nForce Applied: %.2f\nMode: %s", + self.force, + self.mul, + self:IsOn() and math.min( self.force * self.mul, self.force_max ) or 0, + (mode == 0 and "XYZ Local") or (mode == 1 and "XYZ World") or (mode == 2 and "XY Local, Z World") + )) +end + +duplicator.RegisterEntityClass("gmod_wire_vectorthruster", WireLib.MakeWireEnt, "Data", "force", "force_min", "force_max", "oweffect", "uweffect", "owater", "uwater", "bidir", "soundname", "mode", "angleinputs", "lengthismul") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_vehicle.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_vehicle.lua new file mode 100644 index 0000000..9830a43 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_vehicle.lua @@ -0,0 +1,72 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Vehicle Controller" +ENT.WireDebugName = "Vehicle Controller" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Inputs = Wire_CreateInputs( self, { "Throttle", "Steering", "Handbrake", "Engine", "Lock" } ) +end + +function ENT:LinkEnt( pod ) + pod = WireLib.GetClosestRealVehicle(pod,self:GetPos(),self:GetPlayer()) + + if not IsValid(pod) or not pod:IsVehicle() then return false, "Must link to a vehicle" end + self.Vehicle = pod + WireLib.SendMarks(self, {pod}) + return true +end +function ENT:UnlinkEnt() + self.Vehicle = nil + WireLib.SendMarks(self, {}) + return true +end + +function ENT:TriggerInput(iname, value) + if not IsValid(self.Vehicle) then return end + if (iname == "Throttle") then + self.Throttle = value + elseif (iname == "Steering") then + self.Steering = value + elseif (iname == "Handbrake") then + self.Vehicle:Fire("handbrake"..(value~=0 and "on" or "off"), 1, 0) + elseif (iname == "Engine") then + self.Vehicle:Fire("turn"..(value~=0 and "on" or "off"), 1, 0) + if value~=0 then self.Vehicle:Fire("handbrakeoff", 1, 0) end + elseif (iname == "Lock") then + self.Vehicle:Fire((value~=0 and "" or "un").."lock", 1, 0) + end +end + +function ENT:Think() + if IsValid(self.Vehicle) then + local delta = CurTime()%1/1000 -- A miniscule constant change + if self.Steering then self.Vehicle:Fire("steer", self.Steering+delta, 0) end + if self.Throttle then self.Vehicle:Fire("throttle",self.Throttle+delta, 0) end + end + self:NextThink(CurTime()) + return true +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + + if (self.Vehicle) and (self.Vehicle:IsValid()) then + info.Vehicle = self.Vehicle:EntIndex() + end + + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + self.Vehicle = GetEntByID(info.Vehicle) +end + +duplicator.RegisterEntityClass("gmod_wire_vehicle", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_watersensor.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_watersensor.lua new file mode 100644 index 0000000..b069fd0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_watersensor.lua @@ -0,0 +1,30 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Water Sensor" +ENT.WireDebugName = "Water Sensor" + +if CLIENT then return end -- No more client + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Outputs = Wire_CreateOutputs(self, {"Out"}) +end + +function ENT:ShowOutput() + self:SetOverlayText( (self:WaterLevel()>0) and "Submerged" or "Above Water" ) +end + +function ENT:Think() + BaseClass.Think(self) + if(self:WaterLevel()>0)then + Wire_TriggerOutput(self,"Out",1) + else + Wire_TriggerOutput(self,"Out",0) + end + self:ShowOutput() + self:NextThink(CurTime()+0.125) +end + +duplicator.RegisterEntityClass("gmod_wire_watersensor", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_waypoint.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_waypoint.lua new file mode 100644 index 0000000..7b43b69 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_waypoint.lua @@ -0,0 +1,101 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Waypoint Beacon" +ENT.WireDebugName = "Waypoint" + +function ENT:GetNextWaypoint() + return self:GetNWEntity("NextWaypoint") +end + +if CLIENT then + local physBeamMat = Material("cable/physbeam") + function ENT:Draw() + BaseClass.Draw(self) + + local nextWP = self:GetNextWaypoint() + if IsValid(nextWP) and (LocalPlayer():GetEyeTrace().Entity == self) and (EyePos():Distance(self:GetPos()) < 4096) then + local start = self:GetPos() + local endpos = nextWP:GetPos() + local scroll = -3*CurTime() + + render.SetMaterial(physBeamMat) + render.DrawBeam(start, endpos, 8, scroll, (endpos-start):Length()/10+scroll, Color(255, 255, 255, 192)) + end + end + + return -- No more client +end + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + self.Outputs = Wire_CreateOutputs(self, { "Waypoints [ARRAY]" }) + + self.waypoints = { self } + Wire_TriggerOutput(self, "Waypoints", self.waypoints) +end + +function ENT:Setup(range) + self.range = range +end + +function ENT:GetBeaconPos(sensor) + if ((sensor:GetPos()-self:GetPos()):Length() < self.range) then + sensor:LinkEnt(self:GetNextWaypoint()) + end + + return self:GetPos() +end +function ENT:GetBeaconVelocity(sensor) + return self:GetVelocity() +end + +function ENT:SetNextWaypoint(wp) + local SavedNextWaypoint = self:GetNextWaypoint() + + if SavedNextWaypoint:IsValid() and SavedNextWaypoint ~= wp then + self:SetNWEntity("NextWaypoint", wp) + + local waypoints = self.waypoints + for _,ent in ipairs(waypoints) do + ent.waypoints = { ent } + end + + for _,ent in ipairs(waypoints) do + ent:SetNextWaypoint(ent:GetNextWaypoint()) + end + + return + end + + self:SetNWEntity("NextWaypoint", wp) + + if table.HasValue(self.waypoints, wp) then return end + + table.Add(self.waypoints, wp.waypoints) + for _,ent in ipairs(self.waypoints) do + ent.waypoints = self.waypoints + Wire_TriggerOutput(ent, "Waypoints", ent.waypoints) + end +end + +function ENT:OnRemove() + -- empty tables on all ents from current table and update all tables + + local waypoints = self.waypoints + for _,ent in ipairs(waypoints) do + ent.waypoints = { ent } + end + + for _,ent in ipairs(waypoints) do + if ent == self or ent:GetNextWaypoint() == self then + ent:SetNextWaypoint(NULL) + elseif ent:IsValid() then + ent:SetNextWaypoint(ent:GetNextWaypoint()) + end + end +end + +duplicator.RegisterEntityClass("gmod_wire_waypoint", WireLib.MakeWireEnt, "Data", "range") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_weight.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_weight.lua new file mode 100644 index 0000000..5dccdab --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_weight.lua @@ -0,0 +1,44 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Weight" +ENT.WireDebugName = "Weight" + +if CLIENT then return end -- No more client + +local MODEL = Model("models/props_interiors/pot01a.mdl") + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self.Inputs = Wire_CreateInputs(self,{"Weight"}) + self.Outputs = Wire_CreateOutputs(self,{"Weight"}) + self:ShowOutput(self:GetPhysicsObject():GetMass()) +end + +function ENT:TriggerInput(iname,value) + if(value>0)then + value = math.Clamp(value, 0.001, 50000) + local phys = self:GetPhysicsObject() + if ( phys:IsValid() ) then + phys:SetMass(value) + phys:Wake() + self:ShowOutput(value) + Wire_TriggerOutput(self,"Weight",value) + end + end + return true +end + +function ENT:Think() + BaseClass.Think(self) +end + +function ENT:Setup() +end + +function ENT:ShowOutput(value) + self:SetOverlayText( "Weight: "..tostring(value) ) +end + +duplicator.RegisterEntityClass("gmod_wire_weight", WireLib.MakeWireEnt, "Data") diff --git a/garrysmod/addons/feature-wire/lua/entities/gmod_wire_wheel.lua b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_wheel.lua new file mode 100644 index 0000000..fd7ff74 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/gmod_wire_wheel.lua @@ -0,0 +1,201 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Wheel" +ENT.WireDebugName = "Wheel" + +if CLIENT then return end -- No more client + +-- As motor constraints can't have their initial torque updated, +-- we always create it with 1000 initial torque (needs to be > friction) and then Scale it with a multiplier +local WHEEL_BASE_TORQUE = 1000 + +function ENT:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self.BaseTorque = 1 + self.Breaking = 0 + self.SpeedMod = 0 + self.Go = 0 + + self.Inputs = Wire_CreateInputs(self, { "A: Go", "B: Break", "C: SpeedMod" }) +end + +function ENT:Setup(fwd, bck, stop, torque, direction, axis) + self.fwd = fwd + self.bck = bck + self.stop = stop + if torque then self:SetTorque(math.max(1, torque)) end + if direction then self:SetDirection( direction ) end + if axis then self.Axis = axis end + + self:UpdateOverlayText() +end + +function ENT:UpdateOverlayText(speed) + local motor = self:GetMotor() + local friction = 0 + if IsValid(motor) then friction = motor.friction end + self:SetOverlayText( + "Torque: " .. math.floor( self.BaseTorque ) .. + "\nFriction: " .. friction .. + "\nSpeed: " .. (speed or 0) .. + "\nBreak: " .. self.Breaking .. + "\nSpeedMod: " .. math.floor( self.SpeedMod * 100 ) .. "%" ) +end + +function ENT:SetAxis( vec ) + self.Axis = self:GetPos() + vec * 512 + self.Axis = self:NearestPoint( self.Axis ) + self.Axis = self:WorldToLocal( self.Axis ) +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:SetMotor( Motor ) + self.Motor = Motor + self:UpdateOverlayText() +end + +function ENT:GetMotor() + if not self.Motor then + self.Motor = constraint.FindConstraintEntity( self, "Motor" ) + if not IsValid(self.Motor) then + self.Motor = nil + end + end + return self.Motor +end + +function ENT:SetDirection( dir ) + self:SetNWInt( 1, dir ) + self.direction = dir +end + +function ENT:Forward( mul ) + if not self:IsValid() then return false end + local Motor = self:GetMotor() + if not IsValid(Motor) then return false end + + mul = mul or 1 + local mdir = Motor.direction + local Speed = mdir * mul * (self.BaseTorque / WHEEL_BASE_TORQUE) * (1 + self.SpeedMod) + + self:UpdateOverlayText(mul ~= 0 and (mdir * mul * (1 + self.SpeedMod)) or 0) + + Motor:Fire( "Scale", Speed, 0 ) + Motor:GetTable().forcescale = Speed + Motor:Fire( "Activate", "" , 0 ) + + return true +end + +function ENT:TriggerInput(iname, value) + if (iname == "A: Go") then + if ( value == self.fwd ) then self.Go = 1 + elseif ( value == self.bck ) then self.Go = -1 + elseif ( value == self.stop ) then self.Go =0 end + elseif (iname == "B: Break") then + self.Breaking = value + elseif (iname == "C: SpeedMod") then + self.SpeedMod = (value / 100) + end + self:Forward( self.Go ) +end + + +--[[--------------------------------------------------------- + Name: PhysicsUpdate + Desc: happy fun time breaking function +---------------------------------------------------------]] +function ENT:PhysicsUpdate( physobj ) + local vel = physobj:GetVelocity() + + if (self.Breaking > 0) then -- to prevent badness + if (self.Breaking >= 100) then --100% breaking!!! + vel.x = 0 --full stop! + vel.y = 0 + else + vel.x = vel.x * ((100.0 - self.Breaking)/100.0) + vel.y = vel.y * ((100.0 - self.Breaking)/100.0) + end + else + return -- physobj:SetVelocity(physobj:GetVelocity()) will create constant acceleration + end + + physobj:SetVelocity(vel) +end + +function ENT:SetTorque( torque ) + self.BaseTorque = torque + + local Motor = self:GetMotor() + if not IsValid(Motor) then return end + Motor:Fire( "Scale", Motor:GetTable().direction * Motor:GetTable().forcescale * (torque / WHEEL_BASE_TORQUE), 0 ) + + self:UpdateOverlayText() +end + +--[[--------------------------------------------------------- + Creates the direction arrows on the wheel +---------------------------------------------------------]] +function ENT:DoDirectionEffect() + local Motor = self:GetMotor() + if not IsValid(Motor) then return end + + local effectdata = EffectData() + effectdata:SetOrigin( self.Axis ) + effectdata:SetEntity( self ) + effectdata:SetScale( Motor.direction ) + util.Effect( "wheel_indicator", effectdata, true, true ) +end + +--[[--------------------------------------------------------- + Reverse the wheel direction when a player uses the wheel +---------------------------------------------------------]] +function ENT:Use( activator, caller, type, value ) + local Motor = self:GetMotor() + local Owner = self:GetPlayer() + + if (Motor and (Owner == nil or Owner == activator)) then + if (Motor:GetTable().direction == 1) then + Motor:GetTable().direction = -1 + else + Motor:GetTable().direction = 1 + end + + Motor:Fire( "Scale", Motor:GetTable().direction * Motor:GetTable().forcescale * (self.BaseTorque / WHEEL_BASE_TORQUE), 0 ) + self:SetDirection( Motor:GetTable().direction ) + + self:DoDirectionEffect() + end +end + +duplicator.RegisterEntityClass("gmod_wire_wheel", WireLib.MakeWireEnt, "Data", "fwd", "bck", "stop", "BaseTorque", "direction", "Axis") + +function ENT:SetWheelBase(Base) + Base:DeleteOnRemove( self ) + self.Base = Base +end + +function ENT:BuildDupeInfo() + local info = BaseClass.BuildDupeInfo(self) or {} + if IsValid(self.Base) then + info.Base = self.Base:EntIndex() + end + return info +end + +function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID) + BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID) + + local Base = GetEntByID(info.Base) + if IsValid(Base) then + self:SetWheelBase(Base) + end + self:UpdateOverlayText() +end diff --git a/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/convert.lua b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/convert.lua new file mode 100644 index 0000000..0180b4d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/convert.lua @@ -0,0 +1,143 @@ +-- This part is for converting between map and wire + +-- Per type converting functions for +-- converting from map inputs to wire outputs. (String to Value) +local MapToWireTypes = { + [0] = {"NORMAL", function(str) -- Number, default + return tonumber(str) or 0 + end}, + + [1] = {"NORMAL", function(self, ent, I) -- switches between 0 and 1 each call, useful for toggling. + if (!IsValid(self) or !IsValid(ent) or !I) then return 0 end + + self.WireOutputToggle = self.WireOutputToggle or {} + self.WireOutputToggle[ent] = self.WireOutputToggle[ent] or {} + self.WireOutputToggle[ent][I] = !self.WireOutputToggle[ent][I] + + return self.WireOutputToggle[ent][I] and 1 or 0 + end, true}, + + [2] = {"STRING", function(str) -- String + return str or "" + end}, + + [3] = {"VECTOR2", function(str) -- 2D Vector + local x, y = unpack(string.Explode(" ", str or "")) + x = tonumber(x) or 0 + y = tonumber(y) or 0 + + return {x, y} + end}, + + [4] = {"VECTOR", function(str) -- 3D Vector + local x, y, z = unpack(string.Explode(" ", str or "")) + x = tonumber(x) or 0 + y = tonumber(y) or 0 + z = tonumber(z) or 0 + + return Vector(x, y, z) + end}, + + [5] = {"VECTOR4", function(str) -- 4D Vector + local x, y, z, w = unpack(string.Explode(" ", str or "")) + x = tonumber(x) or 0 + y = tonumber(y) or 0 + z = tonumber(z) or 0 + w = tonumber(w) or 0 + + return {x, y, z, w} + end}, + + [6] = {"ANGLE", function(str) -- Angle + local p, y, r = unpack(string.Explode(" ", str or "")) + p = tonumber(p) or 0 + y = tonumber(y) or 0 + r = tonumber(r) or 0 + + return Angle(p, y, r) + end}, + + [7] = {"ENTITY", function(val) -- Entity + return Entity(tonumber(val) or 0) or NULL + end}, + + [8] = {"ARRAY", function(str) -- Array/Table + return string.Explode(" ", str or "") + end}, +} + +-- Per type converting functions for +-- converting from wire inputs to map outputs. (Value to String) +local WireToMapTypes = { + [0] = {"NORMAL", function(val) -- Number, default + return tostring(val or 0) + end}, + + [1] = {"NORMAL", function(val) -- Return a boolean, 0 = false, 1 = true, useful for toggling. + return (tonumber(val) or 0) > 0 + end, true}, + + [2] = {"STRING", function(val) -- String + return val or "" + end}, + + [3] = {"VECTOR2", function(val) -- 2D Vector + val = val or {0, 0} + + local x = math.Round(val[1] or 0) + local y = math.Round(val[2] or 0) + + return x.." "..y + end}, + + [4] = {"VECTOR", function(val) -- 3D Vector + val = val or Vector(0, 0, 0) + + local x = math.Round(val.x or 0) + local y = math.Round(val.y or 0) + local z = math.Round(val.z or 0) + + return x.." "..y.." "..z + end}, + + [5] = {"VECTOR4", function(val) --4D Vector + val = val or {0, 0, 0, 0} + + local x = math.Round(val[1] or 0) + local y = math.Round(val[2] or 0) + local z = math.Round(val[3] or 0) + local w = math.Round(val[4] or 0) + + return x.." "..y.." "..z.." "..w + end}, + + [6] = {"ANGLE", function(val) -- Angle + val = val or Angle(0, 0, 0) + + local p = math.Round(val.p or 0) + local y = math.Round(val.y or 0) + local r = math.Round(val.r or 0) + + return p.." "..y.." "..r + end}, + + [7] = {"ENTITY", function(val) -- Entity + if (!IsValid(val)) then return "0" end + + return tostring(val:EntIndex()) + end}, + + [8] = {"ARRAY", function(val) -- Array/Table + return table.concat(val or {}, " ") + end}, +} + +-- Converting functions +function ENT:Convert_MapToWire(n) + local typetab = MapToWireTypes[n or 0] or MapToWireTypes[0] + return typetab[1], typetab[2], typetab[3] +end +function ENT:Convert_WireToMap(n) + local typetab = WireToMapTypes[n or 0] or WireToMapTypes[0] + return typetab[1], typetab[2], typetab[3] +end diff --git a/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/entitycontrol.lua b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/entitycontrol.lua new file mode 100644 index 0000000..b54697f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/entitycontrol.lua @@ -0,0 +1,254 @@ +-- This part of the wire map interface entity controls +-- the adding and removing of its in-/outputs entities. + + +-- The removing function +-- Its for removing all the wiremod stuff from unused entities. +local function RemoveWire(Entity, SendToCL) + if (!IsValid(Entity)) then return end + + Wire_Remove(Entity, !SendToCL) + + local self = Entity._WireMapInterfaceEnt + if (IsValid(self)) then + self.WireEntsCount = math.max(self.WireEntsCount - 1, 0) + if (self.WireEnts) then + self.WireEnts[Entity] = nil + end + if (self.WireOutputToggle) then + self.WireOutputToggle[Entity] = nil + end + if (self.Wired) then + self.Wired[Entity] = nil + end + end + + if (!SendToCL) then return end + + Entity:_RemoveOverrides() + WireLib._RemoveWire(Entity:EntIndex(), true) -- Remove entity from the list, so it doesn't count as a wire able entity anymore. + + umsg.Start("WireMapInterfaceEnt") + umsg.Entity(Entity) + umsg.Char(-1) + umsg.End() + + Entity:RemoveCallOnRemove("WireMapInterface_OnRemove") + if (table.IsEmpty(Entity.OnDieFunctions)) then + Entity.OnDieFunctions = nil + end +end + +function ENT:Timedpairs(name, tab, steps, cb, endcb, ...) + if (table.IsEmpty(tab)) then return end + + local name = self:EntIndex().."_"..tostring(name) + self.TimedpairsTable = self.TimedpairsTable or {} + + WireLib.Timedpairs(name, tab, steps, function(...) + + if (IsValid(self)) then + self.TimedpairsTable[name] = true + end + return cb(...) + + end, function(...) + + if (IsValid(self)) then + self.TimedpairsTable[name] = nil + end + if (endcb) then + return endcb(...) + end + + end, ...) +end + +local function CallOnEnd(self, AddedEnts) + if (!IsValid(self)) then return end + + self.WirePortsChanged = true + self:GiveWireInterfeceClient(nil, AddedEnts) + self:TriggerOutput("onwireentscreated", self) + self:TriggerOutput("onwireentsready", self) +end + +function ENT:GiveWireInterfece(EntsToAdd) + if (!EntsToAdd) then return end + if (table.IsEmpty(EntsToAdd) or !self.WirePortsChanged) then return end + local AddedEnts = {} + self:UpdateData() + + self:Timedpairs("WireMapInterface_Adding", EntsToAdd, 1, function(obj1, obj2, self) + if (!IsValid(self)) then return false end -- Stop loop when the entity gets removed. + if (self.WirePortsChanged) then + self:TriggerOutput("onwireentsstartchanging", self) + end + self.WirePortsChanged = nil + + local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + + local Ent, Func = self:AddSingleEntity(Entity, CallOnEnd, AddedEnts) + if (Ent == "limid_exceeded") then return false end -- Stop loop when maximum got exceeded + if (!IsValid(Ent) or !Func) then return end + + AddedEnts[Ent] = Func + end, function(k, v, self) CallOnEnd(self, AddedEnts) end, self) +end + +function ENT:GiveWireInterfeceClient(ply, EntsToAdd) + if (!self.WireEnts) then return end + + self:Timedpairs((IsValid(ply) and (ply:EntIndex().."") or "").."WireMapInterface_Adding_CL", EntsToAdd or self.WireEnts, 1, function(Entity, Func, self) + if (!IsValid(self)) then return false end -- Stop loop when the entity gets removed. + if (!IsValid(Entity)) then return end + if (!self:IsWireableEntity(Entity)) then return end + + Func(self, Entity, ply, !IsValid(ply)) + end, nil, self) +end + + +-- Entity add functions +function ENT:AddEntitiesByName(Name) + Name = tostring(Name or "") + if (Name == "") then return end + + self:AddEntitiesByTable(ents.FindByName(Name)) +end + +function ENT:AddEntitiesByTable(Table) + if (!Table) then return end + + self:GiveWireInterfece(Table) +end + +local function AddSingleEntityCL(self, Entity, ply, SendToAll) + if (!IsValid(Entity)) then return end + if (!IsValid(self)) then return end + if (!self.WireEnts[Entity]) then return end + if (!SendToAll and !IsValid(ply)) then return end + + if (SendToAll) then + umsg.Start("WireMapInterfaceEnt") + else + umsg.Start("WireMapInterfaceEnt", ply) + end + umsg.Entity(Entity) + umsg.Char(self.flags % 64) + -- Allow valid spawnflags only. + umsg.End() +end + +function ENT:AddSingleEntity(Entity, callOnEnd, AddedEnts) + if (!IsValid(Entity)) then return end + if (!self:IsWireableEntity(Entity)) then return end + if (!self:CheckEntLimid(callOnEnd, AddedEnts)) then return "limid_exceeded" end + + if (IsValid(Entity._WireMapInterfaceEnt)) then + RemoveWire(Entity, true) + end + + self:OverrideEnt(Entity) + Entity:CallOnRemove("WireMapInterface_OnRemove", RemoveWire) + + if (self.Inames) then + Entity.Inputs = WireLib.CreateSpecialInputs(Entity, self.Inames, self.Itypes, self.Idescs) + end + if (self.Onames) then + Entity.Outputs = WireLib.CreateSpecialOutputs(Entity, self.Onames, self.Otypes, self.Odescs) + end + + self.WireEnts = self.WireEnts or {} + self.WireEnts[Entity] = AddSingleEntityCL + + return Entity, AddSingleEntityCL +end + + +-- Entity remove functions +function ENT:RemoveAllEntities(callback) + for name, _ in pairs(self.TimedpairsTable or {}) do + WireLib.TimedpairsStop(name) + end + + self.WirePortsChanged = true + + self:RemoveEntitiesByTable(self.WireEnts, callback) + self.WireEntsCount = 0 +end + + +function ENT:RemoveEntitiesByName(Name, callback) + Name = tostring(Name or "") + if (Name == "") then return end + + self:RemoveEntitiesByTable(ents.FindByName(Name), callback) +end + +function ENT:RemoveEntitiesByTable(Table, callback) + if (!Table) then return end + if (table.IsEmpty(Table) or !self.WirePortsChanged) then return end + + local Removed = nil + self:Timedpairs("WireMapInterface_Removing", Table, 1, function(obj1, obj2, self) + local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + + if (!IsValid(Entity)) then return end + if (!IsValid(Entity._WireMapInterfaceEnt)) then return end + if (Entity._WireMapInterfaceEnt ~= self) then return end + if (self and self.WirePortsChanged) then + self:TriggerOutput("onwireentsstartchanging", self) + self.WirePortsChanged = nil + end + + RemoveWire(Entity, true) + Removed = true + end, + function(k, v, self, callback) + if (!IsValid(self)) then return end + self.WirePortsChanged = true + + if (callback) then + callback(self, Removed) + end + + if (!Removed) then return end + self:TriggerOutput("onwireentsremoved", self) + self:TriggerOutput("onwireentsready", self) + end, self, callback) + +end + +function ENT:RemoveSingleEntity(Entity) + if (!IsValid(Entity)) then return end + if (!IsValid(Entity._WireMapInterfaceEnt)) then return end + if (Entity._WireMapInterfaceEnt ~= self) then return end + + RemoveWire(Entity, true) + self:TriggerOutput("onwireentsremoved", self) + self:TriggerOutput("onwireentsready", self) +end + + + +function ENT:GetWiredEntities() + return table.Copy(self.WireEnts or {}) +end + +function ENT:SetWiredEntities(Table) + if (!Table) then return end + + local Ents = {} + local Count = 0 + for obj1, obj2 in pairs(Table) do -- Filter invalid stuff out! + local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + if (IsValid(Entity)) then + Count = Count + 1 + Ents[Count] = Entity + end + end + self:RemoveAllEntities(function(self) + self:AddEntitiesByTable(Ents) + end) +end diff --git a/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/entityoverride.lua b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/entityoverride.lua new file mode 100644 index 0000000..7527398 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/entityoverride.lua @@ -0,0 +1,151 @@ +-- Stuff that the entity gets for its wire stuff. + +local WIREENT = {} +-- Trigger wire input +function WIREENT:TriggerInput(name, value, ...) + if (!name or (name == "") or !value) then return end + + local Entity = self._WireMapInterfaceEnt + if (!IsValid(Entity)) then return end + if (!Entity.TriggerWireInput) then return end + + local Input = self.Inputs[name] or {} + Entity:TriggerWireInput(name, value, (IsValid(Input.Src) == true), self, ...) +end + +-- Copied from the gmod base entity, it's changed to work with wire ports. +-- It's only changed for this entity. +-- This function is used to store an output. +function WIREENT:StoreOutput(name, info) + local rawData = string.Explode(",", info) + + local Output = {} + Output.entities = rawData[1] or "" + Output.input = rawData[2] or "" + Output.param = rawData[3] or "" + Output.delay = tonumber(rawData[4]) or 0 + Output.times = tonumber(rawData[5]) or -1 + + self._OutputsToMap = self._OutputsToMap or {} + -- This table (self._OutputsToMap) got renamed, + -- because it was called self.Outputs, + -- that caused conflicts with wiremod! + + self._OutputsToMap[name] = self._OutputsToMap[name] or {} + table.insert(self._OutputsToMap[name], Output) +end + +-- Nice helper function, this does all the work. +-- Returns false if the output should be removed from the list. +local function FireSingleOutput(output, this, activator) + if (output.times == 0) then return false end + local delay = output.delay + local entitiesToFire = {} + + if (output.entities == "!activator") then + entitiesToFire = {activator} + elseif (output.entities == "!self") then + entitiesToFire = {this} + elseif (output.entities == "!player") then + entitiesToFire = player.GetAll() + else + entitiesToFire = ents.FindByName(output.entities) + end + + for _,ent in pairs(entitiesToFire) do + if (IsValid(ent)) then + if (delay == 0) then + ent:Input(output.input, activator, this, output.param) + else + timer.Simple(delay, function() + if (IsValid(ent)) then + ent:Input(output.input, activator, this, output.param) + end + end) + end + end + end + + if (output.times ~= -1) then + output.times = output.times - 1 + end + + return ((output.times > 0) or (output.times == -1)) +end + +-- This function is used to store an output. +function WIREENT:TriggerOutput(name, activator) + if (!self._OutputsToMap) then return end + local OutputsToMap = self._OutputsToMap[name] + if (!OutputsToMap) then return end + + for idx,op in pairs(OutputsToMap) do + if (!FireSingleOutput(op, self, activator)) then + self._OutputsToMap[name][idx] = nil + end + end +end + + +-- Remove its overrides +function WIREENT:_RemoveOverrides() + for k, v in pairs(self._Overrides_WireMapInterfaceEnt or {}) do + self[k] = v + end + self._Overrides_WireMapInterfaceEnt = nil + + for k, _ in pairs(self._Added_WireMapInterfaceEnt or {}) do + self[k] = nil + end + self._Added_WireMapInterfaceEnt = nil + + for key, value in pairs(self._Settings_WireMapInterfaceEnt or {}) do + if (!value or (value == 0) or (value == "")) then + self[key] = nil + else + self[key] = value + end + end + self._Settings_WireMapInterfaceEnt = nil + + if (self.Outputs) then + table.Merge(self.Outputs, self._OutputsToMap) + end + self._OutputsToMap = nil + + self._WireMapInterfaceEnt = nil + self._RemoveOverride = nil +end + + +-- Adds its overrides +function ENT:OverrideEnt(Entity) + Entity._Overrides_WireMapInterfaceEnt = Entity._Overrides_WireMapInterfaceEnt or {} + Entity._Added_WireMapInterfaceEnt = Entity._Added_WireMapInterfaceEnt or {} + + for k, v in pairs(WIREENT) do + if ((Entity[k] == nil) or (k == "_Overrides_WireMapInterfaceEnt") or (k == "_Added_WireMapInterfaceEnt") or (k == "_Settings_WireMapInterfaceEnt")) then + Entity._Overrides_WireMapInterfaceEnt[k] = nil + Entity._Added_WireMapInterfaceEnt[k] = true + else + Entity._Overrides_WireMapInterfaceEnt[k] = v + Entity._Added_WireMapInterfaceEnt[k] = nil + end + Entity[k] = v + end + + + Entity._Settings_WireMapInterfaceEnt = Entity._Settings_WireMapInterfaceEnt or {} + + if (bit.band(self.flags, 1) > 0) then -- Protect in-/output entities from non-wire tools + Entity._Settings_WireMapInterfaceEnt.m_tblToolsAllowed = Entity.m_tblToolsAllowed or false + Entity.m_tblToolsAllowed = {"wire", "wire_adv", "wire_debugger", "wire_wirelink", "gui_wiring", "multi_wire"} + end + + if (bit.band(self.flags, 2) > 0) then -- Protect in-/output entities from the physgun + Entity._Settings_WireMapInterfaceEnt.PhysgunDisabled = Entity.PhysgunDisabled or false + Entity.PhysgunDisabled = true + end + + Entity._WireMapInterfaceEnt = self +end diff --git a/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/init.lua b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/init.lua new file mode 100644 index 0000000..6837995 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/init.lua @@ -0,0 +1,465 @@ +--[[ +This is the wire map interface entity by Grocel. (info_wiremapinterface) + +This point entity allows you to give other entities wire in-/outputs. +Those wire ports allows you to control thinks on the map with Wiremod +or to let the map return thinks to wire outputs. + +It supports many datatypes and custom lua codes. +A lua code is run when its input triggers. +It has special globals: + WIRE_NAME = Input name + WIRE_VALUE = Input value + WIRE_WIRED = Is the input wired? + WIRE_CALLER = This entity + WIRE_ACTIVATOR = The entity that has the input + +Keep in mind that you have to know what you do +and that you have to activate spawnflag 8 to make it work. +Spawnflag 8 is better known as "Run given Lua codes (For advanced users!)" in the Hammer Editor. + +Please don't change thinks unless you know what you do. You may break maps if do something wrong. +]] + +include("convert.lua") +include("entitycontrol.lua") +include("entityoverride.lua") +include("output.lua") + +local ALLOW_INTERFACE = CreateConVar("sv_wire_mapinterface", "1", {FCVAR_NOTIFY, FCVAR_ARCHIVE, FCVAR_GAMEDLL}, "Aktivate or deaktivate the wire map interface. Default: 1") + + +local Ents = {} +hook.Add("PlayerInitialSpawn", "WireMapInterface_PlayerInitialSpawn", function(ply) + if (!IsValid(ply)) then return end + for Ent, time in ipairs(Ents) do + if (!IsValid(Ent)) then break end + + timer.Simple(time + 0.1, function() + if (!IsValid(ply)) then return end + if (!IsValid(Ent)) then return end + if (!Ent.GiveWireInterfeceClient) then return end + + Ent:GiveWireInterfeceClient(ply) + end) + end +end) + +-- This is a point entity +ENT.Base = "base_point" +ENT.Type = "point" + +local MAX_PORTS = 256 -- This entity supports more than the 8 ports you see in the editor. This value is the port limit. +local MAX_ENTITIES = 32 -- The maximum number of entities per interface entitiy that can get wire ports +local MIN_TRIGGER_TIME = 0.01 -- Minimum triggering Time for in- and outputs. + +-- This checks if you can give an entity wiremod abilities +function ENT:IsWireableEntity(Entity) + if (!IsValid(Entity)) then return false end -- No interface for invalid entities! + if (IsValid(Entity._WireMapInterfaceEnt) and (Entity._WireMapInterfaceEnt ~= self)) then return false end -- Only one interface per entity! + if (!IsValid(Entity._WireMapInterfaceEnt) and (WireLib.HasPorts(Entity) or Entity.IsWire or Entity.Inputs or Entity.Outputs)) then return false end -- Don't destroy wiremod entites! + + if (Entity:IsWorld()) then return false end -- No interface for the worldspawn! + if (Entity:IsVehicle()) then return false end -- No interface for vehicles! + if (Entity:IsNPC()) then return false end -- No interface for NPCs! + if (Entity:IsPlayer()) then return false end -- No interface for players! + if (Entity:IsWeapon()) then return false end -- No interface for weapons! + if (string.match(Entity:GetClass(), "^(item_[%w_]+)")) then return false end -- No interface for items! + if (Entity:IsConstraint()) then return false end -- No interface for constraints! + + if (Entity:GetPhysicsObjectCount() > 1) then return false end -- No interface for ragdolls! + if (IsValid(Entity:GetPhysicsObject())) then return true end -- Everything with a single physics object can get an interface! + + return true +end + +function ENT:CheckEntLimid(CallOnMax, ...) + if (self.WireEntsCount > MAX_ENTITIES) then + MsgN(self.ErrorName..": Warning, to many wire entities linked!") + if (CallOnMax) then + CallOnMax(self, ...) + end + return false + end + self.WireEntsCount = self.WireEntsCount + 1 + return true +end + +-- Run the given lua code +local function RunLua(I, name, value, wired, self, Ent) + local lua = self.Ins[I].lua or "" + if ((lua == "") or !self.RunLuaCode) then return end + + local func = CompileString(lua, self.ErrorName.." (Input "..I..")", false) + local Err + if isfunction(func) then + -- Globals + WIRE_NAME = name -- Input name + WIRE_VALUE = value -- Input value + WIRE_WIRED = wired -- Is the input wired? + WIRE_CALLER = self -- This entity + WIRE_ACTIVATOR = Ent -- The entity that has the input + + local status, err = xpcall(func, debug.traceback) + if (!status) then + Err = err or "" + end + + -- Remove globals + WIRE_NAME = nil + WIRE_VALUE = nil + WIRE_WIRED = nil + WIRE_CALLER = nil + WIRE_ACTIVATOR = nil + else + Err = func + end + + if (Err and (Err ~= "")) then + ErrorNoHalt(Err.."\n") + end +end + +-- Wire input +function ENT:TriggerWireInput(name, value, wired, Ent) + if (!WireAddon) then return end + if (!IsValid(Ent)) then return end + if ((!self.Active or !ALLOW_INTERFACE:GetBool()) and wired) then + self.SavedIn = self.SavedIn or {} + self.SavedIn[name] = {value, wired, Ent} + + return + end + + self.Wired = self.Wired or {} + self.Wired[Ent] = self.Wired[Ent] or {} + local WireRemoved = ((self.Wired[Ent][name] or false) ~= wired) and !wired + self.Wired[Ent][name] = wired + + self.Timer = self.Timer or {} + self.Timer.In = self.Timer.In or {} + if (((CurTime() - (self.Timer.In[name] or 0)) < (self.min_trigger_time or MIN_TRIGGER_TIME)) and !WireRemoved) then return end + self.Timer.In[name] = CurTime() + + self.Data = self.Data or {} + self.Data.In = self.Data.In or {} + if ((self.Data.In[name] == value) and !WireRemoved) then return end + self.Data.In[name] = value + + local I = self.InsIDs[name] or 0 + if ((I > 0) and (I <= MAX_PORTS) and self.InsExist[I]) then + local _, Convert, Toggle = self:Convert_WireToMap(self.Ins[I].type) + if (!Convert) then return end + local Output = "onwireinput"..I + + -- Map output + if (!wired) then + if (WireRemoved) then + if (!Toggle) then + self:TriggerOutput(Output, Ent, Convert(value)) + end + self:TriggerOutput("onresetwireinput"..I, Ent) + + RunLua(I, name, value, wired, self, Ent) + end + else + if (Toggle) then + if (Convert(value)) then + self:TriggerOutput(Output, Ent) + + RunLua(I, name, value, wired, self, Ent) + end + else + self:TriggerOutput(Output, Ent, Convert(value)) + + RunLua(I, name, value, wired, self, Ent) + end + end + end +end + +-- Wire output +function ENT:TriggerWireOutput(ent, i, val) + if (!IsValid(ent)) then return false end + + local OutputName = self.Outs[i].name or "" + if (OutputName == "") then return false end + + local _, Convert, Toggle = self:Convert_MapToWire(self.Outs[i].type) + if (!Convert) then return false end + + if (Toggle) then + Wire_TriggerOutput(ent, OutputName, Convert(self, ent, i)) + else + Wire_TriggerOutput(ent, OutputName, Convert(val)) + end + return true +end + +-- Map input +function ENT:AcceptInput(name, activator, caller, data) + if (!WireAddon) then return false end + name = string.lower(tostring(name or "")) + if (name == "") then return false end + + if (name == "activate") then + self.Active = true + return true + end + + if (name == "deactivate") then + self.Active = false + return true + end + + if (name == "toggle") then + self.Active = !self.Active + return true + end + + if (self.Active and ALLOW_INTERFACE:GetBool()) then + local pattern = "(%d+)" + local I = tonumber(string.match(name, "triggerwireoutput"..pattern)) or 0 + + if ((I > 0) and (I <= MAX_PORTS) and (self.OutsExist[I])) then + self.Timer = self.Timer or {} + self.Timer.Out = self.Timer.Out or {} + + if ((CurTime() - (self.Timer.Out[name] or 0)) < (self.min_trigger_time or MIN_TRIGGER_TIME)) then return false end + self.Timer.Out[name] = CurTime() + + -- Wire output + for Ent, _ in pairs(self.WireEnts or {}) do + self:TriggerWireOutput(Ent, I, data) + end + + return true + end + end + + if (!self.WirePortsChanged) then return false end + + if (name == "addentity") then + local Ent, Func = self:AddSingleEntity(caller) + + if (!IsValid(Ent) or !Func) then return false end + timer.Simple(0.02, function() Func( self, Ent, nil, true) end) + + self:TriggerOutput("onwireentscreated", self) + self:TriggerOutput("onwireentsready", self) + return true + end + + if (name == "removeentity") then + self:RemoveSingleEntity(caller) + return true + end + + if (name == "addentities") then + self:AddEntitiesByName(data) + return true + end + + if (name == "removeentities") then + self:RemoveEntitiesByName(data) + return true + end + + if (name == "removeallentities") then + self:RemoveAllEntities() + return true + end + + return false +end + +function ENT:KeyValue(key, value) + if (!WireAddon) then return end + + key = string.lower(tostring(key or "")) + value = tostring(value or "") + + if ((key == "") or (value == "")) then return end + + local pattern = "(%d+)" + local I = tonumber(string.match(key, "onwireinput"..pattern)) or 0 + if ((I > 0) and (I <= MAX_PORTS)) then + self:StoreOutput(key, value) + end + + local I = tonumber(string.match(key, "onresetwireinput"..pattern)) or 0 + if ((I > 0) and (I <= MAX_PORTS)) then + self:StoreOutput(key, value) + end + + if ((key == "onwireentscreated") or (key == "onwireentsremoved") or + (key == "onwireentsready") or (key == "onwireentsstartchanging")) then + self:StoreOutput(key, value) + end + + if (key == "wire_entity_name") then + self.WireEntName = value + end + + if (key == "min_trigger_time") then + self.min_trigger_time = math.max(tonumber(value) or 0, MIN_TRIGGER_TIME) + end + + local pattern = "(%d+)_(%l+)" + local I, name = string.match(key, "input"..pattern) + local I, name = tonumber(I) or 0, tostring(name or "") + if ((I > 0) and (I <= MAX_PORTS) and (name ~= "")) then + self.Ins = self.Ins or {} + self.InsIDs = self.InsIDs or {} + self.InsExist = self.InsExist or {} + + self.Ins[I] = self.Ins[I] or {} + if (name == "lua") then + self.Ins[I][name] = value + elseif (name == "type") then + self.Ins[I][name] = tonumber(value) + elseif (name == "desc") then + self.Ins[I][name] = value + elseif (name == "name") then + self.InsIDs[value] = I + self.InsExist[I] = true + self.Ins[I][name] = value + end + end + + local I, name = string.match(key, "output"..pattern) + local I, name = tonumber(I) or 0, tostring(name or "") + if ((I > 0) and (I <= MAX_PORTS) and (name ~= "")) then + self.Outs = self.Outs or {} + --self.OutsIDs = self.OutsIDs or {} + self.OutsExist = self.OutsExist or {} + + self.Outs[I] = self.Outs[I] or {} + if (name == "type") then + self.Outs[I][name] = tonumber(value) + elseif (name == "desc") then + self.Outs[I][name] = value + elseif (name == "name") then + --self.OutsIDs[value] = I + self.OutsExist[I] = true + self.Outs[I][name] = value + end + end +end + +local Count = 1 +function ENT:Initialize() + if (!WireAddon) then return end + self.WireEnts = self.WireEnts or {} + self.WireEntsCount = 0 + self.WireEntName = self.WireEntName or "" + + self:UpdateData() + self.Active = (bit.band(self.flags, 16) > 0) -- Start Active + self.oldActive = self.Active + self.old_ALLOW_INTERFACE_bool = ALLOW_INTERFACE:GetBool() + + local Name = self:GetName() or "" + local ErrorName = "Wire Map Interface: "..tostring(self) + + if (Name == "") then + self.ErrorName = ErrorName + else + self.ErrorName = ErrorName.."['"..Name.."']" + end + + + local time = Count * 0.3 + 0.5 + + if (self.WireEntName == "") then + self.WirePortsChanged = true + else + timer.Simple(time, function() + if (!IsValid(self)) then return end + self.WirePortsChanged = true + + self:AddEntitiesByName(self.WireEntName) + end) + end + + Ents[self] = time + Count = Count + 1 +end + +-- To cleanup and get the in-/outputs information. +local function SplitTable(tab, self) + if (!IsValid(self)) then return end + + if (!tab) then return end + if (#tab == 0) then return end + local tab = table.Copy(tab) + + local allowlua = self.RunLuaCode + + local names, types, descs = {}, {}, {} + local Index = 0 + + for i = 1, #tab do + local Port = tab[i] + if (Port) then + local name = Port.name -- The port name for checking + if (name) then -- Do not add ports with no names + Index = Index + 1 + + names[Index] = name -- The port name + types[Index] = self:Convert_MapToWire(Port.type) -- The port type + descs[Index] = Port.desc -- The port description + if (!allowlua) then + tab[i].lua = nil -- remove lua codes if the lua mode isn't on. + end + else + tab[i] = nil -- Resort and cleanup the given table for later using + end + end + end + + return names, types, descs, tab +end + +function ENT:UpdateData() + self.flags = self:GetSpawnFlags() + self.RunLuaCode = (bit.band(self.flags, 8) > 0) -- Run given Lua codes + + self.Inames, self.Itypes, self.Idescs, self.Ins = SplitTable(self.Ins, self) + self.Onames, self.Otypes, self.Odescs, self.Outs = SplitTable(self.Outs, self) +end + +function ENT:Think() + if (!WireAddon) then return end + + local ALLOW_INTERFACE_bool = ALLOW_INTERFACE:GetBool() + if ((self.Active ~= self.oldActive) or (ALLOW_INTERFACE_bool ~= self.old_ALLOW_INTERFACE_bool)) then + if (self.Active and ALLOW_INTERFACE_bool) then + self.SavedIn = self.SavedIn or {} + for name, values in pairs(self.SavedIn) do + self:TriggerWireInput(name, unpack(values)) + self.SavedIn[name] = nil + end + end + self.oldActive = self.Active + self.old_ALLOW_INTERFACE_bool = ALLOW_INTERFACE_bool + end +end + +function ENT:OnRemove() + if (!WireAddon) then return end + + self.flags = self:GetSpawnFlags() + + if (bit.band(self.flags, 4) > 0) then -- Remove in-/output entities on remove + for obj1, obj2 in pairs(self.WireEnts or {}) do + local Entity = (IsEntity(obj1) and obj1) or (IsEntity(obj2) and obj2) + + if (IsValid(Entity)) then + SafeRemoveEntity(Entity) + end + end + else + self:RemoveAllEntities() + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/output.lua b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/output.lua new file mode 100644 index 0000000..29c26a3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/info_wiremapinterface/output.lua @@ -0,0 +1,72 @@ +-- Copied from the gmod base entity, it's changed to work with wire map interface. +-- It's only changed for this entity. + +-- This function is used to store an output. +function ENT:StoreOutput(name, info) + local rawData = string.Explode(",", info) + + local Output = {} + Output.entities = rawData[1] or "" + Output.input = rawData[2] or "" + Output.param = rawData[3] or "" + Output.delay = tonumber(rawData[4]) or 0 + Output.times = tonumber(rawData[5]) or -1 + + self._OutputsToMap = self._OutputsToMap or {} + self._OutputsToMap[name] = self._OutputsToMap[name] or {} + table.insert(self._OutputsToMap[name], Output) +end + + +-- Nice helper function, this does all the work. +-- Returns false if the output should be removed from the list. +local function FireSingleOutput(output, this, activator, value, delayoffset) + if (output.times == 0) then return false end + local delay = output.delay + (delayoffset or 0) + local entitiesToFire = {} + + if (output.entities == "!activator") then + entitiesToFire = {activator} + elseif (output.entities == "!self") then + entitiesToFire = {this} + elseif (output.entities == "!player") then + entitiesToFire = player.GetAll() + else + entitiesToFire = ents.FindByName(output.entities) + end + + for _,ent in pairs(entitiesToFire) do + if (IsValid(ent)) then + if (delay == 0) then + ent:Input(output.input, activator, this, value or output.param) + else + timer.Simple(delay, function() + if (IsValid(ent)) then + ent:Input(output.input, activator, this, value or output.param) + end + end) + end + end + end + + if (output.times ~= -1) then + output.times = output.times - 1 + end + + return ((output.times > 0) or (output.times == -1)) +end + + +-- This function is used to trigger an output. +-- This changed version supports value replacemant and delay offsets. +function ENT:TriggerOutput(name, activator, value, delayoffset) + if (!self._OutputsToMap) then return end + local OutputsToMap = self._OutputsToMap[name] + if (!OutputsToMap) then return end + + for idx,op in pairs(OutputsToMap) do + if (!FireSingleOutput(op, self, activator, value, delayoffset)) then + self._OutputsToMap[name][idx] = nil + end + end +end diff --git a/garrysmod/addons/feature-wire/lua/entities/sent_deployableballoons.lua b/garrysmod/addons/feature-wire/lua/entities/sent_deployableballoons.lua new file mode 100644 index 0000000..618fc96 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/entities/sent_deployableballoons.lua @@ -0,0 +1,237 @@ +AddCSLuaFile() +DEFINE_BASECLASS( "base_wire_entity" ) +ENT.PrintName = "Wire Balloon Deployer" +ENT.Author = "LuaPinapple" +ENT.Contact = "evilpineapple@cox.net" +ENT.Purpose = "It Deploys Balloons." +ENT.Instructions = "Use wire." +ENT.Category = "Wiremod" + +ENT.Spawnable = true +ENT.AdminOnly = false + +ENT.WireDebugName = "Balloon Deployer" +cleanup.Register("wire_deployers") + +if CLIENT then + language.Add( "Cleanup_wire_deployers", "Balloon Deployers" ) + language.Add( "Cleaned_wire_deployers", "Cleaned up Balloon Deployers" ) + language.Add( "SBoxLimit_wire_deployers", "You have hit the Balloon Deployers limit!" ) + return -- No more client +end + + +local material = "cable/rope" +local BalloonTypes = + { + Model("models/MaxOfS2D/balloon_classic.mdl"), + Model("models/balloons/balloon_classicheart.mdl"), + Model("models/balloons/balloon_dog.mdl"), + Model("models/balloons/balloon_star.mdl") + } +CreateConVar('sbox_maxwire_deployers', 2) + +local DmgFilter + +local function CreateDamageFilter() + if IsValid(DmgFilter) then return end + DmgFilter = ents.Create("filter_activator_name") + DmgFilter:SetKeyValue("targetname", "DmgFilter") + DmgFilter:SetKeyValue("negated", "1") + DmgFilter:Spawn() +end +hook.Add("InitPostEntity", "CreateDamageFilter", CreateDamageFilter) + +local function MakeBalloonSpawner(pl, Data) + if IsValid(pl) and not pl:CheckLimit("wire_deployers") then return nil end + if Data.Model and not WireLib.CanModel(pl, Data.Model, Data.Skin) then return false end + + local ent = ents.Create("sent_deployableballoons") + if not ent:IsValid() then return end + duplicator.DoGeneric(ent, Data) + ent:SetPlayer(pl) + ent:Spawn() + ent:Activate() + + duplicator.DoGenericPhysics(ent, pl, Data) + + if IsValid(pl) then + pl:AddCount("wire_deployers", ent) + pl:AddCleanup("wire_deployers", ent) + end + + return ent +end + +duplicator.RegisterEntityClass("sent_deployableballoons", MakeBalloonSpawner, "Data") +scripted_ents.Alias("gmod_iballoon", "gmod_balloon") + +--Moves old "Lenght" input to new "Length" input for older dupes +WireLib.AddInputAlias( "Lenght", "Length" ) + +function ENT:SpawnFunction( ply, tr ) + if (not tr.Hit) then return end + local SpawnPos = tr.HitPos+tr.HitNormal*16 + local ent = MakeBalloonSpawner(ply, {Pos=SpawnPos}) + return ent +end + +function ENT:Initialize() + self:SetModel("models/props_junk/PropaneCanister001a.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid(SOLID_VPHYSICS) + self.Deployed = 0 + self.Balloon = nil + self.Constraints = {} + self.force = 500 + self.weld = false + self.popable = true + self.rl = 64 + if WireAddon then + self.Inputs = Wire_CreateInputs(self,{ "Force", "Length", "Weld?", "Popable?", "BalloonType", "Deploy" }) + self.Outputs=WireLib.CreateSpecialOutputs(self, { "Deployed", "BalloonEntity" }, {"NORMAL","ENTITY" }) + Wire_TriggerOutput(self,"Deployed", self.Deployed) + --Wire_TriggerOutput(self,"Force", self.force) + end + local phys = self:GetPhysicsObject() + if(phys:IsValid()) then + phys:SetMass(250) + phys:Wake() + end + self:UpdateOverlay() +end + +function ENT:TriggerInput(key,value) + if (key == "Deploy") then + if value ~= 0 then + if self.Deployed == 0 then + self:DeployBalloons() + self.Deployed = 1 + end + Wire_TriggerOutput(self, "Deployed", self.Deployed) + else + if self.Deployed ~= 0 then + self:RetractBalloons() + self.Deployed = 0 + end + Wire_TriggerOutput(self, "Deployed", self.Deployed) + end + elseif (key == "Force") then + self.force = value + if self.Deployed ~= 0 then + self.Balloon:SetForce(value) + end + elseif (key == "Length") then + self.rl = value + elseif (key == "Weld?") then + self.weld = value ~= 0 + elseif (key == "Popable?") then + self.popable = value ~= 0 + self:UpdatePopable() + elseif (key == "BalloonType") then + self.balloonType=value+1 --To correct for 1 based indexing + end + self:UpdateOverlay() +end + +local balloon_registry = {} + +hook.Add("EntityRemoved", "balloon_deployer", function(ent) + local deployer = balloon_registry[ent] + if IsValid(deployer) and deployer.TriggerInput then + deployer.Deployed = 0 + deployer:TriggerInput("Deploy", 0) + end +end) +function ENT:UpdatePopable() + local balloon = self.Balloon + if balloon ~= nil and balloon:IsValid() then + if not self.popable then + balloon:Fire("setdamagefilter", "DmgFilter", 0); + else + balloon:Fire("setdamagefilter", "", 0); + end + end +end + +function ENT:DeployBalloons() + local balloon + balloon = ents.Create("gmod_balloon") --normal balloon + + local model = BalloonTypes[self.balloonType] + if(model==nil) then + model = BalloonTypes[1] + end + balloon:SetModel(model) + balloon:Spawn() + balloon:SetColor(Color(math.random(0,255), math.random(0,255), math.random(0,255), 255)) + balloon:SetForce(self.force) + balloon:SetMaterial("models/balloon/balloon") + balloon:SetPlayer(self:GetPlayer()) + duplicator.DoGeneric(balloon,{Pos = self:GetPos() + (self:GetUp()*25)}) + duplicator.DoGenericPhysics(balloon,pl,{Pos = Pos}) + + local balloonPos = balloon:GetPos() -- the origin the balloon is at the bottom + local hitEntity = self + local hitPos = self:LocalToWorld(Vector(0, 0, self:OBBMaxs().z)) -- the top of the spawner + + -- We trace from the balloon to us, and if there's anything in the way, we + -- attach a constraint to that instead - that way, the balloon spawner can + -- be hidden underneath a plate which magically gets balloons attached to it. + local balloonToSpawner = (hitPos - balloonPos):GetNormalized() * 250 + local trace = util.QuickTrace(balloon:GetPos(), balloonToSpawner, balloon) + + if constraint.CanConstrain(trace.Entity, trace.PhysicsBone) then + local phys = trace.Entity:GetPhysicsObjectNum(trace.PhysicsBone) + if IsValid(phys) then + hitEntity = trace.Entity + hitPos = trace.HitPos + end + end + + if self.weld then + local constraint = constraint.Weld( balloon, hitEntity, 0, trace.PhysicsBone, 0) + balloon:DeleteOnRemove(constraint) + else + balloonPos = balloon:WorldToLocal(balloonPos) + hitPos = hitEntity:WorldToLocal(hitPos) + + local constraint, rope = constraint.Rope( + balloon, hitEntity, 0, trace.PhysicsBone, balloonPos, hitPos, + 0, self.rl, 0, 1.5, material, false) + if constraint then + balloon:DeleteOnRemove(constraint) + balloon:DeleteOnRemove(rope) + end + end + self:DeleteOnRemove(balloon) + self.Balloon = balloon + self:UpdatePopable() + balloon_registry[balloon] = self + Wire_TriggerOutput(self, "BalloonEntity", self.Balloon) +end + +function ENT:OnRemove() + if self.Balloon then + balloon_registry[self.Balloon] = nil + end + Wire_Remove(self) +end + +function ENT:RetractBalloons() + if self.Balloon:IsValid() then + local c = self.Balloon:GetColor() + local effectdata = EffectData() + effectdata:SetOrigin( self.Balloon:GetPos() ) + effectdata:SetStart( Vector(c.r,c.g,c.b) ) + util.Effect( "balloon_pop", effectdata ) + self.Balloon:Remove() + else + self.Balloon = nil + end +end + +function ENT:UpdateOverlay() + self:SetOverlayText( "Deployed = " .. ((self.Deployed ~= 0) and "yes" or "no") ) +end diff --git a/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_adv.lua b/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_adv.lua new file mode 100644 index 0000000..3e9cd67 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_adv.lua @@ -0,0 +1,1237 @@ + +-- Load wiremod tools in /lua/wire/stools/ +-- Note: If this tool is ever removed, be sure to put this in another stool! +local OLD_TOOL = TOOL +TOOL = nil +include( "wire/tool_loader.lua" ) +TOOL = OLD_TOOL + +TOOL.Category = "Tools" +TOOL.Name = "Wire" +TOOL.Tab = "Wire" + +if CLIENT then + language.Add( "Tool.wire_adv.name", "Wiring Tool" ) + language.Add( "Tool.wire_adv.desc", "Connect things with wires. (Press Shift+F to switch to the debugger tool)" ) + language.Add( "Tool.wire_adv.desc2", "Used to connect wirable props." ) + language.Add( "WireTool_width", "Width:" ) + language.Add( "WireTool_material", "Material:" ) + language.Add( "WireTool_colour", "Colour:" ) + language.Add( "WireTool_stick", "Stick to surfaces" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Select input (Shift: Select multiple; Alt: Select all)" }, + { name = "right_0", stage = 0, text = "Next" }, + { name = "reload_0", stage = 0, text = "Unlink (Alt: Unlink all)" }, + { name = "mwheel_0", stage = 0, text = "Mouse wheel: Next" }, + { name = "left_1", stage = 1, text = "Select entity" }, + { name = "right_1", stage = 1, text = "Add wirepoint" }, + { name = "f_0", stage = 1, text = "F: Undo wirepoint" }, + { name = "reload_1", stage = 1, text = "Cancel" }, + { name = "left_2", stage = 2, text = "Select output (Alt: Auto-connect matching input/outputs)" }, + { name = "right_2", stage = 2, text = "Next" }, + { name = "mwheel_2", stage = 2, text = "Mouse wheel: Next" }, + } + for _, info in pairs(TOOL.Information) do + language.Add("Tool.wire_adv." .. info.name, info.text) + end + + TOOL.Wire_ToolMenuIcon = "icon16/connect.png" +end + +TOOL.ClientConVar = { + width = 2, + material = "cable/cable2", + r = 255, + g = 255, + b = 255, + stick = 1 +} + +util.PrecacheSound("weapons/pistol/pistol_empty.wav") +util.PrecacheSound("buttons/lightswitch2.wav") +util.PrecacheSound("buttons/button16.wav") + +local function get_active_tool(ply, tool) + -- find toolgun + local activeWep = ply:GetActiveWeapon() + if not IsValid(activeWep) or activeWep:GetClass() ~= "gmod_tool" or activeWep.Mode ~= tool then return end + + return activeWep:GetToolObject(tool) +end + +if SERVER then + ----------------------------------------------------------------- + -- Duplicator modifiers + ----------------------------------------------------------------- + function WireLib.CreateWirelinkOutput( ply, ent, data ) + if data[1] == true then + if ent.Outputs then + local names = {} + local types = {} + local descs = {} + local x = 0 + for k,v in pairs( ent.Outputs ) do + x = x + 1 + local num = v.Num + names[num] = v.Name + if v.Name == "wirelink" then return end -- we already have a wirelink output, abort + types[num] = v.Type + descs[num] = v.Desc + end + + names[x+1] = "wirelink" + types[x+1] = "WIRELINK" + descs[x+1] = "" + + WireLib.AdjustSpecialOutputs( ent, names, types, descs ) + else + WireLib.CreateSpecialOutputs( ent, { "wirelink" }, { "WIRELINK" } ) + end + + ent.extended = true + WireLib.TriggerOutput( ent, "wirelink", ent ) + end + duplicator.StoreEntityModifier( ent, "CreateWirelinkOutput", data ) + end + duplicator.RegisterEntityModifier( "CreateWirelinkOutput", WireLib.CreateWirelinkOutput ) + + function WireLib.CreateEntityOutput( ply, ent, data ) + if data[1] == true then + if ent.Outputs then + local names = {} + local types = {} + local descs = {} + local x = 0 + for k,v in pairs( ent.Outputs ) do + x = x + 1 + local num = v.Num + names[num] = v.Name + if v.Name == "entity" then return end -- we already have an entity output, abort + types[num] = v.Type + descs[num] = v.Desc + end + + names[x+1] = "entity" + types[x+1] = "ENTITY" + descs[x+1] = "" + + WireLib.AdjustSpecialOutputs( ent, names, types, descs ) + else + WireLib.CreateSpecialOutputs( ent, { "entity" }, { "ENTITY" } ) + end + + WireLib.TriggerOutput( ent, "entity", ent ) + end + duplicator.StoreEntityModifier( ent, "CreateEntityOutput", data ) + end + duplicator.RegisterEntityModifier( "CreateEntityOutput", WireLib.CreateEntityOutput ) + + + ----------------------------------------------------------------- + -- Receving data from client + ----------------------------------------------------------------- + + util.AddNetworkString( "wire_adv_upload" ) + net.Receive( "wire_adv_upload", function( len, ply ) + local wirings = net.ReadTable() + + local tool = get_active_tool(ply,"wire_adv") + if not tool then return end + + local material = tool:GetClientInfo("material") + local width = tool:GetClientNumber("width") + local color = Color(tool:GetClientNumber("r"), tool:GetClientNumber("g"), tool:GetClientNumber("b")) + + local uid = ply:UniqueID() + + for i=1,#wirings do + local wiring = wirings[i] + + local inputentity = wiring[3] + local outputentity = wiring[5] + + if IsValid( inputentity ) and IsValid( outputentity ) and + hook.Run( "CanTool", ply, WireLib.dummytrace( inputentity ), "wire_adv" ) and + hook.Run( "CanTool", ply, WireLib.dummytrace( outputentity ), "wire_adv" ) then + + local inputname = wiring[1] + local inputpos = wiring[2] + if WireLib.Link_Start( uid, inputentity, inputpos, inputname, material, color, width ) then + for i=1,#wiring[4] do + local node = wiring[4][i] + WireLib.Link_Node( uid, node[1], node[2] ) + end + local outputpos = wiring[6] + local outputname = wiring[7] + + if outputname == "Create Wirelink" and (not outputentity.Outputs or not outputentity.Outputs["wirelink"]) then + WireLib.CreateWirelinkOutput( ply, outputentity, {true} ) + outputname = "wirelink" + elseif outputname == "Create Wirelink" and outputentity.Outputs and outputentity.Outputs["wirelink"] then + outputname = "wirelink" + elseif outputname == "Create Entity" and (not outputentity.Outputs or not outputentity.Outputs["entity"]) then + WireLib.CreateEntityOutput( ply, outputentity, {true} ) + outputname = "entity" + elseif outputname == "Create Entity" and outputentity.Outputs and outputentity.Outputs["entity"] then + outputname = "entity" + end + + WireLib.Link_End( uid, outputentity, outputpos, outputname, ply ) + end + end + end + end) + + util.AddNetworkString( "wire_adv_unwire" ) + net.Receive( "wire_adv_unwire", function( len, ply ) + local ent = net.ReadEntity() + local tbl = net.ReadTable() + + if hook.Run( "CanTool", ply, WireLib.dummytrace( ent ), "wire_adv" ) then + for i=1,#tbl do + WireLib.Link_Clear( ent, tbl[i] ) + end + end + end) + + WireToolHelpers.SetupSingleplayerClickHacks(TOOL) +elseif CLIENT then + + ----------------------------------------------------------------- + -- Tool helper functions + ----------------------------------------------------------------- + TOOL._stage = 0 + function TOOL:SetStage(stage) -- Garry's stage functions didn't work, had to make my own + self._stage = stage + end + function TOOL:GetStage() + return self._stage + end + function TOOL:SanitizeUpload() -- Removes any wirings that are no longer valid + for i=#self.Wiring,1,-1 do + local wiring = self.Wiring[i] + local inputentity = wiring[3] + local outputentity = wiring[5] + local outputname = wiring[7] + + if not IsValid(inputentity) or + not IsValid(outputentity) or + not outputname then -- we don't need to check everything because only these things can possibly be invalid + + table.remove(self.Wiring,i) + end + end + end + function TOOL:Upload() + self:SanitizeUpload() -- Remove all invalid wirings before sending + + net.Start( "wire_adv_upload" ) + net.WriteTable( self.Wiring ) + net.SendToServer() + + self:Holster() + end + function TOOL:Unwire( ent, names ) + net.Start( "wire_adv_unwire" ) + net.WriteEntity( ent ) + net.WriteTable( names ) + net.SendToServer() + end + + + ----------------------------------------------------------------- + -- GetPorts + ----------------------------------------------------------------- + TOOL.AimingEnt2 = nil + TOOL.CurrentInputs = nil + TOOL.CurrentOutputs = nil + function TOOL:CachePorts( ent ) + local inputs, outputs = WireLib.GetPorts( ent ) + + local copied = false + + if self.ShowWirelink then + if outputs then + local found = false + for i=1,#outputs do + if outputs[i][2] == "WIRELINK" then found = true break end + end + if not found then + outputs = table.Copy(outputs) -- we don't want to modify the original table + copied = true + outputs[#outputs+1] = { "Create Wirelink", "WIRELINK" } + end + else + outputs = { { "Create Wirelink", "WIRELINK" } } + end + end + + if self.ShowEntity then + if outputs then + local found = false + for i=1,#outputs do + if (outputs[i][1] == "entity" and outputs[i][2] == "ENTITY") or + (outputs[i][1] == "Create Entity" and outputs[i][2] == "ENTITY") then found = true break end + end + if not found then + if not copied then -- we don't want to copy it twice + outputs = table.Copy(outputs) + end + outputs[#outputs+1] = { "Create Entity", "ENTITY" } + end + else + outputs = { { "Create Entity", "ENTITY" } } + end + end + self.CurrentInputs = inputs + self.CurrentOutputs = outputs + end + + local next_recache = 0 + function TOOL:GetPorts( ent ) + if IsValid( ent ) then + if ent ~= self.AimingEnt2 or next_recache < CurTime() then + next_recache = CurTime() + 1 + self.AimingEnt2 = ent + self:CachePorts( ent ) + end + return self.CurrentInputs, self.CurrentOutputs + else + self.CurrentInputs = nil + self.CurrentOutputs = nil + self.AimingEnt2 = nil + end + end + + TOOL.CurrentWireIndex = 1 + TOOL.CurrentEntity = nil -- entity which was selected in mode 1 + TOOL.Wiring = {} -- all wires + TOOL.WiringRender = {} -- table for rendering inputs nicely + TOOL.NeedsUpload = false -- bool for sending all wires to server + TOOL.ShowWirelink = false -- bool for showing "Create Wirelink" output + TOOL.ShowEntity = false -- bool for showing "Create Entity" output + + function TOOL:Holster() + if IsValid(self.CurrentEntity) then self.CurrentEntity:SetNWString("BlinkWire", "") end + if IsValid(self.AimingEnt) then self.AimingEnt:SetNWString("BlinkWire", "") end + self.CurrentEntity = nil + self.Wiring = {} + self.WiringRender = {} + self.ShowWirelink = false + self.ShowEntity = false + self:SetStage(0) + end + + ----------------------------------------------------------------- + -- Wiring helper functions + ----------------------------------------------------------------- + + --[[ Wirings table format: + self.Wiring[x] = wiring + + where + + wiring = { + [1] = inputname, + [2] = inputpos, + [3] = inputentity, + [4] = nodes, + [5] = outputentity, + [6] = outputpos, + [7] = outputname, + [8] = inputtype, + } + + where + + nodes = { + [1] = entity, + [2] = pos, + } + ]] + + + function TOOL:FindWiring( entity, inputname, inputtype ) + for i=1,#self.Wiring do + local wiring = self.Wiring[i] + if wiring[1] == inputname and wiring[3] == entity and wiring[8] == inputtype then return wiring, i end + end + end + + function TOOL:WireStart( entity, pos, inputname, inputtype ) + local wiring, id = self:FindWiring( entity, inputname, inputtype ) + if wiring then -- wiring is already started, user wants to cancel it + table.remove( self.Wiring, id ) + self:WiringRenderRemove( inputname, inputtype ) + return + end + + local t = { inputname, entity:WorldToLocal( pos ), entity, {} } + t[8] = inputtype + self.Wiring[#self.Wiring+1] = t + + if inputtype == "WIRELINK" then + self.ShowWirelink = true + elseif inputtype == "ENTITY" then + self.ShowEntity = true + end + + -- Add info to the wiringrender table, which is used to render the "x2" "x3" etc + self:WiringRenderAdd( inputname, inputtype ) + + return t + end + function TOOL:WireNode( wiring, entity, pos ) + wiring[4][#wiring[4]+1] = { entity, entity:WorldToLocal( pos ) } + end + function TOOL:WireEndEntityPos( wiring, entity, pos ) + wiring[5] = entity + wiring[6] = entity:WorldToLocal( pos ) + end + function TOOL:WireEndOutputName( wiring, outputname ) + wiring[7] = outputname + wiring[8] = nil -- we don't need to send the type to the server; wasted net message space. Delete it + self.NeedsUpload = true -- We want to upload next tick. We don't upload immediately because the client may call this function more this tick (for multi wiring) + end + + + -- This function will help when using ALT to wire, when a single output's type matches but the name does not + -- it will allow us to check if only a single output of matching types exist on the entity without looping + -- through the entity's outputs several times per frame + TOOL.AutoWiringTypeLookup_t = {} + function TOOL:AutoWiringTypeLookup( ent ) + if not IsValid( ent ) then + self.AutoWiringTypeLookup_t = {} + else + self.AutoWiringTypeLookup_t = {} + local _, outputs = self:GetPorts( ent ) + for i=1,#outputs do + local outputtype = outputs[i][2] + if self.AutoWiringTypeLookup_t[outputtype] == nil then -- if we haven't found any outputs of this type yet, + self.AutoWiringTypeLookup_t[outputtype] = i -- set the index + elseif self.AutoWiringTypeLookup_t[outputtype] ~= nil then -- if we've already found outputs of this type, + self.AutoWiringTypeLookup_t[outputtype] = false -- set to false + end + end + end + end + function TOOL:AutoWiringTypeLookup_Check( inputtype ) + return self.AutoWiringTypeLookup_t[inputtype] + end + + -- Updates the trace hit position and normal to the surface of the parent, perpendicular to the originally hit entity. + -- As not all models have the same forward, up, etc. it checks all directions perpendicular to the hit entity, until it finds the parent. + -- If the parent is not found in any perpendicular direction, it traces the parent in the direction of the tool gun. + function TOOL:UpdateTraceForSurface(trace, parent) + if self:GetClientNumber("stick") == 0 then return end + if not WireLib.HasPorts(trace.Entity) then return end + + local hitParentPos + local hitParentNormal + local foundParent + local closestDistanceSquared + + local entityUp = trace.Entity:GetUp() * 1000 + local entityForward = trace.Entity:GetForward() * 1000 + local entityRight = trace.Entity:GetRight() * 1000 + local traceVectors = + { + entityUp, + entityForward, + entityRight, + -entityUp, + -entityForward, + -entityRight + } + + -- Looks for the parent on all local axes, and returns the closest hit surface on the parent. + local function findParent() + foundParent = false + closestDistanceSquared = math.huge + + for i = 1, 6 do + local traceVector = traceVectors[i] + + local traceData = util.GetPlayerTrace(LocalPlayer()) + traceData.start = trace.HitPos -- Start from the original trace. + + traceData.endpos = trace.HitPos + traceVector + traceData.filter = { LocalPlayer(), trace.Entity } + traceData.collisiongroup = LAST_SHARED_COLLISION_GROUP + local newTrace = util.TraceLine(traceData) + + if newTrace.Hit and newTrace.Entity == parent then + local distanceSquared = newTrace.HitPos:DistToSqr(trace.HitPos) + if distanceSquared <= 75 * 75 and distanceSquared < closestDistanceSquared then + closestDistanceSquared = distanceSquared + hitParentPos = newTrace.HitPos + hitParentNormal = newTrace.HitNormal + foundParent = true + end + end + end + end + + if IsValid(parent) then + findParent() + end + + if not foundParent then + -- Didn't find the parent in any direction, treat whichever entity can be traced behind the entity as the parent. + -- This can happen if eg. the component is not directly on the parent (or if the entity just never had an actual parent). + local traceData = util.GetPlayerTrace(LocalPlayer()) + traceData.filter = { LocalPlayer(), trace.Entity } + traceData.collisiongroup = LAST_SHARED_COLLISION_GROUP + newTrace = util.TraceLine(traceData) + parent = newTrace.Entity + if not IsValid(parent) or parent == game.GetWorld() then + -- Hit the world, don't update the trace. + return + end + + findParent() -- Try again with the new assumed parent. + end + + if foundParent then + trace.HitPos = hitParentPos + trace.HitNormal = hitParentNormal + end + end + + ----------------------------------------------------------------- + -- Mouse buttons + ----------------------------------------------------------------- + + TOOL.wtfgarry = 0 + function TOOL:LeftClick(trace) + if self.wtfgarry > CurTime() then return end + self.wtfgarry = CurTime() + 0.1 + + local shift = self:GetOwner():KeyDown(IN_SPEED) + local alt = self:GetOwner():KeyDown(IN_WALK) + + if IsValid( trace.Entity ) then + if self:GetStage() == 0 then + self:UpdateTraceForSurface(trace, trace.Entity:GetParent()) + self:BeginRenderingCurrentWire() + + local inputs, _ = self:GetPorts( trace.Entity ) + if not inputs then return end + + if alt then -- Select everything + for i=1,#inputs do + self:WireStart( trace.Entity, trace.HitPos, inputs[i][1], inputs[i][2] ) + end + else + -- Single input selection + if not inputs[self.CurrentWireIndex] then return end -- Can happen if theres no inputs, only outputs + self:WireStart( trace.Entity, trace.HitPos, inputs[self.CurrentWireIndex][1], inputs[self.CurrentWireIndex][2] ) + end + + self:GetOwner():EmitSound( "weapons/airboat/airboat_gun_lastshot" .. math.random(1,2) .. ".wav" ) + + if not shift then + self:SetStage(1) -- Set this immediately so the HUD doesn't glitch + end + + return + elseif self:GetStage() == 1 then + self:UpdateTraceForSurface(trace, trace.Entity:GetParent()) + local _, outputs = self:GetPorts( trace.Entity ) + if not outputs then return end + + self.CurrentEntity = trace.Entity + self:AutoWiringTypeLookup( self.CurrentEntity ) + + self:SetStage(2) + + for i=1,#self.Wiring do + self:WireEndEntityPos( self.Wiring[i], self.CurrentEntity, trace.HitPos ) + end + + if next(outputs,next(outputs)) == nil then -- there's only one element in the table + self.wtfgarry = 0 + self:LeftClick( trace ) -- wire it right away + return + end + + self:LoadMemorizedIndex( self.CurrentEntity, true ) + + -- find first matching output by name or type + local oldport = self.CurrentWireIndex + local port = self.CurrentWireIndex + local matchingByType + local matchingByName + repeat + if outputs[port][1] == self.Wiring[1][1] and outputs[port][2] == self.Wiring[1][8] and not matchingByName then + matchingByName = port + end + if outputs[port][2] == self.Wiring[1][8] and not matchingByType then + matchingByType = port + end + + port = port + 1 + if port > #outputs then + port = 1 + end + until port == oldport or (matchingByName and matchingByType) + + if matchingByName then + self.CurrentWireIndex = matchingByName + elseif matchingByType then + self.CurrentWireIndex = matchingByType + end + + self:GetOwner():EmitSound( "weapons/airboat/airboat_gun_lastshot" .. math.random(1,2) .. ".wav" ) + return + end + end + + if self:GetStage() == 2 then + local _, outputs = self:GetPorts( self.CurrentEntity ) + + if alt then -- Auto wiring + local notwired = 0 + local typematched = 0 + for i=1,#self.Wiring do + local wiring = self.Wiring[i] + local inputname = wiring[1] + local inputtype = wiring[8] + local found = false + for j=1,#outputs do + local outputname = outputs[j][1] + local outputtype = outputs[j][2] + + if self:IsMatch( inputname, inputtype, outputname, outputtype, true ) then + self:WireEndOutputName( wiring, outputname ) + found = true + break + end + end + + if not found then -- if we didn't find a matching name & type, check if there's only one matching type (ignoring name) + local idx = self:AutoWiringTypeLookup_Check( inputtype ) + if idx then + self:WireEndOutputName( wiring, outputs[idx][1] ) + typematched = typematched + 1 + else + notwired = notwired + 1 + end + end + end + + if notwired > 0 then + WireLib.AddNotify( "Could not find a matching name/type for " .. notwired .. " inputs. They were not wired.", NOTIFY_HINT, 10, NOTIFYSOUND_DRIP1 ) + end + if typematched > 0 then + WireLib.AddNotify( "Could not find a matching name/type for " .. typematched .. " inputs. However, a single output of that type was found, which was used instead.", NOTIFY_HINT, 10, NOTIFYSOUND_DRIP1 ) + end + else -- Normal wiring + local notwired = 0 + for i=1,#self.Wiring do + if outputs[self.CurrentWireIndex][2] == self.Wiring[i][8] then + self:WireEndOutputName( self.Wiring[i], outputs[self.CurrentWireIndex][1] ) + else + notwired = notwired + 1 + end + end + + if notwired > 0 then + WireLib.AddNotify( "The type did not match for " .. notwired .. " inputs. They were not wired.", NOTIFY_HINT, 5, NOTIFYSOUND_DRIP1 ) + end + end + + self:SetStage(0) + self.WiringRender = {} -- Empty this now so the HUD doesn't glitch + self:GetOwner():EmitSound( "weapons/airboat/airboat_gun_lastshot" .. math.random(1,2) .. ".wav" ) + self:StopRenderingCurrentWire() + end + end + + function TOOL:RightClick(trace) + if self.wtfgarry > CurTime() then return end + self.wtfgarry = CurTime() + 0.1 + + self:UpdateTraceForSurface(trace, trace.Entity:GetParent()) + if self:GetStage() == 0 or self:GetStage() == 2 then + self:ScrollDown(trace) + elseif IsValid(trace.Entity) and self:GetStage() == 1 then + for i=1,#self.Wiring do + self:WireNode( self.Wiring[i], trace.Entity, trace.HitPos + trace.HitNormal*(self:GetClientNumber("width")/2) ) + end + self:GetOwner():EmitSound("buttons/lightswitch2.wav") + end + end + + function TOOL:Reload(trace) + if self.wtfgarry > CurTime() then return end + self.wtfgarry = CurTime() + 0.1 + + if self:GetStage() == 0 and IsValid( trace.Entity ) and WireLib.HasPorts( trace.Entity ) then + local inputs, outputs = self:GetPorts( trace.Entity ) + if not inputs then return end + if self:GetOwner():KeyDown( IN_WALK ) then + local t = {} + for i=1,#inputs do + t[i] = inputs[i][1] + end + self:Unwire( trace.Entity, t ) + else + self:Unwire( trace.Entity, { inputs[self.CurrentWireIndex][1] } ) + end + else + self:Holster() + end + + self:StopRenderingCurrentWire() + self:GetOwner():EmitSound( "weapons/airboat/airboat_gun_lastshot" .. math.random(1,2) .. ".wav" ) + end + + function TOOL:Scroll(trace,dir) + local ent = self:GetStage() == 0 and trace.Entity or self.CurrentEntity + if IsValid(ent) then + local inputs, outputs = self:GetPorts( ent ) + if not inputs and not outputs then return end + local check = self:GetStage() == 0 and inputs or outputs + if #check == 0 then return end + + local b = false + local oldport = self.CurrentWireIndex + + if self:GetStage() == 2 then + repeat + self.CurrentWireIndex = self.CurrentWireIndex + dir + if self.CurrentWireIndex > #check then + self.CurrentWireIndex = 1 + elseif self.CurrentWireIndex < 1 then + self.CurrentWireIndex = #check + end + until not self:IsBlocked( "Outputs", outputs, ent, self.CurrentWireIndex ) or self.CurrentWireIndex == oldport + else + self.CurrentWireIndex = self.CurrentWireIndex + dir + + if self.CurrentWireIndex > #check then + self.CurrentWireIndex = 1 + elseif self.CurrentWireIndex < 1 then + self.CurrentWireIndex = #check + end + end + + if oldport ~= self.CurrentWireIndex then + ent:SetNWString("BlinkWire", check[self.CurrentWireIndex][1]) + self:GetOwner():EmitSound("weapons/pistol/pistol_empty.wav") + end + return true + end + end + + function TOOL:ScrollUp(trace) return self:Scroll(trace,-1) end + function TOOL:ScrollDown(trace) return self:Scroll(trace,1) end + + local function hookfunc( ply, bind, pressed ) + if not pressed then return end + + if bind == "invnext" then + local self = get_active_tool(ply, "wire_adv") + if not self then return end + + return self:ScrollDown(ply:GetEyeTraceNoCursor()) + elseif bind == "invprev" then + local self = get_active_tool(ply, "wire_adv") + if not self then return end + + return self:ScrollUp(ply:GetEyeTraceNoCursor()) + elseif bind == "impulse 100" then + if ply:KeyDown( IN_SPEED ) then + local self = get_active_tool(ply, "wire_adv") + if not self then + self = get_active_tool(ply, "wire_debugger") + if not self then return end + + spawnmenu.ActivateTool( "wire_adv") -- switch back to wire adv + return true + end + + spawnmenu.ActivateTool("wire_debugger") -- switch to debugger + return true + else + local self = get_active_tool(ply, "wire_adv") + if self and self:GetStage() == 1 then + local len = #self.Wiring + if len > 0 then + local nodesCount = 0 + for i=1,len do + nodesCount = nodesCount + #self.Wiring[i][4] + if #self.Wiring[i][4] > 0 then + table.remove(self.Wiring[i][4]) + end + end + + if nodesCount > 0 then + self:GetOwner():EmitSound( "buttons/button16.wav" ) + end + end + return true + end + end + end + end + + if game.SinglePlayer() then -- wtfgarry (have to have a delay in single player or the hook won't get added) + timer.Simple(5,function() hook.Add( "PlayerBindPress", "wire_adv_playerbindpress", hookfunc ) end) + else + hook.Add( "PlayerBindPress", "wire_adv_playerbindpress", hookfunc ) + end + + ----------------------------------------------------------------- + -- Remember wire indexes + ----------------------------------------------------------------- + + -- Remember wire index positions for entities + TOOL.WireIndexMemory = {} + TOOL.AimingEnt = nil + TOOL.AimingStage = 0 + function TOOL:LoadMemorizedIndex( ent, forceload ) + if ent ~= self.AimingEnt or self:GetStage() ~= self.AimingStage or forceload then + if self:GetStage() == 2 and self.CurrentEntity ~= ent then -- if you aim away during stage 2, don't change CurrentWireIndex + return + end + + -- Memorize selected input + if IsValid(self.AimingEnt) and not forceload then + if not self.WireIndexMemory[self.AimingEnt] then + self.WireIndexMemory[self.AimingEnt] = {} + end + if not self.WireIndexMemory[self.AimingEnt:GetClass()] then + self.WireIndexMemory[self.AimingEnt:GetClass()] = {} -- save to class as well + end + self.WireIndexMemory[self.AimingEnt][self.AimingStage] = self.CurrentWireIndex + self.WireIndexMemory[self.AimingEnt:GetClass()][self.AimingStage] = self.CurrentWireIndex -- save to class as well + end + + -- Clear blinking wire + if IsValid( self.AimingEnt ) then + self.AimingEnt:SetNWString("BlinkWire", "") + end + + if IsValid( ent ) then + -- Retrieve memorized selected input + if self.WireIndexMemory[ent] and self.WireIndexMemory[ent][self:GetStage()] then + self.CurrentWireIndex = self.WireIndexMemory[ent][self:GetStage()] + elseif self.WireIndexMemory[ent:GetClass()] and self.WireIndexMemory[ent:GetClass()][self:GetStage()] then -- if this entity doesn't have a stored position, get it from the class instead + self.CurrentWireIndex = self.WireIndexMemory[ent:GetClass()][self:GetStage()] + else + self.CurrentWireIndex = 1 + end + + -- Clamp index + local inputs, outputs = self:GetPorts( ent ) + local check = self:GetStage() == 0 and inputs or outputs + if check then + self.CurrentWireIndex = math.Clamp( self.CurrentWireIndex, 1, #check ) + + -- Set blinking wire + if check[self.CurrentWireIndex] then + ent:SetNWString("BlinkWire", check[self.CurrentWireIndex][1]) + end + end + end + + self.AimingEnt = ent + self.AimingStage = self:GetStage() + end + end + + ----------------------------------------------------------------- + -- Think + ----------------------------------------------------------------- + function TOOL:Think() + local ent = self:GetOwner():GetEyeTrace().Entity + self:LoadMemorizedIndex( ent ) + + -- Check for holding shift etc + local shift = self:GetOwner():KeyDown( IN_SPEED ) + + if #self.Wiring > 0 and self:GetStage() == 0 and not shift then + self:SetStage(1) + elseif #self.Wiring > 0 and self:GetStage() == 1 and shift then + self:SetStage(0) + end + + -- Check if we need to upload + if self.NeedsUpload then + self.NeedsUpload = false + self:Upload() + end + end + + ----------------------------------------------------------------- + -- HUD Stuff + ----------------------------------------------------------------- + function TOOL:IsHighlighted( name, tbl, ent, idx ) + local alt = self:GetOwner():KeyDown( IN_WALK ) + if name == "Outputs" and self:GetStage() == 2 then + if alt then -- if we're holding alt, highlight all outputs that will be wired to + local outputname = tbl[idx][1] + local outputtype = tbl[idx][2] + + for i=1,#self.WiringRender do + local inputname = self.WiringRender[i][1] + local inputtype = self.WiringRender[i][2] + + if self:IsMatch( inputname, inputtype, outputname, outputtype, true ) then + return true + end + + local _idx = self:AutoWiringTypeLookup_Check( inputtype ) + if _idx then + local _outputtype = tbl[_idx][2] + if outputtype == _outputtype then + return true + end + end + end + else + return self.CurrentWireIndex == idx -- Highlight selected output + end + elseif name == "Selected" and self:GetStage() == 2 and alt then -- highlight all selected inputs that will be wired + local inputs, outputs = self:GetPorts( ent ) + + local inputname = tbl[idx][1] + local inputtype = tbl[idx][2] + + for i=1,#outputs do + local outputname = outputs[i][1] + local outputtype = outputs[i][2] + if self:IsMatch( inputname, inputtype, outputname, outputtype, true ) then + return true + end + end + + local _idx = self:AutoWiringTypeLookup_Check( inputtype ) + if _idx then + return true + end + elseif name == "Inputs" and self:GetStage() == 0 then + local wiring, _ = self:FindWiring( ent, tbl[idx][1], tbl[idx][2] ) + if wiring then return true, true end -- Highlight with a different color + return self.CurrentWireIndex == idx or alt + end + return false + end + + function TOOL:IsBlocked( name, tbl, ent, idx ) + if name == "Outputs" and self:GetStage() > 0 then -- Gray out the ones that we can't wire any of the selected inputs to + for i=1,#self.WiringRender do + local inputtype = self.WiringRender[i][2] + if tbl[idx][2] == inputtype then + return false + end + end + return true + elseif name == "Selected" and self:GetStage() == 2 then + if self:GetOwner():KeyDown( IN_WALK ) then -- Gray out the ones that won't be able to be wired to any input + local inputs, outputs = self:GetPorts( ent ) + if not outputs then return false end + for i=1,#outputs do + if tbl[idx][2] == outputs[i][2] then return false end + end + return true + else -- Gray out the ones that won't be able to be wired to the selected output + local inputs, outputs = self:GetPorts( ent ) + if not outputs then return false end + return tbl[idx][2] ~= outputs[self.CurrentWireIndex][2] + end + end + return false + end + + local function getName( input ) + local name = input[1] + local tp = input[8] or (type(input[2]) == "string" and input[2] or "") + local desc = (IsEntity(input[3]) and "" or input[3]) or "" + return name .. (desc ~= "" and " (" .. desc .. ")" or "") .. (tp ~= "NORMAL" and " [" .. tp.. "]" or "") + end + + local function getWidthHeight( inputs ) + local width, height = 0, 0 + for i=1,#inputs do + local input = inputs[i] + local name = getName( input ) + local w,h = surface.GetTextSize( name ) + if w > width then + if input[9] and input[9] > 1 then w = w + 14 end + width = w + end + height = height + h + end + return width, height + end + + local fontData = {font = "Trebuchet MS"} -- 24 and 18 are stock + for _,size in pairs({22,20,16,14}) do + fontData.size = size + surface.CreateFont("Trebuchet"..size, fontData) + end + + local fontheights + + local function getFontSizes() + fontheights = {} + for i=14,24,2 do + local fontname = "Trebuchet" .. i + local h = draw.GetFontHeight( fontname ) + fontheights[fontname] = h + end + end + + TOOL.CurrentFont = "Trebuchet24" + -- Find the largest font that can fit `lines` lines of text into a box `maxsize` + -- tall. Set that font as current, and return the size of one line of text. + -- If no fonts would fit, then the smallest font possible will be returned. + function TOOL:fitFont( lines, maxsize ) + if not fontheights then + getFontSizes() + end + + local minFontSize = 14 + + for i=24, minFontSize, -2 do + local fontname = "Trebuchet" .. i + local height = fontheights[fontname] + if height * lines <= maxsize or i == minFontSize then + self.CurrentFont = fontname + surface.SetFont( fontname ) + local w, _ = surface.GetTextSize( "Selected:" ) + return w, height + end + end + end + + function TOOL:DrawList( name, tbl, ent, x, y, w, h, fonth ) + draw.RoundedBox( 6, x, y, w+16, h+14, Color(50,50,75,192) ) + + x = x + 8 + y = y + 2 + + local temp,_ = surface.GetTextSize( name .. ":" ) + surface.SetTextColor( Color(255,255,255,255) ) + surface.SetTextPos( x-temp/2+w/2, y ) + surface.DrawText( name .. ":" ) + surface.SetDrawColor( Color(255,255,255,255) ) + surface.DrawLine( x, y + fonth+2, x+w, y + fonth+2 ) + + y = y + 6 + + -- Draw inputs + for i=1,#tbl do + y = y + fonth + + local highlighted, diffcolor = self:IsHighlighted( name, tbl, ent, i ) + if highlighted then + local clr = Color(0,150,0,192) + if diffcolor and (self.CurrentWireIndex == i or self:GetOwner():KeyDown( IN_WALK )) then clr = Color(100,100,175,192) + elseif diffcolor then clr = Color(0,0,150,192) end + draw.RoundedBox( 4, x-4,y, w+8,fonth+2, clr ) + end + + if tbl[i][4] == true then + surface.SetTextColor( Color(255,0,0,255) ) + elseif self:IsBlocked( name, tbl, ent, i ) then + surface.SetTextColor( Color(255,255,255,32) ) + else + surface.SetTextColor( Color(255,255,255,255) ) + end + + if tbl[i][9] and tbl[i][9] > 1 then + surface.SetFont( "Trebuchet14" ) + local tempw, temph = surface.GetTextSize( "x" .. tbl[i][9] ) + surface.SetTextPos( x+w-tempw+2, y+fonth/2-temph/2 ) + surface.DrawText( "x" .. tbl[i][9] ) + surface.SetFont( self.CurrentFont ) + end + + surface.SetTextPos( x, y ) + surface.DrawText( getName( tbl[i] ) ) + end + end + + function TOOL:DrawHUD() + local centerx, centery = ScrW()/2, ScrH()/2 + + local ent = self:GetStage() == 2 and self.CurrentEntity or self:GetOwner():GetEyeTrace().Entity + local maxwidth = 0 + if IsValid( ent ) then + local inputs, outputs = self:GetPorts( ent ) + if inputs and #inputs > 0 and self:GetStage() == 0 then + local w, h = self:fitFont( #inputs, ScrH() - 32 ) + local ww, hh = getWidthHeight( inputs ) + ww = math.max(ww,w) + hh = math.max(hh,h) + h + maxwidth = ww + 22 + local x = centerx-ww-38 + local y = centery-hh/2-16 + self:DrawList( "Inputs", inputs, ent, x, y, ww, hh, h ) + end + + if outputs and #outputs > 0 and self:GetStage() > 0 then + local w, h = self:fitFont( #outputs, ScrH() - 32 ) + local ww, hh = getWidthHeight( outputs ) + ww = math.max(ww,w) + hh = math.max(hh,h) + h + local x = centerx+38 + local y = centery-hh/2-16 + self:DrawList( "Outputs", outputs, ent, x, y, ww, hh, h ) + end + end + + if #self.WiringRender > 0 then + local w, h = self:fitFont( #self.WiringRender, ScrH() - 32 ) + local ww, hh = getWidthHeight( self.WiringRender ) + local ww = math.max(ww,w) + local hh = math.max(hh,h) + h + local x = centerx-maxwidth-ww-38 + local y = centery-hh/2-16 + self:DrawList( "Selected", self.WiringRender, ent, x, y, ww, hh, h ) + end + end + + function TOOL:StopRenderingCurrentWire() + hook.Remove("PostDrawOpaqueRenderables", "Wire.ToolWireRenderHook") + self.IsRenderingCurrentWire = false; + WireLib.Wire_GrayOutWires = false + end + + function TOOL:BeginRenderingCurrentWire() + if self.IsRenderingCurrentWire then return end + self.IsRenderingCurrentWire = true + WireLib.Wire_GrayOutWires = true + hook.Add("PostDrawOpaqueRenderables", "Wire.ToolWireRenderHook", function() + -- Draw the wire path + render.SetColorMaterial() + for i=1, #self.Wiring do + local wiring = self.Wiring[i] + local nodes = wiring[4] + local outputEntity = wiring[5] + + local color = Color(self:GetClientNumber("r"), self:GetClientNumber("g"), self:GetClientNumber("b")) + local matName = self:GetClientInfo("material") + local width = self:GetClientInfo("width") + local mat = Material(matName) + local theEnt = wiring[3] + if not theEnt:IsValid() then + break + end + + local start = theEnt:LocalToWorld(wiring[2]) + + local scroll = 0.5 + render.SetMaterial(mat) + render.StartBeam((#nodes*2)+1+1+1) -- + startpoint + same as last node (to not have transition to aiming point) +point where player is aiming + render.AddBeam(start, width, scroll, color) + + for j=1, #nodes do + local node = nodes[j] + + local nodeEnt = node[1] + local nodeOffset = node[2] + local nodePosition = nodeEnt:LocalToWorld(nodeOffset) + + scroll = scroll+(nodePosition-start):Length()/10 + render.AddBeam(nodePosition, width, scroll, color) + render.AddBeam(nodePosition, width, scroll, color) + + start = nodePosition + end + + render.AddBeam(start, width, scroll, Color(255,255,255,255)) + + if not IsValid(outputEntity) then + local traceData = util.GetPlayerTrace(LocalPlayer()) + traceData.filter = { LocalPlayer() } + + traceData.collisiongroup = LAST_SHARED_COLLISION_GROUP + local traceResult = util.TraceLine(traceData) + if WireLib.HasPorts(traceResult.Entity) then + self:UpdateTraceForSurface(traceResult, traceResult.Entity:GetParent()) + end + render.AddBeam(traceResult.HitPos, width, scroll+(traceResult.HitPos-start):Length()/10, Color(100,100,100,255)) + else + local outputPos = wiring[6] + outputPos = outputEntity:LocalToWorld(outputPos) + render.AddBeam(outputPos, width, scroll+(outputPos-start):Length()/10, Color(100,100,100,255)) + end + render.EndBeam() + end + end) + end + + + ----------------------------------------------------------------- + -- Wiring Render + -- This table is almost the same as TOOL.Wiring, + -- but is organized differently and is used to + -- render the "Selected" box on screen properly. + ----------------------------------------------------------------- + function TOOL:IsMatch( inputname, inputtype, outputname, outputtype, checkcreated ) + if checkcreated then + return (outputname == inputname and outputtype == inputtype) or (inputtype == "WIRELINK" and (outputname == "wirelink" or outputname == "Create Wirelink") and outputtype == "WIRELINK") or + (inputtype == "ENTITY" and (outputname == "entity" or outputname == "Create Entity") and outputtype == "ENTITY") + else + return (outputname == inputname and outputtype == inputtype) + end + end + + function TOOL:WiringRenderFind( inputname, inputtype ) + for i=1,#self.WiringRender do + local wiringrender = self.WiringRender[i] + if self:IsMatch( wiringrender[1], wiringrender[2], inputname, inputtype ) then + return wiringrender, i + end + end + end + + function TOOL:WiringRenderRemove( inputname, inputtype ) + local wiringrender, idx = self:WiringRenderFind( inputname, inputtype ) + + if wiringrender then + wiringrender[9] = wiringrender[9] - 1 + if wiringrender[9] == 0 then + table.remove( self.WiringRender, idx ) + end + end + end + + function TOOL:WiringRenderAdd( inputname, inputtype ) + local wiringrender = self:WiringRenderFind( inputname, inputtype ) + + if wiringrender then + wiringrender[9] = wiringrender[9] + 1 + else + local wiringrender = { inputname, inputtype } + --wiringrender[8] = inputtype + wiringrender[9] = 1 + self.WiringRender[#self.WiringRender+1] = wiringrender + end + end + + function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { Text = "#Tool.wire_adv.name", Description = "#Tool.wire_adv.desc2" }) + WireToolHelpers.MakePresetControl(panel, "wire_adv") + + panel:NumSlider("#WireTool_width", "wire_adv_width", 0, 5, 2) + local matselect = panel:AddControl( "RopeMaterial", { Label = "#WireTool_material", convar = "wire_adv_material" } ) + matselect:AddMaterial("Arrowire", "arrowire/arrowire") + matselect:AddMaterial("Arrowire2", "arrowire/arrowire2") + + panel:AddControl("Color", { + Label = "#WireTool_colour", + Red = "wire_adv_r", + Green = "wire_adv_g", + Blue = "wire_adv_b" + }) + + panel:CheckBox("#WireTool_stick", "wire_adv_stick") + end + +end diff --git a/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_debugger.lua b/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_debugger.lua new file mode 100644 index 0000000..a44e668 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_debugger.lua @@ -0,0 +1,474 @@ +TOOL.Category = "Tools" +TOOL.Name = "Debugger" +TOOL.Command = nil +TOOL.ConfigName = "" +TOOL.Tab = "Wire" + +if ( CLIENT ) then + language.Add( "Tool.wire_debugger.name", "Debugging Tool" ) + language.Add( "Tool.wire_debugger.desc", "Shows selected components info on the HUD." ) + language.Add( "Tool.wire_debugger.left", "Add component to HUD" ) + language.Add( "Tool.wire_debugger.right", "Remove component from HUD" ) + language.Add( "Tool.wire_debugger.reload", "Clear HUD" ) + language.Add( "Tool_wire_debugger_showports", "Show overlay of ports in HUD" ) + language.Add( "Tool_wire_debugger_orientvertical", "Orient the Inputs/Outputs Vertically" ) + TOOL.Information = { "left", "right", "reload" } +end +if SERVER then + util.AddNetworkString("WireDbgCount") + util.AddNetworkString("WireDbg") +end + +TOOL.ClientConVar[ "showports" ] = "1" +TOOL.ClientConVar[ "orientvertical" ] = "1" + +local Components = {} +local UpdateLineCount +local dbg_line_cache + +local function IsWire(entity) --try to find out if the entity is wire + if (WireLib.HasPorts(entity)) then return true end + --if entity.IsWire == true then return true end --this shold always be true if the ent is wire compatible, but only is if the base of the entity is "base_wire_entity" THIS NEEDS TO BE FIXED <-- CHALLENGE ACCEPTED! -Grocel + --if entity.Inputs or entity.Outputs then return true end --this is how the wire STool gun does it + return false +end + +local function stopDebuggingEntity(ply, ent) + for k,cmp in ipairs(Components[ply]) do + if (cmp == ent) then + table.remove(Components[ply], k) + if SERVER then + dbg_line_cache[ply] = nil + end + return true + end + end + if not next(Components[ply]) then + if SERVER then + UpdateLineCount(ply, 0) + end + Components[ply] = nil + end +end + +properties.Add("wire_debugger_start", { + MenuLabel = "Debug", + MenuIcon = "icon16/bug.png", + Order = 500, + + Filter = function(self,ent,ply) + if not IsValid(ent) then return false end + if not IsWire(ent) then return false end + if not hook.Run("CanTool", ply, WireLib.dummytrace(ent), "wire_debugger") then return false end + if Components[ply] then + for _, cmp in ipairs(Components[ply]) do + if (cmp == ent) then return false end + end + end + return true + end, + + Action = function(self,ent) + self:MsgStart() + net.WriteEntity(ent) + self:MsgEnd() + local ply = LocalPlayer() + Components[ply] = Components[ply] or {} + table.insert(Components[ply], ent) + end, + + Receive = function(self,len,ply) + local ent = net.ReadEntity() + if not self:Filter(ent,ply) then return end + + Components[ply] = Components[ply] or {} + table.insert(Components[ply], ent) + end, +}) +properties.Add("wire_debugger_stop", { + MenuLabel = "Stop Debugging", + MenuIcon = "icon16/bug.png", + Order = 500, + + Filter = function(self,ent,ply) + if not IsValid(ent) then return false end + if not IsWire(ent) then return false end + if Components[ply] then + for _, cmp in ipairs(Components[ply]) do + if (cmp == ent) then return true end + end + end + return false + end, + + Action = function(self,ent) + self:MsgStart() + net.WriteEntity(ent) + self:MsgEnd() + local ply = LocalPlayer() + stopDebuggingEntity(ply, ent) + end, + + Receive = function(self,len,ply) + local ent = net.ReadEntity() + if not self:Filter(ent,ply) then return end + + stopDebuggingEntity(ply, ent) + end, +}) + +function TOOL:LeftClick(trace) + if not trace.Entity:IsValid() then return end + if not IsWire(trace.Entity) then return end + + local ply = self:GetOwner() + Components[ply] = Components[ply] or {} + + for _, cmp in ipairs(Components[ply]) do + if (cmp == trace.Entity) then return end + end + + table.insert(Components[ply], trace.Entity) + + return true +end + +if SERVER then + local dbg_linecount_cache = {} + function UpdateLineCount(ply, count) + if dbg_linecount_cache[ply] ~= count then + dbg_linecount_cache[ply] = count + net.Start("WireDbgCount") net.WriteUInt(count,16) net.Send(ply) + end + end +end + + +function TOOL:RightClick(trace) + if not trace.Entity:IsValid() then return end + if not IsWire(trace.Entity) then return end + + local ply = self:GetOwner() + if not Components[ply] then return end + + return stopDebuggingEntity(ply, trace.Entity) +end + +if CLIENT then + function TOOL:DrawHUD() + if self:GetClientNumber("showports") == 0 then return end + local ent = LocalPlayer():GetEyeTraceNoCursor().Entity + if not ent:IsValid() then return end + + local inputs, outputs = WireLib.GetPorts(ent) + + if inputs and #inputs ~= 0 then + surface.SetFont("Trebuchet24") + local boxh, boxw = 0,0 + for _, port in ipairs(inputs) do + local name, tp = unpack(port) + local text = tp == "NORMAL" and name or string.format("%s [%s]", name, tp) + port.text = text + port.y = boxh + local textw,texth = surface.GetTextSize(text) + if textw > boxw then boxw = textw end + boxh = boxh + texth + end + + local boxx, boxy = ScrW()/2-boxw-32, ScrH()/2-boxh/2 + draw.RoundedBox(8, + boxx-8, boxy-8, + boxw+16, boxh+16, + Color(109,146,129,192) + ) + + for _, port in ipairs(inputs) do + surface.SetTextPos(boxx,boxy+port.y) + if port[4] then + surface.SetTextColor(Color(255,0,0,255)) + else + surface.SetTextColor(Color(255,255,255,255)) + end + surface.DrawText(port.text) + port.text = nil + port.y = nil + end + end + + if outputs and #outputs ~= 0 then + surface.SetFont("Trebuchet24") + local boxh, boxw = 0,0 + for _, port in ipairs(outputs) do + local name, tp = unpack(port) + local text = tp == "NORMAL" and name or string.format("%s [%s]", name, tp) + port.text = text + port.y = boxh + local textw,texth = surface.GetTextSize(text) + if textw > boxw then boxw = textw end + boxh = boxh + texth + end + + local boxx, boxy = ScrW()/2+32, ScrH()/2-boxh/2 + draw.RoundedBox(8, + boxx-8, boxy-8, + boxw+16, boxh+16, + Color(109,146,129,192) + ) + + for _, port in ipairs(outputs) do + surface.SetTextPos(boxx,boxy+port.y) + surface.SetTextColor(Color(255,255,255,255)) + surface.DrawText(port.text) + port.text = nil + port.y = nil + end + end + end +end + +function TOOL:Reload(trace) + Components[self:GetOwner()] = nil + if (CLIENT) then return end + UpdateLineCount(self:GetOwner(), 0) + dbg_line_cache[self:GetOwner()] = nil +end + + +if (SERVER) then + WireToolHelpers.SetupSingleplayerClickHacks(TOOL) + + dbg_line_cache = {} + + local formatPort = WireLib.Debugger.formatPort + + local function updateForPlayer(ply, cmps) + if not (IsValid(ply) and ply:IsPlayer()) then -- if player has left, clear the hud + Components[ply] = nil + return + end + + local OrientVertical = ply:GetInfoNum("wire_debugger_orientvertical", 0) ~= 0 + + -- TODO: Add EntityRemoved hook to clean up Components array. + table.Compact(cmps, function(cmp) return cmp:IsValid() and IsWire(cmp) end) + + UpdateLineCount(ply, #cmps) + + if #cmps == 0 then Components[ply] = nil return end + + for l,cmp in ipairs(cmps) do + local dbginfo = cmp.WireDebugName + if not dbginfo or dbginfo == "No Name" then + dbginfo = cmp:GetClass() + end + dbginfo = dbginfo .. " (" ..cmp:EntIndex() .. ") - " + + if (cmp.Inputs and table.Count(cmp.Inputs) > 0) then + if OrientVertical then + dbginfo = dbginfo .. "\n" + end + dbginfo = dbginfo .. "IN: " + if OrientVertical then + dbginfo = dbginfo .. "\n" + end + for k, Input in pairs_sortvalues(cmp.Inputs, WireLib.PortComparator) do + if formatPort[Input.Type] then + dbginfo = dbginfo .. k .. ":" .. formatPort[Input.Type](Input.Value or WireLib.DT[Input.Type].Zero, OrientVertical) + if OrientVertical then + dbginfo = dbginfo .. "\n" + else + dbginfo = dbginfo .. " " + end + end + end + end + + if (cmp.Outputs and table.Count(cmp.Outputs) > 0) then + if(cmp.Inputs and table.Count(cmp.Inputs) > 0) then + dbginfo = dbginfo .. "\n " + end + if not cmp.Inputs and OrientVertical then + dbginfo = dbginfo .. "\n" + end + dbginfo = dbginfo .. "OUT: " + if OrientVertical then + dbginfo = dbginfo .. "\n" + end + for k, Output in pairs_sortvalues(cmp.Outputs, WireLib.PortComparator) do + if formatPort[Output.Type] then + dbginfo = dbginfo .. k .. ":" .. formatPort[Output.Type](Output.Value or WireLib.DT[Output.Type].Zero, OrientVertical) + if OrientVertical then + dbginfo = dbginfo .. "\n" + else + dbginfo = dbginfo .. " " + end + end + end + end + + if (not cmp.Inputs) and (not cmp.Outputs) then + dbginfo = dbginfo .. "No info" + end + + dbg_line_cache[ply] = dbg_line_cache[ply] or {} + if (dbg_line_cache[ply][l] ~= dbginfo) then + --split the message up into managable chuncks and send them + net.Start("WireDbg") + net.WriteBit(OrientVertical) + net.WriteUInt(l,16) + net.WriteString(dbginfo) + net.Send(ply) + + dbg_line_cache[ply][l] = dbginfo + end + end + end + + local function Wire_DebuggerThink() + for ply,cmps in pairs(Components) do + updateForPlayer(ply, cmps) + end + end + + timer.Create("Wire_DebuggerThink", game.SinglePlayer() and 0.05 or 0.1, 0, Wire_DebuggerThink) + -- hook.Add("Think", "Wire_DebuggerThink", Wire_DebuggerThink) + +end + + +if (CLIENT) then + + local dbg_lines = {} + local dgb_orient_vert = false + local BoxWidth = 300 + local LastBoxUpdate = 0 + + local function DebuggerDrawHUD() + if not next(dbg_lines) then return end + + --setup the font + surface.SetFont("Default") + + --buid the table of entries + local Line_Count = 0 + local ColorType = 0 + local Entries = {} + for _, Line in pairs(dbg_lines) do + local CurEntry = {} + CurEntry.Lines = {} + + local ExplodeLines = string.Explode("\n", Line) + + for Index, ExplodeLine in ipairs(ExplodeLines) do --break it into multible lines for 1 entry + if string.Trim(ExplodeLine) ~= "" then + + local XPos = 0 + if(Index > 1) then + if dgb_orient_vert then --if the string is not the first and it is vertical, line it up acordingly + if(string.Trim(ExplodeLine) == "OUT:" or string.Trim(ExplodeLine) == "IN:") then + XPos = 17 + else + XPos = 42 + end + else --if the string is not the first and it is not vertical, line it up with the IN on the first line + if(CurEntry.Lines[1].LineText and string.find(CurEntry.Lines[1].LineText,"IN:")) then + local TextPos = string.find(CurEntry.Lines[1].LineText,"IN:")-1 + XPos = surface.GetTextSize( string.Left(CurEntry.Lines[1].LineText, TextPos) ) + end + end + + end + + local TrimLine = { + LineText = string.Trim(ExplodeLine), + OffsetPos = { XPos, Line_Count*14 } --move the next text down some for each line + } + table.insert(CurEntry.Lines, TrimLine ) + Line_Count = Line_Count+1 + + end + end + + --set the color + if(ColorType == 0) then + CurEntry.TextColor = Color(255,255,255) + else + CurEntry.TextColor = Color(130,255,158) + end + + --put it in the table + table.insert(Entries, CurEntry) + + --switch the color + ColorType = 1-ColorType + end + + + + --determine the box width every second + if(LastBoxUpdate < CurTime()) then + local LongestWidth = 0 + local TextWidth + for _, Entry in ipairs(Entries) do + for _, Line in ipairs(Entry.Lines) do + TextWidth = surface.GetTextSize(string.Trim(Line.LineText)) + TextWidth = TextWidth+Line.OffsetPos[1] --offset it with the text's offset + + if(TextWidth > LongestWidth) then + LongestWidth = TextWidth + end + end + end + BoxWidth = LongestWidth+16 + LastBoxUpdate = CurTime()+1 + end + + + --move the box down if the active weapon is the tool gun + local MoveBox = 0 + if(LocalPlayer():IsValid() and LocalPlayer():IsPlayer()) then + if IsValid(LocalPlayer():GetActiveWeapon()) and LocalPlayer():GetActiveWeapon():GetClass() == "gmod_tool" then + MoveBox = 1 + end + else --return if the player is dead or non-existant + return + end + + + -- TODO: account for larger usage info boxes. + --draw the box + draw.RoundedBox(8, 2, 2+250*MoveBox, BoxWidth, Line_Count*14+16, Color(50, 50, 50, 128)) + + --step through all of the entries and their text to print them + for _, Entry in ipairs(Entries) do + for _, Line in ipairs(Entry.Lines) do + draw.Text({ + text = string.Trim(Line.LineText) or "", + font = "Default", + pos = { Line.OffsetPos[1]+10, 250*MoveBox+10+Line.OffsetPos[2] }, + color = Entry.TextColor + }) + + end + end + + end + hook.Add("HUDPaint", "DebuggerDrawHUD", DebuggerDrawHUD) + + net.Receive("WireDbgCount", function(netlen) + for k=net.ReadUInt(16)+1, #dbg_lines do + dbg_lines[k] = nil + end + end) + net.Receive("WireDbg", function(netlen) + dgb_orient_vert = net.ReadBit() ~= 0 + dbg_lines[net.ReadUInt(16)] = net.ReadString() + end) + +end + +function TOOL.BuildCPanel(panel) + panel:Help("#Tool.wire_debugger.desc") + panel:CheckBox("#Tool_wire_debugger_showports", "wire_debugger_showports") + panel:CheckBox("#Tool_wire_debugger_orientvertical", "wire_debugger_orientvertical") +end diff --git a/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_namer.lua b/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_namer.lua new file mode 100644 index 0000000..ef81858 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/gmod_tool/stools/wire_namer.lua @@ -0,0 +1,61 @@ +TOOL.Category = "Tools" +TOOL.Name = "Namer" +TOOL.Command = nil +TOOL.ConfigName = "" +TOOL.Tab = "Wire" + +if ( CLIENT ) then + language.Add( "Tool.wire_namer.name", "Naming Tool" ) + language.Add( "Tool.wire_namer.desc", "Names components." ) + language.Add( "Tool.wire_namer.left", "Set name" ) + language.Add( "Tool.wire_namer.right", "Copy name" ) + language.Add( "WireNamerTool_name", "Name:" ) + TOOL.Information = { "left", "right" } +end + +TOOL.ClientConVar[ "name" ] = "" + +local function SetName( Player, Entity, Data ) + if ( Data and Data.name ) then + Entity:SetNWString("WireName", Data.name) + duplicator.StoreEntityModifier( Entity, "WireName", Data ) + end +end +duplicator.RegisterEntityModifier( "WireName", SetName ) + +function TOOL:LeftClick(trace) + if (not trace.Entity:IsValid()) then return end + if (CLIENT) then return end + if (not trace.Entity.IsWire) then return end + + local name = self:GetClientInfo("name") + + //trace.Entity:SetNWString("WireName", name) + + //made the WireName duplicatable entmod (TAD2020) + SetName( Player, trace.Entity, {name = name} ) + + return true +end + + +function TOOL:RightClick(trace) + if (not trace.Entity:IsValid()) then return end + if (CLIENT) then return end + + local name = trace.Entity:GetNWString("WireName") + if (not name) then return end + + self:GetOwner():ConCommand('wire_namer_name "' .. name .. '"') +end + + +function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { Text = "#Tool.wire_namer.name", Description = "#Tool.wire_namer.desc" }) + + panel:AddControl("TextBox", { + Label = "#WireNamerTool_name", + Command = "wire_namer_name", + MaxLength = "20" + }) +end diff --git a/garrysmod/addons/feature-wire/lua/weapons/laserpointer/cl_init.lua b/garrysmod/addons/feature-wire/lua/weapons/laserpointer/cl_init.lua new file mode 100644 index 0000000..a53018c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/laserpointer/cl_init.lua @@ -0,0 +1,50 @@ +include('shared.lua') +SWEP.PrintName = "Laser Pointer" +SWEP.Slot = 0 +SWEP.SlotPos = 4 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +local LASER = Material('cable/redlaser') + +function SWEP:Setup(ply) + if ply.GetViewModel and ply:GetViewModel():IsValid() then + local attachmentIndex = ply:GetViewModel():LookupAttachment("muzzle") + if attachmentIndex == 0 then attachmentIndex = ply:GetViewModel():LookupAttachment("1") end + if LocalPlayer():GetAttachment(attachmentIndex) then + self.VM = ply:GetViewModel() + self.Attach = attachmentIndex + end + end + if ply:IsValid() then + local attachmentIndex = ply:LookupAttachment("anim_attachment_RH") + if ply:GetAttachment(attachmentIndex) then + self.WM = ply + self.WAttach = attachmentIndex + end + end +end +function SWEP:Initialize() + self:Setup(self:GetOwner()) +end +function SWEP:Deploy(ply) + self:Setup(self:GetOwner()) +end + +function SWEP:ViewModelDrawn() + if self.Weapon:GetNWBool("Active") and self.VM then + //Draw the laser beam. + render.SetMaterial( LASER ) + render.DrawBeam(self.VM:GetAttachment(self.Attach).Pos, self:GetOwner():GetEyeTrace().HitPos, 2, 0, 12.5, Color(255, 0, 0, 255)) + end +end +function SWEP:DrawWorldModel() + self.Weapon:DrawModel() + if self.Weapon:GetNWBool("Active") and self.WM then + //Draw the laser beam. + render.SetMaterial( LASER ) + local posang = self.WM:GetAttachment(self.WAttach) + if not posang then self.WM = nil ErrorNoHalt("Laserpointer CL: Attachment lost, did they change model or something?\n") return end + render.DrawBeam(posang.Pos + posang.Ang:Forward()*10 + posang.Ang:Up()*4.4 + posang.Ang:Right(), self:GetOwner():GetEyeTrace().HitPos, 2, 0, 12.5, Color(255, 0, 0, 255)) + end +end diff --git a/garrysmod/addons/feature-wire/lua/weapons/laserpointer/init.lua b/garrysmod/addons/feature-wire/lua/weapons/laserpointer/init.lua new file mode 100644 index 0000000..62bc1ee --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/laserpointer/init.lua @@ -0,0 +1,56 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include('shared.lua') + +SWEP.Weight = 8 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.Receiver = nil +SWEP.Pointing = false + +function SWEP:Initialize() + self.Pointing = false +end + +function SWEP:Equip( newOwner ) + if IsValid(newOwner.LasReceiver) then + self.Receiver = newOwner.LasReceiver + newOwner.LasReceiver = nil + newOwner:PrintMessage( HUD_PRINTTALK, "Relinked Sucessfully" ) + end +end + +function SWEP:PrimaryAttack() + self.Pointing = !self.Pointing + self.Weapon:SetNWBool("Active", self.Pointing) + if self.Pointing and IsValid(self.Receiver) then + Wire_TriggerOutput(self.Receiver,"Active",1) + else + Wire_TriggerOutput(self.Receiver,"Active",0) + end +end + +function SWEP:SecondaryAttack() + local trace = self:GetOwner():GetEyeTrace() + + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_las_receiver" and gamemode.Call("CanTool", self:GetOwner(), trace, "wire_las_receiver") then + self.Receiver = trace.Entity + self:GetOwner():PrintMessage( HUD_PRINTTALK, "Linked Sucessfully" ) + return true + end +end + +function SWEP:Think() + if(self.Pointing && self.Receiver && self.Receiver:IsValid())then + local trace = self:GetOwner():GetEyeTrace() + local point = trace.HitPos + if (COLOSSAL_SANDBOX) then point = point * 6.25 end + Wire_TriggerOutput(self.Receiver, "X", point.x) + Wire_TriggerOutput(self.Receiver, "Y", point.y) + Wire_TriggerOutput(self.Receiver, "Z", point.z) + Wire_TriggerOutput(self.Receiver, "Pos", point) + Wire_TriggerOutput(self.Receiver, "RangerData", trace) + self.Receiver.VPos = point + end +end diff --git a/garrysmod/addons/feature-wire/lua/weapons/laserpointer/shared.lua b/garrysmod/addons/feature-wire/lua/weapons/laserpointer/shared.lua new file mode 100644 index 0000000..fe73c6c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/laserpointer/shared.lua @@ -0,0 +1,21 @@ +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "Left Click to designate targets. Right click to select laser receiver." +SWEP.Category = "Wiremod" + +SWEP.Spawnable = true; +SWEP.AdminOnly = false + +SWEP.viewModel = "models/weapons/v_pistol.mdl"; +SWEP.worldModel = "models/weapons/w_pistol.mdl"; + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" diff --git a/garrysmod/addons/feature-wire/lua/weapons/remotecontroller.lua b/garrysmod/addons/feature-wire/lua/weapons/remotecontroller.lua new file mode 100644 index 0000000..99561f9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/weapons/remotecontroller.lua @@ -0,0 +1,114 @@ +AddCSLuaFile() + +SWEP.Author = "Divran" -- Originally by ShaRose, rewritten by Divran at 2011-04-03 +SWEP.Contact = "" +SWEP.Purpose = "Remote control for Pod Controllers in wire." +SWEP.Instructions = "Left Click on Pod Controller to link up, and use to start controlling." +SWEP.Category = "Wiremod" + +SWEP.PrintName = "Remote Control" +SWEP.Slot = 0 +SWEP.SlotPos = 4 +SWEP.DrawAmmo = false + +SWEP.Weight = 1 +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.viewModel = "models/weapons/v_pistol.mdl" +SWEP.worldModel = "models/weapons/w_pistol.mdl" + +SWEP.HideFromHelp = true + +if CLIENT then return end + +function SWEP:PrimaryAttack() + local ply = self:GetOwner() + local trace = ply:GetEyeTrace() + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_pod" and gamemode.Call("PlayerUse", ply, trace.Entity) then + self.Linked = trace.Entity + ply:ChatPrint("Remote Controller linked.") + end +end + +function SWEP:Holster() + if self.Linked then + self:Off() + end + + return true +end + +function SWEP:Deploy() + return true +end + +function SWEP:OnDrop() + if not self.Linked then return end + + self:Off() + self.Linked = nil +end + +function SWEP:On() + local ply = self:GetOwner() + + if IsValid(self.Linked) and self.Linked.HasPly and self.Linked:HasPly() then + if hook.Run("CanTool", ply, WireLib.dummytrace(self.Linked), "remotecontroller") then + if self.Linked.RC then + self.Linked:RCEject(self.Linked:GetPly()) + else + self.Linked:GetPly():ExitVehicle() + end + else + ply:ChatPrint("Pod is in use.") + return + end + end + + self.Active = true + self.OldMoveType = not ply:InVehicle() and ply:GetMoveType() or MOVETYPE_WALK + ply:SetMoveType(MOVETYPE_NONE) + ply:DrawViewModel(false) + + if IsValid(self.Linked) and self.Linked.PlayerEntered then + self.Linked:PlayerEntered(ply, self) + end +end + +function SWEP:Off() + local ply = self:GetOwner() + + if self.Active then + ply:SetMoveType(self.OldMoveType or MOVETYPE_WALK) + end + + self.Active = nil + self.OldMoveType = nil + ply:DrawViewModel(true) + + if IsValid(self.Linked) and self.Linked:GetPly() == ply then + self.Linked:PlayerExited(ply) + end +end + +function SWEP:Think() + if not self.Linked then return end + + if self:GetOwner():KeyPressed(IN_USE) then + if not self.Active then + self:On() + else + self:Off() + end + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/cl_modelplug.lua b/garrysmod/addons/feature-wire/lua/wire/client/cl_modelplug.lua new file mode 100644 index 0000000..9898a64 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/cl_modelplug.lua @@ -0,0 +1,657 @@ +--Msg("=== Loading Wire Model Packs ===\n") + +CreateConVar("cl_showmodeltextbox", "0") + +--[[ +-- Loads and converts model lists from the old WireModelPacks format +do + local converted = {} + + + MsgN("WM: Loading models...") + for _,filename in ipairs( file.Find("WireModelPacks/*", "DATA") ) do + --for _,filename in ipairs{"bull_buttons.txt","bull_modelpack.txt","cheeze_buttons2.txt","default.txt","expression2.txt","wire_model_pack_1.txt","wire_model_pack_1plus.txt"} do + filename = "WireModelPacks/"..filename + print("Loading from WireModelPacks/"..filename) + local f = file.Read(filename, "DATA") + if f then + converted[#converted+1] = "-- Converted from "..filename + local packtbl = util.KeyValuesToTable(f) + for name,entry in pairs(packtbl) do + print(string.format("\tLoaded model %s => %s", name, entry.model)) + local categorytable = string.Explode(",", entry.categories or "none") or { "none" } + for _,cat in pairs(categorytable) do + list.Set( "Wire_"..cat.."_Models", entry.model, true ) + converted[#converted+1] = string.format('list.Set("Wire_%s_Models", "%s", true)', cat, entry.model) + end + end + converted[#converted+1] = "" + else + print("Error opening "..filename) + end + end + MsgN("End loading models") + + file.Write("converted.txt", table.concat(converted, "\n")) +end +]] + +-- +-- Add some more options to the stools +-- + +--screens with a GPULib setup +list.Set( "WireScreenModels", "models/props_lab/monitor01b.mdl", true ) +list.Set( "WireScreenModels", "models/props/cs_office/tv_plasma.mdl", true ) +list.Set( "WireScreenModels", "models/blacknecro/tv_plasma_4_3.mdl", true ) +list.Set( "WireScreenModels", "models/props/cs_office/computer_monitor.mdl", true ) +list.Set( "WireScreenModels", "models/kobilica/wiremonitorbig.mdl", true ) +list.Set( "WireScreenModels", "models/kobilica/wiremonitorsmall.mdl", true ) +list.Set( "WireScreenModels", "models/props/cs_assault/Billboard.mdl", true ) +list.Set( "WireScreenModels", "models/cheeze/pcb/pcb4.mdl", true ) +list.Set( "WireScreenModels", "models/cheeze/pcb/pcb6.mdl", true ) +list.Set( "WireScreenModels", "models/cheeze/pcb/pcb5.mdl", true ) +list.Set( "WireScreenModels", "models/cheeze/pcb/pcb7.mdl", true ) +list.Set( "WireScreenModels", "models/cheeze/pcb/pcb8.mdl", true ) +list.Set( "WireScreenModels", "models/cheeze/pcb2/pcb8.mdl", true ) +list.Set( "WireScreenModels", "models/props/cs_militia/reload_bullet_tray.mdl", true ) +list.Set( "WireScreenModels", "models/props_lab/workspace002.mdl", true ) +--list.Set( "WireScreenModels", "models/blacknecro/ledboard60.mdl", true ) --broken + +--TF2 Billboards +list.Set( "WireScreenModels", "models/props_mining/billboard001.mdl", true ) +list.Set( "WireScreenModels", "models/props_mining/billboard002.mdl", true ) + +--PHX3 +list.Set( "WireScreenModels", "models/hunter/plates/plate1x1.mdl", true ) +list.Set( "WireScreenModels", "models/hunter/plates/plate2x2.mdl", true ) +list.Set( "WireScreenModels", "models/hunter/plates/plate4x4.mdl", true ) +list.Set( "WireScreenModels", "models/hunter/plates/plate8x8.mdl", true ) +list.Set( "WireScreenModels", "models/hunter/plates/plate05x05.mdl", true ) +list.Set( "WireScreenModels", "models/hunter/blocks/cube1x1x1.mdl", true ) + +--screens that are transparent +list.Set( "WireScreenModels", "models/props_phx/construct/windows/window1x1.mdl", true ) + +--screens with out a GPULib setup (for the tools wire_panel and wire_screen) +list.Set( "WireNoGPULibScreenModels", "models/props_lab/monitor01b.mdl", true ) +list.Set( "WireNoGPULibScreenModels", "models/props/cs_office/tv_plasma.mdl", true ) +list.Set( "WireNoGPULibScreenModels", "models/props/cs_office/computer_monitor.mdl", true ) +list.Set( "WireNoGPULibScreenModels", "models/kobilica/wiremonitorbig.mdl", true ) +list.Set( "WireNoGPULibScreenModels", "models/kobilica/wiremonitorsmall.mdl", true ) + +--sounds +local WireSounds = { + ["Warning"] = "common/warning.wav", + ["Talk"] = "common/talk.wav", + ["Button"] = "buttons/button15.wav", + ["Denied"] = "buttons/weapon_cant_buy.wav", + ["Zap"] = "ambient/energy/zap2.wav", + ["Oh No"] = "vo/npc/male01/ohno.wav", + ["Yeah"] = "vo/npc/male01/yeah02.wav", + ["apc alarm"] = "ambient/alarms/apc_alarm_loop1.wav", + ["Coast Siren"] = "coast.siren_citizen", + ["Bunker Siren"] = "coast.bunker_siren1", + ["Alarm Bell"] = "d1_canals.Floodgate_AlarmBellLoop", + ["Engine Start"] = "ATV_engine_start", + ["Engine Stop"] = "ATV_engine_stop", + ["Zombie Breathe"] = "NPC_PoisonZombie.Moan1", + ["Idle Zombies"] = "Zombie.Idle", + ["Turret Alert"] = "NPC_FloorTurret.Alert", + ["Helicopter Rotor"] = "NPC_CombineGunship.RotorSound", + ["Heartbeat"] = "k_lab.teleport_heartbeat", + ["Breathing"] = "k_lab.teleport_breathing", +} +for k,v in pairs(WireSounds) do + list.Set("WireSounds",k,{wire_soundemitter_sound=v}); +end + + +--some extra wheels that wired wheels have +local wastelandwheels = { + "models/props_wasteland/wheel01a.mdl", + "models/props_wasteland/wheel02a.mdl", + "models/props_wasteland/wheel03a.mdl", + "models/props_wasteland/wheel03b.mdl" +} +for k,v in pairs(wastelandwheels) do + if file.Exists(v,"GAME") then + list.Set( "WheelModels", v, { wheel_rx = 90, wheel_ry = 0, wheel_rz = 90} ) + end +end + + +--Cheeze's Buttons Pack +local CheezesButtons = { + "models/cheeze/buttons/button_arm.mdl", + "models/cheeze/buttons/button_clear.mdl", + "models/cheeze/buttons/button_enter.mdl", + "models/cheeze/buttons/button_fire.mdl", + "models/cheeze/buttons/button_minus.mdl", + "models/cheeze/buttons/button_muffin.mdl", + "models/cheeze/buttons/button_plus.mdl", + "models/cheeze/buttons/button_reset.mdl", + "models/cheeze/buttons/button_set.mdl", + "models/cheeze/buttons/button_start.mdl", + "models/cheeze/buttons/button_stop.mdl", +} +for k,v in ipairs(CheezesButtons) do + if file.Exists(v,"GAME") then + list.Set( "ButtonModels", v, {} ) + list.Set( "Wire_button_Models", v, true ) + end +end +local CheezesSmallButtons = { + "models/cheeze/buttons/button_0.mdl", + "models/cheeze/buttons/button_1.mdl", + "models/cheeze/buttons/button_2.mdl", + "models/cheeze/buttons/button_3.mdl", + "models/cheeze/buttons/button_4.mdl", + "models/cheeze/buttons/button_5.mdl", + "models/cheeze/buttons/button_6.mdl", + "models/cheeze/buttons/button_7.mdl", + "models/cheeze/buttons/button_8.mdl", + "models/cheeze/buttons/button_9.mdl", +} +for k,v in ipairs(CheezesSmallButtons) do + if file.Exists(v,"GAME") then + list.Set( "ButtonModels", v, {} ) + list.Set( "Wire_button_small_Models", v, true ) + end +end + +local Buttons = { + "models/props_citizen_tech/Firetrap_button01a.mdl", + "models/props_c17/clock01.mdl", + "models/dav0r/buttons/switch.mdl", + "models/dav0r/buttons/button.mdl", + "models/cheeze/buttons2/air.mdl", + "models/cheeze/buttons2/go.mdl", + "models/cheeze/buttons2/3.mdl", + "models/cheeze/buttons2/right.mdl", + "models/cheeze/buttons2/alert.mdl", + "models/cheeze/buttons2/plus.mdl", + "models/cheeze/buttons2/activate.mdl", + "models/cheeze/buttons2/coolant.mdl", + "models/cheeze/buttons2/pwr_blue.mdl", + "models/cheeze/buttons2/6.mdl", + "models/cheeze/buttons2/easy.mdl", + "models/cheeze/buttons2/muffin.mdl", + "models/cheeze/buttons2/pwr_red.mdl", + "models/cheeze/buttons2/1.mdl", + "models/cheeze/buttons2/8.mdl", + "models/cheeze/buttons2/aim.mdl", + "models/cheeze/buttons2/compile.mdl", + "models/cheeze/buttons2/set.mdl", + "models/cheeze/buttons2/0.mdl", + "models/cheeze/buttons2/arm.mdl", + "models/cheeze/buttons2/test.mdl", + "models/cheeze/buttons2/left.mdl", + "models/cheeze/buttons2/pwr_green.mdl", + "models/cheeze/buttons2/clock.mdl", + "models/cheeze/buttons2/divide.mdl", + "models/cheeze/buttons2/fire.mdl", + "models/cheeze/buttons2/cake.mdl", + "models/cheeze/buttons2/clear.mdl", + "models/cheeze/buttons2/4.mdl", + "models/cheeze/buttons2/power.mdl", + "models/cheeze/buttons2/5.mdl", + "models/cheeze/buttons2/deactivate.mdl", + "models/cheeze/buttons2/down.mdl", + "models/cheeze/buttons2/minus.mdl", + "models/cheeze/buttons2/stop.mdl", + "models/cheeze/buttons2/energy.mdl", + "models/cheeze/buttons2/charge.mdl", + "models/cheeze/buttons2/overide.mdl", + "models/cheeze/buttons2/equals.mdl", + "models/cheeze/buttons2/up.mdl", + "models/cheeze/buttons2/toggle.mdl", + "models/cheeze/buttons2/reset.mdl", + "models/cheeze/buttons2/enter.mdl", + "models/cheeze/buttons2/2.mdl", + "models/cheeze/buttons2/start.mdl", + "models/cheeze/buttons2/multiply.mdl", + "models/cheeze/buttons2/7.mdl", + "models/cheeze/buttons2/9.mdl", + --animated buttons from here + "models/props_lab/freightelevatorbutton.mdl", + "models/props/switch001.mdl", + "models/props_combine/combinebutton.mdl", + "models/props_mining/control_lever01.mdl", + "models/props_mining/freightelevatorbutton01.mdl", + "models/props_mining/freightelevatorbutton02.mdl", + "models/props_mining/switch01.mdl", + "models/props_mining/switch_updown01.mdl", + "models/maxofs2d/button_01.mdl", + "models/maxofs2d/button_02.mdl", + "models/maxofs2d/button_03.mdl", + "models/maxofs2d/button_04.mdl", + "models/maxofs2d/button_05.mdl", + "models/maxofs2d/button_06.mdl", + "models/bull/buttons/toggle_switch.mdl", + "models/bull/buttons/rocker_switch.mdl", + "models/bull/buttons/key_switch.mdl", +} +for k,v in ipairs(Buttons) do + if file.Exists(v,"GAME") then + list.Set( "Wire_button_Models", v, true ) + end +end + +--Dynamic button materials +local WireDynamicButtonMaterials = { + ["No Material"] = "", + ["Clean"] = "bull/dynamic_button_clean", + ["0"] = "bull/dynamic_button_0", + ["1"] = "bull/dynamic_button_1", + ["2"] = "bull/dynamic_button_2", + ["3"] = "bull/dynamic_button_3", + ["4"] = "bull/dynamic_button_4", + ["5"] = "bull/dynamic_button_5", + ["6"] = "bull/dynamic_button_6", + ["7"] = "bull/dynamic_button_7", + ["8"] = "bull/dynamic_button_8", + ["9"] = "bull/dynamic_button_9" +} + +for k,v in pairs(WireDynamicButtonMaterials) do + list.Set("WireDynamicButtonMaterialsOn" ,k,{wire_dynamic_button_material_on =v}); + list.Set("WireDynamicButtonMaterialsOff",k,{wire_dynamic_button_material_off=v}); +end + +--Thrusters +--Jaanus Thruster Pack +--MsgN("\tJaanus' Thruster Pack") +local JaanusThrusters = { + "models/props_junk/garbage_metalcan001a.mdl", + "models/jaanus/thruster_flat.mdl", + "models/jaanus/thruster_invisi.mdl", + "models/jaanus/thruster_shoop.mdl", + "models/jaanus/thruster_smile.mdl", + "models/jaanus/thruster_muff.mdl", + "models/jaanus/thruster_rocket.mdl", + "models/jaanus/thruster_megaphn.mdl", + "models/jaanus/thruster_stun.mdl" +} +for k,v in pairs(JaanusThrusters) do + if file.Exists(v,"GAME") then + list.Set( "ThrusterModels", v, true ) + end +end + +local explosivemodels = { + "models/dav0r/tnt/tnt.mdl", + "models/Combine_Helicopter/helicopter_bomb01.mdl", + "models/jaanus/thruster_flat.mdl", + "models/props_c17/oildrum001.mdl", + "models/props_c17/oildrum001_explosive.mdl", + "models/props_phx/cannonball.mdl", + "models/props_phx/facepunch_barrel.mdl", + "models/props_phx/oildrum001.mdl", + "models/props_phx/oildrum001_explosive.mdl", + "models/props_phx/amraam.mdl", + "models/props_phx/mk-82.mdl", + "models/props_phx/rocket1.mdl", + "models/props_phx/torpedo.mdl", + "models/props_phx/ww2bomb.mdl", + "models/props_junk/plasticbucket001a.mdl", + "models/props_junk/PropaneCanister001a.mdl", + "models/props_junk/propane_tank001a.mdl", + "models/props_junk/PopCan01a.mdl", + "models/props_lab/jar01a.mdl", + "models/props_c17/canister_propane01a.mdl", + "models/props_c17/canister01a.mdl", + "models/props_c17/canister02a.mdl", + "models/props_wasteland/gaspump001a.mdl", + "models/props_junk/cardboard_box001a.mdl", + "models/props_junk/cardboard_box001b.mdl", + "models/props_junk/cardboard_box002a.mdl", + "models/props_junk/cardboard_box002b.mdl", + "models/props_junk/cardboard_box003a.mdl", + "models/props_junk/cardboard_box003b.mdl", + "models/props_junk/cardboard_box004a.mdl", + "models/props_junk/CinderBlock01a.mdl", + "models/props_junk/gascan001a.mdl", + "models/props_junk/TrafficCone001a.mdl", + "models/props_junk/metalgascan.mdl", + "models/props_junk/metal_paintcan001a.mdl", + "models/props_junk/wood_crate001a.mdl", + "models/props_junk/wood_crate002a.mdl", + "models/props_junk/wood_pallet001a.mdl", +} +for k,v in pairs(explosivemodels) do + if file.Exists(v,"GAME") then list.Set( "Wire_Explosive_Models", v, true ) end +end + +for k,v in pairs({ + "models/props_c17/canister01a.mdl", + "models/props_interiors/Furniture_Lamp01a.mdl", + "models/props_c17/oildrum001.mdl", + "models/props_phx/misc/smallcannon.mdl", + "models/props_c17/fountain_01.mdl" + }) do + if file.Exists(v,"GAME") then + list.Set( "Wire_Gimbal_Models", v, true ) + end +end + +local valuemodels = { + "models/kobilica/value.mdl", + "models/bull/gates/resistor.mdl", + "models/bull/gates/transistor1.mdl", + "models/bull/gates/transistor2.mdl", + "models/cheeze/wires/cpu.mdl", + "models/cheeze/wires/chip.mdl", + "models/cheeze/wires/ram.mdl", + "models/cheeze/wires/nano_value.mdl", -- This guy doesn't have a normal sized one in that folder +} +for k,v in pairs(valuemodels) do + if file.Exists(v,"GAME") then list.Set( "Wire_Value_Models", v, true ) end +end + +local teleportermodels = { + "models/props_c17/utilityconducter001.mdl", + "models/Combine_Helicopter/helicopter_bomb01.mdl", + "models/props_combine/combine_interface001.mdl", + "models/props_combine/combine_interface002.mdl", + "models/props_combine/combine_interface003.mdl", + "models/props_combine/combine_emitter01.mdl", + "models/props_junk/sawblade001a.mdl", + "models/props_combine/health_charger001.mdl", + "models/props_combine/suit_charger001.mdl", + "models/props_lab/reciever_cart.mdl", + "models/props_lab/reciever01a.mdl", + "models/props_lab/reciever01b.mdl", + "models/props_lab/reciever01d.mdl", + "models/props_c17/pottery03a.mdl", + "models/props_wasteland/laundry_washer003.mdl" +} +for k,v in pairs(teleportermodels) do + if file.Exists(v,"GAME") then list.Set( "WireTeleporterModels", v, true ) end +end + +local turretmodels = { + "models/weapons/w_smg1.mdl", + "models/weapons/w_smg_mp5.mdl", + "models/weapons/w_smg_mac10.mdl", + "models/weapons/w_rif_m4a1.mdl", + "models/weapons/w_357.mdl", + "models/weapons/w_shot_m3super90.mdl" +} +for k,v in pairs(turretmodels) do + if file.Exists(v,"GAME") then list.Set( "WireTurretModels", v, true ) end +end + +local satellitedish_models = { + "models/props_wasteland/prison_lamp001c.mdl", + "models/props_rooftop/satellitedish02.mdl", -- EP2, but its perfect +} +for k,v in pairs(satellitedish_models) do + if file.Exists(v,"GAME") then + list.Set( "Wire_satellitedish_Models", v, true ) + end +end + +--Beer's models +--MsgN("\tBeer's Model pack") + +--Keyboard +list.Set( "Wire_Keyboard_Models", "models/beer/wiremod/keyboard.mdl", true ) +list.Set( "Wire_Keyboard_Models", "models/jaanus/wiretool/wiretool_input.mdl", true ) +list.Set( "Wire_Keyboard_Models", "models/props/kb_mouse/keyboard.mdl", true ) +list.Set( "Wire_Keyboard_Models", "models/props_c17/computer01_keyboard.mdl", true ) + +--Hydraulic +list.Set( "Wire_Hydraulic_Models", "models/beer/wiremod/hydraulic.mdl", true ) +list.Set( "Wire_Hydraulic_Models", "models/jaanus/wiretool/wiretool_siren.mdl", true ) + +--GPS +list.Set( "Wire_GPS_Models", "models/beer/wiremod/gps.mdl", true ) +list.Set( "Wire_GPS_Models", "models/jaanus/wiretool/wiretool_speed.mdl", true ) + +--Numpad +list.Set( "Wire_Numpad_Models", "models/beer/wiremod/numpad.mdl", true ) +list.Set( "Wire_Numpad_Models", "models/jaanus/wiretool/wiretool_input.mdl", true ) +list.Set( "Wire_Numpad_Models", "models/jaanus/wiretool/wiretool_output.mdl", true ) + +--Water Sensor +list.Set( "Wire_WaterSensor_Models", "models/beer/wiremod/watersensor.mdl", true ) +list.Set( "Wire_WaterSensor_Models", "models/jaanus/wiretool/wiretool_range.mdl", true ) + +--Target Finder +list.Set( "Wire_TargetFinder_Models", "models/beer/wiremod/targetfinder.mdl", true ) +list.Set( "Wire_TargetFinder_Models", "models/props_lab/powerbox02d.mdl", true ) + +list.Set( "Wire_Forcer_Models", "models/jaanus/wiretool/wiretool_grabber_forcer.mdl", true ) +list.Set( "Wire_Forcer_Models", "models/jaanus/wiretool/wiretool_siren.mdl", true ) + +--Misc Tools (Entity Marker, Eye Pod, GpuLib Switcher, ect...) +list.Set( "Wire_Misc_Tools_Models", "models/jaanus/wiretool/wiretool_range.mdl", true ) +list.Set( "Wire_Misc_Tools_Models", "models/jaanus/wiretool/wiretool_siren.mdl", true ) +list.Set( "Wire_Misc_Tools_Models", "models/props_lab/powerbox02d.mdl", true ) + +--Laser Tools (Ranger, User, etc) +list.Set( "Wire_Laser_Tools_Models", "models/jaanus/wiretool/wiretool_range.mdl", true ) +list.Set( "Wire_Laser_Tools_Models", "models/jaanus/wiretool/wiretool_siren.mdl", true ) +list.Set( "Wire_Laser_Tools_Models", "models/jaanus/wiretool/wiretool_beamcaster.mdl", true ) + +list.Set( "Wire_Socket_Models", "models/props_lab/tpplugholder_single.mdl", true ) +list.Set( "Wire_Socket_Models", "models/bull/various/usb_socket.mdl", true ) +list.Set( "Wire_Socket_Models", "models/hammy/pci_slot.mdl", true ) +list.Set( "Wire_Socket_Models", "models/wingf0x/isasocket.mdl", true ) +list.Set( "Wire_Socket_Models", "models/wingf0x/altisasocket.mdl", true ) +list.Set( "Wire_Socket_Models", "models/wingf0x/ethernetsocket.mdl", true ) +list.Set( "Wire_Socket_Models", "models/wingf0x/hdmisocket.mdl", true ) + +-- Converted from WireModelPacks/wire_model_pack_1plus.txt +list.Set("Wire_radio_Models", "models/props_lab/reciever01b.mdl", true) +list.Set("Wire_pixel_Models", "models/jaanus/wiretool/wiretool_pixel_med.mdl", true) +list.Set("Wire_indicator_Models", "models/jaanus/wiretool/wiretool_pixel_med.mdl", true) +list.Set("Wire_waypoint_Models", "models/jaanus/wiretool/wiretool_waypoint.mdl", true) +list.Set("Wire_pixel_Models", "models/jaanus/wiretool/wiretool_pixel_sml.mdl", true) +list.Set("Wire_indicator_Models", "models/jaanus/wiretool/wiretool_pixel_sml.mdl", true) +list.Set("Wire_radio_Models", "models/props_lab/reciever01a.mdl", true) +list.Set("Wire_gate_Models", "models/jaanus/wiretool/wiretool_controlchip.mdl", true) +list.Set("Wire_chip_Models", "models/jaanus/wiretool/wiretool_controlchip.mdl", true) +list.Set("Wire_control_Models", "models/jaanus/wiretool/wiretool_controlchip.mdl", true) +list.Set("Wire_detonator_Models", "models/jaanus/wiretool/wiretool_detonator.mdl", true) +list.Set("Wire_beamcasting_Models", "models/jaanus/wiretool/wiretool_beamcaster.mdl", true) +list.Set("Wire_radio_Models", "models/props_lab/reciever01c.mdl", true) +list.Set("Wire_pixel_Models", "models/jaanus/wiretool/wiretool_pixel_lrg.mdl", true) +list.Set("Wire_indicator_Models", "models/jaanus/wiretool/wiretool_pixel_lrg.mdl", true) + +-- Converted from WireModelPacks/wire_model_pack_1.txt +list.Set("Wire_gate_Models", "models/cheeze/wires/amd_test.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/amd_test.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/mini_cpu.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/mini_cpu.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/ram.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/ram.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_logic.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_logic.mdl", true) +list.Set("Wire_gate_Models", "models/kobilica/transistorsmall.mdl", true) +list.Set("Wire_chip_Models", "models/kobilica/transistorsmall.mdl", true) +list.Set("Wire_gate_Models", "models/kobilica/transistor.mdl", true) +list.Set("Wire_chip_Models", "models/kobilica/transistor.mdl", true) +list.Set("Wire_radio_Models", "models/cheeze/wires/wireless_card.mdl", true) +list.Set("Wire_gate_Models", "models/cyborgmatt/capacitor_large.mdl", true) +list.Set("Wire_chip_Models", "models/cyborgmatt/capacitor_large.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_memory.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_memory.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_trig.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_trig.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_chip.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_chip.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/cpu2.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/cpu2.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_math.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_math.mdl", true) +list.Set("Wire_radio_Models", "models/cheeze/wires/router.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_select.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_select.mdl", true) +list.Set("Wire_gate_Models", "models/cyborgmatt/capacitor_medium.mdl", true) +list.Set("Wire_chip_Models", "models/cyborgmatt/capacitor_medium.mdl", true) +list.Set("Wire_gate_Models", "models/cyborgmatt/capacitor_small.mdl", true) +list.Set("Wire_chip_Models", "models/cyborgmatt/capacitor_small.mdl", true) +list.Set("Wire_speaker_Models", "models/killa-x/speakers/speaker_small.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_compare.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_compare.mdl", true) +list.Set("Wire_speaker_Models", "models/killa-x/speakers/speaker_medium.mdl", true) +list.Set("Wire_speaker_Models", "models/props_junk/garbage_metalcan002a.mdl", true) +list.Set("Wire_gate_Models", "models/kobilica/capacatitor.mdl", true) +list.Set("Wire_chip_Models", "models/kobilica/capacatitor.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/cpu.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/cpu.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/mini_chip.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/mini_chip.mdl", true) +list.Set("Wire_gate_Models", "models/kobilica/lowpolygate.mdl", true) +list.Set("Wire_chip_Models", "models/kobilica/lowpolygate.mdl", true) +list.Set("Wire_gate_Models", "models/cheeze/wires/nano_timer.mdl", true) +list.Set("Wire_chip_Models", "models/cheeze/wires/nano_timer.mdl", true) + +-- Converted from WireModelPacks/expression2.txt +list.Set("Wire_expr2_Models", "models/expression 2/cpu_controller.mdl", true) +list.Set("Wire_expr2_Models", "models/expression 2/cpu_microchip.mdl", true) +list.Set("Wire_expr2_Models", "models/expression 2/cpu_expression.mdl", true) + +-- Converted from WireModelPacks/default.txt +list.Set("Wire_pixel_Models", "models/segment2.mdl", true) +list.Set("Wire_indicator_Models", "models/segment2.mdl", true) +list.Set("Wire_indicator_Models", "models/props_trainstation/trainstation_clock001.mdl", true) +list.Set("Wire_pixel_Models", "models/segment.mdl", true) +list.Set("Wire_indicator_Models", "models/segment.mdl", true) +list.Set("Wire_gyroscope_Models", "models/bull/various/gyroscope.mdl", true) +list.Set("Wire_weight_Models", "models/props_interiors/pot01a.mdl", true) +list.Set("Wire_pixel_Models", "models/jaanus/wiretool/wiretool_siren.mdl", true) +list.Set("Wire_indicator_Models", "models/jaanus/wiretool/wiretool_siren.mdl", true) +list.Set("Wire_indicator_Models", "models/props_borealis/bluebarrel001.mdl", true) +list.Set("Wire_indicator_Models", "models/props_junk/TrafficCone001a.mdl", true) +list.Set("Wire_speaker_Models", "models/props_junk/garbage_metalcan002a.mdl", true) +list.Set("Wire_pixel_Models", "models/led2.mdl", true) +list.Set("Wire_indicator_Models", "models/led2.mdl", true) +list.Set("Wire_weight_Models", "models/props_lab/huladoll.mdl", true) +list.Set("Wire_radio_Models", "models/props_lab/binderblue.mdl", true) +list.Set("Wire_pixel_Models", "models/led.mdl", true) +list.Set("Wire_indicator_Models", "models/led.mdl", true) +list.Set("Wire_gyroscope_Models", "models/cheeze/wires/gyroscope.mdl", true) +list.Set("Wire_pixel_Models", "models/jaanus/wiretool/wiretool_range.mdl", true) +list.Set("Wire_indicator_Models", "models/jaanus/wiretool/wiretool_range.mdl", true) +list.Set("Wire_pixel_Models", "models/props_junk/PopCan01a.mdl", true) +list.Set("Wire_indicator_Models", "models/props_junk/PopCan01a.mdl", true) +list.Set("Wire_gate_Models", "models/jaanus/wiretool/wiretool_gate.mdl", true) +list.Set("Wire_chip_Models", "models/jaanus/wiretool/wiretool_gate.mdl", true) +list.Set("Wire_detonator_Models", "models/props_combine/breenclock.mdl", true) +list.Set("Wire_speaker_Models", "models/cheeze/wires/speaker.mdl", true) +list.Set("Wire_indicator_Models", "models/props_c17/clock01.mdl", true) +list.Set("Wire_indicator_Models", "models/props_c17/gravestone004a.mdl", true) + +-- Converted from WireModelPacks/cheeze_buttons2.txt +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/compile_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/arm_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/fire_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/left_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/clear_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/aim_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/1_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/up_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/plus_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/stop_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/minus_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/6_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/coolant_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/power_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/toggle_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/activate_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/overide_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/pwr_red_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/go_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/pwr_blue_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/test_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/equals_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/energy_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/divide_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/clock_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/charge_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/alert_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/enter_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/5_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/2_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/deactivate_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/7_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/0_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/cake_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/reset_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/multiply_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/down_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/pwr_green_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/3_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/4_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/set_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/start_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/right_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/easy_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/8_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/muffin_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/air_small.mdl", true) +list.Set("Wire_button_small_Models", "models/cheeze/buttons2/9_small.mdl", true) + +-- Converted from WireModelPacks/bull_modelpack.txt +list.Set("Wire_gate_Models", "models/bull/gates/processor.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/processor.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/resistor_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/resistor_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/microcontroller2_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/microcontroller2_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/transistor2_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/transistor2_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/transistor1.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/transistor1.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/logic_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/logic_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/resistor_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/resistor_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/microcontroller2.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/microcontroller2.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/logic.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/logic.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/processor_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/processor_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/microcontroller1_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/microcontroller1_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/capacitor_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/capacitor_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/capacitor_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/capacitor_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/transistor1_nano.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/transistor1_nano.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/microcontroller2_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/microcontroller2_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/microcontroller1_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/microcontroller1_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/resistor.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/resistor.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/transistor2.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/transistor2.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/capacitor.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/capacitor.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/transistor1_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/transistor1_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/microcontroller1.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/microcontroller1.mdl", true) +list.Set("Wire_speaker_Models", "models/bull/various/speaker.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/logic_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/logic_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/processor_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/processor_mini.mdl", true) +list.Set("Wire_gate_Models", "models/bull/gates/transistor2_mini.mdl", true) +list.Set("Wire_chip_Models", "models/bull/gates/transistor2_mini.mdl", true) +list.Set("Wire_speaker_Models", "models/bull/various/subwoofer.mdl", true) + +-- Converted from WireModelPacks/bull_buttons.txt +list.Set("Wire_dynamic_button_Models", "models/bull/dynamicbuttonmedium.mdl", true) +list.Set("Wire_dynamic_button_Models", "models/bull/dynamicbuttonflat.mdl", true) +list.Set("Wire_dynamic_button_Models", "models/bull/dynamicbutton.mdl", true) +list.Set("Wire_dynamic_button_small_Models", "models/bull/dynamicbuttonmedium_small.mdl", true) +list.Set("Wire_dynamic_button_small_Models", "models/bull/dynamicbutton_small.mdl", true) +list.Set("Wire_dynamic_button_small_Models", "models/bull/dynamicbuttonflat_small.mdl", true) +list.Set("Wire_dynamic_button_Models", "models/maxofs2d/button_05.mdl", true) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/cl_wire_map_interface.lua b/garrysmod/addons/feature-wire/lua/wire/client/cl_wire_map_interface.lua new file mode 100644 index 0000000..3135271 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/cl_wire_map_interface.lua @@ -0,0 +1,89 @@ +-- The client part of the wire map interface. +-- It's for the clientside wire ports adding and removing, also for the rendering stuff. +-- It's in in this folder, because point entities are serverside only. + +-- Removing wire stuff and other changes that were done. +local OverRiddenEnts = {} +local function RemoveWire(Entity) + if (!IsValid(Entity)) then return end + + local ID = Entity:EntIndex() + + Entity._NextRBUpdate = nil + Entity.ppp = nil + OverRiddenEnts[ID] = nil + WireLib._RemoveWire(ID) -- Remove entity, so it doesn't count as a wire able entity anymore. + + for key, value in pairs(Entity._Settings_WireMapInterfaceEnt or {}) do + if (!value or (value == 0) or (value == "")) then + Entity[key] = nil + else + Entity[key] = value + end + end + Entity._Settings_WireMapInterfaceEnt = nil +end + +-- Adding wire stuff and changes. +usermessage.Hook("WireMapInterfaceEnt", function(data) + local Entity = data:ReadEntity() + local Flags = data:ReadChar() + local Remove = (Flags == -1) + if (!WIRE_CLIENT_INSTALLED) then return end + if (!IsValid(Entity)) then return end + + if (Remove) then + RemoveWire(Entity) + return + end + + Entity._Settings_WireMapInterfaceEnt = {} + + if (bit.band(Flags, 1) > 0) then -- Protect in-/output entities from non-wire tools + Entity._Settings_WireMapInterfaceEnt.m_tblToolsAllowed = Entity.m_tblToolsAllowed or false + Entity.m_tblToolsAllowed = {"wire", "wire_adv", "wire_debugger", "wire_wirelink", "gui_wiring", "multi_wire"} + end + + if (bit.band(Flags, 2) > 0) then -- Protect in-/output entities from the physgun + Entity._Settings_WireMapInterfaceEnt.PhysgunDisabled = Entity.PhysgunDisabled or false + Entity.PhysgunDisabled = true + end + + local ID = Entity:EntIndex() + if (bit.band(Flags, 32) > 0) then -- Render Wires + OverRiddenEnts[ID] = true + else + OverRiddenEnts[ID] = nil + end +end) + +-- Render bounds updating +hook.Add("Think", "WireMapInterface_Think", function() + for ID, _ in pairs(OverRiddenEnts) do + local self = Entity(ID) + if (!IsValid(self) or !WIRE_CLIENT_INSTALLED) then + OverRiddenEnts[ID] = nil + + return + end + + if (CurTime() >= (self._NextRBUpdate or 0)) then + self._NextRBUpdate = CurTime() + math.random(30,100) / 10 + Wire_UpdateRenderBounds(self) + end + end +end) + +-- Rendering +hook.Add("PostDrawOpaqueRenderables", "WireMapInterface_Draw", function() + for ID, _ in pairs(OverRiddenEnts) do + local self = Entity(ID) + if (!IsValid(self) or !WIRE_CLIENT_INSTALLED) then + OverRiddenEnts[ID] = nil + + return + end + + Wire_Render(self) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/cl_wirelib.lua b/garrysmod/addons/feature-wire/lua/wire/client/cl_wirelib.lua new file mode 100644 index 0000000..70a69d8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/cl_wirelib.lua @@ -0,0 +1,253 @@ +local WIRE_SCROLL_SPEED = 0.5 +local WIRE_BLINKS_PER_SECOND = 2 +local CurPathEnt = {} +local Wire_DisableWireRender = 0 +WireLib.Wire_GrayOutWires = false + +WIRE_CLIENT_INSTALLED = 1 + +--Msg("loading materials\n") +list.Add( "WireMaterials", "cable/rope_icon" ) +list.Add( "WireMaterials", "cable/cable2" ) +list.Add( "WireMaterials", "cable/xbeam" ) +list.Add( "WireMaterials", "cable/redlaser" ) +list.Add( "WireMaterials", "cable/blue_elec" ) +list.Add( "WireMaterials", "cable/physbeam" ) +list.Add( "WireMaterials", "cable/hydra" ) +--new wire materials by Acegikmo +list.Add( "WireMaterials", "arrowire/arrowire" ) +list.Add( "WireMaterials", "arrowire/arrowire2" ) + +local mats = { + ["tripmine_laser"] = Material("tripmine_laser"), + ["Models/effects/comball_tape"] = Material("Models/effects/comball_tape") +} +for _,mat in pairs(list.Get( "WireMaterials" )) do + --Msg("loading material: ",mat,"\n") + mats[mat] = Material(mat) +end +local function getmat( mat ) + if mats[mat] == nil then + mats[mat] = Material(mat) + end + return mats[mat] +end +local beam_mat = mats["tripmine_laser"] +local beamhi_mat = mats["Models/effects/comball_tape"] + +local lastrender, scroll, shouldblink = 0, 0, false +function Wire_Render(ent) + if (Wire_DisableWireRender == 0) then + local wires = ent.WirePaths + if wires then + if next(wires) then + local t = CurTime() + if lastrender ~= t then + local w, f = math.modf(t*WIRE_BLINKS_PER_SECOND) + shouldblink = f < 0.5 + scroll = t*WIRE_SCROLL_SPEED + lastrender = t + end + + local blink = shouldblink and ent:GetNWString("BlinkWire") + + for net_name, wiretbl in pairs(wires) do + local width = wiretbl.Width + if width > 0 and blink ~= net_name then + local start = wiretbl.StartPos + if IsValid(ent) then start = ent:LocalToWorld(start) end + local color = wiretbl.Color + if WireLib.Wire_GrayOutWires then + local h, s, v = ColorToHSV(color) + v = 0.175 + local tmpColor = HSVToColor(h, s, v) + color = Color(tmpColor.r, tmpColor.g, tmpColor.b, tmpColor.a) -- HSVToColor does not return a proper Color structure. + end + + local nodes = wiretbl.Path + local len = #nodes + if len>0 then + render.SetMaterial(getmat(wiretbl.Material)) + render.StartBeam(len * 2 + 1) + render.AddBeam(start, width, scroll, color) + + for j=1, len do + local node = nodes[j] + local node_ent = node.Entity + if IsValid(node_ent) then + local endpos = node_ent:LocalToWorld(node.Pos) + + scroll = scroll+(endpos-start):Length()/10 + render.AddBeam(endpos, width, scroll, color) + render.AddBeam(endpos, width, scroll, color) -- A second beam in the same position ensures the line stays consistent and doesn't change width/become distorted. + + start = endpos + end + end + render.EndBeam() + end + end + end + end + else + ent.WirePaths = {} + net.Start("WireLib.Paths.RequestPaths") + net.WriteEntity(ent) + net.SendToServer() + end + end +end + + +local function Wire_GetWireRenderBounds(ent) + if not IsValid(ent) then return end + + local bbmin = ent:OBBMins() + local bbmax = ent:OBBMaxs() + + if ent.WirePaths then + for net_name, wiretbl in pairs(ent.WirePaths) do + local nodes = wiretbl.Path + local len = #nodes + for j=1, len do + local node_ent = nodes[j].Entity + local nodepos = nodes[j].Pos + if (node_ent:IsValid()) then + nodepos = ent:WorldToLocal(node_ent:LocalToWorld(nodepos)) + + if (nodepos.x < bbmin.x) then bbmin.x = nodepos.x end + if (nodepos.y < bbmin.y) then bbmin.y = nodepos.y end + if (nodepos.z < bbmin.z) then bbmin.z = nodepos.z end + if (nodepos.x > bbmax.x) then bbmax.x = nodepos.x end + if (nodepos.y > bbmax.y) then bbmax.y = nodepos.y end + if (nodepos.z > bbmax.z) then bbmax.z = nodepos.z end + end + end + end + end + + if (ent.ExtraRBoxPoints) then + for _,point_l in pairs( ent.ExtraRBoxPoints ) do + local point = point_l + if (point.x < bbmin.x) then bbmin.x = point.x end + if (point.y < bbmin.y) then bbmin.y = point.y end + if (point.z < bbmin.z) then bbmin.z = point.z end + if (point.x > bbmax.x) then bbmax.x = point.x end + if (point.y > bbmax.y) then bbmax.y = point.y end + if (point.z > bbmax.z) then bbmax.z = point.z end + end + end + + return bbmin, bbmax +end + + +function Wire_UpdateRenderBounds(ent) + local bbmin, bbmax = Wire_GetWireRenderBounds(ent) + ent:SetRenderBounds(bbmin, bbmax) +end + +local function WireDisableRender(pl, cmd, args) + if args[1] then + Wire_DisableWireRender = tonumber(args[1]) + end + Msg("\nWire DisableWireRender/WireRenderMode = "..tostring(Wire_DisableWireRender).."\n") +end +concommand.Add( "cl_Wire_DisableWireRender", WireDisableRender ) +concommand.Add( "cl_Wire_SetWireRenderMode", WireDisableRender ) + + +function Wire_DrawTracerBeam( ent, beam_num, hilight, beam_length ) + local beam_length = beam_length or ent:GetBeamLength(beam_num) + if (beam_length > 0) then + + local x, y = 0, 0 + if (ent.GetSkewX and ent.GetSkewY) then + x, y = ent:GetSkewX(beam_num), ent:GetSkewY(beam_num) + end + + local start, ang = ent:GetPos(), ent:GetAngles() + + if (ent.ls != start or ent.la != ang or ent.ll != beam_length or ent.lx != x or ent.ly != y) then + ent.ls, ent.la = start, ang + + if (ent.ll != beam_length or ent.lx != x or ent.ly != y) then + ent.ll, ent.lx, ent.ly = beam_length, x, y + + if (x == 0 and y == 0) then + ent.endpos = start + (ent:GetUp() * beam_length) + else + local skew = Vector(x, y, 1) + skew = skew*(beam_length/skew:Length()) + local beam_x = ent:GetRight()*skew.x + local beam_y = ent:GetForward()*skew.y + local beam_z = ent:GetUp()*skew.z + ent.endpos = start + beam_x + beam_y + beam_z + end + ent.ExtraRBoxPoints = ent.ExtraRBoxPoints or {} + ent.ExtraRBoxPoints[beam_num] = ent:WorldToLocal(ent.endpos) + else + ent.endpos = ent:LocalToWorld(ent.ExtraRBoxPoints[beam_num]) + end + end + + local trace = {} + trace.start = start + trace.endpos = ent.endpos + trace.filter = { ent } + if ent:GetNWBool("TraceWater") then trace.mask = MASK_ALL end + trace = util.TraceLine(trace) + + render.SetMaterial(beam_mat) + render.DrawBeam(start, trace.HitPos, 6, 0, 10, ent:GetColor()) + if (hilight) then + render.SetMaterial(beamhi_mat) + render.DrawBeam(start, trace.HitPos, 6, 0, 10, Color(255,255,255,255)) + end + end +end + +hook.Add("InitPostEntity", "language_strings", function() + for class, tbl in pairs(scripted_ents.GetList()) do + if tbl.t.PrintName and tbl.t.PrintName ~= "" then + language.Add( class, tbl.t.PrintName ) + end + end +end) + +if not CanRunConsoleCommand then + function CanRunConsoleCommand() return false end + hook.Add("Initialize", "CanRunConsoleCommand", function() + function CanRunConsoleCommand() return true end + end) +end + +function Derma_StringRequestNoBlur(...) + local f = math.max + + function math.max(...) + local ret = f(...) + + for i = 1,20 do + local name, value = debug.getlocal(2, i) + if name == "Window" then + value:SetBackgroundBlur( false ) + break + end + end + + return ret + end + local ok, ret = xpcall(Derma_StringRequest, debug.traceback, ...) + math.max = f + + if not ok then error(ret, 0) end + return ret +end + +function WireLib.hud_debug(text, oneframe) + hook.Add("HUDPaint","wire_hud_debug",function() + if oneframe then hook.Remove("HUDPaint","wire_hud_debug") end + draw.DrawText(text,"Trebuchet24",10,200,Color(255,255,255,255),0) + end) +end \ No newline at end of file diff --git a/garrysmod/addons/feature-wire/lua/wire/client/customspawnmenu.lua b/garrysmod/addons/feature-wire/lua/wire/client/customspawnmenu.lua new file mode 100644 index 0000000..e2487da --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/customspawnmenu.lua @@ -0,0 +1,732 @@ + +local PANEL = {} + +AccessorFunc( PANEL, "m_TabID", "TabID" ) + +local expand_all = CreateConVar( "wire_tool_menu_expand_all", 0, {FCVAR_ARCHIVE} ) +local separate_wire_extras = CreateConVar( "wire_tool_menu_separate_wire_extras", 1, {FCVAR_ARCHIVE} ) +local hide_duplicates = CreateConVar( "wire_tool_menu_hide_duplicates", 0, {FCVAR_ARCHIVE} ) +local custom_for_all_tabs = CreateConVar( "wire_tool_menu_custom_menu_for_all_tabs", 0, {FCVAR_ARCHIVE} ) +local tab_width = CreateConVar( "wire_tool_menu_tab_width", -1, {FCVAR_ARCHIVE} ) +local horizontal_divider_width = CreateConVar( "wire_tool_menu_horizontal_divider_width", 0.28, {FCVAR_ARCHIVE} ) +local custom_icons = CreateConVar( "wire_tool_menu_custom_icons", 1, {FCVAR_ARCHIVE} ) +local autocollapse = CreateConVar( "wire_tool_menu_autocollapse", 0, {FCVAR_ARCHIVE} ) + +-- Helper functions +local function expandall( bool, nodes ) + for i=1,#nodes do + if nodes[i].m_bExpanded ~= bool then + nodes[i]:SetExpanded( bool ) + if nodes[i].WireCookieText then cookie.Set( nodes[i].WireCookieText, bool and 1 or 0 ) end + end + + if nodes[i].ChildNodes then + expandall( bool, nodes[i].ChildNodes:GetChildren() ) + end + end +end + +local function expandbycookie( nodes ) + for i=1,#nodes do + local b = cookie.GetNumber( nodes[i].WireCookieText ) + + if b and b == 1 then + nodes[i]:SetExpanded( b and b == 1 ) + + if nodes[i].ChildNodes then + expandbycookie( nodes[i].ChildNodes:GetChildren() ) + end + else + nodes[i]:SetExpanded( false ) + end + end +end + + +---------------------------------------------------------------------- +-- Init +---------------------------------------------------------------------- +function PANEL:Init() + self.Divider = vgui.Create( "DHorizontalDivider", self ) + self.Divider:Dock( FILL ) + self.Divider:SetDividerWidth( 6 ) + + local width = tab_width:GetInt() + local divider_width = horizontal_divider_width:GetFloat() + if width > ScrW() * 0.6 then -- too big! you won't be able to see the rest of the spawn menu if it's this big, let's make it smaller + width = ScrW() * 0.6 + elseif width == -1 then -- set up default value + width = 390 + if ScrW() > 1600 then width = 548 + elseif ScrW() > 1280 then width = 460 end + elseif width < 390 then -- too small! you won't be able to see the tools, make it bigger + width = 390 + end + + if width ~= tab_width:GetInt() then -- things changed, update convars + divider_width = 0.28 -- reset horizontal divider width + RunConsoleCommand( "wire_tool_menu_tab_width", width ) + RunConsoleCommand( "wire_tool_menu_horizontal_divider_width", divider_width ) + end + + self:SetWide( width ) + self.Divider:SetLeftWidth( width * divider_width ) + + local old = self.Divider.OnMouseReleased + function self.Divider.OnMouseReleased( ... ) + local width_percent = math.Round(self.Divider:GetLeftWidth() / self:GetWide(),2) + RunConsoleCommand( "wire_tool_menu_horizontal_divider_width", width_percent ) + old( ... ) + end + + local LeftPanel = vgui.Create( "DPanel" ) + self.Divider:SetLeft( LeftPanel ) + + local SearchBoxPanel = vgui.Create( "DPanel", LeftPanel ) + SearchBoxPanel:SetTall( 44 ) + SearchBoxPanel:DockPadding( 2,2,2,2 ) + SearchBoxPanel:Dock( TOP ) + + self.SearchBox = vgui.Create( "DTextEntry", SearchBoxPanel ) + self.SearchBox:DockMargin( 2, 2, 2, 0 ) + self.SearchBox:Dock( TOP ) + self:SetupSearchbox() + + local ExpandAll = vgui.Create( "DCheckBoxLabel", SearchBoxPanel ) -- create this here so that it's below the slider + + self.List = vgui.Create( "DTree", LeftPanel ) + + ExpandAll:SetText( "Expand All" ) + ExpandAll:SetConVar( "wire_tool_menu_expand_all" ) + ExpandAll:DockMargin( 4, 4, 0, 0 ) + ExpandAll:Dock( BOTTOM ) + + local first = true + + local parent = self + local oldval + function ExpandAll:OnChange( value ) + if oldval == value then return end -- wtfgarry + oldval = value + + local childNodes = parent.List:Root().ChildNodes:GetChildren() + + if first then + -- this was the only way to get this to run at the right time... garry bypassing hacks wohoo + -- it works because DCheckBox:OnChange is called when the player first sees the checkbox (aka when they open the wire tool tab) + first = false + expandbycookie( childNodes ) + else + expandall( value, childNodes ) + end + end + ExpandAll.Label:SetDark(true) + + self.List:Dock( FILL ) + + self.SearchList = vgui.Create( "DListView", LeftPanel ) + local x,y = self.List:GetPos() + local w,h = self.List:GetSize() + self.SearchList:SetPos( x + w, 160 ) + self.SearchList:SetSize( w, h ) + self.SearchList:SetVisible( false ) + self.SearchList:AddColumn( "Name" ) + self.SearchList:AddColumn( "Category" ) + self.SearchList:SetMultiSelect( false ) + + function self.SearchList:OnClickLine( line ) + -- Deselect old + local t = self:GetSelected() + if t and next(t) then + t[1]:SetSelected(false) + end + + line:SetSelected(true) -- Select new + spawnmenu.ActivateTool( line.Name ) + end + + self.Content = vgui.Create( "DCategoryList" ) + self.Divider:SetRight( self.Content ) + + local searchlist = self.SearchList + function LeftPanel:PerformLayout() + searchlist:SetWide( self:GetWide() ) + end + + self.ToolTable = {} + self.OriginalToolTable = {} + self.CategoryLookup = {} +end + +---------------------------------------------------------------------- +-- ReloadEverything +-- Called when a user adds/removes favourites or checks/unchecks +-- the wire extras checkbox +---------------------------------------------------------------------- +function PANEL:ReloadEverything() + self.List:Root():Remove() + self.List:Init() + self.SearchList:Clear() + self.SearchBox:SetValue( "" ) + self.SearchBox:OnTextChanged() + + self.CategoryLookup = {} + self.ToolTable = {} + + self:LoadToolsFromTable( self.OriginalToolTable ) + expandbycookie( self.List:Root().ChildNodes:GetChildren() ) +end + + +---------------------------------------------------------------------- +-- SetupSearchbox +-- This was so big, I moved it into its own function +-- rather than doing it in PANEL:Init() +---------------------------------------------------------------------- +function PANEL:SetupSearchbox() + local clearsearch = vgui.Create( "DImageButton", self.SearchBox ) + clearsearch:SetMaterial( "icon16/cross.png" ) + local src = self.SearchBox + function clearsearch:DoClick() + src:SetValue( "" ) + src:OnTextChanged() + src:SetValue( "Search..." ) + end + clearsearch:DockMargin( 2,2,4,2 ) + clearsearch:Dock( RIGHT ) + clearsearch:SetSize( 14, 10 ) + clearsearch:SetVisible( false ) + self.SearchBox.clearsearch = clearsearch + + -- OnEnter + local parent = self + function self.SearchBox:OnEnter( select_next ) + local lines = #parent.SearchList:GetLines() + if lines > 0 then -- if we have no lines at all, do nothing + local line = parent.SearchList:GetSelectedLine() or 0 + if select_next then -- if tabbed, select next line + if lines > line then + parent.SearchList:OnClickLine( parent.SearchList:GetLine( line+1 ) ) + else + parent.SearchList:OnClickLine( parent.SearchList:GetLine( 1 ) ) + end + elseif line == 0 then -- if not tabbed, only select first line if no line is selected + parent.SearchList:OnClickLine( parent.SearchList:GetLine( 1 ) ) + end + end + end + + local old = self.SearchBox.OnGetFocus + function self.SearchBox:OnGetFocus() + if self:GetValue() == "Search..." then -- If "Search...", erase it + self:SetValue( "" ) + end + old( self ) + end + + -- On lose focus + local old = self.SearchBox.OnLoseFocus + function self.SearchBox:OnLoseFocus() + if self.Tabbed then -- regain focus if tabbed + self:RequestFocus() + self.Tabbed = nil + else + if self:GetValue() == "" then -- if empty, reset "Search..." text + timer.Simple( 0, function() self:SetValue( "Search..." ) end ) + end + old( self ) + end + end + + -- detecting tab to select next item in search result + local old = self.SearchBox.OnKeyCodeTyped + function self.SearchBox:OnKeyCodeTyped( code ) + if code == 67 then -- tab + self:OnEnter( true ) + self.Tabbed = true + else + old( self, code ) + end + end + + self.SearchBox:SetValue( "Search..." ) + + local searching + function self.SearchBox:OnTextChanged() + timer.Remove( "wire_customspawnmenu_hidesearchbox" ) + + local text = self:GetValue() + if text ~= "" then + if not searching then + searching = true + local x,y = parent.List:GetPos() + local w,h = parent.List:GetSize() + parent.SearchList:SetPos( x + w, y ) + parent.SearchList:MoveTo( x, y, 0.1, 0, 1 ) + parent.SearchList:SetSize( w, h ) + parent.SearchList:SetVisible( true ) + self.clearsearch:SetVisible( true ) + parent.List:AlphaTo( 0, 0.1, 0 ) + end + local results = parent:Search( text ) + parent.SearchList:Clear() + for i=1,#results do + local result = results[i] + local line = parent.SearchList:AddLine( result.item.Text, result.item.Category ) + line.Name = result.item.ItemName + line.WireFavouritesCookieText = result.item.WireFavouritesCookieText + + function line:OnRightClick() + -- the menu wasn't clickable unless the search list had focus for some reason + parent.SearchList:RequestFocus() + + local menu = DermaMenu() + + local b = cookie.GetNumber( self.WireFavouritesCookieText ) + if b and b == 1 then + menu:AddOption( "Remove from favourites", function() cookie.Set( self.WireFavouritesCookieText, 0 ) parent:ReloadEverything() end ) + else + menu:AddOption( "Add to favourites", function() cookie.Set( self.WireFavouritesCookieText, 1 ) parent:ReloadEverything() end ) + end + menu:Open() + + return true + end + end + else + if searching then + searching = false + local x,y = parent.List:GetPos() + local w,h = parent.List:GetSize() + parent.SearchList:SetPos( x, y ) + parent.SearchList:MoveTo( x + w, y, 0.1, 0, 1 ) + parent.SearchList:SetSize( w, h ) + timer.Create( "wire_customspawnmenu_hidesearchbox", 0.1, 1, function() + if IsValid( parent ) then + parent.SearchList:SetVisible( false ) + end + end ) + self.clearsearch:SetVisible( false ) + parent.List:AlphaTo( 255, 0.1, 0 ) + end + parent.SearchList:Clear() + end + end +end + +---------------------------------------------------------------------- +-- Search algorithm +---------------------------------------------------------------------- + +local string_find = string.find +local table_SortByMember = table.SortByMember +local string_lower = string.lower +local language_GetPhrase = language.GetPhrase +local string_gsub = string.gsub + +-- Searching algorithm +function PANEL:Search( text ) + text = string_lower(text) + + local results = {} + for categoryID,categories in pairs( self.ToolTable ) do + for _, v in pairs( categories ) do + local name = language_GetPhrase(string_gsub(v.Text,"^#","")) + local lowname = string_lower( name ) + + if string_find( lowname, text, 1, true ) and not string_find( lowname, "(legacy)", 1, true ) and not v.Alias then + results[#results+1] = { + item = v, + dist = WireLib.levenshtein( text, lowname ) + } + end + end + end + + table_SortByMember( results, "dist", true ) + + return results +end + +-- Helper function +local function AddNode( list, text, icon, cookietext ) + local node = list:AddNode( text, icon ) + + node.Label:SetFont( "DermaDefaultBold" ) + + cookietext = "ToolMenu.Wire." .. cookietext + node.WireCookieText = cookietext + + function node:DoClick() + if autocollapse:GetBool() then + local parent = (list.RootNode and list.RootNode.ChildNodes:GetChildren() or list.ChildNodes:GetChildren()) + expandall( false, parent ) + end + + local b = not self.m_bExpanded + self:SetExpanded( b ) + + cookie.Set( cookietext, b and 1 or 0 ) + end + node.Expander.DoClick = function() node:DoClick() end + + function node:DoRightClick() + local menu = DermaMenu() + + local b = self.m_bExpanded + if b then + menu:AddOption( "Collapse all", function() + self:SetExpanded( false ) + expandall( false, self.ChildNodes:GetChildren() ) + end ) + else + menu:AddOption( "Expand all", function() + self:SetExpanded( true ) + expandall( true, self.ChildNodes:GetChildren() ) + end ) + end + menu:Open() + + return true + end + + return node +end + +---------------------------------------------------------------------- +-- CreateCategories +-- Creates the categories before placing tools in them to ensure +-- they are created in the correct order, and only once +---------------------------------------------------------------------- +function PANEL:CreateCategories() + for k,v in pairs( self.ToolTable ) do + if istable( v ) then + local category = v.ItemName + + local expl = string.Explode("/",category) + + if not separate_wire_extras:GetBool() and expl[1] == "Wire Extras" then + table.remove( expl, 1 ) + v.ItemName = string.gsub( v.ItemName, "Wire Extras/", "" ) + v.Text = string.gsub( v.Text, "Wire Extras/", "" ) + category = v.ItemName + end + + if #expl == 1 then + if not self.CategoryLookup[category] then + local node = AddNode( self.List, v.Text, v.Icon, category ) + self.CategoryLookup[category] = node + end + else + local category = expl[1] + if not self.CategoryLookup[category] then + local node = AddNode( self.List, category, nil, category ) + self.CategoryLookup[category] = node + end + + for i=2,#expl do + local str = expl[i] + + local path = table.concat(expl,"/",1,i) + if not self.CategoryLookup[path] then + local node = AddNode( self.CategoryLookup[table.concat(expl,"/",1,i-1)], str, nil, path ) + self.CategoryLookup[path] = node + end + end + end + end + end +end + + +---------------------------------------------------------------------- +-- AddToolToCategories +-- Handles multiple categories by copying the tool and moving the copies into said categories +---------------------------------------------------------------------- +function PANEL:AddToolToCategories( tool, categories ) + for i=1,#categories do + local categoryName = categories[i] + + local added = false + + local copy = table.Copy( tool ) + copy.Alias = true + + for _, category in pairs( self.ToolTable ) do + if category.ItemName == categoryName then + added = true + category[#category+1] = copy + table_SortByMember( category, "Text", true ) + end + end + + if not added then + local new = { + ItemName = categoryName, + Text = categoryName, + } + + new[1] = copy + self.ToolTable[#self.ToolTable+1] = new + end + end +end + +---------------------------------------------------------------------- +-- FixWireCategories +-- Handles multi categories and favourites +---------------------------------------------------------------------- +function PANEL:FixWireCategories() + local t = table.Copy( self.ToolTable ) + + for _,category in pairs( t ) do + if istable(category) then + for _, tool in pairs( category ) do + if istable(tool) then + -- favourites + local fav = cookie.GetNumber( "ToolMenu.Wire.Favourites." .. tool.ItemName ) + if fav and fav == 1 then + self:AddToolToCategories( tool, {"Favourites"} ) + end + + -- multi categories + if not hide_duplicates:GetBool() then + local tooltbl = weapons.Get("gmod_tool").Tool[tool.ItemName] + if tooltbl then + if tooltbl.Wire_MultiCategories then + self:AddToolToCategories( tool, tooltbl.Wire_MultiCategories ) + end + end + end + end + end + end + end +end + +----------------------------------------------------------- +-- Name: LoadToolsFromTable +----------------------------------------------------------- +function PANEL:LoadToolsFromTable( inTable ) + self.OriginalToolTable = table.Copy( inTable ) + self.ToolTable = table.Copy( inTable ) + + -- If this tab has no favourites category, add one at the top + if self.ToolTable[1].ItemName ~= "Favourites" then + table.insert( self.ToolTable, 1, { ItemName = "Favourites", Text = "Favourites", Icon = "icon16/star.png" } ) + + -- If this tab DOES have a favourites category, set its icon to a star + elseif self.ToolTable[1].ItemName == "Favourites" then + self.ToolTable[1].Icon = "icon16/star.png" + end + + -- First, we copy all tools into their multi categories + self:FixWireCategories() + + -- Then we create the categories, so everything goes to the right place + self:CreateCategories() + + -- Then, we add all tools to the DTree + for k, v in pairs( self.ToolTable ) do + + if ( istable( v ) ) then + + -- Remove these from the table so we can + -- send the rest of the table to the other + -- function + + local Name = v.ItemName + local Label = v.Text + v.ItemName = nil + v.Text = nil + v.Icon = nil + + self:AddCategory( Name, Label, v ) + end + end +end + +----------------------------------------------------------- +-- Name: AddCategory +----------------------------------------------------------- +function PANEL:AddCategory( Name, Label, tItems, CategoryID ) + + local Category = self.CategoryLookup[Name] + if not Category then + return + end + + for k, v in pairs( tItems ) do + + v.Category = Label + v.CategoryID = CategoryID + + local icon = "icon16/wrench.png" + + if custom_icons:GetBool() then + local tooltbl = weapons.Get("gmod_tool").Tool[v.ItemName] + if tooltbl then + if tooltbl.Wire_ToolMenuIcon then + icon = tooltbl.Wire_ToolMenuIcon + end + end + end + + local item = Category:AddNode( v.Text, icon ) + + function item:DoClick() + + spawnmenu.ActivateTool( self.Name ) + + end + + local parent = self + function item:DoRightClick() + local menu = DermaMenu() + + local b = cookie.GetNumber( self.WireFavouritesCookieText ) + if b and b == 1 then + menu:AddOption( "Remove from favourites", function() cookie.Set( self.WireFavouritesCookieText, 0 ) parent:ReloadEverything() end ) + else + menu:AddOption( "Add to favourites", function() cookie.Set( self.WireFavouritesCookieText, 1 ) parent:ReloadEverything() end ) + end + menu:Open() + + return true + end + + item.WireFavouritesCookieText = "ToolMenu.Wire.Favourites." .. v.ItemName + v.WireFavouritesCookieText = item.WireFavouritesCookieText + item.ControlPanelBuildFunction = v.CPanelFunction + item.Command = v.Command + item.Name = v.ItemName + item.Controls = v.Controls + item.Text = v.Text + + end + + self:InvalidateLayout() +end + +function PANEL:SetActive( cp ) + local kids = self.Content:GetCanvas():GetChildren() + for k, v in pairs( kids ) do + v:SetVisible( false ) + end + + self.Content:AddItem( cp ) + cp:SetVisible( true ) + cp:Dock( TOP ) +end + +vgui.Register( "WireToolPanel", PANEL, "Panel" ) + +local wire_tab +local all_tabs = {} + +local function setUpTabReloadOnChange( checkbox ) + checkbox.first = true + function checkbox:OnChange( value ) + if self.oldval == value then return end -- wtfgarry + self.oldval = value + + if self.first then self.first = false return end + + timer.Simple( 0.1, function() + if IsValid( wire_tab ) then + wire_tab:ReloadEverything() + end + end ) + end +end + +local function CreateCPanel( panel ) + local checkbox = panel:CheckBox( "Use wire's custom tool menu for all tabs", "wire_tool_menu_custom_menu_for_all_tabs" ) + checkbox:SetToolTip( "Requires rejoin to take effect" ) + + if WireLib.WireExtrasInstalled then + local SeparateWireExtras = panel:CheckBox( "Separate Wire Extras", "wire_tool_menu_separate_wire_extras" ) + SeparateWireExtras:SetToolTip( "Whether or not to separate wire extras tools into its own category." ) + + setUpTabReloadOnChange( SeparateWireExtras ) + end + + local HideDuplicates = panel:CheckBox( "Hide tool duplicates", "wire_tool_menu_hide_duplicates" ) + setUpTabReloadOnChange( HideDuplicates ) + panel:Help( "It makes sense to have certain tools in multiple categories at once. However, if you don't want this, you can disable it here. The tools will then only appear in their primary category." ) + + local UseIcons = panel:CheckBox( "Use custom icons", "wire_tool_menu_custom_icons" ) + setUpTabReloadOnChange( UseIcons ) + UseIcons:SetToolTip( "If disabled, all tools will use the 'wrench' icon." ) + + local AutoCollapse = panel:CheckBox( "Autocollapse", "wire_tool_menu_autocollapse" ) + AutoCollapse:SetToolTip( "If enabled, opening a category will collapse other categories." ) + + local TabWidth = panel:NumSlider( "Tab width", "wire_tool_menu_tab_width", 300, 3000, 0 ) + panel:Help( [[Set the width of all tabs. +Defaults: +Screen width > 1600px: 548px, +Screen width > 1280px: 460px, +Screen width < 1280px: 390px. +Note: +Can't be smaller than the width of any non-custom tab, and can't be greater than screenwidth * 0.6. +Changes will take effect 3 seconds after you edit the value.]] ) + + function TabWidth:ValueChanged( value ) + timer.Remove( "wire_tab_width_changed" ) + timer.Create( "wire_tab_width_changed", 3, 1, function() + all_tabs[1]:GetParent():SetWide( 390 ) + for i=1,#all_tabs do -- change the width of all registered tabs + all_tabs[i]:SetWidth( math.Clamp( value, 390, ScrW() * 0.6 ) ) + end + all_tabs[1]:GetParent():PerformLayout() + end) + end +end + +---------------------------------------------------------------------- +-- Incoming garry hack +---------------------------------------------------------------------- + +local tabs = {} + +-- Allow any addon to register to be given a custom menu +function WireLib.registerTabForCustomMenu( tab ) + tabs[tab] = true +end + +-- Register ourselves +WireLib.registerTabForCustomMenu( "Wire" ) + +local old +hook.Add( "PopulateToolMenu", "Wire_CustomSpawnMenu", function() + spawnmenu.AddToolMenuOption( "Wire", "Options", "Custom Tool Menu Options", "Custom Tool Menu Options", "", "", CreateCPanel ) + + local ToolMenu = vgui.GetControlTable( "ToolMenu" ) + + old = ToolMenu.AddToolPanel + function ToolMenu:AddToolPanel( Name, ToolTable ) + if tabs[ToolTable.Name] or custom_for_all_tabs:GetBool() == true then + local Panel = vgui.Create( "WireToolPanel" ) + + if ToolTable.Name == "Wire" then + wire_tab = Panel -- for wire tab options menu + end + all_tabs[#all_tabs+1] = Panel -- list of all registered tabs + + Panel:SetTabID( Name ) + Panel:LoadToolsFromTable( ToolTable.Items ) + + self:AddSheet( ToolTable.Label, Panel, ToolTable.Icon ) + self.ToolPanels[ Name ] = Panel + else + return old( self, Name, ToolTable ) + end + end +end) + +hook.Add( "PostReloadToolsMenu", "Wire_CustomSpawnMenu", function() + local ToolMenu = vgui.GetControlTable( "ToolMenu" ) + ToolMenu.AddToolPanel = old + old = nil +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/e2_extension_menu.lua b/garrysmod/addons/feature-wire/lua/wire/client/e2_extension_menu.lua new file mode 100644 index 0000000..fa57246 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/e2_extension_menu.lua @@ -0,0 +1,69 @@ +-- the names of the concommands used to enable/disable extensions +-- (with a trailing space so we can concatenate extension names straight on) +local CONCOMMAND_NAMES = { + [false] = "wire_expression2_extension_disable ", + [true] = "wire_expression2_extension_enable " +} + +-- the same parameters as DermaDefault, but with italic=true +surface.CreateFont("DermaDefaultItalic", { + font = system.IsLinux() and "DejaVu Sans" or "Tahoma", + size = system.IsLinux() and 14 or 13, + italic = true, +}) + +local function BuildExtensionMenu(panel) + local allowed = LocalPlayer():IsSuperAdmin() + + if not allowed then + local permissionNotice = panel:Help("You are not a superadmin - you cannot change these settings, only view them.") + permissionNotice:SetColor(Color(153, 51, 0, 255)) + end + + for _, name in pairs(E2Lib.GetExtensions()) do + local item = vgui.Create("DListLayout", panel) + item:DockPadding(5, 5, 5, 5) + item:SetPaintBackground(true) + + panel:AddItem(item) + + local checkbox = vgui.Create("DCheckBoxLabel", item) + checkbox:SetText(name) + checkbox:SetChecked(E2Lib.GetExtensionStatus(name)) + checkbox.Button:SetDisabled(not allowed) + checkbox:SizeToContents() + checkbox:SetDark(true) + function checkbox:OnChange(value) + LocalPlayer():ConCommand(CONCOMMAND_NAMES[value] .. name) + end + + if allowed then + function item:OnMouseReleased() checkbox:Toggle() end + end + + local documentation = E2Lib.GetExtensionDocumentation(name) + if documentation.Description then + local description = Label(documentation.Description, item) + description:DockMargin(40, 5, 5, 5) + description:SetWrap(true) + description:SetDark(true) + description:SetAutoStretchVertical(true) + description:SetFont("DermaDefaultItalic") + end + + -- only display warnings to admins (as they're usually about ways that + -- players could exploit an E2 extension). Yes, this is a bit paranoid. + if allowed and documentation.Warning then + local warning = Label(documentation.Warning, item) + warning:DockMargin(40, 5, 5, 5) + warning:SetWrap(true) + warning:SetTextColor(Color(153, 51, 0)) + warning:SetAutoStretchVertical(true) + warning:SetFont("DermaDefaultBold") + end + end +end + +hook.Add("PopulateToolMenu", "AddAddWireAdminControlPanelMenu", function() + spawnmenu.AddToolMenuOption("Utilities", "Admin", "WireE2Extensions", "E2 Extensions", "", "", BuildExtensionMenu, {}) +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/e2descriptions.lua b/garrysmod/addons/feature-wire/lua/wire/client/e2descriptions.lua new file mode 100644 index 0000000..57a141c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/e2descriptions.lua @@ -0,0 +1,1542 @@ +if not E2Helper then return end + +local name, short, type, typeid +timer.Simple(0.1, function() + for k, v in pairs( wire_expression_types ) do + if k == "NORMAL" then k = "NUMBER" end + + name = k:sub(1,1) .. k:sub(2):lower() + short = name:Left(3) + type = name:lower() + typeid = v[1] + -- tables + E2Helper.Descriptions["insert"..name.."(t:n"..typeid..")"] = "Inserts the variable at the specified position. Moves all other indexes up one step to compensate" + E2Helper.Descriptions["remove"..name.."(t:n)"] = "Removes the variable at the specified numerical index, with the specified type, and returns it. All sequential keys will be moved down to fill the gap" + E2Helper.Descriptions["remove"..name.."(t:s)"] = "Removes the variable at the specified string index, with the specified type, and returns it" + E2Helper.Descriptions["pop"..name.."(t:)"] = "Removes and returns the last variable" + E2Helper.Descriptions["push"..name.."(t:"..typeid..")"] = "Adds the variable to the end of the table" + E2Helper.Descriptions["unshift"..name.."(t:"..typeid..")"] = "Adds the data to the beginning of the table. Will move all other entries up one step to compensate" + -- arrays + E2Helper.Descriptions["insert"..name.."(r:n"..typeid..")"] = "Inserts the variable at the specified position. Moves all other indexes up one step to compensate" + E2Helper.Descriptions["set"..name.."(r:n"..typeid..")"] = "Sets a variable at specified index. Deprecated, use R[N,"..type.."] = X instead" + E2Helper.Descriptions[type.."(r:n)"] = "Returns the "..type.." stored in the array under specified index. Deprecated, use R[N,"..type.."] instead" + E2Helper.Descriptions["pop"..name.."(r:)"] = "Deletes and returns the last entry in the array. Be sure not to use popNumber() on a vector or similar, as the data may be lost" + E2Helper.Descriptions["push"..name.."(r:"..typeid..")"] = "Saves the data at the end of the array" + E2Helper.Descriptions["unshift"..name.."(r:"..typeid..")"] = "Adds the data to the beginning of the array. Will move all other entries up one address" + E2Helper.Descriptions["shift"..name.."(r:)"] = "Deletes and returns the first element of the array, moving other entries down one address to compensate" + E2Helper.Descriptions["remove"..name.."(r:n)"] = "Deletes and returns the specified entry, moving subsequent entries down to compensate" + -- gvars + E2Helper.Descriptions["gRemoveAll"..name.."s()"] = "Removes all variables of the "..type.." type in your non-shared table" + E2Helper.Descriptions["gRemoveAll"..name.."s(s)"] = "Removes all variables of the "..type.." type in your non-shared table in group S" + E2Helper.Descriptions["gDeleteAll"..short.."()"] = "Exactly the same as gRemoveAll"..name.."s(S) (Except it removes in the group set by gSetGroup instead of using the group as an argument)" + E2Helper.Descriptions["gDelete"..short.."(s)"] = "Removes and returns the variable of the "..type.." type at the index S in the current group" + E2Helper.Descriptions["gDelete"..short.."(n)"] = "Exactly the same as gDelete"..short.."(N:toString())" + E2Helper.Descriptions["gGet"..short.."(s)"] = "Gets a variable of the "..type.." type from index S in the current group" + E2Helper.Descriptions["gGet"..short.."(n)"] = "Exactly the same as gGet"..short.."(N:toString())" + E2Helper.Descriptions["gSet"..short.."(s"..typeid..")"] = "Sets a variable of the "..type.." type at index S in the current group" + E2Helper.Descriptions["gSet"..short.."(n"..typeid..")"] = "Exactly the same as gSet"..short.."(N:toString(),"..type..")" + E2Helper.Descriptions["remove"..name.."(xgt:s)"] = "Removes and returns the variable of the "..type.." type at the index S" + -- self-aware + E2Helper.Descriptions["ioGetInput"..name.."(s)"] = "Get the value of the input S of the E2" + E2Helper.Descriptions["ioSetOutput(s"..typeid..")"] = "Trigger the output S of the E2 with the "..type.." value" + E2Helper.Descriptions["select(n"..typeid.."...)"] = "Returns the Nth value given after the index, "..type.."'s zero element otherwise. If you mix types, the behaviour is undefined" + -- datasignals + E2Helper.Descriptions["dsGet"..name.."()"] = "Returns the received "..type + -- wirelink + E2Helper.Descriptions[type.."(xwl:s)"] = "Returns the "..type.." from the specified address of linked component. Deprecated, use XWL[S,"..type.."] instead" + E2Helper.Descriptions["set"..name.."(xwl:s"..typeid..")"] = "Sets the component's input of the specified name equal to specified "..type..". Deprecated, use XWL[S,"..type.."] = X instead" + end +end) + +-- Number +E2Helper.Descriptions["finite(n)"] = "Returns 1 if given value is a finite number; otherwise 0." +E2Helper.Descriptions["isinf(n)"] = "Returns 1 if given value is a positive infinity or -1 if given value is a negative infinity; otherwise 0." +E2Helper.Descriptions["isnan(n)"] = "Returns 1 if given value is not a number (NaN); otherwise 0." +E2Helper.Descriptions["inf()"] = "Returns a huge constant (infinity)" +E2Helper.Descriptions["mod(nn)"] = "Modulo, returns the Remainder after Argument 1 has been divided by Argument 2. Note \"mod(-1, 3) = -1\"" +E2Helper.Descriptions["sqrt(n)"] = "Returns the Square Root of the Argument" +E2Helper.Descriptions["cbrt(n)"] = "Returns the Cube Root of the Argument" +E2Helper.Descriptions["root(nn)"] = "Returns the Nth Root of the first Argument" +E2Helper.Descriptions["e()"] = "Returns Euler's Constant" +E2Helper.Descriptions["frexp(n)"] = "Returns the mantissa and exponent of the given floating-point number as a vector2 (X component holds a mantissa, and Y component holds an exponent)" +E2Helper.Descriptions["exp(n)"] = "Returns e to the power of the Argument (same as e()^N but shorter and faster this way)" +E2Helper.Descriptions["ln(n)"] = "Returns the logarithm to base e of the Argument" +E2Helper.Descriptions["log2(n)"] = "Returns the logarithm to base 2 of the Argument" +E2Helper.Descriptions["log10(n)"] = "Returns the logarithm to base 10 of the Argument" +E2Helper.Descriptions["log(nn)"] = "Returns the logarithm to base Argument 2 of Argument 1" +E2Helper.Descriptions["abs(n)"] = "Returns the Magnitude of the Argument" +E2Helper.Descriptions["ceil(n)"] = "Rounds the Argument up to the nearest Integer" +E2Helper.Descriptions["ceil(nn)"] = "Rounds Argument 1 up to Argument 2's decimal precision" +E2Helper.Descriptions["floor(n)"] = "Rounds the Argument down to the nearest Integer" +E2Helper.Descriptions["floor(nn)"] = "Rounds Argument 1 down to Argument 2's decimal precision" +E2Helper.Descriptions["round(n)"] = "Rounds the Argument to the nearest Integer" +E2Helper.Descriptions["round(nn)"] = "Rounds Argument 1 to Argument 2's decimal precision" +E2Helper.Descriptions["int(n)"] = "Returns the Integer part of the Argument (always rounds towards zero)" +E2Helper.Descriptions["frac(n)"] = "Returns the Fractional part (decimal places) of the Argument" +E2Helper.Descriptions["clamp(nnn)"] = "If Arg1 = Arg3 (max) returns Arg3; otherwise returns Arg1" +E2Helper.Descriptions["inrange(nnn)"] = "Returns 1 if N is in the interval [N2; N3], 0 otherwise. This means it is equivalent to ((N2 <= N) & (N <= N3))" +E2Helper.Descriptions["sign(n)"] = "Returns the sign of argument (-1,0,1) [sign(N) = N / abs(N) ]" +E2Helper.Descriptions["min(nn)"] = "Returns the lowest value Argument" +E2Helper.Descriptions["min(nnn)"] = "Returns the lowest value Argument" +E2Helper.Descriptions["min(nnnn)"] = "Returns the lowest value Argument" +E2Helper.Descriptions["max(nn)"] = "Returns the highest value Argument" +E2Helper.Descriptions["max(nnn)"] = "Returns the highest value Argument" +E2Helper.Descriptions["max(nnnn)"] = "Returns the highest value Argument" +E2Helper.Descriptions["random()"] = "Returns a random floating-point number between 0 and 1 [0 <= x < 1 ]" +E2Helper.Descriptions["random(n)"] = "Returns a random floating-point number between 0 and the specified value [0 <= x < a ]" +E2Helper.Descriptions["random(nn)"] = "Returns a random floating-point number between the specified interval [a <= x < b ]" +E2Helper.Descriptions["randint(n)"] = "Returns a random integer from 1 to the specified value [1 <= x <= a ]" +E2Helper.Descriptions["randint(nn)"] = "Returns a random integer in the specified interval [a <= x <= b ]" +E2Helper.Descriptions["pi()"] = "Returns the constant PI" +E2Helper.Descriptions["toRad(n)"] = "Converts Degree angles to Radian angles" +E2Helper.Descriptions["toDeg(n)"] = "Converts Radian angles to Degree angles" +E2Helper.Descriptions["sin(n)"] = "Returns the sine of N degrees" +E2Helper.Descriptions["cos(n)"] = "Returns the cosine of N degrees" +E2Helper.Descriptions["tan(n)"] = "Returns the tangent of N degrees" +E2Helper.Descriptions["cot(n)"] = "Returns the cotangent of N degrees" +E2Helper.Descriptions["sec(n)"] = "Returns the secant of N degrees" +E2Helper.Descriptions["csc(n)"] = "Returns the cosecant of N degrees" +E2Helper.Descriptions["asin(n)"] = "Returns the inverse sine of the argument, in degrees" +E2Helper.Descriptions["acos(n)"] = "Returns the inverse cosine of the argument, in degrees" +E2Helper.Descriptions["atan(n)"] = "Returns the inverse tangent of the argument, in degrees" +E2Helper.Descriptions["atan(nn)"] = "Returns the inverse tangent of the arguments (arg1 / arg2), in degrees. This function accounts for positive/negative arguments, and arguments at or close to 0" +E2Helper.Descriptions["sinh(n)"] = "Returns the hyperbolic sine of N degrees" +E2Helper.Descriptions["cosh(n)"] = "Returns the hyperbolic cosine of N degrees" +E2Helper.Descriptions["tanh(n)"] = "Returns the hyperbolic tangent of N degrees" +E2Helper.Descriptions["coth(n)"] = "Returns the hyperbolic cotangent of N degrees" +E2Helper.Descriptions["sech(n)"] = "Returns the hyperbolic secant of N degrees" +E2Helper.Descriptions["csch(n)"] = "Returns the hyperbolic cosecant of N degrees" +E2Helper.Descriptions["sinr(n)"] = "Returns the sine of N radians" +E2Helper.Descriptions["cosr(n)"] = "Returns the cosine of N radians" +E2Helper.Descriptions["tanr(n)"] = "Returns the tangent of N radians" +E2Helper.Descriptions["cotr(n)"] = "Returns the cotangent of N radians" +E2Helper.Descriptions["secr(n)"] = "Returns the secant of N radians" +E2Helper.Descriptions["cscr(n)"] = "Returns the cosecant of N radians" +E2Helper.Descriptions["sinhr(n)"] = "Returns the hyperbolic sine of N radians" +E2Helper.Descriptions["coshr(n)"] = "Returns the hyperbolic cosine of N radians" +E2Helper.Descriptions["tanhr(n)"] = "Returns the hyperbolic tangent of N radians" +E2Helper.Descriptions["cothr(n)"] = "Returns the hyperbolic cotangent of N radians" +E2Helper.Descriptions["sechr(n)"] = "Returns the hyperbolic secant of N radians" +E2Helper.Descriptions["cschr(n)"] = "Returns the hyperbolic cosecant of N radians" +E2Helper.Descriptions["asinr(n)"] = "Returns the inverse sine of the argument, in radians" +E2Helper.Descriptions["acosr(n)"] = "Returns the inverse cosine of the argument, in radians" +E2Helper.Descriptions["atanr(n)"] = "Returns the inverse tangent of the argument, in radians" +E2Helper.Descriptions["atanr(nn)"] = "Returns the inverse tangent of the arguments (arg1 / arg2), in radians. This function accounts for positive/negative arguments, and arguments at or close to 0" +E2Helper.Descriptions["airDensity()"] = "Returns air density (affects how drag slows down props)" +E2Helper.Descriptions["speedLimit()"] = "Returns the speed limit" +E2Helper.Descriptions["angSpeedLimit()"] = "Returns the angular speed limit" +E2Helper.Descriptions["bearing(vav)"] = "Gets the bearing from the first position, at the specified angle, to the second position" +E2Helper.Descriptions["elevation(vav)"] = "Gets the elevation from the first position, at the specified angle, to the second position" +E2Helper.Descriptions["heading(vav)"] = "Gets the elevation and bearing from the first position, at the specified angle, to the second position" +E2Helper.Descriptions["changed"] = "Checks if the value or variable was changed. Accepts any type except table and array" +E2Helper.Descriptions["wrap(nn)"] = "Performs (n1 + n2) % (n2 * 2) - n2" + +-- String +E2Helper.Descriptions["index(s:n)"] = "Returns Nth letter of the string, formatted as a string" +E2Helper.Descriptions["length(s:)"] = "Returns the length of the string" +E2Helper.Descriptions["unicodeLength(s:)"] = "Returns the unicode length of the string" +E2Helper.Descriptions["upper(s:)"] = "All characters are made uppercase" +E2Helper.Descriptions["lower(s:)"] = "All characters are made lowercase" +E2Helper.Descriptions["sub(s:n)"] = "Returns a substring, starting at the number argument and ending at the end of the string" +E2Helper.Descriptions["sub(s:nn)"] = "Returns a substring, starting at the first number argument and ending at the second" +E2Helper.Descriptions["left(s:n)"] = "Returns N amount of characters starting from the leftmost character" +E2Helper.Descriptions["right(s:n)"] = "Returns N amount of characters starting from the rightmost character" +E2Helper.Descriptions["find(s:s)"] = "Returns the 1st occurrence of the string S, returns 0 if not found" +E2Helper.Descriptions["find(s:sn)"] = "Returns the 1st occurrence of the string S starting at N and going to the end of the string, returns 0 if not found" +E2Helper.Descriptions["findRE(s:s)"] = "Returns the 1st occurrence of the string S using REGEX functions, returns 0 if not found" +E2Helper.Descriptions["findRE(s:sn)"] = "Returns the 1st occurrence of the string S starting at N and going to the end of the string using REGEX functions, returns 0 if not found" +E2Helper.Descriptions["explode(s:s)"] = "Splits the string into an array, along the boundaries formed by the string S. See also String.Explode" +E2Helper.Descriptions["explodeRE(s:s)"] = "Splits the string into an array, along the boundaries formed by the string pattern S. See also String.Explode" +E2Helper.Descriptions["repeat(s:n)"] = "Repeats the input string N times" +E2Helper.Descriptions["trim(s:)"] = "Trims away spaces at the beginning and end of a string" +E2Helper.Descriptions["trimLeft(s:)"] = "Trims away opening spaces on the string" +E2Helper.Descriptions["trimRight(s:)"] = "Trims away spaces at the end of a string" +E2Helper.Descriptions["replace(s:ss)"] = "Finds and replaces every occurrence of the first argument with the second argument" +E2Helper.Descriptions["replaceRE(s:ss)"] = "Finds and replaces every occurrence of the first argument using REGEX with the second argument" +E2Helper.Descriptions["reverse(s:)"] = "Returns a reversed version of S" +E2Helper.Descriptions["toNumber(s:)"] = "Parses a number from a string" +E2Helper.Descriptions["toNumber(s:n)"] = "Parses a number from a string. The argument given is the base. I.e. toNumber(16) will parse hex" +E2Helper.Descriptions["toString(n:)"] = "Formats a number as a string. (Numbers may be concatenated into a string without using this function)" +E2Helper.Descriptions["toString(n)"] = "Formats a number as a string. (Numbers may be concatenated into a string without using this function)" +E2Helper.Descriptions["toString(n:n)"] = "Formats a number as a string, using argument 2 as the base. i.e. using 16 for base would convert the number to hex" +E2Helper.Descriptions["toString(nn)"] = "Formats a number as a string, using argument 2 as the base. i.e. using 16 for base would convert the number to hex" +E2Helper.Descriptions["toChar(n)"] = "Returns a one-character string from it's ASCII code, where 32 = argument 1 = 255. An empty string is returned for numbers outside that range" +E2Helper.Descriptions["toUnicodeChar(n)"] = "Returns a one-character string from it's UNICODE code" +E2Helper.Descriptions["toByte(s)"] = "Returns the ASCII code of the 1st character in the string" +E2Helper.Descriptions["toUnicodeByte(s)"] = "Returns the Unicode code of the 1st character in the string" +E2Helper.Descriptions["toByte(sn)"] = "Returns the ASCII code of the Nth character in the string" +E2Helper.Descriptions["format(s...)"] = "Formats a values exactly like Lua's [http://www.lua.org/manual/5.1/manual.html#pdf-string.format string.format]. Any number and type of parameter can be passed through the \"...\". Prints errors to the chat area" +E2Helper.Descriptions["match(s:s)"] = "runs string.match(S, S2) and returns the sub-captures as an array" +E2Helper.Descriptions["match(s:sn)"] = "runs string.match(S, S2, N) and returns the sub-captures as an array" +E2Helper.Descriptions["matchFirst(s:s)"] = "runs string.match(S, S2) and returns the first match or an empty string if the match failed" +E2Helper.Descriptions["matchFirst(s:sn)"] = "runs string.match(S, S2, N) and returns the first match or an empty string if the match failed" +E2Helper.Descriptions["gmatch(s:s)"] = "runs string.gmatch(S, S2) and returns the captures in arrays in a table" +E2Helper.Descriptions["gmatch(s:sn)"] = "runs string.gmatch(S, S2, N) and returns the captures in arrays in a table" + +-- Entity/Player +E2Helper.Descriptions["entity(n)"] = "Gets the entity associated with the id" +E2Helper.Descriptions["owner()"] = "Gets the owner of the expression ( same as entity():owner() )" +E2Helper.Descriptions["id(e:)"] = "Gets the numeric id of an entity" +E2Helper.Descriptions["noentity()"] = "Returns an invalid entity" +E2Helper.Descriptions["world()"] = "Returns the world entity" +E2Helper.Descriptions["sunDirection()"] = "Returns the vector direction that points towards the sun" +E2Helper.Descriptions["type(e:)"] = "Gets the class of an entity" +E2Helper.Descriptions["model(e:)"] = "Gets the model of an entity" +E2Helper.Descriptions["keyvalues(e:)"] = "Returns the keyvalue table of an entity" +E2Helper.Descriptions["owner(e:)"] = "Gets the owner of an entity" +E2Helper.Descriptions["name(e:)"] = "Gets the name of a player" +E2Helper.Descriptions["steamID(e:)"] = "Gets the steam ID of the player" +E2Helper.Descriptions["steamID64(e:)"] = "Gets the Steam Community ID (aka Steam64) of the given player" +E2Helper.Descriptions["isSteamFriend(e:e)"] = "Returns if the given Entity is a steam friend of the first Entity" +E2Helper.Descriptions["steamFriends(e:)"] = "Returns a Array with E's steam friends on the server E is playing on" +E2Helper.Descriptions["pos(e:)"] = "Gets the position of the entity" +E2Helper.Descriptions["eye(e:)"] = "Gets a players view direction else entity forward direction" +E2Helper.Descriptions["eyeAngles(e:)"] = "Gets a players view direction" +E2Helper.Descriptions["eyeTrace(e:)"] = "Performs a quick trace from the player's eye. Equivalent to rangerOffset(16384, E:shootPos(), E:eye()), but faster. Does not respect filters or ranger flags" +E2Helper.Descriptions["eyeTraceCursor(e:)"] = "Same as eyeTrace, except it also works when the player (for example) is holding down C" +E2Helper.Descriptions["shootPos(e:)"] = "Returns a players shoot position" +E2Helper.Descriptions["aimEntity(e:)"] = "Returns the entity that the entity is aiming at" +E2Helper.Descriptions["aimBone(e:)"] = "Returns the bone the player is currently aiming at" +E2Helper.Descriptions["aimPos(e:)"] = "Returns the point that the entity is looking at" +E2Helper.Descriptions["aimNormal(e:)"] = "Returns a normalized directional vector perpendicular to the surface pointed at" +E2Helper.Descriptions["frags(e:)"] = "Returns the number of kills the player has made" +E2Helper.Descriptions["deaths(e:)"] = "Returns the number of times the player died" +E2Helper.Descriptions["team(e:)"] = "Returns the team number a player is on" +E2Helper.Descriptions["teamName(n)"] = "Returns the name of the team associated with the team number" +E2Helper.Descriptions["teamName(n:)"] = "Returns the name of the team associated with the team number" +E2Helper.Descriptions["teamColor(n)"] = "Returns the color of the team associated with the team number" +E2Helper.Descriptions["teamDeaths(n)"] = "Returns the number of deaths of the team associated with the team number" +E2Helper.Descriptions["teamDeaths(n:)"] = "Returns the number of deaths of the team associated with the team number" +E2Helper.Descriptions["teamFrags(n)"] = "Returns the number of kills of the team associated with the team number" +E2Helper.Descriptions["teamFrags(n:)"] = "Returns the number of kills of the team associated with the team number" +E2Helper.Descriptions["teamPlayers(n)"] = "Returns the number of players of the team associated with the team number" +E2Helper.Descriptions["teamPlayers(n:)"] = "Returns the number of players of the team associated with the team number" +E2Helper.Descriptions["teamScore(n)"] = "Returns the score of the team associated with the team number" +E2Helper.Descriptions["teamScore(n:)"] = "Returns the score of the team associated with the team number" +E2Helper.Descriptions["teams()"] = "Returns an array of all teams" +E2Helper.Descriptions["forward(e:)"] = "Gets the forward direction of the entity 2)" +E2Helper.Descriptions["right(e:)"] = "Gets the right direction of the entity" +E2Helper.Descriptions["up(e:)"] = "Gets the up direction of the entity" +E2Helper.Descriptions["vel(e:)"] = "Gets the velocity of the entity" +E2Helper.Descriptions["velL(e:)"] = "Gets the local velocity of the entity" +E2Helper.Descriptions["boxCenter(e:)"] = "Gets the center of the entity's bounding box, as a local position vector" +E2Helper.Descriptions["boxMax(e:)"] = "Gets the maximum local XYZ of the entity's bounding box (the \"highest\" corner), as a local position vector" +E2Helper.Descriptions["boxMin(e:)"] = "Gets the minimum local XYZ of the entity's bounding box (the \"lowest\" corner), as a local position vector" +E2Helper.Descriptions["boxSize(e:)"] = "Gets the dimensions of the entity's bounding box as a vector (length, width, height)" +E2Helper.Descriptions["toWorld(e:v)"] = "Transforms from a vector local to E to a world vector" +E2Helper.Descriptions["toLocal(e:v)"] = "Transforms from a world vector to a vector local to E" +E2Helper.Descriptions["toWorld(e:a)"] = "Transforms from an angle local to E to a world angle" +E2Helper.Descriptions["toLocal(e:a)"] = "Transforms from a world angle to an angle local to E" +E2Helper.Descriptions["toWorldAxis(e:v)"] = "Transforms an axis local to E to a global axis" +E2Helper.Descriptions["toLocalAxis(e:v)"] = "Transforms a world axis to an axis local to E" +E2Helper.Descriptions["angVel(e:)"] = "Gets the angular velocity of the entity" +E2Helper.Descriptions["angVelVector(e:)"] = "Returns rotation axis, velocity and direction given as the vector's direction, magnitude and sense" +E2Helper.Descriptions["angles(e:)"] = "Gets the pitch, yaw and roll of the entity" +E2Helper.Descriptions["radius(e:)"] = "Gets the size of the object (not precisely, but useful)" +E2Helper.Descriptions["height(e:)"] = "Gets the height of a player or npc" +E2Helper.Descriptions["bearing(e:v)"] = "Gets the bearing from the entity to the vector" +E2Helper.Descriptions["elevation(e:v)"] = "Gets the elevation from the entity to the vector" +E2Helper.Descriptions["heading(e:v)"] = "Gets the elevation and bearing from the entity to the vector" +E2Helper.Descriptions["health(e:)"] = "Gets the health of the entity" +E2Helper.Descriptions["maxHealth(e:)"] = "Gets the max health of the entity" +E2Helper.Descriptions["armor(e:)"] = "Gets the armor of the player" +E2Helper.Descriptions["volume(e:)"] = "Gets the volume of the entity" +E2Helper.Descriptions["surfaceArea(e:)"] = "Gets the surface area of the entity" +E2Helper.Descriptions["stress(e:)"] = "Gets the stress of the entity" +E2Helper.Descriptions["mass(e:)"] = "Gets the mass of the entity" +E2Helper.Descriptions["timeConnected(e:)"] = "Returns a players time connected to a server" +E2Helper.Descriptions["creationTime(e:)"] = "Returns the time the entity was created on, relative to curtime." +E2Helper.Descriptions["massCenter(e:)"] = "Gets the Center of Mass of the entity" +E2Helper.Descriptions["massCenterL(e:)"] = "Gets the center of mass as a local vector" +E2Helper.Descriptions["setMass(n)"] = "Sets the mass of the E2 chip (between 0.001 and 50,000)" +E2Helper.Descriptions["setMass(e:n)"] = "Sets the mass of the entity (between 0.001 and 50,000)" +E2Helper.Descriptions["inertia(e:)"] = "Gets the principal components of the entity's inertia tensor in the form ( Ixx, Iyy, Izz )" +E2Helper.Descriptions["applyForce(v)"] = "Applies force to the E2 chip according to the given vector's direction and magnitude" +E2Helper.Descriptions["applyForce(e:v)"] = "Applies force to the entity according to the given vector's direction and magnitude" +E2Helper.Descriptions["applyOffsetForce(vv)"] = "Applies force to the E2 chip according to the first vector from the location of the second" +E2Helper.Descriptions["applyOffsetForce(e:vv)"] = "Applies force to the entity according to the first vector from the location of the second" +E2Helper.Descriptions["applyAngForce(a)"] = "Applies torque to the E2 chip according to the given angle" +E2Helper.Descriptions["applyAngForce(e:a)"] = "Applies torque to the entity according to the given angle" +E2Helper.Descriptions["applyTorque(v)"] = "Applies torque to the E2 chip according to the given vector, representing the torque axis, magnitude and direction" +E2Helper.Descriptions["applyTorque(e:v)"] = "Applies torque to the entity according to the given vector, representing the torque axis, magnitude and direction" +E2Helper.Descriptions["isPlayer(e:)"] = "Is the entity a player?" +E2Helper.Descriptions["isOnFire(e:)"] = "Is the entity on fire?" +E2Helper.Descriptions["isWeapon(e:)"] = "Is the entity a weapon?" +E2Helper.Descriptions["isNPC(e:)"] = "Is the entity a NPC?" +E2Helper.Descriptions["isFrozen(e:)"] = "Is the entity frozen?" +E2Helper.Descriptions["isVehicle(e:)"] = "Is the entity a vehicle?" +E2Helper.Descriptions["inVehicle(e:)"] = "Is the player in a vehicle?" +E2Helper.Descriptions["isWorld(e:)"] = "Is the entity the world?" +E2Helper.Descriptions["isOnGround(e:)"] = "Is the player/NPC resting on something?" +E2Helper.Descriptions["isUnderWater(e:)"] = "Is the entity under water?" +E2Helper.Descriptions["isPlayerHolding(e:)"] = "Is the entity being held by a player?" +E2Helper.Descriptions["isAdmin(e:)"] = "Is the player an admin?" +E2Helper.Descriptions["isSuperAdmin(e:)"] = "Is the player a super admin?" +E2Helper.Descriptions["isAlive(e:)"] = "Is the player or NPC alive?" +E2Helper.Descriptions["isCrouch(e:)"] = "Is the player crouching?" +E2Helper.Descriptions["isFlashlightOn(e:)"] = "Returns 1 if the player has flashlight on, 0 otherwise" +E2Helper.Descriptions["isTyping(e:)"] = "Is the player typing a message in chat?" +E2Helper.Descriptions["isValid(e:)"] = "Returns 1 if the entity is valid, 0 otherwise" +E2Helper.Descriptions["isValidPhysics(e:)"] = "Returns 1 if the entity has valid physics (players don't)" +E2Helper.Descriptions["inNoclip(e:)"] = "Is the player in noclip mode?" +E2Helper.Descriptions["friends(e:)"] = "Returns an array of players on the prop protection friends list" +E2Helper.Descriptions["trusts(e:e)"] = "Is E2 on the prop protection friends list of E?" +E2Helper.Descriptions["keyAttack1(e:)"] = "Is the player pressing their primary fire key?" +E2Helper.Descriptions["keyAttack2(e:)"] = "Is the player pressing their secondary fire key?" +E2Helper.Descriptions["keyDuck(e:)"] = "Is the player pressing their crouch key?" +E2Helper.Descriptions["keyForward(e:)"] = "Is the player pressing their forward key? (default W)" +E2Helper.Descriptions["keyJump(e:)"] = "Is the player pressing their jump key?" +E2Helper.Descriptions["keyLeft(e:)"] = "Is the player pressing their left key? (default A)" +E2Helper.Descriptions["keyLeftTurn(e:)"] = "Is the player pressing their Look left key?" +E2Helper.Descriptions["keyReload(e:)"] = "Is the player pressing their reload key?" +E2Helper.Descriptions["keyRight(e:)"] = "Is the player pressing their right key? (default D)" +E2Helper.Descriptions["keyRightTurn(e:)"] = "Is the player pressing their Look right key?" +E2Helper.Descriptions["keySprint(e:)"] = "Is the player pressing their sprint key?" +E2Helper.Descriptions["keyWalk(e:)"] = "Is the player pressing their walk key?" +E2Helper.Descriptions["keyZoom(e:)"] = "Is the player pressing their zoom key?" +E2Helper.Descriptions["keyUse(e:)"] = "Is the player pressing their use key?" +E2Helper.Descriptions["keyPressed(e:s)"] = "Is the player pressing the KEY_ enumeration [S]? For example, 'W', 'K', '4', 'COMMA'" +E2Helper.Descriptions["keyBack(e:)"] = "Is the player pressing their back key? (default S)" +E2Helper.Descriptions["driver(e:)"] = "Returns the driver of the vehicle if there is one, nil otherwise" +E2Helper.Descriptions["passenger(e:)"] = "Returns the passenger of the vehicle if there is one, in single seat pods this will return the driver" +E2Helper.Descriptions["vehicle(e:)"] = "Returns the entity of the vehicle that the specified player is in" +E2Helper.Descriptions["ejectPod(e:)"] = "Ejects player in vehicle" +E2Helper.Descriptions["lockPod(e:n)"] = "1 locks and 0 unlocks the vehicle" +E2Helper.Descriptions["killPod(e:)"] = "Kills player in vehicle" +E2Helper.Descriptions["podStripWeapons(e:)"] = "Strips player in vehicle" +E2Helper.Descriptions["weapon(e:s)"] = "Returns the weapon with specified class of player E" +E2Helper.Descriptions["weapon(e:)"] = "Returns the weapon that player E is currently holding" +E2Helper.Descriptions["weapons(e:)"] = "Returns the weapons that player E has" +E2Helper.Descriptions["clip1(e:)"] = "Returns the amount of ammo in the primary clip of weapon E, -1 if there is no primary clip" +E2Helper.Descriptions["clip2(e:)"] = "Returns the amount of ammo in the secondary clip of weapon E, -1 if there is no secondary clip 1)" +E2Helper.Descriptions["primaryAmmoType(e:)"] = "Returns the name of the primary weapon's ammo" +E2Helper.Descriptions["secondaryAmmoType(e:)"] = "Returns the name of the secondary weapon's ammo" +E2Helper.Descriptions["ammoCount(e:s)"] = "Returns the amount of stored ammo of type S on player E, excluding current clip" +E2Helper.Descriptions["tool(e:)"] = "returns the name of the tool the player E is currently holding" +E2Helper.Descriptions["nearestPoint(e:v)"] = "Returns the closest point on the edge of the entity's bounding box to the given vector" +E2Helper.Descriptions["boxCenterW(e:)"] = "Same as using E:toWorld(E:boxCenter()), but since Lua is faster, this is more efficient (also shorter to write)" +E2Helper.Descriptions["aabbMin(e:)"] = "Returns the entity's (min) axis-aligned bounding box" +E2Helper.Descriptions["aabbMax(e:)"] = "Returns the entity's (max) axis-aligned bounding box" +E2Helper.Descriptions["aabbSize(e:)"] = "Returns the entity's axis-aligned bounding box size" +E2Helper.Descriptions["aabbWorldMin(e:)"] = "Returns the rotated entity's min world-axis-aligned bounding box corner" +E2Helper.Descriptions["aabbWorldMax(e:)"] = "Returns the rotated entity's max world-axis-aligned bounding box corner" +E2Helper.Descriptions["aabbWorldSize(e:)"] = "Returns the rotated entity's world-axis-aligned bounding box size" +E2Helper.Descriptions["keyClk()"] = "Returns the player that pressed/released the key if the E2 was triggered by runOnKeys" +E2Helper.Descriptions["keyClk(e)"] = "Returns 1 if the E2 was triggered by the player pressing a key or -1 when releasing a key" +E2Helper.Descriptions["keyClkPressed()"] = "Returns the name of the pressed/released key that triggered the E2" +E2Helper.Descriptions["runOnUse(n)"] = "If set to 1, E2 will run when a player presses E on the E2" +E2Helper.Descriptions["useClk()"] = "Returns the player that used the E2, if the E2 was triggered by runOnUse" +E2Helper.Descriptions["setTrails(e:nnnsvn)"] = "StartSize, EndSize, Length, Material, Color (RGB), Alpha. Adds a trail to E with the specified attributes" +E2Helper.Descriptions["setTrails(e:nnnsvnnn)"] = "StartSize, EndSize, Length, Material, Color (RGB), Alpha, AttachmentID, Additive. Adds a trail to E with the specified attributes" +E2Helper.Descriptions["removeTrails(e:)"] = "Removes the trail from E" +E2Helper.Descriptions["runOnKeys(en)"] = "If set to 1, E2 will run when specified player presses/releases their key" +E2Helper.Descriptions["playerDisconnectClk()"] = "Returns 1 if the chip is being executed because of a player disconnect event. Returns 0 otherwise" +E2Helper.Descriptions["lastDisconnectedPlayer()"] = "Returns the last player to disconnect. Must be done while in a disconnectClk() as anytime after the player object is gone." +E2Helper.Descriptions["runOnPlayerDisconnect(n)"] = "If set to 0, the chip will no longer run on player disconnect events, otherwise it makes this chip execute when someone disconnects. Only needs to be called once, not in every execution" +E2Helper.Descriptions["playerConnectClk()"] = "Returns 1 if the chip is being executed because of a player connect event. Returns 0 otherwise" +E2Helper.Descriptions["lastConnectedPlayer()"] = "Returns the last player to connect." +E2Helper.Descriptions["runOnPlayerConnect(n)"] = "If set to 0, the chip will no longer run on player connect events, otherwise it makes this chip execute when someone connects. Only needs to be called once, not in every execution" +E2Helper.Descriptions["inGodMode(e:)"] = "Returns whether the player has god mode or not" + +-- Attachment +E2Helper.Descriptions["lookupAttachment(e:s)"] = "Returns Es attachment ID associated with attachmentName" +E2Helper.Descriptions["attachmentPos(e:n)"] = "Returns Es attachment position associated with attachmentID" +E2Helper.Descriptions["attachmentAng(e:n)"] = "Returns Es attachment angle associated with attachmentID" +E2Helper.Descriptions["attachmentPos(e:s)"] = "Same as E:attachmentPos(E:lookupAttachment(attachmentName))" +E2Helper.Descriptions["attachmentAng(e:s)"] = "Same as E:attachmentAng(E:lookupAttachment(attachmentName))" +E2Helper.Descriptions["attachments(e:)"] = "Returns array of attachment names of the entity" + +-- Vector +E2Helper.Descriptions["vec2(n)"] = "Makes a 2D vector" +E2Helper.Descriptions["vec2(nn)"] = "Makes a 2D vector" +E2Helper.Descriptions["vec2()"] = "Same as vec2(0,0)" +E2Helper.Descriptions["vec2(v)"] = "Converts a 3D vector into a 2D vector (the z component is dropped)" +E2Helper.Descriptions["vec2(xv4)"] = "Converts a 4D vector into a 2D vector (the z and w components are dropped)" +E2Helper.Descriptions["shift(xv2)"] = "Swaps the vector's x,y components" +E2Helper.Descriptions["toAngle(xv2:)"] = "Returns the 2D angle of the vector (given in degrees, -180 to 180)" +E2Helper.Descriptions["dehomogenized(v:)"] = "Converts a 2D homogeneous vector (x,y,w) into a 2D cartesian vector" +E2Helper.Descriptions["vec(n)"] = "Makes a 3D vector" +E2Helper.Descriptions["vec(nnn)"] = "Makes a 3D vector" +E2Helper.Descriptions["vec()"] = "Same as vec(0,0,0)" +E2Helper.Descriptions["vec(xv2)"] = "Converts a 2D vector into a 3D vector (the z component is set to 0)" +E2Helper.Descriptions["vec(xv2n)"] = "Converts a 2D vector into a 3D vector (the z component is set to the second argument)" +E2Helper.Descriptions["vec(xv4)"] = "Converts a 4D vector into a 3D vector (the w component is dropped)" +E2Helper.Descriptions["vec(a)"] = "Changes an angle variable into a vector variable. PYR become XYZ respectively" +E2Helper.Descriptions["randvec()"] = "Returns a uniformly distributed, random, normalized direction vector" +E2Helper.Descriptions["randvec2()"] = "Returns a uniformly distributed, random, normalized direction vector" +E2Helper.Descriptions["randvec4()"] = "Returns a uniformly distributed, random, normalized direction vector" +E2Helper.Descriptions["randvec(nn)"] = "Returns a random vector with its components between N1 and N2" +E2Helper.Descriptions["randvec2(nn)"] = "Returns a random vector with its components between N1 and N2" +E2Helper.Descriptions["randvec4(nn)"] = "Returns a random vector with its components between N1 and N2" +E2Helper.Descriptions["randvec2(xv2xv2)"] = "Returns a random vector between V1 and V2" +E2Helper.Descriptions["randvec(vv)"] = "Returns a random vector between V1 and V2" +E2Helper.Descriptions["randvec4(xv4xv4)"] = "Returns a random vector between V1 and V2" +E2Helper.Descriptions["shiftL(v)"] = "Shifts the vector's components left: shiftL( x,y,z ) = ( y,z,x )" +E2Helper.Descriptions["shiftR(v)"] = "Shifts the vector's components right: shiftR( x,y,z ) = ( z,x,y )" +E2Helper.Descriptions["rotate(v:a)"] = "Gets the rotated vector" +E2Helper.Descriptions["rotate(v:nnn)"] = "Gets the rotated vector" +E2Helper.Descriptions["rotate(xv2:n)"] = "Rotates a vector by the argument (given in degrees)" +E2Helper.Descriptions["toAngle(v:)"] = "Converts a direction vector into an angle" +E2Helper.Descriptions["toAngle(v:v)"] = "Converts a direction vector into an angle with roll being determined by the up vector" +E2Helper.Descriptions["toDeg(xv2)"] = "Converts the vector's magnitude from radians to degrees" +E2Helper.Descriptions["toDeg(v)"] = "Converts the vector's magnitude from radians to degrees" +E2Helper.Descriptions["toDeg(xv4)"] = "Converts the vector's magnitude from radians to degrees" +E2Helper.Descriptions["toRad(xv2)"] = "Converts the vector's magnitude from radians to radians" +E2Helper.Descriptions["toRad(v)"] = "Converts the vector's magnitude from radians to radians" +E2Helper.Descriptions["toRad(xv4)"] = "Converts the vector's magnitude from radians to radians" +E2Helper.Descriptions["dehomogenized(xv4:)"] = "Converts a 3D homogeneous vector (x,y,z,w) into a 3D cartesian vector" +E2Helper.Descriptions["isInWorld(v:)"] = "Returns 1 if the position vector is within the world, 0 if not" +E2Helper.Descriptions["vec4(n)"] = "Makes a 4D vector" +E2Helper.Descriptions["vec4(nnnn)"] = "Makes a 4D vector" +E2Helper.Descriptions["vec4()"] = "Same as vec4(0,0,0,0)" +E2Helper.Descriptions["vec4(xv2)"] = "Converts a 2D vector into a 4D vector (the z and w components are set to 0)" +E2Helper.Descriptions["vec4(xv2nn)"] = "Converts a 2D vector into a 4D vector (the z and w components are set to the second and third arguments)" +E2Helper.Descriptions["vec4(xv2xv2)"] = "Creates a 4D vector from two 2D vectors" +E2Helper.Descriptions["vec4(v)"] = "Converts a 3D vector into a 4D vector (the w component is set to 0)" +E2Helper.Descriptions["vec4(vn)"] = "Converts a 3D vector into a 4D vector (the w component is set to the second argument)" +E2Helper.Descriptions["shiftL(xv4)"] = "Shifts the vector's components left: shiftL( x,y,z,w ) = ( y,z,w,x )" +E2Helper.Descriptions["shiftR(xv4)"] = "Shifts the vector's components right: shiftR( x,y,z,w ) = ( w,x,y,z )" +E2Helper.Descriptions["ceil(xv2)"] = "Rounds XY up to the nearest integer" +E2Helper.Descriptions["ceil(v)"] = "Rounds XYZ up to the nearest integer" +E2Helper.Descriptions["ceil(xv4)"] = "Rounds XYZW up to the nearest integer" +E2Helper.Descriptions["ceil(xv2n)"] = "Rounds XY up to argument 2's decimal precision" +E2Helper.Descriptions["ceil(vn)"] = "Rounds XYZ up to argument 2's decimal precision" +E2Helper.Descriptions["ceil(xv4n)"] = "Rounds XYZW up to argument 2's decimal precision" +E2Helper.Descriptions["floor(xv2)"] = "Rounds XY down to the nearest integer" +E2Helper.Descriptions["floor(v)"] = "Rounds XYZ down to the nearest integer" +E2Helper.Descriptions["floor(xv4)"] = "Rounds XYZW down to the nearest integer" +E2Helper.Descriptions["floor(xv2n)"] = "Rounds XY down to argument 2's decimal precision" +E2Helper.Descriptions["floor(vn)"] = "Rounds XYZ down to argument 2's decimal precision" +E2Helper.Descriptions["floor(xv4n)"] = "Rounds XYZW down to argument 2's decimal precision" +E2Helper.Descriptions["round(xv2)"] = "Rounds XY to the nearest integer" +E2Helper.Descriptions["round(v)"] = "Rounds XYZ to the nearest integer" +E2Helper.Descriptions["round(xv4)"] = "Rounds XYZW to the nearest integer" +E2Helper.Descriptions["round(xv2n)"] = "Rounds XY to argument 2's decimal precision" +E2Helper.Descriptions["round(vn)"] = "Rounds XYZ to argument 2's decimal precision" +E2Helper.Descriptions["round(xv4n)"] = "Rounds XYZW to argument 2's decimal precision" +E2Helper.Descriptions["mod(xv2n)"] = "Returns the remainder after XY have been divided by argument 2" +E2Helper.Descriptions["mod(vn)"] = "Returns the remainder after XYZ have been divided by argument 2" +E2Helper.Descriptions["mod(xv4n)"] = "Returns the remainder after XYZW have been divided by argument 2" +E2Helper.Descriptions["mod(xv2xv2)"] = "Returns the remainder after the components of vector 1 have been divided by the components of vector 2" +E2Helper.Descriptions["mod(vv)"] = "Returns the remainder after the components of vector 1 have been divided by the components of vector 2" +E2Helper.Descriptions["mod(xv4xv4)"] = "Returns the remainder after the components of vector 1 have been divided by the components of vector 2" +E2Helper.Descriptions["clamp(xv2xv2xv2)"] = "Clamps vector 1's XY between the XY of vector 2(min) and vector 3(max)" +E2Helper.Descriptions["clamp(vvv)"] = "Clamps vector 1's XYZ between the XYZ of vector 2(min) and vector 3(max)" +E2Helper.Descriptions["clamp(xv4xv4xv4)"] = "Clamps vector 1's XYZW between the XYZW of vector 2(min) and vector 3(max)" +E2Helper.Descriptions["clamp(xv2nn)"] = "Returns a vector in the same direction as vector 1, with length clamped between argument 2(min) and argument 3(max)" +E2Helper.Descriptions["clamp(vnn)"] = "Returns a vector in the same direction as vector 1, with length clamped between argument 2(min) and argument 3(max)" +E2Helper.Descriptions["clamp(xv4nn)"] = "Returns a vector in the same direction as vector 1, with length clamped between argument 2(min) and argument 3(max)" +E2Helper.Descriptions["min(xv2xv2)"] = "Returns the vector with the smallest length" +E2Helper.Descriptions["min(vv)"] = "Returns the vector with the smallest length" +E2Helper.Descriptions["min(xv4xv4)"] = "Returns the vector with the smallest length" +E2Helper.Descriptions["max(xv2xv2)"] = "Returns the vector with the greatest length" +E2Helper.Descriptions["max(vv)"] = "Returns the vector with the greatest length" +E2Helper.Descriptions["max(xv4xv4)"] = "Returns the vector with the greatest length" +E2Helper.Descriptions["minVec(xv2xv2)"] = "Returns a vector combining the lowest value components of V1 and V2" +E2Helper.Descriptions["minVec(vv)"] = "Returns a vector combining the lowest value components of V1 and V2" +E2Helper.Descriptions["minVec(xv4xv4)"] = "Returns a vector combining the lowest value components of V1 and V2" +E2Helper.Descriptions["maxVec(xv2xv2)"] = "Returns the vector combining the highest value components of V1 and V2" +E2Helper.Descriptions["maxVec(vv)"] = "Returns the vector combining the highest value components of V1 and V2" +E2Helper.Descriptions["maxVec(xv4xv4)"] = "Returns the vector combining the highest value components of V1 and V2" +E2Helper.Descriptions["mix(xv2xv2n)"] = "Combines vector 1's XY with vector 2's XY by a proportion given by argument 3 (between 0 and 1)" +E2Helper.Descriptions["mix(vvn)"] = "Combines vector 1's XYZ with vector 2's XYZ by a proportion given by argument 3 (between 0 and 1)" +E2Helper.Descriptions["mix(xv4xv4n)"] = "Combines vector 1's XYZW with vector 2's XYZW by a proportion given by argument 3 (between 0 and 1)" +E2Helper.Descriptions["positive(xv2)"] = "Returns a vector containing the positive value of each vector component, equivalent to abs(N)" +E2Helper.Descriptions["positive(v)"] = "Returns a vector containing the positive value of each vector component, equivalent to abs(N)" +E2Helper.Descriptions["positive(xv4)"] = "Returns a vector containing the positive value of each vector component, equivalent to abs(N)" +E2Helper.Descriptions["inrange(xv2xv2xv2)"] = "Returns 1 if each component of V is between (or is equal to) the components of Vmin and Vmax" +E2Helper.Descriptions["inrange(vvv)"] = "Returns 1 if each component of V is between (or is equal to) the components of Vmin and Vmax" +E2Helper.Descriptions["inrange(xv4xv4xv4)"] = "Returns 1 if each component of V is between (or is equal to) the components of Vmin and Vmax" +E2Helper.Descriptions["length(xv2:)"] = "Gets the length of the vector" +E2Helper.Descriptions["length(v:)"] = "Gets the length of the vector" +E2Helper.Descriptions["length(xv4:)"] = "Gets the length of the vector" +E2Helper.Descriptions["length2(xv2:)"] = "Gets the squared length of the vector" +E2Helper.Descriptions["length2(v:)"] = "Gets the squared length of the vector" +E2Helper.Descriptions["length2(xv4:)"] = "Gets the squared length of the vector" +E2Helper.Descriptions["distance(xv2:xv2)"] = "Gets the distance between 2D vectors" +E2Helper.Descriptions["distance(v:v)"] = "Gets the distance between vectors" +E2Helper.Descriptions["distance(xv4:xv4)"] = "Gets the distance between 4D vectors" +E2Helper.Descriptions["distance2(xv2:xv2)"] = "Gets the squared distance between 2D vectors" +E2Helper.Descriptions["distance2(v:v)"] = "Gets the squared distance between vectors" +E2Helper.Descriptions["distance2(xv4:xv4)"] = "Gets the squared distance between 4D vectors" +E2Helper.Descriptions["normalized(xv2:)"] = "Gets the normalized vector" +E2Helper.Descriptions["normalized(v:)"] = "Gets the normalized vector" +E2Helper.Descriptions["normalized(xv4:)"] = "Gets the normalized vector" +E2Helper.Descriptions["dot(xv2:xv2)"] = "Gets the 2D vector dot (scalar) product" +E2Helper.Descriptions["dot(v:v)"] = "Gets the vector dot (scalar) product" +E2Helper.Descriptions["dot(xv4:xv4)"] = "Gets the 4D vector dot (scalar) product" +E2Helper.Descriptions["cross(xv2:xv2)"] = "Gets the 2D vector cross product/wedge product" +E2Helper.Descriptions["cross(v:v)"] = "Gets the vector cross product" +E2Helper.Descriptions["x(xv2:)"] = "Gets the x component of the vector" +E2Helper.Descriptions["x(v:)"] = "Gets the x component of the vector" +E2Helper.Descriptions["x(xv4:)"] = "Gets the x component of the vector" +E2Helper.Descriptions["y(xv2:)"] = "Gets the y component of the vector" +E2Helper.Descriptions["y(v:)"] = "Gets the y component of the vector" +E2Helper.Descriptions["y(xv4:)"] = "Gets the y component of the vector" +E2Helper.Descriptions["z(v:)"] = "Gets the z component of the vector" +E2Helper.Descriptions["z(xv4:)"] = "Gets the z component of the vector" +E2Helper.Descriptions["w(xv4:)"] = "Gets the w component of the vector" +E2Helper.Descriptions["setX(xv2:n)"] = "Returns a copy of the 2D vector with X replaced (use as Vec2 = Vec2:setX(...))" +E2Helper.Descriptions["setX(v:n)"] = "Returns a copy of the vector with X replaced (use as Vec = Vec:setX(...))" +E2Helper.Descriptions["setX(xv4:n)"] = "Returns a copy of the 4D vector with X replaced (use as Vec4 = Vec4:setX(...))" +E2Helper.Descriptions["setY(xv2:n)"] = "Returns a copy of the 2D vector with Y replaced (use as Vec2 = Vec2:setY(...))" +E2Helper.Descriptions["setY(v:n)"] = "Returns a copy of the vector with Y replaced (use as Vec = Vec:setY(...))" +E2Helper.Descriptions["setY(xv4:n)"] = "Returns a copy of the 4D vector with Y replaced (use as Vec4 = Vec4:setY(...))" +E2Helper.Descriptions["setZ(v:n)"] = "Returns a copy of the vector with Z replaced (use as Vec = Vec:setZ(...))" +E2Helper.Descriptions["setZ(xv4:n)"] = "Returns a copy of the 4D vector with Z replaced (use as Vec4 = Vec4:setZ(...))" +E2Helper.Descriptions["setW(xv4:n)"] = "Returns a copy of the 4D vector with W replaced (use as Vec4 = Vec4:setW(...))" +E2Helper.Descriptions["toString(xv2:)"] = "Gets the vector nicely formatted as a string \"[X,Y]\"" +E2Helper.Descriptions["toString(xv2)"] = "Gets the vector nicely formatted as a string \"[X,Y]\"" +E2Helper.Descriptions["toString(v:)"] = "Gets the vector nicely formatted as a string \"[X,Y,Z]\"" +E2Helper.Descriptions["toString(v)"] = "Gets the vector nicely formatted as a string \"[X,Y,Z]\"" +E2Helper.Descriptions["toString(xv4:)"] = "Gets the vector nicely formatted as a string \"[X,Y,Z,W]\"" +E2Helper.Descriptions["toString(xv4)"] = "Gets the vector nicely formatted as a string \"[X,Y,Z,W]\"" +E2Helper.Descriptions["toWorld(vava)"] = "Converts a local position/angle to a world position/angle and returns the position" +E2Helper.Descriptions["toWorldAng(vava)"] = "Converts a local position/angle to a world position/angle and returns the angle" +E2Helper.Descriptions["toWorldPosAng(vava)"] = "Converts a local position/angle to a world position/angle and returns both in an array" +E2Helper.Descriptions["toLocal(vava)"] = "Converts a world position/angle to a local position/angle and returns the position" +E2Helper.Descriptions["toLocalAng(vava)"] = "Converts a world position/angle to a local position/angle and returns the angle" +E2Helper.Descriptions["toLocalPosAng(vava)"] = "Converts a world position/angle to a local position/angle and returns both in an array" +E2Helper.Descriptions["outerProduct(v:v)"] = "Gets the outer product (tensor product) and returns a matrix (tensor)" +E2Helper.Descriptions["outerProduct(xv2:xv2)"] = "Gets the outer product (tensor product) and returns a matrix (tensor)" +E2Helper.Descriptions["outerProduct(xv4:xv4)"] = "Gets the outer product (tensor product) and returns a matrix (tensor)" +E2Helper.Descriptions["pointContents(v)"] = "Returns a string with all the \"content\" types in the vector point, seperated by commas" +E2Helper.Descriptions["pointContentsArray(v)"] = "Returns an array with all the \"content\" types in the vector point" +E2Helper.Descriptions["pointHasContent(vs)"] = "'S' can be a string containing the last half of the CONTENTS_ enums (ie without the \"CONTENTS_\"). Multiple CONTENTS types can be seperated by a comma. Check: Enumeration_List:Contents for a full list. Examples: \"water,solid\" or \"empty,transparent\". The function returns 1 if any one of the types are found in the vector point" +E2Helper.Descriptions["bezier(xv2xv2xv2n)"] = "Returns the 2D position on the bezier curve between the starting and ending 2D vector, given by the ratio (value between 0 and 1)" +E2Helper.Descriptions["bezier(vvvn)"] = "Returns the 3D vector position on the bezier curve between the starting and ending 3D vector, given by the ratio (value between 0 and 1)" + +-- Matrix +E2Helper.Descriptions["identity2()"] = "Creates a 2x2 identity matrix" +E2Helper.Descriptions["matrix2()"] = "Creates a 2x2 zero matrix" +E2Helper.Descriptions["matrix2(nnnn)"] = "Creates a matrix with values in order (i.j) of: (1,1), (1,2), (2,1), (2,2)" +E2Helper.Descriptions["matrix2(xv2xv2)"] = "Creates a matrix with vectors by columns" +E2Helper.Descriptions["matrix2(m)"] = "Converts a 3x3 matrix into a 2x2 matrix - all (i,3) and (3,j) are omitted" +E2Helper.Descriptions["matrix2(xm4)"] = "Converts a 4x4 matrix into a 2x2 matrix - all (i,3), (i,4), (3,j) and (4,j) are omitted" +E2Helper.Descriptions["swapRows(xm2:)"] = "Swaps rows" +E2Helper.Descriptions["swapColumns(xm2:)"] = "Swaps columns" +E2Helper.Descriptions["setRow(xm2:nnn)"] = "Sets the values of a row. The first argument given specifies the row(j), the following arguments are the values 1j, 2j" +E2Helper.Descriptions["setRow(xm2:nxv2)"] = "Sets the values of a row. The first argument given specifies the row, the vector contains the values to set" +E2Helper.Descriptions["setColumn(xm2:nnn)"] = "Sets the values of a column. The first argument given specifies the column(i), the following arguments are the values i1, i2" +E2Helper.Descriptions["setColumn(xm2:nxv2)"] = "Sets the values of a column. The first argument given specifies the column, the vector contains the values to set" +E2Helper.Descriptions["identity2()"] = "Creates a 2x2 identity matrix" +E2Helper.Descriptions["matrix2()"] = "Creates a 2x2 zero matrix" +E2Helper.Descriptions["matrix2(nnnn)"] = "Creates a matrix with values in order (i.j) of: (1,1), (1,2), (2,1), (2,2)" +E2Helper.Descriptions["matrix2(xv2xv2)"] = "Creates a matrix with vectors by columns" +E2Helper.Descriptions["matrix2(m)"] = "Converts a 3x3 matrix into a 2x2 matrix - all (i,3) and (3,j) are omitted" +E2Helper.Descriptions["matrix2(xm4)"] = "Converts a 4x4 matrix into a 2x2 matrix - all (i,3), (i,4), (3,j) and (4,j) are omitted" +E2Helper.Descriptions["swapRows(xm2:)"] = "Swaps rows" +E2Helper.Descriptions["swapColumns(xm2:)"] = "Swaps columns" +E2Helper.Descriptions["setRow(xm2:nnn)"] = "Sets the values of a row. The first argument given specifies the row(j), the following arguments are the values 1j, 2j" +E2Helper.Descriptions["setRow(xm2:nxv2)"] = "Sets the values of a row. The first argument given specifies the row, the vector contains the values to set" +E2Helper.Descriptions["setColumn(xm2:nnn)"] = "Sets the values of a column. The first argument given specifies the column(i), the following arguments are the values i1, i2" +E2Helper.Descriptions["setColumn(xm2:nxv2)"] = "Sets the values of a column. The first argument given specifies the column, the vector contains the values to set" +E2Helper.Descriptions["identity()"] = "Creates a 3x3 identity matrix" +E2Helper.Descriptions["matrix()"] = "Creates a 3x3 zero matrix" +E2Helper.Descriptions["matrix(nnnnnnnnn)"] = "Creates a matrix with 9 values in the following order (i.j): (1,1), (1,2), (1,3), (2,1) etc" +E2Helper.Descriptions["matrix(vvv)"] = "Creates a matrix with vectors by columns" +E2Helper.Descriptions["matrix(xm2)"] = "Converts a 2x2 matrix into a 3x3 matrix - all (i,3) and (3,j) are filled with 0's" +E2Helper.Descriptions["matrix(xm4)"] = "Converts a 4x4 matrix into a 3x3 matrix - all (i,4) and (4,j) are omitted" +E2Helper.Descriptions["swapRows(m:nn)"] = "Swaps the two rows specified" +E2Helper.Descriptions["swapColumns(m:nn)"] = "Swaps the two columns specified" +E2Helper.Descriptions["setRow(m:nnnn)"] = "Sets the values of a row. The first argument given specifies the row(j), the following arguments are the values 1j, 2j, 3j" +E2Helper.Descriptions["setRow(m:nv)"] = "Sets the values of a row. The first argument given specifies the row, the vector contains the values to set" +E2Helper.Descriptions["setColumn(m:nnnn)"] = "Sets the values of a column. The first argument given specifies the column(i), the following arguments are the values i1, i2, i3" +E2Helper.Descriptions["setColumn(m:nv)"] = "Sets the values of a column. The first argument given specifies the column, the vector contains the values to set" +E2Helper.Descriptions["setDiagonal(m:nnn)"] = "Sets the elements of the leading diagonal" +E2Helper.Descriptions["setDiagonal(m:v)"] = "Sets the elements of the leading diagonal from the components of a vector" +E2Helper.Descriptions["matrix(e)"] = "Creates a reference frame matrix from an entity's local direction vectors by columns in the order ( x, y, z )" +E2Helper.Descriptions["matrix(a)"] = "Returns a 3x3 reference frame matrix as described by the angle A. Multiplying by this matrix will be the same as rotating by the given angle" +E2Helper.Descriptions["x(m:)"] = "Returns the local x direction vector from a 3x3 coordinate reference frame matrix ( same as M:column(1) )" +E2Helper.Descriptions["y(m:)"] = "Returns the local y direction vector from a 3x3 coordinate reference frame matrix ( same as M:column(2) )" +E2Helper.Descriptions["z(m:)"] = "Returns the local z direction vector from a 3x3 coordinate reference frame matrix ( same as M:column(3) )" +E2Helper.Descriptions["mRotation(vn)"] = "Creates a 3x3 rotation matrix, where the vector is the axis of rotation, and the number is the angle (anti-clockwise) in degrees. Example*: to rotate a vector (7,8,9) by 50 degrees about the axis (1,1,0), you would write V = mRotation(vec(1,1,0), 50) * vec(7,8,9)" +E2Helper.Descriptions["identity4()"] = "Creates a 4x4 identity matrix" +E2Helper.Descriptions["matrix4()"] = "Creates a 4x4 zero matrix" +E2Helper.Descriptions["matrix4(nnnnnnnnnnnnnnnn)"] = "Creates a matrix with 16 values in the following order (i.j): (1,1), (1,2), (1,3), (1,4), (2,1) etc" +E2Helper.Descriptions["matrix4(xv4xv4xv4xv4)"] = "Creates a matrix with vectors by columns" +E2Helper.Descriptions["matrix4(xm2)"] = "Converts a 2x2 matrix into a 4x4 matrix - all (i,3), (i,4), (3,j) and (4,j) are filled with 0's" +E2Helper.Descriptions["matrix4(xm2xm2xm2xm2)"] = "Constructs a 4x4 matrix from four 2x2 matrices" +E2Helper.Descriptions["matrix4(m)"] = "Converts a 3x3 matrix into a 4x4 matrix - all (i,4) and (4,j) are filled with 0's" +E2Helper.Descriptions["swapRows(xm4:nn)"] = "Swaps the two rows specified" +E2Helper.Descriptions["swapColumns(xm4:nn)"] = "Swaps the two columns specified" +E2Helper.Descriptions["setRow(xm4:nnnnn)"] = "Sets the values of a row. The first argument given specifies the row(j), the following arguments are the values 1j, 2j, 3j, 4j" +E2Helper.Descriptions["setRow(xm4:nxv4)"] = "Sets the values of a row. The first argument given specifies the row, the vector contains the values to set" +E2Helper.Descriptions["setColumn(xm4:nnnnn)"] = "Sets the values of a column. The first argument given specifies the column(i), the following arguments are the values i1, i2, i3, i4" +E2Helper.Descriptions["setColumn(xm4:nxv4)"] = "Sets the values of a column. The first argument given specifies the column, the vector contains the values to set" +E2Helper.Descriptions["setDiagonal(xm4:nnnn)"] = "Sets the elements of the leading diagonal" +E2Helper.Descriptions["setDiagonal(xm4:xv4)"] = "Sets the elements of the leading diagonal from the components of a vector" +E2Helper.Descriptions["matrix4(e)"] = "Creates a 4x4 reference frame matrix from an entity's local direction vectors by columns in the order (x, y, z, pos), with the bottom row (0,0,0,1)" +E2Helper.Descriptions["matrix4(a)"] = "Returns a 4x4 reference frame matrix as described by the angle A. Multiplying by this matrix will be the same as rotating by the given angle" +E2Helper.Descriptions["matrix4(av)"] = "Returns a 4x4 reference frame matrix as described by the angle A and the position V. Multiplying by this matrix will be the same as rotating by the given angle and offsetting by the given vector" +E2Helper.Descriptions["x(xm4:)"] = "Returns the local x direction vector from a 4x4 coordinate reference frame matrix" +E2Helper.Descriptions["y(xm4:)"] = "Returns the local y direction vector from a 4x4 coordinate reference frame matrix" +E2Helper.Descriptions["z(xm4:)"] = "Returns the local z direction vector from a 4x4 coordinate reference frame matrix" +E2Helper.Descriptions["pos(xm4:)"] = "Returns the position vector from a 4x4 coordinate reference frame matrix" +E2Helper.Descriptions["inverseA(xm4)"] = "Finds the matrix inverse of a standard 4x4 affine transformation matrix ( the type created by matrix4(E) ). This should only be used on matrices with a particular format, where the top left 3x3 specifies rotation, the rightmost 3-column specifies translation, and the bottom row is (0,0,0,1)" +E2Helper.Descriptions["row(xm:n)"] = "Returns the row as a vector" +E2Helper.Descriptions["column(xm2:n)"] = "Returns the column as a 2D vector" +E2Helper.Descriptions["column(m:n)"] = "Returns the column as a vector" +E2Helper.Descriptions["column(xm4:n)"] = "Returns the column as a 4D vector" +E2Helper.Descriptions["element(xm2:nn)"] = "Returns the element with indices (i,j)" +E2Helper.Descriptions["element(m:nn)"] = "Returns the element with indices (i,j)" +E2Helper.Descriptions["element(xm4:nn)"] = "Returns the element with indices (i,j)" +E2Helper.Descriptions["setElement(xm2:nnn)"] = "Sets an element's value. The first two arguments specify the indices (i,j), the third argument is the value to set it to" +E2Helper.Descriptions["setElement(m:nnn)"] = "Sets an element's value. The first two arguments specify the indices (i,j), the third argument is the value to set it to" +E2Helper.Descriptions["setElement(xm4:nnn)"] = "Sets an element's value. The first two arguments specify the indices (i,j), the third argument is the value to set it to" +E2Helper.Descriptions["swapElements(xm2:nnnn)"] = "Swaps two elements, specified by indices ( i1, j1, i2, j2 )" +E2Helper.Descriptions["swapElements(m:nnnn)"] = "Swaps two elements, specified by indices ( i1, j1, i2, j2 )" +E2Helper.Descriptions["swapElements(xm4:nnnn)"] = "Swaps two elements, specified by indices ( i1, j1, i2, j2 )" +E2Helper.Descriptions["diagonal(xm2)"] = "Returns a 2D vector comprising the elements along the leading diagonal" +E2Helper.Descriptions["diagonal(m)"] = "Returns a vector comprising the elements along the leading diagonal" +E2Helper.Descriptions["diagonal(xm4)"] = "Returns a 4D vector comprising the elements along the leading diagonal" +E2Helper.Descriptions["trace(xm2)"] = "Returns the trace of a matrix" +E2Helper.Descriptions["trace(m)"] = "Returns the trace of a matrix" +E2Helper.Descriptions["trace(xm4)"] = "Returns the trace of a matrix" +E2Helper.Descriptions["det(xm2)"] = "Returns the determinant of a matrix (Does not work for 4x4 matrices)" +E2Helper.Descriptions["det(m)"] = "Returns the determinant of a matrix (Does not work for 4x4 matrices)" +E2Helper.Descriptions["transpose(xm2)"] = "Returns the transpose of a matrix" +E2Helper.Descriptions["transpose(m)"] = "Returns the transpose of a matrix" +E2Helper.Descriptions["transpose(xm4)"] = "Returns the transpose of a matrix" +E2Helper.Descriptions["adj(m)"] = "Returns the adjugate of a matrix (Does not work for 4x4 matrices)" +E2Helper.Descriptions["adj(xm2)"] = "Returns the adjugate of a matrix (Does not work for 4x4 matrices)" +E2Helper.Descriptions["row(xm2:n)"] = "Returns the row as a 2D vector" +E2Helper.Descriptions["row(m:n)"] = "Returns the row as a vector" +E2Helper.Descriptions["row(xm4:n)"] = "Returns the row as a 4D vector" +E2Helper.Descriptions["rowMatrix(vvv)"] = "Creates a 3x3 matrix with vectors by rows" +E2Helper.Descriptions["rowMatrix2(xv2xv2)"] = "Creates a 2x2 matrix with 2D vectors by rows" +E2Helper.Descriptions["rowMatrix4(xv4xv4xv4xv4)"] = "Creates a 4x4 matrix with 4D vectors by rows" +E2Helper.Descriptions["toAngle(m:)"] = "Returns an angle derived from a 3x3 rotation matrix" + +-- Angle +E2Helper.Descriptions["ang(n)"] = "Makes an angle" +E2Helper.Descriptions["ang(nnn)"] = "Makes an angle" +E2Helper.Descriptions["ang()"] = "Same as ang(0,0,0)" +E2Helper.Descriptions["ang(v)"] = "Changes a vector variable into an angle variable. XYZ become PYR respectively" +E2Helper.Descriptions["ceil(a)"] = "Rounds PYR up to the nearest integer" +E2Helper.Descriptions["ceil(an)"] = "Rounds PYR up to argument 2's decimal precision" +E2Helper.Descriptions["floor(a)"] = "Rounds PYR down to the nearest integer" +E2Helper.Descriptions["floor(an)"] = "Rounds PYR down to argument 2's decimal precision" +E2Helper.Descriptions["round(a)"] = "Rounds PYR to the nearest integer" +E2Helper.Descriptions["round(an)"] = "Rounds PYR to argument 2's decimal precision" +E2Helper.Descriptions["mod(an)"] = "Returns the remainder after PYR have been divided by argument 2" +E2Helper.Descriptions["mod(aa)"] = "Returns the remainder after the components of angle 1 have been divided by the components of angle 2" +E2Helper.Descriptions["clamp(aaa)"] = "Clamps angle 1's PYR between the PYR of angle 2(min) and angle 3(max)" +E2Helper.Descriptions["clamp(ann)"] = "Clamps angle 1's PYR between argument 2(min) and argument 3(max)" +E2Helper.Descriptions["mix(aan)"] = "Combines angle 1's PYR with angle 2's PYR by a proportion given by argument 3 (between 0 and 1)" +E2Helper.Descriptions["shiftL(a)"] = "Shifts the angle's components left: shiftL( p,y,r ) = ( y,r,p )" +E2Helper.Descriptions["shiftR(a)"] = "Shifts the angle's components right: shiftR( p,y,r ) = ( r,p,y )" +E2Helper.Descriptions["inrange(aaa)"] = "Returns 1 if each component of A is between (or is equal to) the components of Amin and Amax" +E2Helper.Descriptions["angnorm(a)"] = "Gets the normalized angle of an angle" +E2Helper.Descriptions["angnorm(n)"] = "Gets the normalized angle of a number" +E2Helper.Descriptions["pitch(a:)"] = "Gets the pitch of the angle" +E2Helper.Descriptions["yaw(a:)"] = "Gets the yaw of the angle" +E2Helper.Descriptions["roll(a:)"] = "Gets the roll of the angle" +E2Helper.Descriptions["setPitch(a:n)"] = "Returns a copy of the angle with Pitch replaced (use as Ang = Ang:setPitch(...))" +E2Helper.Descriptions["setYaw(a:n)"] = "Returns a copy of the angle with Yaw replaced (use as Ang = Ang:setYaw(...))" +E2Helper.Descriptions["setRoll(a:n)"] = "Returns a copy of the angle with Roll replaced (use as Ang = Ang:setRoll(...))" +E2Helper.Descriptions["toString(a)"] = "Gets the angle nicely formatted as a string \"[P,Y,R]\"" +E2Helper.Descriptions["toString(a:)"] = "Gets the angle nicely formatted as a string \"[P,Y,R]\"" +E2Helper.Descriptions["toDeg(a)"] = "Converts the angle's magnitude from radians to degrees" +E2Helper.Descriptions["toRad(a)"] = "Converts the angle's magnitude from radians to radians" + +-- Entity +E2Helper.Descriptions["forward(a:)"] = "Gets the forward vector of the angle" +E2Helper.Descriptions["right(a:)"] = "Gets the right vector of the angle" +E2Helper.Descriptions["up(a:)"] = "Gets the up vector of the angle" +E2Helper.Descriptions["rotateAroundAxis(a:vn)"] = "Returns the angle A rotated around vector V by N degrees" +E2Helper.Descriptions["rotateAroundAxis(v:vn)"] = "Returns the vector V1 rotated around vector V2 by N degrees" +E2Helper.Descriptions["bone(e:n)"] = "Returns Es Nth bone" +E2Helper.Descriptions["bones(e:)"] = "Returns an array containing all of Es bones. This array's first element has the index 0!" +E2Helper.Descriptions["boneCount(e:)"] = "Returns Es number of bones" +E2Helper.Descriptions["nobone()"] = "Returns an invalid bone" +E2Helper.Descriptions["aimBone(e:)"] = "Returns the bone the player is currently aiming at" +E2Helper.Descriptions["entity(b:)"] = "Returns the entity B belongs to" +E2Helper.Descriptions["index(b:)"] = "Returns Bs index in the entity it belongs to. Returns -1 if the bone is invalid or an error occured" +E2Helper.Descriptions["pos(b:)"] = "Returns Bs position" +E2Helper.Descriptions["forward(b:)"] = "Returns a vector describing Bs forward direction" +E2Helper.Descriptions["right(b:)"] = "Returns a vector describing Bs right direction" +E2Helper.Descriptions["up(b:)"] = "Returns a vector describing Bs up direction" +E2Helper.Descriptions["vel(b:)"] = "Returns Bs velocity" +E2Helper.Descriptions["velL(b:)"] = "Returns Bs velocity in local coordinates" +E2Helper.Descriptions["toWorld(b:v)"] = "Transforms V from local coordinates (as seen from B) to world coordinates" +E2Helper.Descriptions["toLocal(b:v)"] = "Transforms V from world coordinates to local coordinates (as seen from B)" +E2Helper.Descriptions["angVel(b:)"] = "Returns Bs angular velocity" +E2Helper.Descriptions["angles(b:)"] = "Returns Bs pitch, yaw and roll angles" +E2Helper.Descriptions["bearing(b:v)"] = "Gets the bearing from the bone to the vector" +E2Helper.Descriptions["elevation(b:v)"] = "Gets the elevation from the bone to the vector" +E2Helper.Descriptions["heading(b:v)"] = "Gets the elevation and bearing from the bone to the vector" +E2Helper.Descriptions["mass(b:)"] = "Returns Bs mass" +E2Helper.Descriptions["massCenter(b:)"] = "Returns Bs Center of Mass" +E2Helper.Descriptions["massCenterL(b:)"] = "Returns Bs Center of Mass in local coordinates" +E2Helper.Descriptions["inertia(b:)"] = "Gets the principal components of Bs inertia tensor in the form vec(Ixx, Iyy, Izz)" +E2Helper.Descriptions["isFrozen(b:)"] = "Returns 1 if B is frozen, 0 otherwise" +E2Helper.Descriptions["angVelVector(b:)"] = "Returns rotation axis, velocity and direction given as the vector's direction, magnitude and sense" +E2Helper.Descriptions["applyOffsetForce(b:vv)"] = "Applies force to the bone according to the first vector from the location of the second" +E2Helper.Descriptions["applyForce(b:v)"] = "Applies force to the bone according to the given vector's direction and magnitude" +E2Helper.Descriptions["applyAngForce(b:a)"] = "Applies torque to the bone according to the given angle" +E2Helper.Descriptions["applyTorque(b:v)"] = "Applies torque to the bone according to the given vector, representing the torque axis, magnitude and direction" +E2Helper.Descriptions["setMass(b:n)"] = "Sets the mass of the bone (between 0.001 and 50,000)" +E2Helper.Descriptions["toString(e:)"] = "Converts entity to string" +E2Helper.Descriptions["toString(e)"] = "Converts entity to string" +E2Helper.Descriptions["toString(b)"] = "Converts bone to string" + +-- Wirelink +E2Helper.Descriptions["nowirelink()"] = "Returns an invalid wirelink" +E2Helper.Descriptions["wirelink()"] = "Returns wirelink to this E2" +E2Helper.Descriptions["isHiSpeed(xwl:)"] = "Returns true if the linked component is high-speed capable" +E2Helper.Descriptions["entity(xwl:)"] = "Returns the entity of the linked component" +E2Helper.Descriptions["hasInput(xwl:s)"] = "Returns true if the linked component has an input of the specified name" +E2Helper.Descriptions["hasOutput(xwl:s)"] = "Returns true if the linked component has an output of the specified name" +E2Helper.Descriptions["xyz(xwl:)"] = "Retrieves the X/Y/Z as the corresponding values in the vector" +E2Helper.Descriptions["writeString(xwl:ns)"] = "Writes a null-terminated string to the given address. Returns the next free address or 0 on failure" +E2Helper.Descriptions["writeString(xwl:snnv)"] = "Same as XWL:writeString(snn), with an extra argument for the text colour" +E2Helper.Descriptions["writeString(xwl:snnvn)"] = "Same as XWL:writeString(snnv), with an extra argument for background colour. This is in the form of a 3-digit RGB code. 0 is black, while 999 is white, 900 is pure red and so on" +E2Helper.Descriptions["writeString(xwl:snnvv)"] = "Same as XWL:writeString(snnv), with an extra argument for background colour" +E2Helper.Descriptions["writeString(xwl:snnn)"] = "Same as XWL:writeString(snn), with an extra argument for the text colour. This is in the form of a 3-digit RGB code. 0 is black, while 999 is white, 900 is pure red and so on" +E2Helper.Descriptions["writeString(xwl:snnvvn)"] = "Same as XWL:writeString(snnvv), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeString(xwl:snn)"] = "A helper function for using the Wired Console Screen. The string will be written to the screen in white text on black background. The number arguments specify the starting position - X/Horizontal (0-29 recommended) and Y/vertical (0-17)" +E2Helper.Descriptions["writeString(xwl:snnnnn)"] = "Same as XWL:writeString(snnnn), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeString(xwl:snnnn)"] = "Same as XWL:writeString(snnn), with an extra argument for background colour. 3-digit RGB again" +E2Helper.Descriptions["writeString(xwl:snnvnn)"] = "Same as XWL:writeString(snnvn), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeString(xwl:snnnv)"] = "Same as XWL:writeString(snnn), with an extra argument for background colour" +E2Helper.Descriptions["writeString(xwl:snnnvn)"] = "Same as XWL:writeString(snnnv), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeUnicodeString(xwl:snnv)"] = "Same as XWL:writeUnicodeString(snn), with an extra argument for the text colour" +E2Helper.Descriptions["writeUnicodeString(xwl:snnvn)"] = "Same as XWL:writeUnicodeString(snnv), with an extra argument for background colour. This is in the form of a 3-digit RGB code. 0 is black, while 999 is white, 900 is pure red and so on" +E2Helper.Descriptions["writeUnicodeString(xwl:snnvv)"] = "Same as XWL:writeUnicodeString(snnv), with an extra argument for background colour" +E2Helper.Descriptions["writeUnicodeString(xwl:snnn)"] = "Same as XWL:writeUnicodeString(snn), with an extra argument for the text colour. This is in the form of a 3-digit RGB code. 0 is black, while 999 is white, 900 is pure red and so on" +E2Helper.Descriptions["writeUnicodeString(xwl:snnvvn)"] = "Same as XWL:writeUnicodeString(snnvv), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeUnicodeString(xwl:snn)"] = "A helper function for using the Wired Console Screen. The unicode string will be written to the screen in white text on black background. The number arguments specify the starting position - X/Horizontal (0-29 recommended) and Y/vertical (0-17)" +E2Helper.Descriptions["writeUnicodeString(xwl:snnnnn)"] = "Same as XWL:writeUnicodeString(snnnn), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeUnicodeString(xwl:snnnn)"] = "Same as XWL:writeUnicodeString(snnn), with an extra argument for background colour. 3-digit RGB again" +E2Helper.Descriptions["writeUnicodeString(xwl:snnvnn)"] = "Same as XWL:writeUnicodeString(snnvn), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["writeUnicodeString(xwl:snnnv)"] = "Same as XWL:writeUnicodeString(snnn), with an extra argument for background colour" +E2Helper.Descriptions["writeUnicodeString(xwl:snnnvn)"] = "Same as XWL:writeUnicodeString(snnnv), with an extra argument for flashing text. 0 or 1 is recommended" +E2Helper.Descriptions["readArray(xwl:nn)"] = "Reads an array's elements from a piece of memory. Strings and sub-tables (angles, vectors, matrices) are written as pointers to the actual data. Strings are written null-terminated" +E2Helper.Descriptions["writeCell(xwl:nn)"] = "Writes value into specified memory cell. Deprecated, use XWL[N] = X instead" +E2Helper.Descriptions["readCell(xwl:n)"] = "Returns contents of the specified memory cell. Deprecated, use XWL[N] instead" +E2Helper.Descriptions["readString(xwl:n)"] = "Reads a null-terminated string from the given address. Returns an empty string on failure" +E2Helper.Descriptions["writeArray(xwl:nr)"] = "Writes an array's elements into a piece of memory. Strings and sub-tables (angles, vectors, matrices) are written as pointers to the actual data. Strings are written null-terminated" +E2Helper.Descriptions["writeTable(xwl:nt)"] = "Same as writeArray, except it uses the numerically indexed variables of the table instead" +E2Helper.Descriptions["inputs(xwl:)"] = "Returns an array of all the inputs that XWL has without their types. Returns an empty array if it has none" +E2Helper.Descriptions["outputs(xwl:)"] = "Returns an array of all the outputs that XWL has without their types. Returns an empty array if it has none" +E2Helper.Descriptions["inputType(xwl:s)"] = "Returns the type of input that S is in lowercase. ( \"NORMAL\" is changed to \"number\" )" +E2Helper.Descriptions["outputType(xwl:s)"] = "Returns the type of output that S is in lowercase. ( \"NORMAL\" is changed to \"number\" )" +E2Helper.Descriptions["setXyz(xwl:v)"] = "Sets the X/Y/Z to the corresponding values in the vector" + +-- Quaternions +E2Helper.Descriptions["comp()"] = "Returns complex zero" +E2Helper.Descriptions["comp(n)"] = "Converts a real number to complex (returns complex number with real part N and imaginary part 0)" +E2Helper.Descriptions["comp(nn)"] = "Returns a + b*i" +E2Helper.Descriptions["i()"] = "Returns the imaginary unit i" +E2Helper.Descriptions["i(n)"] = "Returns N*i" +E2Helper.Descriptions["abs(c)"] = "Returns the absolute value of C" +E2Helper.Descriptions["arg(c)"] = "Returns the argument of C" +E2Helper.Descriptions["conj(c)"] = "Returns the conjugate of C" +E2Helper.Descriptions["real(c)"] = "Returns the real part of C" +E2Helper.Descriptions["imag(c)"] = "Returns the imaginary part of C" +E2Helper.Descriptions["exp(c)"] = "Raises Euler's constant e to the power of C" +E2Helper.Descriptions["log(c)"] = "Calculates the natural logarithm of C" +E2Helper.Descriptions["log(cc)"] = "Calculates the logarithm of C2 to a complex base C" +E2Helper.Descriptions["log(nc)"] = "Calculates the logarithm of C to a real base N" +E2Helper.Descriptions["log2(c)"] = "Calculates the logarithm of C to base 2" +E2Helper.Descriptions["log10(c)"] = "Calculates the logarithm of C to base 10" +E2Helper.Descriptions["sqrt(c)"] = "Calculates the square root of C" +E2Helper.Descriptions["csqrt(n)"] = "Calculates the complex square root of the real number N" +E2Helper.Descriptions["sin(c)"] = "Calculates the sine of C" +E2Helper.Descriptions["cos(c)"] = "Calculates the cosine of C" +E2Helper.Descriptions["tan(c)"] = "Calculates the tangent of C" +E2Helper.Descriptions["cot(c)"] = "Calculates the cotangent of C" +E2Helper.Descriptions["sec(c)"] = "Calculates the secant of C" +E2Helper.Descriptions["csc(c)"] = "Calculates the cosecant of C" +E2Helper.Descriptions["asin(c)"] = "Calculates the inverse sine of C" +E2Helper.Descriptions["acos(c)"] = "Calculates the inverse cosine of C" +E2Helper.Descriptions["atan(c)"] = "Calculates the inverse tangent of C" +E2Helper.Descriptions["atan2(c)"] = "Calculates the principle value of C" +E2Helper.Descriptions["sinh(c)"] = "Calculates the hyperbolic sine of C" +E2Helper.Descriptions["cosh(c)"] = "Calculates the hyperbolic cosine of C" +E2Helper.Descriptions["tanh(c)"] = "Calculates the hyperbolic tangent of C" +E2Helper.Descriptions["coth(c)"] = "Calculates the hyperbolic cotangent of C" +E2Helper.Descriptions["sech(c)"] = "Calculates the hyperbolic secant of C" +E2Helper.Descriptions["csch(c)"] = "Calculates the hyperbolic cosecant of C" +E2Helper.Descriptions["toString(c)"] = "Formats C as a string" +E2Helper.Descriptions["toString(c:)"] = "The same as toString(C)" +E2Helper.Descriptions["quat()"] = "Creates a zero quaternion" +E2Helper.Descriptions["quat(n)"] = "Creates a quaternion with real part equal to N" +E2Helper.Descriptions["quat(c)"] = "Creates a quaternion with real and \"i\" parts equal to C" +E2Helper.Descriptions["quat(v)"] = "Converts a vector to a quaternion (returns V.x*i + V.y*j + V.z*k)" +E2Helper.Descriptions["quat(nnnn)"] = "Returns N+N2i+N3j+N4k" +E2Helper.Descriptions["quat(a)"] = "Converts A to a quaternion" +E2Helper.Descriptions["quat(vv)"] = "Creates a quaternion given forward (V) and up (V2) vectors" +E2Helper.Descriptions["quat(e)"] = "Converts angle of E to a quaternion" +E2Helper.Descriptions["qi()"] = "Returns quaternion i" +E2Helper.Descriptions["qi(n)"] = "Returns quaternion N*i" +E2Helper.Descriptions["qj()"] = "Returns j" +E2Helper.Descriptions["qj(n)"] = "Returns N*j" +E2Helper.Descriptions["qk()"] = "Returns k" +E2Helper.Descriptions["qk(n)"] = "Returns N*k" +E2Helper.Descriptions["dot(q:q)"] = "Returns dot product of Q with Q2" +E2Helper.Descriptions["abs(q)"] = "Returns absolute value of Q" +E2Helper.Descriptions["conj(q)"] = "Returns the conjugate of Q" +E2Helper.Descriptions["inv(q)"] = "Returns the inverse of Q" +E2Helper.Descriptions["real(q:)"] = "Returns the real component of the quaternion" +E2Helper.Descriptions["i(q:)"] = "Returns the i component of the quaternion" +E2Helper.Descriptions["j(q:)"] = "Returns the j component of the quaternion" +E2Helper.Descriptions["k(q:)"] = "Returns the k component of the quaternion" +E2Helper.Descriptions["exp(q)"] = "Raises Euler's constant e to the power Q" +E2Helper.Descriptions["log(q)"] = "Calculates natural logarithm of Q" +E2Helper.Descriptions["qMod(q)"] = "Changes quaternion Q so that the represented rotation is by an angle between 0 and 180 degrees (by coder0xff)" +E2Helper.Descriptions["nlerp(qqn)"] = "Performs linear interpolation between Q and Q2. Returns normalized Q for N=0, Q2 for N=1." +E2Helper.Descriptions["slerp(qqn)"] = "Performs spherical linear interpolation between Q and Q2. Returns Q for N=0, Q2 for N=1" +E2Helper.Descriptions["forward(q:)"] = "Returns vector pointing forward for Q" +E2Helper.Descriptions["right(q:)"] = "Returns vector pointing right for Q" +E2Helper.Descriptions["up(q:)"] = "Returns vector pointing up for Q" +E2Helper.Descriptions["normalized(q:)"] = "Returns new normalized quaternion for Q" +E2Helper.Descriptions["qRotation(vn)"] = "Returns quaternion for rotation about axis V by angle N" +E2Helper.Descriptions["qRotation(v)"] = "Construct a quaternion from the rotation vector V. Vector direction is axis of rotation, magnitude is angle in degress (by coder0xff)" +E2Helper.Descriptions["rotationAngle(q)"] = "Returns the angle of rotation in degrees (by coder0xff)" +E2Helper.Descriptions["rotationAxis(q)"] = "Returns the axis of rotation (by coder0xff)" +E2Helper.Descriptions["rotationVector(q)"] = "Returns the rotation vector - rotation axis where magnitude is the angle of rotation in degress (by coder0xff)" +E2Helper.Descriptions["vec(q)"] = "Converts Q to a vector by dropping the real component" +E2Helper.Descriptions["matrix(q)"] = "Converts Q to a transformation matrix" +E2Helper.Descriptions["toAngle(q:)"] = "Returns angle represented by Q" +E2Helper.Descriptions["toString(q)"] = "Formats Q as a string" +E2Helper.Descriptions["toString(q:)"] = "Formats Q as a string" + +-- Selfaware +E2Helper.Descriptions["first()"] = "Returns 1 if the expression was spawned or reset" +E2Helper.Descriptions["duped()"] = "Returns 1 if the expression was duplicated" +E2Helper.Descriptions["dupefinished()"] = "Returns 1 when the contraption has finished duping." +E2Helper.Descriptions["inputClk()"] = "Returns 1 if the expression was triggered by an input" +E2Helper.Descriptions["last()"] = "Returns 1 if it is being called on the last execution of the expression gate before it is removed or reset. This execution must be requested with the runOnLast(1) command" +E2Helper.Descriptions["removing()"] = "Returns 1 if this is the last() execution and caused by the entity being removed" +E2Helper.Descriptions["ops()"] = "Returns how many ops are used every execution on average" +E2Helper.Descriptions["opcounter()"] = "Returns how many ops have been used so far in this execution plus the amount of hard quota used" +E2Helper.Descriptions["minquota()"] = "The ops left before soft quota is used up" +E2Helper.Descriptions["maxquota()"] = "The ops left before hard quota is exceeded and the expression shuts down" +E2Helper.Descriptions["softQuota()"] = "Returns the size of the soft quota" +E2Helper.Descriptions["hardQuota()"] = "Returns the size of the hard quota" +E2Helper.Descriptions["timeQuota()"] = "Returns the time quota in seconds" +E2Helper.Descriptions["perf()"] = "If used as a while loop condition, stabilizes the expression around hardquota used" +E2Helper.Descriptions["perf(n)"] = "If used as a while loop condition, stabilizes the expression around specified number (in %)" +E2Helper.Descriptions["entity()"] = "Gets the entity of the expression" +E2Helper.Descriptions["getName(e:)"] = "Get the name of another E2, compatible entity or wiremod component name" +E2Helper.Descriptions["setName(s)"] = "Set the name of the E2" +E2Helper.Descriptions["setName(e:s)"] = "Set the name of another E2 or component name for other entities" +E2Helper.Descriptions["cpuUsage()"] = "Returns the average time per tick the server spends running this E2, in seconds (multiply it by 1000000 to get the same value as is displayed on the E2 overlay)" +E2Helper.Descriptions["cpuUsage(e:)"] = "Returns the average time per tick the server spends running the specified E2, in seconds (multiply it by 1000000 to get the same value as is displayed on the E2 overlay)" +E2Helper.Descriptions["error(s)"] = "Shuts down the E2 with specified script error message" +E2Helper.Descriptions["assert(n)"] = "If the argument is 0, shut down the E2 with an error message" +E2Helper.Descriptions["assert(ns)"] = "If the first argument is 0, shut down the E2 with the given error message string" +E2Helper.Descriptions["reset()"] = "Reset the expression itself as if it was just spawned, stops execution" +E2Helper.Descriptions["exit()"] = "Stops the execution of any code after it" +E2Helper.Descriptions["getCode()"] = "Returns the code of the E2 as a string" +E2Helper.Descriptions["getCodeIncludes()"] = "Returns a table where indices (keys) are names of included files and entries are their codes" +E2Helper.Descriptions["remoteSetCode(e:s)"] = "Sets the E2's code with main file" +E2Helper.Descriptions["remoteSetCode(e:st)"] = "Sets the E2's code with main file & includes" +E2Helper.Descriptions["remoteUpload(e:s)"] = "Uploads the code from your computer to the server" +E2Helper.Descriptions["getCodeIncludes()"] = "Returns a table where indices (keys) are names of included files and entries are their codes" +E2Helper.Descriptions["hash()"] = "Returns a numerical hash using the code of the E2 itself (Including comments)" +E2Helper.Descriptions["hash(s)"] = "Returns a numerical hash using the string specified" +E2Helper.Descriptions["hashNoComments()"] = "Returns a numerical hash using the code of the E2 itself (Excluding comments)" +E2Helper.Descriptions["concmd(s)"] = "Takes a string and executes it in console. Returns 1 if it succeeded and 0 if it failed.The client must enable this in the console with \"wire_expression2_concmd 1\". \"wire_expression2_concmd_whitelist\" allows you to choose which commands can be used.[http://www.wiremod.com/forum/151800-post12.html]" +E2Helper.Descriptions["ioInputEntity(s)"] = "Returns the entity the input S is wired to" +E2Helper.Descriptions["ioOutputEntities(s)"] = "Returns an array of all entities wired to the output S" +E2Helper.Descriptions["runOnLast(n)"] = "If set to 1, the chip will run once when it is removed, setting the last() flag when it does" +E2Helper.Descriptions["selfDestruct()"] = "Removes the expression" +E2Helper.Descriptions["selfDestructAll()"] = "Removes the expression and all constrained props" + +-- Debug +E2Helper.Descriptions["playerCanPrint()"] = "Returns whether or not the next print-message will be printed or omitted by antispam" +E2Helper.Descriptions["printDriver(e:s)"] = "Posts a string to the chat of Es driver. Returns 1 if the text was printed, 0 if not" +E2Helper.Descriptions["hintDriver(e:sn)"] = "Displays a hint popup to the driver of vehicle E, with message S for N seconds (N being clamped between 0.7 and 7). Same return value as printDriver" +E2Helper.Descriptions["printDriver(e:ns)"] = "Same as EE:printDriver(S), but can make the text show up in different places. N can be one of the following: _HUD_PRINTCENTER, _HUD_PRINTCONSOLE, _HUD_PRINTNOTIFY, _HUD_PRINTTALK" +E2Helper.Descriptions["hint(sn)"] = "Displays a hint popup with message S for N seconds (N being clamped between 0.7 and 7)" +E2Helper.Descriptions["print(ns)"] = "Same as print(S), but can make the text show up in different places. N can be one of the following: _HUD_PRINTCENTER, _HUD_PRINTCONSOLE, _HUD_PRINTNOTIFY, _HUD_PRINTTALK" +E2Helper.Descriptions["print(...)"] = "Prints all arguments to the chat area, seperated by a tab. Automatically does toString for you (Can print arrays but not tables). Works just like lua's print" +E2Helper.Descriptions["printColor(...)"] = "Works like chat.AddText(...). Parameters can be any amount and combination of numbers, strings, player entities, color vectors (both 3D and 4D)" +E2Helper.Descriptions["printColorC(...)"] = "Works like MsgC. Parameters can be any amount and combination of numbers, strings, player entities, color vectors (both 3D and 4D)" +E2Helper.Descriptions["printColor(r)"] = "Like printColor(...), except taking an array containing all the parameters" +E2Helper.Descriptions["printColorC(r)"] = "Like printColorC(...), except taking an array containing all the parameters" +E2Helper.Descriptions["printColorDriver(e:...)"] = "Like printColor but prints to the driver of a specified vehicle" +E2Helper.Descriptions["printColorDriver(e:r)"] = "Like printColorDriver but takes an array containing all the parameters" +E2Helper.Descriptions["printTable(t)"] = "Prints a table like the lua function PrintTable does, except to the chat area" +E2Helper.Descriptions["printTable(r)"] = "Prints an array like the lua function PrintTable does, except to the chat area" + +-- Time +E2Helper.Descriptions["tickClk()"] = "Returns 1 if the current execution was caused by \"runOnTick\"" +E2Helper.Descriptions["tickInterval()"] = "Returns the time (in seconds) between each server tick" +E2Helper.Descriptions["curtime()"] = "Returns the current game time since server-start in seconds" +E2Helper.Descriptions["realtime()"] = "Returns the current real time since server-start in seconds" +E2Helper.Descriptions["systime()"] = "Returns a highly accurate time (also in seconds) since the server was started. Ideal for benchmarking" +E2Helper.Descriptions["clk(s)"] = "Returns 1 if the current execution was caused by the inserted name" +E2Helper.Descriptions["clk()"] = "Returns 1 if the current execution was caused by the interval" +E2Helper.Descriptions["clkName()"] = "Returns the name of the timer that caused current execution" +E2Helper.Descriptions["getTimers()"] = "Returns an array of all timers used in the E2" +E2Helper.Descriptions["interval(n)"] = "Sets a one-time timer with name \"interval\" and delay in milliseconds (minimum delay for timers is 10ms)" +E2Helper.Descriptions["runOnTick(n)"] = "If set to 1, the expression will execute once every game tick" +E2Helper.Descriptions["timer(sn)"] = "Sets a one-time timer with entered name and delay in milliseconds" +E2Helper.Descriptions["stoptimer(s)"] = "Stops a timer, can stop interval with stoptimer(\"interval\")" +E2Helper.Descriptions["stopAllTimers()"] = "Stops all timers" + +-- Unit conversion +E2Helper.Descriptions["toUnit(sn)"] = "Converts default garrysmod units to specified units" +E2Helper.Descriptions["fromUnit(sn)"] = "Converts specified units to default garrysmod units" +E2Helper.Descriptions["convertUnit(ssn)"] = "Converts between two units" + +-- Steam ID conversion +E2Helper.Descriptions["steamIDFrom64(s)"] = "Converts Steam Community ID to Steam ID" +E2Helper.Descriptions["steamIDTo64(s)"] = "Converts Steam ID to Steam Community ID" + +-- Server information +E2Helper.Descriptions["map()"] = "Returns the current map name" +E2Helper.Descriptions["hostname()"] = "Returns the Name of the server" +E2Helper.Descriptions["hostip()"] = "Returns the IP of the server" +E2Helper.Descriptions["isLan()"] = "Returns 1 if lan mode is enabled" +E2Helper.Descriptions["gamemode()"] = "Returns the name of the current gamemode" +E2Helper.Descriptions["gravity()"] = "Returns gravity" +E2Helper.Descriptions["ping(e:)"] = "Returns the latency for player E" +E2Helper.Descriptions["isSinglePlayer()"] = "Returns 1 if singleplayer, 0 if multiplayer" +E2Helper.Descriptions["isDedicated()"] = "Returns 1 if server is dedicated, 0 if listen" +E2Helper.Descriptions["players()"] = "Returns an array containing all players on the server" +E2Helper.Descriptions["playersAdmins()"] = "Returns an array containing all admins on the server" +E2Helper.Descriptions["playersSuperAdmins()"] = "Returns an array containing all super admins on the server" +E2Helper.Descriptions["numPlayers()"] = "Returns the number of players currently in the server" +E2Helper.Descriptions["maxPlayers()"] = "Returns the max number of players allowed in the server" +E2Helper.Descriptions["maxOfType(s)"] = "Returns the maximum allowed of a certain type of entity, i.e. maxOfType(\"wire_thrusters\"). Returns 0 if you enter an invalid parameter" +E2Helper.Descriptions["playerDamage()"] = "Returns 1 if player vs player damage is enabled on the server" +E2Helper.Descriptions["convar(s)"] = "Give a console command such as \"name\" and it returns the set value" +E2Helper.Descriptions["convarnum(s)"] = "Give a console command such as \"sbox_godmode\" and it returns the set value" +E2Helper.Descriptions["time()"] = "Returns the time in seconds" +E2Helper.Descriptions["time(s)"] = "Returns numerical time/date info from the server. Possible arguments: \"year\", \"month\", \"day\", \"hour\", \"min\", \"sec\", \"wday\" (weekday, Sunday is 1), \"yday\" (day of the year), and \"isdst\" (daylight saving flag 0/1)" +E2Helper.Descriptions["time(t)"] = "Attempts to construct the time from the data in the given table (same as lua's os.time). The table structure must be the same as in the date() functions. If any values are missing or of the wrong type, that value is ignored (it will be nil)" +E2Helper.Descriptions["date()"] = "Returns the server's current time and date" +E2Helper.Descriptions["dateUTC()"] = "Returns the server's current time and date in UTC" +E2Helper.Descriptions["date(n)"] = "Returns the specified unix time" +E2Helper.Descriptions["dateUTC(n)"] = "Returns the specified unix time in UTC" +E2Helper.Descriptions["maxFrictionMass()"] = "Returns how much friction influences props throughout the server" +E2Helper.Descriptions["minFrictionMass()"] = "Returns how much friction influences props throughout the server" + +-- Constraints +E2Helper.Descriptions["getConstraints(e:)"] = "Returns an array with all entities directly or indirectly constrained to E, except E itself. Deprecated, use E:getConnectedEntities(...) instead." +E2Helper.Descriptions["getConnectedEntities(e:...)"] = "Returns an array with all entities directly or indirectly constrained or parented to E, including E itself." +E2Helper.Descriptions["getConnectedEntities(e:r)"] = "Returns an array with all entities directly or indirectly constrained or parented to E, including E itself." +E2Helper.Descriptions["hasConstraints(e:)"] = "Returns the number of the constraints E has" +E2Helper.Descriptions["hasConstraints(e:s)"] = "Returns the number of the constraints E has with the given constraint type" +E2Helper.Descriptions["isConstrained(e:)"] = "Returns 1 if E has constraints, 0 if not" +E2Helper.Descriptions["isWeldedTo(e:)"] = "Returns the first entity E was welded to" +E2Helper.Descriptions["isWeldedTo(e:n)"] = "Returns the Nth entity E was welded to" +E2Helper.Descriptions["isConstrainedTo(e:)"] = "Returns the first entity E was constrained to" +E2Helper.Descriptions["isConstrainedTo(e:n)"] = "Returns the Nth entity E was constrained to" +E2Helper.Descriptions["isConstrainedTo(e:s)"] = "Returns the first entity E was constrained to with the given constraint type (see the types list below)" +E2Helper.Descriptions["isConstrainedTo(e:sn)"] = "Returns the Nth entity E was constrained to with the given constraint type (see the types list below)" +E2Helper.Descriptions["parent(e:)"] = "Returns the entity E is parented to" +E2Helper.Descriptions["parentBone(e:)"] = "Returns the bone E is parented to" +E2Helper.Descriptions["children(e:)"] = "Returns an array containing all the children of the entity - that is, every entity whose parent is this entity" + +-- Chat +E2Helper.Descriptions["chatClk()"] = "Returns 1 if the chip is being executed because of a chat event. Returns 0 otherwise" +E2Helper.Descriptions["chatClk(e)"] = "Returns 1 if the chip is being executed because of a chat event by player E. Returns 0 otherwise" +E2Helper.Descriptions["lastSpoke()"] = "Returns the last player to speak" +E2Helper.Descriptions["lastSaid()"] = "Returns the last message in the chat log" +E2Helper.Descriptions["lastSaidWhen()"] = "Returns the time the last message was sent" +E2Helper.Descriptions["lastSaidTeam()"] = "Returns 1 if the last message was sent in the team chat, 0 otherwise" +E2Helper.Descriptions["lastSaid(e:)"] = "Returns what the player E last said" +E2Helper.Descriptions["lastSaidWhen(e:)"] = "Returns when the given player last said something" +E2Helper.Descriptions["lastSaidTeam(e:)"] = "Returns 1 if the last message was sent in the team chat, 0 otherwise" +E2Helper.Descriptions["hideChat(n)"] = "Hides the chat messages written by E2 owner" +E2Helper.Descriptions["runOnChat(n)"] = "If set to 0, the chip will no longer run on chat events, otherwise it makes this chip execute when someone chats. Only needs to be called once, not in every execution" + +-- Color +E2Helper.Descriptions["setBodygroup(e:nn)"] = "Group ID, Group SubID\nSets the bodygroups of the given entity" +E2Helper.Descriptions["getBodygroups(e:n)"] = "Group ID\nReturns the number of bodygroups in the Group ID of the given entity" +E2Helper.Descriptions["setColor(nnn)"] = "Sets the color of the E2 chip" +E2Helper.Descriptions["setColor(e:vn)"] = "Sets the color (as vector) and alpha (as number) of the entity" +E2Helper.Descriptions["setColor(e:xv4)"] = "Sets the color and alpha (as 4D vector) of the entity" +E2Helper.Descriptions["setColor(e:nnn)"] = "Sets the color of the entity" +E2Helper.Descriptions["setColor(e:v)"] = "Sets the color of the entity" +E2Helper.Descriptions["setColor(e:nnnn)"] = "Sets the color and alpha of the entity" +E2Helper.Descriptions["getColor(e:)"] = "Returns the color of an entity as a vector (R,G,B)" +E2Helper.Descriptions["getColor4(e:)"] = "Returns the color of an entity as a 4D vector (R,G,B,A)" +E2Helper.Descriptions["setAlpha(e:n)"] = "Sets the alpha of an entity" +E2Helper.Descriptions["getAlpha(e:)"] = "Returns the alpha of an entity" +E2Helper.Descriptions["setMaterial(e:s)"] = "Sets the material of an entity" +E2Helper.Descriptions["getMaterial(e:)"] = "Returns the material of an entity" +E2Helper.Descriptions["setSkin(e:n)"] = "Sets the skin of an entity" +E2Helper.Descriptions["getSkin(e:)"] = "Gets Es current skin number" +E2Helper.Descriptions["getSkinCount(e:)"] = "Gets Es number of skins" +E2Helper.Descriptions["setRenderMode(e:n)"] = "Sets the render mode of the entity (0 = Normal, 1 = TransColor, 2 = TransTexture, 3 = Glow, 4 = TransAlpha, 5 = TransAdd, 6 = Enviromental, 7 = TransAddFrameBlend, 8 = TransAlphaAdd, 9 = WorldGlow, 10 = None)" +E2Helper.Descriptions["getPlayerColor(e:)"] = "Returns the player's model color as a vector (R,G,B)" +E2Helper.Descriptions["getWeaponColor(e:)"] = "Returns the player's weapon color as a vector (R,G,B)" +E2Helper.Descriptions["hsl2rgb(v)"] = "Converts V from the HSL color space to the RGB color space" +E2Helper.Descriptions["hsl2rgb(nnn)"] = "Converts N,N,N from the HSL color space to the RGB color space" +E2Helper.Descriptions["hsv2rgb(v)"] = "Converts V from the HSV color space to the RGB color space" +E2Helper.Descriptions["hsv2rgb(nnn)"] = "Converts N,N,N from the HSV color space to the RGB color space" +E2Helper.Descriptions["rgb2hsv(v)"] = "Converts V from the RGB to the HSV color space" +E2Helper.Descriptions["rgb2hsv(nnn)"] = "Converts N,N,N from the RGB to the HSV color space" +E2Helper.Descriptions["rgb2digi(vn)"] = "Converts the RGB vector V to a number in digital screen format. N Specifies a mode, either 0, 2 or 3, corresponding to Digital Screen color modes" +E2Helper.Descriptions["rgb2digi(nnnn)"] = "Converts the RGB color (N,N2,N3) to a number in digital screen format. N4 Specifies a mode, either 0, 2 or 3, corresponding to Digital Screen color modes" +E2Helper.Descriptions["rgb2hsl(v)"] = "Converts V from RGB color space to the HSL color space" +E2Helper.Descriptions["rgb2hsl(nnn)"] = "Converts N,N,N from RGB color space to the HSL color space" + +-- Entity Discovery +E2Helper.Descriptions["findUpdateRate()"] = "Returns the minimum delay between entity find events on a chip" +E2Helper.Descriptions["findPlayerUpdateRate()"] = "Returns the minimum delay between entity find events per player" +E2Helper.Descriptions["findCanQuery()"] = "Returns 1 if find functions can be used, 0 otherwise" +E2Helper.Descriptions["findInSphere(vn)"] = "Finds entities in a sphere around V with a radius of N, returns the number found after filtering" +E2Helper.Descriptions["findInCone(vvnn)"] = "Like findInSphere but with a http://mathworld.wolfram.com/SphericalCone.html Spherical cone, arguments are for position, direction, length, and degrees (works now)" +E2Helper.Descriptions["findInBox(vv)"] = "Like findInSphere but with a globally aligned box, the arguments are the diagonal corners of the box" +E2Helper.Descriptions["findByName(s)"] = "Find all entities with the given name" +E2Helper.Descriptions["findByModel(s)"] = "Find all entities with the given model" +E2Helper.Descriptions["findByClass(s)"] = "Find all entities with the given class" +E2Helper.Descriptions["findPlayerByName(s)"] = "Returns the player with the given name, this is an exception to the rule" +E2Helper.Descriptions["findPlayerBySteamID(s)"] = "Returns the player with the given SteamID32" +E2Helper.Descriptions["findPlayerBySteamID64(s)"] = "Returns the player with the given SteamID64" +E2Helper.Descriptions["findResult(n)"] = "Returns the indexed entity from the previous find event (valid parameters are 1 to the number of entities found)" +E2Helper.Descriptions["findClosest(v)"] = "Returns the closest entity to the given point from the previous find event" +E2Helper.Descriptions["findToArray()"] = "Formats the query as an array, R[Index,entity] to get an entity" +E2Helper.Descriptions["find()"] = "Equivalent to findResult(1)" +E2Helper.Descriptions["findCount()"] = "Returns the remaining available find calls" +E2Helper.Descriptions["findMax()"] = "Returns the maximum number of finds per E2" +E2Helper.Descriptions["findSortByDistance(v)"] = "Sorts the entities from the last find event, index 1 is the closest to point V, returns the number of entities in the list" +E2Helper.Descriptions["findClipToClass(s)"] = "Filters the list of entities by removing all entities that are NOT of this class" +E2Helper.Descriptions["findClipFromClass(s)"] = "Filters the list of entities by removing all entities that are of this class" +E2Helper.Descriptions["findClipToEntities(r)"] = "Filters the list of entities by removing all entities that are NOT in this array" +E2Helper.Descriptions["findClipFromEntities(r)"] = "Filters the list of entities by removing all entities that are in this array" +E2Helper.Descriptions["findClipToEntity(e)"] = "Filters the list of entities by removing all except this entity" +E2Helper.Descriptions["findClipFromEntity(e)"] = "Filters the list of entities by removing this entity" +E2Helper.Descriptions["findClipToModel(s)"] = "Filters the list of entities by removing all entities that do NOT have this model" +E2Helper.Descriptions["findClipFromModel(s)"] = "Filters the list of entities by removing all entities that do have this model" +E2Helper.Descriptions["findClipToName(s)"] = "Filters the list of entities by removing all entities that do NOT have this name" +E2Helper.Descriptions["findClipFromName(s)"] = "Filters the list of entities by removing all entities that do have this name" +E2Helper.Descriptions["findClipToSphere(vn)"] = "Filters the list of entities by removing all entities NOT within the specified sphere (center, radius)" +E2Helper.Descriptions["findClipFromSphere(vn)"] = "Filters the list of entities by removing all entities within the specified sphere (center, radius)" +E2Helper.Descriptions["findClipToRegion(vv)"] = "Filters the list of entities by removing all entities NOT on the positive side of the defined plane. (Plane origin, vector perpendicular to the plane) You can define any convex hull using this" +E2Helper.Descriptions["findClipFromBox(vv)"] = "Filters the list of entities by removing all entities within the specified box" +E2Helper.Descriptions["findClipToBox(vv)"] = "Filters the list of entities by removing all entities NOT within the specified box" +E2Helper.Descriptions["findAllowClass(s)"] = "Remove entities with this class (or partial class name) from the blacklist" +E2Helper.Descriptions["findAllowEntities(r)"] = "Remove all entities in array from the blacklist" +E2Helper.Descriptions["findAllowEntity(e)"] = "Remove entity from the blacklist" +E2Helper.Descriptions["findAllowModel(s)"] = "Remove entities with this model (or partial model name) from the blacklist" +E2Helper.Descriptions["findAllowPlayer(s)"] = "Remove player with specified name from the entity blacklist" +E2Helper.Descriptions["findAllowPlayer(e)"] = "Remove specified player from the entity blacklist" +E2Helper.Descriptions["findAllowPlayerProps(s)"] = "Remove entities owned by player with specified name from the blacklist" +E2Helper.Descriptions["findAllowPlayerProps(e)"] = "Remove entities owned by specified player from the blacklist" +E2Helper.Descriptions["findClearBlackClassList()"] = "Clear all entries from the class blacklist" +E2Helper.Descriptions["findClearBlackEntityList()"] = "Clear all entries from the entity blacklist" +E2Helper.Descriptions["findClearBlackList()"] = "Clear all entries from the entire blacklist" +E2Helper.Descriptions["findClearBlackModelList()"] = "Clear all entries from the model blacklist" +E2Helper.Descriptions["findClearBlackPlayerPropList()"] = "Clear all entries from the prop owner blacklist" +E2Helper.Descriptions["findClearWhiteClassList()"] = "Clear all entries from the class whitelist" +E2Helper.Descriptions["findClearWhiteEntityList()"] = "Clear all entries from the player whitelist" +E2Helper.Descriptions["findClearWhiteList()"] = "Clear all entries from the entire whitelist" +E2Helper.Descriptions["findClearWhiteModelList()"] = "Clear all entries from the model whitelist" +E2Helper.Descriptions["findClearWhitePlayerPropList()"] = "Clear all entries from the prop owner whitelist" +E2Helper.Descriptions["findDisallowClass(s)"] = "Remove entities with this class (or partial class name) from the whitelist" +E2Helper.Descriptions["findDisallowEntities(r)"] = "Remove all entities in array from the whitelist" +E2Helper.Descriptions["findDisallowEntity(e)"] = "Remove entity from the whitelist" +E2Helper.Descriptions["findDisallowModel(s)"] = "Remove entities with this model (or partial model name) from the whitelist" +E2Helper.Descriptions["findDisallowPlayer(s)"] = "Remove player with specified name from the entity whitelist" +E2Helper.Descriptions["findDisallowPlayer(e)"] = "Remove specified player from the entity whitelist" +E2Helper.Descriptions["findDisallowPlayerProps(s)"] = "Remove entities owned by player with specified name from the whitelist" +E2Helper.Descriptions["findDisallowPlayerProps(e)"] = "Remove entities owned by specified player from the whitelist" +E2Helper.Descriptions["findExcludeClass(s)"] = "Exclude entities with this class (or partial class name) from future finds" +E2Helper.Descriptions["findExcludeEntities(r)"] = "Exclude all entities in array from future finds" +E2Helper.Descriptions["findExcludeEntity(e)"] = "Exclude entity from future finds" +E2Helper.Descriptions["findExcludeModel(s)"] = "Exclude entities with this model (or partial model name) from future finds" +E2Helper.Descriptions["findExcludePlayer(s)"] = "Exclude player with specified name from future finds (put it on the entity blacklist)" +E2Helper.Descriptions["findExcludePlayer(e)"] = "Exclude specified player from future finds (put it on the entity blacklist)" +E2Helper.Descriptions["findExcludePlayerProps(s)"] = "Exclude entities owned by player with specified name from future finds" +E2Helper.Descriptions["findExcludePlayerProps(e)"] = "Exclude entities owned by specified player from future finds" +E2Helper.Descriptions["findIncludeClass(s)"] = "Include entities with this class (or partial class name) in future finds, and remove others not in the whitelist" +E2Helper.Descriptions["findIncludeEntities(r)"] = "Include all entities in array in future finds, and remove others not in the whitelist" +E2Helper.Descriptions["findIncludeEntity(e)"] = "Include entity in future finds, and remove others not in the whitelist" +E2Helper.Descriptions["findIncludeModel(s)"] = "Include entities with this model (or partial model name) in future finds, and remove others not in the whitelist" +E2Helper.Descriptions["findIncludePlayer(s)"] = "Include player with specified name in future finds, and remove other entities not in the entity whitelist" +E2Helper.Descriptions["findIncludePlayer(e)"] = "Include specified player in future finds, and remove other entities not in the entity whitelist" +E2Helper.Descriptions["findIncludePlayerProps(s)"] = "Include entities owned by player with specified name in future finds, and remove others not in the whitelist" +E2Helper.Descriptions["findIncludePlayerProps(e)"] = "Include entities owned by specified player in future finds, and remove others not in the whitelist" + +-- Ranger +E2Helper.Descriptions["rangerFlags()"] = "Returns the ranger flags as a string" +E2Helper.Descriptions["rangerFlags(s)"] = "Sets the ranger flags. S can be any combination of I=ignore world, W=hit water, E=hit entities and Z=default to zero" +E2Helper.Descriptions["rangerFilter(e)"] = "Feed entities you don't want the trace to hit" +E2Helper.Descriptions["rangerFilter(r)"] = "Feed an array of entities you don't want the trace to hit" +E2Helper.Descriptions["ranger(n)"] = "You input max range, it returns ranger data" +E2Helper.Descriptions["ranger(nnn)"] = "Same as above with added inputs for X and Y skew" +E2Helper.Descriptions["rangerAngle(nnn)"] = "You input the distance, x-angle and y-angle (both in degrees) it returns ranger data" +E2Helper.Descriptions["rangerOffset(vv)"] = "You input two vector points, it returns ranger data" +E2Helper.Descriptions["rangerOffset(nvv)"] = "You input the range, a position vector, and a direction vector and it returns ranger data" +E2Helper.Descriptions["ranger(en)"] = "Same as ranger(distance): You input max range, it returns ranger data, only used on another entity" +E2Helper.Descriptions["rangerHull(nv)"] = "Inputs: Distance, Hull BoxSize" +E2Helper.Descriptions["rangerHull(nvv)"] = "Input: Distance, Hull MinSize, Hull MaxSize" +E2Helper.Descriptions["rangerHull(nnnv)"] = "Inputs: Distance, X Skew, Y Skew, Hull BoxSize" +E2Helper.Descriptions["rangerHull(nnnvv)"] = "Inputs: Distance, X Skew, Y Skew, Hull MinSize, Hull MaxSize" +E2Helper.Descriptions["rangerAngleHull(nnnv)"] = "Inputs: Distance, X Angle, Y Angle, Hull BoxSize" +E2Helper.Descriptions["rangerAngleHull(nnnvv)"] = "Inputs: Distance, X Angle, Y Angle, Hull MinSize, Hull MaxSize" +E2Helper.Descriptions["rangerOffsetHull(vvv)"] = "Inputs: StartPos, EndPos, Hull BoxSize" +E2Helper.Descriptions["rangerOffsetHull(vvvv)"] = "Inputs: StartPos, EndPos, Hull MinSize, Hull MaxSize" +E2Helper.Descriptions["rangerOffsetHull(nvvv)"] = "Inputs: Distance, StartPos, Direction, Hull BoxSize" +E2Helper.Descriptions["rangerOffsetHull(nvvvv)"] = "Inputs: Distance, StartPos, Direction, Hull MinSize, Hull MaxSize" +E2Helper.Descriptions["rangerOffsetHull(evv)"] = "Use entity collision box for the ranger. Inputs: Entity, StartPos, EndPos" +E2Helper.Descriptions["distance(xrd:)"] = "Outputs the distance from the rangerdata input, else depends on rangerDefault" +E2Helper.Descriptions["position(xrd:)"] = "Outputs the position of the input ranger data trace IF it hit anything, else returns (0,0,0)" +E2Helper.Descriptions["fraction(xrd:)"] = "Returns a number between 0-1 which represents the percentage of the distance between the start & hit position of the trace. StartPos + (EndPos-StartPos):normalized() * RD:fraction() * (EndPos-StartPos):Length() is equal to RD:pos()" +E2Helper.Descriptions["entity(xrd:)"] = "Returns the entity of the input ranger data trace IF it hit an entity, else returns nil" +E2Helper.Descriptions["bone(xrd:)"] = "Returns the bone of the input ranger data trace IF it hit a bone, else returns nil" +E2Helper.Descriptions["hit(xrd:)"] = "Returns 1 if the input ranger data hit anything and 0 if it didn't" +E2Helper.Descriptions["hitNormal(xrd:)"] = "Outputs a normalized vector perpendicular to the surface the ranger is pointed at" +E2Helper.Descriptions["hitGroup(xrd:)"] = "Returns the hit group (ie chest, face, left arm, right leg, etc)" +E2Helper.Descriptions["hitSky(xrd:)"] = "Returns 1 if the trace hit the sky, 0 otherwise" +E2Helper.Descriptions["hitTexture(xrd:)"] = "Returns the texture of the surface the ranger is pointed at" +E2Helper.Descriptions["hitWorld(xrd:)"] = "Returns 1 if the trace hit the world, 0 otherwise" +E2Helper.Descriptions["distanceLeftSolid(xrd:)"] = "Returns the distance between the position at which the trace left the world and the trace's Start Position" +E2Helper.Descriptions["positionLeftSolid(xrd:)"] = "Returns the position at which the trace left the world, if it was started inside the world. Else return the trace's Start Position" +E2Helper.Descriptions["fractionLeftSolid(xrd:)"] = "Same as RD:fraction() except it represents the distance between the start position and the LeftSolid position" +E2Helper.Descriptions["startSolid(xrd:)"] = "Returns 1 if the trace was started inside the world, else 0" +E2Helper.Descriptions["matType(xrd:)"] = "Returns the material type (ie wood, metal, dirt, flesh, etc)" +E2Helper.Descriptions["noranger()"] = "Returns an invalid ranger" +E2Helper.Descriptions["pos(xrd:)"] = "Returns the hit position. The difference between this function and RD:position() is that if you start the trace inside the world, RD:position() will return the position at which the trace EXITS the world. RD:pos(), however, will continue on and return the hit position outside the wall you started the trace in" +E2Helper.Descriptions["rangerDefaultZero(n)"] = "If given any value other than 0 it will default the distance data to zero when nothing is hit" +E2Helper.Descriptions["rangerHitEntities(n)"] = "Default is 1, if value is given as 0 it will ignore entities" +E2Helper.Descriptions["rangerHitWater(n)"] = "Default is 0, if any other value is given it will hit water" +E2Helper.Descriptions["rangerIgnoreWorld(n)"] = "Default is 0, if any other value is given it will ignore world" +E2Helper.Descriptions["rangerPersist(n)"] = "Passing 0 (the default) resets all ranger flags and filters every execution and after calling ranger/rangerOffset. Passing anything else will make the flags and filters persist until they're changed again" +E2Helper.Descriptions["rangerReset()"] = "Resets all ranger flags and filters" +E2Helper.Descriptions["toTable(xrd:)"] = "Converts the trace data into an E2-style table and returns it. Remember that this returns the raw data, so for matType and hitGroup, it is recommend that you use the functions instead of this table" + +-- NPCs +E2Helper.Descriptions["npcGetTarget(e:)"] = "Returns what the npc is currently targeting" +E2Helper.Descriptions["npcRelationship(e:esn)"] = "Will set the NPC's relationship to the specified entity to the S input, priority N. Priority is any number between 0 and 999. The relationship string can be either \"like\", \"neutral\", \"hate\" or \"fear\". Same goes for all other relationship functions" +E2Helper.Descriptions["npcRelationship(e:ssn)"] = "Same as E:npcRelationship(entity,string,number), but sets relationship to an entire class specified by the first string. Example: \"npc_manhack\", \"prop_physics\"" +E2Helper.Descriptions["npcRelationshipByOwner(e:esn)"] = "Sets the NPC's relationship to all currently existing NPCs owned by player E. Returns number of entities added to relationships" +E2Helper.Descriptions["npcDisp(e:e)"] = "Returns the NPC's relationship to entity E" +E2Helper.Descriptions["npcAttack(e:)"] = "Tells the NPC to use their melee attack" +E2Helper.Descriptions["npcFace(e:v)"] = "This will rotate the NPC to face position V. This is purely aesthetic and can't be used to aim their weapon" +E2Helper.Descriptions["npcGiveWeapon(e:)"] = "Gives the NPC an SMG" +E2Helper.Descriptions["npcGiveWeapon(e:s)"] = "Gives the NPC a weapon. Example: E:npcGiveWeapon(\"pistol\"). Other arguments include \"ar2\", \"crowbar\", \"357\", \"shotgun\", \"crossbow\", \"rpg\", \"frag\", etc. Other such as the bugbait or slam may be buggy" +E2Helper.Descriptions["npcGoRun(e:v)"] = "Tells the NPC to run to position V" +E2Helper.Descriptions["npcGoWalk(e:v)"] = "Tells the NPC to walk to position V" +E2Helper.Descriptions["npcStop(e:)"] = "Stops any anything the NPC is doing, including things it decided to do by itself" +E2Helper.Descriptions["npcSetTarget(e:e)"] = "Sets the npcs current target" +E2Helper.Descriptions["npcGetTarget(e:)"] = "Returns what the npc is currently targeting" +E2Helper.Descriptions["npcShoot(e:)"] = "Tells the NPC to shoot their gun" + +-- Signals +E2Helper.Descriptions["signalGetGroup()"] = "Gets the E-2's current signal group" +E2Helper.Descriptions["signalSetGroup(s)"] = "Sets the E-2's current signal group to S, this is applied during runOnSignal, signalSend, and signalSetOnRemove calls, so call it first" +E2Helper.Descriptions["signalSetOnRemove(sn)"] = "Sets the signal that the chip sends when it is removed from the world" +E2Helper.Descriptions["signalClk()"] = "Returns 1 if the chip was executed because of any signal, regardless of name, group or scope. Returns 0 otherwise" +E2Helper.Descriptions["signalClk(s)"] = "Returns 1 if the chip was executed because the signal S was sent, regardless of group or scope. Returns 0 otherwise" +E2Helper.Descriptions["signalClk(sn)"] = "Returns 1 if the chip was executed because the signal S was sent to the scope N, regardless of group. Returns 0 otherwise" +E2Helper.Descriptions["signalClk(ss)"] = "Returns 1 if the chip was executed because the signal S2 was sent in the group S, regardless of scope. Returns 0 otherwise" +E2Helper.Descriptions["signalClk(ssn)"] = "Returns 1 if the chip was executed because the signal S2 was sent in the group S to the scope N. Returns 0 otherwise" +E2Helper.Descriptions["signalName()"] = "Returns the name of the received signal" +E2Helper.Descriptions["signalGroup()"] = "Returns the group name of the received signal" +E2Helper.Descriptions["signalSend(sn)"] = "Sends signal S to scope N. Additional calls to this function with the same signal will overwrite the old call until the signal is issued" +E2Helper.Descriptions["signalSendDirect(se)"] = "Sends signal S to the given chip. Multiple calls for different chips do not overwrite each other" +E2Helper.Descriptions["signalSendToPlayer(se)"] = "Sends signal S to chips owned by the given player, multiple calls for different players do not overwrite each other" +E2Helper.Descriptions["signalSender()"] = "Returns the entity of the chip that sent the signal" +E2Helper.Descriptions["signalSenderId()"] = "Returns the entity ID of the chip that sent the signal. Useful if the entity doesn't exist anymore" +E2Helper.Descriptions["signalClearOnRemove()"] = "Clears the signal that the chip sends when it is removed from the world" + +-- Holograms +E2Helper.Descriptions["holoAlpha(nn)"] = "Sets the transparency (0-255) of the hologram" +E2Helper.Descriptions["holoAng(na)"] = "Sets the angle of the hologram" +E2Helper.Descriptions["holoBodygroup(nnn)"] = "Index, Group ID, Group SubID\nSets the bodygroups of the given hologram" +E2Helper.Descriptions["holoBodygroups(nn)"] = "Index, Group ID\nReturns the number of bodygroups in the Group ID of the given hologram" +E2Helper.Descriptions["holoCanCreate()"] = "Returns 1 when holoCreate() will successfully create a new hologram until the Max limit is reached\nReplaces holoRemainingSpawns()" +E2Helper.Descriptions["holoColor(nxv4)"] = "Sets the color and alpha of the hologram" +E2Helper.Descriptions["holoColor(nv)"] = "Sets the color of the hologram" +E2Helper.Descriptions["holoColor(nvn)"] = "Sets the color and alpha of the hologram" +E2Helper.Descriptions["holoPlayerColor(nv)"] = "Sets the sub-color of a hologram with player model such as clothes or physgun" +E2Helper.Descriptions["holoCreate(nvvavs)"] = "Index, Position, Scale, Angle, Color (RGB), Model\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(nvvaxv4s)"] = "Index, Position, Scale, Angle, Color (RGBA), Model\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(nvvav)"] = "Index, Position, Scale, Angle, Color (RGB)\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(nvvaxv4)"] = "Index, Position, Scale, Angle, Color (RGBA)\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(nvva)"] = "Index, Position, Scale, Angle\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(nvv)"] = "Index, Position, Scale\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(nv)"] = "Index, Position\nCreates a new hologram entity" +E2Helper.Descriptions["holoCreate(n)"] = "Index\nCreates a new hologram entity" +E2Helper.Descriptions["holoDelete(n)"] = "Removes a hologram" +E2Helper.Descriptions["holoDeleteAll()"] = "Removes all holograms made by this E2" +E2Helper.Descriptions["holoDisableShading(nn)"] = "If 1, supresses engine lighting when drawing this hologram" +E2Helper.Descriptions["holoEntity(n)"] = "Returns the entity corresponding to the hologram given by the specified index" +E2Helper.Descriptions["holoIndex(e)"] = "Returns the index of the given hologram entity" +E2Helper.Descriptions["holoMaterial(ns)"] = "Sets the overlay material of the hologram" +E2Helper.Descriptions["holoModel(ns)"] = "Sets the model.\nMust be from holoModelList unless wire_holograms_modelany is 1 (see holoModelAny())" +E2Helper.Descriptions["holoModel(nsn)"] = "Sets the model and skin.\nMust be from holoModelList unless wire_holograms_modelany is 1 (see holoModelAny())" +E2Helper.Descriptions["holoModelAny()"] = "Returns 1 if models outside of holoModelList can be used.\nReads convar 'wire_holograms_modelany'" +E2Helper.Descriptions["holoModelList()"] = "Returns the list of valid models\nSee holoModelAny()" +E2Helper.Descriptions["holoParent(ne)"] = "Parents the hologram to an entity" +E2Helper.Descriptions["holoParent(nn)"] = "Parents the hologram to another hologram" +E2Helper.Descriptions["holoParentAttachment(nes)"] = "Parents the hologram to an entity's bone by its attachment name" +E2Helper.Descriptions["holoUnparent(n)"] = "Un-parents the hologram" +E2Helper.Descriptions["holoPos(nv)"] = "Sets the position of the hologram" +E2Helper.Descriptions["holoRemainingSpawns()"] = "Returns how many holograms can be created this execution" +E2Helper.Descriptions["holoReset(nsvvs)"] = "Similar to holoCreate, but reusing the old entity" +E2Helper.Descriptions["holoScale(n)"] = "Returns the scale of the given hologram" +E2Helper.Descriptions["holoScale(nv)"] = "Sets the scale of the given hologram, as a multiplier" +E2Helper.Descriptions["holoBoneScale(nn)"] = "Returns the scale of the given hologram bone" +E2Helper.Descriptions["holoBoneScale(nnv)"] = "Sets the scale of the given hologram bone, as a multiplier" +E2Helper.Descriptions["holoBoneScale(ns)"] = "Returns the scale of the given hologram named bone" +E2Helper.Descriptions["holoBoneScale(nsv)"] = "Sets the scale of the given hologram named bone, as a multiplier" +E2Helper.Descriptions["holoScaleUnits(n)"] = "Returns the scale of the given hologram in source units" +E2Helper.Descriptions["holoScaleUnits(nv)"] = "Sets the scale of the given hologram, in source units" +E2Helper.Descriptions["holoShadow(nn)"] = "Enables the hologram's shadow" +E2Helper.Descriptions["holoVisible(nen)"] = "If 0, prevents a specific player from seeing the hologram" +E2Helper.Descriptions["holoVisible(nrn)"] = "If 0, prevents an array of players from seeing the hologram" +E2Helper.Descriptions["holoClip(nnvvn)"] = "Defines a plane used to clip a hologram specified by it's index, position, direction and number 1/0 whether the position should be global or local to the hologram" +E2Helper.Descriptions["holoClip(nvvn)"] = "Defines a plane used to clip a hologram specified by it's position, direction and number 1/0 whether the position should be global or local to the hologram" +E2Helper.Descriptions["holoClip(nnvve)"] = "Defines a plane used to clip a hologram specified by it's index, position, and direction local to the given entity" +E2Helper.Descriptions["holoClip(nvve)"] = "Defines a plane used to clip a hologram specified by it's position, and direction local to the given entity" +E2Helper.Descriptions["holoClipEnabled(nn)"] = "Enables / disables clipping for a hologram with specified index" +E2Helper.Descriptions["holoClipEnabled(nnn)"] = "Enables / disables clipping for a hologram with specified index. Clip index is for use with multiple clipping planes" +E2Helper.Descriptions["holoClipsAvailable()"] = "Returns the maximum number of clipping planes allowed per hologram" +E2Helper.Descriptions["holoRenderFX(nn)"] = "Changes the RenderFX for a hologram" +E2Helper.Descriptions["holoSkin(nn)"] = "Changes the skin of a hologram" + +-- File +E2Helper.Descriptions["fileLoaded()"] = "Returns whether or not the file has been loaded onto the server" +E2Helper.Descriptions["fileRead()"] = "Returns the contents of the last uploaded file, or an empty string if there is no currently uploaded file" +E2Helper.Descriptions["fileReadList()"] = "Returns the contents of the last uploaded list" +E2Helper.Descriptions["fileClk(s)"] = "Returns whether the execution was run because a file finished uploading and was that file of a specific file name" +E2Helper.Descriptions["fileClk()"] = "Returns whether the execution was run because a file finished uploading" +E2Helper.Descriptions["fileLoadedList()"] = "If the list has been loaded and it is called, it will return 1. Any time after that until a new list is loaded it will return 0" +E2Helper.Descriptions["fileLoadingList()"] = "Returns whether a list is currently uploading" +E2Helper.Descriptions["fileList(s)"] = "Returns an array of file names that have been loaded" +E2Helper.Descriptions["fileListTable()"] = "Returns a table of file names that have been loaded. (Tbl[\"filename\"] = \"filename\")" +E2Helper.Descriptions["fileListClk(s)"] = "Returns whether the execution was run because a list with specified name was uploaded to the server" +E2Helper.Descriptions["fileListClk()"] = "Returns whether the execution was run because a list was uploaded to the server" +E2Helper.Descriptions["fileAppend(ss)"] = "Adds string data to the end of the file" +E2Helper.Descriptions["fileWrite(ss)"] = "Writes string data to the file overwriting it" +E2Helper.Descriptions["fileCanList()"] = "Returns 1 if the file list can be uploaded to the server" +E2Helper.Descriptions["fileCanLoad()"] = "Returns 1 if the file can be loaded" +E2Helper.Descriptions["fileCanWrite()"] = "Returns 1 if the file can be written" +E2Helper.Descriptions["fileLoad(s)"] = "Loads specified file to the server" +E2Helper.Descriptions["fileLoading()"] = "Returns whether a file is currently uploading" +E2Helper.Descriptions["fileMaxSize()"] = "Returns the maximum file size that can be uploaded or downloaded. Default is 300 KiB" +E2Helper.Descriptions["fileName()"] = "Returns the name of the last uploaded file, or an empty string if there is no currently uploaded file" +E2Helper.Descriptions["fileStatus()"] = "Returns the status of the upload in progress. Returns one of _FILE_UNKNOWN, _FILE_OK, _FILE_TIMEOUT, _FILE_404 or _FILE_TRANSFER_ERROR" +E2Helper.Descriptions["runOnFile(n)"] = "Specifies whether the E2 will run when a file finishes uploading" +E2Helper.Descriptions["runOnList(n)"] = "Specifies whether the E2 will run when a list finishes uploading" + +-- Datasignals +E2Helper.Descriptions["dsSend"] = "Sends a datasignal to the specified group and scope" +E2Helper.Descriptions["dsSendDirect"] = "Sends a datasignal to the specified E2 (or use an array for several E2s)" +E2Helper.Descriptions["dsGetSender()"] = "Returns the entity of the E2 which sent the signal" +E2Helper.Descriptions["dsJoinGroup(s)"] = "Join the group to receive signals from it" +E2Helper.Descriptions["dsLeaveGroup(s)"] = "Leave the group to stop receiving signals from it" +E2Helper.Descriptions["dsClearGroups()"] = "Leave all groups" +E2Helper.Descriptions["dsClk()"] = "Returns 1 if the current execution was caused by a datasignal" +E2Helper.Descriptions["dsClk(s)"] = "Returns 1 if the current execution was caused by a datasignal with the specified signal name" +E2Helper.Descriptions["dsClkName()"] = "Returns the name of the signal" +E2Helper.Descriptions["dsGetType()"] = "Returns the type of the received data" +E2Helper.Descriptions["dsGetGroups()"] = "Returns an array of the groups the E2 is in" +E2Helper.Descriptions["dsGetGroup()"] = "Get the name of the group the signal was sent to" +E2Helper.Descriptions["dsProbe"] = "Returns an array of E2s the signal would have been sent to if it had been sent with the specified arguments" +E2Helper.Descriptions["dsSetScope(n)"] = "Sets the scope of the E2. Setting the scope determines which signals the E2 can receive. Check the wiki for more info about scopes" +E2Helper.Descriptions["dsGetScope()"] = "Returns the scope the E2 is currently in" +E2Helper.Descriptions["dsGetHash()"] = "Returns the hash of sending E2" +E2Helper.Descriptions["runOnSignal(snn)"] = "If N2 == 0 the chip will no longer run on this signal, otherwise it makes this chip execute when signal S is sent by someone in scope N" + +-- gvars +E2Helper.Descriptions["gTable(sn)"] = "Returns the gTable. The string determines group, and the number determines wether or not the table should be shared" +E2Helper.Descriptions["gTable(s)"] = "Returns the gTable. The string determines group" +E2Helper.Descriptions["gTableSafe(n)"] = "Returns a safe gTable which group is a numerical hash created from the code of the E2 itself" +E2Helper.Descriptions["gRemoveAll()"] = "Removes all non-shared variables and group tables you have created" +E2Helper.Descriptions["clear(xgt:)"] = "Clears the gTable" +E2Helper.Descriptions["count(xgt:)"] = "Returns the number of entries in the gTable. Does not add the entries in subtables" +E2Helper.Descriptions["gGetGroup()"] = "Gets the E2's current group" +E2Helper.Descriptions["gSetGroup(s)"] = "Sets the E2's current group. Does persist" +E2Helper.Descriptions["gGetShare()"] = "Returns 1/0" +E2Helper.Descriptions["gResetGroup()"] = "Resets the group back to \"default\"" +E2Helper.Descriptions["gShare(n)"] = "Sets wether or not you want to share the variables. (1/0) Remember that there are two tables for each group: one which is shared and one which is not; values do not transition between the two" +E2Helper.Descriptions["toTable(xgt:)"] = "Converts the GTable into a table" + +-- tables +E2Helper.Descriptions["table"] = "Returns a table with the values specified in the array-part" +E2Helper.Descriptions["clear(t:)"] = "Clears the table" +E2Helper.Descriptions["count(t:)"] = "Returns the number of entries in the table. Does not add the entries in subtables" +E2Helper.Descriptions["ncount(t:)"] = "Returns the number of sequential numerical indexes" +E2Helper.Descriptions["depth(t:)"] = "Returns the depth at which the table is in, relative to its parents" +E2Helper.Descriptions["flip(t:)"] = "Returns a flipped copy of the table. Only affects string values in the array part and number values in the table part" +E2Helper.Descriptions["typeids(t:)"] = "Returns a new table with the typeids of the table" +E2Helper.Descriptions["clipToTypeid(t:s)"] = "Removes all entries not of the specified type" +E2Helper.Descriptions["clipFromTypeid(t:s)"] = "Removes all entries of the specified type" +E2Helper.Descriptions["min(t:)"] = "Returns the smallest numerical entry in the array-part" +E2Helper.Descriptions["max(t:)"] = "Returns the largest numerical entry in the array-part" +E2Helper.Descriptions["minIndex(t:)"] = "Returns the index of the smallest numerical entry in the array-part" +E2Helper.Descriptions["maxIndex(t:)"] = "Returns the index of the largest numerical entry in the array-part" +E2Helper.Descriptions["typeidsArray(t:)"] = "Returns an array with the typeids of the array-part of the table" +E2Helper.Descriptions["toArray(t:)"] = "Converts the table into an array. (Note that there is no R:totable() function because E2 arrays do not save typeids)" +E2Helper.Descriptions["findToTable()"] = "Inserts the finds from an entity discovery event into an table's array-part and returns it. (Basically the same as findToArray())" +E2Helper.Descriptions["toTable(t:)"] = "Converts the table into a table" +E2Helper.Descriptions["typeidsTable(t:)"] = "Returns a table with the typeids of the table-part of the table" +E2Helper.Descriptions["clone(t:)"] = "Returns a copy of the table" +E2Helper.Descriptions["concat(t:)"] = "Concatenates the array-part of the table" +E2Helper.Descriptions["concat(t:s)"] = "Concatenates the array-part of the table, with a string delimiter" +E2Helper.Descriptions["concat(t:n)"] = "Concatenates the array-part of the table, starting at index N" +E2Helper.Descriptions["concat(t:sn)"] = "Concatenates the array-part of the table, starting at index N, with string S in between each" +E2Helper.Descriptions["concat(t:nn)"] = "Concatenates the array-part of the table, starting at index N1 and ending at N2" +E2Helper.Descriptions["concat(t:snn)"] = "Concatenates the array-part of the table, starting at index N1 and ending at N2, with string S in between each" +E2Helper.Descriptions["toString(t)"] = "Formats the table as a human-readable string" +E2Helper.Descriptions["toString(t:)"] = "Formats the table as a human-readable string" +E2Helper.Descriptions["id(t:)"] = "Returns the unique ID of the table" +E2Helper.Descriptions["add(t:t)"] = "Adds the contents of the second table to the end of the first table. Returns new table" +E2Helper.Descriptions["difference(t:t)"] = "Removes all variables with keys that exist in T2" +E2Helper.Descriptions["intersect(t:t)"] = "Removes all variables with keys which don't exist in T2" +E2Helper.Descriptions["merge(t:t)"] = "Merges T2 with T. Any variables with the same indexes are overwritten by T2's variables" +E2Helper.Descriptions["exists(t:s)"] = "Returns 1 if the table contains any value at specified index" +E2Helper.Descriptions["exists(t:n)"] = "Returns 1 if the table contains any value at specified index" +E2Helper.Descriptions["invert(t)"] = "Inverts the table, creating a lookup table" +E2Helper.Descriptions["keys(t:)"] = "Returns an array with the keys of the table" +E2Helper.Descriptions["values(t:)"] = "Returns an array with the values of the table (tables and arrays, which arrays do not support, are discarded)" +E2Helper.Descriptions["pop(t:)"] = "Removes the last entry in the array-part and returns 1 if removed" +E2Helper.Descriptions["shift(t:)"] = "Removes the first element of the table; all other entries will move down one address and returns 1 if removed" +E2Helper.Descriptions["remove(t:n)"] = "Removes the specified entry from the array-part and returns 1 if removed" +E2Helper.Descriptions["remove(t:s)"] = "Removes the specified entry from the table-part and returns 1 if removed" +E2Helper.Descriptions["unset(t:n)"] = "Force removes the specified entry from the array-part, without moving subsequent entries down and returns 1 if removed" +E2Helper.Descriptions["unset(t:s)"] = "Force removes the specified entry from the table-part, without moving subsequent entries down and returns 1 if removed" + +-- arrays +E2Helper.Descriptions["array(...)"] = "Creates an array" +E2Helper.Descriptions["add(r:r)"] = "Adds the contents of the second array to the end of the first array. Returns new array" +E2Helper.Descriptions["sum(r:)"] = "Adds all numbers in the array together and returns result" +E2Helper.Descriptions["average(r:)"] = "Gives the average of all numbers in array" +E2Helper.Descriptions["clear(r:)"] = "Clears the array" +E2Helper.Descriptions["clone(r:)"] = "Returns a copy of the array" +E2Helper.Descriptions["concat(r:)"] = "Concatenates all values in the array" +E2Helper.Descriptions["concat(r:s)"] = "Concatenates all values in the array with the specified string in between each" +E2Helper.Descriptions["concat(r:n)"] = "Concatenates all values in the array, starting at index N" +E2Helper.Descriptions["concat(r:sn)"] = "Concatenates all values in the array, starting at index N, with string S in between each" +E2Helper.Descriptions["concat(r:nn)"] = "Concatenates all values in the array, starting at index N1 and ending at N2" +E2Helper.Descriptions["concat(r:snn)"] = "Concatenates all values in the array, starting at index N1 and ending at N2, with string S in between each" +E2Helper.Descriptions["count(r:)"] = "Returns the number of entries in the array" +E2Helper.Descriptions["exists(r:n)"] = "Returns 1 if the array contains any value at specified index" +E2Helper.Descriptions["id(r:)"] = "Returns the unique ID of the array" +E2Helper.Descriptions["invert(r)"] = "Inverts the array, creating a lookup table" +E2Helper.Descriptions["min(r:)"] = "Returns the smallest number in array" +E2Helper.Descriptions["max(r:)"] = "Returns the largest number in array" +E2Helper.Descriptions["minIndex(r:)"] = "Returns the index of the smallest number in array" +E2Helper.Descriptions["maxIndex(r:)"] = "Returns the index of the largest number in array" +E2Helper.Descriptions["merge(r:r)"] = "Merges R2 with R. Any variables with the same indexes are overwritten by R2's variables" +E2Helper.Descriptions["pop(r:)"] = "Removes the last entry in the array and returns 1 if removed" +E2Helper.Descriptions["shift(r:)"] = "Removes the first element of the array; all other entries will move down one address and returns 1 if removed" +E2Helper.Descriptions["remove(r:n)"] = "Removes the specified entry, moving subsequent entries down to compensate and returns 1 if removed" +E2Helper.Descriptions["unset(r:n)"] = "Force removes the specified entry, without moving subsequent entries down and returns 1 if removed" + +-- binary +E2Helper.Descriptions["bOr(nn)"] = "Performs bitwise OR against the two numbers" +E2Helper.Descriptions["bAnd(nn)"] = "Performs bitwise AND against the two numbers" +E2Helper.Descriptions["bXor(nn)"] = "Performs bitwise XOR against the two numbers" +E2Helper.Descriptions["bShl(nn)"] = "Performs bitwise shift left on the first number by the amount of the second" +E2Helper.Descriptions["bShr(nn)"] = "Performs bitwise shift right on the first number by the amount of the second" +E2Helper.Descriptions["bNot(n)"] = "Performs a binary Not" +E2Helper.Descriptions["bNot(nn)"] = "Performs a binary Not. The second argument is the length of the number you wish to perform Not on in bits" + +-- EGP +E2Helper.Descriptions["egpAlign(xwl:nn)"] = "Changes the horizontal alignment. Works on: text and text layout. Number can be 0, 1 or 2" +E2Helper.Descriptions["egpAlign(xwl:nnn)"] = "Changes the horizontal and vertical alignment. Works on: text and text layout. Numbers can be 0, 1 or 2" +E2Helper.Descriptions["egpAlpha(xwl:nn)"] = "Changes the alpha (transparency) of an object" +E2Helper.Descriptions["egpAlpha(xwl:n)"] = "Returns the alpha of the object" +E2Helper.Descriptions["egpAngle(xwl:nn)"] = "Changes the angle of the object" +E2Helper.Descriptions["egpAngle(xwl:n)"] = "Returns the angle of the object" +E2Helper.Descriptions["egpAngle(xwl:nxv2xv2n)"] = "Rotates the object around the first vec2 with the second vec2 as offset at angle N" +E2Helper.Descriptions["egpColor(xwl:n)"] = "Returns the color of the object as 3D vector" +E2Helper.Descriptions["egpColor(xwl:nnnnn)"] = "Changes the color and alpha of the object" +E2Helper.Descriptions["egpColor(xwl:nxv4)"] = "Changes the color and alpha of the object" +E2Helper.Descriptions["egpColor(xwl:nv)"] = "Changes the color of the object" +E2Helper.Descriptions["egpColor4(xwl:n)"] = "Returns the color of the object as 4D vector (including alpha)" +E2Helper.Descriptions["egpFidelity(xwl:n)"] = "Returns the fidelity of the object" +E2Helper.Descriptions["egpFidelity(xwl:nn)"] = "Changes the fidelity of the object (the number of vertices the circle will use)" +E2Helper.Descriptions["egpFont(xwl:nsn)"] = "Changes the font and size of the text object" +E2Helper.Descriptions["egpFont(xwl:ns)"] = "Changes the font of the text object" +E2Helper.Descriptions["egpMaterial(xwl:n)"] = "Returns the material of the object" +E2Helper.Descriptions["egpMaterial(xwl:ns)"] = "Changes the material of the object" +E2Helper.Descriptions["egpMaterialFromScreen(xwl:ne)"] = "Sets the material of the object to a current snapshot of the target screen. Note that this only works for players which see both the egp as well the target screen at that time" +E2Helper.Descriptions["egpOrder(xwl:nn)"] = "Sets the order at which the object will be rendered" +E2Helper.Descriptions["egpOrder(xwl:n)"] = "Returns the order at which the object is rendered" +E2Helper.Descriptions["egpParent(xwl:n)"] = "Returns the index of the parent object" +E2Helper.Descriptions["egpParent(xwl:ne)"] = "Parents the 3D tracker object to an entity" +E2Helper.Descriptions["egpParent(xwl:nn)"] = "Parents the object to another object. Parented objects' positions are local to their parent" +E2Helper.Descriptions["egpParentToCursor(xwl:n)"] = "Parents the object to player's cursor" +E2Helper.Descriptions["egpUnParent(xwl:n)"] = "Un-parents the object" +E2Helper.Descriptions["egpPos(xwl:n)"] = "Returns the position of the object" +E2Helper.Descriptions["egpPos(xwl:nxv2)"] = "Changes the position of the object" +E2Helper.Descriptions["egpPos(xwl:nv)"] = "Changes the world position of the 3D tracker object" +E2Helper.Descriptions["egpQueue()"] = "Returns the number of items in your queue" +E2Helper.Descriptions["egpQueueClk(e)"] = "Returns 1 if the current execution was caused by the EGP queue system of specified screen" +E2Helper.Descriptions["egpQueueClk(xwl)"] = "Returns 1 if the current execution was caused by the EGP queue system of specified screen" +E2Helper.Descriptions["egpQueueClk()"] = "Returns 1 if the current execution was caused by the EGP queue system" +E2Helper.Descriptions["egpQueueClkPly(e)"] = "Returns 1 if the current execution was caused by the EGP queue system, and the player E was the player who ordered the items to be sent" +E2Helper.Descriptions["egpQueuePlayer()"] = "Returns the player which ordered the items to be sent" +E2Helper.Descriptions["egpQueueScreen()"] = "Returns the screen entity which the queue finished sending items for" +E2Helper.Descriptions["egpQueueScreenWirelink()"] = "Returns the screen wirelink which the queue finished sending items for" +E2Helper.Descriptions["egpRadius(xwl:nn)"] = "Changes the corner radius of the rounded box object" +E2Helper.Descriptions["egpRadius(xwl:n)"] = "Returns the corcner radius of the rounded box object" +E2Helper.Descriptions["egpRemove(xwl:n)"] = "Removes the object from the screen" +E2Helper.Descriptions["egpResolution(xwl:xv2xv2)"] = "Sets the scale of the screen such that the top left corner is equal to the first vector and the bottom right corner is equal to the second vector" +E2Helper.Descriptions["egpScale(xwl:xv2xv2)"] = "Sets the scale of the screen's X axis to the first vector and Y axis to the second vector" +E2Helper.Descriptions["egpScrH(e)"] = "Returns the player's screen resolution height" +E2Helper.Descriptions["egpScrW(e)"] = "Returns the player's screen resolution width" +E2Helper.Descriptions["egpScrSize(e)"] = "Returns the player's screen resolution size" +E2Helper.Descriptions["egpSetText(xwl:ns)"] = "Changes the text of the text object" +E2Helper.Descriptions["egpSize(xwl:n)"] = "Returns the size of the object" +E2Helper.Descriptions["egpSize(xwl:nn)"] = "Changes the size of the text/line/outline object" +E2Helper.Descriptions["egpSize(xwl:nxv2)"] = "Changes the size of the object" +E2Helper.Descriptions["egpSizeNum(xwl:n)"] = "Returns the size of the text/line/outline object" +E2Helper.Descriptions["egpToWorld(xwl:xv2)"] = "Converts a 2D vector on the screen or emitter into a 3D vector in the world" +E2Helper.Descriptions["egpTrackerParent(xwl:n)"] = "Returns the parent entity of the 3D tracker object" + +--E2Helper.Descriptions["egpAddVertices(xwl:nr)"] = "" +--E2Helper.Descriptions["egpBytesLeft()"] = "" +E2Helper.Descriptions["egpCanSendUmsg()"] = "Returns 1 if you can send an usermessage at the moment, 0 otherwise" +E2Helper.Descriptions["egpClear(xwl:)"] = "Clears the EGP screen" +E2Helper.Descriptions["egpClearQueue()"] = "Clears your entire queue" +E2Helper.Descriptions["egpCopy(xwl:nn)"] = "Copies the settings of the second object into the first. If the first object does not exist, it's created" +E2Helper.Descriptions["egpCursor(xwl:e)"] = "Returns the specified player's aim position on the screen" +E2Helper.Descriptions["egpDrawTopLeft(xwl:n)"] = "Set to 1 to make boxes, outline boxes, rounded boxes, and rounded outline boxes draw from the top left corner instead of from the center" +E2Helper.Descriptions["egpGlobalPos(xwl:n)"] = "Returns the \"global\" (= it takes the parents' positions into consideration) position as a 3D vector. X and Y being the 2D X,Y coordinates, while Z is the angle" +E2Helper.Descriptions["egpGlobalVertices(xwl:n)"] = "Returns an array of 2D vectors with the \"global\" positions of the vertices in the object" +E2Helper.Descriptions["egpGlobalFiltering(xwl:n)"] = "Changes the texture filter used to draw all EGP Objects. Works only on EGP Screens. See _TEXFILTER constants (POINT=sharp, ANISOTROPIC=blurry/default)" +E2Helper.Descriptions["egpHasObject(xwl:n)"] = "Returns 1 if the object with specified index exists on the screen, 0 if not" +E2Helper.Descriptions["egpObjectContainsPoint(xwl:nxv2)"] = "Returns 1 if the object with specified index contains the specified point" +E2Helper.Descriptions["egpHudToggle(xwl:)"] = "Toggles the HUD on/off" +E2Helper.Descriptions["egpLoadFrame(xwl:n)"] = "Loads the frame with specified index" +E2Helper.Descriptions["egpLoadFrame(xwl:s)"] = "Loads the frame with specified name" +E2Helper.Descriptions["egpSaveFrame(xwl:n)"] = "Saves the frame under specified index" +E2Helper.Descriptions["egpSaveFrame(xwl:s)"] = "Saves the frame under specified name" +E2Helper.Descriptions["egpFiltering(xwl:nn)"] = "Changes the texture filter used to draw the object. Works on objects that draw a material. See _TEXFILTER constants (POINT=sharp, ANISOTROPIC=blurry/default)" +E2Helper.Descriptions["egpMaxObjects()"] = "Returns the maximum amount of objects you can have" +E2Helper.Descriptions["egpMaxUmsgPerSecond()"] = "Returns the maximum number of usermessages you can send per second" +E2Helper.Descriptions["egpNumObjects(xwl:)"] = "Returns the number of objects on the screen" +E2Helper.Descriptions["egpRunOnQueue(xwl:n)"] = "Set to 1 if you want your E2 to be triggered once the queue has finished sending all items in the queue for the screen" +E2Helper.Descriptions["egpVertices(xwl:n)"] = "Returns an array of the vertices of the object" +E2Helper.Descriptions["egpObjectIndexes(xwl:)"] = "Returns an array containing all object indexes being used" +E2Helper.Descriptions["egpObjectType(xwl:n)"] = "Returns the type of the object with specified index" +E2Helper.Descriptions["egpObjectTypes(xwl:)"] = "Returns an array whose keys are bound to object index, and value being the type of particular object" + +E2Helper.Descriptions["egp3DTracker(xwl:nv)"] = "Creates a 3D tracker object at specified world position" +E2Helper.Descriptions["egpBox(xwl:nxv2xv2)"] = "Creates a box. First 2D vector is the position, second is size" +E2Helper.Descriptions["egpBoxOutline(xwl:nxv2xv2)"] = "Creates an outline box. First 2D vector is the position, second is size" +E2Helper.Descriptions["egpCircle(xwl:nxv2xv2)"] = "Creates a circle. First 2D vector is the position, second is size" +E2Helper.Descriptions["egpCircleOutline(xwl:nxv2xv2)"] = "Creates an outline circle. First 2D vector is the position, second is size" +E2Helper.Descriptions["egpLine(xwl:nxv2xv2)"] = "Creates a line. First 2D vector is the start position, second is end position" +E2Helper.Descriptions["egpLineStrip(xwl:n...)"] = "Creates a curve with specified points as 2D/4D vectors (x,y)/(x,y,u,v)" +E2Helper.Descriptions["egpLineStrip(xwl:nr)"] = "Creates a curve with specified points as array of 2D/4D vectors (x,y)/(x,y,u,v)" +E2Helper.Descriptions["egpPoly(xwl:nr)"] = "Creates a polygon with specified points as array of 2D/4D vectors (x,y)/(x,y,u,v)" +E2Helper.Descriptions["egpPoly(xwl:n...)"] = "Creates a polygon with specified points as 2D/4D vectors (x,y)/(x,y,u,v)" +E2Helper.Descriptions["egpPolyOutline(xwl:nr)"] = "Creates a outline polygon with specified points as array of 2D/4D vectors (x,y)/(x,y,u,v)" +E2Helper.Descriptions["egpPolyOutline(xwl:n...)"] = "Creates a outline polygon with specified points as 2D/4D vectors (x,y)/(x,y,u,v)" +E2Helper.Descriptions["egpRoundedBox(xwl:nxv2xv2)"] = "Creates a rounded box. First 2D vector is the position, second is size" +E2Helper.Descriptions["egpRoundedBoxOutline(xwl:nxv2xv2)"] = "Creates a rounded outline box. First 2D vector is the position, second is size" +E2Helper.Descriptions["egpText(xwl:nsxv2)"] = "Creates a text object" +E2Helper.Descriptions["egpTextLayout(xwl:nsxv2xv2)"] = "Creates a text layout object" +E2Helper.Descriptions["egpTriangle(xwl:nxv2xv2xv2)"] = "Creates a triangle with specified vertices" +E2Helper.Descriptions["egpTriangleOutline(xwl:nxv2xv2xv2)"] = "Creates a outline triangle with specified vertices" +E2Helper.Descriptions["egpWedge(xwl:nxv2xv2)"] = "Creates a wedge object. Wedge objects are like circles, except they have a cake-piece-like mouth which you can change using egpSize" +E2Helper.Descriptions["egpWedgeOutline(xwl:nxv2xv2)"] = "Creates a outline wedge object. Wedge objects are like circles, except they have a cake-piece-like mouth which you can change using egpSize" + +-- (de)serialization +E2Helper.Descriptions["glonDecode(s)"] = "Decodes a string into an array using GLON" +E2Helper.Descriptions["glonDecodeTable(s)"] = "Decodes a string into a table using GLON" +E2Helper.Descriptions["glonEncode(r)"] = "Encodes an array into a string using GLON" +E2Helper.Descriptions["glonEncode(t)"] = "Encodes a table into a string using GLON" +E2Helper.Descriptions["glonError()"] = "Returns the last glon error" + +E2Helper.Descriptions["vonDecode(s)"] = "Decodes a string into an array using vON" +E2Helper.Descriptions["vonDecodeTable(s)"] = "Decodes a string into a table using vON" +E2Helper.Descriptions["vonEncode(r)"] = "Encodes an array into a string using vON" +E2Helper.Descriptions["vonEncode(t)"] = "Encodes a table into a string using vON" +E2Helper.Descriptions["vonError()"] = "Returns the last von error" + +E2Helper.Descriptions["jsonDecode(s)"] = "Decodes a string into an array using json" +E2Helper.Descriptions["jsonDecodeTable(s)"] = "Decodes a string into a table using json" +E2Helper.Descriptions["jsonEncode(r)"] = "Encodes an array into a string using json" +E2Helper.Descriptions["jsonEncode(rn)"] = "Encodes an array into a string using json" +E2Helper.Descriptions["jsonEncode(t)"] = "Encodes a table into a string using json" +E2Helper.Descriptions["jsonEncode(tn)"] = "Encodes a table into a string using json" +E2Helper.Descriptions["jsonEncodeExternal(t)"] = "Encodes a table into a string using json, in a form that is suitable to be exported to external resources. Unfortunately, arrays are ignored because E2 contains many ambiguous types, and arrays don't keep track of those types. You will have to convert your array to a table manually before encoding it." +E2Helper.Descriptions["jsonEncodeExternal(tn)"] = "Encodes a table into a string using json, in a form that is suitable to be exported to external resources. Unfortunately, arrays are ignored because E2 contains many ambiguous types, and arrays don't keep track of those types. You will have to convert your array to a table manually before encoding it." +E2Helper.Descriptions["jsonDecodeTableExternal(s)"] = "Decodes a string into a table using json" +E2Helper.Descriptions["jsonError()"] = "Returns the last json error" + +-- http +E2Helper.Descriptions["httpCanRequest()"] = "Returns whether you can make a new request (delay has been met or previous request timed out)" +E2Helper.Descriptions["httpClk()"] = "Returns whether the execution was run because of a completed request" +E2Helper.Descriptions["httpData()"] = "Returns the data received from the last request" +E2Helper.Descriptions["httpSuccess()"] = "Returns whether the previous request was successful" +E2Helper.Descriptions["httpRequest(s)"] = "Starts a new request" +E2Helper.Descriptions["httpRequestUrl()"] = "Returns the URL of the last request" +E2Helper.Descriptions["httpUrlDecode(s)"] = "Returns decoded URL data" +E2Helper.Descriptions["httpUrlEncode(s)"] = "Returns formatted string to be placed in the URL" +E2Helper.Descriptions["runOnHTTP(n)"] = "Sets whether to run the expression when a request finishes" + +-- sound +E2Helper.Descriptions["soundDuration(s)"] = "soundDuration(string Path to File) Returns the duration of the sound. Note: If the server hasn't the file it returns 60" +E2Helper.Descriptions["soundPitch(sn)"] = "soundPitch(string Index, integer Pitch) Default Pitch is 100, max is 255. Pitch is scaled linearly (like frequency), rather than by octave" +E2Helper.Descriptions["soundPitch(nnn)"] = "soundPitch(integer Index, integer Pitch, integer Fadetime) Default Pitch is 100, max is 255. Pitch is scaled linearly (like frequency), rather than by octave" +E2Helper.Descriptions["soundPitch(snn)"] = "soundPitch(string Index, integer Pitch, integer Fadetime) Default Pitch is 100, max is 255. Pitch is scaled linearly (like frequency), rather than by octave" +E2Helper.Descriptions["soundPitch(nn)"] = "soundPitch(integer Index, integer Pitch) Default Pitch is 100, max is 255. Pitch is scaled linearly (like frequency), rather than by octave" +E2Helper.Descriptions["soundPlay(e:sns)"] = "Plays sound from an entity. soundPlay(string Index, int Duration, string Path to File)" +E2Helper.Descriptions["soundPlay(nns)"] = "Plays sound from the E2 chip. soundPlay(int Index, int Duration, string Path to File)" +E2Helper.Descriptions["soundPlay(snsn)"] = "Plays sound from the E2 chip. soundPlay(string Index, int Duration, string Path to File, int FadeTime)" +E2Helper.Descriptions["soundPlay(sns)"] = "Plays sound from the E2 chip. soundPlay(string Index, int Duration, string Path to File)" +E2Helper.Descriptions["soundPlay(nnsn)"] = "Plays sound from the E2 chip. soundPlay(int Index, int Duration, string Path to File, int FadeTime)" +E2Helper.Descriptions["soundPlay(e:nns)"] = "Plays sound from an entity. soundPlay(int Index, int Duration, string Path to File)" +E2Helper.Descriptions["soundPlay(e:snsn)"] = "Plays sound from an entity. soundPlay(string Index, int Duration, string Path to File, int FadeTime)" +E2Helper.Descriptions["soundPlay(e:nnsn)"] = "Plays sound from an entity. soundPlay(int Index, int Duration, string Path to File, int FadeTime)" +E2Helper.Descriptions["soundPurge()"] = "Clears the sound table and stops all sounds" +E2Helper.Descriptions["soundStop(n)"] = "Stops the sound stored at the integer index and removes the entry" +E2Helper.Descriptions["soundStop(s)"] = "Stops the sound stored at the string index and removes the entry" +E2Helper.Descriptions["soundStop(nn)"] = "Fades the sound stored at the first input's integer index in the second input's amount of seconds and removes the entry" +E2Helper.Descriptions["soundStop(sn)"] = "Fades the sound stored at the string index in the integer input's amount of seconds and removes the entry" +E2Helper.Descriptions["soundVolume(snn)"] = "soundVolume(string Index, Volume, FadeTime), where Volume is a number between 0 and 1. Default Volume is 1" +E2Helper.Descriptions["soundVolume(sn)"] = "soundVolume(string Index, Volume), where Volume is a number between 0 and 1. Default Volume is 1" +E2Helper.Descriptions["soundVolume(nn)"] = "soundVolume(integer Index, Volume), where Volume is a number between 0 and 1. Default Volume is 1" +E2Helper.Descriptions["soundVolume(nnn)"] = "soundVolume(integer Index, Volume, FadeTime), where Volume is a number between 0 and 1. Default Volume is 1" + +-- UTF-8 +E2Helper.Descriptions["toUnicodeChar(...)"] = "Returns the UTF-8 string from the given Unicode code-points" +E2Helper.Descriptions["toUnicodeChar(r)"] = "Returns the UTF-8 string from the given Unicode code-points" +E2Helper.Descriptions["toUnicodeByte(s:nn)"] = "Returns the Unicode code-points from the given UTF-8 string" +E2Helper.Descriptions["unicodeLength(s:nn)"] = "Returns the length of the given UTF-8 string" + +-- Damage +E2Helper.Descriptions["runOnDeath(n)"] = "If set to 0, chip won't run on players dying" +E2Helper.Descriptions["deathClk()"] = "Returns if the E2 was triggered by a death" +E2Helper.Descriptions["lastDeathTime()"] = "Returns the last time a player died" +E2Helper.Descriptions["lastDeathTime(e)"] = "Returns the last time given player died" +E2Helper.Descriptions["lastDeathInflictor()"] = "Returns the entity that inflicted the last death" +E2Helper.Descriptions["lastDeathInflictor(e)"] = "Returns the entity that inflicted the given player's last death" +E2Helper.Descriptions["lastDeathVictim()"] = "Returns the last player to die" +E2Helper.Descriptions["lastDeathAttacker()"] = "Returns the attacker who killed the last player to die" +E2Helper.Descriptions["lastDeathAttacker(e)"] = "Returns the attacker who killed the player provided in their last death" +-- +E2Helper.Descriptions["runOnSpawn(n)"] = "If set to 0, chip won't run on players spawning" +E2Helper.Descriptions["spawnClk()"] = "Returns if the E2 was triggered by a player spawning" +E2Helper.Descriptions["lastSpawnTime()"] = "Returns the last time a player spawned" +E2Helper.Descriptions["lastSpawnTime(e)"] = "Returns the last time the given player spawned" +E2Helper.Descriptions["lastSpawnedPlayer()"] = "Returns the last player to spawn" + +---- Custom ---- +-- Effect +E2Helper.Descriptions["effect()"] = "Creates and returns new effect" +E2Helper.Descriptions["play(xef:s)"] = "Plays the effect with given name (eg. watersplash)" +E2Helper.Descriptions["setAngles(xef:a)"] = "Sets the angle of the effect" +E2Helper.Descriptions["setAttachment(xef:n)"] = "Creates new attachment ID for the effect" +E2Helper.Descriptions["setColor(xef:n)"] = "Sets the color of the effect. Color is represented by a byte" +E2Helper.Descriptions["setDamageType(xef:n)"] = "Sets the damage type of the effect. See DMG_ Enums on GMod Wiki" +E2Helper.Descriptions["setEntIndex(xef:n)"] = "Sets the entity of the effect via its index" +E2Helper.Descriptions["setEntity(xef:e)"] = "Sets the entity of the effect" +E2Helper.Descriptions["setFlags(xef:n)"] = "Sets the flags of the effect" +E2Helper.Descriptions["setHitBox(xef:n)"] = "Sets the hit box index of the effect" +E2Helper.Descriptions["setMagnitude(xef:n)"] = "Sets the magnitude of the effect" +E2Helper.Descriptions["setMaterialIndex(xef:n)"] = "Sets the material index of the effect" +E2Helper.Descriptions["setNormal(xef:v)"] = "Sets the normalized direction vector of the effect" +E2Helper.Descriptions["setOrigin(xef:v)"] = "Sets the origin of the effect" +E2Helper.Descriptions["setRadius(xef:n)"] = "Sets the radius of the effect" +E2Helper.Descriptions["setScale(xef:n)"] = "Sets the scale of the effect" +E2Helper.Descriptions["setStart(xef:v)"] = "Sets the start of the effect" +E2Helper.Descriptions["setSurfaceProp(xef:n)"] = "Sets the surface property index of the effect" diff --git a/garrysmod/addons/feature-wire/lua/wire/client/e2helper.lua b/garrysmod/addons/feature-wire/lua/wire/client/e2helper.lua new file mode 100644 index 0000000..07d3e81 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/e2helper.lua @@ -0,0 +1,464 @@ +--[[ + Expression 2 Helper for Expression 2 + -HP- (and tomylobo, though he breaks a lot ^^) Divran made CPU support +]] -- + +E2Helper = {} +local E2Helper = E2Helper -- faster access +E2Helper.Descriptions = {} + +include("e2descriptions.lua") + +------------------------------- +---- CPU support +E2Helper.CPUDescriptions = {} +E2Helper.CPUTable = {} +E2Helper.CurrentMode = true -- E2/CPU. True = E2, false = CPU + +local function AddCPUDesc(FuncName, Args, Desc, Platform, Type) + table.insert(E2Helper.CPUTable, { [1] = FuncName, [2] = Args, [3] = Platform, [4] = Type }) + E2Helper.CPUDescriptions[FuncName] = Desc +end + +-- Add help on all opcodes +for _, instruction in ipairs(CPULib.InstructionTable) do + if (instruction.Mnemonic ~= "RESERVED") and + (not instruction.Obsolete) then + + local instructionArgs = instruction.Operand1 + if instruction.Operand2 ~= "" then + instructionArgs = instructionArgs .. ", " .. instruction.Operand2 + end + + AddCPUDesc(instruction.Mnemonic, + instructionArgs, + instruction.Reference, + instruction.Set, + instruction.Opcode) + end +end + + +-- Which tables are we going to use? +local function CurrentDescs() + if E2Helper.CurrentMode == true then + return E2Helper.Descriptions + else + return E2Helper.CPUDescriptions + end +end + +local function CurrentTable() + if E2Helper.CurrentMode == true then + return wire_expression2_funcs + else + return E2Helper.CPUTable + end +end + +------------------------------- + +local lastmax = 0 +local cookie_maxresults, cookie_tooltip, cookie_w, cookie_h +local function cookie_update() + local current_maxresults = E2Helper.MaxEntry:GetValue() + if current_maxresults > lastmax then + lastmax = current_maxresults + E2Helper.Update() + return -- return, since E2Helper.Update() already called cookie_update again. + end + + if current_maxresults ~= cookie_maxresults then + cookie.Set("e2helper_maxresults", current_maxresults) + cookie_maxresults = current_maxresults + end + + local current_tooltip = E2Helper.Tooltip:GetChecked(true) and 1 or 0 + if current_tooltip ~= cookie_tooltip then + cookie.Set("e2helper_tooltip", current_tooltip) + cookie_tooltip = current_tooltip + end + + local current_w, current_h = E2Helper.Frame:GetSize() + if current_w ~= cookie_w then + cookie.Set("e2helper_w", current_w) + cookie_w = current_w + end + + if current_h ~= cookie_h then + cookie.Set("e2helper_h", current_h) + cookie_h = current_h + end +end + +-- returns a function that executes , delayed by seconds +local function delayed(t, func) + return function() + timer.Remove("e2helper_delayed") + timer.Create("e2helper_delayed", t, 1, func) + end +end + +local function getdesc(name, args) + return CurrentDescs()[string.format("%s(%s)", name, args)] or CurrentDescs()[name] +end + +function E2Helper.Create(reset) + local x, y, w, h + + E2Helper.Frame = vgui.Create("DFrame") + E2Helper.Frame:SetSize(280, 425) + E2Helper.Frame:Center() + E2Helper.Frame:SetSizable(true) + E2Helper.Frame:SetScreenLock(true) + E2Helper.Frame:SetDeleteOnClose(false) + E2Helper.Frame:SetVisible(false) + E2Helper.Frame:SetTitle("E2Helper") + E2Helper.Frame._PerformLayout = E2Helper.Frame.PerformLayout + function E2Helper.Frame:PerformLayout(...) + local w, h = E2Helper.Frame:GetSize() + if w < 240 then w = 240 end + if h < 300 then h = 300 end + E2Helper.Frame:SetSize(w, h) + + self:_PerformLayout(...) + E2Helper.Resize() + end + + -- holds all the lines describing a constant + E2Helper.constants = {} + + E2Helper.DescriptionEntry = vgui.Create("DTextEntry", E2Helper.Frame) + E2Helper.DescriptionEntry:SetPos(5, 330) + E2Helper.DescriptionEntry:SetSize(270, 45) + E2Helper.DescriptionEntry:SetEditable(true) + E2Helper.DescriptionEntry:SetMultiline(true) + + E2Helper.ResultFrame = vgui.Create("DListView", E2Helper.Frame) + E2Helper.ResultFrame:SetPos(5, 60) + E2Helper.ResultFrame:SetSize(270, 240) + E2Helper.ResultFrame:SetMultiSelect(false) + E2Helper.ResultFrame:AddColumn("Function"):SetWidth(126) + E2Helper.ResultFrame:AddColumn("Takes"):SetWidth(60) + E2Helper.ReturnsColumn = E2Helper.ResultFrame:AddColumn("Returns") + E2Helper.ReturnsColumn:SetWidth(60) + E2Helper.CostColumn = E2Helper.ResultFrame:AddColumn("Cost") + E2Helper.CostColumn:SetWidth(30) + + function E2Helper.ResultFrame:OnClickLine(line) + self:ClearSelection() + self:SelectItem(line) + + -- don't try describing the function when it is actually a constant + if E2Helper.constants[line] then + E2Helper.FuncEntry:SetText("Constant value") + + E2Helper.DescriptionEntry:SetText("Constants do not support descriptions (yet)") + E2Helper.DescriptionEntry:SetTextColor(Color(128, 128, 128)) + else + E2Helper.FuncEntry:SetText(E2Helper.GetFunctionSyntax(line:GetValue(1), line:GetValue(2), line:GetValue(3))) + local desc = getdesc(line:GetValue(1), line:GetValue(2)) + if desc then + E2Helper.DescriptionEntry:SetText(desc) + E2Helper.DescriptionEntry:SetTextColor(Color(0, 0, 0)) + else + E2Helper.DescriptionEntry:SetText("No description found :(") + E2Helper.DescriptionEntry:SetTextColor(Color(128, 128, 128)) + end + end + end + + E2Helper.Image = vgui.Create("DImage", E2Helper.Frame) + E2Helper.Image:SetPos(5, 75) + E2Helper.Image:SetImage("expression 2/cog.vtf") + E2Helper.Image:SetImageColor(Color(255, 0, 0, 8)) + E2Helper.Image:SetSize(225, 225) + + E2Helper.ResultLabel = vgui.Create("DLabel", E2Helper.Frame) + E2Helper.ResultLabel:SetPos(5, 405) + E2Helper.ResultLabel:SetText("") + E2Helper.ResultLabel:SizeToContents() + + E2Helper.CredLabel = vgui.Create("DLabel", E2Helper.Frame) + E2Helper.CredLabel:SetPos(238, 405) + E2Helper.CredLabel:SetText("By -HP-") + E2Helper.CredLabel:SizeToContents() + E2Helper.CredLabel.Resize = { true, true, false, false } + + E2Helper.NameEntry = vgui.Create("DTextEntry", E2Helper.Frame) + E2Helper.NameEntry:SetPos(5, 32) + E2Helper.NameEntry:SetWide(129) + + E2Helper.ParamEntry = vgui.Create("DTextEntry", E2Helper.Frame) + E2Helper.ParamEntry:SetPos(136, 32) + E2Helper.ParamEntry:SetWide(68) + + E2Helper.ReturnEntry = vgui.Create("DTextEntry", E2Helper.Frame) + E2Helper.ReturnEntry:SetPos(206, 32) + E2Helper.ReturnEntry:SetWide(68) + + E2Helper.Tooltip = vgui.Create("DCheckBoxLabel", E2Helper.Frame) + E2Helper.Tooltip:SetPos(5, 384) + E2Helper.Tooltip:SetText("Tooltip") + E2Helper.Tooltip:SetValue(reset and 0 or cookie.GetNumber("e2helper_tooltip", 0)) + E2Helper.Tooltip:SizeToContents() + + E2Helper.FuncEntry = vgui.Create("DTextEntry", E2Helper.Frame) + E2Helper.FuncEntry:SetText("") + E2Helper.FuncEntry:SetWidth(270) + E2Helper.FuncEntry:SetPos(5, 305) + + E2Helper.MaxEntry = vgui.Create("DNumberWang", E2Helper.Frame) + E2Helper.MaxEntry:SetPos(235, 380) + E2Helper.MaxEntry:SetWide(40) + E2Helper.MaxEntry:SetTooltip("E2 is being loaded, please wait...") + timer.Create("E2Helper.SetMaxEntry", 1, 0, function() + if e2_function_data_received then + E2Helper.MaxEntry:SetMax(table.Count(CurrentTable())) + timer.Remove("E2Helper.SetMaxEntry") + E2Helper.MaxEntry:SetTooltip(false) + end + end) + E2Helper.MaxEntry:SetValue(reset and 50 or cookie.GetNumber("e2helper_maxresults", 50)) + E2Helper.MaxEntry:SetDecimals(0) + + E2Helper.MaxLabel = vgui.Create("DLabel", E2Helper.Frame) + E2Helper.MaxLabel:SetPos(170, 384) + E2Helper.MaxLabel:SetText("Max results:") + E2Helper.MaxLabel:SizeToContents() + + E2Helper.E2Mode = vgui.Create("DCheckBoxLabel", E2Helper.Frame) + E2Helper.E2Mode:SetPos(90, 384) + E2Helper.E2Mode:SetText("E2") + E2Helper.E2Mode:SetValue(true) + E2Helper.E2Mode:SizeToContents() + function E2Helper.E2Mode.Button:Toggle() + self:SetValue(true) + E2Helper.CurrentMode = true + E2Helper.CPUMode:SetValue(false) + E2Helper.CostColumn:SetName("Cost") + E2Helper.ReturnsColumn:SetName("Returns") + E2Helper.Update() + end + + E2Helper.CPUMode = vgui.Create("DCheckBoxLabel", E2Helper.Frame) + E2Helper.CPUMode:SetPos(90, 404) + E2Helper.CPUMode:SetText("CPU/GPU") + E2Helper.CPUMode:SetValue(false) + E2Helper.CPUMode:SizeToContents() + function E2Helper.CPUMode.Button:Toggle() + self:SetValue(true) + E2Helper.CurrentMode = false + E2Helper.E2Mode:SetValue(false) + E2Helper.CostColumn:SetName("Opcode") + E2Helper.ReturnsColumn:SetName("Platform") + E2Helper.Update() + end + + E2Helper.NameEntry.OnTextChanged = delayed(0.1, E2Helper.Update) + E2Helper.ParamEntry.OnTextChanged = delayed(0.1, E2Helper.Update) + E2Helper.ReturnEntry.OnTextChanged = delayed(0.1, E2Helper.Update) + E2Helper.Tooltip.OnChange = E2Helper.Update + E2Helper.MaxEntry.OnValueChanged = delayed(1, cookie_update) + + local x, y, w, h + E2Helper.Originals = {} + for k, v in pairs(E2Helper) do + if type(v) == "Panel" then + x, y = v:GetPos() + w, h = v:GetSize() + E2Helper.Originals[k] = { x, y, w, h } + end + end + + if not reset then + E2Helper.Frame:SetSize(cookie.GetNumber("e2helper_w", 280), cookie.GetNumber("e2helper_h", 425)) + else + cookie_update() + end + E2Helper.Resize() +end + +function E2Helper.GetFunctionSyntax(func, args, rets) + if E2Helper.CurrentMode == true then + local signature = func .. "(" .. args .. ")" + local ret = E2Lib.generate_signature(signature, rets, wire_expression2_funcs[signature].argnames) + if rets ~= "" then ret = ret:sub(1, 1):upper() .. ret:sub(2) end + return ret + else + --local args = string.gsub(args, "(%a)", "%1,", string.len( args ) - 1) -- this gsub puts a comma in between each letter + return func .. " " .. args + end +end + +function E2Helper.Update() + + cookie_update() + + E2Helper.ResultFrame:Clear() + + local search_name, search_args, search_rets = E2Helper.NameEntry:GetValue():lower(), E2Helper.ParamEntry:GetValue():lower(), E2Helper.ReturnEntry:GetValue():lower() + local count = 0 + local maxcount = E2Helper.MaxEntry:GetValue() + local tooltip = E2Helper.Tooltip:GetChecked(true) + + -- add E2 constants + E2Helper.constants = {} + if E2Helper.CurrentMode == true then + for k, v in pairs(wire_expression2_constants) do + -- set the type according to the functions + local strType = E2Lib.guess_type(v) + + -- constants have no arguments and no cost + local name, args, rets, cost = k, nil, strType, nil + if name:lower():find(search_name, 1, true) and search_args == "" and rets:lower():find(search_rets, 1, true) then + local line = E2Helper.ResultFrame:AddLine(name, args, rets, cost, "constant") + E2Helper.constants[line] = true + count = count + 1 + if count >= maxcount then break end + end + end + end + + if count < maxcount then + for _, v in pairs(CurrentTable()) do + if E2Helper.CurrentMode == true then + local argnames, signature, rets, func, cost = v.argnames, unpack(v) + local name, args = string.match(signature, "^([^(]+)%(([^)]*)%)$") + + if signature:sub(1, 3) ~= "op:" and + name:lower():find(search_name, 1, true) and + args:lower():find(search_args, 1, true) and + rets:lower():find(search_rets, 1, true) then + local line = E2Helper.ResultFrame:AddLine(name, args, rets, cost or 20) + if tooltip then line:SetTooltip(E2Helper.GetFunctionSyntax(name, args, rets)) end + count = count + 1 + if count >= maxcount then break end + end + else + local funcname, args, forwhat, functype = unpack(v) + if (funcname:lower():find(search_name, 1, true) and + args:lower():find(search_args, 1, true) and + forwhat:lower():find(search_rets, 1, true)) then + local line = E2Helper.ResultFrame:AddLine(funcname, args, forwhat, functype) + if tooltip then line:SetTooltip(funcname .. " " .. args) end + count = count + 1 + if count >= maxcount then break end + end + end + end + end + + E2Helper.ResultFrame:SortByColumn(1) + E2Helper.ResultLabel:SetText(count .. " results") + E2Helper.ResultLabel:SizeToContents() +end + +function E2Helper.Show(searchtext) + if not E2Helper.Frame then E2Helper.Create(false) end + E2Helper.Frame:MakePopup() + E2Helper.Frame:SetVisible(true) + if searchtext and searchtext ~= E2Helper.NameEntry:GetValue() then + E2Helper.NameEntry:SetValue(searchtext) + E2Helper.Update() + E2Helper.FuncEntry:SetValue("") + E2Helper.DescriptionEntry:SetValue("") + else + E2Helper.NameEntry:RequestFocus() + end +end + +function E2Helper.UseE2(nEditorType) + E2Helper.CurrentMode = false + E2Helper.E2Mode:Toggle() + local val = E2Helper.ReturnEntry:GetValue() + if val and (val == "CPU" or val == "GPU") then E2Helper.ReturnEntry:SetText("") end + E2Helper.CostColumn:SetName("Cost") + E2Helper.ReturnsColumn:SetName("Returns") +end + +function E2Helper.UseCPU(nEditorType) + E2Helper.CurrentMode = true + E2Helper.CPUMode:Toggle() + E2Helper.CostColumn:SetName("Type") + E2Helper.ReturnsColumn:SetName("For What") + E2Helper.ReturnEntry:SetText(nEditorType) +end + +local delayed_cookie_update = delayed(1, cookie_update) + +local lastw, lasth +function E2Helper.Resize() + local w, h = E2Helper.Frame:GetSize() + if w == lastw and h == lasth then return end + + local orig = E2Helper.Originals + local changew, changeh = w - orig.Frame[3], h - orig.Frame[4] + + -- Epically messy code: + E2Helper.CredLabel:SetPos(orig.CredLabel[1] + changew, orig.CredLabel[2] + changeh) + E2Helper.MaxLabel:SetPos(orig.MaxLabel[1] + changew, orig.MaxLabel[2] + changeh) + E2Helper.MaxEntry:SetPos(orig.MaxEntry[1] + changew, orig.MaxEntry[2] + changeh) + E2Helper.ResultLabel:SetPos(orig.ResultLabel[1], orig.ResultLabel[2] + changeh) + E2Helper.Tooltip:SetPos(orig.Tooltip[1], orig.Tooltip[2] + changeh) + E2Helper.FuncEntry:SetPos(orig.FuncEntry[1], orig.FuncEntry[2] + changeh) + E2Helper.FuncEntry:SetSize(orig.FuncEntry[3] + changew, orig.FuncEntry[4]) + E2Helper.DescriptionEntry:SetPos(orig.DescriptionEntry[1], orig.DescriptionEntry[2] + changeh) + E2Helper.DescriptionEntry:SetSize(orig.DescriptionEntry[3] + changew, orig.DescriptionEntry[4]) + E2Helper.ResultFrame:SetSize(orig.ResultFrame[3] + changew, orig.ResultFrame[4] + changeh) + E2Helper.E2Mode:SetPos(orig.E2Mode[1], orig.E2Mode[2] + changeh) + E2Helper.CPUMode:SetPos(orig.CPUMode[1], orig.CPUMode[2] + changeh) + + E2Helper.NameEntry:SetSize(orig.NameEntry[3] + changew * 0.5, orig.NameEntry[4]) + E2Helper.ParamEntry:SetPos(orig.ParamEntry[1] + changew * 0.5, orig.ParamEntry[2]) + E2Helper.ParamEntry:SetSize(orig.ParamEntry[3] + changew * 0.25, orig.ParamEntry[4]) + E2Helper.ReturnEntry:SetPos(orig.ReturnEntry[1] + changew * 0.75, orig.ReturnEntry[2]) + E2Helper.ReturnEntry:SetSize(orig.ReturnEntry[3] + changew * 0.25, orig.ReturnEntry[4]) + + -- Keep the (funky) image overlay centered on the listview + local w1, h1 = E2Helper.ResultFrame:GetSize() + local x1, y1 = E2Helper.ResultFrame:GetPos() + + -- add borders of mysterious dimensions + x1 = x1 + 15 + w1 = w1 - 30 + y1 = y1 + 30 + h1 = h1 - 45 + + -- fix aspect ratio + if w1 > h1 then + x1 = x1 + (w1 - h1) / 2 + w1 = h1 + else + y1 = y1 + (h1 - w1) / 2 + h1 = w1 + end + + -- apply position and size + E2Helper.Image:SetPos(x1, y1) + E2Helper.Image:SetSize(w1, h1) + + delayed_cookie_update() + + lastw, lasth = w, h +end + +concommand.Add("e2helper", function() E2Helper.Show() end) +concommand.Add("e2helper_reset", function() + if E2Helper.Frame then + E2Helper.Frame:SetDeleteOnClose(true) + E2Helper.Frame:Close() + end + E2Helper.Create(true) + cookie_update() +end) + +local PrevCtrlQ +hook.Add("Think", "E2Helper_KeyListener", function() + if not E2Helper.Frame then return end + local CtrlQ = input.IsKeyDown(KEY_Q) and (input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL)) + if CtrlQ and not PrevCtrlQ and E2Helper.Frame:IsActive() then + E2Helper.Frame:SetVisible(false) + end + PrevCtrlQ = CtrlQ +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/gmod_tool_auto.lua b/garrysmod/addons/feature-wire/lua/wire/client/gmod_tool_auto.lua new file mode 100644 index 0000000..53526be --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/gmod_tool_auto.lua @@ -0,0 +1,208 @@ +local ent_tool_mappings = { + prop_physics = { "easy_precision", "!weapon_physgun" }, + func_physbox = { "easy_precision", "!weapon_physgun" }, +} + +local ent_tool_patterns = { + {"^.*$", ent_tool_mappings}, + + {"^prop_", "!weapon_physgun"}, + bogus = {"^gmod_(.*)$", true}, -- The bogus index ensures that this always is iterated last, so extensions can override it. +} + +local function pattern_mappings(ent, class, ntapped) + local function maprep(replacement, result, ...) + if not result then return end + + if replacement == true then + return result + elseif isstring(replacement) then + return replacement + elseif istable(replacement) then + local narray = #replacement + if narray == 0 then + return maprep(replacement[result], result, ...) + else + return maprep(replacement[((ntapped-1) % narray)+1], result, ...) + end + elseif isfunction(replacement) then + return maprep(replacement(ent, ntapped, result, ...), result, ...) + end + end + + for _,pattern,replacement in pairs_map(ent_tool_patterns, unpack) do + local ret = maprep(replacement, class:match(pattern)) + if ret then return ret end + end +end + +local lastent = NULL +local ntapped = 0 + +concommand.Add("gmod_tool_auto", function(ply, command, args) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + local class = ent:GetClass() + + if ent ~= lastent then + lastent = ent + ntapped = 0 + end + ntapped = ntapped + 1 + local toolmode = pattern_mappings(ent, class, ntapped) + + if not toolmode then return end + local weapon = toolmode:match("^!(.*)$") + if weapon then + RunConsoleCommand( "use", weapon ) + return + end + + spawnmenu.ActivateTool(toolmode) +end) + +local toolbuttons = {} +hook.Add("PostReloadToolsMenu", "toolcpanel_ListTools",function() + local toolmenu = g_SpawnMenu:GetToolMenu() + for toolpanelid=1,#toolmenu.ToolPanels do + if toolmenu:GetToolPanel(toolpanelid) and toolmenu:GetToolPanel(toolpanelid).List.GetChildren and toolmenu:GetToolPanel(toolpanelid).List:GetChildren()[1] then + for sectionid,section in pairs(toolmenu:GetToolPanel(toolpanelid).List:GetChildren()[1]:GetChildren()) do + for buttonid,button in pairs(section:GetChildren()) do + if tobool(button.Command) then toolbuttons[button.Command] = button end + end + end + end + end +end) +concommand.Add("toolcpanel", function(ply,cmd,args) + local panel = toolbuttons["gmod_tool "..args[1]] + if panel then panel:DoClick() end +end) + +-- extension interface: +gmod_tool_auto = {} + +local lastuniqueid = 0 +--- Adds a pattern to be matched against the entity class for gmod_tool_auto. Good for packs with some kind of naming scheme. +--- Returns a uniqueid that can be used to remove the pattern later. +--- +--- replacement can be: +--- true: Use the first pattern capture as the toolmode. +--- string: Use this string as the toolmode. +--- table: Look up first pattern capture and use the result as the toolmode. If nothing was found, the match is ignored. +--- array table: Cycles through the table's entries when using gmod_tool_auto multiple times on the same entity. +--- function(ent, ntapped, capture1, capture2, ...): pass the captures to a function, along with a number that specifies how often gmod_tool_auto was used on the same entity. +--- +--- The table/array lookups and function calls are done recursively. +function gmod_tool_auto.AddPattern(pattern, replacement, index) + lastuniqueid = lastuniqueid + 1 + table.insert(ent_tool_patterns, index or #ent_tool_patterns+1, { pattern, replacement, lastuniqueid }) + return lastuniqueid +end + +--- Removes a pattern given by uniqueid +function gmod_tool_auto.RemovePattern(uniqueid) + for i,pattern,replacement,uid in ipairs_map(ent_tool_patterns, unpack) do + if uniqueid == uid then + table.remove(ent_tool_patterns, i) + return pattern, replacement + end + end +end + +--- Maps a single entity class for gmod_tool_auto. Good for single tools and tools that break your pack's norm. +--- "replacement" can be the same as in gmod_tool_auto.AddPattern. +function gmod_tool_auto.AddSimple(class, replacement) + ent_tool_mappings[class] = replacement +end + +--- Adds all mappings in the given table to the table of single mappings. +--- This basically corresponds to a bunch of calls to gmod_tool_auto.AddSimple. +function gmod_tool_auto.AddSimpleMultiple(mappings) + table.Merge(ent_tool_mappings, mappings) +end + +--- Returns the pattern table and the table of single mappings. +function gmod_tool_auto.GetTables() + return ent_tool_patterns, ent_tool_mappings +end + +local hook = {Add=function(a,b,c)c()end} + +--------------------------------- wiremod part --------------------------------- +local wiremod_mappings = { + -- wiremod+advdupe + gmod_wire_gate = "wire_gates", + gmod_wire_cameracontroller = "wire_cam", + gmod_wire_cd_lock = "wire_cd_ray", + gmod_wire_vectorthruster = "wire_vthruster", + gmod_adv_dupe_paster = "adv_duplicator", +} + +-- Cycle with wire_adv and wire_debugger. +for k,v in pairs(wiremod_mappings) do + wiremod_mappings[k] = { v, "wire_adv", "wire_debugger" } +end + +hook.Add("Initialize", "gmod_tool_auto_wiremod", function() + if not gmod_tool_auto then return end + + gmod_tool_auto.AddPattern("^gmod_(wire_.*)$", { true, "wire_adv", "wire_debugger" }) + gmod_tool_auto.AddSimpleMultiple(wiremod_mappings) +end) + +-------------------------- resource distribution part -------------------------- +local rd_mappings = { + resource_node = { "resourcenodes", "rd3_dev_link2" }, + rd_pump = { "pumps", "rd3_dev_link2" }, +} + +hook.Add("Initialize", "gmod_tool_auto_resource_distribution", function() + if not gmod_tool_auto then return end + + gmod_tool_auto.AddPattern("^rd_.*_valve$", { "valves", "rd3_dev_link2" })-- TODO: add valve links? + + gmod_tool_auto.AddSimpleMultiple(rd_mappings) +end) + +------------------------------- life support part ------------------------------ +local ls_mappings = { + other_screen = "ls3_other", + other_lamp = "ls3_other_lights", + other_spotlight = "ls3_other_lights", +} + +-- Cycle with smart link tool. +for k,v in pairs(ls_mappings) do + ls_mappings[k] = { v, "rd3_dev_link2" } +end + +hook.Add("Initialize", "gmod_tool_auto_life_support", function() + if not gmod_tool_auto then return end + + gmod_tool_auto.AddPattern("^storage_.*$", { "ls3_receptacles", "rd3_dev_link2" }) + gmod_tool_auto.AddPattern("^generator_.*$", { "ls3_energysystems", "rd3_dev_link2" }) + gmod_tool_auto.AddPattern("^other_.*$", { "ls3_environmental_control", "rd3_dev_link2" }) + gmod_tool_auto.AddPattern("^base_.*$", { "ls3_environmental_control", "rd3_dev_link2" }) + gmod_tool_auto.AddPattern("^nature_.*$", { "ls3_environmental_control", "rd3_dev_link2" }) + + gmod_tool_auto.AddSimpleMultiple(ls_mappings) +end) + +-------------------------------- spacebuild part ------------------------------- +local spacebuild_mappings = { + base_terraformer = "sb_terraformer", + nature_dev_tree = "sb_dev_plants", + base_default_res_module = "sbep_res_mods", +} + +-- Cycle with smart link tool. +for k,v in pairs(spacebuild_mappings) do + spacebuild_mappings[k] = { v, "rd3_dev_link2" } +end + +hook.Add("Initialize", "gmod_tool_auto_life_support", function() + if not gmod_tool_auto then return end + + gmod_tool_auto.AddSimpleMultiple(spacebuild_mappings) +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_codetree.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_codetree.lua new file mode 100644 index 0000000..6f28460 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_codetree.lua @@ -0,0 +1,690 @@ +-------------------------------------------------------------------------------- +-- Creates new code tree leaf +function HCOMP:NewLeaf(parentLeaf) + local leaf = { + Opcode = "INVALID", -- Opcode number in this leaf + Operands = {}, + ParentLabel = self.CurrentParentLabel, + } + + if parentLeaf then + leaf.CurrentPosition = parentLeaf.CurrentPosition + else + leaf.CurrentPosition = self:CurrentSourcePosition() + end + return leaf +end + +function HCOMP:NewOpcode(opcode,op1,op2) + local leaf = self:NewLeaf() + leaf.Opcode = opcode + leaf.Operands[1] = op1 + leaf.Operands[2] = op2 + return leaf +end + +-- Each operand can contain the following entries: +-- Register - number of register to use (if it's an "eax" or "es:eax" operand) +-- Constant - constant value to use (if it's an "123" or "es:123" or "123:es" operand) +-- Memory - leaf of memory block to use (if it's an "ptr" or "es:ptr" or "ptr:es" operand) +-- MemoryPointer - explict pointer to memory (can be constant or a leaf) +-- Segment - number of segment register to use +-- MemoryRegister - memory by register (if it's an "#eax" or es:#eax +-- UnknownOperationByLabel - operation type will be determined by label +-- PointerToLabel - operation to retrieve pointer by label +-- Stack - stack operation (can be constant offset or a leaf) +-- TrigonometryHack - operand copied from the first one +-- PreviousLeaf - leaf that must be generated prior to calculating operand +-- ForceTemporary - forces this register to be marked busy & temporary + +-- Each leaf contains the following REQUIRED items: +-- Opcode (required) +-- Operands (required if opcode is asm one) +-- CurrentPosition (position in the sourcecode leaf corresponds to) + +-- Each leaf can contain the following OPTIONAL markers: +-- ForceType - force type of the operation (int, vector, matrix) +-- ExplictAssign (true/nil, the opcode MUST be performed. Result returned BEFORE the operation finished) +-- ReturnAfterAssign (true/nil, if this and ExplictAssign are set, result is returned AFTER the operation finished) +-- ZeroPadding (amount of zero bytes that must be padded after the leaf) +-- PreviousLeaf (leaf that must be generated prior to generating this one) +-- Comment (extra commentary inserted before leaf in assembly listing) +-- Label (label this leaf corresponds to) +-- Data (extra data to be written after the leaf) +-- SetWritePointer (command for leaf to change write pointer in the output stream) +-- SetPointerOffset (command for leaf to change offset in label pointers in output stream) +-- BusyRegisters (array of busy registers for this leaf) +-- ParentLabel (which label this leaf is assigned to, if label was not referenced, this code is not generated) + +-- Special opcodes: +-- LABEL - label leaf (has no size) +-- DATA - data leaf (allocated data) +-- MARKER - marker which tells how to resync write pointer + +-- Types of labels +-- Unknown (undefined) +-- Pointer +-- Variable +-- Stack +-- Register + +-- Label can have extra marker: +-- Array (is this variable an array?) + +-------------------------------------------------------------------------------- +-- Adds leaf to the tail +function HCOMP:AddLeafToTail(leaf) + if self.BusyRegisters then + leaf.BusyRegisters = self.BusyRegisters + end + + if self.GenerateInlineFunction then + table.insert(self.InlineFunctionCode,leaf) + else + table.insert(self.CodeTree,leaf) + end +end + + +-------------------------------------------------------------------------------- +-- Returns free (non-busy) register. Does not check EBP and ESP +function HCOMP:FreeRegister() + -- Try to find a free register + for i=1,6 do + if not self.RegisterBusy[i] then return i end + end + + -- Try to find a register that wasnt pushed to stack yet +-- for i=1,6 do +-- if not self.RegisterStackOffset[i] then +-- local pushLeaf = self:NewLeaf() +-- pushLeaf.Opcode = "push" +-- pushLeaf.Operands[1] = { Register = i } +-- self.RegisterStackOffset[i] = 0 +-- end +-- end + -- FIXME: non-busy register must always exist? + + self:Error("Out of free registers",self.ErrorReportLeaf) + return 1 +end + +-- Gets current list of registers used by users. This can be either the global +-- list, or list inside current code block +function HCOMP:GetUserRegisters() + return self.UserRegisters +end + + + +-------------------------------------------------------------------------------- +-- Makes first operand temporary +-- Returns the register number used, changes the first operand to a temp register +function HCOMP:MakeFirstOperandTemporary(operands) + local freeReg = self:FreeRegister() + + -- Generate MOV + local movLeaf = self:NewLeaf(self.ErrorReportLeaf) + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { + Register = freeReg + } + movLeaf.Operands[2] = operands[1] + self:GenerateLeaf(movLeaf) + + -- Sets operand to be free register, and marks it as temporary and busy + operands[1] = movLeaf.Operands[1] + operands[1].Temporary = true + self.RegisterBusy[freeReg] = true + + return freeReg +end + +-- Reads operand from stack into a temp register (index: which operand is the stack index) +function HCOMP:ReadOperandFromStack(operands,index,forceRead) + local stackOffset = operands[index].Stack + local freeReg = self:FreeRegister() + + -- Generate RSTACK opcode to read value from stack + local rstackLeaf = self:NewLeaf(self.ErrorReportLeaf) + rstackLeaf.Opcode = "rstack" + rstackLeaf.Operands[1] = { + Register = freeReg, + } + if tonumber(stackOffset) then -- Stack offset is a constant value + rstackLeaf.Operands[2] = { + Constant = stackOffset, + Segment = 16 + } + else -- Stack offset is a leaf that returns a constant value + -- Register must be marked used up so its not used in next gen step + self.RegisterBusy[freeReg] = true + + -- Request result of this leaf into a register + local offsetReg,isTemp = self:GenerateLeaf(operands[index].Stack,true) + rstackLeaf.Operands[2] = { + Register = offsetReg, + Segment = 16, + Temporary = isTemp + } + self.RegisterBusy[offsetReg] = isTemp + + self.RegisterBusy[freeReg] = false + end + + -- Generate "RSTACK" leaf + self:GenerateLeaf(rstackLeaf) + + -- Mark register as used (couldn't do before or else code generator would + -- mess up the RSTACK instruction) + self.RegisterBusy[freeReg] = true + + -- Change the operand (and make sure it retains stack offset) + operands[index] = rstackLeaf.Operands[1] + operands[index].Temporary = true + operands[index].Stack = stackOffset +end + +-- Reads operand from memory (index: which operand is the memory pointer) +-- Replaces operand with a temporary register +function HCOMP:ReadOperandFromMemory(operands,index) + if operands[index].MemoryPointer.Opcode then -- Parse complex expression + local addrReg,isTemp = self:GenerateLeaf(operands[index].MemoryPointer,true) + operands[index] = { MemoryRegister = addrReg, Temporary = isTemp } + self.RegisterBusy[addrReg] = isTemp + return addrReg + else -- Parse an operand + local freeReg = self:FreeRegister() + if operands[index].MemoryPointer.Stack then -- Generate stack read + if not tonumber(operands[index].MemoryPointer.Stack) then self:Error("Internal error 186") end + local rstackLeaf = self:NewLeaf(self.ErrorReportLeaf) + rstackLeaf.Opcode = "rstack" + rstackLeaf.Operands[1] = { Register = freeReg } + rstackLeaf.Operands[2] = { Constant = operands[index].MemoryPointer.Stack, Segment = 16 } + self:GenerateLeaf(rstackLeaf) + + operands[index] = { MemoryRegister = freeReg, Temporary = true } + self.RegisterBusy[freeReg] = true + return addrReg + else -- Generate more than just a stack read + if operands[index].MemoryPointer.Register then + operands[index] = { MemoryRegister = operands[index].MemoryPointer.Register, Temporary = operands[index].MemoryPointer.Temporary } + return operands[index].Register + elseif operands[index].MemoryPointer.Constant then + operands[index] = { Memory = operands[index].MemoryPointer.Constant } + return nil + else + local movLeaf = self:NewLeaf(self.ErrorReportLeaf) + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { Register = freeReg, Temporary = true } + movLeaf.Operands[2] = operands[index].MemoryPointer + self.RegisterBusy[freeReg] = true + + local addrReg,isTemp = self:GenerateLeaf(movLeaf,true) + operands[index] = { MemoryRegister = addrReg, Temporary = isTemp } + self.RegisterBusy[addrReg] = isTemp + return addrReg + end + end + end +end + +-- Turns known label operation into a know one +function HCOMP:TurnUnknownLabelIntoKnown(operand) + local label = operand.UnknownOperationByLabel + operand.UnknownOperationByLabel = nil + --print("UNKLABEL",label.Name,label.Type) + if label.Type == "Variable" then + operand.Memory = label + elseif label.Type == "Pointer" then + operand.Constant = label.Expression or {{ Type = self.TOKEN.IDENT, Data = label.Name, Position = self.ErrorReportLeaf.CurrentPosition }} + else + if not label.Name then self:Error("Internal error 033") end + self:Error("Undefined label: "..label.Name,self.ErrorReportLeaf) + end +end + + + + + + + + + +-------------------------------------------------------------------------------- +-- This is THE BEST function. Its the one which turns code tree into generated +-- code. +-- +-- Generates code for the leaf. Returns register which stores result, if required +-- +-- MASSIVE NOTE: it returns WHETHER THE REGISTER >>>MUST<<< BE MADE TEMPORARY, +-- not that it's actually temporary! +-- +function HCOMP:GenerateLeaf(leaf,needResult) + -- Set this leaf for error reporting (small hack) + self.ErrorReportLeaf = leaf + +-- local initTempRegisters = {} +-- for k,v in pairs(self.RegisterBusy) do +-- initTempRegisters[k] = v +-- end + + -- If we have previous leaf, generate it + if leaf.PreviousLeaf then + self:GenerateLeaf(leaf.PreviousLeaf,false) + leaf.PreviousLeaf = nil + end + + -- Do not generate invalid tree leaves + if not leaf.Opcode then + if not leaf.PreviousLeaf then --not leaf.Register then + if istable(leaf.Constant) then + self:Warning("Trying to generate invalid code ("..self:PrintTokens(leaf.Constant)..")",leaf) + elseif not leaf.ForceTemporary then + self:Warning("Trying to generate invalid code",leaf) + end + end + return + end + + -- If operand has a previous leaf assigned, generate it +-- for i=1,#leaf.Operands do +-- if leaf.Operands[i].PreviousLeaf then +-- self:GenerateLeaf(leaf.Operands[i].PreviousLeaf,false) +-- leaf.Operands[i].PreviousLeaf = nil +-- end +-- end + + -- Check if this opcode writes to its first argument + local opcodeWritesFirstOperand = (#leaf.Operands == 2) or + ((#leaf.Operands == 1) and (self.OpcodeWritesOperand[leaf.Opcode])) + + -- Generate explict operands for this leaf + local genOperands = {} + local i = #leaf.Operands + while i >= 1 do + if leaf.Operands[i].PreviousLeaf then + self:GenerateLeaf(leaf.Operands[i].PreviousLeaf,false) + leaf.Operands[i].PreviousLeaf = nil + end + + -- Turn unknown label into known one + if leaf.Operands[i].UnknownOperationByLabel then + --self:TurnUnknownLabelIntoKnown(leaf.Operands[i]) + local label = leaf.Operands[i].UnknownOperationByLabel + leaf.Operands[i].UnknownOperationByLabel = nil + if label.Type == "Variable" then + leaf.Operands[i].Memory = label + elseif label.Type == "Pointer" then + leaf.Operands[i].Constant = label.Expression or {{ Type = self.TOKEN.IDENT, Data = label.Name, Position = self.ErrorReportLeaf.CurrentPosition }} + else + if not label.Name then self:Error("Internal error 033") end + self:Error("Undefined label: "..label.Name,self.ErrorReportLeaf) + end + end + if leaf.Operands[i].MemoryPointer and + istable(leaf.Operands[i].MemoryPointer) and + leaf.Operands[i].MemoryPointer.UnknownOperationByLabel then +-- self:TurnUnknownLabelIntoKnown(leaf.Operands[i].MemoryPointer) + local label = leaf.Operands[i].MemoryPointer.UnknownOperationByLabel + leaf.Operands[i].MemoryPointer.UnknownOperationByLabel = nil + if label.Type == "Variable" then + leaf.Operands[i].MemoryPointer = { Memory = label } + elseif label.Type == "Pointer" then + leaf.Operands[i].MemoryPointer = label.Expression or {{ Type = self.TOKEN.IDENT, Data = label.Name, Position = self.ErrorReportLeaf.CurrentPosition }, TokenList = true } + elseif label.Type == "Register" then + leaf.Operands[i].MemoryPointer = { MemoryRegister = label.Value } + else + if not label.Name then self:Error("Internal error 033") end + self:Error("Undefined label: "..label.Name,self.ErrorReportLeaf) + end + end + + if leaf.Operands[i].Opcode then -- It's an opcode, not an operand we can write + -- Try to calculate the leaf into some temporary register + local resultReg,isTemp = self:GenerateLeaf(leaf.Operands[i],true) + if resultReg then + self.RegisterBusy[resultReg] = isTemp + genOperands[i] = { + Register = resultReg, + Temporary = isTemp, + } + else + -- Generate invalid constant value + genOperands[i] = { + Constant = self.Settings.MagicValue, + } + self:Error("Expression messed up",leaf) + end + else + genOperands[i] = {} + for k,v in pairs(leaf.Operands[i]) do genOperands[i][k] = v end + + -- Need a real explict value if its laying on stack, and we want to write into it + -- Do not gen explict value if our opcode is MOV though (we can turn it into sstack later) + -- Also do not generate explict value if its an explict assign + if genOperands[i].Stack then + if ((i == 1) and (leaf.Opcode ~= "mov")) or -- Force value out of stack if we are about to perform an instruction on it + (i == 2) then -- Or if we are about to read from it + self:ReadOperandFromStack(genOperands,i) + end + end + + -- Need a real explict value if its a non-constant address to memory + if istable(genOperands[i].MemoryPointer) and not genOperands[i].MemoryPointer.TokenList then + self:ReadOperandFromMemory(genOperands,i) + end + + -- Calculate pointer to the label if required + -- was "(i > 1) and genOperands[i].PointerToLabel" + if genOperands[i].PointerToLabel then + local label = genOperands[i].PointerToLabel + if (label.Type == "Variable") or (label.Type == "Unknown") then -- Read from a variable + + genOperands[i] = + { Constant = + { { Type = self.TOKEN.AND, Position = leaf.CurrentPosition }, + { Type = self.TOKEN.IDENT, Data = genOperands[i].PointerToLabel.Name, Position = leaf.CurrentPosition }, + } + } + elseif label.Type == "Stack" then -- Read from stack + genOperands[i] = { Constant = genOperands[i].PointerToLabel.StackOffset } + elseif label.Type == "Pointer" then -- Pointer value + genOperands[i] = { Constant = {{ Type = self.TOKEN.IDENT, Data = genOperands[i].PointerToLabel.Name, Position = leaf.CurrentPosition }} } + end + end + + -- Make register operand temporary if requested + if genOperands[i].ForceTemporary then + local initReg = genOperands[i].Register + genOperands[i].ForceTemporary = false + + if self.RegisterBusy[initReg] then + local freeReg = self:FreeRegister() + self.RegisterBusy[initReg] = false + + local pushLeaf = self:NewLeaf(leaf) + pushLeaf.Opcode = "push" + pushLeaf.Operands[1] = { Register = initReg } + self:GenerateLeaf(pushLeaf) + + local movLeaf = self:NewLeaf(leaf) + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { Register = freeReg, Temporary = true } + movLeaf.Operands[2] = { Register = initReg } + self.RegisterBusy[freeReg] = true + + local addrReg,isTemp = self:GenerateLeaf(movLeaf,true) + genOperands[i] = { Register = addrReg, Temporary = isTemp } + self.RegisterBusy[addrReg] = isTemp + + local popLeaf = self:NewLeaf(leaf) + popLeaf.Opcode = "pop" + popLeaf.Operands[1] = { Register = initReg } + self:GenerateLeaf(popLeaf) + + self.RegisterBusy[initReg] = true + else + genOperands[i].Temporary = true + self.RegisterBusy[initReg] = true + end + end + end + + i = i - 1 + end + + -- Result to return (if needResult is true) + local destRegister,isDestTemp + + if opcodeWritesFirstOperand then + -- Apply hack for trigonometric operations which look like "FSIN EAX,EAX" instead of "FSIN EAX" + if (#leaf.Operands > 1) and (genOperands[2].TrigonometryHack) then + genOperands[2] = genOperands[1] + end + + -- Are we trying to operate on a value which is busy or must not be changed? (MOV busyReg,<...>) + -- But if register is temporary, lets just re-assign it + if (not leaf.ExplictAssign) and + (genOperands[1].Register) and (not genOperands[1].Temporary) and + ((self.RegisterBusy[genOperands[1].Register] == true) or + (self.RegisterBusy[genOperands[1].Register] == nil)) then + self:MakeFirstOperandTemporary(genOperands) + end + + -- Check if we are trying to do "MOV VAR,<...>" when VAR is a stack one (change to SSTACK instead) + if (genOperands[1].Stack) and (leaf.Opcode == "mov") then + -- MOV STK(10),123 -> SSTACK EBP:10,123 + leaf.Opcode = "sstack" + if tonumber(genOperands[1].Stack) then + genOperands[1].Constant = genOperands[1].Stack + genOperands[1].Register = nil + else + local offsetReg,isTemp = self:GenerateLeaf(genOperands[1].Stack,true) + genOperands[1] = { Register = offsetReg, Temporary = isTemp } + self.RegisterBusy[offsetReg] = isTemp + end + genOperands[1].Segment = 16 + genOperands[1].Stack = nil + end + + -- Check if we are trying to do "INC VAR" when VAR is a stack, explict one + if leaf.ExplictAssign and (genOperands[1].Stack) then + -- INC STK(10) -> INC STK(10); SSTACK EBP:10,VAR + local sstackLeaf = self:NewLeaf(leaf) + sstackLeaf.Opcode = "sstack" + if tonumber(genOperands[1].Stack) then + sstackLeaf.Operands[1] = { Constant = genOperands[1].Stack } + else + local offsetReg,isTemp = self:GenerateLeaf(genOperands[1].Stack,true) + sstackLeaf.Operands[1] = { Register = offsetReg, Temporary = isTemp } + self.RegisterBusy[offsetReg] = isTemp + end + + genOperands[1].Stack = nil + sstackLeaf.Operands[1].Segment = 16 + sstackLeaf.Operands[2] = genOperands[1] + leaf.NextLeaf = sstackLeaf + end + + -- Are we trying to do "ADD VAR,<...>" when VAR is a stack one? + -- At this point "VAR" is already read into a register, so generate this leaf, and then + -- change it to sstack + if (genOperands[1].Stack) and (genOperands[1].Register) then + local tempReg = genOperands[1].Register + local stackOffset = genOperands[1].Stack + + -- Generate proper opcode for the opepration + local operationLeaf = self:NewLeaf(leaf) + operationLeaf.Opcode = leaf.Opcode + operationLeaf.Operands[1] = { + Register = tempReg, + Temporary = true + } + self.RegisterBusy[tempReg] = true + if #leaf.Operands > 1 then + -- Generate second operand. It's already read from stack into a temp register, + -- so remove the "stack" marker + operationLeaf.Operands[2] = genOperands[2] + operationLeaf.Operands[2].Stack = nil + end + self:GenerateLeaf(operationLeaf) + + -- Turn this operation into SSTACK + leaf.Opcode = "sstack" + genOperands[2] = { + Register = tempReg, + Temporary = true + } + genOperands[1] = { + Constant = stackOffset, + Segment = 16 + } + self.RegisterBusy[tempReg] = true + end + + -- If we really need result, then make sure it lies in some register + if needResult then + if genOperands[1].Register and (not leaf.ExplictAssign) then + -- If operand is already a register, just return it + -- (that is, unless we want an explict assign operation on it) + destRegister = genOperands[1].Register + isDestTemp = genOperands[1].Temporary + else + -- Otherwise we will need to copy this result into a register + if leaf.Opcode == "sstack" then + -- turn SSTACK into MOV + destRegister = self:FreeRegister() + leaf.Opcode = "mov" + genOperands[1] = { Register = destRegister, Temporary = true } --FIXME + isDestTemp = true + + -- This happens when there is a code tree like this: + -- cmp + -- 0 + -- add + -- stack 0 + -- stack 1 + else + if leaf.ExplictAssign then -- Perform explict assign and copy to temp register anyway + if leaf.ReturnAfterAssign then + -- HACK: generate opcode before the MOV + self:GenerateOpcode(leaf,genOperands) + leaf.Opcode = nil + + local movLeaf = self:NewLeaf(leaf) + destRegister = self:FreeRegister() + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { + Register = destRegister + } + movLeaf.Operands[2] = genOperands[1] + self:GenerateLeaf(movLeaf) + self.RegisterBusy[destRegister] = true + isDestTemp = true + else + local movLeaf = self:NewLeaf(leaf) + destRegister = self:FreeRegister() + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { + Register = destRegister + } + movLeaf.Operands[2] = genOperands[1] + self:GenerateLeaf(movLeaf) + self.RegisterBusy[destRegister] = true + isDestTemp = true + end + else -- Just make it temporary + destRegister = self:MakeFirstOperandTemporary(genOperands) + isDestTemp = true + end + end + end + end + end + + -- Generate the opcode, unless it's a "MOV REG,REG" (happens with RETURN token) + if (not ((leaf.Opcode == "mov") and genOperands[1].Register and (genOperands[1].Register == genOperands[2].Register))) and + (leaf.Opcode) then + self:GenerateOpcode(leaf,genOperands) + end + + -- Generate next leaf too, if required + if leaf.NextLeaf then + self:GenerateLeaf(leaf.NextLeaf,false) + end + + -- Reset all temporary registers used in the expression + for i=1,#genOperands do + if genOperands[i].Temporary then + if genOperands[i].Register then + self.RegisterBusy[genOperands[i].Register] = false + elseif genOperands[i].MemoryRegister then + self.RegisterBusy[genOperands[i].MemoryRegister] = false + else + self:Error("Internal error 379",leaf) + end + end + end + +-- for k,v in pairs(initTempRegisters) do +-- self.RegisterBusy[k] = v +-- if not (isDestTemp and (destRegister == k)) then +-- if self.RegisterBusy[k] ~= v then +-- print("Internal error 564: register mismatch "..k.." ~= "..tostring(v).." @ "..leaf.Opcode,leaf) +-- end +-- end +-- end + + return destRegister,isDestTemp +end + + +-------------------------------------------------------------------------------- +-- Generate opcode (writes it to the generated code list) +function HCOMP:GenerateOpcode(leaf,operands) +-- local registerBusyHint = {} +-- for k,v in pairs(self.RegisterBusy) do registerBusyHint[k] = v end +-- RegisterBusyHint = registerBusyHint, -- Hint on temporary registers for the optimizer + +-- local rstr = "" +-- for i=1,8 do if self.RegisterBusy[i] then rstr = rstr.."1" else rstr = rstr.."0" end end +-- print(leaf.Opcode,rstr,leaf.CurrentPosition.Line) + + table.insert(self.GeneratedCode,{ + Opcode = leaf.Opcode, + Operands = operands, + Comment = leaf.Comment, + CurrentPosition = leaf.CurrentPosition, + }) +end + +-- Generate other marker +function HCOMP:GenerateMarker(leaf) + table.insert(self.GeneratedCode,{ + ZeroPadding = leaf.ZeroPadding, + Data = leaf.Data, + Label = leaf.Label, + Comment = leaf.Comment, + + SetWritePointer = leaf.SetWritePointer, + SetPointerOffset = leaf.SetPointerOffset, + CurrentPosition = leaf.CurrentPosition, + }) +end + + +-------------------------------------------------------------------------------- +-- Generate leaf (called by the stage, it generates special leaves too) +function HCOMP:StageGenerateLeaf(leaf) + if self.Settings.OutputCodeTree then self:PrintLeaf(leaf) end + + if self.Settings.NoUnreferencedLeaves == true then + if leaf.ParentLabel and (leaf.ParentLabel.Referenced == false) then + -- Do not generate leafs that are parented to unreferenced labels + return false + end + end + + if (leaf.Opcode == "DATA") or + (leaf.Opcode == "LABEL") or + (leaf.Opcode == "MARKER") then + self:GenerateMarker(leaf) + return false + else + -- Make sure it never attempts to use ESP/EBP, mark them always busy + self.RegisterBusy = { false,false,false,false,false,false,true,true } + self.RegisterStackOffset = {} + + if leaf.BusyRegisters then + for k,v in pairs(leaf.BusyRegisters) do + self.RegisterBusy[k] = v + end + end + + self:GenerateLeaf(leaf) + + return false + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_compiler.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_compiler.lua new file mode 100644 index 0000000..b1336fe --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_compiler.lua @@ -0,0 +1,872 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- This is a high-level assembly language compiler, based on ZASM2 and C syntax. +-- +-- I tried to make the compiler as understandable as possible, but you will need +-- to read the source files in this order to understand internal workings of +-- the compiler. Each of these files performs own task, and is independant of others. +-- +-- hc_opcodes: lists all opcodes that are recognized by the compiler +-- hc_compiler: compiler initialization +-- error reporting +-- parser functions +-- hc_preprocess: preprocessor macro parsing +-- hc_tokenizer: turns source code into tokens +-- hc_codetree: unwraps code tree into generated code +-- hc_output: resolves labels and generates +-- hc_syntax: parses language syntax +-- hc_expression: parses expressions +-- hc_optimize: optimizes the resulting code +-------------------------------------------------------------------------------- +HCOMP = {} + + + + +-------------------------------------------------------------------------------- +-- Files required by the compiler +include("wire/client/hlzasm/hc_opcodes.lua") +include("wire/client/hlzasm/hc_expression.lua") +include("wire/client/hlzasm/hc_preprocess.lua") +include("wire/client/hlzasm/hc_syntax.lua") +include("wire/client/hlzasm/hc_codetree.lua") +include("wire/client/hlzasm/hc_optimize.lua") +include("wire/client/hlzasm/hc_output.lua") +include("wire/client/hlzasm/hc_tokenizer.lua") + + + + + +-------------------------------------------------------------------------------- +-- Formats the prefix according to one of the three possible ways to raise error +function HCOMP:formatPrefix(param1,param2,param3) + local line,col,file + if param2 then -- Specify line/col/file directly + line = param1 + col = param2 + file = param3 + elseif param1 then -- Specify parameter by block + if param1.CurrentPosition then + line = param1.CurrentPosition.Line + col = param1.CurrentPosition.Col + file = param1.CurrentPosition.File + end + else -- Get position from parser + local currentPosition = self:CurrentSourcePosition() + line = currentPosition.Line + col = currentPosition.Col + file = currentPosition.File + end + + if (not file) or (not line) or (not col) then + error("[global 1:1] Internal error 048") + end + + -- Format prefix for reporting warnings/errors + return "["..file.." "..line..":"..col.."]",file,line,col +end + + + + +-- Display an error message. There are three ways to call it: +-- +-- Error(msg) +-- Raise an error when parsing code +-- Error(msg,block) +-- Raise an error when generating or outputting block +-- Error(msg,line,col,filename) +-- Raise an error when preprocessing +function HCOMP:Error(msg,param1,param2,param3) + local prefix,file,line,col = self:formatPrefix(param1,param2,param3) + self.ErrorMessage = prefix..": "..msg + self.ErrorPosition = { Line = line, Col = col, File = file } + error(self.ErrorMessage) +end + + + + +-- Display a warning message +-- +-- Same rules for calling as for Error() +function HCOMP:Warning(msg,param1,param2,param3) + print(self:formatPrefix(param1,param2,param3)..": Warning: "..msg) +end + + + + +-- Print a line to specific file +function HCOMP:PrintLine(file,...) + if self.Settings.OutputToFile then + local ffile = self.Settings[file] or file + local outputString = "" + local argc = select("#",...) + for i=1,argc do + if i < argc then + outputString = outputString..select(i,...).."\t" + else + outputString = outputString..select(i,...) + end + end + outputString = outputString.."\n" + self.OutputText[ffile] = (self.OutputText[ffile] or "") .. outputString + + -- Forced garbage collection for very large output + if #self.OutputText[ffile] > 96000 then + collectgarbage("step") + end + else + print(...) + end +end + + + + +-------------------------------------------------------------------------------- +-- Emit a code byte to the output stream +function HCOMP:WriteByte(byte,block) + if self.WriteByteCallback then + self.WriteByteCallback(self.WriteByteCaller,self.WritePointer,byte) + end + + if not byte then error("[global 1:1] Internal error 108") end + + -- Remember debug data + if block.CurrentPosition then + local currentPositionKey = block.CurrentPosition.Line..":"..block.CurrentPosition.File + self.DebugInfo.PositionByPointer[self.WritePointer] = block.CurrentPosition + if self.DebugInfo.PointersByLine[currentPositionKey] then + self.DebugInfo.PointersByLine[currentPositionKey][2] = self.WritePointer + else + self.DebugInfo.PointersByLine[currentPositionKey] = { self.WritePointer, self.WritePointer } + end + else + self.DebugInfo.PositionByPointer[self.WritePointer] = { Line = 0, Col = 0, File = "undefined" } + end + + -- Output binary listing + if self.Settings.OutputBinaryListing then + if not self.CurrentBinaryListingLine then + self.CurrentBinaryListingLine = "db " + end + self.CurrentBinaryListingLine = self.CurrentBinaryListingLine .. byte .. "," + if #self.CurrentBinaryListingLine > 60 then + self:PrintLine("binlist",string.sub(self.CurrentBinaryListingLine,1,#self.CurrentBinaryListingLine-1)) + self.CurrentBinaryListingLine = nil + end + end + self.WritePointer = self.WritePointer + 1 +end + + + + +-------------------------------------------------------------------------------- +-- Start compiling the sourcecode. File name will define working directory +-- +-- Will call writeByteCallback(writeByteCaller,Address,Value) to output a byte +function HCOMP:StartCompile(sourceCode,fileName,writeByteCallback,writeByteCaller) + -- Remember callbacks for the writing functions + self.WriteByteCallback = writeByteCallback + self.WriteByteCaller = writeByteCaller + + -- Set the working directory + self.FileName = string.sub(fileName,string.find(fileName,"\\$") or 1) + if string.GetPathFromFilename then + local filePath = string.GetPathFromFilename(fileName) + self.WorkingDir = string.sub(filePath,(string.find(string.lower(filePath),"chip") or -4)+5) + else + self.WorkingDir = "" + end + + -- Initialize compiler settings + self.Settings = {} + + -- Internal settings + self.Settings.CurrentLanguage = "HLZASM" -- ZASM2 + self.Settings.CurrentPlatform = "CPU" + self.Settings.MagicValue = -700500 -- This magic value will appear in invalid output code + self.Settings.OptimizeLevel = 0 -- 0: none, 1: low, 2: high; high optimize level might mangle code for perfomance + + -- Verbosity settings + self.Settings.OutputCodeTree = false -- Output code tree for the source + self.Settings.OutputResolveListing = false -- Output code listing for resolve stage + self.Settings.OutputFinalListing = false -- Output code listing for final stage + self.Settings.OutputTokenListing = false -- Output tokenized code + self.Settings.OutputBinaryListing = false -- Output final binary as listing + self.Settings.OutputDebugListing = false -- Output debug data as listing + self.Settings.OutputToFile = false -- Output listings to files instead of console + self.Settings.OutputOffsetsInListing = true -- Output binary offsets in listings + self.Settings.OutputLabelsInListing = true -- Output labels in final listing + self.Settings.GenerateComments = true -- Generates comments in output listing + + -- Code generation settings + self.Settings.FixedSizeOutput = false -- Output fixed-size instructions + self.Settings.SeparateDataSegment = false -- Puts all variables into separate data segment + self.Settings.GenerateLibrary = false -- Generate precompiled library + self.Settings.AlwaysEnterLeave = false -- Always generate the enter/leave blocks + self.Settings.NoUnreferencedLeaves = false -- Dont generate functions, variables that are not referenced + self.Settings.DataSegmentOffset = 0 -- Data segment offset for separate data segment + + -- Search paths + self.SearchPaths = { + "lib", + "inc" + } + + -- Prepare parser + self.Stage = 1 + self.Tokens = {} + self.Code = {{ Text = sourceCode, Line = 1, Col = 1, File = self.FileName, NextCharPos = 1 }} + + -- Structs + self.Structs = {} + self.StructSize = {} + + -- Prepare debug information + self.DebugInfo = {} + self.DebugInfo.Labels = {} + self.DebugInfo.PositionByPointer = {} + self.DebugInfo.PointersByLine = {} + + -- Exported function list (library generation) + self.ExportedSymbols = {} + self.LabelLookup = {} + self.LabelLookupCounter = 0 + + -- All functions defined so far + self.Functions = {} + + -- All macros defined so far + self.Defines = {} + self.Defines["__LINE__"] = 0 + self.Defines["__FILE__"] = "" + self.IFDEFLevel = {} + + -- Output text + self.OutputText = {} +end + + + + +-------------------------------------------------------------------------------- +-- Call this until the function returns false (it returns false when there +-- is nothing more to do) +function HCOMP:Compile() + return self:UnprotectedCompile() +-- local status,result = pcall(self.UnprotectedCompile,self) +-- if not status then +-- print("ERROR: "..result) +-- else +-- return result +-- end +end + + + + +-- Unprotected function that does the actual compiling +function HCOMP:UnprotectedCompile() + if self.Stage == 1 then + -- Tokenize stage + -- + -- At this stage sourcecode is converted to list of tokens + + local stageResult = self:Tokenize() + if not stageResult then + -- Output tokens if required + if self.Settings.OutputTokenListing then + for k,v in pairs(self.Tokens) do + self:PrintLine("toklist",k,self.TOKEN_NAME[v.Type],v.Data,v.Position.File.." "..v.Position.Line..":"..v.Position.Col) + end + end + + -- Clean up preprocessor variables + self.Code = nil + self.SourceCode = nil + self.Defines = nil + + -- Go to the first token + self.CurrentToken = 1 + + -- Sest up variables for code parsing + self.CodeTree = {} -- Code tree that will be built (see hc_codetree.lua) + self.GlobalLabels = {} -- Table of globally defined labels + self.LabelCounter = 0 -- Counter for internal labels (for IF, CASE, etc) + self.UserRegisters = {} -- Registers used by user in global scope + self.BlockDepth = 0 -- Nesting depth of the {..} block + self.GlobalStringTable = {} -- Global table for string leaves + + -- Reset parsing the blocks + self.SpecialLeaf = nil + self.LocalLabels = nil + self.StackPointer = nil + self.TokenData = nil + self.StringsTable = nil + self.ParameterPointer = nil + self.BlockType = nil + + -- Set special labels + self:SetSpecialLabels() + + self.Stage = 2 + end + return true + elseif self.Stage == 2 then + -- Parse code stage + -- + -- At this stage code is parsed, and code tree is built + + local stageResult = self:Statement() + if not stageResult then + -- Index for code tree leaves + self.CurrentLeafIndex = 1 + + -- Create storage for generated code + self.GeneratedCode = {} + self.Stage = 3 + end + return true + elseif self.Stage == 3 then + -- Generate stage + -- + -- This will generate code based on the code tree that was created by the + -- parser + + local stageResult = false + if self.CodeTree[self.CurrentLeafIndex] and self:StageGenerateLeaf(self.CodeTree[self.CurrentLeafIndex]) then + stageResult = true + end + + if not stageResult then + self.CurrentLeafIndex = self.CurrentLeafIndex + 1 + if not self.CodeTree[self.CurrentLeafIndex] then + self.Stage = 4 + end + end + return true + elseif self.Stage == 4 then + -- Code optimize stage + -- + -- At this stage code is optimized for the known patterns + + local stageResult + + -- Do not perform this stage if optimization is set to 0 + if self.Settings.OptimizeLevel == 0 then + stageResult = false + else + stageResult = self:OptimizeCode() + end + if not stageResult then + -- Initialize iteration through generated code + self.CurrentBlockIndex = 1 + self.Stage = 5 + + -- Set write pointers + self.PointerOffset = 0 + self.WritePointer = 0 + self.DataPointer = 0 + end + return true + elseif self.Stage == 5 then + -- Resolve stage + -- + -- This will attempt to output the code without actually writing it. + -- All the labels will be resolved at this stage + + local stageResult = false + if self.GeneratedCode[self.CurrentBlockIndex] and self:Resolve(self.GeneratedCode[self.CurrentBlockIndex]) then + stageResult = true + end + if not stageResult then + self.CurrentBlockIndex = self.CurrentBlockIndex + 1 + if not self.GeneratedCode[self.CurrentBlockIndex] then + -- Set special labels + self:SetSpecialLabels() + + -- Initialize iteration through generated code + self.CurrentBlockIndex = 1 + self.Stage = 6 + + -- Set write pointers + self.PointerOffset = 0 + self.WritePointer = 0 + self.DataPointer = 0 + end + end + + return true + elseif self.Stage == 6 then + -- Output stage + -- + -- The code will be output as binary at this stage + + local stageResult = false + if self.GeneratedCode[self.CurrentBlockIndex] and self:Output(self.GeneratedCode[self.CurrentBlockIndex]) then + stageResult = true + end + if not stageResult then + self.CurrentBlockIndex = self.CurrentBlockIndex + 1 + if not self.GeneratedCode[self.CurrentBlockIndex] then + self.Stage = 7 + + -- Generate labels for the debugger + for labelName,labelData in pairs(self.GlobalLabels) do + if string.sub(labelName,1,2) ~= "__" then + if labelData.DebugAsVector then + self.DebugInfo.Labels[string.upper(labelData.Name)] = { + Vector = labelData.Value, + Size = labelData.DebugAsVector -- vector size + } + elseif (labelData.Type == "Variable") or (labelData.DebugAsVariable) then + self.DebugInfo.Labels[string.upper(labelData.Name)] = { + Offset = labelData.Value + } + elseif labelData.Type == "Pointer" then + self.DebugInfo.Labels[string.upper(labelData.Name)] = { + Pointer = labelData.Value + } + end + end + end + + -- Write binary output + if self.Settings.OutputBinaryListing then + if self.CurrentBinaryListingLine then + self:PrintLine("binlist",string.sub(self.CurrentBinaryListingLine,1,#self.CurrentBinaryListingLine-1)) + self.CurrentBinaryListingLine = nil + end + end + + -- Write the debug data + if self.Settings.OutputDebugListing then + self:PrintLine("dbglist","Labels:") + self:PrintLine("dbglist","Name","Offset","Type") + for k,v in pairs(self.DebugInfo.Labels) do + if v.Offset then self:PrintLine("dbglist",k,v.Offset,"MEMORY") + elseif v.Pointer then self:PrintLine("dbglist",k,v.Pointer,"POINTER") + elseif v.StackOffset then self:PrintLine("dbglist",k,v.StackOffset,"STACK") + end + end + + self:PrintLine("dbglist","Position by pointer:") + self:PrintLine("dbglist","Pointer","Line","Column","File") + for k,v in pairs(self.DebugInfo.PositionByPointer) do + self:PrintLine("dbglist",k,v.Line,v.Col,v.File) + end + + self:PrintLine("dbglist","Pointers by line:") + self:PrintLine("dbglist","Line","Start","End") + for k,v in pairs(self.DebugInfo.PointersByLine) do + self:PrintLine("dbglist",k,v[1],v[2]) + end + end + + -- Write header file for library + if self.Settings.GenerateLibrary then + if self.DBString then + self:PrintLine("lib",self.DBString) + self.DBString = nil + end + + self:PrintLine("lib","") + + for symName,symData in pairs(self.ExportedSymbols) do + local printText = "#pragma export " + if symData.FunctionName then + printText = printText .. string.lower(self.TOKEN_TEXT["TYPE"][2][symData.ReturnType]) + printText = printText .. string.rep("*",symData.ReturnPtrLevel) + + printText = printText .. " " .. symData.FunctionName .. "(" + for varIdx,varData in pairs(symData.Parameters) do + printText = printText .. string.lower(self.TOKEN_TEXT["TYPE"][2][varData.Type]) + printText = printText .. string.rep("*",varData.PtrLevel) + printText = printText .. " " .. varData.Name + if varIdx < #symData.Parameters then + printText = printText .. ", " + end + end + printText = printText .. ")" + end + self:PrintLine("lib",printText) + end + end + + -- Clean up + self.LabelLookup = nil + self.LabelLookupCounter = nil + + -- Close all output files + for k,v in pairs(self.OutputText) do self:SaveFile(self.WorkingDir..k..".txt",v) end + end + end + + return true + else + -- Reset compiler to start point and return false when work is done + self.Stage = 1 + return false + end +end + + + + +-------------------------------------------------------------------------------- +-- Get label (local or global one). Second result returns true if label is new +-- Third result returns if label was referenced before +function HCOMP:GetLabel(name,declareLocalVariable) + local trueName = string.upper(name) + + -- Should we treat unknown variables as local label definition + -- This assumes self.LocalLabel is defined at this point + if declareLocalVariable then + if self.LocalLabels[trueName] then + return self.LocalLabels[trueName],not self.LocalLabels[trueName].Defined + else + self.LocalLabels[trueName] = { + Type = "Unknown", + Name = name, + Position = self:CurrentSourcePosition(), + } + return self.LocalLabels[trueName],true + end + else + -- If in local mode then try to resolve label amongst the local ones first + if self.LocalLabels and self.LocalLabels[trueName] then + return self.LocalLabels[trueName],not self.LocalLabels[trueName].Defined + elseif self.GlobalLabels[trueName] then + local wasReferenced = self.GlobalLabels[trueName].Referenced + self.GlobalLabels[trueName].Referenced = true + return self.GlobalLabels[trueName],not self.GlobalLabels[trueName].Defined,wasReferenced + else + self.GlobalLabels[trueName] = { + Type = "Unknown", + Name = name, + Position = self:CurrentSourcePosition(), + Referenced = true, + } + return self.GlobalLabels[trueName],true,false + end + end +end + + + +-- Define a new label +function HCOMP:DefineLabel(name,declareLocalVariable) + local label,isNew,wasReferenced = self:GetLabel(name,declareLocalVariable) + if not isNew then -- + if label.Position then + self:Error("Variable redefined: \""..name.."\", previously defined at ".. + self:formatPrefix(label.Position.Line,label.Position.Col,label.Position.File)) + else + self:Error("Variable redefined: \""..name.."\"") + end + end + + -- Clear referenced flag if required + label.Referenced = false or wasReferenced + return label +end + + + +-- Redefine label under new name +function HCOMP:RedefineLabel(oldName,newName) + local label = self:GetLabel(oldName) + self.GlobalLabels[string.upper(label.Name)] = nil + + label.Name = newName + local prevLabel = self.GlobalLabels[string.upper(label.Name)] + if prevLabel then + if prevLabel.Position then + self:Error("Variable redefined: \""..newName.."\", previously defined at ".. + self:formatPrefix(prevLabel.Position.Line,prevLabel.Position.Col,prevLabel.Position.File)) + else + self:Error("Variable redefined: \""..newName.."\"") + end + else + self.GlobalLabels[string.upper(label.Name)] = label + end +end + + + + +-- Get a new temporary/internal label for use in complex language structures +function HCOMP:GetTempLabel() + local labelName = "__"..self.LabelCounter + self.GlobalLabels[labelName] = { + Type = "Unknown", + Name = labelName, + } + + self.LabelCounter = self.LabelCounter + 1 + return self.GlobalLabels[labelName] +end + + + + +-- Set a label to specific value (used for special labels) +function HCOMP:SetLabel(name,value) + local label = self:GetLabel(name) + label.Type = "Pointer" + label.Value = value +end + + + + +-- Set special labels +function HCOMP:SetSpecialLabels() + -- Set special labels + self:DefineLabel("__PTR__").Type = "Pointer" + self:SetLabel("programsize",self.WritePointer) + self:SetLabel("__PROGRAMSIZE__",self.WritePointer) + self:SetLabel("__DATE_YEAR__", tonumber(os.date("%Y"))) + self:SetLabel("__DATE_MONTH__", tonumber(os.date("%m"))) + self:SetLabel("__DATE_DAY__", tonumber(os.date("%d"))) + self:SetLabel("__DATE_HOUR__", tonumber(os.date("%H"))) + self:SetLabel("__DATE_MINUTE__",tonumber(os.date("%M"))) + self:SetLabel("__DATE_SECOND__",tonumber(os.date("%S"))) + + if self.Settings.CurrentPlatform == "GPU" then + self:SetLabel("regClk", 65535) + self:SetLabel("regReset", 65534) + self:SetLabel("regHWClear", 65533) + self:SetLabel("regVertexMode", 65532) + self:SetLabel("regHalt", 65531) + self:SetLabel("regRAMReset", 65530) + self:SetLabel("regAsyncReset", 65529) + self:SetLabel("regAsyncClk", 65528) + self:SetLabel("regAsyncFreq", 65527) + self:SetLabel("regIndex", 65526) + + self:SetLabel("regHScale", 65525) + self:SetLabel("regVScale", 65524) + self:SetLabel("regHWScale", 65523) + self:SetLabel("regRotation", 65522) + self:SetLabel("regTexSize", 65521) + self:SetLabel("regTexDataPtr", 65520) + self:SetLabel("regTexDataSz", 65519) + self:SetLabel("regRasterQ", 65518) + self:SetLabel("regTexBuffer", 65517) + + + self:SetLabel("regWidth", 65515) + self:SetLabel("regHeight", 65514) + self:SetLabel("regRatio", 65513) + self:SetLabel("regParamList", 65512) + + self:SetLabel("regCursorX", 65505) + self:SetLabel("regCursorY", 65504) + self:SetLabel("regCursor", 65503) + self:SetLabel("regCursorButtons", 65502) + + self:SetLabel("regBrightnessW", 65495) + self:SetLabel("regBrightnessR", 65494) + self:SetLabel("regBrightnessG", 65493) + self:SetLabel("regBrightnessB", 65492) + self:SetLabel("regContrastW", 65491) + self:SetLabel("regContrastR", 65490) + self:SetLabel("regContrastG", 65489) + self:SetLabel("regContrastB", 65488) + + self:SetLabel("regCircleQuality", 65485) + self:SetLabel("regOffsetX", 65484) + self:SetLabel("regOffsetY", 65483) + self:SetLabel("regRotation", 65482) + self:SetLabel("regScale", 65481) + self:SetLabel("regCenterX", 65480) + self:SetLabel("regCenterY", 65479) + self:SetLabel("regCircleStart", 65478) + self:SetLabel("regCircleEnd", 65477) + self:SetLabel("regLineWidth", 65476) + self:SetLabel("regScaleX", 65475) + self:SetLabel("regScaleY", 65474) + self:SetLabel("regFontAlign", 65473) + self:SetLabel("regFontHalign", 65473) + self:SetLabel("regZOffset", 65472) + self:SetLabel("regFontValign", 65471) + self:SetLabel("regCullDistance", 65470) + self:SetLabel("regCullMode", 65469) + self:SetLabel("regLightMode", 65468) + self:SetLabel("regVertexArray", 65467) + self:SetLabel("regTexRotation", 65466) + self:SetLabel("regTexScale", 65465) + self:SetLabel("regTexCenterU", 65464) + self:SetLabel("regTexCenterV", 65463) + self:SetLabel("regTexOffsetU", 65462) + self:SetLabel("regTexOffsetV", 65461) + end +end + + + + +-------------------------------------------------------------------------------- +-- Converts integer to binary representation +function HCOMP:IntegerToBinary(n) + -- Check sign + n = math.floor(n or 0) + if n < 0 then + local bits = self:IntegerToBinary(2^48 + n) + bits[48-1] = 1 + return bits + end + + -- Convert to binary + local bits = {} + local cnt = 0 + while (n > 0) and (cnt < 48) do + local bit = n % 2 + bits[cnt] = bit + + n = (n-bit)/2 + cnt = cnt + 1 + end + + -- Fill in missing zero bits + while cnt < 48 do + bits[cnt] = 0 + cnt = cnt + 1 + end + + return bits +end + + + + +-------------------------------------------------------------------------------- +-- Converts binary representation back to integer +function HCOMP:BinaryToInteger(bits) + local n = #bits + local result = 0 + + -- Convert to integer + for i = 0, 48-2 do + result = result + (bits[i] or 0) * (2 ^ i) + end + + -- Add sign + if bits[48-1] == 1 then + return -2^(48-1)+result + else + return result + end +end + + + + +-------------------------------------------------------------------------------- +-- Binary OR +function HCOMP:BinaryOr(m,n) + local bits_m = self:IntegerToBinary(m) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, 48-1 do + bits[i] = math.min(1,bits_m[i]+bits_n[i]) + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary AND +function HCOMP:BinaryAnd(m,n) + local bits_m = self:IntegerToBinary(m) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, 48-1 do + bits[i] = bits_m[i]*bits_n[i] + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary NOT +function HCOMP:BinaryNot(n) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, 48-1 do + bits[i] = 1-bits_n[i] + end + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary XOR +function HCOMP:BinaryXor(m,n) + local bits_m = self:IntegerToBinary(m) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, 48-1 do + bits[i] = (bits_m[i]+bits_n[i]) % 2 + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary shift right +function HCOMP:BinarySHR(n,cnt) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + local rslt = #bits_n + for i = 0, 48-cnt-1 do + bits[i] = bits_n[i+cnt] + end + for i = 48-cnt,rslt-1 do + bits[i] = 0 + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary shift left +function HCOMP:BinarySHL(n,cnt) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = cnt,48-1 do + bits[i] = bits_n[i-cnt] + end + for i = 0,cnt-1 do + bits[i] = 0 + end + + return self:BinaryToInteger(bits) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_expression.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_expression.lua new file mode 100644 index 0000000..a6b0773 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_expression.lua @@ -0,0 +1,955 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- Dynamic and constant expression generator +-------------------------------------------------------------------------------- + + + + +-------------------------------------------------------------------------------- +-- Returns leaf for an expression on specific level +-- This will also test whether the expression is constant or not +function HCOMP:Expression_LevelLeaf(level) + local levelLeaf + local levelConst,levelValue,levelExpr = self:ConstantExpression(false,level) + + if levelConst then + if levelExpr + then levelLeaf = levelExpr -- Expression that has to be recalculated later + else levelLeaf = levelValue -- Numeric value + end + + return { Constant = levelLeaf, CurrentPosition = self:CurrentSourcePosition() } + else + return self["Expression_Level"..level](self) + end +end + + + +-- generate explict increment/decrement opcode +function HCOMP:Expression_ExplictIncDec(opcode,label,returnAfter) + local operationLeaf = self:NewLeaf() + operationLeaf.Opcode = opcode + + if tonumber(label) then --returnBefore + operationLeaf.Operands[1] = { Register = label } + elseif not label.Type then + operationLeaf.Operands[1] = label + else + if label.Type == "Variable" then + operationLeaf.Operands[1] = { Memory = label } + elseif label.Type == "Unknown" then + operationLeaf.Operands[1] = { UnknownOperationByLabel = label } + elseif label.Type == "Stack" then + operationLeaf.Operands[1] = { Stack = label.StackOffset } + elseif label.Type == "Register" then + operationLeaf.Operands[1] = { Register = label.Value } + end + end + + -- Mark this leaf as an explict assign operation + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = returnAfter + + return operationLeaf +end + + + +-- generate code for function call +function HCOMP:Expression_FunctionCall(label) local TOKEN = self.TOKEN + -- Parse arguments and push them to stack + local argumentCount = 0 + local argumentExpression = {} + while not (self:PeekToken() == TOKEN.RPAREN) do + -- Parse argument + argumentExpression[#argumentExpression+1] = self:Expression() + + -- Go to next one + argumentCount = argumentCount + 1 + self:MatchToken(TOKEN.COMMA) + end + self:ExpectToken(TOKEN.RPAREN) + + -- Find the function definition + local functionEntry = self.Functions[label.Name] + + -- All leaves that must be generated previously to knowing correct result + local genLeaves = {} + + -- Push arguments to stack in reverse order + for argNo = #argumentExpression,1,-1 do + local pushLeaf = self:NewLeaf() + pushLeaf.Opcode = "push" + pushLeaf.Operands[1] = argumentExpression[argNo] + table.insert(genLeaves,pushLeaf) + + if functionEntry then + if functionEntry.Parameters[argNo] then + pushLeaf.Comment = label.Name.." arg #"..argNo.." (".. + string.lower( + self.TOKEN_TEXT["TYPE"][2][functionEntry.Parameters[argNo].Type] or + functionEntry.Parameters[argNo].Type).. + string.rep("*",functionEntry.Parameters[argNo].PtrLevel).. + " ".. + functionEntry.Parameters[argNo].Name..")" + else + pushLeaf.Comment = label.Name.." arg #"..argNo.." (unknown)" + end + end + end + + -- Call function + if functionEntry and functionEntry.InlineCode then + for i=1,#functionEntry.InlineCode do +-- self:AddLeafToTail(functionEntry.InlineCode[i]) + if functionEntry.InlineCode[i].Opcode ~= "LABEL" then + table.insert(genLeaves,functionEntry.InlineCode[i]) + end + end + else + -- Push argument count to stack + local argCountLeaf = self:NewLeaf() + argCountLeaf.Opcode = "mov" + argCountLeaf.ExplictAssign = true + argCountLeaf.Operands[1] = { Register = 3 } -- ECX is the argument count register + argCountLeaf.Operands[2] = { Constant = argumentCount } + table.insert(genLeaves,argCountLeaf) + + local callLeaf = self:NewLeaf() + callLeaf.Opcode = "call" + callLeaf.Comment = label.Name.."(...)" + if label.Type == "Stack" then + callLeaf.Operands[1] = { Stack = label.StackOffset } + else --{ PointerToLabel = label } + callLeaf.Operands[1] = { UnknownOperationByLabel = label } + end + table.insert(genLeaves,callLeaf) + end + + -- Stack cleanup + if argumentCount > 0 then + local stackCleanupLeaf = self:NewLeaf() + stackCleanupLeaf.Opcode = "add" + stackCleanupLeaf.ExplictAssign = true + stackCleanupLeaf.Operands[1] = { Register = 7 } + stackCleanupLeaf.Operands[2] = { Constant = argumentCount } + table.insert(genLeaves,stackCleanupLeaf) + end + + -- Create correct leaf tree + for i=2,#genLeaves do + genLeaves[i].PreviousLeaf = genLeaves[i-1] + end + + -- Return EAX as the return value + return { Register = 1, ForceTemporary = true, PreviousLeaf = genLeaves[#genLeaves] } +end + + + +-- generate code for array access +function HCOMP:Expression_ArrayAccess(label) local TOKEN = self.TOKEN + local operationLeaf + local arrayOffsetLeaf = self:Expression() + self:ExpectToken(TOKEN.RSUBSCR) + + -- Create leaf for calculating address + local addressLeaf = self:NewLeaf() + + if label.Array then -- Parse array access treating label as pointer to array + if label.Type == "Stack" then + if arrayOffsetLeaf.Constant then + addressLeaf = { Constant = label.StackOffset+arrayOffsetLeaf.Constant } + operationLeaf = { Stack = label.StackOffset+arrayOffsetLeaf.Constant } + else + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = { Constant = label.StackOffset } + addressLeaf.Operands[2] = arrayOffsetLeaf + operationLeaf = { Stack = addressLeaf } + end + else + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = arrayOffsetLeaf + addressLeaf.Operands[2] = { PointerToLabel = label } + operationLeaf = { MemoryPointer = addressLeaf } + end + else -- Parse array access treating variable as pointer + if label.Type == "Stack" then + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = { Stack = label.StackOffset } + addressLeaf.Operands[2] = arrayOffsetLeaf + operationLeaf = { MemoryPointer = addressLeaf } + elseif label.Type == "Variable" then + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = arrayOffsetLeaf + addressLeaf.Operands[2] = { Memory = label } + operationLeaf = { MemoryPointer = addressLeaf } + elseif label.Type == "Pointer" then + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = arrayOffsetLeaf + addressLeaf.Operands[2] = { Constant = {{ Type = TOKEN.IDENT, Data = label.Name, Position = self:CurrentSourcePosition() }} } + operationLeaf = { MemoryPointer = addressLeaf } + elseif label.Type == "Register" then + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = arrayOffsetLeaf + addressLeaf.Operands[2] = { Register = label.Value } + operationLeaf = { MemoryPointer = addressLeaf } + else + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = arrayOffsetLeaf + addressLeaf.Operands[2] = { UnknownOperationByLabel = label } + operationLeaf = { MemoryPointer = addressLeaf } + end + end + + if self:MatchToken(TOKEN.INC) then -- reg++ + operationLeaf = self:Expression_ExplictIncDec("inc",operationLeaf) + elseif self:MatchToken(TOKEN.DEC) then -- reg-- + operationLeaf = self:Expression_ExplictIncDec("dec",operationLeaf) + end + + return operationLeaf,addressLeaf +end + + + +-- level3: () or +function HCOMP:Expression_Level3() local TOKEN = self.TOKEN + local negateLeaf,operationLeaf + + + -- Negate value if required + if self:MatchToken(TOKEN.MINUS) then -- "-" + negateLeaf = self:NewLeaf() + negateLeaf.Opcode = "neg" + end + -- Logically negate value if required + if self:MatchToken(TOKEN.NOT) then -- "!" + negateLeaf = self:NewLeaf() + negateLeaf.Opcode = "lneg" + end + + + if self:MatchToken(TOKEN.AND) then -- Parse retrieve pointer operation (&var) + if self:MatchToken(TOKEN.IDENT) then + local label = self:GetLabel(self.TokenData) + + if self:MatchToken(TOKEN.LSUBSCR) then -- Parse array access + local _,addressLeaf = self:Expression_ArrayAccess(label) + if label.Type == "Stack" then + operationLeaf = self:NewLeaf() + operationLeaf.Opcode = "add" + operationLeaf.Operands[1] = { Register = 7, Segment = 2 } -- EBP:SS + operationLeaf.Operands[2] = addressLeaf + else + operationLeaf = addressLeaf + end + else + if label.Type == "Stack" then + if self:MatchToken(TOKEN.LSUBSCR) then -- Pointer to element of an array on stack + local arrayOffsetLeaf = self:Expression() + self:ExpectToken(TOKEN.RSUBSCR) + + -- Create leaf for calculating address + local addressLeaf + if arrayOffsetLeaf.Constant then + addressLeaf = { Constant = label.StackOffset+arrayOffsetLeaf.Constant } + else + addressLeaf = self:NewLeaf() + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = { Constant = label.StackOffset } + addressLeaf.Operands[2] = arrayOffsetLeaf + end + + -- Create leaf that returns pointer to stack + operationLeaf = self:NewLeaf() + operationLeaf.Opcode = "add" + operationLeaf.Operands[1] = { Register = 7, Segment = 2 } -- EBP:SS + operationLeaf.Operands[2] = addressLeaf + else -- Pointer to a stack variable + -- FIXME: check if var is an array + + -- Create leaf that returns pointer to stack + operationLeaf = self:NewLeaf() + operationLeaf.Opcode = "add" + operationLeaf.Operands[1] = { Register = 7, Segment = 2 } -- EBP:SS + operationLeaf.Operands[2] = { Constant = label.StackOffset } + end + else + -- All other pointers must be resolved by constant expression parser + -- If they are not, it's a bug + self:Error("Internal error 085") + end + end + else + self:Error("Identifier expected") + return + end + elseif self:MatchToken(TOKEN.TIMES) then -- Parse memory read operation + local pointerLeaf = self:Expression_LevelLeaf(3) + operationLeaf = { MemoryPointer = pointerLeaf } + elseif self:MatchToken(TOKEN.INC) then -- Parse ++X + local operandLeaf = self:Expression_LevelLeaf(3) + operationLeaf = self:Expression_ExplictIncDec("inc",operandLeaf,true) + elseif self:MatchToken(TOKEN.DEC) then -- Parse --X + local operandLeaf = self:Expression_LevelLeaf(3) + operationLeaf = self:Expression_ExplictIncDec("dec",operandLeaf,true) + elseif self:MatchToken(TOKEN.REGISTER) or self:MatchToken(TOKEN.SEGMENT) then + local register = self.TokenData + if self.TokenType == TOKEN.SEGMENT then register = register + 15 end + + if self:MatchToken(TOKEN.INC) then -- reg++ + operationLeaf = self:Expression_ExplictIncDec("inc",register) + elseif self:MatchToken(TOKEN.DEC) then -- reg-- + operationLeaf = self:Expression_ExplictIncDec("dec",register) + else + operationLeaf = { Register = register } + end + elseif self:MatchToken(TOKEN.IDENT) then + -- Check if variable lies inside structure + local dotPos = string.find(self.TokenData,"[.]") + if dotPos then + local structName = string.sub(self.TokenData,1,dotPos-1) + local memberName = string.sub(self.TokenData,dotPos+1) + local structLabel = self:GetLabel(structName) + if structLabel.Struct then -- Fetch structure member + local structData = self.Structs[structLabel.Struct] + + -- Some error checks + if not structData[memberName] then + self:Error("Undefined structure member: "..memberName) + end + + -- Generate leaf + local addressLeaf = self:NewLeaf() + if structLabel.Type == "Stack" then + if structLabel.PointerToStruct then + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = { Stack = structLabel.StackOffset } + addressLeaf.Operands[2] = { Constant = structData[memberName].Offset } + operationLeaf = { MemoryPointer = addressLeaf } + else + operationLeaf = { Stack = structLabel.StackOffset+structData[memberName].Offset } + end + elseif structLabel.Type == "Variable" then + if structLabel.PointerToStruct then + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = { Constant = structData[memberName].Offset } + addressLeaf.Operands[2] = { Memory = structLabel } + operationLeaf = { MemoryPointer = addressLeaf } + else + addressLeaf.Opcode = "add" + addressLeaf.Operands[1] = { Constant = structData[memberName].Offset } + addressLeaf.Operands[2] = { Constant = + { { Type = self.TOKEN.AND, Position = self:CurrentSourcePosition() }, + { Type = self.TOKEN.IDENT, Data = structLabel.Name, Position = self:CurrentSourcePosition() }, + } + } + operationLeaf = { MemoryPointer = addressLeaf } + end + else + self:Error("Internal error 164") + end + + return operationLeaf + end + end + + -- Try to fetch it normal way + local label = self:GetLabel(self.TokenData) + local forceType = label.ForceType + + if self:MatchToken(TOKEN.INC) then -- Parse var++ + operationLeaf = self:Expression_ExplictIncDec("inc",label) + elseif self:MatchToken(TOKEN.DEC) then -- Parse var-- + operationLeaf = self:Expression_ExplictIncDec("dec",label) + elseif self:MatchToken(TOKEN.LPAREN) then -- Parse a function call + operationLeaf = self:Expression_FunctionCall(label) + elseif self:MatchToken(TOKEN.LSUBSCR) then -- Parse array access + operationLeaf = self:Expression_ArrayAccess(label) + else -- Parse variable access + if label.Type == "Variable" then -- Read from a variable + -- Array variables are resolved as pointers at constant expression stage + operationLeaf = { Memory = label, ForceType = forceType } + elseif label.Type == "Unknown" then -- Read from an unknown variable + operationLeaf = { UnknownOperationByLabel = label, ForceType = forceType } + elseif label.Type == "Stack" then -- Read from stack + if label.Array then + -- Array on stack - return pointer + operationLeaf = self:NewLeaf() + operationLeaf.Opcode = "add" + operationLeaf.Operands[1] = { Register = 7, Segment = 2 } -- EBP:SS + operationLeaf.Operands[2] = { Constant = label.StackOffset } + else + -- Stack variable + operationLeaf = { Stack = label.StackOffset, ForceType = forceType } + end + elseif label.Type == "Register" then + -- Register variable + operationLeaf = { Register = label.Value } + end + end + elseif self:MatchToken(TOKEN.LPAREN) then -- (...) + if self:MatchToken(TOKEN.TYPE) then + local forceType = self.TokenData + operationLeaf = self:Expression_LevelLeaf(3) + operationLeaf.ForceType = forceType + else + operationLeaf = self:Expression_LevelLeaf(0) + end + self:ExpectToken(TOKEN.RPAREN) + end + + if not operationLeaf then + self:Error("Expression expected, got \""..self:PrintTokens(self:GetSavedTokens()).."\"") + return + else + -- Assign sourcecode position to leaf + if not operationLeaf.CurrentPosition then + operationLeaf.CurrentPosition = self:CurrentSourcePosition() + end + + -- Negate the result if required + if negateLeaf then + negateLeaf.Operands[1] = operationLeaf + return negateLeaf + else + return operationLeaf + end + end +end + + +-- level2: * +function HCOMP:Expression_Level2() + local leftLeaf = self:Expression_LevelLeaf(3) + + local token = self:PeekToken() + if (token == self.TOKEN.TIMES) or + (token == self.TOKEN.SLASH) or + (token == self.TOKEN.POWER) or + (token == self.TOKEN.MODULUS) then + self:NextToken() + local rightLeaf = self:Expression_LevelLeaf(2) + + if token == self.TOKEN.TIMES then return self:NewOpcode("mul", leftLeaf,rightLeaf) end + if token == self.TOKEN.SLASH then return self:NewOpcode("div", leftLeaf,rightLeaf) end + if token == self.TOKEN.POWER then return self:NewOpcode("fpwr",leftLeaf,rightLeaf) end + if token == self.TOKEN.MODULUS then return self:NewOpcode("mod",leftLeaf,rightLeaf) end + else + return leftLeaf + end +end + + +-- level1: + +function HCOMP:Expression_Level1() + local leftLeaf = self:Expression_LevelLeaf(2) + + local token = self:PeekToken() + if (token == self.TOKEN.PLUS) or + (token == self.TOKEN.MINUS) then -- +- + -- Treat "-" as negate instead of subtraction FIXME + if token == self.TOKEN.PLUS then self:NextToken() end + + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("add",leftLeaf,rightLeaf) + elseif (token == self.TOKEN.LAND) or + (token == self.TOKEN.LOR) then -- &&, || + self:NextToken() + local rightLeaf = self:Expression_LevelLeaf(0) + + if token == self.TOKEN.LAND then return self:NewOpcode("and",leftLeaf,rightLeaf) end + if token == self.TOKEN.LOR then return self:NewOpcode("or",leftLeaf,rightLeaf) end + elseif (token == self.TOKEN.AND) or + (token == self.TOKEN.OR) or + (token == self.TOKEN.XOR) then -- &, |, ^ + self:NextToken() + local rightLeaf = self:Expression_LevelLeaf(0) + + if token == self.TOKEN.AND then return self:NewOpcode("band",leftLeaf,rightLeaf) end + if token == self.TOKEN.OR then return self:NewOpcode("bor", leftLeaf,rightLeaf) end + if token == self.TOKEN.XOR then return self:NewOpcode("bxor",leftLeaf,rightLeaf) end + else + return leftLeaf + end +end + + +-- level0: = +function HCOMP:Expression_Level0() + local leftLeaf = self:Expression_LevelLeaf(1) + + if self:MatchToken(self.TOKEN.EQUAL) then -- = + local rightLeaf = self:Expression_LevelLeaf(0) + + -- Mark this leaf as an explict assign operation + local operationLeaf = self:NewOpcode("mov",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + elseif self:MatchToken(self.TOKEN.LSS) then -- < + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("max", + self:NewOpcode("fsgn", + self:NewOpcode("sub",rightLeaf,leftLeaf), + { TrigonometryHack = true } + ), + { Constant = 0 } + ) + elseif self:MatchToken(self.TOKEN.GTR) then -- > + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("max", + self:NewOpcode("fsgn", + self:NewOpcode("neg", + self:NewOpcode("sub",rightLeaf,leftLeaf) + ), + { TrigonometryHack = true } + ), + { Constant = 0 } + ) + elseif self:MatchToken(self.TOKEN.LEQ) then -- <= + -- FIXME: returns "0", "1", or "2" instead of just 1 or 0 + -- Does not alter comparsions, but might be annoying? + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("max", + self:NewOpcode("inc", + self:NewOpcode("fsgn", + self:NewOpcode("sub",rightLeaf,leftLeaf), + { TrigonometryHack = true } + ) + ), + { Constant = 0 } + ) + elseif self:MatchToken(self.TOKEN.GEQ) then -- >= + -- FIXME: returns "0", "1", or "2" instead of just 1 or 0 + -- Does not alter comparsions, but might be annoying? + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("max", + self:NewOpcode("inc", + self:NewOpcode("fsgn", + self:NewOpcode("neg", + self:NewOpcode("sub",rightLeaf,leftLeaf) + ), + { TrigonometryHack = true } + ) + ), + { Constant = 0 } + ) + elseif self:MatchToken(self.TOKEN.EQL) then -- == + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("lneg", + self:NewOpcode("fsgn", + self:NewOpcode("fabs", + self:NewOpcode("sub",rightLeaf,leftLeaf), + { TrigonometryHack = true } + ), + { TrigonometryHack = true } + ) + ) + elseif self:MatchToken(self.TOKEN.NEQ) then -- != + local rightLeaf = self:Expression_LevelLeaf(0) + return self:NewOpcode("fsgn", + self:NewOpcode("fabs", + self:NewOpcode("sub",rightLeaf,leftLeaf), + { TrigonometryHack = true } + ), + { TrigonometryHack = true } + ) + elseif self:MatchToken(self.TOKEN.EQLADD) then -- += + local rightLeaf = self:Expression_LevelLeaf(0) + + local operationLeaf = self:NewOpcode("add",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + elseif self:MatchToken(self.TOKEN.EQLSUB) then -- -= + local rightLeaf = self:Expression_LevelLeaf(0) + + local operationLeaf = self:NewOpcode("sub",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + elseif self:MatchToken(self.TOKEN.EQLMUL) then -- *= + local rightLeaf = self:Expression_LevelLeaf(0) + + local operationLeaf = self:NewOpcode("mul",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + elseif self:MatchToken(self.TOKEN.EQLDIV) then -- /= + local rightLeaf = self:Expression_LevelLeaf(0) + + local operationLeaf = self:NewOpcode("div",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + elseif self:MatchToken(self.TOKEN.SHR) then -- >> + local rightLeaf = self:Expression_LevelLeaf(0) + + local operationLeaf = self:NewOpcode("bshr",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + elseif self:MatchToken(self.TOKEN.SHL) then -- << + local rightLeaf = self:Expression_LevelLeaf(0) + + local operationLeaf = self:NewOpcode("bshl",leftLeaf,rightLeaf) + operationLeaf.ExplictAssign = true + operationLeaf.ReturnAfterAssign = true + return operationLeaf + else + return leftLeaf + end +end + + + +-- Compile a single expression (statement) and return corresponding leaf +function HCOMP:Expression() + local leaf = self:Expression_Level0() + return leaf +end + + + + + + + + + + +-------------------------------------------------------------------------------- +-- Constant expression parser +-- Each level function returns 3 values +-- 1 Is return value constant (true/false) +-- 2 Is return value precise (false if value depends on floating pointer) +-- 3 Return value (or magic value) + +--level3: () or +function HCOMP:ConstantExpression_Level3() + local constSign = 1 + if self:MatchToken(self.TOKEN.MINUS) then constSign = -1 end + if self:MatchToken(self.TOKEN.PLUS) then constSign = 1 end + + if self:MatchToken(self.TOKEN.AND) then -- &pointer + if self:MatchToken(self.TOKEN.IDENT) then + local label = self:GetLabel(self.TokenData) + + -- Check if it's a pointer of array member (always dynamic) + if label.Array and self:MatchToken(self.TOKEN.LSUBSCR) then return false end + + -- Check if it's any of the known types + if label.Type == "Pointer" then + self:Error("Ident "..self.TokenData.." is not a variable") + elseif label.Type == "Variable" then + if label.Value and (not self.Settings.GenerateLibrary) + then return true,true,label.Value*constSign + else return true,false,self.Settings.MagicValue + end + elseif label.Type == "Stack" then + -- Pointer to stack value is not a constant + return false + elseif label.Type == "Register" then + -- Register variable is not a constant + return false + elseif label.Type == "Unknown" then + return true,false,self.Settings.MagicValue + else + self:Error("Ident "..self.TokenData.." is not a label/pointer") + end + else + return false + end + elseif self:MatchToken(self.TOKEN.NUMBER) then + return true,true,self.TokenData*constSign + elseif self:MatchToken(self.TOKEN.CHAR) then + return true,true,self.TokenData*constSign + elseif self:MatchToken(self.TOKEN.STRING) and (not self.IgnoreStringInExpression) then + local stringData = self.TokenData + while self:MatchToken(self.TOKEN.STRING) do + stringData = stringData .. self.TokenData + end + + if self.GlobalStringTable[stringData] then + if self.GlobalStringTable[stringData].Label.Value then + return true,true,self.GlobalStringTable[stringData].Label.Value*constSign + else + return true,false,self.Settings.MagicValue + end + else + if self.StringsTable then + if self.StringsTable[stringData] then + if self.StringsTable[stringData].Label.Value then + return true,true,self.StringsTable[stringData].Label.Value*constSign + else + return true,false,self.Settings.MagicValue + end + else + self.StringsTable[stringData] = self:NewLeaf() + self.StringsTable[stringData].Opcode = "DATA" + self.StringsTable[stringData].Data = { stringData, 0 } + + local stringLabel = self:GetTempLabel() + stringLabel.Leaf = self.StringsTable[stringData] + self.StringsTable[stringData].Label = stringLabel + self.GlobalStringTable[stringData] = self.StringsTable[stringData] + return true,false,self.Settings.MagicValue + end + else + return false + end + end + elseif self:MatchToken(self.TOKEN.IDENT) then + local label = self:GetLabel(self.TokenData) + if self:MatchToken(self.TOKEN.LSUBSCR) then + -- Array access is never constant + return false + end + if self:MatchToken(self.TOKEN.LPAREN) then + -- Function calls are never constant + return false + end + + if label.Type == "Pointer" then + -- Pointers are constant + if label.Value and (not self.Settings.GenerateLibrary) + then return true,true,label.Value*constSign + else return true,false,self.Settings.MagicValue + end + elseif label.Type == "Variable" then + if label.Array then + -- Array variables must be treated as pointers + if label.Value and (not self.Settings.GenerateLibrary) + then return true,true,label.Value*constSign + else return true,false,self.Settings.MagicValue + end + else + -- Variables are not constant + return false + end + elseif label.Type == "Stack" then + -- Stack variables are not constant + return false + elseif label.Type == "Register" then + -- Register variable is not a constant + return false + elseif label.Type == "Unknown" then + if self.MostLikelyConstantExpression then + -- Unknown variables are not constant, but they usually are. + return true,false,self.Settings.MagicValue + else + -- It's probably not a constant expression + return false + end + + -- If this variable wasn't really constant, the error will be caught + -- on the final output stage when all constant expressions are + -- recalculated + else + self:Error("Ident "..self.TokenData.." is not a label/pointer") + end + elseif self:MatchToken(self.TOKEN.LPAREN) then + local isConst,isPrecise,Value = self:ConstantExpression_Level0() + if not isConst then return false end + self:MatchToken(self.TOKEN.RPAREN) + --FIXME: this should be expect when you NEED constant value, and match when you TEST FOR constant value + + return true,isPrecise,Value*constSign + end + + return false +end + + +--level2: * +function HCOMP:ConstantExpression_Level2() + local leftConst,leftPrecise,leftValue = self:ConstantExpression_Level3() + if not leftConst then return false end + + local token = self:PeekToken() + if (token == self.TOKEN.TIMES) or + (token == self.TOKEN.SLASH) or + (token == self.TOKEN.POWER) or + (token == self.TOKEN.MODULUS) then + self:NextToken() + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level2() + if not rightConst then return false end + + if token == self.TOKEN.TIMES then return true,(leftPrecise and rightPrecise),leftValue*rightValue end + if token == self.TOKEN.SLASH then return true,(leftPrecise and rightPrecise),leftValue/rightValue end + if token == self.TOKEN.POWER then return true,(leftPrecise and rightPrecise),leftValue^rightValue end + if token == self.TOKEN.MODULUS then return true,(leftPrecise and rightPrecise),leftValue%rightValue end + else + return true,leftPrecise,leftValue + end +end + + +--level1: + +function HCOMP:ConstantExpression_Level1() + local leftConst,leftPrecise,leftValue = self:ConstantExpression_Level2() + if not leftConst then return false end + + local token = self:PeekToken() + if (token == self.TOKEN.PLUS) or + (token == self.TOKEN.MINUS) then + if token == self.TOKEN.PLUS then self:NextToken() end + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + return true,(leftPrecise and rightPrecise),leftValue+rightValue + elseif (token == self.TOKEN.LAND) or + (token == self.TOKEN.LOR) then -- &&, || + self:NextToken() + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if token == self.TOKEN.LAND then + if (leftValue > 0) and (rightValue > 0) then + return true,(leftPrecise and rightPrecise),1 + else + return true,(leftPrecise and rightPrecise),0 + end + end + if token == self.TOKEN.LOR then + if (leftValue > 0) or (rightValue > 0) then + return true,(leftPrecise and rightPrecise),1 + else + return true,(leftPrecise and rightPrecise),0 + end + end + elseif (token == self.TOKEN.AND) or + (token == self.TOKEN.OR) or + (token == self.TOKEN.XOR) then -- &, |, ^ + self:NextToken() + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if token == self.TOKEN.AND then return true,(leftPrecise and rightPrecise),self:BinaryAnd(leftValue,rightValue) end + if token == self.TOKEN.OR then return true,(leftPrecise and rightPrecise),self:BinaryOr (leftValue,rightValue) end + if token == self.TOKEN.XOR then return true,(leftPrecise and rightPrecise),self:BinaryXor(leftValue,rightValue) end + else + return true,leftPrecise,leftValue + end +end + + +--level0: = +function HCOMP:ConstantExpression_Level0() + local leftConst,leftPrecise,leftValue = self:ConstantExpression_Level1() + if not leftConst then return false end + + if self:MatchToken(self.TOKEN.EQUAL) then -- = + return false + elseif self:MatchToken(self.TOKEN.LSS) then -- < + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if leftValue < rightValue + then return true,(leftPrecise and rightPrecise),1 + else return true,(leftPrecise and rightPrecise),0 + end + elseif self:MatchToken(self.TOKEN.GTR) then -- > + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if leftValue > rightValue + then return true,(leftPrecise and rightPrecise),1 + else return true,(leftPrecise and rightPrecise),0 + end + elseif self:MatchToken(self.TOKEN.LEQ) then -- <= + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if leftValue <= rightValue + then return true,(leftPrecise and rightPrecise),1 + else return true,(leftPrecise and rightPrecise),0 + end + elseif self:MatchToken(self.TOKEN.GEQ) then -- >= + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if leftValue >= rightValue + then return true,(leftPrecise and rightPrecise),1 + else return true,(leftPrecise and rightPrecise),0 + end + elseif self:MatchToken(self.TOKEN.EQL) then -- == + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if leftValue == rightValue + then return true,(leftPrecise and rightPrecise),1 + else return true,(leftPrecise and rightPrecise),0 + end + elseif self:MatchToken(self.TOKEN.NEQ) then -- != + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + + if leftValue ~= rightValue + then return true,(leftPrecise and rightPrecise),1 + else return true,(leftPrecise and rightPrecise),0 + end + elseif self:MatchToken(self.TOKEN.EQLADD) then -- += + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + return true,(leftPrecise and rightPrecise),leftValue+rightValue + elseif self:MatchToken(self.TOKEN.EQLSUB) then -- -= + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + return true,(leftPrecise and rightPrecise),leftValue-rightValue + elseif self:MatchToken(self.TOKEN.EQLMUL) then -- *= + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + return true,(leftPrecise and rightPrecise),leftValue*rightValue + elseif self:MatchToken(self.TOKEN.EQLDIV) then -- /= + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + return true,(leftPrecise and rightPrecise),leftValue/rightValue + elseif self:MatchToken(self.TOKEN.SHR) then -- >> + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + return true,(leftPrecise and rightPrecise),self:BinarySHR(leftValue,rightValue) + elseif self:MatchToken(self.TOKEN.SHL) then -- << + local rightConst,rightPrecise,rightValue = self:ConstantExpression_Level0() + if not rightConst then return false end + return true,(leftPrecise and rightPrecise),self:BinarySHL(leftValue,rightValue) + end + + return true,leftPrecise,leftValue +end + + +-- Calculate constant expression and return expression +function HCOMP:ConstantExpression(needResultNow,startLevel) + self:SaveParserState() + + local isConst,isPrecise,value + if startLevel == 3 then isConst,isPrecise,value = self:ConstantExpression_Level3() + elseif startLevel == 2 then isConst,isPrecise,value = self:ConstantExpression_Level2() + elseif startLevel == 1 then isConst,isPrecise,value = self:ConstantExpression_Level1() + else isConst,isPrecise,value = self:ConstantExpression_Level0() + end + + if isPrecise then + return true,value + else + if needResultNow then + self:RestoreParserState() + return false + end + + if isConst then + -- Return list of tokens that correspond to parsed expression + -- This is used to recalculate expression later + return true,nil,self:GetSavedTokens() + else + self:RestoreParserState() + return false + end + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_opcodes.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_opcodes.lua new file mode 100644 index 0000000..ddd91f2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_opcodes.lua @@ -0,0 +1,43 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- Opcode definition file +-------------------------------------------------------------------------------- + + + + +-------------------------------------------------------------------------------- +-- Initialize opcode count lookup table +HCOMP.OperandCount = {} +for _,instruction in pairs(CPULib.InstructionTable) do + HCOMP.OperandCount[instruction.Opcode] = instruction.OperandCount +end + +-- Initialize table of single-operand instructions which write 1st operand +HCOMP.OpcodeWritesOperand = {} +for _,instruction in pairs(CPULib.InstructionTable) do + if instruction.WritesFirstOperand and (instruction.Mnemonic ~= "RESERVED") then + HCOMP.OpcodeWritesOperand[string.lower(instruction.Mnemonic)] = true + end +end + +-- Initialize opcode number lookup table +HCOMP.OpcodeNumber = {} +for _,instruction in pairs(CPULib.InstructionTable) do + if instruction.Mnemonic ~= "RESERVED" then + HCOMP.OpcodeNumber[string.lower(instruction.Mnemonic)] = instruction.Opcode + end +end + +-- Initialize list of obsolete/old opcodes +HCOMP.OpcodeObsolete = {} +HCOMP.OpcodeOld = {} +for _,instruction in pairs(CPULib.InstructionTable) do + if instruction.Obsolete and (instruction.Mnemonic ~= "RESERVED") then + HCOMP.OpcodeObsolete[string.lower(instruction.Mnemonic)] = true + end + if instruction.Old and (instruction.Mnemonic ~= "RESERVED") then + HCOMP.OpcodeOld[string.lower(instruction.Mnemonic)] = string.lower(instruction.Reference) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_optimize.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_optimize.lua new file mode 100644 index 0000000..64acbbd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_optimize.lua @@ -0,0 +1,136 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- Optimizer +-------------------------------------------------------------------------------- + + + + +local OptimizationPattern = { +-------------------------------------------------------------------------------- + {{{"sstack","A","B"}, + {"rstack","C","A"}, + }, + {{"sstack","A","B"}, + {"mov", "C","B"}, + }, + }, +-------------------------------------------------------------------------------- + {{{"?1" ,"A","B"}, + {"sstack","C","D"}, + {"?2" ,"E","F"}, + {"sstack","C","D"}, + }, + {{"?1" ,"A","B"}, + {"?2" ,"E","F"}, + {"sstack","C","D"}, + }, + }, +-------------------------------------------------------------------------------- + {{{"mov","A","A"}, + }, + { + }, + } +} + + + + +-------------------------------------------------------------------------------- +-- Compare if two operands match +local function CompareOperands(op1,op2) + return op1 and op2 and + (op1.Constant == op2.Constant) and + (op1.Register == op2.Register) and + (op1.Segment == op2.Segment) and + (op1.Memory == op2.Memory) and + (op1.MemoryPointer == op2.MemoryPointer) +end + + + + +-------------------------------------------------------------------------------- +-- Optimizes the generated code. Returns true if something was optimized +function HCOMP:OptimizeCode() + -- For all opcodes + for index,opcode in ipairs(self.GeneratedCode) do + -- Check all optimization patterns + for _,pattern in pairs(OptimizationPattern) do + -- Check that pattern is long enough + if self.GeneratedCode[index+#pattern[1]-1] then + -- Check if pattern matches + local patternMatches = true + local temporaryOperands = {} + local temporaryOpcodes = {} + + -- Check all opcodes in pattern, continously + for i,matchPattern in ipairs(pattern[1]) do + local matchOpcode = matchPattern[1] + if not self.OpcodeNumber[matchPattern[1]] then + if not temporaryOpcodes[matchPattern[1]] then + temporaryOpcodes[matchPattern[1]] = self.GeneratedCode[index+i-1].Opcode + end + matchOpcode = temporaryOpcodes[matchPattern[1]] + end + + if (matchOpcode) and (self.GeneratedCode[index+i-1].Opcode == matchOpcode) then + local operand1 = self.GeneratedCode[index+i-1].Operands[1] + local operand2 = self.GeneratedCode[index+i-1].Operands[2] + + if matchPattern[2] and (not temporaryOperands[matchPattern[2]]) then + temporaryOperands[matchPattern[2]] = operand1 + end + if matchPattern[3] and (not temporaryOperands[matchPattern[3]]) then + temporaryOperands[matchPattern[3]] = operand2 + end + + -- Compare so operand "A" equals operand "A", etc + if matchPattern[2] and (not CompareOperands(operand1,temporaryOperands[matchPattern[2]])) then + patternMatches = false + end + if matchPattern[3] and (not CompareOperands(operand2,temporaryOperands[matchPattern[3]])) then + patternMatches = false + end + else + patternMatches = false + end + end + + if patternMatches then + -- If match found, delete all old entries + for i=1,#pattern[1] do + table.remove(self.GeneratedCode,index) + end + + -- Re-add the new ones + for i=1,#pattern[2] do + local tempOperands = {} + -- This will properly match letters with operands parsed when matching + if pattern[2][i][2] then tempOperands[1] = temporaryOperands[pattern[2][i][2]] end + if pattern[2][i][3] then tempOperands[2] = temporaryOperands[pattern[2][i][3]] end + + if self.OpcodeNumber[pattern[2][i][1]] then + table.insert(self.GeneratedCode,index+i-1, + { + Opcode = pattern[2][i][1], + Operands = tempOperands, + }) + else + table.insert(self.GeneratedCode,index+i-1, + { + Opcode = temporaryOpcodes[pattern[2][i][1]], + Operands = tempOperands, + }) + end + end + return true + end + end + end + end + + return false +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_output.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_output.lua new file mode 100644 index 0000000..0e9cbba --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_output.lua @@ -0,0 +1,647 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- Code resolver and output generator +-------------------------------------------------------------------------------- + + + + +-------------------------------------------------------------------------------- +-- Resolve a single block/opcode (offsets and labels in it) +function HCOMP:Resolve(block) + -- Set offset for the block + block.Offset = self.WritePointer + + + -- Set pointer offset + block.PointerOffset = self.PointerOffset + + -- Label precedes the opcode and the data in the leaf + if block.Label then + if (self.Settings.SeparateDataSegment == true) and (block.Data) then + + else + block.Label.Value = self.WritePointer + end + end + + -- Account for the opcode generated by the block + if block.Opcode then + if not self.Settings.FixedSizeOutput then -- Variable-sized instructions + -- Write opcode + self.WritePointer = self.WritePointer + 1 + -- Write RM if more than 1 operand + if #block.Operands > 0 then self.WritePointer = self.WritePointer + 1 end + -- Write all segment prefixes and constant operands + for i=1,#block.Operands do + if block.Operands[i].Segment then self.WritePointer = self.WritePointer + 1 end + if block.Operands[i].Constant then self.WritePointer = self.WritePointer + 1 end + if block.Operands[i].MemoryPointer then self.WritePointer = self.WritePointer + 1 end + if block.Operands[i].Memory then self.WritePointer = self.WritePointer + 1 end + end + else -- Fixed-size instructions + self.WritePointer = self.WritePointer + 6 + end + -- Preventive setting up __PTR__ as constant number (const = currentOpcodeOffset + currentOpcodeSize) + for i=1,#block.Operands do + if istable(block.Operands[i].Constant) then + for j=1, #block.Operands[i].Constant do + if (block.Operands[i].Constant[j].Data == "__PTR__") then + block.Operands[i].Constant[j].Data = self.WritePointer + block.Operands[i].Constant[j].Type = self.TOKEN.NUMBER + end + end + end + end + end + + -- Account for extra data in the block + if block.Data then + if self.Settings.SeparateDataSegment == true then + for index,value in ipairs(block.Data) do + if isnumber(value)then -- Data is a number + self.DataPointer = self.DataPointer + 1 + elseif istable(value)then -- Data is a constant expression + self.WritePointer = self.DataPointer + 1 + else -- Data is a string + self.DataPointer = self.DataPointer + #value + end + end + else + for index,value in ipairs(block.Data) do + if isnumber(value)then -- Data is a number + self.WritePointer = self.WritePointer + 1 + elseif istable(value)then -- Data is a constant expression + self.WritePointer = self.WritePointer + 1 + else -- Data is a string + self.WritePointer = self.WritePointer + #value + end + end + end + end + + -- Zero padding after the block + if block.ZeroPadding then + if self.Settings.SeparateDataSegment == true then + + else + self.WritePointer = self.WritePointer + block.ZeroPadding + end + end + + -- Special marker to change write pointer + if block.SetWritePointer then + self.WritePointer = block.SetWritePointer + end + + -- Special marker to change pointer offset + if block.SetPointerOffset then + self.PointerOffset = block.SetPointerOffset + end + + -- Output the block if required + if self.Settings.OutputResolveListing then + self:PrintBlock(block,"rlist") + end + -- Output the block as library if required + if self.Settings.GenerateLibrary then +-- self:PrintBlock(block,"lib",true) + end + return false +end + + + + +-------------------------------------------------------------------------------- +-- Output a single block to the output stream (for library mode) +function HCOMP:OutputLibrary(block) + -- Write label + if block.Label then + if self.DBString then + self:PrintLine("lib",self.DBString) + self.DBString = nil + end + if not self.LabelLookup[block.Label.Name] then + self.LabelLookup[block.Label.Name] = "_"..self.LabelLookupCounter + self.LabelLookupCounter = self.LabelLookupCounter + 1 + end + self:PrintLine("lib",self.LabelLookup[block.Label.Name]..":") + end + + -- Resolve constant values in the block + if block.Opcode then + for i=1,#block.Operands do + if block.Operands[i].Constant and (not tonumber(block.Operands[i].Constant)) then + if block.PointerOffset ~= 0 + then block.Operands[i].Constant = self:PrintTokens(block.Operands[i].Constant).."+"..block.PointerOffset + else block.Operands[i].Constant = self:PrintTokens(block.Operands[i].Constant) + end + end + + if block.Operands[i].MemoryPointer and (not tonumber(block.Operands[i].MemoryPointer)) then + if block.PointerOffset ~= 0 + then block.Operands[i].MemoryPointer = self:PrintTokens(block.Operands[i].MemoryPointer).."+"..block.PointerOffset + else block.Operands[i].MemoryPointer = self:PrintTokens(block.Operands[i].MemoryPointer) + end + end + end + end + + -- Resolve constant values in data + if block.Data then + for index,value in ipairs(block.Data) do + if istable(value)then -- Data is a constant expression + block.Data[index] = self:PrintTokens(value) + end + end + end + + -- Write binary code + local tempWriteByte = self.WriteByte + self.WriteByte = function(self,value,block) + if not self.DBString then + self.DBString = "db "..value + else + if #self.DBString > 40 then + self:PrintLine("lib",self.DBString) + self.DBString = "db "..value + else + self.DBString = self.DBString..","..value + end + end + end + self:WriteBlock(block) + self.WriteByte = tempWriteByte +end + + + + +-------------------------------------------------------------------------------- +-- Output a single block to the output stream +function HCOMP:Output(block) + -- Generate library output + if self.Settings.GenerateLibrary then + self:OutputLibrary(block) + return + end + -- Resolve constant values in the block + if block.Opcode then + for i=1,#block.Operands do + if block.Operands[i].Constant and (not tonumber(block.Operands[i].Constant)) then + -- Prepare expression to parse + self:RestoreParserState(block.Operands[i].Constant) + -- Try to parse the expression + local c,v = self:ConstantExpression(true) + if not c then + if (#block.Operands[i].Constant == 1) and + (block.Operands[i].Constant[1].Type == self.TOKEN.IDENT) then + self:Error("Undefined label or variable: "..block.Operands[i].Constant[1].Data,block) + elseif (#block.Operands[i].Constant == 2) and + (block.Operands[i].Constant[1].Type == self.TOKEN.AND) and + (block.Operands[i].Constant[2].Type == self.TOKEN.IDENT) then + self:Error("Undefined function or array: "..block.Operands[i].Constant[2].Data,block) + else + self:Error("Must be constant expression: "..self:PrintTokens(block.Operands[i].Constant),block) + end + end + -- Set the result + if v + then block.Operands[i].Constant = v + block.PointerOffset + else block.Operands[i].Constant = self.Settings.MagicValue + end + end + + if block.Operands[i].MemoryPointer and (not tonumber(block.Operands[i].MemoryPointer)) then + -- Prepare expression to parse + self:RestoreParserState(block.Operands[i].MemoryPointer) + -- Try to parse the expression + local c,v = self:ConstantExpression(true) + if not c then + if (#block.Operands[i].MemoryPointer == 1) and + (block.Operands[i].MemoryPointer[1].Type == self.TOKEN.IDENT) then + self:Error("Undefined label or variable: "..block.Operands[i].MemoryPointer[1].Data,block) + elseif (#block.Operands[i].MemoryPointer == 2) and + (block.Operands[i].MemoryPointer[1].Type == self.TOKEN.AND) and + (block.Operands[i].MemoryPointer[2].Type == self.TOKEN.IDENT) then + self:Error("Undefined function or array: "..block.Operands[i].MemoryPointer[2].Data,block) + else + self:Error("Must be constant expression: "..self:PrintTokens(block.Operands[i].MemoryPointer),block) + end + end + -- Set the result + if v + then block.Operands[i].MemoryPointer = v + block.PointerOffset + else block.Operands[i].MemoryPointer = self.Settings.MagicValue + end + end + end + end + + -- Resolve constant values in data + if block.Data then + for index,value in ipairs(block.Data) do + if istable(value)then -- Data is a constant expression + -- Prepare expression to parse + self:RestoreParserState(value) + -- Try to parse the expression + self.IgnoreStringInExpression = true + local c,v = self:ConstantExpression(true) + if not c then + if (#value == 1) and + (value[1].Type == self.TOKEN.IDENT) then + self:Error("Undefined label or variable: "..value[1].Data,block) + else + self:Error("Must be constant expression: "..self:PrintTokens(value),block) + end + end + self.IgnoreStringInExpression = false + -- Set the result + block.Data[index] = v or self.Settings.MagicValue + end + end + end + + + -- Output the block + if self.Settings.OutputFinalListing then + self:PrintBlock(block,"flist") + end + self:WriteBlock(block) +end + + + + +-------------------------------------------------------------------------------- +-- Lookup for CPU registers +local RegisterName = {} +RegisterName[01] = "EAX" +RegisterName[02] = "EBX" +RegisterName[03] = "ECX" +RegisterName[04] = "EDX" +RegisterName[05] = "ESI" +RegisterName[06] = "EDI" +RegisterName[07] = "ESP" +RegisterName[08] = "EBP" +RegisterName[16] = "CS" +RegisterName[17] = "SS" +RegisterName[18] = "DS" +RegisterName[19] = "ES" +RegisterName[20] = "GS" +RegisterName[21] = "FS" +RegisterName[22] = "KS" +RegisterName[23] = "LS" +for port=0,1023 do RegisterName[1024+port] = "port"..port end +for reg=0,31 do RegisterName[96+reg] = "R"..reg end + + +local SegmentRegisterName = {} +SegmentRegisterName[01] = "CS" +SegmentRegisterName[02] = "SS" +SegmentRegisterName[03] = "DS" +SegmentRegisterName[04] = "ES" +SegmentRegisterName[05] = "GS" +SegmentRegisterName[06] = "FS" +SegmentRegisterName[07] = "KS" +SegmentRegisterName[08] = "LS" +SegmentRegisterName[09] = "EAX" +SegmentRegisterName[10] = "EBX" +SegmentRegisterName[11] = "ECX" +SegmentRegisterName[12] = "EDX" +SegmentRegisterName[13] = "ESI" +SegmentRegisterName[14] = "EDI" +SegmentRegisterName[15] = "ESP" +SegmentRegisterName[16] = "EBP" +for reg=0,31 do SegmentRegisterName[17+reg] = "R"..reg end + + + + +-------------------------------------------------------------------------------- +-- Print a block in a readable format +function HCOMP:PrintBlock(block,file,isLibrary) + -- Print corresponding label + if block.Label then + if (self.Settings.OutputLabelsInListing == true) or (isLibrary) then + self:PrintLine(file,block.Label.Name..":")-- /".."/ offset "..block.Label.Value) + end + end + + -- Print a comment + if block.Comment and (not isLibrary) then + self:PrintLine(file,"/".."/ "..block.Comment) + end + + -- Print the opcode + if block.Opcode then -- instruction + local printText = "" + if (self.Settings.OutputOffsetsInListing == true) and (not isLibrary) then + printText = printText .. string.format("%6d ",block.Offset) + else + printText = printText .. " " + end + printText = printText .. block.Opcode .. " " + + for i=1,#block.Operands do + if block.Operands[i].Segment then + printText = printText .. (SegmentRegisterName[block.Operands[i].Segment] or "???") .. ":" + end + + if block.Operands[i].Constant then + if istable(block.Operands[i].Constant) then + printText = printText .. self:PrintTokens(block.Operands[i].Constant) + else + printText = printText .. block.Operands[i].Constant + end + elseif block.Operands[i].MemoryPointer then + if istable(block.Operands[i].MemoryPointer) then + printText = printText .. "#" .. self:PrintTokens(block.Operands[i].MemoryPointer) + else + printText = printText .. "#" .. block.Operands[i].MemoryPointer + end + elseif block.Operands[i].Memory then + if istable(block.Operands[i].Memory) then + if block.Operands[i].Memory.Value + then printText = printText .. "#" .. block.Operands[i].Memory.Value + else printText = printText .. "#" .. block.Operands[i].Memory.Name + end + else + printText = printText .. "#" .. block.Operands[i].Memory + end + elseif block.Operands[i].MemoryRegister then + printText = printText .. "#" .. (RegisterName[block.Operands[i].MemoryRegister] or "???") + else + printText = printText .. (RegisterName[block.Operands[i].Register] or "???") + end + + if i < #block.Operands then printText = printText.."," end + end + + self:PrintLine(file,printText) + end + + -- Print the data + if block.Data then + local printText = "" + if (self.Settings.OutputOffsetsInListing == true) and (not isLibrary) then + printText = printText .. string.format("%6d db ",block.Offset) + else + printText = printText .. " db " + end + + for index,value in ipairs(block.Data) do + if isnumber(value)then -- Data is a number + printText = printText .. value + elseif istable(value)then -- Data is an expression + printText = printText .. self:PrintTokens(value) + else -- Data is a string + printText = printText .. "\"" .. value .. "\"" + end + if index < #block.Data then + printText = printText .. "," + end + end + self:PrintLine(file,printText) + end + + -- Add zero padding + if block.ZeroPadding and (block.ZeroPadding > 0) then + if (self.Settings.OutputOffsetsInListing == true) and (not isLibrary) then + self:PrintLine(file,string.format("%6d alloc %d",block.Offset,block.ZeroPadding)) + else + self:PrintLine(file,string.format("alloc %d",block.ZeroPadding)) + end + end + + -- Parse marker commands + if block.SetWritePointer then + if (self.Settings.OutputOffsetsInListing == true) and (not isLibrary) then + self:PrintLine(file,string.format("%6d org %d",block.Offset,block.SetWritePointer)) + else + self:PrintLine(file,string.format("org %d",block.SetWritePointer)) + end + end + + -- Parse marker commands + if block.SetPointerOffset then + if (self.Settings.OutputOffsetsInListing == true) and (not isLibrary) then + self:PrintLine(file,string.format("%6d offset %d",block.Offset,block.SetPointerOffset)) + else + self:PrintLine(file,string.format("offset %d",block.SetPointerOffset)) + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Print a leaf in a readable format with specific nesting level +function HCOMP:PrintLeaf(leaf,level) + -- Generate string for padding + if not level then level = 0 end + local pad = string.rep(" ",level) + + if istable(leaf) then + if leaf.PreviousLeaf then +-- self:PrintLine("ctree",pad.."previous leaf:") + self:PrintLeaf(leaf.PreviousLeaf,level) + end + + if leaf.Opcode then + if leaf.Opcode == "LABEL" then + self:PrintLine("ctree",pad..leaf.Label.Name..": ("..string.lower(leaf.Label.Type)..")") + elseif leaf.Opcode == "DATA" then + if leaf.Label then + self:PrintLine("ctree",pad..leaf.Label.Name..": ("..string.lower(leaf.Label.Type)..")") + end + self:PrintLine("ctree",pad.." ["..(leaf.ZeroPadding or 0).." zero bytes, "..(#(leaf.Data or {})).." data bytes]") + elseif leaf.Opcode == "MARKER" then + self:PrintLine("ctree",pad.." [marker]") + else + self:PrintLine("ctree",pad..leaf.Opcode) + for i=1,#leaf.Operands do self:PrintLeaf(leaf.Operands[i],level+1) end + end + else + if leaf.Constant then + if istable(leaf.Constant) then + self:PrintLine("ctree",pad..self:PrintTokens(leaf.Constant)) + else + self:PrintLine("ctree",pad..leaf.Constant) + end + elseif leaf.Memory then + self:PrintLine("ctree",pad.."#"..leaf.Memory.Name) + elseif leaf.Register then + self:PrintLine("ctree",pad..RegisterName[leaf.Register]) + elseif leaf.MemoryRegister then + self:PrintLine("ctree",pad.."#"..RegisterName[leaf.MemoryRegister]) + elseif leaf.MemoryPointer then + if istable(leaf.MemoryPointer) then + if leaf.MemoryPointer.Opcode then + self:PrintLine("ctree",pad.."#[") + self:PrintLeaf(leaf.MemoryPointer,level+1) + self:PrintLine("ctree",pad.." ]") + else + self:PrintLine("ctree",pad.."#"..self:PrintTokens(leaf.MemoryPointer)) + end + else + self:PrintLine("ctree",pad.."#"..leaf.MemoryPointer) + end + elseif leaf.Stack then + if istable(leaf.Stack) then + self:PrintLine("ctree",pad.."stack[") + self:PrintLeaf(leaf.Stack,level+1) + self:PrintLine("ctree",pad.." ]") + else + self:PrintLine("ctree",pad.."stack["..leaf.Stack.."]") + end + elseif leaf.PointerToLabel then + self:PrintLine("ctree",pad.."&"..leaf.PointerToLabel.Name) + elseif leaf.TrigonometryHack then + self:PrintLine("ctree",pad.."(trigonometry hack)") + else + for k,v in pairs(leaf) do + print(k,v) + end + self:Error("Internal error 295") + end + end + else + self:Error("Internal error 229") + end +end + + + + +-------------------------------------------------------------------------------- +-- Generate RM for an operand +function HCOMP:OperandRM(operand,block) + if operand.Constant then + if not operand.Segment then + operand.Value = operand.Constant + return 0 + else + operand.Value = operand.Constant + return 50 + end + elseif operand.Memory then + if istable(operand.Memory) then -- label + operand.Value = operand.Memory.Value + else -- constant + operand.Value = operand.Memory + end + return 25 + elseif operand.MemoryPointer then + operand.Value = operand.MemoryPointer + return 25 + elseif operand.Register then + if not operand.Segment then + if (operand.Register >= 1) and (operand.Register <= 8) then return operand.Register end + if (operand.Register >= 16) and (operand.Register <= 23) then return operand.Register-16+9 end + if (operand.Register >= 1024) and (operand.Register < 2048) then return operand.Register-1024+1000 end + if (operand.Register >= 96) and (operand.Register <= 127) then return operand.Register-96+2048 end + else + if (operand.Register >= 1) and (operand.Register <= 8) then return operand.Register+26 end + if (operand.Register >= 16) and (operand.Register <= 23) then + self:Error("Invalid instruction operand (cannot use segment prefix for segment register access)",block) + end + if (operand.Register >= 1024) and (operand.Register < 2048) then + self:Error("Invalid instruction operand (cannot use segment prefix for port access)",block) + end + if (operand.Register >= 96) and (operand.Register <= 127) then return operand.Register-96+2112 end + end + elseif operand.MemoryRegister then + if (operand.MemoryRegister >= 1) and (operand.MemoryRegister <= 8) then return operand.MemoryRegister+16 end + if (operand.MemoryRegister >= 16) and (operand.MemoryRegister <= 23) then + self:Error("Invalid instruction operand (cannot use segment register for memory access)",block) + end + if (operand.MemoryRegister >= 1024) and (operand.MemoryRegister < 2048) then + self:Error("Invalid instruction operand (cannot use port for memory access)",block) + end + if (operand.MemoryRegister >= 96) and (operand.MemoryRegister <= 127) then return operand.MemoryRegister-96+2080 end + end + + self:Error("Invalid instruction operand",block) +end + + + + +-------------------------------------------------------------------------------- +-- Write the block to output stream +function HCOMP:WriteBlock(block) + -- Write the opcode + if block.Opcode then + if not self.OpcodeNumber[block.Opcode] then + self:Error("Undefined opcode: "..block.Opcode,block) + return + end + local Opcode,RM = self.OpcodeNumber[block.Opcode],nil + + -- Generate RM if more than 1 operand + if #block.Operands > 0 then + RM = self:OperandRM(block.Operands[1],block) + if #block.Operands > 1 then RM = RM + self:OperandRM(block.Operands[2],block)*10000 end + end + + -- Generate segment offset marker + if (#block.Operands > 0) and (block.Operands[1].Segment) then Opcode = Opcode + 1000 end + if (#block.Operands > 1) and (block.Operands[2].Segment) then Opcode = Opcode + 10000 end + + if not self.Settings.FixedSizeOutput then -- Variable-size instructions + -- Write opcode + self:WriteByte(Opcode,block) + -- Write RM + if RM then self:WriteByte(RM,block) end + + -- Write segment offsets + if (#block.Operands > 0) and (block.Operands[1].Segment) then self:WriteByte(block.Operands[1].Segment,block) end + if (#block.Operands > 1) and (block.Operands[2].Segment) then self:WriteByte(block.Operands[2].Segment,block) end + + -- Write immediate bytes + if (#block.Operands > 0) and (block.Operands[1].Value) then self:WriteByte(block.Operands[1].Value,block) end + if (#block.Operands > 1) and (block.Operands[2].Value) then self:WriteByte(block.Operands[2].Value,block) end + else -- Fixed-size instructions + -- Write opcode + self:WriteByte(Opcode + 2000,block) + + -- Write RM + self:WriteByte(RM or 0,block) + + -- Write segment offsets + if #block.Operands > 0 then self:WriteByte(block.Operands[1].Segment or -4,block) else self:WriteByte(-4,block) end + if #block.Operands > 1 then self:WriteByte(block.Operands[2].Segment or -4,block) else self:WriteByte(-4,block) end + + -- Write immediate bytes + if #block.Operands > 0 then self:WriteByte(block.Operands[1].Value or 0,block) else self:WriteByte(0,block) end + if #block.Operands > 1 then self:WriteByte(block.Operands[2].Value or 0,block) else self:WriteByte(0,block) end + end + end + + -- Write the data + if block.Data then + for index,value in ipairs(block.Data) do + if isnumber(value) then -- Data is a number + self:WriteByte(value,block) + else -- Data is a string + for charIdx=1,#value do + self:WriteByte(string.byte(value,charIdx),block) + end + end + end + end + + -- Write zero padding + if block.ZeroPadding then + for i=1,block.ZeroPadding do self:WriteByte(0,block) end + end + + -- Set write pointer + if block.SetWritePointer then + self.WritePointer = block.SetWritePointer + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_preprocess.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_preprocess.lua new file mode 100644 index 0000000..9d6d951 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_preprocess.lua @@ -0,0 +1,164 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- Preprocessor macro parser +-------------------------------------------------------------------------------- + + + +-------------------------------------------------------------------------------- +-- Load file +function HCOMP:LoadFile(filename) + return file.Read("data/"..self.Settings.CurrentPlatform.."Chip/"..filename, "GAME") -- So we also get /addons/wire/data/ +end + +-- Save file +function HCOMP:SaveFile(filename,text) + file.Write(self.Settings.CurrentPlatform.."Chip/"..filename,text) +end + +-- Trim spaces at string sides +local function trimString(str) + return string.gsub(str, "^%s*(.-)%s*$", "%1") +end + + + + +-------------------------------------------------------------------------------- +-- Handle preprocessor macro +function HCOMP:ParsePreprocessMacro(lineText,macroPosition) + -- Trim spaces + local macroLine = trimString(lineText) + + -- Find out macro name and parameters + local macroNameEnd = (string.find(macroLine," ") or 0) + local macroName = trimString(string.sub(macroLine,2,macroNameEnd-1)) + local macroParameters = trimString(string.sub(macroLine,macroNameEnd+1)) + + if macroName == "pragma" then + local pragmaName = string.lower(trimString(string.sub(macroParameters,1,(string.find(macroParameters," ") or 0)-1))) + local pragmaCommand = trimString(string.sub(macroParameters,(string.find(macroParameters," ") or 0)+1)) + + if pragmaName == "set" then + local entryName = trimString(string.sub(pragmaCommand,1,(string.find(pragmaCommand," ") or 0)-1)) + local entryValue = trimString(string.sub(pragmaCommand,(string.find(pragmaCommand," ") or 0)+1)) + + if entryValue == "true" then + self.Settings[entryName] = true + elseif entryValue == "false" then + self.Settings[entryName] = false + else + self.Settings[entryName] = tonumber(entryValue) or entryValue + end + elseif pragmaName == "language" then + if string.lower(pragmaCommand) == "hlzasm" then self.Settings.CurrentLanguage = "HLZASM" end + if string.lower(pragmaCommand) == "zasm" then self.Settings.CurrentLanguage = "ZASM" end + elseif pragmaName == "crt" then + local crtFilename = "lib\\"..string.lower(pragmaCommand).."\\init.txt" + local fileText = self:LoadFile(crtFilename) + if fileText then + table.insert(self.Code, 1, { Text = fileText, Line = 1, Col = 1, File = crtFilename, NextCharPos = 1 }) + else + self:Error("Unable to include CRT library "..pragmaCommand, + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + + self.Defines[string.upper(pragmaCommand)] = "" + table.insert(self.SearchPaths,"lib\\"..string.lower(pragmaCommand)) + elseif pragmaName == "cpuname" then + CPULib.CPUName = pragmaCommand + elseif pragmaName == "searchpath" then + table.insert(self.SearchPaths,pragmaCommand) + end + elseif macroName == "define" then -- #define + local defineName = trimString(string.sub(macroParameters,1,(string.find(macroParameters," ") or 0)-1)) + local defineValue = string.sub(macroParameters,(string.find(macroParameters," ") or 0)+1) + if tonumber(defineName) then + self:Error("Bad idea to redefine numbers", + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + self.Defines[defineName] = defineValue + elseif macroName == "undef" then -- #undef + local defineName = trimString(string.sub(macroParameters,1,(string.find(macroParameters," ") or 0)-1)) + if tonumber(defineName) then + self:Error("Bad idea to undefine numbers", + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + self.Defines[defineName] = nil + elseif macroName == "ifdef" then -- #ifdef + local defineName = trimString(string.sub(macroParameters,1,(string.find(macroParameters," ") or 0)-1)) + if self.Defines[defineName] then + self.IFDEFLevel[#self.IFDEFLevel+1] = false + else + self.IFDEFLevel[#self.IFDEFLevel+1] = true + end + elseif macroName == "ifndef" then -- #ifndef + local defineName = trimString(string.sub(macroParameters,1,(string.find(macroParameters," ") or 0)-1)) + if not self.Defines[defineName] then + self.IFDEFLevel[#self.IFDEFLevel+1] = false + else + self.IFDEFLevel[#self.IFDEFLevel+1] = true + end + elseif macroName == "else" then -- #else + if #self.IFDEFLevel == 0 then + self:Error("Unexpected #else macro", + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + + self.IFDEFLevel[#self.IFDEFLevel] = not self.IFDEFLevel[#self.IFDEFLevel] + elseif macroName == "endif" then -- #endif + if #self.IFDEFLevel == 0 then + self:Error("Unexpected #endif macro", + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + + self.IFDEFLevel[#self.IFDEFLevel] = nil + elseif (macroName == "include") or + (macroName == "#include##") then -- #include or ZASM2 compatible ##include## + local symL,symR + local fileName + + -- ZASM2 compatibility syntax support + if macroName == "#include##" then + symL,symR = "<",">" + fileName = trimString(string.sub(macroParameters,1,-1)) + else + symL,symR = string.sub(macroParameters,1,1),string.sub(macroParameters,-1,-1) + fileName = trimString(string.sub(macroParameters,2,-2)) + end + + -- Full file name including the path to file + local fullFileName + if (symL == "\"") and (symR == "\"") then -- File relative to current one + fullFileName = self.WorkingDir..fileName + elseif (symL == "<") and (symR == ">") then -- File relative to root directory + fullFileName = fileName + else + self:Error("Invalid syntax for #include macro (wrong brackets)", + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + + -- Search for file on search paths + local fileText = self:LoadFile(fullFileName) + if (symL == "<") and (symR == ">") and (not fileText) then + for _,searchPath in pairs(self.SearchPaths) do + if not fileText then + fileText = self:LoadFile(searchPath.."\\"..fullFileName) + fileName = searchPath.."\\"..fullFileName + end + end + end + + -- Push this file on top of the stack + if fileText then + table.insert(self.Code, 1, { Text = fileText, Line = 1, Col = 1, File = fileName, NextCharPos = 1 }) + else + self:Error("Cannot open file: "..fileName, + macroPosition.Line,macroPosition.Col,macroPosition.File) + end + else + self:Error("Invalid macro: #"..macroName, + macroPosition.Line,macroPosition.Col,macroPosition.File) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_syntax.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_syntax.lua new file mode 100644 index 0000000..44ac905 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_syntax.lua @@ -0,0 +1,1513 @@ +-------------------------------------------------------------------------------- +-- ZASM2 compatible syntax +-------------------------------------------------------------------------------- + + + + +-- Syntax lookup for vector definitions +local VectorSyntax = { + FLOAT = { {} }, + SCALAR = { {} }, + VECTOR1F = { {"x"} }, + VECTOR2F = { {"x"},{"y"} }, + VECTOR3F = { {"x"},{"y"},{"z"} }, + VECTOR4F = { {"x"},{"y"},{"z"},{"w"} }, + VEC1F = { {"x"} }, + VEC2F = { {"x"},{"y"} }, + VEC3F = { {"x"},{"y"},{"z"} }, + VEC4F = { {"x"},{"y"},{"z"},{"w"} }, + UV = { {"x","u"},{"y","v"} }, + COLOR = { {"x","r"},{"y","g"},{"z","b"},{"w","a"} }, + MATRIX = {}, +} +for i=0,15 do VectorSyntax.MATRIX[i+1] = {tostring(i)} end + + + + +-------------------------------------------------------------------------------- +-- Compile an opcode (called after if self:MatchToken(TOKEN.OPCODE)) +function HCOMP:Opcode() local TOKEN = self.TOKEN + local opcodeName = self.TokenData + local opcodeNo = self.OpcodeNumber[self.TokenData] + local operandCount = self.OperandCount[opcodeNo] + + -- Check if opcode is obsolete or old + if self.OpcodeObsolete[opcodeName] then + self:Warning("Instruction \""..opcodeName.."\" is obsolete") + end + if self.OpcodeOld[opcodeName] then + self:Warning("Mnemonic \""..opcodeName.."\" is an old mnemonic for this instruction. Please use the newer mnemonic \""..self.OpcodeOld[opcodeName].."\".") + end + + -- Create leaf + local opcodeLeaf = self:NewLeaf() + opcodeLeaf.Opcode = opcodeName + opcodeLeaf.ExplictAssign = true + + -- Parse operands + for i=1,operandCount do + local segmentOffset,constantValue,expressionLeaf + local isMemoryReference,useSpecialMemorySyntax + + -- Check if it's a special memory reference ([<...>]) + if self:MatchToken(TOKEN.LSUBSCR) then + isMemoryReference = true + useSpecialMemorySyntax = true + end + + -- Check for segment prefix (ES:<...> or ES+<...>) + if ((self:PeekToken() == TOKEN.SEGMENT) or (self:PeekToken() == TOKEN.REGISTER)) and + ((self:PeekToken(1) == TOKEN.DCOLON) or + (useSpecialMemorySyntax and (self:PeekToken(1) == TOKEN.PLUS))) then -- next character is : or + + if self:MatchToken(TOKEN.SEGMENT) then + -- 1 to 8: CS .. LS + segmentOffset = self.TokenData + elseif self:MatchToken(TOKEN.REGISTER) then + if self.TokenData >= 96 then -- 17+: extended registers + segmentOffset = 17 + self.TokenData - 96 + else -- 9 to 16: EAX .. EBP + segmentOffset = self.TokenData + 8 + end + end + + if useSpecialMemorySyntax then + if not self:MatchToken(TOKEN.DCOLON) then self:ExpectToken(TOKEN.PLUS) end + else + self:ExpectToken(TOKEN.DCOLON) + end + end + + -- Check if it's a memory reference (#<...>) + if not useSpecialMemorySyntax then + if self:MatchToken(TOKEN.HASH) then isMemoryReference = true end + end + + -- Parse operand expression (use previous result if previous const wasnt related to seg offset) + local c,v,e = self:ConstantExpression() + if c then -- Constant value + if v + then constantValue = v -- Exact value + else constantValue = e -- Expression to be recalculated later + end + else -- Expression + expressionLeaf = self:Expression() + if expressionLeaf.Opcode then + self:Warning("Using complex expression as operand: might corrupt user register") + end + -- FIXME: warning about using extra registers? + end + + -- Check for segment prefix again (reversed syntax <...>:ES) + if self:MatchToken(TOKEN.DCOLON) then + if (not segmentOffset) and + ((self:PeekToken() == TOKEN.SEGMENT) or (self:PeekToken() == TOKEN.REGISTER)) then + if self:MatchToken(TOKEN.SEGMENT) then + -- 1 to 8: CS .. LS + segmentOffset = self.TokenData + elseif self:MatchToken(TOKEN.REGISTER) then + if self.TokenData >= 96 then -- 17+: extended registers + segmentOffset = 17 + self.TokenData - 96 + else -- 9 to 16: EAX .. EBP + segmentOffset = self.TokenData + 8 + end + end + else + self:Error("Invalid segment offset syntax") + end + end + + -- Trailing bracket for [...] memory syntax + if useSpecialMemorySyntax then + self:ExpectToken(TOKEN.RSUBSCR) + end + + -- Create operand + if isMemoryReference then + if expressionLeaf then + if expressionLeaf.Register then + opcodeLeaf.Operands[i] = { MemoryRegister = expressionLeaf.Register, Segment = segmentOffset } + else + opcodeLeaf.Operands[i] = { MemoryPointer = expressionLeaf, Segment = segmentOffset } + end + else + opcodeLeaf.Operands[i] = { MemoryPointer = constantValue, Segment = segmentOffset } + end + else + if expressionLeaf then + if expressionLeaf.Register then + if (expressionLeaf.Register >= 16) and (expressionLeaf.Register <= 23) and (segmentOffset) then + -- Swap EBX:ES with ES:EBX (because the former one is invalid in ZCPU) + local register = expressionLeaf.Register + local segment = segmentOffset + + -- Convert segment register index to register index + if (segment >= 1) and (segment <= 8) then expressionLeaf.Register = segment + 15 end + if (segment >= 9) and (segment <= 16) then expressionLeaf.Register = segment - 8 end + + -- Convert register index to segment register index + if (register >= 1) and (register <= 8) then segmentOffset = register + 8 end + if (register >= 16) and (register <= 23) then segmentOffset = register - 15 end + end + opcodeLeaf.Operands[i] = { Register = expressionLeaf.Register, Segment = segmentOffset } + else + if segmentOffset then + opcodeLeaf.Operands[i] = self:NewLeaf() + opcodeLeaf.Operands[i].Opcode = "add" + opcodeLeaf.Operands[i].Operands[1] = { Register = segmentOffset+15 } + opcodeLeaf.Operands[i].Operands[2] = expressionLeaf + else + opcodeLeaf.Operands[i] = expressionLeaf + end + end + else + opcodeLeaf.Operands[i] = { Constant = constantValue, Segment = segmentOffset } + end + end + + -- Attach information from expression + if expressionLeaf then + opcodeLeaf.Operands[i].PreviousLeaf = expressionLeaf.PreviousLeaf + end + + -- Syntax + if i < operandCount then + self:ExpectToken(TOKEN.COMMA) + else + if self:MatchToken(TOKEN.COMMA) then + self:Error("Invalid operand count") + end + end + end + + -- Check if first operand is a non-preserved register + if self.BusyRegisters then + if opcodeLeaf.Operands[1] and opcodeLeaf.Operands[1].Register and + (self.BusyRegisters[opcodeLeaf.Operands[1].Register] == false) and + (self.BlockDepth > 0) then + self:Warning("Warning: using an unpreserved register") + end + + if opcodeLeaf.Operands[1] and opcodeLeaf.Operands[1].MemoryRegister and + (self.BusyRegisters[opcodeLeaf.Operands[1].MemoryRegister] == false) and + (self.BlockDepth > 0) then + self:Warning("Warning: using an unpreserved register") + end + end + + -- Add opcode to tail + self:AddLeafToTail(opcodeLeaf) + self:MatchToken(TOKEN.COLON) + return true +end + + +-------------------------------------------------------------------------------- +-- Start a new block +function HCOMP:BlockStart(blockType) + if self.BlockDepth == 0 then + -- Create leaf that corresponds to ENTER instruction + self.HeadLeaf = self:NewLeaf() + self.HeadLeaf.Opcode = "enter" + self.HeadLeaf.Operands[1] = { Constant = self.Settings.MagicValue } + self:AddLeafToTail(self.HeadLeaf) + + self.LocalLabels = {} + self.StackPointer = 0 + if self.GenerateInlineFunction then + self.ParameterPointer = 0 -- Skip EBP + else + self.ParameterPointer = 1 -- Skip EBP and return address + end + + self.StringsTable = {} + + self.BlockType = {} + self.SpecialLeaf = {} + + -- Create busy registers list + self.BusyRegisters = { false,false,false,false,false,false,true,true } + end + + -- Create a leaf that corresponds to label for BREAK + local breakLeaf = self:NewLeaf() + breakLeaf.Opcode = "LABEL" + breakLeaf.Label = self:GetTempLabel() + breakLeaf.Label.Type = "Pointer" + breakLeaf.Label.Leaf = breakLeaf + + -- Create a leaf that corresponds to label for CONTINUE + local continueLeaf = self:NewLeaf() + continueLeaf.Opcode = "LABEL" + continueLeaf.Label = self:GetTempLabel() + continueLeaf.Label.Type = "Pointer" + continueLeaf.Label.Leaf = continueLeaf + self:AddLeafToTail(continueLeaf) + + -- Only FOR loops have step code + if (blockType == "WHILE") or + (blockType == "DO") then + self.CurrentStepLeaf = nil + end + + self.SpecialLeaf[#self.SpecialLeaf+1] = { + Break = breakLeaf, + Continue = continueLeaf, + JumpBack = self:NewLeaf(), + Step = self.CurrentStepLeaf, + } + + if (blockType == "FOR") or + (blockType == "WHILE") or + (blockType == "DO") then + self.CurrentContinueLeaf = self.SpecialLeaf[#self.SpecialLeaf].Continue + self.CurrentBreakLeaf = self.SpecialLeaf[#self.SpecialLeaf].Break + end + + -- Push block type + table.insert(self.BlockType,blockType or "FUNCTION") + self.BlockDepth = self.BlockDepth + 1 +end + + +-------------------------------------------------------------------------------- +-- End the block +function HCOMP:BlockEnd() + -- If required, end the previous block + local endPreviousBlock = self.SpecialLeaf[#self.SpecialLeaf].EndPreviousBlock + + -- If required, add leaf that jumps back to block start + if self.SpecialLeaf[#self.SpecialLeaf].JumpBack.Opcode ~= "INVALID" then + self.SpecialLeaf[#self.SpecialLeaf].JumpBack.CurrentPosition = self:CurrentSourcePosition() + self:AddLeafToTail(self.SpecialLeaf[#self.SpecialLeaf].JumpBack) + end + + -- Add leaf that corresponds to break label + self.SpecialLeaf[#self.SpecialLeaf].Break.CurrentPosition = self:CurrentSourcePosition() + self:AddLeafToTail(self.SpecialLeaf[#self.SpecialLeaf].Break) + + -- Pop current continue leaf if required + if self.CurrentContinueLeaf == self.SpecialLeaf[#self.SpecialLeaf].Continue then + if self.SpecialLeaf[#self.SpecialLeaf-1] then + self.CurrentContinueLeaf = self.SpecialLeaf[#self.SpecialLeaf-1].Continue + self.CurrentBreakLeaf = self.SpecialLeaf[#self.SpecialLeaf-1].Break + self.CurrentStepLeaf = self.SpecialLeaf[#self.SpecialLeaf-1].Step + else + self.CurrentContinueLeaf = nil + self.CurrentBreakLeaf = nil + self.CurrentStepLeaf = nil + end + end + + -- Pop unused leaves + self.SpecialLeaf[#self.SpecialLeaf] = nil + + -- Pop block type + local blockType = self.BlockType[#self.BlockType] + self.BlockType[#self.BlockType] = nil + + self.BlockDepth = self.BlockDepth - 1 + if self.BlockDepth == 0 then + -- Update head leaf with new stack data + self.HeadLeaf.Operands[1].Constant = -self.StackPointer + if (self.StackPointer == 0) and + (self.ParameterPointer == 0) and + (not self.Settings.AlwaysEnterLeave) then self.HeadLeaf.Opcode = "DATA" end + + -- Create leaf for exiting local scope + local leaveLeaf = self:NewLeaf() + leaveLeaf.Opcode = "leave" + if (self.StackPointer ~= 0) or + (self.ParameterPointer ~= 0) or + (self.Settings.AlwaysEnterLeave) then + self:AddLeafToTail(leaveLeaf) + end + + -- Create leaf for returning from call + if blockType == "FUNCTION" then + if not self.GenerateInlineFunction then + local retLeaf = self:NewLeaf() + retLeaf.Opcode = "ret" + self:AddLeafToTail(retLeaf) + end + end + + -- Write down strings table + for string,leaf in pairs(self.StringsTable) do + self:AddLeafToTail(leaf) + end + self.StringsTable = nil + + -- Add local labels to lookup list + for labelName,labelData in pairs(self.LocalLabels) do + self.DebugInfo.Labels["local."..labelName] = { StackOffset = labelData.StackOffset } + end + + self.LocalLabels = nil + self.StackPointer = nil + self.ParameterPointer = nil + + self.BlockType = nil + self.SpecialLeaf = nil + + -- Zap all registers preserved inside the function + self.BusyRegisters = nil + + -- Disable inlining + if self.GenerateInlineFunction then + self.Functions[self.GenerateInlineFunction].InlineCode = self.InlineFunctionCode + self.GenerateInlineFunction = nil + self.InlineFunctionCode = nil + end + + -- Disable parent label + self.CurrentParentLabel = nil + end + + -- End it, see first line of the function + if endPreviousBlock then + self:BlockEnd() + end +end + + +-------------------------------------------------------------------------------- +-- Parse ELSE clause +function HCOMP:ParseElse(parentBlockExists) + -- Add a jump over the else clause + local jumpLeaf = self:NewLeaf() + jumpLeaf.Opcode = "jmp" + self:AddLeafToTail(jumpLeaf) + + -- Alter the conditional jump so it goes to else clause + local jumpOverLabelLeaf = self:NewLeaf() + local jumpOverLabel = self:GetTempLabel() + jumpOverLabelLeaf.Opcode = "LABEL" + jumpOverLabel.Type = "Pointer" + jumpOverLabel.Leaf = jumpOverLabelLeaf + jumpOverLabelLeaf.Label = jumpOverLabel + self:AddLeafToTail(jumpOverLabelLeaf) + + if parentBlockExists and self.SpecialLeaf[#self.SpecialLeaf].ConditionalBreak then + self:AddLeafToTail(self.SpecialLeaf[#self.SpecialLeaf].ConditionalBreak) + end + + -- Enter the ELSE block + local needBlock = self:MatchToken(self.TOKEN.LBRACKET) + self:BlockStart("ELSE") + + -- Update properly the jump leaf + jumpLeaf.Operands[1] = { PointerToLabel = self.SpecialLeaf[#self.SpecialLeaf].Break.Label } + + if needBlock then + -- Special marker that means that ending this block ends previous one too + self.SpecialLeaf[#self.SpecialLeaf].EndPreviousBlock = parentBlockExists + else + -- Parse next statement if dont need a block + local previousBlockDepth = #self.BlockType + self:Statement() + + -- If did not enter any new blocks, it was a plain statement. Pop ELSE block + if #self.BlockType == previousBlockDepth then + -- End the ELSE block + self:BlockEnd() + + -- End the IF block, if required + if parentBlockExists then self:BlockEnd() end + else + -- Special marker that means that ending this block ends ELSE block early too + self.SpecialLeaf[#self.SpecialLeaf].EndPreviousBlock = true + -- Special marker that means that ending ELSE block ends previous one too + self.SpecialLeaf[#self.SpecialLeaf-1].EndPreviousBlock = parentBlockExists + end + end +end + + +-------------------------------------------------------------------------------- +function HCOMP:DeclareRegisterVariable() + if self.BlockDepth > 0 then + for reg=1,6 do + if not self.BusyRegisters[reg] then + self.BusyRegisters[reg] = true + return reg + end + end + self:Error("Out of free registers for declaring local variables") + else + self:Error("Unable to declare a register variable") + end +end + + +-------------------------------------------------------------------------------- +-- Compile a variable/function. Returns corresponding labels +function HCOMP:DefineVariable(isFunctionParam,isForwardDecl,isRegisterDecl,isStructMember) local TOKEN = self.TOKEN + local varType,varSize,isStruct + if self:MatchToken(TOKEN.IDENT) then -- Define structure + varType = self.TokenData + varSize = 0 -- Depends on pointer level + isStruct = true + else -- Define variable + self:ExpectToken(TOKEN.TYPE) + varType = self.TokenData + varSize = 1 + if varType == 5 then varSize = 4 end + end + + -- Variable labels list + local labelsList = {} + + -- Parse all variables to define + while true do + -- Get pointer level (0, *, **, ***, etc) + local pointerLevel = 0 + while self:MatchToken(TOKEN.TIMES) do pointerLevel = pointerLevel + 1 end + + -- Fix structure size + if isStruct then + if pointerLevel > 0 + then varSize = 1 + else varSize = self.StructSize[varType] + end + end + + -- Get variable name + self:ExpectToken(TOKEN.IDENT) + local varName = self.TokenData + + -- Try to read information about array size, stuff + local arraySize + while self:MatchToken(TOKEN.LSUBSCR) do -- varname[] + if self:MatchToken(TOKEN.RSUBSCR) then -- varname[] + if isFunctionParam then -- just a pointer to an array + pointerLevel = 1 + end + else + local c,v = self:ConstantExpression(true) -- need precise value here, no ptrs allowed + if c then + if not arraySize then arraySize = {} end + arraySize[#arraySize+1] = v + else + self:Error("Array size must be constant") + end + + self:ExpectToken(TOKEN.RSUBSCR) + end + end + + -- Calculate size of array + local bytesArraySize + if arraySize then + for k,v in pairs(arraySize) do + bytesArraySize = (bytesArraySize or 0) + v*varSize + end + end + + -- Add to global list + table.insert(labelsList,{ Name = varName, Type = varType, PtrLevel = pointerLevel, Size = bytesArraySize or varSize }) + + if not isStructMember then -- Do not define struct members + if self:MatchToken(TOKEN.LPAREN) then -- Define function + -- Create function entrypoint + local label + label = self:DefineLabel(varName) + + label.Type = "Pointer" + label.Defined = true + + -- Make all further leaves parented to this label + self.CurrentParentLabel = label + + -- Create label leaf + label.Leaf = self:NewLeaf() + label.Leaf.Opcode = "LABEL" + label.Leaf.Label = label + self:AddLeafToTail(label.Leaf) --isInlined + + -- Define a function + local _,functionVariables = nil,{} + + self:BlockStart() + if not self:MatchToken(TOKEN.RPAREN) then + _,functionVariables = self:DefineVariable(true) + self:ExpectToken(TOKEN.RPAREN) + + -- Add comments about function into assembly listing + if self.Settings.GenerateComments then + for i=1,#functionVariables do + label.Leaf.Comment = (label.Leaf.Comment or "")..(functionVariables[i].Name) + if i < #functionVariables then label.Leaf.Comment = label.Leaf.Comment.."," end + end + end + end + + -- Forward declaration, mess up label name + if isForwardDecl then + local newName = label.Name.."@" + for i=1,#functionVariables do + newName = newName..functionVariables[i].Name..functionVariables[i].Type + if i < #functionVariables then + newName = newName.."_" + end + end + self:RedefineLabel(label.Name,newName) + end + + -- Generate comment if required + if self.Settings.GenerateComments then label.Leaf.Comment = varName.."("..(label.Leaf.Comment or "")..")" end + self:ExpectToken(TOKEN.LBRACKET) + return true,functionVariables,varName,varType,pointerLevel + else -- Define variable + -- Check if there's an initializer + local initializerLeaves,initializerValues + if self:MatchToken(TOKEN.EQUAL) then + if not self.LocalLabels then -- Check rules for global init + if self:MatchToken(TOKEN.LBRACKET) then -- Array initializer + if not bytesArraySize then self:Error("Cannot initialize value: not an array") end + + initializerValues = {} + while not self:MatchToken(TOKEN.RBRACKET) do + local c,v = self:ConstantExpression(true) + if not c + then self:Error("Cannot have expressions in global initializers") + else table.insert(initializerValues,v) + end + self:MatchToken(TOKEN.COMMA) + end + else -- Single initializer + if bytesArraySize then self:Error("Cannot initialize value: is an array") end + + local c,v = self:ConstantExpression(true) + if not c then + -- initializerLeaves = { self:Expression() } + self:Error("Cannot have expressions in global initializers") + else + initializerValues = { v } + end + end + else -- Local init always an expression + if self:MatchToken(TOKEN.LBRACKET) then -- Array initializer + if not bytesArraySize then self:Error("Cannot initialize value: not an array") end + + initializerLeaves = {} + while not self:MatchToken(TOKEN.RBRACKET) do + table.insert(initializerLeaves,self:Expression()) + self:MatchToken(TOKEN.COMMA) + end + + if #initializerLeaves > 256 then + self:Error("Too much local variable initializers") + end + else + if bytesArraySize then self:Error("Cannot initialize value: is an array") end + initializerLeaves = { self:Expression() } + end + end + end + + -- Define a variable + if self.LocalLabels then -- check if var is local + local label = self:DefineLabel(varName,true) + + if isRegisterDecl then + label.Type = "Register" + label.Value = self:DeclareRegisterVariable() + if isStruct then self:Error("Cannot hold structure variables in registers - yet") end + else + label.Type = "Stack" + if isStruct and (pointerLevel > 0) then label.PointerToStruct = true end + end + label.Defined = true + if varType == 5 then label.ForceType = "vector" end + if isStruct then label.Struct = varType end + + -- If label has associated array size, mark it as an array + if bytesArraySize then label.Array = bytesArraySize end + + if not isRegisterDecl then + if not isFunctionParam then + -- Add a new local variable (stack pointer increments) + self.StackPointer = self.StackPointer - (bytesArraySize or varSize) + label.StackOffset = self.StackPointer + else + -- Add a new function variable + self.ParameterPointer = self.ParameterPointer + (bytesArraySize or varSize) + label.StackOffset = self.ParameterPointer + end + end + + -- Initialize local variable + if isRegisterDecl then + if initializerLeaves then + local movLeaf = self:NewLeaf() + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { Register = label.Value } + movLeaf.Operands[2] = initializerLeaves[1] + movLeaf.ExplictAssign = true + self:AddLeafToTail(movLeaf) + end + else + if initializerLeaves then + for i=1,#initializerLeaves do -- FIXME: find a nicer way to initialize + local movLeaf = self:NewLeaf() + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { Stack = label.StackOffset+i-1 } + movLeaf.Operands[2] = initializerLeaves[i] + movLeaf.ExplictAssign = true + self:AddLeafToTail(movLeaf) + end + for i=#initializerLeaves+1,bytesArraySize or 1 do + local movLeaf = self:NewLeaf() + movLeaf.Opcode = "mov" + movLeaf.Operands[1] = { Stack = label.StackOffset+i-1 } + movLeaf.Operands[2] = { Constant = 0 } + movLeaf.ExplictAssign = true + self:AddLeafToTail(movLeaf) + end + end + end + else + -- Define a new global variable + local label = self:DefineLabel(varName) + + if isRegisterDecl then + label.Type = "Register" + label.Value = self:DeclareRegisterVariable() + else + label.Type = "Variable" + if isStruct and (pointerLevel > 0) then label.PointerToStruct = true end + end + label.Defined = true + if varType == 5 then label.ForceType = "vector" end + if isStruct then label.Struct = varType end + + -- If label has associated array size, mark it as an array + if bytesArraySize then label.Array = bytesArraySize end + + -- Create initialization leaf + label.Leaf = self:NewLeaf() + label.Leaf.ParentLabel = self.CurrentParentLabel or label + label.Leaf.Opcode = "DATA" + if initializerValues then + label.Leaf.Data = initializerValues + label.Leaf.ZeroPadding = (bytesArraySize or varSize) - #initializerValues + else + label.Leaf.ZeroPadding = bytesArraySize or varSize + end + label.Leaf.Label = label + self:AddLeafToTail(label.Leaf) + end + end + else -- Struct member + -- Do nothing right now + end + + if not self:MatchToken(TOKEN.COMMA) then + return true,labelsList + else --int x, char y, float z + local nextToken,structName = self:PeekToken(0,true) + if (nextToken == TOKEN.IDENT) and (self.Structs[structName]) then + self:MatchToken(TOKEN.IDENT) + local structData = self.Structs[structName] + varType = self.TokenData + varSize = 0 + isStruct = true + elseif self:MatchToken(TOKEN.TYPE) then + varType = self.TokenData + varSize = 1 + if varType == 5 then varSize = 4 end + isStruct = false + end + end + end +end + + +-------------------------------------------------------------------------------- +-- Compile a single statement +function HCOMP:Statement() local TOKEN = self.TOKEN + -- Parse code for absolute labels and define (LABEL:) + if self.CurrentToken == 1 then + while not(self:MatchToken(TOKEN.EOF)) do + if self:MatchToken(TOKEN.IDENT) then + if(self:PeekToken() == TOKEN.DCOLON) then + local label = self:DefineLabel(self.TokenData) + label.Type = "Pointer" + label.Defined = true + else + self:PreviousToken() + end + end + self:NextToken() + end + self.CurrentToken = 1 + end + + + -- Parse end of line colon + if self:MatchToken(TOKEN.COLON) then return true end + + -- Check for EOF + if self:MatchToken(TOKEN.EOF) then return false end + + -- Parse variable/function definition + local exportSymbol = self:MatchToken(TOKEN.EXPORT) + local inlineFunction = self:MatchToken(TOKEN.INLINE) + local forwardFunction = self:MatchToken(TOKEN.FORWARD) + local registerValue = self:MatchToken(TOKEN.LREGISTER) + + if self:PeekToken() == TOKEN.TYPE then + if inlineFunction then + self.GenerateInlineFunction = true + self.InlineFunctionCode = {} + end + + local isDefined,variableList,functionName,returnType,returnPtrLevel = self:DefineVariable(false,forwardFunction,registerValue) + if isDefined then + if functionName then + self.Functions[functionName] = { + FunctionName = functionName, + Parameters = variableList, + ReturnType = returnType, + ReturnPtrLevel = returnPtrLevel, + } + if exportSymbol then + self.ExportedSymbols[functionName] = self.Functions[functionName] + end + if inlineFunction then + self.GenerateInlineFunction = functionName + end + else + if exportSymbol then + self:Error("Exporting variables not supported right now by the compiler") + end + end + end + + if inlineFunction and (not functionName) then + self:Error("Can only inline functions") + end + if forwardFunction and (not functionName) then + self:Error("Can only forward-declare functions") + end + return isDefined + end + + -- Peek structure definition + local nextToken,structName = self:PeekToken(0,true) + if (nextToken == TOKEN.IDENT) and (self.Structs[structName]) then + self:DefineVariable() + return true + end + + if inlineFunction or exportSymbol or forwardFunction or registerValue then + self:Error("Function definition or symbol definition expected") + end + + -- Parse preserve/zap + if self:MatchToken(TOKEN.PRESERVE) or self:MatchToken(TOKEN.ZAP) then + local tokenType = self.TokenType + if self.BlockDepth > 0 then + while self:MatchToken(TOKEN.REGISTER) do + self.BusyRegisters[self.TokenData] = tokenType == TOKEN.PRESERVE + self:MatchToken(TOKEN.COMMA) + end + self:MatchToken(TOKEN.COLON) + return true + else + self:Error("Can only zap/preserve registers inside functions/local blocks") + end + end + + -- Parse assembly instruction + if self:MatchToken(TOKEN.OPCODE) then return self:Opcode() end + + -- Parse STRUCT macro + if self:MatchToken(TOKEN.STRUCT) then + self:ExpectToken(TOKEN.IDENT) + local structName = self.TokenData + + -- Create structure + self.Structs[structName] = {} + self.StructSize[structName] = 0 + + -- Populate structure + self:ExpectToken(TOKEN.LBRACKET) + while (not self:MatchToken(TOKEN.RBRACKET)) and (not self:MatchToken(TOKEN.EOF)) do + local _,variableList = self:DefineVariable(false,false,false,true) + for _,variableData in ipairs(variableList) do + variableData.Offset = self.StructSize[structName] + self.Structs[structName][variableData.Name] = variableData + self.StructSize[structName] = self.StructSize[structName] + variableData.Size + end + self:ExpectToken(TOKEN.COLON) + end + return true + end + + -- Parse VECTOR macro + if self:MatchToken(TOKEN.VECTOR) then + if self.BlockDepth > 0 then + self:Warning("Defining a vector inside a function block might cause issues") + end + + -- Vector type (VEC2F, etc) + local vectorType = self.TokenData + + -- Vector name + self:ExpectToken(TOKEN.IDENT) + local vectorName = self.TokenData + + -- Create leaf and label for vector name + local vectorNameLabelLeaf = self:NewLeaf() + vectorNameLabelLeaf.Opcode = "LABEL" + + local vectorNameLabel = self:DefineLabel(vectorName) + vectorNameLabel.Type = "Pointer" + vectorNameLabel.Defined = true + vectorNameLabel.Leaf = vectorNameLabelLeaf + vectorNameLabel.DebugAsVector = #VectorSyntax[vectorType] + vectorNameLabelLeaf.Label = vectorNameLabel + self:AddLeafToTail(vectorNameLabelLeaf) + + -- Create leaves for all vector labels and their data + local vectorLeaves = {} + for index,labelNames in pairs(VectorSyntax[vectorType]) do + -- Create leaves for labels + for labelIndex,labelName in pairs(labelNames) do + local vectorLabelLeaf = self:NewLeaf() + vectorLabelLeaf.Opcode = "LABEL" + + local vectorLabel = self:GetLabel(vectorName.."."..labelName) + vectorLabel.Type = "Pointer" + vectorLabel.Defined = true + vectorLabel.Leaf = vectorLabelLeaf + vectorLabelLeaf.Label = vectorLabel + self:AddLeafToTail(vectorLabelLeaf) + end + + -- Create leaf for data + vectorLeaves[index] = self:NewLeaf() + vectorLeaves[index].Opcode = "DATA" + vectorLeaves[index].Data = { 0 } + self:AddLeafToTail(vectorLeaves[index]) + + if vectorType == "COLOR" then + vectorLeaves[index].Data = { 255 } + end + end + + -- Parse initialization + self.MostLikelyConstantExpression = true + if self:MatchToken(TOKEN.COMMA) then + for index,labelNames in pairs(VectorSyntax[vectorType]) do + local c,v,e = self:ConstantExpression(false) + if c then + vectorLeaves[index].Data[1] = v or e + else + self:Error("Vector initialization must be constant") + end + + if (index == #VectorSyntax[vectorType]) and self:MatchToken(TOKEN.COMMA) then + self:Error("Too much values for intialization") + end + if (index < #VectorSyntax[vectorType]) and (not self:MatchToken(TOKEN.COMMA)) then + return true + end + end + end + self.MostLikelyConstantExpression = false + return true + end + + -- Parse DATA macro + if self:MatchToken(TOKEN.DATA) then + local jmpLeaf = self:NewLeaf() + jmpLeaf.Opcode = "jmp" + jmpLeaf.Operands[1] = { + Constant = {{ Type = TOKEN.IDENT, Data = "_code", Position = self:CurrentSourcePosition() }} + } + self:AddLeafToTail(jmpLeaf) + return true + end + + -- Parse CODE macro + if self:MatchToken(TOKEN.CODE) then + local label = self:DefineLabel("_code") + label.Type = "Pointer" + + label.Leaf = self:NewLeaf() + label.Leaf.Opcode = "LABEL" + label.Leaf.Label = label + self:AddLeafToTail(label.Leaf) + return true + end + + -- Parse ORG macro + if self:MatchToken(TOKEN.ORG) then + -- org x + local markerLeaf = self:NewLeaf() + markerLeaf.Opcode = "MARKER" + + local c,v = self:ConstantExpression(true) + if c then markerLeaf.SetWritePointer = v + else self:Error("ORG offset must be constant") end + + self:AddLeafToTail(markerLeaf) + return true + end + + -- Parse OFFSET macro + if self:MatchToken(TOKEN.OFFSET) then + -- offset x + local markerLeaf = self:NewLeaf() + markerLeaf.Opcode = "MARKER" + + local c,v = self:ConstantExpression(true) + if c then markerLeaf.SetPointerOffset = v + else self:Error("OFFSET offset must be constant") end + + self:AddLeafToTail(markerLeaf) + return true + end + + -- Parse DB macro + if self:MatchToken(TOKEN.DB) then + -- db 1,... + self.IgnoreStringInExpression = true + self.MostLikelyConstantExpression = true + local dbLeaf = self:NewLeaf() + dbLeaf.Opcode = "DATA" + dbLeaf.Data = {} + local c,v,e = self:ConstantExpression(false) + while c or (self:PeekToken() == TOKEN.STRING) do + -- Insert data into leaf + if self:MatchToken(TOKEN.STRING) then + table.insert(dbLeaf.Data,self.TokenData) + else + table.insert(dbLeaf.Data,v or e) + end + + -- Only keep parsing if next token is comma + if self:MatchToken(TOKEN.COMMA) then + c,v,e = self:ConstantExpression(false) + else + c = false + end + end + self.IgnoreStringInExpression = false + self.MostLikelyConstantExpression = false + + self:AddLeafToTail(dbLeaf) + return true + end + + -- Parse STRING macro + if self:MatchToken(TOKEN.STRALLOC) then + -- string name,1,... + self:ExpectToken(TOKEN.IDENT) + + -- Create leaf and label for vector name + local stringNameLabelLeaf = self:NewLeaf() + stringNameLabelLeaf.Opcode = "LABEL" + + local stringNameLabel = self:DefineLabel(self.TokenData) + stringNameLabel.Type = "Pointer" + stringNameLabel.Defined = true + stringNameLabel.Leaf = stringNameLabelLeaf + stringNameLabelLeaf.Label = stringNameLabel + self:AddLeafToTail(stringNameLabelLeaf) + self:ExpectToken(TOKEN.COMMA) + + self.IgnoreStringInExpression = true + self.MostLikelyConstantExpression = true + local stringLeaf = self:NewLeaf() + stringLeaf.Opcode = "DATA" + stringLeaf.Data = {} + local c,v,e = self:ConstantExpression(false) + while c or (self:PeekToken() == TOKEN.STRING) do + -- Insert data into leaf + if self:MatchToken(TOKEN.STRING) then + table.insert(stringLeaf.Data,self.TokenData) + else + table.insert(stringLeaf.Data,v or e) + end + + -- Only keep parsing if next token is comma + if self:MatchToken(TOKEN.COMMA) then + c,v,e = self:ConstantExpression(false) + else + c = false + end + end + table.insert(stringLeaf.Data,0) + self.IgnoreStringInExpression = false + self.MostLikelyConstantExpression = false + + self:AddLeafToTail(stringLeaf) + return true + end + + -- Parse DEFINE macro + if self:MatchToken(TOKEN.DEFINE) then + -- define label,value + self:ExpectToken(TOKEN.IDENT) + local defineLabel = self:DefineLabel(self.TokenData) + defineLabel.Type = "Pointer" + defineLabel.Defined = true + + self:ExpectToken(TOKEN.COMMA) + + self.MostLikelyConstantExpression = true + local c,v,e = self:ConstantExpression(false) + if c then + if v then + defineLabel.Value = v + else + defineLabel.Expression = e + end + else + self:Error("Define value must be constant") + end + self.MostLikelyConstantExpression = false + + return true + end + + -- Parse ALLOC macro + if self:MatchToken(TOKEN.ALLOC) then + -- alloc label,size,value + -- alloc label,value + -- alloc label + -- alloc size + local allocLeaf = self:NewLeaf() + local allocLabel,allocSize,allocValue = nil,1,0 + local expectSize = false + allocLeaf.Opcode = "DATA" + + -- Add a label to this alloc + if self:MatchToken(TOKEN.IDENT) then + allocLabel = self:DefineLabel(self.TokenData) + allocLabel.Type = "Pointer" + allocLabel.Defined = true + allocLabel.DebugAsVariable = true + + allocLabel.Leaf = allocLeaf + allocLeaf.Label = allocLabel + + if self:MatchToken(TOKEN.COMMA) then expectSize = true end + end + + -- Read size + self.MostLikelyConstantExpression = true + if (not allocLabel) or (expectSize) then + local c,v = self:ConstantExpression(true) -- need precise value here, no ptrs allowed + if c then allocSize = v + else self:Error("Alloc size must be constant") end + end + + if allocLabel and expectSize then + if self:MatchToken(TOKEN.COMMA) then + local c,v = self:ConstantExpression(true) -- need precise value here, no ptrs allowed + if c then allocValue = v + else self:Error("Alloc value must be constant") end + else + allocValue = allocSize + allocSize = 1 + end + end + self.MostLikelyConstantExpression = false + + -- Initialize alloc + allocLeaf.ZeroPadding = allocSize + self:AddLeafToTail(allocLeaf) + return true + end + + + + + + -- Parse RETURN + if self:MatchToken(TOKEN.RETURN) and self.HeadLeaf then + if not self:MatchToken(TOKEN.COLON) then + local returnExpression = self:Expression() + local returnLeaf = self:NewLeaf() + returnLeaf.Opcode = "mov" + returnLeaf.Operands[1] = { Register = 1 } + returnLeaf.Operands[2] = returnExpression + returnLeaf.ExplictAssign = true + self:AddLeafToTail(returnLeaf) + end + self:MatchToken(TOKEN.COLON) + + -- Check if this is the last return in the function +-- if self:MatchToken(TOKEN.RBRACKET) then +-- if self.BlockDepth > 0 then +-- self:BlockEnd() +-- return true +-- else +-- self:Error("Unexpected bracket") +-- end +-- end + + + if not self.GenerateInlineFunction then + -- Create leaf for exiting local scope + local leaveLeaf = self:NewLeaf() + leaveLeaf.Opcode = "leave" + if (self.StackPointer ~= 0) or + (self.ParameterPointer ~= 0) or + (self.Settings.AlwaysEnterLeave) then + self:AddLeafToTail(leaveLeaf) + end + + -- Create leaf for returning from call + local retLeaf = self:NewLeaf() + retLeaf.Opcode = "ret" + self:AddLeafToTail(retLeaf) + end + + return true + end + + -- Parse IF syntax + if self:MatchToken(TOKEN.IF) then + -- Parse condition + self:ExpectToken(TOKEN.LPAREN) + local firstToken = self.CurrentToken + self:SaveParserState() + local conditionLeaf = self:Expression() + local conditionText = "if ("..self:PrintTokens(self:GetSavedTokens(firstToken))..")" + self:ExpectToken(TOKEN.RPAREN) + + -- Enter the IF block + local needBlock = self:MatchToken(TOKEN.LBRACKET) + self:BlockStart("IF") + + -- Calculate condition + local cmpLeaf = self:NewLeaf() + cmpLeaf.Opcode = "cmp" + cmpLeaf.Operands[1] = { Constant = 0 } + cmpLeaf.Operands[2] = conditionLeaf + cmpLeaf.Comment = conditionText + self:AddLeafToTail(cmpLeaf) + + -- Create label for conditional break (if condition is false) + local conditionalBreakLeaf = self:NewLeaf() + local conditionalBreak = self:GetTempLabel() + conditionalBreakLeaf.Opcode = "LABEL" + conditionalBreak.Type = "Pointer" + conditionalBreak.Leaf = conditionalBreakLeaf + conditionalBreakLeaf.Label = conditionalBreak + self.SpecialLeaf[#self.SpecialLeaf].ConditionalBreak = conditionalBreakLeaf +-- self:AddLeafToTail(conditionalBreakLeaf) + + -- Generate conditional jump over the block + local jumpLeaf = self:NewLeaf() + jumpLeaf.Opcode = "jge" + jumpLeaf.Operands[1] = { PointerToLabel = conditionalBreakLeaf.Label } + self:AddLeafToTail(jumpLeaf) + + if not needBlock then + -- Parse next statement if dont need a block + self:Statement() + + -- End the IF block early + self:BlockEnd() + + -- Add exit label + self:AddLeafToTail(conditionalBreakLeaf) + + -- Check for out-of-block ELSE + if self:MatchToken(TOKEN.ELSE) then + self:ParseElse(false) + end +-- else +-- self:AddLeafToTail(conditionalBreak) +-- self.SpecialLeaf[#self.SpecialLeaf].ConditionalBreak = jumpLeaf + end + + return true + end + + -- Parse WHILE syntax + if self:MatchToken(TOKEN.WHILE) then + local returnLabel + + -- Parse condition + self:ExpectToken(TOKEN.LPAREN) + local firstToken = self.CurrentToken + self:SaveParserState() + local conditionLeaf = self:Expression() + local conditionText = "if ("..self:PrintTokens(self:GetSavedTokens(firstToken)) + self:ExpectToken(TOKEN.RPAREN) + + -- Enter the WHILE block + local needBlock = self:MatchToken(TOKEN.LBRACKET) + if needBlock then + self:BlockStart("WHILE") + end + + if not needBlock then + -- Generate return label + local returnLabelLeaf = self:NewLeaf() + returnLabel = self:GetTempLabel() + returnLabelLeaf.Opcode = "LABEL" + returnLabel.Type = "Pointer" + returnLabel.Leaf = returnLabelLeaf + returnLabelLeaf.Label = returnLabel + self:AddLeafToTail(returnLabelLeaf) + end + + -- Calculate condition + local cmpLeaf = self:NewLeaf() + cmpLeaf.Opcode = "cmp" + cmpLeaf.Operands[1] = { Constant = 0 } + cmpLeaf.Operands[2] = conditionLeaf + cmpLeaf.Comment = conditionText + self:AddLeafToTail(cmpLeaf) + + if not needBlock then + -- Generate conditional jump over the block + local jumpOverLabelLeaf = self:NewLeaf() + local jumpOverLabel = self:GetTempLabel() + jumpOverLabelLeaf.Opcode = "LABEL" + jumpOverLabel.Type = "Pointer" + jumpOverLabel.Leaf = jumpOverLabelLeaf + jumpOverLabelLeaf.Label = jumpOverLabel + + local jumpOverLeaf = self:NewLeaf() + jumpOverLeaf.Opcode = "jz" + jumpOverLeaf.Operands[1] = { PointerToLabel = jumpOverLabel } + self:AddLeafToTail(jumpOverLeaf) + + -- Parse next statement if dont need a block + self:Statement() + + -- Generate the jump back leaf + local jumpBackLeaf = self:NewLeaf() + jumpBackLeaf.Opcode = "jmp" + jumpBackLeaf.Operands[1] = { PointerToLabel = returnLabel } + self:AddLeafToTail(jumpBackLeaf) + + -- Add exit label + self:AddLeafToTail(jumpOverLabelLeaf) + else + -- Generate conditional jump over the block + local jumpOverLeaf = self:NewLeaf() + jumpOverLeaf.Opcode = "jz" + jumpOverLeaf.Operands[1] = { PointerToLabel = self.SpecialLeaf[#self.SpecialLeaf].Break.Label } + self:AddLeafToTail(jumpOverLeaf) + + -- Set the jump back leaf + self.SpecialLeaf[#self.SpecialLeaf].JumpBack.Opcode = "jmp" + self.SpecialLeaf[#self.SpecialLeaf].JumpBack.Operands[1] = { PointerToLabel = self.SpecialLeaf[#self.SpecialLeaf].Continue.Label } + end + + return true + end + + -- Parse FOR syntax + if self:MatchToken(TOKEN.FOR) then + local returnLabel + + -- Parse syntax + self:ExpectToken(TOKEN.LPAREN) + local initLeaf = self:Expression() + initLeaf.Comment = "init loop" + self:ExpectToken(TOKEN.COLON) + local conditionLeaf = self:Expression() + conditionLeaf.Comment = "condition" + self:ExpectToken(TOKEN.COLON) + local stepLeaf = self:Expression() + stepLeaf.Comment = "loop step" + self:ExpectToken(TOKEN.RPAREN) + + self:AddLeafToTail(initLeaf) + + -- Save stepLeaf for inlining continue + self.CurrentStepLeaf = stepLeaf + + -- Enter the FOR block + local needBlock = self:MatchToken(TOKEN.LBRACKET) + if needBlock then + self:BlockStart("FOR") + end + + if not needBlock then + -- Generate return label + local returnLabelLeaf = self:NewLeaf() + returnLabel = self:GetTempLabel() + returnLabelLeaf.Opcode = "LABEL" + returnLabel.Type = "Pointer" + returnLabel.Leaf = returnLabelLeaf + returnLabelLeaf.Label = returnLabel + self:AddLeafToTail(returnLabelLeaf) + end + + -- Calculate condition + local cmpLeaf = self:NewLeaf() + cmpLeaf.Opcode = "cmp" + cmpLeaf.Operands[1] = { Constant = 0 } + cmpLeaf.Operands[2] = conditionLeaf + self:AddLeafToTail(cmpLeaf) + + if not needBlock then + -- Generate conditional jump over the block + local jumpOverLabelLeaf = self:NewLeaf() + local jumpOverLabel = self:GetTempLabel() + jumpOverLabelLeaf.Opcode = "LABEL" + jumpOverLabel.Type = "Pointer" + jumpOverLabel.Leaf = jumpOverLabelLeaf + jumpOverLabelLeaf.Label = jumpOverLabel + + local jumpOverLeaf = self:NewLeaf() + jumpOverLeaf.Opcode = "jz" + jumpOverLeaf.Operands[1] = { PointerToLabel = jumpOverLabel } + self:AddLeafToTail(jumpOverLeaf) + + -- Parse next statement if dont need a block + self:Statement() + + -- Generate the jump back leaf + local jumpBackLeaf = self:NewLeaf() + jumpBackLeaf.Opcode = "jmp" + jumpBackLeaf.Operands[1] = { PointerToLabel = returnLabel } + self:AddLeafToTail(stepLeaf) + self:AddLeafToTail(jumpBackLeaf) + + -- Add exit label + self:AddLeafToTail(jumpOverLabelLeaf) + else + -- Generate conditional jump over the block + local jumpOverLeaf = self:NewLeaf() + jumpOverLeaf.Opcode = "jz" + jumpOverLeaf.Operands[1] = { PointerToLabel = self.SpecialLeaf[#self.SpecialLeaf].Break.Label } + self:AddLeafToTail(jumpOverLeaf) + + -- Set the jump back leaf + self.SpecialLeaf[#self.SpecialLeaf].JumpBack.Opcode = "jmp" + self.SpecialLeaf[#self.SpecialLeaf].JumpBack.Operands[1] = { PointerToLabel = self.SpecialLeaf[#self.SpecialLeaf].Continue.Label } + self.SpecialLeaf[#self.SpecialLeaf].JumpBack.PreviousLeaf = stepLeaf + end + + return true + end + + -- Parse CONTINUE + if self:MatchToken(TOKEN.CONTINUE) then + if (self.BlockDepth > 0) and (self.CurrentContinueLeaf) then + local jumpBackLeaf = self:NewLeaf() + jumpBackLeaf.Opcode = "jmp" + jumpBackLeaf.Operands[1] = { PointerToLabel = self.CurrentContinueLeaf.Label } + + if (self.CurrentStepLeaf) then + self:AddLeafToTail(self.CurrentStepLeaf) + end + + self:AddLeafToTail(jumpBackLeaf) + return true + else + self:Error("Nowhere to continue here") + end + end + + -- Parse BREAK + if self:MatchToken(TOKEN.BREAK) then + if (self.BlockDepth > 0) and (self.CurrentBreakLeaf) then + local jumpLeaf = self:NewLeaf() + jumpLeaf.Opcode = "jmp" + jumpLeaf.Operands[1] = { PointerToLabel = self.CurrentBreakLeaf.Label } + self:AddLeafToTail(jumpLeaf) + return true + else + self:Error("Nowhere to break from here") + end + end + + -- Parse GOTO + if self:MatchToken(TOKEN.GOTO) then + local gotoExpression = self:Expression() + + local jumpLeaf = self:NewLeaf() + jumpLeaf.Opcode = "jmp" + jumpLeaf.Operands[1] = gotoExpression + self:AddLeafToTail(jumpLeaf) + return true + end + + -- Parse block open bracket + if self:MatchToken(TOKEN.LBRACKET) then + self:BlockStart("LBLOCK") + return true + end + + -- Parse block close bracket + if self:MatchToken(TOKEN.RBRACKET) then + if self.BlockDepth > 0 then + local blockType = self.BlockType[#self.BlockType] + if (blockType == "IF") and self:MatchToken(TOKEN.ELSE) then -- Add ELSE block, IF remains in stack + self:ParseElse(true) + else + if blockType == "IF" then -- FIXME: It kind of is redundant + self:AddLeafToTail(self.SpecialLeaf[#self.SpecialLeaf].ConditionalBreak) + end + self:BlockEnd() + end + return true + else + self:Error("Unexpected bracket") + end + end + + -- Parse possible label definition + local firstToken = self.CurrentToken + self:SaveParserState() + + if self:MatchToken(TOKEN.IDENT) then + if (self:PeekToken() == TOKEN.COMMA) then + -- Label definition for sure + while true do + local label = self:DefineLabel(self.TokenData) + label.Type = "Pointer" + label.Defined = true + + label.Leaf = self:NewLeaf() + label.Leaf.Opcode = "LABEL" + label.Leaf.Label = label + self:AddLeafToTail(label.Leaf) + + self:MatchToken(TOKEN.COMMA) + if not self:MatchToken(TOKEN.IDENT) then break end + end + self:MatchToken(TOKEN.COLON) + return true + elseif (self:PeekToken() == TOKEN.DCOLON) then + local label = self:GetLabel(self.TokenData) + label.Leaf = self:NewLeaf() + label.Leaf.Opcode = "LABEL" + label.Leaf.Label = label + self:AddLeafToTail(label.Leaf) + self:ExpectToken(TOKEN.DCOLON) + return true + else + self:RestoreParserState() + end + end + + -- If nothing else, must be some kind of an expression + local expressionLeaf = self:Expression() + self:AddLeafToTail(expressionLeaf) + + -- Add expression to leaf comment + if self.Settings.GenerateComments then + expressionLeaf.Comment = self:PrintTokens(self:GetSavedTokens(firstToken)) + end + + -- Skip a colon + self:MatchToken(TOKEN.COLON) + return true +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_tokenizer.lua b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_tokenizer.lua new file mode 100644 index 0000000..b4ba79f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/hlzasm/hc_tokenizer.lua @@ -0,0 +1,585 @@ +-------------------------------------------------------------------------------- +-- HCOMP / HL-ZASM compiler +-- +-- Tokenizer +-------------------------------------------------------------------------------- + + + + +-------------------------------------------------------------------------------- +-- All symbols (tokens) recognized by parser +HCOMP.TOKEN_TEXT = {} +HCOMP.TOKEN_TEXT["IDENT"] = {{"ZASM","HLZASM"},{}} -- ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz _ +HCOMP.TOKEN_TEXT["NUMBER"] = {{"ZASM","HLZASM"},{}} -- 0123456789 +HCOMP.TOKEN_TEXT["LPAREN"] = {{"ZASM","HLZASM"},{"("}} +HCOMP.TOKEN_TEXT["RPAREN"] = {{"ZASM","HLZASM"},{")"}} +HCOMP.TOKEN_TEXT["LBRACKET"] = {{ "HLZASM"},{"{"}} +HCOMP.TOKEN_TEXT["RBRACKET"] = {{ "HLZASM"},{"}"}} +HCOMP.TOKEN_TEXT["LSUBSCR"] = {{"ZASM","HLZASM"},{"["}} +HCOMP.TOKEN_TEXT["RSUBSCR"] = {{"ZASM","HLZASM"},{"]"}} +HCOMP.TOKEN_TEXT["COLON"] = {{"ZASM","HLZASM"},{";"}} +HCOMP.TOKEN_TEXT["DCOLON"] = {{"ZASM","HLZASM"},{":"}} +HCOMP.TOKEN_TEXT["HASH"] = {{"ZASM","HLZASM"},{"#"}} +HCOMP.TOKEN_TEXT["TIMES"] = {{"ZASM","HLZASM"},{"*"}} +HCOMP.TOKEN_TEXT["SLASH"] = {{"ZASM","HLZASM"},{"/"}} +HCOMP.TOKEN_TEXT["MODULUS"] = {{ "HLZASM"},{"%"}} +HCOMP.TOKEN_TEXT["PLUS"] = {{"ZASM","HLZASM"},{"+"}} +HCOMP.TOKEN_TEXT["MINUS"] = {{"ZASM","HLZASM"},{"-"}} +HCOMP.TOKEN_TEXT["AND"] = {{ "HLZASM"},{"&"}} +HCOMP.TOKEN_TEXT["OR"] = {{ "HLZASM"},{"|"}} +HCOMP.TOKEN_TEXT["XOR"] = {{ "HLZASM"},{"^"}} +HCOMP.TOKEN_TEXT["POWER"] = {{ "HLZASM"},{"^^"}} +HCOMP.TOKEN_TEXT["INC"] = {{ "HLZASM"},{"++"}} +HCOMP.TOKEN_TEXT["DEC"] = {{ "HLZASM"},{"--"}} +HCOMP.TOKEN_TEXT["SHL"] = {{ "HLZASM"},{"<<"}} +HCOMP.TOKEN_TEXT["SHR"] = {{ "HLZASM"},{">>"}} +HCOMP.TOKEN_TEXT["EQL"] = {{ "HLZASM"},{"=="}} +HCOMP.TOKEN_TEXT["NEQ"] = {{ "HLZASM"},{"!="}} +HCOMP.TOKEN_TEXT["LEQ"] = {{ "HLZASM"},{"<="}} +HCOMP.TOKEN_TEXT["LSS"] = {{ "HLZASM"},{"<"}} +HCOMP.TOKEN_TEXT["GEQ"] = {{ "HLZASM"},{">="}} +HCOMP.TOKEN_TEXT["GTR"] = {{ "HLZASM"},{">"}} +HCOMP.TOKEN_TEXT["NOT"] = {{ "HLZASM"},{"!"}} +HCOMP.TOKEN_TEXT["EQUAL"] = {{ "HLZASM"},{"="}} +HCOMP.TOKEN_TEXT["LAND"] = {{ "HLZASM"},{"&&"}} +HCOMP.TOKEN_TEXT["LOR"] = {{ "HLZASM"},{"||"}} +HCOMP.TOKEN_TEXT["EQLADD"] = {{ "HLZASM"},{"+="}} +HCOMP.TOKEN_TEXT["EQLSUB"] = {{ "HLZASM"},{"-="}} +HCOMP.TOKEN_TEXT["EQLMUL"] = {{ "HLZASM"},{"*="}} +HCOMP.TOKEN_TEXT["EQLDIV"] = {{ "HLZASM"},{"/="}} +HCOMP.TOKEN_TEXT["COMMA"] = {{"ZASM","HLZASM"},{","}} +HCOMP.TOKEN_TEXT["DOT"] = {{"ZASM","HLZASM"},{"."}} + +HCOMP.TOKEN_TEXT["GOTO"] = {{"HLZASM"},{"GOTO"}} +HCOMP.TOKEN_TEXT["FOR"] = {{"HLZASM"},{"FOR"}} +HCOMP.TOKEN_TEXT["IF"] = {{"HLZASM"},{"IF"}} +HCOMP.TOKEN_TEXT["ELSE"] = {{"HLZASM"},{"ELSE"}} +HCOMP.TOKEN_TEXT["WHILE"] = {{"HLZASM"},{"WHILE"}} +HCOMP.TOKEN_TEXT["DO"] = {{"HLZASM"},{"DO"}} +HCOMP.TOKEN_TEXT["SWITCH"] = {{"HLZASM"},{"SWITCH"}} +HCOMP.TOKEN_TEXT["CASE"] = {{"HLZASM"},{"CASE"}} +HCOMP.TOKEN_TEXT["CONST"] = {{"HLZASM"},{"CONST"}} +HCOMP.TOKEN_TEXT["RETURN"] = {{"HLZASM"},{"RETURN"}} +HCOMP.TOKEN_TEXT["BREAK"] = {{"HLZASM"},{"BREAK"}} +HCOMP.TOKEN_TEXT["CONTINUE"] = {{"HLZASM"},{"CONTINUE"}} +HCOMP.TOKEN_TEXT["EXPORT"] = {{"HLZASM"},{"EXPORT"}} +HCOMP.TOKEN_TEXT["INLINE"] = {{"HLZASM"},{"INLINE"}} +HCOMP.TOKEN_TEXT["FORWARD"] = {{"HLZASM"},{"FORWARD"}} +HCOMP.TOKEN_TEXT["LREGISTER"] = {{"HLZASM"},{"REGISTER"}} +HCOMP.TOKEN_TEXT["STRUCT"] = {{"HLZASM"},{"STRUCT"}} + +HCOMP.TOKEN_TEXT["DB"] = {{"ZASM","HLZASM"},{"DB"}} +HCOMP.TOKEN_TEXT["ALLOC"] = {{"ZASM","HLZASM"},{"ALLOC"}} +HCOMP.TOKEN_TEXT["VECTOR"] = {{"ZASM","HLZASM"},{"SCALAR","VECTOR1F","VECTOR2F","UV","VECTOR3F", + "VECTOR4F","COLOR","VEC1F","VEC2F","VEC3F","VEC4F","MATRIX"}} + +HCOMP.TOKEN_TEXT["STRALLOC"] = {{"ZASM","HLZASM"},{"STRING"}} +HCOMP.TOKEN_TEXT["DB"] = {{"ZASM","HLZASM"},{"DB"}} +HCOMP.TOKEN_TEXT["DEFINE"] = {{"ZASM","HLZASM"},{"DEFINE"}} +HCOMP.TOKEN_TEXT["CODE"] = {{"ZASM","HLZASM"},{"CODE"}} +HCOMP.TOKEN_TEXT["DATA"] = {{"ZASM","HLZASM"},{"DATA"}} +HCOMP.TOKEN_TEXT["ORG"] = {{"ZASM","HLZASM"},{"ORG"}} +HCOMP.TOKEN_TEXT["OFFSET"] = {{"ZASM","HLZASM"},{"OFFSET"}} +HCOMP.TOKEN_TEXT["TYPE"] = {{"ZASM","HLZASM"},{"VOID","FLOAT","CHAR","INT48","VECTOR"}} +HCOMP.TOKEN_TEXT["USERTYPE"] = {{"ZASM","HLZASM"},{}} + +HCOMP.TOKEN_TEXT["PRESERVE"] = {{"HLZASM"},{"PRESERVE"}} +HCOMP.TOKEN_TEXT["ZAP"] = {{"HLZASM"},{"ZAP"}} + +HCOMP.TOKEN_TEXT["REGISTER"] = {{"ZASM","HLZASM"},{"EAX","EBX","ECX","EDX","ESI","EDI","ESP","EBP"}} +HCOMP.TOKEN_TEXT["SEGMENT"] = {{"ZASM","HLZASM"},{"CS","SS","DS","ES","GS","FS","KS","LS"}} +HCOMP.TOKEN_TEXT["OPCODE"] = {{"ZASM","HLZASM"},{}} -- mov, cmp, etc... +HCOMP.TOKEN_TEXT["COMMENT1"] = {{"ZASM","HLZASM"},{"//"}} -- comment 1 +HCOMP.TOKEN_TEXT["COMMENT2"] = {{"ZASM","HLZASM"},{"/*"}} -- comment 2 +HCOMP.TOKEN_TEXT["COMMENT3"] = {{"ZASM","HLZASM"},{"*/"}} -- comment 3 +HCOMP.TOKEN_TEXT["MACRO"] = {{"ZASM","HLZASM"},{}} -- preprocessor macro +HCOMP.TOKEN_TEXT["STRING"] = {{"ZASM","HLZASM"},{}} -- buffer of chars +HCOMP.TOKEN_TEXT["CHAR"] = {{"ZASM","HLZASM"},{}} -- single character +HCOMP.TOKEN_TEXT["EOF"] = {{"ZASM","HLZASM"},{}} -- end of file + +-- Add ZCPU ports +for port=0,1023 do + HCOMP.TOKEN_TEXT["REGISTER"][2][1024+port] = "PORT"..port +end + +-- Add extended registers +for reg=0,31 do + HCOMP.TOKEN_TEXT["REGISTER"][2][96+reg] = "R"..reg +end + + + + +-------------------------------------------------------------------------------- +-- Generate table of all possible tokens +HCOMP.TOKEN = {} +HCOMP.TOKEN_NAME = {} +HCOMP.TOKEN_NAME2 = {} +local IDX = 1 +for tokenName,tokenData in pairs(HCOMP.TOKEN_TEXT) do + HCOMP.TOKEN[tokenName] = IDX + HCOMP.TOKEN_NAME[IDX] = tokenName + HCOMP.TOKEN_NAME2[IDX] = {} + + for k,v in pairs(tokenData[2]) do + HCOMP.TOKEN_NAME2[IDX][k] = v + end + IDX = IDX + 1 +end + +-- Create lookup tables for faster parsing +HCOMP.PARSER_LOOKUP = {} +for symID,symList in pairs(HCOMP.TOKEN_TEXT) do + for _,languageName in pairs(symList[1]) do + HCOMP.PARSER_LOOKUP[languageName] = HCOMP.PARSER_LOOKUP[languageName] or {} + for symSubID,symText in pairs(symList[2]) do + if symID == "VECTOR" then -- Special case for vector symbols + HCOMP.PARSER_LOOKUP[languageName][symText] = { symText, HCOMP.TOKEN[symID] } + else + HCOMP.PARSER_LOOKUP[languageName][symText] = { symSubID, HCOMP.TOKEN[symID] } + end + end + end +end + + +-- Create lookup table for symbols and double-character tokens +HCOMP.PARSER_SYMBOLS = {} +HCOMP.PARSER_DBCHAR = {} +for symID,symList in pairs(HCOMP.TOKEN_TEXT) do + local languages = symList[1] + local symText = symList[2][1] or "" + if #symText == 2 then + local char1 = string.sub(symText,1,1) + local char2 = string.sub(symText,2,2) + for _,lang in pairs(languages) do + HCOMP.PARSER_DBCHAR[lang] = HCOMP.PARSER_DBCHAR[lang] or {} + HCOMP.PARSER_DBCHAR[lang][char1] = HCOMP.PARSER_DBCHAR[lang][char1] or {} + HCOMP.PARSER_DBCHAR[lang][char1][char2] = true + end + end + if #symText == 1 then + for _,lang in pairs(languages) do + HCOMP.PARSER_SYMBOLS[lang] = HCOMP.PARSER_SYMBOLS[lang] or {} + HCOMP.PARSER_SYMBOLS[lang][symText] = true + end + end +end + + +-- Add opcodes to the lookup table +for _,languageName in pairs(HCOMP.TOKEN_TEXT["OPCODE"][1]) do + HCOMP.PARSER_LOOKUP[languageName] = HCOMP.PARSER_LOOKUP[languageName] or {} + for opcodeName,opcodeNo in pairs(HCOMP.OpcodeNumber) do + HCOMP.PARSER_LOOKUP[languageName][string.upper(opcodeName)] = { opcodeName, HCOMP.TOKEN.OPCODE } + end +end + + + + +-------------------------------------------------------------------------------- +-- Skip a single file in input +function HCOMP:nextFile() + table.remove(self.Code,1) + if not self.Code[1] then + self.Code[1] = { Text = "", Line = 1, Col = 1, File = "internal error", NextCharPos = 1 } + end +end + +-- Return next character +function HCOMP:getChar() + local pos = self.Code[1].NextCharPos + local char = string.sub(self.Code[1].Text,pos,pos) + if char == "" then + self:nextFile() + char = string.sub(self.Code[1].Text,pos,pos) + end + return char +end + +-- Skip current char +function HCOMP:nextChar() + local code = self.Code[1] + local pos = code.NextCharPos + if pos > #code.Text then + self:nextFile() + else + local char = string.sub(code.Text,pos,pos) + if char == "\n" then + code.Line = code.Line + 1 + code.Col = 1 + else + code.Col = code.Col + 1 + end + code.NextCharPos = pos + 1 + end +end + + + +-------------------------------------------------------------------------------- +-- Tokenize the code +function HCOMP:Tokenize() local TOKEN = self.TOKEN + -- Skip whitespaces + while (self:getChar() == " ") or + (self:getChar() == "\t") or + (self:getChar() == "\n") or + (self:getChar() == "\r") do self:nextChar() end + + -- Read token position + local tokenPosition = { Line = self.Code[1].Line, + Col = self.Code[1].Col, + File = self.Code[1].File } + + -- Check for end of file + if self:getChar() == "" then + table.insert(self.Tokens,{ + Type = TOKEN.EOF, + Data = nil, + Position = tokenPosition, + }) + return false + end + + -- Is it a preprocessor macro + if (self.Code[1].Col == 1) and (self:getChar() == "#") then + local macroLine = "" + while (self:getChar() ~= "") and (self:getChar() ~= "\n") do + macroLine = macroLine .. self:getChar() + self:nextChar() + end + + -- Parse it + self:ParsePreprocessMacro(macroLine,tokenPosition) + return true + end + + -- If still inside IFDEF, do not parse what follows + if self.IFDEFLevel[#self.IFDEFLevel] == true then + self:nextChar() + return true + end + + -- Is it a string + if (self:getChar() == "'") or (self:getChar() == "\"") then + local stringType = self:getChar() + self:nextChar() -- Skip leading character + + local fetchString = "" + while self.Code[1].NextCharPos <= #self.Code[1].Text and self:getChar() ~= stringType do + + if self:getChar() == "\\" then + self:nextChar() + if self:getChar() == "'" then fetchString = fetchString .. "'" + elseif self:getChar() == "\"" then fetchString = fetchString .. "\"" + elseif self:getChar() == "a" then fetchString = fetchString .. "\a" + elseif self:getChar() == "b" then fetchString = fetchString .. "\b" +-- elseif self:getChar() == "c" then fetchString = fetchString .. "\c" + elseif self:getChar() == "f" then fetchString = fetchString .. "\f" + elseif self:getChar() == "r" then fetchString = fetchString .. "\r" + elseif self:getChar() == "n" then fetchString = fetchString .. "\n" + elseif self:getChar() == "t" then fetchString = fetchString .. "\t" + elseif self:getChar() == "v" then fetchString = fetchString .. "\v" + elseif self:getChar() == "0" then fetchString = fetchString .. "\0" + end + self:nextChar() + elseif self:getChar() == "\n" then + self:Error("Missing terminating " .. stringType .. " character", + tokenPosition.Line,tokenPosition.Col,tokenPosition.File) + else + fetchString = fetchString .. self:getChar() + self:nextChar() + end + end + self:nextChar() -- Skip trailing character + + if (stringType == "'") and (#fetchString == 1) then + table.insert(self.Tokens,{ + Type = TOKEN.CHAR, + Data = string.byte(fetchString), + Position = tokenPosition, + }) + else + --if stringType == "'" then + -- self:Warning("Using character definition syntax for defining a string - might cause problems") + --end + table.insert(self.Tokens,{ + Type = TOKEN.STRING, + Data = fetchString, + Position = tokenPosition, + }) + end + return true + end + + -- Fetch entire token + local token = "" + while string.find(self:getChar(),"[%w_.@]") do + token = token .. self:getChar() + self:nextChar() + end + + -- Check if token was redefined + if (token ~= "") and (self.Defines[token]) then + if token == "__FILE__" then + table.insert(self.Tokens,{ + Type = TOKEN.STRING, + Data = tokenPosition.File, + Position = tokenPosition, + }) + return true + elseif token == "__LINE__" then + table.insert(self.Tokens,{ + Type = TOKEN.STRING, + Data = tostring(tokenPosition.Line), + Position = tokenPosition, + }) + return true + else + token = self.Defines[token] + end + end + + local is_symbol = false + + -- If no alphanumeric token fetched, try to fetch the special-character ones + if token == "" then + token = self:getChar() + self:nextChar() + + if HCOMP.PARSER_DBCHAR[self.Settings.CurrentLanguage][token] and HCOMP.PARSER_DBCHAR[self.Settings.CurrentLanguage][token][self:getChar()] then + local curChar = self:getChar() + token = token .. curChar + self:nextChar() + if token == "//" then -- Line comment + while (self:getChar() ~= "") and (self:getChar() ~= "\n") do self:nextChar() end + return true + elseif token == "/*" then -- Block comment open + while self:getChar() ~= "" do + local curChar = self:getChar() + self:nextChar() + if (curChar == "*") and (self:getChar() == "/") then + self:nextChar() + return true + end + end + + -- Error in tokenizing + self:Error("Comment block not closed (reached end of file)", + tokenPosition.Line,tokenPosition.Col,tokenPosition.File) + return true + elseif token == "*/" then -- Block comment end (returns error token) + table.insert(self.Tokens,{ + Type = TOKEN.COMMENT3, + Position = tokenPosition, + }) + return true + end + + -- Else it's a two-character symbol token + is_symbol = true + + elseif HCOMP.PARSER_SYMBOLS[self.Settings.CurrentLanguage][token] then + -- It's a one-character symbol token + is_symbol = true + + else + -- We have no idea what this is (it's not an identifier character, nor a recognized symbol) + self:Error("Unknown character '"..token.."'", + tokenPosition.Line,tokenPosition.Col,tokenPosition.File) + end + end + + assert(token ~= "") + + -- Determine which token it is + local tokenLookupTable = self.PARSER_LOOKUP[self.Settings.CurrentLanguage][string.upper(token)] + if tokenLookupTable then + table.insert(self.Tokens,{ + Type = tokenLookupTable[2], + Data = tokenLookupTable[1], + Position = tokenPosition, + }) + return true + end + + if is_symbol then + -- If we get here something is weird, because why would a symbol be in PARSER_DBCHARS or PARSER_SYMBOLS but not in PARSER_LOOKUP? + self:Error("Unknown symbol '"..token.."'", + tokenPosition.Line,tokenPosition.Col,tokenPosition.File) + end + + -- Maybe its a number + if tonumber(token) then + table.insert(self.Tokens,{ + Type = TOKEN.NUMBER, + Data = tonumber(token), + Position = tokenPosition, + }) + return true + end + + -- Wow it must have been ident afterall + table.insert(self.Tokens,{ + Type = TOKEN.IDENT, + Data = token, + Position = tokenPosition, + }) + return true +end + + + + +-------------------------------------------------------------------------------- +-- Print a string of tokens as an expression +function HCOMP:PrintTokens(tokenList) + local text = "" + if not istable(tokenList) then error("[global 1:1] Internal error 516 ("..tokenList..")") end + + for _,token in ipairs(tokenList) do + if (token.Type == self.TOKEN.NUMBER) or + (token.Type == self.TOKEN.OPCODE) then + text = text..token.Data + elseif token.Type == self.TOKEN.IDENT then + if self.Settings.GenerateLibrary then + if not self.LabelLookup[token.Data] then + self.LabelLookup[token.Data] = "_"..self.LabelLookupCounter + self.LabelLookupCounter = self.LabelLookupCounter + 1 + end + text = text..self.LabelLookup[token.Data] + else + text = text..token.Data + end + elseif token.Type == self.TOKEN.STRING then + text = text.."\""..token.Data.."\"" + elseif token.Type == self.TOKEN.CHAR then + if token.Data >= 32 then + text = text.."'"..string.char(token.Data).."'" + else + text = text.."'\\"..token.Data.."'" + end + else + text = text..(self.TOKEN_NAME2[token.Type][token.Data or 1] or "") + end + end + return text +end + + + + +-------------------------------------------------------------------------------- +-- Expects next token to be tok, otherwise will raise an error +function HCOMP:ExpectToken(tok) + if not self.Tokens[self.CurrentToken] then + if tok == self.TOKEN.EOF then + self:Error("Expected "..HCOMP.TOKEN_NAME[tok]..", got "..HCOMP.TOKEN_NAME[self.TOKEN.EOF].." instead") + end + end + + if self.Tokens[self.CurrentToken].Type == tok then + self.TokenType = self.Tokens[self.CurrentToken].Type + self.TokenData = self.Tokens[self.CurrentToken].Data + self.CurrentToken = self.CurrentToken + 1 + else + self:Error("Expected "..HCOMP.TOKEN_NAME[tok]..", got "..HCOMP.TOKEN_NAME[self.Tokens[self.CurrentToken].Type].." instead") + end +end + + + + +-- Returns true and skips a token if it matches this one +function HCOMP:MatchToken(tok) + if not self.Tokens[self.CurrentToken] then + return tok == self.TOKEN.EOF + end + + if self.Tokens[self.CurrentToken].Type == tok then + self.TokenType = self.Tokens[self.CurrentToken].Type + self.TokenData = self.Tokens[self.CurrentToken].Data + self.CurrentToken = self.CurrentToken + 1 + return true + else + return false + end +end + + + + +-- Go to next token +function HCOMP:NextToken() + self.CurrentToken = self.CurrentToken + 1 +end + +-- Go to previous token +function HCOMP:PreviousToken() + self.CurrentToken = self.CurrentToken - 1 +end + + +-- Returns next token type. Looks forward into stream if offset is specified +function HCOMP:PeekToken(offset,extended) + if self.Tokens[self.CurrentToken+(offset or 0)] then + if extended then + return self.Tokens[self.CurrentToken+(offset or 0)].Type, + self.Tokens[self.CurrentToken+(offset or 0)].Data + else + return self.Tokens[self.CurrentToken+(offset or 0)].Type + end + else + return self.TOKEN.EOF + end +end + + + + +-- Store current parser state (so code could be reparsed again later) +function HCOMP:SaveParserState() + self.SavedToken = self.CurrentToken +end + + + + +-- Get all tokens between saved state and current state. This is used for +-- reparsing expressions during resolve stage. +function HCOMP:GetSavedTokens(firstToken) + local savedTokens = {} + for tokenIdx = firstToken or self.SavedToken,self.CurrentToken-1 do + table.insert(savedTokens,self.Tokens[tokenIdx]) + end + savedTokens.TokenList = true + return savedTokens +end + + + + +-- Restore parser state. Can accept a list of tokens and restore state to that +-- (see GetSavedTokens()) +function HCOMP:RestoreParserState(tokenList) + if tokenList then + self.Tokens = tokenList + self.CurrentToken = 1 + else + self.CurrentToken = self.SavedToken + end +end + + + + +-- Returns current position in source file +function HCOMP:CurrentSourcePosition() + if self.Tokens[self.CurrentToken-1] then + return self.Tokens[self.CurrentToken-1].Position + else + return { Line = 1, Col = 1, File = "HL-ZASM" } + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/rendertarget_fix.lua b/garrysmod/addons/feature-wire/lua/wire/client/rendertarget_fix.lua new file mode 100644 index 0000000..aa7272c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/rendertarget_fix.lua @@ -0,0 +1,118 @@ +WireLib.RTFix = {} +local RTFix = WireLib.RTFix + +--------------------------------------------------------------------- +-- RTFix Lib +--------------------------------------------------------------------- +RTFix.List = {} + +function RTFix:Add( ClassName, NiceName, Function ) + RTFix.List[ClassName] = { NiceName, Function } +end + +function RTFix:GetAll() + return RTFix.List +end + +function RTFix:Get( ClassName ) + return RTFix.List[ClassName] +end + +function RTFix:ReloadAll() + for k,v in pairs( RTFix.List ) do + local func = v[2] + for k2,v2 in ipairs( ents.FindByClass( k ) ) do + func( v2 ) + end + end +end + +function RTFix:Reload( ClassName ) + local func = RTFix.List[ClassName][2] + for k, v in ipairs( ents.FindByClass( ClassName ) ) do + func( v ) + end +end + +--------------------------------------------------------------------- +-- Console Command +--------------------------------------------------------------------- + +concommand.Add("wire_rt_fix",function() + RTFix:ReloadAll() +end) + +--------------------------------------------------------------------- +-- Tool Menu +--------------------------------------------------------------------- + +local function CreateCPanel( Panel ) + Panel:ClearControls() + + Panel:Help( [[Here you can fix screens that use +rendertargets if they break due to lag. +If a screen is not on this list, it means +that either its author has not added it to +this list, the screen has its own fix, or +that no fix is necessary. +You can also use the console command +"wire_rt_fix", which does the same thing +as pressing the "All" button.]] ) + + local btn = vgui.Create("DButton") + btn:SetText("All") + function btn:DoClick() + RTFix:ReloadAll() + end + btn:SetToolTip( "Fix all RTs on the map." ) + Panel:AddItem( btn ) + + for k,v in pairs( RTFix.List ) do + local btn = vgui.Create("DButton") + btn:SetText( v[1] ) + btn:SetToolTip( "Fix all " .. v[1] .. "s on the map\n("..k..")" ) + function btn:DoClick() + RTFix:Reload( k ) + end + Panel:AddItem( btn ) + end +end + +hook.Add("PopulateToolMenu","WireLib_RenderTarget_Fix",function() + spawnmenu.AddToolMenuOption( "Wire", "Options", "RTFix", "Fix RenderTargets", "", "", CreateCPanel, nil ) +end) + +--------------------------------------------------------------------- +-- Add all default wire components +-- credits to sk89q for making this: http://www.wiremod.com/forum/bug-reports/19921-cs-egp-gpu-etc-issue-when-rejoin-lag-out.html#post193242 +--------------------------------------------------------------------- + +-- Helper function +local function def( ent, redrawkey ) + if (ent.GPU or ent.GPU.RT) then + ent.GPU:FreeRT() + end + + ent.GPU:Initialize() + + if (redrawkey) then + ent[redrawkey] = true + end +end + +RTFix:Add("gmod_wire_consolescreen","Console Screen", function( ent ) def( ent, "NeedRefresh" ) end) +RTFix:Add("gmod_wire_digitalscreen","Digital Screen", function( ent ) def( ent, "NeedRefresh" ) end) +RTFix:Add("gmod_wire_gpu","GPU", def) +--RTFix:Add("gmod_wire_graphics_tablet","Graphics Tablet", function( ent ) def( ent, nil, true ) end) No fix is needed for this +RTFix:Add("gmod_wire_oscilloscope","Oscilloscope", def) +--RTFix:Add("gmod_wire_panel","Control Panel", function( ent ) def( ent, nil, true ) end) No fix is needed for this +--RTFix:Add("gmod_wire_screen","Screen", function( ent ) def( ent, nil, true ) end) No fix is needed for this +RTFix:Add("gmod_wire_textscreen","Text Screen", function( ent ) def( ent, "NeedRefresh" ) end) +RTFix:Add("gmod_wire_egp","EGP",function( ent ) def( ent, "NeedsUpdate" ) end) + +-- EGP Emitter needs a check because it can optionally not use RTs +RTFix:Add("gmod_wire_egp_emitter","EGP Emitter",function( ent ) + if ent:GetUseRT() then + def( ent, "NeedsUpdate" ) + end +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/sound_browser.lua b/garrysmod/addons/feature-wire/lua/wire/client/sound_browser.lua new file mode 100644 index 0000000..4d238ba --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/sound_browser.lua @@ -0,0 +1,945 @@ +// A sound browser for the sound emitter and the expression 2 editor. +// Made by Grocel. + +local max_char_count = 200 //File length limit +local max_char_chat_count = 110 // chat has a ~128 char limit, varies depending on char wide. + +local Disabled_Gray = Color(140, 140, 140, 255) + +local SoundBrowserPanel = nil +local TabFileBrowser = nil +local TabSoundPropertyList = nil +local TabFavourites = nil +local SoundInfoTree = nil +local SoundInfoTreeRoot = nil + +local SoundObj = nil +local SoundObjNoEffect = nil + +local TranslateCHAN = { + [CHAN_REPLACE] = "CHAN_REPLACE", + [CHAN_AUTO] = "CHAN_AUTO", + [CHAN_WEAPON] = "CHAN_WEAPON", + [CHAN_VOICE] = "CHAN_VOICE", + [CHAN_ITEM] = "CHAN_ITEM", + [CHAN_BODY] = "CHAN_BODY", + [CHAN_STREAM] = "CHAN_STREAM", + [CHAN_STATIC] = "CHAN_STATIC", + [CHAN_VOICE2] = "CHAN_VOICE2", + [CHAN_VOICE_BASE] = "CHAN_VOICE_BASE", + [CHAN_USER_BASE] = "CHAN_USER_BASE" +} + +// Output the infos about the given sound. +local function GetFileInfos(strfile) + if (!isstring(strfile) or strfile == "") then return end + + local nsize = tonumber(file.Size("sound/" .. strfile, "GAME") or "-1") + local strformat = string.lower(string.GetExtensionFromFilename(strfile) or "n/a") + + return nsize, strformat +end + +local function FormatSize(nsize) + if (!nsize) then return end + + //Negative filessizes aren't Valid. + if (nsize < 0) then return end + + return nsize, string.NiceSize(nsize) +end + +local function FormatLength(nduration) + if (!nduration) then return end + + //Negative durations aren't Valid. + if (nduration < 0) then return end + + local nm = math.floor(nduration / 60) + local ns = math.floor(nduration % 60) + local nms = (nduration % 1) * 1000 + return nduration, (string.format("%01d", nm)..":"..string.format("%02d", ns).."."..string.format("%03d", nms)) +end + +local function GetInfoTable(strfile) + local nsize, strformat, nduration = GetFileInfos(strfile) + if (!nsize) then return end + + nduration = SoundDuration(strfile) //Get the duration for the info text only. + if(nduration) then + nduration = math.Round(nduration * 1000) / 1000 + end + local nduration, strduration = FormatLength(nduration, nsize) + local nsizeB, strsize = FormatSize(nsize) + + local T = {} + local tabproperty = sound.GetProperties(strfile) + + if (tabproperty) then + T = tabproperty + else + T.Path = strfile + T.Duration = {strduration or "n/a", nduration and nduration.." sec"} + T.Size = {strsize or "n/a", nsizeB and nsizeB.." Bytes"} + T.Format = strformat + end + + return T, !tabproperty +end + + +// Output the infos about the given sound. +local oldstrfile +local function GenerateInfoTree(strfile, backnode, count) + if(oldstrfile == strfile and strfile) then return end + oldstrfile = strfile + + local SoundData, IsFile = GetInfoTable(strfile) + + if (!IsValid(backnode)) then + if (IsValid(SoundInfoTreeRoot)) then + SoundInfoTreeRoot:Remove() + end + end + if(!SoundData) then return end + + local strcount = "" + if (count) then + strcount = " ("..count..")" + end + + if (IsFile) then + local index = "" + local node = nil + local mainnode = nil + local subnode = nil + + if (IsValid(backnode)) then + mainnode = backnode:AddNode("Sound File"..strcount, "icon16/sound.png") + else + mainnode = SoundInfoTree:AddNode("Sound File", "icon16/sound.png") + SoundInfoTreeRoot = mainnode + end + + + do + index = "Path" + node = mainnode:AddNode(index, "icon16/sound.png") + subnode = node:AddNode(SoundData[index], "icon16/page.png") + subnode.IsSoundNode = true + subnode.IsDataNode = true + end + do + index = "Duration" + node = mainnode:AddNode(index, "icon16/time.png") + for k, v in pairs(SoundData[index]) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + index = "Size" + node = mainnode:AddNode(index, "icon16/disk.png") + for k, v in pairs(SoundData[index]) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + index = "Format" + node = mainnode:AddNode(index, "icon16/page_white_key.png") + subnode = node:AddNode(SoundData[index], "icon16/page.png") + subnode.IsDataNode = true + end + else + local node = nil + local mainnode = nil + + if (IsValid(backnode)) then + mainnode = backnode:AddNode("Sound Property"..strcount, "icon16/table_gear.png") + else + mainnode = SoundInfoTree:AddNode("Sound Property", "icon16/table_gear.png") + SoundInfoTreeRoot = mainnode + end + + do + node = mainnode:AddNode("Name", "icon16/sound.png") + subnode = node:AddNode(SoundData["name"], "icon16/page.png") + subnode.IsSoundNode = true + subnode.IsDataNode = true + end + do + local tabchannel = SoundData["channel"] or 0 + if (istable(tabchannel)) then + node = mainnode:AddNode("Channel", "icon16/page_white_gear.png") + for k, v in pairs(tabchannel) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + subnode = node:AddNode(TranslateCHAN[v] or TranslateCHAN[CHAN_USER_BASE], "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Channel", "icon16/page_white_gear.png") + subnode = node:AddNode(tabchannel, "icon16/page.png") + subnode.IsDataNode = true + subnode = node:AddNode(TranslateCHAN[tabchannel] or TranslateCHAN[CHAN_USER_BASE], "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tablevel = SoundData["level"] or 0 + if (istable(tablevel)) then + node = mainnode:AddNode("Level", "icon16/page_white_gear.png") + for k, v in pairs(tablevel) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Level", "icon16/page_white_gear.png") + subnode = node:AddNode(tablevel, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tabpitch = SoundData["volume"] or 0 + if (istable(tabpitch)) then + node = mainnode:AddNode("Volume", "icon16/page_white_gear.png") + for k, v in pairs(tabpitch) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Volume", "icon16/page_white_gear.png") + subnode = node:AddNode(tabpitch, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tabpitch = SoundData["pitch"] or 0 + if (istable(tabpitch)) then + node = mainnode:AddNode("Pitch", "icon16/page_white_gear.png") + for k, v in pairs(tabpitch) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Pitch", "icon16/page_white_gear.png") + subnode = node:AddNode(tabpitch, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tabsound = SoundData["sound"] or "" + if (istable(tabsound)) then + node = mainnode:AddNode("Sounds", "icon16/table_multiple.png") + else + node = mainnode:AddNode("Sound", "icon16/table.png") + end + + node.SubData = tabsound + node.BackNode = mainnode + node.Expander.DoClick = function(self) + if (!IsValid(SoundInfoTree)) then return end + if (!IsValid(node)) then return end + + node:SetExpanded(false) + SoundInfoTree:SetSelectedItem(node) + end + node:AddNode("Dummy") + end + end + + if (IsValid(backnode)) then + return + end + + if (IsValid(SoundInfoTreeRoot)) then + SoundInfoTreeRoot:SetExpanded(true) + end +end + +// Set the volume of the sound. +local function SetSoundVolume(volume) + if(!SoundObj) then return end + + SoundObj:ChangeVolume(tonumber(volume) or 1, 0.1) +end + +// Set the pitch of the sound. +local function SetSoundPitch(pitch) + if(!SoundObj) then return end + + SoundObj:ChangePitch(tonumber(pitch) or 100, 0.1) +end + +// Play the given sound, if no sound is given then mute a playing sound. +local function PlaySound(file, volume, pitch) + if(SoundObj) then + SoundObj:Stop() + SoundObj = nil + end + + if (!file or file == "") then return end + + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + util.PrecacheSound(file) + + SoundObj = CreateSound(ply, file) + if(SoundObj) then + SoundObj:PlayEx(tonumber(volume) or 1, tonumber(pitch) or 100) + end +end + +// Play the given sound without effects, if no sound is given then mute a playing sound. +local function PlaySoundNoEffect(file) + if(SoundObjNoEffect) then + SoundObjNoEffect:Stop() + SoundObjNoEffect = nil + end + + if (!file or file == "") then return end + + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + util.PrecacheSound(file) + + SoundObjNoEffect = CreateSound(ply, file) + if(SoundObjNoEffect) then + SoundObjNoEffect:PlayEx(1, 100) + end +end + +local function SetupSoundemitter(strSound) + // Setup the Soundemitter stool with the soundpath. + RunConsoleCommand("wire_soundemitter_sound", strSound) + + // Pull out the soundemitter stool after setup. + spawnmenu.ActivateTool("wire_soundemitter") +end + +local function SetupClipboard(strSound) + // Copy the soundpath to Clipboard. + SetClipboardText(strSound) +end + +local function Sendmenu(strSound, SoundEmitter, nSoundVolume, nSoundPitch) // Open a sending and setup menu on right click on a sound file. + if (!isstring(strSound)) then return end + if (strSound == "") then return end + + local Menu = DermaMenu() + local MenuItem = nil + + if (SoundEmitter) then + + //Setup soundemitter + MenuItem = Menu:AddOption("Setup soundemitter", function() + SetupSoundemitter(strSound) + end) + MenuItem:SetImage("icon16/sound.png") + + //Setup soundemitter and close + MenuItem = Menu:AddOption("Setup soundemitter and close", function() + SetupSoundemitter(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/sound.png") + + //Copy to clipboard + MenuItem = Menu:AddOption("Copy to clipboard", function() + SetupClipboard(strSound) + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Copy to clipboard and close + MenuItem = Menu:AddOption("Copy to clipboard and close", function() + SetupClipboard(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/page_paste.png") + + else + + //Copy to clipboard + MenuItem = Menu:AddOption("Copy to clipboard", function() + SetupClipboard(strSound) + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Copy to clipboard and close + MenuItem = Menu:AddOption("Copy to clipboard and close", function() + SetupClipboard(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Setup soundemitter + MenuItem = Menu:AddOption("Setup soundemitter", function() + SetupSoundemitter(strSound) + end) + MenuItem:SetImage("icon16/sound.png") + + //Setup soundemitter and close + MenuItem = Menu:AddOption("Setup soundemitter and close", function() + SetupSoundemitter(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/sound.png") + + end + + Menu:AddSpacer() + + if (IsValid(TabFavourites)) then + // Add the soundpath to the favourites. + if (TabFavourites:ItemInList(strSound)) then + + //Remove from favourites + MenuItem = Menu:AddOption("Remove from favourites", function() + TabFavourites:RemoveItem(strSound) + end) + MenuItem:SetImage("icon16/bin_closed.png") + + else + + //Add to favourites + MenuItem = Menu:AddOption("Add to favourites", function() + TabFavourites:AddItem(strSound, sound.GetProperties(strSound) and "property" or "file") + end) + MenuItem:SetImage("icon16/star.png") + local max_item_count = TabFavourites:GetMaxItems() + local count = TabFavourites.TabfileCount + if (count >= max_item_count) then + MenuItem:SetTextColor(Disabled_Gray) // custom disabling + MenuItem.DoClick = function() end + + MenuItem:SetToolTip("The favourites list is Full! It can't hold more than "..max_item_count.." items!") + end + + end + end + + Menu:AddSpacer() + + //Print to console + MenuItem = Menu:AddOption("Print to console", function() + // Print the soundpath in the Console/HUD. + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + ply:PrintMessage( HUD_PRINTTALK, strSound) + end) + MenuItem:SetImage("icon16/monitor_go.png") + + //Print to Chat + MenuItem = Menu:AddOption("Print to Chat", function() + // Say the the soundpath. + RunConsoleCommand("say", strSound) + end) + MenuItem:SetImage("icon16/group_go.png") + + local len = #strSound + if (len > max_char_chat_count) then + MenuItem:SetTextColor(Disabled_Gray) // custom disabling + MenuItem.DoClick = function() end + + MenuItem:SetToolTip("The filepath ("..len.." chars) is too long to print in chat. It should be shorter than "..max_char_chat_count.." chars!") + end + + Menu:AddSpacer() + + //Play + MenuItem = Menu:AddOption("Play", function() + PlaySound(strSound, nSoundVolume, nSoundPitch, strtype) + PlaySoundNoEffect() + end) + MenuItem:SetImage("icon16/control_play.png") + + //Play without effects + MenuItem = Menu:AddOption("Play without effects", function() + PlaySound() + PlaySoundNoEffect(strSound, strtype) + end) + MenuItem:SetImage("icon16/control_play_blue.png") + + Menu:Open() +end + +local function Infomenu(parent, node, SoundEmitter, nSoundVolume, nSoundPitch) + if(!IsValid(node)) then return end + if(!node.IsDataNode) then return end + + local strNodeName = node:GetText() + local IsSoundNode = node.IsSoundNode + + if(IsSoundNode) then + Sendmenu(strNodeName, SoundEmitter, nSoundVolume, nSoundPitch) + return + end + + local Menu = DermaMenu() + + //Copy to clipboard + MenuItem = Menu:AddOption("Copy to clipboard", function() + SetupClipboard(strNodeName) + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Print to console + MenuItem = Menu:AddOption("Print to console", function() + // Print the soundpath in the Console/HUD. + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + ply:PrintMessage( HUD_PRINTTALK, strNodeName) + end) + MenuItem:SetImage("icon16/monitor_go.png") + + //Print to Chat + MenuItem = Menu:AddOption("Print to Chat", function() + // Say the the soundpath. + RunConsoleCommand("say", strNodeName) + end) + MenuItem:SetImage("icon16/group_go.png") + + local len = #strNodeName + if (len > max_char_chat_count) then + MenuItem:SetTextColor(Disabled_Gray) // custom disabling + MenuItem.DoClick = function() end + + MenuItem:SetToolTip("The filepath ("..len.." chars) is too long to print in chat. It should be shorter than "..max_char_chat_count.." chars!") + end + + Menu:Open() +end + +// Save the file path. It should be cross session. +// It's used when opening the browser in the e2 editor. +local function SaveFilePath(panel, file) + if (!IsValid(panel)) then return end + if (panel.Soundemitter) then return end + + panel:SetCookie("wire_soundfile", file) +end + +// Open the Sound Browser. +local function CreateSoundBrowser(path, se) + local soundemitter = false + if (isstring(path) and path ~= "") then + soundemitter = true + + if (tonumber(se) ~= 1) then + soundemitter = false + end + end + + if (tonumber(se) == 1) then + soundemitter = true + end + + local strSound = "" + local nSoundVolume = 1 + local nSoundPitch = 100 + + if(IsValid(SoundBrowserPanel)) then SoundBrowserPanel:Remove() end + if(IsValid(TabFileBrowser)) then TabFileBrowser:Remove() end + if(IsValid(TabSoundPropertyList)) then TabSoundPropertyList:Remove() end + if(IsValid(TabFavourites)) then TabFavourites:Remove() end + if(IsValid(SoundInfoTree)) then SoundInfoTree:Remove() end + if(IsValid(SoundInfoTreeRoot)) then SoundInfoTreeRoot:Remove() end + + SoundBrowserPanel = vgui.Create("DFrame") // The main frame. + SoundBrowserPanel:SetPos(50,25) + SoundBrowserPanel:SetSize(750, 500) + + SoundBrowserPanel:SetMinWidth(700) + SoundBrowserPanel:SetMinHeight(400) + + SoundBrowserPanel:SetSizable(true) + SoundBrowserPanel:SetDeleteOnClose( false ) + SoundBrowserPanel:SetTitle("Sound Browser") + SoundBrowserPanel:SetVisible(false) + SoundBrowserPanel:SetCookieName( "wire_sound_browser" ) + + TabFileBrowser = vgui.Create("wire_filebrowser") // The file tree browser. + TabSoundPropertyList = vgui.Create("wire_soundpropertylist") // The sound property browser. + TabFavourites = vgui.Create("wire_listeditor") // The favourites manager. + + TabFileBrowser:SetListSpeed(6) + TabFileBrowser:SetMaxItemsPerPage(200) + + TabSoundPropertyList:SetListSpeed(100) + TabSoundPropertyList:SetMaxItems(400) + + TabFavourites:SetListSpeed(40) + TabFavourites:SetMaxItems(512) + + local BrowserTabs = vgui.Create("DPropertySheet") // The tabs. + BrowserTabs:DockMargin(5, 5, 5, 5) + BrowserTabs:AddSheet("File Browser", TabFileBrowser, "icon16/folder.png", false, false, "Browse your sound folder.") + BrowserTabs:AddSheet("Sound Property Browser", TabSoundPropertyList, "icon16/table_gear.png", false, false, "Browse the sound properties.") + BrowserTabs:AddSheet("Favourites", TabFavourites, "icon16/star.png", false, false, "View your favourites.") + + SoundInfoTree = vgui.Create("DTree") // The info tree. + SoundInfoTree:SetClickOnDragHover(false) + local oldClicktime = CurTime() + SoundInfoTree.DoClick = function( parent, node ) + if (!IsValid(parent)) then return end + if (!IsValid(node)) then return end + parent:SetSelectedItem(node) + + local Clicktime = CurTime() + if ((Clicktime - oldClicktime) > 0.3) then oldClicktime = Clicktime return end + oldClicktime = Clicktime + + if (!node.IsSoundNode) then return end + + local file = node:GetText() + PlaySound(file, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + end + SoundInfoTree.DoRightClick = function( parent, node ) + if (!IsValid(parent)) then return end + if (!IsValid(node)) then return end + + parent:SetSelectedItem(node) + Infomenu(parent, node, SoundEmitter, nSoundVolume, nSoundPitch) + end + + SoundInfoTree.OnNodeSelected = function( parent, node ) + if (!IsValid(parent)) then return end + if (!IsValid(node)) then return end + + local backnode = node.BackNode + if (!IsValid(node.BackNode)) then + node:SetExpanded(!node.m_bExpanded) + return + end + + local tabsound = node.SubData + if (!tabsound) then + node:SetExpanded(!node.m_bExpanded) + return + end + + node:SetExpanded(false) + node:Remove() + + if (istable(tabsound)) then + node = backnode:AddNode("Sounds", "icon16/table_multiple.png") + for k, v in pairs(tabsound) do + GenerateInfoTree(v, node, k) + end + else + node = backnode:AddNode("Sound", "icon16/table.png") + GenerateInfoTree(tabsound, node) + end + + node:SetExpanded(false) + parent:SetSelectedItem(node) + node:SetExpanded(!node.m_bExpanded) + end + + local SplitPanel = SoundBrowserPanel:Add( "DHorizontalDivider" ) + SplitPanel:Dock(FILL) + SplitPanel:SetLeft(BrowserTabs) + SplitPanel:SetRight(SoundInfoTree) + SplitPanel:SetLeftWidth(570) + SplitPanel:SetLeftMin(500) + SplitPanel:SetRightMin(150) + SplitPanel:SetDividerWidth(3) + + TabFileBrowser:SetRootName("sound") + TabFileBrowser:SetRootPath("sound") + TabFileBrowser:SetWildCard("GAME") + TabFileBrowser:SetFileTyps({"*.mp3","*.wav","*.ogg"}) + + //TabFileBrowser:AddColumns("Type", "Size", "Length") //getting the duration is very slow. + local Columns = TabFileBrowser:AddColumns("Format", "Size") + Columns[1]:SetFixedWidth(70) + Columns[1]:SetWide(70) + Columns[2]:SetFixedWidth(70) + Columns[2]:SetWide(70) + + TabFileBrowser.LineData = function(self, id, strfile, ...) + if (#strfile > max_char_count) then return nil, true end // skip and hide to long filenames. + + local nsize, strformat, nduration = GetFileInfos(strfile) + if (!nsize) then return end + + local nsizeB, strsize = FormatSize(nsize, nduration) + local nduration, strduration = FormatLength(nduration, nsize) + + //return {strformat, strsize or "n/a", strduration or "n/a"} //getting the duration is very slow. + return {strformat, strsize or "n/a"} + end + + TabFileBrowser.OnLineAdded = function(self, id, line, strfile, ...) + + end + + TabFileBrowser.DoClick = function(parent, file) + SaveFilePath(SoundBrowserPanel, file) + + strSound = file + GenerateInfoTree(file) + end + + TabFileBrowser.DoDoubleClick = function(parent, file) + PlaySound(file, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + SaveFilePath(SoundBrowserPanel, file) + + strSound = file + end + + TabFileBrowser.DoRightClick = function(parent, file) + Sendmenu(file, SoundBrowserPanel.Soundemitter, nSoundVolume, nSoundPitch) + SaveFilePath(SoundBrowserPanel, file) + + strSound = file + GenerateInfoTree(file) + end + + + TabSoundPropertyList.DoClick = function(parent, property) + SaveFilePath(SoundBrowserPanel, property) + + strSound = property + GenerateInfoTree(property) + end + + TabSoundPropertyList.DoDoubleClick = function(parent, property) + PlaySound(property, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + SaveFilePath(SoundBrowserPanel, property) + + strSound = property + end + + TabSoundPropertyList.DoRightClick = function(parent, property) + Sendmenu(property, SoundBrowserPanel.Soundemitter, nSoundVolume, nSoundPitch) + SaveFilePath(SoundBrowserPanel, property) + + strSound = property + GenerateInfoTree(property) + end + + file.CreateDir("soundlists") + TabFavourites:SetRootPath("soundlists") + + TabFavourites.DoClick = function(parent, item, data) + if(file.Exists("sound/"..item, "GAME")) then + TabFileBrowser:SetOpenFile(item) + end + + strSound = item + GenerateInfoTree(item) + end + + TabFavourites.DoDoubleClick = function(parent, item, data) + if(file.Exists("sound/"..item, "GAME")) then + TabFileBrowser:SetOpenFile(item) + end + + PlaySound(item, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + strSound = item + end + + TabFavourites.DoRightClick = function(parent, item, data) + if(file.Exists("sound/"..item, "GAME")) then + TabFileBrowser:SetOpenFile(item) + end + + Sendmenu(item, SoundBrowserPanel.Soundemitter, nSoundVolume, nSoundPitch) + strSound = item + GenerateInfoTree(item) + end + + local ControlPanel = SoundBrowserPanel:Add("DPanel") // The bottom part of the frame. + ControlPanel:DockMargin(0, 5, 0, 0) + ControlPanel:Dock(BOTTOM) + ControlPanel:SetTall(60) + ControlPanel:SetDrawBackground(false) + + local ButtonsPanel = ControlPanel:Add("DPanel") // The buttons. + ButtonsPanel:DockMargin(4, 0, 0, 0) + ButtonsPanel:Dock(RIGHT) + ButtonsPanel:SetWide(250) + ButtonsPanel:SetDrawBackground(false) + + local TunePanel = ControlPanel:Add("DPanel") // The effect Sliders. + TunePanel:DockMargin(0, 4, 0, 0) + TunePanel:Dock(LEFT) + TunePanel:SetWide(350) + TunePanel:SetDrawBackground(false) + + local TuneVolumeSlider = TunePanel:Add("DNumSlider") // The volume slider. + TuneVolumeSlider:DockMargin(2, 0, 0, 0) + TuneVolumeSlider:Dock(TOP) + TuneVolumeSlider:SetText("Volume") + TuneVolumeSlider:SetDecimals(0) + TuneVolumeSlider:SetMinMax(0, 100) + TuneVolumeSlider:SetValue(100) + TuneVolumeSlider.Label:SetWide(40) + TuneVolumeSlider.OnValueChanged = function(self, val) + nSoundVolume = val / 100 + SetSoundVolume(nSoundVolume) + end + + local TunePitchSlider = TunePanel:Add("DNumSlider") // The pitch slider. + TunePitchSlider:DockMargin(2, 0, 0, 0) + TunePitchSlider:Dock(BOTTOM) + TunePitchSlider:SetText("Pitch") + TunePitchSlider:SetDecimals(0) + TunePitchSlider:SetMinMax(0, 255) + TunePitchSlider:SetValue(100) + TunePitchSlider.Label:SetWide(40) + TunePitchSlider.OnValueChanged = function(self, val) + nSoundPitch = val + SetSoundPitch(nSoundPitch) + end + + local PlayStopPanel = ButtonsPanel:Add("DPanel") // Play and stop. + PlayStopPanel:DockMargin(0, 0, 0, 2) + PlayStopPanel:Dock(TOP) + PlayStopPanel:SetDrawBackground(false) + + local PlayButton = PlayStopPanel:Add("DButton") // The play button. + PlayButton:SetText("Play") + PlayButton:Dock(LEFT) + PlayButton:SetWide(PlayStopPanel:GetWide() / 2 - 2) + PlayButton.DoClick = function() + PlaySound(strSound, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + end + + local StopButton = PlayStopPanel:Add("DButton") // The stop button. + StopButton:SetText("Stop") + StopButton:Dock(RIGHT) + StopButton:SetWide(PlayButton:GetWide()) + StopButton.DoClick = function() + PlaySound() // Mute a playing sound by not giving a sound. + PlaySoundNoEffect() + end + + local SoundemitterButton = ButtonsPanel:Add("DButton") // The soundemitter button. Hidden in e2 mode. + SoundemitterButton:SetText("Send to soundemitter") + SoundemitterButton:DockMargin(0, 2, 0, 0) + SoundemitterButton:Dock(FILL) + SoundemitterButton:SetVisible(false) + SoundemitterButton.DoClick = function(btn) + SetupSoundemitter(strSound) + end + + local ClipboardButton = ButtonsPanel:Add("DButton") // The soundemitter button. Hidden in soundemitter mode. + ClipboardButton:SetText("Copy to clipboard") + ClipboardButton:DockMargin(0, 2, 0, 0) + ClipboardButton:Dock(FILL) + ClipboardButton:SetVisible(false) + ClipboardButton.DoClick = function(btn) + SetupClipboard(strSound) + end + + local oldw, oldh = SoundBrowserPanel:GetSize() + SoundBrowserPanel.PerformLayout = function(self, ...) + SoundemitterButton:SetVisible(self.Soundemitter) + ClipboardButton:SetVisible(!self.Soundemitter) + + local w = self:GetWide() + local rightw = SplitPanel:GetLeftWidth() + w - oldw + + if (rightw < SplitPanel:GetLeftMin()) then + rightw = SplitPanel:GetLeftMin() + end + SplitPanel:SetLeftWidth(rightw) + + local minw = w - SplitPanel:GetRightMin() + SplitPanel:GetDividerWidth() + if (SplitPanel:GetLeftWidth() > minw) then + SplitPanel:SetLeftWidth(minw) + end + + PlayStopPanel:SetTall(ControlPanel:GetTall() / 2 - 2) + PlayButton:SetWide(PlayStopPanel:GetWide() / 2 - 2) + StopButton:SetWide(PlayButton:GetWide()) + + if (self.Soundemitter) then + SoundemitterButton:SetTall(PlayStopPanel:GetTall() - 2) + else + ClipboardButton:SetTall(PlayStopPanel:GetTall() - 2) + end + + oldw, oldh = self:GetSize() + + DFrame.PerformLayout(self, ...) + end + + SoundBrowserPanel.OnClose = function() // Set effects back and mute when closing. + nSoundVolume = 1 + nSoundPitch = 100 + TuneVolumeSlider:SetValue(nSoundVolume * 100) + TunePitchSlider:SetValue(nSoundPitch) + vgui.GetWorldPanel():SetWorldClicker(false) -- Not allow the breakage of other addons installed. + PlaySound() + PlaySoundNoEffect() + end + + SoundBrowserPanel:InvalidateLayout(true) +end + +// Open the Sound Browser. +local function OpenSoundBrowser(pl, cmd, args) + local path = args[1] // nil or "" will put the browser in e2 mode else the soundemitter mode is applied. + local se = args[2] + + if (!IsValid(SoundBrowserPanel)) then + CreateSoundBrowser(path, se) + end + + SoundBrowserPanel:SetVisible(true) + SoundBrowserPanel:MakePopup() + SoundBrowserPanel:InvalidateLayout(true) + + vgui.GetWorldPanel():SetWorldClicker(true) + + if (!IsValid(TabFileBrowser)) then return end + + //Replaces the timer, doesn't get paused in singleplayer. + WireLib.Timedcall(function(SoundBrowserPanel, TabFileBrowser, path, se) + if (!IsValid(SoundBrowserPanel)) then return end + if (!IsValid(TabFileBrowser)) then return end + + local soundemitter = false + if (isstring(path) and path ~= "") then + soundemitter = true + end + + local soundemitter = false + if (isstring(path) and path ~= "") then + soundemitter = true + + if (tonumber(se) ~= 1) then + soundemitter = false + end + end + + if (tonumber(se) == 1) then + soundemitter = true + end + + SoundBrowserPanel.Soundemitter = soundemitter + SoundBrowserPanel:InvalidateLayout(true) + + if (!soundemitter) then + path = SoundBrowserPanel:GetCookie("wire_soundfile", "") // load last session + end + TabFileBrowser:SetOpenFile(path) + end, SoundBrowserPanel, TabFileBrowser, path, se) +end + +concommand.Add("wire_sound_browser_open", OpenSoundBrowser) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/text_editor/modes/e2.lua b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/modes/e2.lua new file mode 100644 index 0000000..df8f5f2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/modes/e2.lua @@ -0,0 +1,557 @@ +local table_concat = table.concat +local string_sub = string.sub +local string_gmatch = string.gmatch +local string_gsub = string.gsub + +local EDITOR = {} + +local function istype(tp) + return wire_expression_types[tp:upper()] or tp == "number" +end + +-- keywords[name][nextchar!="("] +local keywords = { + -- keywords that can be followed by a "(": + ["if"] = { [true] = true, [false] = true }, + ["elseif"] = { [true] = true, [false] = true }, + ["while"] = { [true] = true, [false] = true }, + ["for"] = { [true] = true, [false] = true }, + ["foreach"] = { [true] = true, [false] = true }, + ["switch"] = { [true] = true, [false] = true }, + ["case"] = { [true] = true, [false] = true }, + ["default"] = { [true] = true, [false] = true }, + + -- keywords that cannot be followed by a "(": + ["else"] = { [true] = true }, + ["break"] = { [true] = true }, + ["continue"] = { [true] = true }, + --["function"] = { [true] = true }, + ["return"] = { [true] = true }, + ["local"] = { [true] = true }, +} + +-- fallback for nonexistant entries: +setmetatable(keywords, { __index=function(tbl,index) return {} end }) + +local directives = { + ["@name"] = 0, -- all yellow + ["@model"] = 0, + ["@inputs"] = 1, -- directive yellow, types orange, rest normal + ["@outputs"] = 1, + ["@persist"] = 1, + ["@trigger"] = 2, -- like 1, except that all/none are yellow + ["@autoupdate"] = 0, +} + +local colors = { + ["directive"] = { Color(240, 240, 160), false}, -- yellow + ["number"] = { Color(240, 160, 160), false}, -- light red + ["function"] = { Color(160, 160, 240), false}, -- blue + ["notfound"] = { Color(240, 96, 96), false}, -- dark red + ["variable"] = { Color(160, 240, 160), false}, -- light green + ["string"] = { Color(128, 128, 128), false}, -- grey + ["keyword"] = { Color(160, 240, 240), false}, -- turquoise + ["operator"] = { Color(224, 224, 224), false}, -- white + ["comment"] = { Color(128, 128, 128), false}, -- grey + ["ppcommand"] = { Color(240, 96, 240), false}, -- purple + ["typename"] = { Color(240, 160, 96), false}, -- orange + ["constant"] = { Color(240, 160, 240), false}, -- pink + ["userfunction"] = { Color(102, 122, 102), false}, -- dark grayish-green +} + +function EDITOR:GetSyntaxColor(name) + return colors[name][1] +end + +function EDITOR:SetSyntaxColor( colorname, colr ) + if not colors[colorname] then return end + colors[colorname][1] = colr +end + +-- cols[n] = { tokendata, color } +local cols = {} +local lastcol +local function addToken(tokenname, tokendata) + local color = colors[tokenname] + if lastcol and color == lastcol[2] then + lastcol[1] = lastcol[1] .. tokendata + else + cols[#cols + 1] = { tokendata, color, tokenname } + lastcol = cols[#cols] + end +end + +function EDITOR:CommentSelection(removecomment) + local sel_start, sel_caret = self:MakeSelection( self:Selection() ) + local mode = self:GetParent().BlockCommentStyleConVar:GetInt() + + if mode == 0 then -- New (alt 1) + local str = self:GetSelection() + if removecomment then + if str:find( "^#%[\n" ) and str:find( "\n%]#$" ) then + self:SetSelection( str:gsub( "^#%[\n(.+)\n%]#$", "%1" ) ) + sel_caret[1] = sel_caret[1] - 2 + end + else + self:SetSelection( "#[\n" .. str .. "\n]#" ) + sel_caret[1] = sel_caret[1] + 1 + sel_caret[2] = 3 + end + elseif mode == 1 then -- New (alt 2) + local str = self:GetSelection() + if removecomment then + if str:find( "^#%[" ) and str:find( "%]#$" ) then + self:SetSelection( str:gsub( "^#%[(.+)%]#$", "%1" ) ) + + sel_caret[2] = sel_caret[2] - 4 + end + else + self:SetSelection( "#[" .. self:GetSelection() .. "]#" ) + end + elseif mode == 2 then -- Old + local comment_char = "#" + if removecomment then + -- shift-TAB with a selection -- + local tmp = string_gsub("\n"..self:GetSelection(), "\n"..comment_char, "\n") + + -- makes sure that the first line is outdented + self:SetSelection(tmp:sub(2)) + else + -- plain TAB with a selection -- + self:SetSelection(comment_char .. self:GetSelection():gsub("\n", "\n"..comment_char)) + end + else + ErrorNoHalt( "Invalid block comment style" ) + end + + return { sel_start, sel_caret } +end + +function EDITOR:BlockCommentSelection(removecomment) + local sel_start, sel_caret = self:MakeSelection( self:Selection() ) + local str = self:GetSelection() + if removecomment then + if str:find( "^#%[" ) and str:find( "%]#$" ) then + self:SetSelection( str:gsub( "^#%[(.+)%]#$", "%1" ) ) + + if sel_caret[1] == sel_start[1] then + sel_caret[2] = sel_caret[2] - 4 + else + sel_caret[2] = sel_caret[2] - 2 + end + end + else + self:SetSelection( "#[" .. str .."]#" ) + + if sel_caret[1] == sel_start[1] then + sel_caret[2] = sel_caret[2] + 4 + else + sel_caret[2] = sel_caret[2] + 2 + end + end + return { sel_start, sel_caret } +end + +function EDITOR:ShowContextHelp(word) + E2Helper.Show() + E2Helper.UseE2(self:GetParent().EditorType) + E2Helper.Show(word) +end + +function EDITOR:ResetTokenizer(row) + if row == self.Scroll[1] then + + -- This code checks if the visible code is inside a string or a block comment + self.blockcomment = nil + self.multilinestring = nil + local singlelinecomment = false + + local str = string_gsub( table_concat( self.Rows, "\n", 1, self.Scroll[1]-1 ), "\r", "" ) + + for pos, char in string_gmatch( str, '()([#"\n])' ) do + if not self.blockcomment and not self.multilinestring and not singlelinecomment then + if char == '"' then + self.multilinestring = true + elseif char == "#" and string_sub( str, pos + 1, pos + 1 ) == "[" then + self.blockcomment = true + elseif char == "#" then + singlelinecomment = true + end + elseif self.multilinestring and char == '"' then + local escapecount = 0 + while pos - escapecount - 1 > 0 and string_sub( str, pos - escapecount - 1, pos - escapecount -1 ) == "\\" do + escapecount = escapecount + 1 + end + if escapecount % 2 == 0 then + self.multilinestring = nil + end + elseif self.blockcomment and char == "#" and string_sub( str, pos - 1, pos - 1 ) == "]" then + self.blockcomment = nil + elseif singlelinecomment and char == "\n" then + singlelinecomment = false + end + end + end + + + for k,v in pairs( self.e2fs_functions ) do + if v == row then + self.e2fs_functions[k] = nil + end + end +end + +function EDITOR:SyntaxColorLine(row) + cols,lastcol = {}, nil + + + self:ResetTokenizer(row) + self:NextCharacter() + + -- 0=name 1=port 2=trigger 3=foreach 4=foreachkey 5=foreachvalue + local highlightmode = nil + + if self.blockcomment then + if self:NextPattern(".-]#") then + self.blockcomment = nil + else + self:NextPattern(".*") + end + + addToken("comment", self.tokendata) + elseif self.multilinestring then + while self.character do -- Find the ending " + if self.character == '"' then + self.multilinestring = nil + self:NextCharacter() + break + end + if self.character == "\\" then self:NextCharacter() end + self:NextCharacter() + end + + addToken("string", self.tokendata) + elseif self:NextPattern("^@[^ ]*") then + highlightmode = directives[self.tokendata] + + -- check for unknown directives + if not highlightmode then + return { + { "@", colors.directive }, + { self.line:sub(2), colors.notfound } + } + end + + -- check for plain text directives + if highlightmode == 0 then return {{ self.line, colors.directive }} end + + -- parse the rest like regular code + cols = {{ self.tokendata, colors.directive }} + end + + local found = self:SkipPattern( "( *function)" ) + if found then + addToken( "keyword", found ) -- Add "function" + self.tokendata = "" -- Reset tokendata + + local spaces = self:SkipPattern( " *" ) + if spaces then addToken( "comment", spaces ) end + + if self:NextPattern( "[a-z][a-zA-Z0-9]*%s%s*[a-z][a-zA-Z0-9]*:[a-z][a-zA-Z0-9_]*" ) then -- Everything specified (returntype typeindex:funcname) + local returntype, spaces, typeindex, funcname = self.tokendata:match( "([a-z][a-zA-Z0-9]*)(%s%s*)([a-z][a-zA-Z0-9]*):([a-z][a-zA-Z0-9_]*)" ) + + if istype( returntype ) or returntype == "void" then + addToken( "typename", returntype ) + else + addToken( "notfound", returntype ) + end + addToken( "comment", spaces ) + if istype( typeindex ) then + addToken( "typename", typeindex ) + else + addToken( "notfound", typeindex ) + end + addToken( "operator", ":" ) + addToken( "userfunction", funcname ) + + if not wire_expression2_funclist[funcname] then + self.e2fs_functions[funcname] = row + end + + self.tokendata = "" + elseif self:NextPattern( "[a-z][a-zA-Z0-9]*%s%s*[a-z][a-zA-Z0-9_]*" ) then -- returntype funcname + local returntype, spaces, funcname = self.tokendata:match( "([a-z][a-zA-Z0-9]*)(%s%s*)([a-z][a-zA-Z0-9_]*)" ) + + if istype( returntype ) or returntype == "void" then + addToken( "typename", returntype ) + else + addToken( "notfound", returntype ) + end + addToken( "comment", spaces ) + addToken( "userfunction", funcname ) + + if not wire_expression2_funclist[funcname] then + self.e2fs_functions[funcname] = row + end + + self.tokendata = "" + elseif self:NextPattern( "[a-z][a-zA-Z0-9]*:[a-z][a-zA-Z0-9_]*" ) then -- typeindex:funcname + local typeindex, funcname = self.tokendata:match( "([a-z][a-zA-Z0-9]*):([a-z][a-zA-Z0-9_]*)" ) + + if istype( typeindex ) then + addToken( "typename", typeindex ) + else + addToken( "notfound", typeindex ) + end + addToken( "operator", ":" ) + addToken( "userfunction", funcname ) + + if not wire_expression2_funclist[funcname] then + self.e2fs_functions[funcname] = row + end + + self.tokendata = "" + elseif self:NextPattern( "[a-z][a-zA-Z0-9_]*" ) then -- funcname + local funcname = self.tokendata:match( "[a-z][a-zA-Z0-9_]*" ) + + addToken( "userfunction", funcname ) + + if not wire_expression2_funclist[funcname] then + self.e2fs_functions[funcname] = row + end + + self.tokendata = "" + end + + if self:NextPattern( "%(" ) then -- We found a bracket + -- Color the bracket + addToken( "operator", self.tokendata ) + + while self.character and self.character ~= ")" do -- Loop until the ending bracket + self.tokendata = "" + + local spaces = self:SkipPattern( " *" ) + if spaces then addToken( "comment", spaces ) end + + local invalidInput = self:SkipPattern( "[^A-Z:%[]*" ) + if invalidInput then addToken( "notfound", invalidInput ) end + + if self:NextPattern( "%[" ) then -- Found a [ + -- Color the bracket + addToken( "operator", self.tokendata ) + self.tokendata = "" + + while self:NextPattern( "[A-Z][a-zA-Z0-9_]*" ) do -- If we found a variable + addToken( "variable", self.tokendata ) + self.tokendata = "" + + local spaces = self:SkipPattern( " *" ) + if spaces then addToken( "comment", spaces ) end + end + + if self:NextPattern( "%]" ) then + addToken( "operator", "]" ) + self.tokendata = "" + end + elseif self:NextPattern( "[A-Z][a-zA-Z0-9_]*" ) then -- If we found a variable + -- Color the variable + addToken( "variable", self.tokendata ) + self.tokendata = "" + end + + if self:NextPattern( ":" ) then -- Check for the colon + addToken( "operator", ":" ) + self.tokendata = "" + end + + -- Find the type + if self:NextPattern( "[a-z][a-zA-Z0-9_]*" ) then + if istype( self.tokendata ) or self.tokendata == "void" then -- If it's a type + addToken( "typename", self.tokendata ) + else -- aww + addToken( "notfound", self.tokendata ) + end + end + + local spaces = self:SkipPattern( " *" ) + if spaces then addToken( "comment", spaces ) end + + -- If we found a comma, skip it + if self.character == "," then addToken( "operator", "," ) self:NextCharacter() end + end + end + + self.tokendata = "" + if self:NextPattern( "%) *{?" ) then -- check for ending bracket (and perhaps an ending {?) + addToken( "operator", self.tokendata ) + end + end + + while self.character do + local tokenname = "" + self.tokendata = "" + + -- eat all spaces + local spaces = self:SkipPattern(" *") + if spaces then addToken("operator", spaces) end + if not self.character then break end + + -- eat next token + if self:NextPattern("^_[A-Z][A-Z_0-9]*") then + local word = self.tokendata + for k,_ in pairs( wire_expression2_constants ) do + if k == word then + tokenname = "constant" + end + end + if tokenname == "" then tokenname = "notfound" end + elseif self:NextPattern("^0[xb][0-9A-F]+") then + tokenname = "number" + elseif self:NextPattern("^[0-9][0-9.e]*") then + tokenname = "number" + + elseif self:NextPattern("^[a-z][a-zA-Z0-9_]*") then + local sstr = self.tokendata + if highlightmode then + if highlightmode == 1 and istype(sstr) then + tokenname = "typename" + elseif highlightmode == 2 and (sstr == "all" or sstr == "none") then + tokenname = "directive" + elseif (highlightmode == 4 or highlightmode == 5) and istype(sstr) then + tokenname = "typename" + + if highlightmode == 5 then + highlightmode = nil + end + else + tokenname = "notfound" + end + else + -- is this a keyword or a function? + local char = self.character or "" + local keyword = char ~= "(" + + local spaces = self:SkipPattern(" *") or "" + + if self.character == "]" then + -- X[Y,typename] + tokenname = istype(sstr) and "typename" or "notfound" + elseif keywords[sstr][keyword] then + tokenname = "keyword" + if sstr == "foreach" then + highlightmode = 3 + elseif sstr == "return" and self:NextPattern( "void" ) then + addToken( "keyword", "return" ) + tokenname = "typename" + self.tokendata = spaces .. "void" + spaces = "" + end + elseif wire_expression2_funclist[sstr] then + tokenname = "function" + + elseif self.e2fs_functions[sstr] then + tokenname = "userfunction" + + else + tokenname = "notfound" + + local correctName = wire_expression2_funclist_lowercase[sstr:lower()] + if correctName then + self.tokendata = "" + for i = 1,#sstr do + local c = sstr:sub(i,i) + if correctName:sub(i,i) == c then + tokenname = "function" + else + tokenname = "notfound" + end + if i == #sstr then + self.tokendata = c + else + addToken(tokenname, c) + end + end + end + end + addToken(tokenname, self.tokendata) + tokenname = "operator" + self.tokendata = spaces + end + + elseif self:NextPattern("^[A-Z][a-zA-Z0-9_]*") then + tokenname = "variable" + + if highlightmode == 3 then + highlightmode = 4 + elseif highlightmode == 4 then + highlightmode = 5 + end + elseif self.character == '"' then + self:NextCharacter() + while self.character do -- Find the ending " + if self.character == '"' then + tokenname = "string" + break + end + if self.character == "\\" then self:NextCharacter() end + self:NextCharacter() + end + + if tokenname == "" then -- If no ending " was found... + self.multilinestring = true + tokenname = "string" + else + self:NextCharacter() + end + + elseif self.character == "#" then + self:NextCharacter() + if self.character == "[" then -- Check if there is a [ directly after the # + while self.character do -- Find the ending ] + if self.character == "]" then + self:NextCharacter() + if self.character == "#" then -- Check if there is a # directly after the ending ] + tokenname = "comment" + break + end + end + if self.character == "\\" then self:NextCharacter() end + self:NextCharacter() + end + if tokenname == "" then -- If no ending ]# was found... + self.blockcomment = true + tokenname = "comment" + else + self:NextCharacter() + end + end + + if tokenname == "" then + + self:NextPattern("[^ ]*") -- Find the whole word + + if E2Lib.PreProcessor["PP_"..self.tokendata:sub(2)] then + -- there is a preprocessor command by that name => mark as such + tokenname = "ppcommand" + elseif self.tokendata == "#include" then + tokenname = "keyword" + else + -- eat the rest and mark as a comment + self:NextPattern(".*") + tokenname = "comment" + end + + end + else + self:NextCharacter() + + tokenname = "operator" + end + + addToken(tokenname, self.tokendata) + end + + return cols +end + +WireTextEditor.Modes.E2 = EDITOR diff --git a/garrysmod/addons/feature-wire/lua/wire/client/text_editor/modes/zcpu.lua b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/modes/zcpu.lua new file mode 100644 index 0000000..d9fc97a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/modes/zcpu.lua @@ -0,0 +1,325 @@ +local math_floor = math.floor +local string_gmatch = string.gmatch +local string_gsub = string.gsub +local draw_WordBox = draw.WordBox + +local EDITOR = {} + +-- CPU hint box +local oldpos, haschecked = {0,0}, false +function EDITOR:Think() + local caret = self:CursorToCaret() + local startpos, word = self:getWordStart( caret, true ) + + if word and word ~= "" then + if not haschecked then + oldpos = {startpos[1],startpos[2]} + haschecked = true + timer.Simple(0.3,function() + if not self then return end + if not self.CursorToCaret then return end + local caret = self:CursorToCaret() + local startpos, word = self:getWordStart( caret, true ) + if startpos[1] == oldpos[1] and startpos[2] == oldpos[2] then + self.CurrentVarValue = { startpos, word } + end + end) + elseif (oldpos[1] ~= startpos[1] or oldpos[2] ~= startpos[2]) and haschecked then + haschecked = false + self.CurrentVarValue = nil + oldpos = {0,0} + end + else + self.CurrentVarValue = nil + haschecked = false + oldpos = {0,0} + end +end + +local colors = { + ["normal"] = { Color(255, 255, 136), false}, + ["opcode"] = { Color(255, 136, 0), false}, + ["comment"] = { Color(128, 128, 128), false}, + ["register"] = { Color(255, 255, 136), false}, + ["number"] = { Color(232, 232, 0), false}, + ["string"] = { Color(255, 136, 136), false}, + ["filename"] = { Color(232, 232, 232), false}, + ["label"] = { Color(255, 255, 176), false}, + ["keyword"] = { Color(255, 136, 0), false}, + ["memref"] = { Color(232, 232, 0), false}, + ["pmacro"] = { Color(136, 136, 255), false}, + ["error"] = { Color(240, 96, 96), false}, + -- ["compare"] = { Color(255, 186, 40), true}, +} + +-- Build lookup table for opcodes +local opcodeTable = {} +for k,v in pairs(CPULib.InstructionTable) do + if v.Mnemonic ~= "RESERVED" then + opcodeTable[v.Mnemonic] = true + end +end + +-- Build lookup table for keywords +local keywordsList = { + "GOTO","FOR","IF","ELSE","WHILE","DO","SWITCH","CASE","CONST","RETURN","BREAK", + "CONTINUE","EXPORT","INLINE","FORWARD","REGISTER","DB","ALLOC","SCALAR","VECTOR1F", + "VECTOR2F","UV","VECTOR3F","VECTOR4F","COLOR","VEC1F","VEC2F","VEC3F","VEC4F","MATRIX", + "STRING","DB","DEFINE","CODE","DATA","ORG","OFFSET","INT48","FLOAT","CHAR","VOID", + "INT","FLOAT","CHAR","VOID","PRESERVE","ZAP","STRUCT","VECTOR" +} + +local keywordsTable = {} +for k,v in pairs(keywordsList) do + keywordsTable[v] = true +end + +-- Build lookup table for registers +local registersTable = { + EAX = true,EBX = true,ECX = true,EDX = true,ESI = true,EDI = true, + ESP = true,EBP = true,CS = true,SS = true,DS = true,ES = true,GS = true, + FS = true,KS = true,LS = true +} +for reg=0,31 do registersTable["R"..reg] = true end +for port=0,1023 do registersTable["PORT"..port] = true end + +-- Build lookup table for macros +local macroTable = { + ["PRAGMA"] = true, + ["INCLUDE"] = true, + ["#INCLUDE##"] = true, + ["DEFINE"] = true, + ["IFDEF"] = true, + ["IFNDEF"] = true, + ["ENDIF"] = true, + ["ELSE"] = true, + ["UNDEF"] = true, +} + +function EDITOR:CommentSelection(removecomment) + local comment_char = "//" + if removecomment then + -- shift-TAB with a selection -- + local tmp = string_gsub("\n"..self:GetSelection(), "\n"..comment_char, "\n") + + -- makes sure that the first line is outdented + self:SetSelection(tmp:sub(2)) + else + -- plain TAB with a selection -- + self:SetSelection(comment_char .. self:GetSelection():gsub("\n", "\n"..comment_char)) + end +end + +function EDITOR:BlockCommentSelection(removecomment) + local sel_start, sel_caret = self:MakeSelection( self:Selection() ) + local str = self:GetSelection() + if removecomment then + if str:find( "^/%*" ) and str:find( "%*/$" ) then + self:SetSelection( str:gsub( "^/%*(.+)%*/$", "%1" ) ) + + sel_caret[2] = sel_caret[2] - 2 + end + else + self:SetSelection( "/*" .. str .. "*/" ) + + if sel_caret[1] == sel_start[1] then + sel_caret[2] = sel_caret[2] + 4 + else + sel_caret[2] = sel_caret[2] + 2 + end + end + return { sel_start, sel_caret } +end + +function EDITOR:ShowContextHelp(word) + E2Helper.Show() + E2Helper.UseCPU(self:GetParent().EditorType) + E2Helper.Show(word) +end + +function EDITOR:ResetTokenizer(row) + if row == self.Scroll[1] then + -- As above, but for HL-ZASM: Check whether the line self.Scroll[1] starts within a block comment. + self.blockcomment = nil + + for k=1, self.Scroll[1]-1 do + local row = self.Rows[k] + + for match in string_gmatch(row, "[/*][/*]") do + if match == "//" then + -- single line comment start; skip remainder of line + break + elseif match == "/*" then + self.blockcomment = true + elseif match == "*/" then + self.blockcomment = nil + end + end + end + end +end + +function EDITOR:SyntaxColorLine(row) + local cols = {} + self:ResetTokenizer(row) + self:NextCharacter() + + if self.blockcomment then + if self:NextPattern(".-%*/") then + self.blockcomment = nil + else + self:NextPattern(".*") + end + + cols[#cols + 1] = {self.tokendata, colors["comment"]} + end + + local isGpu = self:GetParent().EditorType == "GPU" + + while self.character do + local tokenname = "" + self.tokendata = "" + + self:NextPattern(" *") + if not self.character then break end + + if self:NextPattern("^[a-zA-Z0-9_@.]+:") then + tokenname = "label" + elseif self:NextPattern("^[a-zA-Z0-9_@.]+") then + local sstr = string.upper(self.tokendata:Trim()) + if opcodeTable[sstr] then + tokenname = "opcode" + elseif registersTable[sstr] then + tokenname = "register" + elseif keywordsTable[sstr] then + tokenname = "keyword" + elseif tonumber(self.tokendata) then + tokenname = "number" + else + tokenname = "normal" + end + elseif (self.character == "'") or (self.character == "\"") then + tokenname = "string" + local delimiter = self.character + self:NextCharacter() + while self.character ~= delimiter do + if not self.character then tokenname = "error" break end + if self.character == "\\" then self:NextCharacter() end + self:NextCharacter() + end + self:NextCharacter() + elseif self:NextPattern("^//.*$") then + tokenname = "comment" + elseif self:NextPattern("^/%*") then -- start of a multi-line comment + --addToken("comment", self.tokendata) + self.blockcomment = true + if self:NextPattern(".-%*/") then + self.blockcomment = nil + else + self:NextPattern(".*") + end + + tokenname = "comment" + elseif self.character == "#" then + self:NextCharacter() + + if self:NextPattern("include +<") then + + cols[#cols + 1] = {self.tokendata:sub(1,-2), colors["pmacro"]} + + self.tokendata = "<" + if self:NextPattern("^[a-zA-Z0-9_/\\]+%.txt>") then + tokenname = "filename" + else + self:NextPattern(".*$") + tokenname = "normal" + end + elseif self:NextPattern("include +\"") then + + cols[#cols + 1] = {self.tokendata:sub(1,-2), colors["pmacro"]} + + self.tokendata = "\"" + if self:NextPattern("^[a-zA-Z0-9_/\\]+%.txt\"") then + tokenname = "filename" + else + self:NextPattern(".*$") + tokenname = "normal" + end + elseif self:NextPattern("^[a-zA-Z0-9_@.#]+") then + local sstr = string.sub(string.upper(self.tokendata:Trim()),2) + if macroTable[sstr] then + self:NextPattern(".*$") + tokenname = "pmacro" + else + tokenname = "memref" + end + else + tokenname = "memref" + end + elseif self.character == "[" or self.character == "]" then + self:NextCharacter() + tokenname = "memref" + else + self:NextCharacter() + tokenname = "normal" + end + + local color = colors[tokenname] + if #cols > 1 and color == cols[#cols][2] then + cols[#cols][1] = cols[#cols][1] .. self.tokendata + else + cols[#cols + 1] = {self.tokendata, color} + end + end + return cols +end + +function EDITOR:PopulateMenu(menu) + if not self.chosenfile then return end + + menu:AddSpacer() + + local caretPos = self:CursorToCaret() + local IsBreakpointSet = CPULib.GetDebugBreakpoint( self.chosenfile, caretPos ) + + if not IsBreakpointSet then + menu:AddOption( "Add Breakpoint", function() + CPULib.SetDebugBreakpoint( self.chosenfile, caretPos, true ) + end) + -- menu:AddOption( "Add Conditional Breakpoint", function() + -- Derma_StringRequestNoBlur( "Add Conditional Breakpoint", "456", "123", + -- function( strTextOut ) + -- CPULib.SetDebugBreakpoint( caretPos, strTextOut ) + -- end ) + -- end) + else + menu:AddOption( "Remove Breakpoint", function() + CPULib.SetDebugBreakpoint( self.chosenfile, caretPos ) + end) + end +end + +function EDITOR:Paint() + -- Paint CPU debug hints + if self.CurrentVarValue then + local pos = self.CurrentVarValue[1] + local x, y = (pos[2]+2) * self.FontWidth, (pos[1]-1-self.Scroll[1]) * self.FontHeight + local txt = CPULib.GetDebugPopupText(self.CurrentVarValue[2]) + if txt then + draw_WordBox(2, x, y, txt, "E2SmallFont", Color(0,0,0,255), Color(255,255,255,255) ) + end + end + + if CPULib.DebuggerAttached then + local debugWindowText = CPULib.GetDebugWindowText() + for k,v in ipairs(debugWindowText) do + if v ~= "" then + local y = (k % 24) + local x = 15*(1 + math_floor(#debugWindowText / 24) - math_floor(k / 24)) + draw_WordBox(2, self:GetWide()-self.FontWidth*x, self.FontHeight*(-1+y), v, "E2SmallFont", Color(0,0,0,255), Color(255,255,255,255) ) + end + end + end +end + +WireTextEditor.Modes.ZCPU = EDITOR diff --git a/garrysmod/addons/feature-wire/lua/wire/client/text_editor/texteditor.lua b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/texteditor.lua new file mode 100644 index 0000000..65c8e7d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/texteditor.lua @@ -0,0 +1,2875 @@ +-- +-- Expression 2 Text Editor for Garry's Mod +-- Andreas "Syranide" Svensson, me@syranide.com +-- + +local string_Explode = string.Explode +local table_concat = table.concat +local string_sub = string.sub +local table_remove = table.remove +local math_floor = math.floor +local math_Clamp = math.Clamp +local math_ceil = math.ceil +local string_match = string.match +local string_gmatch = string.gmatch +local string_gsub = string.gsub +local string_rep = string.rep +local string_byte = string.byte +local string_format = string.format +local string_Trim = string.Trim +local string_reverse = string.reverse +local math_min = math.min +local table_insert = table.insert +local table_sort = table.sort +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawRect = surface.DrawRect +local surface_SetFont = surface.SetFont +local surface_GetTextSize = surface.GetTextSize +local surface_PlaySound = surface.PlaySound +local surface_SetTextPos = surface.SetTextPos +local surface_SetTextColor = surface.SetTextColor +local surface_DrawText = surface.DrawText +local draw_SimpleText = draw.SimpleText +local draw_WordBox = draw.WordBox +local draw_RoundedBox = draw.RoundedBox + +WireTextEditor = { Modes = {} } +include("modes/e2.lua") +include("modes/zcpu.lua") +WireTextEditor.Modes.Default = { SyntaxColorLine = function(self, row) return { { self.Rows[row], { Color(255, 255, 255, 255), false } } } end } + +local wire_expression2_autocomplete_controlstyle = CreateClientConVar( "wire_expression2_autocomplete_controlstyle", "0", true, false ) + +local AC_STYLE_DEFAULT = 0 -- Default style - Tab/CTRL+Tab to choose item;\nEnter/Space to use;\nArrow keys to abort. +local AC_STYLE_VISUALCSHARP = 1 -- Visual C# Style - Ctrl+Space to use the top match;\nArrow keys to choose item;\nTab/Enter/Space to use;\nCode validation hotkey (ctrl+space) moved to ctrl+b. +local AC_STYLE_SCROLLER = 2 -- Scroller style - Mouse scroller to choose item;\nMiddle mouse to use. +local AC_STYLE_SCROLLER_ENTER = 3 -- Scroller Style w/ Enter - Mouse scroller to choose item;\nEnter to use. +local AC_STYLE_ECLIPSE = 4 -- Eclipse Style - Enter to use top match;\nTab to enter auto completion menu;\nArrow keys to choose item;\nEnter to use;\nSpace to abort. +local AC_STYLE_ATOM = 5 -- Atom style - Tab/Enter to use, arrow keys to choose + +local EDITOR = {} + +function EDITOR:Init() + self:SetCursor("beam") + + self.Rows = {""} + self.Caret = {1, 1} + self.Start = {1, 1} + self.Scroll = {1, 1} + self.Size = {1, 1} + self.Undo = {} + self.Redo = {} + self.PaintRows = {} + + self.CurrentMode = assert(WireTextEditor.Modes.Default) + + self.LineNumberWidth = 2 + + self.Blink = RealTime() + + self.ScrollBar = vgui.Create("DVScrollBar", self) + self.ScrollBar:SetUp(1, 1) + + self.TextEntry = vgui.Create("TextEntry", self) + self.TextEntry:SetMultiline(true) + self.TextEntry:SetSize(0, 0) + + self.TextEntry.OnLoseFocus = function (self) self.Parent:_OnLoseFocus() end + self.TextEntry.OnTextChanged = function (self) self.Parent:_OnTextChanged() end + self.TextEntry.OnKeyCodeTyped = function (self, code) return self.Parent:_OnKeyCodeTyped(code) end + + self.TextEntry.Parent = self + + self.LastClick = 0 + + self.e2fs_functions = {} + + self.Colors = { + dblclickhighlight = Color(0, 100, 0), + } +end + +function EDITOR:SetMode(mode_name) + self.CurrentMode = WireTextEditor.Modes[mode_name or "Default"] + if not self.CurrentMode then + Msg("Couldn't find text editor mode '".. tostring(mode_name) .. "'") + self.CurrentMode = assert(WireTextEditor.Modes.Default, "Couldn't find default text editor mode") + end +end + +function EDITOR:DoAction(name, ...) + if not self.CurrentMode then return end + local f = assert(self.CurrentMode, "No current mode set")[name] + if not f then f = WireTextEditor.Modes.Default[name] end + if f then return f(self, ...) end +end + +function EDITOR:GetParent() + return self.parentpanel +end + +function EDITOR:RequestFocus() + self.TextEntry:RequestFocus() +end + +function EDITOR:OnGetFocus() + self.TextEntry:RequestFocus() +end + +function EDITOR:CursorToCaret() + local x, y = self:CursorPos() + + x = x - (self.LineNumberWidth + 6) + if x < 0 then x = 0 end + if y < 0 then y = 0 end + + local line = math_floor(y / self.FontHeight) + local char = math_floor(x / self.FontWidth+0.5) + + line = line + self.Scroll[1] + char = char + self.Scroll[2] + + if line > #self.Rows then line = #self.Rows end + local length = #self.Rows[line] + if char > length + 1 then char = length + 1 end + + return { line, char } +end + +local wire_expression2_editor_highlight_on_double_click = CreateClientConVar( "wire_expression2_editor_highlight_on_double_click", "1", true, false ) + +function EDITOR:OpenContextMenu() + self:AC_SetVisible( false ) + local menu = DermaMenu() + + if self:CanUndo() then + menu:AddOption("Undo", function() + self:DoUndo() + end) + end + if self:CanRedo() then + menu:AddOption("Redo", function() + self:DoRedo() + end) + end + + if self:CanUndo() or self:CanRedo() then + menu:AddSpacer() + end + + if self:HasSelection() then + menu:AddOption("Cut", function() + self:Cut() + end) + menu:AddOption("Copy", function() + self:Copy() + end) + end + + menu:AddOption("Paste", function() + if self.CurrentMode.clipboard then + self:SetSelection(self.CurrentMode.clipboard) + else + self:SetSelection() + end + end) + + if self:HasSelection() then + menu:AddOption("Delete", function() + self:SetSelection() + end) + end + + menu:AddSpacer() + + menu:AddOption("Select all", function() + self:SelectAll() + end) + + menu:AddSpacer() + + menu:AddOption("Indent", function() + self:Indent(false) + end) + menu:AddOption("Outdent", function() + self:Indent(true) + end) + + if self:HasSelection() then + menu:AddSpacer() + + menu:AddOption("Comment Block", function() + self:CommentSelection(false) + end) + menu:AddOption("Uncomment Block", function() + self:CommentSelection(true) + end) + + menu:AddOption("Comment Selection",function() + self:BlockCommentSelection( false ) + end) + menu:AddOption("Uncomment Selection",function() + self:BlockCommentSelection( true ) + end) + end + + self:DoAction("PopulateMenu", menu) + + menu:AddSpacer() + + menu:AddOption( "Copy with BBCode colors", function() + local str = string_format( "[code][font=%s]", self:GetParent().FontConVar:GetString() ) + + local prev_colors + local first_loop = true + + for i=1,#self.Rows do + local colors = self:SyntaxColorLine(i) + + for _, v in pairs( colors ) do + local color = v[2][1] + + if (prev_colors and prev_colors == color) or string_Trim(v[1]) == "" then + str = str .. v[1] + else + prev_colors = color + + if first_loop then + str = str .. string_format( '[color="#%x%x%x"]', color.r - 50, color.g - 50, color.b - 50 ) .. v[1] + first_loop = false + else + str = str .. string_format( '[/color][color="#%x%x%x"]', color.r - 50, color.g - 50, color.b - 50 ) .. v[1] + end + end + end + + str = str .. "\r\n" + + end + + str = str .. "[/color][/font][/code]" + + self.CurrentMode.clipboard = str + SetClipboardText( str ) + end) + + menu:Open() + return menu +end + +function EDITOR:OnMousePressed(code) + if code == MOUSE_LEFT then + local cursor = self:CursorToCaret() + if (CurTime() - self.LastClick) < 1 and self.tmp and cursor[1] == self.Caret[1] and cursor[2] == self.Caret[2] then + self.Start = self:getWordStart(self.Caret) + self.Caret = self:getWordEnd(self.Caret) + self.tmp = false + + if wire_expression2_editor_highlight_on_double_click:GetBool() then + self.HighlightedAreasByDoubleClick = {} + local all_finds = self:FindAllWords( self:GetSelection() ) + if all_finds then + all_finds[0] = {1,1} -- Set [0] so the [i-1]'s don't fail on the first iteration + self.HighlightedAreasByDoubleClick[0] = {{1,1}, {1,1}} + for i=1,#all_finds do + -- Instead of finding the caret by searching from the beginning every time, start searching from the previous caret + local start = all_finds[i][1] - all_finds[i-1][1] + local stop = all_finds[i][2] - all_finds[i-1][2] + local caretstart = self:MovePosition( self.HighlightedAreasByDoubleClick[i-1][1], start ) + local caretstop = self:MovePosition( self.HighlightedAreasByDoubleClick[i-1][2], stop ) + self.HighlightedAreasByDoubleClick[i] = { caretstart, caretstop } + + -- This checks if it's NOT the word the user just highlighted + if caretstart[1] ~= self.Start[1] or caretstart[2] ~= self.Start[2] or + caretstop[1] ~= self.Caret[1] or caretstop[2] ~= self.Caret[2] then + local c = self:GetSyntaxColor("dblclickhighlight") + self:HighlightArea( { caretstart, caretstop }, c.r, c.g, c.b, 100 ) + end + end + end + end + return + elseif self.HighlightedAreasByDoubleClick then + for i=1,#self.HighlightedAreasByDoubleClick do + self:HighlightArea( self.HighlightedAreasByDoubleClick[i] ) + end + self.HighlightedAreasByDoubleClick = nil + end + + self.tmp = true + + self.LastClick = CurTime() + self:RequestFocus() + self.Blink = RealTime() + self.MouseDown = true + + self.Caret = self:CopyPosition( cursor ) + if not input.IsKeyDown(KEY_LSHIFT) and not input.IsKeyDown(KEY_RSHIFT) then + self.Start = self:CopyPosition( cursor ) + end + self:AC_Check() + elseif code == MOUSE_RIGHT then + self:OpenContextMenu() + end +end + +function EDITOR:OnMouseReleased(code) + if not self.MouseDown then return end + + if code == MOUSE_LEFT then + self.MouseDown = nil + if not self.tmp then return end + self.Caret = self:CursorToCaret() + end +end + +function EDITOR:SetText(text) + self.Rows = string_Explode("\n", text) + if self.Rows[#self.Rows] ~= "" then + self.Rows[#self.Rows + 1] = "" + end + + self.Caret = {1, 1} + self.Start = {1, 1} + self.Scroll = {1, 1} + self.Undo = {} + self.Redo = {} + self.PaintRows = {} + self:AC_Reset() + + self.ScrollBar:SetUp(self.Size[1], #self.Rows - 1) +end + +function EDITOR:GetValue() + return (string_gsub(table_concat(self.Rows, "\n"), "\r", "")) +end + +function EDITOR:HighlightLine( line, r, g, b, a ) + if not self.HighlightedLines then self.HighlightedLines = {} end + if not r and self.HighlightedLines[line] then + self.HighlightedLines[line] = nil + return true + elseif r and g and b and a then + self.HighlightedLines[line] = { r, g, b, a } + return true + end + return false +end +function EDITOR:ClearHighlightedLines() self.HighlightedLines = nil end + +function EDITOR:PaintLine(row) + if row > #self.Rows then return end + + if not self.PaintRows[row] then + self.PaintRows[row] = self:SyntaxColorLine(row) + end + + local width, height = self.FontWidth, self.FontHeight + + if row == self.Caret[1] and self.TextEntry:HasFocus() then + surface_SetDrawColor(48, 48, 48, 255) + surface_DrawRect(self.LineNumberWidth + 5, (row - self.Scroll[1]) * height, self:GetWide() - (self.LineNumberWidth + 5), height) + end + + if self.HighlightedLines and self.HighlightedLines[row] then + local color = self.HighlightedLines[row] + surface_SetDrawColor( color[1], color[2], color[3], color[4] ) + surface_DrawRect(self.LineNumberWidth + 5, (row - self.Scroll[1]) * height, self:GetWide() - (self.LineNumberWidth + 5), height) + end + + if self:HasSelection() then + local start, stop = self:MakeSelection(self:Selection()) + local line, char = start[1], start[2] + local endline, endchar = stop[1], stop[2] + + surface_SetDrawColor(0, 0, 160, 255) + local length = self.Rows[row]:len() - self.Scroll[2] + 1 + + char = char - self.Scroll[2] + endchar = endchar - self.Scroll[2] + if char < 0 then char = 0 end + if endchar < 0 then endchar = 0 end + + if row == line and line == endline then + surface_DrawRect(char * width + self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, width * (endchar - char), height) + elseif row == line then + surface_DrawRect(char * width + self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, width * (length - char + 1), height) + elseif row == endline then + surface_DrawRect(self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, width * endchar, height) + elseif row > line and row < endline then + surface_DrawRect(self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, width * (length + 1), height) + end + end + + + draw_SimpleText(tostring(row), self.CurrentFont, self.LineNumberWidth + 2, (row - self.Scroll[1]) * height, Color(128, 128, 128, 255), TEXT_ALIGN_RIGHT) + + local offset = -self.Scroll[2] + 1 + for _, cell in ipairs(self.PaintRows[row]) do + if offset < 0 then + if cell[1]:len() > -offset then + local line = cell[1]:sub(1-offset) + offset = line:len() + + if cell[2][2] then + draw_SimpleText(line .. " ", self.CurrentFont .. "_Bold", self.LineNumberWidth+ 6, (row - self.Scroll[1]) * height, cell[2][1]) + else + draw_SimpleText(line .. " ", self.CurrentFont, self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, cell[2][1]) + end + else + offset = offset + cell[1]:len() + end + else + if cell[2][2] then + draw_SimpleText(cell[1] .. " ", self.CurrentFont .. "_Bold", offset * width + self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, cell[2][1]) + else + draw_SimpleText(cell[1] .. " ", self.CurrentFont, offset * width + self.LineNumberWidth + 6, (row - self.Scroll[1]) * height, cell[2][1]) + end + + offset = offset + cell[1]:len() + end + end + + +end + +function EDITOR:PerformLayout() + self.ScrollBar:SetSize(16, self:GetTall()) + self.ScrollBar:SetPos(self:GetWide() - 16, 0) + + self.Size[1] = math_floor(self:GetTall() / self.FontHeight) - 1 + self.Size[2] = math_floor((self:GetWide() - (self.LineNumberWidth + 6) - 16) / self.FontWidth) - 1 + + self.ScrollBar:SetUp(self.Size[1], #self.Rows - 1) +end + +function EDITOR:HighlightArea( area, r,g,b,a ) + if not self.HighlightedAreas then self.HighlightedAreas = {} end + if not r then + local _start, _stop = area[1], area[2] + for k,v in pairs( self.HighlightedAreas ) do + local start = v[1][1] + local stop = v[1][2] + if start[1] == _start[1] and start[2] == _start[2] and stop[1] == _stop[1] and stop[2] == _stop[2] then + table.remove( self.HighlightedAreas, k ) + break + end + end + return true + elseif r and g and b and a then + self.HighlightedAreas[#self.HighlightedAreas+1] = {area, r, g, b, a } + return true + end + return false +end +function EDITOR:ClearHighlightedAreas() self.HighlightedAreas = nil end + +do + -- match = { matchedWith, searchDown } + local matchSearch = { + ["{"] = { "}", true }, + ["}"] = { "{", false }, + + ["["] = { "]", true }, + ["]"] = { "[", false }, + + ["("] = { ")", true }, + [")"] = { "(", false }, + } + + -- This will convert forward text position to reverse text position and vice versa + local function fixPos(row, pos, downward) + return downward and pos or #row - pos + 1 + end + + local function matchBalanced(self, startPos, opening, closing, downward) + local searchStr = "[" .. string.PatternSafe(opening .. closing) .. "]" + local balance = 0 + + local startIndex = startPos[1] + local endIndex = downward and #self.Rows or 1 + local skip = downward and 1 or -1 + + for row = startIndex, endIndex, skip do + local rowStr = downward and self.Rows[row] or self.Rows[row]:reverse() + local pos = row == startPos[1] and fixPos(rowStr, startPos[2], downward) or 1 + + repeat + local foundPos = rowStr:find(searchStr, pos) + + if foundPos then + local editorPos = { row, fixPos(rowStr, foundPos, downward) } + local token = self:GetTokenAtPosition(editorPos) + + if token ~= "comment" and token ~= "string" then + local char = rowStr[foundPos] + + if char == opening then + balance = balance + 1 + else + balance = balance - 1 + end + + if balance == 0 then + return editorPos + end + end + + pos = foundPos + 1 + end + until not foundPos + end + end + + local function isMatchable(self, pos) + local char = self.Rows[pos[1]]:sub(pos[2], pos[2]) + if not matchSearch[char] then return false end + + local token = self:GetTokenAtPosition(pos) + if token == "comment" or token == "string" then return false end + + return true + end + + local function getMatchingCharacter(self, pos) + local char = self.Rows[pos[1]]:sub(pos[2], pos[2]) + local info = matchSearch[char] + + return matchBalanced(self, pos, char, info[1], info[2]) + end + + function EDITOR:PaintTextOverlay() + + if self.TextEntry:HasFocus() and self.Caret[2] - self.Scroll[2] >= 0 then + local width, height = self.FontWidth, self.FontHeight + + if (RealTime() - self.Blink) % 0.8 < 0.4 then + surface_SetDrawColor(240, 240, 240, 255) + surface_DrawRect((self.Caret[2] - self.Scroll[2]) * width + self.LineNumberWidth + 6, (self.Caret[1] - self.Scroll[1]) * height, 1, height) + end + + -- Area highlighting + if self.HighlightedAreas then + local xofs = self.LineNumberWidth + 6 + for _, data in pairs( self.HighlightedAreas ) do + local area, r,g,b,a = data[1], data[2], data[3], data[4], data[5] + surface_SetDrawColor( r,g,b,a ) + local start, stop = self:MakeSelection( area ) + + if start[1] == stop[1] then -- On the same line + surface_DrawRect( xofs + (start[2]-self.Scroll[2]) * width, (start[1]-self.Scroll[1]) * height, (stop[2]-start[2]) * width, height ) + elseif start[1] < stop[1] then -- Ends below start + for i=start[1],stop[1] do + if i == start[1] then + surface_DrawRect( xofs + (start[2]-self.Scroll[2]) * width, (i-self.Scroll[1]) * height, (#self.Rows[start[1]]-start[2]) * width, height ) + elseif i == stop[1] then + surface_DrawRect( xofs + (self.Scroll[2]-1) * width, (i-self.Scroll[1]) * height, (#self.Rows[stop[1]]-stop[2]) * width, height ) + else + surface_DrawRect( xofs + (self.Scroll[2]-1) * width, (i-self.Scroll[1]) * height, #self.Rows[i] * width, height ) + end + end + end + end + end + + -- Bracket matching + local startPos, endPos + + startPos = self:CopyPosition(self.Caret) + startPos[2] = startPos[2] - 1 + + if isMatchable(self, startPos) then + endPos = getMatchingCharacter(self, startPos) + end + + -- If we fail to get a match on the left side of the cursor, check the right side + if not endPos then + startPos[2] = startPos[2] + 1 + + if isMatchable(self, startPos) then + endPos = getMatchingCharacter(self, startPos) + end + end + + if startPos and endPos then + surface_SetDrawColor(255, 0, 0, 50) + + local xofs = self.LineNumberWidth + 6 + surface_DrawRect((startPos[2] - self.Scroll[2]) * width + xofs, (startPos[1] - self.Scroll[1]) * height, width, height) + surface_DrawRect((endPos[2] - self.Scroll[2]) * width + xofs, (endPos[1] - self.Scroll[1]) * height, width, height) + end + end + end +end + +local wire_expression2_editor_display_caret_pos = CreateClientConVar("wire_expression2_editor_display_caret_pos","0",true,false) + +function EDITOR:Paint() + self.LineNumberWidth = self.FontWidth * #tostring(self.Scroll[1]+self.Size[1]+1) + + if not input.IsMouseDown(MOUSE_LEFT) then + self:OnMouseReleased(MOUSE_LEFT) + end + + if not self.PaintRows then + self.PaintRows = {} + end + + if self.MouseDown then + self.Caret = self:CursorToCaret() + end + + surface_SetDrawColor(0, 0, 0, 255) + surface_DrawRect(0, 0, self.LineNumberWidth + 4, self:GetTall()) + + surface_SetDrawColor(32, 32, 32, 255) + surface_DrawRect(self.LineNumberWidth + 5, 0, self:GetWide() - (self.LineNumberWidth + 5), self:GetTall()) + + self.Scroll[1] = math_floor(self.ScrollBar:GetScroll() + 1) + + for i=self.Scroll[1],self.Scroll[1]+self.Size[1]+1 do + self:PaintLine(i) + end + + -- Paint the overlay of the text (bracket highlighting and carret postition) + self:PaintTextOverlay() + + if wire_expression2_editor_display_caret_pos:GetBool() then + local str = "Length: " .. #self:GetValue() .. " Lines: " .. #self.Rows .. " Ln: " .. self.Caret[1] .. " Col: " .. self.Caret[2] + if self:HasSelection() then + str = str .. " Sel: " .. #self:GetSelection() + end + surface_SetFont( "Default" ) + local w,h = surface_GetTextSize( str ) + local _w, _h = self:GetSize() + draw_WordBox( 4, _w - w - (self.ScrollBar:IsVisible() and 16 or 0) - 10, _h - h - 10, str, "Default", Color( 0,0,0,100 ), Color( 255,255,255,255 ) ) + end + + self:DoAction("Paint") + + return true +end + +-- Moves the caret to a new position. Optionally also collapses the selection +-- into a single caret. If maintain_selection is nil, then the selection will +-- be maintained only if Shift is pressed. +function EDITOR:SetCaret(caret, maintain_selection) + self.Caret = self:CopyPosition(caret) + + self.Caret[1] = math.Clamp(self.Caret[1], 1, #self.Rows) + self.Caret[2] = math.Clamp(self.Caret[2], 1, #self.Rows[self.Caret[1]] + 1) + + if maintain_selection == nil then + maintain_selection = input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) + end + + if not maintain_selection then + self.Start = self:CopyPosition(self.Caret) + end + + self:ScrollCaret() +end + + +function EDITOR:CopyPosition(caret) + return { caret[1], caret[2] } +end + +function EDITOR:MovePosition(caret, offset) + local row, col = caret[1], caret[2] + + if offset > 0 then + local numRows = #self.Rows + while true do + local length = #(self.Rows[row]) - col + 2 + if offset < length then + col = col + offset + break + elseif row == numRows then + col = col + length - 1 + break + else + offset = offset - length + row = row + 1 + col = 1 + end + end + elseif offset < 0 then + offset = -offset + + while true do + if offset < col then + col = col - offset + break + elseif row == 1 then + col = 1 + break + else + offset = offset - col + row = row - 1 + col = #(self.Rows[row]) + 1 + end + end + end + + return {row, col} +end + + +function EDITOR:HasSelection() + return self.Caret[1] ~= self.Start[1] or self.Caret[2] ~= self.Start[2] +end + +function EDITOR:Selection() + return { { self.Caret[1], self.Caret[2] }, { self.Start[1], self.Start[2] } } +end + +function EDITOR:MakeSelection(selection) + local start, stop = selection[1], selection[2] + + if start[1] < stop[1] or (start[1] == stop[1] and start[2] < stop[2]) then + return start, stop + else + return stop, start + end +end + + +function EDITOR:GetArea(selection) + local start, stop = self:MakeSelection(selection) + + if start[1] == stop[1] then + return string_sub(self.Rows[start[1]], start[2], stop[2] - 1) + else + local text = string_sub(self.Rows[start[1]], start[2]) + + for i=start[1]+1,stop[1]-1 do + text = text .. "\n" .. self.Rows[i] + end + + return text .. "\n" .. string_sub(self.Rows[stop[1]], 1, stop[2] - 1) + end +end + +function EDITOR:SetArea(selection, text, isundo, isredo, before, after) + local start, stop = self:MakeSelection(selection) + + local buffer = self:GetArea(selection) + + if start[1] ~= stop[1] or start[2] ~= stop[2] then + -- clear selection + self.Rows[start[1]] = string_sub(self.Rows[start[1]], 1, start[2] - 1) .. string_sub(self.Rows[stop[1]], stop[2]) + self.PaintRows[start[1]] = false + + for _=start[1]+1,stop[1] do + table_remove(self.Rows, start[1] + 1) + table_remove(self.PaintRows, start[1] + 1) + self.PaintRows = {} -- TODO: fix for cache errors + end + + -- add empty row at end of file (TODO!) + if self.Rows[#self.Rows] ~= "" then + self.Rows[#self.Rows + 1] = "" + self.PaintRows[#self.Rows + 1] = false + end + end + + if not text or text == "" then + self.ScrollBar:SetUp(self.Size[1], #self.Rows - 1) + + self.PaintRows = {} + + self:OnTextChanged() + + if isredo then + self.Undo[#self.Undo + 1] = { { self:CopyPosition(start), self:CopyPosition(start) }, buffer, after, before } + return before + elseif isundo then + self.Redo[#self.Redo + 1] = { { self:CopyPosition(start), self:CopyPosition(start) }, buffer, after, before } + return before + else + self.Redo = {} + self.Undo[#self.Undo + 1] = { { self:CopyPosition(start), self:CopyPosition(start) }, buffer, self:CopyPosition(selection[1]), self:CopyPosition(start) } + return start + end + end + + -- insert text + local rows = string_Explode("\n", text) + + local remainder = string_sub(self.Rows[start[1]], start[2]) + self.Rows[start[1]] = string_sub(self.Rows[start[1]], 1, start[2] - 1) .. rows[1] + self.PaintRows[start[1]] = false + + for i=2,#rows do + table_insert(self.Rows, start[1] + i - 1, rows[i]) + table_insert(self.PaintRows, start[1] + i - 1, false) + self.PaintRows = {} -- TODO: fix for cache errors + end + + stop = { start[1] + #rows - 1, #(self.Rows[start[1] + #rows - 1]) + 1 } + + self.Rows[stop[1]] = self.Rows[stop[1]] .. remainder + self.PaintRows[stop[1]] = false + + -- add empty row at end of file (TODO!) + if self.Rows[#self.Rows] ~= "" then + self.Rows[#self.Rows + 1] = "" + self.PaintRows[#self.Rows + 1] = false + self.PaintRows = {} -- TODO: fix for cache errors + end + + self.ScrollBar:SetUp(self.Size[1], #self.Rows - 1) + + self.PaintRows = {} + + self:OnTextChanged() + + if isredo then + self.Undo[#self.Undo + 1] = { { self:CopyPosition(start), self:CopyPosition(stop) }, buffer, after, before } + return before + elseif isundo then + self.Redo[#self.Redo + 1] = { { self:CopyPosition(start), self:CopyPosition(stop) }, buffer, after, before } + return before + else + self.Redo = {} + self.Undo[#self.Undo + 1] = { { self:CopyPosition(start), self:CopyPosition(stop) }, buffer, self:CopyPosition(selection[1]), self:CopyPosition(stop) } + return stop + end +end + + +function EDITOR:GetSelection() + return self:GetArea(self:Selection()) +end + +function EDITOR:SetSelection(text) + self:SetCaret(self:SetArea(self:Selection(), text), false) +end + +function EDITOR:OnTextChanged() +end + +function EDITOR:_OnLoseFocus() + if self.TabFocus then + self:RequestFocus() + self.TabFocus = nil + end +end + +-- removes the first 0-4 spaces from a string and returns it +local function unindent(line) + --local i = line:find("%S") + --if i == nil or i > 5 then i = 5 end + --return line:sub(i) + return line:match("^ ? ? ? ?(.*)$") +end + +function EDITOR:_OnTextChanged() + local ctrlv = false + local text = self.TextEntry:GetText() + self.TextEntry:SetText("") + + if (input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL)) and not (input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT)) then + -- ctrl+[shift+]key + if input.IsKeyDown(KEY_V) then + -- ctrl+[shift+]V + ctrlv = true + else + -- ctrl+[shift+]key with key ~= V + return + end + end + + if text == "" then return end + if not ctrlv then + if text == "\n" or text == "`" then return end + if text == "}" and GetConVarNumber('wire_expression2_autoindent') ~= 0 then + self:SetSelection(text) + local row = self.Rows[self.Caret[1]] + if string_match("{" .. row, "^%b{}.*$") then + local newrow = unindent(row) + self.Rows[self.Caret[1]] = newrow + self.Caret[2] = self.Caret[2] + newrow:len()-row:len() + self.Start[2] = self.Caret[2] + end + return + end + end + + self:SetSelection(text) + self:AC_Check() +end + +function EDITOR:OnMouseWheeled(delta) + if self.AC_Panel and self.AC_Panel:IsVisible() then + local mode = wire_expression2_autocomplete_controlstyle:GetInt() + if mode == AC_STYLE_SCROLLER or mode == AC_STYLE_SCROLLER_ENTER then + self.AC_Panel.Selected = self.AC_Panel.Selected - delta + if self.AC_Panel.Selected > #self.AC_Suggestions then self.AC_Panel.Selected = 1 end + if self.AC_Panel.Selected < 1 then self.AC_Panel.Selected = #self.AC_Suggestions end + self:AC_FillInfoList( self.AC_Suggestions[self.AC_Panel.Selected] ) + self.AC_Panel:RequestFocus() + return + else + self:AC_SetVisible(false) + end + end + + self.Scroll[1] = self.Scroll[1] - 4 * delta + if self.Scroll[1] < 1 then self.Scroll[1] = 1 end + if self.Scroll[1] > #self.Rows then self.Scroll[1] = #self.Rows end + self.ScrollBar:SetScroll(self.Scroll[1] - 1) +end + +function EDITOR:OnShortcut() +end + +function EDITOR:ScrollCaret() + if self.Caret[1] - self.Scroll[1] < 2 then + self.Scroll[1] = self.Caret[1] - 2 + if self.Scroll[1] < 1 then self.Scroll[1] = 1 end + end + + if self.Caret[1] - self.Scroll[1] > self.Size[1] - 2 then + self.Scroll[1] = self.Caret[1] - self.Size[1] + 2 + if self.Scroll[1] < 1 then self.Scroll[1] = 1 end + end + + if self.Caret[2] - self.Scroll[2] < 4 then + self.Scroll[2] = self.Caret[2] - 4 + if self.Scroll[2] < 1 then self.Scroll[2] = 1 end + end + + if self.Caret[2] - 1 - self.Scroll[2] > self.Size[2] - 4 then + self.Scroll[2] = self.Caret[2] - 1 - self.Size[2] + 4 + if self.Scroll[2] < 1 then self.Scroll[2] = 1 end + end + + self.ScrollBar:SetScroll(self.Scroll[1] - 1) +end + +-- Initialize find settings +local wire_expression2_editor_find_use_patterns = CreateClientConVar( "wire_expression2_editor_find_use_patterns", "0", true, false ) +local wire_expression2_editor_find_ignore_case = CreateClientConVar( "wire_expression2_editor_find_ignore_case", "0", true, false ) +local wire_expression2_editor_find_whole_word_only = CreateClientConVar( "wire_expression2_editor_find_whole_word_only", "0", true, false ) +local wire_expression2_editor_find_wrap_around = CreateClientConVar( "wire_expression2_editor_find_wrap_around", "0", true, false ) +local wire_expression2_editor_find_dir = CreateClientConVar( "wire_expression2_editor_find_dir", "1", true, false ) + +function EDITOR:HighlightFoundWord( caretstart, start, stop ) + caretstart = caretstart or self:CopyPosition( self.Start ) + if istable( start ) then + self.Start = self:CopyPosition( start ) + elseif isnumber( start ) then + self.Start = self:MovePosition( caretstart, start ) + end + if istable( stop ) then + self.Caret = { stop[1], stop[2] + 1 } + elseif isnumber( stop ) then + self.Caret = self:MovePosition( caretstart, stop+1 ) + end + self:ScrollCaret() +end + +function EDITOR:Find( str, looped ) + if looped and looped >= 2 then return end + if str == "" then return end + local _str = str + + local use_patterns = wire_expression2_editor_find_use_patterns:GetBool() + local ignore_case = wire_expression2_editor_find_ignore_case:GetBool() + local whole_word_only = wire_expression2_editor_find_whole_word_only:GetBool() + local wrap_around = wire_expression2_editor_find_wrap_around:GetBool() + local dir = wire_expression2_editor_find_dir:GetBool() + + -- Check if the match exists anywhere at all + local temptext = self:GetValue() + if ignore_case then + temptext = temptext:lower() + str = str:lower() + end + local _start,_stop = temptext:find( str, 1, not use_patterns ) + if not _start or not _stop then return false end + + if dir then -- Down + local line = self.Rows[self.Start[1]] + local text = line:sub(self.Start[2]) .. "\n" + text = text .. table_concat( self.Rows, "\n", self.Start[1]+1 ) + if ignore_case then text = text:lower() end + + if not use_patterns then + str = string.PatternSafe(str) + end + + if whole_word_only then + str = "%f[%w_]" .. str .. "%f[^%w_]" + end + + local start, stop = text:find(str, 2) + if start and stop then + self:HighlightFoundWord(nil, start - 1, stop - 1) + return true + end + + if wrap_around then + self:SetCaret({1, 1}, false) + return self:Find(_str, (looped or 0) + 1) + end + + return false + else -- Up + local text = table_concat( self.Rows, "\n", 1, self.Start[1]-1 ) + local line = self.Rows[self.Start[1]] + text = text .. "\n" .. line:sub( 1, self.Start[2]-1 ) + + str = string_reverse( str ) + text = string_reverse( text ) + + if ignore_case then text = text:lower() end + + if not use_patterns then + str = string.PatternSafe(str) + end + + if whole_word_only then + str = "%f[%w_]" .. str .. "%f[^%w_]" + end + + local start, stop = text:find(str, 2) + if start and stop then + self:HighlightFoundWord( nil, -(start-1), -(stop+1) ) + return true + end + + if wrap_around then + self:SetCaret( { #self.Rows,#self.Rows[#self.Rows] }, false ) + return self:Find( _str, (looped or 0) + 1 ) + end + + return false + end +end + +function EDITOR:Replace( str, replacewith ) + if str == "" or str == replacewith then return end + + local use_patterns = wire_expression2_editor_find_use_patterns:GetBool() + + local selection = self:GetSelection() + + local _str = str + if not use_patterns then + str = string.PatternSafe(str) + replacewith = replacewith:gsub( "%%", "%%%1" ) + end + + if selection:match( str ) ~= nil then + self:SetSelection( selection:gsub( str, replacewith ) ) + return self:Find( _str ) + else + return self:Find( _str ) + end +end + +function EDITOR:ReplaceAll( str, replacewith ) + if str == "" then return end + + local whole_word_only = wire_expression2_editor_find_whole_word_only:GetBool() + local ignore_case = wire_expression2_editor_find_ignore_case:GetBool() + local use_patterns = wire_expression2_editor_find_use_patterns:GetBool() + + if not use_patterns then + str = string.PatternSafe(str) + replacewith = replacewith:gsub( "%%", "%%%1" ) + end + + if ignore_case then + str = str:lower() + end + + local pattern + if whole_word_only then + pattern = "%f[%w_]()" .. str .. "%f[^%w_]()" + else + pattern = "()" .. str .. "()" + end + + local txt = self:GetValue() + + if ignore_case then + local txt2 = txt -- Store original cased copy + txt = txt:lower() -- Lowercase everything + + local positions = {} + + for startpos, endpos in string_gmatch( txt, pattern ) do + positions[#positions+1] = {startpos,endpos} + end + + -- Do the replacing backwards, or it won't work + for i=#positions,1,-1 do + local startpos, endpos = positions[i][1], positions[i][2] + txt2 = string_sub(txt2,1,startpos-1) .. replacewith .. string_sub(txt2,endpos) + end + + -- Replace everything with the edited copy + self:SelectAll() + self:SetSelection( txt2 ) + else + txt = string_gsub( txt, pattern, replacewith ) + + self:SelectAll() + self:SetSelection( txt ) + end +end + +function EDITOR:CountFinds( str ) + if str == "" then return 0 end + + local whole_word_only = wire_expression2_editor_find_whole_word_only:GetBool() + local ignore_case = wire_expression2_editor_find_ignore_case:GetBool() + local use_patterns = wire_expression2_editor_find_use_patterns:GetBool() + + if not use_patterns then + str = string.PatternSafe(str) + end + + local txt = self:GetValue() + + if ignore_case then + txt = txt:lower() + str = str:lower() + end + + if whole_word_only then + str = "%f[%w_]()" .. str .. "%f[^%w_]()" + end + + return select(2, txt:gsub(str, "")) +end + +function EDITOR:FindAllWords( str ) + if str == "" then return end + + local txt = self:GetValue() + -- %f[set] is a 'frontier' pattern - it matches an empty string at a position such that the + -- next character belongs to set and the previous character does not belong to set. + -- The beginning and the end of the string are handled as if they were the character '\0'. + -- As a special case, the empty capture () captures the current string position (a number). + -- - https://www.lua.org/manual/5.3/manual.html#6.4.1 + local pattern = "%f[%w_]()" .. string.PatternSafe(str) .. "%f[^%w_]()" + + local ret = {} + for start,stop in txt:gmatch( pattern ) do + ret[#ret+1] = { start, stop } + end + + return ret +end + +function EDITOR:CreateFindWindow() + self.FindWindow = vgui.Create( "DFrame", self ) + + local pnl = self.FindWindow + pnl:SetSize( 322, 201 ) + pnl:ShowCloseButton( true ) + pnl:SetDeleteOnClose( false ) -- No need to create a new window every time + pnl:MakePopup() -- Make it separate from the editor itself + pnl:SetVisible( false ) -- but hide it for now + pnl:SetTitle( "Find" ) + pnl:SetScreenLock( true ) + do + local old = pnl.Close + function pnl.Close() + self.ForceDrawCursor = false + old( pnl ) + end + end + + -- Center it above the editor + local x,y = self:GetParent():GetPos() + local w,h = self:GetSize() + pnl:SetPos( x+w/2-150, y+h/2-100 ) + + pnl.TabHolder = vgui.Create( "DPropertySheet", pnl ) + pnl.TabHolder:StretchToParent( 1, 23, 1, 1 ) + + -- Options + local common_panel = vgui.Create( "DPanel", pnl ) + common_panel:SetSize( 225, 60 ) + common_panel:SetPos( 10, 130 ) + common_panel.Paint = function() + local w,h = common_panel:GetSize() + draw_RoundedBox( 4, 0, 0, w, h, Color(0,0,0,150) ) + end + + local use_patterns = vgui.Create( "DCheckBoxLabel", common_panel ) + use_patterns:SetText( "Use Patterns" ) + use_patterns:SetToolTip( "Use/Don't use Lua patterns in the find." ) + use_patterns:SizeToContents() + use_patterns:SetConVar( "wire_expression2_editor_find_use_patterns" ) + use_patterns:SetPos( 4, 4 ) + do + local old = use_patterns.Button.SetValue + use_patterns.Button.SetValue = function( pnl, b ) + if wire_expression2_editor_find_whole_word_only:GetBool() then return end + old( pnl, b ) + end + end + + local case_sens = vgui.Create( "DCheckBoxLabel", common_panel ) + case_sens:SetText( "Ignore Case" ) + case_sens:SetToolTip( "Ignore/Don't ignore case in the find." ) + case_sens:SizeToContents() + case_sens:SetConVar( "wire_expression2_editor_find_ignore_case" ) + case_sens:SetPos( 4, 24 ) + + local whole_word = vgui.Create( "DCheckBoxLabel", common_panel ) + whole_word:SetText( "Match Whole Word" ) + whole_word:SetToolTip( "Match/Don't match the entire word in the find." ) + whole_word:SizeToContents() + whole_word:SetConVar( "wire_expression2_editor_find_whole_word_only" ) + whole_word:SetPos( 4, 44 ) + do + local old = whole_word.Button.Toggle + whole_word.Button.Toggle = function( pnl ) + old( pnl ) + if pnl:GetValue() then use_patterns:SetValue( false ) end + end + end + + local wrap_around = vgui.Create( "DCheckBoxLabel", common_panel ) + wrap_around:SetText( "Wrap Around" ) + wrap_around:SetToolTip( "Start/Don't start from the top after reaching the bottom, or the bottom after reaching the top." ) + wrap_around:SizeToContents() + wrap_around:SetConVar( "wire_expression2_editor_find_wrap_around" ) + wrap_around:SetPos( 130, 4 ) + + local dir_down = vgui.Create( "DCheckBoxLabel", common_panel ) + local dir_up = vgui.Create( "DCheckBoxLabel", common_panel ) + + dir_up:SetText( "Up" ) + dir_up:SizeToContents() + dir_up:SetPos( 130, 24 ) + dir_up:SetTooltip( "Note: Most patterns won't work when searching up because the search function reverses the string to search backwards." ) + dir_up:SetValue( not wire_expression2_editor_find_dir:GetBool() ) + dir_down:SetText( "Down" ) + dir_down:SizeToContents() + dir_down:SetPos( 130, 44 ) + dir_down:SetValue( wire_expression2_editor_find_dir:GetBool() ) + + function dir_up.Button:Toggle() + dir_up:SetValue(true) + dir_down:SetValue(false) + RunConsoleCommand( "wire_expression2_editor_find_dir", "0" ) + end + function dir_down.Button:Toggle() + dir_down:SetValue(true) + dir_up:SetValue(false) + RunConsoleCommand( "wire_expression2_editor_find_dir", "1" ) + end + + do + -- Find tab + local findtab = vgui.Create( "DPanel" ) + + -- Label + local FindLabel = vgui.Create( "DLabel", findtab ) + FindLabel:SetText( "Find:" ) + FindLabel:SetPos( 4, 4 ) + FindLabel:SetTextColor( Color(0,0,0,255) ) + + -- Text entry + local FindEntry = vgui.Create( "DTextEntry", findtab ) + FindEntry:SetPos(30,4) + FindEntry:SetSize(200,20) + FindEntry:RequestFocus() + FindEntry.OnEnter = function( pnl ) + self:Find( pnl:GetValue() ) + pnl:RequestFocus() + end + + -- Find next button + local FindNext = vgui.Create( "DButton", findtab ) + FindNext:SetText("Find Next") + FindNext:SetToolTip( "Find the next match and highlight it." ) + FindNext:SetPos(233,4) + FindNext:SetSize(70,20) + FindNext.DoClick = function(pnl) + self:Find( FindEntry:GetValue() ) + end + + -- Find button + local Find = vgui.Create( "DButton", findtab ) + Find:SetText("Find") + Find:SetToolTip( "Find the next match, highlight it, and close the Find window." ) + Find:SetPos(233,29) + Find:SetSize(70,20) + Find.DoClick = function(pnl) + self.FindWindow:Close() + self:Find( FindEntry:GetValue() ) + end + + -- Count button + local Count = vgui.Create( "DButton", findtab ) + Count:SetText( "Count" ) + Count:SetPos( 233, 95 ) + Count:SetSize( 70, 20 ) + Count:SetTooltip( "Count the number of matches in the file." ) + Count.DoClick = function(pnl) + Derma_Message( self:CountFinds( FindEntry:GetValue() ) .. " matches found.", "", "Ok" ) + end + + -- Cancel button + local Cancel = vgui.Create( "DButton", findtab ) + Cancel:SetText("Cancel") + Cancel:SetPos(233,120) + Cancel:SetSize(70,20) + Cancel.DoClick = function(pnl) + self.FindWindow:Close() + end + + pnl.FindTab = pnl.TabHolder:AddSheet( "Find", findtab, "icon16/page_white_find.png", false, false ) + pnl.FindTab.Entry = FindEntry + end + + do + -- Replace tab + local replacetab = vgui.Create( "DPanel" ) + + -- Label + local FindLabel = vgui.Create( "DLabel", replacetab ) + FindLabel:SetText( "Find:" ) + FindLabel:SetPos( 4, 4 ) + FindLabel:SetTextColor( Color(0,0,0,255) ) + + -- Text entry + local FindEntry = vgui.Create( "DTextEntry", replacetab ) + local ReplaceEntry + FindEntry:SetPos(30,4) + FindEntry:SetSize(200,20) + FindEntry:RequestFocus() + FindEntry.OnEnter = function( pnl ) + self:Replace( pnl:GetValue(), ReplaceEntry:GetValue() ) + ReplaceEntry:RequestFocus() + end + + -- Label + local ReplaceLabel = vgui.Create( "DLabel", replacetab ) + ReplaceLabel:SetText( "Replace With:" ) + ReplaceLabel:SetPos( 4, 32 ) + ReplaceLabel:SizeToContents() + ReplaceLabel:SetTextColor( Color(0,0,0,255) ) + + -- Replace entry + ReplaceEntry = vgui.Create( "DTextEntry", replacetab ) + ReplaceEntry:SetPos(75,29) + ReplaceEntry:SetSize(155,20) + ReplaceEntry:RequestFocus() + ReplaceEntry.OnEnter = function( pnl ) + self:Replace( FindEntry:GetValue(), pnl:GetValue() ) + pnl:RequestFocus() + end + + -- Find next button + local FindNext = vgui.Create( "DButton", replacetab ) + FindNext:SetText("Find Next") + FindNext:SetToolTip( "Find the next match and highlight it." ) + FindNext:SetPos(233,4) + FindNext:SetSize(70,20) + FindNext.DoClick = function(pnl) + self:Find( FindEntry:GetValue() ) + end + + -- Replace next button + local ReplaceNext = vgui.Create( "DButton", replacetab ) + ReplaceNext:SetText("Replace") + ReplaceNext:SetToolTip( "Replace the current selection if it matches, else find the next match." ) + ReplaceNext:SetPos(233,29) + ReplaceNext:SetSize(70,20) + ReplaceNext.DoClick = function(pnl) + self:Replace( FindEntry:GetValue(), ReplaceEntry:GetValue() ) + end + + -- Replace all button + local ReplaceAll = vgui.Create( "DButton", replacetab ) + ReplaceAll:SetText("Replace All") + ReplaceAll:SetToolTip( "Replace all occurences of the match in the entire file, and close the Find window." ) + ReplaceAll:SetPos(233,54) + ReplaceAll:SetSize(70,20) + ReplaceAll.DoClick = function(pnl) + self.FindWindow:Close() + self:ReplaceAll( FindEntry:GetValue(), ReplaceEntry:GetValue() ) + end + + -- Count button + local Count = vgui.Create( "DButton", replacetab ) + Count:SetText( "Count" ) + Count:SetPos( 233, 95 ) + Count:SetSize( 70, 20 ) + Count:SetTooltip( "Count the number of matches in the file." ) + Count.DoClick = function(pnl) + Derma_Message( self:CountFinds( FindEntry:GetValue() ) .. " matches found.", "", "Ok" ) + end + + -- Cancel button + local Cancel = vgui.Create( "DButton", replacetab ) + Cancel:SetText("Cancel") + Cancel:SetPos(233,120) + Cancel:SetSize(70,20) + Cancel.DoClick = function(pnl) + self.FindWindow:Close() + end + + pnl.ReplaceTab = pnl.TabHolder:AddSheet( "Replace", replacetab, "icon16/page_white_wrench.png", false, false ) + pnl.ReplaceTab.Entry = FindEntry + end + + -- Go to line tab + local gototab = vgui.Create( "DPanel" ) + + -- Label + local GotoLabel = vgui.Create( "DLabel", gototab ) + GotoLabel:SetText( "Go to Line:" ) + GotoLabel:SetPos( 4, 4 ) + GotoLabel:SetTextColor( Color(0,0,0,255) ) + + -- Text entry + local GoToEntry = vgui.Create( "DTextEntry", gototab ) + GoToEntry:SetPos(57,4) + GoToEntry:SetSize(173,20) + GoToEntry:SetNumeric( true ) + + -- Goto Button + local Goto = vgui.Create( "DButton", gototab ) + Goto:SetText("Go to Line") + Goto:SetPos(233,4) + Goto:SetSize(70,20) + + -- Action + local function GoToAction(panel) + local val = tonumber(GoToEntry:GetValue()) + if val then + val = math_Clamp(val, 1, #self.Rows) + self:SetCaret({val, #self.Rows[val] + 1}, false) + end + GoToEntry:SetText(tostring(val)) + self.FindWindow:Close() + end + GoToEntry.OnEnter = GoToAction + Goto.DoClick = GoToAction + + pnl.GoToLineTab = pnl.TabHolder:AddSheet( "Go to Line", gototab, "icon16/page_white_go.png", false, false ) + pnl.GoToLineTab.Entry = GoToEntry + + -- Tab buttons + do + local old = pnl.FindTab.Tab.OnMousePressed + pnl.FindTab.Tab.OnMousePressed = function( ... ) + pnl.FindTab.Entry:SetText( pnl.ReplaceTab.Entry:GetValue() or "" ) + local active = pnl.TabHolder:GetActiveTab() + if active == pnl.GoToLineTab.Tab then + pnl:SetHeight( 200 ) + pnl.TabHolder:StretchToParent( 1, 23, 1, 1 ) + end + old( ... ) + end + end + + do + local old = pnl.ReplaceTab.Tab.OnMousePressed + pnl.ReplaceTab.Tab.OnMousePressed = function( ... ) + pnl.ReplaceTab.Entry:SetText( pnl.FindTab.Entry:GetValue() or "" ) + local active = pnl.TabHolder:GetActiveTab() + if active == pnl.GoToLineTab.Tab then + pnl:SetHeight( 200 ) + pnl.TabHolder:StretchToParent( 1, 23, 1, 1 ) + end + old( ... ) + end + end + + do + local old = pnl.GoToLineTab.Tab.OnMousePressed + pnl.GoToLineTab.Tab.OnMousePressed = function( ... ) + pnl:SetHeight( 86 ) + pnl.TabHolder:StretchToParent( 1, 23, 1, 1 ) + pnl.GoToLineTab.Entry:SetText(self.Caret[1]) + old( ... ) + end + end +end + +function EDITOR:OpenFindWindow( mode ) + if not self.FindWindow then self:CreateFindWindow() end + self.FindWindow:SetVisible( true ) + self.FindWindow:MakePopup() -- This will move it above the E2 editor if it is behind it. + self.ForceDrawCursor = true + + local selection = self:GetSelection():Left(100) + + if mode == "find" then + if selection and selection ~= "" then self.FindWindow.FindTab.Entry:SetText( selection ) end + self.FindWindow.TabHolder:SetActiveTab( self.FindWindow.FindTab.Tab ) + self.FindWindow.FindTab.Entry:RequestFocus() + self.FindWindow:SetHeight( 201 ) + self.FindWindow.TabHolder:StretchToParent( 1, 23, 1, 1 ) + elseif mode == "find and replace" then + if selection and selection ~= "" then self.FindWindow.ReplaceTab.Entry:SetText( selection ) end + self.FindWindow.TabHolder:SetActiveTab( self.FindWindow.ReplaceTab.Tab ) + self.FindWindow.ReplaceTab.Entry:RequestFocus() + self.FindWindow:SetHeight( 201 ) + self.FindWindow.TabHolder:StretchToParent( 1, 23, 1, 1 ) + elseif mode == "go to line" then + self.FindWindow.TabHolder:SetActiveTab( self.FindWindow.GoToLineTab.Tab ) + local caretPos = self.Caret[1] + self.FindWindow.GoToLineTab.Entry:SetText(caretPos) + self.FindWindow.GoToLineTab.Entry:RequestFocus() + self.FindWindow.GoToLineTab.Entry:SelectAllText() + self.FindWindow.GoToLineTab.Entry:SetCaretPos(tostring(caretPos):len()) + self.FindWindow:SetHeight( 83 ) + self.FindWindow.TabHolder:StretchToParent( 1, 23, 1, 1 ) + end +end + +function EDITOR:CanUndo() + return #self.Undo > 0 +end + +function EDITOR:DoUndo() + if #self.Undo > 0 then + local undo = self.Undo[#self.Undo] + self.Undo[#self.Undo] = nil + + self:SetCaret(self:SetArea(undo[1], undo[2], true, false, undo[3], undo[4]), false) + end +end + +function EDITOR:CanRedo() + return #self.Redo > 0 +end + +function EDITOR:DoRedo() + if #self.Redo > 0 then + local redo = self.Redo[#self.Redo] + self.Redo[#self.Redo] = nil + + self:SetCaret(self:SetArea(redo[1], redo[2], false, true, redo[3], redo[4]), false) + end +end + +function EDITOR:SelectAll() + self.Caret = {#self.Rows, #(self.Rows[#self.Rows]) + 1} + self.Start = {1, 1} + self:ScrollCaret() +end + +function EDITOR:Indent(shift) + -- TAB with a selection -- + -- remember scroll position + local tab_scroll = self:CopyPosition(self.Scroll) + + -- normalize selection, so it spans whole lines + local tab_start, tab_caret = self:MakeSelection(self:Selection()) + tab_start[2] = 1 + + if tab_caret[2] ~= 1 then + tab_caret[1] = tab_caret[1] + 1 + tab_caret[2] = 1 + end + + -- remember selection + self.Caret = self:CopyPosition(tab_caret) + self.Start = self:CopyPosition(tab_start) + -- (temporarily) adjust selection, so there is no empty line at its end. + if self.Caret[2] == 1 then + self.Caret = self:MovePosition(self.Caret, -1) + end + if shift then + -- shift-TAB with a selection -- + local tmp = self:GetSelection():gsub("\n ? ? ? ?", "\n") + + -- makes sure that the first line is outdented + self:SetSelection(unindent(tmp)) + else + -- plain TAB with a selection -- + self:SetSelection(" " .. self:GetSelection():gsub("\n", "\n ")) + end + -- restore selection + self.Caret = self:CopyPosition(tab_caret) + self.Start = self:CopyPosition(tab_start) + -- restore scroll position + self.Scroll = self:CopyPosition(tab_scroll) + -- trigger scroll bar update (TODO: find a better way) + self:ScrollCaret() +end + +-- Comment the currently selected area +function EDITOR:BlockCommentSelection( removecomment ) + if not self:HasSelection() then return end + + local scroll = self:CopyPosition( self.Scroll ) + + local new_selection = self:DoAction("BlockCommentSelection", removecomment) + if not new_selection then return end + + self.Start, self.Caret = self:MakeSelection(new_selection) + -- restore scroll position + self.Scroll = scroll + -- trigger scroll bar update (TODO: find a better way) + self:ScrollCaret() +end + +-- CommentSelection +-- Idea by Jeremydeath +-- Rewritten by Divran to use block comment +function EDITOR:CommentSelection( removecomment ) + if not self:HasSelection() then return end + + -- Remember scroll position + local scroll = self:CopyPosition( self.Scroll ) + + -- Normalize selection, so it spans whole lines + local sel_start, sel_caret = self:MakeSelection( self:Selection() ) + sel_start[2] = 1 + + if sel_caret[2] ~= 1 then + sel_caret[1] = sel_caret[1] + 1 + sel_caret[2] = 1 + end + + -- Remember selection + self.Caret = self:CopyPosition( sel_caret ) + self.Start = self:CopyPosition( sel_start ) + -- (temporarily) adjust selection, so there is no empty line at its end. + if self.Caret[2] == 1 then + self.Caret = self:MovePosition(self.Caret, -1) + end + local new_selection = self:DoAction("CommentSelection", removecomment) + if not new_selection then return end + + self.Start, self.Caret = self:MakeSelection(new_selection) + + -- restore scroll position + self.Scroll = scroll + -- trigger scroll bar update (TODO: find a better way) + self:ScrollCaret() +end + +function EDITOR:ContextHelp() + local word + if self:HasSelection() then + word = self:GetSelection() + else + local row, col = unpack(self.Caret) + local line = self.Rows[row] + if not line:sub(col, col):match("^[a-zA-Z0-9_]$") then + col = col - 1 + end + if not line:sub(col, col):match("^[a-zA-Z0-9_]$") then + surface_PlaySound("buttons/button19.wav") + return + end + + -- TODO substitute this for getWordStart, if it fits. + local startcol = col + while startcol > 1 and line:sub(startcol-1, startcol-1):match("^[a-zA-Z0-9_]$") do + startcol = startcol - 1 + end + + -- TODO substitute this for getWordEnd, if it fits. + local _,endcol = line:find("[^a-zA-Z0-9_]", col) + endcol = (endcol or 0) - 1 + + word = line:sub(startcol, endcol) + end + + self:DoAction("ShowContextHelp", word) +end + +function EDITOR:Copy() + if not self:HasSelection() then return end + self.CurrentMode.clipboard = string_gsub(self:GetSelection(), "\n", "\r\n") + return SetClipboardText(self.CurrentMode.clipboard) +end + +function EDITOR:Cut() + self:Copy() + return self:SetSelection("") +end + +-- TODO these two functions have no place in here +function EDITOR:PreviousTab() + local parent = self:GetParent() + + local currentTab = parent:GetActiveTabIndex() - 1 + if currentTab < 1 then currentTab = currentTab + parent:GetNumTabs() end + + parent:SetActiveTabIndex(currentTab) +end + +function EDITOR:NextTab() + local parent = self:GetParent() + + local currentTab = parent:GetActiveTabIndex() + 1 + local numTabs = parent:GetNumTabs() + if currentTab > numTabs then currentTab = currentTab - numTabs end + + parent:SetActiveTabIndex(currentTab) +end + +function EDITOR:DuplicateLine() + -- Save current selection + local old_start = self:CopyPosition( self.Start ) + local old_end = self:CopyPosition( self.Caret ) + local old_scroll = self:CopyPosition( self.Scroll ) + + local str = self:GetSelection() + if str ~= "" then -- If you have a selection + self:SetSelection( str:rep(2) ) -- Repeat it + else -- If you don't + -- Select the current line + self.Start = { self.Start[1], 1 } + self.Caret = { self.Start[1], #self.Rows[self.Start[1]]+1 } + -- Get the text + local str = self:GetSelection() + -- Repeat it + self:SetSelection( str .. "\n" .. str ) + end + + -- Restore selection + self.Caret = old_end + self.Start = old_start + self.Scroll = old_scroll + self:ScrollCaret() +end + +function EDITOR:_OnKeyCodeTyped(code) + local handled = true + self.Blink = RealTime() + + local alt = input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) + if alt then return end + + local shift = input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT) + local control = input.IsKeyDown(KEY_LCONTROL) or input.IsKeyDown(KEY_RCONTROL) + + -- allow ctrl-ins and shift-del (shift-ins, like ctrl-v, is handled by vgui) + if not shift and control and code == KEY_INSERT then + shift,control,code = true,false,KEY_C + elseif shift and not control and code == KEY_DELETE then + shift,control,code = false,true,KEY_X + end + + if control then + if code == KEY_A then + self:SelectAll() + elseif code == KEY_Z then + self:DoUndo() + elseif code == KEY_Y then + self:DoRedo() + elseif code == KEY_X then + self:Cut() + elseif code == KEY_C then + self:Copy() + -- pasting is now handled by the textbox that is used to capture input + --[[ + elseif code == KEY_V then + if self.CurrentMode.clipboard then + self:SetSelection(self.CurrentMode.clipboard) + end + ]] + elseif code == KEY_F then + self:OpenFindWindow( "find" ) + elseif code == KEY_H then + self:OpenFindWindow( "find and replace" ) + elseif code == KEY_G then + self:OpenFindWindow( "go to line" ) + elseif code == KEY_K then + self:CommentSelection(shift) + elseif code == KEY_Q then + self:GetParent():Close() + elseif code == KEY_T then + self:GetParent():NewTab() + elseif code == KEY_W then + self:GetParent():CloseTab() + elseif code == KEY_PAGEUP then + self:PreviousTab() + elseif code == KEY_PAGEDOWN then + self:NextTab() + elseif code == KEY_UP then + self.Scroll[1] = self.Scroll[1] - 1 + if self.Scroll[1] < 1 then self.Scroll[1] = 1 end + elseif code == KEY_DOWN then + self.Scroll[1] = self.Scroll[1] + 1 + elseif code == KEY_LEFT then + self:SetCaret(self:wordLeft(self.Caret)) + elseif code == KEY_RIGHT then + self:SetCaret(self:wordRight(self.Caret)) + --[[ -- old code that scrolls on ctrl-left/right: + elseif code == KEY_LEFT then + self.Scroll[2] = self.Scroll[2] - 1 + if self.Scroll[2] < 1 then self.Scroll[2] = 1 end + elseif code == KEY_RIGHT then + self.Scroll[2] = self.Scroll[2] + 1 + ]] + elseif code == KEY_HOME then + self:SetCaret({ 1, 1 }) + elseif code == KEY_END then + self:SetCaret({ #self.Rows, 1 }) + elseif code == KEY_D then + self:DuplicateLine() + else + handled = false + end + + else + + if code == KEY_ENTER then + local mode = wire_expression2_autocomplete_controlstyle:GetInt() + if mode == AC_STYLE_ECLIPSE and self.AC_HasSuggestions and self.AC_Suggestions[1] and self.AC_Panel and self.AC_Panel:IsVisible() then + if self:AC_Use( self.AC_Suggestions[1] ) then return end + end + local row = self.Rows[self.Caret[1]]:sub(1,self.Caret[2]-1) + local diff = (row:find("%S") or (row:len()+1))-1 + local tabs = string_rep(" ", math_floor(diff / 4)) + if GetConVarNumber('wire_expression2_autoindent') ~= 0 and (string_match("{" .. row .. "}", "^%b{}.*$") == nil) then tabs = tabs .. " " end + self:SetSelection("\n" .. tabs) + elseif code == KEY_UP then + if self.AC_Panel and self.AC_Panel:IsVisible() then + local mode = wire_expression2_autocomplete_controlstyle:GetInt() + if mode == AC_STYLE_VISUALCSHARP or mode == AC_STYLE_ATOM then + self.AC_Panel:RequestFocus() + return + end + end + + self.Caret[1] = self.Caret[1] - 1 + self:SetCaret(self.Caret) + elseif code == KEY_DOWN then + if self.AC_Panel and self.AC_Panel:IsVisible() then + local mode = wire_expression2_autocomplete_controlstyle:GetInt() + if mode == AC_STYLE_VISUALCSHARP or mode == AC_STYLE_ATOM then + self.AC_Panel:RequestFocus() + return + end + end + + self.Caret[1] = self.Caret[1] + 1 + self:SetCaret(self.Caret) + elseif code == KEY_LEFT then + if self:HasSelection() and not shift then + self:SetCaret(self.Caret, false) + else + self:SetCaret(self:MovePosition(self.Caret, -1)) + end + elseif code == KEY_RIGHT then + if self:HasSelection() and not shift then + self:SetCaret(self.Caret, false) + else + self:SetCaret(self:MovePosition(self.Caret, 1)) + end + elseif code == KEY_PAGEUP then + self.Caret[1] = self.Caret[1] - math_ceil(self.Size[1] / 2) + self.Scroll[1] = self.Scroll[1] - math_ceil(self.Size[1] / 2) + self:SetCaret(self.Caret) + elseif code == KEY_PAGEDOWN then + self.Caret[1] = self.Caret[1] + math_ceil(self.Size[1] / 2) + self.Scroll[1] = self.Scroll[1] + math_ceil(self.Size[1] / 2) + self:SetCaret(self.Caret) + elseif code == KEY_HOME then + local row = self.Rows[self.Caret[1]] + local first_char = row:find("%S") or row:len()+1 + if self.Caret[2] == first_char then + self.Caret[2] = 1 + else + self.Caret[2] = first_char + end + self:SetCaret(self.Caret) + elseif code == KEY_END then + local length = #(self.Rows[self.Caret[1]]) + self.Caret[2] = length + 1 + self:SetCaret(self.Caret) + elseif code == KEY_BACKSPACE then + if self:HasSelection() then + self:SetSelection() + else + local buffer = self:GetArea({self.Caret, {self.Caret[1], 1}}) + local delta = -1 + if self.Caret[2] % 4 == 1 and #(buffer) > 0 and string_rep(" ", #(buffer)) == buffer then + delta = -4 + end + self:SetCaret(self:SetArea({self.Caret, self:MovePosition(self.Caret, delta)})) + end + elseif code == KEY_DELETE then + if self:HasSelection() then + self:SetSelection() + else + local buffer = self:GetArea({{self.Caret[1], self.Caret[2] + 4}, {self.Caret[1], 1}}) + local delta = 1 + if self.Caret[2] % 4 == 1 and string_rep(" ", #(buffer)) == buffer and #(self.Rows[self.Caret[1]]) >= self.Caret[2] + 4 - 1 then + delta = 4 + end + self:SetCaret(self:SetArea({self.Caret, self:MovePosition(self.Caret, delta)})) + end + elseif code == KEY_F1 then + self:ContextHelp() + else + handled = false + end + end + + if code == KEY_TAB and self.AC_Panel and self.AC_Panel:IsVisible() then + local mode = wire_expression2_autocomplete_controlstyle:GetInt() + if mode == AC_STYLE_DEFAULT or mode == AC_STYLE_ECLIPSE or mode == AC_STYLE_ATOM then + self.AC_Panel:RequestFocus() + if (mode == AC_STYLE_ECLIPSE or mode == AC_STYLE_ATOM) and self.AC_Panel.Selected == 0 then self.AC_Panel.Selected = 1 end + return + end + handled = true + end + + if code == KEY_TAB or (control and (code == KEY_I or code == KEY_O)) then + if code == KEY_O then shift = not shift end + if code == KEY_TAB and control then shift = not shift end + if self:HasSelection() then + self:Indent(shift) + else + -- TAB without a selection -- + if shift then + local newpos = self.Caret[2]-4 + if newpos < 1 then newpos = 1 end + self.Start = { self.Caret[1], newpos } + if self:GetSelection():find("%S") then + -- TODO: what to do if shift-tab is pressed within text? + self.Start = self:CopyPosition(self.Caret) + else + self:SetSelection("") + end + else + local count = (self.Caret[2] + 2) % 4 + 1 + self:SetSelection(string_rep(" ", count)) + end + end + -- signal that we want our focus back after (since TAB normally switches focus) + if code == KEY_TAB then self.TabFocus = true end + handled = true + end + + if control and not handled then + handled = self:OnShortcut(code) + end + + self:AC_Check() + + return handled +end + +--------------------------------------------------------------------------------------------------------- +-- Auto Completion +-- By Divran +--------------------------------------------------------------------------------------------------------- + +function EDITOR:IsVarLine() + local line = self.Rows[self.Caret[1]] + local word = line:match( "^@(%w+)" ) + return (word == "inputs" or word == "outputs" or word == "persist") +end + +function EDITOR:IsDirectiveLine() + local line = self.Rows[self.Caret[1]] + return line:match( "^@" ) ~= nil +end + +function EDITOR:getWordStart(caret,getword) + local line = self.Rows[caret[1]] + + for startpos, endpos in line:gmatch( "()[a-zA-Z0-9_]+()" ) do -- "()%w+()" + if startpos <= caret[2] and endpos >= caret[2] then + return { caret[1], startpos }, getword and line:sub(startpos,endpos-1) or nil + end + end + return {caret[1],1} +end + +function EDITOR:getWordEnd(caret,getword) + local line = self.Rows[caret[1]] + + for startpos, endpos in line:gmatch( "()[a-zA-Z0-9_]+()" ) do -- "()%w+()" + if startpos <= caret[2] and endpos >= caret[2] then + return { caret[1], endpos }, getword and line:sub(startpos,endpos-1) or nil + end + end + return {caret[1],#line+1} +end + +----------------------------------------------------------- +-- GetCurrentWord +-- Gets the word the cursor is currently at, and the symbol in front +----------------------------------------------------------- + +function EDITOR:AC_GetCurrentWord() + local startpos, word = self:getWordStart( self.Caret, true ) + local symbolinfront = self:GetArea( { { startpos[1], startpos[2] - 1}, startpos } ) + return word, symbolinfront +end + +-- Thank you http://lua-users.org/lists/lua-l/2009-07/msg00461.html +-- Returns the minimum number of character changes required to make one of the words equal the other +-- Used to sort the suggestions in order of relevance +local function CheckDifference( word1, word2 ) + local d, sn, tn = {}, #word1, #word2 + local byte, min = string_byte, math_min + for i = 0, sn do d[i * tn] = i end + for j = 0, tn do d[j] = j end + for i = 1, sn do + local si = byte(word1, i) + for j = 1, tn do + d[i*tn+j] = min(d[(i-1)*tn+j]+1, d[i*tn+j-1]+1, d[(i-1)*tn+j-1]+(si == byte(word2,j) and 0 or 1)) + end + end + return d[#d] +end + +----------------------------------------------------------- +-- NewAutoCompletion +-- Sets the autocompletion table +----------------------------------------------------------- + +function EDITOR:AC_NewAutoCompletion( tbl ) + self.AC_AutoCompletion = tbl +end + +local tbl = {} + +----------------------------------------------------------- +-- FindConstants +-- Adds all matching constants to the suggestions table +----------------------------------------------------------- + +local function GetTableForConstant( str ) + return { nice_str = function( t ) return t.data[1] end, + str = function( t ) return t.data[1] end, + replacement = function( t ) return t.data[1] end, + data = { str } } +end + +local function FindConstants( self, word ) + local len = #word + local wordu = word:upper() + local count = 0 + + local suggestions = {} + + for name, _ in pairs( wire_expression2_constants ) do + if name:sub(1,len) == wordu then + count = count + 1 + suggestions[count] = GetTableForConstant( name ) + end + end + + return suggestions +end + +tbl[1] = function( self ) + local word = self:AC_GetCurrentWord() + if word and word ~= "" and word:sub(1,1) == "_" then + return FindConstants( self, word ) + end +end + +-------------------- +-- FindFunctions +-- Adds all matching functions to the suggestions table +-------------------- + +local function GetTableForFunction() + return { nice_str = function( t ) return t.data[2] end, + str = function( t ) return t.data[1] end, + replacement = function( t, editor ) + local caret = editor:CopyPosition( editor.Caret ) + caret[2] = caret[2] - 1 + local wordend = editor:getWordEnd( caret ) + local has_bracket = editor:GetArea( { wordend, { wordend[1], wordend[2] + 1 } } ) == "(" -- If there already is a bracket, we don't want to add more of them. + local ret = t:str() + return ret..(has_bracket and "" or "()"), #ret+1 + end, + others = function( t ) return t.data[3] end, + description = function( t ) + if t.data[4] and E2Helper.Descriptions[t.data[4]] then + return E2Helper.Descriptions[t.data[4]] + end + if t.data[1] and E2Helper.Descriptions[t.data[1]] then + return E2Helper.Descriptions[t.data[1]] + end + end, + data = {} } +end + +local function FindFunctions( self, has_colon, word ) + -- Filter out magic characters + word = string.PatternSafe(word) + + local len = #word + local wordl = word:lower() + local count = 0 + local suggested = {} + local suggestions = {} + + for func_id,_ in pairs( wire_expression2_funcs ) do + if wordl == func_id:lower():sub(1,len) then -- Check if the beginning of the word matches + local name, types = func_id:match( "(.+)(%b())" ) -- Get the function name and types + local first_type, colon, other_types = types:match( "%((%w*)(:?)(.*)%)" ) -- Sort the function types + if (colon == ":") == has_colon then -- If they both have colons (or not) + first_type = first_type:upper() + other_types = other_types:upper() + if not suggested[name] then -- If it hasn't already been suggested + count = count + 1 + suggested[name] = count + + -- Add to suggestions + if colon == ":" then + local t = GetTableForFunction() + t.data = { name, first_type .. ":" .. name .. "(" .. other_types .. ")", {}, func_id } + suggestions[count] = t + else + local t = GetTableForFunction() + t.data = { name, name .. "(" .. first_type .. ")", {}, func_id } + suggestions[count] = t + end + else -- If it has already been suggested + -- Get previous data + local others = suggestions[suggested[name]]:others(self) + local i = #others+1 + + -- Add it to the end of the list + if colon == ":" then + local t = GetTableForFunction() + t.data = { name, first_type .. ":" .. name .. "(" .. other_types .. ")", nil, func_id } + others[i] = t + else + local t = GetTableForFunction() + t.data = { name, name .. "(" .. first_type .. ")", nil, func_id } + others[i] = t + end + end + end + end + end + return suggestions +end + +tbl[2] = function( self ) + local word, symbolinfront = self:AC_GetCurrentWord() + if word and word ~= "" and word:sub(1,1):upper() ~= word:sub(1,1) then + return FindFunctions( self, (symbolinfront == ":"), word ) + end +end + +----------------------------------------------------------- +-- SaveVariables +-- Saves all variables to a table +----------------------------------------------------------- + +function EDITOR:AC_SaveVariables() + local OK, directives,_ = E2Lib.PreProcessor.Execute( self:GetValue() ) + + if not OK or not directives then + return + end + + self.AC_Directives = directives +end + +----------------------------------------------------------- +-- FindVariables +-- Adds all matching variables to the suggestions table +----------------------------------------------------------- + +local function GetTableForVariables( str ) + return { nice_str = function( t ) return t.data[1] end, + str = function( t ) return t.data[1] end, + replacement = function( t ) return t.data[1] end, + data = { str } } +end + + +local function FindVariables( self, word ) + local len = #word + local wordl = word:lower() + local count = 0 + + local suggested = {} + local suggestions = {} + + local directives = self.AC_Directives + if not directives then self:AC_SaveVariables() end -- If directives is nil, attempt to find + directives = self.AC_Directives + if not directives then -- If finding failed, abort + self:AC_SetVisible( false ) + return + end + + for _, v in pairs( directives["inputs"][1] ) do + if v:lower():sub(1,len) == wordl then + if not suggested[v] then + suggested[v] = true + count = count + 1 + suggestions[count] = GetTableForVariables( v ) + end + end + end + + for _, v in pairs( directives["outputs"][1] ) do + if v:lower():sub(1,len) == wordl then + if not suggested[v] then + suggested[v] = true + count = count + 1 + suggestions[count] = GetTableForVariables( v ) + end + end + end + + for _, v in pairs( directives["persist"][1] ) do + if v:lower():sub(1,len) == wordl then + if not suggested[v] then + suggested[v] = true + count = count + 1 + suggestions[count] = GetTableForVariables( v ) + end + end + end + + return suggestions +end + +tbl[3] = function( self ) + local word = self:AC_GetCurrentWord() + if word and word ~= "" and word:sub(1,1):upper() == word:sub(1,1) then + return FindVariables( self, word ) + end +end + +local wire_expression2_autocomplete = CreateClientConVar( "wire_expression2_autocomplete", "1", true, false ) +tbl.RunOnCheck = function( self ) + -- Only autocomplete if it's the E2 editor, if it's enabled + if not self:GetParent().E2 or not wire_expression2_autocomplete:GetBool() then + self:AC_SetVisible( false ) + return false + end + + local caret = self:CopyPosition(self.Caret) + local tokenname = self:GetTokenAtPosition(caret) + + if tokenname and (tokenname == "string" or tokenname == "comment") then + caret[2] = caret[2] - 1 + tokenname = self:GetTokenAtPosition(caret) + + if tokenname and (tokenname == "string" or tokenname == "comment") then + self:AC_SetVisible(false) + return false + end + end + + if self:IsVarLine() and not self.AC_WasVarLine then -- If the user IS editing a var line, and they WEREN'T editing a var line before this.. + self.AC_WasVarLine = true + elseif not self:IsVarLine() and self.AC_WasVarLine then -- If the user ISN'T editing a var line, and they WERE editing a var line before this.. + self.AC_WasVarLine = nil + self:AC_SaveVariables() + end + if self:IsDirectiveLine() then -- In case you're wondering, DirectiveLine ~= VarLine (A directive line is any line starting with @, a var line is @inputs, @outputs, and @persists) + self:AC_SetVisible( false ) + return false + end + + return true +end + +----------------------------------------------------------- +-- Check +-- Runs the autocompletion +----------------------------------------------------------- + +function EDITOR:AC_Check( notimer ) + + if not notimer then + timer.Create("E2_AC_Check", 0, 1, function() + if self.AC_Check then self:AC_Check(true) end + end) + return + end + + if not self.AC_AutoCompletion then self:AC_NewAutoCompletion( tbl ) end -- Default to E2 autocompletion + if not self.AC_Panel then self:AC_CreatePanel() end + if self.AC_AutoCompletion.RunOnCheck then + local ret = self.AC_AutoCompletion.RunOnCheck( self ) + if ret == false then + return + end + end + + self.AC_Suggestions = {} + self.AC_HasSuggestions = false + + local suggestions = {} + for i=1,#self.AC_AutoCompletion do + local _suggestions = self.AC_AutoCompletion[i]( self ) + if _suggestions ~= nil and #_suggestions > 0 then + suggestions = _suggestions + break + end + end + + if #suggestions > 0 then + + local word, _ = self:AC_GetCurrentWord() + + table_sort( suggestions, function( a, b ) + local diff1 = CheckDifference( word, a.str( a ) ) + local diff2 = CheckDifference( word, b.str( b ) ) + return diff1 < diff2 + end) + + if word == suggestions[1].str( suggestions[1] ) and #suggestions == 1 then -- The word matches the first suggestion exactly, and there are no more suggestions. No need to bother displaying + self:AC_SetVisible( false ) + return + end + + for i=1,10 do + self.AC_Suggestions[i] = suggestions[i] + end + self.AC_HasSuggestions = true + + -- Show the panel + local panel = self.AC_Panel + self:AC_SetVisible( true ) + + -- Calculate its position + local caret = self:CopyPosition( self.Caret ) + caret[2] = caret[2] - 1 + local wordstart = self:getWordStart( caret ) + + local x = self.FontWidth * (wordstart[2] - self.Scroll[2] + 1) + 22 + local y = self.FontHeight * (wordstart[1] - self.Scroll[1] + 1) + 2 + + panel:SetPos( x, y ) + + -- Fill the list + self:AC_FillList() + return + end + + self:AC_SetVisible( false ) +end + +----------------------------------------------------------- +-- Use +-- Replaces the word +----------------------------------------------------------- +local wire_expression2_autocomplete_highlight_after_use = CreateClientConVar("wire_expression2_autocomplete_highlight_after_use","1",true,false) +function EDITOR:AC_Use( suggestion ) + if not suggestion then return false end + local ret = false + + -- Get word position + local wordstart = self:getWordStart( self.Caret ) + local wordend = self:getWordEnd( self.Caret ) + + -- Get replacement + local replacement, caretoffset = suggestion:replacement( self ) + + -- Check if anything needs changing + local selection = self:GetArea( { wordstart, wordend } ) + if selection == replacement then -- There's no point in doing anything. + return false + end + + -- Overwrite selection + if replacement and replacement ~= "" then + self:SetArea( { wordstart, wordend }, replacement ) + + -- Move caret + if caretoffset then + self.Start = { wordstart[1], wordstart[2] + caretoffset } + self.Caret = { wordstart[1], wordstart[2] + caretoffset } + else + if wire_expression2_autocomplete_highlight_after_use:GetBool() then + self.Start = wordstart + self.Caret = {wordstart[1],wordstart[2]+#replacement} + else + self.Start = { wordstart[1],wordstart[2]+#replacement } + self.Caret = { wordstart[1],wordstart[2]+#replacement } + end + end + ret = true + end + + self:ScrollCaret() + + self:RequestFocus() + self.AC_HasSuggestion = false + return ret +end + +----------------------------------------------------------- +-- CreatePanel +----------------------------------------------------------- + +function EDITOR:AC_CreatePanel() + -- Create the panel + local panel = vgui.Create( "DPanel",self ) + panel:SetSize( 100, 202 ) + panel.Selected = {} + panel.Paint = function( pnl ) + surface_SetDrawColor( 0,0,0,230 ) + surface_DrawRect( 0,0,pnl:GetWide(), pnl:GetTall() ) + end + + -- Override think, to make it listen for key presses + panel.Think = function( pnl, code ) + if not self.AC_HasSuggestions or not self.AC_Panel_Visible then return end + + local mode = wire_expression2_autocomplete_controlstyle:GetInt() + if mode == AC_STYLE_DEFAULT then + + if input.IsKeyDown( KEY_ENTER ) or input.IsKeyDown( KEY_SPACE ) then -- Use + self:AC_SetVisible( false ) + self:AC_Use( self.AC_Suggestions[pnl.Selected] ) + elseif input.IsKeyDown( KEY_TAB ) and not pnl.AlreadySelected then -- Select + if input.IsKeyDown( KEY_LCONTROL ) then -- If control is held down + pnl.Selected = pnl.Selected - 1 -- Scroll up + if pnl.Selected < 1 then pnl.Selected = #self.AC_Suggestions end + else -- If control isn't held down + pnl.Selected = pnl.Selected + 1 -- Scroll down + if pnl.Selected > #self.AC_Suggestions then pnl.Selected = 1 end + end + self:AC_FillInfoList( self.AC_Suggestions[pnl.Selected] ) -- Fill the info list + pnl:RequestFocus() + pnl.AlreadySelected = true -- To keep it from scrolling a thousand times a second + elseif pnl.AlreadySelected and not input.IsKeyDown( KEY_TAB ) then + pnl.AlreadySelected = nil + elseif input.IsKeyDown( KEY_UP ) or input.IsKeyDown( KEY_DOWN ) or input.IsKeyDown( KEY_LEFT ) or input.IsKeyDown( KEY_RIGHT ) then + self:AC_SetVisible( false ) + end + elseif mode == AC_STYLE_VISUALCSHARP or mode == AC_STYLE_ATOM then + + if input.IsKeyDown( KEY_TAB ) or input.IsKeyDown( KEY_ENTER ) or input.IsKeyDown( KEY_SPACE ) then -- Use + self:AC_SetVisible( false ) + self:AC_Use( self.AC_Suggestions[pnl.Selected] ) + elseif input.IsKeyDown( KEY_DOWN ) and not pnl.AlreadySelected then -- Select + pnl.Selected = pnl.Selected + 1 -- Scroll down + if pnl.Selected > #self.AC_Suggestions then pnl.Selected = 1 end + self:AC_FillInfoList( self.AC_Suggestions[pnl.Selected] ) -- Fill the info list + pnl.AlreadySelected = true -- To keep it from scrolling a thousand times a second + elseif input.IsKeyDown( KEY_UP ) and not pnl.AlreadySelected then -- Select + pnl.Selected = pnl.Selected - 1 -- Scroll up + if pnl.Selected < 1 then pnl.Selected = #self.AC_Suggestions end + self:AC_FillInfoList( self.AC_Suggestions[pnl.Selected] ) -- Fill the info list + pnl.AlreadySelected = true -- To keep it from scrolling a thousand times a second + elseif pnl.AlreadySelected and not input.IsKeyDown( KEY_UP ) and not input.IsKeyDown( KEY_DOWN ) then + pnl.AlreadySelected = nil + end + + elseif mode == AC_STYLE_SCROLLER then + + if input.IsMouseDown( MOUSE_MIDDLE ) then + self:AC_SetVisible( false ) + self:AC_Use( self.AC_Suggestions[pnl.Selected] ) + end + + elseif mode == AC_STYLE_SCROLLER_ENTER then + + if input.IsKeyDown( KEY_ENTER ) then + self:AC_SetVisible( false ) + self:AC_Use( self.AC_Suggestions[pnl.Selected] ) + end + + elseif mode == AC_STYLE_ECLIPSE then + + if input.IsKeyDown( KEY_ENTER ) then -- Use + self:AC_SetVisible( false ) + self:AC_Use( self.AC_Suggestions[pnl.Selected] ) + elseif input.IsKeyDown( KEY_SPACE ) then + self:AC_SetVisible( false ) + elseif input.IsKeyDown( KEY_DOWN ) and not pnl.AlreadySelected then -- Select + pnl.Selected = pnl.Selected + 1 -- Scroll down + if pnl.Selected > #self.AC_Suggestions then pnl.Selected = 1 end + self:AC_FillInfoList( self.AC_Suggestions[pnl.Selected] ) -- Fill the info list + pnl.AlreadySelected = true -- To keep it from scrolling a thousand times a second + elseif input.IsKeyDown( KEY_UP ) and not pnl.AlreadySelected then -- Select + pnl.Selected = pnl.Selected - 1 -- Scroll up + if pnl.Selected < 1 then pnl.Selected = #self.AC_Suggestions end + self:AC_FillInfoList( self.AC_Suggestions[pnl.Selected] ) -- Fill the info list + pnl.AlreadySelected = true -- To keep it from scrolling a thousand times a second + elseif pnl.AlreadySelected and not input.IsKeyDown( KEY_UP ) and not input.IsKeyDown( KEY_DOWN ) then + pnl.AlreadySelected = nil + end + + end + end + + -- Create list + local list = vgui.Create( "DPanelList", panel ) + list:StretchToParent( 1,1,1,1 ) + list.Paint = function() end + + -- Create info list + local infolist = vgui.Create( "DPanelList", panel ) + infolist:SetPos( 1000, 1000 ) + infolist:SetSize( 100, 200 ) + infolist:EnableVerticalScrollbar( true ) + infolist.Paint = function() end + + self.AC_Panel = panel + panel.list = list + panel.infolist = infolist + self:AC_SetVisible( false ) +end + + +----------------------------------------------------------- +-- FillInfoList +-- Fills the "additional information" box +----------------------------------------------------------- + +local wire_expression2_autocomplete_moreinfo = CreateClientConVar( "wire_expression2_autocomplete_moreinfo", "1", true, false ) + +local function SimpleWrap( txt, width ) + local ret = "" + + local prev_end, prev_newline = 0, 0 + for cur_end in txt:gmatch( "[^ \n]+()" ) do + local w, _ = surface_GetTextSize( txt:sub( prev_newline, cur_end ) ) + if w > width then + ret = ret .. txt:sub( prev_newline, prev_end ) .. "\n" + prev_newline = prev_end + 1 + end + prev_end = cur_end + end + ret = ret .. txt:sub( prev_newline ) + + return ret +end + +function EDITOR:AC_FillInfoList( suggestion ) + local panel = self.AC_Panel + + if not suggestion or not suggestion.description or not wire_expression2_autocomplete_moreinfo:GetBool() then -- If the suggestion is invalid, the suggestion does not need additional information, or if the user has disabled additional information, abort + panel:SetSize( panel.curw, panel.curh ) + panel.infolist:SetPos( 1000, 1000 ) + return + end + + local infolist = panel.infolist + infolist:Clear() + + local desc_label = vgui.Create("DLabel") + infolist:AddItem( desc_label ) + + local desc = suggestion:description( self ) + + local maxw = 164 + local maxh = 0 + + local others + if suggestion.others then others = suggestion:others( self ) end + + if desc and desc ~= "" then + desc = "Description:\n" .. desc + end + + if #others > 0 then -- If there are other functions with the same name... + desc = (desc or "") .. ((desc and desc ~= "") and "\n" or "") .. "Others with the same name:" + + -- Loop through the "others" table to add all of them + surface_SetFont(self.CurrentFont) + for _, v in pairs( others ) do + local nice_name = v:nice_str( self ) + + local namew, nameh = surface_GetTextSize( nice_name ) + + local label = vgui.Create("DLabel") + label:SetText( "" ) + label.Paint = function( pnl ) + local w,h = pnl:GetSize() + surface_SetDrawColor(65, 105, 255) + surface_DrawRect(0, 0, w, h) + surface_SetFont(self.CurrentFont) + surface_SetTextPos( 6, h/2-nameh/2 ) + surface_SetTextColor( 255,255,255,255 ) + surface_DrawText( nice_name ) + end + + infolist:AddItem( label ) + + if namew + 15 > maxw then maxw = namew + 15 end + maxh = maxh + 20 + end + end + + if not desc or desc == "" then + panel:SetSize( panel.curw, panel.curh ) + infolist:SetPos( 1000, 1000 ) + return + end + + -- Wrap the text, set it, and calculate size + desc = SimpleWrap( desc, maxw ) + desc_label:SetText( desc ) + desc_label:SizeToContents() + local _, texth = surface_GetTextSize( desc ) + + -- If it's bigger than the size of the panel, change it + if panel.curh < texth + 4 then panel:SetTall( texth + 6 ) else panel:SetTall( panel.curh ) end + if maxh + texth > panel:GetTall() then maxw = maxw + 25 end + + -- Set other positions/sizes/etc + panel:SetWide( panel.curw + maxw ) + infolist:SetPos( panel.curw, 1 ) + infolist:SetSize( maxw - 1, panel:GetTall() - 2 ) +end + +----------------------------------------------------------- +-- FillList +----------------------------------------------------------- + +function EDITOR:AC_FillList() + local panel = self.AC_Panel + panel.list:Clear() + panel.Selected = 0 + local maxw = 15 + + surface.SetFont(self.CurrentFont) + + -- Add all suggestions to the list + for count,suggestion in pairs( self.AC_Suggestions ) do + local nice_name = suggestion:nice_str( self ) + + local txt = vgui.Create("DLabel") + txt:SetText( "" ) + txt.count = count + txt.suggestion = suggestion + + -- Override paint to give it the "E2 theme" and to make it highlight when selected + txt.Paint = function( pnl, w, h ) + local backgroundColor + if panel.Selected == pnl.count then + backgroundColor = Color(49, 80, 169, 192) + else + backgroundColor = Color(65, 105, 225, 255) + end + surface_SetDrawColor(backgroundColor) + surface_DrawRect(0, 0, w, h) + + surface.SetFont(self.CurrentFont) + local _, h2 = surface.GetTextSize( nice_name ) + + surface.SetTextPos( 6, (h / 2) - (h2 / 2) ) + surface.SetTextColor( 255,255,255,255 ) + surface.DrawText( nice_name ) + end + + -- Enable mouse presses + txt.OnMousePressed = function( pnl, code ) + if code == MOUSE_LEFT then + self:AC_SetVisible( false ) + self:AC_Use( pnl.suggestion ) + end + end + + -- Enable mouse hovering + txt.OnCursorEntered = function( pnl ) + panel.Selected = pnl.count + self:AC_FillInfoList( pnl.suggestion ) + end + + panel.list:AddItem( txt ) + + -- get the width of the widest suggestion + local w,_ = surface_GetTextSize( nice_name ) + w = w + 15 + if w > maxw then maxw = w end + end + + -- Size and positions etc + panel:SetSize( maxw, #self.AC_Suggestions * 20 + 2 ) + panel.curw = maxw + panel.curh = #self.AC_Suggestions * 20 + 2 + panel.list:StretchToParent( 1,1,1,1 ) + panel.infolist:SetPos( 1000, 1000 ) +end + +----------------------------------------------------------- +-- SetVisible +----------------------------------------------------------- + +function EDITOR:AC_SetVisible( bool ) + if self.AC_Panel_Visible == bool or not self.AC_Panel then return end + self.AC_Panel_Visible = bool + self.AC_Panel:SetVisible( bool ) + self.AC_Panel.infolist:SetPos( 1000, 1000 ) +end + +----------------------------------------------------------- +-- Reset +----------------------------------------------------------- + +function EDITOR:AC_Reset() + self.AC_HasSuggestions = false + self.AC_Suggestions = false + self.AC_Directives = nil + local panel = self.AC_Panel + if not panel then return end + self:AC_SetVisible( false ) + panel.list:Clear() + panel.infolist:Clear() + panel:SetSize( 100, 202 ) + panel.infolist:SetPos( 1000, 1000 ) + panel.infolist:SetSize( 100, 200 ) + panel.list:StretchToParent( 1,1,1,1 ) +end + +function EDITOR:Think() + self:DoAction("Think") +end + +--------------------------------------------------------------------------------------------------------- + +-- helpers for ctrl-left/right +function EDITOR:wordLeft(caret) + local row = self.Rows[caret[1]] + if caret[2] == 1 then + if caret[1] == 1 then return caret end + caret = { caret[1]-1, #self.Rows[caret[1]-1] } + row = self.Rows[caret[1]] + end + local pos = row:sub(1,caret[2]-1):match("[^%w@]()[%w@]+[^%w@]*$") + caret[2] = pos or 1 + return caret +end + +function EDITOR:wordRight(caret) + local row = self.Rows[caret[1]] + if caret[2] > #row then + if caret[1] == #self.Rows then return caret end + caret = { caret[1]+1, 1 } + row = self.Rows[caret[1]] + if row:sub(1,1) ~= " " then return caret end + end + local pos = row:match("[^%w@]()[%w@]",caret[2]) + caret[2] = pos or (#row+1) + return caret +end + +function EDITOR:GetTokenAtPosition( caret ) + local column = caret[2] + local line = self.PaintRows[caret[1]] + if line then + local startindex = 0 + for _, data in pairs( line ) do + startindex = startindex+#data[1] + if startindex >= column then return data[3] end + end + end +end + +-- Syntax highlighting -------------------------------------------------------- + +function EDITOR:ResetTokenizer(row) + self.line = self.Rows[row] + self.position = 0 + self.character = "" + self.tokendata = "" + + self:DoAction("ResetTokenizer", row) +end + +function EDITOR:NextCharacter() + if not self.character then return end + + self.tokendata = self.tokendata .. self.character + self.position = self.position + 1 + + if self.position <= self.line:len() then + self.character = self.line:sub(self.position, self.position) + else + self.character = nil + end +end + +function EDITOR:SkipPattern(pattern) + -- TODO: share code with NextPattern + if not self.character then return nil end + local startpos,endpos,text = self.line:find(pattern, self.position) + + if startpos ~= self.position then return nil end + local buf = self.line:sub(startpos, endpos) + if not text then text = buf end + + --self.tokendata = self.tokendata .. text + + + self.position = endpos + 1 + if self.position <= #self.line then + self.character = self.line:sub(self.position, self.position) + else + self.character = nil + end + return text +end + +function EDITOR:NextPattern(pattern) + if not self.character then return false end + local startpos,endpos,text = self.line:find(pattern, self.position) + + if startpos ~= self.position then return false end + local buf = self.line:sub(startpos, endpos) + if not text then text = buf end + + self.tokendata = self.tokendata .. text + + + self.position = endpos + 1 + if self.position <= #self.line then + self.character = self.line:sub(self.position, self.position) + else + self.character = nil + end + return true +end + +function EDITOR:GetSyntaxColor(name) + return self.Colors[name] or self:DoAction("GetSyntaxColor", name) +end + +function EDITOR:SetSyntaxColors(colors) + for name, color in pairs(colors) do + self:SetSyntaxColor(name, color) + end +end + +function EDITOR:SetSyntaxColor(name, color) + if self.Colors[name] then + self.Colors[name] = color + else + return self:DoAction("SetSyntaxColor", name, color) + end +end + +function EDITOR:SyntaxColorLine(row) + return self:DoAction("SyntaxColorLine", row) +end + +-- register editor panel +vgui.Register("Expression2Editor", EDITOR, "Panel"); + +concommand.Add("wire_expression2_reloadeditor", function(ply, command, args) + local code = wire_expression2_editor and wire_expression2_editor:GetCode() + wire_expression2_editor = nil + ZCPU_Editor = nil + ZGPU_Editor = nil + include("wire/client/text_editor/texteditor.lua") + include("wire/client/text_editor/wire_expression2_editor.lua") + initE2Editor() + if code then wire_expression2_editor:SetCode(code) end +end) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/text_editor/wire_expression2_editor.lua b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/wire_expression2_editor.lua new file mode 100644 index 0000000..48f6e2b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/text_editor/wire_expression2_editor.lua @@ -0,0 +1,1965 @@ +local Editor = {} + +-- ---------------------------------------------------------------------- +-- Fonts +-- ---------------------------------------------------------------------- + +local defaultFont + +if system.IsWindows() then + defaultFont = "Courier New" +elseif system.IsOSX() then + defaultFont = "Monaco" +else + defaultFont = "DejaVu Sans Mono" +end + +Editor.FontConVar = CreateClientConVar("wire_expression2_editor_font", defaultFont, true, false) +Editor.FontSizeConVar = CreateClientConVar("wire_expression2_editor_font_size", 16, true, false) +Editor.BlockCommentStyleConVar = CreateClientConVar("wire_expression2_editor_block_comment_style", 1, true, false) +Editor.NewTabOnOpen = CreateClientConVar("wire_expression2_new_tab_on_open", "1", true, false) +Editor.ops_sync_subscribe = CreateClientConVar("wire_expression_ops_sync_subscribe",0,true,false) + +Editor.Fonts = {} +-- Font Description + +-- Windows +Editor.Fonts["Courier New"] = "Windows standard font" +Editor.Fonts["DejaVu Sans Mono"] = "" +Editor.Fonts["Consolas"] = "" +Editor.Fonts["Fixedsys"] = "" +Editor.Fonts["Lucida Console"] = "" + +-- Mac +Editor.Fonts["Monaco"] = "Mac standard font" + +surface.CreateFont("DefaultBold", { + font = "Tahoma", + size = 12, + weight = 700, + antialias = true, + additive = false, +}) + +Editor.CreatedFonts = {} + +function Editor:SetEditorFont(editor) + if not self.CurrentFont then + self:ChangeFont(self.FontConVar:GetString(), self.FontSizeConVar:GetInt()) + return + end + + editor.CurrentFont = self.CurrentFont + editor.FontWidth = self.FontWidth + editor.FontHeight = self.FontHeight +end + +function Editor:ChangeFont(FontName, Size) + if not FontName or FontName == "" or not Size then return end + + -- If font is not already created, create it. + if not self.CreatedFonts[FontName .. "_" .. Size] then + local fontTable = + { + font = FontName, + size = Size, + weight = 400, + antialias = false, + additive = false, + } + surface.CreateFont("Expression2_" .. FontName .. "_" .. Size, fontTable) + fontTable.weight = 700 + surface.CreateFont("Expression2_" .. FontName .. "_" .. Size .. "_Bold", fontTable) + self.CreatedFonts[FontName .. "_" .. Size] = true + end + + self.CurrentFont = "Expression2_" .. FontName .. "_" .. Size + surface.SetFont(self.CurrentFont) + self.FontWidth, self.FontHeight = surface.GetTextSize(" ") + + for i = 1, self:GetNumTabs() do + self:SetEditorFont(self:GetEditor(i)) + end +end + +------------------------------------------------------------------------ +-- Colors +------------------------------------------------------------------------ + +local colors = { + -- Table copied from TextEditor, used for saving colors to convars. + ["directive"] = Color(240, 240, 160), -- yellow + ["number"] = Color(240, 160, 160), -- light red + ["function"] = Color(160, 160, 240), -- blue + ["notfound"] = Color(240, 96, 96), -- dark red + ["variable"] = Color(160, 240, 160), -- light green + ["string"] = Color(128, 128, 128), -- grey + ["keyword"] = Color(160, 240, 240), -- turquoise + ["operator"] = Color(224, 224, 224), -- white + ["comment"] = Color(128, 128, 128), -- grey + ["ppcommand"] = Color(240, 96, 240), -- purple + ["typename"] = Color(240, 160, 96), -- orange + ["constant"] = Color(240, 160, 240), -- pink + ["userfunction"] = Color(102, 122, 102), -- dark grayish-green + ["dblclickhighlight"] = Color(0, 100, 0) -- dark green +} + +local colors_defaults = {} + +local colors_convars = {} +for k, v in pairs(colors) do + colors_defaults[k] = Color(v.r, v.g, v.b) -- Copy to save defaults + colors_convars[k] = CreateClientConVar("wire_expression2_editor_color_" .. k, v.r .. "_" .. v.g .. "_" .. v.b, true, false) +end + +function Editor:LoadSyntaxColors() + for k, v in pairs(colors_convars) do + local r, g, b = v:GetString():match("(%d+)_(%d+)_(%d+)") + local def = colors_defaults[k] + colors[k] = Color(tonumber(r) or def.r, tonumber(g) or def.g, tonumber(b) or def.b) + end + + for i = 1, self:GetNumTabs() do + self:GetEditor(i):SetSyntaxColors(colors) + end +end + +function Editor:SetSyntaxColor(colorname, colr) + if not colors[colorname] then return end + colors[colorname] = colr + RunConsoleCommand("wire_expression2_editor_color_" .. colorname, colr.r .. "_" .. colr.g .. "_" .. colr.b) + + for i = 1, self:GetNumTabs() do + self:GetEditor(i):SetSyntaxColor(colorname, colr) + end +end + +------------------------------------------------------------------------ + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +-- overwritten commands +function Editor:Init() + -- don't use any of the default DFrame UI components + for _, v in pairs(self:GetChildren()) do v:Remove() end + self.Title = "" + self.subTitle = "" + self.LastClick = 0 + self.GuiClick = 0 + self.SimpleGUI = false + self.Location = "" + + self.C = {} + self.Components = {} + + -- Load border colors, position, & size + self:LoadEditorSettings() + + local fontTable = { + font = "default", + size = 11, + weight = 300, + antialias = false, + additive = false, + } + surface.CreateFont("E2SmallFont", fontTable) + self.logo = surface.GetTextureID("vgui/e2logo") + + self:InitComponents() + + -- This turns off the engine drawing + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + + self:SetV(false) + + self:InitShutdownHook() +end + +local size = CreateClientConVar("wire_expression2_editor_size", "800_600", true, false) +local pos = CreateClientConVar("wire_expression2_editor_pos", "-1_-1", true, false) + +function Editor:LoadEditorSettings() + + -- Position & Size + local w, h = size:GetString():match("(%d+)_(%d+)") + w = tonumber(w) + h = tonumber(h) + + self:SetSize(w, h) + + local x, y = pos:GetString():match("(%-?%d+)_(%-?%d+)") + x = tonumber(x) + y = tonumber(y) + + if x == -1 and y == -1 then + self:Center() + else + self:SetPos(x, y) + end + + if x < 0 or y < 0 or x + w > ScrW() or y + h > ScrH() then -- If the editor is outside the screen, reset it + local width, height = math.min(surface.ScreenWidth() - 200, 800), math.min(surface.ScreenHeight() - 200, 620) + self:SetPos((surface.ScreenWidth() - width) / 2, (surface.ScreenHeight() - height) / 2) + self:SetSize(width, height) + + self:SaveEditorSettings() + end +end + +function Editor:SaveEditorSettings() + + -- Position & Size + local w, h = self:GetSize() + RunConsoleCommand("wire_expression2_editor_size", w .. "_" .. h) + + local x, y = self:GetPos() + RunConsoleCommand("wire_expression2_editor_pos", x .. "_" .. y) +end + + +function Editor:PaintOver() + surface.SetFont("DefaultBold") + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(10, 6) + surface.DrawText(self.Title .. self.subTitle) + --[[ + if(self.E2) then + surface.SetTexture(self.logo) + surface.SetDrawColor( 255, 255, 255, 128 ) + surface.DrawTexturedRect( w-148, h-158, 128, 128) + end + ]] -- + surface.SetDrawColor(255, 255, 255, 255) + surface.SetTextPos(0, 0) + surface.SetFont("Default") + return true +end + +function Editor:PerformLayout() + local w, h = self:GetSize() + + for i = 1, #self.Components do + local c = self.Components[i] + local c_x, c_y, c_w, c_h = c.Bounds.x, c.Bounds.y, c.Bounds.w, c.Bounds.h + if (c_x < 0) then c_x = w + c_x end + if (c_y < 0) then c_y = h + c_y end + if (c_w < 0) then c_w = w + c_w - c_x end + if (c_h < 0) then c_h = h + c_h - c_y end + c:SetPos(c_x, c_y) + c:SetSize(c_w, c_h) + end +end + +function Editor:OnMousePressed(mousecode) + if mousecode ~= 107 then return end -- do nothing if mouseclick is other than left-click + if not self.pressed then + self.pressed = true + self.p_x, self.p_y = self:GetPos() + self.p_w, self.p_h = self:GetSize() + self.p_mx = gui.MouseX() + self.p_my = gui.MouseY() + self.p_mode = self:getMode() + if self.p_mode == "drag" then + if self.GuiClick > CurTime() - 0.2 then + self:fullscreen() + self.pressed = false + self.GuiClick = 0 + else + self.GuiClick = CurTime() + end + end + end +end + +function Editor:OnMouseReleased(mousecode) + if mousecode ~= 107 then return end -- do nothing if mouseclick is other than left-click + self.pressed = false +end + +function Editor:Think() + if self.fs then return end + if self.pressed then + if not input.IsMouseDown(MOUSE_LEFT) then -- needs this if you let go of the mouse outside the panel + self.pressed = false + end + local movedX = gui.MouseX() - self.p_mx + local movedY = gui.MouseY() - self.p_my + if self.p_mode == "drag" then + local x = self.p_x + movedX + local y = self.p_y + movedY + if (x < 10 and x > -10) then x = 0 end + if (y < 10 and y > -10) then y = 0 end + if (x + self.p_w < surface.ScreenWidth() + 10 and x + self.p_w > surface.ScreenWidth() - 10) then x = surface.ScreenWidth() - self.p_w end + if (y + self.p_h < surface.ScreenHeight() + 10 and y + self.p_h > surface.ScreenHeight() - 10) then y = surface.ScreenHeight() - self.p_h end + self:SetPos(x, y) + end + if self.p_mode == "sizeBR" then + local w = self.p_w + movedX + local h = self.p_h + movedY + if (self.p_x + w < surface.ScreenWidth() + 10 and self.p_x + w > surface.ScreenWidth() - 10) then w = surface.ScreenWidth() - self.p_x end + if (self.p_y + h < surface.ScreenHeight() + 10 and self.p_y + h > surface.ScreenHeight() - 10) then h = surface.ScreenHeight() - self.p_y end + if (w < 300) then w = 300 end + if (h < 200) then h = 200 end + self:SetSize(w, h) + end + if self.p_mode == "sizeR" then + local w = self.p_w + movedX + if (w < 300) then w = 300 end + self:SetWide(w) + end + if self.p_mode == "sizeB" then + local h = self.p_h + movedY + if (h < 200) then h = 200 end + self:SetTall(h) + end + end + if not self.pressed then + local cursor = "arrow" + local mode = self:getMode() + if (mode == "sizeBR") then cursor = "sizenwse" + elseif (mode == "sizeR") then cursor = "sizewe" + elseif (mode == "sizeB") then cursor = "sizens" + end + if cursor ~= self.cursor then + self.cursor = cursor + self:SetCursor(self.cursor) + end + end + + local x, y = self:GetPos() + local w, h = self:GetSize() + + if w < 518 then w = 518 end + if h < 200 then h = 200 end + if x < 0 then x = 0 end + if y < 0 then y = 0 end + if x + w > surface.ScreenWidth() then x = surface.ScreenWidth() - w end + if y + h > surface.ScreenHeight() then y = surface.ScreenHeight() - h end + if y < 0 then y = 0 end + if x < 0 then x = 0 end + if w > surface.ScreenWidth() then w = surface.ScreenWidth() end + if h > surface.ScreenHeight() then h = surface.ScreenHeight() end + + self:SetPos(x, y) + self:SetSize(w, h) +end + +-- special functions + +function Editor:fullscreen() + if self.fs then + self:SetPos(self.preX, self.preY) + self:SetSize(self.preW, self.preH) + self.fs = false + else + self.preX, self.preY = self:GetPos() + self.preW, self.preH = self:GetSize() + self:SetPos(0, 0) + self:SetSize(surface.ScreenWidth(), surface.ScreenHeight()) + self.fs = true + end +end + +function Editor:getMode() + local x, y = self:GetPos() + local w, h = self:GetSize() + local ix = gui.MouseX() - x + local iy = gui.MouseY() - y + + if (ix < 0 or ix > w or iy < 0 or iy > h) then return end -- if the mouse is outside the box + if (iy < 22) then + return "drag" + end + if (iy > h - 10) then + if (ix > w - 20) then return "sizeBR" end + return "sizeB" + end + if (ix > w - 10) then + if (iy > h - 20) then return "sizeBR" end + return "sizeR" + end +end + +function Editor:addComponent(panel, x, y, w, h) + assert(not panel.Bounds) + panel.Bounds = { x = x, y = y, w = w, h = h } + self.Components[#self.Components + 1] = panel + return panel +end + +-- TODO: Fix this function +local function extractNameFromCode(str) + return str:match("@name ([^\r\n]+)") +end + +local function getPreferredTitles(Line, code) + local title + local tabtext + + local str = Line + if str and str ~= "" then + title = str + tabtext = str + end + + str = extractNameFromCode(code) + if str and str ~= "" then + if not title then + title = str + end + tabtext = str + end + + return title, tabtext +end + +function Editor:GetLastTab() return self.LastTab end + +function Editor:SetLastTab(Tab) self.LastTab = Tab end + +function Editor:GetActiveTab() return self.C.TabHolder:GetActiveTab() end + +function Editor:GetNumTabs() return #self.C.TabHolder.Items end + +function Editor:SetActiveTab(val) + if self:GetActiveTab() == val then + val:GetPanel():RequestFocus() + return + end + self:SetLastTab(self:GetActiveTab()) + if isnumber(val) then + self.C.TabHolder:SetActiveTab(self.C.TabHolder.Items[val].Tab) + self:GetCurrentEditor():RequestFocus() + elseif val and val:IsValid() then + self.C.TabHolder:SetActiveTab(val) + val:GetPanel():RequestFocus() + end + if self.E2 then self:Validate() end + + self:UpdateActiveTabTitle() +end + +function Editor:UpdateActiveTabTitle() + local title, tabtext = getPreferredTitles(self:GetChosenFile(), self:GetCode()) + + if title then self:SubTitle("Editing: " .. title) else self:SubTitle() end + if tabtext then + if self:GetActiveTab():GetText() ~= tabtext then + self:GetActiveTab():SetText(tabtext) + self.C.TabHolder.tabScroller:InvalidateLayout() + end + end +end + +function Editor:GetActiveTabIndex() + local tab = self:GetActiveTab() + for k, v in pairs(self.C.TabHolder.Items) do + if tab == v.Tab then + return k + end + end + return -1 +end + + +function Editor:SetActiveTabIndex(index) + local tab = self.C.TabHolder.Items[index].Tab + + if not tab then return end + + self:SetActiveTab(tab) +end + +local function extractNameFromFilePath(str) + local found = str:reverse():find("/", 1, true) + if found then + return str:Right(found - 1) + else + return str + end +end + +function Editor:SetEditorMode(mode_name) + self.EditorMode = mode_name + for i = 1, self:GetNumTabs() do + self:GetEditor(i):SetMode(mode_name) + end +end + +function Editor:GetEditorMode() return self.EditorMode end + +local old +function Editor:FixTabFadeTime() + if old ~= nil then return end -- It's already being fixed + old = self.C.TabHolder:GetFadeTime() + self.C.TabHolder:SetFadeTime(0) + timer.Simple(old, function() self.C.TabHolder:SetFadeTime(old) old = nil end) +end + +function Editor:CreateTab(chosenfile) + local editor = vgui.Create("Expression2Editor") + editor.parentpanel = self + + local sheet = self.C.TabHolder:AddSheet(extractNameFromFilePath(chosenfile), editor) + self:SetEditorFont(editor) + editor.chosenfile = chosenfile + + sheet.Tab.OnMousePressed = function(pnl, keycode, ...) + + if keycode == MOUSE_MIDDLE then + --self:FixTabFadeTime() + self:CloseTab(pnl) + return + elseif keycode == MOUSE_RIGHT then + local menu = DermaMenu() + menu:AddOption("Close", function() + --self:FixTabFadeTime() + self:CloseTab(pnl) + end) + menu:AddOption("Close all others", function() + self:FixTabFadeTime() + self:SetActiveTab(pnl) + for i = self:GetNumTabs(), 1, -1 do + if self.C.TabHolder.Items[i] ~= sheet then + self:CloseTab(i) + end + end + end) + menu:AddSpacer() + menu:AddOption("Save", function() + self:FixTabFadeTime() + local old = self:GetLastTab() + local currentTab = self:GetActiveTab() + self:SetActiveTab(pnl) + self:SaveFile(self:GetChosenFile(), false) + self:SetActiveTab(currentTab) + self:SetLastTab(old) + end) + menu:AddOption("Save As", function() + self:FixTabFadeTime() + self:SetActiveTab(pnl) + self:SaveFile(self:GetChosenFile(), false, true) + end) + menu:AddOption("Reload", function() + self:FixTabFadeTime() + local old = self:GetLastTab() + self:SetActiveTab(pnl) + self:LoadFile(editor.chosenfile, false) + self:SetActiveTab(self:GetLastTab()) + self:SetLastTab(old) + end) + menu:AddSpacer() + menu:AddOption("Copy file path to clipboard", function() + if editor.chosenfile and editor.chosenfile ~= "" then + SetClipboardText(editor.chosenfile) + end + end) + menu:AddOption("Copy all file paths to clipboard", function() + local str = "" + for i = 1, self:GetNumTabs() do + local chosenfile = self:GetEditor(i).chosenfile + if chosenfile and chosenfile ~= "" then + str = str .. chosenfile .. ";" + end + end + str = str:sub(1, -2) + SetClipboardText(str) + end) + menu:Open() + return + end + + self:SetActiveTab(pnl) + end + + editor.OnTextChanged = function(panel) + timer.Create("e2autosave", 5, 1, function() + self:AutoSave() + end) + hook.Run("WireEditorText", self, editor) + end + editor.OnShortcut = function(_, code) + if code == KEY_S then + self:SaveFile(self:GetChosenFile()) + if self.E2 then self:Validate() end + else + local mode = GetConVar("wire_expression2_autocomplete_controlstyle"):GetInt() + local enabled = GetConVar("wire_expression2_autocomplete"):GetBool() + if mode == 1 and enabled then + if code == KEY_B then + self:Validate(true) + elseif code == KEY_SPACE then + local ed = self:GetCurrentEditor() + if (ed.AC_Panel and ed.AC_Panel:IsVisible()) then + ed:AC_Use(ed.AC_Suggestions[1]) + end + end + elseif code == KEY_SPACE then + self:Validate(true) + end + end + end + editor:RequestFocus() + + editor:SetMode(self:GetEditorMode()) + + self:OnTabCreated(sheet) -- Call a function that you can override to do custom stuff to each tab. + + return sheet +end + +function Editor:OnTabCreated(sheet) end + +-- This function is made to be overwritten + +function Editor:GetNextAvailableTab() + local activetab = self:GetActiveTab() + for _, v in pairs(self.C.TabHolder.Items) do + if v.Tab and v.Tab:IsValid() and v.Tab ~= activetab then + return v.Tab + end + end +end + +function Editor:NewTab() + local sheet = self:CreateTab("generic") + self:SetActiveTab(sheet.Tab) + if self.E2 then + self:NewScript(true) + end +end + +function Editor:CloseTab(_tab) + local activetab, sheetindex + if _tab then + if isnumber(_tab) then + local temp = self.C.TabHolder.Items[_tab] + if temp then + activetab = temp.Tab + sheetindex = _tab + else + return + end + else + activetab = _tab + -- Find the sheet index + for k, v in pairs(self.C.TabHolder.Items) do + if activetab == v.Tab then + sheetindex = k + break + end + end + end + else + activetab = self:GetActiveTab() + -- Find the sheet index + for k, v in pairs(self.C.TabHolder.Items) do + if activetab == v.Tab then + sheetindex = k + break + end + end + end + + self:AutoSave() + + -- There's only one tab open, no need to actually close any tabs + if self:GetNumTabs() == 1 then + activetab:SetText("generic") + self.C.TabHolder:InvalidateLayout() + self:NewScript(true) + return + end + + -- Find the panel (for the scroller) + local tabscroller_sheetindex + for k, v in pairs(self.C.TabHolder.tabScroller.Panels) do + if v == activetab then + tabscroller_sheetindex = k + break + end + end + + self:FixTabFadeTime() + + if activetab == self:GetActiveTab() then -- We're about to close the current tab + if self:GetLastTab() and self:GetLastTab():IsValid() then -- If the previous tab was saved + if activetab == self:GetLastTab() then -- If the previous tab is equal to the current tab + local othertab = self:GetNextAvailableTab() -- Find another tab + if othertab and othertab:IsValid() then -- If that other tab is valid, use it + self:SetActiveTab(othertab) + self:SetLastTab() + else -- Reset the current tab (backup) + self:GetActiveTab():SetText("generic") + self.C.TabHolder:InvalidateLayout() + self:NewScript(true) + return + end + else -- Change to the previous tab + self:SetActiveTab(self:GetLastTab()) + self:SetLastTab() + end + else -- If the previous tab wasn't saved + local othertab = self:GetNextAvailableTab() -- Find another tab + if othertab and othertab:IsValid() then -- If that other tab is valid, use it + self:SetActiveTab(othertab) + else -- Reset the current tab (backup) + self:GetActiveTab():SetText("generic") + self.C.TabHolder:InvalidateLayout() + self:NewScript(true) + return + end + end + end + + self:OnTabClosed(activetab) -- Call a function that you can override to do custom stuff to each tab. + + activetab:GetPanel():Remove() + activetab:Remove() + table.remove(self.C.TabHolder.Items, sheetindex) + table.remove(self.C.TabHolder.tabScroller.Panels, tabscroller_sheetindex) + + self.C.TabHolder.tabScroller:InvalidateLayout() + local w, h = self.C.TabHolder:GetSize() + self.C.TabHolder:SetSize(w + 1, h) -- +1 so it updates +end + +function Editor:OnTabClosed(sheet) end + +-- This function is made to be overwritten + +-- initialization commands +function Editor:InitComponents() + self.Components = {} + self.C = {} + + local function PaintFlatButton(panel, w, h) + if not (panel:IsHovered() or panel:IsDown()) then return end + derma.SkinHook("Paint", "Button", panel, w, h) + end + + local DMenuButton = vgui.RegisterTable({ + Init = function(panel) + panel:SetText("") + panel:SetSize(24, 20) + panel:Dock(LEFT) + end, + Paint = PaintFlatButton, + DoClick = function(panel) + local name = panel:GetName() + local f = name and name ~= "" and self[name] or nil + if f then f(self) end + end + }, "DButton") + + -- addComponent( panel, x, y, w, h ) + -- if x, y, w, h is minus, it will stay relative to right or buttom border + self.C.Close = self:addComponent(vgui.Create("DButton", self), -45-4, 0, 45, 22) -- Close button + self.C.Inf = self:addComponent(vgui.CreateFromTable(DMenuButton, self), -45-4-26, 0, 24, 22) -- Info button + self.C.ConBut = self:addComponent(vgui.CreateFromTable(DMenuButton, self), -45-4-24-26, 0, 24, 22) -- Control panel open/close + + self.C.Divider = vgui.Create("DHorizontalDivider", self) + + self.C.Browser = vgui.Create("wire_expression2_browser", self.C.Divider) -- Expression browser + + self.C.MainPane = vgui.Create("DPanel", self.C.Divider) + self.C.Menu = vgui.Create("DPanel", self.C.MainPane) + self.C.Val = vgui.Create("Button", self.C.MainPane) -- Validation line + self.C.TabHolder = vgui.Create("DPropertySheet", self.C.MainPane) + + self.C.Btoggle = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Toggle Browser being shown + self.C.Sav = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Save button + self.C.NewTab = vgui.CreateFromTable(DMenuButton, self.C.Menu, "NewTab") -- New tab button + self.C.CloseTab = vgui.CreateFromTable(DMenuButton, self.C.Menu, "CloseTab") -- Close tab button + self.C.Reload = vgui.CreateFromTable(DMenuButton, self.C.Menu) -- Reload tab button + self.C.SaE = vgui.Create("DButton", self.C.Menu) -- Save & Exit button + self.C.SavAs = vgui.Create("DButton", self.C.Menu) -- Save As button + + self.C.Control = self:addComponent(vgui.Create("Panel", self), -350, 52, 342, -32) -- Control Panel + self.C.Credit = self:addComponent(vgui.Create("DTextEntry", self), -160, 52, 150, 150) -- Credit box + self.C.Credit:SetEditable(false) + + self:CreateTab("generic") + + -- extra component options + + self.C.Divider:SetLeft(self.C.Browser) + self.C.Divider:SetRight(self.C.MainPane) + self.C.Divider:Dock(FILL) + self.C.Divider:SetDividerWidth(4) + self.C.Divider:SetCookieName("wire_expression2_editor_divider") + self.C.Divider:SetLeftMin(0) + + local DoNothing = function() end + self.C.MainPane.Paint = DoNothing + --self.C.Menu.Paint = DoNothing + + self.C.Menu:Dock(TOP) + self.C.TabHolder:Dock(FILL) + self.C.Val:Dock(BOTTOM) + + self.C.TabHolder:SetPadding(1) + + self.C.Menu:SetHeight(24) + self.C.Menu:DockPadding(2,2,2,2) + self.C.Val:SetHeight(22) + + self.C.SaE:SetSize(80, 20) + self.C.SaE:Dock(RIGHT) + self.C.SavAs:SetSize(51, 20) + self.C.SavAs:Dock(RIGHT) + + self.C.Inf:Dock(NODOCK) + self.C.ConBut:Dock(NODOCK) + + self.C.Close:SetText("r") + self.C.Close:SetFont("Marlett") + self.C.Close.DoClick = function(btn) self:Close() end + + self.C.ConBut:SetImage("icon16/wrench.png") + self.C.ConBut:SetText("") + self.C.ConBut.Paint = PaintFlatButton + self.C.ConBut.DoClick = function() self.C.Control:SetVisible(not self.C.Control:IsVisible()) end + + self.C.Inf:SetImage("icon16/information.png") + self.C.Inf.Paint = PaintFlatButton + self.C.Inf.DoClick = function(btn) + self.C.Credit:SetVisible(not self.C.Credit:IsVisible()) + end + + + self.C.Sav:SetImage("icon16/disk.png") + self.C.Sav.DoClick = function(button) self:SaveFile(self:GetChosenFile()) end + self.C.Sav:SetToolTip( "Save" ) + + self.C.NewTab:SetImage("icon16/page_white_add.png") + self.C.NewTab.DoClick = function(button) self:NewTab() end + self.C.NewTab:SetToolTip( "New tab" ) + + self.C.CloseTab:SetImage("icon16/page_white_delete.png") + self.C.CloseTab.DoClick = function(button) self:CloseTab() end + self.C.CloseTab:SetToolTip( "Close tab" ) + + self.C.Reload:SetImage("icon16/page_refresh.png") + self.C.Reload:SetToolTip( "Refresh file" ) + self.C.Reload.DoClick = function(button) + self:LoadFile(self:GetChosenFile(), false) + end + + self.C.SaE:SetText("Save and Exit") + self.C.SaE.DoClick = function(button) self:SaveFile(self:GetChosenFile(), true) end + + self.C.SavAs:SetText("Save As") + self.C.SavAs.DoClick = function(button) self:SaveFile(self:GetChosenFile(), false, true) end + + self.C.Browser:AddRightClick(self.C.Browser.filemenu, 4, "Save to", function() + Derma_Query("Overwrite this file?", "Save To", + "Overwrite", function() + self:SaveFile(self.C.Browser.File.FileDir) + end, + "Cancel") + end) + self.C.Browser.OnFileOpen = function(_, filepath, newtab) + self:Open(filepath, nil, newtab) + end + + self.C.Val:SetText(" Click to validate...") + self.C.Val.UpdateColours = function(button, skin) + return button:SetTextStyleColor(skin.Colours.Button.Down) + end + self.C.Val.SetBGColor = function(button, r, g, b, a) + self.C.Val.bgcolor = Color(r, g, b, a) + end + self.C.Val.bgcolor = Color(255, 255, 255) + self.C.Val.Paint = function(button) + local w, h = button:GetSize() + draw.RoundedBox(1, 0, 0, w, h, button.bgcolor) + if button.Hovered then draw.RoundedBox(0, 1, 1, w - 2, h - 2, Color(0, 0, 0, 128)) end + end + self.C.Val.OnMousePressed = function(panel, btn) + if btn == MOUSE_RIGHT then + local menu = DermaMenu() + menu:AddOption("Copy to clipboard", function() + SetClipboardText(self.C.Val:GetValue():sub(4)) + end) + menu:Open() + else + self:Validate(true) + end + end + self.C.Btoggle:SetImage("icon16/application_side_contract.png") + function self.C.Btoggle.DoClick(button) + if button.hide then + self.C.Divider:LoadCookies() + else + self.C.Divider:SetLeftWidth(0) + end + self.C.Divider:InvalidateLayout() + button:InvalidateLayout() + end + + local oldBtoggleLayout = self.C.Btoggle.PerformLayout + function self.C.Btoggle.PerformLayout(button) + oldBtoggleLayout(button) + if self.C.Divider:GetLeftWidth() > 0 then + button.hide = false + button:SetImage("icon16/application_side_contract.png") + else + button.hide = true + button:SetImage("icon16/application_side_expand.png") + end + end + + self.C.Credit:SetTextColor(Color(0, 0, 0, 255)) + self.C.Credit:SetText("\t\tCREDITS\n\n\tEditor by: \tSyranide and Shandolum\n\n\tTabs (and more) added by Divran.\n\n\tFixed for GMod13 By Ninja101") -- Sure why not ;) + self.C.Credit:SetMultiline(true) + self.C.Credit:SetVisible(false) + + self:InitControlPanel(self.C.Control) -- making it seperate for better overview + self.C.Control:SetVisible(false) + if self.E2 then self:Validate() end +end + +-- code1 contains the code that is not to be marked +local code1 = "@name \n@inputs \n@outputs \n@persist \n@trigger \n\n" +-- code2 contains the code that is to be marked, so it can simply be overwritten or deleted. +local code2 = [[#[ + Documentation and examples are available at: + https://github.com/wiremod/wire/wiki/Expression-2 + + Discord is available at https://discord.gg/H8UKY3Y + Reddit is available at https://www.reddit.com/r/wiremod + Report any bugs you find here https://github.com/wiremod/wire/issues +]#]] +local defaultcode = code1 .. code2 .. "\n" + +function Editor:AutoSave() + local buffer = self:GetCode() + if self.savebuffer == buffer or buffer == defaultcode or buffer == "" then return end + self.savebuffer = buffer + file.Write(self.Location .. "/_autosave_.txt", buffer) +end + +function Editor:AddControlPanelTab(label, icon, tooltip) + local frame = self.C.Control + local panel = vgui.Create("DPanel") + local ret = frame.TabHolder:AddSheet(label, panel, icon, false, false, tooltip) + local old = ret.Tab.OnMousePressed + function ret.Tab.OnMousePressed(...) + timer.Simple(0.1,function() frame:ResizeAll() end) -- timers solve everything + old(...) + end + + ret.Panel:SetBackgroundColor(Color(96, 96, 96, 255)) + + return ret +end + +function Editor:InitControlPanel(frame) + -- Add a property sheet to hold the tabs + local tabholder = vgui.Create("DPropertySheet", frame) + tabholder:SetPos(2, 4) + frame.TabHolder = tabholder + + -- They need to be resized one at a time... dirty fix incoming (If you know of a nicer way to do this, don't hesitate to fix it.) + local function callNext(t, n) + local obj = t[n] + local pnl = obj[1] + if pnl and pnl:IsValid() then + local x, y = obj[2], obj[3] + pnl:SetPos(x, y) + local w, h = pnl:GetParent():GetSize() + local wofs, hofs = w - x * 2, h - y * 2 + pnl:SetSize(wofs, hofs) + end + n = n + 1 + if n <= #t then + timer.Simple(0, function() callNext(t, n) end) + end + end + + function frame:ResizeAll() + timer.Simple(0, function() + callNext(self.ResizeObjects, 1) + end) + end + + -- Resize them at the right times + local oldFrameSetSize = frame.SetSize + function frame:SetSize(...) + self:ResizeAll() + oldFrameSetSize(self, ...) + end + + local oldFrameSetVisible = frame.SetVisible + function frame:SetVisible(...) + self:ResizeAll() + oldFrameSetVisible(self, ...) + end + + -- Function to add more objects to resize automatically + frame.ResizeObjects = {} + function frame:AddResizeObject(...) + self.ResizeObjects[#self.ResizeObjects + 1] = { ... } + end + + -- Our first object to auto resize is the tabholder. This sets it to position 2,4 and with a width and height offset of w-4, h-8. + frame:AddResizeObject(tabholder, 2, 4) + + -- ------------------------------------------- EDITOR TAB + local sheet = self:AddControlPanelTab("Editor", "icon16/wrench.png", "Options for the editor itself.") + + -- WINDOW BORDER COLORS + + local dlist = vgui.Create("DPanelList", sheet.Panel) + dlist.Paint = function() end + frame:AddResizeObject(dlist, 4, 4) + dlist:EnableVerticalScrollbar(true) + + -- Color Mixer PANEL - Houses label, combobox, mixer, reset button & reset all button. + local mixPanel = vgui.Create( "panel" ) + mixPanel:SetTall( 240 ) + dlist:AddItem( mixPanel ) + + do + -- Label + local label = vgui.Create( "DLabel", mixPanel ) + label:Dock( TOP ) + label:SetText( "Syntax Colors" ) + label:SizeToContents() + + -- Dropdown box of convars to change ( affects editor colors ) + local box = vgui.Create( "DComboBox", mixPanel ) + box:Dock( TOP ) + box:SetValue( "Color feature" ) + local active = nil + + -- Mixer + local mixer = vgui.Create( "DColorMixer", mixPanel ) + mixer:Dock( FILL ) + mixer:SetPalette( true ) + mixer:SetAlphaBar( true ) + mixer:SetWangs( true ) + mixer.ValueChanged = function ( _, clr ) + self:SetSyntaxColor( active, clr ) + end + + for k, _ in pairs( colors_convars ) do + box:AddChoice( k ) + end + + box.OnSelect = function ( self, index, value, data ) + -- DComboBox doesn't have a method for getting active value ( to my knowledge ) + -- Therefore, cache it, we're in a local scope so we're fine. + active = value + mixer:SetColor( colors[ active ] or Color( 255, 255, 255 ) ) + end + + -- Reset ALL button + local rAll = vgui.Create( "DButton", mixPanel ) + rAll:Dock( BOTTOM ) + rAll:SetText( "Reset ALL to Default" ) + + rAll.DoClick = function () + for k, v in pairs( colors_defaults ) do + self:SetSyntaxColor( k, v ) + end + mixer:SetColor( colors_defaults[ active ] ) + end + + -- Reset to default button + local reset = vgui.Create( "DButton", mixPanel ) + reset:Dock( BOTTOM ) + reset:SetText( "Set to Default" ) + + reset.DoClick = function () + self:SetSyntaxColor( active, colors_defaults[ active ] ) + mixer:SetColor( colors_defaults[ active ] ) + end + + + -- Select a convar to be displayed automatically + box:ChooseOptionID( 1 ) + end + + --- - FONTS + + local FontLabel = vgui.Create("DLabel") + dlist:AddItem(FontLabel) + FontLabel:SetText("Font: Font Size:") + FontLabel:SizeToContents() + FontLabel:SetPos(10, 0) + + local temp = vgui.Create("Panel") + temp:SetTall(25) + dlist:AddItem(temp) + + local FontSelect = vgui.Create("DComboBox", temp) + -- dlist:AddItem( FontSelect ) + FontSelect.OnSelect = function(panel, index, value) + if value == "Custom..." then + Derma_StringRequestNoBlur("Enter custom font:", "", "", function(value) + self:ChangeFont(value, self.FontSizeConVar:GetInt()) + RunConsoleCommand("wire_expression2_editor_font", value) + end) + else + value = value:gsub(" %b()", "") -- Remove description + self:ChangeFont(value, self.FontSizeConVar:GetInt()) + RunConsoleCommand("wire_expression2_editor_font", value) + end + end + for k, v in pairs(self.Fonts) do + FontSelect:AddChoice(k .. (v ~= "" and " (" .. v .. ")" or "")) + end + FontSelect:AddChoice("Custom...") + FontSelect:SetSize(240 - 50 - 4, 20) + + local FontSizeSelect = vgui.Create("DComboBox", temp) + FontSizeSelect.OnSelect = function(panel, index, value) + value = value:gsub(" %b()", "") + self:ChangeFont(self.FontConVar:GetString(), tonumber(value)) + RunConsoleCommand("wire_expression2_editor_font_size", value) + end + for i = 11, 26 do + FontSizeSelect:AddChoice(i .. (i == 16 and " (Default)" or "")) + end + FontSizeSelect:SetPos(FontSelect:GetWide() + 4, 0) + FontSizeSelect:SetSize(50, 20) + + + local label = vgui.Create("DLabel") + dlist:AddItem(label) + label:SetText("Auto completion options") + label:SizeToContents() + + local AutoComplete = vgui.Create("DCheckBoxLabel") + dlist:AddItem(AutoComplete) + AutoComplete:SetConVar("wire_expression2_autocomplete") + AutoComplete:SetText("Auto Completion") + AutoComplete:SizeToContents() + AutoComplete:SetTooltip("Enable/disable auto completion in the E2 editor.") + + local AutoCompleteExtra = vgui.Create("DCheckBoxLabel") + dlist:AddItem(AutoCompleteExtra) + AutoCompleteExtra:SetConVar("wire_expression2_autocomplete_moreinfo") + AutoCompleteExtra:SetText("More Info (for AC)") + AutoCompleteExtra:SizeToContents() + AutoCompleteExtra:SetTooltip("Enable/disable additional information for auto completion.") + + label = vgui.Create("DLabel") + dlist:AddItem(label) + label:SetText("Auto completion control style") + label:SizeToContents() + + local AutoCompleteControlOptions = vgui.Create("DComboBox") + dlist:AddItem(AutoCompleteControlOptions) + + local modes = {} + modes["Default"] = { 0, "Current mode:\nTab/CTRL+Tab to choose item;\nEnter/Space to use;\nArrow keys to abort." } + modes["Visual C# Style"] = { 1, "Current mode:\nCtrl+Space to use the top match;\nArrow keys to choose item;\nTab/Enter/Space to use;\nCode validation hotkey (ctrl+space) moved to ctrl+b." } + modes["Scroller"] = { 2, "Current mode:\nMouse scroller to choose item;\nMiddle mouse to use." } + modes["Scroller w/ Enter"] = { 3, "Current mode:\nMouse scroller to choose item;\nEnter to use." } + modes["Eclipse Style"] = { 4, "Current mode:\nEnter to use top match;\nTab to enter auto completion menu;\nArrow keys to choose item;\nEnter to use;\nSpace to abort." } + modes["Atom/IntelliJ style"] = { 5, "Current mode:\nTab/Enter to use;\nArrow keys to choose." } + -- modes["Qt Creator Style"] = { 6, "Current mode:\nCtrl+Space to enter auto completion menu;\nSpace to abort; Enter to use top match." } <-- probably wrong. I'll check about adding Qt style later. + + for k, _ in pairs(modes) do + AutoCompleteControlOptions:AddChoice(k) + end + + modes[0] = modes["Default"][2] + modes[1] = modes["Visual C# Style"][2] + modes[2] = modes["Scroller"][2] + modes[3] = modes["Scroller w/ Enter"][2] + modes[4] = modes["Eclipse Style"][2] + modes[5] = modes["Atom/IntelliJ style"][2] + AutoCompleteControlOptions:SetToolTip(modes[GetConVar("wire_expression2_autocomplete_controlstyle"):GetInt()]) + + + AutoCompleteControlOptions.OnSelect = function(panel, index, value) + panel:SetToolTip(modes[value][2]) + RunConsoleCommand("wire_expression2_autocomplete_controlstyle", modes[value][1]) + end + + local HighightOnUse = vgui.Create("DCheckBoxLabel") + dlist:AddItem(HighightOnUse) + HighightOnUse:SetConVar("wire_expression2_autocomplete_highlight_after_use") + HighightOnUse:SetText("Highlight word after AC use.") + HighightOnUse:SizeToContents() + HighightOnUse:SetTooltip("Enable/Disable highlighting of the entire word after using auto completion.\nIn E2, this is only for variables/constants, not functions.") + + label = vgui.Create("DLabel") + dlist:AddItem(label) + label:SetText("Other options") + label:SizeToContents() + + local NewTabOnOpen = vgui.Create("DCheckBoxLabel") + dlist:AddItem(NewTabOnOpen) + NewTabOnOpen:SetConVar("wire_expression2_new_tab_on_open") + NewTabOnOpen:SetText("New tab on open") + NewTabOnOpen:SizeToContents() + NewTabOnOpen:SetTooltip("Enable/disable loaded files opening in a new tab.\nIf disabled, loaded files will be opened in the current tab.") + + local SaveTabsOnClose = vgui.Create("DCheckBoxLabel") + dlist:AddItem(SaveTabsOnClose) + SaveTabsOnClose:SetConVar("wire_expression2_editor_savetabs") + SaveTabsOnClose:SetText("Save tabs on close") + SaveTabsOnClose:SizeToContents() + SaveTabsOnClose:SetTooltip("Save the currently opened tab file paths on shutdown.\nOnly saves tabs whose files are saved.") + + local OpenOldTabs = vgui.Create("DCheckBoxLabel") + dlist:AddItem(OpenOldTabs) + OpenOldTabs:SetConVar("wire_expression2_editor_openoldtabs") + OpenOldTabs:SetText("Open old tabs on load") + OpenOldTabs:SizeToContents() + OpenOldTabs:SetTooltip("Open the tabs from the last session on load.\nOnly tabs whose files were saved before disconnecting from the server are stored.") + + local DisplayCaretPos = vgui.Create("DCheckBoxLabel") + dlist:AddItem(DisplayCaretPos) + DisplayCaretPos:SetConVar("wire_expression2_editor_display_caret_pos") + DisplayCaretPos:SetText("Show Caret Position") + DisplayCaretPos:SizeToContents() + DisplayCaretPos:SetTooltip("Shows the position of the caret.") + + local HighlightOnDoubleClick = vgui.Create("DCheckBoxLabel") + dlist:AddItem(HighlightOnDoubleClick) + HighlightOnDoubleClick:SetConVar("wire_expression2_editor_highlight_on_double_click") + HighlightOnDoubleClick:SetText("Highlight copies of selected word") + HighlightOnDoubleClick:SizeToContents() + HighlightOnDoubleClick:SetTooltip("Find all identical words and highlight them after a double-click.") + + local WorldClicker = vgui.Create("DCheckBoxLabel") + dlist:AddItem(WorldClicker) + WorldClicker:SetConVar("wire_expression2_editor_worldclicker") + WorldClicker:SetText("Enable Clicking Outside Editor") + WorldClicker:SizeToContents() + function WorldClicker.OnChange(pnl, bVal) + self:GetParent():SetWorldClicker(bVal) + end + + --------------------------------------------- EXPRESSION 2 TAB + sheet = self:AddControlPanelTab("Expression 2", "icon16/computer.png", "Options for Expression 2.") + + dlist = vgui.Create("DPanelList", sheet.Panel) + dlist.Paint = function() end + frame:AddResizeObject(dlist, 2, 2) + dlist:EnableVerticalScrollbar(true) + + label = vgui.Create("DLabel") + dlist:AddItem(label) + label:SetText("Clientside expression 2 options") + label:SizeToContents() + + local AutoIndent = vgui.Create("DCheckBoxLabel") + dlist:AddItem(AutoIndent) + AutoIndent:SetConVar("wire_expression2_autoindent") + AutoIndent:SetText("Auto indenting") + AutoIndent:SizeToContents() + AutoIndent:SetTooltip("Enable/disable auto indenting.") + + local Concmd = vgui.Create("DCheckBoxLabel") + dlist:AddItem(Concmd) + Concmd:SetConVar("wire_expression2_concmd") + Concmd:SetText("concmd") + Concmd:SizeToContents() + Concmd:SetTooltip("Allow/disallow the E2 from running console commands on you.") + + label = vgui.Create("DLabel") + dlist:AddItem(label) + label:SetText("Concmd whitelist") + label:SizeToContents() + + local ConcmdWhitelist = vgui.Create("DTextEntry") + dlist:AddItem(ConcmdWhitelist) + ConcmdWhitelist:SetConVar("wire_expression2_concmd_whitelist") + ConcmdWhitelist:SetToolTip("Separate the commands with commas.") + + label = vgui.Create("DLabel") + dlist:AddItem(label) + label:SetText("Expression 2 block comment style") + label:SizeToContents() + + local BlockCommentStyle = vgui.Create("DComboBox") + dlist:AddItem(BlockCommentStyle) + + local blockCommentModes = {} + blockCommentModes["New (alt 1)"] = { + 0, [[Current mode: +#[ +Text here +Text here +]#]] + } + blockCommentModes["New (alt 2)"] = { + 1, [[Current mode: +#[Text here +Text here]# ]] + } + blockCommentModes["Old"] = { + 2, [[Current mode: +#Text here +#Text here]] + } + + for k, _ in pairs(blockCommentModes) do + BlockCommentStyle:AddChoice(k) + end + + blockCommentModes[0] = blockCommentModes["New (alt 1)"][2] + blockCommentModes[1] = blockCommentModes["New (alt 2)"][2] + blockCommentModes[2] = blockCommentModes["Old"][2] + BlockCommentStyle:SetToolTip(blockCommentModes[self.BlockCommentStyleConVar:GetInt()]) + + BlockCommentStyle.OnSelect = function(panel, index, value) + panel:SetToolTip(blockCommentModes[value][2]) + RunConsoleCommand("wire_expression2_editor_block_comment_style", blockCommentModes[value][1]) + end + + local ops_sync_checkbox = vgui.Create("DCheckBoxLabel") + dlist:AddItem(ops_sync_checkbox) + ops_sync_checkbox:SetConVar("wire_expression_ops_sync_subscribe") + ops_sync_checkbox:SetText("ops/cpu usage syncing for remote uploader (Admin only)") + ops_sync_checkbox:SizeToContents() + ops_sync_checkbox:SetTooltip("Opt into live ops/cpu usage for all E2s on the server via the remote uploader tab. If you're not admin, this checkbox does nothing.") + + -- ------------------------------------------- REMOTE UPDATER TAB + sheet = self:AddControlPanelTab("Remote Updater", "icon16/world.png", "Manage your E2s from far away.") + + dlist = vgui.Create("DPanelList", sheet.Panel) + dlist.Paint = function() end + frame:AddResizeObject(dlist, 2, 2) + dlist:EnableVerticalScrollbar(true) + dlist:SetSpacing(2) + + local dlist2 = vgui.Create("DPanelList") + dlist:AddItem(dlist2) + dlist2:EnableVerticalScrollbar(true) + -- frame:AddResizeObject( dlist2, 2,2 ) + -- dlist2:SetTall( 444 ) + dlist2:SetSpacing(1) + + local painted = 0 + local opened = false + dlist2.Paint = function() painted = SysTime() + 0.05 end + timer.Create( "wire_expression2_ops_sync_check", 0, 0, function() + if painted > SysTime() and not opened then + opened = true + if Editor.ops_sync_subscribe:GetBool() then RunConsoleCommand("wire_expression_ops_sync","1") end + elseif painted < SysTime() and opened then + opened = false + RunConsoleCommand("wire_expression_ops_sync","0") + end + end) + + local UpdateList = vgui.Create("DButton") + UpdateList:SetText("Update List (Show only yours)") + dlist:AddItem(UpdateList) + UpdateList.DoClick = function(pnl, showall) + local E2s = ents.FindByClass("gmod_wire_expression2") + dlist2:Clear() + local size = 0 + for _, v in pairs(E2s) do + local ply = v:GetNWEntity("player", NULL) + if IsValid(ply) and ply == LocalPlayer() or showall then + local nick + if not ply or not ply:IsValid() then nick = "Unknown" else nick = ply:Nick() end + local name = v:GetNWString("name", "generic") + + local singleline = string.match( name, "(.-)\n" ) + if singleline then name = singleline .. "..." end + + local max = 20 + if #name > max then name = string.sub(name,1,max) .. "..." end + + local panel = vgui.Create("DPanel") + panel:SetTall((LocalPlayer():IsAdmin() and 74 or 47)) + panel.Paint = function(panel) + local w, h = panel:GetSize() + draw.RoundedBox(1, 0, 0, w, h, Color(65, 105, 255, 100)) + end + dlist2:AddItem(panel) + size = size + panel:GetTall() + 1 + + local label = vgui.Create("DLabel", panel) + local idx = v:EntIndex() + + local str = string.format("Name: %s\nEntity ID: '%d'\nOwner: %s",name,idx,nick) + if LocalPlayer():IsAdmin() then + str = string.format("Name: %s\nEntity ID: '%d'\n%i ops, %i%% %s\ncpu time: %ius\nOwner: %s",name,idx,0,0,"",0,nick) + end + + label:SetText(str) + label:SizeToContents() + label:SetWide(280) + label:SetWrap(true) + label:SetPos(4, 4) + label:SetTextColor(Color(255, 255, 255, 255)) + + if LocalPlayer():IsAdmin() then + local hardquota = GetConVar("wire_expression2_quotahard") + local softquota = GetConVar("wire_expression2_quotasoft") + + function label:Think() + if not IsValid(v) then + label.Think = function() end + return + end + + local data = v:GetOverlayData() + if data then + local prfbench = data.prfbench + local prfcount = data.prfcount + local timebench = data.timebench + + local e2_hardquota = hardquota:GetInt() + local e2_softquota = softquota:GetInt() + + local hardtext = (prfcount / e2_hardquota > 0.33) and "(+" .. tostring(math.Round(prfcount / e2_hardquota * 100)) .. "%)" or "" + + label:SetText(string.format("Name: %s\nEntity ID: '%d'\n%i ops, %i%% %s\ncpu time: %ius\nOwner: %s",name,idx,prfbench,prfbench / e2_softquota * 100,hardtext,timebench*1000000,nick)) + end + end + end + + do + local btn = vgui.Create("DButton", panel) + btn:SetText("Upload") + btn:SetSize(57, 18) + timer.Simple(0, function() btn:SetPos(panel:GetWide() - btn:GetWide() * 2 - 6, 4) end) + btn.DoClick = function(pnl) + WireLib.Expression2Upload(v) + end + end + + do + local btn = vgui.Create("DButton", panel) + btn:SetText("Download") + btn:SetSize(57, 18) + timer.Simple(0, function() btn:SetPos(panel:GetWide() - btn:GetWide() - 4, 4) end) + btn.DoClick = function(pnl) + RunConsoleCommand("wire_expression_requestcode", v:EntIndex()) + end + end + + do + local btn = vgui.Create("DButton", panel) + btn:SetText("Halt execution") + btn:SetSize(75, 18) + timer.Simple(0, function() btn:SetPos(panel:GetWide() - btn:GetWide() - 4, 24) end) + btn.DoClick = function(pnl) + RunConsoleCommand("wire_expression_forcehalt", v:EntIndex()) + end + + local btn2 = vgui.Create("DButton", panel) + btn2:SetText("Reset") + btn2:SetSize(39, 18) + timer.Simple(0, function() btn2:SetPos(panel:GetWide() - btn2:GetWide() - btn:GetWide() - 6, 24) end) + btn2.DoClick = function(pnl) + RunConsoleCommand("wire_expression_reset", v:EntIndex()) + end + end + end + end + dlist2:SetTall(size + 2) + dlist:InvalidateLayout() + end + local UpdateList2 = vgui.Create("DButton") + UpdateList2:SetText("Update List (Show all)") + dlist:AddItem(UpdateList2) + UpdateList2.DoClick = function(pnl) UpdateList:DoClick(true) end +end + +-- used with color-circles +function Editor:TranslateValues(panel, x, y) + x = x - 0.5 + y = y - 0.5 + local angle = math.atan2(x, y) + local length = math.sqrt(x * x + y * y) + length = math.Clamp(length, 0, 0.5) + x = 0.5 + math.sin(angle) * length + y = 0.5 + math.cos(angle) * length + panel:SetHue(math.deg(angle) + 270) + panel:SetSaturation(length * 2) + panel:SetRGB(HSVToColor(panel:GetHue(), panel:GetSaturation(), 1)) + panel:SetFrameColor() + return x, y +end + +-- options + +function Editor:NewScript(incurrent) + if not incurrent and self.NewTabOnOpen:GetBool() then + self:NewTab() + else + self:AutoSave() + self:ChosenFile() + + -- Set title + self:GetActiveTab():SetText("generic") + self.C.TabHolder:InvalidateLayout() + + if self.E2 then + -- add both code1 and code2 to the editor + self:SetCode(defaultcode) + local ed = self:GetCurrentEditor() + -- mark only code2 + ed.Start = ed:MovePosition({ 1, 1 }, code1:len()) + ed.Caret = ed:MovePosition({ 1, 1 }, defaultcode:len()) + else + self:SetCode("") + end + end +end + +local wire_expression2_editor_savetabs = CreateClientConVar("wire_expression2_editor_savetabs", "1", true, false) + +local id = 0 +function Editor:InitShutdownHook() + id = id + 1 + + -- save code when shutting down + hook.Add("ShutDown", "wire_expression2_ShutDown" .. id, function() + -- if wire_expression2_editor == nil then return end + local buffer = self:GetCode() + if buffer == defaultcode then return end + file.Write(self.Location .. "/_shutdown_.txt", buffer) + + if wire_expression2_editor_savetabs:GetBool() then + self:SaveTabs() + end + end) +end + +function Editor:SaveTabs() + local strtabs = "" + local tabs = {} + for i=1, self:GetNumTabs() do + local chosenfile = self:GetEditor(i).chosenfile + if chosenfile and chosenfile ~= "" and not tabs[chosenfile] then + strtabs = strtabs .. chosenfile .. ";" + tabs[chosenfile] = true -- Prevent duplicates + end + end + + strtabs = strtabs:sub(1, -2) + + file.Write(self.Location .. "/_tabs_.txt", strtabs) +end + +local wire_expression2_editor_openoldtabs = CreateClientConVar("wire_expression2_editor_openoldtabs", "1", true, false) + +function Editor:OpenOldTabs() + if not file.Exists(self.Location .. "/_tabs_.txt", "DATA") then return end + + -- Read file + local tabs = file.Read(self.Location .. "/_tabs_.txt") + if not tabs or tabs == "" then return end + + -- Explode around ; + tabs = string.Explode(";", tabs) + if not tabs or #tabs == 0 then return end + + -- Temporarily remove fade time + self:FixTabFadeTime() + + local is_first = true + for _, v in pairs(tabs) do + if v and v ~= "" then + if (file.Exists(v, "DATA")) then + -- Open it in a new tab + self:LoadFile(v, true) + + -- If this is the first loop, close the initial tab. + if (is_first) then + timer.Simple(0, function() + self:CloseTab(1) + end) + is_first = false + end + end + end + end +end + +function Editor:Validate(gotoerror) + if self.EditorType == "E2" then + local errors = wire_expression2_validate(self:GetCode()) + if not errors then + self.C.Val:SetBGColor(0, 110, 20, 255) + self.C.Val:SetText(" Validation successful") + return true + end + if gotoerror then + local row, col = errors:match("at line ([0-9]+), char ([0-9]+)$") + if not row then + row, col = errors:match("at line ([0-9]+)$"), 1 + end + if row then self:GetCurrentEditor():SetCaret({ tonumber(row), tonumber(col) }) end + end + self.C.Val:SetBGColor(110, 0, 20, 255) + self.C.Val:SetText(" " .. errors) + elseif self.EditorType == "CPU" or self.EditorType == "GPU" or self.EditorType == "SPU" then + self.C.Val:SetBGColor(64, 64, 64, 180) + self.C.Val:SetText(" Recompiling...") + CPULib.Validate(self, self:GetCode(), self:GetChosenFile()) + end + return true +end + +function Editor:SetValidatorStatus(text, r, g, b, a) + self.C.Val:SetBGColor(r or 0, g or 180, b or 0, a or 180) + self.C.Val:SetText(" " .. text) +end + +function Editor:SubTitle(sub) + if not sub then self.subTitle = "" + else self.subTitle = " - " .. sub + end +end + +local wire_expression2_editor_worldclicker = CreateClientConVar("wire_expression2_editor_worldclicker", "0", true, false) +function Editor:SetV(bool) + if bool then + self:MakePopup() + self:InvalidateLayout(true) + if self.E2 then self:Validate() end + end + self:SetVisible(bool) + self:SetKeyBoardInputEnabled(bool) + self:GetParent():SetWorldClicker(wire_expression2_editor_worldclicker:GetBool() and bool) -- Enable this on the background so we can update E2's without closing the editor + if CanRunConsoleCommand() then + RunConsoleCommand("wire_expression2_event", bool and "editor_open" or "editor_close") + if not e2_function_data_received and bool then -- Request the E2 functions + RunConsoleCommand("wire_expression2_sendfunctions") + end + end +end + +function Editor:GetChosenFile() + return self:GetCurrentEditor().chosenfile +end + +function Editor:ChosenFile(Line) + self:GetCurrentEditor().chosenfile = Line + if Line then + self:SubTitle("Editing: " .. Line) + else + self:SubTitle() + end +end + +function Editor:FindOpenFile(FilePath) + for i = 1, self:GetNumTabs() do + local ed = self:GetEditor(i) + if ed.chosenfile == FilePath then + return ed + end + end +end + +function Editor:ExtractName() + if not self.E2 then self.savefilefn = "filename" return end + local code = self:GetCode() + local name = extractNameFromCode(code) + if name and name ~= "" then + Expression2SetName(name) + self.savefilefn = name + else + Expression2SetName(nil) + self.savefilefn = "filename" + end +end + +function Editor:SetCode(code) + self:GetCurrentEditor():SetText(code) + self.savebuffer = self:GetCode() + if self.E2 then self:Validate() end + self:ExtractName() +end + +function Editor:GetEditor(n) + if self.C.TabHolder.Items[n] then + return self.C.TabHolder.Items[n].Panel + end +end + +function Editor:GetCurrentEditor() + return self:GetActiveTab():GetPanel() +end + +function Editor:GetCode() + return self:GetCurrentEditor():GetValue() +end + +function Editor:Open(Line, code, forcenewtab) + if self:IsVisible() and not Line and not code then self:Close() end + hook.Run("WireEditorOpen", self, Line, code, forcenewtab) + self:SetV(true) + if self.chip then + self.C.SaE:SetText("Upload & Exit") + else + self.C.SaE:SetText("Save and Exit") + end + if code then + if not forcenewtab then + for i = 1, self:GetNumTabs() do + if self:GetEditor(i).chosenfile == Line then + self:SetActiveTab(i) + self:SetCode(code) + return + elseif self:GetEditor(i):GetValue() == code then + self:SetActiveTab(i) + return + end + end + end + local _, tabtext = getPreferredTitles(Line, code) + local tab + if self.NewTabOnOpen:GetBool() or forcenewtab then + tab = self:CreateTab(tabtext).Tab + else + tab = self:GetActiveTab() + tab:SetText(tabtext) + self.C.TabHolder:InvalidateLayout() + end + self:SetActiveTab(tab) + + self:ChosenFile() + self:SetCode(code) + if Line then self:SubTitle("Editing: " .. Line) end + return + end + if Line then self:LoadFile(Line, forcenewtab) return end +end + +function Editor:SaveFile(Line, close, SaveAs) + self:ExtractName() + if close and self.chip then + if not self:Validate(true) then return end + WireLib.Expression2Upload(self.chip, self:GetCode()) + self:Close() + return + end + if not Line or SaveAs or Line == self.Location .. "/" .. ".txt" then + local str + if self.C.Browser.File then + str = self.C.Browser.File.FileDir -- Get FileDir + if str and str ~= "" then -- Check if not nil + + -- Remove "expression2/" or "cpuchip/" etc + local n, _ = str:find("/", 1, true) + str = str:sub(n + 1, -1) + + if str and str ~= "" then -- Check if not nil + if str:Right(4) == ".txt" then -- If it's a file + str = string.GetPathFromFilename(str):Left(-2) -- Get the file path instead + if not str or str == "" then + str = nil + end + end + else + str = nil + end + else + str = nil + end + end + Derma_StringRequestNoBlur("Save to New File", "", (str ~= nil and str .. "/" or "") .. self.savefilefn, + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + self:SaveFile(self.Location .. "/" .. strTextOut .. ".txt", close) + self:UpdateActiveTabTitle() + end) + return + end + + file.Write(Line, self:GetCode()) + + local panel = self.C.Val + timer.Simple(0, function() panel.SetText(panel, " Saved as " .. Line) end) + surface.PlaySound("ambient/water/drip3.wav") + + if not self.chip then self:ChosenFile(Line) end + if close then + if self.E2 then + GAMEMODE:AddNotify("Expression saved as " .. Line .. ".", NOTIFY_GENERIC, 7) + else + GAMEMODE:AddNotify("Source code saved as " .. Line .. ".", NOTIFY_GENERIC, 7) + end + self:Close() + end +end + +function Editor:LoadFile(Line, forcenewtab) + if not Line or file.IsDir(Line, "DATA") then return end + + local f = file.Open(Line, "r", "DATA") + if not f then + ErrorNoHalt("Erroring opening file: " .. Line) + else + local str = f:Read(f:Size()) or "" + f:Close() + self:AutoSave() + if not forcenewtab then + for i = 1, self:GetNumTabs() do + if self:GetEditor(i).chosenfile == Line then + self:SetActiveTab(i) + if forcenewtab ~= nil then self:SetCode(str) end + return + elseif self:GetEditor(i):GetValue() == str then + self:SetActiveTab(i) + return + end + end + end + + local _, tabtext = getPreferredTitles(Line, str) + local tab + if self.NewTabOnOpen:GetBool() or forcenewtab then + tab = self:CreateTab(tabtext).Tab + else + tab = self:GetActiveTab() + tab:SetText(tabtext) + self.C.TabHolder:InvalidateLayout() + end + self:SetActiveTab(tab) + self:ChosenFile(Line) + + self:SetCode(str) + end +end + +function Editor:Close() + timer.Stop("e2autosave") + self:AutoSave() + + self:Validate() + self:ExtractName() + self:SetV(false) + self.chip = false + + self:SaveEditorSettings() + + hook.Run("WireEditorClose", self) +end + +function Editor:Setup(nTitle, nLocation, nEditorType) + self.Title = nTitle + self.Location = nLocation + self.EditorType = nEditorType + self.C.Browser:Setup(nLocation) + + local textEditorModes = { + CPU = "ZCPU", + GPU = "ZCPU", + SPU = "ZCPU", + E2 = "E2", + [""] = "Default" + } + + local helpModes = { + CPU = E2Helper.UseCPU, + GPU = E2Helper.UseCPU, + SPU = E2Helper.UseCPU, + E2 = E2Helper.UseE2 + } + + self:SetEditorMode(textEditorModes[nEditorType or ""]) + + + local helpMode = helpModes[nEditorType or ""] + if helpMode then -- Add "E2Helper" button + local E2Help = vgui.Create("Button", self.C.Menu) + E2Help:SetSize(58, 20) + E2Help:Dock(RIGHT) + E2Help:SetText("E2Helper") + E2Help.DoClick = function() + E2Helper.Show() + helpMode(nEditorType) + E2Helper.Update() + end + self.C.E2Help = E2Help + end + + local useValidator = nEditorType ~= nil + local useSoundBrowser = nEditorType == "SPU" or nEditorType == "E2" + local useDebugger = nEditorType == "CPU" + + if not useValidator then + self.C.Val:SetVisible(false) + end + + if useSoundBrowser then -- Add "Sound Browser" button + local SoundBrw = vgui.Create("Button", self.C.Menu) + SoundBrw:SetSize(85, 20) + SoundBrw:Dock(RIGHT) + SoundBrw:SetText("Sound Browser") + SoundBrw.DoClick = function() RunConsoleCommand("wire_sound_browser_open") end + self.C.SoundBrw = SoundBrw + end + + if useDebugger then + -- Add "step forward" button + local DebugForward = self:addComponent(vgui.Create("Button", self), -306, 31, -226, 20) + DebugForward:SetText("Step Forward") + DebugForward.Font = "E2SmallFont" + DebugForward.DoClick = function() + local currentPosition = CPULib.Debugger.PositionByPointer[CPULib.Debugger.Variables.IP] + if currentPosition then + local linePointers = CPULib.Debugger.PointersByLine[currentPosition.Line .. ":" .. currentPosition.File] + if linePointers then -- Run till end of line + RunConsoleCommand("wire_cpulib_debugstep", linePointers[2]) + else -- Run just once + RunConsoleCommand("wire_cpulib_debugstep") + end + else -- Run just once + RunConsoleCommand("wire_cpulib_debugstep") + end + -- Reset interrupt text + CPULib.InterruptText = nil + end + self.C.DebugForward = DebugForward + + -- Add "reset" button + local DebugReset = self:addComponent(vgui.Create("Button", self), -346, 31, -306, 20) + DebugReset:SetText("Reset") + DebugReset.DoClick = function() + RunConsoleCommand("wire_cpulib_debugreset") + -- Reset interrupt text + CPULib.InterruptText = nil + end + self.C.DebugReset = DebugReset + + -- Add "run" button + local DebugRun = self:addComponent(vgui.Create("Button", self), -381, 31, -346, 20) + DebugRun:SetText("Run") + DebugRun.DoClick = function() RunConsoleCommand("wire_cpulib_debugrun") end + self.C.DebugRun = DebugRun + end + + if nEditorType == "E2" then + self.E2 = true + end + + self:NewScript(true) -- Opens initial tab, in case OpenOldTabs is disabled or fails. + + if wire_expression2_editor_openoldtabs:GetBool() then + self:OpenOldTabs() + end + self:LoadSyntaxColors() + + self:InvalidateLayout() +end + + +vgui.Register("Expression2EditorFrame", Editor, "DFrame") diff --git a/garrysmod/addons/feature-wire/lua/wire/client/thrusterlib.lua b/garrysmod/addons/feature-wire/lua/wire/client/thrusterlib.lua new file mode 100644 index 0000000..2d00518 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/thrusterlib.lua @@ -0,0 +1,1368 @@ +CreateConVar( "cl_drawthrusterseffects", "1" ) + +local matHeatWave = Material( "sprites/heatwave" ) +local matFire = Material( "effects/fire_cloud1" ) +local matPlasma = Material( "effects/strider_muzzle" ) +local matColor = Material( "effects/bloodstream" ) + +-- fixed by WeltEnSTurm: one emitter is enough! +local emitter = ParticleEmitter(Vector(0,0,0)) + +WireLib.ThrusterEffectThink = {} +WireLib.ThrusterEffectDraw = {} + +WireLib.ThrusterEffectDraw.fire = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local scroll = CurTime() * -10 + + render.SetMaterial( matFire ) + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 32, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 32, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matHeatWave ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 32, 32, scroll + 2, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 128, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matFire ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 16, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 16, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + +end + +WireLib.ThrusterEffectDraw.heatwave = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local scroll = CurTime() * -10 + + render.SetMaterial( matHeatWave ) + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 32, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 32, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matHeatWave ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 32, 32, scroll + 2, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 128, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matHeatWave ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 16, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 16, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + +end + +WireLib.ThrusterEffectDraw.color = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local scroll = CurTime() * -10 + + render.SetMaterial( matColor ) + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 255, 0, 0, 128) ) + render.AddBeam( vOffset + vNormal * 60, 32, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 32, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 255, 0, 128) ) + render.AddBeam( vOffset + vNormal * 32, 32, scroll + 2, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 128, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 16, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 16, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + +end + +WireLib.ThrusterEffectDraw.color_random = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local scroll = CurTime() * -10 + + render.SetMaterial( matColor ) + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 255, 0, 0, 128) ) + render.AddBeam( vOffset + vNormal * 60, 32, scroll + 1, Color( math.random(0,255), math.random(0,255), math.random(0,255), 128) ) + render.AddBeam( vOffset + vNormal * 148, 32, scroll + 3, Color( math.random(0,255), math.random(0,255), math.random(0,255), 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 255, 0, 128) ) + render.AddBeam( vOffset + vNormal * 32, 32, scroll + 2, Color( math.random(0,255), math.random(0,255), math.random(0,255), 255) ) + render.AddBeam( vOffset + vNormal * 128, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 16, scroll + 1, Color( math.random(0,255), math.random(0,255), math.random(0,255), 128) ) + render.AddBeam( vOffset + vNormal * 148, 16, scroll + 3, Color( math.random(0,255), math.random(0,255), math.random(0,255), 0) ) + render.EndBeam() + +end + +WireLib.ThrusterEffectDraw.color_diy = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + local c = self:GetColor() + + local scroll = CurTime() * -10 + + render.SetMaterial( matColor ) + + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 255, 0, 0, 128) ) + render.AddBeam( vOffset + vNormal * 60, 32, scroll + 1, Color( c.r, c.g, c.g, 128) ) + render.AddBeam( vOffset + vNormal * 148, 32, scroll + 3, Color( c.r, c.g, c.b, 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 255, 0, 128) ) + render.AddBeam( vOffset + vNormal * 32, 32, scroll + 2, Color( c.r, c.g, c.g, 255) ) + render.AddBeam( vOffset + vNormal * 128, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 16, scroll + 1, Color( c.r, c.g, c.g, 128) ) + render.AddBeam( vOffset + vNormal * 148, 16, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + +end + +WireLib.ThrusterEffectDraw.plasma = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local scroll = CurTime() * -20 + + render.SetMaterial( matPlasma ) + + scroll = scroll * 0.9 + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 16, scroll, Color( 0, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 8, 16, scroll + 0.01, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 64, 16, scroll + 0.02, Color( 0, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.9 + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 16, scroll, Color( 0, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 8, 16, scroll + 0.01, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 64, 16, scroll + 0.02, Color( 0, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.9 + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 16, scroll, Color( 0, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 8, 16, scroll + 0.01, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 64, 16, scroll + 0.02, Color( 0, 255, 255, 0) ) + render.EndBeam() + +end + +WireLib.ThrusterEffectDraw.fire_smoke = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + self.EffectAvg = ( self.EffectAvg * 29 + math.min( self:GetNWFloat("Thrust") / 100000, 100 ) ) / 30 + local Magnitude = self.EffectAvg + + local scroll = CurTime() * -10 + + render.SetMaterial( matFire ) + + render.StartBeam( 3 ) + render.AddBeam( vOffset, Magnitude/6, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * Magnitude, Magnitude/2, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * Magnitude * 2, Magnitude/2, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matHeatWave ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * Magnitude, 32, scroll + 2, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * Magnitude * 2, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matFire ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * Magnitude, 16, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * Magnitude * 2, 16, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.015 + + local orth1 = Vector( vNormal.z, vNormal.x, vNormal.y ) + orth1 = ( orth1 - vNormal * vNormal:Dot(orth1) ):GetNormalized() + local orth2 = vNormal:Cross( orth1 ) + + local particle = emitter:Add( "particles/smokey", vOffset ) + particle:SetVelocity( vNormal * math.Rand( Magnitude*19, Magnitude*20 ) + orth1 * math.Rand( -50, 50 ) + orth2 * math.Rand( -50, 50 ) ) + particle:SetAirResistance( 60 ) + particle:SetDieTime( 2.0 ) + particle:SetStartAlpha( math.Rand( 0, 10 ) ) + particle:SetEndAlpha( 200 ) + particle:SetStartSize( math.Rand( 16, 24 ) ) + particle:SetEndSize( math.Rand( 10+Magnitude/2, 30+Magnitude/2 ) ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) + particle:SetColor( 200, 200, 210 ) +end + +WireLib.ThrusterEffectDraw.fire_smoke_big = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 5 ) + effectdata:SetScale( 3 ) + util.Effect( "HelicopterMegaBomb", effectdata ) + + vOffset = self:LocalToWorld(self:GetOffset()) + Vector( math.Rand( -3, 3 ), math.Rand( -3, 3 ), math.Rand( -3, 3 ) ) + vNormal = self:CalcNormal() + + local particle = emitter:Add( "particles/smokey", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 10, 20 ) ) + particle:SetDieTime( 3.0 ) + particle:SetStartAlpha( math.Rand( 150, 255 ) ) + particle:SetStartSize( math.Rand( 64, 128 ) ) + particle:SetEndSize( math.Rand( 256, 128 ) ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) + particle:SetColor( 200, 200, 210 ) + + + + effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + util.Effect( "ThumperDust ", effectdata ) +end + +WireLib.ThrusterEffectThink.smoke = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.015 + + local vOffset = self:LocalToWorld(self:GetOffset()) + Vector( math.Rand( -3, 3 ), math.Rand( -3, 3 ), math.Rand( -3, 3 ) ) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "particles/smokey", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 10, 30 ) ) + particle:SetDieTime( 2.0 ) + particle:SetStartAlpha( math.Rand( 50, 150 ) ) + particle:SetStartSize( math.Rand( 16, 32 ) ) + particle:SetEndSize( math.Rand( 64, 128 ) ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) + particle:SetColor( 200, 200, 210 ) +end + +WireLib.ThrusterEffectThink.smoke_firecolors = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.015 + + local vOffset = self:LocalToWorld(self:GetOffset()) + Vector( math.Rand( -3, 3 ), math.Rand( -3, 3 ), math.Rand( -3, 3 ) ) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "particles/smokey", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 10, 30 ) ) + particle:SetDieTime( 2.0 ) + particle:SetStartAlpha( math.Rand( 50, 150 ) ) + particle:SetStartSize( math.Rand( 16, 32 ) ) + particle:SetEndSize( math.Rand( 64, 128 ) ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) + particle:SetColor(math.random(220,255),math.random(110,220),0 ) +end + +WireLib.ThrusterEffectThink.smoke_random = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.015 + + local vOffset = self:LocalToWorld(self:GetOffset()) + Vector( math.Rand( -3, 3 ), math.Rand( -3, 3 ), math.Rand( -3, 3 ) ) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "particles/smokey", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 10, 30 ) ) + particle:SetDieTime( 2.0 ) + particle:SetStartAlpha( math.Rand( 50, 150 ) ) + particle:SetStartSize( math.Rand( 16, 32 ) ) + particle:SetEndSize( math.Rand( 64, 128 ) ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) + particle:SetColor( math.random(100,255),math.random(100,255),math.random(100,255) ) +end + +WireLib.ThrusterEffectThink.smoke_diy = function(self) + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.015 + + local vOffset = self:LocalToWorld(self:GetOffset()) + Vector( math.Rand( -3, 3 ), math.Rand( -3, 3 ), math.Rand( -3, 3 ) ) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "particles/smokey", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 10, 30 ) ) + particle:SetDieTime( 2.0 ) + particle:SetStartAlpha( math.Rand( 50, 150 ) ) + particle:SetStartSize( math.Rand( 16, 32 ) ) + particle:SetEndSize( math.Rand( 64, 128 ) ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) + particle:SetColor(self:GetColor()) +end + +WireLib.ThrusterEffectDraw.color_magic = function(self) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local scroll = CurTime() * -10 + + render.SetMaterial( matColor ) + + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 255, 0, 0, 128) ) + render.AddBeam( vOffset + vNormal * 60, 32, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 32, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + scroll = scroll * 0.5 + + render.UpdateRefractTexture() + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 255, 0, 128) ) + render.AddBeam( vOffset + vNormal * 32, 32, scroll + 2, Color( 255, 255, 255, 255) ) + render.AddBeam( vOffset + vNormal * 128, 48, scroll + 5, Color( 0, 0, 0, 0) ) + render.EndBeam() + + + scroll = scroll * 1.3 + render.SetMaterial( matColor ) + render.StartBeam( 3 ) + render.AddBeam( vOffset, 8, scroll, Color( 0, 0, 255, 128) ) + render.AddBeam( vOffset + vNormal * 60, 16, scroll + 1, Color( 255, 255, 255, 128) ) + render.AddBeam( vOffset + vNormal * 148, 16, scroll + 3, Color( 255, 255, 255, 0) ) + render.EndBeam() + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.00005 + + vOffset = self:LocalToWorld(self:GetOffset()) + vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "sprites/gmdm_pickups/light", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 50, 80 ) ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetStartSize( math.Rand( 1, 3 ) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) +end + +WireLib.ThrusterEffectThink.money = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + math.random(0.005,0.00005) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 20 + + local particle = emitter:Add( "thrusteraddon/money"..math.floor(math.random(1,3)).."", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 0, 70 ) ) + particle:SetDieTime( math.Rand(3,5 ) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand( -90, 90 ) ) +end + +WireLib.ThrusterEffectThink.debug_10 = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "decals/cross", vOffset ) + particle:SetVelocity( vNormal * 0 ) + particle:SetDieTime( 10 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(0,255,0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( math.Rand(7,10) ) + particle:SetRoll(0) +end + +WireLib.ThrusterEffectThink.debug_30 = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "decals/cross", vOffset ) + particle:SetVelocity( vNormal * 0 ) + particle:SetDieTime( 30 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(0,255,0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( math.Rand(7,10) ) + particle:SetRoll(0) +end + +WireLib.ThrusterEffectThink.debug_60 = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local particle = emitter:Add( "decals/cross", vOffset ) + particle:SetVelocity( vNormal * 0 ) + particle:SetDieTime( 60 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(0,255,0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( math.Rand(7,10) ) + particle:SetRoll(0) +end + +WireLib.ThrusterEffectThink.souls = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 20 + + local particle = emitter:Add( "sprites/soul", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 0, 50 ) ) + particle:SetDieTime( math.Rand(3,5 ) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 0 ) + particle:SetColor(255,255,255 ) + particle:SetStartSize( 0 ) + particle:SetEndSize( math.Rand(7,10) ) + particle:SetRoll( math.Rand( -90, 90 ) ) +end + +WireLib.ThrusterEffectThink.sperm = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + math.random(0.005,0.00005) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "thrusteraddon/sperm", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 0, 70 ) ) + particle:SetDieTime( math.Rand(3,5 ) ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 200 ) + particle:SetStartSize( 10 ) + particle:SetEndSize( 1 ) + particle:SetRoll( math.random(-180, 180) ) + + local particle2 = emitter:Add( "thrusteraddon/goo", vOffset ) + particle2:SetVelocity( vNormal * 0.5 ) + particle2:SetDieTime( math.Rand(3,5 ) ) + particle2:SetStartAlpha( 100 ) + particle2:SetEndAlpha( 5 ) + particle2:SetColor(255,255,255 ) + particle2:SetStartSize( 5 ) + particle2:SetEndSize( 1 ) + particle2:SetRoll( math.random(-180, 180) ) + + local particle3 = emitter:Add( "thrusteraddon/goo2", vOffset ) + particle3:SetVelocity( vNormal * 0.5 ) + particle3:SetDieTime( math.Rand(3,5 ) ) + particle3:SetStartAlpha(100 ) + particle3:SetEndAlpha( 5 ) + particle3:SetColor(255,255,255 ) + particle3:SetStartSize( 5 ) + particle3:SetEndSize( 1 ) + particle3:SetRoll( math.random(-180, 180) ) +end + +WireLib.ThrusterEffectThink.feather = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + math.random(0.005,0.00005) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 30 + + local particle = emitter:Add( "thrusteraddon/feather"..math.floor(math.random(2,4)).."", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 0, 50 ) ) + particle:SetDieTime( math.Rand(5,7 ) ) + particle:SetStartAlpha( 120 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand( -90, 90 ) ) +end + +WireLib.ThrusterEffectThink.goldstar = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + math.random(0.005,0.00005) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 10 + + local particle = emitter:Add( "thrusteraddon/Goldstar", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 150, 200 ) ) + particle:SetDieTime( math.Rand(0,1 ) ) + particle:SetStartAlpha( 120 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand( -90, 90 ) ) +end + +WireLib.ThrusterEffectThink.candy_cane = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + math.random(0.005,0.00005) + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "thrusteraddon/candy", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 0, 20 ) ) + particle:SetDieTime( math.Rand(5,7 ) ) + particle:SetStartAlpha( 120 ) + particle:SetEndAlpha( 0 ) + particle:SetStartSize( 5 ) + particle:SetEndSize( 5 ) + particle:SetRoll( math.Rand( -90, 90 ) ) +end + +WireLib.ThrusterEffectThink.jetflame = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.0000005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local speed = math.Rand(90,252) + local roll = math.Rand(-90,90) + + local particle = emitter:Add( "particle/fire", vOffset ) + particle:SetVelocity( vNormal * speed ) + particle:SetDieTime( 0.3 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 150 ) + particle:SetStartSize( 15.8 ) + particle:SetEndSize( 9 ) + particle:SetColor( math.Rand(220,255),math.Rand(180,220),55 ) + particle:SetRoll( roll ) + + local particle3 = emitter:Add( "sprites/heatwave", vOffset ) + particle3:SetVelocity( vNormal * speed ) + particle3:SetDieTime( 0.7 ) + particle3:SetStartAlpha( 255 ) + particle3:SetEndAlpha( 255 ) + particle3:SetStartSize( 16 ) + particle3:SetEndSize( 18 ) + particle3:SetColor( 255,255,255 ) + particle3:SetRoll( roll ) + + vOffset = self:LocalToWorld(self:GetOffset()) + + local particle2 = emitter:Add( "particle/fire", vOffset ) + particle2:SetVelocity( vNormal * speed ) + particle2:SetDieTime( 0.2 ) + particle2:SetStartAlpha( 200 ) + particle2:SetEndAlpha( 50 ) + particle2:SetStartSize( 8.8 ) + particle2:SetEndSize( 5 ) + particle2:SetColor( 200,200,200 ) + particle2:SetRoll( roll ) +end + +WireLib.ThrusterEffectThink.jetflame_purple = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.0000005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local speed = math.Rand(90,252) + local roll = math.Rand(-90,90) + + local particle = emitter:Add( "particle/fire", vOffset ) + particle:SetVelocity( vNormal * speed ) + particle:SetDieTime( 0.3 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 150 ) + particle:SetStartSize( 15.8 ) + particle:SetEndSize( 9 ) + particle:SetColor( math.Rand(220,255),55, math.Rand(220,255) ) + particle:SetRoll( roll ) + + local particle3 = emitter:Add( "sprites/heatwave", vOffset ) + particle3:SetVelocity( vNormal * speed ) + particle3:SetDieTime( 0.7 ) + particle3:SetStartAlpha( 255 ) + particle3:SetEndAlpha( 255 ) + particle3:SetStartSize( 16 ) + particle3:SetEndSize( 18 ) + particle3:SetColor( 255,255,255 ) + particle3:SetRoll( roll ) + + vOffset = self:LocalToWorld(self:GetOffset()) + + local particle2 = emitter:Add( "particle/fire", vOffset ) + particle2:SetVelocity( vNormal * speed ) + particle2:SetDieTime( 0.2 ) + particle2:SetStartAlpha( 200 ) + particle2:SetEndAlpha( 50 ) + particle2:SetStartSize( 8.8 ) + particle2:SetEndSize( 5 ) + particle2:SetColor( 200,200,200 ) + particle2:SetRoll( roll ) +end + +WireLib.ThrusterEffectThink.jetflame_red = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.0000005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local speed = math.Rand(90,252) + local roll = math.Rand(-90,90) + + local particle = emitter:Add( "particle/fire", vOffset ) + particle:SetVelocity( vNormal * speed ) + particle:SetDieTime( 0.3 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 150 ) + particle:SetStartSize( 15.8 ) + particle:SetEndSize( 9 ) + particle:SetColor( math.Rand(220,255),55,55 ) + particle:SetRoll( roll ) + + local particle3 = emitter:Add( "sprites/heatwave", vOffset ) + particle3:SetVelocity( vNormal * speed ) + particle3:SetDieTime( 0.7 ) + particle3:SetStartAlpha( 255 ) + particle3:SetEndAlpha( 255 ) + particle3:SetStartSize( 16 ) + particle3:SetEndSize( 18 ) + particle3:SetColor( 255,255,255 ) + particle3:SetRoll( roll ) + + vOffset = self:LocalToWorld(self:GetOffset()) + + local particle2 = emitter:Add( "particle/fire", vOffset ) + particle2:SetVelocity( vNormal * speed ) + particle2:SetDieTime( 0.2 ) + particle2:SetStartAlpha( 200 ) + particle2:SetEndAlpha( 50 ) + particle2:SetStartSize( 8.8 ) + particle2:SetEndSize( 5 ) + particle2:SetColor( 200,200,200 ) + particle2:SetRoll( roll ) +end + +WireLib.ThrusterEffectThink.jetflame_blue = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.0000005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local speed = math.Rand(90,252) + local roll = math.Rand(-90,90) + + local particle = emitter:Add( "particle/fire", vOffset ) + particle:SetVelocity( vNormal * speed ) + particle:SetDieTime( 0.3 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 150 ) + particle:SetStartSize( 15.8 ) + particle:SetEndSize( 9 ) + particle:SetColor( 55,55, math.Rand(220,255) ) + particle:SetRoll( roll ) + + local particle3 = emitter:Add( "sprites/heatwave", vOffset ) + particle3:SetVelocity( vNormal * speed ) + particle3:SetDieTime( 0.7 ) + particle3:SetStartAlpha( 255 ) + particle3:SetEndAlpha( 255 ) + particle3:SetStartSize( 16 ) + particle3:SetEndSize( 18 ) + particle3:SetColor( 255,255,255 ) + particle3:SetRoll( roll ) + + vOffset = self:LocalToWorld(self:GetOffset()) + + local particle2 = emitter:Add( "particle/fire", vOffset ) + particle2:SetVelocity( vNormal * speed ) + particle2:SetDieTime( 0.2 ) + particle2:SetStartAlpha( 200 ) + particle2:SetEndAlpha( 50 ) + particle2:SetStartSize( 8.8 ) + particle2:SetEndSize( 5 ) + particle2:SetColor( 200,200,200 ) + particle2:SetRoll( roll ) +end + +WireLib.ThrusterEffectThink.balls_firecolors = function(self) + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.025 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + vOffset = vOffset + VectorRand() * 2 + + local particle = emitter:Add( "sprites/sent_ball", vOffset ) + particle:SetVelocity( vNormal * 80 ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(math.random(220,255),math.random(100,200),0) + particle:SetStartSize( 4 ) + particle:SetEndSize( 0 ) + particle:SetRoll( 0 ) +end + +WireLib.ThrusterEffectThink.balls_random = function(self) + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.025 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + vOffset = vOffset + VectorRand() * 2 + + local particle = emitter:Add( "sprites/sent_ball", vOffset ) + particle:SetVelocity( vNormal * 80 ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(math.random(0,255),math.random(0,255),math.random(0,255)) + particle:SetStartSize( 4 ) + particle:SetEndSize( 0 ) + particle:SetRoll( 0 ) +end + +WireLib.ThrusterEffectThink.balls = function(self) + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.025 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + vOffset = vOffset + VectorRand() * 2 + + local particle = emitter:Add( "sprites/sent_ball", vOffset ) + particle:SetVelocity( vNormal * 80 ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(self:GetColor()) + particle:SetStartSize( 4 ) + particle:SetEndSize( 0 ) + particle:SetRoll( 0 ) +end + +WireLib.ThrusterEffectThink.plasma_rings = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "sprites/magic", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 50, 80 ) ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetStartSize( math.Rand( 3,5 ) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) +end + +WireLib.ThrusterEffectThink.magic_firecolors = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "sprites/gmdm_pickups/light", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 50, 80 ) ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(math.random(220,255),math.random(100,200),0) + particle:SetStartSize( math.Rand( 1, 3 ) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) +end + +WireLib.ThrusterEffectThink.magic = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "sprites/gmdm_pickups/light", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 50, 80 ) ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetStartSize( math.Rand( 1, 3 ) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) +end + +WireLib.ThrusterEffectThink.magic_diy = function(self) + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "sprites/gmdm_pickups/light", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 50, 80 ) ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor(self:GetColor()) + particle:SetStartSize( math.Rand( 1, 3 ) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) +end + +WireLib.ThrusterEffectThink.magic_color = function(self) + + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "sprites/gmdm_pickups/light", vOffset ) + particle:SetVelocity( vNormal * math.Rand( 50, 80) ) + particle:SetDieTime( 1 ) + particle:SetStartAlpha( 255 ) + particle:SetEndAlpha( 255 ) + particle:SetColor( math.random(0,255),math.random(0,255),math.random(0,255)) + particle:SetStartSize( math.Rand( 1, 3 ) ) + particle:SetEndSize( 0 ) + particle:SetRoll( math.Rand( -0.2, 0.2 ) ) +end + +WireLib.ThrusterEffectDraw.rings = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetMagnitude(0) -- growth rate + util.Effect( "thruster_ring", effectdata ) + +end + +WireLib.ThrusterEffectDraw.tesla = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 1 ) + effectdata:SetScale( 1 ) + util.Effect( "TeslaZap ", effectdata ) + +end + +WireLib.ThrusterEffectDraw.blood = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 1 ) + effectdata:SetScale( 1 ) + util.Effect( "BloodImpact", effectdata ) + +end + +WireLib.ThrusterEffectDraw.some_sparks = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 1 ) + effectdata:SetScale( 1 ) + util.Effect( "StunstickImpact", effectdata ) + +end + +WireLib.ThrusterEffectDraw.spark_fountain = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 1 ) + effectdata:SetScale( 1 ) + util.Effect( "ManhackSparks", effectdata ) + +end + +WireLib.ThrusterEffectDraw.more_sparks = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 1 ) + effectdata:SetScale( 1 ) + util.Effect( "cball_explode", effectdata ) + +end + +WireLib.ThrusterEffectDraw.water_small = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 2 ) + effectdata:SetScale( 2 ) + util.Effect( "watersplash", effectdata ) + +end + +WireLib.ThrusterEffectDraw.water_medium = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 6 ) + effectdata:SetScale( 6 ) + + util.Effect( "watersplash", effectdata ) + +end + +WireLib.ThrusterEffectDraw.water_big = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 10 ) + effectdata:SetScale( 10 ) + util.Effect( "watersplash", effectdata ) + +end + +WireLib.ThrusterEffectDraw.water_huge = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 18 ) + effectdata:SetScale( 18 ) + util.Effect( "watersplash", effectdata ) + +end + +WireLib.ThrusterEffectDraw.striderblood_small = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 0.1 ) + effectdata:SetScale( 0.1 ) + util.Effect( "StriderBlood", effectdata ) + +end + +WireLib.ThrusterEffectDraw.striderblood_medium = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 0.7 ) + effectdata:SetScale( 0.7 ) + + util.Effect( "StriderBlood", effectdata ) + +end + +WireLib.ThrusterEffectDraw.striderblood_big = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 1.15 ) + effectdata:SetScale( 1.15 ) + util.Effect( "StriderBlood", effectdata ) + +end + +WireLib.ThrusterEffectDraw.striderblood_huge = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.05 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetRadius( 2 ) + effectdata:SetScale( 2 ) + util.Effect( "StriderBlood", effectdata ) + +end + +WireLib.ThrusterEffectDraw.rings_grow = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetMagnitude(0.08) -- growth rate + util.Effect("thruster_ring", effectdata) + +end + +WireLib.ThrusterEffectDraw.rings_grow_rings = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetMagnitude(0.08) -- growth rate + util.Effect("thruster_ring", effectdata) + effectdata:SetMagnitude(0.06) + util.Effect("thruster_ring", effectdata) + effectdata:SetMagnitude(0.04) + util.Effect("thruster_ring", effectdata) + effectdata:SetMagnitude(0.02) + util.Effect("thruster_ring", effectdata) + effectdata:SetMagnitude(0) + util.Effect("thruster_ring", effectdata) +end + +WireLib.ThrusterEffectDraw.rings_shrink = function(self) + + self.RingTimer = self.RingTimer or 0 + if ( self.RingTimer > CurTime() ) then return end + self.RingTimer = CurTime() + 0.00005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + + local effectdata = EffectData() + effectdata:SetOrigin( vOffset ) + effectdata:SetNormal( vNormal ) + effectdata:SetMagnitude(-0.02) -- growth rate + util.Effect("thruster_ring", effectdata) + +end + +WireLib.ThrusterEffectThink.bubble = function(self) + self.SmokeTimer = self.SmokeTimer or 0 + if ( self.SmokeTimer > CurTime() ) then return end + + self.SmokeTimer = CurTime() + 0.005 + + local vOffset = self:LocalToWorld(self:GetOffset()) + local vNormal = self:CalcNormal() + vOffset = vOffset + VectorRand() * 5 + + local particle = emitter:Add( "effects/bubble", vOffset ) + vNormal.x = vNormal.x * 0.7 + vNormal.y = vNormal.y * 0.7 + vNormal.z = (vNormal.z+1) * 20 + particle:SetVelocity( vNormal) + particle:SetDieTime( 2 ) + particle:SetStartAlpha( 125 ) + particle:SetEndAlpha( 125 ) + particle:SetColor(255,255,255) + particle:SetStartSize( 7 ) + particle:SetEndSize( 0 ) + particle:SetRoll( 0 ) + + +end diff --git a/garrysmod/addons/feature-wire/lua/wire/client/wire_expression2_browser.lua b/garrysmod/addons/feature-wire/lua/wire/client/wire_expression2_browser.lua new file mode 100644 index 0000000..c063971 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/wire_expression2_browser.lua @@ -0,0 +1,427 @@ +-- Made by Shandolum - Shandolum@gmail.com +-- Overhauled by Grocel on request from Divran + +local PANEL = {} + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +local function GetFileName(name) + local name = string.Replace(name, ".txt", "") + return string.Replace(name, "/", "") +end + +local function InternalDoClick(self) + self:GetRoot():SetSelectedItem(self) + if (self:DoClick()) then return end + if (self:GetRoot():DoClick(self)) then return end +end + +local function InternalDoRightClick(self) + self:GetRoot():SetSelectedItem(self) + if (self:DoRightClick()) then return end + if (self:GetRoot():DoRightClick(self)) then return end +end + +local function fileName(filepath) + return string.match(filepath, "[/\\]?([^/\\]*)$") +end + +local string_find = string.find +local string_lower = string.lower +function PANEL:Search( str, foldername, fullpath, parentfullpath, first_recursion ) + if not self.SearchFolders[fullpath] then + self.SearchFolders[fullpath] = (self.SearchFolders[parentfullpath] or self.Folders):AddNode( foldername ) + + local files, folders = file.Find( fullpath .. "/*", "DATA" ) + + local node = self.SearchFolders[fullpath] + if fullpath == self.startfolder then self.Root = node end -- get root + node.Icon:SetImage( "icon16/arrow_refresh.png" ) + node:SetExpanded( true ) + + local myresults = 0 + for i=1,#files do + if string_find( string_lower( files[i] ), str, 1, true ) ~= nil then + local filenode = node:AddNode( files[i], "icon16/page_white.png" ) + filenode:SetFileName( fullpath .. "/" .. files[i] ) + myresults = myresults + 1 + end + + coroutine.yield() + end + + if #folders == 0 then + if myresults == 0 then + if node ~= self.Root then node:Remove() end + if first_recursion then + coroutine.yield( false, myresults ) + else + return false, myresults + end + end + + node.Icon:SetImage( "icon16/folder.png" ) + if first_recursion then + coroutine.yield( true, myresults ) + else + return true, myresults + end + else + for i=1,#folders do + local b, res = self:Search( str, folders[i], fullpath .. "/" .. folders[i], fullpath ) + if b then + myresults = myresults + res + end + + coroutine.yield() + end + + + if myresults > 0 then + node.Icon:SetImage( "icon16/folder.png" ) + if first_recursion then + coroutine.yield( true, myresults ) + else + return true, myresults + end + else + if node ~= self.Root then node:Remove() end + if first_recursion then + coroutine.yield( false, myresults ) + else + return false, myresults + end + end + end + end + + if first_recursion then + coroutine.yield( false, 0 ) + else + return false, 0 + end +end + +function PANEL:CheckSearchResults( status, bool, count ) + if bool ~= nil and count ~= nil then -- we're done searching + if count == 0 then + local node = self.Root:AddNode( "No results" ) + node.Icon:SetImage( "icon16/exclamation.png" ) + self.Root.Icon:SetImage( "icon16/folder.png" ) + end + timer.Remove( "wire_expression2_search" ) + return true + elseif not status then -- something went wrong, abort + timer.Remove( "wire_expression2_search" ) + return true + end +end + +function PANEL:StartSearch( str ) + self:UpdateFolders( true ) + + self.SearchFolders = {} + + local crt = coroutine.create( self.Search ) + local status, bool, count = coroutine.resume( crt, self, str, self.startfolder, self.startfolder, "", true ) + self:CheckSearchResults( status, bool, count ) + + timer.Create( "wire_expression2_search", 0, 0, function() + for i=1,100 do -- Load loads of files/folders at a time + local status, bool, count = coroutine.resume( crt ) + + if self:CheckSearchResults( status, bool, count ) then + return -- exit loop etc + end + end + end ) +end + +function PANEL:Init() + self:SetDrawBackground(false) + + self.SearchBox = vgui.Create( "DTextEntry", self ) + self.SearchBox:Dock( TOP ) + self.SearchBox:DockMargin( 0,0,0,0 ) + self.SearchBox:SetValue( "Search..." ) + + local clearsearch = vgui.Create( "DImageButton", self.SearchBox ) + clearsearch:SetMaterial( "icon16/cross.png" ) + local src = self.SearchBox + function clearsearch:DoClick() + src:SetValue( "" ) + src:OnEnter() + src:SetValue( "Search..." ) + end + clearsearch:DockMargin( 2,2,4,2 ) + clearsearch:Dock( RIGHT ) + clearsearch:SetSize( 14, 10 ) + clearsearch:SetVisible( false ) + + + local old = self.SearchBox.OnGetFocus + function self.SearchBox:OnGetFocus() + if self:GetValue() == "Search..." then -- If "Search...", erase it + self:SetValue( "" ) + end + old( self ) + end + + -- On lose focus + local old = self.SearchBox.OnLoseFocus + function self.SearchBox:OnLoseFocus() + if self:GetValue() == "" then -- if empty, reset "Search..." text + timer.Simple( 0, function() self:SetValue( "Search..." ) end ) + end + old( self ) + end + + function self.SearchBox.OnEnter() + local str = self.SearchBox:GetValue() + + if str ~= "" then + self:StartSearch( string.Replace( string.lower( str ), " ", "_" ) ) + + clearsearch:SetVisible( true ) + else + timer.Remove( "wire_expression2_search" ) + self:UpdateFolders() + clearsearch:SetVisible( false ) + end + end + + self.Update = vgui.Create("DButton", self) + self.Update:SetTall(20) + self.Update:Dock(BOTTOM) + self.Update:DockMargin(0, 0, 0, 0) + self.Update:SetText("Update") + self.Update.DoClick = function(button) + self:UpdateFolders() + end + + self.Folders = vgui.Create("DTree", self) + self.Folders:Dock(FILL) + self.Folders:DockMargin(0, 0, 0, 0) + + self.panelmenu = {} + self.filemenu = {} + self.foldermenu = {} + self.lastClick = CurTime() + + self:AddRightClick(self.filemenu, nil, "Open", function() + self:OnFileOpen(self.File:GetFileName(), false) + end) + self:AddRightClick(self.filemenu, nil, "Open in New Tab", function() + self:OnFileOpen(self.File:GetFileName(), true) + end) + self:AddRightClick(self.filemenu, nil, "*SPACER*") + self:AddRightClick(self.filemenu, nil, "Rename to..", function() + local fname = string.StripExtension(fileName(self.File:GetFileName())) + Derma_StringRequestNoBlur("Rename File \"" .. fname .. "\"", "Rename file " .. fname, fname, + function(strTextOut) + -- Renaming starts in the garrysmod folder now, in comparison to other commands that start in the data folder. + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) .. ".txt" + local newFileName = string.GetPathFromFilename(self.File:GetFileName()) .. "/" .. strTextOut + if file.Exists(newFileName, "DATA") then + WireLib.AddNotify("File already exists (" .. strTextOut .. ")", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + elseif not file.Rename(self.File:GetFileName(), newFileName) then + WireLib.AddNotify("Rename was not successful", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + end + self:UpdateFolders() + end) + end) + self:AddRightClick(self.filemenu, nil, "Copy to..", function() + local fname = string.StripExtension(fileName(self.File:GetFileName())) + Derma_StringRequestNoBlur("Copy File \"" .. fname .. "\"", "Copy File to...", fname, + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(string.GetPathFromFilename(self.File:GetFileName()) .. "/" .. strTextOut .. ".txt", file.Read(self.File:GetFileName())) + self:UpdateFolders() + end) + end) + self:AddRightClick(self.filemenu, nil, "*SPACER*") + self:AddRightClick(self.filemenu, nil, "New File", function() + Derma_StringRequestNoBlur("New File in \"" .. string.GetPathFromFilename(self.File:GetFileName()) .. "\"", "Create new file", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(string.GetPathFromFilename(self.File:GetFileName()) .. "/" .. strTextOut .. ".txt", "") + self:UpdateFolders() + end) + end) + self:AddRightClick(self.filemenu, nil, "Delete", function() + Derma_Query("Delete this file?", "Delete", + "Delete", function() + if (file.Exists(self.File:GetFileName(), "DATA")) then + file.Delete(self.File:GetFileName()) + self:UpdateFolders() + end + end, + "Cancel") + end) + + self:AddRightClick(self.foldermenu, nil, "New File..", function() + Derma_StringRequestNoBlur("New File in \"" .. self.File:GetFolder() .. "\"", "Create new file", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(self.File:GetFolder() .. "/" .. strTextOut .. ".txt", "") + self:UpdateFolders() + end) + end) + self:AddRightClick(self.foldermenu, nil, "New Folder..", function() + Derma_StringRequestNoBlur("new folder in \"" .. self.File:GetFolder() .. "\"", "Create new folder", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.CreateDir(self.File:GetFolder() .. "/" .. strTextOut) + self:UpdateFolders() + end) + end) + self:AddRightClick(self.foldermenu, nil, "Rename to..", function() + --get full path and remove the current folder name + local fullpath = string.Split(self.File:GetFolder(),"/") + local oldFolderName = table.remove(fullpath) + + Derma_StringRequestNoBlur("Rename folder \"" .. self.File:GetFolder() .. "\"", "Rename Folder", oldFolderName, + function(strTextOut) + -- Renaming starts in the garrysmod folder now, in comparison to other commands that start in the data folder. + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + + --we are editing the root folder ("expression2" folder node) + if #fullpath == 0 or #strTextOut == 0 then + return + end + + local newFolderPath = table.concat(fullpath,"/") .. "/" .. strTextOut + if file.Exists(newFolderPath, "DATA") then + WireLib.AddNotify("Folder already exists (" .. strTextOut .. ")", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + elseif not file.Rename(self.File:GetFolder(), newFolderPath) then + WireLib.AddNotify("Rename was not successful", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + end + self:UpdateFolders() + end) + end) + + self:AddRightClick(self.panelmenu, nil, "New File..", function() + Derma_StringRequestNoBlur("New File in \"" .. self.File:GetFolder() .. "\"", "Create new file", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(self.File:GetFolder() .. "/" .. strTextOut .. ".txt", "") + self:UpdateFolders() + end) + end) + self:AddRightClick(self.panelmenu, nil, "New Folder..", function() + Derma_StringRequestNoBlur("new folder in \"" .. self.File:GetFolder() .. "\"", "Create new folder", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.CreateDir(self.File:GetFolder() .. "/" .. strTextOut) + self:UpdateFolders() + end) + end) +end + +function PANEL:OnFileOpen(filepath, newtab) + error("Please override wire_expression2_browser:OnFileOpen(filepath, newtab)", 0) +end + +function PANEL:UpdateFolders( empty ) + self.Folders:Clear(true) + if IsValid(self.Root) then + self.Root:Remove() + end + + if not empty then + self.Root = self.Folders.RootNode:AddFolder(self.startfolder, self.startfolder, "DATA", true) + self.Root:SetExpanded(true) + end + + self.Folders.DoClick = function(tree, node) + if self.File == node and CurTime() <= self.lastClick + 0.5 then + self:OnFileOpen(node:GetFileName()) + elseif self.OpenOnSingleClick then + self.OpenOnSingleClick:LoadFile(node:GetFileName()) + end + self.File = node + self.lastClick = CurTime() + return true + end + self.Folders.DoRightClick = function(tree, node) + self.File = node + if node:GetFileName() then + self:OpenMenu(self.filemenu) + else + self:OpenMenu(self.foldermenu) + end + return true + end + + self:OnFolderUpdate(self.startfolder) +end + +function PANEL:GetFileName() + if not IsValid(self.File) then return end + + return self.File:GetFileName() +end + +function PANEL:GetFileNode() + return self.File +end + +function PANEL:OpenMenu(menu) + if not menu or not IsValid(self.Folders) then return end + if #menu < 1 then return end + + self.Menu = vgui.Create("DMenu", self.Folders) + for k, v in pairs(menu) do + local name, option = v[1], v[2] + if (name == "*SPACER*") then + self.Menu:AddSpacer() + else + self.Menu:AddOption(name, option) + end + end + self.Menu:Open() +end + +function PANEL:AddRightClick(menu, pos, name, option) + if not menu then menu = {} end + if not pos then pos = #menu + 1 end + + if menu[pos] then + table.insert(menu, pos, { name, option }) + return + end + + menu[pos] = { name, option } +end + +function PANEL:RemoveRightClick(name) + for k, v in pairs(self.filemenu) do + if (v[1] == name) then + self.filemenu[k] = nil + break + end + end +end + + +function PANEL:Setup(folder) + self.startfolder = folder + self:UpdateFolders() +end + +function PANEL:OnFolderUpdate(folder) + -- override +end + +PANEL.Refresh = PANEL.UpdateFolders -- self:Refresh() is common + +vgui.Register("wire_expression2_browser", PANEL, "DPanel") diff --git a/garrysmod/addons/feature-wire/lua/wire/client/wire_filebrowser.lua b/garrysmod/addons/feature-wire/lua/wire/client/wire_filebrowser.lua new file mode 100644 index 0000000..2af9cbd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/wire_filebrowser.lua @@ -0,0 +1,681 @@ +// A file browser panel, used by the sound browser. +// Can be used for any file type, recommend for huge file numbers. +// Made by Grocel. + +local PANEL = {} + +AccessorFunc( PANEL, "m_strRootName", "RootName" ) // name of the root Root +AccessorFunc( PANEL, "m_strRootPath", "RootPath" ) // path of the root Root +AccessorFunc( PANEL, "m_strWildCard", "WildCard" ) // "GAME", "DATA" etc. +AccessorFunc( PANEL, "m_tFilter", "FileTyps" ) // "*.wav", "*.mdl", {"*.vmt", "*.vtf"} etc. + +AccessorFunc( PANEL, "m_strOpenPath", "OpenPath" ) // open path +AccessorFunc( PANEL, "m_strOpenFile", "OpenFile" ) // open path+file +AccessorFunc( PANEL, "m_strOpenFilename", "OpenFilename" ) // open file + +AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once +AccessorFunc( PANEL, "m_nMaxItemsPerPage", "MaxItemsPerPage" ) // how may items per page +AccessorFunc( PANEL, "m_nPage", "Page" ) // Page to show + +local invalid_chars = { + ["\n"] = "", + ["\r"] = "", + ["\\"] = "/", + ["//"] = "/", + //["/.svn"] = "", // Disallow access to .svn folders. (Not needed.) + //["/.git"] = "", // Disallow access to .git folders. (Not needed.) +} + +local function ConnectPathes(path1, path2) + local path = "" + + if (isstring(path1) and path1 ~= "") then + path = path1 + if (isstring(path2) and path2 ~= "") then + path = path1.."/"..path2 + end + else + if (isstring(path2) and path2 ~= "") then + path = path2 + end + end + + return path +end + +local function PathFilter(Folder, TxtPanel, Root) + if (!isstring(Folder) or Folder == "") then return end + + local ValidFolder = Folder + + //local ValidFolder = string.lower(Folder) // for .svn and .git filters. + for k, v in pairs(invalid_chars) do + for i = 1, #string.Explode(k, ValidFolder) do + if (!string.match(ValidFolder, k)) then break end + + ValidFolder = string.gsub(ValidFolder, k, v) + end + end + + ValidFolder = string.Trim(ValidFolder) + /*if (string.sub(ValidFolder, 0, 4) == ".svn") then // Disallow access to .svn folders. (Not needed.) + ValidFolder = string.sub(ValidFolder, -4) + if (ValidFolder == ".svn") then + ValidFolder = "" + end + end*/ + + /*if (string.sub(ValidFolder, 0, 4) == ".git") then // Disallow access to .git folders. (Not needed.) + ValidFolder = string.sub(ValidFolder, -4) + if (ValidFolder == ".git") then + ValidFolder = "" + end + end*/ + + ValidFolder = string.Trim(ValidFolder, "/") + + if (IsValid(TxtPanel)) then + TxtPanel:SetText(ValidFolder) + end + + local Dirs = #string.Explode("/", ValidFolder) + for i = 1, Dirs do + if (!file.IsDir(ConnectPathes(Root, ValidFolder), "GAME")) then + ValidFolder = string.GetPathFromFilename(ValidFolder) + ValidFolder = string.Trim(ValidFolder, "/") + end + end + + ValidFolder = string.Trim(ValidFolder, "/") + + if (ValidFolder == "") then return end + return ValidFolder +end + +local function EnableButton(button, bool) + button:SetEnabled(bool) + button:SetMouseInputEnabled(bool) +end + +local function BuildFileList(path, filter, wildcard) + local files = {} + + if (istable(filter)) then + for k, v in ipairs(filter) do + table.Add(files, file.Find(ConnectPathes(path, v), wildcard or "GAME")) + end + else + table.Add(files, file.Find(ConnectPathes(path, tostring(filter)), wildcard or "GAME")) + end + + table.sort(files) + + return files +end + +local function NavigateToFolder(self, path) + if (!IsValid(self)) then return end + + path = ConnectPathes(self.m_strRootPath, path) + + local root = self.Tree:Root() + if (!IsValid(root)) then return end + if (!IsValid(root.ChildNodes)) then return end + + local nodes = root.ChildNodes:GetChildren() + local lastnode = nil + + local nodename = "" + + self.NotUserPressed = true + local dirs = string.Explode("/", path) + for k, v in ipairs(dirs) do + if (nodename == "") then + nodename = string.lower(v) + else + nodename = nodename .. "/" .. string.lower(v) + if (!IsValid(lastnode)) then continue end + if (!IsValid(lastnode.ChildNodes)) then continue end + + nodes = lastnode.ChildNodes:GetChildren() + end + + local found = false + for _, node in pairs(nodes) do + if (!IsValid(node)) then continue end + + local path = string.lower(node.m_strFolder) + if ( nodename == "" ) then break end + + if ( path ~= nodename or found) then + node:SetExpanded(false) + continue + end + + if (k == #dirs) then // just select the last one + self.Tree:SetSelectedItem(node) + end + + node:SetExpanded(true) + lastnode = node + found = true + end + end + + self.NotUserPressed = false +end + +local function ShowFolder(self, path) + if (!IsValid(self)) then return end + + self.m_strOpenPath = path + path = ConnectPathes(self.m_strRootPath, path) + self.oldpage = nil + + self.Files = BuildFileList(path, self.m_tFilter, self.m_strWildCard) + + self.m_nPage = 0 + self.m_nPageCount = math.ceil(#self.Files / self.m_nMaxItemsPerPage) + + self.PageMode = self.m_nPageCount > 1 + self.PageChoosePanel:SetVisible(self.PageMode) + + if (self.m_nPageCount <= 0 or !self.PageMode) then + self.m_nPageCount = 1 + self:SetPage(1) + return + end + + self.PageChooseNumbers:Clear(true) + self.PageChooseNumbers.Buttons = {} + + for i=1, self.m_nPageCount do + self.PageChooseNumbers.Buttons[i] = self.PageChooseNumbers:Add("DButton") + local button = self.PageChooseNumbers.Buttons[i] + + button:SetWide(self.PageButtonSize) + button:Dock(LEFT) + button:SetText(tostring(i)) + button:SetVisible(false) + button:SetToolTip("Page " .. i .. " of " .. self.m_nPageCount) + + button.DoClick = function(panel) + self:SetPage(i) + self:LayoutPages(true) + end + end + + self:SetPage(1) +end + +--[[--------------------------------------------------------- + Name: Init +-----------------------------------------------------------]] +function PANEL:Init() + self.TimedpairsName = "wire_filebrowser_items_" .. tostring({}) + + self.PageButtonSize = 20 + + self:SetListSpeed(6) + self:SetMaxItemsPerPage(200) + + self.m_nPageCount = 1 + + self.m_strOpenPath = nil + self.m_strOpenFile = nil + self.m_strOpenFilename = nil + + self:SetDrawBackground(false) + + self.FolderPathPanel = self:Add("DPanel") + self.FolderPathPanel:DockMargin(0, 0, 0, 3) + self.FolderPathPanel:SetTall(20) + self.FolderPathPanel:Dock(TOP) + self.FolderPathPanel:SetDrawBackground(false) + + self.FolderPathText = self.FolderPathPanel:Add("DTextEntry") + self.FolderPathText:DockMargin(0, 0, 3, 0) + self.FolderPathText:Dock(FILL) + self.FolderPathText.OnEnter = function(panel) + self:SetOpenPath(panel:GetValue()) + end + + self.RefreshIcon = self.FolderPathPanel:Add("DImageButton") // The Folder Button. + self.RefreshIcon:SetImage("icon16/arrow_refresh.png") + self.RefreshIcon:SetWide(20) + self.RefreshIcon:Dock(RIGHT) + self.RefreshIcon:SetToolTip("Refresh") + self.RefreshIcon:SetStretchToFit(false) + self.RefreshIcon.DoClick = function() + self:Refresh() + end + + self.FolderPathIcon = self.FolderPathPanel:Add("DImageButton") // The Folder Button. + self.FolderPathIcon:SetImage("icon16/folder_explore.png") + self.FolderPathIcon:SetWide(20) + self.FolderPathIcon:Dock(RIGHT) + self.FolderPathIcon:SetToolTip("Open Folder") + self.FolderPathIcon:SetStretchToFit(false) + + self.FolderPathIcon.DoClick = function() + self.FolderPathText:OnEnter() + end + + self.NotUserPressed = false + self.Tree = vgui.Create( "DTree" ) + self.Tree:SetClickOnDragHover(false) + self.Tree.OnNodeSelected = function( parent, node ) + local path = node.m_strFolder + + if ( !path ) then return end + path = string.sub(path, #self.m_strRootPath+1) + path = string.Trim(path, "/") + + if (!self.NotUserPressed) then + self.FolderPathText:SetText(path) + end + + if (self.m_strOpenPath == path) then return end + ShowFolder(self, path) + end + + self.PagePanel = vgui.Create("DPanel") + self.PagePanel:SetDrawBackground(false) + + self.PageChoosePanel = self.PagePanel:Add("DPanel") + self.PageChoosePanel:DockMargin(0, 0, 0, 0) + self.PageChoosePanel:SetTall(self.PageButtonSize) + self.PageChoosePanel:Dock(BOTTOM) + self.PageChoosePanel:SetDrawBackground(false) + self.PageChoosePanel:SetVisible(false) + + self.PageLastLeftButton = self.PageChoosePanel:Add("DButton") + self.PageLastLeftButton:SetWide(self.PageButtonSize) + self.PageLastLeftButton:Dock(LEFT) + self.PageLastLeftButton:SetText("<<") + self.PageLastLeftButton.DoClick = function(panel) + self:SetPage(1) + end + + self.PageLastRightButton = self.PageChoosePanel:Add("DButton") + self.PageLastRightButton:SetWide(self.PageButtonSize) + self.PageLastRightButton:Dock(RIGHT) + self.PageLastRightButton:SetText(">>") + self.PageLastRightButton.DoClick = function(panel) + self:SetPage(self.m_nPageCount) + end + + self.PageLeftButton = self.PageChoosePanel:Add("DButton") + self.PageLeftButton:SetWide(self.PageButtonSize) + self.PageLeftButton:Dock(LEFT) + self.PageLeftButton:SetText("<") + self.PageLeftButton.DoClick = function(panel) + if (self.m_nPage <= 1 or !self.PageMode) then + self.m_nPage = 1 + return + end + + self:SetPage(self.m_nPage - 1) + end + + self.PageRightButton = self.PageChoosePanel:Add("DButton") + self.PageRightButton:SetWide(self.PageButtonSize) + self.PageRightButton:Dock(RIGHT) + self.PageRightButton:SetText(">") + self.PageRightButton.DoClick = function(panel) + if (self.m_nPage >= self.m_nPageCount or !self.PageMode) then + self.m_nPage = self.m_nPageCount + return + end + + self:SetPage(self.m_nPage + 1) + end + + self.PageChooseNumbers = self.PageChoosePanel:Add("DPanel") + self.PageChooseNumbers:DockMargin(0, 0, 0, 0) + self.PageChooseNumbers:SetSize(self.PageChoosePanel:GetWide()-60, self.PageChoosePanel:GetTall()) + self.PageChooseNumbers:Center() + self.PageChooseNumbers:SetDrawBackground(false) + + self.PageLoadingProgress = self.PagePanel:Add("DProgress") + self.PageLoadingProgress:DockMargin(0, 0, 0, 0) + self.PageLoadingProgress:SetTall(self.PageButtonSize) + self.PageLoadingProgress:Dock(BOTTOM) + self.PageLoadingProgress:SetVisible(false) + + self.PageLoadingLabel = self.PageLoadingProgress:Add("DLabel") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + self.PageLoadingLabel:SetText("") + self.PageLoadingLabel:SetPaintBackground(false) + self.PageLoadingLabel:SetDark(true) + + + self.List = self.PagePanel:Add( "DListView" ) + self.List:Dock( FILL ) + self.List:SetMultiSelect(false) + local Column = self.List:AddColumn("Name") + Column:SetMinWidth(150) + Column:SetWide(200) + + self.List.OnRowSelected = function(parent, id, line) + local name = line.m_strFilename + local path = line.m_strPath + local file = line.m_strFile + self.m_strOpenFilename = name + self.m_strOpenFile = file + + self:DoClick(file, path, name, parent, line) + end + + self.List.DoDoubleClick = function(parent, id, line) + local name = line.m_strFilename + local path = line.m_strPath + local file = line.m_strFile + self.m_strOpenFilename = name + self.m_strOpenFile = file + + self:DoDoubleClick(file, path, name, parent, line) + end + + self.List.OnRowRightClick = function(parent, id, line) + local name = line.m_strFilename + local path = line.m_strPath + local file = line.m_strFile + self.m_strOpenFilename = name + self.m_strOpenFile = file + + self:DoRightClick(file, path, name, parent, line) + end + + self.SplitPanel = self:Add( "DHorizontalDivider" ) + self.SplitPanel:Dock( FILL ) + self.SplitPanel:SetLeft(self.Tree) + self.SplitPanel:SetRight(self.PagePanel) + self.SplitPanel:SetLeftWidth(200) + self.SplitPanel:SetLeftMin(150) + self.SplitPanel:SetRightMin(300) + self.SplitPanel:SetDividerWidth(3) +end + +function PANEL:Refresh() + local file = self:GetOpenFile() + local page = self:GetPage() + + self.bSetup = self:Setup() + + self:SetOpenFile(file) + self:SetPage(page) +end + + +function PANEL:UpdatePageToolTips() + self.PageLeftButton:SetToolTip("Previous Page (" .. self.m_nPage - 1 .. " of " .. self.m_nPageCount .. ")") + self.PageRightButton:SetToolTip("Next Page (" .. self.m_nPage + 1 .. " of " .. self.m_nPageCount .. ")") + + self.PageLastRightButton:SetToolTip("Last Page (" .. self.m_nPageCount .. " of " .. self.m_nPageCount .. ")") + self.PageLastLeftButton:SetToolTip("First Page (1 of " .. self.m_nPageCount .. ")") +end + +function PANEL:LayoutPages(forcelayout) + if (!self.PageChoosePanel:IsVisible()) then + self.oldpage = nil + return + end + + local x, y = self.PageRightButton:GetPos() + local Wide = x - self.PageLeftButton:GetWide()-40 + if (Wide <= 0 or forcelayout) then + self.oldpage = nil + self:InvalidateLayout() + return + end + if (self.oldpage == self.m_nPage and self.oldpage and self.m_nPage) then return end + self.oldpage = self.m_nPage + + if (self.m_nPage >= self.m_nPageCount) then + EnableButton(self.PageLeftButton, true) + EnableButton(self.PageRightButton, false) + EnableButton(self.PageLastLeftButton, true) + EnableButton(self.PageLastRightButton, false) + elseif (self.m_nPage <= 1) then + EnableButton(self.PageLeftButton, false) + EnableButton(self.PageRightButton, true) + EnableButton(self.PageLastLeftButton, false) + EnableButton(self.PageLastRightButton, true) + else + EnableButton(self.PageLeftButton, true) + EnableButton(self.PageRightButton, true) + EnableButton(self.PageLastLeftButton, true) + EnableButton(self.PageLastRightButton, true) + end + + local ButtonCount = math.ceil(math.floor(Wide/self.PageButtonSize)/2) + local pagepos = math.Clamp(self.m_nPage, ButtonCount, self.m_nPageCount-ButtonCount+1) + + local VisibleButtons = 0 + for i=1, self.m_nPageCount do + local button = self.PageChooseNumbers.Buttons[i] + if (!IsValid(button)) then continue end + + if (pagepos < i+ButtonCount and pagepos >= i-ButtonCount+1) then + button:SetVisible(true) + EnableButton(button, true) + VisibleButtons = VisibleButtons + 1 + else + button:SetVisible(false) + EnableButton(button, false) + end + + button.Depressed = false + end + + local SelectButton = self.PageChooseNumbers.Buttons[self.m_nPage] + if (IsValid(SelectButton)) then + SelectButton.Depressed = true + SelectButton:SetMouseInputEnabled(false) + end + + self.PageChooseNumbers:SetWide(VisibleButtons*self.PageButtonSize) + self.PageChooseNumbers:Center() +end + +function PANEL:AddColumns(...) + local Column = {} + for k, v in ipairs({...}) do + Column[k] = self.List:AddColumn(v) + end + return Column +end + +function PANEL:Think() + if (self.SplitPanel:GetDragging()) then + self.oldpage = nil + self:InvalidateLayout() + end + + if ( !self.bSetup ) then + self.bSetup = self:Setup() + end +end + +function PANEL:PerformLayout() + self:LayoutPages() + self.Tree:InvalidateLayout() + self.List:InvalidateLayout() + + local minw = self:GetWide() - self.SplitPanel:GetRightMin() - self.SplitPanel:GetDividerWidth() + local oldminw = self.SplitPanel:GetLeftWidth(minw) + + if (oldminw > minw) then + self.SplitPanel:SetLeftWidth(minw) + end + + + //Fixes scrollbar glitches on resize + self.Tree:OnMouseWheeled(0) + self.List:OnMouseWheeled(0) + + if (!self.PageLoadingProgress:IsVisible()) then return end + + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() +end + +function PANEL:Setup() + if (!self.m_strRootName) then return false end + if (!self.m_strRootPath) then return false end + + WireLib.TimedpairsStop(self.TimedpairsName) + + self.m_strOpenPath = nil + self.m_strOpenFile = nil + self.m_strOpenFilename = nil + self.oldpage = nil + + self.Tree:Clear(true) + if (IsValid(self.Root)) then + self.Root:Remove() + end + self.Root = self.Tree.RootNode:AddFolder( self.m_strRootName, self.m_strRootPath, self.m_strWildCard or "GAME", false) + + return true +end + +function PANEL:SetOpenFilename(filename) + if(!isstring(filename)) then filename = "" end + + self.m_strOpenFilename = filename + self.m_strOpenFile = ConnectPathes(self.m_strOpenPath, self.m_strOpenFilename) +end + +function PANEL:SetOpenPath(path) + self.Root:SetExpanded(true) + + path = PathFilter(path, self.FolderPathText, self.m_strRootPath) or "" + if (self.m_strOpenPath == path) then return end + self.oldpage = nil + + NavigateToFolder(self, path) + self.m_strOpenPath = path + self.m_strOpenFile = ConnectPathes(self.m_strOpenPath, self.m_strOpenFilename) +end + +function PANEL:SetOpenFile(file) + if(!isstring(file)) then file = "" end + + self:SetOpenPath(string.GetPathFromFilename(file)) + self:SetOpenFilename(string.GetFileFromFilename("/" .. file)) +end + +function PANEL:SetPage(page) + if (page < 1) then return end + if (page > self.m_nPageCount) then return end + if (page == self.m_nPage) then return end + + WireLib.TimedpairsStop(self.TimedpairsName) + self.List:Clear(true) + + self.m_nPage = page + self:UpdatePageToolTips() + + local filepage + if(self.PageMode) then + filepage = {} + for i=1, self.m_nMaxItemsPerPage do + local index = i + self.m_nMaxItemsPerPage * (page - 1) + local value = self.Files[index] + if (!value) then break end + filepage[i] = value + end + else + filepage = self.Files + end + + local Fraction = 0 + local FileCount = #filepage + local ShowProgress = (FileCount > self.m_nListSpeed * 5) + + self.PageLoadingProgress:SetVisible(ShowProgress) + if (FileCount <= 0) then + self.PageLoadingProgress:SetVisible(false) + + return + end + + self.PageLoadingProgress:SetFraction(Fraction) + self.PageLoadingLabel:SetText("0 of " .. FileCount .. " files found.") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + + self:InvalidateLayout() + + WireLib.Timedpairs(self.TimedpairsName, filepage, self.m_nListSpeed, function(id, name, self) + if (!IsValid(self)) then return false end + if (!IsValid(self.List)) then return false end + + local file = ConnectPathes(self.m_strOpenPath, name) + local args, bcontinue, bbreak = self:LineData(id, file, self.m_strOpenPath, name) + + if (bcontinue) then return end // continue + if (bbreak) then return false end // break + + local line = self.List:AddLine(name, unpack(args or {})) + if (!IsValid(line)) then return end + + line.m_strPath = self.m_strOpenPath + line.m_strFilename = name + line.m_strFile = file + + if (self.m_strOpenFile == file) then + self.List:SelectItem(line) + end + + self:OnLineAdded(id, line, file, self.m_strOpenPath, name) + + Fraction = id / FileCount + + if (!IsValid(self.PageLoadingProgress)) then return end + if (!ShowProgress) then return end + + self.PageLoadingProgress:SetFraction(Fraction) + + self.PageLoadingLabel:SetText(id .. " of " .. FileCount .. " files found.") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + end, function(id, name, self) + if (!IsValid(self)) then return end + Fraction = 1 + + if (!IsValid(self.PageLoadingProgress)) then return end + if (!ShowProgress) then return end + + self.PageLoadingProgress:SetFraction(Fraction) + self.PageLoadingLabel:SetText(id .. " of " .. FileCount .. " files found.") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + + self.PageLoadingProgress:SetVisible(false) + self:InvalidateLayout() + end, self) +end + +function PANEL:DoClick(file, path, name) + -- Override +end +function PANEL:DoDoubleClick(file, path, name) + -- Override +end +function PANEL:DoRightClick(file, path, name) + -- Override +end + +function PANEL:LineData(id, file, path, name) + return // to override +end + +function PANEL:OnLineAdded(id, line, file, path, name) + return // to override +end + +vgui.Register("wire_filebrowser", PANEL, "DPanel") diff --git a/garrysmod/addons/feature-wire/lua/wire/client/wire_listeditor.lua b/garrysmod/addons/feature-wire/lua/wire/client/wire_listeditor.lua new file mode 100644 index 0000000..970f976 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/wire_listeditor.lua @@ -0,0 +1,590 @@ +// A list editor. It allows reading editing and saving lists as *.txt files. +// It uses wire_expression2_browser for it's file browser. +// The files have an easy structure for easy editing. Rows are separated by '\n' and columns by '|'. +// Made by Grocel. + +local PANEL = {} + +AccessorFunc( PANEL, "m_strRootPath", "RootPath" ) // path of the root Root +AccessorFunc( PANEL, "m_strList", "List" ) // List file +AccessorFunc( PANEL, "m_strFile", "File" ) // sounds listed in list files +AccessorFunc( PANEL, "m_bUnsaved", "Unsaved" ) // edited list file Saved? +AccessorFunc( PANEL, "m_strSelectedList", "SelectedList" ) // Selected list file + +AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once +AccessorFunc( PANEL, "m_nMaxItems", "MaxItems" ) // how may items at maximum + +local max_char_count = 200 //File length limit + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +local invalid_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["\\"] = "", + ['"'] = "", +} + +local function ConnectPathes(path1, path2) + local path = "" + + if (isstring(path1) and path1 ~= "") then + path = path1 + if (isstring(path2) and path2 ~= "") then + path = path1.."/"..path2 + end + else + if (isstring(path2) and path2 ~= "") then + path = path2 + end + end + + return path +end + +//Parse the lines from a given file object +local function ReadLine(filedata) + if (!filedata) then return end + + local fileline = "" + local comment = false + local count = 0 + + for i=1, 32 do // skip 32 lines at maximum + local line = "" + local fileend = false + + for i=1, max_char_count+56 do // maximum chars per line + local byte = filedata:ReadByte() + fileend = !byte + + if (fileend) then break end // file end + local char = string.char(byte) + + if (invalid_chars[char]) then // replace invalid chars + char = invalid_chars[char] + end + + if (char == "\n") then break end // line end + line = line .. char + end + line = string.Trim(line) + + if (!fileend and line == "") then continue end + fileline = line + + break + end + + local linetable = string.Explode("|", fileline) or {} + + if (#linetable == 0) then return end + + for k, v in ipairs(linetable) do // cleanup + local line = linetable[k] + + if (k == 1) then + line = string.Trim(line, "/") + end + line = string.Trim(line) + + linetable[k] = line + end + + if (#linetable[1] == 0) then return end + + return linetable +end + +local function fileName(filepath) + return string.match(filepath, "[/\\]?([^/\\]*)$") +end + +local function SaveTo(self, func, ...) + if (!IsValid(self)) then return end + local args = {...} + + local path = self.FileBrowser:GetFileName() or self.m_strList or "" + + Derma_StringRequestNoBlur( + "Save to New File", + "", + string.sub(fileName(path), 0, -5), // remove .txt at the end + + function( strTextOut ) + if (!IsValid(self)) then return end + + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + if (strTextOut == "") then return end + + local filepath = string.GetPathFromFilename(path) + if (!filepath or filepath == "") then filepath = self.m_strRootPath.."/" end + + local saved = self:SaveList(filepath..strTextOut..".txt") + if (saved and func) then + func(self, unpack(args)) + end + end + ) + return true +end + +//Ask for override: Opens a confirmation if the file name is different box. +local function AsForOverride(self, func, filename, ...) + if (!IsValid(self)) then return end + + if (!func) then return end + if (filename == self.m_strList) then func(self, filename, ...) return end + if (!file.Exists(filename, "DATA")) then func(self, filename, ...) return end + + local args = {...} + + Derma_Query( + "Overwrite this file?", + "Save To", + "Overwrite", + + function() + if (!IsValid(self)) then return end + + func(self, filename, unpack(args)) + end, + + "Cancel" + ) +end + +//Ask for save: Opens a confirmation box. +local function AsForSave(self, func, ...) + if (!IsValid(self)) then return end + + if (!func) then return end + if (!self.m_bUnsaved) then func(self, ...) return end + + local args = {...} + + Derma_Query( "Would you like to save the changes?", + "Unsaved List!", + + "Yes", // Save and resume. + function() + if (!IsValid(self)) then return end + + if (!self.m_strList or self.m_strList == "") then + SaveTo(self, func, unpack(args)) + return + end + + local saved = self:SaveList(self.m_strList) + if (saved) then + func(self, unpack(args)) + end + end, + + "No", // Don't save and resume. + function() + if (!IsValid(self)) then return end + func(self, unpack(args)) + end, + + "Cancel" // Do nothing. + ) +end + + +function PANEL:Init() + self.TimedpairsName = "wire_listeditor_items_" .. tostring({}) + + self:SetDrawBackground(false) + + self:SetListSpeed(40) + self:SetMaxItems(512) + self:SetUnsaved(false) + + self.TabfileCount = 0 + self.Tabfile = {} + + self.ListsPanel = vgui.Create("DPanel") + self.ListsPanel:SetDrawBackground(false) + + self.FilesPanel = vgui.Create("DPanel") + self.FilesPanel:SetDrawBackground(false) + + self.FileBrowser = self.ListsPanel:Add("wire_expression2_browser") + self.FileBrowser:Dock(FILL) + self.FileBrowser.OnFileOpen = function(panel, listfile) + self:OpenList(listfile) + end + self.FileBrowser:RemoveRightClick("Open in New Tab") // we don't need tabs. + self.FileBrowser:AddRightClick(self.FileBrowser.filemenu,4,"Save To..", function() + self:SaveList(self.FileBrowser:GetFileName()) + end) + self.FileBrowser.Update:Remove() // it's replaced + + self.Files = self.FilesPanel:Add("DListView") + self.Files:SetMultiSelect(false) + self.Files:Dock(FILL) + + local Column = self.Files:AddColumn("No.") + Column:SetFixedWidth(30) + Column:SetWide(30) + + self.Files:AddColumn("Name") + + local Column = self.Files:AddColumn("Type") + Column:SetFixedWidth(70) + Column:SetWide(70) + + self.Files.OnRowSelected = function(parent, id, line) + local name = line.m_strFilename + local data = line.m_tabData + self.m_strFile = name + self.m_strSelectedList = self.m_strList + + self:DoClick(name, data, parent, line) + end + + self.Files.DoDoubleClick = function(parent, id, line) + local name = line.m_strFilename + local data = line.m_tabData + self.m_strFile = name + self.m_strSelectedList = self.m_strList + + self:DoDoubleClick(name, data, parent, line) + end + + self.Files.OnRowRightClick = function(parent, id, line) + local name = line.m_strFilename + local data = line.m_tabData + self.m_strFile = name + self.m_strSelectedList = self.m_strList + + self:DoRightClick(name, data, parent, line) + end + + self.ListTopPanel = self.FilesPanel:Add("DPanel") + self.ListTopPanel:SetDrawBackground(false) + self.ListTopPanel:Dock(TOP) + self.ListTopPanel:SetTall(20) + self.ListTopPanel:DockMargin(0, 0, 0, 3) + + self.SaveIcon = self.ListTopPanel:Add("DImageButton") + self.SaveIcon:SetImage("icon16/table_save.png") + self.SaveIcon:SetWide(20) + self.SaveIcon:Dock(LEFT) + self.SaveIcon:SetToolTip("Save list") + self.SaveIcon:SetStretchToFit(false) + self.SaveIcon:DockMargin(0, 0, 0, 0) + self.SaveIcon.DoClick = function() + if (!self.m_strList or self.m_strList == "") then + SaveTo(self) + return + end + + self:SaveList(self.m_strList) + end + + self.SaveToIcon = self.ListTopPanel:Add("DImageButton") + self.SaveToIcon:SetImage("icon16/disk.png") + self.SaveToIcon:SetWide(20) + self.SaveToIcon:Dock(LEFT) + self.SaveToIcon:SetToolTip("Save To..") + self.SaveToIcon:SetStretchToFit(false) + self.SaveToIcon:DockMargin(0, 0, 0, 0) + self.SaveToIcon.DoClick = function() + SaveTo(self) + end + + self.NewIcon = self.ListTopPanel:Add("DImageButton") + self.NewIcon:SetImage("icon16/table_add.png") + self.NewIcon:SetWide(20) + self.NewIcon:Dock(LEFT) + self.NewIcon:SetToolTip("New list") + self.NewIcon:SetStretchToFit(false) + self.NewIcon:DockMargin(10, 0, 0, 0) + self.NewIcon.DoClick = function() + self:ClearList() + end + + self.RefreshIcon = self.ListTopPanel:Add("DImageButton") + self.RefreshIcon:SetImage("icon16/arrow_refresh.png") + self.RefreshIcon:SetWide(20) + self.RefreshIcon:Dock(LEFT) + self.RefreshIcon:SetToolTip("Refresh and Reload") + self.RefreshIcon:SetStretchToFit(false) + self.RefreshIcon:DockMargin(0, 0, 0, 0) + self.RefreshIcon.DoClick = function() + self:Refresh() + end + + self.ListNameLabel = self.ListTopPanel:Add("DLabel") + self.ListNameLabel:SetText("") + self.ListNameLabel:SetWide(20) + self.ListNameLabel:Dock(FILL) + self.ListNameLabel:DockMargin(12, 0, 0, 0) + self.ListNameLabel:SetDark(true) + + self.SplitPanel = self:Add( "DHorizontalDivider" ) + self.SplitPanel:Dock( FILL ) + self.SplitPanel:SetLeft(self.ListsPanel) + self.SplitPanel:SetRight(self.FilesPanel) + self.SplitPanel:SetLeftWidth(200) + self.SplitPanel:SetLeftMin(150) + self.SplitPanel:SetRightMin(300) + self.SplitPanel:SetDividerWidth(3) + + self:SetRootPath("wirelists") +end + +function PANEL:PerformLayout() + local minw = self:GetWide() - self.SplitPanel:GetRightMin() - self.SplitPanel:GetDividerWidth() + local oldminw = self.SplitPanel:GetLeftWidth(minw) + + if (oldminw > minw) then + self.SplitPanel:SetLeftWidth(minw) + end + + //Fixes scrollbar glitches on resize + if (IsValid(self.FileBrowser.Folders)) then + self.FileBrowser.Folders:OnMouseWheeled(0) + end + self.Files:OnMouseWheeled(0) +end + +function PANEL:UpdateListNameLabel() + if (!IsValid(self.ListNameLabel)) then return end + + self.ListNameLabel:SetText((self.m_bUnsaved and "*" or "")..(self.m_strList or "")) +end + + +function PANEL:ClearList() + AsForSave(self, function(self) + self:SetList(nil) + self:SetUnsaved(false) + self.TabfileCount = 0 + + WireLib.TimedpairsStop(self.TimedpairsName) + self.Files:Clear(true) + self.Tabfile = {} + end) +end + +function PANEL:Setup() + if (!self.m_strRootPath) then return false end + self.m_strSelectedList = nil + self.m_strFile = nil + + self:ClearList() + + self.FileBrowser:Setup(self.m_strRootPath) + + return true +end + +function PANEL:Refresh() + self.FileBrowser:Refresh() + self:OpenList(self.m_strList) + + self:InvalidateLayout() +end + +function PANEL:Think() + if (self.SplitPanel:GetDragging()) then + self:InvalidateLayout() + end + + if ( !self.bSetup ) then + self.bSetup = self:Setup() + end +end + +function PANEL:AddItem(...) + local itemtable = {...} + local item = itemtable[1] + + if (!isstring(item) or item == "") then return end + if (self.TabfileCount > self.m_nMaxItems) then return end + if (#item > max_char_count) then return end + if (self.Tabfile[item]) then return end + + local itemargs = {} + local i = 0 + + for k, v in ipairs(itemtable) do + if (k == 1) then continue end + + i = i + 1 + itemargs[i] = v + end + self.Tabfile[item] = itemargs + + local line = self.Files:AddLine(self.TabfileCount + 1, ...) + line.m_strFilename = item + line.m_tabData = itemargs + + //if (self.m_strFile == item) then + if (self.m_strSelectedList == self.m_strList and self.m_strFile == item) then + self.Files:SelectItem(line) + end + + self.TabfileCount = self.TabfileCount + 1 + self:SetUnsaved(true) + return line +end + +function PANEL:ItemInList(item) + if (!item) then return false end + if (self.Tabfile[item]) then return true end + + return false +end + +function PANEL:RemoveItem(item) + if (!item) then return end + if (!self.Tabfile[item]) then return end + if (!self.Files.Lines) then return end + + for k, v in ipairs(self.Files.Lines) do + if (v.m_strFilename == item) then + self.Files:RemoveLine(v:GetID()) + self.Tabfile[item] = nil + self:SetUnsaved(true) + self.TabfileCount = self.TabfileCount - 1 + + break + end + end +end + +function PANEL:OpenList(strfile) + if (!strfile) then return end + if (strfile == "") then return end + + AsForSave(self, function(self, strfile) + local filedata = file.Open(strfile, "rb", "DATA") + if (!filedata) then return end + + WireLib.TimedpairsStop(self.TimedpairsName) + self.Files:Clear(true) + self.Tabfile = {} + self.TabfileCount = 0 + + local counttab={} + for i=1, self.m_nMaxItems do + counttab[i] = true + end + + WireLib.Timedpairs(self.TimedpairsName, counttab, self.m_nListSpeed, function(index, _, self, filedata) + if (!IsValid(self)) then + filedata:Close() + return false + end + + if (!IsValid(self.Files)) then + filedata:Close() + return false + end + + if (self.TabfileCount >= self.m_nMaxItems) then + filedata:Close() + self:SetUnsaved(false) + + return false + end + + local linetable = ReadLine(filedata) + if (!linetable) then // do not add to empty lines + filedata:Close() + self:SetUnsaved(false) + + return false + end + + self:AddItem(unpack(linetable)) + self:SetUnsaved(false) + end, function(index, _, self, filedata) + filedata:Close() + + if (!IsValid(self)) then return end + self:SetUnsaved(false) + end, self, filedata) + + self:SetUnsaved(false) + self:SetList(strfile) + end, strfile) +end + +function PANEL:SaveList(strfile) + if (!self.Tabfile) then return end + if (!strfile) then return end + if (strfile == "") then return end + + AsForOverride(self, function(self, strfile) + local filedata = file.Open(strfile, "w", "DATA") + if (!filedata) then + Derma_Query( "File could not be saved!", + "Error!", + "OK" + ) + + return + end + + for key, itemtable in SortedPairs(self.Tabfile) do + local item = key + for k, supitem in ipairs(itemtable) do + item = item.." | "..supitem + end + filedata:Write(item.."\n") + end + + filedata:Close() + + self:SetUnsaved(false) + self:SetList(strfile) + + self:Refresh() + end, strfile) +end + +function PANEL:SetRootPath(path) + self.m_strRootPath = path + + self.bSetup = self:Setup() +end + +function PANEL:SetUnsaved(bool) + self.m_bUnsaved = bool + + self:UpdateListNameLabel() +end + +function PANEL:SetList(listfile) + self.m_strList = listfile + + self:UpdateListNameLabel() +end + +function PANEL:DoClick(name, data, parent, line) + -- Override +end +function PANEL:DoDoubleClick(name, data, parent, line) + -- Override +end +function PANEL:DoRightClick(name, data, parent, line) + -- Override +end + +vgui.Register("wire_listeditor", PANEL, "DPanel") diff --git a/garrysmod/addons/feature-wire/lua/wire/client/wire_soundpropertylist.lua b/garrysmod/addons/feature-wire/lua/wire/client/wire_soundpropertylist.lua new file mode 100644 index 0000000..aec51fd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/wire_soundpropertylist.lua @@ -0,0 +1,239 @@ +// A sound property browsner. It helps to find all sounds which are defined in sound scripts or by sound.Add(). +// Made by Grocel. + +local PANEL = {} + +local max_char_count = 200 //Name length limit + +AccessorFunc( PANEL, "m_strSearchPattern", "SearchPattern" ) // Pattern to search for. +AccessorFunc( PANEL, "m_strSelectedSound", "SelectedSound" ) // Pattern to search for. +AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once +AccessorFunc( PANEL, "m_nMaxItems", "MaxItems" ) // how may items at maximum + +local function IsInString(strSource, strPattern) + if (!strPattern) then return true end + if (strPattern == "") then return true end + + strSource = string.lower(strSource) + strPattern = string.lower(strPattern) + + if string.find(strSource, strPattern, 0, true) then return true end + + return false +end + +local function GenerateList(self, strPattern) + if (!IsValid(self)) then return end + self:ClearList() + + local soundtable = sound.GetTable() or {} + local soundcount = #soundtable + self.SearchProgress:SetVisible(true) + if (soundcount <= 0) then + self.SearchProgress:SetVisible(false) + + return + end + + self.SearchProgress:SetFraction(0) + self.SearchProgressLabel:SetText("Searching... (0 %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + WireLib.Timedpairs(self.TimedpairsName, soundtable, self.m_nListSpeed, function(k, v, self) + if (!IsValid(self)) then return false end + if (!IsValid(self.SoundProperties)) then return false end + if (!IsValid(self.SearchProgress)) then return false end + + self.SearchProgress:SetFraction(k / soundcount) + self.SearchProgressLabel:SetText("Searching... ("..math.Round(k / soundcount * 100).." %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + if (self.TabfileCount >= self.m_nMaxItems) then + self.SearchProgress:SetFraction(1) + + self.SearchProgressLabel:SetText("Searching... (100 %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + self.SearchProgress:SetVisible(false) + self:InvalidateLayout() + + return false + end + + if (!IsInString(v, strPattern)) then return end + + self:AddItem(k, v) + + end, function(k, v, self) + if (!IsValid(self)) then return end + if (!IsValid(self.SoundProperties)) then return end + if (!IsValid(self.SearchProgress)) then return end + + self.SearchProgress:SetFraction(1) + + self.SearchProgressLabel:SetText("Searching... (100 %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + self.SearchProgress:SetVisible(false) + self:InvalidateLayout() + end, self) +end + +function PANEL:Init() + self.TimedpairsName = "wire_soundpropertylist_items_" .. tostring({}) + + self:SetDrawBackground(false) + self:SetListSpeed(100) + self:SetMaxItems(400) + + self.SearchPanel = self:Add("DPanel") + self.SearchPanel:DockMargin(0, 0, 0, 3) + self.SearchPanel:SetTall(20) + self.SearchPanel:Dock(TOP) + self.SearchPanel:SetDrawBackground(false) + + self.SearchText = self.SearchPanel:Add("DTextEntry") + self.SearchText:DockMargin(0, 0, 3, 0) + self.SearchText:Dock(FILL) + self.SearchText.OnChange = function(panel) + self:SetSearchPattern(panel:GetValue()) + end + + self.RefreshIcon = self.SearchPanel:Add("DImageButton") // The Folder Button. + self.RefreshIcon:SetImage("icon16/arrow_refresh.png") + self.RefreshIcon:SetWide(20) + self.RefreshIcon:Dock(RIGHT) + self.RefreshIcon:SetToolTip("Refresh") + self.RefreshIcon:SetStretchToFit(false) + self.RefreshIcon.DoClick = function() + self:Refresh() + end + + self.SearchProgress = self:Add("DProgress") + self.SearchProgress:DockMargin(0, 0, 0, 0) + self.SearchProgress:SetTall(20) + self.SearchProgress:Dock(BOTTOM) + self.SearchProgress:SetVisible(false) + + self.SearchProgressLabel = self.SearchProgress:Add("DLabel") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + self.SearchProgressLabel:SetText("") + self.SearchProgressLabel:SetPaintBackground(false) + self.SearchProgressLabel:SetDark(true) + + + self.SoundProperties = self:Add("DListView") + self.SoundProperties:SetMultiSelect(false) + self.SoundProperties:Dock(FILL) + + local Column = self.SoundProperties:AddColumn("No.") + Column:SetFixedWidth(30) + Column:SetWide(30) + + local Column = self.SoundProperties:AddColumn("ID") + Column:SetFixedWidth(40) + Column:SetWide(40) + + self.SoundProperties:AddColumn("Name") + + self.SoundProperties.OnRowSelected = function(parent, id, line) + local name = line.m_strSoundname + local data = line.m_tabData + self.m_strSelectedSound = name + + self:DoClick(name, data, parent, line) + end + + self.SoundProperties.DoDoubleClick = function(parent, id, line) + local name = line.m_strSoundname + local data = line.m_tabData + self.m_strSelectedSound = name + + self:DoDoubleClick(name, data, parent, line) + end + + self.SoundProperties.OnRowRightClick = function(parent, id, line) + local name = line.m_strSoundname + local data = line.m_tabData + self.m_strSelectedSound = name + + self:DoRightClick(name, data, parent, line) + end + + self:Refresh() +end + +function PANEL:PerformLayout() + if (!self.SearchProgress:IsVisible()) then return end + + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() +end + +function PANEL:ClearList() + WireLib.TimedpairsStop(self.TimedpairsName) + self.SoundProperties:Clear(true) + + self.TabfileCount = 0 +end + +function PANEL:AddItem(...) + local itemtable = {...} + local item = itemtable[2] + + if (!isstring(item) or item == "") then return end + if (self.TabfileCount > self.m_nMaxItems) then return end + if (#item > max_char_count) then return end + + local itemargs = {} + local i = 0 + + for k, v in ipairs(itemtable) do + if (k == 2) then continue end + + i = i + 1 + itemargs[i] = v + end + + local line = self.SoundProperties:AddLine(self.TabfileCount + 1, ...) + line.m_strSoundname = item + line.m_tabData = itemargs + + if (self.m_strSelectedSound == item) then + self.SoundProperties:SelectItem(line) + end + + self.TabfileCount = self.TabfileCount + 1 + return line +end + +function PANEL:SetSearchPattern(strPattern) + self.m_strSearchPattern = strPattern or "" + self:Refresh() +end + +function PANEL:SetSelectedSound(strSelectedSound) + self.m_strSelectedSound = strSelectedSound or "" + self:Refresh() +end + +function PANEL:Refresh() + GenerateList(self, self.m_strSearchPattern) +end + +function PANEL:DoClick(name, data, parent, line) + -- Override +end +function PANEL:DoDoubleClick(name, data, parent, line) + -- Override +end +function PANEL:DoRightClick(name, data, parent, line) + -- Override +end + +vgui.Register("wire_soundpropertylist", PANEL, "DPanel") diff --git a/garrysmod/addons/feature-wire/lua/wire/client/wiredermaexts.lua b/garrysmod/addons/feature-wire/lua/wire/client/wiredermaexts.lua new file mode 100644 index 0000000..f84ee43 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/wiredermaexts.lua @@ -0,0 +1,107 @@ +WireDermaExts = {} +language.Add("wire_model", "Model:") + +-- Shortcut functions for Wire tools to make their model select controls +-- TODO: redo category system +function ModelPlug_AddToCPanel(panel, category, toolname, textbox_label, height) + local list = list.Get("Wire_"..category.."_Models") + if table.Count(list) > 1 then + local ModelSelect = vgui.Create("DWireModelSelect", self) + ModelSelect:SetModelList(list, toolname .. "_model") + ModelSelect:SetHeight(height) + panel:AddPanel(ModelSelect) + end + if textbox_label and GetConVarNumber("cl_showmodeltextbox") > 0 then + panel:TextEntry("#wire_model", toolname .. "_model") + end +end + +function ModelPlug_AddToCPanel_Multi(panel, categories, toolname, textbox_label, height) + local ModelSelect = vgui.Create("DWireModelSelectMulti", panel) + ModelSelect:SetHeight(height) + panel:AddPanel(ModelSelect) + local cvar = toolname .. "_model" + for category, name in pairs_sortkeys(categories) do + local list = list.Get("Wire_"..category.."_Models") + if list then + ModelSelect:AddModelList(name, list, cvar) + end + end + if textbox_label and GetConVarNumber("cl_showmodeltextbox") > 0 then + panel:TextEntry(textbox_label, toolname .. "_model") + end +end + + +function WireDermaExts.ModelSelect(panel, convar, list, height, show_textbox) + if table.Count(list) > 1 then + local ModelSelect = vgui.Create("DWireModelSelect", panel) + ModelSelect:SetModelList(list, convar) + ModelSelect:SetHeight(height) + panel:AddPanel(ModelSelect) + if show_textbox and GetConVarNumber("cl_showmodeltextbox") > 0 then + panel:TextEntry("#wire_model", convar) + end + return ModelSelect + end +end + + +-- +-- Additional Derma controls +-- +-- This are under testing +-- I will try to have them included in to GMod when they are stable +-- + + +-- +-- DWireModelSelect +-- sexy model select +local PANEL = {} + +function PANEL:Init() + self:EnableVerticalScrollbar() + self:SetTall(66 * 2 + 2) +end + +function PANEL:SetHeight(height) + self:SetTall(66 * (height or 2) + 2) +end + +function PANEL:SetModelList( list, cvar ) + for model,v in pairs(list) do + local icon = vgui.Create("SpawnIcon") + icon:SetModel(model) + icon.Model = model + icon:SetSize(64, 64) + icon:SetTooltip(model) + self:AddPanel(icon, {[cvar] = model}) + end + self:SortByMember("Model", false) +end + +derma.DefineControl( "DWireModelSelect", "", PANEL, "DPanelSelect" ) + +-- +-- DWireModelSelectMulti +-- sexy tabbed model select with categories +local PANEL = {} + +function PANEL:Init() + self.ModelPanels = {} + self:SetTall(66 * 2 + 26) +end + +function PANEL:SetHeight(height) + self:SetTall(66 * (height or 2) + 26) +end + +function PANEL:AddModelList( Name, list, cvar ) + local PanelSelect = vgui.Create("DWireModelSelect", self) + PanelSelect:SetModelList(list, cvar) + self:AddSheet(Name, PanelSelect) + self.ModelPanels[Name] = PanelSelect +end + +derma.DefineControl( "DWireModelSelectMulti", "", PANEL, "DPropertySheet" ) diff --git a/garrysmod/addons/feature-wire/lua/wire/client/wiremenus.lua b/garrysmod/addons/feature-wire/lua/wire/client/wiremenus.lua new file mode 100644 index 0000000..800644d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/client/wiremenus.lua @@ -0,0 +1,50 @@ +local Categories = { + "Favourites", + "Chips, Gates", + "Visuals", + "Detection", + "Input, Output", + "Vehicle Control", + "Physics", + "Other", + "Memory", + "Advanced", + "Tools", + "Options", +} + +hook.Add( "AddToolMenuCategories", "WireCategories", function() + for i=1,#Categories do + local Category = Categories[i] + spawnmenu.AddToolCategory("Wire", Category, Category) + end +end) + +local function WireTab() + spawnmenu.AddToolTab( "Wire", "Wire" ) + + --start: UGLY HACK, BAD BAD BAD D: + local oldspawnmenuAddToolMenuOption = spawnmenu.AddToolMenuOption + function spawnmenu.AddToolMenuOption(tab, category, ...) + if tab == "Main" and string.lower(string.Left(category, 4)) == "wire" then tab = "Wire" end + oldspawnmenuAddToolMenuOption(tab, category, ...) + end + --end: UGLY HACK, BAD BAD BAD D: +end +hook.Add( "AddToolMenuTabs", "WireTab", WireTab) + +local devs = {} +function AddWireAdminMaxDevice(pluralname, dev) + devs[pluralname] = dev +end + +local function BuildAdminControlPanel(Panel) + for name,dev in pairs(devs) do + local slider = Panel:NumSlider(name, "sbox_max"..dev, 0, 999, 0) + end +end + +local function AddWireAdminControlPanelMenu() + spawnmenu.AddToolMenuOption("Utilities", "Admin", "WireAdminControlPanel", "Max Wire Devices", "", "", BuildAdminControlPanel, {}) +end +hook.Add("PopulateToolMenu", "AddAddWireAdminControlPanelMenu", AddWireAdminControlPanelMenu) diff --git a/garrysmod/addons/feature-wire/lua/wire/cpulib.lua b/garrysmod/addons/feature-wire/lua/wire/cpulib.lua new file mode 100644 index 0000000..b13e471 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/cpulib.lua @@ -0,0 +1,1190 @@ +-------------------------------------------------------------------------------- +-- ZCPU utility & support code +-------------------------------------------------------------------------------- +local INVALID_BREAKPOINT_IP = 2e7 + +CPULib = CPULib or {} +if CLIENT then + -- Sourcecode available as compiled binary + CPULib.Source = "" + -- Compiled binary + CPULib.Buffer = {} + + -- Sourcecode currently being compiled + CPULib.CurrentSource = "" + -- Buffer currently being written + CPULib.CurrentBuffer = {} + + -- State variables + CPULib.Compiling = false + CPULib.Uploading = false + CPULib.ServerUploading = false + + -- Debugger + CPULib.DebuggerAttached = false + CPULib.Debugger = {} + CPULib.Debugger.Variables = {} + CPULib.Debugger.SourceTab = nil + + -- Reset on recompile + CPULib.Debugger.MemoryVariableByIndex = {} + CPULib.Debugger.MemoryVariableByName = {} + CPULib.Debugger.Labels = {} + CPULib.Debugger.PositionByPointer = {} + CPULib.Debugger.PointersByLine = {} + + CPULib.Debugger.Breakpoint = {} + + -- Convars to control CPULib + local wire_cpu_upload_speed = CreateClientConVar("wire_cpu_upload_speed",1000,false,false) + local wire_cpu_compile_speed = CreateClientConVar("wire_cpu_compile_speed",256,false,false) + local wire_cpu_show_all_registers = CreateClientConVar("wire_cpu_show_all_registers",0,false,false) + + ------------------------------------------------------------------------------ + -- Request compiling specific sourcecode + function CPULib.Compile(source,fileName,successCallback,errorCallback,targetPlatform) + -- Stop any compile/upload process that is running right now + timer.Remove("cpulib_compile") + timer.Remove("cpulib_upload") + CPULib.Uploading = false + + -- See if compiled source is available + --if CPULib.Source == source then + -- successCallback() + -- return + --end + + -- Remember the sourcecode being compiled + CPULib.CurrentSource = source + CPULib.CurrentBuffer = {} + + -- Clear debugging info + CPULib.Debugger.MemoryVariableByIndex = {} + CPULib.Debugger.MemoryVariableByName = {} + CPULib.Debugger.Labels = {} + CPULib.Debugger.PositionByPointer = {} + CPULib.Debugger.PointersByLine = {} + CPULib.CPUName = nil + + -- Start compiling the sourcecode + HCOMP:StartCompile(source,fileName or "source",CPULib.OnWriteByte,nil) + HCOMP.Settings.CurrentPlatform = targetPlatform or "CPU" + print("=== HL-ZASM High Level Assembly Compiler Output ==") + + -- Initialize callbacks + CPULib.SuccessCallback = successCallback + CPULib.ErrorCallback = errorCallback + + -- Run the timer + timer.Create("cpulib_compile",1/60,0,CPULib.OnCompileTimer) + CPULib.Compiling = true + end + + ------------------------------------------------------------------------------ + -- Make sure the file is opened in the tab + function CPULib.SelectTab(editor,fileName) + if not editor then return end + local editorType = string.lower(editor.EditorType) + local fullFileName = editorType.."chip\\"..fileName + + if string.sub(fileName,1,7) == editorType.."chip" then + fullFileName = fileName + end + + local sourceTab + for tab=1,editor:GetNumTabs() do + if editor:GetEditor(tab).chosenfile == fullFileName then + sourceTab = tab + end + end + + if not sourceTab then + editor:LoadFile(fullFileName,true) + sourceTab = editor:GetActiveTabIndex() + else + editor:SetActiveTab(sourceTab) + end + + return editor:GetEditor(sourceTab),sourceTab + end + + ------------------------------------------------------------------------------ + -- Request validating the code + function CPULib.Validate(editor,source,fileName) + CPULib.Compile(source,fileName, + function() + editor.C.Val:SetBGColor(50, 128, 20, 180) + editor.C.Val:SetFGColor(255, 255, 255, 128) + editor.C.Val:SetText(" Success, "..(HCOMP.WritePointer or "?").." bytes compiled.") + end, + function(error,errorPos) + editor.C.Val:SetBGColor(128, 20, 50, 180) + editor.C.Val:SetFGColor(255, 255, 255, 128) + editor.C.Val:SetText(" "..(error or "unknown error")) + + if not errorPos then return end + + local textEditor = CPULib.SelectTab(editor,errorPos.File) + if not textEditor then return end + textEditor:SetCaret({errorPos.Line,errorPos.Col}) + end,editor.EditorType) + end + + ------------------------------------------------------------------------------ + -- Compiler callback + function CPULib.OnWriteByte(caller,address,byte) + CPULib.CurrentBuffer[address] = byte + end + + ------------------------------------------------------------------------------ + -- Compiler timer + function CPULib.OnCompileTimer() + local compile_speed = wire_cpu_compile_speed:GetFloat() + + for _ = 1, compile_speed do + local status,result = pcall(HCOMP.Compile,HCOMP) + if not status then + print("==================================================") + if CPULib.ErrorCallback then CPULib.ErrorCallback(HCOMP.ErrorMessage or ("Internal error: "..result),HCOMP.ErrorPosition) end + timer.Remove("cpulib_compile") + CPULib.Compiling = false + + return + elseif not result then + print("==================================================") + CPULib.Source = CPULib.CurrentSource + CPULib.Buffer = CPULib.CurrentBuffer + + if CPULib.SuccessCallback then + CPULib.Debugger.Labels = HCOMP.DebugInfo.Labels + CPULib.Debugger.PositionByPointer = HCOMP.DebugInfo.PositionByPointer + CPULib.Debugger.PointersByLine = HCOMP.DebugInfo.PointersByLine + + CPULib.SuccessCallback() + end + timer.Remove("cpulib_compile") + CPULib.Compiling = false + + return + end + end + end + + + ------------------------------------------------------------------------------ + -- Uploader timer + function CPULib.OnUploadTimer() + if not CPULib.RemainingData then return end + + local upload_speed = wire_cpu_upload_speed:GetFloat() -- Number of index/value pairs to send (11 bytes each) + if game.SinglePlayer() then upload_speed = 5000 end + + local iters = math.min(upload_speed, CPULib.RemainingUploadData) + net.Start("wire_cpulib_buffer") + net.WriteUInt(iters, 16) + for _ = 1, iters do + local index,value = next(CPULib.RemainingData) + CPULib.RemainingUploadData = CPULib.RemainingUploadData - 1 + net.WriteUInt(index, 24) + net.WriteDouble(value or 0) -- 64bits, in case theres any float literals. Int21 is sufficient for all function calls/memory addresses + CPULib.RemainingData[index] = nil + end + if CPULib.RemainingUploadData <= 0 then + timer.Remove("cpulib_upload") + net.WriteBit(true) -- End + CPULib.Uploading = false + else + net.WriteBit(false) -- Keep going + end + net.SendToServer() + end + + ------------------------------------------------------------------------------ + -- Start upload + function CPULib.Upload(customBuffer) + -- Stop any upload in the progress + timer.Remove("cpulib_upload") + + -- Send the buffer over to server + net.Start("wire_cpulib_bufferstart") net.WriteString(CPULib.CPUName or "") net.SendToServer() + + CPULib.TotalUploadData = 0 + CPULib.RemainingData = {} + if customBuffer then + for k,v in pairs(customBuffer) do + CPULib.RemainingData[k] = v + CPULib.TotalUploadData = CPULib.TotalUploadData + 1 + end + else + for k,v in pairs(CPULib.Buffer) do + CPULib.RemainingData[k] = v + CPULib.TotalUploadData = CPULib.TotalUploadData + 1 + end + end + + CPULib.RemainingUploadData = CPULib.TotalUploadData + timer.Create("cpulib_upload",0.5,0,CPULib.OnUploadTimer) + CPULib.Uploading = true + end + + ------------------------------------------------------------------------------ + -- Get debug text for specific variable/function name + function CPULib.GetDebugPopupText(var) + if not var then return "" end + local csvar = string.upper(var) + + if CPULib.Debugger.Variables[csvar] then + return var.." = "..CPULib.Debugger.Variables[csvar] + else + if CPULib.Debugger.Labels[csvar] then + if CPULib.Debugger.Labels[csvar].Offset then + if not CPULib.Debugger.MemoryVariableByName[csvar] then + CPULib.Debugger.MemoryVariableByName[csvar] = CPULib.Debugger.Labels[csvar].Offset + table.insert(CPULib.Debugger.MemoryVariableByIndex,csvar) + RunConsoleCommand("wire_cpulib_debugvar",#CPULib.Debugger.MemoryVariableByIndex,CPULib.Debugger.Labels[csvar].Offset) + end + return var.." = ..." + elseif CPULib.Debugger.Labels[csvar].Pointer then + return var.." = ptr "..CPULib.Debugger.Labels[csvar].Pointer + else + return var.." = cannot resolve" + end + end + end + end + + ------------------------------------------------------------------------------ + -- Get debug text for specific variable/function name + CPULib.InterruptText = nil + function CPULib.GetDebugWindowText() + local result = { + "EAX = "..(CPULib.Debugger.Variables.EAX or "#####"), + "EBX = "..(CPULib.Debugger.Variables.EBX or "#####"), + "ECX = "..(CPULib.Debugger.Variables.ECX or "#####"), + "EDX = "..(CPULib.Debugger.Variables.EDX or "#####"), + "ESI = "..(CPULib.Debugger.Variables.ESI or "#####"), + "EDI = "..(CPULib.Debugger.Variables.EDI or "#####"), + "EBP = "..(CPULib.Debugger.Variables.EBP or "#####"), + "ESP = "..(CPULib.Debugger.Variables.ESP or "#####"), + } + + table.insert(result,"") + local maxReg = 7 + if wire_cpu_show_all_registers:GetFloat() == 1 then maxReg = 31 end + + for reg=0,maxReg do + table.insert(result,"R"..reg.." = "..(CPULib.Debugger.Variables["R"..reg] or "#####")) + end + + + table.insert(result,"") + if CPULib.Debugger.Variables.IP == INVALID_BREAKPOINT_IP then + table.insert(result,"IP = #####") + else + table.insert(result,"IP = "..(CPULib.Debugger.Variables.IP or "#####")) + end + + if CPULib.InterruptText then + table.insert(result,"") + table.insert(result,CPULib.InterruptText) + end + + return result + end + + ------------------------------------------------------------------------------ + -- Invalidate debugger data + function CPULib.InvalidateDebugger() + CPULib.InterruptText = nil + CPULib.Debugger.MemoryVariableByIndex = {} + CPULib.Debugger.MemoryVariableByName = {} + CPULib.Debugger.Breakpoint = {} + CPULib.Debugger.Variables = {} + CPULib.Debugger.FirstFile = nil + CPULib.DebugUpdateHighlights() + end + + net.Receive("CPULib.InvalidateDebugger", function(netlen) + local state = net.ReadUInt(2) -- 0: No change just invalidate, 1: detach, 2: attach + if state == 1 then + CPULib.DebuggerAttached = false + GAMEMODE:AddNotify("CPU debugger detached!",NOTIFY_GENERIC,7) + elseif state == 2 then + CPULib.DebuggerAttached = true + GAMEMODE:AddNotify("CPU debugger has been attached!",NOTIFY_GENERIC,7) + end + CPULib.InvalidateDebugger() + end) + + -- Get breakpoint at line + function CPULib.GetDebugBreakpoint(fileName,caretPos) + if not fileName or not caretPos then return nil end + return CPULib.Debugger.Breakpoint[caretPos[1]..":"..fileName] + end + + -- Set breakpoint at line + -- FIXME: bug: can only set breakpoints in one file + function CPULib.SetDebugBreakpoint(fileName,caretPos,condition) + if not fileName or not caretPos then return nil end + if not condition then + CPULib.Debugger.Breakpoint[caretPos[1]..":"..fileName] = nil + if CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName] then + RunConsoleCommand("wire_cpulib_debugbreakpoint",CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName][1],0) + end + else + if CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName] then + CPULib.Debugger.Breakpoint[caretPos[1]..":"..fileName] = condition + RunConsoleCommand("wire_cpulib_debugbreakpoint",CPULib.Debugger.PointersByLine[caretPos[1]..":"..fileName][1],condition) + end + end + + CPULib.DebugUpdateHighlights(true) + end + + -- Update highlighted lines + function CPULib.DebugUpdateHighlights(dontForcePosition) + if ZCPU_Editor then + -- Highlight current position + local currentPosition = CPULib.Debugger.PositionByPointer[CPULib.Debugger.Variables.IP] + + if currentPosition then + -- Clear all highlighted lines + for tab=1,ZCPU_Editor:GetNumTabs() do + ZCPU_Editor:GetEditor(tab):ClearHighlightedLines() + end + + local textEditor = CPULib.SelectTab(ZCPU_Editor,currentPosition.File) + if textEditor then + textEditor:HighlightLine(currentPosition.Line,130,0,0,255) + if not dontForcePosition then + textEditor:SetCaret({currentPosition.Line,1}) --currentPosition.Col + end + end + end + + -- Highlight breakpoints + for key,breakpoint in pairs(CPULib.Debugger.Breakpoint) do + local line = tonumber(string.sub(key,1,(string.find(key,":") or 0) - 1)) or 0 + local file = string.sub(key, (string.find(key,":") or 0) + 1) + + local textEditor = CPULib.SelectTab(ZCPU_Editor,file) + if textEditor then + if currentPosition and (currentPosition.Line == line) then + if breakpoint == true then + textEditor:HighlightLine(line,130,70,20,255) + else + textEditor:HighlightLine(line,130,20,70,255) + end + else + if breakpoint == true then + textEditor:HighlightLine(line,0,70,20,255) + else + textEditor:HighlightLine(line,0,20,70,255) + end + end + end + end + end + end + + ------------------------------------------------------------------------------ + -- Debug data arrived from server + function CPULib.OnDebugData_Registers(um) + CPULib.Debugger.Variables.IP = um:ReadFloat() + CPULib.Debugger.Variables.EAX = um:ReadFloat() + CPULib.Debugger.Variables.EBX = um:ReadFloat() + CPULib.Debugger.Variables.ECX = um:ReadFloat() + CPULib.Debugger.Variables.EDX = um:ReadFloat() + CPULib.Debugger.Variables.ESI = um:ReadFloat() + CPULib.Debugger.Variables.EDI = um:ReadFloat() + CPULib.Debugger.Variables.EBP = um:ReadFloat() + CPULib.Debugger.Variables.ESP = um:ReadFloat() + + for reg=0,31 do + CPULib.Debugger.Variables["R"..reg] = um:ReadFloat() + end + + CPULib.DebugUpdateHighlights() + end + usermessage.Hook("cpulib_debugdata_registers", CPULib.OnDebugData_Registers) + + function CPULib.OnDebugData_Variables(um) + local startIndex = um:ReadShort() + for varIdx = startIndex,startIndex+59 do + if CPULib.Debugger.MemoryVariableByIndex[varIdx] then + CPULib.Debugger.Variables[CPULib.Debugger.MemoryVariableByIndex[varIdx]] = um:ReadFloat() + end + end + end + usermessage.Hook("cpulib_debugdata_variables", CPULib.OnDebugData_Variables) + + function CPULib.OnDebugData_Interrupt(um) + local interruptNo,interruptParameter = um:ReadFloat(),um:ReadFloat() + CPULib.InterruptText = "Error #"..interruptNo.. " ["..interruptParameter.."]" + end + usermessage.Hook("cpulib_debugdata_interrupt", CPULib.OnDebugData_Interrupt) + + ------------------------------------------------------------------------------ + -- Show ZCPU/ZGPU documentation + CPULib.HandbookWindow = nil + + function CPULib.ShowDocumentation(platform) + local w = ScrW() * 2/3 + local h = ScrH() * 2/3 + local browserWindow = vgui.Create("DFrame") + browserWindow:SetTitle("Documentation") + browserWindow:SetPos((ScrW() - w)/2, (ScrH() - h)/2) + browserWindow:SetSize(w,h) + browserWindow:MakePopup() + + local browser = vgui.Create("DHTML",browserWindow) + browser:SetPos(10, 25) + browser:SetSize(w - 20, h - 35) + + browser:OpenURL("http://wiki.wiremod.com/wiki/Category:ZCPU_Handbook") + end +end + + + + + + + +if SERVER then + util.AddNetworkString("CPULib.ServerUploading") + ------------------------------------------------------------------------------ + -- Data received from server + CPULib.DataBuffer = {} + + ------------------------------------------------------------------------------ + -- Set this entity as a receiver for networked upload + function CPULib.SetUploadTarget(entity,player) + CPULib.DataBuffer[player:UserID()] = { + Entity = entity, + Player = player, + Data = {}, + } + end + + util.AddNetworkString("wire_cpulib_bufferstart") + net.Receive("wire_cpulib_bufferstart", function(netlen, player) + local Buffer = CPULib.DataBuffer[player:UserID()] + if (not Buffer) or (Buffer.Player ~= player) then return end + if not IsValid(Buffer.Entity) then return end + + net.Start("CPULib.ServerUploading") net.WriteBit(true) net.Send(player) + if Buffer.Entity:GetClass() == "gmod_wire_cpu" then + Buffer.Entity:SetCPUName(net.ReadString()) + end + end) + + -- Concommand to send a single stream of bytes + util.AddNetworkString("wire_cpulib_buffer") + net.Receive("wire_cpulib_buffer", function(netlen, player) + local Buffer = CPULib.DataBuffer[player:UserID()] + if (not Buffer) or (Buffer.Player ~= player) then return end + if not Buffer.Entity then return end + + for _ = 1, net.ReadUInt(16) do + Buffer.Data[net.ReadUInt(24)] = net.ReadDouble() + end + + if net.ReadBit() ~= 0 then -- We're done! + CPULib.DataBuffer[player:UserID()] = nil + net.Start("CPULib.ServerUploading") net.WriteBit(false) net.Send(player) + + if Buffer.Entity:GetClass() == "gmod_wire_cpu" then + Buffer.Entity:FlashData(Buffer.Data) + elseif Buffer.Entity:GetClass() == "gmod_wire_dhdd" then + for k,v in pairs(Buffer.Data) do + Buffer.Entity.Memory[k] = v + end + Buffer.Entity:ShowOutputs() + elseif (Buffer.Entity:GetClass() == "gmod_wire_gpu") or (Buffer.Entity:GetClass() == "gmod_wire_spu") then + Buffer.Entity:WriteCell(65535,0) + if Buffer.Entity.WriteCell then + for k,v in pairs(Buffer.Data) do + Buffer.Entity:WriteCell(k,v) + end + end + Buffer.Entity:WriteCell(65535,Buffer.Entity.Clk) + Buffer.Entity:WriteCell(65534,1) + else + if Buffer.Entity.WriteCell then + for k,v in pairs(Buffer.Data) do + Buffer.Entity:WriteCell(k,v) + end + end + end + end + end) + + ------------------------------------------------------------------------------ + -- Players and corresponding entities (for the debugger) + CPULib.DebuggerData = {} + + ------------------------------------------------------------------------------ + -- Attach a debugger + function CPULib.AttachDebugger(entity,player) + if entity then + entity.BreakpointInstructions = {} + entity.OnBreakpointInstruction = function(IP) + CPULib.SendDebugData(entity.VM,CPULib.DebuggerData[player:UserID()].MemPointers,player) + end + entity.OnVMStep = function() + if CurTime() - CPULib.DebuggerData[player:UserID()].PreviousUpdateTime > 0.2 then + CPULib.DebuggerData[player:UserID()].PreviousUpdateTime = CurTime() + + -- Send a fake update that messes up line pointer, updates registers + local tempIP = entity.VM.IP + entity.VM.IP = INVALID_BREAKPOINT_IP + CPULib.SendDebugData(entity.VM,nil,player) + entity.VM.IP = tempIP + end + end + if not entity.VM.BaseJump then + entity.VM.BaseJump = entity.VM.Jump + entity.VM.Jump = function(VM,IP,CS) + VM:BaseJump(IP,CS) + entity.ForceLastInstruction = true + end + entity.VM.BaseInterrupt = entity.VM.Interrupt + entity.VM.Interrupt = function(VM,interruptNo,interruptParameter,isExternal,cascadeInterrupt) + VM:BaseInterrupt(interruptNo,interruptParameter,isExternal,cascadeInterrupt) + if interruptNo < 27 then + CPULib.DebugLogInterrupt(player,interruptNo,interruptParameter,isExternal,cascadeInterrupt) + CPULib.SendDebugData(entity.VM,CPULib.DebuggerData[player:UserID()].MemPointers,player) + end + end + end + else + if CPULib.DebuggerData[player:UserID()] then + if CPULib.DebuggerData[player:UserID()].Entity and + CPULib.DebuggerData[player:UserID()].Entity.VM and + CPULib.DebuggerData[player:UserID()].Entity.VM.BaseInterrupt then + + CPULib.DebuggerData[player:UserID()].Entity.BreakpointInstructions = nil + if CPULib.DebuggerData[player:UserID()].Entity.VM.BaseJump then + CPULib.DebuggerData[player:UserID()].Entity.VM.Jump = CPULib.DebuggerData[player:UserID()].Entity.VM.BaseJump + CPULib.DebuggerData[player:UserID()].Entity.VM.Interrupt = CPULib.DebuggerData[player:UserID()].Entity.VM.BaseInterrupt + CPULib.DebuggerData[player:UserID()].Entity.VM.BaseJump = nil + CPULib.DebuggerData[player:UserID()].Entity.VM.BaseInterrupt = nil + end + end + end + end + + CPULib.DebuggerData[player:UserID()] = { + Entity = entity, + Player = player, + MemPointers = {}, + PreviousUpdateTime = CurTime(), + } + end + + -- Log debug interrupt + function CPULib.DebugLogInterrupt(player,interruptNo,interruptParameter,isExternal,cascadeInterrupt) + local umsgrp = RecipientFilter() + umsgrp:AddPlayer(player) + + umsg.Start("cpulib_debugdata_interrupt", umsgrp) + umsg.Float(interruptNo) + umsg.Float(interruptParameter) + umsg.End() + end + + -- Send debug log entry to client + function CPULib.SendDebugLogEntry(player,text) +-- + end + + -- Send debugging data to client + function CPULib.SendDebugData(VM,MemPointers,Player,onlyMemPointers) + local umsgrp = RecipientFilter() + umsgrp:AddPlayer(Player) + + if not onlyMemPointers then + umsg.Start("cpulib_debugdata_registers", umsgrp) + umsg.Float(VM.IP) + umsg.Float(VM.EAX) + umsg.Float(VM.EBX) + umsg.Float(VM.ECX) + umsg.Float(VM.EDX) + umsg.Float(VM.ESI) + umsg.Float(VM.EDI) + umsg.Float(VM.EBP) + umsg.Float(VM.ESP) + + for reg = 0,31 do + umsg.Float(VM["R"..reg]) + end + umsg.End() + end + + if MemPointers then + for msgIdx=0,math.floor(#MemPointers/60) do + umsg.Start("cpulib_debugdata_variables", umsgrp) + umsg.Short(msgIdx*60) + for varIdx=msgIdx*60,msgIdx*60+59 do + if MemPointers[varIdx] then + umsg.Float(VM:ReadCell(MemPointers[varIdx])) + end + end + umsg.End() + end + end + end + + -- Concommand to step forward + concommand.Add("wire_cpulib_debugstep", function(player, command, args) + local Data = CPULib.DebuggerData[player:UserID()] + if (not Data) or (Data.Player ~= player) then return end + if not IsValid(Data.Entity) then return end + + if not args[1] then -- Step forward + Data.Entity.VM:Step(1) + Data.Entity.VMStopped = true + else -- Run until instruction + Data.Entity.VMStopped = false + Data.Entity:NextThink(CurTime()) + + Data.Entity.LastInstruction = tonumber(args[1]) or 0 + Data.Entity.OnLastInstruction = function() + Data.Entity.LastInstruction = nil + Data.Entity.OnLastInstruction = nil + CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player) + end + end + CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player) + end) + + -- Concommand to run till breakpoint + concommand.Add("wire_cpulib_debugrun", function(player, command, args) + local Data = CPULib.DebuggerData[player:UserID()] + if (not Data) or (Data.Player ~= player) then return end + if not IsValid(Data.Entity) then return end + + -- Send a fake update that messes up line pointer + local tempIP = Data.Entity.VM.IP + Data.Entity.VM.IP = INVALID_BREAKPOINT_IP + CPULib.SendDebugData(Data.Entity.VM,nil,Data.Player) + Data.Entity.VM.IP = tempIP + + Data.Entity.Clk = true + Data.Entity:NextThink(CurTime()) + end) + + -- Concommand to reset + concommand.Add("wire_cpulib_debugreset", function(player, command, args) + local Data = CPULib.DebuggerData[player:UserID()] + if (not Data) or (Data.Player ~= player) then return end + if not IsValid(Data.Entity) then return end + + Data.Entity.VM:Reset() + CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player) + end) + + -- Concommand to add a variable + concommand.Add("wire_cpulib_debugvar", function(player, command, args) + local Data = CPULib.DebuggerData[player:UserID()] + if (not Data) or (Data.Player ~= player) then return end + if not IsValid(Data.Entity) then return end + + Data.MemPointers[tonumber(args[1]) or 0] = tonumber(args[2]) + CPULib.SendDebugData(Data.Entity.VM,Data.MemPointers,Data.Player,true) + end) + + -- Concommand to set a debug breakpoint + concommand.Add("wire_cpulib_debugbreakpoint", function(player, command, args) + local Data = CPULib.DebuggerData[player:UserID()] + if (not Data) or (Data.Player ~= player) then return end + if not IsValid(Data.Entity) then return end + + if tonumber(args[2]) == 0 then + Data.Entity.BreakpointInstructions[tonumber(args[1]) or 0] = nil + else + Data.Entity.BreakpointInstructions[tonumber(args[1]) or 0] = true + end + end) +end + + +-------------------------------------------------------------------------------- +-- Create a new virtual machine +-------------------------------------------------------------------------------- +function CPULib.VirtualMachine() + -- Create new instance of the VM + include("wire/zvm/zvm_core.lua") + + -- Remove from global scope + local newVM = ZVM + ZVM = nil + return newVM +end + + +-------------------------------------------------------------------------------- +-- Generate a serial number +-------------------------------------------------------------------------------- +local sessionBase, sessionDate +function CPULib.GenerateSN(entityType) + local currentDate = os.date("*t") + + local SNDate = (currentDate.year-2007)*500+(currentDate.yday) + if (not sessionBase) or (SNDate ~= sessionDate) then + sessionBase = math.floor(math.random()*99999) + sessionDate = SNDate + else + sessionBase = sessionBase + 1 + end + + if entityType == "CPU" then + return sessionBase + 100000 + SNDate*1000000 + elseif entityType == "SPU" then + return sessionBase + 200000 + SNDate*1000000 + elseif entityType == "GPU" then + return sessionBase + 300000 + SNDate*1000000 + elseif entityType == "UNK" then + return sessionBase + 700000 + SNDate*1000000 + end +end + + +-------------------------------------------------------------------------------- +-- Get device type +-------------------------------------------------------------------------------- +local DeviceType = { + ["gmod_wire_extbus"] = 2, + ["gmod_wire_addressbus"] = 3, + ["gmod_wire_cpu"] = 4, + ["gmod_wire_gpu"] = 5, + ["gmod_wire_spu"] = 6, + ["gmod_wire_hdd"] = 7, + ["gmod_wire_dhdd"] = 8, + ["gmod_wire_datarate"] = 9, + ["gmod_wire_cd_ray"] = 10, + ["gmod_wire_consolescreen"] = 11, + ["gmod_wire_digitalscreen"] = 12, + ["gmod_wire_dataplug"] = 13, + ["gmod_wire_datasocket"] = 14, + ["gmod_wire_keyboard"] = 15, + ["gmod_wire_oscilloscope"] = 16, + ["gmod_wire_soundemitter"] = 17, + ["gmod_wire_value"] = 18, + ["gmod_wire_dataport"] = 19, + ["gmod_wire_gate"] = 20, +} + +function CPULib.GetDeviceType(class) + return DeviceType[class] or 1 +end + + + + + +-------------------------------------------------------------------------------- +-- Columns in the instruction set reference table: +-- Opc - Instruction number +-- Mnemonic - Symbolic mnemonic (uppercase). Can be "RESERVED" +-- Ops - Number of operands +-- Version - Minimum CPU version required +-- Flags - Several or none of the following flags: +-- W1: single-operand opcode which writes 1st operand +-- R0: runlevel 0 opcode (privileged opcode) +-- OB: obsolete/should not be used +-- UB: unconditional branching instruction +-- CB: conditional branching instruction +-- TR: trigonometric syntax operand +-- OL: old mnemonic for the instruction +-- BL: instruction supports block prefix +-- Op1 - operand 1 name +-- Op2 - operand 2 name + +-- Possible operand names: +-- X,Y: arbitrary integer or floating-point value +-- PTR: 48-bit pointer into memory +-- CS: 48-bit pointer into memory, new value of CS segment +-- IDX: unsigned integer index into internal processor table +-- PAGE: unsigned integer page number (each page is 128 bytes) +-- PORT: unsigned 47-bit integer port number +-- BIT: integer between 0 and 47 +-- INTR: interrupt nubmer (integer between 0 and 255) +-- SIZE: unsigned 47-bit integer memory block size +-- +-- COLOR: 4-byte color +-- VEC2F: 2-byte vector +-- +-- INT: 48-bit signed integer + +CPULib.InstructionTable = {} +local W1,R0,OB,UB,CB,TR,OL,BL = 1,2,4,8,16,32,64,128 + +local function Bit(x,n) return (math.floor(x / n) % 2) == 1 end +local function Entry(Set,Opc,Mnemonic,Ops,Version,Flags,Op1,Op2,Reference) + table.insert(CPULib.InstructionTable, + { Set = Set, + Opcode = Opc, + Mnemonic = Mnemonic, + OperandCount = Ops, + MinimumVersion = Version, + Operand1 = Op1, + Operand2 = Op2, + Reference = Reference, + + WritesFirstOperand = Bit(Flags,W1), + Privileged = Bit(Flags,R0), + Obsolete = Bit(Flags,OB), + UnconditionalBranching = Bit(Flags,UB), + ConditionalBranching = Bit(Flags,CB), + Trigonometric = Bit(Flags,TR), + Old = Bit(Flags,OL), + BlockPrefix = Bit(Flags,BL), + }) +end +local function CPU(...) Entry("CPU",...) end +local function GPU(...) Entry("GPU",...) end +local function VEX(...) Entry("VEX",...) end +local function SPU(...) Entry("SPU",...) end + + +------------------------------------------------------------------------------------------------------------------------------------------------- +-- Zyelios CPU/GPU/SPU instruction set reference table +------------------------------------------------------------------------------------------------------------------------------------------------- +--- Opc Mnemonic ------- Ops Version Flags ---- Op1 ---- Op2 ---- Reference ------------------------------------------------------------------ +CPU(000, "RESERVED", 0, 0.00, 0, "", "", "Stop processor execution") +CPU(001, "JNE", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is not equal") +CPU(001, "JNZ", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is not zero") +CPU(002, "JMP", 1, 1.00, UB, "PTR", "", "Jump to PTR") +CPU(003, "JG", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is greater") +CPU(003, "JNLE", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is not less or equal") +CPU(004, "JGE", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is greater or equal") +CPU(004, "JNL", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is not less") +CPU(005, "JL", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is less") +CPU(005, "JNGE", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is not greater or equal") +CPU(006, "JLE", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is less or equal") +CPU(006, "JNG", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is not greater") +CPU(007, "JE", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is equal") +CPU(007, "JZ", 1, 1.00, CB, "PTR", "", "Jump to PTR if result is zero") +CPU(008, "CPUID", 1, 1.00, 0, "IDX", "", "Write processor information variable IDX into EAX register") +CPU(009, "PUSH", 1, 1.00, 0, "X", "", "Push X onto processor stack") +---- Dec 1 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(010, "ADD", 2, 1.00, 0, "X", "Y", "X = X + Y") +CPU(011, "SUB", 2, 1.00, 0, "X", "Y", "X = X - Y") +CPU(012, "MUL", 2, 1.00, 0, "X", "Y", "X = X * Y") +CPU(013, "DIV", 2, 1.00, 0, "X", "Y", "X = X / Y") +CPU(014, "MOV", 2, 1.00, 0, "X", "Y", "X = Y") +CPU(015, "CMP", 2, 1.00, 0, "X", "Y", "Compare X and Y. Use with conditional branching instructions") +CPU(016, "RD", 2, 1.00, R0+OB, "X", "PTR", "Read value from memory by pointer PTR") +CPU(017, "WD", 2, 1.00, R0+OB, "PTR", "Y", "Write value to memory by pointer PTR") +CPU(018, "MIN", 2, 1.00, 0, "X", "Y", "Set X to smaller value out of X and Y") +CPU(019, "MAX", 2, 1.00, 0, "X", "Y", "Set X to bigger value out of X and Y") +---- Dec 2 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(020, "INC", 1, 1.00, W1, "X", "", "Increase X by one") +CPU(021, "DEC", 1, 1.00, W1, "X", "", "Decrease X by one") +CPU(022, "NEG", 1, 1.00, W1, "X", "", "Change sign of X") +CPU(023, "RAND", 1, 1.00, W1, "X", "", "Set X to random value") +CPU(024, "LOOP", 1, 2.00, CB, "PTR", "", "Decrease ECX, and if ECX is not set to 0, jump to PTR") +CPU(024, "LOOPC", 1, 10.00, CB, "PTR", "", "Decrease ECX, and if ECX is not set to 0, jump to PTR") +CPU(025, "LOOPA", 1, 2.00, CB, "PTR", "", "Decrease EAX, and if EAX is not set to 0, jump to PTR") +CPU(026, "LOOPB", 1, 2.00, CB, "PTR", "", "Decrease EBX, and if EBX is not set to 0, jump to PTR") +CPU(027, "LOOPD", 1, 2.00, CB, "PTR", "", "Decrease EDX, and if EDX is not set to 0, jump to PTR") +CPU(028, "SPG", 1, 2.00, R0, "PAGE", "", "Make PAGE readonly") +CPU(029, "CPG", 1, 2.00, R0, "PAGE", "", "Make PAGE readable and writeable") +---- Dec 3--------------------------------------------------------------------------------------------------------------------------------------- +CPU(030, "POP", 1, 1.00, 0, "X", "", "Pop value off stack and write it into X") +CPU(031, "CALL", 1, 1.00, UB, "PTR", "", "Call subroutine by address PTR") +CPU(032, "BNOT", 1, 1.00, W1, "INT", "", "Flip all bits in the integer number") +CPU(033, "FINT", 1, 1.00, W1, "X", "", "Force X to be an integer value") +CPU(034, "FRND", 1, 1.00, W1, "X", "", "Round X to the nearest integer value") +CPU(034, "RND", 1, 1.00, W1+OL, "X", "", "FRND") +CPU(035, "FFRAC", 1, 1.00, W1, "X", "", "Remove integer part of the X, leaving only the fractional part") +CPU(036, "FINV", 1, 1.00, W1, "X", "", "X = 1 / X") +CPU(037, "HALT", 1, 1.00, OB, "PORT", "", "Halt processor execution until PORT is written to") +CPU(038, "FSHL", 1, 2.00, W1, "X", "", "Multiply X by 2 (does not floor)") +CPU(039, "FSHR", 1, 2.00, W1, "X", "", "Divide X by 2 (does not floor)") +---- Dec 4 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(040, "RET", 0, 1.00, UB, "", "", "Return from a subroutine") +CPU(041, "IRET", 0, 2.00, UB, "", "", "Return from an interrupt") +CPU(042, "STI", 0, 2.00, R0, "", "", "Enable interrupt handling") +CPU(043, "CLI", 0, 2.00, R0, "", "", "Disable interrupt handling") +CPU(044, "STP", 0, 2.00, R0+OB, "", "", "Enable protected mode") +CPU(045, "CLP", 0, 2.00, R0+OB, "", "", "Disable protected mode") +CPU(046, "RESERVED", 0, 0.00, R0, "", "", "") +CPU(047, "RETF", 0, 1.00, UB, "", "", "Return from a far subroutine call") +CPU(048, "STEF", 0, 4.00, R0, "", "", "Enable extended mode") +CPU(049, "CLEF", 0, 4.00, R0, "", "", "Disable extended mode") +---- Dec 5 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(050, "AND", 2, 1.00, 0, "X", "Y", "Logical AND between X and Y") +CPU(051, "OR", 2, 1.00, 0, "X", "Y", "Logical OR between X and Y") +CPU(052, "XOR", 2, 1.00, 0, "X", "Y", "Logical XOR between X and Y") +CPU(053, "FSIN", 2, 1.00, TR, "X", "Y", "Write sine of X to Y") +CPU(054, "FCOS", 2, 1.00, TR, "X", "Y", "Write cosine of X to Y") +CPU(055, "FTAN", 2, 1.00, TR, "X", "Y", "Write tangent of X to Y") +CPU(056, "FASIN", 2, 1.00, TR, "X", "Y", "Write arcsine of X to Y") +CPU(057, "FACOS", 2, 1.00, TR, "X", "Y", "Write arccosine of X to Y") +CPU(058, "FATAN", 2, 1.00, TR, "X", "Y", "Write arctangent of X to Y") +CPU(059, "MOD", 2, 2.00, 0, "X", "Y", "Write remainder of X/Y to Y") +---- Dec 6 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(060, "BIT", 2, 2.00, 0, "INT", "BIT", "Test whether BIT of X is set. Use with conditional branching instructions") +CPU(061, "SBIT", 2, 2.00, 0, "INT", "BIT", "Set BIT of X") +CPU(062, "CBIT", 2, 2.00, 0, "INT", "BIT", "Clear BIT of X") +CPU(063, "TBIT", 2, 2.00, 0, "INT", "BIT", "Toggle BIT of X") +CPU(064, "BAND", 2, 2.00, 0, "INT", "INT", "Write result of binary AND between operands") +CPU(065, "BOR", 2, 2.00, 0, "INT", "INT", "Write result of binary OR between operands") +CPU(066, "BXOR", 2, 2.00, 0, "INT", "INT", "Write result of binary XOR between operands") +CPU(067, "BSHL", 2, 2.00, 0, "INT", "X", "Shift bits of INT left by X") +CPU(068, "BSHR", 2, 2.00, 0, "INT", "X", "Shift bits of INT right by X") +CPU(069, "JMPF", 2, 2.00, UB, "PTR", "CS", "Jump to PTR in code segment CS") +---- Dec 7 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(070, "NMIINT", 1, 4.00, R0+OL, "INTR", "", "EXTINT") +CPU(070, "EXTINT", 1, 10.00, R0, "INTR", "", "Call interrupt INTR as an external interrupt") +CPU(071, "CNE", 1, 2.00, CB, "PTR", "", "Call subrotine if result is not equal") +CPU(071, "CNZ", 1, 2.00, CB, "PTR", "", "Call subrotine if result is not zero") +CPU(072, "RESERVED", 1, 0.00, 0, "", "", "") +CPU(073, "CG", 1, 2.00, CB, "PTR", "", "Call subrotine if result is greater") +CPU(073, "CNLE", 1, 2.00, CB, "PTR", "", "Call subrotine if result is not less or equal") +CPU(074, "CGE", 1, 2.00, CB, "PTR", "", "Call subrotine if result is greater or equal") +CPU(074, "CNL", 1, 2.00, CB, "PTR", "", "Call subrotine if result is not less") +CPU(075, "CL", 1, 2.00, CB, "PTR", "", "Call subrotine if result is less") +CPU(075, "CNGE", 1, 2.00, CB, "PTR", "", "Call subrotine if result is not greater or equal") +CPU(076, "CLE", 1, 2.00, CB, "PTR", "", "Call subrotine if result is less or equal") +CPU(076, "CNG", 1, 2.00, CB, "PTR", "", "Call subrotine if result is not greater") +CPU(077, "CE", 1, 2.00, CB, "PTR", "", "Call subrotine if result is equal") +CPU(077, "CZ", 1, 2.00, CB, "PTR", "", "Call subrotine if result is zero") +CPU(078, "MCOPY", 1, 2.00, BL, "INT", "", "Copy INT bytes from array pointed by ESI to EDI") +CPU(079, "MXCHG", 1, 2.00, BL, "INT", "", "Swap INT bytes between two arrays pointed by ESI and EDI") +---- Dec 8 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(080, "FPWR", 2, 2.00, 0, "X", "Y", "Raise X to power Y") +CPU(081, "XCHG", 2, 2.00, 0, "X", "Y", "Swap X and Y") +CPU(082, "FLOG", 2, 2.00, OL, "X", "Y", "FLN") +CPU(082, "FLN", 2, 10.00, 0, "X", "Y", "Write logarithm (base e) of Y to X") +CPU(083, "FLOG10", 2, 2.00, 0, "X", "Y", "Write logarithm (base 10) of Y to X") +CPU(084, "IN", 2, 2.00, 0, "X", "PORT", "Input value from PORT to X") +CPU(085, "OUT", 2, 2.00, 0, "PORT", "Y", "Write X to PORT") +CPU(086, "FABS", 2, 2.00, TR, "X", "Y", "Write absolute value of Y to X") +CPU(087, "FSGN", 2, 2.00, TR, "X", "Y", "Write sign of Y to X") +CPU(088, "FEXP", 2, 2.00, TR, "X", "Y", "Write exponent of Y to X") +CPU(089, "CALLF", 2, 2.00, UB, "PTR", "CS", "Call subroutine by offset PTR in code segment CS") +---- Dec 9 -------------------------------------------------------------------------------------------------------------------------------------- +CPU(090, "FPI", 1, 2.00, W1, "X", "", "Set X to precise value of PI (3.1415926..)") +CPU(091, "FE", 1, 2.00, W1, "X", "", "Set X to precise value of E (2.7182818..)") +CPU(092, "INT", 1, 2.00, 0, "INTR", "", "Call interrupt INTR") +CPU(093, "TPG", 1, 2.00, 0, "PAGE", "", "Test PAGE. Use branching instructions to test for zero on failure, non-zero if test passed.") +CPU(094, "FCEIL", 1, 2.00, W1, "X", "", "Rounds X up to the next integer") +CPU(095, "ERPG", 1, 2.00, R0, "PAGE", "", "Erase ROM page") +CPU(096, "WRPG", 1, 2.00, R0, "PAGE", "", "Copy RAM page into ROM page") +CPU(097, "RDPG", 1, 2.00, R0, "PAGE", "", "Read ROM page into RAM") +CPU(098, "TIMER", 1, 2.00, W1, "X", "", "Set X to value of the internal processor timer") +CPU(099, "LIDTR", 1, 2.00, R0, "PTR", "", "Set interrupt table pointer to PTR") +---- Dec 10 ------------------------------------------------------------------------------------------------------------------------------------- +CPU(100, "RESERVED", 1, 0.00, R0, "", "", "") +CPU(101, "JNER", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is not equal") +CPU(101, "JNZR", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is not zero") +CPU(102, "JMPR", 1, 3.00, UB, "INT", "", "Relative jump INT bytes forward") +CPU(103, "JGR", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is greater") +CPU(103, "JNLER", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is not less or equal") +CPU(104, "JGER", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is greater or equal") +CPU(104, "JNLR", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is not less") +CPU(105, "JLR", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is less") +CPU(105, "JNGER", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is not greater or equal") +CPU(106, "JLER", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is less or equal") +CPU(106, "JNGR", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is not greater") +CPU(107, "JER", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is equal") +CPU(107, "JZR", 1, 3.00, CB, "INT", "", "Relative jump INT bytes forward if result is zero") +CPU(108, "LNEG", 1, 3.00, W1, "X", "", "Logically negate X") +CPU(109, "RESERVED", 1, 0.00, R0, "", "", "") +---- Dec 11 ------------------------------------------------------------------------------------------------------------------------------------- +CPU(110, "NMIRET", 0, 2.00, R0+OL, "", "", "EXTRET") +CPU(110, "EXTRET", 0, 10.00, R0, "", "", "Return from an external interrupt") +CPU(111, "IDLE", 0, 4.00, R0, "", "", "Skip several processor cycles") +CPU(112, "NOP", 0, 5.00, 0, "", "", "Do nothing") +CPU(113, "RESERVED", 0, 0.00, 0, "", "", "") +CPU(114, "PUSHA", 0, 8.00, 0, "", "", "Push all general purpose registers to stack") +CPU(115, "POPA", 0, 8.00, 0, "", "", "Pop all general purpose registers off stack") +CPU(116, "STD2", 0, 10.00, R0, "", "", "Enable hardware debug mode") +CPU(117, "LEAVE", 0, 10.00, 0, "", "", "Leave subroutine stack frame") +CPU(118, "STM", 0, 10.00, R0, "", "", "Enable extended memory mode") +CPU(119, "CLM", 0, 10.00, R0, "", "", "Disable extended memory mode") +---- Dec 12 ------------------------------------------------------------------------------------------------------------------------------------- +CPU(120, "CPUGET", 2, 5.00, R0, "X", "IDX", "Read internal processor register IDX") +CPU(121, "CPUSET", 2, 5.00, R0, "IDX", "Y", "Write internal processor register IDX") +CPU(122, "SPP", 2, 5.00, R0+BL, "PAGE", "IDX", "Set page flag IDX") +CPU(123, "CPP", 2, 5.00, R0+BL, "PAGE", "IDX", "Clear page flag IDX") +CPU(124, "SRL", 2, 5.00, R0+BL, "PAGE", "INT", "Set page runlevel to INT") +CPU(125, "CRL", 2, 5.00, R0, "X", "PAGE", "Write page runlevel to INT") +CPU(126, "LEA", 2, 5.00, 0, "X", "Y", "Load absolute address fetched by operand Y into X") +CPU(127, "BLOCK", 2, 6.00, 0, "PTR", "SIZE", "Make next instruction run on this block") +CPU(128, "CMPAND", 2, 6.00, 0, "X", "Y", "Compare X and Y, and logically combine with result of previous comparsion using AND") +CPU(129, "CMPOR", 2, 6.00, 0, "X", "Y", "Compare X and Y, and logically combine with result of previous comparsion using OR") +---- Dec 13 ------------------------------------------------------------------------------------------------------------------------------------- +CPU(130, "MSHIFT", 2, 7.00, 0, "COUNT", "OFFSET","Shift (and rotate) data pointed by ESI by OFFSET bytes") +CPU(131, "SMAP", 2, 8.00, R0+BL, "PAGE1", "PAGE2", "Remap PAGE1 to physical page PAGE2") +CPU(132, "GMAP", 2, 8.00, R0, "X", "PAGE", "Read what physical page PAGE is mapped to") +CPU(133, "RSTACK", 2, 9.00, 0, "X", "IDX", "Read value from stack at offset IDX (from address SS+IDX)") +CPU(134, "SSTACK", 2, 9.00, 0, "IDX", "Y", "Write value to stack at offset IDX (to address SS+IDX)") +CPU(135, "ENTER", 1, 10.00, 0, "SIZE", "", "Enter stack frame and allocate SIZE bytes on stack for local variables") +CPU(136, "IRETP", 1, 2.00, R0, "PTBL", "", "Set PTBL, then return from an interrupt") +CPU(137, "EXTRETP", 1, 10.00, R0, "PTBL", "", "Set PTBL, then return from an external interrupt") +---- Dec 14 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +CPU(140, "EXTRETA", 0, 11.00, R0, "", "", "Return from an external interrupt and restore R0-R31 registers") +CPU(141, "EXTRETPA", 1, 11.00, R0, "PTBL", "", "Set PTBL, then return from an external interrupt with restoring R0-R31 registers") +---- Dec 15 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +---- Dec 16 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +---- Dec 17 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +---- Dec 18 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +---- Dec 19 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +---- Dec 20 -- Output buffer control ------------------------------------------------------------------------------------------------------------ +GPU(200, "DTEST", 0, 1.0, 0, "", "", "Output a test pattern to screen") +GPU(200, "DRECT_TEST", 0, 0.5, OL, "", "", "DTEST") +GPU(201, "DEXIT", 0, 0.5, UB, "", "", "End execution of the current frame") +GPU(201, "DVSYNC", 0, 1.0, 0, "", "", "Wait until next frame (only in asynchronous thread)") +GPU(202, "DCLR", 0, 0.5, 0, "", "", "Clear screen color to black") +GPU(203, "DCLRTEX", 0, 0.5, 0, "", "", "Clear background with texture") +GPU(204, "DVXFLUSH", 0, 0.6, 0, "", "", "Flush current vertex buffer to screen") +GPU(205, "DVXCLEAR", 0, 0.6, 0, "", "", "Clear vertex buffer") +GPU(206, "DSETBUF_VX", 0, 1.0, 0, "", "", "Set frame buffer to vertex output") +GPU(207, "DSETBUF_SPR", 0, 1.0, 0, "", "", "Set frame buffer to sprite buffer") +GPU(207, "DBACKBUF", 0, 1.0, 0, "", "", "Set frame buffer to back buffer") +GPU(208, "DSETBUF_FBO", 0, 1.0, 0, "", "", "Set frame buffer to view buffer") +GPU(208, "DFRONTBUF", 0, 1.0, 0, "", "", "Set frame buffer to front buffer") +GPU(209, "DSWAP", 0, 1.0, 0, "", "", "Copy back buffer to front buffer") +---- Dec 21 -- Pipe controls and one-operand opcodes -------------------------------------------------------------------------------------------- +GPU(210, "DVXPIPE", 1, 0.5, 0, "IDX", "", "Set vertex pipe") +GPU(211, "DCVXPIPE", 1, 0.5, OL, "IDX", "", "DCPIPE") +GPU(211, "DCPIPE", 1, 1.0, 0, "IDX", "", "Set coordinate pipe") +GPU(212, "DENABLE", 1, 0.5, 0, "IDX", "", "Enable parameter") +GPU(213, "DDISABLE", 1, 0.5, 0, "IDX", "", "Disable parameter") +GPU(214, "DCLRSCR", 1, 0.5, 0, "COLOR", "", "Clear screen with color") +GPU(215, "DCOLOR", 1, 0.5, 0, "COLOR", "", "Set current color") +GPU(216, "DTEXTURE", 1, 1.0, 0, "IDX", "", "Set current texture") +GPU(217, "DSETFONT", 1, 0.5, 0, "IDX", "", "Set current font") +GPU(218, "DSETSIZE", 1, 0.5, 0, "INT", "", "Set font size") +GPU(219, "DMOVE", 1, 0.5, 0, "VEC2F", "", "Set drawing position offset") +---- Dec 22 -- Rendering opcodes ---------------------------------------------------------------------------------------------------------------- +GPU(220, "DVXDATA_2F", 2, 0.5, 0, "VEC2F", "IDX", "Draw a solid 2D polygon (pointer to 2D data, vertex count)") +GPU(220, "DVXPOLY", 2, 0.5, 0, "VEC2F", "IDX", "Draw a solid 2D polygon (pointer to 2D data, vertex count)") +GPU(221, "DVXDATA_2F_TEX",2, 0.5, 0, "VEC2F", "IDX", "Draw a textured 2D polygon (pointer to 2D data, vertex count)") +GPU(221, "DVXTEXPOLY", 2, 0.5, 0, "VEC2F", "IDX", "Draw a textured 2D polygon (pointer to 2D data, vertex count)") +GPU(222, "DVXDATA_3F", 2, 0.5, 0, "VEC3F", "IDX", "Draw a solid 3D polygon (pointer to 3D data, vertex count)") +GPU(223, "DVXDATA_3F_TEX",2, 0.5, 0, "VEC3FT","IDX", "Draw a textured 3D polygon (pointer to 3D data, vertex count)") +GPU(224, "DVXDATA_3F_WF", 2, 0.5, 0, "VEC3F", "IDX", "Draw a wireframe 3D polygon (pointer to 3D data, vertex count)") +GPU(225, "DRECT", 2, 0.5, 0, "VEC2F", "VEC2F", "Draw a rectangle (by endpoints)") +GPU(226, "DCIRCLE", 2, 0.5, 0, "VEC2F", "Y", "Draw a circle with radius Y") +GPU(227, "DLINE", 2, 0.5, 0, "VEC2F", "VEC2F", "Draw a line") +GPU(228, "DRECTWH", 2, 0.6, 0, "VEC2F", "VEC2F", "Draw a rectangle (by offset, size)") +GPU(229, "DORECT", 2, 0.5, 0, "VEC2F", "VEC2F", "Draw an outlined rectangle") +---- Dec 23 -- Additional rendering opcodes ----------------------------------------------------------------------------------------------------- +GPU(230, "DTRANSFORM2F", 2, 0.5, 0, "VEC2F", "VEC2F", "Transform vector and write it to first operand") +GPU(231, "DTRANSFORM3F", 2, 0.5, 0, "VEC3F", "VEC3F", "Transform vector and write it to first operand") +GPU(232, "DSCRSIZE", 2, 0.5, 0, "X", "Y", "Set screen size") +GPU(233, "DROTATESCALE", 2, 0.5, 0, "X", "Y", "Rotate by X, scale by Y") +GPU(234, "DORECTWH", 2, 0.5, 0, "VEC2F", "VEC2F", "Draw an outlined rectangle by width/height") +GPU(235, "DCULLMODE", 2, 0.7, 0, "IDX", "IDX", "Set cullmode and lighting mode") +--GPU(236, "DARRAY", 2, 1.0, 0, "VEC2F", "STRUCT","Draw an array of pixels") +--GPU(237, "DDTERMINAL", 2, 1.0, 0, "VEC2F", "STRUCT","Draw a console screen/terminal window") +GPU(238, "DPIXEL", 2, 1.0, 0, "VEC2F", "COLOR", "Draw a pixel to screen") +GPU(239, "RESERVED", 2, 0.0, 0, "", "", "") +---- Dec 24 -- Text output and lighting --------------------------------------------------------------------------------------------------------- +GPU(240, "DWRITE", 2, 0.5, 0, "VEC2F", "STRING","Write a string") +GPU(241, "DWRITEI", 2, 0.5, 0, "VEC2F", "INT", "Write an integer value") +GPU(242, "DWRITEF", 2, 0.5, 0, "VEC3F", "Y", "Write a float value") +GPU(243, "DENTRYPOINT", 2, 0.5, 0, "IDX", "PTR", "Set entry point") +GPU(244, "DSETLIGHT", 2, 0.6, 0, "IDX", "STRUCT","Set light") +GPU(245, "DGETLIGHT", 2, 0.6, 0, "STRUCT","IDX", "Get light") +GPU(246, "DWRITEFMT", 2, 0.6, 0, "VEC2F", "STRING","Write a formatted string") +GPU(247, "DWRITEFIX", 2, 0.5, 0, "VEC2F", "Y", "Write a fixed value") +GPU(248, "DTEXTWIDTH", 2, 0.8, 0, "INT", "STRING","Return text width") +GPU(249, "DTEXTHEIGHT", 2, 0.8, 0, "INT", "STRING","Return text height") +---- Dec 25 -- Vector mode extension ------------------------------------------------------------------------------------------------------------ +VEX(250, "VADD", 2, 7.00, 0, "VEC", "VEC", "X = X + Y") +VEX(251, "VSUB", 2, 7.00, 0, "VEC", "VEC", "X = X - Y") +VEX(252, "VMUL", 2, 7.00, 0, "VEC", "X", "X = X * SCALAR Y") +VEX(253, "VDOT", 2, 7.00, 0, "VEC", "VEC", "X = X dot Y") +VEX(254, "VCROSS", 2, 7.00, 0, "VEC", "VEC", "X = X cross Y") +VEX(255, "VMOV", 2, 7.00, 0, "VEC", "VEC", "X = Y") +VEX(256, "VNORM", 2, 7.00, 0, "VEC", "VEC", "X = NORMALIZE(Y)") +VEX(257, "VCOLORNORM", 2, 10.0, 0, "COLOR", "COLOR", "Normalize color (clamp it to RGB range)") +GPU(258, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(259, "DLOOPXY", 2, 0.7, CB, "PTR", "PTR", "2D loop by ECX/EDX registers") +VEX(259, "LOOPXY", 2, 10.0, CB, "PTR", "PTR", "2D loop by ECX/EDX registers") +---- Dec 26 -- Matrix math ---------------------------------------------------------------------------------------------------------------------- +VEX(260, "MADD", 2, 7.00, 0, "MATRIX","MATRIX","X = X + Y") +VEX(261, "MSUB", 2, 7.00, 0, "MATRIX","MATRIX","X = X - Y") +VEX(262, "MMUL", 2, 7.00, 0, "MATRIX","MATRIX","X = X * Y") +VEX(263, "MROTATE", 2, 7.00, 0, "MATRIX","VEC4F", "Rotation matrix based on rotation vector") +VEX(264, "MSCALE", 2, 7.00, 0, "MATRIX","VEC4F", "Scaling matrix based on scaling vector") +VEX(265, "MPERSPECTIVE", 2, 7.00, 0, "MATRIX","VEC4F", "Perspective matrix based on FOV and near/far planes") +VEX(266, "MTRANSLATE", 2, 7.00, 0, "MATRIX","VEC4F", "Translation matrix based on translation vector") +VEX(267, "MLOOKAT", 2, 7.00, 0, "MATRIX","VEC4F", "Lookat matrix based on three vectors") +VEX(268, "MMOV", 2, 7.00, 0, "MATRIX","MATRIX","X = Y") +VEX(269, "VLEN", 2, 7.00, 0, "X", "VEC", "X = Sqrt(Y dot Y)") +---- Dec 27 -- Matrix math ---------------------------------------------------------------------------------------------------------------------- +VEX(270, "MIDENT", 1, 7.00, 0, "MATRIX","", "Load identity matrix") +GPU(271, "MLOADPROJ", 1, 0.6, 0, "MATRIX","", "Load matrix into view matrix") +GPU(272, "MREAD", 1, 0.6, 0, "MATRIX","", "Write view matrix into matrix") +VEX(273, "VMODE", 1, 7.00, 0, "IDX", "", "Set vector math mode") +GPU(274, "DT", 1, 0.6, W1, "X", "", "Set X to frame length time") +GPU(275, "RESERVED", 1, 0.0, 0, "", "", "") +GPU(276, "DSHADE", 1, 0.5, 0, "X", "", "Shade the current color") +GPU(277, "DSETWIDTH", 1, 0.5, 0, "X", "", "Set line width") +GPU(278, "MLOAD", 1, 0.6, 0, "MATRIX","", "Load matrix into model matrix") +GPU(279, "DSHADENORM", 1, 0.6, 0, "X", "", "Shade the current color and normalize it") +GPU(279, "DSHADECOL", 1, 0.6, OL, "X", "", "DSHADENORM") +---- Dec 28 -- Advanced rendering --------------------------------------------------------------------------------------------------------------- +GPU(280, "DDFRAME", 1, 1.0, 0, "STRUCT","", "Draw bordered frame") +GPU(281, "DDBAR", 1, 1.0, 0, "STRUCT","", "Draw a progress bar") +GPU(282, "DDGAUGE", 1, 1.0, 0, "STRUCT","", "Draw gauge needle") +GPU(283, "DRASTER", 1, 0.6, 0, "INT", "", "Set rasterizer quality level") +GPU(284, "DDTERRAIN", 1, 0.8, 0, "STRUCT","", "Draw terrain") +GPU(285, "RESERVED", 1, 0.0, 0, "", "", "") +GPU(286, "RESERVED", 1, 0.0, 0, "", "", "") +GPU(287, "RESERVED", 1, 0.0, 0, "", "", "") +GPU(288, "RESERVED", 1, 0.0, 0, "", "", "") +GPU(289, "RESERVED", 1, 0.0, 0, "", "", "") +---- Dec 29 -- Additional instructions ---------------------------------------------------------------------------------------------------------- +GPU(290, "DLOADBYTES", 2, 1.0, 0, "IDX", "PTR", "Load into texture slot by pointer") +GPU(291, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(292, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(293, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(294, "DMULDT", 2, 0.7, 0, "X", "Y", "X = Y * dT") +VEX(295, "VDIV", 2, 7.00, 0, "VEC", "Y", "VEC = VEC / Y") +VEX(296, "VTRANSFORM", 2, 8.00, 0, "VEC", "MATRIX","X = X * MATRIX") +GPU(297, "DSMOOTH", 2, 1.0, 0, "X", "Y", "Smooth X with smoothness Y") +GPU(298, "DBEGIN", 0, 1.0, 0, "", "", "Begin rendering (from async thread)") +GPU(299, "DEND", 0, 1.0, 0, "", "", "End rendering (from async thread)") +---- Dec 30 -- 3D rendering --------------------------------------------------------------------------------------------------------------------- +GPU(300, "DROTATE", 1, 1.0, 0, "VEC4F", "", "Rotate model by vector") +GPU(301, "DTRANSLATE", 1, 1.0, 0, "VEC4F", "", "Translate model by vector") +GPU(302, "DSCALE", 1, 1.0, 0, "VEC4F", "", "Scale model by vector") +GPU(303, "DXTEXTURE", 1, 1.0, 0, "STR", "", "Bind a specific external texture") +GPU(304, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(305, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(306, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(307, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(308, "RESERVED", 2, 0.0, 0, "", "", "") +GPU(309, "RESERVED", 2, 0.0, 0, "", "", "") +---- Dec 31 -- UNDEFINED ------------------------------------------------------------------------------------------------------------------------ +---- Dec 32 -- SPU output control --------------------------------------------------------------------------------------------------------------- +SPU(320, "CHRESET" , 1, 1.0, 0, "CHAN", "", "Reset channel") +SPU(321, "CHSTART", 1, 1.0, 0, "CHAN", "", "Start sound on channel") +SPU(322, "CHSTOP", 1, 1.0, 0, "CHAN", "", "Stop sound on channel") +SPU(323, "CHTRIGGER", 1, 1.0, 0, "CHAN", "", "Trigger the ADSR envelope on channel") +SPU(324, "CHRELEASE", 1, 1.0, 0, "CHAN", "", "Release the ADSR envelope on channel") +SPU(325, "RESERVED", 1, 0.0, 0, "", "", "") +SPU(326, "RESERVED", 1, 0.0, 0, "", "", "") +SPU(327, "RESERVED", 1, 0.0, 0, "", "", "") +SPU(328, "RESERVED", 1, 0.0, 0, "", "", "") +SPU(329, "RESERVED", 1, 0.0, 0, "", "", "") +---- Dec 33 -- SPU channel control -------------------------------------------------------------------------------------------------------------- +SPU(330, "WSET", 2, 1.0, 0, "WAVE", "STRING","Set lookup name for specific wave") +SPU(331, "CHWAVE", 2, 1.0, 0, "CHAN", "WAVE", "Set waveform") +SPU(332, "CHLOOP", 2, 1.0, 0, "CHAN", "IDX", "Set looping mode") +SPU(333, "CHVOLUME", 2, 1.0, 0, "CHAN", "X", "Set volume") +SPU(334, "CHPITCH", 2, 1.0, 0, "CHAN", "X", "Set pitch (value interpretation depends on register)") +SPU(335, "CHMODT", 2, 1.0, 0, "CHAN", "X", "Set LFO modulation type") +SPU(336, "CHMODA", 2, 1.0, 0, "CHAN", "X", "Set LFO modulation amplitude") +SPU(337, "CHMODF", 2, 1.0, 0, "CHAN", "X", "Set LFO modulation frequency") +SPU(338, "CHADSR", 2, 1.0, 0, "CHAN", "VEC4F", "Set channel ADSR") +SPU(339, "WLEN", 2, 1.0, 0, "X", "WAVE", "Read sound length in seconds") diff --git a/garrysmod/addons/feature-wire/lua/wire/default_data_decompressor.lua b/garrysmod/addons/feature-wire/lua/wire/default_data_decompressor.lua new file mode 100644 index 0000000..1038c87 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/default_data_decompressor.lua @@ -0,0 +1,45 @@ +-- Garry has imposed a file extension whitelist for the Steam Workshop which does not permit the dangerous format .txt +-- Therefore, we must store our .txt's in default_data_files.lua, and then extract them when first run + +local ignored_dirs = { + ["expression2/tests"] = true, +} + +-- Compress all files in addons/wire/data recursively into 1 json string +local function ReadDir(root) + if ignored_dirs[root] then return nil end + local tab = {} + local files,dirs = file.Find("addons/wire/data/"..root.."*","GAME") + for _, f in pairs(files) do + f = root..f + tab[f] = file.Read("addons/wire/data/"..f, "GAME") + end + for _, f in pairs(dirs) do + f = root..f.."/" + tab[f] = ReadDir(f) + end + return tab +end +-- Uncomment and Rename this file to wire/lua/wire/default_data_files.lua to update it +-- file.Write("default_data_files.txt", "//"..util.TableToJSON(ReadDir(""))) + +-- Decompress the json string wire/lua/wire/default_data_files.lua into the corresponding 36+ default data files +local function WriteDir(tab) + for f, contents in pairs(tab) do + if isstring(contents) then + file.Write(f, contents) + else + file.CreateDir(f) + WriteDir(contents) + end + end +end +-- Only expand the files if they aren't present already +if not file.Exists("expression2/_helloworld_.txt", "DATA") then + local compressed = file.Read("wire/default_data_files.lua","LUA") + -- The client cannot read lua files sent by the server (for security?), so clientside this'll only work + -- if the client actually has Wiremod installed, though with workshop autodownload that'll be common + if compressed ~= nil then + WriteDir(util.JSONToTable(string.sub(compressed, 3))) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/default_data_files.lua b/garrysmod/addons/feature-wire/lua/wire/default_data_files.lua new file mode 100644 index 0000000..a3907a3 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/default_data_files.lua @@ -0,0 +1 @@ +//{"expression2/":{"expression2/_helloworld_.txt":"@name Hello World\n@inputs A B\n@outputs Add Sub Mul Div\n@outputs GreaterThan Highest Lowest\n@outputs Vector:vector\n@persist D\n@trigger all\n\nAdd = A + B\nSub = A - B\nMul = A * B\nDiv = A / B\n\nGreaterThan = A > B\n\nif(A > B) {\n Highest = A, Lowest = B\n} else {\n Highest = B, Lowest = A\n}\n\nVector = vec(A, B, 0)\nVector = Vector + vec(0, 0, A + B)\nVector = Vector:normalized()\n","expression2/tests/":{"expression2/tests/regressions/":{"expression2/tests/regressions/1975.txt":"vec() * 0\n0 * vec()\n\nlocal Calls = 0\nfunction number f() {\n Calls++\n return 1\n}\nf() * 0\n0 * f()\nassert(Calls == 2)\n"},"expression2/tests/parsing.txt":"@inputs In\n@outputs Out\n\nlocal A = 0\n\nif (A) { A = 0 }\nif (A) { A = 0 } else { A = 0 }\nif (A) { A = 0 } elseif (A) { A = 0 }\nif (A) { A = 0 } elseif (A) { A = 0 }\n\nwhile (A) { A = 0 }\n\nfor (B = 1, 2) { A = 0 }\nfor (B = 1, 2, 3) { A = 0 }\n\nforeach (K, V: number = array(1, 2, 3)) { A = 0 }\n\nwhile (A) { break }\nwhile (A) { continue }\n\nA++\nA--\nA += 1\nA -= 1\nA *= 1\nA /= 1\n\nlocal B = vec()\nB[1] = 2\n\nswitch (A) {\n case A + A,\n A = 0\n A = 0\n case A + A + A,\n A = 0\n break\n default,\n A = 0\n}\n\nfunction f() {}\nfunction void f() {}\nfunction void f() { return }\nfunction number g() { return 0 }\nfunction number f(X) { return X }\nfunction vector f(X: vector) { return X }\nfunction number f(X, Y) { return X + Y }\nfunction number f(X, Y: vector) { return X }\nfunction number f([X Y]) { return X + Y }\nfunction number f([X Y]: number) { return X + Y }\n\nA ? A : A\nA ?: A\nA | A\nA & A\nA || A\nA && A\nA ^^ A\nA == A\nA != A\nA > A\nA < A\nA >= A\nA <= A\nA << A\nA >> A\nA + A\nA - A\nA * A\nA / A\nA % A\nA ^ A\n+A\n-A\n!A\n\narray():count()\narray()[0, number]\n(A)\n0\n\"foo\"\n~In\n$In\n->In\nA\n\ntable(1 = 1, 2 = 2)\n"}},"gpuchip/":{"gpuchip/examples/":{"gpuchip/examples/sprite.txt":"// Author: Drunkie\n// Description: A very simple sprite example\n\nMain();\n\n#include \n\nvoid Main()\n{\n // Enable vertex mode\n glVertexMode( 1 );\n\n // Draw to sprite buffer\n glSetRenderTarget( GL_BUFFER_BACK );\n glClear( 0, 255, 0 );\n\n // Draw to vertex buffer (world)\n glSetRenderTarget( GL_BUFFER_VERTEX );\n glEnable( GL_VERTEX_TEXTURING );\n\n // Sample from sprite 0\n glTextureSize( 256 );\n glTexture( 0 );\n glClear( 0, 255, 0 );\n glRectWH( 128, 128, 256, 256 );\n\n glSetRenderTarget( GL_BUFFER_FRONT );\n\n glExit();\n}\n","gpuchip/examples/3d_icosahedron.txt":"// Author: Drunkie\n// Description: Draws a 3D icosahedron model (solid and wireframe)\n\nMain();\n\n#include \n\nvoid Main()\n{\n glSleep( 40 ); // Sleep for 40 milliseconds (Reduces fps lag)\n glClear( 0, 0, 0 ); // Clear screen\n\n glCoordPipe( GL_CPIPE_N1_1 ); // Set coordinate pipe to [-1 to 1] mode\n glVertexPipe( GL_VPIPE_XYZTRANSFORM ); // Set vertex pipe to xyz transformation\n\n glLightPos( 0, 0, -50 ); // Set the light position\n glLightColor( 255, 255, 255, 1 ); // Set the light color\n\n glLookAt(\n 0, 0, -2.25, // Camera pos\n 0, 0, 0, // Camera target\n 0, 1, 0 // Camera up\n );\n\n // Create variable to hold curtime\n float time;\n timer time;\n\n // Create perspective and matrix transformations\n glPerspective( 30, 1, 1, 20 ); // FOV, ASPECT RATIO, ZNEAR, ZFAR\n glRotate( 1, 1, 0, time ); // AXIS X, Y, Z, ANGLE W\n glTranslate( 0, 0, 0 ); // TRANSLATION X, Y, Z\n glScale( 1, 1, 1, 0 ); // SCALE X, Y, Z\n\n glEnable( GL_VERTEX_BUFFER ); // Enable vertex buffer\n glEnable( GL_VERTEX_ZSORT ); // Enable Z sorting\n glEnable( GL_VERTEX_LIGHTING ); // Enable vertex lighting\n //glEnable( GL_VERTEX_CULLING ); // Enable face culling\n\n // Solid 3D polygon\n glFillMode( GL_FILL_SOLID ); // Set fillmode as solid\n glColor4( 100, 149, 237, 180 ); // Set the draw color with alpha\n glPoly3D( vertexBuffer, 20 ); // Draw 3D polygon\n glFlush(); // Flush the vertex buffer to the screen\n\n glDisable( GL_VERTEX_LIGHTING ); // Enable vertex lighting\n\n // Wireframe 3D polygon\n glLineWidth( 1 ); // Set line width of wireframe\n glFillMode( GL_FILL_WIREFRAME ); // Set fillmode as wireframe\n glColor4( 255, 255, 255, 255 ); // Set the draw color with alpha\n glPoly3D( vertexBuffer, 20 ); // Draw 3D polygon\n glFlush(); // Flush the vertex buffer to the screen\n\n glExit(); // Exit\n}\n\n// The vertex data for our model\nvertexBuffer:\ndb 0,0,1; db 0,0.9,0.5; db 0.9,0.3,0.4;\ndb 0,0,1; db -0.9,0.3,0.4; db 0,0.9,0.5;\ndb 0,0,1; db -0.5,-0.7,0.4; db -0.9,0.3,0.4;\ndb 0,0,1; db 0.5,-0.7,0.4; db -0.5,-0.7,0.4;\ndb 0,0,1; db 0.9,0.3,0.4; db 0.5,-0.7,0.4;\ndb 0.9,-0.3,-0.4; db 0.9,0.3,0.4; db 0.5,0.7,-0.4;\ndb 0,0.9,0.5; db 0.5,0.7,-0.4; db 0.9,0.3,0.4;\ndb 0,0.9,0.5; db -0.5,0.7,-0.4; db 0.5,0.7,-0.4;\ndb 0,0.9,0.5; db -0.9,0.3,0.4; db -0.5,0.7,-0.4;\ndb -0.9,-0.3,-0.4; db -0.5,0.7,-0.4; db -0.9,0.3,0.4;\ndb -0.9,-0.3,-0.4; db -0.9,0.3,0.4; db -0.5,-0.7,0.4;\ndb -0.9,-0.3,-0.4; db -0.5,-0.7,0.4; db 0,-0.9,-0.5;\ndb 0.5,-0.7,0.4; db 0,-0.9,-0.5; db -0.5,-0.7,0.4;\ndb 0.5,-0.7,0.4; db 0.9,-0.3,-0.4; db 0,-0.9,-0.5;\ndb 0.5,-0.7,0.4; db 0.9,0.3,0.4; db 0.9,-0.3,-0.4;\ndb 0,0,-1; db 0,-0.9,-0.5; db 0.9,-0.3,-0.4;\ndb 0,0,-1; db 0.9,-0.3,-0.4; db 0.5,0.7,-0.4;\ndb 0,0,-1; db 0.5,0.7,-0.4 db -0.5,0.7,-0.4;\ndb 0,0,-1; db -0.5,0.7,-0.4; db -0.9,-0.3,-0.4;\ndb 0,0,-1; db -0.9,-0.3,-0.4; db 0,-0.9,-0.5;\n","gpuchip/examples/mt3.txt":"//== 3D Graphics begin here ====================================================\n dvxpipe 3;\n dcvxpipe 3;\n\n //Calc depth here\n mov #Background.MinDepth, 0.8; //Near distance\n mov #Background.MaxDepth, 6.0; //Far distance\n mov #Background.ShadeStart,1.0;\n mov #Background.DepthStep ,0.3; //Depth step. The lower, the higher quality is\n\n timer #Time; mul #Time,3;\n\n mov EAX,#Time; mod EAX,#Background.DepthStep;\n\n sub #Background.MinDepth,EAX;\n sub #Background.MaxDepth,EAX;\n\n //Initialize depth range\n mov #Background.deltaDepth,#Background.MaxDepth;\n sub #Background.deltaDepth,#Background.MinDepth;\n\n //Compute background stuff\n mov #Background.ShadeStep,#Background.deltaDepth;\n div #Background.ShadeStep,#Background.DepthStep;\n frnd #Background.ShadeStep;\n finv #Background.ShadeStep;\n mul #Background.ShadeStep,#Background.ShadeStepMul;\n\n //Brightness too\n mov EAX,#Time; mod EAX,#Background.ShadeStep;\n sub #Background.ShadeStart,EAX;\n\n mov #_rect.color.r,200;\n mov #_rect.color.b,200;\n\n// Uncomment this for trippy camera\n// timer EAX; div EAX,8; fsin EBX,EAX; mul EBX,2;\n// drotatescale EAX,EBX; mul EBX,2;\n\n dsetwidth 0.8;\n call Draw.Background;\ndexit;\n\nalloc Time;\n\n//==============================================================================\nDraw.Background:\n //Draw all the rectangles\n mov EAX,#Background.MinDepth; mov ECX,#Background.ShadeStart;\n BackgroundLoop:\n mov EDX,#Time; add EDX,EAX;\n mov EBP,#Time; div EBP,6.28; fcos EBP,EBP;\n\n fsin EDI,EDX; mul EDI,EBP; mul EDI,0.8; sub EDI,1;\n mov #_rect.offset.x,EDI;\n\n fcos ESI,EDX; mul ESI,EBP; mul ESI,0.4; sub ESI,1;\n mov #_rect.offset.y,ESI;\n\n mov EDX,ECX; fpwr EDX,2;\n mov #regZOffset,EAX;\n\n dcolor _rect.color;\n// Uncomment this for trippy HSL color\n// mov ESI,#Time; add ESI,EAX;\n// fsin #HSL.R,ESI; mul #HSL.R,127; add #HSL.R,128; add ESI,1.57;// mul EAX,2;\n// fsin #HSL.G,ESI; mul #HSL.G,127; add #HSL.G,128; add ESI,1.57;// mul EAX,2;\n// fsin #HSL.B,ESI; mul #HSL.B,127; add #HSL.B,128;\n//\n// dcolor HSL;\n dshade EDX;\n dorectwh _rect.offset,_rect.wh;\n\n sub ECX,#Background.ShadeStep;\n add EAX,#Background.DepthStep;\n\n cmp EAX,#Background.MaxDepth;\n jl BackgroundLoop;\nret\n\n//==============================================================================\n//Drawing parameters\nscalar Background.MinDepth;\nscalar Background.MaxDepth;\nscalar Background.deltaDepth;\nscalar Background.DepthStep;\nscalar Background.ShadeStart;\nscalar Background.ShadeStep;\nscalar Background.ShadeStepMul,0.5;\n\ncolor HSL;\n\n//Generic rectangle\nvector2f _rect.offset,-1,-1;\nvector2f _rect.wh,2,2;\n\nvector2f _pad1.offset;\nvector2f _pad2.offset;\nvector2f _pad.wh;\n\n//Color scheme\ncolor _rect.color, 200,200,200;\ncolor _rect.color2,200,200,000;\n\ncolor _pad1.color, 000,200,000;\ncolor _pad2.color, 200,000,000;\n","gpuchip/examples/cube.txt":"//timer EAX;// div EAX,8;\n//fsin EAX,EAX;\n//mul EAX,512;\n//fabs EAX,EAX;\n//neg EAX;\n//add EAX,512;\n\ndcvxpipe 3; //-1..1 (opengl screen)\ndvxpipe 5; //matrix projection\n\n//Initialize transform\nmperspective mProjectionMatrix,vPerspective;\n\n//Render starts\ndclrscr bg_color;\nmlookat mViewMatrix,vLookAt; //View matrix\n\ntimer eax;\nmov #vRotate.w,eax;\n\n//Rotate translate\nmrotate mRotateMatrix,vRotate;\nmtranslate mTranslateMatrix,vTranslate;\n\n//Create model matrix\nmmov mModelMatrix,mTranslateMatrix;\nmmul mModelMatrix,mRotateMatrix;\n\n//modelViewMatrix = ViewMatrix * modelMatrx\nmmov mModelViewMatrix,mViewMatrix;\nmmul mModelViewMatrix,mModelMatrix;\n\n//load matrix\nmload mModelViewMatrix;\nmloadproj mProjectionMatrix;\n\n//setup light\ndsetlight 0,lightdata;\n\n//setup buffer\ndenable 0; //Vertex buffer\ndenable 1; //ZSorting\ndenable 2; //Lighting\ndenable 3; //Face culling\n\n//render cube\ndcolor fg_color;\ndvxdata_3f cube2,12;\ndvxflush;\n\nddisable 0; //Disable everything!\nddisable 1;\nddisable 2;\nddisable 3;\n\ndcvxpipe 0;\ndvxpipe 0;\n\n//You can write some text here now\n//\ndexit;\n\n//========\ncube2:\ndb -1,-1,-1;\ndb 1,-1,-1;\ndb 1,1,-1;\ncube3:\ndb -1,-1,-1;\ndb 1,1,-1;\ndb -1,1,-1;\ncube4:\ndb 1,-1,1;\ndb -1,-1,1;\ndb 1,1,1;\ncube5:\ndb -1,-1,1;\ndb -1,1,1;\ndb 1,1,1;\ncube6:\ndb 1,-1,-1;\ndb -1,-1,-1;\ndb 1,-1,1;\ncube7:\ndb -1,-1,-1;\ndb -1,-1,1;\ndb 1,-1,1;\ncube8:\ndb -1,1,-1;\ndb 1,1,-1;\ndb 1,1,1;\ncube9:\ndb -1,1,1;\ndb -1,1,-1;\ndb 1,1,1;\ncube10:\ndb -1,-1,-1;\ndb -1,1,-1;\ndb -1,1,1;\ncube11:\ndb -1,-1,1;\ndb -1,-1,-1;\ndb -1,1,1;\ncube12:\ndb 1,1,-1;\ndb 1,-1,-1;\ndb 1,1,1;\ncube13:\ndb 1,-1,-1;\ndb 1,-1,1;\ndb 1,1,1;\n\nlightdata:\nvector4f lightpos, 0,50,-50, 0; //x y z \ncolor lightcol,255,255,255, 1; //R G B Brightness\n//========\n\nmatrix mRotateMatrix;\nmatrix mTranslateMatrix;\n\nmatrix mProjectionMatrix;\t//This defines our projection to screen\nmatrix mViewMatrix;\t\t//This defines our camera transformations\n\nmatrix mModelMatrix;\t\t//This is our model transformations\nmatrix mModelViewMatrix;\t//This is our model relatively to camera transform\n\n\nvector4f vRotate, 1, 1, 1, 0; // \nvector4f vTranslate, 0, 0, 0, 0; // <0>\nvector4f vPerspective, 30, 1.6, 1, 20; // \n\nvLookAt:\nvector3f vLookAt_Eye, 0, 0, -5; //Where our camera is\nvector3f vLookAt_Center, 0, 0, 0; //What we look at\nvector3f vLookAt_Up, 0, 1, 0; //Where our matt-hat is\n\ncolor fg_color,255,255,25;\ncolor bg_color,64,32,12;\n","gpuchip/examples/verynice2.txt":"//Generated by WGUI tool. Get it at wiremod.com\n_page_0:\ndsetsize 16\ndcolor _c_0\ndrect _a_1,_a_2\ndcolor _c_1\ndrect _a_4,_a_5\ndcolor _c_2\ndrect _a_7,_a_8\ndcolor _c_3\ndrect _a_10,_a_11\ndcolor _c_3\ndrect _a_13,_a_14\ndcolor _c_2\nmov #_f_17,port0\ndwrite _a_16,_s_17\ndcolor _c_4\ndrect _a_19,_a_20\ndcolor _c_2\nmov #_f_23,port0\ndwrite _a_22,_s_23\ndcolor _c_4\ndwritefmt _a_25,_s_26\ndcolor _c_4\ndwritefmt _a_28,_s_29\ndcolor _c_4\ndwritefmt _a_31,_s_32\ndcolor _c_4\ndwritefmt _a_34,_s_35\ndcolor _c_4\ndwritefmt _a_37,_s_38\ndcolor _c_4\ndwritefmt _a_40,_s_41\ndcolor _c_4\ndwritefmt _a_43,_s_44\ndcolor _c_4\ndwritefmt _a_46,_s_47\ndcolor _c_3\ndrect _a_49,_a_50\ndcolor _c_2\nmov #_f_53,port0\ndwrite _a_52,_s_53\ndcolor _c_3\ndrect _a_55,_a_56\ndcolor _c_2\nmov #_f_59,port0\ndwrite _a_58,_s_59\ndcolor _c_2\ndwritefmt _a_61,_s_62\ndcolor _c_2\ndwritefmt _a_64,_s_65\ndcolor _c_2\ndwritefmt _a_67,_s_68\ndcolor _c_2\ndwritefmt _a_70,_s_71\ndcolor _c_2\ndwritefmt _a_73,_s_74\ndcolor _c_2\ndwritefmt _a_76,_s_77\ndexit\n\ncolor _c_0,0,0,160\nvec2f _a_1,8,8\nvec2f _a_2,504,504\ncolor _c_1,7,51,122\nvec2f _a_4,16,16\nvec2f _a_5,496,496\ncolor _c_2,0,0,0\nvec2f _a_7,24,24\nvec2f _a_8,488,488\ncolor _c_3,192,192,192\nvec2f _a_10,32,32\nvec2f _a_11,480,216\nvec2f _a_13,32,224\nvec2f _a_14,480,392\nvec2f _a_16,40,40\nstring _s_17,'VERYNICE GUI V2.1 Initialized...'\nalloc _f_17,0\ncolor _c_4,128,128,128\nvec2f _a_19,32,64\nvec2f _a_20,480,72\nvec2f _a_22,40,232\nstring _s_23,'Raw data feed:'\nalloc _f_23,0\nvec2f _a_25,88,256\nstring _s_26,'Input port 0: %f'\nvec2f _a_28,88,272\nstring _s_29,'Input port 1: %f'\nvec2f _a_31,88,288\nstring _s_32,'Input port 2: %f'\nvec2f _a_34,88,304\nstring _s_35,'Input port 3: %f'\nvec2f _a_37,88,320\nstring _s_38,'Input port 4: %f'\nvec2f _a_40,88,336\nstring _s_41,'Input port 5: %f'\nvec2f _a_43,88,352\nstring _s_44,'Input port 6: %f'\nvec2f _a_46,88,368\nstring _s_47,'Input port 7: %f'\nvec2f _a_49,32,400\nvec2f _a_50,248,480\nvec2f _a_52,40,408\nstring _s_53,'Vector feed 1:'\nalloc _f_53,0\nvec2f _a_55,264,400\nvec2f _a_56,480,480\nvec2f _a_58,272,408\nstring _s_59,'Vector feed 2:'\nalloc _f_59,0\nvec2f _a_61,40,424\nstring _s_62,'X: %f'\nvec2f _a_64,40,440\nstring _s_65,'Y: %f'\nvec2f _a_67,40,456\nstring _s_68,'Z: %f'\nvec2f _a_70,272,424\nstring _s_71,'X: %f'\nvec2f _a_73,272,440\nstring _s_74,'Y: %f'\nvec2f _a_76,272,456\nstring _s_77,'Z: %f'\n","gpuchip/examples/verynice1.txt":"dcolor c1;\ndrect p1,p2;\ndcolor c2;\ndrect p3,p4;\ndcolor c3;\ndrect p5,p6;\n\nmov #textpos1.y,80;\n\ndcolor c4;\ndsetsize 12;\ndwrite textpos1,text1;\n\nmov ecx,0;\nport_loop:\n add #textpos1.y,18;\n mov #textpos2.y,#textpos1.y;\n\n mov #textpos2.x,#textpos1.x;\n add #textpos2.x,90;\n dwrite textpos1,text2;\n dwritei textpos2,ecx;\n\n in eax,ecx;\n\n mov #textpos2.x,#textpos1.x;\n add #textpos2.x,192;\n dwritef textpos2,eax;\n\n inc ecx;\n cmp ecx,18;\n jl port_loop;\n\ndexit;\n\nstring text1,'VERYNICE HUD SYSTEM INITIALIZED... VER 1.0';\nstring text2,'INPUT PORT VALUE';\n\nvec2f textpos1,80,80;\nvec2f textpos2,80,80;\n\ncolor c1,0,0,255;\ncolor c2,0,0,127;\ncolor c3,0,0,64;\ncolor c4,255,255,255;\n\nvec2f p1,50,50;\nvec2f p2,450,450;\n\nvec2f p3,60,60;\nvec2f p4,430,430;\n\nvec2f p5,70,70;\nvec2f p6,440,440;\n","gpuchip/examples/trig.txt":"// Author: Jasongamer\n// Description: A tool for helping people learn trig\n\nMain();\n\n#include \n\nvoid Main()\n{\n glClear( 0, 0, 0 ); // Clear screen\n glCoordPipe( GL_CPIPE_N1_1 ); // Set coordinate pipe (-1 to 1 mode)\n\n timer R0; // Set time to curtime()\n R0 = -R0 * 1;\n\n glColor( 255, 255, 255 ); // Set draw color\n glCircle( *orig.x, *orig.y, 0.66, 40 ); // Draw circle (x, y, radius, quality)\n\n glColor( 0, 0, 0 );\n glCircle( *orig.x, *orig.y, 0.64, 40 );\n\n // Set the points for the trig\n fcos *cos,R0;\n fsin *sin,R0;\n\n *PosR.x = *cos;\n *PosR.x *= 0.65;\n *PosR.x += *orig.x;\n\n *PosR.y = *sin;\n *PosR.y *= 0.65;\n *PosR.y += *orig.y;\n\n *PosX.x = *PosR.x;\n\n glLineWidth( 0.01 ); // Set line width\n glFontSize( 24 ); // Set font size\n\n // X part of triangle\n glColor( 0, 0, 255 );\n glLine( *orig.x, *orig.y, *PosX.x, *PosX.y ); // Draw line\n glWriteFmt( -0.95, -0.95, sCos ) // Write formatted string\n\n // Y part of triangle\n glColor( 255, 0, 0 );\n glLine( *PosR.x, *PosR.y, *PosX.x, *PosX.y );\n *sin *= -1; // Negate\n glWriteFmt( -0.95, -0.85, sSin );\n\n glColor( 255, 255, 255 );\n glLine( *orig.x, *orig.y, *PosR.x, *PosR.y ); // Draw line\n\n glExit(); // Exit\n}\n\nvec2f orig,0,0;\nvec2f PosR,0,0;\nvec2f PosX,0,0;\n\nstring sCos,\"Cosine = %f\";\nalloc cos;\n\nstring sSin,\"Sine = %f\";\nalloc sin;\n","gpuchip/examples/texture.txt":"// Author: Drunkie\n// Description: A very simple texture example\n\nMain();\n\n#include \n\nvoid Main()\n{\n glVertexMode( 1 );\n glColor( 255, 255, 255, 255 );\n\n glBindTexture( 'brick/brick_model' );\n glColor( 255, 255, 255, 255 );\n glRectWH( 128, 128, 256, 256 );\n\n glExit();\n}\n\n// ZASM version\n\n//mov #regVertexMode,1;\n//dcolor white;\n//dxtexture tex;\n//drectwh pos,size;\n//dexit;\n//color white,255,255,255;\n//string tex,'brick/brick_model';\n//vec2f pos,128,128;\n//vec2f size,256,256;\n","gpuchip/examples/terrain.txt":"// Matrix code is based on cube example\nDCPIPE 3 // -1 to 1 coordinate range, required by DDTERRAIN\nDVXPIPE 5 // XYZ projection + matrix\n\nDENABLE 0 // Vertex buffer\nDENABLE 1 // Z sorting\nDENABLE 2 // Lighting\nDENABLE 3 // Backface culling\n\nDSETLIGHT 0, Light\n\nMPERSPECTIVE ProjectionMatrix, Perspective\n\n// Rotate the terrain\nTIMER EAX\nDIV EAX, 4\nMOV #RotateVector.w, EAX\nMROTATE RotateMatrix, RotateVector\n\n// Point camera at terrain\nMLOOKAT ViewMatrix, LookAtArgs\nMMUL ViewMatrix, RotateMatrix\n\nMLOAD ViewMatrix\nMLOADPROJ ProjectionMatrix\n\nDCLRSCR Background\n\nDCOLOR Foreground\n//DXTEXTURE Texture // Doesn't work\nDDTERRAIN Terrain\n\nDVXFLUSH\n\nDEXIT\n\nCOLOR Foreground, 253, 186, 49, 255\nCOLOR Background, 1, 46, 87, 255\nCOLOR White, 255, 255, 255, 255\n\n//STRING Texture, \"brick/brick_model\";\n\n// The terrain struct\nTerrain:\n DB 8, 8 // Terrain size\n DB 16 // Draw distance, between 0 and 16\n DB 0, 0 // Terrain offset\n\n // 11 bytes unused\n DB 0,0,0,0,0,0\n DB 0,0,0,0,0\n\n // 8 x 8 heightmap\n DB 0.0, 0.3, 0.3, 0.3, 0.3, 0.0, 0.0, 0.0\n DB 0.3, 0.3, 0.3, 0.5, 0.0, -0.5, 0.0, 0.0\n DB 0.0, 1.5, 1.5, 1.0, 0.3, -0.5, -0.3, 0.0\n DB 0.0, 1.0, 2.3, 1.8, 0.8, 0.3, 0.0, 0.0\n DB 0.3, 0.8, 1.3, 2.3, 1.6, 0.8, 0.5, 0.0\n DB 0.3, 0.5, 1.0, 1.3, 0.5, 0.3, 0.3, 0.0\n DB 0.0, 0.3, 0.3, 0.3, 0.0, -0.3, -0.5, 0.0\n DB 0.0, 0.0, 0.0, 0.0, -0.8, -0.8, -1.0, -0.5\n\nLight:\n DB 0, 50, -50, 0 // Position\n DB 255, 249, 225, 0.9 // RGB + Intensity\n\nMATRIX ProjectionMatrix\nMATRIX RotateMatrix\nMATRIX ViewMatrix\n\nVEC4F RotateVector, 0, 0, 1, 0 // Rotate around Z axis\nVEC4F Perspective, 50, 1, 1, 20 // 2nd value is aspect ratio\n\nLookAtArgs:\n DB 0, 5, 4 // Camera\n DB 0, 0, 0 // Look at\n DB 0, 0, -1 // Up (terrain is upside-down for some reason)\n","gpuchip/examples/foxlogo.txt":"//Fox game console logo (also example on how to work with polygons)\n\ndclrscr chassis;\n\ndcolor fox1c;\ndvxdata_2f fox1a,16; //16 max!!\ndvxdata_2f fox2a,3;\ndvxdata_2f fox3a,3;\ndvxdata_2f fox4a,3;\ndvxdata_2f fox5a,3;\ndvxdata_2f fox6a,3;\ndvxdata_2f fox7a,6;\ndvxdata_2f fox8a,3;\n\ndcolor fox2c;\ndvxdata_2f fox2,4;\n\ndexit;\n\n//===========================================\ncolor chassis,0,0,0;\n\ncolor fox1c,60,60,60;\ncolor fox2c,100,100,100;\n//===========================================\nfox1a: //N=16\ndb 60,218\ndb 62,173\ndb 32,36\ndb 214,119\ndb 268,128\ndb 318,168\ndb 352,233\ndb 494,243\ndb 499,254\ndb 496,266\ndb 478,321\ndb 335,374\ndb 265,408\ndb 223,419\ndb 95,430\ndb 109,408\n\nfox2a: //N = 3\ndb 109,408\ndb 57,432\ndb 69,376\nfox3a:\ndb 69,376\ndb 33,394\ndb 59,327\nfox4a:\ndb 59,327\ndb 24,348\ndb 54,273\nfox5a:\ndb 54,273\ndb 29,286\ndb 57,240\nfox6a:\ndb 57,240\ndb 26,245\ndb 60,218\n\nfox7a: //N=6\ndb 109,408\ndb 69,376\ndb 59,327\ndb 54,273\ndb 57,240\ndb 60,218\n\nfox8a: //N=3\ndb 177,150;\ndb 269,150;\ndb 190,47;\n\n//===========================================\nfox2: //N=4\ndb 340,238\ndb 286,257\ndb 274,203\ndb 311,213\n//===========================================\n","gpuchip/examples/3d_letter_a.txt":"// Author: Drunkie\n// Description: Draws a 3D model of the letter A\n\nMain();\n\n#include \n\nvoid Main()\n{\n glSleep( 40 ); // Sleep for 40 milliseconds (reduces fps lag)\n glClear( 100, 149, 237 ); // Clear screen\n\n glCoordPipe( GL_CPIPE_N1_1 ); // Set coordinate pipe to [-1 to 1] mode\n glVertexPipe( GL_VPIPE_XYZTRANSFORM ); // Set vertex pipe to xyz transformation\n\n glLightPos( 0, 0, -50 ); // Set the light position\n glLightColor( 255, 255, 255, 1 ); // Set the light color\n\n glLookAt(\n 0, 0, -2.8, // Camera pos\n 0, 0, 0, // Camera target\n 0, 1, 0 // Camera up\n );\n\n // Create variable to hold curtime\n float time;\n timer time;\n\n // Create perspective and matrix transformations\n glPerspective( 30, 1, 1, 20 ); // FOV, ASPECT RATIO, ZNEAR, ZFAR\n glRotate( 0, 1, 0, time ); // AXIS X, Y, Z, ANGLE W\n glTranslate( 0, -0.1, 0, 0 ); // TRANSLATION X, Y, Z\n glScale( 1, 1, 1, 0 ); // SCALE X, Y, Z\n\n glEnable( GL_VERTEX_BUFFER ); // Enable vertex buffer\n glEnable( GL_VERTEX_ZSORT ); // Enable Z sorting\n glEnable( GL_VERTEX_LIGHTING ); // Enable vertex lighting\n glEnable( GL_VERTEX_CULLING ); // Enable face culling\n\n // Solid 3D polygon\n glFillMode( GL_FILL_SOLID ); // Set fillmode as solid\n glColor4( 255, 255, 255, 255 ); // Set draw color with alpha\n glPoly3D( vertexBuffer, 30 ); // Draw 3D polygon\n glFlush(); // Flush the vertex buffer to the screen\n\n glExit(); // Exit\n}\n\n// The vertex data for our model\nvertexBuffer:\ndb 1,1,0; db 0.75,1,0; db 0.25,-1,0;\ndb 0.75,1,0; db 0,-1,0; db 0.25,-1,0;\ndb -1,1,0; db -0.25,-1,0; db -0.75,1,0;\ndb -0.75,1,0; db -0.25,-1,0; db 0,-1,0;\ndb 1,1,0.25; db 0.25,-1,0.25; db 0.75,1,0.25;\ndb 0.75,1,0.25; db 0.25,-1,0.25; db 0,-1,0.25;\ndb -1,1,0.25; db -0.75,1,0.25; db -0.25,-1,0.25;\ndb -0.75,1,0.25; db 0,-1,0.25; db -0.25,-1,0.25;\ndb 0.25,-1,0; db -0.25,-1,0; db 0.25,-1,0.25;\ndb -0.25,-1,0; db -0.25,-1,0.25; db 0.25,-1,0.25;\ndb -1,1,0; db -1,1,0.25; db -0.25,-1,0;\ndb -1,1,0.25; db -0.25,-1,0.25; db -0.25,-1,0;\ndb 1,1,0.25; db 1,1,0; db 0.25,-1,0;\ndb 1,1,0.25; db 0.25,-1,0; db 0.25,-1,0.25;\ndb -0.75,1,0; db 0,-1,0; db -0.75,1,0.25;\ndb -0.75,1,0.25; db 0,-1,0; db 0,-1,0.25;\ndb 0.75,1,0; db 0.75,1,0.25; db 0,-1,0;\ndb 0.75,1,0.25; db 0,-1,0.25; db 0,-1,0;\ndb -0.47,0.25,0; db -0.38,0.01,0; db 0.38,0.01,0;\ndb 0.38,0.01,0; db 0.47,0.25,0; db -0.47,0.25,0;\ndb -0.47,0.25,0.25; db 0.38,0.01,0.25; db -0.38,0.01,0.25;\ndb 0.38,0.01,0.25; db -0.47,0.25,0.25; db 0.47,0.25,0.25;\ndb -0.38,0.01,0; db -0.38,0.01,0.25; db 0.38,0.01,0;\ndb 0.38,0.01,0; db -0.38,0.01,0.25; db 0.38,0.01,0.25;\ndb -0.47,0.25,0; db 0.47,0.25,0; db -0.47,0.25,0.25;\ndb -0.47,0.25,0.25; db 0.47,0.25,0; db 0.47,0.25,0.25;\ndb -1,1,0; db -0.75,1,0; db -1,1,0.25;\ndb -0.75,1,0; db -0.75,1,0.25; db -1,1,0.25;\ndb 1,1,0; db 1,1,0.25; db 0.75,1,0;\ndb 0.75,1,0; db 1,1,0.25; db 0.75,1,0.25;\n","gpuchip/examples/3d_tunnel.txt":"// Author: Drunkie\n// Description: Draws a never ending tunnel in 3D!\n\nMain();\n\n#include \n\nvoid Main()\n{\n glSleep( 60 ); // Sleep for 60 milliseconds (reduces fps lag)\n glClear( 0, 0, 0 ); // Clear screen\n\n glCoordPipe( GL_CPIPE_N1_1 ); // Set coordinate pipe to [-1 to 1] mode\n glVertexPipe( GL_VPIPE_XYZTRANSFORM ); // Set vertex pipe to xyz transformation\n\n glLightPos( -1, -1, -1 ); // Set the light position\n glLightColor( 255, 255, 255, 1.25 ); // Set the light color\n\n glLookAt(\n 0, 0, -25, // Camera pos\n 0, 0, 0, // Camera target\n 0, 1, 0 // Camera up\n );\n\n // Loop and draw 4 models\n for (i = 0; i < 4; i++)\n {\n // Set translations for each model\n timer zTranslate;\n zTranslate *= -16;\n mod zTranslate,16;\n zTranslate += (i * 16);\n\n // Create perspective and matrix transformations\n glPerspective( 8, 1, 0.4, 20 ); // FOV, ASPECT RATIO, ZNEAR, ZFAR\n glRotate( 0, 0, 0, 0 ); // AXIS X, Y, Z, ANGLE W\n glTranslate( 0, 0, zTranslate ); // TRANSLATION X, Y, Z\n glScale( 1.2, 1, 8 ); // SCALE X, Y, Z\n\n glEnable( GL_VERTEX_BUFFER ); // Enable vertex buffer\n glEnable( GL_VERTEX_ZSORT ); // Enable Z sorting\n glEnable( GL_VERTEX_LIGHTING ); // Enable vertex lighting\n glEnable( GL_VERTEX_CULLING ); // Enable face culling\n\n // Solid 3D polygon\n glFillMode( GL_FILL_SOLID ); // Set fillmode as solid\n glColor4( 255, 255, 255, 150 ); // Set the draw color with alpha\n glPoly3D( VertexBuffer, 12 ); // Draw 3D polygon\n glFlush(); // Send our vertex buffer to screen\n\n // Wireframe 3D polygon\n glLineWidth( 1 ); // Set line width of wireframe\n glFillMode( GL_FILL_WIREFRAME ); // Set fillmode to wireframe\n glColor4( 255, 255, 255, 255 ); // Set the draw color with alpha\n glPoly3D( vertexBuffer, 8 ); // Draw 3D polygon\n glFlush(); // Send our vertex buffer to screen\n }\n\n glExit(); // Exit\n}\n\nfloat i;\nfloat zTranslate;\n\nvertexBuffer:\ndb 1,1,-1; db -1,1,-1; db 1,1,1;\ndb -1,1,-1; db -1,1,1; db 1,1,1;\ndb 1,1,-1; db 1,1,1; db 1,-1,-1;\ndb 1,-1,-1; db 1,1,1; db 1,-1,1;\ndb -1,1,-1; db -1,-1,-1; db -1,1,1;\ndb -1,-1,-1; db -1,-1,1; db -1,1,1;\ndb 1,-1,-1; db 1,-1,1; db -1,-1,-1;\ndb -1,-1,-1; db 1,-1,1; db -1,-1,1;\n","gpuchip/examples/hud_engine.txt":"//mov #65522,1;\n//mov #65525,0.66;\n//port0 & port1 - engine left/right throttle (0..1)\n//port2 & port3 - delta (not used)\n\n//This displays engine window in PhoenixWings airplane\n\nmov #65485,16; //set circle quality\n\ndclrscr hud_border;\n\ndcolor hud_text;\ndcircle hud_engine1gauge,68;\ndcircle hud_engine2gauge,68;\ndcolor hud_border;\ndcircle hud_engine1gauge,64;\ndcircle hud_engine2gauge,64;\n\ndcolor hud_text;\ndsetwidth 1;\ndline hud_engine1gauge_start,hud_engine1gauge;\ndline hud_engine2gauge_start,hud_engine2gauge;\n\ndsetwidth 2;\n\n//===\nmov eax,port0; mul eax,100;\nmul eax,0.1;\nmul #left_power,1.9;\nadd #left_power,eax;\ndiv #left_power,2;\n\nmov eax,#left_power; div eax,100;\nmul eax,6.00;\nadd eax,1.57;\n\ndrotatescale eax,1;\ndmove hud_engine1gauge;\n\ndline gauge_base,gauge_needle;\n//==\nmov #right_power,#left_power; //comment this and..\n//uncomment if your left/right engines are not synchronized\n//mov eax,port1; mul eax,100;\n//mul eax,0.1;\n//mul #right_power,1.9;\n//add #right_power,eax;\n//div #right_power,2;\n\n//mov eax,#right_power; div eax,100;\n//mul eax,6.00;\n//add eax,1.57;\n\ndrotatescale eax,1;\ndmove hud_engine2gauge;\n\ndline gauge_base,gauge_needle;\n//==\n\n//use this for whatever you wanna\n//mov #left_delta,port2; sub #left_delta,7.6; mul #left_delta,10;\n//mov #right_delta,port3; sub #right_delta,7.6; mul #right_delta,10;\n\ndrotatescale 0,1; //reset!\ndmove 0;\n\ndsetfont 4;\ndsetsize 28;\ndwritefmt hud_text1pos,hud_text1;\ndwritefmt hud_text2pos,hud_text2;\n\ncmp port4,1;\ndcolor hud_yellow;\nje _nsh;\n dshade 0.25;\n_nsh:\ndwrite hud_text3pos,hud_text3;\ndexit;\n\nvector2f hud_text1pos,70,212;\nvector2f hud_text2pos,310,212;\nvector2f hud_text3pos,20,460;\n\nstring hud_text1,'Left Engine',10,10,'N1 = %i%%',10,'Delta = %i%%';\nalloc left_power; alloc left_delta;\nstring hud_text2,'Right Engine',10,10,'N1 = %i%%',10,'Delta = %i%%';\nalloc right_power; alloc right_delta;\nstring hud_text3,'';\n\nvector2f hud_engine1gauge,128,128;\nvector2f hud_engine1gauge_start,128,64;\n\nvector2f hud_engine2gauge,384,128;\nvector2f hud_engine2gauge_start,384,64;\n\nvector2f gauge_base,0,0;\nvector2f gauge_needle,0,-48;\n\ncolor hud_text,64,255,64;\ncolor hud_yellow,255,255,64;\ncolor hud_border,30,30,30;\n","gpuchip/examples/table.txt":"// Author: Drunkie\n// Description: Draws a table; useful for calendars or spreadsheets!\n\nMain();\n\n#include \n\nfloat rows = 6;\nfloat cols = 5;\nfloat sizex = 476;\nfloat sizey = 400;\nfloat linewidth = 3;\n\nfloat i, j, day;\n\nvoid Main()\n{\n dentrypoint 0,DrawThread;\n dentrypoint 4,AsyncThread;\n\n *regHWClear = 0\n *regAsyncFreq = 200000;\n *regAsyncClk = 1;\n}\n\nvoid DrawThread()\n{\n dexit;\n}\n\nvoid AsyncThread()\n{\n glBegin();\n\n glClear( 35, 35, 35 ); // Clear screen color\n\n glColor( 255, 255, 255 ); // Set draw color\n glFont( GL_FONT_ARIAL ); // Set font type\n glFontSize( 36 ); // Set font size\n glWriteString( 16, 6, 'Simple-Calendar 1.0');\n\n glColor( 120, 120, 120 );\n glOffset( 16, 64 ); // Set screen offset\n glRectWH( 0, 0, sizex + linewidth, sizey + linewidth); // Draw rectangle\n\n glFont( GL_FONT_TREBUCHET );\n glFontSize( 14 );\n\n // Calculate rectangle size\n float sx = (sizex / rows) - linewidth;\n float sy = (sizey / cols) - linewidth;\n\n // Loop through rows\n for (i = 0; i < rows; i++)\n {\n // Loop through columns\n for (j = 0; j < cols; j++)\n {\n // Calculate x,y coordinate to draw at\n float x = i * (sizex / rows);\n float y = j * (sizey / cols);\n\n glColor( 200, 200, 200 ); // Set draw color\n glRectWH( x + linewidth, y + linewidth, sx, sy ); // Draw rectangle\n\n glColor( 0, 0, 0 ); // Set draw color\n\n // Write integer to screen\n day = i + (j * rows)\n glWriteInt( x + linewidth + 2, y + linewidth + 2, day + 1 );\n }\n }\n\n glEnd();\n}\n","gpuchip/examples/3d_cube.txt":"// Author: Drunkie\n// Description: 3D Cube\n\nMain();\n\n#include \n\nvoid Main()\n{\n glClear( 0, 0, 0 ); // Clear screen\n\n glCoordPipe( GL_CPIPE_N1_1 ); // Set coordinate pipe to [-1 to 1] mode\n glVertexPipe( GL_VPIPE_XYZTRANSFORM ); // Set vertex pipe to xyz transformation\n\n glLightPos( 0, 0, -20 ); // Set the light position\n glLightColor( 255, 255, 255, 1 ); // Set the light color\n\n glLookAt(\n 0, 0, -5, // Camera pos\n 0, 0, 0, // Camera target\n 0, 1, 0 // Camera up\n );\n\n // Create variable to hold curtime\n float time;\n timer time;\n\n // Create perspective and matrix transformations\n glPerspective( 30, 1, 1, 20 ); // FOV, ASPECT RATIO, ZNEAR, ZFAR\n glRotate( 1, 1, 0, time ); // AXIS X, Y, Z, ANGLE W\n glTranslate( 0, 0, 0 ); // TRANSLATION X, Y, Z\n glScale( 1, 1, 1 ); // SCALE X, Y, Z\n\n glEnable( GL_VERTEX_BUFFER ); // Enable vertex buffer\n glEnable( GL_VERTEX_ZSORT ); // Enable Z sorting\n\n // Solid 3D polygon\n glFillMode( GL_FILL_SOLID ); // Set fillmode as solid\n glColor4( 100, 149, 237, 180 ); // Set draw color with alpha\n glPoly3D( vertexBuffer, 12 ); // Draw 3D polygon\n glFlush(); // Flush the vertex buffer to the screen\n\n // Wireframe 3D polygon\n glLineWidth( 1 ); // Set line width\n glFillMode( GL_FILL_WIREFRAME ); // Set fillmode as solid\n glColor4( 255, 255, 255, 255 ); // Set draw color with alpha\n glPoly3D( vertexBuffer, 12 ); // Draw 3D polygon\n glFlush(); // Flush the vertex buffer to the screen\n\n glExit(); // Exit\n}\n\n// The vertex data for our model\nvertexBuffer:\ndb -1,-1,-1; db 1,-1,-1; db 1,1,-1;\ndb -1,-1,-1; db 1,1,-1; db -1,1,-1;\ndb 1,-1,1; db -1,-1,1; db 1,1,1;\ndb -1,-1,1; db -1,1,1; db 1,1,1;\ndb 1,-1,-1; db -1,-1,-1; db 1,-1,1;\ndb -1,-1,-1; db -1,-1,1; db 1,-1,1;\ndb -1,1,-1; db 1,1,-1; db 1,1,1;\ndb -1,1,1; db -1,1,-1; db 1,1,1;\ndb -1,-1,-1; db -1,1,-1; db -1,1,1;\ndb -1,-1,1; db -1,-1,-1; db -1,1,1;\ndb 1,1,-1; db 1,-1,-1; db 1,1,1;\ndb 1,-1,-1; db 1,-1,1; db 1,1,1;\n","gpuchip/examples/hud_fighter.txt":"//Aircraft hud\n//port0 - ROLL\n//port1 - PITCH\n//port2 - YAW (heading)\n//port3 - speed (units/sec)\n//port4 - altitude (units)\n//port5 - radar altitude (put ranger under your plane, and attach to this)\n//port6 - flaps active, 1 or 0\n//port7 - go to \"Gates - Time\", and find \"Derivative\". Attach this to derivative, and derivative to altitude (vertical speed)\n\n//Artiftical horizon\nin eax,0; //Roll\nin ebx,1; //Pitch\n\n//mul ebx,0.017453292;\nmul eax,0.017453292;\nadd eax,1.57;\n\ndiv ebx,90;\nmul ebx,512;\nadd ebx,256;\n\nmov #horizon_moveoffset.y,ebx;\n\ndrotatescale eax,1;\ndmove horizon_moveoffset;\n\ndcolor art_sky;\ndrectwh horizon_sky_offset,horizon_size;\ndcolor art_grnd;\ndrectwh horizon_grnd_offset,horizon_size;\n\ndcolor hud_text;\ndsetsize 20;\nmov eax,-45;\n_horizon_text:\n mov ebx,eax;\n mul ebx,5.68;\n sub ebx,10;\n mov #horizon_textpos1.y,ebx;\n mov #horizon_textpos2.y,ebx; add ebx,9;\n mov #horizon_rectpos1.y,ebx; add ebx,2;\n mov #horizon_rectpos2.y,ebx;\n\n drect horizon_rectpos1,horizon_rectpos2;\n dwritei horizon_textpos1,eax;\n dwritei horizon_textpos2,eax;\n\n add eax,15;\n cmp eax,45;\n jle _horizon_text;\n\n//Reset\ndmove 0;\ndrotatescale 0,1;\n\n//Border around art horizon\ndcolor border_color;\ndrect border_p1,border_p2;\ndrect border_p3,border_p4;\ndrect border_p5,border_p6;\ndrect border_p7,border_p8;\ndcolor border_color2;\ndrect border_p9,border_p10;\n\n//Draw hud stuff\nmov #roll,port0;\nmov #pitch,port1;\nmov #hdg,port2; add #hdg,180;\nmov #spd,port3; div #spd,17.6;\nmov #alt,port4;\nadd #alt,12000;\ndiv #alt,12;\nmov #ralt,port5; div #ralt,12;\nmov #vspd,port7; div #vspd,17.6;\ndcolor hud_text;\ndwritefmt hud_pos1,hud_text1;\ndsetsize 16;\ndwritefmt hud_pos2,hud_text2;\n\ndcolor hud_text;\nmov eax,port6; mul eax,0.75; add eax,0.25;\ndshade eax;\ndwritefmt hud_pos3,hud_text3;\n\n\ndexit;\n\nvec2f hud_pos1,50,20;\nstring hud_text1,'ROLL %i %tPITCH %i%tHDG %i';\nalloc roll;\nalloc pitch;\nalloc hdg;\n\nvec2f hud_pos2,45,120;\nstring hud_text2,'SPD',10,'%ikt',10,10,'ALT',10,'%ift',10,10,'RALT',10,'%ift',10,10,'VSPD',10,'%ift/s';\nalloc spd;\nalloc alt;\nalloc ralt;\nalloc vspd;\n\nvec2f hud_pos3,45,400;\nstring hud_text3,'FLAPS';\n\n\nvec2f horizon_textpos1,96,0;\nvec2f horizon_textpos2,-64,0;\nvec2f horizon_rectpos1,-50,0;\nvec2f horizon_rectpos2,50,0;\ncolor hud_text,64,255,64;\n\ncolor border_color2,255,255,255;\ncolor border_color,30,30,30;\nvec2f border_p1,0,0;\nvec2f border_p2,128,512;\nvec2f border_p3,384,0;\nvec2f border_p4,512,512;\n\nvec2f border_p5,128,0;\nvec2f border_p6,384,64;\nvec2f border_p7,128,448;\nvec2f border_p8,384,512;\n\nvec2f border_p9,128,254;\nvec2f border_p10,384,258;\n\nvec2f horizon_sky_offset,-256,-512;\nvec2f horizon_grnd_offset,-256,0;\nvec2f horizon_size,512,512;\n\nvec2f horizon_moveoffset,256,256;\n\ncolor art_sky,24,144,255;\ncolor art_grnd,192,72,0;\n","gpuchip/examples/line_graph.txt":"// Author: Drunkie\n// Description: A fake lag-o-meter that plots points on a grid\n\nMain();\n\n#include \n\nfloat i;\nfloat x, y;\nfloat ox = 0, oy = 256;\nfloat lines = 10;\nfloat lineWidth = 1\nfloat frameWidth = 510;\nfloat frameHeight = 300;\n\nvoid Main()\n{\n glVertexMode( 1 ); // Enable vertex mode\n glColor( 255, 255, 255 ); // Set draw color\n\n // Set texture as background\n glBindTexture( \"phoenix_storms/lag_sign\" );\n glClearTexture();\n glBindTexture( 0 ); // Discard texture\n\n glFont( GL_FONT_AKBAR ); // Set font type\n glFontSize( 36 ); // Set font size\n glWriteString( 2, 2, \"LAG-O-METER\" ); // Write string to screen\n\n glOffset( lineWidth, 90 ); // Offset the screen coordinates\n\n glColor4( 0, 0, 0, 160 ); // Set draw color with alpha\n glRectWH( 0, 0, frameWidth, frameHeight ); // Draw rectangle\n\n glColor( 255, 255, 255 );\n glLineWidth( lineWidth ); // Set line width\n glORectWH( 0, 0, frameWidth, frameHeight ); // Draw outlined rectangle\n\n glLineWidth( 1 ); // Set line width to 1\n\n // Loop and make a bunch of connected lines\n for (i = 0; i < lines; i++)\n {\n if (i == 0) {\n ox = 0;\n rand oy;\n oy *= frameHeight;\n }\n else {\n ox = x; oy = y;\n }\n x = ((i+1) / lines) * frameWidth;\n rand y;\n y *= frameHeight;\n glLine( ox, oy, x, y ); // Draw line on graph\n }\n\n glOffset( 0, 0 ); // Set screen offset back to 0,0\n glWriteString( 2, 400, \"INTENSE LAG DETECTED\" );\n\n glExit(); // Exit\n}\n","gpuchip/examples/mt2.txt":"dcvxpipe 3;\nmov #regHWClear,0; //Stop hardware clearing\ndsetwidth 0.05;\n\ntimer EAX;\nmov EDX,EAX; sub EDX,#PrevTime; //EDX = Delta time\nmov #PrevTime,EAX;\n\nmov EBP,0.4; //Speed of rotation\n\nmov ECX,8;\nDrawLoop:\n mov EAX,#Angle; mul EAX,1;\n fsin #EndPoint.X,EAX; mul EAX,2;\n fcos #EndPoint.Y,EAX;\n\n //HSL coloring\n fsin #HSL.R,EAX; mul #HSL.R,127; add #HSL.R,128; add EAX,1.57;// mul EAX,2;\n fsin #HSL.G,EAX; mul #HSL.G,127; add #HSL.G,128; add EAX,1.57;// mul EAX,2;\n fsin #HSL.B,EAX; mul #HSL.B,127; add #HSL.B,128;\n\n dcolor HSL;\n\n //Looks very nice\n dline StartPoint1,EndPoint;\n dline StartPoint2,EndPoint;\n dline StartPoint3,EndPoint;\n dline StartPoint4,EndPoint;\n\n mul EDX,EBP;\n add #Angle,EDX;\nloop DrawLoop;\n\ndexit;\n\nalloc Angle;\nalloc PrevTime;\n\ncolor HSL;\n\nvector2f EndPoint,0,0;\nvector2f StartPoint0,0,0;\nvector2f StartPoint1,1,1;\nvector2f StartPoint2,1,-1;\nvector2f StartPoint3,-1,-1;\nvector2f StartPoint4,-1,1;\n","gpuchip/examples/bounce.txt":"//////////////////////////////////\n// BOUNCING BALL GPU EXAMPLE //\n//////////////////////////////////\ndentrypoint 0,_draw;\t\t// Set draw start entrypoint to \"_draw\"\n\t\t\t\t//\nrand #ball.x;\t\t\t// Set random ball start point\nrand #ball.y;\t\t\t//\n\t\t\t\t//\ndexit;\t\t\t\t// Exit the initialization routine...\n//////////////////////////////////\n_draw:\t\t\t\t// Entrypoint for the drawing function\n\t\t\t\t//\ndcvxpipe 2;\t\t\t// Set coordinate pipe to 2 (to use coordinates 0...1)\ndclrscr bg_color;\t\t// Clear screen with background color\n\t\t\t\t//\ndmuldt eax,#d.x;\t\t// EAX = Direction Vector * Delta (change of coords per frame)\nadd #ball.x,eax;\t\t// Move the ball\ndmuldt eax,#d.y;\t\t//\nadd #ball.y,eax;\t\t//\n\t\t\t\t//\ncmp #ball.x,0.9;\t\t// Check hits against walls\ncge bounce.x;\t\t\t// Call bounce routine...\ncmp #ball.x,0.0;\t\t//\ncle bounce.x;\t\t\t//\n\t\t\t\t//\ncmp #ball.y,0.9;\t\t// Bounce on other axis\ncge bounce.y;\t\t\t//\ncmp #ball.y,0.0;\t\t//\ncle bounce.y;\t\t\t//\n\t\t\t\t//\ndcolor ball_color;\t\t// Set color to color of ball\ndrectwh ball,ball_wh;\t\t// Draw the ball\n\t\t\t\t//\ndsetsize 24;\t\t\t// Set font size\ndwrite textpos,text;\n\t\t\t\t//\ndexit;\t\t\t\t// Exit the draw function\n//////////////////////////////////\nbounce.x:\t\t\t// Bounce function (change X speed)\n neg #d.x; \t\t\t//\n min #ball.x,0.9;\t\t//\n max #ball.x,0.0;\t\t//\nret\t\t\t\t//\n\t\t\t\t//\nbounce.y:\t\t\t// Bounce function (change Y speed)\n neg #d.y;\t\t\t//\n min #ball.y,0.9;\t\t//\n max #ball.y,0.0;\t\t//\nret\t\t\t\t//\n//////////////////////////////////\n// Data and resources\t\t//\n//////////////////////////////////\n\t\t\t\t//\ncolor ball_color,255,255,255;\t// Ball color (white)\ncolor bg_color, 64, 32,128;\t// Background color (neon violet)\n\t\t\t\t//\nvector2f ball;\t\t\t// Ball position\nvector2f ball_wh,0.1,0.1;\t// Ball width/height\n\t\t\t\t//\nvector2f textpos,0.1,0.1;\t// Text position\n\t\t\t\t//\nvector2f d,1.0,1.0;\t\t// Movement direction & speed\n\t\t\t\t//\nstring text,'Bouncing ball!';\t// \"Bouncing ball!\"\n//////////////////////////////////\n","gpuchip/examples/plasma.txt":"//Plasma fractals\n//Converted by dlb from http://bocoup.com/processing-js/docs/index.php?page=Plasma%20Fractals\n//Which was converted by F1LT3R @ Hyper-Metrix.com from original at http://www.ic.sunysb.edu/Stu/jseyster/plasma/\n\nmov #regHWClear,0; //Stop GPU clearing itself\n\ndentrypoint 0,_draw; //Set the entry point for the draw loop\ndentrypoint 4,_async; //Set the enty point for the async loop\n\nmov #regAsyncFreq,2000000; //Make async run as fast as it can\nmov #regAsyncClk,1; //Start async\n\ndexit; //End init\n_draw: //Start draw\ndexit; //End draw\n\n//Setup variables\ncolor col;\nvec2f pos;\nvec2f size;\nfloat gridSize, edge1, edge2, edge3, edge4, midPoint, newWidth, newHeight, width, height, noise;\n\n_async: //Enter async\n\nmain(); //Run main function\n\nwhile(1){idle} //Infinatly loop\n\nvoid main(){ //Main function\n dsetbuf_spr; //Use sprite buffer\n\n setColor(255,255,255); //Set the colour to white\n rect(0,0,512,512); //Draw a large rectangle\n\n gridSize = 4; //How big each rectangle will be\n width = 512; //GPU Width\n height = 512; //GPU Height\n noise = 5; //How noisy it will be\n\n //Give initial corner values\n R1 = random(1);\n R2 = random(1);\n R3 = random(1);\n R4 = random(1);\n\n plasma(0,0,width,height,R1,R2,R3,R4) //Start recursive function\n}\n\nvoid plasma(float x, y, width, height, c1, c2, c3, c4){ //Plasma function\n\n //Setup local variables\n float edge1, edge2, edge3, edge4, midPoint;\n\n //Work out the size of the next segments\n float newWidth = width / 2;\n float newHeight = height / 2;\n\n if((width > gridSize)||(height > gridSize)){ //If it is still bigger than the rectangle size\n\n midPoint = (c1 + c2 + c3 + c4) / 4 + displace(newWidth + newHeight); //Randomly change the midpoint\n\n //Calculate edges by averaging the corners\n edge1 = (c1 + c2) / 2;\n edge2 = (c2 + c3) / 2;\n edge3 = (c3 + c4) / 2;\n edge4 = (c4 + c1) / 2;\n\n //Make sure it doesn't displace too far\n max midPoint,0;\n min midPoint,1;\n\n //Run on the newly calculated segments\n plasma(x, y, newWidth, newHeight, c1, edge1, midPoint, edge4);\n plasma(x + newWidth, y, newWidth, newHeight, edge1, c2, edge2, midPoint);\n plasma(x + newWidth, y + newHeight, newWidth, newHeight, midPoint, edge2, c3, edge3);\n plasma(x, y + newHeight, newWidth, newHeight, edge4, midPoint, edge3, c4);\n }else{ //Woo! It's the right size\n float c = (c1 + c2 + c3 + c4) / 4; //Average the corners\n\n float grey = c*255; //Multiply the corners by 255 to get a valid color\n\n setColor(grey,grey,grey); //Set the color to your new color based on the \"height\"\n rect(x,y,gridSize,gridSize); //Draw your rectangle\n }\n}\n\nfloat displace(float num){ //Displace function, it just works\n float m = num / (width + height) * (1/noise);\n R1 = random(1);\n return (R1-0.5) * m;\n}\n\nfloat random(float x){ //C version of the ASM rand opcode\n preserve EAX;\n rand EAX;\n return EAX*x;\n}\n\nvoid setColor(float r,float g,float b){ //C version of the dcolor opcode\n mov #col.r,r;\n mov #col.g,g;\n mov #col.b,b;\n\n dcolor col;\n}\n\nvoid rect(float x, float y, float width, float height){ //C version of the drectwh opcode\n mov #pos.x,x;\n mov #pos.y,y;\n\n mov #size.x,width;\n mov #size.y,height;\n\n drectwh pos, size;\n dswap; //dswap to make it show since we're drawing to the sprite buffer\n}\n","gpuchip/examples/stargate.txt":"//STARGATE DIAL COMPUTER MAIN DISPLAY (realistic colors)\n//\n//How to connect:\n//GPU IOBus to Data Port\n//Port0 to \"Open\"\n//Port1 to \"Active\"\n//Port2 to \"Chevron\"\n//Port3 to \"Inbound\"\n//Port4 to iris\n//\n//That's all!\n\ndiv #65525,1.33;\nmov #65485,16; //65485 is the circle quality register\n\n//24 means circles have 24 sides\n//You can have up to 128 sides, but that LAGS\n//32 sides is not even noticable comparing to 128\n\n//= Misc decorations ==================\n\ndcolor stargate_out_ring;\ndcircle center,250;\ndcolor stargate_middle_ring;\ndcircle center,240;\ndcolor stargate_out_ring;\ndcircle center,223;\n\n//= Rotating ring =====================\nmov #65485,12;\ndcolor stargate_inner_ring;\n\nin ecx,2; //This block checks if chevron 7 is engaged\ncmp ecx,7; //If yes, dont spin\nmov eax,0;\njge _norotate;\n timer eax;\n_norotate:\n\nin ebx,1; //This one checks if stargate is active\nmul eax,ebx;\n\nin ebx,3; neg ebx; add ebx,1; //This one checks if its inbound\nmul eax,ebx; //wormhole\n\ndrotatescale eax,1; //rotate by EAX radians\ndmove center;\ndcircle 0,220;\n\ndrotatescale 0,1; //Reset scale/movment\ndmove 0;\n\n//= Inner ring around EH ==============\nmov #65485,24;\ndcolor stargate_out_ring;\ndcircle center,190;\n\n\n//= EH ================================\ndcolor black;\ndcircle center,180; //draw black hole instead of event horizon\n\ndcolor stargate_eventhorizon;\n\nin ebx,0; //Stargate active?\ncmp ebx,0;\nmov eax,0;\nje _active;\n rand eax;\n mul eax,0.1;\n add eax,0.9;\n_active:\n\nin ebx,0; mul ebx,180;\n\nmul #eventhorizon_radius,0.99;\nmul ebx,1.01;\nadd #eventhorizon_radius,ebx;\ndiv #eventhorizon_radius,2;\n\n\ndshade eax;\ndcircle center,#eventhorizon_radius;\n\n//= Iris ==============================\nmov edx,port4;\nneg edx; add edx,1;\n\nmov eax,#iris_status;\nsub eax,edx;\nfabs eax,eax;\n\ndmuldt ecx,8;\n\ncmp eax,0.02;\njl _donothing;\n cmp #iris_status,edx;\n jl _lower;\n sub #iris_status,ecx;\n jmp _donothing;\n _lower:\n add #iris_status,ecx;\n_donothing:\n\nmov #iris1.y,#iris_status;\nmul #iris1.y,#iris2.y;\n\ndmove center;\n\nmov ecx,12;\n_iris:\n fsin ebx,ecx; fabs ebx,ebx; div ebx,10; add ebx,0.7;\n\n mov eax,ecx; mul eax,0.490; add eax,0.01; //0.697\n add eax,#iris_status;\n\n drotatescale eax,1;\n\n dcolor iris_color;\n dshade ebx;\n\n drect iris1,iris2;\nloop _iris;\n\ndmove 0;\n\n//= Chevrons ==========================\nmov eax,1; //Chevron ID\nin ebx,2;\ndmove center;\n_chevron_loop:\n mov edx,eax; //Compute chevron angle in radians\n mul edx,0.69815;\n sub edx,1.23333;\n\n drotatescale edx,1; //Rotate chevron polygon\n dcolor stargate_chevron;\n\n mov edx,eax:#chevron_triggers;\n\n cmp edx,ebx; //Check if chevron is light up\n jle _noshade;\n dshade 0.25;\n _noshade:\n\n dvxpoly chevron_polygon,4; //draw chevron polygon\n\n inc eax;\n cmp eax,9;\n jle _chevron_loop;\n\n//= Computer text =====================\ndrotatescale 0,1; //reset movement and scale\ndmove 0;\n\nin eax,3; //Is inbound?\ncmp eax,0;\nje _dexit;\n\n timer eax; mul eax,2; fint eax; mod eax,2;\n dcolor sgc_text;\n dshade eax;\n\n dsetsize 64; //draw message\n dwrite sgc_inboundpos,sgc_inbound;\n\n_dexit:\ndexit;\n\n//= Helpers ===========================\n\nchevron_triggers:\ndb 9,4,5,6,7,1,2,3,8;\n// 1 2 3 4 5 6 7 8 9\n// Order in which chevrons light up\n// Only 1-7 are used though\n\n//=====================================\n\ncolor sgc_text,255,255,255;\n\nvector2f sgc_inboundpos,120,215;\nstring sgc_inbound,'INBOUND';\n\ncolor stargate_out_ring, 116,105, 76;\ncolor stargate_middle_ring, 93 , 85, 60;\ncolor stargate_inner_ring, 138,137,108;\ncolor stargate_eventhorizon, 93,114,162;\ncolor stargate_chevron, 250,162, 54;\ncolor iris_color, 192,192,192;\n\ncolor black,0,0,0;\n\nvector2f center,256,256;\n\nvector2f iris1,-44,0;\nvector2f iris2,44,175;\n\nvector2f chevcenter,-16,-256;\nvector2f chevsize,32,32;\n\nalloc eventhorizon_radius;\nalloc iris_status;\n\n//raw chevron poly data\n//format: \nchevron_polygon: //n=4\ndb -16,-251;\ndb 16,-251;\ndb 10,-230;\ndb -10,-230;\n"},"gpuchip/lib/":{"gpuchip/lib/drivers/":{"gpuchip/lib/drivers/drv_gl_toolkit.txt":"#ifndef GL\n#include \n#endif\n\n#ifndef GLT\n\n#define GLT\n#define GLT_MAX_TRIANGLES 32\n\nfloat __GLT_VERTBUFF[(GLT_MAX_TRIANGLES * 3) * 3];\nfloat __GLT_VERTCNT = 0;\n\nvoid gltVertex(float x, float y, float z)\n{\n if ((__GLT_VERTCNT / 3) >= GLT_MAX_TRIANGLES)\n return;\n\n float* ptr = __GLT_VERTBUFF;\n ptr += (__GLT_VERTCNT * 3);\n\n *ptr = x;\n *(++ptr) = y;\n *(++ptr) = z;\n\n __GLT_VERTCNT++;\n}\n\nvoid gltTriangle(float x1, float y1, float z1,\n float x2, float y2, float z2,\n float x3, float y3, float z3)\n{\n gltVertex(x1, y1, z1);\n gltVertex(x2, y2, z2);\n gltVertex(x3, y3, z3);\n}\n\nvoid gltQuad(float tlx, float tly, float tlz,\n float trx, float try, float trz,\n float brx, float bry, float brz,\n float blx, float bly, float blz)\n{\n gltTriangle(\n trx, try, trz,\n tlx, tly, tlz,\n blx, bly, blz\n );\n\n gltTriangle(\n brx, bry, brz,\n trx, try, trz,\n blx, bly, blz\n );\n}\n\nvoid gltCube(float cex, float cey, float cez, float size)\n{\n float s2 = size / 2;\n\n gltQuad(\n cex - s2, cey + s2, cez - s2,\n cex + s2, cey + s2, cez - s2,\n cex + s2, cey - s2, cez - s2,\n cex - s2, cey - s2, cez - s2\n );\n\n gltQuad(\n cex + s2, cey + s2, cez + s2,\n cex - s2, cey + s2, cez + s2,\n cex - s2, cey - s2, cez + s2,\n cex + s2, cey - s2, cez + s2\n );\n\n gltQuad(\n cex - s2, cey + s2, cez + s2,\n cex - s2, cey + s2, cez - s2,\n cex - s2, cey - s2, cez - s2,\n cex - s2, cey - s2, cez + s2\n );\n\n gltQuad(\n cex + s2, cey + s2, cez - s2,\n cex + s2, cey + s2, cez + s2,\n cex + s2, cey - s2, cez + s2,\n cex + s2, cey - s2, cez - s2\n );\n\n gltQuad(\n cex - s2, cey + s2, cez + s2,\n cex + s2, cey + s2, cez + s2,\n cex + s2, cey + s2, cez - s2,\n cex - s2, cey + s2, cez - s2\n );\n\n gltQuad(\n cex - s2, cey - s2, cez - s2,\n cex + s2, cey - s2, cez - s2,\n cex + s2, cey - s2, cez + s2,\n cex - s2, cey - s2, cez + s2\n );\n}\n\nvoid gltClearBuffer()\n{\n __GLT_VERTCNT = 0;\n}\n\nvoid gltFlushBuffer()\n{\n if (__GLT_VERTCNT <= 3)\n return;\n\n float vcnt = __GLT_VERTCNT;\n float tcnt = (vcnt / 3) - (vcnt % 3);\n\n if (tcnt > GLT_MAX_TRIANGLES)\n tcnt = GLT_MAX_TRIANGLES;\n\n glPoly3D(__GLT_VERTBUFF, tcnt);\n glFlush();\n}\n\n#endif\n","gpuchip/lib/drivers/drv_gl.txt":"// [Author] - Drunkie\n// [Description] - A graphics driver that provides C-style functions for GPU\n// [Documentation] - http://goo.gl/DHhYb\n\n\n#define GL\n\n// Font\n#define GL_FONT_LUCIDA_CONSOLE 0\n#define GL_FONT_COURIER_NEW 1\n#define GL_FONT_TREBUCHET 2\n#define GL_FONT_ARIAL 3\n#define GL_FONT_TIMES_NEW_ROMAN 4\n#define GL_FONT_COOLVETICA 5\n#define GL_FONT_AKBAR 6\n#define GL_FONT_CSD 7\n\n// Buffer\n#define GL_BUFFER_FRONT 0\n#define GL_BUFFER_BACK 1\n#define GL_BUFFER_SPRITE 1\n#define GL_BUFFER_VERTEX 2\n\n// Coordinate pipe\n#define GL_CPIPE_DIRECT 0\n#define GL_CPIPE_RESOLUTION 1\n#define GL_CPIPE_0_1 2\n#define GL_CPIPE_N1_1 3\n#define GL_CPIPE_N256_256 4\n\n// Vertex pipe\n#define GL_VPIPE_XY 0\n#define GL_VPIPE_YZ 1\n#define GL_VPIPE_XZ 2\n#define GL_VPIPE_XYZPROJ 3\n#define GL_VPIPE_XYTRANSFORM 4\n#define GL_VPIPE_XYZTRANSFORM 5\n\n// denable / ddisable\n#define GL_VERTEX_BUFFER 0\n#define GL_VERTEX_ZSORT 1\n#define GL_VERTEX_LIGHTING 2\n#define GL_VERTEX_CULLING 3\n#define GL_VERTEX_DCULLING 4\n#define GL_VERTEX_TEXTURING 5\n\n// Fillmode\n#define GL_FILL_SOLID 0\n#define GL_FILL_WIREFRAME 1\n#define GL_FILL_TEXTURE 2\n\n// Cullmode\n#define GL_CULL_FRONT 0\n#define GL_CULL_BACK 1\n\n// Lightmode\n#define GL_LIGHT_FRONT 1\n#define GL_LIGHT_BACK -1\n\n// Horizontal font\n#define GL_ALIGN_LEFT 0\n#define GL_ALIGN_CENTER 1\n#define GL_ALIGN_RIGHT 2\n\n// Vertical font\n#define GL_VALIGN_TOP 0\n#define GL_VALIGN_MIDDLE 1\n#define GL_VALIGN_BOTTOM 2\n\n// Compatibility\n#define glSetTexture glBindTexture\n#define glWriteFmt glWriteFormat\n#define glFontHAlign glFontAlign\n\n\n// Clear\nvoid glClear( float r, float g, float b ) {\n mov #GL_BG.r,r; mov #GL_BG.g,g; mov #GL_BG.b,b; mov #GL_BG.a,255;\n dclrscr GL_BG;\n}\nvoid glClear4( float r, float g, float b, float a ) {\n mov #GL_BG.r,r; mov #GL_BG.g,g; mov #GL_BG.b,b; mov #GL_BG.a,a;\n dclrscr GL_BG;\n}\nvoid glClearTexture() {\n dclrtex;\n}\nvoid glHWClear( float n ) {\n mov #regHWClear,n;\n}\n\n// Color\nvoid glColor( float r, float g, float b ) {\n mov #GL_FG.r,r; mov #GL_FG.g,g; mov #GL_FG.b,b; mov #GL_FG.a,255;\n dcolor GL_FG;\n}\nvoid glColor4( float r, float g, float b, float a ) {\n mov #GL_FG.r,r; mov #GL_FG.g,g; mov #GL_FG.b,b; mov #GL_FG.a,a;\n dcolor GL_FG;\n}\nvoid glBrightness( float r, float g, float b, float a ) {\n mov #regBrightnessR,r;\n mov #regBrightnessG,g;\n mov #regBrightnessB,b;\n mov #regBrightnessW,a;\n}\nvoid glContrast( float r, float g, float b, float a ) {\n mov #regContrastR,r;\n mov #regContrastG,g;\n mov #regContrastB,b;\n mov #regContrastW,a;\n}\nvoid glShade( float n ) {\n dshade n;\n}\nvoid glShadeNorm( float n ) {\n dshadenorm n;\n}\n\n// Texture\nvoid glBindTexture( char* str ) {\n dxtexture str;\n}\nvoid glTexture( float id ) {\n dtexture id;\n}\nvoid glTextureSize( float n ) {\n mov #regTexSize,n;\n}\nvoid glTextureDataPtr( float n ) {\n mov #regTexDataPtr,n;\n}\nvoid glTextureDataSize( float n ) {\n mov #regTexDataSz,n;\n}\nvoid glTextureRotation( float n ) {\n mov #regTexRotation,n;\n}\nvoid glTextureScale( float n ) {\n mov #regTexScale,n;\n}\nvoid glTextureCenterUV( float u, float v ) {\n mov #regTexCenterU,u;\n mov #regTexCenterV,v;\n}\nvoid glTextureOffsetUV( float u, float v ) {\n mov #regTexOffsetU,u;\n mov #regTexOffsetV,v;\n}\n\n// Frame\nvoid glSleep( float ms ) {\n div ms,1000;\n timer #GL_CURTIME;\n sub #GL_CURTIME,#GL_TIMESTAMP;\n if (*GL_CURTIME <= ms) {\n mov #regHWClear,0;\n dexit;\n }\n timer #GL_TIMESTAMP;\n}\nvoid glExit() {\n dexit;\n}\n\n// Pipeline\nvoid glCoordPipe( float c ) {\n dcpipe c;\n}\nvoid glVertexPipe( float v ) {\n dvxpipe v;\n}\n\n// Hardware\nvoid glReset( float n ) {\n mov #regReset,n;\n}\nvoid glHalt( float n ) {\n mov #regHalt,n;\n}\nvoid glRAMReset( float n ) {\n mov #regRAMReset,n;\n}\nvoid glHScale( float n ) {\n mov #regHScale,n;\n}\nvoid glVScale( float n ) {\n mov #regVScale,n;\n}\nvoid glHWScale( float n ) {\n mov #regHWScale,n;\n}\nvoid glHWRotate( float n ) {\n mov #regRotation,n;\n}\n\n// Offset\nvoid glOffset( float x, float y ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dmove GL_V1;\n}\nfloat glOffsetX() {\n preserve eax;\n mov eax,#regOffsetX;\n}\nfloat glOffsetY() {\n preserve eax;\n mov eax,#regOffsetY;\n}\nvoid glCenter( float x, float y ) {\n mov #regCenterX,x;\n mov #regCenterY,y;\n}\n\n// Async\nvoid glAsyncReset( float n ) {\n mov #regAsyncReset,n;\n}\nvoid glAsyncClk( float n ) {\n mov #regAsyncClk,n;\n}\nvoid glAsyncFreq( float n ) {\n mov #regAsyncFreq,n;\n}\nvoid glEntryPoint( float idx, float ptr ) {\n dentrypoint idx,ptr;\n}\nvoid glBegin() {\n dbegin;\n}\nvoid glEnd() {\n dend;\n}\nvoid glSwap() {\n dswap;\n}\nvoid glSync() {\n dvsync;\n}\n\n// Cursor\nvoid glCursor( float n ) {\n mov #regCursor,n;\n}\nfloat glCursorX() {\n preserve eax;\n mov eax,#regCursorX;\n}\nfloat glCursorY() {\n preserve eax;\n mov eax,#regCursorY;\n}\n\n// Circle\nvoid glCircleQuality( float n ) {\n mov #regCircleQuality,n;\n}\nvoid glCircleStart( float n ) {\n mov #regCircleStart,n;\n}\nvoid glCircleEnd( float n ) {\n mov #regCircleEnd,n;\n}\n\n// Screen scaling\nvoid glScreenScale( float n ) {\n mov #regScale,n;\n}\nvoid glScreenScaleX( float x ) {\n mov #regScaleX,x;\n}\nvoid glScreenScaleY( float y ) {\n mov #regScaleY,y;\n}\n\n// 2D graphics\nvoid glCircle( float x, float y, float radius ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dcircle GL_V1,radius;\n}\nvoid glRect( float x, float y, float dx, float dy ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n mov #GL_V2.x,dx; mov #GL_V2.y,dy;\n drect GL_V1,GL_V2;\n}\nvoid glRectWH( float x, float y, float w, float h ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n mov #GL_V2.x,w; mov #GL_V2.y,h;\n drectwh GL_V1,GL_V2;\n}\nvoid glORect( float x, float y, float dx, float dy ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n mov #GL_V2.x,dx; mov #GL_V2.y,dy;\n dorect GL_V1,GL_V2;\n}\nvoid glORectWH( float x, float y, float w, float h ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n mov #GL_V2.x,w; mov #GL_V2.y,h;\n dorectwh GL_V1,GL_V2;\n}\nvoid glPixel( float x, float y ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dpixel GL_V1,GL_FG;\n}\nvoid glLine( float x, float y, float dx, float dy ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n mov #GL_V2.x,dx; mov #GL_V2.y,dy;\n dline GL_V1,GL_V2;\n}\nvoid glLineWidth( float w ) {\n dsetwidth w;\n}\nvoid glPoly2D( float* buffer, float count ) {\n dvxdata_2f buffer,count;\n}\n\n\n// Text\nvoid glFont( float id ) {\n dsetfont id;\n}\nvoid glFontAlign( float n ) {\n mov #regFontHalign,n;\n}\nvoid glFontVAlign( float n ) {\n mov #regFontValign,n;\n}\nvoid glFontSize( float n ) {\n dsetsize n;\n}\nfloat glTextWidth( char* str ) {\n preserve eax;\n dtextwidth eax,str;\n}\nfloat glTextHeight( char* str ) {\n preserve eax;\n dtextheight eax,str;\n}\nvoid glWriteString( float x, float y, char* str ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dwrite GL_V1,str;\n}\nvoid glWriteFloat( float x, float y, float n ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dwritef GL_V1,n;\n}\nvoid glWriteInt( float x, float y, float n ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dwritei GL_V1,n;\n}\nvoid glWriteFormat( float x, float y, char* str ) {\n mov #GL_V1.x,x; mov #GL_V1.y,y;\n dwritefmt GL_V1,str;\n}\nfloat glParamList() {\n preserve eax;\n mov eax,#regParamList;\n}\n\n\n// 3D graphics\nvoid glPoly3D( float* buffer, float count ) {\n\n if (*GL_MUPDATE == 1) {\n mov #GL_MUPDATE,0;\n mrotate GL_MROTATEMATRIX,GL_VROTATE;\n mtranslate GL_MTRANSLATEMATRIX,GL_VTRANSLATE;\n mscale GL_MSCALEMATRIX,GL_VSCALE;\n mmov GL_MMODELMATRIX,GL_MTRANSLATEMATRIX;\n mmul GL_MMODELMATRIX,GL_MROTATEMATRIX;\n mmul GL_MMODELMATRIX,GL_MSCALEMATRIX;\n mmov GL_MMODELVIEWMATRIX,GL_MVIEWMATRIX;\n mmul GL_MMODELVIEWMATRIX,GL_MMODELMATRIX;\n mload GL_MMODELVIEWMATRIX;\n mloadproj GL_MPROJECTIONMATRIX;\n }\n\n if (*GL_FILLMODE == GL_FILL_SOLID) {\n dvxdata_3f buffer,count;\n }\n else if (*GL_FILLMODE == GL_FILL_WIREFRAME) {\n dvxdata_3f_wf buffer,count;\n }\n else if (*GL_FILLMODE == GL_FILL_TEXTURE) {\n dvxdata_3f_tex buffer,count\n }\n}\nvoid glFlush() {\n dvxflush;\n}\nvoid glEnable( float n ) {\n denable n;\n}\nvoid glDisable( float n ) {\n ddisable n;\n}\nvoid glLightPos( float x, float y, float z ) {\n mov #GL_LIGHTPOS.x,x; mov #GL_LIGHTPOS.y,y; mov #GL_LIGHTPOS.z,z;\n dsetlight 0,GL_LIGHTDATA;\n}\nvoid glLightColor( float r, float g, float b, float a ) {\n mov #GL_LIGHTCOL.r,r; mov #GL_LIGHTCOL.g,g;\n mov #GL_LIGHTCOL.b,b; mov #GL_LIGHTCOL.a,a;\n dsetlight 0,GL_LIGHTDATA;\n}\nvoid glFillMode( float n ) {\n mov #GL_FILLMODE,n;\n}\nvoid glLookAt( float x, float y, float z, float tx, float ty, float tz, float ux, float uy, float uz ) {\n mov #GL_VLOOKAT_POS.x,x; mov #GL_VLOOKAT_POS.y,y; mov #GL_VLOOKAT_POS.z,z;\n mov #GL_VLOOKAT_TARG.x,tx; mov #GL_VLOOKAT_TARG.y,ty; mov #GL_VLOOKAT_TARG.z,tz;\n mov #GL_VLOOKAT_UP.x,ux; mov #GL_VLOOKAT_UP.y,uy; mov #GL_VLOOKAT_UP.z,uz;\n mlookat GL_MVIEWMATRIX,GL_VLOOKAT;\n mov #GL_MUPDATE,1;\n}\nvoid glPerspective( float fov, float asp, float znear, float zfar ) {\n mov #GL_VPERSPECTIVE.x,fov; mov #GL_VPERSPECTIVE.y,asp;\n mov #GL_VPERSPECTIVE.z,znear; mov #GL_VPERSPECTIVE.w,zfar;\n mperspective GL_MPROJECTIONMATRIX,GL_VPERSPECTIVE;\n mov #GL_MUPDATE,1;\n}\nvoid glRotate( float x, float y, float z, float w ) {\n mov #GL_VROTATE.x,x; mov #GL_VROTATE.y,y; mov #GL_VROTATE.z,z; mov #GL_VROTATE.w,w;\n mov #GL_MUPDATE,1;\n}\nvoid glTranslate( float x, float y, float z ) {\n mov #GL_VTRANSLATE.x,x; mov #GL_VTRANSLATE.y,y; mov #GL_VTRANSLATE.z,z;\n mov #GL_MUPDATE,1;\n}\nvoid glScale( float x, float y, float z ) {\n mov #GL_VSCALE.x,x; mov #GL_VSCALE.y,y; mov #GL_VSCALE.z,z;\n mov #GL_MUPDATE,1;\n}\nvoid glZOffset( float n ) {\n mov #regZOffset,n;\n}\nvoid glCullDistance( float n ) {\n mov #regCullDistance,n;\n}\nvoid glCullMode( float n ) {\n mov #regCullMode,n;\n}\nvoid glLightMode( float n ) {\n mov #regLightMode,n;\n}\nvoid glVertexArray( float n ) {\n mov #regVertexArray,n;\n}\n\n// Other\nvoid glVertexMode( float n ) {\n mov #regVertexMode,n;\n}\nvoid glSetRenderTarget( float n ) {\n if (n == GL_BUFFER_FRONT) {\n dsetbuf_fbo;\n }\n else if (n == GL_BUFFER_BACK) {\n dsetbuf_spr;\n }\n else if (n == GL_BUFFER_VERTEX) {\n dsetbuf_vx;\n }\n}\nfloat glIndex() {\n preserve eax;\n mov eax,#regIndex;\n}\n\n\n// Allocated variables for GL\ncolor GL_FG,255,255,255;\ncolor GL_BG;\nvec4f GL_V1;\nvec4f GL_V2;\n\nalloc GL_TIMESTAMP;\nalloc GL_CURTIME;\nalloc GL_FILLMODE;\n\nGL_LIGHTDATA:\nvec4f GL_LIGHTPOS,0,0,-10;\ncolor GL_LIGHTCOL,255,255,255,1;\n\nGL_VLOOKAT:\nvec3f GL_VLOOKAT_POS,0,0,-10;\nvec3f GL_VLOOKAT_TARG,0,0,0;\nvec3f GL_VLOOKAT_UP,0,1,0;\n\nmatrix GL_MROTATEMATRIX;\nmatrix GL_MTRANSLATEMATRIX;\nmatrix GL_MSCALEMATRIX;\nmatrix GL_MPROJECTIONMATRIX;\nmatrix GL_MVIEWMATRIX;\nmatrix GL_MMODELMATRIX;\nmatrix GL_MMODELVIEWMATRIX;\nalloc GL_MUPDATE,1;\n\nvec4f GL_VROTATE;\nvec4f GL_VTRANSLATE;\nvec4f GL_VPERSPECTIVE;\nvec4f GL_VSCALE,1,1,1,0;\n"}}},"cpuchip/":{"cpuchip/examples/":{"cpuchip/examples/udh_test.txt":"//------------------------------------------------------------------------------\n// Universal Device Host driver test application\n//------------------------------------------------------------------------------\n#pragma CRT ZCRT\n\n//Include drivers for console screen and device host\n#include \n#include \n\nvoid main() {\n float i;\n udhSetBusAddress(65536);\n\n cscrInitialize(0);\n\n udhQueryDevices();\n\n for (i = 0; i < MAX_CONSOLE_SCREENS; i++) {\n cscrSelect(i);\n cscrSetActive(1);\n cscrClear();\n\n cscrSetCursor(0,0);\n cscrPrintLine(\"Screen \",930);\n cscrPrintNumber(i,930);\n }\n\n cscrSelect(0);\n cscrSetCursor(0,2);\n cscrPrintLine(\"UDH driver test\\n\",039);\n for (i = 0; i < 8; i++) {\n cscrPrintLine(\"DEVICE \",999);\n cscrPrintNumber(i,999);\n cscrPrintLine(\": \",999);\n cscrPrintLine(udhGetDeviceName(i),666);\n cscrPrintLine(\"\\n\",999);\n }\n}\n","cpuchip/examples/helloworld.txt":"//Wired Hello World!\n//Connect CPU membus input to console screen\n//Connect CPUs CLK input to button (toggle)\n//Notice how you can store your\n//subroutines/calls in DATA area\njmp _code;\nmessage:\n db 'Hello World!',0;\nWriteString: //ESI - String pointer, EDX - Param\n mov eax,65536;\n AWriteLoop:\n cmp #esi,0; //Terminate on char 0\n je AEnd;\n mov #eax,#esi; //Output char\n inc eax;\n mov #eax,edx; //Output char param\n inc eax;\n inc esi;\n jmp AWriteLoop;\n AEnd:\nret //Return from call\n\n_code:\n mov esi,message;\n mov edx,000999; //White foreground on black background\n call WriteString;\n\n//More about colors:\n//Lower 3 digits are foreground,\n//and higher 3 digits are background\n//Each of 3 digits shows amount of\n//RED, GREEN, and BLUE (in order)\n//Each color has 10 shades - from 0 to 9\n//\n//For example, 999044 will be dark yellow (044) on\n//a white background (999)\n//\n//Experiment with colors!\n//\n//Also, the 7th digit (if its not equal to 0) will\n//cause the character to blink by changing foreground and\n//background places (actual data in memory wont change)\n"},"cpuchip/lib/":{"cpuchip/lib/zcrt/":{"cpuchip/lib/zcrt/string.txt":"//------------------------------------------------------------------------------\n// ZCPU CRT sourcecode (for HL-ZASM compiler) (C) 2011 by Black Phoenix\n//\n// String library. Contains functions to work with C strings (C89-compatible)\n//------------------------------------------------------------------------------\n\n#define NULL 0\n\n//copies n bytes between two memory areas; if there is overlap, the behavior is undefined\nvoid *memcpy(void *dest, void *src, float n) {\n preserve esi,edi;\n register float rem;\n\n esi = src;\n edi = dest;\n rem = n;\n while (rem) {\n register float count = rem;\n min count,8192;\n mcopy count;\n rem = rem - count;\n }\n return dest;\n}\n\n//copies n bytes between two memory areas; unlike with memcpy the areas may overlap\n//void *memmove(void *dest, void *src, float n);\n#define memmove memcpy\n\n//returns a pointer to the first occurrence of c in the first n bytes of s, or NULL if not found\nvoid* memchr(void *s, float c, float n) {\n register void *r = s;\n register float rem = n;\n\n while (rem) {\n if (*r == c) {\n return r;\n }\n ++r;\n --rem;\n }\n\n return NULL;\n}\n\n//compares the first n bytes of two memory areas\n//int memcmp(const void *s1, const void *s2, float n);\n#define memcmp strcmp\n\n//overwrites a memory area with n copies of c\nvoid* memset(void *ptr, float c, float n) {\n register void *p = ptr;\n register float rem = n;\n register float ch = c;\n\n while (rem) {\n *p++ = ch;\n --rem;\n }\n\n return ptr;\n}\n\n//appends the string src to dest\nchar* strcat(char *src, *dest) {\n register char *srcptr, *destptr;\n\n srcptr = src;\n while (*++srcptr) ;\n\n destptr = dest;\n while (*srcptr++ = *destptr++) ;\n return src;\n}\n\n//appends at most n bytes of the string src to dest\nchar* strncat(char *src, *dest, float n) {\n register char *srcptr, *destptr;\n register float i;\n\n srcptr = src;\n srcptr--;\n while (*++srcptr) ;\n\n destptr = dest;\n i = n;\n while (i--) {\n if (*srcptr++ = *destptr++) continue;\n }\n *srcptr = 0;\n return src;\n}\n\n//locates character c in a string, searching from the beginning\nchar* strchr(char *str, c) {\n register char *strptr, ch;\n strptr = str;\n ch = c;\n while(*strptr) {\n if (*strptr == ch) return strptr;\n ++strptr;\n }\n return 0;\n}\n\n//locates character c in a string, searching from the end\nchar* strrchr(char *str, c) {\n register char *strptr, ch;\n register char *findptr;\n\n findptr = 0;\n strptr = str;\n ch = c;\n while (*strptr) {\n if (*strptr == ch) findptr = strptr;\n ++strptr;\n }\n return findptr;\n}\n\n//compares two strings lexicographically\nfloat strcmp(char *src, *dest) {\n register char *srcptr, *destptr;\n\n srcptr = src;\n destptr = dest;\n while (*srcptr == *destptr) {\n if (*srcptr == 0) return 0;\n ++srcptr; ++destptr;\n }\n return (*srcptr - *destptr);\n}\n\n//compares up to the first n bytes of two strings lexicographically\nfloat strncmp(char *src, *dest, float n) {\n register char *srcptr, *destptr;\n register float i;\n\n srcptr = src;\n destptr = dest;\n i = n;\n\n while (i && (*srcptr == *destptr)) {\n if (*srcptr == 0) return 0;\n ++srcptr; ++destptr; --i;\n }\n if (i) return (*srcptr - *destptr);\n return 0;\n}\n\n//copies a string from one location to another\nchar* strcpy(char *dest, *src) {\n register char *srcptr, *destptr;\n\n destptr = dest;\n srcptr = src;\n while (*destptr++ = *srcptr++) ;\n return dest;\n}\n\n\n//write exactly n bytes to dest, copying from src or add 0's\nchar* strncpy(char *dest, *src, float n) {\n register char *srcptr, *destptr;\n register float i;\n\n destptr = dest;\n srcptr = src;\n i = n;\n\n while (i-- > 0) {\n if (*destptr++ = *srcptr++) continue;\n while (i-- > 0) *destptr++ = 0;\n }\n *destptr = 0;\n return dest;\n}\n\n//returns the string representation of an error number e.g. errno\n//char *strerror(int);\n\n//finds the length of a C string\nfloat strlen(char* str) {\n register char* strptr;\n register float n;\n\n strptr = str;\n n = 0;\n while (*strptr++) n++;\n return n;\n}\n\n//determines the length of the maximal initial substring consisting entirely of characters in accept\nfloat strspn(char *str, *accept) {\n register char *s = str;\n register char *p = accept;\n\n while (*p) {\n if (*p++ == *s) {\n ++s;\n p = accept;\n }\n }\n return s - str;\n}\n\n//determines the length of the maximal initial substring consisting entirely of characters not in reject\nfloat strcspn(char *str, char *reject) {\n register char *s, *p;\n\n for (s=str; *s; s++) {\n for (p=reject; *p; p++) {\n if (*p == *s) goto done;\n }\n }\n done:\n return s - str;\n}\n\n//finds the first occurrence of any character in accept\nchar* strpbrk(char *str, char *accept) {\n register char *s;\n register char *p;\n\n for (s=str; *s; s++) {\n for (p=accept; *p; p++) {\n if (*p == *s) return s;\n }\n }\n return NULL;\n}\n\n//finds the first occurrence of the string \"needle\" in the longer string \"haystack\"\nchar *strstr(char *haystack, char *needle) {\n register char *s = haystack;\n register char *p = needle;\n\n while (1) {\n if (!*p) {\n return haystack;\n }\n if (*p == *s) {\n ++p;\n ++s;\n } else {\n p = needle;\n if (!*s) {\n return NULL;\n }\n s = ++haystack;\n }\n }\n}\n\n//parses a string into a sequence of tokens; non-thread safe in the spec, non-reentrant\n//char *strtok(char *, const char * delim);\n\n//transforms src into a collating form, such that the numerical sort order of the transformed string is equivalent to the collating order of src\n//float strxfrm(char *dest, const char *src, float n);\n","cpuchip/lib/zcrt/init.txt":"//------------------------------------------------------------------------------\n// ZCPU CRT sourcecode (for HL-ZASM compiler) (C) 2011 by Black Phoenix\n//\n// C runtime library initialization\n//------------------------------------------------------------------------------\n\n#ifdef ZCRT_EXTENDED_MODE\n // Initialize extended mode\n mov edi,&zcrtInterruptTable; //Need \"&\" because array is defined below\n mov esi,&zcrtInterruptTable; add esi,1024;\n @InitTable:\n mov #edi,zcrtErrorHandler; inc edi;\n mov #edi,0; inc edi;\n mov #edi,0; inc edi;\n mov #edi,32; inc edi;\n cmp edi,esi;\n jl @InitTable;\n\n lidtr zcrtInterruptTable;\n stef;\n#endif\n\n// Call main function\nmain();\n\n// Stop the processor execution\n#ifdef ZCRT_EXTENDED_MODE\n clef;\n#endif\nint 1;\n\n//------------------------------------------------------------------------------\n// Allocate the interrupt table\n#ifdef ZCRT_EXTENDED_MODE\n float zcrtInterruptTable[1024];\n char* zcrtInterruptEntrypoint;\n\n // Default interrupt handlers\n zcrtErrorHandler:\n //Execute handler if required\n if (zcrtInterruptEntrypoint) {\n float errorNo,errorCode;\n cpuget errorNo,28;\n cpuget errorCode,27;\n\n zcrtInterruptEntrypoint(errorNo,errorCode);\n }\n iret\n#endif\n","cpuchip/lib/zcrt/ctype.txt":"//------------------------------------------------------------------------------\n// ZCPU CRT sourcecode (for HL-ZASM compiler) (C) 2011 by Black Phoenix\n//\n// Character classification functions.\n//------------------------------------------------------------------------------\n\n#define _CONTROL 1\n#define _SPACE 2\n#define _BLANK 4\n#define _DIGIT 8\n#define _HEX 16\n#define _PUNCT 32\n#define _UPPER 64\n#define _LOWER 128\n#define _GRAPH 256\n\n#define _MAXCHARS 0x83\n\n//test for alphanumeric character\nfloat isalnum(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_LOWER+_UPPER+_DIGIT;\n}\n\n//test for alphabetic character\nfloat isalpha(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_LOWER+_UPPER;\n}\n\n//test for blank character\nfloat isblank(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_BLANK;\n}\n\n//test for control character\nfloat iscontrol(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_CONTROL;\n}\n\n//test for digit\nfloat isdigit(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_DIGIT;\n}\n\n//test for graphic character, excluding the space character\nfloat isgraph(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_GRAPH;\n}\n\n//test for lowercase character\nfloat islower(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_LOWER;\n}\n\n//test for printable character, including the space character.\nfloat isprint(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_LOWER+_UPPER+_DIGIT+_PUNCT+_BLANK+_GRAPH;\n}\n\n//test for punctuation character\nfloat ispunct(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_PUNCT;\n}\n\n//test for any whitespace character\nfloat isspace(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_SPACE;\n}\n\n//test for uppercase character\nfloat isupper(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_UPPER;\n}\n\n//test for hexadecimal digit. Not locale-specific.\nfloat isxdigit(char c) {\n preserve eax;\n eax = c; max eax,0; min eax,_MAXCHARS;\n eax = __ctype_characters[eax];\n\n band eax,_HEX;\n}\n\n//convert character to lowercase\nchar tolower(char c) {\n if (islower(c)) return c - 0x20;\n return c;\n}\n\n//convert character to uppercase\nchar toupper(char c) {\n if (isupper(c)) return c + 0x20;\n return c;\n}\n\n__ctype_characters:\n db _CONTROL, //00 (NUL)\n db _CONTROL; //01 (SOH)\n db _CONTROL; //02 (STX)\n db _CONTROL; //03 (ETX)\n db _CONTROL; //04 (EOT)\n db _CONTROL; //05 (ENQ)\n db _CONTROL; //06 (ACK)\n db _CONTROL; //07 (BEL)\n db _CONTROL; //08 (BS)\n db _SPACE+_CONTROL; //09 (HT)\n db _SPACE+_CONTROL; //0A (LF)\n db _SPACE+_CONTROL; //0B (VT)\n db _SPACE+_CONTROL; //0C (FF)\n db _SPACE+_CONTROL; //0D (CR)\n db _CONTROL; //0E (SI)\n db _CONTROL; //0F (SO)\n db _CONTROL; //10 (DLE)\n db _CONTROL; //11 (DC1)\n db _CONTROL; //12 (DC2)\n db _CONTROL; //13 (DC3)\n db _CONTROL; //14 (DC4)\n db _CONTROL; //15 (NAK)\n db _CONTROL; //16 (SYN)\n db _CONTROL; //17 (ETB)\n db _CONTROL; //18 (CAN)\n db _CONTROL; //19 (EM)\n db _CONTROL; //1A (SUB)\n db _CONTROL; //1B (ESC)\n db _CONTROL; //1C (FS)\n db _CONTROL; //1D (GS)\n db _CONTROL; //1E (RS)\n db _CONTROL; //1F (US)\n db _SPACE+_BLANK; //20 SPACE\n db _PUNCT; //21 !\n db _PUNCT; //22 \"\n db _PUNCT; //23 #\n db _PUNCT; //24 $\n db _PUNCT; //25 %\n db _PUNCT; //26 &\n db _PUNCT; //27 '\n db _PUNCT; //28 (\n db _PUNCT; //29 )\n db _PUNCT; //2A *\n db _PUNCT; //2B +\n db _PUNCT; //2C ;\n db _PUNCT; //2D -\n db _PUNCT; //2E .\n db _PUNCT; //2F /\n db _DIGIT+_HEX; //30 0\n db _DIGIT+_HEX; //31 1\n db _DIGIT+_HEX; //32 2\n db _DIGIT+_HEX; //33 3\n db _DIGIT+_HEX; //34 4\n db _DIGIT+_HEX; //35 5\n db _DIGIT+_HEX; //36 6\n db _DIGIT+_HEX; //37 7\n db _DIGIT+_HEX; //38 8\n db _DIGIT+_HEX; //39 9\n db _PUNCT; //3A :\n db _PUNCT; //3B ;\n db _PUNCT; //3C <\n db _PUNCT; //3D =\n db _PUNCT; //3E >\n db _PUNCT; //3F ?\n db _PUNCT; //40 @\n db _UPPER+_HEX; //41 A\n db _UPPER+_HEX; //42 B\n db _UPPER+_HEX; //43 C\n db _UPPER+_HEX; //44 D\n db _UPPER+_HEX; //45 E\n db _UPPER+_HEX; //46 F\n db _UPPER; //47 G\n db _UPPER; //48 H\n db _UPPER; //49 I\n db _UPPER; //4A J\n db _UPPER; //4B K\n db _UPPER; //4C L\n db _UPPER; //4D M\n db _UPPER; //4E N\n db _UPPER; //4F O\n db _UPPER; //50 P\n db _UPPER; //51 Q\n db _UPPER; //52 R\n db _UPPER; //53 S\n db _UPPER; //54 T\n db _UPPER; //55 U\n db _UPPER; //56 V\n db _UPPER; //57 W\n db _UPPER; //58 X\n db _UPPER; //59 Y\n db _UPPER; //5A Z\n db _PUNCT; //5B [\n db _PUNCT; //5C \\\n db _PUNCT; //5D ]\n db _PUNCT; //5E ^\n db _PUNCT; //5F _\n db _PUNCT; //60 `\n db _LOWER+_HEX; //61 a\n db _LOWER+_HEX; //62 b\n db _LOWER+_HEX; //63 c\n db _LOWER+_HEX; //64 d\n db _LOWER+_HEX; //65 e\n db _LOWER+_HEX; //66 f\n db _LOWER; //67 g\n db _LOWER; //68 h\n db _LOWER; //69 i\n db _LOWER; //6A j\n db _LOWER; //6B k\n db _LOWER; //6C l\n db _LOWER; //6D m\n db _LOWER; //6E n\n db _LOWER; //6F o\n db _LOWER; //70 p\n db _LOWER; //71 q\n db _LOWER; //72 r\n db _LOWER; //73 s\n db _LOWER; //74 t\n db _LOWER; //75 u\n db _LOWER; //76 v\n db _LOWER; //77 w\n db _LOWER; //78 x\n db _LOWER; //79 y\n db _LOWER; //7A z\n db _PUNCT; //7B {\n db _PUNCT; //7C |\n db _PUNCT; //7D }\n db _PUNCT; //7E ~\n db _CONTROL; //7F (DEL)\n\n db _GRAPH; //80\n db _GRAPH; //81\n db _GRAPH; //82\n db _GRAPH; //83\n"},"cpuchip/lib/drivers/":{"cpuchip/lib/drivers/drv_cscr.txt":"//------------------------------------------------------------------------------\n// ZCPU standard library and drivers set (C) 2011 by Black Phoenix\n//\n// UDH-enabled console screen highspeed driver\n//------------------------------------------------------------------------------\n\n//Define to check if console screen driver is available\n#define CSCR_DRIVER\n\n//Maximum number of console screens supported\n#define MAX_CONSOLE_SCREENS 8\n\n//Console screen registers\n#define CURSOR_RATE 2043\n#define CURSOR_SIZE 2044\n#define CURSOR_POSITION 2045\n#define CURSOR_VISIBLE 2046\n#define LOW_SHIFT_COL 2031\n#define HIGH_SHIFT_COL 2032\n#define LOW_SHIFT_ROW 2033\n#define HIGH_SHIFT_ROW 2034\n#define SHIFT_ROWS 2038\n#define SHIFT_CELLS 2037\n#define CLEAR_SCREEN 2041\n#define BACKGROUND_COLOR 2042\n#define SCREEN_ACTIVE 2047\n#define SCREEN_ROTATION 2024\n#define SCREEN_BRIGHTNESS 2036\n\n//Driver data\nchar* cscrOffsets[MAX_CONSOLE_SCREENS];\nfloat cscrDevices[MAX_CONSOLE_SCREENS];\nchar* cscrCharacterPointer[MAX_CONSOLE_SCREENS];\nfloat cscrSelectedScreen;\n\n#ifdef UDH_DRIVER\n//Update console screen offsets\nvoid cscrUDHQueryFunction() {\n float i,n;\n n = udhGetDevices(11,MAX_CONSOLE_SCREENS,cscrDevices);\n for (i = 0; i < n; i++) {\n cscrOffsets[i] = udhGetDeviceOffset(cscrDevices[i]);\n }\n}\n#endif\n\n//Initialize console screen driver. screenOffset may be 0 if using UDH\nvoid cscrInitialize(char* screenOffset) {\n float i;\n\n for (i = 0; i < MAX_CONSOLE_SCREENS; i++) {\n cscrOffsets[i] = screenOffset;\n }\n\n#ifdef UDH_DRIVER\n if (!screenOffset) {\n udhRegisterDriver(cscrUDHQueryFunction);\n cscrUDHQueryFunction();\n }\n#endif\n cscrSelectedScreen = 0;\n}\n\nfloat cscrPresent(float screen) {\n return cscrOffsets[cscrSelectedScreen] != 0;\n}\n\nvoid cscrSelect(float screen) {\n cscrSelectedScreen = screen;\n max cscrSelectedScreen,0;\n min cscrSelectedScreen,MAX_CONSOLE_SCREENS;\n}\n\nvoid cscrSetActive(float clk) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n *(cscrOffsets[cscrSelectedScreen]+SCREEN_ACTIVE) = clk;\n}\n\nvoid cscrClear() {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n *(cscrOffsets[cscrSelectedScreen]+CLEAR_SCREEN) = 1;\n cscrCharacterPointer[cscrSelectedScreen] = 0;\n}\n\nvoid cscrSetBackground(float col) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n *(cscrOffsets[cscrSelectedScreen]+BACKGROUND_COLOR) = col;\n}\n\nvoid cscrSetRotation(float rot) {\n *(cscrOffsets[cscrSelectedScreen]+SCREEN_ROTATION) = rot;\n}\n\nvoid cscrSetBrightness(float bright) {\n *(cscrOffsets[cscrSelectedScreen]+SCREEN_BRIGHTNESS) = bright;\n}\n\nvoid cscrLoadImage(char* imgdata) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n\n preserve ESI,EDI;\n ESI = imgdata;\n EDI = cscrOffsets[cscrSelectedScreen];\n mcopy 30*18*2;\n}\n\nvoid cscrPutLine(char* scrptr, float col, char* str) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n char* curptr = scrptr;\n\n while (*str) {\n *(cscrOffsets[cscrSelectedScreen]+curptr*2+0) = *str;\n *(cscrOffsets[cscrSelectedScreen]+curptr*2+1) = col;\n\n str++;\n curptr++;\n }\n}\n\nvoid cscrPutChar(char* scrptr, float col, char ch) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n\n *(cscrOffsets[cscrSelectedScreen]+scrptr*2+0) = ch;\n *(cscrOffsets[cscrSelectedScreen]+scrptr*2+1) = col;\n}\n\nvoid cscrNewLine() {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n\n cscrCharacterPointer[cscrSelectedScreen] /= 30;\n fint cscrCharacterPointer[cscrSelectedScreen];\n cscrCharacterPointer[cscrSelectedScreen] = (cscrCharacterPointer[cscrSelectedScreen]+1)*30;\n\n if (cscrCharacterPointer[cscrSelectedScreen] >= 30*18) {\n cscrCharacterPointer[cscrSelectedScreen] = cscrCharacterPointer[cscrSelectedScreen] - 30;\n *(cscrOffsets[cscrSelectedScreen]+SHIFT_ROWS) = 1;\n }\n}\n\nvoid cscrPrintLine(char* str, float col) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n\n while (*str) {\n if (*str == '\\n') {\n cscrNewLine();\n str++;\n if (*str == 0) return;\n }\n\n *(cscrOffsets[cscrSelectedScreen]+cscrCharacterPointer[cscrSelectedScreen]*2+0) = *str;\n *(cscrOffsets[cscrSelectedScreen]+cscrCharacterPointer[cscrSelectedScreen]*2+1) = col;\n\n cscrCharacterPointer[cscrSelectedScreen]++;\n if (cscrCharacterPointer[cscrSelectedScreen] >= 30*18) cscrNewLine();\n str++;\n }\n}\n\nvoid cscrPrintNumber(float num, float col) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n\n float ndig,a;\n a = num;\n ndig = 0;\n while (a > 0) {\n ndig++;\n a /= 10;\n fint a;\n }\n max ndig,1;\n a = num;\n\n cscrCharacterPointer[cscrSelectedScreen] = cscrCharacterPointer[cscrSelectedScreen] + ndig;\n char* charPtr = cscrCharacterPointer[cscrSelectedScreen] - 1;\n while (ndig > 0) {\n preserve EDX;\n mov EDX,a;\n mod EDX,10;\n add EDX,48;\n\n *(cscrOffsets[cscrSelectedScreen]+charPtr*2+0) = EDX;\n *(cscrOffsets[cscrSelectedScreen]+charPtr*2+1) = col;\n charPtr--;\n\n a /= 10;\n fint a;\n\n ndig--;\n }\n}\n\nvoid cscrSetCursor(float x, y) {\n if (!cscrOffsets[cscrSelectedScreen]) return;\n cscrCharacterPointer[cscrSelectedScreen] = x+y*30;\n}\n","cpuchip/lib/drivers/drv_udh.txt":"//------------------------------------------------------------------------------\n// ZCPU standard library and drivers set (C) 2011 by Black Phoenix\n//\n// Universal device host driver. Only supports 8 devices right now\n//------------------------------------------------------------------------------\n\n#define UDH_DRIVER\n\n//Maximum number of devices supported\n#define MAX_UDH_DEVICES 8\n\n//Address range of a single device\n#define MAX_UDH_ADDRESS_RANGE 4*1024\n\n//Maximum number of drivers that may register with UDH\n#define MAX_UDH_DRIVERS 8\n\n//Device name/string data\nstring udhDeviceString0,\"None\";\nstring udhDeviceString1,\"Unknown\";\nstring udhDeviceString2,\"Extended bus\";\nstring udhDeviceString3,\"Address bus\";\nstring udhDeviceString4,\"Zyelios CPU\";\nstring udhDeviceString5,\"Zyelios GPU\";\nstring udhDeviceString6,\"Zyelios SPU\";\nstring udhDeviceString7,\"Flash EEPROM\";\nstring udhDeviceString8,\"ROM\";\nstring udhDeviceString9,\"Data bus\";\nstring udhDeviceString10,\"CD Ray\";\nstring udhDeviceString11,\"Console screen\";\nstring udhDeviceString12,\"Digital screen\";\nstring udhDeviceString13,\"Data plug\";\nstring udhDeviceString14,\"Data socket\";\nstring udhDeviceString15,\"Keyboard\";\nstring udhDeviceString16,\"Oscilloscope\";\nstring udhDeviceString17,\"Sound emitter\";\nstring udhDeviceString18,\"Constant value\";\nstring udhDeviceString19,\"Data port\";\nstring udhDeviceString20,\"RAM\";\nudhDeviceName:\n db udhDeviceString0, udhDeviceString1, udhDeviceString2;\n db udhDeviceString3, udhDeviceString4, udhDeviceString5;\n db udhDeviceString6, udhDeviceString7, udhDeviceString8;\n db udhDeviceString9, udhDeviceString10,udhDeviceString11;\n db udhDeviceString12,udhDeviceString13,udhDeviceString14;\n db udhDeviceString15,udhDeviceString16,udhDeviceString17;\n db udhDeviceString18,udhDeviceString19,udhDeviceString20;\n\n//Extended bus offset\nchar* udhBusOffset;\n\n//List of callbacks to call when querying devices\nvoid* udhQueryCallback[MAX_UDH_DRIVERS];\nfloat udhQueryCallbackCount = 0;\n\nfloat udhSetBusAddress(char* extOffset) {\n udhBusOffset = extOffset;\n udhQueryDevices();\n}\n\nvoid udhQueryDevices() {\n float i;\n\n //Run the query\n udhBusOffset[16] = 32+MAX_UDH_DEVICES;\n udhBusOffset[17] = 1;\n\n //Reconfigure all devices\n //FIXME: only supports single extended bus right now\n for (i = 0; i < 8; i++) {\n udhBusOffset[i*2+0] = (4*1024)*i;\n udhBusOffset[i*2+1] = (4*1024)*i+((4*1024)-1);\n }\n\n //Update all drivers\n for (i = 0; i < udhQueryCallbackCount; i++) {\n void* functionPtr = udhQueryCallback[i];\n functionPtr();\n }\n}\n\nvoid udhRegisterDriver(void* queryDeviceFunction) {\n udhQueryCallback[udhQueryCallbackCount] = queryDeviceFunction;\n if (udhQueryCallbackCount < MAX_UDH_DRIVERS) udhQueryCallbackCount++;\n}\n\nfloat udhGetDeviceType(float busIndex) {\n return udhBusOffset[32+busIndex];\n}\n\nfloat udhGetDeviceOffset(float busIndex) {\n return 65536+32+MAX_UDH_DEVICES+udhBusOffset[busIndex*2];\n}\n\nchar* udhGetDeviceName(float busIndex) {\n float deviceType = udhGetDeviceType(busIndex);\n if ((deviceType >= 0) && (deviceType <= 20)) {\n return udhDeviceName[deviceType];\n } else {\n return udhDeviceName[1];\n }\n}\n\nvoid udhSetDeviceOffsetSize(float busIndex, char* offst, char* size) {\n udhBusOffset[busIndex*2+0] = offst;\n udhBusOffset[busIndex*2+1] = offst+size-1;\n}\n\nfloat udhGetNumDevices() {\n return MAX_UDH_DEVICES;\n}\n\nfloat udhGetDevices(float type, float maxCount, char* deviceList) {\n float i,devPtr,n;\n\n devPtr = deviceList;\n n = 0;\n for (i = 0; i < MAX_UDH_DEVICES; i++) {\n if ((udhGetDeviceType(i) == type) && (n < maxCount)) {\n n++;\n *devPtr++ = i;\n }\n }\n\n return n;\n}\n"}}},"spuchip/":{"spuchip/examples/":{"spuchip/examples/mario_theme.txt":"// Author: Jasongamer\n// Song: Mario Underwater Theme\n\n// Set track wave to channel 0 and start\nwset 0,trackwave;\nchwave 0,0;\nchvolume 0,0.2;\nchstart 0;\n\n// Set track wave to channel 1 and start\nwset 1,trackwave;\nchwave 1,1;\nchvolume 1,0.2;\nchstart 1;\n\n// Set bass wave to channel 2 and start\nwset 2,basswave;\nchwave 2,1;\nchvolume 2,0.3;\nchstart 2;\n\n// Get track length\ntracklen = strlen(trackA);\n\nvoid main()\n{\n // Tempo\n if ((i > 120) && (i <= 230))\n tempo( 1000 );\n else\n tempo( 864 );\n\n // Track A\n note = 2;\n fpwr note,(trackA[i]/12);\n note /= 100;\n chpitch 0,note;\n\n // Track B\n note = 2;\n fpwr note,(trackB[i]/12);\n note /= 100;\n chpitch 1,note;\n\n // Bass\n note = 2;\n fpwr note,(bass[i]/12);\n note /= 100;\n chpitch 2,note;\n\n // Index\n i++; mod i,tracklen;\n\n // Repeat\n jmp main;\n}\n\n// Accurate tempo function for beats-per-minute\nvoid tempo( float bpm )\n{\n timer timestamp;\n while ((time - timestamp) < (60 / bpm)) { timer time; }\n}\n\n// Returns the length of a string\nfloat strlen(char* str)\n{\n char* strptr = str;\n while (*strptr++);\n return (strptr - str);\n}\n\nfloat note, i;\nfloat tracklen;\nfloat time, timestamp;\n\nstring trackwave,\"synth/square.wav\";\nstring basswave,\"synth/tri.wav\";\n\ntrackA:\n\n// Intro\ndb 73,73,73,73, 75,75,75,75, 77,77,77,77, 78,78,78,78, 80,80,80,80, 81,81,81,81;\ndb 82,-1,82,-1, 82,82,82,-1, 82,82,82,-1, 82,82,82,82, 82,82,82,-1, -1,-1,78,78;\n\n// Part 1\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, 86,86,86,86, 86,86,86,86, 86,86,86,-1;\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, -1,-1,78,78, 80,80,82,82, 83,83,85,85;\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, 86,86,86,86, 86,86,86,-1, 88,88,88,-1;\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,78,78;\ndb 85,85,85,85, 85,85,85,85, 85,85,85,-1, 84,84,84,84, 84,84,84,84, 84,84,84,-1;\ndb 85,85,85,85, 85,85,85,85, 85,85,85,-1, -1,-1,78,78, 80,80,82,82, 83,83,84,84;\ndb 85,85,85,85, 85,85,85,85, 85,85,85,-1, 78,78,78,78, 78,78,78,-1, 88,88,88,-1;\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,78,78;\n\n// Part 2\ndb 90,90,90,90, 90,90,90,90, 90,90,90,-1, 90,90,90,90, 90,90,90,90, 90,90,90,-1;\ndb 90,90,90,90, 90,90,90,90, 90,90,90,-1, 90,90,90,-1, 92,92,-1,-1, -1,-1,90,90;\ndb 88,88,88,88, 88,88,88,88, 88,88,88,-1, 88,88,88,88, 88,88,88,88, 88,88,88,-1;\ndb 88,88,88,88, 88,88,88,88, 88,88,88,-1, 88,88,88,-1, 90,90,-1,-1, -1,-1,88,88;\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, 80,80,80,-1, 82,82,82,-1, 88,88,88,-1;\ndb 87,-1,87,-1, 87,87,87,87, 87,-1,82,82, 83,83,83,83, 83,83,83,83, 83,83,83,-1;\n\ndb 0; // End string\n\ntrackB:\n\n// Intro\ndb 73,73,73,73, 72,72,72,72, 71,71,71,71, 70,70,70,70, 71,71,71,71, 72,72,72,72;\ndb 73,-1,73,-1, 73,73,73,-1, 75,75,75,-1, 76,76,76,76, 76,76,76,-1, -1,-1,-1,-1;\n\n// Part 1\ndb 78,78,78,78, 78,78,78,78, 78,78,78,-1, 77,77,77,77, 77,77,77,77, 77,77,77,-1;\ndb 78,78,78,78, 78,78,78,78, 78,78,78,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\ndb 78,78,78,78, 78,78,78,78, 78,78,78,-1, 77,77,77,77, 77,77,77,-1, 80,80,80,-1;\ndb 78,78,78,78, 78,78,78,78, 78,78,78,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\ndb 76,76,76,76, 76,76,76,76, 76,76,76,-1, 75,75,75,75, 75,75,75,75, 75,75,75,-1;\ndb 76,76,76,76, 76,76,76,76, 76,76,76,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\ndb 76,76,76,76, 76,76,76,76, 76,76,76,-1, 70,70,70,70, 70,70,70,-1, 80,80,80,-1;\ndb 78,78,78,78, 78,78,78,78, 78,78,78,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\n\n// Part 2\ndb 87,87,87,87, 87,87,87,87, 87,87,87,-1, 85,85,85,85, 85,85,85,85, 85,85,85,-1;\ndb 84,84,84,84, 84,84,84,84, 84,84,84,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\ndb 85,85,85,85, 85,85,85,85, 85,85,85,-1, 84,84,84,84, 84,84,84,84, 84,84,84,-1;\ndb 83,83,83,83, 83,83,83,83, 83,83,83,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\ndb 71,71,71,71, 71,71,71,71, 71,71,71,-1, 76,76,76,-1, 78,78,78,-1, 82,82,82,-1;\ndb 82,-1,82,-1, 82,82,82,-1, -1,-1,76,76, 75,75,75,75, 75,75,75,75, 75,75,75,-1;\n\ndb 0; // End string\n\nbass:\n\n// Intro\ndb -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1;\ndb -1,-1,-1,-1, -1,-1,-1,-1, 66,66,66,-1, 66,66,66,66, 66,66,66,66, 66,66,66,-1;\n\n// Part 1\ndb 59,59,59,-1, 66,66,66,-1, 71,71,71,-1, 58,58,58,-1, 66,66,66,-1, 70,70,70,-1;\ndb 59,59,59,-1, 66,66,66,-1, 71,71,71,-1, 63,63,63,-1, 66,66,66,-1, 71,71,71,-1;\ndb 59,59,59,-1, 66,66,66,-1, 71,71,71,-1, 58,58,58,-1, 66,66,66,-1, 70,70,70,-1;\ndb 59,59,59,-1, 66,66,66,-1, 71,71,71,-1, 63,63,63,-1, 66,66,66,-1, 71,71,71,-1;\ndb 61,61,61,-1, 66,66,66,-1, 70,70,70,-1, 60,60,60,-1, 65,65,65,-1, 69,69,69,-1;\ndb 61,61,61,-1, 66,66,66,-1, 70,70,70,-1, 58,58,58,-1, 66,66,66,-1, 70,70,70,-1;\ndb 61,61,61,-1, 66,66,66,-1, 70,70,70,-1, 58,58,58,-1, 66,66,66,-1, 70,70,70,-1;\ndb 59,59,59,-1, 66,66,66,-1, 71,71,71,-1, 54,54,54,-1, 66,66,66,-1, 71,71,71,-1;\n\n// Part 2\ndb 59,59,59,-1, 66,66,66,-1, 75,75,75,-1, 58,58,58,-1, 66,66,66,-1, 73,73,73,-1;\ndb 57,57,57,-1, 66,66,66,-1, 72,72,72,-1, 60,60,60,-1, 66,66,66,-1, 75,75,75,-1;\ndb 61,61,61,-1, 68,68,68,-1, 76,76,76,-1, 60,60,60,-1, 68,68,68,-1, 76,76,76,-1;\ndb 59,59,59,-1, 68,68,68,-1, 76,76,76,-1, 58,58,58,-1, 66,66,66,-1, 76,76,76,-1;\ndb 47,47,47,-1, 66,66,66,-1, 75,75,75,-1, 54,54,54,-1, 66,66,66,-1, 66,66,66,-1;\ndb 64,-1,64,-1, 64,64,64,-1, -1,-1,58,58, 59,59,59,59, 59,59,59,59, 59,59,59,-1;\n\ndb 0; // End string\n","spuchip/examples/beatbox.txt":"wset 4,inst1;\nchwave 1,4;\nchpitch 1,2.55;\n\nchwave 2,0;\nchvolume 2,0.5;\nchstart 2;\nchpitch 2,0;\n\nmainloop:\n timer r0;\n mul r0,6;\n mov r1,r0;\n\n fint r0;\n mod r0,16;\n add r0,0;\n mod r1,1;\n currentTick = r0;\n currentTickTime = r1;\n\n instr1 = patternData1[currentTick];\n instr2 = patternData2[currentTick];\n instr3 = patternData3[currentTick];\n\n if ((pinstr1 == 0) && (instr1 == 1)) {\n chstart 0;\n } else {\n chstop 0;\n }\n\n if ((pinstr2 == 0) && (instr2 == 1)) {\n chstart 1;\n } else {\n chstop 1;\n }\n\n mov r0,currentTickTime; neg r0; add r0,1; fpwr r0,4;\n mul r0,0.6; // add r0,0.64;\n chpitch 0,r0;\n\n mov r0,instr3;\n mul r0,0.1;\n add r0,0.2;\n chpitch 2,r0;\njmp mainloop;\n\nfloat currentTick,currentTickTime;\nfloat instr1,instr2,instr3;\nfloat pinstr1,pinstr2;\n\npatternData1: db 1,1,0,0, 1,0,0,0, 1,0,0,1, 0,1,0,0, 1,1,0,0, 1,1,0,0, 1,1,0,1, 1,0,1,0;\npatternData2: db 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0;\npatternData3: db 0,1,0,2, 0,1,2,2, 0,0,1,1, 1,2,1,2, 2,2,1,2, 2,2,1,1, 1,1,0,0, 0,0,1,2;\n\nstring inst1,\"synth/pink_noise.wav\";\n"}},"soundlists/":{"soundlists/common_sounds.txt":"AlyxEMP.Charge | property\nAlyxEMP.Discharge | property\nAlyxEMP.Stop | property\nBaseExplosionEffect.Sound | property\nBaseGrenade.BounceSound | property\nBaseGrenade.Explode | property\nBaseGrenade.StopSounds | property\nBullets.DefaultNearmiss | property\nBullets.GunshipNearmiss | property\nBullets.StriderNearmiss | property\nFX_RicochetSound.Ricochet | property\nFuncTank.Fire | property\nFunc_Tank.BeginUse | property\nGenericNPC.GunSound | property\nGrenade.Blip | property\nGrenadeBeam.HitSound | property\nGrenadeBottle.Detonate | property\nGrenadeBugBait.Splat | property\nGrenadeHomer.StopSounds | property\nGrenadePathfollower.StopSounds | property\nGrenadeScanner.StopSound | property\nGrenade_Molotov.Detonate | property\nTripwireGrenade.ShootRope | property\nWaterExplosionEffect.Sound | property\nWeaponFrag.Roll | property\nWeaponFrag.Throw | property\nWeapon_357.OpenLoader | property\nWeapon_357.Reload | property\nWeapon_357.RemoveLoader | property\nWeapon_357.ReplaceLoader | property\nWeapon_357.Single | property\nWeapon_357.Spin | property\nWeapon_AR2.Double | property\nWeapon_AR2.Empty | property\nWeapon_AR2.NPC_Double | property\nWeapon_AR2.NPC_Reload | property\nWeapon_AR2.NPC_Single | property\nWeapon_AR2.Reload | property\nWeapon_AR2.Reload_Push | property\nWeapon_AR2.Reload_Rotate | property\nWeapon_AR2.Single | property\nWeapon_AR2.Special1 | property\nWeapon_AR2.Special2 | property\nWeapon_Binoculars.Reload | property\nWeapon_Binoculars.Special1 | property\nWeapon_Binoculars.Special2 | property\nWeapon_Brickbat.Special1 | property\nWeapon_Bugbait.Splat | property\nWeapon_CombineGuard.Special1 | property\nWeapon_Crossbow.BoltElectrify | property\nWeapon_Crossbow.BoltFly | property\nWeapon_Crossbow.BoltHitBody | property\nWeapon_Crossbow.BoltHitWorld | property\nWeapon_Crossbow.BoltSkewer | property\nWeapon_Crossbow.Reload | property\nWeapon_Crossbow.Single | property\nWeapon_Crowbar.Melee_Hit | property\nWeapon_Crowbar.Melee_HitWorld | property\nWeapon_Crowbar.Single | property\nWeapon_Extinguisher.Double | property\nWeapon_Extinguisher.Empty | property\nWeapon_Extinguisher.NPC_Double | property\nWeapon_Extinguisher.NPC_Reload | property\nWeapon_Extinguisher.NPC_Single | property\nWeapon_Extinguisher.Reload | property\nWeapon_Extinguisher.Single | property\nWeapon_Extinguisher.Special1 | property\nWeapon_FlareGun.Burn | property\nWeapon_FlareGun.Reload | property\nWeapon_FlareGun.Single | property\nWeapon_Gauss.ChargeLoop | property\nWeapon_IRifle.Empty | property\nWeapon_IRifle.Single | property\nWeapon_MegaPhysCannon.Charge | property\nWeapon_MegaPhysCannon.ChargeZap | property\nWeapon_MegaPhysCannon.Drop | property\nWeapon_MegaPhysCannon.DryFire | property\nWeapon_MegaPhysCannon.HoldSound | property\nWeapon_MegaPhysCannon.Launch | property\nWeapon_MegaPhysCannon.Pickup | property\nWeapon_Mortar.Impact | property\nWeapon_Mortar.Incomming | property\nWeapon_Mortar.Single | property\nWeapon_PhysCannon.Charge | property\nWeapon_PhysCannon.CloseClaws | property\nWeapon_PhysCannon.Drop | property\nWeapon_PhysCannon.DryFire | property\nWeapon_PhysCannon.HoldSound | property\nWeapon_PhysCannon.Launch | property\nWeapon_PhysCannon.OpenClaws | property\nWeapon_PhysCannon.Pickup | property\nWeapon_PhysCannon.TooHeavy | property\nWeapon_Physgun.HeavyObject | property\nWeapon_Physgun.LightObject | property\nWeapon_Physgun.LockedOn | property\nWeapon_Physgun.Off | property\nWeapon_Physgun.On | property\nWeapon_Physgun.Scanning | property\nWeapon_Physgun.Special1 | property\nWeapon_Pistol.Burst | property\nWeapon_Pistol.Empty | property\nWeapon_Pistol.NPC_Reload | property\nWeapon_Pistol.NPC_Single | property\nWeapon_Pistol.Reload | property\nWeapon_Pistol.Single | property\nWeapon_Pistol.Special1 | property\nWeapon_Pistol.Special2 | property\nWeapon_RPG.LaserOff | property\nWeapon_RPG.LaserOn | property\nWeapon_RPG.NPC_Single | property\nWeapon_RPG.Single | property\nWeapon_SMG1.Burst | property\nWeapon_SMG1.Double | property\nWeapon_SMG1.Empty | property\nWeapon_SMG1.NPC_Reload | property\nWeapon_SMG1.NPC_Single | property\nWeapon_SMG1.Reload | property\nWeapon_SMG1.Single | property\nWeapon_SMG1.Special1 | property\nWeapon_SMG1.Special2 | property\nWeapon_Shotgun.Double | property\nWeapon_Shotgun.Empty | property\nWeapon_Shotgun.NPC_Reload | property\nWeapon_Shotgun.NPC_Single | property\nWeapon_Shotgun.Reload | property\nWeapon_Shotgun.Single | property\nWeapon_Shotgun.Special1 | property\nWeapon_SniperRifle.NPC_Reload | property\nWeapon_SniperRifle.NPC_Single | property\nWeapon_SniperRifle.Reload | property\nWeapon_SniperRifle.Single | property\nWeapon_SniperRifle.Special1 | property\nWeapon_SniperRifle.Special2 | property\nWeapon_StunStick.Activate | property\nWeapon_StunStick.Deactivate | property\nWeapon_StunStick.Melee_Hit | property\nWeapon_StunStick.Melee_HitWorld | property\nWeapon_StunStick.Melee_Miss | property\nWeapon_StunStick.Swing | property\nWeapon_functank.Single | property\n"}} \ No newline at end of file diff --git a/garrysmod/addons/feature-wire/lua/wire/flir.lua b/garrysmod/addons/feature-wire/lua/wire/flir.lua new file mode 100644 index 0000000..c7c0d3a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/flir.lua @@ -0,0 +1,90 @@ +--[[ + Simulation of FLIR (forward-looking infrared) vision. + Possible future ideas: + * Different materials have different emissivities: + aluminium is about 20%, wherease asphalt is 95%. + maybe we could use the physical properties to simulate this? + * the luminance of a texture contributes *negatively* to its emissivity + * IR sensors often have auto gain control that we might simulate with auto-exposure + * players are drawn fullbright but NPCs aren't. +--]] + +if not FLIR then FLIR = { enabled = false } end + +if CLIENT then + FLIR.living = CreateMaterial("flir_living", "UnlitGeneric", { + ["$basetexture"] = "color/white", + ["$model"] = 1, + }) + + FLIR.normal = CreateMaterial("flir_normal", "VertexLitGeneric", { + ["$basetexture"] = "color/white", + ["$model"] = 1, + ["$halflambert"] = 1 -- causes the diffuse lighting to 'wrap around' more + }) + + FLIR.colmod = { + [ "$pp_colour_addr" ] = 0.4, + [ "$pp_colour_addg" ] = -.5, + [ "$pp_colour_addb" ] = -.5, + [ "$pp_colour_brightness" ] = .1, + [ "$pp_colour_contrast" ] = 1.2, + [ "$pp_colour_colour" ] = 0, + [ "$pp_colour_mulr" ] = 0, + [ "$pp_colour_mulg" ] = 0, + [ "$pp_colour_mulb" ] = 0 + } + + local materialOverrides = { + PlayerDraw = { FLIR.living, FLIR.normal }, + DrawOpaqueRenderables = { FLIR.normal, nil }, + DrawTranslucentRenderables = { FLIR.normal, nil }, + DrawSkybox = { FLIR.normal, nil } + } + + function FLIR.start() + if FLIR.enabled then return else FLIR.enabled = true end + + for hookName, materials in pairs(materialOverrides) do + hook.Add("Pre" .. hookName, "flir", function() render.MaterialOverride(materials[1]) end) + hook.Add("Post" .. hookName, "flir", function() render.MaterialOverride(materials[2]) end) + end + + hook.Add("RenderScreenspaceEffects", "flir", function() + DrawColorModify(FLIR.colmod) + DrawBloom(0,100,5,5,3,0.1,0,0,0) + DrawSharpen(1,0.5) + end) + end + + function FLIR.stop() + if FLIR.enabled then FLIR.enabled = false else return end + for hookName, materials in pairs(materialOverrides) do + hook.Remove("Pre" .. hookName, "flir") + hook.Remove("Post" .. hookName, "flir") + end + hook.Remove("RenderScreenspaceEffects", "flir") + render.MaterialOverride(nil) + end + + function FLIR.enable(enabled) + if enabled then FLIR.start() else FLIR.stop() end + end + + usermessage.Hook("flir.enable",function(um) + FLIR.enable(um:ReadBool()) + end) + + concommand.Add("flir_enable", function(player, command, args) + FLIR.enable(tobool(args[1])) + end) +else + function FLIR.start(player) FLIR.enable(player, true) end + function FLIR.stop(player) FLIR.enable(player, false) end + + function FLIR.enable(player, enabled) + umsg.Start( "flir.enable", player) + umsg.Bool( enabled ) + umsg.End() + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/fx_emitter_default.lua b/garrysmod/addons/feature-wire/lua/wire/fx_emitter_default.lua new file mode 100644 index 0000000..b0191dd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/fx_emitter_default.lua @@ -0,0 +1,101 @@ +AddCSLuaFile() + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( angle:Forward() * 2 ) + effectdata:SetMagnitude( 1 ) + effectdata:SetScale( 1 ) + effectdata:SetRadius( 2 ) + util.Effect( "Sparks", effectdata ) +end +AddFXEmitterEffect( "small_sparks", FX, "Sparks (Small)" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetNormal( angle:Forward() * 2 ) + effectdata:SetMagnitude( 2 ) + effectdata:SetScale( 1 ) + effectdata:SetRadius( 6 ) + util.Effect( "Sparks", effectdata ) +end +AddFXEmitterEffect( "sparks", FX, "Sparks" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos + angle:Forward() * 5 ) + effectdata:SetAngles( angle ) + effectdata:SetScale( 1 ) + util.Effect( "MuzzleEffect", effectdata ) +end +AddFXEmitterEffect( "muzzle", FX, "Muzzleflash" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos + angle:Forward() * 5 ) + effectdata:SetAngles( angle ) + effectdata:SetScale( 2 ) + util.Effect( "MuzzleEffect", effectdata ) +end +AddFXEmitterEffect( "muzzlebig", FX, "Muzzleflash (Big)" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + util.Effect( "BloodImpact", effectdata ) +end +AddFXEmitterEffect( "bloodimpact", FX, "Blood Impact" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetAngles( angle ) + effectdata:SetNormal( angle:Forward() * 2 ) + effectdata:SetMagnitude( 1 ) + effectdata:SetScale( 1 ) + effectdata:SetRadius( 1 ) + util.Effect( "StriderBlood", effectdata ) +end +AddFXEmitterEffect( "striderblood", FX, "Strider Blood" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetAngles( angle ) + util.Effect( "ShotgunShellEject", effectdata ) +end +AddFXEmitterEffect( "shotgun shell", FX, "Shotgun Shell" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetAngles( angle ) + util.Effect( "RifleShellEject", effectdata ) +end +AddFXEmitterEffect( "rifle shell", FX, "Rifle Shell" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetAngles( angle ) + util.Effect( "ShellEject", effectdata ) +end +AddFXEmitterEffect( "pistol shell", FX, "Pistol Shell" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetAngles( angle ) + effectdata:SetNormal( angle:Forward() ) + util.Effect( "MetalSpark", effectdata ) +end +AddFXEmitterEffect( "metalsparks", FX, "Metal Sparks" ) + +local function FX( pos, angle ) + local effectdata = EffectData() + effectdata:SetOrigin( pos ) + effectdata:SetAngles( angle ) + util.Effect( "GlassImpact", effectdata ) +end +AddFXEmitterEffect( "glassimpact", FX, "Glass Impact" ) diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/angle.lua b/garrysmod/addons/feature-wire/lua/wire/gates/angle.lua new file mode 100644 index 0000000..57b37c9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/angle.lua @@ -0,0 +1,342 @@ +--[[ + Angle gates +]] + +GateActions("Angle") + +-- Add +GateActions["angle_add"] = { + name = "Addition", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE" }, + compact_inputs = 2, + outputtypes = { "ANGLE" }, + output = function(gate, A , B , C , D , E , F , G , H) + if !A then A = Angle (0, 0, 0) end + if !B then B = Angle (0, 0, 0) end + if !C then C = Angle (0, 0, 0) end + if !D then D = Angle (0, 0, 0) end + if !E then E = Angle (0, 0, 0) end + if !F then F = Angle (0, 0, 0) end + if !G then G = Angle (0, 0, 0) end + if !H then H = Angle (0, 0, 0) end + return (A + B + C + D + E + F + G + H) + end, + label = function(Out) + return string.format ("Addition = (%d,%d,%d)", + Out.p, Out.y, Out.r) + end +} + +-- Subtract +GateActions["angle_sub"] = { + name = "Subtraction", + inputs = { "A", "B" }, + inputtypes = { "ANGLE", "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A, B) + if !A then A = Angle (0, 0, 0) end + if !B then B = Angle (0, 0, 0) end + return (A - B) + end, + label = function(Out, A, B) + return string.format ("%s - %s = (%d,%d,%d)", A, B, Out.p, Out.y, Out.r) + end +} + +-- Negate +GateActions["angle_neg"] = { + name = "Negate", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + if !A then A = Angle (0, 0, 0) end + return Angle (-A.p, -A.y, -A.r) + end, + label = function(Out, A) + return string.format ("-%s = (%d,%d,%d)", A, Out.p, Out.y, Out.r) + end +} + +-- Multiply/Divide by constant +GateActions["angle_mul"] = { + name = "Multiplication", + inputs = { "A", "B" }, + inputtypes = { "ANGLE", "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A, B) + if !A then A = Angle (0, 0, 0) end + if !B then B = Angle (0, 0, 0) end + return Angle(A.p * B.p , A.y * B.y , A.r * B.r) + end, + label = function(Out, A, B) + return string.format ("%s * %s = (%d,%d,%d)", A, B, Out.p, Out.y, Out.r) + end +} + +-- Component Derivative +GateActions["angle_derive"] = { + name = "Delta", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + timed = true, + output = function(gate, A) + local t = CurTime () + if !A then A = Angle (0, 0, 0) end + local dT, dA = t - gate.LastT, A - gate.LastA + gate.LastT, gate.LastA = t, A + if (dT) then + return Angle (dA.p/dT, dA.y/dT, dA.r/dT) + else + return Angle (0, 0, 0) + end + end, + reset = function(gate) + gate.LastT, gate.LastA = CurTime (), Angle (0, 0, 0) + end, + label = function(Out, A) + return string.format ("diff(%s) = (%d,%d,%d)", A, Out.p, Out.y, Out.r) + end +} + +GateActions["angle_divide"] = { + name = "Division", + inputs = { "A", "B" }, + inputtypes = { "ANGLE", "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A, B) + if !A then A = Angle (0, 0, 0) end + if !B or B == Angle (0, 0, 0) then B = Angle (0, 0, 0) return B end + return Angle(A.p / B.p , A.y / B.y , A.r / B.r) + end, + label = function(Out, A, B) + return string.format ("%s / %s = (%d,%d,%d)", A, B, Out.p, Out.y, Out.r) + end +} +-- Conversion To/From +GateActions["angle_convto"] = { + name = "Compose", + inputs = { "Pitch", "Yaw", "Roll" }, + inputtypes = { "NORMAL", "NORMAL", "NORMAL" }, + outputtypes = { "ANGLE" }, + output = function(gate, Pitch, Yaw, Roll) + return Angle (Pitch, Yaw, Roll) + end, + label = function(Out, Pitch, Yaw, Roll) + return string.format ("angle(%s,%s,%s) = (%d,%d,%d)", Pitch, Yaw, Roll, Out.p, Out.y, Out.r) + end +} + +GateActions["angle_convfrom"] = { + name = "Decompose", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputs = { "Pitch", "Yaw", "Roll" }, + output = function(gate, A) + if A then + return A.p, A.y, A.r + end + return 0, 0, 0 + end, + label = function(Out, A) + return string.format ("%s -> Pitch:%d Yaw:%d Roll:%d", A, Out.Pitch, Out.Yaw, Out.Roll) + end +} + +-- Identity +GateActions["angle_ident"] = { + name = "Identity", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + if !A then A = Angle (0, 0, 0) end + return A + end, + label = function(Out, A) + return string.format ("%s = (%d,%d,%d)", A, Out.p, Out.y, Out.r) + end +} + +-- Shifts the components left. +GateActions["angle_shiftl"] = { + name = "Shift Components Left", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + if !A then A = Angle (0, 0, 0) end + return Angle(A.y,A.r,A.p) + end, + label = function(Out, A ) + return string.format ("shiftL(%s) = (%d,%d,%d)", A , Out.p, Out.y, Out.r) + end +} + +-- Shifts the components right. +GateActions["angle_shiftr"] = { + name = "Shift Components Right", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + if !A then A = Angle (0, 0, 0) end + return Angle(A.r,A.p,A.y) + end, + label = function(Out, A ) + return string.format ("shiftR(%s) = (%d,%d,%d)", A , Out.p, Out.y, Out.r) + end +} + +GateActions["angle_fruvecs"] = { + name = "Direction - (forward, up, right)", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputs = { "Forward", "Up" , "Right" }, + outputtypes = { "VECTOR" , "VECTOR" , "VECTOR" }, + timed = true, + output = function(gate, A ) + if !A then return Vector(0,0,0) , Vector(0,0,0) , Vector(0,0,0) else return A:Forward() , A:Up() , A:Right() end + end, + label = function(Out) + return string.format ("Forward = (%f , %f , %f)\nUp = (%f , %f , %f)\nRight = (%f , %f , %f)", Out.Forward.x , Out.Forward.y , Out.Forward.z, Out.Up.x , Out.Up.y , Out.Up.z, Out.Right.x , Out.Right.y , Out.Right.z) + end +} + +GateActions["angle_norm"] = { + name = "Normalize", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + if !A then A = Angle (0, 0, 0) end + return Angle(math.NormalizeAngle(A.p),math.NormalizeAngle(A.y),math.NormalizeAngle(A.r)) + end, + label = function(Out, A ) + return string.format ("normalize(%s) = (%d,%d,%d)", A , Out.p, Out.y, Out.r) + end +} + +GateActions["angle_tostr"] = { + name = "To String", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = Angle (0, 0, 0) end + return "["..tostring(A.p)..","..tostring(A.y)..","..tostring(A.r).."]" + end, + label = function(Out, A ) + return string.format ("toString(%s) = \""..Out.."\"", A) + end +} + + +-- Equal +GateActions["angle_compeq"] = { + name = "Equal", + inputs = { "A", "B" }, + inputtypes = { "ANGLE", "ANGLE" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if (A == B) then return 1 end + return 0 + end, + label = function(Out, A, B) + return string.format ("(%s == %s) = %d", A, B, Out) + end +} + +-- Not Equal +GateActions["angle_compineq"] = { + name = "Not Equal", + inputs = { "A", "B" }, + inputtypes = { "ANGLE", "ANGLE" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if (A == B) then return 0 end + return 1 + end, + label = function(Out, A, B) + return string.format ("(%s != %s) = %d", A, B, Out) + end +} + +-- Returns a rounded angle. +GateActions["angle_round"] = { + name = "Round", + inputs = { "A" }, + inputtypes = { "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + if !A then A = Angle(0, 0, 0) end + return Angle(math.Round(A.p),math.Round(A.y),math.Round(A.r)) + end, + label = function(Out, A) + return string.format ("round(%s) = (%d,%d,%d)", A, Out.p, Out.y, Out.r) + end +} + +GateActions["angle_select"] = { + name = "Select", + inputs = { "Choice", "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "NORMAL", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE", "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function(gate, Choice, ...) + Choice = math.Clamp(Choice,1,8) + return ({...})[Choice] + end, + label = function(Out, Choice) + return string.format ("select(%s) = %s", Choice, Out) + end +} + + +GateActions["angle_mulcomp"] = { + name = "Multiplication (component)", + inputs = { "A", "B" }, + inputtypes = { "ANGLE", "NORMAL" }, + outputtypes = { "ANGLE" }, + output = function(gate, A, B) + if !A then A = Angle(0, 0, 0) end + if !B then B = 0 end + return Angle( A.p * B, A.y * B, A.r * B ) + end, + label = function(Out, A, B) + return string.format ("%s * %s = "..tostring(Out), A, B ) + end +} + +GateActions["angle_clampn"] = { + name = "Clamp (numbers)", + inputs = { "A", "Min", "Max" }, + inputtypes = { "ANGLE", "NORMAL", "NORMAL" }, + outputtypes = { "ANGLE" }, + output = function( gate, A, Min, Max ) + if (Min > Max) then Min, Max = Max, Min end + return Angle( math.Clamp(A.p,Min,Max), math.Clamp(A.y,Min,Max), math.Clamp(A.r,Min,Max) ) + end, + label = function( Out, A, Min, Max ) + return "Clamp(" .. A .. "," .. Min .. "," .. Max .. ") = " .. tostring(Out) + end +} + +GateActions["angle_clampa"] = { + name = "Clamp (angles)", + inputs = { "A", "Min", "Max" }, + inputtypes = { "ANGLE", "ANGLE", "ANGLE" }, + outputtypes = { "ANGLE" }, + output = function( gate, A, Min, Max ) + if (Min.p > Max.p) then Min.p, Max.p = Max.p, Min.p end + if (Min.y > Max.y) then Min.y, Max.y = Max.y, Min.y end + if (Min.r > Max.r) then Min.r, Max.r = Max.r, Min.r end + return Angle( math.Clamp(A.p,Min.p,Max.p), math.Clamp(A.y,Min.y,Max.y), math.Clamp(A.r,Min.r,Max.r) ) + end, + label = function( Out, A, Min, Max ) + return "Clamp(" .. A .. "," .. Min .. "," .. Max .. ") = " .. tostring(Out) + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/arithmetic.lua b/garrysmod/addons/feature-wire/lua/wire/gates/arithmetic.lua new file mode 100644 index 0000000..f3ec7ca --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/arithmetic.lua @@ -0,0 +1,422 @@ +--[[ + Arithmetic Gates +]] + +GateActions("Arithmetic") + +GateActions["increment"] = { + name = "Increment", + inputs = { "A", "Clk", "Reset" }, + output = function(gate, A, Clk, Reset) + local clk = ( Clk > 0 ) + local reset = ( Reset > 0 ) + + if ( gate.PrevValue ~= clk ) then + gate.PrevValue = clk + if ( clk ) then + if ( gate.Memory == nil ) then + gate.Memory = A + else + gate.Memory = gate.Memory + A + end + end + end + + if( gate.PrevReset ~= reset ) then + gate.PrevReset = reset + if ( reset ) then + gate.Memory = 0 + end + end + + return gate.Memory + end, + label = function(Out, A) + return "LastNum += " .. A .. " = " .. Out + end +} + +GateActions["identity"] = { + name = "Identity (No change)", + inputs = { "A" }, + output = function(gate, A) + return A + end, + label = function(Out, A) + return A.." = "..Out + end +} + +GateActions["negate"] = { + name = "Negate", + inputs = { "A" }, + output = function(gate, A) + return -A + end, + label = function(Out, A) + return "-"..A.." = "..Out + end +} + +GateActions["inverse"] = { + name = "Inverse", + inputs = { "A" }, + output = function(gate, A) + if (A) and (math.abs(A) >= 0.0001) then return 1/A end + return 0 + end, + label = function(Out, A) + return "1/"..A.." = "..Out + end +} + +GateActions["sqrt"] = { + name = "Square Root", + inputs = { "A" }, + output = function(gate, A) + return math.sqrt(math.abs(A)) -- Negatives are possible, use absolute value + end, + label = function(Out, A) + --[[if ( A < 0 ) then + return "sqrt("..A..") = i"..Out -- Display as imaginary if A is negative + else]] + return "sqrt("..A..") = "..Out + --end + end +} + +GateActions["log"] = { + name = "Log", + inputs = { "A" }, + output = function(gate, A) + return math.log(A) + end, + label = function(Out, A) + return "log("..A..") = "..Out + end +} + +GateActions["log10"] = { + name = "Log 10", + inputs = { "A" }, + output = function(gate, A) + return math.log10(A) + end, + label = function(Out, A) + return "log10("..A..") = "..Out + end +} + +GateActions["abs"] = { + name = "Absolute", + inputs = { "A" }, + output = function(gate, A) + return math.abs(A) + end, + label = function(Out, A) + return "abs("..A..") = "..Out + end +} + +GateActions["sgn"] = { + name = "Sign (-1,0,1)", + inputs = { "A" }, + output = function(gate, A) + if (A > 0) then return 1 end + if (A < 0) then return -1 end + return 0 + end, + label = function(Out, A) + return "sgn("..A..") = "..Out + end +} + +GateActions["floor"] = { + name = "Floor (Round down)", + inputs = { "A" }, + output = function(gate, A) + return math.floor(A) + end, + label = function(Out, A) + return "floor("..A..") = "..Out + end +} + +GateActions["round"] = { + name = "Round", + inputs = { "A" }, + output = function(gate, A) + return math.Round(A) + end, + label = function(Out, A) + return "round("..A..") = "..Out + end +} + +GateActions["ceil"] = { + name = "Ceiling (Round up)", + inputs = { "A" }, + output = function(gate, A) + return math.ceil(A) + end, + label = function(Out, A) + return "ceil("..A..") = "..Out + end +} + +GateActions["+"] = { + name = "Add", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + local result = 0 + for k,v in ipairs({...}) do + if (v) then result = result+v end + end + return result + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." + " end + end + return string.sub(txt, 1, -4).." = "..Out + end +} + +GateActions["-"] = { + name = "Subtract", + inputs = { "A", "B" }, + colors = { Color(255, 0, 0, 255), Color(0, 0, 255, 255) }, + output = function(gate, A, B) + return A-B + end, + label = function(Out, A, B) + return A.." - "..B.." = "..Out + end +} + +GateActions["*"] = { + name = "Multiply", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + local result = 1 + for k,v in ipairs({...}) do + if (v) then result = result*v end + end + return result + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." * " end + end + return string.sub(txt, 1, -4).." = "..Out + end +} + +GateActions["/"] = { + name = "Divide", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (math.abs(B) < 0.0001) then return 0 end + return A/B + end, + label = function(Out, A, B) + return A.." / "..B.." = "..Out + end +} + +GateActions["%"] = { + name = "Modulo", + inputs = { "A", "B" }, + output = function(gate, A, B) + if ( B == 0 ) then return 0 end + return math.fmod(A,B) + end, + label = function(Out, A, B) + return A.." % "..B.." = "..Out + end +} + +GateActions["rand"] = { + name = "Random", + inputs = { "A", "B" }, + timed = true, + output = function(gate, A, B) + return math.random()*(B-A)+A + end, + label = function(Out, A, B) + return "random("..A.." - "..B..") = "..Out + end +} + +GateActions["PI"] = { + name = "PI", + inputs = { }, + output = function(gate) + return math.pi + end, + label = function(Out) + return "PI = "..Out + end +} + +GateActions["exp"] = { + name = "Exp", + inputs = { "A" }, + output = function(gate, A) + return math.exp(A) + end, + label = function(Out, A) + return "exp("..A..") = "..Out + end +} + +GateActions["pow"] = { + name = "Exponential Powers", + inputs = { "A", "B" }, + output = function(gate, A, B) + return A ^ B + end, + label = function(Out, A, B) + return "pow("..A..", "..B..") = "..Out + end +} + +GateActions["and/add"] = { + name = "And/Add", + inputs = { "A", "B"}, + output = function(gate, A, B) + if ((A) and (A <= 0)) or ((B) and (B <= 0)) then return 0 end + return A+B + end, + label = function(Out, A, B) + return A.." and/and "..B.." = "..Out + end +} + +GateActions["Percent"] = { + name = "Percent", + inputs = { "Value", "Max" }, + compact_inputs = 2, + output = function(gate, Value, Max) + if (math.abs(Max) < 0.0001) then return 0 end + return Value / Max * 100 + end, + label = function(Out, Value, Max) + return Value.." / "..Max.." * 100 = "..Out.."%" + end +} + +GateActions["Delta"] = { + name = "Delta", + inputs = { "A" }, + output = function(gate, A) + gate.PrevValue = gate.PrevValue or 0 + local delta = A - gate.PrevValue + gate.PrevValue = A + return delta + end, + reset = function(gate) + gate.PrevValue = 0 + end, + label = function(Out, A) + return "Delta("..A..") " + end +} + +GateActions["Delta360"] = { + name = "Delta (Rectified)", + inputs = { "A" }, + output = function(gate, A) + gate.PrevValue = gate.PrevValue or 0 + local delta = A - gate.PrevValue + gate.PrevValue = A + return ( math.fmod( (math.fmod( delta, 360 ) + 540 ), 360 ) - 180 ) + end, + reset = function(gate) + gate.PrevValue = 0 + end, + label = function(Out, A) + return "Delta("..A..") " + end +} + +GateActions["Average"] = { + name = "Average", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + local vals = 0 + local value = 0 + for k,v in ipairs({...}) do + vals = vals + 1 + value = value + v + end + return value / vals + end, + label = function(Out, ...) + local vals = 0 + local message = "(" + for k,v in ipairs({...}) do + vals = vals + 1 + message = message .. v .. " + " + end + message = string.sub(message,1,-4) + message = message .. ") / " .. vals .. " = " .. Out + return message + end +} + + +GateActions["increment/decrement"] = { + name = "Increment/Decrement", + inputs = { "A", "Increment", "Decrement", "Reset" }, + output = function(gate, A, Increment, Decrement, Reset) + local increment = ( Increment > 0 ) + local decrement = ( Decrement > 0 ) + local reset = (Reset > 0) + + if ( gate.PrevValue ~= increment ) then + gate.PrevValue = increment + if ( increment ) then + gate.Memory = (gate.Memory or 0) + A + end + end + + if ( gate.PrevValue ~= decrement ) then + gate.PrevValue = decrement + if ( decrement ) then + gate.Memory = (gate.Memory or 0) - A + end + end + + if( gate.PrevReset ~= reset ) then + gate.PrevReset = reset + if ( reset ) then + gate.Memory = 0 + end + end + + return gate.Memory + end, + label = function(Out, A) + return "(" .. A .. " +/- LastNum) = " .. Out + end +} + +GateActions["clamp"] = { + group = "Arithmetic", + name = "Clamp", + inputs = { "A", "Min", "Max" }, + output = function( gate, A, Min, Max ) + return math.Clamp( A, Min, Max ) + end, + label = function( Out, A, Min, Max ) + return "Clamp(" .. A .. "," .. Min .. "," .. Max .. ") = " .. Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/array.lua b/garrysmod/addons/feature-wire/lua/wire/gates/array.lua new file mode 100644 index 0000000..30f542c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/array.lua @@ -0,0 +1,150 @@ +--[[ + Array gates +]] + +GateActions("Array") + + +local types_defaults = { + NUMBER = 0, + ANGLE = Angle(0,0,0), + VECTOR = Vector(0,0,0), + STRING = "", + ENTITY = NULL, +} + +local types_formats = { + NUMBER = function(x) return tostring(x) end, + ANGLE = function(x) return string.format("(%d,%d,%d)",x.p,x.r,x.y) end, + VECTOR = function(x) return string.format("(%d,%d,%d)",x.x,x.y,x.z) end, + STRING = function(x) return x end, + ENTITY = function(x) return tostring(x) end, +} + +local types_compare = { -- used for array find gates. + ANGLE = function(a,b) return a.p == b.p and + a.y == b.y and + a.r == b.r end, + VECTOR = function(a,b) return a.x == b.x and + a.y == b.y and + a.z == b.z end, +} +local normal_compare = function(a,b) return a == b end + +for type_name, default in pairs( types_defaults ) do + local type_name2 = type_name + if type_name2 == "NUMBER" then type_name2 = "NORMAL" end + local compare = types_compare[type_name] or normal_compare + + GateActions["array_read_" .. type_name] = { + name = "Array Read (" .. type_name .. ")", + inputs = { "R", "Index" }, + inputtypes = { "ARRAY", "NORMAL" }, + outputtypes = { type_name2 }, + output = function(gate, r, index) + local var = r[math.floor(index)] + local tp = type(var) + + if not var then return default end + + if tp == "Player" and type_name == "ENTITY" then return var end -- Special case + if tp == "NPC" and type_name == "ENTITY" then return var end -- Special case + if string.upper(tp) ~= type_name then return default end + + return var + end, + label = function(Out,r,index) + return string.format( "%s[%s] = %s", r, index, types_formats[type_name](Out) ) + end, + } + + GateActions["array_find_" .. type_name] = { + name = "Array Find (" .. type_name .. ")", + inputs = { "R", "Value" }, + inputtypes = { "ARRAY", type_name2 }, + outputtypes = { "NORMAL" }, + output = function(gate, r, value) + for i=1,#r do + if i > 10000 then return 0 end -- Stop iterating too much to prevent lag + + local var = r[i] + + if compare(var,value) then return i end + end + + return 0 + end, + label = function(Out,r,index) + return string.format( "find(%s,%s) = %d", r, index, Out ) + end, + } + + --[[ + I feel there is no need for these gates at this time. + The only time you'll encounter arrays with gates + are in a situation where you only need to read. + I'll add it here for future reference + + GateActions["array_write_" .. type_name] = { + name = "Array Write (" .. type_name .. ")", + inputs = { "R", "Index", "Value" }, + inputtypes = { "ARRAY", "NORMAL", type_name2 }, + output = function(gate, r, index, value) + if type(var) ~= string.lower(type_name) then return end + if not var then return end + + r[math.floor(index)] = value + end, + label = function(Out,r,index) + return string.format( "%s[%s] = %s", r, index, types_formats[type_name](Out) ) + end, + } + ]] +end + +--[[ + I feel there is no need for this gate at this time. + The only time you'll encounter arrays with gates + are in a situation where you only need to read. + I'll add it here for future reference + +GateActions["array_create"] = { + name = "Array Create", + output = function(gate) + return {} + end, +} +]] + +GateActions["array_gettype"] = { + name = "Array Get Type", + inputs = { "R", "Index" }, + inputtypes = { "ARRAY", "NORMAL" }, + outputtypes = { "STRING" }, + output = function(gate, r, index) + local tp = type(r[math.floor(index)]) + + if tp == "nil" then return "NIL" end + if tp == "Player" then return "ENTITY" end -- Special case + if tp == "NPC" then return "ENTITY" end -- Special case + if not types_defaults[string.upper(tp)] then return "TYPE NOT SUPPORTED" end + + return tp + end, + label = function(Out,r,index) + return string.format( "type(%s[%s]) = %s", r, index, Out ) + end, +} + +GateActions["array_count"] = { + name = "Array Count", + inputs = { "R" }, + inputtypes = { "ARRAY" }, + outputtypes = { "NORMAL" }, + output = function(gate, r) + return #r + end, + label = function(Out,r,index) + return string.format( "#%s = %s", r, Out ) + end, +} diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/bitwise.lua b/garrysmod/addons/feature-wire/lua/wire/gates/bitwise.lua new file mode 100644 index 0000000..f7148e4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/bitwise.lua @@ -0,0 +1,73 @@ +--[[ + Bitwise Gates +]] + +GateActions("Bitwise") + +GateActions["bnot"] = { + name = "Not", + inputs = { "A" }, + output = function(gate, A) + return bit.bnot(A) + end, + label = function(Out, A) + return "not "..A.." = "..Out + end +} + +GateActions["bor"] = { + name = "Or", + inputs = { "A", "B" }, + output = function(gate, A, B) + return bit.bor(A, B) + end, + label = function(Out, A, B) + return A.." or "..B.." = "..Out + end +} + +GateActions["band"] = { + name = "And", + inputs = { "A", "B" }, + output = function(gate, A, B) + return bit.band(A, B) + end, + label = function(Out, A, B) + return A.." and "..B.." = "..Out + end +} + +GateActions["bxor"] = { + name = "Xor", + inputs = { "A", "B" }, + output = function(gate, A, B) + return bit.bxor(A, B) + end, + label = function(Out, A, B) + return A.." xor "..B.." = "..Out + end +} + +GateActions["bshr"] = { + name = "Bit shift right", + inputs = { "A", "B" }, + output = function(gate, A, B) + return bit.rshift(A, B) + end, + label = function(Out, A, B) + return A.." >> "..B.." = "..Out + end +} + +GateActions["bshl"] = { + name = "Bit shift left", + inputs = { "A", "B" }, + output = function(gate, A, B) + return bit.lshift(A, B) + end, + label = function(Out, A, B) + return A.." << "..B.." = "..Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/comparison.lua b/garrysmod/addons/feature-wire/lua/wire/gates/comparison.lua new file mode 100644 index 0000000..4690153 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/comparison.lua @@ -0,0 +1,113 @@ +--[[ + Comparison Gates +]] + +GateActions("Comparison") + +GateActions["="] = { + name = "Equal", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (math.abs(A-B) < 0.001) then return 1 end + return 0 + end, + label = function(Out, A, B) + return A.." == "..B.." = "..Out + end +} + +GateActions["!="] = { + name = "Not Equal", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (math.abs(A-B) < 0.001) then return 0 end + return 1 + end, + label = function(Out, A, B) + return A.." ~= "..B.." = "..Out + end +} + +GateActions["<"] = { + name = "Less Than", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (A < B) then return 1 end + return 0 + end, + label = function(Out, A, B) + return A.." < "..B.." = "..Out + end +} + +GateActions[">"] = { + name = "Greater Than", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (A > B) then return 1 end + return 0 + end, + label = function(Out, A, B) + return A.." > "..B.." = "..Out + end +} + +GateActions["<="] = { + name = "Less or Equal", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (A <= B) then return 1 end + return 0 + end, + label = function(Out, A, B) + return A.." <= "..B.." = "..Out + end +} + +GateActions[">="] = { + name = "Greater or Equal", + inputs = { "A", "B" }, + output = function(gate, A, B) + if (A >= B) then return 1 end + return 0 + end, + label = function(Out, A, B) + return A.." >= "..B.." = "..Out + end +} + +GateActions["inrangei"] = { + name = "Is In Range (Inclusive)", + inputs = { "Min", "Max", "Value" }, + output = function(gate, Min, Max, Value) + if (Max < Min) then + local temp = Max + Max = Min + Min = temp + end + if ((Value >= Min) && (Value <= Max)) then return 1 end + return 0 + end, + label = function(Out, Min, Max, Value) + return Min.." <= "..Value.." <= "..Max.." = "..Out + end +} + +GateActions["inrangee"] = { + name = "Is In Range (Exclusive)", + inputs = { "Min", "Max", "Value" }, + output = function(gate, Min, Max, Value) + if (Max < Min) then + local temp = Max + Max = Min + Min = temp + end + if ((Value > Min) && (Value < Max)) then return 1 end + return 0 + end, + label = function(Out, Min, Max, Value) + return Min.." < "..Value.." < "..Max.." = "..Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/entity.lua b/garrysmod/addons/feature-wire/lua/wire/gates/entity.lua new file mode 100644 index 0000000..f87283d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/entity.lua @@ -0,0 +1,933 @@ +--[[ + Entity gates +]] + +GateActions("Entity") + +local clamp = WireLib.clampForce + +local function isAllowed( gate, ent ) + if not IsValid(gate:GetPlayer()) then return false end + return hook.Run( "PhysgunPickup", gate:GetPlayer(), ent ) ~= false +end + +GateActions["entity_applyf"] = { + name = "Apply Force", + inputs = { "Ent" , "Vec" }, + inputtypes = { "ENTITY" , "VECTOR" }, + timed = true, + output = function(gate, ent, vec ) + if not IsValid( ent ) then return end + local phys = ent:GetPhysicsObject() + if not IsValid( phys ) then return end + if not isAllowed( gate, ent ) then return end + if !isvector(vec) then vec = Vector (0, 0, 0) end + vec = clamp(vec) + if vec.x == 0 and vec.y == 0 and vec.z == 0 then return end + + phys:ApplyForceCenter( vec ) + end, + label = function(_,ent,vec) + return string.format( "(%s):applyForce(%s)", ent, vec ) + end +} + +GateActions["entity_applyof"] = { + name = "Apply Offset Force", + inputs = { "Ent" , "Vec" , "Offset" }, + inputtypes = { "ENTITY" , "VECTOR" , "VECTOR" }, + timed = true, + output = function(gate, ent, vec, offset ) + if not IsValid( ent ) then return end + local phys = ent:GetPhysicsObject() + if not IsValid( phys ) then return end + if not isAllowed( gate, ent ) then return end + if !isvector(vec) then vec = Vector (0, 0, 0) end + if !isvector(offset) then offset = Vector (0, 0, 0) end + vec = clamp(vec) + offset = clamp(offset) + if vec.x == 0 and vec.y == 0 and vec.z == 0 then return end + + phys:ApplyForceOffset(vec, offset) + end, + label = function(_,ent,vec,offset) + return string.format( "(%s):applyForceOffset(%s,%s)", ent, vec, offset ) + end +} + +-- Base code taken from Expression 2 + +GateActions["entity_applyaf"] = { + name = "Apply Angular Force", + inputs = { "Ent" , "Ang" }, + inputtypes = { "ENTITY" , "ANGLE" }, + timed = true, + output = function(gate, ent, angForce ) + if not IsValid( ent ) then return end + local phys = ent:GetPhysicsObject() + if not IsValid( phys ) then return end + if not isAllowed( gate, ent ) then return end + local clampedForce = clamp(angForce) + if clampedForce.x == 0 and clampedForce.y == 0 and clampedForce.z == 0 then return end + + -- assign vectors + local up = ent:GetUp() + local left = ent:GetRight() * -1 + local forward = ent:GetForward() + + -- apply pitch force + if clampedForce.x ~= 0 then + local pitch = up * (clampedForce.x * 0.5) + phys:ApplyForceOffset( forward, pitch ) + phys:ApplyForceOffset( forward * -1, pitch * -1 ) + end + + -- apply yaw force + if clampedForce.y ~= 0 then + local yaw = forward * (clampedForce.y * 0.5) + phys:ApplyForceOffset( left, yaw ) + phys:ApplyForceOffset( left * -1, yaw * -1 ) + end + + -- apply roll force + if clampedForce.z ~= 0 then + local roll = left * (clampedForce.z * 0.5) + phys:ApplyForceOffset( up, roll ) + phys:ApplyForceOffset( up * -1, roll * -1 ) + end + end, + label = function(Out,ent,angForce) + return string.format( "(%s):applyAngForce(%s)", ent, angForce ) + end +} + + +-- Taken from Expression 2 +local abs = math.abs +GateActions["entity_applytorq"] = { + name = "Apply Torque", + inputs = { "Ent" , "Vec" }, + inputtypes = { "ENTITY" , "VECTOR" }, + timed = true, + output = function(gate, ent, vec ) + if not IsValid( ent ) then return end + local phys = ent:GetPhysicsObject() + if not IsValid( phys ) then return end + if not isAllowed( gate, ent ) then return end + if !isvector(vec) then vec = Vector (0, 0, 0) end + if !isvector(offset) then offset = Vector (0, 0, 0) end + vec = clamp(vec) + offset = clamp(offset) + if vec.x == 0 and vec.y == 0 and vec.z == 0 then return end + + local tq = vec + local torqueamount = tq:Length() + + -- Convert torque from local to world axis + tq = phys:LocalToWorld( tq ) - phys:GetPos() + + -- Find two vectors perpendicular to the torque axis + local off + if abs(tq.x) > torqueamount * 0.1 or abs(tq.z) > torqueamount * 0.1 then + off = Vector(-tq.z, 0, tq.x) + else + off = Vector(-tq.y, tq.x, 0) + end + off = off:GetNormal() * torqueamount * 0.5 + + local dir = ( tq:Cross(off) ):GetNormal() + + dir = clamp(dir) + off = clamp(off) + phys:ApplyForceOffset( dir, off ) + phys:ApplyForceOffset( dir * -1, off * -1 ) + end, + label = function(_,ent,vec) + return string.format( "(%s):applyTorque(%s)", ent, vec ) + end +} + + + +GateActions["entity_class"] = { + name = "Class", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "STRING" }, + output = function(gate, Ent) + if !Ent:IsValid() then return "" else return Ent:GetClass() end + end, + label = function(Out) + return string.format ("Class = %q", Out) + end +} + +GateActions["entity_entid"] = { + name = "Entity ID", + inputs = { "A" }, + inputtypes = { "ENTITY" }, + output = function(gate, A) + if (A and A:IsValid()) then return A:EntIndex() end + return 0 + end, + label = function(Out, A) + return string.format ("entID(%s) = %d", A, Out) + end +} + +GateActions["entity_id2ent"] = { + name = "ID to Entity", + inputs = { "A" }, + outputtypes = { "ENTITY" }, + output = function(gate, A) + local Ent = Entity(A) + if !Ent:IsValid() then return NULL end + return Ent + end, + label = function(Out, A) + return string.format ("Entity(%s) = %s", A, tostring(Out)) + end +} + + +GateActions["entity_model"] = { + name = "Model", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "STRING" }, + output = function(gate, Ent) + if !Ent:IsValid() then return "" else return Ent:GetModel() end + end, + label = function(Out) + return string.format ("Model = %q", Out) + end +} + +GateActions["entity_steamid"] = { + name = "SteamID", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "STRING" }, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:IsPlayer() then return "" else return Ent:SteamID() end + end, + label = function(Out) + return string.format ("SteamID = %q", Out) + end +} + +GateActions["entity_pos"] = { + name = "Position", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Vector(0,0,0) else return Ent:GetPos() end + end, + label = function(Out) + return string.format ("Position = (%d,%d,%d)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_fruvecs"] = { + name = "Direction - (forward, right, up)", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputs = { "Forward", "Right" , "Up" }, + outputtypes = { "VECTOR" , "VECTOR" , "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Vector(0,0,0) , Vector(0,0,0) , Vector(0,0,0) else return Ent:GetForward() , Ent:GetRight() , Ent:GetUp() end + end, + label = function(Out) + return string.format ("Forward = (%f , %f , %f)\nUp = (%f , %f , %f)\nRight = (%f , %f , %f)", Out.Forward.x , Out.Forward.y , Out.Forward.z, Out.Up.x , Out.Up.y , Out.Up.z, Out.Right.x , Out.Right.y , Out.Right.z) + end +} + +GateActions["entity_isvalid"] = { + name = "Is Valid", + inputs = { "A" }, + inputtypes = { "ENTITY" }, + timed = true, + output = function(gate, A) + if (A and IsEntity (A) and A:IsValid ()) then + return 1 + end + return 0 + end, + label = function(Out, A) + return string.format ("isValid(%s) = %s", A, Out) + end +} + +GateActions["entity_vell"] = { + name = "Velocity (local)", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Vector(0,0,0) else return Ent:WorldToLocal(Ent:GetVelocity() + Ent:GetPos()) end + end, + label = function(Out) + return string.format ("Velocity (local) = (%f , %f , %f)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_vel"] = { + name = "Velocity", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Vector(0,0,0) else return Ent:GetVelocity() end + end, + label = function(Out) + return string.format ("Velocity = (%f , %f , %f)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_angvel"] = { + name = "Angular Velocity", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ANGLE" }, + timed = true, + output = function(gate, Ent) + local Vec + if !Ent:IsValid() or !Ent:GetPhysicsObject():IsValid() then Vec = Vector(0,0,0) else Vec = Ent:GetPhysicsObject():GetAngleVelocity() end + return Angle(Vec.y, Vec.z, Vec.x) + end, + label = function(Out) + return string.format ("Angular Velocity = (%f , %f , %f)", Out.p , Out.y , Out.r ) + end +} + +GateActions["entity_angvelvec"] = { + name = "Angular Velocity (vector)", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if not Ent:IsValid() then return Vector(0,0,0) end + local phys = Ent:GetPhysicsObject() + if not phys:IsValid() then return Vector( 0, 0, 0 ) end + return phys:GetAngleVelocity() + end, + label = function(Out) + return string.format ("Angular Velocity = (%f , %f , %f)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_wor2loc"] = { + name = "World To Local (vector)", + inputs = { "Ent" , "Vec" }, + inputtypes = { "ENTITY" , "VECTOR" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent , Vec ) + if Ent:IsValid() and isvector(Vec) then return Ent:WorldToLocal(Vec) else return Vector(0,0,0) end + end, + label = function(Out) + return string.format ("World To Local = (%f , %f , %f)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_loc2wor"] = { + name = "Local To World (Vector)", + inputs = { "Ent" , "Vec" }, + inputtypes = { "ENTITY" , "VECTOR" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent , Vec ) + if Ent:IsValid() and isvector(Vec) then return Ent:LocalToWorld(Vec) else return Vector(0,0,0) end + end, + label = function(Out) + return string.format ("Local To World Vector = (%f , %f , %f)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_wor2loc"] = { + name = "World To Local (Vector)", + inputs = { "Ent" , "Vec" }, + inputtypes = { "ENTITY" , "VECTOR" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent , Vec ) + if Ent:IsValid() and isvector(Vec) then return Ent:WorldToLocal(Vec) else return Vector(0,0,0) end + end, + label = function(Out) + return string.format ("World To Local Vector = (%f , %f , %f)", Out.x , Out.y , Out.z ) + end +} + +GateActions["entity_loc2worang"] = { + name = "Local To World (Angle)", + inputs = { "Ent" , "Ang" }, + inputtypes = { "ENTITY" , "ANGLE" }, + outputtypes = { "ANGLE" }, + timed = true, + output = function(gate, Ent , Ang ) + if Ent:IsValid() and Ang then return Ent:LocalToWorldAngles(Ang) else return Angle(0,0,0) end + end, + label = function(Out) + return string.format ("Local To World Angles = (%d,%d,%d)", Out.p , Out.y , Out.r ) + end +} + +GateActions["entity_wor2locang"] = { + name = "World To Local (Angle)", + inputs = { "Ent" , "Ang" }, + inputtypes = { "ENTITY" , "ANGLE" }, + outputtypes = { "ANGLE" }, + timed = true, + output = function(gate, Ent , Ang ) + if Ent:IsValid() and Ang then return Ent:WorldToLocalAngles(Ang) else return Angle(0,0,0) end + end, + label = function(Out) + return string.format ("World To Local Angles = (%d,%d,%d)", Out.p , Out.y , Out.r ) + end +} + +GateActions["entity_health"] = { + name = "Health", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 else return Ent:Health() end + end, + label = function(Out) + return string.format ("Health = %d", Out) + end +} + +GateActions["entity_radius"] = { + name = "Radius", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 else return Ent:BoundingRadius() end + end, + label = function(Out) + return string.format ("Radius = %d", Out) + end +} + +GateActions["entity_mass"] = { + name = "Mass", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:GetPhysicsObject():IsValid() then return 0 else return Ent:GetPhysicsObject():GetMass() end + end, + label = function(Out) + return string.format ("Mass = %d", Out) + end +} + +GateActions["entity_masscenter"] = { + name = "Mass Center", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:GetPhysicsObject():IsValid() then return Vector(0,0,0) else return Ent:LocalToWorld(Ent:GetPhysicsObject():GetMassCenter()) end + end, + label = function(Out) + return string.format ("Mass Center = (%d,%d,%d)", Out.x , Out.y , Out.z) + end +} + +GateActions["entity_masscenterlocal"] = { + name = "Mass Center (local)", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:GetPhysicsObject():IsValid() then return Vector(0,0,0) else return Ent:GetPhysicsObject():GetMassCenter() end + end, + label = function(Out) + return string.format ("Mass Center (local) = (%d,%d,%d)", Out.x , Out.y , Out.z) + end +} + +GateActions["entity_isplayer"] = { + name = "Is Player", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsPlayer() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is Player = %d", Out) + end +} + +GateActions["entity_isnpc"] = { + name = "Is NPC", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsNPC() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is NPC = %d", Out) + end +} + +GateActions["entity_isvehicle"] = { + name = "Is Vehicle", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsVehicle() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is Vehicle = %d", Out) + end +} + +GateActions["entity_isworld"] = { + name = "Is World", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsWorld() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is World = %d", Out) + end +} + +GateActions["entity_isongrnd"] = { + name = "Is On Ground", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsOnGround() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is On Ground = %d", Out) + end +} + +GateActions["entity_isunderwater"] = { + name = "Is Under Water", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:WaterLevel() > 0 then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is Under Water = %d", Out) + end +} + +GateActions["entity_angles"] = { + name = "Angles", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ANGLE" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Angle(0,0,0) else return Ent:GetAngles() end + end, + label = function(Out) + return string.format ("Angles = (%d,%d,%d)", Out.p , Out.y , Out.r) + end +} + +GateActions["entity_material"] = { + name = "Material", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "STRING" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return "" else return Ent:GetMaterial() end + end, + label = function(Out) + return string.format ("Material = %q", Out) + end +} + +GateActions["entity_owner"] = { + name = "Owner", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ENTITY" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return WireLib.GetOwner(gate) end + return WireLib.GetOwner(Ent) + end, + label = function(Out,Ent) + return string.format ("owner(%s) = %s", Ent, tostring(Out)) + end +} + +GateActions["entity_isheld"] = { + name = "Is Player Holding", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsPlayerHolding() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is Player Holding = %d", Out) + end +} + +GateActions["entity_isonfire"] = { + name = "Is On Fire", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsOnFire()then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is On Fire = %d", Out) + end +} + +GateActions["entity_isweapon"] = { + name = "Is Weapon", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsWeapon() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is Weapon = %d", Out) + end +} + +GateActions["player_invehicle"] = { + name = "Is In Vehicle", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsPlayer() and Ent:InVehicle() then return 1 else return 0 end + end, + label = function(Out) + return string.format ("Is In Vehicle = %d", Out) + end +} + +GateActions["player_connected"] = { + name = "Time Connected", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return 0 end + if Ent:IsPlayer() then return Ent:TimeConnected() else return 0 end + end, + label = function(Out) + return string.format ("Time Connected = %d", Out) + end +} +GateActions["entity_aimentity"] = { + name = "AimEntity", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ENTITY" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return NULL end + local EntR = Ent:GetEyeTraceNoCursor().Entity + if !EntR:IsValid() then return NULL end + return EntR + end, + label = function(Out) + return string.format ("Aim Entity = %s", tostring(Out)) + end +} + +GateActions["entity_aimenormal"] = { + name = "AimNormal", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Vector(0,0,0) end + if (Ent:IsPlayer()) then + return Ent:GetAimVector() + else + return Ent:GetForward() + end + end, + label = function(Out, A) + return string.format ("Aim Normal (%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +GateActions["entity_aimedirection"] = { + name = "AimDirection", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:IsPlayer() then return Vector(0,0,0) end + return Ent:GetEyeTraceNoCursor().Normal + end, + label = function(Out, A) + return string.format ("Aim Direction (%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +GateActions["entity_inertia"] = { + name = "Inertia", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:GetPhysicsObject():IsValid() then return Vector(0,0,0) end + return Ent:GetPhysicsObject():GetInertia() + end, + label = function(Out, A) + return string.format ("inertia(%s) = (%d,%d,%d)", Ent, Out.x, Out.y, Out.z) + end +} + +GateActions["entity_setmass"] = { + name = "Set Mass", + inputs = { "Ent" , "Val" }, + inputtypes = { "ENTITY" , "NORMAL" }, + output = function(gate, Ent, Val ) + if !Ent:IsValid() then return end + if !Ent:GetPhysicsObject():IsValid() then return end + if not gamemode.Call("CanTool", WireLib.GetOwner(gate), WireLib.dummytrace(Ent), "weight") then return end + if !Val then Val = Ent:GetPhysicsObject():GetMass() end + Val = math.Clamp(Val, 0.001, 50000) + Ent:GetPhysicsObject():SetMass(Val) + end, + label = function(Out, Ent , Val) + return string.format ("setMass(%s , %s)", Ent, Val) + end +} + +GateActions["entity_equal"] = { + name = "Equal", + inputs = { "A" , "B" }, + inputtypes = { "ENTITY" , "ENTITY" }, + output = function(gate, A, B ) + if A == B then return 1 else return 0 end + end, + label = function(Out, A , B) + return string.format ("(%s = = %s) = %d", A, B, Out) + end +} + +GateActions["entity_inequal"] = { + name = "Inequal", + inputs = { "A" , "B" }, + inputtypes = { "ENTITY" , "ENTITY" }, + output = function(gate, A, B ) + if A ~= B then return 1 else return 0 end + end, + label = function(Out, A , B) + return string.format ("(%s ! = %s) = %d", A, B, Out) + end +} + +GateActions["entity_setcol"] = { + name = "Set Color", + inputs = { "Ent" , "Col" }, + inputtypes = { "ENTITY" , "VECTOR" }, + output = function(gate, Ent, Col ) + if !Ent:IsValid() then return end + if not gamemode.Call("CanTool", WireLib.GetOwner(gate), WireLib.dummytrace(Ent), "color") then return end + if !isvector(Col) then Col = Vector(255,255,255) end + Ent:SetColor(Color(Col.x,Col.y,Col.z,255)) + end, + label = function(Out, Ent , Col) + if !isvector(Col) then Col = Vector(0,0,0) end + return string.format ("setColor(%s ,(%d,%d,%d) )", Ent , Col.x, Col.y, Col.z) + end +} + +GateActions["entity_driver"] = { + name = "Driver", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "ENTITY" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:IsVehicle() then return NULL end + return Ent:GetDriver() + end, + label = function(Out, A) + local Name = "NULL" + if Out:IsValid() then Name = Out:Nick() end + return string.format ("Driver: %s", Name) + end +} + + +GateActions["entity_clr"] = { + name = "Color", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() then return Vector(0,0,0) end + local c = Ent:GetColor() + return Vector(c.r,c.g,c.b) + end, + label = function(Out, Ent) + return string.format ("color(%s) = (%d,%d,%d)", Ent , Out.x, Out.y, Out.z) + end +} + + + +GateActions["entity_name"] = { + name = "Name", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "STRING" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:IsPlayer() then return "" else return Ent:Nick() end + end, + label = function(Out, Ent) + return string.format ("name(%s) = %s", Ent, Out) + end +} + +GateActions["entity_aimpos"] = { + name = "AimPosition", + inputs = { "Ent" }, + inputtypes = { "ENTITY" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, Ent) + if !Ent:IsValid() or !Ent:IsPlayer() then return Vector(0,0,0) else return Ent:GetEyeTraceNoCursor().HitPos end + end, + label = function(Out) + return string.format ("Aim Position = (%f , %f , %f)", Out.x , Out.y , Out.z) + end +} + +GateActions["entity_select"] = { + name = "Select", + inputs = { "Choice", "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "NORMAL", "ENTITY", "ENTITY", "ENTITY", "ENTITY", "ENTITY", "ENTITY", "ENTITY", "ENTITY" }, + outputtypes = { "ENTITY" }, + output = function(gate, Choice, ...) + math.Clamp(Choice,1,8) + return ({...})[Choice] + end, + label = function(Out, Choice) + return string.format ("select(%s) = %s", Choice, Out) + end +} + +-- Bearing and Elevation, copied from E2 + +GateActions["entity_bearing"] = { + name = "Bearing", + inputs = { "Entity", "Position" }, + inputtypes = { "ENTITY", "VECTOR", "NORMAL" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function( gate, Entity, Position ) + if (!Entity:IsValid()) then return 0 end + Position = Entity:WorldToLocal(Position) + return 180 / math.pi * math.atan2( Position.y, Position.x ) + end, + label = function( Out, Entity, Position ) + return Entity .. ":Bearing(" .. Position .. ") = " .. Out + end +} + +GateActions["entity_elevation"] = { + name = "Elevation", + inputs = { "Entity", "Position" }, + inputtypes = { "ENTITY", "VECTOR", "NORMAL" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function( gate, Entity, Position ) + if (!Entity:IsValid()) then return 0 end + Position = Entity:WorldToLocal(Position) + local len = Position:Length() + return 180 / math.pi * math.asin(Position.z / len) + end, + label = function( Out, Entity, Position ) + return Entity .. ":Elevation(" .. Position .. ") = " .. Out + end +} + +GateActions["entity_heading"] = { + name = "Heading", + inputs = { "Entity", "Position" }, + inputtypes = { "ENTITY", "VECTOR", "NORMAL" }, + outputs = { "Bearing", "Elevation", "Heading" }, + outputtypes = { "NORMAL", "NORMAL", "ANGLE" }, + timed = true, + output = function( gate, Entity, Position ) + if (!Entity:IsValid()) then return 0, 0, Angle(0,0,0) end + Position = Entity:WorldToLocal(Position) + + -- Bearing + local bearing = 180 / math.pi * math.atan2( Position.y, Position.x ) + + -- Elevation + local len = Position:Length() + elevation = 180 / math.pi * math.asin( Position.z / len ) + + return bearing, elevation, Angle(bearing,elevation,0) + end, + label = function( Out, Entity, Position ) + return Entity .. ":Heading(" .. Position .. ") = " .. tostring(Out.Heading) + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/highspeed.lua b/garrysmod/addons/feature-wire/lua/wire/gates/highspeed.lua new file mode 100644 index 0000000..d869f25 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/highspeed.lua @@ -0,0 +1,44 @@ +GateActions("Highspeed") + +GateActions["highspeed_write"] = { + name = "Highspeed Write", + inputs = { "Clk", "Memory", "Address", "Data" }, + inputtypes = { "NORMAL", "WIRELINK", "NORMAL", "NORMAL" }, + output = function(gate, Clk, Memory, Address, Data) + if not Memory then return 0 end + if not Memory.WriteCell then return 0 end + if Clk <= 0 then return 0 end + + Address = math.floor(Address) + if Address < 0 then return 0 end + + return Memory:WriteCell(Address, Data) and 1 or 0 + end, + label = function(Out, Clk, Memory, Address, Data) + return string.format("Clock:%s Memory:%s Address:%s Data:%s = %s", Clk, Memory, Address, Data, Out) + end +} + +GateActions["highspeed_read"] = { + name = "Highspeed Read", + inputs = { "Clk", "Memory", "Address" }, + inputtypes = { "NORMAL", "WIRELINK", "NORMAL" }, + output = function(gate, Clk, Memory, Address) + if Clk <= 0 then return gate.Memory or 0 end + + Address = math.floor(Address or -1) + if not Memory or not Memory.ReadCell or Address < 0 then + gate.Memory = 0 + return 0 + end + + gate.Memory = Memory:ReadCell(Address) + return gate.Memory or 0 + end, + label = function(Out, Clk, Memory, Address) + return string.format("Clock:%s Memory:%s Address:%s = %s", Clk, Memory, Address, Out) + end +} + + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/logic.lua b/garrysmod/addons/feature-wire/lua/wire/gates/logic.lua new file mode 100644 index 0000000..5f179d1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/logic.lua @@ -0,0 +1,135 @@ +--[[ + Logic Gates +]] + +GateActions("Logic") + +GateActions["not"] = { + name = "Not (Invert)", + inputs = { "A" }, + output = function(gate, A) + if (A > 0) then return 0 end + return 1 + end, + label = function(Out, A) + return "not "..A.." = "..Out + end +} + +GateActions["and"] = { + name = "And (All)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + for k,v in ipairs({...}) do + if (v) and (v <= 0) then return 0 end + end + return 1 + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." and " end + end + return string.sub(txt, 1, -6).." = "..Out + end +} + +GateActions["or"] = { + name = "Or (Any)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + for k,v in ipairs({...}) do + if (v) and (v > 0) then return 1 end + end + return 0 + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." or " end + end + return string.sub(txt, 1, -5).." = "..Out + end +} + +GateActions["xor"] = { + name = "Exclusive Or (Odd)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + local result = 0 + for k,v in ipairs({...}) do + if (v) and (v > 0) then result = (1-result) end + end + return result + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." xor " end + end + return string.sub(txt, 1, -6).." = "..Out + end +} + +GateActions["nand"] = { + name = "Not And (Not All)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + for k,v in ipairs({...}) do + if (v) and (v <= 0) then return 1 end + end + return 0 + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." nand " end + end + return string.sub(txt, 1, -7).." = "..Out + end +} + +GateActions["nor"] = { + name = "Not Or (None)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + for k,v in ipairs({...}) do + if (v) and (v > 0) then return 0 end + end + return 1 + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." nor " end + end + return string.sub(txt, 1, -6).." = "..Out + end +} + +GateActions["xnor"] = { + name = "Exclusive Not Or (Even)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + local result = 1 + for k,v in ipairs({...}) do + if (v) and (v > 0) then result = (1-result) end + end + return result + end, + label = function(Out, ...) + local txt = "" + for k,v in ipairs({...}) do + if (v) then txt = txt..v.." xnor " end + end + return string.sub(txt, 1, -7).." = "..Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/memory.lua b/garrysmod/addons/feature-wire/lua/wire/gates/memory.lua new file mode 100644 index 0000000..e8abe00 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/memory.lua @@ -0,0 +1,446 @@ +--[[ + Memory Gates +]] + +GateActions("Memory") + +GateActions["latch"] = { + name = "Latch (Edge triggered)", + inputs = { "Data", "Clk" }, + output = function(gate, Data, Clk) + local clk = (Clk > 0) + if (gate.PrevValue ~= clk) then + gate.PrevValue = clk + if (clk) then + gate.LatchStore = Data + end + end + return gate.LatchStore or 0 + end, + reset = function(gate) + gate.LatchStore = 0 + gate.PrevValue = nil + end, + label = function(Out, Data, Clk) + return "Latch Data:"..Data.." Clock:"..Clk.." = "..Out + end +} + +GateActions["dlatch"] = { + name = "D-Latch", + inputs = { "Data", "Clk" }, + output = function(gate, Data, Clk) + if (Clk > 0) then + gate.LatchStore = Data + end + return gate.LatchStore or 0 + end, + reset = function(gate) + gate.LatchStore = 0 + end, + label = function(Out, Data, Clk) + return "D-Latch Data:"..Data.." Clock:"..Clk.." = "..Out + end +} + +GateActions["srlatch"] = { + name = "SR-Latch", + inputs = { "S", "R" }, + output = function(gate, S, R) + if (S > 0) and (R <= 0) then + gate.LatchStore = 1 + elseif (S <= 0) and (R > 0) then + gate.LatchStore = 0 + end + return gate.LatchStore + end, + reset = function(gate) + gate.LatchStore = 0 + end, + label = function(Out, S, R) + return "S:"..S.." R:"..R.." == "..Out + end +} + +GateActions["rslatch"] = { + name = "RS-Latch", + inputs = { "S", "R" }, + output = function(gate, S, R) + if (S > 0) and (R < 1) then + gate.LatchStore = 1 + elseif (R > 0) then + gate.LatchStore = 0 + end + return gate.LatchStore + end, + reset = function(gate) + gate.LatchStore = 0 + end, + label = function(Out, S, R) + return "S:"..S.." R:"..R.." == "..Out + end +} + +GateActions["toggle"] = { + name = "Toggle (Edge triggered)", + inputs = { "Clk", "OnValue", "OffValue" }, + output = function(gate, Clk, OnValue, OffValue) + local clk = (Clk > 0) + if (gate.PrevValue ~= clk) then + gate.PrevValue = clk + if (clk) then + gate.LatchStore = (not gate.LatchStore) + end + end + + if (gate.LatchStore) then return OnValue end + return OffValue + end, + reset = function(gate) + gate.LatchStore = false + gate.PrevValue = nil + end, + label = function(Out, Clk, OnValue, OffValue) + return "Off:"..OffValue.." On:"..OnValue.." Clock:"..Clk.." = "..Out + end +} + +GateActions["wom4"] = { + name = "Write Only Memory(4 store)", + inputs = { "Clk", "AddrWrite", "Data" }, + output = function( gate, Clk, AddrWrite, Data ) + AddrWrite = math.floor(tonumber(AddrWrite)) + if ( Clk > 0 ) then + if ( AddrWrite >= 0 ) and ( AddrWrite < 4 ) then + gate.LatchStore[AddrWrite] = Data + end + end + return 0 + end, + reset = function( gate ) + gate.LatchStore = {} + for i = 0, 3 do + gate.LatchStore[i] = 0 + end + end, + label = function() + return "Write Only Memory - 4 store" + end +} + +GateActions["ram8"] = { + name = "RAM(8 store)", + inputs = { "Clk", "AddrRead", "AddrWrite", "Data", "Reset" }, + output = function(gate, Clk, AddrRead, AddrWrite, Data, Reset ) + if (Reset > 0) then + gate.LatchStore = {} + end + + AddrRead = math.floor(tonumber(AddrRead)) + AddrWrite = math.floor(tonumber(AddrWrite)) + + if (Clk > 0) then + if (AddrWrite >= 0) and (AddrWrite < 8) then + gate.LatchStore[AddrWrite] = Data + end + end + + if (AddrRead < 0) or (AddrRead >= 8) then return 0 end + + return gate.LatchStore[AddrRead] or 0 + end, + reset = function(gate) + gate.LatchStore = {} + end, + label = function(Out, Clk, AddrRead, AddrWrite, Data, Reset) + return "WriteAddr:"..AddrWrite.." Data:"..Data.." Clock:"..Clk.." Reset:"..Reset.. + "\nReadAddr:"..AddrRead.." = "..Out + end, + ReadCell = function(dummy,gate,Address) + if (Address < 0) || (Address >= 8) then + return 0 + else + return gate.LatchStore[Address] or 0 + end + end, + WriteCell = function(dummy,gate,Address,value) + if (Address < 0) || (Address >= 8) then + return false + else + gate.LatchStore[Address] = value + return true + end + end +} + +GateActions["ram64"] = { + name = "RAM(64 store)", + inputs = { "Clk", "AddrRead", "AddrWrite", "Data", "Reset" }, + output = function(gate, Clk, AddrRead, AddrWrite, Data, Reset ) + if (Reset > 0) then + gate.LatchStore = {} + end + + AddrRead = math.floor(tonumber(AddrRead)) + AddrWrite = math.floor(tonumber(AddrWrite)) + if (Clk > 0) then + if (AddrWrite < 64) then + gate.LatchStore[AddrWrite] = Data + end + end + return gate.LatchStore[AddrRead] or 0 + end, + reset = function(gate) + gate.LatchStore = {} + end, + label = function(Out, Clk, AddrRead, AddrWrite, Data, Reset) + return "WriteAddr:"..AddrWrite.." Data:"..Data.." Clock:"..Clk.." Reset:"..Reset.. + "\nReadAddr:"..AddrRead.." = "..Out + end, + ReadCell = function(dummy,gate,Address) + if (Address < 0) || (Address >= 64) then + return 0 + else + return gate.LatchStore[Address] or 0 + end + end, + WriteCell = function(dummy,gate,Address,value) + if (Address < 0) || (Address >= 64) then + return false + else + gate.LatchStore[Address] = value + return true + end + end +} + +GateActions["ram1k"] = { + name = "RAM(1kb)", + inputs = { "Clk", "AddrRead", "AddrWrite", "Data", "Reset" }, + output = function(gate, Clk, AddrRead, AddrWrite, Data, Reset ) + if (Reset > 0) then + gate.LatchStore = {} + end + + AddrRead = math.floor(tonumber(AddrRead)) + AddrWrite = math.floor(tonumber(AddrWrite)) + if (Clk > 0) then + if (AddrWrite < 1024) then + gate.LatchStore[AddrWrite] = Data + end + end + return gate.LatchStore[AddrRead] or 0 + end, + reset = function(gate) + gate.LatchStore = {} + end, + label = function(Out, Clk, AddrRead, AddrWrite, Data, Reset ) + return "WriteAddr:"..AddrWrite.." Data:"..Data.." Clock:"..Clk.." Reset:"..Reset.. + "\nReadAddr:"..AddrRead.." = "..Out + end, + ReadCell = function(dummy,gate,Address) + if (Address < 0) || (Address >= 1024) then + return 0 + else + return gate.LatchStore[Address] or 0 + end + end, + WriteCell = function(dummy,gate,Address,value) + if (Address < 0) || (Address >= 1024) then + return false + else + gate.LatchStore[Address] = value + return true + end + end +} + +GateActions["ram32k"] = { + name = "RAM(32kb)", + inputs = { "Clk", "AddrRead", "AddrWrite", "Data", "Reset" }, + output = function(gate, Clk, AddrRead, AddrWrite, Data, Reset ) + if (Reset > 0) then + gate.LatchStore = {} + end + + AddrRead = math.floor(tonumber(AddrRead)) + AddrWrite = math.floor(tonumber(AddrWrite)) + if (Clk > 0) then + if (AddrWrite < 32768) then + gate.LatchStore[AddrWrite] = Data + end + end + return gate.LatchStore[AddrRead] or 0 + end, + reset = function(gate) + gate.LatchStore = {} + end, + label = function(Out, Clk, AddrRead, AddrWrite, Data, Reset ) + return "WriteAddr:"..AddrWrite.." Data:"..Data.." Clock:"..Clk.." Reset:"..Reset.. + "\nReadAddr:"..AddrRead.." = "..Out + end, + ReadCell = function(dummy,gate,Address) + if (Address < 0) || (Address >= 32768) then + return 0 + else + return gate.LatchStore[Address] or 0 + end + end, + WriteCell = function(dummy,gate,Address,value) + if (Address < 0) || (Address >= 32768) then + return false + else + gate.LatchStore[Address] = value + return true + end + end +} + +GateActions["ram128k"] = { + name = "RAM(128kb)", + inputs = { "Clk", "AddrRead", "AddrWrite", "Data", "Reset" }, + output = function(gate, Clk, AddrRead, AddrWrite, Data, Reset ) + if (Reset > 0) then + gate.LatchStore = {} + end + + AddrRead = math.floor(tonumber(AddrRead)) + AddrWrite = math.floor(tonumber(AddrWrite)) + if (Clk > 0) then + if (AddrWrite < 131072) then + gate.LatchStore[AddrWrite] = Data + end + end + return gate.LatchStore[AddrRead] or 0 + end, + reset = function(gate) + gate.LatchStore = {} + end, + label = function(Out, Clk, AddrRead, AddrWrite, Data) + return "WriteAddr:"..AddrWrite.." Data:"..Data.." Clock:"..Clk.. + "\nReadAddr:"..AddrRead.." = "..Out + end, + ReadCell = function(dummy,gate,Address) + if (Address < 0) || (Address >= 131072) then + return 0 + else + return gate.LatchStore[Address] or 0 + end + end, + WriteCell = function(dummy,gate,Address,value) + if (Address < 0) || (Address >= 131072) then + return false + else + gate.LatchStore[Address] = value + return true + end + end +} + +GateActions["ram64x64"] = { + name = "RAM(64x64 store)", + inputs = { "Clk", "AddrReadX", "AddrReadY", "AddrWriteX", "AddrWriteY", "Data", "Reset" }, + output = function(gate, Clk, AddrReadX, AddrReadY, AddrWriteX, AddrWriteY, Data, Reset ) + if (Reset > 0) then + gate.LatchStore = {} + end + + AddrReadX = math.floor(tonumber(AddrReadX)) + AddrReadY = math.floor(tonumber(AddrReadY)) + AddrWriteX = math.floor(tonumber(AddrWriteX)) + AddrWriteY = math.floor(tonumber(AddrWriteY)) + if (Clk > 0) then + if (AddrWriteX >= 0) and (AddrWriteX < 64) or (AddrWriteY >= 0) and (AddrWriteY < 64) then + gate.LatchStore[AddrWriteX + AddrWriteY*64] = Data + end + end + + if (AddrReadX < 0) or (AddrReadX >= 64) or (AddrReadY < 0) or (AddrReadY >= 64) then + return 0 + end + + return gate.LatchStore[AddrReadX + AddrReadY*64] or 0 + end, + reset = function(gate) + gate.LatchStore = {} + end, + label = function(Out, Clk, AddrReadX, AddrReadY, AddrWriteX, AddrWriteY, Data, Reset) + return "WriteAddr:"..AddrWriteX..", "..AddrWriteY.." Data:"..Data.." Clock:"..Clk.." Reset:"..Reset.. + "\nReadAddr:"..AddrReadX..", "..AddrReadY.." = "..Out + end, + ReadCell = function(dummy,gate,Address) + if (Address < 0) || (Address >= 4096) then + return 0 + else + return gate.LatchStore[Address] or 0 + end + end, + WriteCell = function(dummy,gate,Address,value) + if (Address < 0) || (Address >= 4096) then + return false + else + gate.LatchStore[Address] = value + return true + end + end +} + +GateActions["udcounter"] = { + name = "Up/Down Counter", + inputs = { "Increment", "Decrement", "Clk", "Reset"}, + output = function(gate, Inc, Dec, Clk, Reset) + local lInc = (Inc > 0) + local lDec = (Dec > 0) + local lClk = (Clk > 0) + local lReset = (Reset > 0) + if ((gate.PrevInc ~= lInc || gate.PrevDec ~= lDec || gate.PrevClk ~= lClk) && lClk) then + if (lInc) and (!lDec) and (!lReset) then + gate.countStore = (gate.countStore or 0) + 1 + elseif (!lInc) and (lDec) and (!lReset) then + gate.countStore = (gate.countStore or 0) - 1 + end + gate.PrevInc = lInc + gate.PrevDec = lDec + gate.PrevClk = lClk + end + if (lReset) then + gate.countStore = 0 + end + return gate.countStore + end, + label = function(Out, Inc, Dec, Clk, Reset) + return "Increment:"..Inc.." Decrement:"..Dec.." Clk:"..Clk.." Reset:"..Reset.." = "..Out + end +} + +GateActions["togglewhile"] = { + name = "Toggle While(Edge triggered)", + inputs = { "Clk", "OnValue", "OffValue", "While" }, + output = function(gate, Clk, OnValue, OffValue, While) + local clk = (Clk > 0) + + if (While <= 0) then + clk = false + gate.LatchStore = false + end + + if (gate.PrevValue ~= clk) then + gate.PrevValue = clk + if (clk) then + gate.LatchStore = (not gate.LatchStore) + end + end + + if (gate.LatchStore) then return OnValue end + return OffValue + end, + reset = function(gate) + gate.LatchStore = 0 + gate.PrevValue = nil + end, + label = function(Out, Clk, OnValue, OffValue, While) + return "Off:"..OffValue.." On:"..OnValue.." Clock:"..Clk.." While:"..While.." = "..Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/rangerdata.lua b/garrysmod/addons/feature-wire/lua/wire/gates/rangerdata.lua new file mode 100644 index 0000000..d0559b1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/rangerdata.lua @@ -0,0 +1,118 @@ +--[[ + Rangerdata gates +]] + +GateActions("Ranger") + +GateActions["rd_trace"] = { + name = "Trace", + inputs = { "Startpos", "Endpos" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "RANGER" }, + timed = true, + output = function(gate, Startpos, Endpos) + if !isvector(Startpos) then Startpos = Vector (0, 0, 0) end + if !isvector(Endpos) then Endpos = Vector (0, 0, 0) end + local tracedata = {} + tracedata.start = Startpos + tracedata.endpos = Endpos + return util.TraceLine(tracedata) + end, + label = function(Out, Startpos, Endpos) + return string.format ("trace(%s , %s)", Startpos, Endpos) + end +} + +GateActions["rd_hitpos"] = { + name = "Hit Position", + inputs = { "A" }, + inputtypes = { "RANGER" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, A) + if !A then return Vector(0,0,0) end + if A.StartSolid then return A.StartPos end + return A.HitPos + end, + label = function(Out, A) + return string.format ("hitpos(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +GateActions["rd_hitnorm"] = { + name = "Hit Normal", + inputs = { "A" }, + inputtypes = { "RANGER" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, A) + if !A then return Vector(0,0,0) end + return A.HitNormal + end, + label = function(Out, A) + return string.format ("hitnormal(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +GateActions["rd_entity"] = { + name = "Entity", + inputs = { "A" }, + inputtypes = { "RANGER" }, + outputtypes = { "ENTITY" }, + timed = true, + output = function(gate, A) + if !A then return NULL end + return A.Entity + end, + label = function(Out, A) + return string.format ("hitentity(%s) = %s", A, tostring(Out)) + end +} + +GateActions["rd_hitworld"] = { + name = "Hit World", + inputs = { "A" }, + inputtypes = { "RANGER" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, A) + if !A then return 0 end + return A.HitWorld and 1 or 0 + end, + label = function(Out, A) + return string.format ("hitworld(%s) = %d", A, Out and 1 or 0) + end +} + +GateActions["rd_hit"] = { + name = "Hit", + inputs = { "A" }, + inputtypes = { "RANGER" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, A) + if !A then return 0 end + return A.Hit and 1 or 0 + end, + label = function(Out, A) + return string.format ("hit(%s) = %d", A, Out and 1 or 0) + end +} + +GateActions["rd_distance"] = { + name = "Distance", + inputs = { "A" }, + inputtypes = { "RANGER" }, + outputtypes = { "NORMAL" }, + timed = true, + output = function(gate, A) + if !A then return 0 end + if A.StartSolid then return A.StartPos:Distance(A.HitPos)*(1/(1-A.FractionLeftSolid)-1) end + return A.StartPos:Distance(A.HitPos) + end, + label = function(Out, A) + return string.format ("distance(%s) = %d", A, Out) + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/selection.lua b/garrysmod/addons/feature-wire/lua/wire/gates/selection.lua new file mode 100644 index 0000000..2da90ca --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/selection.lua @@ -0,0 +1,151 @@ +--[[ + Selection Gates +]] + +GateActions("Selection") + +GateActions["min"] = { + name = "Minimum (Smallest)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + return math.min(unpack({...})) + end, + label = function(Out, ...) + local txt = "min(" + for k,v in ipairs({...}) do + if (v) then txt = txt..v..", " end + end + return string.sub(txt, 1, -3)..") = "..Out + end +} + +GateActions["max"] = { + name = "Maximum (Largest)", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + compact_inputs = 2, + output = function(gate, ...) + return math.max(unpack({...})) + end, + label = function(Out, ...) + local txt = "max(" + for k,v in ipairs({...}) do + if (v) then txt = txt..v..", " end + end + return string.sub(txt, 1, -3)..") = "..Out + end +} + +GateActions["minmax"] = { + name = "Value Range", + inputs = { "Min", "Max", "Value" }, + output = function(gate, Min, Max, Value) + local temp = Min + if Min > Max then + Min = Max + Max = temp + end + if Value < Min then return Min end + if Value > Max then return Max end + return Value + end, + label = function(Out, Min, Max, Value) + local temp = Min + if Min > Max then + Min = Max + Max = temp + end + return "Min: "..Min.." Max: "..Max.." Value: "..Value.." = "..Out + end +} + +GateActions["if"] = { + name = "If Then Else", + inputs = { "A", "B", "C" }, + output = function(gate, A, B, C) + if (A) and (A > 0) then return B end + return C + end, + label = function(Out, A, B, C) + return "if "..A.." then "..B.." else "..C.." = "..Out + end +} + +GateActions["select"] = { + name = "Select (Choice)", + inputs = { "Choice", "A", "B", "C", "D", "E", "F", "G", "H" }, + output = function(gate, Choice, ...) + local idx = math.floor(Choice) + if (idx > 0) and (idx <= 8) then + return ({...})[idx] + end + + return 0 + end, + label = function(Out, Choice) + return "Select Choice:"..Choice.." Out:"..Out + end +} + +GateActions["router"] = { + name = "Router", + inputs = { "Path", "Data" }, + outputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + output = function(gate, Path, Data) + local result = { 0, 0, 0, 0, 0, 0, 0, 0 } + + local idx = math.floor(Path) + if (idx > 0) and (idx <= 8) then + result[idx] = Data + end + + return unpack(result) + end, + label = function(Out, Path, Data) + return "Router Path:"..Path.." Data:"..Data + end +} + +local SegmentInfo = { + None = { 0, 0, 0, 0, 0, 0, 0 }, + [0] = { 1, 1, 1, 1, 1, 1, 0 }, + [1] = { 0, 1, 1, 0, 0, 0, 0 }, + [2] = { 1, 1, 0, 1, 1, 0, 1 }, + [3] = { 1, 1, 1, 1, 0, 0, 1 }, + [4] = { 0, 1, 1, 0, 0, 1, 1 }, + [5] = { 1, 0, 1, 1, 0, 1, 1 }, + [6] = { 1, 0, 1, 1, 1, 1, 1 }, + [7] = { 1, 1, 1, 0, 0, 0, 0 }, + [8] = { 1, 1, 1, 1, 1, 1, 1 }, + [9] = { 1, 1, 1, 1, 0, 1, 1 }, +} + +GateActions["7seg"] = { + name = "7 Segment Decoder", + inputs = { "A", "Clear" }, + outputs = { "A", "B", "C", "D", "E", "F", "G" }, + output = function(gate, A, Clear) + if (Clear > 0) then return unpack(SegmentInfo.None) end + + local idx = math.fmod(math.abs(math.floor(A)), 10) + if idx > #SegmentInfo then return unpack(SegmentInfo.None) end + return unpack(SegmentInfo[idx]) -- same as: return SegmentInfo[idx][1], SegmentInfo[idx][2], ... + end, + label = function(Out, A) + return "7-Seg In:" .. A .. " Out:" .. Out.A .. Out.B .. Out.C .. Out.D .. Out.E .. Out.F .. Out.G + end +} + +GateActions["timedec"] = { + name = "Time/Date decoder", + inputs = { "Time", "Date" }, + outputs = { "Hours","Minutes","Seconds","Year","Day" }, + output = function(gate, Time, Date) + return math.floor(Time / 3600),math.floor(Time / 60) % 60,math.floor(Time) % 60,math.floor(Date / 366),math.floor(Date) % 366 + end, + label = function(Out, A) + return "Date decoder" + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/string.lua b/garrysmod/addons/feature-wire/lua/wire/gates/string.lua new file mode 100644 index 0000000..277e45a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/string.lua @@ -0,0 +1,411 @@ +--[[ + String gates ! :P +]] + +local MAX_LEN = 1024*1024 -- max string length of 1MB + +GateActions("String") + +GateActions["string_ceq"] = { + name = "Equal", + inputs = { "A" , "B" }, + inputtypes = { "STRING" , "STRING" }, + output = function(gate, A, B) + if A == B then return 1 else return 0 end + end, + label = function(Out, A, B) + return string.format ("(%s == %s) = %d", A, B, Out) + end +} + +GateActions["string_cineq"] = { + name = "Inequal", + inputs = { "A" , "B" }, + inputtypes = { "STRING" , "STRING" }, + output = function(gate, A, B) + if A ~= B then return 1 else return 0 end + end, + label = function(Out, A, B) + return string.format ("(%s != %s) = %d", A, B, Out) + end +} + +GateActions["string_index"] = { + name = "Index", + inputs = { "A" , "Index" }, + inputtypes = { "STRING" , "NORMAL" }, + outputtypes = { "STRING" }, + output = function(gate, A, B) + if !A then A = "" end + if !B then B = 0 end + return string.sub(A,B,B) + end, + label = function(Out, A, B) + return string.format ("index(%s , %s) = %q", A, B, Out) + end +} + +GateActions["string_length"] = { + name = "Length", + inputs = { "A" }, + inputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = "" end + return #A + end, + label = function(Out, A) + return string.format ("length(%s) = %d", A, Out) + end +} + +GateActions["string_upper"] = { + name = "Uppercase", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = "" end + return string.upper(A) + end, + label = function(Out, A) + return string.format ("upper(%s) = %q", A, Out) + end +} + +GateActions["string_lower"] = { + name = "Lowercase", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = "" end + return string.lower(A) + end, + label = function(Out, A) + return string.format ("lower(%s) = %q", A, Out) + end +} + +GateActions["string_sub"] = { + name = "Substring", + inputs = { "A" , "Start" , "End" }, + inputtypes = { "STRING" , "NORMAL" , "NORMAL" }, + outputtypes = { "STRING" }, + output = function(gate, A, B, C) + if !A then A = "" end + if !B then B = 1 end -- defaults to start of string + if !C then C = -1 end -- defaults to end of string + return string.sub(A,B,C) + end, + label = function(Out, A, B, C) + return string.format ("%s:sub(%s , %s) = %q", A, B, C, Out) + end +} + +GateActions["string_explode"] = { + name = "Explode", + inputs = { "A" , "Separator" }, + inputtypes = { "STRING" , "STRING" }, + outputtypes = { "ARRAY" }, + output = function(gate, A, B) + if !A then A = "" end + if !B then B = "" end + return string.Explode(B,A) + end, + label = function(Out, A, B) + return string.format ("explode(%s , %s)", A, B) + end +} + +GateActions["string_find"] = { + name = "Find", + inputs = { "A", "B", "StartIndex" }, + inputtypes = { "STRING", "STRING" }, + outputtypes = { "NORMAL" }, + outputs = { "Out" }, + output = function(gate, A, B, StartIndex) + local r,_ = string.find(A,B,StartIndex,true) + return r or 0 + end, + label = function(Out, A, B) + if istable(Out) then Out = Out.Out end + return string.format ("find(%s , %s) = %d", A, B, Out) + end +} + +GateActions["string_concat"] = { + name = "Concatenate", + inputs = { "A" , "B" , "C" , "D" , "E" , "F" , "G" , "H" }, + inputtypes = { "STRING" , "STRING" , "STRING" , "STRING" , "STRING" , "STRING" , "STRING" , "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A, B, C, D, E, F, G, H) + if (A and #A or 0) + + (B and #B or 0) + + (C and #C or 0) + + (D and #D or 0) + + (E and #E or 0) + + (F and #F or 0) + + (G and #G or 0) + + (H and #H or 0) > MAX_LEN + then + return false + end + local T = {A,B,C,D,E,F,G,H} + return table.concat(T) + end, + label = function(Out) + return string.format ("concat = %q", Out) + end +} + +GateActions["string_trim"] = { + name = "Trim", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = "" end + return string.Trim(A) + end, + label = function(Out, A) + return string.format ("trim(%s) = %q", A, Out) + end +} + +GateActions["string_replace"] = { + name = "Replace", + inputs = { "String" , "ToBeReplaced" , "Replacer" }, + inputtypes = { "STRING" , "STRING" , "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A, B, C) + if !A then A = "" end + if !B then B = "" end + if !C then C = "" end + return string.gsub(A,B,C) + end, + label = function(Out, A, B, C) + return string.format ("%s:replace(%s , %s) = %q", A, B, C, Out) + end +} + +GateActions["string_reverse"] = { + name = "Reverse", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = "" end + return string.reverse(A) + end, + label = function(Out, A) + return string.format ("reverse(%s) = %q", A, Out) + end +} + +GateActions["string_tonum"] = { + name = "To Number", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + if !A then A = "" end + return tonumber(A) + end, + label = function(Out, A) + return string.format ("tonumber(%s) = %d", A, Out) + end +} + +GateActions["string_tostr"] = { + name = "Number to String", + inputs = { "A" }, + inputtypes = { "NORMAL" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = 0 end + return tostring(A) + end, + label = function(Out, A) + return string.format ("tostring(%s) = %q", A, Out) + end +} + +GateActions["string_tobyte"] = { + name = "To Byte", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + if !A then A = "" end + return string.byte(A) + end, + label = function(Out, A) + return string.format ("tobyte(%s) = %d", A, Out) + end +} + +GateActions["string_tochar"] = { + name = "To Character", + inputs = { "A" }, + inputtypes = { "NORMAL" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !A then A = 0 end + return string.char(A) + end, + label = function(Out, A) + return string.format ("tochar(%s) = %q", A, Out) + end +} + +GateActions["string_repeat"] = { + name = "Repeat", + inputs = { "A" , "Num"}, + inputtypes = { "STRING" , "NORMAL" }, + outputtypes = { "STRING" }, + output = function(gate, A, B) + if !A then A = "" end + if !B or B<1 then B = 1 end + + if B * #A > MAX_LEN then return false end + + return string.rep(A,B) + end, + label = function(Out, A) + return string.format ("repeat(%s) = %q", A, Out) + end +} + +GateActions["string_ident"] = { + name = "Identity", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, A ) + return A + end, + label = function(Out, A) + return string.format ("%s = %s", A, Out) + end +} + +GateActions["string_select"] = { + name = "Select", + inputs = { "Choice", "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "NORMAL", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING" }, + outputtypes = { "STRING" }, + output = function(gate, Choice, ...) + return ({...})[math.Clamp(Choice,1,8)] + end, + label = function(Out, Choice) + return string.format ("select(%s) = %s", Choice, Out) + end +} + +GateActions["string_to_memory"] = { + name = "String => Memory", + inputs = { "A" }, + inputtypes = { "STRING" }, + outputs = { "Memory" }, + reset = function(gate) + gate.stringQueued = false + gate.stringChanged = false + gate.currentString = "" + end, + + output = function(gate, A) + if (A ~= gate.currentString) then + if (not gate.stringChanged) then + gate.stringChanged = true + gate.currentString = A + gate.stringQueued = false + else + gate.stringQueued = true + end + end + return gate.Outputs["Memory"].Value --This will prevent Wire_TriggerOutput from changing anything + end, + + ReadCell = function(self, gate, Address) + if (Address == 0) then --Clk + if (gate.stringChanged) then return 1 else return 0 end + elseif (Address == 1) then --String length + return #(gate.currentString) + else --Return string bytes + local index = Address - 1 + if (index > #(gate.currentString)) then -- Check whether requested address is outside the string + return 0 + else + return string.byte(gate.currentString, index) + end + end + end, + + WriteCell = function(self, gate, Address, value) + if (Address == 0) and (value == 0) then --String got accepted + gate.stringChanged = false + if gate.stringQueued then --Get queued string + gate.stringQueued = false + gate.currentString = gate.Inputs["A"].Value + gate.stringChanged = true + end + return true + else + return false + end + end +} + + +GateActions["string_from_memory"] = { + name = "Memory => String", + inputs = {}, + outputs = { "Out", "Memory" }, + outputtypes = { "STRING", "NORMAL" }, + reset = function(gate) --initialize the gate + gate.memory = {} + gate.stringLength = 0 + gate.currentString = "" + gate.ready = true + end, + + output = function(gate) + return gate.currentString, gate.Outputs["Memory"].Value + end, + + ReadCell = function(self, gate, address) + if (address == 0) then + return 0 + elseif (address == 1) then + return gate.stringLength + else + return gate.memory[address-1] or 0 -- "or 0" to prevent it from returning nil if index is outside the array + end + end, + + WriteCell = function(self, gate, address, value) + if (value >= 0) then + if (address == 0) and (value == 1) then -- Clk has been set + local maxIndex = gate.stringLength + for i=1,gate.stringLength,1 do + if not gate.memory[i] then + maxIndex = i-1 + break + end + end + gate.currentString = string.char(unpack(gate.memory, 1, maxIndex)) + gate:CalcOutput() + return true + elseif (address == 1) then -- Set string length + gate.stringLength = math.floor(value) + return true + elseif (address > 1) then -- Set memory cell + gate.memory[address-1] = math.floor(value) + return true + end + end + return false; -- if (value < 0) or ((address == 0) and (value != 1)) + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/time.lua b/garrysmod/addons/feature-wire/lua/wire/gates/time.lua new file mode 100644 index 0000000..3b5f549 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/time.lua @@ -0,0 +1,309 @@ +--[[ + Time Gates +]] + +GateActions("Time") + +GateActions["accumulator"] = { + name = "Accumulator", + inputs = { "A", "Hold", "Reset" }, + timed = true, + output = function(gate, A, Hold, Reset) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + if (Reset > 0) then + gate.Accum = 0 + elseif (Hold <= 0) then + gate.Accum = gate.Accum+A*DeltaTime + end + return gate.Accum or 0 + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, A, Hold, Reset) + return "A:"..A.." Hold:"..Hold.." Reset:"..Reset.." = "..Out + end +} + +GateActions["smoother"] = { + name = "Smoother", + inputs = { "A", "Rate" }, + timed = true, + output = function(gate, A, Rate) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + local Delta = A-gate.Accum + if (Delta > 0) then + gate.Accum = gate.Accum+math.min(Delta, Rate*DeltaTime) + elseif (Delta < 0) then + gate.Accum = gate.Accum+math.max(Delta, -Rate*DeltaTime) + end + return gate.Accum or 0 + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, A, Rate) + return "A:"..A.." Rate:"..Rate.." = "..Out + end +} + +GateActions["timer"] = { + name = "Timer", + inputs = { "Run", "Reset" }, + timed = true, + output = function(gate, Run, Reset) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + if ( Reset > 0 ) then + gate.Accum = 0 + elseif ( Run > 0 ) then + gate.Accum = gate.Accum+DeltaTime + end + return gate.Accum or 0 + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, Run, Reset) + return "Run:"..Run.." Reset:"..Reset.." = "..Out + end +} + +GateActions["ostime"] = { + name = "OS Time", + inputs = { }, + timed = true, + output = function(gate) + return os.date("%H")*3600+os.date("%M")*60+os.date("%S") + end, + label = function(Out) + return "OS Time = "..Out + end +} + +GateActions["osdate"] = { + name = "OS Date", + inputs = { }, + timed = true, + output = function(gate) + return os.date("%Y")*366+os.date("%j") + end, + label = function(Out) + return "OS Date = "..Out + end +} + +GateActions["pulser"] = { + name = "Pulser", + inputs = { "Run", "Reset", "TickTime" }, + timed = true, + output = function(gate, Run, Reset, TickTime) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + if ( Reset > 0 ) then + gate.Accum = 0 + elseif ( Run > 0 ) then + gate.Accum = gate.Accum+DeltaTime + if (gate.Accum >= TickTime) then + gate.Accum = gate.Accum - TickTime + return 1 + end + end + return 0 + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, Run, Reset, TickTime) + return "Run:"..Run.." Reset:"..Reset.."TickTime:"..TickTime.." = "..Out + end +} + +GateActions["squarepulse"] = { + name = "Square Pulse", + inputs = { "Run", "Reset", "PulseTime", "GapTime", "Min", "Max" }, + timed = true, + output = function(gate, Run, Reset, PulseTime, GapTime, Min, Max) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + + if (Reset > 0) then + gate.Accum = 0 + elseif (Run > 0) then + gate.Accum = gate.Accum+DeltaTime + if (gate.Accum <= PulseTime) then + return Max + end + if (gate.Accum >= PulseTime + GapTime) then + gate.Accum = 0 + end + end + return Min + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, Run, Reset, PulseTime, GapTime) + return "Run:"..Run.." Reset:"..Reset.." PulseTime:"..PulseTime.." GapTime:"..GapTime.." = "..Out + end +} + +GateActions["sawpulse"] = { + name = "Saw Pulse", + inputs = { "Run", "Reset", "SlopeRaiseTime", "PulseTime", "SlopeDescendTime", "GapTime", "Min", "Max" }, + timed = true, + output = function(gate, Run, Reset, SlopeRaiseTime, PulseTime, SlopeDescendTime, GapTime, Min, Max) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + + if Reset > 0 then + gate.Accum = 0 + return Min + end + if Run <= 0 then + return Min + end + + SlopeRaiseTime = math.max(SlopeRaiseTime, 0) + PulseTime = math.max(PulseTime, 0) + SlopeDescendTime = math.max(SlopeDescendTime, 0) + GapTime = math.max(GapTime, 0) + + gate.Accum = (gate.Accum + DeltaTime) % (SlopeRaiseTime + PulseTime + SlopeDescendTime + GapTime) + if gate.Accum < SlopeRaiseTime then + return Min + (Max - Min) * gate.Accum / SlopeRaiseTime + elseif gate.Accum < SlopeRaiseTime + PulseTime then + return Max + elseif gate.Accum < SlopeRaiseTime + PulseTime + SlopeDescendTime then + return Max + (Min - Max) * (gate.Accum - SlopeRaiseTime - PulseTime) / SlopeDescendTime + else + return Min + end + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, Run, Reset, PulseTime, GapTime) + return "Run:"..Run.." Reset:"..Reset.." PulseTime:"..PulseTime.." GapTime:"..GapTime.." = "..Out + end +} + + +GateActions["derive"] = { + name = "Derivative", + inputs = {"A"}, + timed = false, + output = function(gate, A) + local t = CurTime() + local dT = t - gate.LastT + gate.LastT = t + local dA = A - gate.LastA + gate.LastA = A + if dT ~= 0 then + return dA/dT + else + return 0; + end + end, + reset = function(gate) + gate.LastT = CurTime() + gate.LastA = 0 + end, + label = function(Out, A) + return "d/dt["..A.."] = "..Out + end +} + +GateActions["delay"] = { + name = "Delay", + inputs = { "Clk", "Delay", "Hold", "Reset" }, + outputs = { "Out", "TimeElapsed", "Remaining" }, + timed = true, + output = function(gate, Clk, Delay, Hold, Reset) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + local out = 0 + + if ( Reset > 0 ) then + gate.Stage = 0 + gate.Accum = 0 + end + + if ( gate.Stage == 1 ) then + if ( gate.Accum >= Delay ) then + gate.Stage = 2 + gate.Accum = 0 + out = 1 + else + gate.Accum = gate.Accum+DeltaTime + end + elseif ( gate.Stage == 2 ) then + if ( gate.Accum >= Hold ) then + gate.Stage = 0 + gate.Accum = 0 + out = 0 + else + out = 1 + gate.Accum = gate.Accum+DeltaTime + end + else + if ( Clk > 0 ) then + gate.Stage = 1 + gate.Accum = 0 + end + end + + return out, gate.Accum, Delay-gate.Accum + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + gate.Stage = 0 + end, + label = function(Out, Clk, Delay, Hold, Reset) + return "Clk: "..Clk.." Delay: "..Delay.. + "\nHold: "..Hold.." Reset: "..Reset.. + "\nTime Elapsed: "..Out.TimeElapsed.." = "..Out.Out + end +} + + +GateActions["monostable"] = { + name = "Monostable Timer", + inputs = { "Run", "Time", "Reset" }, + timed = true, + output = function(gate, Run, Time, Reset) + local DeltaTime = CurTime()-(gate.PrevTime or CurTime()) + gate.PrevTime = (gate.PrevTime or CurTime())+DeltaTime + if ( Reset > 0 ) then + gate.Accum = 0 + elseif gate.Accum > 0 or Run > 0 then + gate.Accum = gate.Accum+DeltaTime + if(gate.Accum > Time) then + gate.Accum = 0 + end + end + if(gate.Accum > 0)then + return 1 + else + return 0 + end + end, + reset = function(gate) + gate.PrevTime = CurTime() + gate.Accum = 0 + end, + label = function(Out, Run, Time, Reset) + return "Run:"..Run.." Time:"..Time.." Reset:"..Reset.." = "..Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/trig.lua b/garrysmod/addons/feature-wire/lua/wire/gates/trig.lua new file mode 100644 index 0000000..d6373e9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/trig.lua @@ -0,0 +1,207 @@ +--[[ + Trig Gates +]] + +GateActions("Trig") + +GateActions["quadratic"] = { + name = "Quadratic Formula", + inputs = { "A", "B", "C" }, + outputs = { "Pos", "Neg" }, + output = function(gate, A, B, C) + local temp = math.sqrt(B^2 - 4*A*C) + return (-B + temp) / (2*A), (-B - temp) / (2*A) + end, + label = function(Out,A,B,C) + return ("AX^2 + BX + C\n(-%s +/- sqrt(%s^2 - 4*%s*%s)) / (2*%s) = %s,%s"):format( B, B, A, C, A, Out.Pos, Out.Neg ) + end +} + +GateActions["sin"] = { + name = "Sin(Rad)", + inputs = { "A" }, + output = function(gate, A) + return math.sin(A) + end, + label = function(Out, A) + return "sin("..A.."rad) = "..Out + end +} + +GateActions["cos"] = { + name = "Cos(Rad)", + inputs = { "A" }, + output = function(gate, A) + return math.cos(A) + end, + label = function(Out, A) + return "cos("..A.."rad) = "..Out + end +} + +GateActions["tan"] = { + name = "Tan(Rad)", + inputs = { "A" }, + output = function(gate, A) + return math.tan(A) + end, + label = function(Out, A) + return "tan("..A.."rad) = "..Out + end +} + +GateActions["asin"] = { + name = "Asin(Rad)", + inputs = { "A" }, + output = function(gate, A) + return math.asin(A) + end, + label = function(Out, A) + return "asin("..A..") = "..Out.."rad" + end +} + +GateActions["acos"] = { + name = "Acos(Rad)", + inputs = { "A" }, + output = function(gate, A) + return math.acos(A) + end, + label = function(Out, A) + return "acos("..A..") = "..Out.."rad" + end +} + +GateActions["atan"] = { + name = "Atan(Rad)", + inputs = { "A" }, + output = function(gate, A) + return math.atan(A) + end, + label = function(Out, A) + return "atan("..A..") = "..Out.."rad" + end +} + +GateActions["sin_d"] = { + name = "Sin(Deg)", + inputs = { "A" }, + output = function(gate, A) + return math.sin(math.rad(A)) + end, + label = function(Out, A) + return "sin("..A.."deg) = "..Out + end +} + +GateActions["cos_d"] = { + name = "Cos(Deg)", + inputs = { "A" }, + output = function(gate, A) + return math.cos(math.rad(A)) + end, + label = function(Out, A) + return "cos("..A.."deg) = "..Out + end +} + +GateActions["tan_d"] = { + name = "Tan(Deg)", + inputs = { "A" }, + output = function(gate, A) + return math.tan(math.rad(A)) + end, + label = function(Out, A) + return "tan("..A.."deg) = "..Out + end +} + +GateActions["asin_d"] = { + name = "Asin(Deg)", + inputs = { "A" }, + output = function(gate, A) + return math.deg(math.asin(A)) + end, + label = function(Out, A) + return "asin("..A..") = "..Out.."deg" + end +} + +GateActions["acos_d"] = { + name = "Acos(Deg)", + inputs = { "A" }, + output = function(gate, A) + return math.deg(math.acos(A)) + end, + label = function(Out, A) + return "acos("..A..") = "..Out.."deg" + end +} + +GateActions["atan_d"] = { + name = "Atan(Deg)", + inputs = { "A" }, + output = function(gate, A) + return math.deg(math.atan(A)) + end, + label = function(Out, A) + return "atan("..A..") = "..Out.."deg" + end +} + +GateActions["rad2deg"] = { + name = "Radians to Degrees", + inputs = { "A" }, + output = function(gate, A) + return math.deg(A) + end, + label = function(Out, A) + return A.."rad = "..Out.."deg" + end +} + +GateActions["deg2rad"] = { + name = "Degrees to Radians", + inputs = { "A" }, + output = function(gate, A) + return math.rad(A) + end, + label = function(Out, A) + return A.."deg = "..Out.."rad" + end +} + +GateActions["angdiff"] = { + name = "Difference(rad)", + inputs = { "A", "B" }, + output = function(gate, A, B) + return math.rad(math.AngleDifference(math.deg(A), math.deg(B))) + end, + label = function(Out, A, B) + return A .. "deg - " .. B .. "deg = " .. Out .. "deg" + end +} + +GateActions["angdiff_d"] = { + name = "Difference(deg)", + inputs = { "A", "B" }, + output = function(gate, A, B) + return math.AngleDifference(A, B) + end, + label = function(Out, A, B) + return A .. "deg - " .. B .. "deg = " .. Out .. "deg" + end +} + +GateActions["atan2"] = { + name = "Atan2", + inputs = { "A", "B" }, + output = function( gate, A, B ) + return math.atan2( A, B ) + end, + label = function( Out, A, B ) + return "atan2(" .. A .. "," .. B .. ") = " .. Out + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gates/vector.lua b/garrysmod/addons/feature-wire/lua/wire/gates/vector.lua new file mode 100644 index 0000000..effeeee --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gates/vector.lua @@ -0,0 +1,703 @@ +--[[ + Vector gates +]] + +GateActions("Vector") + +-- Add +GateActions["vector_add"] = { + name = "Addition", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR" }, + compact_inputs = 2, + outputtypes = { "VECTOR" }, + output = function(gate, ...) + local sum = Vector (0, 0, 0) + for _, v in pairs ({...}) do + if (v and isvector (v)) then + sum = sum + v + end + end + return sum + end, + label = function(Out, ...) + local tip = "" + for _, v in ipairs ({...}) do + if (v) then tip = tip .. " + " .. v end + end + return string.format ("%s = (%d,%d,%d)", string.sub (tip, 3), + Out.x, Out.y, Out.z) + end +} + +-- Subtract +GateActions["vector_sub"] = { + name = "Subtraction", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + return (A - B) + end, + label = function(Out, A, B) + return string.format ("%s - %s = (%d,%d,%d)", A, B, Out.x, Out.y, Out.z) + end +} + +-- Negate +GateActions["vector_neg"] = { + name = "Negate", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return Vector (-A.x, -A.y, -A.z) + end, + label = function(Out, A) + return string.format ("-%s = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +-- Multiply/Divide by constant +GateActions["vector_mul"] = { + name = "Multiplication", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A, B) + if !A then A = Vector(0, 0, 0) end + if !B then B = Vector(0, 0, 0) end + return Vector( A.x * B.x, A.y * B.y, A.z * B.z ) + end, + label = function(Out, A, B) + return string.format ("%s * %s = (%d,%d,%d)", A, B, Out.x, Out.y, Out.z) + end +} + +GateActions["vector_divide"] = { + name = "Division", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "NORMAL" }, + outputtypes = { "VECTOR" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if (B) then + return (A / B) + end + return Vector (0, 0, 0) + end, + label = function(Out, A, B) + return string.format ("%s / %s = (%d,%d,%d)", A, B, Out.x, Out.y, Out.z) + end +} + +-- Dot/Cross Product +GateActions["vector_dot"] = { + name = "Dot Product", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + return A:Dot (B) + end, + label = function(Out, A, B) + return string.format ("dot(%s, %s) = %d", A, B, Out) + end +} + +GateActions["vector_cross"] = { + name = "Cross Product", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + return A:Cross (B) + end, + label = function(Out, A, B) + return string.format ("cross(%s, %s) = (%d,%d,%d)", A, B, Out.x, Out.y, Out.z) + end +} + +-- Yaw/Pitch +GateActions["vector_ang"] = { + name = "Angles (Degree)", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputs = { "Yaw", "Pitch" }, + outputtypes = { "NORMAL", "NORMAL" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + local ang = A:Angle () + return ang.y, ang.p + end, + label = function(Out, A) + return string.format ("ang(%s) = %d, %d", A, Out.Yaw, Out.Pitch) + end +} + +-- Yaw/Pitch (Radian) +GateActions["vector_angrad"] = { + name = "Angles (Radian)", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputs = { "Yaw", "Pitch" }, + outputtypes = { "NORMAL", "NORMAL" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + local ang = A:Angle () + return (ang.y * math.pi / 180), (ang.p * math.pi / 180) + end, + label = function(Out, A) + return string.format ("angr(%s) = %d, %d", A, Out.Yaw, Out.Pitch) + end +} + +-- Magnitude +GateActions["vector_mag"] = { + name = "Magnitude", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return A:Length () + end, + label = function(Out, A) + return string.format ("|%s| = %d", A, Out) + end +} + +-- Conversion To/From +GateActions["vector_convto"] = { + name = "Compose", + inputs = { "X", "Y", "Z" }, + inputtypes = { "NORMAL", "NORMAL", "NORMAL" }, + outputtypes = { "VECTOR" }, + output = function(gate, X, Y, Z) + return Vector (X, Y, Z) + end, + label = function(Out, X, Y, Z) + return string.format ("vector(%s,%s,%s) = (%d,%d,%d)", X, Y, Z, Out.x, Out.y, Out.z) + end +} + +GateActions["vector_convfrom"] = { + name = "Decompose", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputs = { "X", "Y", "Z" }, + outputtypes = { "NORMAL", "NORMAL", "NORMAL" }, + output = function(gate, A) + if (A and isvector (A)) then + return A.x, A.y, A.z + end + return 0, 0, 0 + end, + label = function(Out, A) + return string.format ("%s -> X:%d Y:%d Z:%d", A, Out.X, Out.Y, Out.Z) + end +} + +-- Normalise +GateActions["vector_norm"] = { + name = "Normalise", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return A:GetNormal() + end, + label = function(Out, A) + return string.format( "norm(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z ) + --return "norm(" .. A .. ") = [" .. math.Round(Out.x,3) .. "," .. math.Round(Out.y,3) .. "," .. math.Round(Out.z,3) .. "]" + end +} + +-- Identity +GateActions["vector_ident"] = { + name = "Identity", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return A + end, + label = function(Out, A) + return string.format ("%s = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +-- Random (really needed?) +GateActions["vector_rand"] = { + name = "Random", + inputs = { }, + inputtypes = { }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate) + local vec = Vector (math.random (), math.random (), math.random ()) + vec:Normalize() + return vec + end, + label = function(Out) + return "Random Vector" + end +} + +-- Component Derivative +GateActions["vector_derive"] = { + name = "Delta", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, A) + local t = CurTime () + if !isvector (A) then A = Vector (0, 0, 0) end + local dT, dA = t - gate.LastT, A - gate.LastA + gate.LastT, gate.LastA = t, A + if (dT) then + return Vector (dA.x/dT, dA.y/dT, dA.z/dT) + else + return Vector (0, 0, 0) + end + end, + reset = function(gate) + gate.LastT, gate.LastA = CurTime (), Vector (0, 0, 0) + end, + label = function(Out, A) + return string.format ("diff(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +-- Component Integral +GateActions["vector_cint"] = { + name = "Component Integral", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + timed = true, + output = function(gate, A) + local t = CurTime () + if !isvector (A) then A = Vector (0, 0, 0) end + local dT = t - (gate.LastT or t) + gate.LastT, gate.Integral = t, (gate.Integral or Vector (0, 0, 0)) + A * dT + -- Lifted (kinda) from wiregates.lua to prevent massive values + local TempInt = gate.Integral:Length () + if (TempInt > 100000) then + gate.Integral = gate.Integral:GetNormalized() * 100000 + end + if (TempInt < -100000) then + gate.Integral = gate.Integral:GetNormalized() * -100000 + end + return gate.Integral + end, + reset = function(gate) + gate.Integral, gate.LastT = Vector (0, 0, 0), CurTime() + end, + label = function(Out, A) + return string.format ("int(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + +-- Multiplexer +GateActions["vector_mux"] = { + name = "Multiplexer", + inputs = { "Sel", "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "NORMAL", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR" }, + compact_inputs = 3, + outputtypes = { "VECTOR" }, + output = function(gate, Sel, ...) + if isnumber(Sel) then -- If Sel is unwired, because of compact_inputs 3, it will become the first vector input, so just return Vector(0,0,0) + Sel = math.floor(Sel) + if Sel > 0 and Sel <= 8 then + return ({...})[Sel] + end + end + return Vector (0, 0, 0) + end, + label = function(Out, Sel, ...) + return string.format ("Select: %s Out: (%d,%d,%d)", + Sel, Out.x, Out.y, Out.z) + end +} + +-- Demultiplexer +GateActions["vector_dmx"] = { + name = "Demultiplexer", + inputs = { "Sel", "In" }, + inputtypes = { "NORMAL", "VECTOR" }, + outputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + outputtypes = { "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR" }, + output = function(gate, Sel, In) + local Out = { Vector (0, 0, 0), Vector (0, 0, 0), Vector (0, 0, 0), Vector (0, 0, 0), + Vector (0, 0, 0), Vector (0, 0, 0), Vector (0, 0, 0), Vector (0, 0, 0) } + Sel = math.floor (Sel) + if (Sel > 0 && Sel <= 8) then + Out[Sel] = In + end + return unpack (Out) + end, + label = function(Out, Sel, In) + if !isvector (In) then In = Vector (0, 0, 0) end + if !Sel then Sel = 0 end + return string.format ("Select: %s, In: (%d,%d,%d)", + Sel, In.x, In.y, In.z) + end +} + +-- Latch +GateActions["vector_latch"] = { + name = "Latch", + inputs = { "In", "Clk" }, + inputtypes = { "VECTOR", "NORMAL" }, + outputtypes = { "VECTOR" }, + output = function(gate, In, Clk) + Clk = (Clk > 0) + if (gate.PrevClk != Clk) then + gate.PrevClk = Clk + if (Clk) then + if !isvector (In) then In = Vector (0, 0, 0) end + gate.LatchStore = In + end + end + return gate.LatchStore or Vector (0, 0, 0) + end, + reset = function(gate) + gate.LatchStore = Vector (0, 0, 0) + gate.PrevValue = 0 + end, + label = function(Out, In, Clk) + return string.format ("Latch Data: %s Clock: %s Out: (%d,%d,%d)", + In, Clk, Out.x, Out.y, Out.z) + end +} + +-- D-latch +GateActions["vector_dlatch"] = { + name = "D-Latch", + inputs = { "In", "Clk" }, + inputtypes = { "VECTOR", "NORMAL" }, + outputtypes = { "VECTOR" }, + output = function(gate, In, Clk) + if (Clk > 0) then + if !isvector (In) then In = Vector (0, 0, 0) end + gate.LatchStore = In + end + return gate.LatchStore or Vector (0, 0, 0) + end, + reset = function(gate) + gate.LatchStore = Vector (0, 0, 0) + end, + label = function(Out, In, Clk) + return string.format ("Latch Data: %s Clock: %s Out: (%d,%d,%d)", + In, Clk, Out.x, Out.y, Out.z) + end +} + +-- Equal +GateActions["vector_compeq"] = { + name = "Equal", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if (A == B) then return 1 end + return 0 + end, + label = function(Out, A, B) + return string.format ("(%s == %s) = %d", A, B, Out) + end +} + +-- Not Equal +GateActions["vector_compineq"] = { + name = "Not Equal", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if (A == B) then return 0 end + return 1 + end, + label = function(Out, A, B) + return string.format ("(%s != %s) = %d", A, B, Out) + end +} + +-- Less-than +GateActions["vector_complt"] = { + name = "Less Than", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + if (A:Length () < B:Length ()) then return 1 end + end, + label = function(Out, A, B) + return string.format ("(|%s| < |%s|) = %d", A, B, Out) + end +} + +-- Less-than or Equal-to +GateActions["vector_complteq"] = { + name = "Less Than or Equal To", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + if (A:Length () <= B:Length ()) then return 1 end + return 0 + end, + label = function(Out, A, B) + return string.format ("(|%s| <= |%s|) = %d", A, B, Out) + end +} + +-- Greater-than +GateActions["vector_compgt"] = { + name = "Greater Than", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + if (A:Length () > B:Length ()) then return 1 end + return 0 + end, + label = function(Out, A, B) + return string.format ("(|%s| > |%s|) = %d", A, B, Out) + end +} + +-- Greater-than or Equal-to +GateActions["vector_compgteq"] = { + name = "Greater Than or Equal To", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "VECTOR" }, + output = function(gate, A, B) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + if (A:Length () >= B:Length ()) then return 1 end + return 0 + end, + label = function(Out, A, B) + return string.format ("(|%s| >= |%s|) = %d", A, B, Out) + end +} + +-- Returns a positive vector. +GateActions["vector_positive"] = { + name = "Positive", + inputs = { "A" }, + inputtypes = { "VECTOR"}, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return Vector(math.abs(A.x),math.abs(A.y),math.abs(A.z)) + end, + label = function(Out, A) + return string.format ("abs(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + + +-- Returns a rounded vector. +GateActions["vector_round"] = { + name = "Round", + inputs = { "A" }, + inputtypes = { "VECTOR"}, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return Vector(math.Round(A.x),math.Round(A.y),math.Round(A.z)) + end, + label = function(Out, A) + return string.format ("round(%s) = (%d,%d,%d)", A, Out.x, Out.y, Out.z) + end +} + + +-- Returns the largest vector. +GateActions["vector_max"] = { + name = "Largest", + inputs = { "A" , "B" }, + inputtypes = { "VECTOR" , "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + if A:Length() > B:Length() then return A else return B end + end, + label = function(Out, A , B) + return string.format ("max(%s , %s) = (%d,%d,%d)", A , B, Out.x, Out.y, Out.z) + end +} + +-- Returns the smallest vector. +GateActions["vector_min"] = { + name = "Smallest", + inputs = { "A" , "B" }, + inputtypes = { "VECTOR" , "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + if !isvector (B) then B = Vector (0, 0, 0) end + if A:Length() < B:Length() then return A else return B end + end, + label = function(Out, A , B) + return string.format ("min(%s , %s) = (%d,%d,%d)", A , B, Out.x, Out.y, Out.z) + end +} + +-- Shifts the components left. +GateActions["vector_shiftl"] = { + name = "Shift Components Left", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return Vector(A.y,A.z,A.x) + end, + label = function(Out, A ) + return string.format ("shiftL(%s) = (%d,%d,%d)", A , Out.x, Out.y, Out.z) + end +} + +-- Shifts the components right. +GateActions["vector_shiftr"] = { + name = "Shift Components Right", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + return Vector(A.z,A.x,A.y) + end, + label = function(Out, A ) + return string.format ("shiftR(%s) = (%d,%d,%d)", A , Out.x, Out.y, Out.z) + end +} + + +-- Returns 1 if a vector is on world. +GateActions["vector_isinworld"] = { + name = "Is In World", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + output = function(gate, A) + if !isvector (A) then A = Vector (0, 0, 0) end + if util.IsInWorld(A) then return 1 else return 0 end + end, + label = function(Out, A ) + return string.format ("isInWorld(%s) = %d", A , Out) + end +} + +GateActions["vector_tostr"] = { + name = "To String", + inputs = { "A" }, + inputtypes = { "VECTOR" }, + outputtypes = { "STRING" }, + output = function(gate, A) + if !isvector(A) then A = Vector (0, 0, 0) end + return "["..tostring(A.x)..","..tostring(A.y)..","..tostring(A.z).."]" + end, + label = function(Out, A ) + return string.format ("toString(%s) = \""..Out.."\"", A) + end +} + +GateActions["vector_select"] = { + name = "Select", + inputs = { "Choice", "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "NORMAL", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR", "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function(gate, Choice, ...) + Choice = math.Clamp(Choice,1,8) + return ({...})[Choice] + end, + label = function(Out, Choice) + return string.format ("select(%s) = %s", Choice, Out) + end +} + +GateActions["vector_rotate"] = { + name = "Rotate", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "ANGLE" }, + outputtypes = { "VECTOR" }, + output = function(gate, A, B) + if !A then A = Vector(0, 0, 0) end + if !B then B = Angle(0, 0, 0) end + A = Vector(A[1],A[2],A[3]) + A:Rotate(B) + return A + end, + label = function(Out, A, B) + return string.format ("rotate(%s, %s) = "..tostring(Out), A, B ) + end +} + +GateActions["vector_mulcomp"] = { + name = "Multiplication (component)", + inputs = { "A", "B" }, + inputtypes = { "VECTOR", "NORMAL" }, + outputtypes = { "VECTOR" }, + output = function(gate, A, B) + if !A then A = Vector(0, 0, 0) end + if !B then B = 0 end + return Vector( A.x * B, A.y * B, A.z * B ) + end, + label = function(Out, A, B) + return string.format ("%s * %s = (%d,%d,%d)", A, B, Out.x, Out.y, Out.z ) + end +} + +GateActions["vector_clampn"] = { + name = "Clamp (numbers)", + inputs = { "A", "Min", "Max" }, + inputtypes = { "VECTOR", "NORMAL", "NORMAL" }, + outputtypes = { "VECTOR" }, + output = function( gate, A, Min, Max ) + if (Min > Max) then Min, Max = Max, Min end + return Vector( math.Clamp(A.x,Min,Max), math.Clamp(A.y,Min,Max), math.Clamp(A.z,Min,Max) ) + end, + label = function( Out, A, Min, Max ) + return "Clamp(" .. A .. "," .. Min .. "," .. Max .. ") = " .. tostring(Out) + end +} + +GateActions["vector_clampv"] = { + name = "Clamp (vectors)", + inputs = { "A", "Min", "Max" }, + inputtypes = { "VECTOR", "VECTOR", "VECTOR" }, + outputtypes = { "VECTOR" }, + output = function( gate, A, Min, Max ) + for i=1,3 do + if (Min[i] > Max[i]) then + Min[i], Max[i] = Max[i], Min[i] + end + end + return Vector( math.Clamp(A.x,Min.x,Max.x), math.Clamp(A.y,Min.y,Max.y), math.Clamp(A.z,Min.z,Max.z) ) + end, + label = function( Out, A, Min, Max ) + return "Clamp(" .. A .. "," .. Min .. "," .. Max .. ") = " .. tostring(Out) + end +} + +GateActions() diff --git a/garrysmod/addons/feature-wire/lua/wire/gpulib.lua b/garrysmod/addons/feature-wire/lua/wire/gpulib.lua new file mode 100644 index 0000000..e91bad5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/gpulib.lua @@ -0,0 +1,868 @@ +-------------------------------------------------------------------------------- +-- WireGPU class +-------------------------------------------------------------------------------- +-- Usage: +-- Initialize: +-- self.GPU = WireGPU(self.Entity) +-- +-- OnRemove: +-- self.GPU:Finalize() +-- +-- Draw (if something changes): +-- self.GPU:RenderToGPU(function() +-- ...code... +-- end) +-- +-- Draw (every frame): +-- self.GPU:Render() +-------------------------------------------------------------------------------- + + +GPULib = {} + +local GPU = {} +GPU.__index = GPU +GPULib.GPU = GPU + +function GPULib.WireGPU(ent, ...) + local self = { + entindex = ent and ent:EntIndex() or 0, + Entity = ent or NULL, + } + setmetatable(self, GPU) + self:Initialize(...) + return self +end +WireGPU = GPULib.WireGPU + +function GPU:SetTranslucentOverride(bool) + self.translucent = bool; +end + +function GPU:GetInfo() + local ent = self.Entity + if not ent:IsValid() then ent = self.actualEntity end + if not ent then return end + + local model = ent:GetModel() + local monitor = WireGPU_Monitors[model] + + local pos = ent:LocalToWorld(monitor.offset) + local ang = ent:LocalToWorldAngles(monitor.rot) + + return monitor, pos, ang +end + +if CLIENT then + local materialCache = {} + function GPULib.Material(name) + if not materialCache[name] then + local protoMaterial = Material(name) + local textureName = protoMaterial:GetString("$basetexture") + local imageName = protoMaterial:GetName() + local materialParameters = { + ["$basetexture"] = textureName, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1, + } + materialCache[name] = CreateMaterial(imageName.."_DImage", "UnlitGeneric", materialParameters) + end + return materialCache[name] + end + + -- Handles rendertarget caching + local RT_CACHE_SIZE = 32 + local RenderTargetCache = { } + + for i = 1,RT_CACHE_SIZE do + local Target = { + false, -- Is rendertarget in use + false -- The rendertarget (false if doesn't exist) + } + table.insert( RenderTargetCache, Target ) + end + + -- Returns a render target from the cache pool and marks it as used + local function GetRT() + + for i, RT in pairs( RenderTargetCache ) do + if not RT[1] then -- not used + + local rendertarget = RT[2] + if rendertarget then + RT[1] = true -- Mark as used + return rendertarget + end + + end + end + + -- No free rendertargets. Find first non used and create it. + for i, RT in pairs( RenderTargetCache ) do + if not RT[1] and RT[2] == false then -- not used and doesn't exist, let's create the render target. + + local rendertarget = GetRenderTarget("WireGPU_RT_"..i, 512, 512) + + if rendertarget then + RT[1] = true -- Mark as used + RT[2] = rendertarget -- Assign the RT + return rendertarget + else + RT[1] = true -- Mark as used since we couldn't create it + ErrorNoHalt("Wiremod: Render target ".."WireGPU_RT_"..i.." could not be created!\n") + end + + end + end + + ErrorNoHalt("All render targets are in use, some wire screens may not draw!\n") + return nil + + end + + -- Frees an used RT + local function FreeRT(rt) + + for i, RT in pairs( RenderTargetCache ) do + if RT[2] == rt then + + RT[1] = false + return + end + end + + ErrorNoHalt("RT Screen ",rt," could not be freed (not found)\n") + + end + + // + // Create basic fonts + // + local fontData = + { + font="lucida console", + size=20, + weight=800, + antialias= true, + additive = false, + } + surface.CreateFont("WireGPU_ConsoleFont", fontData) + // + // Create screen textures and materials + // + WireGPU_matScreen = CreateMaterial("sprites/GPURT","UnlitGeneric",{ + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1, + ["$translucent"] = 1, + ["$ignorez"] = 1, + ["$nolod"] = 1, + }) + WireGPU_matBuffer = CreateMaterial("sprites/GPUBUF","UnlitGeneric",{ + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1, + ["$translucent"] = 1, + ["$ignorez"] = 1, + ["$nolod"] = 1, + }) + + + function GPU:Initialize(no_rendertarget) + if no_rendertarget then return nil end + -- Rendertarget cache management + + -- This should not even happen. + if self.RT then + ErrorNoHalt("Warning: GPU:Initialize called, but an RT still existed. Maybe you are not killing it properly?") + FreeRT(self.RT) + end + + -- find a free one + self.RT = GetRT() + if not self.RT then + return nil + end + + -- clear the new RT + self.ForceClear = true + return self.RT + end + + function GPULib.WireGPU(ent, ...) + local self = { + entindex = ent and ent:EntIndex() or 0, + Entity = ent or NULL, + } + setmetatable(self, GPU) + self:Initialize(...) + return self + end + + function GPU:Finalize() + if not self.RT then return end + timer.Simple(0.2, function() -- This is to test if the entity has truly been removed. If you really know you need to remove the RT, call FreeRT() + if IsValid(self.Entity) then + --MsgN(self,"Entity still exists, exiting.") + return + end + self:FreeRT() + end) + end + + function GPU:FreeRT() + FreeRT( self.RT ) + self.RT = nil + end + + function GPU:Clear(color) + if not self.RT then return end + render.ClearRenderTarget(self.RT, color or Color(0, 0, 0, 0)) + end + + local texcoords = { + [0] = { + { u = 0, v = 0 }, + { u = 1, v = 0 }, + { u = 1, v = 1 }, + { u = 0, v = 1 }, + }, + { + { u = 0, v = 1 }, + { u = 0, v = 0 }, + { u = 1, v = 0 }, + { u = 1, v = 1 }, + }, + { + { u = 1, v = 1 }, + { u = 0, v = 1 }, + { u = 0, v = 0 }, + { u = 1, v = 0 }, + }, + { + { u = 1, v = 0 }, + { u = 1, v = 1 }, + { u = 0, v = 1 }, + { u = 0, v = 0 }, + }, + } + -- helper function for GPU:Render + function GPU.DrawScreen(x, y, w, h, rotation, scale) + -- generate vertex data + local vertices = { + --[[ + Vector(x , y ), + Vector(x+w, y ), + Vector(x+w, y+h), + Vector(x , y+h), + ]] + { x = x , y = y }, + { x = x+w, y = y }, + { x = x+w, y = y+h }, + { x = x , y = y+h }, + } + + -- rotation and scaling + local rotated_texcoords = texcoords[rotation] or texcoords[0] + for index,vertex in ipairs(vertices) do + local tex = rotated_texcoords[index] + if tex.u == 0 then + vertex.u = tex.u-scale + else + vertex.u = tex.u+scale + end + if tex.v == 0 then + vertex.v = tex.v-scale + else + vertex.v = tex.v+scale + end + end + + surface.DrawPoly(vertices) + --render.DrawQuad(unpack(vertices)) + end + + function GPU:RenderToGPU(renderfunction) + if not self.RT then return end + + if self.ForceClear then + self:Clear() + self.ForceClear = nil + end + + local oldw = ScrW() + local oldh = ScrH() + + local NewRT = self.RT + local OldRT = render.GetRenderTarget() + + render.SetRenderTarget(NewRT) + render.SetViewPort(0, 0, 512, 512) + cam.Start2D() + local ok, err = xpcall(renderfunction, debug.traceback) + if not ok then WireLib.ErrorNoHalt(err) end + cam.End2D() + render.SetViewPort(0, 0, oldw, oldh) + render.SetRenderTarget(OldRT) + end + + -- If width is specified, height is ignored. if neither is specified, a height of 512 is used. + function GPU:RenderToWorld(width, height, renderfunction, zoffset, emulateRT) + local monitor, pos, ang = self:GetInfo() + + if zoffset then + pos = pos + ang:Up()*zoffset + end + + if emulateRT then + pos = pos - ang:Right()*(monitor.y2-monitor.y1)/2 + pos = pos - ang:Forward()*(monitor.x2-monitor.x1)/2 + end + + local h = width and width*monitor.RatioX or height or 512 + local w = width or h/monitor.RatioX + local x = -w/2 + local y = -h/2 + + local res = monitor.RS*512/h + cam.Start3D2D(pos, ang, res) + local ok, err = xpcall(renderfunction, debug.traceback, x, y, w, h, monitor, pos, ang, res) + if not ok then WireLib.ErrorNoHalt(err) end + cam.End3D2D() + end + + function GPU:Render(rotation, scale, width, height, postrenderfunction) + if not self.RT then return end + + local monitor, pos, ang = self:GetInfo() + + local OldTex = WireGPU_matScreen:GetTexture("$basetexture") + WireGPU_matScreen:SetTexture("$basetexture", self.RT) + + local res = monitor.RS + cam.Start3D2D(pos, ang, res) + local ok, err = xpcall(function() + local aspect = 1/monitor.RatioX + local w = (width or 512)*aspect + local h = (height or 512) + local x = -w/2 + local y = -h/2 + + local translucent = self.translucent; + + if translucent == nil then + translucent = monitor.translucent + end + + if not translucent then + surface.SetDrawColor(0,0,0,255) + surface.DrawRect(-256*aspect,-256,512*aspect,512) + end + + surface.SetDrawColor(255,255,255,255) + surface.SetMaterial(WireGPU_matScreen) + + render.PushFilterMag(self.texture_filtering or TEXFILTER.POINT) + render.PushFilterMin(self.texture_filtering or TEXFILTER.POINT) + + self.DrawScreen(x, y, w, h, rotation or 0, scale or 0) + + render.PopFilterMin() + render.PopFilterMag() + + if postrenderfunction then postrenderfunction(pos, ang, res, aspect, monitor) end + end, debug.traceback) + if not ok then WireLib.ErrorNoHalt(err) end + cam.End3D2D() + + WireGPU_matScreen:SetTexture("$basetexture", OldTex) + end + + -- compatibility + + local GPUs = {} + + function WireGPU_NeedRenderTarget(entindex) + if not GPUs[entindex] then GPUs[entindex] = GPULib.WireGPU(Entity(entindex)) end + return GPUs[entindex].RT + end + + function WireGPU_GetMyRenderTarget(entindex) + local self = GPUs[entindex] + if self.RT then return self.RT end + + return self:Initialize() + end + + function WireGPU_ReturnRenderTarget(entindex) + return GPUs[entindex]:Finalize() + end + + function WireGPU_DrawScreen(x, y, w, h, rotation, scale) + return GPU.DrawScreen(x, y, w, h, rotation, scale) + end + +end + +-- GPULib switcher functionality +if CLIENT then + + usermessage.Hook("wire_gpulib_setent", function(um) + local screen = Entity(um:ReadShort()) + if not screen:IsValid() then return end + if not screen.GPU then return end + + local ent = Entity(um:ReadShort()) + if not ent:IsValid() then return end + + screen.GPU.Entity = ent + screen.GPU.entindex = ent:EntIndex() + + if screen == ent then return end + + screen.GPU.actualEntity = screen + + local model = ent:GetModel() + local monitor = WireGPU_Monitors[model] + + local h = 512*monitor.RS + local w = h/monitor.RatioX + local x = -w/2 + local y = -h/2 + + local corners = { + { x , y }, + { x , y+h }, + { x+w, y }, + { x+w, y+h }, + } + + local mins, maxs = screen:OBBMins(), screen:OBBMaxs() + + local timerid = "wire_gpulib_updatebounds"..screen:EntIndex() + local function setbounds() + if not screen:IsValid() then + timer.Remove(timerid) + return + end + if not ent:IsValid() then + timer.Remove(timerid) + + screen.ExtraRBoxPoints[1001] = nil + screen.ExtraRBoxPoints[1002] = nil + screen.ExtraRBoxPoints[1003] = nil + screen.ExtraRBoxPoints[1004] = nil + Wire_UpdateRenderBounds(screen) + + screen.GPU.Entity = screen.GPU.actualEntity + screen.GPU.entindex = screen.GPU.actualEntity:EntIndex() + screen.GPU.actualEntity = nil + + return + end + + local ang = ent:LocalToWorldAngles(monitor.rot) + local pos = ent:LocalToWorld(monitor.offset) + + screen.ExtraRBoxPoints = screen.ExtraRBoxPoints or {} + for i,x,y in ipairs_map(corners, unpack) do + local p = Vector(x, y, 0) + p:Rotate(ang) + p = screen:WorldToLocal(p+pos) + + screen.ExtraRBoxPoints[i+1000] = p + end + + Wire_UpdateRenderBounds(screen) + end + + timer.Create(timerid, 5, 0, setbounds) + + setbounds() + end) -- usermessage.Hook + +elseif SERVER then + + function GPULib.switchscreen(screen, ent) + screen.GPUEntity = ent + umsg.Start("wire_gpulib_setent") + umsg.Short(screen:EntIndex()) + umsg.Short(ent:EntIndex()) + umsg.End() + end + +end + + + + +-- GPULib caching functionality +if CLIENT then + ------------------------------------------------------------------------------ + -- Attach cache receiver to this entity + ------------------------------------------------------------------------------ + local writeHandler = {} + function GPULib.ClientCacheCallback(ent, writeFunction) + writeHandler[ent and ent:EntIndex() or 0] = writeFunction + end + + ------------------------------------------------------------------------------ + -- RLE-decompress incoming message + ------------------------------------------------------------------------------ + --[[local blockText = { + { "[no offset]", "[1-offset]", "[2-offset]", "[4-offset]" }, + { "[no rep]", nil, "[rep 2]", "[rep 4]" }, + { "[cnt 1]", nil, "[cnt 2]", "[cnt 3]" }, + { "[1-byte]", "[2-byte]", "[4-byte]", "[marker]" }, + } ]]-- + + local function GPULib_MemorySync(um) + -- Find the referenced entity + local GPUIdx = um:ReadLong() + local GPU = ents.GetByIndex(GPUIdx) + if not GPU then return end + if not GPU:IsValid() then return end + if not writeHandler[GPUIdx] then return end + + -- Start reading blocks + local blockCount = 0 + local currentOffset = 0 + while true do + -- Read next block + blockCount = blockCount + 1 + if blockCount > 256 then error("GPULib usermessage read error") return end + + -- Read block flags + local dataFlags = um:ReadChar()+128 + if dataFlags == 240 then return end + + local offsetSize = dataFlags % 4 + local repeatCount = math.floor(dataFlags/4) % 4 + local dataCount = math.floor(dataFlags/16) % 4 + local valueSize = math.floor(dataFlags/64) % 4 + + local Repeat = 0 + local Count = 0 + + if offsetSize > 0 then + local deltaOffset = 0 + if offsetSize == 1 then deltaOffset = um:ReadChar () end + if offsetSize == 2 then deltaOffset = um:ReadShort() end + if offsetSize == 3 then deltaOffset = um:ReadFloat() end + currentOffset = currentOffset + deltaOffset + --print(" dOffset = "..deltaOffset..", offset = "..currentOffset) + end + + if dataCount == 0 then Count = 1 end + if dataCount == 1 then Count = um:ReadChar()+130 end + if dataCount == 2 then Count = 2 end + if dataCount == 3 then Count = 3 end + + if repeatCount == 0 then Repeat = 1 end + if repeatCount == 1 then Repeat = um:ReadChar()+130 end + if repeatCount == 2 then Repeat = 2 end + if repeatCount == 3 then Repeat = 4 end + + --[[print(" Block ", + blockText[1][offsetSize+1], + blockText[2][repeatCount+1] or ("[rep "..Repeat.."* ]"), + blockText[3][dataCount+1] or ("[cnt "..Count.."* ]"), + blockText[4][valueSize+1])]]-- + + for i=1,Count do + local Value = 0 + if valueSize == 0 then Value = um:ReadChar() end + if valueSize == 1 then Value = um:ReadShort() end + if valueSize == 2 then Value = um:ReadLong() end + if valueSize == 3 then Value = um:ReadFloat() end + + for j=1,Repeat do + --print(" ["..currentOffset.."] = "..Value) + writeHandler[GPUIdx](currentOffset,Value) + currentOffset = currentOffset + 1 + end + end + end + end + usermessage.Hook("wire_memsync", GPULib_MemorySync) +elseif SERVER then + local CACHEMGR = {} + CACHEMGR.__index = CACHEMGR + GPULib.CACHEMGR = CACHEMGR + + + ------------------------------------------------------------------------------ + -- Create new cache manager (serverside) + ------------------------------------------------------------------------------ + function GPULib.GPUCacheManager(ent, orderMatters, ...) + local self = { + EntIndex = ent and ent:EntIndex() or 0, + Entity = ent or NULL, + } + setmetatable(self, CACHEMGR) + self.ValueOrderMatters = orderMatters + self.Enabled = true + self:Reset() + return self + end + GPUCacheManager = GPULib.GPUCacheManager + + + ------------------------------------------------------------------------------ + -- Get size of the value to write + ------------------------------------------------------------------------------ + local function getSize(value) + if (value >= -128) and (value <= 127) and (math.floor(value) == value) then return 1,false end + if (value >= -32768) and (value <= 32767) and (math.floor(value) == value) then return 2,false end + if (value >= -2147483648) and (value <= 2147483647) and (math.floor(value) == value) then return 4,false end + return 4,true + end + + + ------------------------------------------------------------------------------ + -- Initialize cache manager + ------------------------------------------------------------------------------ + function CACHEMGR:Reset() + self.Cache = {} + self.CacheBytes = 0 + end + + + ------------------------------------------------------------------------------ + -- Write a single value to cache + ------------------------------------------------------------------------------ + function CACHEMGR:Write(Address,Value) + local valueSize,valueFloat + if Value then + valueSize,valueFloat = getSize(Value) + self.CacheBytes = self.CacheBytes + valueSize + end + + table.insert(self.Cache,{ Address, Value, valueSize, valueFloat }) + --if #self.Cache > 2048 then self:Flush() end + end + + + ------------------------------------------------------------------------------ + -- Send value right away + ------------------------------------------------------------------------------ + function CACHEMGR:WriteNow(Address,Value,forcePlayer) + umsg.Start("wire_memsync", forcePlayer) + umsg.Long(self.EntIndex) + umsg.Char(195-128) + umsg.Float(Address) + umsg.Float(Value) + umsg.Char(240-128) + umsg.End() + end + + + ------------------------------------------------------------------------------ + -- RLE-compress cache and send it + ------------------------------------------------------------------------------ + function CACHEMGR:Flush(forcePlayer) + -- Don't flush if nothing cached + if #self.Cache == 0 then return end + self.CacheBytes = 0 + + -- Sort cache so all addresses are continiously layed out + -- Do not sort if order at which values are written matters + if not self.ValueOrderMatters then + table.sort(self.Cache,function(A,B) + return A[1] < B[1] + end) + end + + -- RLE-encode the data + local compressInfo = {} + for _,data in ipairs(self.Cache) do + local address,value,size,isfloat = data[1],(data[2] or 0),(data[3] or 1),(data[4] or false) + local compressBlock = compressInfo[#compressInfo] + local sequentialBlock + local previousBlockEnd + if compressBlock then + previousBlockEnd = compressBlock.Offset+#compressBlock.Data*compressBlock.Repeat + sequentialBlock = previousBlockEnd == address + end + + if not compressBlock then + -- New block of data + compressBlock = { + Data = { value }, + Offset = address, + SetOffset = address, + Repeat = 1, + Size = size, + IsFloat = isfloat, + } + table.insert(compressInfo,compressBlock) + elseif sequentialBlock and + (compressBlock.Size == size) then + -- Add to previous block of data + if (#compressBlock.Data == 1) and (compressBlock.Data[1] == value) and (sequentialBlock) and (compressBlock.Repeat < 256) then + -- RLE compression + compressBlock.Repeat = compressBlock.Repeat + 1 + elseif compressBlock.Repeat > 1 then + -- Cant add to a repeating block, make new + compressBlock = { + Data = { value }, + Offset = address, + Repeat = 1, + Size = size, + IsFloat = isfloat, + } + if not sequentialBlock then compressBlock.SetOffset = address-previousBlockEnd end + table.insert(compressInfo,compressBlock) + else + -- Append to a group of values, unless the block is too big + if #compressBlock.Data*compressBlock.Repeat*compressBlock.Size < 196 then + table.insert(compressBlock.Data,value) + else + -- Add it to a new block instead + compressBlock = { + Data = { value }, + Offset = address, + Repeat = 1, + Size = size, + IsFloat = isfloat, + } + if not sequentialBlock then compressBlock.SetOffset = address-previousBlockEnd end + table.insert(compressInfo,compressBlock) + end + end + else + -- Create new block + compressBlock = { + Data = { value }, + Offset = address, + Repeat = 1, + Size = size, + IsFloat = isfloat, + } + if not sequentialBlock then compressBlock.SetOffset = address-previousBlockEnd end + table.insert(compressInfo,compressBlock) + end + end + + --PrintTable(compressInfo) + + -- Start the message + local messageSize = 4 + umsg.Start("wire_memsync", forcePlayer) + umsg.Long(self.EntIndex) + + -- Start sending all compressed blocks + for k,v in ipairs(compressInfo) do + --======================================================================-- + -- Generate flags for sending the data + --======================================================================-- + -- [0..1] Delta offset + -- 0: no offset + -- 1: 1-byte offset + -- 2: 2-byte offset + -- 3: 4-byte offset + -- [2..3] Repeat count + -- 0: none + -- 1: repeat count 1-byte follows + -- 2: repeat 2 times + -- 3: repeat 4 times + -- [4..5] Data count + -- 0: 1 element + -- 1: data size 1-byte follows + -- 2: 2 elements + -- 3: 3 elements (but not floats) + -- [6..7] Size + -- 0: 1-byte + -- 1: 2-byte + -- 2: 4-byte int + -- 3: 4-byte float + -- + -- If it's a special data marker, then bitmap is: + -- [0..1] Marker type + -- [2..5] Marker data + -- [6] 1 + -- [7] 1 + + local dataFlags = 0 + if v.SetOffset then + local offsetSize = getSize(v.SetOffset) + if offsetSize == 1 then dataFlags = dataFlags + 1 end + if offsetSize == 2 then dataFlags = dataFlags + 2 end + if offsetSize == 4 then dataFlags = dataFlags + 3 end + end + + if v.Repeat > 1 then + if v.Repeat == 2 then dataFlags = dataFlags + 8 + elseif v.Repeat == 4 then dataFlags = dataFlags + 12 + else dataFlags = dataFlags + 4 + end + end + + if #v.Data > 1 then + if #v.Data == 2 then + dataFlags = dataFlags + 32 + elseif (#v.Data == 3) and (not v.IsFloat) then + dataFlags = dataFlags + 48 + else + dataFlags = dataFlags + 16 + end + end + + if v.Size == 1 then dataFlags = dataFlags + 0 end + if v.Size == 2 then dataFlags = dataFlags + 64 end + if (v.Size == 4) and (not v.IsFloat) then dataFlags = dataFlags + 128 end + if (v.Size == 4) and ( v.IsFloat) then dataFlags = dataFlags + 192 end + + umsg.Char(dataFlags-128) + messageSize = messageSize + 4 + + + --======================================================================-- + -- Send the data + --======================================================================-- + if v.SetOffset then + local offsetSize = getSize(v.SetOffset) + if offsetSize == 1 then umsg.Char (v.SetOffset) messageSize = messageSize + 1 end + if offsetSize == 2 then umsg.Short(v.SetOffset) messageSize = messageSize + 2 end + if offsetSize == 4 then umsg.Float(v.SetOffset) messageSize = messageSize + 4 end + end + + if (#v.Data > 2) then + if (#v.Data ~= 3) or (v.IsFloat) then + umsg.Char(#v.Data-130) messageSize = messageSize + 1 + end + end + + if (v.Repeat > 1) and + (v.Repeat ~= 2) and + (v.Repeat ~= 4) then umsg.Char(v.Repeat-130) messageSize = messageSize + 1 end + + for _,value in ipairs(v.Data) do + if v.Size == 1 then umsg.Char (value) messageSize = messageSize + 1 end + if v.Size == 2 then umsg.Short(value) messageSize = messageSize + 2 end + if (v.Size == 4) and (not v.IsFloat) then umsg.Long(value) messageSize = messageSize + 4 end + if (v.Size == 4) and ( v.IsFloat) then umsg.Float(value) messageSize = messageSize + 4 end + end + + + --======================================================================-- + -- Check size of next data block. If it fits into usermessage, continue. + -- Otherwise just create new message + --======================================================================-- + if compressInfo[k+1] then + local nextSize = #compressInfo[k+1].Data*compressInfo[k+1].Repeat*compressInfo[k+1].Size + if nextSize + messageSize > 248 then + umsg.Char(240-128) + umsg.End() + messageSize = 4 + umsg.Start("wire_memsync", forcePlayer) + umsg.Long(self.EntIndex) + compressInfo[k+1].SetOffset = compressInfo[k+1].Offset -- Force set offset + end + else + umsg.Char(240-128) + umsg.End() + end + end + + self.Cache = {} + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/server/debuggerlib.lua b/garrysmod/addons/feature-wire/lua/wire/server/debuggerlib.lua new file mode 100644 index 0000000..b17ebde --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/server/debuggerlib.lua @@ -0,0 +1,160 @@ +local formatPort = {} +WireLib.Debugger = { formatPort = formatPort } -- Make it global +function formatPort.NORMAL(value) + return string.format("%.3f",value) +end + +function formatPort.STRING(value) + return '"' .. value .. '"' +end + +function formatPort.VECTOR(value) + return string.format("(%.1f,%.1f,%.1f)", value[1], value[2], value[3]) +end + +function formatPort.ANGLE(value) + return string.format("(%.1f,%.1f,%.1f)", value.p, value.y, value.r) +end + +formatPort.ENTITY = function(ent) + if not IsValid(ent) then return "(null)" end + return tostring(ent) +end +formatPort.BONE = e2_tostring_bone + +function formatPort.MATRIX(value) + local RetText = "[11="..value[1]..",12="..value[2]..",13="..value[3] + RetText = RetText..",21="..value[4]..",22="..value[5]..",23="..value[6] + RetText = RetText..",31="..value[7]..",32="..value[8]..",33="..value[9].."]" + return RetText +end + +function formatPort.MATRIX2(value) + local RetText = "[11="..value[1]..",12="..value[2] + RetText = RetText..",21="..value[3]..",22="..value[4].."]" + return RetText +end + +function formatPort.MATRIX4(value) + local RetText = "[11="..value[1]..",12="..value[2]..",13="..value[3]..",14="..value[4] + RetText = RetText..",21="..value[5]..",22="..value[6]..",23="..value[7]..",24="..value[8] + RetText = RetText..",31="..value[9]..",32="..value[10]..",33="..value[11]..",34="..value[12] + RetText = RetText..",41="..value[13]..",42="..value[14]..",43="..value[15]..",44="..value[16].."]" + return RetText +end + +function formatPort.RANGER(value) return "ranger" end + +function formatPort.ARRAY(value, OrientVertical) + local RetText = "" + local ElementCount = 0 + for Index, Element in ipairs(value) do + ElementCount = ElementCount+1 + if(ElementCount > 10) then + break + end + RetText = RetText..Index.."=" + --Check for array element type + if isnumber(Element) then --number + RetText = RetText..formatPort.NORMAL(Element) + elseif((istable(Element) and #Element == 3) or isvector(Element)) then --vector + RetText = RetText..formatPort.VECTOR(Element) + elseif(istable(Element) and #Element == 2) then --vector2 + RetText = RetText..formatPort.VECTOR2(Element) + elseif(istable(Element) and #Element == 4) then --vector4 + RetText = RetText..formatPort.VECTOR4(Element) + elseif((istable(Element) and #Element == 3) or isangle(Element)) then --angle + if(isangle(Element)) then + RetText = RetText..formatPort.ANGLE(Element) + else + RetText = RetText.."(" .. math.Round(Element[1],1) .. "," .. math.Round(Element[2],1) .. "," .. math.Round(Element[3],1) .. ")" + end + elseif(istable(Element) and #Element == 9) then --matrix + RetText = RetText..formatPort.MATRIX(Element) + elseif(istable(Element) and #Element == 16) then --matrix4 + RetText = RetText..formatPort.MATRIX4(Element) + elseif(isstring(Element)) then --string + RetText = RetText..formatPort.STRING(Element) + elseif(isentity(Element)) then --entity + RetText = RetText..formatPort.ENTITY(Element) + elseif(type(Element) == "Player") then --player + RetText = RetText..tostring(Element) + elseif(type(Element) == "Weapon") then --weapon + RetText = RetText..tostring(Element)..Element:GetClass() + elseif(type(Element) == "PhysObj" and e2_tostring_bone(Element) != "(null)") then --Bone + RetText = RetText..formatPort.BONE(Element) + else + RetText = RetText.."No Display for "..type(Element) + end + --TODO: add matrix 2 + if OrientVertical then + RetText = RetText..",\n" + else + RetText = RetText..", " + end + end + RetText = string.sub(RetText,1,-3) + return "{"..RetText.."}" +end + +function formatPort.TABLE(value, OrientVertical) + local RetText = "" + local ElementCount = 0 + for Index, Element in pairs(value) do + ElementCount = ElementCount+1 + if(ElementCount > 7) then + break + end + + local long_typeid = string.sub(Index,1,1) == "x" + local typeid = string.sub(Index,1,long_typeid and 3 or 1) + local IdxID = string.sub(Index,(long_typeid and 3 or 1)+1) + + RetText = RetText..IdxID.."=" + --Check for array element type + if(typeid == "n") then --number + RetText = RetText..formatPort.NORMAL(Element) + elseif(istable(Element) and #Element == 3) or isvector(Element) then --vector + RetText = RetText..formatPort.VECTOR(Element) + elseif(istable(Element) and #Element == 2) then --vector2 + RetText = RetText..formatPort.VECTOR2(Element) + elseif(istable(Element) and #Element == 4 and typeid == "v4") then --vector4 + RetText = RetText..formatPort.VECTOR4(Element) + elseif(istable(Element) and #Element == 3) or isangle(Element) then --angle + if isangle(Element) then + RetText = RetText..formatPort.ANGLE(Element) + else + RetText = RetText.."(" .. math.Round(Element[1]*10)/10 .. "," .. math.Round(Element[2]*10)/10 .. "," .. math.Round(Element[3]*10)/10 .. ")" + end + elseif(istable(Element) and #Element == 9) then --matrix + RetText = RetText..formatPort.MATRIX(Element) + elseif(istable(Element) and #Element == 16) then --matrix4 + RetText = RetText..formatPort.MATRIX4(Element) + elseif(typeid == "s") then --string + RetText = RetText..formatPort.STRING(Element) + elseif(isentity(Element) and typeid == "e") then --entity + RetText = RetText..formatPort.ENTITY(Element) + elseif(type(Element) == "Player") then --player + RetText = RetText..tostring(Element) + elseif(type(Element) == "Weapon") then --weapon + RetText = RetText..tostring(Element)..Element:GetClass() + elseif(typeid == "b") then + RetText = RetText..formatPort.BONE(Element) + else + RetText = RetText.."No Display for "..type(Element) + end + --TODO: add matrix 2 + if OrientVertical then + RetText = RetText..",\n" + else + RetText = RetText..", " + end + end + RetText = string.sub(RetText,1,-3) + return "{"..RetText.."}" +end + + +function WireLib.registerDebuggerFormat(typename, func) + formatPort[typename:upper()] = func +end diff --git a/garrysmod/addons/feature-wire/lua/wire/server/modelplug.lua b/garrysmod/addons/feature-wire/lua/wire/server/modelplug.lua new file mode 100644 index 0000000..64d090a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/server/modelplug.lua @@ -0,0 +1,40 @@ +ModelPlugInfo = {} + +--uncomment line 15 and line 26-34 to enable sending model packs to clients + +function ModelPlug_Register(category) + if (not ModelPlugInfo[category]) then + local catinfo = {} + + local packs = file.Find("WireModelPacks/*.txt", "DATA") + for _,filename in pairs(packs) do + --resource.AddFile("data/WireModelPacks/" .. filename) + + local packtbl = util.KeyValuesToTable(file.Read("WireModelPacks/" .. filename) or {}) + + for name,entry in pairs(packtbl) do + local categorytable = string.Explode(",", entry.categories or "none") or { "none" } + + for _,cat in pairs(categorytable) do + if (cat == category) then + catinfo[name] = entry.model or "" + + --[[if (entry.model) then + resource.AddFile(entry.model) + end + + if (entry.files) then + for _,extrafilename in pairs(entry.files) do + resource.AddFile(extrafilename) + end + end]] + + break + end + end + end + end + + ModelPlugInfo[category] = catinfo + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/server/radiolib.lua b/garrysmod/addons/feature-wire/lua/wire/server/radiolib.lua new file mode 100644 index 0000000..9391c63 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/server/radiolib.lua @@ -0,0 +1,142 @@ +//First there was phenes +//Then there was High6 +//Then Black Phoenix came and rewrote everything, what a bastard + +local Radio_Entities = {} + +function Radio_Register(ent) + table.insert(Radio_Entities, ent) +end + +function Radio_Unregister(ent) + for k,v in ipairs(Radio_Entities) do + if (v == ent) then + table.remove(Radio_Entities, k) + elseif (IsEntity(v.Entity)) then + //Zero out all channels that this radio used + for i=0,31 do + if (v.RecievedData[i].Owner == ent) then + v.RecievedData[i].Owner = nil + v.RecievedData[i].Data = 0 + v:NotifyDataRecieved(i) + end + end + v:ShowOutput() + end + end +end + +function Radio_SendData(ent, subch, data) + ent.SentData[subch] = data + + for k,v in ipairs(Radio_Entities) do + if (not IsEntity(v.Entity)) then //Invalid radio + Radio_Unregister(v) + elseif (ent:EntIndex() != v.Entity:EntIndex()) then //Not sender + if ((ent.Secure) && (v.Secure)) then + if ((ent:GetPlayer():SteamID() == v:GetPlayer():SteamID()) && (ent.Channel == v.Channel)) then + v.RecievedData[subch].Owner = ent + v.RecievedData[subch].Data = data + v:NotifyDataRecieved(subch) + end + else + if (ent.Channel == v.Channel) then + v.RecievedData[subch].Owner = ent + v.RecievedData[subch].Data = data + v:NotifyDataRecieved(subch) + end + end + v:ShowOutput() + end + end +end + +function Radio_RecieveData(ent) + for i=0,31 do + ent.RecievedData[i].Owner = nil + ent.RecievedData[i].Data = 0 + ent:NotifyDataRecieved(i) + end + + for k,v in ipairs(Radio_Entities) do + if (not IsEntity(v.Entity)) then //Invalid radio + Radio_Unregister(v) + elseif (ent:EntIndex() != v.Entity:EntIndex()) then //Not sender + if ((ent.Secure) && (v.Secure)) then + if ((ent:GetPlayer():SteamID() == v:GetPlayer():SteamID()) && (ent.Channel == v.Channel)) then + for i=0,31 do + ent.RecievedData[i].Owner = v + ent.RecievedData[i].Data = v.SentData[i] + ent:NotifyDataRecieved(i) + end + end + else + if (ent.Channel == v.Channel) then + for i=0,31 do + ent.RecievedData[i].Owner = v + ent.RecievedData[i].Data = v.SentData[i] + ent:NotifyDataRecieved(i) + end + end + end + end + end + ent:ShowOutput() +end + +function Radio_ChangeChannel(ent) + //Request all other radios send data to this radio + Radio_RecieveData(ent) + + for k,v in ipairs(Radio_Entities) do + if (not IsEntity(v.Entity)) then //Invalid radio + Radio_Unregister(v) + elseif (ent:EntIndex() != v.Entity:EntIndex()) then //Not sender + //1. Kill all transmissions for this radio + //for i=0,31 do + // if (v.RecievedData[i].Owner == ent) then + // v.RecievedData[i].Owner = nil + // v.RecievedData[i].Data = 0 + // v:NotifyDataRecieved(i) + // end + //end + Radio_RecieveData(v) + + //2. Retransmit under new channel + if ((ent.Secure) && (v.Secure)) then + if ((ent:GetPlayer():SteamID() == v:GetPlayer():SteamID()) && (ent.Channel == v.Channel)) then + for i=0,31 do + if (ent.SentData[i] ~= 0) then //dont send zeroes + v.RecievedData[i].Owner = ent + v.RecievedData[i].Data = ent.SentData[i] + v:NotifyDataRecieved(i) + end + end + end + else + if (ent.Channel == v.Channel) then + for i=0,31 do + if (ent.SentData[i] ~= 0) then //dont send zeroes + v.RecievedData[i].Owner = ent + v.RecievedData[i].Data = ent.SentData[i] + v:NotifyDataRecieved(i) + end + end + end + end + + v:ShowOutput() + end + end +end + +local radio_twowaycounter = 0 + +function Radio_GetTwoWayID() + radio_twowaycounter = radio_twowaycounter + 1 + return radio_twowaycounter +end + +-- phenex: End radio mod. +//Modified by High6 (To support 4 values) +//Rebuilt by high6 to allow defined amount of values/secure lines diff --git a/garrysmod/addons/feature-wire/lua/wire/server/wirelib.lua b/garrysmod/addons/feature-wire/lua/wire/server/wirelib.lua new file mode 100644 index 0000000..f115d2e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/server/wirelib.lua @@ -0,0 +1,1181 @@ +-- Compatibility Global + +if not WireLib then return end + +WireAddon = 1 + +local ents = ents +local timer = timer +local string = string +local math_clamp = math.Clamp +local table = table +local hook = hook +local concommand = concommand +local Msg = Msg +local MsgN = MsgN +local pairs = pairs +local ipairs = ipairs +local IsValid = IsValid +local tostring = tostring +local Vector = Vector +local Color = Color +local Material = Material + +local HasPorts = WireLib.HasPorts -- Very important for checks! + + +function WireLib.PortComparator(a,b) + return a.Num < b.Num +end + +-- Allow to specify the description and type, like "Name (Description) [TYPE]" +local function ParsePortName(namedesctype, fbtype, fbdesc) + local namedesc, tp = namedesctype:match("^(.+) %[(.+)%]$") + if not namedesc then + namedesc = namedesctype + tp = fbtype + end + + local name, desc = namedesc:match("^(.+) %((.*)%)$") + if not name then + name = namedesc + desc = fbdesc + end + return name, desc, tp +end + +local Inputs = {} +local Outputs = {} +local CurLink = {} +local CurTime = CurTime + +-- helper function that pcalls an input +function WireLib.TriggerInput(ent, name, value, ...) + if (not IsValid(ent) or not HasPorts(ent) or not ent.Inputs or not ent.Inputs[name]) then return end + ent.Inputs[name].Value = value + + if (not ent.TriggerInput) then return end + local ok, ret = xpcall(ent.TriggerInput, debug.traceback, ent, name, value, ...) + if not ok then + local ply = WireLib.GetOwner(ent) + local owner_msg = IsValid(ply) and (" by %s"):format(tostring(ply)) or "" + local message = ("Wire error (%s%s):\n%s\n"):format(tostring(ent),owner_msg, ret) + WireLib.ErrorNoHalt(message) + if IsValid(ply) then WireLib.ClientError(message, ply) end + end +end + +-- an array of data types +WireLib.DT = { + NORMAL = { + Zero = 0 + }, -- Numbers + VECTOR = { + Zero = Vector(0, 0, 0) + }, + ANGLE = { + Zero = Angle(0, 0, 0) + }, + COLOR = { + Zero = Color(0, 0, 0) + }, + ENTITY = { + Zero = NULL + }, + STRING = { + Zero = "" + }, + TABLE = { + Zero = {n={},ntypes={},s={},stypes={},size=0}, + }, + BIDIRTABLE = { + Zero = {n={},ntypes={},s={},stypes={},size=0}, + BiDir = true + }, + ANY = { + Zero = 0 + }, + ARRAY = { + Zero = {} + }, + BIDIRARRAY = { + Zero = {}, + BiDir = true + }, +} + +function WireLib.CreateSpecialInputs(ent, names, types, descs) + types = types or {} + descs = descs or {} + local ent_ports = {} + ent.Inputs = ent_ports + for n,v in pairs(names) do + local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n]) + + local port = { + Entity = ent, + Name = name, + Desc = desc, + Type = tp, + Value = WireLib.DT[ tp ].Zero, + Material = "tripmine_laser", + Color = Color(255, 255, 255, 255), + Width = 1, + Num = n, + } + + local idx = 1 + while (Inputs[idx]) do + idx = idx+1 + end + port.Idx = idx + + ent_ports[name] = port + Inputs[idx] = port + end + + WireLib._SetInputs(ent) + + return ent_ports +end + +function WireLib.CreateSpecialOutputs(ent, names, types, descs) + types = types or {} + descs = descs or {} + local ent_ports = {} + ent.Outputs = ent_ports + for n,v in pairs(names) do + local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n]) + + local port = { + Entity = ent, + Name = name, + Desc = desc, + Type = tp, + Value = WireLib.DT[ tp ].Zero, + Connected = {}, + TriggerLimit = 8, + Num = n, + } + + local idx = 1 + while (Outputs[idx]) do + idx = idx+1 + end + port.Idx = idx + + ent_ports[name] = port + Outputs[idx] = port + end + + WireLib._SetOutputs(ent) + + return ent_ports +end + +function WireLib.AdjustSpecialInputs(ent, names, types, descs) + types = types or {} + descs = descs or {} + local ent_ports = ent.Inputs or {} + for n,v in ipairs(names) do + local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n]) + + if (ent_ports[name]) then + if tp ~= ent_ports[name].Type then + timer.Simple(0, function() WireLib.Link_Clear(ent, name) end) + ent_ports[name].Value = WireLib.DT[tp].Zero + ent_ports[name].Type = tp + end + ent_ports[name].Keep = true + ent_ports[name].Num = n + ent_ports[name].Desc = descs[n] + else + local port = { + Entity = ent, + Name = name, + Desc = desc, + Type = tp, + Value = WireLib.DT[ tp ].Zero, + Material = "tripmine_laser", + Color = Color(255, 255, 255, 255), + Width = 1, + Keep = true, + Num = n, + } + + local idx = 1 + while (Inputs[idx]) do + idx = idx+1 + end + port.Idx = idx + + ent_ports[name] = port + Inputs[idx] = port + end + end + + for portname,port in pairs(ent_ports) do + if (port.Keep) then + port.Keep = nil + else + WireLib.Link_Clear(ent, portname) + + ent_ports[portname] = nil + end + end + + WireLib._SetInputs(ent) + + return ent_ports +end + + +function WireLib.AdjustSpecialOutputs(ent, names, types, descs) + types = types or {} + descs = descs or {} + local ent_ports = ent.Outputs or {} + for n,v in ipairs(names) do + local name, desc, tp = ParsePortName(v, types[n] or "NORMAL", descs and descs[n]) + + if (ent_ports[name]) then + if tp ~= ent_ports[name].Type then + WireLib.DisconnectOutput(ent, name) + ent_ports[name].Type = tp + end + ent_ports[name].Keep = true + ent_ports[name].Num = n + ent_ports[name].Desc = descs[n] + else + local port = { + Keep = true, + Name = name, + Desc = descs[n], + Type = types[n] or "NORMAL", + Value = WireLib.DT[ (types[n] or "NORMAL") ].Zero, + Connected = {}, + TriggerLimit = 8, + Num = n, + } + + local idx = 1 + while (Outputs[idx]) do + idx = idx+1 + end + port.Idx = idx + + ent_ports[name] = port + Outputs[idx] = port + end + end + + for portname,port in pairs(ent_ports) do + if (port.Keep) then + port.Keep = nil + else + WireLib.DisconnectOutput(ent, portname) + ent_ports[portname] = nil + end + end + + WireLib._SetOutputs(ent) + + return ent_ports +end + +--- Disconnects all wires from the given output. +function WireLib.DisconnectOutput(entity, output_name) + local output = entity.Outputs[output_name] + if output == nil then return end + for _, input in pairs_consume(output.Connected) do + if IsValid(input.Entity) then + WireLib.Link_Clear(input.Entity, input.Name) + end + end +end + +function WireLib.RetypeInputs(ent, iname, itype, descs) + if not HasPorts(ent) then return end + + local ent_ports = ent.Inputs + if (not ent_ports[iname]) or (not itype) then return end + if itype ~= ent_ports[iname].Type then + WireLib.Link_Clear(ent, iname) + ent_ports[iname].Type = itype + end + ent_ports[iname].Desc = descs + ent_ports[iname].Value = WireLib.DT[itype].Zero + + WireLib._SetInputs(ent) +end + + +function WireLib.RetypeOutputs(ent, oname, otype, descs) + if not HasPorts(ent) then return end + + local ent_ports = ent.Outputs + if (not ent_ports[oname]) or (not otype) then return end + if otype ~= ent_ports[oname].Type then + WireLib.DisconnectOutput(ent, oname) + ent_ports[oname].Type = otype + end + ent_ports[oname].Desc = descs + ent_ports[oname].Value = WireLib.DT[otype].Zero + + WireLib._SetOutputs(ent) +end + + +-- force_outputs is only needed for existing components to allow them to be updated +function WireLib.Restored(ent, force_outputs) + if not HasPorts(ent) then return end + + local ent_ports = ent.Inputs + if (ent_ports) then + for name,port in pairs(ent_ports) do + if (not port.Material) then -- Must be an old save + port.Name = name + + if (port.Ropes) then + for _,rope in pairs(port.Ropes) do + rope:Remove() + end + port.Ropes = nil + end + end + + port.Entity = ent + port.Type = port.Type or "NORMAL" + port.Material = port.Material or "cable/blue_elec" + port.Color = port.Color or Color(255, 255, 255, 255) + port.Width = port.Width or 2 + port.StartPos = port.StartPos or Vector(0, 0, 0) + if (port.Src) and (not port.Path) then + port.Path = { { Entity = port.Src, Pos = Vector(0, 0, 0) } } + end + + local idx = 1 + while (Inputs[idx]) do + idx = idx+1 + end + port.Idx = idx + + Inputs[idx] = port + end + end + + local ent_ports = ent.Outputs + if (ent_ports) then + for _,port in pairs(ent_ports) do + port.Entity = ent + port.Type = port.Type or "NORMAL" + + local idx = 1 + while (Outputs[idx]) do + idx = idx+1 + end + port.Idx = idx + + Outputs[idx] = port + end + elseif (force_outputs) then + ent.Outputs = WireLib.CreateOutputs(ent, force_outputs) + end +end + +local function ClearPorts(ports, ConnectEnt, DontSendToCL) + local Valid, EmergencyBreak = true, 0 + + -- There is a strange bug, not all the links get removed at once. + -- It works when you run it multiple times. + while (Valid and (EmergencyBreak < 32)) do + local newValid = nil + + for k,v in ipairs(ports) do + local Ent, Name = v.Entity, v.Name + if (IsValid(Ent) and (not ConnectEnt or (ConnectEnt == Ent))) then + local ports = Ent.Inputs + if (ports) then + local port = ports[Name] + if (port) then + WireLib.Link_Clear(Ent, Name, DontSendToCL) + newValid = true + end + end + end + end + + Valid = newValid + EmergencyBreak = EmergencyBreak + 1 -- Prevents infinite loops if something goes wrong. + end +end + +-- Set DontUnList to true, if you want to call WireLib._RemoveWire(eid) manually. +function WireLib.Remove(ent, DontUnList) + --Clear the inputs + local ent_ports = ent.Inputs + if (ent_ports) then + for _,inport in pairs(ent_ports) do + local Source = inport.Src + if (IsValid(Source)) then + local Outports = Source.Outputs + if (Outports) then + local outport = Outports[inport.SrcId] + if (outport) then + ClearPorts(outport.Connected, ent, true) + end + end + end + Inputs[inport.Idx] = nil + end + end + + --Clear the outputs + local ent_ports = ent.Outputs + if (ent_ports) then + for _,outport in pairs(ent_ports) do + ClearPorts(outport.Connected) + Outputs[outport.Idx] = nil + end + end + + ent.Inputs = nil -- Remove the inputs + ent.Outputs = nil -- Remove the outputs + ent.IsWire = nil -- Remove the wire mark + + if (DontUnList) then return end -- Set DontUnList to true if you want to remove ent from the list manually. + WireLib._RemoveWire(ent:EntIndex()) -- Remove entity from the list, so it doesn't count as a wire able entity anymore. Very important for IsWire checks! +end + + +local function Wire_Link(dst, dstid, src, srcid, path) + if (not IsValid(dst) or not HasPorts(dst) or not dst.Inputs or not dst.Inputs[dstid]) then + Msg("Wire_link: Invalid destination!\n") + return + end + if (not IsValid(src) or not HasPorts(src) or not src.Outputs or not src.Outputs[srcid]) then + Msg("Wire_link: Invalid source!\n") + return + end + + local input = dst.Inputs[dstid] + local output = src.Outputs[srcid] + + if (IsValid(input.Src)) then + if (input.Src.Outputs) then + local oldOutput = input.Src.Outputs[input.SrcId] + if (oldOutput) then + for k,v in ipairs(oldOutput.Connected) do + if (v.Entity == dst) and (v.Name == dstid) then + table.remove(oldOutput.Connected, k) + end + end + end + end + end + + input.Src = src + input.SrcId = srcid + input.Path = path + + WireLib.Paths.Add(input) + WireLib._SetLink(input) + + table.insert(output.Connected, { Entity = dst, Name = dstid }) + + if dst.OnInputWireLink then + -- ENT:OnInputWireLink(iName, iType, oEnt, oName, oType) + dst:OnInputWireLink(dstid, input.Type, src, srcid, output.Type) + end + + if src.OnOutputWireLink then + -- ENT:OnOutputWireLink(oName, oType, iEnt, iName, iType) + src:OnOutputWireLink(srcid, output.Type, dst, dstid, input.Type) + end + + WireLib.TriggerInput(dst, dstid, output.Value) +end + +function WireLib.TriggerOutput(ent, oname, value, iter) + if not IsValid(ent) then return end + if not HasPorts(ent) then return end + if (not ent.Outputs) then return end + + local output = ent.Outputs[oname] + if (output) and (value ~= output.Value or output.Type == "ARRAY" or output.Type == "TABLE") then + local timeOfFrame = CurTime() + if timeOfFrame ~= output.TriggerTime then + -- Reset the TriggerLimit every frame + output.TriggerLimit = 4 + output.TriggerTime = timeOfFrame + elseif output.TriggerLimit <= 0 then + return + end + output.TriggerLimit = output.TriggerLimit - 1 + + output.Value = value + + if (iter) then + for _,dst in ipairs(output.Connected) do + if (IsValid(dst.Entity)) then + iter:Add(dst.Entity, dst.Name, value) + end + end + return + end + + iter = WireLib.CreateOutputIterator() + + for _,dst in ipairs(output.Connected) do + if (IsValid(dst.Entity)) then + WireLib.TriggerInput(dst.Entity, dst.Name, value, iter) + end + end + + iter:Process() + + end +end + +local function Wire_Unlink(ent, iname, DontSendToCL) + if not HasPorts(ent) then return end + + local input = ent.Inputs[iname] + if (input) then + if (IsValid(input.Src)) then + local outputs = input.Src.Outputs or {} + local output = outputs[input.SrcId] + if (output) then + for k,v in ipairs(output.Connected) do + if (v.Entity == ent) and (v.Name == iname) then + table.remove(output.Connected, k) + end + end + -- untested + if input.Src.OnOutputWireLink then + -- ENT:OnOutputWireLink(oName, oType, iEnt, iName, iType) + input.Src:OnOutputWireLink(input.SrcId, outputs[input.SrcId].Type, ent, iname, input.Type) + end + end + -- untested + if ent.OnInputWireUnlink then + -- ENT:OnInputWireUnlink(iName, iType, oEnt, oName, oType) + ent:OnInputWireUnlink(iname, input.Type, input.Src, input.SrcId, outputs[input.SrcId].Type) + end + end + + input.Src = nil + input.SrcId = nil + input.Path = nil + + WireLib.TriggerInput(ent, iname, WireLib.DT[input.Type].Zero, nil) + + if (DontSendToCL) then return end + WireLib._SetLink(input) + end +end + +function WireLib.Link_Start(idx, ent, pos, iname, material, color, width) + if not IsValid(ent) then return end + if not HasPorts(ent) then return end + if (not ent.Inputs or not ent.Inputs[iname]) then return end + + local input = ent.Inputs[iname] + + if not input.Path then input.Path = {} end + + CurLink[idx] = { + Dst = ent, + DstId = iname, + Path = input.Path, + OldPath = {} + } + for i=1, #input.Path do + CurLink[idx].OldPath[i] = input.Path[i] + input.Path[i] = nil + end + + input.StartPos = pos + input.Material = material + input.Color = color + input.Width = math_clamp(width, 0, 5) + + return true +end + + +function WireLib.Link_Node(idx, ent, pos) + if not CurLink[idx] then return end + if not IsValid(CurLink[idx].Dst) then return end + if not IsValid(ent) then return end -- its the world, give up + + table.insert(CurLink[idx].Path, { Entity = ent, Pos = pos }) + WireLib.Paths.Add(CurLink[idx].Dst.Inputs[CurLink[idx].DstId]) +end + + +function WireLib.Link_End(idx, ent, pos, oname, pl) + if not CurLink[idx] then return end + + if not IsValid(CurLink[idx].Dst) then return end + if not HasPorts(CurLink[idx].Dst) then return end + + if not IsValid(ent) then return end + if not HasPorts(ent) then return end + if not ent.Outputs then return end + + if (CurLink[idx].Dst:GetClass() == "gmod_wire_sensor") and (ent:GetClass() ~= "gmod_wire_target_finder") then + MsgN("Wire_link: Beacon Sensor can only be wired to a Target Finder!") + if pl then + WireLib.AddNotify(pl, "Beacon Sensor can only be wired to a Target Finder!", NOTIFY_GENERIC, 7) + end + WireLib.Link_Cancel(idx) + return + end + + local input = CurLink[idx].Dst.Inputs[CurLink[idx].DstId] + local output = ent.Outputs[oname] + if not output then + --output = { Type = "NORMAL" } + local text = "Selected output not found or no output present." + MsgN(text) + if pl then WireLib.AddNotify(pl, text, NOTIFY_GENERIC, 7) end + WireLib.Link_Cancel(idx) + return + end + --Msg("input type= " .. input.Type .. " output type= " .. (output.Type or "NIL") .. "\n") -- I bet that was getting anoying (TAD2020) + if (input.Type ~= output.Type) and (input.Type ~= "ANY") and (output.Type ~= "ANY") then + local text = "Data Type Mismatch! Input takes "..input.Type.." and Output gives "..output.Type + MsgN(text) + if pl then WireLib.AddNotify(pl, text, NOTIFY_GENERIC, 7) end + WireLib.Link_Cancel(idx) + return + end + + table.insert(CurLink[idx].Path, { Entity = ent, Pos = pos }) + Wire_Link(CurLink[idx].Dst, CurLink[idx].DstId, ent, oname, CurLink[idx].Path) + + if (WireLib.DT[input.Type].BiDir) then + Wire_Link(ent, oname, CurLink[idx].Dst, CurLink[idx].DstId, {}) + end + + CurLink[idx] = nil +end + + +function WireLib.Link_Cancel(idx) + if not CurLink[idx] then return end + if not IsValid(CurLink[idx].Dst) then return end + + if CurLink[idx].input then + CurLink[idx].Path = CurLink[idx].input.Path + else + WireLib.Paths.Add({Entity = CurLink[idx].Dst, Name = CurLink[idx].DstId, Width = 0}) + end + CurLink[idx] = nil +end + + +function WireLib.Link_Clear(ent, iname, DontSendToCL) + WireLib.Paths.Add({Entity = ent, Name = iname, Width = 0}) + Wire_Unlink(ent, iname, DontSendToCL) +end + +function WireLib.WireAll(ply, ient, oent, ipos, opos, material, color, width) + if not IsValid(ient) or not IsValid(oent) or not ient.Inputs or not oent.Outputs then return false end + + for iname, _ in pairs(ient.Inputs) do + if oent.Outputs[iname] then + WireLib.Link_Start(ply:UniqueID(), ient, ipos, iname, material or "arrowire/arrowire2", color or Color(255,255,255), width or 0) + WireLib.Link_End(ply:UniqueID(), oent, opos, iname, ply) + end + end +end + +do -- class OutputIterator + local OutputIterator = {} + OutputIterator.__index = OutputIterator + + function OutputIterator:Add(ent, iname, value) + table.insert(self, { Entity = ent, IName = iname, Value = value }) + end + + function OutputIterator:Process() + if self.Processing then return end -- should not occur + self.Processing = true + + while #self > 0 do + local nextelement = self[1] + table.remove(self, 1) + + WireLib.TriggerInput(nextelement.Entity, nextelement.IName, nextelement.Value, self) + end + + self.Processing = nil + end + + function WireLib.CreateOutputIterator() + return setmetatable({}, OutputIterator) + end +end -- class OutputIterator + + +duplicator.RegisterEntityModifier("WireDupeInfo", function(ply, Ent, DupeInfo) + -- this does nothing for now, we need the blank function to get the duplicator to copy the WireDupeInfo into the pasted ent +end) + + +-- used for welding wired stuff, if trace is world, the ent is not welded and is frozen instead +function WireLib.Weld(ent, traceEntity, tracePhysicsBone, DOR, collision, AllowWorldWeld) + if (not ent or not traceEntity or traceEntity:IsNPC() or traceEntity:IsPlayer()) then return end + local phys = ent:GetPhysicsObject() + if ( traceEntity:IsValid() ) or ( traceEntity:IsWorld() and AllowWorldWeld ) then + local const = constraint.Weld( ent, traceEntity, 0, tracePhysicsBone, 0, (not collision), DOR ) + -- Don't disable collision if it's not attached to anything + if (not collision) then + if phys:IsValid() then phys:EnableCollisions( false ) end + ent.nocollide = true + end + return const + else + if phys:IsValid() then ent:GetPhysicsObject():EnableMotion( false ) end + return nil + end +end + + +function WireLib.BuildDupeInfo( Ent ) + if not Ent.Inputs then return {} end + + local info = { Wires = {} } + for portname,input in pairs(Ent.Inputs) do + if (IsValid(input.Src)) then + info.Wires[portname] = { + StartPos = input.StartPos, + Material = input.Material, + Color = input.Color, + Width = input.Width, + Src = input.Src:EntIndex(), + SrcId = input.SrcId, + SrcPos = Vector(0, 0, 0), + } + + if (input.Path) then + info.Wires[portname].Path = {} + + for _,v in ipairs(input.Path) do + if (IsValid(v.Entity)) then + table.insert(info.Wires[portname].Path, { Entity = v.Entity:EntIndex(), Pos = v.Pos }) + end + end + + local n = #info.Wires[portname].Path + if (n > 0) and (info.Wires[portname].Path[n].Entity == info.Wires[portname].Src) then + info.Wires[portname].SrcPos = info.Wires[portname].Path[n].Pos + table.remove(info.Wires[portname].Path, n) + end + end + end + end + + return info +end + +function WireLib.ApplyDupeInfo( ply, ent, info, GetEntByID ) + if info.extended and not ent.extended then + WireLib.CreateWirelinkOutput( ply, ent, {true} ) -- old dupe compatibility; use the new function + end + + local idx = 0 + if IsValid(ply) then idx = ply:UniqueID() end -- Map Save loading does not have a ply + if (info.Wires) then + for k,input in pairs(info.Wires) do + local ent2 = GetEntByID(input.Src) + + -- Input alias + if ent.Inputs and not ent.Inputs[k] then -- if the entity has any inputs and the input 'k' is not one of them... + if ent.InputAliases and ent.InputAliases[k] then + k = ent.InputAliases[k] + else + Msg("ApplyDupeInfo: Error, Could not find input '" .. k .. "' on entity type: '" .. ent:GetClass() .. "'\n") + continue + end + end + + if IsValid( ent2 ) then + -- Wirelink and entity outputs + + -- These are required if whichever duplicator you're using does not do entity modifiers before it runs PostEntityPaste + -- because if so, the wirelink and entity outputs may not have been created yet + + if input.SrcId == "link" or input.SrcId == "wirelink" then -- If the target entity has no wirelink output, create one (& more old dupe compatibility) + input.SrcId = "wirelink" + if not ent2.extended then + WireLib.CreateWirelinkOutput( ply, ent2, {true} ) + end + elseif input.SrcId == "entity" and ((ent2.Outputs and not ent2.Outputs.entity) or not ent2.Outputs) then -- if the input name is 'entity', and the target entity doesn't have that output... + WireLib.CreateEntityOutput( ply, ent2, {true} ) + end + + -- Output alias + if ent2.Outputs and not ent2.Outputs[input.SrcId] then -- if the target entity has any outputs and the output 'input.SrcId' is not one of them... + if ent2.OutputAliases and ent2.OutputAliases[input.SrcId] then + input.SrcId = ent2.OutputAliases[input.SrcId] + else + Msg("ApplyDupeInfo: Error, Could not find output '" .. input.SrcId .. "' on entity type: '" .. ent2:GetClass() .. "'\n") + continue + end + end + end + + WireLib.Link_Start(idx, ent, input.StartPos, k, input.Material, input.Color, input.Width) + + if input.Path then + for _,v in ipairs(input.Path) do + local ent2 = GetEntByID(v.Entity) + if IsValid(ent2) then + WireLib.Link_Node(idx, ent2, v.Pos) + else + Msg("ApplyDupeInfo: Error, Could not find the entity for wire path\n") + end + end + end + + if IsValid(ent2) then + WireLib.Link_End(idx, ent2, input.SrcPos, input.SrcId) + else + Msg("ApplyDupeInfo: Error, Could not find the output entity\n") + end + end + end +end + +function WireLib.RefreshSpecialOutputs(ent) + local names = {} + local types = {} + local descs = {} + + if ent.Outputs then + for _,output in pairs(ent.Outputs) do + local index = output.Num + names[index] = output.Name + types[index] = output.Type + descs[index] = output.Desc + end + + ent.Outputs = WireLib.AdjustSpecialOutputs(ent, names, types, descs) + else + ent.Outputs = WireLib.CreateSpecialOutputs(ent, names, types, descs) + end + + WireLib.TriggerOutput(ent, "link", ent) +end + +function WireLib.CreateInputs(ent, names, descs) + return WireLib.CreateSpecialInputs(ent, names, {}, descs) +end + + +function WireLib.CreateOutputs(ent, names, descs) + return WireLib.CreateSpecialOutputs(ent, names, {}, descs) +end + + +function WireLib.AdjustInputs(ent, names, descs) + return WireLib.AdjustSpecialInputs(ent, names, {}, descs) +end + + +function WireLib.AdjustOutputs(ent, names, descs) + return WireLib.AdjustSpecialOutputs(ent, names, {}, descs) +end + +-- Backwards compatibility +Wire_CreateInputs = WireLib.CreateInputs +Wire_CreateOutputs = WireLib.CreateOutputs +Wire_AdjustInputs = WireLib.AdjustInputs +Wire_AdjustOutputs = WireLib.AdjustOutputs +Wire_Restored = WireLib.Restored +Wire_Remove = WireLib.Remove +Wire_TriggerOutput = WireLib.TriggerOutput +Wire_Link_Start = WireLib.Link_Start +Wire_Link_Node = WireLib.Link_Node +Wire_Link_End = WireLib.Link_End +Wire_Link_Cancel = WireLib.Link_Cancel +Wire_Link_Clear = WireLib.Link_Clear +Wire_CreateOutputIterator = WireLib.CreateOutputIterator +Wire_BuildDupeInfo = WireLib.BuildDupeInfo +Wire_ApplyDupeInfo = WireLib.ApplyDupeInfo + +-- prevent applyForce+Anti-noclip-based killing contraptions +hook.Add("InitPostEntity", "antiantinoclip", function() + local ENT = scripted_ents.GetList().rt_antinoclip_handler + if not ENT then return end + ENT = ENT.t + + local rt_antinoclip_handler_StartTouch = ENT.StartTouch + function ENT:StartTouch(...) + if self.speed >= 20 then return end + + local phys = self.Ent:GetPhysicsObject() + if phys:IsValid() and phys:GetAngleVelocity():Length() > 20 then return end + + rt_antinoclip_handler_StartTouch(self, ...) + end + + --local rt_antinoclip_handler_Think = ENT.Think + function ENT:Think() + + local t = CurTime() + local dt = t-self.lastt + self.lastt = t + + local phys = self.Ent:GetPhysicsObject() + local pos + if phys:IsValid() then + pos = phys:LocalToWorld(phys:GetMassCenter()) + else + pos = self.Ent:GetPos() + end + self.speed = pos:Distance(self.oldpos)/dt + self.oldpos = pos + --rt_antinoclip_handler_Think(self, ...) + end + + ENT.speed = 20 + ENT.lastt = 0 + ENT.oldpos = Vector(0,0,0) +end) + +function WireLib.GetOwner(ent) + return E2Lib.getOwner({}, ent) +end + +function WireLib.NumModelSkins(model) + if NumModelSkins then + return NumModelSkins(model) + end + local info = util.GetModelInfo(model) + return info and info.SkinCount +end + +--- @return whether the given player can spawn an object with the given model and skin +function WireLib.CanModel(player, model, skin) + if not util.IsValidModel(model) then return false end + if skin ~= nil then + local count = WireLib.NumModelSkins(model) + if skin < 0 or (count and skin >= count) then return false end + end + if IsValid(player) and player:IsPlayer() and not hook.Run("PlayerSpawnObject", player, model, skin) then return false end + return true +end + +function WireLib.MakeWireEnt( pl, Data, ... ) + Data.Class = scripted_ents.Get(Data.Class).ClassName + if IsValid(pl) and not pl:CheckLimit(Data.Class:sub(6).."s") then return false end + if Data.Model and not WireLib.CanModel(pl, Data.Model, Data.Skin) then return false end + + local ent = ents.Create( Data.Class ) + if not IsValid(ent) then return false end + + duplicator.DoGeneric( ent, Data ) + ent:Spawn() + ent:Activate() + duplicator.DoGenericPhysics( ent, pl, Data ) -- Is deprecated, but is the only way to access duplicator.EntityPhysics.Load (its local) + + ent:SetPlayer(pl) + if ent.Setup then ent:Setup(...) end + + if IsValid(pl) then pl:AddCount( Data.Class:sub(6).."s", ent ) end + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + if Data.frozen then phys:EnableMotion(false) end + if Data.nocollide then phys:EnableCollisions(false) end + end + + return ent +end + +-- Adds an input alias so that we can rename inputs on entities without breaking old dupes +-- Usage: WireLib.AddInputAlias( old, new ) works if used in the entity's file +-- or WireLib.AddInputAlias( class, old, new ) if used elsewhere +-- or WireLib.AddInputAlias( entity, old, new ) for a specific entity +function WireLib.AddInputAlias( class, old, new ) + if not new then + new = old + old = class + class = nil + end + + local ENT_table + + if not class and ENT then + ENT_table = ENT + elseif isstring( class ) then + ENT_table = scripted_ents.GetStored( class ) + elseif isentity( class ) and IsValid( class ) then + ENT_table = class + else + error( "Invalid class or entity specified" ) + return + end + + if not ENT_table.InputAliases then ENT_table.InputAliases = {} end + ENT_table.InputAliases[old] = new +end + +-- Adds an output alias so that we can rename outputs on entities without breaking old dupes +-- Usage: WireLib.AddOutputAlias( old, new ) works if used in the entity's file +-- or WireLib.AddOutputAlias( class, old, new ) if used elsewhere +-- or WireLib.AddOutputAlias( entity, old, new ) for a specific entity +function WireLib.AddOutputAlias( class, old, new ) + if not new then + new = old + old = class + class = nil + end + + local ENT_table + + if not class and ENT then + ENT_table = ENT + elseif isstring( class ) then + ENT_table = scripted_ents.GetStored( class ) + elseif isentity( class ) and IsValid( class ) then + ENT_table = class + else + error( "Invalid class or entity specified" ) + return + end + + if not ENT_table.OutputAliases then ENT_table.OutputAliases = {} end + ENT_table.OutputAliases[old] = new +end + +local function effectiveMass(ent) + if not isentity(ent) then return 1 end + if ent:IsWorld() then return 99999 end + if not IsValid(ent) or not IsValid(ent:GetPhysicsObject()) then return 1 end + return ent:GetPhysicsObject():GetMass() +end + +function WireLib.CalcElasticConsts(Ent1, Ent2) + local minMass = math.min(effectiveMass(Ent1), effectiveMass(Ent2)) + local const = minMass * 100 + local damp = minMass * 20 + + return const, damp +end + + +-- Returns a string like "Git f3a4ac3" or "SVN 2703" or "Workshop" or "Extracted" +-- The partial git hash can be plugged into https://github.com/wiremod/wire/commit/f3a4ac3 to show the actual commit +local cachedversion +function WireLib.GetVersion() + -- If we've already found our version just return that again + if cachedversion then return cachedversion end + + -- Find what our legacy folder is called + local wirefolder = "addons/wire" + if not file.Exists(wirefolder, "GAME") then + for k, folder in pairs(({file.Find("addons/*", "GAME")})[2]) do + if folder:find("wire") and not folder:find("extra") then + wirefolder = "addons/"..folder + break + end + end + end + + if file.Exists(wirefolder, "GAME") then + if file.Exists(wirefolder.."/.git", "GAME") then + cachedversion = "Git "..(file.Read(wirefolder.."/.git/refs/heads/master", "GAME") or "Unknown"):sub(1,7) + elseif file.Exists(wirefolder.."/.svn", "GAME") then + -- Note: This method will likely only detect TortoiseSVN installs + local wcdb = file.Read(wirefolder.."/.svn/wc.db", "GAME") or "" + local start = wcdb:find("/wiremod/wire/!svn/ver/%d+/branches%)") + if start then + cachedversion = "SVN "..wcdb:sub(start+23, start+26) + else + cachedversion = "SVN Unknown" + end + else + cachedversion = "Extracted" + end + end + + -- Check if we're Workshop version first + for k, addon in pairs(engine.GetAddons()) do + if addon.wsid == "160250458" then + cachedversion = "Workshop" + return cachedversion + end + end + + if not cachedversion then cachedversion = "Unknown" end + + return cachedversion +end +concommand.Add("wireversion", function(ply,cmd,args) + local text = "Wiremod's version: '"..WireLib.GetVersion().."'" + if IsValid(ply) then + ply:ChatPrint(text) + else + print(text) + end +end, nil, "Prints the server's Wiremod version") + + +local material_blacklist = { + ["engine/writez"] = true, + ["pp/copy"] = true, + ["effects/ar2_altfire1"] = true +} +function WireLib.IsValidMaterial(material) + material = string.sub(material, 1, 260) + local path = string.StripExtension(string.GetNormalizedFilepath(string.lower(material))) + if material_blacklist[path] then return "" end + return material +end + +function WireLib.SetColor(ent, color) + color.r = math_clamp(color.r, 0, 255) + color.g = math_clamp(color.g, 0, 255) + color.b = math_clamp(color.b, 0, 255) + color.a = ent:IsPlayer() and ent:GetColor().a or math_clamp(color.a, 0, 255) + + local rendermode = ent:GetRenderMode() + if rendermode == RENDERMODE_NORMAL or rendermode == RENDERMODE_TRANSALPHA then + rendermode = color.a == 255 and RENDERMODE_NORMAL or RENDERMODE_TRANSALPHA + ent:SetRenderMode(rendermode) + else + rendermode = nil -- Don't modify the current stored modifier + end + + ent:SetColor(color) + duplicator.StoreEntityModifier(ent, "colour", { Color = color, RenderMode = rendermode }) +end + +if not WireLib.PatchedDuplicator then + WireLib.PatchedDuplicator = true + + local localPos + + local oldSetLocalPos = duplicator.SetLocalPos + function duplicator.SetLocalPos(pos, ...) + localPos = pos + return oldSetLocalPos(pos, ...) + end + + local oldPaste = duplicator.Paste + function duplicator.Paste(player, entityList, constraintList, ...) + local result = { oldPaste(player, entityList, constraintList, ...) } + local createdEntities, createdConstraints = result[1], result[2] + local data = { + EntityList = entityList, ConstraintList = constraintList, + CreatedEntities = createdEntities, CreatedConstraints = createdConstraints, + Player = player, HitPos = localPos, + } + hook.Run("AdvDupe_FinishPasting", {data}, 1) + return unpack(result) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/7seg.lua b/garrysmod/addons/feature-wire/lua/wire/stools/7seg.lua new file mode 100644 index 0000000..8229549 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/7seg.lua @@ -0,0 +1,112 @@ +WireToolSetup.setCategory( "Visuals/Indicators" ) +WireToolSetup.open( "7seg", "7 Segment Display", "gmod_wire_indicator", nil, "7 Segment Displays" ) + +TOOL.GhostAngle = Angle(90, 0, 0) +TOOL.GhostMin = "x" + +if CLIENT then + language.Add( "tool.wire_7seg.name", "7-Segment Display Tool" ) + language.Add( "tool.wire_7seg.desc", "Spawns 7 indicators for numeric display with the wire system." ) + language.Add( "ToolWire7Seg_a_colour", "Off Colour:" ) + language.Add( "ToolWire7Seg_b_colour", "On Colour:" ) + language.Add( "ToolWire7SegTool_worldweld", "Allow weld to world" ) + language.Add( "undone_wire7seg", "Undone 7-Segment Display" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/lightbulb_add.png" ) +end + +WireToolSetup.BaseLang() + +-- define MaxLimitName cause this tool just uses gmod_wire_indicators +TOOL.MaxLimitName = "wire_indicators" + +if SERVER then + function TOOL:GetConVars() + return 0, + math.Clamp(self:GetClientNumber("ar"),0,255), + math.Clamp(self:GetClientNumber("ag"),0,255), + math.Clamp(self:GetClientNumber("ab"),0,255), + math.Clamp(self:GetClientNumber("aa"),0,255), + 1, + math.Clamp(self:GetClientNumber("br"),0,255), + math.Clamp(self:GetClientNumber("bg"),0,255), + math.Clamp(self:GetClientNumber("bb"),0,255), + math.Clamp(self:GetClientNumber("ba"),0,255) + end + + function TOOL:MakeEnt( ply, model, Ang, trace ) + return MakeWire7Seg( ply, trace.HitPos, Ang, model, self:GetConVars() ) + end +end + +TOOL.ClientConVar = { + model = "models/segment.mdl", + ar = 70, --default: dark grey off, full red on + ag = 70, + ab = 70, + aa = 255, + br = 255, + bg = 0, + bb = 0, + ba = 255, + worldweld = 1, +} + +function TOOL:PostMake_SetPos() end + +function TOOL:LeftClick_PostMake( wire_indicators, ply, trace ) + if not istable(wire_indicators) then return end + local worldweld = self:GetClientNumber("worldweld") == 1 + undo.Create("Wire7Seg") + for x=1, 7 do + --make welds + local const = WireLib.Weld(wire_indicators[x], trace.Entity, trace.PhysicsBone, true, false, worldweld) + undo.AddEntity( wire_indicators[x] ) + undo.AddEntity( const ) + ply:AddCleanup( "wire_indicators", wire_indicators[x] ) + ply:AddCleanup( "wire_indicators", const) + end + undo.SetPlayer( ply ) + undo.Finish() + return true +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_7seg") + + panel:AddControl("Color", { + Label = "#ToolWire7Seg_a_colour", + Red = "wire_7seg_ar", + Green = "wire_7seg_ag", + Blue = "wire_7seg_ab", + Alpha = "wire_7seg_aa", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + panel:AddControl("Color", { + Label = "#ToolWire7Seg_b_colour", + Red = "wire_7seg_br", + Green = "wire_7seg_bg", + Blue = "wire_7seg_bb", + Alpha = "wire_7seg_ba", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + panel:AddControl("ComboBox", { + Label = "#wire_model", + Options = { + ["Huge 7-seg bar"] = { wire_7seg_model = "models/segment2.mdl" }, + ["Normal 7-seg bar"] = { wire_7seg_model = "models/segment.mdl" }, + ["Small 7-seg bar"] = { wire_7seg_model = "models/segment3.mdl" }, + } + }) + + panel:CheckBox("#ToolWire7SegTool_worldweld", "wire_7seg_worldweld") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/addressbus.lua b/garrysmod/addons/feature-wire/lua/wire/stools/addressbus.lua new file mode 100644 index 0000000..cb82838 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/addressbus.lua @@ -0,0 +1,62 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "addressbus", "Data - Address Bus", "gmod_wire_addressbus", nil, "Address Buses" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_addressbus.name", "Address bus tool (Wire)" ) + language.Add( "Tool.wire_addressbus.desc", "Spawns an address bus. Address spaces may overlap!" ) + TOOL.Information = { { name = "left", text = "Create/Update address bus" } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_gate.mdl" +TOOL.ClientConVar[ "addrspace1sz" ] = 0 +TOOL.ClientConVar[ "addrspace2sz" ] = 0 +TOOL.ClientConVar[ "addrspace3sz" ] = 0 +TOOL.ClientConVar[ "addrspace4sz" ] = 0 +TOOL.ClientConVar[ "addrspace1st" ] = 0 +TOOL.ClientConVar[ "addrspace2st" ] = 0 +TOOL.ClientConVar[ "addrspace3st" ] = 0 +TOOL.ClientConVar[ "addrspace4st" ] = 0 + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "addrspace1st" ), self:GetClientNumber( "addrspace2st" ), self:GetClientNumber( "addrspace3st" ), self:GetClientNumber( "addrspace4st" ), + self:GetClientNumber( "addrspace1sz" ), self:GetClientNumber( "addrspace2sz" ), self:GetClientNumber( "addrspace3sz" ), self:GetClientNumber( "addrspace4sz" ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL:RightClick( trace ) + if trace.Entity:IsPlayer() then return false end + if (CLIENT) then return true end + + local ply = self:GetOwner() + + if ( trace.Entity:IsValid() && trace.Entity:GetClass() == "gmod_wire_addressbus" ) then + ply:ConCommand("wire_addressbus_addrspace1sz "..(trace.Entity.MemEnd[1]-trace.Entity.MemStart[1]+1)) + ply:ConCommand("wire_addressbus_addrspace1st "..(trace.Entity.MemStart[1])) + ply:ConCommand("wire_addressbus_addrspace2sz "..(trace.Entity.MemEnd[2]-trace.Entity.MemStart[2]+1)) + ply:ConCommand("wire_addressbus_addrspace2st "..(trace.Entity.MemStart[2])) + ply:ConCommand("wire_addressbus_addrspace3sz "..(trace.Entity.MemEnd[3]-trace.Entity.MemStart[3]+1)) + ply:ConCommand("wire_addressbus_addrspace3st "..(trace.Entity.MemStart[3])) + ply:ConCommand("wire_addressbus_addrspace4sz "..(trace.Entity.MemEnd[4]-trace.Entity.MemStart[4]+1)) + ply:ConCommand("wire_addressbus_addrspace4st "..(trace.Entity.MemStart[4])) + end + return true +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_addressbus") + ModelPlug_AddToCPanel(panel, "gate", "wire_addressbus", nil, 4) + + panel:NumSlider("1 offset", "wire_addressbus_addrspace1st", 0, 16777216, 0) + panel:NumSlider("1 size", "wire_addressbus_addrspace1sz", 0, 16777216, 0) + panel:NumSlider("2 offset", "wire_addressbus_addrspace2st", 0, 16777216, 0) + panel:NumSlider("2 size", "wire_addressbus_addrspace2sz", 0, 16777216, 0) + panel:NumSlider("3 offset", "wire_addressbus_addrspace3st", 0, 16777216, 0) + panel:NumSlider("3 size", "wire_addressbus_addrspace3sz", 0, 16777216, 0) + panel:NumSlider("4 offset", "wire_addressbus_addrspace4st", 0, 16777216, 0) + panel:NumSlider("4 size", "wire_addressbus_addrspace4sz", 0, 16777216, 0) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/adv_emarker.lua b/garrysmod/addons/feature-wire/lua/wire/stools/adv_emarker.lua new file mode 100644 index 0000000..2c1a002 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/adv_emarker.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "adv_emarker", "Adv Entity Marker", "gmod_wire_adv_emarker", nil, "Adv Entity Markers" ) + +if CLIENT then + language.Add( "Tool.wire_adv_emarker.name", "Adv Entity Marker Tool (Wire)" ) + language.Add( "Tool.wire_adv_emarker.desc", "Spawns an Adv Entity Marker for use with the wire system." ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", +} + +-- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function + +WireToolSetup.SetupLinking() -- Generates RightClick, Reload, and DrawHUD functions + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_adv_emarker") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/adv_input.lua b/garrysmod/addons/feature-wire/lua/wire/stools/adv_input.lua new file mode 100644 index 0000000..111b760 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/adv_input.lua @@ -0,0 +1,53 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction" ) +WireToolSetup.open( "adv_input", "Adv. Input", "gmod_wire_adv_input", nil, "Adv. Inputs" ) + +if CLIENT then + language.Add( "tool.wire_adv_input.name", "Adv. Input Tool (Wire)" ) + language.Add( "tool.wire_adv_input.desc", "Spawns a adv. input for use with the wire system." ) + language.Add( "WireAdvInputTool_keymore", "Increase:" ) + language.Add( "WireAdvInputTool_keyless", "Decrease:" ) + language.Add( "WireAdvInputTool_toggle", "Toggle" ) + language.Add( "WireAdvInputTool_value_min", "Minimum:" ) + language.Add( "WireAdvInputTool_value_max", "Maximum:" ) + language.Add( "WireAdvInputTool_value_start", "Start at:" ) + language.Add( "WireAdvInputTool_speed", "Change per second:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("Numpad") + + function TOOL:GetConVars() + return self:GetClientNumber( "keymore" ), self:GetClientNumber( "keyless" ), self:GetClientNumber( "toggle" ), + self:GetClientNumber( "value_min" ), self:GetClientNumber( "value_max" ), self:GetClientNumber( "value_start" ), + self:GetClientNumber( "speed" ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/numpad.mdl", + modelsize = "", + keymore = "3", + keyless = "1", + toggle = "0", + value_min = "0", + value_max = "10", + value_start = "5", + speed = "1", +} + +function TOOL.BuildCPanel( panel ) + WireToolHelpers.MakeModelSizer(panel, "wire_adv_input_modelsize") + ModelPlug_AddToCPanel(panel, "Numpad", "wire_adv_input", true) + panel:AddControl( "Numpad", {Label = "#WireAdvInputTool_keymore", Command = "wire_adv_input_keymore"}) + panel:AddControl( "Numpad", {Label = "#WireAdvInputTool_keyless", Command = "wire_adv_input_keyless"}) + panel:CheckBox("#WireAdvInputTool_toggle", "wire_adv_input_toggle") + panel:NumSlider("#WireAdvInputTool_value_min", "wire_adv_input_value_min", -50, 50, 0) + panel:NumSlider("#WireAdvInputTool_value_max", "wire_adv_input_value_max", -50, 50, 0) + panel:NumSlider("#WireAdvInputTool_value_start", "wire_adv_input_value_start", -50, 50, 0) + panel:NumSlider("#WireAdvInputTool_speed", "wire_adv_input_speed", 0.1, 50, 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/button.lua b/garrysmod/addons/feature-wire/lua/wire/stools/button.lua new file mode 100644 index 0000000..29b8d69 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/button.lua @@ -0,0 +1,51 @@ +WireToolSetup.setCategory( "Input, Output" ) +WireToolSetup.open( "button", "Button", "gmod_wire_button", nil, "Buttons" ) + +if CLIENT then + language.Add( "tool.wire_button.name", "Button Tool (Wire)" ) + language.Add( "tool.wire_button.desc", "Spawns a button for use with the wire system." ) + language.Add( "WireButtonTool_toggle", "Toggle" ) + language.Add( "WireButtonTool_entityout", "Output Entity" ) + language.Add( "WireButtonTool_value_on", "Value On:" ) + language.Add( "WireButtonTool_value_off", "Value Off:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("button") + + function TOOL:GetConVars() + return self:GetClientNumber( "toggle" ) ~= 0, self:GetClientNumber( "value_off" ), self:GetClientNumber( "value_on" ), + self:GetClientInfo( "description" ), self:GetClientNumber( "entityout" ) ~= 0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/props_c17/clock01.mdl", + model_category = "button", + toggle = "0", + value_off = "0", + value_on = "1", + description = "", + entityout = "0" +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_button") + + ModelPlug_AddToCPanel_Multi( + panel, + { button = "Normal", + button_small = "Small" + }, + "wire_button", "#Button_Model", 6 + ) + panel:CheckBox("#WireButtonTool_toggle", "wire_button_toggle") + panel:CheckBox("#WireButtonTool_entityout", "wire_button_entityout") + panel:NumSlider("#WireButtonTool_value_on", "wire_button_value_on", -10, 10, 1) + panel:NumSlider("#WireButtonTool_value_off", "wire_button_value_off", -10, 10, 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/cam.lua b/garrysmod/addons/feature-wire/lua/wire/stools/cam.lua new file mode 100644 index 0000000..0b288b4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/cam.lua @@ -0,0 +1,75 @@ +WireToolSetup.setCategory( "Vehicle Control", "Visuals" ) +WireToolSetup.open( "cam", "Cam Controller", "gmod_wire_cameracontroller", nil, "Cam Controllers" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_cam.name", "Cam Controller Tool (Wire)" ) + language.Add( "Tool.wire_cam.desc", "Spawns a constant Cam Controller prop for use with the wire system." ) + language.Add( "Tool.wire_cam.parentlocal", "Coordinates local to parent" ) + language.Add( "Tool.wire_cam.freemove", "Free movement" ) + language.Add( "Tool.wire_cam.automove", "Client side movement" ) + language.Add( "Tool.wire_cam.localmove", "Localized movement" ) + language.Add( "Tool.wire_cam.allowzoom", "Client side zooming" ) + language.Add( "Tool.wire_cam.autounclip", "Auto un-clip" ) + language.Add( "Tool.wire_cam.autounclip_ignorewater", "Auto un-clip ignores water" ) + language.Add( "Tool.wire_cam.drawplayer", "Draw player" ) + language.Add( "Tool.wire_cam.drawparent", "Draw parent" ) + language.Add( "Tool.wire_cam.smooth_amount", "Smooth speed (default: 18)" ) + + WireToolSetup.setToolMenuIcon( "icon16/camera.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "parentlocal" ), + self:GetClientNumber( "automove" ), + self:GetClientNumber( "freemove" ), + self:GetClientNumber( "localmove" ), + self:GetClientNumber( "allowzoom" ), + self:GetClientNumber( "autounclip" ), + self:GetClientNumber( "drawplayer" ), + self:GetClientNumber( "autounclip_ignorewater" ), + self:GetClientNumber( "drawparent" ) + end +end + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" +TOOL.ClientConVar[ "parentlocal" ] = "0" +TOOL.ClientConVar[ "automove" ] = "0" +TOOL.ClientConVar[ "freemove" ] = "0" +TOOL.ClientConVar[ "localmove" ] = "0" +TOOL.ClientConVar[ "allowzoom" ] = "0" +TOOL.ClientConVar[ "autounclip" ] = "0" +TOOL.ClientConVar[ "autounclip_ignorewater" ] = "0" +TOOL.ClientConVar[ "drawplayer" ] = "1" +TOOL.ClientConVar[ "drawparent" ] = "1" +TOOL.ClientConVar[ "smooth_amount" ] = "18" + +WireToolSetup.SetupLinking(false, "pod") + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_cam_model", list.Get( "Wire_Misc_Tools_Models" ), 1) + panel:CheckBox("#Tool.wire_cam.parentlocal", "wire_cam_parentlocal" ) + panel:CheckBox("#Tool.wire_cam.automove", "wire_cam_automove" ) + panel:Help( "Allow the player to rotate the camera using their mouse. When active, the position input becomes the center of the camera's orbit." ) + panel:CheckBox("#Tool.wire_cam.freemove", "wire_cam_freemove" ) + panel:Help( "Modifies mouse input to allow 360 degree rotation. The 'UnRoll' input can be toggled to match the parent entity's roll. (NOTE: only used if 'client side movement' is enabled)" ) + panel:CheckBox("#Tool.wire_cam.localmove", "wire_cam_localmove" ) + panel:Help( "Determines whether the client side movement is local to the parent or not (NOTE: only used if 'client side movement' is enabled)" ) + panel:CheckBox("#Tool.wire_cam.allowzoom", "wire_cam_allowzoom" ) + panel:Help( "Allow the player to move the camera in and out using the scroller on their mouse. The 'Distance' input is used as an offset for this. (NOTE: only used if 'client side movement' is enabled. NOTE: The cam controller's outputs might be wrong when this is enabled, because the server doesn't know how much they've zoomed - it only knows what the 'Distance' input is set to)." ) + panel:CheckBox("#Tool.wire_cam.autounclip", "wire_cam_autounclip" ) + panel:Help( "Automatically prevents the camera from clipping into walls by moving it closer to the parent entity (or cam controller if no parent is specified)." ) + panel:CheckBox("#Tool.wire_cam.autounclip_ignorewater", "wire_cam_autounclip_ignorewater" ) + + panel:CheckBox("#Tool.wire_cam.drawplayer", "wire_cam_drawplayer" ) + panel:Help( "Enable/disable the player being able to see themselves. Useful if you want to position the camera inside the player's head." ) + panel:CheckBox("#Tool.wire_cam.drawparent", "wire_cam_drawparent" ) + panel:Help( "Enable/disable the rendering of the parent entity. Useful if you want to position the camera inside the parent." ) + + panel:Help( "As you may have noticed, there are a lot of behaviours that change depending on which checkboxes are checked. For a detailed walk-through of everything, go to http://wiki.wiremod.com/wiki/Cam_Controller") + + panel:NumSlider( "#Tool.wire_cam.smooth_amount", "wire_cam_smooth_amount", 4, 30, 1 ) + panel:Help( "Smooth speed is a client side setting, and is not saved on the cam controller entity. Changing it will immediately affect all cam controllers you use." ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/cd_disk.lua b/garrysmod/addons/feature-wire/lua/wire/stools/cd_disk.lua new file mode 100644 index 0000000..9e0b713 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/cd_disk.lua @@ -0,0 +1,52 @@ +WireToolSetup.setCategory( "Memory" ) +WireToolSetup.open( "cd_disk", "CD Disk", "gmod_wire_cd_disk", nil, "CD Disks" ) + +if (CLIENT) then + language.Add("Tool.wire_cd_disk.name", "CD Disk Tool (Wire)") + language.Add("Tool.wire_cd_disk.desc", "Spawns a CD Disk.") + language.Add("WireDataTransfererTool_cd_disk", "CD Disk:") + + list.Set( "Wire_Laser_Disk_Models", "models/venompapa/wirecd_small.mdl", true ) + list.Set( "Wire_Laser_Disk_Models", "models/venompapa/wirecd_medium.mdl", true ) + list.Set( "Wire_Laser_Disk_Models", "models/venompapa/wirecd_huge.mdl", true ) + + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Change model" }, + } + + WireToolSetup.setToolMenuIcon( "venompapa/wirecd/wirecd" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + function TOOL:GetConVars() + return self:GetClientNumber( "precision" ), self:GetClientNumber( "iradius" ), self:GetClientNumber( "skin" ) + end +end + +TOOL.ClientConVar["model"] = "models/venompapa/wirecd_medium.mdl" +TOOL.ClientConVar["skin"] = "0" +TOOL.ClientConVar["precision"] = 4 +TOOL.ClientConVar["iradius"] = 10 + +function TOOL:RightClick(trace) + if (CLIENT) then return true end + + if (trace.Entity and trace.Entity:IsValid()) then + if (trace.Entity:GetClass() == "prop_physics") then + self:GetOwner():ConCommand('wire_cd_disk_model "'..trace.Entity:GetModel()..'"\n') + self:GetOwner():ConCommand('wire_cd_disk_skin "'..trace.Entity:GetSkin()..'"\n') + end + end + + return true +end + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_cd_disk_Model", list.Get( "Wire_Laser_Disk_Models" ), 1) + panel:NumSlider("Disk density (inches per block, ipb)","wire_cd_disk_precision",1,16,0) + panel:NumSlider("Inner radius (disk hole radius)","wire_cd_disk_iradius",1,48,0) + panel:NumSlider("Disk skin (0..8, standard disks only)","wire_cd_disk_skin",0,8,0) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/cd_ray.lua b/garrysmod/addons/feature-wire/lua/wire/stools/cd_ray.lua new file mode 100644 index 0000000..c00373c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/cd_ray.lua @@ -0,0 +1,80 @@ +WireToolSetup.setCategory( "Memory" ) +WireToolSetup.open( "cd_ray", "CD Ray", "gmod_wire_cd_ray", nil, "CD Rays" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_cd_ray.name", "CD Ray Tool (Wire)" ) + language.Add( "Tool.wire_cd_ray.desc", "Spawns a CD Ray." ) + language.Add( "WireCDRayTool_cd_ray", "CD Ray:" ) + language.Add( "sboxlimit_wire_cd_rays", "You've hit CD Rays limit!" ) + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Create CD lock (to keep CD in same spot)" }, + } +end + +WireToolSetup.BaseLang() + +if (SERVER) then + CreateConVar('sbox_maxwire_cd_rays', 20) + CreateConVar('sbox_maxwire_cd_locks', 20) +end + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_beamcaster.mdl" +TOOL.ClientConVar[ "lockmodel" ] = "models/venompapa/wirecdlock.mdl" +TOOL.ClientConVar[ "Range" ] = "64" +TOOL.ClientConVar[ "DefaultZero" ] = "0" + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("Range"), self:GetClientNumber("DefaultZero") ~= 0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + + +function TOOL:RightClick(trace) + if (!trace.HitPos) then return false end + if (trace.Entity:IsPlayer()) then return false end + if ( CLIENT ) then return true end + + local ply = self:GetOwner() + + if ( trace.Entity:IsValid() && trace.Entity:GetClass() == "gmod_wire_cd_lock" ) then + return true + end + + if ( !self:GetSWEP():CheckLimit( "wire_cd_locks" ) ) then return false end + + local Ang = trace.HitNormal:Angle() + Ang.pitch = Ang.pitch + 90 + + local range = self:GetClientNumber("Range") + local defZero = (self:GetClientNumber("DefaultZero") ~= 0) + local model = self:GetClientInfo("lockmodel") + + if not util.IsValidModel( model ) or not util.IsValidProp( model ) then return end + + local wire_cd_lock = WireLib.MakeWireEnt(ply, {Class = "gmod_wire_cd_lock", Pos=trace.HitPos, Angle=Ang, Model=model}) + + local min = wire_cd_lock:OBBMins() + wire_cd_lock:SetPos( trace.HitPos - trace.HitNormal * min.z ) + + local const = WireLib.Weld(wire_cd_lock, trace.Entity, trace.PhysicsBone, true) + + undo.Create("Wire Data CD Locky") + undo.AddEntity( wire_cd_lock ) + undo.AddEntity( const ) + undo.SetPlayer( ply ) + undo.Finish() + + ply:AddCleanup( "wire_cd_locks", wire_cd_lock ) + ply:AddCleanup( "wire_cd_locks", const ) + + return true +end + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_cd_ray_Model", list.Get( "Wire_Laser_Tools_Models" ), 1) + panel:NumSlider("Range","wire_cd_ray_Range",1,512,2) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/clutch.lua b/garrysmod/addons/feature-wire/lua/wire/stools/clutch.lua new file mode 100644 index 0000000..387bf1a --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/clutch.lua @@ -0,0 +1,312 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "clutch", "Clutch", "gmod_wire_clutch", nil, "Clutchs" ) + +if CLIENT then + language.Add( "Tool.wire_clutch.name", "Clutch Tool (Wire)" ) + language.Add( "Tool.wire_clutch.desc", "Control rotational friction between props" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Place/Select a clutch controller" }, + { name = "right_0", stage = 0, text = "Select an entity to apply the clutch to" }, + { name = "reload_0", stage = 0, text = "Remove clutch from entity/deselect controller" }, + { name = "right_1", stage = 1, text = "Right click on the second entity you want the clutch to apply to" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 8 ) + +if SERVER then + CreateConVar( "wire_clutch_maxlinks", 10 ) -- how many constraints can be added per controller + CreateConVar( "wire_clutch_maxrate", 40 ) -- how many constraints/sec may be changed per controller +end + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" +cleanup.Register( "wire_clutch" ) + + +/*--------------------------------------------------------- + -- Server Usermessages -- + Send entity tables for the DrawHUD display +---------------------------------------------------------*/ +local Send_Links + +if SERVER then + // Send info: constraints associated with the selected clutch controller + Send_Links = function( ply, constrained_pairs ) + umsg.Start( "wire_clutch_links", ply ) + local num_constraints = #constrained_pairs + umsg.Short( num_constraints ) + + for k, v in pairs( constrained_pairs ) do + umsg.Entity( v.Ent1 ) + umsg.Entity( v.Ent2 ) + end + umsg.End() + end +end + + +/*--------------------------------------------------------- + -- Client Usermessages -- + Receive entity tables for the DrawHUD display +---------------------------------------------------------*/ +if CLIENT then + local Linked_Ents = {} -- Table of constrained ents, with Ent1 as k and Ent2 as v + local Unique_Ents = {} -- Table of entities as keys + + // Receive stage 0 info + local function Receive_links( um ) + table.Empty( Linked_Ents ) + local num_constraints = um:ReadShort() or 0 + + if num_constraints ~= 0 then + for i = 1, num_constraints do + local Ent1 = um:ReadEntity() + local Ent2 = um:ReadEntity() + table.insert( Linked_Ents, {Ent1 = Ent1, Ent2 = Ent2} ) + + Unique_Ents[Ent1] = true + Unique_Ents[Ent2] = true + end + end + end + + usermessage.Hook( "wire_clutch_links", Receive_links ) + + /*--------------------------------------------------------- + -- DrawHUD -- + Display clutch constraints associated with a controller + ---------------------------------------------------------*/ + local function InView( pos2D ) + if pos2D.x > 0 and pos2D.y > 0 and pos2D.x < ScrW() and pos2D.y < ScrH() then + return true + end + return false + end + + + // Client function for drawing a line to represent constraint to world + local function DrawBaseLine( pos, viewpos ) + local dist = math.Clamp( viewpos:Distance( pos ), 50, 5000 ) + local linelength = 3000 / dist + + local pos2D = pos:ToScreen() + local pos1 = { x = pos2D.x + linelength, y = pos2D.y } + local pos2 = { x = pos2D.x - linelength, y = pos2D.y } + + surface.DrawLine( pos1.x, pos1.y, pos2.x, pos2.y ) + end + + + // Client function for drawing a circle around the currently selected controller + local function DrawSelectCircle( pos, viewpos ) + local pos2D = pos:ToScreen() + + if InView( pos2D ) then + surface.DrawCircle( pos2D.x, pos2D.y, 7, Color(255, 100, 100, 255 ) ) + end + end + + function TOOL:DrawHUD() + local DrawnEnts = {} -- Used to keep track of which ents already have a circle + + local controller = self:GetWeapon():GetNWEntity( "WireClutchController" ) + if !IsValid( controller ) then return end + + // Draw circle around the controller + local viewpos = LocalPlayer():GetViewModel():GetPos() + local controllerpos = controller:LocalToWorld( controller:OBBCenter() ) + DrawSelectCircle( controllerpos, viewpos ) + + local numconstraints_0 = #Linked_Ents + if numconstraints_0 ~= 0 then + // Draw lines between each pair of constrained ents + surface.SetDrawColor( 100, 255, 100, 255 ) + + + // Check whether each entity/position can be drawn + for k, v in pairs( Linked_Ents ) do + local basepos + local pos1, pos2 + + local IsValid1 = IsValid( v.Ent1 ) + local IsValid2 = IsValid( v.Ent2 ) + + if IsValid1 then pos1 = v.Ent1:GetPos():ToScreen() end + if IsValid2 then pos2 = v.Ent2:GetPos():ToScreen() end + + if !IsValid1 and !IsValid2 then + table.remove( Linked_Ents, k ) + elseif v.Ent1:IsWorld() then + basepos = v.Ent2:GetPos() + Vector(0, 0, -30) + pos1 = basepos:ToScreen() + elseif v.Ent2:IsWorld() then + basepos = v.Ent1:GetPos() + Vector(0, 0, -30) + pos2 = basepos:ToScreen() + end + + if pos1 and pos2 then + if InView( pos1 ) and InView( pos2 ) then + surface.DrawLine( pos1.x, pos1.y, pos2.x, pos2.y ) + + if !DrawnEnts[v.Ent1] and IsValid1 then + surface.DrawCircle( pos1.x, pos1.y, 5, Color(100, 255, 100, 255 ) ) + DrawnEnts[v.Ent1] = true + end + + if !DrawnEnts[v.Ent2] and IsValid2 then + surface.DrawCircle( pos2.x, pos2.y, 5, Color(100, 255, 100, 255 ) ) + DrawnEnts[v.Ent2] = true + end + + if basepos then + DrawBaseLine( basepos, viewpos ) + end + end + end + end + end + end +end + + +if SERVER then + function TOOL:SelectController( controller ) + self.controller = controller + self:GetWeapon():SetNWEntity( "WireClutchController", controller or Entity(0) ) -- Must use null entity since nil won't send + + // Send constraint from the controller to the client + local constrained_pairs = {} + if IsValid( controller ) then + constrained_pairs = controller:GetConstrainedPairs() + end + + Send_Links( self:GetOwner(), constrained_pairs ) + end + + function TOOL:PostMake(ent) + self:SelectController(ent) + end + + function TOOL:LeftClick_Update( trace ) + self:PostMake(trace.Entity) + end +end + +/*--------------------------------------------------------- + -- Right click -- + Associates ents with the currently selected controller +---------------------------------------------------------*/ +function TOOL:RightClick( trace ) + if CLIENT then return true end + + local ply = self:GetOwner() + local stage = self:NumObjects() + + if !IsValid( self.controller ) then + ply:PrintMessage( HUD_PRINTTALK, "Select a clutch controller with left click first" ) + return + end + + if ( !IsValid( trace.Entity ) and !trace.Entity:IsWorld() ) or trace.Entity:IsPlayer() then return end + + // First click: select the first entity + if stage == 0 then + if trace.Entity:IsWorld() then + ply:PrintMessage( HUD_PRINTTALK, "Select a valid entity" ) + return + end + + // Check that we won't be going over the max number of links allowed + local maxlinks = GetConVarNumber( "wire_clutch_maxlinks", 10 ) + if table.Count( self.controller.clutch_ballsockets ) >= maxlinks then + ply:PrintMessage( HUD_PRINTTALK, "A maximum of " .. tostring( maxlinks ) .. " links are allowed per clutch controller" ) + return + end + + // Store this entity for use later + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + self:SetObject( 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + self:SetStage(1) + + // Second click: select the second entity, and update the controller + else + local Ent1, Ent2 = self:GetEnt(1), trace.Entity + + if Ent1 == Ent2 then + ply:PrintMessage( HUD_PRINTTALK, "Select a different entity" ) + return false + end + + // Check that these ents aren't already registered on this controller + if self.controller:ClutchExists( Ent1, Ent2 ) then + ply:PrintMessage( HUD_PRINTTALK, "Entities have already been registered to this controller!" ) + return true + end + + // Add this constraint to the clutch controller + self.controller:AddClutch( Ent1, Ent2 ) + WireLib.AddNotify( ply, "Entities registered with clutch controller", NOTIFY_GENERIC, 7 ) + + // Update client + Send_Links( ply, self.controller:GetConstrainedPairs() ) + + self:ClearObjects() + self:SetStage(0) + + end + + return true +end + + +/*--------------------------------------------------------- + -- Reload -- + Remove clutch association between current controller and + the traced entity + Removes all current selections if hits world +---------------------------------------------------------*/ +function TOOL:Reload( trace ) + local stage = self:NumObjects() + + if stage == 1 then + self:ClearObjects() + self:SetStage(0) + return + + // Remove clutch associations with this entity + elseif IsValid( self.controller ) then + if trace.Entity:IsWorld() then + self:ClearObjects() + self:SetStage(0) + self.controller = nil + + else + for k, v in pairs( self.controller.clutch_ballsockets ) do + if k.Ent1 == trace.Entity or k.Ent2 == trace.Entity then + self.controller:RemoveClutch( k ) + end + end + + end + + // Update client with new constraint info + self:SelectController( self.controller ) + end + + return true +end + +function TOOL:Holster() + self:ClearObjects() + self:SetStage(0) + self:ReleaseGhostEntity() +end + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#Tool.wire_clutch.name", Description = "#Tool.wire_clutch.desc" } ) + WireDermaExts.ModelSelect(panel, "wire_clutch_model", list.Get( "Wire_Misc_Tools_Models" ), 1) +end + + +if CLIENT then return end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/colorer.lua b/garrysmod/addons/feature-wire/lua/wire/stools/colorer.lua new file mode 100644 index 0000000..beb1a32 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/colorer.lua @@ -0,0 +1,33 @@ +WireToolSetup.setCategory( "Visuals" ) +WireToolSetup.open( "colorer", "Colorer", "gmod_wire_colorer", nil, "Colorers" ) + +if CLIENT then + language.Add( "Tool.wire_colorer.name", "Colorer Tool (Wire)" ) + language.Add( "Tool.wire_colorer.desc", "Spawns a constant colorer prop for use with the wire system." ) + language.Add( "WireColorerTool_colorer", "Colorer:" ) + language.Add( "WireColorerTool_outColor", "Output Color" ) + language.Add( "WireColorerTool_Range", "Max Range:" ) + language.Add( "WireColorerTool_Model", "Choose a Model:") + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/color_wheel.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "outColor" ) ~= 0, self:GetClientNumber( "range" ) + end +end + +TOOL.ClientConVar[ "Model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" +TOOL.ClientConVar[ "outColor" ] = "0" +TOOL.ClientConVar[ "range" ] = "2000" + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_colorer") + WireDermaExts.ModelSelect(panel, "wire_colorer_model", list.Get( "Wire_Laser_Tools_Models" ), 1, true) + panel:CheckBox("#WireColorerTool_outColor", "wire_colorer_outColor") + panel:NumSlider("#WireColorerTool_Range", "wire_colorer_Range", 1, 10000, 2) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/consolescreen.lua b/garrysmod/addons/feature-wire/lua/wire/stools/consolescreen.lua new file mode 100644 index 0000000..836b51e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/consolescreen.lua @@ -0,0 +1,24 @@ +WireToolSetup.setCategory( "Visuals/Screens" ) +WireToolSetup.open( "consolescreen", "Console Screen", "gmod_wire_consolescreen", nil, "Screens" ) + +if CLIENT then + language.Add( "tool.wire_consolescreen.name", "Console Screen Tool (Wire)" ) + language.Add( "tool.wire_consolescreen.desc", "Spawns a console screen" ) + TOOL.Information = { { name = "left", text = "Create " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/application_xp_terminal.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.NoLeftOnClass = true -- no update ent function needed +TOOL.ClientConVar = { + model = "models/props_lab/monitor01b.mdl", + createflat = 0, +} + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_consolescreen_model", list.Get( "WireScreenModels" ), 5) + panel:CheckBox("#Create Flat to Surface", "wire_consolescreen_createflat") + panel:Help("CharParam is LBBBFFF format: background and foreground colour of the character (one digit each for RGB), if L is nonzero the char flashes") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/cpu.lua b/garrysmod/addons/feature-wire/lua/wire/stools/cpu.lua new file mode 100644 index 0000000..2d84261 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/cpu.lua @@ -0,0 +1,400 @@ +WireToolSetup.setCategory( "Chips, Gates", "Advanced" ) +WireToolSetup.open( "cpu", "CPU", "gmod_wire_cpu", nil, "CPUs" ) + +if CLIENT then + language.Add("Tool.wire_cpu.name", "CPU Tool (Wire)") + language.Add("Tool.wire_cpu.desc", "Spawns a central processing unit") + language.Add("ToolWirecpu_Model", "Model:" ) + TOOL.Information = { + { name = "left", text = "Upload program to hispeed device" }, + { name = "right", text = "Open editor" }, + { name = "reload", text = "Attach debugger" }, + { name = "reload_shift", text = "Shift+Reload: Clear" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 7 ) + +TOOL.ClientConVar = { + model = "models/cheeze/wires/cpu.mdl", + filename = "", + memorymodel = "64krom", +} + +if CLIENT then + ------------------------------------------------------------------------------ + -- Make sure firing animation is displayed clientside + ------------------------------------------------------------------------------ + function TOOL:LeftClick() return true end + function TOOL:Reload() return true end + function TOOL:RightClick() return false end +end + + +if SERVER then + util.AddNetworkString("ZCPU_RequestCode") + util.AddNetworkString("ZCPU_OpenEditor") + util.AddNetworkString("CPULib.InvalidateDebugger") + ------------------------------------------------------------------------------ + -- Reload: wipe ROM/RAM and reset memory model, or attach debugger + ------------------------------------------------------------------------------ + function TOOL:Reload(trace) + if trace.Entity:IsPlayer() then return false end + local player = self:GetOwner() + + if player:KeyDown(IN_SPEED) then + if (trace.Entity:IsValid()) and + (trace.Entity:GetClass() == "gmod_wire_cpu") then + trace.Entity:SetMemoryModel(self:GetClientInfo("memorymodel")) + trace.Entity:FlashData({}) + net.Start("CPULib.InvalidateDebugger") net.WriteUInt(0,2) net.Send(player) + end + else + if (not trace.Entity:IsPlayer()) and + (trace.Entity:IsValid()) and + (trace.Entity:GetClass() == "gmod_wire_cpu") then + CPULib.AttachDebugger(trace.Entity,player) + CPULib.SendDebugData(trace.Entity.VM,nil,player) + net.Start("CPULib.InvalidateDebugger") net.WriteUInt(2,2) net.Send(player) + else + CPULib.AttachDebugger(nil,player) + net.Start("CPULib.InvalidateDebugger") net.WriteUInt(1,2) net.Send(player) + end + end + return true + end + + -- Left click: spawn CPU or upload current program into it + function TOOL:CheckHitOwnClass(trace) + return trace.Entity:IsValid() and (trace.Entity:GetClass() == self.WireClass or trace.Entity.WriteCell) + end + function TOOL:LeftClick_Update(trace) + CPULib.SetUploadTarget(trace.Entity, self:GetOwner()) + net.Start("ZCPU_RequestCode") net.Send(self:GetOwner()) + net.Start("CPULib.InvalidateDebugger") net.WriteUInt(0,2) net.Send(player) + end + function TOOL:MakeEnt(ply, model, Ang, trace) + local ent = WireLib.MakeWireEnt(ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}) + ent:SetMemoryModel(self:GetClientInfo("memorymodel")) + self:LeftClick_Update(trace) + return ent + end + + + -- Right click: open editor + function TOOL:RightClick(trace) + net.Start("ZCPU_OpenEditor") net.Send(self:GetOwner()) + return true + end +end + + +if CLIENT then + ------------------------------------------------------------------------------ + -- Compiler callbacks on the compiling state + ------------------------------------------------------------------------------ + local function compile_success() + CPULib.Upload() + end + + local function compile_error(errorText) + print(errorText) + GAMEMODE:AddNotify(errorText,NOTIFY_GENERIC,7) + end + + + ------------------------------------------------------------------------------ + -- Request code to be compiled (called remotely from server) + ------------------------------------------------------------------------------ + function ZCPU_RequestCode() + if ZCPU_Editor then + CPULib.Compile(ZCPU_Editor:GetCode(),ZCPU_Editor:GetChosenFile(),compile_success,compile_error) + end + end + net.Receive("ZCPU_RequestCode", ZCPU_RequestCode) + + ------------------------------------------------------------------------------ + -- Open ZCPU editor + ------------------------------------------------------------------------------ + function ZCPU_OpenEditor() + if not ZCPU_Editor then + ZCPU_Editor = vgui.Create("Expression2EditorFrame") + ZCPU_Editor:Setup("ZCPU Editor", "cpuchip", "CPU") + end + ZCPU_Editor:Open() + end + net.Receive("ZCPU_OpenEditor", ZCPU_OpenEditor) + + ------------------------------------------------------------------------------ + -- Build tool control panel + ------------------------------------------------------------------------------ + function TOOL.BuildCPanel(panel) + local Button = vgui.Create("DButton" , panel) + panel:AddPanel(Button) + Button:SetText("Online ZCPU documentation") + Button.DoClick = function(button) CPULib.ShowDocumentation("ZCPU") end + + + ---------------------------------------------------------------------------- + local currentDirectory + local FileBrowser = vgui.Create("wire_expression2_browser" , panel) + panel:AddPanel(FileBrowser) + FileBrowser:Setup("cpuchip") + FileBrowser:SetSize(235,400) + function FileBrowser:OnFileOpen(filepath, newtab) + if not ZCPU_Editor then + ZCPU_Editor = vgui.Create("Expression2EditorFrame") + ZCPU_Editor:Setup("ZCPU Editor", "cpuchip", "CPU") + end + ZCPU_Editor:Open(filepath, nil, newtab) + end + + + ---------------------------------------------------------------------------- + local New = vgui.Create("DButton" , panel) + panel:AddPanel(New) + New:SetText("New file") + New.DoClick = function(button) + ZCPU_OpenEditor() + ZCPU_Editor:AutoSave() + ZCPU_Editor:NewScript(false) + end + panel:AddControl("Label", {Text = ""}) + + ---------------------------------------------------------------------------- + local OpenEditor = vgui.Create("DButton", panel) + panel:AddPanel(OpenEditor) + OpenEditor:SetText("Open Editor") + OpenEditor.DoClick = ZCPU_OpenEditor + + + ---------------------------------------------------------------------------- + panel:AddControl("Label", {Text = ""}) + panel:AddControl("Label", {Text = "CPU settings:"}) + + + ---------------------------------------------------------------------------- + local modelPanel = WireDermaExts.ModelSelect(panel, "wire_cpu_model", list.Get("Wire_gate_Models"), 2) + panel:AddControl("Label", {Text = ""}) + + + ---------------------------------------------------------------------------- + panel:AddControl("ComboBox", { + Label = "Memory model", + Options = { + ["128 bytes ROM only"] = {wire_cpu_memorymodel = "128rom"}, + ["128 bytes RAM/ROM"] = {wire_cpu_memorymodel = "128"}, + ["64KB RAM/ROM"] = {wire_cpu_memorymodel = "64krom"}, + ["64KB RAM only"] = {wire_cpu_memorymodel = "64k"}, + ["32KB RAM/ROM"] = {wire_cpu_memorymodel = "32krom"}, + ["32KB RAM only"] = {wire_cpu_memorymodel = "32k"}, + ["8KB RAM/ROM"] = {wire_cpu_memorymodel = "8krom"}, + ["8KB RAM only"] = {wire_cpu_memorymodel = "8k"}, + ["128KB RAM/ROM"] = {wire_cpu_memorymodel = "128krom"}, + ["No internal RAM/ROM"] = {wire_cpu_memorymodel = "flat"}, + } + }) + panel:AddControl("Label", {Text = "Sets the processor memory model (determines interaction with the external devices)"}) + end + + + ------------------------------------------------------------------------------ + -- Tool screen + ------------------------------------------------------------------------------ + net.Receive("CPULib.ServerUploading", function(netlen) + CPULib.ServerUploading = net.ReadBit() ~= 0 + end) + + local fontData = + { + font = "Lucida Console", + size = 30, + weight = 1000, + antialias = true, + additive = false + } + surface.CreateFont( "ZCPUToolScreenFont", fontData ) + fontData.size = 26 + surface.CreateFont( "ZCPUToolScreenFontSmall", fontData ) + + local function outc(text,y,color) draw.DrawText(text or "","ZCPUToolScreenFont",2,32*y,color,0) end + local prevStateTime = RealTime() + local prevState = nil + local consoleHistory = { "", "", "", "", "", "" } + local stageName = {"Preprocessing","Tokenizing","Parsing","Generating","Optimizing","Resolving","Outputting"} + local stageNameShort = {"Preproc","Tokenize","Parse","Generate","Optimize","Resolve","Output"} + + local function outform(x,y,w,h,title) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawRect(x*28-3,y*32-3,w*28,h*32) + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(x*28+3,y*32+3,w*28,h*32) + + surface.SetDrawColor(192, 220, 192, 255) + surface.DrawRect(x*28,y*32,w*28-3,h*32-3) + + surface.SetDrawColor(192, 192, 192, 255) + surface.DrawRect(x*28,y*32,w*28,h*32) + + if title then + surface.SetDrawColor(0, 0, 128, 255) + surface.DrawRect(x*28+4,y*32+4,w*28-4,1*32-4) + draw.DrawText(title,"ZCPUToolScreenFontSmall",x*28+4,y*32+4,Color(255,255,255,255),0) + end + end + + function CPULib.RenderCPUTool(screenIndex,toolName) + if screenIndex == 0 then + surface.SetDrawColor(0, 0, 128, 255) + surface.DrawRect(0, 0, 256, 256) + + surface.SetDrawColor(240, 240, 0, 255) + surface.DrawRect(0,0,256,32) + outc(" ToolOS r"..VERSION.." ",0,Color(0,0,0,255)) + + if CPULib.Uploading then + outc("Program size:",2,Color(255,255,255,255)) + outc(string.format("%d bytes",CPULib.TotalUploadData),3,Color(255,255,255,255)) + outc(string.format("Uploading %2d%%",100-100*CPULib.RemainingUploadData/(CPULib.TotalUploadData+1e-12)),5,Color(255,255,255,255)) + outc(string.format("%d bytes",CPULib.RemainingUploadData),6,Color(255,255,255,255)) + prevStateTime = RealTime() + elseif CPULib.ServerUploading then + outc("Program size:",2,Color(255,255,255,255)) + outc(string.format("%d bytes",#CPULib.Buffer),3,Color(255,255,255,255)) + outc("Uploading 100",5,Color(255,255,255,255)) + outc(" Standby ",6,Color(255,255,255,255)) + prevStateTime = RealTime() + elseif CPULib.Compiling then + outc(string.format("Stage %2d/7",HCOMP.Stage+1),2,Color(255,255,255,255)) + outc(stageName[HCOMP.Stage+1],3,Color(255,255,255,255)) + prevStateTime = RealTime() + else + if RealTime() - prevStateTime > 0.15 then + outc("Flash utility",1,Color(255,255,255,255)) + outc("(C) 2007-2011",2,Color(255,255,255,255)) + outc("Black Phoenix",3,Color(255,255,255,255)) + + outc(string.format("RAM: %5d KB",collectgarbage("count") or 0),7,Color(255,255,255,255)) + else + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, 256, 256) + end + end + elseif screenIndex == 1 then + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, 256, 256) + + surface.SetDrawColor(240, 120, 0, 255) + surface.DrawRect(16*(#toolName+1),32*0+14,256,4) + outc(toolName,0,Color(240, 120,0,255)) + outc(string.format(" RAM %5d KB",collectgarbage("count") or 0),1,Color(255,255,255,255)) + + surface.SetDrawColor(240, 120, 0, 255) + surface.DrawRect(16*(5),32*2+14,256,4) + outc("TASK",2,Color(240, 120,0,255)) + outc(" STATUS",3,Color(255,255,255,255)) + + surface.SetDrawColor(240, 120, 0, 255) + surface.DrawRect(16*(4),32*6+14,256,4) + outc("NET",6,Color(240, 120,0,255)) + if CPULib.Uploading then + outc(string.format("UP %.3f KB",CPULib.RemainingUploadData/1024),7,Color(255,255,255,255)) + outc(string.format("ROMUPL [%3d%%]",100-100*CPULib.RemainingUploadData/(CPULib.TotalUploadData+1e-12)),4,Color(255,255,255,255)) + outc("UPLMON [ OK ]",5,Color(255,255,255,255)) + elseif CPULib.ServerUploading then + outc("UPLMON [ OK ]",4,Color(255,255,255,255)) + outc("DOWN SYNC",7,Color(255,255,255,255)) + elseif CPULib.Compiling then + outc(string.format("HCOMP [%2d/7]",HCOMP.Stage),4,Color(255,255,255,255)) + outc("IDLE",7,Color(255,255,255,255)) + else + outc("IDLE",7,Color(255,255,255,255)) + end + elseif screenIndex == 2 then + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, 256, 256) + + outc("TL-UNIX "..(VERSION/100),0,Color(200,200,200,255)) + + outc(consoleHistory[1],2,Color(200,200,200,255)) + outc(consoleHistory[2],3,Color(200,200,200,255)) + outc(consoleHistory[3],4,Color(200,200,200,255)) + outc(consoleHistory[4],5,Color(200,200,200,255)) + outc(consoleHistory[5],6,Color(200,200,200,255)) + outc(consoleHistory[6],7,Color(200,200,200,255)) + + if CPULib.Uploading then + if prevState ~= 0 then + consoleHistory[1] = consoleHistory[2] + consoleHistory[2] = consoleHistory[3] + consoleHistory[3] = consoleHistory[4] + consoleHistory[4] = string.lower(toolName).."@:/# upl" + end + + consoleHistory[5] = string.format(" %3d%%",100-100*CPULib.RemainingUploadData/(CPULib.TotalUploadData+1e-12)) + consoleHistory[6] = string.format(" %d B",CPULib.RemainingUploadData) + + prevState = 0 + elseif CPULib.ServerUploading then + consoleHistory[5] = " ###" + consoleHistory[6] = " 0 B" + prevState = 0 + elseif CPULib.Compiling then + if prevState ~= 1 then + consoleHistory[1] = consoleHistory[2] + consoleHistory[2] = consoleHistory[3] + consoleHistory[3] = consoleHistory[4] + consoleHistory[4] = consoleHistory[5] + consoleHistory[5] = string.lower(toolName).."@:/# hcmp" + end + consoleHistory[6] = string.format("Stage %2d/7",HCOMP.Stage+1) + prevState = 1 + else + if prevState ~= 2 then + consoleHistory[1] = consoleHistory[2] + consoleHistory[2] = consoleHistory[3] + consoleHistory[3] = consoleHistory[4] + consoleHistory[4] = consoleHistory[5] + consoleHistory[5] = consoleHistory[6] + consoleHistory[6] = string.lower(toolName).."@:/# " + end + prevState = 2 + end + elseif screenIndex == 3 then + surface.SetDrawColor(0, 128, 128, 255) + surface.DrawRect(0, 0, 256, 256) + + outform(0,7,12,1) + + outform(0,7,3,1) + outc("MENU",7,Color(0,0,0,255)) + + if CPULib.Uploading then + outform(1,1,7,5,"Upload") + outc(string.format(" %.3f kb",CPULib.RemainingUploadData/1024),3,Color(0,0,0,255)) + outc(string.format(" %3d%% done",100-100*CPULib.RemainingUploadData/(CPULib.TotalUploadData+1e-12)),4,Color(0,0,0,255)) + + outform(1,5,7,0.9) + surface.SetDrawColor(0, 0, 128, 255) + surface.DrawRect(1*28+4,5*32+4, + math.floor((7*28-4)*(1-CPULib.RemainingUploadData/(CPULib.TotalUploadData+1e-12))/14)*14, + 1*32-8) + elseif CPULib.ServerUploading then + outform(1,3,7,3,"Upload") + outc(" Standby",5,Color(0,0,0,255)) + elseif CPULib.Compiling then + outform(1,1,7,5,"HL-ZASM") + outc(string.format(" Stage %d/7",HCOMP.Stage+1),3,Color(0,0,0,255)) + outc(" "..stageNameShort[HCOMP.Stage+1],4,Color(0,0,0,255)) + else + -- + end + end + end + + function TOOL:DrawToolScreen(width, height) + local currentTime = os.date("*t") + CPULib.RenderCPUTool(currentTime.yday % 4,"CPU") + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/damage_detector.lua b/garrysmod/addons/feature-wire/lua/wire/stools/damage_detector.lua new file mode 100644 index 0000000..039041f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/damage_detector.lua @@ -0,0 +1,30 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "damage_detector", "Damage Detector", "gmod_wire_damage_detector", nil, "Damage Detectors" ) + +if CLIENT then + language.Add( "Tool.wire_damage_detector.name", "Damage Detector Tool (Wire)" ) + language.Add( "Tool.wire_damage_detector.desc", "Spawns a damage detector for use with the wire system" ) + language.Add( "Tool.wire_damage_detector.includeconstrained", "Include Constrained Props" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + includeconstrained = 0 +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "includeconstrained" ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +WireToolSetup.SetupLinking() + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_damage_detector") + panel:CheckBox("#Tool.wire_damage_detector.includeconstrained","wire_damage_detector_includeconstrained") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/data_satellitedish.lua b/garrysmod/addons/feature-wire/lua/wire/stools/data_satellitedish.lua new file mode 100644 index 0000000..2e4249d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/data_satellitedish.lua @@ -0,0 +1,18 @@ +WireToolSetup.setCategory( "Memory" ) +WireToolSetup.open( "data_satellitedish", "Satellite Dish", "gmod_wire_data_satellitedish", nil, "Satellite Dishs" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_data_satellitedish.name", "Satellite Dish Tool (Wire)" ) + language.Add( "Tool.wire_data_satellitedish.desc", "Spawns a Satellite Dish." ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar["model"] = "models/props_wasteland/prison_lamp001c.mdl" + +TOOL.ReloadSetsModel = true +WireToolSetup.SetupLinking(true, "Wire Transferer") + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_data_satellitedish_model", list.Get( "Wire_satellitedish_Models" ), 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/data_store.lua b/garrysmod/addons/feature-wire/lua/wire/stools/data_store.lua new file mode 100644 index 0000000..dc3911f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/data_store.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Memory" ) +WireToolSetup.open( "data_store", "Store", "gmod_wire_data_store", nil, "Data Stores" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_data_store.name", "Data Store Tool (Wire)" ) + language.Add( "Tool.wire_data_store.desc", "Spawns a data store." ) + language.Add( "WireDataStoreTool_data_store", "Data Store:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_range.mdl" + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_data_store_model", list.Get( "Wire_Misc_Tools_Models" ), 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/data_transferer.lua b/garrysmod/addons/feature-wire/lua/wire/stools/data_transferer.lua new file mode 100644 index 0000000..c4c3ebb --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/data_transferer.lua @@ -0,0 +1,39 @@ +WireToolSetup.setCategory( "Memory" ) +WireToolSetup.open( "data_transferer", "Transferer", "gmod_wire_data_transferer", nil, "Transferers" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_data_transferer.name", "Data Transferer Tool (Wire)" ) + language.Add( "Tool.wire_data_transferer.desc", "Spawns a data transferer." ) + language.Add( "WireDataTransfererTool_data_transferer", "Data Transferer:" ) + language.Add( "WireDataTransfererTool_Range", "Max Range:" ) + language.Add( "WireDataTransfererTool_DefaultZero","Default To Zero") + language.Add( "WireDataTransfererTool_IgnoreZero","Ignore Zero") + language.Add( "WireDataTransfererTool_Model", "Choose a Model:") + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("Range"), self:GetClientNumber("DefaultZero") ~= 0, self:GetClientNumber("IgnoreZero") ~= 0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + Model = "models/jaanus/wiretool/wiretool_siren.mdl", + Range = "25000", + DefaultZero = 0, + IgnoreZero = 0, +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_data_transferer") + ModelPlug_AddToCPanel(panel, "Laser_Tools", "wire_data_transferer") + + panel:NumSlider("#WireDataTransfererTool_Range", "wire_data_transferer_Range", 1, 30000, 0) + panel:CheckBox("#WireDataTransfererTool_DefaultZero", "wire_data_transferer_DefaultZero") + panel:CheckBox("#WireDataTransfererTool_IgnoreZero", "wire_data_transferer_IgnoreZero") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/dataplug.lua b/garrysmod/addons/feature-wire/lua/wire/stools/dataplug.lua new file mode 100644 index 0000000..c599396 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/dataplug.lua @@ -0,0 +1,113 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "dataplug", "Data - Plug/Socket", "gmod_wire_datasocket", nil, "Plugs and Sockets" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_dataplug.name", "Data Plug Tool (Wire)" ) + language.Add( "Tool.wire_dataplug.desc", "Spawns plugs and sockets for use with the hi-speed wire system." ) + language.Add( "sboxlimit_wire_dataplugs", "You've hit plugs limit!" ) + language.Add( "sboxlimit_wire_datasockets", "You've hit sockets limit!" ) + language.Add( "undone_wiredataplug", "Undone Wire Data Plug" ) + language.Add( "undone_wiredatasocket", "Undone Wire Data Socket" ) + language.Add( "Tool_wire_dataplug_weldforce", "Plug weld force:" ) + language.Add( "Tool_wire_dataplug_attachrange", "Plug attachment detection range:" ) + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Create/Update Plug" }, + } +end + +WireToolSetup.BaseLang() + +if (SERVER) then + CreateConVar('sbox_maxwire_dataplugs', 20) + CreateConVar('sbox_maxwire_datasockets', 20) +end + +TOOL.ClientConVar["model"] = "models/hammy/pci_slot.mdl" +TOOL.ClientConVar["weldforce"] = 5000 +TOOL.ClientConVar["attachrange"] = 5 + +function TOOL:GetConVars() + return self:GetClientNumber("weldforce"), math.Clamp(self:GetClientNumber("attachrange"), 1, 100) +end + +local SocketModels = { + ["models/props_lab/tpplugholder_single.mdl"] = "models/props_lab/tpplug.mdl", + ["models/bull/various/usb_socket.mdl"] = "models/bull/various/usb_stick.mdl", + ["models/hammy/pci_slot.mdl"] = "models/hammy/pci_card.mdl", + ["models/wingf0x/isasocket.mdl"] = "models/wingf0x/isaplug.mdl", + ["models/wingf0x/altisasocket.mdl"] = "models/wingf0x/isaplug.mdl", + ["models/wingf0x/ethernetsocket.mdl"] = "models/wingf0x/ethernetplug.mdl", + ["models/wingf0x/hdmisocket.mdl"] = "models/wingf0x/hdmiplug.mdl" +} + +local AngleOffset = { + ["models/props_lab/tpplugholder_single.mdl"] = Angle(0,0,0), + ["models/props_lab/tpplug.mdl"] = Angle(0,0,0), + ["models/bull/various/usb_socket.mdl"] = Angle(0,0,0), + ["models/bull/various/usb_stick.mdl"] = Angle(0,0,0), + ["models/hammy/pci_slot.mdl"] = Angle(90,0,0), + ["models/hammy/pci_card.mdl"] = Angle(90,0,0), + ["models/wingf0x/isasocket.mdl"] = Angle(90,0,0), + ["models/wingf0x/isaplug.mdl"] = Angle(90,0,0), + ["models/wingf0x/altisasocket.mdl"] = Angle(90,00,0), + ["models/wingf0x/ethernetsocket.mdl"] = Angle(90,0,0), + ["models/wingf0x/ethernetplug.mdl"] = Angle(90,0,0), + ["models/wingf0x/hdmisocket.mdl"] = Angle(90,0,0), + ["models/wingf0x/hdmiplug.mdl"] = Angle(90,0,0) +} + +cleanup.Register( "wire_dataplugs" ) + +function TOOL:GetModel() + local model = self:GetClientInfo( "model" ) + if (!util.IsValidModel( model ) or !util.IsValidProp( model ) or !SocketModels[ model ]) then return "models/props_lab/tpplugholder_single.mdl", "models/props_lab/tpplug.mdl" end + return model, SocketModels[ model ] +end + +-- Create socket +-- Handled by WireToolObj + +-- Create plug +function TOOL:RightClick( trace ) + if (!trace) then return false end + if (trace.Entity) then + if (trace.Entity:IsPlayer()) then return false end + if (trace.Entity:GetClass() == "gmod_wire_dataplug") then + if (CLIENT) then return true end + trace.Entity:Setup() + return true + end + end + if (CLIENT) then return true end + if not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + local ply = self:GetOwner() + local _, plugmodel = self:GetModel() + + local plug = WireLib.MakeWireEnt(ply, {Class = "gmod_wire_dataplug", Pos=trace.HitPos, Angle=self:GetAngle(trace), Model=plugmodel}) + if not IsValid(plug) then return false end + + plug:SetPos( trace.HitPos - trace.HitNormal * plug:OBBMins().x ) + + undo.Create("wiredataplug") + undo.AddEntity( plug ) + undo.SetPlayer( ply ) + undo.Finish() + + ply:AddCleanup( "wire_dataplugs", plug ) + + return true +end + +function TOOL:GetGhostAngle(trace) + local socketmodel = self:GetModel() + return trace.HitNormal:Angle() + (AngleOffset[socketmodel] or Angle(0,0,0)) - Angle(90,0,0) +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_dataplug") + ModelPlug_AddToCPanel(panel, "Socket", "wire_dataplug") + panel:NumSlider("#Tool_wire_dataplug_weldforce", "wire_dataplug_weldforce", 0, 100000) + panel:NumSlider("#Tool_wire_dataplug_attachrange", "wire_dataplug_attachrange", 1, 100) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/dataport.lua b/garrysmod/addons/feature-wire/lua/wire/stools/dataport.lua new file mode 100644 index 0000000..6b86be0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/dataport.lua @@ -0,0 +1,20 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "dataport", "Data - Port", "gmod_wire_dataport", nil, "Data Ports" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_dataport.name", "Data port tool (Wire)" ) + language.Add( "Tool.wire_dataport.desc", "Spawns data port consisting of 8 ports" ) + TOOL.Information = { { name = "left", text = "Create/Update data port" } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_gate.mdl" + +if SERVER then + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "gate", "wire_dataport", nil, 4) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/datarate.lua b/garrysmod/addons/feature-wire/lua/wire/stools/datarate.lua new file mode 100644 index 0000000..235f3d6 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/datarate.lua @@ -0,0 +1,20 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "datarate", "Data - Transfer Bus", "gmod_wire_datarate", nil, "Transfer Buses" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_datarate.name", "Data transfer bus tool (Wire)" ) + language.Add( "Tool.wire_datarate.desc", "Spawns a data transferrer. Data transferrer acts like identity gate for hi-speed and regular links" ) + TOOL.Information = { { name = "left", text = "Create/Update data transferrer" } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_gate.mdl" + +if SERVER then + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "gate", "wire_datarate", nil, 4) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/detonator.lua b/garrysmod/addons/feature-wire/lua/wire/stools/detonator.lua new file mode 100644 index 0000000..9d61154 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/detonator.lua @@ -0,0 +1,34 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "detonator", "Detonator", "gmod_wire_detonator", nil, "Detonators" ) + +if CLIENT then + language.Add( "tool.wire_detonator.name", "Detonator Tool (Wire)" ) + language.Add( "tool.wire_detonator.desc", "Spawns a Detonator for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("detonator") + function TOOL:GetConVars() + return self:GetClientNumber( "damage" ) + end + + function TOOL:MakeEnt(ply, model, Ang, trace) + local ent = WireToolObj.MakeEnt(self, ply, model, Ang, trace ) + ent.target = trace.Entity + return ent + end +end + +TOOL.ClientConVar = { + damage = 1, + model = "models/props_combine/breenclock.mdl" +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_detonator") + panel:NumSlider("#Damage", "wire_detonator_damage", 1, 200, 0) + ModelPlug_AddToCPanel(panel, "detonator", "wire_detonator", true, 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/dhdd.lua b/garrysmod/addons/feature-wire/lua/wire/stools/dhdd.lua new file mode 100644 index 0000000..579cb64 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/dhdd.lua @@ -0,0 +1,24 @@ +WireToolSetup.setCategory( "Memory" ) +WireToolSetup.open( "dhdd", "DHDD", "gmod_wire_dhdd", nil, "DHDDs" ) + +if CLIENT then + language.Add( "Tool.wire_dhdd.name", "DHDD Tool (Wire)" ) + language.Add( "Tool.wire_dhdd.desc", "Spawns a dupeable hard drive gate for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + language.Add( "Tool.wire_dhdd.weld", "Weld the DHDD." ) + language.Add( "Tool.wire_dhdd.weldtoworld", "Weld the DHDD to the world." ) + language.Add( "Tool.wire_dhdd.note", "NOTE: The DHDD only saves the first\n512^2 values to prevent\nmassive dupe files and lag." ) + + TOOL.ClientConVar["model"] = "models/jaanus/wiretool/wiretool_gate.mdl" + + function TOOL.BuildCPanel( panel ) + ModelPlug_AddToCPanel(panel, "gate", "wire_dhdd", nil, 4) + + panel:Help("#Tool.wire_dhdd.note") + end + + WireToolSetup.setToolMenuIcon( "icon16/database.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/digitalscreen.lua b/garrysmod/addons/feature-wire/lua/wire/stools/digitalscreen.lua new file mode 100644 index 0000000..0def44f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/digitalscreen.lua @@ -0,0 +1,30 @@ +WireToolSetup.setCategory( "Visuals/Screens" ) +WireToolSetup.open( "digitalscreen", "Digital Screen", "gmod_wire_digitalscreen", nil, "Digital Screens" ) + +if CLIENT then + language.Add( "tool.wire_digitalscreen.name", "Digital Screen Tool (Wire)" ) + language.Add( "tool.wire_digitalscreen.desc", "Spawns a digital screen, which can be used to draw pixel by pixel." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientInfo("width"), self:GetClientInfo("height") + end +end + +TOOL.ClientConVar = { + model = "models/props_lab/monitor01b.mdl", + width = 32, + height = 32, + createflat = 0, +} + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_digitalscreen_model", list.Get( "WireScreenModels" ), 5) + panel:NumSlider("Width", "wire_digitalscreen_width", 1, 512, 0) + panel:NumSlider("Height", "wire_digitalscreen_height", 1, 512, 0) + panel:CheckBox("#Create Flat to Surface", "wire_digitalscreen_createflat") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/dual_input.lua b/garrysmod/addons/feature-wire/lua/wire/stools/dual_input.lua new file mode 100644 index 0000000..6eccd85 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/dual_input.lua @@ -0,0 +1,59 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction" ) +WireToolSetup.open( "dual_input", "Dual Input", "gmod_wire_dual_input", nil, "Dual Inputs" ) + +if CLIENT then + language.Add( "tool.wire_dual_input.name", "Dual Input Tool (Wire)" ) + language.Add( "tool.wire_dual_input.desc", "Spawns a daul input for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + language.Add( "WireDualInputTool_keygroup", "Key 1:" ) + language.Add( "WireDualInputTool_keygroup2", "Key 2:" ) + language.Add( "WireDualInputTool_toggle", "Toggle" ) + language.Add( "WireDualInputTool_value_on", "Value 1 On:" ) + language.Add( "WireDualInputTool_value_on2", "Value 2 On:" ) + language.Add( "WireDualInputTool_value_off", "Value Off:" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("Numpad") + + function TOOL:GetConVars() + return self:GetClientNumber( "keygroup" ), self:GetClientNumber( "keygroup2" ), self:GetClientNumber( "toggle" ), + self:GetClientNumber( "value_off" ), self:GetClientNumber( "value_on" ), self:GetClientNumber( "value_on2" ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/numpad.mdl", + modelsize = "", + keygroup = 7, + keygroup2 = 4, + toggle = 0, + value_off = 0, + value_on = 1, + value_on2 = -1, +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_dual_input") + WireToolHelpers.MakeModelSizer(panel, "wire_dual_input_modelsize") + ModelPlug_AddToCPanel(panel, "Numpad", "wire_dual_input", true) + + panel:AddControl("Numpad", { + Label = "#WireDualInputTool_keygroup", + Command = "wire_dual_input_keygroup" + }) + + panel:AddControl("Numpad", { + Label = "#WireDualInputTool_keygroup2", + Command = "wire_dual_input_keygroup2" + }) + + panel:CheckBox("#WireDualInputTool_toggle", "wire_dual_input_toggle") + panel:NumSlider("#WireDualInputTool_value_on", "wire_dual_input_value_on", -10, 10, 1) + panel:NumSlider("#WireDualInputTool_value_off", "wire_dual_input_value_off", -10, 10, 1) + panel:NumSlider("#WireDualInputTool_value_on2", "wire_dual_input_value_on2", -10, 10, 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/dynamic_button.lua b/garrysmod/addons/feature-wire/lua/wire/stools/dynamic_button.lua new file mode 100644 index 0000000..d478dd4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/dynamic_button.lua @@ -0,0 +1,88 @@ +WireToolSetup.setCategory( "Input, Output" ) +WireToolSetup.open( "dynamic_button", "Dynamic Button", "gmod_wire_dynamic_button", nil, "Dynamic Buttons" ) + +if CLIENT then + language.Add( "tool.wire_dynamic_button.name", "Dynamic Button Tool (Wire)" ) + language.Add( "tool.wire_dynamic_button.desc", "Spawns a dynamic button for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + language.Add( "WireDynamicButtonTool_toggle", "Toggle" ) + language.Add( "WireDynamicButtonTool_entityout", "Output Entity" ) + language.Add( "WireDynamicButtonTool_value_on", "Value On:" ) + language.Add( "WireDynamicButtonTool_value_off", "Value Off:" ) + language.Add( "WireDynamicButtonTool_materials_on", "Material On:" ) + language.Add( "WireDynamicButtonTool_materials_off", "Material Off:" ) + language.Add( "WireDynamicButtonTool_colour_on", "Color On:" ) + language.Add( "WireDynamicButtonTool_colour_off", "Color Off:" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "toggle" ) ~= 0, self:GetClientNumber( "value_on" ), self:GetClientNumber( "value_off" ), + self:GetClientInfo( "description" ), self:GetClientNumber( "entityout" ) ~= 0, self:GetClientInfo( "material_on" ), self:GetClientInfo( "material_off" ), + self:GetClientNumber( "on_r" ), self:GetClientNumber( "on_g" ), self:GetClientNumber( "on_b" ), + self:GetClientNumber( "off_r" ), self:GetClientNumber( "off_g" ), self:GetClientNumber( "off_b" ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/bull/ranger.mdl", + model_category = "dynamic_button", + toggle = "0", + value_off = "0", + value_on = "1", + description = "", + entityout = "0", + material_on = "bull/dynamic_button_1", + material_off = "bull/dynamic_button_0", + on_r = 0, + on_g = 255, + on_b = 0, + off_r = 255, + off_g = 0, + off_b = 0 +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_dynamic_button") + + ModelPlug_AddToCPanel_Multi( + panel, + { dynamic_button = "Normal", + dynamic_button_small = "Small" + }, + "wire_dynamic_button", "#Dynamic_Button_Model", 1.3 + ) + + panel:NumSlider("#WireDynamicButtonTool_value_on", "wire_dynamic_button_value_on", -10, 10, 1) + panel:AddControl("ListBox", { + Label = "#WireDynamicButtonTool_materials_on", + Options = list.Get( "WireDynamicButtonMaterialsOn" ) + } ) + + panel:AddControl("Color", { + Label = "#WireDynamicButtonTool_colour_on", + Red = "wire_dynamic_button_on_r", + Green = "wire_dynamic_button_on_g", + Blue = "wire_dynamic_button_on_b", + }) + + panel:NumSlider("#WireDynamicButtonTool_value_off", "wire_dynamic_button_value_off", -10, 10, 1) + panel:AddControl("ListBox", { + Label = "#WireDynamicButtonTool_materials_off", + Options = list.Get( "WireDynamicButtonMaterialsOff" ) + } ) + + panel:AddControl("Color", { + Label = "#WireDynamicButtonTool_colour_off", + Red = "wire_dynamic_button_off_r", + Green = "wire_dynamic_button_off_g", + Blue = "wire_dynamic_button_off_b", + }) + + panel:CheckBox("#WireDynamicButtonTool_toggle", "wire_dynamic_button_toggle") + panel:CheckBox("#WireDynamicButtonTool_entityout", "wire_dynamic_button_entityout") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/egp.lua b/garrysmod/addons/feature-wire/lua/wire/stools/egp.lua new file mode 100644 index 0000000..fe2af8d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/egp.lua @@ -0,0 +1,397 @@ +-- Wire EGP by Divran +WireToolSetup.setCategory( "Visuals/Screens" ) +WireToolSetup.open( "egp", "EGP v3", "gmod_wire_egp", nil, "EGPs" ) + +TOOL.ClientConVar["model"] = "models/kobilica/wiremonitorbig.mdl" +TOOL.ClientConVar["type"] = 1 +TOOL.ClientConVar["createflat"] = 1 +TOOL.ClientConVar["weld"] = 0 +TOOL.ClientConVar["weldworld"] = 0 +TOOL.ClientConVar["freeze"] = 1 +TOOL.ClientConVar["emitter_usert"] = 1 +TOOL.ClientConVar["translucent"] = 0 + +cleanup.Register( "wire_egps" ) + +if (SERVER) then + CreateConVar('sbox_maxwire_egps', 5) + + local function SpawnEnt( ply, Pos, Ang, model, class) + if IsValid(ply) and (not ply:CheckLimit("wire_egps")) then return false end + if not ply then ply = game.GetWorld() end -- For Garry's Map Saver + if model and not WireLib.CanModel(ply, model) then return false end + local ent = ents.Create(class) + if (model) then ent:SetModel(model) end + ent:SetAngles(Ang) + ent:SetPos(Pos) + ent:Spawn() + ent:Activate() + + ent:SetPlayer(ply) + ent:SetEGPOwner( ply ) + + if IsValid(ply) then ply:AddCount( "wire_egps", ent ) end + + return ent + end + + local function SpawnEGP( ply, Pos, Ang, model ) + if (EGP.ConVars.AllowScreen:GetInt() == 0) then + ply:ChatPrint("[EGP] The server has blocked EGP screens.") + return + end + local ent = SpawnEnt( ply, Pos, Ang, model, "gmod_wire_egp" ) + if (ent and ent:IsValid()) then + ent.EGP_Duplicated = true + timer.Simple(0.5,function() ent.EGP_Duplicated = nil end) + end + return ent + end + duplicator.RegisterEntityClass("gmod_wire_egp", SpawnEGP, "Pos", "Ang", "model") + local function SpawnHUD( ply, Pos, Ang ) + if (EGP.ConVars.AllowHUD:GetInt() == 0) then + ply:ChatPrint("[EGP] The server has blocked EGP HUDs.") + return + end + local ent = SpawnEnt( ply, Pos, Ang, "models/bull/dynamicbutton.mdl", "gmod_wire_egp_hud" ) + if (ent and ent:IsValid()) then + ent.EGP_Duplicated = true + timer.Simple(0.5,function() ent.EGP_Duplicated = nil end) + end + return ent + end + duplicator.RegisterEntityClass("gmod_wire_egp_hud", SpawnHUD, "Pos", "Ang") + local function SpawnEmitter( ply, Pos, Ang ) + if (EGP.ConVars.AllowEmitter:GetInt() == 0) then + ply:ChatPrint("[EGP] The server has blocked EGP emitters.") + return + end + local ent = SpawnEnt( ply, Pos, Ang, "models/bull/dynamicbutton.mdl", "gmod_wire_egp_emitter" ) + if (ent and ent:IsValid()) then + ent.EGP_Duplicated = true + timer.Simple(0.5,function() ent.EGP_Duplicated = nil end) + end + return ent + end + duplicator.RegisterEntityClass("gmod_wire_egp_emitter",SpawnEmitter,"Pos","Ang" ) + + function TOOL:LeftClick( trace ) + if not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + -- check if the player clicked an emitter + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_egp_emitter" then + trace.Entity:SetUseRT(self:GetClientNumber("emitter_usert")~=0) + return true + end + + -- check if the player clicked a screen + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_egp" then + trace.Entity:SetTranslucent(self:GetClientNumber("translucent")~=0) + return true + end + + local ply = self:GetOwner() + if (not ply:CheckLimit( "wire_egps" )) then return false end + + local ent + local Type = self:GetClientNumber("type") + if (Type == 1) then -- Screen + local model = self:GetClientInfo("model") + if (not util.IsValidModel( model )) then return false end + + ent = SpawnEGP( ply, trace.HitPos, self:GetAngle(trace), model ) + if not IsValid(ent) then return end + + self:SetPos(ent, trace) -- Use WireToolObj's pos code + ent:SetTranslucent(self:GetClientNumber("translucent")~=0) + elseif (Type == 2) then -- HUD + ent = SpawnHUD( ply, trace.HitPos + trace.HitNormal * 0.25, trace.HitNormal:Angle() + Angle(90,0,0) ) + elseif (Type == 3) then -- Emitter + ent = SpawnEmitter( ply, trace.HitPos + trace.HitNormal * 0.25, trace.HitNormal:Angle() + Angle(90,0,0) ) + ent:SetUseRT(self:GetClientNumber("emitter_usert")~=0) + end + + local weld = self:GetClientNumber("weld") ~= 0 and true or false + local weldworld = self:GetClientNumber("weldworld") ~= 0 and true or false + local const + if (trace.Entity) then + if (trace.Entity:IsValid() and weld) then + const = WireLib.Weld( ent, trace.Entity, trace.PhysicsBone, true, false, weldworld ) + elseif (trace.Entity:IsWorld() and weldworld) then + const = WireLib.Weld( ent, trace.Entity, trace.PhysicsBone, true, false, true ) + end + end + + if (self:GetClientNumber("freeze") ~= 0) then + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + phys:Wake() + end + end + + if (not ent or not ent:IsValid()) then return end + undo.Create( "wire_egp" ) + if (const) then undo.AddEntity( const ) end + undo.AddEntity( ent ) + undo.SetPlayer( ply ) + undo.Finish() + + cleanup.Add( ply, "wire_egps", ent ) + + return true + end +end + +if CLIENT then + language.Add( "Tool.wire_egp.name", "E2 Graphics Processor" ) + language.Add( "Tool.wire_egp.desc", "EGP Tool" ) + language.Add( "Tool.wire_egp.left_0", "Create EGP Screen/HUD/Emitter" ) + language.Add( "Tool.wire_egp.right_0", "Link EGP HUD to vehicle" ) + language.Add( "Tool.wire_egp.reload_0", "Open the Reload Menu for several lag fixing options" ) + language.Add( "Tool.wire_egp.1", "Now right click a vehicle." ) + language.Add( "sboxlimit_wire_egps", "You've hit the EGP limit!" ) + language.Add( "Undone_wire_egp", "Undone EGP" ) + language.Add( "Tool_wire_egp_createflat", "Create flat to surface" ) + language.Add( "Tool_wire_egp_weld", "Weld" ) + language.Add( "Tool_wire_egp_weldworld", "Weld to world" ) + language.Add( "Tool_wire_egp_freeze", "Freeze" ) + language.Add( "Tool_wire_egp_drawemitters", "Draw emitters (Clientside)" ) + language.Add( "Tool_wire_egp_emitter_drawdist", "Additional emitter draw distance (Clientside)" ) + language.Add( "Tool_wire_egp_emitter_usert", "Use an RT for emitters (improves performance)" ) + language.Add( "Tool_wire_egp_translucent", "Transparent background" ) +end + +WireToolSetup.SetupLinking(false, "vehicle") -- Generates RightClick, Reload, and DrawHUD functions + +function TOOL:CheckHitOwnClass( trace ) + return IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_egp_hud" -- We only need linking for the hud +end + +-- Remove SetupLinking's reload function +TOOL.Reload = nil + +if CLIENT then + local Menu = {} + local CurEnt + + local function refreshRT( ent ) + ent.GPU:FreeRT() + ent.GPU = GPULib.WireGPU( ent ) + ent:EGP_Update() + end + + local function refreshObjects( ent ) + if ent then + RunConsoleCommand("EGP_Request_Reload",ent:EntIndex()) + else + RunConsoleCommand("EGP_Request_Reload") + end + end + + local function CreateToolReloadMenu() + local pnl = vgui.Create("DFrame") + pnl:SetSize( 200, 114 ) + pnl:Center() + pnl:ShowCloseButton( true ) + pnl:SetDraggable( false ) + pnl:SetTitle( "EGP Reload Menu" ) + pnl:SetDeleteOnClose( false ) + + local w = 200/2-4 + local h = 20 + + local x1, x2 = 2, 200/2 + + local lbl = vgui.Create("DLabel",pnl) + lbl:SetPos( x1+2, 24 ) + lbl:SetText("Current Screen:") + lbl:SizeToContents() + + local lbl2 = vgui.Create("DLabel",pnl) + lbl2:SetPos( x2, 24 ) + lbl2:SetText("All screens on map:") + lbl2:SizeToContents() + + local btn = vgui.Create("DButton",pnl) + btn:SetText("RenderTarget") + btn:SetPos( x1, 40 ) + btn:SetSize( w, h ) + function btn:DoClick() + pnl:SetVisible( false ) + + refreshRT( CurEnt ) + + LocalPlayer():ChatPrint("[EGP] RenderTarget reloaded.") + end + + local btn2 = vgui.Create("DButton",pnl) + btn2:SetText("Objects") + btn2:SetPos( x1, 65 ) + btn2:SetSize( w, h ) + function btn2:DoClick() + pnl:SetVisible( false ) + + refreshObjects( CurEnt ) + + LocalPlayer():ChatPrint("[EGP] Requesting...") + end + + local btn3 = vgui.Create("DButton",pnl) + btn3:SetText("Both") + btn3:SetPos( x1, 90 ) + btn3:SetSize( w, h ) + function btn3:DoClick() + pnl:SetVisible( false ) + if (CurEnt:GetClass() == "gmod_wire_egp_hud" and CurEnt:GetClass() == "gmod_wire_egp_emitter") then + LocalPlayer():ChatPrint("[EGP] Entity does not have a RenderTarget") + else + refreshRT( CurEnt ) + + LocalPlayer():ChatPrint("[EGP] RenderTarget reloaded.") + end + LocalPlayer():ChatPrint("[EGP] Requesting object reload...") + refreshObjects( CurEnt ) + end + + local btn4 = vgui.Create("DButton",pnl) + btn4:SetText("RenderTarget") + btn4:SetPos( x2, 40 ) + btn4:SetSize( w, h ) + function btn4:DoClick() + pnl:SetVisible( false ) + local tbl = ents.FindByClass("gmod_wire_egp") + for _,v in pairs( tbl ) do + refreshRT( v ) + end + LocalPlayer():ChatPrint("[EGP] RenderTargets reloaded on all screens on the map.") + end + + local btn5 = vgui.Create("DButton",pnl) + btn5:SetText("Objects") + btn5:SetPos( x2, 65 ) + btn5:SetSize( w, h ) + function btn5:DoClick() + pnl:SetVisible( false ) + LocalPlayer():ChatPrint("[EGP] Requesting...") + refreshObjects() + end + + local btn6 = vgui.Create("DButton",pnl) + btn6:SetText("Both") + btn6:SetPos( x2, 90 ) + btn6:SetSize( w, h ) + function btn6:DoClick() + pnl:SetVisible( false ) + local tbl = ents.FindByClass("gmod_wire_egp") + for _,v in pairs( tbl ) do + refreshRT( v ) + end + LocalPlayer():ChatPrint("[EGP] RenderTargets reloaded on all screens on the map.") + LocalPlayer():ChatPrint("[EGP] Requesting object reload...") + refreshObjects() + end + + pnl:MakePopup() + pnl:SetVisible( false ) + Menu = { Panel = pnl, SingleRender = btn, SingleObjects = btn2, SingleBoth = btn3, AllRender = btn4, AllObjects = btn5, AllBoth = btn6 } + end + + function TOOL:LeftClick( trace ) return (not trace.Entity or (trace.Entity and not trace.Entity:IsPlayer())) end + function TOOL:Reload( trace ) + + if (not Menu.Panel) then CreateToolReloadMenu() end + + Menu.Panel:SetVisible( true ) + if (not EGP:ValidEGP( trace.Entity )) then + Menu.SingleRender:SetEnabled( false ) + Menu.SingleObjects:SetEnabled( false ) + Menu.SingleBoth:SetEnabled( false ) + else + CurEnt = trace.Entity + if (CurEnt:GetClass() == "gmod_wire_egp_hud" or CurEnt:GetClass() == "gmod_wire_egp_emitter") then + Menu.SingleRender:SetEnabled( false ) + Menu.SingleBoth:SetEnabled( false) + else + Menu.SingleRender:SetEnabled( true ) + Menu.SingleBoth:SetEnabled( true ) + end + Menu.SingleObjects:SetEnabled( true ) + end + end + + function TOOL.BuildCPanel(panel) + if not EGP then return end + panel:SetSpacing( 10 ) + panel:SetName( "E2 Graphics Processor" ) + + WireDermaExts.ModelSelect(panel, "wire_egp_model", list.Get( "WireScreenModels" ), 5) + + local cbox = {} + cbox.Label = "Screen Type" + cbox.MenuButton = 0 + cbox.Options = {} + cbox.Options.Screen = { wire_egp_type = 1 } + cbox.Options.HUD = { wire_egp_type = 2 } + cbox.Options.Emitter = { wire_egp_type = 3 } + panel:AddControl("ComboBox", cbox) + + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_createflat",Command = "wire_egp_createflat"}) + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_weld",Command="wire_egp_weld"}) + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_weldworld",Command="wire_egp_weldworld"}) + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_freeze",Command="wire_egp_freeze"}) + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_translucent",Command="wire_egp_translucent"}) + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_drawemitters",Command="wire_egp_drawemitters"}) + panel:AddControl("Checkbox", {Label = "#Tool_wire_egp_emitter_usert",Command="wire_egp_emitter_usert"}) + + local slider = vgui.Create("DNumSlider") + slider:SetText("#Tool_wire_egp_emitter_drawdist") + slider:SetConVar("wire_egp_emitter_drawdist") + slider:SetMin( 0 ) + slider:SetMax( 5000 ) + slider:SetDecimals( 0 ) + panel:AddItem(slider) + end +end + +function TOOL:UpdateGhost( ent, ply ) + if not IsValid(ent) then return end + local trace = ply:GetEyeTrace() + + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then + ent:SetNoDraw( true ) + return + end + + local Type = self:GetClientNumber("type") + if (Type == 1) then + ent:SetAngles(self:GetAngle(trace)) + self:SetPos(ent, trace) + elseif (Type == 2 or Type == 3) then + ent:SetPos( trace.HitPos + trace.HitNormal * 0.25 ) + ent:SetAngles( trace.HitNormal:Angle() + Angle(90,0,0) ) + end + + ent:SetNoDraw( false ) +end + +function TOOL:Think() + if self.HasLinked then + if not self:GetOwner():KeyDown(IN_SPEED) then self:SetStage(0) end + if self:GetStage() == 0 then self.HasLinked = false end + end + + local Type = self:GetClientNumber("type") + if (not self.GhostEntity or not self.GhostEntity:IsValid()) then + local trace = self:GetOwner():GetEyeTrace() + self:MakeGhostEntity( Model("models/bull/dynamicbutton.mdl"), trace.HitPos, trace.HitNormal:Angle() + Angle(90,0,0) ) + elseif (not self.GhostEntity.Type or self.GhostEntity.Type ~= Type or (self.GhostEntity.Type == 1 and self.GhostEntity:GetModel() ~= self:GetClientInfo("model"))) then + if (Type == 1) then + self.GhostEntity:SetModel(self:GetClientInfo("model")) + elseif (Type == 2 or Type == 3) then + self.GhostEntity:SetModel("models/bull/dynamicbutton.mdl") + end + self.GhostEntity.Type = Type + end + self:UpdateGhost( self.GhostEntity, self:GetOwner() ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/emarker.lua b/garrysmod/addons/feature-wire/lua/wire/stools/emarker.lua new file mode 100644 index 0000000..821171f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/emarker.lua @@ -0,0 +1,102 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "emarker", "Entity Marker", "gmod_wire_emarker", nil, "Entity Markers" ) + +if CLIENT then + language.Add( "Tool.wire_emarker.name", "Entity Marker Tool (Wire)" ) + language.Add( "Tool.wire_emarker.desc", "Spawns an Entity Marker for use with the wire system." ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Create Entity Marker/Display Link Info" }, + { name = "right_0", stage = 0, text = "Link Entity Marker" }, + { name = "reload_0", stage = 0, text = "Unlink Entity Marker" }, + { name = "right_1", stage = 1, text = "Now select the entity to link to" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 30 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", +} + +if SERVER then + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL:LeftClick(trace) + if not trace.HitPos or trace.Entity:IsPlayer() then return false end + if ( CLIENT ) then return true end + if not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + self:SetStage(0) + local ply = self:GetOwner() + + if ( trace.Entity:GetClass() == "gmod_wire_emarker" ) then + self.marker = trace.Entity + + if ( !self.marker.mark || !self.marker.mark:IsValid() ) then + ply:PrintMessage(HUD_PRINTTALK, "Entity Marker not linked") + return false + end + + ply:PrintMessage( HUD_PRINTTALK, "Linked model: " .. self.marker.mark:GetModel() ) + self:GetWeapon():SetNWEntity( "WireEntityMark", self.marker.mark ) + self:GetWeapon():SetNWEntity( "WireEntityMarker", self.marker ) + else + local ent = self:LeftClick_Make( trace, ply ) + return self:LeftClick_PostMake( ent, ply, trace ) + end + return true +end + +function TOOL:RightClick(trace) + if not trace.HitPos or not IsValid(trace.Entity) or trace.Entity:IsPlayer() then return false end + if ( CLIENT ) then return true end + + if ( self:GetStage() == 0 && trace.Entity:GetClass() == "gmod_wire_emarker" ) then + self.marker = trace.Entity + self:SetStage(1) + return true + elseif ( self:GetStage() == 1 ) then + self.marker:LinkEMarker(trace.Entity) + self:SetStage(0) + self:GetOwner():PrintMessage( HUD_PRINTTALK,"Entity Marker linked" ) + self:GetWeapon():SetNWEntity( "WireEntityMark", self.marker.mark ) + self:GetWeapon():SetNWEntity( "WireEntityMarker", self.marker ) + return true + else + return false + end +end + +function TOOL:Reload(trace) + if not trace.HitPos or trace.Entity:IsPlayer() then return false end + if ( CLIENT ) then return true end + + self:SetStage(0) + local marker = trace.Entity + if not IsValid(marker) then return false end + if (marker:GetClass() == "gmod_wire_emarker") then + marker:UnLinkEMarker() + self:GetOwner():PrintMessage( HUD_PRINTTALK,"Entity Marker unlinked" ) + self:GetWeapon():SetNWEntity( "WireEntityMark", self.marker ) // Substitute for null, which won't set + self:GetWeapon():SetNWEntity( "WireEntityMarker", self.marker ) // Set same point so line won't draw + return true + end +end + +function TOOL:DrawHUD() + local mark = self:GetWeapon():GetNWEntity( "WireEntityMark" ) + local marker = self:GetWeapon():GetNWEntity( "WireEntityMarker" ) + if not IsValid(mark) or not IsValid(marker) then return end + + local markerpos = marker:GetPos():ToScreen() + local markpos = mark:GetPos():ToScreen() + if ( markpos.x > 0 && markpos.y > 0 && markpos.x < ScrW() && markpos.y < ScrH( ) ) then + surface.SetDrawColor( 255, 255, 100, 255 ) + surface.DrawLine(markerpos.x, markerpos.y, markpos.x, markpos.y) + end +end + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_emarker") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/exit_point.lua b/garrysmod/addons/feature-wire/lua/wire/stools/exit_point.lua new file mode 100644 index 0000000..1ce4a83 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/exit_point.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Vehicle Control" ) +WireToolSetup.open( "exit_point", "Vehicle Exit Point", "gmod_wire_exit_point", nil, "Exit Points" ) + +if CLIENT then + language.Add( "tool."..TOOL.Mode..".name", TOOL.Name.." Tool (Wire)" ) + language.Add( "tool."..TOOL.Mode..".desc", "Spawns a "..TOOL.Name ) + + WireToolSetup.setToolMenuIcon( "icon16/door_out.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(6) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_range.mdl", +} + +WireToolSetup.SetupLinking(false, "vehicle") + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_exit_point", true) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/explosive.lua b/garrysmod/addons/feature-wire/lua/wire/stools/explosive.lua new file mode 100644 index 0000000..2fe9311 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/explosive.lua @@ -0,0 +1,84 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "explosive", "Explosive", "gmod_wire_explosive", nil, "Explosives" ) + +TOOL.ClientConVar = { + model = "models/props_c17/oildrum001_explosive.mdl", + effect = "Explosion", + trigger = 1, -- Wire input value to cause the explosion + damage = 200, -- Damage to inflict + radius = 300, + removeafter = 0, + affectother = 0, + notaffected = 0, + delaytime = 0, + delayreloadtime = 0, + maxhealth = 100, + bulletproof = 0, + explosionproof = 0, + explodeatzero = 1, + resetatexplode = 1, + fireeffect = 1, + coloreffect = 1, + invisibleatzero = 0, +} +TOOL.ReloadSetsModel = true + +if ( CLIENT ) then + language.Add( "Tool.wire_explosive.name", "Wired Explosives Tool" ) + language.Add( "Tool.wire_explosive.desc", "Creates a variety of different explosives for wire system." ) + language.Add( "Tool.wire_explosive.trigger", "Trigger value:" ) + language.Add( "Tool.wire_explosive.damage", "Damage:" ) + language.Add( "Tool.wire_explosive.radius", "Blast radius:" ) + language.Add( "Tool.wire_explosive.delaytime", "On fire time (delay after triggered before explosion):" ) + language.Add( "Tool.wire_explosive.delayreloadtime", "Delay after explosion before it can be triggered again:" ) + language.Add( "Tool.wire_explosive.removeafter", "Remove on explosion" ) + language.Add( "Tool.wire_explosive.affectother", "Damaged/moved by other wired explosives" ) + language.Add( "Tool.wire_explosive.notaffected", "Not moved by any phyiscal damage" ) + language.Add( "Tool.wire_explosive.maxhealth", "Max health:" ) + language.Add( "Tool.wire_explosive.bulletproof", "Bullet proof" ) + language.Add( "Tool.wire_explosive.explosionproof", "Explosion proof" ) + language.Add( "Tool.wire_explosive.explodeatzero", "Explode when health = zero" ) + language.Add( "Tool.wire_explosive.resetatexplode", "Reset health then" ) + language.Add( "Tool.wire_explosive.fireeffect", "Enable fire effect on triggered" ) + language.Add( "Tool.wire_explosive.coloreffect", "Enable color change effect on damage" ) + language.Add( "Tool.wire_explosive.invisibleatzero", "Become invisible when health reaches 0" ) + TOOL.Information = { + { name = "left", text = "Create " .. TOOL.Name }, + { name = "right", text = "Update " .. TOOL.Name }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("trigger"), self:GetClientNumber("damage"), self:GetClientNumber("delaytime"), self:GetClientNumber("removeafter")~=0, + self:GetClientNumber("radius"), self:GetClientNumber("affectother")~=0, self:GetClientNumber("notaffected")~=0, self:GetClientNumber("delayreloadtime"), + self:GetClientNumber("maxhealth"), self:GetClientNumber("bulletproof")~=0, self:GetClientNumber("explosionproof")~=0, self:GetClientNumber("fallproof")~=0, + self:GetClientNumber("explodeatzero")~=0, self:GetClientNumber("resetatexplode")~=0, self:GetClientNumber("fireeffect")~=0, self:GetClientNumber("coloreffect")~=0, + self:GetClientNumber("invisibleatzero")~=0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_explosive") + ModelPlug_AddToCPanel(panel, "Explosive", "wire_explosive", nil, 3) + panel:NumSlider("#Tool.wire_explosive.trigger", "wire_explosive_trigger", -10, 10, 0 ) + panel:NumSlider("#Tool.wire_explosive.damage", "wire_explosive_damage", 0, 500, 0 ) + panel:NumSlider("#Tool.wire_explosive.radius", "wire_explosive_radius", 1, 1500, 0 ) + panel:NumSlider("#Tool.wire_explosive.delaytime", "wire_explosive_delaytime", 0, 60, 2 ) + panel:NumSlider("#Tool.wire_explosive.delayreloadtime", "wire_explosive_delayreloadtime", 0, 60, 2 ) + panel:CheckBox("#Tool.wire_explosive.removeafter","wire_explosive_removeafter") + panel:CheckBox("#Tool.wire_explosive.affectother","wire_explosive_affectother") + panel:CheckBox("#Tool.wire_explosive.notaffected","wire_explosive_notaffected") + panel:NumSlider("#Tool.wire_explosive.maxhealth", "wire_explosive_maxhealth", 0, 500, 0 ) + panel:CheckBox("#Tool.wire_explosive.bulletproof","wire_explosive_bulletproof") + panel:CheckBox("#Tool.wire_explosive.explosionproof","wire_explosive_explosionproof") + panel:CheckBox("#Tool.wire_explosive.explodeatzero","wire_explosive_explodeatzero") + panel:CheckBox("#Tool.wire_explosive.resetatexplode","wire_explosive_resetatexplode") + panel:CheckBox("#Tool.wire_explosive.fireeffect","wire_explosive_fireeffect") + panel:CheckBox("#Tool.wire_explosive.coloreffect","wire_explosive_coloreffect") + panel:CheckBox("#Tool.wire_explosive.invisibleatzero","wire_explosive_invisibleatzero") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/expression2.lua b/garrysmod/addons/feature-wire/lua/wire/stools/expression2.lua new file mode 100644 index 0000000..fb83abb --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/expression2.lua @@ -0,0 +1,1029 @@ +WireToolSetup.setCategory( "Chips, Gates" ) +WireToolSetup.open("expression2", "Expression 2", "gmod_wire_expression2", nil, "Expression2s") + +if CLIENT then + language.Add("Tool.wire_expression2.name", "Expression 2 Tool (Wire)") + language.Add("Tool.wire_expression2.desc", "Spawns an Expression 2 chip for use with the wire system.") + language.Add("sboxlimit_wire_expressions", "You've hit the Expression limit!") + + TOOL.Information = { + { name = "left", text = "Create " .. TOOL.Name }, + { name = "right", text = "Open " .. TOOL.Name .. " in Editor" }, + } + + --WireToolSetup.setToolMenuIcon( "beer/wiremod/gate_e2" ) + WireToolSetup.setToolMenuIcon( "vgui/e2logo" ) +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/gate_e2.mdl", + modelsize = "", + scriptmodel = "", + select = "", + autoindent = 1, +} + +TOOL.MaxLimitName = "wire_expressions" +WireToolSetup.BaseLang() + +if SERVER then + CreateConVar('sbox_maxwire_expressions', 20) + + function TOOL:MakeEnt(ply, model, Ang, trace) + return MakeWireExpression2(ply, trace.HitPos, Ang, model) + end + + function TOOL:PostMake(ent) + self:Upload(ent) + end + + function TOOL:LeftClick_Update( trace ) + self:Upload(trace.Entity) + end + + function TOOL:Reload(trace) + if trace.Entity:IsPlayer() then return false end + if CLIENT then return true end + + local player = self:GetOwner() + + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_expression2" and trace.Entity.context then + trace.Entity:Reset() + return true + else + return false + end + end + + util.AddNetworkString("WireExpression2_OpenEditor") + function TOOL:RightClick(trace) + if trace.Entity:IsPlayer() then return false end + + local player = self:GetOwner() + + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_expression2" then + self:Download(player, trace.Entity) + return true + end + + net.Start("WireExpression2_OpenEditor") net.Send(player) + return false + end + + function TOOL:Upload(ent) + WireLib.Expression2Upload( self:GetOwner(), ent ) + end + + function WireLib.Expression2Upload( ply, target, filepath ) + if not IsValid( target ) then error( "Invalid entity specified" ) end + net.Start("wire_expression2_tool_upload") + net.WriteUInt(target:EntIndex(), 16) + net.WriteString( filepath or "" ) + net.WriteInt( target.buffer and tonumber(util.CRC( target.buffer )) or -1, 32 ) -- send the hash so we know if there's any difference + net.Send(ply) + end + + function TOOL:Download(ply, ent) + WireLib.Expression2Download(ply, ent, nil, true) + end + + -- -------------------------------------------------------------------------------------------------------------------------- + -- -------------------------------------------------------------------------------------------------------------------------- + -- -------------------------------------------------------------------------------------------------------------------------- + -- ---------------------------------------------------- UPLOAD/DOWNLOAD ----------------------------------------------------- + -- -------------------------------------------------------------------------------------------------------------------------- + -- -------------------------------------------------------------------------------------------------------------------------- + -- -------------------------------------------------------------------------------------------------------------------------- + util.AddNetworkString("wire_expression2_tool_upload") + util.AddNetworkString("wire_expression2_editor_status") + util.AddNetworkString("wire_expression2_download") + util.AddNetworkString("wire_expression2_download_wantedfiles") + util.AddNetworkString("wire_expression2_download_wantedfiles_list") + util.AddNetworkString("wire_expression2_upload") + util.AddNetworkString("wire_expression2_progress") + + -- ------------------------------------------------------------ + -- Serverside Send + -- ------------------------------------------------------------ + function WireLib.Expression2Download(ply, targetEnt, wantedfiles, uploadandexit) + if not IsValid(targetEnt) or targetEnt:GetClass() ~= "gmod_wire_expression2" then + WireLib.AddNotify(ply, "Invalid Expression chip specified.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + return + end + + if not IsValid(ply) or not ply:IsPlayer() then -- wtf + error("Invalid player entity (wtf??). This should never happen. " .. tostring(ply), 0) + end + + if not hook.Run( "CanTool", ply, WireLib.dummytrace(targetEnt), "wire_expression2") then + WireLib.AddNotify(ply, "You're not allowed to download from this Expression (ent index: " .. targetEnt:EntIndex() .. ").", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + return + end + + local main, includes = targetEnt:GetCode() + if not includes or not next(includes) then -- There are no includes + local datastr = WireLib.von.serialize({ { targetEnt.name, main } }) + local numpackets = math.ceil(#datastr / 64000) + + local n = 0 + for i = 1, #datastr, 64000 do + timer.Simple( n, function() + if not IsValid( targetEnt ) then return end + net.Start("wire_expression2_download") + net.WriteEntity(targetEnt) + net.WriteBit(uploadandexit or false) + net.WriteUInt(numpackets, 16) + net.WriteString(datastr:sub(i, i + 63999)) + net.Send(ply) + end) + n = n + 1 + end + elseif not wantedfiles then + local data = {} + for k, v in pairs(includes) do + data[#data + 1] = k + end + + local datastr = WireLib.von.serialize(data) + net.Start("wire_expression2_download_wantedfiles_list") + net.WriteEntity(targetEnt) + net.WriteBit(uploadandexit or false) + net.WriteString(datastr) + net.Send(ply) + else + local data = { {}, {} } + if wantedfiles.main then + data[1] = { targetEnt.name, main } + wantedfiles.main = nil + end + + for i = 1, #wantedfiles do + local path = wantedfiles[i] + if includes[path] then + data[2][path] = includes[path] + else + WireLib.AddNotify(ply, "Nonexistant file requested ('" .. tostring(path) .. "'). File skipped.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + end + end + + local datastr = WireLib.von.serialize(data) + local numpackets = math.ceil(#datastr / 64000) + local n = 0 + for i = 1, #datastr, 64000 do + timer.Simple( n, function() + if not IsValid( targetEnt ) then return end + net.Start("wire_expression2_download") + net.WriteEntity(targetEnt) + net.WriteBit(uploadandexit or false) + net.WriteUInt(numpackets, 16) + net.WriteString(datastr:sub(i, i + 63999)) + net.Send(ply) + end) + n = n + 1 + end + end + end + + local wantedfiles = {} + net.Receive("wire_expression2_download_wantedfiles", function(len, ply) + local toent = net.ReadEntity() + local uploadandexit = net.ReadBit() ~= 0 + local numpackets = net.ReadUInt(16) + + if not IsValid(toent) or toent:GetClass() ~= "gmod_wire_expression2" then + WireLib.AddNotify(ply, "Invalid entity specified to wire_expression2_download_wantedfiles. Download aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + return + end + + if not wantedfiles[ply] then wantedfiles[ply] = {} end + table.insert(wantedfiles[ply], net.ReadString()) + if numpackets <= #wantedfiles[ply] then + local ok, ret = pcall(WireLib.von.deserialize, E2Lib.decode(table.concat(wantedfiles[ply]))) + wantedfiles[ply] = nil + if not ok then + WireLib.AddNotify(ply, "Expression 2 download failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + print("Expression 2 download failed! Error message:\n" .. ret) + return + end + + WireLib.Expression2Download(ply, toent, ret, uploadandexit) + end + end) + + -- ------------------------------------------------------------ + -- Serverside Receive + -- ------------------------------------------------------------ + local uploads = {} + local upload_ents = {} + net.Receive("wire_expression2_upload", function(len, ply) + local toent = Entity(net.ReadUInt(16)) + local numpackets = net.ReadUInt(16) + + if (not IsValid(toent) or toent:GetClass() ~= "gmod_wire_expression2") then + if uploads[ply] then -- this is to prevent notification spam due to the net library automatically limiting its own transfer rate so that the messages arrive late + uploads[ply] = nil + upload_ents[ply] = nil + WireLib.AddNotify(ply, "Invalid Expression chip specified. Upload aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + end + return + end + + if not hook.Run( "CanTool", ply, WireLib.dummytrace( toent ), "wire_expression2" ) then + WireLib.AddNotify(ply, "You are not allowed to upload to the target Expression chip. Upload aborted.", NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + return + end + + if upload_ents[ply] ~= toent then -- a new upload was started, abort previous + uploads[ply] = nil + end + + upload_ents[ply] = toent + + if not uploads[ply] then uploads[ply] = {} end + uploads[ply][#uploads[ply]+1] = net.ReadString() + if numpackets <= #uploads[ply] then + local datastr = E2Lib.decode(table.concat(uploads[ply])) + uploads[ply] = nil + local ok, ret = pcall(WireLib.von.deserialize, datastr) + + if not ok then + WireLib.AddNotify(ply, "Expression 2 upload failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + print("Expression 2 upload failed! Error message:\n" .. ret) + return + end + + local code = ret[1] + + local includes = {} + for k, v in pairs(ret[2]) do + includes[k] = v + end + + local filepath = ret[3] + + toent:Setup(code, includes, nil, nil, filepath) + end + end) + + -- ------------------------------------------------------------ + -- Stuff for the remote updater + -- ------------------------------------------------------------ + + local antispam = {} + -- Returns true if they are spamming, false if they can go ahead and use it + local function canhas(ply) + if not antispam[ply] then antispam[ply] = 0 end + if antispam[ply] < CurTime() then + antispam[ply] = CurTime() + 1 + return false + else + WireLib.ClientError("This command has a 1 second anti spam protection. Try again in " .. math.Round(antispam[ply] - CurTime(), 2) .. " seconds.", ply) + return true + end + end + + concommand.Add("wire_expression_forcehalt", function(player, command, args) + local E2 = tonumber(args[1]) + if not E2 then return end + E2 = Entity(E2) + if not IsValid(E2) or E2:GetClass() ~= "gmod_wire_expression2" then return end + if canhas(player) then return end + if E2.error then return end + if hook.Run( "CanTool", player, WireLib.dummytrace( E2 ), "wire_expression2", "halt execution" ) then + E2:PCallHook("destruct") + E2:Error("Execution halted (Triggered by: " .. player:Nick() .. ")", "Execution halted") + if E2.player ~= player then + WireLib.AddNotify(player, "Expression halted.", NOTIFY_GENERIC, 5, math.random(1, 5)) + player:PrintMessage(HUD_PRINTCONSOLE, "Expression halted.") + end + else + WireLib.ClientError("You do not have permission to halt this E2.", player) + end + end) + + concommand.Add("wire_expression_requestcode", function(player, command, args) + local E2 = tonumber(args[1]) + if not E2 then return end + E2 = Entity(E2) + if canhas(player) then return end + if not IsValid(E2) or E2:GetClass() ~= "gmod_wire_expression2" then return end + if hook.Run( "CanTool", player, WireLib.dummytrace( E2 ), "wire_expression2", "request code" ) then + WireLib.Expression2Download(player, E2) + WireLib.AddNotify(player, "Downloading code...", NOTIFY_GENERIC, 5, math.random(1, 4)) + player:PrintMessage(HUD_PRINTCONSOLE, "Downloading code...") + if E2.player ~= player then + WireLib.AddNotify(E2.player, player:Nick() .. " is reading your E2 '" .. E2.name .. "' using remote updater.", NOTIFY_GENERIC, 5, math.random(1, 4)) + E2.player:PrintMessage(HUD_PRINTCONSOLE, player:Nick() .. " is reading your E2 '" .. E2.name .. "' using remote updater.") + end + else + WireLib.ClientError("You do not have permission to read this E2.", player) + end + end) + + concommand.Add("wire_expression_reset", function(player, command, args) + local E2 = tonumber(args[1]) + if not E2 then return end + E2 = Entity(E2) + if not IsValid(E2) or E2:GetClass() ~= "gmod_wire_expression2" then return end + if canhas(player) then return end + if hook.Run( "CanTool", player, WireLib.dummytrace( E2 ), "wire_expression2", "reset" ) then + if E2.context.data.last or E2.first then return end + + E2:Reset() + + WireLib.AddNotify(player, "Expression reset.", NOTIFY_GENERIC, 5, math.random(1, 4)) + player:PrintMessage(HUD_PRINTCONSOLE, "Expression reset.") + if E2.player ~= player then + WireLib.AddNotify(E2.player, player:Nick() .. " reset your E2 '" .. E2.name .. "' using remote updater.", NOTIFY_GENERIC, 5, math.random(1, 4)) + E2.player:PrintMessage(HUD_PRINTCONSOLE, player:Nick() .. " reset your E2 '" .. E2.name .. "' using remote updater.") + end + else + WireLib.ClientError("You do not have permission to reset this E2.", player) + end + end) + + ------------------------------------------------------ + -- Syncing ops for remote uploader (admin only) + -- Server part + ------------------------------------------------------ + + local players_synced = {} + util.AddNetworkString( "wire_expression_sync_ops" ) + concommand.Add("wire_expression_ops_sync", function(player,command,args) + if not player:IsAdmin() then return end + + local bool = args[1] ~= "0" + + if bool then + players_synced[player] = true + else + players_synced[player] = nil + end + + if next( players_synced ) and not timer.Exists( "wire_expression_ops_sync" ) then + + timer.Create( "wire_expression_ops_sync",0.2,0,function() + local plys = {} + for ply,_ in pairs( players_synced ) do + if not IsValid( ply ) then + players_synced[ply] = nil + else + plys[#plys+1] = ply + end + end + if not next( players_synced ) then + timer.Remove( "wire_expression_ops_sync" ) + end + + local E2s = ents.FindByClass("gmod_wire_expression2") + + net.Start( "wire_expression_sync_ops" ) + net.WriteInt( #E2s, 16 ) + for i=1,#E2s do + net.WriteEntity( E2s[i] ) + local data = E2s[i]:GetOverlayData() + net.WriteDouble( data.prfbench ) + net.WriteDouble( data.prfcount ) + net.WriteDouble( data.timebench ) + end + net.Send( plys ) + end) + elseif not next( players_synced ) and timer.Exists( "wire_expression_ops_sync" ) then + timer.Remove( "wire_expression_ops_sync" ) + end + + end) +elseif CLIENT then + ------------------------------------------------------ + -- Syncing ops for remote uploader (admin only) + -- Client part + ------------------------------------------------------ + net.Receive( "wire_expression_sync_ops", function( len ) + local num = net.ReadInt( 16 ) + for i=1,num do + local E2 = net.ReadEntity() + if E2.GetOverlayData and E2.SetOverlayData then + local prfbench = net.ReadDouble() + local prfcount = net.ReadDouble() + local timebench = net.ReadDouble() + + local data = E2:GetOverlayData() or {} + + E2:SetOverlayData( { + txt = data.txt or "(generic)", + error = data.error or false, + prfbench = prfbench, + prfcount = prfcount, + timebench = timebench + } ) + end + end + end ) + + -------------------------------------------------------------- + -- Clientside Send + -------------------------------------------------------------- + + local queue_max = 0 + local queue = {} + local sending = false + + local upload_queue + + -- send next E2 + local function next_queue() + local queue_progress = (queue_max > 1 and (1-((#queue-1) / queue_max)) * 100 or nil) + Expression2SetProgress( nil, queue_progress ) + table.remove( queue, 1 ) + + -- Clear away all removed E2s from the queue + while true do + if #queue == 0 then break end + if queue[1].timeStarted + 2 < CurTime() -- only clear it if more than 2 seconds has passed since the upload was requested (if the user has high ping) + and not IsValid( Entity(queue[1].targetEnt) ) then + table.remove( queue, 1 ) + else + break + end + end + + timer.Simple( 1, function() -- wait a while before doing anything so stuff doesn't lag + if #queue == 0 then + Expression2SetProgress() + sending = false -- done sending + queue_max = 0 + else + upload_queue() -- send next + end + end) + end + + upload_queue = function(first) + local q = queue[1] + + local targetEnt = q.targetEnt + local datastr = q.datastr + local timeStarted = q.timeStarted + + local queue_progress = (queue_max > 1 and (1-((#queue-1) / queue_max)) * 100 or nil) + Expression2SetProgress(1, queue_progress) + + local numpackets = math.ceil(#datastr / 64000) + local delay = first and 0.01 or 1 + local packet = 0 + local exited = false + for i = 1, #datastr, 64000 do + timer.Simple( delay, function() + packet = packet + 1 + if timeStarted + 2 < CurTime() and -- only remove the E2 from the queue if more than 2 seconds has passed since the upload was requested (if the user has high ping) + not IsValid(Entity( targetEnt )) then + if exited then + return + else + exited = true + next_queue() + return + end + end + + if packet == numpackets then + next_queue() + end + + local queue_progress = (queue_max > 1 and (1-((#queue-1) / queue_max)) * 100 or nil) + Expression2SetProgress( packet / numpackets * 100, queue_progress ) + + net.Start("wire_expression2_upload") + net.WriteUInt(targetEnt, 16) + net.WriteUInt(numpackets, 16) + net.WriteString(datastr:sub(i, i + 63999)) + net.SendToServer() + end) + delay = delay + 1 + end + end + + function WireLib.Expression2Upload(targetEnt, code, filepath) + if not targetEnt then targetEnt = LocalPlayer():GetEyeTrace().Entity or NULL end + if isentity(targetEnt) then + if not IsValid(targetEnt) then return end -- We don't know what entity its going to + targetEnt = targetEnt:EntIndex() + end + + for i=1,#queue do + if queue[i].targetEnt == targetEnt then + WireLib.AddNotify("You're already uploading that E2!", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + return + end + end + + if not code and not wire_expression2_editor then return end -- If the player leftclicks without opening the editor or cpanel (first spawn) + code = code or wire_expression2_editor:GetCode() + filepath = filepath or wire_expression2_editor:GetChosenFile() + local err, includes + + if e2_function_data_received then + err, includes = wire_expression2_validate(code) + if err then + WireLib.AddNotify(err, NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + return + end + else + WireLib.AddNotify("The Expression 2 function data has not been transferred to the client yet;\n uploading the E2 to the server for validation.\nNote that any includes will not be sent. You must wait for the function data to finish\n transmitting before you are able to use includes.", NOTIFY_ERROR, 14, NOTIFYSOUND_DRIP3) + + -- This message is so long, the user might not be able to read it fast enough. Printing it to the console so they can read it there, too. + Msg("The Expression 2 function data has not been transferred to the client yet; uploading the E2 to the server for validation.\nNote that any includes will not be sent. You must wait for the function data to finish transmitting before you are able to use includes.\n") + end + + local datastr + if includes then + local newincludes = {} + for k, v in pairs(includes) do + newincludes[k] = v + end + + datastr = E2Lib.encode(WireLib.von.serialize({ code, newincludes, filepath })) + else + datastr = E2Lib.encode(WireLib.von.serialize({ code, {}, filepath })) + end + + queue[#queue+1] = { + targetEnt = targetEnt, + datastr = datastr, + timeStarted = CurTime() + } + + queue_max = queue_max + 1 + + if sending then return end + sending = true + upload_queue(true) // true means its the first packet, suppressing the delay + end + + net.Receive("wire_expression2_tool_upload", function(len, ply) + local ent = net.ReadUInt(16) + local filepath = net.ReadString() + local hash = net.ReadInt(32) + if filepath ~= "" then + if filepath and file.Exists(filepath, "DATA") then + local str = file.Read(filepath) + local strhash = tonumber(util.CRC(str)) + if hash ~= strhash then -- Only upload if we need to + WireLib.Expression2Upload(ent,str,filepath) + end + end + else + WireLib.Expression2Upload(ent) + end + end) + + -------------------------------------------------------------- + -- Clientside Receive + -------------------------------------------------------------- + local buffer, count = "", 0 + local current_ent + net.Receive("wire_expression2_download", function(len) + local ent = net.ReadEntity() + + if IsValid( current_ent ) and IsValid( ent ) and ent ~= current_ent then + -- different E2, reset buffer + buffer = "" + count = 0 + end + + local uploadandexit = net.ReadBit() ~= 0 + local numpackets = net.ReadUInt(16) + + buffer = buffer .. net.ReadString() + count = count + 1 + + Expression2SetProgress(count / numpackets * 100, nil, "Downloading") + if numpackets <= count then + local ok, ret = pcall(WireLib.von.deserialize, buffer) + buffer, count = "", 0 + if not ok then + WireLib.AddNotify(ply, "Expression 2 download failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + return + end + local files = ret + + local name, main + if files[1] then + name = files[1][1] + main = files[1][2] + end + + if not wire_expression2_editor then initE2Editor() end + + if uploadandexit then + wire_expression2_editor.chip = ent + end + + if files[2] and next(files[2]) then + for k, v in pairs(files[2]) do + wire_expression2_editor:Open(k, v) + end + end + + wire_expression2_editor:Open(name, main) + timer.Create("wire_expression2_reset_progress", 0.75, 1, Expression2SetProgress) + end + end) + + net.Receive("wire_expression2_download_wantedfiles_list", function(len) + local ent = net.ReadEntity() + local uploadandexit = net.ReadBit() ~= 0 + local buffer = net.ReadString() + + local ok, ret = pcall(WireLib.von.deserialize, buffer) + if not ok then + WireLib.AddNotify(ply, "Expression 2 file list download failed! Error message:\n" .. ret, NOTIFY_ERROR, 7, NOTIFYSOUND_DRIP3) + print("Expression 2 file list download failed! Error message:\n" .. ret) + return + end + + local files = ret + local height = 23 + + local pnl = vgui.Create("DFrame") + pnl:SetSize(200, 100) + pnl:Center() + pnl:SetTitle("Select files to download") + + local lst = vgui.Create("DPanelList", pnl) + lst.Paint = function() end + lst:SetSpacing(2) + + local selectedfiles = { main = true } + + local checkboxes = {} + + local check = vgui.Create("DCheckBoxLabel") + check:SetText("Main") + check:Toggle() + lst:AddItem(check) + function check:OnChange(val) + if val then + selectedfiles.main = true + else + selectedfiles.main = nil + end + end + + checkboxes[#checkboxes + 1] = check + height = height + check:GetTall() + 2 + + for i = 1, #files do + local path = files[i] + local check = vgui.Create("DCheckBoxLabel") + check:SetText(path) + lst:AddItem(check) + function check:OnChange(val) + if val then + selectedfiles[i] = path + else + table.remove(selectedfiles, i) + end + end + + checkboxes[#checkboxes + 1] = check + height = height + check:GetTall() + 2 + end + + local selectall = vgui.Create("DButton") + selectall:SetText("Select all") + lst:AddItem(selectall) + function selectall:DoClick() + selectedfiles = {} + for k, v in pairs(files) do + selectedfiles[#selectedfiles + 1] = v + end + selectedfiles.main = true + + for i = 1, #checkboxes do + if not checkboxes[i]:GetChecked() then checkboxes[i]:Toggle() end -- checkboxes[i]:SetChecked( true ) + end + end + + height = height + selectall:GetTall() + 2 + + local selectnone = vgui.Create("DButton") + selectnone:SetText("Select none") + lst:AddItem(selectnone) + function selectnone:DoClick() + selectedfiles = {} + + for i = 1, #checkboxes do + if checkboxes[i]:GetChecked() then checkboxes[i]:Toggle() end -- checkboxes[i]:SetChecked( false ) + end + end + + height = height + selectnone:GetTall() + 2 + + local ok = vgui.Create("DButton") + ok:SetText("Ok") + ok:SetToolTip("Shortcut for this button: Right click anywhere") + lst:AddItem(ok) + function ok:DoClick() + local haschoice = false + for k, v in pairs(selectedfiles) do haschoice = true break end + if not haschoice then pnl:Close() return end + + local datastr = E2Lib.encode(WireLib.von.serialize(selectedfiles)) + local numpackets = math.ceil(#datastr / 64000) + for i = 1, #datastr, 64000 do + net.Start("wire_expression2_download_wantedfiles") + net.WriteEntity(ent) + net.WriteBit(uploadandexit) + net.WriteUInt(numpackets, 16) + net.WriteString(datastr:sub(i, i + 63999)) + net.SendToServer() + end + + pnl:Close() + end + + height = height + ok:GetTall() + + local down = input.IsMouseDown(MOUSE_RIGHT) + function pnl:Think() + if not down and input.IsMouseDown(MOUSE_RIGHT) then + ok:DoClick() + end + down = input.IsMouseDown(MOUSE_RIGHT) + end + + pnl:SetTall(math.min(height + 2, ScrH() / 2)) + lst:EnableVerticalScrollbar(true) + lst:StretchToParent(2, 23, 2, 2) + pnl:MakePopup() + pnl:SetVisible(true) + end) + + -------------------------------------------------------------- + function TOOL.BuildCPanel(panel) + local w, h = panel:GetSize() + + WireToolHelpers.MakeModelSizer(panel, "wire_expression2_modelsize") + --[[ + local ParentPanel = vgui.Create( "DPanel", panel ) + ParentPanel:SetSize(w,h-40) + ParentPanel:Dock(TOP) + ]] + --[[ + local MaterialGallery = vgui.Create( "DCollapsibleCategory", ParentPanel ) + MaterialGallery:SetSize(w,100) + ]] + -- lazy.. lazy.. lazy.. deprecated.. + panel:AddControl("MaterialGallery", { + Height = "100", + Width = "100", + Rows = 2, + Stretch = false, + ConVar = "wire_expression2_select", + Options = { + ["Modern"] = { wire_expression2_select = "Modern", Value = "Modern", Material = "beer/wiremod/gate_e2", wire_expression2_model = "models/beer/wiremod/gate_e2.mdl" }, + ["Expression"] = { wire_expression2_select = "Expression", Value = "Expression", Material = "models/expression 2/exprssn", wire_expression2_model = "models/expression 2/cpu_expression.mdl" }, + ["Microchip"] = { wire_expression2_select = "Microchip", Value = "Microchip", Material = "models/expression 2/mcrochp", wire_expression2_model = "models/expression 2/cpu_microchip.mdl" }, + ["Interface"] = { wire_expression2_select = "Interface", Value = "Interface", Material = "models/expression 2/intrfce", wire_expression2_model = "models/expression 2/cpu_interface.mdl" }, + ["Controller"] = { wire_expression2_select = "Controller", Value = "Controller", Material = "models/expression 2/cntrllr", wire_expression2_model = "models/expression 2/cpu_controller.mdl" }, + ["Processor"] = { wire_expression2_select = "Processor", Value = "Processor", Material = "models/expression 2/prcssor", wire_expression2_model = "models/expression 2/cpu_processor.mdl" }, + } + }) + + if (wire_expression2_editor == nil) then initE2Editor() end + + local FileBrowser = vgui.Create("wire_expression2_browser", panel) + FileBrowser.OpenOnSingleClick = wire_expression2_editor + panel:AddPanel(FileBrowser) + FileBrowser:Setup("expression2") + FileBrowser:SetSize(w, 300) + FileBrowser:DockMargin(5, 5, 5, 5) + FileBrowser:DockPadding(5, 5, 5, 5) + FileBrowser:Dock(TOP) + function FileBrowser:OnFileOpen(filepath, newtab) + wire_expression2_editor:Open(filepath, nil, newtab) + end + + local OpenEditor = panel:Button("Open Editor") + OpenEditor.DoClick = function(button) + wire_expression2_editor:Open() + end + + local NewExpression = panel:Button("New Expression") + NewExpression.DoClick = function(button) + wire_expression2_editor:Open() + wire_expression2_editor:NewScript() + end + end + + function initE2Editor() + wire_expression2_editor = vgui.Create("Expression2EditorFrame") + wire_expression2_editor:Setup("Expression 2 Editor", "expression2", "E2") + end + + function openE2Editor() + if (wire_expression2_editor == nil) then initE2Editor() end + wire_expression2_editor:Open() + end + + net.Receive("WireExpression2_OpenEditor", openE2Editor) + + --[[ + Expression 2 Tool Screen for Garry's Mod + Andreas "Syranide" Svensson, me@syranide.com + ]] -- + + local fontTable = { + font = "Arial", + size = 40, + weight = 1000, + antialias = true, + additive = false, + } + surface.CreateFont("Expression2ToolScreenFont", fontTable) + fontTable.size = 30 + surface.CreateFont("Expression2ToolScreenSubFont", fontTable) + + local percent = nil + local percent2 = nil + local name = "Unnamed" + local what = "Uploading" + + function Expression2SetName(n) + name = n + if not name then + name = "Unnamed" + return + end + + surface.SetFont("Expression2ToolScreenSubFont") + local ww = surface.GetTextSize("...") + + local w, h = surface.GetTextSize(name) + if w < 240 then return end + + while true do + local w, h = surface.GetTextSize(name) + if w < 240 - ww then break end + name = string.sub(name, 1, -2) + end + + name = string.Trim(name) .. "..." + end + + function Expression2SetProgress(p, p2, w) + what = w or "Uploading" + percent = p and math.Clamp(p,0,100) or nil + percent2 = p2 and math.Clamp(p2,0,100) or nil + end + + function DrawTextOutline(text, font, x, y, color, xalign, yalign, bordercolor, border) + for i = 0, 8 do + draw.SimpleText(text, font, x + border * math.sin(i * math.pi / 4), y + border * math.cos(i * math.pi / 4), bordercolor, xalign, yalign) + end + + draw.SimpleText(text, font, x, y, color, xalign, yalign) + end + + local CogColor = Color(150, 34, 34, 255) + local CogTexture = surface.GetTextureID("expression 2/cog") + if CogTexture == surface.GetTextureID("texturemissing") then CogTexture = nil end + + function TOOL:DrawToolScreen(width, height) + surface.SetDrawColor(32, 32, 32, 255) + surface.DrawRect(0, 0, 256, 256) + + if CogTexture then + local ToColor = Color(150, 34, 34, 255) + if percent then + ToColor = Color(34, 150, 34, 255) + end + + local CogDelta = 750 * FrameTime() + + CogColor.r = CogColor.r + math.max(-CogDelta, math.min(CogDelta, ToColor.r - CogColor.r)) + CogColor.g = CogColor.g + math.max(-CogDelta, math.min(CogDelta, ToColor.g - CogColor.g)) + CogColor.b = CogColor.b + math.max(-CogDelta, math.min(CogDelta, ToColor.b - CogColor.b)) + + surface.SetTexture(CogTexture) + surface.SetDrawColor(CogColor.r, CogColor.g, CogColor.b, 255) + surface.DrawTexturedRectRotated(256, 256, 455, 455, RealTime() * 10) + surface.DrawTexturedRectRotated(30, 30, 227.5, 227.5, RealTime() * -20 + 12.5) + end + + surface.SetFont("Expression2ToolScreenFont") + local w, h = surface.GetTextSize(" ") + surface.SetFont("Expression2ToolScreenSubFont") + local w2, h2 = surface.GetTextSize(" ") + + if percent or percent2 then + surface.SetFont("Expression2ToolScreenFont") + local w, h = surface.GetTextSize(what) + DrawTextOutline(what, "Expression2ToolScreenFont", 128, 128, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, 255), 4) + + if percent then + draw.RoundedBox(4, 128 - w / 2 - 2, 128 + h / 2 - 0, ((w+4) * percent) / 100, h2 - 4, Color(0, 0, 0, 255)) + draw.RoundedBox(2, 128 - w / 2 + 2, 128 + h / 2 + 4, ((w-4) * percent) / 100, h2 - 12, Color(224, 224, 224, 255)) + end + + if percent2 then + draw.RoundedBox(4, 128 - w / 2 - 2, 128 + h / 2 + 24, ((w+4) * percent2) / 100, h2 - 4, Color(0, 0, 0, 255)) + draw.RoundedBox(2, 128 - w / 2 + 2, 128 + h / 2 + 28, ((w-4) * percent2) / 100, h2 - 12, Color(224, 224, 224, 255)) + end + elseif name then + DrawTextOutline("Expression 2", "Expression2ToolScreenFont", 128, 128, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, 255), 4) + DrawTextOutline(name, "Expression2ToolScreenSubFont", 128, 128 + (h + h2) / 2 - 4, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(0, 0, 0, 255), 4) + end + end +end + +-- -------- 'in editor' animation ------------------------ + +if SERVER then + + -- -------------- client-side event handling ------------------ + -- this might fit better elsewhere + + local wire_expression2_event = {} + + concommand.Add("wire_expression2_event", function(ply, command, args) + local handler = wire_expression2_event[args[1]] + if not handler then return end + return handler(ply, args) + end) + + -- actual editor open/close handlers + + function wire_expression2_event.editor_open(ply, args) + net.Start("wire_expression2_editor_status") + net.WriteEntity(ply) + net.WriteBit(true) + net.Broadcast() + end + + function wire_expression2_event.editor_close(ply, args) + net.Start("wire_expression2_editor_status") + net.WriteEntity(ply) + net.WriteBit(false) + net.Broadcast() + end + +elseif CLIENT then + + local busy_players = {} + hook.Add("EntityRemoved", "wire_expression2_busy_animation", function(ply) + busy_players[ply] = nil + end) + + local emitter = ParticleEmitter(vector_origin) + + net.Receive("wire_expression2_editor_status", function(len) + local ply = net.ReadEntity() + local status = net.ReadBit() ~= 0 -- net.ReadBit returns 0 or 1, despite net.WriteBit taking a boolean + if not IsValid(ply) or ply == LocalPlayer() then return end + + busy_players[ply] = status or nil + end) + + local rolldelta = math.rad(80) + timer.Create("wire_expression2_editor_status", 1, 0, function() + rolldelta = -rolldelta + for ply, _ in pairs(busy_players) do + local BoneIndx = ply:LookupBone("ValveBiped.Bip01_Head1") or ply:LookupBone("ValveBiped.HC_Head_Bone") or 0 + local BonePos, BoneAng = ply:GetBonePosition(BoneIndx) + local particle = emitter:Add("expression 2/cog_world", BonePos + Vector(0, 0, 16)) + if particle then + particle:SetColor(150, 34, 34) + particle:SetVelocity(Vector(0, 0, 17)) + + particle:SetDieTime(3) + particle:SetLifeTime(0) + + particle:SetStartSize(10) + particle:SetEndSize(10) + + particle:SetStartAlpha(255) + particle:SetEndAlpha(0) + + particle:SetRollDelta(rolldelta) + end + end + end) +end + +local prevmodel, prevvalid +function validModelCached(model) + if model ~= prevmodel then + prevmodel = model + prevvalid = util.IsValidModel(model) + end + return prevvalid +end + +TOOL.Model = "models/beer/wiremod/gate_e2.mdl" +function TOOL:GetModel() + local scriptmodel = self:GetClientInfo("scriptmodel") + if scriptmodel and scriptmodel ~= "" and validModelCached(scriptmodel) then return Model(scriptmodel) end + return WireToolObj.GetModel(self) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/extbus.lua b/garrysmod/addons/feature-wire/lua/wire/stools/extbus.lua new file mode 100644 index 0000000..bda8ab0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/extbus.lua @@ -0,0 +1,16 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "extbus", "Data - Extended Bus", "gmod_wire_extbus", nil, "Extended Buses" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_extbus.name", "Extended bus tool (Wire)" ) + language.Add( "Tool.wire_extbus.desc", "Spawns an extended bus (programmable address bus)" ) + TOOL.Information = { { name = "left", text = "Create/Update extended bus" } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_gate.mdl" + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_extbus_model", list.Get("Wire_gate_Models"), 5) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/eyepod.lua b/garrysmod/addons/feature-wire/lua/wire/stools/eyepod.lua new file mode 100644 index 0000000..a039632 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/eyepod.lua @@ -0,0 +1,158 @@ +WireToolSetup.setCategory( "Vehicle Control" ) +WireToolSetup.open( "eyepod", "Eye Pod", "gmod_wire_eyepod", nil, "Eye Pods" ) + +if ( CLIENT ) then + --tool hud lang + language.Add( "Tool.wire_eyepod.name", "Eye Pod Tool (Wire)" ) + language.Add( "Tool.wire_eyepod.desc", "Spawns an Eye Pod Mouse Controller." ) + + --panel control lang + language.Add( "WireEyePod_DefaultToZero", "Default Outputs To Zero When Inactive" ) + language.Add( "WireEyePod_CumulativeOutput", "Output Cumulative Mouse Position" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 15 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" +TOOL.ClientConVar[ "DefaultToZero" ] = "1" +TOOL.ClientConVar[ "CumulativeOutput" ] = "0" +TOOL.ClientConVar[ "XMin" ] = "0" +TOOL.ClientConVar[ "XMax" ] = "0" +TOOL.ClientConVar[ "YMin" ] = "0" +TOOL.ClientConVar[ "YMax" ] = "0" + +if SERVER then + function TOOL:GetConVars() + local DefaultToZero = self:GetClientNumber("DefaultToZero") + local CumulativeOutput = self:GetClientNumber("CumulativeOutput") + local ShowRateOfChange = (CumulativeOutput ~= 0) and 0 or 1 + --set the default to zero to one if you are showing the mouse position instead + if (ShowRateOfChange == 1) then DefaultToZero = 1 end + + local ClampXMin = self:GetClientNumber("XMin") + local ClampXMax = self:GetClientNumber("XMax") + local ClampYMin = self:GetClientNumber("YMin") + local ClampYMax = self:GetClientNumber("YMax") + local ClampX = 0 + local ClampY = 0 + --test clamp + if ( (ClampXMin != 0 or ClampXMax != 0) and (ClampYMin != 0 or ClampYMax != 0) and + ClampXMin != ClampXMax and ClampYMin != ClampYMax and + ClampXMin < ClampXMax and ClampYMin < ClampYMax ) then + ClampX = 1 + ClampY = 1 + elseif( (ClampXMin == 0 and ClampXMax == 0) or (ClampYMin == 0 or ClampYMax == 0) )then + if(ClampXMin == 0 and ClampXMax == 0 and (ClampYMin != 0 or ClampYMax != 0)) then + ClampX = 0 + ClampY = 1 + elseif(ClampYMin == 0 and ClampYMax == 0 and (ClampXMin != 0 or ClampXMax != 0)) then + ClampX = 1 + ClampY = 0 + else + ClampX = 0 + ClampY = 0 + end + else + WireLib.AddNotify(ply, "Invalid Clamping of Wire EyePod Values!", NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP1) + return 1, 0, 0, 0, 0, 0, 0, 0 + end + return DefaultToZero, ShowRateOfChange, ClampXMin, ClampXMax, ClampYMin, ClampYMax, ClampX, ClampY + end +end + +WireToolSetup.SetupLinking(true, "eyepod") + +-------------------------------------- TOOL Menu --------------------------------------------------- +--TODO: Figure out a way for dynamic panels to work with check boxes (check boxes that use concommands instead of convars default to 1 allways) +--check for client +if (CLIENT) then + + local function Wire_EyePod_Menu(panel) + panel:ClearControls() + + panel:AddControl("Header", { + Text = "#Tool.wire_eyepod.name", + Description = "#Tool.wire_eyepod.desc" + }) + + --preset chooser + panel:AddControl("ComboBox", { + Label = "#Presets", + MenuButton = "1", + Folder = "wire_eyepod", + + Options = { + Default = { + wire_eyepod_DefaultToZero = "1", + wire_eyepod_CumulativeOutput = "0", + wire_eyepod_XMin = "0", + wire_eyepod_XMax = "0", + wire_eyepod_YMin = "0", + wire_eyepod_YMax = "0" + } + }, + + CVars = { + [0] = "wire_eyepod_DefaultToZero", + [1] = "wire_eyepod_CumulativeOutput", + [2] = "wire_eyepod_XMin", + [3] = "wire_eyepod_XMax", + [4] = "wire_eyepod_YMin", + [5] = "wire_eyepod_YMax" + } + }) + + WireDermaExts.ModelSelect(panel, "wire_eyepod_model", list.Get( "Wire_Misc_Tools_Models" ), 1) + + panel:AddControl("CheckBox", { + Label = "#WireEyePod_CumulativeOutput", + Command = "wire_eyepod_CumulativeOutput" + }) + + panel:AddControl("CheckBox", { + Label = "#WireEyePod_DefaultToZero", + Command = "wire_eyepod_DefaultToZero" + }) + + --clamps + panel:AddControl( "Label", { + Text = "\nClamp the output of the EyePod. \nSet both sliders to 0 to remove the clamp in that axis.", + Description = "Clamps the outputs of the EyePod. Set to 0 not to clamp in that axis"} ) + + panel:AddControl( "Slider", { + Label = "X Min", + Type = "Float", + Min = -2000, + Max = 2000, + Command = "wire_eyepod_XMin", + Description = "Clamps the output of the EyePod's X to this minimum"} ) + + panel:AddControl( "Slider", { + Label = "X Max", + Type = "Float", + Min = -2000, + Max = 2000, + Command = "wire_eyepod_XMax", + Description = "Clamps the output of the EyePod's X to this maximum"} ) + panel:AddControl( "Slider", { + Label = "Y Min", + Type = "Float", + Min = -2000, + Max = 2000, + Command = "wire_eyepod_YMin", + Description = "Clamps the output of the EyePod's Y to this minimum"} ) + + panel:AddControl( "Slider", { + Label = "Y Max", + Type = "Float", + Min = -2000, + Max = 2000, + Command = "wire_eyepod_YMax", + Description = "Clamps the output of the EyePod's Y to this maximum"} ) + end + + function TOOL.BuildCPanel( panel ) + Wire_EyePod_Menu(panel) + end + +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/forcer.lua b/garrysmod/addons/feature-wire/lua/wire/stools/forcer.lua new file mode 100644 index 0000000..53f5a1d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/forcer.lua @@ -0,0 +1,43 @@ +WireToolSetup.setCategory( "Physics/Force" ) +WireToolSetup.open( "forcer", "Forcer", "gmod_wire_forcer", nil, "Forcers" ) + +if CLIENT then + language.Add( "tool.wire_forcer.name", "Forcer Tool (Wire)" ) + language.Add( "tool.wire_forcer.desc", "Spawns a forcer prop for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + multiplier = 1, + length = 100, + beam = 1, + reaction = 0, + model = "models/jaanus/wiretool/wiretool_siren.mdl" +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "multiplier" ), self:GetClientNumber( "length" ), self:GetClientNumber( "beam" )==1, self:GetClientNumber( "reaction" )==1 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL:GetGhostMin( min, trace ) + if self:GetModel() == "models/jaanus/wiretool/wiretool_grabber_forcer.mdl" then + return min.z + 20 + else + return min.z + end +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_forcer") + ModelPlug_AddToCPanel(panel, "Forcer", "wire_forcer", true, 1) + panel:NumSlider("Force multiplier", "wire_forcer_multiplier", 1, 10000, 0) + panel:NumSlider("Force distance", "wire_forcer_length", 1, 2048, 0) + panel:CheckBox("Show beam", "wire_forcer_beam") + panel:CheckBox("Apply reaction force", "wire_forcer_reaction") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/freezer.lua b/garrysmod/addons/feature-wire/lua/wire/stools/freezer.lua new file mode 100644 index 0000000..c0c0569 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/freezer.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Physics/Constraints" ) +WireToolSetup.open( "freezer", "Freezer", "gmod_wire_freezer", nil, "Freezers" ) + +if CLIENT then + language.Add( "Tool.wire_freezer.name", "Freezer Tool (Wire)" ) + language.Add( "Tool.wire_freezer.desc", "Spawns a Freezer Controller for use with the wire system." ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", +} + +-- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function + +WireToolSetup.SetupLinking() -- Generates RightClick, Reload, and DrawHUD functions + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_freezer") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/friendslist.lua b/garrysmod/addons/feature-wire/lua/wire/stools/friendslist.lua new file mode 100644 index 0000000..6103239 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/friendslist.lua @@ -0,0 +1,278 @@ +WireToolSetup.setCategory( "Input, Output" ) +WireToolSetup.open( "friendslist", "Friends List", "gmod_wire_friendslist", nil, "Friends Lists" ) + +if CLIENT then + language.Add( "tool.wire_friendslist.name", "Friends List Tool (Wire)" ) + language.Add( "tool.wire_friendslist.desc", "Spawns a friends list entity for use with the wire system." ) + + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Copy settings" }, + } + + language.Add( "wire_friendslist_save_on_entity", "Save On Entity" ) + language.Add( "wire_friendslist_save_on_entity_tooltip", "When enabled, the friends list will be saved on the entity and carried across in dupes. When disabled, the friends list will mirror any changes you make in this UI." ) + language.Add( "wire_friendslist_invalid_steamid", "Invalid SteamID" ) + language.Add( "wire_friendslist_connected_players", "Currently connected players" ) + language.Add( "wire_friendslist_not_connected", "Not Connected" ) + + + WireToolSetup.setToolMenuIcon( "icon16/group.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(8) + +TOOL.ClientConVar = { + model = "models/kobilica/value.mdl", + save_on_entity = 0 +} + +-- shared helper functions +local function netWriteValues( values ) + net.WriteUInt(#values,11) + for i=1,#values do + net.WriteString( string.sub(values[i],1,32) ) + end +end +local function netReadValues() + local t = {} + local amount = net.ReadUInt(11) + for i=1,amount do + t[i] = net.ReadString() + end + return t +end + + +local friends = {} + +if SERVER then + util.AddNetworkString( "wire_friendslist" ) + net.Receive( "wire_friendslist", function( length, ply ) + friends[ply] = netReadValues() + + -- Update all friendslists which have save_on_entity set to false + local friendslists = ents.FindByClass( "gmod_wire_friendslist" ) + for i=1,#friendslists do + local ent = friendslists[i] + if ent:GetPlayer() == ply then + ent:UpdateFriendslist( friends[ply] or {} ) + end + end + end) + + function TOOL:GetConVars() + return self:GetClientNumber( "save_on_entity" ) ~= 0, friends[self:GetOwner()] or {} + end + + hook.Add( "OnEntityCreated", "wire_friendslist_created", function( ent ) + if ent:GetClass() == "gmod_wire_friendslist" then + timer.Simple( 0, function() -- wait for wire to set the GetPlayer variable + if IsValid( ent ) then + ent:UpdateFriendslist( friends[ent:GetPlayer()] or {} ) + end + end) + end + end) + + -- Right click to copy + function TOOL:RightClick(trace) + if not self:CheckHitOwnClass(trace) then return false end + + friends[self:GetOwner()] = trace.Entity.steamids + net.Start( "wire_friendslist" ) + netWriteValues( trace.Entity.steamids ) + net.Send( self:GetOwner() ) + end +else + function TOOL:RightClick(trace) + return self:CheckHitOwnClass(trace) + end + + local function loadFile() + if not file.Exists( "wire_friendslist.txt", "DATA" ) then return {} end + local str = file.Read( "wire_friendslist.txt", "DATA" ) + local t = string.Explode( ",", str ) + + local ret = {} + for i=1,#t do + local str = string.Trim(t[i]) + if string.match( str, "^STEAM_%d:%d:%d+$") ~= nil then + ret[#ret+1] = str + end + end + + return ret + end + + local function saveFile( friends ) + file.Write( "wire_friendslist.txt", table.concat(friends,",") ) + end + + local function sendFriends( dontsave ) + if #friends == 0 then return end + + net.Start("wire_friendslist") + netWriteValues( friends ) + net.SendToServer() + + if not dontsave then + saveFile( friends ) + end + end + + -- Receive values for copying + local cpanel_list + net.Receive( "wire_friendslist", function( length ) + friends = netReadValues() + saveFile( friends ) + + if not IsValid(cpanel_list) then -- They right clicked without opening the cpanel first, just save the values + return + end + + cpanel_list:Clear() + for i=1,#friends do + local ply = player.GetBySteamID( friends[i] ) + local col2 = "#wire_friendslist_not_connected" + if IsValid(ply) then col2 = ply:Nick() end + cpanel_list:AddLine( friends[i], col2 ) + end + end) + + local function addSteamID( steamid ) + for i=1,#friends do + if friends[i] == steamid then return false end + end + + if string.match( steamid, "^STEAM_%d:%d:%d+$") == nil then + WireLib.AddNotify( LocalPlayer(), "#wire_friendslist_invalid_steamid", NOTIFY_ERROR, 8, NOTIFYSOUND_ERROR1 ) + return false + end + + friends[#friends+1] = steamid + sendFriends() + return true + end + + local function removeSteamID( steamid ) + for i=1,#friends do + if friends[i] == steamid then + table.remove( friends, i ) + return true + end + end + return false + end + + hook.Add( "Initialize", "wire_friendslist_init", function() + timer.Simple( 5, function() + friends = loadFile() + sendFriends( true ) + end) + end) + + function TOOL.BuildCPanel(panel) + -- Use the same models as the wire constant value + WireToolHelpers.MakeModelSizer(panel, "wire_friendslist_modelsize") + ModelPlug_AddToCPanel(panel, "Value", "wire_friendslist", true) + + local save_on_entity = panel:CheckBox( "#wire_friendslist_save_on_entity", "wire_friendslist_save_on_entity" ) + save_on_entity:SetToolTip( "#wire_friendslist_save_on_entity_tooltip" ) + + local pnl = vgui.Create( "DPanel", panel ) + pnl:Dock( TOP ) + pnl:DockMargin( 2,2,2,2 ) + pnl:DockPadding( 2,2,2,2 ) + + local list = vgui.Create( "DListView", pnl ) + list:AddColumn( "SteamID" ) + list:AddColumn( "Name" ) + list:SetMultiSelect( false ) + list:Dock( TOP ) + list:SetHeight( 200 ) + cpanel_list = list + + local txt = vgui.Create( "DTextEntry", pnl ) + txt:Dock( TOP ) + + local btn_add = vgui.Create( "DButton", pnl ) + btn_add:Dock( TOP ) + btn_add:SetText( "Add" ) + + function btn_add:DoClick() + local steamid = string.upper(string.Trim(txt:GetValue())) + + if addSteamID( steamid ) then + local ply = player.GetBySteamID( steamid ) + local col2 = "#wire_friendslist_not_connected" + if IsValid(ply) then col2 = ply:Nick() end + list:AddLine( steamid, col2 ) + + txt:SetValue( "" ) + end + end + + local btn_remove = vgui.Create( "DButton", pnl ) + btn_remove:Dock( TOP ) + btn_remove:SetText( "Remove" ) + + function btn_remove:DoClick() + local selected = list:GetSelectedLine() + + if selected then + local steamid = list:GetLine( selected ):GetColumnText( 1 ) + list:RemoveLine( selected ) + + if removeSteamID( steamid ) then + sendFriends() + end + end + end + + for i=1,#friends do + local ply = player.GetBySteamID( friends[i] ) + local col2 = "#wire_friendslist_not_connected" + if IsValid(ply) then col2 = ply:Nick() end + list:AddLine( friends[i], col2 ) + end + + local pnl2 = vgui.Create( "DPanel", panel ) + pnl2:Dock( TOP ) + pnl2:DockMargin( 2,2,2,2 ) + pnl2:DockPadding( 2,2,2,2 ) + + local lbl = vgui.Create( "DLabel", pnl2 ) + lbl:SetText( "#wire_friendslist_connected_players" ) + lbl:SizeToContents() + lbl:Dock( TOP ) + lbl:SetTextColor( Color(0,0,0,255) ) + + local connected_list = vgui.Create( "DListView", pnl2 ) + connected_list:Dock( TOP ) + connected_list:AddColumn( "SteamID" ) + connected_list:AddColumn( "Name" ) + connected_list:SetHeight( 200 ) + + function connected_list:OnRowSelected( index, row ) + txt:SetValue(row:GetColumnText( 1 )) + end + + local refresh = vgui.Create( "DButton", pnl2 ) + refresh:Dock( TOP ) + refresh:SetText( "Refresh" ) + function refresh:DoClick() + connected_list:Clear() + local plys = player.GetHumans() + for i=1,#plys do + connected_list:AddLine( plys[i]:SteamID(), plys[i]:Nick() ) + end + end + + refresh:DoClick() + + -- wanted to use SizeToContents here but it didn't work, thanks garry + pnl:SetHeight( 268 ) + pnl2:SetHeight( 240 ) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/fx_emitter.lua b/garrysmod/addons/feature-wire/lua/wire/stools/fx_emitter.lua new file mode 100644 index 0000000..3409b09 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/fx_emitter.lua @@ -0,0 +1,39 @@ +WireToolSetup.setCategory( "Visuals" ) +WireToolSetup.open( "fx_emitter", "FX Emitter", "gmod_wire_fx_emitter", nil, "FX Emitters" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_fx_emitter.name", "Wire FX Emitter" ) + language.Add( "Tool.wire_fx_emitter.desc", "Wire FX Emitter Emits effects eh?" ) + language.Add( "Tool.wire_fx_emitter.delay", "Delay between effect pulses" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + model = "models/props_lab/tpplug.mdl", + Effect = "sparks", + Delay = 0.07, + weld = 1, + createflat = 1, -- Needed for tpplug.mdl +} +TOOL.GhostMin = "y" + +if SERVER then + function TOOL:GetConVars() + return math.Clamp(self:GetClientNumber( "Delay" ), 0.05, 20), ComboBox_Wire_FX_Emitter_Options[self:GetClientInfo( "Effect" )] + end +end + +function TOOL.BuildCPanel( CPanel ) + CPanel:AddControl( "Header", { Text = "#Tool.wire_fx_emitter.name", Description = "#Tool.wire_fx_emitter.desc" } ) + + // Effect types + local params = { Label = "#Effect", Height = "250", MenuButton="0", Options = {} } + for k,_ in pairs(ComboBox_Wire_FX_Emitter_Options) do + params.Options[ "#wire_fx_emitter_" .. k ] = { wire_fx_emitter_Effect = k } + end + CPanel:AddControl( "ListBox", params ) + + CPanel:NumSlider("#Tool.wire_fx_emitter.delay", "wire_fx_emitter_Delay", 0.05, 5, 2) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/gates.lua b/garrysmod/addons/feature-wire/lua/wire/stools/gates.lua new file mode 100644 index 0000000..33e6f03 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/gates.lua @@ -0,0 +1,324 @@ +-- Made by Divran 06/01/2012 +WireToolSetup.setCategory( "Chips, Gates" ) +WireToolSetup.open( "gates", "Gates", "gmod_wire_gate", nil, "Gates" ) + +WireToolSetup.SetupMax(100) + +if SERVER then + ModelPlug_Register("gate") +end + +if CLIENT then + ---------------------------------------------------------------------------------------------------- + -- Tool Info + ---------------------------------------------------------------------------------------------------- + + language.Add( "Tool.wire_gates.name", "Gates Tool (Wire)" ) + language.Add( "Tool.wire_gates.desc", "Spawns gates for use with the wire system." ) + + TOOL.ClientConVar["model"] = "models/jaanus/wiretool/wiretool_gate.mdl" + TOOL.ClientConVar["parent"] = 0 + TOOL.ClientConVar["noclip"] = 1 + TOOL.ClientConVar["angleoffset"] = 0 + TOOL.ClientConVar["action"] = "+" + TOOL.ClientConVar["searchresultnum"] = 28 + + language.Add( "WireGatesTool_action", "Gate action" ) + language.Add( "WireGatesTool_noclip", "NoCollide" ) + language.Add( "WireGatesTool_parent", "Parent" ) + language.Add( "WireGatesTool_angleoffset", "Spawn angle offset" ) + language.Add( "sboxlimit_wire_gates", "You've hit your gates limit!" ) + + WireToolSetup.setToolMenuIcon( "bull/gates/gate_logic_and" ) + + TOOL.Information = { + { name = "left", text = "Create/Update Gate" }, + { name = "right", text = "Copy Gate" }, + { name = "reload", text = "Increase angle offset by 45 degrees" }, + { name = "reload_shift", text = "Shift+Reload: Unparent gate (If parented)" }, + } + + function TOOL.BuildCPanel( panel ) + ----------------- GATE SELECTION & SEARCHING + + -- Create panels + local searchbox = vgui.Create( "DTextEntry" ) + + searchbox:SetValue( "Search..." ) + + local oldOnGetFocus = searchbox.OnGetFocus + function searchbox:OnGetFocus() + if self:GetValue() == "Search..." then -- If "Search...", erase it + self:SetValue( "" ) + end + oldOnGetFocus( self ) + end + + -- On lose focus + local oldOnLoseFocus = searchbox.OnLoseFocus + function searchbox:OnLoseFocus() + if self:GetValue() == "" then -- if empty, reset "Search..." text + timer.Simple( 0, function() self:SetValue( "Search..." ) end ) + end + oldOnLoseFocus( self ) + end + + local holder = vgui.Create( "DPanel" ) + holder:SetTall( 500 ) + + local tree = vgui.Create( "DTree", holder ) + local searchlist = vgui.Create( "DListView", holder ) + searchlist:AddColumn( "Gate Name" ) + searchlist:AddColumn( "Category" ) + + local string_find = string.find + local table_SortByMember = table.SortByMember + local string_lower = string.lower + + -- Searching algorithm + local function Search( text ) + text = string_lower(text) + + local results = {} + for action,gate in pairs( GateActions ) do + local name = gate.name + local lowname = string_lower(name) + if string_find( lowname, text, 1, true ) then -- If it has ANY match at all + results[#results+1] = { name = gate.name, group = gate.group, action = action, dist = WireLib.levenshtein( text, lowname ) } + end + end + + table_SortByMember( results, "dist", true ) + + return results + end + + -- Main searching + local searching + function searchbox:OnTextChanged() + local text = searchbox:GetValue() + if text ~= "" then + if not searching then + searching = true + local x,y = tree:GetPos() + local w,h = tree:GetSize() + searchlist:SetPos( x + w, y ) + searchlist:MoveTo( x, y, 0.1, 0, 1 ) + searchlist:SetSize( w, h ) + searchlist:SetVisible( true ) + end + local results = Search( text ) + searchlist:Clear() + for i=1,#results do + local result = results[i] + + local line = searchlist:AddLine( result.name, result.group ) + local action = GetConVarString("wire_gates_action") + if action == result.action then + line:SetSelected( true ) + end + line.action = result.action + end + else + if searching then + searching = false + local x,y = tree:GetPos() + local w,h = tree:GetSize() + searchlist:SetPos( x, y ) + searchlist:MoveTo( x + w, y, 0.1, 0, 1 ) + searchlist:SetSize( w, h ) + timer.Create( "wire_customspawnmenu_hidesearchlist", 0.1, 1, function() + if IsValid( searchlist ) then + searchlist:SetVisible( false ) + end + end ) + end + searchlist:Clear() + end + end + + function searchlist:OnClickLine( line ) + -- Deselect old + local t = searchlist:GetSelected() + if t and next(t) then + t[1]:SetSelected(false) + end + + line:SetSelected(true) -- Select new + RunConsoleCommand( "wire_gates_action", line.action ) + end + + function searchbox:OnEnter() + if #searchlist:GetLines() > 0 then + searchlist:OnClickLine( searchlist:GetLine( 1 ) ) + end + end + + panel:AddItem( searchbox ) + + tree:Dock( FILL ) + + -- Set sizes & other settings + searchlist:SetVisible( false ) + searchlist:SetMultiSelect( false ) + + + local function FillSubTree( tree, node, temp ) + node.Icon:SetImage( "icon16/folder.png" ) + + local subtree = {} + for k,v in pairs( temp ) do + subtree[#subtree+1] = { action = k, gate = v, name = v.name } + end + + table_SortByMember(subtree, "name", true ) + + for index=1, #subtree do + local action, gate = subtree[index].action, subtree[index].gate + local node2 = node:AddNode( gate.name or "No name found :(" ) + node2.name = gate.name + node2.action = action + function node2:DoClick() + RunConsoleCommand( "wire_gates_Action", self.action ) + end + node2.Icon:SetImage( "icon16/newspaper.png" ) + end + tree:InvalidateLayout() + end + + local CategoriesSorted = {} + + for gatetype, gatefuncs in pairs( WireGatesSorted ) do + local allowed_gates = {} + local any_allowed = false + for k,v in pairs(gatefuncs) do + if not v.is_banned then + allowed_gates[k] = v + any_allowed = true + end + end + if any_allowed then + CategoriesSorted[#CategoriesSorted+1] = { gatetype = gatetype, gatefuncs = allowed_gates } + end + end + + table.sort( CategoriesSorted, function( a, b ) return a.gatetype < b.gatetype end ) + + for i=1,#CategoriesSorted do + local gatetype = CategoriesSorted[i].gatetype + local gatefuncs = CategoriesSorted[i].gatefuncs + + local node = tree:AddNode( gatetype ) + node.Icon:SetImage( "icon16/folder.png" ) + FillSubTree( tree, node, gatefuncs ) + function node:DoClick() + self:SetExpanded( not self.m_bExpanded ) + end + end + + -- add it all to the main panel + panel:AddItem( holder ) + + + -- MISCELLANEOUS PLACEMENT OPTIONS, AND MODEL + + local nocollidebox = panel:CheckBox("#WireGatesTool_noclip", "wire_gates_noclip") + local parentbox = panel:CheckBox("#WireGatesTool_parent","wire_gates_parent") + + panel:Help("When parenting, you should check the nocollide box, or adv duplicator might not dupe the gate.") + + panel:NumSlider( "#WireGatesTool_angleoffset","wire_gates_angleoffset", 0, 360, 0 ) + + WireDermaExts.ModelSelect(panel, "wire_gates_model", list.Get("Wire_gate_Models"), 3, true) + + function nocollidebox.Button:DoClick() + self:Toggle() + end + + function parentbox.Button:DoClick() -- when you check the parent box, check the nocollide box + self:Toggle() + if (self:GetChecked() == true) then + nocollidebox:SetValue(1) + end + end + + end +end + +WireToolSetup.BaseLang() + +if SERVER then + function TOOL:GetConVars() + return self:GetClientInfo( "action" ), self:GetClientNumber( "noclip" ) == 1 + end + + function TOOL:MakeEnt( ply, model, Ang, trace ) + return WireLib.MakeWireGate( ply, trace.HitPos, Ang, model, self:GetConVars() ) + end +end + + +-------------------- +-- RightClick +-- Copy gate +-------------------- +function TOOL:RightClick( trace ) + if CLIENT then return true end + if self:CheckHitOwnClass(trace) then + local action = GateActions[trace.Entity.action] + assert(action, "Attempted to copy gate " .. tostring(trace.Entity) .. " with no action!") + + self:GetOwner():ConCommand( "wire_gates_action " .. trace.Entity.action ) + self:GetOwner():ChatPrint( "Gate copied ('" .. action.name .. "')." ) + return true + else + return false + end +end + +-------------------- +-- Reload +-- Increase angle offset by 45 degrees +-------------------- +function TOOL:Reload( trace ) + if self:GetOwner():KeyDown( IN_SPEED ) then -- Unparent + if not trace or not trace.Hit then return false end + if (CLIENT and trace.Entity) then return true end + if (trace.Entity:GetParent():IsValid()) then + + -- Get its position + local pos = trace.Entity:GetPos() + + -- Unparent + trace.Entity:SetParent() + + -- Teleport it back to where it was before unparenting it (because unparenting causes issues which makes the gate teleport to random wierd places) + trace.Entity:SetPos( pos ) + + -- Wake + local phys = trace.Entity:GetPhysicsObject() + if (phys) then + phys:Wake() + end + + -- Notify + self:GetOwner():ChatPrint("Entity unparented.") + return true + end + return false + else + if game.SinglePlayer() and SERVER then + self:GetOwner():ConCommand( "wire_gates_angleoffset " .. (self:GetClientNumber( "angleoffset" ) + 45) % 360 ) + elseif CLIENT then + RunConsoleCommand( "wire_gates_angleoffset", (self:GetClientNumber( "angleoffset" ) + 45) % 360 ) + end + end + + return false +end + +function TOOL:GetAngle( trace ) + local ang = WireToolObj.GetAngle(self, trace) + ang:RotateAroundAxis( trace.HitNormal, self:GetClientNumber( "angleoffset" ) ) + return ang +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/gimbal.lua b/garrysmod/addons/feature-wire/lua/wire/stools/gimbal.lua new file mode 100644 index 0000000..dc4bce9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/gimbal.lua @@ -0,0 +1,25 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "gimbal", "Gimbal (Facer)", "gmod_wire_gimbal", nil, "Gimbals" ) + +if CLIENT then + language.Add( "tool.wire_gimbal.name", "Gimbal Tool (Wire)" ) + language.Add( "tool.wire_gimbal.desc", "Spawns a Gimbal (Facer)" ) + + TOOL.Information = { + { name = "left", text = "Create/Update Gimbal" }, + { name = "reload", text = "Copy model" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 8 ) + +-- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function + +TOOL.ClientConVar = { + model = "models/props_c17/canister01a.mdl", +} +TOOL.ReloadSetsModel = true + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Gimbal", "wire_gimbal", true) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/gps.lua b/garrysmod/addons/feature-wire/lua/wire/stools/gps.lua new file mode 100644 index 0000000..0519e8f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/gps.lua @@ -0,0 +1,26 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "gps", "GPS", "gmod_wire_gps", nil, "GPSs" ) + +if CLIENT then + language.Add( "Tool.wire_gps.name", "GPS Tool (Wire)" ) + language.Add( "Tool.wire_gps.desc", "Spawns a GPS for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/world.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + ModelPlug_Register("GPS") +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/gps.mdl", + modelsize = "", +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakeModelSizer(panel, "wire_gps_modelsize") + ModelPlug_AddToCPanel(panel, "GPS", "wire_gps") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/gpu.lua b/garrysmod/addons/feature-wire/lua/wire/stools/gpu.lua new file mode 100644 index 0000000..a663b6f --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/gpu.lua @@ -0,0 +1,195 @@ +WireToolSetup.setCategory( "Chips, Gates", "Visuals/Screens", "Advanced" ) +WireToolSetup.open( "gpu", "GPU", "gmod_wire_gpu", nil, "GPUs" ) + +if CLIENT then + language.Add("Tool.wire_gpu.name", "GPU Tool (Wire)") + language.Add("Tool.wire_gpu.desc", "Spawns a graphics processing unit") + language.Add("ToolWiregpu_Model", "Model:" ) + TOOL.Information = { + { name = "left", text = "Upload program to hispeed device" }, + { name = "right", text = "open editor and/or attach debugger to the ZGPU" }, + { name = "reload", text = "Wipe ROM/RAM and reset memory model" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 7 ) + +TOOL.ClientConVar = { + model = "models/cheeze/wires/cpu.mdl", + filename = "", + memorymodel = "64k", +} + +if CLIENT then + ------------------------------------------------------------------------------ + -- Make sure firing animation is displayed clientside + ------------------------------------------------------------------------------ + function TOOL:LeftClick() return true end + function TOOL:Reload() return true end + function TOOL:RightClick() return false end +end + +if SERVER then + util.AddNetworkString("ZGPU_RequestCode") + util.AddNetworkString("ZGPU_OpenEditor") + ------------------------------------------------------------------------------ + -- Reload: wipe ROM/RAM and reset memory model + ------------------------------------------------------------------------------ + function TOOL:Reload(trace) + if trace.Entity:IsPlayer() then return false end + + local player = self:GetOwner() + if (trace.Entity:IsValid()) and + (trace.Entity:GetClass() == "gmod_wire_gpu") then + trace.Entity:SetMemoryModel(self:GetClientInfo("memorymodel")) + return true + end + end + + -- Left click: spawn GPU or upload current program into it + function TOOL:CheckHitOwnClass(trace) + return trace.Entity:IsValid() and (trace.Entity:GetClass() == self.WireClass or trace.Entity.WriteCell) + end + function TOOL:LeftClick_Update(trace) + CPULib.SetUploadTarget(trace.Entity, self:GetOwner()) + net.Start("ZGPU_RequestCode") net.Send(self:GetOwner()) + end + function TOOL:MakeEnt(ply, model, Ang, trace) + local ent = WireLib.MakeWireEnt(ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}) + ent:SetMemoryModel(self:GetClientInfo("memorymodel")) + self:LeftClick_Update(trace) + return ent + end + + + function TOOL:RightClick(trace) + net.Start("ZGPU_OpenEditor") net.Send(self:GetOwner()) + return true + end +end + + +if CLIENT then + ------------------------------------------------------------------------------ + -- Compiler callbacks on the compiling state + ------------------------------------------------------------------------------ + local function compile_success() + CPULib.Upload() + end + + local function compile_error(errorText) + print(errorText) + GAMEMODE:AddNotify(errorText,NOTIFY_GENERIC,7) + end + + + ------------------------------------------------------------------------------ + -- Request code to be compiled (called remotely from server) + ------------------------------------------------------------------------------ + function ZGPU_RequestCode() + if ZGPU_Editor then + CPULib.Debugger.SourceTab = ZGPU_Editor:GetActiveTab() + CPULib.Compile(ZGPU_Editor:GetCode(),ZGPU_Editor:GetChosenFile(),compile_success,compile_error,"GPU") + end + end + net.Receive("ZGPU_RequestCode", ZGPU_RequestCode) + + ------------------------------------------------------------------------------ + -- Open ZGPU editor + ------------------------------------------------------------------------------ + function ZGPU_OpenEditor() + if not ZGPU_Editor then + ZGPU_Editor = vgui.Create("Expression2EditorFrame") + ZGPU_Editor:Setup("ZGPU Editor", "gpuchip", "GPU") + end + ZGPU_Editor:Open() + end + net.Receive("ZGPU_OpenEditor", ZGPU_OpenEditor) + + ------------------------------------------------------------------------------ + -- Build tool control panel + ------------------------------------------------------------------------------ + function TOOL.BuildCPanel(panel) + local Button = vgui.Create("DButton" , panel) + panel:AddPanel(Button) + Button:SetText("Online ZGPU documentation") + Button.DoClick = function(button) CPULib.ShowDocumentation("ZGPU") end + + + ---------------------------------------------------------------------------- + local currentDirectory + local FileBrowser = vgui.Create("wire_expression2_browser" , panel) + panel:AddPanel(FileBrowser) + FileBrowser:Setup("GPUChip") + FileBrowser:SetSize(235,400) + function FileBrowser:OnFileOpen(filepath, newtab) + if not ZGPU_Editor then + ZGPU_Editor = vgui.Create("Expression2EditorFrame") + ZGPU_Editor:Setup("ZGPU Editor", "gpuchip", "GPU") + end + ZGPU_Editor:Open(filepath, nil, newtab) + end + + + ---------------------------------------------------------------------------- + local New = vgui.Create("DButton" , panel) + panel:AddPanel(New) + New:SetText("New file") + New.DoClick = function(button) + ZGPU_OpenEditor() + ZGPU_Editor:AutoSave() + ZGPU_Editor:NewScript(false) + end + panel:AddControl("Label", {Text = ""}) + + ---------------------------------------------------------------------------- + local OpenEditor = vgui.Create("DButton", panel) + panel:AddPanel(OpenEditor) + OpenEditor:SetText("Open Editor") + OpenEditor.DoClick = ZGPU_OpenEditor + + + ---------------------------------------------------------------------------- + local modelPanel = WireDermaExts.ModelSelect(panel, "wire_gpu_model", list.Get("WireScreenModels"), 3) + modelPanel:SetModelList(list.Get("Wire_gate_Models"),"wire_gpu_model") + panel:AddControl("Label", {Text = ""}) + + + ---------------------------------------------------------------------------- + panel:AddControl("ComboBox", { + Label = "Memory model", + Options = { + ["128K"] = {wire_gpu_memorymodel = "128k"}, + ["128K chip"] = {wire_gpu_memorymodel = "128kc"}, + ["256K"] = {wire_gpu_memorymodel = "256k"}, + ["256K chip"] = {wire_gpu_memorymodel = "256kc"}, + ["512K"] = {wire_gpu_memorymodel = "512k"}, + ["512K chip"] = {wire_gpu_memorymodel = "512kc"}, + ["1024K"] = {wire_gpu_memorymodel = "1024k"}, + ["1024K chip"] = {wire_gpu_memorymodel = "1024kc"}, + ["2048K"] = {wire_gpu_memorymodel = "2048k"}, + ["2048K chip"] = {wire_gpu_memorymodel = "2048kc"}, + + ["64K (compatibility mode)"] = {wire_gpu_memorymodel = "64k"}, + ["64K chip"] = {wire_gpu_memorymodel = "64kc"}, + } + }) + panel:AddControl("Label", {Text = "Memory model selects GPU memory size and its operation mode"}) + + + ---------------------------------------------------------------------------- +-- panel:AddControl("Button", { +-- Text = "ZGPU documentation (online)" +-- }) +-- panel:AddControl("Label", { +-- Text = "Loads online GPU documentation and tutorials" +-- }) + end + + ------------------------------------------------------------------------------ + -- Tool screen + ------------------------------------------------------------------------------ + function TOOL:DrawToolScreen(width, height) + CPULib.RenderCPUTool(1,"ZGPU") + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/gpulib_switcher.lua b/garrysmod/addons/feature-wire/lua/wire/stools/gpulib_switcher.lua new file mode 100644 index 0000000..ccf35ae --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/gpulib_switcher.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Visuals" ) +WireToolSetup.open( "gpulib_switcher", "GPULib Switcher", "gmod_wire_gpulib_controller", nil, "GPULib Switchers" ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" + +if (CLIENT) then + language.Add("Tool.wire_gpulib_switcher.name", "GPULib Screen Switcher") + language.Add("Tool.wire_gpulib_switcher.desc", "Spawn/link a GPULib Screen Switcher.") + + function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { Text = "#Tool.wire_gpulib_switcher.name", Description = "#Tool.wire_gpulib_switcher.desc" }) + WireDermaExts.ModelSelect(panel, "wire_gpulib_switcher_model", list.Get( "Wire_Misc_Tools_Models" ), 1) + end +end + +WireToolSetup.SetupLinking(true, "screen") + +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.NoLeftOnClass = true diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/grabber.lua b/garrysmod/addons/feature-wire/lua/wire/stools/grabber.lua new file mode 100644 index 0000000..4ce9d7b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/grabber.lua @@ -0,0 +1,42 @@ +WireToolSetup.setCategory( "Physics/Constraints" ) +WireToolSetup.open( "grabber", "Grabber", "gmod_wire_grabber", nil, "Grabbers" ) + +if CLIENT then + language.Add( "tool.wire_grabber.name", "Grabber Tool (Wire)" ) + language.Add( "tool.wire_grabber.desc", "Spawns a constant grabber prop for use with the wire system." ) + language.Add( "tool.wire_grabber.right_0", "Link grabber to a prop that'll also be welded for stability" ) + language.Add( "WireGrabberTool_Range", "Max Range:" ) + language.Add( "WireGrabberTool_Gravity", "Disable Gravity" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("Range"), self:GetClientNumber("Gravity")~=0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_range.mdl", + Range = 100, + Gravity = 1, +} + +WireToolSetup.SetupLinking(true, "prop") + +function TOOL:GetGhostMin( min ) + if self:GetModel() == "models/jaanus/wiretool/wiretool_grabber_forcer.mdl" then + return min.z + 20 + end + return min.z +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_grabber") + ModelPlug_AddToCPanel(panel, "Forcer", "wire_grabber", true, 1) + panel:CheckBox("#WireGrabberTool_Gravity", "wire_grabber_Gravity") + panel:NumSlider("#WireGrabberTool_Range", "wire_grabber_Range", 1, 10000, 0) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/graphics_tablet.lua b/garrysmod/addons/feature-wire/lua/wire/stools/graphics_tablet.lua new file mode 100644 index 0000000..d7a2d3d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/graphics_tablet.lua @@ -0,0 +1,46 @@ +--Wire graphics tablet by greenarrow +--http://gmodreviews.googlepages.com/ +--http://forums.facepunchstudios.com/greenarrow +--There may be a few bits of code from the wire panel here and there as i used it as a starting point. +--Credit to whoever created the first wire screen, from which all others seem to use the lagacy clientside drawing code (this one included) + +WireToolSetup.setCategory( "Input, Output/Mouse Interaction" ) +WireToolSetup.open( "graphics_tablet", "Graphics Tablet", "gmod_wire_graphics_tablet", nil, "Graphics Tablet" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_graphics_tablet.name", "Graphics Tablet Tool (Wire)" ) + language.Add( "Tool.wire_graphics_tablet.desc", "Spawns a graphics tablet, which outputs cursor coordinates" ) + language.Add( "Tool_wire_graphics_tablet_mode", "Output mode: -1 to 1 (ticked), 0 to 1 (unticked)" ) + language.Add( "Tool_wire_graphics_tablet_draw_background", "Draw background" ) + language.Add( "Tool_wire_graphics_tablet_createflat", "Create flat to surface" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetDataTables() + return { + DrawBackground = self:GetClientNumber("draw_background") ~= 0, + CursorMode = self:GetClientNumber("outmode") ~= 0 + } + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/kobilica/wiremonitorbig.mdl", + outmode = 0, + createflat = 1, + draw_background = 1, +} + +function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { Text = "#Tool.wire_graphics_tablet.name", Description = "#Tool.wire_graphics_tablet.desc" }) + + WireDermaExts.ModelSelect(panel, "wire_graphics_tablet_model", list.Get( "WireScreenModels" ), 5) -- screen with out a GPUlip setup + panel:CheckBox("#Tool_wire_graphics_tablet_mode", "wire_graphics_tablet_outmode") + panel:CheckBox("#Tool_wire_graphics_tablet_draw_background", "wire_graphics_tablet_draw_background") + panel:CheckBox("#Tool_wire_graphics_tablet_createflat", "wire_graphics_tablet_createflat") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/gyroscope.lua b/garrysmod/addons/feature-wire/lua/wire/stools/gyroscope.lua new file mode 100644 index 0000000..388aaba --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/gyroscope.lua @@ -0,0 +1,29 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "gyroscope", "Gyroscope", "gmod_wire_gyroscope", nil, "Gyroscopes" ) + +if CLIENT then + language.Add( "Tool.wire_gyroscope.name", "Gyroscope Tool (Wire)" ) + language.Add( "Tool.wire_gyroscope.desc", "Spawns a gyroscope for use with the wire system." ) + language.Add( "Tool.wire_gyroscope.out180", "Output -180 to 180 instead of 0 to 360" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + ModelPlug_Register("GPS") + + function TOOL:GetConVars() + return self:GetClientNumber("out180")~=0 + end +end + +TOOL.ClientConVar = { + model = "models/bull/various/gyroscope.mdl", + out180 = 0, +} + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "gyroscope", "wire_gyroscope") + panel:CheckBox("#Tool.wire_gyroscope.out180","wire_gyroscope_out180") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/hdd.lua b/garrysmod/addons/feature-wire/lua/wire/stools/hdd.lua new file mode 100644 index 0000000..3a721c8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/hdd.lua @@ -0,0 +1,315 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "hdd", "Memory - Flash EEPROM", "gmod_wire_hdd", nil, "Flash EEPROMs" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_hdd.name", "Flash (EEPROM) tool (Wire)" ) + language.Add( "Tool.wire_hdd.desc", "Spawns flash memory. It is used for permanent storage of data (carried over sessions)" ) + TOOL.Information = { { name = "left", text = "Create/Update flash memory" } } + + WireToolSetup.setToolMenuIcon( "icon16/database.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + function TOOL:GetConVars() + return self:GetClientNumber("driveid"), self:GetClientNumber("drivecap") + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_gate.mdl" +TOOL.ClientConVar[ "driveid" ] = 0 +TOOL.ClientConVar[ "client_driveid" ] = 0 +TOOL.ClientConVar[ "drivecap" ] = 128 + +TOOL.ClientConVar[ "packet_bandwidth" ] = 100 +TOOL.ClientConVar[ "packet_rate" ] = 0.4 + +local function GetStructName(steamID,HDD,name) + return "WireFlash\\"..(steamID or "UNKNOWN").."\\HDD"..HDD.."\\"..name..".txt" +end + +local function ParseFormatData(formatData) + local driveCap = 0 + local blockSize = 16 + if tonumber(formatData) then + driveCap = tonumber(formatData) + else + local formatInfo = string.Explode("\n",formatData) + if formatInfo[1] == "FLASH1" then + driveCap = tonumber(formatInfo[2]) or 0 + blockSize = 32 + end + end + return driveCap,blockSize +end + +local function GetFloatTable(Text) + local text = Text + local tbl = {} + local ptr = 0 + while (string.len(text) > 0) do + local value = string.sub(text,1,24) + text = string.sub(text,24,string.len(text)) + tbl[ptr] = tonumber(value) + ptr = ptr + 1 + end + return tbl +end + +local function MakeFloatTable(Table) + local text = "" + for i=0,#Table-1 do + --Clamp size to 24 chars + local floatstr = string.sub(tostring(Table[i]),1,24) + --Make a string, and append missing spaces + floatstr = floatstr .. string.rep(" ",24-string.len(floatstr)) + + text = text..floatstr + end + + return text +end +--[[ +if SERVER then + -- Concommand to send a single stream of bytes + local buffer = {} + local bufferBlock = nil + concommand.Add("wire_hdd_uploaddata", function(player, command, args) + local HDDID = tonumber(args[1]) + if (not HDDID) or (HDDID < 0) or (HDDID > 3) then return end + HDDID = math.floor(HDDID) + local STEAMID = player:SteamID() + STEAMID = string.gsub(STEAMID, ":", "_") + if (STEAMID == "UNKNOWN") or (STEAMID == "STEAM_0_0_0") then + STEAMID = "SINGLEPLAYER" + end + + local address = tonumber(args[2]) or 0 + local value = tonumber(args[3]) or 0 + + local block = math.floor(address / 32) + if block == bufferBlock then + buffer[address % 32] = value + else + if bufferBlock then + file.Write(GetStructName(STEAMID,HDDID,bufferBlock),MakeFloatTable(buffer)) + file.Write(GetStructName(STEAMID,HDDID,"drive"), + "FLASH1\n"..GetConVarNumber("wire_hdd_drivecap").."\n"..(address+31)) + end + + bufferBlock = block + buffer = {} + buffer[address % 32] = value + end + end) + + concommand.Add("wire_hdd_uploadend", function(player, command, args) + local HDDID = tonumber(args[1]) + if (not HDDID) or (HDDID < 0) or (HDDID > 3) then return end + HDDID = math.floor(HDDID) + local STEAMID = player:SteamID() + STEAMID = string.gsub(STEAMID, ":", "_") + if (STEAMID == "UNKNOWN") or (STEAMID == "STEAM_0_0_0") then + STEAMID = "SINGLEPLAYER" + end + + if bufferBlock then + file.Write(GetStructName(STEAMID,HDDID,block),MakeFloatTable(buffer)) + file.Write(GetStructName(STEAMID,HDDID,"drive"), + "FLASH1\n"..GetConVarNumber("wire_hdd_drivecap").."\n"..(address)) + end + + bufferBlock = nil + buffer = {} + end) + + -- Download from server to client + local downloadPointer = {} + concommand.Add("wire_hdd_download", function(player, command, args) + local HDDID = tonumber(args[1]) + if (not HDDID) or (HDDID < 0) or (HDDID > 3) then return end + HDDID = math.floor(HDDID) + local STEAMID = player:SteamID() + STEAMID = string.gsub(STEAMID, ":", "_") + if (STEAMID == "UNKNOWN") or (STEAMID == "STEAM_0_0_0") then + STEAMID = "SINGLEPLAYER" + end + + local formatData = file.Read(GetStructName(STEAMID,HDDID,"drive")) or "" + local driveCap,blockSize = ParseFormatData(formatData) + + -- Download code + downloadPointer[player:UserID()] = 0 + timer.Remove("flash_download"..player:UserID()) + timer.Create("flash_download"..player:UserID(),1/60,0,function() + local umsgrp = RecipientFilter() + umsgrp:AddPlayer(player) + + if file.Exists(GetStructName(STEAMID,HDDID,downloadPointer[player:UserID()])) then + local dataTable = GetFloatTable(file.Read(GetStructName(STEAMID,HDDID,downloadPointer[player:UserID()]))) + umsg.Start("flash_downloaddata", umsgrp) + umsg.Long(downloadPointer[player:UserID()]*blockSize) + umsg.Long(blockSize) + for i=1,blockSize do + umsg.Float(dataTable[i-1]) + end + umsg.End() + end + + downloadPointer[player:UserID()] = downloadPointer[player:UserID()] + 1 + if downloadPointer[player:UserID()] >= driveCap*1024/blockSize then + timer.Remove("flash_download"..player:UserID()) + end + end) + end) + + -- Clear hard drive + concommand.Add("wire_hdd_clearhdd", function(player, command, args) + local HDDID = tonumber(args[1]) + if (not HDDID) or (HDDID < 0) or (HDDID > 3) then return end + local STEAMID = player:SteamID() + STEAMID = string.gsub(STEAMID or "UNKNOWN", ":", "_") + if (STEAMID == "UNKNOWN") or (STEAMID == "STEAM_0_0_0") then + STEAMID = "SINGLEPLAYER" + end + + local formatData = file.Read(GetStructName(STEAMID,HDDID,"drive")) or "" + local driveCap,blockSize = ParseFormatData(formatData) + + -- FIXME: have to limit this to 2 kb until I add a timer + driveCap = math.min(driveCap,2) + file.Delete(GetStructName(STEAMID,HDDID,"drive")) + for block = 0,math.floor(driveCap*1024/blockSize) do + if file.Exists(GetStructName(STEAMID,HDDID,block)) then + file.Delete(GetStructName(STEAMID,HDDID,block)) + end + end + end) +else + function CPULib.OnDownloadData(um) + local HDDID = GetConVarNumber("wire_hdd_client_driveid") + + local offset,size = um:ReadLong(),um:ReadLong() + local dataTable = {} + for address=1,size do + dataTable[address] = um:ReadFloat() + end + file.Write(GetStructName("SINGLEPLAYER",HDDID,math.floor(offset/size)),MakeFloatTable(dataTable)) + file.Write(GetStructName("SINGLEPLAYER",HDDID,"drive"), + "FLASH1\n"..GetConVarNumber("wire_hdd_drivecap").."\n"..(offset+size-1)) + end + usermessage.Hook("flash_downloaddata", CPULib.OnDownloadData) + + concommand.Add("wire_hdd_clearhdd_client", function(player, command, args) + local HDDID = GetConVarNumber("wire_hdd_client_driveid") + local formatData = file.Read(GetStructName("SINGLEPLAYER",HDDID,"drive")) or "" + local driveCap,blockSize = ParseFormatData(formatData) + + -- FIXME: have to limit this to 2 kb until I add a timer + driveCap = math.min(driveCap,2) + file.Delete(GetStructName("SINGLEPLAYER",HDDID,"drive")) + for block = 0,math.floor(driveCap*1024/blockSize) do + if file.Exists(GetStructName("SINGLEPLAYER",HDDID,block)) then + file.Delete(GetStructName("SINGLEPLAYER",HDDID,block)) + end + end + end) + + -- Upload from client to server + local uploadPointer = 0 + concommand.Add("wire_hdd_upload", function(player, command, args) + local HDDID = GetConVarNumber("wire_hdd_client_driveid") + local TGTHDDID = GetConVarNumber("wire_hdd_driveid") + local formatData = file.Read(GetStructName("SINGLEPLAYER",HDDID,"drive")) or "" + local driveCap,blockSize = ParseFormatData(formatData) + + -- Upload code + uploadPointer = 0 + timer.Remove("flash_upload") + timer.Create("flash_upload",1/10,0,function() + if file.Exists(GetStructName("SINGLEPLAYER",HDDID,uploadPointer)) then + local dataTable = GetFloatTable(file.Read(GetStructName("SINGLEPLAYER",HDDID,uploadPointer))) + for i=0,blockSize-1 do + RunConsoleCommand("wire_hdd_uploaddata",TGTHDDID,i+uploadPointer*blockSize,dataTable[i]) + end + end + + uploadPointer = uploadPointer + 1 + if uploadPointer >= driveCap*1024/blockSize then + RunConsoleCommand("wire_hdd_uploadend",TGTHDDID) + end + end) + end) +end +]]-- +function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { Text = "#Tool.wire_hdd.name", Description = "#Tool.wire_hdd.desc" }) + + local mdl = vgui.Create("DWireModelSelect") + mdl:SetModelList( list.Get("Wire_gate_Models"), "wire_hdd_model" ) + mdl:SetHeight( 5 ) + panel:AddItem( mdl ) + + panel:AddControl("Slider", { + Label = "Drive ID", + Type = "Integer", + Min = "0", + Max = "3", + Command = "wire_hdd_driveid" + }) + + panel:AddControl("Slider", { + Label = "Capacity (KB)", + Type = "Integer", + Min = "0", + Max = "128", + Command = "wire_hdd_drivecap" + }) + + panel:AddControl("Label", { Text = "" }) + panel:AddControl("Label", { Text = "Flash memory manager" }) + + panel:AddControl("Slider", { + Label = "Server drive ID", + Type = "Integer", + Min = "0", + Max = "3", + Command = "wire_hdd_driveid" + }) + + panel:AddControl("Slider", { + Label = "Client drive ID", + Type = "Integer", + Min = "0", + Max = "99", + Command = "wire_hdd_client_driveid" + }) + + local Button = vgui.Create("DButton", panel) + panel:AddPanel(Button) + Button:SetText("Download server drive to client drive") + Button.DoClick = function() + RunConsoleCommand("wire_hdd_download",GetConVarNumber("wire_hdd_driveid")) + end + + panel:AddControl("Button", { + Text = "Upload client drive to server drive", + Command = "wire_hdd_upload" + }) + + local Button = vgui.Create("DButton", panel) + panel:AddPanel(Button) + Button:SetText("Clear server drive") + Button.DoClick = function() + RunConsoleCommand("wire_hdd_clearhdd",GetConVarNumber("wire_hdd_driveid")) + end + + panel:AddControl("Button", { + Text = "Clear client drive", + Command = "wire_hdd_clearhdd_client" + }) + +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/holoemitter.lua b/garrysmod/addons/feature-wire/lua/wire/stools/holoemitter.lua new file mode 100644 index 0000000..272def5 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/holoemitter.lua @@ -0,0 +1,67 @@ +WireToolSetup.setCategory( "Visuals/Holographic" ) +WireToolSetup.open( "holoemitter", "HoloEmitter", "gmod_wire_holoemitter", nil, "HoloEmitters" ) + +if CLIENT then + language.Add( "tool.wire_holoemitter.name", "Holographic Emitter Tool (Wire)" ) + language.Add( "tool.wire_holoemitter.desc", "The emitter required for holographic projections" ) + language.Add( "Tool.wire_holoemitter.fadetime", "Max fade time" ) + language.Add( "Tool.wire_holoemitter.fadetime.description", "Client side max fade time. Set to 0 to never fade (WARNING: May cause FPS issues if set to 0 or too high).") + language.Add( "Tool.wire_holoemitter.keeplatestdot", "Keep latest dot indefinitely (prevent fading)." ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Create emitter" }, + { name = "right_0", stage = 0, text = "Link emitter to any entity (makes it draw local to that entity instead)" }, + { name = "right_1", stage = 1, text = "Link to entity (click the same holoemitter again to unlink it)" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_range.mdl", +} + +function TOOL:RightClick( trace ) + if CLIENT then return true end + + local ent = trace.Entity + if (self:GetStage() == 0) then + if (ent:GetClass() == "gmod_wire_holoemitter") then + self.Target = ent + self:SetStage(1) + else + self:GetOwner():ChatPrint("That's not a holoemitter.") + return false + end + else + if (self.Target == ent or ent:IsWorld()) then + self:GetOwner():ChatPrint("Holoemitter unlinked.") + self.Target:UnLink() + self:SetStage(0) + return true + end + self.Target:Link( ent ) + self:SetStage(0) + self:GetOwner():ChatPrint( "Holoemitter linked to entity (".. tostring(ent)..")" ) + end + + return true +end + +function TOOL.Reload(trace) + self.Linked = nil + self:SetStage(0) + + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_holoemitter" then + ent:LinkToGrid( nil ) + return true + end +end + +function TOOL.BuildCPanel( panel ) + WireToolHelpers.MakePresetControl(panel, "wire_holoemitter") + WireDermaExts.ModelSelect(panel, "wire_holoemitter_model", list.Get( "Wire_Misc_Tools_Models" ), 1) + + panel:NumSlider("#Tool.wire_holoemitter.fadetime", "cl_wire_holoemitter_maxfadetime", 0, 100, 1) + panel:Help( "#Tool.wire_holoemitter.fadetime.description" ) + panel:CheckBox("#Tool.wire_holoemitter.keeplatestdot", "wire_holoemitter_keeplatestdot") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/hologrid.lua b/garrysmod/addons/feature-wire/lua/wire/stools/hologrid.lua new file mode 100644 index 0000000..d7a35b8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/hologrid.lua @@ -0,0 +1,71 @@ +WireToolSetup.setCategory( "Visuals/Holographic" ) +WireToolSetup.open( "hologrid", "HoloGrid", "gmod_wire_hologrid", nil, "HoloGrids" ) + +if CLIENT then + language.Add( "tool.wire_hologrid.name", "Holographic Grid Tool (Wire)" ) + language.Add( "tool.wire_hologrid.desc", "The grid to aid in holographic projections" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Create grid" }, + { name = "right_0", stage = 0, text = "Link HoloGrid with HoloEmitter or reference entity" }, + { name = "reload_0", stage = 0, text = "Unlink HoloEmitter or HoloGrid" }, + { name = "right_1", stage = 1, text = "Select the HoloGrid to link to" }, + } + language.Add( "Tool_wire_hologrid_usegps", "Use GPS coordinates" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "usegps" )~=0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + usegps = 0, +} + +function TOOL:RightClick( trace ) + if CLIENT then return true end + + local ent = trace.Entity + if (self:GetStage() == 0) then + if (ent:GetClass() == "gmod_wire_holoemitter") then + self.Target = ent + self:SetStage(1) + else + self:GetOwner():ChatPrint("That's not a holoemitter.") + return false + end + else + if (self.Target == ent or ent:IsWorld()) then + self:GetOwner():ChatPrint("Holoemitter unlinked.") + self.Target:UnLink() + self:SetStage(0) + return true + end + self.Target:Link( ent ) + self:SetStage(0) + self:GetOwner():ChatPrint( "Holoemitter linked to entity (".. tostring(ent)..")" ) + end + + return true +end + +function TOOL.Reload(trace) + self.Linked = nil + self:SetStage(0) + + if IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_hologrid" then + self.Linked:TriggerInput("Reference", nil) + return true + end +end + +function TOOL.BuildCPanel( panel ) + WireDermaExts.ModelSelect(panel, "wire_hologrid_model", list.Get( "Wire_Misc_Tools_Models" ), 1) + panel:CheckBox("#Tool_wire_hologrid_usegps", "wire_hologrid_usegps") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/hoverball.lua b/garrysmod/addons/feature-wire/lua/wire/stools/hoverball.lua new file mode 100644 index 0000000..42a6a19 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/hoverball.lua @@ -0,0 +1,46 @@ +WireToolSetup.setCategory( "Physics/Force" ) +WireToolSetup.open( "hoverball", "Hoverball", "gmod_wire_hoverball", nil, "Hoverballs" ) + +if CLIENT then + language.Add( "tool.wire_hoverball.name", "Wired Hoverball Tool" ) + language.Add( "tool.wire_hoverball.desc", "Spawns a hoverball for use with the wire system." ) + language.Add( "tool.wire_hoverball.starton", "Create with hover mode on" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 30 ) + +TOOL.ClientConVar = { + model = "models/dav0r/hoverball.mdl", + speed = 1, + resistance = 0, + strength = 1, + starton = 1, +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "speed" ), math.Clamp(self:GetClientNumber( "resistance" ), 0, 20), + math.Clamp(self:GetClientNumber( "strength" ), 0.1, 20), self:GetClientNumber( "starton" ) == 1 + end +end + +function TOOL:GetAngle(trace) + return Angle(0, 0, 0) +end + +function TOOL:GetGhostMin( min, trace ) + if trace.Entity:IsWorld() then + return -8 + end + return 0 +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_hoverball") + WireDermaExts.ModelSelect(panel, "wire_hoverball_model", list.Get("HoverballModels"), 2, true) + panel:NumSlider("#Movement Speed", "wire_hoverball_speed", 1, 10, 0) + panel:NumSlider("#Air Resistance", "wire_hoverball_resistance", 1, 20, 0) + panel:NumSlider("#Strength", "wire_hoverball_strength", .1, 20, 2) + panel:CheckBox("#tool.wire_hoverball.starton", "wire_hoverball_starton") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/hudindicator.lua b/garrysmod/addons/feature-wire/lua/wire/stools/hudindicator.lua new file mode 100644 index 0000000..6ff345b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/hudindicator.lua @@ -0,0 +1,392 @@ +-- Created by TheApathetic, so you know who to +-- blame if something goes wrong (someone else :P) +WireToolSetup.setCategory( "Visuals/Indicators" ) +WireToolSetup.open( "hudindicator", "Hud Indicator", "gmod_wire_hudindicator", nil, "Hud Indicators" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_hudindicator.name", "Hud Indicator Tool (Wire)" ) + language.Add( "Tool.wire_hudindicator.desc", "Spawns a Hud Indicator for use with the wire system." ) + + -- HUD Indicator stuff + language.Add( "ToolWireHudIndicator_showinhud", "Show in my HUD") + language.Add( "ToolWireHudIndicator_hudheaderdesc", "HUD Indicator Settings:") + language.Add( "ToolWireHudIndicator_huddesc", "Description:") + language.Add( "ToolWireHudIndicator_hudaddname", "Add description as Name") + language.Add( "ToolWireHudIndicator_hudaddnamedesc", "Also adds description as name of indicator (like Wire Namer)") + language.Add( "ToolWireHudIndicator_hudshowvalue", "Show Value as:") + language.Add( "ToolWireHudIndicator_hudshowvaluedesc", "How to display value in HUD readout along with description") + language.Add( "ToolWireHudIndicator_hudx", "HUD X:") + language.Add( "ToolWireHudIndicator_hudxdesc", "X of the upper-left corner of HUD display") + language.Add( "ToolWireHudIndicator_hudy", "HUD Y:") + language.Add( "ToolWireHudIndicator_hudydesc", "Y of the upper-left corner of HUD display") + language.Add( "ToolWireHudIndicator_hudstyle", "HUD Style:") + language.Add( "ToolWireHudIndicator_allowhook", "Allow others to hook") + language.Add( "ToolWireHudIndicator_allowhookdesc", "Allows others to hook this indicator with right-click") + language.Add( "ToolWireHudIndicator_hookhidehud", "Allow HideHUD on hooked") + language.Add( "ToolWireHudIndicator_hookhidehuddesc", "Whether your next hooked indicator will be subject to the HideHUD input of that indicator") + language.Add( "ToolWireHudIndicator_fullcircleangle", "Start angle for full circle gauge (deg):") + language.Add( "ToolWireHudIndicator_registeredindicators", "Registered Indicators:") + language.Add( "ToolWireHudIndicator_deleteselected", "Unregister Selected Indicator") + + TOOL.Information = { + { name = "left_0", stage = 0, text = "Create/Update " .. TOOL.Name }, + { name = "right_0", stage = 0, text = "Hook/Unhook someone else's " .. TOOL.Name }, + { name = "reload_0", stage = 0, text = "Link Hud Indicator to vehicle" }, + { name = "reload_1", stage = 1, text = "Now use Reload on a vehicle to link this Hud Indicator to it, or on the same Hud Indicator to unlink it" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" +TOOL.ClientConVar[ "a" ] = "0" +TOOL.ClientConVar[ "ar" ] = "255" +TOOL.ClientConVar[ "ag" ] = "0" +TOOL.ClientConVar[ "ab" ] = "0" +TOOL.ClientConVar[ "aa" ] = "255" +TOOL.ClientConVar[ "b" ] = "1" +TOOL.ClientConVar[ "br" ] = "0" +TOOL.ClientConVar[ "bg" ] = "255" +TOOL.ClientConVar[ "bb" ] = "0" +TOOL.ClientConVar[ "ba" ] = "255" +TOOL.ClientConVar[ "rotate90" ] = "0" +TOOL.ClientConVar[ "material" ] = "models/debug/debugwhite" +-- HUD Indicator stuff +TOOL.ClientConVar[ "showinhud" ] = "0" +TOOL.ClientConVar[ "huddesc" ] = "" +TOOL.ClientConVar[ "hudaddname" ] = "0" +TOOL.ClientConVar[ "hudshowvalue" ] = "0" +TOOL.ClientConVar[ "hudx" ] = "22" +TOOL.ClientConVar[ "hudy" ] = "200" +TOOL.ClientConVar[ "hudstyle" ] = "0" +TOOL.ClientConVar[ "allowhook" ] = "1" +TOOL.ClientConVar[ "hookhidehud" ] = "0" -- Couldn't resist this name :P +TOOL.ClientConVar[ "fullcircleangle" ] = "0" +TOOL.ClientConVar[ "registerdelete" ] = "0" + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("a"), math.min(self:GetClientNumber("ar"), 255), math.min(self:GetClientNumber("ag"), 255), math.min(self:GetClientNumber("ab"), 255), math.min(self:GetClientNumber("aa"), 255), + self:GetClientNumber("b"), math.min(self:GetClientNumber("br"), 255), math.min(self:GetClientNumber("bg"), 255), math.min(self:GetClientNumber("bb"), 255), math.min(self:GetClientNumber("ba"), 255), + self:GetClientInfo( "material" ), self:GetClientNumber( "showinhud" ) ~= 0, self:GetClientInfo( "huddesc" ), self:GetClientNumber( "hudaddname" ) ~= 0, + self:GetClientNumber( "hudshowvalue" ), self:GetClientNumber( "hudstyle" ), self:GetClientNumber( "allowhook" ) ~= 0, self:GetClientNumber( "fullcircleangle" ) + end +end + +function TOOL:RightClick( trace ) + -- Can only right-click on HUD Indicators + if not IsValid(trace.Entity) or trace.Entity:GetClass() ~= self.WireClass then return false end + + if (CLIENT) then return true end + + local ply = self:GetOwner() + local hookhidehud = (self:GetClientNumber( "hookhidehud" ) > 0) + + -- Can't hook your own HUD Indicators + if (ply == trace.Entity:GetPlayer()) then + WireLib.AddNotify(self:GetOwner(), "You cannot hook your own HUD Indicators!", NOTIFY_GENERIC, 7) + return false + end + + if not trace.Entity:CheckRegister(ply) then + -- Has the creator allowed this HUD Indicator to be hooked? + if not trace.Entity.AllowHook then + WireLib.AddNotify(self:GetOwner(), "You are not allowed to hook this HUD Indicator.", NOTIFY_GENERIC, 7) + return false + end + + trace.Entity:RegisterPlayer(ply, hookhidehud) + else + trace.Entity:UnRegisterPlayer(ply) + end + + return true +end + +-- Hook HUD Indicator to vehicle +function TOOL:Reload( trace ) + -- Can only use this on HUD Indicators and vehicles + -- The class checks are done later on, no need to do it twice + if not IsValid(trace.Entity) then return false end + + if (CLIENT) then return true end + + local iNum = self:NumObjects() + + if (iNum == 0) then + if trace.Entity:GetClass() ~= self.WireClass then + WireLib.AddNotify(self:GetOwner(), "You must select a HUD Indicator to link first.", NOTIFY_GENERIC, 7) + return false + end + + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + self:SetObject( 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + self:SetStage(1) + elseif (iNum == 1) then + if trace.Entity ~= self:GetEnt(1) then + if not string.find(trace.Entity:GetClass(), "prop_vehicle_") then + WireLib.AddNotify(self:GetOwner(), "HUD Indicators can only be linked to vehicles.", NOTIFY_GENERIC, 7) + self:ClearObjects() + self:SetStage(0) + return false + end + + local ent = self:GetEnt(1) + local bool = ent:LinkVehicle(trace.Entity) + + if not bool then + WireLib.AddNotify(self:GetOwner(), "Could not link HUD Indicator!", NOTIFY_GENERIC, 7) + return false + end + else + -- Unlink HUD Indicator from this vehicle + trace.Entity:UnLinkVehicle() + end + + self:ClearObjects() + self:SetStage(0) + end + + return true +end + +function TOOL:GetAngle( trace ) + local Ang = trace.HitNormal:Angle() + local Model = self:GetModel() + -- these models get mounted differently + if Model == "models/props_borealis/bluebarrel001.mdl" or Model == "models/props_junk/PopCan01a.mdl" then + return Ang + Angle(270, 0, 0) + elseif Model == "models/props_trainstation/trainstation_clock001.mdl" or Model == "models/segment.mdl" or Model == "models/segment2.mdl" then + return Ang + Angle(0, 0, (self:GetClientNumber("rotate90") * 90)) + else + return Ang + Angle(90,0,0) + end +end + +function TOOL:GetSelectedMin( min ) + local Model = self:GetModel() + -- these models are different + if Model == "models/props_trainstation/trainstation_clock001.mdl" or Model == "models/segment.mdl" or Model == "models/segment2.mdl" then + return min.x + else + return min.z + end +end + +function TOOL:Think() + local model = self:GetModel() + if not IsValid(self.GhostEntity) or self.GhostEntity:GetModel() ~= model then + self:MakeGhostEntity( model, Vector(0,0,0), Angle(0,0,0) ) + end + + self:UpdateGhost( self.GhostEntity ) + + if (SERVER) then + -- Add check to see if player is registered with + -- the HUD Indicator at which they are pointing + if ((self.NextCheckTime or 0) < CurTime()) then + local ply = self:GetOwner() + local trace = ply:GetEyeTrace() + + if IsValid(trace.Entity) and trace.Entity:GetClass() == self.WireClass and trace.Entity:GetPlayer() ~= ply then + local currentcheck = trace.Entity:CheckRegister(ply) + if currentcheck ~= self.LastRegisterCheck then + self.LastRegisterCheck = currentcheck + self:GetWeapon():SetNWBool("HUDIndicatorCheckRegister", currentcheck) + end + else + if (self.LastRegisterCheck == true) then + -- Don't need to set this every 1/10 of a second + self.LastRegisterCheck = false + self:GetWeapon():SetNWBool("HUDIndicatorCheckRegister", false) + end + end + self.NextCheckTime = CurTime() + 0.10 + end + end +end + +if (CLIENT) then + function TOOL:DrawHUD() + local isregistered = self:GetWeapon():GetNWBool("HUDIndicatorCheckRegister") + + if (isregistered) then + draw.WordBox(8, ScrW() / 2 + 10, ScrH() / 2 + 10, "Registered", "Default", Color(50, 50, 75, 192), Color(255, 255, 255, 255)) + end + end +end + +function TOOL:Holster() + self:ReleaseGhostEntity() + self:GetWeapon():SetNWBool("HUDIndicatorCheckRegister", false) +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_hudindicator") + + panel:AddControl("Slider", { + Label = "#ToolWireIndicator_a_value", + Type = "Float", + Min = "-10", + Max = "10", + Command = "wire_hudindicator_a" + }) + panel:AddControl("Color", { + Label = "#ToolWireIndicator_a_colour", + Red = "wire_hudindicator_ar", + Green = "wire_hudindicator_ag", + Blue = "wire_hudindicator_ab", + Alpha = "wire_hudindicator_aa", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + panel:AddControl("Slider", { + Label = "#ToolWireIndicator_b_value", + Type = "Float", + Min = "-10", + Max = "10", + Command = "wire_hudindicator_b" + }) + panel:AddControl("Color", { + Label = "#ToolWireIndicator_b_colour", + Red = "wire_hudindicator_br", + Green = "wire_hudindicator_bg", + Blue = "wire_hudindicator_bb", + Alpha = "wire_hudindicator_ba", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + ModelPlug_AddToCPanel(panel, "indicator", "wire_hudindicator", true) + + panel:AddControl("ComboBox", { + Label = "#ToolWireIndicator_Material", + MenuButton = "0", + + Options = { + ["Matte"] = { wire_hudindicator_material = "models/debug/debugwhite" }, + ["Shiny"] = { wire_hudindicator_material = "models/shiny" }, + ["Metal"] = { wire_hudindicator_material = "models/props_c17/metalladder003" } + } + }) + + panel:AddControl("CheckBox", { + Label = "#ToolWireIndicator_90", + Command = "wire_hudindicator_rotate90" + }) + + panel:AddControl("Header", { + Text = "#ToolWireHudIndicator_hudheaderdesc", + Description = "#ToolWireHudIndicator_hudheaderdesc" + }) + + panel:AddControl("CheckBox", { + Label = "#ToolWireHudIndicator_showinhud", + Command = "wire_hudindicator_showinhud" + }) + + panel:AddControl("TextBox", { + Label = "#ToolWireHudIndicator_huddesc", + Command = "wire_hudindicator_huddesc", + MaxLength = "20" + }) + + panel:AddControl("ComboBox", { + Label = "#ToolWireHudIndicator_hudstyle", + MenuButton = "0", + Options = { + ["Basic"] = { wire_hudindicator_hudstyle = "0" }, + ["Gradient"] = { wire_hudindicator_hudstyle = "1" }, + ["Percent Bar"] = { wire_hudindicator_hudstyle = "2" }, + ["Full Circle"] = { wire_hudindicator_hudstyle = "3" }, + ["Semi-circle"] = { wire_hudindicator_hudstyle = "4" } + } + }) + + panel:AddControl("CheckBox", { + Label = "#ToolWireHudIndicator_hudaddname", + Command = "wire_hudindicator_hudaddname", + Description = "#ToolWireHudIndicator_hudaddnamedesc" + }) + + panel:AddControl("ComboBox", { + Label = "#ToolWireHudIndicator_hudshowvalue", + MenuButton = "0", + Options = { + ["Do Not Show"] = { wire_hudindicator_hudshowvalue = "0" }, + ["Percent"] = { wire_hudindicator_hudshowvalue = "1" }, + ["Value"] = { wire_hudindicator_hudshowvalue = "2" } + }, + Description = "#ToolWireHudIndicator_hudshowvaluedesc" + }) + + panel:AddControl("CheckBox", { + Label = "#ToolWireHudIndicator_allowhook", + Command = "wire_hudindicator_allowhook", + Description = "#ToolWireHudIndicator_allowhookdesc" + }) + + panel:AddControl("CheckBox", { + Label = "#ToolWireHudIndicator_hookhidehud", + Command = "wire_hudindicator_hookhidehud", + Description = "#ToolWireHudIndicator_hookhidehuddesc" + }) + + panel:AddControl("Slider", { + Label = "#ToolWireHudIndicator_fullcircleangle", + Type = "Float", + Min = "0", + Max = "360", + Command = "wire_hudindicator_fullcircleangle" + }) + + -- Get the currently registered HUD Indicators for this player that can be unregistered + local registered = HUDIndicator_GetCurrentRegistered() + if (#registered > 0) then + local options = {} + for _, indinfo in pairs(registered) do + local txt = indinfo.Description or ("Indicator #"..indinfo.EIndex) + options[txt] = { wire_hudindicator_registerdelete = tostring(indinfo.EIndex) } + end + + panel:AddControl("ListBox", { + Label = "#ToolWireHudIndicator_registeredindicators", + MenuButton = 0, + Height = 120, + Options = options + }) + + panel:AddControl("Button", { + Text = "#ToolWireHudIndicator_deleteselected", + Command = "wire_hudindicator_delete" + }) + end + + panel:AddControl("TextBox", { + Label = "#ToolWireHudIndicator_hudx", + Command = "wire_hudindicator_hudx", + Description = "#ToolWireHudIndicator_hudxdesc" + }) + + panel:AddControl("TextBox", { + Label = "#ToolWireHudIndicator_hudy", + Command = "wire_hudindicator_hudy", + Description = "#ToolWireHudIndicator_hudydesc" + }) +end + +-- Concommand to unregister HUD Indicator through control panel +local function HUDIndicator_RemoteUnRegister(ply, cmd, arg) + local eindex = ply:GetInfoNum("wire_hudindicator_registerdelete", 0) + if (eindex == 0) then return end + local ent = ents.GetByIndex(eindex) + if IsValid(ent) then + ent:UnRegisterPlayer(ply) + end +end +concommand.Add("wire_hudindicator_delete", HUDIndicator_RemoteUnRegister) diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/hydraulic.lua b/garrysmod/addons/feature-wire/lua/wire/stools/hydraulic.lua new file mode 100644 index 0000000..ae6eb7e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/hydraulic.lua @@ -0,0 +1,211 @@ +WireToolSetup.setCategory( "Physics/Constraints" ) +WireToolSetup.open( "hydraulic", "Hydraulic", "gmod_wire_hydraulic", nil, "Hydraulics" ) + +TOOL.ClientConVar = { + material = "cable/rope", + width = "3", + speed = "16", + model = "models/beer/wiremod/hydraulic.mdl", + modelsize = "", + fixed = "0", + stretchonly = "0", +} + +if CLIENT then + language.Add( "Tool.wire_hydraulic.name", "Hydraulic Tool (Wire)" ) + language.Add( "Tool.wire_hydraulic.desc", "Makes a controllable hydraulic" ) + language.Add( "Tool.wire_hydraulic.stretchonly", "Winch Mode (Stretch Only)" ) + language.Add( "Tool.wire_hydraulic.stretchonly.help", "If this isn't enabled then it acts like a spring, pushing away the objects as they move closer." ) + language.Add( "Tool.wire_hydraulic.width", "Width:" ) + language.Add( "Tool.wire_hydraulic.material", "Material:" ) + language.Add( "Tool.wire_hydraulic.fixed", "Fixed" ) + language.Add( "Tool.wire_hydraulic.speed", "In/Out Speed Mul" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Place hydraulic" }, + { name = "right_0", stage = 0, text = "Place hydraulic along the hit normal" }, + { name = "left_1", stage = 1, text = "Choose the second point" }, + { name = "left_2", stage = 2, text = "Place the controller" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(16) + +if SERVER then + function TOOL:MakeEnt(ply, model, Ang, trace) + return MakeWireHydraulicController(ply, trace.HitPos, Ang, model, nil, self.constraint, self.rope) + end +end + +function TOOL:GetConVars() + return self:GetClientInfo( "material" ) or "cable/rope", + self:GetClientNumber("width", 3), + self:GetClientNumber("speed", 16), + self:GetClientNumber("fixed", 0), + self:GetClientNumber("stretchonly", 0) ~= 0 +end + +function TOOL:LeftClick_Update( trace ) + local const = trace.Entity.constraint + if IsValid( const ) then + -- Don't remove the controller when the constraint is removed + const:DontDeleteOnRemove( trace.Entity ) + if IsValid(trace.Entity.rope) then trace.Entity.rope:DontDeleteOnRemove( trace.Entity ) end + -- Get constraint info + local tbl = const:GetTable() + -- Remove constraint + const:Remove() + + -- Get convars + local material, width, speed, fixed, stretchonly = self:GetConVars() + + -- Make new constraint, at the old constraint's position, but with the new convar settings + local const, rope = MakeWireHydraulic( self:GetOwner(), + tbl.Ent1, tbl.Ent2, + tbl.Bone1, tbl.Bone2, + tbl.LPos1, tbl.LPos2, + width, material, speed, fixed, stretchonly ) + + -- Set the new references + const.MyCrtl = trace.Entity:EntIndex() + trace.Entity:SetConstraint( const ) + trace.Entity:DeleteOnRemove( const ) + + if rope then + trace.Entity:SetRope( rope ) + trace.Entity:DeleteOnRemove( rope ) + end + end +end + +function TOOL:LeftClick( trace ) + if not trace.Hit or ( trace.Entity:IsValid() and trace.Entity:IsPlayer() ) then return end + if ( SERVER and not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then return false end + + local iNum = self:NumObjects() + + -- Update existing constraint + if self:CheckHitOwnClass( trace ) and iNum == 0 then + if SERVER then + self:LeftClick_Update( trace ) + end + return true + end + + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + self:SetObject( iNum + 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if ( iNum > 1 ) then + + if ( CLIENT ) then + self:ClearObjects() + return true + end + + local ply = self:GetOwner() + local const, rope = self.constraint, self.rope + + if not IsValid(const) then + WireLib.AddNotify(self:GetOwner(), "Wire Hydraulic Invalid!", NOTIFY_GENERIC, 7) + self:ClearObjects() + self:SetStage(0) + return + end + + local controller = self:LeftClick_Make(trace, ply) + if isbool(controller) then return controller end + self:LeftClick_PostMake(controller, ply, trace) + + if controller then + controller:DeleteOnRemove(const) + if rope then controller:DeleteOnRemove( rope ) end + end + + self:ClearObjects() + self:SetStage(0) + + elseif ( iNum == 1 ) then + + if CLIENT then return true end + + -- Get client's CVars + local material, width, speed, fixed, stretchonly = self:GetConVars() + + -- Get information we're about to use + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + local Bone1, Bone2 = self:GetBone(1), self:GetBone(2) + local LPos1, LPos2 = self:GetLocalPos(1),self:GetLocalPos(2) + + local const, rope = MakeWireHydraulic(self:GetOwner(), Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, width, material, speed, fixed, stretchonly) + if not IsValid(const) then + WireLib.AddNotify(self:GetOwner(), "Wire Hydraulic Invalid!", NOTIFY_GENERIC, 7) + self:ClearObjects() + self:SetStage(0) + return + end + + self.constraint, self.rope = const,rope + + undo.Create(self.WireClass) + undo.AddEntity(const) + if rope then undo.AddEntity(rope) end + undo.SetPlayer(self:GetOwner()) + undo.Finish() + self:GetOwner():AddCleanup("ropeconstraints", const) + if rope then self:GetOwner():AddCleanup("ropeconstraints", rope) end + + self:SetStage(2) + else + self:SetStage( iNum+1 ) + end + + return true +end + +function TOOL:RightClick( trace ) + if self:NumObjects() > 1 then + if not IsValid(self.constraint) then + self:ClearObjects() + self:SetStage(0) + else + return false + end + end + + local tr = {} + tr.start = trace.HitPos + tr.endpos = tr.start + (trace.HitNormal * 16384) + tr.filter = {self:GetOwner()} + if IsValid(trace.Entity) then + tr.filter[2] = trace.Entity + end + local trace2 = util.TraceLine( tr ) + + if not hook.Run( "CanTool", self:GetOwner(), trace2, "wire_hydraulic" ) then return false end + + return self:LeftClick(trace) and self:LeftClick(trace2) +end + +function TOOL:Reload( trace ) + if not IsValid(trace.Entity) or trace.Entity:IsPlayer() then return false end + if CLIENT then return true end + + return constraint.RemoveConstraints( trace.Entity, "WireHydraulic" ) +end + +function TOOL:Think() + -- Disable ghost when making the constraint + if self:GetStage() == 2 then + WireToolObj.Think(self) + end +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakeModelSizer(panel, "wire_hydraulic_modelsize") + WireDermaExts.ModelSelect(panel, "wire_hydraulic_model", list.Get( "Wire_Hydraulic_Models" ), 1, true) + panel:CheckBox("#Tool.wire_hydraulic.stretchonly","wire_hydraulic_stretchonly") + panel:ControlHelp("#Tool.wire_hydraulic.stretchonly.help") + panel:CheckBox("#Tool.wire_hydraulic.fixed","wire_hydraulic_fixed") + panel:NumSlider("#Tool.wire_hydraulic.speed","wire_hydraulic_speed",4,120,0) + panel:NumSlider("#Tool.wire_hydraulic.width","wire_hydraulic_width",1,20,2) + panel:AddControl( "RopeMaterial", { Label = "#Tool.wire_hydraulic.material", convar = "wire_hydraulic_material" } ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/igniter.lua b/garrysmod/addons/feature-wire/lua/wire/stools/igniter.lua new file mode 100644 index 0000000..a46129e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/igniter.lua @@ -0,0 +1,34 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "igniter", "Igniter", "gmod_wire_igniter", nil, "Igniters" ) + +if CLIENT then + language.Add( "tool.wire_igniter.name", "Igniter Tool (Wire)" ) + language.Add( "tool.wire_igniter.desc", "Spawns a constant igniter prop for use with the wire system." ) + language.Add( "WireIgniterTool_trgply", "Allow Player Igniting" ) + language.Add( "WireIgniterTool_Range", "Max Range:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + CreateConVar('sbox_wire_igniters_maxlen', 30) + CreateConVar('sbox_wire_igniters_allowtrgply',1) + + function TOOL:GetConVars() + return self:GetClientNumber( "trgply" )~=0, self:GetClientNumber("range") + end +end + +TOOL.ClientConVar = { + trgply = 0, + range = 2048, + model = "models/jaanus/wiretool/wiretool_siren.mdl", +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_igniter") + WireDermaExts.ModelSelect(panel, "wire_igniter_Model", list.Get( "Wire_Laser_Tools_Models" ), 1, true) + panel:CheckBox("#WireIgniterTool_trgply", "wire_igniter_trgply") + panel:NumSlider("#WireIgniterTool_Range", "wire_igniter_range", 1, 10000, 0) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/indicator.lua b/garrysmod/addons/feature-wire/lua/wire/stools/indicator.lua new file mode 100644 index 0000000..350f1d2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/indicator.lua @@ -0,0 +1,123 @@ +WireToolSetup.setCategory( "Visuals/Indicators" ) +WireToolSetup.open( "indicator", "Indicator", "gmod_wire_indicator", nil, "Indicators" ) + +if CLIENT then + language.Add( "tool.wire_indicator.name", "Indicator Tool (Wire)" ) + language.Add( "tool.wire_indicator.desc", "Spawns a indicator for use with the wire system." ) + language.Add( "ToolWireIndicator_a_value", "A Value:" ) + language.Add( "ToolWireIndicator_a_colour", "A Colour:" ) + language.Add( "ToolWireIndicator_b_value", "B Value:" ) + language.Add( "ToolWireIndicator_b_colour", "B Colour:" ) + language.Add( "ToolWireIndicator_Material", "Material:" ) + language.Add( "ToolWireIndicator_90", "Rotate segment 90" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/lightbulb_add.png" ) +end +WireToolSetup.BaseLang() + +WireToolSetup.SetupMax( 21 ) + +if SERVER then + ModelPlug_Register("indicator") + + function TOOL:GetConVars() + return self:GetClientNumber("a"), + math.Clamp(self:GetClientNumber("ar"),0,255), + math.Clamp(self:GetClientNumber("ag"),0,255), + math.Clamp(self:GetClientNumber("ab"),0,255), + math.Clamp(self:GetClientNumber("aa"),0,255), + self:GetClientNumber("b"), + math.Clamp(self:GetClientNumber("br"),0,255), + math.Clamp(self:GetClientNumber("bg"),0,255), + math.Clamp(self:GetClientNumber("bb"),0,255), + math.Clamp(self:GetClientNumber("ba"),0,255) + end + + function TOOL:PostMake(ent) + duplicator.StoreEntityModifier( ent, "material", { MaterialOverride = self:GetClientInfo("material") } ) + end +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + a = 0, + ar = 255, + ag = 0, + ab = 0, + aa = 255, + b = 1, + br = 0, + bg = 255, + bb = 0, + ba = 255, + material = "models/debug/debugwhite", + rotate90 = 0, +} + +--function TOOL:GetGhostAngle( Ang ) +function TOOL:GetAngle( trace ) + local Ang = trace.HitNormal:Angle() + local Model = self:GetModel() + --these models get mounted differently + if Model == "models/props_borealis/bluebarrel001.mdl" || Model == "models/props_junk/PopCan01a.mdl" then + return Ang + Angle(-90, 0, 0) + elseif Model == "models/props_trainstation/trainstation_clock001.mdl" or Model == "models/segment.mdl" or Model == "models/segment2.mdl" then + return Ang + Angle(0, 0, (self:GetClientNumber("rotate90") * 90)) + end + Ang.pitch = Ang.pitch + 90 + return Ang +end + +function TOOL:GetGhostMin( min ) + local Model = self:GetModel() + --these models are different + if Model == "models/props_trainstation/trainstation_clock001.mdl" or Model == "models/segment.mdl" or Model == "models/segment2.mdl" then + return min.x + end + return min.z +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_indicator") + panel:NumSlider("#ToolWireIndicator_a_value", "wire_indicator_a", -10, 10, 1) + + panel:AddControl("Color", { + Label = "#ToolWireIndicator_a_colour", + Red = "wire_indicator_ar", + Green = "wire_indicator_ag", + Blue = "wire_indicator_ab", + Alpha = "wire_indicator_aa", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + panel:NumSlider("#ToolWireIndicator_b_value", "wire_indicator_b", -10, 10, 1) + + panel:AddControl("Color", { + Label = "#ToolWireIndicator_b_colour", + Red = "wire_indicator_br", + Green = "wire_indicator_bg", + Blue = "wire_indicator_bb", + Alpha = "wire_indicator_ba", + ShowAlpha = "1", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + ModelPlug_AddToCPanel(panel, "indicator", "wire_indicator", true) + + panel:AddControl("ComboBox", { + Label = "#ToolWireIndicator_Material", + Options = { + ["Matte"] = { wire_indicator_material = "models/debug/debugwhite" }, + ["Shiny"] = { wire_indicator_material = "models/shiny" }, + ["Metal"] = { wire_indicator_material = "models/props_c17/metalladder003" } + } + }) + + panel:CheckBox("#ToolWireIndicator_90", "wire_indicator_rotate90") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/input.lua b/garrysmod/addons/feature-wire/lua/wire/stools/input.lua new file mode 100644 index 0000000..2c805f0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/input.lua @@ -0,0 +1,44 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction" ) +WireToolSetup.open( "input", "Numpad Input", "gmod_wire_input", nil, "Numpad Inputs" ) + +if CLIENT then + language.Add( "tool.wire_input.name", "Input Tool (Wire)" ) + language.Add( "tool.wire_input.desc", "Spawns a input for use with the wire system." ) + language.Add( "WireInputTool_keygroup", "Key:" ) + language.Add( "WireInputTool_toggle", "Toggle" ) + language.Add( "WireInputTool_value_on", "Value On:" ) + language.Add( "WireInputTool_value_off", "Value Off:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("Numpad") + + function TOOL:GetConVars() + return self:GetClientNumber( "keygroup" ), self:GetClientNumber( "toggle" ), self:GetClientNumber( "value_off" ), self:GetClientNumber( "value_on" ) + end +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/numpad.mdl", + modelsize = "", + keygroup = 7, + toggle = 0, + value_off = 0, + value_on = 1, +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_input") + WireToolHelpers.MakeModelSizer(panel, "wire_input_modelsize") + ModelPlug_AddToCPanel(panel, "Numpad", "wire_input", true) + panel:AddControl("Numpad", { + Label = "#WireInputTool_keygroup", + Command = "wire_input_keygroup" + }) + panel:CheckBox("#WireInputTool_toggle", "wire_input_toggle") + panel:NumSlider("#WireInputTool_value_on", "wire_input_value_on", -10, 10, 1) + panel:NumSlider("#WireInputTool_value_off", "wire_input_value_off", -10, 10, 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/keyboard.lua b/garrysmod/addons/feature-wire/lua/wire/stools/keyboard.lua new file mode 100644 index 0000000..9d5a36b --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/keyboard.lua @@ -0,0 +1,59 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction", "Vehicle Control" ) +WireToolSetup.open( "keyboard", "Keyboard", "gmod_wire_keyboard", nil, "Keyboards" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_keyboard.name", "Wired Keyboard Tool (Wire)" ) + language.Add( "Tool.wire_keyboard.desc", "Spawns a keyboard input for use with the hi-speed wire system." ) + language.Add( "Tool.wire_keyboard.leavekey", "Leave Key" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + ModelPlug_Register("Keyboard") + + function TOOL:GetConVars() + return self:GetClientNumber( "autobuffer" ) ~= 0, self:GetClientNumber( "sync" ) ~= 0, self:GetClientNumber( "enterkeyascii" ) ~= 0 + end +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_input.mdl", + sync = "1", + layout = "American", + autobuffer = "1", + leavekey = KEY_LALT, + enterkeyascii = "1" +} + +WireToolSetup.SetupLinking(true, "vehicle") + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Keyboard", "wire_keyboard", true) + + local languages = panel:ComboBox("Keyboard Layout", "wire_keyboard_layout") + local curlayout = LocalPlayer():GetInfo("wire_keyboard_layout") + for k,v in pairs( Wire_Keyboard_Remap ) do + languages:AddChoice( k ) + if k == curlayout then + local curindex = #languages.Choices + timer.Simple(0, function() languages:ChooseOptionID(curindex) end) -- This needs to be delayed or it'll set the box to show "0" + end + end + panel:Help( "The selected language is clientside. Any keyboard you use, created by any player, will use your selection. If your keyboard layout is not available in this list, you are welcome to create it and post it as a pull request on the wiremod github page." ) + + panel:AddControl("Numpad", { + Label = "#Tool.wire_keyboard.leavekey", + Command = "wire_keyboard_leavekey", + }) + panel:Help( "This is the key used to exit a keyboard. This option is clientside. Any keyboard you use, created by any player, will use this key." ) + + panel:CheckBox("Lock player controls on keyboard", "wire_keyboard_sync") + panel:Help( "When on, 'locks' the player into the keyboard, meaning any keys they press will not move their character around. When off, they can walk around while typing. This option is serverside, and will be saved on the keyboard through duplications." ) + + panel:CheckBox("Automatic buffer clear", "wire_keyboard_autobuffer") + panel:Help( "When on, automatically removes the key from the buffer when the user releases it.\nWhen off, leaves all keys in the buffer until they are manually removed.\nTo manually remove a key, write any value to cell 0 to remove the first key, or write a specific ascii value to any address other than 0 to remove that specific key. This option is serverside, and will be saved on the keyboard through duplications.") + + panel:CheckBox("Use '\\n' for ENTER key instead of '\\r'","wire_keyboard_enterkeyascii") + panel:Help( "On: Enter=10 ('\\n')\nOff: Enter=13 ('\\r')\nThis option is serverside, and will be saved on the keyboard through duplications." ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/keypad.lua b/garrysmod/addons/feature-wire/lua/wire/stools/keypad.lua new file mode 100644 index 0000000..9a79962 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/keypad.lua @@ -0,0 +1,51 @@ +WireToolSetup.setCategory( "Input, Output" ) +WireToolSetup.open( "keypad", "Keypad", "gmod_wire_keypad", nil, "Keypads" ) + +if CLIENT then + language.Add( "tool."..TOOL.Mode..".name", TOOL.Name.." Tool (Wire)" ) + language.Add( "tool."..TOOL.Mode..".desc", "Spawns a "..TOOL.Name ) + language.Add( "tool."..TOOL.Mode..".password", "Password: " ) + language.Add( "tool."..TOOL.Mode..".secure", "Display Asterisks: " ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(10) + +if SERVER then + function TOOL:GetConVars() + return util.CRC(self:GetClientInfo("password")), self:GetClientNumber("secure") ~= 0 + end + + function TOOL:CheckPassword() + local password = self:GetClientNumber("password") + if password == nil or string.find(password, "0") then + WireLib.AddNotify(self:GetOwner(), "Password can only contain numbers 1-9", NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP3) + return false + elseif string.len(password) > 4 then + WireLib.AddNotify(self:GetOwner(), "Password cannot be over 4 characters", NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP3) + return false + end + return true + end + + function TOOL:MakeEnt( ply, model, Ang, trace ) + return self:CheckPassword() and WireLib.MakeWireEnt( ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}, self:GetConVars() ) + end + + function TOOL:LeftClick_Update( trace ) + if self:CheckPassword() then trace.Entity:Setup(self:GetConVars()) end + end +end + +TOOL.ClientConVar = { + model = "models/props_lab/keypad.mdl", + password = "", + secure = "0", + createflat = "1", -- The model needs this +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_keypad") + panel:TextEntry("#tool.wire_keypad.password", "wire_keypad_password"):SetNumeric(true) + panel:CheckBox("#tool.wire_keypad.secure", "wire_keypad_secure") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/lamp.lua b/garrysmod/addons/feature-wire/lua/wire/stools/lamp.lua new file mode 100644 index 0000000..761aa8c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/lamp.lua @@ -0,0 +1,166 @@ +WireToolSetup.setCategory( "Visuals/Lights" ) +WireToolSetup.open( "lamp", "Lamp", "gmod_wire_lamp", nil, "Lamps" ) + +if CLIENT then + language.Add( "tool.wire_lamp.name", "Wire Lamps" ) + language.Add( "tool.wire_lamp.desc", "Spawns a lamp for use with the wire system." ) + language.Add( "WireLampTool_RopeLength", "Rope Length:") + language.Add( "WireLampTool_FOV", "FOV:") + language.Add( "WireLampTool_Dist", "Distance:") + language.Add( "WireLampTool_Bright", "Brightness:") + language.Add( "WireLampTool_Const", "Constraint:" ) + language.Add( "WireLampTool_Color", "Color:" ) + TOOL.Information = { + { name = "left", text = "Create hanging lamp" }, + { name = "right", text = "Create unattached lamp" }, + } + + WireToolSetup.setToolMenuIcon( "icon16/lightbulb.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + function TOOL:GetConVars() + return math.Clamp( self:GetClientNumber( "r" ), 0, 255 ), + math.Clamp( self:GetClientNumber( "g" ), 0, 255 ), + math.Clamp( self:GetClientNumber( "b" ), 0, 255 ), + self:GetClientInfo( "texture" ), + self:GetClientNumber( "fov" ), + self:GetClientNumber( "distance" ), + self:GetClientNumber( "brightness" ) + end + + function TOOL:LeftClick_PostMake( ent, ply, trace ) + if ent == true then return true end + if ent == nil or ent == false or not ent:IsValid() then return false end + + local const = self:GetClientInfo( "const" ) + + if const == "weld" then + local const = WireLib.Weld( ent, trace.Entity, trace.PhysicsBone, true ) + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.AddEntity( const ) + undo.SetPlayer( ply ) + undo.Finish() + elseif const == "rope" then + + local length = self:GetClientNumber( "ropelength" ) + local material = self:GetClientInfo( "ropematerial" ) + + local LPos1 = Vector( -15, 0, 0 ) + local LPos2 = trace.Entity:WorldToLocal( trace.HitPos ) + + if trace.Entity:IsValid() then + local phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + if phys:IsValid() then + LPos2 = phys:WorldToLocal( trace.HitPos ) + end + end + + local constraint, rope = constraint.Rope( ent, trace.Entity, 0, trace.PhysicsBone, LPos1, LPos2, 0, length, 0, 1.5, material, nil ) + + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.AddEntity( rope ) + undo.AddEntity( constraint ) + undo.SetPlayer( ply ) + undo.Finish() + + else --none + ent:GetPhysicsObject():EnableMotion(false) -- freeze + + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.SetPlayer( ply ) + undo.Finish() + end + + ply:AddCleanup( self.WireClass, ent ) + + return true + end +end + +function TOOL:GetAngle( trace ) + return trace.HitNormal:Angle() +end + +function TOOL:SetPos( ent, trace ) + ent:SetPos(trace.HitPos + trace.HitNormal * 10) +end + +TOOL.ClientConVar = { + ropelength = 64, + ropematerial = "cable/rope", + r = 255, + g = 255, + b = 255, + const = "rope", + texture = "effects/flashlight001", + fov = 90, + distance = 1024, + brightness = 8, + model = "models/lamps/torch.mdl" +} + +-- Spawn a lamp without constraints (just frozen) +function TOOL:RightClick( trace ) + -- TODO: redo this function + if not trace.HitPos then return false end + if trace.Entity:IsPlayer() then return false end + if CLIENT then return true end + + local ply = self:GetOwner() + local noconstraint = true + + local ent = self:LeftClick_Make( trace, ply, noconstraint ) + if ent == true then return true end + if ent == nil or ent == false or not ent:IsValid() then return false end + + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.AddEntity( const ) + undo.SetPlayer( ply ) + undo.Finish() + + ply:AddCleanup( self.WireClass, ent ) + + return true +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_lamp") + + WireDermaExts.ModelSelect(panel, "wire_lamp_model", list.Get( "LampModels" ), 1) + panel:NumSlider("#WireLampTool_RopeLength", "wire_lamp_ropelength", 4, 400, 0) + panel:NumSlider("#WireLampTool_FOV", "wire_lamp_fov", 10, 170, 2) + panel:NumSlider("#WireLampTool_Dist", "wire_lamp_distance", 64, 2048, 0) + panel:NumSlider("#WireLampTool_Bright", "wire_lamp_brightness", 0, 8, 2) + + panel:AddControl("ComboBox", { + Label = "#WireLampTool_Const", + Options = { + ["Rope"] = { wire_lamp_const = "rope" }, + ["Weld"] = { wire_lamp_const = "weld" }, + ["None"] = { wire_lamp_const = "none" }, + } + }) + + panel:AddControl("Color", { + Label = "#WireLampTool_Color", + Red = "wire_lamp_r", + Green = "wire_lamp_g", + Blue = "wire_lamp_b", + ShowAlpha = "0", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + + local MatSelect = panel:MatSelect( "wire_lamp_texture", nil, true, 0.33, 0.33 ) + for k, v in pairs( list.Get( "LampTextures" ) ) do + MatSelect:AddMaterial( v.Name or k, k ) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/las_receiver.lua b/garrysmod/addons/feature-wire/lua/wire/stools/las_receiver.lua new file mode 100644 index 0000000..a447a50 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/las_receiver.lua @@ -0,0 +1,18 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "las_receiver", "Laser Pointer Receiver", "gmod_wire_las_receiver", nil, "Laser Pointer Receivers" ) + +if CLIENT then + language.Add( "Tool.wire_las_receiver.name", "Laser Receiver Tool (Wire)" ) + language.Add( "Tool.wire_las_receiver.desc", "Spawns a constant laser receiver prop for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_range.mdl", +} + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_las_receiver") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/latch.lua b/garrysmod/addons/feature-wire/lua/wire/stools/latch.lua new file mode 100644 index 0000000..e4eeb3e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/latch.lua @@ -0,0 +1,103 @@ +WireToolSetup.setCategory( "Physics/Constraints" ) +WireToolSetup.open( "latch", "Weld/Constraint Latch", "gmod_wire_latch", nil, "Constraint Latches" ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" + +if CLIENT then + language.Add( "Tool.wire_latch.name", "Latch Tool (Wire)" ) + language.Add( "Tool.wire_latch.desc", "Makes a controllable latch" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Choose the first entity to be latched" }, + { name = "left_1", stage = 1, text = "Choose the second entity to be latched" }, + { name = "reload_1", stage = 1, text = "Cancel" }, + { name = "left_2", stage = 2, text = "Place the controller" }, + { name = "reload_2", stage = 2, text = "Cancel" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 15 ) + +function TOOL:LeftClick( trace ) + if trace.Entity:IsValid() and trace.Entity:IsPlayer() then return end + + // If there's no physics object then we can't constraint it! + if SERVER and !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + local iNum = self:NumObjects() + + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + self:SetObject( iNum + 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if ( iNum > 1 ) then + if CLIENT then + self:ClearObjects() + return true + end + + local ply = self:GetOwner() + local Ent1, Ent2, Ent3 = self:GetEnt(1), self:GetEnt(2), trace.Entity + local const = self.Constraint + + local controller = self:LeftClick_Make( trace, ply ) + if isbool(controller) then return controller end + if !IsValid(controller) then + WireLib.AddNotify( self:GetOwner(), "Weld latch controller placement failed!", NOTIFY_GENERIC, 7 ) + self.Constraint = nil + self:ClearObjects() + self:SetStage(0) + return false + end + self:LeftClick_PostMake( controller, ply, trace ) + + // Send entity and constraint info over to the controller + controller:SendVars( self.Ent1, self.Ent2, self.Bone1, self.Bone2, self.Constraint ) + + // Initialize controller inputs/outputs + controller:TriggerInput( "Activate", 1 ) + Wire_TriggerOutput( controller, "Welded", 1 ) + + self.Constraint = nil + self:ClearObjects() + self:SetStage(0) + + elseif ( iNum == 1 ) then + if CLIENT then + return true + end + + // Get information we're about to use + self.Ent1, self.Ent2 = self:GetEnt(1), self:GetEnt(2) + self.Bone1, self.Bone2 = self:GetBone(1), self:GetBone(2) + + self.Constraint = MakeWireLatch( self.Ent1, self.Ent2, self.Bone1, self.Bone2 ) + + if IsValid(self.Constraint) then + self:SetStage(2) + else + WireLib.AddNotify( self:GetOwner(), "Weld latch invalid!", NOTIFY_GENERIC, 7 ) + self:ClearObjects() + self:SetStage(0) + end + + else + self:SetStage( iNum+1 ) + + end + + return true +end + +function TOOL:Reload( trace ) + if IsValid(self.Constraint) then + self.Constraint:Remove() + end + + self.Constraint = nil + self:ClearObjects() + self:SetStage(0) +end + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#Tool.wire_latch.name", Description = "#Tool.wire_latch.desc" } ) + WireDermaExts.ModelSelect(panel, "wire_latch_model", list.Get( "Wire_Misc_Tools_Models" ), 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/lever.lua b/garrysmod/addons/feature-wire/lua/wire/stools/lever.lua new file mode 100644 index 0000000..234bd01 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/lever.lua @@ -0,0 +1,34 @@ +WireToolSetup.setCategory( "Input, Output" ) +WireToolSetup.open( "lever", "Lever", "gmod_wire_lever", nil, "Levers" ) + +if CLIENT then + language.Add( "tool.wire_lever.name", "Lever Tool (Wire)" ) + language.Add( "tool.wire_lever.desc", "Spawns a Lever for use with the wire system." ) + language.Add( "tool.wire_lever.minvalue", "Min Value:" ) + language.Add( "tool.wire_lever.maxvalue", "Max Value:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "min" ), self:GetClientNumber( "max" ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +function TOOL:GetModel() + return "models/props_wasteland/tram_leverbase01.mdl" +end + +TOOL.ClientConVar = { + min = 0, + max = 1 +} + +function TOOL.BuildCPanel(panel) + panel:NumSlider("#Tool.wire_lever.minvalue", "wire_lever_min", -10, 10, 2 ) + panel:NumSlider("#Tool.wire_lever.maxvalue", "wire_lever_max", -10, 10, 2 ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/light.lua b/garrysmod/addons/feature-wire/lua/wire/stools/light.lua new file mode 100644 index 0000000..34bf2aa --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/light.lua @@ -0,0 +1,135 @@ +WireToolSetup.setCategory( "Visuals/Lights" ) +WireToolSetup.open( "light", "Light", "gmod_wire_light", nil, "Lights" ) + +if CLIENT then + language.Add( "tool.wire_light.name", "Light Tool (Wire)" ) + language.Add( "tool.wire_light.desc", "Spawns a Light for use with the wire system." ) + language.Add( "WireLightTool_RopeLength", "Rope Length:") + language.Add( "WireLightTool_bright", "Glow brightness:") + language.Add( "WireLightTool_size", "Glow size:" ) + language.Add( "WireLightTool_directional", "Directional Component" ) + language.Add( "WireLightTool_radiant", "Radiant Component" ) + language.Add( "WireLightTool_glow", "Glow Component" ) + language.Add( "WireLightTool_const", "Constraint:" ) + language.Add( "WireLightTool_color", "Initial Color:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/lightbulb.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(8) + +if SERVER then + function TOOL:GetConVars() + return + self:GetClientNumber("directional") ~= 0, + self:GetClientNumber("radiant") ~= 0, + self:GetClientNumber("glow") ~= 0, + self:GetClientNumber("brightness"), + self:GetClientNumber("size"), + self:GetClientNumber("r"), + self:GetClientNumber("g"), + self:GetClientNumber("b") + end + + function TOOL:LeftClick_PostMake( ent, ply, trace ) + if ent == true then return true end + if ent == nil or ent == false or not ent:IsValid() then return false end + + local const = self:GetClientInfo( "const" ) + + if const == "weld" then + local const = WireLib.Weld( ent, trace.Entity, trace.PhysicsBone, true ) + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.AddEntity( const ) + undo.SetPlayer( ply ) + undo.Finish() + elseif const == "rope" then + + local length = math.Clamp( self:GetClientNumber( "ropelength" ), 4, 1024 ) + local material = "cable/rope" + + local LPos1 = Vector( 0, 0, 0 ) + if ent:GetModel() == "models/maxofs2d/light_tubular.mdl" then LPos1 = Vector( 0, 0, 5 ) end + + local LPos2 = trace.Entity:WorldToLocal( trace.HitPos ) + + if trace.Entity:IsValid() then + local phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + if phys:IsValid() then + LPos2 = phys:WorldToLocal( trace.HitPos ) + end + end + + local constraint, rope = constraint.Rope( ent, trace.Entity, 0, trace.PhysicsBone, LPos1, LPos2, 0, length, 0, 1.5, material, nil ) + + ent:GetPhysicsObject():Wake() + + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.AddEntity( rope ) + undo.AddEntity( constraint ) + undo.SetPlayer( ply ) + undo.Finish() + + else --none + ent:GetPhysicsObject():EnableMotion(false) -- freeze + + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + undo.SetPlayer( ply ) + undo.Finish() + end + + ply:AddCleanup( self.WireClass, ent ) + + return true + end +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + directional = 0, + radiant = 0, + glow = 0, + ropelength = 64, + brightness = 2, + size = 256, + const = "weld", + r = 0, + g = 0, + b = 0 +} + +function TOOL.BuildCPanel(panel) + local Models = list.Get( "Wire_Misc_Tools_Models" ) -- default wire models + Models["models/MaxOfS2D/light_tubular.mdl"] = true -- GMod light + + WireDermaExts.ModelSelect(panel, "wire_light_model", Models, 1) + panel:CheckBox("#WireLightTool_directional", "wire_light_directional") + panel:CheckBox("#WireLightTool_radiant", "wire_light_radiant") + panel:CheckBox("#WireLightTool_glow", "wire_light_glow") + panel:NumSlider("#WireLightTool_bright", "wire_light_brightness", 0, 10, 0) + panel:NumSlider("#WireLightTool_size", "wire_light_size", 0, 1024, 0) + panel:AddControl("ComboBox", { + Label = "#WireLightTool_Const", + Options = { + ["Weld"] = { wire_light_const = "weld" }, + ["None"] = { wire_light_const = "none" }, + ["Rope"] = { wire_light_const = "rope" } + } + }) + panel:NumSlider("#WireLightTool_RopeLength", "wire_light_ropelength", 4, 1024, 0) + panel:AddControl("Color", { + Label = "#WireLightTool_color", + Red = "wire_light_r", + Green = "wire_light_g", + Blue = "wire_light_b", + ShowAlpha = "0", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/locator.lua b/garrysmod/addons/feature-wire/lua/wire/stools/locator.lua new file mode 100644 index 0000000..cdc9193 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/locator.lua @@ -0,0 +1,20 @@ +WireToolSetup.setCategory( "Detection/Beacon" ) +WireToolSetup.open( "locator", "Locator", "gmod_wire_locator", nil, "Locators" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_locator.name", "Locator Beacon Tool (Wire)" ) + language.Add( "Tool.wire_locator.desc", "Spawns a locator beacon for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 30 ) + +TOOL.ClientConVar = { + model = "models/props_lab/powerbox02d.mdl", + createflat = 1 +} + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_locator") + panel:CheckBox("#Create Flat to Surface", "wire_locator_createflat") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/motor.lua b/garrysmod/addons/feature-wire/lua/wire/stools/motor.lua new file mode 100644 index 0000000..a1d5182 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/motor.lua @@ -0,0 +1,216 @@ +WireToolSetup.setCategory( "Physics/Force" ) +WireToolSetup.open( "motor", "Motor", "gmod_wire_motor", nil, "Motors" ) + +if CLIENT then + language.Add( "Tool.wire_motor.name", "Motor Tool (Wire)" ) + language.Add( "Tool.wire_motor.desc", "Makes a controllable motor" ) + language.Add( "WireMotorTool_torque", "Torque:" ) + language.Add( "WireMotorTool_friction", "Hinge Friction:" ) + language.Add( "WireMotorTool_nocollide", "No Collide" ) + language.Add( "WireMotorTool_forcelimit", "Force Limit:" ) + language.Add( "WireMotorTool_offset", "Position Offset:" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Choose the wheel's axis" }, + { name = "left_1", stage = 1, text = "Choose the base's axis" }, + { name = "left_2", stage = 2, text = "Place the controller" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + function TOOL:MakeEnt(ply, model, Ang, trace) + return MakeWireMotorController(ply, trace.HitPos, Ang, nil, model, self.constraint, self.axis) + end +end + +function TOOL:GetConVars() + return self:GetClientNumber( "torque" ), + self:GetClientNumber( "friction" ), + self:GetClientNumber( "nocollide" ), + self:GetClientNumber( "forcelimit" ), + self:GetClientNumber( "offset" ) +end + +function TOOL:LeftClick_Update( trace ) + local const = trace.Entity.constraint + if IsValid( const ) then + -- Don't remove the controller when the constraint is removed + const:DontDeleteOnRemove( trace.Entity ) + if IsValid(trace.Entity.axis) then trace.Entity.axis:DontDeleteOnRemove( trace.Entity ) end + -- Get constraint info + local tbl = const:GetTable() + -- Remove constraint + const:Remove() + + -- Get convars + local torque, friction, nocollide, forcelimit = self:GetConVars() + + -- Make new constraint, at the old constraint's position, but with the new convar settings + local const, axis = MakeWireMotor( self:GetOwner(), + tbl.Ent1, tbl.Ent2, + tbl.Bone1, tbl.Bone2, + tbl.LPos1, tbl.LPos2, + friction, torque, nocollide, forcelimit ) + + -- Set the new references + const.MyCrtl = trace.Entity:EntIndex() + trace.Entity:SetConstraint( const ) + trace.Entity:DeleteOnRemove( const ) + + if axis then + trace.Entity:SetAxis( axis ) + trace.Entity:DeleteOnRemove( axis ) + end + end +end + +function TOOL:LeftClick( trace ) + if IsValid( trace.Entity ) and trace.Entity:IsPlayer() then return end + + -- If there's no physics object then we can't constraint it! + if SERVER and not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + local iNum = self:NumObjects() + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + + -- Update existing constraint + if self:CheckHitOwnClass( trace ) and iNum == 0 then + if SERVER then + self:LeftClick_Update( trace ) + end + return true + end + + -- Don't allow us to choose the world as the first object + if iNum == 0 and not IsValid( trace.Entity ) then return end + + -- Don't allow us to choose the same object + if iNum == 1 and trace.Entity == self:GetEnt(1) then return end + + + -- Get client's CVars + local torque, friction, nocollide, forcelimit, offset = self:GetConVars() + + if iNum > 1 then + if CLIENT then + self:ClearObjects() + return true + end + + local ply = self:GetOwner() + local Ent1, Ent2, Ent3 = self:GetEnt(1), self:GetEnt(2), trace.Entity + local const, axis = self.constraint, self.axis + + if not const or not IsValid( const ) then + WireLib.AddNotify(self:GetOwner(), "Wire Motor Invalid!", NOTIFY_GENERIC, 7) + self:ClearObjects() + self:SetStage(0) + return + end + + local controller = self:LeftClick_Make(trace, ply) + if isbool(controller) then return controller end + self:LeftClick_PostMake(controller, ply, trace) + + if controller then + controller:DeleteOnRemove( const ) + if axis then controller:DeleteOnRemove( axis ) end + end + + self:ClearObjects() + self:SetStage(0) + elseif iNum == 1 then + self:SetObject( iNum + 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + if CLIENT then + self:ClearObjects() + self:ReleaseGhostEntity() + return true + end + + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + local Bone1, Bone2 = self:GetBone(1), self:GetBone(2) + local WPos1, WPos2 = self:GetPos(1), self:GetPos(2) + local LPos1, LPos2 = self:GetLocalPos(1), self:GetLocalPos(2) + local Norm1, Norm2 = self:GetNormal(1), self:GetNormal(2) + local Phys1, Phys2 = self:GetPhys(1), self:GetPhys(2) + + -- Note: To keep stuff ragdoll friendly try to treat things as physics objects rather than entities + local Ang1, Ang2 = Norm1:Angle(), (Norm2 * -1):Angle() + local TargetAngle = Phys1:AlignAngles( Ang1, Ang2 ) + + Phys1:SetAngles( TargetAngle ) + + -- Move the object so that the hitpos on our object is at the second hitpos + local TargetPos = WPos2 + (Phys1:GetPos() - self:GetPos(1)) + + -- Set the position + Phys1:SetPos( TargetPos ) + + -- Wake up the physics object so that the entity updates + Phys1:Wake() + + -- Set the hinge Axis perpendicular to the trace hit surface + LPos1 = Phys1:WorldToLocal( WPos2 + Norm2 * 64 ) + + local constraint, axis = MakeWireMotor( self:GetOwner(), Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, friction, torque, nocollide, forcelimit ) + self.constraint, self.axis = constraint, axis + + undo.Create("gmod_wire_motor") + if axis then undo.AddEntity( axis ) end + if constraint then undo.AddEntity( constraint ) end + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + + if axis then self:GetOwner():AddCleanup( "constraints", axis ) end + if constraint then self:GetOwner():AddCleanup( "constraints", constraint ) end + + self:SetStage(2) + self:ReleaseGhostEntity() + else + offset = math.Clamp(offset, 0, 1024) + self:SetObject( iNum + 1, trace.Entity, trace.HitPos + trace.HitNormal*offset, Phys, trace.PhysicsBone, trace.HitNormal ) + self:StartGhostEntity( trace.Entity ) + self:SetStage( iNum+1 ) + end + + return true +end + +function TOOL:RightClick( trace ) + return false +end + +function TOOL:Reload( trace ) + if not IsValid( trace.Entity ) or trace.Entity:IsPlayer() then return false end + if CLIENT then return true end + + return constraint.RemoveConstraints( trace.Entity, "WireMotor" ) +end + +function TOOL:Think() + if self:NumObjects() == 1 then self:UpdateGhostEntity() end +end + +TOOL.ClientConVar = { + torque = 500, + friction = 1, + nocollide = 1, + forcelimit = 0, + offset = 2, + model = "models/jaanus/wiretool/wiretool_siren.mdl" +} + +function TOOL.BuildCPanel(panel) + local models = { + ["models/jaanus/wiretool/wiretool_siren.mdl"] = true, + ["models/jaanus/wiretool/wiretool_controlchip.mdl"] = true + } + + WireDermaExts.ModelSelect( panel, "wire_motor_model", models, 1 ) + panel:NumSlider( "#WireMotorTool_torque", "wire_motor_torque", 0, 10000, 5 ) + panel:NumSlider( "#WireMotorTool_forcelimit", "wire_motor_forcelimit", 0, 50000, 10 ) + panel:NumSlider( "#WireMotorTool_friction", "wire_motor_friction", 0, 100, 1 ) + panel:NumSlider( "#WireMotorTool_offset", "wire_motor_offset", 0, 512, 2 ) + panel:CheckBox( "#WireMotorTool_nocollide", "wire_motor_nocollide" ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/nailer.lua b/garrysmod/addons/feature-wire/lua/wire/stools/nailer.lua new file mode 100644 index 0000000..c85e768 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/nailer.lua @@ -0,0 +1,31 @@ +WireToolSetup.setCategory( "Physics/Constraints" ) +WireToolSetup.open( "nailer", "Nailer", "gmod_wire_nailer", nil, "Nailers" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_nailer.name", "Nailer Tool (Wire)" ) + language.Add( "Tool.wire_nailer.desc", "Spawns a constant nailer prop for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + function TOOL:GetConVars() + return self:GetClientNumber( "forcelim" ), self:GetClientNumber( "range" ), self:GetClientNumber( "beam" )==1 + end +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + forcelim = "0", + range = 100, + beam = 1, +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_nailer") + ModelPlug_AddToCPanel(panel, "Laser_Tools", "wire_nailer", true) + panel:NumSlider("#Force Limit", "wire_nailer_forcelim", 0, 10000, 0) + panel:NumSlider("Range", "wire_nailer_range", 1, 2048, 0) + panel:CheckBox("Show Beam", "wire_nailer_beam") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/numpad.lua b/garrysmod/addons/feature-wire/lua/wire/stools/numpad.lua new file mode 100644 index 0000000..bca169c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/numpad.lua @@ -0,0 +1,38 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction" ) +WireToolSetup.open( "numpad", "Numpad", "gmod_wire_numpad", nil, "Numpads" ) + +if CLIENT then + language.Add( "Tool.wire_numpad.name", "Wired Numpad Tool (Wire)" ) + language.Add( "Tool.wire_numpad.desc", "Spawns a numpad input for use with the wire system." ) + language.Add( "WireNumpadTool_toggle", "Toggle" ) + language.Add( "WireNumpadTool_value_on", "Value On:" ) + language.Add( "WireNumpadTool_value_off", "Value Off:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("Numpad") + + function TOOL:GetConVars() + return self:GetClientNumber( "toggle" )==1, self:GetClientNumber( "value_off" ), self:GetClientNumber( "value_on" ) + end +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/numpad.mdl", + modelsize = "", + toggle = 0, + value_off = 0, + value_on = 0, +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_numpad") + WireToolHelpers.MakeModelSizer(panel, "wire_numpad_modelsize") + ModelPlug_AddToCPanel(panel, "Numpad", "wire_numpad", true) + panel:CheckBox("#WireNumpadTool_toggle","wire_numpad_toggle") + panel:NumSlider("#WireNumpadTool_value_on","wire_numpad_value_on",-10,10,0) + panel:NumSlider("#WireNumpadTool_value_off","wire_numpad_value_off",-10,10,0) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/oscilloscope.lua b/garrysmod/addons/feature-wire/lua/wire/stools/oscilloscope.lua new file mode 100644 index 0000000..a135ec1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/oscilloscope.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Visuals/Screens" ) +WireToolSetup.open( "oscilloscope", "Oscilloscope", "gmod_wire_oscilloscope", nil, "Oscilloscopes" ) + +if CLIENT then + language.Add( "tool.wire_oscilloscope.name", "Oscilloscope Tool (Wire)" ) + language.Add( "tool.wire_oscilloscope.desc", "Spawns an oscilloscope that displays line graphs." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.NoLeftOnClass = true -- no update ent function needed +TOOL.ClientConVar = { + model = "models/props_lab/monitor01b.mdl", + createflat = 0, +} + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_oscilloscope_model", list.Get( "WireScreenModels" ), 5) + panel:CheckBox("#Create Flat to Surface", "wire_oscilloscope_createflat") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/output.lua b/garrysmod/addons/feature-wire/lua/wire/stools/output.lua new file mode 100644 index 0000000..25f9caf --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/output.lua @@ -0,0 +1,35 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction" ) +WireToolSetup.open( "output", "Numpad Output", "gmod_wire_output", nil, "Numpad Outputs" ) + +if CLIENT then + language.Add( "Tool.wire_output.name", "Output Tool (Wire)" ) + language.Add( "Tool.wire_output.desc", "Spawns an output for use with the wire system." ) + language.Add( "Tool.wire_output.keygroup", "Key:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + ModelPlug_Register("Numpad") + + function TOOL:GetConVars() + return self:GetClientNumber( "keygroup" ) + end +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/numpad.mdl", + modelsize = "", + keygroup = 1 +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_output") + WireToolHelpers.MakeModelSizer(panel, "wire_output_modelsize") + ModelPlug_AddToCPanel(panel, "Numpad", "wire_output", true) + panel:AddControl("Numpad", { + Label = "#Tool.wire_output.keygroup", + Command = "wire_output_keygroup", + }) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/pixel.lua b/garrysmod/addons/feature-wire/lua/wire/stools/pixel.lua new file mode 100644 index 0000000..f3bfaef --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/pixel.lua @@ -0,0 +1,25 @@ +WireToolSetup.setCategory( "Visuals/Indicators" ) +WireToolSetup.open( "pixel", "Pixel", "gmod_wire_pixel", nil, "Pixels" ) + +if CLIENT then + language.Add( "tool.wire_pixel.name", "Pixel Tool (Wire)" ) + language.Add( "tool.wire_pixel.desc", "Spawns a Pixel for use with the wire system." ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + WireToolSetup.setToolMenuIcon( "icon16/lightbulb_add.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("pixel") +end + +TOOL.NoLeftOnClass = true -- no update ent function needed +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", +} + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_pixel_model", list.Get("Wire_pixel_Models"), 3, true) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/plug.lua b/garrysmod/addons/feature-wire/lua/wire/stools/plug.lua new file mode 100644 index 0000000..1fbadc4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/plug.lua @@ -0,0 +1,147 @@ +WireToolSetup.setCategory( "Input, Output/Data Transfer" ) +WireToolSetup.open( "plug", "Plug", "gmod_wire_socket", nil, "Plugs" ) + +if (SERVER) then + + CreateConVar("sbox_maxwire_plugs",20) + CreateConVar("sbox_maxwire_sockets",20) + +else + language.Add( "Tool.wire_plug.name", "Plug & Socket Tool (Wire)" ) + language.Add( "Tool.wire_plug.desc", "Spawns plugs and sockets for use with the wire system." ) + language.Add( "sboxlimit_wire_plugs", "You've hit the Wire Plugs limit!" ) + language.Add( "sboxlimit_wire_sockets", "You've hit the Wire Sockets limit!" ) + language.Add( "undone_wireplug", "Undone Wire Plug" ) + language.Add( "undone_wiresocket", "Undone Wire Socket" ) + + language.Add( "Tool_wire_plug_freeze", "Freeze the socket." ) + language.Add( "Tool_wire_plug_array", "Use array inputs/outputs instead." ) + language.Add( "Tool_wire_plug_weldforce", "Plug weld force:" ) + language.Add( "Tool_wire_plug_attachrange", "Plug attachment detection range:" ) + language.Add( "Tool_wire_plug_drawoutline", "Draw the white outline on plugs and sockets." ) + language.Add( "Tool_wire_plug_drawoutline_tooltip", "Disabling this helps you see inside the USB plug model when you set its material to wireframe." ) + language.Add( "Tool_wire_plug_angleoffset", "Spawn angle offset" ) + TOOL.Information = { + { name = "left", text = "Create/Update Socket" }, + { name = "right", text = "Create/Update " .. TOOL.Name }, + { name = "reload", text = "Increase angle offset by 45 degrees" }, + } +end + +WireToolSetup.BaseLang() + +TOOL.ClientConVar["model"] = "models/props_lab/tpplugholder_single.mdl" +TOOL.ClientConVar["array"] = 0 +TOOL.ClientConVar["weldforce"] = 5000 +TOOL.ClientConVar["attachrange"] = 5 +TOOL.ClientConVar["drawoutline"] = 1 +TOOL.ClientConVar["angleoffset"] = 0 + +local SocketModels = { + ["models/props_lab/tpplugholder_single.mdl"] = "models/props_lab/tpplug.mdl", + ["models/bull/various/usb_socket.mdl"] = "models/bull/various/usb_stick.mdl", + ["models/hammy/pci_slot.mdl"] = "models/hammy/pci_card.mdl", + ["models/wingf0x/isasocket.mdl"] = "models/wingf0x/isaplug.mdl", + ["models/wingf0x/altisasocket.mdl"] = "models/wingf0x/isaplug.mdl", + ["models/wingf0x/ethernetsocket.mdl"] = "models/wingf0x/ethernetplug.mdl", + ["models/wingf0x/hdmisocket.mdl"] = "models/wingf0x/hdmiplug.mdl" +} + +local AngleOffset = { + ["models/props_lab/tpplugholder_single.mdl"] = Angle(0,0,0), + ["models/props_lab/tpplug.mdl"] = Angle(0,0,0), + ["models/bull/various/usb_socket.mdl"] = Angle(0,0,0), + ["models/bull/various/usb_stick.mdl"] = Angle(0,0,0), + ["models/hammy/pci_slot.mdl"] = Angle(90,0,0), + ["models/hammy/pci_card.mdl"] = Angle(90,0,0), + ["models/wingf0x/isasocket.mdl"] = Angle(90,0,0), + ["models/wingf0x/isaplug.mdl"] = Angle(90,0,0), + ["models/wingf0x/altisasocket.mdl"] = Angle(90,00,0), + ["models/wingf0x/ethernetsocket.mdl"] = Angle(90,0,0), + ["models/wingf0x/ethernetplug.mdl"] = Angle(90,0,0), + ["models/wingf0x/hdmisocket.mdl"] = Angle(90,0,0), + ["models/wingf0x/hdmiplug.mdl"] = Angle(90,0,0) +} + +cleanup.Register( "wire_plugs" ) + +function TOOL:GetModel() + local model = self:GetClientInfo( "model" ) + if (!util.IsValidModel( model ) or !util.IsValidProp( model ) or !SocketModels[ model ]) then return "models/props_lab/tpplugholder_single.mdl" end + return model +end + +function TOOL:GetAngle( trace ) + local ang + if math.abs(trace.HitNormal.x) < 0.001 and math.abs(trace.HitNormal.y) < 0.001 then + ang = Vector(0,0,trace.HitNormal.z):Angle() + (AngleOffset[self:GetModel()] or Angle(0,0,0)) + else + ang = trace.HitNormal:Angle() + (AngleOffset[self:GetModel()] or Angle(0,0,0)) + end + ang:RotateAroundAxis( trace.HitNormal, self:GetClientNumber( "angleoffset" ) ) + return ang +end + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("array") ~= 0, self:GetClientNumber("weldforce"), math.Clamp(self:GetClientNumber("attachrange"), 1, 100) + end + + -- Socket creation handled by WireToolObj +end + +-- Create Plug +function TOOL:RightClick( trace ) + if (!trace) then return false end + if (trace.Entity) then + if (trace.Entity:IsPlayer()) then return false end + if (trace.Entity:GetClass() == "gmod_wire_plug") then + if (CLIENT) then return true end + trace.Entity:Setup( self:GetClientNumber( "array" ) != 0 ) + return true + end + end + if (CLIENT) then return true end + if not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + local ply = self:GetOwner() + local plugmodel = SocketModels[self:GetModel()] + + local plug = WireLib.MakeWireEnt(ply, {Class = "gmod_wire_plug", Pos=trace.HitPos, Angle=self:GetAngle(trace), Model=plugmodel}, self:GetClientNumber( "array" ) != 0) + if not IsValid(plug) then return false end + + plug:SetPos( trace.HitPos - trace.HitNormal * plug:OBBMins().x ) + + undo.Create("wireplug") + undo.AddEntity( plug ) + undo.SetPlayer( ply ) + undo.Finish() + + ply:AddCleanup( "wire_plugs", plug ) + + return true +end + +-------------------- +-- Reload +-- Increase angle offset by 45 degrees +-------------------- +function TOOL:Reload( trace ) + if game.SinglePlayer() and SERVER then + self:GetOwner():ConCommand( "wire_plug_angleoffset " .. (self:GetClientNumber( "angleoffset" ) + 45) % 360 ) + elseif CLIENT then + RunConsoleCommand( "wire_plug_angleoffset", (self:GetClientNumber( "angleoffset" ) + 45) % 360 ) + end + + return false +end + +function TOOL.BuildCPanel( panel ) + WireToolHelpers.MakePresetControl(panel, "wire_plug") + ModelPlug_AddToCPanel(panel, "Socket", "wire_plug") + panel:CheckBox("#Tool_wire_plug_array", "wire_plug_array") + panel:NumSlider("#Tool_wire_plug_weldforce", "wire_plug_weldforce", 0, 100000) + panel:NumSlider("#Tool_wire_plug_attachrange", "wire_plug_attachrange", 1, 100) + panel:CheckBox("#Tool_wire_plug_drawoutline", "wire_plug_drawoutline") + panel:NumSlider( "#Tool_wire_plug_angleoffset","wire_plug_angleoffset", 0, 360, 0 ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/pod.lua b/garrysmod/addons/feature-wire/lua/wire/stools/pod.lua new file mode 100644 index 0000000..ca39dcf --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/pod.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Vehicle Control" ) +WireToolSetup.open( "pod", "Pod Controller", "gmod_wire_pod", nil, "Pod Controllers" ) + +if CLIENT then + language.Add("tool.wire_pod.name", "Pod Controller Tool (Wire)") + language.Add("tool.wire_pod.desc", "Spawn/link a Wire Pod controller.") +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.NoLeftOnClass = true +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl" +} + +WireToolSetup.SetupLinking(true, "pod") + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_pod", nil, 1) + panel:Help("Formerly known as 'Advanced Pod Controller'") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/radio.lua b/garrysmod/addons/feature-wire/lua/wire/stools/radio.lua new file mode 100644 index 0000000..ed38114 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/radio.lua @@ -0,0 +1,36 @@ +WireToolSetup.setCategory( "Input, Output/Data Transfer" ) +WireToolSetup.open( "radio", "Radio", "gmod_wire_radio", nil, "Radios" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_radio.name", "Radio Tool (Wire)" ) + language.Add( "Tool.wire_radio.desc", "Spawns a radio for use with the wire system." ) + language.Add( "WireRadioTool_channel", "Channel:" ) + language.Add( "WireRadioTool_values", "Values:" ) + language.Add( "WireRadioTool_secure", "Secure" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + ModelPlug_Register("radio") + function TOOL:GetConVars() + return self:GetClientInfo("channel"), self:GetClientNumber("values"), self:GetClientNumber("secure") ~= 0 + end +end + +TOOL.ClientConVar = { + channel = 1, + values = 4, + secure = 0, + model = "models/props_lab/binderblue.mdl" +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_radio") + WireDermaExts.ModelSelect(panel, "wire_radio_model", list.Get( "Wire_radio_Models" ), 2, true) + + panel:NumSlider("#WireRadioTool_channel","wire_radio_channel",1,30,0) + panel:NumSlider("#WireRadioTool_values","wire_radio_values",1,20,0) + panel:CheckBox("#WireRadioTool_secure","wire_radio_secure") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/ranger.lua b/garrysmod/addons/feature-wire/lua/wire/stools/ranger.lua new file mode 100644 index 0000000..2a37102 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/ranger.lua @@ -0,0 +1,75 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "ranger", "Ranger", "gmod_wire_ranger", nil, "Rangers" ) + +if CLIENT then + language.Add( "Tool.wire_ranger.name", "Ranger Tool (Wire)" ) + language.Add( "Tool.wire_ranger.desc", "Spawns a ranger for use with the wire system." ) + language.Add( "Tool.wire_ranger.range", "Range:" ) + language.Add( "Tool.wire_ranger.default_zero", "Default to zero" ) + language.Add( "Tool.wire_ranger.show_beam", "Show Beam" ) + language.Add( "Tool.wire_ranger.ignore_world", "Ignore world" ) + language.Add( "Tool.wire_ranger.trace_water", "Hit water" ) + language.Add( "Tool.wire_ranger.out_dist", "Output Distance" ) + language.Add( "Tool.wire_ranger.out_pos", "Output Position" ) + language.Add( "Tool.wire_ranger.out_vel", "Output Velocity" ) + language.Add( "Tool.wire_ranger.out_ang", "Output Angle" ) + language.Add( "Tool.wire_ranger.out_col", "Output Color" ) + language.Add( "Tool.wire_ranger.out_val", "Output Value" ) + language.Add( "Tool.wire_ranger.out_sid", "Output SteamID(number)" ) + language.Add( "Tool.wire_ranger.out_uid", "Output UniqueID" ) + language.Add( "Tool.wire_ranger.out_eid", "Output Entity+EntID" ) + language.Add( "Tool.wire_ranger.out_hnrm", "Output HitNormal" ) + language.Add( "Tool.wire_ranger.hires", "High Resolution") + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("range"), self:GetClientNumber("default_zero")~=0, self:GetClientNumber("show_beam")~=0, self:GetClientNumber("ignore_world")~=0, + self:GetClientNumber("trace_water")~=0, self:GetClientNumber("out_dist")~=0, self:GetClientNumber("out_pos")~=0, self:GetClientNumber("out_vel")~=0, + self:GetClientNumber("out_ang")~=0, self:GetClientNumber("out_col")~=0, self:GetClientNumber("out_val")~=0, self:GetClientNumber("out_sid")~=0, + self:GetClientNumber("out_uid")~=0, self:GetClientNumber("out_eid")~=0, self:GetClientNumber("out_hnrm")~=0, self:GetClientNumber("hires")~=0 + end +end + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_range.mdl", + range = 1500, + default_zero = 1, + show_beam = 1, + ignore_world = 0, + trace_water = 0, + out_dist = 1, + out_pos = 0, + out_vel = 0, + out_ang = 0, + out_col = 0, + out_val = 0, + out_sid = 0, + out_uid = 0, + out_eid = 0, + out_hnrm = 0, + hires = 0, +} + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Laser_Tools", "wire_ranger") + panel:NumSlider("#Tool.wire_ranger.range", "wire_ranger_range", 1, 1000, 2 ) + panel:CheckBox("#Tool.wire_ranger.default_zero","wire_ranger_default_zero") + panel:CheckBox("#Tool.wire_ranger.show_beam","wire_ranger_show_beam") + panel:CheckBox("#Tool.wire_ranger.ignore_world","wire_ranger_ignore_world") + panel:CheckBox("#Tool.wire_ranger.trace_water","wire_ranger_trace_water") + panel:CheckBox("#Tool.wire_ranger.out_dist","wire_ranger_out_dist") + panel:CheckBox("#Tool.wire_ranger.out_pos","wire_ranger_out_pos") + panel:CheckBox("#Tool.wire_ranger.out_vel","wire_ranger_out_vel") + panel:CheckBox("#Tool.wire_ranger.out_ang","wire_ranger_out_ang") + panel:CheckBox("#Tool.wire_ranger.out_col","wire_ranger_out_col") + panel:CheckBox("#Tool.wire_ranger.out_val","wire_ranger_out_val") + panel:CheckBox("#Tool.wire_ranger.out_sid","wire_ranger_out_sid") + panel:CheckBox("#Tool.wire_ranger.out_uid","wire_ranger_out_uid") + panel:CheckBox("#Tool.wire_ranger.out_eid","wire_ranger_out_eid") + panel:CheckBox("#Tool.wire_ranger.out_hnrm","wire_ranger_out_hnrm") + panel:CheckBox("#Tool.wire_ranger.hires","wire_ranger_hires") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/relay.lua b/garrysmod/addons/feature-wire/lua/wire/stools/relay.lua new file mode 100644 index 0000000..c50436e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/relay.lua @@ -0,0 +1,105 @@ +WireToolSetup.setCategory( "Input, Output/Data Transfer" ) +WireToolSetup.open( "relay", "Relay", "gmod_wire_relay", nil, "Relays" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_relay.name", "Relay" ) + language.Add( "Tool.wire_relay.desc", "Spawns a multi pole, multi throw relay switch." ) + language.Add( "WireRelayTool_keygroup1", "Input 1 Key:" ) + language.Add( "WireRelayTool_keygroup2", "Input 2 Key:" ) + language.Add( "WireRelayTool_keygroup3", "Input 3 Key:" ) + language.Add( "WireRelayTool_keygroup4", "Input 4 Key:" ) + language.Add( "WireRelayTool_keygroup5", "Input 5 Key:" ) + language.Add( "WireRelayTool_keygroupoff", "Open (off) Key:" ) + language.Add( "WireRelayTool_nokey", "No Key switching" ) + language.Add( "WireRelayTool_toggle", "Toggle" ) + language.Add( "WireRelayTool_normclose", "Normaly:" ) + language.Add( "WireRelayTool_poles", "Number of poles:" ) + language.Add( "WireRelayTool_throws", "Number of throws:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + keygroupoff = "0", + keygroup1 = "1", + keygroup2 = "2", + keygroup3 = "3", + keygroup4 = "4", + keygroup5 = "5", + nokey = "0", + toggle = "1", + normclose = "0", + poles = "1", + throws = "2", + model = "models/kobilica/relay.mdl", +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("keygroup1"), self:GetClientNumber("keygroup2"), self:GetClientNumber("keygroup3"), self:GetClientNumber("keygroup4"), self:GetClientNumber("keygroup5"), + self:GetClientNumber("keygroupoff"), self:GetClientNumber("toggle") ~= 0, self:GetClientNumber("normclose"), + self:GetClientNumber("poles"), self:GetClientNumber("throws"), self:GetClientNumber("nokey") ~= 0 + end +end + +function TOOL.BuildCPanel(panel) + panel:AddControl("Header", { Text = "#Tool.wire_relay.name", Description = "#Tool.wire_relay.desc" }) + WireToolHelpers.MakePresetControl(panel, "wire_radio") + + panel:AddControl("Slider", { + Label = "#WireRelayTool_poles", + Type = "Integer", + Min = "1", + Max = "8", + Command = "wire_relay_poles" + }) + + panel:AddControl("Slider", { + Label = "#WireRelayTool_throws", + Type = "Integer", + Min = "1", + Max = "10", + Command = "wire_relay_throws" + }) + + + panel:AddControl("CheckBox", { + Label = "#WireRelayTool_toggle", + Command = "wire_relay_toggle" + }) + + panel:AddControl("ComboBox", { + Label = "#WireRelayTool_normclose", + Options = { + ["Open"] = { wire_relay_normclose = "0" }, + ["Closed to 1"] = { wire_relay_normclose = "1" }, + ["Closed to 2"] = { wire_relay_normclose = "2" }, + ["Closed to 3"] = { wire_relay_normclose = "3" }, + ["Closed to 4"] = { wire_relay_normclose = "4" }, + ["Closed to 5"] = { wire_relay_normclose = "5" } + } + }) + + panel:AddControl("CheckBox", { + Label = "#WireRelayTool_nokey", + Command = "wire_relay_nokey" + }) + + panel:AddControl("Numpad", { + Label = "#WireRelayTool_keygroupoff", Label2 = "#WireRelayTool_keygroup1", + Command = "wire_relay_keygroupoff", Command2 = "wire_relay_keygroup1", + ButtonSize = "22" + }) + panel:AddControl("Numpad", { + Label = "#WireRelayTool_keygroup2", Label2 = "#WireRelayTool_keygroup3", + Command = "wire_relay_keygroup2", Command2 = "wire_relay_keygroup3", + ButtonSize = "22" + }) + panel:AddControl("Numpad", { + Label = "#WireRelayTool_keygroup4", Label2 = "#WireRelayTool_keygroup5", + Command = "wire_relay_keygroup4", Command2 = "wire_relay_keygroup5", + ButtonSize = "22" + }) + +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/rom.lua b/garrysmod/addons/feature-wire/lua/wire/stools/rom.lua new file mode 100644 index 0000000..0dd4dbc --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/rom.lua @@ -0,0 +1,33 @@ +WireToolSetup.setCategory( "Advanced" ) +WireToolSetup.open( "rom", "Memory - ROM", "gmod_wire_dhdd", nil, "Memory ROMs" ) + +if CLIENT then + language.Add( "Tool.wire_rom.name", "ROM Tool (Wire)" ) + language.Add( "Tool.wire_rom.desc", "Spawns a ROM chip" ) + + language.Add( "Tool.wire_rom.note", "ROM size will depend on written data.\nThe maximum size is 256 KB." ) + + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + TOOL.ClientConVar["model"] = "models/jaanus/wiretool/wiretool_gate.mdl" + + function TOOL.BuildCPanel( panel ) + ModelPlug_AddToCPanel(panel, "gate", "wire_rom", nil, 4) + + panel:Help("#Tool.wire_rom.note") + end + + WireToolSetup.setToolMenuIcon( "icon16/database.png" ) +end +TOOL.MaxLimitName = "wire_dhdds" + +if SERVER then + function TOOL:MakeEnt( ply, model, Ang, trace ) + local rom = WireLib.MakeWireEnt(ply, {Class = "gmod_wire_dhdd", Pos=trace.HitPos, Angle=Ang, Model=model}, self:GetConVars()) + if IsValid(rom) then + rom.ROM = true + rom:SetOverlayText("ROM") + end + return rom + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/screen.lua b/garrysmod/addons/feature-wire/lua/wire/stools/screen.lua new file mode 100644 index 0000000..cea6f74 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/screen.lua @@ -0,0 +1,64 @@ +WireToolSetup.setCategory( "Visuals/Screens" ) +WireToolSetup.open( "screen", "Screen", "gmod_wire_screen", nil, "Screens" ) + +if CLIENT then + language.Add( "tool.wire_screen.name", "Screen Tool (Wire)" ) + language.Add( "tool.wire_screen.desc", "Spawns a screen that display values." ) + language.Add("Tool_wire_screen_singlevalue", "Only one value") + language.Add("Tool_wire_screen_singlebigfont", "Use bigger font for single-value screen") + language.Add("Tool_wire_screen_texta", "Text A:") + language.Add("Tool_wire_screen_textb", "Text B:") + language.Add("Tool_wire_screen_leftalign", "Left alignment") + language.Add("Tool_wire_screen_floor", "Floor screen value") + language.Add("Tool_wire_screen_formatnumber", "Format the number into millions, billions, etc") + language.Add("Tool_wire_screen_formattime", "Format the number as a duration, in seconds") + language.Add("Tool_wire_screen_createflat", "Create flat to surface") + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("pixel") + + function TOOL:GetDataTables() + return { + SingleValue = self:GetClientNumber("singlevalue") == 1, + SingleBigFont = self:GetClientNumber("singlebigfont") == 1, + TextA = self:GetClientInfo("texta"), + TextB = self:GetClientInfo("textb"), + LeftAlign = self:GetClientNumber("leftalign") == 1, + Floor = self:GetClientNumber("floor") == 1, + FormatNumber = self:GetClientNumber("formatnumber") == 1, + FormatTime = self:GetClientNumber("formattime") == 1 + } + end +end + +TOOL.ClientConVar = { + model = "models/props_lab/monitor01b.mdl", + singlevalue = 0, + singlebigfont = 1, + texta = "Value A", + textb = "Value B", + createflat = 1, + leftalign = 0, + floor = 0, + formatnumber = 0, + formattime = 0, +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_screen") + WireDermaExts.ModelSelect(panel, "wire_screen_model", list.Get( "WireScreenModels" ), 5) -- screen with GPULib setup + panel:CheckBox("#Tool_wire_screen_singlevalue", "wire_screen_singlevalue") + panel:CheckBox("#Tool_wire_screen_singlebigfont", "wire_screen_singlebigfont") + panel:CheckBox("#Tool_wire_screen_leftalign", "wire_screen_leftalign") + panel:CheckBox("#Tool_wire_screen_floor", "wire_screen_floor") + panel:CheckBox("#Tool_wire_screen_formatnumber", "wire_screen_formatnumber") + local p = panel:CheckBox("#Tool_wire_screen_formattime", "wire_screen_formattime") + p:SetToolTip( "This overrides the two above settings" ) + panel:TextEntry("#Tool_wire_screen_texta", "wire_screen_texta") + panel:TextEntry("#Tool_wire_screen_textb", "wire_screen_textb") + panel:CheckBox("#Tool_wire_screen_createflat", "wire_screen_createflat") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/sensor.lua b/garrysmod/addons/feature-wire/lua/wire/stools/sensor.lua new file mode 100644 index 0000000..4c012c0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/sensor.lua @@ -0,0 +1,51 @@ +WireToolSetup.setCategory( "Detection/Beacon" ) +WireToolSetup.open( "sensor", "Beacon Sensor", "gmod_wire_sensor", nil, "Beacon Sensors" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_sensor.name", "Beacon Sensor Tool (Wire)" ) + language.Add( "Tool.wire_sensor.desc", "Returns distance and/or bearing to a beacon" ) + language.Add( "WireSensorTool_outdist", "Output distance" ) + language.Add( "WireSensorTool_outbrng", "Output bearing" ) + language.Add( "WireSensorTool_xyz_mode", "Output local position, relative to beacon" ) + language.Add( "WireSensorTool_gpscord", "Output world position ('split XYZ')" ) + language.Add( "WireSensorTool_direction_vector", "Output direction Vector" ) + language.Add( "WireSensorTool_direction_normalized", "Normalize direction Vector" ) + language.Add( "WireSensorTool_target_velocity", "Output target's velocity" ) + language.Add( "WireSensorTool_velocity_normalized", "Normalize velocity" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("xyz_mode") ~= 0, self:GetClientNumber("outdist") ~= 0, self:GetClientNumber("outbrng") ~= 0, + self:GetClientNumber("gpscord") ~= 0, self:GetClientNumber("direction_vector") ~= 0, self:GetClientNumber("direction_normalized") ~= 0, + self:GetClientNumber("target_velocity") ~= 0, self:GetClientNumber("velocity_normalized") ~= 0 + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar[ "xyz_mode" ] = "0" +TOOL.ClientConVar[ "outdist" ] = "1" +TOOL.ClientConVar[ "outbrng" ] = "0" +TOOL.ClientConVar[ "gpscord" ] = "0" +TOOL.ClientConVar[ "direction_vector" ] = "0" +TOOL.ClientConVar[ "direction_normalized" ] = "0" +TOOL.ClientConVar[ "target_velocity" ] = "0" +TOOL.ClientConVar[ "velocity_normalized" ] = "0" + +TOOL.Model = "models/props_lab/huladoll.mdl" + +WireToolSetup.SetupLinking(true, "beacon") + +function TOOL.BuildCPanel( panel ) + panel:CheckBox("#WireSensorTool_outdist", "wire_sensor_outdist") + panel:CheckBox("#WireSensorTool_outbrng", "wire_sensor_outbrng") + panel:CheckBox("#WireSensorTool_xyz_mode", "wire_sensor_xyz_mode") + panel:CheckBox("#WireSensorTool_gpscord", "wire_sensor_gpscord") + panel:CheckBox("#WireSensorTool_direction_vector", "wire_sensor_direction_vector") + panel:CheckBox("#WireSensorTool_direction_normalized", "wire_sensor_direction_normalized") + panel:CheckBox("#WireSensorTool_target_velocity", "wire_sensor_target_velocity") + panel:CheckBox("#WireSensorTool_velocity_normalized", "wire_sensor_velocity_normalized") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/simple_explosive.lua b/garrysmod/addons/feature-wire/lua/wire/stools/simple_explosive.lua new file mode 100644 index 0000000..49f9edd --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/simple_explosive.lua @@ -0,0 +1,44 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "simple_explosive", "Explosives (Simple)", "gmod_wire_simple_explosive", nil, "Simple Explosives" ) + +if CLIENT then + language.Add( "tool.wire_simple_explosive.name", "Simple Wired Explosives Tool" ) + language.Add( "tool.wire_simple_explosive.desc", "Creates a simple explosives for wire system." ) + language.Add( "Tool.simple_explosive.model", "Model:" ) + language.Add( "Tool.simple_explosive.trigger", "Trigger value:" ) + language.Add( "Tool.simple_explosive.damage", "Damage:" ) + language.Add( "Tool.simple_explosive.removeafter", "Remove on explosion" ) + language.Add( "Tool.simple_explosive.radius", "Blast radius:" ) + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "reload", text = "Copy model" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "trigger" ), self:GetClientNumber( "damage" ), self:GetClientNumber( "removeafter" )==1, + self:GetClientNumber( "radius" ) + end +end + +TOOL.ClientConVar = { + model = "models/props_c17/oildrum001_explosive.mdl", + modelman = "", + trigger = 1, -- Wire input value to cause the explosion + damage = 200, -- Damage to inflict + radius = 300, + removeafter = 0, +} +TOOL.ReloadSetsModel = true + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Explosive", "wire_simple_explosive") + panel:Help("This tool is deprecated as its functionality is contained within Wire Explosive, and will be removed soon.") + panel:NumSlider("#Tool.simple_explosive.trigger", "wire_simple_explosive_trigger", -10, 10, 0 ) + panel:NumSlider("#Tool.simple_explosive.damage", "wire_simple_explosive_damage", 0, 500, 0 ) + panel:NumSlider("#Tool.simple_explosive.radius", "wire_simple_explosive_radius", 1, 1500, 0 ) + panel:CheckBox("#Tool.simple_explosive.removeafter","wire_simple_explosive_removeafter") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/soundemitter.lua b/garrysmod/addons/feature-wire/lua/wire/stools/soundemitter.lua new file mode 100644 index 0000000..91bb5d9 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/soundemitter.lua @@ -0,0 +1,98 @@ +WireToolSetup.setCategory( "Other/Sound" ) +WireToolSetup.open( "soundemitter", "Sound Emitter", "gmod_wire_soundemitter", nil, "Sound Emitters" ) + +if CLIENT then + language.Add( "tool.wire_soundemitter.name", "Sound Emitter Tool (Wire)" ) + language.Add( "tool.wire_soundemitter.desc", "Spawns a sound emitter for use with the wire system." ) + language.Add( "WireEmitterTool_sound", "Sound:" ) + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Open Sound Browser" }, + } + + WireToolSetup.setToolMenuIcon( "bull/various/subwoofer" ) +end +WireToolSetup.BaseLang() + +WireToolSetup.SetupMax( 10 ) + +if SERVER then + ModelPlug_Register("speaker") + + function TOOL:GetConVars() + return self:GetClientInfo( "sound" ) + end +end + +TOOL.ClientConVar = { + model = "models/cheeze/wires/speaker.mdl", + sound = "synth/square.wav", +} + +function TOOL:RightClick( trace ) + if SERVER and !game.SinglePlayer() then return false end + RunConsoleCommand("wire_sound_browser_open", self:GetClientInfo("sound"), "1") + + return false +end + +function TOOL.BuildCPanel(panel) + + local wide = panel:GetWide() + + local SoundNameText = vgui.Create("DTextEntry", ValuePanel) + SoundNameText:SetText("") + SoundNameText:SetWide(wide) + SoundNameText:SetTall(20) + SoundNameText:SetMultiline(false) + SoundNameText:SetConVar("wire_soundemitter_sound") + SoundNameText:SetVisible(true) + panel:AddItem(SoundNameText) + + local SoundBrowserButton = vgui.Create("DButton") + SoundBrowserButton:SetText("Open Sound Browser") + SoundBrowserButton:SetWide(wide) + SoundBrowserButton:SetTall(20) + SoundBrowserButton:SetVisible(true) + SoundBrowserButton.DoClick = function() + RunConsoleCommand("wire_sound_browser_open", SoundNameText:GetValue(), "1") + end + panel:AddItem(SoundBrowserButton) + + local SoundPre = vgui.Create("DPanel") + SoundPre:SetWide(wide) + SoundPre:SetTall(20) + SoundPre:SetVisible(true) + + local SoundPreWide = SoundPre:GetWide() + + local SoundPrePlay = vgui.Create("DButton", SoundPre) + SoundPrePlay:SetText("Play") + SoundPrePlay:SetWide(SoundPreWide / 2) + SoundPrePlay:SetPos(0, 0) + SoundPrePlay:SetTall(20) + SoundPrePlay:SetVisible(true) + SoundPrePlay.DoClick = function() + RunConsoleCommand("play",SoundNameText:GetValue()) + end + + local SoundPreStop = vgui.Create("DButton", SoundPre) + SoundPreStop:SetText("Stop") + SoundPreStop:SetWide(SoundPreWide / 2) + SoundPreStop:SetPos(SoundPreWide / 2, 0) + SoundPreStop:SetTall(20) + SoundPreStop:SetVisible(true) + SoundPreStop.DoClick = function() + RunConsoleCommand("play", "common/NULL.WAV") //Playing a silent sound will mute the preview but not the sound emitters. + end + panel:AddItem(SoundPre) + SoundPre:InvalidateLayout(true) + SoundPre.PerformLayout = function() + local SoundPreWide = SoundPre:GetWide() + SoundPrePlay:SetWide(SoundPreWide / 2) + SoundPreStop:SetWide(SoundPreWide / 2) + SoundPreStop:SetPos(SoundPreWide / 2, 0) + end + + ModelPlug_AddToCPanel(panel, "speaker", "wire_soundemitter", true) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/spawner.lua b/garrysmod/addons/feature-wire/lua/wire/stools/spawner.lua new file mode 100644 index 0000000..55b2412 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/spawner.lua @@ -0,0 +1,78 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "spawner", "Prop Spawner", "gmod_wire_spawner", nil, "Prop Spawners" ) + +TOOL.ClientConVar = { + delay = 0, + undo_delay = 0, + spawn_effect = 0, +} + +if CLIENT then + language.Add( "Tool.wire_spawner.name", "Prop Spawner (Wire)" ) + language.Add( "Tool.wire_spawner.desc", "Spawns a prop at a pre-defined location" ) + TOOL.Information = { { name = "left", text = "Click a prop to turn it into a " .. TOOL.Name } } +end + +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(10) + +function TOOL:LeftClick(trace) + local ent = trace.Entity + if !ent or !ent:IsValid() then return false end + if ent:GetClass() != "prop_physics" && ent:GetClass() != "gmod_wire_spawner" then return false end + if CLIENT then return true end + + local pl = self:GetOwner() + local delay = self:GetClientNumber("delay", 0) + local undo_delay = self:GetClientNumber("undo_delay", 0) + local spawn_effect = self:GetClientNumber("spawn_effect", 0) + // In multiplayer we clamp the delay to help prevent people being idiots + if !game.SinglePlayer() and delay < 0.1 then + delay = 0.1 + end + if ent:GetClass() == "gmod_wire_spawner" then + ent:Setup(delay, undo_delay, spawn_effect) + return true + end + + if !self:GetSWEP():CheckLimit("wire_spawners") then return false end + + local phys = ent:GetPhysicsObject() + if !phys:IsValid() then return false end + + local model = ent:GetModel() + local frozen = not phys:IsMoveable() + local Pos = ent:GetPos() + local Ang = ent:GetAngles() + local mat = ent:GetMaterial() + local c = ent:GetColor() + local skin = ent:GetSkin() or 0 + + local preserveMotion = phys:IsMotionEnabled() + + local wire_spawner = WireLib.MakeWireEnt(pl, {Class = self.WireClass, Pos=Pos, Angle=Ang, Model=model}, delay, undo_delay, spawn_effect, mat, c.r, c.g, c.b, c.a, skin) + if !wire_spawner:IsValid() then return end + + local physObj = wire_spawner:GetPhysicsObject() + if IsValid( physObj ) then + physObj:EnableMotion( preserveMotion ) + end + + ent:Remove() + + undo.Create("gmod_wire_spawner") + undo.AddEntity( wire_spawner ) + undo.SetPlayer( pl ) + undo.Finish() + + return true +end + +function TOOL:Think() end -- Disable ghost + +function TOOL.BuildCPanel( panel ) + WireToolHelpers.MakePresetControl(panel, "wire_spawner") + panel:NumSlider("#Spawn Delay", "wire_spawner_delay", 0.1, 100, 2) + panel:NumSlider("#Automatic Undo Delay", "wire_spawner_undo_delay", 0.1, 100, 2) + panel:CheckBox("#Prop spawn effect", "wire_spawner_spawn_effect") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/speedometer.lua b/garrysmod/addons/feature-wire/lua/wire/stools/speedometer.lua new file mode 100644 index 0000000..8d3bb76 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/speedometer.lua @@ -0,0 +1,29 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "speedometer", "Speedometer", "gmod_wire_speedometer", nil, "Speedometers" ) + +if CLIENT then + language.Add( "tool.wire_speedometer.name", "Speedometer Tool (Wire)" ) + language.Add( "tool.wire_speedometer.desc", "Spawns a speedometer for use with the wire system." ) + language.Add( "Tool_wire_speedometer_xyz_mode", "Split Outputs to X,Y,Z" ) + language.Add( "Tool_wire_speedometer_angvel", "Add Angular Velocity Outputs" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +if SERVER then + function TOOL:GetConVars() + return tobool(self:GetClientNumber("xyz_mode")), tobool(self:GetClientNumber("angvel")) + end +end + +TOOL.Model = "models/jaanus/wiretool/wiretool_speed.mdl" +TOOL.ClientConVar = { + xyz_mode = 0, + angvel = 0 +} + +function TOOL.BuildCPanel(panel) + panel:CheckBox("#Tool_wire_speedometer_xyz_mode", "wire_speedometer_xyz_mode") + panel:CheckBox("#Tool_wire_speedometer_angvel", "wire_speedometer_AngVel") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/spu.lua b/garrysmod/addons/feature-wire/lua/wire/stools/spu.lua new file mode 100644 index 0000000..6fc663c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/spu.lua @@ -0,0 +1,168 @@ +WireToolSetup.setCategory( "Chips, Gates", "Other/Sound", "Advanced" ) +WireToolSetup.open( "spu", "SPU", "gmod_wire_spu", nil, "SPUs" ) + +if CLIENT then + language.Add("Tool.wire_spu.name", "SPU Tool (Wire)") + language.Add("Tool.wire_spu.desc", "Spawns a sound processing unit") + language.Add("ToolWirespu_Model", "Model:" ) + TOOL.Information = { + { name = "left", text = "Create/reflash " .. TOOL.Name }, + { name = "right", text = "Open editor" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 7 ) + +TOOL.ClientConVar = { + model = "models/cheeze/wires/cpu.mdl", + filename = "", +} + +if CLIENT then + ------------------------------------------------------------------------------ + -- Make sure firing animation is displayed clientside + ------------------------------------------------------------------------------ + function TOOL:LeftClick() return true end + function TOOL:Reload() return true end + function TOOL:RightClick() return false end +end + + +if SERVER then + util.AddNetworkString("ZSPU_RequestCode") + util.AddNetworkString("ZSPU_OpenEditor") + ------------------------------------------------------------------------------ + -- Reload: wipe ROM/RAM and reset memory model + ------------------------------------------------------------------------------ + function TOOL:Reload(trace) + if trace.Entity:IsPlayer() then return false end + + local player = self:GetOwner() + if (trace.Entity:IsValid()) and + (trace.Entity:GetClass() == "gmod_wire_spu") then + trace.Entity:SetMemoryModel(self:GetClientInfo("memorymodel")) + return true + end + end + + -- Left click: spawn SPU or upload current program into it + function TOOL:CheckHitOwnClass(trace) + return trace.Entity:IsValid() and (trace.Entity:GetClass() == self.WireClass or trace.Entity.WriteCell) + end + function TOOL:LeftClick_Update(trace) + CPULib.SetUploadTarget(trace.Entity, self:GetOwner()) + net.Start("ZSPU_RequestCode") net.Send(self:GetOwner()) + end + function TOOL:MakeEnt(ply, model, Ang, trace) + local ent = WireLib.MakeWireEnt(ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}) + ent:SetMemoryModel(self:GetClientInfo("memorymodel")) + self:LeftClick_Update(trace) + return ent + end + + + function TOOL:RightClick(trace) + net.Start("ZSPU_OpenEditor") net.Send(self:GetOwner()) + return true + end +end + + +if CLIENT then + ------------------------------------------------------------------------------ + -- Compiler callbacks on the compiling state + ------------------------------------------------------------------------------ + local function compile_success() + CPULib.Upload() + end + + local function compile_error(errorText) + GAMEMODE:AddNotify(errorText,NOTIFY_GENERIC,7) + end + + + ------------------------------------------------------------------------------ + -- Request code to be compiled (called remotely from server) + ------------------------------------------------------------------------------ + function ZSPU_RequestCode() + if ZSPU_Editor then + CPULib.Debugger.SourceTab = ZSPU_Editor:GetActiveTab() + CPULib.Compile(ZSPU_Editor:GetCode(),ZSPU_Editor:GetChosenFile(),compile_success,compile_error) + end + end + net.Receive("ZSPU_RequestCode", ZSPU_RequestCode) + + ------------------------------------------------------------------------------ + -- Open ZSPU editor + ------------------------------------------------------------------------------ + function ZSPU_OpenEditor() + if not ZSPU_Editor then + ZSPU_Editor = vgui.Create("Expression2EditorFrame") + ZSPU_Editor:Setup("ZSPU Editor", "spuchip", "SPU") + end + ZSPU_Editor:Open() + end + net.Receive("ZSPU_OpenEditor", ZSPU_OpenEditor) + + ------------------------------------------------------------------------------ + -- Build tool control panel + ------------------------------------------------------------------------------ + function TOOL.BuildCPanel(panel) + local Button = vgui.Create("DButton" , panel) + panel:AddPanel(Button) + Button:SetText("Online ZSPU documentation") + Button.DoClick = function(button) CPULib.ShowDocumentation("ZSPU") end + + local Button = vgui.Create("DButton" , panel) + panel:AddPanel(Button) + Button:SetText("Open Sound Browser") + Button.DoClick = function() + RunConsoleCommand("wire_sound_browser_open") + end + + + ---------------------------------------------------------------------------- + local currentDirectory + local FileBrowser = vgui.Create("wire_expression2_browser" , panel) + panel:AddPanel(FileBrowser) + FileBrowser:Setup("spuchip") + FileBrowser:SetSize(235,400) + function FileBrowser:OnFileOpen(filepath, newtab) + if not ZSPU_Editor then + ZSPU_Editor = vgui.Create("Expression2EditorFrame") + ZSPU_Editor:Setup("ZSPU Editor", "spuchip", "SPU") + end + ZSPU_Editor:Open(filepath, nil, newtab) + end + + + ---------------------------------------------------------------------------- + local New = vgui.Create("DButton" , panel) + panel:AddPanel(New) + New:SetText("New file") + New.DoClick = function(button) + ZSPU_OpenEditor() + ZSPU_Editor:AutoSave() + ZSPU_Editor:NewScript(false) + end + panel:AddControl("Label", {Text = ""}) + + ---------------------------------------------------------------------------- + local OpenEditor = vgui.Create("DButton", panel) + panel:AddPanel(OpenEditor) + OpenEditor:SetText("Open Editor") + OpenEditor.DoClick = ZSPU_OpenEditor + + + ---------------------------------------------------------------------------- + WireDermaExts.ModelSelect(panel, "wire_spu_model", list.Get("Wire_gate_Models"), 2) + panel:AddControl("Label", {Text = ""}) + end + + ------------------------------------------------------------------------------ + -- Tool screen + ------------------------------------------------------------------------------ + function TOOL:DrawToolScreen(width, height) + CPULib.RenderCPUTool(1,"ZSPU") + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/target_finder.lua b/garrysmod/addons/feature-wire/lua/wire/stools/target_finder.lua new file mode 100644 index 0000000..d071ece --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/target_finder.lua @@ -0,0 +1,137 @@ +WireToolSetup.setCategory( "Detection/Beacon" ) +WireToolSetup.open( "target_finder", "Target Finder", "gmod_wire_target_finder", nil, "Target Finders" ) + +if CLIENT then + language.Add( "Tool.wire_target_finder.name", "Target Finder Beacon Tool (Wire)" ) + language.Add( "Tool.wire_target_finder.desc", "Spawns a target finder beacon for use with the wire system." ) + + language.Add( "WireTargetFinderTool_minrange", "Minimum Range:" ) + language.Add( "WireTargetFinderTool_maxrange", "Maximum Range:" ) + language.Add( "WireTargetFinderTool_maxtargets", "Maximum number of targets to track:" ) + language.Add( "WireTargetFinderTool_MaxBogeys", "Max number of bogeys (closest):" ) + language.Add( "WireTargetFinderTool_MaxBogeys_desc", "Set to 0 for all within range, this needs to be atleast as many as Max Targets." ) + language.Add( "WireTargetFinderTool_players", "Target players" ) + language.Add( "WireTargetFinderTool_notowner", "Do not target owner" ) + language.Add( "WireTargetFinderTool_notownersstuff", "Do not target owner's stuff" ) + language.Add( "WireTargetFinderTool_npcs", "Target NPCs" ) + language.Add( "WireTargetFinderTool_npcname", "NPC Filter:" ) + language.Add( "WireTargetFinderTool_beacons", "Target Locators" ) + language.Add( "WireTargetFinderTool_hoverballs", "Target Hoverballs" ) + language.Add( "WireTargetFinderTool_thrusters", "Target Thrusters" ) + language.Add( "WireTargetFinderTool_props", "Target Props" ) + language.Add( "WireTargetFinderTool_propmodel", "Prop Model Filter:" ) + language.Add( "WireTargetFinderTool_vehicles", "Target Vehicles" ) + language.Add( "WireTargetFinderTool_rpgs", "Target RPGs" ) + language.Add( "WireTargetFinderTool_PaintTarget", "Paint Target" ) + language.Add( "WireTargetFinderTool_PaintTarget_desc", "Paints currently selected target(s)." ) + language.Add( "WireTargetFinderTool_casesen", "Case Sensitive" ) + language.Add( "WireTargetFinderTool_playername", "Name Filter:" ) + language.Add( "WireTargetFinderTool_entity", "Entity Name:" ) + language.Add( "WireTargetFinderTool_steamname", "SteamID Filter:" ) + language.Add( "WireTargetFinderTool_colorcheck", "Color Filter") + language.Add( "WireTargetFinderTool_colortarget", "Color Target/Skip") + language.Add( "WireTargetFinderTool_pcolR", "Red:") + language.Add( "WireTargetFinderTool_pcolG", "Green:") + language.Add( "WireTargetFinderTool_pcolB", "Blue:") + language.Add( "WireTargetFinderTool_pcolA", "Alpha:") + language.Add( "WireTargetFinderTool_checkbuddylist", "Check Propprotection Buddy List (EXPERIMENTAL!)" ) + language.Add( "WireTargetFinderTool_onbuddylist", "Target Only Buddys (EXPERIMENTAL!)" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("Numpad") + CreateConVar("wire_target_finders_maxtargets",10) + CreateConVar("wire_target_finders_maxbogeys",30) + function TOOL:GetConVars() + return self:GetClientNumber("maxrange"), self:GetClientNumber("players") ~= 0, self:GetClientNumber("npcs") ~= 0, self:GetClientInfo("npcname"), + self:GetClientNumber("beacons") ~= 0, self:GetClientNumber("hoverballs") ~= 0, self:GetClientNumber("thrusters") ~= 0, self:GetClientNumber("props") ~= 0, + self:GetClientInfo("propmodel"), self:GetClientNumber("vehicles") ~= 0, self:GetClientInfo("playername"), self:GetClientNumber("casesen") ~= 0, + self:GetClientNumber("rpgs") ~= 0, self:GetClientNumber("painttarget") ~= 0, self:GetClientNumber("minrange"), self:GetClientNumber("maxtargets"), + self:GetClientNumber("maxbogeys"), self:GetClientNumber("notargetowner") != 0, self:GetClientInfo("entityfil"), self:GetClientNumber("notownersstuff") != 0, + self:GetClientInfo("steamname"), (self:GetClientNumber("colorcheck") ~= 0), (self:GetClientNumber("colortarget") ~= 0), + self:GetClientNumber("pcolR"), self:GetClientNumber("pcolG"), self:GetClientNumber("pcolB"), self:GetClientNumber("pcolA"), + self:GetClientNumber("checkbuddylist") != 0, self:GetClientNumber("onbuddylist") != 0 + end +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/targetfinder.mdl", + modelsize = "", + minrange = 1, + maxrange = 1000, + players = 0, + npcs = 1, + npcname = "", + beacons = 0, + hoverballs = 0, + thrusters = 0, + props = 0, + propmodel = "", + vehicles = 0, + playername = "", + steamname = "", + colorcheck = 0, + colortarget = 0, + pcolR = 255, + pcolG = 255, + pcolB = 255, + pcolA = 255, + casesen = 0, + rpgs = 0, + painttarget = 1, + maxtargets = 1, + maxbogeys = 1, + notargetowner = 0, + notownersstuff = 0, + entityfil = "", + checkbuddylist = 0, + onbuddylist = 0, +} + +function TOOL:Reload(trace) + if trace.Entity:IsValid() then + self:GetOwner():ConCommand("wire_target_finder_entityfil "..trace.Entity:GetClass().."\n") + else + self:GetOwner():ConCommand("wire_target_finder_entityfil \n") + end + return true +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_target_finder") + WireToolHelpers.MakeModelSizer(panel, "wire_target_finder_modelsize") + ModelPlug_AddToCPanel(panel, "TargetFinder", "wire_target_finder", true, 1) + panel:NumSlider("#WireTargetFinderTool_minrange","wire_target_finder_minrange",1,10000,0) + panel:NumSlider("#WireTargetFinderTool_maxrange","wire_target_finder_maxrange",1,10000,0) + panel:NumSlider("#WireTargetFinderTool_maxtargets","wire_target_finder_maxtargets",1,10,0) + panel:NumSlider("#WireTargetFinderTool_MaxBogeys","wire_target_finder_maxbogeys",0,30,0) + panel:NumSlider("#WireTargetFinderTool_minrange","wire_target_finder_minrange",1,1000,0) + panel:CheckBox( "#WireTargetFinderTool_players","wire_target_finder_players") + panel:CheckBox( "#WireTargetFinderTool_notowner","wire_target_finder_notargetowner") + panel:CheckBox( "#WireTargetFinderTool_notownersstuff","wire_target_finder_notownersstuff") + panel:CheckBox( "#WireTargetFinderTool_npcs","wire_target_finder_npcs") + panel:TextEntry("#WireTargetFinderTool_npcname","wire_target_finder_npcname") + panel:CheckBox( "#WireTargetFinderTool_beacons","wire_target_finder_beacons") + panel:CheckBox( "#WireTargetFinderTool_hoverballs","wire_target_finder_hoverballs") + panel:CheckBox( "#WireTargetFinderTool_thrusters","wire_target_finder_thrusters") + panel:CheckBox( "#WireTargetFinderTool_props","wire_target_finder_props") + panel:TextEntry("#WireTargetFinderTool_propmodel","wire_target_finder_propmodel") + panel:CheckBox( "#WireTargetFinderTool_vehicles","wire_target_finder_vehicles") + panel:CheckBox( "#WireTargetFinderTool_rpgs","wire_target_finder_rpgs") + panel:CheckBox( "#WireTargetFinderTool_PaintTarget","wire_target_finder_painttarget") + panel:CheckBox( "#WireTargetFinderTool_casesen","wire_target_finder_casesen") + panel:TextEntry("#WireTargetFinderTool_playername","wire_target_finder_playername") + panel:TextEntry("#WireTargetFinderTool_entity","wire_target_finder_entityfil") + panel:TextEntry("#WireTargetFinderTool_steamname","wire_target_finder_steamname") + panel:CheckBox( "#WireTargetFinderTool_colorcheck","wire_target_finder_colorcheck") + panel:CheckBox( "#WireTargetFinderTool_colortarget","wire_target_finder_colortarget") + panel:NumSlider("#WireTargetFinderTool_pcolR","wire_target_finder_pcolR",0,255,0) + panel:NumSlider("#WireTargetFinderTool_pcolG","wire_target_finder_pcolG",0,255,0) + panel:NumSlider("#WireTargetFinderTool_pcolB","wire_target_finder_pcolB",0,255,0) + panel:NumSlider("#WireTargetFinderTool_pcolA","wire_target_finder_pcolA",0,255,0) + panel:CheckBox( "#WireTargetFinderTool_checkbuddylist","wire_target_finder_checkbuddylist") + panel:CheckBox( "#WireTargetFinderTool_onbuddylist","wire_target_finder_onbuddylist") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/teleporter.lua b/garrysmod/addons/feature-wire/lua/wire/stools/teleporter.lua new file mode 100644 index 0000000..dd462e7 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/teleporter.lua @@ -0,0 +1,43 @@ +WireToolSetup.setCategory( "Physics" ) +WireToolSetup.open( "teleporter", "Teleporter", "gmod_wire_teleporter", nil, "Teleporters" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_teleporter.name", "Teleporter Tool" ) + language.Add( "Tool.wire_teleporter.desc", "Spawns a Wire Teleporter" ) + language.Add( "Tool.wire_teleporter.effects", "Toggle effects" ) + language.Add( "Tool.wire_teleporter.sounds", "Toggle sounds (Also has an input)" ) + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "reload", text = "Copy model" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(3) + +TOOL.ClientConVar = { + model = "models/props_c17/utilityconducter001.mdl", + sounds = 1, + effects = 1 +} + +if (SERVER) then + function TOOL:GetConVars() + return self:GetClientNumber("sounds") ~= 0, self:GetClientNumber("effects") ~= 0 + end +else + function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_teleporter_model", list.Get( "WireTeleporterModels" ), 4) + panel:CheckBox("#Tool.wire_teleporter.effects","wire_teleporter_effects") + panel:CheckBox("#Tool.wire_teleporter.sounds","wire_teleporter_sounds") + end +end + +function TOOL:Reload( trace ) + if not IsValid(trace.Entity) then return end + if CLIENT then + RunConsoleCommand("wire_teleporter_model", trace.Entity:GetModel()) + else + self:GetOwner():ChatPrint("Teleporter model set to: " .. trace.Entity:GetModel()) + end + return true +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/textentry.lua b/garrysmod/addons/feature-wire/lua/wire/stools/textentry.lua new file mode 100644 index 0000000..6d2fc70 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/textentry.lua @@ -0,0 +1,36 @@ +-- Author: mitterdoo (with help from Divran) + +WireToolSetup.setCategory("Input, Output/Keyboard Interaction") +WireToolSetup.open("textentry","Text Entry","gmod_wire_textentry",nil,"Text Entries") +if CLIENT then + language.Add( "Tool.wire_textentry.name", "Wire Text Entry" ) + language.Add( "Tool.wire_textentry.desc", "Input strings into a prompt to be used with the wire system." ) + language.Add( "Tool.wire_textentry.disableuse", "Disable use" ) + language.Add( "Tool.wire_textentry.hold", "Hold length" ) +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/keyboard.mdl", + hold = "1", + disableuse = "1", +} + +WireToolSetup.BaseLang() +WireToolSetup.SetupMax(20) + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("hold"),self:GetClientNumber("disableuse") + end +end + +WireToolSetup.SetupLinking(true, "vehicle") + +function TOOL.BuildCPanel(panel) + panel:AddControl("Header",{Description="Input strings on a keyboard to be used with the wire system."}) + ModelPlug_AddToCPanel(panel, "Keyboard", "wire_textentry", true) + panel:NumSlider( "#Tool.wire_textentry.hold","wire_textentry_hold",0,10,1) + panel:ControlHelp("Sets how long the string output remains set. 0 for forever.") + panel:CheckBox( "#Tool.wire_textentry.disableuse", "wire_textentry_disableuse" ) + panel:ControlHelp("Pressing use on the keyboard normally brings up the prompt. This option allows you to disable that. Useful when linked to a vehicle.") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/textreceiver.lua b/garrysmod/addons/feature-wire/lua/wire/stools/textreceiver.lua new file mode 100644 index 0000000..a5aa19d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/textreceiver.lua @@ -0,0 +1,110 @@ +WireToolSetup.setCategory( "Input, Output/Keyboard Interaction" ) +WireToolSetup.open( "textreceiver", "Text Receiver", "gmod_wire_textreceiver", nil, "Text Receivers" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_textreceiver.name", "Text Receiver Tool (Wire)" ) + language.Add( "Tool.wire_textreceiver.desc", "Spawns a text receiver for use with the wire system." ) + + language.Add( "Tool_wire_textreceiver_case_insensitive", "Case insensitive" ) + language.Add( "Tool_wire_textreceiver_use_lua_patterns", "Use Lua Patterns" ) + language.Add( "Tool_wire_textreceiver_num_matches", "Number of matches to use" ) + for i=1,24 do + language.Add( "Tool_wire_textreceiver_match" .. i, "Match " .. i .. ":" ) + end + + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Copy settings" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar["case_insensitive"] = 1 +TOOL.ClientConVar["use_lua_patterns"] = 0 + +TOOL.ClientConVar["num_matches"] = 1 +TOOL.ClientConVar["match1"] = "Hello World" +for i=2,24 do + TOOL.ClientConVar["match"..i] = "" +end + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_range.mdl" + +if SERVER then + function TOOL:GetConVars() + local matches = {} + for i=1,math.Clamp(self:GetClientNumber("num_matches"),0,24) do + matches[i] = self:GetClientInfo("match"..i) + end + return self:GetClientNumber("use_lua_patterns") ~= 0, matches, self:GetClientNumber("case_insensitive") ~= 0 + end +end + +function TOOL:RightClick( trace ) + if trace.Entity and trace.Entity:IsValid() and trace.Entity:GetClass() == "gmod_wire_textreceiver" then + if CLIENT then return true end + + local UseLuaPatterns = trace.Entity.UseLuaPatterns + local Matches = trace.Entity.Matches + local CaseInsensitive = trace.Entity.CaseInsensitive + + local ply = self:GetOwner() + ply:ConCommand( "wire_textreceiver_use_lua_patterns " .. (UseLuaPatterns and 1 or 0)) + ply:ConCommand( "wire_textreceiver_case_insensitive " .. (CaseInsensitive and 1 or 0)) + for i=1,24 do + local match = Matches[i] + if match ~= nil then + ply:ConCommand( "wire_textreceiver_match" .. i .. " " .. match ) + end + end + + ply:ChatPrint( "Text receiver settings copied." ) + else + return false + end +end + +if CLIENT then + function TOOL.BuildCPanel( panel ) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_textreceiver") + panel:CheckBox("#Tool_wire_textreceiver_case_insensitive", "wire_textreceiver_case_insensitive") + panel:CheckBox("#Tool_wire_textreceiver_use_lua_patterns", "wire_textreceiver_use_lua_patterns") + local NumMatches = panel:NumSlider("#Tool_wire_textreceiver_num_matches", "wire_textreceiver_num_matches", 0, 24, 0) + + local matchlist = vgui.Create( "DPanelList" ) + + matchlist:SetTall( 300 ) + matchlist:EnableVerticalScrollbar( true ) + + local function UpdateMatchList(n) + local n = math.Clamp(math.Round(n) or GetConVarNumber( "wire_textreceiver_num_matches" ),0,24) + + matchlist:Clear() + + for i=1,n do + local pnl = vgui.Create( "DPanel" ) + + local label = vgui.Create( "DLabel", pnl ) + label:SetText( "Match " .. i .. ":" ) + label:SetPos( 2, 2 ) + label:SetDark(true) + label:SizeToContents() + + local text = vgui.Create( "DTextEntry", pnl ) + text:SetText( GetConVarString( "wire_textreceiver_match" .. i ) ) + text:SetPos( 50, 2 ) + text:SetWide( 220 ) + text:SetConVar( "wire_textreceiver_match" .. i ) + + matchlist:AddItem(pnl) + end + end + + function NumMatches:OnValueChanged( value ) + UpdateMatchList(tonumber(value)) -- what the fuck garry it's a string?! + end + + panel:AddItem( matchlist ) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/textscreen.lua b/garrysmod/addons/feature-wire/lua/wire/stools/textscreen.lua new file mode 100644 index 0000000..f7d897e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/textscreen.lua @@ -0,0 +1,136 @@ +WireToolSetup.setCategory( "Visuals/Screens" ) +--Originally by http://forums.facepunchstudios.com/greenarrow +WireToolSetup.open( "textscreen", "Text Screen", "gmod_wire_textscreen", nil, "Text Screens" ) + +if CLIENT then + language.Add("tool.wire_textscreen.name", "Text Screen Tool (Wire)" ) + language.Add("tool.wire_textscreen.desc", "Spawns a screen that displays text." ) + + language.Add("Tool_wire_textscreen_tsize", "Text size:") + language.Add("Tool_wire_textscreen_tjust", "Horizontal alignment:") + language.Add("Tool_wire_textscreen_valign", "Vertical alignment:") + language.Add("Tool_wire_textscreen_tfont", "Text font:") + language.Add("Tool_wire_textscreen_colour", "Text colour:") + language.Add("Tool_wire_textscreen_bgcolour", "Background colour:") + language.Add("Tool_wire_textscreen_createflat", "Create flat to surface") + language.Add("Tool_wire_textscreen_text", "Default text:") + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Copy settings" }, + } + +end +WireToolSetup.BaseLang() + +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("speaker") + + function TOOL:GetConVars() + return + self:GetClientInfo("text"), + (16 - tonumber(self:GetClientInfo("tsize"))), + self:GetClientNumber("tjust"), + self:GetClientNumber("valign"), + self:GetClientInfo("tfont"), + Color( + math.Clamp(self:GetClientNumber("tred"), 0, 255), + math.Clamp(self:GetClientNumber("tgreen"), 0, 255), + math.Clamp(self:GetClientNumber("tblue"), 0, 255) + ), + Color( + math.Clamp(self:GetClientNumber("tbgred"), 0, 255), + math.Clamp(self:GetClientNumber("tbggreen"), 0, 255), + math.Clamp(self:GetClientNumber("tbgblue"), 0, 255) + ) + end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/kobilica/wiremonitorbig.mdl", + tsize = 10, + tjust = 1, + valign = 0, + tfont = "Arial", + tred = 255, + tblue = 255, + tgreen = 255, + tbgred = 0, + tbgblue = 0, + tbggreen = 0, + ninputs = 3, + createflat = 1, + text = "", +} + +function TOOL:RightClick( trace ) + if not trace.HitPos then return false end + local ent = trace.Entity + if ent:IsPlayer() then return false end + if CLIENT then return true end + + local ply = self:GetOwner() + + if ent:IsValid() && ent:GetClass() == "gmod_wire_textscreen" then + ply:ConCommand('wire_textscreen_text "'..ent.text..'"') + return true + end + +end + +function TOOL.BuildCPanel(panel) + local Fonts = { + "WireGPU_ConsoleFont", + "Coolvetica", + "Arial", + "Lucida Console", + "Trebuchet", + "Courier New", + "Times New Roman", + "ChatFont", + "Marlett", + "Verdana", + "Tahoma", + "HalfLife2", + "HL2cross", + "Trebuchet MS", + "HL2MP" + } + local Options = {} + for k,v in ipairs(Fonts) do Options[v] = { wire_textscreen_tfont = v } end + + WireToolHelpers.MakePresetControl(panel, "wire_textscreen") + panel:TextEntry("#Tool_wire_textscreen_text", "wire_textscreen_text") + panel:NumSlider("#Tool_wire_textscreen_tsize", "wire_textscreen_tsize", 1, 15, 0) + panel:NumSlider("#Tool_wire_textscreen_tjust", "wire_textscreen_tjust", 0, 2, 0) + panel:NumSlider("#Tool_wire_textscreen_valign", "wire_textscreen_valign", 0, 2, 0) + panel:AddControl("ComboBox", { + Label = "#Tool_wire_textscreen_tfont", + Options = Options + }) + panel:CheckBox("#Tool_wire_textscreen_createflat", "wire_textscreen_createflat") + panel:AddControl("Color", { + Label = "#Tool_wire_textscreen_colour", + Red = "wire_textscreen_tred", + Green = "wire_textscreen_tgreen", + Blue = "wire_textscreen_tblue", + ShowAlpha = "0", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + panel:AddControl("Color", { + Label = "#Tool_wire_textscreen_bgcolour", + Red = "wire_textscreen_tbgred", + Green = "wire_textscreen_tbggreen", + Blue = "wire_textscreen_tbgblue", + ShowAlpha = "0", + ShowHSV = "1", + ShowRGB = "1", + Multiplier = "255" + }) + WireDermaExts.ModelSelect(panel, "wire_textscreen_model", list.Get( "WireScreenModels" ), 5) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/thruster.lua b/garrysmod/addons/feature-wire/lua/wire/stools/thruster.lua new file mode 100644 index 0000000..b02f9b0 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/thruster.lua @@ -0,0 +1,156 @@ +WireToolSetup.setCategory( "Physics/Force" ) +WireToolSetup.open( "thruster", "Thruster", "gmod_wire_thruster", nil, "Thrusters" ) + +if CLIENT then + language.Add( "tool.wire_thruster.name", "Thruster Tool (Wire)" ) + language.Add( "tool.wire_thruster.desc", "Spawns a thruster for use with the wire system." ) + language.Add( "WireThrusterTool_Model", "Model:" ) + language.Add( "WireThrusterTool_force", "Force multiplier:" ) + language.Add( "WireThrusterTool_force_min", "Input threshold:" ) + language.Add( "WireThrusterTool_force_min.help", "If the input force is below this amount, the thruster will not fire.") + language.Add( "WireThrusterTool_force_max", "Force maximum:" ) + language.Add( "WireThrusterTool_bidir", "Bi-directional" ) + language.Add( "WireThrusterTool_soundname", "Select sound" ) + language.Add( "WireThrusterTool_owater", "Works out of water" ) + language.Add( "WireThrusterTool_uwater", "Works under water" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar = { + force = 1500, + force_min = 0, + force_max = 10000, + model = "models/props_c17/lampShade001a.mdl", + bidir = 1, + soundname = "", + oweffect = "fire", + uweffect = "same", + owater = 1, + uwater = 1, +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "force" ), self:GetClientNumber( "force_min" ), self:GetClientNumber( "force_max" ), self:GetClientInfo( "oweffect" ), + self:GetClientInfo( "uweffect" ), self:GetClientNumber( "owater" ) ~= 0, self:GetClientNumber( "uwater" ) ~= 0, self:GetClientNumber( "bidir" ) ~= 0, + self:GetClientInfo( "soundname" ) + end +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_thruster") + + WireDermaExts.ModelSelect(panel, "wire_thruster_model", list.Get( "ThrusterModels" ), 4, true) + + local Effects = { + ["#No Effects"] = "none", + --["#Same as over water"] = "same", + ["#Flames"] = "fire", + ["#Plasma"] = "plasma", + ["#Smoke"] = "smoke", + ["#Smoke Random"] = "smoke_random", + ["#Smoke Do it Youself"] = "smoke_diy", + ["#Rings"] = "rings", + ["#Rings Growing"] = "rings_grow", + ["#Rings Shrinking"] = "rings_shrink", + ["#Bubbles"] = "bubble", + ["#Magic"] = "magic", + ["#Magic Random"] = "magic_color", + ["#Magic Do It Yourself"] = "magic_diy", + ["#Colors"] = "color", + ["#Colors Random"] = "color_random", + ["#Colors Do It Yourself"] = "color_diy", + ["#Blood"] = "blood", + ["#Money"] = "money", + ["#Sperms"] = "sperm", + ["#Feathers"] = "feather", + ["#Candy Cane"] = "candy_cane", + ["#Goldstar"] = "goldstar", + ["#Water Small"] = "water_small", + ["#Water Medium"] = "water_medium", + ["#Water Big"] = "water_big", + ["#Water Huge"] = "water_huge", + ["#Striderblood Small"] = "striderblood_small", + ["#Striderblood Medium"] = "striderblood_medium", + ["#Striderblood Big"] = "striderblood_big", + ["#Striderblood Huge"] = "striderblood_huge", + ["#More Sparks"] = "more_sparks", + ["#Spark Fountain"] = "spark_fountain", + ["#Jetflame"] = "jetflame", + ["#Jetflame Blue"] = "jetflame_blue", + ["#Jetflame Red"] = "jetflame_red", + ["#Jetflame Purple"] = "jetflame_purple", + ["#Comic Balls"] = "balls", + ["#Comic Balls Random"] = "balls_random", + ["#Comic Balls Fire Colors"] = "balls_firecolors", + ["#Souls"] = "souls", + --["#Debugger 10 Seconds"] = "debug_10", These are just buggy and shouldn't be used. + --["#Debugger 30 Seconds"] = "debug_30", + --["#Debugger 60 Seconds"] = "debug_60", + ["#Fire and Smoke"] = "fire_smoke", + ["#Fire and Smoke Huge"] = "fire_smoke_big", + ["#5 Growing Rings"] = "rings_grow_rings", + ["#Color and Magic"] = "color_magic", + } + + local CateGoryOW = vgui.Create("DCollapsibleCategory") + CateGoryOW:SetSize(0, 50) + CateGoryOW:SetExpanded(0) + CateGoryOW:SetLabel("Overwater Effect List") + + local ctrl = vgui.Create( "MatSelect", CateGoryOW ) + ctrl:SetItemWidth( 128 ) + ctrl:SetItemHeight( 128 ) + ctrl:SetConVar("wire_thruster_oweffect") + for name, mat in pairs( Effects ) do + ctrl:AddMaterialEx( name, "gui/thrustereffects/"..mat, mat, {wire_thruster_oweffect = mat} ) + end + + CateGoryOW:SetContents( ctrl ) + + panel:AddItem(CateGoryOW) + + Effects["#Same as over water"] = "same" + + local CateGoryUW = vgui.Create("DCollapsibleCategory") + CateGoryUW:SetSize(0, 50) + CateGoryUW:SetExpanded(0) + CateGoryUW:SetLabel("Underwater Effect List") + + local ctrlUW = vgui.Create( "MatSelect", CateGoryUW ) + ctrlUW:SetItemWidth( 128 ) + ctrlUW:SetItemHeight( 128 ) + ctrlUW:SetConVar("wire_thruster_uweffect") + for name, mat in pairs( Effects ) do + ctrlUW:AddMaterialEx( name, "gui/thrustereffects/"..mat, mat, {wire_thruster_uweffect = mat} ) + end + + CateGoryUW:SetContents( ctrlUW ) + + panel:AddItem(CateGoryUW) + + + local lst = {} + for k,v in pairs( list.Get("ThrusterSounds") ) do + lst[k] = {} + for k2,v2 in pairs( v ) do + lst[k]["wire_"..k2] = v2 + end + end + + panel:AddControl( "ComboBox", { Label = "#WireThrusterTool_soundname", + Description = "Thruster_Sounds_Desc", + MenuButton = "0", + Options = lst } ) + + panel:NumSlider("#WireThrusterTool_force", "wire_thruster_force", 1, 10000, 0) + panel:NumSlider("#WireThrusterTool_force_min", "wire_thruster_force_min", -10000, 10000, 0):SetTooltip("#WireThrusterTool_force_min.help") + panel:NumSlider("#WireThrusterTool_force_max", "wire_thruster_force_max", -10000, 10000, 0) + panel:CheckBox("#WireThrusterTool_bidir", "wire_thruster_bidir") + panel:CheckBox("#WireThrusterTool_owater", "wire_thruster_owater") + panel:CheckBox("#WireThrusterTool_uwater", "wire_thruster_uwater") +end +--from model pack 1 +list.Set( "ThrusterModels", "models/jaanus/thruster_flat.mdl", {} ) diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/trail.lua b/garrysmod/addons/feature-wire/lua/wire/stools/trail.lua new file mode 100644 index 0000000..9daff93 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/trail.lua @@ -0,0 +1,26 @@ +WireToolSetup.setCategory( "Visuals" ) +WireToolSetup.open( "trail", "Trail", "gmod_wire_trail", nil, "Trails" ) + +if CLIENT then + language.Add( "tool.wire_trail.name", "Trail Tool (Wire)" ) + language.Add( "tool.wire_trail.desc", "Spawns a wired trail." ) + language.Add( "WireTrailTool_trail", "Trail:" ) + language.Add( "WireTrailTool_mat", "Material:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + material = "" +} +TOOL.Model = "models/jaanus/wiretool/wiretool_range.mdl" + +if SERVER then + function TOOL:GetConVars() return { Material = self:GetClientInfo("material", "sprites/obsolete") } end +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_trail") + panel:AddControl( "MatSelect", { Height = "2", ConVar = "wire_trail_material", Options = list.Get( "trail_materials" ), ItemWidth = 64, ItemHeight = 64 } ) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/trigger.lua b/garrysmod/addons/feature-wire/lua/wire/stools/trigger.lua new file mode 100644 index 0000000..60c504c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/trigger.lua @@ -0,0 +1,130 @@ +-- Wire Trigger created by mitterdoo +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "trigger", "Trigger", "gmod_wire_trigger", nil, "Triggers" ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + filter = 0, -- 0: all entities, 1: only players, 2: only props (and stuff that isn't a player) + owneronly = 0, + sizex = 64, + sizey = 64, + sizez = 64, + offsetx = 0, + offsety = 0, + offsetz = 0, +} +local DrawOutline +if CLIENT then + DrawOutline = CreateClientConVar( "wire_trigger_drawalltriggers", "0", true ) + language.Add( "Tool.wire_trigger.filter", "Filters" ) + language.Add( "Tool.wire_trigger.owneronly", "Owner's Stuff Only" ) + language.Add( "Tool.wire_trigger.sizex", "Size X" ) + language.Add( "Tool.wire_trigger.sizey", "Size Y" ) + language.Add( "Tool.wire_trigger.sizez", "Size Z" ) + language.Add( "Tool.wire_trigger.offsetx", "Offset X" ) + language.Add( "Tool.wire_trigger.offsety", "Offset Y" ) + language.Add( "Tool.wire_trigger.offsetz", "Offset Z" ) + language.Add( "tool.wire_trigger.name", "Trigger Tool (Wire)" ) + language.Add( "tool.wire_trigger.desc", "Spawns a Trigger" ) + language.Add( "Tool.wire_trigger.alltriggers", "All Triggers Visible" ) + language.Add( "tool.wire_trigger.resetsize", "Reset Size" ) + language.Add( "tool.wire_trigger.resetoffset", "Reset Offset" ) + language.Add( "Tool.wire_trigger.filter_all", "All Entities" ) + language.Add( "Tool.wire_trigger.filter_players", "Only Players" ) + language.Add( "Tool.wire_trigger.filter_props", "Only Props" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } + + concommand.Add( "wire_trigger_reset_size", function( ply, cmd, args ) + + RunConsoleCommand( "wire_trigger_sizex", 64 ) + RunConsoleCommand( "wire_trigger_sizey", 64 ) + RunConsoleCommand( "wire_trigger_sizez", 64 ) + + end ) + concommand.Add( "wire_trigger_reset_offset", function( ply, cmd, args ) + + RunConsoleCommand( "wire_trigger_offsetx", 0 ) + RunConsoleCommand( "wire_trigger_offsety", 0 ) + RunConsoleCommand( "wire_trigger_offsetz", 0 ) + + end ) + +end + +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 64 ) + +function TOOL:GetConVars() + return self:GetClientInfo( "model" ), self:GetClientNumber( "filter" ), self:GetClientNumber( "owneronly" ), self:GetClientNumber( "sizex" ), self:GetClientNumber( "sizey" ), self:GetClientNumber( "sizez" ), self:GetClientNumber( "offsetx" ), self:GetClientNumber( "offsety" ), self:GetClientNumber( "offsetz" ) +end + +local function DrawTriggerOutlines( list ) + cam.Start3D( LocalPlayer():EyePos(), LocalPlayer():EyeAngles() ) + for k,ent in pairs( list ) do + local trig = ent:GetTriggerEntity() + + render.DrawWireframeBox( trig:GetPos(), Angle(0,0,0), trig:OBBMins(), trig:OBBMaxs(), Color( 255, 255, 0 ), true ) + render.DrawLine( trig:GetPos(), ent:GetPos(), Color( 255, 255, 0 ) ) + end + cam.End3D() +end + +hook.Add( "HUDPaint", "wire_trigger_draw_all_triggers", function() + if DrawOutline:GetBool() then + DrawTriggerOutlines( ents.FindByClass( "gmod_wire_trigger" ) ) + end +end ) + +function TOOL:DrawHUD() + local tr = util.TraceLine( util.GetPlayerTrace( LocalPlayer() ) ) + local ent = tr.Entity + if IsValid( ent ) and ent:GetClass() == "gmod_wire_trigger" and not DrawOutline:GetBool() then + DrawTriggerOutlines( {ent} ) + end +end +function TOOL:RightClick( tr ) + + if IsValid( tr.Entity ) then + local ent = tr.Entity + if ent:GetClass() == "gmod_wire_trigger" then + + -- http:--youtu.be/RTR1ny0O_io + local size = ent:GetTriggerSize() + local offset = ent:GetTriggerOffset() + RunConsoleCommand( "wire_trigger_sizex", size.x ) + RunConsoleCommand( "wire_trigger_sizey", size.y ) + RunConsoleCommand( "wire_trigger_sizez", size.z ) + RunConsoleCommand( "wire_trigger_offsetx", offset.x ) + RunConsoleCommand( "wire_trigger_offsety", offset.y ) + RunConsoleCommand( "wire_trigger_offsetz", offset.z ) + RunConsoleCommand( "wire_trigger_filter", ent:GetFilter() ) + RunConsoleCommand( "wire_trigger_owneronly", ent:GetOwnerOnly() and 1 or 0 ) + RunConsoleCommand( "wire_trigger_model", ent:GetModel() ) + return true + + end + end +end + + +function TOOL.BuildCPanel( panel ) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_trigger") + panel:CheckBox( "#Tool.wire_trigger.alltriggers", "wire_trigger_drawalltriggers" ) + panel:AddControl( "ComboBox", { + Label = "#Tool.wire_trigger.filter", + Options = { + ["#Tool.wire_trigger.filter_all"] = { wire_trigger_filter = 0 }, + ["#Tool.wire_trigger.filter_players"] = { wire_trigger_filter = 1 }, + ["#Tool.wire_trigger.filter_props"] = { wire_trigger_filter = 2 }, + } + }) + panel:CheckBox( "#Tool.wire_trigger.owneronly", "wire_trigger_owneronly" ) + panel:Button( "#Tool.wire_trigger.resetsize", "wire_trigger_reset_size" ) + panel:NumSlider("#Tool.wire_trigger.sizex", "wire_trigger_sizex", -1000, 1000, 64) + panel:NumSlider("#Tool.wire_trigger.sizey", "wire_trigger_sizey", -1000, 1000, 64) + panel:NumSlider("#Tool.wire_trigger.sizez", "wire_trigger_sizez", -1000, 1000, 64) + panel:Button( "#Tool.wire_trigger.resetoffset", "wire_trigger_reset_offset" ) + panel:NumSlider("#Tool.wire_trigger.offsetx", "wire_trigger_offsetx", -1000, 1000, 0) + panel:NumSlider("#Tool.wire_trigger.offsety", "wire_trigger_offsety", -1000, 1000, 0) + panel:NumSlider("#Tool.wire_trigger.offsetz", "wire_trigger_offsetz", -1000, 1000, 0) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/turret.lua b/garrysmod/addons/feature-wire/lua/wire/stools/turret.lua new file mode 100644 index 0000000..aae338e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/turret.lua @@ -0,0 +1,104 @@ +WireToolSetup.setCategory( "Other" ) +WireToolSetup.open( "turret", "Turret", "gmod_wire_turret", nil, "Turrets" ) + +-- Precache these sounds.. +Sound( "ambient.electrical_zap_3" ) +Sound( "NPC_FloorTurret.Shoot" ) + +-- Add Default Language translation (saves adding it to the txt files) +if CLIENT then + language.Add( "tool.wire_turret.name", "Turret" ) + language.Add( "tool.wire_turret.desc", "Throws bullets at things" ) + + language.Add( "Tool_wire_turret_spread", "Bullet Spread" ) + language.Add( "Tool_wire_turret_numbullets", "Bullets per Shot" ) + language.Add( "Tool_wire_turret_force", "Bullet Force" ) + language.Add( "Tool_wire_turret_sound", "Shoot Sound" ) + language.Add( "Tool_wire_turret_tracernum", "Tracer Every x Bullets:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + delay = 0.05, + force = 1, + sound = 0, + damage = 10, + spread = 0, + numbullets = 1, + automatic = 1, + tracer = "Tracer", + tracernum = 1, + model = "models/weapons/w_smg1.mdl" +} + +TOOL.GhostAngle = Angle(-90,0,0) +TOOL.GetGhostMin = function() return -2 end + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("delay"), self:GetClientNumber("damage"), self:GetClientNumber("force"), self:GetClientInfo("sound"), + self:GetClientNumber("numbullets"), self:GetClientNumber("spread"), self:GetClientInfo("tracer"), self:GetClientNumber("tracernum") + end +end + +local ValidTurretModels = { + ["models/weapons/w_smg1.mdl"] = true, + ["models/weapons/w_smg_mp5.mdl"] = true, + ["models/weapons/w_smg_mac10.mdl"] = true, + ["models/weapons/w_rif_m4a1.mdl"] = true, + ["models/weapons/w_357.mdl"] = true, + ["models/weapons/w_shot_m3super90.mdl"] = true +} + +function TOOL:GetModel() + local model = WireToolObj.GetModel(self) + return ValidTurretModels[model] and model or "models/weapons/w_smg1.mdl" +end + +function TOOL.BuildCPanel( CPanel ) + WireToolHelpers.MakePresetControl(CPanel, "wire_turret") + + -- Shot sounds + local weaponSounds = {Label = "#Tool_wire_turret_sound", MenuButton = 0, Options={}, CVars = {}} + weaponSounds["Options"]["#No Weapon"] = { wire_turret_sound = "" } + weaponSounds["Options"]["#Pistol"] = { wire_turret_sound = "Weapon_Pistol.Single" } + weaponSounds["Options"]["#SMG"] = { wire_turret_sound = "Weapon_SMG1.Single" } + weaponSounds["Options"]["#AR2"] = { wire_turret_sound = "Weapon_AR2.Single" } + weaponSounds["Options"]["#Shotgun"] = { wire_turret_sound = "Weapon_Shotgun.Single" } + weaponSounds["Options"]["#Floor Turret"] = { wire_turret_sound = "NPC_FloorTurret.Shoot" } + weaponSounds["Options"]["#Airboat Heavy"] = { wire_turret_sound = "Airboat.FireGunHeavy" } + weaponSounds["Options"]["#Zap"] = { wire_turret_sound = "ambient.electrical_zap_3" } + + + CPanel:AddControl("ComboBox", weaponSounds ) + + WireDermaExts.ModelSelect(CPanel, "wire_turret_model", list.Get( "WireTurretModels" ), 2) + + -- Tracer + local TracerType = {Label = "#Tracer", MenuButton = 0, Options={}, CVars = {}} + TracerType["Options"]["#Default"] = { wire_turret_tracer = "Tracer" } + TracerType["Options"]["#AR2 Tracer"] = { wire_turret_tracer = "AR2Tracer" } + TracerType["Options"]["#Airboat Tracer"] = { wire_turret_tracer = "AirboatGunHeavyTracer" } + TracerType["Options"]["#Laser"] = { wire_turret_tracer = "LaserTracer" } + + CPanel:AddControl("ComboBox", TracerType ) + + -- Various controls that you should play with! + if game.SinglePlayer() then + CPanel:NumSlider("#Tool_wire_turret_numbullets", "wire_turret_numbullets", 1, 10, 0) + end + CPanel:NumSlider("#Damage", "wire_turret_damage", 0, 100, 0) + CPanel:NumSlider("#Tool_wire_turret_spread", "wire_turret_spread", 0, 1.0, 2) + CPanel:NumSlider("#Tool_wire_turret_force", "wire_turret_force", 0, 500, 1) + + -- The delay between shots. + if game.SinglePlayer() then + CPanel:NumSlider("#Delay", "wire_turret_delay", 0.01, 1.0, 2) + CPanel:NumSlider("#Tool_wire_turret_tracernum", "wire_turret_tracernum", 0, 15, 0) + else + CPanel:NumSlider("#Delay", "wire_turret_delay", 0.05, 1.0, 2) + end + +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/twoway_radio.lua b/garrysmod/addons/feature-wire/lua/wire/stools/twoway_radio.lua new file mode 100644 index 0000000..4d51a2c --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/twoway_radio.lua @@ -0,0 +1,21 @@ +WireToolSetup.setCategory( "Input, Output/Data Transfer" ) +WireToolSetup.open( "twoway_radio", "Two-way Radio", "gmod_wire_twoway_radio", nil, "Two-way Radios" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_twoway_radio.name", "Two-Way Radio Tool (Wire)" ) + language.Add( "Tool.wire_twoway_radio.desc", "Spawns a two-way radio for use with the wire system." ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if (SERVER) then + ModelPlug_Register("radio") +end + +TOOL.ClientConVar[ "model" ] = "models/props_lab/binderblue.mdl" + +WireToolSetup.SetupLinking(true, "two-way radio") + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_twoway_radio_model", list.Get( "Wire_radio_Models" ), 2, true) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/user.lua b/garrysmod/addons/feature-wire/lua/wire/stools/user.lua new file mode 100644 index 0000000..5f20fee --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/user.lua @@ -0,0 +1,25 @@ +WireToolSetup.setCategory( "Other" ) +WireToolSetup.open( "user", "User", "gmod_wire_user", nil, "Users" ) + +if CLIENT then + language.Add( "Tool.wire_user.name", "User Tool (Wire)" ) + language.Add( "Tool.wire_user.desc", "Spawns a constant user prop for use with the wire system." ) + language.Add( "Tool.wire_user.range", "Max Range:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar = { + model = "models/jaanus/wiretool/wiretool_siren.mdl", + range = 200, +} + +if SERVER then + function TOOL:GetConVars() return self:GetClientNumber("range") end +end + +function TOOL.BuildCPanel( panel ) + ModelPlug_AddToCPanel(panel, "Laser_Tools", "wire_user", true) + panel:NumSlider("#tool.wire_user.range", "wire_user_range", 1, 1000, 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/value.lua b/garrysmod/addons/feature-wire/lua/wire/stools/value.lua new file mode 100644 index 0000000..e5984a1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/value.lua @@ -0,0 +1,431 @@ +WireToolSetup.setCategory( "Input, Output" ) +WireToolSetup.open( "value", "Constant Value", "gmod_wire_value", nil, "Constant Values" ) + +if CLIENT then + language.Add("Tool.wire_value.name", "Value Tool (Wire)") + language.Add("Tool.wire_value.desc", "Spawns a constant value for use with the wire system.") + + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "right", text = "Copy settings" }, + } + + WireToolSetup.setToolMenuIcon( "icon16/database_go.png" ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +-- shared helper functions +local function netWriteValues( selectedValues ) + local amount = math.Clamp(#selectedValues,0,20) + net.WriteUInt(amount,5) + for i=1,amount do + local DataType, Value = selectedValues[i].DataType, selectedValues[i].Value + + net.WriteString( selectedValues[i].DataType ) + net.WriteString( string.sub(selectedValues[i].Value,1,3000) ) + end +end +local function netReadValues() + local t = {} + local amount = net.ReadUInt(5) + for i=1,amount do + t[i] = { + DataType=net.ReadString(), + Value=net.ReadString() + } + end + return t +end + +if SERVER then + local playerValues = {} + util.AddNetworkString( "wire_value_values" ) + net.Receive( "wire_value_values", function( length, ply ) + playerValues[ply] = netReadValues() + end) + function TOOL:GetConVars() + return playerValues[self:GetOwner()] or {} + end + + function TOOL:RightClick(trace) + if not IsValid(trace.Entity) or trace.Entity:GetClass() != "gmod_wire_value" then return false end + playerValues[self:GetOwner()] = trace.Entity.value + net.Start("wire_value_values") + netWriteValues(trace.Entity.value) + net.Send(self:GetOwner()) + end +end + +TOOL.ClientConVar = { + model = "models/kobilica/value.mdl", + modelsize = "", + guesstype = "1", +} + +if CLIENT then + function TOOL:RightClick(trace) + return IsValid(trace.Entity) and trace.Entity:GetClass() == "gmod_wire_value" + end + + -- Supported data types + local types_lookup = { + Number = "NORMAL", + String = "STRING", + Angle = "ANGLE", + Vector = "VECTOR", + ["2D Vector"] = "VECTOR2", + ["4D Vector"] = "VECTOR4", + } + local types_lookup2 = { + NORMAL = "Number", + STRING = "String", + ANGLE = "Angle", + VECTOR = "Vector", + VECTOR2 = "2D Vector", + VECTOR4 = "4D Vector", + } + + local types_ordered = { "Number", "String", "Angle", "Vector", "2D Vector", "4D Vector" } + + local ValuePanels = {} + local selectedValues = {} + local panels = {} + local slider + local typeGuessCheckbox + local itemPanel + local resetButton + + local SendUpdate + + -- Saves the values in cookies so that they can be loaded next session + local function saveValues( values ) + values = values or selectedValues + + local old_amount = cookie.GetNumber( "wire_constant_value_amount", 0 ) + + cookie.Set( "wire_constant_value_amount", #values ) + + if old_amount > #values then + for i=#values+1,old_amount do + cookie.Delete( "wire_constant_value_value" .. i ) + cookie.Delete( "wire_constant_value_type" .. i ) + end + end + + for k, v in pairs( values ) do + cookie.Set( "wire_constant_value_value" .. k, string.sub(v.Value,1,3000) ) + cookie.Set( "wire_constant_value_type" .. k, v.DataType ) + end + end + + -- Loads the values from cookies + -- Don't worry about performance, because while garry's cookies are saved in a database, + -- they're also saved in Lua tables, which means they're accessed instantly. + local function loadValues( dontupdate ) + local oldSendUpdate = SendUpdate + SendUpdate = function() end -- Don't update now + + local amount = cookie.GetNumber( "wire_constant_value_amount", 0 ) + + slider:SetValue(amount) + + for i=1,amount do + local tp = cookie.GetString( "wire_constant_value_type" .. i, "NORMAL" ) + local val = cookie.GetString( "wire_constant_value_value" .. i, "0" ) + + tp = types_lookup2[tp] or "Number" + + selectedValues[i].DataType = tp + selectedValues[i].Value = val + + panels[i].valueEntry:SetValue( val ) + panels[i].typeSelection:SetText( tp ) + panels[i].typeSelection:OnSelect( _, tp ) + end + + SendUpdate = oldSendUpdate -- okay now it's fine to update again + end + + -- Sends the values to the server + function SendUpdate() + net.Start("wire_value_values") + netWriteValues(selectedValues) + net.SendToServer() + + saveValues() + end + + local validityChecks = { + Number = function( val ) return tonumber(val) ~= nil end, + ["2D Vector"] = function( val ) local x,y = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil end, + Vector = function( val ) local x,y,z = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil end, + ["4D Vector"] = function( val ) local x,y,z,w = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([%d.]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil and tonumber(w) ~= nil end, + String = function( val ) return true end, + } + validityChecks.Angle = validityChecks.Vector -- it's the same as vectors + + local examples = { + Number = "12.34", + ["2D Vector"] = "12.34, 12.34", + Vector = "12.34, 12.34, 12.34", + ["4D Vector"] = "12.34, 12.34, 12.34, 12.34", + String = "Hello World", + Angle = "90, 180, 360", + } + + -- Check if what the user wrote is a valid value of the specified type + local function validateValue( str, tp ) + if validityChecks[tp] then + return validityChecks[tp]( str ) + else + return false + end + end + + local typeGuessing = { -- we're not checking angle because it's indistinguishable from vectors + -- dropdown position, function + {1, validityChecks.Number}, + {5, validityChecks["2D Vector"]}, + {4, validityChecks.Vector}, + {6, validityChecks["4D Vector"]}, + {2, validityChecks.String}, + } + + -- Guess the type of the value the user wrote + local function guessType( str, typeSelection ) + for i=1,#typeGuessing do + local dropdownPos = typeGuessing[i][1] + local func = typeGuessing[i][2] + + if func( str ) then + typeSelection:ChooseOptionID( dropdownPos ) + return + end + end + end + + -- Add another value panel + local function AddValue( panel, id ) + local w = panel:GetWide() + + selectedValues[id] = { + DataType = "NORMAL", + Value = 0, + } + + local pnl = vgui.Create( "DCollapsibleCategory", panel ) + pnl:SetWide( w ) + pnl:SetLabel( "Value " .. id ) + pnl:Dock( TOP ) + pnl.id = id + + local top_panel = vgui.Create( "DPanel", pnl ) + top_panel.Paint = function() end + top_panel:Dock( TOP ) + + local _ = vgui.Create( "DPanel", top_panel ) -- this was the only solution I could think of to properly align this shit + _:Dock( RIGHT ) + _.Paint = function() end + _:SetSize( 16, 14 ) + local rem = vgui.Create( "DImageButton", _ ) + rem:SetImage( "icon16/delete.png" ) + rem:SizeToContents() + rem:SetPos( 0, 4 ) + rem:SetToolTip( "Remove this value" ) + + rem.DoClick = function() + if #selectedValues == 1 then -- can't remove the last value + resetButton:DoClick() -- instead, do a reset + return + end + + local id = pnl.id + panels[id]:Remove() + table.remove( panels, id ) + table.remove( selectedValues, id ) + slider:SetValue( math.max( slider:GetValue() - 1, 1 ) ) + for i=id, math.Clamp(math.Round(slider:GetValue()),1,20) do + panels[i].id = i + panels[i]:SetLabel( "Value " .. i ) + end + end + + local typeSelection = vgui.Create( "DComboBox", top_panel ) + typeSelection:Dock( FILL ) + typeSelection:DockMargin( 2, 2, 2, 2 ) + pnl.typeSelection = typeSelection + + typeSelection.OnSelect = function( panel, index, value ) + selectedValues[pnl.id].DataType = types_lookup[value] or "NORMAL" + SendUpdate() + + local val, tp = selectedValues[pnl.id].Value, selectedValues[pnl.id].DataType + tp = types_lookup2[tp] or "Number" + + if validateValue( val, tp ) then + pnl.valueEntry:SetToolTip() + pnl.parseIcon:SetImage( "icon16/accept.png" ) + else + pnl.valueEntry:SetToolTip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." ) + pnl.parseIcon:SetImage( "icon16/cancel.png" ) + end + end + + for k,v in pairs( types_ordered ) do + typeSelection:AddChoice(v) + end + + local valueEntry = vgui.Create( "DTextEntry",pnl ) + pnl.valueEntry = valueEntry + valueEntry:Dock( TOP ) + valueEntry:DockMargin( 2, 2, 2, 2 ) + + valueEntry.OnChange = function( panel ) + selectedValues[pnl.id].Value = panel:GetValue() + + local val, tp = selectedValues[pnl.id].Value, selectedValues[pnl.id].DataType + tp = types_lookup2[tp] or "Number" + + if typeGuessCheckbox:GetChecked() then + guessType( val, typeSelection ) + else + if validateValue( val, tp ) then + pnl.valueEntry:SetToolTip() + pnl.parseIcon:SetImage( "icon16/accept.png" ) + else + pnl.valueEntry:SetToolTip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." ) + pnl.parseIcon:SetImage( "icon16/cancel.png" ) + end + end + end + + local oldLoseFocus = valueEntry.OnLoseFocus + valueEntry.OnLoseFocus = function( panel ) + selectedValues[pnl.id].Value = panel:GetValue() + SendUpdate() + oldLoseFocus(panel) -- Otherwise we can't close the spawnmenu! + end + + local parseIcon = vgui.Create( "DImage", valueEntry ) + pnl.parseIcon = parseIcon + parseIcon:Dock( RIGHT ) + parseIcon:DockMargin( 2,2,2,2 ) + parseIcon:SetImage( "icon16/accept.png" ) + parseIcon:SizeToContents() + + typeSelection:ChooseOptionID( 1 ) + + return pnl + end + + -- Receive values from the server (when they right click to copy) + net.Receive( "wire_value_values", function( length ) + saveValues( netReadValues() ) + + if not IsValid(slider) then -- They right clicked without opening the cpanel first, just save the values + return + end + + loadValues() + end) + + -- Build context menu panel + function TOOL.BuildCPanel( panel ) + WireToolHelpers.MakeModelSizer(panel, "wire_value_modelsize") + ModelPlug_AddToCPanel(panel, "Value", "wire_value", true) + + local reset = panel:Button("Reset Values") + resetButton = reset + + typeGuessCheckbox = panel:CheckBox( "Automatically guess types", "wire_value_guesstype" ) + typeGuessCheckbox:SetToolTip( +[[When enabled, the type dropdown will automatically be updated as you type with +guessed types. It's unable to guess angles because they look the same as vectors. + +The green check you see inside the text boxes is the validator. If the value you write +is a value that can't be parsed as the selected type, the green check will turn into +a red X to indicate there's an error (You can then hover your cursor over the text box +to see what's wrong). + +There will never be an error if auto type guessing is enabled (unless you manually +set the type), because it will automatically set the type to a string when all other +types fail.]] ) + + local w,_ = panel:GetSize() + local valueSlider = vgui.Create( "DNumSlider" ) + slider = valueSlider + panel:AddItem( valueSlider ) + valueSlider:SetText( "Amount:" ) + valueSlider:SetMin(1) + valueSlider:SetMax(20) + valueSlider:SetDark( true ) + valueSlider:SetDecimals( 0 ) + + local LastValueAmount = 0 + reset.DoClick = function( panel ) + valueSlider:SetValue(1) + + for k,v in pairs(panels) do + v:Remove() + panels[k] = nil + end + + for k,v in pairs( selectedValues ) do + selectedValues[k] = nil + end + + LastValueAmount = 0 + + valueSlider:OnValueChanged( 1 ) + SendUpdate() + end + + valueSlider.OnValueChanged = function( valueSlider, value ) + local value = math.Clamp(math.Round(tonumber(value)),1,20) + if value ~= LastValueAmount then + if value > LastValueAmount then + for i = LastValueAmount + 1, value, 1 do + panels[i] = AddValue( itemPanel, i ) + end + elseif value < LastValueAmount then + for i = value + 1, LastValueAmount, 1 do + selectedValues[i] = nil + if IsValid(panels[i]) then panels[i]:Remove() end + panels[i] = nil + end + end + + itemPanel:SetTall( value * 73 ) + LastValueAmount = value + SendUpdate() + end + end + + itemPanel = vgui.Create( "DPanel" ) + itemPanel.Paint = function() end + panel:AddItem( itemPanel ) + itemPanel:SetTall( 73 ) + + loadValues() + SendUpdate() + + local pnl = vgui.Create( "DPanel" ) + panel:AddItem( pnl ) + pnl.Paint = function() end + pnl:SetTall( 16 ) + + local add = vgui.Create( "DImageButton", pnl ) + add:SetImage( "icon16/add.png" ) + add:SizeToContents() + add:SetToolTip( "Add a new value" ) + + function pnl.PerformLayout() + add:Center() + end + + function add.DoClick() + slider:SetValue( math.min( slider:GetValue() + 1, 20 ) ) + end + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/vehicle.lua b/garrysmod/addons/feature-wire/lua/wire/stools/vehicle.lua new file mode 100644 index 0000000..e712acb --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/vehicle.lua @@ -0,0 +1,17 @@ +WireToolSetup.setCategory( "Vehicle Control" ) +WireToolSetup.open( "vehicle", "Vehicle Controller", "gmod_wire_vehicle", nil, "Vehicle Controllers" ) + +if CLIENT then + language.Add("Tool.wire_vehicle.name", "Vehicle Controller Tool (Wire)") + language.Add("Tool.wire_vehicle.desc", "Spawn/link a Wire Vehicle controller.") +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_siren.mdl" + +WireToolSetup.SetupLinking(true, "vehicle") + +function TOOL.BuildCPanel(panel) + WireDermaExts.ModelSelect(panel, "wire_vehicle_model", list.Get( "Wire_Misc_Tools_Models" ), 1) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/vthruster.lua b/garrysmod/addons/feature-wire/lua/wire/stools/vthruster.lua new file mode 100644 index 0000000..791d7b8 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/vthruster.lua @@ -0,0 +1,244 @@ +WireToolSetup.setCategory( "Physics/Force" ) +WireToolSetup.open( "vthruster", "Vector Thruster", "gmod_wire_vectorthruster", nil, "Vector Thrusters" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_vthruster.name", "Vector Thruster Tool (Wire)" ) + language.Add( "Tool.wire_vthruster.desc", "Spawns a vector thruster for use with the wire system." ) + language.Add( "WireVThrusterTool_Mode", "Mode:" ) + language.Add( "WireVThrusterTool_Angle", "Use Yaw/Pitch Inputs Instead" ) + language.Add( "WireVThrusterTool_LengthIsMul", "Use Vector Length for Mul" ) + + TOOL.Information = { + { name = "left_0", stage = 0, text = "Create/Update " .. TOOL.Name }, + { name = "left_1", stage = 1, text = "Set the Angle, hold Shift to lock to 45 degrees" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 10 ) + +TOOL.ClientConVar[ "force" ] = "1500" +TOOL.ClientConVar[ "force_min" ] = "0" +TOOL.ClientConVar[ "force_max" ] = "10000" +TOOL.ClientConVar[ "model" ] = "models/jaanus/wiretool/wiretool_speed.mdl" +TOOL.ClientConVar[ "bidir" ] = "1" +TOOL.ClientConVar[ "soundname" ] = "" +TOOL.ClientConVar[ "oweffect" ] = "fire" +TOOL.ClientConVar[ "uweffect" ] = "same" +TOOL.ClientConVar[ "owater" ] = "1" +TOOL.ClientConVar[ "uwater" ] = "1" +TOOL.ClientConVar[ "mode" ] = "0" +TOOL.ClientConVar[ "angleinputs" ] = "0" +TOOL.ClientConVar[ "lengthismul" ] = "0" + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "force" ), self:GetClientNumber( "force_min" ), self:GetClientNumber( "force_max" ), self:GetClientInfo( "oweffect" ), + self:GetClientInfo( "uweffect" ), self:GetClientNumber( "owater" ) ~= 0, self:GetClientNumber( "uwater" ) ~= 0, self:GetClientNumber( "bidir" ) ~= 0, + self:GetClientInfo( "soundname" ), self:GetClientNumber( "mode" ), self:GetClientNumber( "angleinputs" ) ~= 0, self:GetClientNumber( "lengthismul" ) ~= 0 + end +end + +function TOOL:LeftClick( trace ) + local numobj = self:NumObjects() + + local ply = self:GetOwner() + + if (numobj == 0) then + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then return false end + + -- If there's no physics object then we can't constraint it! + if ( SERVER and not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then return false end + if (CLIENT) then return true end + + local ent = WireToolObj.LeftClick_Make(self, trace, ply ) + if isbool(ent) then return ent end + if IsValid(ent) then + ent:GetPhysicsObject():EnableMotion( false ) + self:ReleaseGhostEntity() + + self:SetObject(1, trace.Entity, trace.HitPos, trace.Entity:GetPhysicsObjectNum(trace.PhysicsBone), trace.PhysicsBone, trace.HitNormal) + self:SetObject(2, ent, trace.HitPos, ent:GetPhysicsObject(), 0, trace.HitNormal) + self:SetStage(1) + end + else + if (CLIENT) then return true end + + local anchor, wire_thruster = self:GetEnt(1), self:GetEnt(2) + local anchorbone = self:GetBone(1) + + local const = WireLib.Weld(wire_thruster, anchor, anchorbone, true, false) + + local Phys = wire_thruster:GetPhysicsObject() + Phys:EnableMotion( true ) + + undo.Create("WireVThruster") + undo.AddEntity( wire_thruster ) + undo.AddEntity( const ) + undo.SetPlayer( ply ) + undo.Finish() + + ply:AddCleanup( "wire_vthrusters", wire_thruster ) + ply:AddCleanup( "wire_vthrusters", const ) + + self:ClearObjects() + end + + return true +end + +local degrees = 0 + +function TOOL:Think() + if (self:NumObjects() > 0) then + if ( SERVER ) then + local Phys2 = self:GetPhys(2) + local Norm2 = self:GetNormal(2) + local cmd = self:GetOwner():GetCurrentCommand() + degrees = degrees + cmd:GetMouseX() * 0.05 + local ra = degrees + if (self:GetOwner():KeyDown(IN_SPEED)) then ra = math.Round(ra/45)*45 end + local Ang = Norm2:Angle() + Ang.pitch = Ang.pitch + 90 + Ang:RotateAroundAxis(Norm2, ra) + Phys2:SetAngles( Ang ) + Phys2:Wake() + end + else + WireToolObj.Think(self) -- Basic ghost + end +end + +if (CLIENT) then + function TOOL:FreezeMovement() + return self:GetStage() == 1 + end +end + +function TOOL:Holster() + if self:NumObjects() > 0 and IsValid(self:GetEnt(2)) then + self:GetEnt(2):Remove() + end + self:ClearObjects() +end + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakePresetControl(panel, "wire_vthruster") + WireDermaExts.ModelSelect(panel, "wire_vthruster_model", list.Get( "ThrusterModels" ), 4, true) + + local Effects = { + ["#No Effects"] = "none", + --["#Same as over water"] = "same", + ["#Flames"] = "fire", + ["#Plasma"] = "plasma", + ["#Smoke"] = "smoke", + ["#Smoke Random"] = "smoke_random", + ["#Smoke Do it Youself"] = "smoke_diy", + ["#Rings"] = "rings", + ["#Rings Growing"] = "rings_grow", + ["#Rings Shrinking"] = "rings_shrink", + ["#Bubbles"] = "bubble", + ["#Magic"] = "magic", + ["#Magic Random"] = "magic_color", + ["#Magic Do It Yourself"] = "magic_diy", + ["#Colors"] = "color", + ["#Colors Random"] = "color_random", + ["#Colors Do It Yourself"] = "color_diy", + ["#Blood"] = "blood", + ["#Money"] = "money", + ["#Sperms"] = "sperm", + ["#Feathers"] = "feather", + ["#Candy Cane"] = "candy_cane", + ["#Goldstar"] = "goldstar", + ["#Water Small"] = "water_small", + ["#Water Medium"] = "water_medium", + ["#Water Big"] = "water_big", + ["#Water Huge"] = "water_huge", + ["#Striderblood Small"] = "striderblood_small", + ["#Striderblood Medium"] = "striderblood_medium", + ["#Striderblood Big"] = "striderblood_big", + ["#Striderblood Huge"] = "striderblood_huge", + ["#More Sparks"] = "more_sparks", + ["#Spark Fountain"] = "spark_fountain", + ["#Jetflame"] = "jetflame", + ["#Jetflame Blue"] = "jetflame_blue", + ["#Jetflame Red"] = "jetflame_red", + ["#Jetflame Purple"] = "jetflame_purple", + ["#Comic Balls"] = "balls", + ["#Comic Balls Random"] = "balls_random", + ["#Comic Balls Fire Colors"] = "balls_firecolors", + ["#Souls"] = "souls", + --["#Debugger 10 Seconds"] = "debug_10", These are just buggy and shouldn't be used. + --["#Debugger 30 Seconds"] = "debug_30", + --["#Debugger 60 Seconds"] = "debug_60", + ["#Fire and Smoke"] = "fire_smoke", + ["#Fire and Smoke Huge"] = "fire_smoke_big", + ["#5 Growing Rings"] = "rings_grow_rings", + ["#Color and Magic"] = "color_magic", + } + + local CateGoryOW = vgui.Create("DCollapsibleCategory") + CateGoryOW:SetSize(0, 50) + CateGoryOW:SetExpanded(0) + CateGoryOW:SetLabel("Overwater Effect List") + + local ctrl = vgui.Create( "MatSelect", CateGoryOW ) + ctrl:SetItemWidth( 128 ) + ctrl:SetItemHeight( 128 ) + ctrl:SetConVar("wire_vthruster_oweffect") + for name, mat in pairs( Effects ) do + ctrl:AddMaterialEx( name, "gui/thrustereffects/"..mat, mat, {wire_vthruster_oweffect = mat} ) + end + + CateGoryOW:SetContents( ctrl ) + + panel:AddItem(CateGoryOW) + + Effects["#Same as over water"] = "same" + + local CateGoryUW = vgui.Create("DCollapsibleCategory") + CateGoryUW:SetSize(0, 50) + CateGoryUW:SetExpanded(0) + CateGoryUW:SetLabel("Underwater Effect List") + + local ctrlUW = vgui.Create( "MatSelect", CateGoryUW ) + ctrlUW:SetItemWidth( 128 ) + ctrlUW:SetItemHeight( 128 ) + ctrlUW:SetConVar("wire_vthruster_uweffect") + for name, mat in pairs( Effects ) do + ctrlUW:AddMaterialEx( name, "gui/thrustereffects/"..mat, mat, {wire_vthruster_uweffect = mat} ) + end + + CateGoryUW:SetContents( ctrlUW ) + + panel:AddItem(CateGoryUW) + + local lst = {} + for k,v in pairs( list.Get("ThrusterSounds") ) do + lst[k] = {} + for k2,v2 in pairs( v ) do + lst[k]["wire_v"..k2] = v2 + end + end + panel:AddControl( "ListBox", { Label = "#Thruster_Sounds", Options = lst } ) + + panel:NumSlider("#WireThrusterTool_force", "wire_vthruster_force", 1, 10000, 2 ) + panel:NumSlider("#WireThrusterTool_force_min", "wire_vthruster_force_min", -10000, 10000, 2 ):SetTooltip("#WireThrusterTool_force_min.help") + panel:NumSlider("#WireThrusterTool_force_max", "wire_vthruster_force_max", -10000, 10000, 2 ) + panel:CheckBox("#WireThrusterTool_bidir", "wire_vthruster_bidir") + panel:CheckBox("#WireThrusterTool_owater", "wire_vthruster_owater") + panel:CheckBox("#WireThrusterTool_uwater", "wire_vthruster_uwater") + + panel:AddControl("ListBox", { + Label = "#WireVThrusterTool_Mode", + Options = { + ["#XYZ Local"] = { wire_vthruster_mode = "0" }, + ["#XYZ World"] = { wire_vthruster_mode = "1" }, + ["#XY Local, Z World"] = { wire_vthruster_mode = "2" }, + } + }) + + panel:CheckBox("#WireVThrusterTool_Angle", "wire_vthruster_angleinputs") + panel:CheckBox("#WireVThrusterTool_LengthIsMul", "wire_vthruster_lengthismul") +end + +list.Set( "ThrusterModels", "models/jaanus/wiretool/wiretool_speed.mdl", {} ) diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/watersensor.lua b/garrysmod/addons/feature-wire/lua/wire/stools/watersensor.lua new file mode 100644 index 0000000..62f73f2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/watersensor.lua @@ -0,0 +1,25 @@ +WireToolSetup.setCategory( "Detection" ) +WireToolSetup.open( "watersensor", "Water Sensor", "gmod_wire_watersensor", nil, "Water Sensors" ) + +if CLIENT then + language.Add( "Tool.wire_watersensor.name", "Water Sensor Tool (Wire)" ) + language.Add( "Tool.wire_watersensor.desc", "Spawns a constant Water Sensor prop for use with the wire system." ) + language.Add( "WireWatersensorTool_watersensor", "Water Sensor:" ) + TOOL.Information = { { name = "left", text = "Create/Update " .. TOOL.Name } } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("WaterSensor") +end + +TOOL.ClientConVar = { + model = "models/beer/wiremod/watersensor.mdl", + modelsize = "", +} + +function TOOL.BuildCPanel(panel) + WireToolHelpers.MakeModelSizer(panel, "wire_watersensor_modelsize") + ModelPlug_AddToCPanel(panel, "WaterSensor", "wire_watersensor") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/waypoint.lua b/garrysmod/addons/feature-wire/lua/wire/stools/waypoint.lua new file mode 100644 index 0000000..bf77af4 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/waypoint.lua @@ -0,0 +1,94 @@ +WireToolSetup.setCategory( "Detection/Beacon" ) +WireToolSetup.open( "waypoint", "Waypoint", "gmod_wire_waypoint", nil, "Waypoints" ) + +if ( CLIENT ) then + language.Add( "Tool.wire_waypoint.name", "Waypoint Beacon Tool (Wire)" ) + language.Add( "Tool.wire_waypoint.desc", "Spawns a waypoint beacon for use with the wire system." ) + language.Add( "WireWaypointTool_range", "Range:" ) + language.Add( "WireWaypointTool_alink", "Auto-link previous" ) + TOOL.Information = { + { name = "left_0", stage = 0, text = "Create/Update " .. TOOL.Name }, + { name = "right_0", stage = 0, text = "Link to next waypoint" }, + { name = "reload_0", stage = 0, text = "Remove link to next waypoint" }, + { name = "left_1", stage = 1, text = "Select waypoint to go to after this one" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 30 ) + +TOOL.ClientConVar = { + model = "models/props_lab/powerbox02d.mdl", + range = 150, + alink = 0, + createflat = 1, +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber("range") + end +end + +function TOOL:LeftClick(trace) + if (!trace.HitPos) then return false end + if (trace.Entity:IsPlayer()) then return false end + if ( CLIENT ) then return true end + if not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) then return false end + + local ply = self:GetOwner() + + if (self:GetStage() == 1) then + self:SetStage(0) + + if (trace.Entity:IsValid()) and (trace.Entity:GetClass() == "gmod_wire_waypoint") and (self.SrcWaypoint) and (self.SrcWaypoint:IsValid()) then + self.SrcWaypoint:SetNextWaypoint(trace.Entity) + self.SrcWaypoint = nil + + return true + end + + self.SrcWaypoint = nil + + return + end + + local ent = self:LeftClick_Make( trace, ply ) + if isbool(ent) then return ent end + local ret = self:LeftClick_PostMake( ent, ply, trace ) + + // Auto-link (itsbth) + if ( self.OldWaypoint && self.OldWaypoint:IsValid() and self:GetClientNumber("alink") == 1 ) then + self.OldWaypoint:SetNextWaypoint(ent) + end + self.OldWaypoint = ent + + return ret +end + +function TOOL:RightClick(trace) + if (self:GetStage() == 0) and (trace.Entity:IsValid()) and (trace.Entity:GetClass() == "gmod_wire_waypoint") then + self.SrcWaypoint = trace.Entity + self:SetStage(1) + + return true + end + + return self:LeftClick(trace) +end + +function TOOL:Reload(trace) + if self:GetStage() ~= 0 then return false end + + if (trace.Entity:IsValid()) and (trace.Entity:GetClass() == "gmod_wire_waypoint") then + trace.Entity:SetNextWaypoint(NULL) + + return true + end +end + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "Misc_Tools", "wire_waypoint") + panel:NumSlider("#WireWaypointTool_range","wire_waypoint_range",1,2000,2) + panel:CheckBox("#WireWaypointTool_alink","wire_waypoint_alink") + panel:CheckBox("#Create Flat to Surface", "wire_waypoint_createflat") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/weight.lua b/garrysmod/addons/feature-wire/lua/wire/stools/weight.lua new file mode 100644 index 0000000..fc34e52 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/weight.lua @@ -0,0 +1,29 @@ +WireToolSetup.setCategory( "Physics", "Detection" ) +WireToolSetup.open( "weight", "Weight (Adjustable)", "gmod_wire_weight", nil, "Adjustable Weights" ) + +if CLIENT then + language.Add( "tool.wire_weight.name", "Weight Tool (Wire)" ) + language.Add( "tool.wire_weight.desc", "Spawns a weight." ) + TOOL.Information = { + { name = "left", text = "Create/Update " .. TOOL.Name }, + { name = "reload", text = "Copy model" }, + } +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 20 ) + +if SERVER then + ModelPlug_Register("weight") + function TOOL:GetConVars() end + + -- Uses default WireToolObj:MakeEnt's WireLib.MakeWireEnt function +end + +TOOL.ClientConVar = { + model = "models/props_interiors/pot01a.mdl", +} +TOOL.ReloadSetsModel = true + +function TOOL.BuildCPanel(panel) + ModelPlug_AddToCPanel(panel, "weight", "wire_weight", true) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/stools/wheel.lua b/garrysmod/addons/feature-wire/lua/wire/stools/wheel.lua new file mode 100644 index 0000000..e895eac --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/stools/wheel.lua @@ -0,0 +1,114 @@ +WireToolSetup.setCategory( "Physics/Force" ) +WireToolSetup.open( "wheel", "Wheel", "gmod_wire_wheel", nil, "Wheels" ) + +if CLIENT then + language.Add( "tool.wire_wheel.name", "Wheel Tool (wire)" ) + language.Add( "tool.wire_wheel.desc", "Attaches a wheel to something." ) + TOOL.Information = { { name = "left", text = "Attach a wheel" } } + + language.Add( "tool.wire_wheel.group", "Input value to go forward:" ) + language.Add( "tool.wire_wheel.group_reverse", "Input value to go in reverse:" ) + language.Add( "tool.wire_wheel.group_stop", "Input value for no acceleration:" ) + language.Add( "tool.wire_wheel.group_desc", "All these values need to be different." ) +end +WireToolSetup.BaseLang() +WireToolSetup.SetupMax( 30 ) + +TOOL.ClientConVar = { + torque = 3000, + friction = 1, + nocollide = 1, + forcelimit = 0, + fwd = 1, -- Forward + bck = -1, -- Back + stop = 0, -- Stop +} + +if SERVER then + function TOOL:GetConVars() + return self:GetClientNumber( "fwd" ), self:GetClientNumber( "bck" ), self:GetClientNumber( "stop" ), self:GetClientNumber( "torque" ) + end + + function TOOL:MakeEnt( ply, model, Ang, trace ) + local targetPhys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + + -- Get client's CVars + local friction = self:GetClientNumber( "friction" ) + local nocollide = self:GetClientNumber( "nocollide" ) + local limit = self:GetClientNumber( "forcelimit" ) + + local fwd,bck,stop,torque = self:GetConVars() -- These are the ones used in Setup + if fwd == stop or bck == stop or fwd == bck then return false end + + -- Create the wheel + local wheelEnt = WireLib.MakeWireEnt(ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}, fwd, bck, stop, torque ) + self:SetPos( wheelEnt, trace ) + + -- Wake up the physics object so that the entity updates + wheelEnt:GetPhysicsObject():Wake() + + -- Set the hinge Axis perpendicular to the trace hit surface + local LPos1 = wheelEnt:GetPhysicsObject():WorldToLocal( wheelEnt:GetPos() + trace.HitNormal ) + local LPos2 = targetPhys:WorldToLocal( trace.HitPos ) + + local constraint, axis = constraint.Motor( wheelEnt, trace.Entity, 0, trace.PhysicsBone, LPos1, LPos2, friction, 1000, 0, nocollide, false, ply, limit ) + + undo.Create(self.WireClass) + undo.AddEntity( axis ) + undo.AddEntity( constraint ) + undo.AddEntity( wheelEnt ) + undo.SetPlayer( ply ) + undo.Finish() + + ply:AddCleanup( self.WireClass, axis ) + ply:AddCleanup( self.WireClass, constraint ) + ply:AddCleanup( self.WireClass, wheelEnt ) + + --BUGFIX:WIREMOD-11:Deleting prop did not deleting wheels + wheelEnt:SetWheelBase(trace.Entity) + + wheelEnt:SetMotor( constraint ) + wheelEnt:SetDirection( constraint.direction ) + wheelEnt:SetAxis( trace.HitNormal ) + wheelEnt:DoDirectionEffect() + + return wheelEnt + end + + function TOOL:LeftClick_PostMake(_, _, _) end -- We're handling this in MakeEnt since theres a motor +end + +function TOOL:GetAngle(trace) + return trace.HitNormal:Angle() + Angle(self:GetOwner():GetInfoNum( "wheel_rx", 0 ), self:GetOwner():GetInfoNum( "wheel_ry", 0 ), self:GetOwner():GetInfoNum( "wheel_rz", 0 )) +end +function TOOL:SetPos( ent, trace ) + local wheelOffset = ent:GetPos() - ent:NearestPoint( ent:GetPos() - (trace.HitNormal * 512) ) + ent:SetPos( trace.HitPos + wheelOffset + trace.HitNormal ) +end + +function TOOL:GetModel() + local ply = self:GetOwner() + if self:CheckValidModel(ply:GetInfo("wheel_model")) then --use a valid model or the server crashes :< + return ply:GetInfo("wheel_model") + else + return "models/props_c17/oildrum001.mdl" --use some other random, valid prop instead + end +end + +function TOOL.BuildCPanel( panel ) + WireToolHelpers.MakePresetControl(panel, "wire_wheel") + panel:NumSlider("#tool.wire_wheel.group", "wire_wheel_fwd", -10, 10, 0) + panel:NumSlider("#tool.wire_wheel.group_stop", "wire_wheel_stop", -10, 10, 0) + panel:NumSlider("#tool.wire_wheel.group_reverse", "wire_wheel_bck", -10, 10, 0) + --WireDermaExts.ModelSelect(panel, "wheel_model", list.Get( "WheelModels" ), 3, true) -- This doesn't seem to set the wheel_rx convars right + panel:AddControl( "PropSelect", { Label = "#tool.wheel.model", + ConVar = "wheel_model", + Category = "Wheels", + height = 5, + Models = list.Get( "WheelModels" ) } ) + panel:NumSlider("#tool.wheel.torque", "wire_wheel_torque", 10, 10000, 0) + panel:NumSlider("#tool.wheel.forcelimit", "wire_wheel_forcelimit", 0, 50000, 0) + local frictionPanel = panel:NumSlider("#tool.wheel.friction", "wire_wheel_friction", 0, 50, 2) + frictionPanel:SetTooltip("How quickly the wheel comes to a stop. Note: An existing wheel's friction cannot be updated") + panel:CheckBox("#tool.wheel.nocollide", "wire_wheel_nocollide") +end diff --git a/garrysmod/addons/feature-wire/lua/wire/timedpairs.lua b/garrysmod/addons/feature-wire/lua/wire/timedpairs.lua new file mode 100644 index 0000000..4b7fe7d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/timedpairs.lua @@ -0,0 +1,94 @@ +-- Timedpairs by Grocel. (Rewrite by Divran) +-- It allows you to go through long tables, but without game freezing. +-- Its like a for-pairs loop. +-- +-- How to use: +-- WireLib.Timedpairs(string unique name, table, number ticks done at once, function tickcallback[, function endcallback, ...]) +-- +-- tickcallback is called every tick, it ticks for each KeyValue of the table. +-- Its arguments are the current key and value. +-- Return false in the tickcallback function to break the loop. +-- tickcallback(key, value, ...) +-- +-- endcallback is called after the last tickcallback has been called. +-- Its arguments are the same as the last arguments of WireLib.Timedpairs +-- endcallback(lastkey, lastvalue, ...) + +if (!WireLib) then return end + +local next = next +local pairs = pairs +local unpack = unpack +local pcall = pcall + +local functions = {} +function WireLib.TimedpairsGetTable() + return functions +end + +function WireLib.TimedpairsStop(name) + functions[name] = nil +end + +local function copy( t ) -- custom table copy function to convert to numerically indexed table + local ret = {} + for k,v in pairs( t ) do + ret[#ret+1] = { key = k, value = v } + end + return ret +end + +local function Timedpairs() + if not next(functions) then return end + + local toremove = {} + + for name, data in pairs( functions ) do + for i=1,data.step do + data.currentindex = data.currentindex + 1 -- increment index counter + local lookup = data.lookup or {} + if data.currentindex <= #lookup then -- If there are any more values.. + local kv = lookup[data.currentindex] or {} -- Get the current key and value + local ok, err = xpcall( data.callback, debug.traceback, kv.key, kv.value, unpack(data.args) ) -- DO EET + + if not ok then -- oh noes + WireLib.ErrorNoHalt( "Error in Timedpairs '" .. name .. "': " .. err ) + toremove[#toremove+1] = name + break + elseif err == false then -- They returned false inside the function + toremove[#toremove+1] = name + break + end + else -- Out of keys. Entire table looped + if data.endcallback then -- If we had any end callback function + local kv = lookup[data.currentindex-1] or {} -- get previous key & value + local ok, err = xpcall( data.endcallback, debug.traceback, kv.key, kv.value, unpack(data.args) ) + + if not ok then + WireLib.ErrorNoHalt( "Error in Timedpairs '" .. name .. "' (in end function): " .. err ) + end + end + toremove[#toremove+1] = name + break + end + end + end + + for i=1,#toremove do -- Remove all that were flagged for removal + functions[toremove[i]] = nil + end +end +if (CLIENT) then + hook.Add("PostRenderVGUI", "WireLib_Timedpairs", Timedpairs) // Doesn't get paused in single player. Can be important for vguis. +else + hook.Add("Think", "WireLib_Timedpairs", Timedpairs) // Servers still uses Think. +end + +function WireLib.Timedpairs(name,tab,step,callback,endcallback,...) + functions[name] = { lookup = copy(tab), step = step, currentindex = 0, callback = callback, endcallback = endcallback, args = {...} } +end + +function WireLib.Timedcall(callback,...) // calls the given function like simple timer, but isn't affected by game pausing. + local dummytab = {true} + WireLib.Timedpairs("Timedcall_"..tostring(dummytab),dummytab,1,function(k, v, ...) callback(...) end,nil,...) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/tool_loader.lua b/garrysmod/addons/feature-wire/lua/wire/tool_loader.lua new file mode 100644 index 0000000..4e5d9df --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/tool_loader.lua @@ -0,0 +1,654 @@ +if SERVER then AddCSLuaFile() end + +local function LoadTools() + -- load tools + for _, filename in pairs(file.Find("wire/stools/*.lua","LUA")) do + include("wire/stools/"..filename) + AddCSLuaFile("wire/stools/"..filename) + end + + -- close last TOOL + if TOOL then WireToolSetup.close() end +end + + +-- prevent showing the ghost when poiting at any class in the TOOL.NoGhostOn table +local function NoGhostOn(self, trace) + return self.NoGhostOn and table.HasValue( self.NoGhostOn, trace.Entity:GetClass()) +end + + +WireToolObj = {} +setmetatable( WireToolObj, ToolObj ) + + +WireToolObj.Tab = "Wire" + + +-- optional LeftClick tool function for basic tools that just place/weld a device [default] +function WireToolObj:LeftClick( trace ) + if not trace.HitPos or trace.Entity:IsPlayer() or trace.Entity:IsNPC() or (SERVER and not util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone )) then return false end + if self.NoLeftOnClass and trace.HitNonWorld and (trace.Entity:GetClass() == self.WireClass or NoGhostOn(self, trace)) then return false end + + if CLIENT then return true end + + local ply = self:GetOwner() + + local ent = self:LeftClick_Make( trace, ply ) -- WireToolObj.LeftClick_Make will be called if another function was not defined + + return self:LeftClick_PostMake( ent, ply, trace ) +end + +if SERVER then + -- + function WireToolObj:LeftClick_Make( trace, ply ) + -- hit our own class, update + if self:CheckHitOwnClass(trace) then + self:LeftClick_Update(trace) + return true + end + + local model = self:GetModel() + if not self:CheckMaxLimit() or not self:CheckValidModel(model) then return false end + + local Ang = self:GetAngle( trace ) + + local ent = self:MakeEnt( ply, model, Ang, trace ) + + if IsValid(ent) then self:PostMake_SetPos( ent, trace ) end + + return ent + end + + -- Default MakeEnt function, override to use a different MakeWire* function + function WireToolObj:MakeEnt( ply, model, Ang, trace ) + local ent = WireLib.MakeWireEnt( ply, {Class = self.WireClass, Pos=trace.HitPos, Angle=Ang, Model=model}, self:GetConVars() ) + if ent.RestoreNetworkVars then ent:RestoreNetworkVars(self:GetDataTables()) end + return ent + end + + function WireToolObj:GetConVars() return end + + function WireToolObj:GetDataTables() return {} end + + -- + -- to prevent update, set TOOL.NoLeftOnClass = true + function WireToolObj:LeftClick_Update( trace ) + if trace.Entity.Setup then trace.Entity:Setup(self:GetConVars()) end + if trace.Entity.RestoreNetworkVars then trace.Entity:RestoreNetworkVars(self:GetDataTables()) end + end + + -- + -- this function needs to return true if the tool beam should be "fired" + function WireToolObj:LeftClick_PostMake( ent, ply, trace ) + if ent == true then return true end + if ent == nil or ent == false or not ent:IsValid() then return false end + + -- Parenting + local nocollide, const + if self:GetClientNumber( "parent" ) == 1 then + if (trace.Entity:IsValid()) then + -- Nocollide the gate to the prop to make adv duplicator (and normal duplicator) find it + if (!self.ClientConVar.noclip or self:GetClientNumber( "noclip" ) == 1) then + nocollide = constraint.NoCollide( ent, trace.Entity, 0,trace.PhysicsBone ) + end + + ent:SetParent( trace.Entity ) + end + elseif not self:GetOwner():KeyDown(IN_WALK) then + -- Welding + const = WireLib.Weld( ent, trace.Entity, trace.PhysicsBone, true, false, self:GetOwner():GetInfo( "wire_tool_weldworld" )~="0" ) + + -- Nocollide All + if self:GetOwner():GetInfo( "wire_tool_nocollide" )~="0" then + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + end + + undo.Create( self.WireClass ) + undo.AddEntity( ent ) + if (const) then undo.AddEntity( const ) end + if (nocollide) then undo.AddEntity( nocollide ) end + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + + ply:AddCleanup( self.WireClass, ent ) + + if self.PostMake then self:PostMake(ent, ply, trace) end + duplicator.ApplyEntityModifiers(ply, ent) + + return true + end +end + +function WireToolObj:Reload( trace ) + if not IsValid(trace.Entity) then return false end + if CLIENT then return true end + if self.ReloadSetsModel then + self:GetOwner():ConCommand(self.Mode.."_model " .. trace.Entity:GetModel()) + self:GetOwner():PrintMessage( HUD_PRINTTALK, self.Name.." model set to " .. trace.Entity:GetModel() ) + return true + end + if (trace.Entity:GetParent():IsValid()) then + + -- Get its position + local pos = trace.Entity:GetPos() + + -- Unparent + trace.Entity:SetParent() + + -- Teleport it back to where it was before unparenting it (because unparenting causes issues which makes the gate teleport to random wierd places) + trace.Entity:SetPos( pos ) + + -- Wake + local phys = trace.Entity:GetPhysicsObject() + if (phys) then + phys:Wake() + end + + -- Notify + self:GetOwner():ChatPrint("Entity unparented.") + return true + end + return false +end + +-- basic UpdateGhost function that should cover most of wire's ghost updating needs [default] +function WireToolObj:UpdateGhost( ent ) + if not IsValid(ent) then return end + + local trace = self:GetOwner():GetEyeTrace() + if not trace.Hit then return end + + -- don't draw the ghost if we hit nothing, a player, an npc, the type of device this tool makes, or any class this tool says not to + if IsValid(trace.Entity) and (trace.Entity:IsPlayer() or trace.Entity:IsNPC() or trace.Entity:GetClass() == self.WireClass or NoGhostOn(self, trace)) then + ent:SetNoDraw( true ) + return + end + + ent:SetAngles( self:GetAngle( trace ) ) + self:SetPos( ent, trace ) + + --show the ghost + ent:SetNoDraw( false ) +end + + +-- option tool Think function for updating the pos of the ghost and making one when needed [default] +function WireToolObj:Think() + local model = self:GetModel() + if not IsValid(self.GhostEntity) or self.GhostEntity:GetModel() ~= model then + if self.GetGhostAngle then -- the tool as a function for getting the proper angle for the ghost + self:MakeGhostEntity( model, Vector(0,0,0), self:GetGhostAngle(self:GetOwner():GetEyeTrace()) ) + else -- the tool gives a fixed angle to add else use a zero'd angle + self:MakeGhostEntity( model, Vector(0,0,0), self.GhostAngle or Angle(0,0,0) ) + end + if IsValid(self.GhostEntity) and CLIENT then self.GhostEntity:SetPredictable(true) end + end + self:UpdateGhost( self.GhostEntity ) +end + +function WireToolObj:CheckHitOwnClass( trace ) + return trace.Entity:IsValid() and trace.Entity:GetClass() == self.WireClass +end + +if SERVER then + function WireToolObj:CheckMaxLimit() + return self:GetSWEP():CheckLimit(self.MaxLimitName or (self.Mode.."s")) + end +end + +-- Allow ragdolls to be used? +local ValidModelCache = {[""] = false} +function WireToolObj:CheckValidModel( model ) + local val = ValidModelCache[model or ""] + if val~=nil then return val end + if SERVER then + ValidModelCache[model] = util.IsValidModel(model) and util.IsValidProp(model) + else + ValidModelCache[model] = file.Exists(model,"GAME") -- util.IsValidModel doesn't work clientside until after the server runs util.PrecacheModel + end + return ValidModelCache[model] +end + +function WireToolObj:GetModel() + local model_convar = self:GetClientInfo( "model" ) + if self.ClientConVar.modelsize then + local modelsize = self:GetClientInfo( "modelsize" ) + if modelsize != "" then + local model = string.sub(model_convar, 1, -5) .."_".. modelsize .. string.sub(model_convar, -4) + if self:CheckValidModel(model) then return model end + model = string.GetPathFromFilename(model_convar) .. modelsize .."_".. string.GetFileFromFilename(model_convar) + if self:CheckValidModel(model) then return model end + end + end + if self:CheckValidModel(model_convar) then --use a valid model or the server crashes :< + return model_convar + end + return self.Model or self.ClientConVar.model or "models/props_c17/oildrum001.mdl" +end + +function WireToolObj:GetAngle( trace ) + local Ang + if math.abs(trace.HitNormal.x) < 0.001 and math.abs(trace.HitNormal.y) < 0.001 then + Ang = Vector(0,0,trace.HitNormal.z):Angle() + else + Ang = trace.HitNormal:Angle() + end + if self.GetGhostAngle then -- the tool as a function for getting the proper angle for the ghost + Ang = self:GetGhostAngle( trace ) + elseif self.GhostAngle then -- the tool gives a fixed angle to add + Ang = Ang + self.GhostAngle + end + if self.ClientConVar.createflat then + -- Screen models need a bit of adjustment + if self:GetClientNumber("createflat") == 0 then + Ang.pitch = Ang.pitch + 90 + end + local model = self:GetModel() + if string.find(model, "pcb") or string.find(model, "hunter") then + -- PHX Screen models should thus be +180 when not flat, +90 when flat + Ang.pitch = Ang.pitch + 90 + end + else + Ang.pitch = Ang.pitch + 90 + end + + return Ang +end + +function WireToolObj:SetPos( ent, trace ) + -- move the ghost to aline properly to where the device will be made + local min = ent:OBBMins() + if self.GetGhostMin then -- tool has a function for getting the min + ent:SetPos( trace.HitPos - trace.HitNormal * self:GetGhostMin( min, trace ) ) + elseif self.GhostMin then -- tool gives the axis for the OBBmin to use + ent:SetPos( trace.HitPos - trace.HitNormal * min[self.GhostMin] ) + elseif self.ClientConVar.createflat and (self:GetClientNumber("createflat") == 1) ~= ((string.find(self:GetModel(), "pcb") or string.find(self:GetModel(), "hunter")) ~= nil) then + -- Screens have odd models. If createflat is 1, or its 0 and its a PHX model, use max.x + ent:SetPos( trace.HitPos + trace.HitNormal * ent:OBBMaxs().x ) + else -- default to the z OBBmin + ent:SetPos( trace.HitPos - trace.HitNormal * min.z ) + end +end +if SERVER then WireToolObj.PostMake_SetPos = WireToolObj.SetPos end + +if CLIENT then + local fonttab = {font = "Helvetica", size = 60, weight = 900} + for size=60,20,-2 do + fonttab.size = size + surface.CreateFont("GmodToolScreen"..size, fonttab) + end + + local iconparams = { + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 1, + ["$ignorez"] = 1 -- This is essential, since the base Gmod screen_bg has ignorez, otherwise it'll draw overtop of us + } + local txBackground = surface.GetTextureID("models/weapons/v_toolgun/wirescreen_bg") + function WireToolObj:DrawToolScreen(width, height) + surface.SetTexture(txBackground) + surface.DrawTexturedRect(0, 0, width, height) + + local text = self.Name + if self.ScreenFont then + surface.SetFont(self.ScreenFont) + else + for size=60,20,-2 do + surface.SetFont("GmodToolScreen"..size) + local x,y = surface.GetTextSize(text) + if x <= (width - 16) then + self.ScreenFont = "GmodToolScreen"..size + break + end + end + end + local w, h = surface.GetTextSize(text) + local x = width/2 - w/2 + local y = 105 - h/2 + + -- Draw shadow first + surface.SetTextColor(0, 0, 0, 255) + surface.SetTextPos(x + 3, y + 3) + surface.DrawText(text) + + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(x, y) + surface.DrawText(text) + + iconparams[ "$basetexture" ] = "spawnicons/"..self:GetModel():sub(1,-5) + local mat = CreateMaterial(self:GetModel() .. "_DImage", "UnlitGeneric", iconparams ) + surface.SetMaterial(mat) + surface.DrawTexturedRect( 128 - 32, 150, 64, 64) + + local on = self:GetOwner():GetInfo( "wire_tool_weldworld" )~="0" and not self:GetOwner():KeyDown(IN_WALK) + draw.DrawText("World Weld: "..(on and "On" or "Off"), + "GmodToolScreen20", + 5, height-38, + Color(on and 150 or 255, on and 255 or 150, 150, 255) + ) + local on = self:GetOwner():GetInfo( "wire_tool_nocollide" )~="0" and not self:GetOwner():KeyDown(IN_WALK) + draw.DrawText("Nocollide All: "..(on and "On" or "Off"), + "GmodToolScreen20", + 5, height-22, + Color(on and 150 or 255, on and 255 or 150, 150, 255) + ) + end + + CreateClientConVar( "wire_tool_weldworld", "0", true, true ) + CreateClientConVar( "wire_tool_nocollide", "1", true, true ) + local function CreateCPanel_WireOptions( Panel ) + Panel:ClearControls() + + Panel:Help("Hold Alt while spawning Wire entities\nto disable Weld and Nocollide All") + Panel:CheckBox("Allow Weld to World", "wire_tool_weldworld") + Panel:CheckBox("Nocollide All", "wire_tool_nocollide") + end + hook.Add("PopulateToolMenu","WireLib_WireOptions",function() + spawnmenu.AddToolMenuOption( "Wire", "Options", "WireOptions", "Tool Options", "", "", CreateCPanel_WireOptions, nil ) + end) +end + + +-- function used by TOOL.BuildCPanel +WireToolHelpers = {} + +if CLIENT then + -- gets the TOOL since TOOL.BuildCPanel isn't passed this var. wts >_< + function WireToolHelpers.GetTOOL(mode) + for _,wep in ipairs(LocalPlayer():GetWeapons()) do + if wep:GetClass() == "gmod_tool" then + return wep:GetToolObject(mode) + end + end + end + + -- makes the preset control for use cause we're lazy + function WireToolHelpers.MakePresetControl(panel, mode, folder) + if not mode or not panel then return end + local TOOL = WireToolHelpers.GetTOOL(mode) + if not TOOL then return end + local ctrl = vgui.Create( "ControlPresets", panel ) + ctrl:SetPreset(folder or mode) + if TOOL.ClientConVar then + local options = {} + for k, v in pairs(TOOL.ClientConVar) do + if k ~= "id" then + k = mode.."_"..k + options[k] = v + ctrl:AddConVar(k) + end + end + ctrl:AddOption("#Default", options) + end + panel:AddPanel( ctrl ) + end + + function WireToolHelpers.MakeModelSizer(panel, convar) + return panel:AddControl("ListBox", { + Label = "Model Size", + Options = { + ["normal"] = { [convar] = "" }, + ["mini"] = { [convar] = "mini" }, + ["nano"] = { [convar] = "nano" } + } + }) + end + + -- adds the neato model select control + function WireToolHelpers.MakeModelSel(panel, mode) + local TOOL = WireToolHelpers.GetTOOL(mode) + if not TOOL then return end + ModelPlug_AddToCPanel(panel, TOOL.short_name, TOOL.Mode, true) + end +end + +function WireToolHelpers.SetupSingleplayerClickHacks(TOOL) end -- empty stub outside of Singleplayer +if game.SinglePlayer() then -- wtfgarry + -- In Singleplayer, "Because its Predicted", LeftClick/RightClick/Reload don't fire Clientside. Lets work around that + if SERVER then + util.AddNetworkString("wire_singleplayer_tool_wtfgarry") + local function send_singleplayer_click(ply, funcname, toolname) + net.Start("wire_singleplayer_tool_wtfgarry") + net.WriteString(funcname) + net.WriteString(toolname) + net.Send(ply) + end + + function WireToolHelpers.SetupSingleplayerClickHacks(TOOL) + local originalLeftClick = TOOL.LeftClick + function TOOL:LeftClick(trace) + send_singleplayer_click(self:GetOwner(), "LeftClick", TOOL.Mode) + return originalLeftClick(self, trace) + end + local originalRightClick = TOOL.RightClick + function TOOL:RightClick(trace) + send_singleplayer_click(self:GetOwner(), "RightClick", TOOL.Mode) + return originalRightClick(self, trace) + end + local originalReload = TOOL.Reload + function TOOL:Reload(trace) + send_singleplayer_click(self:GetOwner(), "Reload", TOOL.Mode) + return originalReload(self, trace) + end + end + elseif CLIENT then + net.Receive( "wire_singleplayer_tool_wtfgarry", function(len) + local funcname = net.ReadString() + local toolname = net.ReadString() + local tool = WireToolHelpers.GetTOOL(toolname) + if not tool then return end + tool[funcname](tool, LocalPlayer():GetEyeTrace()) + end) + end +end + + +WireToolSetup = {} + +-- sets the ToolCategory for every wire tool made fallowing its call +function WireToolSetup.setCategory( s_cat, ... ) + WireToolSetup.cat = s_cat + + local categories = {...} + if #categories > 0 then + WireToolSetup.Wire_MultiCategories = categories + else + WireToolSetup.Wire_MultiCategories = nil + end +end + +-- Sets the icon for the current tool +function WireToolSetup.setToolMenuIcon( icon ) + if SERVER then return end + TOOL.Wire_ToolMenuIcon = icon +end + +-- makes a new TOOL +-- s_mode: Tool_mode, same as the old tool lua file name, minus the "wire_" part +-- s_name: Proper name for the tool +-- s_class: For tools that make a device. Should begin with "gmod_wire_". Can be nil if not using WireToolObj.LeftClick or WireToolSetup.BaseLang +-- f_toolmakeent: Server side function for making the tools device. Can be nil if not using WireToolObj.LeftClick +function WireToolSetup.open( s_mode, s_name, s_class, f_toolmakeent, s_pluralname ) + -- close the previous TOOL if not done so already + if TOOL then WireToolSetup.close() end + + -- make new TOOL object + TOOL = WireToolObj:Create() + + -- default vars, + TOOL.Mode = "wire_"..s_mode + TOOL.short_name = s_mode + TOOL.Category = WireToolSetup.cat + TOOL.Wire_MultiCategories = WireToolSetup.Wire_MultiCategories + TOOL.Name = s_name + TOOL.PluralName = s_pluralname + TOOL.WireClass = s_class + if f_toolmakeent then + TOOL.LeftClick_Make = f_toolmakeent + end + local info = debug.getinfo(2, "S") + if info then + TOOL.SourceFile = info.short_src + end +end + +-- closes and saves the open TOOL obj +function WireToolSetup.close() + TOOL:CreateConVars() + SWEP.Tool[TOOL.Mode] = TOOL + TOOL = nil +end + + +-- optional function to add the basic language for basic tools +function WireToolSetup.BaseLang() + if CLIENT then + language.Add( "undone_"..TOOL.WireClass, "Undone Wire "..TOOL.Name ) + if TOOL.PluralName then + language.Add( "Cleanup_"..TOOL.WireClass, "Wire "..TOOL.PluralName ) + language.Add( "Cleaned_"..TOOL.WireClass, "Cleaned Up Wire "..TOOL.PluralName ) + end + for _, info in pairs(TOOL.Information or {}) do + if info.text then + language.Add("Tool." .. TOOL.Mode .. "." .. info.name, info.text) + end + end + end + cleanup.Register(TOOL.WireClass) +end + +function WireToolSetup.SetupMax( i_limit, s_maxlimitname , s_warning ) + TOOL.MaxLimitName = s_maxlimitname or TOOL.WireClass:sub(6).."s" + s_warning = s_warning or "You've hit the Wire "..TOOL.PluralName.." limit!" + if CLIENT then + language.Add("SBoxLimit_"..TOOL.MaxLimitName, s_warning) + AddWireAdminMaxDevice(TOOL.PluralName, TOOL.MaxLimitName) + else + CreateConVar("sbox_max"..TOOL.MaxLimitName, i_limit) + end +end + +-- Sets up a tool with RightClick, Reload, and DrawHUD functions that link/unlink entities +-- The SENT should have ENT:LinkEnt(e), ENT:UnlinkEnt(e), and ENT:ClearEntities() +-- It should also send ENT.Marks to the client via WireLib.SendMarks(ent) +-- Pass it true to disable linking multiple entities (ie for Pod Controllers) +function WireToolSetup.SetupLinking(SingleLink, linkedname) + TOOL.SingleLink = SingleLink + linkedname = linkedname or "entity" + if CLIENT then + if TOOL.Information == nil or next(TOOL.Information) == nil then + TOOL.Information = { + { name = "left_0", stage = 0 }, + { name = "right_0", stage = 0 }, + { name = "reload_0", stage = 0 }, + { name = "right_1", stage = 1 }, + { name = "right_2", stage = 2 }, + } + if not SingleLink then + table.insert(TOOL.Information, { name = "info_1", stage = 1 }) + table.insert(TOOL.Information, { name = "info_2", stage = 2 }) + table.insert(TOOL.Information, { name = "reload_2", stage = 2 }) + end + end + + language.Add( "Tool."..TOOL.Mode..".left_0", "Create/Update "..TOOL.Name ) + language.Add( "Tool."..TOOL.Mode..".right_0", "Select a " .. TOOL.Name .. " to link" ) + language.Add( "Tool."..TOOL.Mode..".reload_0", "Unlink everything from a " .. TOOL.Name ) + language.Add( "Tool."..TOOL.Mode..".right_1", "Now select the " .. linkedname .. " to link to" ) + language.Add( "Tool."..TOOL.Mode..".right_2", "Now select the " .. linkedname .. " to unlink" ) + + if not SingleLink then + language.Add( "Tool."..TOOL.Mode..".info_1", "Hold shift to link to more") + language.Add( "Tool."..TOOL.Mode..".info_2", "Hold shift to unlink from more") + language.Add( "Tool."..TOOL.Mode..".reload_2", "Reload on the same controller again to clear all linked entities.") + end + + function TOOL:DrawHUD() + local trace = self:GetOwner():GetEyeTrace() + if self:CheckHitOwnClass(trace) and trace.Entity.Marks then + local markerpos = trace.Entity:GetPos():ToScreen() + for _, ent in pairs(trace.Entity.Marks) do + if IsValid(ent) then + local markpos = ent:GetPos():ToScreen() + surface.SetDrawColor( 255,255,100,255 ) + surface.DrawLine( markerpos.x, markerpos.y, markpos.x, markpos.y ) + end + end + end + end + end + + function TOOL:RightClick(trace) + if not trace.HitPos or not IsValid(trace.Entity) or trace.Entity:IsPlayer() then return false end + if CLIENT then return true end + + local ent = trace.Entity + if self:GetStage() == 0 then -- stage 0: right-clicking on our own class selects it + if self:CheckHitOwnClass(trace) then + self.Controller = ent + self:SetStage(1) + return true + else + return false + end + elseif self:GetStage() == 1 then -- stage 1: right-clicking on something links it + if not IsValid(self.Controller) then self:SetStage(0) return end + local ply = self:GetOwner() + local success, message = self.Controller:LinkEnt(ent) + if success then + if self.SingleLink or not ply:KeyDown(IN_SPEED) then self:SetStage(0) end + self.HasLinked = true + WireLib.AddNotify(ply, "Linked entity: " .. tostring(ent) .. " to the "..self.Name, NOTIFY_GENERIC, 5) + else + WireLib.AddNotify(ply, message or "That entity is already linked to the "..self.Name, NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP3) + end + return success + end + end + + function TOOL:Reload(trace) + if not trace.HitPos or not IsValid(trace.Entity) or trace.Entity:IsPlayer() then + self:SetStage(0) + return false + end + if CLIENT then return true end + local ent = trace.Entity + + if self:CheckHitOwnClass(trace) then -- regardless of stage, reloading on our own class clears it + local ply = self:GetOwner() + self:SetStage(0) + if ent.ClearEntities then + ent:ClearEntities() + WireLib.AddNotify(ply, "All entities unlinked from the "..self.Name, NOTIFY_GENERIC, 7) + else + ent:UnlinkEnt() + WireLib.AddNotify(ply, "Unlinked "..self.Name, NOTIFY_GENERIC, 5) + end + return true + elseif self:GetStage() == 1 then -- stage 1: reloading on something else unlinks it + local ply = self:GetOwner() + local success, message = self.Controller:UnlinkEnt(ent) + if success then + if not self:GetOwner():KeyDown(IN_SPEED) then self:SetStage(0) end + self.HasLinked = true + WireLib.AddNotify(ply, "Unlinked entity: " .. tostring(ent) .. " from the "..self.Name, NOTIFY_GENERIC, 5) + else + WireLib.AddNotify(ply, message or "That entity is not linked to the "..self.Name, NOTIFY_ERROR, 5, NOTIFYSOUND_DRIP3) + end + return success + end + end + + if not SingleLink then + function TOOL:Think() + if self.HasLinked then + if not self:GetOwner():KeyDown(IN_SPEED) then self:SetStage(0) end + if self:GetStage() == 0 then self.HasLinked = false end + end + WireToolObj.Think(self) -- Basic ghost + end + end +end + +LoadTools() diff --git a/garrysmod/addons/feature-wire/lua/wire/von.lua b/garrysmod/addons/feature-wire/lua/wire/von.lua new file mode 100644 index 0000000..cf8d28d --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/von.lua @@ -0,0 +1,814 @@ +--[[ vON 1.3.4 + + Copyright 2012-2014 Alexandru-Mihai Maftei + aka Vercas + + GitHub Repository: + https://github.com/vercas/vON + + 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 (Vercas) if you publish your work based on (and/or using) this. + + If you modify the code for any purpose, the above obligations still apply. + If you make any interesting modifications, try forking the GitHub repository instead. + + Instead of copying this code over for sharing, rather use the link: + https://github.com/vercas/vON/blob/master/von.lua + + The author may not be held responsible for any damage or losses directly or indirectly caused by + the use of vON. + + If you disagree with the above, don't use the code. + +----------------------------------------------------------------------------------------------------------------------------- + + Thanks to the following people for their contribution: + - Divran Suggested improvements for making the code quicker. + Suggested an excellent new way of deserializing strings. + Lead me to finding an extreme flaw in string parsing. + - pennerlord Provided some performance tests to help me improve the code. + - Chessnut Reported bug with handling of nil values when deserializing array components. + + - People who contributed on the GitHub repository by reporting bugs, posting fixes, etc. + +----------------------------------------------------------------------------------------------------------------------------- + + The vanilla types supported in this release of vON are: + - table + - number + - boolean + - string + - nil + + The Garry's Mod-specific types supported in this release are: + - Vector + - Angle + + Entities: + - Entity + - Vehicle + - Weapon + - NPC + - Player + - NextBot + + These are the types one would normally serialize. + +----------------------------------------------------------------------------------------------------------------------------- + + New in this version: + - Fixed addition of extra entity types. I messed up really badly. +--]] + + + +local _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable +local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next + + + +--[[ This section contains localized functions which (de)serialize + variables according to the types found. ]] + + + +-- This is kept away from the table for speed. +function d_findVariable(s, i, len, lastType, jobstate) + local i, c, typeRead, val = i or 1 + + -- Keep looping through the string. + while true do + -- Stop at the end. Throw an error. This function MUST NOT meet the end! + if i > len then + error("vON: Reached end of string, cannot form proper variable.") + end + + -- Cache the character. Nobody wants to look for the same character ten times. + c = sub(s, i, i) + + -- If it just read a type definition, then a variable HAS to come after it. + if typeRead then + -- Attempt to deserialize a variable of the freshly read type. + val, i = _deserialize[lastType](s, i, len, false, jobstate) + -- Return the value read, the index of the last processed character, and the type of the last read variable. + return val, i, lastType + + -- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store. + elseif c == "@" then + return nil, i, lastType + + -- $ means a table reference will follow - a number basically. + elseif c == "$" then + lastType = "table_reference" + typeRead = true + + -- n means a number will follow. Base 10... :C + elseif c == "n" then + lastType = "number" + typeRead = true + + -- b means boolean flags. + elseif c == "b" then + lastType = "boolean" + typeRead = true + + -- ' means the start of a string. + elseif c == "'" then + lastType = "string" + typeRead = true + + -- " means the start of a string prior to version 1.2.0. + elseif c == "\"" then + lastType = "oldstring" + typeRead = true + + -- { means the start of a table! + elseif c == "{" then + lastType = "table" + typeRead = true + + +--[[ Garry's Mod types go here ]] + + -- e means an entity ID will follow. + elseif c == "e" then + lastType = "Entity" + typeRead = true +--[[ + -- c means a vehicle ID will follow. + elseif c == "c" then + lastType = "Vehicle" + typeRead = true + + -- w means a weapon entity ID will follow. + elseif c == "w" then + lastType = "Weapon" + typeRead = true + + -- x means a NPC ID will follow. + elseif c == "x" then + lastType = "NPC" + typeRead = true +--]] + -- p means a player ID will follow. + -- Kept for backwards compatibility. + elseif c == "p" then + lastType = "Entity" + typeRead = true + + -- v means a vector will follow. 3 numbers. + elseif c == "v" then + lastType = "Vector" + typeRead = true + + -- a means an Euler angle will follow. 3 numbers. + elseif c == "a" then + lastType = "Angle" + typeRead = true + +--[[ Garry's Mod types end here ]] + + + -- If no type has been found, attempt to deserialize the last type read. + elseif lastType then + val, i = _deserialize[lastType](s, i, len, false, jobstate) + return val, i, lastType + + -- This will occur if the very first character in the vON code is wrong. + else + error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c) + end + + -- Move the pointer one step forward. + i = i + 1 + end +end + +-- This is kept away from the table for speed. +-- Yeah, ton of parameters. +function s_anyVariable(data, lastType, isNumeric, isKey, isLast, jobstate) + local tp = type(data) + + if jobstate[1] and jobstate[2][data] then + tp = "table_reference" + end + + -- Basically, if the type changes. + if lastType ~= tp then + -- Remember the new type. Caching the type is useless. + lastType = tp + + if _serialize[lastType] then + -- Return the serialized data and the (new) last type. + -- The second argument, which is true now, means that the data type was just changed. + return _serialize[lastType](data, true, isNumeric, isKey, isLast, false, jobstate), lastType + else + error("vON: No serializer defined for type \"" .. lastType .. "\"!") + end + end + + -- Otherwise, simply serialize the data. + return _serialize[lastType](data, false, isNumeric, isKey, isLast, false, jobstate), lastType +end + + + +--[[ This section contains the tables with the functions necessary + for decoding basic Lua data types. ]] + + + +_deserialize = { +-- Well, tables are very loose... +-- The first table doesn't have to begin and end with { and }. + ["table"] = function(s, i, len, unnecessaryEnd, jobstate) + local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1 + -- Locals, locals, locals, locals, locals, locals, locals, locals and locals. + + if sub(s, i, i) == "#" then + local e = find(s, "#", i + 2, true) + + if e then + local id = tonumber(sub(s, i + 1, e - 1)) + + if id then + if jobstate[1][id] and not jobstate[2] then + error("vON: There already is a table of reference #" .. id .. "! Missing an option maybe?") + end + + jobstate[1][id] = ret + + i = e + 1 + else + error("vON: Malformed table! Reference ID starting at char #" .. i .. " doesn't contain a number!") + end + else + error("vON: Malformed table! Cannot find end of reference ID start at char #" .. i .. "!") + end + end + + -- Keep looping. + while true do + -- Until it meets the end. + if i > len then + -- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example. + if unnecessaryEnd then + return ret, i + + -- Otherwise, the data has to be damaged. + else + error("vON: Reached end of string, incomplete table definition.") + end + end + + -- Cache the character. + c = sub(s, i, i) + --print(i, "table char:", c, tostring(unnecessaryEnd)) + + -- If it's the end of a table definition, return. + if c == "}" then + return ret, i + + -- If it's the component separator, switch to key:value pairs. + elseif c == "~" then + numeric = false + + elseif c == ";" then + -- Lol, nothing! + -- Remenant from numbers, for faster parsing. + + -- OK, now, if it's on the numeric component, simply add everything encountered. + elseif numeric then + -- Find a variable and it's value + val, i, lastType = d_findVariable(s, i, len, lastType, jobstate) + -- Add it to the table. + ret[ind] = val + + ind = ind + 1 + + -- Otherwise, if it's the key:value component... + else + -- If a value is expected... + if expectValue then + -- Read it. + val, i, lastType = d_findVariable(s, i, len, lastType, jobstate) + -- Add it? + ret[key] = val + -- Clean up. + expectValue, key = false, nil + + -- If it's the separator... + elseif c == ":" then + -- Expect a value next. + expectValue = true + + -- But, if there's a key read already... + elseif key then + -- Then this is malformed. + error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c) + + -- Otherwise the key will be read. + else + -- I love multi-return and multi-assignement. + key, i, lastType = d_findVariable(s, i, len, lastType, jobstate) + end + end + + i = i + 1 + end + + return nil, i + end, + +-- Just a number which points to a table. + ["table_reference"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + local n = tonumber(sub(s, i, a - 1)) + + if n then + return jobstate[1][n] or error("vON: Table reference does not point to a (yet) known table!"), a - 1 + else + error("vON: Table reference definition does not contain a valid number!") + end + end + + -- Using %D breaks identification of negative numbers. :( + + error("vON: Number definition started... Found no end.") + end, + + +-- Numbers are weakly defined. +-- The declaration is not very explicit. It'll do it's best to parse the number. +-- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards. + ["number"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + return tonumber(sub(s, i, a - 1)) or error("vON: Number definition does not contain a valid number!"), a - 1 + end + + -- Using %D breaks identification of negative numbers. :( + + error("vON: Number definition started... Found no end.") + end, + + +-- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false. +-- Any other attempt at boolean declaration will result in a failure. + ["boolean"] = function(s, i, len, unnecessaryEnd, jobstate) + local c = sub(s,i,i) + -- Only one character is needed. + + -- If it's 1, then it's true + if c == "1" then + return true, i + + -- If it's 0, then it's false. + elseif c == "0" then + return false, i + end + + -- Any other supposely "boolean" is just a sign of malformed data. + error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c) + end, + + +-- Strings prior to 1.2.0 + ["oldstring"] = function(s, i, len, unnecessaryEnd, jobstate) + local res, i, a = "", i or 1 + -- Locals, locals, locals, locals + + while true do + a = find(s, "\"", i, true) + + if a then + if sub(s, a - 1, a - 1) == "\\" then + res = res .. sub(s, i, a - 2) .. "\"" + i = a + 1 + else + return res .. sub(s, i, a - 2), a + end + else + error("vON: Old string definition started... Found no end.") + end + end + end, + +-- Strings after 1.2.0 + ["string"] = function(s, i, len, unnecessaryEnd, jobstate) + local res, i, a = "", i or 1 + -- Locals, locals, locals, locals + + while true do + a = find(s, "\"", i, true) + + if a then + if sub(s, a - 1, a - 1) == "\\" then + res = res .. sub(s, i, a - 2) .. "\"" + i = a + 1 + else + return res .. sub(s, i, a - 1), a + end + else + error("vON: String definition started... Found no end.") + end + end + end, +} + + + +_serialize = { +-- Uh. Nothing to comment. +-- Ton of parameters. +-- Makes stuff faster than simply passing it around in locals. +-- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY. + ["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + --print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first))) + + local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0 + -- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals. + + -- First thing to be done is separate the numeric and key:value components of the given table in two tables. + -- pairs(data) is slower than next, data as far as my tests tell me. + for k, v in next, data do + -- Skip the numeric keyz. + if type(k) ~= "number" or k < 1 or k > len or (k % 1 ~= 0) then -- k % 1 == 0 is, as proven by personal benchmarks, + keyvals[#keyvals + 1] = k -- the quickest way to check if a number is an integer. + end -- k % 1 ~= 0 is the fastest way to check if a number + end -- is NOT an integer. > is proven slower. + + keyvalsLen = #keyvals + + -- Main chunk - no initial character. + if not first then + result[#result + 1] = "{" + end + + if jobstate[1] and jobstate[1][data] then + if jobstate[2][data] then + error("vON: Table #" .. jobstate[1][data] .. " written twice..?") + end + + result[#result + 1] = "#" + result[#result + 1] = jobstate[1][data] + result[#result + 1] = "#" + + jobstate[2][data] = true + end + + -- Add numeric values. + if len > 0 then + for i = 1, len do + val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, jobstate) + result[#result + 1] = val + end + end + + -- If there are key:value pairs. + if keyvalsLen > 0 then + -- Insert delimiter. + result[#result + 1] = "~" + + -- Insert key:value pairs. + for _i = 1, keyvalsLen do + keyvalsProgress = keyvalsProgress + 1 + + val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, jobstate) + + result[#result + 1] = val..":" + + val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, jobstate) + + result[#result + 1] = val + end + end + + -- Main chunk needs no ending character. + if not first then + result[#result + 1] = "}" + end + + return concat(result) + end, + +-- Number which points to table. + ["table_reference"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + data = jobstate[1][data] + + -- If a number hasn't been written before, add the type prefix. + if mustInitiate then + if isKey or isLast then + return "$"..data + else + return "$"..data..";" + end + end + + if isKey or isLast then + return data + else + return data..";" + end + end, + + +-- Normal concatenations is a lot faster with small strings than table.concat +-- Also, not so branched-ish. + ["number"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + -- If a number hasn't been written before, add the type prefix. + if mustInitiate then + if isKey or isLast then + return "n"..data + else + return "n"..data..";" + end + end + + if isKey or isLast then + return data + else + return data..";" + end + end, + + +-- I hope gsub is fast enough. + ["string"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + if sub(data, #data, #data) == "\\" then -- Hah, old strings fix this best. + return "\"" .. gsub(data, "\"", "\\\"") .. "v\"" + end + + return "'" .. gsub(data, "\"", "\\\"") .. "\"" + end, + + +-- Fastest. + ["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + -- Prefix if we must. + if mustInitiate then + if data then + return "b1" + else + return "b0" + end + end + + if data then + return "1" + else + return "0" + end + end, + + +-- Fastest. + ["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + return "@" + end, +} + + + +--[[ This section handles additions necessary for Garry's Mod. ]] + + + +if gmod then -- Luckily, a specific table named after the game is present in Garry's Mod. + local Entity = Entity + + + + local extra_deserialize = { +-- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway. +-- Exactly like a number definition, except it begins with "e". + ["Entity"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a = i or 1 + -- Locals, locals, locals, locals + + a = find(s, "[;:}~]", i) + + if a then + return Entity(tonumber(sub(s, i, a - 1))), a - 1 + end + + error("vON: Entity ID definition started... Found no end.") + end, + + +-- A pair of 3 numbers separated by a comma (,). + ["Vector"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a, x, y, z = i or 1 + -- Locals, locals, locals, locals + + a = find(s, ",", i) + + if a then + x = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, ",", i) + + if a then + y = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, "[;:}~]", i) + + if a then + z = tonumber(sub(s, i, a - 1)) + end + + if x and y and z then + return Vector(x, y, z), a - 1 + end + + error("vON: Vector definition started... Found no end.") + end, + + +-- A pair of 3 numbers separated by a comma (,). + ["Angle"] = function(s, i, len, unnecessaryEnd, jobstate) + local i, a, p, y, r = i or 1 + -- Locals, locals, locals, locals + + a = find(s, ",", i) + + if a then + p = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, ",", i) + + if a then + y = tonumber(sub(s, i, a - 1)) + i = a + 1 + end + + a = find(s, "[;:}~]", i) + + if a then + r = tonumber(sub(s, i, a - 1)) + end + + if p and y and r then + return Angle(p, y, r), a - 1 + end + + error("vON: Angle definition started... Found no end.") + end, + } + + local extra_serialize = { +-- Same as numbers, except they start with "e" instead of "n". + ["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + data = data:EntIndex() + + if mustInitiate then + if isKey or isLast then + return "e"..data + else + return "e"..data..";" + end + end + + if isKey or isLast then + return data + else + return data..";" + end + end, + + +-- 3 numbers separated by a comma. + ["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + if mustInitiate then + if isKey or isLast then + return "v"..data.x..","..data.y..","..data.z + else + return "v"..data.x..","..data.y..","..data.z..";" + end + end + + if isKey or isLast then + return data.x..","..data.y..","..data.z + else + return data.x..","..data.y..","..data.z..";" + end + end, + + +-- 3 numbers separated by a comma. + ["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) + if mustInitiate then + if isKey or isLast then + return "a"..data.p..","..data.y..","..data.r + else + return "a"..data.p..","..data.y..","..data.r..";" + end + end + + if isKey or isLast then + return data.p..","..data.y..","..data.r + else + return data.p..","..data.y..","..data.r..";" + end + end, + } + + for k, v in pairs(extra_serialize) do + _serialize[k] = v + end + + for k, v in pairs(extra_deserialize) do + _deserialize[k] = v + end + + local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" } + + for i = 1, #extraEntityTypes do + _serialize[extraEntityTypes[i]] = _serialize.Entity + end +end + + + +--[[ This section exposes the functions of the library. ]] + + + +local function checkTableForRecursion(tab, checked, assoc) + local id = checked.ID + + if not checked[tab] and not assoc[tab] then + assoc[tab] = id + checked.ID = id + 1 + else + checked[tab] = true + end + + for k, v in pairs(tab) do + if type(k) == "table" and not checked[k] then + checkTableForRecursion(k, checked, assoc) + end + + if type(v) == "table" and not checked[v] then + checkTableForRecursion(v, checked, assoc) + end + end +end + + + +local _s_table = _serialize.table +local _d_table = _deserialize.table + +_d_meta = { + __call = function(self, str, allowIdRewriting) + if type(str) == "string" then + return _d_table(str, nil, #str, true, {{}, allowIdRewriting}) + end + + error("vON: You must deserialize a string, not a "..type(str)) + end +} +_s_meta = { + __call = function(self, data, checkRecursion) + if type(data) == "table" then + if checkRecursion then + local assoc, checked = {}, {ID = 1} + + checkTableForRecursion(data, checked, assoc) + + return _s_table(data, nil, nil, nil, nil, true, {assoc, {}}) + end + + return _s_table(data, nil, nil, nil, nil, true, {false}) + end + + error("vON: You must serialize a table, not a "..type(data)) + end +} + + + +WireLib.von = { + version = "1.3.4", + versionNumber = 1003004, -- Reserving 3 digits per version component. + + deserialize = setmetatable(_deserialize,_d_meta), + serialize = setmetatable(_serialize,_s_meta) +} diff --git a/garrysmod/addons/feature-wire/lua/wire/wire_paths.lua b/garrysmod/addons/feature-wire/lua/wire/wire_paths.lua new file mode 100644 index 0000000..74f4761 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/wire_paths.lua @@ -0,0 +1,108 @@ +-- wire_paths.lua +-- +-- This file implements syncing of wire paths, which are the visual +-- component of wires. +-- +-- Conceptually, a wire path has a material, a color, and a non-zero width, as +-- well as as a non-empty polyline along the wire. (Each point in the line +-- has both a parent entity, and a local offset from that entity.) +-- + +if not WireLib then return end + +if CLIENT then + net.Receive("WireLib.Paths.TransmitPath", function(length) + local path = { + Path = {} + } + path.Entity = net.ReadEntity() + if not path.Entity:IsValid() then return end + path.Name = net.ReadString() + path.Width = net.ReadFloat() + if path.Width<=0 then + if path.Entity.WirePaths then + path.Entity.WirePaths[path.Name] = nil + end + return + end + path.StartPos = net.ReadVector() + path.Material = net.ReadString() + path.Color = net.ReadColor() + + local num_points = net.ReadUInt(16) + for i = 1, num_points do + path.Path[i] = { Entity = net.ReadEntity(), Pos = net.ReadVector() } + end + + if path.Entity.WirePaths == nil then path.Entity.WirePaths = {} end + path.Entity.WirePaths[path.Name] = path + + end) + return +end + +WireLib.Paths = {} +local transmit_queues = setmetatable({}, { __index = function(t,p) t[p] = {} return t[p] end }) +util.AddNetworkString("WireLib.Paths.RequestPaths") +util.AddNetworkString("WireLib.Paths.TransmitPath") + +net.Receive("WireLib.Paths.RequestPaths", function(length, ply) + local ent = net.ReadEntity() + if ent:IsValid() and ent.Inputs then + for name, input in pairs(ent.Inputs) do + if input.Src then + WireLib.Paths.Add(input, ply) + end + end + end +end) + +local function TransmitPath(input, ply) + net.Start("WireLib.Paths.TransmitPath") + local color = input.Color + net.WriteEntity(input.Entity) + net.WriteString(input.Name) + if not input.Src or input.Width<=0 then + net.WriteFloat(0) + else + net.WriteFloat(input.Width) + net.WriteVector(input.StartPos) + net.WriteString(input.Material) + net.WriteColor(Color(color.r or 255, color.g or 255, color.b or 255, color.a or 255)) + net.WriteUInt(#input.Path, 16) + for _, point in ipairs(input.Path) do + net.WriteEntity(point.Entity) + net.WriteVector(point.Pos) + end + end + net.Send(ply) +end + +local function ProcessQueue() + for ply, queue in pairs(transmit_queues) do + if not ply:IsValid() then transmit_queues[ply] = nil continue end + local nextinqueue = table.remove(queue, 1) + if nextinqueue then + TransmitPath(nextinqueue, ply) + else + transmit_queues[ply] = nil + end + end + if not next(transmit_queues) then + timer.Remove("WireLib.Paths.ProcessQueue") + end +end + +-- Add a path to every player's transmit queue +function WireLib.Paths.Add(input, ply) + if ply then + table.insert(transmit_queues[ply], input) + else + for _, player in pairs(player.GetAll()) do + table.insert(transmit_queues[player], input) + end + end + if not timer.Exists("WireLib.Paths.ProcessQueue") then + timer.Create("WireLib.Paths.ProcessQueue", 0, 0, ProcessQueue) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/wiregates.lua b/garrysmod/addons/feature-wire/lua/wire/wiregates.lua new file mode 100644 index 0000000..3adf0f2 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/wiregates.lua @@ -0,0 +1,57 @@ +--[[ + Gate Loader + + Define gate behavior in gates/*.lua. +]] + +-- This separate table makes __newindex get called every time a gate is added, even if that gate was already added previously +-- This fixes the bug where duplicate gate entries would cause an error +local gates = {} + +local gamt +gamt = { + curcat = "DEFAULT", + __newindex = function(t,k,v) + if not v.group then + v.group = gamt.curcat + end + rawset(gates,k,v) + end, + __index = function(t,k) + return rawget(gates,k) + end, + __call = function(t,s) --call the table to set a default category + gamt.curcat = s or "DEFAULT" + end +} + +function LoadWireGates() + GateActions = {} + setmetatable(GateActions,gamt) + local entries = file.Find( "wire/gates/*.lua", "LUA" ) + for _,v in pairs(entries) do + include("gates/"..v) + if (SERVER) then AddCSLuaFile("gates/"..v) end + end + GateActions = gates + + WireGatesSorted = {} + for name,gate in pairs(GateActions) do + if not WireGatesSorted[gate.group] then + WireGatesSorted[gate.group] = {} + end + WireGatesSorted[gate.group][name] = gate + end +end +LoadWireGates() + +local banned_categories_convar = CreateConVar("wire_banned_gate_categories", "", {FCVAR_ARCHIVE, FCVAR_REPLICATED, FCVAR_NOTIFY}, "For multiple categories, separate them with commas. If you change this, existing gates won't be affected.") +local function UpdateBannedGates(reload_spawnmenu) + local as_list = string.Explode(",", banned_categories_convar:GetString()) + for _,v in pairs(GateActions) do + v.is_banned = table.HasValue(as_list, v.group) + end + if CLIENT and reload_spawnmenu then RunConsoleCommand("spawnmenu_reload") end +end +cvars.AddChangeCallback("wire_banned_gate_categories", function() UpdateBannedGates(true) end, "UpdateBannedGates") +UpdateBannedGates(false) diff --git a/garrysmod/addons/feature-wire/lua/wire/wiremonitors.lua b/garrysmod/addons/feature-wire/lua/wire/wiremonitors.lua new file mode 100644 index 0000000..242dc84 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/wiremonitors.lua @@ -0,0 +1,232 @@ +WireGPU_Monitors = {} + +function WireGPU_AddMonitor(name,model,tof,tou,tor,trs,x1,x2,y1,y2,rot,translucent) + if not rot then + rot = Angle(0,90,90) + elseif !isangle(rot) then + rot = Angle(0,90,0) + end + local RatioX = (y2-y1)/(x2-x1) + + local monitor = { + Name = name, + offset = Vector(tof, -tor, tou), + RS = trs, + RatioX = RatioX, + + x1 = x1, + x2 = x2, + y1 = y1, + y2 = y2, + + z = tof, + + rot = rot, + + translucent = translucent, + } + WireGPU_Monitors[model] = monitor +end + +local function mindimension(vec) + -- add a bias to make the screen appear on the front face of a cube + if vec.x-0.002 < vec.y then + -- x < y + -- another bit of bias, otherwise it'd appear on the left face. + if vec.x-0.002 < vec.z then + -- x < y, x < z + return Vector(1,0,0) + else + -- x < y, z<=x -> z < y + return Vector(0,0,1) + end + else + -- y <= x + if vec.y < vec.z then + -- y <= x, y < z + return Vector(0,1,0) + else + -- y <= x, z <= y -> z <= x + return Vector(0,0,1) + end + end +end + +local function maxdimension(vec) + -- add a small bias, so squared screens draw text in the correct orientation (y+/down = forward axis) + if vec.x-0.002 > vec.y then + -- x > y + if vec.x > vec.z then + -- x > y, x > z + return Vector(1,0,0) + else + -- x > y, z>=x -> z > y + return Vector(0,0,1) + end + else + -- y >= x + -- more bias, this time to give the front face the correct orientation + if vec.y+0.002 > vec.z then + -- y >= x, y > z + return Vector(0,1,0) + else + -- y >= x, z >= y -> z >= x + return Vector(0,0,1) + end + end +end + +function WireGPU_FromBox(name, model, boxmin, boxmax, translucent) + local dim = boxmax-boxmin + local mindim, maxdim = mindimension(dim), maxdimension(dim) + + -- get an angle with up=mindim + local rot = mindim:Angle()+Angle(90,0,0) + + -- make sure forward=maxdim + if math.abs(maxdim:Dot(rot:Forward())) < 0.01 then + rot:RotateAroundAxis(mindim, 90) + end + + -- unrotate boxmin/max + local box1 = WorldToLocal(boxmin, Angle(0,0,0), Vector(0,0,0), rot) + local box2 = WorldToLocal(boxmax, Angle(0,0,0), Vector(0,0,0), rot) + + -- sort boxmin/max + local boxmin = Vector(math.min(box1.x,box2.x), math.min(box1.y,box2.y), math.min(box1.z,box2.z)) + local boxmax = Vector(math.max(box1.x,box2.x), math.max(box1.y,box2.y), math.max(box1.z,box2.z)) + + -- make a new gpu screen + return WireGPU_FromBox_Helper(name, model, boxmin, boxmax, rot, translucent) +end + +-- boxmin/boxmax have to be already rotated +function WireGPU_FromBox_Helper(name, model, boxmin, boxmax, rot, translucent) + local boxcenter = (boxmin+boxmax)*0.5 + local offset = Vector(boxcenter.x,boxcenter.y,boxmax.z+0.2) + + boxmin = boxmin - offset + boxmax = boxmax - offset + + local x1, y1 = boxmin.x, boxmin.y + local x2, y2 = boxmax.x, boxmax.y + + offset:Rotate(rot) + + local monitor = { + Name = name, + offset = offset, + RS = (y2-y1)/512, + RatioX = (y2-y1)/(x2-x1), + + x1 = x1, + x2 = x2, + y1 = y1, + y2 = y2, + + z = offset.z, + + rot = rot, + + translucent = translucent, + } + + WireGPU_Monitors[model] = monitor + return monitor +end + +function WireGPU_FromRotatedBox(name, model, box1, box2, box3, box4, rot, translucent) + if isvector(rot) then + rot = Vector:Angle() + end + + --local boxvectors = { box1, box2, box3, box4 } + + local box1 = WorldToLocal(box1, Angle(0,0,0), Vector(0,0,0), rot) + local box2 = WorldToLocal(box2, Angle(0,0,0), Vector(0,0,0), rot) + local box3 = WorldToLocal(box3, Angle(0,0,0), Vector(0,0,0), rot) + local box4 = WorldToLocal(box4, Angle(0,0,0), Vector(0,0,0), rot) + + local boxmin = Vector( + math.min(box1.x,box2.x,box3.x,box4.x), + math.min(box1.y,box2.y,box3.y,box4.y), + math.min(box1.z,box2.z,box3.z,box4.z) + ) + local boxmax = Vector( + math.max(box1.x,box2.x,box3.x,box4.x), + math.max(box1.y,box2.y,box3.y,box4.y), + math.max(box1.z,box2.z,box3.z,box4.z) + ) + print(boxmin, boxmax, rot) + return WireGPU_FromBox_Helper(name, model, boxmin, boxmax, rot, translucent) +end + +WireGPU_FromBox_Helper("Workspace 002", "models/props_lab/workspace002.mdl", Vector(-20, 49, -34), Vector(16.2, 84, -30.5), Angle(0, 133.34, 59.683)) + +-- Offset front, offset up, offset right, resolution/scale OF OU OR SCALE LOWX HIGHX LOWY HIGHY ROTATE90 +WireGPU_AddMonitor("Small TV", "models/props_lab/monitor01b.mdl", 6.53, 0.45 , 1.0, 0.0185, -5.535 , 3.5 , -4.1 , 5.091 ) +WireGPU_AddMonitor("Monitor Small", "models/kobilica/wiremonitorsmall.mdl", 0.3 , 5.0 , 0 , 0.0175, -4.4 , 4.5 , 0.6 , 9.5 ) +WireGPU_AddMonitor("LCD Monitor (4:3)", "models/props/cs_office/computer_monitor.mdl", 3.3 , 16.7 , 0 , 0.031 , -10.5 , 10.5 , 8.6 , 24.7 ) +WireGPU_AddMonitor("Monitor Big", "models/kobilica/wiremonitorbig.mdl", 0.2 , 13 , 0 , 0.045 , -11.5 , 11.6 , 1.6 , 24.5 ) +WireGPU_AddMonitor("Plasma TV (4:3)", "models/blacknecro/tv_plasma_4_3.mdl", 0.1 , -0.5 , 0 , 0.082 , -27.87 , 27.87 , -20.93 , 20.93 ) +WireGPU_AddMonitor("Plasma TV (16:10)", "models/props/cs_office/tv_plasma.mdl", 6.1 , 18.93, 0 , 0.065 , -28.5 , 28.5 , 2 , 36 ) +WireGPU_AddMonitor("Billboard", "models/props/cs_assault/billboard.mdl", 1 , 0 , 0 , 0.23 , -110.512, 110.512, -57.647, 57.647) + +WireGPU_AddMonitor("Cube 1x1x1", "models/hunter/blocks/cube1x1x1.mdl", 24 , 0 , 0 , 0.09 , -48 , 48 , -48 , 48 ) +WireGPU_AddMonitor("Panel 1x1", "models/hunter/plates/plate1x1.mdl", 0 , 1.7 , 0 , 0.09 , -48 , 48 , -48 , 48 , true) +WireGPU_AddMonitor("Panel 2x2", "models/hunter/plates/plate2x2.mdl", 0 , 1.7 , 0 , 0.182 , -48 , 48 , -48 , 48 , true) +WireGPU_AddMonitor("Panel 0.5x0.5", "models/hunter/plates/plate05x05.mdl", 0 , 1.7 , 0 , 0.045 , -48 , 48 , -48 , 48 , true) + +WireGPU_AddMonitor("Tray", "models/props/cs_militia/reload_bullet_tray.mdl", 0 , 0.8 , 0 , 0.009 , 0 , 100 , 0 , 60 , true) + +-- Offset front, offset up, offset right, resolution/scale OF OU OR SCALE LOWX HIGHX LOWY HIGHY ROTATE90 +--WireGPU_AddMonitor("LED Board (1:1)", "models/blacknecro/ledboard60.mdl", 6.1, 18.5 , 11 , 0.065 , -60 , 60 , -60 , 60 ) -- broken + +WireGPU_FromBox("TF2 Red billboard", "models/props_mining/billboard001.mdl", Vector(0,-168,0), Vector(3,168,192), false) +WireGPU_FromBox("TF2 Red vs Blue billboard", "models/props_mining/billboard002.mdl", Vector(0,-306,96), Vector(3,306,288), false) + + +-- transparent screens +WireGPU_AddMonitor("Window", "models/props_phx/construct/windows/window1x1.mdl", 0, 1.7, 0, 0.07, -46, 46, -46, 46, true, true) + +--[[ +models/props_c17/tv_monitor01.mdl +models/props_wasteland/controlroom_monitor001b.mdl +models/props/cs_militia/television_console01.mdl +models/props/cs_militia/tv_console.mdl +models/props_silo/silo_launchroom_monitor.mdl +models/props_bts/glados_screenborder_curve.mdl +models/props_spytech/tv001.mdl +models/props_lab/monitor02.mdl +models/props_lab/monitor01a.mdl + +workspaces: +models/props_lab/workspace003.mdl +models/props_lab/securitybank.mdl + +too curvy? +models/props_spytech/computer_screen_bank.mdl +]] + +local function fallback(self, model) + local ent + local entities = ents.GetAll() -- tried to use FindByModel, but it didn't work - don't know why + for i=1,#entities do + local e = entities[i] + if e:GetModel() == model and + e:GetClass() ~= "class C_BaseFlex" and -- don't include adv dupe 2 ghosts + e:GetClass() ~= "gmod_ghost" then -- don't include adv dupe 1 ghosts + ent = e + break + end + end + if not ent then return nil end + + local gap = Vector(0.25,0.25,0.25) + local boxmin = ent:OBBMins()+gap + local boxmax = ent:OBBMaxs()-gap + + return WireGPU_FromBox("Auto: "..model:match("([^/]*)$"), model, boxmin, boxmax, false) +end + +setmetatable(WireGPU_Monitors, { __index = fallback }) diff --git a/garrysmod/addons/feature-wire/lua/wire/wireshared.lua b/garrysmod/addons/feature-wire/lua/wire/wireshared.lua new file mode 100644 index 0000000..f634ddf --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/wireshared.lua @@ -0,0 +1,1207 @@ +WireLib = WireLib or {} + +local pairs = pairs +local setmetatable = setmetatable +local rawget = rawget +local next = next +local IsValid = IsValid +local LocalPlayer = LocalPlayer +local Entity = Entity + +local string = string +local hook = hook + +-- extra table functions + +-- Returns a noniterable version of tbl. So indexing still works, but pairs(tbl) won't find anything +-- Useful for hiding entity lookup tables, since Garrydupe uses util.TableToJSON, which crashes on tables with entity keys +function table.MakeNonIterable(tbl) -- luacheck: ignore + return setmetatable({}, { __index = tbl, __setindex = tbl}) +end + +-- Compacts an array by rejecting entries according to cb. +function table.Compact(tbl, cb, n) -- luacheck: ignore + n = n or #tbl + local cpos = 1 + for i = 1, n do + if cb(tbl[i]) then + tbl[cpos] = tbl[i] + cpos = cpos + 1 + end + end + + while (cpos <= n) do + tbl[cpos] = nil + cpos = cpos + 1 + end +end + +-- I don't even know if I need this one. +-- HUD indicator needs this one +function table.MakeSortedKeys(tbl) -- luacheck: ignore + local result = {} + + for k,_ in pairs(tbl) do table.insert(result, k) end + table.sort(result) + + return result +end + + +function string.GetNormalizedFilepath( path ) -- luacheck: ignore + local null = string.find(path, "\x00", 1, true) + if null then path = string.sub(path, 1, null-1) end + + local tbl = string.Explode( "[/\\]+", path, true ) + local i = 1 + while i <= #tbl do + if tbl[i] == "." or tbl[i]=="" then + table.remove(tbl, i) + elseif tbl[i] == ".." then + table.remove(tbl, i) + if i>1 then + i = i - 1 + table.remove(tbl, i) + end + else + i = i + 1 + end + end + return table.concat(tbl, "/") +end + +-- works like pairs() except that it iterates sorted by keys. +-- criterion is optional and should be a function(a,b) returning whether a is less than b. (same as table.sort's criterions) +function pairs_sortkeys(tbl, criterion) + local tmp = {} + for k, _ in pairs(tbl) do table.insert(tmp,k) end + table.sort(tmp, criterion) + + local iter, state, index, k = ipairs(tmp) + return function() + index,k = iter(state, index) + if index == nil then return nil end + return k,tbl[k] + end +end + +-- sorts by values +function pairs_sortvalues(tbl, criterion) + local crit = criterion and + function(a,b) + return criterion(tbl[a],tbl[b]) + end + or + function(a,b) + return tbl[a] < tbl[b] + end + + local tmp = {} + tbl = tbl or {} + for k, _ in pairs(tbl) do table.insert(tmp,k) end + table.sort(tmp, crit) + + local iter, state, index, k = ipairs(tmp) + return function() + index,k = iter(state, index) + if index == nil then return nil end + return k,tbl[k] + end +end + +--- Iterates over a table like `pairs`, but also removes each field from the +--- table as it does so. Adding to the table while iterating is allowed, and +--- each added entry will be consumed in some future iteration. +function pairs_consume(table) + return function() + local k, v = next(table) + if k then table[k] = nil return k, v end + end +end + +-- like ipairs, except it maps the value with mapfunction before returning. +function ipairs_map(tbl, mapfunction) + local iter, state, k = ipairs(tbl) + return function(state, k) + local v + k,v = iter(state, k) + if k == nil then return nil end + return k,mapfunction(v) + end, state, k +end + +-- like pairs, except it maps the value with mapfunction before returning. +function pairs_map(tbl, mapfunction) + local iter, state, k = pairs(tbl) + return function(state, k) + local v + k,v = iter(state, k) + if k == nil then return nil end + return k,mapfunction(v) + end, state, k +end + +-- end extra table functions + +function WireLib.dummytrace(ent) + local pos = ent:GetPos() + return { + FractionLeftSolid = 0, + HitNonWorld = true, + Fraction = 0, + Entity = ent, + HitPos = pos, + HitNormal = Vector(0,0,0), + HitBox = 0, + Normal = Vector(1,0,0), + Hit = true, + HitGroup = 0, + MatType = 0, + StartPos = pos, + PhysicsBone = 0, + WorldToLocal = Vector(0,0,0), + } +end + +local table = table +local pairs_sortvalues = pairs_sortvalues +local ipairs_map = ipairs_map + +-------------------------------------------------------------------------------- + +do -- containers + local function new(metatable, ...) + local tbl = {} + setmetatable(tbl, metatable) + local init = metatable.Initialize + if init then init(tbl, ...) end + return tbl + end + + local function newclass(container_name) + local meta = { new = new } + meta.__index = meta + WireLib.containers[container_name] = meta + return meta + end + + WireLib.containers = { new = new, newclass = newclass } + + do -- class autocleanup + local autocleanup = newclass("autocleanup") + + function autocleanup:Initialize(depth, parent, parentindex) + rawset(self, "depth", depth or 0) + rawset(self, "parent", parent) + rawset(self, "parentindex", parentindex) + rawset(self, "data", {}) + end + + function autocleanup:__index(index) + local data = rawget(self, "data") + + local element = data[index] + if element then return element end + + local depth = rawget(self, "depth") + if depth == 0 then return nil end + element = new(autocleanup, depth-1, self, index) + + return element + end + + function autocleanup:__newindex(index, value) + local data = rawget(self, "data") + local parent = rawget(self, "parent") + local parentindex = rawget(self, "parentindex") + + if value ~= nil and not next(data) and parent then parent[parentindex] = self end + data[index] = value + if value == nil and not next(data) and parent then parent[parentindex] = nil end + end + + function autocleanup:__pairs() + local data = rawget(self, "data") + + return pairs(data) + end + + pairs_ac = autocleanup.__pairs + end -- class autocleanup +end -- containers + +-------------------------------------------------------------------------------- + +--[[ wire_addnotify: send notifications to the client + WireLib.AddNotify([ply, ]Message, Type, Duration[, Sound]) + If ply is left out, the notification is sent to everyone. If Sound is left out, no sound is played. + On the client, only the local player can be notified. +]] +do + -- The following sounds can be used: + NOTIFYSOUND_NONE = 0 -- optional, default + NOTIFYSOUND_DRIP1 = 1 + NOTIFYSOUND_DRIP2 = 2 + NOTIFYSOUND_DRIP3 = 3 + NOTIFYSOUND_DRIP4 = 4 + NOTIFYSOUND_DRIP5 = 5 + NOTIFYSOUND_ERROR1 = 6 + NOTIFYSOUND_CONFIRM1 = 7 + NOTIFYSOUND_CONFIRM2 = 8 + NOTIFYSOUND_CONFIRM3 = 9 + NOTIFYSOUND_CONFIRM4 = 10 + + if CLIENT then + + local sounds = { + [NOTIFYSOUND_DRIP1 ] = "ambient/water/drip1.wav", + [NOTIFYSOUND_DRIP2 ] = "ambient/water/drip2.wav", + [NOTIFYSOUND_DRIP3 ] = "ambient/water/drip3.wav", + [NOTIFYSOUND_DRIP4 ] = "ambient/water/drip4.wav", + [NOTIFYSOUND_DRIP5 ] = "ambient/water/drip5.wav", + [NOTIFYSOUND_ERROR1 ] = "buttons/button10.wav", + [NOTIFYSOUND_CONFIRM1] = "buttons/button3.wav", + [NOTIFYSOUND_CONFIRM2] = "buttons/button14.wav", + [NOTIFYSOUND_CONFIRM3] = "buttons/button15.wav", + [NOTIFYSOUND_CONFIRM4] = "buttons/button17.wav", + } + + function WireLib.AddNotify(ply, Message, Type, Duration, Sound) + if isstring(ply) then + Message, Type, Duration, Sound = ply, Message, Type, Duration + elseif ply ~= LocalPlayer() then + return + end + GAMEMODE:AddNotify(Message, Type, Duration) + if Sound and sounds[Sound] then surface.PlaySound(sounds[Sound]) end + end + + net.Receive("wire_addnotify", function(netlen) + local Message = net.ReadString() + local Type = net.ReadUInt(8) + local Duration = net.ReadFloat() + local Sound = net.ReadUInt(8) + + WireLib.AddNotify(LocalPlayer(), Message, Type, Duration, Sound) + end) + + elseif SERVER then + + NOTIFY_GENERIC = 0 + NOTIFY_ERROR = 1 + NOTIFY_UNDO = 2 + NOTIFY_HINT = 3 + NOTIFY_CLEANUP = 4 + + util.AddNetworkString("wire_addnotify") + function WireLib.AddNotify(ply, Message, Type, Duration, Sound) + if isstring(ply) then ply, Message, Type, Duration, Sound = nil, ply, Message, Type, Duration end + if ply and not ply:IsValid() then return end + net.Start("wire_addnotify") + net.WriteString(Message) + net.WriteUInt(Type or 0,8) + net.WriteFloat(Duration) + net.WriteUInt(Sound or 0,8) + if ply then net.Send(ply) else net.Broadcast() end + end + + end +end -- wire_addnotify + +--[[ wire_clienterror: displays Lua errors on the client + Usage: WireLib.ClientError("Hello", ply) +]] +if CLIENT then + net.Receive("wire_clienterror", function(netlen) + local message = net.ReadString() + print("sv: "..message) + local lines = string.Explode("\n", message) + for i,line in ipairs(lines) do + if i == 1 then + WireLib.AddNotify(line, NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + else + WireLib.AddNotify(line, NOTIFY_ERROR, 7) + end + end + end) +elseif SERVER then + util.AddNetworkString("wire_clienterror") + function WireLib.ClientError(message, ply) + net.Start("wire_clienterror") + net.WriteString(message) + net.Send(ply) + end +end + +function WireLib.ErrorNoHalt(message) + -- ErrorNoHalt clips messages to 512 characters, so chain calls if necessary + for i=1,#message, 511 do + ErrorNoHalt(message:sub(i,i+510)) + end +end + +--- Generate a random version 4 UUID and return it as a string. +function WireLib.GenerateUUID() + -- It would be easier to generate this by word rather than by byte, but + -- MSVC's RAND_MAX = 0x7FFF, which means math.random(0, 0xFFFF) won't + -- return all possible values. + local bytes = {} + for i = 1, 16 do bytes[i] = math.random(0, 0xFF) end + bytes[7] = bit.bor(0x40, bit.band(bytes[7], 0x0F)) + bytes[9] = bit.bor(0x80, bit.band(bytes[7], 0x3F)) + return string.format("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", unpack(bytes)) +end + +local PERSISTENT_UUID_KEY = "WireLib.GetServerUUID" +if SERVER then + --- Return a persistent UUID associated with the server. + function WireLib.GetServerUUID() + local uuid = cookie.GetString(PERSISTENT_UUID_KEY) + if not uuid then + uuid = WireLib.GenerateUUID() + cookie.Set(PERSISTENT_UUID_KEY, uuid) + end + return uuid + end + + util.AddNetworkString(PERSISTENT_UUID_KEY) + + hook.Add("PlayerInitialSpawn", PERSISTENT_UUID_KEY, function(player) + net.Start(PERSISTENT_UUID_KEY) + net.WriteString(WireLib.GetServerUUID()) + net.Send(player) + end) + +else + local SERVER_UUID + net.Receive(PERSISTENT_UUID_KEY, function() SERVER_UUID = net.ReadString() end) + function WireLib.GetServerUUID() return SERVER_UUID end +end + + +--[[ wire_netmsg system + A basic framework for entities that should send newly connecting players data + + Server requirements: ENT:Retransmit(ply) -- Should send all data to one player + Client requirements: + WireLib.netRegister(self) in ENT:Initialize() + ENT:Receive() + + To send: + function ENT:Retransmit(ply) + WireLib.netStart(self) -- This automatically net.WriteEntity(self)'s + net.Write*... + WireLib.netEnd(ply) -- you can pass a Player or a table of players to only send to some clients, otherwise it broadcasts + end + + To receive: + function ENT:Receive() + net.Read*... + end + + To unregister: WireLib.netUnRegister(self) -- Happens automatically on entity removal +]] + +if SERVER then + util.AddNetworkString("wire_netmsg_register") + util.AddNetworkString("wire_netmsg_registered") + + function WireLib.netStart(self) + net.Start("wire_netmsg_registered") + net.WriteEntity(self) + end + function WireLib.netEnd(ply) + if ply then net.Send(ply) else net.Broadcast() end + end + + net.Receive("wire_netmsg_register", function(netlen, ply) + local self = net.ReadEntity() + if IsValid(self) and self.Retransmit then self:Retransmit(ply) end + end) +elseif CLIENT then + function WireLib.netRegister(self) + net.Start("wire_netmsg_register") net.WriteEntity(self) net.SendToServer() + end + + net.Receive("wire_netmsg_registered", function(netlen) + local self = net.ReadEntity() + if IsValid(self) and self.Receive then self:Receive() end + end) +end + +--[[ wire_ports: client-side input/output names/types/descs + umsg format: + any number of the following: + Char start + start==0: break + start==-1: delete inputs + start==-2: delete outputs + start==-3: set eid + start==-4: connection state + start > 0: + Char amount + abs(amount)*3 strings describing name, type, desc +]] + +-- Checks if the entity has wire ports. +-- Works for every entity that has wire in-/output. +-- Very important and useful for checks! +function WireLib.HasPorts(ent) + if (ent.IsWire) then return true end + if (ent.Base == "base_wire_entity") then return true end + + -- Checks if the entity is in the list, it checks if the entity has self.in-/outputs too. + local In, Out = WireLib.GetPorts(ent) + if (In and (ent.Inputs or CLIENT)) then return true end + if (Out and (ent.Outputs or CLIENT)) then return true end + + return false +end + +if SERVER then + local INPUT,OUTPUT = 1,-1 + local DELETE,PORT,LINK = 1,2,3 + + local ents_with_inputs = {} + local ents_with_outputs = {} + --local IOlookup = { [INPUT] = ents_with_inputs, [OUTPUT] = ents_with_outputs } + + util.AddNetworkString("wire_ports") + timer.Create("Debugger.PoolTypeStrings",1,1,function() + if WireLib.Debugger and WireLib.Debugger.formatPort then + for typename,_ in pairs(WireLib.Debugger.formatPort) do util.AddNetworkString(typename) end -- Reduce bandwidth + end + end) + local queue = {} + + function WireLib.GetPorts(ent) + local eid = ent:EntIndex() + return ents_with_inputs[eid], ents_with_outputs[eid] + end + + function WireLib._RemoveWire(eid, DontSend) -- To remove the inputs without to remove the entity. Very important for IsWire checks! + local hasinputs, hasoutputs = ents_with_inputs[eid], ents_with_outputs[eid] + if hasinputs or hasoutputs then + ents_with_inputs[eid] = nil + ents_with_outputs[eid] = nil + if not DontSend then + net.Start("wire_ports") + net.WriteInt(-3, 8) -- set eid + net.WriteUInt(eid, 16) -- entity id + if hasinputs then net.WriteInt(-1, 8) end -- delete inputs + if hasoutputs then net.WriteInt(-2, 8) end -- delete outputs + net.WriteInt(0, 8) -- break + net.Broadcast() + end + end + end + + hook.Add("EntityRemoved", "wire_ports", function(ent) + if not ent:IsPlayer() then + WireLib._RemoveWire(ent:EntIndex()) + end + end) + + function WireLib._SetInputs(ent, lqueue) + local queue = lqueue or queue + local eid = ent:EntIndex() + + if not ents_with_inputs[eid] then ents_with_inputs[eid] = {} end + + queue[#queue+1] = { eid, DELETE, INPUT } + + for Name, CurPort in pairs_sortvalues(ent.Inputs, WireLib.PortComparator) do + local entry = { Name, CurPort.Type, CurPort.Desc or "" } + ents_with_inputs[eid][#ents_with_inputs[eid]+1] = entry + queue[#queue+1] = { eid, PORT, INPUT, entry, CurPort.Num } + end + for _, CurPort in pairs_sortvalues(ent.Inputs, WireLib.PortComparator) do + WireLib._SetLink(CurPort, lqueue) + end + end + + function WireLib._SetOutputs(ent, lqueue) + local queue = lqueue or queue + local eid = ent:EntIndex() + + if not ents_with_outputs[eid] then ents_with_outputs[eid] = {} end + + queue[#queue+1] = { eid, DELETE, OUTPUT } + + for Name, CurPort in pairs_sortvalues(ent.Outputs, WireLib.PortComparator) do + local entry = { Name, CurPort.Type, CurPort.Desc or "" } + ents_with_outputs[eid][#ents_with_outputs[eid]+1] = entry + queue[#queue+1] = { eid, PORT, OUTPUT, entry, CurPort.Num } + end + end + + function WireLib._SetLink(input, lqueue) + local ent = input.Entity + local num = input.Num + local state = input.SrcId and true or false + + local queue = lqueue or queue + local eid = ent:EntIndex() + + queue[#queue+1] = {eid, LINK, num, state} + end + + local eid = 0 + local numports, firstportnum, portstrings = {}, {}, {} + local function writeCurrentStrings() + -- Write the current (input or output) string information + for IO=OUTPUT,INPUT,2 do -- so, k= -1 and k= 1 + if numports[IO] then + net.WriteInt(firstportnum[IO], 8) -- Control code for inputs/outputs is also the offset (the first port number we're writing over) + net.WriteUInt(numports[IO], 8) -- Send number of ports + net.WriteBit(IO==OUTPUT) + for i=1,numports[IO]*3 do net.WriteString(portstrings[IO][i] or "") end + numports[IO] = nil + end + end + end + local function writemsg(msg) + -- First write a signed int for the command code + -- Then sometimes write extra data specific to the command (type varies) + + if msg[1] ~= eid then + eid = msg[1] + writeCurrentStrings() -- We're switching to talking about a different entity, lets send port information + net.WriteInt(-3,8) + net.WriteUInt(eid,16) + end + + local msgtype = msg[2] + + if msgtype == DELETE then + numports[msg[3]] = nil + net.WriteInt(msg[3] == INPUT and -1 or -2, 8) + elseif msgtype == PORT then + local _,_,IO,entry,num = unpack(msg) + + if not numports[IO] then + firstportnum[IO] = num + numports[IO] = 0 + portstrings[IO] = {} + end + local i = numports[IO]*3 + portstrings[IO][i+1] = entry[1] + portstrings[IO][i+2] = entry[2] + portstrings[IO][i+3] = entry[3] + numports[IO] = numports[IO]+1 + elseif msgtype == LINK then + local _,_,num,state = unpack(msg) + net.WriteInt(-4, 8) + net.WriteUInt(num, 8) + net.WriteBit(state) + end + end + + local function FlushQueue(lqueue, ply) + -- Zero these two for the writemsg function + eid = 0 + numports = {} + + net.Start("wire_ports") + for i=1,#lqueue do + writemsg(lqueue[i]) + end + writeCurrentStrings() + net.WriteInt(0,8) + if ply then net.Send(ply) else net.Broadcast() end + end + + hook.Add("Think", "wire_ports", function() + if not next(queue) then return end + FlushQueue(queue) + queue = {} + end) + + hook.Add("PlayerInitialSpawn", "wire_ports", function(ply) + local lqueue = {} + for eid, _ in pairs(ents_with_inputs) do + WireLib._SetInputs(Entity(eid), lqueue) + end + for eid, _ in pairs(ents_with_outputs) do + WireLib._SetOutputs(Entity(eid), lqueue) + end + FlushQueue(lqueue, ply) + end) + +elseif CLIENT then + local ents_with_inputs = {} + local ents_with_outputs = {} + + net.Receive("wire_ports", function(netlen) + local eid = 0 + local connections = {} -- In case any cmd -4's come in before link strings + while true do + local cmd = net.ReadInt(8) + if cmd == 0 then + break + elseif cmd == -1 then + ents_with_inputs[eid] = nil + elseif cmd == -2 then + ents_with_outputs[eid] = nil + elseif cmd == -3 then + eid = net.ReadUInt(16) + elseif cmd == -4 then + connections[#connections+1] = {eid, net.ReadUInt(8), net.ReadBit() ~= 0} -- Delay this process till after the loop + elseif cmd > 0 then + local entry + + local amount = net.ReadUInt(8) + if net.ReadBit() ~= 0 then + -- outputs + entry = ents_with_outputs[eid] + if not entry then + entry = {} + ents_with_outputs[eid] = entry + end + else + -- inputs + entry = ents_with_inputs[eid] + if not entry then + entry = {} + ents_with_inputs[eid] = entry + end + end + + local endindex = cmd+amount-1 + for i = cmd,endindex do + entry[i] = {net.ReadString(), net.ReadString(), net.ReadString()} + end + end + end + for i=1, #connections do + local eid, num, state = unpack(connections[i]) + local entry = ents_with_inputs[eid] + if not entry then + entry = {} + ents_with_inputs[eid] = entry + elseif entry[num] then + entry[num][4] = state + end + end + end) + + function WireLib.GetPorts(ent) + local eid = ent:EntIndex() + return ents_with_inputs[eid], ents_with_outputs[eid] + end + + function WireLib._RemoveWire(eid) -- To remove the inputs without to remove the entity. + ents_with_inputs[eid] = nil + ents_with_outputs[eid] = nil + end + + local flag = false + function WireLib.TestPorts() + flag = not flag + if flag then + local lasteid = 0 + hook.Add("HUDPaint", "wire_ports_test", function() + local ent = LocalPlayer():GetEyeTraceNoCursor().Entity + --if not ent:IsValid() then return end + local eid = IsValid(ent) and ent:EntIndex() or lasteid + lasteid = eid + + local text = "ID "..eid.."\nInputs:\n" + for _,name,tp,desc,connected in ipairs_map(ents_with_inputs[eid] or {}, unpack) do + + text = text..(connected and "-" or " ") + text = text..string.format("%s (%s) [%s]\n", name, tp, desc) + end + text = text.."\nOutputs:\n" + for _,name,tp,desc in ipairs_map(ents_with_outputs[eid] or {}, unpack) do + text = text..string.format("%s (%s) [%s]\n", name, tp, desc) + end + draw.DrawText(text,"Trebuchet24",10,300,Color(255,255,255,255),0) + end) + else + hook.Remove("HUDPaint", "wire_ports_test") + end + end +end + +-- For transmitting the yellow lines showing links between controllers and ents, as used by the Adv Entity Marker +if SERVER then + util.AddNetworkString("WireLinkedEnts") + function WireLib.SendMarks(controller, marks) + if not IsValid(controller) or not (controller.Marks or marks) then return end + net.Start("WireLinkedEnts") + net.WriteEntity(controller) + net.WriteUInt(#(controller.Marks or marks), 16) + for _,v in pairs(controller.Marks or marks) do + net.WriteEntity(v) + end + net.Broadcast() + end +else + net.Receive("WireLinkedEnts", function(netlen) + local Controller = net.ReadEntity() + if IsValid(Controller) then + Controller.Marks = {} + for _=1, net.ReadUInt(16) do + local link = net.ReadEntity() + if IsValid(link) then + table.insert(Controller.Marks, link) + end + end + end + end) +end + +--[[ + Returns the "distance" between two strings + ie the amount of character swaps you have to do to get the first string to equal the second + Example: + levenshtein( "test", "toast" ) returns 2, because two steps: 'e' swapped to 'o', and 'a' is added + + Very useful for searching algorithms + Used by custom spawn menu search & gate tool search, for example + Credits go to: http://lua-users.org/lists/lua-l/2009-07/msg00461.html +]] +function WireLib.levenshtein( s, t ) + local d, sn, tn = {}, #s, #t + local byte, min = string.byte, math.min + for i = 0, sn do d[i * tn] = i end + for j = 0, tn do d[j] = j end + for i = 1, sn do + local si = byte(s, i) + for j = 1, tn do + d[i*tn+j] = min(d[(i-1)*tn+j]+1, d[i*tn+j-1]+1, d[(i-1)*tn+j-1]+(si == byte(t,j) and 0 or 1)) + end + end + return d[#d] +end + +--[[ + nicenumber + by Divran + + Adds several functions to format numbers into millions, billions, etc + Adds a function to format a number (assumed seconds) into a duration (weeks, days, hours, minutes, seconds, etc) + + This is used, for example, by the wire screen. +]] + +WireLib.nicenumber = {} +local nicenumber = WireLib.nicenumber + +local numbers = { + { + name = "septillion", + short = "sep", + symbol = "Y", + prefix = "yotta", + zeroes = 10^24, + }, + { + name = "sextillion", + short = "sex", + symbol = "Z", + prefix = "zetta", + zeroes = 10^21, + }, + { + name = "quintillion", + short = "quint", + symbol = "E", + prefix = "exa", + zeroes = 10^18, + }, + { + name = "quadrillion", + short = "quad", + symbol = "P", + prefix = "peta", + zeroes = 10^15, + }, + { + name = "trillion", + short = "T", + symbol = "T", + prefix = "tera", + zeroes = 10^12, + }, + { + name = "billion", + short = "B", + symbol = "B", + prefix = "giga", + zeroes = 10^9, + }, + { + name = "million", + short = "M", + symbol = "M", + prefix = "mega", + zeroes = 10^6, + }, + { + name = "thousand", + short = "K", + symbol = "K", + prefix = "kilo", + zeroes = 10^3 + } +} + +local one = { + name = "ones", + short = "", + symbol = "", + prefix = "", + zeroes = 1 +} + +-- returns a table of tables that inherit from the above info +local floor = math.floor +function nicenumber.info( n, steps ) + if not n or n < 0 then return {} end + if n > 10 ^ 300 then n = 10 ^ 300 end + + local t = {} + + steps = steps or #numbers + + local displayones = true + local cursteps = 0 + + for i = 1, #numbers do + local zeroes = numbers[i].zeroes + + local nn = floor(n / zeroes) + if nn > 0 then + cursteps = cursteps + 1 + if cursteps > steps then break end + + t[#t+1] = setmetatable({value = nn},{__index = numbers[i]}) + + n = n % numbers[i].zeroes + + displayones = false + end + end + + if n >= 0 and displayones then + t[#t+1] = setmetatable({value = n},{__index = one}) + end + + return t +end + +local sub = string.sub + +-- returns string +-- example 12B 34M +function nicenumber.format( n, steps ) + local t = nicenumber.info( n, steps ) + + steps = steps or #numbers + + local str = "" + for i=1,#t do + if i > steps then break end + str = str .. t[i].value .. t[i].symbol .. " " + end + + return sub( str, 1, -2 ) -- remove trailing space +end + +-- returns string with decimals +-- example 12.34B +local round = math.Round +function nicenumber.formatDecimal( n, decimals ) + local t = nicenumber.info( n, 1 ) + + decimals = decimals or 2 + + local largest = t[1] + if largest then + n = n / largest.zeroes + return round( n, decimals ) .. largest.symbol + else + return "0" + end +end + +------------------------- +-- nicetime +------------------------- +local times = { + { "y", 31556926 }, -- years + { "mon", 2629743.83 }, -- months + { "w", 604800 }, -- weeks + { "d", 86400 }, -- days + { "h", 3600 }, -- hours + { "m", 60 }, -- minutes + { "s", 1 }, -- seconds +} +function nicenumber.nicetime( n ) + n = math.abs( n ) + + if n == 0 then return "0s" end + + local prev_name = "" + local prev_val = 0 + for i=1,#times do + local name = times[i][1] + local num = times[i][2] + + local temp = floor(n / num) + if temp > 0 or prev_name ~= "" then + if prev_name ~= "" then + return prev_val .. prev_name .. " " .. temp .. name + else + prev_name = name + prev_val = temp + n = n % num + end + end + end + + if prev_name ~= "" then + return prev_val .. prev_name + else + return "0s" + end +end + +function WireLib.isnan(n) + return n ~= n +end +local isnan = WireLib.isnan + +-- This function clamps the position before moving the entity +local minx, miny, minz = -16384, -16384, -16384 +local maxx, maxy, maxz = 16384, 16384, 16384 +local clamp = math.Clamp +function WireLib.clampPos(pos) + pos = Vector(pos) + pos.x = clamp(pos.x, minx, maxx) + pos.y = clamp(pos.y, miny, maxy) + pos.z = clamp(pos.z, minz, maxz) + return pos +end + +function WireLib.setPos(ent, pos) + if isnan(pos.x) or isnan(pos.y) or isnan(pos.z) then return end + return ent:SetPos(WireLib.clampPos(pos)) +end + +local huge, abs = math.huge, math.abs +function WireLib.setAng(ent, ang) + if isnan(ang.pitch) or isnan(ang.yaw) or isnan(ang.roll) then return end + if abs(ang.pitch) == huge or abs(ang.yaw) == huge or abs(ang.roll) == huge then return false end -- SetAngles'ing inf crashes the server + + ang = Angle(ang) + ang:Normalize() + + return ent:SetAngles(ang) +end + +-- Used by any applyForce function available to the user +-- Ensures that the force is within the range of a float, to prevent +-- physics engine crashes +-- 2*maxmass*maxvelocity should be enough impulse to do whatever you want. +-- Timer resolves issue with table not existing until next tick on Linux +local max_force, min_force +hook.Add("InitPostEntity","WireForceLimit",function() + timer.Simple(0, function() + max_force = 100000*physenv.GetPerformanceSettings().MaxVelocity + min_force = -max_force + end) +end) + +-- Nan never equals itself, so if the value doesn't equal itself replace it with 0. +function WireLib.clampForce( v ) + v = Vector(v[1], v[2], v[3]) + v[1] = v[1] == v[1] and math.Clamp( v[1], min_force, max_force ) or 0 + v[2] = v[2] == v[2] and math.Clamp( v[2], min_force, max_force ) or 0 + v[3] = v[3] == v[3] and math.Clamp( v[3], min_force, max_force ) or 0 + return v +end + + +--[[---------------------------------------------- + GetClosestRealVehicle + This function checks if the provided entity is a "real" vehicle + If it is, it does nothing and returns the same entity back. + If it isn't, it scans the contraption of said vehicle, and + finds the closest one to the specified location + and returns it +------------------------------------------------]] + +-- this helper function attempts to determine if the vehicle is actually a real vehicle +-- and not a "fake" vehicle created by an 'scars'-like addon +local valid_vehicles = { + prop_vehicle = true, + prop_vehicle_airboat = true, + prop_vehicle_apc = true, + prop_vehicle_cannon = true, + prop_vehicle_choreo_generic = true, + prop_vehicle_crane = true, + prop_vehicle_driveable = true, + prop_vehicle_jeep = true, + prop_vehicle_prisoner_pod = true +} +local function IsRealVehicle(pod) + return valid_vehicles[pod:GetClass()] +end + +-- GetClosestRealVehicle +-- Args: +-- vehicle; the vehicle that the user would like to link a controller to +-- position; the position to find the closest vehicle to. If unspecified, uses the vehicle's position +-- notify_this_player; notifies this player if a different vehicle was selected. If unspecified, notifies no one. +function WireLib.GetClosestRealVehicle(vehicle,position,notify_this_player) + if not IsValid(vehicle) then return vehicle end + if not position then position = vehicle:GetPos() end + + -- If this is a valid entity, but not a real vehicle, then let's get started + if IsValid(vehicle) and not IsRealVehicle(vehicle) then + -- get all "real" vehicles in the contraption and calculate distance + local contraption = constraint.GetAllConstrainedEntities(vehicle) + local vehicles = {} + for _, ent in pairs( contraption ) do + if IsRealVehicle(ent) then + vehicles[#vehicles+1] = { + distance = position:Distance(ent:GetPos()), + entity = ent + } + end + end + + if #vehicles > 0 then + -- sort them by distance + table.sort(vehicles,function(a,b) return a.distance < b.distance end) + -- get closest + vehicle = vehicles[1].entity + + -- notify the owner of the change + if IsValid(notify_this_player) and notify_this_player:IsPlayer() then + WireLib.AddNotify(notify_this_player, + "That wasn't a vehicle!\n".. + "The contraption has been scanned and this entity has instead been linked to the closest vehicle in this contraption.\n".. + "Hover your cursor over the controller to view the yellow line, which indicates the selected vehicle.", + NOTIFY_GENERIC,14,NOTIFYSOUND_DRIP1) + end + end + end + + -- If the selected vehicle is still not a real vehicle even after all of the above, notify the user of this + if IsValid(notify_this_player) and notify_this_player:IsPlayer() and not IsRealVehicle(vehicle) then + WireLib.AddNotify(notify_this_player, + "The entity you linked to is not a 'real' vehicle, " .. + "and we were unable to find any 'real' vehicles attached to it. This controller might not work.", + NOTIFY_GENERIC,14,NOTIFYSOUND_DRIP1) + end + + return vehicle +end + +-- Garry's Mod lets serverside Lua check whether the key associated with a particular bind is +-- pressed or not via the KeyPress and KeyRelease hooks, and the KeyDown function. However, this +-- is only available for a small number of binds (mostly ones related to movement), which are +-- exposed via the IN_ enums. It's possible to check any key manually serverside (with the +-- player.keystate table), but that doesn't handle rebinding so isn't very friendly to users with +-- non-QWERTY keyboard layouts. This system lets us extend arbitrarily the set of binds that the +-- serverside knows about. +do + local MESSAGE_NAME = "WireLib.SyncBinds" + + local interestingBinds = { + "invprev", + "invnext", + "impulse 100", + "attack", + "jump", + "duck", + "forward", + "back", + "use", + "left", + "right", + "moveleft", + "moveright", + "attack2", + "reload", + "alt1", + "alt2", + "showscores", + "speed", + "walk", + "zoom", + "grenade1", + "grenade2", + } + local interestingBindsLookup = {} + for k, v in pairs(interestingBinds) do interestingBindsLookup[v] = k end + + if CLIENT then + hook.Add("InitPostEntity", MESSAGE_NAME, function() + local data = {} + for button = BUTTON_CODE_NONE, BUTTON_CODE_LAST do + local binding = input.LookupKeyBinding(button) + if binding ~= nil then + if string.sub(binding, 1, 1) == "+" then binding = string.sub(binding, 2) end + local bindingIndex = interestingBindsLookup[binding] + if bindingIndex ~= nil then + table.insert(data, { Button = button, BindingIndex = bindingIndex }) + end + end + end + + -- update net integer precisions if interestingBinds exceeds 32 + if (BUTTON_CODE_COUNT >= 65536) then ErrorNoHalt("ERROR! BUTTON_CODE_COUNT exceeds 65536!") end + if (#interestingBinds >= 32) then ErrorNoHalt("ERROR! Interesting binds exceeds 32!") end + + net.Start(MESSAGE_NAME) + net.WriteUInt(#data, 8) + for _, datum in pairs(data) do + net.WriteUInt(datum.Button, 16) + net.WriteUInt(datum.BindingIndex, 5) + end + net.SendToServer() + end) + elseif SERVER then + util.AddNetworkString(MESSAGE_NAME) + net.Receive(MESSAGE_NAME, function(_, player) + player.SyncedBindings = {} + local count = net.ReadUInt(8) + for _ = 1, count do + local button = net.ReadUInt(16) + local bindingIndex = net.ReadUInt(5) + if button > BUTTON_CODE_NONE and button <= BUTTON_CODE_LAST then + local binding = interestingBinds[bindingIndex] + player.SyncedBindings[button] = binding + end + end + end) + + hook.Add("PlayerButtonDown", MESSAGE_NAME, function(player, button) + if not player.SyncedBindings then return end + local binding = player.SyncedBindings[button] + hook.Run("PlayerBindDown", player, binding, button) + end) + + hook.Add("PlayerButtonUp", MESSAGE_NAME, function(player, button) + if not player.SyncedBindings then return end + local binding = player.SyncedBindings[button] + hook.Run("PlayerBindUp", player, binding, button) + end) + + hook.Add("StartCommand", MESSAGE_NAME, function(player, command) + if not player.SyncedBindings then return end + local wheel = command:GetMouseWheel() + if wheel == 0 then return end + local button = wheel > 0 and MOUSE_WHEEL_UP or MOUSE_WHEEL_DOWN + local binding = player.SyncedBindings[button] + if not binding then return end + hook.Run("PlayerBindDown", player, binding, button) + hook.Run("PlayerBindUp", player, binding, button) + end) + end +end diff --git a/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_core.lua b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_core.lua new file mode 100644 index 0000000..7922bc1 --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_core.lua @@ -0,0 +1,666 @@ +-------------------------------------------------------------------------------- +-- Zyelios VM (Zyelios CPU/GPU virtual machine) +-- +-- Virtual machine implementation core +-------------------------------------------------------------------------------- +ZVM = {} +if not SERVER and not CLIENT then + ZVM.MicrocodeDebug = true +end + + + + +-------------------------------------------------------------------------------- +-- Include extra files +include("wire/zvm/zvm_opcodes.lua") +include("wire/zvm/zvm_features.lua") +include("wire/zvm/zvm_data.lua") + + + + + +-------------------------------------------------------------------------------- +-- Emit "microcode" to the output stream +if ZVM.MicrocodeDebug then -- Debug microcode generator + local pad = 0 + function ZVM:Emit(text) + if string.find(text,"end") and (not string.find(text,"if")) + then pad = pad - 1 end + + if string.find(text,"elseif") or string.find(text,"else") + then self.EmitBlock = self.EmitBlock..string.rep(" ",pad-1)..text.."\n" + else self.EmitBlock = self.EmitBlock..string.rep(" ",pad)..text.."\n" + end + + if (string.find(text,"if") or string.find(text,"for")) + and (not string.find(text,"elseif")) + and (not string.find(text,"end")) + then pad = pad + 1 end + end +else + function ZVM:Emit(text) + self.EmitBlock = self.EmitBlock..text.."\n" + end +end + + + + +-------------------------------------------------------------------------------- +-- Start new dynamic precompile block +function ZVM:Dyn_StartBlock() + self.EmitBlock = "" + self.EmitRegisterChanged = {} + self.EmitOperand = { "0", "0" } + self.EmitExpression = {} + + -- This instruction requires an interrupt check after being used + self.EmitNeedInterruptCheck = false + -- Operand RM function to be used for the operand + self.EmitOperandRM = {} + -- Operand byte to be used (replaces $BYTE) + self.EmitOperandByte = {} + -- Operand segment prefix to be used (replaces $SEG) + self.EmitOperandSegment = {} + + -- Mark local registers + self:Emit("local EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP,OP1,OP2") + self:Emit("local R0, R1, R2, R3, R4, R5, R6, R7") + self:Emit("local R8, R9,R10,R11,R12,R13,R14,R15") + self:Emit("local R16,R17,R18,R19,R20,R21,R22,R23") + self:Emit("local R24,R25,R26,R27,R28,R29,R30,R31") +end + + + + +-------------------------------------------------------------------------------- +-- Load/fetch operand (by RM) +function ZVM:Dyn_LoadOperand(OP,RM) + if self.OperandReadFunctions[RM] then + local preEmit + if self.ReadInvolvedRegisterLookup[RM] and + self.EmitRegisterChanged[self.ReadInvolvedRegisterLookup[RM]] then + -- Available local value for this register + preEmit = self.OperandFastReadFunctions[RM] + else + preEmit = self.OperandReadFunctions[RM] + end + + -- Make sure segment register is global + self:Dyn_EmitForceRegisterGlobal(self.EmitOperandSegment[OP]) + + -- Generate operand text + preEmit = string.gsub(preEmit,"$BYTE",self.EmitOperandByte[OP] or "0") + preEmit = string.gsub(preEmit,"$SEG","VM."..(self.EmitOperandSegment[OP] or "DS")) + self.EmitOperand[OP] = preEmit + + if self.NeedInterruptCheck[RM] then self.EmitNeedInterruptCheck = true end + end + + self.EmitOperandRM[OP] = RM +end + + + + +-------------------------------------------------------------------------------- +-- Write operand (by RM) +function ZVM:Dyn_WriteOperand(OP,RM) + if RM == 9 then -- Special case: attempting to write to CS + self:Dyn_EmitInterrupt("13","1") + return + end + + if self.OperandWriteFunctions[RM] then + if self.EmitExpression[OP] then -- check if we need writeback + local preEmit + if self.WriteInvolvedRegisterLookup[RM] then + preEmit = self.OperandFastWriteFunctions[RM] + self.EmitRegisterChanged[self.WriteInvolvedRegisterLookup[RM]] + = self.InternalRegister[self.WriteInvolvedRegisterLookup[RM]] + else + if self.WriteRequiredRegisterLookup[RM] and + self.EmitRegisterChanged[self.WriteRequiredRegisterLookup[RM]] then + preEmit = self.OperandFastWriteFunctions[RM] + else + preEmit = self.OperandWriteFunctions[RM] + end + end + + preEmit = string.gsub(preEmit,"$EXPR",self.EmitExpression[OP]) + preEmit = string.gsub(preEmit,"$BYTE",self.EmitOperandByte[OP] or "0") + preEmit = string.gsub(preEmit,"$SEG","VM."..(self.EmitOperandSegment[OP] or "0")) + + self:Emit(preEmit) + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Preprocess microcode text (for microcode syntax to work) +function ZVM:Dyn_PreprocessEmit(text) + local preEmit = string.gsub( text,"$1",self.EmitOperand[1]) + preEmit = string.gsub(preEmit,"$2",self.EmitOperand[2]) + return string.gsub(preEmit,"$L","local") +end + + + + +-------------------------------------------------------------------------------- +-- Emit preprocessed text +function ZVM:Dyn_Emit(text) + self:Emit(self:Dyn_PreprocessEmit(text)) +end + + + + +-------------------------------------------------------------------------------- +-- Emit operand being set to specific expression +function ZVM:Dyn_EmitOperand(OP,text,emitNow) + if not text then + self.EmitExpression[1] = self:Dyn_PreprocessEmit(OP) + else + self.EmitExpression[OP] = self:Dyn_PreprocessEmit(text) + if emitNow then + self:Emit("OP"..OP.." = "..self.EmitExpression[OP]) + self.EmitExpression[OP] = "OP"..OP + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Force current state to be updated +function ZVM:Dyn_EmitState(errorState) + -- Do we need to emit registers + for v,v in pairs(self.EmitRegisterChanged) do + --if (not errorState) or (not self.EmitRegisterChangedByOperand[k]) then + self:Emit("VM."..v.." = "..v) + --end + end +end + + + + +-------------------------------------------------------------------------------- +-- Emit forced block return +function ZVM:Dyn_EmitBreak(emitIP) + self:Emit("VM.TMR = VM.TMR + "..self.PrecompileInstruction) + self:Emit("VM.CODEBYTES = VM.CODEBYTES + "..self.PrecompileBytes) + if emitIP then + self:Emit("VM.IP = "..(self.PrecompileIP or 0)) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + end + if self.ExtraEmitFunction then self.ExtraEmitFunction(self) end + self:Emit("if true then return end") +end + + + + +-------------------------------------------------------------------------------- +-- Make sure specific register value is really globally set +function ZVM:Dyn_EmitForceRegisterGlobal(register) + for k,v in pairs(self.EmitRegisterChanged) do + if v == register then + self:Emit("VM."..v.." = "..v) + self.EmitRegisterChanged[k] = nil + return + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Make sure specific register value is really locally set +function ZVM:Dyn_EmitForceRegisterLocal(register) + if not self.EmitRegisterChanged[self.NeedRegisterLookup[register]] then + self:Emit(register.." = ".."VM."..register) + self.EmitRegisterChanged[self.NeedRegisterLookup[register]] = register + end +end + + + + +-------------------------------------------------------------------------------- +-- Flag register as changed/altered +function ZVM:Dyn_EmitRegisterValueChanged(register) + for k,v in pairs(self.InternalRegister) do + if string.upper(v) == register then + self.EmitRegisterChanged[k] = register + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Emit specific opcode +function ZVM:Dyn_EmitOpcode(opcode) + self.EmitExpression = {} + if self.OpcodeTable[opcode] then + self.OpcodeTable[opcode](self) + end +end + + + + +-------------------------------------------------------------------------------- +-- Emit interrupt call +function ZVM:Dyn_EmitInterrupt(intNo,intParam) + self:Dyn_EmitState() + self:Emit("VM.IP = "..(self.PrecompileIP or 0)) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + self:Dyn_Emit("VM:Interrupt("..intNo..","..intParam..")") + self:Dyn_EmitBreak() +end + + + + +-------------------------------------------------------------------------------- +-- Emit interrupt check +function ZVM:Dyn_EmitInterruptCheck() + if self.RQCAP == 1 then + self:Emit("if VM.MEMRQ > 0 then") -- Extended memory request + self:Emit("if VM.MEMRQ == 1 then") -- Delayed request + self:Emit("VM.IP = "..self.PrecompileStartIP) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + self:Emit("VM.IDLE = 1") + self:Dyn_EmitState(true) + self:Dyn_EmitBreak() + self:Emit("elseif VM.MEMRQ == 2 then") -- Reading + self:Dyn_EmitState(true) + self:Emit("VM.MEMRQ = 4") + self:Emit("VM.IP = "..self.PrecompileStartIP) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + self:Emit("VM:Interrupt(28,VM.LADD)") + self:Dyn_EmitBreak() + self:Emit("elseif VM.MEMRQ == 3 then") -- Writing + self:Dyn_EmitState(true) + self:Emit("VM.MEMRQ = 5") + self:Emit("VM.IP = "..self.PrecompileStartIP) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + self:Emit("VM:Interrupt(29,VM.LADD)") + self:Dyn_EmitBreak() + self:Emit("end") + self:Emit("end") + end + self:Emit("if VM.INTR == 1 then") + self:Dyn_EmitBreak(false) + self:Emit("end") +end + + + + +-------------------------------------------------------------------------------- +-- End precompile block +function ZVM:Dyn_EndBlock() + if not self.PrecompileBreak then + self:Dyn_EmitState() + self:Dyn_EmitBreak(true) + end + + if self.MicrocodeDebug then + if Msg then + local str = self.EmitBlock + Msg("BLOCK: \n") + while str ~= "" do + Msg(string.sub(str,1,100)) + str = string.sub(str,101) + end + Msg("\n") + else + print(self.EmitBlock) + end + end + return self.EmitBlock +end + + + + +-------------------------------------------------------------------------------- +function ZVM:Precompile_Initialize() + self.PrecompileXEIP = self.XEIP + self.PrecompileIP = self.IP + self.PrecompileStartXEIP = self.XEIP + self.PrecompileBreak = false + self.PrecompileInstruction = 0 + self.PrecompileBytes = 0 + + self.PrecompilePreviousPage = math.floor(self.XEIP / 128) + self:Dyn_StartBlock() +end + +function ZVM:Precompile_Finalize() + -- Emit finalizer + self:Dyn_EndBlock() + + local result,message + if CompileString + then result,message = CompileString(self.EmitBlock,"ZVM:["..self.PrecompileStartXEIP.."]") + else result,message = loadstring(self.EmitBlock,"ZVM:["..self.PrecompileStartXEIP.."]") + end + if not result then + print("[ZVM ERROR]: "..(message or "unknown error")) + else + for address = self.PrecompileStartXEIP, self.PrecompileXEIP-1 do + if not self.IsAddressPrecompiled[address] then + self.IsAddressPrecompiled[address] = { } + end + table.insert(self.IsAddressPrecompiled[address],self.PrecompileStartXEIP) + end + self.PrecompiledData[self.PrecompileStartXEIP] = result + end + + return result +end + +function ZVM:Precompile_Fetch() + local prevIF = self.IF + self.IF = 0 + local value = self:ReadCell(self.PrecompileXEIP) + self.IF = prevIF + + self.PrecompileXEIP = self.PrecompileXEIP + 1 + self.PrecompileIP = self.PrecompileIP + 1 + self.PrecompileBytes = self.PrecompileBytes + 1 + return value or 0 +end + +function ZVM:Precompile_Peek() + local prevIF = self.IF + self.IF = 0 + self:ReadCell(self.PrecompileXEIP) + self.IF = prevIF +end + +function ZVM:Precompile_Step() + -- Set true XEIP register value for this step (this value will be used if XEIP is accessed) + self.PrecompileTrueXEIP = self.PrecompileXEIP + self.PrecompileStartIP = self.PrecompileIP + + -- Move on to the next instruction + self.PrecompileInstruction = self.PrecompileInstruction + 1 + + -- Reset requirement for an interrupt check, reset registers + self.EmitNeedInterruptCheck = false + --self.EmitRegisterChangedByOperand = {} + + -- Reset interrupts trigger if precompiling + self.INTR = 0 + + -- Check if we crossed the page boundary, if so - repeat the check + if math.floor(self.PrecompileXEIP / 128) ~= self.PrecompilePreviousPage then + self:Emit("VM:SetCurrentPage("..math.floor(self.PrecompileXEIP/128)..")") + self:Emit("if (VM.PCAP == 1) and (VM.CurrentPage.Execute == 0) and") + self:Emit(" (VM.PreviousPage.RunLevel ~= 0) then") + self:Dyn_EmitInterrupt("14",self.PrecompileIP) + self:Emit("end") + self:Emit("VM:SetPreviousPage("..math.floor(self.PrecompileXEIP/128)..")") + + self.PrecompilePreviousPage = math.floor(self.PrecompileXEIP / 128) + end + + -- Fetch instruction and RM byte + local Opcode,RM = self:Precompile_Fetch(),0 + local isFixedSize = false + + -- Check if it is a fixed-size instruction + if ((Opcode >= 2000) and (Opcode < 4000)) or + ((Opcode >= 12000) and (Opcode < 14000)) then + Opcode = Opcode - 2000 + isFixedSize = true + end + + -- Fetch RM if required + if (self.OperandCount[Opcode % 1000] and (self.OperandCount[Opcode % 1000] > 0)) or + (self:Precompile_Peek() == 0) or isFixedSize then + RM = self:Precompile_Fetch() + end + + -- If failed to fetch opcode/RM then report an error + if (not Opcode) or (not RM) then--if self.INTR == 1 then + self.IF = 1 + self:Interrupt(5,12) + return + end + + -- Check opcode runlevel + if self.OpcodeRunLevel[Opcode] then + self:Emit("if (VM.PCAP == 1) and (VM.CurrentPage.RunLevel > "..self.OpcodeRunLevel[Opcode]..") then") + self:Dyn_EmitInterrupt("13",Opcode) + self:Emit("end") + end + + -- Calculate operand RM bytes + local dRM2 = math.floor(RM / 10000) + local dRM1 = RM - dRM2*10000 + + -- Default segment offsets + local Segment1 = -4 + local Segment2 = -4 + + -- Decode segment prefixes + if Opcode > 1000 then + if Opcode > 10000 then + Segment2 = self:Precompile_Fetch() or 0 + + Opcode = Opcode-10000 + if Opcode > 1000 then + Segment1 = self:Precompile_Fetch() or 0 + + Opcode = Opcode-1000 + + local temp = Segment2 + Segment2 = Segment1 + Segment1 = temp + else + if isFixedSize then + self:Precompile_Fetch() + end + end + else + Segment1 = self:Precompile_Fetch() or 0 + Opcode = Opcode-1000 + if isFixedSize then + self:Precompile_Fetch() + end + end + elseif isFixedSize then + self:Precompile_Fetch() + self:Precompile_Fetch() + end + + -- If failed to fetch segment prefix then report an error + if (not Segment1) or (not Segment2) then--if self.INTR == 1 then + self:Interrupt(5,12) + return + end + + -- Check if opcode is invalid + if not self.OperandCount[Opcode] then + self:Dyn_EmitInterrupt("4",Opcode) + self.PrecompileBreak = true + else + -- Emit segment prefix if required + self.EmitOperandSegment[1] = self.SegmentLookup[Segment1] + self.EmitOperandSegment[2] = self.SegmentLookup[Segment2] + + -- Fetch immediate values if required + if isFixedSize then + self.EmitOperandByte[1] = self:Precompile_Fetch() or 0 + if not self.EmitOperandByte[1] then self:Interrupt(5,22) return end + self.EmitOperandByte[2] = self:Precompile_Fetch() or 0 + if not self.EmitOperandByte[2] then self:Interrupt(5,32) return end + + if self.OperandCount[Opcode] > 0 then + self:Dyn_LoadOperand(1,dRM1) + if self.OperandCount[Opcode] > 1 then + self:Dyn_LoadOperand(2,dRM2) + end + end + else + if self.OperandCount[Opcode] > 0 then + if self.NeedFetchByteLookup[dRM1] then + self.EmitOperandByte[1] = self:Precompile_Fetch() or 0 + -- If failed to read the byte, report an error + if not self.EmitOperandByte[1] then self:Interrupt(5,22) return end + end + self:Dyn_LoadOperand(1,dRM1) + + if self.OperandCount[Opcode] > 1 then + if self.NeedFetchByteLookup[dRM2] then + self.EmitOperandByte[2] = self:Precompile_Fetch() or 0 + -- If failed to read the byte, report an error + if not self.EmitOperandByte[2] then self:Interrupt(5,32) return end + end + self:Dyn_LoadOperand(2,dRM2) + end + end + end + + -- Emit interrupt check prefix + if self.EmitNeedInterruptCheck then + self:Emit("VM.IP = "..(self.PrecompileIP or 0)) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + end + + -- Emit opcode + self:Dyn_EmitOpcode(Opcode) + + -- Write back the values + if self.OperandCount[Opcode] and (self.OperandCount[Opcode] > 0) then + self:Dyn_WriteOperand(1,dRM1) + if self.OperandCount[Opcode] > 1 then + self:Dyn_WriteOperand(2,dRM2) + end + end + + -- Emit interrupt check + if self.EmitNeedInterruptCheck then + self:Dyn_EmitInterruptCheck() + end + end + + -- Do not repeat if opcode breaks the stream + return not self.PrecompileBreak +end + + + + +-------------------------------------------------------------------------------- +-- VM step forward +function ZVM:Step(overrideSteps,extraEmitFunction) + if self.BusLock == 1 then return end + + -- Trigger timers + self:TimerLogic() + + -- Calculate absolute execution address and set current page + self.XEIP = self.IP + self.CS + self:SetCurrentPage(math.floor(self.XEIP/128)) + + -- Do not allow execution if we are not on kernel page, or not calling from kernel page + if (self.PCAP == 1) and (self.CurrentPage.Execute == 0) and + (self.PreviousPage.RunLevel ~= 0) then + self:Interrupt(14,self.IP) + return -- Step failed + end + + -- Reset interrupts flags + self.INTR = 0 + if self.NIF then + self.IF = self.NIF + self.NIF = nil + end + + -- Check if current instruction is precompiled + local instructionXEIP = self.XEIP + if self.PrecompiledData[instructionXEIP] or overrideSteps then + -- Precompile next instruction + if overrideSteps then + self:Precompile_Initialize() + self.ExtraEmitFunction = extraEmitFunction + local instruction = 1 + while (instruction <= overrideSteps) and self:Precompile_Step() do + if self.ExtraEmitFunction then + self:Emit("VM.IP = "..(self.PrecompileIP or 0)) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + self.ExtraEmitFunction(self) + end + instruction = instruction + 1 + end + self.ExtraEmitFunction = nil + self:Precompile_Finalize() + + -- Step clock forward (account for precompiling) + self.TMR = self.TMR + 24*8000 -- + overrideSteps*9000 + end + + -- Execute precompiled instruction + local previousVM = VM + VM = self + if CLIENT then -- FIXME: hack around crash on PCALL + self.PrecompiledData[self.XEIP]() + else + local status,message = pcall(self.PrecompiledData[self.XEIP]) + if not status then + print("[ZVM ERROR]: "..message) + self:Interrupt(5,1) + end + end + VM = previousVM + else + -- Precompile several next instructions + self:Precompile_Initialize() + + local instruction = 1 + while (instruction <= 24) and self:Precompile_Step() do + instruction = instruction + 1 + end + + self:Precompile_Finalize() + + -- Step clock forward (account for precompiling) + self.TMR = self.TMR + 24*8000--instruction*9000 + end + + -- Set this page as previous (if it is executable) + self.XEIP = self.IP + self.CS + self:SetPreviousPage(math.floor(self.XEIP/128)) + return +end + + + + +-------------------------------------------------------------------------------- +function ZVM:PrintState() + print("===========================") + print("TMR="..self.TMR.." TIMER="..self.TIMER.." IP="..self.IP.." CMPR="..self.CMPR) + print("EAX="..self.EAX.." EBX="..self.EBX.." ECX="..self.ECX.." EDX="..self.EDX) + print("ESI="..self.ESI.." EDI="..self.EDI.." ESP="..self.ESP.." EBP="..self.EBP.." ESZ="..self.ESZ) + print("CS="..self.CS.." SS="..self.SS.." DS="..self.DS.." FS="..self.FS.. + " GS="..self.GS.." ES="..self.ES.." KS="..self.KS.." LS="..self.LS) + print("MEMRQ="..self.MEMRQ.." MEMADDR="..self.MEMADDR.." LADD="..self.LADD) +end diff --git a/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_data.lua b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_data.lua new file mode 100644 index 0000000..1a0edac --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_data.lua @@ -0,0 +1,489 @@ +-------------------------------------------------------------------------------- +-- Zyelios VM (Zyelios CPU/GPU virtual machine) +-- +-- Virtual machine lookup tables +-------------------------------------------------------------------------------- + + + + +-------------------------------------------------------------------------------- +-- Internal registers mapped to names +ZVM.InternalRegister = {} +ZVM.InternalLimits = {IPREC = {1, 128}} +ZVM.ReadOnlyRegister = {} + +ZVM.InternalRegister[00] = "IP" +ZVM.InternalRegister[01] = "EAX" +ZVM.InternalRegister[02] = "EBX" +ZVM.InternalRegister[03] = "ECX" +ZVM.InternalRegister[04] = "EDX" +ZVM.InternalRegister[05] = "ESI" +ZVM.InternalRegister[06] = "EDI" +ZVM.InternalRegister[07] = "ESP" +ZVM.InternalRegister[08] = "EBP" +ZVM.InternalRegister[09] = "ESZ" +---------------------------------- +ZVM.InternalRegister[16] = "CS" +ZVM.InternalRegister[17] = "SS" +ZVM.InternalRegister[18] = "DS" +ZVM.InternalRegister[19] = "ES" +ZVM.InternalRegister[20] = "GS" +ZVM.InternalRegister[21] = "FS" +ZVM.InternalRegister[22] = "KS" +ZVM.InternalRegister[23] = "LS" +---------------------------------- +ZVM.InternalRegister[24] = "IDTR" +ZVM.InternalRegister[25] = "CMPR" +ZVM.InternalRegister[26] = "XEIP" ZVM.ReadOnlyRegister[26] = true +ZVM.InternalRegister[27] = "LADD" +ZVM.InternalRegister[28] = "LINT" +ZVM.InternalRegister[29] = "TMR" +ZVM.InternalRegister[30] = "TIMER" +ZVM.InternalRegister[31] = "CPAGE" ZVM.ReadOnlyRegister[31] = true +ZVM.InternalRegister[32] = "IF" +ZVM.InternalRegister[33] = "PF" +ZVM.InternalRegister[34] = "EF" +ZVM.InternalRegister[35] = "NIF" +ZVM.InternalRegister[36] = "MF" +ZVM.InternalRegister[37] = "PTBL" +ZVM.InternalRegister[38] = "PTBE" +ZVM.InternalRegister[39] = "PCAP" +ZVM.InternalRegister[40] = "RQCAP" +---------------------------------- +ZVM.InternalRegister[41] = "PPAGE" ZVM.ReadOnlyRegister[41] = true +ZVM.InternalRegister[42] = "MEMRQ" +---------------------------------- +ZVM.InternalRegister[43] = "RAMSize" ZVM.ReadOnlyRegister[43] = true +ZVM.InternalRegister[44] = "External" +ZVM.InternalRegister[45] = "BusLock" +ZVM.InternalRegister[46] = "Idle" +ZVM.InternalRegister[47] = "INTR" +---------------------------------- +ZVM.InternalRegister[48] = "SerialNo" ZVM.ReadOnlyRegister[48] = true +ZVM.InternalRegister[49] = "CODEBYTES" ZVM.ReadOnlyRegister[49] = true +ZVM.InternalRegister[50] = "BPREC" +ZVM.InternalRegister[51] = "IPREC" +ZVM.InternalRegister[52] = "NIDT" +ZVM.InternalRegister[53] = "BlockStart" +ZVM.InternalRegister[54] = "BlockSize" +ZVM.InternalRegister[55] = "VMODE" +ZVM.InternalRegister[56] = "XTRL" +ZVM.InternalRegister[57] = "HaltPort" +ZVM.InternalRegister[58] = "HWDEBUG" +ZVM.InternalRegister[59] = "DBGSTATE" +ZVM.InternalRegister[60] = "DBGADDR" +ZVM.InternalRegister[61] = "CRL" +ZVM.InternalRegister[62] = "TimerDT" ZVM.ReadOnlyRegister[62] = true +ZVM.InternalRegister[63] = "MEMADDR" +---------------------------------- +ZVM.InternalRegister[64] = "TimerMode" +ZVM.InternalRegister[65] = "TimerRate" +ZVM.InternalRegister[66] = "TimerPrevTime" +ZVM.InternalRegister[67] = "TimerAddress" +ZVM.InternalRegister[68] = "TimerPrevMode" +---------------------------------- +for reg=0,31 do ZVM.InternalRegister[96+reg] = "R"..reg end + + + + +-------------------------------------------------------------------------------- +-- Segment register index mapped to segment register +ZVM.SegmentLookup = {} + +-- Old ZCPU format +ZVM.SegmentLookup[-02] = "CS" +ZVM.SegmentLookup[-03] = "SS" +ZVM.SegmentLookup[-04] = "DS" +ZVM.SegmentLookup[-05] = "ES" +ZVM.SegmentLookup[-06] = "GS" +ZVM.SegmentLookup[-07] = "FS" +ZVM.SegmentLookup[-08] = "KS" +ZVM.SegmentLookup[-09] = "LS" +ZVM.SegmentLookup[-10] = "EAX" +ZVM.SegmentLookup[-11] = "EBX" +ZVM.SegmentLookup[-12] = "ECX" +ZVM.SegmentLookup[-13] = "EDX" +ZVM.SegmentLookup[-14] = "ESI" +ZVM.SegmentLookup[-15] = "EDI" +ZVM.SegmentLookup[-16] = "ESP" +ZVM.SegmentLookup[-17] = "EBP" + +-- New ZCPU format +ZVM.SegmentLookup[01] = "CS" +ZVM.SegmentLookup[02] = "SS" +ZVM.SegmentLookup[03] = "DS" +ZVM.SegmentLookup[04] = "ES" +ZVM.SegmentLookup[05] = "GS" +ZVM.SegmentLookup[06] = "FS" +ZVM.SegmentLookup[07] = "KS" +ZVM.SegmentLookup[08] = "LS" +ZVM.SegmentLookup[09] = "EAX" +ZVM.SegmentLookup[10] = "EBX" +ZVM.SegmentLookup[11] = "ECX" +ZVM.SegmentLookup[12] = "EDX" +ZVM.SegmentLookup[13] = "ESI" +ZVM.SegmentLookup[14] = "EDI" +ZVM.SegmentLookup[15] = "ESP" +ZVM.SegmentLookup[16] = "EBP" +for reg=0,31 do ZVM.SegmentLookup[17+reg] = "R"..reg end + + + + + +-------------------------------------------------------------------------------- +-- Functions to decode RM bytes (READ) +ZVM.OperandReadFunctions = {} + +ZVM.OperandReadFunctions[00] = "$BYTE" +-- GP registers +ZVM.OperandReadFunctions[01] = "VM.EAX" +ZVM.OperandReadFunctions[02] = "VM.EBX" +ZVM.OperandReadFunctions[03] = "VM.ECX" +ZVM.OperandReadFunctions[04] = "VM.EDX" +ZVM.OperandReadFunctions[05] = "VM.ESI" +ZVM.OperandReadFunctions[06] = "VM.EDI" +ZVM.OperandReadFunctions[07] = "VM.ESP" +ZVM.OperandReadFunctions[08] = "VM.EBP" +-- Segment registers +ZVM.OperandReadFunctions[09] = "VM.CS" +ZVM.OperandReadFunctions[10] = "VM.SS" +ZVM.OperandReadFunctions[11] = "VM.DS" +ZVM.OperandReadFunctions[12] = "VM.ES" +ZVM.OperandReadFunctions[13] = "VM.GS" +ZVM.OperandReadFunctions[14] = "VM.FS" +ZVM.OperandReadFunctions[15] = "VM.KS" +ZVM.OperandReadFunctions[16] = "VM.LS" +-- Read from memory by GP +ZVM.OperandReadFunctions[17] = "(VM:ReadCell(VM.EAX+$SEG) or 0)" +ZVM.OperandReadFunctions[18] = "(VM:ReadCell(VM.EBX+$SEG) or 0)" +ZVM.OperandReadFunctions[19] = "(VM:ReadCell(VM.ECX+$SEG) or 0)" +ZVM.OperandReadFunctions[20] = "(VM:ReadCell(VM.EDX+$SEG) or 0)" +ZVM.OperandReadFunctions[21] = "(VM:ReadCell(VM.ESI+$SEG) or 0)" +ZVM.OperandReadFunctions[22] = "(VM:ReadCell(VM.EDI+$SEG) or 0)" +ZVM.OperandReadFunctions[23] = "(VM:ReadCell(VM.ESP+$SEG) or 0)" +ZVM.OperandReadFunctions[24] = "(VM:ReadCell(VM.EBP+$SEG) or 0)" +-- Read from memory by displacement +ZVM.OperandReadFunctions[25] = "(VM:ReadCell($BYTE+$SEG) or 0)" +-- Register plus segment +ZVM.OperandReadFunctions[26] = "(VM.EAX+$SEG)" +ZVM.OperandReadFunctions[27] = "(VM.EBX+$SEG)" +ZVM.OperandReadFunctions[28] = "(VM.ECX+$SEG)" +ZVM.OperandReadFunctions[29] = "(VM.EDX+$SEG)" +ZVM.OperandReadFunctions[30] = "(VM.ESI+$SEG)" +ZVM.OperandReadFunctions[31] = "(VM.EDI+$SEG)" +ZVM.OperandReadFunctions[32] = "(VM.ESP+$SEG)" +ZVM.OperandReadFunctions[33] = "(VM.EBP+$SEG)" +-- Read by register plus immediate +ZVM.OperandReadFunctions[34] = "(VM:ReadCell(VM.EAX+$BYTE) or 0)" +ZVM.OperandReadFunctions[35] = "(VM:ReadCell(VM.EBX+$BYTE) or 0)" +ZVM.OperandReadFunctions[36] = "(VM:ReadCell(VM.ECX+$BYTE) or 0)" +ZVM.OperandReadFunctions[37] = "(VM:ReadCell(VM.EDX+$BYTE) or 0)" +ZVM.OperandReadFunctions[38] = "(VM:ReadCell(VM.ESI+$BYTE) or 0)" +ZVM.OperandReadFunctions[39] = "(VM:ReadCell(VM.EDI+$BYTE) or 0)" +ZVM.OperandReadFunctions[40] = "(VM:ReadCell(VM.ESP+$BYTE) or 0)" +ZVM.OperandReadFunctions[41] = "(VM:ReadCell(VM.EBP+$BYTE) or 0)" +-- Register plus immediate +ZVM.OperandReadFunctions[42] = "(VM.EAX+$BYTE)" +ZVM.OperandReadFunctions[43] = "(VM.EBX+$BYTE)" +ZVM.OperandReadFunctions[44] = "(VM.ECX+$BYTE)" +ZVM.OperandReadFunctions[45] = "(VM.EDX+$BYTE)" +ZVM.OperandReadFunctions[46] = "(VM.ESI+$BYTE)" +ZVM.OperandReadFunctions[47] = "(VM.EDI+$BYTE)" +ZVM.OperandReadFunctions[48] = "(VM.ESP+$BYTE)" +ZVM.OperandReadFunctions[49] = "(VM.EBP+$BYTE)" +-- Constant plus segment +ZVM.OperandReadFunctions[50] = "($BYTE+$SEG)" +-- Ports +for i=1000,2023 do ZVM.OperandReadFunctions[i] = "(VM:ReadPort("..(i-1000)..") or 0)" end +-- Extended registers +for reg=0,31 do ZVM.OperandReadFunctions[2048+reg] = "VM.R"..reg end +for reg=0,31 do ZVM.OperandReadFunctions[2080+reg] = "(VM:ReadCell(VM.R"..reg.."+$SEG) or 0)" end +for reg=0,31 do ZVM.OperandReadFunctions[2112+reg] = "(VM.R"..reg.."+$SEG)" end +for reg=0,31 do ZVM.OperandReadFunctions[2144+reg] = "(VM:ReadCell(VM.R"..reg.."+$BYTE) or 0)" end +for reg=0,31 do ZVM.OperandReadFunctions[2176+reg] = "(VM.R"..reg.."+$BYTE)" end + + + + +-------------------------------------------------------------------------------- +-- Registers required by read operation +ZVM.ReadInvolvedRegisterLookup = {} +for i= 1, 8 do ZVM.ReadInvolvedRegisterLookup[i] = i- 1+ 1 end +--for i= 9,16 do ZVM.ReadInvolvedRegisterLookup[i] = i- 9+16 end +for i=17,24 do ZVM.ReadInvolvedRegisterLookup[i] = i-17+ 1 end +for i=26,33 do ZVM.ReadInvolvedRegisterLookup[i] = i-26+ 1 end +for i=34,41 do ZVM.ReadInvolvedRegisterLookup[i] = i-34+ 1 end +for i=42,49 do ZVM.ReadInvolvedRegisterLookup[i] = i-42+ 1 end +for i=2048,2079 do ZVM.ReadInvolvedRegisterLookup[i] = i-2048+96 end +for i=2080,2111 do ZVM.ReadInvolvedRegisterLookup[i] = i-2080+96 end +for i=2112,2143 do ZVM.ReadInvolvedRegisterLookup[i] = i-2112+96 end +for i=2144,2175 do ZVM.ReadInvolvedRegisterLookup[i] = i-2144+96 end +for i=2176,2207 do ZVM.ReadInvolvedRegisterLookup[i] = i-2176+96 end + + + + + +-------------------------------------------------------------------------------- +-- Functions to decode RM bytes (WRITE) +ZVM.OperandWriteFunctions = {} +ZVM.OperandWriteFunctions[00] = "" +-- GP registers +ZVM.OperandWriteFunctions[01] = "VM.EAX = $EXPR" +ZVM.OperandWriteFunctions[02] = "VM.EBX = $EXPR" +ZVM.OperandWriteFunctions[03] = "VM.ECX = $EXPR" +ZVM.OperandWriteFunctions[04] = "VM.EDX = $EXPR" +ZVM.OperandWriteFunctions[05] = "VM.ESI = $EXPR" +ZVM.OperandWriteFunctions[06] = "VM.EDI = $EXPR" +ZVM.OperandWriteFunctions[07] = "VM.ESP = $EXPR" +ZVM.OperandWriteFunctions[08] = "VM.EBP = $EXPR" +-- Segment registers +ZVM.OperandWriteFunctions[10] = "VM.SS = $EXPR" +ZVM.OperandWriteFunctions[11] = "VM.DS = $EXPR" +ZVM.OperandWriteFunctions[12] = "VM.ES = $EXPR" +ZVM.OperandWriteFunctions[13] = "VM.GS = $EXPR" +ZVM.OperandWriteFunctions[14] = "VM.FS = $EXPR" +ZVM.OperandWriteFunctions[15] = "VM.KS = $EXPR" +ZVM.OperandWriteFunctions[16] = "VM.LS = $EXPR" +-- Write from memory by GP +ZVM.OperandWriteFunctions[17] = "VM:WriteCell(VM.EAX+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[18] = "VM:WriteCell(VM.EBX+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[19] = "VM:WriteCell(VM.ECX+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[20] = "VM:WriteCell(VM.EDX+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[21] = "VM:WriteCell(VM.ESI+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[22] = "VM:WriteCell(VM.EDI+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[23] = "VM:WriteCell(VM.ESP+$SEG,$EXPR)" +ZVM.OperandWriteFunctions[24] = "VM:WriteCell(VM.EBP+$SEG,$EXPR)" +-- Write from memory by displacement +ZVM.OperandWriteFunctions[25] = "VM:WriteCell($BYTE+$SEG,$EXPR)" +-- Write by register plus immediate +ZVM.OperandWriteFunctions[34] = "VM:WriteCell(VM.EAX+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[35] = "VM:WriteCell(VM.EBX+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[36] = "VM:WriteCell(VM.ECX+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[37] = "VM:WriteCell(VM.EDX+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[38] = "VM:WriteCell(VM.ESI+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[39] = "VM:WriteCell(VM.EDI+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[40] = "VM:WriteCell(VM.ESP+$BYTE,$EXPR)" +ZVM.OperandWriteFunctions[41] = "VM:WriteCell(VM.EBP+$BYTE,$EXPR)" +-- Ports +for i=1000,2023 do ZVM.OperandWriteFunctions[i] = "VM:WritePort("..(i-1000)..",$EXPR)" end +-- Extended registers +for reg=0,31 do ZVM.OperandWriteFunctions[2048+reg] = "VM.R"..reg.." = $EXPR" end +for reg=0,31 do ZVM.OperandWriteFunctions[2080+reg] = "VM:WriteCell(VM.R"..reg.."+$SEG,$EXPR)" end +for reg=0,31 do ZVM.OperandWriteFunctions[2144+reg] = "VM:WriteCell(VM.R"..reg.."+$BYTE,$EXPR)" end + + + + + +-------------------------------------------------------------------------------- +-- Registers changed by writeback +ZVM.WriteInvolvedRegisterLookup = {} +for i= 1, 8 do ZVM.WriteInvolvedRegisterLookup[i] = i end +for i=2048,2079 do ZVM.WriteInvolvedRegisterLookup[i] = i-2048+96 end + + + + +-------------------------------------------------------------------------------- +-- Registers required by write operation +ZVM.WriteRequiredRegisterLookup = {} +for i=17,24 do ZVM.WriteRequiredRegisterLookup[i] = i-17+ 1 end +for i=34,41 do ZVM.WriteRequiredRegisterLookup[i] = i-34+ 1 end +for i=2080,2111 do ZVM.WriteRequiredRegisterLookup[i] = i-2080+96 end +for i=2144,2175 do ZVM.WriteRequiredRegisterLookup[i] = i-2144+96 end + + + + +-------------------------------------------------------------------------------- +-- Functions to decode RM bytes (psuedo-WRITE) +ZVM.OperandFastWriteFunctions = {} +-- GP registers +ZVM.OperandFastWriteFunctions[01] = "EAX = $EXPR" +ZVM.OperandFastWriteFunctions[02] = "EBX = $EXPR" +ZVM.OperandFastWriteFunctions[03] = "ECX = $EXPR" +ZVM.OperandFastWriteFunctions[04] = "EDX = $EXPR" +ZVM.OperandFastWriteFunctions[05] = "ESI = $EXPR" +ZVM.OperandFastWriteFunctions[06] = "EDI = $EXPR" +ZVM.OperandFastWriteFunctions[07] = "ESP = $EXPR" +ZVM.OperandFastWriteFunctions[08] = "EBP = $EXPR" +-- Write from memory by GP +ZVM.OperandFastWriteFunctions[17] = "VM:WriteCell(EAX+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[18] = "VM:WriteCell(EBX+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[19] = "VM:WriteCell(ECX+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[20] = "VM:WriteCell(EDX+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[21] = "VM:WriteCell(ESI+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[22] = "VM:WriteCell(EDI+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[23] = "VM:WriteCell(ESP+$SEG,$EXPR)" +ZVM.OperandFastWriteFunctions[24] = "VM:WriteCell(EBP+$SEG,$EXPR)" +-- Write from memory by displacement +ZVM.OperandFastWriteFunctions[25] = "VM:WriteCell($BYTE+$SEG,$EXPR)" +-- Write by register plus immediate +ZVM.OperandFastWriteFunctions[34] = "VM:WriteCell(EAX+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[35] = "VM:WriteCell(EBX+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[36] = "VM:WriteCell(ECX+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[37] = "VM:WriteCell(EDX+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[38] = "VM:WriteCell(ESI+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[39] = "VM:WriteCell(EDI+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[40] = "VM:WriteCell(ESP+$BYTE,$EXPR)" +ZVM.OperandFastWriteFunctions[41] = "VM:WriteCell(EBP+$BYTE,$EXPR)" +-- Extended registers +for reg=0,31 do ZVM.OperandFastWriteFunctions[2048+reg] = "R"..reg.." = $EXPR" end +for reg=0,31 do ZVM.OperandFastWriteFunctions[2080+reg] = "VM:WriteCell(R"..reg.."+$SEG,$EXPR)" end +for reg=0,31 do ZVM.OperandFastWriteFunctions[2144+reg] = "VM:WriteCell(R"..reg.."+$BYTE,$EXPR)" end + + + + +-------------------------------------------------------------------------------- +-- Functions to decode RM bytes (psuedo-READ) +ZVM.OperandFastReadFunctions = {} +-- GP registers +ZVM.OperandFastReadFunctions[01] = "EAX" +ZVM.OperandFastReadFunctions[02] = "EBX" +ZVM.OperandFastReadFunctions[03] = "ECX" +ZVM.OperandFastReadFunctions[04] = "EDX" +ZVM.OperandFastReadFunctions[05] = "ESI" +ZVM.OperandFastReadFunctions[06] = "EDI" +ZVM.OperandFastReadFunctions[07] = "ESP" +ZVM.OperandFastReadFunctions[08] = "EBP" +-- Read from memory by GP +ZVM.OperandFastReadFunctions[17] = "(VM:ReadCell(EAX+$SEG) or 0)" +ZVM.OperandFastReadFunctions[18] = "(VM:ReadCell(EBX+$SEG) or 0)" +ZVM.OperandFastReadFunctions[19] = "(VM:ReadCell(ECX+$SEG) or 0)" +ZVM.OperandFastReadFunctions[20] = "(VM:ReadCell(EDX+$SEG) or 0)" +ZVM.OperandFastReadFunctions[21] = "(VM:ReadCell(ESI+$SEG) or 0)" +ZVM.OperandFastReadFunctions[22] = "(VM:ReadCell(EDI+$SEG) or 0)" +ZVM.OperandFastReadFunctions[23] = "(VM:ReadCell(ESP+$SEG) or 0)" +ZVM.OperandFastReadFunctions[24] = "(VM:ReadCell(EBP+$SEG) or 0)" +-- Read from memory by displacement +ZVM.OperandFastReadFunctions[25] = "(VM:ReadCell($BYTE+$SEG) or 0)" +-- Register plus segment +ZVM.OperandFastReadFunctions[26] = "(EAX+$SEG)" +ZVM.OperandFastReadFunctions[27] = "(EBX+$SEG)" +ZVM.OperandFastReadFunctions[28] = "(ECX+$SEG)" +ZVM.OperandFastReadFunctions[29] = "(EDX+$SEG)" +ZVM.OperandFastReadFunctions[30] = "(ESI+$SEG)" +ZVM.OperandFastReadFunctions[31] = "(EDI+$SEG)" +ZVM.OperandFastReadFunctions[32] = "(ESP+$SEG)" +ZVM.OperandFastReadFunctions[33] = "(EBP+$SEG)" +-- Read by register plus immediate +ZVM.OperandFastReadFunctions[34] = "(VM:ReadCell(EAX+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[35] = "(VM:ReadCell(EBX+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[36] = "(VM:ReadCell(ECX+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[37] = "(VM:ReadCell(EDX+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[38] = "(VM:ReadCell(ESI+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[39] = "(VM:ReadCell(EDI+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[40] = "(VM:ReadCell(ESP+$BYTE) or 0)" +ZVM.OperandFastReadFunctions[41] = "(VM:ReadCell(EBP+$BYTE) or 0)" +-- Register plus immediate +ZVM.OperandFastReadFunctions[42] = "(EAX+$BYTE)" +ZVM.OperandFastReadFunctions[43] = "(EBX+$BYTE)" +ZVM.OperandFastReadFunctions[44] = "(ECX+$BYTE)" +ZVM.OperandFastReadFunctions[45] = "(EDX+$BYTE)" +ZVM.OperandFastReadFunctions[46] = "(ESI+$BYTE)" +ZVM.OperandFastReadFunctions[47] = "(EDI+$BYTE)" +ZVM.OperandFastReadFunctions[48] = "(ESP+$BYTE)" +ZVM.OperandFastReadFunctions[49] = "(EBP+$BYTE)" +-- Extended registers +for reg=0,31 do ZVM.OperandFastReadFunctions[2048+reg] = "R"..reg end +for reg=0,31 do ZVM.OperandFastReadFunctions[2080+reg] = "(VM:ReadCell(R"..reg.."+$SEG) or 0)" end +for reg=0,31 do ZVM.OperandFastReadFunctions[2112+reg] = "(R"..reg.."+$SEG)" end +for reg=0,31 do ZVM.OperandFastReadFunctions[2144+reg] = "(VM:ReadCell(R"..reg.."+$BYTE) or 0)" end +for reg=0,31 do ZVM.OperandFastReadFunctions[2176+reg] = "(R"..reg.."+$BYTE)" end + + + + +-------------------------------------------------------------------------------- +-- Is byte fetch required for the RM +ZVM.NeedFetchByteLookup = {} +ZVM.NeedFetchByteLookup[0] = true +ZVM.NeedFetchByteLookup[25] = true +ZVM.NeedFetchByteLookup[34] = true +ZVM.NeedFetchByteLookup[35] = true +ZVM.NeedFetchByteLookup[36] = true +ZVM.NeedFetchByteLookup[37] = true +ZVM.NeedFetchByteLookup[38] = true +ZVM.NeedFetchByteLookup[39] = true +ZVM.NeedFetchByteLookup[40] = true +ZVM.NeedFetchByteLookup[41] = true +ZVM.NeedFetchByteLookup[42] = true +ZVM.NeedFetchByteLookup[43] = true +ZVM.NeedFetchByteLookup[44] = true +ZVM.NeedFetchByteLookup[45] = true +ZVM.NeedFetchByteLookup[46] = true +ZVM.NeedFetchByteLookup[47] = true +ZVM.NeedFetchByteLookup[48] = true +ZVM.NeedFetchByteLookup[49] = true +ZVM.NeedFetchByteLookup[50] = true + +-- Is interrupt check required for the RM +ZVM.NeedInterruptCheck = {} +ZVM.NeedInterruptCheck[17] = true +ZVM.NeedInterruptCheck[18] = true +ZVM.NeedInterruptCheck[19] = true +ZVM.NeedInterruptCheck[20] = true +ZVM.NeedInterruptCheck[21] = true +ZVM.NeedInterruptCheck[22] = true +ZVM.NeedInterruptCheck[23] = true +ZVM.NeedInterruptCheck[24] = true +ZVM.NeedInterruptCheck[25] = true +ZVM.NeedInterruptCheck[34] = true +ZVM.NeedInterruptCheck[35] = true +ZVM.NeedInterruptCheck[36] = true +ZVM.NeedInterruptCheck[37] = true +ZVM.NeedInterruptCheck[38] = true +ZVM.NeedInterruptCheck[39] = true +ZVM.NeedInterruptCheck[40] = true +ZVM.NeedInterruptCheck[41] = true +for i=1000,2023 do ZVM.NeedInterruptCheck[i] = true end +for i=2048,2207 do ZVM.NeedInterruptCheck[i] = true end + +-- Register lookup table FIXME: add segments +ZVM.NeedRegisterLookup = {} +ZVM.NeedRegisterLookup["EAX"] = 1 +ZVM.NeedRegisterLookup["EBX"] = 2 +ZVM.NeedRegisterLookup["ECX"] = 3 +ZVM.NeedRegisterLookup["EDX"] = 4 +ZVM.NeedRegisterLookup["ESI"] = 5 +ZVM.NeedRegisterLookup["EDI"] = 6 +ZVM.NeedRegisterLookup["ESP"] = 7 +ZVM.NeedRegisterLookup["EBP"] = 8 +for reg=0,31 do ZVM.NeedRegisterLookup["R"..reg] = reg+96 end + + + + +-------------------------------------------------------------------------------- +-- Lookup for LEA instruction +ZVM.OperandEffectiveAddress = {} +-- Read from memory by GP +ZVM.OperandEffectiveAddress[17] = "(VM.EAX+$SEG)" +ZVM.OperandEffectiveAddress[18] = "(VM.EBX+$SEG)" +ZVM.OperandEffectiveAddress[19] = "(VM.ECX+$SEG)" +ZVM.OperandEffectiveAddress[20] = "(VM.EDX+$SEG)" +ZVM.OperandEffectiveAddress[21] = "(VM.ESI+$SEG)" +ZVM.OperandEffectiveAddress[22] = "(VM.EDI+$SEG)" +ZVM.OperandEffectiveAddress[23] = "(VM.ESP+$SEG)" +ZVM.OperandEffectiveAddress[24] = "(VM.EBP+$SEG)" +-- Read from memory by displacement +ZVM.OperandEffectiveAddress[25] = "($BYTE+$SEG)" +-- Read by register plus immediate +ZVM.OperandEffectiveAddress[34] = "(VM.EAX+$BYTE)" +ZVM.OperandEffectiveAddress[35] = "(VM.EBX+$BYTE)" +ZVM.OperandEffectiveAddress[36] = "(VM.ECX+$BYTE)" +ZVM.OperandEffectiveAddress[37] = "(VM.EDX+$BYTE)" +ZVM.OperandEffectiveAddress[38] = "(VM.ESI+$BYTE)" +ZVM.OperandEffectiveAddress[39] = "(VM.EDI+$BYTE)" +ZVM.OperandEffectiveAddress[40] = "(VM.ESP+$BYTE)" +ZVM.OperandEffectiveAddress[41] = "(VM.EBP+$BYTE)" +-- Ports +for i=1000,2024 do ZVM.OperandEffectiveAddress[i] = -i+1000-1 end +-- Extended registers +for reg=0,31 do ZVM.OperandEffectiveAddress[2080+reg] = "(VM.R"..reg.."+$SEG)" end +for reg=0,31 do ZVM.OperandEffectiveAddress[2144+reg] = "(VM.R"..reg.."+$BYTE)" end diff --git a/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_features.lua b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_features.lua new file mode 100644 index 0000000..779413e --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_features.lua @@ -0,0 +1,1121 @@ +-------------------------------------------------------------------------------- +-- Zyelios VM (Zyelios CPU/GPU virtual machine) +-- +-- Implementation for most default ZCPU features (including the data bus) +-------------------------------------------------------------------------------- + + + + +-------------------------------------------------------------------------------- +-- Default configuration +ZVM.RAMSize = 65536 -- Internal RAM size +ZVM.ROMSize = 65536 -- Internal ROM size +ZVM.PCAP = 1 -- Paging capability +ZVM.RQCAP = 1 -- Memory request delaying capability +ZVM.CPUVER = 1000 -- Version reported by CPUID +ZVM.CPUTYPE = 0 -- Processor type +ZVM.ROM = {} + +-- CPUID instruction result +function ZVM:CPUID(index) + if index == 0 then + return self.CPUVER -- CPU version + elseif index == 1 then + return self.RAMSize -- Amount of internal RAM + elseif index == 2 then + return self.CPUTYPE -- 0: ZCPU, 1: ZGPU, 2: ZSPU + elseif index == 3 then + return self.ROMSize -- Amount of internal ROM + end +end + + + + +-------------------------------------------------------------------------------- +-- VM state reset +function ZVM:Reset() + self.IP = 0 -- Instruction pointer + + self.EAX = 0 -- General purpose registers + self.EBX = 0 + self.ECX = 0 + self.EDX = 0 + self.ESI = 0 + self.EDI = 0 + self.ESP = math.max(0,self.RAMSize-1) + self.EBP = 0 + + self.CS = 0 -- Segment pointer registers + self.SS = 0 + self.DS = 0 + self.ES = 0 + self.GS = 0 + self.FS = 0 + self.KS = 0 + self.LS = 0 + + -- Extended registers + for reg=0,31 do self["R"..reg] = 0 end + + -- Stack size register + self.ESZ = math.max(0,self.RAMSize-1) + + self.IDTR = 0 -- Interrupt descriptor table register + self.NIDT = 256 -- Size of interrupt descriptor table + self.EF = 0 -- Extended mode flag + self.PF = 0 -- Protected mode flag + self.MF = 0 -- Memory extended mode flag + self.IF = 1 -- Interrupts enabled flag + self.NIF = nil -- Value of IF flag for next frame + + self.PTBL = 0 -- Page table offset + self.PTBE = 0 -- Page table size + + self.CMPR = 0 -- Compare register + self.XEIP = 0 -- Current instruction address register + self.LADD = 0 -- Last interrupt parameter + self.LINT = 0 -- Last interrupt number + self.TMR = 0 -- Internal timer + self.TIMER = 0 -- Internal clock + self.CPAGE = 0 -- Current page ID + self.PPAGE = 0 -- Previous page ID + + self.BPREC = 48 -- Binary precision for integer emulation mode (default: 48) + self.IPREC = 48 -- Integer precision (48 - floating point mode, 8, 16, 32, 64 - integer mode) + self.VMODE = 2 -- Vector mode (2D, 3D) + + self.CODEBYTES = 0 -- Executed size of code + self.HWDEBUG = 0 -- Hardware debug mode + self.DBGSTATE = 0 -- 0: halt; 1: reset; 2: step fwd and halt; 3: run; 4: read registers; 5: write registers + self.DBGADDR = 0 -- 0: external ports, everything else: absolute memory address + + -- Timer system registers + self.TimerMode = 0 -- 0: disable; NMI: 1: every X seconds; 2: every N ticks + self.TimerRate = 0 -- Seconds or ticks + self.TimerPrevTime = 0 -- Previous fire time + self.TimerAddress = 32 -- Interrupt number to call (modes 1,2) + self.TimerPrevMode = 0 -- Previous timer mode + + -- Internal operation registers + self.MEMRQ = 0 -- Handling a memory request (1: delayed request, 2: read request, 3: write request) + self.MEMADDR = 0 -- Address of the memory request + self.INTR = 0 -- Handling an interrupt + self.BusLock = 0 -- Bus is locked for read/write + self.Idle = 0 -- Idle flag + self.External = 0 -- External IO operation + + -- Misc registers + self.BlockStart = 0 -- Start of the block + self.BlockSize = 0 -- Size of the block + self.HaltPort = 0 -- Unused/obsolete + self.TimerDT = 0 -- Timer deltastep within cached instructions block + + -- Runlevel registers + self.CRL = 0 -- Current runlevel + self.XTRL = 1 -- Runlevel for external IO + + -- Reset internal memory, precompiler data, page table + self.Memory = {} + self.PrecompiledData = {} + self.IsAddressPrecompiled = {} + self.PageData = {} + + -- Restore ROM to memory + self.INTR = 1 + if self.ROMSize > 0 then + for address,value in pairs(self.ROM) do + self:WriteCell(address,value) + end + end + + -- Reset pages + self:SetCurrentPage(0) + self:SetPreviousPage(0) + self.INTR = 0 +end + + + + +-------------------------------------------------------------------------------- +-- Checks if address is valid +local function IsValidAddress(n) + return n and (math.floor(n) == n) and (n >= -140737488355327) and (n <= 140737488355328) +end + + + + +-------------------------------------------------------------------------------- +function ZVM:SignalError(errorCode) + +end + +function ZVM:SignalShutdown() + +end + +function ZVM:ExternalWrite(Address,Value) + if Address >= 0 + then self:Interrupt(7,Address) return false -- MemBus + else return true -- IOBus + end +end + +function ZVM:ExternalRead(Address,Value) + if Address >= 0 + then self:Interrupt(7,Address) return -- MemBus + else return 0 -- IOBus + end +end + + + + +-------------------------------------------------------------------------------- +-- Default WritePort handler +function ZVM:WritePort(Port,Value) + self:WriteCell(-Port-1,Value) +end + + + + +-------------------------------------------------------------------------------- +-- Default ReadPort handler +function ZVM:ReadPort(Port) + return self:ReadCell(-Port-1) +end + + + + +-------------------------------------------------------------------------------- +-- Default ReadCell handler +function ZVM:ReadCell(Address) + -- Check bus lock flag + if self.BusLock == 1 then return end + + -- Cycles required to perform memory read + self.TMR = self.TMR + 5 + + -- Check if address is valid + if not IsValidAddress(Address) then + self:Interrupt(15,Address) + return + end + + -- Do we need to perform page checking + if self.PCAP == 1 and self.MF == 1 then + -- Fetch page + local PageIndex = math.floor(Address / 128) + local Page = self:GetPageByIndex(PageIndex) + + if Page.Trapped == 1 then + self:Interrupt(30,Address) --generate interrupt and continue + end + + -- Check if page is disabled + if Page.Disabled == 1 then + self:Interrupt(7,Address) + return + end + + + -- Permission and remap checks need to happen before override check + -- so that we have data for the override interrupt to process + -- Page permissions + if (self.EF == 1) and (self.CurrentPage.RunLevel > Page.RunLevel) and (Page.Read == 0) then + self:Interrupt(12,Address) + return + end + + -- Page remapping + if (Page.Remapped == 1) and (Page.MappedIndex ~= PageIndex) then + Address = Address % 128 + Page.MappedIndex * 128 + end + local value + -- Perform I/O operation + if (Address >= 0) and (Address < self.RAMSize) then + value = self.Memory[Address] or 0 + else + -- Extra cycles for the external operation + self.TMR = self.TMR + 15 + value = self:ExternalRead(Address) + end + + -- Check if page is overriden + if Page.Override == 1 then + if self.MEMRQ == 4 then -- Data available + self.MEMRQ = 0 + return self.LADD + else -- No data: generate a request + self.MEMRQ = 2 + self.MEMADDR = Address + self.LADD = value + -- Extra cycles for early termination + self.TMR = self.TMR + 10 + return + end + end + end + + -- Perform I/O operation + if (Address >= 0) and (Address < self.RAMSize) then + return self.Memory[Address] or 0 + else + -- Extra cycles for the external operation + self.TMR = self.TMR + 15 + return self:ExternalRead(Address) + end +end + + + + +-------------------------------------------------------------------------------- +-- Default WriteCell handler +function ZVM:WriteCell(Address,Value) + -- Check bus lock flag + if self.BusLock == 1 then return false end + + -- Cycles required to perform memory write + self.TMR = self.TMR + 5 + + -- Check if address is valid + if not IsValidAddress(Address) then + self:Interrupt(15,Address) + return false + end + + -- Invalidate precompiled data + if self.IsAddressPrecompiled[Address] then + for k,v in ipairs(self.IsAddressPrecompiled[Address]) do + self.PrecompiledData[v] = nil + self.IsAddressPrecompiled[Address][k] = nil + end + end + + -- Do we need to perform page checking + if self.PCAP == 1 and self.MF == 1 then + -- Fetch page + local PageIndex = math.floor(Address / 128) + local Page = self:GetPageByIndex(PageIndex) + + if Page.Trapped == 1 then + self:Interrupt(30,Address) -- Generate interrupt and continue + end + + -- Check if page is disabled + if Page.Disabled == 1 then + self:Interrupt(7,Address) + return false + end + + -- MEMRQ: 0 - no action + -- 1 - ??? + -- 2 - read interrupt requested + -- 3 - write interrupt requested + -- 4 - read interrupt handled + -- 5 - write interrupt handled + -- Check if page is overriden + if Page.Override == 1 then + if self.MEMRQ == 5 then -- write IRQ handled, new address/value available + self.MEMRQ = 0 + Address = self.MEMADDR + Value = self.LADD + --return true + else + self.MEMRQ = 3 + self.MEMADDR = Address + self.LADD = Value + + -- Extra cycles for early termination + self.TMR = self.TMR + 10 + return false + end + end + + -- Page permissions + if (self.EF == 1) and (self.CurrentPage.RunLevel > Page.RunLevel) and (Page.Write == 0) then + self:Interrupt(9,Address) + return false + end + + -- Page remapping + if (Page.Remapped == 1) and (Page.MappedIndex ~= PageIndex) then + Address = Address % 128 + Page.MappedIndex * 128 + end + end + + -- Perform I/O operation + if (Address >= 0) and (Address < self.RAMSize) then + self.Memory[Address] = Value + else + -- Extra cycles for the external operation + self.TMR = self.TMR + 15 + return self:ExternalWrite(Address,Value) + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:Push(Value) + -- Check bus lock flag + if self.BusLock == 1 then return false end + + -- Write to stack + self:WriteCell(self.ESP+self.SS, Value) + self.ESP = self.ESP - 1 + + -- Stack check + if self.ESP < 0 then + self.ESP = 0 + self:Interrupt(6,self.ESP) + return false + end + return true +end + + + + +-------------------------------------------------------------------------------- +function ZVM:Pop() + -- Check bus lock flag + if self.BusLock == 1 then return 0 end + + -- Read from stack + self.ESP = self.ESP + 1 + if self.ESP > self.ESZ then + self.ESP = self.ESZ + self:Interrupt(6,self.ESP) + return 0 + end + + local Value = self:ReadCell(self.ESP+self.SS) + if Value then return Value + else self:Interrupt(6,self.ESP) return 0 end +end + + + + +-------------------------------------------------------------------------------- +-- Write value to stack (SSTACK implementation) +function ZVM:WriteToStack(Index,Value) + -- Check bus lock flag + if self.BusLock == 1 then return false end + + -- Write to stack + if (Index > self.ESZ) or (Index < 0) then self:Interrupt(6,Index) return false end + self:WriteCell(self.SS + Index,Value) +end + + + + +-------------------------------------------------------------------------------- +-- Read a value from stack (RSTACK implementation) +function ZVM:ReadFromStack(Index) + -- Check bus lock flag + if self.BusLock == 1 then return 0 end + + -- Read from stack + if (Index > self.ESZ) or (Index < 0) then self:Interrupt(6,Index) return 0 end + local Value = self:ReadCell(self.SS + Index) + if Value then return Value else self:Interrupt(6,Index) return 0 end +end + + + + +-------------------------------------------------------------------------------- +-- Extended mode stuff +local defaultPage = { + Disabled = 0, -- 00 Is page disabled? Set to 1 to disable this page + Remapped = 0, -- 01 Is page remapped? Set to 1 to remap this page + Trapped = 0, -- 02 Page must generate NMI 30 (page trap) upon access + Override = 0, -- 03 Page overrides reading/writing from it + Read = 0, -- 05 Read permissions (0: allowed, 1: disabled) + Write = 0, -- 06 Write permissions (0: allowed, 1: disabled) + Execute = 0, -- 07 Execute permissions (0: allowed, 1: disabled) + RunLevel = 0, + MappedIndex = 0, +} + +local errorPage = { + Disabled = 1, + Remapped = 0, + Trapped = 0, + Override = 0, + Read = 0, + Write = 0, + Execute = 0, + RunLevel = 0, + MappedIndex = 0, +} + +function ZVM:ResetPage(index) + local newPage = {} + newPage.Disabled = 0 + newPage.Remapped = 0 + newPage.Trapped = 0 + newPage.Override = 0 + newPage.Read = 1 + newPage.Write = 1 + newPage.Execute = 1 + newPage.RunLevel = 0 + newPage.MappedIndex = 0 + + self.PageData[index] = newPage +end + +function ZVM:SetPagePermissions(index,permissionMask,mappedPage) + if not self.PageData[index] then self:ResetPage(index) end + local targetPage = self.PageData[index] + + local permissionBits = self:IntegerToBinary(permissionMask) + local runlevel = math.floor(permissionMask / 256) % 256 + + targetPage.Disabled = permissionBits[0] + targetPage.Remapped = permissionBits[1] + targetPage.Trapped = permissionBits[2] + targetPage.Override = permissionBits[3] + targetPage.Read = 1-permissionBits[5] + targetPage.Write = 1-permissionBits[6] + targetPage.Execute = 1-permissionBits[7] + targetPage.RunLevel = runlevel + targetPage.MappedIndex = mappedPage +end + +function ZVM:GetPagePermissions(index) + if not self.PageData[index] then self:ResetPage(index) end + local sourcePage = self.PageData[index] + + local permissionBits = {} + permissionBits[0] = sourcePage.Disabled + permissionBits[1] = sourcePage.Remapped + permissionBits[2] = sourcePage.Trapped + permissionBits[3] = sourcePage.Override + permissionBits[5] = 1-sourcePage.Read + permissionBits[6] = 1-sourcePage.Write + permissionBits[7] = 1-sourcePage.Execute + + return self:BinaryToInteger(permissionBits) + sourcePage.RunLevel * 256,sourcePage.MappedIndex +end + + + + +-------------------------------------------------------------------------------- +function ZVM:GetPageByIndex(index) + if self.PCAP == 1 then + if self.MF == 1 then + -- Find page entry offset + local pageEntryOffset + if (index >= self.PTBE) or (index < 0) + then pageEntryOffset = self.PTBL + else pageEntryOffset = self.PTBL+(index+1)*2 + end + + -- Read page entry + self.PCAP = 0 -- Stop infinite recursive page table lookup + local pagePermissionMask = self:ReadCell(pageEntryOffset+0) + local pageMappedTo = self:ReadCell(pageEntryOffset+1) + self.PCAP = 1 + + if (not pagePermissionMask) or (not pageMappedTo) then + self:Interrupt(13,8) + return errorPage + end + + self:SetPagePermissions(index,pagePermissionMask,pageMappedTo) + return self.PageData[index] + else + if not self.PageData[index] then self:ResetPage(index) end + return self.PageData[index] + end + else + return defaultPage + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:SetPageByIndex(index) + if self.PCAP == 1 then + if self.MF == 1 then + -- Find page entry offset + local pageEntryOffset + if (index >= self.PTBE) or (index < 0) + then pageEntryOffset = self.PTBL + else pageEntryOffset = self.PTBL+(index+1)*2 + end + + -- Write page entry + local pagePermissionMask,pageMappedTo = self:GetPagePermissions(index) + self.PCAP = 0 -- Stop possible infinite recursive page redirection + self:WriteCell(pageEntryOffset+0,pagePermissionMask) + self:WriteCell(pageEntryOffset+1,pageMappedTo) + self.PCAP = 1 + end + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:SetCurrentPage(index) + if self.PCAP == 1 then + self.CurrentPage = self:GetPageByIndex(index) + else + self.CurrentPage = defaultPage + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:SetPreviousPage(index) + if self.PCAP == 1 then + self.PreviousPage = self:GetPageByIndex(index) + else + self.PreviousPage = defaultPage + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:Jump(newIP,newCS) + local targetXEIP = newIP + (newCS or self.CS) + local targetPage = self:GetPageByIndex(math.floor(targetXEIP/128)) + + -- Do not allow execution if not calling from kernel page + if (self.PCAP == 1) and (targetPage.Execute == 0) and (self.CurrentPage.RunLevel ~= 0) then + self:Interrupt(14,newIP) + return -- Jump failed + end + + self.IP = newIP + if newCS then + self.CS = newCS + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:ExternalInterrupt(interruptNo) + if ((self.IF == 1) and + self:Push(self.LS) and + self:Push(self.KS) and + self:Push(self.ES) and + self:Push(self.GS) and + self:Push(self.FS) and + self:Push(self.DS) and + self:Push(self.SS) and + self:Push(self.CS) and + + self:Push(self.EDI) and + self:Push(self.ESI) and + self:Push(self.ESP) and + self:Push(self.EBP) and + self:Push(self.EDX) and + self:Push(self.ECX) and + self:Push(self.EBX) and + self:Push(self.EAX) and + + self:Push(self.CMPR) and + self:Push(self.IP)) then + self:Interrupt(interruptNo,0,1) + end +end + + + + +-------------------------------------------------------------------------------- +function ZVM:Interrupt(interruptNo,interruptParameter,isExternal,cascadeInterrupt) + -- Do not allow cascade interrupts unless they are explicty stated as such + if (not cascadeInterrupt) and (self.INTR == 1) then return end + + -- Interrupt is active, lock the bus to prevent any further read/write + self.INTR = 1 + + -- Set registers + self.LINT = interruptNo + self.LADD = interruptParameter or self.XEIP + + -- Output an error externally + local fractionalParameter = self.LADD * (10^math.floor(-math.log10(math.abs(self.LADD)+1e-12)-1)) + self:SignalError(fractionalParameter+interruptNo) + + -- Check if interrupts handling is enabled + if self.IF == 1 then + if self.EF == 1 then -- Extended mode + -- Boundary check + if (interruptNo < 0) or (interruptNo > 255) then + if not cascadeInterrupt then self:Interrupt(13,3,false,true) end + return + end + + -- Check if basic logic must be used + if interruptNo > self.NIDT-1 then + if interruptNo == 0 then + self:Reset() + end + if interruptNo == 1 then + self:SignalShutdown() + end + return + end + + -- Calculate absolute offset in the interrupt table + local interruptOffset = self.IDTR + interruptNo*4 + + -- Disable bus lock, set the current page for read operations to succeed + self.BusLock = 0 + self:SetCurrentPage(math.floor(interruptOffset/128)) + + self.IF = 0 + self.INTR = 0 + local prevPCAP = self.PCAP + self.PCAP = 0 -- Use absolute addressing + local IP = self:ReadCell(interruptOffset+0) + local CS = self:ReadCell(interruptOffset+1) + local NewPTB = self:ReadCell(interruptOffset+2) + local FLAGS = self:IntegerToBinary(self:ReadCell(interruptOffset+3)) + self.PCAP = prevPCAP + self.IF = 1 + if self.INTR == 1 then + if not cascadeInterrupt then self:Interrupt(13,2,false,true) end + return + else + self.INTR = 1 + end + + -- Set previous page to trigger same logic as if CALL-ing from a privilegied page + self:SetCurrentPage(math.floor(self.XEIP/128)) + self:SetPreviousPage(math.floor(interruptOffset/128)) + self.BusLock = 1 + + --Flags: + --3 [8 ] = CMPR shows if interrupt occured + --4 [16] = Interrupt does not set CS + --5 [32] = Interrupt enabled + --6 [64] = NMI interrupt + --7 [128] = Replace PTBL with NewPTE (overrides #8) + --8 [256] = Replace PTBE with NewPTE + --9 [512] = Push extended registers (R0-R31) + + if isExternal and (FLAGS[6] ~= 1) then + if not cascadeInterrupt then self:Interrupt(13,4,false,true) end + return + end + + if FLAGS[5] == 1 then -- Interrupt enabled + -- Push extended registers + self.BusLock = 0 + if FLAGS[9] == 1 then + for i=31,0,-1 do + self:Push(self["R"..i]) + end + end + + -- Push return data + self.IF = 0 + self.INTR = 0 + self:Push(self.IP) + self:Push(self.CS) + self.IF = 1 + if self.INTR == 1 then + if not cascadeInterrupt then self:Interrupt(13,6,false,true) end + return + else + self.INTR = 1 + end + --self.BusLock = 1 + + -- Perform a short or a long jump + self.IF = 0 + self.INTR = 0 + if FLAGS[4] == 0 + then self:Jump(IP,CS) + else self:Jump(IP) + end + self.IF = 1 + if self.INTR == 1 then + if not cascadeInterrupt then self:Interrupt(13,7,false,true) end + return + else + self.INTR = 1 + end + + -- Set CMPR + if FLAGS[3] == 1 then + self.CMPR = 1 + end + else + if interruptNo == 0 then + self:Reset() + end + if interruptNo == 1 then + self:SignalShutdown() + end + if FLAGS[3] == 1 then + self.CMPR = 1 + end + end + + if FLAGS[7] == 1 then + self.PTBL = NewPTB + elseif FLAGS[8] == 1 then + self.PTBE = 1 + end + + elseif self.PF == 1 then -- Compatibility extended mode + -- Boundary check + if (interruptNo < 0) or (interruptNo > 255) then + if not cascadeInterrupt then self:Interrupt(13,3,false,true) end + return + end + + -- Memory size check + if self.RAMSize < 512 then + if not cascadeInterrupt then self:Interrupt(13,5,false,true) end + return + end + + -- Calculate absolute offset in the interrupt table + local interruptOffset = self.IDTR + interruptNo*2 + + if interruptOffset > self.RAMSize-2 then interruptOffset = self.RAMSize-2 end + if interruptOffset < 0 then interruptOffset = 0 end + + interruptOffset = self.Memory[interruptOffset] + local interruptFlags = self.Memory[interruptOffset+1] + if (interruptFlags == 32) or (interruptFlags == 96) then + self.BusLock = 0 + self.IF = 0 + self.INTR = 0 + if (interruptNo == 4 ) or + (interruptNo == 7 ) or + (interruptNo == 9 ) or + (interruptNo == 10) then + self:Push(self.LADD) + end + if (interruptNo == 4 ) or + (interruptNo == 31) then + self:Push(self.LINT) + end + if self:Push(self.IP) and self:Push(self.XEIP) then + self:Jump(interruptOffset) + end + self.IF = 1 + if self.INTR == 1 then + if not cascadeInterrupt then self:Interrupt(13,6,false,true) end + return + else + self.INTR = 1 + end + self.CMPR = 0 + self.BusLock = 1 + else + if interruptNo == 1 then + self:SignalShutdown() + end + self.CMPR = 1 + end + else + if (interruptNo < 0) or (interruptNo > 255) or (interruptNo > self.NIDT-1) then + -- Interrupt not handled + return + end + if interruptNo == 0 then + self:Reset() + return + end + if interruptNo ~= 31 then --Don't die on the debug trap + self:SignalShutdown() + end + end + end + + -- Unlock the bus + self.BusLock = 0 +end + + + + +-------------------------------------------------------------------------------- +-- Timer firing checks +function ZVM:TimerLogic() + if self.TimerMode ~= self.TimerPrevMode then + if self.TimerMode == 1 then + self.TimerPrevTime = self.TIMER + elseif self.TimerMode == 2 then + self.TimerPrevTime = self.TMR + end + self.TimerPrevMode = self.TimerMode + end + + if self.TimerMode ~= 0 then + if self.TimerMode == 1 then + if (self.TIMER - self.TimerPrevTime) >= self.TimerRate then + self:ExternalInterrupt(math.floor(self.TimerAddress)) + self.TimerPrevTime = self.TIMER + end + elseif self.TimerMode == 2 then + if (self.TMR - self.TimerPrevTime) >= self.TimerRate then + self:ExternalInterrupt(math.floor(self.TimerAddress)) + self.TimerPrevTime = self.TMR + end + end + end +end + + + + +-------------------------------------------------------------------------------- +-- Vector reading/writing instructions +function ZVM:ReadVector2f(address) + if address == 0 then + return { x = 0, y = 0, z = 0, w = 0 } + else + return { x = self:ReadCell(address+0) or 0, + y = self:ReadCell(address+1) or 0, + z = 0, + w = 0 } + end +end + +function ZVM:ReadVector3f(address) + if address == 0 then + return { x = 0, y = 0, z = 0, w = 0 } + else + return { x = self:ReadCell(address+0) or 0, + y = self:ReadCell(address+1) or 0, + z = self:ReadCell(address+2) or 0, + w = 0 } + end +end + +function ZVM:ReadVector4f(address) + if address == 0 then + return { x = 0, y = 0, z = 0, w = 0 } + else + return { x = self:ReadCell(address+0) or 0, + y = self:ReadCell(address+1) or 0, + z = self:ReadCell(address+2) or 0, + w = self:ReadCell(address+3) or 0 } + end +end + +function ZVM:ReadMatrix(address) + local resultMatrix = {} + for i= 0,15 do resultMatrix[i] = self:ReadCell(address+i) or 0 end + return resultMatrix +end + +function ZVM:WriteVector2f(address,vector) + self:WriteCell(address+0,vector.x) + self:WriteCell(address+1,vector.y) +end + +function ZVM:WriteVector3f(address,vector) + self:WriteCell(address+0,vector.x) + self:WriteCell(address+1,vector.y) + self:WriteCell(address+2,vector.z) +end + +function ZVM:WriteVector4f(address,vector) + self:WriteCell(address+0,vector.x) + self:WriteCell(address+1,vector.y) + self:WriteCell(address+2,vector.z) + self:WriteCell(address+3,vector.w) +end + +function ZVM:WriteMatrix(address,matrix) + for i=0,15 do self:WriteCell(address+i,matrix[i]) end +end + + + + +-------------------------------------------------------------------------------- +-- Converts integer to binary representation +function ZVM:IntegerToBinary(n) + -- Check sign + n = math.floor(n or 0) + if n < 0 then + local bits = self:IntegerToBinary(2^self.IPREC + n) + bits[self.IPREC-1] = 1 + return bits + end + + -- Convert to binary + local bits = {} + local cnt = 0 + while (n > 0) and (cnt < self.IPREC) do + local bit = n % 2 + bits[cnt] = bit + + n = (n-bit)/2 + cnt = cnt + 1 + end + + -- Fill in missing zero bits + while cnt < self.IPREC do + bits[cnt] = 0 + cnt = cnt + 1 + end + + return bits +end + + + + +-------------------------------------------------------------------------------- +-- Converts binary representation back to integer +function ZVM:BinaryToInteger(bits) + local result = 0 + + -- Convert to integer + for i = 0, self.IPREC-2 do + result = result + (bits[i] or 0) * (2 ^ i) + end + + -- Add sign + if bits[self.IPREC-1] == 1 then + return -2^(self.IPREC-1)+result + else + return result + end +end + + + + +-------------------------------------------------------------------------------- +-- Binary OR +function ZVM:BinaryOr(m,n) + local bits_m = self:IntegerToBinary(m) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, self.IPREC-1 do + bits[i] = math.min(1,bits_m[i]+bits_n[i]) + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary AND +function ZVM:BinaryAnd(m,n) + local bits_m = self:IntegerToBinary(m) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, self.IPREC-1 do + bits[i] = bits_m[i]*bits_n[i] + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary NOT +function ZVM:BinaryNot(n) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, self.IPREC-1 do + bits[i] = 1-bits_n[i] + end + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary XOR +function ZVM:BinaryXor(m,n) + local bits_m = self:IntegerToBinary(m) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = 0, self.IPREC-1 do + bits[i] = (bits_m[i]+bits_n[i]) % 2 + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary shift right +function ZVM:BinarySHR(n,cnt) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + local rslt = #bits_n + for i = 0, self.IPREC-cnt-1 do + bits[i] = bits_n[i+cnt] + end + for i = self.IPREC-cnt,rslt-1 do + bits[i] = 0 + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Binary shift left +function ZVM:BinarySHL(n,cnt) + local bits_n = self:IntegerToBinary(n) + local bits = {} + + for i = cnt,self.IPREC-1 do + bits[i] = bits_n[i-cnt] + end + for i = 0,cnt-1 do + bits[i] = 0 + end + + return self:BinaryToInteger(bits) +end + + + + +-------------------------------------------------------------------------------- +-- Reset to initial state +ZVM:Reset() diff --git a/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_opcodes.lua b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_opcodes.lua new file mode 100644 index 0000000..28a44ce --- /dev/null +++ b/garrysmod/addons/feature-wire/lua/wire/zvm/zvm_opcodes.lua @@ -0,0 +1,1657 @@ +-------------------------------------------------------------------------------- +-- Zyelios VM (Zyelios CPU/GPU virtual machine) +-- +-- Primary opcode set +-------------------------------------------------------------------------------- + + + + +-- Initialize opcode count lookup table +ZVM.OperandCount = {} +for _,instruction in pairs(CPULib.InstructionTable) do + ZVM.OperandCount[instruction.Opcode] = instruction.OperandCount +end + + + + +-- Initialize runlevel lookup table +ZVM.OpcodeRunLevel = {} +for _,instruction in pairs(CPULib.InstructionTable) do + if instruction.Privileged then + ZVM.OpcodeRunLevel[instruction.Opcode] = 0 + end +end + + + + +-------------------------------------------------------------------------------- +-- Hand-leg guide to writing ZCPU microcode +-- self:Dyn_Emit(code) +-- Emits microcode to output stream +-- +-- self:Dyn_EmitOperand(OP,code,emitNow) +-- self:Dyn_EmitOperand(code) +-- Emits write to specific operand (if no operant specified - then operand #1) +-- Passing emitNow as true will emit operand at the current spot in microcode, +-- and set it later (must be done if operand is set inside block). +-- +-- self:Dyn_EmitState() +-- Pushes state update - all global registers are set to their local values +-- +-- self:Dyn_EmitBreak(emitIP) +-- Emits a return (does not push state). Emits new IP if required +-- +-- self:Dyn_EmitForceRegisterGlobal(register) +-- Forces a specific register to be global (so VM.EAX has valid value) +-- +-- self:Dyn_EmitForceRegisterLocal(register) +-- Forces a specific register to be local (so EAX has valid value) +-- Marks register as changed +-- +-- self:Dyn_EmitRegisterValueChanged(register) +-- Marks that there now exists local state for the register +-- +-- self:Dyn_EmitInterrupt(intNo,intParam) +-- Emits interrupt call +-- +-- self:Dyn_EmitInterruptCheck() +-- Emits interrupt check + + + +-------------------------------------------------------------------------------- +-- Load all opcodes +ZVM.OpcodeTable = {} + +ZVM.OpcodeTable[0] = function(self) --END (STOP) + self:Dyn_EmitInterrupt("2","0") + self.PrecompileBreak = true -- Stop precompiler from following further +end +ZVM.OpcodeTable[1] = function(self) --JNE + self:Dyn_Emit("if VM.CMPR ~= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[2] = function(self) --JMP + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self.PrecompileBreak = true -- Stop precompiler from following further +end +ZVM.OpcodeTable[3] = function(self) --JG + self:Dyn_Emit("if VM.CMPR > 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[4] = function(self) --JGE + self:Dyn_Emit("if VM.CMPR >= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[5] = function(self) --JL + self:Dyn_Emit("if VM.CMPR < 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +-- self.PrecompileBreak = true +end +ZVM.OpcodeTable[6] = function(self) --JLE + self:Dyn_Emit("if VM.CMPR <= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[7] = function(self) --JE + self:Dyn_Emit("if VM.CMPR == 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[8] = function(self) --CPUID + self:Dyn_Emit("EAX = VM:CPUID($1)") + self:Dyn_EmitRegisterValueChanged("EAX") +end +ZVM.OpcodeTable[9] = function(self) --PUSH + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("VM:Push($1)") + self:Dyn_EmitInterruptCheck() +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[10] = function(self) --ADD + self:Dyn_EmitOperand("$1 + $2") +end +ZVM.OpcodeTable[11] = function(self) --SUB + self:Dyn_EmitOperand("$1 - $2") +end +ZVM.OpcodeTable[12] = function(self) --MUL + self:Dyn_EmitOperand("$1 * $2") +end +ZVM.OpcodeTable[13] = function(self) --DIV + self:Dyn_Emit("$L OP = $2") + self:Dyn_EmitOperand("$1 / OP") + self:Dyn_Emit("if math.abs(OP) < 1e-12 then") + self:Dyn_EmitInterrupt("3","0") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[14] = function(self) --MOV + self:Dyn_EmitOperand("$2") +end +ZVM.OpcodeTable[15] = function(self) --CMP + self:Dyn_Emit("VM.CMPR = $1 - $2") +end +ZVM.OpcodeTable[16] = function(self) --RD + self:Dyn_Emit("$L OP,ANS = $2,0") + self:Dyn_EmitOperand("ANS") + self:Dyn_Emit("if VM.Memory[OP] then") + self:Dyn_Emit("ANS = VM.Memory[OP]") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[17] = function(self) --WD + self:Dyn_Emit("$L ADDR = math.floor($1)") + self:Dyn_Emit("if (ADDR >= 0) and (ADDR <= 65535) then") + self:Dyn_Emit("VM.Memory[ADDR] = $2") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[18] = function(self) --MIN + self:Dyn_EmitOperand("math.min($1,$2)") +end +ZVM.OpcodeTable[19] = function(self) --MAX + self:Dyn_EmitOperand("math.max($1,$2)") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[20] = function(self) --INC + self:Dyn_EmitOperand("$1 + 1") +end +ZVM.OpcodeTable[21] = function(self) --DEC + self:Dyn_EmitOperand("$1 - 1") +end +ZVM.OpcodeTable[22] = function(self) --NEG + self:Dyn_EmitOperand("-$1") +end +ZVM.OpcodeTable[23] = function(self) --RAND + self:Dyn_EmitOperand("math.random()") +end +ZVM.OpcodeTable[24] = function(self) --LOOP + self:Dyn_EmitForceRegisterLocal("ECX") + self:Dyn_Emit("ECX = ECX - 1") + self:Dyn_Emit("if ECX ~= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[25] = function(self) --LOOPA + self:Dyn_EmitForceRegisterLocal("EAX") + self:Dyn_Emit("EAX = EAX - 1") + self:Dyn_Emit("if EAX ~= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[26] = function(self) --LOOPB + self:Dyn_EmitForceRegisterLocal("EBX") + self:Dyn_Emit("EBX = EBX - 1") + self:Dyn_Emit("if VM.EBX ~= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[27] = function(self) --LOOPD + self:Dyn_EmitForceRegisterLocal("EDX") + self:Dyn_Emit("EDX = EDX - 1") + self:Dyn_Emit("if EDX ~= 0 then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[28] = function(self) --SPG + self:Dyn_Emit("$L IDX = math.floor($1 / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if VM.CurrentPage.RunLevel <= PAGE.RunLevel then") + self:Dyn_Emit("PAGE.Read = 1") + self:Dyn_Emit("PAGE.Write = 0") + self:Dyn_Emit("VM:SetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("11","IDX") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[29] = function(self) --CPG + self:Dyn_Emit("$L idx = math.floor($1 / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if VM.CurrentPage.RunLevel <= VM.Page[idx].RunLevel then") + self:Dyn_Emit("PAGE.Read = 1") + self:Dyn_Emit("PAGE.Write = 1") + self:Dyn_Emit("VM:SetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("11","IDX") + self:Dyn_Emit("end") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[30] = function(self) --POP + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_EmitOperand(1,"VM:Pop()",true) + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[31] = function(self) --CALL + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + + self.PrecompileBreak = true +end +ZVM.OpcodeTable[32] = function(self) --BNOT + self:Dyn_EmitOperand("VM:BinaryNot($1)") +end +ZVM.OpcodeTable[33] = function(self) --FINT + self:Dyn_EmitOperand("math.floor($1)") +end +ZVM.OpcodeTable[34] = function(self) --RND + self:Dyn_EmitOperand("math.Round($1)") +end +ZVM.OpcodeTable[35] = function(self) --FFRAC + self:Dyn_Emit("$L OP = $1") + self:Dyn_EmitOperand("OP - math.floor(OP)") +end +ZVM.OpcodeTable[36] = function(self) --FINV + self:Dyn_Emit("$L OP = $1") + self:Dyn_EmitOperand("1 / OP") + self:Dyn_Emit("if math.abs(OP) < 1e-12 then") + self:Dyn_EmitInterrupt("3","1") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[37] = function(self) --HALT + self:Dyn_Emit("VM.HaltPort = math.floor($1)") +end +ZVM.OpcodeTable[38] = function(self) --FSHL + self:Dyn_EmitOperand("$1 * 2") +end +ZVM.OpcodeTable[39] = function(self) --FSHR + self:Dyn_EmitOperand("$1 / 2") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[40] = function(self) --RET + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("$L IP = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Jump(IP)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + + self.PrecompileBreak = true +end +ZVM.OpcodeTable[41] = function(self) --IRET + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.EF == 1 then") + self:Dyn_Emit("$L CS = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L IP = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Jump(IP,CS)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("else") + self:Dyn_Emit("$L IP = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Jump(IP,CS)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + + self.PrecompileBreak = true +end +ZVM.OpcodeTable[42] = function(self) --STI + self:Dyn_Emit("VM.NIF = 1") +end +ZVM.OpcodeTable[43] = function(self) --CLI + self:Dyn_Emit("VM.IF = 0") +end +ZVM.OpcodeTable[44] = function(self) --STP + self:Dyn_Emit("VM.PF = 1") +end +ZVM.OpcodeTable[45] = function(self) --CLP + self:Dyn_Emit("VM.PF = 0") +end +ZVM.OpcodeTable[46] = function(self) --STD + if self.MicrocodeDebug then + self:Dyn_Emit("VM.Debug = true") + end +end +ZVM.OpcodeTable[47] = function(self) --RETF + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("$L IP = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L CS = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Jump(IP,CS)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + + self.PrecompileBreak = true +end +ZVM.OpcodeTable[48] = function(self) --STEF + self:Dyn_Emit("VM.EF = 1") +end +ZVM.OpcodeTable[49] = function(self) --CLEF + self:Dyn_Emit("VM.EF = 0") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[50] = function(self) --AND + self:Dyn_Emit("$L OP = 0") + self:Dyn_EmitOperand("OP") + self:Dyn_Emit("if ($1 > 0) and ($2 > 0) then") + self:Dyn_Emit("OP = 1") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[51] = function(self) --OR + self:Dyn_Emit("$L OP = 0") + self:Dyn_EmitOperand("OP") + self:Dyn_Emit("if ($1 > 0) or ($2 > 0) then") + self:Dyn_Emit("OP = 1") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[52] = function(self) --XOR + self:Dyn_Emit("$L OP1,OP2 = $1,$2") + self:Dyn_Emit("$L OP = 0") + self:Dyn_EmitOperand("OP") + self:Dyn_Emit("if ((OP1 > 0) and (OP2 <= 0)) or") + self:Dyn_Emit(" ((OP1 <= 0) and (OP2 > 0)) then") + self:Dyn_Emit("OP = 1") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[53] = function(self) --FSIN + self:Dyn_EmitOperand("math.sin($2)") +end +ZVM.OpcodeTable[54] = function(self) --FCOS + self:Dyn_EmitOperand("math.cos($2)") +end +ZVM.OpcodeTable[55] = function(self) --FTAN + self:Dyn_EmitOperand("math.tan($2)") +end +ZVM.OpcodeTable[56] = function(self) --FASIN + self:Dyn_EmitOperand("math.asin($2)") +end +ZVM.OpcodeTable[57] = function(self) --FACOS + self:Dyn_EmitOperand("math.acos($2)") +end +ZVM.OpcodeTable[58] = function(self) --FATAN + self:Dyn_EmitOperand("math.atan($2)") +end +ZVM.OpcodeTable[59] = function(self) --MOD + self:Dyn_EmitOperand("math.fmod($1,$2)") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[60] = function(self) --BIT + self:Dyn_Emit("$L BITS = VM:IntegerToBinary($1)") + self:Dyn_Emit("VM.CMPR = BITS[math.floor($2)] or 0") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[61] = function(self) --SBIT + self:Dyn_Emit("$L BITS = VM:IntegerToBinary($1)") + self:Dyn_Emit("BITS[math.floor($2)] = 1") + self:Dyn_EmitOperand("VM:BinaryToInteger(BITS)") + self:Dyn_Emit("VM.TMR = VM.TMR + 20") +end +ZVM.OpcodeTable[62] = function(self) --CBIT + self:Dyn_Emit("$L BITS = VM:IntegerToBinary($1)") + self:Dyn_Emit("BITS[math.floor($2)] = 0") + self:Dyn_EmitOperand("VM:BinaryToInteger(BITS)") + self:Dyn_Emit("VM.TMR = VM.TMR + 20") +end +ZVM.OpcodeTable[63] = function(self) --TBIT + self:Dyn_Emit("$L BITS = VM:IntegerToBinary($1)") + self:Dyn_Emit("BITS[math.floor($2)] = 1 - (BITS[math.floor($2)] or 0)") + self:Dyn_EmitOperand("VM:BinaryToInteger(BITS)") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[64] = function(self) --BAND + self:Dyn_EmitOperand("VM:BinaryAnd($1,$2)") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[65] = function(self) --BOR + self:Dyn_EmitOperand("VM:BinaryOr($1,$2)") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[66] = function(self) --BXOR + self:Dyn_EmitOperand("VM:BinaryXor($1,$2)") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[67] = function(self) --BSHL + self:Dyn_EmitOperand("VM:BinarySHL($1,$2)") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[68] = function(self) --BSHR + self:Dyn_EmitOperand("VM:BinarySHR($1,$2)") + self:Dyn_Emit("VM.TMR = VM.TMR + 30") +end +ZVM.OpcodeTable[69] = function(self) --JMPF + self:Dyn_Emit("VM:Jump($1,$2)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self.PrecompileBreak = true +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[70] = function(self) --EXTINT + self:Dyn_EmitState() + self:Emit("VM.IP = "..(self.PrecompileIP or 0)) + self:Emit("VM.XEIP = "..(self.PrecompileTrueXEIP or 0)) + self:Dyn_Emit("VM:ExternalInterrupt(math.floor($1))") + self:Dyn_EmitBreak() + self.PrecompileBreak = true +end +ZVM.OpcodeTable[71] = function(self) --CNE + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.CMPR ~= 0 then") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[72] = function(self) --CJMP + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[73] = function(self) --CG + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.CMPR > 0 then") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[74] = function(self) --CGE + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.CMPR >= 0 then") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[75] = function(self) --CL + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.CMPR < 0 then") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[76] = function(self) --CLE + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.CMPR <= 0 then") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[77] = function(self) --CE + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM.CMPR == 0 then") + self:Dyn_Emit("if VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[78] = function(self) --MCOPY + self:Dyn_EmitForceRegisterLocal("ESI") + self:Dyn_EmitForceRegisterLocal("EDI") + self:Dyn_Emit("for i = 1,math.Clamp($1,0,8192) do") + self:Dyn_Emit("$L VAL") + self:Dyn_Emit("VAL = VM:ReadCell(ESI)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteCell(EDI,VAL)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("EDI = EDI + 1") + self:Dyn_Emit("ESI = ESI + 1") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[79] = function(self) --MXCHG + self:Dyn_EmitForceRegisterLocal("ESI") + self:Dyn_EmitForceRegisterLocal("EDI") + self:Dyn_Emit("for i = 1,math.Clamp($1,0,8192) do") + self:Dyn_Emit("$L VAL1,VAL2") + self:Dyn_Emit("VAL1 = VM:ReadCell(ESI)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VAL2 = VM:ReadCell(EDI)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteCell(EDI,VAL1)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteCell(ESI,VAL2)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("EDI = EDI + 1") + self:Dyn_Emit("ESI = ESI + 1") + self:Dyn_Emit("end") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[80] = function(self) --FPWR + self:Dyn_EmitOperand("$1^$2") +end +ZVM.OpcodeTable[81] = function(self) --XCHG + self:Dyn_Emit("$L L0,L1 = $1,$2") + self:Dyn_EmitOperand(1,"L1") + self:Dyn_EmitOperand(2,"L0") +end +ZVM.OpcodeTable[82] = function(self) --FLOG + self:Dyn_EmitOperand("math.log($2)") +end +ZVM.OpcodeTable[83] = function(self) --FLOG10 + self:Dyn_EmitOperand("math.log10($2)") +end +ZVM.OpcodeTable[84] = function(self) --IN + self:Dyn_EmitOperand("VM:ReadPort($2)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[85] = function(self) --OUT + self:Dyn_Emit("VM:WritePort($1,$2)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[86] = function(self) --FABS + self:Dyn_EmitOperand("math.abs($2)") +end +ZVM.OpcodeTable[87] = function(self) --FSGN + self:Dyn_Emit("$L OP = $2") + self:Dyn_Emit("if OP > 0 then") + self:Dyn_EmitOperand(1,"1",true) + self:Dyn_Emit("elseif OP < 0 then") + self:Dyn_EmitOperand(1,"-1",true) + self:Dyn_Emit("else") + self:Dyn_EmitOperand(1,"0",true) + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[88] = function(self) --FEXP + self:Dyn_EmitOperand("math.exp($2)") +end +ZVM.OpcodeTable[89] = function(self) --CALLF + self:Dyn_EmitForceRegisterGlobal("CS") + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("if VM:Push(VM.CS) and VM:Push("..self.PrecompileIP..") then") + self:Dyn_Emit("VM:Jump($1,$2)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + + self.PrecompileBreak = true +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[90] = function(self) --FPI + self:Dyn_EmitOperand("3.141592653589793") +end +ZVM.OpcodeTable[91] = function(self) --FE + self:Dyn_EmitOperand("2.718281828459045") +end +ZVM.OpcodeTable[92] = function(self) --INT + self:Dyn_EmitInterrupt("$1","0") +end +ZVM.OpcodeTable[93] = function(self) --TPG + self:Dyn_Emit("$L TADD = math.floor($1*128)") + self:Dyn_Emit("$L OLDIF = VM.IF") + self:Dyn_Emit("$L OP = $1") + self:Dyn_Emit("VM.IF = 0") + self:Dyn_Emit("VM.CMPR = 0") + self:Dyn_Emit("while TADD < OP*128+128 do") + self:Dyn_Emit("$L VAL = VM:ReadCell(TADD)") + self:Dyn_Emit("if VM.INTR == 1 then") + self:Dyn_Emit("VM.CMPR = TADD") + self:Dyn_Emit("TADD = OP*128+128") + self:Dyn_Emit("end") + self:Dyn_Emit("TADD = TADD+1") + self:Dyn_Emit("end") + self:Dyn_Emit("VM.INTR = 0") + self:Dyn_Emit("VM.IF = OLDIF") +end +ZVM.OpcodeTable[94] = function(self) --FCEIL + self:Dyn_EmitOperand("math.ceil($1)") +end +ZVM.OpcodeTable[95] = function(self) --ERPG + self:Dyn_Emit("$L OP = $1") + self:Dyn_Emit("if (OP >= 0) and (OP < VM.ROMSize/128) then") + self:Dyn_Emit("$L TADD = OP*128") + self:Dyn_Emit("while TADD < OP*128+128 do") + self:Dyn_Emit("VM.ROM[TADD] = nil") + self:Dyn_Emit("TADD = TADD+1") + self:Dyn_Emit("end") + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("12","0") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[96] = function(self) --WRPG + self:Dyn_Emit("$L OP = $1") + self:Dyn_Emit("if (OP >= 0) and (OP < VM.ROMSize/128) then") + self:Dyn_Emit("$L TADD = OP*128") + self:Dyn_Emit("while TADD < OP*128+128 do") + self:Dyn_Emit("VM.ROM[TADD] = VM.Memory[TADD]") + self:Dyn_Emit("TADD = TADD+1") + self:Dyn_Emit("end") + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("12","0") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[97] = function(self) --RDPG + self:Dyn_Emit("$L OP = $1") + self:Dyn_Emit("if (OP >= 0) and (OP < VM.ROMSize/128) then") + self:Dyn_Emit("$L TADD = OP*128") + self:Dyn_Emit("while TADD < OP*128+128 do") + self:Dyn_Emit("VM.Memory[TADD] = VM.ROM[TADD]") + self:Dyn_Emit("TADD = TADD+1") + self:Dyn_Emit("end") + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("12","0") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[98] = function(self) --TIMER + self:Dyn_EmitOperand("(VM.TIMER+"..(self.PrecompileInstruction or 0).."*VM.TimerDT)") +end +ZVM.OpcodeTable[99] = function(self) --LIDTR + self:Dyn_Emit("VM.IDTR = $1") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[100] = function(self) --STATESTORE + self:Dyn_EmitState() + self:Dyn_Emit("VM:WriteCell($1 + 00,"..self.PrecompileIP..")") + + self:Dyn_Emit("VM:WriteCell($1 + 01,VM.EAX)") + self:Dyn_Emit("VM:WriteCell($1 + 02,VM.EBX)") + self:Dyn_Emit("VM:WriteCell($1 + 03,VM.ECX)") + self:Dyn_Emit("VM:WriteCell($1 + 04,VM.EDX)") + self:Dyn_Emit("VM:WriteCell($1 + 05,VM.ESI)") + self:Dyn_Emit("VM:WriteCell($1 + 06,VM.EDI)") + self:Dyn_Emit("VM:WriteCell($1 + 07,VM.ESP)") + self:Dyn_Emit("VM:WriteCell($1 + 08,VM.EBP)") + + self:Dyn_Emit("VM:WriteCell($1 + 09,VM.CS)") + self:Dyn_Emit("VM:WriteCell($1 + 10,VM.SS)") + self:Dyn_Emit("VM:WriteCell($1 + 11,VM.DS)") + self:Dyn_Emit("VM:WriteCell($1 + 12,VM.ES)") + self:Dyn_Emit("VM:WriteCell($1 + 13,VM.GS)") + self:Dyn_Emit("VM:WriteCell($1 + 14,VM.FS)") + + self:Dyn_Emit("VM:WriteCell($1 + 15,VM.CMPR)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[101] = function(self) --JNER + self:Dyn_Emit("if VM.CMPR ~= 0 then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[102] = function(self) --JMPR + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + + self.PrecompileBreak = true +end +ZVM.OpcodeTable[103] = function(self) --JGR + self:Dyn_Emit("if VM.CMPR > 0 then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[104] = function(self) --JGER + self:Dyn_Emit("if VM.CMPR >= 0 then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[105] = function(self) --JLR + self:Dyn_Emit("if VM.CMPR < 0 then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[106] = function(self) --JLER + self:Dyn_Emit("if VM.CMPR <= 0 then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[107] = function(self) --JER + self:Dyn_Emit("if VM.CMPR == 0 then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP.." + $1)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[108] = function(self) --LNEG + self:Dyn_EmitOperand("1-math.Clamp($1,0,1)") +end +ZVM.OpcodeTable[109] = function(self) --STATERESTORE + self:Dyn_Emit(" VM:ReadCell($1 + 00)") + + self:Dyn_Emit("EAX = VM:ReadCell($1 + 01) or 0") + self:Dyn_Emit("EBX = VM:ReadCell($1 + 02) or 0") + self:Dyn_Emit("ECX = VM:ReadCell($1 + 03) or 0") + self:Dyn_Emit("EDX = VM:ReadCell($1 + 04) or 0") + self:Dyn_Emit("ESI = VM:ReadCell($1 + 05) or 0") + self:Dyn_Emit("EDI = VM:ReadCell($1 + 06) or 0") + self:Dyn_Emit("ESP = VM:ReadCell($1 + 07) or 0") + self:Dyn_Emit("EBP = VM:ReadCell($1 + 08) or 0") + + self:Dyn_Emit("CS = VM:ReadCell($1 + 09) or 0") + self:Dyn_Emit("SS = VM:ReadCell($1 + 10) or 0") + self:Dyn_Emit("DS = VM:ReadCell($1 + 11) or 0") + self:Dyn_Emit("ES = VM:ReadCell($1 + 12) or 0") + self:Dyn_Emit("GS = VM:ReadCell($1 + 13) or 0") + self:Dyn_Emit("FS = VM:ReadCell($1 + 14) or 0") + + self:Dyn_Emit("VM.CMPR = VM:ReadCell($1 + 15) or 0") + + self:Dyn_EmitInterruptCheck() + + self:Dyn_EmitRegisterValueChanged("EAX") + self:Dyn_EmitRegisterValueChanged("EBX") + self:Dyn_EmitRegisterValueChanged("ECX") + self:Dyn_EmitRegisterValueChanged("EDX") + self:Dyn_EmitRegisterValueChanged("ESI") + self:Dyn_EmitRegisterValueChanged("EDI") + self:Dyn_EmitRegisterValueChanged("ESP") + self:Dyn_EmitRegisterValueChanged("EBP") + + self:Dyn_EmitRegisterValueChanged("CS") + self:Dyn_EmitRegisterValueChanged("SS") + self:Dyn_EmitRegisterValueChanged("DS") + self:Dyn_EmitRegisterValueChanged("ES") + self:Dyn_EmitRegisterValueChanged("GS") + self:Dyn_EmitRegisterValueChanged("FS") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[110] = function(self) --EXTRET + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("$L V = 0") + self:Dyn_EmitState() + + self:Dyn_Emit("V = VM:Pop()") -- IRET CS + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("V = VM:Pop()") -- IRET EIP + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("$L IP = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.CMPR = V") + + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EAX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EBX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.ECX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EDX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EBP = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() -- Do not set ESP right now + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.ESI = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EDI = V") + + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("$L CS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() -- Do not set SS right now + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.DS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.FS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.GS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.ES = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.KS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.LS = V") + + self:Dyn_Emit("VM:Jump(IP,CS)") + self:Dyn_EmitBreak() + + self.PrecompileBreak = true +end +ZVM.OpcodeTable[111] = function(self) --IDLE + self:Dyn_Emit("VM.Idle = 1") +end +ZVM.OpcodeTable[112] = function(self) --NOP +end +ZVM.OpcodeTable[113] = function(self) --RLADD + self:Dyn_Emit("EAX = VM.LADD") + self:Dyn_EmitRegisterValueChanged("EAX") +end +ZVM.OpcodeTable[114] = function(self) --PUSHA + self:Dyn_EmitForceRegisterLocal("EAX") + self:Dyn_EmitForceRegisterLocal("EBX") + self:Dyn_EmitForceRegisterLocal("ECX") + self:Dyn_EmitForceRegisterLocal("EDX") + self:Dyn_EmitForceRegisterLocal("ESI") + self:Dyn_EmitForceRegisterLocal("EDI") + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_EmitForceRegisterLocal("EBP") + + self:Dyn_Emit("VM:Push(EDI)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(ESI)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(EBP)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(VM.ESP)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(EDX)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(ECX)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(EBX)") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:Push(EAX)") self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[115] = function(self) --POPA + self:Dyn_EmitForceRegisterGlobal("ESP") + + self:Dyn_Emit("EAX = VM:Pop()") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("EBX = VM:Pop()") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("ECX = VM:Pop()") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("EDX = VM:Pop()") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L SP = VM:Pop()") self:Dyn_EmitInterruptCheck() -- Do not write stack pointer + self:Dyn_Emit("EBP = VM:Pop()") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("ESI = VM:Pop()") self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("EDI = VM:Pop()") self:Dyn_EmitInterruptCheck() + + self:Dyn_EmitRegisterValueChanged("EAX") + self:Dyn_EmitRegisterValueChanged("EBX") + self:Dyn_EmitRegisterValueChanged("ECX") + self:Dyn_EmitRegisterValueChanged("EDX") + self:Dyn_EmitRegisterValueChanged("ESI") + self:Dyn_EmitRegisterValueChanged("EDI") + self:Dyn_EmitRegisterValueChanged("EBP") +end +ZVM.OpcodeTable[116] = function(self) --STD2 + self:Dyn_Emit("VM.HWDEBUG = 1") + self:Dyn_Emit("VM.DBGSTATE = 0") +end +ZVM.OpcodeTable[117] = function(self) --LEAVE + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_EmitForceRegisterGlobal("EBP") + self:Dyn_Emit("VM.ESP = VM.EBP-1") + + self:Dyn_Emit("EBP = VM:Pop()") + self:Dyn_EmitInterruptCheck() + self:Dyn_EmitRegisterValueChanged("EBP") +end +ZVM.OpcodeTable[118] = function(self) --STM + self:Dyn_Emit("VM.MF = 1") +end +ZVM.OpcodeTable[119] = function(self) --CLM + self:Dyn_Emit("VM.MF = 0") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[120] = function(self) --CPUGET + self:Dyn_Emit("$L REG = $2") + self:Dyn_Emit("$L OP = 0") + self:Dyn_EmitState() + self:Dyn_EmitOperand("OP") + self:Dyn_Emit("if VM.InternalRegister[REG] then") + self:Dyn_Emit("OP = VM[VM.InternalRegister[REG]]") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[121] = function(self) --CPUSET + self:Dyn_Emit("$L REG = $1") + self:Dyn_Emit("if VM.InternalRegister[REG] and (not VM.ReadOnlyRegister[REG]) then") + self:Dyn_Emit("$L OP = $2") + self:Dyn_Emit("$L limit = VM.InternalLimits[REG]") + self:Dyn_Emit("VM[VM.InternalRegister[REG]] = limit and math.Clamp(OP, limit[1], limit[2]) or OP") + self:Dyn_Emit("if (REG == 0) or (REG == 16) then") + self:Dyn_Emit("VM:Jump("..self.PrecompileIP..",VM.CS)") + self:Dyn_EmitState() + self:Dyn_EmitBreak() + self:Dyn_Emit("else") + self:Dyn_Emit("if REG == 1 then EAX = OP end") + self:Dyn_Emit("if REG == 2 then EBX = OP end") + self:Dyn_Emit("if REG == 3 then ECX = OP end") + self:Dyn_Emit("if REG == 4 then EDX = OP end") + self:Dyn_Emit("if REG == 5 then ESI = OP end") + self:Dyn_Emit("if REG == 6 then EDI = OP end") + self:Dyn_Emit("if REG == 7 then ESP = OP end") + self:Dyn_Emit("if REG == 8 then EBP = OP end") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + + -- FIXME: registers must be properly synced +end +ZVM.OpcodeTable[122] = function(self) --SPP + self:Dyn_Emit("$L FirstAddr") + self:Dyn_Emit("$L LastAddr") + self:Dyn_Emit("if VM.BlockSize > 0 then") + self:Dyn_Emit("FirstAddr = VM.BlockStart") + self:Dyn_Emit("LastAddr = FirstAddr + math.Clamp(VM.BlockSize,0,8192)") + self:Dyn_Emit("VM.BlockSize = 0") + self:Dyn_Emit("else") + self:Dyn_Emit("FirstAddr = $1 * 128") + self:Dyn_Emit("LastAddr = $1 * 128 + 127") + self:Dyn_Emit("end") + + self:Dyn_Emit("$L ADDR = FirstAddr") + self:Dyn_Emit("$L FLAG = $2") + self:Dyn_Emit("while ADDR < LastAddr do") + self:Dyn_Emit("$L IDX = math.floor(ADDR / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if VM.CurrentPage.RunLevel <= PAGE.RunLevel then") + self:Dyn_Emit(" if FLAG == 0 then PAGE.Read = 1") + self:Dyn_Emit("elseif FLAG == 1 then PAGE.Write = 1") + self:Dyn_Emit("elseif FLAG == 2 then PAGE.Execute = 1") + self:Dyn_Emit("elseif FLAG == 3 then PAGE.RunLevel = 1 end") + self:Dyn_Emit("VM:SetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("11","IDX") + self:Dyn_Emit("end") + self:Dyn_Emit("ADDR = ADDR + 128") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[123] = function(self) --CPP + self:Dyn_Emit("$L FirstAddr") + self:Dyn_Emit("$L LastAddr") + self:Dyn_Emit("if VM.BlockSize > 0 then") + self:Dyn_Emit("FirstAddr = VM.BlockStart") + self:Dyn_Emit("LastAddr = FirstAddr + math.Clamp(VM.BlockSize,0,8192)") + self:Dyn_Emit("VM.BlockSize = 0") + self:Dyn_Emit("else") + self:Dyn_Emit("FirstAddr = $1 * 128") + self:Dyn_Emit("LastAddr = $1 * 128 + 127") + self:Dyn_Emit("end") + + self:Dyn_Emit("$L ADDR = FirstAddr") + self:Dyn_Emit("$L FLAG = $2") + self:Dyn_Emit("while ADDR < LastAddr do") + self:Dyn_Emit("$L IDX = math.floor(ADDR / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if VM.CurrentPage.RunLevel <= PAGE.RunLevel then") + self:Dyn_Emit(" if FLAG == 0 then PAGE.Read = 0") + self:Dyn_Emit("elseif FLAG == 1 then PAGE.Write = 0") + self:Dyn_Emit("elseif FLAG == 2 then PAGE.Execute = 0") + self:Dyn_Emit("elseif FLAG == 3 then PAGE.RunLevel = 0 end") + self:Dyn_Emit("VM:SetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("11","IDX") + self:Dyn_Emit("end") + self:Dyn_Emit("ADDR = ADDR + 128") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[124] = function(self) --SRL + self:Dyn_Emit("$L FirstAddr") + self:Dyn_Emit("$L LastAddr") + self:Dyn_Emit("if VM.BlockSize > 0 then") + self:Dyn_Emit("FirstAddr = VM.BlockStart") + self:Dyn_Emit("LastAddr = FirstAddr + math.Clamp(VM.BlockSize,0,8192)") + self:Dyn_Emit("VM.BlockSize = 0") + self:Dyn_Emit("else") + self:Dyn_Emit("FirstAddr = $1 * 128") + self:Dyn_Emit("LastAddr = $1 * 128 + 127") + self:Dyn_Emit("end") + + self:Dyn_Emit("$L ADDR = FirstAddr") + self:Dyn_Emit("while ADDR < LastAddr do") + self:Dyn_Emit("$L IDX = math.floor(ADDR / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if VM.CurrentPage.RunLevel <= PAGE.RunLevel then") + self:Dyn_Emit("PAGE.RunLevel = $2") + self:Dyn_Emit("VM:SetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("11","IDX") + self:Dyn_Emit("end") + self:Dyn_Emit("ADDR = ADDR + 128") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[125] = function(self) --GRL + self:Dyn_Emit("$L IDX = math.floor($2 / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_EmitOperand("PAGE.RunLevel") +end +ZVM.OpcodeTable[126] = function(self) --LEA + local emitText = self.OperandEffectiveAddress[self.EmitOperandRM[2]] or "0" + emitText = string.gsub(emitText,"$BYTE",self.EmitOperandByte[2] or "0") + emitText = string.gsub(emitText,"$SEG","VM."..(self.EmitOperandSegment[2] or "DS")) + self:Dyn_EmitOperand(emitText) +end +ZVM.OpcodeTable[127] = function(self) --BLOCK + self:Dyn_Emit("VM.BlockStart = $1") + self:Dyn_Emit("VM.BlockSize = $2") +end +ZVM.OpcodeTable[128] = function(self) --CMPAND + self:Dyn_Emit("if VM.CMPR ~= 0 then") + self:Dyn_Emit("VM.CMPR = $1 - $2") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[129] = function(self) --CMPOR + self:Dyn_Emit("if VM.CMPR == 0 then") + self:Dyn_Emit("VM.CMPR = $1 - $2") + self:Dyn_Emit("end") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[130] = function(self) --MSHIFT FIXME: Inoperative + self:Dyn_EmitForceRegisterLocal("ESI") + self:Dyn_Emit("$L Count = math.Clamp($1,0,8192)") + self:Dyn_Emit("if Count ~= 0 then") + self:Dyn_Emit("$L Offset = $2") + self:Dyn_Emit("$L Buffer = {}") + + self:Dyn_Emit("if Offset > 0 then") + self:Dyn_Emit("for i = 0,math.Clamp(Count-1-Offset,0,8191) do") --Shifted part + self:Dyn_Emit("Buffer[i] = VM:ReadCell(ESI+i+Offset)") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("for i = math.Clamp(Count-1-Offset+1,0,8191),math.Clamp(Count,0,8191) do") --Remaining part + self:Dyn_Emit("Buffer[i] = VM:ReadCell(ESI+i-(Count-1-Offset+1))") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("else") + self:Dyn_Emit("for i = math.Clamp(-Offset,0,8191),math.Clamp(Count,0,8191) do") --Shifted part + self:Dyn_Emit("Buffer[i] = VM:ReadCell(ESI+i+Offset)") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("for i = 0,math.Clamp(-Offset-1,0,8191) do") --Remaining part + self:Dyn_Emit("Buffer[i] = VM:ReadCell(ESI+i+(Count-1+Offset+1))") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") + + self:Dyn_Emit("for i = 0,Count-1 do") + self:Dyn_Emit("VM:WriteCell(ESI+i,Buffer[i] or 32)") + self:Dyn_Emit("end") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("ESI = ESI + Count") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[131] = function(self) --SMAP + self:Dyn_Emit("$L FirstAddr") + self:Dyn_Emit("$L LastAddr") + self:Dyn_Emit("if VM.BlockSize > 0 then") + self:Dyn_Emit("FirstAddr = VM.BlockStart") + self:Dyn_Emit("LastAddr = FirstAddr + math.Clamp(VM.BlockSize,0,8192)") + self:Dyn_Emit("VM.BlockSize = 0") + self:Dyn_Emit("else") + self:Dyn_Emit("FirstAddr = $1 * 128") + self:Dyn_Emit("LastAddr = $1 * 128 + 127") + self:Dyn_Emit("end") + + self:Dyn_Emit("$L ADDR = FirstAddr") + self:Dyn_Emit("while ADDR < LastAddr do") + self:Dyn_Emit("$L IDX = math.floor(ADDR / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("if VM.CurrentPage.RunLevel <= PAGE.RunLevel then") + self:Dyn_Emit("PAGE.MappedIndex = $2") + self:Dyn_Emit("PAGE.Remapped = 1") + self:Dyn_Emit("VM:SetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("for address=IDX*128,IDX*128+127 do") + self:Dyn_Emit("if VM.IsAddressPrecompiled[address] then") + self:Dyn_Emit("for k,v in ipairs(VM.IsAddressPrecompiled[address]) do") + self:Dyn_Emit("VM.PrecompiledData[v] = nil") + self:Dyn_Emit("VM.IsAddressPrecompiled[address][k] = nil") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + self:Dyn_Emit("else") + self:Dyn_EmitInterrupt("11","IDX") + self:Dyn_Emit("end") + self:Dyn_Emit("ADDR = ADDR + 128") + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[132] = function(self) --GMAP + self:Dyn_Emit("$L IDX = math.floor(ADDR / 128)") + self:Dyn_Emit("$L PAGE = VM:GetPageByIndex(IDX)") + self:Dyn_EmitInterruptCheck() + self:Dyn_EmitOperand("PAGE.MappedIndex") +end +ZVM.OpcodeTable[133] = function(self) --RSTACK + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_EmitOperand("VM:ReadFromStack($2)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[134] = function(self) --SSTACK + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("VM:WriteToStack($1,$2)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[135] = function(self) --ENTER + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_EmitForceRegisterLocal("EBP") + + self:Dyn_Emit("VM:Push(EBP)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("EBP = VM.ESP+1") + self:Dyn_Emit("VM.ESP = VM.ESP-$1") +end +ZVM.OpcodeTable[136] = function(self) --IRETP + self:Dyn_Emit("VM.PTBL = $1") + self.OpcodeTable[41](self) -- as IRET +end +ZVM.OpcodeTable[137] = function(self) --EXTRETP + self:Dyn_Emit("VM.PTBL = $1") + self.OpcodeTable[110](self) -- as EXTRET +end +ZVM.OpcodeTable[139] = function(self) --CLD + if self.MicrocodeDebug then + self:Dyn_Emit("VM.Debug = false") + end +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[140] = function(self) --EXTRETA + self:Dyn_EmitForceRegisterGlobal("ESP") + self:Dyn_Emit("$L V = 0") + self:Dyn_EmitState() + + self:Dyn_Emit("V = VM:Pop()") -- IRET CS + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("V = VM:Pop()") -- IRET EIP + self:Dyn_EmitInterruptCheck() + + for i=0,31 do + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.R"..i.." = V") + end + + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("$L IP = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.CMPR = V") + + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EAX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EBX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.ECX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EDX = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EBP = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() -- Do not set ESP right now + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.ESI = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.EDI = V") + + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("$L CS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() -- Do not set SS right now + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.DS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.FS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.GS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.ES = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.KS = V") + self:Dyn_Emit("V = VM:Pop()") self:Dyn_EmitInterruptCheck() self:Dyn_Emit("VM.LS = V") + self:Dyn_Emit("VM:Jump(IP,CS)") + + self:Dyn_EmitBreak() + self.PrecompileBreak = true +end +ZVM.OpcodeTable[141] = function(self) --EXTRETPA + self:Dyn_Emit("VM.PTBL = $1") + self.OpcodeTable[140](self) -- as EXTRETP +end + + +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[250] = function(self) --VADD + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V1 = VM:ReadVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.x+V2.x, y=V1.y+V2.y, z=0})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V1 = VM:ReadVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.x+V2.x, y=V1.y+V2.y, z=V1.z+V2.z})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[251] = function(self) --VSUB + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V1 = VM:ReadVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.x-V2.x, y=V1.y-V2.y, z=0})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V1 = VM:ReadVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.x-V2.x, y=V1.y-V2.y, z=V1.z-V2.z})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[252] = function(self) --VMUL + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V1 = VM:ReadVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = $2") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.x*V2, y=V1.y*V2, z=0})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V1 = VM:ReadVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = $2") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.x*V2, y=V1.y*V2, z=V1.z*V2})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[253] = function(self) --VDOT + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V1 = VM:ReadVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteCell($1 + VM."..self.EmitOperandSegment[1]..",") + self:Dyn_Emit("V1.x*V2.x+V1.y*V2.y)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V1 = VM:ReadVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteCell($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("V1.x*V2.x+V1.y*V2.y+V1.z*V2.z)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[254] = function(self) --VCROSS + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V1 = VM:ReadVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteCell($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("V1.x*V2.y-V1.y*V2.x)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V1 = VM:ReadVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L V2 = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V1.y*V2.z-V1.z*V2.y, y=V1.z*V2.x-V1.x*V2.z, z=V1.x*V2.y-V1.y*V2.x})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[255] = function(self) --VMOV + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",V)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",V)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[256] = function(self) --VNORM + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L D = (V.x^2+V.y^2)^(1/2)+1e-8") + self:Dyn_Emit("VM:WriteVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V.x/D, y = V.y/D, z = 0})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("$L D = (V.x^2+V.y^2+V.z^2)^(1/2)+1e-8") + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",") + self:Dyn_Emit("{x = V.x/D, y = V.y/D, z = V.z/D})") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[257] = function(self) --VCOLORNORM + self:Dyn_Emit("$L V = VM:ReadVector4f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("V.x = math.min(255,math.max(0,V.x))") + self:Dyn_Emit("V.y = math.min(255,math.max(0,V.y))") + self:Dyn_Emit("V.z = math.min(255,math.max(0,V.z))") + self:Dyn_Emit("V.w = math.min(255,math.max(0,V.w))") + self:Dyn_Emit("VM:WriteVector4f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",V)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[259] = function(self) --LOOPXY + self:Dyn_EmitForceRegisterLocal("ECX") + self:Dyn_EmitForceRegisterLocal("EDX") + +-- self:Dyn_Emit(" +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[260] = function(self) --MADD + self:Dyn_Emit("$L M1 = VM:ReadMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L M2 = VM:ReadMatrix($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("for i=0,15 do RM[i] = M1[i]+M2[i] end") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[261] = function(self) --MSUB + self:Dyn_Emit("$L M1 = VM:ReadMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L M2 = VM:ReadMatrix($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("for i=0,15 do RM[i] = M1[i]-M2[i] end") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[262] = function(self) --MMUL + self:Dyn_Emit("$L M1 = VM:ReadMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_Emit("$L M2 = VM:ReadMatrix($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("for i=0,3 do") + self:Dyn_Emit("for j=0,3 do") + self:Dyn_Emit("RM[i*4+j] = M1[i*4+0]*M2[0*4+j] +") + self:Dyn_Emit(" M1[i*4+1]*M2[1*4+j] +") + self:Dyn_Emit(" M1[i*4+2]*M2[2*4+j] +") + self:Dyn_Emit(" M1[i*4+3]*M2[3*4+j]") + self:Dyn_Emit("end") + self:Dyn_Emit("end") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[263] = function(self) --MROTATE + self:Dyn_Emit("$L VEC = VM:ReadVector4f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L MAG = math.sqrt(VEC.x^2+VEC.y^2+VEC.z^2)+1e-7") + self:Dyn_Emit("VEC.x = VEC.x / MAG") + self:Dyn_Emit("VEC.y = VEC.y / MAG") + self:Dyn_Emit("VEC.z = VEC.z / MAG") + + self:Dyn_Emit("$L SIN = math.sin(VEC.w)") + self:Dyn_Emit("$L COS = math.cos(VEC.w)") + + self:Dyn_Emit("$L ab = VEC.x * VEC.y * (1 - COS)") + self:Dyn_Emit("$L bc = VEC.y * VEC.z * (1 - COS)") + self:Dyn_Emit("$L ca = VEC.z * VEC.x * (1 - COS)") + self:Dyn_Emit("$L tx = VEC.x * VEC.x") + self:Dyn_Emit("$L ty = VEC.y * VEC.y") + self:Dyn_Emit("$L tz = VEC.z * VEC.z") + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("RM[0] = tx + COS * (1 - tx)") + self:Dyn_Emit("RM[1] = ab + VEC.z * SIN") + self:Dyn_Emit("RM[2] = ca - VEC.y * SIN") + self:Dyn_Emit("RM[3] = 0") + self:Dyn_Emit("RM[4] = ab - VEC.z * SIN") + self:Dyn_Emit("RM[5] = ty + COS * (1 - ty)") + self:Dyn_Emit("RM[6] = bc + VEC.x * SIN") + self:Dyn_Emit("RM[7] = 0") + self:Dyn_Emit("RM[8] = ca + VEC.y * SIN") + self:Dyn_Emit("RM[9] = bc - VEC.x * SIN") + self:Dyn_Emit("RM[10] = tz + COS * (1 - tz)") + self:Dyn_Emit("RM[11] = 0") + self:Dyn_Emit("RM[12] = 0") + self:Dyn_Emit("RM[13] = 0") + self:Dyn_Emit("RM[14] = 0") + self:Dyn_Emit("RM[15] = 1") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[264] = function(self) --MSCALE + self:Dyn_Emit("$L VEC = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("RM[0] = VEC.x") + self:Dyn_Emit("RM[1] = 0") + self:Dyn_Emit("RM[2] = 0") + self:Dyn_Emit("RM[3] = 0") + + self:Dyn_Emit("RM[4] = 0") + self:Dyn_Emit("RM[5] = VEC.y") + self:Dyn_Emit("RM[6] = 0") + self:Dyn_Emit("RM[7] = 0") + + self:Dyn_Emit("RM[8] = 0") + self:Dyn_Emit("RM[9] = 0") + self:Dyn_Emit("RM[10] = VEC.z") + self:Dyn_Emit("RM[11] = 0") + + self:Dyn_Emit("RM[12] = 0") + self:Dyn_Emit("RM[13] = 0") + self:Dyn_Emit("RM[14] = 0") + self:Dyn_Emit("RM[15] = 1") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[265] = function(self) --MPERSPECTIVE + self:Dyn_Emit("$L VEC = VM:ReadVector4f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L DZ = VEC.w - VEC.z") + self:Dyn_Emit("$L RADS = (VEC.x / 2.0) * math.pi / 180") + self:Dyn_Emit("$L SIN = math.sin(RADS)") + self:Dyn_Emit("$L CTG = math.cos(RADS)/SIN") + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("RM[0] = CTG / VEC.y") + self:Dyn_Emit("RM[4] = 0") + self:Dyn_Emit("RM[8] = 0") + self:Dyn_Emit("RM[12] = 0") + + self:Dyn_Emit("RM[1] = 0") + self:Dyn_Emit("RM[5] = CTG") + self:Dyn_Emit("RM[9] = 0") + self:Dyn_Emit("RM[13] = 0") + + self:Dyn_Emit("RM[2] = 0") + self:Dyn_Emit("RM[6] = 0") + self:Dyn_Emit("RM[10] = -(VEC.z + VEC.w) / DZ") + self:Dyn_Emit("RM[14] = -2*VEC.z*VEC.w / DZ") + + self:Dyn_Emit("RM[3] = 0") + self:Dyn_Emit("RM[7] = 0") + self:Dyn_Emit("RM[11] = -1") + self:Dyn_Emit("RM[15] = 0") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[266] = function(self) --MTRANSLATE + self:Dyn_Emit("$L VEC = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("RM[0] = 1") + self:Dyn_Emit("RM[1] = 0") + self:Dyn_Emit("RM[2] = 0") + self:Dyn_Emit("RM[3] = VEC.x") + + self:Dyn_Emit("RM[4] = 0") + self:Dyn_Emit("RM[5] = 1") + self:Dyn_Emit("RM[6] = 0") + self:Dyn_Emit("RM[7] = VEC.y") + + self:Dyn_Emit("RM[8] = 0") + self:Dyn_Emit("RM[9] = 0") + self:Dyn_Emit("RM[10] = 1") + self:Dyn_Emit("RM[11] = VEC.z") + + self:Dyn_Emit("RM[12] = 0") + self:Dyn_Emit("RM[13] = 0") + self:Dyn_Emit("RM[14] = 0") + self:Dyn_Emit("RM[15] = 1") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[267] = function(self) --MLOOKAT + self:Dyn_Emit("$L EYE = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS").."+0)") + self:Dyn_Emit("$L CENTER = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS").."+3)") + self:Dyn_Emit("$L UP = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS").."+6)") + self:Dyn_EmitInterruptCheck() + + self:Dyn_Emit("$L X = { 0, 0, 0 }") + self:Dyn_Emit("$L Y = { UP.x, UP.y, UP.z }") + self:Dyn_Emit("$L Z = { EYE.x - CENTER.x, EYE.y - CENTER.y, EYE.z - CENTER.z }") + + self:Dyn_Emit("$L ZMAG = math.sqrt(Z[1]^2+Z[2]^2+Z[3]^2)+1e-7") + self:Dyn_Emit("Z[1] = Z[1] / ZMAG") + self:Dyn_Emit("Z[2] = Z[2] / ZMAG") + self:Dyn_Emit("Z[3] = Z[3] / ZMAG") + + self:Dyn_Emit("X[1] = Y[2]*Z[3] - Y[3]*Z[2]") + self:Dyn_Emit("X[2] = -Y[1]*Z[3] + Y[3]*Z[1]") + self:Dyn_Emit("X[3] = Y[1]*Z[2] - Y[2]*Z[1]") + + self:Dyn_Emit("Y[1] = Z[2]*X[3] - Z[3]*X[2]") + self:Dyn_Emit("Y[2] = -Z[1]*X[3] + Z[3]*X[1]") + self:Dyn_Emit("Y[3] = Z[1]*X[2] - Z[2]*X[1]") + + self:Dyn_Emit("$L XMAG = math.sqrt(X[1]^2+X[2]^2+X[3]^2)+1e-7") + self:Dyn_Emit("X[1] = X[1] / XMAG") + self:Dyn_Emit("X[2] = X[2] / XMAG") + self:Dyn_Emit("X[3] = X[3] / XMAG") + + self:Dyn_Emit("$L YMAG = math.sqrt(Y[1]^2+Y[2]^2+Y[3]^2)+1e-7") + self:Dyn_Emit("Y[1] = Y[1] / YMAG") + self:Dyn_Emit("Y[2] = Y[2] / YMAG") + self:Dyn_Emit("Y[3] = Y[3] / YMAG") + + self:Dyn_Emit("$L RM = {}") + self:Dyn_Emit("RM[0] = X[1]") + self:Dyn_Emit("RM[1] = X[2]") + self:Dyn_Emit("RM[2] = X[3]") + self:Dyn_Emit("RM[3] = -X[1]*EYE.x + -X[2]*EYE.y + -X[3]*EYE.z") + + self:Dyn_Emit("RM[4] = Y[1]") + self:Dyn_Emit("RM[5] = Y[2]") + self:Dyn_Emit("RM[6] = Y[3]") + self:Dyn_Emit("RM[7] = -Y[1]*EYE.x + -Y[2]*EYE.y + -Y[3]*EYE.z") + + self:Dyn_Emit("RM[8] = Z[1]") + self:Dyn_Emit("RM[9] = Z[2]") + self:Dyn_Emit("RM[10] = Z[3]") + self:Dyn_Emit("RM[11] = -Z[1]*EYE.x + -Z[2]*EYE.y + -Z[3]*EYE.z") + + self:Dyn_Emit("RM[12] = 0") + self:Dyn_Emit("RM[13] = 0") + self:Dyn_Emit("RM[14] = 0") + self:Dyn_Emit("RM[15] = 1") + + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",RM)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[268] = function(self) --MMOV + self:Dyn_Emit("$L M = VM:ReadMatrix($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",M)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[269] = function(self) --VLEN + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V = VM:ReadVector2f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_EmitOperand(1,"(V.x^2+V.y^2)^0.5",true) + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V = VM:ReadVector3f($2 + VM."..(self.EmitOperandSegment[2] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_EmitOperand(1,"(V.x^2+V.y^2+V.z^2)^0.5",true) + self:Dyn_Emit("end") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[270] = function(self) --MIDENT + self:Dyn_Emit("$L M = {}") + self:Dyn_Emit("M[ 0]=1 M[ 1]=0 M[ 2]=0 M[ 3]=0") + self:Dyn_Emit("M[ 4]=0 M[ 5]=1 M[ 6]=0 M[ 7]=0") + self:Dyn_Emit("M[ 8]=0 M[ 9]=0 M[10]=1 M[11]=0") + self:Dyn_Emit("M[12]=0 M[13]=0 M[14]=0 M[15]=1") + self:Dyn_Emit("VM:WriteMatrix($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",M)") + self:Dyn_EmitInterruptCheck() +end +ZVM.OpcodeTable[273] = function(self) --VMODE + self:Dyn_Emit("VM.VMODE = $1") +end +-------------------------------------------------------------------------------- +ZVM.OpcodeTable[295] = function(self) --VDIV + self:Dyn_Emit("$L SCALAR = $2") + self:Dyn_Emit("if VM.VMODE == 2 then") + self:Dyn_Emit("$L V = VM:ReadVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("V.x = V.x / SCALAR") + self:Dyn_Emit("V.y = V.y / SCALAR") + self:Dyn_Emit("VM:WriteVector2f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",V)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("elseif VM.VMODE == 3 then") + self:Dyn_Emit("$L V = VM:ReadVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..")") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("V.x = V.x / SCALAR") + self:Dyn_Emit("V.y = V.y / SCALAR") + self:Dyn_Emit("V.z = V.z / SCALAR") + self:Dyn_Emit("VM:WriteVector3f($1 + VM."..(self.EmitOperandSegment[1] or "DS")..",V)") + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit("end") +end +ZVM.OpcodeTable[296] = function(self) --VTRANSFORM + self:Dyn_Emit("local address_1 = $1 + VM."..(self.EmitOperandSegment[1] or "DS")) + self:Dyn_Emit("local address_2 = $2 + VM."..(self.EmitOperandSegment[2] or "DS")) + self:Dyn_Emit [[ + local V = {0, 0, 0, 1} + if address_1~=0 then + for i = 1, VM.VMODE do + V[i] = VM:ReadCell(address_1 + i - 1) or 0 + end + end + ]] + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit [[local M = VM:ReadMatrix(address_2)]] + self:Dyn_EmitInterruptCheck() + self:Dyn_Emit [[ + for i = 0, VM.VMODE-1 do + local result = M[i*4 + 0] * V[1] + + M[i*4 + 1] * V[2] + + M[i*4 + 2] * V[3] + + M[i*4 + 3] * V[4] + VM:WriteCell(address_1 + i, result) + end + ]] + self:Dyn_EmitInterruptCheck() +end diff --git a/garrysmod/addons/gmod-ents/lua/cmenu/items/letter.lua b/garrysmod/addons/gmod-ents/lua/cmenu/items/letter.lua new file mode 100644 index 0000000..e9c8188 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/cmenu/items/letter.lua @@ -0,0 +1,5 @@ +octogui.cmenu.registerItem('rp', 'letter', { + text = L.write_doc, + icon = octolib.icons.silk16('page_edit'), + say = '/write', +}) diff --git a/garrysmod/addons/gmod-ents/lua/entities/base_gmodentity.lua b/garrysmod/addons/gmod-ents/lua/entities/base_gmodentity.lua new file mode 100644 index 0000000..986b5c6 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/base_gmodentity.lua @@ -0,0 +1,124 @@ + +AddCSLuaFile() +DEFINE_BASECLASS( "base_anim" ) + +ENT.Spawnable = false + +if ( CLIENT ) then + ENT.MaxWorldTipDistance = 256 + + function ENT:BeingLookedAtByLocalPlayer() + local ply = LocalPlayer() + if ( !IsValid( ply ) ) then return false end + + local view = ply:GetViewEntity() + local dist = self.MaxWorldTipDistance + dist = dist * dist + + -- If we're spectating a player, perform an eye trace + if ( view:IsPlayer() ) then + return view:EyePos():DistToSqr( self:GetPos() ) <= dist && view:GetEyeTrace().Entity == self + end + + -- If we're not spectating a player, perform a manual trace from the entity's position + local pos = view:GetPos() + + if ( pos:DistToSqr( self:GetPos() ) <= dist ) then + return util.TraceLine( { + start = pos, + endpos = pos + ( view:GetAngles():Forward() * dist ), + filter = view + } ).Entity == self + end + + return false + end + + local function thinkLater(self) + self:NextThink(CurTime() + 3) + return true + end + function ENT:Think() + + local text = self:GetOverlayText() + if text == '' then return thinkLater(self) end + + local ply = LocalPlayer() + if not IsValid(ply) then return thinkLater(self) end + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then return thinkLater(self) end + local wepClass = wep:GetClass() + if wepClass ~= 'weapon_physgun' and wepClass ~= 'gmod_tool' then return thinkLater(self) end + if self.IsWire and wepClass == 'gmod_tool' then return thinkLater(self) end + + if self:BeingLookedAtByLocalPlayer() then + AddWorldTip(self:EntIndex(), text, 0.5, self:GetPos(), self) + halo.Add({ self }, color_white, 1, 1, 1, true, true) + end + end +end + +function ENT:SetOverlayText( text ) + self:SetNWString( "GModOverlayText", text ) +end + +function ENT:GetOverlayText() + + local txt = self:GetNWString( "GModOverlayText" ) + + if ( txt == "" ) then + return "" + end + + if ( game.SinglePlayer() ) then + return txt + end + + local PlayerName = self:GetPlayerName() + + return txt .. "\n(" .. PlayerName .. ")" + +end + +function ENT:SetPlayer( ply ) + + if ( IsValid( ply ) ) then + + self:SetVar( "Founder", ply ) + self:SetVar( "FounderIndex", ply:UniqueID() ) + self:SetVar( "FounderSID", ply:SteamID() ) + + self:SetNWString( "FounderName", ply:Nick() ) + + end + +end + +function ENT:GetPlayer() + + return self:GetVar( "Founder", NULL ) + +end + +function ENT:GetPlayerIndex() + + return self:GetVar( "FounderIndex", 0 ) + +end + +function ENT:GetPlayerSteamID() + + return self:GetVar( "FounderSID", "" ) + +end + +function ENT:GetPlayerName() + + local ply = self:GetPlayer() + if ( IsValid( ply ) ) then + return ply:Nick() + end + + return self:GetNWString( "FounderName" ) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/cl_init.lua new file mode 100644 index 0000000..5045ba0 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/cl_init.lua @@ -0,0 +1,11 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/init.lua b/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/init.lua new file mode 100644 index 0000000..18e68b9 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/init.lua @@ -0,0 +1,89 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/props/de_nuke/nucleartestcabinet.mdl' +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true +ENT.Containers = { + utilizer = { + name = 'Утилизатор', icon = octolib.icons.color('inbox'), volume = 300, + hooks = { + {'canMoveIn', 'utilizer', function(cont, ply, item) + if item and item.class ~= 'zip' and item.class ~= 'body_mat' then + return false, 'Это запрещено загружать в утилизатор' + end + end}, + }, + }, + analyzer = { name = L.analyzer, icon = octolib.icons.color('microscope'), volume = 0.5, + hooks = { + {'canMoveIn', 'analyzer', function(cont, ply, item) + if item and item.class ~= 'body_mat' then + return false, 'Это запрещено загружать в анализатор' + end + end}, + }, + }, +} + +ENT.progressAnalysis = 0 +ENT.progressUtilize = 0 + +function ENT:FinishAnalysis(item) + + if os.time() < item:GetData('expire') then + local criminals = item:GetData('criminals') + local text = #criminals > 0 + and L.analyzer_prints:format(table.concat(criminals, ', ')) + or L.analyzer_no_prints + item:SetData('criminalsStr', text) + else + item:SetData('criminalsStr', L.analyzer_old_prints) + end + + item:SetData('analysed', true) + item:GetParent():QueueSync() + self.progressAnalysis = 0 + self:EmitSound('weapons/ar2/ar2_reload_push.wav', 65) + +end + +function ENT:NextItem() + local item = self.inv.conts.utilizer:GetItem(1) + if not item or self.utilizerBusy then return end + self.inv.conts.utilizer:TakeItem(item) + self.progressUtilize = 0 + self.utilizerBusy = true +end + +function ENT:Think() + + local item = self.inv.conts.analyzer:GetItem(1) + if item and not item:GetData('analysed') then + self.progressAnalysis = self.progressAnalysis + 0.05 + if self.progressAnalysis >= 1 then + self:FinishAnalysis(item) + else + self:EmitSound('weapons/ar2/ar2_reload_rotate.wav', 65) + end + else + self.progressAnalysis = 0 + end + + if self.utilizerBusy then + self.progressUtilize = self.progressUtilize + 0.1 + if self.progressUtilize >= 1 then + self.utilizerBusy = nil + self:EmitSound('items/suitchargeno1.wav', 80) + self:NextItem() + else + self:EmitSound('items/medshotno1.wav', 90) + end + end + + self:NextThink(CurTime() + 3) + return true + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/shared.lua new file mode 100644 index 0000000..fbf3919 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/dbg_police_analyser/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' +ENT.PrintName = L.analyzer +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/cl_init.lua new file mode 100644 index 0000000..c3321f1 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/cl_init.lua @@ -0,0 +1,7 @@ +include 'shared.lua' + +function ENT:Draw() + + self:DrawModel() + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/init.lua new file mode 100644 index 0000000..a7d713d --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/init.lua @@ -0,0 +1,41 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/props/de_tides/vending_cart.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + +end + +function ENT:Use(ply) + + if not ply:Alive() or not ply.inv or not ply.inv.conts._hand then + ply:Notify('warning', L.hands_free) + return + end + + if ply:GetDBVar('gotBDayCap') then + ply:Notify('warning', L.you_received_cap) + return + end + + ply:SetDBVar('gotBDayCap', true) + ply.inv.conts._hand:AddItem('h_mask', { + name = L.cap_dobrograd, + icon = 'octoteam/icons/clothes_cap.png', + desc = L.cap_dobrograd_desc, + mask = 'cap_dobrograd', + }) + + ply:Notify(L.bday_desc) + timer.Simple(2, function() + ply:Notify('hint', 'Хей, парниша, не хочешь получить НАСТОЯЩУЮ кепку патриота? Тогда участвуй в нашем конкурсе!') + ply:Notify('hint', 'Подробности в нашем сообществе VK: ', {'https://vk.com/octoteam'}) + end) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/shared.lua new file mode 100644 index 0000000..74388cd --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_bday/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = L.bday +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/cl_init.lua new file mode 100644 index 0000000..75575d3 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/cl_init.lua @@ -0,0 +1,4 @@ +include('shared.lua') + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/init.lua new file mode 100644 index 0000000..592ceb8 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/init.lua @@ -0,0 +1,32 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +function ENT:Initialize() + + self:SetModel('models/boxopencigshib.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + + self.amount = self.amount or 10 + +end + +function ENT:Use(ply) + + if ply:HasWeapon('dbg_cigarette') then + ply:Notify('warning', L.you_already_have_a_cigarette) + return + end + + ply:Give('dbg_cigarette') + self.amount = self.amount - 1 + if self.amount <= 0 then self:Remove() return end + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/shared.lua new file mode 100644 index 0000000..97cc5fc --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cigarette/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Cigarette Pack" +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/cl_init.lua new file mode 100644 index 0000000..505f1ff --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/cl_init.lua @@ -0,0 +1,36 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.DPU = 10 +ENT.DrawShared = true +ENT.DrawPos = Vector(2.65, -35, 22.5) +ENT.DrawAng = Angle(0, 90, 90) + +surface.CreateFont('city-board.title', { + font = 'Calibri', + extended = true, + size = 48, + weight = 350, +}) + +local colors = CFG.skinColors +function ENT:InitPanel(pnl) + + pnl:SetSize(700, 450) + function pnl:Paint(w, h) + draw.RoundedBox(8, 0, 0, w, h, colors.bg) + end + + local t1 = octolib.label(pnl, 'Доброград сегодня') + t1:SetFont('city-board.title') + t1:SetContentAlignment(5) + t1:SetTall(50) + + local b1 = octolib.button(pnl, 'Тест', octolib.fQuery('Круто?', 'Тест', 'Да', function() + octolib.notify.show('hint', 'Тест!') + end, 'Нет')) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/init.lua new file mode 100644 index 0000000..31bed0c --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/init.lua @@ -0,0 +1,36 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/nightreaper/softwood/5x75x50_panel_flat.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local lp = ents.Create 'prop_dynamic' + lp:SetParent(self) + lp:SetModel('models/nightreaper/softwood/5x5x75_beam_medium.mdl') + lp:SetLocalPos(Vector(-5, 20, -30)) + lp:SetLocalAngles(Angle()) + + local rp = ents.Create 'prop_dynamic' + rp:SetParent(self) + rp:SetModel('models/nightreaper/softwood/5x5x75_beam_medium.mdl') + rp:SetLocalPos(Vector(-5, -20, -30)) + rp:SetLocalAngles(Angle()) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + +end + +function ENT:Think() + + self:NextThink(CurTime() + 1) + return true + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/shared.lua new file mode 100644 index 0000000..49dcecc --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_cityboard/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'ent_interactive_board' +ENT.PrintName = 'Городская доска' +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/cl_init.lua new file mode 100644 index 0000000..c62f855 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/cl_init.lua @@ -0,0 +1,32 @@ +include 'shared.lua' + +function ENT:Initialize() + + self.em = ParticleEmitter(Vector()) + +end + +function ENT:Think() + + local e = self.em + if self:GetNetVar('working') and IsValid(e) then + local p = e:Add('effects/spark', self:LocalToWorld(Vector(0, 0, 8))) + p:SetDieTime(0.1) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(2) + p:SetEndSize(0) + p:SetVelocity((VectorRand() + self:GetUp()) * 40) + end + + self:NextThink(CurTime() + 0.1) + return true + +end + +function ENT:OnRemove() + + local e = self.em + if IsValid(e) then e:Finish() end + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/init.lua new file mode 100644 index 0000000..ea068f2 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/init.lua @@ -0,0 +1,53 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.ShellsNum = 10 +ENT.LaunchInterval = 0.5 +ENT.LaunchVariance = { -1, 1 } + +function ENT:Initialize() + + self:SetModel('models/items/boxflares.mdl') + self:PhysicsInitBox(Vector(-9, -2, 0), Vector(9, 2, 7)) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:SetMass(10) + phys:Wake() + end + +end + +function ENT:Use() + + if self.activated then return end + self.activated = true + + self:SetNetVar('working', true) + timer.Simple(4, function() + if not IsValid(self) then return end + self:SetNetVar('working', nil) + end) + + for i = 1, self.ShellsNum do + timer.Simple(5 + i * self.LaunchInterval + math.Rand(unpack(self.LaunchVariance)), function() + if not IsValid(self) then return end + + local dir = self:GetUp() + VectorRand() * 0.1 + local s = ents.Create 'ent_dbg_fireworkshell' + s:SetAngles((dir):Angle()) + s:SetPos(self:LocalToWorld(Vector(0,0,5))) + s:Spawn() + s:GetPhysicsObject():SetVelocity(dir * 1000) + end) + end + + timer.Simple(15 + self.ShellsNum * self.LaunchInterval, function() + if not IsValid(self) then return end + self:Remove() + end) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/shared.lua new file mode 100644 index 0000000..3e136f7 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworks/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.salute +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/cl_init.lua new file mode 100644 index 0000000..16b78f6 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/cl_init.lua @@ -0,0 +1,145 @@ +include 'shared.lua' + +function ENT:Initialize() + + self.em = ParticleEmitter(Vector()) + +end + +function ENT:Think() + + local e = self.em + if IsValid(e) then + local p = e:Add('effects/spark', self:GetPos()) + p:SetDieTime(0.1) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(2) + p:SetEndSize(0) + p:SetVelocity((VectorRand() - self:GetForward()) * 40) + end + + self:NextThink(CurTime() + 0.1) + return true + +end + +function ENT:OnRemove() + + local e = self.em + timer.Simple(1, function() + if IsValid(e) then e:Finish() end + end) + +end + +function ENT:Draw() + + -- nothing + +end + +local function getGlowAmp(finish, time) + local st = (finish - CurTime()) / time + if st > 0.9 then + return 0.27 + (st - 0.9) * 9 + else + return st * 0.3 + end +end + +local types = { + {3, function(e, pos) -- default + local col = HSVToColor(math.random(0, 359), 0.75, 1) + local size = math.random(500, 1000) + local time = 2 + local g = e:Add('sprites/light_glow02_add', pos) + g:SetDieTime(time) + g:SetStartAlpha(255) + g:SetEndAlpha(0) + g:SetStartSize(size) + g:SetEndSize(size) + g:SetGravity(Vector(0,0,0)) + g:SetColor(col.r * 0.3, col.g * 0.3, col.b * 0.3) + + local dieTime = CurTime() + time + g:SetNextThink(CurTime()) + g:SetThinkFunction(function(self) + if not IsValid(e) then return end + + local c = getGlowAmp(dieTime, time) + self:SetColor(col.r * c, col.g * c, col.b * c) + self:SetNextThink(CurTime()) + end) + + for i = 1, 20 do + local p = e:Add('sprites/light_glow02_add', pos) + p:SetDieTime(3 + math.Rand(0, 2)) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(8) + p:SetStartLength(20) + p:SetEndSize(3) + p:SetEndLength(2) + p:SetGravity(Vector(0,0,-100)) + p:SetAirResistance(100) + p:SetVelocity(VectorRand() * 500) + p:SetColor(col.r, col.g, col.b) + end + end}, + {2, function(e, pos) -- sparkled + local g = e:Add('sprites/light_glow02_add', pos) + g:SetDieTime(1) + g:SetStartAlpha(255) + g:SetEndAlpha(0) + g:SetStartSize(math.random(750, 1250)) + g:SetEndSize(0) + g:SetGravity(Vector(0,0,0)) + g:SetColor(30, 30, 20) + + for i = 1, 20 do + local lifeTime = math.Rand(1, 1.5) + local p = e:Add('sprites/light_glow02_add', pos) + p:SetDieTime(lifeTime) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(5) + p:SetEndSize(3) + p:SetGravity(Vector(0,0,-100)) + p:SetAirResistance(100) + p:SetVelocity(VectorRand() * 500) + p:SetColor(255, 255, 220) + + local sparkleEnd = CurTime() + lifeTime + p:SetNextThink(CurTime() + 0.03) + p:SetThinkFunction(function(self) + if not IsValid(e) then return end + + local st = (sparkleEnd - CurTime()) / lifeTime + local p = e:Add('effects/spark', self:GetPos()) + p:SetDieTime(0.3 + 0.1*st) + p:SetStartAlpha(255 * st) + p:SetEndAlpha(0) + p:SetStartSize(2 + 1*st) + p:SetEndSize(0) + p:SetVelocity(VectorRand() * (60 + 40*st) - self:GetVelocity() * 0.5) + + self:SetNextThink(CurTime() + 0.03) + end) + end + end}, +} + +netstream.Hook('dbg-fireworks.explode', function(pos) + + local e = ParticleEmitter(Vector()) + if IsValid(e) then + local func = octolib.array.randomWeighted(types) + if func then func(e, pos) end + end + + timer.Simple(5, function() + if IsValid(e) then e:Finish() end + end) + +end) diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/init.lua new file mode 100644 index 0000000..6d34400 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/init.lua @@ -0,0 +1,50 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.LifeTime = 2 +ENT.SoundFlame = {'ambient/gas/steam2.wav', 55, 175, 08} +ENT.SoundExplode = {'weapons/357_fire2.wav', 75, 150, 1} + +function ENT:Initialize() + + local half = Vector(1,1,1) + self:SetModel('models/hunter/plates/plate.mdl') + self:PhysicsInitBox(-half, half) + self:DrawShadow(false) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then + phys:SetMass(1) + phys:Wake() + end + + local sd = self.SoundFlame + local s = CreateSound(self, sd[1]) + s:SetSoundLevel(sd[2]) + s:PlayEx(sd[4], sd[3]) + self.sndFlame = s + + timer.Simple(self.LifeTime, function() + if not IsValid(self) then return end + self:Explode() + end) + +end + +function ENT:Explode() + + netstream.Start(nil, 'dbg-fireworks.explode', self:GetPos()) + + local sd = self.SoundExplode + self:EmitSound(sd[1], sd[2], sd[3] + math.random(-10, 10), sd[4]) + self:Remove() + +end + +function ENT:OnRemove() + + self.sndFlame:Stop() + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/shared.lua new file mode 100644 index 0000000..954a00a --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_fireworkshell/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.saluteshell +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/cl_init.lua new file mode 100644 index 0000000..af13f52 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/cl_init.lua @@ -0,0 +1,5 @@ +include('shared.lua') + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/init.lua new file mode 100644 index 0000000..a3153e6 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/init.lua @@ -0,0 +1,71 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" +local lastUses = {} + +function ENT:Initialize() + + self:SetModel('models/hunter/plates/plate2x3.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetColor(Color(0,0,0, 0)) + + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + +end + +-- function ENT:PhysicsCollide( colData, collider ) + +-- local ply = colData.HitEntity +-- if not ply:IsPlayer() or not ply:IsGhost() then return end + +-- local priestAmount = team.NumPlayers(TEAM_PRIEST) +-- if priestAmount < 1 then +-- local time = CurTime() + 30 - math.min(ply:GetKarma(), 2) * 10 +-- if ply:GetNetVar("_SpawnTime", 0) > time then +-- ply:EmitSound('dbg/revive.ogg', 70, 100, 0.8) +-- local effectdata = EffectData() +-- effectdata:SetOrigin(ply:GetPos() + Vector(0,0,45)) +-- effectdata:SetMagnitude(2.5) +-- effectdata:SetScale(2) +-- effectdata:SetRadius(3) +-- util.Effect("GlassImpact", effectdata, true, true) +-- end +-- ply:SetNetVar( "_SpawnTime", math.min(ply:GetNetVar("_SpawnTime", 0), time) ) +-- else +-- ply:Notify('warning', L.priest_online) +-- end + +-- end + +function ENT:Use(ply) + + ply:DelayedAction('reviverKarma', L.confession, { + time = 60, + check = function() return octolib.use.check(ply, self) end, + succ = function() + local sID = ply:SteamID() + if CurTime() - (lastUses[sID] or -2400) > 2400 then + ply:AddKarma(1, L.confession_help) + print('[KARMA] ' .. tostring(ply) .. ' +1 karma for prayer') + lastUses[sID] = CurTime() + else + ply:Notify('warning', L.god_love_you) + end + end, + }, { + time = 3, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_BOW) + end, + }) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/shared.lua new file mode 100644 index 0000000..4fd3c79 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_reviver/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Ghost Respawner" +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/cl_init.lua new file mode 100644 index 0000000..3a4e13d --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/cl_init.lua @@ -0,0 +1 @@ +include('shared.lua') diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/init.lua new file mode 100644 index 0000000..925ff60 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/init.lua @@ -0,0 +1,23 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +function ENT:Initialize() + + self:SetModel('models/props_wasteland/controlroom_storagecloset001a.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + +end + +function ENT:Use(ply) + + EventMakeRefugee(ply, nil, function() return IsValid(self) and octolib.use.check(ply, self) end) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/shared.lua new file mode 100644 index 0000000..26b5844 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_survivorlocker/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.survivor_locker +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/cl_init.lua new file mode 100644 index 0000000..3092607 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/cl_init.lua @@ -0,0 +1,11 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + self:DrawModel() + local bubble = self.TrashData[self:GetModel()] or {} + render.DrawBubble(self, bubble[1] or Vector(20, 0, 10), bubble[2] or Angle(0, 90, 90), 'Мусорка', 100, 50) +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/init.lua new file mode 100644 index 0000000..0750cf6 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/init.lua @@ -0,0 +1,132 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +local maxItems = 2 +local regenTime = 5 * 60 +local searchTime = 15 + +function ENT:Initialize() + + self.BaseClass.Initialize(self) + + self.items = maxItems + self.innerCont = self:GetInventory():AddContainer('trashInner', { volume = 0 }) + + self.onUserRemove = function(cont, ply) + if ply == self then return end -- player isn't trash .__. + for _, item in ipairs(cont.items) do + self.innerCont:AddItem(item) + hook.Run('dbg-trash.add', self, ply, item) + end + cont:Remove() + end + +end + +function ENT:NonHoboUse(ply) + + local sid = ply:SteamID() + local inv = self:GetInventory() + local cont = inv:GetContainer(sid) or inv:AddContainer(sid, { + name = 'Мусорка', + volume = 999, + icon = octolib.icons.color('trash'), + }):Hook('userRemove', 'flushTrash', self.onUserRemove) + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not (IsValid(self) and IsValid(ply)) then return end + ply:OpenInventory(inv, { sid }) + end) + +end + +function ENT:Use(ply) + + if self.pendingWorker == ply then + local volume = math.min(100, (-self.innerCont:FreeSpace()) / 10) + if not (ply.inv and ply.inv.conts._hand and ply.inv.conts._hand:FreeSpace() >= volume) then + return ply:Notify('warning', 'Освободи руки, чтобы собрать мусор') + end + ply:ClearMarkers('work1') + ply:DelayedAction('work', 'Сбор мусора', { + time = 8, + check = function() return octolib.use.check(ply, self) and ply.inv and ply.inv.conts._hand and ply.inv.conts._hand:FreeSpace() >= volume end, + succ = function() + self.innerCont:Clear() + ply.inv.conts._hand:AddItem('souvenir', { + name = 'Мусор', + desc = 'Заполненный мусорный мешок', + model = 'models/props_junk/garbage_bag001a.mdl', + icon = octolib.icons.color('trash_pile'), + mass = math.min(volume / 10, 10), + volume = volume, + expire = os.time() + 60 * 60 + }) + ply:Notify('Теперь отнеси этот мешок в указанную точку на утилизацию') + self.pendingWorker = nil + hook.Run('dbg-trash.empty', self, ply) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + self:EmitSound('physics/cardboard/cardboard_box_strain'..math.random(1,3)..'.wav', 60, 100, 0.75) + end, + }) + return + end + + if not ply:getJobTable().hobo then + return self:NonHoboUse(ply) + end + + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= 'dbg_hands' then + ply:Notify('warning', L.trash_can_search) + return + end + + ply:DelayedAction('trash', L.trash_search, { + time = searchTime, + check = function() return octolib.use.check(ply, self) end, + succ = function() + if self.items <= 0 and not self.innerCont.items[1] then + ply:Notify('warning', L.trash_nothing) + return + end + + local item + if self.innerCont.items[1] then + item = { self.innerCont.items[1].class, self.innerCont.items[1]:Export() } + self.innerCont:TakeItem(self.innerCont.items[1]) + end + if not item then + item = octoinv.getRandomLoot({mode = 'trash', flatten = math.Clamp(ply:GetKarma() or 0, -1000, 1000) / 2000}).item + self.items = self.items - 1 + end + ply.inv.conts._hand:AddItem(unpack(item)) + hook.Run('dbg-trash.loot', self, ply, item) + ply:Notify((L.trash_searched):format(octoinv.itemStr(item))) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + end, + }) + +end + +function ENT:Think() + + if self.items >= maxItems then return end + + self.items = self.items + 1 + self:NextThink(CurTime() + regenTime) + return true + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/shared.lua new file mode 100644 index 0000000..8c468c4 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_trash/shared.lua @@ -0,0 +1,57 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' +ENT.PrintName = L.trash +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.Model = 'models/props_junk/trashdumpster01a.mdl' +ENT.Physics = false + +ENT.TrashData = { + ['models/highrise/trash_can_03.mdl'] = { Vector(14, 0, 15), Angle(0, 90, 90) }, + ['models/highrise/trashcanashtray_01.mdl'] = { Vector(15.75, -10, 10), Angle(0, 90, 90) }, + ['models/props_generic/trashbin002.mdl'] = { Vector(12, 0, 16), Angle(0, 90, 90) }, + ['models/props_interiors/trashcan01.mdl'] = { Vector(12, 0, 36), Angle(0, 90, 90) }, + ['models/props_interiors/trashcankitchen01.mdl'] = { Vector(10.5, 0, 25.5), Angle(0, 90, 90) }, + ['models/props_junk/trashbin01a.mdl'] = { Vector(12.5, 0, 13.5), Angle(0, 90, 90) }, + ['models/props_junk/trashdumpster01a.mdl'] = { Vector(20, 0, 10), Angle(0, 90, 90) }, + ['models/props_trainstation/trashcan_indoor001a.mdl'] = { Vector(13, 0, 18), Angle(0, 90, 90) }, + ['models/props_trainstation/trashcan_indoor001b.mdl'] = { Vector(13, 0, 18), Angle(0, 90, 90) }, + ['models/props/cs_office/trash_can.mdl'] = { Vector(0, -7.75, 19), Angle(0, 0, 90) }, + ['models/props/cs_office/trash_can_p.mdl'] = { Vector(0, -7.75, 19), Angle(0, 0, 90) }, +} +ENT.models = {} +local i = 1 +for k in pairs(ENT.TrashData) do + ENT.models[#ENT.models + 1] = { + name = string.StripExtension(string.GetFileFromFilename(k)), + model = k, + previewOffset = Vector(0, 0, 25), + } + i = i + 1 +end + +duplicator.RegisterEntityClass('ent_dbg_trash', function(ply, data) + local ent = duplicator.GenericDuplicatorFunction(ply, data) + ent:SetModel(ent.Model) + ent:PhysicsInit(SOLID_VPHYSICS) + ent:SetMoveType(MOVETYPE_VPHYSICS) + ent:SetSolid(SOLID_VPHYSICS) + local physObj = ent:GetPhysicsObject() + if IsValid(physObj) then physObj:EnableMotion(false) end + + if IsValid(ply) then + undo.Create('ent_dbg_trash') + undo.AddEntity(ent) + undo.SetPlayer(ply) + undo.Finish() + ply:AddCount('ent_dbg_trash', ent) + ply:AddCleanup('ent_dbg_trash', ent) + end + + return ent +end, 'Data', 'Model') diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/cl_init.lua new file mode 100644 index 0000000..444aa13 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/cl_init.lua @@ -0,0 +1,281 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +surface.CreateFont('dbg-travel.normal', { + font = 'Calibri', + extended = true, + size = 48, + weight = 350, +}) + +surface.CreateFont('dbg-travel.small', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +surface.CreateFont('dbg-travel.xsmall', { + font = 'Calibri', + extended = true, + size = 24, + weight = 350, +}) + +local dstVal, timeVal = '%s останов%s', '≈%sсек' +local gbMsgs = {'Приятной поездки!', 'Хорошей дороги!', 'Счастливого пути!', 'Доброго пути!'} +local noFeeTime, transferFee, transferLength = ENT.noFeeTime, ENT.transferFee, ENT.transferLength +local goFrame + +local function param(parent, name, val) + local wrap = parent:Add('DPanel') + wrap:Dock(TOP) + wrap:SetTall(45) + + local title = wrap:Add('DLabel') + title:Dock(LEFT) + title:DockMargin(5, 5, 5, 5) + title:SetFont('f4.normal') + title:SetText(name) + title:SizeToContentsX() + + local value = wrap:Add('DLabel') + value:Dock(FILL) + value:DockMargin(5, 5, 5, 5) + value:SetFont('f4.normal') + value:SetText(val or '...') + value:SetContentAlignment(6) + + return value +end + +netstream.Hook('dbg-travel.buildMenu', function(stops, nt, justUpdate, curStop) + + if IsValid(goFrame) then return end -- maybe he is afk, so we shouldn't transfer him further + + if justUpdate then + for i,v in ipairs(stops) do + local mark = octomap.createMarker('bus_' .. i) + mark:SetPos(v[1]) + mark:SetIconSize(16) + mark:SetClickable(true) + mark.pos = i + mark:SetIcon('octoteam/icons-16/bus.png') + mark:AddToSidebar('Остановка', 'bus') + mark.LeftClick = octomap.sidebarMarkerClick + end + + return + end + + local start = CurTime() + goFrame = vgui.Create('DFrame') + goFrame:SetTitle('Автобус') + goFrame:SetSize(720, 480) + goFrame:SetMinHeight(395) + goFrame:SetSizable(true) + goFrame:SetPos(ScrW(), (ScrH() - goFrame:GetTall()) / 2) + local tgt = (ScrW() - goFrame:GetWide()) / 2 + goFrame:MakePopup() + + local map = goFrame:Add('octomap') + map:DockMargin(5, 5, 5, 5) + map:SetOptions({ + minScale = 0.01, + scale = 0.15, + tgtScale = 0.15, + paddingR = 270, + }) + + local ot = goFrame.Think + function goFrame:Think() + if isfunction(ot) then ot(self) end + local x, y = self:GetPos() + x = x + (tgt - x) * octolib.tween.easing.outQuad(math.min(CurTime() - start, 0.75) / 0.75, 0, 1, 1) + self:SetPos(x, y) + if x <= tgt then + self.Think = ot + elseif x - tgt <= 88 then + map:AlignToBounds() + end + end + + local osc = goFrame.OnSizeChanged + function goFrame:OnSizeChanged(w, h) + if isfunction(osc) then osc(self, w, h) end + if w ~= 1.5 * h then self:SetWide(1.5 * h) end + map.tgtScale = 0.000333 * self:GetTall() + end + + local right = map:Add('DPanel') + right:Dock(RIGHT) + right:DockMargin(5, 5, 5, 5) + right:DockPadding(5, 5, 5, 5) + right:SetWide(260) + + local e = octolib.label(right, 'Автобус') + e:SetTall(45) + e:SetFont('f4.medium') + e:SetContentAlignment(5) + + local descPnl = right:Add('DPanel') + descPnl:Dock(TOP) + descPnl:SetTall(45) + e = octolib.label(descPnl, 'Выбери остановку на карте') + e:Dock(FILL) + e:SetFont('dbg-travel.xsmall') + e:DockMargin(5, 5, 5, 5) + e:SetContentAlignment(5) + + local params = right:Add('DPanel') + params:Dock(TOP) + params:SetTall(140) + params:DockMargin(0, 5, 0, 5) + params:SetDrawBackground(false) + + local marks, cur, sel = {} + local dist, time, price = param(params, 'Расстояние'), param(params, 'Время в пути'), param(params, 'Стоимость проезда') + local function updateParams() + local dst = octolib.math.loopedDist(cur, sel, 0, #stops) + dist:SetText(dstVal:format(dst, octolib.string.formatCount(dst, 'ка', 'ки', 'ок'))) + time:SetText(timeVal:format(transferLength * dst)) + price:SetText(DarkRP.formatMoney(curStop:GetFee(LocalPlayer(), dst))) + end + + local function click(marker) + if marker.pos == cur or marker.pos == sel then return end + marks[sel]:SetIcon('octoteam/icons-32/bullet_white.png') + sel = marker.pos + marker:SetIcon('octoteam/icons-32/bullet_green.png') + updateParams() + end + local function reset() + for _,v in ipairs(marks) do + v:SetIcon('octoteam/icons-16/bus.png') + v:SetIconSize(16) + v.LeftClick = octomap.sidebarMarkerClick + v.RightClick = nil + v:AddToSidebar('Остановка', 'bus') + end + end + local ooc = goFrame.OnClose + function goFrame:OnClose() + if isfunction(ooc) then ooc(self) end + reset() + end + + for i,v in ipairs(stops) do + local mark = octomap.createMarker('bus_' .. i) + mark:SetPos(v[1]) + mark:SetIconSize(32) + mark.pos = i + if v[2] then + cur = i + mark:SetIcon('octoteam/icons-32/bullet_purple.png') + else + mark.LeftClick, mark.RightClick = click + mark:SetIcon('octoteam/icons-32/bullet_white.png') + end + marks[#marks+1] = mark + end + sel = cur % #stops + 1 + marks[sel]:SetIcon('octoteam/icons-32/bullet_green.png') + + updateParams() + + local tl = math.ceil(nt - CurTime()) - 1 + local tlPanel = octolib.label(right, 'Отправление через ' .. tl) + tlPanel:SetFont('f4.normal') + tlPanel:SetTall(40) + tlPanel:SetContentAlignment(5) + local usageWarn = octolib.label(right, 'Не закрывай это окно, чтобы поехать') + usageWarn:SetContentAlignment(5) + timer.Create('bus.tl', 1, tl, function() + if not IsValid(tlPanel) then + timer.Remove('bus.tl') + reset() + return + end + if tl <= 1 then + netstream.Start('dbg-travel.submit', cur, sel) + tlPanel:SetText(gbMsgs[math.random(#gbMsgs)]) + descPnl:Remove() + usageWarn:Remove() + timer.Remove('bus.tl') + return + end + tl = tl - 1 + tlPanel:SetText('Отправление через ' .. tl) + end) + +end) + +function ENT:Initialize() + + self.controlText = ('Нажми %s, чтобы выбрать остановку'):format(input.LookupBinding('+use', true):upper()) + self.timeText = ('%d:%02d'):format(0, 0) + noFeeTime = self.noFeeTime or noFeeTime or 5 * 60 * 60 + transferFee = self.transferFee or transferFee or 40 + transferLength = self.transferLength or transferLength or 10 + +end + +local screenPos, screenAng = Vector(-27,59,93), Angle(0,0,100) +local colors = CFG.skinColors + +function ENT:DrawInfobox(relPos, relAng, al) + local col1, col2, ply = Color(238,238,238, al), Color(180,180,180, al), LocalPlayer() + local pos, ang = LocalToWorld(relPos, relAng, self:GetPos(), self:GetAngles()) + + local mins = self:GetRenderBounds() + if self:WorldToLocal(EyePos()).x < mins.x then return end + + cam.Start3D2D(pos, ang, 0.058) + draw.RoundedBox(8, -250, -75, 500, 225, ColorAlpha(colors.bg50, al)) + draw.SimpleText(L.next_bus .. (self.timeText or '0:00'), 'dbg-travel.normal', 0, 0, col1, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + local price = (self:FreeTransfer(ply) and '' or 'от ') .. DarkRP.formatMoney(self:GetFee(ply)) + draw.SimpleText(L.price_bus .. price, 'dbg-travel.normal', 0, 40, col1, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(self.insideText or 'Дождись, пока приедет автобус', 'dbg-travel.small', 0, 85, col2, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() +end + +function ENT:Draw() + + self:DrawModel() + + local ply = LocalPlayer() + if IsValid(ply) then + local al = math.Clamp(1 - (self:GetPos():DistToSqr(EyePos()) - 40000) / 40000, 0, 1) * 255 + if al > 0 then + self:DrawInfobox(screenPos, screenAng, al) + end + end + + +end + +function ENT:Think() + + if self:GetPos():DistToSqr(EyePos()) < 80000 then + local time = math.max(self:GetNetVar('nextTransfer', 0) - CurTime(), 0) + self.timeText = ('%d:%02d'):format(math.floor(time / 60), math.floor(time % 60)) + + local ply = LocalPlayer() + if IsValid(ply) then + local can, why = self:CanTransfer(ply, self:GetFee(ply)) + self.insideText = why + if can then + if self:GetNetVar('nextTransfer', 0) - CurTime() > self.menuLength then + self.insideText = 'Дождись, пока приедет автобус' + else self.insideText = self.controlText end + end + end + end + + self:NextThink(CurTime() + 1) + return true + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/init.lua new file mode 100644 index 0000000..0809ba9 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/init.lua @@ -0,0 +1,135 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +local transferDiff = {70, 130} -- time difference in % + +netstream.Hook('dbg-travel.submit', function(ply, from, to) + + if from == to then return end + local stops = ents.FindByClass('ent_dbg_travel') + if from < 1 or from > #stops then return end + if to < 1 or to > #stops then return end + + local travel = stops[from] + if CurTime() - travel.pendingTransfer > 3 then return end + if not travel:IsPlayerWaiting(ply) then return end + + local dist = octolib.math.loopedDist(from, to, 0, #stops) + local fee = travel:GetFee(ply, dist) + if not travel:CanTransfer(ply, fee, notify) then return end + + local time = dist * travel.transferLength * math.random(unpack(transferDiff)) * 0.01 + ply:ScreenFade(SCREENFADE.OUT, color_black, 1, time) + timer.Simple(1.5, function() + if IsValid(ply) then + ply:ExitVehicle() + ply:SetNoDraw(true) + ply:SetNotSolid(true) + ply:Freeze(true) + end + end) + timer.Simple(time, function() + if IsValid(ply) then + local pos = stops[to]:LocalToWorld(Vector(45, -30 + 35 * stops[to].transferPos, 10)) + stops[to].transferPos = (stops[to].transferPos + 1) % 3 + ply:SetPos(pos) + ply:SetNoDraw(false) + if IsValid(ply:GetActiveWeapon()) then ply:GetActiveWeapon():SetNoDraw(false) end + ply:SetNotSolid(false) + ply:Freeze() + ply:addMoney(-fee) + ply:Notify(L.you_used_bus .. DarkRP.formatMoney(fee)) + ply:ScreenFade(SCREENFADE.IN, color_black, 1, 1) + end + end) +end) + +function ENT:Initialize() + + self:SetModel('models/octoteam/props/modern_bus_stop.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.pendingTransfer = 0 + self.transferPos = 0 + self.nextMenuShow = 0 + +end + +function ENT:Think() + + if self:GetNetVar('nextTransfer', 0) <= CurTime() then + self:TransferPlayers() + elseif self:GetNetVar('nextTransfer', 0) - CurTime() <= 3 then + self.pendingTransfer = CurTime() + end + if self.nextMenuShow <= CurTime() then + self:ShowMenu() + end + + self:NextThink(CurTime() + 1) + return true + +end + +function ENT:IsPlayerWaiting(ply) + for _,v in ipairs(self:GetWaitingPlayers()) do + if v == ply then return true end + end + return false +end + +function ENT:GetWaitingPlayers() + return ents.FindInSphere(self:LocalToWorld(self:OBBCenter()), self:BoundingRadius() * 0.5) +end + +function ENT:TransferPlayers() + local transferTime = CurTime() + math.random(unpack(CurTime() < 1800 and self.transferIntervalLow or self.transferInterval)) + self:SetNetVar('nextTransfer', transferTime) + self.nextMenuShow = math.max(CurTime(), transferTime - self.menuLength) +end + +function ENT:ShowMenu() + local stops = {} + for _,v in ipairs(ents.FindByClass('ent_dbg_travel')) do + stops[#stops+1] = {v:GetPos()} + if v == self then stops[#stops][2] = true end + end + for i, ply in ipairs(self:GetWaitingPlayers()) do + if not ply:IsPlayer() or ply:IsAFK() or ply.sg_invisible then continue end + if self:CanTransfer(ply, self:GetFee(ply), true) then + netstream.Start(ply, 'dbg-travel.buildMenu', stops, self:GetNetVar('nextTransfer', 0), false, self) + end + end + self.nextMenuShow = math.huge +end + +function ENT:Use(activator) + if not self:IsPlayerWaiting(activator) then return end + if not self:CanTransfer(activator, self:GetFee(activator), true) then return end + if self:GetNetVar('nextTransfer', 0) - CurTime() > self.menuLength then + activator:Notify('Автобус еще не приехал') + return + end + local stops = {} + for _,v in ipairs(ents.FindByClass('ent_dbg_travel')) do + stops[#stops+1] = {v:GetPos()} + if v == self then stops[#stops][2] = true end + end + netstream.Start(activator, 'dbg-travel.buildMenu', stops, self:GetNetVar('nextTransfer', 0), false, self) +end + +-- send player bus stops' positions +hook.Add('PlayerFinishedLoading', 'dbg-travel', function(ply) + + local stops = {} + for _,v in ipairs(ents.FindByClass('ent_dbg_travel')) do + stops[#stops+1] = {v:GetPos()} + end + + netstream.Start(ply, 'dbg-travel.buildMenu', stops, 0, true) + +end) diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/shared.lua new file mode 100644 index 0000000..2f84c80 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_travel/shared.lua @@ -0,0 +1,43 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.travel +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.transferFee = 50 +ENT.transferInterval = {60, 120} +ENT.transferIntervalLow = {30, 60} +ENT.transferLength = 5 +ENT.noFeeTime = 5 * 60 * 60 +ENT.menuLength = 15 + +function ENT:FreeTransfer(ply) + return ply:GetTimeHere() <= (self.noFeeTime or 5 * 60 * 60) +end + +function ENT:GetFee(ply, dst) + return (dst or 1) * (self.transferFee or 50) * (CurTime() <= 1800 and 0.5 or 1) * (self:FreeTransfer(ply) and 0 or 1) +end + +hook.Add('dbg-travel.canTransfer', 'dbg-travel.default', function(ply, fee, travel) + if not (ply:canAfford(fee) or travel:FreeTransfer(ply)) then + return false, L.bus_not_enough_money + end +end) + +function ENT:CanTransfer(ply, fee, notify) + local can, why = hook.Run('dbg-travel.canTransfer', ply, fee, self) + if can == false and notify then + why = why or 'Ты не можешь сейчас воспользоваться автобусом' + if SERVER then + ply:Notify('warning', why) + elseif CLIENT and ply == LocalPlayer() then + octolib.notify.show('warning', why) + end + end + return can ~= false, why +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/cl_init.lua new file mode 100644 index 0000000..9d325b2 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/cl_init.lua @@ -0,0 +1,16 @@ +include('shared.lua') + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Initialize() + + -- something + +end + +function ENT:Draw() + self:DrawModel() + render.DrawBubble(self, Vector(0, -21.8, 70), Angle(0, 0, 90), L.soda, 200, 200) +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/init.lua new file mode 100644 index 0000000..d025cdc --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/init.lua @@ -0,0 +1,114 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +function ENT:Initialize() + + self:SetModel('models/props/cs_office/vending_machine.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.bottlesLeft = 5 + +end + +function ENT:Use(ply) + + if IsValid(self.pendingWorker) then + if self.pendingWorker ~= ply then ply:Notify(L.automatic_service) end + + local item, itemID + if ply.inv and ply.inv.conts._hand then + for i, v in ipairs(ply.inv.conts._hand:GetItems()) do + if v:GetData('name') == L.soda_block then + item, itemID = v, i + break + end + end + end + + if not item then + ply:Notify('warning', L.where_soda) + return + end + + ply:ClearMarkers('work2') + ply:DelayedAction('work', L.load_soda, { + time = 20, + check = function() return octolib.use.check(ply, self) and ply.inv and ply.inv.conts._hand and ply.inv.conts._hand:GetItem(itemID) == item end, + succ = function() + self.bottlesLeft = 15 + item:Remove() + self.finish(ply) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + self:EmitSound('physics/cardboard/cardboard_box_strain'..math.random(1,3)..'.wav', 60, 100, 0.75) + end, + }) + else + if IsValid(ply) then ply:Notify('ooc', L.to_buy_soda) end + end + +end + +local cost = 200 +function ENT:StartTouch(ent) + + if not IsValid(ent) or ent:GetClass() ~= 'octoinv_item' or not ent:GetNetVar('Item') or ent.aquired then return end + local ply = ent.droppedBy + + if self.bottlesLeft <= 0 then + if IsValid(ply) then ply:Notify('warning', L.run_out_of_bottles) end + return + end + + if ent:GetNetVar('Item')[2] ~= cost then + if IsValid(ply) then ply:Notify('warning', L.only_200) end + return + end + + ent.aquired = true + ent:Remove() + + local pos, ang = LocalToWorld(Vector(0,-20,16), Angle(-90,0,0), self:GetPos(), self:GetAngles()) + local ent = ents.Create 'octoinv_item' + ent:SetPos(pos) + ent:SetAngles(ang) + ent.Model = 'models/props/cs_office/water_bottle.mdl' + + ent:SetData('food', { + name = L.soda, + model = 'models/props/cs_office/water_bottle.mdl', + icon = 'octoteam/icons/bottle.png', + energy = 15, + maxenergy = 35, + drink = true, + }) + + self.bottlesLeft = self.bottlesLeft - 1 + + ent:Spawn() + ent:Activate() + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + +end + +-- function ENT:Think() + +-- self:NextThink(CurTime() + 60) +-- self.bottlesLeft = math.min(self.bottlesLeft + 1, 15) + +-- return true + +-- end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/shared.lua new file mode 100644 index 0000000..002b316 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_vend/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.soda_machine +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/cl_init.lua new file mode 100644 index 0000000..8df3434 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/cl_init.lua @@ -0,0 +1,15 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Draw() + + self:DrawModel() + + local job = LocalPlayer():getJobTable() + if not job or not job.worker then return end + render.DrawBubble(self, Vector(-28, 16.99, 50), Angle(0, 180, 90), L.city_warehouse, 200, 200) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/init.lua new file mode 100644 index 0000000..90b7be5 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/init.lua @@ -0,0 +1,54 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +ENT.Model = 'models/props/cs_militia/crate_stackmill.mdl' +ENT.CollisionGroup = COLLISION_GROUP_NONE +ENT.Physics = true + +function ENT:Use(ply, caller) + + if not ply:IsPlayer() then return end + + local sid = ply:SteamID() + if self.finishTrash and self.finishTrash[sid] then + local item = ply.inv:FindItem({ class = 'souvenir', name = 'Мусор' }) + if not item then return end + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if item:GetParent():TakeItem(item) == 1 then + self.finishTrash[sid](ply) + self.finishTrash[sid] = nil + ply:ClearMarkers('work2') + end + end) + return + end + + if not ply:CanUseInventory(self.inv) then return end + + local cont = self.inv:GetContainer(sid) + if not cont then return end + + ply:ClearMarkers('work1') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not IsValid(self) then return end + ply:OpenInventory(self.inv, { sid }) + end) + +end + +function ENT:Think() + + for contID, cont in pairs(self.inv.conts) do + if cont.volume == cont:FreeSpace() then + cont:Remove() + end + end + + self:NextThink(CurTime() + 1) + return true + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/shared.lua new file mode 100644 index 0000000..5150218 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workerbox/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' +ENT.PrintName = L.workerbox +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true \ No newline at end of file diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/cl_init.lua new file mode 100644 index 0000000..76b8034 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/cl_init.lua @@ -0,0 +1,13 @@ +include 'shared.lua' + +ENT.Spawnable = true +ENT.AdminSpawnable = true +ENT.RenderGroup = RENDERGROUP_OPAQUE + +function ENT:Draw() + + if octolib.drawDebug then + self:DrawModel() + end + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/init.lua new file mode 100644 index 0000000..23b327a --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/init.lua @@ -0,0 +1,94 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/hunter/blocks/cube1x1x1.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + self:SetUseType(SIMPLE_USE) + self:DrawShadow(false) + +end + +function ENT:CheckRequired(ply) + + if self.pendingWorker ~= ply then return false end + if self.requiredItems then + for i, v in ipairs(self.requiredItems) do + if ply:HasItem(v[1]) < v[2] then + ply:Notify('warning', L.where_details) + return false + end + end + end + + return true + +end + +function ENT:SetWork(ply, data) + + if not IsValid(ply) or not data or not data.finish then return end + + self.pendingWorker = ply + self.finish = data.finish + self.repairTime = data.time or 20 + self.requiredItems = data.items + +end + +function ENT:UnsetWork() + + self.pendingWorker = nil + self.finish = nil + self.repairTime = nil + self.requiredItems = nil + +end + +local sounds = { + 'physics/metal/chain_impact_hard1.wav', + 'physics/metal/chain_impact_soft3.wav', + 'physics/metal/metal_box_impact_soft3.wav', + 'physics/metal/metal_box_strain2.wav', + 'physics/metal/metal_box_strain4.wav', + 'ambient/machines/pneumatic_drill_1.wav', + 'ambient/machines/pneumatic_drill_2.wav', + 'ambient/machines/squeak_2.wav', + 'ambient/machines/squeak_5.wav', + 'ambient/machines/squeak_6.wav', + 'ambient/machines/squeak_7.wav', + 'ambient/machines/squeak_8.wav', +} + +function ENT:Use(ply) + if not self:CheckRequired(ply) then return end + + ply:ClearMarkers('work2') + ply:DelayedAction('work', L.repair_action, { + time = self.repairTime, + check = function() + return ply:GetPos():DistToSqr(self:GetPos()) < self.RepairDistSqr + end, + succ = function() + if not self:CheckRequired(ply) then return end + for i, v in ipairs(self.requiredItems) do + ply:TakeItem(v[1], v[2]) + end + self.finish(ply) + end, + }, { + time = 1.5, + inst = true, + action = function() + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + local snd = table.Random(sounds) + self:EmitSound(snd, 60, 100, 0.8) + end, + }) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/shared.lua new file mode 100644 index 0000000..8726a95 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_workproblem/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = L.workproblem +ENT.Category = L.dobrograd +ENT.Author = 'chelog' +ENT.Contact = 'chelog@octothorp.team' + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.RepairDistSqr = 6400 diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/cl_init.lua new file mode 100644 index 0000000..7b97202 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/cl_init.lua @@ -0,0 +1,5 @@ +include "shared.lua" + +function ENT:Draw() + self:DrawModel() +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/init.lua new file mode 100644 index 0000000..ca726e0 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/init.lua @@ -0,0 +1,181 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +octoinv.registerLoot(70, { + mode = 'christmas', + item = {'food', { + name = L.cookie, + energy = 15, + icon = 'octoteam/icons/food_cookies.png', + mass = 0.15, + volume = 0.15, + -- amount = 5, + }}, +}) + +octoinv.registerLoot(50, { + mode = 'christmas', + item = {'food', { + name = 'Леденец', + desc = '', + energy = 25, + icon = 'octoteam/icons/food_candy.png', + mass = 0.2, + volume = 0.2, + -- amount = 3, + }}, +}) + +octoinv.registerLoot(4, { + mode = 'christmas', + item = {'h_mask', { + name = L.mask_gingerbread, + mask = 'gingerbread', + }}, +}) + +octoinv.registerLoot(4, { + mode = 'christmas', + item = {'h_mask', { + name = 'Новогодний колпак', + icon = 'octoteam/icons/xmas_hat.png', + mask = 'santa', + }}, +}) + +octoinv.registerLoot(1, { + mode = 'christmas', + item = {'car_att', { + name = 'Снеговичок', + att = 'snowman', + desc = 'Немного рождества на приборной панели твоего авто!', + }}, +}) + +local perDay, resetTime, interval = 2, 23*60*60, 3*60 +local function checkTreat(ply) + + local amount, resetAfter = unpack(ply:GetDBVar('nyCache') or { perDay, os.time() + resetTime }) + if amount <= 0 and os.time() > resetAfter then + amount = perDay + ply:SetDBVar('nyCache', { amount, resetAfter }) + end + + return amount > 0 + +end + +local function takeTreat(ply) + + local amount, resetAfter = unpack(ply:GetDBVar('nyCache') or { perDay, os.time() + resetTime }) + amount = amount - 1 + if amount <= 0 then + resetAfter = os.time() + resetTime + end + + ply:SetDBVar('nyCache', { amount, resetAfter }) + +end + +local function canMoveIn() + return false, '' +end + +local pmeta = FindMetaTable('Player') + +function pmeta:RandomXMasTreat(prevent) + + local box = table.Random(ents.FindByClass('ent_dbg_xmas_box')) + if not IsValid(box) then return end + + local sID = self:SteamID() + local inv = box:GetInventory() + local cont = inv:GetContainer(sID) or inv:AddContainer(sID, {name = 'Подарок', volume = 250, icon = octolib.icons.color('gift_xmas')}):Hook('canMoveIn', 'xmasbox', canMoveIn) + if self:GetKarma() < 0 and math.random(2) == 1 then + cont:AddItem('souvenir', { + name = 'Уголек', + icon = 'octoteam/icons/coal.png', + desc = 'Существует древняя традиция, согласно которой Санта Клаус оставляет непослушным детям уголек вместо подарка', + }) + else + local item = octoinv.getRandomLoot({ + flatten = math.Clamp(self:GetKarma() or 0, -1000, 1000) / 1000, + mode = 'christmas', + }).item + cont:AddItem(unpack(item)) + end + + self:Notify('hint', 'Санта положил тебе подарок под елочку, забирай скорей!') + self:AddMarker { + txt = 'Подарок', + pos = box:GetPos() + Vector(0,0,12), + col = Color(255,92,38), + des = {'dist', { 100 }}, + icon = 'octoteam/icons-16/gift_add.png', + } + if not prevent then takeTreat(self) end + +end + +timer.Create('dbg-newyear.giveAway', interval, 0, function() + + if player.GetCount() < 10 or math.random(2) ~= 1 then return end + + local plys = octolib.array.filter(player.GetAll(), function(ply) return not ply:IsAFK() and checkTreat(ply) end) + if #plys >= 1 then table.Random(plys):RandomXMasTreat() end + +end) + +timer.Simple(10, function() + math.randomseed(os.time()) + for _,v in ipairs(ents.FindByClass('ent_dbg_xmas_box')) do + v:SetSkin(math.random(0, 5)) + end +end) + +function ENT:Initialize() + + self:SetModel('models/brewstersmodels/christmas/present1_large.mdl') + self:SetSkin(math.random(0, 5)) + self:SetModelScale(2.5) + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.inv = self:CreateInventory() + +end + +function ENT:Use(ply, caller) + + if not ply:IsPlayer() then return end + if not ply:CanUseInventory(self.inv) then return end + + local contID = ply:SteamID() + local cont = self.inv:GetContainer(contID) + if not cont then return end + + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + timer.Simple(0.5, function() + if not IsValid(self) then return end + ply:OpenInventory(self.inv, { contID }) + end) + +end + +function ENT:Think() + + for contID, cont in pairs(self.inv.conts) do + if cont.volume == cont:FreeSpace() then + cont:Remove() + end + end + + self:NextThink(CurTime() + 1) + return true + +end + diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/shared.lua new file mode 100644 index 0000000..eacb638 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_box/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = 'anim' +ENT.Base = 'octoinv_cont' + +ENT.Category = L.dobrograd +ENT.PrintName = 'Подарок на новый год' +ENT.Author = 'Wani4ka' +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/cl_init.lua new file mode 100644 index 0000000..5e5217f --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/cl_init.lua @@ -0,0 +1,30 @@ +include "shared.lua" + +netstream.Hook('ornament.sparkles', function(pos, color) + + local emitter = ParticleEmitter(pos) + for i = 1, 20 do + local dir = VectorRand() + local particle = emitter:Add("particle/fire", pos + dir * 1) + if particle then + particle:SetColor(color.r, color.g, color.b) + + particle:SetVelocity(dir * (50 + math.random(0, 30))) + particle:SetDieTime(math.random(5,10) / 2) + particle:SetLifeTime(0) + + particle:SetAngles(AngleRand()) + particle:SetStartSize(5) + particle:SetEndSize(0) + + particle:SetStartAlpha(255) + particle:SetEndAlpha(0) + + particle:SetAirResistance(100) + particle:SetCollide(true) + end + end + + timer.Simple(6, function() emitter:Finish() end) + +end) diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/init.lua new file mode 100644 index 0000000..f33df61 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/init.lua @@ -0,0 +1,24 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +function ENT:Initialize() + + if not self.ownerSID64 then return self:Remove() end + + if self.isStar then + self:SetModel('models/wilderness/treestar.mdl') + else + self:SetModel('models/unconid/xmas/ornaments_u.mdl') + self:SetSkin(math.random(0, 2)) + self:SetModelScale(2.5) + end + + self:PhysicsInit(SOLID_VPHYSICS) + --self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetUnFreezable(true) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/shared.lua new file mode 100644 index 0000000..0c12715 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmas_ornament/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = 'Новогодний шарик' +ENT.Category = L.dobrograd +ENT.Author = "Wani4ka" +ENT.Contact = "4wk@wani4ka.ru" + +ENT.Spawnable = true +ENT.AdminSpawnable = false diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/cl_init.lua new file mode 100644 index 0000000..554de5e --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/cl_init.lua @@ -0,0 +1,28 @@ +include 'shared.lua' + +local lposes = { + { Vector(30, 0, 330), true }, + { Vector(-30, 0, 330), true }, + { Vector(0, 0, 100), false }, +} + +function ENT:Think() + + local pos = self:GetPos() + if pos:DistToSqr(EyePos()) > 1500000 then return end + + for i, v in ipairs(lposes) do + local dlight = DynamicLight(self:EntIndex() + i - 1, v[2]) + if dlight then + dlight.pos = self:GetPos() + v[1] + dlight.r = 255 + dlight.g = 200 + dlight.b = 150 + dlight.brightness = 0.5 + dlight.Decay = 1000 + dlight.Size = 1024 + dlight.DieTime = CurTime() + 1 + end + end + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/init.lua new file mode 100644 index 0000000..a990853 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/init.lua @@ -0,0 +1,210 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/mark2580/sanctum/props/sanctum_christmas_tree.mdl') + for i = 1, 4 do + self:SetBodygroup(i, 1) + end + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self.balls = {} + self.fireworks = {} + self.lastPurchaseID = 0 + self.nextBallPosition = 1 + +end + +local function parse(data) + math.randomseed(CurTime()) + local ans = {} + for _,obj in RandomPairs(string.Explode('; ', data)) do + local dat = string.Explode(' ', obj) + ans[#ans + 1] = { + Vector(tonumber(dat[1]), tonumber(dat[2]), tonumber(dat[3]) - 1.5), + Angle(tonumber(dat[4]), tonumber(dat[5]), tonumber(dat[6])), + } + end + return ans +end +local poses = parse('70.5 -41.1 37.3 0 -62.7 0; 70.5 -68.9 38.5 0 16.4 0; -50.7 50.8 39.2 0 67.1 0; -59.3 24.3 40.2 0 0 0; -25.2 -67 41 0 -60 0; -61.3 -44.2 41.2 0 -60 0; 64.3 -13.7 42.1 0 -25.4 0; -8.6 98.6 47.6 0 0 0; -74.9 -12.2 50.9 0 -60 0; 12.5 -74.6 51.3 0 16.4 0; 26.7 81.2 54.4 0 0 0; -59.6 53.7 62.2 0 45.4 0; 82.2 33.3 67.1 0 -60.8 0; -70.4 -57.8 68.3 0 -60 0; -81.6 -4.8 70.1 0 -60 0; 85.8 5.4 71.3 0 31.9 0; 63.9 -76.3 75 0 98 0; -57.4 -36.6 75.5 0 -60 0; 67.5 58.4 76.7 0 -15.3 0; 50 -92.2 77.6 0 6.1 0; -73.5 37.6 77.7 0 94.2 0; 4.4 -82.1 78.7 0 6.1 0; -24.2 74.2 80.6 0 22.3 0; 66.3 -23.8 81.4 0 35.4 0; -77.6 20 81.6 0 50.4 0; 26.3 63.3 81.6 0 -15.3 0; -40.3 -69.8 81.7 0 -60 0; -11.3 88.9 86 0 0 0; -45.6 60.3 88.8 0 56.8 0; 41.6 -70 93.6 0 18.1 0; 96.2 17.5 95.1 0 -94.6 0; 64.5 -10.5 100.2 0 -94.6 0; -52 -21.3 102 0 56.8 0; -40.1 -61.3 105.2 0 119.2 0; -18.8 -82 108.7 0 11.7 0; 64.3 14.5 115.6 0 18.1 0; 39.4 50.4 117.9 0 -52.7 0; 59.2 -38 122.3 0 52.8 0; 28 79.6 129.9 0 -19 0; -13.6 56.8 130.2 0 38 0; -48.7 38.1 133 0 51.2 0; -48.7 -2.6 135.9 0 -78.8 0; 29.5 -36.5 142.4 0 -7.4 0; -43.2 -28.2 144.6 0 -42 0; 5.2 -59.7 147.8 0 52.8 0; -28.2 -60.5 148.2 0 -42 0; 69.1 5.3 149.5 0 -84.2 0; -1.2 65.4 153.7 0 -13.7 0; 60.7 26.3 160.2 0 0 0; 23.5 -59.3 162.5 0 52.8 0; 56.4 -23.1 163.6 0 81.9 0; -32.5 53.9 165 0 29 0; -40.1 -46.2 170.7 0 -27 0; 24.1 44.3 176 0 -33.5 0; -21.5 -45.6 179.5 0 -27 0; 40 -43 182.8 0 82.7 0; 8.7 45 185.9 0 -33.5 0; 48.8 9 188.9 0 -33.5 0; -51.1 10.3 189.6 0 5.5 0; -39.9 40.6 192.3 0 5.5 0; 23.3 -24.6 192.5 0 106.5 0; -16.1 58 194 0 54.6 0; 49.2 -10.9 196.4 0 106.5 0; 36.8 21.2 198 0 -111.7 0; -52.5 -4.6 201.8 0 97 0; -36.9 -31.8 203.3 0 -27 0; -13.2 -42.6 207.7 0 -27 0; 31.9 31.6 211.9 0 -7.5 0; 19.2 -32.9 215.5 0 11 0; -8 35.6 224 0 -35.8 0; -39.2 21.6 226.1 0 54.9 0; 11.6 37.9 231.2 0 -7.5 0; 38.5 7.3 235.9 0 -111.3 0; 16.7 -29.3 249.9 0 13.2 0; -12.3 -33.5 254.5 0 0 0; 28.5 14.1 259.9 0 -32.1 0; 33.4 -2.9 260.9 0 88.5 0; -14.8 22.3 269 0 0 0; 19 -14.3 274 0 -38.3 0; -25.9 4.8 279.3 0 -91.2 0; 13.9 23 279.5 0 71.6 0; -11.8 -19.5 281.8 0 -17.9 0; -4.9 17.1 291.4 0 24.1 0') + +local function placeAround(tree, ent, r) + local dir = VectorRand() + dir.z = 0 + dir = dir:GetNormalized() * math.Rand(0.75, 1.1) * r + dir = dir + tree:GetPos() + local res = util.TraceEntity({ + start = dir + Vector(0, 0, 200), + endpos = dir - Vector(0, 0, 100), + filter = tree, + }, ent) + ent:SetPos(res.HitPos) + ent:SetAngles(Angle(0, math.random(0, 360), 0)) +end + +function ENT:QueueFirework(sid64, name, amount) + + self.fireworks[#self.fireworks + 1] = {name, amount} + + -- local ply = player.GetBySteamID64(sid64) + -- if IsValid(ply) then + -- ply:RandomXMasTreat(true) + -- end + +end + +function ENT:LaunchFireworks() + + if not self.fireworks or #self.fireworks == 0 then return end + + local names = {} + for i, v in ipairs(self.fireworks) do + names[v[1]] = true + + local fw = ents.Create('ent_dbg_fireworks') + placeAround(self, fw, 150) + fw.ShellsNum = math.min(math.ceil(v[2] / 50), 20) * 10 + fw.LaunchInterval = 1 + fw.LaunchVariance = { -2, 2 } + fw:Spawn() + self:DeleteOnRemove(fw) + fw:SetCollisionGroup(COLLISION_GROUP_IN_VEHICLE) + local phys = fw:GetPhysicsObject() + if IsValid(phys) then phys:EnableMotion(false) end + + fw:SetNetVar('dbgLook', { + name = v[1], + desc = tostring(v[2]) .. 'р', + time = 1, + }) + + timer.Simple(i + 5 * 60, function() + if IsValid(fw) then + fw:Use(Entity(0), Entity(0), USE_TOGGLE, 1) + end + end) + end + table.Empty(self.fireworks) + + names = table.GetKeys(names) + local msg = table.concat(names, ', ', 1, #names-1) + if #names > 1 then msg = msg .. ' и ' .. names[#names] + else msg = names[1] end + octolib.notify.sendAll('hint', 'Через 5 минут около елки будет салют в честь ' .. msg .. '! <3') + +end + +function ENT:AddBall(sid64, name, amount) + + local pos = poses[self.nextBallPosition] + if not pos then + return self:QueueFirework(sid64, name, amount) + end + + local ball = ents.Create('ent_dbg_xmas_ornament') + ball:SetParent(self, 0) + ball:SetLocalPos(pos[1]) + ball:SetLocalAngles(pos[2]) + self:DeleteOnRemove(ball) + ball.ownerSID64 = sid64 + ball:Spawn() + + local phys = ball:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + + ball:SetNetVar('dbgLook', { + name = '', + desc = ('%s от %s'):format(amount .. 'р', name), + time = 1, + }) + self.balls[#self.balls + 1] = ball + if #self.balls >= 42 then self:SetBodygroup(4, 0) end + +end + +function ENT:UpdateStar(name) + + local sid64, amount = + self.maxPayment.steamID64, + self.maxPayment.amount + + local star = self.star + if not IsValid(star) then + star = ents.Create('ent_dbg_xmas_ornament') + star:SetParent(self) + star:SetLocalPos(Vector(0, 0, 343)) + star.isStar = true + star.ownerSID64 = sid64 + star:Spawn() + self.star = star + end + + star:SetNetVar('dbgLook', { + name = name, + desc = tostring(amount) .. 'р', + time = 1, + }) + +end + +function ENT:Think() + + if CWI.IsNight() then + self:LaunchFireworks() + end + + octolib.db:PrepareQuery([[ + SELECT payments.id, amount, anonymous, steamID64, timeCompleted FROM ]] .. CFG.db.shop .. [[.octoshop_payments AS payments + JOIN ]] .. CFG.db.shop .. [[.octoshop_users AS users + ON payments.userID = users.id + WHERE payments.id > ? + AND amount >= ? + AND timeCompleted >= UNIX_TIMESTAMP(curdate() - INTERVAL((WEEKDAY(curdate()))) DAY) + ]], { self.lastPurchaseID or 0, self.ballCost or 100 }, function(_, _, dbResult) + if not istable(dbResult) then + print(dbResult) + return + end + + if #dbResult < 1 then return end + + -- update star + local maxPayment = octolib.array.reduce(dbResult, function(max, payment) return max.amount >= payment.amount and max or payment end) + if maxPayment.amount > self.starCost then + self.maxPayment = maxPayment + self.starCost = maxPayment.amount + if tobool(maxPayment.anonymous) then + self:UpdateStar(self.anonymousName) + else + octolib.getSteamData({ maxPayment.steamID64 }, function(steamResult) + self:UpdateStar(steamResult[1].name) + end) + end + end + + -- update balls + local sids = octolib.table.map(dbResult, function(payment) return payment.steamID64 end) + octolib.getSteamData(sids, function(steamResult) + for i, steamData in ipairs(steamResult) do + local payment = dbResult[i] + local name = tobool(payment.anonymous) and self.anonymousName or steamData.name + self:AddBall(payment.steamID64, name, payment.amount) + self.nextBallPosition = self.nextBallPosition + 1 + + self.lastPurchaseID = payment.id + end + end) + end) + + self:NextThink(CurTime() + self.updateInterval) + return true + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/shared.lua new file mode 100644 index 0000000..d78496c --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_xmastree/shared.lua @@ -0,0 +1,14 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.tree +ENT.Category = L.dobrograd +ENT.Author = "chelog & Wani4ka" +ENT.Contact = "chelog@octothorp.team | 4wk@wani4ka.ru" + +ENT.Spawnable = true +ENT.AdminSpawnable = true + +ENT.updateInterval = 15 +ENT.ballCost = 50 +ENT.starCost = 999 +ENT.anonymousName = 'Аноним' diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/cl_init.lua new file mode 100644 index 0000000..3a4e13d --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/cl_init.lua @@ -0,0 +1 @@ +include('shared.lua') diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/init.lua new file mode 100644 index 0000000..88b2b11 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/init.lua @@ -0,0 +1,86 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "shared.lua" + +include "shared.lua" + +function ENT:Initialize() + + local halfSize = Vector(10, 10, 10) + self:SetModel('models/props/de_prodigy/turret.mdl') + self:PhysicsInitBox(-halfSize, halfSize) + -- self:SetMoveType(MOVETYPE_VPHYSICS) + -- self:SetSolid(SOLID_VPHYSICS) + +end + +local enemies = { + npc_zombie = true, + npc_zombie_torso = true, + npc_fastzombie = true, + npc_headcrab_black = true, + npc_headcrab_fast = true, + npc_headcrab_poison = true, + npc_fastzombie_torso = true, + npc_headcrab = true, + npc_poisonzombie = true, +} + +hook.Add('PlayerSpawnedNPC', 'dbg-zombie', function(ply, npc) + + for i, v in ipairs(player.GetAll()) do + if v:GetNetVar('zombie') then npc:AddEntityRelationship(v, D_NU, 99) end + end + +end) + +hook.Add('dbg-zombie.changed', 'dbg-zombie', function(ply, st) + + for i, npc in ipairs(ents.FindByClass('npc_*')) do + if npc.AddEntityRelationship then + npc:AddEntityRelationship(ply, st and D_NU or D_HT, 99) + end + end + +end) + +function ENT:Think() + + for i, v in ipairs(ents.FindInSphere(self:GetPos(), 1200)) do + if (v:IsPlayer() and v:Alive() and v:HasWeapon('weapon_zombie')) or (enemies[v:GetClass()] and v:GetNPCState() ~= NPC_STATE_DEAD) then + local pos, mins, maxs = v:GetPos(), v:OBBMins() * 0.75, v:OBBMaxs() * 0.75 + pos.x = pos.x + math.random(mins.x, maxs.x) + pos.y = pos.y + math.random(mins.y, maxs.y) + pos.z = pos.z + math.random(mins.z, maxs.z) + self:Shoot(pos) + break + end + end + + self:NextThink(CurTime() + 1) + return true + +end + +function ENT:Shoot(pos) + + local dir = pos - self:GetPos() + local firepos = Vector(dir.x, dir.y, dir.z) + firepos.z = 0 + firepos:Normalize() + firepos:Mul(60) + + self:EmitSound('weapons/shotgun/shotgun_fire6.wav', 85, 100, 1) + self:FireBullets({ + Attacker = self, + IgnoreEntity = self, + Damage = 200, + Force = 50, + Num = 1, + Tracer = 1, + TracerName = 'AR2Tracer', + Src = self:GetPos() + firepos, + Dir = dir, + HullSize = 4, + }) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/shared.lua new file mode 100644 index 0000000..17af854 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_dbg_zombieturret/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = L.turret +ENT.Category = L.dobrograd +ENT.Author = "chelog" +ENT.Contact = "chelog@octothorp.team" + +ENT.Spawnable = true +ENT.AdminSpawnable = true diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/cl_init.lua new file mode 100644 index 0000000..2193757 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/cl_init.lua @@ -0,0 +1,142 @@ +include 'shared.lua' + +ENT.DrawShared = false +ENT.DrawPos = Vector(2.65, -35, 22.5) +ENT.DrawAng = Angle(0, 90, 90) +ENT.DPU = 5 + +InteractivePanels = InteractivePanels or {} + +function ENT:InitPanel(pnl) + + local but = vgui.Create('DButton', pnl) + but:SetSize(50, 30) + but:Center() + + but:SetText('Test') + +end + +function ENT:Initialize() + + local function createPanel() + vgui.MaxRange3D2D(100) + + local pnl = vgui.Create('DPanel') + pnl:SetSize(474, 474) + pnl:SetPos(0, 0) + pnl:ParentToHUD() + + self:InitPanel(pnl) + pnl:Paint3D2D() + return pnl + end + + if self.DrawShared then + local pnl = InteractivePanels[self:GetClass()] + if IsValid(pnl) then + self.panel = pnl + else + pnl = createPanel() + InteractivePanels[self:GetClass()] = pnl + self.panel = pnl + end + else + self.panel = createPanel() + end + +end + +function ENT:OnRemove() + + local pnl = self.panel + timer.Simple(0, function() + if self.DrawShared and #ents.FindByClass(self:GetClass()) > 0 then return end + if IsValid(pnl) then pnl:Remove() end + end) + +end + +function ENT:Draw() + + self:DrawModel() + + local dist = self:GetPos():DistToSqr(LocalPlayer():EyePos()) + if dist < 90000 then + local pos, ang = LocalToWorld(self.DrawPos, self.DrawAng, self:GetPos(), self:GetAngles()) + vgui.Start3D2D(pos, ang, 1 / self.DPU) + surface.SetAlphaMultiplier((90000 - dist) / 50000) + self.panel:Paint3D2D() + self:DrawScreen() + surface.SetAlphaMultiplier(1) + vgui.End3D2D() + end + +end + +function ENT:Think() + + if not IsValid(self.panel) then self:Initialize() end + if not self.panel:IsVisible() then self.panel:SetVisible(true) end + + local ply = LocalPlayer() + local tr = ply:GetEyeTrace() + if ply:KeyDown(IN_USE) and tr.Entity == self then + if not self.isDragging then + local dist = tr.HitPos:DistToSqr(ply:EyePos()) + if dist > 10000 then return end + + local pos, ang = LocalToWorld(self.DrawPos, self.DrawAng, self:GetPos(), self:GetAngles()) + local hitPos = util.IntersectRayWithPlane(ply:EyePos(), ply:GetAimVector(), pos, ang:Up()) + local relPos = WorldToLocal(hitPos, Angle(), pos, ang) + relPos.y = -relPos.y + + self:DoClick(relPos * self.DPU) + self.isDragging = true + end + else + self.isDragging = false + end + +end + +function ENT:DoClick(clickPos) + + self:MakeClickEffect(clickPos) + self:EmitSound('ui/buttonrollover.wav') + +end + +function ENT:DrawScreen() + + for k, v in pairs(self.clickEffects) do + local state = v.time + 1 - CurTime() + + local poly, radius = {}, math.pow(1-state, 0.4) * 4 * self.DPU + for i = 1, 24 do + poly[ i ] = { x = v.x + math.cos(math.pi / 12 * i) * radius, y = v.y + math.sin(math.pi / 12 * i) * radius } + end + + draw.NoTexture() + surface.SetDrawColor(255,255,255, math.pow(state, 2) * 255) + surface.DrawPoly(poly) + + if state <= 0 then table.remove(self.clickEffects, k) end + end + + -- local tr = LocalPlayer():GetEyeTrace() + -- local relPos = self:WorldToLocal(tr.HitPos) * self.DPU + -- draw.RoundedBox(4, relPos.x - 4, relPos.y + 4, 8, 8, color_white) + +end + +ENT.clickEffects = {} +function ENT:MakeClickEffect(clickPos) + + table.insert(self.clickEffects, { + time = CurTime(), + x = clickPos.x, + y = clickPos.y, + }) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/init.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/init.lua new file mode 100644 index 0000000..2b514bc --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/init.lua @@ -0,0 +1,15 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/hunter/plates/plate2x2.mdl') + self:SetMaterial('models/debug/debugwhite') + self:SetColor(Color(0,0,0)) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/shared.lua new file mode 100644 index 0000000..5d88917 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/ent_interactive_board/shared.lua @@ -0,0 +1,5 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'Interactive board' +ENT.Author = 'chelog' +ENT.Spawnable = false diff --git a/garrysmod/addons/gmod-ents/lua/entities/gmod_button.lua b/garrysmod/addons/gmod-ents/lua/entities/gmod_button.lua new file mode 100644 index 0000000..ea55f4d --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/gmod_button.lua @@ -0,0 +1,153 @@ + +AddCSLuaFile() +DEFINE_BASECLASS( "base_gmodentity" ) + +ENT.PrintName = "Button" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.Editable = true + +function ENT:SetupDataTables() + + self:NetworkVar( "Int", 0, "Key" ) + self:NetworkVar( "Bool", 0, "On" ) + self:NetworkVar( "Bool", 1, "IsToggle", { KeyName = "tg", Edit = { type = "Boolean", order = 1, title = "#tool.button.toggle" } } ) + self:NetworkVar( "String", 0, "Label", { KeyName = "lbl", Edit = { type = "Generic", order = 2, title = "#tool.button.text" } } ) + + if ( SERVER ) then + self:SetOn( false ) + self:SetIsToggle( false ) + end + +end + +function ENT:Initialize() + + if ( SERVER ) then + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( ONOFF_USE ) + + else + + self.PosePosition = 0 + + end + +end + +function ENT:GetOverlayText() + + local text = self:GetLabel() + + text = string.gsub( text, "\\", "" ) + text = string.sub( text, 0, 20 ) + + if ( text == "" ) then return "" end + + local txt = "\"" .. text .. "\"" + + if ( txt == "" ) then return "" end + if ( game.SinglePlayer() ) then return txt end + + return txt .. "\n(" .. self:GetPlayerName() .. ")" + +end + +function ENT:Use( activator, caller, type, value ) + + if ( !activator:IsPlayer() ) then return end -- Who the frig is pressing this shit!? + + if ( self:GetIsToggle() ) then + + if ( type == USE_ON ) then + self:Toggle( !self:GetOn(), activator ) + end + return + + end + + if ( IsValid( self.LastUser ) ) then return end -- Someone is already using this button + + -- + -- Switch off + -- + if ( self:GetOn() ) then + self:Toggle( false, activator ) + return + end + + -- + -- Switch on + -- + self:Toggle( true, activator ) + self:NextThink( CurTime() ) + self.LastUser = activator + +end + +function ENT:Think() + + -- Add a world tip if the player is looking at it + self.BaseClass.Think( self ) + + -- Update the animation + if ( CLIENT ) then + + self:UpdateLever() + + end + + -- + -- If the player looks away while holding down use it will stay on + -- Lets fix that.. + -- + if ( SERVER && self:GetOn() && !self:GetIsToggle() ) then + + if ( !IsValid( self.LastUser ) || !self.LastUser:KeyDown( IN_USE ) ) then + + self:Toggle( false, self.LastUser ) + self.LastUser = nil + + end + + self:NextThink( CurTime() ) + + end + +end + +-- +-- Makes the button trigger the keys +-- +function ENT:Toggle( bEnable, ply ) + + if ( bEnable ) then + + numpad.Activate( self:GetPlayerSteamID(), self:GetKey(), true ) + self:SetOn( true ) + + else + + numpad.Deactivate( self:GetPlayerSteamID(), self:GetKey(), true ) + self:SetOn( false ) + + end + +end + +-- +-- Update the lever animation +-- +function ENT:UpdateLever() + + local TargetPos = 0.0 + if ( self:GetOn() ) then TargetPos = 1.0 end + + self.PosePosition = math.Approach( self.PosePosition, TargetPos, FrameTime() * 5.0 ) + + self:SetPoseParameter( "switch", self.PosePosition ) + self:InvalidateBoneCache() + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/gmod_cameraprop.lua b/garrysmod/addons/gmod-ents/lua/entities/gmod_cameraprop.lua new file mode 100644 index 0000000..0a3f541 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/gmod_cameraprop.lua @@ -0,0 +1,219 @@ + +AddCSLuaFile() + +if ( CLIENT ) then + CreateConVar( "cl_drawcameras", "1", 0, "Should the cameras be visible?" ) +end + +ENT.Type = "anim" +ENT.PrintName = "Camera" +ENT.RenderGroup = RENDERGROUP_BOTH + +local CAMERA_MODEL = Model( "models/dav0r/camera.mdl" ) + +function ENT:SetupDataTables() + + self:NetworkVar( "Int", 0, "Key" ) + self:NetworkVar( "Bool", 0, "On" ) + self:NetworkVar( "Vector", 0, "vecTrack" ) + self:NetworkVar( "Entity", 0, "entTrack" ) + self:NetworkVar( "Entity", 1, "Player" ) + +end + +function ENT:Initialize() + + if ( SERVER ) then + + self:SetModel( CAMERA_MODEL ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:DrawShadow( false ) + + -- Don't collide with the player + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + local phys = self:GetPhysicsObject() + + if ( IsValid( phys ) ) then + phys:Sleep() + end + + end + +end + +function ENT:SetTracking( Ent, LPos ) + + if ( IsValid( Ent ) ) then + + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_BBOX ) + + else + + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + + end + + self:NextThink( CurTime() ) + + self:SetvecTrack( LPos ) + self:SetentTrack( Ent ) + +end + +function ENT:SetLocked( locked ) + + if ( locked == 1 ) then + + self.PhysgunDisabled = true + + self:SetMoveType( MOVETYPE_NONE ) + self:SetSolid( SOLID_BBOX ) + + self:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + else + + self.PhysgunDisabled = false + + end + + self.locked = locked + +end + +function ENT:OnTakeDamage( dmginfo ) + if ( self.locked ) then return end + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:OnRemove() + + if ( IsValid( self.UsingPlayer ) ) then + + self.UsingPlayer:SetViewEntity( self.UsingPlayer ) + + end + +end + +if ( SERVER ) then + + numpad.Register( "Camera_On", function ( sid, ent ) + + if ( !IsValid( ent ) ) then return false end + local pl = player.GetBySteamID(sid) + + pl:SetViewEntity( ent ) + pl.UsingCamera = ent + ent.UsingPlayer = pl + + end ) + + numpad.Register( "Camera_Toggle", function ( sid, ent, idx, buttoned ) + + -- The camera was deleted or something - return false to remove this entry + local pl = player.GetBySteamID(sid) + if ( !IsValid( ent ) ) then return false end + if ( !IsValid( pl ) ) then return false end + + -- Something else changed players view entity + if ( pl.UsingCamera && pl.UsingCamera == ent && pl:GetViewEntity() != ent ) then + pl.UsingCamera = nil + ent.UsingPlayer = nil + end + + if ( pl.UsingCamera && pl.UsingCamera == ent ) then + + pl:SetViewEntity( pl ) + pl.UsingCamera = nil + ent.UsingPlayer = nil + netstream.Start(pl, 'dbgView.hideHead', true) + netstream.Start(pl, 'dbg-mask.SetMaskVisible', pl, false) + + else + + pl:SetViewEntity( ent ) + pl.UsingCamera = ent + ent.UsingPlayer = pl + netstream.Start(pl, 'dbgView.hideHead', false) + netstream.Start(pl, 'dbg-mask.SetMaskVisible', pl, true) + + end + + end ) + + numpad.Register( "Camera_Off", function( sid, ent ) + + if ( !IsValid( ent ) ) then return false end + local pl = player.GetBySteamID(sid) + + if ( pl.UsingCamera && pl.UsingCamera == ent ) then + pl:SetViewEntity( pl ) + pl.UsingCamera = nil + ent.UsingPlayer = nil + end + + end ) + +end + +function ENT:Think() + + if CLIENT then + self:TrackEntity( self:GetentTrack(), self:GetvecTrack() ) + end + +end + +function ENT:TrackEntity( ent, lpos ) + + if ( !IsValid( ent ) ) then return end + + local WPos = ent:LocalToWorld( lpos ) + + if ( ent:IsPlayer() ) then + WPos = WPos + ent:GetViewOffset() * 0.85 + end + + local CamPos = self:GetPos() + local Ang = WPos - CamPos + + Ang = Ang:Angle() + self:SetAngles( Ang ) + +end + +function ENT:CanTool( ply, trace, mode ) + + if ( self:GetMoveType() == MOVETYPE_NONE ) then return false end + + return true + +end + +function ENT:Draw() + + if ( GetConVarNumber( "cl_drawcameras" ) == 0 ) then return end + + -- Don't draw the camera if we're taking pics + local wep = LocalPlayer():GetActiveWeapon() + if ( IsValid( wep ) ) then + if ( wep:GetClass() == "gmod_camera" ) then return end + end + + self:DrawModel() + +end + +hook.Add('EntityRemoved', 'dbg-camera', function(ent) + if IsValid(ent) and ent:GetClass() == 'gmod_cameraprop' and ent.UsingPlayer ~= nil then + local ply = ent.UsingPlayer + netstream.Start(ply, 'dbgView.hideHead', true) + netstream.Start(ply, 'dbg-mask.SetMaskVisible', ply, false) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-ents/lua/entities/gmod_light.lua b/garrysmod/addons/gmod-ents/lua/entities/gmod_light.lua new file mode 100644 index 0000000..9d3e915 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/gmod_light.lua @@ -0,0 +1,146 @@ + +AddCSLuaFile() +DEFINE_BASECLASS( "base_gmodentity" ) + +ENT.PrintName = "Light" +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.Editable = true + +local matLight = Material( "sprites/light_ignorez" ) +local MODEL = Model( "models/maxofs2d/light_tubular.mdl" ) + +-- +-- Set up our data table +-- +function ENT:SetupDataTables() + + self:NetworkVar("Bool", 0, "On") + self:NetworkVar("Vector", 0, "LightColor") + self:NetworkVar("Bool", 1, "Toggle") + self:NetworkVar("Float", 1, "LightSize") + self:NetworkVar("Float", 2, "Brightness") + + self:NetworkVar("Bool", 2, "LightWorld") + self:NetworkVar("Bool", 3, "LightModels") + +end + +function ENT:Initialize() + + if ( CLIENT ) then + + self.PixVis = util.GetPixelVisibleHandle() + + end + + if ( SERVER ) then --lights are rolling around even though the model isn't round!! + + self:SetModel( MODEL ) + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + + if ( IsValid( phys ) ) then + phys:Wake() + end + + end + +end + +function ENT:Draw() + + BaseClass.Draw( self ) + +end + +function ENT:Think() + + self.BaseClass.Think( self ) + + if ( CLIENT ) then + + if ( !self:GetOn() ) then return end + local pos = self:GetPos() + if pos:DistToSqr(EyePos()) > 1500000 then return end + + local dlight = DynamicLight( self:EntIndex() ) + + if ( dlight ) then + + local c = self:GetLightColor():ToColor() + + local size = self:GetLightSize() + local brght = self:GetBrightness() + -- Clamp for multiplayer + if ( !game.SinglePlayer() ) then + size = math.Clamp( size, 0, 350 ) + brght = math.Clamp( brght, 0, 2 ) + end + + dlight.Pos = pos + dlight.r = c.r + dlight.g = c.g + dlight.b = c.b + dlight.Brightness = brght + dlight.Decay = size * 5 + dlight.Size = size + dlight.DieTime = CurTime() + 1 + + -- dlight.dir = Vector(0,0,-1) + -- dlight.innerangle = 60 + -- dlight.outerangle = 180 + dlight.minlight = 0.3 + + dlight.noworld = false --self:GetLightWorld() + dlight.nomodel = false --self:GetLightModels() + + end + + end + +end + +function ENT:DrawTranslucent() + + BaseClass.DrawTranslucent( self, true ) + + local up = self:GetAngles():Up() + + local LightPos = self:GetPos() + render.SetMaterial( matLight ) + + local ViewNormal = self:GetPos() - EyePos() + local Distance = ViewNormal:Length() + ViewNormal:Normalize() + + local Visibile = util.PixelVisible( LightPos, 4, self.PixVis ) + + if ( !Visibile || Visibile < 0.1 ) then return end + + if ( !self:GetOn() ) then return end + + local c = self:GetLightColor():ToColor() + local Alpha = 255 * Visibile + + render.DrawSprite( LightPos - up * 2, 8, 8, Color( 255, 255, 255, Alpha ), Visibile ) + render.DrawSprite( LightPos - up * 4, 8, 8, Color( 255, 255, 255, Alpha ), Visibile ) + render.DrawSprite( LightPos - up * 6, 8, 8, Color( 255, 255, 255, Alpha ), Visibile ) + render.DrawSprite( LightPos - up * 5, 64, 64, Color( c.r, c.g, c.b, 64 ), Visibile ) + +end + +function ENT:GetOverlayText() + return '' +end + +function ENT:OnTakeDamage( dmginfo ) + self:TakePhysicsDamage( dmginfo ) +end + +function ENT:Toggle() + self:SetOn( !self:GetOn() ) +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/letter/cl_init.lua b/garrysmod/addons/gmod-ents/lua/entities/letter/cl_init.lua new file mode 100644 index 0000000..c661eb8 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/letter/cl_init.lua @@ -0,0 +1,160 @@ +include('shared.lua') + +surface.CreateFont('dbg-write', { + font = 'Cambria', + extended = true, + size = 20, + weight = 500, +}) + +local frame, editFrame + +function ENT:Draw() + self:DrawModel() +end + +netstream.Hook('dbg-write', function(letter, action, data) + + if action == 'use' then + gui.EnableScreenClicker(true) + + local menu = DermaMenu() + menu:AddOption(L.read, function() + netstream.Start('dbg-write', letter, 'read', { false }) + end):SetImage(octolib.icons.silk16('report_magnify')) + menu:AddOption(L.change, function() + netstream.Start('dbg-write', letter, 'read', { true }) + end):SetImage(octolib.icons.silk16('report_edit')) + + local signed = letter:GetNetVar('Signed') == LocalPlayer() + menu:AddOption(signed and L.remove_sign or L.add_sign, function() + netstream.Start('dbg-write', letter, 'sign', { not signed }) + end):SetImage(octolib.icons.silk16('digital_signature_pen')) + + if data[1] then + menu:AddOption(L.freeze, function() + netstream.Start('dbg-write', letter, 'freeze', { false }) + end):SetImage(octolib.icons.silk16('arrow_down')) + else + menu:AddOption(L.unfreeze, function() + netstream.Start('dbg-write', letter, 'freeze', { true }) + end):SetImage(octolib.icons.silk16('arrow_up')) + end + + if not IsValid(letter:GetNetVar('IsFor')) then + local plys = player.GetAll() + table.sort(plys, function(a, b) return a:Name() < b:Name() end) + + if #plys > 1 then + local sm, pmo = menu:AddSubMenu(L.write_give) + for _, ply in ipairs(plys) do + if ply ~= LocalPlayer() then + sm:AddOption(ply:Name(), function() + netstream.Start('dbg-write', letter, 'give', { ply }) + end) + end + end + pmo:SetImage(octolib.icons.silk16('report_go')) + end + else + menu:AddOption(L.cancel_give, function() + netstream.Start('dbg-write', letter, 'give', {}) + end):SetImage(octolib.icons.silk16('report_delete')) + end + + menu:AddOption(L.write_destroy, function() + netstream.Start('dbg-write', letter, 'destroy') + end):SetImage(octolib.icons.silk16('bin')) + + menu:Center() + menu:Open() + + gui.EnableScreenClicker(false) + elseif action == 'read' then + local letterMsg = data[1] or '' + if data[2] then + if IsValid(editFrame) then editFrame:Remove() end + + editFrame = vgui.Create 'DFrame' + editFrame:SetSize(600,600) + editFrame:SetTitle(L.change_letter) + editFrame:Center() + editFrame:MakePopup() + + local b = editFrame:Add 'DButton' + b:SetText(L.save) + b:Dock(BOTTOM) + b:SetTall(30) + + local e = editFrame:Add 'DTextEntry' + e:Dock(FILL) + e:SetMultiline(true) + e:SetContentAlignment(7) + e:SetValue(letterMsg) + + function b:DoClick() + editFrame:Remove() + netstream.Start('dbg-write', letter, 'edit', { e:GetValue() }) + end + else + if IsValid(frame) then frame:Remove() end + + frame = vgui.Create('DPanel') + frame:SetPaintBackground(false) + frame.letter = letter + frame:Dock(FILL) + frame:MakePopup() + + local killBut = vgui.Create('DButton', frame) + killBut:SetText(L.close) + killBut:SetPos((ScrW() - 256) / 2, 15) + killBut:SetSize(256, 64) + function killBut:DoClick() + frame:Remove() + end + + local scrPan = frame:Add('DScrollPanel') + scrPan:SetPos(ScrW() * .2, ScrH() * 0.3) + scrPan:SetSize(ScrW() * .6, ScrH() * 0.84) + + local textWrap = scrPan:Add('DPanel') + textWrap:Dock(TOP) + + local ltr = markup.Parse('' .. letterMsg .. '
', ScrW() * .6 - 100) + local textH = ltr:GetHeight() + textWrap:SetTall(math.max(ScrH() * 0.84, textH + 200)) + + function textWrap:Paint(w, h) + draw.RoundedBox(8, 0, 0, w, h, color_white) + local x, y = self:GetPos() + ltr:Draw(x + 50, y + 50, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + local signed = letter:GetNetVar('Signed') + if IsValid(signed) then + local name + local customJob = signed:GetNetVar('customJob') + if customJob then + name = customJob[1] + else + local job = DarkRP.getJobByCommand(signed:GetNetVar('dbg-police.job', '')) or signed:getJobTable() + name = job.name + end + draw.DrawNonParsedText( + L.write_sign .. name .. ', ' .. signed:Name(), + 'dbg-write', + w * .5 + 50, + textH + 100, + Color(0, 0, 0), + 0 + ) + end + end + + scrPan:SetAlpha(0) + scrPan:AlphaTo(255, 0.5, 0) + scrPan:MoveBy(0, -ScrH() * .15, 0.5, 0) + + end + end + +end) diff --git a/garrysmod/addons/gmod-ents/lua/entities/letter/commands.lua b/garrysmod/addons/gmod-ents/lua/entities/letter/commands.lua new file mode 100644 index 0000000..d0b6fc1 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/letter/commands.lua @@ -0,0 +1,12 @@ +local meta = FindMetaTable 'Player' +function meta:GetLetterCount() + + local playerHas = 0 + for _,v in ipairs(ents.FindByClass('letter')) do + if v:GetNetVar('Owner') == self then + playerHas = playerHas + 1 + end + end + return playerHas + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/letter/init.lua b/garrysmod/addons/gmod-ents/lua/entities/letter/init.lua new file mode 100644 index 0000000..32f04ac --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/letter/init.lua @@ -0,0 +1,127 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +include 'shared.lua' +include 'commands.lua' + +function ENT:Initialize() + + self:SetModel('models/props_lab/clipboard.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS) + local phys = self:GetPhysicsObject() + + phys:Wake() + hook.Add('PlayerDisconnected', self, self.onPlayerDisconnected) + +end + +function ENT:OnTakeDamage(dmg) + + self:TakePhysicsDamage(dmg) + if dmg:IsDamageType(DMG_BULLET) or dmg:IsDamageType(DMG_BLAST) then + self:Destroy() + end + +end + +function ENT:Destroy() + + local d = ents.Create('prop_physics') + d:SetModel('models/props/cs_office/trash_can_p1.mdl') + d:SetPos(self:GetPos()) + d:SetAngles(self:GetAngles()) + d:Spawn() + d:SetCollisionGroup(COLLISION_GROUP_WEAPON) + d:Fire('Kill', '', 15) + + self:Remove() + +end + +function ENT:Use(ply) + + if self:GetNetVar('Owner') == ply then + netstream.Start(ply, 'dbg-write', self, 'use', { self:GetPhysicsObject():IsMotionEnabled() }) + else + if self:GetNetVar('IsFor') == ply then + if ply:GetLetterCount() >= 3 then + ply:Notify('warning', L.too_much_documents) + return + end + + self:SetNetVar('Owner', ply) + self:SetNetVar('IsFor', nil) + ply:Notify(L.your_document) + end + + netstream.Start(ply, 'dbg-write', self, 'read', { self.text, false }) + end + +end + +function ENT:SignLetter(ply) + + self:SetNetVar('Signed', ply) + +end + +function ENT:onPlayerDisconnected(ply) + + if self:GetNetVar('Owner') == ply then + self:Destroy() + end + +end + +netstream.Hook('dbg-write', function(ply, ent, action, data) + + if not IsValid(ent) or ent:GetClass() ~= 'letter' + or ent:GetPos():DistToSqr(ply:GetPos()) > 900000 then return end + + -- public access + if action == 'read' then + netstream.Start(ply, 'dbg-write', ent, 'read', { ent.text, data[1] }) + end + + -- for owner + if ent:GetNetVar('Owner') ~= ply then return end + if action == 'destroy' then + netstream.StartPVS(ent:GetPos(), 'dbg-write', ent, 'close') + ent:Destroy() + elseif action == 'edit' then + netstream.StartPVS(ent:GetPos(), 'dbg-write', ent, 'close') + ent.text = data[1] + ent:SetNetVar('Signed', nil) + elseif action == 'sign' then + ent:SetNetVar('Signed', data[1] and ply or nil) + elseif action == 'give' then + local isFor = data[1] + if not IsValid(isFor) or not isFor:IsPlayer() then return end + + ent:SetNetVar('IsFor', isFor) + ply:Notify(isFor and (L.giving_document .. isFor:Name()) or L.giving_document_cancel) + elseif action == 'freeze' then + if data[1] then + local phys = ent:GetPhysicsObject() + phys:EnableMotion(true) + phys:Wake() + else + local pos = ent:GetPos() + timer.Simple(1, function() + if not IsValid(ent) then return end + if ent:GetPos():DistToSqr(pos) < 1 then + ent:GetPhysicsObject():EnableMotion(false) + ply:Notify(L.document_freeze) + else + ply:Notify(L.dont_move_document) + end + end) + end + end + +end) diff --git a/garrysmod/addons/gmod-ents/lua/entities/letter/shared.lua b/garrysmod/addons/gmod-ents/lua/entities/letter/shared.lua new file mode 100644 index 0000000..01afeab --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/letter/shared.lua @@ -0,0 +1,9 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "letter" +ENT.Author = "Pcwizdan" +ENT.Spawnable = false + +function ENT:SetupDataTables() + self:NetworkVar("Entity",1,"owning_ent") +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/prop_effect.lua b/garrysmod/addons/gmod-ents/lua/entities/prop_effect.lua new file mode 100644 index 0000000..0ceff1d --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/prop_effect.lua @@ -0,0 +1,100 @@ + +AddCSLuaFile() + +if ( CLIENT ) then + CreateConVar( "cl_draweffectrings", "1", 0, "Should the effect green rings be visible?" ) +end + +ENT.Type = "anim" + +ENT.Spawnable = false + +function ENT:Initialize() + + local Radius = 6 + local min = Vector( 1, 1, 1 ) * Radius * -0.5 + local max = Vector( 1, 1, 1 ) * Radius * 0.5 + + if ( SERVER ) then + self:PhysicsInitBox( min, max ) + + local phys = self:GetPhysicsObject() + if ( IsValid( phys ) ) then + phys:Wake() + phys:EnableGravity( false ) + phys:EnableDrag( false ) + end + + -- self:DrawShadow( false ) + -- self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + else + self.GripMaterial = Material( "sprites/grip" ) + end + + -- Set collision bounds exactly + self:SetCollisionBounds( min, max ) + +end + +function ENT:Draw() + + self:DrawModel() + + if ( GetConVarNumber( "cl_draweffectrings" ) == 0 ) then return end + + -- don't draw rings over other's props + local owner = self:CPPIGetOwner() + if owner ~= LocalPlayer() then return end + + -- Don't draw the grip if there's no chance of us picking it up + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + if ( !IsValid( wep ) ) then return end + + local weapon_name = wep:GetClass() + + if ( weapon_name != "weapon_physgun" && weapon_name != "weapon_physcannon" && weapon_name != "gmod_tool" ) then + return + end + + render.SetMaterial( self.GripMaterial ) + render.DrawSprite( self:GetPos(), 16, 16, color_white ) + +end + +function ENT:PhysicsUpdate( physobj ) + + if ( CLIENT ) then return end + + -- Don't do anything if the player isn't holding us + if ( !self:IsPlayerHolding() && !self:IsConstrained() ) then + + physobj:SetVelocity( Vector( 0, 0, 0 ) ) + physobj:Sleep() + + end + +end + +function ENT:OnEntityCopyTableFinish( tab ) + + -- We need to store the model of the attached entity + -- Not the one we have here. + tab.Model = self:GetModel() + + -- Store the attached entity's table so we can restore it after being pasted + tab.AttachedEntityInfo = table.Copy( tab ) + tab.AttachedEntityInfo.Pos = nil -- Don't even save angles and position, we are a parented entity + tab.AttachedEntityInfo.Angle = nil + + -- Do NOT store the attached entity itself in our table! + -- Otherwise, if we copy-paste the prop with the duplicator, its AttachedEntity value will point towards the original prop's attached entity instead, and that'll break stuff + tab.AttachedEntity = nil + +end + +function ENT:PostEntityPaste( ply ) + + self.AttachedEntityInfo = nil + +end diff --git a/garrysmod/addons/gmod-ents/lua/entities/sent_soccerball.lua b/garrysmod/addons/gmod-ents/lua/entities/sent_soccerball.lua new file mode 100644 index 0000000..ab96db6 --- /dev/null +++ b/garrysmod/addons/gmod-ents/lua/entities/sent_soccerball.lua @@ -0,0 +1,246 @@ +AddCSLuaFile() + +ENT.Type = 'anim' +ENT.Base = 'base_anim' +ENT.PrintName = L.soccerball +ENT.Category = L.dobrograd +ENT.Author = 'Jvs' +ENT.Spawnable = true +ENT.AdminOnly = false + +if CLIENT then + ENT.HitMaterial = Material(util.DecalMaterial('impact.concrete')) + ENT.RenderGroup = RENDERGROUP_BOTH +else + ENT.CanPickupSoccerball = CreateConVar( + 'sv_pickupsoccerball' , + '1', + { + FCVAR_SERVER_CAN_EXECUTE, + FCVAR_ARCHIVE + }, + 'When true, it allows anyone to pickup the soccerball' + ) +end + +function ENT:SpawnFunction(ply , tr , classname) + if not tr.Hit then + return + end + + local spawnpos = tr.HitPos + tr.HitNormal * 25 + + local ent = ents.Create(classname) + ent:SetPos(spawnpos) + ent:Spawn() + return ent +end + +function ENT:SetupDataTables() + self:NetworkVar('Float' , 0 , 'LastImpact') --I made this into a dtvar because at some point I'll add some clientside animations for when the ball bounces + self:NetworkVar('Float' , 1 , 'PressureExpireStart') + self:NetworkVar('Float' , 2 , 'PressureExpireEnd') +end + +function ENT:Initialize() + if SERVER then + self:SetMaxHealth(50) + self:SetHealth(50) + self:SetLagCompensated(true) --players can shoot at us even with their shitty ping! + self:SetUseType(SIMPLE_USE) --don't let players spam +use on us, that's rude + self:SetModel('models/props_phx/misc/soccerball.mdl') + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self:ResetPressure() + self:SetTrigger(true) --allow us to use touch,starttouch and whatever even if we can't collide with the player + + local physobj = self:GetPhysicsObject() + + if IsValid(physobj) then + physobj:AddGameFlag(FVPHYSICS_NO_IMPACT_DMG) + physobj:AddGameFlag(FVPHYSICS_NO_NPC_IMPACT_DMG) + physobj:SetBuoyancyRatio(0.5) + physobj:SetDamping(0.25 , 1) + physobj:Wake() + end + + self:StartMotionController() + end + + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) +end + +--[[ + I wanted to have this complicated system where the soccerball would lose pressure every frame but first off, + that's annoying to code for little to no benefit, and second, I can fake it off by just using a timed variable + that gets decreased everytime +]] + +function ENT:GetPressure() + local pressure = math.Clamp(math.TimeFraction(self:GetPressureExpireEnd() , self:GetPressureExpireStart() , CurTime()) , 0 , 1) + + return pressure +end + +function ENT:ResetPressure() + self:SetPressureExpireStart(0) + self:SetPressureExpireEnd(0) +end + +function ENT:IsLosingPressure() + return self:GetPressureExpireStart() ~= 0 and self:GetPressureExpireEnd() ~= 0 +end + +function ENT:OnRemove() + if CLIENT then + if self.PressureLeakSound then + self.PressureLeakSound:Stop() + self.PressureLeakSound = nil + end + end +end + +if SERVER then + function ENT:PhysicsSimulate(physobj , delta) + return SIM_NOTHING + end + + function ENT:OnTakeDamage(dmginfo) + if self:IsEFlagSet(EFL_KILLME) then + return + end + + self:TakePhysicsDamage(dmginfo) + + local dmg = dmginfo:GetDamage() + + local healthtoset = math.Clamp(self:Health() - dmg , 0 , self:GetMaxHealth()) + + self:SetHealth(healthtoset) + + local isoverkill = (dmg >= (self:GetMaxHealth() / 2)) or (healthtoset <= 0) + + + --either overkill + if (isoverkill and not dmginfo:IsDamageType(DMG_BULLET)) or isoverkill then + + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetScale(self:GetPressure()) + util.Effect('soccerball_explode', effectdata) + + self:Remove() + end + + --we haven't been killed yet, start to lose pressure if the damage was coming from a bullet + if not self:IsEFlagSet(EFL_KILLME) and dmginfo:IsDamageType(DMG_BULLET) then + if not self:IsLosingPressure() then + self:SetPressureExpireStart(CurTime()) + self:SetPressureExpireEnd(CurTime() + 5) + else + --decrease it a bit for every shot + self:SetPressureExpireEnd(self:GetPressureExpireEnd() - 0.5) + end + end + end + + function ENT:Use(ply) + if self:GetVelocity():Length2DSqr() > 100 or not ply.inv or not ply.inv.conts._hand then + ply:Notify('warning', L.take_soccerball) + return + end + + local i = ply.inv.conts._hand:AddItem('soccer') + if i and i ~= 0 then self:Remove() end + end + + function ENT:PhysicsCollide(data, physobj) + if self:GetLastImpact() < CurTime() and data.DeltaTime > 0.2 and data.OurOldVelocity:Length() > 100 then + self:EmitSound('Rubber.ImpactHard') + self:SetLastImpact(CurTime() + 0.1) + end + end + + function ENT:PhysicsUpdate(physobj) + physobj:SetMass(10) + physobj:SetBuoyancyRatio(0.5 * self:GetPressure()) + physobj:SetDamping(0.25 , 1) + end + + --touch is not called when the physics object hits an entity, but rather when the collision bounds do, which + --is done with traces on the game side + function ENT:StartTouch(ent) + if not SERVER or not IsValid(ent) or self:IsPlayerHolding() then + return + end + + --ignore players that might be noclipping or in a vehicle + if ent:IsPlayer() and ent:GetMoveType() ~= MOVETYPE_WALK then + return + end + + local tr = self:GetTouchTrace() + local kickmultiplier = 1.5 + 1 * self:GetPressure() + local massmultiplier = 15 + local direction = tr.Normal + + local normal = (ent:WorldSpaceCenter() - self:GetPos()):GetNormal() * -1 + local physobj = self:GetPhysicsObject() + local ourvel = self:GetVelocity() + local theirvel = ent:GetVelocity() + + + if IsValid(physobj) and (ent:IsPlayer() or ent:IsNPC()) then + + local aimvec = ent:EyeAngles() + aimvec.p = 0 + aimvec = aimvec:Forward() + aimvec.z = 0 + + if aimvec:Dot(theirvel:GetNormal()) < 0 then + theirvel = vector_origin + theirvel = normal * physobj:GetMass() * massmultiplier + end + --kick the ball! + if theirvel ~= vector_origin then + self:EmitSound('Rubber.BulletImpact') + physobj:SetVelocityInstantaneous(theirvel * kickmultiplier + Vector(0, 0 , physobj:GetMass() * massmultiplier) ) + self:SetLastImpact(CurTime() + 0.1) + else --bounce the ball back + self:EmitSound('Rubber.ImpactHard') + physobj:SetVelocityInstantaneous(-1 * normal * ourvel:Dot(normal)) + end + self:SetLastImpact(CurTime() + 0.1) --we just kicked the ball, suppress the bounce sound for a little while + end + end + +else + + function ENT:Think() + self:HandleSound() + end + + function ENT:HandleSound() + if not self.PressureLeakSound then + self.PressureLeakSound = CreateSound(self , 'PhysicsCannister.ThrusterLoop') + end + + if self:IsLosingPressure() and self:GetPressure() ~= 0 then + self.PressureLeakSound:Play() + self.PressureLeakSound:ChangeVolume(self:GetPressure()) + else + self.PressureLeakSound:Stop() + end + end + + function ENT:Draw(flags) + self:DrawModel() + end + + function ENT:DrawTranslucent() + end + + +end diff --git a/garrysmod/addons/gmod-sweps/lua/autorun/client/octocamera.lua b/garrysmod/addons/gmod-sweps/lua/autorun/client/octocamera.lua new file mode 100644 index 0000000..a4ba547 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/autorun/client/octocamera.lua @@ -0,0 +1,17 @@ +CreateClientConVar('octocam_speed', '1', true, true) +CreateClientConVar('octocam_lerp_pos', '0.5', true, false) +CreateClientConVar('octocam_lerp_ang', '0.5', true, false) +CreateClientConVar('octocam_lerp_fov', '0.5', true, false) + +local function sbMenu(p) + + p:NumSlider('Скорость движения', 'octocam_speed', 0.05, 3, 2) + p:NumSlider('Сглаживание позиции', 'octocam_lerp_pos', 0.05, 2, 2) + p:NumSlider('Сглаживание угла', 'octocam_lerp_ang', 0.05, 2, 2) + p:NumSlider('Сглаживание FOV', 'octocam_lerp_fov', 0.05, 2, 2) + +end + +hook.Add('PopulateToolMenu', 'octocam', function() + spawnmenu.AddToolMenuOption('Options', 'Player', 'Camera', 'OctoCamera', '', '', sbMenu) +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/autorun/flashlight_init.lua b/garrysmod/addons/gmod-sweps/lua/autorun/flashlight_init.lua new file mode 100644 index 0000000..75083cc --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/autorun/flashlight_init.lua @@ -0,0 +1,47 @@ +-- Since people have had trouble with stuck flashlight spots +-- and I've failed to eliminate the problem so far, I'm adding +-- this as a temporary workaround. +cleanup.Register('flspot') + +local disabledWeps = octolib.array.toKeys {'gmod_tool', 'weapon_physgun', 'dbg_admingun', 'gmod_camera'} + +local function FlashlightBind(ply, ucmd) + local wep = ply:GetActiveWeapon() + if not IsValid(wep) then return ucmd:SetImpulse(0) end + if ucmd:GetImpulse() == 100 then + if disabledWeps[wep:GetClass()] or wep.HasFlashlight then + if ply:HasWeapon('weapon_flashlight') then return end + else + if wep:GetClass() == 'weapon_flashlight' then + RunConsoleCommand('lastinv') + else + RunConsoleCommand('use', 'weapon_flashlight') + end + end + ucmd:SetImpulse(0) + end +end +hook.Add('StartCommand', 'SWEP FlashlightBind', FlashlightBind) + +hook.Add('PlayerSwitchWeapon', 'dbg-flashlight', function(ply, old, new) + if SERVER and ply:FlashlightIsOn() and not (disabledWeps[new:GetClass()] or new.HasFlashlight) then ply:Flashlight(false) end +end) + +if (CLIENT) then + local function LightOptions(CPanel) + -- HEADER + CPanel:AddControl('Header', { Description = 'Server Settings' } ) + CPanel:AddControl('Header', { Description = 'Client Settings' } ) + + CPanel:AddControl('Checkbox', { Label = 'Refresh Light on Reload', Command = 'cl_flashlight_allow_refresh' }) + end + + hook.Add('PopulateToolMenu', 'AddFLMenu', function() + spawnmenu.AddToolMenuOption('Options', 'Flashlight', 'FlashlightSettings', 'Settings', '', '', LightOptions) + end) + + language.Add('cleanup_flspot', 'Flashlight Spots') + language.Add('cleaned_flspot', 'Removed Flashlight Spots') + + RunConsoleCommand('r_shadows', 1) +end diff --git a/garrysmod/addons/gmod-sweps/lua/effects/fire_hose_effect.lua b/garrysmod/addons/gmod-sweps/lua/effects/fire_hose_effect.lua new file mode 100644 index 0000000..1547b22 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/effects/fire_hose_effect.lua @@ -0,0 +1,67 @@ + +function EFFECT:Init(data) + + self.Player = data:GetEntity() + self.Origin = data:GetOrigin() + self.Attachment = data:GetAttachment() + self.Forward = data:GetNormal() + self.Scale = data:GetScale() + + if not IsValid(self.Player) or not IsValid(self.Player:GetActiveWeapon()) then return end + + self.Angle = self.Forward:Angle() + self.Position = self:GetTracerShootPos(self.Origin, self.Player:GetActiveWeapon(), self.Attachment) + + if self.Position == self.Origin then + local att = self.Player:GetAttachment(self.Player:LookupAttachment('anim_attachment_RH')) + if att then self.Position = att.Pos + att.Ang:Forward() * 2 end + end + + local teh_effect = ParticleEmitter(self.Player:GetPos(), true) + if not teh_effect then return end + + for i = 1, 10 * self.Scale do + local particle = teh_effect:Add('effects/splash1', self.Position) + if particle then + local Spread = 0.3 + particle:SetVelocity((Vector(math.sin(math.Rand(0, 600)) * math.Rand(-Spread, Spread), math.cos(math.Rand( 0, 600 )) * math.Rand(-Spread, Spread), math.sin(math.random()) * math.Rand(-Spread, Spread)) + self.Forward) * 750) + + local ang = self.Angle + if i / 2 == math.floor(i / 2) then ang = (self.Forward * -1):Angle() end + particle:SetAngles(ang) + particle:SetDieTime(0.25) + particle:SetColor(255, 255, 255) + particle:SetStartAlpha(255) + particle:SetEndAlpha(0) + particle:SetStartSize(8) + particle:SetEndSize(0) + particle:SetCollide(1) + particle:SetCollideCallback(function(particleC, HitPos, normal) + particleC:SetAngleVelocity(Angle(0, 0, 0)) + particleC:SetVelocity(Vector(0, 0, 0)) + particleC:SetPos(HitPos + normal * 0.1) + particleC:SetGravity(Vector(0, 0, 0)) + + local angles = normal:Angle() + angles:RotateAroundAxis(normal, particleC:GetAngles().y) + particleC:SetAngles(angles) + + particleC:SetLifeTime(0) + particleC:SetDieTime(10) + particleC:SetStartSize(8) + particleC:SetEndSize(0) + particleC:SetStartAlpha(128) + particleC:SetEndAlpha(0) + end) + end + end + + teh_effect:Finish() +end + +function EFFECT:Think() + return false +end + +function EFFECT:Render() +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/entities/weapon_hl2axe_throw/shared.lua b/garrysmod/addons/gmod-sweps/lua/entities/weapon_hl2axe_throw/shared.lua new file mode 100644 index 0000000..e52181b --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/entities/weapon_hl2axe_throw/shared.lua @@ -0,0 +1,186 @@ +ENT.Type = "anim" +ENT.PrintName = "Axe" +ENT.Author = "Worshipper, well, sort of. most of it is his anyway" +ENT.Contact = "Josephcadieux@hotmail.com" +ENT.Purpose = "" +ENT.Instructions = "" + +if SERVER then + +AddCSLuaFile("shared.lua") + +/*--------------------------------------------------------- + Name: ENT:Initialize() +---------------------------------------------------------*/ +function ENT:Initialize() + + self:SetModel("models/weapons/HL2meleepack/w_axe.mdl") + self:PhysicsInit(SOLID_VPHYSICS) + self.Entity:SetMoveType(MOVETYPE_VPHYSICS) + self.Entity:SetSolid(SOLID_VPHYSICS) + local phys = self.Entity:GetPhysicsObject() + self.NextThink = CurTime() + 1 + + if (phys:IsValid()) then + phys:Wake() + phys:SetMass(10) + end + + util.PrecacheSound("physics/metal/metal_grenade_impact_hard3.wav") + util.PrecacheSound("physics/metal/metal_grenade_impact_hard2.wav") + util.PrecacheSound("physics/metal/metal_grenade_impact_hard1.wav") + util.PrecacheSound("physics/flesh/flesh_impact_bullet1.wav") + util.PrecacheSound("physics/flesh/flesh_impact_bullet2.wav") + util.PrecacheSound("physics/flesh/flesh_impact_bullet3.wav") + + self.Hit = { + Sound("physics/metal/metal_grenade_impact_hard1.wav"), + Sound("physics/metal/metal_grenade_impact_hard2.wav"), + Sound("physics/metal/metal_grenade_impact_hard3.wav")}; + + self.FleshHit = { + Sound("physics/flesh/flesh_impact_bullet1.wav"), + Sound("physics/flesh/flesh_impact_bullet2.wav"), + Sound("physics/flesh/flesh_impact_bullet3.wav")} + + self:GetPhysicsObject():SetMass(2) + + self.Entity:SetUseType(SIMPLE_USE) +end + +/*--------------------------------------------------------- + Name: ENT:Disable() +---------------------------------------------------------*/ +function ENT:Disable() + + self.PhysicsCollide = function() end + + timer.Simple(0, function() + self.Entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self.Entity:SetOwner(NULL) + end) + +end + +/*--------------------------------------------------------- + Name: ENT:PhysicsCollided() +---------------------------------------------------------*/ +function ENT:PhysicsCollide(data, phys) + + local Ent = data.HitEntity + local stuck = math.random(4) ~= 1 + -- if !IsValid( Ent ) then return end + + local health = Ent:Health() + if health ~= 0 and health < 80 then stuck = false end + Ent:TakeDamage(80, self:GetOwner(), self.Entity) + + if (Ent:IsPlayer() or Ent:IsNPC() or Ent:GetClass() == "prop_ragdoll") then + + -- if not(Ent:IsPlayer() or Ent:IsNPC() or Ent:GetClass() == "prop_ragdoll") then + -- util.Decal("ManhackCut", data.HitPos + data.HitNormal, data.HitPos - data.HitNormal) + -- self:EmitSound(self.Hit[math.random(1, #self.Hit)]) + -- self:Disable() + -- end + + -- if (Ent:IsPlayer() or Ent:IsNPC() or Ent:GetClass() == "prop_ragdoll") then + local effectdata = EffectData() + effectdata:SetStart(data.HitPos) + effectdata:SetOrigin(data.HitPos) + effectdata:SetScale(1) + util.Effect("BloodImpact", effectdata) + + self:EmitSound(self.FleshHit[math.random(1,#self.FleshHit)]) + self:Disable() + self.Entity:GetPhysicsObject():SetVelocity(data.OurOldVelocity / 4) + + -- end + + else + + -- util.Decal("ManhackCut", data.HitPos + data.HitNormal, data.HitPos - data.HitNormal) + + local yaw = data.OurOldVelocity:Angle().y + self:EmitSound( stuck and "Canister.ImpactHard" or table.Random(self.Hit) ) + if stuck then + self:SetPos(data.HitPos - data.HitNormal * 12) + self:SetAngles(data.HitNormal:Angle() + Angle(135, math.abs(data.HitNormal.z) == 1 and yaw or 0, -90)) + end + + + if Ent:IsWorld() then + if stuck then self:GetPhysicsObject():EnableMotion(false) end + elseif stuck then + self:SetParent( Ent ) + self.Entity:GetPhysicsObject():SetVelocity(data.OurOldVelocity) + end + + self:Disable() + + + end + +end + +/*--------------------------------------------------------- + FROM octoinv_item +---------------------------------------------------------*/ + +function ENT:SetData(item, data) + + self:SetNetVar('Item', { item, data }) + local phys = self:GetPhysicsObject() + if IsValid(phys) then + local amount = istable(data) and data.amount or isnumber(data) and data or 1 + local mass = istable(data) and data.mass or octoinv.items[item].mass or 0 + + self.baseMass = self.baseMass or phys:GetMass() + phys:SetMass(self.baseMass + mass * amount) + end + +end + +function ENT:Use(ply) + + if not ply:Alive() or ply:IsGhost() then return end + + local cont = ply:GetInventory():GetContainer('_hand') + if not cont then + ply:Notify('warning', L.onlyhandsitem2) + return + end + + local itemData = self:GetNetVar('Item') + if not itemData then return end + + local added, item = cont:AddItem(itemData[1], itemData[2]) + if added < 1 then + ply:Notify('warning', L.inhandsnospace) + return + end + + if istable(itemData[2]) then + itemData[2].amount = itemData[2].amount and (itemData[2].amount - added) or 0 + self:SetNetVar('Item', { itemData[1], itemData[2] }) + if itemData[2].amount < 1 then self:Remove() end + elseif isnumber(itemData[2]) then + itemData[2] = itemData[2] - added + self:SetNetVar('Item', { itemData[1], itemData[2] }) + if itemData[2] < 1 then self:Remove() end + end + ply:DoAnimation(ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND) + + hook.Run('octoinv.pickup', ply, self, item, added) + +end + +end + +if CLIENT then +/*--------------------------------------------------------- + Name: ENT:Draw() +---------------------------------------------------------*/ +function ENT:Draw() + self.Entity:DrawModel() +end +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/cl_init.lua new file mode 100644 index 0000000..c2dae12 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/cl_init.lua @@ -0,0 +1,191 @@ +local function playTime(time) + + local h, m, s + h = math.floor(time / 60 / 60) + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format('%02i:%02i:%02i', h, m, s) + +end + +include 'shared.lua' + +local weps = { + 'dbg_punisher', + 'weapon_octo_ak', + 'weapon_octo_aug', + 'weapon_octo_awp', + 'weapon_octo_deagle', + 'weapon_octo_dualelites', + 'weapon_octo_famas', + 'weapon_octo_fiveseven', + 'weapon_octo_g3sg1', + 'weapon_octo_galil', + 'weapon_octo_glock', + 'weapon_octo_knife', + 'weapon_octo_m249', + 'weapon_octo_m3', + 'weapon_octo_m4a1', + 'weapon_octo_mac10', + 'weapon_octo_mp5', + 'weapon_octo_p228', + 'weapon_octo_p90', + 'weapon_octo_scout', + 'weapon_octo_sg550', + 'weapon_octo_sg552', + 'weapon_octo_tmp', + 'weapon_octo_ump45', + 'weapon_octo_usp', + 'weapon_octo_xm1014', + 'weapon_octo_usps', + 'weapon_octo_357', + 'weapon_octo_knife', + 'weapon_octo_axe', + 'weapon_octo_shovel', + 'weapon_octo_hook', + 'weapon_flashlight', + 'keypad_cracker', + 'gmod_camera', +} + +local logs = { + dbg = 'dbg_cd', + dbg2 = 'dbg_nd', +} + +local warns = L.warns + +hook.Add('dbg-admin.getActions', 'dbg-admingun', function(menu, ply, id) + + local steamID = ply:SteamID() + if steamID == 'NULL' then steamID = 'BOT' end + + local name = ('%s (%s)'):format(ply:Name(), ply:SteamName()) + menu:AddOption(name, function() + ply:ShowProfile() + end):SetIcon(octolib.icons.silk16('user')) + + -- steamid + menu:AddOption(L.action_copySteamID, function() + SetClipboardText(steamID) + end):SetIcon(octolib.icons.silk16('page_copy')) + + menu:AddSpacer() + + -- time + local time1 = playTime(ply:GetTimeHere()) + menu:AddOption('Наиграно здесь: ' .. time1,function() + SetClipboardText(time1) + end):SetIcon(octolib.icons.silk16('clock')) + local time2 = playTime(ply:GetTimeTotal()) + menu:AddOption('Наиграно всего: ' .. time2,function() + SetClipboardText(time2) + end):SetIcon(octolib.icons.silk16('clock_red')) + + if LocalPlayer():IsAdmin() then + menu:AddSpacer() + + local sm, pmo = menu:AddSubMenu(L.teleport) + sm:AddOption('Goto', function() RunConsoleCommand('sgs', 'goto', steamID) end) + sm:AddOption('Bring', function() RunConsoleCommand('sgs', 'bring', steamID) end) + sm:AddOption('Return', function() RunConsoleCommand('sgs', 'return', steamID) end) + sm:AddOption('To Admin Zone', function() RunConsoleCommand('sgs', 'adminzone', steamID) end) + pmo:SetIcon(octolib.icons.silk16('bullet_go')) + + local sm, pmo = menu:AddSubMenu(L.condition) + sm:AddOption('100HP', function() RunConsoleCommand('sgs', 'hp', steamID, '100') end) + sm:AddOption(L.hundred_energy, function() RunConsoleCommand('sgs', 'hunger', steamID, '100') end) + sm:AddOption(L.spawn, function() netstream.Start('chat', '/spawn ' .. ply:Name()) end) + sm:AddOption('Slay', function() RunConsoleCommand('sgs', 'slay', steamID) end) + pmo:SetIcon(octolib.icons.silk16('pill')) + + local sm, pmo = menu:AddSubMenu(L.weapons) + local sm2, pmo2 = sm:AddSubMenu(L.give_weapon) + for i, wep in ipairs(weps) do + sm2:AddOption(wep, function() RunConsoleCommand('sgs', 'give', steamID, wep) end) + end + + sm:AddOption(L.give_ammo, function() + Derma_StringRequest(L.give_ammo, L.give_ammo_hint, '', function(s) + RunConsoleCommand('sgs', 'ammo', steamID, s) + end, function() end, L.ok, L.cancel) + end) + pmo:SetIcon(octolib.icons.silk16('gun')) + + local sm, pmo = menu:AddSubMenu(L.warns) + sm:AddOption('Редактировать', serverguard.editAdminTell):SetIcon(octolib.icons.silk16('pencil')) + for i, v in ipairs(L.warns_list) do + sm:AddOption(v[1], function() netstream.Start('AdminTell', ply, v[2], v[1], v[3]) end) + end + pmo:SetIcon(octolib.icons.silk16('warning')) + + menu:AddOption(L.ulogs_search, function() + octoesc.OpenURL('https://octothorp.team/logs/' .. (logs[CFG.serverID] or CFG.serverID) .. '/search/' .. steamID) + end):SetIcon(octolib.icons.silk16('page_white_magnify')) + menu:AddOption(L.admingun_admintell, function() octochat.say('/admintell', steamID) end):SetIcon(octolib.icons.silk16('textfield')) + menu:AddOption('Screenshot', function() RunConsoleCommand('sgs', 'capture', steamID) end):SetIcon(octolib.icons.silk16('photo_add')) + menu:AddOption('Spectate', function() RunConsoleCommand('FSpectate', steamID) end):SetIcon(octolib.icons.silk16('eye')) + menu:AddOption('Watchlist', function() RunConsoleCommand('sg', 'watch', steamID) end):SetIcon(octolib.icons.silk16('edit_recipient_list')) + + local sm, pmo = menu:AddSubMenu('Запретить игру...') + + sm:AddOption('За криминал', function() + Derma_StringRequest('Срок запрета', 'Укажи срок запрета', '', function(s) + RunConsoleCommand('sg', 'denycrime', steamID, s) + end) + end):SetIcon(octolib.icons.silk16('gun')) + + sm:AddOption('За полицию', function() + Derma_StringRequest('Срок запрета', 'Укажи срок запрета', '', function(s) + RunConsoleCommand('sg', 'denypolice', steamID, s) + end) + end):SetIcon(octolib.icons.silk16('baton')) + + pmo:SetIcon(octolib.icons.silk16('delete')) + + menu:AddOption('Kick', function() + Derma_StringRequest(L.reason_kick,L.reason_kick_hint, '', function(s) + RunConsoleCommand('sg', 'kick', steamID, s) + end, function() end, L.ok, L.cancel) + end):SetIcon(octolib.icons.silk16('delete')) + + menu:AddOption('Ban', function() + Derma_StringRequest(L.length_ban, L.length_ban_hint, '', function(s1) + Derma_StringRequest(L.reason_ban, L.reason_ban_hint, '', function(s2) + RunConsoleCommand('sg', 'ban', steamID, s1, s2) + end, function() end, L.ok, L.cancel) + end, function() end, L.ok, L.cancel) + end):SetIcon(octolib.icons.silk16('delete')) + end + +end) + +function SWEP:PrimaryAttack() + + if not IsFirstTimePredicted() then return end + + gui.EnableScreenClicker( true ) + local menu = DermaMenu() + + local ply = self.Owner:GetEyeTrace().Entity + if not IsValid(ply) or not ply:IsPlayer() then ply = LocalPlayer() end + + hook.Run('dbg-admin.getActions', menu, ply, 'admingun') + + menu:Open() + gui.EnableScreenClicker( false ) + +end + +function SWEP:SecondaryAttack() + + -- keep calm and do nothing + +end + +function SWEP:Reload() + + -- keep calm and do nothing + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/init.lua new file mode 100644 index 0000000..4535b2c --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/init.lua @@ -0,0 +1,206 @@ +AddCSLuaFile "shared.lua" +AddCSLuaFile "cl_init.lua" +include "shared.lua" + +------------------------------------------------- +-- MAIN +------------------------------------------------- + +function SWEP:PrimaryAttack() + + -- do nothing + +end + +function SWEP:SecondaryAttack() + + local ply = self.Owner:GetEyeTrace().Entity + if IsValid(ply) and ply:IsPlayer() then + if not ply:GetDBVar('sgMuted') then + serverguard.command.Run(self.Owner, 'mute', true, ply:SteamID(), 15) + serverguard.command.Run(self.Owner, 'gag', true, ply:SteamID(), 15) + self.Owner:Notify('ooc', L.gag_and_mute:format(ply:Name())) + ply:Notify('ooc', self.Owner:Name() .. L.gag_and_mute2) + else + serverguard.command.Run(self.Owner, 'unmute', true, ply:SteamID()) + serverguard.command.Run(self.Owner, 'ungag', true, ply:SteamID()) + self.Owner:Notify('ooc', L.ungag_and_unmute:format(ply:Name())) + ply:Notify('ooc', self.Owner:Name() .. L.ungag_and_unmute2) + end + end + +end + +SWEP.nextReload = 0 +function SWEP:Reload() + + if CurTime() < self.nextReload then return end + + self.Owner:Say('/invisible') + if self.Owner:GetNoDraw() then + self.Owner:Notify('ooc', L.invisible_god_en) + else + self.Owner:Notify('ooc', L.invisible_god_dis) + end + + self.nextReload = CurTime() + 1.5 + +end + +concommand.Add('dbg_admin', function(ply) + + if not ply:IsAdmin() or IsValid(ply.tazeragdoll) then return end + + if ply:Team() ~= TEAM_ADMIN then + local GM = GM or GAMEMODE + if ply:IsInvisible() then + ply:MakeInvisible(false) + timer.Simple(0, function() + ply:MakeInvisible(true) + end) + end + + local weapons = {} + for _,wep in ipairs(ply:GetWeapons()) do + if not (GM.Config.DisallowDrop[wep:GetClass()] or ply:jobHasWeapon(wep:GetClass())) then + table.insert(weapons, {wep:GetClass(), wep:Clip1()}) + end + end + + ply.dbgAdmin_data = { + j = ply:getJobTable().command, + mdl = ply:GetModel(), + sk = ply:GetSkin(), + a = ply:Armor(), + p = ply:GetAmmoCount('pistol'), + smg = ply:GetAmmoCount('SMG1'), + lc = ply:GetNetVar('HasGunlicense'), + wps = weapons, + freq = ply:GetFrequency(), + } + + ply:changeTeam(TEAM_ADMIN, true, true) + ply:SetModel('models/player/kleiner.mdl') + ply:GodEnable() + + ply:SetAmmo(180, 'pistol') + ply:SetAmmo(360, 'SMG1') + ply:SetArmor(100) + ply:ConnectTalkie('ems') + ply:SyncTalkieChannels() + + ply:SelectWeapon('dbg_admingun') + ply:Notify('ooc', L.admin_mode_on) + elseif ply.dbgAdmin_data then + + if ply:IsInvisible() then + ply:MakeInvisible(false) + timer.Simple(0, function() + ply:MakeInvisible(true) + end) + end + + local data = ply.dbgAdmin_data + + local _, job = DarkRP.getJobByCommand(data.j) + ply:changeTeam(job, true, true) + ply:SetModel(data.mdl) + ply:SetSkin(data.sk) + ply:SetArmor(data.a) + ply:SetAmmo(data.p, 'pistol') + ply:SetAmmo(data.smg, 'SMG1') + ply:SetNetVar('HasGunlicense', data.lc) + for _,v in ipairs(data.wps) do + local wep = ply:Give(v[1], true) + if IsValid(wep) then wep:SetClip1(v[2]) end + end + ply:GodDisable() + + ply.dbgAdmin_data = nil + + ply:SelectWeapon('dbg_hands') + ply:Notify('ooc', L.admin_mode_off) + + else ply:Notify('warning', 'Информация об админ-режиме утеряна! Попробуй сменить персонажа или воскресить себя') end + +end) + +netstream.Hook('AdminTell', function(ply, target, time, title, msg) + + if not ply:IsAdmin() then return end + if not IsValid(target) or not target:IsPlayer() then return end + + hook.Run('dbg-admin.tell', ply, time, title, msg, target) + target:Notify('admin', time, title, msg) + +end) + + +-- local _desc = ' Можно продать у входа в убежище' +-- local function souvenir(name, icon, vol, model, desc) +-- return {'souvenir', { +-- name = name, +-- icon = 'octoteam/icons/' .. icon .. '.png', +-- model = model, +-- desc = desc, +-- volume = vol, +-- mass = 1, +-- }} +-- end + +-- local items = { +-- {'coupon_food', 1}, +-- {'coupon_ammo', 1}, +-- {'coupon_exit', 1}, +-- {'radio', 1}, +-- {'h_mask', { +-- name = 'Противогаз', +-- icon = 'octoteam/icons/gasmask.png', +-- mask = 'gasmask', +-- }}, +-- {'food', { +-- name = 'МРЕ', +-- icon = 'octoteam/icons/mre.png', +-- energy = 100, +-- }}, +-- souvenir('Пыльный набор механика', 'hammer', 3, 'models/props_junk/cardboard_box004a.mdl', 'Хм... Может для кого и сгодится' .. _desc), +-- souvenir('Странные таблеточки', 'pills', 1, 'models/props_lab/jar01b.mdl', 'Помнится такие же по переулкам валялись, только пустые' .. _desc), +-- souvenir('Странный механизм', 'robot_arm', 1.5, 'models/jaanus/wiretool/wiretool_grabber_forcer.mdl', 'Выглядит, как что-то полезное' .. _desc), +-- souvenir('Серебряный бюст', 'bust', 1, 'models/props_junk/cardboard_box004a.mdl', 'Наверное он был какой-то важной шишкой в обществе. Думаю, кто-то сочтет это ценным' .. _desc), +-- souvenir(, 'battery_charge', 1, 'models/Items/car_battery01.mdl', 'Любые источники энергии сейчас на вес золота' .. _desc), +-- souvenir('Гантели', 'dumbbell', 3, 'models/props_junk/cardboard_box004a.mdl', 'Фух! Надеюсь, дотащу...' .. _desc), +-- souvenir('Моток каната', 'rope', 2, 'models/props_junk/cardboard_box004a.mdl', 'Полезная штуковина для выживания' .. _desc), +-- souvenir('Газовый баллон', 'keg', 20, 'models/props_junk/PropaneCanister001a.mdl', 'Незаменимая вещь в полевой кухне' .. _desc), +-- souvenir('Топливный генератор', 'engine', 35, 'models/gibs/airboat_broken_engine.mdl', 'Доступная энергия, главное не забыть заправить' .. _desc), +-- souvenir('Образец «Плантего Уртика»', 'plant', 0.2, 'models/props_junk/cardboard_box004a.mdl', 'Важный образец одного из видов растений. Отнеси это ученому'), +-- souvenir('Образец «Нигрум Мэйор»', 'plant', 0.2, 'models/props_junk/cardboard_box004a.mdl', 'Важный образец одного из видов растений. Отнеси это ученому'), +-- souvenir('Образец «Рубиус Чалгос»', 'plant', 0.2, 'models/props_junk/cardboard_box004a.mdl', 'Важный образец одного из видов растений. Отнеси это ученому'), +-- } + +-- netstream.Hook('dysplasia.give', function(ply, tgt, itemID) + +-- local cont = tgt.inv and tgt.inv.conts._hand +-- if not cont then +-- ply:Notify('warning', 'У игрока должны быть свободны руки') +-- return +-- end + +-- if itemID == 1 then +-- for i, data in ipairs({ +-- {'coupon_food', 3}, +-- {'coupon_ammo', 1}, +-- {'radio', 1}, +-- {'h_mask', { +-- name = 'Противогаз', +-- icon = 'octoteam/icons/gasmask.png', +-- mask = 'gasmask', +-- }}, +-- }) do +-- cont:AddItem(data[1], data[2]) +-- end +-- else +-- local data = items[itemID - 1] +-- if data then cont:AddItem(data[1], data[2]) end +-- end + +-- end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/shared.lua new file mode 100644 index 0000000..eca55ff --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_admingun/shared.lua @@ -0,0 +1,57 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = "weapon_base" +SWEP.Category = L.dobrograd +SWEP.PrintName = L.admin_tool +SWEP.Instructions = L.instruction_admin_tool +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 62 +SWEP.ViewModel = "" +SWEP.WorldModel = "" +SWEP.HoldType = "normal" +SWEP.UseHands = true +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "ЛКМ - вызвать админ-меню игрока\nПКМ - запретить игроку разговаривать\nR - переключить невидимость и бессмертие" +SWEP.UseHands = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + + +function SWEP:Initialize() + + self:SetHoldType(self.HoldType) + +end + +function SWEP:Holster() + + return true + +end + +function SWEP:Deploy() + + self:SetNextPrimaryFire(CurTime() + 0.5) + self:SetNextSecondaryFire(CurTime() + 0.5) + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/cl_init.lua new file mode 100644 index 0000000..3a53734 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/cl_init.lua @@ -0,0 +1,205 @@ +--[[ + + well, if you're reading this, you probably tried to steal the code + luckily, I can properly split realms and you get not working clientside code + + - chelog + +]] + +include 'shared.lua' + +function SWEP:DrawWorldModel() + + return false + +end + +function SWEP:DrawWorldModelTranslucent() + + return false + +end + +function SWEP:PrimaryAttack() + + -- keep calm and do nothing + +end + +function SWEP:SecondaryAttack() + + -- keep calm and do nothing + +end + +function SWEP:Reload() + + -- keep calm and do nothing + +end + +function SWEP:Think() + + -- nothing + +end + +function SWEP:Holster() + + local ply = self:GetOwner() + if not IsValid(ply) then return true end + + local b1 = ply:LookupBone 'ValveBiped.Bip01_R_Upperarm' + local b2 = ply:LookupBone 'ValveBiped.Bip01_R_Forearm' + if b1 and b2 then + ply:ManipulateBoneAngles(b1, Angle()) + ply:ManipulateBoneAngles(b2, Angle()) + end + + return true + +end + +function SWEP:OnRemove() + + local ply = self:GetOwner() + if not IsValid(ply) then return true end + + local b1 = ply:LookupBone 'ValveBiped.Bip01_R_Upperarm' + local b2 = ply:LookupBone 'ValveBiped.Bip01_R_Forearm' + if b1 and b2 then + ply:ManipulateBoneAngles(b1, Angle()) + ply:ManipulateBoneAngles(b2, Angle()) + end + +end + +local offPosDown1, offPosDown2, offAngDown = Vector(3.7,-1.5,0.75), Vector(3.9,-1.9,0.35), Angle(0,25,0) +local offPosUp1, offPosUp2, offAngUp = Vector(2.6, -1, -3), Vector(2, -0.8, -3), Angle(0, 78, 0) +local offPosSmoke = Vector(0, -1.5, 0) + +local smokers = {} +local function updateCigarette(ply) + local wep = ply:GetWeapon('dbg_cigarette') + + local dummy = ply.dbgCigarette + if IsValid(wep) then + smokers[#smokers + 1] = ply + elseif IsValid(dummy) then + local em = dummy.emitter + timer.Simple(2, function() + em:Finish() + end) + dummy:Remove() + ply.dbgCigarette = nil + end + +end +timer.Create('dbg-cigarette', 0.5, 0, function() + table.Empty(smokers) + for _, ply in ipairs(player.GetAll()) do updateCigarette(ply) end +end) + +local function createCigaretteDummy(ply) + if ply.dbgCigaretteCreating then return end + ply.dbgCigaretteCreating = true + timer.Simple(0, function() + ply.dbgCigarette = octolib.createDummy('models/phycigold.mdl', RENDERGROUP_BOTH) + ply.dbgCigarette.emitter = ParticleEmitter(Vector()) + ply.dbgCigarette.female = not octolib.models.isMale(ply:GetModel()) + ply.dbgCigaretteCreating = false + end) +end + +local function drawSmoker(ply, ct, ft) + + if not IsValid(ply) then return end + + local c = ply.dbgCigarette + if not IsValid(c) then + createCigaretteDummy(ply) + return + end + + local aWep = ply:GetActiveWeapon() + c.mult = math.Approach(c.mult or 0, ply:GetNetVar('IsSmoking') and 1 or 0, ft * 2) + local b1 = ply:LookupBone 'ValveBiped.Bip01_R_Upperarm' + local b2 = ply:LookupBone 'ValveBiped.Bip01_R_Forearm' + if b1 and b2 then + ply:ManipulateBoneAngles(b1, Angle(20*c.mult, -62*c.mult, 10*c.mult)) + ply:ManipulateBoneAngles(b2, Angle(-5*c.mult, -10*c.mult, 0)) + c.isUp = c.mult > 0.9 + + local onHead = c.isUp or not (IsValid(aWep) and aWep:GetClass() == 'dbg_cigarette') + if onHead ~= c.onHead then + c:SetParent() + if onHead then + c:SetParent(ply, ply:LookupAttachment('eyes') or 1) + c:SetLocalPos(c.female and offPosUp2 or offPosUp1) + c:SetLocalAngles(offAngUp) + else + c:SetParent(ply, ply:LookupAttachment('anim_attachment_RH') or 5) + c:SetLocalPos(c.female and offPosDown2 or offPosDown1) + c:SetLocalAngles(offAngDown) + end + end + c.onHead = onHead + end + + if ct > (c.nextP or 0) and not c.isUp then + local pos = ply.dbgCigarette:LocalToWorld(offPosSmoke) + local p = c.emitter:Add(string.format('particle/smokesprites_00%02d',math.random(7,16)), pos) + p:SetColor(255,255,255) + p:SetGravity(Vector(0,0,10)) + p:SetDieTime(2) + p:SetLifeTime(0) + p:SetStartSize(0.5) + p:SetEndSize(2) + p:SetStartAlpha(100) + p:SetEndAlpha(0) + p:SetCollide(false) + p:SetRoll(math.random(360)) + p:SetRollDelta((math.random() - 0.5) * 2) + p:SetAirResistance(50) + c.nextP = ct + 0.15 + end + +end + +hook.Add('PostDrawTranslucentRenderables', 'dbg-cigarette', function() + local ct, ft = CurTime(), FrameTime() + for _, ply in ipairs(smokers) do + drawSmoker(ply, ct, ft) + end +end) + +net.Receive('dbg.cigarette.exhale', function(len) + + local ply = net.ReadEntity() + local amount = net.ReadUInt(8) + + local em = ParticleEmitter(Vector()) + for i = 0, amount-1 do + timer.Simple(0.5 + i * 0.1, function() + if IsValid(ply) then + local att = ply:GetAttachment(ply:LookupAttachment('mouth') or 0) + local p = em:Add(string.format('particle/smokesprites_00%02d', math.random(7,16)), att.Pos) + p:SetColor(255,255,255) + p:SetVelocity(att.Ang:Forward() * (15 - i * 0.5)) + p:SetGravity(Vector(0,0,1)) + p:SetDieTime(8) + p:SetLifeTime(0) + p:SetStartSize(1) + p:SetEndSize(8) + p:SetStartAlpha(50) + p:SetEndAlpha(0) + p:SetCollide(false) + p:SetRoll(math.random(360)) + p:SetRollDelta((math.random() - 0.5)) + p:SetAirResistance(50) + end + end) + end + +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/init.lua new file mode 100644 index 0000000..79f42a4 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/init.lua @@ -0,0 +1,112 @@ +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' +include 'shared.lua' + +------------------------------------------------- +-- MAIN +------------------------------------------------- + +function SWEP:Holster() + + self.Owner:SetNetVar('IsSmoking', false) + return true + +end + +function SWEP:OnRemove() + + self.Owner:SetNetVar('IsSmoking', false) + +end + +function SWEP:PrimaryAttack() + + -- do nothing + +end + +function SWEP:SecondaryAttack() + + self:DropCigarette() + self:SetNextSecondaryFire(CurTime() + 1) + +end + +function SWEP:DropCigarette() + + local ply = self.Owner + if not IsValid(ply) then return end + + local e = ents.Create 'prop_physics' + e:SetPos(ply:GetShootPos()) + e:SetModel('models/phycigold.mdl') + e:SetAngles(ply:EyeAngles()) + e:Spawn() + e:Activate() + e:SetCollisionGroup(COLLISION_GROUP_WORLD) + local phys = e:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + phys:AddVelocity(ply:GetAimVector() * 300) + end + + timer.Simple(20, function() + if IsValid(e) then e:Remove() end + end) + + self.Owner:SetNetVar('IsSmoking', false) + self:Remove() + + local curWep = ply:GetActiveWeapon() + if curWep == self then + ply:ConCommand('lastinv') + + if not IsValid(curWep) and ply:HasWeapon('dbg_hands') then + ply:SelectWeapon('dbg_hands') + end + end + +end + +function SWEP:Reload() + + -- do nothing + +end + +util.AddNetworkString 'dbg.cigarette.exhale' +function SWEP:Think() + + if not IsValid(self.Owner) then return end + if self.Owner:KeyPressed(IN_ATTACK) then + self:SetHoldType('slam') + self.Owner:SetNetVar('IsSmoking', true) + self.inhaleTime = CurTime() + 0.5 + elseif self.Owner:KeyReleased(IN_ATTACK) then + self:SetHoldType('normal') + self.Owner:SetNetVar('IsSmoking', false) + + local amount = math.min(math.floor((CurTime() - (self.inhaleTime or 0)) * 4), 15) + if amount > 0 then + net.Start('dbg.cigarette.exhale') + net.WriteEntity(self.Owner) + net.WriteUInt(amount, 8) + net.SendPVS(self.Owner:GetPos()) + + self.dieTime = self.dieTime - amount * 6 + end + end + +end + +timer.Create('dbg.cigarette.think', 2, 0, function() + + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + local c = ply:GetWeapon('dbg_cigarette') + if IsValid(c) and not ply:GetNetVar('IsSmoking') and CurTime() > c.dieTime then + c:DropCigarette() + end + end) + +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/shared.lua new file mode 100644 index 0000000..f91d2d5 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_cigarette/shared.lua @@ -0,0 +1,51 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = "weapon_base" +SWEP.Category = L.dobrograd +SWEP.PrintName = L.cigarette +SWEP.Instructions = L.instruction_cigarette +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 62 +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/phycigold.mdl" +SWEP.HoldType = "normal" +SWEP.UseHands = true +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "Взять сигарету в правую руку" +SWEP.Instructions = "ЛКМ - затянуться\nПКМ - выбросить" +SWEP.UseHands = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +function SWEP:Initialize() + + self:SetHoldType(self.HoldType) + self.dieTime = CurTime() + 360 + +end + +function SWEP:Deploy() + + self:SetNextPrimaryFire(CurTime() + 0.5) + self:SetNextSecondaryFire(CurTime() + 0.5) + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/cl_init.lua new file mode 100644 index 0000000..91db467 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/cl_init.lua @@ -0,0 +1,13 @@ +include('shared.lua') + +SWEP.DrawWorldModel = octolib.func.no +SWEP.DrawWorldModelTranslucent = octolib.func.no +SWEP.PrimaryAttack = octolib.func.zero +SWEP.SecondaryAttack = octolib.func.zero + +function SWEP:Think() + local ct = CurTime() + if (self.nextThink or 0) > ct then return end + dbgView.hideHead(true) + self.nextThink = ct + 3 +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/init.lua new file mode 100644 index 0000000..76cdae2 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/init.lua @@ -0,0 +1,83 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +SWEP.HitDistance = 48 + +function SWEP:PrimaryAttack() + self.attack = CurTime() + 1 + local owner = self:GetOwner() + if owner:GetVelocity():Length2DSqr() < 1 then + owner:DoAnimation(ACT_GMOD_GESTURE_DISAGREE) + end + owner:EmitSound('octoteam/characters/dog/rawr' .. math.random(2) .. '.ogg') + self:SetNextPrimaryFire(CurTime() + 1.5) +end + +function SWEP:SecondaryAttack() + local owner = self:GetOwner() + if owner:GetVelocity():Length2DSqr() < 1 then + owner:DoAnimation(ACT_GMOD_GESTURE_BOW) + end + timer.Simple(0.5, function() + if IsValid(owner) then owner:EmitSound('octoteam/characters/dog/bark' .. math.random(5) .. '.ogg') end + end) + self:SetNextSecondaryFire(CurTime() + 0.5) +end + +function SWEP:Think() + if not (self.attack and CurTime() > self.attack) then return end + self.attack = nil + + local owner = self:GetOwner() + owner:LagCompensation(true) + + local aim = owner:EyeAngles():Forward() + local tr = util.TraceLine({ + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + aim * self.HitDistance, + filter = owner, + mask = MASK_SHOT_HULL + }) + + if not IsValid(tr.Entity) then + tr = util.TraceHull({ + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + aim * self.HitDistance, + filter = owner, + mins = Vector(-10, -10, -8), + maxs = Vector(10, 10, 8), + mask = MASK_SHOT_HULL + }) + end + + if IsValid(tr.Entity) and tr.Entity:IsPlayer() and tr.Entity:Health() > 0 then + local dmginfo = DamageInfo() + + local attacker = owner + if not IsValid(attacker) then attacker = self end + dmginfo:SetAttacker(attacker) + + dmginfo:SetInflictor(self) + local dmgAmount = math.random(8, 12) + dmginfo:SetDamage(dmgAmount) + dmginfo:SetDamageType(DMG_SLASH) + + tr.Entity:TakeDamageInfo(dmginfo) + DarkRP.damageHands(tr.Entity, 70) + tr.Entity:MoveModifier('dog-bite', { + walkmul = 0.75, + runmul = 0.75, + nojump = true, + }) + tr.Entity:EmitSound('vo/npc/' .. (tr.Entity:IsMale() and '' or 'fe') .. 'male01/pain0' .. math.random(9) .. '.wav') + timer.Create('dog-bite' .. tr.Entity:SteamID(), math.random(2, 4), 1, function() + tr.Entity:MoveModifier('dog-bite') + end) + owner:EmitSound('npc/barnacle/barnacle_crunch' .. math.random(2,3) .. '.wav') + + end + + owner:LagCompensation(false) + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/shared.lua new file mode 100644 index 0000000..d94637a --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_dog/shared.lua @@ -0,0 +1,44 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'weapon_base' +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Собака' +SWEP.Instructions = 'ЛКМ - укусить\nПКМ - полаять' +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 62 +SWEP.ViewModel = Model('models/weapons/v_crowbar.mdl') +SWEP.WorldModel = Model('models/weapons/w_crowbar.mdl') +SWEP.HoldType = 'normal' +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Spawnable = false +SWEP.AdminOnly = false + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) +end + +function SWEP:Holster() + return true +end + +function SWEP:Deploy() + self:SetNextPrimaryFire(CurTime() + 0.5) + self:SetNextSecondaryFire(CurTime() + 0.5) +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/cl_init.lua new file mode 100644 index 0000000..5544302 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/cl_init.lua @@ -0,0 +1,217 @@ +--[[ + + well, if you're reading this, you probably tried to steal the code + luckily, I can properly split realms and you get not working clientside code + + - chelog + +]] + +include('shared.lua') + +SWEP.DrawCrosshair = true + +function SWEP:PrimaryAttack() + + if not IsFirstTimePredicted() then return end + + if not self.Owner:KeyDown(IN_ATTACK2) then + local aim = self.Owner:EyeAngles():Forward() + local should = true + local tr = util.TraceLine({ + start = self.Owner:EyePos(), + endpos = self.Owner:EyePos() + aim * 100, + filter = self.Owner + }) + if tr.Entity:IsPlayer() then + should = not tr.Entity:GetNetVar('Ghost') and tr.Entity:Crouching() + and IsValid(tr.Entity:GetActiveWeapon()) and tr.Entity:GetActiveWeapon():GetHoldType() == 'pistol' + end + local veh = self.Owner:GetVehicle() + if IsValid(veh) and IsValid(veh:GetParent()) then return end + if IsValid(tr.Entity) and not self.doNotDrag[tr.Entity:GetClass()] and tr.Entity:GetClass() ~= 'prop_ragdoll' and not tr.Entity:IsDoor() and should then + self.pickedEnt = tr.Entity + self.pickedEntOffset = tr.Entity:WorldToLocal( tr.HitPos ) + self.isHolding = true + end + end + +end + +function SWEP:SecondaryAttack() + + -- keep calm and do nothing + +end + +function SWEP:Reload() + + if self.Owner:InVehicle() then return end + if not self.NextToggleCrosshair or self.NextToggleCrosshair <= CurTime() then + self.DrawCrosshair = not self.DrawCrosshair + self.NextToggleCrosshair = CurTime() + 0.5 + octolib.notify.show('rp', L.crosshair, ' ', self.DrawCrosshair and L.enabled or L.disabled) + end + +end + +function SWEP:DrawWorldModel() + + return false + +end + +function SWEP:DrawWorldModelTranslucent() + + return false + +end + +local nextPunch = 0 +function SWEP:Think() + + local owner = self.Owner + local meleetime = self:GetNextMeleeAttack() + if ( meleetime > 0 and CurTime() > meleetime and owner:KeyDown(IN_ATTACK2) and CurTime() > nextPunch ) then + owner:SetAnimation(PLAYER_ATTACK1) + nextPunch = CurTime() + 0.2 + end + + if not owner:InVehicle() and self.isHolding then + if not IsValid(self.pickedEnt) or (not input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT)) then + self.isHolding = nil + self.pickedEnt = nil + self.pickedEntOffset = nil + end + end + + HAND_DRAGGING = self.isHolding + +end + +local curStep, target = 1, nil +local bones = { + 'ValveBiped.Bip01_Head1', + 'ValveBiped.Bip01_Spine', + 'ValveBiped.Bip01_L_UpperArm', + 'ValveBiped.Bip01_R_UpperArm', +} + +local function priestThingies( self ) + + if self.Owner:KeyDown(IN_ATTACK) then + local x, y = ScrW()/2, ScrH()/2 + + if not IsValid(target) then + local pos = self.Owner:GetPos() + local r = Vector(150,150,150) + for i, ent in ipairs(ents.FindInBox(pos - r, pos + r)) do + if IsValid(ent) and ent:IsPlayer() and ent:GetNetVar('Ghost') and ent:GetPos():DistToSqr(self.Owner:GetPos()) < 6000 then + target = ent + end + end + end + + if IsValid(target) and bones[curStep] then + if target:GetPos():DistToSqr(self.Owner:GetPos()) > 6000 then + target = nil + return + end + + local bone = target:LookupBone(bones[curStep]) + if bone then + local pos, ang = target:GetBonePosition(bone) + pos = pos:ToScreen() + draw.RoundedBox(8, pos.x-8, pos.y-8, 16, 16, color_white) + surface.SetDrawColor(255,255,255) + surface.DrawLine(x, y, pos.x, pos.y) + + if math.abs(pos.x - x) < 8 and math.abs(pos.y - y) < 8 then + curStep = curStep + 1 + end + end + end + + if curStep == 5 and IsValid(target) then + net.Start('dbg-revive') + net.WriteEntity(target) + net.SendToServer() + + curStep = curStep + 1 + end + else + curStep = 1 + target = nil + end + +end + +function SWEP:DrawHUD() + + if self.Owner:Team() == TEAM_PRIEST then + priestThingies( self ) + end + +end + +local dragging, isHands +hook.Add('PostDrawTranslucentRenderables', 'dbg-hands', function() + + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'dbg_hands' then + if IsValid(wep.pickedEnt) then + local pos1 = wep.Owner:EyePos() + wep.Owner:GetAimVector() * 80 + local pos2 = wep.pickedEnt:LocalToWorld( wep.pickedEntOffset ) + wep.pos1 = pos1 + wep.pos2 = pos2 + + cam.Start3D() + render.DrawLine(pos1, pos2, Color(255,255,255), true) + render.DrawLine(pos1, pos2, Color(255,255,255, 10), false) + cam.End3D() + + dragging = {pos1, (EyePos() - pos1):GetNormalized()} + else + dragging = nil + end + isHands = wep + else + isHands = nil + end + +end) + +hook.Add('dbg-view.chShouldDraw', 'dbg_hands', function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + local wep = ply:GetActiveWeapon() + if ply:InVehicle() and IsValid(wep) and wep:GetClass() == 'dbg_hands' then return false end +end) + +local iconOn, iconOff = Material('octoteam/icons/hand_fist.png'), Material('octoteam/icons/hand.png') +hook.Add('dbg-view.chOverride', 'dbg_hands', function(tr) + + if dragging then + tr.HitPos = dragging[1] + tr.HitNormal = dragging[2] + tr.Fraction = 0.05 + return iconOn, 255, 0.35 + elseif isHands and tr.Fraction < 0.05 then + local ent = tr.Entity + if IsValid(ent) and isHands.doNotDrag and not isHands.doNotDrag[ent:GetClass()] then + return iconOff, 255, 0.35 + end + end + +end) + +netstream.Hook('dbg_hands.StopDragging', function() + + local wep = LocalPlayer():GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'dbg_hands' and wep.isHolding then + wep.isHolding = nil + wep.pickedEnt = nil + wep.pickedEntOffset = nil + end + +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/init.lua new file mode 100644 index 0000000..fbed0e5 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/init.lua @@ -0,0 +1,424 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +local SwingSound = Sound('WeaponFrag.Throw') +local HitSound = Sound('Flesh.ImpactHard') + +SWEP.HitDistance = 48 +SWEP.KnockDistSqr = 65 * 65 +SWEP.isDragging = false +SWEP.pickedEnt = nil +SWEP.pickedEntOffset = Vector(0,0,0) + +------------------------------------------------- +-- MAIN +------------------------------------------------- + +local function isCarOwner(ent, ply) + + local owner = ent:CPPIGetOwner() + return IsValid(owner) and + (owner == ply or owner.Buddies and owner.Buddies[ply] and table.HasValue(owner.Buddies[ply], true)) and + not ply:InVehicle() -- to prevent mouse clicks lock/unlock the car + +end + +function SWEP:lookingAtLockable(ply, ent) + + local eyepos = ply:EyePos() + if not IsValid(ent) then return false end + + if ent:IsDoor() and not ent:IsBlocked() and eyepos:DistToSqr(ent:GetPos()) < self.KnockDistSqr then + return true + end + + if ent.isFadingDoor and ent:GetPos():DistToSqr(eyepos) < self.KnockDistSqr then return true end + if ent.IsSimfphyscar then return true end + +end + +function SWEP:lockUnlockAnimation(ply, snd) + + ply:EmitSound('doors/door_latch1.wav') + ply:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + +end + +function SWEP:doKnock(ply, sound) + + ply:EmitSound(sound, 100, math.random(90, 110)) + ply:DoAnimation(ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST) + +end + +SWEP.UseDel = CurTime() +function SWEP:DoTrace() + + local trace = {} + trace.start = self.Owner:GetShootPos() + trace.endpos = trace.start + (self.Owner:GetAimVector() * 10000) + trace.filter = { self.Owner, self.Weapon } + local tr = util.TraceLine(trace) + + return tr + +end + +function SWEP:DealDamage() + + if not self.anim then return end + + local owner = self:GetOwner() + owner:LagCompensation(true) + + local aim = owner:EyeAngles():Forward() + local tr = util.TraceLine({ + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + aim * self.HitDistance, + filter = owner, + mask = MASK_SHOT_HULL + }) + + if not IsValid(tr.Entity) then + tr = util.TraceHull({ + start = owner:GetShootPos(), + endpos = owner:GetShootPos() + aim * self.HitDistance, + filter = owner, + mins = Vector(-10, -10, -8), + maxs = Vector(10, 10, 8), + mask = MASK_SHOT_HULL + }) + end + + if tr.Hit then + owner:EmitSound(HitSound) + end + + if (SERVER and IsValid(tr.Entity) and (tr.Entity:IsNPC() or tr.Entity:IsPlayer() or tr.Entity:Health() > 0)) then + local dmginfo = DamageInfo() + + local attacker = owner + if not IsValid(attacker) then attacker = self end + dmginfo:SetAttacker(attacker) + + dmginfo:SetInflictor(self) + local dmgAmount = math.random(8, 12) + if tr.Entity:IsPlayer() then dmgAmount = math.Clamp(tr.Entity:Health() - (attacker:HasBuff('Meth') and 0 or 20), 0, dmgAmount) end + dmginfo:SetDamage(dmgAmount) + dmginfo:SetDamageType(DMG_CLUB) + + if (self.anim == 'fists_left') then + dmginfo:SetDamageForce(owner:GetRight() * 4912 + owner:GetForward() * 9998) + elseif (self.anim == 'fists_right') then + dmginfo:SetDamageForce(owner:GetRight() * -4912 + owner:GetForward() * 9989) + end + + tr.Entity:TakeDamageInfo(dmginfo) + + -- if tr.Entity:GetClass() == 'func_breakable_surf' then + -- tr.Entity:Fire('Shatter', Vector(0,0,0)) + -- owner:TakeDamage(math.random(10, 15), Entity(0), tr.Entity) + -- end + end + + if SERVER and IsValid(tr.Entity) then + local phys = tr.Entity:GetPhysicsObject() + if (IsValid(phys)) then + phys:ApplyForceOffset(owner:GetAimVector() * 80 * phys:GetMass(), tr.HitPos) + end + + local ent = tr.Entity + local carOwner = ent:CPPIGetOwner() + if IsValid(carOwner) and ent.IsSimfphyscar and carOwner ~= owner + and not (carOwner.Buddies and carOwner.Buddies[owner] and table.HasValue(carOwner.Buddies[owner], true)) + and CurTime() - (owner.karmaLast.cardamage or -180) > 180 then + owner:AddKarma(-1, L.cardamage) + print('[KARMA] ' .. tostring(owner) .. ' -1 karma for car fists damage') + owner.karmaLast.cardamage = CurTime() + end + end + + owner:LagCompensation(false) + +end + +function SWEP:PrimaryAttack() + + local owner = self:GetOwner() + if owner:IsGhost() or owner:IsHandcuffed() then return end + + if self.isFists and self:GetHoldType() == 'fist' then + local can, why = hook.Run('dbg-hands.canPunch', owner, owner:GetEyeTrace()) + if can == false then + if why then owner:Notify('warning', why) end + return + end + owner:SetAnimation(PLAYER_ATTACK1) + + self.anim = self.punchRight and 'fists_right' or 'fists_left' + owner:EmitSound(SwingSound) + + self:SetNextMeleeAttack(CurTime() + 0.2) + self:SetNextPrimaryFire(CurTime() + 0.9) + self:SetNextSecondaryFire(CurTime() + 0.9) + + self.punchRight = not self.punchRight + else + local ent = owner:GetEyeTrace().Entity + + if not self:lookingAtLockable(owner, ent) or owner:GetNetVar('Ghost') then return end + self:SetNextPrimaryFire(CurTime() + 0.3) + + if ent:CanBeLockedBy(owner) and CurTime() > (ent.nextLock or 0) then + owner:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + + owner:DelayedAction('door_lock', 'Закрытие замка', { + time = 1.5, + check = function() return octolib.use.check(owner, ent) end, + succ = function() + ent:EmitSound('doors/door_latch1.wav') + ent:DoLock() + end, + }) + elseif ent.IsSimfphyscar then + if isCarOwner(ent, owner) and CurTime() > (ent.nextLock or 0) then + ent:Lock() + end + else + self:doKnock(owner, 'physics/wood/wood_crate_impact_hard2.wav') + end + end + +end + +function SWEP:SecondaryAttack() + + local owner = self.Owner + if owner:IsGhost() or owner:IsHandcuffed() then return end + + local ent = owner:GetEyeTrace().Entity + if not self:lookingAtLockable(owner, ent) then return end + + self:SetNextSecondaryFire(CurTime() + 0.3) + + if ent:CanBeUnlockedBy(owner) then + ent:DoUnlock() + ent:EmitSound('doors/door_latch1.wav') + owner:DoAnimation(ACT_GMOD_GESTURE_ITEM_PLACE) + elseif ent.IsSimfphyscar then + if isCarOwner(ent, owner) then + ent:UnLock() + end + else + self:doKnock(owner, 'physics/wood/wood_crate_impact_hard3.wav') + end + +end + +function SWEP:Reload() + + -- do nothing + +end + +function SWEP:PrimaryAttackPressed() + + if self.isFists then return end + + if self.isDragging then + self:StopDragging() + return + end + + local owner = self:GetOwner() + local veh = owner:GetVehicle() + if IsValid(veh) and IsValid(veh:GetParent()) then return end + + local tr = util.TraceLine({ + start = owner:EyePos(), + endpos = owner:EyePos() + owner:GetAimVector() * 100, + filter = owner + }) + + local ent = tr.Entity + if not IsValid(ent) then + self:SetHoldType('pistol') + self.isPointing = true + return + end + + local can, why = hook.Run('dbg-hands.canDrag', owner, ent, tr) + if can == false then + if why then owner:Notify('warning', why) end + return + end + + local class = ent:GetClass() + local ph = class and not self.doNotDrag[class] and ent:GetPhysicsObjectNum(tr.PhysicsBone) + local should = not ent:IsPlayer() or (not ent:IsGhost() and ent:Crouching() and ent:KeyDown(IN_ATTACK)) + if should and IsValid(ph) and not ent:IsDoor() then + self.pickedEnt = ent + if ent.OnHandsPickup then + ent:OnHandsPickup(owner, self) + end + self.pickedEntBone = tr.PhysicsBone + self.pickedEntOffset = ph:WorldToLocal(tr.HitPos) + self.isDragging = true + self.pickedEnt.lastCarrier = owner -- to register killer + self:SetHoldType(IsValid(veh) and 'pistol' or 'magic') + + if class == 'prop_ragdoll' then + local name = owner:Name() + if owner:isCP() then name = name .. L.analyzer_cop end + + ent.criminals = ent.criminals or {} + if not table.HasValue(ent.criminals, name) then table.insert(ent.criminals, name) end + end + else + self:SetHoldType('pistol') + self.isPointing = true + end + +end + +function SWEP:PrimaryAttackReleased() + + if not self.isPointing then return end + + self:SetHoldType('normal') + self.isPointing = nil + +end + +function SWEP:SecondaryAttackPressed() + + self.isFists = true + self.isPointing = nil + + self:SetHoldType('fist') + octolib.stopAnimations(self:GetOwner()) + +end + +function SWEP:SecondaryAttackReleased() + + self.isFists = nil + + self:SetHoldType('normal') + self.anim = nil + +end + +function SWEP:StopDragging() + + if self.isDragging then + self.isDragging = false + self.pickedEnt = nil + self:SetHoldType('normal') + + netstream.Start(self:GetOwner(), 'dbg_hands.StopDragging') + end + +end + +function SWEP:Think() + + local owner = self:GetOwner() + if owner:IsGhost() then return end + + if self.isDragging then + local ent = self.pickedEnt + if not IsValid(ent) then + self:StopDragging() + return + end + + local should = owner:KeyDown(IN_ATTACK) and not owner:KeyDown(IN_ATTACK2) and owner:GetGroundEntity() ~= ent and not ent.APG_Picked + if ent:IsPlayer() and not (ent:KeyDown(IN_ATTACK) and ent:Crouching() and not ent:IsGhost()) then should = false end + if ent.IsSimfphyscar and ent:EngineActive() and ent:GetThrottle() > 0.5 then should = false end + + if not should then return self:StopDragging() end + + local ph = ent:GetPhysicsObjectNum(self.pickedEntBone) + local ph2 = ent:GetPhysicsObject() + + local pos = owner:EyePos() + owner:GetAimVector()*80 + local pos2 = ph:LocalToWorld(self.pickedEntOffset) + if (pos - pos2):LengthSqr() > 10000 then + self:StopDragging() + return + end + + local force = math.min(100, math.pow(ph2:GetMass(), 0.7) * 4) + if ent:GetPhysicsObjectCount() > 1 then force = force * 1.5 end + local vel = (pos - pos2) * force + if vel.z > 0 and ph:GetVelocity().z < 0 then vel.z = vel.z * 2.5 end + + ph:ApplyForceOffset(vel, pos2) + ph:ApplyForceCenter(-ph:GetVelocity() * 0.3) + ph:AddAngleVelocity(-ph:GetAngleVelocity() * 0.3) + + vel.z = vel.z * 0.4 + owner:SetVelocity(-vel * 0.02) + elseif self.isFists then + local meleetime = self:GetNextMeleeAttack() + if meleetime > 0 and CurTime() > meleetime and owner:KeyDown(IN_ATTACK2) then + if not owner:InVehicle() then + self:DealDamage() + end + self:SetNextMeleeAttack(0) + end + end + + self:NextThink(CurTime() + 0.03) + return true + +end + +------------------------------------------------- +-- JOB-SPECIFIC +------------------------------------------------- + +util.AddNetworkString 'dbg-revive' +net.Receive('dbg-revive', function(len, ply) + + local target = net.ReadEntity() + if not IsValid(target) or not target:IsPlayer() or not target:IsGhost() + or ply:IsGhost() or ply:Team() ~= TEAM_PRIEST or target:GetPos():DistToSqr(ply:GetPos()) > 6000 then + return + end + + local ghostBuffs = target:GetDBVar('ghostBuffs', 0) + if ghostBuffs >= 10 then + ply:Notify('error', 'Священник может помочь грешникам, но всему есть предел!') + return + end + target:SetDBVar('ghostBuffs', ghostBuffs + 1) + + target:SetNetVar('_SpawnTime', math.max((math.min(CurTime() + 5, target:GetNetVar('_SpawnTime', 0))), target:GetNetVar('_SpawnTime', 0) - 30)) + target:EmitSound('dbg/revive.ogg', 70, 100, 0.8) + local effectdata = EffectData() + effectdata:SetOrigin(target:GetPos() + Vector(0,0,45)) + effectdata:SetMagnitude(2.5) + effectdata:SetScale(2) + effectdata:SetRadius(3) + util.Effect('GlassImpact', effectdata, true, true) + +end) + +------------------------------------------------- +-- HOOKS +------------------------------------------------- + +function SWEP:CanUseAnimation() + + if self:GetHoldType() ~= 'normal' then + return false + end + +end + +hook.Add('AllowPlayerPickup', 'dbg_hands', function() + return false +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/shared.lua new file mode 100644 index 0000000..f5c7776 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_hands/shared.lua @@ -0,0 +1,73 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = "weapon_base" +SWEP.Category = L.dobrograd +SWEP.PrintName = L.hands +SWEP.Instructions = L.instruction_hand +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 62 +SWEP.ViewModel = "models/weapons/v_crowbar.mdl" +SWEP.WorldModel = "models/weapons/w_crowbar.mdl" +SWEP.HoldType = "normal" +SWEP.UseHands = true +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "ЛКМ - указать/тащить\nПКМ - боевая стойка\nR - переключить отображение прицела" +SWEP.Sound = "doors/door_latch3.wav" +SWEP.UseHands = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +SWEP.doNotDrag = { + func_breakable_surf = true, + prop_door_rotating = true, + func_door_rotating = true, + func_door = true, + func_breakable = true, + gmod_sent_vehicle_fphysics_wheel = true, +} + +function SWEP:SetupDataTables() + + self:NetworkVar( "Float", 0, "NextMeleeAttack" ) + +end + +function SWEP:Initialize() + + self.Weapon:DrawShadow(false) + self:SetHoldType(self.HoldType) + +end + +function SWEP:Holster() + + return true + +end + +function SWEP:Deploy() + + self:SetNextPrimaryFire( CurTime() + 0.5 ) + self:SetNextSecondaryFire( CurTime() + 0.5 ) + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/cl_init.lua new file mode 100644 index 0000000..58fb3fb --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/cl_init.lua @@ -0,0 +1,67 @@ +include 'shared.lua' +local headOffset = Vector(0,0,10) + +function SWEP:PrimaryAttack() + if not IsFirstTimePredicted() then return end + if IsValid(self.target) then netstream.Start('dbg-punisher.kick', self.target) end + self:SetNextPrimaryFire(CurTime() + 0.5) +end + +function SWEP:SecondaryAttack() + if not IsFirstTimePredicted() then return end + if IsValid(self.target) then netstream.Start('dbg-punisher.mute', self.target) end + self:SetNextSecondaryFire(CurTime() + 0.5) +end + +local sidCopied = 'SteamID игрока %s (%s) скопирован в буфер обмена' +SWEP.nextReload = 0 +function SWEP:Reload() + if not IsFirstTimePredicted() or CurTime() < self.nextReload then return end + local target = self.target + if not IsValid(target) then return end + local sid = target:SteamID() + SetClipboardText(sid) + octolib.notify.show(sidCopied:format(target:Name(), sid)) + self.nextReload = CurTime() + 0.5 +end + +hook.Add('Think', 'dbg-punisher', function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + local wep = ply:GetActiveWeapon() + if not (IsValid(wep) and wep:GetClass() == 'dbg_punisher') then return end + local target = ply:GetEyeTrace().Entity + if IsValid(target) and target:IsPlayer() then wep.target = target + else wep.target = nil end +end) + +SWEP.DrawWorldModel = octolib.func.no +SWEP.DrawWorldModelTranslucent = octolib.func.no + +local function drawText(txt, font, xOffset, yOffset) + draw.SimpleText(txt, font .. '-sh', xOffset, yOffset, color_black, TEXT_ALIGN_RIGHT) + draw.SimpleText(txt, font, xOffset, yOffset, color_white, TEXT_ALIGN_RIGHT) +end + +function SWEP:DrawHUD() + local sw = ScrW() + drawText('Режим пушки-наказушки', 'f4.medium', sw - 10, 5) + drawText('ЛКМ - кик', 'f4.normal', sw - 10, 45) + drawText('ПКМ - мут', 'f4.normal', sw - 10, 65) + drawText('R - SteamID', 'f4.normal', sw - 10, 85) + local target = self.target + if not IsValid(target) then return end + + cam.Start3D() + local mins, maxs = target:GetCollisionBounds() + render.DrawWireframeBox(target:GetPos(), target:GetAngles(), mins, maxs, color_red) + cam.End3D() + + local boneID = target:LookupBone('ValveBiped.Bip01_Head1') + if not boneID then return end + local pos = target:GetBonePosition(boneID) + pos:Add(headOffset) + local data = pos:ToScreen() + draw.SimpleText(target:Name(), 'dbg-hud.normal-sh', data.x, data.y, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(target:Name(), 'dbg-hud.normal', data.x, data.y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/init.lua new file mode 100644 index 0000000..3a78f33 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/init.lua @@ -0,0 +1,23 @@ +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' +include 'shared.lua' + +netstream.Hook('dbg-punisher.kick', function(ply, target) + if not (ply:IsAdmin() and IsValid(target) and target:IsPlayer()) then return end + serverguard.command.Run(ply, 'kick', true, target:SteamID(), 'Нарушение атмосферы') +end) + +netstream.Hook('dbg-punisher.mute', function(ply, target) + if not (ply:IsAdmin() and IsValid(target) and target:IsPlayer()) then return end + if not target:GetDBVar('sgMuted') then + serverguard.command.Run(ply, 'mute', true, target:SteamID(), 15) + serverguard.command.Run(ply, 'gag', true, target:SteamID(), 15) + ply:Notify('ooc', L.gag_and_mute:format(target:Name())) + target:Notify('ooc', ply:Name() .. L.gag_and_mute2) + else + serverguard.command.Run(ply, 'unmute', true, target:SteamID()) + serverguard.command.Run(ply, 'ungag', true, target:SteamID()) + ply:Notify('ooc', L.ungag_and_unmute:format(target:Name())) + target:Notify('ooc', ply:Name() .. L.ungag_and_unmute2) + end +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/shared.lua new file mode 100644 index 0000000..1fcbe54 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_punisher/shared.lua @@ -0,0 +1,58 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'weapon_base' +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Пушка-наказушка' +SWEP.Instructions = 'ЛКМ - кикнуть игрока\nПКМ - выдать мут игроку\nR - скопировать SteamID игрока' +SWEP.Slot = 4 +SWEP.SlotPos = 10 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true +SWEP.ViewModelFlip = false +SWEP.ViewModelFOV = 62 +SWEP.ViewModel = 'models/weapons/v_rpg.mdl' +SWEP.WorldModel = 'models/weapons/w_rocket_launcher.mdl' +SWEP.HoldType = 'normal' +SWEP.UseHands = true +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Author = 'Wani4ka' +SWEP.Contact = '4wk@wani4ka.ru' +SWEP.Purpose = 'Оперативное устранение игроков, нарушающих атмосферу на ивентах' +SWEP.UseHands = true +SWEP.Icon = octolib.icons.color('gun_rpg') + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +function SWEP:Initialize() + + self:DrawShadow(false) + self:SetHoldType('normal') + +end + +function SWEP:Holster() + return true +end + +function SWEP:Deploy() + local owner = self.Owner + if not (IsValid(owner) and (owner:query('Kick') or owner:query('Mute') or owner:query('Gag'))) then + if IsValid(owner) then owner:ConCommand('lastinv') end + return self:Remove() + end + self:SetNextPrimaryFire(CurTime() + 0.5) + self:SetNextSecondaryFire(CurTime() + 0.5) +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/cl_init.lua new file mode 100644 index 0000000..b3b49c1 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/cl_init.lua @@ -0,0 +1,74 @@ +include 'shared.lua' + +SWEP.PrimaryAttack = octolib.func.zero +SWEP.SecondaryAttack = octolib.func.zero +SWEP.Reload = octolib.func.zero + +function SWEP:GetCSModel() + + if not self.TypeData then return end + + local mdl = self.csmodel + if not IsValid(mdl) then + mdl = ClientsideModel(self.WorldModel) + + local pos, ang = unpack(self.TypeData) + local ply = self:GetOwner() + mdl:SetParent(ply, ply:LookupAttachment('anim_attachment_rh')) + mdl:SetLocalPos(pos) + mdl:SetLocalAngles(ang) + + self.csmodel = mdl + end + + return mdl + +end + +function SWEP:RemoveCSEnt() + if IsValid(self.csmodel) then self.csmodel:Remove() end +end + +function SWEP:DrawWorldModel() + + local mdl = self:GetNetVar('WorldModel') + if self.lastMdl ~= mdl then + self.WorldModel = mdl + self.TypeData = self.Types[mdl] + self.lastMdl = mdl + end + + self:GetCSModel() + +end + +function SWEP:Holster() + self:RemoveCSEnt() + return true +end + +SWEP.OwnerChanged = SWEP.RemoveCSEnt +SWEP.OnRemove = SWEP.RemoveCSEnt + +local lastAng +hook.Add('CreateMove', 'dbg_shield', function(cmd) + + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + if not IsValid(wep) or wep:GetClass() ~= 'dbg_shield' then + lastAng = nil + return + end + + local ang = cmd:GetViewAngles() + if not ply:Crouching() then + ang.p = math.Clamp(ang.p, -45, 5) + else + lastAng = lastAng or ang + ang = Angle(math.Clamp(ang.p, -35, -7), lastAng.y, lastAng.r) + end + + cmd:SetViewAngles(ang) + lastAng = ang + +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/init.lua new file mode 100644 index 0000000..c23838e --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/init.lua @@ -0,0 +1,157 @@ +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' +include 'shared.lua' + +function SWEP:Initialize() + + self.TypeData = self.Types[self.WorldModel] + self:SetNetVar('WorldModel', self.WorldModel) + +end + + +function SWEP:Deploy() + + self:SetHoldType(self.HoldType) + self:SetNextPrimaryFire(CurTime() + 1) + self:SetNextSecondaryFire(CurTime() + 1) + octolib.stopAnimations(self:GetOwner()) + +end + +function SWEP:Push() + + local ct = CurTime() + if ct < (self.nextPush or 0) then return end + + self:SetNextPrimaryFire(ct) + self:SetNextSecondaryFire(ct) + + local ply = self:GetOwner() + local tr = {} + tr.start = ply:GetShootPos() + tr.endpos = tr.start + ply:GetAimVector() * 100 + tr.filter = { ply, self, self.collider } + + local ent = util.TraceLine(tr).Entity + if not IsValid(ent) or not ent:IsPlayer() then return end + + local dir = ply:GetAimVector() + dir:Normalize() + dir.z = 0.3 + ent:SetVelocity(dir * 700) + + ent:ViewPunch(Angle(math.random(-5, 5), math.random(-5, 5), 0)) + ent:EmitSound('physics/body/body_medium_impact_soft'..math.random(1,7)..'.wav', 45) + + ply:DoAnimation(ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST) + self.nextPush = ct + 2 + +end +SWEP.PrimaryAttack = SWEP.Push +SWEP.SecondaryAttack = SWEP.Push + +function SWEP:Think() + + local pos, ang, mins, maxs = unpack(self.TypeData) + + local col = self.collider + if not IsValid(col) then + col = ents.Create 'collider' + self.collider = col + -- col:SetModel(self.WorldModel) + -- col:SetRenderMode(RENDERMODE_TRANSALPHA) + -- col:SetColor(0,0,0, 1) + -- col:SetNoDraw(true) + -- col:PhysicsInitBox(mins, maxs) + -- col:SetSolid(SOLID_VPHYSICS) + col:SetNetVar('weapon', self) + col:Spawn() + col:Activate() + col:Setup('model', self.WorldModel) + col:SetCustomCollisionCheck(true) + col:CollisionRulesChanged() + col:GetPhysicsObject():EnableMotion(false) + col.OnTakeDamage = function(_, dmg) self:OnTakeDamage(dmg) end + end + + local ply = self:GetOwner() + local vel = ply:GetVelocity() + + local att = ply:GetAttachment(ply:LookupAttachment('anim_attachment_rh')) + local propPos, propAng = LocalToWorld(pos, ang, att.Pos, att.Ang) + if ply:GetAimVector():Dot(vel) > -0.25 then + propPos:Add(vel * 0.1) + end + col:SetPos(propPos) + col:SetAngles(propAng) + + self:NextThink(CurTime() + 0.2) + +end + +function SWEP:OnTakeDamage(dmg) + + if dmg:IsExplosionDamage() then return end + + local ply = self:GetOwner() + if not ply:Crouching() then + ply:SetVelocity(dmg:GetDamageForce() / 80) + else + ply:SetVelocity(dmg:GetDamageForce() / 200) + end + + local effectdata = EffectData() + effectdata:SetOrigin(dmg:GetDamagePosition()) + effectdata:SetNormal(-dmg:GetDamageForce():GetNormalized()) + util.Effect(self.TypeData[3], effectdata) + +end + +function SWEP:Holster() + self:RemoveCollider() + return true +end + +function SWEP:RemoveCollider() + if IsValid(self.collider) then self.collider:Remove() end +end +SWEP.OwnerChanged = SWEP.RemoveCollider +SWEP.OnDrop = SWEP.RemoveCollider +SWEP.OnRemove = SWEP.RemoveCollider + +hook.Add('octolib.canUseAnimation', 'dbg_shield', function(ply) + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'dbg_shield' then + ply:Notify('warning', 'Ты не можешь это делать с щитом в руках') + return false + end + +end) + +hook.Add('EntityTakeDamage', 'dbg_shield', function(ply, dmg) + + if not ply:IsPlayer() or not dmg:IsExplosionDamage() then return end + + local tr = {} + tr.start = dmg:GetDamagePosition() + tr.endpos = ply:GetPos() + ply:OBBCenter() + -- tr.collisiongroup = COLLISION_GROUP_PLAYER + tr.filter = dmg:GetInflictor() + + local ent = util.TraceLine(tr).Entity + if not IsValid(ent) or ent == ply then return end + + local wep = ply:GetActiveWeapon() + if ent:GetClass() ~= 'collider' or ent:GetNetVar('weapon') ~= wep then return end + + if GAMEMODE.Config.DisallowDrop[wep:GetClass()] or ply:jobHasWeapon(wep:GetClass()) then + ply:SelectWeapon('dbg_hands') + else + ply:dropDRPWeapon(wep) + end + + return true + +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/shared.lua new file mode 100644 index 0000000..92014c7 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_shield/shared.lua @@ -0,0 +1,56 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'weapon_base' +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Баллистический щит' +SWEP.Instructions = '' +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.ViewModel = '' +SWEP.WorldModel = 'models/bshields/hshield.mdl' +SWEP.HoldType = 'melee2' +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +SWEP.NoHandDamageDrop = true +SWEP.Types = { + ['models/bshields/hshield.mdl'] = { Vector(-.5, 10.4, -2.7), Angle(4.4, 13.8, -13), 'MetalSpark' }, + ['models/bshields/rshield.mdl'] = { Vector(-1, 10.4, -4.5), Angle(4.4, 13.8, -13), 'GlassImpact' }, +} + +local function getShieldOwner(ent) + if not IsValid(ent) or ent:GetClass() ~= 'collider' then return false end + local wep = ent:GetNetVar('weapon') + if IsValid(wep) and wep:GetClass() == 'dbg_shield' then + return wep:GetOwner() + end +end + +local ignoreClasses = octolib.array.toKeys({'gmod_sent_vehicle_fphysics_base', 'gmod_sent_vehicle_fphysics_wheel'}) +hook.Add('ShouldCollide', 'octolib.collider', function(ent1, ent2) + + local ply1 = getShieldOwner(ent1) + local ply2 = getShieldOwner(ent2) + if not ply1 and not ply2 then return end + + -- no collide players with their own shields and other players' shields + if ply1 and (ply1 == ent2 or ignoreClasses[ent2:GetClass()] or (ent2:IsPlayer() and ent2:GetActiveWeaponClass() == 'dbg_shield')) + or ply2 and (ply2 == ent1 or ignoreClasses[ent1:GetClass()] or (ent1:IsPlayer() and ent1:GetActiveWeaponClass() == 'dbg_shield')) then + return false + end + +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/cl_init.lua new file mode 100644 index 0000000..73f177e --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/cl_init.lua @@ -0,0 +1,30 @@ +include 'shared.lua' + +SWEP.Slot = 5 +SWEP.SlotPos = 19 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +function SWEP:Holster() + + return true + +end + +function SWEP:Deploy() + + return true + +end + +function SWEP:OnRemove() + + self:Holster() + +end + +function SWEP:OnDrop() + + self:Holster() + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/init.lua new file mode 100644 index 0000000..43b46d8 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/init.lua @@ -0,0 +1,16 @@ +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' + +include 'shared.lua' + +function SWEP:Measure(tr) + + self.Owner:EmitSound('npc/turret_floor/shoot1.wav', 75, 200) + local ent = tr.Entity + if not IsValid(ent) or not ent.IsSimfphyscar then return end + if self.Owner:GetShootPos():DistToSqr(tr.HitPos) > 4000000 then return end + + local speed = math.Round(ent:GetVelocity():Length() * 0.0568182, 0) + self.Owner:Notify(L.result_speedometer:format(speed)) + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/shared.lua new file mode 100644 index 0000000..95593ee --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/dbg_speedometer/shared.lua @@ -0,0 +1,112 @@ +SWEP.Base = 'weapon_octo_base_pistol' +SWEP.PrintName = L.speedometer +SWEP.Author = 'chelog' +SWEP.Contact = '' +SWEP.Purpose = '' + +SWEP.ViewModelFOV = 50 +SWEP.ViewModelFlip = false +SWEP.ViewModel = Model('models/weapons/v_crowbar.mdl') +SWEP.WorldModel = Model('models/weapons/w_toolgun.mdl') + +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Ammo = 'none' +SWEP.Primary.Automatic = false + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' + +SWEP.MuzzlePos = Vector(10, -0.7, 5) +SWEP.MuzzleAng = Angle(-3, -1, 0) + +SWEP.Charge = 100 +SWEP.CanScare = false +SWEP.IsLethal = false + +function SWEP:DoEffect(tr) + + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) + self.Owner:SetAnimation(PLAYER_ATTACK1) + + -- local effectdata = EffectData() + -- effectdata:SetOrigin(tr.HitPos) + -- effectdata:SetStart(self.Owner:GetShootPos()) + -- effectdata:SetAttachment(1) + -- effectdata:SetEntity(self) + -- util.Effect('ToolTracer', effectdata) + +end + +function SWEP:PrimaryAttack() + + local ct = CurTime() + if not IsFirstTimePredicted() or not self:CanFire() or + (self.nextFire or 0) > ct or self:GetNextPrimaryFire() > ct + then return end + + if self.Charge < 100 then + if SERVER then + self.Owner:EmitSound('weapons/clipempty_pistol.wav', 60) + end + + self.nextFire = ct + 1 + self:SetNextPrimaryFire(self.nextFire) + return + end + self.Charge = 0 + + self.Owner:LagCompensation(true) + local shootPos, shootDir = self:GetShootPosAndDir() + local tr = util.TraceLine({ + start = shootPos, + endpos = shootPos + shootDir * 32768, + filter = self.Owner, + }) + self.Owner:LagCompensation(false) + + self:DoEffect(tr) + + if SERVER then self:Measure(tr) end + +end + +function SWEP:Think() + + self.BaseClass.Think(self) + if SERVER or (CLIENT and IsFirstTimePredicted()) then + local oldCharge = self.Charge + self.Charge = math.min(self.Charge + FrameTime() * 20, 100) + if oldCharge ~= 100 and self.Charge >= 100 then self.Owner:EmitSound('ambient/energy/spark' .. math.random(1,4) .. '.wav', 60, 100, 0.3) end + end + +end + +function SWEP:FailAttack() + + self:SetNextSecondaryFire(CurTime() + 0.5) + self.Owner:EmitSound('Weapon_Pistol.Empty') + +end + +function SWEP:Reload() + + -- nothing + +end + +local shoulddisable = {} +shoulddisable[21] = true +shoulddisable[5003] = true +shoulddisable[6001] = true + +function SWEP:FireAnimationEvent(pos, ang, event, options) + + if shoulddisable[event] then return true end + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/gmod_camera.lua b/garrysmod/addons/gmod-sweps/lua/weapons/gmod_camera.lua new file mode 100644 index 0000000..cc6d91c --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/gmod_camera.lua @@ -0,0 +1,239 @@ + +AddCSLuaFile() + +SWEP.ViewModel = Model( "models/weapons/c_arms_animations.mdl" ) +SWEP.WorldModel = Model( "models/MaxOfS2D/camera.mdl" ) + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = "none" + + +SWEP.PrintName = L.camera + +SWEP.Slot = 5 +SWEP.SlotPos = 1 + +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.Spawnable = true +SWEP.Icon = 'octoteam/icons/camera.png' +SWEP.ShootSound = Sound( "NPC_CScanner.TakePhoto" ) + +if ( SERVER ) then + + SWEP.AutoSwitchTo = false + SWEP.AutoSwitchFrom = false + + -- + -- A concommand to quickly switch to the camera + -- + concommand.Add( "gmod_camera", function( player, command, arguments ) + + player:SelectWeapon( "gmod_camera" ) + + end ) + +end + +-- +-- Network/Data Tables +-- +function SWEP:SetupDataTables() + + self:NetworkVar( "Float", 0, "Zoom" ) + self:NetworkVar( "Float", 1, "Roll" ) + + if ( SERVER ) then + self:SetZoom( 70 ) + self:SetRoll( 0 ) + end + +end + +-- +-- Initialize Stuff +-- +function SWEP:Initialize() + + self:SetHoldType('normal') + +end + +-- +-- Reload resets the FOV and Roll +-- +function SWEP:Reload() + + if ( !self.Owner:KeyDown( IN_ATTACK2 ) ) then self:SetZoom( self.Owner:IsBot() && 75 || self.Owner:GetInfoNum( "fov_desired", 75 ) ) end + self:SetRoll( 0 ) + +end + +-- +-- PrimaryAttack - make a screenshot +-- +function SWEP:PrimaryAttack() + + self:SetHoldType('camera') + timer.Simple(0.1, function() + self:DoShootEffect() + timer.Create('camera' .. self.Owner:SteamID(), 0.5, 1, function() + if IsValid(self) then + self:SetHoldType('normal') + end + end) + + if CLIENT and not IsFirstTimePredicted() then self.Owner:ConCommand('jpeg') end + end) + + local ct = CurTime() + self:SetNextPrimaryFire(ct + 2) + +end + +-- +-- SecondaryAttack - Nothing. See Tick for zooming. +-- +function SWEP:SecondaryAttack() +end + +-- +-- Mouse 2 action +-- +function SWEP:Tick() + + if ( CLIENT && self.Owner != LocalPlayer() ) then return end -- If someone is spectating a player holding this weapon, bail + + local cmd = self.Owner:GetCurrentCommand() + + if ( !cmd:KeyDown( IN_ATTACK2 ) ) then return end -- Not holding Mouse 2, bail + + self:SetZoom( math.Clamp( self:GetZoom() + cmd:GetMouseY() * 0.1, 0.1, 175 ) ) -- Handles zooming + self:SetRoll( self:GetRoll() + cmd:GetMouseX() * 0.025 ) -- Handles rotation + +end + +-- +-- Override players Field Of View +-- +function SWEP:TranslateFOV( current_fov ) + + return self:GetZoom() + +end + +-- +-- Deploy - Allow lastinv +-- +function SWEP:Deploy() + + return true + +end + +-- +-- Set FOV to players desired FOV +-- +function SWEP:Equip() + + if ( self:GetZoom() == 70 && self.Owner:IsPlayer() && !self.Owner:IsBot() ) then + self:SetZoom( self.Owner:GetInfoNum( "fov_desired", 75 ) ) + end + +end + +function SWEP:ShouldDropOnDie() return false end + +-- +-- The effect when a weapon is fired successfully +-- +function SWEP:DoShootEffect() + + self:EmitSound(self.ShootSound, 35) + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self.Owner:SetAnimation( PLAYER_ATTACK1 ) + + if ( SERVER && !game.SinglePlayer() ) then + + -- + -- Note that the flash effect is only + -- shown to other players! + -- + + local vPos = self.Owner:GetShootPos() + local vForward = self.Owner:GetAimVector() + + local trace = {} + trace.start = vPos + trace.endpos = vPos + vForward * 256 + trace.filter = self.Owner + + -- local tr = util.TraceLine( trace ) + + -- local effectdata = EffectData() + -- effectdata:SetOrigin( tr.HitPos ) + -- util.Effect( "camera_flash", effectdata, true ) + + end + +end + +function SWEP:Holster() + self:SetHoldType('normal') + return true +end + +if ( SERVER ) then return end -- Only clientside lua after this line + +SWEP.WepSelectIcon = surface.GetTextureID( "vgui/gmod_camera" ) + +-- Don't draw the weapon info on the weapon selection thing +function SWEP:DrawHUD() end +function SWEP:PrintWeaponInfo( x, y, alpha ) end + +function SWEP:HUDShouldDraw( name ) + + -- So we can change weapons + return name == 'CHudWeaponSelection' + +end + +function SWEP:FreezeMovement() + + -- Don't aim if we're holding the right mouse button + if ( self.Owner:KeyDown( IN_ATTACK2 ) || self.Owner:KeyReleased( IN_ATTACK2 ) ) then + return true + end + + return false + +end + +function SWEP:CalcView( ply, origin, angles, fov ) + + if ( self:GetRoll() != 0 ) then + angles.Roll = self:GetRoll() + end + + return origin, angles, fov + +end + +function SWEP:AdjustMouseSensitivity() + + if ( self.Owner:KeyDown( IN_ATTACK2 ) ) then return 1 end + + return self:GetZoom() / 80 + +end + +function SWEP:CanUseAnimation() + return false +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/guitar.lua b/garrysmod/addons/gmod-sweps/lua/weapons/guitar.lua new file mode 100644 index 0000000..8a2a9b8 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/guitar.lua @@ -0,0 +1,87 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'octolib_custom' +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Гитара' +SWEP.Instructions = '' +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.ViewModel = '' +SWEP.HoldType = 'slam' +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +SWEP.WorldModel = 'models/custom/guitar/m_d_45.mdl' +SWEP.WorldModelAtt = 'anim_attachment_lh' +SWEP.WorldModelPos = Vector(-9.1, -9.6, -37.0) +SWEP.WorldModelAng = Angle(-3.9, -218.3, 19.1) +SWEP.WorldModelBones = { + ['ValveBiped.Bip01_R_Clavicle'] = Angle(8.8, 12.5, 27.6), + ['ValveBiped.Bip01_R_UpperArm'] = Angle(11.9, 4.1, -5.5), + ['ValveBiped.Bip01_R_Forearm'] = Angle(-32.1, 8.5, 14.2), + ['ValveBiped.Bip01_R_Hand'] = Angle(-3.4, -56.4, 0.0), + ['ValveBiped.Bip01_L_Clavicle'] = Angle(-17.3, 2.2, 12.8), + ['ValveBiped.Bip01_L_UpperArm'] = Angle(-12.3, -9.8, -55.9), + ['ValveBiped.Bip01_L_Forearm'] = Angle(12.8, -82.2, -26.1), + ['ValveBiped.Bip01_L_Hand'] = Angle(-46.5, -14.6, -52.5), +} + +local sounds = {} +for i = 1, 16 do table.insert(sounds, 'weapons/guitar/guitar_' .. i .. '.mp3') end + +if SERVER then +function SWEP:StopMusic() + if self.sound and self.sound:IsPlaying() then self.sound:Stop() end +end +end + +function SWEP:PrimaryAttack() + self.Owner:SetAnimation(PLAYER_ATTACK1) + if SERVER then + self:StopMusic() + self.sound = CreateSound(self, sounds[math.random(1, 8)]) + self.sound:Play() + end + self:SetNextPrimaryFire(CurTime() + 1) + self:SetNextSecondaryFire(CurTime() + 1) +end + +function SWEP:SecondaryAttack() + self.Owner:SetAnimation(PLAYER_ATTACK1) + if SERVER then + self:StopMusic() + self.sound = CreateSound(self, sounds[math.random(9, 16)]) + self.sound:Play() + end + self:SetNextPrimaryFire(CurTime() + 1) + self:SetNextSecondaryFire(CurTime() + 1) +end + +function SWEP:Reload() + local ct = CurTime() + if (self.nextReload or 0) > ct then return end + self.Owner:SetAnimation(PLAYER_ATTACK1) + if SERVER then self:StopMusic() end + self.nextReload = ct + 1 +end + +function SWEP:OnRemove() + if CLIENT then + self:RemoveModel() + else self:StopMusic() end + return true +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/guitar_electric.lua b/garrysmod/addons/gmod-sweps/lua/weapons/guitar_electric.lua new file mode 100644 index 0000000..d0f6240 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/guitar_electric.lua @@ -0,0 +1,87 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'octolib_custom' +SWEP.Category = L.dobrograd +SWEP.PrintName = 'Гитара - Электронная' +SWEP.Instructions = '' +SWEP.Slot = 1 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.ViewModel = '' +SWEP.HoldType = 'slam' +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +SWEP.WorldModel = 'models/props_phx/misc/fender.mdl' +SWEP.WorldModelAtt = 'anim_attachment_lh' +SWEP.WorldModelPos = Vector(-2.5, -6.6, -9.6) +SWEP.WorldModelAng = Angle(112.5, 44.6, -16.0) +SWEP.WorldModelBones = { + ['ValveBiped.Bip01_R_Clavicle'] = Angle(8.8, 12.5, 27.6), + ['ValveBiped.Bip01_R_UpperArm'] = Angle(11.9, 4.1, -5.5), + ['ValveBiped.Bip01_R_Forearm'] = Angle(-32.1, 8.5, 14.2), + ['ValveBiped.Bip01_R_Hand'] = Angle(-3.4, -56.4, 0.0), + ['ValveBiped.Bip01_L_Clavicle'] = Angle(-17.3, 2.2, 12.8), + ['ValveBiped.Bip01_L_UpperArm'] = Angle(-12.3, -9.8, -55.9), + ['ValveBiped.Bip01_L_Forearm'] = Angle(12.8, -82.2, -26.1), + ['ValveBiped.Bip01_L_Hand'] = Angle(-46.5, -14.6, -52.5), +} + +local sounds = {} +for i = 1, 11 do table.insert(sounds, 'weapons/guitar_electric/guitar_' .. i .. '.mp3') end + +if SERVER then +function SWEP:StopMusic() + if self.sound and self.sound:IsPlaying() then self.sound:Stop() end +end +end + +function SWEP:PrimaryAttack() + self.Owner:SetAnimation(PLAYER_ATTACK1) + if SERVER then + self:StopMusic() + self.sound = CreateSound(self, sounds[math.random(1, 6)]) + self.sound:Play() + end + self:SetNextPrimaryFire(CurTime() + 1) + self:SetNextSecondaryFire(CurTime() + 1) +end + +function SWEP:SecondaryAttack() + self.Owner:SetAnimation(PLAYER_ATTACK1) + if SERVER then + self:StopMusic() + self.sound = CreateSound(self, sounds[math.random(7, 11)]) + self.sound:Play() + end + self:SetNextPrimaryFire(CurTime() + 1) + self:SetNextSecondaryFire(CurTime() + 1) +end + +function SWEP:Reload() + local ct = CurTime() + if (self.nextReload or 0) > ct then return end + self.Owner:SetAnimation(PLAYER_ATTACK1) + if SERVER then self:StopMusic() end + self.nextReload = ct + 1 +end + +function SWEP:OnRemove() + if CLIENT then + self:RemoveModel() + else self:StopMusic() end + return true +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/cl_init.lua new file mode 100644 index 0000000..22c820f --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/cl_init.lua @@ -0,0 +1,158 @@ +include 'shared.lua' + +SWEP.PrintName = 'Octo Camera' +SWEP.Slot = 5 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.Spawnable = true +SWEP.AdminSpawnable = true + +-- local matLogo = Material('octoteam/icons/clock.png') + +-- local url = 'https://i.imgur.com/hJG1POs.png' +-- local name = url:gsub('.+%/', '') +-- local pathFile = 'imgscreen/' .. name +-- local pathImg = '../data/' .. pathFile + +-- http.Fetch(url, function(content) +-- file.Write(pathFile, content) +-- local matName = pathImg:gsub('%.png', '') +-- RunConsoleCommand('mat_reloadmaterial', matName) +-- matLogo = Material(pathImg) +-- end) + +local cvLerpPos, cvLerpAng, cvLerpFov = GetConVar('octocam_lerp_pos'), GetConVar('octocam_lerp_ang'), GetConVar('octocam_lerp_fov') + +function SWEP:ResetVariables() + + local ply = self:GetOwner() + self.lastPos = ply:GetShootPos() + self.lastAng = ply:EyeAngles() + self.lastFov = ply:GetInfoNum('fov_desired', 75) + self.tgtFov = self.lastFov + +end + +function SWEP:PrimaryAttack() + -- nothing +end + +function SWEP:SecondaryAttack() + -- nothing +end + +function SWEP:Reload() + self:ResetVariables() +end + +-- local poses = { +-- -- { Vector(28, 0, 0), Angle(90, 0, 0) }, +-- { Vector(-21.2, 7, 6.1), Angle(90, 90, 0) }, +-- { Vector(-21.2, -7.5, 6.1), Angle(90, -90, 0) }, +-- } + +-- hook.Add('PostDrawTranslucentRenderables', 'octocamera', function() + +-- local lp = LocalPlayer() +-- for _, ply in ipairs(player.GetAll()) do +-- local wep = ply:GetActiveWeapon() +-- if not IsValid(wep) or wep:GetClass() ~= 'octo_camera' or ply == lp then continue end + +-- local ent = wep.visEnt +-- if not IsValid(ent) then +-- ent = ClientsideModel('models/tools/camera/camera.mdl') +-- ent:SetModelScale(3, 0) +-- -- ent:SetNoDraw(true) +-- wep.visEnt = ent +-- end + +-- wep:Lerp() +-- ent:SetPos(wep.lastPos) +-- ent:SetAngles(wep.lastAng) +-- -- ent:DrawModel() + +-- for _, pose in ipairs(poses) do +-- local pos, ang = LocalToWorld(pose[1], pose[2], wep.lastPos, wep.lastAng) +-- cam.Start3D2D(pos, ang, 0.12) +-- surface.SetMaterial(matLogo) +-- surface.SetDrawColor(255,255,255, 255) +-- surface.DrawTexturedRect(-64, -64, 128, 128) +-- cam.End3D2D() +-- end +-- end + +-- end) + +function SWEP:DrawWorldModel() + -- do not draw WM +end + +function SWEP:OnRemove() + + if IsValid(self.visEnt) then + self.visEnt:Remove() + end + +end + +function SWEP:Deploy() + self:ResetVariables() +end + +function SWEP:Holster() + + if IsValid(self.visEnt) then + self.visEnt:Remove() + end + + return true + +end + +function SWEP:Lerp() + + local ct = CurTime() + if ct <= (self.lerpAfter or 0) then return end + + local ply = self:GetOwner() + if not self.lastPos then + self:ResetVariables() + end + + local ft = FrameTime() + self.lastPos = LerpVector(ft / cvLerpPos:GetFloat(), self.lastPos, ply:GetShootPos()) + self.lastAng = LerpAngle(ft / cvLerpAng:GetFloat(), self.lastAng, ply:EyeAngles()) + self.lastFov = Lerp(ft / cvLerpFov:GetFloat(), self.lastFov, self.tgtFov) + + self.lerpAfter = ct + +end + +function SWEP:CalcView() + + if not self:GetNetVar('filming') then return end + + self:Lerp() + return self.lastPos, self.lastAng, self.lastFov + +end + +function SWEP:Tick() + + if not self:GetNetVar('filming') then return end + + local cmd = self:GetOwner():GetCurrentCommand() + local delta = -cmd:GetMouseWheel() * 0.25 + local v = math.min(self.tgtFov + delta, self.tgtFov) + if v < 20 then delta = delta / (20 / v) end + + self.tgtFov = math.Clamp(self.tgtFov + delta, 1, 120) + +end + +local show = octolib.array.toKeys {'CHudChat'} +function SWEP:HUDShouldDraw(name) + if not self:GetNetVar('filming') then return true end + return show[name] +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/init.lua new file mode 100644 index 0000000..a5144aa --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/init.lua @@ -0,0 +1,48 @@ +include 'shared.lua' +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' + +function SWEP:PrimaryAttack() + -- nothing +end + +function SWEP:SecondaryAttack() + -- nothing +end + +function SWEP:Reload() + + local ct = CurTime() + if ct < (self.nextReload or 0) then return end + + local newVal = not self:GetNetVar('filming') + self:SetNetVar('filming', newVal) + + self.nextReload = ct + 0.5 + +end + +function SWEP:HideOwner(hide) + + local ply = self:GetOwner() + if not IsValid(ply) then return end + ply:MakeInvisible(hide) + +end + +function SWEP:Deploy() + self:HideOwner(true) +end + +function SWEP:Holster() + self:HideOwner(false) + return true +end + +function SWEP:OnRemove() + self:HideOwner(false) +end + +hook.Add('canDropWeapon', 'octocamera', function(ply, wep) + if wep:GetClass() == 'octo_camera' then return false end +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/shared.lua new file mode 100644 index 0000000..70615d7 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/octo_camera/shared.lua @@ -0,0 +1,58 @@ +SWEP.Author = 'chelog' +SWEP.Contact = '' +SWEP.Purpose = '' +SWEP.Instructions = [[R - вкл/выкл +Колесико - зум +Shift - быстро +Ctrl - медленно +Space - вверх +Alt - вниз]] + +SWEP.HoldType = 'camera' + +SWEP.ViewModel = 'models/weapons/c_arms_animations.mdl' +SWEP.WorldModel = 'models/tools/camera/camera.mdl' + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = 'none' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' + +local cvSpeed = GetConVar('octocam_speed') +local vector_up = Vector(0, 0, 1) + +hook.Add('SetupMove', 'octocamera', function(ply, mv, cmd) + local wep = ply:GetActiveWeapon() + if ply:GetMoveType() ~= MOVETYPE_NOCLIP or not IsValid(wep) or wep:GetClass() ~= 'octo_camera' or not wep:GetNetVar('filming') then return end + + local speed = (mv:KeyDown(IN_SPEED) and 0.3 or mv:KeyDown(IN_DUCK) and 0.02 or 0.1) * 5000 + if SERVER then + speed = speed * ply:GetInfoNum('octocam_speed', 1) + elseif ply == LocalPlayer() then + speed = speed * cvSpeed:GetFloat() + end + + if mv:KeyDown(IN_JUMP) then + cmd:SetUpMove(10000) + mv:SetButtons(bit.bxor(mv:GetButtons(), IN_JUMP)) + elseif mv:KeyDown(IN_WALK) then + cmd:SetUpMove(-10000) + mv:SetButtons(bit.bxor(mv:GetButtons(), IN_WALK)) + end + + local ang = ply:EyeAngles() + local moveVector = + ang:Forward() * cmd:GetForwardMove() + + ang:Right() * cmd:GetSideMove() + + vector_up * cmd:GetUpMove() + + mv:SetForwardSpeed(0) + mv:SetSideSpeed(0) + mv:SetUpSpeed(0) + mv:SetOrigin(ply:GetPos() + FrameTime() * moveVector:GetNormalized() * speed) +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/stungun/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/stungun/cl_init.lua new file mode 100644 index 0000000..ac66bbc --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/stungun/cl_init.lua @@ -0,0 +1,16 @@ + +--[[ +Stungun SWEP Created by Donkie (http://steamcommunity.com/id/Donkie/) +For personal/server usage only, do not resell or distribute! +]] + +include("shared.lua") + +SWEP.Slot = 5 +SWEP.SlotPos = 20 +SWEP.DrawAmmo = (not SWEP.InfiniteAmmo) +SWEP.DrawCrosshair = true + +hook.Add('dbg-view.override', 'stungun', function() + if LocalPlayer():GetNetVar('Tased') then return false end +end) diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/stungun/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/stungun/init.lua new file mode 100644 index 0000000..3f1eb2d --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/stungun/init.lua @@ -0,0 +1,642 @@ + +--[[ +Stungun SWEP Created by Donkie (http://steamcommunity.com/id/Donkie/) +For personal/server usage only, do not resell or distribute! +]] + +AddCSLuaFile("shared.lua") +AddCSLuaFile("config/stungun.lua") +AddCSLuaFile("cl_init.lua") + +include("shared.lua") + +local ragdolls = {} + +function SWEP:Equip( ply ) + self.BaseClass.Equip(self,ply) + self.lastowner = ply +end + +util.AddNetworkString("tazerondrop") +function SWEP:OnDrop() + self.BaseClass.OnDrop(self) + if IsValid(self.lastowner) then + net.Start("tazerondrop") + net.WriteEntity(self) + net.Send(self.lastowner) + end +end + + +--[[ +Makes a hull trace the size of a player. +]] +local hulltrdata = {} +function STUNGUN.PlayerHullTrace(pos, ply, filter) + hulltrdata.start = pos + hulltrdata.endpos = pos + hulltrdata.filter = filter + + return util.TraceEntity( hulltrdata, ply ) +end + +--[[ +Attemps to place the player at this position or as close as possible. +]] +-- Directions to check +local directions = { + Vector(0,0,0), Vector(0,0,1), -- Center and up + Vector(1,0,0), Vector(-1,0,0), Vector(0,1,0), Vector(0,-1,0) -- All cardinals + } +for deg = 45, 315, 90 do -- Diagonals + local r = math.rad(deg) + table.insert(directions, Vector(math.Round(math.cos(r)), math.Round(math.sin(r)), 0)) +end + +local magn = 15 -- How much increment for each iteration +local iterations = 2 -- How many iterations +function STUNGUN.PlayerSetPosNoBlock( ply, pos, filter ) + local tr + + local dirvec + local m = magn + local i = 1 + local its = 1 + repeat + dirvec = directions[i] * m + i = i + 1 + if i > #directions then + its = its + 1 + i = 1 + m = m + magn + if its > iterations then + ply:SetPos(pos) -- We've done as many checks as we wanted, lets just force him to get stuck then. + return false + end + end + + tr = STUNGUN.PlayerHullTrace(dirvec + pos, ply, filter) + until tr.Hit == false + + ply:SetPos(pos + dirvec) + return true +end + +--[[ +Sets the player invisible/visible +]] +function STUNGUN.PlayerInvis( ply, bool ) + ply:SetNoDraw(bool) + ply:DrawShadow(not bool) + ply:SetCollisionGroup(bool and COLLISION_GROUP_IN_VEHICLE or COLLISION_GROUP_PLAYER) + ply:SetNotSolid(bool) + ply:DrawWorldModel(not bool) + ply._stungunfrozen = bool + + if bool then + ply:Lock() + else + ply:UnLock() + end +end + +octolib.func.loop(function(done) + octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + if IsValid(ply) and ply._stungunfrozen then + ply:DrawWorldModel(false) + end + end):Then(done) +end) + +--[[ +Deploy player ragdoll +]] +function STUNGUN.Ragdoll( ply, pushdir ) + local plyphys = ply:GetPhysicsObject() + local plyvel = Vector(0,0,0) + if plyphys:IsValid() then + plyvel = plyphys:GetVelocity() + end + + ply.tazedpos = ply:GetPos() -- Store pos incase the ragdoll is missing when we're to unrag him. + + if IsValid(ply:GetVehicle()) then + ply:ExitVehicle() + end + + local weapon = ply:GetActiveWeapon() + if IsValid(weapon) and not (GAMEMODE.Config.DisallowDrop[ weapon:GetClass() ] or ply:jobHasWeapon(weapon:GetClass())) then + ply:dropDRPWeapon( weapon ) + end + + local mdl, sk, bg = ply:GetModel(), ply:GetSkin(), ply:GetBodyGroups() + if STUNGUN.BrokenModels[mdl] then + mdl, sk, bg = STUNGUN.DefaultModel, 0, '0' + end + + local rag = ents.Create("prop_ragdoll") + rag:SetModel(mdl) + rag:SetSkin(sk) + rag:SetBodyGroups(bg) + rag:SetPos(ply:GetPos()) + rag:SetAngles(Angle(0,ply:GetAngles().y,0)) + rag:SetColor(ply:GetColor()) + rag:SetMaterial(ply:GetMaterial()) + rag:Spawn() + rag:Activate() + rag:SetCollisionGroup(COLLISION_GROUP_WEAPON) + ragdolls[rag] = true + + if not IsValid(rag:GetPhysicsObject()) then + SafeRemoveEntity(rag) + + if STUNGUN.DefaultModel then + rag = ents.Create("prop_ragdoll") + rag:SetModel(STUNGUN.DefaultModel) + rag:SetPos(ply:GetPos()) + rag:SetAngles(Angle(0,ply:GetAngles().y,0)) + rag:SetColor(ply:GetColor()) + rag:SetMaterial(ply:GetMaterial()) + rag:Spawn() + rag:Activate() + else + MsgN("A tazed player didn't get a valid ragdoll. Model (" .. ply:GetModel() .. ")!") + return false + end + end + + rag.tazesnd = CreateSound(rag, "stungun/tazer.wav") + rag.tazesnd:PlayEx(1, 70) + + -- Lower inertia makes the ragdoll have trouble rolling. Citizens have 1,1,1 as default, while combines have 0.2,0.2,0.2. + rag:GetPhysicsObject():SetInertia(Vector(1,1,1)) + + -- Set mass of all limbs, forces and shit are weird if mass is not same. + -- for i = 1, rag:GetPhysicsObjectCount() do + -- if IsValid(rag:GetPhysicsObject(i-1)) then + -- rag:GetPhysicsObject(i-1):SetMass(12.7) + -- end + -- end + + -- Push him back abit + plyvel = plyvel + pushdir * 50 + + -- Code copied from TTT + local num = rag:GetPhysicsObjectCount() - 1 + for i = 0, num do + local bone = rag:GetPhysicsObjectNum(i) + if IsValid(bone) then + local bp, ba = ply:GetBonePosition(rag:TranslatePhysBoneToBone(i)) + if bp and ba then + bone:SetPos(bp) + bone:SetAngles(ba) + end + + bone:SetVelocity(plyvel) + end + end + + -- Prevents any kind of pickup if user don't want him to + rag.CanPickup = STUNGUN.CanPickup + + -- Handcuff support + local cuffs = ply:GetWeapon("weapon_cuffed") + if IsValid(cuffs) then + -- if cuffs:GetIsLeash() then + -- rag.isleashed = true + -- rag.leashowner = cuffs:GetKidnapper() + -- rag.ropelength = cuffs:GetRopeLength() + -- else + rag.iscuffed = true + -- end + + -- rag:SetNWBool("cuffs_isleash", rag.isleashed) + end + + -- Make him follow the ragdoll, if the player gets away from the ragdoll he won't get stuff rendered properly. + ply:SetParent(rag) + + -- Make the player invisible. + STUNGUN.PlayerInvis(ply, true) + + ply.tazeragdoll = rag + rag.tazeplayer = ply + rag:SetDTEntity(1, ply) -- Used to gain instant access to player on client + + ply:SetNWEntity("tazerviewrag", rag) + rag:SetNWEntity("plyowner", ply) + ply:SetNWBool("tazefrozen", true) + ply:SetNetVar('DeathRagdoll', rag) + + return true +end + +function STUNGUN.UnRagdoll( ply ) + local ragvalid = IsValid(ply.tazeragdoll) + local pos + if ragvalid then -- Sometimes the ragdoll is missing when we want to unrag, not good! + if ply.tazeragdoll.hasremoved then return end -- It has already been removed. + + pos = ply.tazeragdoll:GetPos() + -- ply:SetModel(ply.tazeragdoll:GetModel()) + if ply.tazeragdoll.tazesnd then + ply.tazeragdoll.tazesnd:Stop() + ply.tazeragdoll.tazesnd = nil + end + ply.tazeragdoll.hasremoved = true + else + pos = ply.tazedpos -- Put him at the place he got tazed, works great. + end + ply:SetParent() + + STUNGUN.PlayerSetPosNoBlock(ply, pos, {ply, ply.tazeragdoll}) + + timer.Simple(0,function() + SafeRemoveEntity(ply.tazeragdoll) + STUNGUN.PlayerInvis(ply, false) + end) + + net.Start("tazeendview") + net.Send(ply) +end + + +util.AddNetworkString("tazestartview") +util.AddNetworkString("tazeendview") + +function STUNGUN.Electrolute( ply, pushdir ) + if ply.tazeimmune then return end + + -- Ragdoll + STUNGUN.Ragdoll(ply, pushdir) + + -- Gag + ply.tazeismuted = true + + ply:SetNetVar('Tased', true) + + local id = ply:UserID() + timer.Create("Unelectrolute" .. id, STUNGUN.ParalyzedTime, 1, function() + if IsValid(ply) then STUNGUN.UnElectrolute( ply ) end + end) + timer.Create("tazeUngag" .. id, STUNGUN.MuteTime, 1, function() + if IsValid(ply) then STUNGUN.UnMute( ply ) end + end) + + timer.Create("HurtingTimer" .. id,2,0,function() + if not IsValid(ply) or not IsValid(ply.tazeragdoll) then timer.Remove("HurtingTimer" .. id) return end + ply.tazeragdoll:EmitSound(STUNGUN.PlayHurtSound(ply), 70, 100, 1) + end) + + hook.Call("PlayerHasBeenTazed", GAMEMODE, ply, ply.tazeragdoll) +end + +function STUNGUN.UnMute( ply ) + ply.tazeismuted = false +end + +function STUNGUN.UnElectrolute( ply ) + STUNGUN.UnRagdoll( ply ) + timer.Remove("HurtingTimer" .. ply:UserID()) + + hook.Call("PlayerUnTazed", GAMEMODE, ply) + + local unfreezef = function() + ply:SetNWBool("tazefrozen", false) + + hook.Call("PlayerTazeUnFrozen", GAMEMODE, ply) + end + if (STUNGUN.FreezeTime or 0) > 0 then + timer.Create("StungunPlayerFreeze" .. ply:UserID(), STUNGUN.FreezeTime, 1, unfreezef) + else + unfreezef() + end + + if STUNGUN.Immunity > 0 then + ply.tazeimmune = true + timer.Simple(STUNGUN.Immunity, function() + if IsValid(ply) then + ply.tazeimmune = false + end + end) + end + + ply:SetNetVar('Tased', nil) +end +STUNGUN.Unelectrolute = STUNGUN.UnElectrolute + +hook.Add("PlayerSay", "Tazer", function(ply, str) + if ply.tazeismuted then return "" end +end) + +util.AddNetworkString("tazersendhealth") +hook.Add("Think", "Tazer", function() + for ragdoll, _ in pairs(ragdolls) do + if IsValid(ragdoll) and IsValid(ragdoll.tazeplayer) then + local v = ragdoll.tazeplayer + local mode = 0 + if STUNGUN.PhysEffect then + mode = STUNGUN.PhysEffect + elseif STUNGUN.ShouldRoll != nil then + mode = STUNGUN.ShouldRoll and 1 or 0 + end + + if mode > 0 then + local rag = ragdoll + local phys = rag:GetPhysicsObjectNum(0) + if phys:IsValid() then + if mode == 1 then + phys:AddAngleVelocity(Vector(0,math.sin(CurTime()) * 1200 * FrameTime(),0)) + elseif mode == 2 then + local vel = VectorRand() * 5 + for i = 1, rag:GetPhysicsObjectCount() do + if IsValid(rag:GetPhysicsObject(i-1)) then + rag:GetPhysicsObjectNum(i-1):AddVelocity(vel) + end + end + end + + -- Pulls the hands together if he's cuffed + if rag.iscuffed then + local lhandbonenum = rag:LookupBone("ValveBiped.Bip01_L_Hand") + local rhandbonenum = rag:LookupBone("ValveBiped.Bip01_R_Hand") + if lhandbonenum and rhandbonenum then + local lhandnum = rag:TranslateBoneToPhysBone(lhandbonenum) + local rhandnum = rag:TranslateBoneToPhysBone(rhandbonenum) + + if lhandnum and rhandnum then + local lhand = rag:GetPhysicsObjectNum(lhandnum) + local rhand = rag:GetPhysicsObjectNum(rhandnum) + + if lhand and rhand then + local vel = (rhand:GetPos() - lhand:GetPos()) * 2 + lhand:AddVelocity(vel) + rhand:AddVelocity(-vel) + end + end + end + elseif IsValid(rag.leashowner) then + local headpos = rag:GetPos() + local physent = phys + local bone = rag:LookupBone("ValveBiped.Bip01_Neck1") + if bone then + local matrix = rag:GetBoneMatrix(bone) + if matrix then + headpos = matrix:GetTranslation() + end + + if rag:TranslateBoneToPhysBone(bone) then + physent = rag:GetPhysicsObjectNum(rag:TranslateBoneToPhysBone(bone)) + end + end + + local kidnapper = rag.leashowner + local TargetPoint = (kidnapper:IsPlayer() and kidnapper:GetShootPos()) or kidnapper:GetPos() + local MoveDir = (TargetPoint - headpos):GetNormal() + local Dist = rag.ropelength + + local distFromTarget = headpos:Distance( TargetPoint ) + if distFromTarget <= Dist + 5 then return end + + local TargetPos = TargetPoint - (MoveDir * Dist) + + local vel = (TargetPos - headpos) * 1 + physent:AddVelocity(vel) + end + end + end + else + ragdolls[ragdoll] = nil + end + end +end) + +hook.Add("EntityTakeDamage", "Tazer", function(ent, dmginfo) + if ent:IsPlayer() and IsValid(ent.tazeragdoll) and not ent.ragdolldamage then -- If we're hitting the player somehow we won't let, the ragdoll should take the damage. + dmginfo:SetDamage(0) + return + end + + if STUNGUN.AllowDamage and IsValid(ent.tazeplayer) and IsValid(dmginfo:GetAttacker()) and (dmginfo:GetAttacker() != game.GetWorld()) then -- Worldspawn appears to be very eager to damage ragdolls. Don't! + if STUNGUN.IsDarkRP and dmginfo:GetAttacker():IsPlayer() and IsValid(dmginfo:GetAttacker():GetActiveWeapon()) and dmginfo:GetAttacker():GetActiveWeapon().ClassName == "stunstick" then -- Negate stunstick damage + return + end + + local ply = ent.tazeplayer + -- To prevent infiniteloop and other trickery, we need to know if it was ragdamage. + ply.ragdolldamage = true + ply:TakeDamageInfo(dmginfo) -- Apply all ragdoll damage directly to the player. + ply.ragdolldamage = false + + if dmginfo:GetDamage() > 0 and STUNGUN.bLogs then + local atkrname, atkr + if dmginfo:GetAttacker():IsPlayer() then + atkr = dmginfo:GetAttacker() + atkrname = bLogs.GetName(atkr) + else + atkrname = dmginfo:GetAttacker():GetClass() + end + + bLogs.Log({ + module = "Stungun", + log = string.format("%s took %i damage from %s while being ragdolled.", bLogs.GetName(ply), dmginfo:GetDamage(), atkrname), + involved = {ply, atkr} + }) + end + + if dmginfo:GetDamage() > 0 and STUNGUN.pLogs then + local data = { + ["Name"] = ply:Name(), + ["SteamID"] = ply:SteamID(), + ["Damage"] = dmginfo:GetDamage(), + } + + local atkrname + if dmginfo:GetAttacker():IsPlayer() then + atkrname = dmginfo:GetAttacker():NameID() + data["Attacker Name"] = dmginfo:GetAttacker():Name() + data["Attacker SteamID"] = dmginfo:GetAttacker():SteamID() + else + atkrname = dmginfo:GetAttacker():GetClass() + end + + plogs.PlayerLog(ply, "Stungun", string.format("%s took %i damage from %s while being ragdolled.", ply:NameID(), dmginfo:GetDamage(), atkrname), data) + end + end +end) + +function STUNGUN.CleanupParalyze(ply) + if IsValid(ply.tazeragdoll) then + if ply.tazeragdoll.tazesnd then + ply.tazeragdoll.tazesnd:Stop() + ply.tazeragdoll.tazesnd = nil + end + timer.Simple(0,function() + SafeRemoveEntity(ply.tazeragdoll) + end) + + timer.Remove("HurtingTimer" .. ply:UserID()) + timer.Remove("Unelectrolute" .. ply:UserID()) + timer.Remove("tazeUngag" .. ply:UserID()) + timer.Remove("StungunPlayerFreeze" .. ply:UserID()) + net.Start("tazeendview") + net.Send(ply) + + ply:SetNWBool("tazefrozen", false) + + -- While he'll respawn and get this reset, his deadbody won't be visible so we need to reset it here. + STUNGUN.PlayerInvis(ply, false) + + -- If he's respawning the immediate un-invisible won't have any effect. We need some delay. + timer.Simple(.5,function() + STUNGUN.PlayerInvis(ply, false) + end) + end + + ply.tazeismuted = false +end + +-- If someone removes the ragdoll, untaze the player. +hook.Add("EntityRemoved", "Tazer", function(ent) + if IsValid(ent.tazeplayer) and not ent.hasremoved then + STUNGUN.UnRagdoll(ent.tazeplayer) + end +end) + +-- Some code directly respawns the player using :Spawn() without even killing him. We need to remove shit then. +hook.Add("PlayerSpawn", "Tazer", function(ply) + STUNGUN.CleanupParalyze(ply) +end) +-- If he dies, clean up. +hook.Add("DoPlayerDeath", "Tazer", function(ply, inf, atk) + STUNGUN.CleanupParalyze(ply) +end) + +hook.Add("PlayerCanSeePlayersChat", "Tazer", function(text, teamOnly, listener, talker) + if (not STUNGUN.IsTTT or GetRoundState() == ROUND_ACTIVE) and talker.tazeismuted then + return false + end +end) + +hook.Add("PlayerCanHearPlayersVoice", "Tazer", function(listener, talker) + if (not STUNGUN.IsTTT or GetRoundState() == ROUND_ACTIVE) and talker.tazeismuted then + return false,false + end +end) + +hook.Add("CanPlayerSuicide", "Tazer", function(ply) + if not STUNGUN.ParalyzeAllowSuicide and IsValid(ply.tazeragdoll) then return false end + if not STUNGUN.MuteAllowSuicide and ply.tazeismuted then return false end +end) + +hook.Add("PlayerCanPickupWeapon", "Tazer", function(ply, wep) + if IsValid(ply.tazeragdoll) then return false end +end) + +hook.Add("CuffsCanHandcuff", "Tazer", function(ply, target) + if IsValid(target.tazeragdoll) then return false end +end) + +gameevent.Listen("player_disconnect") +hook.Add("player_disconnect", "Tazer", function(data) + local ply = Player(data.userid) + if not IsValid(ply) then return end + + -- Taken from CleanupParalyze, slightly simplified though + if IsValid(ply.tazeragdoll) then + if ply.tazeragdoll.tazesnd then + ply.tazeragdoll.tazesnd:Stop() + ply.tazeragdoll.tazesnd = nil + end + + SafeRemoveEntity(ply.tazeragdoll) + + timer.Remove("HurtingTimer" .. ply:UserID()) + timer.Remove("Unelectrolute" .. ply:UserID()) + timer.Remove("tazeUngag" .. ply:UserID()) + end +end) + +local function DoFallDmg(ply, vel, veldir, umph) + local dmg = math.floor(hook.Call("GetFallDamage", GAMEMODE, ply, vel)) + if dmg != 0 then + local dmginfo = DamageInfo() + dmginfo:SetDamageType(DMG_FALL) + dmginfo:SetDamage(dmg) + dmginfo:SetDamageForce(vel * veldir) + dmginfo:SetDamagePosition(ply.tazeragdoll:GetPos()) + dmginfo:SetAttacker(game.GetWorld()) + dmginfo:SetInflictor(game.GetWorld()) + + ply.ragdolldamage = true + ply:TakeDamageInfo(dmginfo) + ply.ragdolldamage = false + end +end + +hook.Add("Think", "TazerDoRagDmg", function() + if not STUNGUN.Falldamage then return end + + for k,v in pairs(ents.FindByClass("prop_ragdoll")) do + if IsValid(v.tazeplayer) then + local phys = v:GetPhysicsObject() + local vel = phys:GetVelocity():Length() + + if not v.lastfallvel then + v.lastfallvel = vel + end + + if vel >= v.lastfallvel then + v.lastfallvel = vel + else + local deltavel = (v.lastfallvel - vel) + local umph = deltavel * FrameTime() -- Retardation + umph = umph * umph -- More realistic when squared + if umph > 50 then + DoFallDmg(v.tazeplayer, deltavel, phys:GetVelocity():GetNormal(), umph) + v.lastfallvel = 0 + end + end + end + end +end) + +--[[ +DarkRP specifics +]] +-- I'm not sure of the differences between these but one of them lets me put a nice message, while the other takes account in all cases. So I use both. +hook.Add("canChangeJob", "Tazer", function(ply, job) + if IsValid(ply.tazeragdoll) then + return false, "You can't change job while paralyzed!" + end +end) +hook.Add("playerCanChangeTeam", "Tazer", function(ply) + if IsValid(ply.tazeragdoll) then + return false + end +end) + +--[[ +TTT Specifics +]] +function SWEP:WasBought(buyer) + if not self.InfiniteAmmo then + buyer:GiveAmmo(math.max(0, self.Ammo - 1), "ammo_stungun") + end +end + +--[[ +bLogs +]] +local function InitbLogs() + bLogs.DefineLogger("Stungun","Extras") + + STUNGUN.bLogs = true + MsgN("[STUNGUN] bLogs detected.") +end + +if bLogsInit then + InitbLogs() +else + hook.Add("bLogsInit","stungun_waitforblogs",InitbLogs) +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/stungun/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/stungun/shared.lua new file mode 100644 index 0000000..c5ad35d --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/stungun/shared.lua @@ -0,0 +1,330 @@ + +--[[ +Stungun SWEP Created by Donkie (http://steamcommunity.com/id/Donkie/) +For personal/server usage only, do not resell or distribute! +]] + +STUNGUN = {} -- General stungun stuff table + +STUNGUN.IsDarkRP = ((type(DarkRP) == "table") or (RPExtraTeams != nil)) +STUNGUN.IsTTT = ((ROLE_TRAITOR != nil) and (ROLE_INNOCENT != nil) and (ROLE_DETECTIVE != nil) and (GetRoundState != nil)) -- For a gamemode to be TTT, these should probably exist. + +include("config/stungun.lua") + +-- if STUNGUN.IsTTT then +-- SWEP.Base = "weapon_octo_base_zoom" +-- SWEP.AmmoEnt = "" +-- SWEP.IsSilent = false +-- SWEP.NoSights = true +-- end + +SWEP.Base = "weapon_octo_base_pistol" +SWEP.PrintName = L.stungun +SWEP.Author = "Donkie" +SWEP.Instructions = string.format("Left click to stun a person.%s", STUNGUN.CanUntaze and "\nRight click to unstun a person." or "") +SWEP.Contact = "" +SWEP.Purpose = "" + +SWEP.ViewModelFOV = 50 +SWEP.ViewModelFlip = false +SWEP.ViewModel = Model("models/weapons/c_pistol.mdl") +SWEP.WorldModel = Model("models/weapons/cg_ocrp2/w_taser.mdl") + +SWEP.UseHands = true + +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.MuzzlePos = Vector(10, 0.65, 3.5) +SWEP.MuzzleAng = Angle(-4, 2, 0) + +-- SWEP.InfiniteAmmo = (SWEP.Ammo <= -1) -- Not used anymore +if SWEP.InfiniteAmmo then + SWEP.Primary.ClipSize = -1 + SWEP.Primary.DefaultClip = 0 + SWEP.Primary.Ammo = "none" +else + SWEP.Primary.ClipSize = 1 + SWEP.Primary.DefaultClip = 1 + + if STUNGUN.IsTTT then + SWEP.Primary.ClipMax = SWEP.Ammo + end + + SWEP.Primary.Ammo = "ammo_stungun" +end +SWEP.Primary.Automatic = false + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +-- print(SERVER and "SERVER INIT" or "CLIENT INIT") + +SWEP.Uncharging = false + +game.AddAmmoType({ + name = "ammo_stungun", + dmgtype = DMG_GENERIC, + tracer = TRACER_NONE, + plydmg = 0, + npcdmg = 0, + force = 0, + minsplash = 0, + maxsplash = 0 +}) + +function SWEP:DoEffect(tr) + -- Animations + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self.Owner:SetAnimation(PLAYER_ATTACK1) + + -- Electric bolt, taken from toolgun + local effectdata = EffectData() + effectdata:SetOrigin( tr.HitPos ) + effectdata:SetStart( self.Owner:GetShootPos() ) + effectdata:SetAttachment( 1 ) + effectdata:SetEntity( self ) + util.Effect( "ToolTracer", effectdata ) +end + +function SWEP:PrimaryAttack() + + local ct = CurTime() + if not IsFirstTimePredicted() or not self:CanFire() or + (self.nextFire or 0) > ct or self:GetNextPrimaryFire() > ct + then return end + + if self.Charge < 100 then + if SERVER then + self.Owner:EmitSound('weapons/clipempty_pistol.wav', 60) + end + + self.nextFire = ct + 1 + self:SetNextPrimaryFire(self.nextFire) + return + end + + if not self.InfiniteAmmo then + if self:Clip1() <= 0 then return end + self:TakePrimaryAmmo(1) + end + + self.Uncharging = true + + -- Shoot trace + self.Owner:LagCompensation(true) + local shootPos, shootDir = self:GetShootPosAndDir() + local trData = { + start = shootPos, + endpos = shootPos + shootDir * 32768, + filter = self.Owner, + } + local tr = util.TraceLine(trData) + local ent = tr.Entity + if IsValid(ent) and ent:GetClass() == 'gmod_sent_vehicle_fphysics_base' then + trData.filter = { ent, trData.filter } + trData.collisiongroup = COLLISION_GROUP_NONE + tr = util.TraceLine(trData) + end + self.Owner:LagCompensation(false) + + self:DoEffect(tr) + + if SERVER then + self.Owner:EmitSound("npc/turret_floor/shoot1.wav", 75, 100) + end + + local ent = tr.Entity + + if CLIENT then return end + + self.Owner:ViewPunch(Angle(math.random(-12,-5) * self.Primary.Recoil / 50, math.random(-5,5) * self.Primary.Recoil / 50)) + + -- Don't proceed if we don't hit any player + if not IsValid(ent) or not ent:IsPlayer() then return end + if ent == self.Owner then return end + if IsValid(ent.tazeragdoll) then return end + if self.Owner:GetShootPos():Distance(tr.HitPos) > self.Range then return end + + if not STUNGUN.IsPlayerImmune(ent) and (STUNGUN.AllowFriendlyFire or not STUNGUN.SameTeam(self.Owner, ent)) then + -- Damage + if (STUNGUN.StunDamage and STUNGUN.StunDamage > 0) and not ent.tazeimmune then + local dmginfo = DamageInfo() + dmginfo:SetDamage(STUNGUN.StunDamage) + dmginfo:SetAttacker(self.Owner) + dmginfo:SetInflictor(self) + dmginfo:SetDamageType(DMG_SHOCK) + dmginfo:SetDamagePosition(tr.HitPos) + dmginfo:SetDamageForce(self.Owner:GetAimVector() * 30) + ent:TakeDamageInfo(dmginfo) + end + + --The player might have died while getting tazed + if ent:Alive() then + -- Electrolute the player + STUNGUN.Electrolute( ent, (ent:GetPos() - self.Owner:GetPos()):GetNormal() ) + end + + if STUNGUN.bLogs then + bLogs.Log({ + module = "Stungun", + log = string.format("%s was tazed by %s.", bLogs.GetName(ent), bLogs.GetName(self.Owner)), + involved = {ent, self.Owner} + }) + end + + if STUNGUN.pLogs then + plogs.PlayerLog(ent, "Stungun", string.format("%s was tazed by %s.", ent:NameID(), self.Owner:NameID()), { + ["Name"] = ent:Name(), + ["SteamID"] = ent:SteamID(), + ["Attacker Name"] = self.Owner:Name(), + ["Attacker SteamID"] = self.Owner:SteamID(), + }) + end + + hook.Run('tazer.tazed', self.Owner, ent) + end +end + +local chargeinc +function SWEP:Think() + self.BaseClass.Think(self) + -- In charge of charging the swep + -- Since we got the same in-sensitive code both client and serverside we don't need to network anything. + if SERVER or (CLIENT and IsFirstTimePredicted()) then + if not chargeinc then + -- Calculate how much we should increase charge every tick based on how long we want it to take. + chargeinc = ((100 / self.RechargeTime) * engine.TickInterval()) + end + + local inc = self.Uncharging and (-5) or chargeinc + + if self:Clip1() <= 0 and not self.InfiniteAmmo then inc = math.min(inc, 0) end -- If we're out of clip, we shouldn't be allowed to recharge. + + local oldCharge = self.Charge + self.Charge = math.min(self.Charge + inc, 100) + if oldCharge ~= 100 and self.Charge >= 100 then self.Owner:EmitSound('ambient/energy/spark' .. math.random(1,4) .. '.wav', 60, 100, 0.3) end + if self.Charge < 0 then self:Reload() self.Uncharging = false self.Charge = 0 end + end +end + +function SWEP:FailAttack() + self:SetNextSecondaryFire(CurTime() + 0.5) + self.Owner:EmitSound( "Weapon_Pistol.Empty" ) +end + +-- function SWEP:SecondaryAttack() +-- if STUNGUN.CanUntaze then +-- if self:GetNextSecondaryFire() >= CurTime() then self:FailAttack() return end + +-- -- Shoot trace +-- self.Owner:LagCompensation(true) +-- local tr = util.TraceLine(util.GetPlayerTrace( self.Owner )) +-- self.Owner:LagCompensation(false) + +-- local ent = tr.Entity + +-- if CLIENT then return end + +-- -- Don't proceed if we don't hit any raggy +-- if not IsValid(ent) or ent:GetClass() != "prop_ragdoll" or not IsValid(ent.tazeplayer) then self:FailAttack() return end +-- if self.Owner:GetShootPos():Distance(tr.HitPos) > self.Range then self:FailAttack() return end + +-- self:DoEffect(tr) + +-- self.Owner:EmitSound("npc/turret_floor/shoot1.wav",100,100) + +-- local ply = ent.tazeplayer +-- timer.Simple(.4, function() ply:EmitSound("items/smallmedkit1.wav",100,100) end) + +-- STUNGUN.UnMute( ply ) +-- STUNGUN.UnElectrolute( ply ) + +-- local id = ply:UserID() +-- timer.Remove("Unelectrolute" .. id) +-- timer.Remove("tazeUngag" .. id) + +-- if STUNGUN.bLogs then +-- bLogs.Log({ +-- module = "Stungun", +-- log = string.format("%s was un-tazed by %s.", bLogs.GetName(ply), bLogs.GetName(self.Owner)), +-- involved = {ply, self.Owner} +-- }) +-- end + +-- if STUNGUN.pLogs then +-- plogs.PlayerLog(ply, "Stungun", string.format("%s was un-tazed by %s.", ply:NameID(), self.Owner:NameID()), { +-- ["Name"] = ply:Name(), +-- ["SteamID"] = ply:SteamID(), +-- ["Attacker Name"] = self.Owner:Name(), +-- ["Attacker SteamID"] = self.Owner:SteamID(), +-- }) +-- end + +-- self:SetNextSecondaryFire(CurTime() + 2) +-- end +-- return false +-- end + +function SWEP:Reload() + self:DefaultReload( ACT_VM_RELOAD ) + return true +end + +local shoulddisable = {} -- Disables muzzleflashes and ejections +shoulddisable[21] = true +shoulddisable[5003] = true +shoulddisable[6001] = true +function SWEP:FireAnimationEvent( pos, ang, event, options ) + if shoulddisable[event] then return true end +end + +hook.Add("PhysgunPickup", "Tazer", function(_,ent) + if not STUNGUN.AllowPhysgun and IsValid(ent:GetNWEntity("plyowner")) then return false end +end) +hook.Add("CanTool", "Tazer", function(_,tr,_) + if not STUNGUN.AllowToolgun and IsValid(tr.Entity) and IsValid(tr.Entity:GetNWEntity("plyowner")) then return false end +end) + +hook.Add("StartCommand", "Tazer", function(ply, cmd) + if ply:GetNWBool("tazefrozen", false) == false then return end + + cmd:ClearMovement() + cmd:RemoveKey(IN_ATTACK) + cmd:RemoveKey(IN_ATTACK2) + cmd:RemoveKey(IN_RELOAD) + cmd:RemoveKey(IN_USE) + cmd:RemoveKey(IN_DUCK) +end) + +--[[ +pLogs-2 +]] +local function InitpLogs() + if not plogs or not plogs.Version then return end + local versionstr = plogs.Version + local versiontbl = string.Explode(".", versionstr) + if #versiontbl != 3 then return end + + local major = tonumber(versiontbl[1]) + local minor = tonumber(versiontbl[2]) + local patch = tonumber(versiontbl[3]) + + if major < 2 or (major == 2 and minor < 7) then + MsgN("[STUNGUN] pLogs detected but its version is too old!") + return + end + + plogs.Register("Stungun", true, Color(255,163,0)) + STUNGUN.pLogs = true + + MsgN("[STUNGUN] pLogs detected.") +end + +if plogs then + InitpLogs() +else + hook.Add("Initialize","stungun_waitforplogs",InitpLogs) +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_fire_hose.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_fire_hose.lua new file mode 100644 index 0000000..32a9cb8 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_fire_hose.lua @@ -0,0 +1,121 @@ +AddCSLuaFile('effects/fire_hose_effect.lua') + +SWEP.PrintName = 'Пожарный рукав' +SWEP.Author = '' +SWEP.Category = 'Доброград' +SWEP.Contact = '' +SWEP.Purpose = '' +SWEP.Instructions = '' + +SWEP.AutoSwitchTo = true +SWEP.AutoSwitchFrom = true + +SWEP.Spawnable = true +SWEP.Slot = 4 +SWEP.SlotPos = 4 + +SWEP.WorldModel = 'models/weapons/w_firehose.mdl' +SWEP.HoldType = 'shotgun' + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +SWEP.effectScale = 20 +SWEP.pushForce = 190 + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) +end + +function SWEP:DoEffect() + local effectData = EffectData() + effectData:SetAttachment(1) + effectData:SetEntity(self.Owner) + effectData:SetOrigin(self.Owner:GetShootPos()) + effectData:SetNormal(self.Owner:GetAimVector()) + effectData:SetScale(self.effectScale) + util.Effect('fire_hose_effect', effectData) +end + +function SWEP:DoExtinguish() + local tr = self.Owner:GetEyeTrace() + local pos = tr.HitPos + + for id, prop in pairs(ents.FindInSphere(pos, 100)) do + if not IsValid(prop) or prop:GetPos():Distance(self:GetPos()) > 256 then continue end + if self.pushForce > 0 then + local physobj = prop:GetPhysicsObject() + if IsValid(physobj) then + physobj:ApplyForceOffset(self.Owner:GetAimVector() * self.pushForce, pos) + end + end + if SERVER and math.random(1, 100) <= 2 then + local ok = hook.Call('DoExtinguish', nil, prop) + if ok == true then continue end + if prop:IsOnFire() then + prop:Extinguish() + if math.random(1, 100) <= 10 then + timer.Simple(math.random(1,7), function() + prop:Ignite(math.random(300, 1200), 0) + end) + end + end + end + end + + self:DoEffect() +end + +function SWEP:PrimaryAttack() + if self:GetNextPrimaryFire() > CurTime() then return end + self:DoExtinguish() + self:SetNextPrimaryFire(CurTime() + 0.01) +end + +function SWEP:Think() + if self.Owner:KeyPressed(IN_ATTACK) then + self.Sound = CreateSound(self.Owner, Sound('weapons/extinguisher/fire1.wav')) + self.Sound:Play() + end + + if (self.Owner:KeyReleased(IN_ATTACK) or not self.Owner:KeyDown(IN_ATTACK)) and self.Sound then + self.Sound:Stop() + self.Sound = nil + end +end + + +function SWEP:Deploy() + return true +end + +function SWEP:Holster() + self:SetHoldType(self.HoldType) + if self.Sound then + self.Sound:Stop() + self.Sound = nil + end + return true +end + +function SWEP:OnDrop() + if self.Sound then + self.Sound:Stop() + self.Sound = nil + end +end + +function SWEP:SecondaryAttack() + -- snip +end + +function SWEP:Reload() + -- snip +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/cl_init.lua new file mode 100644 index 0000000..5802b76 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/cl_init.lua @@ -0,0 +1,22 @@ +include('shared.lua') + +SWEP.Slot = 0 +SWEP.SlotPos = 5 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.WeaponIcon = surface.GetTextureID("weapons/weapon_flashlight") +killicon.Add( "weapon_flashlight", "weapons/weapon_flashlight_kill", Color( 255, 80, 0, 255 ) ) + +function SWEP:DrawWeaponSelection(x, y, wide, tall, alpha) + + surface.SetDrawColor( 255, 240, 0, 255 ) + surface.SetTexture( self.WeaponIcon ) + surface.DrawTexturedRect( x + wide * 0.15, y, wide / 1.5, tall ) + +end + +function SWEP:PrimaryAttack() + + -- keep calm and do nothing + +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/init.lua new file mode 100644 index 0000000..19e48eb --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/init.lua @@ -0,0 +1,115 @@ +include('shared.lua') +AddCSLuaFile('shared.lua') + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +local switchSound = Sound('flashlight.toggle') + +function SWEP:OnDrop() + + local ply = self:GetOwner() + if self:GetActive() and IsValid(ply) and not ply:GetNetVar('Invisible') then + ply:EmitSound(switchSound) + end + self:KillLight() + +end + +function SWEP:Holster() + + local ply = self:GetOwner() + if self:GetActive() and IsValid(ply) and not ply:GetNetVar('Invisible') then + ply:EmitSound(switchSound) + end + self:KillLight() + + return true + +end + +function SWEP:PrimaryAttack() + + self:SetNextSecondaryFire(CurTime() + 0.23) + if not self:GetOwner():GetNetVar('Invisible') then + self:GetOwner():EmitSound(switchSound) + end + + if not IsValid(self.projectedlight) then + self:BuildLight() + return + end + + local newState = not self:GetActive() + self:SetActive(newState) + + if newState then + self.projectedlight:Fire('TurnOn') + self:SetHoldType('pistol') + else + self.projectedlight:Fire('TurnOff') + self:SetHoldType('normal') + end + +end + +function SWEP:Deploy() + + self:SetNextSecondaryFire(CurTime() + 0.835) + self:SetNextPrimaryFire(CurTime() + 0.835) + self:SetNextReload(CurTime() + 1.835) + if not self:GetOwner():GetNetVar('Invisible') then + self:GetOwner():EmitSound(switchSound) + end + self:BuildLight() + self:SetHoldType(self:GetActive() and 'pistol' or 'normal') + + return true + +end + +function SWEP:OnRemove() + + local ply = self:GetOwner() + if self:GetActive() and IsValid(ply) and not ply:GetNetVar('Invisible') then + ply:EmitSound(switchSound) + end + self:KillLight() + +end + +function SWEP:BuildLight() + + if not IsValid(self) then return end + + local ply = self:GetOwner() + if not IsValid(ply) or ply:GetActiveWeapon() ~= self then return end + + self.projectedlight = ents.Create('env_projectedtexture') + self.projectedlight:SetParent(ply) + self.projectedlight:SetPos(ply:GetShootPos()) + self.projectedlight:SetAngles(ply:GetAngles()) + self.projectedlight:SetKeyValue('enableshadows', 1) + self.projectedlight:SetKeyValue('nearz', 7) + self.projectedlight:SetKeyValue('farz', 750.0) + self.projectedlight:SetKeyValue('lightcolor', self.FlashColor) + self.projectedlight:SetKeyValue('lightfov', 70) + self.projectedlight:Spawn() + self.projectedlight:Input('SpotlightTexture', NULL, NULL, 'effects/flashlight001') + self.projectedlight:Fire('setparentattachment', 'anim_attachment_RH', 0.01) + self:DeleteOnRemove(self.projectedlight) + cleanup.Add(ply, 'flspot', self.projectedlight) + + self:SetActive(true) + +end + +function SWEP:KillLight() + + self:SetActive(false) + if IsValid(self.projectedlight) then + SafeRemoveEntity (self.projectedlight) + end + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/shared.lua new file mode 100644 index 0000000..33b872c --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight/shared.lua @@ -0,0 +1,76 @@ + +SWEP.PrintName = L.flashlight +SWEP.Category = L.dobrograd +SWEP.Author = "Paynamia + chelog" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = L.instruction_flashlight + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.UseHands = true + +SWEP.ViewModel = "models/weapons/c_flashlight_zm.mdl" +SWEP.WorldModel = "models/weapons/w_flashlight_zm.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Damage = 10 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Icon = 'octoteam/icons/flashlight.png' + +SWEP.FlashColor = '255 255 255 255' + +sound.Add({ + name = 'flashlight.toggle', + channel = CHAN_WEAPON, + volume = 0.8, + sound = 'weapons/flashlight_toggle.ogg', + pitch = {95, 105}, + level = 65, +}) + +function SWEP:Precache() + + util.PrecacheSound( "HL2Player.FlashLightOn" ) + +end + +function SWEP:Initialize() + + self:SetHoldType( "pistol" ) + +end + +function SWEP:SetupDataTables() + + self:NetworkVar( "Bool", 0, "Active" ) + self:NetworkVar( "Float", 1, "NextReload" ) + self:NetworkVar( "Float", 2, "NextPrimaryFire" ) + self:NetworkVar( "Float", 3, "NextSecondaryFire" ) + +end + +function SWEP:Reload() + + -- keep calm and do nothing + +end + +function SWEP:SecondaryAttack() + + -- keep calm and do nothing + +end + +function SWEP:Think() + + -- keep calm and do nothing + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight_uv.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight_uv.lua new file mode 100644 index 0000000..7ae1f82 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_flashlight_uv.lua @@ -0,0 +1,30 @@ + +SWEP.Base = 'weapon_flashlight' + +SWEP.PrintName = L.flashlight_uv +SWEP.Category = L.dobrograd +SWEP.Author = "Paynamia + chelog" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = L.instruction_flashlight + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.UseHands = true + +SWEP.ViewModel = "models/weapons/c_flashlight_zm.mdl" +SWEP.WorldModel = "models/weapons/w_flashlight_zm.mdl" + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Damage = 10 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" +SWEP.Icon = 'octoteam/icons/flashlight.png' + +SWEP.FlashColor = '100 0 255 255' \ No newline at end of file diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/cl_init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/cl_init.lua new file mode 100644 index 0000000..42d9c82 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/cl_init.lua @@ -0,0 +1,48 @@ +include 'shared.lua' + +function SWEP:Precache() + + util.PrecacheSound('npc/zombie/zombie_voice_idle1.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle2.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle3.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle4.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle5.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle6.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle7.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle8.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle9.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle10.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle11.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle12.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle13.wav') + util.PrecacheSound('npc/zombie/zombie_voice_idle14.wav') + util.PrecacheSound('npc/zombie/claw_strike1.wav') + util.PrecacheSound('npc/zombie/claw_strike2.wav') + util.PrecacheSound('npc/zombie/claw_strike3.wav') + util.PrecacheSound('npc/zombie/claw_miss1.wav') + util.PrecacheSound('npc/zombie/claw_miss2.wav') + +end + +function SWEP:Initialize() + + self:Precache() + + hook.Add('RenderScreenspaceEffects', 'dbg-zombie', function() + + if not LocalPlayer():GetNetVar('zombie') then return end + DrawColorModify({ + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0.3, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -0.1, + ['$pp_colour_contrast'] = 0.8, + ['$pp_colour_colour'] = 0.2, + }) + + end) + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/init.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/init.lua new file mode 100644 index 0000000..74b8606 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/init.lua @@ -0,0 +1,159 @@ +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +hook.Add('canDropWeapon', 'dbg-zombie', function(ply, wep) + + if IsValid(wep) and wep:GetClass() == 'weapon_zombie' then + return false + end + +end) + +hook.Add('PlayerSwitchWeapon', 'dbg-zombie', function(ply, old, new) + + if IsValid(old) and old:GetClass() == 'weapon_zombie' then + return true + end + +end) + +hook.Add('onDarkRPWeaponDropped', 'dbg-zombie', function(ply, spW, orW) + + if IsValid(orW) and orW:GetClass() == 'weapon_zombie' then + spW:Remove() + end + +end) + +hook.Add('shouldViewPunchOnDamage', 'dbg-zombie', function(ply) + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'weapon_zombie' then + return true + end + +end) + +hook.Add('PlayerCanHearPlayersVoice', 'dbg-zombie', function(listener, talker) + + if talker:GetNetVar('zombie') and not listener:GetNetVar('zombie') then + return false + end + +end) + +hook.Add('PlayerCanSeePlayersChat', 'dbg-zombie', function(txt, t, listener, talker) + + if talker:GetNetVar('zombie') and not listener:GetNetVar('zombie') then + return false + end + +end) + +hook.Add('PlayerLoadout', 'dbg-zombie', function(ply) + + -- if ply.pendingZombie and not ply:IsGhost() then + -- timer.Simple(3, function() + -- ply:Give('weapon_zombie') + -- end) + -- return true + -- end + + if not ply:HasWeapon('weapon_zombie') then + if ply:GetNetVar('zombie') ~= nil then hook.Run('dbg-zombie.changed', ply, false) end + ply:MoveModifier('zombie', nil) + ply:SetNetVar('zombie', nil) + end + +end) + +hook.Add('PlayerDeath', 'dbg-zombie', function(ply, inf, att) + + if att:IsPlayer() and att:HasWeapon('weapon_zombie') then + ply.pendingZombie = true + end + +end) + +hook.Add('hungerUpdate', 'dbg-zombie', function(ply) + + if ply:HasWeapon('weapon_zombie') then + return true + end + +end) + +function SWEP:Initialize() + + self:SetHoldType(self.HoldType) + + timer.Simple(0.5, function() + local ply = self.Owner + if not IsValid(ply) or not ply:HasWeapon('weapon_zombie') then return end + for k, wep in pairs(ply:GetWeapons()) do + local class = wep:GetClass() + if class ~= 'weapon_zombie' then ply:StripWeapon(class) end + ply:SelectWeapon('weapon_zombie') + end + end) + +end + +function SWEP:NormalSpeed() + + if IsValid(self.Owner) then + if self.Owner:GetNetVar('zombie') ~= nil then hook.Run('dbg-zombie.changed', self.Owner, false) end + self.Owner:MoveModifier('zombie', nil) + self.Owner:SetNetVar('zombie', nil) + end + +end + +function SWEP:CustomSpeed() + + if IsValid(self.Owner) then + if self.Owner:GetNetVar('zombie') ~= true then hook.Run('dbg-zombie.changed', self.Owner, true) end + self.Owner:SetNetVar('zombie', true) + self.Owner:MoveModifier('zombie', { + walkadd = -20, + runadd = 30, + jumpmul = 1.6, + }) + end + +end + +function SWEP:Deploy() + + if IsValid(self.Owner) then + self:CustomSpeed() + self.Owner:SetModel('models/player/zombie_fast.mdl') + self.Owner:SetHealth(game.GetMap():find('rp_eastcoast') and 2000 or 250) + end + return true + +end + +function SWEP:Holster() + + self:NormalSpeed() + return true + +end + +function SWEP:Damage(ent, damage, pl) + + if ent:IsPlayer() and ent:HasWeapon('weapon_zombie') then return end + + if ent:IsDoor() then + ent:DoUnlock() + ent:Fire('Open') + ent:EmitSound('physics/wood/wood_crate_impact_hard4.wav', 65) + end + + ent:TakeDamage(damage, pl, self) + if ent:GetClass() == 'func_breakable_surf' then + ent:Fire('Shatter', Vector(0,0,0)) + end + +end diff --git a/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/shared.lua b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/shared.lua new file mode 100644 index 0000000..8134435 --- /dev/null +++ b/garrysmod/addons/gmod-sweps/lua/weapons/weapon_zombie/shared.lua @@ -0,0 +1,114 @@ +SWEP.PrintName = L.zombie +SWEP.Category = 'Other' +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.ViewModelFOV = 70 +SWEP.ViewModelFlip = false +SWEP.CSMuzzleFlashes = false +SWEP.IconLetter = 'J' + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.HoldType = 'knife' +SWEP.Author = 'ErrolLiamP' +SWEP.Purpose = 'Kill Humans' +SWEP.Instructions = L.instruction_zombie + +SWEP.ViewModel = '' +SWEP.WorldModel = '' + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = 'none' +SWEP.Primary.Delay = 1.6 + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Ammo = 'none' + +function SWEP:Think() + + if SERVER and CurTime() > (self.nextAutoMoan or 0) then + self:SecondaryAttack() + self.nextAutoMoan = CurTime() + math.random(30, 120) + end + + if not self.NextHit or CurTime() < self.NextHit then return end + self.NextHit = nil + + local pl = self.Owner + + local vStart = pl:EyePos() + Vector(0, 0, -10) + local trace = util.TraceLine({start=vStart, endpos = vStart + pl:GetAimVector() * 71, filter = pl, mask = MASK_SHOT}) + + local ent + if trace.HitNonWorld then + ent = trace.Entity + elseif self.PreHit and self.PreHit:IsValid() and not (self.PreHit:IsPlayer() and not self.PreHit:Alive()) and self.PreHit:GetPos():Distance(vStart) < 110 then + ent = self.PreHit + trace.Hit = true + end + + if SERVER then + pl:EmitSound('npc/zombie/zombie_voice_idle'..math.random(1, 14)..'.wav') + if trace.Hit then + pl:EmitSound('npc/zombie/claw_strike'..math.random(1, 3)..'.wav') + else + pl:EmitSound('npc/zombie/claw_miss'..math.random(1, 2)..'.wav') + end + end + + self.PreHit = nil + + if ent and ent:IsValid() and not (ent:IsPlayer() and not ent:Alive()) then + local damage = 25 + local phys = ent:GetPhysicsObject() + if phys:IsValid() and not ent:IsNPC() and phys:IsMoveable() then + local vel = damage * 487 * pl:GetAimVector() + + phys:ApplyForceOffset(vel, (ent:NearestPoint(pl:GetShootPos()) + ent:GetPos() * 2) / 3) + if ent.SetPhysicsAttacker then + ent:SetPhysicsAttacker(pl) + end + + end + + if SERVER then self:Damage(ent, damage, pl) end + end + +end + +SWEP.NextSwing = 0 +function SWEP:PrimaryAttack() + + if CurTime() < self.NextSwing then return end + + self.Owner:DoAnimationEvent(ACT_GMOD_GESTURE_RANGE_ZOMBIE) + + timer.Simple(1.4, function() + if not IsValid(self) or not IsValid(self.Owner) then return end + end) + + self.NextSwing = CurTime() + self.Primary.Delay + self.NextHit = CurTime() + 1 + local vStart = self.Owner:EyePos() + Vector(0, 0, -10) + local trace = util.TraceLine({start=vStart, endpos = vStart + self.Owner:GetAimVector() * 65, filter = self.Owner, mask = MASK_SHOT}) + if trace.HitNonWorld then + self.PreHit = trace.Entity + end + +end + +SWEP.NextMoan = 0 +function SWEP:SecondaryAttack() + + if CurTime() < self.NextMoan then return end + if SERVER then + self.Owner:DoAnimation(ACT_GMOD_GESTURE_TAUNT_ZOMBIE) + self.Owner:EmitSound('npc/zombie/zombie_voice_idle'..math.random(1, 14)..'.wav') + end + self.NextMoan = CurTime() + 3 + +end diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/cl_file.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/cl_file.lua new file mode 100644 index 0000000..b5e7442 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/cl_file.lua @@ -0,0 +1,104 @@ +local function AdvDupe2_ReceiveFile(len, ply) + local Autosave = net.ReadUInt(8) == 1 + + net.ReadStream(nil, function(data) + AdvDupe2.RemoveProgressBar() + if(!data)then + AdvDupe2.Notify("File was not saved!",NOTIFY_ERROR,5) + return + end + local path = "" + if(AutoSave)then + if(LocalPlayer():GetInfo("advdupe2_auto_save_overwrite")~="1")then + path = AdvDupe2.GetFilename(AdvDupe2.AutoSavePath) + end + else + path = AdvDupe2.GetFilename(AdvDupe2.SavePath) + end + + local dupefile = file.Open(path, "wb", "DATA") + if(!dupefile)then + AdvDupe2.Notify("File was not saved!",NOTIFY_ERROR,5) + return + end + dupefile:Write(data) + dupefile:Close() + + local errored = false + if(LocalPlayer():GetInfo("advdupe2_debug_openfile")=="1")then + if(not file.Exists(path, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end + + local readFile = file.Open(path, "rb", "DATA") + if not readFile then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end + local readData = readFile:Read(readFile:Size()) + readFile:Close() + local success,dupe,info,moreinfo = AdvDupe2.Decode(readData) + if(success)then + AdvDupe2.Notify("DEBUG CHECK: File successfully opens. No EOF errors.") + else + AdvDupe2.Notify("DEBUG CHECK: " .. dupe, NOTIFY_ERROR) + errored = true + end + end + + local filename = string.StripExtension(string.GetFileFromFilename( path )) + if(AutoSave)then + if(IsValid(AdvDupe2.FileBrowser.AutoSaveNode))then + local add = true + for i=1, #AdvDupe2.FileBrowser.AutoSaveNode.Files do + if(filename==AdvDupe2.FileBrowser.AutoSaveNode.Files[i].Label:GetText())then + add=false + break + end + end + if(add)then + AdvDupe2.FileBrowser.AutoSaveNode:AddFile(filename) + AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.AutoSaveNode) + end + end + else + AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode:AddFile(filename) + AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode) + end + if(!errored)then + AdvDupe2.Notify("File successfully saved!",NOTIFY_GENERIC, 5) + end + end) +end +net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile) + +local uploading = nil +function AdvDupe2.UploadFile(ReadPath, ReadArea) + if uploading then AdvDupe2.Notify("Already opening file, please wait.", NOTIFY_ERROR) return end + if(ReadArea==0)then + ReadPath = AdvDupe2.DataFolder.."/"..ReadPath..".txt" + elseif(ReadArea==1)then + ReadPath = AdvDupe2.DataFolder.."/-Public-/"..ReadPath..".txt" + else + ReadPath = "adv_duplicator/"..ReadPath..".txt" + end + + if(not file.Exists(ReadPath, "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end + + local read = file.Read(ReadPath) + if not read then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end + local name = string.Explode("/", ReadPath) + name = name[#name] + name = string.sub(name, 1, #name-4) + + local success, dupe, info, moreinfo = AdvDupe2.Decode(read) + if(success)then + net.Start("AdvDupe2_ReceiveFile") + net.WriteString(name) + uploading = net.WriteStream(read, function() + uploading = nil + AdvDupe2.File = nil + AdvDupe2.RemoveProgressBar() + end) + net.SendToServer() + + AdvDupe2.LoadGhosts(dupe, info, moreinfo, name) + else + AdvDupe2.Notify("File could not be decoded. ("..dupe..") Upload Canceled.", NOTIFY_ERROR) + end +end diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/cl_ghost.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/cl_ghost.lua new file mode 100644 index 0000000..6efd4a2 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/cl_ghost.lua @@ -0,0 +1,317 @@ +function AdvDupe2.LoadGhosts(dupe, info, moreinfo, name, preview) + + AdvDupe2.RemoveGhosts() + AdvDupe2.Ghosting = true + + AdvDupe2.GhostToSpawn = {} + local count = 0 + + local time + local desc + local date + local creator + + if(info.ad1)then + time = moreinfo["Time"] or "" + desc = info["Description"] or "" + date = info["Date"] or "" + creator = info["Creator"] or "" + + AdvDupe2.HeadEnt = dupe.HeadEnt.Index + AdvDupe2.HeadPos = dupe.HeadEnt.Pos + local z = dupe.HeadEnt.Z + AdvDupe2.HeadZPos = z + AdvDupe2.HeadPos.Z = AdvDupe2.HeadPos.Z + z + + local Pos + local Ang + for k,v in pairs(dupe["Entities"])do + + if(v.SavedParentIdx)then + if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos*1 + Ang = v.LocalAngle*1 + else + Pos = nil + Ang = nil + end + for i,p in pairs(v.PhysicsObjects)do + p.Pos = Pos or (p.LocalPos*1) + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or (p.LocalAngle*1) + p.LocalPos = nil + p.LocalAngle = nil + end + v.LocalPos = nil + v.LocalAngle = nil + AdvDupe2.GhostToSpawn[count] = {Model=v.Model, PhysicsObjects=v.PhysicsObjects} + if(AdvDupe2.HeadEnt == k)then + AdvDupe2.HeadEnt = count + end + count = count + 1 + end + + AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + + else + time = info["time"] or "" + desc = dupe["Description"] or "" + date = info["date"] or "" + creator = info["name"] or "" + + AdvDupe2.HeadEnt = dupe["HeadEnt"].Index + AdvDupe2.HeadZPos = dupe["HeadEnt"].Z + AdvDupe2.HeadPos = dupe["HeadEnt"].Pos + AdvDupe2.HeadOffset = dupe["Entities"][AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = dupe["Entities"][AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + + for k,v in pairs(dupe["Entities"])do + AdvDupe2.GhostToSpawn[count] = {Model=v.Model, PhysicsObjects=v.PhysicsObjects} + if(AdvDupe2.HeadEnt == k)then + AdvDupe2.HeadEnt = count + end + count = count + 1 + end + end + + if(not preview)then + AdvDupe2.Info.File:SetText("File: "..name) + AdvDupe2.Info.Creator:SetText("Creator: "..creator) + AdvDupe2.Info.Date:SetText("Date: "..date) + AdvDupe2.Info.Time:SetText("Time: "..time) + AdvDupe2.Info.Size:SetText("Size: "..string.NiceSize(tonumber(info.size) or 0)) + AdvDupe2.Info.Desc:SetText("Desc: "..(desc or "")) + AdvDupe2.Info.Entities:SetText("Entities: "..table.Count(dupe["Entities"])) + AdvDupe2.Info.Constraints:SetText("Constraints: "..table.Count(dupe["Constraints"])) + end + + AdvDupe2.StartGhosting() + AdvDupe2.Preview = preview + +end + +function AdvDupe2.RemoveGhosts() + + if(AdvDupe2.Ghosting)then + hook.Remove("Tick", "AdvDupe2_SpawnGhosts") + AdvDupe2.Ghosting = false + if(not AdvDupe2.BusyBar)then + AdvDupe2.RemoveProgressBar() + end + end + + if(AdvDupe2.GhostEntities)then + for k,v in pairs(AdvDupe2.GhostEntities)do + if(IsValid(v))then + v:Remove() + end + end + end + + if(IsValid(AdvDupe2.HeadGhost))then + AdvDupe2.HeadGhost:Remove() + end + AdvDupe2.HeadGhost = nil + AdvDupe2.CurrentGhost = 1 + AdvDupe2.GhostEntities = nil + AdvDupe2.Preview = false +end + +--Creates a ghost from the given entity's table +local function MakeGhostsFromTable(EntTable) + + if(not EntTable)then return end + if(not EntTable.Model or EntTable.Model=="" or EntTable.Model[#EntTable.Model-3]~=".")then EntTable.Model="models/error.mdl" end + + local GhostEntity = octolib.createDummy(EntTable.Model, RENDERGROUP_TRANSLUCENT) + + -- If there are too many entities we might not spawn.. + if not IsValid(GhostEntity) then + AdvDupe2.RemoveGhosts() + AdvDupe2.Notify("Too many entities to spawn ghosts", NOTIFY_ERROR) + return + end + + GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA ) --Was broken, making ghosts invisible + GhostEntity:SetColor( Color(255, 255, 255, 150) ) + GhostEntity.Phys = EntTable.PhysicsObjects[0] + + if util.IsValidRagdoll(EntTable.Model) then + GhostEntity:SetupBones() + local parents = {} + local angs = {} + local ref = {} + for k, v in pairs( EntTable.PhysicsObjects ) do + local bone = GhostEntity:TranslatePhysBoneToBone(k) + local parentbone = GhostEntity:GetBoneParent(bone) + if parentbone == -1 then + ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR() + else + parentbone = GhostEntity:TranslatePhysBoneToBone(GhostEntity:TranslateBoneToPhysBone(parentbone)) + parents[bone] = parentbone + ref[bone] = GhostEntity:GetBoneMatrix(bone):GetInverseTR() * GhostEntity:GetBoneMatrix(parentbone) + end + local m = Matrix() m:SetAngles(v.Angle) + angs[bone] = m + end + for bone, ang in pairs( angs ) do + if parents[bone] and angs[parents[bone]] then + local localrotation = angs[parents[bone]]:GetInverseTR() * ang + local m = ref[bone] * localrotation + GhostEntity:ManipulateBoneAngles(bone, m:GetAngles()) + else + local pos = GhostEntity:GetBonePosition(bone) + GhostEntity:ManipulateBonePosition(bone, -pos) + GhostEntity:ManipulateBoneAngles(bone, ref[bone]:GetAngles()) + end + end + end + + return GhostEntity +end + +local function SpawnGhosts() + if AdvDupe2.CurrentGhost==AdvDupe2.HeadEnt then AdvDupe2.CurrentGhost = AdvDupe2.CurrentGhost + 1 end + + local g = AdvDupe2.GhostToSpawn[AdvDupe2.CurrentGhost] + if g then + AdvDupe2.GhostEntities[AdvDupe2.CurrentGhost] = MakeGhostsFromTable(g) + if(not AdvDupe2.BusyBar)then + AdvDupe2.ProgressBar.Percent = AdvDupe2.CurrentGhost/AdvDupe2.TotalGhosts*100 + end + AdvDupe2.CurrentGhost = AdvDupe2.CurrentGhost + 1 + AdvDupe2.UpdateGhosts(true) + else + AdvDupe2.Ghosting = false + hook.Remove("Tick", "AdvDupe2_SpawnGhosts") + if(not AdvDupe2.BusyBar)then + AdvDupe2.RemoveProgressBar() + end + end +end + +net.Receive("AdvDupe2_SendGhosts", function(len, ply, len2) + AdvDupe2.RemoveGhosts() + AdvDupe2.GhostToSpawn = {} + AdvDupe2.HeadEnt = net.ReadInt(16) + AdvDupe2.HeadZPos = net.ReadFloat() + AdvDupe2.HeadPos = net.ReadVector() + local cache = {} + for i=1, net.ReadInt(16) do + cache[i] = net.ReadString() + end + + for i=1, net.ReadInt(16) do + AdvDupe2.GhostToSpawn[i] = {Model = cache[net.ReadInt(16)], PhysicsObjects = {}} + for k=0, net.ReadInt(8) do + AdvDupe2.GhostToSpawn[i].PhysicsObjects[k] = {Angle = net.ReadAngle(), Pos = net.ReadVector()} + end + end + + AdvDupe2.GhostEntities = {} + AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt]) + AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost + AdvDupe2.CurrentGhost = 1 + AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn + + if(AdvDupe2.TotalGhosts>1)then + AdvDupe2.Ghosting = true + if(not AdvDupe2.BusyBar)then + AdvDupe2.InitProgressBar("Ghosting: ") + AdvDupe2.BusyBar = false + end + hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts) + else + AdvDupe2.Ghosting = false + end +end) + +net.Receive("AdvDupe2_AddGhost", function(len, ply, len2) + local ghost = {Model = net.ReadString(), PhysicsObjects = {}} + for k=0, net.ReadInt(8) do + ghost.PhysicsObjects[k] = {Angle = net.ReadAngle(), Pos = net.ReadVector()} + end + AdvDupe2.GhostEntities[AdvDupe2.CurrentGhost] = MakeGhostsFromTable(ghost) + AdvDupe2.CurrentGhost = AdvDupe2.CurrentGhost + 1 +end) + +function AdvDupe2.StartGhosting() + AdvDupe2.RemoveGhosts() + if(not AdvDupe2.GhostToSpawn)then return end + AdvDupe2.Ghosting = true + AdvDupe2.GhostEntities = {} + AdvDupe2.HeadGhost = MakeGhostsFromTable(AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt]) + AdvDupe2.GhostEntities[AdvDupe2.HeadEnt] = AdvDupe2.HeadGhost + AdvDupe2.CurrentGhost = 1 + AdvDupe2.TotalGhosts = #AdvDupe2.GhostToSpawn + + if AdvDupe2.TotalGhosts > 1 then + if(not AdvDupe2.BusyBar)then + AdvDupe2.InitProgressBar("Ghosting: ") + AdvDupe2.BusyBar = false + end + hook.Add("Tick", "AdvDupe2_SpawnGhosts", SpawnGhosts) + else + AdvDupe2.Ghosting = false + end +end +usermessage.Hook("AdvDupe2_StartGhosting", function() + AdvDupe2.StartGhosting() +end) + +usermessage.Hook("AdvDupe2_RemoveGhosts", AdvDupe2.RemoveGhosts) + +--Update the ghost's postion and angles based on where the player is looking and the offsets +local Lheadpos, Lheadang = Vector(), Angle() +function AdvDupe2.UpdateGhosts(force) + if not IsValid(AdvDupe2.HeadGhost) then + AdvDupe2.RemoveGhosts() + AdvDupe2.Notify("Invalid ghost parent.", NOTIFY_ERROR) + return + end + + local trace = util.TraceLine(util.GetPlayerTrace(LocalPlayer(), LocalPlayer():GetAimVector())) + if (not trace.Hit) then return end + + local origin, offsetang, headpos, headang + if(tobool(GetConVarNumber("advdupe2_original_origin")))then + headpos = AdvDupe2.HeadPos + AdvDupe2.HeadOffset + headang = AdvDupe2.HeadAngle + origin = Vector(AdvDupe2.HeadPos) + offsetang = Angle() + else + local headangle = AdvDupe2.HeadAngle + if(tobool(GetConVarNumber("advdupe2_offset_world")))then headangle = Angle(0,0,0) end + trace.HitPos.Z = trace.HitPos.Z + math.Clamp(AdvDupe2.HeadZPos + GetConVarNumber("advdupe2_offset_z") or 0, -16000, 16000) + origin = trace.HitPos + offsetang = Angle(math.Clamp(GetConVarNumber("advdupe2_offset_pitch") or 0,-180,180), math.Clamp(GetConVarNumber("advdupe2_offset_yaw") or 0,-180,180), math.Clamp(GetConVarNumber("advdupe2_offset_roll") or 0,-180,180)) + headpos, headang = LocalToWorld(AdvDupe2.HeadOffset, headangle, origin, offsetang) + end + + local x, y, z = GetConVarNumber('advdupe2_original_offset_x'), GetConVarNumber('advdupe2_original_offset_y'), GetConVarNumber('advdupe2_original_offset_z') + if x ~= 0 or y ~= 0 or z ~= 0 then + origin:Add(Vector(x, y, z)) + end + + if math.abs(Lheadpos.x - headpos.x)>0.01 or math.abs(Lheadpos.y - headpos.y)>0.01 or math.abs(Lheadpos.z - headpos.z)>0.01 or + math.abs(Lheadang.p - headang.p)>0.01 or math.abs(Lheadang.y - headang.y)>0.01 or math.abs(Lheadang.r - headang.r)>0.01 or force then + + Lheadpos = headpos + Lheadang = headang + + AdvDupe2.HeadGhost:SetPos(headpos) + AdvDupe2.HeadGhost:SetAngles(headang) + + for k, ghost in ipairs(AdvDupe2.GhostEntities) do + local phys = ghost.Phys + local pos, ang = LocalToWorld(phys.Pos, phys.Angle, origin, offsetang) + ghost:SetPos(pos) + ghost:SetAngles(ang) + end + + end +end diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/cl_networking.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/cl_networking.lua new file mode 100644 index 0000000..829be5a --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/cl_networking.lua @@ -0,0 +1,302 @@ +include "nullesc.lua" + +AdvDupe2.NetFile = "" +local AutoSave = false +local uploading = false +local uploading_timeout = 0 +local uploading_timeout_time = 10 + +local function CheckFileNameCl(path) + if file.Exists(path..".txt", "DATA") then + for i = 1, AdvDupe2.FileRenameTryLimit do + if not file.Exists(path.."_"..i..".txt", "DATA") then + return path.."_"..i + end + end + end + return path +end + +--[[ + Name: AdvDupe2_ReceiveFile + Desc: Receive file data from the server when downloading to the client + Params: + Returns: +]] +local function AdvDupe2_ReceiveFile(len, ply, len2) + local status = net.ReadInt(8) + + if(status==1)then AdvDupe2.NetFile = "" end + AdvDupe2.NetFile=AdvDupe2.NetFile..net.ReadString() + + if(status==2)then + local path = "" + if(AutoSave)then + if(LocalPlayer():GetInfo("advdupe2_auto_save_overwrite")~="1")then + path = CheckFileNameCl(AdvDupe2.AutoSavePath) + end + else + path = CheckFileNameCl(AdvDupe2.SavePath) + end + file.Write(path..".txt", AdvDupe2.Null.invesc(AdvDupe2.NetFile)) + + if(!file.Exists(path..".txt", "DATA"))then + AdvDupe2.NetFile = "" + AdvDupe2.Notify("File was not saved!",NOTIFY_ERROR,5) + return + end + + local errored = false + if(LocalPlayer():GetInfo("advdupe2_debug_openfile")=="1")then + if(not file.Exists(path..".txt", "DATA"))then AdvDupe2.Notify("File does not exist", NOTIFY_ERROR) return end + + local read = file.Read(path..".txt") + if not read then AdvDupe2.Notify("File could not be read", NOTIFY_ERROR) return end + AdvDupe2.Decode(read, function(success,dupe,info,moreinfo) + if(success)then + AdvDupe2.Notify("DEBUG CHECK: File successfully opens. No EOF errors.") + else + AdvDupe2.Notify("DEBUG CHECK: File contains EOF errors.", NOTIFY_ERROR) + errored = true + end + end) + end + + local filename = string.Explode("/", path) + filename = filename[#filename] + if(AutoSave)then + if(IsValid(AdvDupe2.FileBrowser.AutoSaveNode))then + local add = true + for i=1, #AdvDupe2.FileBrowser.AutoSaveNode.Files do + if(filename==AdvDupe2.FileBrowser.AutoSaveNode.Files[i].Label:GetText())then + add=false + break + end + end + if(add)then + AdvDupe2.FileBrowser.AutoSaveNode:AddFile(filename) + AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.AutoSaveNode) + end + end + else + AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode:AddFile(filename) + AdvDupe2.FileBrowser.Browser.pnlCanvas:Sort(AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode) + end + AdvDupe2.NetFile = "" + if(!errored)then + AdvDupe2.Notify("File successfully saved!",NOTIFY_GENERIC, 5) + end + return + end + +end +net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile) + +function AdvDupe2.LoadGhosts(dupe, info, moreinfo, name, preview) + + AdvDupe2.RemoveGhosts() + AdvDupe2.Ghosting = true + + if(preview)then + AdvDupe2.Preview = true + if(AdvDupe2.HeadEnt)then + AdvDupe2.PHeadEnt = AdvDupe2.HeadEnt + AdvDupe2.PHeadZPos = AdvDupe2.HeadZPos + AdvDupe2.PHeadPos = AdvDupe2.HeadPos*1 + AdvDupe2.PHeadOffset = AdvDupe2.HeadOffset*1 + AdvDupe2.PHeadAngle = AdvDupe2.HeadAngle*1 + AdvDupe2.GhostToPreview = table.Copy(AdvDupe2.GhostToSpawn) + end + else + AdvDupe2.PHeadEnt = nil + AdvDupe2.PHeadZPos = nil + AdvDupe2.PHeadPos = nil + AdvDupe2.PHeadOffset = nil + AdvDupe2.PHeadAngle = nil + AdvDupe2.GhostToPreview = nil + AdvDupe2.Preview=false + end + + AdvDupe2.GhostToSpawn = {} + local count = 0 + + local time + local desc + local date + local creator + + if(info.ad1)then + time = moreinfo["Time"] or "" + desc = info["Description"] or "" + date = info["Date"] or "" + creator = info["Creator"] or "" + + AdvDupe2.HeadEnt = tonumber(moreinfo.Head) + local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$") + AdvDupe2.HeadPos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0) + local z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1 + AdvDupe2.HeadZPos = z + AdvDupe2.HeadPos.Z = AdvDupe2.HeadPos.Z + z + + local Pos + local Ang + for k,v in pairs(dupe["Entities"])do + + if(v.SavedParentIdx)then + if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos*1 + Ang = v.LocalAngle*1 + else + Pos = nil + Ang = nil + end + for i,p in pairs(v.PhysicsObjects)do + p.Pos = Pos or (p.LocalPos*1) + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or (p.LocalAngle*1) + p.LocalPos = nil + p.LocalAngle = nil + end + v.LocalPos = nil + v.LocalAngle = nil + AdvDupe2.GhostToSpawn[count] = {Model=v.Model, PhysicsObjects=v.PhysicsObjects} + if(AdvDupe2.HeadEnt == k)then + AdvDupe2.HeadEnt = count + end + count = count + 1 + end + + AdvDupe2.HeadOffset = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = AdvDupe2.GhostToSpawn[AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + + else + time = info["time"] + desc = dupe["Description"] + date = info["date"] + creator = info["name"] + + AdvDupe2.HeadEnt = dupe["HeadEnt"].Index + AdvDupe2.HeadZPos = dupe["HeadEnt"].Z + AdvDupe2.HeadPos = dupe["HeadEnt"].Pos + AdvDupe2.HeadOffset = dupe["Entities"][AdvDupe2.HeadEnt].PhysicsObjects[0].Pos + AdvDupe2.HeadAngle = dupe["Entities"][AdvDupe2.HeadEnt].PhysicsObjects[0].Angle + + for k,v in pairs(dupe["Entities"])do + AdvDupe2.GhostToSpawn[count] = {Model=v.Model, PhysicsObjects=v.PhysicsObjects} + if(AdvDupe2.HeadEnt == k)then + AdvDupe2.HeadEnt = count + end + count = count + 1 + end + end + + if(not preview)then + AdvDupe2.Info.File:SetText("File: "..name) + AdvDupe2.Info.Creator:SetText("Creator: "..creator) + AdvDupe2.Info.Date:SetText("Date: "..date) + AdvDupe2.Info.Time:SetText("Time: "..time) + AdvDupe2.Info.Size:SetText("Size: "..string.NiceSize(tonumber(info.size) or 0)) + AdvDupe2.Info.Desc:SetText("Desc: "..(desc or "")) + AdvDupe2.Info.Entities:SetText("Entities: "..table.Count(dupe["Entities"])) + AdvDupe2.Info.Constraints:SetText("Constraints: "..table.Count(dupe["Constraints"])) + end + + AdvDupe2.StartGhosting() + +end + +--[[ + Name: InitializeUpload + Desc: When the client clicks upload, prepares to send data to the server + Params: File Data, Path to save + Returns: +]] +function AdvDupe2.InitializeUpload(ReadPath, ReadArea) + if(uploading and CurTime()AdvDupe2.Length)then + eof = AdvDupe2.Length + end + + local data = string.sub(AdvDupe2.File, AdvDupe2.LastPos, AdvDupe2.LastPos+eof) + + AdvDupe2.LastPos = AdvDupe2.LastPos+eof+1 + AdvDupe2.ProgressBar.Percent = math.min(math.floor((AdvDupe2.LastPos/AdvDupe2.Length)*100),100) + + net.Start("AdvDupe2_ReceiveFile") + net.WriteBit(AdvDupe2.LastPos>=AdvDupe2.Length) + net.WriteString(data) + net.SendToServer() + +end + +usermessage.Hook("AdvDupe2_ReceiveNextStep",function(um) + if AdvDupe2.PendingDupe then + local read,dupe,info,moreinfo,name = unpack( AdvDupe2.PendingDupe ) + + AdvDupe2.PendingDupe = nil + AdvDupe2.LoadGhosts(dupe, info, moreinfo, name ) + AdvDupe2.File = AdvDupe2.Null.esc(read) + AdvDupe2.LastPos = 0 + AdvDupe2.Length = string.len(AdvDupe2.File) + AdvDupe2.InitProgressBar("Opening:") + end + + if uploading then + SendFileToServer(um:ReadShort()) + end +end) + +usermessage.Hook("AdvDupe2_UploadRejected",function(um) + if uploading then + uploading = false + AdvDupe2.PendingDupe = nil + AdvDupe2.File = nil + AdvDupe2.LastPos = nil + AdvDupe2.Length = nil + end + if(um:ReadBool())then AdvDupe2.RemoveProgressBar() end +end) + +concommand.Add("AdvDupe2_SaveType", function(ply, cmd, args) + AutoSave = args[1]=="1" +end) diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/file_browser.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/file_browser.lua new file mode 100644 index 0000000..c4410ac --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/file_browser.lua @@ -0,0 +1,1328 @@ +--[[ + Title: Adv. Dupe 2 File Browser + + Desc: Displays and interfaces with duplication files. + + Author: TB + + Version: 1.0 +]] + +local History = {} +local Narrow = {} + +local switch=true +local count = 0 + +local function AddHistory(txt) + txt = string.lower(txt) + local char1 = txt[1] + local char2 + for i=1,#History do + char2 = History[i][1] + if(char1 == char2)then + if(History[i]==txt)then + return + end + elseif(char11)then + for k=2, #txt do + if(txt[k]~=History[i][k])then + break + end + if(k==#txt)then + table.insert(temp, History[i]) + end + end + else + table.insert(temp, History[i]) + end + elseif(char122)then + parent.Info:SetText('Are you sure that you want to delete \nthe FILE, "'..node.Label:GetText()..'" \nfrom your CLIENT?') + else + parent.Info:SetText('Are you sure that you want to delete \nthe FILE, "'..node.Label:GetText()..'" from your CLIENT?') + end + parent.Info:SizeToContents() + parent.Info:SetVisible(true) + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + local path, area = GetNodePath(node) + if(area==1)then path = "-Public-/"..path end + if(area==2)then + path = "adv_duplicator/"..path..".txt" + else + path = AdvDupe2.DataFolder.."/"..path..".txt" + end + node.Control:RemoveNode(node) + file.Delete(path) + AdvDupe2.FileBrowser:Slide(false) + end + end) + end + else + if(root~="-Advanced Duplicator 1-")then + Menu:AddOption("Save", function() + if(parent.Expanding)then return end + parent.Submit:SetMaterial("icon16/page_save.png") + parent.Submit:SetTooltip("Save Duplication") + if(parent.FileName:GetValue()=="Folder_Name...")then + parent.FileName:SetText("File_Name...") + end + parent.Desc:SetVisible(true) + parent.Info:SetVisible(false) + parent.FileName.FirstChar = true + parent.FileName.PrevText = parent.FileName:GetValue() + parent.FileName:SetVisible(true) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnMousePressed() + parent.FileName:RequestFocus() + node.Control.ActionNode = node + parent.Expanding=true + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + local name = parent.FileName:GetValue() + if(name=="" or name=="File_Name...")then + AdvDupe2.Notify("Name field is blank.", NOTIFY_ERROR) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnGetFocus() + parent.FileName:RequestFocus() + return + end + local desc = parent.Desc:GetValue() + if(desc=="Description...")then desc="" end + AdvDupe2.SavePath = GetFullPath(node)..name + AddHistory(name) + if(game.SinglePlayer())then + RunConsoleCommand("AdvDupe2_SaveFile", name, desc, GetNodePath(node)) + else + RunConsoleCommand("AdvDupe2_SaveFile", name) + end + AdvDupe2.FileBrowser:Slide(false) + end + parent.FileName.OnEnter = function() + parent.FileName:KillFocus() + parent.Desc:SelectAllOnFocus(true) + parent.Desc.OnMousePressed() + parent.Desc:RequestFocus() + end + parent.Desc.OnEnter = parent.Submit.DoClick + end) + end + Menu:AddOption("New Folder", function() + if(parent.Expanding)then return end + parent.Submit:SetMaterial("icon16/folder_add.png") + parent.Submit:SetTooltip("Add new folder") + if(parent.FileName:GetValue()=="File_Name...")then + parent.FileName:SetText("Folder_Name...") + end + parent.Desc:SetVisible(false) + parent.Info:SetVisible(false) + parent.FileName.FirstChar = true + parent.FileName.PrevText = parent.FileName:GetValue() + parent.FileName:SetVisible(true) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnMousePressed() + parent.FileName:RequestFocus() + parent.Expanding=true + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() AddNewFolder(node) end + parent.FileName.OnEnter = parent.Submit.DoClick + end) + Menu:AddOption("Search", function() + parent.Submit:SetMaterial("icon16/find.png") + parent.Submit:SetTooltip("Search Files") + if(parent.FileName:GetValue()=="Folder_Name...")then + parent.FileName:SetText("File_Name...") + end + parent.Desc:SetVisible(false) + parent.Info:SetVisible(false) + parent.FileName.FirstChar = true + parent.FileName.PrevText = parent.FileName:GetValue() + parent.FileName:SetVisible(true) + parent.FileName:SelectAllOnFocus(true) + parent.FileName:OnMousePressed() + parent.FileName:RequestFocus() + parent.Expanding=true + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + Search(node, string.lower(parent.FileName:GetValue())) + AddHistory(parent.FileName:GetValue()) + parent.FileName:SetVisible(false) + parent.Submit:SetMaterial("icon16/arrow_undo.png") + parent.Submit:SetTooltip("Return to Browser") + parent.Info:SetVisible(true) + parent.Info:SetText(#parent.Search.pnlCanvas.Files..' files found searching for, "'..parent.FileName:GetValue()..'"') + parent.Info:SizeToContents() + parent.Submit.DoClick = function() + parent.Search:Remove() + parent.Search = nil + parent.Browser:SetVisible(true) + AdvDupe2.FileBrowser:Slide(false) + parent.Cancel:SetVisible(true) + end + parent.Cancel:SetVisible(false) + end + parent.FileName.OnEnter = parent.Submit.DoClick + end) + if(node.Label:GetText()[1]~="-")then + Menu:AddOption("Delete", function() + parent.Submit:SetMaterial("icon16/bin_empty.png") + parent.Submit:SetTooltip("Delete Folder") + parent.FileName:SetVisible(false) + parent.Desc:SetVisible(false) + if(#node.Label:GetText()>22)then + parent.Info:SetText('Are you sure that you want to delete \nthe FOLDER, "'..node.Label:GetText()..'" \nfrom your CLIENT?') + else + parent.Info:SetText('Are you sure that you want to delete \nthe FOLDER, "'..node.Label:GetText()..'" from your CLIENT?') + end + parent.Info:SizeToContents() + parent.Info:SetVisible(true) + AdvDupe2.FileBrowser:Slide(true) + parent.Submit.DoClick = function() + local path, area = GetNodePath(node) + if(area==1)then path = "-Public-/"..path end + if(area==2)then + path = "adv_duplicator/"..path.."/" + else + path = AdvDupe2.DataFolder.."/"..path.."/" + end + node.Control:RemoveNode(node) + DeleteFilesInFolders(path) + AdvDupe2.FileBrowser:Slide(false) + end + end) + end + end + if(not node.Control.Search)then + Menu:AddSpacer() + Menu:AddOption("Collapse Folder", function() if(node.ParentNode.ParentNode)then node.ParentNode:SetExpanded(false) end end) + Menu:AddOption("Collapse Root", function() CollapseParentsComplete(node) end) + if(parent.Expanded)then Menu:AddOption("Cancel Action", function() parent.Cancel:DoClick() end) end + end + + Menu:Open() +end + +local function CollapseParents(node, val) + if(not node)then return end + node.ChildList:SetTall(node.ChildList:GetTall() - val) + CollapseParents(node.ParentNode, val) +end + +function BROWSER:RemoveNode(node) + local parent = node.ParentNode + parent.Nodes = parent.Nodes - 1 + if(node.IsFolder)then + if(node.m_bExpanded)then + CollapseParents(parent, node.ChildList:GetTall()+20) + for i=1,#parent.ChildrenExpanded do + if(node == parent.ChildrenExpanded[i])then + table.remove(parent.ChildrenExpanded, i) + break + end + end + elseif(parent.m_bExpanded)then + CollapseParents(parent, 20) + end + for i=1, #parent.Folders do + if(node==parent.Folders[i])then + table.remove(parent.Folders, i) + end + end + node.ChildList:Remove() + node:Remove() + else + for i=1, #parent.Files do + if(node==parent.Files[i])then + table.remove(parent.Files, i) + end + end + CollapseParents(parent, 20) + node:Remove() + if(#parent.Files==0 and #parent.Folders==0)then + parent.Expander:Remove() + parent.Expander=nil + parent.m_bExpanded=false + end + end + if(self.VBar.Scroll>self.VBar.CanvasSize)then + self.VBar:SetScroll(self.VBar.Scroll) + end + if(self.m_pSelectedItem)then + self.m_pSelectedItem=nil + end +end + +function BROWSER:OnMouseWheeled( dlta ) + return self.VBar:OnMouseWheeled( dlta ) +end + +function BROWSER:AddFolder( text ) + local node = vgui.Create("advdupe2_browser_folder", self) + node.Control = self + + node.Offset = 0 + node.ChildrenExpanded = {} + node.Icon:SetPos(18, 1) + node.Label:SetPos(44, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.ParentNode = self + node.IsFolder = true + self.Nodes = self.Nodes + 1 + node.Folders = {} + node.Files = {} + table.insert(self.Folders, node) + self:SetTall(self:GetTall()+20) + + return node +end + +function BROWSER:AddFile( text ) + local node = vgui.Create("advdupe2_browser_file", self) + node.Control = self + node.Offset = 0 + node.Icon:SetPos(18, 1) + node.Label:SetPos(44, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.ParentNode = self + self.Nodes = self.Nodes + 1 + table.insert(self.Files, node) + self:SetTall(self:GetTall()+20) + + return node +end + +function BROWSER:Sort(node) + table.sort(node.Folders, function(a, b) return a.Label:GetText() < b.Label:GetText() end) + table.sort(node.Files, function(a, b) return a.Label:GetText() < b.Label:GetText() end) + + for i=1, #node.Folders do + node.Folders[i]:SetParent(nil) + node.Folders[i]:SetParent(node.ChildList) + node.Folders[i].ChildList:SetParent(nil) + node.Folders[i].ChildList:SetParent(node.ChildList) + end + for i=1, #node.Files do + node.Files[i]:SetParent(nil) + node.Files[i]:SetParent(node.ChildList) + end +end + +function BROWSER:SetSelected(node) + if(IsValid(self.m_pSelectedItem))then self.m_pSelectedItem:SetSelected(false) end + self.m_pSelectedItem = node + if(node)then node:SetSelected(true) end +end + +local function ExpandParents(node, val) + if(not node)then return end + node.ChildList:SetTall(node.ChildList:GetTall() + val) + ExpandParents(node.ParentNode, val) +end + +function BROWSER:Expand(node) + node.ChildList:SetTall(node.Nodes*20) + table.insert(node.ParentNode.ChildrenExpanded, node) + ExpandParents(node.ParentNode, node.Nodes*20) +end + +local function ExtendParents(node) + if(not node)then return end + node.ChildList:SetTall(node.ChildList:GetTall() + 20) + ExtendParents(node.ParentNode) +end + +function BROWSER:Extend(node) + node.ChildList:SetTall(node.ChildList:GetTall()+20) + ExtendParents(node.ParentNode) +end + +function BROWSER:Collapse(node) + CollapseParents(node.ParentNode, node.ChildList:GetTall()) + + for i=1, #node.ParentNode.ChildrenExpanded do + if(node.ParentNode.ChildrenExpanded[i] == node)then + table.remove(node.ParentNode.ChildrenExpanded, i) + break + end + end + CollapseChildren(node) +end + +function BROWSER:RenameNode(name) + self.ActionNode.Label:SetText(name) + self.ActionNode.Label:SizeToContents() + self:Sort(self.ActionNode.ParentNode) +end + +function BROWSER:MoveNode(name) + self:RemoveNode(self.ActionNode) + self.ActionNode2:AddFile(name) + self:Sort(self.ActionNode2) +end + +function BROWSER:DeleteNode() + self:RemoveNode(self.ActionNode) +end + +derma.DefineControl( "advdupe2_browser_tree", "AD2 File Browser", BROWSER, "Panel" ) + +local FOLDER = {} + +AccessorFunc( FOLDER, "m_bBackground", "PaintBackground", FORCE_BOOL ) +AccessorFunc( FOLDER, "m_bgColor", "BackgroundColor" ) + +Derma_Hook( FOLDER, "Paint", "Paint", "Panel" ) + +function FOLDER:Init() + self:SetMouseInputEnabled( true ) + + self:SetTall(20) + self:SetPaintBackground( true ) + self:SetPaintBackgroundEnabled( false ) + self:SetPaintBorderEnabled( false ) + self:SetBackgroundColor(Color(0,0,0,0)) + + + self.Icon = vgui.Create( "DImage", self ) + self.Icon:SetImage( "icon16/folder.png" ) + + self.Icon:SizeToContents() + + self.Label = vgui.Create("DLabel", self) + self.Label:SetDark(true) + + + self.m_bExpanded = false + self.Nodes = 0 + self.ChildrenExpanded = {} + + self:Dock(TOP) + + self.ChildList = vgui.Create("Panel", self:GetParent()) + self.ChildList:Dock(TOP) + self.ChildList:SetTall(0) +end + +local function ExpandNode(self) + self:GetParent():SetExpanded() +end + +function FOLDER:AddFolder(text) + if(self.Nodes==0)then + self.Expander = vgui.Create("DExpandButton", self) + self.Expander.DoClick = ExpandNode + self.Expander:SetPos(self.Offset, 2) + end + + local node = vgui.Create("advdupe2_browser_folder", self.ChildList) + node.Control = self.Control + + node.Offset = self.Offset+20 + + node.Icon:SetPos(18 + node.Offset, 1) + node.Label:SetPos(44 + node.Offset, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.ParentNode = self + node.IsFolder = true + node.Folders = {} + node.Files = {} + + self.Nodes = self.Nodes + 1 + table.insert(self.Folders, node) + + if(self.m_bExpanded)then + self.Control:Extend(self) + end + + return node +end + +function FOLDER:AddFile(text) + if(self.Nodes==0)then + self.Expander = vgui.Create("DExpandButton", self) + self.Expander.DoClick = ExpandNode + self.Expander:SetPos(self.Offset, 2) + end + + local node = vgui.Create("advdupe2_browser_file", self.ChildList) + node.Control = self.Control + node.Offset = self.Offset+20 + node.Icon:SetPos(18 + node.Offset, 1) + node.Label:SetPos(44 + node.Offset, 0) + node.Label:SetText(text) + node.Label:SizeToContents() + node.ParentNode = self + + self.Nodes = self.Nodes + 1 + table.insert(self.Files, node) + + if(self.m_bExpanded)then + self.Control:Extend(self) + end + + return node +end + +function FOLDER:SetExpanded(bool) + if(!self.Expander)then return end + if(bool==nil)then self.m_bExpanded = not self.m_bExpanded else self.m_bExpanded = bool end + self.Expander:SetExpanded(self.m_bExpanded) + if(self.m_bExpanded)then + self.Control:Expand(self) + else + self.Control:Collapse(self) + end +end + +function FOLDER:SetSelected(bool) + if(bool)then + self:SetBackgroundColor(self:GetSkin().bg_color_bright) + else + self:SetBackgroundColor(Color(0,0,0,0)) + end +end + +function FOLDER:OnMousePressed(code) + if(code==107)then + self.Control:DoNodeLeftClick(self) + elseif(code==108)then + self.Control:DoNodeRightClick(self) + end +end + +derma.DefineControl( "advdupe2_browser_folder", "AD2 Browser Folder node", FOLDER, "Panel" ) + + + + + + +local FILE = {} + +AccessorFunc( FILE, "m_bBackground", "PaintBackground", FORCE_BOOL ) +AccessorFunc( FILE, "m_bgColor", "BackgroundColor" ) +Derma_Hook( FILE, "Paint", "Paint", "Panel" ) + +function FILE:Init() + self:SetMouseInputEnabled( true ) + + self:SetTall(20) + self:SetPaintBackground(true) + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled( false ) + self:SetBackgroundColor(Color(0,0,0,0)) + + self.Icon = vgui.Create( "DImage", self ) + self.Icon:SetImage( "icon16/page.png" ) + + self.Icon:SizeToContents() + + self.Label = vgui.Create("DLabel", self) + self.Label:SetDark(true) + + self:Dock(TOP) +end + +function FILE:SetSelected(bool) + if(bool)then + self:SetBackgroundColor(self:GetSkin().bg_color_bright) + else + self:SetBackgroundColor(Color(0,0,0,0)) + end +end + +function FILE:OnMousePressed(code) + if(code==107)then + self.Control:DoNodeLeftClick(self) + elseif(code==108)then + self.Control:DoNodeRightClick(self) + end +end +derma.DefineControl( "advdupe2_browser_file", "AD2 Browser File node", FILE, "Panel" ) + +local PANEL = {} +AccessorFunc( PANEL, "m_bBackground", "PaintBackground", FORCE_BOOL ) +AccessorFunc( PANEL, "m_bgColor", "BackgroundColor" ) +Derma_Hook( PANEL, "Paint", "Paint", "Panel" ) +Derma_Hook( PANEL, "PerformLayout", "Layout", "Panel" ) + +function PANEL:PerformLayout() + if(self:GetWide()==self.LastX)then return end + local x = self:GetWide() + + if(self.Search)then + self.Search:SetWide(x) + end + + self.Browser:SetWide(x) + local x2, y2 = self.Browser:GetPos() + local BtnX = x - self.Help:GetWide() - 5 + self.Help:SetPos(BtnX, 3) + BtnX = BtnX - self.Refresh:GetWide() - 5 + self.Refresh:SetPos(BtnX, 3) + + BtnX = x - self.Submit:GetWide() - 15 + self.Cancel:SetPos(BtnX, self.Browser:GetTall()+20) + BtnX = BtnX - self.Submit:GetWide() - 5 + self.Submit:SetPos(BtnX, self.Browser:GetTall()+20) + + self.FileName:SetWide(BtnX - 10) + self.FileName:SetPos(5, self.Browser:GetTall()+20) + self.Desc:SetWide(x-10) + self.Desc:SetPos(5, self.Browser:GetTall()+39) + self.Info:SetPos(5, self.Browser:GetTall()+20) + + self.LastX = x +end + +local pnlorigsetsize +local function PanelSetSize(self, x, y) + if(not self.LaidOut)then + pnlorigsetsize(self, x, y) + + self.Browser:SetSize(x, y-20) + self.Browser:SetPos(0, 20) + + if(self.Search)then + self.Search:SetSize(x, y-20) + self.Search:SetPos(0, 20) + end + + self.LaidOut = true + else + pnlorigsetsize(self, x, y) + end + +end + +local function PurgeFiles(path, curParent) + local files, directories = file.Find(path.."*", "DATA") + if(directories)then + for k,v in pairs(directories)do + curParent = curParent:AddFolder(v) + PurgeFiles(path..v.."/", curParent) + curParent = curParent.ParentNode + end + end + + if(files)then + for k,v in pairs(files)do + curParent:AddFile(string.sub(v, 1, #v-4)) + end + end +end + +local function UpdateClientFiles() + + for i=1,2 do + if(AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[1])then + AdvDupe2.FileBrowser.Browser.pnlCanvas:RemoveNode(AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[1]) + end + end + + PurgeFiles("advdupe2/", AdvDupe2.FileBrowser.Browser.pnlCanvas:AddFolder("-Advanced Duplicator 2-")) + + PurgeFiles("adv_duplicator/", AdvDupe2.FileBrowser.Browser.pnlCanvas:AddFolder("-Advanced Duplicator 1-")) + + if(AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[2])then + if(#AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[2].Folders == 0 and #AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[2].Files == 0)then + AdvDupe2.FileBrowser.Browser.pnlCanvas:RemoveNode(AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[2]) + end + + AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[1]:SetParent(nil) + AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[1]:SetParent(AdvDupe2.FileBrowser.Browser.pnlCanvas.ChildList) + AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[1].ChildList:SetParent(nil) + AdvDupe2.FileBrowser.Browser.pnlCanvas.Folders[1].ChildList:SetParent(AdvDupe2.FileBrowser.Browser.pnlCanvas.ChildList) + end + +end + +function PANEL:Init() + + AdvDupe2.FileBrowser = self + self.Expanded = false + self.Expanding = false + self.LastX = 0 + self.LastY = 0 + pnlorigsetsize = self.SetSize + self.SetSize = PanelSetSize + + self:SetPaintBackground(true) + self:SetPaintBackgroundEnabled(false) + self:SetBackgroundColor(self:GetSkin().bg_color_bright) + + self.Browser = vgui.Create("advdupe2_browser_panel", self) + UpdateClientFiles() + self.Refresh = vgui.Create("DImageButton", self) + self.Refresh:SetMaterial( "icon16/arrow_refresh.png" ) + self.Refresh:SizeToContents() + self.Refresh:SetTooltip("Refresh Files") + self.Refresh.DoClick = function(button) + UpdateClientFiles() + end + + self.Help = vgui.Create("DImageButton", self) + self.Help:SetMaterial( "icon16/help.png" ) + self.Help:SizeToContents() + self.Help:SetTooltip("Help Section") + self.Help.DoClick = function(btn) + local Menu = DermaMenu() + Menu:AddOption("Bug Reporting", function() gui.OpenURL("https://github.com/wiremod/advdupe2/issues") end) + Menu:AddOption("Controls", function() gui.OpenURL("https://github.com/wiremod/advdupe2/wiki/Controls") end) + Menu:AddOption("Commands", function() gui.OpenURL("https://github.com/wiremod/advdupe2/wiki/Server-settings") end) + Menu:Open() + end + + self.Submit = vgui.Create("DImageButton", self) + self.Submit:SetMaterial( "icon16/page_save.png" ) + self.Submit:SizeToContents() + self.Submit:SetTooltip("Confirm Action") + self.Submit.DoClick = function() + self.Expanding=true + AdvDupe2.FileBrowser:Slide(false) + end + + self.Cancel = vgui.Create("DImageButton", self) + self.Cancel:SetMaterial( "icon16/cross.png" ) + self.Cancel:SizeToContents() + self.Cancel:SetTooltip("Cancel Action") + self.Cancel.DoClick = function() self.Expanding=true AdvDupe2.FileBrowser:Slide(false) end + + self.FileName = vgui.Create("DTextEntry", self) + self.FileName:SetAllowNonAsciiCharacters( true ) + self.FileName:SetText("File_Name...") + self.FileName.Last = 0 + self.FileName.OnEnter = function() + self.FileName:KillFocus() + self.Desc:SelectAllOnFocus(true) + self.Desc.OnMousePressed() + self.Desc:RequestFocus() + end + self.FileName.OnMousePressed = function() + self.FileName:OnGetFocus() + if(self.FileName:GetValue()=="File_Name..." or self.FileName:GetValue()=="Folder_Name...")then + self.FileName:SelectAllOnFocus(true) + end + end + self.FileName:SetUpdateOnType(true) + self.FileName.OnTextChanged = function() + + if(self.FileName.FirstChar)then + if(string.lower(self.FileName:GetValue()[1]) == string.lower(input.LookupBinding( "menu" )))then + self.FileName:SetText(self.FileName.PrevText) + self.FileName:SelectAll() + self.FileName.FirstChar = false + else + self.FileName.FirstChar = false + end + end + + local new, changed = self.FileName:GetValue():gsub("[^%w_ ]","") + if changed > 0 then + self.FileName:SetText(new) + self.FileName:SetCaretPos(#new) + end + if(#self.FileName:GetValue()>0)then + NarrowHistory(self.FileName:GetValue(), self.FileName.Last) + local options = {} + if(#Narrow>4)then + for i=1, 4 do + table.insert(options, Narrow[i]) + end + else + options = Narrow + end + if(#options~=0 and #self.FileName:GetValue()~=0)then + self.FileName.HistoryPos = 0 + self.FileName:OpenAutoComplete(options) + self.FileName.Menu.Attempts = 1 + if(#Narrow>4)then + self.FileName.Menu:AddOption("...", function() end) + end + elseif(IsValid(self.FileName.Menu))then + self.FileName.Menu:Remove() + end + end + self.FileName.Last = #self.FileName:GetValue() + end + self.FileName.OnKeyCodeTyped = function(txtbox, code) + txtbox:OnKeyCode( code ) + + if ( code == KEY_ENTER && !txtbox:IsMultiline() && txtbox:GetEnterAllowed() ) then + if(txtbox.HistoryPos == 5 and txtbox.Menu:ChildCount()==5)then + if((txtbox.Menu.Attempts+1)*4<#Narrow)then + for i=1,4 do + txtbox.Menu:GetChild(i):SetText(Narrow[i+txtbox.Menu.Attempts*4]) + end + else + txtbox.Menu:GetChild(5):Remove() + for i=4, (txtbox.Menu.Attempts*4-#Narrow)*-1+1, -1 do + txtbox.Menu:GetChild(i):Remove() + end + + for i=1, #Narrow-txtbox.Menu.Attempts*4 do + txtbox.Menu:GetChild(i):SetText(Narrow[i+txtbox.Menu.Attempts*4]) + end + end + txtbox.Menu:ClearHighlights() + txtbox.Menu:HighlightItem(txtbox.Menu:GetChild(1)) + txtbox.HistoryPos = 1 + txtbox.Menu.Attempts = txtbox.Menu.Attempts+1 + return true + end + + if ( IsValid( txtbox.Menu ) ) then + txtbox.Menu:Remove() + end + txtbox:FocusNext() + txtbox:OnEnter() + txtbox.HistoryPos = 0 + end + + if ( txtbox.m_bHistory || IsValid( txtbox.Menu ) ) then + if ( code == KEY_UP ) then + txtbox.HistoryPos = txtbox.HistoryPos - 1; + if(txtbox.HistoryPos~=-1 || txtbox.Menu:ChildCount()~=5)then + txtbox:UpdateFromHistory() + else + txtbox.Menu:ClearHighlights() + txtbox.Menu:HighlightItem( txtbox.Menu:GetChild(5) ) + txtbox.HistoryPos=5 + end + end + if ( code == KEY_DOWN || code == KEY_TAB ) then + txtbox.HistoryPos = txtbox.HistoryPos + 1; + if(txtbox.HistoryPos~=5 || txtbox.Menu:ChildCount()~=5)then + txtbox:UpdateFromHistory() + else + txtbox.Menu:ClearHighlights() + txtbox.Menu:HighlightItem( txtbox.Menu:GetChild(5) ) + end + end + + end + end + self.FileName.OnValueChange = function() + if(self.FileName:GetValue()~="File_Name..." and self.FileName:GetValue()~="Folder_Name...")then + local new,changed = self.FileName:GetValue():gsub("[^%w_ ]","") + if changed > 0 then + self.FileName:SetText(new) + self.FileName:SetCaretPos(#new) + end + end + end + + self.Desc = vgui.Create("DTextEntry", self) + self.Desc.OnEnter = self.Submit.DoClick + self.Desc:SetText("Description...") + self.Desc.OnMousePressed = function() + self.Desc:OnGetFocus() + if(self.Desc:GetValue()=="Description...")then + self.Desc:SelectAllOnFocus(true) + end + end + + self.Info = vgui.Create("DLabel", self) + self.Info:SetVisible(false) + +end + +function PANEL:Slide(expand) + if(expand)then + if(self.Expanded)then + self:SetTall(self:GetTall()-40) self.Expanded=false + else + self:SetTall(self:GetTall()+5) + end + else + if(not self.Expanded)then + self:SetTall(self:GetTall()+40) self.Expanded=true + else + self:SetTall(self:GetTall()-5) + end + end + count = count+1 + if(count<9)then + timer.Simple(0.01, function() self:Slide(expand) end) + else + if(expand)then + self.Expanded=true + else + self.Expanded=false + end + self.Expanding = false + count = 0 + end +end + +function PANEL:GetFullPath(node) + return GetFullPath(node) +end + +function PANEL:GetNodePath(node) + return GetNodePath(node) +end + +if(game.SinglePlayer())then + usermessage.Hook("AdvDupe2_AddFile", function(um) + if(um:ReadBool())then + if(IsValid(AdvDupe2.FileBrowser.AutoSaveNode))then + local name = um:ReadString() + for i=1, #AdvDupe2.FileBrowser.AutoSaveNode.Files do + if(name==AdvDupe2.FileBrowser.AutoSaveNode.Files[i])then + return + end + end + + AdvDupe2.FileBrowser.AutoSaveNode:AddFile(name) + AdvDupe2.FileBrowser.AutoSaveNode.Control:Sort(AdvDupe2.FileBrowser.AutoSaveNode) + end + else + AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode:AddFile(um:ReadString()) + AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode.Control:Sort(AdvDupe2.FileBrowser.Browser.pnlCanvas.ActionNode) + end + end) +end +vgui.Register("advdupe2_browser", PANEL, "Panel") diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/nullesc.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/nullesc.lua new file mode 100644 index 0000000..d22628d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/nullesc.lua @@ -0,0 +1,44 @@ +local char = string.char +local find = string.find +local gsub = string.gsub +local match = string.match + +local Null = {} + +local escseq = { --no palindromes + "bbq", + "wtf", + "cat", + "car", + "bro", + "moo", + "sky", +} + +function Null.esc(str) + local genseq + for i=1,#escseq do + if not find(str, escseq[i]) then + local genseq = escseq[i] + return genseq.."\n"..gsub(str,"%z",genseq) + end + end + for i=30,200 do + genseq = char(i, i-1, i+1) + if not find(str, genseq) then + return genseq.."\n"..gsub(str,"%z",genseq) + end + genseq = char(i, i, i+1) + if not find(str, genseq) then + return genseq.."\n"..gsub(str,"%z",genseq) + end + end + error("nullesc could not escape the string") +end + +function Null.invesc(str) + local delim,huff = match(str,"^(.-)\n(.-)$") + return gsub(huff,delim,"\0") +end + +AdvDupe2.Null = Null diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sh_codec.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sh_codec.lua new file mode 100644 index 0000000..1c0749c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sh_codec.lua @@ -0,0 +1,517 @@ +--[[ + Title: Adv. Dupe 2 Codec + + Desc: Dupe encoder/decoder. + + Author: emspike + + Version: 2.0 +]] + +local REVISION = 5 + +include "sh_codec_legacy.lua" +AddCSLuaFile "sh_codec_legacy.lua" + +local pairs = pairs +local type = type +local error = error +local Vector = Vector +local Angle = Angle +local format = string.format +local char = string.char +local byte = string.byte +local sub = string.sub +local gsub = string.gsub +local find = string.find +local gmatch = string.gmatch +local match = string.match +local concat = table.concat +local compress = util.Compress +local decompress = util.Decompress + +AdvDupe2.CodecRevision = REVISION + + +--[[ + Name: GenerateDupeStamp + Desc: Generates an info table. + Params: ply + Return: stamp +]] +function AdvDupe2.GenerateDupeStamp(ply) + local stamp = {} + stamp.name = ply:GetName() + stamp.time = os.date("%I:%M %p") + stamp.date = os.date("%d %B %Y") + stamp.timezone = os.date("%z") + hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp) + return stamp +end + +local function makeInfo(tbl) + local info = "" + for k,v in pairs(tbl) do + info = concat{info,k,"\1",v,"\1"} + end + return info.."\2" +end + +local AD2FF = "AD2F%s\n%s\n%s" + +local tables +local buff + +local function noserializer() end + +local enc = {} +for i=1,255 do enc[i] = noserializer end + +local function isArray(tbl) + local ret = true + local m = 0 + + for k, v in pairs(tbl) do + m = m + 1 + if k ~= m or enc[TypeID(v)]==noserializer then + ret = false + break + end + end + + return ret +end + +local function write(obj) + enc[TypeID(obj)](obj) +end + +local len +local tables,tablesLookup + +enc[TYPE_TABLE] = function(obj) --table + tables = tables + 1 + if not tablesLookup[obj] then + tablesLookup[obj] = tables + else + buff:WriteByte(247) + buff:WriteShort(tablesLookup[obj]) + return + end + + if isArray(obj) then + buff:WriteByte(254) + for i,v in pairs(obj) do + write(v) + end + else + buff:WriteByte(255) + for k,v in pairs(obj) do + if(enc[TypeID(k)]!=noserializer and enc[TypeID(v)]!=noserializer)then + write(k) + write(v) + end + end + end + buff:WriteByte(246) +end +enc[TYPE_BOOL] = function(obj) --boolean + buff:WriteByte(obj and 253 or 252) +end +enc[TYPE_NUMBER] = function(obj) --number + buff:WriteByte(251) + buff:WriteDouble(obj) +end +enc[TYPE_VECTOR] = function(obj) --vector + buff:WriteByte(250) + buff:WriteDouble(obj.x) + buff:WriteDouble(obj.y) + buff:WriteDouble(obj.z) +end +enc[TYPE_ANGLE] = function(obj) --angle + buff:WriteByte(249) + buff:WriteDouble(obj.p) + buff:WriteDouble(obj.y) + buff:WriteDouble(obj.r) +end +enc[TYPE_STRING] = function(obj) --string + + len = #obj + + if len < 246 then + buff:WriteByte(len) + buff:Write(obj) + else + buff:WriteByte(248) + buff:WriteULong(len) + buff:Write(obj) + end + +end + +local function error_nodeserializer() + buff:Seek(buff:Tell()-1) + error(format("couldn't find deserializer for type {typeid:%d}", buff:ReadByte())) +end + +local read4, read5 + +local reference = 0 +do --Version 4 + local dec = {} + for i=1,255 do dec[i] = error_nodeserializer end + + local function read() + local tt = buff:ReadByte() + if not tt then + error("expected value, got EOF") + end + if tt == 0 then + return nil + end + return dec[tt]() + end + read4 = read + + dec[255] = function() --table + local t = {} + local k + reference = reference + 1 + local ref = reference + repeat + k = read() + if k ~= nil then + t[k] = read() + end + until (k == nil) + tables[ref] = t + return t + end + + dec[254] = function() --array + local t = {} + local k,v = 0 + reference = reference + 1 + local ref = reference + repeat + k = k + 1 + v = read() + if(v != nil) then + t[k] = v + end + + until (v == nil) + tables[ref] = t + return t + end + + dec[253] = function() + return true + end + dec[252] = function() + return false + end + dec[251] = function() + return buff:ReadDouble() + end + dec[250] = function() + return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[249] = function() + return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[248] = function() --null-terminated string + local start = buff:Tell() + local slen = 0 + + while buff:ReadByte() != 0 do + slen = slen + 1 + end + + buff:Seek(start) + + local retv = buff:Read(slen) + if(not retv)then retv="" end + buff:ReadByte() + + return retv + end + dec[247] = function() --table reference + reference = reference + 1 + return tables[buff:ReadShort()] + end + + for i=1,246 do dec[i] = function() return buff:Read(i) end end +end + +do --Version 5 + local dec = {} + for i=1,255 do dec[i] = error_nodeserializer end + + local function read() + local tt = buff:ReadByte() + if not tt then + error("expected value, got EOF") + end + return dec[tt]() + end + read5 = read + + dec[255] = function() --table + local t = {} + reference = reference + 1 + tables[reference] = t + + for k in read do + t[k] = read() + end + + return t + end + + dec[254] = function() --array + local t = {} + reference = reference + 1 + tables[reference] = t + + local k = 1 + for v in read do + t[k] = v + k = k + 1 + end + + return t + end + + dec[253] = function() + return true + end + dec[252] = function() + return false + end + dec[251] = function() + return buff:ReadDouble() + end + dec[250] = function() + return Vector(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[249] = function() + return Angle(buff:ReadDouble(),buff:ReadDouble(),buff:ReadDouble()) + end + dec[248] = function() -- Length>246 string + local slen = buff:ReadULong() + local retv = buff:Read(slen) + if(not retv)then retv="" end + return retv + end + dec[247] = function() --table reference + return tables[buff:ReadShort()] + end + dec[246] = function() --nil + return + end + + for i=1,245 do dec[i] = function() return buff:Read(i) end end + dec[0] = function() return "" end +end + +local function serialize(tbl) + tables = 0 + tablesLookup = {} + + buff = file.Open("ad2temp.txt", "wb", "DATA") + write(tbl) + buff:Close() + + buff = file.Open("ad2temp.txt","rb","DATA") + local ret = buff:Read(buff:Size()) + buff:Close() + return ret +end + + +local function deserialize(str, read) + + if(str==nil)then + error("File could not be decompressed.") + return {} + end + + tables = {} + reference = 0 + buff = file.Open("ad2temp.txt","wb","DATA") + buff:Write(str) + buff:Flush() + buff:Close() + + buff = file.Open("ad2temp.txt","rb", "DATA") + local success, tbl = pcall(read) + buff:Close() + + if success then + return tbl + else + error(tbl) + end +end + +--[[ + Name: Encode + Desc: Generates the string for a dupe file with the given data. + Params:
dupe,
info, callback, <...> args + Return: runs callback( encoded_dupe, <...> args) +]] +function AdvDupe2.Encode(dupe, info, callback, ...) + + local encodedTable = compress(serialize(dupe)) + info.check = "\r\n\t\n" + info.size = #encodedTable + + callback(AD2FF:format(char(REVISION), makeInfo(info), encodedTable),...) + +end + +--seperates the header and body and converts the header to a table +local function getInfo(str) + local last = str:find("\2") + if not last then + error("attempt to read AD2 file with malformed info block") + end + local info = {} + local ss = str:sub(1,last-1) + for k,v in ss:gmatch("(.-)\1(.-)\1") do + info[k] = v + end + + if info.check ~= "\r\n\t\n" then + if info.check == "\10\9\10" then + error("detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)") + else + error("attempt to read AD2 file with malformed info block") + end + end + return info, str:sub(last+2) +end + +--decoders for individual versions go here +local versions = {} + +versions[1] = AdvDupe2.LegacyDecoders[1] +versions[2] = AdvDupe2.LegacyDecoders[2] + +versions[3] = function(encodedDupe) + encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n", "\n") + encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n") + return versions[4](encodedDupe) +end + +versions[4] = function(encodedDupe) + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize(decompress(dupestring), read4), info +end + +versions[5] = function(encodedDupe) + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize(decompress(dupestring), read5), info +end + + +function AdvDupe2.CheckValidDupe(dupe, info) + if not dupe.HeadEnt then return false, "Missing HeadEnt table" end + if not dupe.HeadEnt.Index then return false, "Missing HeadEnt.Index" end + if not dupe.HeadEnt.Z then return false, "Missing HeadEnt.Z" end + if not dupe.HeadEnt.Pos then return false, "Missing HeadEnt.Pos" end + if not dupe.Entities then return false, "Missing Entities table" end + if not dupe.Entities[dupe.HeadEnt.Index] then return false, "Missing HeadEnt index from Entities table" end + if not dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects then return false, "Missing PhysicsObject table from HeadEnt Entity table" end + if not dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects[0] then return false, "Missing PhysicsObject[0] table from HeadEnt Entity table" end + if info.ad1 then + if not dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects[0].LocalPos then return false, "Missing PhysicsObject[0].LocalPos from HeadEnt Entity table" end + if not dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects[0].LocalAngle then return false, "Missing PhysicsObject[0].LocalAngle from HeadEnt Entity table" end + else + if not dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects[0].Pos then return false, "Missing PhysicsObject[0].Pos from HeadEnt Entity table" end + if not dupe.Entities[dupe.HeadEnt.Index].PhysicsObjects[0].Angle then return false, "Missing PhysicsObject[0].Angle from HeadEnt Entity table" end + end + return true, dupe +end + +--[[ + Name: Decode + Desc: Generates the table for a dupe from the given string. Inverse of Encode + Params: encodedDupe, callback, <...> args + Return: runs callback( success,
tbl,
info) +]] +function AdvDupe2.Decode(encodedDupe) + + local sig, rev = encodedDupe:match("^(....)(.)") + + if not rev then + return false, "malformed dupe (wtf <5 chars long?!)" + end + + rev = rev:byte() + + if sig ~= "AD2F" then + if sig == "[Inf" then --legacy support, ENGAGE (AD1 dupe detected) + local success, tbl, info, moreinfo = pcall(AdvDupe2.LegacyDecoders[0], encodedDupe) + + if success then + info.size = #encodedDupe + info.revision = 0 + info.ad1 = true + + local index = tonumber(moreinfo.Head) or (istable(tbl.Entities) and next(tbl.Entities)) + if not index then return false, "Missing head index" end + local pos + if isstring(moreinfo.StartPos) then + local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$") + pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0) + else + pos = Vector() + end + local z + if isstring(moreinfo.HoldPos) then + z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1 + else + z = 0 + end + tbl.HeadEnt = { + Index = index, + Pos = pos, + Z = z + } + + else + ErrorNoHalt(tbl) + end + + if success then + success, tbl = AdvDupe2.CheckValidDupe(tbl, info) + end + + return success, tbl, info, moreinfo + else + return false, "unknown duplication format" + end + elseif rev > REVISION then + return false, format("Newer codec needed. (have rev %u, need rev %u) Update Advdupe2.",REVISION,rev) + elseif rev < 1 then + return false, format("attempt to use an invalid format revision (rev %d)", rev) + else + local success, tbl, info = pcall(versions[rev], encodedDupe) + + if success then + success, tbl = AdvDupe2.CheckValidDupe(tbl, info) + end + + if success then + info.revision = rev + else + ErrorNoHalt(tbl) + end + + return success, tbl, info + end +end diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sh_codec_legacy.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sh_codec_legacy.lua new file mode 100644 index 0000000..a3f0abe --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sh_codec_legacy.lua @@ -0,0 +1,560 @@ +--[[ + Title: Adv. Dupe 2 Codec Legacy Support + + Desc: Facilitates opening of dupes from AD1 and earlier AD2 versions. + + Author: emspike + + Version: 2.0 +]] + +local pairs = pairs +local type = type +local tonumber = tonumber +local error = error +local Vector = Vector +local Angle = Angle +local unpack = unpack +local format = string.format +local char = string.char +local byte = string.byte +local sub = string.sub +local gsub = string.gsub +local find = string.find +local gmatch = string.gmatch +local match = string.match +local concat = table.concat + +--[[ + Name: GenerateDupeStamp + Desc: Generates an info table. + Params: ply + Return:
stamp +]] +function AdvDupe2.GenerateDupeStamp(ply) + local stamp = {} + stamp.name = ply:GetName() + stamp.time = os.date("%I:%M %p") + stamp.date = os.date("%d %B %Y") + stamp.timezone = os.date("%z") + hook.Call("AdvDupe2_StampGenerated",GAMEMODE,stamp) + return stamp +end + +local AD2FF = "AD2F%s\n%s\n%s" + +local decode_types_v1, decode_types_v2 +local tables = 0 +local str,pos +local a,b,c,m,n,w,tblref + +local function read_v2() + local t = byte(str, pos+1) + if t then + local dt = decode_types_v2[t] + if dt then + pos = pos + 1 + return dt() + else + error(format("encountered invalid data type (%u)\n",t)) + end + else + error("expected value, got EOF\n") + end +end + +decode_types_v2 = { + [1 ] = function() + error("expected value, got terminator\n") + end, + [2 ] = function() -- table + + m = find(str, "\1", pos) + if m then + w = sub(str, pos+1, m-1) + pos = m + else + error("expected table identifier, got EOF\n") + end + + local t = {} + tables[w] = t + + while true do + if byte(str, pos+1) == 1 then + pos = pos + 1 + return t + else + t[read_v2()] = read_v2() + end + end + end, + [3 ] = function() -- array + + m = find(str, "\1", pos) + if m then + w = sub(str, pos+1, m-1) + pos = m + else + error("expected table identifier, got EOF\n") + end + + local t, i = {}, 1 + + tables[w] = t + + while true do + if byte(str,pos+1) == 1 then + pos = pos+1 + return t + else + t[i] = read_v2() + i = i + 1 + end + end + end, + [4 ] = function() -- false boolean + return false + end, + [5 ] = function() -- true boolean + return true + end, + [6 ] = function() -- number + m = find(str, "\1", pos) + if m then + a = tonumber(sub(str, pos+1, m-1)) or 0 + pos = m + return a + else + error("expected number, got EOF\n") + end + end, + [7 ] = function() -- string + m = find(str,"\1",pos) + if m then + w = sub(str, pos+1, m-1) + pos = m + return w + else + error("expected string, got EOF\n") + end + end, + [8 ] = function() -- Vector + m,n = find(str,".-\1.-\1.-\1", pos) + if m then + a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Vector(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected vector, got EOF\n") + end + end, + [9 ] = function() -- Angle + m,n = find(str, ".-\1.-\1.-\1", pos) + if m then + a,b,c = match(str, "^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Angle(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected angle, got EOF\n") + end + end, + [10 ] = function() -- Table Reference + m = find(str,"\1",pos) + if m then + w = sub(str,pos+1,m-1) + pos = m + else + error("expected table identifier, got EOF\n") + end + tblref = tables[w] + + if tblref then + return tblref + else + error(format("table identifier %s points to nil\n", w)) + end + + end +} + + + +local function read_v1() + local t = byte(str,pos+1) + if t then + local dt = decode_types_v1[t] + if dt then + pos = pos + 1 + return dt() + else + error(format("encountered invalid data type (%u)\n",t)) + end + else + error("expected value, got EOF\n") + end +end + +decode_types_v1 = { + [1 ] = function() + error("expected value, got terminator\n") + end, + [2 ] = function() -- table + local t = {} + while true do + if byte(str,pos+1) == 1 then + pos = pos+1 + return t + else + t[read_v1()] = read_v1() + end + end + end, + [3 ] = function() -- array + local t, i = {}, 1 + while true do + if byte(str,pos+1) == 1 then + pos = pos+1 + return t + else + t[i] = read_v1() + i = i + 1 + end + end + end, + [4 ] = function() -- false boolean + return false + end, + [5 ] = function() -- true boolean + return true + end, + [6 ] = function() -- number + m = find(str,"\1",pos) + if m then + a = tonumber(sub(str,pos+1,m-1)) or 0 + pos = m + return a + else + error("expected number, got EOF\n") + end + end, + [7 ] = function() -- string + m = find(str,"\1",pos) + if m then + w = sub(str,pos+1,m-1) + pos = m + return w + else + error("expected string, got EOF\n") + end + end, + [8 ] = function() -- Vector + m,n = find(str,".-\1.-\1.-\1",pos) + if m then + a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Vector(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected vector, got EOF\n") + end + end, + [9 ] = function() -- Angle + m,n = find(str,".-\1.-\1.-\1",pos) + if m then + a,b,c = match(str,"^(.-)\1(.-)\1(.-)\1",pos+1) + pos = n + return Angle(tonumber(a), tonumber(b), tonumber(c)) + else + error("expected angle, got EOF\n") + end + end +} + +local function deserialize_v1(data) + str = data + pos = 0 + tables = {} + return read_v1() +end + +local function deserialize_v2(data) + str = data + pos = 0 + tables = {} + return read_v2() +end + +local function lzwDecode(encoded) + local dictionary_length = 256 + local dictionary = {} + for i = 0, 255 do + dictionary[i] = char(i) + end + + local pos = 2 + local decompressed = {} + local decompressed_length = 1 + + local index = byte(encoded) + local word = dictionary[index] + + decompressed[decompressed_length] = dictionary[index] + + local entry + local encoded_length = #encoded + local firstbyte --of an index + while pos <= encoded_length do + firstbyte = byte(encoded,pos) + if firstbyte > 252 then --now we know it's a length indicator for a multibyte index + index = 0 + firstbyte = 256 - firstbyte + + --[[if pos+firstbyte > encoded_length then --will test for performance impact + error("expected index got EOF") + end]] + + for i = pos+firstbyte, pos+1, -1 do + index = bit.bor(bit.lshift(index, 8), byte(encoded,i)) + end + pos = pos + firstbyte + 1 + else + index = firstbyte + pos = pos + 1 + end + entry = dictionary[index] or (word..sub(word,1,1)) + decompressed_length = decompressed_length + 1 + decompressed[decompressed_length] = entry + dictionary[dictionary_length] = word..sub(entry,1,1) + dictionary_length = dictionary_length + 1 + word = entry + end + return concat(decompressed) +end + +--http://en.wikipedia.org/wiki/Huffman_coding#Decompression + +local invcodes = {[2]={[0]="\254"},[5]={[22]="\1",[11]="\2"},[6]={[13]="\7",[35]="\6",[37]="\5",[58]="\3",[31]="\8",[9]="\13",[51]="\9",[55]="\10",[57]="\4",[59]="\15"},[7]={[1]="\14",[15]="\16",[87]="\31",[89]="\30",[62]="\26",[17]="\27",[97]="\19",[19]="\43",[10]="\12",[39]="\33",[41]="\24",[82]="\40",[3]="\32",[46]="\41",[47]="\38",[94]="\25",[65]="\23",[50]="\39",[26]="\11",[7]="\28",[33]="\18",[61]="\17",[25]="\42"},[8]={[111]="\101",[162]="\29",[2]="\34",[133]="\21",[142]="\36",[5]="\20",[21]="\37",[170]="\44",[130]="\22",[66]="\35"},[9]={[241]="\121",[361]="\104",[365]="\184",[125]="\227",[373]="\198",[253]="\117",[381]="\57",[270]="\49",[413]="\80",[290]="\47",[294]="\115",[38]="\112",[429]="\74",[433]="\0",[437]="\48",[158]="\183",[453]="\107",[166]="\111",[469]="\182",[477]="\241",[45]="\86",[489]="\69",[366]="\100",[497]="\61",[509]="\76",[49]="\53",[390]="\78",[279]="\196",[283]="\70",[414]="\98",[53]="\55",[422]="\109",[233]="\79",[349]="\89",[369]="\52",[14]="\105",[238]="\56",[319]="\162",[323]="\83",[327]="\63",[458]="\65",[335]="\231",[339]="\225",[337]="\114",[347]="\193",[493]="\139",[23]="\209",[359]="\250",[490]="\68",[42]="\54",[63]="\91",[286]="\97",[254]="\50",[510]="\108",[109]="\73",[67]="\103",[255]="\122",[69]="\170",[70]="\110",[407]="\176",[411]="\119",[110]="\120",[83]="\146",[149]="\163",[151]="\224",[85]="\51",[155]="\177",[79]="\251",[27]="\118",[447]="\159",[451]="\228",[455]="\175",[383]="\174",[463]="\243",[467]="\157",[173]="\210",[475]="\167",[177]="\84",[90]="\45",[487]="\206",[93]="\226",[495]="\245",[207]="\64",[127]="\147",[191]="\155",[511]="\153",[195]="\208",[197]="\85",[199]="\178",[181]="\82",[102]="\116",[103]="\71",[285]="\144",[105]="\102",[211]="\199",[213]="\123",[301]="\66",[305]="\46",[219]="\137",[81]="\67",[91]="\88",[157]="\130",[325]="\95",[29]="\58",[231]="\201",[117]="\99",[341]="\222",[237]="\77",[239]="\211",[71]="\223"},[10]={[710]="\149",[245]="\60",[742]="\172",[774]="\81",[134]="\151",[917]="\145",[274]="\216",[405]="\242",[146]="\194",[838]="\246",[298]="\248",[870]="\189",[1013]="\150",[894]="\190",[326]="\244",[330]="\166",[334]="\217",[465]="\179",[346]="\59",[354]="\180",[966]="\212",[974]="\143",[370]="\148",[998]="\154",[625]="\138",[382]="\161",[194]="\141",[198]="\126",[402]="\96",[206]="\185",[586]="\129",[721]="\187",[610]="\135",[618]="\181",[626]="\72",[226]="\62",[454]="\127",[658]="\113",[462]="\164",[234]="\214",[474]="\140",[242]="\106",[714]="\188",[730]="\87",[498]="\237",[746]="\125",[754]="\229",[786]="\128",[202]="\93",[18]="\255",[810]="\173",[846]="\131",[74]="\192",[842]="\142",[977]="\252",[858]="\235",[78]="\134",[874]="\234",[882]="\90",[646]="\92",[1006]="\160",[126]="\165",[914]="\221",[718]="\94",[738]="\238",[638]="\197",[482]="\230",[34]="\220",[962]="\133",[6]="\213",[706]="\219",[986]="\171",[994]="\233",[866]="\200",[1010]="\247",[98]="\169",[518]="\236",[494]="\207",[230]="\205",[542]="\191",[501]="\202",[530]="\203",[450]="\204",[209]="\158",[106]="\186",[590]="\136",[218]="\232",[733]="\124",[309]="\168",[221]="\152",[757]="\240",[113]="\215",[114]="\156",[362]="\239",[486]="\132",[358]="\249",[262]="\75",[30]="\218",[821]="\195",[546]="\253"}} + +local function huffmanDecode(encoded) + + local h1,h2,h3 = byte(encoded, 1, 3) + + if (not h3) or (#encoded < 4) then + error("invalid input") + end + + local original_length = bit.bor(bit.lshift(h3,16), bit.lshift(h2,8), h1) + local encoded_length = #encoded+1 + local decoded = {} + local decoded_length = 0 + local buffer = 0 + local buffer_length = 0 + local code + local code_len = 2 + local temp + local pos = 4 + + while decoded_length < original_length do + if code_len <= buffer_length then + temp = invcodes[code_len] + code = bit.band(buffer, bit.lshift(1, code_len)-1) + if temp and temp[code] then --most of the time temp is nil + decoded_length = decoded_length + 1 + decoded[decoded_length] = temp[code] + buffer = bit.rshift(buffer, code_len) + buffer_length = buffer_length - code_len + code_len = 2 + else + code_len = code_len + 1 + if code_len > 10 then + error("malformed code") + end + end + else + buffer = bit.bor(buffer, bit.lshift(byte(encoded, pos), buffer_length)) + buffer_length = buffer_length + 8 + pos = pos + 1 + if pos > encoded_length then + error("malformed code") + end + end + end + + return concat(decoded) +end + +local function invEscapeSub(str) + local escseq,body = match(str,"^(.-)\n(.-)$") + + if not escseq then error("invalid input") end + + return gsub(body,escseq,"\26") +end + +local dictionary +local subtables + +local function deserializeChunk(chunk) + + local ctype,val = byte(chunk),sub(chunk,3) + + if ctype == 89 then return dictionary[ val ] + elseif ctype == 86 then + local a,b,c = match(val,"^(.-),(.-),(.+)$") + return Vector( tonumber(a), tonumber(b), tonumber(c) ) + elseif ctype == 65 then + local a,b,c = match(val,"^(.-),(.-),(.+)$") + return Angle( tonumber(a), tonumber(b), tonumber(c) ) + elseif ctype == 84 then + local t = {} + local tv = subtables[val] + if not tv then + tv = {} + subtables[ val ] = tv + end + tv[#tv+1] = t + return t + elseif ctype == 78 then return tonumber(val) + elseif ctype == 83 then return gsub(sub(val,2,-2),"",";") + elseif ctype == 66 then return val == "t" + elseif ctype == 80 then return 1 + end + + error(format("AD1 deserialization failed: invalid chunk (%u:%s)",ctype,val)) + +end + +local function deserializeAD1(dupestring) + + dupestring = dupestring:Replace("\r\n", "\n") + local header, extraHeader, dupeBlock, dictBlock = dupestring:match("%[Info%]\n(.+)\n%[More Information%]\n(.+)\n%[Save%]\n(.+)\n%[Dict%]\n(.+)") + + if not header then + error("unknown duplication format") + end + + local info = {} + for k,v in header:gmatch("([^\n:]+):([^\n]+)") do + info[k] = v + end + + local moreinfo = {} + for k,v in extraHeader:gmatch("([^\n:]+):([^\n]+)") do + moreinfo[k] = v + end + + dictionary = {} + for k,v in dictBlock:gmatch("(.-):\"(.-)\"\n") do + dictionary[k] = v + end + + local dupe = {} + for key,block in dupeBlock:gmatch("([^\n:]+):([^\n]+)") do + + local tables = {} + subtables = {} + local head + + for id,chunk in block:gmatch('(%w+){(.-)}') do + + --check if this table is the trunk + if byte(id) == 72 then + id = sub(id,2) + head = id + end + + tables[id] = {} + + for kv in gmatch(chunk,'[^;]+') do + + local k,v = match(kv,'(.-)=(.+)') + + if k then + k = deserializeChunk( k ) + v = deserializeChunk( v ) + + tables[id][k] = v + else + v = deserializeChunk( kv ) + local tid = tables[id] + tid[#tid+1]=v + end + + end + end + + --Restore table references + for id,tbls in pairs( subtables ) do + for _,tbl in pairs( tbls ) do + if not tables[id] then error("attempt to reference a nonexistent table") end + for k,v in pairs(tables[id]) do + tbl[k] = v + end + end + end + + dupe[key] = tables[ head ] + + end + + return dupe, info, moreinfo + +end + +--seperates the header and body and converts the header to a table +local function getInfo(str) + local last = str:find("\2") + if not last then + error("attempt to read AD2 file with malformed info block error 1") + end + local info = {} + local ss = str:sub(1,last-1) + for k,v in ss:gmatch("(.-)\1(.-)\1") do + info[k] = v + end + if info.check ~= "\r\n\t\n" then + if info.check == "\10\9\10" then + error("detected AD2 file corrupted in file transfer (newlines homogenized)(when using FTP, transfer AD2 files in image/binary mode, not ASCII/text mode)") + else + error("attempt to read AD2 file with malformed info block error 2") + end + end + return info, str:sub(last+2) +end + +--decoders for individual versions go here +local versions = {} + +versions[2] = function(encodedDupe) + encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n", "\n") + encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n") + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize_v2( + lzwDecode( + huffmanDecode( + invEscapeSub(dupestring) + ) + ) + ), info +end + +versions[1] = function(encodedDupe) + encodedDupe = encodedDupe:Replace("\r\r\n\t\r\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n\t\n", "\t\t\t\t") + encodedDupe = encodedDupe:Replace("\r\n", "\n") + encodedDupe = encodedDupe:Replace("\t\t\t\t", "\r\n\t\n") + local info, dupestring = getInfo(encodedDupe:sub(7)) + return deserialize_v1( + lzwDecode( + huffmanDecode( + invEscapeSub(dupestring) + ) + ) + ), info +end + +versions[0] = deserializeAD1 + +AdvDupe2.LegacyDecoders = versions \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sh_netstream.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sh_netstream.lua new file mode 100644 index 0000000..b9e415e --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sh_netstream.lua @@ -0,0 +1,358 @@ +--A net extension which allows sending large streams of data without overflowing the reliable channel +net.Stream = {} +net.Stream.ReadStreamQueues = {} --This holds a read stream for each player, or one read stream for the server if running on the CLIENT +net.Stream.WriteStreams = {} --This holds the write streams +net.Stream.SendSize = 20000 --This is the maximum size of each stream to send +net.Stream.Timeout = 30 --How long the data should exist in the store without being used before being destroyed +net.Stream.MaxServerReadStreams = 128 --The maximum number of keep-alives to have queued. This should prevent naughty players from flooding the network with keep-alive messages. +net.Stream.MaxServerChunks = 3200 --Maximum number of pieces the stream can send to the server. 64 MB +net.Stream.MaxTries = 3 --Maximum times the client may retry downloading the whole data +net.Stream.MaxKeepalive = 15 --Maximum times the client may request data stay live + +net.Stream.ReadStream = {} +--Send the data sender a request for data +function net.Stream.ReadStream:Request() + if self.downloads == net.Stream.MaxTries * self.numchunks then self:Remove() return end + self.downloads = self.downloads + 1 + -- print("Requesting",self.identifier,false,false,#self.chunks) + + net.Start("NetStreamRequest") + net.WriteUInt(self.identifier, 32) + net.WriteBit(false) + net.WriteBit(false) + net.WriteUInt(#self.chunks, 32) + if CLIENT then net.SendToServer() else net.Send(self.player) end + + timer.Create("NetStreamReadTimeout" .. self.identifier, net.Stream.Timeout/2, 1, function() self:Request() end) + +end + +--Received data so process it +function net.Stream.ReadStream:Read(size) + + timer.Remove("NetStreamReadTimeout" .. self.identifier) + + local crc = net.ReadString() + local data = net.ReadData(size) + + if crc == util.CRC(data) then + self.chunks[#self.chunks + 1] = data + end + if #self.chunks == self.numchunks then + self.returndata = table.concat(self.chunks) + if self.compressed then + self.returndata = util.Decompress(self.returndata) + end + self:Remove() + else + self:Request() + end + +end + +--Gets the download progress +function net.Stream.ReadStream:GetProgress() + return #self.chunks/self.numchunks +end + +--Pop the queue and start the next task +function net.Stream.ReadStream:Remove() + + local ok, err = xpcall(self.callback, debug.traceback, self.returndata) + if not ok then ErrorNoHalt(err) end + + net.Start("NetStreamRequest") + net.WriteUInt(self.identifier, 32) + net.WriteBit(false) + net.WriteBit(true) + if CLIENT then net.SendToServer() else net.Send(self.player) end + + timer.Remove("NetStreamReadTimeout" .. self.identifier) + timer.Remove("NetStreamKeepAlive" .. self.identifier) + + if self == self.queue[1] then + table.remove(self.queue, 1) + local nextInQueue = self.queue[1] + if nextInQueue then + timer.Remove("NetStreamKeepAlive" .. nextInQueue.identifier) + nextInQueue:Request() + else + net.Stream.ReadStreamQueues[self.player] = nil + end + else + for k, v in ipairs(self.queue) do + if v == self then + table.remove(self.queue, k) + break + end + end + end +end + +net.Stream.ReadStream.__index = net.Stream.ReadStream + +net.Stream.WriteStream = {} + +-- The player wants some data +function net.Stream.WriteStream:Write(ply) + local progress = net.ReadUInt(32)+1 + local chunk = self.chunks[progress] + if chunk then + self.clients[ply].progress = progress + net.Start("NetStreamDownload") + net.WriteUInt(#chunk.data, 32) + net.WriteString(chunk.crc) + net.WriteData(chunk.data, #chunk.data) + if CLIENT then net.SendToServer() else net.Send(ply) end + end +end + +-- The player notified us they finished downloading or cancelled +function net.Stream.WriteStream:Finished(ply) + self.clients[ply].finished = true + if self.callback then + local ok, err = xpcall(self.callback, debug.traceback, ply) + if not ok then ErrorNoHalt(err) end + end +end + +-- Get player's download progress +function net.Stream.WriteStream:GetProgress(ply) + return self.clients[ply].progress / #self.chunks +end + +-- If the stream owner cancels it, notify everyone who is subscribed +function net.Stream.WriteStream:Remove() + local sendTo = {} + for ply, client in pairs(self.clients) do + if not client.finished then + client.finished = true + if ply:IsValid() then sendTo[#sendTo+1] = ply end + end + end + + net.Start("NetStreamDownload") + net.WriteUInt(0, 32) + net.WriteUInt(self.identifier, 32) + if SERVER then net.Send(sendTo) else net.SendToServer() end + net.Stream.WriteStreams[self.identifier] = nil +end + +net.Stream.WriteStream.__index = net.Stream.WriteStream + +--Store the data and write the file info so receivers can request it. +local identifier = 1 +function net.WriteStream(data, callback, dontcompress) + + if not isstring(data) then + error("bad argument #1 to 'WriteStream' (string expected, got " .. type(data) .. ")", 2) + end + if callback ~= nil and not isfunction(callback) then + error("bad argument #2 to 'WriteStream' (function expected, got " .. type(callback) .. ")", 2) + end + + local compressed = not dontcompress + if compressed then + data = util.Compress(data) or "" + end + + if #data == 0 then + net.WriteUInt(0, 32) + return + end + + local numchunks = math.ceil(#data / net.Stream.SendSize) + if CLIENT and numchunks > net.Stream.MaxServerChunks then + ErrorNoHalt("net.WriteStream request is too large! ", #data/1048576, "MiB") + net.WriteUInt(0, 32) + return + end + + local chunks = {} + for i=1, numchunks do + local datachunk = string.sub(data, (i - 1) * net.Stream.SendSize + 1, i * net.Stream.SendSize) + chunks[i] = { + data = datachunk, + crc = util.CRC(datachunk), + } + end + + local startid = identifier + while net.Stream.WriteStreams[identifier] do + identifier = identifier % 1024 + 1 + if identifier == startid then + ErrorNoHalt("Netstream is full of WriteStreams!") + net.WriteUInt(0, 32) + return + end + end + + local stream = { + identifier = identifier, + chunks = chunks, + compressed = compressed, + numchunks = numchunks, + callback = callback, + clients = setmetatable({},{__index = function(t,k) + local r = { + finished = false, + downloads = 0, + keepalives = 0, + progress = 0, + } t[k]=r return r + end}) + } + setmetatable(stream, net.Stream.WriteStream) + + net.Stream.WriteStreams[identifier] = stream + timer.Create("NetStreamWriteTimeout" .. identifier, net.Stream.Timeout, 1, function() stream:Remove() end) + + net.WriteUInt(numchunks, 32) + net.WriteUInt(identifier, 32) + net.WriteBool(compressed) + + return stream +end + +--If the receiver is a player then add it to a queue. +--If the receiver is the server then add it to a queue for each individual player +function net.ReadStream(ply, callback) + + if CLIENT then + ply = NULL + else + if type(ply) ~= "Player" then + error("bad argument #1 to 'ReadStream' (Player expected, got " .. type(ply) .. ")", 2) + elseif not ply:IsValid() then + error("bad argument #1 to 'ReadStream' (Tried to use a NULL entity!)", 2) + end + end + if not isfunction(callback) then + error("bad argument #2 to 'ReadStream' (function expected, got " .. type(callback) .. ")", 2) + end + + local queue = net.Stream.ReadStreamQueues[ply] + if queue then + if SERVER and #queue == net.Stream.MaxServerReadStreams then + ErrorNoHalt("Receiving too many ReadStream requests from ", ply) + return + end + else + queue = {} net.Stream.ReadStreamQueues[ply] = queue + end + + local numchunks = net.ReadUInt(32) + if numchunks == nil then + return + elseif numchunks == 0 then + local ok, err = xpcall(callback, debug.traceback, "") + if not ok then ErrorNoHalt(err) end + return + end + if SERVER and numchunks > net.Stream.MaxServerChunks then + ErrorNoHalt("ReadStream requests from ", ply, " is too large! ", numchunks * net.Stream.SendSize / 1048576, "MiB") + return + end + + local identifier = net.ReadUInt(32) + local compressed = net.ReadBool() + --print("Got info", numchunks, identifier, compressed) + + for _, v in ipairs(queue) do + if v.identifier == identifier then + ErrorNoHalt("Tried to start a new ReadStream for an already existing stream!") + return + end + end + + local stream = { + identifier = identifier, + chunks = {}, + compressed = compressed, + numchunks = numchunks, + callback = callback, + queue = queue, + player = ply, + downloads = 0 + } + setmetatable(stream, net.Stream.ReadStream) + + queue[#queue + 1] = stream + if #queue > 1 then + timer.Create("NetStreamKeepAlive" .. identifier, net.Stream.Timeout / 2, 0, function() + net.Start("NetStreamRequest") + net.WriteUInt(identifier, 32) + net.WriteBit(true) + if CLIENT then net.SendToServer() else net.Send(ply) end + end) + else + stream:Request() + end + + return stream +end + +if SERVER then + + util.AddNetworkString("NetStreamRequest") + util.AddNetworkString("NetStreamDownload") + +end + +--Stream data is requested +net.Receive("NetStreamRequest", function(len, ply) + + local identifier = net.ReadUInt(32) + local stream = net.Stream.WriteStreams[identifier] + + if stream then + ply = ply or NULL + local client = stream.clients[ply] + + if not client.finished then + local keepalive = net.ReadBit() == 1 + if keepalive then + if client.keepalives < net.Stream.MaxKeepalive then + client.keepalives = client.keepalives + 1 + timer.Adjust("NetStreamWriteTimeout" .. identifier, net.Stream.Timeout, 1) + end + else + local completed = net.ReadBit() == 1 + if completed then + stream:Finished(ply) + else + if client.downloads < net.Stream.MaxTries * #stream.chunks then + client.downloads = client.downloads + 1 + stream:Write(ply) + timer.Adjust("NetStreamWriteTimeout" .. identifier, net.Stream.Timeout, 1) + else + client.finished = true + end + end + end + end + end + +end) + +--Download the stream data +net.Receive("NetStreamDownload", function(len, ply) + + ply = ply or NULL + local queue = net.Stream.ReadStreamQueues[ply] + if queue then + local size = net.ReadUInt(32) + if size > 0 then + queue[1]:Read(size) + else + local id = net.ReadUInt(32) + for k, v in ipairs(queue) do + if v.identifier == id then + v:Remove() + break + end + end + end + end + +end) diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sv_clipboard.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_clipboard.lua new file mode 100644 index 0000000..9a50881 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_clipboard.lua @@ -0,0 +1,1616 @@ +--[[ + Title: Adv. Duplicator 2 Module + + Desc: Provides advanced duplication functionality for the Adv. Dupe 2 tool. + + Author: TB + + Version: 1.0 +]] + +require "duplicator" + +AdvDupe2.duplicator = {} + +AdvDupe2.JobManager = {} +AdvDupe2.JobManager.PastingHook = false +AdvDupe2.JobManager.Queue = {} + +local constraints = {Weld=true, Axis=true, Ballsocket=true, Elastic=true, Hydraulic=true, Motor=true, Muscle=true, Pulley=true, Rope=true, Slider=true, Winch=true} +local serializable = { + [TYPE_BOOL] = true, + [TYPE_NUMBER] = true, + [TYPE_VECTOR] = true, + [TYPE_ANGLE] = true, + [TYPE_TABLE] = true, + [TYPE_STRING] = true +} + +local function CopyClassArgTable(tab) + local done = {} + local function recursiveCopy(oldtable) + local newtable = {} + done[oldtable] = newtable + for k, v in pairs(oldtable) do + local varType = TypeID(v) + if serializable[varType] then + if varType == TYPE_TABLE then + if done[v] then + newtable[k] = done[v] + else + newtable[k] = recursiveCopy(v) + end + else + newtable[k] = v + end + -- else + -- print("[AdvDupe2] ClassArg table with key \"" .. tostring(k) .. "\" has unsupported value of type \"".. type(v) .."\"!\n") + end + end + return newtable + end + return recursiveCopy(tab) +end + +local function doSpawnHooks(ply, ent) + local class = ent:GetClass() + if class == 'prop_effect' then + gamemode.Call('PlayerSpawnedEffect', ply, ent:GetModel(), ent) + elseif class == 'prop_physics' or class:find('prop_dynamic', 1, false) then + gamemode.Call('PlayerSpawnedProp', ply, ent:GetModel(), ent) + else + gamemode.Call('PlayerSpawnedSENT', ply, ent) + end +end + +--[[ + Name: CopyEntTable + Desc: Returns a copy of the passed entity's table + Params: Ent + Returns:
enttable +]] + +/*--------------------------------------------------------- + Returns a copy of the passed entity's table +---------------------------------------------------------*/ +local function CopyEntTable( Ent, Offset ) + -- Filter duplicator blocked entities out. + if Ent.DoNotDuplicate then return nil end + + if(not IsValid(Ent:GetPhysicsObject()))then return nil end + + local Tab = {} + + if Ent.PreEntityCopy then + local status, valid = pcall(Ent.PreEntityCopy, Ent) + if(not status)then + print("AD2 PreEntityCopy Error: "..tostring(valid)) + end + end + + local EntityClass = duplicator.FindEntityClass( Ent:GetClass() ) + + local EntTable = Ent:GetTable() + + if EntityClass then + for iNumber, Key in pairs( EntityClass.Args ) do + -- Ignore keys from old system + if Key~="Pos" and Key~="Model" and Key~="Ang" and Key~="Angle" and Key~="ang" and Key~="angle" and Key~="pos" and Key~="position" and Key~="model" then + local varType=TypeID(EntTable[Key]) + if serializable[varType] then + if varType == TYPE_TABLE then + Tab[Key] = CopyClassArgTable(EntTable[Key]) + else + Tab[Key] = EntTable[Key] + end + -- elseif varType ~= TYPE_NIL then + -- print("[AdvDupe2] Entity ClassArg \"" .. Key .. "\" of type \"".. Ent:GetClass() .."\" has unsupported value of type \"".. type(EntTable[ Key ]) .."\"!\n") + end + end + end + end + + Tab.BoneMods = table.Copy( Ent.BoneMods ) + if(Ent.EntityMods)then + Tab.EntityMods = table.Copy(Ent.EntityMods) + end + + if Ent.PostEntityCopy then + local status, valid = pcall(Ent.PostEntityCopy, Ent) + if(not status)then + print("AD2 PostEntityCopy Error: "..tostring(valid)) + end + end + + Tab.Pos = Ent:GetPos() + Tab.Class = Ent:GetClass() + Tab.Model = Ent:GetModel() + Tab.Skin = Ent:GetSkin() + Tab.CollisionGroup = Ent:GetCollisionGroup() + Tab.ModelScale = Ent:GetModelScale() + + if(Tab.Skin==0)then Tab.Skin = nil end + if(Tab.ModelScale == 1)then Tab.ModelScale = nil end + + if(Tab.Class == "gmod_cameraprop")then + Tab.key = Ent:GetNetworkedInt("key") + end + -- Allow the entity to override the class + -- This is a hack for the jeep, since it's real class is different from the one it reports as + -- (It reports a different class to avoid compatibility problems) + if Ent.ClassOverride then Tab.Class = Ent.ClassOverride end + + Tab.PhysicsObjects = {} + + -- Physics Objects + local PhysObj + for Bone = 0, Ent:GetPhysicsObjectCount()-1 do + PhysObj = Ent:GetPhysicsObjectNum( Bone ) + if IsValid(PhysObj) then + Tab.PhysicsObjects[ Bone ] = Tab.PhysicsObjects[ Bone ] or {} + if(PhysObj:IsMoveable())then Tab.PhysicsObjects[ Bone ].Frozen = true end + PhysObj:EnableMotion(false) + Tab.PhysicsObjects[ Bone ].Pos = PhysObj:GetPos() - Tab.Pos + Tab.PhysicsObjects[ Bone ].Angle = PhysObj:GetAngles() + end + end + + Tab.PhysicsObjects[0].Pos = Tab.Pos - Offset + + Tab.Pos = nil + if(Tab.Class~="prop_physics")then + if(not Tab.BuildDupeInfo)then Tab.BuildDupeInfo = {} end + Tab.BuildDupeInfo.IsNPC = Ent:IsNPC() + Tab.BuildDupeInfo.IsVehicle = Ent:IsVehicle() + end + if( IsValid(Ent:GetParent()) ) then + if(not Tab.BuildDupeInfo)then Tab.BuildDupeInfo = {} end + Tab.PhysicsObjects[ 0 ].Angle = Ent:GetAngles() + Tab.BuildDupeInfo.DupeParentID = Ent:GetParent():EntIndex() + end + + -- Flexes + local FlexNum = Ent:GetFlexNum() + Tab.Flex = Tab.Flex or {} + local weight + local flexes + for i = 0, FlexNum do + weight = Ent:GetFlexWeight( i ) + if(weight~=0)then + Tab.Flex[ i ] = weight + flexes = true + end + end + if(flexes or Ent:GetFlexScale()~=1)then + Tab.FlexScale = Ent:GetFlexScale() + else + Tab.Flex = nil + end + + -- Body Groups + Tab.BodyG = {} + for k, v in pairs(Ent:GetBodyGroups()) do + if ( Ent:GetBodygroup( v.id ) > 0 ) then + Tab.BodyG[ v.id ] = Ent:GetBodygroup( v.id ) + end + end + + if(table.Count(Tab.BodyG)==0)then + Tab.BodyG = nil + end + + -- Bone Manipulator + if ( Ent:HasBoneManipulations() ) then + + Tab.BoneManip = {} + local t + local s + local a + local p + for i=0, Ent:GetBoneCount() do + t={} + s = Ent:GetManipulateBoneScale( i ) + a = Ent:GetManipulateBoneAngles( i ) + p = Ent:GetManipulateBonePosition( i ) + + if ( s != Vector( 1, 1, 1 ) ) then t[ 's' ] = s end + if ( a != Angle( 0, 0, 0 ) ) then t[ 'a' ] = a end + if ( p != Vector( 0, 0, 0 ) ) then t[ 'p' ] = p end + + if ( t['s'] or t['a'] or t['p'] ) then + Tab.BoneManip[ i ] = t + end + + end + + end + + if Ent.GetNetworkVars then + Tab.DT = Ent:GetNetworkVars() + end + + // Make this function on your SENT if you want to modify the + // returned table specifically for your entity. + if Ent.OnEntityCopyTableFinish then + local status, valid = pcall(Ent.OnEntityCopyTableFinish, Ent, Tab) + if(not status)then + print("AD2 OnEntityCopyTableFinish Error: "..tostring(valid)) + end + end + + return Tab + +end + + +--[[ + Name: CopyConstraintTable + Desc: Create a table for constraints + Params:
Constraints + Returns:
Constraints,
Entities +]] +local function CopyConstraintTable( Const, Offset ) + if(Const==nil)then return nil, {} end + + -- Filter duplicator blocked constraints out. + if Const.DoNotDuplicate then return nil, {} end + + local Type = duplicator.ConstraintType[ Const.Type ] + if(not Type)then return nil, {} end + local Constraint = {} + local Entities = {} + + Const.Constraint = nil + Const.OnDieFunctions=nil + Constraint.Entity={} + for k, key in pairs( Type.Args ) do + if(key~="pl" and not string.find(key, "Ent") and not string.find(key, "Bone"))then + Constraint[key] = Const[ key ] + end + end + + if((Const["Ent"] and Const["Ent"]:IsWorld()) or IsValid(Const["Ent"]))then + Constraint.Entity[ 1 ] = {} + Constraint.Entity[ 1 ].Index = Const["Ent"]:EntIndex() + if(not Const["Ent"]:IsWorld())then table.insert( Entities, Const["Ent"] ) end + Constraint.Type = Const.Type + if(Const.BuildDupeInfo)then Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) end + else + local ent + for i=1,4 do + ent = "Ent"..i + + if((Const[ent] and Const[ent]:IsWorld()) or IsValid(Const[ent]))then + Constraint.Entity[ i ] = {} + Constraint.Entity[ i ].Index = Const[ent]:EntIndex() + Constraint.Entity[ i ].Bone = Const[ "Bone"..i ] + Constraint.Entity[ i ].Length = Const[ "Length"..i ] + Constraint.Entity[ i ].World = Const[ "World"..i ] + + if Const[ ent ]:IsWorld() then + Constraint.Entity[ i ].World = true + if ( Const[ "LPos"..i ] ) then + if(i~= 4 and i~=2)then + if(Const["Ent2"])then + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] - Const["Ent2"]:GetPos() + Constraint[ "LPos"..i ] = Const[ "LPos"..i ] - Const["Ent2"]:GetPos() + elseif(Const["Ent4"])then + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] - Const["Ent4"]:GetPos() + Constraint[ "LPos"..i ] = Const[ "LPos"..i ] - Const["Ent4"]:GetPos() + end + elseif(Const["Ent1"])then + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] - Const["Ent1"]:GetPos() + Constraint[ "LPos"..i ] = Const[ "LPos"..i ] - Const["Ent1"]:GetPos() + end + else + Constraint.Entity[ i ].LPos = Offset + Constraint[ "LPos"..i ] = Offset + end + else + Constraint.Entity[ i ].LPos = Const[ "LPos"..i ] + Constraint.Entity[ i ].WPos = Const[ "WPos"..i ] + end + + if(not Const[ent]:IsWorld())then table.insert( Entities, Const[ent] ) end + end + + if(Const["WPos"..i])then + if(not Const["Ent1"]:IsWorld())then + Constraint["WPos"..i] = Const[ "WPos"..i ] - Const["Ent1"]:GetPos() + else + Constraint["WPos"..i] = Const[ "WPos"..i ] - Const["Ent4"]:GetPos() + end + end + end + + Constraint.Type = Const.Type + if(Const.BuildDupeInfo)then Constraint.BuildDupeInfo = table.Copy(Const.BuildDupeInfo) end + end + return Constraint, Entities +end + +--[[ + Name: Copy + Desc: Copy an entity and all entities constrained + Params: Entity + Returns:
Entities,
Constraints +]] +local function Copy( Ent, EntTable, ConstraintTable, Offset ) + + local index = Ent:EntIndex() + if EntTable[index] then return EntTable, ConstraintTable end + + local EntData = CopyEntTable(Ent, Offset) + if EntData == nil then return EntTable, ConstraintTable end + EntTable[index] = EntData + + if Ent.Constraints then + for k, Constraint in pairs( Ent.Constraints ) do + if Constraint:IsValid() then + index = Constraint:GetCreationID() + if index and not ConstraintTable[index] then + local ConstTable, EntTab = CopyConstraintTable( table.Copy(Constraint:GetTable()), Offset ) + ConstraintTable[index] = ConstTable + for j,e in pairs(EntTab) do + if e and ( e:IsWorld() or e:IsValid() ) then + Copy( e, EntTable, ConstraintTable, Offset ) + end + end + end + end + end + end + + do -- Wiremod Wire Connections + if istable(Ent.Inputs) then + for k, v in pairs(Ent.Inputs) do + if isentity(v.Src) and v.Src:IsValid() then + Copy( v.Src, EntTable, ConstraintTable, Offset ) + end + end + end + + if istable(Ent.Outputs) then + for k, v in pairs(Ent.Outputs) do + if istable(v.Connected) then + for k, v in pairs(v.Connected) do + if isentity(v.Entity) and v.Entity:IsValid() then + Copy( v.Entity, EntTable, ConstraintTable, Offset ) + end + end + end + end + end + end + + do -- Parented stuff + local parent = Ent:GetParent() + if IsValid(parent) then Copy(parent, EntTable, ConstraintTable, Offset) end + for k, child in pairs(Ent:GetChildren()) do + Copy(child, EntTable, ConstraintTable, Offset) + end + end + + for k,v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects)do + Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) + end + + return EntTable, ConstraintTable +end +AdvDupe2.duplicator.Copy = Copy + +--[[ + Name: AreaCopy + Desc: Copy based on a box + Params: Entity + Returns:
Entities,
Constraints +]] +function AdvDupe2.duplicator.AreaCopy( Entities, Offset, CopyOutside ) + local Constraints, EntTable, ConstraintTable = {}, {}, {} + local index, add, AddEnts, AddConstrs, ConstTable, EntTab + + for _,Ent in pairs(Entities) do + + index = Ent:EntIndex() + EntTable[index] = CopyEntTable(Ent, Offset) + if(EntTable[index]~=nil)then + + if ( not constraint.HasConstraints( Ent ) ) then + for k,v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects)do + Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) + end + else + for k,v in pairs(Ent.Constraints)do + -- Filter duplicator blocked constraints out. + if v.DoNotDuplicate then continue end + + index = v:GetCreationID() + if(index and not Constraints[index])then + Constraints[index] = v + end + end + + for k,v in pairs(EntTable[Ent:EntIndex()].PhysicsObjects)do + Ent:GetPhysicsObjectNum(k):EnableMotion(v.Frozen) + end + end + end + end + + for _, Constraint in pairs( Constraints ) do + ConstTable, EntTab = CopyConstraintTable( table.Copy(Constraint:GetTable()), Offset ) + //If the entity is constrained to an entity outside of the area box, don't copy the constraint. + if(not CopyOutside)then + add = true + for k,v in pairs(EntTab)do + if(not Entities[v:EntIndex()])then add=false end + end + if(add)then ConstraintTable[_] = ConstTable end + else //Copy entities and constraints outside of the box that are constrained to entities inside the box + ConstraintTable[_] = ConstTable + for k,v in pairs(EntTab)do + Copy(v, EntTable, ConstraintTable, Offset) + end + end + end + + return EntTable, ConstraintTable +end + +--[[ + Name: CreateConstraintFromTable + Desc: Creates a constraint from a given table + Params:
Constraint,
EntityList,
EntityTable + Returns: CreatedConstraint +]] +local function CreateConstraintFromTable(Constraint, EntityList, EntityTable, Player, DontEnable) + + local Factory = duplicator.ConstraintType[ Constraint.Type ] + if not Factory then return end + + if Factory.Tool and Constraint.Entity and Constraint.Entity[1] then + local override = hook.Run('CanTool', Player, { Entity = Constraint.Entity[1] }, Factory.Tool) + if override == false then return end + end + + local first --Ent1 or Ent in the constraint's table + local second --Any other Ent that is not Ent1 or Ent + + -- Build the argument list for the Constraint's spawn function + local Args = {} + local Val + for k, Key in pairs( Factory.Args ) do + + Val = Constraint[ Key ] + + if Key == "pl" or Key == "ply" then + Val = Player + end + + for i=1, 4 do + if ( Constraint.Entity and Constraint.Entity[ i ] ) then + if Key == "Ent"..i or Key == "Ent" then + if ( Constraint.Entity[ i ].World ) then + Val = game.GetWorld() + else + Val = EntityList[ Constraint.Entity[ i ].Index ] + + if not IsValid(Val) then + if(Player)then + Player:ChatPrint("DUPLICATOR: ERROR, "..Constraint.Type.." Constraint could not find an entity!") + else + print("DUPLICATOR: ERROR, "..Constraint.Type.." Constraint could not find an entity!") + end + return + else + if(IsValid(Val:GetPhysicsObject()))then + Val:GetPhysicsObject():EnableMotion(false) + end + --Important for perfect duplication + --Get which entity is which so we can reposition them before constraining + if(Key== "Ent" or Key == "Ent1")then + first=Val + firstindex = Constraint.Entity[ i ].Index + else + second=Val + secondindex = Constraint.Entity[ i ].Index + end + + end + end + + end + + if Key == "Bone"..i or Key == "Bone" then Val = Constraint.Entity[ i ].Bone or 0 end + + if Key == "LPos"..i then + if (Constraint.Entity[i].World and Constraint.Entity[i].LPos)then + if(i==2 or i==4)then + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[1].Index]:GetPos() + elseif(i==1)then + if(Constraint.Entity[2])then + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[2].Index]:GetPos() + else + Val = Constraint.Entity[i].LPos + EntityList[Constraint.Entity[4].Index]:GetPos() + end + end + elseif( Constraint.Entity[i].LPos ) then + Val = Constraint.Entity[ i ].LPos + end + end + + if Key == "Length"..i then Val = Constraint.Entity[ i ].Length end + end + if Key == "WPos"..i then + if(not Constraint.Entity[1].World)then + Val = Constraint["WPos"..i] + EntityList[Constraint.Entity[1].Index]:GetPos() + else + Val = Constraint["WPos"..i] + EntityList[Constraint.Entity[4].Index]:GetPos() + end + end + + end + -- If there's a missing argument then unpack will stop sending at that argument + Val = Val or false + table.insert( Args, Val ) + + end + + local Bone1 + local Bone1Index + local ReEnableFirst + local Bone2 + local Bone2Index + local ReEnableSecond + if(Constraint.BuildDupeInfo)then + + if first ~= nil and second ~= nil and not second:IsWorld() and Constraint.BuildDupeInfo.EntityPos ~= nil then + local SecondPhys = second:GetPhysicsObject() + if IsValid(SecondPhys) then + if not DontEnable then ReEnableSecond = SecondPhys:IsMoveable() end + SecondPhys:EnableMotion(false) + second:SetPos(first:GetPos()-Constraint.BuildDupeInfo.EntityPos) + if(Constraint.BuildDupeInfo.Bone2) then + Bone2Index = Constraint.BuildDupeInfo.Bone2 + Bone2 = second:GetPhysicsObjectNum(Bone2Index) + if IsValid(Bone2) then + Bone2:EnableMotion(false) + Bone2:SetPos(second:GetPos() + Constraint.BuildDupeInfo.Bone2Pos) + Bone2:SetAngles(Constraint.BuildDupeInfo.Bone2Angle) + end + end + end + end + + if first ~= nil and Constraint.BuildDupeInfo.Ent1Ang ~= nil then + local FirstPhys = first:GetPhysicsObject() + if IsValid(FirstPhys) then + if not DontEnable then ReEnableFirst = FirstPhys:IsMoveable() end + FirstPhys:EnableMotion(false) + first:SetAngles(Constraint.BuildDupeInfo.Ent1Ang) + if(Constraint.BuildDupeInfo.Bone1) then + Bone1Index = Constraint.BuildDupeInfo.Bone1 + Bone1 = first:GetPhysicsObjectNum(Bone1Index) + if IsValid(Bone1) then + Bone1:EnableMotion(false) + Bone1:SetPos(first:GetPos() + Constraint.BuildDupeInfo.Bone1Pos) + Bone1:SetAngles(Constraint.BuildDupeInfo.Bone1Angle) + end + end + end + end + + if second ~= nil and Constraint.BuildDupeInfo.Ent2Ang ~= nil then + second:SetAngles(Constraint.BuildDupeInfo.Ent2Ang) + end + + if second ~= nil and Constraint.BuildDupeInfo.Ent4Ang ~= nil then + second:SetAngles(Constraint.BuildDupeInfo.Ent4Ang) + end + end + + local status, Ent = pcall( Factory.Func, unpack(Args)) + + if not status or not Ent then + if(Player)then + AdvDupe2.Notify(Player, "ERROR, Failed to create "..Constraint.Type.." Constraint!", NOTIFY_ERROR) + else + print("DUPLICATOR: ERROR, Failed to create "..Constraint.Type.." Constraint!") + end + return + end + + Ent.BuildDupeInfo = table.Copy(Constraint.BuildDupeInfo) + + //Move the entities back after constraining them + if(EntityTable)then + if(first~=nil)then + first:SetPos(EntityTable[firstindex].BuildDupeInfo.PosReset) + first:SetAngles(EntityTable[firstindex].BuildDupeInfo.AngleReset) + if(IsValid(Bone1) and Bone1Index~=0)then + Bone1:SetPos(EntityTable[firstindex].BuildDupeInfo.PosReset + EntityTable[firstindex].BuildDupeInfo.PhysicsObjects[Bone1Index].Pos) + Bone1:SetAngles(EntityTable[firstindex].PhysicsObjects[Bone1Index].Angle) + end + + local FirstPhys = first:GetPhysicsObject() + if IsValid(FirstPhys) then + if ReEnableFirst then + FirstPhys:EnableMotion(true) + end + end + end + if(second~=nil)then + second:SetPos(EntityTable[secondindex].BuildDupeInfo.PosReset) + second:SetAngles(EntityTable[secondindex].BuildDupeInfo.AngleReset) + if(IsValid(Bone2) and Bone2Index~=0)then + Bone2:SetPos(EntityTable[secondindex].BuildDupeInfo.PosReset + EntityTable[secondindex].BuildDupeInfo.PhysicsObjects[Bone2Index].Pos) + Bone2:SetAngles(EntityTable[secondindex].PhysicsObjects[Bone2Index].Angle) + end + + local SecondPhys = second:GetPhysicsObject() + if IsValid(SecondPhys) then + if ReEnableSecond then + SecondPhys:EnableMotion(true) + end + end + end + end + + if(Ent and Ent.length)then Ent.length = Constraint["length"] end //Fix for weird bug with ropes + + return Ent +end + +local function ApplyEntityModifiers( Player, Ent ) + if not Ent.EntityMods then return end + if Ent.EntityMods.trail then + Ent.EntityMods.trail.EndSize = math.Clamp(tonumber(Ent.EntityMods.trail.EndSize) or 0, 0, 1024) + Ent.EntityMods.trail.StartSize = math.Clamp(tonumber(Ent.EntityMods.trail.StartSize) or 0, 0, 1024) + end + local status, error + for Type, Data in SortedPairs( Ent.EntityMods ) do + local ModFunction = duplicator.EntityModifiers[ Type ] + if ( ModFunction ) then + status, error = pcall(ModFunction, Player, Ent, Data ) + if(not status)then + if(Player)then + Player:ChatPrint('Error applying entity modifer, "'..tostring(Type)..'". ERROR: '..error) + else + print('Error applying entity modifer, "'..tostring(Type)..'". ERROR: '..error) + end + end + end + end + if(Ent.EntityMods["mass"] and duplicator.EntityModifiers["mass"])then + status, error = pcall(duplicator.EntityModifiers["mass"], Player, Ent, Ent.EntityMods["mass"] ) + if(not status)then + if(Player)then + Player:ChatPrint('Error applying entity modifer, "mass". ERROR: '..error) + else + print('Error applying entity modifer, "'..tostring(Type)..'". ERROR: '..error) + end + end + end + +end + +local function ApplyBoneModifiers( Player, Ent ) + if(not Ent.BoneMods or not Ent.PhysicsObjects)then return end + + local status, error, PhysObject + for Type, ModFunction in pairs( duplicator.BoneModifiers ) do + for Bone, Args in pairs( Ent.PhysicsObjects ) do + if ( Ent.BoneMods[ Bone ] and Ent.BoneMods[ Bone ][ Type ] ) then + PhysObject = Ent:GetPhysicsObjectNum( Bone ) + if ( Ent.PhysicsObjects[ Bone ] ) then + status, error = pcall(ModFunction, Player, Ent, Bone, PhysObject, Ent.BoneMods[ Bone ][ Type ] ) + if(not status)then + Player:ChatPrint('Error applying bone modifer, "'..tostring(Type)..'". ERROR: '..error) + end + end + end + end + end +end + +--[[ + Name: DoGenericPhysics + Desc: Applies bone data, generically. + Params: Player,
data + Returns: Entity,
data +]] +local function DoGenericPhysics( Entity, data, Player ) + + if (not data) then return end + if (not data.PhysicsObjects) then return end + local Phys + if(Player)then + for Bone, Args in pairs( data.PhysicsObjects ) do + Phys = Entity:GetPhysicsObjectNum(Bone) + if ( IsValid(Phys) ) then + Phys:SetPos( Args.Pos ) + Phys:SetAngles( Args.Angle ) + Phys:EnableMotion( false ) + Player:AddFrozenPhysicsObject( Entity, Phys ) + end + end + else + for Bone, Args in pairs( data.PhysicsObjects ) do + Phys = Entity:GetPhysicsObjectNum(Bone) + if ( IsValid(Phys) ) then + Phys:SetPos( Args.Pos ) + Phys:SetAngles( Args.Angle ) + Phys:EnableMotion( false ) + end + end + end +end + +local function reportclass(ply,class) + umsg.Start("AdvDupe2_ReportClass", ply) + umsg.String(class) + umsg.End() +end + +local function reportmodel(ply,model) + umsg.Start("AdvDupe2_ReportModel", ply) + umsg.String(model) + umsg.End() +end + +--[[ + Name: GenericDuplicatorFunction + Desc: Override the default duplicator's GenericDuplicatorFunction function + Params: Player,
data + Returns: Entity +]] +local function GenericDuplicatorFunction( data, Player ) + + local Entity = ents.Create( data.Class ) + if ( not IsValid(Entity) ) then + if(Player)then + reportclass(Player,data.Class) + else + print("Advanced Duplicator 2 Invalid Class: "..data.Class) + end + return nil + end + + if( not util.IsValidModel(data.Model) and not file.Exists( data.Model, "GAME" ) )then + if(Player)then + reportmodel(Player,data.Model) + else + print("Advanced Duplicator 2 Invalid Model: "..data.Model) + end + return nil + end + + duplicator.DoGeneric( Entity, data ) + Entity:Spawn() + Entity:Activate() + DoGenericPhysics( Entity, data, Player ) + -- APG.entGhost(Entity, true, true) + -- timer.Simple(0.1, function() + -- if IsValid(Entity) then APG.entUnGhost(Entity) end + -- end) + + table.Add( Entity:GetTable(), data ) + return Entity +end + +--[[ + Name: MakeProp + Desc: Make prop without spawn effects + Params: Player, Pos, Ang, Model,
PhysicsObject,
Data + Returns: Prop +]] +local function MakeProp(Player, Pos, Ang, Model, PhysicsObject, Data) + + if( not util.IsValidModel(Model) and not file.Exists( Data.Model, "GAME" ) )then + if(Player)then + reportmodel(Player,Data.Model) + else + print("Advanced Duplicator 2 Invalid Model: "..Model) + end + return nil + end + + Data.Pos = Pos + Data.Angle = Ang + Data.Model = Model + Data.Frozen = true + // Make sure this is allowed + if( Player )then + if ( not gamemode.Call( "PlayerSpawnProp", Player, Model ) ) then return false end + end + + local Prop = ents.Create( "prop_physics" ) + if not IsValid(Prop) then return false end + + duplicator.DoGeneric( Prop, Data ) + Prop:Spawn() + Prop:Activate() + DoGenericPhysics( Prop, Data, Player ) + if(Data.Flex)then duplicator.DoFlex( Prop, Data.Flex, Data.FlexScale ) end + -- APG.entGhost(Prop, true, true) + -- timer.Simple(0.1, function() + -- if IsValid(Prop) then APG.entUnGhost(Prop) end + -- end) + + return Prop +end + +local function MakeDynProp(Player, Pos, Ang, Model, PhysicsObject, Data) + + if( not util.IsValidModel(Model) and not file.Exists( Data.Model, "GAME" ) )then + if(Player)then + reportmodel(Player,Data.Model) + else + print("Advanced Duplicator 2 Invalid Model: "..Model) + end + return nil + end + + Data.Pos = Pos + Data.Angle = Ang + Data.Model = Model + Data.Frozen = true + // Make sure this is allowed + if( Player )then + if ( not gamemode.Call( "PlayerSpawnProp", Player, Model ) ) then return false end + end + + local Prop = ents.Create( "prop_dynamic_override" ) + if not IsValid(Prop) then return false end + + duplicator.DoGeneric( Prop, Data ) + Prop:Spawn() + Prop:PhysicsInitStatic(SOLID_VPHYSICS) + Prop:Activate() + DoGenericPhysics( Prop, Data, Player ) + if(Data.Flex)then duplicator.DoFlex( Prop, Data.Flex, Data.FlexScale ) end + -- APG.entGhost(Prop, true, true) + -- timer.Simple(0.1, function() + -- if IsValid(Prop) then APG.entUnGhost(Prop) end + -- end) + + return Prop +end + +local function RestoreBodyGroups( ent, BodyG ) + for k, v in pairs( BodyG ) do + ent:SetBodygroup( k, v ) + end +end + +--[[ + Name: CreateEntityFromTable + Desc: Creates an entity from a given table + Params:
EntTable, Player + Returns: nil +]] +local function IsAllowed(Player, Class, EntityClass) + if ( scripted_ents.GetMember( Class, "DoNotDuplicate" ) ) then return false end + + if ( IsValid( Player ) and !Player:IsAdmin()) then + if !duplicator.IsAllowed(Class) then return false end + if ( !scripted_ents.GetMember( Class, "Spawnable" ) and not EntityClass ) then return false end + if ( scripted_ents.GetMember( Class, "AdminOnly" ) ) then return false end + end + return true +end + +local function CreateEntityFromTable(EntTable, Player) + + local classToSearch = EntTable.Class:find('prop_dynamic', 1, false) and 'prop_physics' or EntTable.Class + local EntityClass = duplicator.FindEntityClass( classToSearch ) + if not IsAllowed(Player, EntTable.Class, EntityClass) then + Player:ChatPrint([[Entity Class Black listed, "]]..EntTable.Class..[["]]) + return nil + end + + local sent = false + local status, valid + local GENERIC = false + + // This class is unregistered. Instead of failing try using a generic + // Duplication function to make a new copy. + if (not EntityClass) then + GENERIC = true + sent = true + + if(EntTable.Class=="prop_effect")then + sent = gamemode.Call( "PlayerSpawnEffect", Player, EntTable.Model) + else + sent = gamemode.Call( "PlayerSpawnSENT", Player, EntTable.Class) + end + + if(sent==false)then + print("Advanced Duplicator 2: Creation rejected for class, : "..EntTable.Class) + return nil + else + sent = true + end + + if IsAllowed(Player, EntTable.Class, EntityClass) then + status, valid = pcall(GenericDuplicatorFunction, EntTable, Player ) + else + print("Advanced Duplicator 2: ENTITY CLASS IS BLACKLISTED, CLASS NAME: "..EntTable.Class) + return nil + end + end + + if(not GENERIC)then + + // Build the argument list for the Entitie's spawn function + local ArgList = {} + local Arg + for iNumber, Key in pairs( EntityClass.Args ) do + + Arg = nil + // Translate keys from old system + if ( Key == "pos" or Key == "position" ) then Key = "Pos" end + if ( Key == "ang" or Key == "Ang" or Key == "angle" ) then Key = "Angle" end + if ( Key == "model" ) then Key = "Model" end + if ( Key == "VehicleTable" and EntTable[Key] and EntTable[Key].KeyValues)then + EntTable[Key].KeyValues = {vehiclescript=EntTable[Key].KeyValues.vehiclescript, limitview=EntTable[Key].KeyValues.limitview} + end + + Arg = EntTable[ Key ] + + // Special keys + if ( Key == "Data" ) then Arg = EntTable end + + ArgList[ iNumber ] = Arg + + end + + // Create and return the entity + if(EntTable.Class=="prop_physics")then + valid = MakeProp(Player, unpack(ArgList, 1, #EntityClass.Args)) //Create prop_physics like this because if the model doesn't exist it will cause + elseif(EntTable.Class:find("prop_dynamic", 1, false)) then + valid = MakeDynProp(Player, unpack(ArgList, 1, #EntityClass.Args)) + elseif IsAllowed(Player, EntTable.Class, EntityClass) then + //Create sents using their spawn function with the arguments we stored earlier + sent = true + + if (EntTable.Class:find("prop_dynamic", 1, false)) then + sent = gamemode.Call( "PlayerSpawnProp", Player, EntTable.Model ) ~= false + elseif(not EntTable.BuildDupeInfo.IsVehicle and not EntTable.BuildDupeInfo.IsNPC and EntTable.Class ~= "prop_ragdoll" and EntTable.Class ~= "prop_effect") then //These four are auto done + sent = hook.Call("PlayerSpawnSENT", nil, Player, EntTable.Class) + end + + if not sent then + if EntTable.Class:sub(1, 5) == 'gmod_' then + local tool = EntTable.Class:sub(6) + local override = hook.Run('CanTool', Player, { Entity = NULL }, tool) + if override ~= false then sent = true end + end + + local tool = scripted_ents.GetMember(EntTable.Class, 'Tool') + if tool then + local override = hook.Run('CanTool', Player, { Entity = NULL }, tool) + if override ~= false then sent = true end + end + end + + if(sent==false)then + print("Advanced Duplicator 2: Creation rejected for class, : "..EntTable.Class) + return nil + else + sent = true + end + + status,valid = pcall( EntityClass.Func, Player, unpack(ArgList, 1, #EntityClass.Args) ) + else + print("Advanced Duplicator 2: ENTITY CLASS IS BLACKLISTED, CLASS NAME: "..EntTable.Class) + return nil + end + end + + //If its a valid entity send it back to the entities list so we can constrain it + if( status~=false and IsValid(valid) )then + for _, ply in pairs(player.GetAll()) do + FPP.calculateCanTouch(ply, valid) + end + if(sent)then + local iNumPhysObjects = valid:GetPhysicsObjectCount() + local PhysObj + if(Player)then + for Bone = 0, iNumPhysObjects-1 do + PhysObj = valid:GetPhysicsObjectNum( Bone ) + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + Player:AddFrozenPhysicsObject( valid, PhysObj ) + end + end + else + for Bone = 0, iNumPhysObjects-1 do + PhysObj = valid:GetPhysicsObjectNum( Bone ) + if IsValid(PhysObj) then + PhysObj:EnableMotion(false) + end + end + end + if(EntTable.Skin)then valid:SetSkin(EntTable.Skin) end + if ( EntTable.BodyG ) then RestoreBodyGroups( valid, EntTable.BodyG ) end + + if valid.RestoreNetworkVars then + valid:RestoreNetworkVars(EntTable.DT) + end + + -- if GENERIC then + -- if(EntTable.Class=="prop_effect")then + -- gamemode.Call("PlayerSpawnedEffect", Player, valid:GetModel(), valid) + -- else + -- gamemode.Call("PlayerSpawnedSENT", Player, valid) + -- end + -- end + + elseif(Player)then + -- gamemode.Call( "PlayerSpawnedProp", Player, valid:GetModel(), valid ) + end + + return valid + else + if(valid==false)then + return false + else + return nil + end + end +end + +--[[ + Name: Paste + Desc: Override the default duplicator's paste function + Params: Player,
Entities,
Constraints + Returns:
Entities,
Constraints +]] +function AdvDupe2.duplicator.Paste( Player, EntityList, ConstraintList, Position, AngleOffset, OrigPos, Parenting ) + + local CreatedEntities = {} + -- + -- Create entities + -- + local proppos + DisablePropCreateEffect = true + for k, v in pairs( EntityList ) do + if(not v.BuildDupeInfo)then v.BuildDupeInfo={} end + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + proppos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PhysicsObjects[0].Pos = Vector(0,0,0) + if( OrigPos )then + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos = p.Pos + proppos + OrigPos + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.PosReset = v.Pos + v.BuildDupeInfo.AngleReset = v.Angle + else + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects)do + v.PhysicsObjects[i].Pos, v.PhysicsObjects[i].Angle = LocalToWorld(p.Pos + proppos, p.Angle, Position, AngleOffset) + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PosReset = v.Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.AngleReset = v.Angle + end + + AdvDupe2.SpawningEntity = true + local Ent = CreateEntityFromTable(v, Player) + AdvDupe2.SpawningEntity = false + + if Ent then + if(Player)then Player:AddCleanup( "AdvDupe2", Ent ) end + Ent.BoneMods = table.Copy( v.BoneMods ) + Ent.EntityMods = table.Copy( v.EntityMods ) + Ent.PhysicsObjects = table.Copy( v.PhysicsObjects ) + if(v.CollisionGroup)then Ent:SetCollisionGroup(v.CollisionGroup) end + if(Ent.OnDuplicated)then Ent:OnDuplicated(v) end + ApplyEntityModifiers( Player, Ent ) + ApplyBoneModifiers( Player, Ent ) + Ent.SolidMod = not Ent:IsSolid() + Ent:SetNotSolid(true) + doSpawnHooks(Player, Ent) + elseif(Ent==false)then + Ent = nil + --ConstraintList = {} + --break + else + Ent = nil + end + CreatedEntities[k] = Ent + end + + local CreatedConstraints = {} + local Entity + -- + -- Create constraints + -- + for k, Constraint in pairs( ConstraintList ) do + Entity = CreateConstraintFromTable( Constraint, CreatedEntities, EntityList, Player ) + if(IsValid(Entity))then + table.insert( CreatedConstraints, Entity ) + end + end + + if(Player)then + + undo.Create "AdvDupe2_Paste" + for _,v in pairs( CreatedEntities ) do + --If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + local status, valid = pcall(v.PostEntityPaste, v, Player, v, CreatedEntities) + if(not status)then + print("AD2 PostEntityPaste Error: "..tostring(valid)) + end + end + v:GetPhysicsObject():EnableMotion(false) + + if(EntityList[_].BuildDupeInfo.DupeParentID and Parenting)then + v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) + end + v:SetNotSolid( v.SolidMod ) + undo.AddEntity( v ) + end + undo.SetPlayer( Player ) + undo.Finish() + + //if(Tool)then AdvDupe2.FinishPasting(Player, true) end + else + + for _,v in pairs( CreatedEntities ) do + --If the entity has a PostEntityPaste function tell it to use it now + if v.PostEntityPaste then + local status, valid = pcall(v.PostEntityPaste, v, Player, v, CreatedEntities) + if(not status)then + print("AD2 PostEntityPaste Error: "..tostring(valid)) + end + end + v:GetPhysicsObject():EnableMotion(false) + + if(EntityList[_].BuildDupeInfo.DupeParentID and Parenting)then + v:SetParent(CreatedEntities[EntityList[_].BuildDupeInfo.DupeParentID]) + end + + v:SetNotSolid( v.SolidMod ) + end + end + DisablePropCreateEffect = nil + hook.Call("AdvDupe_FinishPasting", nil, {{EntityList=EntityList, CreatedEntities=CreatedEntities, ConstraintList=ConstraintList, CreatedConstraints=CreatedConstraints, HitPos=OrigPos or Position, Player=Player}}, 1) + + return CreatedEntities, CreatedConstraints +end + +local function AdvDupe2_Spawn() + + local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] + + if(not Queue or not IsValid(Queue.Player))then + if Queue then + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + end + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end + return + end + + if(Queue.Entity)then + if(Queue.Current==1)then + AdvDupe2.InitProgressBar(Queue.Player,"Pasting:") + Queue.Player.AdvDupe2.Queued = false + end + local newpos + if(Queue.Current>#Queue.SortedEntities)then + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + return + end + if(not Queue.SortedEntities[Queue.Current])then Queue.Current = Queue.Current+1 return end + + local k = Queue.SortedEntities[Queue.Current] + local v = Queue.EntityList[k] + + if(not v.BuildDupeInfo)then v.BuildDupeInfo={} end + if(v.LocalPos)then + for i,p in pairs(v.PhysicsObjects) do + v.PhysicsObjects[i] = {Pos=v.LocalPos, Angle=v.LocalAngle} + end + end + + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + proppos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PhysicsObjects[0].Pos = Vector(0,0,0) + if( Queue.OrigPos )then + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects) do + v.PhysicsObjects[i].Pos = p.Pos + proppos + Queue.OrigPos + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.PosReset = v.Pos + v.BuildDupeInfo.AngleReset = v.Angle + else + for i,p in pairs(v.BuildDupeInfo.PhysicsObjects)do + v.PhysicsObjects[i].Pos, v.PhysicsObjects[i].Angle = LocalToWorld(p.Pos + proppos, p.Angle, Queue.PositionOffset, Queue.AngleOffset) + v.PhysicsObjects[i].Frozen = true + end + v.Pos = v.PhysicsObjects[0].Pos + v.BuildDupeInfo.PosReset = v.Pos + v.Angle = v.PhysicsObjects[0].Angle + v.BuildDupeInfo.AngleReset = v.Angle + end + + AdvDupe2.SpawningEntity = true + local Ent = CreateEntityFromTable(v, Queue.Player) + AdvDupe2.SpawningEntity = false + + if Ent then + local colData = v.EntityMods and v.EntityMods.colour + if colData then + if colData.Color then Ent:SetColor(colData.Color) end + if colData.RenderFX then Ent:SetRenderFX(colData.RenderFX) end + if colData.RenderMode then Ent:SetRenderMode(colData.RenderMode) end + Ent.APG_oldColor = colData.Color + end + Queue.Player:AddCleanup( "AdvDupe2", Ent ) + Ent.BoneMods = table.Copy( v.BoneMods ) + Ent.EntityMods = table.Copy( v.EntityMods ) + Ent.PhysicsObjects = table.Copy( v.PhysicsObjects ) + Ent.SolidMod = not Ent:IsSolid() + + local Phys = Ent:GetPhysicsObject() + if(IsValid(Phys))then Phys:EnableMotion(false) end + if(not Queue.DisableProtection)then Ent:SetNotSolid(true) end + if(v.CollisionGroup)then Ent:SetCollisionGroup(v.CollisionGroup) end + if(Ent.OnDuplicated)then Ent:OnDuplicated(v) end + doSpawnHooks(Queue.Player, Ent) + elseif(Ent==false)then + Ent = nil + else + Ent = nil + end + Queue.CreatedEntities[ k ] = Ent + + AdvDupe2.UpdateProgressBar(Queue.Player, math.floor((Queue.Percent*Queue.Current)*100)) + Queue.Current = Queue.Current+1 + if(Queue.Current>#Queue.SortedEntities)then + + for _,Ent in pairs(Queue.CreatedEntities)do + ApplyEntityModifiers( Queue.Player, Ent ) + ApplyBoneModifiers( Queue.Player, Ent ) + + --If the entity has a PostEntityPaste function tell it to use it now + if Ent.PostEntityPaste then + local status, valid = pcall(Ent.PostEntityPaste, Ent, Queue.Player, Ent, Queue.CreatedEntities) + if(not status)then + print("AD2 PostEntityPaste Error: "..tostring(valid)) + end + end + end + + Queue.Entity = false + Queue.Constraint = true + Queue.Current = 1 + end + + if(#AdvDupe2.JobManager.Queue>=AdvDupe2.JobManager.CurrentPlayer+1)then + AdvDupe2.JobManager.CurrentPlayer = AdvDupe2.JobManager.CurrentPlayer+1 + else + AdvDupe2.JobManager.CurrentPlayer = 1 + end + else + if(#Queue.ConstraintList>0)then + + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end + if(not Queue.ConstraintList[Queue.Current])then Queue.Current = Queue.Current+1 return end + + local Entity = CreateConstraintFromTable( Queue.ConstraintList[Queue.Current], Queue.CreatedEntities, Queue.EntityList, Queue.Player, true ) + if IsValid(Entity) then + table.insert( Queue.CreatedConstraints, Entity ) + end + elseif(table.Count(Queue.ConstraintList)>0)then + local tbl = {} + for k,v in pairs(Queue.ConstraintList)do + table.insert(tbl, v) + end + Queue.ConstraintList = tbl + Queue.Current=0 + end + + AdvDupe2.UpdateProgressBar(Queue.Player, math.floor((Queue.Percent*(Queue.Current+Queue.Plus))*100)) + Queue.Current = Queue.Current+1 + + if(Queue.Current>#Queue.ConstraintList)then + + local unfreeze = tobool(Queue.Player:GetInfo("advdupe2_paste_unfreeze")) or false + local preservefrozenstate = tobool(Queue.Player:GetInfo("advdupe2_preserve_freeze")) or false + + //Remove the undo for stopping pasting + local undos = undo.GetTable()[Queue.Player:UniqueID()] + local str = "AdvDupe2_"..Queue.Player:UniqueID() + for i=#undos, 1, -1 do + if(undos[i] and undos[i].Name == str)then + undos[i] = nil + -- Undo module netmessage + net.Start( "Undo_Undone" ) + net.WriteInt( i, 16 ) + net.Send( Queue.Player ) + break + end + end + + undo.Create "AdvDupe2" + local phys + local edit + local mass + for _,v in pairs( Queue.CreatedEntities ) do + if(not IsValid(v))then + v = nil + else + edit = true + + if(Queue.EntityList[_].BuildDupeInfo.DupeParentID~=nil and Queue.Parenting)then + v:SetParent(Queue.CreatedEntities[Queue.EntityList[_].BuildDupeInfo.DupeParentID]) + if(v.Constraints~=nil)then + for i,c in pairs(v.Constraints)do + if(c and constraints[c.Type])then + edit=false + break + end + end + end + if(edit and IsValid(v:GetPhysicsObject()))then + mass = v:GetPhysicsObject():GetMass() + v:PhysicsInitShadow(false, false) + v:SetCollisionGroup(COLLISION_GROUP_WORLD) + v:GetPhysicsObject():EnableMotion(false) + v:GetPhysicsObject():Sleep() + v:GetPhysicsObject():SetMass(mass) + end + else + edit=false + end + + if(unfreeze)then + for i=0, v:GetPhysicsObjectCount() do + phys = v:GetPhysicsObjectNum(i) + if(IsValid(phys))then + phys:EnableMotion(true) //Unfreeze the entitiy and all of its objects + phys:Wake() + end + end + elseif(preservefrozenstate)then + for i=0, v:GetPhysicsObjectCount() do + phys = v:GetPhysicsObjectNum(i) + if(IsValid(phys))then + if(Queue.EntityList[_].BuildDupeInfo.PhysicsObjects[i].Frozen)then + phys:EnableMotion(true) //Restore the entity and all of its objects to their original frozen state + phys:Wake() + else + Queue.Player:AddFrozenPhysicsObject( v, phys ) + end + end + end + else + for i=0, v:GetPhysicsObjectCount() do + phys = v:GetPhysicsObjectNum(i) + if(IsValid(phys))then + if(phys:IsMoveable())then + phys:EnableMotion(false) //Freeze the entitiy and all of its objects + Queue.Player:AddFrozenPhysicsObject( v, phys ) + end + end + end + end + + if v:GetClass() == 'prop_dynamic' and not IsValid(v:GetPhysicsObject()) then + v:PhysicsInitStatic(SOLID_VPHYSICS) + v:Activate() + elseif(not edit or not Queue.DisableParents)then + v:SetNotSolid(v.SolidMod) + end + + undo.AddEntity( v ) + end + end + undo.SetCustomUndoText("Undone "..(Queue.Name or "Advanced Duplication")) + undo.SetPlayer( Queue.Player ) + undo.Finish() + + if Queue.Perma and IsValid(Queue.Player) and Queue.Player:query(L.permissions_permaprops) then + permaprops.markPerma(Queue.CreatedEntities) + end + + hook.Call("AdvDupe_FinishPasting", nil, {{EntityList=Queue.EntityList, CreatedEntities=Queue.CreatedEntities, ConstraintList=Queue.ConstraintList, CreatedConstraints=Queue.CreatedConstraints, HitPos=Queue.PositionOffset, Player=Queue.Player}}, 1) + AdvDupe2.FinishPasting(Queue.Player, true) + + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + end + end + if(#AdvDupe2.JobManager.Queue>=AdvDupe2.JobManager.CurrentPlayer+1)then + AdvDupe2.JobManager.CurrentPlayer = AdvDupe2.JobManager.CurrentPlayer+1 + else + AdvDupe2.JobManager.CurrentPlayer = 1 + end + end +end + +local ticktotal = 0 +local function ErrorCatchSpawning() + + ticktotal = ticktotal + AdvDupe2.SpawnRate + while ticktotal >= 1 do + ticktotal = ticktotal - 1 + local status, error = pcall(AdvDupe2_Spawn) + + if(not status)then + //PUT ERROR LOGGING HERE + + if(not AdvDupe2.JobManager.Queue)then + print("[AdvDupe2Notify]\t"..error) + AdvDupe2.JobManager.Queue = {} + return + end + + local Queue = AdvDupe2.JobManager.Queue[AdvDupe2.JobManager.CurrentPlayer] + if(not Queue)then + print("[AdvDupe2Notify]\t"..error) + return + end + + if(IsValid(Queue.Player))then + AdvDupe2.Notify(Queue.Player, error) + + local undos = undo.GetTable()[Queue.Player:UniqueID()] + local str = "AdvDupe2_"..Queue.Player:UniqueID() + for i=#undos, 1, -1 do + if(undos[i] and undos[i].Name == str)then + undos[i] = nil + -- Undo module netmessage + net.Start( "Undo_Undone" ) + net.WriteInt( i, 16 ) + net.Send( Queue.Player ) + break + end + end + else + print("[AdvDupe2Notify]\t"..error) + end + + for k,v in pairs(Queue.CreatedEntities)do + if(IsValid(v))then v:Remove() end + end + + if(IsValid(Queue.Player))then + AdvDupe2.FinishPasting(Queue.Player, true) + end + + table.remove(AdvDupe2.JobManager.Queue, AdvDupe2.JobManager.CurrentPlayer) + + if(#AdvDupe2.JobManager.Queue==0)then + hook.Remove("Tick", "AdvDupe2_Spawning") + DisablePropCreateEffect = nil + AdvDupe2.JobManager.PastingHook = false + else + if(#Queuetonumber(GetConVarString("AdvDupe2_MaxConstraints")))then + AdvDupe2.Notify(ply,"Amount of constraints is greater than "..GetConVarString("AdvDupe2_MaxConstraints"),NOTIFY_ERROR) + return false + end + end + + ply.AdvDupe2.Entities = {} + ply.AdvDupe2.Constraints = {} + ply.AdvDupe2.HeadEnt={} + + if(info.ad1)then + + ply.AdvDupe2.HeadEnt.Index = tonumber(moreinfo.Head) + local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$") + ply.AdvDupe2.HeadEnt.Pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0) + local z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1 + ply.AdvDupe2.HeadEnt.Z = z + ply.AdvDupe2.HeadEnt.Pos.Z = ply.AdvDupe2.HeadEnt.Pos.Z + z + local Pos + local Ang + for k,v in pairs(dupe["Entities"])do + Pos = nil + Ang = nil + if(v.SavedParentIdx)then + if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos*1 + Ang = v.LocalAngle*1 + end + for i,p in pairs(v.PhysicsObjects)do + p.Pos = Pos or (p.LocalPos*1) + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or (p.LocalAngle*1) + p.LocalPos = nil + p.LocalAngle = nil + p.Frozen = not p.Frozen -- adv dupe 2 does this wrong way + end + v.LocalPos = nil + v.LocalAngle = nil + end + + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + + else + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + ply.AdvDupe2.HeadEnt = dupe["HeadEnt"] + end + AdvDupe2.ResetOffsets(ply, true) +end + +local function AdvDupe2_ReceiveFile(len, ply) + if not IsValid(ply) then return end + if not ply.AdvDupe2 then ply.AdvDupe2 = {} end + + local name = net.ReadString() + local _1, _2, _3 = string.find(name, "([%w_]+)") + if _3 then + ply.AdvDupe2.Name = string.sub(_3, 1, 32) + else + ply.AdvDupe2.Name = "Advanced Duplication" + end + + local stream = net.ReadStream(ply, function(data) + if data then + AdvDupe2.LoadDupe(ply, AdvDupe2.Decode(data)) + else + AdvDupe2.Notify(ply, "Duplicator Upload Failed!", NOTIFY_ERROR, 5) + end + ply.AdvDupe2.Uploading = false + end) + + if ply.AdvDupe2.Uploading then + if stream then + stream:Remove() + end + AdvDupe2.Notify(ply, "Duplicator is Busy!", NOTIFY_ERROR, 5) + elseif stream then + ply.AdvDupe2.Uploading = true + AdvDupe2.InitProgressBar(ply, "Opening: ") + end +end +net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile) diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sv_ghost.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_ghost.lua new file mode 100644 index 0000000..3a38f3f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_ghost.lua @@ -0,0 +1,70 @@ + +util.AddNetworkString("AdvDupe2_SendGhosts") +util.AddNetworkString("AdvDupe2_AddGhost") + +function AdvDupe2.SendGhost(ply, AddOne) + net.Start("AdvDupe2_AddGhost") + net.WriteString(AddOne.Model) + net.WriteInt(#AddOne.PhysicsObjects, 8) + for i=0, #AddOne.PhysicsObjects do + net.WriteAngle(AddOne.PhysicsObjects[i].Angle) + net.WriteVector(AddOne.PhysicsObjects[i].Pos) + end + net.Send(ply) +end + +function AdvDupe2.SendGhosts(ply) + if(not ply.AdvDupe2.Entities)then return end + + local cache = {} + local temp = {} + local mdls = {} + local cnt = 1 + local add = true + local head + + for k,v in pairs(ply.AdvDupe2.Entities)do + temp[cnt] = v + for i=1,#cache do + if(cache[i]==v.Model)then + mdls[cnt] = i + add=false + break + end + end + if(add)then + mdls[cnt] = table.insert(cache, v.Model) + else + add = true + end + if(k==ply.AdvDupe2.HeadEnt.Index)then + head = cnt + end + cnt = cnt+1 + end + + if(!head)then + AdvDupe2.Notify(ply, "Invalid head entity for ghosts.", NOTIFY_ERROR); + return + end + + net.Start("AdvDupe2_SendGhosts") + net.WriteInt(head, 16) + net.WriteFloat(ply.AdvDupe2.HeadEnt.Z) + net.WriteVector(ply.AdvDupe2.HeadEnt.Pos) + net.WriteInt(#cache, 16) + for i=1,#cache do + net.WriteString(cache[i]) + end + net.WriteInt(cnt-1, 16) + for i=1, #temp do + net.WriteInt(mdls[i], 16) + net.WriteInt(#temp[i].PhysicsObjects, 8) + for k=0, #temp[i].PhysicsObjects do + net.WriteAngle(temp[i].PhysicsObjects[k].Angle) + net.WriteVector(temp[i].PhysicsObjects[k].Pos) + end + end + net.Send(ply) + +end diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sv_misc.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_misc.lua new file mode 100644 index 0000000..3c831e9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_misc.lua @@ -0,0 +1,134 @@ +--[[ + Title: Miscellaneous + + Desc: Contains miscellaneous (serverside) things AD2 needs to function that don't fit anywhere else. + + Author: TB + + Version: 1.0 +]] + +--[[ + Name: SavePositions + Desc: Save the position of the entities to prevent sagging on dupe. + Params: Constraint + Returns: nil +]] + +local function SavePositions( Constraint ) + + if IsValid(Constraint) then + + if Constraint.BuildDupeInfo then return end + + if not Constraint.BuildDupeInfo then Constraint.BuildDupeInfo = {} end + + local Ent1 + local Ent2 + if IsValid(Constraint.Ent) then + Constraint.BuildDupeInfo.Ent1Ang = Constraint.Ent:GetAngles() + end + + if IsValid(Constraint.Ent1) then + Constraint.BuildDupeInfo.Ent1Ang = Constraint.Ent1:GetAngles() + if(Constraint.Ent1:GetPhysicsObjectCount()>1)then + Constraint.BuildDupeInfo.Bone1 = Constraint["Bone1"] + Constraint.BuildDupeInfo.Bone1Pos = Constraint.Ent1:GetPhysicsObjectNum(Constraint["Bone1"]):GetPos() - Constraint.Ent1:GetPos() + Constraint.BuildDupeInfo.Bone1Angle = Constraint.Ent1:GetPhysicsObjectNum(Constraint["Bone1"]):GetAngles() + end + if IsValid(Constraint.Ent2) then + Constraint.BuildDupeInfo.EntityPos = Constraint.Ent1:GetPos() - Constraint.Ent2:GetPos() + Constraint.BuildDupeInfo.Ent2Ang = Constraint.Ent2:GetAngles() + if(Constraint.Ent2:GetPhysicsObjectCount()>1)then + Constraint.BuildDupeInfo.Bone2 = Constraint["Bone2"] + Constraint.BuildDupeInfo.Bone2Pos = Constraint.Ent2:GetPhysicsObjectNum(Constraint["Bone2"]):GetPos() - Constraint.Ent2:GetPos() + Constraint.BuildDupeInfo.Bone2Angle = Constraint.Ent2:GetPhysicsObjectNum(Constraint["Bone2"]):GetAngles() + end + elseif IsValid(Constraint.Ent4) then + Constraint.BuildDupeInfo.EntityPos = Constraint.Ent1:GetPos() - Constraint.Ent4:GetPos() + Constraint.BuildDupeInfo.Ent4Ang = Constraint.Ent4:GetAngles() + if(Constraint.Ent4:GetPhysicsObjectCount()>1)then + Constraint.BuildDupeInfo.Bone2 = Constraint["Bone4"] + Constraint.BuildDupeInfo.Bone2Pos = Constraint.Ent4:GetPhysicsObjectNum(Constraint["Bone4"]):GetPos() - Constraint.Ent4:GetPos() + Constraint.BuildDupeInfo.Bone2Angle = Constraint.Ent4:GetPhysicsObjectNum(Constraint["Bone4"]):GetAngles() + end + end + + end + + end + +end + + +local function FixMagnet(Magnet) + Magnet.Entity = Magnet +end + +//Find out when a Constraint is created +timer.Simple(0, function() + hook.Add( "OnEntityCreated", "AdvDupe2_SavePositions", function(entity) + + if not IsValid( entity ) then return end + + local a,b = entity:GetClass():match("^(.-)_(.+)") + + if b == "magnet" then + timer.Simple( 0, function() FixMagnet(entity) end) + end + + if a == "phys" then + if(b=="constraintsystem")then return end + timer.Simple( 0, function() SavePositions(entity) end) + end + + end ) + end) + +-- Register camera entity class +-- fixes key not being saved (Conna) +local function CamRegister(Player, Pos, Ang, Key, Locked, Toggle, Vel, aVel, Frozen, Nocollide) + if (!Key) then return end + + local Camera = ents.Create("gmod_cameraprop") + Camera:SetAngles(Ang) + Camera:SetPos(Pos) + Camera:Spawn() + Camera:SetKey(Key) + Camera:SetPlayer(Player) + Camera:SetLocked(Locked) + Camera.toggle = Toggle + Camera:SetTracking(NULL, Vector(0)) + + if (Toggle == 1) then + numpad.OnDown(Player, Key, "Camera_Toggle", Camera) + else + numpad.OnDown(Player, Key, "Camera_On", Camera) + numpad.OnUp(Player, Key, "Camera_Off", Camera) + end + + if (Nocollide) then Camera:GetPhysicsObject():EnableCollisions(false) end + + -- Merge table + local Table = { + key = Key, + toggle = Toggle, + locked = Locked, + pl = Player, + nocollide = nocollide + } + table.Merge(Camera:GetTable(), Table) + + -- remove any camera that has the same key defined for this player then add the new one + local ID = Player:UniqueID() + GAMEMODE.CameraList[ID] = GAMEMODE.CameraList[ID] or {} + local List = GAMEMODE.CameraList[ID] + if (List[Key] and List[Key] != NULL ) then + local Entity = List[Key] + Entity:Remove() + end + List[Key] = Camera + return Camera + +end +duplicator.RegisterEntityClass("gmod_cameraprop", CamRegister, "Pos", "Ang", "key", "locked", "toggle", "Vel", "aVel", "frozen", "nocollide") \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/advdupe2/sv_networking.lua b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_networking.lua new file mode 100644 index 0000000..af2c6d5 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/advdupe2/sv_networking.lua @@ -0,0 +1,296 @@ +include "nullesc.lua" +AddCSLuaFile "nullesc.lua" + +AdvDupe2.Network = {} + +AdvDupe2.Network.Networks = {} +AdvDupe2.Network.ClientNetworks = {} +AdvDupe2.Network.SvStaggerSendRate = 0 +AdvDupe2.Network.ClStaggerSendRate = 0 +AdvDupe2.Network.Timeout = 10 + +local function CheckFileNameSv(path) + if file.Exists(path..".txt", "DATA") then + for i = 1, AdvDupe2.FileRenameTryLimit do + if not file.Exists(path.."_"..i..".txt", "DATA") then + return path.."_"..i..".txt" + end + end + end + + return path..".txt" +end + +function AdvDupe2.UpdateProgressBar(ply,percent) + umsg.Start("AdvDupe2_UpdateProgressBar",ply) + umsg.Char(percent) + umsg.End() +end + +function AdvDupe2.RemoveProgressBar(ply) + umsg.Start("AdvDupe2_RemoveProgressBar",ply) + umsg.End() +end + +//=========================================== +//========= Server To Client ========= +//=========================================== + +--[[ + Name: EstablishNetwork + Desc: Add user to the queue and set up to begin data sending + Params: Player, File data + Returns: +]] +function AdvDupe2.EstablishNetwork(ply, file) + if(not IsValid(ply))then return end + local id = ply:UniqueID() + ply.AdvDupe2.Downloading = true + AdvDupe2.Network.Networks[id] = {Player = ply, File=AdvDupe2.Null.esc(file), Length = #file, LastPos=1} + + local Cur_Time = CurTime() + local time = AdvDupe2.Network.SvStaggerSendRate - Cur_Time + + if(time > 0)then + AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate")) + time + timer.Simple(time, function() AdvDupe2_SendFile(id) end) + else + AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate")) + AdvDupe2_SendFile(id) + end + +end + +--[[ + Name: AdvDupe2_SendFile + Desc: Client has responded and is ready for the next chunk of data + Params: Network table, Network ID + Returns: +]] +function AdvDupe2_SendFile(ID) + local Net = AdvDupe2.Network.Networks[ID] + + if(not IsValid(Net.Player))then + AdvDupe2.Network.Networks[ID] = nil + return + end + + local status = 0 + local data = "" + + if(Net.LastPos==1)then status = 1 AdvDupe2.InitProgressBar(Net.Player,"Saving:") end + data = string.sub(Net.File, Net.LastPos, Net.LastPos+tonumber(GetConVarString("AdvDupe2_MaxDownloadBytes2"))) + + Net.LastPos=Net.LastPos+tonumber(GetConVarString("AdvDupe2_MaxDownloadBytes2"))+1 + + if(Net.LastPos>=Net.Length)then status = 2 end + + net.Start("AdvDupe2_ReceiveFile") + net.WriteInt(status, 8) + net.WriteString(data) + net.Send(Net.Player) + + AdvDupe2.UpdateProgressBar(Net.Player, math.floor((Net.LastPos/Net.Length)*100)) + + if(Net.LastPos>=Net.Length)then + Net.Player.AdvDupe2.Downloading = false + AdvDupe2.RemoveProgressBar(Net.Player) + AdvDupe2.Network.Networks[ID] = nil + return + end + + local Cur_Time = CurTime() + local time = AdvDupe2.Network.SvStaggerSendRate - Cur_Time + + timer.Simple(time, function() AdvDupe2_SendFile(ID) end) + + if(time > 0)then + AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate")) + time + else + AdvDupe2.Network.SvStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ServerSendRate")) + end + +end + + +//=========================================== +//========= Client To Server ========= +//=========================================== + +function AdvDupe2.LoadDupe(ply,success,dupe,info,moreinfo) + if(not IsValid(ply))then return end + + if not success then + AdvDupe2.Notify(ply,"Could not open "..dupe,NOTIFY_ERROR) + return + end + + if(not game.SinglePlayer())then + if(tonumber(GetConVarString("AdvDupe2_MaxConstraints"))~=0 and #dupe["Constraints"]>tonumber(GetConVarString("AdvDupe2_MaxConstraints")))then + AdvDupe2.Notify(ply,"Amount of constraints is greater than "..GetConVarString("AdvDupe2_MaxConstraints"),NOTIFY_ERROR) + return false + end + end + + ply.AdvDupe2.Entities = {} + ply.AdvDupe2.Constraints = {} + ply.AdvDupe2.HeadEnt={} + + if(info.ad1)then + + ply.AdvDupe2.HeadEnt.Index = tonumber(moreinfo.Head) + local spx,spy,spz = moreinfo.StartPos:match("^(.-),(.-),(.+)$") + ply.AdvDupe2.HeadEnt.Pos = Vector(tonumber(spx) or 0, tonumber(spy) or 0, tonumber(spz) or 0) + local z = (tonumber(moreinfo.HoldPos:match("^.-,.-,(.+)$")) or 0)*-1 + ply.AdvDupe2.HeadEnt.Z = z + ply.AdvDupe2.HeadEnt.Pos.Z = ply.AdvDupe2.HeadEnt.Pos.Z + z + local Pos + local Ang + for k,v in pairs(dupe["Entities"])do + Pos = nil + Ang = nil + if(v.SavedParentIdx)then + if(not v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.DupeParentID = v.SavedParentIdx + Pos = v.LocalPos*1 + Ang = v.LocalAngle*1 + end + for i,p in pairs(v.PhysicsObjects)do + p.Pos = Pos or (p.LocalPos*1) + p.Pos.Z = p.Pos.Z - z + p.Angle = Ang or (p.LocalAngle*1) + p.LocalPos = nil + p.LocalAngle = nil + p.Frozen = not p.Frozen -- adv dupe 2 does this wrong way + end + v.LocalPos = nil + v.LocalAngle = nil + end + + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + + else + ply.AdvDupe2.Entities = dupe["Entities"] + ply.AdvDupe2.Constraints = dupe["Constraints"] + ply.AdvDupe2.HeadEnt = dupe["HeadEnt"] + end + if(game.SinglePlayer())then AdvDupe2.SendGhosts(ply) end + AdvDupe2.ResetOffsets(ply, true) +end + +function AdvDupe2.ReceiveNextStep(id) + if(not IsValid(AdvDupe2.Network.ClientNetworks[id].Player))then AdvDupe2.Network.ClientNetworks[id] = nil return end + umsg.Start("AdvDupe2_ReceiveNextStep", AdvDupe2.Network.ClientNetworks[id].Player) + umsg.Short(tonumber(GetConVarString("AdvDupe2_MaxUploadBytes2"))) + umsg.End() +end + +--[[ + Name: AdvDupe2_InitReceiveFile + Desc: Start the file recieving process and send the servers settings to the client + Params: concommand + Returns: +]] +local function AdvDupe2_InitReceiveFile( ply, cmd, args ) + if(not IsValid(ply))then return end + if(not ply.AdvDupe2)then ply.AdvDupe2={} end + + local id = ply:UniqueID() + if(ply.AdvDupe2.Pasting or ply.AdvDupe2.Downloading or AdvDupe2.Network.ClientNetworks[id])then + if AdvDupe2.Network.ClientNetworks[id] and AdvDupe2.Network.ClientNetworks[id].Timeout < CurTime() then + AdvDupe2.Network.ClientNetworks[id]=nil + ply.AdvDupe2.Downloading = false + ply.AdvDupe2.Uploading = false + else + umsg.Start("AdvDupe2_UploadRejected", ply) + umsg.Bool(false) + umsg.End() + AdvDupe2.Notify(ply, "Duplicator is Busy!",NOTIFY_ERROR,5) + return + end + end + + ply.AdvDupe2.Downloading = true + ply.AdvDupe2.Uploading = true + //ply.AdvDupe2.Name = args[1] + + AdvDupe2.Network.ClientNetworks[id] = {Player = ply, Data = "", Size = 0, Timeout = CurTime() + AdvDupe2.Network.Timeout} + + local Cur_Time = CurTime() + local time = AdvDupe2.Network.ClStaggerSendRate - Cur_Time + if(time > 0)then + AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate")) + time + AdvDupe2.Network.ClientNetworks[id].NextSend = time + Cur_Time + timer.Simple(time, function() AdvDupe2.ReceiveNextStep(id) end) + else + AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate")) + AdvDupe2.Network.ClientNetworks[id].NextSend = Cur_Time + AdvDupe2.ReceiveNextStep(id) + end + +end +concommand.Add("AdvDupe2_InitReceiveFile", AdvDupe2_InitReceiveFile) + + +local function AdvDupe2_SetNextResponse(id) + + local Cur_Time = CurTime() + local time = AdvDupe2.Network.ClStaggerSendRate - Cur_Time + if(time > 0)then + AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate")) + time + AdvDupe2.Network.ClientNetworks[id].NextSend = time + Cur_Time + timer.Simple(time, function() AdvDupe2.ReceiveNextStep(id) end) + else + AdvDupe2.Network.ClStaggerSendRate = Cur_Time + tonumber(GetConVarString("AdvDupe2_ClientSendRate")) + AdvDupe2.Network.ClientNetworks[id].NextSend = Cur_Time + AdvDupe2.ReceiveNextStep(id) + end + +end + +--[[ + Name: AdvDupe2_ReceiveFile + Desc: Receive file data from the client to save on the server + Params: concommand + Returns: +]] +local function AdvDupe2_ReceiveFile(len, ply, len2) + if(not IsValid(ply))then return end + + local id = ply:UniqueID() + if(not AdvDupe2.Network.ClientNetworks[id])then return end + local Net = AdvDupe2.Network.ClientNetworks[id] + Net.Timeout = CurTime() + AdvDupe2.Network.Timeout + + //Someone tried to mess with upload commands + if(Net.NextSend - CurTime()>0)then + AdvDupe2.Network.ClientNetworks[id]=nil + ply.AdvDupe2.Downloading = false + ply.AdvDupe2.Uploading = false + + umsg.Start("AdvDupe2_UploadRejected", ply) + umsg.Bool(false) + umsg.End() + AdvDupe2.Notify(ply,"Upload Rejected!",NOTIFY_GENERIC,5) + return + end + + local status = net.ReadBit() + Net.Data = Net.Data..net.ReadString() + + if(status==1)then + AdvDupe2.Decode(AdvDupe2.Null.invesc(Net.Data), function(success,dupe,info,moreinfo) AdvDupe2.LoadDupe(ply, success, dupe, info, moreinfo) end) + AdvDupe2.Network.ClientNetworks[id]=nil + ply.AdvDupe2.Downloading = false + ply.AdvDupe2.Uploading = false + + umsg.Start("AdvDupe2_UploadRejected", ply) + umsg.Bool(true) + umsg.End() + return + end + + AdvDupe2_SetNextResponse(id) +end +net.Receive("AdvDupe2_ReceiveFile", AdvDupe2_ReceiveFile) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/advdupe2_cl_init.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/advdupe2_cl_init.lua new file mode 100644 index 0000000..b86a243 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/advdupe2_cl_init.lua @@ -0,0 +1,30 @@ +AdvDupe2 = { + Version = "1.1.0", + Revision = 51, + InfoText = {}, + DataFolder = "advdupe2", + FileRenameTryLimit = 256, + ProgressBar = {} +} + +if(!file.Exists(AdvDupe2.DataFolder, "DATA"))then + file.CreateDir(AdvDupe2.DataFolder) +end + +include "advdupe2/file_browser.lua" +include "advdupe2/sh_codec.lua" +include "advdupe2/sh_netstream.lua" +include "advdupe2/cl_file.lua" +include "advdupe2/cl_ghost.lua" + +function AdvDupe2.Notify(msg,typ,dur) + surface.PlaySound(typ == 1 and "buttons/button10.wav" or "ambient/water/drip1.wav") + GAMEMODE:AddNotify(msg, typ or NOTIFY_GENERIC, dur or 5) + //if not game.SinglePlayer() then + print("[AdvDupe2Notify]\t"..msg) + //end +end + +usermessage.Hook("AdvDupe2Notify",function(um) + AdvDupe2.Notify(um:ReadString(),um:ReadChar(),um:ReadChar()) +end) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/cl_permaload.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/cl_permaload.lua new file mode 100644 index 0000000..018268a --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/cl_permaload.lua @@ -0,0 +1,36 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +if not PermaProps then PermaProps = {} end + +print("---------------------------------") +print("| Loading ClientSide PermaProps |") +print("---------------------------------") + +for k, v in pairs(file.Find("permaprops/cl_*.lua", "LUA")) do + + include("permaprops/".. v) + print("permaprops/".. v) + + +end + +print("---------------------------------") +print("| Loading Shared PermaProps |") +print("---------------------------------") + +for k, v in pairs(file.Find("permaprops/sh_*.lua", "LUA")) do + + include("permaprops/".. v) + print("permaprops/".. v) + + +end + +print("---------------------------------") \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/dbg-event.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/dbg-event.lua new file mode 100644 index 0000000..f5da2ac --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/dbg-event.lua @@ -0,0 +1,9 @@ +netstream.Hook('dbg-event.askForRefugee', function(mdlData) + + local pnl = octolib.models.selector({mdlData}, function(_, skin, bgs) + netstream.Start('dbg-event.askForRefugee', skin, bgs) + end) + pnl.layoutPan:Remove() + pnl.params:ChangeModel(1, mdlData) + +end) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/keypad.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/keypad.lua new file mode 100644 index 0000000..803cf85 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/keypad.lua @@ -0,0 +1,110 @@ + +hook.Add('PlayerBindPress', 'Keypad', function(ply, bind, pressed) + if not pressed then + return + end + + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * 65, + filter = ply + }) + + local ent = tr.Entity + + if not IsValid(ent) or not ent.IsKeypad then + return + end + + if string.find(bind, '+use', nil, true) then + local element = ent:GetHoveredElement() + if not element then return end + if element.number then + return octolib.notify.show('warning', 'Используй для этого кнопки с цифрами на клавиатуре') + end + if not element.click then + return + end + element.click(ent) + end + + if string.find(bind, 'slot') then + return true + end + +end) + + +local physical_keypad_commands = { + + [KEY_ENTER] = function(self) + self:SendCommand(self.Command_Accept) + end, + + [KEY_PAD_ENTER] = function(self) + self:SendCommand(self.Command_Accept) + end, + + [KEY_MINUS] = function(self) + self:SendCommand(self.Command_Abort) + end, + + [KEY_PAD_MINUS] = function(self) + self:SendCommand(self.Command_Abort) + end, + +} + +for i = KEY_PAD_1, KEY_PAD_9 do + physical_keypad_commands[i] = function(self) + self:SendCommand(self.Command_Enter, i - KEY_PAD_1 + 1) + end +end + +for i = KEY_1, KEY_9 do + physical_keypad_commands[i] = function(self) + self:SendCommand(self.Command_Enter, i - KEY_1 + 1) + end +end + +local last_press = 0 + +local enter_strict = CreateConVar('keypad_willox_enter_strict', '0', FCVAR_ARCHIVE, 'Only allow the numpad\'s enter key to be used to accept keypads\' input') + +hook.Add('CreateMove', 'Keypad', function(cmd) + + if RealTime() - 0.1 < last_press then + return + end + + for key, handler in pairs(physical_keypad_commands) do + if input.WasKeyPressed(key) then + + if enter_strict:GetBool() and key == KEY_ENTER then + continue + end + + local ply = LocalPlayer() + + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * 65, + filter = ply + }) + + local ent = tr.Entity + + if not IsValid(ent) or not ent.IsKeypad then + return + end + + last_press = RealTime() + + handler(ent) + + return + + end + end + +end) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/materialbrowser.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/materialbrowser.lua new file mode 100644 index 0000000..987d11d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/materialbrowser.lua @@ -0,0 +1,101 @@ +local Window +local MaterialBox +local List +local Reload +local MatRetMaterial = CreateMaterial("MatRetMaterial", "UnlitGeneric", {["$basetexture"] = ""}) + +local function ParseDir(t, dir, ext) + local files, dirs = file.Find(dir.."*", "GAME") + for _, fdir in pairs(dirs) do + local n = t:AddNode(fdir) + n:SetExpanded(true) + n.DoClick = function() + ParseDir(n, dir..fdir.."/", ext) + n.DoClick = function() end + end + end + for k,v in pairs(files) do + local pathExt = string.sub(v, -4) + local isValidExt = false + for _,y in pairs(ext) do + if pathExt == y then + isValidExt = true + break + end + end + if isValidExt then + local arq = string.sub(dir..v, 11, -5) + local n = t:AddNode(v) + n.Icon:SetImage("icon16/picture.png") + n.DoClick = function() + RunConsoleCommand("mapret_material", arq) + if not Material(arq):IsError() then -- If the file is a .vmt + if Material(arq):GetTexture("$basetexture") then -- If the file has a $basetexture + MatRetMaterial:SetTexture("$basetexture", Material(arq):GetTexture("$basetexture")); + else + MatRetMaterial:SetTexture("$basetexture", Material("vgui/avatar_default"):GetTexture("$basetexture")); + end + MaterialBox:SetMaterial(MatRetMaterial) + else + MaterialBox:SetImage(arq..pathExt, "vgui/avatar_default") -- Shows every texture. Beautiful + -- MaterialBox:SetTexture("$basetexture",arq) -- Shows the textures that I can apply. Realistic + end + end + end + end +end + +local function CreateMaterialBrowser() + Window = vgui.Create("DFrame") + Window:SetTitle("Material Browser") + Window:SetSize(300, 750) + Window:SetDeleteOnClose(false) + Window:SetSizable(true) + Window:SetMinHeight(750) + Window:SetMinWidth(300) + Window:SetIcon("icon16/picture.png") + Window:SetBackgroundBlur(true) + Window:Center() + Window:SetPaintBackgroundEnabled(false) + + MaterialBox = vgui.Create("DImage", Window) + MaterialBox:SetSize(Window:GetWide(), 0.41*(Window:GetTall()-25)) + MaterialBox:SetPos(0, 25) + + local function CreateList() + List = vgui.Create("DTree", Window) + List:SetSize(Window:GetWide(), 0.55*(Window:GetTall()-25)) + List:SetPos(0, 0.41*(Window:GetTall()-25)+25) + List:SetShowIcons(true) + end + CreateList() + + local function FillList() + local node = List:AddNode("Materials! (click one to select)") + --ParseDir(node, "materials/", { ".vmt", ".png", ".jpg" }) + ParseDir(node, "materials/", { ".vmt" }) + node:SetExpanded(true) + end + + Reload = vgui.Create("DButton", Window) + Reload:SetSize(Window:GetWide(), 0.04*(Window:GetTall()-25)) + Reload:SetPos(0, 0.96*(Window:GetTall()-25)+25) + Reload:SetText("Reload List") + Reload.DoClick = function() + List:Remove() + CreateList() + FillList() + end + + FillList() +end + +local function ShowBrowser() + if not Window then + CreateMaterialBrowser() + end + Window:SetVisible(true) + Window:MakePopup() +end + +concommand.Add("mapret_materialbrowser", ShowBrowser) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/morematerials.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/morematerials.lua new file mode 100644 index 0000000..ac52300 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/morematerials.lua @@ -0,0 +1,279 @@ +// adding materials to the material toolguns list + +list.Add( "OverrideMaterials", "models/XQM//Deg360" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesGB" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesRed" ) +list.Add( "OverrideMaterials", "models/XQM//SquaredMat" ) +list.Add( "OverrideMaterials", "models/XQM//WoodTexture_1" ) +list.Add( "OverrideMaterials", "models/airboat/airboat_blur02" ) +list.Add( "OverrideMaterials", "models/alyx/emptool_glow" ) +list.Add( "OverrideMaterials", "models/antlion/antlion_innards" ) +list.Add( "OverrideMaterials", "models/barnacle/roots" ) +list.Add( "OverrideMaterials", "models/combine_advisor/body9" ) +list.Add( "OverrideMaterials", "models/combine_advisor/mask" ) +list.Add( "OverrideMaterials", "models/combine_scanner/scanner_eye" ) +list.Add( "OverrideMaterials", "models/debug/debugwhite" ) +list.Add( "OverrideMaterials", "models/dog/eyeglass" ) +list.Add( "OverrideMaterials", "models/effects/portalrift_sheet" ) +list.Add( "OverrideMaterials", "models/effects/slimebubble_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode1_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode_sheet" ) +list.Add( "OverrideMaterials", "models/gibs/metalgibs/metal_gibs" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs01" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs02" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs03" ) +list.Add( "OverrideMaterials", "models/player/player_chrome1" ) +list.Add( "OverrideMaterials", "models/props_animated_breakable/smokestack/brickwall002a" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_buildings/destroyedbuilldingwall01a" ) +list.Add( "OverrideMaterials", "models/props_buildings/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_c17/frostedglass_01a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric001a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric002a" ) +list.Add( "OverrideMaterials", "models/props_c17/furnituremetal001a" ) +list.Add( "OverrideMaterials", "models/props_c17/gate_door02a" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder001" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder002" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder003" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01a" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01b" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01c" ) +list.Add( "OverrideMaterials", "models/props_canal/canalmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/coastmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/metalcrate001d" ) +list.Add( "OverrideMaterials", "models/props_canal/metalwall005b" ) +list.Add( "OverrideMaterials", "models/props_canal/rock_riverbed01a" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable_b" ) +list.Add( "OverrideMaterials", "models/props_combine/com_shield001a" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_interface_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_monitorbay_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/metal_combinebridge001" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes01" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes03" ) +list.Add( "OverrideMaterials", "models/props_combine/prtl_sky_sheet" ) +list.Add( "OverrideMaterials", "models/props_combine/stasisfield_beam" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template010a" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template022j" ) +list.Add( "OverrideMaterials", "models/props_debris/composite_debris" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor013a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor020a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretewall019a" ) +list.Add( "OverrideMaterials", "models/props_debris/metalwall001a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterceiling008a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall009d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall039c" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall040c" ) +list.Add( "OverrideMaterials", "models/props_debris/tilefloor001c" ) +list.Add( "OverrideMaterials", "models/props_foliage/driftwood_01a" ) +list.Add( "OverrideMaterials", "models/props_foliage/oak_tree01" ) +list.Add( "OverrideMaterials", "models/props_foliage/tree_deciduous_01a_trunk" ) +list.Add( "OverrideMaterials", "models/props_interiors/metalfence007a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01b" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01c" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01d" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01e" ) +list.Add( "OverrideMaterials", "models/props_lab/Tank_Glass001" ) +list.Add( "OverrideMaterials", "models/props_lab/cornerunit_cloud" ) +list.Add( "OverrideMaterials", "models/props_lab/door_klab01" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens2" ) +list.Add( "OverrideMaterials", "models/props_lab/warp_sheet" ) +list.Add( "OverrideMaterials", "models/props_lab/xencrystal_sheet" ) +list.Add( "OverrideMaterials", "models/props_pipes/GutterMetal01a") +list.Add( "OverrideMaterials", "models/props_pipes/destroyedpipes01a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipemetal001a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipeset_metal02" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin1" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin2" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001b" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretefloor010a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall064b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall066a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/dirtwall001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/metal_tram001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/quarryobjects01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff04a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockgranite02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat02" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a_skin2" ) +list.Add( "OverrideMaterials", "models/shadertest/predator" ) +list.Add( "OverrideMaterials", "models/weapons/v_crossbow/rebar_glow" ) +list.Add( "OverrideMaterials", "models/weapons/v_crowbar/crowbar_cyl" ) +list.Add( "OverrideMaterials", "models/weapons/v_grenade/grenade body" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light1" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light2" ) +list.Add( "OverrideMaterials", "models/weapons/v_smg1/texture5" ) +list.Add( "OverrideMaterials", "models/XQM/BoxFull_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CellShadedCamo_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CinderBlock_Tex" ) +list.Add( "OverrideMaterials", "models/XQM/JetBody2TailPiece_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/PoleX1_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/Rails/gumball_1" ) +list.Add( "OverrideMaterials", "models/XQM/SquaredMatInverted" ) +list.Add( "OverrideMaterials", "models/XQM/WoodPlankTexture" ) +list.Add( "OverrideMaterials", "models/XQM/boxfull_diffuse" ) +list.Add( "OverrideMaterials", "models/dav0r/hoverball" ) +list.Add( "OverrideMaterials", "models/spawn_effect" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_white" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_wood" ) +list.Add( "OverrideMaterials", "phoenix_storms/Future_vents" ) +list.Add( "OverrideMaterials", "phoenix_storms/FuturisticTrackRamp_1-2" ) +list.Add( "OverrideMaterials", "phoenix_storms/OfficeWindow_1-1" ) +list.Add( "OverrideMaterials", "phoenix_storms/Pro_gear_side" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/blue_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/camera" ) +list.Add( "OverrideMaterials", "phoenix_storms/car_tire" ) +list.Add( "OverrideMaterials", "phoenix_storms/checkers_map" ) +list.Add( "OverrideMaterials", "phoenix_storms/cigar" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete0" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete1" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete2" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete3" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier2_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_pipe_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/egg" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear_top" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/heli" ) +list.Add( "OverrideMaterials", "phoenix_storms/indentTiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/iron_rails" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_wheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalfence004a" ) +list.Add( "OverrideMaterials", "phoenix_storms/middle" ) +list.Add( "OverrideMaterials", "phoenix_storms/mrref2" ) +list.Add( "OverrideMaterials", "phoenix_storms/output_jack" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/interior_sides" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/train_floor" ) +list.Add( "OverrideMaterials", "phoenix_storms/potato" ) +list.Add( "OverrideMaterials", "phoenix_storms/pro_gear_top2" ) +list.Add( "OverrideMaterials", "phoenix_storms/ps_grass" ) +list.Add( "OverrideMaterials", "phoenix_storms/road" ) +list.Add( "OverrideMaterials", "phoenix_storms/roadside" ) +list.Add( "OverrideMaterials", "phoenix_storms/scrnspace" ) +list.Add( "OverrideMaterials", "phoenix_storms/side" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic1" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/smallwheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/spheremappy" ) +list.Add( "OverrideMaterials", "phoenix_storms/t_light" ) +list.Add( "OverrideMaterials", "phoenix_storms/thruster" ) +list.Add( "OverrideMaterials", "phoenix_storms/tiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/top" ) +list.Add( "OverrideMaterials", "phoenix_storms/torpedo" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamside" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamtop" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plateside" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_fps" ) +list.Add( "OverrideMaterials", "phoenix_storms/window" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_blue" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_green" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_red" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_dome" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_side" ) + +// Checking if CSS is mounted and adding CSS textures if it is + +function engine.IsMounted(g) + for k,v in pairs(engine.GetGames()) do + if (' cstrike' ) then + return true; + end + end +end + +if IsMounted( 'cstrike' ) and (engine.IsMounted('cstrike')) then + +list.Add( "OverrideMaterials", "models/cs_havana/wndb" ) +list.Add( "OverrideMaterials", "models/cs_havana/wndd" ) +list.Add( "OverrideMaterials", "models/cs_italy/light_orange" ) +list.Add( "OverrideMaterials", "models/cs_italy/plaster" ) +list.Add( "OverrideMaterials", "models/cs_italy/pwtrim2" ) +list.Add( "OverrideMaterials", "models/de_cbble/wndarch" ) +list.Add( "OverrideMaterials", "models/de_chateau/ch_arch_b1" ) +list.Add( "OverrideMaterials", "models/pi_window/plaster" ) +list.Add( "OverrideMaterials", "models/pi_window/trim128" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/dollar" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/fireescapefloor" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/metal_stairs1" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap02" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneytop" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/pylon" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/boulder01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milceil001" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarock" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarockb" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milwall006" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/rocks01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams02" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams03" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/RoofEdges" ) +list.Add( "OverrideMaterials", "models/props/cs_office/clouds" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet2" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet3" ) +list.Add( "OverrideMaterials", "models/props/cs_office/screen" ) +list.Add( "OverrideMaterials", "models/props/cs_office/snowmana" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/de_inferno_boulder_03" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflra" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflrd" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/inftowertop" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/offwndwb_break" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/roofbits" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/tileroof01" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/woodfloor008a" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukconcretewalla" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukecardboard" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/pipeset_metal" ) + +end + + + +// Making sure there's no double materials in the list in case of other addons, plus sorting them + +timer.Simple(0, function() + local mats = list.GetForEdit("OverrideMaterials"); + local cleaner = {}; + for i, mat in pairs(mats) do + cleaner[mat] = true; + mats[i] = nil; + end + local i = 1; + for mat in pairs(cleaner) do + mats[i] = mat; + i = i + 1; + end + table.sort(mats); +end); diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/propshistory.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/propshistory.lua new file mode 100644 index 0000000..fe0392f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/propshistory.lua @@ -0,0 +1,148 @@ +if not spawnmenu then return end + +spawnmenu.AddCreationTab(" ", function() + local cont = vgui.Create("ContentContainer") + cont.DoRightClick = nil + g_SpawnMenu.PropsHistory = cont + local label = vgui.Create("ContentHeader") + cont:Add(label) + label:SetZPos(0) + label.m_DragSlot = nil + label:SetText("Последние спауны") + cont.IconList:SetReadOnly(true) + cont.IconList:SetDnD(nil) + cont.IconList:SetSelectionCanvas(false) + + return cont +end, "icon16/page_paste.png", -100) + +hook.Add("PopulateContent", "PropsHistory", function() + local cm = g_SpawnMenu.CreateMenu + local spawnlist = cm.Items[2] + + if spawnlist then + cm:SetActiveTab(spawnlist.Tab) + end +end) + +hook.Add("PopulateToolMenu", "PropsHistory", function() + spawnmenu.AddToolMenuOption("Utilities", "GodSent Tools", "Props_History", "#propshistory.name", "", "", function(cp) + local list = cp:AddControl("listbox", { + Label = "Размер иконок" + }) + + list:SetSortItems(false) + + local sizes = {64, 128, 256, 512} + + for a = 1, #sizes do + local an = sizes[a] + + for b = 1, #sizes do + local bn = sizes[b] + local name = an .. "x" .. bn + + list:AddOption(name, { + propshistory_size = name + }) + end + end + end) +end) + +local t = { + model = false, + wide = false, + tall = false, + skin = false, + body = false, +} + +do + local cv = CreateClientConVar("propshistory_size", "64x64", true, false, "Sets icons size") + local tonumber, stringmatch = tonumber, string.match + + local function SetIconSize(_, _, val) + local w, h = stringmatch(val, "(%d+)x(%d+)") + w, h = tonumber(w), tonumber(h) + if not w or not h then return end + t.wide = w + t.tall = h + local spawnmenu = g_SpawnMenu + if not spawnmenu then return end + local propshistory = g_SpawnMenu.PropsHistory + + if propshistory then + local list = propshistory.IconList + local children = list:GetChildren() + + for k = 1, #children do + local v = children[k] + + if v.ClassName ~= "ContentHeader" then + v:SetSize(w, h) + v:InvalidateLayout(true) + v:SetModel(v:GetModelName(), v:GetSkinID(), v:GetBodyGroup()) + end + end + + list:Layout() + end + end + + cvars.AddChangeCallback("propshistory_size", SetIconSize, "PropsHistory") + SetIconSize(_, _, cv:GetString()) +end + +local spawnmenuGetContentType, netReadString, netReadUInt = spawnmenu.GetContentType, net.ReadString, net.ReadUInt +local rightClick = function(self) + octolib.menu({ + { "Удалить", octolib.icons.silk16("delete"), function() self:Remove() end }, + }):Open() +end + +net.Receive("PropsHistory", function() + local model = netReadString() + local netReadUInt = netReadUInt + local skin = netReadUInt(8) + local body = netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) .. netReadUInt(4) + local exists + local temp = {} + local PropsHistory = g_SpawnMenu.PropsHistory + local list = PropsHistory.IconList + local children = list:GetChildren() + + for k = 1, #children do + local v = children[k] + + if v.ClassName ~= "ContentHeader" then + if v:GetModelName() == model and v:GetBodyGroup() == body and v:GetSkinID() == skin then + exists = v + else + temp[#temp + 1] = v + end + + v:SetParent() + end + end + + if exists then + list:Add(exists) + else + local cp = spawnmenuGetContentType("model") + + if cp then + local t = t + t.model = model + t.body = body + t.skin = skin + local icon = cp(PropsHistory, t) + icon.DoRightClick = rightClick + icon.OpenMenu = octolib.func.zero + end + end + + for k = 1, #temp do + list:Add(temp[k]) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/smartsnap.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/smartsnap.lua new file mode 100644 index 0000000..1449977 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/smartsnap.lua @@ -0,0 +1,833 @@ +--[[ + + +Written by Syranide, me@syranide.com +fixed and updated by minifisch, mail@minifisch.net +big thanks to Syranide! :) + + +]] +-- +if SERVER then + AddCSLuaFile() +end + +if CLIENT then + local target = { + active = false + } + + local snaptarget = { + active = false + } + + local snapkey = false + local snaptime = false + local snaplock = false + local snapclick = false + local snapclickfade = 0 + local snapcursor = false + local snapspawnmenu = false + + local cache = { + vPlayerPos = 0, + vLookPos = 0, + vLookClipPos = 0, + vLookVector = 0 + } + + local condefs = { + snap_enabled = 1, + snap_gcboost = 1, + snap_gcstrength = 125, + snap_hidegrid = 0, + snap_clickgrid = 0, + snap_toggledelay = 0, + snap_disableuse = 0, + snap_allentities = 0, + -- snap_alltools = 0, + snap_enabletoggle = 0, + snap_lockdelay = 0.5, + snap_distance = 250, + snap_gridlimit = 16, + snap_gridsize = 8, + snap_gridalpha = 0.4, + snap_gridoffset = 0.5, + snap_boundingbox = 1, + snap_revertaim = 1, + snap_centerline = 1 + } + + local convars = {} + + for key, value in pairs(condefs) do + convars[#convars + 1] = key + end + + local modelsaveset = {} + local modeloffsets = {} + + local function DrawScreenLine(vsA, vsB) + surface.DrawLine(vsA.x, vsA.y, vsB.x, vsB.y) + end + + local function ToScreen(vWorld) + local vsScreen = vWorld:ToScreen() + + return Vector(vsScreen.x, vsScreen.y, 0) + end + + local function PointToScreen(vPoint) + if cache.vLookVector:DotProduct(vPoint - cache.vLookClipPos) > 0 then return ToScreen(vPoint) end + end + + local function LineToScreen(vStart, vEnd) + local dotStart = cache.vLookVector:DotProduct(vStart - cache.vLookClipPos) + local dotEnd = cache.vLookVector:DotProduct(vEnd - cache.vLookClipPos) + + if dotStart > 0 and dotEnd > 0 then + return ToScreen(vStart), ToScreen(vEnd) + elseif dotStart > 0 or dotEnd > 0 then + local vLength = vEnd - vStart + local vIntersect = vStart + vLength * ((cache.vLookClipPos:DotProduct(cache.vLookVector) - vStart:DotProduct(cache.vLookVector)) / vLength:DotProduct(cache.vLookVector)) + + if dotStart <= 0 then + return ToScreen(vIntersect), ToScreen(vEnd) + else + return ToScreen(vStart), ToScreen(vIntersect) + end + end + end + + local function RayQuadIntersect(vOrigin, vDirection, vPlane, vX, vY) + local vp = vDirection:Cross(vY) + local d = vX:DotProduct(vp) + if (d <= 0.0) then return end + local vt = vOrigin - vPlane + local u = vt:DotProduct(vp) + if (u < 0.0 or u > d) then return end + local v = vDirection:DotProduct(vt:Cross(vX)) + if (v < 0.0 or v > d) then return end + + return Vector(u / d, v / d, 0) + end + + local function OnInitialize() + for key, value in pairs(condefs) do + CreateClientConVar(key, value, true, false) + end + + for _, filename in ipairs(file.Find('smartsnap_offsets_*.png', "GAME")) do + local file = file.Read(filename) + + if file then + lines = string.Explode("\n", file) + header = table.remove(lines, 1) + + if header == "SMARTSNAP_OFFSETS" then + for _, line in ipairs(lines) do + local pos = string.find(line, '=') + + if pos then + local key = string.lower(string.Trim(string.sub(line, 1, pos - 1))) + local value = string.Trim(string.sub(line, pos + 1)) + local c = string.Explode(",", value) + modeloffsets[key] = {tonumber(c[1]), tonumber(c[2]), tonumber(c[3]), tonumber(c[4]), tonumber(c[5]), tonumber(c[6])} + end + end + end + end + end + end + + local function OnShutDown() + output = file.Read('smartsnap_offsets_custom.png') + + if output == nil then + output = "SMARTSNAP_OFFSETS\n" + end + + for model, _ in pairs(modelsaveset) do + output = output .. model .. '=' .. table.concat(modeloffsets[model], ",") .. "\n" + end + + file.Write('smartsnap_offsets_custom.png', output) + end + + local function GetDevOffset() + local model = string.lower(target.entity:GetModel()) + + if modeloffsets[model] == nil then + modeloffsets[model] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0} + end + + return modeloffsets[model] + end + + concommand.Add("snap_dev_alloffset", function(player, command, arguments) + if target.active == true then + if #arguments >= 1 then + local v = GetDevOffset() + + for i = 1, 6 do + v[i] = v[i] + tonumber(arguments[1]) + end + end + end + end) + + concommand.Add("snap_dev_gridoffset", function(player, command, arguments) + if target.active == true then + if #arguments >= 1 then + local v = GetDevOffset() + v[target.face] = v[target.face] + tonumber(arguments[1]) + end + end + end) + + concommand.Add("snap_dev_saveoffset", function(player, command, arguments) + if target.active == true then + local v = GetDevOffset() + modelsaveset[string.lower(target.entity:GetModel())] = true + end + end) + + local function SnapToggleGrid() + if (GetConVarNumber("snap_enabled") == 0) then + RunConsoleCommand('snap_enabled', '1') + else + RunConsoleCommand('snap_enabled', '0') + end + end + + local function SnapPress() + if GetConVarNumber("snap_clickgrid") ~= 0 and not snapclick then + snapclick = true + snapclickfade = CurTime() + elseif GetConVarNumber("snap_clickgrid") == 0 or snapclick then + if (snaplock or snapcursor) then + snaptime = false + else + local toggledelay = GetConVarNumber("snap_toggledelay") + + if (toggledelay > 0 and snaptime and snaptime + toggledelay > CurTime()) then + SnapToggleGrid() + snaptime = false + snaplock = false + else + snaptime = CurTime() + end + end + + snapkey = target.active + + if (not snapcursor) then + snaplock = false + end + end + end + + local function SnapRelease() + snapkey = false + end + + local function SnapLock() + snaplock = not snaplock + end + + local function OnSpawnMenu() + snapspawnmenu = true + end + + local function OnKeyPress(player, key) + if (key == IN_USE and GetConVarNumber("snap_disableuse") == 0) then + SnapPress() + end + end + + local function OnKeyRelease(player, key) + if (key == IN_USE and GetConVarNumber("snap_disableuse") == 0) then + SnapRelease() + end + end + + local function OnThink() + if (vgui.CursorVisible()) then + if (not snapcursor and snaplock) then + snaptarget = table.Copy(target) + end + + snaptime = false + snapcursor = true + else + if (snapcursor and snaplock) then + target = snaptarget + end + + snapspawnmenu = false + snapcursor = false + end + + if (GetConVarNumber("snap_enabletoggle") ~= 0) then + if (snapkey and snaptime and not snaplock) then + if (CurTime() > snaptime + GetConVarNumber("snap_lockdelay")) then + snaplock = true + snaptime = false + end + end + end + + local locked = target.locked and target.active + target.locked = (snapkey or snaplock and not snapcursor) and target.active + + if (not target.locked and locked and GetConVarNumber("snap_revertaim") ~= 0) then + if (snapcursor) then + local screen = target.entity:LocalToWorld(target.vector):ToScreen() + gui.SetMousePos(math.Round(screen.x), math.Round(screen.y)) + else + local angles = (target.entity:LocalToWorld(target.vector) - LocalPlayer():GetShootPos()):Angle() + LocalPlayer():SetEyeAngles(angles) + end + end + end + + local function CalculateGridAxis(L) + local length = L:Length() + local grid = math.Clamp(math.floor(length / (2 * GetConVarNumber("snap_gridsize"))) * 2, 2, GetConVarNumber("snap_gridlimit")) + local offset = math.Clamp(GetConVarNumber("snap_gridoffset") / length, 0, 1 / grid) + local scale = 1 - offset * 2 + + return { + length = length, + offset = offset, + scale = scale, + grid = grid + } + end + + local function CalculateSnap(X, Y, v) + local LX = CalculateGridAxis(X) + local LY = CalculateGridAxis(Y) + local BX = math.Clamp(math.Round(v.x * LX.grid), 0, LX.grid) + local BY = math.Clamp(math.Round(v.y * LY.grid), 0, LY.grid) + + if BX == 1 and v.x < (1 / LX.grid + LX.offset) / 2 then + BX = 0 + end + + if BX == LX.grid - 1 and v.x > 1 - (1 / LX.grid + LX.offset) / 2 then + BX = LX.grid + end + + if BY == 1 and v.y < (1 / LY.grid + LY.offset) / 2 then + BY = 0 + end + + if BY == LY.grid - 1 and v.y > 1 - (1 / LY.grid + LY.offset) / 2 then + BY = LY.grid + end + + local RX = X * (BX / LX.grid) + local RY = Y * (BY / LY.grid) + + if BX == 0 then + RX = X * math.Clamp(LX.offset, 0, 1 / LX.grid) + end + + if BX == LX.grid then + RX = X * (1 - math.Clamp(LX.offset, 0, 1 / LX.grid)) + end + + if BY == 0 then + RY = Y * math.Clamp(LY.offset, 0, 1 / LY.grid) + end + + if BY == LY.grid then + RY = Y * (1 - math.Clamp(LY.offset, 0, 1 / LY.grid)) + end + + return RX + RY + end + + local function DrawGridLines(vOrigin, vSX, vSY, gridLines, offsetX, offsetY, sign) + local centerline = (GetConVarNumber("snap_centerline") ~= 0) + local vTemp = vOrigin + vSX * 0.5 + local vX = vTemp + vSY * (offsetY) + local vY = vTemp + vSY * (1 - offsetY) + local vOffset, temp + local xtemp = ToScreen(vX) - ToScreen(vY) + xtemp:Normalize() + local vsNormal = xtemp + + if math.abs(vsNormal.x) < 1 - math.abs(vsNormal.y) then + temp = -0.5 * sign + else + temp = 0.5 * sign + end + + if math.abs(vsNormal.x) < math.abs(vsNormal.y) then + vsOffset = Vector(temp, 0, 0) + else + vsOffset = Vector(0, temp, 0) + end + + if offsetX < 1 / gridLines then + local vTemp = vOrigin + vSX * offsetX + local vX = vTemp + vSY * offsetY + local vY = vTemp + vSY * (1 - offsetY) + local vsX, vsY = LineToScreen(vX, vY) + + if (vsX) then + DrawScreenLine(vsX + vsOffset, vsY + vsOffset) + end + end + + for i = 1, gridLines - 1 do + local vTemp = vOrigin + vSX * (i / gridLines) + local vX = vTemp + vSY * offsetY + local vY = vTemp + vSY * (1 - offsetY) + local vsX, vsY = LineToScreen(vX, vY) + + if (vsX) then + if (gridLines / i == 2 and centerline) then + DrawScreenLine(vsX + vsOffset * -1, vsY + vsOffset * -1) + DrawScreenLine(vsX + vsOffset * 3, vsY + vsOffset * 3) + else + DrawScreenLine(vsX + vsOffset, vsY + vsOffset) + end + end + end + + if offsetX < 1 / gridLines then + local vTemp = vOrigin + vSX * (1 - offsetX) + local vX = vTemp + vSY * offsetY + local vY = vTemp + vSY * (1 - offsetY) + local vsX, vsY = LineToScreen(vX, vY) + + if (vsX) then + DrawScreenLine(vsX + vsOffset, vsY + vsOffset) + end + end + end + + local function DrawGrid(vOrigin, vSX, vSY) + local LX = CalculateGridAxis(vSX) + local LY = CalculateGridAxis(vSY) + surface.SetDrawColor(0, 0, 0, math.Round(GetConVarNumber("snap_gridalpha") * 255)) + DrawGridLines(vOrigin, vSX, vSY, LX.grid, LX.offset, LY.offset, 1) + DrawGridLines(vOrigin, vSY, vSX, LY.grid, LY.offset, LX.offset, 1) + surface.SetDrawColor(255, 255, 255, math.Round(GetConVarNumber("snap_gridalpha") * 255)) + DrawGridLines(vOrigin, vSX, vSY, LX.grid, LX.offset, LY.offset, -1) + DrawGridLines(vOrigin, vSY, vSX, LY.grid, LY.offset, LX.offset, -1) + end + + local function DrawBoundaryLines(vOrigin, vOpposite) + local vPoint + + if (vOrigin:Distance(vOpposite) > 5) then + local x = vOpposite - vOrigin + x:Normalize() + vPoint = vOrigin + x * 5 + else + vPoint = vOrigin + (vOpposite - vOrigin) / 2 + end + + local vsA, vsB = LineToScreen(vPoint, vOrigin) + + if (vsA) then + surface.SetDrawColor(0, 0, 255, 192) + DrawScreenLine(vsA, vsB) + end + end + + local function DrawBoundary(vOrigin, vX, vY, vZ) + DrawBoundaryLines(vOrigin, vX) + DrawBoundaryLines(vOrigin, vY) + DrawBoundaryLines(vOrigin, vZ) + end + + local function DrawSnapCross(vsCenter, r, g, b) + surface.SetDrawColor(0, 0, 0, 255) + DrawScreenLine(vsCenter + Vector(-2.5, -2.0), vsCenter + Vector(2.5, 3.0)) + DrawScreenLine(vsCenter + Vector(1.5, -2.0), vsCenter + Vector(-3.5, 3.0)) + surface.SetDrawColor(r, g, b, 255) + DrawScreenLine(vsCenter + Vector(-1.5, -2.0), vsCenter + Vector(3.5, 3.0)) + DrawScreenLine(vsCenter + Vector(2.5, -2.0), vsCenter + Vector(-2.5, 3.0)) + end + + local function ComputeEdges(entity, obbmax, obbmin) + return { + lsw = entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmin.z)), + lse = entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmin.z)), + lnw = entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmin.z)), + lne = entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmin.z)), + usw = entity:LocalToWorld(Vector(obbmin.x, obbmin.y, obbmax.z)), + use = entity:LocalToWorld(Vector(obbmax.x, obbmin.y, obbmax.z)), + unw = entity:LocalToWorld(Vector(obbmin.x, obbmax.y, obbmax.z)), + une = entity:LocalToWorld(Vector(obbmax.x, obbmax.y, obbmax.z)) + } + end + + local function OnPaintHUD() + target.active = false + if GetConVarNumber("snap_clickgrid") ~= 0 and not snapclick then return end + snapclickprev = snapclick + snapclick = snapclickprev and snapclickfade > CurTime() + if (GetConVarNumber("snap_enabled") == 0) then return end + if (not LocalPlayer():Alive() or LocalPlayer():InVehicle()) then return end + + if (target.locked) then + if (not target.entity:IsValid()) then return end + else + local trace = LocalPlayer():GetEyeTrace() + cache.vLookTrace = trace + if (not trace.HitNonWorld) then return end + local entity = trace.Entity + if (entity == nil) then return end + if (not entity:IsValid()) then return end + local class = entity:GetClass() + if (class ~= 'prop_physics' and class ~= 'phys_magnet' and class ~= 'gmod_spawner' and GetConVarNumber('snap_allentities') == 0 or class == 'player') then return end + if (not LocalPlayer():GetActiveWeapon():IsValid()) then return end + if (LocalPlayer():GetActiveWeapon():GetClass() == 'weapon_physgun') then return end + if (LocalPlayer():GetActiveWeapon():GetClass() ~= 'gmod_tool') then return end + target.entity = entity + end + + --ErrorNoHalt(collectgarbage("count")) + if GetConVarNumber("snap_gcboost") ~= 0 then + collectgarbage("step", GetConVarNumber("snap_gcstrength")) + end + + snapclick = snapclickprev + snapclickfade = CurTime() + 0.25 + -- updating the cache perhaps shouldn't be done here, CalcView? + cache.vLookPos = LocalPlayer():GetShootPos() + cache.vLookVector = LocalPlayer():GetAimVector() + cache.vLookClipPos = cache.vLookPos + cache.vLookVector * 3 + local model = string.lower(target.entity:GetModel()) + local offsets = modeloffsets[model] + + if not offsets then + local offset = 0.25 + offsets = {offset, offset, offset, offset, offset, offset} + end + + if cache.eEntity ~= target.entity or cache.vEntAngles ~= target.entity:GetAngles() or vEntPosition ~= target.entity:GetPos() then + cache.eEntity = target.entity + cache.vEntAngles = target.entity:GetAngles() + cache.vEntPosition = target.entity:GetPos() + local obbmax = target.entity:OBBMaxs() + local obbmin = target.entity:OBBMins() + local obvsnap = ComputeEdges(target.entity, obbmax, obbmin) + local obbmax = target.entity:OBBMaxs() - Vector(offsets[5], offsets[3], offsets[1]) + local obbmin = target.entity:OBBMins() + Vector(offsets[6], offsets[4], offsets[2]) + local obvgrid = ComputeEdges(target.entity, obbmax, obbmin) + local faces = {{obvgrid.unw, obvgrid.usw - obvgrid.unw, obvgrid.une - obvgrid.unw, obvgrid.lnw - obvgrid.unw, Vector(0, 0, -offsets[1])}, {obvgrid.lsw, obvgrid.lnw - obvgrid.lsw, obvgrid.lse - obvgrid.lsw, obvgrid.usw - obvgrid.lsw, Vector(0, 0, offsets[2])}, {obvgrid.unw, obvgrid.une - obvgrid.unw, obvgrid.lnw - obvgrid.unw, obvgrid.usw - obvgrid.unw, Vector(0, -offsets[3], 0)}, {obvgrid.usw, obvgrid.lsw - obvgrid.usw, obvgrid.use - obvgrid.usw, obvgrid.unw - obvgrid.usw, Vector(0, offsets[4], 0)}, {obvgrid.une, obvgrid.use - obvgrid.une, obvgrid.lne - obvgrid.une, obvgrid.unw - obvgrid.une, Vector(-offsets[5], 0, 0)}, {obvgrid.unw, obvgrid.lnw - obvgrid.unw, obvgrid.usw - obvgrid.unw, obvgrid.une - obvgrid.unw, Vector(offsets[6], 0, 0)}} + cache.aGrid = obvgrid + cache.aSnap = obvsnap + cache.aFaces = faces + end + + local obvgrid = cache.aGrid + local obvsnap = cache.aSnap + local faces = cache.aFaces + + if (not target.locked) then + -- should improve this by expanding the bounding box or something instead! + -- create a larger bounding box and then planes for each side, and check distance from the plane + -- separate function perhaps? + local distance = (LocalPlayer():GetPos() - target.entity:GetPos()):Length() - (obvgrid.unw - obvgrid.lse):Length() + if (distance > GetConVarNumber("snap_distance")) then return end + + for face, vertices in ipairs(faces) do + intersection = RayQuadIntersect(cache.vLookPos, cache.vLookVector, vertices[1], vertices[2], vertices[3]) + + if (intersection) then + target.face = face + break + end + end + + if intersection == nil then return end + end + + if (GetConVarNumber("snap_boundingbox") ~= 0) then + DrawBoundary(obvgrid.unw, obvgrid.lnw, obvgrid.usw, obvgrid.une) + DrawBoundary(obvgrid.une, obvgrid.lne, obvgrid.use, obvgrid.unw) + DrawBoundary(obvgrid.lnw, obvgrid.unw, obvgrid.lsw, obvgrid.lne) + DrawBoundary(obvgrid.lne, obvgrid.une, obvgrid.lse, obvgrid.lnw) + DrawBoundary(obvgrid.usw, obvgrid.lsw, obvgrid.unw, obvgrid.use) + DrawBoundary(obvgrid.use, obvgrid.lse, obvgrid.une, obvgrid.usw) + DrawBoundary(obvgrid.lsw, obvgrid.usw, obvgrid.lnw, obvgrid.lse) + DrawBoundary(obvgrid.lse, obvgrid.use, obvgrid.lne, obvgrid.lsw) + end + + local vectorOrigin = faces[target.face][1] + local vectorX = faces[target.face][2] + local vectorY = faces[target.face][3] + local vectorZ = faces[target.face][4] + local vectorOffset = faces[target.face][5] + local vectorGrid + + if (not target.locked) then + vectorGrid = vectorOrigin + CalculateSnap(vectorX, vectorY, intersection) + + local trace = util.TraceLine({ + start = target.entity:LocalToWorld(target.entity:WorldToLocal(vectorGrid) - vectorOffset) - vectorZ:GetNormalized() * 0.01, + endpos = vectorGrid + vectorZ + }) + + local vectorSnap = trace.HitPos + target.offset = target.entity:WorldToLocal(vectorSnap) + target.vector = target.entity:WorldToLocal(vectorGrid) + target.error = true + + if (trace.Entity == nil or not trace.Entity:IsValid()) then + snaperror = -1 + elseif (trace.Entity ~= target.entity) then + snaperror = -2 + elseif (trace.HitPos == trace.StartPos) then + snaperror = -2 + else + snaperror = (LocalPlayer():GetEyeTrace().HitPos - trace.HitPos):Length() + target.error = false + + if ((vectorSnap - vectorGrid):Length() > 0.5) then + local marker = PointToScreen(vectorSnap) + + if (marker) then + DrawSnapCross(marker, 255, 255, 255) + end + end + end + else + vectorGrid = target.entity:LocalToWorld(target.vector) + local vectorSnap = target.entity:LocalToWorld(target.offset) + local marker = PointToScreen(vectorSnap) + snaperror = (LocalPlayer():GetEyeTrace().HitPos - vectorSnap):Length() + + if (marker) then + if (target.error == true) then + snaperror = -2 + DrawSnapCross(marker, 0, 255, 255) + elseif (snaperror < 0.001) then + DrawSnapCross(marker, 0, 255, 0) + elseif (snaperror < 0.1) then + DrawSnapCross(marker, 255, 255, 0) + else + DrawSnapCross(marker, 255, 0, 0) + end + end + end + + if (GetConVarNumber("snap_hidegrid") == 0) then + DrawGrid(vectorOrigin, vectorX, vectorY) + end + + target.active = true + local vsCursor = PointToScreen(vectorGrid) + + if (vsCursor) then + if (snaperror == -1) then + target.active = false + DrawSnapCross(vsCursor, 0, 255, 255) + elseif (snaperror == -2) then + DrawSnapCross(vsCursor, 255, 0, 255) + elseif (snaperror < 0.001) then + DrawSnapCross(vsCursor, 0, 255, 0) + elseif (snaperror < 0.1) then + DrawSnapCross(vsCursor, 255, 255, 0) + else + DrawSnapCross(vsCursor, 255, 0, 0) + end + end + end + + local function OnSnapView(player, origin, angles, fov) + local targetvalid = target.active and target.locked and target.entity:IsValid() + local snaptargetvalid = snaptarget.active and snaptarget.locked and snaptarget.entity:IsValid() + + if (snapcursor and not snapspawnmenu and targetvalid) then + local screen = ToScreen(target.entity:LocalToWorld(target.offset)) + gui.SetMousePos(math.Round(screen.x), math.Round(screen.y)) + end + + if (not snapcursor and targetvalid) then + return { + angles = (target.entity:LocalToWorld(target.offset) - player:GetShootPos()):Angle() + } + elseif (snaplock and snaptargetvalid) then + return { + angles = (snaptarget.entity:LocalToWorld(snaptarget.offset) - player:GetShootPos()):Angle() + } + end + end + + local function OnSnapAim(user) + local targetvalid = target.active and target.locked and target.entity:IsValid() + local snaptargetvalid = snaptarget.active and snaptarget.locked and snaptarget.entity:IsValid() + + if (not snapcursor and targetvalid) then + user:SetViewAngles((target.entity:LocalToWorld(target.offset) - LocalPlayer():GetShootPos()):Angle()) + elseif (snaplock and snaptargetvalid) then + user:SetViewAngles((snaptarget.entity:LocalToWorld(snaptarget.offset) - LocalPlayer():GetShootPos()):Angle()) + end + end + + concommand.Add("+snap", SnapPress) + concommand.Add("-snap", SnapRelease) + concommand.Add("snaplock", SnapLock) + concommand.Add("snaptogglegrid", SnapToggleGrid) + hook.Add("Initialize", "SmartsnapInitialize", OnInitialize) + hook.Add("SpawnMenuOpen", "SmartsnapSpawnMenu", OnSpawnMenu) + hook.Add("Think", "SmartsnapThink", OnThink) + hook.Add("ShutDown", "SmartsnapShutDown", OnShutDown) + hook.Add("KeyPress", "SmartsnapKeyPress", OnKeyPress) + hook.Add("KeyRelease", "SmartsnapKeyRelease", OnKeyRelease) + hook.Add("CreateMove", "SmartsnapSnap", OnSnapAim) + hook.Add("CalcView", "SmartsnapSnapView", OnSnapView) + hook.Add("SpawnMenuOpen", "SmartsnapSpawnMenu", OnSpawnMenu) + hook.Add("HUDPaintBackground", "SmartsnapPaintHUD", OnPaintHUD) + + local function OnPopulateToolPanel(panel) + panel:AddControl("ComboBox", { + Options = { + ["default"] = condefs + }, + CVars = convars, + Label = "", + MenuButton = "1", + Folder = "smartsnap" + }) + + panel:AddControl("CheckBox", { + Label = "Enable", + Command = "snap_enabled" + }) + + panel:AddControl("CheckBox", { + Label = "Use click grid (USE temporarily enables grid)", + Command = "snap_clickgrid" + }) + + panel:AddControl("CheckBox", { + Label = "Hide grid (only shows snap point)", + Command = "snap_hidegrid" + }) + + panel:AddControl("CheckBox", { + Label = "Smart toggle enabled", + Command = "snap_enabletoggle" + }) + + panel:AddControl("CheckBox", { + Label = "Revert aim to grid snap on detach", + Command = "snap_revertaim" + }) + + panel:AddControl("CheckBox", { + Label = "Enable for all entities", + Command = "snap_allentities" + }) + + -- panel:AddControl("CheckBox", { + -- Label = "Enable for all tools", + -- Command = "snap_alltools" + -- }) + + panel:AddControl("CheckBox", { + Label = "Draw thick center lines", + Command = "snap_centerline" + }) + + panel:AddControl("Slider", { + Label = "Grid toggle delay (double click snap-key)", + Command = "snap_toggledelay", + Type = "Float", + Min = "0.0", + Max = "0.2" + }) + + panel:AddControl("Slider", { + Label = "Smart lock delay", + Command = "snap_lockdelay", + Type = "Float", + Min = "0.0", + Max = "5.0" + }) + + panel:AddControl("CheckBox", { + Label = "Bounding box enabled", + Command = "snap_boundingbox" + }) + + panel:AddControl("Slider", { + Label = "Grid draw distance", + Command = "snap_distance", + Type = "Integer", + Min = "50", + Max = "1000" + }) + + panel:AddControl("Slider", { + Label = "Grid edge offset", + Command = "snap_gridoffset", + Type = "Float", + Min = "0.0", + Max = "2.5" + }) + + panel:AddControl("Slider", { + Label = "Grid transparency", + Command = "snap_gridalpha", + Type = "Float", + Min = "0.1", + Max = "1.0" + }) + + panel:AddControl("Slider", { + Label = "Maximum number of snap points on an axis", + Command = "snap_gridlimit", + Type = "Integer", + Min = "2", + Max = "64" + }) + + panel:AddControl("Slider", { + Label = "Minimum distance between each snap point", + Command = "snap_gridsize", + Type = "Integer", + Min = "2", + Max = "64" + }) + + panel:AddControl("Label", { + Text = "" + }) + + panel:AddControl("Label", { + Text = "The following option should prevent FPS drops from occuring, however it might have a slight impact on the average FPS while the grid is showing. Do NOT uncheck this option unless you are experiencing very low FPS or fully understands its purpose." + }) + + panel:AddControl("Label", { + Text = "NOTE: This option is only effective when the grid is showing, it does not impact regular gameplay!" + }) + + panel:AddControl("Label", { + Text = "" + }) + + panel:AddControl("CheckBox", { + Label = "Garbage collection boost", + Command = "snap_gcboost" + }) + end + + function OnPopulateToolMenu() + spawnmenu.AddToolMenuOption("Options", "Player", "SmartSnapSettings", "SmartSnap", "", "", OnPopulateToolPanel, { + SwitchConVar = 'snap_enabled' + }) + end + + hook.Add("PopulateToolMenu", "SmartSnapToolMenu", OnPopulateToolMenu) +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/client/toolsearch.lua b/garrysmod/addons/gmod-tools/lua/autorun/client/toolsearch.lua new file mode 100644 index 0000000..27b8550 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/client/toolsearch.lua @@ -0,0 +1,193 @@ + +local cl_toolsearch_autoselect = CreateClientConVar("cl_toolsearch_autoselect", "1") +local cl_toolsearch_favoritesonly = CreateClientConVar("cl_toolsearch_favoritesonly", "0") +local cl_toolsearch_favoritestyle = CreateClientConVar("cl_toolsearch_favoritestyle", "1") + +local favorites = util.JSONToTable(file.Read("tools_favorites.txt", "DATA") or "{}") or {} +local init = false + +hook.Add("PostReloadToolsMenu", "ToolSearch", function() + if not IsValid(g_SpawnMenu) then return end + + local toolPanel = g_SpawnMenu.ToolMenu.ToolPanels[1] + local divider = toolPanel.HorizontalDivider + + local list = toolPanel.List + + if not IsValid(divider) then + error("Something is modifying the spawnmenu and is preventing the tool search addon from working!") + return + end + + local panel = vgui.Create("EditablePanel", divider) + list:SetParent(panel) + list:Dock(FILL) + + local textEntry = panel:Add("EditablePanel") + textEntry:Dock(TOP) + textEntry:DockMargin(0, 0, 0, 2) + textEntry:SetTall(20) + + local search = textEntry:Add("DTextEntry") + search:Dock(FILL) + search:DockMargin(0, 0, 2, 0) + search._Paint = search.Paint + function search:Paint(w, h) + local ret = self:_Paint(w, h) + + if self:GetValue() == "" then + surface.SetFont("DermaDefault") + local txt = L.search2_hint + local txtW, txtH = surface.GetTextSize(txt) + surface.SetTextPos(3, h * 0.5 - txtH * 0.5 + 1) + surface.SetTextColor(Color(0, 0, 0, 192)) + surface.DrawText(txt) + end + + return ret + end + search:SetUpdateOnType(true) + function search:OnValueChange(str, init) + local i = 0 + for _, cat in next, list.pnlCanvas:GetChildren() do + local hidden = 0 + for k, pnl in next, cat:GetChildren() do + if pnl.ClassName ~= "DCategoryHeader" then + if utf8.lower(language.GetPhrase(pnl:GetText())):match(utf8.lower(str)) and (not cl_toolsearch_favoritesonly:GetBool() or (cl_toolsearch_favoritesonly:GetBool() and favorites[pnl.Name])) then + pnl:SetVisible(true) + if cl_toolsearch_autoselect:GetBool() and not init then + i = i + 1 + if i == 1 then + pnl:SetSelected(true) + pnl:DoClick() + else + pnl:SetSelected(false) + end + end + else + pnl:SetVisible(false) + hidden = hidden + 1 + end + end + end + if hidden >= #cat:GetChildren() - 1 then + cat:SetVisible(false) + else + cat:SetVisible(true) + end + cat:InvalidateLayout() + list.pnlCanvas:InvalidateLayout() + end + end + + local clear = textEntry:Add("DButton") + clear:Dock(RIGHT) + clear:SetWide(20) + clear:SetText("") + clear:SetTooltip(L.clear_field ) + function clear:DoClick() + search:SetValue("") + end + local close = Material("icon16/cross.png") + function clear:Paint(w, h) + derma.SkinHook("Paint", "Button", self, w, h) + + surface.SetMaterial(close) + surface.SetDrawColor(Color(255, 255, 255)) + surface.DrawTexturedRect(w * 0.5 - 16 * 0.5, h * 0.5 - 16 * 0.5, 16, 16) + end + + local favsOnly = panel:Add("EditablePanel") + favsOnly:Dock(TOP) + favsOnly:DockMargin(0, 0, 0, 2) + favsOnly:SetTall(20) + + local check = favsOnly:Add("DCheckBoxLabel") + check:SetConVar("cl_toolsearch_favoritesonly") + local showFavsOnly = cl_toolsearch_favoritesonly:GetBool() + check:SetChecked(showFavsOnly) + check:SetText(L.only_features) + check:SetPos(0, 3) + check:SetBright(true) + + local small_star = Material("icon16/bullet_star.png") + local function showFavoritesOnly(showFavs) + for _, cat in next, list.pnlCanvas:GetChildren() do + for k, pnl in next, cat:GetChildren() do + if pnl.ClassName ~= "DCategoryHeader" then + if showFavs then + if favorites[pnl.Name] then + pnl:SetVisible(true) + pnl.Favorite = true + else + pnl:SetVisible(false) + pnl.Favorite = false + end + end + pnl.Favorite = favorites[pnl.Name] + if not pnl._Paint then + local bgCol = CFG.skinColors.b + pnl._Paint = pnl.Paint + function pnl:Paint(w, h) + local ret = self:_Paint(w, h) + + if self.Favorite then + surface.SetMaterial(small_star) + surface.SetDrawColor(Color(255, 255, 255)) + surface.DrawTexturedRect(w - 16, h * 0.5 - 8, 16, 16) + end + + return ret + end + end + function pnl:DoRightClick(w, h) + self.Favorite = not self.Favorite + favorites[self.Name] = self.Favorite + file.Write("tools_favorites.txt", util.TableToJSON(favorites)) + surface.PlaySound("garrysmod/content_downloaded.wav") + end + end + cat:InvalidateLayout() + list.pnlCanvas:InvalidateLayout() + end + end + search:OnValueChange(search:GetValue(), not init or not showFavs) + init = true + end + + local fix = true + function check:OnChange() + if fix then fix = nil return end + showFavoritesOnly(cl_toolsearch_favoritesonly:GetBool()) + end + + divider:SetLeft(panel) + + showFavoritesOnly(showFavsOnly) +end) + +hook.Add("PopulateToolMenu", "ToolSearch", function() + spawnmenu.AddToolMenuOption("Utilities", + "User", + "ToolSearch", + "Tool Search", "", "", + function(pnl) + pnl:AddControl("Header", { + Description = "Configure the Tool Search's behavior." + }) + + pnl:AddControl("CheckBox", { + Label = "Auto-Select", + Command = "cl_toolsearch_autoselect", + }) + pnl:ControlHelp("If enabled, this will select the top most tool automatically when you do a search query.") + + pnl:AddControl("Header", { + Description = "Right-click tools to make them your favorites!" + }) + end + ) +end) + +-- RunConsoleCommand("spawnmenu_reload") + diff --git a/garrysmod/addons/gmod-tools/lua/autorun/init_improvedstacker.lua b/garrysmod/addons/gmod-tools/lua/autorun/init_improvedstacker.lua new file mode 100644 index 0000000..21d53fa --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/init_improvedstacker.lua @@ -0,0 +1,19 @@ +if ( SERVER ) then + + -- needed for custom vgui controls in the menu + AddCSLuaFile( "vgui/stackercontrolpresets.lua" ) + AddCSLuaFile( "vgui/stackerdnumslider.lua" ) + AddCSLuaFile( "vgui/stackerpreseteditor.lua" ) + + -- convenience modules + AddCSLuaFile( "improvedstacker/improvedstacker.lua" ) + AddCSLuaFile( "improvedstacker/localify.lua" ) + +else + + -- needed for custom vgui controls in the menu + include( "vgui/stackercontrolpresets.lua" ) + include( "vgui/stackerdnumslider.lua" ) + include( "vgui/stackerpreseteditor.lua" ) + +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/particlecontrol_autorun.lua b/garrysmod/addons/gmod-tools/lua/autorun/particlecontrol_autorun.lua new file mode 100644 index 0000000..b3b6d7e --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/particlecontrol_autorun.lua @@ -0,0 +1,1197 @@ +AddCSLuaFile() + +ParticleListTable = { + Games = {}, + Addons = {}, +} + +local doubleslash = "//" + + +local function AddToParticleListTable(filename,isaddon,icon) + + //Converts a list string, with one item per line, into a table, taking comments and empty strings into account. Returns nil if the table is empty. + //The format of these tables is item1 = true, item2 = true, etc., not 1 = item1, 2 = item2, etc.; this is so we can use "if table["itemX"] then" to check if a value is inside of it. + // + //Instead of the arg being the string itself, the args are 1: the table containing the string, and 2: the key the string can be found at. + //This is so we can find continuations of the string, which'll be in the same table, but under separate keys. + // + local function ListStringToTable(listcontainer,listname) + //Find the list string we're looking for inside the container, and explode it into its own table. + local liststring = listcontainer[listname] + if type(liststring) != "string" then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname .. "\" inside of \"" .. filename .. "\" doesn't seem to actually be a string! Check the file and make sure you've formatted everything correctly!") return end + if #liststring == 4095 then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname .. "\" inside of \"" .. filename .. "\" is more than 4095 characters long - that's too long for the engine to read all of it! Split it up into multiple lists!") end + local listtab = string.Explode("\n", liststring) + + //Also find any list strings that are continuations of our main list string, and explode them into the same table. + for listname2, liststring2 in pairs (listcontainer) do + if string.StartWith(listname2, listname .. "_cont") then + if type(liststring) != "string" then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname2 .. "\" inside of \"" .. filename .. "\" doesn't seem to actually be a string! Check the file and make sure you've formatted everything correctly!") return end + if #liststring2 == 4095 then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname2 .. "\" inside of \"" .. filename .. "\" is more than 4095 characters long - that's too long for the engine to read all of it! Split it up into multiple lists!") end + table.Add(listtab, string.Explode("\n", liststring2) ) + end + end + + //Finally, filter out any comments or blank spaces from the table. + local listtabfiltered = {} + for _, str in pairs (listtab) do + //if there's a doubleslash in the string then remove it and everything after it + local commentpos, _ = string.find(str, doubleslash) + local resultstr = str + if commentpos then + resultstr = string.sub( str, 1, commentpos - 1 ) + end + + //trim any spaces from the string, and don't add it to the filtered table if it's just an empty string at this point + resultstr = string.Trim(resultstr) + if resultstr != "" then listtabfiltered[resultstr] = true end + end + if table.Count(listtabfiltered) > 0 then + return listtabfiltered + end + end + + local filestr = file.Read(filename,"LUA") + if !filestr then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\" doesn't exist or isn't a readable file! Something went wrong!") return end + local keyvalues = util.KeyValuesToTable( filestr, false, true ) + local resulttable = { Info = {}, Particles = {} } + if !keyvalues or (table.Count(keyvalues) == 0) then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\" isn't a valid keyvalue table and couldn't be read! Check the file and make sure you've formatted everything correctly!") return end + if !keyvalues.Info then + //On Mac/Linux systems, the keyvalues-to-table function messes up and returns the keyname "Info" as something else, like "inf", so it's possible that the info subtable is + //still here, just under a different keyname. The info is the only thing in keyvalues that should be a table value, so we'll check for that: + local foundinfo = false + for k, v in pairs (keyvalues) do + if type(v) == "table" then + keyvalues.Info = v + MsgN("(" .. filename .. ": Found Info table under keyname " .. tostring(k) .. ")") + foundinfo = true + end + end + //If there's no table value in keyvalues at all, then this is a probably a custom file where someone messed up the formatting. + if !foundinfo then + MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\" doesn't have an \"Info\" table that we can find! Check the file and make sure you've formatted everything correctly!") + return + end + end + if !keyvalues.Info.CategoryName then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\"'s Info table doesn't have a \"CategoryName\" string that we can find! Check the file and make sure you've formatted everything correctly!") return end + + resulttable.Info.CategoryName = keyvalues.Info.CategoryName + resulttable.Info.Icon = icon + resulttable.Info.EffectOptions = {} + if keyvalues.Info["EffectOptions"] then + if keyvalues.Info.EffectOptions["Beams"] then + resulttable.Info.EffectOptions["Beams"] = ListStringToTable(keyvalues.Info.EffectOptions, "Beams") + end + if keyvalues.Info.EffectOptions["Color1"] then + resulttable.Info.EffectOptions["Color1"] = ListStringToTable(keyvalues.Info.EffectOptions, "Color1") + end + if keyvalues.Info.EffectOptions["Color255"] then + resulttable.Info.EffectOptions["Color255"] = ListStringToTable(keyvalues.Info.EffectOptions, "Color255") + end + if keyvalues.Info.EffectOptions["Tracers"] then + resulttable.Info.EffectOptions["Tracers"] = ListStringToTable(keyvalues.Info.EffectOptions, "Tracers") + end + end + if keyvalues.Info["UtilEffects"] then + resulttable.Info["UtilEffects"] = keyvalues.Info["UtilEffects"] + end + keyvalues.Info = nil + + + for k, _ in pairs (keyvalues) do + if !string.StartWith(k,doubleslash) and string.EndsWith(k, ".pcf") then //ignore .pcf lists that start with a doubleslash; also ignore lists that don't end with .pcf since these are probably continuations + local particlelist = ListStringToTable(keyvalues, k) + if particlelist then + game.AddParticles("particles/" .. k) + resulttable.Particles[k] = particlelist + end + end + end + + + local subtablename = string.StripExtension( string.GetFileFromFilename(filename) ) + if isaddon then + MsgN( "PARTICLE CONTROL: Added list for addon: " .. resulttable.Info.CategoryName ) + ParticleListTable.Addons[subtablename] = resulttable + else + MsgN( "PARTICLE CONTROL: Added list for game: " .. resulttable.Info.CategoryName ) + ParticleListTable.Games[subtablename] = resulttable + end + +end + + + + +//Source Engine / Garry's Mod + AddToParticleListTable("particlelists/gmod.lua", false, "games/16/garrysmod.png") + +//Team Fortress 2 +if IsMounted("tf") then + AddToParticleListTable("particlelists/tf2.lua", false, "games/16/tf.png") end + +//Half-Life 2: Episode 1 +if IsMounted("episodic") then + AddToParticleListTable("particlelists/ep1.lua", false, "games/16/episodic.png") +end + +//Half-Life 2: Episode 2 +if IsMounted("ep2") then + AddToParticleListTable("particlelists/ep2.lua", false, "games/16/ep2.png") +end + +//Portal +if IsMounted("portal") then + AddToParticleListTable("particlelists/portal.lua", false, "games/16/portal.png") +end + +//Counter-Strike: Source +if IsMounted("cstrike") then + AddToParticleListTable("particlelists/cstrike.lua", false, "games/16/cstrike.png") +end + + +//Find all list files in the particlelists/addons/ directory, and add them to their own section of the table +local alladdons, _ = file.Find( "lua/particlelists/addons/*.lua", "GAME" ) +for _, filename in pairs (alladdons) do + if !string.StartWith( filename, "_" ) then //don't add files that start with an _, this is how we stop it from reading the example files + AddToParticleListTable("particlelists/addons/" .. filename, true, "icon16/bricks.png") + end +end + + + + +if CLIENT then + + //Favorites - this table is global because it's used by every Particle Browser panel on the client + ParticleBrowserFavorites = { + Effects = {}, //effects in the favorites list + Children = {}, //all DTreeNode panels that are using this favorites list + } + + local parctrlfavs_str = file.Read("particlecontrol_favorites.txt","DATA") + if parctrlfavs_str then ParticleBrowserFavorites.Effects = util.JSONToTable(parctrlfavs_str) end + + + function ParticleBrowserFavorites_UpdateEffect( effectdisplay, effectoptions ) + + if effectoptions then + //We're adding the effect + ParticleBrowserFavorites.Effects[effectdisplay] = effectoptions + else + //We're removing the effect + ParticleBrowserFavorites.Effects[effectdisplay] = nil + end + + for _, panel in pairs (ParticleBrowserFavorites.Children) do + if panel then + ParticleBrowserFavorites_UpdateChildPanel( panel ) + end + end + + file.Write( "particlecontrol_favorites.txt", util.TableToJSON(ParticleBrowserFavorites.Effects, true) ) + + end + + + function ParticleBrowserFavorites_UpdateChildPanel( panel ) + + if !panel then return end + + if panel.FavoriteNodes then + //Remove all of the nodes from the panel + for k, node in pairs (panel.FavoriteNodes) do + node:Remove() + panel.FavoriteNodes[k] = nil + end + else + panel.FavoriteNodes = {} + end + + for effectdisplay, effectoptions in SortedPairs (ParticleBrowserFavorites.Effects) do + local effect = effectoptions["InternalName"] + + panel.FavoriteNodes[effect] = panel:AddNode(effectdisplay) + + panel.FavoriteNodes[effect].EffectOptions = {} + if effectoptions["Beam"] then + panel.FavoriteNodes[effect].EffectOptions["Beam"] = true + end + if effectoptions["Colorable"] then + panel.FavoriteNodes[effect].EffectOptions["Colorable"] = true + end + if effectoptions["ColorOutOfOne"] then + panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] = true + end + + //Don't use the icons for features we're not using + local beamicon = ( (panel.CommandInfo.mode_beam) and (panel.FavoriteNodes[effect].EffectOptions["Beam"] == true) ) + local coloricon = ( (panel.CommandInfo.color) and (panel.FavoriteNodes[effect].EffectOptions["Colorable"] == true) ) + //Set the effect icon: + if beamicon and coloricon then + panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line_rainbow.png") + elseif beamicon then + panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line.png") + elseif coloricon then + panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_rainbow.png") + else + panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire.png") + end + + panel.FavoriteNodes[effect].DoClick = function() + RunConsoleCommand( panel.CommandInfo.effectname, effect ) + + if panel.CommandInfo.mode_beam then + if panel.FavoriteNodes[effect].EffectOptions["Beam"] then + RunConsoleCommand( panel.CommandInfo.mode_beam, "1" ) + else + RunConsoleCommand( panel.CommandInfo.mode_beam, "0" ) + end + end + + if panel.CommandInfo.color then + if panel.FavoriteNodes[effect].EffectOptions["Colorable"] then + RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "1" ) + else + RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "0" ) + end + + if panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] then + RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "1" ) + else + RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "0" ) + end + end + end + + panel.FavoriteNodes[effect].DoRightClick = function() + //We only need the remove option here, since it's already in the favorites list + local menu = DermaMenu() + + local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() + ParticleBrowserFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list + end ) + option:SetImage("icon16/delete.png") + + menu:Open() + end + end + + end + + + + + function AddParticleBrowser(panel,data) + + local self = {} + + //Data table contents: + //{ + //name = "Effect" (name to show at the top of the panel) + //commands = (table of concommands that the panel uses) + // { + // effectname = "particlecontrol_effectname" + // mode_beam = "particlecontrol_mode_beam" (optional) + // color = "particlecontrol_color" (optional, assumes there are cvars derived from this name (X_enabled, X_r, X_g, X_b, X_outofone) ) + // utileffect = "particlecontrol_utileffect" (optional, assumes there are cvars derived from this name (X_scale, X_magnitude, X_radius) ) + // + // enabled = "particlecontrol_enabled" (optional - toggles whether or not the panel is open using this cvar) + // } + //} + + self.back = vgui.Create("DForm", panel) + self.back:SetLabel(data.name) + self.back.Paint = function() + derma.SkinHook( "Paint", "CollapsibleCategory", self.back, self.back:GetWide(), self.back:GetTall() ) + surface.SetDrawColor( Color(0,0,0,70) ) + surface.DrawRect( 0, 0, self.back:GetWide(), self.back:GetTall() ) + end + self.back.Header:SetImage("icon16/fire.png") + self.back.Header.DoClick = function() end + panel:AddPanel(self.back) + + + + + self.tree = vgui.Create("DTree", self.back) + self.tree:SetHeight(400) + self.back:AddItem(self.tree) + self.tree:GetParent():DockPadding(5,5,5,0) + + local function PopulateEffectList(parent, effectstab, effectoptions) + for k, v in SortedPairs (effectstab) do + local effect = k + local effectdisplay = k + //if v is another string instead of just a filler "true" value, then that means k is the display name and v is the internal name + if isstring(v) then + effect = v + end + + parent[effect] = parent:AddNode(effectdisplay) + + parent[effect].EffectOptions = {} + if effectoptions["Beams"] and effectoptions["Beams"][effect] then + parent[effect].EffectOptions["Beam"] = true + end + if effectoptions["Color1"] and effectoptions["Color1"][effect] then + parent[effect].EffectOptions["Colorable"] = true + parent[effect].EffectOptions["ColorOutOfOne"] = true + end + if effectoptions["Color255"] and effectoptions["Color255"][effect] then + parent[effect].EffectOptions["Colorable"] = true + parent[effect].EffectOptions["ColorOutOfOne"] = false + end + + //Don't use the icons for features we're not using + local beamicon = ( (data.commands.mode_beam) and (parent[effect].EffectOptions["Beam"] == true) ) + local coloricon = ( (data.commands.color) and (parent[effect].EffectOptions["Colorable"] == true) ) + //Set the effect icon: + if beamicon and coloricon then + parent[effect].Icon:SetImage("icon16/fire_line_rainbow.png") + elseif beamicon then + parent[effect].Icon:SetImage("icon16/fire_line.png") + elseif coloricon then + parent[effect].Icon:SetImage("icon16/fire_rainbow.png") + else + parent[effect].Icon:SetImage("icon16/fire.png") + end + + //Left Click: Select the effect by setting its concommands + parent[effect].DoClick = function() + RunConsoleCommand( data.commands.effectname, effect ) + + if data.commands.mode_beam then + if parent[effect].EffectOptions["Beam"] then + RunConsoleCommand( data.commands.mode_beam, "1" ) + else + RunConsoleCommand( data.commands.mode_beam, "0" ) + end + end + + if data.commands.color then + if parent[effect].EffectOptions["Colorable"] then + RunConsoleCommand( data.commands.color .. "_enabled", "1" ) + else + RunConsoleCommand( data.commands.color .. "_enabled", "0" ) + end + + if parent[effect].EffectOptions["ColorOutOfOne"] then + RunConsoleCommand( data.commands.color .. "_outofone", "1" ) + else + RunConsoleCommand( data.commands.color .. "_outofone", "0" ) + end + end + end + + //Right Click: Display an option to add/remove the effect from favorites + parent[effect].DoRightClick = function() + local menu = DermaMenu() + + if !ParticleBrowserFavorites.Effects[effectdisplay] then + local option = menu:AddOption( "Favorite \'\'" .. effectdisplay .. "\'\'", function() + ParticleBrowserFavorites_UpdateEffect( effectdisplay, { + ["InternalName"] = effect, + ["Beam"] = tobool(parent[effect].EffectOptions["Beam"]), + ["Colorable"] = tobool(parent[effect].EffectOptions["Colorable"]), + ["ColorOutOfOne"] = tobool(parent[effect].EffectOptions["ColorOutOfOne"]), + } ) + end ) + option:SetImage("icon16/add.png") + else + local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() + ParticleBrowserFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list + end ) + option:SetImage("icon16/delete.png") + end + + menu:Open() + end + end + end + + //Games + self.tree.games = self.tree:AddNode("Games") + self.tree.games.Icon:SetImage("icon16/folder_database.png") + self.tree.games:SetExpanded(true) + // + for tabname, tab in SortedPairs (ParticleListTable.Games) do + self.tree.games[tabname] = self.tree.games:AddNode(tab.Info.CategoryName) + self.tree.games[tabname].Icon:SetImage(tab.Info.Icon) + + //Create the utilfx list + if tab.Info.UtilEffects then + self.tree.games[tabname].UtilEffects = self.tree.games[tabname]:AddNode("Scripted Effects") + self.tree.games[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") + + self.tree.games[tabname].UtilEffects.IsPopulated = false + self.tree.games[tabname].UtilEffects.DoClick = function() + if self.tree.games[tabname].UtilEffects.IsPopulated == true then return end + + PopulateEffectList(self.tree.games[tabname].UtilEffects, tab.Info.UtilEffects, tab.Info.EffectOptions) + + self.tree.games[tabname].UtilEffects.IsPopulated = true + self.tree.games[tabname].UtilEffects:SetExpanded(true) + end + end + + //Create the .pcf lists + for pcfname, particles in SortedPairs (tab.Particles) do + local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems + self.tree.games[tabname][pcftabname] = self.tree.games[tabname]:AddNode(pcfname) + self.tree.games[tabname][pcftabname].Icon:SetImage("icon16/page.png") + + self.tree.games[tabname][pcftabname].IsPopulated = false + self.tree.games[tabname][pcftabname].DoClick = function() + if self.tree.games[tabname][pcftabname].IsPopulated == true then return end + + PopulateEffectList(self.tree.games[tabname][pcftabname], particles, tab.Info.EffectOptions) + + self.tree.games[tabname][pcftabname].IsPopulated = true + self.tree.games[tabname][pcftabname]:SetExpanded(true) + end + end + end + + //Addons + self.tree.addons = self.tree:AddNode("Addons") + self.tree.addons.Icon:SetImage("icon16/folder_database.png") + self.tree.addons:SetExpanded(true) + // + for tabname, tab in SortedPairs (ParticleListTable.Addons) do + self.tree.addons[tabname] = self.tree.addons:AddNode(tab.Info.CategoryName) + self.tree.addons[tabname].Icon:SetImage(tab.Info.Icon) + + //Create the utilfx list + if tab.Info.UtilEffects then + self.tree.addons[tabname].UtilEffects = self.tree.addons[tabname]:AddNode("Scripted Effects") + self.tree.addons[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") + + self.tree.addons[tabname].UtilEffects.IsPopulated = false + self.tree.addons[tabname].UtilEffects.DoClick = function() + if self.tree.addons[tabname].UtilEffects.IsPopulated == true then return end + + PopulateEffectList(self.tree.addons[tabname].UtilEffects, tab.Info.UtilEffects, tab.Info.EffectOptions) + + self.tree.addons[tabname].UtilEffects.IsPopulated = true + self.tree.addons[tabname].UtilEffects:SetExpanded(true) + end + end + + //Create the .pcf lists + for pcfname, particles in SortedPairs (tab.Particles) do + local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems + self.tree.addons[tabname][pcftabname] = self.tree.addons[tabname]:AddNode(pcfname) + self.tree.addons[tabname][pcftabname].Icon:SetImage("icon16/page.png") + + self.tree.addons[tabname][pcftabname].IsPopulated = false + self.tree.addons[tabname][pcftabname].DoClick = function() + if self.tree.addons[tabname][pcftabname].IsPopulated == true then return end + + PopulateEffectList(self.tree.addons[tabname][pcftabname], particles, tab.Info.EffectOptions) + + self.tree.addons[tabname][pcftabname].IsPopulated = true + self.tree.addons[tabname][pcftabname]:SetExpanded(true) + end + end + end + + //Favorites + self.tree.favorites = self.tree:AddNode("Favorites") + self.tree.favorites.Icon:SetImage("icon16/star.png") + self.tree.favorites:SetExpanded(true) + // + table.insert( ParticleBrowserFavorites.Children, self.tree.favorites ) //Add it to the Favorites table's list of child panels and let it do the rest of the work + self.tree.favorites.CommandInfo = data.commands //Store the console command info in the panel so the UpdateChildPanel function can access it + ParticleBrowserFavorites_UpdateChildPanel( self.tree.favorites ) + + + + + self.effectnameentry = vgui.Create( "DTextEntry", self.back ) + self.effectnameentry:SetConVar( data.commands.effectname ) + self.back:AddItem(self.effectnameentry) + self.effectnameentry:GetParent():DockPadding(5,5,5,10) + + + + + if data.commands.mode_beam then + self.beam = vgui.Create("DForm", self.back) + self.beam.Paint = function() + derma.SkinHook( "Paint", "CollapsibleCategory", self.beam, self.beam:GetWide(), self.beam:GetTall() ) + surface.SetDrawColor( Color(0,0,0,110) ) + surface.DrawRect( 0, 0, self.beam:GetWide(), self.beam:GetTall() ) + end + self.beam:Dock(TOP) + self.back:AddItem(self.beam) + self.beam:GetParent():DockPadding(5,0,5,0) + + //Modify the header and tweak things so it doesn't show up when the category is closed. + self.beam.Header:SetText( "Beam" ) + self.beam.Header:SetImage("icon16/fire_line.png") + self.beam.Header.DoClick = function() end + self.beam.PerformLayout = function() + local us = self.beam //so when we copy this over to another dform we only have to change this one line + local Padding = us:GetPadding() or 0 + if ( us.Contents ) then + if ( us:GetExpanded() ) then + us.Contents:InvalidateLayout( true ) + us.Contents:SetVisible( true ) + else + us.Contents:SetVisible( false ) + end + end + if ( us:GetExpanded() ) then + us:SizeToChildren( false, true ) + else + us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() + end + -- Make sure the color of header text is set + us.Header:ApplySchemeSettings() + us.animSlide:Run() + us:UpdateAltLines(); + end + + self.beam.text = vgui.Create("DLabel", self.beam) + self.beam.text:SetColor( Color(60,60,60,255) ) + self.beam.text:SetText("This effect attaches to two points.") + self.beam.text:SetWrap( true ) + self.beam.text:SetAutoStretchVertical( true ) + self.beam.text:Dock(TOP) + self.beam.text:SizeToContents() + self.beam:AddItem(self.beam.text) + self.beam.text:GetParent():DockPadding( 15, 10, 15, 10 ) + end + + + + + if data.commands.color then + self.color = vgui.Create("DForm", self.back) + self.color.Paint = function() + derma.SkinHook( "Paint", "CollapsibleCategory", self.color, self.color:GetWide(), self.color:GetTall() ) + surface.SetDrawColor( Color(0,0,0,110) ) + surface.DrawRect( 0, 0, self.color:GetWide(), self.color:GetTall() ) + end + self.color:Dock(TOP) + self.back:AddItem(self.color) + self.color:GetParent():DockPadding(5,0,5,0) + + //Modify the header and tweak things so it doesn't show up when the category is closed. + self.color.Header:SetText( "Colorable" ) + self.color.Header:SetImage("icon16/fire_rainbow.png") + self.color.Header.DoClick = function() end + self.color.PerformLayout = function() + local us = self.color //so when we copy this over to another dform we only have to change this one line + local Padding = us:GetPadding() or 0 + if ( us.Contents ) then + if ( us:GetExpanded() ) then + us.Contents:InvalidateLayout( true ) + us.Contents:SetVisible( true ) + else + us.Contents:SetVisible( false ) + end + end + if ( us:GetExpanded() ) then + us:SizeToChildren( false, true ) + else + us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() + end + -- Make sure the color of header text is set + us.Header:ApplySchemeSettings() + us.animSlide:Run() + us:UpdateAltLines(); + end + + self.color.text = vgui.Create("DLabel", self.color) + self.color.text:SetColor( Color(60,60,60,255) ) + self.color.text:SetText("This effect is colorable.") + self.color.text:SetWrap( true ) + self.color.text:SetAutoStretchVertical( true ) + self.color.text:Dock(TOP) + self.color.text:SizeToContents() + self.color:AddItem(self.color.text) + self.color.text:GetParent():DockPadding( 15, 10, 15, 10 ) + + + self.color.selection = vgui.Create( "CtrlColor", self.color ) + self.color.selection:SetLabel( "" ) + self.color.selection:SetConVarR( data.commands.color .. "_r" ) + self.color.selection:SetConVarG( data.commands.color .. "_g" ) + self.color.selection:SetConVarB( data.commands.color .. "_b" ) + self.color.selection:SetConVarA( nil ) + self.color.selection:Dock(TOP) + self.color:AddItem(self.color.selection) + self.color.selection:GetParent():DockPadding( 10, 0, 10, 10 ) + end + + + + + if data.commands.utileffect then + self.utilfx = vgui.Create("DForm", self.back) + self.utilfx.Paint = function() + derma.SkinHook( "Paint", "CollapsibleCategory", self.utilfx, self.utilfx:GetWide(), self.utilfx:GetTall() ) + surface.SetDrawColor( Color(0,0,0,110) ) + surface.DrawRect( 0, 0, self.utilfx:GetWide(), self.utilfx:GetTall() ) + end + self.utilfx:Dock(TOP) + self.back:AddItem(self.utilfx) + self.utilfx:GetParent():DockPadding(5,0,5,0) + + //Modify the header and tweak things so it doesn't show up when the category is closed. + self.utilfx.Header:SetText( "Scripted Effect" ) + self.utilfx.Header:SetImage("icon16/cog.png") + self.utilfx.Header.DoClick = function() end + self.utilfx.PerformLayout = function() + local us = self.utilfx //so when we copy this over to another dform we only have to change this one line + local Padding = us:GetPadding() or 0 + if ( us.Contents ) then + if ( us:GetExpanded() ) then + us.Contents:InvalidateLayout( true ) + us.Contents:SetVisible( true ) + else + us.Contents:SetVisible( false ) + end + end + if ( us:GetExpanded() ) then + us:SizeToChildren( false, true ) + else + us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() + end + -- Make sure the color of header text is set + us.Header:ApplySchemeSettings() + us.animSlide:Run() + us:UpdateAltLines(); + end + + self.utilfx.text = vgui.Create("DLabel", self.utilfx) + self.utilfx.text:SetColor( Color(60,60,60,255) ) + self.utilfx.text:SetText("These options can be used to modify certain scripted effects in different ways depending on the effect being used.") + self.utilfx.text:SetWrap( true ) + self.utilfx.text:SetAutoStretchVertical( true ) + self.utilfx.text:Dock(TOP) + self.utilfx.text:SizeToContents() + self.utilfx:AddItem(self.utilfx.text) + self.utilfx.text:GetParent():DockPadding( 15, 10, 15, 5 ) + + + self.utilfx.slider1 = vgui.Create( "DNumSlider", self.utilfx ) + self.utilfx.slider1:SetText( "Util.Effect Scale" ) + self.utilfx.slider1:SetMinMax(0.2, 10) + self.utilfx.slider1:SetDecimals(2) + self.utilfx.slider1:SetConVar( data.commands.utileffect .. "_scale" ) + self.utilfx.slider1:SetHeight(15) + self.utilfx.slider1:SizeToContents() + self.utilfx.slider1:SetDark(true) + self.utilfx:AddItem(self.utilfx.slider1) + + self.utilfx.slider2 = vgui.Create( "DNumSlider", self.utilfx ) + self.utilfx.slider2:SetText( "Util.Effect Magnitude" ) + self.utilfx.slider2:SetMinMax(1, 10) + self.utilfx.slider2:SetDecimals(2) + self.utilfx.slider2:SetConVar( data.commands.utileffect .. "_magnitude" ) + self.utilfx.slider2:SetHeight(15) + self.utilfx.slider2:SizeToContents() + self.utilfx.slider2:SetDark(true) + self.utilfx:AddItem(self.utilfx.slider2) + + self.utilfx.slider3 = vgui.Create( "DNumSlider", self.utilfx ) + self.utilfx.slider3:SetText( "Util.Effect Radius" ) + self.utilfx.slider3:SetMinMax(10, 1000) + self.utilfx.slider3:SetDecimals(2) + self.utilfx.slider3:SetConVar( data.commands.utileffect .. "_radius" ) + self.utilfx.slider3:SetHeight(15) + self.utilfx.slider3:SizeToContents() + self.utilfx.slider3:SetDark(true) + self.utilfx:AddItem(self.utilfx.slider3) + end + + + + + self.back.Think = function() + if self.beam then + if GetConVarNumber( data.commands.mode_beam ) > 0 then + //expand it + if self.beam:GetExpanded() == false then self.beam:Toggle() end + else + //contract it + if self.beam:GetExpanded() == true then self.beam:Toggle() end + self.beam:GetParent():SetTall(self.beam:GetTall()) + end + end + if self.color then + if GetConVarNumber( data.commands.color .. "_enabled" ) > 0 then + //expand it + if self.color:GetExpanded() == false then self.color:Toggle() end + else + //contract it + if self.color:GetExpanded() == true then self.color:Toggle() end + self.color:GetParent():SetTall(self.color:GetTall()) + end + end + if self.utilfx then + if string.StartWith( GetConVarString(data.commands.effectname), "!UTILEFFECT!" ) then + //expand it + if self.utilfx:GetExpanded() == false then self.utilfx:Toggle() end + else + //contract it + if self.utilfx:GetExpanded() == true then self.utilfx:Toggle() end + self.utilfx:GetParent():SetTall(self.utilfx:GetTall()) + end + end + + + if data.commands.enabled then + if GetConVarNumber( data.commands.enabled ) > 0 then + //expand it + if self.back:GetExpanded() == false then self.back:Toggle() end + else + //contract it + if self.back:GetExpanded() == true then self.back:Toggle() end + end + end + end + + end + + + + + + + + + + + + + + //Favorites - this table is global too because this is all copied over from the standard Particle Browser, even though I don't suspect we'll ever have more than one of these + ParticleBrowserTracerFavorites = { + Effects = {}, //effects in the favorites list + Children = {}, //all DTreeNode panels that are using this favorites list + } + + local parctrltracerfavs_str = file.Read("particlecontrol_favorites_tracer.txt","DATA") + if parctrltracerfavs_str then ParticleBrowserTracerFavorites.Effects = util.JSONToTable(parctrltracerfavs_str) end + + + function ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay, effectoptions ) + + if effectoptions then + //We're adding the effect + ParticleBrowserTracerFavorites.Effects[effectdisplay] = effectoptions + else + //We're removing the effect + ParticleBrowserTracerFavorites.Effects[effectdisplay] = nil + end + + for _, panel in pairs (ParticleBrowserTracerFavorites.Children) do + if panel then + ParticleBrowserTracerFavorites_UpdateChildPanel( panel ) + end + end + + file.Write( "particlecontrol_favorites_tracer.txt", util.TableToJSON(ParticleBrowserTracerFavorites.Effects, true) ) + + end + + + function ParticleBrowserTracerFavorites_UpdateChildPanel( panel ) + + if !panel then return end + + if panel.FavoriteNodes then + //Remove all of the nodes from the panel + for k, node in pairs (panel.FavoriteNodes) do + node:Remove() + panel.FavoriteNodes[k] = nil + end + else + panel.FavoriteNodes = {} + end + + for effectdisplay, effectoptions in SortedPairs (ParticleBrowserTracerFavorites.Effects) do + local effect = effectoptions["InternalName"] + + panel.FavoriteNodes[effect] = panel:AddNode(effectdisplay) + + panel.FavoriteNodes[effect].EffectOptions = {} + if effectoptions["Colorable"] then + panel.FavoriteNodes[effect].EffectOptions["Colorable"] = true + end + if effectoptions["ColorOutOfOne"] then + panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] = true + end + + //Don't use the icons for features we're not using + local coloricon = ( (panel.CommandInfo.color) and (panel.FavoriteNodes[effect].EffectOptions["Colorable"] == true) ) + //Set the effect icon: + if coloricon then + panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line_rainbow.png") + else + panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line.png") + end + + panel.FavoriteNodes[effect].DoClick = function() + RunConsoleCommand( panel.CommandInfo.effectname, effect ) + + if panel.CommandInfo.color then + if panel.FavoriteNodes[effect].EffectOptions["Colorable"] then + RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "1" ) + else + RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "0" ) + end + + if panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] then + RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "1" ) + else + RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "0" ) + end + end + end + + panel.FavoriteNodes[effect].DoRightClick = function() + //We only need the remove option here, since it's already in the favorites list + local menu = DermaMenu() + + local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() + ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list + end ) + option:SetImage("icon16/delete.png") + + menu:Open() + end + end + + end + + + + + function AddParticleBrowserTracer(panel,data) + + local self = {} + + //Data table contents: + //{ + //name = "Effect" (name to show at the top of the panel) + //commands = (table of concommands that the panel uses) + // { + // effectname = "particlecontrol_effectname" + // color = "particlecontrol_color" (optional, assumes there are cvars derived from this name (X_enabled, X_r, X_g, X_b, X_outofone) ) + // } + //} + + self.back = vgui.Create("DForm", panel) + self.back:SetLabel(data.name) + self.back.Paint = function() + derma.SkinHook( "Paint", "CollapsibleCategory", self.back, self.back:GetWide(), self.back:GetTall() ) + surface.SetDrawColor( Color(0,0,0,70) ) + surface.DrawRect( 0, 0, self.back:GetWide(), self.back:GetTall() ) + end + self.back.Header:SetImage("icon16/fire.png") + self.back.Header.DoClick = function() end + panel:AddPanel(self.back) + + + + + self.tree = vgui.Create("DTree", self.back) + self.tree:SetHeight(400) + self.back:AddItem(self.tree) + self.tree:GetParent():DockPadding(5,5,5,0) + + local function PopulateEffectList(parent, effectstab, effectoptions) + for k, v in SortedPairs (effectstab) do + local effect = k + local effectdisplay = k + //if v is another string instead of just a filler "true" value, then that means k is the display name and v is the internal name + if isstring(v) then + effect = v + end + + parent[effect] = parent:AddNode(effectdisplay) + + parent[effect].EffectOptions = {} + if effectoptions["Color1"] and effectoptions["Color1"][effect] then + parent[effect].EffectOptions["Colorable"] = true + parent[effect].EffectOptions["ColorOutOfOne"] = true + end + if effectoptions["Color255"] and effectoptions["Color255"][effect] then + parent[effect].EffectOptions["Colorable"] = true + parent[effect].EffectOptions["ColorOutOfOne"] = false + end + + //Don't use the icons for features we're not using + local coloricon = ( (data.commands.color) and (parent[effect].EffectOptions["Colorable"] == true) ) + //Set the effect icon: + if coloricon then + parent[effect].Icon:SetImage("icon16/fire_line_rainbow.png") + else + parent[effect].Icon:SetImage("icon16/fire_line.png") + end + + //Left Click: Select the effect by setting its concommands + parent[effect].DoClick = function() + RunConsoleCommand( data.commands.effectname, effect ) + + if data.commands.color then + if parent[effect].EffectOptions["Colorable"] then + RunConsoleCommand( data.commands.color .. "_enabled", "1" ) + else + RunConsoleCommand( data.commands.color .. "_enabled", "0" ) + end + + if parent[effect].EffectOptions["ColorOutOfOne"] then + RunConsoleCommand( data.commands.color .. "_outofone", "1" ) + else + RunConsoleCommand( data.commands.color .. "_outofone", "0" ) + end + end + end + + //Right Click: Display an option to add/remove the effect from favorites + parent[effect].DoRightClick = function() + local menu = DermaMenu() + + if !ParticleBrowserTracerFavorites.Effects[effectdisplay] then + local option = menu:AddOption( "Favorite \'\'" .. effectdisplay .. "\'\'", function() + ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay, { + ["InternalName"] = effect, + ["Colorable"] = tobool(parent[effect].EffectOptions["Colorable"]), + ["ColorOutOfOne"] = tobool(parent[effect].EffectOptions["ColorOutOfOne"]), + } ) + end ) + option:SetImage("icon16/add.png") + else + local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() + ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list + end ) + option:SetImage("icon16/delete.png") + end + + menu:Open() + end + end + end + + //Games + self.tree.games = self.tree:AddNode("Games") + self.tree.games.Icon:SetImage("icon16/folder_database.png") + self.tree.games:SetExpanded(true) + // + for tabname, tab in SortedPairs (ParticleListTable.Games) do + if tab.Info.EffectOptions.Tracers then + self.tree.games[tabname] = self.tree.games:AddNode(tab.Info.CategoryName) + self.tree.games[tabname].Icon:SetImage(tab.Info.Icon) + + //Create the utilfx list + if tab.Info.UtilEffects then + local tracerlist = {} + for k, v in pairs (tab.Info.UtilEffects) do + //in a utileffect table, the effect name is stored in the value + if tab.Info.EffectOptions.Tracers[v] then tracerlist[k] = v end + end + if table.Count(tracerlist) > 0 then + self.tree.games[tabname].UtilEffects = self.tree.games[tabname]:AddNode("Scripted Effects") + self.tree.games[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") + + self.tree.games[tabname].UtilEffects.IsPopulated = false + self.tree.games[tabname].UtilEffects.DoClick = function() + if self.tree.games[tabname].UtilEffects.IsPopulated == true then return end + + PopulateEffectList(self.tree.games[tabname].UtilEffects, tracerlist, tab.Info.EffectOptions) + + self.tree.games[tabname].UtilEffects.IsPopulated = true + self.tree.games[tabname].UtilEffects:SetExpanded(true) + end + end + end + + //Create the .pcf lists + for pcfname, particles in SortedPairs (tab.Particles) do + local tracerlist = {} + for k, v in pairs (particles) do + //in a particle table, the effect name is stored in the key + if tab.Info.EffectOptions.Tracers[k] then tracerlist[k] = v end + end + if table.Count(tracerlist) > 0 then + local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems + self.tree.games[tabname][pcftabname] = self.tree.games[tabname]:AddNode(pcfname) + self.tree.games[tabname][pcftabname].Icon:SetImage("icon16/page.png") + + self.tree.games[tabname][pcftabname].IsPopulated = false + self.tree.games[tabname][pcftabname].DoClick = function() + if self.tree.games[tabname][pcftabname].IsPopulated == true then return end + + PopulateEffectList(self.tree.games[tabname][pcftabname], tracerlist, tab.Info.EffectOptions) + + self.tree.games[tabname][pcftabname].IsPopulated = true + self.tree.games[tabname][pcftabname]:SetExpanded(true) + end + end + end + end + end + + //Addons + self.tree.addons = self.tree:AddNode("Addons") + self.tree.addons.Icon:SetImage("icon16/folder_database.png") + self.tree.addons:SetExpanded(true) + // + for tabname, tab in SortedPairs (ParticleListTable.Addons) do + if tab.Info.EffectOptions.Tracers then + self.tree.addons[tabname] = self.tree.addons:AddNode(tab.Info.CategoryName) + self.tree.addons[tabname].Icon:SetImage(tab.Info.Icon) + + //Create the utilfx list + if tab.Info.UtilEffects then + local tracerlist = {} + for k, v in pairs (tab.Info.UtilEffects) do + //in a utileffect table, the effect name is stored in the value + if tab.Info.EffectOptions.Tracers[v] then tracerlist[k] = v end + end + if table.Count(tracerlist) > 0 then + self.tree.addons[tabname].UtilEffects = self.tree.addons[tabname]:AddNode("Scripted Effects") + self.tree.addons[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") + + self.tree.addons[tabname].UtilEffects.IsPopulated = false + self.tree.addons[tabname].UtilEffects.DoClick = function() + if self.tree.addons[tabname].UtilEffects.IsPopulated == true then return end + + PopulateEffectList(self.tree.addons[tabname].UtilEffects, tracerlist, tab.Info.EffectOptions) + + self.tree.addons[tabname].UtilEffects.IsPopulated = true + self.tree.addons[tabname].UtilEffects:SetExpanded(true) + end + end + end + + //Create the .pcf lists + for pcfname, particles in SortedPairs (tab.Particles) do + local tracerlist = {} + for k, v in pairs (particles) do + //in a particle table, the effect name is stored in the key + if tab.Info.EffectOptions.Tracers[k] then tracerlist[k] = v end + end + if table.Count(tracerlist) > 0 then + local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems + self.tree.addons[tabname][pcftabname] = self.tree.addons[tabname]:AddNode(pcfname) + self.tree.addons[tabname][pcftabname].Icon:SetImage("icon16/page.png") + + self.tree.addons[tabname][pcftabname].IsPopulated = false + self.tree.addons[tabname][pcftabname].DoClick = function() + if self.tree.addons[tabname][pcftabname].IsPopulated == true then return end + + PopulateEffectList(self.tree.addons[tabname][pcftabname], tracerlist, tab.Info.EffectOptions) + + self.tree.addons[tabname][pcftabname].IsPopulated = true + self.tree.addons[tabname][pcftabname]:SetExpanded(true) + end + end + end + end + end + + //Favorites + self.tree.favorites = self.tree:AddNode("Favorites (Tracers)") + self.tree.favorites.Icon:SetImage("icon16/star.png") + self.tree.favorites:SetExpanded(true) + // + table.insert( ParticleBrowserTracerFavorites.Children, self.tree.favorites ) //Add it to the Favorites table's list of child panels and let it do the rest of the work + self.tree.favorites.CommandInfo = data.commands //Store the console command info in the panel so the UpdateChildPanel function can access it + ParticleBrowserTracerFavorites_UpdateChildPanel( self.tree.favorites ) + + + + + self.effectnameentry = vgui.Create( "DTextEntry", self.back ) + self.effectnameentry:SetConVar( data.commands.effectname ) + self.back:AddItem(self.effectnameentry) + self.effectnameentry:GetParent():DockPadding(5,5,5,10) + + + + + if data.commands.color then + self.color = vgui.Create("DForm", self.back) + self.color.Paint = function() + derma.SkinHook( "Paint", "CollapsibleCategory", self.color, self.color:GetWide(), self.color:GetTall() ) + surface.SetDrawColor( Color(0,0,0,110) ) + surface.DrawRect( 0, 0, self.color:GetWide(), self.color:GetTall() ) + end + self.color:Dock(TOP) + self.back:AddItem(self.color) + self.color:GetParent():DockPadding(5,0,5,0) + + //Modify the header and tweak things so it doesn't show up when the category is closed. + self.color.Header:SetText( "Colorable" ) + self.color.Header:SetImage("icon16/fire_rainbow.png") + self.color.Header.DoClick = function() end + self.color.PerformLayout = function() + local us = self.color //so when we copy this over to another dform we only have to change this one line + local Padding = us:GetPadding() or 0 + if ( us.Contents ) then + if ( us:GetExpanded() ) then + us.Contents:InvalidateLayout( true ) + us.Contents:SetVisible( true ) + else + us.Contents:SetVisible( false ) + end + end + if ( us:GetExpanded() ) then + us:SizeToChildren( false, true ) + else + us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() + end + -- Make sure the color of header text is set + us.Header:ApplySchemeSettings() + us.animSlide:Run() + us:UpdateAltLines(); + end + + self.color.text = vgui.Create("DLabel", self.color) + self.color.text:SetColor( Color(60,60,60,255) ) + self.color.text:SetText("This effect is colorable.") + self.color.text:SetWrap( true ) + self.color.text:SetAutoStretchVertical( true ) + self.color.text:Dock(TOP) + self.color.text:SizeToContents() + self.color:AddItem(self.color.text) + self.color.text:GetParent():DockPadding( 15, 10, 15, 10 ) + + + self.color.selection = vgui.Create( "CtrlColor", self.color ) + self.color.selection:SetLabel( "" ) + self.color.selection:SetConVarR( data.commands.color .. "_r" ) + self.color.selection:SetConVarG( data.commands.color .. "_g" ) + self.color.selection:SetConVarB( data.commands.color .. "_b" ) + self.color.selection:SetConVarA( nil ) + self.color.selection:Dock(TOP) + self.color:AddItem(self.color.selection) + self.color.selection:GetParent():DockPadding( 10, 0, 10, 10 ) + end + + + + + self.back.Think = function() + if self.color then + if GetConVarNumber( data.commands.color .. "_enabled" ) > 0 then + //expand it + if self.color:GetExpanded() == false then self.color:Toggle() end + else + //contract it + if self.color:GetExpanded() == true then self.color:Toggle() end + self.color:GetParent():SetTall(self.color:GetTall()) + end + end + end + + end +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/particlefiletest.lua b/garrysmod/addons/gmod-tools/lua/autorun/particlefiletest.lua new file mode 100644 index 0000000..1478362 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/particlefiletest.lua @@ -0,0 +1,128 @@ +AddCSLuaFile() +//MsgN("Added concommand for particle read test!") + + + +function ParticleControl_FileReadTest() + + MsgN("") + MsgN("") + MsgN("") + MsgN("") + MsgN("") + MsgN("=====================================================") + MsgN("STEP 1: Reading the file") + MsgN("=====================================================") + MsgN("") + + local filestr = file.Read("particlelists/particlefiletest.lua","LUA") + if !filestr then + MsgN("FAILURE! We tried to read the file (particlelists/particlefiletest.lua) and didn't get anything. The file can't be found, or it isn't readable, or something, which doesn't make any sense, since the actual tool got farther than this.") + return + end + + if type(filestr) != "string" then + MsgN("FAILURE! We read the file but we got back a " .. type(filestr) .. "instead of a string. What!? Here's what we got:") + MsgN("") + MsgN(filestr) + return + end + + MsgN("SUCCESS! We read the file and got back a string. Here's its contents:") + MsgN("") + MsgN(filestr) + + + MsgN("") + MsgN("") + MsgN("") + MsgN("") + MsgN("") + MsgN("=====================================================") + MsgN("STEP 2: Converting it to a table") + MsgN("=====================================================") + MsgN("") + + local keyvalues = util.KeyValuesToTable( filestr, false, true ) + if !keyvalues then + MsgN("FAILURE! We tried to convert it to a table, but we didn't get anything.") + return + end + if (table.Count(keyvalues) == 0) then + MsgN("FAILURE! We tried to convert it, but we got an empty table. Here's its contents, by which I mean a blank space unless something's weird here:") + MsgN("") + PrintTable(keyvalues) + return + end + + MsgN("SUCCESS! We converted it and got a table of stuff. Here's its contents:") + MsgN("") + PrintTable(keyvalues) + + + MsgN("") + MsgN("") + MsgN("") + MsgN("") + MsgN("") + MsgN("=====================================================") + MsgN("STEP 3: Reading the table") + MsgN("=====================================================") + MsgN("") + + if !keyvalues.Info then + MsgN("FAILURE! We couldn't find a key called \"Info\" inside the table. Is something wrong with the table we got? Like some blank spaces in the key name, or weird capitalization, or something like that which means it's technically not called \"Info\"? Or is the table's formatting messed up? Here's all of the keys we COULD find inside the table, with and without quotes just in case there's weird spaces:") + MsgN("") + for k, v in pairs (keyvalues) do + MsgN("\"" .. k .. "\"") + end + MsgN("") + for k, v in pairs (keyvalues) do + MsgN(k) + end + return + end + + if type(keyvalues.Info) != "table" then + MsgN("FAILURE! The key called \"Info\" inside the table was something other than a sub-table. It was a " .. type(keyvalues.Info) .. " instead. Something's wrong with the table we got. Here's what \"Info\" is:") + MsgN("") + MsgN(keyvalues.Info) + return + end + + MsgN("Found the sub-table called \"Info\" inside the table...") + MsgN("") + + if !keyvalues.Info.CategoryName then + MsgN("FAILURE! We couldn't find the \"CategoryName\" inside the \"Info\" subtable. Something's wrong with the table we got. Here's \"Info\"'s contents:") + MsgN("") + PrintTable(keyvalues.Info) + return + end + + if type(keyvalues.Info.CategoryName) != "string" then + MsgN("FAILURE! The \"CategoryName\" inside the \"Info\" subtable was something other than a string. Something's wrong with the table we got. Here's \"Info\"'s contents:") + MsgN("") + PrintTable(keyvalues.Info) + return + end + + MsgN("SUCCESS! We read the table. It's for a category called " .. keyvalues.Info.CategoryName .. ", and everything else should be there too. The table contents are back up there in Step 2 and they should look normal. Congratulations, everything's working fine! If we were doing this same stuff in the actual tool, it'd use the table to make a list of particle effects. But if the tool was working perfectly for you, then you wouldn't be running this debug thing, now would you? Looks like we're back to square one here.") + +end + + + +if SERVER then + concommand.Add("particlereadtest_server", function() + MsgN("Beginning SERVERSIDE particle file read test!") + ParticleControl_FileReadTest() + end) +end + +if CLIENT then + concommand.Add("particlereadtest_client", function() + MsgN("Beginning CLIENTSIDE particle file read test!") + ParticleControl_FileReadTest() + end) +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/proxycolor_autorun.lua b/garrysmod/addons/gmod-tools/lua/autorun/proxycolor_autorun.lua new file mode 100644 index 0000000..d8d78d3 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/proxycolor_autorun.lua @@ -0,0 +1,52 @@ +local entCache = {} +local entMeta = FindMetaTable('Entity') + +if SERVER then + AddCSLuaFile('matproxy/matproxycolors.lua') +else + include('matproxy/matproxycolors.lua') +end + +function entMeta:GetProxyColors() + return self:GetNetVar('proxyCols') +end + +function entMeta:SetProxyColors(cols) + if not istable(cols) then return end + + for i = 1,7 do + if IsColor(cols[i]) then cols[i] = cols[i]:ToVector() end + end + + if SERVER then + self:SetNetVar('proxyCols', cols) + duplicator.StoreEntityModifier(self, 'proxyColors', cols) + else + for i = 1, 7 do + self['ColorSlot' .. i] = cols[i] + end + end +end + +if SERVER then + duplicator.RegisterEntityModifier('proxyColors', function(ply, ent, cols) + ent:SetProxyColors(cols) + end) +else + hook.Add('NotifyShouldTransmit', 'proxyColors', function(ent, should) + if not should then return end + local cols = ent:GetNetVar('proxyCols') + if cols then ent:SetProxyColors(cols) end + end) + + hook.Add('NetworkEntityCreated', 'proxyColors', function(ent) + local cols = ent:GetNetVar('proxyCols') + if cols then ent:SetProxyColors(cols) end + end) + + hook.Add('octolib.netVarUpdate', 'proxyColors', function(index, key, value) + if key ~= 'proxyCols' then return end + local ent = Entity(index) + if IsValid(ent) then ent:SetProxyColors(value) end + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/autorun/rb655_make_animatable.lua b/garrysmod/addons/gmod-tools/lua/autorun/rb655_make_animatable.lua new file mode 100644 index 0000000..93600df --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/rb655_make_animatable.lua @@ -0,0 +1,126 @@ + +AddCSLuaFile() + +properties.Add( "rb655_make_animatable", { + MenuLabel = "#tool.rb655_easy_animation.property", + Order = 654, + MenuIcon = "icon16/tick.png", + Filter = function( self, ent, ply ) + if ( !IsValid( ent ) or !gamemode.Call( "CanProperty", ply, "rb655_make_animatable", ent ) ) then return false end + if ( ent:GetClass() == "prop_animatable" ) then return false end + --if ( string.find( ent:GetClass(), "prop_physics" ) or string.find( ent:GetClass(), "prop_ragdoll" ) ) then return true end + return true + end, + Action = function( self, ent ) + self:MsgStart() + net.WriteEntity( ent ) + self:MsgEnd() + end, + Receive = function( self, len, ply ) + local ent = net.ReadEntity() + + if ( !IsValid( ply ) or !IsValid( ent ) or !self:Filter( ent, ply ) ) then return false end + if not ply:query('Make Animatable') then return end + + local prop_animatable = ents.Create( "prop_animatable" ) + prop_animatable:SetModel( ent:GetModel() ) + prop_animatable:SetPos( ent:GetPos() ) + prop_animatable:SetAngles( ent:GetAngles() ) + + prop_animatable:SetSkin( ent:GetSkin() ) + prop_animatable:SetFlexScale( ent:GetFlexScale() ) + for i = 0, ent:GetFlexNum() - 1 do prop_animatable:SetFlexWeight( i, ent:GetFlexWeight( i ) ) end + for i = 0, ent:GetNumBodyGroups() - 1 do prop_animatable:SetBodygroup( i, ent:GetBodygroup( i ) ) end + --for i = 0, ent:GetNumPoseParameters() - 1 do prop_animatable:SetPoseParameter( i, ent:GetPoseParameter( i ) ) end + for i = 0, ent:GetBoneCount() do + prop_animatable:ManipulateBoneScale( i, ent:GetManipulateBoneScale( i ) ) + prop_animatable:ManipulateBoneAngles( i, ent:GetManipulateBoneAngles( i ) ) + prop_animatable:ManipulateBonePosition( i, ent:GetManipulateBonePosition( i ) ) + prop_animatable:ManipulateBoneJiggle( i, ent:GetManipulateBoneJiggle( i ) ) -- Even though we don't know what this does, I am still putting this here. + end + --prop_animatable:InvalidateBoneCache() + + prop_animatable:Spawn() + prop_animatable:Activate() + prop_animatable:CPPISetOwner(ent:CPPIGetOwner()) + + prop_animatable.EntityMods = ent.EntityMods + prop_animatable.BoneMods = ent.BoneMods + duplicator.ApplyEntityModifiers( nil, prop_animatable ) + duplicator.ApplyBoneModifiers( nil, prop_animatable ) + + if ( string.find( ent:GetClass(), "prop_ragdoll" ) or ent:IsNPC() ) then -- We use string find because there are might be subclasses, like prop_ragdoll_multiplayer or something + prop_animatable:FixRagdoll() -- This WILL have false-positives, but it will have to do for now + end + + undo.ReplaceEntity( ent, prop_animatable ) + cleanup.ReplaceEntity( ent, prop_animatable ) + + constraint.RemoveAll( ent ) -- Remove all constraints ( this stops ropes from hanging around ) + ent:Remove() + end +} ) + +properties.Add( "rb655_make_ragdoll", { + MenuLabel = "#tool.rb655_easy_animation.property_ragdoll", + Order = 653, + MenuIcon = "icon16/tick.png", + Filter = function( self, ent, ply ) + if ( !IsValid( ent ) or !gamemode.Call( "CanProperty", ply, "rb655_make_ragdoll", ent ) ) then return false end + if ( ent:GetClass() != "prop_animatable" ) then return false end + if ( !ent.GetIsRagdoll or !ent:GetIsRagdoll() ) then return false end + return true + end, + Action = function( self, ent ) + self:MsgStart() + net.WriteEntity( ent ) + self:MsgEnd() + end, + Receive = function( self, len, ply ) + local ent = net.ReadEntity() + + if ( !IsValid( ply ) or !IsValid( ent ) or !self:Filter( ent, ply ) ) then return false end + if not ply:IsSuperAdmin() then return end + + local ragdoll = ents.Create( "prop_ragdoll" ) + ragdoll:SetModel( ent:GetModel() ) + ragdoll:SetPos( ent:GetPos() ) + ragdoll:SetAngles( ent:GetAngles() ) + + ragdoll:SetSkin( ent:GetSkin() ) + ragdoll:SetFlexScale( ent:GetFlexScale() ) + for i = 0, ent:GetNumBodyGroups() - 1 do ragdoll:SetBodygroup( i, ent:GetBodygroup( i ) ) end + for i = 0, ent:GetFlexNum() - 1 do ragdoll:SetFlexWeight( i, ent:GetFlexWeight( i ) ) end + for i = 0, ent:GetBoneCount() do + ragdoll:ManipulateBoneScale( i, ent:GetManipulateBoneScale( i ) ) + ragdoll:ManipulateBoneAngles( i, ent:GetManipulateBoneAngles( i ) ) + ragdoll:ManipulateBonePosition( i, ent:GetManipulateBonePosition( i ) ) + ragdoll:ManipulateBoneJiggle( i, ent:GetManipulateBoneJiggle( i ) ) -- Even though we don't know what this does, I am still putting this here. + end + + ragdoll:Spawn() + ragdoll:Activate() + + ragdoll.EntityMods = ent.EntityMods + ragdoll.BoneMods = ent.BoneMods + duplicator.ApplyEntityModifiers( nil, ragdoll ) + duplicator.ApplyBoneModifiers( nil, ragdoll ) + + for i = 0, ragdoll:GetPhysicsObjectCount() - 1 do + local bone = ragdoll:GetPhysicsObjectNum( i ) + if ( IsValid( bone ) ) then + local pos, ang = ent:GetBonePosition( ragdoll:TranslatePhysBoneToBone( i ) ) + if ( pos ) then bone:SetPos( pos ) end + if ( ang ) then bone:SetAngles( ang ) end + + bone:EnableMotion( false ) + end + end + + undo.ReplaceEntity( ent, ragdoll ) + cleanup.ReplaceEntity( ent, ragdoll ) + + constraint.RemoveAll( ent ) -- Remove all constraints ( this stops ropes from hanging around ) + ent:Remove() + end +} ) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/advdupe2_sv_init.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/advdupe2_sv_init.lua new file mode 100644 index 0000000..86c6105 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/advdupe2_sv_init.lua @@ -0,0 +1,139 @@ +AdvDupe2 = { + Version = "1.1.0", + Revision = 51 +} + +AdvDupe2.DataFolder = "advdupe2" --name of the folder in data where dupes will be saved + +include "advdupe2/sv_clipboard.lua" +include "advdupe2/sh_codec.lua" +include "advdupe2/sh_netstream.lua" +include "advdupe2/sv_misc.lua" +include "advdupe2/sv_file.lua" +include "advdupe2/sv_ghost.lua" + +AddCSLuaFile "autorun/client/advdupe2_cl_init.lua" +AddCSLuaFile "advdupe2/file_browser.lua" +AddCSLuaFile "advdupe2/sh_codec.lua" +AddCSLuaFile "advdupe2/sh_netstream.lua" +AddCSLuaFile "advdupe2/cl_file.lua" +AddCSLuaFile "advdupe2/cl_ghost.lua" + +function AdvDupe2.Notify(ply,msg,typ, showsvr, dur) + umsg.Start("AdvDupe2Notify",ply) + umsg.String(msg) + umsg.Char(typ or NOTIFY_GENERIC) + umsg.Char(dur or 5) + umsg.End() + if(showsvr==true)then + print("[AdvDupe2Notify]\t"..ply:Nick()..": "..msg) + end +end + +AdvDupe2.SpawnRate = AdvDupe2.SpawnRate or 1 +CreateConVar("AdvDupe2_SpawnRate", "1", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MaxFileSize", "200", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MaxEntities", "0", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MaxConstraints", "0", {FCVAR_ARCHIVE}) + +CreateConVar("AdvDupe2_MaxContraptionEntities", "10", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MaxContraptionConstraints", "15", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MinContraptionSpawnDelay", "0.2", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MinContraptionUndoDelay", "0.1", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MaxContraptionUndoDelay", "60", {FCVAR_ARCHIVE}) + +CreateConVar("AdvDupe2_AreaAutoSaveTime", "10", {FCVAR_ARCHIVE}) + +CreateConVar("AdvDupe2_FileModificationDelay", "5", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_UpdateFilesDelay", "10", {FCVAR_ARCHIVE}) + +CreateConVar("AdvDupe2_LoadMap", "0", {FCVAR_ARCHIVE}) +CreateConVar("AdvDupe2_MapFileName", "", {FCVAR_ARCHIVE}) + +cvars.AddChangeCallback("AdvDupe2_SpawnRate", +function(cvar, preval, newval) + newval = tonumber(newval) + if(newval~=nil and newval>0)then + AdvDupe2.SpawnRate = newval + else + print("[AdvDupe2Notify]\tINVALID SPAWN RATE") + end +end) + +local function PasteMap() + if(GetConVarString("AdvDupe2_LoadMap")=="0")then return end + local filename = GetConVarString("AdvDupe2_MapFileName") + + if(not filename or filename == "")then + print("[AdvDupe2Notify]\tInvalid file name to loap map save.") + return + end + + if(not file.Exists("advdupe2_maps/"..filename..".txt", "DATA"))then + print("[AdvDupe2Notify]\tFile does not exist for a map save.") + return + end + + local map = file.Read("advdupe2_maps/"..filename..".txt") + local success,dupe,info,moreinfo = AdvDupe2.Decode(map) + if not success then + print("[AdvDupe2Notify]\tCould not open map save "..dupe) + return + end + + local Tab = {Entities=dupe["Entities"], Constraints=dupe["Constraints"], HeadEnt=dupe["HeadEnt"]} + local Entities = AdvDupe2.duplicator.Paste(nil, table.Copy(Tab.Entities), Tab.Constraints, nil, nil, Tab.HeadEnt.Pos, true) + local maptype = GetConVarString("AdvDupe2_LoadMap") + + if(maptype=="1")then + local PhysObj + for k,v in pairs(Entities) do + if(IsValid(v))then + for i=0, #Tab.Entities[k].PhysicsObjects do + if(Tab.Entities[k].PhysicsObjects[i].Frozen)then + PhysObj = v:GetPhysicsObjectNum( i ) + if IsValid(PhysObj) then + PhysObj:EnableMotion(true) + end + end + end + if v.CPPISetOwner then v:CPPISetOwner(game.GetWorld()) end + end + end + elseif(maptype=="2")then + local PhysObj + for k,v in pairs(Entities) do + if(IsValid(v))then + for i=0, #Tab.Entities[k].PhysicsObjects do + PhysObj = v:GetPhysicsObjectNum( i ) + if IsValid(PhysObj) then + PhysObj:EnableMotion(true) + end + end + if v.CPPISetOwner then v:CPPISetOwner(game.GetWorld()) end + end + end + end + + print("[AdvDupe2Notify]\tMap save pasted.") +end + +util.AddNetworkString("AdvDupe2_SetDupeInfo") +util.AddNetworkString("AdvDupe2_ReceiveFile") +util.AddNetworkString("AdvDupe2_CanAutoSave") + +hook.Add("InitPostEntity", "AdvDupe2_PasteMap", PasteMap) +hook.Add("PostCleanupMap", "AdvDupe2_PasteMap", PasteMap) + +hook.Add("Initialize", "AdvDupe2_CheckServerSettings",function() + AdvDupe2.SpawnRate = tonumber(GetConVarString("AdvDupe2_SpawnRate")) + if not AdvDupe2.SpawnRate or AdvDupe2.SpawnRate <= 0 then + AdvDupe2.SpawnRate = 1 + print("[AdvDupe2Notify]\tINVALID SPAWN RATE DEFAULTING VALUE") + end +end) + +hook.Add("PlayerInitialSpawn","AdvDupe2_AddPlayerTable",function(ply) + ply.AdvDupe2 = {} +end) + diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-event.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-event.lua new file mode 100644 index 0000000..f77e188 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-event.lua @@ -0,0 +1,193 @@ +local mdls = { + ['female_01'] = 'models/dizcordum/citizens/playermodels/p_female_01.mdl', + ['female_02'] = 'models/dizcordum/citizens/playermodels/p_female_02.mdl', + ['female_03'] = 'models/dizcordum/citizens/playermodels/p_female_03.mdl', + ['female_04'] = 'models/dizcordum/citizens/playermodels/p_female_04.mdl', + ['female_06'] = 'models/dizcordum/citizens/playermodels/p_female_06.mdl', + ['female_07'] = 'models/dizcordum/citizens/playermodels/p_female_05.mdl', + + ['male_01_01'] = 'models/dizcordum/citizens/playermodels/pm_male_01.mdl', + ['male_01_02'] = 'models/dizcordum/citizens/playermodels/pm_male_01.mdl', + ['male_01_03'] = 'models/dizcordum/citizens/playermodels/pm_male_01.mdl', + + ['male_02_01'] = 'models/dizcordum/citizens/playermodels/pm_male_02.mdl', + ['male_02_02'] = 'models/dizcordum/citizens/playermodels/pm_male_02.mdl', + ['male_02_03'] = 'models/dizcordum/citizens/playermodels/pm_male_02.mdl', + + ['male_03_01'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + ['male_03_02'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + ['male_03_03'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + ['male_03_04'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + ['male_03_05'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + ['male_03_06'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + ['male_03_07'] = 'models/dizcordum/citizens/playermodels/pm_male_03.mdl', + + ['male_04_01'] = 'models/dizcordum/citizens/playermodels/pm_male_04.mdl', + ['male_04_02'] = 'models/dizcordum/citizens/playermodels/pm_male_04.mdl', + ['male_04_03'] = 'models/dizcordum/citizens/playermodels/pm_male_04.mdl', + ['male_04_04'] = 'models/dizcordum/citizens/playermodels/pm_male_04.mdl', + + ['male_05_01'] = 'models/dizcordum/citizens/playermodels/pm_male_05.mdl', + ['male_05_02'] = 'models/dizcordum/citizens/playermodels/pm_male_05.mdl', + ['male_05_03'] = 'models/dizcordum/citizens/playermodels/pm_male_05.mdl', + ['male_05_04'] = 'models/dizcordum/citizens/playermodels/pm_male_05.mdl', + ['male_05_05'] = 'models/dizcordum/citizens/playermodels/pm_male_05.mdl', + + ['male_06_01'] = 'models/dizcordum/citizens/playermodels/pm_male_06.mdl', + ['male_06_02'] = 'models/dizcordum/citizens/playermodels/pm_male_06.mdl', + ['male_06_03'] = 'models/dizcordum/citizens/playermodels/pm_male_06.mdl', + ['male_06_04'] = 'models/dizcordum/citizens/playermodels/pm_male_06.mdl', + ['male_06_05'] = 'models/dizcordum/citizens/playermodels/pm_male_06.mdl', + + ['male_07_01'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_07_02'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_07_03'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_07_04'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_07_05'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_07_06'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + + ['male_08_01'] = 'models/dizcordum/citizens/playermodels/pm_male_08.mdl', + ['male_08_02'] = 'models/dizcordum/citizens/playermodels/pm_male_08.mdl', + ['male_08_03'] = 'models/dizcordum/citizens/playermodels/pm_male_08.mdl', + ['male_08_04'] = 'models/dizcordum/citizens/playermodels/pm_male_08.mdl', + + ['male_09_01'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_09_02'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', + ['male_09_03'] = 'models/dizcordum/citizens/playermodels/pm_male_08.mdl', + ['male_09_04'] = 'models/dizcordum/citizens/playermodels/pm_male_07.mdl', +} + +local maleBgs = { + { + id = 1, + name = 'Верх', + vals = { + {'Куртка болотного цвета', 0}, + {'Серая куртка', 1}, + {'Серая куртка с капюшоном', 2}, + {'Бирюзовая куртка, расстегнута', 4}, + {'Бирюзовая куртка, застегнута', 7}, + {'Синяя куртка, расстегнута', 3}, + {'Синяя куртка, застегнута', 6}, + {'Красная куртка, расстегнута', 5}, + {'Красная куртка, застегнута', 8}, + }, + }, { + id = 2, + name = 'Низ', + vals = { + {'Синие джинсы', 0}, + {'Темно-синие джинсы', 1}, + {'Серые джинсы', 2}, + {'Синие свободные джинсы', 3}, + {'Темные свободные джинсы', 4}, + {'Серые болоневые штаны', 5}, + {'Синие болоневые штаны', 6}, + {'Серые рабочие брюки', 7}, + {'Камуфляжные рабочие брюки', 8}, + }, + }, { + id = 3, + name = 'Перчатки', + }, { + id = 4, + name = 'Шапка', + vals = {3}, + } +} + +local femaleBgs = { + { + id = 1, + name = 'Верх', + vals = { + {'Куртка болотного цвета', 0}, + {'Бирюзовая куртка, расстегнута', 1}, + {'Бирюзовая куртка, застегнута', 5}, + {'Синяя куртка, расстегнута', 2}, + {'Синяя куртка, застегнута', 4}, + {'Красная куртка, расстегнута', 3}, + {'Красная куртка, застегнута', 6}, + }, + }, + { + id = 2, + name = 'Низ', + vals = { + {'Синие джинсы + Ботинки', 0}, + {'Брюки + Сапоги', 1}, + {'Штаны с полоской + Ботинки', 2}, + {'Синие испачканные штаны + Ботинки', 3}, + {'Серые испачканные штаны + Ботинки', 4}, + {'Серые болоневые штаны', 5}, + {'Светлые болоневые штаны', 6}, + {'Серые рабочие брюки', 7}, + }, + }, { + id = 3, + name = 'Перчатки' + } +} + +local function getMdlData(ply) + local oldMdl, newMdl = ply:GetModel() + for pattern, mdl in pairs(mdls) do + if oldMdl:find(pattern) then newMdl = mdl break end + end + if not newMdl then return end + return { + name = 'Теплая одежда', + model = newMdl, + unisex = true, + bgs = octolib.models.isMale(newMdl) and maleBgs or femaleBgs, + } +end + +function EventMakeRefugee(ply, callback, check) + + callback = isfunction(callback) and callback or octolib.func.zero + if not (IsValid(ply) and ply:IsPlayer()) then return callback(false) end + local mdlData = getMdlData(ply) + if not mdlData then return callback(false) end + + if ply.pendingRefugee then + ply.pendingRefugee[1](false) + ply.pendingRefugee = nil + end + + ply.pendingRefugee = { callback, isfunction(check) and check or octolib.func.yes } + netstream.Start(ply, 'dbg-event.askForRefugee', mdlData) + +end + +netstream.Hook('dbg-event.askForRefugee', function(ply, userSkin, userBgs) + + if not ply.pendingRefugee then return end + local callback, check = unpack(ply.pendingRefugee) + ply.pendingRefugee = nil + if not userSkin then return callback(false) end + + local mdlData = getMdlData(ply) + if not mdlData then return callback(false) end + local skin, bgs = octolib.models.getValidSelection(getMdlData(ply), userSkin, userBgs) + if check(ply, skin, bgs) == false then return callback(false) end + + ply:SetModel(mdlData.model) + ply:SetSkin(skin) + for _, v in ipairs(ply:GetBodyGroups()) do + ply:SetBodygroup(v.id, bgs[v.id] or 0) + end + callback(true) + +end) + +concommand.Add('dbg_makerefugee', function(ply, _, args) + + if not ply:IsAdmin() then return end + + local tgt = player.GetBySteamID(table.concat(args, ''):gsub(' ', '')) + if IsValid(tgt) then + EventMakeRefugee(tgt) + end + +end) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-tool-lookable.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-tool-lookable.lua new file mode 100644 index 0000000..b00c058 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-tool-lookable.lua @@ -0,0 +1,25 @@ +hook.Add('KeyRelease', 'dbg-tools.lookable', function(ply, key) + + if key ~= IN_USE then return end + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) then return end + + local data = ent.lookableData + if not data then return end + + netstream.Start(ply, 'tools.lookable', data) + + if data.sound then + local soundData = table.Copy(data.sound) + soundData.ent = ent + soundData.pos = soundData.pos or Vector() + octolib.audio.play(soundData) + end + +end) + +duplicator.RegisterEntityModifier('lookable', function(ply, ent, data) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'lookable') + if override == false then return end + ent.lookableData = data +end) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-tool-progress.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-tool-progress.lua new file mode 100644 index 0000000..8fc83f4 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/dbg-tool-progress.lua @@ -0,0 +1,157 @@ +local actions = { + agree = {ACT_GMOD_GESTURE_AGREE,}, + eat = {ACT_GMOD_GESTURE_BECON,}, + bow = {ACT_GMOD_GESTURE_BOW,}, + disagree = {ACT_GMOD_GESTURE_DISAGREE,}, + wave = {ACT_GMOD_GESTURE_WAVE,}, + drop = {ACT_GMOD_GESTURE_ITEM_DROP, ACT_GMOD_GESTURE_ITEM_PLACE,}, + throw = {ACT_GMOD_GESTURE_ITEM_THROW,}, + shove = {ACT_GMOD_GESTURE_MELEE_SHOVE_1HAND, ACT_GMOD_GESTURE_MELEE_SHOVE_2HAND,}, + give = {ACT_GMOD_GESTURE_ITEM_GIVE,}, +} + +local function numpadToggle(ent, bind) + if not ent.bPressed then + numpad.Activate( ent.ownerSID, bind, true ) + ent.bPressed = true + else + numpad.Deactivate( ent.ownerSID, bind, true ) + ent.bPressed = false + end +end + +local function doPlus(ply, ent, data) + local owner = ent:CPPIGetOwner() + if not IsValid(owner) then return false end + netstream.Start(owner, 'gmpanel.executeObject', data.action, { ply:SteamID() }) +end + +local function doRegular(ply, ent, data) + + local time = (data.duration or 0) + local title = string.Trim(data.title or '') + local text = string.Trim(data.text or '') + if time > 0 and title ~= '' or text ~= '' then + if data.method == 'chat' then + if title == '' then + title = text + text = '' + end + octochat.talkTo(ply, octochat.textColors.rp, title, ' ', color_white, text) + elseif data.method == 'notify' then + ply:Notify(text) + elseif data.method == 'center' then + if title ~= '' or text ~= '' then + ply:Notify('admin', time, title, text) + end + end + end + + local url = data.urlsound and string.Trim(data.urlsound) ~= '' + if url or (data.gamesound and string.Trim(data.gamesound) ~= '') then + netstream.Start(ply, 'trigger_sound', 'progress' .. ent:EntIndex(), url, string.Trim(url and data.urlsound or data.gamesound), data.volume or 1, {ent:GetPos(), ent:GetAngles()}) + end + +end + +hook.Add('KeyRelease', 'dbg-tools.progress', function(ply, key) + + if key ~= IN_USE then return end + local ent = octolib.use.getTrace(ply).Entity + if not IsValid(ent) then return end + + local data = ent.delayedActionData + if not data then return end + + data.done = data.done or {} + if data.done[ply] then return end + + local keys = {} + local requirements = data.requirements + if requirements and not table.IsEmpty(requirements) then + for _, requirement in ipairs(requirements) do + local class = requirement.class + local password = requirement.password + local amount = requirement.amount + local item = ply:FindItem(password and password ~= '' and { class = 'key', password = password } or { class = class }) + + if item and item:GetData("amount") >= amount then + keys[item] = { + amount = item:GetData("amount"), + container = item:GetParent() + } + else + local msg = password and password ~= '' and data.passwordMsg or data.passwordMsgKey + if msg and string.Trim(msg) ~= '' then ply:Notify('warning', msg) end + return + end + end + end + + ply:DelayedAction('progress', data.name or 'Действие', { + time = data.time or 5, + check = function() + if keys then + for item, data in pairs(keys) do + if not item then return end + if item:GetParent() and item:GetParent() ~= data.container or item:GetData("amount") ~= data.amount then return end + end + end + + return octolib.use.check(ply, ent) and ent.delayedActionData + end, + succ = function() + + if data.action then + if doPlus(ply, ent, data) == false then return end + else + if doRegular(ply, ent, data) == false then return end + end + + if data.mode == 1 then + data.done[ply] = true + elseif data.mode == 2 then + ent.delayedActionData = nil + end + + if data.bind then + numpadToggle(ent, data.bind) + end + + for _, requirement in pairs(requirements or {}) do + if requirement.take then + local class = requirement.class + local password = requirement.password + local item = ply:FindItem(password and password ~= '' and { class = 'key', password = password } or { class = class }) + + ply:TakeItem(item.class, requirement.amount) + end + end + end, + }, { + time = data.animTime or 0.5, + inst = true, + action = function() + if data.animSound then + ply:EmitSound(data.animSound, 50) + end + if data.animAction and actions[data.animAction] then + ply:DoAnimation(actions[data.animAction][math.random(#actions[data.animAction])]) + end + end, + }) + +end) + +duplicator.RegisterEntityModifier('del_action', function(ply, ent, data) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'del_action') + if override == false then return end + if data.action then + if not IsValid(ply) then return octolib.msg('%s contains progress+ data, which can be pasted by an online player only', ent) end + if not ply:query('DBG: Панель ивентов') then + return ply:Notify('warning', 'У тебя нет доступа к панели игровых мастеров, поэтому данные инструмента "Прогресс (расширенный)" не были вставлены') + end + end + ent.ownerSID = IsValid(ply) and ply:SteamID() or '' + ent.delayedActionData = data +end) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/morematerials.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/morematerials.lua new file mode 100644 index 0000000..4795862 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/morematerials.lua @@ -0,0 +1,280 @@ +// adding materials to the material toolguns list +AddCSLuaFile('autorun/client/morematerials.lua') + +list.Add( "OverrideMaterials", "models/XQM//Deg360" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesGB" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesRed" ) +list.Add( "OverrideMaterials", "models/XQM//SquaredMat" ) +list.Add( "OverrideMaterials", "models/XQM//WoodTexture_1" ) +list.Add( "OverrideMaterials", "models/airboat/airboat_blur02" ) +list.Add( "OverrideMaterials", "models/alyx/emptool_glow" ) +list.Add( "OverrideMaterials", "models/antlion/antlion_innards" ) +list.Add( "OverrideMaterials", "models/barnacle/roots" ) +list.Add( "OverrideMaterials", "models/combine_advisor/body9" ) +list.Add( "OverrideMaterials", "models/combine_advisor/mask" ) +list.Add( "OverrideMaterials", "models/combine_scanner/scanner_eye" ) +list.Add( "OverrideMaterials", "models/debug/debugwhite" ) +list.Add( "OverrideMaterials", "models/dog/eyeglass" ) +list.Add( "OverrideMaterials", "models/effects/portalrift_sheet" ) +list.Add( "OverrideMaterials", "models/effects/slimebubble_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode1_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode_sheet" ) +list.Add( "OverrideMaterials", "models/gibs/metalgibs/metal_gibs" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs01" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs02" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs03" ) +list.Add( "OverrideMaterials", "models/player/player_chrome1" ) +list.Add( "OverrideMaterials", "models/props_animated_breakable/smokestack/brickwall002a" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_buildings/destroyedbuilldingwall01a" ) +list.Add( "OverrideMaterials", "models/props_buildings/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_c17/frostedglass_01a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric001a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric002a" ) +list.Add( "OverrideMaterials", "models/props_c17/furnituremetal001a" ) +list.Add( "OverrideMaterials", "models/props_c17/gate_door02a" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder001" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder002" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder003" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01a" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01b" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01c" ) +list.Add( "OverrideMaterials", "models/props_canal/canalmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/coastmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/metalcrate001d" ) +list.Add( "OverrideMaterials", "models/props_canal/metalwall005b" ) +list.Add( "OverrideMaterials", "models/props_canal/rock_riverbed01a" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable_b" ) +list.Add( "OverrideMaterials", "models/props_combine/com_shield001a" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_interface_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_monitorbay_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/metal_combinebridge001" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes01" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes03" ) +list.Add( "OverrideMaterials", "models/props_combine/prtl_sky_sheet" ) +list.Add( "OverrideMaterials", "models/props_combine/stasisfield_beam" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template010a" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template022j" ) +list.Add( "OverrideMaterials", "models/props_debris/composite_debris" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor013a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor020a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretewall019a" ) +list.Add( "OverrideMaterials", "models/props_debris/metalwall001a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterceiling008a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall009d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall039c" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall040c" ) +list.Add( "OverrideMaterials", "models/props_debris/tilefloor001c" ) +list.Add( "OverrideMaterials", "models/props_foliage/driftwood_01a" ) +list.Add( "OverrideMaterials", "models/props_foliage/oak_tree01" ) +list.Add( "OverrideMaterials", "models/props_foliage/tree_deciduous_01a_trunk" ) +list.Add( "OverrideMaterials", "models/props_interiors/metalfence007a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01b" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01c" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01d" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01e" ) +list.Add( "OverrideMaterials", "models/props_lab/Tank_Glass001" ) +list.Add( "OverrideMaterials", "models/props_lab/cornerunit_cloud" ) +list.Add( "OverrideMaterials", "models/props_lab/door_klab01" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens2" ) +list.Add( "OverrideMaterials", "models/props_lab/warp_sheet" ) +list.Add( "OverrideMaterials", "models/props_lab/xencrystal_sheet" ) +list.Add( "OverrideMaterials", "models/props_pipes/GutterMetal01a") +list.Add( "OverrideMaterials", "models/props_pipes/destroyedpipes01a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipemetal001a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipeset_metal02" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin1" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin2" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001b" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretefloor010a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall064b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall066a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/dirtwall001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/metal_tram001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/quarryobjects01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff04a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockgranite02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat02" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a_skin2" ) +list.Add( "OverrideMaterials", "models/shadertest/predator" ) +list.Add( "OverrideMaterials", "models/weapons/v_crossbow/rebar_glow" ) +list.Add( "OverrideMaterials", "models/weapons/v_crowbar/crowbar_cyl" ) +list.Add( "OverrideMaterials", "models/weapons/v_grenade/grenade body" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light1" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light2" ) +list.Add( "OverrideMaterials", "models/weapons/v_smg1/texture5" ) +list.Add( "OverrideMaterials", "models/XQM/BoxFull_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CellShadedCamo_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CinderBlock_Tex" ) +list.Add( "OverrideMaterials", "models/XQM/JetBody2TailPiece_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/PoleX1_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/Rails/gumball_1" ) +list.Add( "OverrideMaterials", "models/XQM/SquaredMatInverted" ) +list.Add( "OverrideMaterials", "models/XQM/WoodPlankTexture" ) +list.Add( "OverrideMaterials", "models/XQM/boxfull_diffuse" ) +list.Add( "OverrideMaterials", "models/dav0r/hoverball" ) +list.Add( "OverrideMaterials", "models/spawn_effect" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_white" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_wood" ) +list.Add( "OverrideMaterials", "phoenix_storms/Future_vents" ) +list.Add( "OverrideMaterials", "phoenix_storms/FuturisticTrackRamp_1-2" ) +list.Add( "OverrideMaterials", "phoenix_storms/OfficeWindow_1-1" ) +list.Add( "OverrideMaterials", "phoenix_storms/Pro_gear_side" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/blue_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/camera" ) +list.Add( "OverrideMaterials", "phoenix_storms/car_tire" ) +list.Add( "OverrideMaterials", "phoenix_storms/checkers_map" ) +list.Add( "OverrideMaterials", "phoenix_storms/cigar" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete0" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete1" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete2" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete3" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier2_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_pipe_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/egg" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear_top" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/heli" ) +list.Add( "OverrideMaterials", "phoenix_storms/indentTiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/iron_rails" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_wheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalfence004a" ) +list.Add( "OverrideMaterials", "phoenix_storms/middle" ) +list.Add( "OverrideMaterials", "phoenix_storms/mrref2" ) +list.Add( "OverrideMaterials", "phoenix_storms/output_jack" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/interior_sides" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/train_floor" ) +list.Add( "OverrideMaterials", "phoenix_storms/potato" ) +list.Add( "OverrideMaterials", "phoenix_storms/pro_gear_top2" ) +list.Add( "OverrideMaterials", "phoenix_storms/ps_grass" ) +list.Add( "OverrideMaterials", "phoenix_storms/road" ) +list.Add( "OverrideMaterials", "phoenix_storms/roadside" ) +list.Add( "OverrideMaterials", "phoenix_storms/scrnspace" ) +list.Add( "OverrideMaterials", "phoenix_storms/side" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic1" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/smallwheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/spheremappy" ) +list.Add( "OverrideMaterials", "phoenix_storms/t_light" ) +list.Add( "OverrideMaterials", "phoenix_storms/thruster" ) +list.Add( "OverrideMaterials", "phoenix_storms/tiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/top" ) +list.Add( "OverrideMaterials", "phoenix_storms/torpedo" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamside" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamtop" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plateside" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_fps" ) +list.Add( "OverrideMaterials", "phoenix_storms/window" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_blue" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_green" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_red" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_dome" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_side" ) + +// Checking if CSS is mounted and adding CSS textures if it is + +function engine.IsMounted(g) + for k,v in pairs(engine.GetGames()) do + if (' cstrike' ) then + return true; + end + end +end + +if IsMounted( 'cstrike' ) and (engine.IsMounted('cstrike')) then + +list.Add( "OverrideMaterials", "models/cs_havana/wndb" ) +list.Add( "OverrideMaterials", "models/cs_havana/wndd" ) +list.Add( "OverrideMaterials", "models/cs_italy/light_orange" ) +list.Add( "OverrideMaterials", "models/cs_italy/plaster" ) +list.Add( "OverrideMaterials", "models/cs_italy/pwtrim2" ) +list.Add( "OverrideMaterials", "models/de_cbble/wndarch" ) +list.Add( "OverrideMaterials", "models/de_chateau/ch_arch_b1" ) +list.Add( "OverrideMaterials", "models/pi_window/plaster" ) +list.Add( "OverrideMaterials", "models/pi_window/trim128" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/dollar" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/fireescapefloor" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/metal_stairs1" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap02" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneytop" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/pylon" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/boulder01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milceil001" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarock" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarockb" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milwall006" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/rocks01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams02" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams03" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/RoofEdges" ) +list.Add( "OverrideMaterials", "models/props/cs_office/clouds" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet2" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet3" ) +list.Add( "OverrideMaterials", "models/props/cs_office/screen" ) +list.Add( "OverrideMaterials", "models/props/cs_office/snowmana" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/de_inferno_boulder_03" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflra" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflrd" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/inftowertop" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/offwndwb_break" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/roofbits" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/tileroof01" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/woodfloor008a" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukconcretewalla" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukecardboard" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/pipeset_metal" ) + +end + + + +// Making sure there's no double materials in the list in case of other addons, plus sorting them + +timer.Simple(0, function() + local mats = list.GetForEdit("OverrideMaterials"); + local cleaner = {}; + for i, mat in pairs(mats) do + cleaner[mat] = true; + mats[i] = nil; + end + local i = 1; + for mat in pairs(cleaner) do + mats[i] = mat; + i = i + 1; + end + table.sort(mats); +end); diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/octo_perma.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/octo_perma.lua new file mode 100644 index 0000000..d16bbf7 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/octo_perma.lua @@ -0,0 +1,158 @@ +permaprops = permaprops or {} + +local toClearCons = octolib.array.toKeys({'BuildDupeInfo','Constraint','FPPOwner','FPPOwnerID','Identity','OnDieFunctions'}) +local noClearEnts = octolib.array.toKeys({'PhysicsObjects', 'EntityMods', 'Class', 'Pos', 'Angle', 'Skin', 'Material', 'Model'}) + +function permaprops.clearData(data) + + -- clean up unnecessary data + for id, entData in pairs(data.Entities) do + local ent = Entity(id) + if IsValid(ent) then + local baseClass = scripted_ents.GetStored(entData.Class) + for k, v in pairs(entData) do + if baseClass and v == baseClass[k] then + entData[k] = nil + end + end + + local noClearClass = {} + local dupeInfo = duplicator.FindEntityClass(entData.Class) + if dupeInfo and dupeInfo.Args then + for i = 2, #dupeInfo.Args do + noClearClass[dupeInfo.Args[i]] = true + end + end + + for k, v in pairs(ent:GetTable()) do + if not noClearEnts[k] and not noClearClass[k] then + entData[k] = nil + end + end + end + end + + for id, conData in pairs(data.Constraints) do + for k, v in pairs(conData) do + if toClearCons[k] then conData[k] = nil end + end + + for i = 1, 2 do + local data = conData.Entity[i] + data.Entity = nil + conData['Ent' .. i] = nil + end + end + + return data + +end + +function permaprops.markPerma(entities) + + for id, ent in pairs(entities) do + ent.perma = true + end + + timer.Simple(0.15, function() + for id, ent in pairs(entities) do + if ent.APG_Ghosted then APG.entUnGhost(ent) end + end + end) + +end + +function permaprops.spawn(data) + + local entities, consts = duplicator.Paste(nil, data.Entities or {}, data.Constraints or {}) + permaprops.markPerma(entities) + for _, ent in pairs(entities) do + if ent:GetClass():find('prop_dynamic', 1, false) then + ent:PhysicsInitStatic(SOLID_VPHYSICS) + ent:Activate() + ent:SetNotSolid(false) -- fix this damn duplicator bug + end + + timer.Simple(1, function() + for _, ent in pairs(entities) do + if IsValid(ent) and ent:GetClass() == 'prop_effect' then + local ph = ent:GetPhysicsObject() + if IsValid(ph) then ph:EnableMotion(false) end + end + end + end) + end + hook.Run('octoperma.spawned', entities, consts) + +end + +function permaprops.save() + + local saveData = { Entities = {}, Constraints = {} } + for k, ent in pairs(ents.GetAll()) do + if ent.perma then + duplicator.Copy(ent, saveData) + end + end + + permaprops.clearData(saveData) + + local fname = 'octo_perma/' .. game.GetMap() .. '.dat' + file.CreateDir('octo_perma') + file.Write(fname, util.Compress(pon.encode(saveData))) + +end + +function permaprops.clear() + + for k, ent in pairs(ents.GetAll()) do + if ent.perma then + ent:Remove() + end + end + +end + +function permaprops.load() + + local fname = 'octo_perma/' .. game.GetMap() .. '.dat' + local raw = file.Read(fname, 'DATA') + if not raw then return end + + local data = pon.decode(util.Decompress(raw)) + permaprops.spawn(data) + +end + +concommand.Add('octo_perma_save', function(ply) + + if not ply:query(L.permissions_permaprops) then return end + + ply:ChatPrint(L.octo_perma_save) + permaprops.save() + ply:ChatPrint(L.octo_perma_save_ready) + +end) + +concommand.Add('octo_perma_clear', function(ply) + + if not ply:query(L.permissions_permaprops) then return end + + permaprops.clear() + ply:ChatPrint(L.octo_perma_clear) + +end) + +concommand.Add('octo_perma_load', function(ply) + + if not ply:query(L.permissions_permaprops) then return end + + ply:ChatPrint(L.octo_perma_load) + permaprops.clear() + permaprops.load() + ply:ChatPrint(L.octo_perma_load_ready) + +end) + +hook.Add('InitPostEntity', 'octo_perma', permaprops.load) +hook.Add('PostCleanupMap', 'octo_perma', permaprops.load) diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/propshistory.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/propshistory.lua new file mode 100644 index 0000000..ae13cf5 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/propshistory.lua @@ -0,0 +1,16 @@ +util.AddNetworkString("PropsHistory") + +local function AddPropHistory(ply, model, ent) + net.Start("PropsHistory") + net.WriteString(model) + net.WriteUInt(ent:GetSkin(), 8) + + for i = 0, 8 do + net.WriteUInt(ent:GetBodygroup(i) or 0, 4) + end + + net.Send(ply) +end + +hook.Add("PlayerSpawnedProp", "PropsHistory", AddPropHistory) +hook.Add("PlayerSpawnedRagdoll", "PropsHistory", AddPropHistory) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/server/resource.lua b/garrysmod/addons/gmod-tools/lua/autorun/server/resource.lua new file mode 100644 index 0000000..ca66b08 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/server/resource.lua @@ -0,0 +1 @@ +-- snip diff --git a/garrysmod/addons/gmod-tools/lua/autorun/sh_catmullromcams.lua b/garrysmod/addons/gmod-tools/lua/autorun/sh_catmullromcams.lua new file mode 100644 index 0000000..309743a --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/sh_catmullromcams.lua @@ -0,0 +1,207 @@ +--[[ + Planetfall: Catmull-Rom Cutscene Camera Track System + by Olivier 'LuaPineapple' Hamel + + I'll be nice and release a stand alone version for you guys. + BUT I EXECPT A GODS AWFUL AMOUNT OF COOKIES! + + If I don't I'll get annoyed and when I get annoyed I become irritable and when I become irritable people DIE! [/quote_dr_evil] +--]] + +--[[ + 5:52 PM - Firgof Umbra: I worry about your sense of perception sometimes. + 5:52 PM - LuaPineapple: I AM GOD! + 5:52 PM - LuaPineapple: WORSHIP ME! + 5:52 PM - Firgof Umbra: ... + 5:53 PM - LuaPineapple: INSECT! +--]] + + + +local function AddLua(filename) + local tmp = string.Explode("/", string.lower(filename)) + local parts = string.Explode("_", tmp[#tmp]) + + if SERVER then + if (parts[1] == "sh") or (parts[1] == "shared.lua") then + include(filename) + return AddCSLuaFile(filename) + elseif parts[1] == "cl" then + return AddCSLuaFile(filename) + elseif (parts[1] == "sv") or (parts[1] == "init.lua") then + return include(filename) + end + + ErrorNoHalt("Unknown file: ",filename,"\n") + PrintTable(tmp) + PrintTable(parts) + Error("Unable to determine if shared, serverside, or clientside.\n") + elseif CLIENT then + if (parts[1] == "sh") or (parts[1] == "cl") or (parts[1] == "shared.lua") then + return include(filename) + elseif (parts[1] == "sv") or (parts[1] == "init.lua") then //others, just to keep the system happy + return + end + + ErrorNoHalt("Unknown file: ",filename,"\n") + PrintTable(tmp) + PrintTable(parts) + Error("Unable to determine if shared, serverside, or clientside.\n") + else + return Error("Apparently we're God as we're not the client or the server.\n") + end +end + +if SERVER then AddCSLuaFile("sh_CatmullRomCams.lua") end + +CatmullRomCams = CatmullRomCams or {} + +CatmullRomCams.AddLua = AddLua +CatmullRomCams.FilePath = "CatmullRomCameraTracks/" + +CatmullRomCams.SV = CatmullRomCams.SV or {} +CatmullRomCams.SH = CatmullRomCams.SH or {} +CatmullRomCams.CL = CatmullRomCams.CL or {} + +CatmullRomCams.SToolMethods = {} + +function CatmullRomCams.SH.UnitsToMeters(dist) + return (dist * 0.0254) +end + +function CatmullRomCams.SH.MetersToUnits(dist) + return (dist * 39.3700787) +end + +function CatmullRomCams.SToolMethods.ValidTrace(trace) + return (trace and trace.Entity and trace.Entity.GetClass and trace.Entity.IsValid and trace.Entity:IsValid() and (trace.Entity:GetClass() == "sent_catmullrom_camera")) +end + +CatmullRomCams.Tracks = CatmullRomCams.Tracks or {} + +local files = file.Find("CatmullRomCams/*.lua", "LUA") + +for _, v in pairs(files) do + AddLua("CatmullRomCams/" .. v) +end + +local files_stools = file.Find("CatmullRomCams/STools/*.lua", "LUA") + +for _, v in pairs(files_stools) do + AddLua("CatmullRomCams/STools/" .. v) +end +---FREEZEBUG: You forgot client tool data. +local files_stools_client = file.Find("weapons/gmod_tool/stools/*.lua", "LUA") +for _, v in pairs(files_stools_client) do + AddCSLuaFile("weapons/gmod_tool/stools/" .. v) +end +--[[ + +CatmullRomCams = CatmullRomCams or {} +CatmullRomCams.AddLua = AddLua +CatmullRomCams.FilePath = "CatmullRomCameraTracks/" +local function AddLua(filename) + local tmp = string.Explode("/", string.lower(filename)) + local parts = string.Explode("_", tmp[#tmp]) + + if SERVER then + if (parts[1] == "sh") or (parts[1] == "shared.lua") then + include(filename) + return AddCSLuaFile(filename) + elseif parts[1] == "cl" then + return AddCSLuaFile(filename) + elseif (parts[1] == "sv") or (parts[1] == "init.lua") then + return include(filename) + end + + ErrorNoHalt("Unknown file: ",filename,"\n") + PrintTable(tmp) + PrintTable(parts) + Error("Unable to determine if shared, serverside, or clientside.\n") + elseif CLIENT then + if (parts[1] == "sh") or (parts[1] == "cl") or (parts[1] == "shared.lua") then + return include(filename) + elseif (parts[1] == "sv") or (parts[1] == "init.lua") then //others, just to keep the system happy + return + end + + ErrorNoHalt("Unknown file: ",filename,"\n") + PrintTable(tmp) + PrintTable(parts) + Error("Unable to determine if shared, serverside, or clientside.\n") + else + return Error("Apparently we're God as we're not the client or the server.\n") + end +end +--[[ +if SERVER then AddCSLuaFile("sh_CatmullRomCams.lua") end + +CatmullRomCams = CatmullRomCams or {} + +CatmullRomCams.AddLua = AddLua +CatmullRomCams.FilePath = "CatmullRomCameraTracks/" + +CatmullRomCams.SV = CatmullRomCams.SV or {} +CatmullRomCams.SH = CatmullRomCams.SH or {} +CatmullRomCams.CL = CatmullRomCams.CL or {} + +CatmullRomCams.SToolMethods = {} + +function CatmullRomCams.SH.UnitsToMeters(dist) + return (dist * 0.0254) +end + +function CatmullRomCams.SH.MetersToUnits(dist) + return (dist * 39.3700787) +end + +function CatmullRomCams.SToolMethods.ValidTrace(trace) + return (trace and trace.Entity and trace.Entity.GetClass and trace.Entity.IsValid and trace.Entity:IsValid() and (trace.Entity:GetClass() == "sent_catmullrom_camera")) +end + +CatmullRomCams.Tracks = CatmullRomCams.Tracks or {} + +if SERVER then +AddCSLuaFile("CatmullRomCams/Stools/sh_duration_editor.lua") +AddCSLuaFile("CatmullRomCams/Stools/sh_hitchcock_effect.lua") +AddCSLuaFile("CatmullRomCams/Stools/sh_layout.lua") +AddCSLuaFile("CatmullRomCams/Stools/sh_linker.lua") +AddCSLuaFile("CatmullRomCams/Stools/sh_map_io_editor.lua") +AddCSLuaFile("CatmullRomCams/Stools/sh_numpad_trigger.lua") +AddCSLuaFile("CatmullRomCams/Stools/sh_smart_look.lua") +AddCSLuaFile("CatmullRomCams/cl_calcview_hook.lua") +AddCSLuaFile("CatmullRomCams/cl_edit_lock_display.lua") +AddCSLuaFile("CatmullRomCams/cl_tab.lua") +AddCSLuaFile("CatmullRomCams/cl_language_defs.lua") +AddCSLuaFile("CatmullRomCams/sh_catmullrom_spline_controller.lua") +AddCSLuaFile("CatmullRomCams/sh_cleanup.lua") +AddCSLuaFile("CatmullRomCams/sh_gc_booster.lua") +AddCSLuaFile("CatmullRomCams/sh_general_hooks.lua") +AddCSLuaFile("CatmullRomCams/sh_Quaternions.lua") +AddCSLuaFile("CatmullRomCams/sh_save_load.lua") +include("CatmullRomCams/sv_icon_resource.lua") +include("CatmullRomCams/sv_adv_dup_paste.lua") +include("CatmullRomCams/sv_saverestore.lua") +end +include("CatmullRomCams/sh_catmullrom_spline_controller.lua") +include("CatmullRomCams/sh_cleanup.lua") +include("CatmullRomCams/sh_gc_booster.lua") +include("CatmullRomCams/sh_general_hooks.lua") +include("CatmullRomCams/sh_Quaternions.lua") +include("CatmullRomCams/sh_save_load.lua") +include("CatmullRomCams/Stools/sh_duration_editor.lua") -- +include("CatmullRomCams/Stools/sh_hitchcock_effect.lua") -- +include("CatmullRomCams/Stools/sh_layout.lua") -- +include("CatmullRomCams/Stools/sh_linker.lua") -- +include("CatmullRomCams/Stools/sh_map_io_editor.lua") -- +include("CatmullRomCams/Stools/sh_numpad_trigger.lua") -- +include("CatmullRomCams/Stools/sh_smart_look.lua") -- + +if CLIENT then +include("CatmullRomCams/cl_language_defs.lua") +include("CatmullRomCams/cl_calcview_hook.lua") +include("CatmullRomCams/cl_edit_lock_display.lua") +include("CatmullRomCams/cl_tab.lua") + +end +]]-- diff --git a/garrysmod/addons/gmod-tools/lua/autorun/sh_mateditor.lua b/garrysmod/addons/gmod-tools/lua/autorun/sh_mateditor.lua new file mode 100644 index 0000000..e35e654 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/sh_mateditor.lua @@ -0,0 +1,223 @@ +materials = materials or {}; +materials.stored = materials.stored or {}; +materials.ents = {} +for _, ent in ipairs(ents.GetAll()) do + if ent.MaterialData ~= nil then + table.insert(materials.ents, ent) + end +end + +function materials:GetStored() + return self.stored; +end; + +function materials:Set(ent, texture, data, filter) + if (SERVER) then + data.texture = texture + netstream.Start(filter, 'Materialize', { ent }, { data }) + table.RemoveByValue(materials.ents, ent) + + if (texture == nil or texture == '') then + if (IsValid(ent)) then + ent:SetMaterial(''); + ent.MaterialData = nil + duplicator.ClearEntityModifier(ent, 'MaterialData') + end; + + return; + end; + + ent:SetMaterial(''); + table.insert(materials.ents, ent) + + ent.MaterialData = { + texture = texture, + ScaleX = data.ScaleX or 1, + ScaleY = data.ScaleY or 1, + OffsetX = data.OffsetX or 0, + OffsetY = data.OffsetY or 0, + UseNoise = data.UseNoise or false, + NoiseTexture = data.NoiseTexture or 'detail/noise_detail_01', + NoiseScaleX = data.NoiseScaleX or 1, + NoiseScaleY = data.NoiseScaleY or 1, + NoiseOffsetX = data.NoiseOffsetX or 0, + NoiseOffsetY = data.NoiseOffsetY or 0, + }; + + texture = texture:lower(); + texture = string.Trim(texture); + local uid = texture .. '+' .. (data.ScaleX or 1) .. '+' .. (data.ScaleY or 1) .. '+' .. (data.OffsetX or 0) .. '+' .. (data.OffsetY or 0); + + if (data.UseNoise) then + uid = uid .. (data.NoiseTexture or 'detail/noise_detail_01') .. '+' .. (data.NoiseScaleX or 1) .. '+' .. (data.NoiseScaleY or 1) .. '+' .. (data.NoiseOffsetX or 0) .. '+' .. (data.NoiseOffsetY or 0); + end; + + uid = uid:gsub('%.', '-'); + + ent:SetMaterial('!' .. uid); + + duplicator.StoreEntityModifier(ent, 'MaterialData', ent.MaterialData); + else + if (texture == nil or texture == '') then + if (IsValid(ent)) then + ent:SetMaterial(''); + end; + + return; + end; + + ent:SetMaterial(''); + + data = data or {}; + data.texture = texture; + data.UseNoise = data.UseNoise or false; + data.ScaleX = data.ScaleX or 1; + data.ScaleY = data.ScaleY or 1; + data.OffsetX = data.OffsetX or 0; + data.OffsetY = data.OffsetY or 0; + data.NoiseTexture = data.NoiseTexture or 'detail/noise_detail_01'; + data.NoiseScaleX = data.NoiseScaleX or 1; + data.NoiseScaleY = data.NoiseScaleY or 1; + data.NoiseOffsetX = data.NoiseOffsetX or 0; + data.NoiseOffsetY = data.NoiseOffsetY or 0; + + texture = texture:lower(); + texture = string.Trim(texture); + + local tempMat = Material(texture); + + if (string.find(texture, '../', 1, true) or string.find(texture, 'pp/', 1, true)) then + return; + end; + + local uid = texture .. '+' .. data.ScaleX .. '+' .. data.ScaleY .. '+' .. data.OffsetX .. '+' .. data.OffsetY; + + if (data.UseNoise) then + uid = uid .. (data.NoiseTexture or 'detail/noise_detail_01') .. '+' .. (data.NoiseScaleX or 1) .. '+' .. (data.NoiseScaleY or 1) .. '+' .. (data.NoiseOffsetX or 0) .. '+' .. (data.NoiseOffsetY or 0); + end; + + uid = uid:gsub('%.', '-'); + + if (!self.stored[uid]) then + + local matTable = { + ['$basetexture'] = tempMat:GetName(), + ['$basetexturetransform'] = 'center .5 .5 scale ' .. (1 / data.ScaleX) .. ' ' .. (1 / data.ScaleY) .. ' rotate 0 translate ' .. data.OffsetX .. ' ' .. data.OffsetY, + ['$vertexalpha'] = 0, + ['$vertexcolor'] = 1 + }; + + for k, v in pairs(data) do + if (k:sub(1, 1) == '$') then + matTable[k] = v; + end; + end; + + if (data.UseNoise) then + matTable['$detail'] = data.NoiseTexture; + end; + + if (file.Exists('materials/' .. texture .. '_normal.vtf', 'GAME')) then + matTable['$bumpmap'] = texture .. '_normal'; + matTable['$bumptransform'] = 'center .5 .5 scale ' .. (1 / data.ScaleX) .. ' ' .. (1 / data.ScaleY) .. ' rotate 0 translate ' .. data.OffsetX .. ' ' .. data.OffsetY; + end; + + local matrix = Matrix(); + matrix:Scale(Vector(1 / data.ScaleX, 1 / data.ScaleY, 1)); + matrix:Translate(Vector(data.OffsetX, data.OffsetY, 0)); + + local noiseMatrix = Matrix(); + noiseMatrix:Scale(Vector(1 / data.NoiseScaleX, 1 / data.NoiseScaleY, 1)); + noiseMatrix:Translate(Vector(data.NoiseOffsetX, data.NoiseOffsetY, 0)); + + self.stored[uid] = CreateMaterial(uid, 'VertexLitGeneric', matTable); + if tempMat:GetTexture('$basetexture') then self.stored[uid]:SetTexture('$basetexture', tempMat:GetTexture('$basetexture')); end + self.stored[uid]:SetMatrix('$basetexturetransform', matrix); + self.stored[uid]:SetMatrix('$detailtexturetransform', noiseMatrix); + end; + + ent.MaterialData = { + texture = texture, + ScaleX = data.ScaleX or 1, + ScaleY = data.ScaleY or 1, + OffsetX = data.OffsetX or 0, + OffsetY = data.OffsetY or 0, + UseNoise = data.UseNoise or false, + NoiseTexture = data.NoiseTexture or 'detail/noise_detail_01', + NoiseScaleX = data.NoiseScaleX or 1, + NoiseScaleY = data.NoiseScaleY or 1, + NoiseOffsetX = data.NoiseOffsetX or 0, + NoiseOffsetY = data.NoiseOffsetY or 0, + }; + + ent:SetMaterial('!' .. uid); + end; +end; + +if (CLIENT) then + netstream.Hook('Materialize', function(ents, mats) + for i, ent in ipairs(ents) do + if not IsValid(ent) then continue end + + local mat = mats[i] + materials:Set(ent, mat and mat.texture or '', mat or {}) + end + end); +else + local function syncMaterials(ply) + local mats = octolib.table.mapSequential(materials.ents, function(ent) + return ent.MaterialData + end) + netstream.Heavy(ply, 'Materialize', materials.ents, mats) + end + hook.Add('PlayerFinishedLoading', 'MaterialData', function(ply) + timer.Simple(15, function() + if not IsValid(ply) then return end + syncMaterials(ply) + end) + end) + + local page = 1 + timer.Create('advMat.sync', 10, 0, function() + if player.GetCount() < 1 then return end + + local plys = octolib.array.page(player.GetAll(), 10, page) + if table.Count(plys) < 1 then + page = 1 + plys = octolib.array.page(player.GetAll(), 10, page) + end + page = page + 1 + + syncMaterials(plys) + end) + + timer.Create('advMat.validate', 30, 0, function() + for i = #materials.ents, 1, -1 do + if not IsValid(materials.ents[i]) then + table.remove(materials.ents, i) + end + end + end) + + hook.Add('EntityRemoved', 'MaterialData', function(ent) + if ent.MaterialData then + table.RemoveByValue(materials.ents, ent) + end + end) + + concommand.Add('advmat_reload', function(ply) + -- if ply.advmat_cooldown then return ply:Notify('Подожди немного, обновлять данные можно не чаще раза в 30 секунд') end + + syncMaterials(ply) + -- ply.advmat_cooldown = true + + -- timer.Simple(30, function() + -- if not IsValid(ply) then return end + -- ply.advmat_cooldown = nil + -- end) + end) +end; + +duplicator.RegisterEntityModifier('MaterialData', function(player, entity, data) + materials:Set(entity, data.texture, data); +end); \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/autorun/textscreens_util.lua b/garrysmod/addons/gmod-tools/lua/autorun/textscreens_util.lua new file mode 100644 index 0000000..5c382db --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/autorun/textscreens_util.lua @@ -0,0 +1,141 @@ +if SERVER then + AddCSLuaFile() + CreateConVar("sbox_maxtextscreens", "1", {FCVAR_NOTIFY, FCVAR_REPLICATED}) + CreateConVar("ss_call_to_home", "1", {FCVAR_NOTIFY, FCVAR_REPLICATED}) + + function SSGetIP() + local hostip = GetConVar("hostip"):GetString() + hostip = tonumber(hostip) + if not hostip or hostip == nil then return 0 end + local ip = {} + ip[1] = bit.rshift(bit.band(hostip, 0xFF000000), 24) + ip[2] = bit.rshift(bit.band(hostip, 0x00FF0000), 16) + ip[3] = bit.rshift(bit.band(hostip, 0x0000FF00), 8) + ip[4] = bit.band(hostip, 0x000000FF) + + return table.concat(ip, ".") + end + + local function StringRandom(int) + math.randomseed(os.time()) + local s = "" + + for i = 1, int do + s = s .. string.char(math.random(65, 90)) + end + + return s + end + + local textscreens = {} + + hook.Add("InitPostEntity", "loadTextScreens", function() + timer.Simple(10, function() + print("Spawning textscreens...") + textscreens = file.Read("textscreens.txt", "DATA") + + if not textscreens then textscreens = {} return end + + textscreens = util.JSONToTable(textscreens) + local count = 0 + + for k, v in pairs(textscreens) do + if v.MapName ~= game.GetMap() then continue end + local textScreen = ents.Create("textscreen") + textScreen:SetPos(Vector(v.posx, v.posy, v.posz)) + textScreen:SetAngles(Angle(v.angp, v.angy, v.angr)) + textScreen.uniqueName = v.uniqueName + textScreen:Spawn() + textScreen:Activate() + textScreen:SetMoveType(MOVETYPE_NONE) + + for k, v in pairs(v.lines or {}) do + textScreen:SetLine(k, v.text, Color(v.color.r, v.color.g, v.color.b, v.color.a), v.size, v.font) + end + + textScreen:SetIsPersisted(true) + count = count + 1 + end + + return print("Spawned " .. count .. " textscreens for map " .. game.GetMap()) + end) + end) + + concommand.Add("SS_TextScreen", function(ply, cmd, args) + if not ply:IsSuperAdmin() or not args or not args[1] or not args[2] or not (args[1] == "delete" or args[1] == "add") then + ply:ChatPrint("not authorised, or bad arguments") + return + end + + local ent = Entity(args[2]) + if not IsValid(ent) or ent:GetClass() ~= "textscreen" then return false end + + if args[1] == "add" then + local pos = ent:GetPos() + local ang = ent:GetAngles() + local toAdd = {} + toAdd.posx = pos.x + toAdd.posy = pos.y + toAdd.posz = pos.z + toAdd.angp = ang.p + toAdd.angy = ang.y + toAdd.angr = ang.r + -- So we can reference it easilly later because EntIndexes are so unreliable + toAdd.uniqueName = StringRandom(10) + toAdd.MapName = game.GetMap() + toAdd.lines = ent.lines + table.insert(textscreens, toAdd) + file.Write("textscreens.txt", util.TableToJSON(textscreens)) + ent:SetIsPersisted(true) + + return ply:ChatPrint("Textscreen made permament and saved.") + else + for k, v in pairs(textscreens) do + if v.uniqueName == ent.uniqueName then + textscreens[k] = nil + end + end + + ent:Remove() + file.Write("textscreens.txt", util.TableToJSON(textscreens)) + + return ply:ChatPrint("Textscreen removed and is no longer permanent.") + end + end) +end + +if CLIENT then + properties.Add("addPermaScreen", { + MenuLabel = "Make perma textscreen", + Order = 2001, + MenuIcon = "icon16/transmit.png", + Filter = function(self, ent, ply) + if not IsValid(ent) or ent:GetClass() ~= "textscreen" then return false end + if ent:GetIsPersisted() then return false end + + return ply:IsAdmin() + end, + Action = function(self, ent) + if not IsValid(ent) then return false end + + return RunConsoleCommand("SS_TextScreen", "add", ent:EntIndex()) + end + }) + + properties.Add("removePermaScreen", { + MenuLabel = "Remove perma textscreen", + Order = 2002, + MenuIcon = "icon16/transmit_delete.png", + Filter = function(self, ent, ply) + if not IsValid(ent) or ent:GetClass() ~= "textscreen" then return false end + if not ent:GetIsPersisted() then return false end + + return ply:IsAdmin() + end, + Action = function(self, ent) + if not IsValid(ent) then return end + + return RunConsoleCommand("SS_TextScreen", "delete", ent:EntIndex()) + end + }) +end diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_calcview_hook.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_calcview_hook.lua new file mode 100644 index 0000000..82004c1 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_calcview_hook.lua @@ -0,0 +1,24 @@ + +function CatmullRomCams.CL.CalcViewOverride(ply, origin, angles, fov) + local weap = ply:GetActiveWeapon() + + local toolmode_active = (CatmullRomCams.SToolMethods.ToolObj and (gmod_toolmode:GetString() == "catmullrom_camera") and weap and weap:IsValid() and (weap:GetClass() == "gmod_tool")) + local playing_track = ply:GetNetVar("UnderControlCatmullRomCamera") and ply:GetNetVar("UnderControlCatmullRomCamera"):IsValid() + + if not (toolmode_active or playing_track) then return end + + local overrides = {} + overrides.origin = origin + overrides.angles = angles + overrides.fov = fov + + if toolmode_active then + overrides.fov = CatmullRomCams.SToolMethods.ToolObj:GetClientNumber("zoom") or 75 + overrides.angles.r = (CatmullRomCams.SToolMethods.ToolObj:GetClientNumber("enable_roll") == 1) and CatmullRomCams.SToolMethods.ToolObj:GetClientNumber("roll") or angles.r + else + overrides.fov = ply.CatmullRomCamsTrackZoom or fov + end + + return overrides +end +hook.Add("CalcView", "CatmullRomCams.CL.CalcViewOverride", CatmullRomCams.CL.CalcViewOverride) diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_edit_lock_display.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_edit_lock_display.lua new file mode 100644 index 0000000..8eb812c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_edit_lock_display.lua @@ -0,0 +1,53 @@ +-- START HACKHACKHACK :( +do return end +local CHECK_ALL_PLAYERS_FOR_EDIT_LOCK = true + +local function CHECK_PLAYER(ply) + if not ply then return end + + if not (ply and (ply ~= NULL) and ply.IsValid and ply:IsValid() and ply:Alive() and ply:GetActiveWeapon() and ply:GetActiveWeapon():IsValid() and (ply:GetActiveWeapon():GetClass() == "gmod_tool")) then + if ply.LockedOnEntity then + ply.LockedOnEntity.IsLocked = false + + ply.LockedOnEntity = nil + end + else + local trace = ply:GetEyeTrace() + + if trace.StartPos:Distance(trace.HitPos) > 512 then + if ply.CatmullRomCams_LockedOnEntity then + ply.CatmullRomCams_LockedOnEntity.IsLocked = false + print("too far") + ply.CatmullRomCams_LockedOnEntity = nil + end + elseif not CatmullRomCams.SToolMethods.ValidTrace(trace) then + if ply.CatmullRomCams_LockedOnEntity then + ply.CatmullRomCams_LockedOnEntity.IsLocked = false + print("invalid") + ply.CatmullRomCams_LockedOnEntity = nil + end + elseif ply.CatmullRomCams_LockedOnEntity then--if ply.CatmullRomCams_LockedOnEntity ~= trace.Entity then -- lazyness to handle the case where more then one player is looking at a node and one looks away <_< + ply.CatmullRomCams_LockedOnEntity.IsLocked = false + + ply.CatmullRomCams_LockedOnEntity = trace.Entity + print("locked") + ply.CatmullRomCams_LockedOnEntity.IsLocked = true + else + ply.CatmullRomCams_LockedOnEntity = trace.Entity + print("locked") + ply.CatmullRomCams_LockedOnEntity.IsLocked = true + end + end +end + +function CatmullRomCams.SToolMethods.LockOnEditingDisplayThink() + if not CHECK_ALL_PLAYERS_FOR_EDIT_LOCK then return CHECK_PLAYER(LocalPlayer()) end + + for k, v in pairs(player.GetAll()) do + CHECK_PLAYER(v) + end +end + +hook.Add("Think", "CatmullRomCams.SToolMethods.LockOnEditingDisplayThink", CatmullRomCams.SToolMethods.LockOnEditingDisplayThink) + +-- END HACKHACKHACK \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_language_defs.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_language_defs.lua new file mode 100644 index 0000000..f72d458 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_language_defs.lua @@ -0,0 +1,31 @@ +language.Add("Tool.catmullrom_camera.name", "Catmull-Rom Cinematic Cameras: Track Layout") +language.Add("Tool.catmullrom_camera.desc", "Spawn a new segment on your current numpad key's track.") +language.Add("Tool.catmullrom_camera.0", "Left click to spawn a new segment, or to update mutable settings on an existing camera.\nRight click to spawn a new segment that will track an entity.") + +language.Add("Tool.catmullrom_camera_duration.name", "Catmull-Rom Cinematic Cameras: Duration Editor") +language.Add("Tool.catmullrom_camera_duration.desc", "Modifies a track segment's duration.") +language.Add("Tool.catmullrom_camera_duration.0", "Left click to set the duration of a track segment. Right click to copy it.\nReload to reset it to default (2 sec.). Use shift + left click for setting meters per seconds.") + +language.Add("Tool.catmullrom_camera_numpad_trigger.name", "Catmull-Rom Cinematic Cameras: Numpad Trigger Editor") +language.Add("Tool.catmullrom_camera_numpad_trigger.desc", "Allows you to specify numpad keys to be presed when the track reaches a specific node.") +language.Add("Tool.catmullrom_camera_numpad_trigger.0", "Left click to set the numpad keys to be triggered when selected node is reached.\nRight click to copy a node's settings. Reload to reset the node.") + +language.Add("Tool.catmullrom_camera_looker.name", "Catmull-Rom Cinematic Cameras: Smart Look Modifier") +language.Add("Tool.catmullrom_camera_looker.desc", "You can make the camera search around for something interesting to look at instead of always looking the same way.") +language.Add("Tool.catmullrom_camera_looker.0", "Left click to apply settings. Right click to copy. Reload to reset.") + +language.Add("Tool.catmullrom_camera_map_io.name", "Catmull-Rom Cinematic Cameras: Map I/O Modifier") +language.Add("Tool.catmullrom_camera_map_io.desc", "Useful only for mappers! Allows you to specify the OnUser output(s) to call when you reach a node.") +language.Add("Tool.catmullrom_camera_map_io.0", "Left click apply settings. Right click to copy. Reload to reset.") + +language.Add("Tool.catmullrom_camera_linker.name", "Catmull-Rom Cinematic Cameras: Track Linker") +language.Add("Tool.catmullrom_camera_linker.desc", "You can link two tracks and merge them using this tool.") +language.Add("Tool.catmullrom_camera_linker.0", "Left click on the first track.") +language.Add("Tool.catmullrom_camera_linker.1", "Now left click on the second track. Reload to cancel.") + +language.Add("Tool.catmullrom_camera_hitchcock.name", "Catmull-Rom Cinematic Cameras: Hitchcock Effect") +language.Add("Tool.catmullrom_camera_hitchcock.desc", "Vomit your guts out with this useful tool! Now you can create that sickening effect that those poor people on the Galactica have to suffer when they jump.") +language.Add("Tool.catmullrom_camera_hitchcock.0", "Left on an end node to setup a Hitchcock sequence. (Makes 4 nodes due to the way the effect works.)") + +language.Add("Undone.CatmullRomCamera", "Undone Catmull-Rom Camera Segment") +language.Add("Undone.CatmullRomCamsHitchcockEffect", "Undone Hitchcock Effect Shot") diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_tab.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_tab.lua new file mode 100644 index 0000000..696d3a5 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/cl_tab.lua @@ -0,0 +1,5 @@ + +function CatmullRomCams.CL.Tab() + return spawnmenu.AddToolTab("CRCCams", "CRCCams", "icon16/camera.png") +end +hook.Add("AddToolMenuTabs", "CatmullRomCams.CL.Tab", CatmullRomCams.CL.Tab) diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_catmullrom_spline_controller.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_catmullrom_spline_controller.lua new file mode 100644 index 0000000..405e218 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_catmullrom_spline_controller.lua @@ -0,0 +1,269 @@ +--[[ + Planetfall Catmull-Rom Spline Controller + + Obviously this one has been customized for the cameras but you should be able to + adapt it to whatever is needed. +--]] + + +local Controller = {} +Controller.STEPS = 10 + +function Controller:New(host_ent) + local obj = {} + + obj.__index = Controller + setmetatable(obj, obj) + + obj.Perc = 0 + + obj.CurSegment = 2 + obj.CurSegmentTimestamp = 0 + + obj.PointsList = {} + + obj.FacingsList = {} + obj.RotationsList = {} + + obj.Spline = {} + obj.ZoomList = {} + + obj.EntityList = {host_ent} + + obj.DurationList = {} + + obj.Host = host_ent + + obj.CLEntityListCount = 0 + + return obj +end + +function Controller:Reset() + self.CurSegment = 2 + self.CurSegmentTimestamp = CurTime() + + if self.DurationList[2] then + self.CurSegmentTimestamp = self.CurSegmentTimestamp + self.DurationList[2] + end +end + +function Controller:AddPoint(index, vec, duration) + self.PointsList[index] = vec + self.DurationList[index] = duration or 2 +end + +function Controller:AddAngle(index, ang, duration) + self.FacingsList[index] = ang:Forward() + self.RotationsList[index] = ang.r + + self.DurationList[index] = duration or 2 +end + +function Controller:AddPointAngle(index, vec, ang, duration) + self.PointsList[index] = vec + + self.FacingsList[index] = ang:Forward() + self.RotationsList[index] = ang.r + + self.DurationList[index] = duration or 2 +end + +function Controller:AddZoomPoint(index, zoom) + self.ZoomList[index] = zoom or 75 +end + +function Controller:CalcPerc() + -- Isolate a very specific issue where if it was at segment 3 and + -- you removed/undid to 4 nodes it would panic. + + if (i == 3) and (#self.PointsList == 4) then + self.CurSegment = 2 + end + + if self.CurSegmentTimestamp == 0 then + self.CurSegmentTimestamp = CurTime() + self.DurationList[self.CurSegment] + end + + if not self.DurationList[self.CurSegment] then + if SERVER then + return self.Host:End() + end + + return + end + + self.Perc = 1 - ((self.CurSegmentTimestamp - CurTime()) / (self.DurationList[self.CurSegment] + .001)) -- So you can't put in zero easily + + if self.Perc > 1 then + self:EndSegment() + end + + return self.Perc +end + +function Controller:EndSegment() + self.Perc = self.Perc - 1 + + self.CurSegment = self.CurSegment + 1 + + if self.Host.OnChangeSegment then + self.Host:OnChangeSegment(self.CurSegment) + end + + if self.CurSegment > (#self.PointsList - 2) then -- I know this looks repetitive + self.CurSegment = 2 + + if SERVER then + self.Host:End() + end + end + + self.CurSegmentTimestamp = CurTime() + self.DurationList[self.CurSegment] + + return self.Perc +end + + +-- Catmull-Rom spline is just like a B spline, only with a different basis +function Controller:CatmullRomCalc(i, perc) + perc = perc or self.Perc + + if i == -1 then + return ((-perc + 2) * perc - 1) * perc * .5 + elseif i == 0 then + return (((3 * perc - 5) * perc) * perc + 2) * .5 + elseif i == 1 then + return ((-3 * perc + 4) * perc + 1)*perc * .5 + elseif i == 2 then + return ((perc - 1) * perc * perc) * .5 + else + ErrorNoHalt("Invalid i: ", i, "\n") + end + + return 0 +end + +function Controller:Point(i, perc) + perc = perc or self.Perc + i = i or self.CurSegment + + local vec = Vector(0, 0, 0) + + local safeguard = (#self.PointsList - 2) + + -- Isolate a very specific issue where if it was at segment 3 and + -- you removed/undid to 4 nodes it would panic. + + if i > safeguard then + i = safeguard + + self.CurSegment = i + end + + for j = -1, 2 do + local idx = i + j + local multi = self:CatmullRomCalc(j, perc) + + if self.PointsList[idx] then + vec.x = vec.x + (multi * self.PointsList[idx].x) + vec.y = vec.y + (multi * self.PointsList[idx].y) + vec.z = vec.z + (multi * self.PointsList[idx].z) + end + end + + return vec +end + +function Controller:Angle(i, perc) -- Gods rotted euler angles! Let's use a pseudo quaternion-like rotation scheme instead. :3 + perc = perc or self.Perc + i = i or self.CurSegment + + -- My intellect is superior to this! + --[[ + do -- It would have been VERY nice of someone to tell me that LerpAngle was a C++ function which used Quaternions. :downs: + return LerpAngle(perc, self.AnglesList[i], self.AnglesList[i + 1]) + --return QuaternionNLerp(self.AnglesList[i]:Quaternion(), self.AnglesList[i + 1]:Quaternion(), perc):ToAngle() + end + --]] + + local facing = Vector(0, 0, 0) + local rotation = 0 + + local safeguard = (#self.PointsList - 2) + + -- Isolate a very specific issue where if it was at segment 3 and + -- you removed/undid to 4 nodes it would panic. + + if i > safeguard then + i = safeguard + + self.CurSegment = i + end + + for j = -1, 2 do + local idx = i + j + local multi = self:CatmullRomCalc(j, perc) + + if self.FacingsList[idx] then + facing.x = facing.x + (multi * self.FacingsList[idx].x) + facing.y = facing.y + (multi * self.FacingsList[idx].y) + facing.z = facing.z + (multi * self.FacingsList[idx].z) + + rotation = rotation + (multi * self.RotationsList[idx]) + end + end + + local ang = facing:Angle() + ang.r = rotation + --print(ang) + return ang +end + +function Controller:CalcZoom(i, perc) -- Gods rotted euler angles! Let's use a pseudo quaternion-like rotation scheme instead. :3 + perc = perc or self.Perc + i = i or self.CurSegment + + local zoom = 0 + + local safeguard = (#self.PointsList - 2) + + -- Isolate a very specific issue where if it was at segment 3 and + -- you removed/undid to 4 nodes it would panic. + + if i > safeguard then + i = safeguard + + self.CurSegment = i + end + + for j = -1, 2 do + local idx = i + j + + if self.ZoomList[idx] then + zoom = zoom + (self:CatmullRomCalc(j, perc) * self.ZoomList[idx]) + end + end + + return zoom +end + +function Controller:CalcEntireSpline() + local nodecount = #self.PointsList + + if nodecount < 4 then + return ErrorNoHalt("Not enough nodes given, I need four and was given ", nodecount, ".\n") + end + + local pointcount = 0 + + for index = 2, (nodecount - 2) do + for j = 1, Controller.STEPS do + pointcount = pointcount + 1 + + self.Spline[pointcount] = self:Point(index, j / Controller.STEPS) + end + end +end + +CatmullRomCams.SH.Controller = Controller diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_cleanup.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_cleanup.lua new file mode 100644 index 0000000..48f2d6c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_cleanup.lua @@ -0,0 +1 @@ +cleanup.Register("catmullrom_cameras") -- :loleyes: \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_gc_booster.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_gc_booster.lua new file mode 100644 index 0000000..a0e5ba9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_gc_booster.lua @@ -0,0 +1,11 @@ + +if CatmullRomCams.SH.GCStarted then return end + +CatmullRomCams.SH.GCStarted = true + +function CatmullRomCams.SH.GarbageCollectionBooster() + collectgarbage() + + return timer.Simple(30, CatmullRomCams.SH.GarbageCollectionBooster) +end +timer.Simple(30, CatmullRomCams.SH.GarbageCollectionBooster) -- Now unneeded! :4chan: But reenabled beause it's not readily noticeable and it still helps. :wink: diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_general_hooks.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_general_hooks.lua new file mode 100644 index 0000000..cd4aaba --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_general_hooks.lua @@ -0,0 +1,72 @@ + +if CLIENT then + CatmullRomCams.CL.TunnelingTracer = {} + --CatmullRomCams.CL.TunnelingTracer.mask = MASK_NPCWORLDSTATIC + + function CatmullRomCams.CL.CalcViewOverride(ply, origin, angles, fov) + local weap = ply:GetActiveWeapon() + + local toolmode_active = (CatmullRomCams.SToolMethods.ToolObj and (GetConVar( "gmod_toolmode"):GetString() == "catmullrom_camera") and weap and weap:IsValid() and (weap:GetClass() == "gmod_tool")) + local playing_track = ply:GetNetVar("UnderControlCatmullRomCamera") and ply:GetNetVar("UnderControlCatmullRomCamera"):IsValid() + + if not (toolmode_active or playing_track) then return end + + local overrides = {} + overrides.origin = origin + overrides.angles = angles + overrides.fov = fov + + if playing_track then + overrides.fov = ply.CatmullRomCamsTrackZoom or fov + else + overrides.fov = CatmullRomCams.SToolMethods.ToolObj:GetClientNumber("zoom") or 75 + overrides.angles.r = (CatmullRomCams.SToolMethods.ToolObj:GetClientNumber("enable_roll") == 1) and CatmullRomCams.SToolMethods.ToolObj:GetClientNumber("roll") or angles.r + end + + CatmullRomCams.CL.TunnelingTracer.start = origin + CatmullRomCams.CL.TunnelingTracer.endpos = origin-- + angles:Forward() + + return overrides + end + hook.Add("CalcView", "CatmullRomCams.CL.CalcViewOverride", CatmullRomCams.CL.CalcViewOverride) + + function CatmullRomCams.CL.HUDHide(element) + local ply = LocalPlayer() + + if ply:GetNetVar("UnderControlCatmullRomCamera") and ply:GetNetVar("UnderControlCatmullRomCamera"):IsValid() then + return false + end + end + hook.Add("HUDShouldDraw", "CatmullRomCams.CL.HUDHide", CatmullRomCams.CL.HUDHide) + + function CatmullRomCams.CL.BlackenScreenDuringTunneling() + local ply = LocalPlayer() + + if ply:GetNetVar("UnderControlCatmullRomCamera") and ply:GetNetVar("UnderControlCatmullRomCamera"):IsValid() then + local tr = util.TraceLine(CatmullRomCams.CL.TunnelingTracer) + + if (tr.FractionLeftSolid == 1) then--and (tr.Entity == ents.GetByIndex(0)) then + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, ScrW(), ScrH()) + end + + //return true + end + end + hook.Add("RenderScreenspaceEffects", "CatmullRomCams.CL.BlackenScreenDuringTunneling", CatmullRomCams.CL.BlackenScreenDuringTunneling) +else + function CatmullRomCams.SH.Toggle(sid, ent, idx, buttoned) + local ply = player.GetBySteamID(sid) + if ent and ply and ply.IsPlayer and ent.IsValid and ply:IsPlayer() and ent:IsValid() and (ent:GetClass() == "sent_catmullrom_camera") then + return ent:Toggle(ply) + end + end + numpad.Register("CatmullRomCamera_Toggle", CatmullRomCams.SH.Toggle) + + function CatmullRomCams.SH.GravGunPuntStopper(ply, ent) + if ent and ent.IsValid and ent:IsValid() and (ent:GetClass() == "sent_catmullrom_camera") then + return false + end + end + hook.Add("GravGunPunt", "CatmullRomCams.SH.GravGunPuntStopper", CatmullRomCams.SH.GravGunPuntStopper) +end diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_quaternions.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_quaternions.lua new file mode 100644 index 0000000..6c9ff6b --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_quaternions.lua @@ -0,0 +1,249 @@ +local function RoundTo(val, num_decimal_points) + local pow = 1 + + for i = 1, num_decimal_points do + pow = pow * 10 + end + + return (math.Round(val * pow) / pow) +end + + +local AngMeta = FindMetaTable("Angle") + +function AngMeta:ToDeg() + self.p = math.deg(self.p) + self.y = math.deg(self.y) + self.r = math.deg(self.r) +end + +function AngMeta:ToRad() + self.p = math.rad(self.p) + self.y = math.rad(self.y) + self.r = math.rad(self.r) +end + +function AngMeta:Quaternion(quaternion_output) + local ang = self * 1 + ang:ToRad() + + local cp = math.cos(ang.p * .5) + local cy = math.cos(ang.y * .5) + local cr = math.cos(ang.r * .5) + + local sp = math.sin(ang.p * .5) + local sy = math.sin(ang.y * .5) + local sr = math.sin(ang.r * .5) + + local cpcy = cp * cy; + local spsy = sp * sy; + + if not quaternion_output then return Quaternion(Vector(sr * cpcy - cr * spsy, + cr * sp * cy + sr * cp * sy, + cr * cp * sy - sr * sp * cy), + cr * cpcy + sr * spsy) end + + quaternion_output.Vec.x = sr * cpcy - cr * spsy + quaternion_output.Vec.y = cr * sp * cy + sr * cp * sy + quaternion_output.Vec.z = cr * cp * sy - sr * sp * cy + + quaternion_output.Rotation = cr * cpcy + sr * spsy +end + +local callmeta = {} + +function callmeta:__call(vec, rot) + return Quaternion:New(vec, rot) +end + +Quaternion = {} +setmetatable(Quaternion, callmeta) + +local opsmeta = {} +opsmeta.__index = Quaternion + +function Quaternion:New(vec, rot) + local obj = {} + --obj.__index = Quaternion + setmetatable(obj, opsmeta) + + obj.Vec = vec or Vector(0, 0, 0) + obj.Rotation = math.rad(rot) or 1 + + return obj +end + +function Quaternion:Reset(make_into_identity) + self.Vec.x = 0 + self.Vec.y = 0 + self.Vec.z = 0 + + self.Rotation = 1 +end + +function Quaternion:IsIdentity() + return tobool((self.Vec.x == 0) and + (self.Vec.y == 0) and + (self.Vec.z == 0) and + (self.Rotation == 1)) +end + +function opsmeta:__eq(quaternion, epsilon) + if not epsilon then + return tobool((self.Vec.x == quaternion.Vec.x) and + (self.Vec.y == quaternion.Vec.y) and + (self.Vec.z == quaternion.Vec.z) and + (self.Rotation == quaternion.Rotation)) + end + + return tobool((math.abs(self.Vec.x - quaternion.Vec.x) < epsilon) and + (math.abs(self.Vec.y - quaternion.Vec.y) < epsilon) and + (math.abs(self.Vec.z - quaternion.Vec.z) < epsilon) and + (math.abs(self.Rotation - quaternion.Rotation) < epsilon)) +end + +function opsmeta:__add(quaternion) + return Quaternion(Vector(self.Vec.x + quaternion.Vec.x, self.Vec.y + quaternion.Vec.y, self.Vec.z + quaternion.Vec.z), self.Rotation + quaternion.Rotation) +end + +function opsmeta:__unm() + self.Vec = self.Vec * -1 +end + +function opsmeta:__mul(inval) + return ((type(inval) == "number") and self:MultiplyScalar(inval) or self:MultiplyQuaternion(inval)) +end + +function Quaternion:MultiplyQuaternion(quaternion) + local vec = Vector(self.Rotation * quaternion.Vec.x + self.Vec.x * quaternion.Rotation + self.Vec.y * quaternion.Vec.z - self.Vec.z * quaternion.Vec.y, + self.Rotation * quaternion.Vec.y + self.Vec.y * quaternion.Rotation + self.Vec.z * quaternion.Vec.x - self.Vec.x * quaternion.Vec.z, + self.Rotation * quaternion.Vec.z + self.Vec.z * quaternion.Rotation + self.Vec.x * quaternion.Vec.y - self.Vec.y * quaternion.Vec.x) + + local rot = self.Rotation * quaternion.Rotation - self.Vec.x * quaternion.Vec.x - self.Vec.y * quaternion.Vec.y - self.Vec.z * quaternion.Vec.z + + return Quaternion(vec, rot) +end + +function Quaternion:MultiplyScalar(val) + return Quaternion(self.Vec * val, self.Rotation * val) +end + +function Quaternion:SetAxis(vec, deg_rotation) + return self:SetAxisRad(vec, math.rad(deg_rotation)) +end + +function Quaternion:SetAxisRad(vec, rad_rotation) + self.Vec = self.Vec * math.sin(rad_rotation) + self.Rotation = math.cos(rad_rotation) +end + +function Quaternion:Dot(quaternion) + return ((self.Vec.x * quaternion.Vec.x) + (self.Vec.y * quaternion.Vec.y) + (self.Vec.z * quaternion.Vec.z) + (self.Rotation * quaternion.Rotation)) +end + +function Quaternion:FromAngle(angle) + angle = angle * 1 + angle:ToRad() + + local cp = math.cos(angle.p * .5) + local cy = math.cos(angle.y * .5) + local cr = math.cos(angle.r * .5) + + local sp = math.sin(angle.p * .5) + local sy = math.sin(angle.y * .5) + local sr = math.sin(angle.r * .5) + + local cpcy = cp * cy; + local spsy = sp * sy; + + self.Vec = Vector(sr * cpcy - cr * spsy, + cr * sp * cy + sr * cp * sy, + cr * cp * sy - sr * sp * cy) + + self.Rotation = cr * cpcy + sr * spsy +end + +function Quaternion:ToAngle(angle) + angle = angle or Angle() + + local singularity_checks = (self.Vec.y * self.Vec.z) + (self.Vec.x * self.Rotation) + + if singularity_checks > 0.499 then -- singularity at north pole + angle.p = math.pi * .5 + angle.y = 2 * math.atan2(self.Vec.y, self.Rotation) + angle.r = 0 + elseif singularity_checks < -0.499 then -- singularity at south pole + angle.p = math.pi * -.5 + angle.y = -2 * math.atan2(self.Vec.y, self.Rotation) + angle.r = 0 + else + local x_2 = 1 - (2 * self.Vec.x^2) + + angle.p = math.asin( 2 * singularity_checks) + angle.y = math.atan2((2 * self.Vec.z * self.Rotation) - (2 * self.Vec.y * self.Vec.x), (x_2 - (2 * self.Vec.z^2))) + angle.r = math.atan2((2 * self.Vec.y * self.Rotation) - (2 * self.Vec.z * self.Vec.x), (x_2 - (2 * self.Vec.y^2))) + end + + angle:ToDeg() + print(angle) + return angle +end + +function Quaternion:Normalize() + local scale = (self.Vec.x ^ 2) + (self.Vec.y ^ 2) + (self.Vec.z ^ 2) + (self.Rotation ^ 2) + + if (scale == 0) or (scale == 1.0) then return (scale == 1.0) end -- Because it might be a normalized already! + + scale = 1 / math.sqrt(scale) + + self.Vec.x = self.Vec.x * scale + self.Vec.y = self.Vec.y * scale + self.Vec.z = self.Vec.z * scale + + self.Rotation = self.Rotation * scale + + return true +end + +function Quaternion:AimZAxis(point_a, point_b) + local vAim = (point_b - point_a):Normalize() + + self.Vec.x = vAim.y + self.Vec.y = -vAim.x + self.Vec.z = 0 + self.Rotation = 1 + vAim.z + + if (self.Vec.x == 0) and (self.Vec.y == 0) and (self.Vec.z == 0) and (self.Rotation == 0) then -- can't norm this + return self:Reset() + else + return self:Normalize() + end +end + +-- Creates a value from spherical linear interpolation +function QuaternionSlerp(start_quat, end_quat, perc) -- THIS IS SLOWER THEN NLERP!!! + if start_quat == end_quat then return start_quat end + + local perc_a = 1 - perc + local perc_b = perc + + local theta = math.acos(a.Dot(b)); + local sin_theta = math.sin(theta); + + if sin_theta > 0.001 then + perc_a = math.sin((1 - perc) * theta ) / sin_theta + perc_b = math.sin(perc * theta) / sin_theta + end + + return ((a * perc_a) + (b * perc_b)) +end + +-- Unlike spherical interpolation, this does not rotate at a constant velocity, and it's faster to do +function QuaternionNLerp(start_quat, end_quat, perc) + if start_quat == end_quat then return start_quat end + + local new_quat = (start_quat * 1) + (end_quat * perc) + new_quat:Normalize() + + return new_quat +end diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_save_load.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_save_load.lua new file mode 100644 index 0000000..ca026df --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sh_save_load.lua @@ -0,0 +1,26 @@ +-- I REALLLY don't feel like working through this mess of networking +-- so saving/loading in multiplayer is only possible if you're the listen host. + +CatmullRomCams.SH.SaveLoad = {} + +function CatmullRomCams.SH.SaveLoad.RequestSpawn(filename) + if (not filename) or SERVER or (LocalPlayer() ~= player.GetByID(1)) then return end + + return RunConsoleCommand("~CatmullRomCams_RequestSpawn", filename) +end + +function CatmullRomCams.SH.SaveLoad.Spawn_CCmd(ply, cmd, args) + local filename = args[1] or "" + + if not file.Exists(CatmullRomCams.FilePath .. filename) then return ErrorNoHalt("Attempted to load non-existant track named '", filename, "'\n") end + + local data = util.KeyValuesToTable(file.Read(CatmullRomCams.FilePath .. filename) or "") or {} + + if not data[1] then return ErrorNoHalt("Invalid load track table given.\n") end + + for k, v in ipairs(data[1]) do + + end +end +concommand.Add("~CatmullRomCams_RequestSpawn", CatmullRomCams.SH.SaveLoad.Spawn_CCmd) + diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_duration_editor.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_duration_editor.lua new file mode 100644 index 0000000..08f6f04 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_duration_editor.lua @@ -0,0 +1,55 @@ +local STool = {} + +CatmullRomCams.SToolMethods.DurationEditor = STool + +function STool.LeftClick(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return true end + + local dur = self:GetClientNumber("duration") or 2 + + if self:GetOwner():KeyDown(IN_SPEED) and CatmullRomCams.Tracks[trace.Entity.UndoData.PID][trace.Entity.UndoData.Key][trace.Entity.UndoData.TrackIndex + 1] then + dur = CatmullRomCams.SH.UnitsToMeters(trace.Entity:GetPos():Distance(CatmullRomCams.Tracks[trace.Entity.UndoData.PID][trace.Entity.UndoData.Key][trace.Entity.UndoData.TrackIndex + 1]:GetPos())) / self:GetClientNumber("duration_mps") + end + + trace.Entity:SetNetVar("Duration", (dur > 0.001) and dur or 0.001) + + return true +end + +function STool.RightClick(self, trace) + if not self:ValidTrace(trace) then return end + + local dur = trace.Entity:GetNetVar("Duration") or 2 + + self:GetOwner():ConCommand("catmullrom_camera_duration_duration " .. ((dur > 0) and dur or 2) .. "\n") + + return true +end + +function STool.Reload(self, trace) + if not self:ValidTrace(trace) then return end + + trace.Entity:SetNetVar("Duration", 2) + + return true +end + +function STool.Think(self) + if SERVER then return end + + local ply = LocalPlayer() + local tr = ply:GetEyeTrace() + + if not self:ValidTrace(tr) then return end + + local dur = tr.Entity:GetNetVar("Duration", 0) + dur = (dur ~= 0) and dur or 2 + + AddWorldTip(tr.Entity:EntIndex(), "Duration: " .. dur, 0.5, tr.Entity:GetPos(), tr.Entity ) +end + +function STool.BuildCPanel(panel) + panel:AddControl("Slider", {Label = "Node Duration: ", Type = "Float", Min = "0.001", Max = "10", Command = "catmullrom_camera_duration_duration"}) + panel:AddControl("Slider", {Label = "Node Duration, Meters Per Seconds: ", Type = "Float", Min = "0.001", Max = "10", Command = "catmullrom_camera_duration_duration_mps"}) +end diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_hitchcock_effect.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_hitchcock_effect.lua new file mode 100644 index 0000000..f246c76 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_hitchcock_effect.lua @@ -0,0 +1,140 @@ +local STool = {} + +CatmullRomCams.SToolMethods.HitchcockEffect = STool + +local function Calc(width, end_fov) + return (width / (2 * math.tan(math.pi * (end_fov / 360)))) +end + +local function MakeLabel() + return ("Distance required for shot: " .. Calc(STool.DurationSlider:GetValue(), STool.EndFOVSlider:GetValue()) .. " Meters") +end + +local function CreateNode(ply, plyID, key, pos, ang, fov, width) + local track_index = #CatmullRomCams.Tracks[plyID][key] + 1 + print(track_index) + local camera = ents.Create("sent_catmullrom_camera") + if not (camera and camera.IsValid and camera:IsValid()) then return end + + camera:SetAngles(ang) + camera:SetPos(pos) + camera:Spawn() + + camera:SetPlayer(ply) + + CatmullRomCams.Tracks[plyID][key][track_index - 1]:DeleteOnRemove(camera) -- Because we don't want to have broken chains let's daisy chain them to self destruct + CatmullRomCams.Tracks[plyID][key][track_index - 1]:SetNetVar("ChildCamera", camera) + + CatmullRomCams.Tracks[plyID][key][track_index] = camera + + camera:SetFaceTravelDir(false) + + camera:SetBankOnTurn(false) + camera:SetBankDeltaMax(1) + camera:SetBankMultiplier(1) + + camera:SetZoom(fov) + camera:SetEnableRoll(false) + camera:SetRoll(0) + + camera:SetNetVar("MasterController", CatmullRomCams.Tracks[plyID][key][1]) + + camera.UndoData = {} + camera.UndoData.PID = plyID + camera.UndoData.Key = key + camera.UndoData.TrackIndex = track_index + + ply:AddCleanup("catmullrom_cameras", camera) + + return {camera, constraint.NoCollide(camera, CatmullRomCams.Tracks[plyID][key][1], 0, 0)} +end + +function STool.CheckDistances(pos, aim, dist, ply) + local endpos = pos + (aim * (CatmullRomCams.SH.MetersToUnits(dist) * -1 - 20)) + local trace = {} + trace.start = pos + trace.endpos = endpos + trace.mask = MASK_NPCWORLDSTATIC + + trace = util.TraceLine(trace) + + local dist_a = CatmullRomCams.SH.UnitsToMeters(pos:Distance(trace.HitPos)) + + if (trace.Fraction == 1) then return true end + print(trace.Fraction) + print(dist_a) + print(dist) + print(CatmullRomCams.SH.MetersToUnits(dist) * -1 - 20) + local TextA = "Error: You do not have enough clearance room to make this shot!" + local TextB = "Error: Clearance is " .. math.Round(dist_a) .. " of " .. math.Round(dist) .. " meters. An additional " .. ((1 - trace.Fraction) * dist) .. " meters is required." + + ply:SendLua("GAMEMODE:AddNotify('" .. TextA .. "', NOTIFY_GENERIC, 10)") + ply:SendLua("GAMEMODE:AddNotify('" .. TextB .. "', NOTIFY_GENERIC, 10)") + + ply:SendLua("surface.PlaySound('ambient/water/drip" .. math.random(1, 4) .. ".wav')") +end + +local function UndoHitchcock(ent) + if ent and ent.IsValid and ent:IsValid() and ent.SetHitchcockEffect then + ent:SetHitchcockEffect(nil) + end +end + +function STool.LeftClick(self, trace) + if not self:ValidTrace(trace) then return end + + local ply = self:GetOwner() + local plyID = ply:UniqueID() + local pos = trace.Entity:GetPos() + local aim = trace.Entity:GetForward() + local ang = trace.Entity:GetAngles() + local fov = self:GetClientNumber("endfov") + local width = self:GetClientNumber("width") + local dist = Calc(width, fov) + + if not STool.CheckDistances(pos, aim, dist, ply) then return end + if CLIENT then return true end + + local key = trace.Entity.UndoData.Key + + print(width) + trace.Entity:SetHitchcockEffect(width) + + undo.Create("CatmullRomCamsHitchcockEffect") + for i = 1, 2 do + local pos = (i == 1) and (pos + (aim * CatmullRomCams.SH.MetersToUnits(dist) * -1)) or (pos + (aim * (CatmullRomCams.SH.MetersToUnits(dist) * -1 - 20))) + local tbl = CreateNode(ply, plyID, key, pos, ang, fov, width) + + undo.AddEntity(tbl[1]) + undo.AddEntity(tbl[2]) + end + + undo.AddFunction(UndoHitchcock, trace.Entity) + undo.SetPlayer(ply) + undo.Finish() + + return true +end + +function STool.RightClick(self, trace) +end + +function STool.Reload(self, trace) +end + +function STool.Think(self) +end + +function STool.BuildCPanel(panel) + STool.DurationSlider = panel:AddControl("Slider", {Label = "Scene Width (Meters): ", Type = "Float", Min = "0.1", Max = "25", Command = "catmullrom_camera_hitchcock_width"}) + STool.EndFOVSlider = panel:AddControl("Slider", {Label = "End FOV: ", Type = "Float", Min = "0.1", Max = "110", Command = "catmullrom_camera_hitchcock_endfov"}) + STool.ETDLabel = panel:AddControl("Label", {Text = MakeLabel(), Description = "How much space do we need for this shot?"}) + + function STool.DurationSlider:OnValueChanged(val) + return STool.ETDLabel:SetText(MakeLabel()) + end + + function STool.EndFOVSlider:OnValueChanged(val) + return STool.ETDLabel:SetText(MakeLabel()) + end +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_layout.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_layout.lua new file mode 100644 index 0000000..0c1e8e8 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_layout.lua @@ -0,0 +1,209 @@ +local STool = {} + +CatmullRomCams.SToolMethods.Layout = STool +CatmullRomCams.SToolMethods.ToolObj = nil + +function STool.LeftClick(self, trace) + local ply = self:GetOwner() + + trace = {} + trace.start = ply:GetShootPos() + trace.endpos = trace.start + (ply:GetAimVector() * 99999999) + trace.filter = ply + trace = util.TraceLine(trace) + + local key = self:GetClientNumber("key") + + if key == -1 then ply:ChatPrint("You must assign a key to the camera before spawning it!") return false end + if CLIENT then return true end -- what's the point, I never seem to be able to get the client to call n STool func :crying: + + local plyID = ply:UniqueID() + + local facetraveldir = (self:GetClientNumber("facetraveldir") == 1) + + local bank_on_turn = (self:GetClientNumber("bankonturn") == 1) + local bank_delta_max = self:GetClientNumber("bankdelta_max") + local bank_multiplyer = self:GetClientNumber("bank_multi") + + local zoom = self:GetClientNumber("zoom") + + local roll_enabled = (self:GetClientNumber("enable_roll") == 1) + local roll = self:GetClientNumber("roll") + + if trace.Entity then + if trace.Entity:IsPlayer() then return end + + if (trace.Entity:GetClass() == "sent_catmullrom_camera") and (ply:GetShootPos():Distance(trace.HitPos) < 512) then + trace.Entity:SetFaceTravelDir(facetraveldir) + print(bank_on_turn) + + trace.Entity:SetBankOnTurn(bank_on_turn) + trace.Entity:SetBankDeltaMax(bank_delta_max) + trace.Entity:SetBankMultiplier(bank_multiplyer) + print(trace.Entity.BankOnTurn) + trace.Entity:SetZoom(zoom or 75) + + trace.Entity:SetEnableRoll(roll_enabled) + trace.Entity:SetRoll(roll) + + return true + end + end + + CatmullRomCams.Tracks[plyID] = CatmullRomCams.Tracks[plyID] or {} + CatmullRomCams.Tracks[plyID][key] = CatmullRomCams.Tracks[plyID][key] or {} + + local track_index = #CatmullRomCams.Tracks[plyID][key] + 1 + print(track_index) + local camera = ents.Create("sent_catmullrom_camera") + if not (camera and camera.IsValid and camera:IsValid()) then return end + + local ang = ply:EyeAngles() + ang.r = (self:GetClientNumber("enable_roll") == 1) and roll or 0 + + camera:SetAngles(ang) + camera:SetPos(trace.StartPos) + camera:Spawn() + + camera:SetPlayer(ply) + + if CatmullRomCams.Tracks[plyID][key][track_index - 1] and CatmullRomCams.Tracks[plyID][key][track_index - 1]:IsValid() then + CatmullRomCams.Tracks[plyID][key][track_index - 1]:DeleteOnRemove(camera) -- Because we don't want to have broken chains let's daisy chain them to self destruct + CatmullRomCams.Tracks[plyID][key][track_index - 1]:SetNetVar("ChildCamera", camera) + else + camera:SetNetVar("IsMasterController", true) + camera:SetNetVar("ControllingPlayer", ply) + + camera:SetKey(key) + + numpad.OnDown(ply, key, "CatmullRomCamera_Toggle", camera) + end + + CatmullRomCams.Tracks[plyID][key][track_index] = camera + + camera:SetFaceTravelDir(facetraveldir) + + camera:SetBankOnTurn(bank_on_turn) + camera:SetBankDeltaMax(bank_delta_max) + camera:SetBankMultiplier(bank_multiplyer) + + camera:SetZoom(zoom or 75) + camera:SetEnableRoll(roll_enabled) + camera:SetRoll(roll) + + camera:SetNetVar("MasterController", CatmullRomCams.Tracks[plyID][key][1]) + + camera.UndoData = {} + camera.UndoData.PID = plyID + camera.UndoData.Key = key + camera.UndoData.TrackIndex = track_index + + undo.Create("CatmullRomCamera") + undo.AddEntity(camera) + + if track_index ~= 1 then + undo.AddEntity(constraint.NoCollide(camera, CatmullRomCams.Tracks[plyID][key][1], 0, 0)) -- so the adv dup grabs everything + end + + undo.SetPlayer(ply) + undo.Finish() + + ply:AddCleanup("catmullrom_cameras", camera) + + return true, camera +end + +function STool.RightClick(self, trace) + -- Fun Fact: If you check the _G table on the server for the key 'camera' you can find the last camera + -- requested by the camera STool's Rightclick method because Garry, in his omniscience, declared + -- that it would be best if he didn't add a local in front of the variable declaration. :loleyes: + + if CLIENT then return true end + + local ply = self:GetOwner() + + if ply:KeyDown(IN_SPEED) and self:ValidTrace(trace) then -- COPY! + ply:ConCommand("catmullrom_camera_facetraveldir " .. (trace.Entity.FaceTravelDir and 1 or 0) .. "\n") + + ply:ConCommand("catmullrom_camera_bankonturn " .. (trace.Entity.BankOnTurn and 1 or 0) .. "\n") + ply:ConCommand("catmullrom_camera_bankdelta_max " .. (trace.Entity.DeltaBankMax or 1) .. "\n") + ply:ConCommand("catmullrom_camera_bank_multi " .. (trace.Entity.DeltaBankMulti or 1) .. "\n") + + ply:ConCommand("catmullrom_camera_zoom " .. (trace.Entity.Zoom or 75) .. "\n") + + ply:ConCommand("catmullrom_camera_enable_roll " .. (trace.Entity.EnableRoll and 1 or 0) .. "\n") + ply:ConCommand("catmullrom_camera_roll " .. (trace.Entity.Roll or 0) .. "\n") + + return true + end + + local _, camera = self:LeftClick(trace) + if not (camera and camera.IsValid and camera:IsValid()) then return end + + if trace.Entity:IsWorld() then + camera:SetTracking(ply, trace.Entity:WorldToLocal(trace.HitPos)) + else + camera:SetTracking(trace.Entity, trace.Entity:WorldToLocal(trace.HitPos)) + end + + return true +end + +function STool.Reload(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return true end + + local ply = self:GetOwner() + + if ply:KeyDown(IN_SPEED) then + ply:ConCommand("catmullrom_camera_facetraveldir 0\n") + + ply:ConCommand("catmullrom_camera_bankonturn 0\n") + ply:ConCommand("catmullrom_camera_bankdelta_max 1\n") + ply:ConCommand("catmullrom_camera_bank_multi 1\n") + + ply:ConCommand("catmullrom_camera_zoom 75\n") + + ply:ConCommand("catmullrom_camera_enable_roll 0\n") + ply:ConCommand("catmullrom_camera_roll 0\n") + else + trace.Entity:SetFaceTravelDir(false) + + trace.Entity:SetBankOnTurn(false) + trace.Entity:SetBankDeltaMax(1) + trace.Entity:SetBankMultiplier(1) + + trace.Entity:SetZoom(75) + + trace.Entity:SetEnableRoll(false) + trace.Entity.SetRoll(0) + end + + return true +end + +function STool.Think(self) + if SERVER then return end + + CatmullRomCams.SToolMethods.ToolObj = self -- Hackz +end + +function STool.BuildCPanel(panel) + --panel:AddControl("Header", {Text = "Catmull-Rom Cinematic Cameras: Track Layout Creator", Description = "Use this to create your track's layout!"}) + + panel:AddControl("Numpad", {Label = "Track Trigger Key: ", Command = "catmullrom_camera_key", ButtonSize = 22}) + + panel:AddControl("CheckBox", {Label = "Face Direction Of Travel: ", Description = "Should the cameras face the direction in which they are moving?", Command = "catmullrom_camera_facetraveldir"}) + + panel:AddControl("CheckBox", {Label = "Bank While Turning: ", Description = "(Requires Face-Direction-Of-Travel) Should the cameras bank/roll when they turn?", Command = "catmullrom_camera_bankonturn"}) + panel:AddControl("Slider", {Label = "Bank Delta: ", Description = "(Change Speed Max) How fast is the maximum we should be able to bank in one frame? (1 = As much as we want.)", Type = "Float", Min = "0.01", Max = "1", Command = "catmullrom_camera_bankdelta_max"}) + panel:AddControl("Slider", {Label = "Bank Multiplier: ", Description = "(Magnify Banking Effect) How much should we multiply the amount we bank in one frame? (1 = No change.)", Type = "Float", Min = "0.01", Max = "5", Command = "catmullrom_camera_bank_multi"}) + + panel:AddControl("Slider", {Label = "Zoom: ", Description = "Default is 75. Press 'USE' (typically 'e' on your keyboard) to reset Zoom & Roll.", Type = "Float", Min = ".1", Max = "110", Command = "catmullrom_camera_zoom"}) + + panel:AddControl("CheckBox", {Label = "Enable Roll: ", Description = "ROLL-UP-THE-RIM-TO-WIN! Caution! This overrides bank-on-turn.", Command = "catmullrom_camera_enable_roll"}) + panel:AddControl("Slider", {Label = "Roll: ", Description = "DO A BARREL ROLL! Beware! Make sure you add a node with '0' if you just want to make part of the track rolling;\nOtherwise the camera will jump!", Type = "Float", Min = "-180", Max = "180", Command = "catmullrom_camera_roll"}) + + --panel:AddControl("CheckBox", {Label = "Don't Stop At Track End: ", Description = "(Requires to be on Control node.) Just stay at the last position at the end of the track.", Command = "catmullrom_camera_enable_stay_on_end"}) + --panel:AddControl("CheckBox", {Label = "Loop Track: ", Description = "(Requires to be on Control node & that the option above is on.) But loop instead.", Command = "catmullrom_camera_enable_looping"}) +end diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_linker.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_linker.lua new file mode 100644 index 0000000..8657656 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_linker.lua @@ -0,0 +1,81 @@ +local STool = {} + +CatmullRomCams.SToolMethods.Linker = STool + +STool.EntA = nil + +local Hackz = {} + +function STool.LeftClick(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return true end + + local ply = self:GetOwner() + local plyID = ply:UniqueID() + + if not trace.Entity.UndoData.PID == plyID then return end + + if not Hackz[ply] then + Hackz[ply] = trace.Entity.UndoData.Key + + self:SetStage(1) + + return true + else + local newkey = trace.Entity.UndoData.Key + + local newcontroller = CatmullRomCams.Tracks[plyID][newkey][1] + local newtrackcount = #CatmullRomCams.Tracks[plyID][newkey] + + local tbl_track = {} -- so we don't instate side effects + + for k, v in ipairs(CatmullRomCams.Tracks[plyID][Hackz[ply]]) do + if k == 1 then -- remove 'is controller' flag & set it to be a child of the last node of the other track + v:SetNetVar("IsMasterController", false) + v:SetNetVar("ControllingPlayer", NULL) + + local last_cam = CatmullRomCams.Tracks[plyID][newkey][newtrackcount] + last_cam:DeleteOnRemove(v) -- Because we don't want to have broken chains let's daisy chain them to self destruct + last_cam:SetNetVar("ChildCamera", v) + end + + -- change key, change track id & update controller + v.UndoData.Key = newkey + v.UndoData.TrackIndex = newtrackcount + k + + v:SetNetVar("MasterController", newcontroller) + + tbl_track[newtrackcount + k] = v + end + + CatmullRomCams.Tracks[plyID][Hackz[ply]] = {} + + table.Merge(CatmullRomCams.Tracks[plyID][Hackz[ply]], tbl_track) + + self:SetStage(0) + Hackz[ply] = nil + + return true + end +end + +function STool.RightClick(self, trace) + if not self:ValidTrace(trace) then return end +end + +function STool.Reload(self, trace) + if self:GetStage() == 1 then + self:SetStage(0) + + Hackz[self:GetOwner()] = nil + + return true + end +end + +function STool.Think(self) + if SERVER then return end +end + +function STool.BuildCPanel(panel) +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_map_io_editor.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_map_io_editor.lua new file mode 100644 index 0000000..9ca17ba --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_map_io_editor.lua @@ -0,0 +1,52 @@ +local STool = {} + +CatmullRomCams.SToolMethods.MapIOEditor = STool + +function STool.LeftClick(self, trace) + if not self:ValidTrace(trace) then return end + + local dur = self:GetClientNumber("duration") or 2 + + trace.Entity:SetNetVar("Duration", (dur > 0.001) and dur or 0.001) + + return true +end + +function STool.RightClick(self, trace) + if not self:ValidTrace(trace) then return end + + local dur = trace.Entity:GetNetVar("Duration") or 2 + + self:GetOwner():ConCommand("catmullrom_camera_duration_duration " .. ((dur > 0.001) and dur or 0.001) .. "\n") + + return true +end + +function STool.Reload(self, trace) + if not self:ValidTrace(trace) then return end + + trace.Entity.MapIOData ={} + + return true +end + +function STool.Think(self) + if SERVER then return end + + local ply = LocalPlayer() + local trace = ply:GetEyeTrace() + + if not self:ValidTrace(trace) then return end + if not trace.Entity.MapIOData then return end + + local msg = "OnUser1: " .. (trace.Entity.MapIOData.User1 and "ACTIVE" or "INACTIVE") .. "\n" .. "OnUser2: " .. (trace.Entity.MapIOData.User2 and "ACTIVE" or "INACTIVE") .. "\n" .. "OnUser3: " .. (trace.Entity.MapIOData.User3 and "ACTIVE" or "INACTIVE") .. "\n" .. "OnUser4: " .. (trace.Entity.MapIOData.User4 and "ACTIVE" or "INACTIVE") + + AddWorldTip(trace.Entity:EntIndex(), msg, 0.5, trace.Entity:GetPos(), trace.Entity ) +end + +function STool.BuildCPanel(panel) + panel:AddControl("CheckBox", {Label = "OnUser1", Command = "catmullrom_camera_map_io_user1"}) + panel:AddControl("CheckBox", {Label = "OnUser2", Command = "catmullrom_camera_map_io_user2"}) + panel:AddControl("CheckBox", {Label = "OnUser3", Command = "catmullrom_camera_map_io_user3"}) + panel:AddControl("CheckBox", {Label = "OnUser4", Command = "catmullrom_camera_map_io_user4"}) +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_numpad_trigger.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_numpad_trigger.lua new file mode 100644 index 0000000..ac48470 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_numpad_trigger.lua @@ -0,0 +1,85 @@ +local STool = {} + +local function BuildBitFlagMessage(keys) + local key_data = 0 + + for k, v in pairs(keys) do + key_data = key_data + (2 ^ k) + end + print("Key data: ", key_data) + return key_data +end + +local function ExtractBitFlagMessage(key_data) + local keys = {} + + for i = 15, 1, -1 do + local bin_flag = 2 ^ i + + if (key_data and bin_flag) == bin_flag then + key_data = key_data - bin_flag + + keys[i] = i + end + end + + return keys +end + +CatmullRomCams.SToolMethods.NumPadTrigger = STool + +function STool.LeftClick(self, trace) + if not self:ValidTrace(trace) then return end + if not SERVER then return true end + + local key_data = self:GetClientNumber("keys") + + trace.Entity.OnNodeTriggerNumPadKey = {Keys = ExtractBitFlagMessage(key_data), Hold = (self:GetClientNumber("hold") == 1)} + + return true +end + +function STool.RightClick(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return true end + if not trace.Entity.OnNodeTriggerNumPadKey then return end + + self:GetOwner():SendLua("CatmullRomCams.SToolMethods.NumPadTrigger.CopyNumPadSettings(" .. BuildBitFlagMessage(trace.Entity.OnNodeTriggerNumPadKey.Keys) .. ");\n") + + return true +end + +function STool.CopyNumPadSettings(key_data) + return STool.CtrlNumPadMulti:SetupKeys(ExtractBitFlagMessage(key_data)) +end + +function STool.Reload(self, trace) + if not self:ValidTrace(trace) then return end + if not SERVER then return true end + + trace.Entity.OnNodeTriggerNumPadKey = nil + + return true +end + +function STool.Think(self) + if SERVER then return end + + local ply = LocalPlayer() + local tr = ply:GetEyeTrace() + + if not self:ValidTrace(tr) then return end + if not tr.Entity.OnNodeTriggerNumPadKey then return end + + --AddWorldTip(tr.Entity:EntIndex(), "Key To Trigger: " .. tostring(tr.Entity.OnNodeTriggerNumPadKey), 0.5, tr.Entity:GetPos(), tr.Entity) +end + +function STool.BuildCPanel(panel) + STool.CtrlNumPadMulti = vgui.Create("CtrlNumPadMulti", panel) -- DNumPadMulti + STool.CtrlNumPadMulti:SetLabel("Keys To Trigger Then Node Is Reached: ") + STool.CtrlNumPadMulti:SetConVar("catmullrom_camera_numpad_trigger_keys") + panel:AddPanel(STool.CtrlNumPadMulti) + + panel:AddControl("CheckBox", {Label = "Hold Key", Description = "Should the just toggle the key or should it press it and then release it? (Careful with this!)", Command = "catmullrom_camera_numpad_trigger_hold"}) +end + diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_smart_look.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_smart_look.lua new file mode 100644 index 0000000..15a73ec --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/stools/sh_smart_look.lua @@ -0,0 +1,84 @@ +local STool = {} + +CatmullRomCams.SToolMethods.SmartLook = STool + +function STool.LeftClick(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return true end + + trace.Entity:SetSmartLook(true) + trace.Entity:SetSmartLookPerc(self:GetClientNumber("percent")) + trace.Entity:SetSmartLookRange(self:GetClientNumber("range") or 512) + trace.Entity:SetSmartLookTraceFilter(self:GetClientNumber("block") or 0) + trace.Entity:SetSmartLookTargetFilter(self:GetClientNumber("target") or 1) + trace.Entity:SetSmartLookClosest(self:GetClientNumber("closest") == 1) + + return true +end + +function STool.RightClick(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return end + + --return true +end + +function STool.Reload(self, trace) + if not self:ValidTrace(trace) then return end + if CLIENT then return true end + + trace.Entity:SetSmartLook(false) + trace.Entity:SetSmartLookPerc(1) + trace.Entity:SetSmartLookRange(512) + trace.Entity:SetSmartLookTraceFilter(2) + trace.Entity:SetSmartLookTargetFilter(1) + + return true +end + +function STool.Think(self) +end + +function STool.BuildCPanel(panel) + local block_filter_listbox = {} + block_filter_listbox.Label = "Filters For LOS: " + block_filter_listbox.Options = {} + block_filter_listbox.Options["NPC Static"] = {catmullrom_camera_looker_block = MASK_NPCWORLDSTATIC} + block_filter_listbox.Options["Solid-To-NPC"] = {catmullrom_camera_looker_block = MASK_NPCSOLID_BRUSHONLY} + block_filter_listbox.Options["Solid-To-Player"] = {catmullrom_camera_looker_block = MASK_PLAYERSOLID_BRUSHONLY} + block_filter_listbox.Options["Solid Brush"] = {catmullrom_camera_looker_block = MASK_SOLID_BRUSHONLY} + block_filter_listbox.Options["Shot Hull"] = {catmullrom_camera_looker_block = MASK_SHOT_HULL} + block_filter_listbox.Options["Shot"] = {catmullrom_camera_looker_block = MASK_SHOT} + block_filter_listbox.Options["Opaque"] = {catmullrom_camera_looker_block = MASK_OPAQUE} + block_filter_listbox.Options["Water"] = {catmullrom_camera_looker_block = MASK_WATER} + block_filter_listbox.Options["NPC Solid"] = {catmullrom_camera_looker_block = MASK_NPCSOLID} + block_filter_listbox.Options["Player Solid"] = {catmullrom_camera_looker_block = MASK_PLAYERSOLID} + block_filter_listbox.Options["Solid"] = {catmullrom_camera_looker_block = MASK_SOLID} + block_filter_listbox.Options["Everything"] = {catmullrom_camera_looker_block = MASK_ALL} + block_filter_listbox.Options["Nothing"] = {catmullrom_camera_looker_block = 0} + + local target_filter_listbox = {} + target_filter_listbox.Label = "Filters For Potential Targets: " + target_filter_listbox.Options = {} + target_filter_listbox.Options["Anything"] = {catmullrom_camera_looker_target = 1} + target_filter_listbox.Options["Fire + Ignited Stuff"] = {catmullrom_camera_looker_target = 2} + target_filter_listbox.Options["Props"] = {catmullrom_camera_looker_target = 3} + target_filter_listbox.Options["Has Physics Object"] = {catmullrom_camera_looker_target = 4} + target_filter_listbox.Options["NPCs"] = {catmullrom_camera_looker_target = 5} + target_filter_listbox.Options["Players"] = {catmullrom_camera_looker_target = 6} + target_filter_listbox.Options["Light Entities"] = {catmullrom_camera_looker_target = 7} + target_filter_listbox.Options["Particle Systems"] = {catmullrom_camera_looker_target = 8} + + panel:AddControl("Label", {Text = "Filters For LOS: ", Description = "What should block our line of sight check?"}) + panel:AddControl("listbox", block_filter_listbox) + + panel:AddControl("Label", {Text = "Filters For Potential Targets: ", Description = "What sort of things should we look for?"}) + panel:AddControl("listbox", target_filter_listbox) + + panel:AddControl("Slider", {Label = "Percent: ", Description = "How much should we apply the look?", Type = "Float", Min = ".01", Max = "1", Command = "catmullrom_camera_looker_percent"}) + panel:AddControl("Slider", {Label = "Range: ", Description = "How far should we look?", Type = "Float", Min = "32", Max = "4096", Command = "catmullrom_camera_looker_range"}) + panel:AddControl("CheckBox", {Label = "Closest Target", Description = "Do I NEEED to look for the closest target? Or can I just pick the first one I find?", Command = "catmullrom_camera_looker_closest"}) +end + + + diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_adv_dup_paste.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_adv_dup_paste.lua new file mode 100644 index 0000000..505524a --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_adv_dup_paste.lua @@ -0,0 +1,10 @@ + +--local Hackz = {} + +function CatmullRomCams.SV.AdvDupPaste(self, Player, plyID, Ent, CreatedEntities) + Ent.EntityMods.CatmullRomCamsDupData.UndoData.PID = plyID + + self:ApplySaveData(Player, plyID, CreatedEntities, Ent.EntityMods.CatmullRomCamsDupData) + + return Player:AddCleanup("catmullrom_cameras", Ent) +end diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_icon_resource.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_icon_resource.lua new file mode 100644 index 0000000..c44b924 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_icon_resource.lua @@ -0,0 +1,29 @@ +resource.AddFile("materials/gui/silkicons/camera.vmt") +resource.AddFile("materials/gui/silkicons/camera.vtf") + +resource.AddFile("materials/gui/silkicons/camera_add.vmt") +resource.AddFile("materials/gui/silkicons/camera_add.vtf") + +resource.AddFile("materials/gui/silkicons/camera_delete.vmt") +resource.AddFile("materials/gui/silkicons/camera_delete.vtf") + +resource.AddFile("materials/gui/silkicons/camera_edit.vmt") +resource.AddFile("materials/gui/silkicons/camera_edit.vtf") + +resource.AddFile("materials/gui/silkicons/camera_error.vmt") +resource.AddFile("materials/gui/silkicons/camera_error.vtf") + +resource.AddFile("materials/gui/silkicons/camera_go.vmt") +resource.AddFile("materials/gui/silkicons/camera_go.vtf") + +resource.AddFile("materials/gui/silkicons/camera_link.vmt") +resource.AddFile("materials/gui/silkicons/camera_link.vtf") + +resource.AddFile("materials/gui/silkicons/camera_small.vmt") +resource.AddFile("materials/gui/silkicons/camera_small.vtf") + +resource.AddFile("materials/gui/silkicons/folder_camera.vmt") +resource.AddFile("materials/gui/silkicons/folder_camera.vtf") + +resource.AddFile("materials/gui/silkicons/page_white_camera.vmt") +resource.AddFile("materials/gui/silkicons/page_white_camera.vtf") diff --git a/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_saverestore.lua b/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_saverestore.lua new file mode 100644 index 0000000..bc2e930 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/catmullromcams/sv_saverestore.lua @@ -0,0 +1,40 @@ +do return end -- BROKEN PIECE OF SHIT! :argh: GARRRRRYYYYYYYYYYYYYY!!!!!!!!!!!! +function CatmullRomCams.SV.Save(save_data) + if not CatmullRomCams.Tracks[player.GetByID(1):UniqueID()] then return end + + local SaveGameData = {} -- I'm assuming here that you can only save games in singleplayer. Tell me if I'm wrong though! :V + + for numpad_key, track in pairs(CatmullRomCams.Tracks[player.GetByID(1):UniqueID()]) do + SaveGameData[numpad_key] = 1--{} + if false then + for index, node in ipairs(track) do + SaveGameData[numpad_key][index] = {Ent = node:EntIndex(), Data = node:RequestSaveData(true)} + end + end + end + + return saverestore.WriteTable(SaveGameData, save_data) +end + +function CatmullRomCams.SV.Restore(restore_data) + local SavedGameData = saverestore.ReadTable(restore_data) + local plyID = player.GetByID(1):UniqueID() + PrintTable(SavedGameData) + + for numpad_key, track in pairs(SavedGameData) do + if false then + CatmullRomCams.Tracks[plyID][numpad_key] = {} + + for index, data in ipairs(track) do + CatmullRomCams.Tracks[plyID][numpad_key][index] = ents.GetByIndex(node.Ent) + CatmullRomCams.Tracks[plyID][numpad_key][index]:ApplyEngineSaveData(node.Data, index == 1) + + print("Loaded ", CatmullRomCams.Tracks[plyID][numpad_key][index], "'s saverestore data.\nDumping:") + PrintTable(node.Data) + end + end + end +end + +saverestore.AddSaveHook( "CatmullRomCams_SaveRestore", CatmullRomCams.SV.Save) +saverestore.AddRestoreHook("CatmullRomCams_SaveRestore", CatmullRomCams.SV.Restore) diff --git a/garrysmod/addons/gmod-tools/lua/cl_dmultinumpad_dev.lua b/garrysmod/addons/gmod-tools/lua/cl_dmultinumpad_dev.lua new file mode 100644 index 0000000..fd54eaa --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/cl_dmultinumpad_dev.lua @@ -0,0 +1,64 @@ +DNumPadMultiHackz = {} + +function DNumPadMultiHackz.Add(self, key_val, pButton) + local bin_flag = 2 ^ key_val + print("Add") + print("Key: ", key_val, "; Flag: ", bin_flag) + print(self.m_KeyFlags & bin_flag) + pButton.LastToggle = CurTime() + .1 -- hackz + print("---") + + if (self.m_KeyFlags & bin_flag) ~= bin_flag then + self.m_KeyFlags = self.m_KeyFlags + bin_flag + + self:UpdateConVar() + end +end + +function DNumPadMultiHackz.Remove(self, key_val, pButton) + local bin_flag = 2 ^ key_val + print(pButton.LastToggle) + print(CurTime()) + if pButton.LastToggle > CurTime() then return end + print("Remove") + print("Key: ", key_val, "; Flag: ", bin_flag) + print(self.m_KeyFlags & bin_flag) + + + print("---") + --debug.Trace() + if (self.m_KeyFlags & bin_flag) == bin_flag then + self.m_KeyFlags = self.m_KeyFlags - bin_flag + + self:UpdateConVar() + end +end + +function DNumPadMultiHackz.Update(self) + if self.m_CVarName then + print("Update: ", self.m_KeyFlags) + RunConsoleCommand(self.m_CVarName, self.m_KeyFlags) + print("---") + end +end + +function DNumPadMultiHackz.Set(self, data) + self:Clear() + + for k, _ in pairs(data) do + self.Buttons[k]:DoClick() -- ? + print("Setting ", k, "...\n") + end +end + +function DNumPadMultiHackz.Press(self, pButton, iButtonNumber) + + + pButton.WasSelectedActive = not pButton.WasSelectedActive + + pButton:SetSelected(pButton.WasSelectedActive) + + local hackz = (pButton.WasSelectedActive and DNumPadMultiHackz.Add(self, iButtonNumber, pButton) or DNumPadMultiHackz.Remove(self, iButtonNumber, pButton)) + --end +end + diff --git a/garrysmod/addons/gmod-tools/lua/cl_hack_check_vgui_type.lua b/garrysmod/addons/gmod-tools/lua/cl_hack_check_vgui_type.lua new file mode 100644 index 0000000..15e244e --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/cl_hack_check_vgui_type.lua @@ -0,0 +1,7 @@ +local oldvguicreate = vgui.Create + +function vgui.Create(...) + print(arg[1]) + + return oldvguicreate(unpack(arg)) +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/ent_bonemerged.lua b/garrysmod/addons/gmod-tools/lua/entities/ent_bonemerged.lua new file mode 100644 index 0000000..fdf9c3f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/ent_bonemerged.lua @@ -0,0 +1,19 @@ + +AddCSLuaFile() + +ENT.Base = "base_anim" +ENT.Type = "anim" +ENT.RenderGroup = RENDERGROUP_BOTH + +--[[ +function ENT:Initialize() end +function ENT:Think() end +function ENT:PreEntityCopy() end +function ENT:PostEntityCopy() end +function ENT:PostEntityPaste() end +]] + +if ( SERVER ) then return end + +function ENT:Draw() self:DrawModel() end +function ENT:DrawTranslucent() self:Draw() end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/gmod_advteleporter.lua b/garrysmod/addons/gmod-tools/lua/entities/gmod_advteleporter.lua new file mode 100644 index 0000000..7c78773 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/gmod_advteleporter.lua @@ -0,0 +1,372 @@ +-- Adv. Teleporter +-- By Anya O'Quinn / Slade Xanthas + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Base = "base_anim" +ENT.PrintName = "Teleporter" + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "Destination") + self:NetworkVar("Float", 0, "TeleUniqueID") + self:NetworkVar("Float", 1, "TeleDestinationUniqueID") + self:NetworkVar("Int", 0, "TeleRadius", {KeyName = "teleradius"}) + self:NetworkVar("String", 0, "TeleSound", {KeyName = "telesound"}) + self:NetworkVar("String", 1, "TeleEffect", {KeyName = "Teleeffect"}) + self:NetworkVar("Bool", 0, "TeleOnUse", {KeyName = "teleonuse"}) + self:NetworkVar("Bool", 1, "TeleOnTouch", {KeyName = "teleontouch"}) + self:NetworkVar("Bool", 2, "TeleShowBeam", {KeyName = "teleshowbeam"}) + self:NetworkVar("Bool", 3, "TeleShowRadius", {KeyName = "teleshowradius"}) + self:NetworkVar("Bool", 4, "TeleShake", {KeyName = "teleshake"}) + self:NetworkVar("Int", 5, "TeleDelay", {KeyName = "teledelay"}) + self:NetworkVar("Int", 6, "TeleHeight", {KeyName = "teleheight"}) +end + +if CLIENT then + + killicon.Add("gmod_advteleporter", "effects/killicons/gmod_advteleporter", color_white ) + + function ENT:Initialize() + + self.Mat = Material("sprites/tp_beam001") + self.Sprite = Material("sprites/blueglow2") + self.LinkedColor = Color(255,255,255,255) + self.UnlinkedColor = Color(255,255,255,255) + + self.RadiusSphere = octolib.createDummy("models/hunter/misc/shell2x2.mdl", RENDERGROUP_OPAQUE) + + if IsValid(self.RadiusSphere) then + self.RadiusSphere:SetNoDraw(true) + self.RadiusSphere:SetPos(self:LocalToWorld(self:OBBCenter())) + self.RadiusSphere:SetParent(self) + end + + end + + function ENT:Draw() + + self:DrawModel() + + local Destination = self:GetDestination() + + if IsValid(self) and IsValid(self.RadiusSphere) then + + if self:GetTeleRadius() and self:GetTeleShowRadius() then + + render.SuppressEngineLighting(true) + + if IsValid(Destination) and IsValid(Destination:GetDestination()) and (self == Destination:GetDestination()) then + render.SetColorModulation(2,2,2) + else + render.SetColorModulation(2,2,2) + end + + render.SetBlend(1) + self.RadiusSphere:DrawModel() + render.SuppressEngineLighting(false) + render.SetBlend(1) + render.SetColorModulation(1,1,1) + self.RadiusSphere:SetModelScale(self:GetTeleRadius() / 45,0) + self.RadiusSphere:SetMaterial("effects/combineshield/comshieldwall3") + + end + + end + + if IsValid(self) and IsValid(Destination) and isfunction(Destination.GetDestination) and IsValid(Destination:GetDestination()) and (self == Destination:GetDestination()) and self:GetTeleShowBeam() and Destination:GetTeleShowBeam() then + render.SetMaterial(self.Mat) + render.DrawBeam(self:LocalToWorld(self:OBBCenter()), Destination:LocalToWorld(Destination:OBBCenter()), 6, 6, 0, self.LinkedColor) + render.SetMaterial(self.Sprite) + local rand = math.random(-3,3) + render.DrawSprite(self:LocalToWorld(self:OBBCenter()), 6 + rand, 6 + rand, self.LinkedColor) + render.DrawSprite(Destination:LocalToWorld(Destination:OBBCenter()), 6 + rand, 6 + rand, Destination.LinkedColor) + self:SetRenderBoundsWS(self:GetPos(), Destination:GetPos()) + elseif IsValid(self) then + self:SetRenderBoundsWS(self:GetPos() + self:OBBMins(),self:GetPos() + self:OBBMaxs()) + end + + end + + function ENT:OnRemove() + if IsValid(self.RadiusSphere) then + self.RadiusSphere:Remove() + self.RadiusSphere = nil + end + end + +end + +if SERVER then + + function ENT:Initialize() + + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.NextTeleport = CurTime() + + if Wire_CreateInputs then + self.Inputs = Wire_CreateInputs(self, {"Teleport","Lock"}) + end + + self.tEnts = {} + + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + if self:GetTeleUniqueID() == 0 then + self:SetTeleUniqueID(RealTime() + self:EntIndex()) + end + + self:LinkUp() + + end + + function ENT:LinkUp() + + if IsValid(self:GetDestination()) then return end + + if self:GetTeleDestinationUniqueID() ~= 0 then + + for _,v in pairs(ents.FindByClass(self:GetClass())) do + if IsValid(v) + and v ~= self + and v:GetTeleUniqueID() ~= 0 + and v:GetTeleDestinationUniqueID() ~= 0 + and self:GetTeleDestinationUniqueID() == v:GetTeleUniqueID() + and v:GetTeleDestinationUniqueID() == self:GetTeleUniqueID() + then + self:SetDestination(v) + v:SetDestination(self) + self.NextTeleport = CurTime() + self:GetTeleDelay() + v.NextTeleport = CurTime() + self:GetTeleDelay() + break + end + end + + else + + for _,v in pairs(ents.FindByClass(self:GetClass())) do + + if IsValid(self) + and IsValid(v) + and v ~= self + and self.TeleKey + and v.TeleKey + and v.TeleKey == self.TeleKey + and v:CPPIGetOwner() == self:CPPIGetOwner() + and not IsValid(self:GetDestination()) + and not IsValid(v:GetDestination()) + then + self:SetDestination(v) + v:SetDestination(self) + self:SetTeleDestinationUniqueID(v:GetTeleUniqueID()) + v:SetTeleDestinationUniqueID(self:GetTeleUniqueID()) + self.NextTeleport = CurTime() + self:GetTeleDelay() + v.NextTeleport = CurTime() + self:GetTeleDelay() + break + end + + end + + end + + end + + function ENT:PreEntityCopy() + local dupeInfo = {} + if IsValid(self) and IsValid(self:GetDestination()) then + dupeInfo.DestinationID = self:GetDestination():EntIndex() + duplicator.StoreEntityModifier(self, "DestinationDupeInfo", dupeInfo) + end + end + + function ENT:PostEntityPaste(pl, Ent, CreatedEntities) + if not Ent.EntityMods then return end + self:SetDestination(CreatedEntities[Ent.EntityMods.DestinationDupeInfo.DestinationID]) + end + + function ENT:Setup(model, sound, effect, radius, ontouch, onuse, showbeam, showradius, shake, delay, height, key) + + self:SetTeleSound(sound) + self:SetTeleEffect(effect) + self:SetTeleOnTouch(ontouch) + self:SetTeleOnUse(onuse) + self:SetTeleRadius(radius) + self:SetTeleShowBeam(showbeam) + self:SetTeleShowRadius(showradius) + self:SetTeleShake(shake) + self:SetTeleDelay(delay) + self:SetTeleHeight(height) + + if not self.TeleKey then + self.TeleKey = key + end + + if not self:GetModel() then + self:SetModel(model) + end + + if not IsValid(self:GetDestination()) then + self:LinkUp() + end + + if IsValid(self:GetDestination()) and self:GetTeleShowBeam() ~= self:GetDestination():GetTeleShowBeam() then + self:GetDestination():SetTeleShowBeam(self:GetTeleShowBeam()) + end + + end + + function ENT:Teleport(ent) + + if not IsValid(self) or not IsValid(self:GetDestination()) or not IsValid(ent) then return end + + local dest = self:GetDestination() + local oldAng = ent:IsPlayer() and ent:EyeAngles() or ent:GetAngles() + local relPos, relAng = WorldToLocal(ent:GetPos(), oldAng, self:GetPos(), self:GetAngles()) + self:EmitSound(self:GetTeleSound()) + + local fx = EffectData() + fx:SetScale(3) + fx:SetRadius(3) + fx:SetMagnitude(7) + fx:SetEntity(self) + fx:SetOrigin(self:GetPos()) + util.Effect(self:GetTeleEffect(), fx, true, true) + fx:SetEntity(dest) + fx:SetOrigin(dest:GetPos()) + util.Effect(dest:GetTeleEffect(), fx, true, true) + + timer.Simple(0, function() + local newPos, newAng = LocalToWorld(relPos, relAng, dest:GetPos(), dest:GetAngles()) + ent:SetPos(newPos) + if ent:IsPlayer() then + ent:SetEyeAngles(newAng) + else + ent:SetAngles(newAng) + end + + table.insert(self.tEnts,ent) + table.insert(dest.tEnts,ent) + + if self:GetTeleShake() then + util.ScreenShake(self.Entity:GetPos(),5,5,1,self:GetTeleRadius()*5) + util.ScreenShake(self.Entity:GetDestination():GetPos(),5,5,1,self:GetTeleRadius()*5) + end + + dest:EmitSound(dest:GetTeleSound()) + end) + end + + function ENT:Use(activator,caller) + if IsValid(activator) and activator:IsPlayer() then + self.Activator = activator + self.IsBeingUsed = true + end + end + + function ENT:Think() + + if not IsValid(self) then return end + + local dest = self:GetDestination() + if IsValid(dest) then + + self:SetDestination(dest) + + local area = ents.FindInSphere(self:GetPos(),self:GetTeleRadius()) + + for i,ent in ipairs(area) do + + if IsValid(ent) + and (ent:IsPlayer() or ent:IsNPC() or ent.Type == "nextbot") + and ent ~= self + and ent ~= dest + and not table.HasValue(self.tEnts,ent) + and not table.HasValue(dest.tEnts,ent) + and (self:GetTeleOnTouch() or self.KeyOn or self.WireTeleport or (self:GetTeleOnUse() and self.IsBeingUsed)) + and not dest.Locked + and self.NextTeleport < CurTime() + and dest.NextTeleport < CurTime() then + self:Teleport(ent) + self.NextTeleport = CurTime() + 0.1 + self:GetTeleDelay() + end + + end + + self.IsBeingUsed = false + + for i,v in pairs(self.tEnts) do + if not table.HasValue(area,v) then + self.tEnts[i] = nil + end + end + + end + + if self.IsBeingUsed and IsValid(self.Activator) and self.Activator:IsPlayer() and (self.Activator:KeyReleased(IN_USE) or not self.Activator:GetEyeTraceNoCursor().Entity == self) then + self.IsBeingUsed = false + end + + if IsValid(dest) then + self:NextThink(CurTime() + 1) + else + self:LinkUp() + self:NextThink(CurTime() + 2) + end + + return true + + end + + local function On(pl, ent) + if not IsValid(ent) then return end + ent.KeyOn = true + return true + end + + local function Off(pl, ent) + if not IsValid(ent) then return end + ent.KeyOn = false + return true + end + + numpad.Register("Teleporter_On", On) + numpad.Register("Teleporter_Off", Off) + + function ENT:TriggerInput(iname, value) + + if iname == "Teleport" then + + if value == 1 then + self.WireTeleport = true + end + + if value == 0 and self.WireTeleport then + self.WireTeleport = false + end + + end + + if iname == "Lock" then + + if value == 1 and not self.Locked then + self.Locked = true + end + + if value == 0 and self.Locked then + self.Locked = false + end + + end + + end + +end + +-- 37062385 diff --git a/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/cl_init.lua new file mode 100644 index 0000000..a2eab73 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/cl_init.lua @@ -0,0 +1,6 @@ +include("shared.lua") + +function ENT:Draw() + self.BaseClass.Draw(self) + self.Entity:DrawModel() +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/init.lua b/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/init.lua new file mode 100644 index 0000000..1de4f65 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/init.lua @@ -0,0 +1,298 @@ +--[[ + Title: Adv. Dupe 2 Contraption Spawner + + Desc: A mobile duplicator + + Author: TB + + Version: 1.0 +]] + + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) +if(WireLib)then + include('entities/base_wire_entity.lua') +end +include('shared.lua') + + +function ENT:Initialize() + + self.Entity:SetMoveType( MOVETYPE_NONE ) + self.Entity:PhysicsInit( SOLID_VPHYSICS ) + self.Entity:SetCollisionGroup( COLLISION_GROUP_WORLD ) + self.Entity:DrawShadow( false ) + + local phys = self.Entity:GetPhysicsObject() + if phys:IsValid() then + phys:Wake() + end + + self.UndoList = {} + self.Ghosts = {} + + self.SpawnLastValue = 0 + self.UndoLastValue = 0 + + self.LastSpawnTime = 0 + + self.CurrentPropCount = 0 + + if WireLib then + self.Inputs = Wire_CreateInputs(self.Entity, {"Spawn", "Undo"}) + self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"Out"}, { "NORMAL" }) + end +end + + + +/*-----------------------------------------------------------------------* + * Sets options for this spawner + *-----------------------------------------------------------------------*/ +function ENT:SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel, hideprops ) + + self.delay = delay + self.undo_delay = undo_delay + + --Key bindings + self.key = key + self.undo_key = undo_key + + numpad.Remove( self.CreateKey ) + numpad.Remove( self.UndoKey ) + self.CreateKey = numpad.OnDown( ply, self.key, "ContrSpawnerCreate", self.Entity, true ) + self.UndoKey = numpad.OnDown( ply, self.undo_key, "ContrSpawnerUndo", self.Entity, true ) + self.DisableGravity = disgrav + self.DisableDrag = disdrag + self.AddVelocity = addvel + self.HideProps = hideprops + + self:ShowOutput() +end + +function ENT:UpdateOptions( options ) + self:SetOptions( options["delay"], options["undo_delay"], options["key"], options["undo_key"]) +end + + +function ENT:AddGhosts() + if self.HideProps then return end + local moveable = self:GetPhysicsObject():IsMoveable() + self:GetPhysicsObject():EnableMotion(false) + local EntTable + local GhostEntity + local Offset = self.DupeAngle - self.EntAngle + local Phys + for EntIndex,v in pairs(self.EntityTable)do + if(EntIndex!=self.HeadEnt)then + if(self.EntityTable[EntIndex].Class=="gmod_contr_spawner")then self.EntityTable[EntIndex] = nil continue end + EntTable = table.Copy(self.EntityTable[EntIndex]) + if(EntTable.BuildDupeInfo && EntTable.BuildDupeInfo.PhysicsObjects)then + Phys = EntTable.BuildDupeInfo.PhysicsObjects[0] + else + if(!v.BuildDupeInfo)then v.BuildDupeInfo = {} end + v.BuildDupeInfo.PhysicsObjects = table.Copy(v.PhysicsObjects) + Phys = EntTable.PhysicsObjects[0] + end + + GhostEntity = nil + + if(EntTable.Model==nil || !util.IsValidModel(EntTable.Model)) then EntTable.Model="models/error.mdl" end + + if ( EntTable.Model:sub( 1, 1 ) == "*" ) then + GhostEntity = ents.Create( "func_physbox" ) + else + GhostEntity = ents.Create( "gmod_ghost" ) + end + + // If there are too many entities we might not spawn.. + if ( !GhostEntity || GhostEntity == NULL ) then return end + + duplicator.DoGeneric( GhostEntity, EntTable ) + + GhostEntity:Spawn() + + GhostEntity:DrawShadow( false ) + GhostEntity:SetMoveType( MOVETYPE_NONE ) + GhostEntity:SetSolid( SOLID_VPHYSICS ); + GhostEntity:SetNotSolid( true ) + GhostEntity:SetRenderMode( RENDERMODE_TRANSALPHA ) + GhostEntity:SetColor( Color(255, 255, 255, 150) ) + + GhostEntity:SetAngles(Phys.Angle) + GhostEntity:SetPos(self:GetPos() + Phys.Pos - self.Offset) + self:SetAngles(self.EntAngle) + GhostEntity:SetParent( self ) + self:SetAngles(self.DupeAngle) + self.Ghosts[EntIndex] = GhostEntity + end + end + self:SetAngles(self.DupeAngle) + self:GetPhysicsObject():EnableMotion(moveable) +end + +function ENT:GetCreationDelay() return self.delay end +function ENT:GetDeletionDelay() return self.undo_delay end + +function ENT:OnTakeDamage( dmginfo ) self.Entity:TakePhysicsDamage( dmginfo ) end + +function ENT:SetDupeInfo( HeadEnt, EntityTable, ConstraintTable ) + self.HeadEnt = HeadEnt + self.EntityTable = EntityTable + self.ConstraintTable = ConstraintTable + if(!self.DupeAngle)then self.DupeAngle = self:GetAngles() end + if(!self.EntAngle)then self.EntAngle = EntityTable[HeadEnt].PhysicsObjects[0].Angle end + if(!self.Offset)then self.Offset = self.EntityTable[HeadEnt].PhysicsObjects[0].Pos end + + local headpos, headang = EntityTable[HeadEnt].PhysicsObjects[0].Pos, EntityTable[HeadEnt].PhysicsObjects[0].Angle + for k, v in pairs(EntityTable) do + for o, p in pairs(v.PhysicsObjects) do + p.LPos, p.LAngle = WorldToLocal(p.Pos, p.Angle, headpos, headang) + end + end +end + + + + +function ENT:DoSpawn( ply ) + for k, v in pairs(self.EntityTable) do + for o, p in pairs(v.PhysicsObjects) do + p.Pos, p.Angle = self:LocalToWorld(p.LPos), self:LocalToWorldAngles(p.LAngle) + end + end + + /*local AngleOffset = self.EntAngle + AngleOffset = self:GetAngles() - AngleOffset + local AngleOffset2 = Angle(0,0,0) + //AngleOffset2.y = AngleOffset.y + AngleOffset2:RotateAroundAxis(self:GetUp(), AngleOffset.y) + AngleOffset2:RotateAroundAxis(self:GetRight(),AngleOffset.p) + AngleOffset2:RotateAroundAxis(self:GetForward(),AngleOffset.r)*/ + + local Ents, Constrs = AdvDupe2.duplicator.Paste(ply, self.EntityTable, self.ConstraintTable, nil, nil, Vector(0,0,0), true) + local i = #self.UndoList+1 + self.UndoList[i] = Ents + + undo.Create("contraption_spawns") + local phys + for k,ent in pairs(Ents)do + phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + if(self.DisableGravity==1)then phys:EnableGravity(false) end + if(self.DisableDrag==1)then phys:EnableDrag(false) end + phys:EnableMotion(true) + if(ent.SetForce)then ent.SetForce(ent, ent.force, ent.mul) end + if(self.AddVelocity==1)then + phys:SetVelocity( self:GetVelocity() ) + phys:AddAngleVelocity( self:GetPhysicsObject():GetAngleVelocity() ) + end + end + + undo.AddEntity(ent) + end + + undo.SetPlayer(ply) + undo.Finish() + + if(self.undo_delay>0)then + timer.Simple(self.undo_delay, function() + if(self.UndoList && self.UndoList[i])then + for k,ent in pairs(self.UndoList[i]) do + if(IsValid(ent)) then + ent:Remove() + end + end + end + end) + end + +end + + + +function ENT:DoUndo( ply ) + + if(!self.UndoList || #self.UndoList == 0)then return end + + local entities = self.UndoList[ #self.UndoList ] + self.UndoList[ #self.UndoList ] = nil + for _,ent in pairs(entities) do + if (IsValid(ent)) then + ent:Remove() + end + end +end + +function ENT:TriggerInput(iname, value) + local ply = self:GetPlayer() + + if(iname == "Spawn")then + if ((value > 0) == self.SpawnLastValue) then return end + self.SpawnLastValue = (value > 0) + + if(self.SpawnLastValue)then + local delay = self:GetCreationDelay() + if (delay == 0) then self:DoSpawn( ply ) return end + if(CurTime() < self.LastSpawnTime)then return end + self:DoSpawn( ply ) + self.LastSpawnTime=CurTime()+delay + end + elseif (iname == "Undo") then + // Same here + if((value > 0) == self.UndoLastValue)then return end + self.UndoLastValue = (value > 0) + + if(self.UndoLastValue)then self:DoUndo(ply) end + end +end + +local text2 = {"Enabled", "Disabled"} +function ENT:ShowOutput() + local text = "\nGravity: " + if(self.DisableGravity==1)then text=text.."Enabled" else text=text.."Disabled" end + text=text.."\nDrag: " + if(self.DisableDrag==1)then text=text.."Enabled" else text=text.."Disabled" end + text=text.."\nVelocity: " + if(self.AddVelocity==1)then text=text.."Enabled" else text=text.."Disabled" end + + self.Entity:SetOverlayText( + "Spawn Delay: " .. tostring(self:GetCreationDelay()) .. + "\nUndo Delay: ".. tostring(self:GetDeletionDelay()) .. + text + ) + +end + + +/*-----------------------------------------------------------------------* + * Handler for spawn keypad input + *-----------------------------------------------------------------------*/ +function SpawnContrSpawner( ply, ent ) + + if (!ent || !ent:IsValid()) then return end + + local delay = ent:GetTable():GetCreationDelay() + + if(delay == 0) then + ent:DoSpawn( ply ) + return + end + + if(CurTime() < ent.LastSpawnTime)then return end + ent:DoSpawn( ply ) + ent.LastSpawnTime=CurTime()+delay +end + +/*-----------------------------------------------------------------------* + * Handler for undo keypad input + *-----------------------------------------------------------------------*/ +function UndoContrSpawner( ply, ent ) + if (!ent || !ent:IsValid()) then return end + ent:DoUndo( ply, true ) +end + +numpad.Register( "ContrSpawnerCreate", SpawnContrSpawner ) +numpad.Register( "ContrSpawnerUndo", UndoContrSpawner ) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/shared.lua new file mode 100644 index 0000000..bfc1947 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/gmod_contr_spawner/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = WireLib and "base_wire_entity" or "base_gmodentity" +ENT.PrintName = "Contraption Spawner" +ENT.Author = "TB" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Spawnable = false +ENT.AdminSpawnable = false diff --git a/garrysmod/addons/gmod-tools/lua/entities/imgscreen/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/imgscreen/cl_init.lua new file mode 100644 index 0000000..dfd0039 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/imgscreen/cl_init.lua @@ -0,0 +1,56 @@ +include 'shared.lua' +ENT.RenderGroup = RENDERGROUP_BOTH + +local dist, distFade = 2000 * 2000, 300 * 300 +local posOff, angOff = Vector(0, 0, 0.5), Angle(0, 90, 0) + +function ENT:Draw() + + local data = self:GetNetVar('img') + if not data then return end + + local al = math.Clamp(1 - (self:GetPos():DistToSqr(EyePos()) - (data[5] or distFade)) / dist, 0, 1) + if al <= 0 then return end + + + local url, w, h, col = unpack(data) + url = url or '' + w = w or 16 + h = h or 16 + col = col or color_white + + local mat = octolib.getURLMaterial(url) + if mat == octolib.loadingMat then + w, h, col = 64, 64, color_white + end + + if not self.rbMins or not self.rbMaxs then + self.rbMins, self.rbMaxs = self:GetRenderBounds() + end + local expectedMaxs = Vector(h * 0.1, w * 0.1, 0.001) + local expectedMins = -expectedMaxs + if self.rbMins ~= expectedMins or self.rbMaxs ~= expectedMaxs then + self:SetRenderBounds(expectedMins, expectedMaxs) + self.rbMins, self.rbMaxs = expectedMins, expectedMaxs + end + + local pos, ang = LocalToWorld(posOff, angOff, self:GetPos(), self:GetAngles()) + cam.Start3D2D(pos, ang, 0.2) + surface.SetDrawColor(col.r, col.g, col.b, col.a * al) + surface.SetMaterial(mat) + surface.DrawTexturedRect(-w / 2, -h / 2, w, h) + cam.End3D2D() + +end + +local function clearCache() + + file.CreateDir('imgscreen') + local fls, fds = file.Find('imgscreen/*', 'DATA') + for _, fl in ipairs(fls) do + file.Delete('imgscreen/' .. fl) + end + +end +hook.Add('Shutdown', 'imgscreen.clearCache', clearCache) +hook.Add('PlayerFinishedLoading', 'imgscreen.clearCache', clearCache) diff --git a/garrysmod/addons/gmod-tools/lua/entities/imgscreen/init.lua b/garrysmod/addons/gmod-tools/lua/entities/imgscreen/init.lua new file mode 100644 index 0000000..a0117b1 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/imgscreen/init.lua @@ -0,0 +1,57 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +function ENT:Initialize() + + self:SetModel('models/hunter/plates/plate1x1.mdl') + self:DrawShadow(false) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + local ph = self:GetPhysicsObject() + ph:SetMass(1) + +end + +-- local allowedTools = octolib.array.toKeys {'imgscreen', 'remover', 'precision'} + +-- hook.Add('CanTool', 'imgscreens.preventTools', function(ply, trace, tool) +-- if IsValid(trace.Entity) and trace.Entity:GetClass() == 'imgscreen' and not allowedTools[tool] then return false end +-- end, -5) + +function ENT:UpdateImage() + + -- compatibility + if self.imgURL:sub(1, 4) ~= 'http' then + self.imgURL = 'https://i.imgur.com/' .. self.imgURL + end + + self:SetNetVar('img', { self.imgURL, self.imgW, self.imgH, self.imgColor, self.imgFade }) + +end + +local tosave = {'nocollide', 'imgURL', 'imgW', 'imgH', 'imgColor'} +duplicator.RegisterEntityClass('imgscreen', function(ply, data) + + if IsValid(ply) and not ply:CheckLimit('imgscreens') then + return false + end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + ent:UpdateImage() + + if ent.nocollide then + ent:GetPhysicsObject():EnableCollisions(false) + ent:SetCollisionGroup(COLLISION_GROUP_WORLD) + end + + if IsValid(ply) then + ply:AddCount('imgscreens', ent) + ply:AddCleanup('imgscreens', ent) + end + + return ent + +end, 'Data', unpack(tosave)) diff --git a/garrysmod/addons/gmod-tools/lua/entities/imgscreen/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/imgscreen/shared.lua new file mode 100644 index 0000000..9028637 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/imgscreen/shared.lua @@ -0,0 +1,8 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'Image Screen' +ENT.Author = 'chelog' +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.Tool = 'imgscreen' diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_init.lua new file mode 100644 index 0000000..0603d38 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_init.lua @@ -0,0 +1,36 @@ +include "sh_init.lua" +include "cl_maths.lua" +include "cl_panel.lua" + +local mat = CreateMaterial("aeypad_baaaaaaaaaaaaaaaaaaase", "VertexLitGeneric", { + ["$basetexture"] = "white", + ["$color"] = "{ 36 36 36 }", +}) + +function ENT:Draw() + render.SetMaterial(mat) + + render.DrawBox(self:GetPos(), self:GetAngles(), self.Mins, self.Maxs, color_white, true) + + local pos, ang = self:CalculateRenderPos(), self:CalculateRenderAng() + + local w, h = self.Width2D, self.Height2D + local x, y = self:CalculateCursorPos() + + local scale = self.Scale -- A high scale avoids surface call integerising from ruining aesthetics + + cam.Start3D2D(pos, ang, self.Scale) + self:Paint(w, h, x, y) + cam.End3D2D() +end + +function ENT:SendCommand(command, data) + net.Start("Keypad") + net.WriteEntity(self) + net.WriteUInt(command, 4) + + if data then + net.WriteUInt(data, 8) + end + net.SendToServer() +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_maths.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_maths.lua new file mode 100644 index 0000000..5d7ba1c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_maths.lua @@ -0,0 +1,56 @@ +-- Avoiding the clutter + +function ENT:CalculateCursorPos() + local ply = LocalPlayer() + + if not IsValid(ply) then + return 0, 0 + end + + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * 65, + filter = ply + }) + + if tr.Entity ~= self then + return 0, 0 + end + + local scale = self.Scale + + local pos, ang = self:CalculateRenderPos(), self:CalculateRenderAng() + local normal = self:GetForward() + + local intersection = util.IntersectRayWithPlane(ply:EyePos(), ply:GetAimVector(), pos, normal) + + if not intersection then + return 0, 0 + end + + local diff = pos - intersection + + local x = diff:Dot(-ang:Forward()) / scale + local y = diff:Dot(-ang:Right()) / scale + + return x, y +end + +function ENT:CalculateRenderPos() + local pos = self:GetPos() + pos:Add(self:GetForward() * self.Maxs.x) -- Translate to front + pos:Add(self:GetRight() * self.Maxs.y) -- Translate to left + pos:Add(self:GetUp() * self.Maxs.z) -- Translate to top + + pos:Add(self:GetForward() * 0.15) -- Pop out of front to stop culling + + return pos +end + +function ENT:CalculateRenderAng() + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Right(), -90) + ang:RotateAroundAxis(ang:Up(), 90) + + return ang +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_panel.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_panel.lua new file mode 100644 index 0000000..54ca3cb --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad/cl_panel.lua @@ -0,0 +1,178 @@ +surface.CreateFont("KeypadAbort", {font = "Roboto", size = 45, weight = 900}) +surface.CreateFont("KeypadOK", {font = "Roboto", size = 60, weight = 900}) +surface.CreateFont("KeypadNumber", {font = "Roboto", size = 70, weight = 600}) +surface.CreateFont("KeypadEntry", {font = "Roboto", size = 120, weight = 900}) +surface.CreateFont("KeypadStatus", {font = "Roboto", size = 60, weight = 900}) + +local COLOR_GREEN = Color(0, 255, 0) +local COLOR_RED = Color(255, 0, 0) + +local function DrawLines(lines, x, y) + local text = table.concat(lines, "\n") + local _, total_h = surface.GetTextSize(text) + + local y_off = 0 + + for _, v in ipairs(lines) do + local w, h = surface.GetTextSize(v) + + surface.SetTextPos(x - w / 2, y - total_h / 2 + y_off) + surface.DrawText(v) + + y_off = y_off + h + end +end + +local elements = { + { -- Screen + x = 0.075, + y = 0.04, + w = 0.85, + h = 0.25, + color = Color(50, 75, 50, 255), + render = function(self, x, y) + local status = self:GetStatus() + + if status == self.Status_None then + surface.SetFont("KeypadEntry") + + local text = self:GetText() + + local textw, texth = surface.GetTextSize(text) + + surface.SetTextColor(color_white) + surface.SetTextPos(x - textw / 2, y - texth / 2) + surface.DrawText(text) + elseif status == self.Status_Denied then + surface.SetFont("KeypadStatus") + surface.SetTextColor(COLOR_RED) + + if self:GetText() == "1337" then + DrawLines({"ACC355", "D3N13D"}, x, y) + else + DrawLines({"ACCESS", "DENIED"}, x, y) + end + elseif status == self.Status_Granted then + surface.SetFont("KeypadStatus") + surface.SetTextColor(COLOR_GREEN) + + if self:GetText() == "1337" then + DrawLines({"ACC355", "GRAN73D"}, x, y) + else + DrawLines({"ACCESS", "GRANTED"}, x, y) + end + end + end, + }, + { -- ABORT + x = 0.075, + y = 0.04 + 0.25 + 0.03, + w = 0.85 / 2 - 0.04 / 2 + 0.05, + h = 0.125, + color = Color(120, 25, 25), + hovercolor = Color(180, 25, 25), + text = "ABORT", + font = "KeypadAbort", + click = function(self) + self:SendCommand(self.Command_Abort) + end + }, + { -- OK + x = 0.5 + 0.04 / 2 + 0.05, + y = 0.04 + 0.25 + 0.03, + w = 0.85 / 2 - 0.04 / 2 - 0.05, + h = 0.125, + color = Color(25, 120, 25), + hovercolor = Color(25, 180, 25), + text = "OK", + font = "KeypadOK", + click = function(self) + self:SendCommand(self.Command_Accept) + end + } +} + +do -- Create numbers + for i = 1, 9 do + local column = (i - 1) % 3 + + local row = math.floor((i - 1) / 3) + + local element = { + x = 0.075 + (0.3 * column), + y = 0.175 + 0.25 + 0.05 + ((0.5 / 3) * row), + w = 0.25, + h = 0.13, + color = Color(120, 120, 120), + hovercolor = Color(180, 180, 180), + text = tostring(i), + number = true, + click = function(self) + self:SendCommand(self.Command_Enter, i) + end + } + + table.insert(elements, element) + end +end + + +function ENT:Paint(w, h) + + local hovered = self:GetHoveredElement() + for _, element in ipairs(elements) do + surface.SetDrawColor(element.color) + + local element_x = w * element.x + local element_y = h * element.y + local element_w = w * element.w + local element_h = h * element.h + + if element == hovered and element.hovercolor then + surface.SetDrawColor(element.hovercolor) + end + + surface.DrawRect( + element_x, + element_y, + element_w, + element_h + ) + + local cx = element_x + element_w / 2 + local cy = element_y + element_h / 2 + + if element.text then + surface.SetFont(element.font or "KeypadNumber") + + local textw, texth = surface.GetTextSize(element.text) + + surface.SetTextColor(color_black) + surface.SetTextPos(cx - textw / 2, cy - texth / 2) + surface.DrawText(element.text) + end + + if element.render then + element.render(self, cx, cy) + end + + end +end + +function ENT:GetHoveredElement() + local w, h = self.Width2D, self.Height2D + local x, y = self:CalculateCursorPos() + + for _, element in ipairs(elements) do + local element_x = w * element.x + local element_y = h * element.y + local element_w = w * element.w + local element_h = h * element.h + + if element_x < x and element_x + element_w > x and + element_y < y and element_y + element_h > y + then + return element + end + end +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad/init.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad/init.lua new file mode 100644 index 0000000..7386d68 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad/init.lua @@ -0,0 +1,234 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'cl_maths.lua' +AddCSLuaFile 'cl_panel.lua' +AddCSLuaFile 'sh_init.lua' + +include 'sh_init.lua' + +util.AddNetworkString('Keypad') + + +net.Receive('Keypad', function(_, ply) + if ply:GetNetVar( 'Ghost' ) then return end + + local ent = net.ReadEntity() + + if not IsValid(ply) or not IsValid(ent) or ent:GetClass():lower() ~= 'keypad' then + return + end + + if ent:GetStatus() ~= ent.Status_None then + return + end + + if ply:EyePos():Distance(ent:GetPos()) >= 120 then + return + end + + local command = net.ReadUInt(4) + + if command == ent.Command_Enter then + local val = tonumber(ent:GetValue() .. net.ReadUInt(8)) + + if val and val > 0 and val <= 9999 then + ent:SetValue(tostring(val)) + ent:EmitSound('buttons/button15.wav') + end + elseif command == ent.Command_Abort then + ent:SetValue('') + elseif command == ent.Command_Accept and not ply.hacking then + if ent:GetValue() == ent:GetPassword() then + ent:Process(true) + else + ent:Process(false) + end + end +end) + +math.randomseed(os.time()) +local secret = math.random(10000) + +function ENT:SetValue(val) + self.Value = val + + if self:GetSecure() then + self:SetText(string.rep('*', #val)) + else + self:SetText(val) + end +end + +function ENT:GetValue() + return self.Value +end + +function ENT:Process(granted) + local length, repeats, delay, initdelay, owner, key + + if granted then + self:SetStatus(self.Status_Granted) + + length = self.KeypadData.LengthGranted + repeats = math.min(self.KeypadData.RepeatsGranted, 50) + delay = self.KeypadData.DelayGranted + initdelay = self.KeypadData.InitDelayGranted + owner = self.KeypadData.Owner + key = tonumber(self.KeypadData.KeyGranted) or 0 + else + self:SetStatus(self.Status_Denied) + + length = self.KeypadData.LengthDenied + repeats = math.min(self.KeypadData.RepeatsDenied, 50) + delay = self.KeypadData.DelayDenied + initdelay = self.KeypadData.InitDelayDenied + owner = self.KeypadData.Owner + key = tonumber(self.KeypadData.KeyDenied) or 0 + end + + timer.Simple(math.max(initdelay + length * (repeats + 1) + delay * repeats + 0.25, 2), function() -- 0.25 after last timer + if IsValid(self) then + self:Reset() + end + end) + + timer.Simple(initdelay, function() + if IsValid(self) then + for i = 0, repeats do + timer.Simple(length * i + delay * i, function() + if IsValid(self) then + numpad.Activate(owner, key, true) + end + end) + + timer.Simple(length * (i + 1) + delay * i, function() + if IsValid(self) then + numpad.Deactivate(owner, key, true) + end + end) + end + end + end) + + if granted then + self:EmitSound('buttons/button9.wav') + else + self:EmitSound('buttons/button11.wav') + end +end + +function ENT:SetPassword(pass) + self.pass = tostring(pass) or '1337' + self.problem = nil + self:GenerateProblem() +end + +function ENT:GetPassword() + return self.pass or '1337' +end + +function ENT:SetData(data) + self.KeypadData = data + + self:SetPassword(data.Password or '1337') + self:Reset() +end + +function ENT:GetData() + return self.KeypadData +end + +local function randomIp() + return string.format('%s.%s.%s.%s', math.random(10, 255), math.random(0, 255), math.random(0, 255), math.random(0, 255)) +end + +local alphabet = '1234567890abcdef' +local function generateHash(len, range) + range = math.min(range or #alphabet, #alphabet) + len = math.max(len or 8, 1) + local str = '' + for i = 1, len do + str = str .. alphabet[math.random(range)] + end + return str +end + +function ENT:GenerateProblem(complex, vars) + + if self.problem then return end + + math.randomseed(CurTime()) + complex = math.max(complex or 6, 4) + vars = math.max(vars or 5, 1) + + math.randomseed(self:GetPassword() + secret) + self.problem = generateHash() + math.randomseed(CurTime()) + self.pwdserver = randomIp() + self.passs = {} + for i = 1, vars-1 do + self.passs[i] = generateHash(#self:GetPassword(), 9) + end + self.passs[vars] = tonumber(self:GetPassword()) + octolib.array.shuffle(self.passs) + + local path = {} + path[1] = '192.168.1.' .. math.random(20) + for i = 2, complex-1 do + path[i] = randomIp() + end + path[complex] = self.pwdserver + self.traceroute = path + + local pts = {} + for i = 1, 4 do + pts[i] = i + end + for i = 5, complex do + pts[i] = math.random(4) + end + octolib.array.shuffle(pts) + self.hashpts = pts + +end + +function ENT:Reset() + self:SetValue('') + self:SetStatus(self.Status_None) + self:SetSecure(self.KeypadData.Secure) +end + +duplicator.RegisterEntityClass('keypad', function(ply, data) + + if IsValid(ply) and not ply:CheckLimit('keypads') then return false end + + if not data.KeypadData then + data.KeypadData = { + DelayDenied = 0, + DelayGranted = 0, + InitDelayDenied = 0, + InitDelayGranted = 0, + KeyDenied = 0, + KeyGranted = 0, + LengthDenied = 0.1, + LengthGranted = 0, + Password = 1234, + RepeatsDenied = 0, + RepeatsGranted = 0, + Secure = false, + } + if IsValid(ply) then + ply.oldKeypads = true + end + end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + if IsValid(ply) then + data.KeypadData.Owner = ply:SteamID() + ply:AddCount('keypads', ent) + ply:AddCleanup('keypads', ent) + end + ent:SetData(data.KeypadData) + + return ent + +end, 'Data', 'KeypadData', 'pass') diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad/sh_init.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad/sh_init.lua new file mode 100644 index 0000000..b9b6d11 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad/sh_init.lua @@ -0,0 +1,78 @@ +ENT.Base = "base_gmodentity" +ENT.Type = "anim" + +ENT.Model = Model("models/props_lab/keypad.mdl") + +ENT.Spawnable = true + +ENT.Scale = 0.02 +ENT.Value = "" + +ENT.Status_None = 0 +ENT.Status_Granted = 1 +ENT.Status_Denied = 2 + +ENT.Command_Enter = 0 +ENT.Command_Accept = 1 +ENT.Command_Abort = 2 + +ENT.IsKeypad = true +ENT.Tool = 'keypad_willox' + +function ENT:Initialize() + self:SetModel(self.Model) + + if CLIENT then + self.Mins = self:OBBMins() + self.Maxs = self:OBBMaxs() + + self.Width2D, self.Height2D = (self.Maxs.y - self.Mins.y) / self.Scale , (self.Maxs.z - self.Mins.z) / self.Scale + end + + if SERVER then + self:PhysicsInit(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:Wake() + end + + self:SetValue("") + self:SetPassword("1337") + + if(not self.KeypadData) then + self:SetData({ + Password = 1337, + + RepeatsGranted = 0, + RepeatsDenied = 0, + + LengthGranted = 0, + LengthDenied = 0, + + DelayGranted = 0, + DelayDenied = 0, + + InitDelayGranted = 0, + InitDelayDenied = 0, + + KeyGranted = 0, + KeyDenied = 0, + + Secure = false, + Owner = '', + }) + end + + self:Reset() + end +end + +function ENT:SetupDataTables() + self:NetworkVar( "String", 0, "Text" ) + + self:NetworkVar( "Int", 0, "Status" ) + + self:NetworkVar( "Bool", 0, "Secure" ) +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_init.lua new file mode 100644 index 0000000..838d753 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_init.lua @@ -0,0 +1,36 @@ +include "sh_init.lua" +include "cl_maths.lua" +include "cl_panel.lua" + +local mat = CreateMaterial("aeypad_baaaaaaaaaaaaaaaaaaase", "VertexLitGeneric", { + ["$basetexture"] = "white", + ["$color"] = "{ 36 36 36 }", +}) + +function ENT:Draw() + render.SetMaterial(mat) + + render.DrawBox(self:GetPos(), self:GetAngles(), self.Mins, self.Maxs, color_white, true) + + local pos, ang = self:CalculateRenderPos(), self:CalculateRenderAng() + + local w, h = self.Width2D, self.Height2D + local x, y = self:CalculateCursorPos() + + local scale = self.Scale -- A high scale avoids surface call integerising from ruining aesthetics + + cam.Start3D2D(pos, ang, self.Scale) + self:Paint(w, h, x, y) + cam.End3D2D() +end + +function ENT:SendCommand(command, data) + net.Start("Keypad_Wire") + net.WriteEntity(self) + net.WriteUInt(command, 4) + + if data then + net.WriteUInt(data, 8) + end + net.SendToServer() +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_maths.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_maths.lua new file mode 100644 index 0000000..5d7ba1c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_maths.lua @@ -0,0 +1,56 @@ +-- Avoiding the clutter + +function ENT:CalculateCursorPos() + local ply = LocalPlayer() + + if not IsValid(ply) then + return 0, 0 + end + + local tr = util.TraceLine({ + start = ply:EyePos(), + endpos = ply:EyePos() + ply:GetAimVector() * 65, + filter = ply + }) + + if tr.Entity ~= self then + return 0, 0 + end + + local scale = self.Scale + + local pos, ang = self:CalculateRenderPos(), self:CalculateRenderAng() + local normal = self:GetForward() + + local intersection = util.IntersectRayWithPlane(ply:EyePos(), ply:GetAimVector(), pos, normal) + + if not intersection then + return 0, 0 + end + + local diff = pos - intersection + + local x = diff:Dot(-ang:Forward()) / scale + local y = diff:Dot(-ang:Right()) / scale + + return x, y +end + +function ENT:CalculateRenderPos() + local pos = self:GetPos() + pos:Add(self:GetForward() * self.Maxs.x) -- Translate to front + pos:Add(self:GetRight() * self.Maxs.y) -- Translate to left + pos:Add(self:GetUp() * self.Maxs.z) -- Translate to top + + pos:Add(self:GetForward() * 0.15) -- Pop out of front to stop culling + + return pos +end + +function ENT:CalculateRenderAng() + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Right(), -90) + ang:RotateAroundAxis(ang:Up(), 90) + + return ang +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_panel.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_panel.lua new file mode 100644 index 0000000..3662d0b --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/cl_panel.lua @@ -0,0 +1,171 @@ +local COLOR_GREEN = Color(0, 255, 0) +local COLOR_RED = Color(255, 0, 0) + +local function DrawLines(lines, x, y) + local text = table.concat(lines, "\n") + local total_w, total_h = surface.GetTextSize(text) + + local y_off = 0 + + for k, v in ipairs(lines) do + local w, h = surface.GetTextSize(v) + + surface.SetTextPos(x - w / 2, y - total_h / 2 + y_off) + surface.DrawText(v) + + y_off = y_off + h + end +end + +local elements = { + { -- Screen + x = 0.075, + y = 0.04, + w = 0.85, + h = 0.25, + color = Color(50, 75, 50, 255), + render = function(self, x, y) + local status = self:GetStatus() + + if status == self.Status_None then + surface.SetFont("KeypadEntry") + + local text = self:GetText() + + local textw, texth = surface.GetTextSize(text) + + surface.SetTextColor(color_white) + surface.SetTextPos(x - textw / 2, y - texth / 2) + surface.DrawText(text) + elseif status == self.Status_Denied then + surface.SetFont("KeypadStatus") + surface.SetTextColor(COLOR_RED) + + if self:GetText() == "1337" then + DrawLines({"ACC355", "D3N13D"}, x, y) + else + DrawLines({"ACCESS", "DENIED"}, x, y) + end + elseif status == self.Status_Granted then + surface.SetFont("KeypadStatus") + surface.SetTextColor(COLOR_GREEN) + + if self:GetText() == "1337" then + DrawLines({"ACC355", "GRAN73D"}, x, y) + else + DrawLines({"ACCESS", "GRANTED"}, x, y) + end + end + end, + }, + { -- ABORT + x = 0.075, + y = 0.04 + 0.25 + 0.03, + w = 0.85 / 2 - 0.04 / 2 + 0.05, + h = 0.125, + color = Color(120, 25, 25), + hovercolor = Color(180, 25, 25), + text = "ABORT", + font = "KeypadAbort", + click = function(self) + self:SendCommand(self.Command_Abort) + end + }, + { -- OK + x = 0.5 + 0.04 / 2 + 0.05, + y = 0.04 + 0.25 + 0.03, + w = 0.85 / 2 - 0.04 / 2 - 0.05, + h = 0.125, + color = Color(25, 120, 25), + hovercolor = Color(25, 180, 25), + text = "OK", + font = "KeypadOK", + click = function(self) + self:SendCommand(self.Command_Accept) + end + } +} + +do -- Create numbers + for i = 1, 9 do + local column = (i - 1) % 3 + + local row = math.floor((i - 1) / 3) + + local element = { + x = 0.075 + (0.3 * column), + y = 0.175 + 0.25 + 0.05 + ((0.5 / 3) * row), + w = 0.25, + h = 0.13, + color = Color(120, 120, 120), + hovercolor = Color(180, 180, 180), + text = tostring(i), + click = function(self) + self:SendCommand(self.Command_Enter, i) + end + } + + table.insert(elements, element) + end +end + + +function ENT:Paint(w, h, x, y) + local hovered = self:GetHoveredElement() + + for k, element in ipairs(elements) do + surface.SetDrawColor(element.color) + + local element_x = w * element.x + local element_y = h * element.y + local element_w = w * element.w + local element_h = h * element.h + + if element == hovered and element.hovercolor then + surface.SetDrawColor(element.hovercolor) + end + + surface.DrawRect( + element_x, + element_y, + element_w, + element_h + ) + + local cx = element_x + element_w / 2 + local cy = element_y + element_h / 2 + + if element.text then + surface.SetFont(element.font or "KeypadNumber") + + local textw, texth = surface.GetTextSize(element.text) + + surface.SetTextColor(color_black) + surface.SetTextPos(cx - textw / 2, cy - texth / 2) + surface.DrawText(element.text) + end + + if element.render then + element.render(self, cx, cy) + end + + end +end + +function ENT:GetHoveredElement() + local w, h = self.Width2D, self.Height2D + local x, y = self:CalculateCursorPos() + + for _, element in ipairs(elements) do + local element_x = w * element.x + local element_y = h * element.y + local element_w = w * element.w + local element_h = h * element.h + + if element_x < x and element_x + element_w > x and + element_y < y and element_y + element_h > y + then + return element + end + end +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/init.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/init.lua new file mode 100644 index 0000000..de503f4 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/init.lua @@ -0,0 +1,150 @@ +AddCSLuaFile "cl_init.lua" +AddCSLuaFile "cl_maths.lua" +AddCSLuaFile "cl_panel.lua" +AddCSLuaFile "sh_init.lua" + +include "sh_init.lua" + +util.AddNetworkString("Keypad_Wire") + + +net.Receive("Keypad_Wire", function(_, ply) + if ply:GetNetVar( "Ghost" ) then return end + + local ent = net.ReadEntity() + + if not IsValid(ply) or not IsValid(ent) or ent:GetClass():lower() ~= "keypad_wire" then + return + end + + if ent:GetStatus() ~= ent.Status_None then + return + end + + if ply:EyePos():Distance(ent:GetPos()) >= 120 then + return + end + + local command = net.ReadUInt(4) + + if command == ent.Command_Enter then + local val = tonumber(ent:GetValue() .. net.ReadUInt(8)) + + if val and val > 0 and val <= 9999 then + ent:SetValue(tostring(val)) + ent:EmitSound("buttons/button15.wav") + end + elseif command == ent.Command_Abort then + ent:SetValue("") + elseif command == ent.Command_Accept then + if ent:GetValue() == ent:GetPassword() then + ent:Process(true) + else + ent:Process(false) + end + end +end) + +function ENT:SetValue(val) + self.Value = val + + if self:GetSecure() then + self:SetText(string.rep("*", #val)) + else + self:SetText(val) + end +end + +function ENT:GetValue() + return self.Value +end + +function ENT:Process(granted) + local length, repeats, delay, initdelay, owner, outputKey + + if(granted) then + self:SetStatus(self.Status_Granted) + + length = self.KeypadData.LengthGranted + repeats = math.min(self.KeypadData.RepeatsGranted, 50) + delay = self.KeypadData.DelayGranted + initdelay = self.KeypadData.InitDelayGranted + owner = self.KeypadData.Owner + outputKey = "Access Granted" + else + self:SetStatus(self.Status_Denied) + + length = self.KeypadData.LengthDenied + repeats = math.min(self.KeypadData.RepeatsDenied, 50) + delay = self.KeypadData.DelayDenied + initdelay = self.KeypadData.InitDelayDenied + owner = self.KeypadData.Owner + outputKey = "Access Denied" + end + + timer.Simple(math.max(initdelay + length * (repeats + 1) + delay * repeats + 0.25, 2), function() -- 0.25 after last timer + if(IsValid(self)) then + self:Reset() + end + end) + + timer.Simple(initdelay, function() + if(IsValid(self)) then + for i = 0, repeats do + timer.Simple(length * i + delay * i, function() + if(IsValid(self)) then + Wire_TriggerOutput(self, outputKey, self.KeypadData.OutputOn) + end + end) + + timer.Simple(length * (i + 1) + delay * i, function() + if(IsValid(self)) then + Wire_TriggerOutput(self, outputKey, self.KeypadData.OutputOff) + end + end) + end + end + end) + + if(granted) then + self:EmitSound("buttons/button9.wav") + else + self:EmitSound("buttons/button11.wav") + end +end + +function ENT:SetData(data) + self.KeypadData = data + + self:SetPassword(data.Password or "1337") + self:Reset() +end + +function ENT:GetData() + return self.KeypadData +end + +function ENT:Reset() + self:SetValue("") + self:SetStatus(self.Status_None) + self:SetSecure(self.KeypadData.Secure) + + Wire_TriggerOutput(self, "Access Granted", self.KeypadData.OutputOff) + Wire_TriggerOutput(self, "Access Denied", self.KeypadData.OutputOff) +end + +duplicator.RegisterEntityClass('keypad_wire', function(ply, data) + + if IsValid(ply) and not ply:CheckLimit('keypad_wires') then return false end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + if IsValid(ply) then + data.KeypadData.Owner = ply:SteamID() + ply:AddCount('keypad_wires', ent) + ply:AddCleanup('keypad_wires', ent) + end + ent:SetData(data.KeypadData) + + return ent + +end, 'Data', 'KeypadData', 'pass') diff --git a/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/sh_init.lua b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/sh_init.lua new file mode 100644 index 0000000..af041ae --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/keypad_wire/sh_init.lua @@ -0,0 +1,90 @@ +ENT.Base = "base_wire_entity" + +if not WireLib then + ENT.Base = "base_gmodentity" -- :( +end + + +ENT.Type = "anim" + +ENT.Model = Model("models/props_lab/keypad.mdl") + +ENT.Spawnable = true + +ENT.Scale = 0.02 +ENT.Value = "" + +ENT.Status_None = 0 +ENT.Status_Granted = 1 +ENT.Status_Denied = 2 + +ENT.Command_Enter = 0 +ENT.Command_Accept = 1 +ENT.Command_Abort = 2 + +ENT.IsKeypad = true +ENT.Tool = 'keypad_willox_wire' + +AccessorFunc(ENT, "m_Password", "Password", FORCE_STRING) + +function ENT:Initialize() + self:SetModel(self.Model) + + if CLIENT then + self.Mins = self:OBBMins() + self.Maxs = self:OBBMaxs() + + self.Width2D, self.Height2D = (self.Maxs.y - self.Mins.y) / self.Scale , (self.Maxs.z - self.Mins.z) / self.Scale + end + + if SERVER then + if not WireLib then self:Remove() return end + + self.Outputs = Wire_CreateOutputs(self, {"Access Granted", "Access Denied"}) + + self:PhysicsInit(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if IsValid(phys) then + phys:Wake() + end + + self:SetValue("") + self:SetPassword("1337") + + if(not self.KeypadData) then + self:SetData({ + Password = false, + + RepeatsGranted = 0, + RepeatsDenied = 0, + + LengthGranted = 0, + LengthDenied = 0, + + DelayGranted = 0, + DelayDenied = 0, + + InitDelayGranted = 0, + InitDelayDenied = 0, + + OutputOn = 0, + OutputOff = 0, + + Secure = false, + Owner = '' + }) + end + + self:Reset() + end +end + +function ENT:SetupDataTables() + self:NetworkVar( "String", 0, "Text" ) + + self:NetworkVar( "Int", 0, "Status" ) + + self:NetworkVar( "Bool", 0, "Secure" ) +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/cl_init.lua new file mode 100644 index 0000000..93a5707 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/cl_init.lua @@ -0,0 +1,136 @@ +include 'shared.lua' + +function ENT:Initialize() + + local size = self:GetNetVar('size') or Vector(1,1,1) + local side = Vector(size.x / 2, size.y / 2, size.z / 2) + self:SetRenderBounds(-side, side) + +end + +function ENT:Draw() + + -- self:DrawModel() + + -- local size = self:GetNetVar('size') or Vector(1,1,1) + -- local side = Vector(size.x / 2, size.y / 2, size.z / 2) + + -- render.SetColorMaterial() + -- render.CullMode(MATERIAL_CULLMODE_CW) + -- render.DrawBox(self:GetPos(), self:GetAngles(), -side, side, Color(255,255,255, 50), true) + -- render.CullMode(MATERIAL_CULLMODE_CCW) + -- render.DrawBox(self:GetPos(), self:GetAngles(), -side, side, Color(255,255,255, 50), true) + + if not octolib.vars.get('tools.trigger.draw') then return end + local wepclass = LocalPlayer():GetActiveWeaponClass() + if wepclass ~= "gmod_tool" then return end + local ang = Angle() + + cam.Start3D() + local tpos = self:GetPos() + local size = (self:GetNetVar('size') or Vector(1,1,1)) / 2 + render.DrawWireframeBox(tpos, ang, -size, size, color_white, false) + if self:GetNetVar('sound3dpos') then + local pos, ang = unpack(self:GetNetVar('sound3dpos')) + if IsValid(self.soundMdl) then + self.soundMdl:SetPos(pos) + self.soundMdl:SetAngles(ang + Angle(90,0,0)) + self.soundMdl:DrawModel() + else self:CreateSoundModel() end + render.DrawLine(pos, tpos, color_white) + end + cam.Start2D() + local spos = tpos:ToScreen() + local x, y = math.ceil(spos.x), math.ceil(spos.y) + surface.SetDrawColor(255, 255, 255, 55) + surface.DrawRect(x - 15, y - 10, 25, 25) + local title, text = self:GetNetVar('title') or L.loading, self:GetNetVar('text') or L.loading + if utf8.len(text) > 64 then text = utf8.sub(text, 1, 64) .. '...' end + draw.SimpleText(title, 'DermaDefault', x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + draw.SimpleText(text, 'DermaDefault', x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + cam.End2D() + cam.End3D() + +end + +function ENT:CreateSoundModel() + if self.creatingModel or IsValid(self.soundMdl) then return end + self.creatingModel = true + timer.Simple(0, function() + self.soundMdl = octolib.createDummy('models/cheeze/wires/speaker.mdl') + self.soundMdl:SetNoDraw(true) + self.creatingModel = true + end) +end + +local activeSounds = {} + +local function tryCreateSound(id, path, vol, stopsound) + local st = CreateSound(LocalPlayer(), path) + if not st then + return octolib.notify.show('warning', L.noplay, 'Звук не найден') + end + st:ChangeVolume(vol) + st:SetSoundLevel(75) + st:Play() + if IsValid(activeSounds[id]) then + activeSounds[id]:Stop() + end + if activeSounds[id] and activeSounds[id].IsPlaying and activeSounds[id]:IsPlaying() then + activeSounds[id]:Stop() + end + activeSounds[id] = st + for _,v in ipairs(stopsound or {}) do + if IsValid(activeSounds[v]) then + activeSounds[v]:Stop() + end + if activeSounds[v] and activeSounds[v].IsPlaying and activeSounds[v]:IsPlaying() then + activeSounds[v]:Stop() + end + activeSounds[v] = nil + end +end + +netstream.Hook('trigger_sound', function(id, url, path, vol, pos, stopsound) + + if not path or path == '' or vol == 0 then return end + local method = sound['Play' .. (url and 'URL' or 'File')] + if not url then path = 'sound/' .. path end + local playFunc = function(st, code, err) + if not IsValid(st) then + if code == 2 and not url then + return tryCreateSound(id, path:sub(7), vol, stopsound) + end + return octolib.notify.show('warning', L.noplay, err) + end + if st:GetLength() == 0 then + return octolib.notify.show('warning', 'Потоковые аудио не поддерживаются') + end + st:SetVolume(vol or 1) + if pos then + st:SetPos(unpack(pos)) + end + if IsValid(activeSounds[id]) then activeSounds[id]:Stop() end + if activeSounds[id] and activeSounds[id].IsPlaying and activeSounds[id]:IsPlaying() then + activeSounds[id]:Stop() + end + st:Play() + activeSounds[id] = st + for _,v in ipairs(stopsound or {}) do + if IsValid(activeSounds[v]) then + activeSounds[v]:Stop() + end + if activeSounds[v] and activeSounds[v].IsPlaying and activeSounds[v]:IsPlaying() then + activeSounds[v]:Stop() + end + activeSounds[v] = nil + end + end + method(path, 'noplay' .. (pos and ' 3d' or ''), playFunc) + +end) + +netstream.Hook('trigger_stop', function(id) + if IsValid(activeSounds[id]) then activeSounds[id]:Stop() end + activeSounds[id] = nil +end) diff --git a/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/init.lua b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/init.lua new file mode 100644 index 0000000..ad8ab45 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/init.lua @@ -0,0 +1,138 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +function ENT:UpdateTransmitState() + + return TRANSMIT_ALWAYS + +end + +function ENT:Initialize() + + self:SetModel('models/props_junk/popcan01a.mdl') + self:DrawShadow(false) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_BBOX) + self:UpdateSize() + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + + self:AddEFlags(EFL_FORCE_CHECK_TRANSMIT) + self:SetTrigger(true) + + self:UpdateNetworkVars() + self.done = self.mode == 0 and {} or 0 + self.uid = octolib.string.uuid() + +end + +function ENT:UpdateSize() + + local size = self.size or Vector(1,1,1) + local side = Vector(size.x / 2, size.y / 2, size.z / 2) + self:SetCollisionBounds(-side, side) + +end + +function ENT:UpdateNetworkVars() + + self:SetNetVar('title', self.title) + self:SetNetVar('text', self.text) + self:SetNetVar('size', self.size) + +end + +function ENT:StartTouch(ent) + + if ent:IsPlayer() then + if self.times > 0 then + local done = self.done + if self.mode == 0 then + done = self.done[ent] or 0 + end + if done >= self.times then return end + done = done + 1 + if self.mode == 0 then + self.done[ent] = done + else + self.done = done + end + end + self:DoTrigger(ent) + end +end + +local function numpadToggle(ent) + if not ent.bPressed then + numpad.Activate( ent:GetPlayerSteamID(), ent.bind, true ) + ent.bPressed = true + else + numpad.Deactivate( ent:GetPlayerSteamID(), ent.bind, true ) + ent.bPressed = false + end +end + +function ENT:DoTrigger(ent) + + if self.sound3d and not self:GetNetVar('sound3dpos') then return end + + local time = (self.duration or 0) + local title = string.Trim(self:GetNetVar('title') or '') + local text = string.Trim(self:GetNetVar('text') or '') + + if self.bind then + numpadToggle(self) + end + + if time > 0 and title ~= '' or text ~= '' then + if self.method == 'chat' then + if title == '' then + title = text + text = '' + end + octochat.talkTo(ent, octochat.textColors.rp, title, ' ', color_white, text) + elseif self.method == 'notify' then + ent:Notify(text) + elseif self.method == 'center' then + if title ~= '' or text ~= '' then + ent:Notify('admin', time, title, text) + end + end + end + + local url = self.urlsound and string.Trim(self.urlsound) ~= '' + if url or (self.gamesound and string.Trim(self.gamesound) ~= '') then + netstream.Start(ent, 'trigger_sound', self.uid, url, string.Trim(url and self.urlsound or self.gamesound), self.volume or 1, self:GetNetVar('sound3dpos'), self.stopsounds) + end + +end + +function ENT:OnRemove() + netstream.Start(nil, 'trigger_stop', self.uid) +end + +local tosave = {'size', 'duration', 'times', 'text', 'volume', 'title', 'method', 'gamesound', 'urlsound', 'mode'} +duplicator.RegisterEntityClass('octo_trigger', function(ply, data) + + if IsValid(ply) then + if not ply:CheckLimit('octo_triggers') then + return false + end + + if not ply:query(L.permissions_trigger_url) then data.urlsound = nil end + end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + + ent:UpdateSize() + ent:UpdateNetworkVars() + + if IsValid(ply) then + ply:AddCount('octo_triggers', ent) + ply:AddCleanup('octo_triggers', ent) + end + + return ent + +end, 'Data', unpack(tosave)) diff --git a/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/shared.lua new file mode 100644 index 0000000..43fa774 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.RenderMode = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = 'Area Trigger' +ENT.Author = 'chelog' + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.Tool = 'octo_trigger' diff --git a/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/cl_init.lua new file mode 100644 index 0000000..5a1665f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/cl_init.lua @@ -0,0 +1,45 @@ +include 'shared.lua' + +function ENT:Initialize() + + local size = self:GetNetVar('size') or Vector(1,1,1) + local side = Vector(size.x / 2, size.y / 2, size.z / 2) + self:SetRenderBounds(-side, side) + +end + +function ENT:Draw() + if not octolib.vars.get('tools.trigger+.draw') then return end + local wepclass = LocalPlayer():GetActiveWeaponClass() + if wepclass ~= "gmod_tool" then return end + local ang = Angle() + + cam.Start3D() + local tpos = self:GetPos() + local size = (self:GetNetVar('size') or Vector(1,1,1)) / 2 + render.DrawWireframeBox(tpos, ang, -size, size, color_white, false) + local owner = self:CPPIGetOwner() + if not IsValid(owner) then return end + cam.Start2D() + local spos = tpos:ToScreen() + local x, y = math.ceil(spos.x), math.ceil(spos.y) + surface.SetDrawColor(255, 255, 255, 55) + surface.DrawRect(x - 15, y - 10, 25, 25) + local title, text = owner:Name(), owner:SteamID() + draw.SimpleText(title, 'DermaDefault', x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + draw.SimpleText(text, 'DermaDefault', x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + cam.End2D() + cam.End3D() +end + +netstream.Hook('gmpanel.executeObject', function(data, players) + if data.type == 'action' then + + local action = gmpanel.actions.available[data.obj.id] + local func = action.execute or gmpanel.actions.defaultExecute + func(data.obj, players) + + elseif data.type == 'scenario' then + gmpanel.scenarios.execute(data.obj, players) + end +end) diff --git a/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/init.lua b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/init.lua new file mode 100644 index 0000000..7dc323c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/init.lua @@ -0,0 +1,94 @@ +AddCSLuaFile 'cl_init.lua' +AddCSLuaFile 'shared.lua' +include 'shared.lua' + +function ENT:UpdateTransmitState() + + return TRANSMIT_ALWAYS + +end + +function ENT:Initialize() + + self:SetModel('models/props_junk/popcan01a.mdl') + self:DrawShadow(false) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_BBOX) + self:UpdateSize() + self:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + + self:AddEFlags(EFL_FORCE_CHECK_TRANSMIT) + self:SetTrigger(true) + + self:UpdateNetworkVars() + self.done = self.mode == 0 and {} or 0 + +end + +function ENT:UpdateNetworkVars() + + self:SetNetVar('size', self.size) + +end + +function ENT:UpdateSize() + + local size = self.size or Vector(1,1,1) + local side = Vector(size.x / 2, size.y / 2, size.z / 2) + self:SetCollisionBounds(-side, side) + +end + +function ENT:StartTouch(ent) + + if ent:IsPlayer() then + if self.times > 0 then + local done = self.done + if self.mode == 0 then + done = self.done[ent] or 0 + end + if done >= self.times then return end + done = done + 1 + if self.mode == 0 then + self.done[ent] = done + else + self.done = done + end + end + self:DoTrigger(ent) + end + +end + +function ENT:DoTrigger(ent) + + if not self.action then return end + local owner = self:CPPIGetOwner() + if not IsValid(owner) then return end + + netstream.Start(owner, 'gmpanel.executeObject', self.action, { ent:SteamID() }) + +end + +local tosave = {'size', 'times', 'mode', 'action'} +duplicator.RegisterEntityClass('octo_trigger_plus', function(ply, data) + if not IsValid(ply) then return end + + if not ply:query('DBG: Панель ивентов') then return false end + if not ply:CheckLimit('octo_triggers_plus') then return false end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + + ent:UpdateSize() + ent:UpdateNetworkVars() + ent.done = ent.mode == 0 and {} or 0 + + if IsValid(ply) then + ply:AddCount('octo_triggers_plus', ent) + ply:AddCleanup('octo_triggers_plus', ent) + end + + return ent + +end, 'Data', unpack(tosave)) diff --git a/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/shared.lua new file mode 100644 index 0000000..2d4a4c4 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/octo_trigger_plus/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.RenderMode = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = 'Area Trigger Extended' +ENT.Author = 'Wani4ka' + +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.Tool = 'octo_trigger_plus' diff --git a/garrysmod/addons/gmod-tools/lua/entities/parctrl_dummyent.lua b/garrysmod/addons/gmod-tools/lua/entities/parctrl_dummyent.lua new file mode 100644 index 0000000..98b0cce --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/parctrl_dummyent.lua @@ -0,0 +1,172 @@ +//This is a dummy entity used by serverside projectile effects. It doesn't do anything special except be a SENT (we can't use PhysicsInitBox on prop_physics, apparently), +//call its own particle effects (since the projectile effect entity can't do that serverside), and not get duplicated! + +AddCSLuaFile() + +ENT.Base = "base_gmodentity" +ENT.PrintName = "Particle Controller Dummy Entity" +ENT.Author = "" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +if CLIENT then + language.Add("parctrl_dummyent", "Physics Object") //for killfeed notices +end + + + + +function ENT:SetupDataTables() + + self:NetworkVar( "String", 0, "EffectName" ); + self:NetworkVar( "Vector", 0, "UtilEffectInfo" ); + self:NetworkVar( "Vector", 1, "ColorInfo" ); + self:NetworkVar( "Int", 0, "AttachNum" ); + +end + + + + +//Since we're creating these entities serverside, we can't create the particle effects in the projectile-firing function, so do them here instead: +function ENT:Think() + + if SERVER then return end + if self.ParticleEffectCalled then return end + + local effectname = self:GetEffectName() + local utileffectinfo = self:GetUtilEffectInfo() + local colorinfo = self:GetColorInfo() + local attachnum = self:GetAttachNum() + + if effectname != nil and effectname != "" and colorinfo != nil and utileffectinfo != nil and attachnum != nil then + + local projfxent = self //terminology's a bit weird here since it's mostly all copy-pasted from the projectile tool, but it's not really worth the trouble changing it all + + if string.StartWith( effectname, "!UTILEFFECT!" ) then + + //Create a util effect + + //Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly + + local effectscale = utileffectinfo.x + local effectmagnitude = utileffectinfo.y + local effectradius = utileffectinfo.z + + local projeffectdata = EffectData() + projeffectdata:SetEntity( projfxent ) + //if ( string.find(effectname, "Tracer", 0, true) != nil ) then projeffectdata:SetScale(5000) else projeffectdata:SetScale( effectscale ) end //for tracer effects, scale is the speed of the bullet, so we need to keep this high; useless for a projectile effect + projeffectdata:SetScale( effectscale ) + projeffectdata:SetMagnitude( effectmagnitude ) + projeffectdata:SetRadius(effectradius ) + + //flags can be set by typing !FLAG#! at the end of the effect name + projeffectdata:SetFlags( 0 ) + if string.EndsWith( effectname, "!" ) then + if string.find( effectname, "!FLAG1!" ) then projeffectdata:SetFlags( 1 ) effectname = string.Replace( effectname, "!FLAG1!", "" ) end + if string.find( effectname, "!FLAG2!" ) then projeffectdata:SetFlags( 2 ) effectname = string.Replace( effectname, "!FLAG2!", "" ) end + if string.find( effectname, "!FLAG3!" ) then projeffectdata:SetFlags( 3 ) effectname = string.Replace( effectname, "!FLAG3!", "" ) end + if string.find( effectname, "!FLAG4!" ) then projeffectdata:SetFlags( 4 ) effectname = string.Replace( effectname, "!FLAG4!", "" ) end + if string.find( effectname, "!FLAG5!" ) then projeffectdata:SetFlags( 5 ) effectname = string.Replace( effectname, "!FLAG5!", "" ) end + if string.find( effectname, "!FLAG6!" ) then projeffectdata:SetFlags( 6 ) effectname = string.Replace( effectname, "!FLAG6!", "" ) end + if string.find( effectname, "!FLAG7!" ) then projeffectdata:SetFlags( 7 ) effectname = string.Replace( effectname, "!FLAG7!", "" ) end + if string.find( effectname, "!FLAG8!" ) then projeffectdata:SetFlags( 8 ) effectname = string.Replace( effectname, "!FLAG8!", "" ) end + if string.find( effectname, "!FLAG9!" ) then projeffectdata:SetFlags( 9 ) effectname = string.Replace( effectname, "!FLAG9!", "" ) end + end + + //colors can also be set the same way + projeffectdata:SetColor(0) + if string.EndsWith( effectname, "!" ) then + if string.find( effectname, "!COLOR1!" ) then projeffectdata:SetColor( 1 ) effectname = string.Replace( effectname, "!COLOR1!", "" ) end + if string.find( effectname, "!COLOR2!" ) then projeffectdata:SetColor( 2 ) effectname = string.Replace( effectname, "!COLOR2!", "" ) end + if string.find( effectname, "!COLOR3!" ) then projeffectdata:SetColor( 3 ) effectname = string.Replace( effectname, "!COLOR3!", "" ) end + if string.find( effectname, "!COLOR4!" ) then projeffectdata:SetColor( 4 ) effectname = string.Replace( effectname, "!COLOR4!", "" ) end + if string.find( effectname, "!COLOR5!" ) then projeffectdata:SetColor( 5 ) effectname = string.Replace( effectname, "!COLOR5!", "" ) end + if string.find( effectname, "!COLOR6!" ) then projeffectdata:SetColor( 6 ) effectname = string.Replace( effectname, "!COLOR6!", "" ) end + if string.find( effectname, "!COLOR7!" ) then projeffectdata:SetColor( 7 ) effectname = string.Replace( effectname, "!COLOR7!", "" ) end + if string.find( effectname, "!COLOR8!" ) then projeffectdata:SetColor( 8 ) effectname = string.Replace( effectname, "!COLOR8!", "" ) end + if string.find( effectname, "!COLOR9!" ) then projeffectdata:SetColor( 9 ) effectname = string.Replace( effectname, "!COLOR9!", "" ) end + end + + //dumb situational crap + if string.find( string.lower(effectname), "shakeropes" ) then projeffectdata:SetMagnitude( effectmagnitude * 20 ) end + if string.find( string.lower(effectname), "thumperdust" ) then projeffectdata:SetScale( effectscale * 50 ) end + if string.find( string.lower(effectname), "bloodspray" ) then projeffectdata:SetScale( effectscale * 4 ) end + + //just in case someone makes a utileffect that works as a projectile effect + if projfxent:GetAttachment(attachnum) != nil then + projeffectdata:SetStart( projfxent:GetAttachment( attachnum ).Pos ) + projeffectdata:SetOrigin( projfxent:GetAttachment( attachnum ).Pos ) + projeffectdata:SetAngles( projfxent:GetAttachment( attachnum ).Ang ) + projeffectdata:SetNormal( projfxent:GetAttachment( attachnum ).Ang:Forward() ) + else + projeffectdata:SetStart( projfxent:GetPos() ) + projeffectdata:SetOrigin( projfxent:GetPos() ) + projeffectdata:SetAngles( projfxent:GetAngles() ) + projeffectdata:SetNormal( projfxent:GetAngles():Forward() ) + end + + util.Effect( string.Replace( effectname, "!UTILEFFECT!", "" ), projeffectdata ) + + else + + //Create a particlesystem effect + + //Since we can't specify attachment points with Entity:CreateParticleEffect(), create an entity there to use as a target + local attachment1 = self:GetAttachment( attachnum ) + if attachment1 != nil then + //if SERVER then + // projfxent = ents.Create("parctrl_dummyent") + // projfxent:SetModel("models/hunter/plates/plate.mdl") + //end + if CLIENT then + projfxent = octolib.createDummy("models/hunter/plates/plate.mdl") + end + + projfxent:SetParent(self, attachnum - 1) + projfxent:SetPos( attachment1.Pos ) + projfxent:SetAngles( attachment1.Ang ) + projfxent:SetNoDraw(true) + + projfxent:Spawn() + projfxent:Activate() + + self:CallOnRemove("RemoveProjFX", function() if IsValid(projfxent) then projfxent:Remove() end end) + end + + + local clrtb = nil + if colorinfo == Vector(0,0,0) then + //MsgN("color = false") + else + //MsgN("color = true") + clrtb = { position = colorinfo } + end + local cpointtable = {} + cpointtable[1] = { entity = projfxent, attachtype = PATTACH_ABSORIGIN_FOLLOW } + + for i = 2, 64 do + if clrtb then + cpointtable[i] = clrtb + else + cpointtable[i] = cpointtable[1] + end + end + + projfxent:CreateParticleEffect(effectname,cpointtable) + + end + + self.ParticleEffectCalled = true + + end + +end + + + + +//don't duplicate this +duplicator.RegisterEntityClass( "parctrl_dummyent", function( ply, data ) +end, "Data" ) diff --git a/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_normal.lua b/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_normal.lua new file mode 100644 index 0000000..5e82a13 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_normal.lua @@ -0,0 +1,361 @@ +AddCSLuaFile() + +ENT.Base = "base_gmodentity" +ENT.PrintName = "Particle Controller - Normal" +ENT.Author = "" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_NONE + + + + +function ENT:SetupDataTables() + + self:NetworkVar( "Entity", 0, "TargetEnt" ); + self:NetworkVar( "Entity", 1, "TargetEnt2" ); + + self:NetworkVar( "String", 0, "EffectName" ); + self:NetworkVar( "String", 1, "NumpadState" ); + + self:NetworkVar( "Float", 0, "RepeatRate" ); + + self:NetworkVar( "Int", 0, "AttachNum" ); + self:NetworkVar( "Int", 1, "AttachNum2" ); + self:NetworkVar( "Int", 2, "NumpadKey" ); + + self:NetworkVar( "Bool", 0, "Active" ); + self:NetworkVar( "Bool", 1, "RepeatSafety" ); + self:NetworkVar( "Bool", 2, "Toggle" ); + + //we have 3 pieces of information we need for util.effect, and they're not going to be changed after the ent's been spawned, so there's no reason we can't save space by storing them all in this vector + self:NetworkVar( "Vector", 0, "UtilEffectInfo" ); //x = scale, y = magnitude, x = radius + +end + + + + +function ENT:Initialize() + + local target = self:GetTargetEnt() + local target2 = self:GetTargetEnt2() + + + if !string.StartWith( self:GetEffectName(), "!UTILEFFECT!" ) then + + //Set things up for a particlesystem effect + + if SERVER then + + //Since we can't specify attachment points with Entity:CreateParticleEffect(), we have to improvise. + + //If target1 is using an attachment, move ourselves to the pos/ang of that attachment and use ourselves as the target for control point 1. + local attachment1 = target:GetAttachment( self:GetAttachNum() ) + if attachment1 != nil then + self:SetPos( attachment1.Pos ) + self:SetAngles( attachment1.Ang ) + self:Fire("setparentattachment", target:GetAttachments()[self:GetAttachNum()].name, 0.01) + self:SetTargetEnt(self) + end + + //If target2 is using an attachment, then spawn an entity at the pos/ang of that attachment to use as the target for control point 2. + if IsValid(target2) then + local attachment2 = target2:GetAttachment( self:GetAttachNum2() ) + if attachment2 != nil then + if self.endpoint then self.endpoint = nil end + self.endpoint = ents.Create( "info_particle_system" ) + self.endpoint:SetKeyValue( "effect_name", "combineball" ) + self.endpoint:SetParent(target2) + + self.endpoint:SetPos( attachment2.Pos ) + self.endpoint:SetAngles( attachment2.Ang ) + self.endpoint:Fire("setparentattachment", target2:GetAttachments()[self:GetAttachNum2()].name, 0.01) + + self:DeleteOnRemove(self.endpoint) + self.endpoint:Spawn() + self.endpoint:Activate() + self:SetTargetEnt2(self.endpoint) + end + end + + else + + //Make a table of control point information, so we only have to do this once instead of every time AttachParticle runs. + + local clrtb = nil + if self:GetColor().r == 0 and self:GetColor().g == 0 and self:GetColor().b == 0 then + //MsgN("color = false") + else + //MsgN("color = true") + if self:GetColor().a == 1 then + clrtb = { position = Vector( self:GetColor().r / 255, self:GetColor().g / 255, self:GetColor().b / 255 ) } + else + clrtb = { position = Vector( self:GetColor().r, self:GetColor().g, self:GetColor().b ) } + end + end + self.cpointtable = {} + self.cpointtable[1] = { entity = target, attachtype = PATTACH_ABSORIGIN_FOLLOW } + + if IsValid(target2) then + self.cpointtable[2] = { entity = target2, attachtype = PATTACH_ABSORIGIN_FOLLOW } + else + if clrtb then + self.cpointtable[2] = clrtb + else + self.cpointtable[2] = self.cpointtable[1] + end + end + + for i = 3, 64 do + if clrtb then + self.cpointtable[i] = clrtb + else + self.cpointtable[i] = self.cpointtable[1] + end + end + + PrecacheParticleSystem(self:GetEffectName()) + + end + + end + self:SetModel("models/hunter/plates/plate.mdl") //it's a tiny block so that model-covering fx show up at a single point instead of all over an invisible error model + self:SetNoDraw(true) + + + if self:GetRepeatRate() > 0 then + //we're going to be repeating the effect, so let's set that up and let think do the test + self.NextRepeat = 0 + else + //we won't be repeating the effect, so just attach it right now + if self:GetActive() then self:AttachParticle() end + end + +end + + + + +function ENT:Think() + + if SERVER then return end + + if self:GetNumpadState() == "off" then + //MsgN("turn off") + self:SetNumpadState("") + self:RemoveParticle(false,self:GetEffectName()) + end + + if self:GetNumpadState() == "on" then + //MsgN("turn on") + self:SetNumpadState("") + if self:GetRepeatRate() > 0 then + //we're going to be repeating the effect, so let's set that up and let think do the test + self.NextRepeat = 0 + else + //we won't be repeating the effect, so just attach it right now + if self:GetActive() then self:AttachParticle() end + end + end + + + if self:GetActive() == true and self:GetRepeatRate() > 0 then + if !( self.NextRepeat > CurTime() ) then + //the repeat function is built into removeparticle so that we can be sure the old particle is gone before we add a new one + //disabling repeat safety lets the user bypass this if they want + if self:GetRepeatSafety() then + self:RemoveParticle(true,self:GetEffectName()) + else + self:AttachParticle() + end + self.NextRepeat = CurTime() + self:GetRepeatRate() + end + end + + self:NextThink(CurTime()) + return true + +end + + + + +function ENT:AttachParticle() + + if SERVER then return end + + local target = self:GetTargetEnt() + local attachnum = self:GetAttachNum() + local effectname = self:GetEffectName() + local target2 = self:GetTargetEnt2() + local attachnum2 = self:GetAttachNum2() + + if effectname == nil or !target:IsValid() then return end + + + if string.StartWith( effectname, "!UTILEFFECT!" ) then + + //Create a util effect + + //Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly + + local effectscale = self:GetUtilEffectInfo().x + local effectmagnitude = self:GetUtilEffectInfo().y + local effectradius = self:GetUtilEffectInfo().z + + local luaeffectdata = EffectData() + luaeffectdata:SetEntity( target ) + if ( string.find(effectname, "Tracer", 0, true) != nil ) then luaeffectdata:SetScale(5000) else luaeffectdata:SetScale( effectscale ) end //for tracer effects, scale is the speed of the bullet, so we need to keep this high + luaeffectdata:SetMagnitude( effectmagnitude ) + luaeffectdata:SetRadius(effectradius ) + + //flags can be set by typing !FLAG#! at the end of the effect name + luaeffectdata:SetFlags( 0 ) + if string.EndsWith( effectname, "!" ) then + if string.find( effectname, "!FLAG1!" ) then luaeffectdata:SetFlags( 1 ) effectname = string.Replace( effectname, "!FLAG1!", "" ) end + if string.find( effectname, "!FLAG2!" ) then luaeffectdata:SetFlags( 2 ) effectname = string.Replace( effectname, "!FLAG2!", "" ) end + if string.find( effectname, "!FLAG3!" ) then luaeffectdata:SetFlags( 3 ) effectname = string.Replace( effectname, "!FLAG3!", "" ) end + if string.find( effectname, "!FLAG4!" ) then luaeffectdata:SetFlags( 4 ) effectname = string.Replace( effectname, "!FLAG4!", "" ) end + if string.find( effectname, "!FLAG5!" ) then luaeffectdata:SetFlags( 5 ) effectname = string.Replace( effectname, "!FLAG5!", "" ) end + if string.find( effectname, "!FLAG6!" ) then luaeffectdata:SetFlags( 6 ) effectname = string.Replace( effectname, "!FLAG6!", "" ) end + if string.find( effectname, "!FLAG7!" ) then luaeffectdata:SetFlags( 7 ) effectname = string.Replace( effectname, "!FLAG7!", "" ) end + if string.find( effectname, "!FLAG8!" ) then luaeffectdata:SetFlags( 8 ) effectname = string.Replace( effectname, "!FLAG8!", "" ) end + if string.find( effectname, "!FLAG9!" ) then luaeffectdata:SetFlags( 9 ) effectname = string.Replace( effectname, "!FLAG9!", "" ) end + end + + //colors can also be set the same way + luaeffectdata:SetColor(0) + if string.EndsWith( effectname, "!" ) then + if string.find( effectname, "!COLOR1!" ) then luaeffectdata:SetColor( 1 ) effectname = string.Replace( effectname, "!COLOR1!", "" ) end + if string.find( effectname, "!COLOR2!" ) then luaeffectdata:SetColor( 2 ) effectname = string.Replace( effectname, "!COLOR2!", "" ) end + if string.find( effectname, "!COLOR3!" ) then luaeffectdata:SetColor( 3 ) effectname = string.Replace( effectname, "!COLOR3!", "" ) end + if string.find( effectname, "!COLOR4!" ) then luaeffectdata:SetColor( 4 ) effectname = string.Replace( effectname, "!COLOR4!", "" ) end + if string.find( effectname, "!COLOR5!" ) then luaeffectdata:SetColor( 5 ) effectname = string.Replace( effectname, "!COLOR5!", "" ) end + if string.find( effectname, "!COLOR6!" ) then luaeffectdata:SetColor( 6 ) effectname = string.Replace( effectname, "!COLOR6!", "" ) end + if string.find( effectname, "!COLOR7!" ) then luaeffectdata:SetColor( 7 ) effectname = string.Replace( effectname, "!COLOR7!", "" ) end + if string.find( effectname, "!COLOR8!" ) then luaeffectdata:SetColor( 8 ) effectname = string.Replace( effectname, "!COLOR8!", "" ) end + if string.find( effectname, "!COLOR9!" ) then luaeffectdata:SetColor( 9 ) effectname = string.Replace( effectname, "!COLOR9!", "" ) end + end + + //dumb situational crap + if string.find( string.lower(effectname), "shakeropes" ) then luaeffectdata:SetMagnitude( effectmagnitude * 20 ) end + if string.find( string.lower(effectname), "thumperdust" ) then luaeffectdata:SetScale( effectscale * 50 ) end + if string.find( string.lower(effectname), "bloodspray" ) then luaeffectdata:SetScale( effectscale * 4 ) end + + if target:GetAttachment(attachnum) != nil then + luaeffectdata:SetAttachment( attachnum ) + luaeffectdata:SetStart( target:GetAttachment( attachnum ).Pos ) + luaeffectdata:SetOrigin( target:GetAttachment( attachnum ).Pos ) + luaeffectdata:SetAngles( target:GetAttachment( attachnum ).Ang ) + luaeffectdata:SetNormal( target:GetAttachment( attachnum ).Ang:Forward() ) + else + luaeffectdata:SetStart( target:GetPos() ) + luaeffectdata:SetOrigin( target:GetPos() ) + luaeffectdata:SetAngles( target:GetAngles() ) + luaeffectdata:SetNormal( target:GetAngles():Forward() ) + end + + if IsValid(target2) then + if target2:GetAttachment(attachnum2) != nil then + luaeffectdata:SetOrigin( target2:GetAttachment( attachnum2 ).Pos ) + luaeffectdata:SetNormal( target2:GetAttachment( attachnum2 ).Ang:Forward() ) + else + luaeffectdata:SetOrigin( target2:GetPos() ) + luaeffectdata:SetNormal( target2:GetAngles():Forward() ) + end + end + + util.Effect( string.Replace( effectname, "!UTILEFFECT!", "" ), luaeffectdata ) + + else + + //Create a particlesystem effect + + target:CreateParticleEffect(effectname,self.cpointtable) + + end + +end + + + +function ENT:RemoveParticle(arewerepeating,effectwereremoving) + + local target = self:GetTargetEnt() + + if CLIENT then + if target:IsValid() then target:StopParticleEmission(effectwereremoving) end + + //StopParticleEmission is broken and removes ALL effects on the entity, not just the one specified, so to try and counteract this we'll reactivate them. + if target != self then + for _, asdf in pairs( ents:GetAll() ) do + if asdf != self then + if IsValid(asdf) and asdf:GetClass() == "particlecontroller_normal" and asdf:GetParent() == target then + //Make sure the ent is active, but not already creating the particle by itself. We don't want to double their particle by mistake. + if asdf.GetActive and asdf:GetActive() == true and asdf:GetNumpadState() == "" then asdf:AttachParticle() end + end + end + end + end + end + + if arewerepeating == true then self:AttachParticle() end + +end + + + + +//numpad functions +if SERVER then + +local function NumpadPress( pl, ent ) + + if ( !ent || ent == NULL ) then return end + + if ( ent:GetToggle() ) then + if ent:GetActive() == false then + ent:SetActive(true) + ent:SetNumpadState("on") + else + ent:SetActive(false) + ent:SetNumpadState("off") + end + else + ent:SetActive( true ) + ent:SetNumpadState("on") + end + +end + +local function NumpadRelease( pl, ent ) + + if ( !ent || ent == NULL ) then return end + + if ( ent:GetToggle() ) then return end + + ent:SetActive(false) + ent:SetNumpadState("off") + +end + +numpad.Register( "Particle_Press", NumpadPress ) +numpad.Register( "Particle_Release", NumpadRelease ) + +end + + + + +function ENT:OnRemove() + self:RemoveParticle(false,self:GetEffectName()) +end + + + + +//don't duplicate this +duplicator.RegisterEntityClass( "particlecontroller_normal", function( ply, data ) +end, "Data" ) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_proj.lua b/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_proj.lua new file mode 100644 index 0000000..e3e6679 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_proj.lua @@ -0,0 +1,583 @@ +AddCSLuaFile() + +ENT.Base = "base_gmodentity" +ENT.PrintName = "Particle Controller - Projectile" +ENT.Author = "" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_NONE + + + + +function ENT:SetupDataTables() + + self:NetworkVar( "Entity", 0, "TargetEnt" ); + + + self:NetworkVar( "String", 0, "ProjFX_EffectName" ); + self:NetworkVar( "Vector", 0, "ProjFX_UtilEffectInfo" ); //x = scale, y = magnitude, x = radius + self:NetworkVar( "Vector", 1, "ProjFX_ColorInfo" ); + + self:NetworkVar( "String", 1, "ImpactFX_EffectName" ); + self:NetworkVar( "Vector", 2, "ImpactFX_UtilEffectInfo" ); //x = scale, y = magnitude, x = radius + self:NetworkVar( "Vector", 3, "ImpactFX_ColorInfo" ); + + self:NetworkVar( "Int", 0, "AttachNum" ); + self:NetworkVar( "Float", 0, "RepeatRate" ); + + self:NetworkVar( "String", 2, "ProjModel" ); + self:NetworkVar( "Int", 1, "ProjModel_AttachNum" ); + self:NetworkVar( "Bool", 0, "ProjModel_Invis" ); + self:NetworkVar( "Float", 1, "ImpactFX_EffectLifetime" ); + + self:NetworkVar( "Float", 2, "ProjEnt_Spread" ); + self:NetworkVar( "Int", 2, "ProjEnt_Velocity" ); + self:NetworkVar( "Bool", 1, "ProjEnt_Gravity" ); + self:NetworkVar( "Int", 3, "ProjEnt_Angle" ); + self:NetworkVar( "Int", 4, "ProjEnt_Spin" ); + self:NetworkVar( "Bool", 2, "ProjEnt_DemomanFix" ); + self:NetworkVar( "Float", 3, "ProjEnt_Lifetime_PreHit" ); + self:NetworkVar( "Float", 4, "ProjEnt_Lifetime_PostHit" ); + self:NetworkVar( "Bool", 3, "ProjEnt_Serverside" ); + + + self:NetworkVar( "Bool", 4, "Active" ); + self:NetworkVar( "Bool", 5, "Toggle" ); + self:NetworkVar( "Int", 5, "NumpadKey" ); + self:NetworkVar( "String", 3, "NumpadState" ); + +end + + + + +function ENT:Initialize() + + util.PrecacheModel(self:GetProjModel()) + local target = self:GetTargetEnt() + + + if SERVER then + + //Use ourselves as the target - we'll fire projectiles from our own position and angles + local attachment1 = target:GetAttachment( self:GetAttachNum() ) + if attachment1 != nil then + self:SetPos( attachment1.Pos ) + self:SetAngles( attachment1.Ang ) + self:Fire("setparentattachment", target:GetAttachments()[self:GetAttachNum()].name, 0.01) + self:SetTargetEnt(self) + end + + else + + //Set things up for a particlesystem effect + + local projfx_effectname = self:GetProjFX_EffectName() + if projfx_effectname != "" and !string.StartWith( projfx_effectname, "!UTILEFFECT!" ) then + PrecacheParticleSystem(projfx_effectname) + end + + local impactfx_effectname = self:GetImpactFX_EffectName() + if impactfx_effectname != "" and !string.StartWith( impactfx_effectname, "!UTILEFFECT!" ) then + PrecacheParticleSystem(impactfx_effectname) + end + + end + //super niche bug fix: if we're in multiplayer and our model has any attachment points, then we'll cause visual jittering in the buildbonepositions callback of + //the entity we're parented to. originally we were using our own model to store the projectile model, but that was causing the aforementioned bug, so now we're + //storing the projectile model separately and having this entity use a neutral model that won't cause problems. + self:SetModel("models/hunter/plates/plate.mdl") + self:SetNoDraw(true) + + + //Don't let players set the repeat rate to a really low amount that lets them spam tons of props every second + if self:GetRepeatRate() > 0 and self:GetRepeatRate() < 0.1 then self:SetRepeatRate(0.1) end + + if self:GetRepeatRate() > 0 then + //we're going to be repeating the effect, so let's set that up and let think do the rest + self.NextRepeat = 0 + else + //we won't be repeating the effect, so just attach it right now + if self:GetActive() then self:AttachParticle() end + end + +end + + + + +function ENT:Think() + + if self:GetProjEnt_Serverside() then + if CLIENT then return end + else + if SERVER then return end + end + + if self:GetNumpadState() == "off" then + //MsgN("turn off") + self:SetNumpadState("") + end + + if self:GetNumpadState() == "on" then + //MsgN("turn on") + self:SetNumpadState("") + if self:GetRepeatRate() > 0 then + //we're going to be repeating the effect, so let's set that up and let think do the rest + self.NextRepeat = 0 + else + //we won't be repeating the effect, so just attach it right now + if self:GetActive() then self:AttachParticle() end + end + end + + + if self:GetActive() == true and self:GetRepeatRate() > 0 then + if !( self.NextRepeat > CurTime() ) then + self:AttachParticle() + self.NextRepeat = CurTime() + self:GetRepeatRate() + end + end + + self:NextThink(CurTime()) + return true + +end + + + + +function ENT:AttachParticle() + + if !self then return end + + if self:GetProjEnt_Serverside() then + if CLIENT then return end + else + if SERVER then return end + end + + local projent_spread = math.Clamp( self:GetProjEnt_Spread(), 0, 4) + local projent_velocity = self:GetProjEnt_Velocity() + local projent_gravity = self:GetProjEnt_Gravity() + local projent_angle = self:GetProjEnt_Angle() + local projent_spin = self:GetProjEnt_Spin() + local projent_demomanfix = self:GetProjEnt_DemomanFix() + local projent_lifetime_prehit = math.Clamp( self:GetProjEnt_Lifetime_PreHit(), 0, 10) + local projent_lifetime_posthit = math.Clamp( self:GetProjEnt_Lifetime_PostHit(), 0, 10) + + local projfx_effectname = self:GetProjFX_EffectName() + local projmodel_attachnum = self:GetProjModel_AttachNum() + + local impactfx_effectname = self:GetImpactFX_EffectName() + local impactfx_effectlifetime = math.Clamp( self:GetImpactFX_EffectLifetime(), 0, 10) + local impactfx_utileffectinfo = self:GetImpactFX_UtilEffectInfo() + local impactfx_colorinfo = self:GetImpactFX_ColorInfo() + + + + //Create the projectile entity + local proj = nil + if SERVER then + proj = ents.Create("parctrl_dummyent") + proj:SetModel(self:GetProjModel()) + end + if CLIENT then + proj = octolib.createDummy(self:GetProjModel()) + end + + if !util.IsValidProp(self:GetProjModel()) then + proj:PhysicsInitBox(proj:GetModelBounds()) + else + proj:PhysicsInit(SOLID_VPHYSICS) + end + proj:GetPhysicsObject():Wake() + + proj:SetNoDraw(self:GetProjModel_Invis()) + proj:SetSkin(self:GetSkin()) + proj:SetMaterial(self:GetMaterial()) + proj:SetPos(self:GetPos()) + + + proj:Spawn() + proj:Activate() + local projphys = proj:GetPhysicsObject() + + local selfang = self:GetAngles() + //a lot of attachment points are oriented at an angle on the roll axis - correct this, we want the default projectile angle to be upright + if self:GetParent() != NULL then //this returns a null entity if the player is outside of the visleaf, be careful + if self:GetParent():GetAttachment( self:GetAttachNum() ) then + local _, attachang = WorldToLocal(self:GetPos(), selfang, self:GetParent():GetPos(), self:GetParent():GetAngles()) + selfang = Angle(selfang.p,selfang.y,selfang.r - attachang.r) + end + end + //muzzle attachments on demoman weapons are oriented 90 degrees to the side for some reason - give players an option to fix this + if projent_demomanfix == true then selfang:RotateAroundAxis( selfang:Up(), -90 ) end + + //spread + local projang = Angle(selfang.p,selfang.y,selfang.r) //self:GetAngles() + local randang = AngleRand() + projang:RotateAroundAxis( projang:Forward(), randang.r ) + projang:RotateAroundAxis( projang:Right(), randang.p * (projent_spread / 2) ) + projang:RotateAroundAxis( projang:Up(), randang.y * (projent_spread / 4) ) + //set the velocity + projphys:SetVelocity(projang:Forward() * projent_velocity) + //now de-randomize the roll - we only needed to do that for the velocity + projang:RotateAroundAxis( projang:Forward(), -randang.r ) + + //change the angle of the projectile + local rotationang = Angle(0,0,0) + //0 = forward, don't change it + //1 = left + if projent_angle == 1 then projang:RotateAroundAxis( projang:Up(), 90 ) rotationang = Angle(0,-90,0) + //2 = right + elseif projent_angle == 2 then projang:RotateAroundAxis( projang:Up(), -90 ) rotationang = Angle(0,90,0) + //3 = up + elseif projent_angle == 3 then projang:RotateAroundAxis( projang:Right(), 90 ) rotationang = Angle(90,0,0) + //4 = down + elseif projent_angle == 4 then projang:RotateAroundAxis( projang:Right(), -90 ) rotationang = Angle(-90,0,0) + //5 = back + elseif projent_angle == 5 then projang:RotateAroundAxis( projang:Up(), 180 ) rotationang = Angle(0,180,0) end + proj:SetAngles(projang) + + //add spin - spin should be the same regardless of which way we've oriented the prop with projent_angle + //0 - don't spin + //1 - spin pitch + if projent_spin == 1 then projphys:AddAngleVelocity( rotationang:Right() * -350 ) + //2 - spin yaw + elseif projent_spin == 2 then projphys:AddAngleVelocity( rotationang:Forward() * 350 ) + //3 - spin roll + elseif projent_spin == 3 then projphys:AddAngleVelocity( rotationang:Up() * -350 ) + //4 - spin random + elseif projent_spin == 4 then projphys:AddAngleVelocity( VectorRand() * -350 ) end + + projphys:EnableGravity(projent_gravity) + projphys:SetMaterial("gmod_silent") //don't play physics sounds + proj:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS) //don't collide with other projectiles + proj:SetOwner(self:GetParent()) //don't collide with the prop that the effect ent is parented to + if SERVER then self:DeleteOnRemove(proj) end //delete the proj if the effect ent is removed - i'd love to do this for clientside projs too but this function is serverside only + + + + //Create the projectile effect, if enabled + if projfx_effectname != "" then + + local projfxent = proj + + if SERVER then + //since we can't call util.Effect or CreateParticleEffect on the server, we have to get the ent to do it itself + projfxent:SetEffectName(projfx_effectname) + projfxent:SetColorInfo(self:GetProjFX_ColorInfo()) + projfxent:SetUtilEffectInfo(self:GetProjFX_UtilEffectInfo()) + projfxent:SetAttachNum(projmodel_attachnum) + end + if CLIENT then + if string.StartWith( projfx_effectname, "!UTILEFFECT!" ) then + + //Create a util effect + + //Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly + + local effectscale = self:GetProjFX_UtilEffectInfo().x + local effectmagnitude = self:GetProjFX_UtilEffectInfo().y + local effectradius = self:GetProjFX_UtilEffectInfo().z + + local projeffectdata = EffectData() + projeffectdata:SetEntity( projfxent ) + //if ( string.find(projfx_effectname, "Tracer", 0, true) != nil ) then projeffectdata:SetScale(5000) else projeffectdata:SetScale( effectscale ) end //for tracer effects, scale is the speed of the bullet, so we need to keep this high; useless for a projectile effect + projeffectdata:SetScale( effectscale ) + projeffectdata:SetMagnitude( effectmagnitude ) + projeffectdata:SetRadius(effectradius ) + + //flags can be set by typing !FLAG#! at the end of the effect name + projeffectdata:SetFlags( 0 ) + if string.EndsWith( projfx_effectname, "!" ) then + if string.find( projfx_effectname, "!FLAG1!" ) then projeffectdata:SetFlags( 1 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG1!", "" ) end + if string.find( projfx_effectname, "!FLAG2!" ) then projeffectdata:SetFlags( 2 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG2!", "" ) end + if string.find( projfx_effectname, "!FLAG3!" ) then projeffectdata:SetFlags( 3 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG3!", "" ) end + if string.find( projfx_effectname, "!FLAG4!" ) then projeffectdata:SetFlags( 4 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG4!", "" ) end + if string.find( projfx_effectname, "!FLAG5!" ) then projeffectdata:SetFlags( 5 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG5!", "" ) end + if string.find( projfx_effectname, "!FLAG6!" ) then projeffectdata:SetFlags( 6 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG6!", "" ) end + if string.find( projfx_effectname, "!FLAG7!" ) then projeffectdata:SetFlags( 7 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG7!", "" ) end + if string.find( projfx_effectname, "!FLAG8!" ) then projeffectdata:SetFlags( 8 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG8!", "" ) end + if string.find( projfx_effectname, "!FLAG9!" ) then projeffectdata:SetFlags( 9 ) projfx_effectname = string.Replace( projfx_effectname, "!FLAG9!", "" ) end + end + + //colors can also be set the same way + projeffectdata:SetColor(0) + if string.EndsWith( projfx_effectname, "!" ) then + if string.find( projfx_effectname, "!COLOR1!" ) then projeffectdata:SetColor( 1 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR1!", "" ) end + if string.find( projfx_effectname, "!COLOR2!" ) then projeffectdata:SetColor( 2 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR2!", "" ) end + if string.find( projfx_effectname, "!COLOR3!" ) then projeffectdata:SetColor( 3 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR3!", "" ) end + if string.find( projfx_effectname, "!COLOR4!" ) then projeffectdata:SetColor( 4 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR4!", "" ) end + if string.find( projfx_effectname, "!COLOR5!" ) then projeffectdata:SetColor( 5 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR5!", "" ) end + if string.find( projfx_effectname, "!COLOR6!" ) then projeffectdata:SetColor( 6 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR6!", "" ) end + if string.find( projfx_effectname, "!COLOR7!" ) then projeffectdata:SetColor( 7 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR7!", "" ) end + if string.find( projfx_effectname, "!COLOR8!" ) then projeffectdata:SetColor( 8 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR8!", "" ) end + if string.find( projfx_effectname, "!COLOR9!" ) then projeffectdata:SetColor( 9 ) projfx_effectname = string.Replace( projfx_effectname, "!COLOR9!", "" ) end + end + + //dumb situational crap + if string.find( string.lower(projfx_effectname), "shakeropes" ) then projeffectdata:SetMagnitude( effectmagnitude * 20 ) end + if string.find( string.lower(projfx_effectname), "thumperdust" ) then projeffectdata:SetScale( effectscale * 50 ) end + if string.find( string.lower(projfx_effectname), "bloodspray" ) then projeffectdata:SetScale( effectscale * 4 ) end + + //just in case someone makes a utileffect that works as a projectile effect + if projfxent:GetAttachment(projmodel_attachnum) != nil then + projeffectdata:SetStart( projfxent:GetAttachment( projmodel_attachnum ).Pos ) + projeffectdata:SetOrigin( projfxent:GetAttachment( projmodel_attachnum ).Pos ) + projeffectdata:SetAngles( projfxent:GetAttachment( projmodel_attachnum ).Ang ) + projeffectdata:SetNormal( projfxent:GetAttachment( projmodel_attachnum ).Ang:Forward() ) + else + projeffectdata:SetStart( projfxent:GetPos() ) + projeffectdata:SetOrigin( projfxent:GetPos() ) + projeffectdata:SetAngles( projfxent:GetAngles() ) + projeffectdata:SetNormal( projfxent:GetAngles():Forward() ) + end + + util.Effect( string.Replace( projfx_effectname, "!UTILEFFECT!", "" ), projeffectdata ) + + else + + //Create a particlesystem effect + + //Since we can't specify attachment points with Entity:CreateParticleEffect(), create an entity there to use as a target + local attachment1 = proj:GetAttachment( projmodel_attachnum ) + if attachment1 != nil then + //if SERVER then + // projfxent = ents.Create("parctrl_dummyent") + // projfxent:SetModel("models/hunter/plates/plate.mdl") + //end + if CLIENT then + projfxent = octolib.createDummy("models/hunter/plates/plate.mdl") + end + + projfxent:SetParent(proj, projmodel_attachnum - 1) + projfxent:SetPos( attachment1.Pos ) + projfxent:SetAngles( attachment1.Ang ) + projfxent:SetNoDraw(true) + + projfxent:Spawn() + projfxent:Activate() + + proj:CallOnRemove("RemoveProjFX", function() if IsValid(projfxent) then projfxent:Remove() end end) + end + + + local clrtb = nil + if self:GetProjFX_ColorInfo() == Vector(0,0,0) then + //MsgN("color = false") + else + //MsgN("color = true") + clrtb = { position = self:GetProjFX_ColorInfo() } + end + local projcpointtable = {} + projcpointtable[1] = { entity = projfxent, attachtype = PATTACH_ABSORIGIN_FOLLOW } + + for i = 2, 64 do + if clrtb then + projcpointtable[i] = clrtb + else + projcpointtable[i] = projcpointtable[1] + end + end + + projfxent:CreateParticleEffect(projfx_effectname,projcpointtable) + + end + end + end + + + + local function projexpire(ent, pos, ang) + + //Create the impact effect, if enabled + if impactfx_effectname != "" then + + //Create a new entity to attach the impact effect to - we can't use proj since we're removing it immediately after this + local impactfxent = nil + if SERVER then + impactfxent = ents.Create("parctrl_dummyent") + impactfxent:SetModel("models/hunter/plates/plate.mdl") + end + if CLIENT then + impactfxent = octolib.createDummy("models/hunter/plates/plate.mdl") + end + impactfxent:SetPos( pos or proj:GetPos() ) + impactfxent:SetAngles( ang or proj:GetAngles() ) + impactfxent:SetNoDraw(true) + timer.Simple(impactfx_effectlifetime, function() if IsValid(impactfxent) then impactfxent:Remove() end; end) + impactfxent:Spawn() + impactfxent:Activate() + + if SERVER then + //since we can't call util.Effect or CreateParticleEffect on the server, we have to get the ent to do it itself + impactfxent:SetEffectName(impactfx_effectname) + impactfxent:SetColorInfo(impactfx_colorinfo) + impactfxent:SetUtilEffectInfo(impactfx_utileffectinfo) + impactfxent:SetAttachNum(0) + end + if CLIENT then + if string.StartWith( impactfx_effectname, "!UTILEFFECT!" ) then + + //Create a util effect + + //Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly + + local effectscale = impactfx_utileffectinfo.x + local effectmagnitude = impactfx_utileffectinfo.y + local effectradius = impactfx_utileffectinfo.z + + local impacteffectdata = EffectData() + impacteffectdata:SetEntity(impactfxent) + //if ( string.find(impactfx_effectname, "Tracer", 0, true) != nil ) then impacteffectdata:SetScale(5000) else impacteffectdata:SetScale(effectscale) end //for tracer effects, scale is the speed of the bullet, so we need to keep this high; useless for an impact effect + impacteffectdata:SetScale(effectscale) + impacteffectdata:SetMagnitude(effectmagnitude) + impacteffectdata:SetRadius(effectradius) + + //flags can be set by typing !FLAG#! at the end of the effect name + impacteffectdata:SetFlags( 0 ) + if string.EndsWith( impactfx_effectname, "!" ) then + if string.find( impactfx_effectname, "!FLAG1!" ) then impacteffectdata:SetFlags( 1 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG1!", "" ) end + if string.find( impactfx_effectname, "!FLAG2!" ) then impacteffectdata:SetFlags( 2 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG2!", "" ) end + if string.find( impactfx_effectname, "!FLAG3!" ) then impacteffectdata:SetFlags( 3 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG3!", "" ) end + if string.find( impactfx_effectname, "!FLAG4!" ) then impacteffectdata:SetFlags( 4 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG4!", "" ) end + if string.find( impactfx_effectname, "!FLAG5!" ) then impacteffectdata:SetFlags( 5 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG5!", "" ) end + if string.find( impactfx_effectname, "!FLAG6!" ) then impacteffectdata:SetFlags( 6 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG6!", "" ) end + if string.find( impactfx_effectname, "!FLAG7!" ) then impacteffectdata:SetFlags( 7 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG7!", "" ) end + if string.find( impactfx_effectname, "!FLAG8!" ) then impacteffectdata:SetFlags( 8 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG8!", "" ) end + if string.find( impactfx_effectname, "!FLAG9!" ) then impacteffectdata:SetFlags( 9 ) impactfx_effectname = string.Replace( impactfx_effectname, "!FLAG9!", "" ) end + end + + //colors can also be set the same way + impacteffectdata:SetColor(0) + if string.EndsWith( impactfx_effectname, "!" ) then + if string.find( impactfx_effectname, "!COLOR1!" ) then impacteffectdata:SetColor( 1 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR1!", "" ) end + if string.find( impactfx_effectname, "!COLOR2!" ) then impacteffectdata:SetColor( 2 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR2!", "" ) end + if string.find( impactfx_effectname, "!COLOR3!" ) then impacteffectdata:SetColor( 3 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR3!", "" ) end + if string.find( impactfx_effectname, "!COLOR4!" ) then impacteffectdata:SetColor( 4 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR4!", "" ) end + if string.find( impactfx_effectname, "!COLOR5!" ) then impacteffectdata:SetColor( 5 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR5!", "" ) end + if string.find( impactfx_effectname, "!COLOR6!" ) then impacteffectdata:SetColor( 6 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR6!", "" ) end + if string.find( impactfx_effectname, "!COLOR7!" ) then impacteffectdata:SetColor( 7 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR7!", "" ) end + if string.find( impactfx_effectname, "!COLOR8!" ) then impacteffectdata:SetColor( 8 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR8!", "" ) end + if string.find( impactfx_effectname, "!COLOR9!" ) then impacteffectdata:SetColor( 9 ) impactfx_effectname = string.Replace( impactfx_effectname, "!COLOR9!", "" ) end + end + + //dumb situational crap + if string.find( string.lower(impactfx_effectname), "shakeropes" ) then impacteffectdata:SetMagnitude( effectmagnitude * 20 ) end + if string.find( string.lower(impactfx_effectname), "thumperdust" ) then impacteffectdata:SetScale( effectscale * 50 ) end + if string.find( string.lower(impactfx_effectname), "bloodspray" ) then impacteffectdata:SetScale( effectscale * 4 ) end + + impacteffectdata:SetStart( impactfxent:GetPos() ) + impacteffectdata:SetOrigin( impactfxent:GetPos() ) + impacteffectdata:SetAngles( impactfxent:GetAngles() ) + impacteffectdata:SetNormal( impactfxent:GetAngles():Forward() ) + + util.Effect( string.Replace( impactfx_effectname, "!UTILEFFECT!", "" ), impacteffectdata ) + + else + + //Create a particlesystem effect + + local clrtb = nil + if impactfx_colorinfo == Vector(0,0,0) then + //MsgN("color = false") + else + //MsgN("color = true") + clrtb = { position = impactfx_colorinfo } + end + local impactcpointtable = {} + impactcpointtable[1] = { entity = impactfxent, attachtype = PATTACH_ABSORIGIN_FOLLOW } + + for i = 2, 64 do + if clrtb then + impactcpointtable[i] = clrtb + else + impactcpointtable[i] = impactcpointtable[1] + end + end + + impactfxent:CreateParticleEffect(impactfx_effectname,impactcpointtable) + + end + end + end + + ent:Remove() + + end + + timer.Simple(projent_lifetime_prehit, function() if IsValid(proj) then projexpire(proj) end; end) + + local function projcollide(entity, data) + if IsValid(entity) then + if entity.HasHitSomething then return end //there's no reason to call this more than once + entity.HasHitSomething = true + + if projent_lifetime_posthit == 0 then + //if lifetime_posthit is 0, then move the impactfx to the pos and angle of impact - + //we still need to use a timer because directly calling ent:Remove() in a PhysicsCollide callback crashes the game + timer.Simple(0, function() if IsValid(proj) then projexpire(proj, data.HitPos, -data.HitNormal:Angle()) end; end) + else + timer.Simple(projent_lifetime_posthit, function() if IsValid(proj) then projexpire(proj) end; end) + end + end + end + if CLIENT then + proj:AddCallback("PhysicsCollide", projcollide) + else + proj.PhysicsCollide = projcollide //AddCallback inexplicably won't work on our sent we're using serverside, but overriding its physicscollide function seems to do it + end + +end + + + + +//numpad functions +if SERVER then + +local function NumpadPress( pl, ent ) + + if ( !ent || ent == NULL ) then return end + + if ( ent:GetToggle() ) then + if ent:GetActive() == false then + ent:SetActive(true) + ent:SetNumpadState("on") + else + ent:SetActive(false) + ent:SetNumpadState("off") + end + else + ent:SetActive( true ) + ent:SetNumpadState("on") + end + +end + +local function NumpadRelease( pl, ent ) + + if ( !ent || ent == NULL ) then return end + + if ( ent:GetToggle() ) then return end + + ent:SetActive(false) + ent:SetNumpadState("off") + +end + +numpad.Register( "Particle_Press", NumpadPress ) +numpad.Register( "Particle_Release", NumpadRelease ) + +end + + + + +//don't duplicate this +duplicator.RegisterEntityClass( "particlecontroller_proj", function( ply, data ) +end, "Data" ) diff --git a/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_tracer.lua b/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_tracer.lua new file mode 100644 index 0000000..658c610 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/particlecontroller_tracer.lua @@ -0,0 +1,417 @@ +AddCSLuaFile() + +ENT.Base = "base_gmodentity" +ENT.PrintName = "Particle Controller - Tracer" +ENT.Author = "" + +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_NONE + + + + +function ENT:SetupDataTables() + + self:NetworkVar( "Entity", 0, "TargetEnt" ); + + self:NetworkVar( "String", 0, "EffectName" ); + self:NetworkVar( "String", 1, "NumpadState" ); + self:NetworkVar( "String", 2, "Impact_EffectName" ); + + self:NetworkVar( "Float", 0, "RepeatRate" ); + self:NetworkVar( "Float", 1, "TracerSpread" ); + self:NetworkVar( "Float", 2, "EffectLifetime" ); + + self:NetworkVar( "Int", 0, "AttachNum" ); + self:NetworkVar( "Int", 1, "NumpadKey" ); + self:NetworkVar( "Int", 2, "TracerCount" ); + + self:NetworkVar( "Bool", 0, "Active" ); + self:NetworkVar( "Bool", 1, "Toggle" ); + self:NetworkVar( "Bool", 2, "LeaveBulletHoles" ); + + //we have 3 pieces of information we need for util.effect, and they're not going to be changed after the ent's been spawned, so there's no reason we can't save space by storing them all in this vector + //self:NetworkVar( "Vector", 0, "UtilEffectInfo" ); //x = scale, y = magnitude, x = radius + self:NetworkVar( "Vector", 0, "Impact_UtilEffectInfo" ); //x = scale, y = magnitude, x = radius + self:NetworkVar( "Vector", 1, "Impact_ColorInfo" ); + +end + + + + +function ENT:Initialize() + + local target = self:GetTargetEnt() + + + if SERVER then + + //Use ourselves as the target for control point 1 - because of how we have tracer effects set up, we're going to do this for both util and .pcf effects + local attachment1 = target:GetAttachment( self:GetAttachNum() ) + if attachment1 != nil then + self:SetPos( attachment1.Pos ) + self:SetAngles( attachment1.Ang ) + self:Fire("setparentattachment", target:GetAttachments()[self:GetAttachNum()].name, 0.01) + self:SetTargetEnt(self) + end + + else + + //Set things up for a particlesystem effect + + if !string.StartWith( self:GetEffectName(), "!UTILEFFECT!" ) then + + //Make a table of control point information, so we only have to do this once instead of every time AttachParticle runs. + + local clrtb = nil + if self:GetColor().r == 0 and self:GetColor().g == 0 and self:GetColor().b == 0 then + //MsgN("color = false") + else + //MsgN("color = true") + if self:GetColor().a == 1 then + clrtb = { position = Vector( self:GetColor().r / 255, self:GetColor().g / 255, self:GetColor().b / 255 ) } + else + clrtb = { position = Vector( self:GetColor().r, self:GetColor().g, self:GetColor().b ) } + end + end + self.cpointtable = {} + self.cpointtable[1] = { entity = target, attachtype = PATTACH_ABSORIGIN_FOLLOW } + + //this entry is a placeholder - we want the second controlpoint to be at the endpos of a trace we haven't performed yet + self.cpointtable[2] = true + + for i = 3, 64 do + if clrtb then + self.cpointtable[i] = clrtb + else + self.cpointtable[i] = self.cpointtable[1] + end + end + + PrecacheParticleSystem(self:GetEffectName()) + + end + + end + self:SetModel("models/hunter/plates/plate.mdl") //it's a tiny block so that model-covering fx show up at a single point instead of all over an invisible error model + self:SetNoDraw(true) + + + if self:GetRepeatRate() > 0 then + //we're going to be repeating the effect, so let's set that up and let think do the rest + self.NextRepeat = 0 + else + //we won't be repeating the effect, so just attach it right now + if self:GetActive() then self:AttachParticle() end + end + +end + + + + +function ENT:Think() + + if SERVER then return end + + if self:GetNumpadState() == "off" then + //MsgN("turn off") + self:SetNumpadState("") + end + + if self:GetNumpadState() == "on" then + //MsgN("turn on") + self:SetNumpadState("") + if self:GetRepeatRate() > 0 then + //we're going to be repeating the effect, so let's set that up and let think do the rest + self.NextRepeat = 0 + else + //we won't be repeating the effect, so just attach it right now + if self:GetActive() then self:AttachParticle() end + end + end + + + if self:GetActive() == true and self:GetRepeatRate() > 0 then + if !( self.NextRepeat > CurTime() ) then + self:AttachParticle() + self.NextRepeat = CurTime() + self:GetRepeatRate() + end + end + + self:NextThink(CurTime()) + return true + +end + + + + +function ENT:AttachParticle() + + if SERVER then return end + + local target = self:GetTargetEnt() + //local attachnum = self:GetAttachNum() //this isn't used anywhere + local effectname = self:GetEffectName() + + local tracerspread = math.Clamp( self:GetTracerSpread(), 0, 4) + local tracercount = self:GetTracerCount() + local leavebulletholes = self:GetLeaveBulletHoles() + local effectlifetime = math.Clamp( self:GetEffectLifetime(), 0.5, 5) + + local impact_effectname = self:GetImpact_EffectName() + + if effectname == nil or !target:IsValid() then return end + + + for i = 1, tracercount do + + //Do a trace to find the endpoint of the bullet + local tracedata = {} + if self:GetParent() != NULL then tracedata.filter = {self:GetParent(), self:GetParent():GetParent()} end //this returns a null entity if the player is outside of the visleaf, be careful + local tracepos = target:GetPos() + local traceang = target:GetAngles() + + //spread + local randang = AngleRand() + traceang:RotateAroundAxis( traceang:Forward(), randang.r ) + traceang:RotateAroundAxis( traceang:Right(), randang.p * (tracerspread / 2) ) + traceang:RotateAroundAxis( traceang:Up(), randang.y * (tracerspread / 4) ) + + tracedata.start = tracepos + tracedata.endpos = tracepos+(traceang:Forward()*20000) + local trace = util.TraceLine(tracedata) + + + //we're going to tie the tracer effect and impact effect to a clientside ent that expires quickly, so people can't create a million permanent medigun beams or anything like that + local tracerent = octolib.createDummy("models/hunter/plates/plate.mdl") + tracerent.RenderGroup = RENDERGROUP_OTHER + tracerent:SetNoDraw(true) + tracerent:SetPos(trace.HitPos) + tracerent:SetAngles(trace.HitNormal:Angle()) + timer.Simple(effectlifetime, function() if IsValid(tracerent) then tracerent:Remove() end; end) + tracerent:Spawn() + tracerent:Activate() + + if self.cpointtable then self.cpointtable[2] = { entity = tracerent, attachtype = PATTACH_ABSORIGIN_FOLLOW } end + + + //Create a bullet hole effect, if enabled + if leavebulletholes == true then + local impacteffectdata = EffectData() + impacteffectdata:SetStart( tracepos ) + impacteffectdata:SetOrigin( trace.HitPos ) + impacteffectdata:SetNormal( trace.HitNormal ) + impacteffectdata:SetEntity( trace.Entity ) + impacteffectdata:SetSurfaceProp( trace.SurfaceProps ) + impacteffectdata:SetHitBox( trace.HitBox ) + util.Effect( "Impact", impacteffectdata ) + end + + + //Create the tracer effect + if string.StartWith( effectname, "!UTILEFFECT!" ) then + + //Create a util effect + + //Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly + + //local effectscale = self:GetUtilEffectInfo().x + //local effectmagnitude = self:GetUtilEffectInfo().y + //local effectradius = self:GetUtilEffectInfo().z + + local luaeffectdata = EffectData() + luaeffectdata:SetEntity( tracerent ) + + luaeffectdata:SetScale(5000) //for tracer effects, scale is the speed of the bullet, so we need to keep this high + //luaeffectdata:SetMagnitude( 20 ) + //luaeffectdata:SetRadius( 10 ) + + //flags can be set by typing !FLAG#! at the end of the effect name + luaeffectdata:SetFlags( 0 ) + if string.EndsWith( effectname, "!" ) then + if string.find( effectname, "!FLAG1!" ) then luaeffectdata:SetFlags( 1 ) effectname = string.Replace( effectname, "!FLAG1!", "" ) end + if string.find( effectname, "!FLAG2!" ) then luaeffectdata:SetFlags( 2 ) effectname = string.Replace( effectname, "!FLAG2!", "" ) end + if string.find( effectname, "!FLAG3!" ) then luaeffectdata:SetFlags( 3 ) effectname = string.Replace( effectname, "!FLAG3!", "" ) end + if string.find( effectname, "!FLAG4!" ) then luaeffectdata:SetFlags( 4 ) effectname = string.Replace( effectname, "!FLAG4!", "" ) end + if string.find( effectname, "!FLAG5!" ) then luaeffectdata:SetFlags( 5 ) effectname = string.Replace( effectname, "!FLAG5!", "" ) end + if string.find( effectname, "!FLAG6!" ) then luaeffectdata:SetFlags( 6 ) effectname = string.Replace( effectname, "!FLAG6!", "" ) end + if string.find( effectname, "!FLAG7!" ) then luaeffectdata:SetFlags( 7 ) effectname = string.Replace( effectname, "!FLAG7!", "" ) end + if string.find( effectname, "!FLAG8!" ) then luaeffectdata:SetFlags( 8 ) effectname = string.Replace( effectname, "!FLAG8!", "" ) end + if string.find( effectname, "!FLAG9!" ) then luaeffectdata:SetFlags( 9 ) effectname = string.Replace( effectname, "!FLAG9!", "" ) end + end + + //colors can also be set the same way - i don't think there are any tracers that use these + luaeffectdata:SetColor(0) + if string.EndsWith( effectname, "!" ) then + if string.find( effectname, "!COLOR1!" ) then luaeffectdata:SetColor( 1 ) effectname = string.Replace( effectname, "!COLOR1!", "" ) end + if string.find( effectname, "!COLOR2!" ) then luaeffectdata:SetColor( 2 ) effectname = string.Replace( effectname, "!COLOR2!", "" ) end + if string.find( effectname, "!COLOR3!" ) then luaeffectdata:SetColor( 3 ) effectname = string.Replace( effectname, "!COLOR3!", "" ) end + if string.find( effectname, "!COLOR4!" ) then luaeffectdata:SetColor( 4 ) effectname = string.Replace( effectname, "!COLOR4!", "" ) end + if string.find( effectname, "!COLOR5!" ) then luaeffectdata:SetColor( 5 ) effectname = string.Replace( effectname, "!COLOR5!", "" ) end + if string.find( effectname, "!COLOR6!" ) then luaeffectdata:SetColor( 6 ) effectname = string.Replace( effectname, "!COLOR6!", "" ) end + if string.find( effectname, "!COLOR7!" ) then luaeffectdata:SetColor( 7 ) effectname = string.Replace( effectname, "!COLOR7!", "" ) end + if string.find( effectname, "!COLOR8!" ) then luaeffectdata:SetColor( 8 ) effectname = string.Replace( effectname, "!COLOR8!", "" ) end + if string.find( effectname, "!COLOR9!" ) then luaeffectdata:SetColor( 9 ) effectname = string.Replace( effectname, "!COLOR9!", "" ) end + end + + //dumb situational crap + //if string.find( string.lower(effectname), "shakeropes" ) then luaeffectdata:SetMagnitude( effectmagnitude * 20 ) end + //if string.find( string.lower(effectname), "thumperdust" ) then luaeffectdata:SetScale( effectscale * 50 ) end + //if string.find( string.lower(effectname), "bloodspray" ) then luaeffectdata:SetScale( effectscale * 4 ) end + + luaeffectdata:SetOrigin( trace.HitPos ) + luaeffectdata:SetStart( tracepos ) + //luaeffectdata:SetAngles( traceang ) + luaeffectdata:SetNormal( trace.HitNormal:Angle():Forward() ) + + util.Effect( string.Replace( effectname, "!UTILEFFECT!", "" ), luaeffectdata ) + + else + + //Create a particlesystem effect + + tracerent:CreateParticleEffect(effectname,self.cpointtable) + + end + + + //Create the impact effect, if enabled + if impact_effectname != "" then + if string.StartWith( impact_effectname, "!UTILEFFECT!" ) then + + //Create a util effect + + //Unfortunately, we have to do all of this every single time the effect repeats, because if we do it in Initialize instead, a whole bunch of stuff doesn't work properly + + local effectscale = self:GetImpact_UtilEffectInfo().x + local effectmagnitude = self:GetImpact_UtilEffectInfo().y + local effectradius = self:GetImpact_UtilEffectInfo().z + + local impacteffectdata = EffectData() + impacteffectdata:SetEntity( tracerent ) + if ( string.find(impact_effectname, "Tracer", 0, true) != nil ) then impacteffectdata:SetScale(5000) else impacteffectdata:SetScale( effectscale ) end //for tracer effects, scale is the speed of the bullet, so we need to keep this high + impacteffectdata:SetMagnitude( effectmagnitude ) + impacteffectdata:SetRadius(effectradius ) + + //flags can be set by typing !FLAG#! at the end of the effect name + impacteffectdata:SetFlags( 0 ) + if string.EndsWith( impact_effectname, "!" ) then + if string.find( impact_effectname, "!FLAG1!" ) then impacteffectdata:SetFlags( 1 ) impact_effectname = string.Replace( impact_effectname, "!FLAG1!", "" ) end + if string.find( impact_effectname, "!FLAG2!" ) then impacteffectdata:SetFlags( 2 ) impact_effectname = string.Replace( impact_effectname, "!FLAG2!", "" ) end + if string.find( impact_effectname, "!FLAG3!" ) then impacteffectdata:SetFlags( 3 ) impact_effectname = string.Replace( impact_effectname, "!FLAG3!", "" ) end + if string.find( impact_effectname, "!FLAG4!" ) then impacteffectdata:SetFlags( 4 ) impact_effectname = string.Replace( impact_effectname, "!FLAG4!", "" ) end + if string.find( impact_effectname, "!FLAG5!" ) then impacteffectdata:SetFlags( 5 ) impact_effectname = string.Replace( impact_effectname, "!FLAG5!", "" ) end + if string.find( impact_effectname, "!FLAG6!" ) then impacteffectdata:SetFlags( 6 ) impact_effectname = string.Replace( impact_effectname, "!FLAG6!", "" ) end + if string.find( impact_effectname, "!FLAG7!" ) then impacteffectdata:SetFlags( 7 ) impact_effectname = string.Replace( impact_effectname, "!FLAG7!", "" ) end + if string.find( impact_effectname, "!FLAG8!" ) then impacteffectdata:SetFlags( 8 ) impact_effectname = string.Replace( impact_effectname, "!FLAG8!", "" ) end + if string.find( impact_effectname, "!FLAG9!" ) then impacteffectdata:SetFlags( 9 ) impact_effectname = string.Replace( impact_effectname, "!FLAG9!", "" ) end + end + + //colors can also be set the same way + impacteffectdata:SetColor(0) + if string.EndsWith( impact_effectname, "!" ) then + if string.find( impact_effectname, "!COLOR1!" ) then impacteffectdata:SetColor( 1 ) impact_effectname = string.Replace( impact_effectname, "!COLOR1!", "" ) end + if string.find( impact_effectname, "!COLOR2!" ) then impacteffectdata:SetColor( 2 ) impact_effectname = string.Replace( impact_effectname, "!COLOR2!", "" ) end + if string.find( impact_effectname, "!COLOR3!" ) then impacteffectdata:SetColor( 3 ) impact_effectname = string.Replace( impact_effectname, "!COLOR3!", "" ) end + if string.find( impact_effectname, "!COLOR4!" ) then impacteffectdata:SetColor( 4 ) impact_effectname = string.Replace( impact_effectname, "!COLOR4!", "" ) end + if string.find( impact_effectname, "!COLOR5!" ) then impacteffectdata:SetColor( 5 ) impact_effectname = string.Replace( impact_effectname, "!COLOR5!", "" ) end + if string.find( impact_effectname, "!COLOR6!" ) then impacteffectdata:SetColor( 6 ) impact_effectname = string.Replace( impact_effectname, "!COLOR6!", "" ) end + if string.find( impact_effectname, "!COLOR7!" ) then impacteffectdata:SetColor( 7 ) impact_effectname = string.Replace( impact_effectname, "!COLOR7!", "" ) end + if string.find( impact_effectname, "!COLOR8!" ) then impacteffectdata:SetColor( 8 ) impact_effectname = string.Replace( impact_effectname, "!COLOR8!", "" ) end + if string.find( impact_effectname, "!COLOR9!" ) then impacteffectdata:SetColor( 9 ) impact_effectname = string.Replace( impact_effectname, "!COLOR9!", "" ) end + end + + //dumb situational crap + if string.find( string.lower(impact_effectname), "shakeropes" ) then impacteffectdata:SetMagnitude( effectmagnitude * 20 ) end + if string.find( string.lower(impact_effectname), "thumperdust" ) then impacteffectdata:SetScale( effectscale * 50 ) end + if string.find( string.lower(impact_effectname), "bloodspray" ) then impacteffectdata:SetScale( effectscale * 4 ) end + + impacteffectdata:SetStart( tracerent:GetPos() ) + impacteffectdata:SetOrigin( tracerent:GetPos() ) + impacteffectdata:SetAngles( tracerent:GetAngles() ) + impacteffectdata:SetNormal( trace.HitNormal ) + + util.Effect( string.Replace( impact_effectname, "!UTILEFFECT!", "" ), impacteffectdata ) + + else + + //Create a particlesystem effect + + local clrtb = nil + if self:GetImpact_ColorInfo() == Vector(0,0,0) then + //MsgN("color = false") + else + //MsgN("color = true") + clrtb = { position = self:GetImpact_ColorInfo() } + end + local impactcpointtable = {} + impactcpointtable[1] = { entity = tracerent, attachtype = PATTACH_ABSORIGIN_FOLLOW } + + for i = 2, 64 do + if clrtb then + impactcpointtable[i] = clrtb + else + impactcpointtable[i] = impactcpointtable[1] + end + end + + tracerent:CreateParticleEffect(impact_effectname,impactcpointtable) + + end + end + + end + +end + + + + +//numpad functions +if SERVER then + +local function NumpadPress( pl, ent ) + + if ( !ent || ent == NULL ) then return end + + if ( ent:GetToggle() ) then + if ent:GetActive() == false then + ent:SetActive(true) + ent:SetNumpadState("on") + else + ent:SetActive(false) + ent:SetNumpadState("off") + end + else + ent:SetActive( true ) + ent:SetNumpadState("on") + end + +end + +local function NumpadRelease( pl, ent ) + + if ( !ent || ent == NULL ) then return end + + if ( ent:GetToggle() ) then return end + + ent:SetActive(false) + ent:SetNumpadState("off") + +end + +numpad.Register( "Particle_Press", NumpadPress ) +numpad.Register( "Particle_Release", NumpadRelease ) + +end + + + + +//don't duplicate this +duplicator.RegisterEntityClass( "particlecontroller_tracer", function( ply, data ) +end, "Data" ) diff --git a/garrysmod/addons/gmod-tools/lua/entities/prop_animatable.lua b/garrysmod/addons/gmod-tools/lua/entities/prop_animatable.lua new file mode 100644 index 0000000..6aaeb3c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/prop_animatable.lua @@ -0,0 +1,101 @@ + +AddCSLuaFile() + +ENT.Base = "base_entity" +ENT.Type = "anim" +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.AutomaticFrameAdvance = true + +function ENT:SetupDataTables() + + self:NetworkVar( "Bool", 0, "IsRagdoll" ) + + if ( CLIENT ) then return end + + self:SetIsRagdoll( false ) + +end + +if ( SERVER ) then + function ENT:Initialize() + self:SetSolid( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_NONE ) + end + + function ENT:FixRagdoll() + local mins = self:OBBMins() + local maxs = self:OBBMaxs() + mins.z = 0 + + self.OriginalCollisions = mins + self.OriginalCollisionsMax = maxs + + self:PhysicsInitBox( mins, maxs ) + self:SetMoveType( MOVETYPE_NONE ) + + -- Used to determine if this animatable prop should have the "Turn into Ragdoll" option. + self:SetIsRagdoll( true ) + end + + function ENT:PreEntityCopy() + self.DuplicatorSavedSequence = self:GetSequence() + self.DuplicatorSavedSequenceName = self:GetSequenceName( self:GetSequence() ) + self.DuplicatorSavedCycle = self:GetCycle() + self.DuplicatorSavedPlaybackRate = self:GetPlaybackRate() + end + + function ENT:PostEntityPaste() + if ( self:GetIsRagdoll() ) then self:FixRagdoll() end + + if ( !self.DuplicatorSavedSequence ) then return end + + if ( self.DuplicatorSavedSequence != self:LookupSequence( self.DuplicatorSavedSequenceName ) ) then + print( "Something went wrong with restoring sequence for animatable prop!" ) + self.DuplicatorSavedSequence = self:LookupSequence( self.DuplicatorSavedSequenceName ) + end + + self:ResetSequence( self.DuplicatorSavedSequence ) + self:SetCycle( self.DuplicatorSavedCycle ) + self:SetPlaybackRate( self.DuplicatorSavedPlaybackRate ) + end +end + +function ENT:Think() + //self:SetCollisionBounds( self:GetModelBounds() ) + + self:NextThink( CurTime() ) + return true +end + +if ( SERVER ) then return end + +local bboxMat = Material( "vgui/white" ) +function ENT:DrawBBox() + if ( GetConVarNumber( "rb655_easy_animation_noglow" ) != 0 ) then return end + + local wep = LocalPlayer():GetActiveWeapon() + if ( IsValid( wep ) && wep:GetClass() == "gmod_camera" ) then + return + end + + local mins = self:OBBMins() + local maxs = self:OBBMaxs() + local corner1 = self:GetPos() + Vector( maxs.x, mins.y, mins.z ) + local corner2 = self:GetPos() + Vector( maxs.x, maxs.y, mins.z ) + local corner3 = self:GetPos() + Vector( mins.x, maxs.y, mins.z ) + + render.SetMaterial( bboxMat ) + render.DrawQuad( corner3,corner2, corner1, self:GetPos() + mins ) +end + +function ENT:Draw() + -- self:DrawBBox() + + self:SetRenderBounds( self:GetModelBounds() ) + self:DrawModel() +end + +function ENT:DrawTranslucent() + self:Draw() +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/cl_init.lua new file mode 100644 index 0000000..ac87d2e --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/cl_init.lua @@ -0,0 +1,328 @@ +include("shared.lua") + +CreateConVar("cl_draw_catmullrom_cameras", "1") + +ENT.RenderGroup = RENDERGROUP_BOTH + +local SpriteOffset = Vector(0, 0, 32) + +local CAMERA_MODEL = Model("models/dav0r/camera.mdl") + +local MatLaser = Material("cable/physbeam") +local MatLaserB = Material("cable/white") +local MatSprite = Material("sprites/physg_glow1") + +local color_yellow = Color(255, 255, 0, 100) +local color_red = Color(255, 0, 0, 100) + +local PointColour = Color(64, 168, 255, 128) + +local VectorUp = Vector(1.25, 1.25, 1.25) + +local IconMats = {} +IconMats.Node = Material("gui/silkicons/camera_link") +IconMats.Controller = Material("gui/silkicons/camera") +IconMats.Controller_Playing = Material("gui/silkicons/camera_go") + +ENT.KeyTextures = {} + +function ENT:Initialize() + self:InitController() + + self.ShouldDraw = 1 + self.ShouldDrawInfo = false + + self.LastDrawCheckTimestamp = 0 + + self.ZoomTimestamp = 0 + self.LastGuideBeamRenderTimestamp = 0 + + self:FireShootOff() +end +---[[ +function ENT:Think() + if self.NeedCameraGhost then + if not self.GuideCameraGhost then + self.GuideCameraGhost = octolib.createDummy(CAMERA_MODEL, RENDERGROUP_OPAQUE) + end + end + + if self:GetNetVar("IsMapController") then return end + if not self:GetNetVar("MasterController", NULL).DoZoom then return end -- Haven't received the controller yet + + if LocalPlayer():GetNetVar("UnderControlCatmullRomCamera") == self:GetNetVar("MasterController", NULL) then + if not self.WasPlayingFOVHackz then + self.CatmullRomController:Reset() + + self.WasPlayingFOVHackz = true + end + + self:RemoveGuideGhost() + + return self:GetNetVar("MasterController", NULL):DoZoom() + else + self.WasPlayingFOVHackz = false + end + + self:TrackEntity(self.Entity:GetNetVar("TrackEnt"), self.Entity:GetNetVar("TrackEntLPos")) + + self.ShouldDrawInfo = (self:GetNetVar("MasterController", NULL):GetNetVar("ControllingPlayer") == LocalPlayer()) + self.ShouldDraw = (GetConVarNumber("cl_draw_catmullrom_cameras") == 0) and 0 or 1 -- Like that you can see other player's cameras while you're filming + + if (self.ShouldDraw == 0) then return end + if not self:GetNetVar("IsMasterController") then return end + + self.Key = self:GetNetVar("NumPadKey") + + if not self.KeyTextures[self.Key] then + + end + + self.Texture = self.Key + + self:GetPointData() + + if #self.CatmullRomController.PointsList >= 4 then + self.CatmullRomController:CalcEntireSpline() + else + self:RemoveGuideGhost() + end + + self:NextThink(CurTime() + .5) +end +---[[ +function ENT:Draw() + if self:GetNetVar("IsMapController") then return end + if not self:GetNetVar("MasterController", NULL).RequestGuideBeamDraw then return end -- Haven't received the controller yet + + if self.ShouldDraw == 0 then return end + if LocalPlayer():GetNetVar("UnderControlCatmullRomCamera") == self:GetNetVar("MasterController", NULL) then return end + + local wep = LocalPlayer():GetActiveWeapon() + + if wep:IsValid() and (wep:GetClass() == "gmod_camera") then return end + + self:DrawGuideBeam() + self:RequestGuideBeamDraw(self.CLTrackIndex) + + self.Entity:DrawModel() + + return self:Icon() +end + +function ENT:DrawTranslucent() + if self:GetNetVar("IsMapController") then return end + if not self:GetNetVar("IsMasterController") then return end + + if (not self.ShouldDrawInfo) or (not self.Texture) then return end + if LocalPlayer():GetNetVar("UnderControlCatmullRomCamera") == self:GetNetVar("MasterController", NULL) then return end + + local wep = LocalPlayer():GetActiveWeapon() + + if wep:IsValid() then + if wep:GetClass() == "gmod_camera" then return end + end + + local SPOS = self:GetPos():ToScreen() + draw.DrawText("" .. self.Texture .. "", "ChatFont", SPOS.x, SPOS.y, Color(255, 0, 0, 255),TEXT_ALIGN_CENTER) + + return self:Icon() +end + +function ENT:DoZoom() + local CTime = CurTime() + + if self.ZoomTimestamp < CTime then + self.ZoomTimestamp = CTime + + self.CatmullRomController:CalcPerc() -- Can't be done in the parameter call or a side effect doesn't manifest properly + LocalPlayer().CatmullRomCamsTrackZoom = self.CatmullRomController:CalcZoom() + end +end + +local scrn = 1 +local size = 2 * scrn + +function ENT:Icon() + local pos = self:GetPos() + (self:GetRight() * 1.75) + (self:GetForward() * -3.5) + (self:GetUp() * -2) + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Right(), -90) + ang:RotateAroundAxis(ang:Up(), 90) + ang:RotateAroundAxis(ang:Right(), 90) + + cam.Start3D2D(pos, ang, scrn) + local ok, err = pcall(self.DrawIcon, self) + if not ok then ErrorNoHalt(err, "\n") end + cam.End3D2D() +end + +function ENT:DrawIcon() + local CTime = SysTime() + local alpha = 200 + (math.sin(CTime * 3) * 55) + + surface.SetMaterial(self:GetNetVar("IsMasterController") and IconMats.Controller or IconMats.Node) + surface.SetDrawColor(255, 255, 255, alpha) + surface.DrawTexturedRect(0, 0, size, size) + + if self.CurShootOff ~= 0 then + local perc = (self.CurShootOff - CTime) / .75 + + if perc > 0 then + local size = size + ((1 - perc) * 2 * size) + local offset = (1 - perc) + + surface.SetDrawColor(255, 255, 255, perc * 150) + surface.DrawTexturedRectRotated(1, 1, size, size, 0) + --surface.DrawTexturedRectRotated(1, 1, size, size, perc * 360 * self.CurShootOffRoll) + else + self.CurShootOff = 0 + + --timer.Simple(math.random(1, 2.5), self.FireShootOff, self) + end + elseif (alpha > 254) and (math.random(1, 2) == 1) then + self:FireShootOff() + end +end + +function ENT:FireShootOff() + if not (self and self:IsValid()) then return end + + self.CurShootOffStart = SysTime() + self.CurShootOff = self.CurShootOffStart + .75 + --[[ + if math.random(1, 2) == 1 then + self.CurShootOffRoll = (math.random(1, 2) == 1) and math.random() or math.random() * -1 + else + self.CurShootOffRoll = 0 + end + --]] +end + +function ENT:DrawGuideBeam() + if self:GetNetVar("IsMapController") then return end + + if not self:GetNetVar("IsMasterController") then + if self:GetNetVar("MasterController", NULL):IsValid() and (LocalPlayer():GetNetVar("UnderControlCatmullRomCamera") ~= self:GetNetVar("MasterController", NULL)) then + return self:GetNetVar("MasterController", NULL):DrawGuideBeam() + end + + return + end + + if #self.CatmullRomController.PointsList < 4 then + self:RemoveGuideGhost() + + return --print("not enough points.", #self.CatmullRomController.PointsList) + end + + local CTime = UnPredictedCurTime() + + if self.LastDrawCheckTimestamp > CTime then return end--print("too soon") end + + self.LastDrawCheckTimestamp = CTime + + local size = 35 + math.abs(self.CatmullRomController.Perc - .5) * 100 + + local pos, ang = self:GetGuideCamPosAng() + + self.NeedCameraGhost = true + + if self.GuideCameraGhost then + + local VMC1 = VectorUp * (math.sin(1 - math.abs(self.CatmullRomController.Perc - .5)) * .5 + .6) + local VMC2 = Matrix() + VMC2:Scale( VMC1 ) + self.GuideCameraGhost:EnableMatrix( "RenderMultiply", VMC2 ) + + + self.GuideCameraGhost:SetAngles(ang) + self.GuideCameraGhost:SetPos(pos) + end +end + +function ENT:RequestGuideBeamDraw(trackindex) + if not trackindex then return end + if (not self:GetNetVar("IsMasterController")) then + if self:GetNetVar("MasterController", NULL).RequestGuideBeamDraw then + return self:GetNetVar("MasterController", NULL):RequestGuideBeamDraw(trackindex) + end + + return + end + + if (trackindex == 1) or (trackindex == self.CatmullRomController.CLEntityListCount) or (self.CatmullRomController.CLEntityListCount == 0) then return end + + local CTime = CurTime() + + --self.LastGuideBeamRenderTimestamp = self.LastGuideBeamRenderTimestamp or 0 -- Had some strange issues with this in MP testing so patch it here + --self.CatmullRomController.EntityList[trackindex - 1].LastGuideBeamRenderTimestamp = self.CatmullRomController.EntityList[trackindex - 1].LastGuideBeamRenderTimestamp or 0 -- Here too. :/ + + local drawbackbeam = (trackindex > 2) and self.CatmullRomController.EntityList[trackindex - 1] and (self.CatmullRomController.EntityList[trackindex - 1].LastGuideBeamRenderTimestamp < CurTime()) + local drawforwardbeam = (trackindex < self.CatmullRomController.CLEntityListCount - 1) and self.CatmullRomController.EntityList[trackindex] and (self.CatmullRomController.EntityList[trackindex].LastGuideBeamRenderTimestamp < CurTime()) + + if drawbackbeam then + self.CatmullRomController.EntityList[trackindex - 1].LastGuideBeamRenderTimestamp = CTime + + render.SetMaterial(MatLaser) + render.StartBeam(self.CatmullRomController.STEPS + 2) + ok, err = pcall(self.RenderSubBeams, self, CTime, (trackindex - 1)) + render.EndBeam() + end + + if drawforwardbeam then + self.CatmullRomController.EntityList[trackindex].LastGuideBeamRenderTimestamp = CTime + + render.SetMaterial(MatLaser) + render.StartBeam(self.CatmullRomController.STEPS + 2) + ok, err = pcall(self.RenderSubBeams, self, CTime, trackindex) + render.EndBeam() + end +end + +function ENT:GetGuideCamPosAng(pos1, pos2, pos3, pos4) + if self:GetNetVar("IsMasterController") then + self:CalcPerc() -- Can't be done in the parameter call or a side effect doesn't manifest properly + + return self.CatmullRomController:Point(), self.CatmullRomController:Angle() + end +end + +function ENT:RenderBeams() + local CTime = CurTime() + + render.AddBeam(self.CatmullRomController.PointsList[2], 10, CTime, color_white) + + for i = 1, #self.CatmullRomController.Spline do + render.AddBeam(self.CatmullRomController.Spline[i], 10, CTime, color_white) + end +end + +function ENT:RenderSubBeams(CTime, trackindex) + local base = (trackindex - 2) * self.CatmullRomController.STEPS + + render.AddBeam(self.CatmullRomController.PointsList[trackindex], 10, CTime or 1, color_white) +---------------------------------------------------- + for i = 1, self.CatmullRomController.STEPS do + render.AddBeam(self.CatmullRomController.Spline[base + i], 10, CTime or 1, color_white) + end + + render.AddBeam(self.CatmullRomController.PointsList[trackindex + 1], 10, CTime or 1, color_white) +end + +function ENT:RemoveGuideGhost() + if self.GuideCameraGhost and self.GuideCameraGhost:IsValid() then + self.GuideCameraGhost:Remove() + + self.GuideCameraGhost = nil + end + + --return collectgarbage() +end + +function ENT:Lock() + self.IsLocked = true +end + +function ENT:Unlock() + self.IsLocked = false +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/init.lua b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/init.lua new file mode 100644 index 0000000..010927c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/init.lua @@ -0,0 +1,183 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +local CAMERA_MODEL = Model("models/dav0r/camera.mdl") + +ENT.KeyTriggerInformation = {} + +function ENT:Initialize() + self.Entity:SetModel(CAMERA_MODEL) + self.Entity:PhysicsInit(SOLID_VPHYSICS) + self.Entity:SetMoveType(MOVETYPE_VPHYSICS) + self.Entity:SetSolid(SOLID_VPHYSICS) + + self.Entity:DrawShadow(false) + + self.Entity:SetCollisionGroup(COLLISION_GROUP_WORLD) + + local phys = self.Entity:GetPhysicsObject() + + if phys:IsValid() then + phys:Sleep() + end + + if not self.CatmullRomController then + return self:InitController() + end +end + +function ENT:Think() + if self.IsMapController then return end + + self:TrackEntity(self.Entity:GetNetVar("TrackEnt"), self.Entity:GetNetVar("TrackEntLPos")) + + if self:GetNetVar("IsMasterController") then + if self.Playing and self.ViewPointEnt then + self:GetPointData() + + if #self.CatmullRomController.PointsList < 4 then + self.ViewPointEnt:Remove() + + self.Playing = false + self:SetNetVar("IsPlaying", false) + end + end + end + + self.Entity:NextThink(CurTime()) +end + +function ENT:OnChangeSegment(segment) + local ent = CatmullRomCams.Tracks[self.UndoData.PID][self.UndoData.Key][segment] + + if ent and ent:IsValid() and ent.OnNodeTriggerNumPadKey then + local ply = self:GetNetVar("ControllingPlayer") + + self:InternalQuickToggleNumpad(ent.OnNodeTriggerNumPadKey.Keys, ply, ply:UniqueID()) + + if not ent.OnNodeTriggerNumPadKey.Hold then + return timer.Simple(.25, self.InternalQuickToggleNumpad, self, ent.OnNodeTriggerNumPadKey.Keys, ply, ply:UniqueID()) + end + end +end + +function ENT:InternalQuickToggleNumpad(keys, ply, plyid) + for _, key in pairs(keys) do + if self.KeyTriggerInformation[key] then + numpad.Deactivate(ply, nil, {key}, plyid) -- Stupid numpad lib needs this as a table ):< + else + numpad.Activate(ply, nil, {key}, plyid) + end + + self.KeyTriggerInformation[key] = not self.KeyTriggerInformation[key] + end +end + +function ENT:PhysicsUpdate(physobj) + if not self.Entity:IsPlayerHolding() then + if self.IsMapController then + physobj:EnableMotion(false) + else + physobj:Sleep() + end + end +end + +function ENT:LoadForMap(filename) + filename = filename or "" + + self:SetCollisionGroup(COLLISION_GROUP_NONE) + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_NONE) + + self.IsMapController = true + self:SetNetVar("IsMapController", true) + + if not CatmullRomCams then + include("autorun/sh_CatmullRomCams") + end + + self:InitController() + + local data = file.Read("CatmullRomCameraTracks/" .. filename .. ".txt") + + if not data then return Error("Could not load filename track '" .. filename .. "' from disk! " .. tostring(self) .. " map controller will not work!!!\n") end + + data = util.KeyValuesToTable(data) + + for k, v in ipairs(data) do + end + + local lastent = self + local count = 1 + + self.CatmullRomController.PointsList = {} + self.CatmullRomController.AnglesList = {} + + self.CatmullRomController.DurationList = {} + + self.CatmullRomController.Spline = {} + + self.CatmullRomController:AddPointAngle(count, lastent:GetPos(), lastent:GetAngles()) + + while true do + count = count + 1 + + local ent = lastent:GetNetVar("ChildCamera") + + if ent and ent:IsValid() then + lastent = ent + + local dur = lastent:GetNetVar("Duration") + + self.CatmullRomController:AddPointAngle(count, lastent:GetPos(), lastent:GetAngles(), (dur > 0) and dur or 2) + + self.CatmullRomController.EntityList[count] = lastent + else + break + end + end +end + +function ENT:KeyValue(k, v) + if k == "TrackFilename" then + return self:LoadForMap(v) + end +end + +function ENT:PreEntityCopy() -- build the DupeInfo table and save it as an entity mod + return duplicator.StoreEntityModifier(self.Entity, "CatmullRomCamsDupData", self:RequestSaveData()) +end + +function ENT:PostEntityPaste(Player, Ent, CreatedEntities) + local plyID = Player:UniqueID() + + if Ent.EntityMods and Ent.EntityMods.CatmullRomCamsDupData then + --[[ + if not CatmullRomCams.Tracks[plyID][Ent.EntityMods.CatmullRomCamsDupData.UndoData.Key].IsLockedForLoad then -- hackz + for k, v in pairs(CatmullRomCams.Tracks[plyID][Ent.EntityMods.CatmullRomCamsDupData.UndoData.Key]) do + if v and v:IsValid() then + v:Remove() + end + end + + CatmullRomCams.Tracks[plyID][Ent.EntityMods.CatmullRomCamsDupData.UndoData.Key] = {} + CatmullRomCams.Tracks[plyID][Ent.EntityMods.CatmullRomCamsDupData.UndoData.Key].IsLockedForLoad = true + end + --]] + return CatmullRomCams.SV.AdvDupPaste(self, Player, plyID, Ent, CreatedEntities) + else + self:Remove() + end +end + +function ENT:BuildDupeInfo() +end + +function ENT:ApplyDupeInfo(ply, ent, data, CreatedEntities) +end + +function ENT.AfterPasteMods(ply, Ent, DupeInfo) -- Happens before PostEntityPaste for some reason <_< +end +duplicator.RegisterEntityModifier("CatmullRomCamsDupData", ENT.AfterPasteMods) diff --git a/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/shared.lua new file mode 100644 index 0000000..48be2f7 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera/shared.lua @@ -0,0 +1,440 @@ +ENT.Type = "anim" +-- We're the root controller, we'll be handling all the real camera stuff (the interpolation will be clientside, but the server will periodicly move the ViewEntity to update the client's PVS) +ENT.PrintName = "Cubic Cameras" +ENT.Author = "Olivier 'LuaPineapple' Hamel" +ENT.Contact = "evilpineapple@cox.net" +ENT.Purpose = "For the lulz." +ENT.Instructions = "Try the GMod Tower servers, but be sure to check out Duke Nukem: Forever first!" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + +function ENT:InitController() + self.CatmullRomController = CatmullRomCams.SH.Controller:New(self) +end + +function ENT:TrackEntity(ent, lpos) + if not (ent and lpos and ent.IsValid and ent:IsValid()) then return end + + self.TrackEnt = ent + + return self:SetAngles(((ent:IsPlayer() and (ent:LocalToWorld(lpos) + Vector(0, 0, 54)) or ent:LocalToWorld(lpos)) - self:GetPos()):Angle()) +end + +function ENT:Toggle(ply) + if ply:GetNetVar("UnderControlCatmullRomCamera", NULL) ~= NULL then + if ply:GetNetVar("UnderControlCatmullRomCamera", NULL) == self then + self:Off(ply) + end + else + self:On(ply) + end +end + +function ENT:On(ply) + if SERVER and self:GetNetVar("IsMasterController") and (ply:GetNetVar("UnderControlCatmullRomCamera", NULL) == NULL) then + print("toggle on: ", ply) + + self:GetPointData() + + if #self.CatmullRomController.PointsList < 4 then + return print("not enough points: ", #self.CatmullRomController.PointsList) + end + + ply:SetNetVar("UnderControlCatmullRomCamera", self) + + self:GetPointData() + --self.CatmullRomController:CalcEntireSpline(self.VecList) + + self.ViewPointEnt = ents.Create("sent_catmullrom_camera_viewpnt") + self.ViewPointEnt:SetPos(self.CatmullRomController.PointsList[2]) + self.ViewPointEnt:SetAngles(self:GetAngles()) + self.ViewPointEnt:Spawn() + + self.ViewPointEnt.Master = self + self.ViewPointEnt.CatmullRomController = self.CatmullRomController + + self.ViewPointEnt.FaceTravelDir = self.FaceTravelDir + self.ViewPointEnt.BankOnTurn = self.BankOnTurn + + self.ViewPointEnt.DeltaBankConstant = self.DeltaBankConstant or 0 + + self.ViewPointEnt:SetPlayers(ply) + + self.Playing = true + self:SetNetVar("IsPlaying", true) + + self.PlyVictim = ply + + self:OnChangeSegment(2) + end +end + +function ENT:Off(ply) + if self:GetNetVar("IsMasterController") and (ply:GetNetVar("UnderControlCatmullRomCamera", NULL) == self) then + ply:SetNetVar("UnderControlCatmullRomCamera", NULL) + print("toggle off: ", ply) + if self.ViewPointEnt and self.ViewPointEnt:IsValid() then + self.ViewPointEnt:Remove() + end + + self.Playing = false + self:SetNetVar("IsPlaying", false) + end +end + +function ENT:End() + return self:Off(self.PlyVictim) +end + +function ENT:SetPlayer(ply) + return self:SetNetVar("ControllingPlayer", ply) +end + +function ENT:RequestSaveData() + local tbl = {} + + tbl.Duration = (self:GetNetVar("Duration", 0) > 0) and self:GetNetVar("Duration", 0) or 2 + + if self:GetNetVar("IsMasterController") then + -- ? + else -- Because there's no point in saving key trigger or map io data, even if there is some, for the controller + tbl.MapIO = self.MapIO + tbl.KeyTriggerData = self.OnNodeTriggerNumPadKey + end + + tbl.FaceTravelDir = self.FaceTravelDir + + tbl.EnableRoll = self.EnableRoll + tbl.Roll = self.Roll + + tbl.SmartLookEnabled = self.SmartLookEnabled + tbl.SmartLookRange = self.SmartLookRange + tbl.SmartLookPerc = self.SmartLookPerc + tbl.SmartLookTargetFilter = self.SmartLookTargetFilter + tbl.SmartLookTraceFilter = self.SmartLookTraceFilter + tbl.SmartLookClosest = self.SmartLookClosest + + tbl.BankOnTurn = self.BankOnTurn + tbl.DeltaBankMax = self.DeltaBankMax + tbl.DeltaBankMulti = self.DeltaBankMulti + + tbl.HitchcockEffect = self.HitchcockEffect + + tbl.Zoom = self.Zoom + + tbl.UndoData = self.UndoData + + tbl.ChildCamera = self:GetNetVar("ChildCamera", NULL):IsValid() and self:GetNetVar("ChildCamera", NULL):EntIndex() or nil + + return tbl +end + +function ENT:ApplySaveData(ply, plyID, CreatedEntities, CatmullRomCamsDupData) + if not CreatedEntities.EntityList then return self:Remove() end + + local tbl = CatmullRomCamsDupData + + self:SetPlayer(ply) + + self:SetNetVar("Duration", (tbl.Duration > 0) and tbl.Duration or nil) + + self.FaceTravelDir = tbl.FaceTravelDir + + self.EnableRoll = tbl.EnableRoll + self.Roll = tbl.Roll + + self.SmartLookEnabled = tbl.SmartLookEnabled + self.SmartLookRange = tbl.SmartLookRange + self.SmartLookPerc = tbl.SmartLookPerc + self.SmartLookTargetFilter = tbl.SmartLookTargetFilter + self.SmartLookTraceFilter = tbl.SmartLookTraceFilter + self.SmartLookClosest = tbl.SmartLookClosest + + self.BankOnTurn = tbl.BankOnTurn + self.DeltaBankMax = tbl.DeltaBankMax + self.DeltaBankMulti = tbl.DeltaBankMulti + + self.HitchcockEffect = tbl.HitchcockEffect + + self:SetZoom(tbl.Zoom) + + self.UndoData = tbl.UndoData + + CatmullRomCams.Tracks[plyID] = CatmullRomCams.Tracks[plyID] or {} + CatmullRomCams.Tracks[plyID][self.UndoData.Key] = CatmullRomCams.Tracks[plyID][self.UndoData.Key] or {} + + if CatmullRomCams.Tracks[plyID][self.UndoData.Key][self.UndoData.TrackIndex] and CatmullRomCams.Tracks[plyID][self.UndoData.Key][self.UndoData.TrackIndex]:IsValid() then + CatmullRomCams.Tracks[plyID][self.UndoData.Key][self.UndoData.TrackIndex]:Remove() + CatmullRomCams.Tracks[plyID][self.UndoData.Key][self.UndoData.TrackIndex] = nil + end + + CatmullRomCams.Tracks[plyID][self.UndoData.Key][self.UndoData.TrackIndex] = self + + if tbl.UndoData.TrackIndex == 1 then + self:SetKey(self.UndoData.Key) + + self:SetNetVar("IsMasterController", true) + self:SetNetVar("ControllingPlayer", ply) + + numpad.OnDown(ply, self.Key, "CatmullRomCamera_Toggle", self) + + for k, v in pairs(CatmullRomCams.Tracks[plyID][self.UndoData.Key]) do + v:SetNetVar("MasterController", self) + + --print(v, "'s controller set to ", CatmullRomCams.Tracks[plyID][self.UndoData.Key][1]) + end + else -- Because there's no point in saving key trigger or map io, even if there is some, for the controller + self.MapIO = tbl.MapIO + self.KeyTriggerData = tbl.OnNodeTriggerNumPadKey + + if CatmullRomCams.Tracks[plyID][self.UndoData.Key][1] and CatmullRomCams.Tracks[plyID][self.UndoData.Key][1]:IsValid() then + self:SetNetVar("MasterController", CatmullRomCams.Tracks[plyID][self.UndoData.Key][1]) + + --print("Set controller to ", CatmullRomCams.Tracks[plyID][self.UndoData.Key][1]) + end + end + + local child = CreatedEntities[CatmullRomCamsDupData.ChildCamera] + + if (not (child and child:IsValid())) and (not CreatedEntities.EntityList[CatmullRomCamsDupData.ChildCamera]) then + child = ents.GetByIndex(CatmullRomCamsDupData.ChildCamera) + end + + if child and child:IsValid() then + self:DeleteOnRemove(child) -- Because we don't want to have broken chains let's daisy chain them to self destruct + + self:SetNetVar("ChildCamera", child) + end + + --return self:InitController() +end + +function ENT:SetTracking(ent, LPos) + if ent and ent.IsValid and ent:IsValid() then + self:SetMoveType(MOVETYPE_NONE) + self:SetSolid(SOLID_BBOX) + else + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + end + + self:SetNetVar("TrackEntLPos", LPos) + self:SetNetVar("TrackEnt", ent) + + return self:NextThink(CurTime()) +end + +function ENT:SetBankOnTurn(val) + self.BankOnTurn = val +end + +function ENT:SetBankDeltaMax(val) + self.DeltaBankMax = val +end + +function ENT:SetBankMultiplier(val) + self.DeltaBankMulti = val +end + +function ENT:SetFaceTravelDir(val) + self.FaceTravelDir = val +end + +function ENT:SetKey(key) + self.Key = key + + self:SetNetVar("NumPadKey", key) +end + +function ENT:SetSmartLook(bool) + self.SmartLookEnabled = bool +end + +function ENT:SetSmartLookPerc(val) + self.SmartLookPerc = math.Clamp(val, .01, 1) +end + +function ENT:SetSmartLookRange(val) + self.SmartLookRange = math.abs(val) +end + +function ENT:SetSmartLookTraceFilter(val) + self.SmartLookTraceFilter = val +end + +function ENT:SetSmartLookTargetFilter(val) + self.SmartLookTargetFilter = val +end + +function ENT:SetSmartLookClosest(val) + self.SmartLookClosest = val +end + +function ENT:SetEnableRoll(bool) + self.EnableRoll = bool +end + +function ENT:SetRoll(roll) + self.Roll = roll +end + +function ENT:SetZoom(zoom) + self.Zoom = zoom + + self:SetNetVar("CamZoom", zoom) +end + +function ENT:SetHitchcockEffect(val) + self.HitchcockEffect = val +end + +function ENT:CalcPerc() + if not self:GetNetVar("IsMasterController") then return end + + return self.CatmullRomController:CalcPerc() +end + +function ENT:GetPointData(terminating_entity_marker) + if not self:GetNetVar("IsMasterController") then return end + if self:SetNetVar("IsMapController") then return end + + local lastent = self + local count = 1 + + self.CatmullRomController:AddPointAngle(count, lastent:GetPos(), lastent:GetAngles()) + + while true do + count = count + 1 + + local ent = lastent:GetNetVar("ChildCamera", NULL) + + if ent == terminating_entity_marker then break end + + if CLIENT then + lastent.CLTrackIndex = count - 1 + end + + local zoom = lastent:GetNetVar("CamZoom", 0) + + self.CatmullRomController:AddZoomPoint(count, (zoom > 0) and zoom or 75) + + if ent and ent:IsValid() then + lastent = ent + + local dur = lastent:GetNetVar("Duration", 0) + + self.CatmullRomController:AddPointAngle(count, lastent:GetPos(), lastent:GetAngles(), (dur > 0) and dur or 2) + + if CLIENT then + lastent.CLTrackIndex = count - 1 + end + + local zoom = lastent:GetNetVar("CamZoom", 0) + + self.CatmullRomController:AddZoomPoint(count, (zoom > 0) and zoom or 75) + + self.CatmullRomController.EntityList[count] = lastent + else + break + end + end + + if CLIENT then + self.CatmullRomController.CLEntityListCount = #self.CatmullRomController.EntityList + end +end + +function ENT:ResetController(ent) + self.CatmullRomController.PointsList = {} + + self.CatmullRomController.FacingsList = {} + self.CatmullRomController.RotationsList = {} + + self.CatmullRomController.DurationList = {} + self.CatmullRomController.EntityList = {} + + self.CatmullRomController.ZoomList = {} -- needed serverside as well if there's a hitchcock camera in the track + + if CLIENT then + self.CatmullRomController.Spline = {} + end +end + +function ENT:RebuildTrack(ent) + self:ResetController() + + return self:GetPointData(ent) +end + +function ENT:ClearTrack(ent, trackidx, dont_loop_back) + if trackidx == 1 then return end + + if (not self:GetNetVar("IsMasterController")) then + if self:GetNetVar("MasterController", NULL):IsValid() then + return self:GetNetVar("MasterController", NULL):ClearTrack(ent, trackidx, dont_loop_back) + end + + return + end + if !self.CatmullRomController["PointsList"] then self:ResetController(self) end -- This is all I could think of. + self.CatmullRomController.PointsList[trackidx] = nil + + self.CatmullRomController.FacingsList[trackidx] = nil + self.CatmullRomController.RotationsList[trackidx] = nil + + self.CatmullRomController.DurationList[trackidx] = nil + self.CatmullRomController.EntityList[trackidx] = nil + + self.CatmullRomController.ZoomList[trackidx] = nil + + if CLIENT then + self.CatmullRomController.Spline[trackidx] = nil + elseif self.Playing then + self:End() + end + + if trackidx < 3 then return end + + if dont_loop_back then + return self:GetNetVar("MasterController", NULL):RebuildTrack(ent) + else + return self:ClearTrack(ent, trackidx - 1, true) + end +end + +function ENT:OnRemove() + local trackidx = -1 + + if SERVER then + if self.ViewPointEnt and self.ViewPointEnt.IsValid and self.ViewPointEnt:IsValid() then + self.ViewPointEnt:Remove() + end + + if self.UndoData then + CatmullRomCams.Tracks[self.UndoData.PID][self.UndoData.Key][self.UndoData.TrackIndex] = nil + + trackidx = self.UndoData.TrackIndex + end + else + self:RemoveGuideGhost() + + if not self.CLTrackIndex then + + + if self:GetNetVar("MasterController", NULL) then + self:GetNetVar("MasterController", NULL):GetPointData() + end + + + + + + + end + + trackidx = self.CLTrackIndex + end + + return self:ClearTrack(self, trackidx) +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/cl_init.lua new file mode 100644 index 0000000..33dcc35 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/cl_init.lua @@ -0,0 +1 @@ +include("shared.lua") diff --git a/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/init.lua b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/init.lua new file mode 100644 index 0000000..3fd2099 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/init.lua @@ -0,0 +1,251 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +local CAMERA_MODEL = Model("models/hunter/blocks/cube025x025x025.mdl") + +ENT.PhysShadowControl = {} +ENT.PhysShadowControl.secondstoarrive = .01 +ENT.PhysShadowControl.pos = Vector(0, 0, 0) +ENT.PhysShadowControl.angle = Angle(0, 0, 0) +ENT.PhysShadowControl.maxspeed = 1000000 +ENT.PhysShadowControl.maxangular = 1000000 +ENT.PhysShadowControl.maxspeeddamp = 1000000 +ENT.PhysShadowControl.maxangulardamp = 1000000 +ENT.PhysShadowControl.dampfactor = 1 +ENT.PhysShadowControl.teleportdistance = 0 +ENT.PhysShadowControl.deltatime = deltatime + +function ENT:Initialize() + self.Entity:SetModel(CAMERA_MODEL) + self.Entity:PhysicsInit(SOLID_VPHYSICS) + self.Entity:SetMoveType(MOVETYPE_VPHYSICS) + self.Entity:SetSolid(SOLID_NONE) + self.Entity:SetColor(Color(255,255,255,0)) + self.Entity:DrawShadow(false) + + self.Entity:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + self.Entity:SetColor(0, 0, 0, 100) + + self.Victims = {} + + local phys = self:GetPhysicsObject() + + if phys and phys:IsValid() then + phys:Wake() + end + + self.LastYaw = (self:GetAngles().y - 180) / 180 + + return self:StartMotionController() +end + +function ENT:KeyValue(k, v) +end + +function ENT:UpdateTransmitState() + return TRANSMIT_ALWAYS +end + +function ENT:IsValidByFilter(ent, filter) + if filter == 1 then -- any + return true + elseif filter == 2 then -- fire + return (ent:IsOnFire() or string.find(ent:GetClass(), "_fire")) + elseif filter == 3 then -- prop + return (ent:GetClass() == "prop_physics") + elseif filter == 4 then -- things with physics objects + return (ent:GetPhysicsObject() and ent:GetPhysicsObject():IsValid()) + elseif filter == 5 then -- npcs + return ent:IsNPC() + elseif filter == 6 then -- players + return ent:IsPlayer() + elseif filter == 7 then -- light entities + return (string.find(ent:GetClass(), "light") or (ent:GetClass() == "env_sprite")) + elseif filter == 8 then -- 'info_particle_system's + return (ent:GetClass() == "info_particle_system") + end +end + +function ENT:SearchForSmartLookTarget() -- I hate this function + local selfpos = self:GetPos() + + local trace = {} + local tr = {} + tr.start = selfpos + + local dist = self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookRange + 1337 + local ent + + for k, v in pairs(ents.FindInSphere(selfpos, self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookRange)) do + if (v ~= self) and (v:GetClass() ~= "sent_catmullrom_camera") then + if self:IsValidByFilter(v, self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookTargetFilter) then + local vpos = v:GetPos() + + tr.endpos = vpos + tr.filter = {self.CatmullRomController.EntityList, game.GetWorld( ) } + tr.mask = (self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookTraceFilter ~= 0) and self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookTraceFilter or nil + + trace = util.TraceLine(tr) + --[[ + print("--") + print(trace.HitPos) + print(vpos) + print(v) + print(trace.Entity) + print("--") + --]] + -- This handles some really strange case where sometimes two valid entities are superimposing and it always returns the other entity instead or when it would be a clear path but it wouldn't hit the other entity because it was immaterial + if (trace.HitPos == vpos) or (trace.Entity and trace.Entity:IsValid() and ((trace.Entity == v) or self:IsValidByFilter(trace.Entity, self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookTargetFilter))) then -- GOTCHA! + local e = (trace.HitPos == vpos) and v or trace.Entity + + if not self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment].SmartLookClosest then + return e + end + + local dist2 = e:Distance(selfpos) + + if dist2 < dist then + ent = e + + dist = dist2 + end + end + end + end + end + + return ent +end + +function ENT:CalcHitchcockEffect(width, fov) + return (width / (2 * math.tan(math.pi * (fov / 360)))) +end + +function ENT:PhysicsSimulate(phys, deltatime) + --if self.Entity:IsPlayerHolding() then return SIM_NOTHING end + + self.CatmullRomController:CalcPerc() -- Can't be done in the parameter call or a side effect doesn't manifest properly + + local CurPos = self:GetPos() + local CurNode = self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment] + + phys:Wake() + + if CurNode.HitchcockEffect then + self.HitchcockEffectEndpoint = self.HitchcockEffectEndpoint or (self.CatmullRomController.PointsList[self.CatmullRomController.CurSegment + 2] - self.CatmullRomController.PointsList[self.CatmullRomController.CurSegment + 1]):GetNormal() + + self.PhysShadowControl.pos = self.CatmullRomController.PointsList[self.CatmullRomController.CurSegment] + (self.HitchcockEffectEndpoint * CatmullRomCams.SH.MetersToUnits(self:CalcHitchcockEffect(CurNode.HitchcockEffect, self.CatmullRomController:CalcZoom()))) + else + self.HitchcockEffectEndpoint = nil + + self.PhysShadowControl.pos = self.CatmullRomController:Point() + end + + if CurNode.FaceTravelDir then + self.PhysShadowControl.angle = self:GetVelocity():Angle() + + if CurNode.BankOnTurn then -- :/ + self.LastPos = self.LastPos or CurPos + + local NextPosNrml = self:WorldToLocal(self.PhysShadowControl.pos):GetNormal() + local Multi = (math.abs(NextPosNrml.y) / NextPosNrml.y) * -1 + + local a = (CurPos - self.LastPos):GetNormal() + local b = (self.PhysShadowControl.pos - CurPos):GetNormal() + local dot = math.Clamp((1 - (a:Dot(b) )) * CurNode.DeltaBankMulti * 50, -1, 1) * 90 * CurNode.DeltaBankMax + + self.LastPos = CurPos + + self.PhysShadowControl.angle.r = dot * 1.0 + end + else + self.PhysShadowControl.angle = self.CatmullRomController:Angle() + end + + if CurNode.SmartLookEnabled then + if not self.SmartLookNodeCount then + self:GetSmartLookNodeCount() + end + + if not self.HasSmartLookTarget then + self.HasSmartLookTarget = self:SearchForSmartLookTarget() + end + + if self.HasSmartLookTarget then + if self:IsOnlyOneSmartNode() then + self.PhysShadowControl.angle = LerpAngle((1 - math.abs(self.CatmullRomController.Perc - .5)) * CurNode.SmartLookPerc, self.PhysShadowControl.angle, (self.HasSmartLookTarget:GetPos() - CurPos):Angle()) + else + local perc = self.CatmullRomController.Perc * CurNode.SmartLookPerc + + if self:IsFirstSmartNode() then + self.PhysShadowControl.angle = LerpAngle(perc, self.PhysShadowControl.angle, (self.HasSmartLookTarget:GetPos() - CurPos):Angle()) + elseif self:IsLastSmartNode() then + self.PhysShadowControl.angle = LerpAngle(perc, (self.HasSmartLookTarget:GetPos() - CurPos):Angle(), self.PhysShadowControl.angle) + else + self.PhysShadowControl.angle = LerpAngle(CurNode.SmartLookPerc, self.PhysShadowControl.angle, (self.HasSmartLookTarget:GetPos() - CurPos):Angle()) + end + end + else + --print("no target") + end + elseif self.HasSmartLookTarget then + self.HasSmartLookTarget = nil + end + + self.PhysShadowControl.deltatime = deltatime + + return phys:ComputeShadowControl(self.PhysShadowControl) +end + +function ENT:IsOnlyOneSmartNode() + return (self.SmartLookNodeCount == 1) +end + +function ENT:IsFirstSmartNode() + return ((self.CatmullRomController.CurSegment - self.SmartNodeOffset) == 0) +end + +function ENT:IsLastSmartNode() + return (self.CatmullRomController.CurSegment == (self.SmartNodeOffset + self.SmartNodeOffset)) +end + +function ENT:GetSmartLookNodeCount() + self.SmartLookNodeCount = 1 + self.SmartNodeOffset = self.CatmullRomController.CurSegment + + while true do + if not (self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment + self.SmartLookNodeCount] and self.CatmullRomController.EntityList[self.CatmullRomController.CurSegment + self.SmartLookNodeCount].SmartLookEnabled) then + break + end + + self.SmartLookNodeCount = self.SmartLookNodeCount + 1 + end +end + +function ENT:SetPlayers(players) + if players.IsPlayer then -- Single player + self.Victims[1] = players + else + self.Victims = players + end + + for _, v in pairs(self.Victims) do + if v.IsValid and v:IsValid() and v.SetViewEntity then + v:SetViewEntity(self.Entity) + end + end +end + +-- The following line checks to see if we have an active player(s) and if we do then we release him/her/it/shi +function ENT:OnRemove() + self.CatmullRomController.CurSegmentTimestamp = 0 + self.CatmullRomController.CurSegment = 2 + + for _, v in pairs(self.Victims) do + if v.IsValid and v:IsValid() and v.SetViewEntity then + v:SetViewEntity(v) + end + end +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/shared.lua new file mode 100644 index 0000000..5f9f683 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/sent_catmullrom_camera_viewpnt/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" + +ENT.PrintName = "Cubic Cameras Viewpoint" +ENT.Author = "Olivier 'LuaPineapple' Hamel" +ENT.Contact = "evilpineapple@cox.net" +ENT.Purpose = "For the lulz." +ENT.Instructions = "Try the GMod Tower servers, but be sure to check out Duke Nukem: Forever first!" + +ENT.Spawnable = false +ENT.AdminSpawnable = false + + diff --git a/garrysmod/addons/gmod-tools/lua/entities/textscreen/cl_init.lua b/garrysmod/addons/gmod-tools/lua/entities/textscreen/cl_init.lua new file mode 100644 index 0000000..7845dbd --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/textscreen/cl_init.lua @@ -0,0 +1,77 @@ +include("shared.lua") +ENT.RenderGroup = RENDERGROUP_BOTH + +function ENT:Initialize() + self:SetMaterial("models/effects/vol_light001") + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetColor(Color(255, 255, 255, 1)) +end + +local function drawIt(totheight, lines) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + local curheight = 0 + + for k, v in pairs(lines) do + local text, font, y = + v.text, TS_GET_FONT(v.font,v.size), -(totheight / 2) + curheight + + draw.DrawText(text, font, 2, y + 2, Color(0,0,0, 200), TEXT_ALIGN_CENTER) + draw.DrawText(text, font, 0, y, v.color, TEXT_ALIGN_CENTER) + curheight = curheight + v.height + end + + render.PopFilterMin() +end + +local posOff, angOff = Vector(0, 0, 0.5), Angle(0, 0, 0) +local dist, distFade = 2000 * 2000, 500 * 500 +function ENT:Draw() + + local al = math.Clamp(1 - (self:GetPos():DistToSqr(EyePos()) - distFade) / dist, 0, 1) + if al <= 0 then return end + + local pos, ang = LocalToWorld(posOff, angOff, self:GetPos(), self:GetAngles()) + local camangle = Angle(ang.p, ang.y, ang.r) + local lines = self:GetNetVar('lines') + if not lines then return end + + self.totheight = self.totheight or 0 + self.maxwidth = self.maxwidth or 0 + + if self.oldLines ~= lines then + self.totheight, self.maxwidth = 0, 0 + for k, v in pairs(lines) do + if string.Trim(v.text) == '' then + lines[k] = nil + continue + end + + v.size = tonumber(v.size) > 100 and 100 or v.size + surface.SetFont( TS_GET_FONT(v.font,v.size) ) + TextWidth, TextHeight = surface.GetTextSize(v.text) + lines[k].width = TextWidth + lines[k].height = TextHeight + self.totheight = self.totheight + TextHeight + self.maxwidth = math.max(self.maxwidth, TextWidth) + end + self.oldLines = lines + + if not self.rbMins or not self.rbMaxs then + self.rbMins, self.rbMaxs = self:GetRenderBounds() + end + local expectedMaxs = Vector(self.maxwidth * .125, self.totheight * .125, 0.001) + local expectedMins = -expectedMaxs + if self.rbMins ~= expectedMins or self.rbMaxs ~= expectedMaxs then + self:SetRenderBounds(expectedMins, expectedMaxs) + self.rbMins, self.rbMaxs = expectedMins, expectedMaxs + end + end + + + cam.Start3D2D(pos, camangle, .25) + surface.SetAlphaMultiplier(al) + drawIt(self.totheight, lines) + surface.SetAlphaMultiplier(1) + cam.End3D2D() + +end diff --git a/garrysmod/addons/gmod-tools/lua/entities/textscreen/init.lua b/garrysmod/addons/gmod-tools/lua/entities/textscreen/init.lua new file mode 100644 index 0000000..440c6a1 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/textscreen/init.lua @@ -0,0 +1,78 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + + +function ENT:Initialize() + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:SetModel("models/hunter/blocks/cube025x025x025.mdl") + self:SetMaterial("models/effects/vol_light001") + self:PhysicsInit(SOLID_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + self:SetMoveType(MOVETYPE_NONE) + self:DrawShadow(false) + self.heldby = 0 +end + +function ENT:PhysicsUpdate(phys) + if self.heldby <= 0 then + phys:Sleep() + end +end + +local function textScreenPickup(ply, ent) + if IsValid(ent) and ent:GetClass() == "textscreen" then + ent.heldby = ent.heldby + 1 + end +end +hook.Add("PhysgunPickup", "textScreensPreventTravelPickup", textScreenPickup) + +local function textScreenDrop(ply, ent) + if IsValid(ent) and ent:GetClass() == "textscreen" then + ent.heldby = ent.heldby - 1 + end +end +hook.Add("PhysgunDrop", "textScreensPreventTravelDrop", textScreenDrop) + +local function textScreenCanTool(ply, trace, tool) + if IsValid(trace.Entity) and trace.Entity:GetClass() == "textscreen" then + if !(tool == "textscreen" or tool == "remover") then + return false + end + end +end +hook.Add("CanTool", "textScreensPreventTools", textScreenCanTool, -5) + +function ENT:SetLine(line, text, color, size, font) + if string.len(text) > 180 then + text = string.sub(text, 1, 180) .. "..." + end + + self.lines = self.lines or {} + self.lines[tonumber(line)] = { + ["text"] = text, + ["color"] = color, + ["font"] = font, + ["size"] = size + } + + self:SetNetVar("lines", self.lines) +end + +duplicator.RegisterEntityClass('textscreen', function(ply, data) + + if IsValid(ply) and not ply:CheckLimit('textscreens') then return false end + + local ent = duplicator.GenericDuplicatorFunction(ply, data) + ent:SetNetVar('lines', ent.lines) + + if IsValid(ply) then + ply:AddCount('textscreens', ent) + ply:AddCleanup('textscreens', ent) + end + + return ent + +end, 'Data', 'lines') diff --git a/garrysmod/addons/gmod-tools/lua/entities/textscreen/shared.lua b/garrysmod/addons/gmod-tools/lua/entities/textscreen/shared.lua new file mode 100644 index 0000000..cba8f79 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/entities/textscreen/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Textscreen" +ENT.Author = "" +ENT.Spawnable = true +ENT.AdminSpawnable = false + +ENT.Tool = 'textscreen' + +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "IsPersisted") +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/improvedstacker/improvedstacker.lua b/garrysmod/addons/gmod-tools/lua/improvedstacker/improvedstacker.lua new file mode 100644 index 0000000..1f0dedc --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/improvedstacker/improvedstacker.lua @@ -0,0 +1,483 @@ +--[[-------------------------------------------------------------------------- + Improved Stacker Module + + Author: + Mista-Tea ([IJWTB] Thomas) + + License: + The MIT License (MIT) + + Copyright (c) 2014-2016 Mista-Tea + + 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. + + Changelog: +----------------------------------------------------------------------------]] + +local math = math +local hook = hook +local Angle = Angle +local Vector = Vector +local GetConVar = GetConVar +local duplicator = duplicator +local CreateConVar = CreateConVar + +--[[-------------------------------------------------------------------------- +-- Namespace Tables +--------------------------------------------------------------------------]]-- + +module( "improvedstacker", package.seeall ) + +--[[-------------------------------------------------------------------------- +-- Localized Functions & Variables +--------------------------------------------------------------------------]]-- + +-- enums for determining stack relativity +MODE_WORLD = 1 -- stacking relative to the world +MODE_PROP = 2 -- stacking relative to the prop + +-- lookup table for validating relative values +Modes = { + [MODE_WORLD] = true, + [MODE_PROP] = true, +} + +-- enums for determining the direction to stack props +DIRECTION_FRONT = 1 +DIRECTION_BACK = 2 +DIRECTION_RIGHT = 3 +DIRECTION_LEFT = 4 +DIRECTION_UP = 5 +DIRECTION_DOWN = 6 + +-- lookup table for validating direction values +Directions = { + [DIRECTION_FRONT] = true, + [DIRECTION_BACK] = true, + [DIRECTION_RIGHT] = true, + [DIRECTION_LEFT] = true, + [DIRECTION_UP] = true, + [DIRECTION_DOWN] = true, +} + +-- constants used for when stacking relative to the World +ANGLE_ZERO = Angle( 0, 0, 0 ) +VECTOR_FRONT = ANGLE_ZERO:Forward() +VECTOR_RIGHT = ANGLE_ZERO:Right() +VECTOR_UP = ANGLE_ZERO:Up() +VECTOR_BACK = -VECTOR_FRONT +VECTOR_LEFT = -VECTOR_RIGHT +VECTOR_DOWN = -VECTOR_UP + +-- there has been a longstanding problem where stacked entities were an inch apart (figuratively), causing gaps everywhere. +-- as it turns out, fixing this issue is as easy as subtracting 0.5 from the forward component of the offset vector. +MAGIC_OFFSET = -0.5 + +--[[-------------------------------------------------------------------------- +-- Namespace Functions +--------------------------------------------------------------------------]]-- + +if ( SERVER ) then + + -- the tables below are used internally and should only generally be interfaced with + -- via the functions declared afterward. + -- basically treat them as private, since they are only public for auto-refresh compatibility + + -- holds the current stacked entity count for every player + m_EntCount = m_EntCount or {} + -- holds the last stacker usage for every player + m_StackTime = m_StackTime or {} + -- holds every stacker entity created + m_Ents = m_Ents or {} + + --[[-------------------------------------------------------------------------- + -- GetEntCount( player, number ) + --]]-- + function GetEntCount( ply, default ) + return m_EntCount[ ply:SteamID() ] or default + end + --[[-------------------------------------------------------------------------- + -- SetEntCount( player, number ) + --]]-- + function SetEntCount( ply, num ) + m_EntCount[ ply:SteamID() ] = num + end + --[[-------------------------------------------------------------------------- + -- IncrementEntCount( player, number ) + --]]-- + function IncrementEntCount( ply, num ) + m_EntCount[ ply:SteamID() ] = GetEntCount( ply, 0 ) + (num or 1) + end + --[[-------------------------------------------------------------------------- + -- DecrementEntCount( player, number ) + --]]-- + function DecrementEntCount( ply, num ) + m_EntCount[ ply:SteamID() ] = ( m_EntCount[ ply:SteamID() ] and m_EntCount[ ply:SteamID() ] - (num or 1) ) or 0 + end + + --[[-------------------------------------------------------------------------- + -- SetLastStackTime( player, number ) + --]]-- + function SetLastStackTime( ply, num ) + m_StackTime[ ply:SteamID() ] = num + end + --[[-------------------------------------------------------------------------- + -- GetLastStackTime( player, number ) + --]]-- + function GetLastStackTime( ply, default ) + return m_StackTime[ ply:SteamID() ] or default + end + + --[[-------------------------------------------------------------------------- + -- Initialize( string ) + -- + -- This should be called immediately after including this file so that the follow + -- variables/functions can use the stacker tool's mode (i.e., the name of the file itself + -- and what is subsequently used in all of the cvars). + --]]-- + function Initialize( mode ) + mode = mode or "stacker_improved" + + --[[-------------------------------------------------------------------------- + -- Hook :: PlayerInitialSpawn + + -- Sets the newly connected player's total stacker ents to 0. + -- See TOOL:IsExceedingMax() for more details + --]]-- + hook.Add( "PlayerInitialSpawn", mode.."_set_ent_count", function( ply ) + m_EntCount[ ply:SteamID() ] = 0 + end ) + --[[-------------------------------------------------------------------------- + -- Hook :: PlayerDisconnected + -- + -- Removes the player from the table when they disconnect (for sanitation). + --]]-- + hook.Add( "PlayerDisconnected", mode.."_remove_ent_count", function( ply ) + m_EntCount[ ply:SteamID() ] = nil + end ) + + --[[-------------------------------------------------------------------------- + -- MarkEntity( player, entity, table ) + -- + -- Marks the entity as a stacker entity. This allows the entity to be + -- collision-checked in GM.ShouldCollide. + --]]-- + function MarkEntity( ply, ent, data ) + m_Ents[ ent ] = true + duplicator.StoreEntityModifier( ent, mode, { StackerEnt = true } ) + ent:SetCustomCollisionCheck( true ) + + -- when the entity is removed, sanitize our internal m_Ents array + ent:CallOnRemove( mode, function( ent ) + ClearEntity( ent ) + end ) + end + --duplicator.RegisterEntityModifier( mode, MarkEntity ) + --[[-------------------------------------------------------------------------- + -- ClearEntity( entity ) + -- + -- Removes the entity from the internal m_Ents array for sanitation purposes. + -- This is called when an entity is just about to be removed. + --]]-- + function ClearEntity( ent ) + if ( m_Ents[ ent ] ) then m_Ents[ ent ] = nil end + end + + --[[-------------------------------------------------------------------------- + -- CanUnfreeze( player, entity, physObject ) + --]]-- + function CanUnfreeze( ply, ent, phys ) + if ( m_Ents[ ent ] ) then print("nope") return false end + end + --hook.Add( "CanPlayerUnfreeze", mode, CanUnfreeze ) + --hook.Add( "PhysgunPickup", mode, CanUnfreeze ) + --hook.Remove( "CanPlayerUnfreeze", mode ) + --hook.Remove( "PhysgunPickup", mode ) + + local cvarNoCollideAll + local cvarNoCollide + --[[-------------------------------------------------------------------------- + -- ShouldCollide( entity, entity ) + --]]-- + function ShouldCollide( a, b ) + if ( not cvarNoCollideAll ) then cvarNoCollideAll = GetConVar( mode.."_force_nocollide_all" ) end + if ( not cvarNoCollide ) then cvarNoCollide = GetConVar( mode.."_force_nocollide" ) end + + if ( cvarNoCollideAll:GetBool() ) then + if ( m_Ents[ a ] ) then + if not ( b:IsPlayer() or b:IsWorld() or b:IsNPC() or b:IsVehicle() ) then return false end + elseif ( m_Ents[ b ] ) then + if not ( a:IsPlayer() or a:IsWorld() or b:IsNPC() or b:IsVehicle() ) then return false end + end + elseif ( cvarNoCollide:GetBool() ) then + if ( m_Ents[ a ] and m_Ents[ b ] ) then return false end + end + end + --hook.Add( "ShouldCollide", mode, ShouldCollide ) + --hook.Remove( "ShouldCollide", mode ) + end + +elseif ( CLIENT ) then + + -- the table below is used internally and should only generally be interfaced with + -- via the functions declared afterward. + -- basically treat it as private, since it is only public for auto-refresh compatibility + + m_Ghosts = m_Ghosts or {} + m_LookingAt = m_LookingAt or nil + m_LookedAt = m_LookedAt or nil + + --[[-------------------------------------------------------------------------- + -- GetGhosts() + --]]-- + function GetGhosts() + return m_Ghosts + end + --[[-------------------------------------------------------------------------- + -- SetGhosts( table ) + --]]-- + function SetGhosts( tbl ) + m_Ghosts = tbl + end + + --[[-------------------------------------------------------------------------- + -- GetLookingAt() + --]]-- + function GetLookingAt() + return m_LookingAt + end + --[[-------------------------------------------------------------------------- + -- SetLookingAt( entity ) + --]]-- + function SetLookingAt( ent ) + m_LookingAt = ent + end + + --[[-------------------------------------------------------------------------- + -- GetLookedAt() + --]]-- + function GetLookedAt() + return m_LookedAt + end + --[[-------------------------------------------------------------------------- + -- SetLookedAt( entity ) + --]]-- + function SetLookedAt( ent ) + m_LookedAt = ent + end + + --[[-------------------------------------------------------------------------- + -- ReleaseGhosts() + -- + -- Attempts to remove all ghosted props in the stack. + -- This occurs when the player stops looking at a prop with the stacker tool equipped. + --]]-- + function ReleaseGhosts() + if ( #m_Ghosts == 0 ) then return end + + for i = 1, #m_Ghosts do + if ( not IsValid( m_Ghosts[ i ] ) ) then continue end + SafeRemoveEntityDelayed( m_Ghosts[ i ], 0 ) + m_Ghosts[ i ] = nil + end + end + + --[[-------------------------------------------------------------------------- + -- Initialize( string ) + -- + -- This should be called immediately after including this file so that the follow + -- variables/functions can use the stacker tool's mode (i.e., the name of the file itself + -- and what is subsequently used in all of the cvars). + --]]-- + function Initialize( mode ) + mode = mode or "stacker_improved" + + SETTINGS_DEFAULT = { + [mode.."_set_max_per_player"] = "-1", + [mode.."_set_max_per_stack"] = "15", + [mode.."_set_delay"] = "0.5", + [mode.."_set_max_offsetx"] = "200", + [mode.."_set_max_offsety"] = "200", + [mode.."_set_max_offsetz"] = "200", + [mode.."_set_force_freeze"] = "0", + [mode.."_set_force_weld"] = "0", + [mode.."_set_force_nocollide"] = "0", + [mode.."_set_force_stayinworld"] = "1", + } + + SETTINGS_SANDBOX = { + [mode.."_set_max_per_player"] = "-1", + [mode.."_set_max_per_stack"] = "30", + [mode.."_set_delay"] = "0.5", + [mode.."_set_max_offsetx"] = "1000", + [mode.."_set_max_offsety"] = "1000", + [mode.."_set_max_offsetz"] = "1000", + [mode.."_set_force_freeze"] = "0", + [mode.."_set_force_weld"] = "0", + [mode.."_set_force_nocollide"] = "0", + [mode.."_set_force_stayinworld"] = "0", + } + + SETTINGS_DARKRP = { + [mode.."_set_max_per_player"] = "50", + [mode.."_set_max_per_stack"] = "5", + [mode.."_set_delay"] = "1", + [mode.."_set_max_offsetx"] = "200", + [mode.."_set_max_offsety"] = "200", + [mode.."_set_max_offsetz"] = "200", + [mode.."_set_force_freeze"] = "1", + [mode.."_set_force_weld"] = "0", + [mode.."_set_force_nocollide"] = "1", + [mode.."_set_force_stayinworld"] = "1", + } + + SETTINGS_SINGLEPLAYER = { + [mode.."_set_max_per_player"] = "-1", + [mode.."_set_max_per_stack"] = "100", + [mode.."_set_delay"] = "0", + [mode.."_set_max_offsetx"] = "10000", + [mode.."_set_max_offsety"] = "10000", + [mode.."_set_max_offsetz"] = "10000", + [mode.."_set_force_freeze"] = "0", + [mode.."_set_force_weld"] = "0", + [mode.."_set_force_nocollide"] = "0", + [mode.."_set_force_stayinworld"] = "0", + } + end + +end + +-- +-- The functions below are used both serverside and clientside for properly orienting +-- and spacing props in a stack +-- + +-- Lookup table that holds functions related to determining the direction of a stack +DirectionFunctions = { + [MODE_WORLD] = { + [DIRECTION_FRONT] = function() return VECTOR_FRONT end, + [DIRECTION_BACK] = function() return VECTOR_BACK end, + [DIRECTION_RIGHT] = function() return VECTOR_RIGHT end, + [DIRECTION_LEFT] = function() return VECTOR_LEFT end, + [DIRECTION_UP] = function() return VECTOR_UP end, + [DIRECTION_DOWN] = function() return VECTOR_DOWN end, + }, + + [MODE_PROP] = { + [DIRECTION_FRONT] = function( angle ) return angle:Forward() end, + [DIRECTION_BACK] = function( angle ) return -angle:Forward() end, + [DIRECTION_RIGHT] = function( angle ) return angle:Right() end, + [DIRECTION_LEFT] = function( angle ) return -angle:Right() end, + [DIRECTION_UP] = function( angle ) return angle:Up() end, + [DIRECTION_DOWN] = function( angle ) return -angle:Up() end, + } +} + +-- Lookup table that holds functions related to determining the distance to offset each prop in a stack +-- before applying the client's actual x/y/z offset values +DistanceFunctions = { + [DIRECTION_FRONT] = function( min, max ) return math.abs(max.x - min.x) end, + [DIRECTION_BACK] = function( min, max ) return math.abs(max.x - min.x) end, + [DIRECTION_RIGHT] = function( min, max ) return math.abs(max.y - min.y) end, + [DIRECTION_LEFT] = function( min, max ) return math.abs(max.y - min.y) end, + [DIRECTION_UP] = function( min, max ) return math.abs(max.z - min.z) end, + [DIRECTION_DOWN] = function( min, max ) return math.abs(max.z - min.z) end, +} + +-- Lookup table that holds functions related to determining the distance to offset each prop in a stack +-- based on the client's x/y/z offset values +OffsetFunctions = { + [DIRECTION_FRONT] = function( angle, offset ) return ( angle:Forward() * offset.x) + ( angle:Up() * offset.z) + ( angle:Right() * offset.y) end, + [DIRECTION_BACK] = function( angle, offset ) return (-angle:Forward() * offset.x) + ( angle:Up() * offset.z) + (-angle:Right() * offset.y) end, + [DIRECTION_RIGHT] = function( angle, offset ) return ( angle:Right() * offset.x) + ( angle:Up() * offset.z) + (-angle:Forward() * offset.y) end, + [DIRECTION_LEFT] = function( angle, offset ) return (-angle:Right() * offset.x) + ( angle:Up() * offset.z) + ( angle:Forward() * offset.y) end, + [DIRECTION_UP] = function( angle, offset ) return ( angle:Up() * offset.x) + (-angle:Forward() * offset.z) + ( angle:Right() * offset.y) end, + [DIRECTION_DOWN] = function( angle, offset ) return (-angle:Up() * offset.x) + ( angle:Forward() * offset.z) + ( angle:Right() * offset.y) end, +} + +RotationFunctions = { + [DIRECTION_FRONT] = function( angle ) return angle:Right(), angle:Up(), angle:Forward() end, + [DIRECTION_BACK] = function( angle ) return -angle:Right(), angle:Up(), -angle:Forward() end, + [DIRECTION_RIGHT] = function( angle ) return -angle:Forward(), angle:Up(), angle:Right() end, + [DIRECTION_LEFT] = function( angle ) return angle:Forward(), angle:Up(), -angle:Right() end, + [DIRECTION_UP] = function( angle ) return -angle:Right(), angle:Forward(), angle:Up() end, + [DIRECTION_DOWN] = function( angle ) return angle:Right(), angle:Forward(), -angle:Up() end, +} + +--[[-------------------------------------------------------------------------- +-- GetDirection( number, number, angle ) +-- +-- Calculates the direction to point the entity to by depending on whether the stack is +-- created relative to the world or the original prop, and the direction to stack in. +--]]-- +function GetDirection( stackMode, stackDir, angle ) + return DirectionFunctions[ stackMode ][ stackDir ]( angle ) +end + +--[[-------------------------------------------------------------------------- +-- GetDistance( number, number, entity ) +-- +-- Calculates the space occupied by the entity depending on the stack direction. +-- This represents the number of units to offset the stack entities so they appear +-- directly in front of the previous entity (depending on direction). +--]]-- +function GetDistance( stackMode, stackDir, ent ) + if ( stackMode == MODE_WORLD ) then + return DistanceFunctions[ stackDir ]( ent:WorldSpaceAABB() ) + elseif ( stackMode == MODE_PROP ) then + return DistanceFunctions[ stackDir ]( ent:OBBMins(), ent:OBBMaxs() ) + end +end + +--[[-------------------------------------------------------------------------- +-- GetOffset( number, number, angle, vector ) +-- +-- Calculates a direction vector used for offsetting a stacked entity based on the facing angle of the previous entity. +-- This function uses a lookup table for added optimization as opposed to an if-else block. +--]]-- +function GetOffset( stackMode, stackDir, angle, offset ) + -- if stacking relative to the world, apply the magic offset fix to the correct direction + if ( stackMode == MODE_WORLD ) then + local direction = DirectionFunctions[ stackMode ][ stackDir ]() + direction = direction * MAGIC_OFFSET + return offset + direction + -- if stacking relative to the prop, apply the magic offset only to the forward (x) component of the vector + elseif ( stackMode == MODE_PROP ) then + local trueOffset = Vector() + trueOffset:Set( offset ) + trueOffset.x = trueOffset.x + MAGIC_OFFSET + return OffsetFunctions[ stackDir ]( angle, trueOffset ) + end +end + +--[[-------------------------------------------------------------------------- +-- RotateAngle( angle, angle ) +-- +-- Rotates the first angle by the second angle. This ensures proper rotation +-- along all three axes and prevents various problems related to simply adding +-- two angles together. The first angle is modified directly by refence, so this does not +-- return anything. +--]]-- +function RotateAngle( stackMode, stackDir, angle, rotation ) + local axisPitch, axisYaw, axisRoll = RotationFunctions[ stackDir ]( angle ) + + angle:RotateAroundAxis( axisPitch, rotation.p ) + angle:RotateAroundAxis( axisYaw, -rotation.y ) + angle:RotateAroundAxis( axisRoll, rotation.r ) +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/improvedstacker/localify.lua b/garrysmod/addons/gmod-tools/lua/improvedstacker/localify.lua new file mode 100644 index 0000000..15ddc54 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/improvedstacker/localify.lua @@ -0,0 +1,323 @@ +--[[-------------------------------------------------------------------------- + Localify Module + + Author: + Mista-Tea ([IJWTB] Thomas) + + License: + The MIT License (MIT) + + Copyright (c) 2015 Mista-Tea + + 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. + + Changelog: +----------------------------------------------------------------------------]] + +--[[-------------------------------------------------------------------------- +-- Namespace Tables +--------------------------------------------------------------------------]]-- + +module( "localify", package.seeall ) + +languages = { + bg = "Bulgarian", + cs = "Czech", + da = "Danish", + de = "German", + el = "Greek", + ["en-pt"] = "Pirate", + en = "English", + es = "Spanish", + et = "Estonian", + fi = "Finnish", + fr = "French", + he = "Hebrew", + hr = "Croatian", + hu = "Hungarian", + it = "Italian", + ja = "Japanese", + ko = "Korean", + lt = "Lithuanian", + nl = "Dutch", + no = "Norwegian", + pl = "Polish", + ["pt-br"] = "Brazilian Portuguese", + ["pt-pt"] = "Portuguese", + ru = "Russian", + sk = "Slovak", + ["sv-se"] = "Swedish", + th = "Thai", + tr = "Turkish", + uk = "Ukranian", + vi = "Vietnamese", + ["zh-cn"] = "Simplified Chinese", + ["zh-tw"] = "Traditional Chinese", +} + +localizations = localizations or { + bg = {}, + cs = {}, + da = {}, + de = {}, + el = {}, + ["en-pt"] = {}, + en = {}, + es = {}, + et = {}, + fi = {}, + fr = {}, + he = {}, + hr = {}, + hu = {}, + it = {}, + ja = {}, + ko = {}, + lt = {}, + nl = {}, + no = {}, + pl = {}, + ["pt-br"] = {}, + ["pt-pt"] = {}, + ru = {}, + sk = {}, + ["sv-se"] = {}, + th = {}, + tr = {}, + uk = {}, + vi = {}, + ["zh-cn"] = {}, + ["zh-tw"] = {}, +} + +--[[-------------------------------------------------------------------------- +-- Localized Functions & Variables +--------------------------------------------------------------------------]]-- + +local error = error +local include = include +local tostring = tostring +local GetConVar = GetConVar +local AddCSLuaFile = AddCSLuaFile + +FALLBACK = FALLBACK or "en" + +--[[-------------------------------------------------------------------------- +-- Namespace Functions +--------------------------------------------------------------------------]]-- + +--[[-------------------------------------------------------------------------- +-- localify.Bind( string, string ) +-- +-- Binds the token (key) and localized phrase (value) to the given language (lang). +-- +-- Example: localify.Bind( "en", "#Hello", "Hello" ) +-- Example: localify.Bind( "es", "#Hello", "Hola" ) +-- Example: localify.Bind( "fr", "#Hello", "Bonjour" ) +--]]-- +function Bind( lang, key, value ) + if ( not IsValidLanguage( lang ) ) then error( "Invalid language provided ('"..tostring(lang).."')" ) return end + + localizations[ lang:lower() ][ key ] = value +end + +--[[-------------------------------------------------------------------------- +-- localify.Localize( string, string ) +-- +-- Returns the localized phrase associated with the token (key). +-- If a language (lang) is provided, the phrase bound to that language will be returned. +-- If no language is provided, the language will default to the client or server's locale. +-- If a localized phrase is not found and returnKey is true-ful, the key will be returned. +-- If a localized phrase is not found and returnKey is false-ful, the phrase associated with the fallback language (en' by default) will be returned, if any. +-- Otherwise, nil will be returned if no binding exists. +-- +-- Example: local str = localify.Localize( "#Hello" ) -- Returns either the locale's binding or the default binding (if any) +-- Example: local str = localify.Localize( "#Hello", "es" ) -- Returns either a Spanish binding, the locale's binding, or the default binding (if any) +-- Example: local str = localify.Localize( "#Hello", "fr" ) -- Returns either a French binding, the locale's binding, or the default binding (if any) +-- Example: local str = localify.Localize( "#Hello", "de", true ) -- Returns either a German binding, the locale's binding, or the key +-- Example: local str = localify.Localize( "#Hello", nil, true ) -- Returns either the locale's binding or the key +--]]-- +function Localize( key, lang, returnKey ) + if ( lang and not IsValidLanguage( lang ) ) then error( "Invalid language provided ('"..tostring(lang).."')" ) return end + + local tbl = localizations[ (lang and lang:lower()) or GetLocale() ] + + return ( tbl and tbl[ key ] ) -- If there is a bind, return it + or ( returnKey and key ) -- If there is no bind and we want to return the key on failure, return the key + or ( localizations[ FALLBACK ] and localizations[ FALLBACK ][ key ] ) -- If there is a bind in the fallback language, return it + or nil -- Otherwise return nil +end + + + +--[[-------------------------------------------------------------------------- +-- localify.AddLanguage( string, string ) +-- +-- Adds a non-GMod language to the table of valid languages. +-- +-- Example: localify.AddLanguage( "zom", "Zombie" ) +-- Example: localify.AddLanguage( "fil", "Filipino" ) +--]]-- +function AddLanguage( lang, name ) + if ( IsValidLanguage( lang ) ) then return end + + languages[ lang:lower() ] = name + localizations[ lang:lower() ] = {} +end + +--[[-------------------------------------------------------------------------- +-- localify.RemoveLanguage( string ) +-- +-- Removes a language from the table of valid languages. +-- If the removed language was the fallback language, "en" (English) will be +-- set as the new fallback language automatically. +-- +-- Example: localify.RemoveLanguage( "zom" ) +-- Example: localify.RemoveLanguage( "fil" ) +--]]-- +function RemoveLanguage( lang ) + if ( not IsValidLanguage( lang ) ) then return end + + languages[ lang:lower() ] = nil + localizations[ lang:lower() ] = nil + + if ( lang:lower() == FALLBACK ) then FALLBACK = "en" end +end + +--[[-------------------------------------------------------------------------- +-- localify.IsValidLanguage() +-- +-- Checks if the passed 2- or 4-letter language code is supported by Localify. +-- Returns true if valid, false if invalid. +-- +-- Example: localify.IsValidLanguage( "vi" ) -- true by default +-- Example: localify.IsValidLanguage( "zz" ) -- false by default +--]]-- +function IsValidLanguage( lang ) + return lang and languages[ lang:lower() ] +end + +--[[-------------------------------------------------------------------------- +-- localify.SetFallbackLanguage() +-- +-- Sets the fallback language to use when a localized phrase is unavailable. +-- This is set to "en" (English) by default. +-- +-- Example: localify.SetFallbackLanguage( "de" ) -- fallback language is now German +--]]-- +function SetFallbackLanguage( lang ) + if ( not IsValidLanguage( lang ) ) then error( "Invalid language provided ('"..tostring(lang).."')" ) return end + + FALLBACK = lang:lower() +end + + + +--[[-------------------------------------------------------------------------- +-- localify.GetLocale( player ) +-- +-- Returns the client or server's in-game locale (separate from system locale). +-- Returns the fallback language if the cvar is empty. +-- The cvar holding this value is "gmod_language". +--]]-- +function GetLocale( ply ) + return ( SERVER and ply and ply:GetInfo( "localify_language" ):lower() ) + or ( GetConVarString( "localify_language" ) == "" and FALLBACK or GetConVarString( "localify_language" ):lower() ) +end + +--[[-------------------------------------------------------------------------- +-- localify.GetLanguages( string ) +-- +-- Returns the table of valid languages and their associated names. +--]]-- +function GetLanguages() + return languages +end + +--[[-------------------------------------------------------------------------- +-- localify.GetLocalizations( string ) +-- +-- Returns every bound localization, the bindings of the given language, or nil +-- if no language was found. If the language is valid but doesn't contain any bindings, +-- an empty table will be returned. +-- +-- Example: localify.GetLocalizations() -- returns bindings for every language +-- Example: localify.GetLocalizations( "en" ) -- returns all English bindings +--]]-- +function GetLocalizations( lang ) + return ( not lang and localizations ) or ( lang and localizations[ lang:lower() ] ) or nil +end + +--[[-------------------------------------------------------------------------- +-- localify.GetFallbackLanguage() +-- +-- Returns the current fallback language ("en" by default). +--]]-- +function GetFallbackLanguage() + return FALLBACK +end + + + +--[[-------------------------------------------------------------------------- +-- localify.LoadSharedFile( string ) +-- +-- Loads a file containing localization phrases onto the server and connecting clients. +--]]-- +function LoadSharedFile( path ) + include( path ) + if ( SERVER ) then AddCSLuaFile( path ) end +end + +--[[-------------------------------------------------------------------------- +-- localify.LoadServerFile( string ) +-- +-- Loads a file containing localization phrases onto the server. +--]]-- +function LoadServerFile( path ) + if ( CLIENT ) then return end + include( path ) +end + +--[[-------------------------------------------------------------------------- +-- localify.LoadClientFile( string ) +-- +-- Loads a file containing localization phrases onto connecting clients. +--]]-- +function LoadClientFile( path ) + if ( SERVER ) then AddCSLuaFile( path ) return end + include( path ) +end + + + +if ( CLIENT ) then + + -- Create a client cvar that copies the gmod_language cvar so that we can retrieve it from + -- the server with ply:GetInfo( "localify_language" ) + CreateClientConVar( "localify_language", GetConVarString( "gmod_language" ), false, true ) + + -- Check for changes to the gmod_language cvar and replicate them to localify_language + cvars.AddChangeCallback( "gmod_language", function( name, old, new ) + if ( not IsValidLanguage( new ) ) then return end + + RunConsoleCommand( "localify_language", new ) + end, "localify" ) + +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/improvedstacker/localization.lua b/garrysmod/addons/gmod-tools/lua/improvedstacker/localization.lua new file mode 100644 index 0000000..468b4e8 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/improvedstacker/localization.lua @@ -0,0 +1,526 @@ +--local prefix = "#tool."..debug.getinfo( 1, "S" ).source:match(".+[/?\\?](.+)%.lua").."." + +local prefix = "#tool.stacker_improved." + +localify.Bind( "en", prefix.."language_en", "English" ) +localify.Bind( "bg", prefix.."language_bg", "Български" ) +localify.Bind( "fr", prefix.."language_fr", "Français" ) +localify.Bind( "pl", prefix.."language_pl", "Polski" ) + +--[[-------------------------------------------------------------------------- +-- English Localization +--------------------------------------------------------------------------]]-- + +-- Tool Settings +localify.Bind( "en", prefix.."name", "Stacker - Improved" ) +localify.Bind( "en", prefix.."desc", "Easily stack duplicated props in any direction" ) +localify.Bind( "en", prefix.."left", "Create a stack" ) +localify.Bind( "en", prefix.."shift_left", "Increase stack size" ) +localify.Bind( "en", prefix.."right", "Create a single prop" ) +localify.Bind( "en", prefix.."shift_right", "Decrease stack size" ) +localify.Bind( "en", prefix.."reload", "Change stack direction" ) +localify.Bind( "en", "Undone_stacker_improved", "Undone stacked prop(s)" ) +-- Errors +localify.Bind( "en", prefix.."error_blocked_by_server", "The server has blocked you from changing this console variable" ) +localify.Bind( "en", prefix.."error_not_admin", "You must be in the 'admin' usergroup to change this console variable" ) +localify.Bind( "en", prefix.."error_invalid_argument", "You must enter a valid number value" ) +localify.Bind( "en", prefix.."error_max_per_stack", "The max props that can be stacked at once is limited to " ) +localify.Bind( "en", prefix.."error_too_quick", "You are using stacker too quickly" ) +localify.Bind( "en", prefix.."error_max_per_player", "Stacker prop limit reached" ) +localify.Bind( "en", prefix.."error_not_in_world", "Stacked props must be spawned within the world" ) +localify.Bind( "en", prefix.."error_max_constraints", "Failed to create constraint, most likely ran out of entity slots" ) +-- Labels +localify.Bind( "en", prefix.."label_presets", "Stacker Presets: " ) +localify.Bind( "en", prefix.."label_relative", "Stack relative to: " ) +localify.Bind( "en", prefix.."label_direction", "Stack direction: " ) +localify.Bind( "en", prefix.."label_count", "Stack size" ) +localify.Bind( "en", prefix.."label_reset_offsets", "Reset offsets" ) +localify.Bind( "en", prefix.."label_reset_angles", "Reset angles" ) +localify.Bind( "en", prefix.."label_show_settings", "Click to show settings" ) +localify.Bind( "en", prefix.."label_hide_settings", "Click to hide settings" ) +localify.Bind( "en", prefix.."label_x", "X (-back, +front)" ) +localify.Bind( "en", prefix.."label_y", "Y (-left, +right)" ) +localify.Bind( "en", prefix.."label_z", "Z (-down, +up)" ) +localify.Bind( "en", prefix.."label_pitch", "Pitch (-down, +up)" ) +localify.Bind( "en", prefix.."label_yaw", "Yaw (-left, +right)" ) +localify.Bind( "en", prefix.."label_roll", "Roll (-left, +right)" ) +localify.Bind( "en", prefix.."label_language", "Language: " ) +localify.Bind( "en", prefix.."label_credits", "" ) +localify.Bind( "en", prefix.."label_max_per_stack", "Stack size" ) +localify.Bind( "en", prefix.."label_max_per_player", "Props per player" ) +localify.Bind( "en", prefix.."label_max_offsetx", "Maximum X offset" ) +localify.Bind( "en", prefix.."label_max_offsety", "Maximum Y offset" ) +localify.Bind( "en", prefix.."label_max_offsetz", "Maximum Z offset" ) +localify.Bind( "en", prefix.."label_delay", "Delay" ) +localify.Bind( "en", prefix.."label_opacity", "Opacity" ) +-- Checkboxes +localify.Bind( "en", prefix.."checkbox_freeze", "Freeze stacked props" ) +localify.Bind( "en", prefix.."checkbox_weld", "Weld stacked props" ) +localify.Bind( "en", prefix.."checkbox_nocollide", "No-Collide stacked props with each other" ) +localify.Bind( "en", prefix.."checkbox_nocollide_all", "No-Collide stacked props with EVERYTHING" ) +localify.Bind( "en", prefix.."checkbox_use_shift_key", "Change stack size with SHIFT + left/right-click" ) +localify.Bind( "en", prefix.."checkbox_relative", "Stack relative to new rotation" ) +localify.Bind( "en", prefix.."checkbox_material", "Apply material" ) +localify.Bind( "en", prefix.."checkbox_color", "Apply color" ) +localify.Bind( "en", prefix.."checkbox_physprop", "Apply physical properties" ) +localify.Bind( "en", prefix.."checkbox_ghost", "Ghost all props in the stack" ) +localify.Bind( "en", prefix.."checkbox_halo", "Add halos to ghosted props" ) +localify.Bind( "en", prefix.."checkbox_halo_color", "Halo color" ) +localify.Bind( "en", prefix.."checkbox_axis", "Draw XYZ axis" ) +localify.Bind( "en", prefix.."checkbox_axis_labels", "Draw XYZ axis labels" ) +localify.Bind( "en", prefix.."checkbox_axis_angles", "Draw XYZ axis angles" ) +localify.Bind( "en", prefix.."checkbox_stayinworld", "Stay in world" ) +-- Comboboxes +localify.Bind( "en", prefix.."combobox_world", "World" ) +localify.Bind( "en", prefix.."combobox_prop", "Prop" ) +localify.Bind( "en", prefix.."combobox_direction_up", "Up" ) +localify.Bind( "en", prefix.."combobox_direction_down", "Down" ) +localify.Bind( "en", prefix.."combobox_direction_front", "Front" ) +localify.Bind( "en", prefix.."combobox_direction_back", "Back" ) +localify.Bind( "en", prefix.."combobox_direction_right", "Right" ) +localify.Bind( "en", prefix.."combobox_direction_left", "Left" ) +localify.Bind( "en", prefix.."combobox_default", "Default" ) +localify.Bind( "en", prefix.."combobox_sandbox", "Sandbox" ) +localify.Bind( "en", prefix.."combobox_darkrp", "DarkRP" ) +localify.Bind( "en", prefix.."combobox_singleplayer", "Singleplayer" ) +-- HUD +localify.Bind( "en", prefix.."hud_front", "Front" ) +localify.Bind( "en", prefix.."hud_right", "Right" ) +localify.Bind( "en", prefix.."hud_up", "Up" ) +-- Help +localify.Bind( "en", prefix.."help_max_per_player", "Maximum stacked props each player is limited to." ) +localify.Bind( "en", prefix.."help_max_per_stack", "Maximum props per stack (left-click)." ) +localify.Bind( "en", prefix.."help_delay", "Delay (in seconds) between each Stacker use (left/right-click)" ) +localify.Bind( "en", prefix.."help_max_offsetx", "Maximum distance between stacked props (X-axis)." ) +localify.Bind( "en", prefix.."help_max_offsety", "Maximum distance between stacked props (Y-axis)." ) +localify.Bind( "en", prefix.."help_max_offsetz", "Maximum distance between stacked props (Z-axis)." ) +localify.Bind( "en", prefix.."help_freeze", "Stacked props are frozen when spawned." ) +localify.Bind( "en", prefix.."help_weld", "Stacked props are welded to each other when spawned." ) +localify.Bind( "en", prefix.."help_nocollide", "Stacked props won't collide with other stacked props." ) +localify.Bind( "en", prefix.."help_nocollide_all", "Stacked props won't collide with anything except players, NPCs, vehicles, and the world." ) +localify.Bind( "en", prefix.."help_stayinworld", "Prevents stacked props from being created outside of the map." ) +-- Warnings +localify.Bind( "en", prefix.."warning_max_per_player", "Primarily for Roleplay gamemodes. For Sandbox gamemodes, this should generally be unlimited (-1)." ) +localify.Bind( "en", prefix.."warning_max_offsetx", "Primarily for Roleplay gamemodes. Don't modify unless you know what you're doing." ) +localify.Bind( "en", prefix.."warning_max_offsety", "Primarily for Roleplay gamemodes. Don't modify unless you know what you're doing." ) +localify.Bind( "en", prefix.."warning_max_offsetz", "Primarily for Roleplay gamemodes. Don't modify unless you know what you're doing." ) +localify.Bind( "en", prefix.."warning_freeze", "For DarkRP." ) +localify.Bind( "en", prefix.."warning_weld", "For DarkRP." ) +localify.Bind( "en", prefix.."warning_nocollide", "For DarkRP." ) +localify.Bind( "en", prefix.."warning_nocollide_all", "DO NOT CHANGE WHILE THE SERVER IS RUNNING. This uses the GM.ShouldCollide hook and is experimental. It works and is guaranteed to stop crashes from Stacker, but needs more testing." ) + +--[[-------------------------------------------------------------------------- +-- Bulgarian Localization +--------------------------------------------------------------------------]]-- + +-- Tool Settings +localify.Bind( "bg", prefix.."name", "Натрупвач - Подобрен" ) +localify.Bind( "bg", prefix.."desc", "Лесно натрупва еднакви предмети във всяка посока" ) +localify.Bind( "bg", prefix.."left", "Създай натрупване" ) +localify.Bind( "bg", prefix.."shift_left", "Увеличи размера на натрупването" ) +localify.Bind( "bg", prefix.."right", "Създай единичен обект" ) +localify.Bind( "bg", prefix.."shift_right", "Намали размера на натрупването" ) +localify.Bind( "bg", prefix.."reload", "Смени направлението на натрупване" ) +localify.Bind( "bg", "Undone_stacker_improved", "Премахва натрупването на предмет(и)" ) +-- Errors +localify.Bind( "bg", prefix.."error_blocked_by_server", "Сървърът ви е блокирал да променяте тази конзолна променлива" ) +localify.Bind( "bg", prefix.."error_not_admin", "Трябва да бъдете в групата 'admin' за да промените тази конзолна променлива" ) +localify.Bind( "bg", prefix.."error_invalid_argument", "Трябва да въведете валидно число за тази стойност" ) +localify.Bind( "bg", prefix.."error_max_per_stack", "Максималния брой предмети които могат да бъдат натрупани на един път е ограничен до " ) +localify.Bind( "bg", prefix.."error_too_quick", "Използвате натрупвача твърде бързо" ) +localify.Bind( "bg", prefix.."error_max_per_player", "Ограничението за натрупани предмети е достигнато" ) +localify.Bind( "bg", prefix.."error_not_in_world", "Натрупваните предмети трябва да се създават вътре в света" ) +localify.Bind( "bg", prefix.."error_max_constraints", "Не можа да се създаде връзка, най вероятно са свършили слотовете за обекти" ) +-- Labels +localify.Bind( "bg", prefix.."label_presets", "Шаблон на натрупвача: " ) +localify.Bind( "bg", prefix.."label_relative", "Натрупвай спрямо: " ) +localify.Bind( "bg", prefix.."label_direction", "Направление на натрупване: " ) +localify.Bind( "bg", prefix.."label_count", "Размер на натрупване" ) +localify.Bind( "bg", prefix.."label_reset_offsets", "Нулиране на отместването" ) +localify.Bind( "bg", prefix.."label_reset_angles", "Нулиране на ъглите" ) +localify.Bind( "bg", prefix.."label_show_settings", "Цъкнете за показване на настройки" ) +localify.Bind( "bg", prefix.."label_hide_settings", "Цъкнете за скриване на настройки" ) +localify.Bind( "bg", prefix.."label_x", "Абсциса (-назад, +напред)" ) +localify.Bind( "bg", prefix.."label_y", "Ордината (-ляво, +дясно)" ) +localify.Bind( "bg", prefix.."label_z", "Апликата (-долу, +горе)" ) +localify.Bind( "bg", prefix.."label_pitch", "Тангаж (-долу, +горе)" ) +localify.Bind( "bg", prefix.."label_yaw", "Азимут (-ляво, +дясно)" ) +localify.Bind( "bg", prefix.."label_roll", "Крен (-ляво, +дясно)" ) +localify.Bind( "bg", prefix.."label_language", "Език: " ) +localify.Bind( "bg", prefix.."label_credits", "Преведено от: [BG][Sk&Bh]Trick or treat KID!" ) +localify.Bind( "bg", prefix.."label_max_per_stack", "Брой на натрупване" ) +localify.Bind( "bg", prefix.."label_max_per_player", "Брой предмети на играч" ) +localify.Bind( "bg", prefix.."label_max_offsetx", "Максимално отместване по абсциса" ) +localify.Bind( "bg", prefix.."label_max_offsety", "Максимално отместване по ордината" ) +localify.Bind( "bg", prefix.."label_max_offsetz", "Максимално отместване по апликата" ) +localify.Bind( "bg", prefix.."label_delay", "Закъснение" ) +localify.Bind( "bg", prefix.."label_opacity", "Тъмнота" ) +-- Checkboxes +localify.Bind( "bg", prefix.."checkbox_freeze", "Замрази натрупаните предмети" ) +localify.Bind( "bg", prefix.."checkbox_weld", "Завари натрупаните предмети" ) +localify.Bind( "bg", prefix.."checkbox_nocollide", "Не-сблъсък на натрупаните предмети един към друг" ) +localify.Bind( "bg", prefix.."checkbox_nocollide_all", "Не-сблъсък на натрупаните предмети с ВСИЧКО" ) +//localify.Bind( "bg", prefix.."checkbox_use_shift_key", "" ) +localify.Bind( "bg", prefix.."checkbox_relative", "Натрупвай спрямо новата ориентация" ) +localify.Bind( "bg", prefix.."checkbox_material", "Приложи текстура" ) +localify.Bind( "bg", prefix.."checkbox_color", "Приложи цвят" ) +localify.Bind( "bg", prefix.."checkbox_physprop", "Приложи физически свойства" ) +localify.Bind( "bg", prefix.."checkbox_ghost", "Сенки за всички предмети в стека" ) +localify.Bind( "bg", prefix.."checkbox_halo", "Добави ореоли към предметите сенки" ) +localify.Bind( "bg", prefix.."checkbox_halo_color", "Цвят на ореола" ) +localify.Bind( "bg", prefix.."checkbox_axis", "Чертай осите XYZ" ) +localify.Bind( "bg", prefix.."checkbox_axis_labels", "Чертай етикети на XYZ осите" ) +localify.Bind( "bg", prefix.."checkbox_axis_angles", "Чертай ъглите на XYZ осите" ) +localify.Bind( "bg", prefix.."checkbox_stayinworld", "Натрупвай в рамките на света" ) +-- Comboboxes +localify.Bind( "bg", prefix.."combobox_world", "Свят" ) +localify.Bind( "bg", prefix.."combobox_prop", "Предмет" ) +localify.Bind( "bg", prefix.."combobox_direction_up", "Горе" ) +localify.Bind( "bg", prefix.."combobox_direction_down", "Долу" ) +localify.Bind( "bg", prefix.."combobox_direction_front", "Отпред" ) +localify.Bind( "bg", prefix.."combobox_direction_back", "Отзад" ) +localify.Bind( "bg", prefix.."combobox_direction_right", "Дясно" ) +localify.Bind( "bg", prefix.."combobox_direction_left", "Ляво" ) +localify.Bind( "bg", prefix.."combobox_default", "По подразбиране" ) +localify.Bind( "bg", prefix.."combobox_sandbox", "Пясъчник" ) +localify.Bind( "bg", prefix.."combobox_darkrp", "Ролева игра" ) +localify.Bind( "bg", prefix.."combobox_singleplayer", "Самостоятелна игра" ) +-- HUD +localify.Bind( "bg", prefix.."hud_front", "Отпред" ) +localify.Bind( "bg", prefix.."hud_right", "Дясно" ) +localify.Bind( "bg", prefix.."hud_up", "Горе" ) +-- Help +localify.Bind( "bg", prefix.."help_max_per_player", "Максимален брой натрупвани предмети до които е ограничен всеки играч" ) +localify.Bind( "bg", prefix.."help_max_per_stack", "Максимален брой предмети при всяко натрупване (ляв клик)." ) +localify.Bind( "bg", prefix.."help_delay", "Закъснение (в секунди) между всяко използване на натрупвача (ляв/десен клик)" ) +localify.Bind( "bg", prefix.."help_max_offsetx", "Максимално разстояние между натрупаните предмети (абсциса)." ) +localify.Bind( "bg", prefix.."help_max_offsety", "Максимално разстояние между натрупаните предмети (ордината)." ) +localify.Bind( "bg", prefix.."help_max_offsetz", "Максимално разстояние между натрупаните предмети (апликата)." ) +localify.Bind( "bg", prefix.."help_freeze", "Натрупваните предмети са замразени при създаване." ) +localify.Bind( "bg", prefix.."help_weld", "Натрупваните предмети са заварени по между си при създаване." ) +localify.Bind( "bg", prefix.."help_nocollide", "Натрупваните предмети няма да се сблъскват с други такива." ) +localify.Bind( "bg", prefix.."help_nocollide_all", "Натрупваните предмети няма да се сблъскват с всичко освен играчи, NPC, превозни средства, и света." ) +localify.Bind( "bg", prefix.."help_stayinworld", "Предотвратява натрупваните обекти да бъдат създавани извън света." ) +-- Warnings +localify.Bind( "bg", prefix.."warning_max_per_player", "Преди всичко за режими на Ролева игра. За режим Пясъчник, това трябва генерално да бъде неограничено (-1)." ) +localify.Bind( "bg", prefix.."warning_max_offsetx", "Преди всичко за режими на Ролева игра. Не модифицирайте ако не знаете какво правите." ) +localify.Bind( "bg", prefix.."warning_max_offsety", "Преди всичко за режими на Ролева игра. Не модифицирайте ако не знаете какво правите." ) +localify.Bind( "bg", prefix.."warning_max_offsetz", "Преди всичко за режими на Ролева игра. Не модифицирайте ако не знаете какво правите." ) +localify.Bind( "bg", prefix.."warning_freeze", "За режим на Ролева игра." ) +localify.Bind( "bg", prefix.."warning_weld", "За режим на Ролева игра." ) +localify.Bind( "bg", prefix.."warning_nocollide", "За режим на Ролева игра." ) +localify.Bind( "bg", prefix.."warning_nocollide_all", "НЕ ПРОМЕНЯЙТЕ ДОКАТО СЪРВЪРА РАБОТИ. Това използва кука /GM.ShouldCollide/ и е експериментално. Работи и с гаранция ще спре крашовете, но се нуждае от още тестване." ) + +--[[-------------------------------------------------------------------------- +-- French Localization +--------------------------------------------------------------------------]]-- + +-- Paramètres de l'outil +localify.Bind( "fr", prefix.."name", "Stacker - Amélioré" ) +localify.Bind( "fr", prefix.."desc", "Empilez facilement les props dupliqués dans n'importe quelle direction" ) +localify.Bind( "fr", prefix.."left", "Créer une pile" ) +localify.Bind( "fr", prefix.."shift_left", "Augmenter la taille des piles" ) +localify.Bind( "fr", prefix.."right", "Créer un seul prop" ) +localify.Bind( "fr", prefix.."shift_right", "Diminuer la taille de la pile" ) +localify.Bind( "en", prefix.."reload", "Changer la direction de la pile" ) +localify.Bind( "fr", "Undone_stacker_improved", "Annuler le(s) prop(s) empilé(s)" ) +-- Erreurs +localify.Bind( "fr", prefix.."error_blocked_by_server", "Le serveur vous a empêché de modifier cette variable de console" ) +localify.Bind( "fr", prefix.."error_not_admin", "Vous devez être dans le groupe d'utilisateurs 'admin' pour modifier cette variable de console" ) +localify.Bind( "fr", prefix.."error_invalid_argument", "Vous devez entrer une valeur numérique valide" ) +localify.Bind( "fr", prefix.."error_max_per_stack", "Les props maximum qui peuvent être empilés à la fois sont limités à " ) +localify.Bind( "fr", prefix.."error_too_quick", "Vous utilisez l'empileur trop rapidement" ) +localify.Bind( "fr", prefix.."error_max_per_player", "Limite de l'empileur atteinte" ) +localify.Bind( "fr", prefix.."error_not_in_world", "Les props empilés doivent être engendrés dans le monde" ) +localify.Bind( "fr", prefix.."error_max_constraints", "Échec de la création de la contrainte, très probablement à court d'emplacements d'entité" ) +-- Etiquettes +localify.Bind( "fr", prefix.."label_presets", "Préréglages de l'empileur " ) +localify.Bind( "fr", prefix.."label_relative", "Pile relatif au: " ) +localify.Bind( "fr", prefix.."label_direction", "Direction: " ) +localify.Bind( "fr", prefix.."label_count", "Taille" ) +localify.Bind( "fr", prefix.."label_reset_offsets", "Effacer les décalages" ) +localify.Bind( "fr", prefix.."label_reset_angles", "Effacer les angles" ) +localify.Bind( "fr", prefix.."label_show_settings", "Afficher les paramètres" ) +localify.Bind( "fr", prefix.."label_hide_settings", "Masquer les paramètres" ) +localify.Bind( "fr", prefix.."label_x", "X (-derrière, +devant)" ) +localify.Bind( "fr", prefix.."label_y", "Y (-gauche, +droite)" ) +localify.Bind( "fr", prefix.."label_z", "Z (-bas, +haut)" ) +localify.Bind( "fr", prefix.."label_pitch", "Pitch (-bas, +haut)" ) +localify.Bind( "fr", prefix.."label_yaw", "Yaw (-gauche, +droite)" ) +localify.Bind( "fr", prefix.."label_roll", "Roll (-gauche, +droite)" ) +localify.Bind( "fr", prefix.."label_language", "Langue: " ) +localify.Bind( "fr", prefix.."label_credits", "" ) +localify.Bind( "fr", prefix.."label_max_per_stack", "Taille" ) +localify.Bind( "fr", prefix.."label_max_per_player", "Props par joueur(s)" ) +localify.Bind( "fr", prefix.."label_max_offsetx", "Décalage X maximal" ) +localify.Bind( "fr", prefix.."label_max_offsety", "Décalage Y maximal" ) +localify.Bind( "fr", prefix.."label_max_offsetz", "Décalage Z maximal" ) +localify.Bind( "fr", prefix.."label_delay", "Délai" ) +localify.Bind( "fr", prefix.."label_opacity", "Opacité" ) +-- Checkboxes +localify.Bind( "fr", prefix.."checkbox_freeze", "Geler les props empilés" ) +localify.Bind( "fr", prefix.."checkbox_weld", "Souder les props empilés" ) +localify.Bind( "fr", prefix.."checkbox_nocollide", "No-Collide les props empilés avec les autres" ) +localify.Bind( "fr", prefix.."checkbox_nocollide_all", "No-Collide les props empilés avec TOUT" ) +localify.Bind( "fr", prefix.."checkbox_use_shift_key", "Changer la taille d'empilement avec SHIFT + clic gauche/droite" ) +localify.Bind( "fr", prefix.."checkbox_relative", "Pile par rapport à la nouvelle rotation" ) +localify.Bind( "fr", prefix.."checkbox_material", "Appliquer un matériaux" ) +localify.Bind( "fr", prefix.."checkbox_color", "Appliquer une couleur" ) +localify.Bind( "fr", prefix.."checkbox_physprop", "Appliquer une propriété physique" ) +localify.Bind( "fr", prefix.."checkbox_ghost", "Traverser tout les props dans l'empilement" ) +localify.Bind( "fr", prefix.."checkbox_halo", "Ajouter des halos aux props fantômes" ) +localify.Bind( "fr", prefix.."checkbox_halo_color", "Couleur du halo" ) +localify.Bind( "fr", prefix.."checkbox_axis", "Dessiner l'axe XYZ" ) +localify.Bind( "fr", prefix.."checkbox_axis_labels", "Dessiner des étiquettes d'axe XYZ" ) +localify.Bind( "fr", prefix.."checkbox_axis_angles", "Dessiner des angles d'axe XYZ" ) +localify.Bind( "fr", prefix.."checkbox_stayinworld", "Rester dans le monde" ) +-- Comboboxes +localify.Bind( "fr", prefix.."combobox_world", "Monde" ) +localify.Bind( "fr", prefix.."combobox_prop", "Prop" ) +localify.Bind( "fr", prefix.."combobox_direction_up", "Haut" ) +localify.Bind( "fr", prefix.."combobox_direction_down", "Bas" ) +localify.Bind( "fr", prefix.."combobox_direction_front", "Devant" ) +localify.Bind( "fr", prefix.."combobox_direction_back", "Derrière" ) +localify.Bind( "fr", prefix.."combobox_direction_right", "Droite" ) +localify.Bind( "fr", prefix.."combobox_direction_left", "Gauche" ) +localify.Bind( "fr", prefix.."combobox_default", "Défaut" ) +localify.Bind( "fr", prefix.."combobox_sandbox", "Sandbox" ) +localify.Bind( "fr", prefix.."combobox_darkrp", "DarkRP" ) +localify.Bind( "fr", prefix.."combobox_singleplayer", "Solo" ) +-- HUD +localify.Bind( "fr", prefix.."hud_front", "Devant" ) +localify.Bind( "fr", prefix.."hud_right", "Droite" ) +localify.Bind( "fr", prefix.."hud_up", "Haut" ) +-- Aide +localify.Bind( "fr", prefix.."help_max_per_player", "Les props empilés sont limités à chaque joueur." ) +localify.Bind( "fr", prefix.."help_max_per_stack", "Maximum de props par pile (clic gauche)." ) +localify.Bind( "fr", prefix.."help_delay", "Délai (en secondes) entre chaque utilisation de l'Empileur (clic gauche / droit)" ) +localify.Bind( "fr", prefix.."help_max_offsetx", "Distance maximale entre les props empilés (axe X)." ) +localify.Bind( "fr", prefix.."help_max_offsety", "Distance maximale entre les props empilés (axe Y)." ) +localify.Bind( "fr", prefix.."help_max_offsetz", "Distance maximale entre les props empilés (axe Z)." ) +localify.Bind( "fr", prefix.."help_freeze", "Les props empilés sont congelés lorsqu'ils sont spawn." ) +localify.Bind( "fr", prefix.."help_weld", "Les props empilés sont soudés les uns aux autres lors du spawn." ) +localify.Bind( "fr", prefix.."help_nocollide", "Les props empilés ne vont pas entrer en collision avec d'autres props empilés." ) +localify.Bind( "fr", prefix.."help_nocollide_all", "Les props empilés n'entreront en collision avec rien d'autre que les joueurs, les PNJ, les véhicules et le monde." ) +localify.Bind( "fr", prefix.."help_stayinworld", "Empêche la création de props empilés à l'extérieur de la carte." ) +-- Attentions +localify.Bind( "fr", prefix.."warning_max_per_player", "Principalement pour les modes de jeu Roleplay. Pour les modes de jeu Sandbox, cela devrait généralement être illimité (-1)." ) +localify.Bind( "fr", prefix.."warning_max_offsetx", "Principalement pour les modes de jeu Roleplay. Ne modifiez pas à moins de savoir ce que vous faites." ) +localify.Bind( "fr", prefix.."warning_max_offsety", "Principalement pour les modes de jeu Roleplay. Ne modifiez pas à moins de savoir ce que vous faites." ) +localify.Bind( "fr", prefix.."warning_max_offsetz", "Principalement pour les modes de jeu Roleplay. Ne modifiez pas à moins de savoir ce que vous faites." ) +localify.Bind( "fr", prefix.."warning_freeze", "Pour DarkRP." ) +localify.Bind( "fr", prefix.."warning_weld", "Pour DarkRP." ) +localify.Bind( "fr", prefix.."warning_nocollide", "Pour DarkRP." ) +localify.Bind( "fr", prefix.."warning_nocollide_all", "NE PAS CHANGER PENDANT QUE LE SERVEUR FONCTIONNE. Cela utilise le crochet GM.ShouldCollide et est expérimental. Cela fonctionne et est garanti pour arrêter les plantages de Stacker, mais a besoin de plus de tests." ) + +--[[-------------------------------------------------------------------------- +-- Polish Localization +--------------------------------------------------------------------------]]-- + +-- Tool Settings +localify.Bind( "pl", prefix.."name", "Stacker - Ulepszony" ) +localify.Bind( "pl", prefix.."desc", "Łatwo kopiuj obiekty w dowolnym kierunku" ) +localify.Bind( "pl", prefix.."left", "Utwórz kopię" ) +localify.Bind( "pl", prefix.."shift_left", "Zwiększ ilość kopii" ) +localify.Bind( "pl", prefix.."right", "Utwórz pojedyńczy obiekt" ) +localify.Bind( "pl", prefix.."shift_right", "Zmniejsz ilość kopii" ) +localify.Bind( "pl", prefix.."reload", "Zmień kierunek kopiowania" ) +localify.Bind( "pl", "Undone_stacker_improved", "Cofnięto skopiowany obiekt" ) +-- Errors +localify.Bind( "pl", prefix.."error_blocked_by_server", "Serwer zablokował twoją możliwość zmieniania wartości serwerowych" ) +localify.Bind( "pl", prefix.."error_not_admin", "Musiz być w grupie 'admin' by zmienić tę wartość konsolową" ) +localify.Bind( "pl", prefix.."error_invalid_argument", "Musisz wprowadzić poprawną wartość liczbową" ) +localify.Bind( "pl", prefix.."error_max_per_stack", "Maksymalna ilość kopii jest ograniczona do " ) +localify.Bind( "pl", prefix.."error_too_quick", "Używasz Stackera za szybko" ) +localify.Bind( "pl", prefix.."error_max_per_player", "Osiągnięto limir obiektów dla Stackera" ) +localify.Bind( "pl", prefix.."error_not_in_world", "Skopiowane obiekty nie mogą wychodzić poza mapę" ) +localify.Bind( "pl", prefix.."error_max_constraints", "Nie udało się utworzyć łaczenia, najprawdopodobniej brakuje miejsc na obiekty" ) +-- Labels +localify.Bind( "pl", prefix.."label_presets", "Ustawienia zapisane: " ) +localify.Bind( "pl", prefix.."label_relative", "Kopiowanie względem: " ) +localify.Bind( "pl", prefix.."label_direction", "Kierunek kopiowania: " ) +localify.Bind( "pl", prefix.."label_count", "Ilość kopii" ) +localify.Bind( "pl", prefix.."label_reset_offsets", "Zresetuj przesunięcia" ) +localify.Bind( "pl", prefix.."label_reset_angles", "Zresetuj kąty" ) +localify.Bind( "pl", prefix.."label_show_settings", "Pokaż ustawienia" ) +localify.Bind( "pl", prefix.."label_hide_settings", "Ukryj ustawienia" ) +localify.Bind( "pl", prefix.."label_x", "X (-tył, +przód)" ) +localify.Bind( "pl", prefix.."label_y", "Y (-lewo, +prawo)" ) +localify.Bind( "pl", prefix.."label_z", "Z (-dół, +góra)" ) +localify.Bind( "pl", prefix.."label_pitch", "Pochylenie (-dół, +góra)" ) +localify.Bind( "pl", prefix.."label_yaw", "Odchylenie (-lewo, +prawo)" ) +localify.Bind( "pl", prefix.."label_roll", "Przechylenie (-lewo, +prawo)" ) +localify.Bind( "pl", prefix.."label_language", "Język: " ) +localify.Bind( "pl", prefix.."label_credits", "" ) +localify.Bind( "pl", prefix.."label_max_per_stack", "Ilość kopii" ) +localify.Bind( "pl", prefix.."label_max_per_player", "Ilość obiektów na gracza" ) +localify.Bind( "pl", prefix.."label_max_offsetx", "Max przesunięcie X" ) +localify.Bind( "pl", prefix.."label_max_offsety", "Max przesunięcie Y" ) +localify.Bind( "pl", prefix.."label_max_offsetz", "Max przesunięcie Z" ) +localify.Bind( "pl", prefix.."label_delay", "Opóźnienie" ) +localify.Bind( "pl", prefix.."label_opacity", "Przezroczystość" ) +-- Checkboxes +localify.Bind( "pl", prefix.."checkbox_freeze", "Zamrażaj skopiowane obiekty" ) +localify.Bind( "pl", prefix.."checkbox_weld", "Spawaj skopiowane obiekty" ) +localify.Bind( "pl", prefix.."checkbox_nocollide", "Wyłącz kolizję pomiędzy skopiowanymi obiektami" ) +localify.Bind( "pl", prefix.."checkbox_nocollide_all", "Wyłącz kolizję skopiowanych obiektamów ze WSZYSTKIM" ) +localify.Bind( "pl", prefix.."checkbox_use_shift_key", "Zmień ilość kopii za pomocą SHIFT + lewy/prawy" ) +localify.Bind( "pl", prefix.."checkbox_relative", "Kopiuj względem nowego obrotu" ) +localify.Bind( "pl", prefix.."checkbox_material", "Zastosuj materiał" ) +localify.Bind( "pl", prefix.."checkbox_color", "Zastosuj kolor" ) +localify.Bind( "pl", prefix.."checkbox_physprop", "Zastosuj właściwości fizyczne" ) +localify.Bind( "pl", prefix.."checkbox_ghost", "Dodaj duchy obiektów wszyschich kopii" ) +localify.Bind( "pl", prefix.."checkbox_halo", "Dodaj poświatę do duchów obiektów" ) +localify.Bind( "pl", prefix.."checkbox_halo_color", "Kolor poświaty" ) +localify.Bind( "pl", prefix.."checkbox_axis", "Rysuj oś XYZ" ) +localify.Bind( "pl", prefix.."checkbox_axis_labels", "Rysuj opis osi XYZ" ) +localify.Bind( "pl", prefix.."checkbox_axis_angles", "Rysuj kąty osi XYZ" ) +localify.Bind( "pl", prefix.."checkbox_stayinworld", "Zostań w środku mapy" ) +-- Comboboxes +localify.Bind( "pl", prefix.."combobox_world", "świat" ) +localify.Bind( "pl", prefix.."combobox_prop", "Obiekt" ) +localify.Bind( "pl", prefix.."combobox_direction_up", "Góra" ) +localify.Bind( "pl", prefix.."combobox_direction_down", "Dół" ) +localify.Bind( "pl", prefix.."combobox_direction_front", "Przód" ) +localify.Bind( "pl", prefix.."combobox_direction_back", "Tył" ) +localify.Bind( "pl", prefix.."combobox_direction_right", "Prawo" ) +localify.Bind( "pl", prefix.."combobox_direction_left", "Lewo" ) +localify.Bind( "pl", prefix.."combobox_default", "Domyślne" ) +localify.Bind( "pl", prefix.."combobox_sandbox", "Sandbox" ) +localify.Bind( "pl", prefix.."combobox_darkrp", "DarkRP" ) +localify.Bind( "pl", prefix.."combobox_singleplayer", "Singleplayer" ) +-- HUD +localify.Bind( "pl", prefix.."hud_front", "Przód" ) +localify.Bind( "pl", prefix.."hud_right", "Prawo" ) +localify.Bind( "pl", prefix.."hud_up", "Góra" ) +-- Help +localify.Bind( "pl", prefix.."help_max_per_player", "Maksymalna ilość skopiowanych obiektó na gracza to." ) +localify.Bind( "pl", prefix.."help_max_per_stack", "Maksymalna ilość obiektów na kopię (lewy przycisk myszki)." ) +localify.Bind( "pl", prefix.."help_delay", "Opóźnienie (w sekundach) pomiędzy każdym użyciem Stackera (lewy/prawy przycisk)" ) +localify.Bind( "pl", prefix.."help_max_offsetx", "Maksymalny dystans pomiędzy skopiowanymi obiektami (oś X)." ) +localify.Bind( "pl", prefix.."help_max_offsety", "Maksymalny dystans pomiędzy skopiowanymi obiektami (oś Y)." ) +localify.Bind( "pl", prefix.."help_max_offsetz", "Maksymalny dystans pomiędzy skopiowanymi obiektami (oś Z)." ) +localify.Bind( "pl", prefix.."help_freeze", "Skopiowane obiekty są zamrożone." ) +localify.Bind( "pl", prefix.."help_weld", "Skopiowane obiekty są zespawane do siebie." ) +localify.Bind( "pl", prefix.."help_nocollide", "Skopiowane obiekty nie będą kolidowały z innymi skopiowanymi obiektami." ) +localify.Bind( "pl", prefix.."help_nocollide_all", "Skopiowane obiekty nie będą kolidowały ze wszystkim oprócz graczy, NPCs, pojazdów, i mapy." ) +localify.Bind( "pl", prefix.."help_stayinworld", "Nie pozwalaj na tworzenie kopii poza mapą." ) +-- Warnings +localify.Bind( "pl", prefix.."warning_max_per_player", "Głównie dla trybu Roleplay. Dla trybów Sandbox, zazwyczaj powinno być nieograniczone (-1)." ) +localify.Bind( "pl", prefix.."warning_max_offsetx", "Głownie dla tryby Roleplay. Nie zmieniaj, jeśli nie wiesz co robisz." ) +localify.Bind( "pl", prefix.."warning_max_offsety", "Głownie dla tryby Roleplay. Nie zmieniaj, jeśli nie wiesz co robisz." ) +localify.Bind( "pl", prefix.."warning_max_offsetz", "Głownie dla tryby Roleplay. Nie zmieniaj, jeśli nie wiesz co robisz." ) +localify.Bind( "pl", prefix.."warning_freeze", "Dla DarkRP." ) +localify.Bind( "pl", prefix.."warning_weld", "Dla DarkRP." ) +localify.Bind( "pl", prefix.."warning_nocollide", "Dla DarkRP." ) +localify.Bind( "pl", prefix.."warning_nocollide_all", "NIE ZMIENIAJ JEŚLI SERWER JEST WŁĄCZONY. To używa GM.ShouldCollide i jest eksperymentalne. Działa i zapobiega awarii serwera spowodowanymi Stackerem, ale wymaga dalszych testów." ) + + +--[[-------------------------------------------------------------------------- +-- Localization +--------------------------------------------------------------------------]]-- +--[[ +-- Tool Settings +localify.Bind( "", prefix.."name", "" ) +localify.Bind( "", prefix.."desc", "" ) +localify.Bind( "", prefix.."left", "" ) +localify.Bind( "", prefix.."shift_left", "" ) +localify.Bind( "", prefix.."right", "" ) +localify.Bind( "", prefix.."shift_right", "" ) +localify.Bind( "", prefix.."reload", "" ) +localify.Bind( "", "Undone_stacker_improved", "" ) +-- Errors +localify.Bind( "", prefix.."error_blocked_by_server", "" ) +localify.Bind( "", prefix.."error_not_admin", "" ) +localify.Bind( "", prefix.."error_invalid_argument", "" ) +localify.Bind( "", prefix.."error_max_count", "" ) +localify.Bind( "", prefix.."error_too_quick", "" ) +localify.Bind( "", prefix.."error_max_total", "" ) +localify.Bind( "", prefix.."error_not_in_world", "" ) +localify.Bind( "", prefix.."error_max_constraints", "" ) +-- Labels +localify.Bind( "", prefix.."label_presets", "" ) +localify.Bind( "", prefix.."label_relative", "" ) +localify.Bind( "", prefix.."label_direction", "" ) +localify.Bind( "", prefix.."label_count", "" ) +localify.Bind( "", prefix.."label_reset_offsets", "" ) +localify.Bind( "", prefix.."label_reset_angles", "" ) +localify.Bind( "", prefix.."label_show_settings", "" ) +localify.Bind( "", prefix.."label_hide_settings", "" ) +localify.Bind( "", prefix.."label_x", "" ) +localify.Bind( "", prefix.."label_y", "" ) +localify.Bind( "", prefix.."label_z", "" ) +localify.Bind( "", prefix.."label_pitch", "" ) +localify.Bind( "", prefix.."label_yaw", "" ) +localify.Bind( "", prefix.."label_roll", "" ) +localify.Bind( "", prefix.."label_language", "" ) +localify.Bind( "", prefix.."label_credits", "" ) +localify.Bind( "", prefix.."label_max_per_stack", "" ) +localify.Bind( "", prefix.."label_max_per_player", "" ) +localify.Bind( "", prefix.."label_max_offsetx", "" ) +localify.Bind( "", prefix.."label_max_offsety", "" ) +localify.Bind( "", prefix.."label_max_offsetz", "" ) +localify.Bind( "", prefix.."label_delay", "" ) +localify.Bind( "", prefix.."label_opacity", "" ) +-- Checkboxes +localify.Bind( "", prefix.."checkbox_freeze", "" ) +localify.Bind( "", prefix.."checkbox_weld", "" ) +localify.Bind( "", prefix.."checkbox_nocollide", "" ) +localify.Bind( "", prefix.."checkbox_nocollide_all", "" ) +localify.Bind( "", prefix.."checkbox_use_shift_key", "" ) +localify.Bind( "", prefix.."checkbox_relative", "" ) +localify.Bind( "", prefix.."checkbox_material", "" ) +localify.Bind( "", prefix.."checkbox_color", "" ) +localify.Bind( "", prefix.."checkbox_physprop", "" ) +localify.Bind( "", prefix.."checkbox_ghost", "" ) +localify.Bind( "", prefix.."checkbox_halo", "" ) +localify.Bind( "", prefix.."checkbox_halo_color", "" ) +localify.Bind( "", prefix.."checkbox_axis", "" ) +localify.Bind( "", prefix.."checkbox_axis_labels", "" ) +localify.Bind( "", prefix.."checkbox_axis_angles", "" ) +localify.Bind( "", prefix.."checkbox_stayinworld", "" ) +-- Comboboxes +localify.Bind( "", prefix.."combobox_world", "" ) +localify.Bind( "", prefix.."combobox_prop", "" ) +localify.Bind( "", prefix.."combobox_direction_up", "" ) +localify.Bind( "", prefix.."combobox_direction_down", "" ) +localify.Bind( "", prefix.."combobox_direction_front", "" ) +localify.Bind( "", prefix.."combobox_direction_back", "" ) +localify.Bind( "", prefix.."combobox_direction_right", "" ) +localify.Bind( "", prefix.."combobox_direction_left", "" ) +localify.Bind( "", prefix.."combobox_default", "" ) +localify.Bind( "", prefix.."combobox_sandbox", "" ) +localify.Bind( "", prefix.."combobox_darkrp", "" ) +localify.Bind( "", prefix.."combobox_singleplayer", "" ) +-- HUD +localify.Bind( "", prefix.."hud_front", "" ) +localify.Bind( "", prefix.."hud_right", "" ) +localify.Bind( "", prefix.."hud_up", "" ) +-- Help +localify.Bind( "", prefix.."help_max_per_player", "" ) +localify.Bind( "", prefix.."help_max_per_stack", "" ) +localify.Bind( "", prefix.."help_delay", "" ) +localify.Bind( "", prefix.."help_max_offsetx", "" ) +localify.Bind( "", prefix.."help_max_offsety", "" ) +localify.Bind( "", prefix.."help_max_offsetz", "" ) +localify.Bind( "", prefix.."help_freeze", "" ) +localify.Bind( "", prefix.."help_weld", "" ) +localify.Bind( "", prefix.."help_nocollide", "" ) +localify.Bind( "", prefix.."help_nocollide_all", "" ) +localify.Bind( "", prefix.."help_stayinworld", "" ) +-- Warnings +localify.Bind( "", prefix.."warning_max_per_player", "" ) +localify.Bind( "", prefix.."warning_max_offsetx", "" ) +localify.Bind( "", prefix.."warning_max_offsety", "" ) +localify.Bind( "", prefix.."warning_max_offsetz", "" ) +localify.Bind( "", prefix.."warning_freeze", "" ) +localify.Bind( "", prefix.."warning_weld", "" ) +localify.Bind( "", prefix.."warning_nocollide", "" ) +localify.Bind( "", prefix.."warning_nocollide_all", "" ) +]] + +-- Hopefully will add more with community/crowdsource support. + +-- If you are multi/bilingual, please consider helping me translate the phrases above into other languages. +-- Create an issue on the Github page ( https://github.com/Mista-Tea/improved-weight ) or +-- add me on Steam ( http://steamcommunity.com/profiles/76561198015280374 ). Thanks! diff --git a/garrysmod/addons/gmod-tools/lua/matproxy/matproxycolors.lua b/garrysmod/addons/gmod-tools/lua/matproxy/matproxycolors.lua new file mode 100644 index 0000000..9a61136 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/matproxy/matproxycolors.lua @@ -0,0 +1,120 @@ +matproxy.Add( { + name = "ColorSlot1", + --ran when a material with the proxy is spawned + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + --ran every frame (not sure if it is the fastest way :<) + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot1) then --only run once + ent.ColorSlot1Name = self.Name + ent.ColorSlot1 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot1) + end +} ) + +matproxy.Add( { + name = "ColorSlot2", + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot2) then + ent.ColorSlot2Name = self.Name + ent.ColorSlot2 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot2) + end +} ) + +matproxy.Add( { + name = "ColorSlot3", + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot3) then + ent.ColorSlot3Name = self.Name + ent.ColorSlot3 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot3) + end +} ) + +matproxy.Add( { + name = "ColorSlot4", + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot4) then + ent.ColorSlot4Name = self.Name + ent.ColorSlot4 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot4) + end +} ) + +matproxy.Add( { + name = "ColorSlot5", + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot5) then + ent.ColorSlot5Name = self.Name + ent.ColorSlot5 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot5) + end +} ) + +matproxy.Add( { + name = "ColorSlot6", + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot6) then + ent.ColorSlot6Name = self.Name + ent.ColorSlot6 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot6) + end +} ) + +matproxy.Add( { + name = "ColorSlot7", + init = function( self, mat, values ) + self.Name = values.name + self.Color = values.resultvar + self.FColor = values.fcolor and util.StringToType(values.fcolor, "Vector") + end, + bind = function( self, mat, ent ) + if (!IsValid( ent )) then return end + if (!ent.ColorSlot7) then + ent.ColorSlot7Name = self.Name + ent.ColorSlot7 = self.FColor or Vector(1,1,1) + end + mat:SetVector(self.Color,ent.ColorSlot7) + end +} ) diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_annotated.lua b/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_annotated.lua new file mode 100644 index 0000000..f146716 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_annotated.lua @@ -0,0 +1,130 @@ +"EffectList" +{ + + + + //At the beginning of the file is an Info table that gives the tool some information it needs - + //what to name the category, which effects should attach to two points, which effects should show the color selector, and that sort of thing. + + "Info" + { + //This is the name of the category that all of your particle lists will get dropped into + "CategoryName" "Example Category" + + "EffectOptions" + { + //List of effects that should be attached to two separate points, like beams or bullet tracers, by moving the first two controlpoints (0 and 1 in the particle editor) + "Beams" + " + electricity_yellow + electricity_white + custombulleteffect01 + custombulleteffect01a + custombulleteffect02 + custombulleteffect02a + laserbeam_colorable01 + laserbeam_colorable01a + laserbeam_colorable01b + !UTILEFFECT!internalname + " + + //Not every effect is colorable - these two lists are for effects that you've designed to be colorable by linking the particle color to the location of a controlpoint + //(util effects aren't colorable, don't add them to these lists) + + //Colorable effects that use a color value out of 1 - i.e. light red should move the color controlpoint(s) to (1 0.5 0.5). + "Color1" + " + flame_colorable + " + + //Colorable effects that use a color value out of 255 - i.e. light red should move the color controlpoint(s) to (255 127 127). + "Color255" + " + laserbeam_colorable01 + laserbeam_colorable01a + laserbeam_colorable01b + " + + //List of effects that should be available in the Tracer tool's browser. Generally, any 'beam' effect will also work as a tracer effect, + //but you should probably avoid adding big, flashy, or permanent effects that you don't want players to be spamming a whole bunch of. + "Tracers" + " + electricity_yellow + electricity_white + custombulleteffect01 + custombulleteffect01a + custombulleteffect02 + custombulleteffect02a + laserbeam_colorable01 + laserbeam_colorable01a + laserbeam_colorable01b + !UTILEFFECT!internalname + " + } + + //This next table is used to add a separate "Scripted Effects" list, used for Lua scripted effects (NOT .pcf effects) that should be played with the util.Effect() function. + //If you only want to add .pcf effects, you can safely remove this table. + //Even though these are different from .pcf effects, they should still be added to the Beams or Tracer list if you want the tool to use that feature. + "UtilEffects" + { + "Display Name" "!UTILEFFECT!internalname" //remember to add the !UTILEFFECT! flag to the beginning + } + } + + + +//After the Info table are your particle lists - the format is just a string with the name of your .pcf file (this'll get game.AddParticles()'d by the addon), +//and then another string, containing all the effect names in the list, with one name per line. +//You don't need to indent these if you don't want to - keeping it all like this makes it a lot easier to copy-paste in big lists of particle names. + +//NOTE: Due to a limitation with GMod's keyvalue-reading function, the game stops reading these list strings after 4095 characters. To get around this, you can add a continuation by adding +//another list with the the same name, followed by _cont (and then anything else after that, so you can have multiple continuations for the same list), and the addon will stitch them +//together automatically when reading the file. For example, if "particle_file.pcf" has gotten too big, you can continue it in another list called "particle_file.pcf_cont1". +//This works for the list strings inside of the EffectOptions table, too - just put the continuations inside of the EffectOptions table as well. + +//Remember to check the console when you're testing out your particle list - it'll tell you if there were any problems reading the file, and if any of your lists need to be split up. + +"example_particle_file.pcf" +" +effect_1 +effect_two +effect_the_third_one +another_dang_effect +etc +effects with spaces in their names work too +//you can comment effects out by putting two slashes in front of them +yet_another_effect //you can also add comments after them, it'll ignore everything on that line after the two slashes +" + + + +"whoa_more_particles.pcf" +" +flame01 +flame02 +flame02_a +flame_colorable +bigexplosion01 +electricity_yellow +electricity_white +custombulleteffect01 +custombulleteffect01a +custombulleteffect02 +custombulleteffect02a +laserbeam_colorable01 +laserbeam_colorable01a +laserbeam_colorable01b +" + + + +"you_get_the_idea.pcf" +" +rain_ambient_01 +rain_ambient_02 +im_gonna_stop_making_up_effect_names_now +" + + + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_blank.lua b/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_blank.lua new file mode 100644 index 0000000..fe5090a --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_blank.lua @@ -0,0 +1,79 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" " " + + "EffectOptions" + { + "Beams" + " + " + + "Color1" + " + " + + "Color255" + " + " + + "Tracers" + " + " + } + + "UtilEffects" + { + " " "!UTILEFFECT!" + } + } + + + +".pcf" +" +" + + + +".pcf" +" +" + + + +".pcf" +" +" + + + +".pcf" +" +" + + + +".pcf" +" +" + + + +".pcf" +" +" + + + +".pcf" +" +" + + + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_blanker.lua b/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_blanker.lua new file mode 100644 index 0000000..94df3a4 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/addons/_example_blanker.lua @@ -0,0 +1,79 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "" + + "EffectOptions" + { + "Beams" + " + " + + "Color1" + " + " + + "Color255" + " + " + + "Tracers" + " + " + } + + "UtilEffects" + { + "" "" + } + } + + + +"" +" +" + + + +"" +" +" + + + +"" +" +" + + + +"" +" +" + + + +"" +" +" + + + +"" +" +" + + + +"" +" +" + + + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/cstrike.lua b/garrysmod/addons/gmod-tools/lua/particlelists/cstrike.lua new file mode 100644 index 0000000..022b7b8 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/cstrike.lua @@ -0,0 +1,301 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Counter-Strike Source" + + "UtilEffects" + { + "Muzzle flash, counter-strike" "!UTILEFFECT!CS_MuzzleFlash" + "Muzzle flash, counter-strike X" "!UTILEFFECT!CS_MuzzleFlash_X" + } + } + + + +"//achievement.pcf" //conflicts with tf2 effects +" +achieved +mini_firework_flare +mini_fireworks +" + + + +"//blood_impact.pcf" //copy of stock source particles +" +blood_advisor_pierce_spray +blood_advisor_pierce_spray_b +blood_advisor_pierce_spray_c +blood_advisor_puncture +blood_advisor_puncture_withdraw +blood_advisor_shrapnel_impact +blood_advisor_shrapnel_spray_1 +blood_advisor_shrapnel_spray_2 +blood_advisor_shrapnel_spurt_1 +blood_advisor_shrapnel_spurt_2 +blood_antlionguard_injured_heavy +blood_antlionguard_injured_heavy_ +blood_antlionguard_injured_heavy_tiny +blood_antlionguard_injured_light +blood_antlionguard_injured_light_tiny +blood_drip_synth_01 +blood_drip_synth_01b +blood_drip_synth_01c +blood_impact_antlion_01 +blood_impact_antlion_worker_01 +blood_impact_green_01 +blood_impact_green_01_chunk +blood_impact_green_01_droplets +blood_impact_green_02_chunk +blood_impact_green_02_droplets +blood_impact_green_02_glow +blood_impact_red_01 +blood_impact_red_01_chunk +blood_impact_red_01_droplets +blood_impact_red_01_goop +blood_impact_red_01_mist +blood_impact_red_01_smalldroplets +blood_impact_synth_01 +blood_impact_synth_01_arc +blood_impact_synth_01_arc2 +blood_impact_synth_01_arc2 +blood_impact_synth_01_arc4 +blood_impact_synth_01_arc_parent +blood_impact_synth_01_arc_parents +blood_impact_synth_01_armor +blood_impact_synth_01_droplets +blood_impact_synth_01_dust +blood_impact_synth_01_short +blood_impact_synth_01_spurt +blood_impact_yellow_01 +blood_impact_zombie_01 +blood_spurt_synth_01 +blood_spurt_synth_01b +blood_zombie_split +blood_zombie_split_spray +blood_zombie_split_spray_tiny +blood_zombie_split_spray_tiny2 +vomit_barnacle +vomit_barnacle_b +" + + + +"//burning_fx.pcf" //copy of stock source particles +" +burning_character +burning_character_b +burning_character_c +burning_character_d +burning_character_e +" + + + +"//error.pcf" //copy of stock source particles +" +error +" + + + +"//fire_01.pcf" //almost an exact copy of the stock particles, has one new effect called bomb_explosion_huge that's just a slightly different explosion_silo +" +bomb_explosion_huge +burning_engine_01 +burning_engine_fire +burning_gib_01 +burning_gib_01_drag +burning_gib_01_follower1 +burning_gib_01_follower2 +burning_gib_01b +burning_vehicle +burning_wood_01 +burning_wood_01b +burning_wood_01c +embers_large_01 +embers_large_02 +embers_medium_01 +embers_medium_03 +embers_small_01 +env_embers_large +env_embers_medium +env_embers_medium_spread +env_embers_small +env_embers_small_spread +env_embers_tiny +env_fire_large +env_fire_large_b +env_fire_large_smoke +env_fire_large_smoke_b +env_fire_medium +env_fire_medium_b +env_fire_medium_smoke +env_fire_medium_spread +env_fire_medium_spread_b +env_fire_random_puff +env_fire_small +env_fire_small_b +env_fire_small_base +env_fire_small_coverage +env_fire_small_coverage_b +env_fire_small_coverage_base +env_fire_small_coverage_base_smoke +env_fire_small_coverage_c +env_fire_small_coverage_smoke +env_fire_small_smoke +env_fire_tiny +env_fire_tiny_b +env_fire_tiny_smoke +explosion_huge +explosion_huge_b +explosion_huge_burning_chunks +explosion_huge_c +explosion_huge_d +explosion_huge_e +explosion_huge_f +explosion_huge_flames +explosion_huge_flames_b +explosion_huge_g +explosion_huge_h +explosion_huge_i +explosion_huge_j +explosion_huge_k +explosion_huge_smoking_chunks +explosion_silo +fire_jet_01 +fire_jet_01_flame +fire_large_01 +fire_large_02 +fire_large_02_filler +fire_large_02_fillerb +fire_large_base +fire_medium_01 +fire_medium_01_glow +fire_medium_02 +fire_medium_02_nosmoke +fire_medium_03 +fire_medium_03_brownsmoke +fire_medium_base +fire_medium_burst +fire_medium_heatwave +fire_small_01 +fire_small_02 +fire_small_03 +fire_small_base +fire_small_flameouts +fire_verysmall_01 +smoke_burning_engine_01 +smoke_exhaust_01 +smoke_exhaust_01a +smoke_exhaust_01b +smoke_gib_01 +smoke_large_01 +smoke_large_01b +smoke_large_02 +smoke_large_02b +smoke_medium_01 +smoke_medium_02 +smoke_medium_02 Version #2 +smoke_medium_02b +smoke_medium_02b Version #2 +smoke_medium_02c +smoke_medium_02d +smoke_small_01 +smoke_small_01b +" + + + +"//fire_medium_01.pcf" //broken textures, overrides some perfectly good, non-broken effects from fire_01 +" +embers_medium_01 +fire_medium_01 +smoke_oily_01 +" + + + +"muzzleflashes.pcf" //behold, the only usable .pcf file in counter-strike source... and it's a bunch of broken, multicolored muzzle flashes. dang. +" +muzzle_autorifles +muzzle_machinegun +muzzle_pistols +muzzle_rifles +muzzle_shotguns +muzzle_smgs +" + + + +"//water_impact.pcf" //copy of stock particles +" +slime_splash_01 +slime_splash_01_droplets +slime_splash_01_reversed +slime_splash_01_surface +slime_splash_02 +slime_splash_03 +water_bubble_ambient_1 +water_bubble_trail_1 +water_foam_01 +water_foam_01b +water_foam_01c +water_foam_01d +water_foam_line_long +water_foam_line_longb +water_foam_line_longc +water_foam_line_longd +water_foam_line_short +water_foam_line_shortb +water_foam_line_shortc +water_foam_line_shortd +water_gunk_1 +water_impact_bubbles_1 +water_impact_bubbles_1b +water_impact_bubbles_1c +water_impact_bubbles_1d +water_splash_01 +water_splash_01_droplets +water_splash_01_refract +water_splash_01_surface1 +water_splash_01_surface2 +water_splash_01_surface3 +water_splash_01_surface4 +water_splash_02 +water_splash_02_animated +water_splash_02_continuous +water_splash_02_droplets +water_splash_02_droplets Version #2 +water_splash_02_froth +water_splash_02_froth2 +water_splash_02_refract +water_splash_02_surface2 +water_splash_02_surface4 +water_splash_02_vertical +water_splash_03 +water_splash_leakypipe_silo +water_splash_leakypipe_silo_froth2 +water_splash_leakypipe_vertical +water_trail_medium +water_trail_medium_b +" + + + + + + +//This is a CUSTOM .pcf file, repackaged because its effect names conflicted with effects from TF2. + +"achievement_cstrike.pcf" +" +cstrike_achieved +cstrike_mini_firework_flare +cstrike_mini_fireworks +" +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/ep1.lua b/garrysmod/addons/gmod-tools/lua/particlelists/ep1.lua new file mode 100644 index 0000000..7350cb8 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/ep1.lua @@ -0,0 +1,22 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Half-Life 2: Episode 1" + } + + + +//This is it. This is the only particle in Episode 1. A nearly invisible energy beam thing. +"ep1_fx.pcf" +" +bridge_vortex +bridge_vortex_b +" + + + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/ep2.lua b/garrysmod/addons/gmod-tools/lua/particlelists/ep2.lua new file mode 100644 index 0000000..e659ad5 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/ep2.lua @@ -0,0 +1,1117 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Half-Life 2: Episode 2" + + "EffectOptions" + { + "Beams" + " + Advisor_Psychic_Attach + Advisor_Psychic_Attach_01 + Advisor_Psychic_Attach_01_finale + Advisor_Psychic_Attach_01b + Advisor_Psychic_Attach_01b_finale + Advisor_Psychic_Attach_02 + Advisor_Psychic_Attach_02b + Advisor_Psychic_Attach_02c + Advisor_Psychic_Attach_02d + Advisor_Psychic_Attach_03 + Advisor_Psychic_Attach_03b + Advisor_Psychic_Attack + Advisor_Psychic_Attack_b + Advisor_Psychic_Attack_c + Advisor_Psychic_Beam + Advisor_Psychic_Beam_02 + Advisor_Psychic_Beam_Sparkles + Advisor_Psychic_Scan_01 + Advisor_Psychic_Scan_01_finale + Advisor_Psychic_Scan_03 + Advisor_Psychic_Scan_Flash + Advisor_Psychic_Scan_Suck + Advisor_Psychic_Shove + Advisor_Psychic_Shove_b + Advisor_Psychic_Shove_c + + choreo_skyflower_01 + choreo_skyflower_01b + choreo_skyflower_01c + choreo_skyflower_02a + choreo_skyflower_02b + + electrical_arc_01 + electrical_arc_01_cp1_beam + electrical_arc_01_cp1_beam2 + electrical_arc_01_cp1_beam3 + electrical_arc_01_cp1_beam4 + electrical_arc_01_cp1_beam5 + electrical_arc_01_cp1_beam6 + st_elmos_fire + + portal_closure_01 + portal_closure_01_core + portal_closure_01b + portal_closure_01c + portal_closure_01d + portal_closure_01e + portal_closure_02 + portal_closure_02b + portal_closure_03 + portal_closure_03b + portal_lightning_01 + portal_lightning_01_06a + portal_lightning_01e + portal_lightning_02 + portal_lightning_02b + portal_lightning_03 + portal_lightning_03b + portal_lightning_backblast + + striderbuster_break_lightning1 + striderbuster_flechette_attached_lightning + + Weapon_Combine_Ion_Cannon + Weapon_Combine_Ion_Cannon_Beam + Weapon_Combine_Ion_Cannon_Black + Weapon_Combine_Ion_Cannon_f + " + + "Tracers" + " + Advisor_Psychic_Attach_01 + Advisor_Psychic_Attach_01_finale + Advisor_Psychic_Attack + Advisor_Psychic_Attack_b + Advisor_Psychic_Attack_c + Advisor_Psychic_Beam + Advisor_Psychic_Beam_02 + Advisor_Psychic_Beam_Sparkles + Advisor_Psychic_Scan_01 + Advisor_Psychic_Scan_01_finale + Advisor_Psychic_Scan_03 + Advisor_Psychic_Scan_Flash + Advisor_Psychic_Scan_Suck + Advisor_Psychic_Shove + Advisor_Psychic_Shove_b + Advisor_Psychic_Shove_c + + choreo_skyflower_01 + choreo_skyflower_01b + choreo_skyflower_01c + choreo_skyflower_02a + choreo_skyflower_02b + + electrical_arc_01 + electrical_arc_01_cp1_beam + electrical_arc_01_cp1_beam2 + electrical_arc_01_cp1_beam3 + electrical_arc_01_cp1_beam4 + electrical_arc_01_cp1_beam5 + electrical_arc_01_cp1_beam6 + st_elmos_fire + + //portal_closure_01 + portal_closure_01_core + portal_closure_01b + portal_closure_01c + portal_closure_01d + portal_closure_01e + //portal_closure_02 + portal_closure_02b + //portal_closure_03 + portal_closure_03b + //portal_lightning_01 + //portal_lightning_01_06a + portal_lightning_01e + //portal_lightning_02 + portal_lightning_02b + //portal_lightning_03 + portal_lightning_03b + //portal_lightning_backblast + + striderbuster_break_lightning1 + striderbuster_flechette_attached_lightning + + Weapon_Combine_Ion_Cannon + Weapon_Combine_Ion_Cannon_Beam + Weapon_Combine_Ion_Cannon_Black + Weapon_Combine_Ion_Cannon_f + " + } + } + + + +"advisor.pcf" +" +advisor_beam1_break +advisor_healthcharger_break +advisor_object_charge +advisor_object_charge_bits +advisor_object_charge_glow +advisor_object_charge_ring +advisor_pillar1_break +advisor_plat_break +" + + + +"advisor_fx.pcf" +" +Advisor_Pod_Explosion_Debris +Advisor_Pod_Explosion_Smoke +Advisor_Pod_Explosion_Smoke_2 +Advisor_Pod_Steam_01 +Advisor_Pod_Steam_01b +Advisor_Pod_Steam_02 +Advisor_Pod_Steam_02b +Advisor_Pod_Steam_Continuous +Advisor_Psychic_Attach +Advisor_Psychic_Attach_01 +Advisor_Psychic_Attach_01_finale +Advisor_Psychic_Attach_01b +Advisor_Psychic_Attach_01b_finale +Advisor_Psychic_Attach_02 +Advisor_Psychic_Attach_02b +Advisor_Psychic_Attach_02c +Advisor_Psychic_Attach_02d +Advisor_Psychic_Attach_03 +Advisor_Psychic_Attach_03b +Advisor_Psychic_Attack +Advisor_Psychic_Attack_b +Advisor_Psychic_Attack_c +Advisor_Psychic_Attack_d +Advisor_Psychic_Attack_e +Advisor_Psychic_Beam +Advisor_Psychic_Beam_02 +Advisor_Psychic_Beam_Sparkles +Advisor_Psychic_Blast +Advisor_Psychic_Near_Beams +Advisor_Psychic_Near_Beams_Backup +Advisor_Psychic_Scan_01 +Advisor_Psychic_Scan_01_finale +Advisor_Psychic_Scan_02 +Advisor_Psychic_Scan_03 +Advisor_Psychic_Scan_BodyWarp +Advisor_Psychic_Scan_BodyWarp Version #2 +Advisor_Psychic_Scan_BodyWarp_finale +Advisor_Psychic_Scan_Flash +Advisor_Psychic_Scan_Glow +Advisor_Psychic_Scan_Green +Advisor_Psychic_Scan_Hot_Cores +Advisor_Psychic_Scan_Hot_Cores Version #2 +Advisor_Psychic_Scan_Hot_Cores_02 +Advisor_Psychic_Scan_Lift_b +Advisor_Psychic_Scan_Suck +Advisor_Psychic_Shield_Idle +Advisor_Psychic_Shield_Idle_Beams +Advisor_Psychic_Shield_Idle_Center +Advisor_Psychic_Shield_Idle_Lower +Advisor_Psychic_Shield_Idle_Middle +Advisor_Psychic_Shield_Idle_Ring +Advisor_Psychic_Shove +Advisor_Psychic_Shove_b +Advisor_Psychic_Shove_c +advisor_roof_debris +advisor_roof_debris_2 +Advisor_Room_Debris +Advisor_Room_Debris_Dust +" + + + +"antlion_gib_01.pcf" +" +antlion_gib_01 +antlion_gib_01_juice +antlion_gib_01_trailsA +antlion_gib_01_trailsb +" + + + +"antlion_gib_02.pcf" +" +antlion_gib_02 +antlion_gib_02_blood +antlion_gib_02_floaters +antlion_gib_02_gas +antlion_gib_02_juice +antlion_gib_02_slime +antlion_gib_02_trailsA +antlion_gib_02_trailsB +" + + + +"antlion_worker.pcf" +" +antlion_spit +antlion_spit_02 +antlion_spit_03 +antlion_spit_05 +antlion_spit_player +antlion_spit_player_splat +antlion_spit_trail +" + + + +"aurora.pcf" +" +aurora_01 +aurora_02 +aurora_02b +" + + + +"aurora_sphere2.pcf" +" +aurora_shockwave +aurora_shockwave_debris +aurora_shockwave_ring +demo_aurora_01 +" + + + +"//blob.pcf" //not compatible, doesn't even work in ep2 itself, seems to be a test of what would later become portal2 fluid particles +" +big bang 1k +big bang 1k lj 1 +big bang 1k lj 2 +big bang 1k lj 3 +big bang 1k lj 4 +big bang 1k lj 5 +big bang 1k lj 6 +big bang 2k +dynamic +dynamic seamless +fluid jet +fluidjet10 +fluidjet11 +fluidjet12 +fluidjet2 +fluidjet3 +fluidjet4 +fluidjet5 +fluidjet6 +fluidjet7 +fluidjet8 +fluidjet9 +funny +ltest +ltest2 +oil slick +oil slick lj +oscillator +static +static seamless +static seamless 2 +static seamless 3 +static seamless 4 +static seamless 5 +static seamless 6 +static seamless 7 +static seamless 8 +" + + + +"//bonfire.pcf" //broken, unused effects, names are really generic and might end up overriding something so let's skip this one +" +bonfire +smoke +" + + + +"building_explosion.pcf" +" +building_explosion +" + + + +"choreo_dog_v_strider.pcf" +" +smoke_dark_plume_1 +smoke_dog_v_strider_dropship +smoke_mesh_01 +smoke_smoulder_01 +strider_brain_geyser_01 +strider_brain_geyser_01b +strider_brain_geyser_01c +strider_brain_open_01 +strider_brain_open_01b +strider_brain_open_01c +strider_braindrip_01 +strider_cannon_impact +strider_cough_01 +strider_cough_01b +strider_cough_02 +strider_goop_01 +strider_goop_01_tubes1 +strider_goop_01_tubes2 +strider_goop_01b +strider_goop_01c +strider_goop_01d +strider_goop_01e +strider_headbeating_01 +strider_headbeating_01b +strider_headbeating_01c +strider_headbeating_02 +strider_headbeating_03 +strider_hit_ground +strider_impale_ground +strider_impale_ground_b +strider_small_spray +strider_tubedrip_01 +strider_tubedrip_01b +strider_wall_smash +water_gate_open +water_gate_open_b +" + + + +"choreo_extract.pcf" +" +larvae_glow +larvae_glow_b +larvae_glow_c +larvae_glow_extract +larvae_glow_extract_b +larvae_glow_extract_c +" + + + +"choreo_gman.pcf" +" +blood_frozen +blood_impact_red_01_chunk_frozen +blood_impact_red_01_droplets_frozen +blood_impact_red_01_goop_frozen +blood_impact_red_01_mist_frozen +blood_impact_red_01_smalldroplets_frozen +extract_vorteat_juice +extract_vorteat_juice_b +extract_vorteat_juice_c +extract_vorteat_juice_d +frozen_steam +muzzleflash_frozen +" + + + +"choreo_launch.pcf" +" +choreo_borealis_snow +Choreo_borealis_snow_2 +choreo_launch_camjet_1 +choreo_launch_camjet_2 +choreo_launch_camjet_3 +choreo_launch_camjet_sparks +choreo_launch_rocket_flame +choreo_launch_rocket_glow +choreo_launch_rocket_glow2 +choreo_launch_rocket_jet +choreo_launch_rocket_skyboxglow +choreo_launch_rocket_skyboxglow2 +choreo_launch_rocket_skyboxsmoke +choreo_launch_rocket_start +choreo_launch_rocket_upsmoke +choreo_launch_rocket_upsmoke_b +choreo_launch_rocket_upsmoke_backup +choreo_launch_rocket_upsmoke_c +choreo_silo_door +choreo_silo_door_b +choreo_silo_door_c +choreo_skyflower_01 +choreo_skyflower_01b +choreo_skyflower_01c +choreo_skyflower_02a +choreo_skyflower_02b +choreo_skyflower_03a +choreo_skyflower_03b +choreo_skyflower_03c +choreo_skyflower_03d +choreo_skyflower_afterbeam +choreo_skyflower_afternexus +choreo_skyflower_afternexus_start +choreo_skyflower_afternexus_start2 +choreo_skyflower_nexus +" + + + +"default.pcf" +" +default +" + + + +"demo_particle_light.pcf" +" +lit_smoke_sphere +lit_smoke_sphere_sub +" + + + +"devtest.pcf" +" +test_beam +test_collision +test_lighting +test_orientation +test_trails +weapon_explosion_grenade +weapon_muzzle_flash_assaultrifle +weapon_muzzle_flash_assaultrifle_glow +weapon_muzzle_flash_assaultrifle_main +weapon_muzzle_flash_assaultrifle_vent +weapon_muzzle_flash_smoke_small2 +weapon_muzzle_smoke +weapon_muzzle_smoke_b +weapon_muzzle_smoke_b Version #2 +weapon_muzzle_smoke_long +weapon_muzzle_smoke_long_b +weapon_shove +" + + + +"door_explosion.pcf" +" +door_explosion_chunks +door_explosion_core +door_explosion_flash +door_explosion_shockwave +door_explosion_smoke +door_pound_core +" + + + +"dust_bombdrop.pcf" +" +dust_bombdrop +" + + + +"dust_rumble.pcf" +" +dust_bridge_crack +dust_bridge_crack_b +dust_bridgefall +dust_bridgefall_2 +dust_bridgefall_2_debris +dust_bridgefall_2_sparks +dust_bridgefall__ +dust_bridgefall_debris +dust_bridgefall_lingering +dust_bridgefall_sparks +Dust_Ceiling_Aligned +Dust_Ceiling_Inn +Dust_Ceiling_Inn_CenterDust +Dust_Ceiling_Inn_Debris +Dust_Ceiling_Inn_Motes +Dust_Ceiling_Rumble_24Diam +Dust_Ceiling_Rumble_24Diam_CenterDust +Dust_Ceiling_Rumble_24Diam_Debris +Dust_Ceiling_Rumble_256Line +Dust_Ceiling_Rumble_256Line_Debris +Dust_Ceiling_Rumble_512Square +dust_motes_lit_cone +explosion_cabin +explosion_cabin_debris +explosion_cabin_sparks +" + + + +"electrical_fx.pcf" +" +electrical_arc_01 +electrical_arc_01_cp0 +electrical_arc_01_cp1 +electrical_arc_01_cp1_beam +electrical_arc_01_cp1_beam2 +electrical_arc_01_cp1_beam3 +electrical_arc_01_cp1_beam4 +electrical_arc_01_cp1_beam5 +electrical_arc_01_cp1_beam6 +electrical_arc_01_cp1b +electrical_arc_01_parent +electrical_arc_01_system +st_elmos_fire +st_elmos_fire_cp0 +st_elmos_fire_cp1 +" + + + +"//explosion.pcf" //conflicts with tf2 effects +" +Explosion_2 +Explosion_2_Chunks +Explosion_2_Embers +Explosion_2_Fire +Explosion_2_FireSmoke +Explosion_2_flash +ExplosionCore +ExplosionEmbers +ExplosionFlash +" + + + +"//fire_ring.pcf" //unused effects with broken textures +" +fire_ring_01 +smoke_oily_01 +" + + + +"//fireflow.pcf" //another broken effect, almost the same as the bonfire one from earlier +" +bonfire +" + + + +"//flamethrowertest.pcf" //doesn't seem to work +" +flame +" + + + +"grenade_fx.pcf" +" +grenade_explosion_01 +grenade_explosion_01b +grenade_explosion_01c +grenade_explosion_01d +grenade_explosion_01e +grenade_explosion_01f +grenade_explosion_01g +grenade_explosion_01h +" + + + +"grub_blood.pcf" +" +GrubBlood +GrubSquashBlood +GrubSquashBlood2 +" + + + +"hunter_flechette.pcf" +" +flechette_halo +inner_sparks +" + + + +"hunter_intro.pcf" +" +ceiling_dust +" + + + +"hunter_projectile.pcf" +" +hunter_flechette_glow +hunter_flechette_glow_striderbuster +hunter_flechette_trail +hunter_flechette_trail_striderbuster +hunter_muzzle_flash +hunter_muzzle_flash_b +hunter_projectile_1 +hunter_projectile_explosion_1 +hunter_projectile_explosion_2b +hunter_projectile_explosion_2c +hunter_projectile_explosion_2c_nocollide +hunter_projectile_explosion_2d +hunter_projectile_explosion_2d_nocollide +hunter_projectile_explosion_2e +hunter_projectile_explosion_2f +hunter_projectile_explosion_2g +hunter_projectile_explosion_2h +hunter_projectile_explosion_2i +hunter_projectile_explosion_2j +hunter_projectile_explosion_2k +hunter_projectile_explosion_3 +hunter_projectile_explosion_3b +hunter_projectile_explosion_3c +hunter_projectile_explosion_3d +hunter_projectile_explosion_3e +hunter_slide_dust +hunter_slide_rocks +" + + + +"hunter_shield_impact.pcf" +" +hunter_shield_impact +hunter_shield_impact2 +hunter_shield_impactglow +" + + + +"//largefire.pcf" //more broken fire +" +fastFire +smoke_blackbillow +" + + + +"light_rays.pcf" +" +light_rays_01 +" + + + +"magnusson_burner.pcf" +" +centralbeam_lower_large +centralbeam_upper_large +centralwarp +cingularity +cingularity_start +magnusson_burner +magnusson_burner_embers +Magnusson_centralbeam_lower +Magnusson_centralbeam_upper +magnusson_teleport_orangelight +magnusson_teleport_orangelight_b +spinA +steamjet +warpage +warpageupper +" + + + +"rain.pcf" +" +Rain_01 +Rain_01_character_head +Rain_01_character_head_b +Rain_01_character_shoulder +Rain_01_character_shoulder_b +Rain_01_fog +Rain_01_impact +Rain_01_impact_sphere +Rain_01_impactfog +Rain_01_impactring +" + + + +"sand.pcf" +" +demo_falling_sand +demo_falling_sand_sub +" + + + +"skybox_smoke.pcf" +" +citadel_shockwave +citadel_shockwave_06 +citadel_shockwave_06_c +citadel_shockwave__ +citadel_shockwave_b +citadel_shockwave_c +citadel_shockwave_d +citadel_shockwave_e +citadel_shockwave_f +citadel_shockwave_g +citadel_shockwave_h +citadel_shockwave_i +citadel_shockwave_j +citadel_shockwave_k +citadel_shockwave_l +portal_closure_01 +portal_closure_01_core +portal_closure_01b +portal_closure_01c +portal_closure_01d +portal_closure_01e +portal_closure_02 +portal_closure_02_branch +portal_closure_02b +portal_closure_02b_branch +portal_closure_03 +portal_closure_03_branch +portal_closure_03b +portal_closure_03b_branch +portal_lightning_01 +portal_lightning_01_06a +portal_lightning_01_core +portal_lightning_01_core_06a +portal_lightning_01b +portal_lightning_01c +portal_lightning_01d +portal_lightning_01e +portal_lightning_02 +portal_lightning_02_branch +portal_lightning_02b +portal_lightning_02b_branch +portal_lightning_03 +portal_lightning_03_branch +portal_lightning_03b +portal_lightning_03b_branch +portal_lightning_backblast +portal_lightning_closure_1 +portal_rift_01 +portal_rift_01b +portal_rift_01c +portal_rift_01d +portal_rift_01e +portal_rift_01f +portal_rift_01g +portal_rift_01h +portal_rift_02 +portal_rift_02b +portal_rift_02c +portal_rift_02d +portal_rift_02e +portal_rift_03 +portal_rift_03b +portal_rift_03c +portal_rift_03d +portal_rift_03e +portal_rift_03f +portal_rift_04 +portal_rift_dust +portal_rift_flash_01 +portal_rift_flash_01b +portal_ritf_flash_01c +skybox_cloud_01 +skybox_cloud_01_b +skybox_cloud_01_c +skybox_cloud_glow +skybox_cloud_glow2 +skybox_cloud_glow3 +skybox_fire_01 +skybox_fire_01 Version #2 +skybox_fire_01 Version #3 +Skybox_Fire_02 +Skybox_Flash_02 +Skybox_Flash_02b +Skybox_Haze_01 +Skybox_Smoke_01 +Skybox_Smoke_03 +Skybox_Smoke_03_06a +Skybox_Smoke_04 +Skybox_Smoke_05 +" + + + +"stalactite.pcf" +" +debris_impact_stalactite +debris_splinter_stalactite +dust2_splinter_stalactite +dust_impact_stalactite +dust_splinter_stalactite +rock_impact_stalactite +rock_splinter_stalactite +" + + + +"steampuff.pcf" +" +steam_jet_50 +steam_jet_50_steam +steam_jet_80 +steam_jet_80_drops +steam_jet_80_dropsteam +steam_jet_80_steam +steam_large_01 +steampuff +" + + + +"striderbuster.pcf" +" +striderbuster_attach +striderbuster_attach_flash +striderbuster_attach_ring +striderbuster_attached_pulse +striderbuster_attached_pulse_solid +striderbuster_break +striderbuster_break_b +striderbuster_break_b Version #2 +striderbuster_break_c +striderbuster_break_d +striderbuster_break_e +striderbuster_break_explode +striderbuster_break_flechette +striderbuster_break_lightning +striderbuster_break_lightning1 +striderbuster_break_lightning2 +striderbuster_break_lightning3 +striderbuster_break_lightning4 +striderbuster_break_shell +striderbuster_break_trail1 +striderbuster_break_trail2 +striderbuster_break_trail3 +striderbuster_break_trail4 +striderbuster_explode_core +striderbuster_explode_dummy_core +striderbuster_explode_dummy_parts +striderbuster_explode_flash +striderbuster_explode_goop +striderbuster_explode_smoke +striderbuster_flechette_attached +striderbuster_flechette_attached_lightning +striderbuster_flechette_attached_lightning2 +striderbuster_flechette_attached_lightning3 +striderbuster_flechette_attached_lightning4 +striderbuster_flechette_attached_lightning5 +striderbuster_flechette_attached_lightning6 +striderbuster_flechette_attached_sparks +striderbuster_gib_trail +striderbuster_shotdown_core_flash +striderbuster_shotdown_explosion_trail +striderbuster_shotdown_trail +striderbuster_smoke +striderbuster_trail +" + + + +"test_grnalpha.pcf" +" +test_green_alpha_sequence +" + + + +"test_noise.pcf" +" +vomit +" + + + +"vehicle.pcf" +" +Exhaust +WheelDirt +WheelDrops +WheelDust +WheelSplash +WheelSplashForward +WheelWake +" + + + +"//vistasmokev1.pcf" //conflicts with tf2 +" +smoke_blackbillow +" + + + +"//vortigaunt_fx.pcf" //it's the same as the one that comes with gmod +" +vortigaunt_beam +vortigaunt_beam_arc_cp1_0 +vortigaunt_beam_arc_cp1_1 +vortigaunt_beam_arc_cp1_2 +vortigaunt_beam_arc_cp1_3 +vortigaunt_beam_arc_cp1_4 +vortigaunt_beam_arc_cp1_5 +vortigaunt_beam_arc_cp1_6 +vortigaunt_beam_b +vortigaunt_beam_charge +vortigaunt_charge_token +vortigaunt_charge_token_b +vortigaunt_charge_token_c +vortigaunt_charge_token_d +vortigaunt_glow_beam_cp0 +vortigaunt_glow_beam_cp0b +vortigaunt_glow_beam_cp1 +vortigaunt_glow_beam_cp1b +vortigaunt_glow_charge_cp0 +vortigaunt_glow_charge_cp1 +vortigaunt_glow_charge_cp1_beam +vortigaunt_glow_charge_cp1_beam2 +vortigaunt_glow_charge_cp1_beam3 +vortigaunt_glow_charge_cp1_beam4 +vortigaunt_glow_charge_cp1_beam5 +vortigaunt_glow_charge_cp1_beam6 +vortigaunt_glow_charge_cp1b +vortigaunt_hand_glow +vortigaunt_hand_glow_b +" + + + +"warpshield.pcf" +" +warp_shield_impact +" + + + +"water_leaks.pcf" +" +WaterLeak_Pipe_1 +WaterLeak_Pipe_1_b +WaterLeak_Pipe_1_c +WaterLeak_Pipe_1_Main +WaterLeak_Pipe_1_Main_Additive +WaterLeak_Pipe_1_Main_Additive_Light +WaterLeak_Pipe_1_Main_Light +WaterLeak_Pipe_1_Mist +WaterLeak_Pipe_1_Old +WaterLeak_Pipe_1_SmallDrops +WaterLeak_Pipe_1_SmallFoam_1 +WaterLeak_Pipe_1_SmallFoam_2 +WaterLeak_Pipe_1_Stream_1 +WaterLeak_Pipe_1_Stream_2 +WaterLeak_Pipe_1_Stream_2 Version #2 +WaterLeak_Pipe_1_Stream_3 +WaterLeak_Pipe_1_Stream_Refract +WaterLeak_Pipe_1_TrailDrops_1 +WaterLeak_Pipe_Silo_01 +" + + + +"waterdrips.pcf" +" +blood_drip_slow +blood_drip_slow_base +larval_extract_drip +larval_extract_drip_2 +larval_extract_drip_2_base +larval_extract_drip_base +larval_extract_glow +slime_drip_slow +slime_drip_slow_base +slime_drip_slow_brown +slime_drip_slow_brown_base +water_drip_fast +water_drip_fast_base +water_drip_moderate +water_drip_moderate_base +water_drip_slow +water_drip_slow_base +waterdrip_ground_fast +waterdrip_ground_moderate +waterdrip_ground_slow +waterdrip_water_fast +waterdrip_water_moderate +waterdrip_water_slow +WGF_childA +WGF_childB +" + + + +"waterfall.pcf" +" +Waterfall_Cascade_01 +Waterfall_Impact_01 +Waterfall_Spray_01 +" + + + +"weapon_fx.pcf" +" +explosion_turret_break +explosion_turret_break_b +explosion_turret_break_chunks +explosion_turret_break_embers +explosion_turret_break_fire +explosion_turret_break_fire_over +explosion_turret_break_flash +explosion_turret_break_pre_flash +explosion_turret_break_pre_smoke +explosion_turret_break_pre_smoke Version #2 +explosion_turret_break_pre_sparks +explosion_turret_break_sparks +explosion_turret_fizzle +Rocket_Smoke +Weapon_Combine_Ion_Cannon +Weapon_Combine_Ion_Cannon_a +Weapon_Combine_Ion_Cannon_b +Weapon_Combine_Ion_Cannon_Backup +Weapon_Combine_Ion_Cannon_Beam +Weapon_Combine_Ion_Cannon_Black +Weapon_Combine_Ion_Cannon_c +Weapon_Combine_Ion_Cannon_d +Weapon_Combine_Ion_Cannon_e +Weapon_Combine_Ion_Cannon_Exlposion_c +Weapon_Combine_Ion_Cannon_Explosion +Weapon_Combine_Ion_Cannon_Explosion_b +Weapon_Combine_Ion_Cannon_Explosion_d +Weapon_Combine_Ion_Cannon_Explosion_e +Weapon_Combine_Ion_Cannon_Explosion_f +Weapon_Combine_Ion_Cannon_Explosion_g +Weapon_Combine_Ion_Cannon_Explosion_h +Weapon_Combine_Ion_Cannon_Explosion_i +Weapon_Combine_Ion_Cannon_Explosion_j +Weapon_Combine_Ion_Cannon_Explosion_k +Weapon_Combine_Ion_Cannon_f +Weapon_Combine_Ion_Cannon_g +Weapon_Combine_Ion_Cannon_h +Weapon_Combine_Ion_Cannon_h Version #2 +Weapon_Combine_Ion_Cannon_i +Weapon_Combine_Ion_Cannon_Intake +Weapon_Combine_Ion_Cannon_Intake_b +" + + + + + + +//This is a CUSTOM .pcf file, repackaged because its file name and effect names conflicted with effects from TF2. + +"explosion_ep2.pcf" //vistasmokev1.pcf is repackaged in here too since it's only one effect (smoke_blackbillow) +" +Explosion_2 +Explosion_2_Chunks +Explosion_2_Embers +Explosion_2_Fire +Explosion_2_FireSmoke +Explosion_2_flash +ep2_ExplosionCore +ep2_ExplosionEmbers +ep2_ExplosionFlash +ep2_smoke_blackbillow +" +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/gmod.lua b/garrysmod/addons/gmod-tools/lua/particlelists/gmod.lua new file mode 100644 index 0000000..c67e1bc --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/gmod.lua @@ -0,0 +1,550 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Source Engine / Garry's Mod" + + "EffectOptions" + { + "Beams" + " + !UTILEFFECT!AirboatGunHeavyTracer + !UTILEFFECT!AirboatGunTracer + !UTILEFFECT!AR2Tracer + !UTILEFFECT!GunshipTracer + !UTILEFFECT!HelicopterTracer + !UTILEFFECT!StriderTracer + !UTILEFFECT!Tracer + !UTILEFFECT!GaussTracer + !UTILEFFECT!LaserTracer + !UTILEFFECT!ToolTracer + + vortigaunt_beam + vortigaunt_beam_arc_cp1_0 + vortigaunt_beam_arc_cp1_1 + vortigaunt_beam_arc_cp1_2 + vortigaunt_beam_arc_cp1_3 + vortigaunt_beam_arc_cp1_4 + vortigaunt_beam_arc_cp1_5 + vortigaunt_beam_arc_cp1_6 + vortigaunt_beam_b + vortigaunt_beam_charge + vortigaunt_glow_charge_cp1_beam + vortigaunt_glow_charge_cp1_beam2 + vortigaunt_glow_charge_cp1_beam3 + vortigaunt_glow_charge_cp1_beam4 + vortigaunt_glow_charge_cp1_beam5 + vortigaunt_glow_charge_cp1_beam6 + " + + "Tracers" + " + !UTILEFFECT!AirboatGunHeavyTracer + !UTILEFFECT!AirboatGunTracer + !UTILEFFECT!AR2Tracer + !UTILEFFECT!GunshipTracer + !UTILEFFECT!HelicopterTracer + !UTILEFFECT!StriderTracer + !UTILEFFECT!Tracer + !UTILEFFECT!GaussTracer + !UTILEFFECT!LaserTracer + !UTILEFFECT!ToolTracer + + vortigaunt_beam + vortigaunt_beam_arc_cp1_0 + vortigaunt_beam_arc_cp1_1 + vortigaunt_beam_arc_cp1_2 + vortigaunt_beam_arc_cp1_3 + vortigaunt_beam_arc_cp1_4 + vortigaunt_beam_arc_cp1_5 + vortigaunt_beam_arc_cp1_6 + vortigaunt_beam_b + vortigaunt_beam_charge + vortigaunt_glow_charge_cp1_beam + vortigaunt_glow_charge_cp1_beam2 + vortigaunt_glow_charge_cp1_beam3 + vortigaunt_glow_charge_cp1_beam4 + vortigaunt_glow_charge_cp1_beam5 + vortigaunt_glow_charge_cp1_beam6 + " + } + + "UtilEffects" + { + "Antlion gib, old" "!UTILEFFECT!AntlionGib" + "Blood, red" "!UTILEFFECT!BloodImpact" + "Blood, yellow" "!UTILEFFECT!BloodImpact!COLOR1!" + "Blood, zombie" "!UTILEFFECT!BloodImpact!COLOR2!" + "Blood, synth" "!UTILEFFECT!BloodImpact!COLOR3!" + "Blood, strider" "!UTILEFFECT!StriderBlood" + "Explosion" "!UTILEFFECT!Explosion" + "Explosion (silent)" "!UTILEFFECT!Explosion!FLAG4!" + "Explosion, combine mortar" "!UTILEFFECT!AR2Explosion" + "Explosion, helicopter bomb" "!UTILEFFECT!HelicopterMegaBomb" + "Muzzle flash, SMG (scalable)" "!UTILEFFECT!MuzzleEffect" + "Muzzle flash, airboat" "!UTILEFFECT!AirboatMuzzleFlash" + "Muzzle flash, helicopter" "!UTILEFFECT!ChopperMuzzleFlash" + "Muzzle flash, strider" "!UTILEFFECT!StriderMuzzleFlash" + "Muzzle flash, gunship" "!UTILEFFECT!GunshipMuzzleFlash" + "Muzzle flash, shotgun" "!UTILEFFECT!MuzzleFlash!FLAG1!" + "Muzzle flash, SMG" "!UTILEFFECT!MuzzleFlash!FLAG2!" + "Muzzle flash, pistol" "!UTILEFFECT!MuzzleFlash!FLAG3!" + "Muzzle flash, AR2" "!UTILEFFECT!MuzzleFlash!FLAG5!" + //"Muzzle flash, hunter" "!UTILEFFECT!HunterMuzzleFlash" //doesn't create an effect, just a small dynamic light + "Impact, AR2" "!UTILEFFECT!AR2Impact" + "Impact, glass" "!UTILEFFECT!GlassImpact" + //"Impact, crossbow" "!UTILEFFECT!BoltImpact" + "Impact, stunstick" "!UTILEFFECT!StunstickImpact" + //"Impact, gunship (??)" "!UTILEFFECT!GunshipImpact" //not sure if this is used by anything - just makes a small flashing sprite for half a second + "Impact, antlion guard" "!UTILEFFECT!HunterDamage" + "Combine ball, bounce" "!UTILEFFECT!cball_bounce" + "Combine ball, explode" "!UTILEFFECT!cball_explode" + "Shell" "!UTILEFFECT!ShellEject" + "Shell, shotgun" "!UTILEFFECT!ShotgunShellEject" + "Shell, rifle" "!UTILEFFECT!RifleShellEject" + "Water, gunshot splash" "!UTILEFFECT!gunshotsplash" + "Water, surface explosion" "!UTILEFFECT!WaterSurfaceExplosion" + "Water, splash" "!UTILEFFECT!watersplash" + "Water, splash" "!UTILEFFECT!watersplash!FLAG1!" + "Water, ripple" "!UTILEFFECT!waterripple" + "Sparks" "!UTILEFFECT!Sparks" + "Sparks, manhack" "!UTILEFFECT!ManhackSparks" + "Sparks, crossbow load" "!UTILEFFECT!CrossbowLoad" + "Sparks, metal" "!UTILEFFECT!MetalSpark" + "Sparks, electric" "!UTILEFFECT!ElectricSpark" + "Tesla" "!UTILEFFECT!TeslaHitBoxes" + "Balloon pop" "!UTILEFFECT!balloon_pop" + "Vortigaunt dispel" "!UTILEFFECT!VortDispel" + "Dust, thumper" "!UTILEFFECT!ThumperDust" + "Dust, wheel" "!UTILEFFECT!WheelDust" + "Shake ropes" "!UTILEFFECT!ShakeRopes" + "Tracer, airboat heavy" "!UTILEFFECT!AirboatGunHeavyTracer" + "Tracer, airboat" "!UTILEFFECT!AirboatGunTracer" + "Tracer, AR2" "!UTILEFFECT!AR2Tracer" + "Tracer, gunship" "!UTILEFFECT!GunshipTracer" + "Tracer, helicopter" "!UTILEFFECT!HelicopterTracer" + "Tracer, strider" "!UTILEFFECT!StriderTracer" + "Tracer" "!UTILEFFECT!Tracer" + "Tracer, gauss" "!UTILEFFECT!GaussTracer" + "Tracer, laser" "!UTILEFFECT!LaserTracer" + "Tracer, toolgun" "!UTILEFFECT!ToolTracer" + //bullet impact effects need to be used in a very specific way to function + //"Impact" "!UTILEFFECT!Impact" //standard bullet impact + //"Impact, helicopter" "!UTILEFFECT!HelicopterImpact" //added sparks + //"Impact, airboat" "!UTILEFFECT!AirboatGunImpact" //added sparks + //"Impact, gauss" "!UTILEFFECT!ImpactGauss" //larger dust particles + //"Impact, jeep" "!UTILEFFECT!ImpactJeep" //larger dust particles + //"Impact, gunship" "!UTILEFFECT!ImpactGunship" //larger dust particles + "Blood spray, red (specks only)" "!UTILEFFECT!bloodspray!FLAG1!" + "Blood spray, red (small mist only)" "!UTILEFFECT!bloodspray!FLAG2!" + "Blood spray, red, small mist" "!UTILEFFECT!bloodspray!FLAG3!" + "Blood spray, red (large mist only)" "!UTILEFFECT!bloodspray!FLAG4!" + "Blood spray, red, large mist" "!UTILEFFECT!bloodspray!FLAG5!" + "Blood spray, yellow (specks only)" "!UTILEFFECT!bloodspray!FLAG1!!COLOR1!" + "Blood spray, yellow (small mist only)" "!UTILEFFECT!bloodspray!FLAG2!!COLOR1!" + "Blood spray, yellow, small mist" "!UTILEFFECT!bloodspray!FLAG3!!COLOR1!" + "Blood spray, yellow (large mist only)" "!UTILEFFECT!bloodspray!FLAG4!!COLOR1!" + "Blood spray, yellow, large mist" "!UTILEFFECT!bloodspray!FLAG5!!COLOR1!" + "Blood spray, pink (specks only)" "!UTILEFFECT!bloodspray!FLAG1!!COLOR2!" + "Blood spray, pink (small mist only)" "!UTILEFFECT!bloodspray!FLAG2!!COLOR2!" + "Blood spray, pink, small mist" "!UTILEFFECT!bloodspray!FLAG3!!COLOR2!" + "Blood spray, pink (large mist only)" "!UTILEFFECT!bloodspray!FLAG4!!COLOR2!" + "Blood spray, pink, large mist" "!UTILEFFECT!bloodspray!FLAG5!!COLOR2!" + "Blood spray, black (specks only)" "!UTILEFFECT!bloodspray!FLAG1!!COLOR3!" + "Blood spray, black (small mist only)" "!UTILEFFECT!bloodspray!FLAG2!!COLOR3!" + "Blood spray, black, small mist" "!UTILEFFECT!bloodspray!FLAG3!!COLOR3!" + "Blood spray, black (large mist only)" "!UTILEFFECT!bloodspray!FLAG4!!COLOR3!" + "Blood spray, black, large mist" "!UTILEFFECT!bloodspray!FLAG5!!COLOR3!" + //"Tesla zap" "!UTILEFFECT!TeslaZap" //doesn't seem to do anything + //"Command pointer" "!UTILEFFECT!CommandPointer" //doesn't seem to do anything + //"Smoke" "!UTILEFFECT!Smoke" //big, permanent, multicolored smoke effect. probably a stress test or something + //"RPG shot down" "!UTILEFFECT!RPGShotDown" //all this does is play a sound effect + //"Impact, gravity gun" "!UTILEFFECT!PhyscannonImpact" //doesn't seem to do anything + //"Blood, hud blood splat" "!UTILEFFECT!HudBloodSplat" //commented out in the SDK, doesn't do anything + //"Tracer, hunter (beta)" "!UTILEFFECT!HunterTracer" //it's the same as the other combine tracers only broken + //"Impact, ragdoll" "!UTILEFFECT!RagdollImpact" //doesn't seem to do anything + //"HL1 shell eject" "!UTILEFFECT!HL1ShellEject" //doesn't seem to do anything + //"HL1 gib" "!UTILEFFECT!HL1Gib" //spits out gib models with broken textures? maybe you need half-life source mounted or something. + //"HL1 " "!UTILEFFECT!HL1GaussWallImpact2" //these next ones don't do anything, but console spam says they're supposed to be spitting out + //"HL1 " "!UTILEFFECT!HL1GaussWallImpact1" //models, so again, maybe I just need half-life source + //"HL1 " "!UTILEFFECT!HL1GaussWallPunchExit" + //"HL1 " "!UTILEFFECT!HL1GaussWallPunchEnter" + //"HL1 " "!UTILEFFECT!HL1GaussReflect" + //"HL1 " "!UTILEFFECT!HL1GaussBeamReflect" + //"HL1 " "!UTILEFFECT!HL1GaussBeam" //doesn't seem to do anything + //"Shell, " "!UTILEFFECT!EjectBrass_338Mag" //i think these shell effects are from counter-strike or something? they're nearly the same as the + //"Shell, " "!UTILEFFECT!EjectBrass_762Nato" //hl2 ones, except they collide with the model they eject from, so let's skip these + //"Shell, " "!UTILEFFECT!EjectBrass_556" + //"Shell, " "!UTILEFFECT!EjectBrass_57" + //"Shell, " "!UTILEFFECT!EjectBrass_12Gauge" + //"Shell, " "!UTILEFFECT!EjectBrass_9mm" + //"" "!UTILEFFECT!ParticleTracer" //i think these are internal things used to play .pcf effects, actually + //"" "!UTILEFFECT!ParticleEffectStop" + //"" "!UTILEFFECT!ParticleEffect" + //"Camera flash" "!UTILEFFECT!camera_flash" //doesn't do anything except create an obnoxious dynamic light + "Sparks, entity remove" "!UTILEFFECT!entity_remove" + "Sparks, inflator tool" "!UTILEFFECT!inflator_magic" + //"Physgun freeze" "!UTILEFFECT!phys_freeze" //creates a halo effect around the entity, absolutely not + //"Physgun unfreeze" "!UTILEFFECT!phys_unfreeze" //creates a halo effect around the entity, absolutely not + "Prop spawn" "!UTILEFFECT!propspawn" + "Impact, toolgun" "!UTILEFFECT!selection_indicator" + "Impact, toolgun (ring only)" "!UTILEFFECT!selection_ring" + //"Wheel indicator" "!UTILEFFECT!wheel_indicator" //doesn't work, requires an "axis" value which we're not giving it + //"Depth of Field node" "!UTILEFFECT!dof_node" //causes an error + //"" "!UTILEFFECT!" + + } + } + + + +"antlion_blood.pcf" +" +AntlionGib +AntlionGibTrails +" + + + +"blood_impact.pcf" +" +blood_advisor_pierce_spray +blood_advisor_pierce_spray_b +blood_advisor_pierce_spray_c +blood_advisor_puncture +blood_advisor_puncture_withdraw +blood_advisor_shrapnel_impact +blood_advisor_shrapnel_spray_1 +blood_advisor_shrapnel_spray_2 +blood_advisor_shrapnel_spurt_1 +blood_advisor_shrapnel_spurt_2 +blood_antlionguard_injured_heavy +blood_antlionguard_injured_heavy_ +blood_antlionguard_injured_heavy_tiny +blood_antlionguard_injured_light +blood_antlionguard_injured_light_tiny +blood_drip_synth_01 +blood_drip_synth_01b +blood_drip_synth_01c +blood_impact_antlion_01 +blood_impact_antlion_worker_01 +blood_impact_green_01 +blood_impact_green_01_chunk +blood_impact_green_01_droplets +blood_impact_green_02_chunk +blood_impact_green_02_droplets +blood_impact_green_02_glow +blood_impact_red_01 +blood_impact_red_01_chunk +blood_impact_red_01_droplets +blood_impact_red_01_goop +blood_impact_red_01_mist +blood_impact_red_01_smalldroplets +blood_impact_synth_01 +blood_impact_synth_01_arc +blood_impact_synth_01_arc2 +blood_impact_synth_01_arc2 +blood_impact_synth_01_arc4 +blood_impact_synth_01_arc_parent +blood_impact_synth_01_arc_parents +blood_impact_synth_01_armor +blood_impact_synth_01_droplets +blood_impact_synth_01_dust +blood_impact_synth_01_short +blood_impact_synth_01_spurt +blood_impact_yellow_01 +blood_impact_zombie_01 +blood_spurt_synth_01 +blood_spurt_synth_01b +blood_zombie_split +blood_zombie_split_spray +blood_zombie_split_spray_tiny +blood_zombie_split_spray_tiny2 +vomit_barnacle +vomit_barnacle_b +" + + + +"burning_fx.pcf" +" +burning_character +burning_character_b +burning_character_c +burning_character_d +burning_character_e +" + + + +"combineball.pcf" +" +combineball +" + + + +"error.pcf" +" +error +" + + + +"fire_01.pcf" +" +burning_engine_01 +burning_engine_fire +burning_gib_01 +burning_gib_01_drag +burning_gib_01_follower1 +burning_gib_01_follower2 +burning_gib_01b +burning_vehicle +burning_wood_01 +burning_wood_01b +burning_wood_01c +embers_large_01 +embers_large_02 +embers_medium_01 +embers_medium_03 +embers_small_01 +env_embers_large +env_embers_medium +env_embers_medium_spread +env_embers_small +env_embers_small_spread +env_embers_tiny +env_fire_large +env_fire_large_b +env_fire_large_smoke +env_fire_large_smoke_b +env_fire_medium +env_fire_medium_b +env_fire_medium_smoke +env_fire_medium_spread +env_fire_medium_spread_b +env_fire_random_puff +env_fire_small +env_fire_small_b +env_fire_small_base +env_fire_small_coverage +env_fire_small_coverage_b +env_fire_small_coverage_base +env_fire_small_coverage_base_smoke +env_fire_small_coverage_c +env_fire_small_coverage_smoke +env_fire_small_smoke +env_fire_tiny +env_fire_tiny_b +env_fire_tiny_smoke +explosion_huge +explosion_huge_b +explosion_huge_burning_chunks +explosion_huge_c +explosion_huge_d +explosion_huge_e +explosion_huge_f +explosion_huge_flames +explosion_huge_flames_b +explosion_huge_g +explosion_huge_h +explosion_huge_i +explosion_huge_j +explosion_huge_k +explosion_huge_smoking_chunks +explosion_silo +fire_jet_01 +fire_jet_01_flame +fire_large_01 +fire_large_02 +fire_large_02_filler +fire_large_02_fillerb +fire_large_base +fire_medium_01 +fire_medium_01_glow +fire_medium_02 +fire_medium_02_nosmoke +fire_medium_03 +fire_medium_03_brownsmoke +fire_medium_base +fire_medium_burst +fire_medium_heatwave +fire_small_01 +fire_small_02 +fire_small_03 +fire_small_base +fire_small_flameouts +fire_verysmall_01 +smoke_burning_engine_01 +smoke_exhaust_01 +smoke_exhaust_01a +smoke_exhaust_01b +smoke_gib_01 +smoke_large_01 +smoke_large_01b +smoke_large_02 +smoke_large_02b +smoke_medium_01 +smoke_medium_02 +smoke_medium_02 Version #2 +smoke_medium_02b +smoke_medium_02b Version #2 +smoke_medium_02c +smoke_medium_02d +smoke_small_01 +smoke_small_01b +" + + + +"gmod_effects.pcf" +" +generic_smoke +" + + + +"//impact_fx.pcf" //this pcf isn't included in the manifest, and for good reason! almost all the effects use some outdated renderer and spew errors in the console when you try to use them. +" +impact_antlion +impact_computer +impact_computer_smoke +impact_concrete +impact_concrete_child_base +impact_concrete_child_smoke +impact_dirt +impact_dirt_child_base +impact_dirt_child_bounce +impact_dirt_child_burst +impact_dirt_child_burst2 +impact_dirt_child_smoke +impact_generic_burn +impact_generic_burst +impact_generic_burst_2 +impact_generic_smoke +impact_metal +impact_metal_child_base +impact_metal_child_glow +impact_metal_child_glow2 +impact_metal_child_smoke +impact_ricochet +impact_wood +impact_wood_child_base +impact_wood_child_burn +impact_wood_child_smoke +impact_wood_noflecks +ricochet_sparks +" + + + +"rocket_fx.pcf" +" +Rocket_Smoke +Rocket_Smoke_Trail +" + + + +"train_steam.pcf" //this pcf isn't actually in the manifest, even the default source one, i guess it's some unused thing +" +steam_train +" + + + +"vortigaunt_fx.pcf" +" +vortigaunt_beam +vortigaunt_beam_arc_cp1_0 +vortigaunt_beam_arc_cp1_1 +vortigaunt_beam_arc_cp1_2 +vortigaunt_beam_arc_cp1_3 +vortigaunt_beam_arc_cp1_4 +vortigaunt_beam_arc_cp1_5 +vortigaunt_beam_arc_cp1_6 +vortigaunt_beam_b +vortigaunt_beam_charge +vortigaunt_charge_token +vortigaunt_charge_token_b +vortigaunt_charge_token_c +vortigaunt_charge_token_d +vortigaunt_glow_beam_cp0 +vortigaunt_glow_beam_cp0b +vortigaunt_glow_beam_cp1 +vortigaunt_glow_beam_cp1b +vortigaunt_glow_charge_cp0 +vortigaunt_glow_charge_cp1 +vortigaunt_glow_charge_cp1_beam +vortigaunt_glow_charge_cp1_beam2 +vortigaunt_glow_charge_cp1_beam3 +vortigaunt_glow_charge_cp1_beam4 +vortigaunt_glow_charge_cp1_beam5 +vortigaunt_glow_charge_cp1_beam6 +vortigaunt_glow_charge_cp1b +vortigaunt_hand_glow +vortigaunt_hand_glow_b +" + + + +"water_impact.pcf" +" +slime_splash_01 +slime_splash_01_droplets +slime_splash_01_reversed +slime_splash_01_surface +slime_splash_02 +slime_splash_03 +water_bubble_ambient_1 +water_bubble_trail_1 +water_foam_01 +water_foam_01b +water_foam_01c +water_foam_01d +water_foam_line_long +water_foam_line_longb +water_foam_line_longc +water_foam_line_longd +water_foam_line_short +water_foam_line_shortb +water_foam_line_shortc +water_foam_line_shortd +water_gunk_1 +water_impact_bubbles_1 +water_impact_bubbles_1b +water_impact_bubbles_1c +water_impact_bubbles_1d +water_splash_01 +water_splash_01_droplets +water_splash_01_refract +water_splash_01_surface1 +water_splash_01_surface2 +water_splash_01_surface3 +water_splash_01_surface4 +water_splash_02 +water_splash_02_animated +water_splash_02_continuous +water_splash_02_droplets +water_splash_02_droplets Version #2 +water_splash_02_froth +water_splash_02_froth2 +water_splash_02_refract +water_splash_02_surface2 +water_splash_02_surface4 +water_splash_02_vertical +water_splash_03 +water_splash_leakypipe_silo +water_splash_leakypipe_silo_froth2 +water_splash_leakypipe_vertical +water_trail_medium +water_trail_medium_b +" + + + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/particlefiletest.lua b/garrysmod/addons/gmod-tools/lua/particlelists/particlefiletest.lua new file mode 100644 index 0000000..330714d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/particlefiletest.lua @@ -0,0 +1,73 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Example Particle List" + + "EffectOptions" + { + "Beams" + " + moreeffects_beam1 + moreeffects_beam2_alsoatracer + moreeffects_beam3_alsoatracer + " + + "Color1" + " + moreeffects_color1_1 + moreeffects_color1_2 + " + + "Color255" + " + moreeffects_color255_1 + moreeffects_color255_2 + " + + "Tracers" + " + moreeffects_beam2_alsoatracer + moreeffects_beam3_alsoatracer + " + } + + "UtilEffects" + { + "A Scripted Effect" "!UTILEFFECT!ThisIsFakeAndDoesntExist" + "A Scripted Effect 2" "!UTILEFFECT!ThisIsFakeToo" + } + } + + + +"example_1.pcf" //this is a comment +" +an_effect +another effect +YetAnotherEffect +An Effect With Capital Letters And Spaces //this is also a comment +//effect_thats_commented_out +" + + + +"example_2.pcf" +" +moreeffects_1 +moreeffects_2 +moreeffects_beam1 +moreeffects_beam2_alsoatracer +moreeffects_beam3_alsoatracer +moreeffects_color1_1 +moreeffects_color1_2 +moreeffects_color255_1 +moreeffects_color255_2 +" + + + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/portal.lua b/garrysmod/addons/gmod-tools/lua/particlelists/portal.lua new file mode 100644 index 0000000..d4a1a3f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/portal.lua @@ -0,0 +1,291 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Portal" + + "EffectOptions" + { + "Beams" + " + Glados_Beam + Glados_Beam_Static + + portal_1_overlap__ + " + + "Tracers" + " + Glados_Beam + Glados_Beam_Static + " + } + } + + + +"//blood_impact.pcf" //it's just a worse version of the stock source one +" +blood_impact_green_01 +blood_impact_red_01 +blood_impact_red_01_chunk +blood_impact_red_01_goop +blood_impact_red_01_smalldroplets +blood_impact_yellow_01 +" + + + +"cleansers.pcf" +" +Cleanser_edge_1 +Cleanser_edge_2 +human_cleanser +human_cleanser_buildup +human_cleanser_buildup_3d +human_cleanser_buildup_3d Version #2 +human_cleanser_buildup_floor +human_cleanser_buildup_floor2 +human_cleanser_large +portal_cleanser +" + + + +"environment.pcf" +" +elevator_beam +elevator_beam3 +elevator_beam_top +elevator_core +elevator_core2 +water_mist_1024_512 +water_mist_1024_512_b +water_mist_256 +water_mist_256_b +water_mist_384_128 +water_mist_384_128_b +water_mist_512 +water_mist_512_128 +water_mist_512_128_b +water_mist_512_b +" + + + +"finale_fx.pcf" +" +finale_arms +finale_finalportal +finale_fx +finale_gasescape1 +finale_gasescape_initial +finale_glowburn +finale_glowburn2 +finale_sparks +finale_sparks2 +" + + + +"//fire_01.pcf" //conflicts with default source engine fx +" +fire_incinerator_base +fire_incinerator_constant +fire_incinerator_constant_ +fire_incinerator_constant_2 +fire_incinerator_constant_end01 +fire_incinerator_constant_end02 +fire_incinerator_constant_smoke +fire_incinerator_door +fire_incinerator_door_heatwaver +fire_incinerator_door_smoke +fire_incinerator_door_smoke_constant +fire_incinerator_heatwaver +fire_incinerator_heatwaver_constant +fire_incinerator_window_constant_flames +fire_incinerator_window_constant_flames2 +fire_incinerator_window_occaisional_flames +fire_incinerator_window_warp +fire_large_02 +fire_large_02_filler +fire_large_02_fillerb +fire_large_02_warp +fire_large_base +fire_medium_01 +fire_medium_01_filler +fire_medium_01_fillerb +fire_medium_01_fillerb Version #2 +fire_medium_base +fire_ploom_01 +smoke_large_02b +smoke_medium_01 +" + + + +"glados.pcf" +" +Glados_Beam +Glados_Beam_Electricity +Glados_Beam_Endpoint +Glados_Beam_Parent_0 +Glados_Beam_Parent_1 +Glados_Beam_Static +" + + + +"neurotoxins.pcf" +" +neurotoxins_step1 +neurotoxins_step1b +neurotoxins_step2 +neurotoxins_step3 +" + + + +"portal_projectile.pcf" +" +portal_1_badsurface +portal_1_badsurface_ +portal_1_badvolume +portal_1_badvolume_ +portal_1_cleanser +portal_1_near +portal_1_near_in +portal_1_near_out +portal_1_near_warp +portal_1_nofit +portal_1_nofit_warp +portal_1_overlap +portal_1_overlap__ +portal_1_overlap_glow +portal_1_overlap_glow_nomove +portal_1_overlap_oval +portal_1_overlap_warp +portal_1_overlap_warp_fast +portal_1_projectile_3rdperson +portal_1_projectile_ball +portal_1_projectile_ball_3rdperson +portal_1_projectile_fiber +portal_1_projectile_stream +portal_1_projectile_stream_pedestal +portal_1_projectile_trail +portal_1_success +portal_2_badsurface +portal_2_badsurface_ +portal_2_badvolume +portal_2_badvolume_ +portal_2_cleanser +portal_2_cleanser_old +portal_2_near +portal_2_near_in +portal_2_near_out +portal_2_nofit +portal_2_overlap +portal_2_overlap_ +portal_2_projectile_3rdperson +portal_2_projectile_ball +portal_2_projectile_ball_3rdperson +portal_2_projectile_fiber +portal_2_projectile_stream +portal_2_projectile_stream_pedestal +portal_2_projectile_trail +portal_2_success +" + + + +"portalgun.pcf" +" +portal_1_charge +portal_1_charge_flash +portal_1_charge_glow +portal_1_charge_pulse +portal_2_charge +portal_2_charge_flash +portal_2_charge_glow +portal_2_charge_pulse +portalgun_1_cleanser +" + + + +"portals.pcf" +" +portal_1_close +portal_1_close_flash +portal_1_edge +portal_1_particles +Portal_1_vacuum +portal_1_vacuum2 +Portal_1_vacuum_ +portal_2_close +portal_2_edge +portal_2_particles +portal_2_test +portal_2_vacuum +" + + + +"red_blast.pcf" +" +large_spinning +Red_blast +" + + + +"tubes.pcf" +" +broken_tube_suck +broken_tube_suck_b +" + + + + + + +//This is a CUSTOM .pcf file, repackaged because its file name and effect names conflicted with default Source Engine/Gmod effects. + +"fire_01_portal.pcf" +" +fire_incinerator_base +fire_incinerator_constant +fire_incinerator_constant_ +fire_incinerator_constant_2 +fire_incinerator_constant_end01 +fire_incinerator_constant_end02 +fire_incinerator_constant_smoke +fire_incinerator_door +fire_incinerator_door_heatwaver +fire_incinerator_door_smoke +fire_incinerator_door_smoke_constant +fire_incinerator_heatwaver +fire_incinerator_heatwaver_constant +fire_incinerator_window_constant_flames +fire_incinerator_window_constant_flames2 +fire_incinerator_window_occaisional_flames +fire_incinerator_window_warp +portal_fire_large_02 +portal_fire_large_02_filler +portal_fire_large_02_fillerb +portal_fire_large_02_warp +portal_fire_large_base +portal_fire_medium_01 +portal_fire_medium_01_filler +portal_fire_medium_01_fillerb +portal_fire_medium_01_fillerb Version #2 +portal_fire_medium_base +portal_fire_ploom_01 +portal_smoke_large_02b +portal_smoke_medium_01 +" +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/particlelists/tf2.lua b/garrysmod/addons/gmod-tools/lua/particlelists/tf2.lua new file mode 100644 index 0000000..d48732e --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/particlelists/tf2.lua @@ -0,0 +1,5451 @@ +"EffectList" +{ + + + + "Info" + { + "CategoryName" "Team Fortress 2" + + "EffectOptions" + { + "Beams" + " + //bullet_tracers.pcf + bullet_bignasty_tracer01_blue + bullet_bignasty_tracer01_blue_crit + bullet_bignasty_tracer01_red + bullet_bignasty_tracer01_red_crit + bullet_pistol_tracer01_blue + bullet_pistol_tracer01_blue_crit + bullet_pistol_tracer01_red + bullet_pistol_tracer01_red_crit + bullet_scattergun_tracer01_blue + bullet_scattergun_tracer01_blue_crit + bullet_scattergun_tracer01_red + bullet_scattergun_tracer01_red_crit + bullet_shotgun_tracer01_blue + bullet_shotgun_tracer01_blue_crit + bullet_shotgun_tracer01_red + bullet_shotgun_tracer01_red_crit + bullet_tracer01 + bullet_tracer01_blue + bullet_tracer01_blue_crit + bullet_tracer01_crit + bullet_tracer01_red + bullet_tracer01_red_crit + bullet_tracer02_blue + bullet_tracer02_blue_crit + bullet_tracer02_red + bullet_tracer02_red_crit + tfc_sniper_distortion_trail + tfc_sniper_distortion_trail_noise + + //class_fx.pcf + laser_sight_beam + laser_sight_beam_dot + + //drg_cowmangler.pcf + cowmangler_idle_child_beam01 + + //dxhr.pcf + arm_muzzleflash_test + arm_muzzleflash_zap + arm_muzzleflash_zap2 + arm_muzzleflash_zap3 + dxhr_arm_muzzleflash + dxhr_arm_muzzleflash2 + dxhr_sniper_laser_blue + dxhr_sniper_laser_red + dxhr_sniper_rail + dxhr_sniper_rail_blue + dxhr_sniper_rail_red + sniper_dxhr_laser_blue_line + sniper_dxhr_laser_linetest + sniper_dxhr_laser_red_line + sniper_dxhr_laser_red_pulse + sniper_dxhr_laser_red_pulse_cp0 + sniper_dxhr_laser_red_pulse_cp1 + sniper_dxhr_laser_red_pulse_line + sniper_dxhr_rail + sniper_dxhr_rail_blue + sniper_dxhr_rail_end + sniper_dxhr_rail_end_blue + sniper_dxhr_rail_end_red + sniper_dxhr_rail_noise + sniper_dxhr_rail_noise_blue + sniper_dxhr_rail_noise_red + sniper_dxhr_rail_red + + //eyeboss.pcf + eb_beam_tp_laser + eb_beam_tp_spotlight + eyeboss_beam_angry + eyeboss_beam_tp + + //flamethrower.pcf + //medicgun_beam_red_trail_stage3 + pyrotaunt_rainbow_ARCA + pyrotaunt_rainbow_ARCB + " + + "Beams_cont1" + " + //halloween.pcf + bombonomicon_spell_trail + merasmus_zap + merasmus_zap_beam01 + merasmus_zap_beam01_BACKUP + merasmus_zap_beam02 + merasmus_zap_beam03 + merasmus_zap_beam_bits + spell_lightningball_hit_blue + spell_lightningball_hit_red + spell_lightningball_hit_zap_blue + spell_lightningball_hit_zap_red + spell_lightningball_parent_rope_blue + spell_lightningball_parent_rope_red + underworld_skull_zap + + //invasion_ray_gun_fx.pcf + bullet_tracer_raygun_blue + bullet_tracer_raygun_blue_crit + bullet_tracer_raygun_blue_rings + bullet_tracer_raygun_red + bullet_tracer_raygun_red_crit + bullet_tracer_raygun_red_rings + + //invasion_unusuals.pcf + unusual_invasion_boogaloop_2_trail + unusual_invasion_boogaloop_3_trail + unusual_invasion_boogaloop_trail + + //item_fx.pcf + superrare_balloon_b + + //medicgun_attrib.pcf + medicgun_beam_attrib_drips + medicgun_beam_attrib_overheal + medicgun_beam_attrib_overheal_blue + medicgun_beam_attrib_overheal_red + medicgun_beam_attrib_shards + + //medicgun_beam.pcf + dispenser_beam_blue_pluses + dispenser_beam_blue_trail + dispenser_beam_red_pluses + dispenser_beam_red_trail + dispenser_heal_blue + dispenser_heal_red + medicgun_beam_blue + medicgun_beam_blue_drips + medicgun_beam_blue_invun + medicgun_beam_blue_invunglow + medicgun_beam_blue_pluses + medicgun_beam_blue_targeted + medicgun_beam_blue_trail + medicgun_beam_blue_trail_stage1 + medicgun_beam_blue_trail_stage2 + medicgun_beam_blue_trail_stage3 + medicgun_beam_machinery + medicgun_beam_machinery_drips + medicgun_beam_machinery_pluses + medicgun_beam_machinery_stage1 + medicgun_beam_machinery_stage2 + medicgun_beam_machinery_stage3 + medicgun_beam_red + medicgun_beam_red_drips + medicgun_beam_red_invun + medicgun_beam_red_invunglow + medicgun_beam_red_pluses + medicgun_beam_red_targeted + medicgun_beam_red_trail + medicgun_beam_red_trail_stage1 + medicgun_beam_red_trail_stage2 + medicgun_beam_red_trail_stage3 + vaccinator_blue_beam1 + vaccinator_blue_beam1_icons + vaccinator_blue_beam2 + vaccinator_blue_beam2_icons + vaccinator_blue_beam3 + vaccinator_blue_beam3_icons + vaccinator_red_beam1 + vaccinator_red_beam1_icons + vaccinator_red_beam2 + vaccinator_red_beam2_icons + vaccinator_red_beam3 + vaccinator_red_beam3_icons + + //passtime_beam.pcf + passtime_beam + passtime_beam_drips + passtime_beam_pluses + passtime_beam_trail + passtime_beam_trail_stage1 + passtime_beam_trail_stage2 + passtime_beam_trail_stage3 + + //powerups.pcf + plague_transmission + powerup_supernova_strike_blue + powerup_supernova_strike_red + " + + "Beams_cont2" + " + //ARENA_BYRE: byreboom.pcf + byreLaser + + //PD_WATERGATE: alien_aly_fx.pcf + alien_laser_blue + alien_laser_blue_core_glow + alien_laser_blue_elec + alien_laser_blue_spin + alien_laser_core + alien_laser_core_glow + alien_laser_elec + alien_laser_elec2 + alien_laser_elec2_blue + alien_laser_elec2_green + alien_laser_elec2_red + alien_laser_green + alien_laser_green_core_glow + alien_laser_green_elec + alien_laser_green_spin + alien_laser_red + alien_laser_red_core_glow + alien_laser_red_elec + alien_laser_red_spin + alien_laser_spin + alien_laser_white + + //PD_WATERGATE: alien2_fx.pcf + alien_props_raygun_laser_body + alien_props_raygun_laser_body2 + alien_props_walker_laser_body + alien_props_walker_laser_body2 + + //CTF_2FORT_INVASION: invasion_2fort_fx.pcf + inv_condring_plasma_rope + + //KOTH_PROBED: koth_probed_fx.pcf + alien_abduction + alien_abduction_bits + alien_abduction_bits3 + alien_abduction_cow + alien_abduction_cows + alien_abduction_glow2 + alien_megalaser_blue + alien_megalaser_blue_core_glow + alien_megalaser_blue_elec + alien_megalaser_blue_spin + alien_megalaser_elec2_blue + alien_megalaser_elec2_red + alien_megalaser_red + alien_megalaser_red_core_glow + alien_megalaser_red_elec + alien_megalaser_red_spin + alien_props_raygun_laser_body + alien_props_raygun_laser_body2 + alien_props_walker_laser_body + alien_props_walker_laser_body2 + + //PL_SNOWYCOAST: pl_snowycoast_fx.pcf + alien_laser_elec2_red + alien_laser_red + alien_laser_red_core_glow + alien_laser_red_elec + alien_laser_red_spin + + //PD_PIT_OF_DEATH_EVENT: pd_pit_of_death_fx.pcf + utaunt_darkness_tentacle_beam + " + + "Color1" + " + //class_fx.pcf + killstreak_t0_lvl1_flash + killstreak_t0_lvl1_flash3 + killstreak_t1_lvl1 + killstreak_t1_lvl1_glow + killstreak_t1_lvl1_sparks + killstreak_t1_lvl2 + killstreak_t1_lvl2_glow + killstreak_t2_lvl1 + killstreak_t2_lvl1_glow + killstreak_t2_lvl2 + killstreak_t2_lvl2_glow + killstreak_t3_lvl1 + killstreak_t3_lvl1_smoke + killstreak_t3_lvl2 + killstreak_t3_lvl2_smoke + killstreak_t4_lvl1 + killstreak_t4_lvl1_glow + killstreak_t4_lvl2 + killstreak_t5_lvl1 + killstreak_t5_lvl1_glow + killstreak_t5_lvl2 + killstreak_t5_lvl2_glow + killstreak_t6_lvl1 + killstreak_t6_lvl1_smoke + killstreak_t6_lvl2 + killstreak_t6_lvl2_smoke + killstreak_t6_lvl2_sparks + killstreak_t6_lvl2_sparks2 + killstreak_t7_lvl1 + killstreak_t7_lvl1_detail + killstreak_t7_lvl1_edge + killstreak_t7_lvl2 + + //drg_bison.pcf + bison_idle_child_electro + bison_idle_child_sparks01 + bison_idle_child_sparks02 + bison_impact_child_electro + bison_impact_child_flare + bison_impact_child_sparks01 + bison_impact_child_sparks02 + bison_muzzleflash_child_beam01 + bison_muzzleflash_child_beam02 + bison_muzzleflash_child_beam04 //(beam03 does not exist) + bison_muzzleflash_child_beam05 + bison_muzzleflash_child_beam06 + bison_muzzleflash_child_electro + bison_muzzleflash_child_flare + bison_muzzleflash_child_sparks01 + bison_muzzleflash_child_sparks02 + bison_muzzleflash_child_sparks_rear + bison_projectile_child_core + bison_projectile_child_core_crit + bison_projectile_child_electro + bison_projectile_child_ring01 + bison_projectile_child_ring01_crit + bison_projectile_child_ring02 + drg_bison_idle + drg_bison_impact + drg_bison_muzzleflash + drg_bison_projectile + drg_bison_projectile_crit + drg_bison_reload + + //drg_cowmangler.pcf + cowmangler_idle_child_electro1 + cowmangler_idle_child_electro2 + cowmangler_impact_charged_child_electro + cowmangler_impact_charged_child_electrohelpers + cowmangler_impact_charged_child_flash + cowmangler_impact_charged_child_rings + cowmangler_impact_charged_child_sparks + cowmangler_impact_normal_child_electro + cowmangler_impact_normal_child_electrohelpers + cowmangler_impact_normal_child_flash + cowmangler_impact_normal_child_rings + cowmangler_impact_normal_child_sparks + cowmangler_muzzleflash_charged_child_electro + cowmangler_muzzleflash_chargeup_child_electro + cowmangler_muzzleflash_chargeup_child_glow + cowmangler_muzzleflash_normal_child_electro + cowmangler_muzzleflash_normal_child_sparks + cowmangler_reload_child_sparks01 + cowmangler_reload_child_sparks02 + cowmangler_trail_charged_child_core03 + cowmangler_trail_charged_child_electro_locked + cowmangler_trail_charged_child_rings + cowmangler_trail_charged_child_sparks + cowmangler_trail_normal_child_core03 + cowmangler_trail_normal_child_rings + drg_cowmangler_idle + drg_cowmangler_impact_charged + drg_cowmangler_impact_normal + drg_cowmangler_muzzleflash_charged + drg_cowmangler_muzzleflash_chargeup + drg_cowmangler_muzzleflash_normal + drg_cowmangler_trail_charged + drg_cowmangler_trail_normal + " + + "Color1_cont1" //yes, color1 is actually big enough that it needs a continuation to get past the 4095 char limit + " + //drg_engineer.pcf + drg_pomson_idle + drg_pomson_muzzleflash + drg_pomson_projectile + drg_pomson_projectile_crit + pomson_idle_sparks01 + pomson_muzzleflash_cone + pomson_muzzleflash_electro + pomson_muzzleflash_flare + pomson_muzzleflash_sparks01 + pomson_muzzleflash_sparks02 + pomson_projectile_core + pomson_projectile_crit + pomson_projectile_crit_bits + pomson_projectile_electro + pomson_projectile_ring01 + pomson_projectile_ring02 + pomson_projectile_zap + + //item_fx.pcf + foot_stamp + halloween_boss_foot_fire_customcolor + halloween_boss_foot_impact_customcolor //the one in item_fx is actually a bunch of stamps, not fire - i wonder which one will override the other? + //speech_mediccall //these are weird ones; they use the R value to control the transparency of the red part of the bubble, in reverse - + //speech_medichurt //0 0 0 is fully red, 1 0 0 is fully white. G and B don't seem to do anything. + + //rps.pcf + rps_bg + rps_burst_repeating_child + rps_paper + rps_rock + rps_scissors + rps_win_flash + + //scary_ghost.pcf + halloween_boss_foot_fire_customcolor + halloween_boss_foot_impact_customcolor + + //speechbubbles.pcf + //speech_mediccall //these are weird ones; they use the R value to control the transparency of the red part of the bubble, in reverse - + //speech_mediccall_auto //0 0 0 is fully red, 1 0 0 is fully white. G and B don't seem to do anything. + " + + "Color255" + " + //class_fx.pcf + laser_sight_beam + laser_sight_beam_dot + + //item_fx.pcf + superare_balloon + superrare_balloon_c + turret_shield + turret_shield_b + + //mvm.pcf + bot_eye_glow + bot_eye_halo + " + + "Tracers" + " + //bullet_tracers.pcf + bullet_bignasty_tracer01_blue + bullet_bignasty_tracer01_blue_crit + bullet_bignasty_tracer01_red + bullet_bignasty_tracer01_red_crit + bullet_pistol_tracer01_blue + bullet_pistol_tracer01_blue_crit + bullet_pistol_tracer01_red + bullet_pistol_tracer01_red_crit + bullet_scattergun_tracer01_blue + bullet_scattergun_tracer01_blue_crit + bullet_scattergun_tracer01_red + bullet_scattergun_tracer01_red_crit + bullet_shotgun_tracer01_blue + bullet_shotgun_tracer01_blue_crit + bullet_shotgun_tracer01_red + bullet_shotgun_tracer01_red_crit + bullet_tracer01 + bullet_tracer01_blue + bullet_tracer01_blue_crit + //bullet_tracer01_crit + bullet_tracer01_red + bullet_tracer01_red_crit + bullet_tracer02_blue + bullet_tracer02_blue_crit + bullet_tracer02_red + bullet_tracer02_red_crit + tfc_sniper_distortion_trail + tfc_sniper_distortion_trail_noise + + //class_fx.pcf + //laser_sight_beam //permanent + //laser_sight_beam_dot + + //drg_cowmangler.pcf + cowmangler_idle_child_beam01 + + //dxhr.pcf + //arm_muzzleflash_test //permanent + arm_muzzleflash_zap + //arm_muzzleflash_zap2 //permanent + arm_muzzleflash_zap3 + dxhr_arm_muzzleflash + //dxhr_arm_muzzleflash2 //permanent + //dxhr_sniper_laser_blue //permanent + //dxhr_sniper_laser_red //permanent + dxhr_sniper_rail + dxhr_sniper_rail_blue + dxhr_sniper_rail_red + //sniper_dxhr_laser_blue_line //permanent + //sniper_dxhr_laser_linetest //permanent + //sniper_dxhr_laser_red_line //permanent + sniper_dxhr_laser_red_pulse + sniper_dxhr_laser_red_pulse_cp0 + sniper_dxhr_laser_red_pulse_cp1 + sniper_dxhr_laser_red_pulse_line + sniper_dxhr_rail + sniper_dxhr_rail_blue + sniper_dxhr_rail_end + sniper_dxhr_rail_end_blue + sniper_dxhr_rail_end_red + sniper_dxhr_rail_noise + sniper_dxhr_rail_noise_blue + sniper_dxhr_rail_noise_red + sniper_dxhr_rail_red + + //eyeboss.pcf + //eb_beam_tp_laser //permanent + //eb_beam_tp_spotlight //permanent + //eyeboss_beam_angry + //eyeboss_beam_tp //permanent + + //flamethrower.pcf + //medicgun_beam_red_trail_stage3 + //pyrotaunt_rainbow_ARCA //permanent + //pyrotaunt_rainbow_ARCB //permanent + " + + "Tracers_cont1" + " + //halloween.pcf + bombonomicon_spell_trail + merasmus_zap + merasmus_zap_beam01 + merasmus_zap_beam01_BACKUP + merasmus_zap_beam02 + merasmus_zap_beam03 + merasmus_zap_beam_bits + spell_lightningball_hit_blue + spell_lightningball_hit_red + spell_lightningball_hit_zap_blue + spell_lightningball_hit_zap_red + //spell_lightningball_parent_rope_blue //permanent + //spell_lightningball_parent_rope_red //permanent + underworld_skull_zap + + //invasion_ray_gun_fx.pcf + bullet_tracer_raygun_blue + bullet_tracer_raygun_blue_crit + bullet_tracer_raygun_blue_rings + bullet_tracer_raygun_red + bullet_tracer_raygun_red_crit + bullet_tracer_raygun_red_rings + + //invasion_unusuals.pcf + unusual_invasion_boogaloop_2_trail + unusual_invasion_boogaloop_3_trail + unusual_invasion_boogaloop_trail + + //item_fx.pcf + //superrare_balloon_b //permanent + + //medicgun_attrib.pcf + medicgun_beam_attrib_drips + medicgun_beam_attrib_overheal + medicgun_beam_attrib_overheal_blue + medicgun_beam_attrib_overheal_red + medicgun_beam_attrib_shards + + //medicgun_beam.pcf + dispenser_beam_blue_pluses + dispenser_beam_blue_trail + dispenser_beam_red_pluses + dispenser_beam_red_trail + dispenser_heal_blue + dispenser_heal_red + medicgun_beam_blue + medicgun_beam_blue_drips + medicgun_beam_blue_invun + medicgun_beam_blue_invunglow + medicgun_beam_blue_pluses + medicgun_beam_blue_targeted + medicgun_beam_blue_trail + medicgun_beam_blue_trail_stage1 + medicgun_beam_blue_trail_stage2 + medicgun_beam_blue_trail_stage3 + medicgun_beam_machinery + medicgun_beam_machinery_drips + medicgun_beam_machinery_pluses + medicgun_beam_machinery_stage1 + medicgun_beam_machinery_stage2 + medicgun_beam_machinery_stage3 + medicgun_beam_red + medicgun_beam_red_drips + medicgun_beam_red_invun + medicgun_beam_red_invunglow + medicgun_beam_red_pluses + medicgun_beam_red_targeted + medicgun_beam_red_trail + medicgun_beam_red_trail_stage1 + medicgun_beam_red_trail_stage2 + medicgun_beam_red_trail_stage3 + vaccinator_blue_beam1 + vaccinator_blue_beam1_icons + vaccinator_blue_beam2 + vaccinator_blue_beam2_icons + vaccinator_blue_beam3 + vaccinator_blue_beam3_icons + vaccinator_red_beam1 + vaccinator_red_beam1_icons + vaccinator_red_beam2 + vaccinator_red_beam2_icons + vaccinator_red_beam3 + vaccinator_red_beam3_icons + + //passtime_beam.pcf + passtime_beam + passtime_beam_drips + passtime_beam_pluses + passtime_beam_trail + passtime_beam_trail_stage1 + passtime_beam_trail_stage2 + passtime_beam_trail_stage3 + + //powerups.pcf + plague_transmission + powerup_supernova_strike_blue + powerup_supernova_strike_red + " + + "Tracers_cont2" + " + //ARENA_BYRE: byreboom.pcf + //byreLaser + + //PD_WATERGATE: alien_aly_fx.pcf + alien_laser_blue + alien_laser_blue_core_glow + alien_laser_blue_elec + alien_laser_blue_spin + alien_laser_core + alien_laser_core_glow + alien_laser_elec + alien_laser_elec2 + alien_laser_elec2_blue + alien_laser_elec2_green + alien_laser_elec2_red + alien_laser_green + alien_laser_green_core_glow + alien_laser_green_elec + alien_laser_green_spin + alien_laser_red + alien_laser_red_core_glow + alien_laser_red_elec + alien_laser_red_spin + alien_laser_spin + alien_laser_white + + //PD_WATERGATE: alien2_fx.pcf + alien_props_raygun_laser_body + alien_props_raygun_laser_body2 + alien_props_walker_laser_body + alien_props_walker_laser_body2 + + //CTF_2FORT_INVASION: invasion_2fort_fx.pcf + inv_condring_plasma_rope + + //KOTH_PROBED: koth_probed_fx.pcf + alien_abduction + alien_abduction_bits + alien_abduction_bits3 + alien_abduction_cow + alien_abduction_cows + alien_abduction_glow2 + alien_megalaser_blue + alien_megalaser_blue_core_glow + alien_megalaser_blue_elec + alien_megalaser_blue_spin + alien_megalaser_elec2_blue + alien_megalaser_elec2_red + alien_megalaser_red + alien_megalaser_red_core_glow + alien_megalaser_red_elec + alien_megalaser_red_spin + alien_props_raygun_laser_body + alien_props_raygun_laser_body2 + alien_props_walker_laser_body + alien_props_walker_laser_body2 + + //PL_SNOWYCOAST: pl_snowycoast_fx.pcf + alien_laser_elec2_red + alien_laser_red + alien_laser_red_core_glow + alien_laser_red_elec + alien_laser_red_spin + + //PD_PIT_OF_DEATH_EVENT: pd_pit_of_death_fx.pcf + utaunt_darkness_tentacle_beam + " + } + } + + + +"bigboom.pcf" +" +explosion_trailFire +explosion_trailFire_mvm +explosion_trailSmoke +explosion_trailSmoke_mvm +explosionTrail_seeds +explosionTrail_seeds_mvm +fire_smokeCloud +fireSmoke_collumn +fireSmoke_Collumn_mvmAcres +fireSmoke_Collumn_mvmAcres_sm +fireSmoke_collumnP +fireSmokeExplosion +fireSmokeExplosion2 +fireSmokeExplosion3 +fireSmokeExplosion4 +fireSmokeExplosion_track +fireSmokeExplosion_trackb +fluidSmokeExpl_ring +fluidSmokeExpl_ring_mvm +fluidSmokeExpl_track +sparks_metal +sparks_metal_2 +woodSplinter +" + + + +"bl_killtaunt.pcf" +" +bl_killtaunt +bl_killtaunt_appear +bl_killtaunt_disappear +bl_killtaunt_explosion +bl_killtaunt_explosion_bits +bl_killtaunt_explosion_flash +bl_killtaunt_explosion_ring +bl_killtaunt_explosion_smoke +bl_killtaunt_finalburst +bl_killtaunt_finalburst_smoke +bl_killtaunt_flash01a +bl_killtaunt_flash01b +bl_killtaunt_flash02a +bl_killtaunt_flash02b +bl_killtaunt_flash03a +bl_killtaunt_flash03b +bl_killtaunt_flashes +bl_killtaunt_lightning01 +bl_killtaunt_lightning02 +bl_killtaunt_lightning03 +bl_killtaunt_lightning_flash +bl_killtaunt_necksparks +bl_killtaunt_necksparks_colliding +bl_killtaunt_necksparks_flashes +bl_killtaunt_rays +bl_killtaunt_rays01a1 +bl_killtaunt_rays01a2 +bl_killtaunt_rays01b1 +bl_killtaunt_rays01b2 +bl_killtaunt_rays02a1 +bl_killtaunt_rays02a2 +bl_killtaunt_rays02b1 +bl_killtaunt_rays02b2 +bl_killtaunt_rays03a1 +bl_killtaunt_rays03a2 +bl_killtaunt_rays03b1 +bl_killtaunt_rays03b2 +bl_killtaunt_sparks +bl_killtaunt_sparks01a +bl_killtaunt_sparks01b +bl_killtaunt_sparks02a +bl_killtaunt_sparks02b +bl_killtaunt_sparks03a +bl_killtaunt_sparks03b +bl_killtaunt_spotlight +" + + + +"//blood_impact.pcf" //conflicts with stock source blood effects +" +blood_bread_biting +blood_bread_biting2 +blood_decap +blood_decap_arterial_spray +blood_decap_fountain +blood_decap_streaks +blood_impact_backscatter +blood_impact_backscatter_ring +blood_impact_green_01 +blood_impact_green_01_droplets +blood_impact_green_01_goop +blood_impact_red_01 +blood_impact_red_01_chunk +blood_impact_red_01_droplets +blood_impact_red_01_goop +blood_impact_red_01_smalldroplets +blood_spray_red_01 +blood_spray_red_01_far +env_grinder_oilspray +env_grinder_oilspray_cash +env_sawblood +env_sawblood_chunk +env_sawblood_goop +env_sawblood_mist +lowV_blood_impact_red_01 +lowV_blood_spray_red_01 +lowV_blood_spray_red_01_far +lowV_debrischunks +lowV_impactglow +lowV_oildroplets +lowV_smallerchunks +lowv_sparks1 //(yes, the v is uncapitalized on just this one) +lowV_water_blood_impact_red_01 +lowV_water_bubbles +lowV_water_debris +temp_blood_spray_red_01 +temp_blood_spray_red_01_far +tfc_sniper_mist +tfc_sniper_mist2 +tfc_sniper_mist_dir +tfc_sniper_mist_streaks +water_blood_impact_red_01 +water_blood_impact_red_01_chunk +water_blood_impact_red_01_goop +" + + + +"blood_trail.pcf" +" +blood_trail_red_01_droplets +blood_trail_red_01_goop +" + + + +"bombinomicon.pcf" //(the large floating one on viaduct_event, not the equippable item) +" +bomb_vortex_skull +bombinomicon_airwaves +bombinomicon_airwaves_base +bombinomicon_vortex +" + + + +"buildingdamage.pcf" +" +buildingdamage_dispenser_fire0 +buildingdamage_dispenser_fire1 +buildingdamage_fire1 +buildingdamage_fire2 +buildingdamage_fire3 +buildingdamage_smoke1 +buildingdamage_smoke2 +buildingdamage_smoke3 +buildingdamage_smoke4 +buildingdamage_sparks1 +buildingdamage_sparks2 +buildingdamage_sparks4 //(no, sparks3 does not exist) +dispenserdamage_1 +dispenserdamage_2 +dispenserdamage_3 +dispenserdamage_4 +dispensersmoke_1 +dispensersmoke_2 +nutsnbolts_build +nutsnbolts_repair +nutsnbolts_upgrade +sentrydamage_1 +sentrydamage_2 +sentrydamage_3 +sentrydamage_4 +tpdamage_1 +tpdamage_2 +tpdamage_3 +tpdamage_4 +tpdamage_smoke1 +" + + + +"bullet_tracers.pcf" +" +bullet_bignasty_impact_chunks +bullet_bignasty_tracer01_blue +bullet_bignasty_tracer01_blue_crit +bullet_bignasty_tracer01_red +bullet_bignasty_tracer01_red_crit +bullet_impact1_blue_crit +bullet_impact1_blue_critglow +bullet_impact1_red_crit +bullet_impact1_red_critglow +bullet_pistol_tracer01_blue +bullet_pistol_tracer01_blue_crit +bullet_pistol_tracer01_red +bullet_pistol_tracer01_red_crit +bullet_scattergun_impact01 +bullet_scattergun_tracer01_blue +bullet_scattergun_tracer01_blue_crit +bullet_scattergun_tracer01_red +bullet_scattergun_tracer01_red_crit +bullet_shotgun_tracer01_blue +bullet_shotgun_tracer01_blue_crit +bullet_shotgun_tracer01_red +bullet_shotgun_tracer01_red_crit +bullet_tracer01 +bullet_tracer01_blue +bullet_tracer01_blue_crit +bullet_tracer01_crit +bullet_tracer01_red +bullet_tracer01_red_crit +bullet_tracer02_blue +bullet_tracer02_blue_crit +bullet_tracer02_red +bullet_tracer02_red_crit +tfc_sniper_distortion_trail +tfc_sniper_distortion_trail_noise +" + + + +"burningplayer.pcf" +" +_brushstroke +burning_torch +burninggibs +burningplayer_blue +burningplayer_blueglow +burningplayer_corpse +burningplayer_corpse_rainbow +burningplayer_corpse_rainbow_glow +burningplayer_corpse_rainbow_glow_white +burningplayer_corpse_rainbow_stars +burningplayer_corpseglow +burningplayer_flyingbits +burningplayer_glow +burningplayer_glow_blue +burningplayer_rainbow +burningplayer_rainbow_blue +burningplayer_rainbow_flame +burningplayer_rainbow_glow +burningplayer_rainbow_glow_old +burningplayer_rainbow_glow_white +burningplayer_rainbow_OLD +burningplayer_rainbow_red +burningplayer_rainbow_stars +burningplayer_rainbow_stars01 +burningplayer_rainbow_stars02 +burningplayer_rainbow_stars03 +burningplayer_rainbow_stars04 +burningplayer_rainbow_stars_base +burningplayer_rainbow_stroke +burningplayer_rainbow_v2 +burningplayer_red +burningplayer_redglow +burningplayer_smoke +burningplayer_smoke_blue +electrocuted_blue +electrocuted_blue_flash +electrocuted_gibbed_blue +electrocuted_gibbed_blue_flash +electrocuted_gibbed_red +electrocuted_gibbed_red_flash +electrocuted_red +electrocuted_red_flash +flaming_arrow +flaming_arrow_smoke +flying_flaming_arrow +flying_flaming_arrow_smoke +ghost_pumpkin +ghost_pumpkin_blueglow +ghost_pumpkin_flyingbits +halloween_burningplayer_flyingbits +pyrovision_flaming_arrow +pyrovision_flying_flaming_arrow +pyrovision_v_flaming_arrow +v_flaming_arrow +v_flaming_arrow_smoke +" + + + +"cig_smoke.pcf" +" +cig_burn +cig_smoke +drg_pipe_smoke +" + + + +"cinefx.pcf" +" +bombinomicon_burning_piece +bombinomicon_burning_piece_halloween +bombinomicon_burningdebris +bombinomicon_burningdebris_halloween +bombinomicon_burningsmoke +bombinomicon_burningsmoke_halloween +bombinomicon_flash +bombinomicon_flash_halloween +bombinomicon_flash_small +bombinomicon_flash_small_halloween +bomibomicon_ring //(yes, the name is mispelled) +cinefx_goldrush +cinefx_goldrush_burningbarrel +cinefx_goldrush_burningdebris +cinefx_goldrush_burningpiece +cinefx_goldrush_burningsmoke +cinefx_goldrush_debris +cinefx_goldrush_embers +cinefx_goldrush_flames +cinefx_goldrush_flash +cinefx_goldrush_hugedustup +cinefx_goldrush_initial_smoke +cinefx_goldrush_initial_smoke2 +cinefx_goldrush_pitglow +cinefx_goldrush_smoke +h2013_corpse_flame +h2013_corpse_smoke +hightower_explosion +hightower_smoke +skull_island_burning_piece +skull_island_burningdebris +skull_island_burningsmoke +skull_island_debris +skull_island_embers +skull_island_explosion +skull_island_flash +skull_island_hugedustup +skull_island_initial_smoke +" + + + +"class_fx.pcf" +" +dodge_test +eye_powerup_blue_lvl_1 +eye_powerup_blue_lvl_1b +eye_powerup_blue_lvl_2 +eye_powerup_blue_lvl_3 +eye_powerup_blue_lvl_4 +eye_powerup_blue_lvl_4b +eye_powerup_green_lvl_1 +eye_powerup_green_lvl_2 +eye_powerup_green_lvl_3 +eye_powerup_green_lvl_4 +eye_powerup_red_lvl_1 +eye_powerup_red_lvl_1b +eye_powerup_red_lvl_2 +eye_powerup_red_lvl_3 +eye_powerup_red_lvl_4 +eye_powerup_red_lvl_4b +god_rays +god_rays_fog +grenade_smoke +grenade_smoke_cycle +heavy_ring_of_fire +heavy_ring_of_fire_child01 +heavy_ring_of_fire_child02 +heavy_ring_of_fire_child03 +heavy_ring_of_fire_fp +heavy_ring_of_fire_fp_child01 +heavy_ring_of_fire_fp_child02 +heavy_ring_of_fire_fp_child03 +killstreak_t0_lvl1_flash +killstreak_t0_lvl1_flash3 +killstreak_t1_lvl1 +killstreak_t1_lvl1_glow +killstreak_t1_lvl1_sparks +killstreak_t1_lvl2 +killstreak_t1_lvl2_glow +killstreak_t2_lvl1 +killstreak_t2_lvl1_glow +killstreak_t2_lvl2 +killstreak_t2_lvl2_glow +killstreak_t3_lvl1 +killstreak_t3_lvl1_smoke +killstreak_t3_lvl2 +killstreak_t3_lvl2_smoke +killstreak_t4_lvl1 +killstreak_t4_lvl1_glow +killstreak_t4_lvl2 +killstreak_t5_lvl1 +killstreak_t5_lvl1_glow +killstreak_t5_lvl2 +killstreak_t5_lvl2_glow +killstreak_t6_lvl1 +killstreak_t6_lvl1_smoke +killstreak_t6_lvl2 +killstreak_t6_lvl2_smoke +killstreak_t6_lvl2_sparks +killstreak_t6_lvl2_sparks2 +killstreak_t7_lvl1 +killstreak_t7_lvl1_detail +killstreak_t7_lvl1_edge +killstreak_t7_lvl2 +laser_sight +laser_sight_beam +laser_sight_beam_dot +medic_healradius_blue_buffed +medic_healradius_red_buffed +medic_megaheal_blue +medic_megaheal_blue_shower +medic_megaheal_red +medic_megaheal_red_shower +medic_radiusheal_blue_spikes +medic_radiusheal_blue_spiral +medic_radiusheal_blue_volume +medic_radiusheal_red_spikes +medic_radiusheal_red_spiral +medic_radiusheal_red_volume +medic_resist_blast +medic_resist_bullet +medic_resist_fire +medic_resist_match_blast_blue +medic_resist_match_blast_red +medic_resist_match_bullet_blue +medic_resist_match_bullet_red +medic_resist_match_fire_blue +medic_resist_match_fire_red +ping_circle +scout_dodge_blue +scout_dodge_pants +scout_dodge_red +scout_dodge_socks +set_taunt_saharan_spy +set_taunt_saharan_spy_cloud +set_taunt_saharan_spy_cloud2 +set_taunt_saharan_spy_sand +set_taunt_saharan_spy_sand2 +set_taunt_saharan_spy_whirlwind +set_taunt_saharan_spy_whirlwind2 +set_taunt_saharan_spy_whirlwind3 +warp_version +" + + + +"classic_rocket_trail.pcf" +" +rocket_explosion_classic +rocket_explosion_classic_b +rocket_explosion_classic_crit_blue +rocket_explosion_classic_crit_red +rocket_trail_classic +rocket_trail_classic_b +rocket_trail_classic_crit_blue +rocket_trail_classic_crit_red +test_classic_trail +" + + + +"coin_spin.pcf" +" +coin_spin //(only shows up on movement) +" + + + +"conc_stars.pcf" +" +bonk_text +conc_stars +conc_trail +ghost_trail +yikes_fx +yikes_text +" + + + +"crit.pcf" +" +achieved +crit_text +doubledonk_text +heal_text //(not used in-game) +hit_text +mini_firework_flare +mini_fireworks +minicrit_text +miss_text +" + + + +"default.pcf" //(not sure what this is. effect is just white sparkles) +" +default +" + + + +"dirty_explode.pcf" //(also has duplicates of the goldrush effects, seemingly identical to ones in cinefx) +" +asplode_hoodoo +asplode_hoodoo_burning_debris +asplode_hoodoo_burning_piece +asplode_hoodoo_debris +asplode_hoodoo_dust +asplode_hoodoo_embers +asplode_hoodoo_flash +asplode_hoodoo_green +asplode_hoodoo_initial_smoke +asplode_hoodoo_shockwave +asplode_hoodoo_smoke +cinefx_goldrush +cinefx_goldrush_burningbarrel +cinefx_goldrush_burningdebris +cinefx_goldrush_burningpiece +cinefx_goldrush_burningsmoke +cinefx_goldrush_debris +cinefx_goldrush_embers +cinefx_goldrush_flames +cinefx_goldrush_flash +cinefx_goldrush_hugedustup +cinefx_goldrush_initial_smoke +cinefx_goldrush_initial_smoke2 +cinefx_goldrush_pitglow +cinefx_goldrush_smoke +" + + + +"disguise.pcf" +" +disguise_flash_blue +disguise_flash_red +spy_start_disguise_blue +spy_start_disguise_red +spy_stolen_flash_blue +spy_stolen_flash_red +spy_stolen_smoke_blue +spy_stolen_smoke_red +" + + + +"doomsday_fx.pcf" +" +australium_bar_glow +australium_bubbles +base_destroyed_smoke_doomsday +dooms_nuke_collumn +dooms_nuke_ring +flash_doomsday +rockettrail_burst_doomsday +rockettrail_doomsday +rockettrail_fire_doomsday +rockettrail_vents_doomsday +shockwave_ring_doomsday +" + + + +"drg_bison.pcf" +" +bison_idle_child_electro +bison_idle_child_sparks01 +bison_idle_child_sparks02 +bison_impact_child_electro +bison_impact_child_flare +bison_impact_child_sparks01 +bison_impact_child_sparks02 +bison_muzzleflash_child_beam01 +bison_muzzleflash_child_beam02 +bison_muzzleflash_child_beam04 //(beam03 does not exist) +bison_muzzleflash_child_beam05 +bison_muzzleflash_child_beam06 +bison_muzzleflash_child_electro +bison_muzzleflash_child_flare +bison_muzzleflash_child_sparks01 +bison_muzzleflash_child_sparks02 +bison_muzzleflash_child_sparks_rear +bison_projectile_child_core +bison_projectile_child_core_crit +bison_projectile_child_electro +bison_projectile_child_ring01 +bison_projectile_child_ring01_crit +bison_projectile_child_ring02 +drg_bison_idle +drg_bison_impact +drg_bison_muzzleflash +drg_bison_projectile +drg_bison_projectile_crit +drg_bison_reload +" + + + +"drg_cowmangler.pcf" +" +cowmangler_idle_child_beam01 +cowmangler_idle_child_electro1 +cowmangler_idle_child_electro2 +cowmangler_impact_charged_child_electro +cowmangler_impact_charged_child_electrohelpers +cowmangler_impact_charged_child_flash +cowmangler_impact_charged_child_rings +cowmangler_impact_charged_child_sparks +cowmangler_impact_normal_child_electro +cowmangler_impact_normal_child_electrohelpers +cowmangler_impact_normal_child_flash +cowmangler_impact_normal_child_rings +cowmangler_impact_normal_child_sparks +cowmangler_muzzleflash_charged_child_electro +cowmangler_muzzleflash_chargeup_child_electro +cowmangler_muzzleflash_chargeup_child_glow +cowmangler_muzzleflash_normal_child_electro +cowmangler_muzzleflash_normal_child_sparks +cowmangler_reload_child_sparks01 +cowmangler_reload_child_sparks02 +cowmangler_trail_charged_child_core03 +cowmangler_trail_charged_child_electro_locked +cowmangler_trail_charged_child_rings +cowmangler_trail_charged_child_sparks +cowmangler_trail_normal_child_core03 +cowmangler_trail_normal_child_rings +drg_cow_explosion_coreflash +drg_cow_explosion_coreflash_blue +drg_cow_explosion_flash_1 +drg_cow_explosion_flash_1_blue +drg_cow_explosion_flashup +drg_cow_explosion_flashup_blue +drg_cow_explosion_flyingembers +drg_cow_explosion_flyingembers_blue +drg_cow_explosion_smoke +drg_cow_explosion_smoke_blue +drg_cow_explosion_sparkles +drg_cow_explosion_sparkles_blue +drg_cow_explosion_sparkles_charged +drg_cow_explosion_sparkles_charged_blue +drg_cow_explosion_sparks +drg_cow_explosion_sparks_blue +drg_cow_explosioncore_charged +drg_cow_explosioncore_charged_blue +drg_cow_explosioncore_normal +drg_cow_explosioncore_normal_blue +drg_cow_idle +drg_cow_idle_blue +drg_cow_idle_electro1 +drg_cow_idle_electro1_blue +drg_cow_idle_electro2 +drg_cow_idle_electro2_blue +drg_cow_muzzleflash_charged +drg_cow_muzzleflash_charged_blue +drg_cow_muzzleflash_normal +drg_cow_muzzleflash_normal_blue +drg_cow_muzzleflash_sparkles +drg_cow_muzzleflash_sparkles_blue +drg_cow_rockettrail_burst +drg_cow_rockettrail_burst_blue +drg_cow_rockettrail_burst_charged +drg_cow_rockettrail_burst_charged_blue +drg_cow_rockettrail_charged +drg_cow_rockettrail_charged_blue +drg_cow_rockettrail_fire +drg_cow_rockettrail_fire_blue +drg_cow_rockettrail_fire_charged +drg_cow_rockettrail_fire_charged_blue +drg_cow_rockettrail_normal +drg_cow_rockettrail_normal_blue +drg_cowmangler_idle +drg_cowmangler_impact_charged +drg_cowmangler_impact_normal +drg_cowmangler_muzzleflash_charged +drg_cowmangler_muzzleflash_chargeup +drg_cowmangler_muzzleflash_normal +drg_cowmangler_trail_charged +drg_cowmangler_trail_normal +" + + + +"drg_engineer.pcf" +" +drg_pomson_idle +drg_pomson_impact +drg_pomson_impact_drain +drg_pomson_muzzleflash +drg_pomson_projectile +drg_pomson_projectile_crit +drg_wrenchmotron_idle +drg_wrenchmotron_impact +drg_wrenchmotron_teleport +pomson_idle_sparks01 +pomson_impact_electro +pomson_impact_flare +pomson_impact_sparks01 +pomson_muzzleflash_cone +pomson_muzzleflash_electro +pomson_muzzleflash_flare +pomson_muzzleflash_sparks01 +pomson_muzzleflash_sparks02 +pomson_projectile_core +pomson_projectile_crit +pomson_projectile_crit_bits +pomson_projectile_electro +pomson_projectile_ring01 +pomson_projectile_ring02 +pomson_projectile_zap +wrenchmotron_idle_electro +wrenchmotron_teleport_beam +wrenchmotron_teleport_flash +wrenchmotron_teleport_glow_big +wrenchmotron_teleport_sparks +" + + + +"drg_pyro.pcf" +" +3rd_attack_glow +3rd_idle_glow +3rd_trail +drg_3rd_idle +drg_3rd_impact +drg_3rd_trail +drg_fiery_death +drg_manmelter_idle +drg_manmelter_impact +drg_manmelter_muzzleflash +drg_manmelter_projectile +drg_manmelter_trail_blue +drg_manmelter_trail_red +drg_manmelter_vacuum +drg_manmelter_vacuum_flames +drg_phlo_idle +drg_phlo_stream +drg_phlo_stream_crit +fiery_death_ash +fiery_death_embers +fiery_death_flame +fiery_death_glow +fiery_death_main +fiery_death_main_smoke +fiery_death_smoke +manmelter_idle_sparks01 +manmelter_impact_electro +manmelter_impact_flare +manmelter_impact_sparks01 +manmelter_impact_sparks02 +manmelter_muzzleflash_electro +manmelter_muzzleflash_flare +manmelter_muzzleflash_sparks01 +manmelter_projectile_electro +manmelter_projectile_glow +manmelter_projectile_plasma +manmelter_projectile_trail +manmelter_trail_blue +manmelter_trail_red +manmelter_vacuum_bits +manmelter_vacuum_flames +manmelter_vacuum_flash +manmelter_vacuum_smoke +phlo_idle_glow +phlo_stream_crit_extras +phlo_stream_drips +phlo_stream_glow +phlo_stream_glow_unlocked +phlo_stream_main +phlo_stream_muzzle +phlo_stream_noise +phlo_stream_noise_sharp +phlo_stream_noise_unlocked +" + + + +"dxhr_fx.pcf" +" +arm_detonate_electro +arm_detonate_flare +arm_detonate_sparks +arm_impact_flare +arm_impact_sparks +arm_muzzleflash_electro +arm_muzzleflash_flare +arm_muzzleflash_test +arm_muzzleflash_zap +arm_muzzleflash_zap2 +arm_muzzleflash_zap3 +diamondback_enemytag_glow +diamondback_enemytag_skull_black +diamondback_enemytag_skull_helper +diamondback_enemytag_skull_white +dxhr_arm_impact +dxhr_arm_muzzleflash +dxhr_arm_muzzleflash2 +dxhr_diamondback_enemytag +dxhr_sniper_fizzle +dxhr_sniper_laser_blue +dxhr_sniper_laser_red +dxhr_sniper_muzzleflash +dxhr_sniper_rail +dxhr_sniper_rail_blue +dxhr_sniper_rail_red +dxhr_widowmaker_metalabsorb +dxhr_widowmaker_shelleject +sniper_dxhr_fizzle +sniper_dxhr_flash_child01 +sniper_dxhr_flash_child02 +sniper_dxhr_laser_blue_line +sniper_dxhr_laser_linetest +sniper_dxhr_laser_red_line +sniper_dxhr_laser_red_pulse +sniper_dxhr_laser_red_pulse_cp0 +sniper_dxhr_laser_red_pulse_cp1 +sniper_dxhr_laser_red_pulse_line +sniper_dxhr_rail +sniper_dxhr_rail_blue +sniper_dxhr_rail_end +sniper_dxhr_rail_end_blue +sniper_dxhr_rail_end_red +sniper_dxhr_rail_noise +sniper_dxhr_rail_noise_blue +sniper_dxhr_rail_noise_red +sniper_dxhr_rail_red +widowmaker_absorb_nutsnbolts +widowmaker_absorb_sparks +widowmaker_shelleject_nutsnbolts +" + + + +"explosion.pcf" +" +airburst_shockwave +airburst_shockwave_d +bday_1balloon +bday_balloon01 +bday_balloon02 +bday_blood +bday_bloodconfetti +bday_bloodconfetti2 +bday_confetti +bday_confetti_colors +Explosion_bubbles +Explosion_CoreFlash +Explosion_Debris001 +Explosion_Dustup +Explosion_Dustup_2 +Explosion_Flash_1 +Explosion_Flashup +Explosion_FloatieEmbers +Explosion_FlyingEmbers +Explosion_ShockWave_01 +Explosion_Smoke_1 +ExplosionCore_buildings +ExplosionCore_MidAir +ExplosionCore_MidAir_Flare +ExplosionCore_MidAir_Jumper +ExplosionCore_MidAir_underwater +ExplosionCore_sapperdestroyed +ExplosionCore_Wall +ExplosionCore_Wall_Jumper +ExplosionCore_Wall_underwater +Explosions_MA_coreflash +Explosions_MA_Debris001 +Explosions_MA_Dustup +Explosions_MA_Dustup_2 +Explosions_MA_Flash_1 +Explosions_MA_Flashup +Explosions_MA_FloatieEmbers +Explosions_MA_FlyingEmbers +Explosions_MA_Smoke_1 +Explosions_UW_Debris001 +pyrovision_1balloon +pyrovision_blood +pyrovision_explosion +pyrovision_flaregun_destroyed +rd_robot_explosion +rd_robot_explosion_bits +rd_robot_explosion_bits2 +rd_robot_explosion_burst +rd_robot_explosion_shockwave +rd_robot_explosion_shockwave2 +rd_robot_explosion_smoke +rd_robot_explosion_smoke_linger +rd_robot_explosion_trail +rd_robot_explosion_trail_burst +rd_robot_explosion_trail_fire +rd_robot_explosion_trail_smoke +rd_robot_explosion_trail_smoke2 +rd_robot_explosion_trail_smoke3 +rd_robot_exposion_glow //yes, it's misspelled +sapper_coreflash +sapper_debris +sapper_flash +sapper_flashup +sapper_flyingembers +sapper_smoke +" + + + +"eyeboss.pcf" //(monoculus effects) +" +eb_aura_angry01 +eb_aura_angry02 +eb_aura_angry_warp +eb_aura_calm01 +eb_aura_calm02 +eb_aura_grumpy01 +eb_aura_grumpy02 +eb_aura_stunned01 +eb_aura_stunned02 +eb_beam_angry_core01 +eb_beam_angry_core02 +eb_beam_angry_core03 +eb_beam_angry_core04 +eb_beam_angry_ring01 +eb_beam_tp_laser +eb_beam_tp_ring01 +eb_beam_tp_spotlight +eb_death_bits +eb_death_flash +eb_death_gas +eb_death_goop +eb_death_vortex01 +eb_death_vortex02 +eb_death_vortex03 +eb_death_vortex04 +eb_death_vortex05 +eb_doorway_vortex04 +eb_projectile_core01 +eb_projectile_core02 +eb_projectile_sparks +eb_spit_bits +eb_spit_gas +eb_spit_goop +eb_tp_escape_bits +eb_tp_escape_flash01 +eb_tp_escape_warp +eb_tp_normal_bits +eb_tp_normal_flash01 +eb_tp_normal_flash02 +eb_tp_normal_warp +eb_tp_player_flash +eb_tp_player_rope +eb_tp_vortex01 +eb_tp_vortex02 +eb_tp_vortex03 +eb_tp_vortex04 +eb_tp_vortex05 +eyeboss_aura_angry +eyeboss_aura_calm +eyeboss_aura_grumpy +eyeboss_aura_stunned +eyeboss_beam_angry +eyeboss_beam_tp +eyeboss_death +eyeboss_death_vortex +eyeboss_doorway_vortex +eyeboss_projectile +eyeboss_spit +eyeboss_team_blue +eyeboss_team_red +eyeboss_team_sparks +eyeboss_team_sparks_red +eyeboss_tp_escape +eyeboss_tp_normal +eyeboss_tp_player +eyeboss_tp_vortex +eyeboss_vortex_blue +eyeboss_vortex_red +" + + + +"firstperson_weapon_fx.pcf" +" +critgun_weaponmodel_blu +critgun_weaponmodel_blu_glow +critgun_weaponmodel_red +critgun_weaponmodel_red_glow +crutgun_firstperson +" + + + +"flag_particles.pcf" +" +cart_flashinglight +cart_flashinglight_glow +cart_flashinglight_glow_red +cart_flashinglight_light +cart_flashinglight_light_red +cart_flashinglight_red +player_australium_boxertrail +player_australium_socktrail +player_australium_sparkles +player_intel_papertrail +player_intel_trail_blue +player_intel_trail_red +" + + + +"flamethrower.pcf" +" +_flamethrower_REAL +flamethrower +flamethrower_blue +flamethrower_crit_blue +flamethrower_crit_blue_glow +flamethrower_crit_blue_sparks2 +flamethrower_crit_pilot_blue +flamethrower_crit_pilot_red +flamethrower_crit_red +flamethrower_crit_red_glow +flamethrower_crit_red_sparks +flamethrower_drips +flamethrower_drips_rainbow +flamethrower_fire_1 +flamethrower_fire_1_rainbow +flamethrower_halloween +flamethrower_halloween_crit_blue +flamethrower_halloween_crit_red +flamethrower_pilot +flamethrower_pilot_rainbow +flamethrower_rainbow +flamethrower_rainbow_bubbles01 +flamethrower_rainbow_bubbles02 +flamethrower_rainbow_FP +flamethrower_rainbow_FP_BACKUP +flamethrower_rainbow_FP_BACKUP2 +flamethrower_rainbow_FP_body +flamethrower_rainbow_REAL +flamethrower_rainbow_swirl +flamethrower_rainbow_swirl_stars +flamethrower_underwater +flaregun_destroyed +flaregun_destroyed_bits +//medicgun_beam_red_trail_stage3 //heh, what's this doing here +pyro_blast +pyro_blast_flash +pyro_blast_lines +pyro_blast_warp +pyro_blast_warp2 +pyrotaunt +pyrotaunt_flame +pyrotaunt_glow +pyrotaunt_powerup +pyrotaunt_rainbow +pyrotaunt_rainbow_arc +pyrotaunt_rainbow_arc_child +pyrotaunt_rainbow_arc_rot +pyrotaunt_rainbow_arc_rotA +pyrotaunt_rainbow_arc_rotB +pyrotaunt_rainbow_arc_v3A +pyrotaunt_rainbow_arc_v3B +pyrotaunt_rainbow_arc_v4_rope +pyrotaunt_rainbow_arc_v4A +pyrotaunt_rainbow_arc_v4A_rope +pyrotaunt_rainbow_arc_v4B +pyrotaunt_rainbow_arc_v4B_rope +pyrotaunt_rainbow_ARCA +pyrotaunt_rainbow_ARCB +pyrotaunt_rainbow_balloonicorn +pyrotaunt_rainbow_balloonicorn_ +pyrotaunt_rainbow_bubbles +pyrotaunt_rainbow_bubbles_flame +pyrotaunt_rainbow_explosion01 +pyrotaunt_rainbow_explosion02 +pyrotaunt_rainbow_explosion03 +pyrotaunt_rainbow_explosion_unicorn +pyrotaunt_rainbow_norainbow +pyrotaunt_rainbow_norainbow_smoke01 +pyrotaunt_rainbow_norainbow_smoke02 +pyrotaunt_rainbow_rof +pyrotaunt_rainbow_rof_embers +pyrotaunt_rainbow_rof_expand +pyrotaunt_rainbow_rof_ground +pyrotaunt_rainbow_SIDEA +pyrotaunt_rainbow_SIDEB +pyrotaunt_rainbow_stars +pyrotaunt_rainbow_sun +pyrotaunt_rainbow_sunglow +pyrotaunt_rainbow_TOP +" + + + +"flamethrower_mvm.pcf" +" +flamethrower_crit_giant_mvm +flamethrower_crit_glow_giant_mvm +flamethrower_crit_pilot_giant_mvm +flamethrower_crit_sparks2_giant_mvm +flamethrower_drips +flamethrower_drips_giant_mvm +flamethrower_fire_1 +flamethrower_fire_1_giant_mvm +flamethrower_giant_mvm +flamethrower_pilot_giant_mvm +" + + + +"halloween.pcf" +" +blood_decap +bombonomicon_spell_trail //yes, it's spelled wrong +bonzo_vomit2_blue +bonzo_vomit2_red +bonzo_vomit_blue +bonzo_vomit_bones2_blue +bonzo_vomit_bones2_red +bonzo_vomit_bones_blue +bonzo_vomit_bones_purple +bonzo_vomit_bones_red +bonzo_vomit_purple +bonzo_vomit_red +doomsday_tentpole_vanish01 +doomsday_tentpole_vanish02 +duck_collect_blood_green +duck_collect_green +duck_collect_sparkles_green +duck_collect_trail_blue +duck_collect_trail_red +duck_collect_trail_special_blue +duck_collect_trail_special_red +duck_pickup +duck_pickup_bubbles +duck_pickup_ring +fuse_flame +fuse_smoke +fuse_sparks +green_steam_jet +green_steam_plume +green_vortex_rain +green_wof_sparks +halloween_explosion +halloween_explosion_bits +halloween_player_death +halloween_player_death_blue +halloween_pumpkin_cloud +hammer_bell_ring_shockwave +hammer_bell_ring_shockwave2 +hammer_bones_kickup +hammer_bones_kickup2 +hammer_debri_kickup2 //yes, it's spelled wrong +hammer_dust_kickup +hammer_impact_button +hammer_impact_button_dust +hammer_impact_button_dust2 +hammer_impact_button_ring +hammer_lock_vanish01 +hammer_lock_vanish02 +hammer_souls_rising +hwn_bats01 +hwn_bats_tent_blue +hwn_bats_tent_red +hwn_cart_cap_blue +hwn_cart_cap_neutral +hwn_cart_cap_red +hwn_cart_drips_blue +hwn_cart_drips_red +hwn_eye_crowd01 +hwn_eye_crowd02 +hwn_lava_01_goop +hwn_lava_01_smoke +hwn_lava_02_goop +hwn_lava_02_smoke +hwn_lava_03_smoke +hwn_skeleton_glow_blue +hwn_skeleton_glow_red +kart_braking_sparks +kart_dust_trail_blue +kart_dust_trail_red +kart_impact_glow +kart_impact_sparks +kartdamage_4 +kartdamage_smoke4 +kartimpacttrail +merasmus_ambient +merasmus_ambient_body +merasmus_ambient_body_add +merasmus_ambient_body_BACKUP +merasmus_ambient_body_noadd +merasmus_ambient_glow +merasmus_ambient_smoke +merasmus_ambient_smoke_add +merasmus_blood +merasmus_blood_bits +merasmus_blood_lowdamage +merasmus_blood_smoke +merasmus_bomb_explosion +merasmus_bomb_explosion_bits +merasmus_bomb_explosion_blast +merasmus_bomb_explosion_flash +merasmus_bomb_explosion_smoke +merasmus_book_attack +merasmus_book_attack_blast +merasmus_book_attack_core +merasmus_book_attack_debris01 +merasmus_book_attack_debris02 +merasmus_book_attack_debris03 +merasmus_book_attack_debris04 +merasmus_book_attack_debris05 +merasmus_book_attack_debris06 +merasmus_book_attack_debris07 +merasmus_book_attack_fog +merasmus_dazed +merasmus_dazed_bits +merasmus_dazed_blood +merasmus_dazed_explosion +merasmus_dazed_flash +merasmus_dazed_smoke +merasmus_object_spawn +merasmus_shoot +merasmus_shoot_bits +merasmus_shoot_blast +merasmus_shoot_flash +merasmus_shoot_smoke +merasmus_spawn +merasmus_spawn_burst +merasmus_spawn_core +merasmus_spawn_flash +merasmus_spawn_flash2 +merasmus_spawn_fog +merasmus_tp +merasmus_tp_bits +merasmus_tp_flash01 +merasmus_tp_flash02 +merasmus_tp_flash03 +merasmus_tp_smoke +merasmus_tp_warp +merasmus_zap +merasmus_zap_beam01 +merasmus_zap_beam01_BACKUP +merasmus_zap_beam02 +merasmus_zap_beam03 +merasmus_zap_beam_bits +merasmus_zap_flash +rockettrail_airstrike_line +" + +"halloween.pcf_cont1" //halloween.pcf is big enough that it needs a continuation +" +soul_trail +spell_batball_blue +spell_batball_imapct2_red //sic +spell_batball_impact2_blue +spell_batball_impact_blue +spell_batball_impact_red +spell_batball_red +spell_batball_throw_blue +spell_batball_throw_child_blue +spell_batball_throw_child_red +spell_batball_throw_red +spell_batball_trail_blue +spell_batball_trail_red +spell_cast_wheel_blue +spell_cast_wheel_red +spell_cast_wheel_text_blue +spell_cast_wheel_text_red +spell_fireball_small_blue +spell_fireball_small_glow_blue +spell_fireball_small_glow_red +spell_fireball_small_red +spell_fireball_small_red_old +spell_fireball_small_trail_blue +spell_fireball_small_trail_red +spell_fireball_tendril_child_blue +spell_fireball_tendril_child_red +spell_fireball_tendril_flash_blue +spell_fireball_tendril_flash_red +spell_fireball_tendril_parent_blue +spell_fireball_tendril_parent_red +spell_lightningball_center_blue +spell_lightningball_center_red +spell_lightningball_glow2_blue +spell_lightningball_glow2_red +spell_lightningball_glow3_blue +spell_lightningball_glow3_red +spell_lightningball_glow_blue +spell_lightningball_glow_red +spell_lightningball_hit_blue +spell_lightningball_hit_electro_blue +spell_lightningball_hit_electro_red +spell_lightningball_hit_flare_blue +spell_lightningball_hit_flare_red +spell_lightningball_hit_red +spell_lightningball_hit_zap_blue +spell_lightningball_hit_zap_red +spell_lightningball_parent_blue +spell_lightningball_parent_red +spell_lightningball_parent_rope_blue +spell_lightningball_parent_rope_red +spell_lightningball_parent_target_blue +spell_lightningball_parent_target_red +spell_lightningball_sparks_blue +spell_lightningball_sparks_red +spell_overheal_blue +spell_overheal_flash_blue +spell_overheal_flash_red +spell_overheal_red +spell_pumpkin_mirv_bits_blue +spell_pumpkin_mirv_bits_red +spell_pumpkin_mirv_goop_blue +spell_pumpkin_mirv_goop_red +spell_skeleton_bits_green +spell_skeleton_goop_green +spell_teleport_black +spell_teleport_black_red +spell_teleport_blue +spell_teleport_red +spellbook_major_burning +spellbook_major_encircling +spellbook_major_fire +spellbook_major_fire_sparkles +spellbook_major_fog +spellbook_major_tp_bits +spellbook_minor_burning +spellbook_minor_fire +spellbook_minor_fire_sparkles +spellbook_minor_sparks +spellbook_rainbow +spellbook_rainbow_glow +spellbook_rainbow_glow_white +spellbook_rainbow_stars +underworld_gate_zap +underworld_skull_eye_glow +underworld_skull_smoke01 +underworld_skull_zap +underworld_smoke01 +underworld_smoke02 +underworld_smoke03 +underworld_smoke04 +underworld_yellow_steam +" + + + +"halloween2015_unusuals.pcf" +" +eldritch_claw_left_orange +eldritch_claw_left_purple +eldritch_claw_right_orange +eldritch_claw_right_purple +eldritch_embers_orange +eldritch_embers_purple +eldritch_eyes_orange +eldritch_eyes_purple +eldritch_flames_orange +eldritch_flames_purple +eldritch_smoke_orange +eldritch_smoke_purple +unusual_eldritch_flames_orange +unusual_eldritch_flames_purple +unusual_hw_deathbydisco_ball +unusual_hw_deathbydisco_ball_beams +unusual_hw_deathbydisco_ball_glow +unusual_hw_deathbydisco_ball_stars +unusual_hw_deathbydisco_bits +unusual_hw_deathbydisco_parent +unusual_hw_deathbydisco_skeletons +unusual_hw_deathbydisco_stage +unusual_hw_deathbydisco_stage_lights +unusual_mystery_ghosts +unusual_mystery_glow +unusual_mystery_glow2 +unusual_mystery_glow2_green +unusual_mystery_glow_green +unusual_mystery_parent +unusual_mystery_parent_green +unusual_mystery_sparkle +unusual_mystery_sparkle_green +unusual_mystery_trail +unusual_mystery_trail_green +unusual_nether_blue +unusual_nether_pink +unusual_nether_rope_blue +unusual_nether_rope_glow_blue +unusual_nether_rope_glow_pink +unusual_nether_rope_pink +unusual_nether_sparkles2_blue +unusual_nether_sparkles2_pink +unusual_nether_sparkles_blue +unusual_nether_sparkles_pink +" + + + +"halloween2016_unusuals.pcf" +" +Tesla_arc_1 +Tesla_arc_2 +Tesla_arc_3 +Tesla_arc_4 +Tesla_arc_5 +Tesla_arc_6 +unusual_star_green_parent +unusual_star_purple_parent +unusual_star_sparkle2_green +unusual_star_sparkle2_purple +unusual_star_sparkle_green +unusual_star_sparkle_purple +unusual_star_trail_green +unusual_star_trail_purple +unusual_star_trailglow_green +unusual_star_trailglow_purple +unusual_tesla_flash +unusual_universe +unusual_universe_burst +unusual_universe_dust +unusual_universe_neutron +unusual_universe_stars +unusual_universe_swirl +" + + + +"harbor_fx.pcf" //cp_foundry +" +cauldron_embers +cauldron_flamethrower +cauldron_flamethrower_pilot +cauldron_smoke_lit +cauldron_smoke_lit_bottom +harbor_smoke_blackbillow_skybox +harbor_smoke_blackbillowflame_skybox +harbor_smoke_whitebillow_skybox +steam_jet +steam_plume +" + + + +"//impact_fx.pcf" //conflicts with an unused stock source .pcf full of broken sprite renderers - gmod will load that one instead if we try to add this. some of these effects are color1s but i guess it doesn't matter, no point in bloating up the color1 list with stuff we're not going to use. +" +impact_antlion +impact_computer +impact_computer_smoke +impact_concrete +impact_concrete_child_base +impact_concrete_child_puff +impact_concrete_noflecks +impact_dirt +impact_dirt_child_bounce +impact_dirt_child_burst +impact_dirt_child_smoke +impact_dirt_nosmoke +impact_generic_burn +impact_generic_burst +impact_generic_burst_2 +impact_generic_smoke +impact_glass +impact_glass_child_backblast +impact_glass_child_burst +impact_glass_child_smoke +impact_metal +impact_metal_child_base +impact_metal_child_glow +impact_metal_child_glowLarge +impact_metal_child_smoke +impact_wood +impact_wood_child_puff +impact_woodbroken +ricochet_sparks +" + + + +"invasion_ray_gun_fx.pcf" +" +bullet_tracer_raygun_blue +bullet_tracer_raygun_blue_bits +bullet_tracer_raygun_blue_crit +bullet_tracer_raygun_blue_rings +bullet_tracer_raygun_red +bullet_tracer_raygun_red_bits +bullet_tracer_raygun_red_crit +bullet_tracer_raygun_red_rings +muzzle_raygun_blue +muzzle_raygun_red +muzzle_raygun_rings +muzzle_raygun_rings_red +muzzle_raygun_sparks +muzzle_raygun_sparks_red +muzzle_raygun_wave +muzzle_raygun_wave_red +raygun_projectile_blue +raygun_projectile_blue_bits +raygun_projectile_blue_crit +raygun_projectile_blue_crit_glow +raygun_projectile_blue_crit_sparks +raygun_projectile_blue_crit_trail +raygun_projectile_blue_rings +raygun_projectile_blue_trail +raygun_projectile_red +raygun_projectile_red_bits +raygun_projectile_red_crit +raygun_projectile_red_crit_glow +raygun_projectile_red_crit_sparks +raygun_projectile_red_crit_trail +raygun_projectile_red_rings +raygun_projectile_red_trail +" + + + +"invasion_unusuals.pcf" +" +unusual_invasion_abduction +unusual_invasion_abduction_cow +unusual_invasion_abduction_debris +unusual_invasion_abduction_glow +unusual_invasion_abduction_rings +unusual_invasion_atomic +unusual_invasion_atomic_green +unusual_invasion_atomic_green_orbitar_A +unusual_invasion_atomic_green_orbitar_B +unusual_invasion_atomic_green_orbitar_C +unusual_invasion_atomic_green_trail +unusual_invasion_atomic_green_trail_beam_core +unusual_invasion_atomic_green_trail_bits +unusual_invasion_atomic_green_trail_head +unusual_invasion_atomic_orbitar_A +unusual_invasion_atomic_orbitar_B +unusual_invasion_atomic_orbitar_C +unusual_invasion_atomic_orbitar_trail +unusual_invasion_atomic_orbitar_trail_beam +unusual_invasion_atomic_orbitar_trail_beam_core +unusual_invasion_atomic_orbitar_trail_bits +unusual_invasion_atomic_orbitar_trail_head +unusual_invasion_boogaloop +unusual_invasion_boogaloop_2 +unusual_invasion_boogaloop_2_child_A +unusual_invasion_boogaloop_2_child_B +unusual_invasion_boogaloop_2_glow +unusual_invasion_boogaloop_2_largearc +unusual_invasion_boogaloop_2_largearc_trail +unusual_invasion_boogaloop_2_sparks_A +unusual_invasion_boogaloop_2_sparks_B +unusual_invasion_boogaloop_2_trail +unusual_invasion_boogaloop_3 +unusual_invasion_boogaloop_3_child_A +unusual_invasion_boogaloop_3_child_B +unusual_invasion_boogaloop_3_glow +unusual_invasion_boogaloop_3_largearc +unusual_invasion_boogaloop_3_largearc_trail +unusual_invasion_boogaloop_3_sparks_A +unusual_invasion_boogaloop_3_sparks_B +unusual_invasion_boogaloop_3_trail +unusual_invasion_boogaloop_child_A +unusual_invasion_boogaloop_child_B +unusual_invasion_boogaloop_glow +unusual_invasion_boogaloop_largearc +unusual_invasion_boogaloop_largearc_trail +unusual_invasion_boogaloop_sparks_A +unusual_invasion_boogaloop_sparks_B +unusual_invasion_boogaloop_trail +unusual_invasion_codex +unusual_invasion_codex_2 +unusual_invasion_codex_2_detail +unusual_invasion_codex_2_glow +unusual_invasion_codex_detail +unusual_invasion_codex_glow +unusual_invasion_nebula +unusual_invasion_nebula_clouds_A +unusual_invasion_nebula_clouds_B +unusual_invasion_nebula_clouds_C +unusual_invasion_nebula_glow +unusual_invasion_nebula_stars +" + + + +"item_fx.pcf" +" +birthday_player_circling +birthday_player_circling_flame +birthday_player_circling_flame_trail +birthday_player_circling_glow +bonersaw_temp +bread_gloves_crumbs +bread_gloves_droplets +bread_gloves_goop +bread_sapper_bolts +bread_sapper_green_goop +breadjar_groundsplash +breadjar_impact +breadjar_impact_bits +breadjar_impact_cloud +community_sparkle +community_sparkle_rand +duel_blue +duel_blue_burst +duel_red +duel_red_burst +energydrink_cola_splash +energydrink_milk_splash +energydrink_splash +foot_stamp +halloween_boss_foot_fire_customcolor +halloween_boss_foot_impact_customcolor +halloween_boss_victim +halloween_boss_victim_repeat +halloween_ghosts +halloween_gift_balloon01 +halloween_gift_balloon02 +halloween_gift_confetti_colors +halloween_gift_pickup +halloween_jack +halloween_jack_glow +halloween_notes +halloween_pickup_active +halloween_pickup_active_green +halloween_pickup_active_red +halopoint +headphone_notes +headphone_soundwave +healhuff_blu +healhuff_blu_mist +healhuff_red +healhuff_red_mist +healthgained_blu +healthgained_blu_giant +healthgained_blu_large +healthgained_red +healthgained_red_giant +healthgained_red_large +healthlost_blu +healthlost_red +mark_for_death +peejar_drips +peejar_drips_milk +peejar_groundsplash +peejar_groundsplash_milk +peejar_groundsplash_small +peejar_icon +peejar_impact +peejar_impact_bits +peejar_impact_bits_milk +peejar_impact_cloud +peejar_impact_cloud_milk +peejar_impact_cloud_small +peejar_impact_milk +peejar_impact_small +peejar_impacts_bits_small +peejar_trail_blu +peejar_trail_blu_glow +peejar_trail_red +peejar_trail_red_glow +pumpkin_bits +pumpkin_cloud +pumpkin_explode +pumpkin_sparkle +sandwich_cheese +sandwich_fx +sandwich_lettuce +sandwich_meat +speech_mediccall +speech_mediccall_attention +speech_medichurt +superare_balloon //(yes, it's misspelled) +superrare_balloon_b +superrare_balloon_c +superrare_beam1_glow +superrare_beams1 +superrare_beams1_newstyle +superrare_beany_b //(there is no beany a) +superrare_beany_c +superrare_beany_green +superrare_beany_green_b +superrare_beany_hearts +superrare_beany_hearts_b +superrare_beany_target +superrare_beany_tf +superrare_burning1 +superrare_burning2 +superrare_burning_smoke +superrare_circling_childglow +superrare_circling_childglow_pink +superrare_circling_glow +superrare_circling_heart +superrare_circling_peacesign +superrare_circling_skull +superrare_circling_tf +superrare_confetti_green +superrare_confetti_purple +superrare_flies +superrare_ghosts +superrare_ghosts_childglow +superrare_greenenergy +superrare_halo +superrare_halo_b +superrare_orbit +superrare_plasma1 +superrare_plasma2 +superrare_purpleenergy +superrare_sparkles1 +superrare_sparkles2 +superrare_sparkles3 +superrare_stormcloud +superrare_stormcloud_b +superrare_test +tfc_sniper_charge_blue +tfc_sniper_charge_red +turret_shield +turret_shield_b +" + +"item_fx.pcf_cont1" //item_fx.pcf is so huge that it needs multiple continuations to get past the 4095 char limit +" +unusual_aaa_aaa //(flying trophies) +unusual_bats_flaming_bat_green +unusual_bats_flaming_bat_orange +unusual_bats_flaming_bat_purple +unusual_bats_flaming_fire_green +unusual_bats_flaming_fire_orange +unusual_bats_flaming_fire_purple +unusual_bats_flaming_glow_green +unusual_bats_flaming_glow_orange +unusual_bats_flaming_glow_purple +unusual_bats_flaming_proxy_green //("proxy" ones are the actual effects, others are children) +unusual_bats_flaming_proxy_orange +unusual_bats_flaming_proxy_purple +unusual_blizzard +unusual_blizzard_clouds +unusual_blizzard_snow +unusual_bubbles +unusual_bubbles_green +unusual_bubbles_green_fumes +unusual_bubbles_green_goo +unusual_bubbles_green_large +unusual_bubbles_large +unusual_crisp_spotlights +unusual_crisp_spotlights_cones01 +unusual_crisp_spotlights_cones02 +unusual_crisp_spotlights_cones03 +unusual_eotl_frostbite +unusual_eotl_frostbite_glow +unusual_eotl_frostbite_mist +unusual_eotl_frostbite_twinkles +unusual_eotl_orbiting_burning_duck +unusual_eotl_orbiting_burning_duck_embers +unusual_eotl_orbiting_burning_duck_glow +unusual_eotl_orbiting_burning_duck_smoke +unusual_eotl_orbiting_burning_duck_sparks +unusual_eotl_oribiting_burning_duck_parent //sic +unusual_eotl_sunrise +unusual_eotl_sunrise_clouds +unusual_eotl_sunrise_clouds_highlight +unusual_eotl_sunrise_glow +unusual_eotl_sunrise_rays +unusual_eotl_sunrise_rays_2 +unusual_eotl_sunset +unusual_eotl_sunset_clouds +unusual_eotl_sunset_clouds_highlight +unusual_eotl_sunset_glow +unusual_eotl_sunset_rays +unusual_eotl_sunset_rays_2 +unusual_eyes_cloud +unusual_eyes_cloud_green +unusual_eyes_cloud_orange +unusual_eyes_cloud_purple +unusual_eyes_green +unusual_eyes_green_parent +unusual_eyes_orange +unusual_eyes_orange_parent +unusual_eyes_purple +unusual_eyes_purple_parent +unusual_fullmoon_cloudy +unusual_fullmoon_cloudy_green +unusual_fullmoon_cloudy_green_clouds +unusual_fullmoon_cloudy_green_clouds2 +unusual_fullmoon_cloudy_green_glow +unusual_fullmoon_cloudy_secret +unusual_fullmoon_cloudy_secret_eyeglow +unusual_fullmoon_cloudy_secret_skull +unusual_hearts_bubbling +unusual_hearts_bubbling_glow +unusual_meteor_cast_text_green +unusual_meteor_cast_text_orange +unusual_meteor_cast_text_purple +unusual_meteor_cast_wheel_green +unusual_meteor_cast_wheel_orange +unusual_meteor_cast_wheel_purple +unusual_meteor_fireball_small_glow_orange +unusual_meteor_fireball_small_glow_purple +unusual_meteor_fireball_small_green +unusual_meteor_fireball_small_orange +unusual_meteor_fireball_small_purple +unusual_meteor_shower01_green +unusual_meteor_shower01_orange +unusual_meteor_shower01_purple +unusual_meteor_shower02_green +unusual_meteor_shower02_orange +unusual_meteor_shower02_purple +unusual_meteor_shower_parent_green //("parent" ones are the actual effects, others are children) +unusual_meteor_shower_parent_orange +unusual_meteor_shower_parent_purple +unusual_meteor_small_trail_green +unusual_meteor_small_trail_orange +unusual_meteor_small_trail_purple +unusual_orbit_cards_teamcolor_blue +unusual_orbit_cards_teamcolor_red +unusual_orbit_cards_trail_teamcolor_blue +unusual_orbit_cards_trail_teamcolor_red +unusual_orbit_cash +unusual_orbit_cash_trail +unusual_orbit_fire +unusual_orbit_fire_dark +unusual_orbit_fire_dark_flames +unusual_orbit_fire_dark_glow +unusual_orbit_fire_flames +unusual_orbit_fire_glow +unusual_orbit_fullmoon_cloudy +unusual_orbit_fullmoon_cloudy_clouds +unusual_orbit_fullmoon_cloudy_glow +unusual_orbit_jack_flaming +unusual_orbit_jack_flaming_fire +unusual_orbit_jack_flaming_glow +unusual_orbit_jack_flaming_glow2 +unusual_orbit_nutsnbolts +unusual_orbit_nutsnbolts_fall +unusual_orbit_nutsnbolts_large +unusual_orbit_planets +unusual_planets_child01 +unusual_planets_child02 +unusual_planets_child03 +unusual_planets_child04 +unusual_planets_planet01 +unusual_planets_planet02 +unusual_planets_planet03 +unusual_planets_planet04 +" + +"item_fx.pcf_cont2" //see, i wasn't kidding. two continuations for one list. this pcf file is stupid huge. +" +unusual_robot_holo_glow_green +unusual_robot_holo_glow_green_pixels +unusual_robot_holo_glow_orange +unusual_robot_holo_glow_orange_pixels +unusual_robot_orbit_binary +unusual_robot_orbit_binary2 +unusual_robot_orbit_binary_fall +unusual_robot_orbit_binary_fall2 +unusual_robot_orbit_binary_glow +unusual_robot_orbit_binary_glow2 +unusual_robot_orbiting_sparks +unusual_robot_orbiting_sparks2 +unusual_robot_orbiting_sparks_arclightning +unusual_robot_orbiting_sparks_arclightning2 +unusual_robot_orbiting_sparks_glow +unusual_robot_orbiting_sparks_glow2 +unusual_robot_orbiting_sparks_sparks +unusual_robot_orbiting_sparks_sparks2 +unusual_robot_orbiting_sparks_star +unusual_robot_orbiting_sparks_star2 +unusual_robot_radioactive +unusual_robot_radioactive2 +unusual_robot_radioactive_drops +unusual_robot_radioactive_drops2 +unusual_robot_radioactive_glow +unusual_robot_radioactive_glow2 +unusual_robot_time_warp +unusual_robot_time_warp2 +unusual_robot_time_warp_edge +unusual_robot_time_warp_edge2 +unusual_robot_time_warp_rays +unusual_robot_time_warp_spiral +unusual_robot_time_warp_spiral2 +unusual_skull_misty +unusual_skull_misty_eyeglow +unusual_skull_misty_glow +unusual_skull_misty_mist +unusual_skull_misty_mist2 +unusual_skull_misty_mist3 +unusual_skull_misty_skull +unusual_smoking +unusual_smoking_base +unusual_smoking_drift +unusual_souls_green_glow +unusual_souls_green_parent +unusual_souls_green_rising +unusual_souls_purple_glow +unusual_souls_purple_parent +unusual_souls_purple_rising +unusual_sparkler_green +unusual_sparkler_orange +unusual_spellbook_circle_green +unusual_spellbook_circle_purple +unusual_spellbook_fire_green +unusual_spellbook_fire_purple +unusual_spellbook_sparkles_green +unusual_spellbook_sparkles_purple +unusual_spotlights +unusual_spotlights_cones01 +unusual_spotlights_cones02 +unusual_spotlights_cones03 +unusual_spotlights_core +unusual_spotlights_sparkles +unusual_spray_confetti +unusual_spray_confetti_balloons +unusual_steaming +unusual_steaming_base +unusual_steaming_drift +unusual_storm +unusual_storm_blood +unusual_storm_blood_clouds +unusual_storm_blood_clouds_core +unusual_storm_blood_rain +unusual_storm_clouds +unusual_storm_knives +unusual_storm_knives_blood +unusual_storm_knives_clouds +unusual_storm_knives_core +unusual_storm_knives_knives +unusual_storm_lightning +unusual_storm_rain +unusual_storm_spooky +unusual_storm_spooky_clouds01 +unusual_storm_spooky_clouds02 +unusual_storm_spooky_lightning +unusual_storm_spooky_lightning2 +unusual_storm_spooky_lightning3 +unusual_storm_spooky_rain +unusual_tentmonster_bones +unusual_tentmonster_cloud +unusual_tentmonster_purple +unusual_tentmonster_purple_parent +unusual_tentmonster_vomit +unusual_zap_green +unusual_zap_green_glow +unusual_zap_yellow +unusual_zap_yellow_glow +" + + + +"items_demo.pcf" +" +loose_cannon_buildup_glow +loose_cannon_buildup_glow1 +loose_cannon_buildup_glow2 +loose_cannon_buildup_smoke1 +loose_cannon_buildup_smoke2 +loose_cannon_buildup_smoke3 +loose_cannon_buildup_smoke3a +loose_cannon_buildup_smoke3b +loose_cannon_buildup_smoke3c +loose_cannon_flame +loose_cannon_smoke +loose_cannon_smoke3a +loose_cannon_sparks +loose_cannon_sparks2 +loose_cannon_sparks_bang +loose_cannon_sparks_flash +" + + + +"items_engineer.pcf" +" +repair_claw_heal_blue +repair_claw_heal_blue2 +repair_claw_heal_blue3 +repair_claw_heal_red +repair_claw_heal_red2 +repair_claw_heal_red3 +" + + + +"killstreak.pcf" //(not used in-game. final killstreak effects are in class_fx.) +" +killstreak_eyes_base +killstreak_eyes_bits01 +killstreak_eyes_bits02 +killstreak_eyes_bits03 +killstreak_eyes_bits04 +killstreak_eyes_level1 +killstreak_eyes_level2 +killstreak_eyes_level3 +killstreak_eyes_level4 +killstreak_eyes_rage01 +killstreak_eyes_rage02 +killstreak_eyes_rays +killstreak_eyes_rays02 +killstreak_eyes_rings01 +killstreak_eyes_rings02 +killstreak_eyes_rings03 +killstreak_eyes_rings04 +killstreak_eyes_spikes01 +killstreak_eyes_trails01 +killstreak_eyes_trails02 +" + + + +"level_fx.pcf" +" +airlock_dust_blue +airlock_dust_red +airlock_dust_warp +airlock_dust_warp2 +airlock_dust_warp3 +airlock_dust_warp4 +hell_megaheal_blue_shower +hell_megaheal_red_shower +lava_01_goop +lava_01_smoke +lava_02_goop +lava_02_smoke +lava_03_smoke +lava_fire +lava_fire_02 +lava_fire_03 +lava_fire_04 +lava_fire_05 +lava_fireball +lava_fireball_01 +lava_fireball_02 +lava_fireball_03 +lava_fireball_trail +lava_fountain +lava_playertouch +lava_playertouch_embers +lava_playertouch_fire +lava_smoke_01 +lava_smoke_02 +m_brazier_flame +m_brazier_flame_smoke +m_portcullis_slam +m_torchflame +m_torchflame_smoke +mannpower_imbalance_blue +mannpower_imbalance_blue_beam +mannpower_imbalance_blue_fleks +mannpower_imbalance_blue_lightning +mannpower_imbalance_red +mannpower_imbalance_red_beam +mannpower_imbalance_red_fleks +mannpower_imbalance_red_lightning +mannworks_smoke +moon_drill_debris_TEST +moon_drill_dust +moon_drill_dust_base +moon_drill_rock_debris +moon_miasma_purple01 +moon_misama_fire_purple01 //yes, the first one is spelled 'miasma', and the next two are 'misama' +moon_misama_goop_purple01 +nucleus_core_bits1 +nucleus_core_steady +outerspace_belt_blue +outerspace_belt_glow_blue +outerspace_belt_parent +outerspace_belt_trail_blue +outerspace_dust01 +outerspace_dust_center01 +outerspace_dust_center02 +outerspace_nebula01 +outerspace_stars01 +outerspace_stars02 +outerspace_stripes01 +outerspace_sun01 +" + + + +"medicgun_attrib.pcf" +" +drain_effect +medicgun_beam_attrib_drips +medicgun_beam_attrib_healing +medicgun_beam_attrib_muzzle +medicgun_beam_attrib_overheal +medicgun_beam_attrib_overheal_blue +medicgun_beam_attrib_overheal_red +medicgun_beam_attrib_shards +overhealedplayer_blue_pluses +overhealedplayer_red_pluses +" + + + +"medicgun_beam.pcf" +" +dispenser_beam_blue_pluses +dispenser_beam_blue_trail +dispenser_beam_red_pluses +dispenser_beam_red_trail +dispenser_heal_blue +dispenser_heal_red +medicgun_beam_blue +medicgun_beam_blue_drips +medicgun_beam_blue_healing +medicgun_beam_blue_invulnbright +medicgun_beam_blue_invun +medicgun_beam_blue_invunglow +medicgun_beam_blue_marker +medicgun_beam_blue_muzzle +medicgun_beam_blue_pluses +medicgun_beam_blue_targeted +medicgun_beam_blue_trail +medicgun_beam_blue_trail_stage1 +medicgun_beam_blue_trail_stage2 +medicgun_beam_blue_trail_stage3 +medicgun_beam_machinery +medicgun_beam_machinery_drips +medicgun_beam_machinery_healing +medicgun_beam_machinery_muzzle +medicgun_beam_machinery_pluses +medicgun_beam_machinery_stage1 +medicgun_beam_machinery_stage2 +medicgun_beam_machinery_stage3 +medicgun_beam_red +medicgun_beam_red_drips +medicgun_beam_red_healing +medicgun_beam_red_invulnbright +medicgun_beam_red_invun +medicgun_beam_red_invunglow +medicgun_beam_red_marker +medicgun_beam_red_muzzle +medicgun_beam_red_pluses +medicgun_beam_red_targeted +medicgun_beam_red_trail +medicgun_beam_red_trail_stage1 +medicgun_beam_red_trail_stage2 +medicgun_beam_red_trail_stage3 +medicgun_invulnstatus_fullcharge_blue +medicgun_invulnstatus_fullcharge_red +vaccinator_blue_beam1 +vaccinator_blue_beam1_icons +vaccinator_blue_beam2 +vaccinator_blue_beam2_icons +vaccinator_blue_beam3 +vaccinator_blue_beam3_icons +vaccinator_blue_buff1 +vaccinator_blue_buff1_burst +vaccinator_blue_buff2 +vaccinator_blue_buff2_burst +vaccinator_blue_buff3 +vaccinator_blue_buff3_burst +vaccinator_red_beam1 +vaccinator_red_beam1_icons +vaccinator_red_beam2 +vaccinator_red_beam2_icons +vaccinator_red_beam3 +vaccinator_red_beam3_icons +vaccinator_red_buff1 +vaccinator_red_buff1_burst +vaccinator_red_buff2 +vaccinator_red_buff2_burst +vaccinator_red_buff3 +vaccinator_red_buff3_burst +" + + + +"muzzle_flash.pcf" +" +muzzle_bignasty +muzzle_bignasty_flash +muzzle_bignasty_sparks +muzzle_grenadelauncher +muzzle_grenadelauncher_core +muzzle_grenadelauncher_embers +muzzle_minigun +muzzle_minigun_constant +muzzle_minigun_constant_core +muzzle_minigun_constant_flare +muzzle_minigun_constant_smoke +muzzle_minigun_constant_sparks +muzzle_minigun_constant_starflash +muzzle_minigun_core +muzzle_minigun_flare +muzzle_minigun_smoke +muzzle_minigun_sparks +muzzle_minigun_starflash01 +muzzle_pipelauncher +muzzle_pistol +muzzle_pistol_smoke +muzzle_pistol_sparks +muzzle_revolver +muzzle_scattergun +muzzle_scattergun_flash +muzzle_sentry +muzzle_sentry2 +muzzle_shotgun +muzzle_shotgun_flash +muzzle_shotgun_smoke +muzzle_shotgun_sparks +muzzle_smg +muzzle_smg_sparks +muzzle_sniperrifle +muzzle_syringe +" + + + +"mvm.pcf" +" +bot_death +bot_death_bolts +bot_death_sparks +bot_eye_glow +bot_eye_halo +bot_impact_heavy +bot_impact_heavy_elec +bot_impact_heavy_elec_glow +bot_impact_heavy_elec_zap +bot_impact_heavy_extrabits +bot_impact_heavy_sparks +bot_impact_light +bot_impact_light_elec +bot_impact_light_elec_glow +bot_impact_light_extrabits +bot_impact_light_sparks +bot_radio_waves +bot_smoking +flare_glow +flare_sparks +mini_firework_flare +mini_fireworks +mvm_cash_embers +mvm_cash_embers_red +mvm_cash_explosion +mvm_cash_explosion_embers +mvm_cash_explosion_smoke +mvm_cash_smoke +mvm_emergency_light_flash +mvm_emergency_light_flash_yellow +mvm_emergency_light_glow_yellow +mvm_emergencylight_glow +mvm_hatch_destroy +mvm_hatch_destroy_burn +mvm_hatch_destroy_embers +mvm_hatch_destroy_smoke +mvm_hatch_destroy_smolder +mvm_hatch_destroy_smolderembers +mvm_item_godrays_glow +mvm_item_godrays_glow2 +mvm_levelup1 +mvm_levelup2 +mvm_levelup3 +mvm_loot_coreflash +mvm_loot_debris +mvm_loot_dustup +mvm_loot_dustup2 +mvm_loot_explosion +mvm_loot_flash1 +mvm_loot_flashup +mvm_loot_floatember +mvm_loot_flyember +mvm_loot_smoke +mvm_path_marker +mvm_pow_bam +mvm_pow_banana +mvm_pow_boing +mvm_pow_boot +mvm_pow_caber +mvm_pow_crack +mvm_pow_crash +mvm_pow_crit +mvm_pow_fireworks_flashup +mvm_pow_fireworks_glow +mvm_pow_fireworks_sparkler +mvm_pow_fireworks_sparkler2 +mvm_pow_fireworks_sparkler3 +mvm_pow_fuse_movement +mvm_pow_fuse_smoke +mvm_pow_fuse_sparks +mvm_pow_gold_seq +mvm_pow_gold_seq_boot +mvm_pow_gold_seq_crash +mvm_pow_gold_seq_crit +mvm_pow_gold_seq_crit2 +mvm_pow_gold_seq_firework +mvm_pow_gold_seq_firework2 +mvm_pow_gold_seq_firework3 +mvm_pow_gold_seq_firework_mid +mvm_pow_gold_seq_firework_trail +mvm_pow_gold_seq_firework_trail2 +mvm_pow_gold_seq_firework_trail3 +mvm_pow_gold_seq_trail_mid +mvm_pow_gold_seq_wood +mvm_pow_gold_seq_wood2 +mvm_pow_gold_seq_wood3 +mvm_pow_gold_seq_wood3mid +mvm_pow_loot +mvm_pow_mmph +mvm_pow_poof +mvm_pow_pow +mvm_pow_punch +mvm_pow_smash +mvm_pow_test +mvm_shield_impact +mvm_shield_impact_flashup +mvm_soldier_shockwave +mvm_soldier_shockwave2b +mvm_soldier_shockwave2c +mvm_soldier_shockwave2d +mvm_soldier_shockwave_stun +mvm_soldier_shockwave_stun2 +mvm_soldier_shockwave_stun3 +mvm_soldier_shockwave_stun4 +mvm_tank_destroy +mvm_tank_destroy_bloom +mvm_tank_destroy_burn +mvm_tank_destroy_embers +mvm_tank_destroy_smoke +mvm_tank_destroy_smokefront +mvm_tank_destroy_smolder +mvm_tank_destroy_smolderembers +mvm_wood_boards_destroy +mvm_wood_boards_destroy_2BR +mvm_wood_boards_destroy_MW +mvm_wood_boards_splinters +mvm_wood_boards_splinters_2BR +mvm_wood_boards_splinters_MW +smoke_marker +" + + + +"nailtrails.pcf" +" +nailtrails_medic_blue +nailtrails_medic_blue_crit +nailtrails_medic_red +nailtrails_medic_red_crit +" + + + +"nemesis.pcf" +" +particle_nemesis_blue +particle_nemesis_burst_blue +particle_nemesis_burst_red +particle_nemesis_red +" + + + +"npc_fx.pcf" //(unused effects for... sentry busters, maybe?) +" +charge_up +charge_up_glow +charge_up_release +npc_boss_bomb_alert +npc_boss_bomb_alert_plate +npc_boss_bomb_aoewarn +npc_boss_bomb_shadow +" + + + +"passtime.pcf" +" +passtime_air_blast +" + + + +"passtime_beam.pcf" +" +passtime_beam +passtime_beam_drips +passtime_beam_healing +passtime_beam_muzzle +passtime_beam_pluses +passtime_beam_trail +passtime_beam_trail_stage1 +passtime_beam_trail_stage2 +passtime_beam_trail_stage3 +" + + + +"passtime_tv_projection.pcf" +" +passtime_tv_fleks +passtime_tv_glow +passtime_tv_projection +passtime_tv_ring +" + + + +"player_recent_teleport.pcf" +" +bot_drips_cheap +bot_recent_teleport_blue +player_drips_blue +player_dripsred //(yes, only one has an underscore) +player_glowblue +player_glowred +player_recent_teleport_blue +player_recent_teleport_red +player_sparkles_blue +player_sparkles_red +" + + + +"powerups.pcf" +" +plague_healthkit_pickup +plague_transmission +powerup_icon_agility +powerup_icon_agility_blue +powerup_icon_agility_red +powerup_icon_haste +powerup_icon_haste_blue +powerup_icon_haste_red +powerup_icon_king +powerup_icon_king_blue +powerup_icon_king_red +powerup_icon_knockout +powerup_icon_knockout_blue +powerup_icon_knockout_red +powerup_icon_plague +powerup_icon_plague_blue +powerup_icon_plague_red +powerup_icon_precision +powerup_icon_precision_blue +powerup_icon_precision_red +powerup_icon_reflect +powerup_icon_reflect_blue +powerup_icon_reflect_red +powerup_icon_regen +powerup_icon_regen_blue +powerup_icon_regen_red +powerup_icon_resist +powerup_icon_resist_blue +powerup_icon_resist_red +powerup_icon_strength +powerup_icon_strength_blue +powerup_icon_strength_red +powerup_icon_supernova +powerup_icon_supernova_blue +powerup_icon_supernova_red +powerup_icon_vampire +powerup_icon_vampire_blue +powerup_icon_vampire_red +powerup_king_blue +powerup_king_both +powerup_king_red +powerup_plague_carrier +powerup_supernova_explode_blue +powerup_supernova_explode_blue_spikes +powerup_supernova_explode_blue_spiral +powerup_supernova_explode_red +powerup_supernova_explode_red_spikes +powerup_supernova_explode_red_spiral +powerup_supernova_ready +powerup_supernova_ready_old +powerup_supernova_strike_blue +powerup_supernova_strike_red +" + + + +"rain_custom.pcf" +" +env_rain_128 +env_rain_128_streaks +env_rain_256 +env_rain_256_mist +env_rain_256_streaks +env_rain_256x768 +env_rain_512 +env_rain_512_mist +env_rain_512_streaks +env_rain_512x1792 +env_rain_512x768 +test +" + + + +"rankup.pcf" +" +badgepress_base +badgepress_dustup +badgepress_glitter +rankdown_base +rankdown_impact_metal +rankdown_impact_metal_child_glow +rankdown_impact_metal_child_glowLarge +rankdown_ricochet_sparks +rankdown_smoke +rankup_base +rankup_dustup +rankup_dustup_flash +rankup_floating_embers +rankup_glitter +rankup_glow +rankup_smoke +" + + + +"rocketbackblast.pcf" +" +rocketbackblast +rocketbackblastsparks +" + + + +"rocketjumptrail.pcf" +" +deflect_fx +deflect_temp +doublejump_puff +doublejump_puff_alt +doublejump_smoke +doublejump_smoke_alt +doublejump_trail +doublejump_trail_alt +rocketjump_flame +rocketjump_flash +rocketjump_smoke +" + + + +"rockettrail.pcf" +" +bullet_distortion_trail +bullet_distortion_trail_tracer +critical_rocket_blue +critical_rocket_bluesparks +critical_rocket_red +critical_rocket_redsparks +flaregun_crit_blue +flaregun_crit_red +flaregun_energyfield_blue +flaregun_energyfield_red +flaregun_sparkles_blue +flaregun_sparkles_red +flaregun_trail_blue +flaregun_trail_crit_blue +flaregun_trail_crit_red +flaregun_trail_red +halloween_rockettrail +healshot_trail_blue +healshot_trail_red +pyrovision_flaregun_trail_blue +pyrovision_flaregun_trail_crit_blue +pyrovision_flaregun_trail_crit_red +pyrovision_flaregun_trail_red +pyrovision_rockettrail +pyrovision_scorchshot_trail_blue +pyrovision_scorchshot_trail_crit_blue +pyrovision_scorchshot_trail_crit_red +pyrovision_scorchshot_trail_red +rockettrail +rockettrail_! +rockettrail_airstrike +rockettrail_airstrike_line +rockettrail_bubbles +rockettrail_burst +rockettrail_burst_airstrike +rockettrail_fire +rockettrail_fire_airstrike +rockettrail_RocketJumper +rockettrail_underwater +rockettrail_waterbubbles +scorchshot_trail_blue +scorchshot_trail_crit_blue +scorchshot_trail_crit_red +scorchshot_trail_red +sentry_rocket +sentry_rocket_burst +sentry_rocket_fire +tranq_distortion_trail +tranq_tracer_teamcolor_blue +tranq_tracer_teamcolor_red +" + + + +"rps.pcf" +" +particle_rps_burst_blue_left +particle_rps_burst_blue_right +particle_rps_burst_red_left +particle_rps_burst_red_right +particle_rps_paper_left +particle_rps_paper_right +particle_rps_paper_win_left +particle_rps_paper_win_right +particle_rps_rock_left +particle_rps_rock_right +particle_rps_rock_win_left +particle_rps_rock_win_right +particle_rps_scissors_left +particle_rps_scissors_right +particle_rps_scissors_win_left +particle_rps_scissors_win_right +rps_bg +rps_bg_blue +rps_bg_red +rps_burst_blue +rps_burst_red +rps_burst_repeating_child +rps_paper +rps_paper_blue +rps_paper_blue_win +rps_paper_red +rps_paper_red_win +rps_rock +rps_rock_blue +rps_rock_blue_win +rps_rock_red +rps_rock_red_win +rps_scissors +rps_scissors_blue +rps_scissors_blue_win +rps_scissors_red +rps_scissors_red_win +rps_scout_win +rps_win_flash +rps_win_flash_bg +rps_win_flash_blue +rps_win_flash_core +rps_win_flash_glow +rps_win_flash_red +rps_win_sparks +" + + + +"scary_ghost.pcf" +" +ghost_appearation +ghost_firepit +ghost_firepit_firebits +ghost_firepit_plate +ghost_firepit_wisps +ghost_flash +ghost_glow +ghost_glow_red +ghost_smoke +ghost_sparkle +ghost_sparkle_red +halloween_boss_axe_hit_sparks +halloween_boss_axe_hit_world +halloween_boss_death +halloween_boss_death_bits +halloween_boss_death_cloud +halloween_boss_death_floatybits +halloween_boss_eye_glow +halloween_boss_foot_fire +halloween_boss_foot_fire_customcolor +halloween_boss_foot_impact +halloween_boss_foot_impact_customcolor +halloween_boss_injured +halloween_boss_pumpkin_chunks +halloween_boss_shape_glow +halloween_boss_summon +halloween_boss_trailing_goop +halloween_ghost_flash +halloween_ghost_smoke +" + + + +"shellejection.pcf" +" +eject_minigunbrass +" + + + +"smoke_blackbillow.pcf" +" +smoke_blackbillow +smoke_blackbillow_dustbowl_skybox +smoke_blackbillow_gravelpit_skybox +smoke_blackbillow_hydro_skybox +smoke_blackbillow_skybox +smoke_blackbillowflame +smoke_blackbillowflame2_gravelpit_skybox +smoke_blackbillowflame_dustbowl_skybox +smoke_blackbillowflame_gravelpit_skybox +smoke_blackbillowflame_skybox +smoke_campfire +smoke_campfireflame +smoke_pipeline_001 +smoke_pipeline_fires +smoke_pipeline_glowsmoke +smoke_rocket_steam +smoke_rocket_steam_dustbowl +smoke_steam +smoke_train +smoke_whitebillow +smoke_whitebillow_skybox +smoke_whitebillow_steamship_skybox +smoke_wispy_black_2fort +smoke_wispy_black_dustbowl +smoke_wispy_black_granary +smoke_wispy_black_gravelpit +smoke_wispy_black_skybox +" + + + +"smoke_blackbillow_hoodoo.pcf" +" +smoke_wispy_black_hoodoo +" + + + +"soldierbuff.pcf" +" +soldierbuff_blue_buffed +soldierbuff_blue_soldier +soldierbuff_blue_spikes +soldierbuff_blue_spiral +soldierbuff_blue_volume +soldierbuff_mvm +soldierbuff_mvm_spiral +soldierbuff_mvm_spiral_glow +soldierbuff_mvm_volume +soldierbuff_red_buffed +soldierbuff_red_soldier +soldierbuff_red_spikes +soldierbuff_red_spiral +soldierbuff_red_volume +speed_boost_trail +" + + + +"sparks.pcf" +" +candle_light1 +candle_light2 +powercore_alert_blue +powercore_alert_red +powercore_embers_blue +powercore_embers_red +powercore_flash2_blue +powercore_flash2_red +powercore_flash_blue +powercore_flash_red +rd_bot_impact_glow +rd_bot_impact_sparks +rd_bot_impact_sparks2 +sapper_sentry1_fx +sapper_sentry1_sparks1 +sapper_sentry1_sparks2 +spark_electric01 +spark_electric01_embers +sparks_powerline_blue +sparks_powerline_blue_glow +sparks_powerline_red +sparks_powerline_red_glow +" + + + +"speechbubbles.pcf" +" +speech_mediccall +speech_mediccall_attention +speech_mediccall_auto +speech_medichurt +speech_mission +speech_mission_attention +speech_revivecall +speech_revivecall_hard +speech_revivecall_medium +speech_taunt_all +speech_taunt_all_repeat +speech_taunt_blue +speech_taunt_blue_repeat +speech_taunt_red +speech_taunt_red_repeat +speech_typing +speech_voice +" + + + +"stamp_spin.pcf" +" +Stamp_spin //this one is complicated, looks like the second controlpoint scales how many particles spawn when it moves, but values past 0.85 don't do anything +" + + + +"stickybomb.pcf" +" +critical_grenade_blue +critical_grenade_blue_extra +critical_grenade_blue_glow +critical_grenade_red +critical_grenade_red_extra +critical_grenade_red_glow +critical_pipe_blue +critical_pipe_red +pipebomb_timer_blue +pipebomb_timer_red +pipebombtrail_blue +pipebombtrail_red +stickybomb_pulse_blue +stickybomb_pulse_red +stickybombtrail_blue +stickybombtrail_red +stunballtrail_blue +stunballtrail_blue_crit +stunballtrail_red +stunballtrail_red_crit +" + + + +"stormfront.pcf" +" +env_snow_stormfront_001 +env_snow_stormfront_mist +" + + + +"taunt_fx.pcf" +" +eotl_pyro_duck_splash +eotl_pyro_duck_splash2 +eotl_pyro_duck_splash3 +eotl_pyro_enter_splash +eotl_pyro_enter_splash2 +eotl_pyro_gas_pour +eotl_pyro_gas_pour2 +eotl_pyro_gas_pour3 +eotl_pyro_gas_splash +eotl_pyro_lighter_flame +eotl_pyro_pool_explosion +eotl_pyro_pool_explosion_flash +eotl_pyro_pool_explosion_shroom1_base +eotl_pyro_pool_explosion_shroom2_collumn +eotl_pyro_pool_explosion_shroom3_ring +eotl_pyro_pool_explosion_shroom4_base +eotl_pyro_pool_explosion_shroom_glow +eotl_pyro_pool_explosion_shroom_ring +eotl_pyro_pool_explosion_streaks +highfive_blue +highfive_blue_border +highfive_blue_flash +highfive_blue_old +highfive_flash +highfive_red +highfive_red_border +highfive_red_flash +highfive_red_old +tada_core +tada_default +tada_glow +tada_glow_core +tada_glow_largecones +tada_glow_smallcones +tada_sparklebits +tada_sparkleburst +taunt_conga_confetti01 +taunt_conga_confetti02 +taunt_conga_fireworks01 +taunt_conga_fireworks02 +taunt_conga_fireworks_flash +taunt_conga_string01 +taunt_conga_string02 +taunt_demo_beer_bubbles +taunt_demo_beer_spray +taunt_demo_beer_spray2 +taunt_demo_beer_spray3 +taunt_demo_flip_bottle +taunt_demo_flip_bottle_trail +taunt_demo_nuke_brew +taunt_demo_nuke_explosion +taunt_demo_nuke_explosion_flash +taunt_demo_nuke_explosion_streaks +taunt_demo_nuke_lighter +taunt_demo_nuke_powder +taunt_demo_nuke_shroom1_base +taunt_demo_nuke_shroom2_collumn +taunt_demo_nuke_shroom3_ring +taunt_demo_nuke_shroom4_base +taunt_demo_nuke_shroom_glow +taunt_demo_nuke_shroom_ring +taunt_demo_nuke_shroomcloud //(_shroomcloud is the entire mushroom cloud effect, the rest of the _shroom effects are children) +taunt_demo_nuke_smokelinger01 +taunt_demo_nuke_smokelinger02 +taunt_demo_nuke_vapors +taunt_engineer_sparks +taunt_flip_land +taunt_flip_land_blue +taunt_flip_land_dust +taunt_flip_land_dust2 +taunt_flip_land_red +taunt_flip_land_ring +taunt_flip_land_stars_blue +taunt_flip_land_stars_red +taunt_flip_trail +taunt_headbutt_impact +taunt_headbutt_impact_blood +taunt_headbutt_impact_blood2 +taunt_headbutt_impact_blood3 +taunt_headbutt_impact_refract +taunt_headbutt_impact_ring +taunt_headbutt_impact_stars +taunt_heavy_flip_land +taunt_heavy_flip_land_debris +taunt_heavy_flip_land_dust +taunt_heavy_flip_trail +taunt_pyro_balloon +taunt_pyro_balloon_explosion +taunt_pyro_balloon_explosion_flame +taunt_pyro_balloon_explosion_flash +taunt_pyro_balloon_explosion_smoke +taunt_pyro_balloon_explosion_stars +taunt_pyro_balloon_explosion_vision +taunt_pyro_balloon_glow +taunt_pyro_balloon_rainbow +taunt_pyro_balloon_startrail +taunt_pyro_balloon_vision +taunt_pyro_flip_land +taunt_pyro_flip_land_rof_expand +taunt_pyro_flip_smoke +taunt_pyro_flip_trail +taunt_pyro_flip_trail_backup +taunt_pyro_headbutt_flame +taunt_pyro_lighter +taunt_scout_bat_trail +taunt_soldier_coffee_beans +taunt_soldier_coffee_beans_fall +taunt_soldier_coffee_beans_ground +taunt_soldier_coffee_beans_ground_backup +taunt_soldier_coffee_cup +taunt_soldier_coffee_splash +taunt_soldier_coffee_steam +taunt_soldier_coffee_warp +taunt_spy_cash +taunt_spy_flip_dirt +taunt_spy_flip_dirtbits +taunt_spy_headbutt_cig_burn +taunt_spy_headbutt_cig_smoke +" + +"taunt_fx.pcf_cont1" +" +utaunt_beams_glow_yellow +utaunt_beams_sparks_yellow +utaunt_beams_yellow //(this is the effect) +utaunt_cash_base +utaunt_cash_confetti //(this is the effect) +utaunt_cash_trail2 +utaunt_disco_ball +utaunt_disco_beams3 +utaunt_disco_beams_dir +utaunt_disco_dots +utaunt_disco_party //(this is the effect) +utaunt_disco_stars +utaunt_firework_blue_launcher +utaunt_firework_blue_launcher2 +utaunt_firework_blue_launcher3 +utaunt_firework_blue_shockwave +utaunt_firework_blue_shockwave2 +utaunt_firework_blue_shockwave3 +utaunt_firework_blue_sparkler +utaunt_firework_blue_sparkler2 +utaunt_firework_blue_sparkler3 +utaunt_firework_blue_stars +utaunt_firework_blue_stars2 +utaunt_firework_blue_stars3 +utaunt_firework_blue_trail +utaunt_firework_blue_trail2 +utaunt_firework_blue_trail3 +utaunt_firework_launcher_glow_1000ms +utaunt_firework_launcher_glow_650ms +utaunt_firework_launcher_glow_850ms +utaunt_firework_launcher_ground +utaunt_firework_launcher_sparks_1000ms +utaunt_firework_launcher_sparks_650ms +utaunt_firework_launcher_sparks_850ms +utaunt_firework_launcher_trail_1000ms +utaunt_firework_launcher_trail_650ms +utaunt_firework_launcher_trail_850ms +utaunt_firework_red_launcher +utaunt_firework_red_launcher2 +utaunt_firework_red_launcher3 +utaunt_firework_red_shockwave +utaunt_firework_red_shockwave2 +utaunt_firework_red_shockwave3 +utaunt_firework_red_sparkler +utaunt_firework_red_sparkler2 +utaunt_firework_red_sparkler3 +utaunt_firework_red_stars +utaunt_firework_red_stars2 +utaunt_firework_red_stars3 +utaunt_firework_red_trail +utaunt_firework_red_trail2 +utaunt_firework_red_trail3 +utaunt_firework_teamcolor_blue //(blue effect) +utaunt_firework_teamcolor_red //(red effect) +utaunt_headless //(this is the effect) +utaunt_headless_base +utaunt_headless_fire +utaunt_headless_fire_bits +utaunt_headless_fire_embers +utaunt_headless_glow +utaunt_headless_smoke +utaunt_headless_wisps +utaunt_headless_wisps_glow +utaunt_hearts_beams +utaunt_hearts_bubbling +utaunt_hearts_bubbling_dots +utaunt_hearts_bubbling_glow +utaunt_hearts_glow_parent //(this is the effect) +utaunt_hellpit_base +utaunt_hellpit_batglow +utaunt_hellpit_bats +utaunt_hellpit_battrail +utaunt_hellpit_firering +utaunt_hellpit_glow +utaunt_hellpit_middlebase +utaunt_hellpit_mist +utaunt_hellpit_parent //(this is the effect) +utaunt_hellpit_skulls +utaunt_hellpit_smokebase +utaunt_hellswirl //(this is the effect) +utaunt_hellswirl_flames +utaunt_hellswirl_smoke +utaunt_hellswirl_smoke_land +utaunt_hellswirl_wheel +utaunt_lightning_bolt +utaunt_lightning_bolt2 +utaunt_lightning_fingers +utaunt_lightning_flash +utaunt_lightning_flash2 +utaunt_lightning_hot_ground +utaunt_lightning_hot_ground2 +utaunt_lightning_impact_electric +utaunt_lightning_impact_shockwave +utaunt_lightning_impact_sparks +utaunt_lightning_parent //(this is the effect) +utaunt_merasmus //(this is the effect) +utaunt_merasmus_base +utaunt_merasmus_fire +utaunt_merasmus_fire_bits +utaunt_merasmus_fire_embers +utaunt_merasmus_glow +utaunt_merasmus_smoke +utaunt_merasmus_wisps +utaunt_merasmus_wisps_glow +utaunt_meteor_drips +utaunt_meteor_licks +utaunt_meteor_parent //(this is the effect) +utaunt_meteor_rotate01 +utaunt_meteor_rotate02 +utaunt_meteor_small +utaunt_meteor_small_glow +utaunt_meteor_trail +utaunt_souls_black_base +utaunt_souls_green_base +utaunt_souls_green_glow +utaunt_souls_green_parent +utaunt_souls_green_rising +utaunt_souls_purple_base +utaunt_souls_purple_glow +utaunt_souls_purple_parent +utaunt_souls_purple_rising +utaunt_tornado_cow +utaunt_tornado_debris +utaunt_tornado_funnel2 +utaunt_tornado_funnel3_black +utaunt_tornado_funnel3_white +utaunt_tornado_funnel_black +utaunt_tornado_funnel_white +utaunt_tornado_ground +utaunt_tornado_oscillate_black +utaunt_tornado_oscillate_white +utaunt_tornado_parent_black //(black tornado effect) +utaunt_tornado_parent_white //(white tornado effect) +utaunt_tornado_twist_black +utaunt_tornado_twist_white +utnaunt_souls_white_base //sic +" + + + +"teleport_status.pcf" +" +teleporter_arms_circle_blue +teleporter_arms_circle_blue_blink +teleporter_arms_circle_red +teleporter_arms_circle_red_blink +teleporter_blue_charged +teleporter_blue_charged_hotplate +teleporter_blue_charged_level1 +teleporter_blue_charged_level2 +teleporter_blue_charged_level3 +teleporter_blue_charged_wisps +teleporter_blue_entrance +teleporter_blue_entrance_disc +teleporter_blue_entrance_level1 +teleporter_blue_entrance_level2 +teleporter_blue_entrance_level3 +teleporter_blue_exit +teleporter_blue_exit_level1 +teleporter_blue_exit_level2 +teleporter_blue_exit_level3 +teleporter_blue_floorglow +teleporter_blue_wisps_level2 +teleporter_blue_wisps_level3 +teleporter_red_charged +teleporter_red_charged_hotplate +teleporter_red_charged_level1 +teleporter_red_charged_level2 +teleporter_red_charged_level3 +teleporter_red_charged_wisps +teleporter_red_entrance +teleporter_red_entrance_disc +teleporter_red_entrance_level1 +teleporter_red_entrance_level2 +teleporter_red_entrance_level3 +teleporter_red_exit +teleporter_red_exit_level1 +teleporter_red_exit_level2 +teleporter_red_exit_level3 +teleporter_red_floorglow +teleporter_red_wisps_level2 +teleporter_red_wisps_level3 +" + + + +"teleported_fx.pcf" +" +teleported_blue +teleported_flash +teleported_mvm_bot +teleported_mvm_bot_bits +teleported_mvm_bot_clouds1 +teleported_mvm_bot_clouds2 +teleported_mvm_bot_core +teleported_mvm_bot_dirt +teleported_mvm_bot_dust +teleported_mvm_bot_flash +teleported_mvm_bot_glowdust +teleported_mvm_bot_groundflash +teleported_mvm_bot_outerbeam +teleported_mvm_bot_rings +teleported_mvm_bot_rings2 +teleported_mvm_bot_rings3 +teleported_mvm_bot_skyflash +teleported_mvm_bot_skyglow +teleported_mvm_bot_trails +teleported_red +teleportedin_blue +teleportedin_red +teleporter_mvm_bot_persist +teleporter_mvm_bot_persist_bits +teleporter_mvm_bot_persist_clouds1 +teleporter_mvm_bot_persist_clouds2 +teleporter_mvm_bot_persist_core +teleporter_mvm_bot_persist_dirt +teleporter_mvm_bot_persist_ring01 +teleporter_mvm_bot_persist_skyglow +teleporter_mvm_bot_ring +" + + + +"training.pcf" +" +coin_blue +coin_large_blue +finishline_confetti +target_break +target_break_child_puff +target_break_initial_dust +tr_bday_balloon01 +tr_bday_balloon02 +tr_bday_confetti_colors +tr_waterfall_bottomsplash +waterfall_bottom +" + + + +"urban_fx.pcf" +" +smoke_urbantower_skybox +" + + + +"vgui_menu_particles.pcf" +" +crate_drop +crate_drop_debris +crate_drop_dust +envelope_dust_cloud +envelope_smoke_puff +envelope_sparks +envelope_trail +versus_door_dust_down +versus_door_dust_up +versus_door_slam +versus_door_sparks +versus_door_sparks_floaty +versus_door_sparksB +" + + + +"water.pcf" +" +env_rain_001 +env_rain_001_mist +env_rain_001_mist_256 +env_rain_001_streaks +env_rain_001_streaks_256 +env_rain_002_256 +env_rain_gutterdrip +env_rain_guttersplash +env_rain_ripples +env_rain_splashring +env_rain_splashripple +env_snow_light_001 +snow_steppuff01 +snow_steppuff_mist +water_bulletsplash01 +water_bulletsplash01_cheap +water_bulletsplash01_minigun +water_burning_steam +water_objectwake +water_playerdive +water_playerdive_bubbles +water_playeremerge +water_playerwake_moving +water_playerwake_static +water_splash01 +water_splash01_bullet +water_splash01_cluster +water_splash01_column +water_splash01_droplets +water_splash01_playerripple +water_splash01_ripple +waterfall_bottommist +waterfall_bottomsplash +waterfall_bottomwaves +waterfall_mist +waterfall_rocksplash +waterfall_rocksplash_2 +waterfall_sides +" + + + +"weapon_unusual_cool.pcf" +" +//_unusual_parent_flamethrower //these effects have the exact same names in all the separate weapon_unusual_X.pcf files, so they override each other +//_unusual_parent_medigun +//_unusual_parent_minigun +//_unusual_parent_pistol +//_unusual_parent_revolver +//_unusual_parent_rocketlauncher +//_unusual_parent_scattergun +//_unusual_parent_shotgun +//_unusual_parent_smg +//_unusual_parent_sniperrifle +//_unusual_parent_stickylauncher +weapon_unusual_cool_base +weapon_unusual_cool_base_1 +weapon_unusual_cool_base_2 +weapon_unusual_cool_base_barrel +weapon_unusual_cool_base_icecubes +weapon_unusual_cool_base_snowflakes +weapon_unusual_cool_flamethrower +weapon_unusual_cool_flamethrower_1 +weapon_unusual_cool_flamethrower_2 +weapon_unusual_cool_flamethrower_barrel +weapon_unusual_cool_flamethrower_icecubes +weapon_unusual_cool_flamethrower_snowflakes +weapon_unusual_cool_flamethrower_vm +weapon_unusual_cool_flamethrower_vm_1 +weapon_unusual_cool_flamethrower_vm_2 +weapon_unusual_cool_flamethrower_vm_icecubes +weapon_unusual_cool_flamethrower_vm_snowflakes +weapon_unusual_cool_grenade_launcher +weapon_unusual_cool_grenade_launcher_1 +weapon_unusual_cool_grenade_launcher_barrel +weapon_unusual_cool_grenade_launcher_icecubes +weapon_unusual_cool_grenade_launcher_vm +weapon_unusual_cool_grenade_launcher_vm_1 +weapon_unusual_cool_grenade_launcher_vm_icecubes +weapon_unusual_cool_grenade_launcher_vm_snowflakes +weapon_unusual_cool_knife +weapon_unusual_cool_knife_1 +weapon_unusual_cool_knife_barrel +weapon_unusual_cool_knife_icecubes +weapon_unusual_cool_knife_snowflakes +weapon_unusual_cool_knife_vm +weapon_unusual_cool_knife_vm_1 +weapon_unusual_cool_knife_vm_barrel +weapon_unusual_cool_knife_vm_icecubes +weapon_unusual_cool_knife_vm_snowflakes +weapon_unusual_cool_medigun +weapon_unusual_cool_medigun_1 +weapon_unusual_cool_medigun_barrel +weapon_unusual_cool_medigun_icecubes +weapon_unusual_cool_medigun_snowflakes +weapon_unusual_cool_minigun +weapon_unusual_cool_minigun_1 +weapon_unusual_cool_minigun_barrel +weapon_unusual_cool_minigun_icecubes +weapon_unusual_cool_minigun_snowflakes +weapon_unusual_cool_minigun_snowflakes_vm +weapon_unusual_cool_minigun_vm +weapon_unusual_cool_minigun_vm_1 +weapon_unusual_cool_minigun_vm_icecubes +weapon_unusual_cool_pistol +weapon_unusual_cool_pistol_1 +weapon_unusual_cool_pistol_barrel +weapon_unusual_cool_pistol_icecubes +weapon_unusual_cool_pistol_snowflakes +weapon_unusual_cool_pistol_vm +weapon_unusual_cool_pistol_vm_1 +weapon_unusual_cool_pistol_vm_icecubes +weapon_unusual_cool_pistol_vm_snowflakes +" + +"weapon_unusual_cool.pcf_cont1" +" +weapon_unusual_cool_revolver +weapon_unusual_cool_revolver_1 +weapon_unusual_cool_revolver_barrel +weapon_unusual_cool_revolver_icecubes +weapon_unusual_cool_revolver_snowflakes +weapon_unusual_cool_revolver_vm +weapon_unusual_cool_revolver_vm_1 +weapon_unusual_cool_revolver_vm_icecubes +weapon_unusual_cool_revolver_vm_snowflakes +weapon_unusual_cool_rocketlauncher +weapon_unusual_cool_rocketlauncher_1 +weapon_unusual_cool_rocketlauncher_barrel +weapon_unusual_cool_rocketlauncher_icecubes +weapon_unusual_cool_rocketlauncher_snowflakes +weapon_unusual_cool_rocketlauncher_vm +weapon_unusual_cool_rocketlauncher_vm_1 +weapon_unusual_cool_rocketlauncher_vm_icecubes +weapon_unusual_cool_rocketlauncher_vm_snowflakes +weapon_unusual_cool_scattergun +weapon_unusual_cool_scattergun_1 +weapon_unusual_cool_scattergun_icecubes +weapon_unusual_cool_scattergun_snowflakes +weapon_unusual_cool_scattergun_vm +weapon_unusual_cool_scattergun_vm_1 +weapon_unusual_cool_scattergun_vm_icecubes +weapon_unusual_cool_scattergun_vm_snowflakes +weapon_unusual_cool_shotgun +weapon_unusual_cool_shotgun_1 +weapon_unusual_cool_shotgun_barrel +weapon_unusual_cool_shotgun_icecubes +weapon_unusual_cool_shotgun_snowflakes +weapon_unusual_cool_shotgun_vm +weapon_unusual_cool_shotgun_vm_1 +weapon_unusual_cool_shotgun_vm_icecubes +weapon_unusual_cool_shotgun_vm_snowflakes +weapon_unusual_cool_smg +weapon_unusual_cool_smg_1 +weapon_unusual_cool_smg_barrel +weapon_unusual_cool_smg_icecubes +weapon_unusual_cool_smg_snowflakes +weapon_unusual_cool_smg_vm +weapon_unusual_cool_smg_vm_1 +weapon_unusual_cool_smg_vm_icecubes +weapon_unusual_cool_smg_vm_snowflakes +weapon_unusual_cool_sniperrifle +weapon_unusual_cool_sniperrifle_1 +weapon_unusual_cool_sniperrifle_barrel +weapon_unusual_cool_sniperrifle_icecubes +weapon_unusual_cool_sniperrifle_snowflakes +weapon_unusual_cool_sniperrifle_vm +weapon_unusual_cool_sniperrifle_vm_1 +weapon_unusual_cool_sniperrifle_vm_icecubes +weapon_unusual_cool_sniperrifle_vm_snowflakes +weapon_unusual_cool_stickybomb_launcher +weapon_unusual_cool_stickybomb_launcher_1 +weapon_unusual_cool_stickybomb_launcher_barrel +weapon_unusual_cool_stickybomb_launcher_icecubes +weapon_unusual_cool_stickybomb_launcher_snowflakes +weapon_unusual_cool_stickybomb_launcher_vm +weapon_unusual_cool_stickybomb_launcher_vm_1 +weapon_unusual_cool_wrench +weapon_unusual_cool_wrench_1 +weapon_unusual_cool_wrench_barrel +weapon_unusual_cool_wrench_icecubes +weapon_unusual_cool_wrench_snowflakes +weapon_unusual_cool_wrench_vm +weapon_unusual_cool_wrench_vm_1 +weapon_unusual_cool_wrench_vm_icecubes +weapon_unusual_cool_wrench_vm_snowflakes +" + + + +"weapon_unusual_energyorb.pcf" +" +//_unusual_parent_flamethrower +//_unusual_parent_medigun +//_unusual_parent_minigun +//_unusual_parent_pistol +//_unusual_parent_revolver +//_unusual_parent_rocketlauncher +//_unusual_parent_scattergun +//_unusual_parent_shotgun +//_unusual_parent_smg +//_unusual_parent_sniperrifle +//_unusual_parent_stickylauncher +weapon_unusual_energyorb_base +weapon_unusual_energyorb_base_1 +weapon_unusual_energyorb_base_2 +weapon_unusual_energyorb_flamethrower +weapon_unusual_energyorb_flamethrower_1 +weapon_unusual_energyorb_flamethrower_2 +weapon_unusual_energyorb_flamethrower_vm +weapon_unusual_energyorb_flamethrower_vm_1 +weapon_unusual_energyorb_flamethrower_vm_2 +weapon_unusual_energyorb_medigun +weapon_unusual_energyorb_minigun +weapon_unusual_energyorb_minigun_vm +weapon_unusual_energyorb_pistol +weapon_unusual_energyorb_pistol_vm +weapon_unusual_energyorb_revolver +weapon_unusual_energyorb_revolver_vm +weapon_unusual_energyorb_rocketlauncher +weapon_unusual_energyorb_rocketlauncher_vm +weapon_unusual_energyorb_scattergun +weapon_unusual_energyorb_scattergun_vm +weapon_unusual_energyorb_shotgun +weapon_unusual_energyorb_shotgun_vm +weapon_unusual_energyorb_smg +weapon_unusual_energyorb_smg_vm +weapon_unusual_energyorb_sniperrifle +weapon_unusual_energyorb_sniperrifle_vm +weapon_unusual_energyorb_stickybomb_launcher +weapon_unusual_energyorb_stickybomb_launcher_vm +" + + + +"weapon_unusual_hot.pcf" +" +//_unusual_parent_flamethrower +//_unusual_parent_medigun +//_unusual_parent_minigun +//_unusual_parent_pistol +//_unusual_parent_revolver +//_unusual_parent_rocketlauncher +//_unusual_parent_scattergun +//_unusual_parent_shotgun +//_unusual_parent_smg +//_unusual_parent_sniperrifle +//_unusual_parent_stickylauncher +weapon_unusual_hot_base +weapon_unusual_hot_base_1 +weapon_unusual_hot_base_2 +weapon_unusual_hot_base_3 +weapon_unusual_hot_base_embers_1 +weapon_unusual_hot_base_embers_2 +weapon_unusual_hot_base_glow +weapon_unusual_hot_base_warp +weapon_unusual_hot_flamethrower +weapon_unusual_hot_flamethrower_1 +weapon_unusual_hot_flamethrower_2 +weapon_unusual_hot_flamethrower_3 +weapon_unusual_hot_flamethrower_embers_1 +weapon_unusual_hot_flamethrower_embers_2 +weapon_unusual_hot_flamethrower_glow +weapon_unusual_hot_flamethrower_vm +weapon_unusual_hot_flamethrower_vm_1 +weapon_unusual_hot_flamethrower_vm_2 +weapon_unusual_hot_flamethrower_vm_embers_1 +weapon_unusual_hot_grenade_launcher +weapon_unusual_hot_grenade_launcher_1 +weapon_unusual_hot_grenade_launcher_embers_1 +weapon_unusual_hot_grenade_launcher_glow +weapon_unusual_hot_grenade_launcher_vm +weapon_unusual_hot_grenade_launcher_vm_1 +weapon_unusual_hot_grenade_launcher_vm_embers_1 +weapon_unusual_hot_knife +weapon_unusual_hot_knife_1 +weapon_unusual_hot_knife_embers_1 +weapon_unusual_hot_knife_glow +weapon_unusual_hot_knife_vm +weapon_unusual_hot_knife_vm_1 +weapon_unusual_hot_knife_vm_embers_1 +weapon_unusual_hot_knife_vm_glow +weapon_unusual_hot_medigun +weapon_unusual_hot_medigun_1 +weapon_unusual_hot_medigun_embers_1 +weapon_unusual_hot_medigun_embers_2 +weapon_unusual_hot_medigun_glow +weapon_unusual_hot_medigun_vm +weapon_unusual_hot_medigun_vm_1 +weapon_unusual_hot_medigun_vm_embers_1 +weapon_unusual_hot_minigun +weapon_unusual_hot_minigun_1 +weapon_unusual_hot_minigun_embers_1 +weapon_unusual_hot_minigun_embers_2 +weapon_unusual_hot_minigun_glow +weapon_unusual_hot_minigun_vm +weapon_unusual_hot_minigun_vm_1 +weapon_unusual_hot_minigun_vm_embers_1 +weapon_unusual_hot_pistol +weapon_unusual_hot_pistol_1 +weapon_unusual_hot_pistol_embers_1 +weapon_unusual_hot_pistol_glow +weapon_unusual_hot_pistol_vm +weapon_unusual_hot_pistol_vm_1 +weapon_unusual_hot_pistol_vm_embers_1 +" + +"weapon_unusual_hot.pcf_cont1" +" +weapon_unusual_hot_revolver +weapon_unusual_hot_revolver_1 +weapon_unusual_hot_revolver_embers_1 +weapon_unusual_hot_revolver_glow +weapon_unusual_hot_revolver_vm +weapon_unusual_hot_revolver_vm_1 +weapon_unusual_hot_revolver_vm_embers_1 +weapon_unusual_hot_rocketlauncher +weapon_unusual_hot_rocketlauncher_1 +weapon_unusual_hot_rocketlauncher_embers_1 +weapon_unusual_hot_rocketlauncher_glow +weapon_unusual_hot_rocketlauncher_vm +weapon_unusual_hot_rocketlauncher_vm_1 +weapon_unusual_hot_rocketlauncher_vm_embers_1 +weapon_unusual_hot_scattergun +weapon_unusual_hot_scattergun_1 +weapon_unusual_hot_scattergun_embers_1 +weapon_unusual_hot_scattergun_vm +weapon_unusual_hot_scattergun_vm_1 +weapon_unusual_hot_scattergun_vm_embers_1 +weapon_unusual_hot_shotgun +weapon_unusual_hot_shotgun_1 +weapon_unusual_hot_shotgun_embers_1 +weapon_unusual_hot_shotgun_embers_2 +weapon_unusual_hot_shotgun_glow +weapon_unusual_hot_shotgun_vm +weapon_unusual_hot_shotgun_vm_1 +weapon_unusual_hot_shotgun_vm_embers_1 +weapon_unusual_hot_smg +weapon_unusual_hot_smg_1 +weapon_unusual_hot_smg_embers_1 +weapon_unusual_hot_smg_glow +weapon_unusual_hot_smg_vm +weapon_unusual_hot_smg_vm_1 +weapon_unusual_hot_smg_vm_embers_1 +weapon_unusual_hot_sniperrifle +weapon_unusual_hot_sniperrifle_1 +weapon_unusual_hot_sniperrifle_embers_1 +weapon_unusual_hot_sniperrifle_glow +weapon_unusual_hot_sniperrifle_vm +weapon_unusual_hot_sniperrifle_vm_1 +weapon_unusual_hot_sniperrifle_vm_embers_1 +weapon_unusual_hot_stickybomb_launcher +weapon_unusual_hot_stickybomb_launcher_1 +weapon_unusual_hot_stickybomb_launcher_embers_1 +weapon_unusual_hot_stickybomb_launcher_glow +weapon_unusual_hot_stickybomb_launcher_vm +weapon_unusual_hot_stickybomb_launcher_vm_1 +weapon_unusual_hot_stickybomb_launcher_vm_embers_1 +weapon_unusual_hot_wrench +weapon_unusual_hot_wrench_1 +weapon_unusual_hot_wrench_embers_1 +weapon_unusual_hot_wrench_vm +weapon_unusual_hot_wrench_vm_1 +weapon_unusual_hot_wrench_vm_embers_1 +" + + + +"//weapon_unusual_isotope.pcf" " +//_unusual_parent_blackbox +//_unusual_parent_flamethrower +//_unusual_parent_grenade_launcher +//_unusual_parent_knife +//_unusual_parent_medigun +//_unusual_parent_minigun +//_unusual_parent_pistol +//_unusual_parent_revolver +//_unusual_parent_rocketlauncher +//_unusual_parent_scattergun +//_unusual_parent_shotgun +//_unusual_parent_smg +//_unusual_parent_sniperrifle +//_unusual_parent_stickylauncher +//_unusual_parent_wrench +weapon_unusual_isotope +weapon_unusual_isotope_flamethrower +weapon_unusual_isotope_flamethrower_glow +weapon_unusual_isotope_flamethrower_glow_vm +weapon_unusual_isotope_flamethrower_muzzleglow +weapon_unusual_isotope_flamethrower_muzzleglow_vm +weapon_unusual_isotope_flamethrower_secondary_rings +weapon_unusual_isotope_flamethrower_secondary_rings_vm +weapon_unusual_isotope_flamethrower_smoke +weapon_unusual_isotope_flamethrower_smoke_vm +weapon_unusual_isotope_flamethrower_sparks +weapon_unusual_isotope_flamethrower_sparks_vm +weapon_unusual_isotope_flamethrower_vm +weapon_unusual_isotope_glow +weapon_unusual_isotope_grenade_launcher +weapon_unusual_isotope_grenade_launcher_glow +weapon_unusual_isotope_grenade_launcher_glow_vm +weapon_unusual_isotope_grenade_launcher_muzzleglow +weapon_unusual_isotope_grenade_launcher_muzzleglow_vm +weapon_unusual_isotope_grenade_launcher_secondary_rings +weapon_unusual_isotope_grenade_launcher_secondary_rings_vm +weapon_unusual_isotope_grenade_launcher_smoke +weapon_unusual_isotope_grenade_launcher_smoke_vm +weapon_unusual_isotope_grenade_launcher_sparks +weapon_unusual_isotope_grenade_launcher_sparks_vm +weapon_unusual_isotope_grenade_launcher_vm +weapon_unusual_isotope_knife +weapon_unusual_isotope_knife_glow +weapon_unusual_isotope_knife_glow_vm +weapon_unusual_isotope_knife_muzzleglow +weapon_unusual_isotope_knife_muzzleglow_vm +weapon_unusual_isotope_knife_secondary_rings +weapon_unusual_isotope_knife_secondary_rings_vm +weapon_unusual_isotope_knife_smoke +weapon_unusual_isotope_knife_smoke_vm +weapon_unusual_isotope_knife_sparks +weapon_unusual_isotope_knife_sparks_vm +weapon_unusual_isotope_knife_vm +weapon_unusual_isotope_medigun +weapon_unusual_isotope_medigun_glow +weapon_unusual_isotope_medigun_glow_vm +weapon_unusual_isotope_medigun_muzzleglow +weapon_unusual_isotope_medigun_muzzleglow_vm +weapon_unusual_isotope_medigun_secondary_rings +weapon_unusual_isotope_medigun_secondary_rings_vm +weapon_unusual_isotope_medigun_smoke +weapon_unusual_isotope_medigun_smoke_vm +weapon_unusual_isotope_medigun_sparks +weapon_unusual_isotope_medigun_sparks_vm +weapon_unusual_isotope_medigun_vm +weapon_unusual_isotope_minigun +weapon_unusual_isotope_minigun_glow +weapon_unusual_isotope_minigun_glow_vm +weapon_unusual_isotope_minigun_muzzleglow +weapon_unusual_isotope_minigun_muzzleglow_vm +weapon_unusual_isotope_minigun_secondary_rings +weapon_unusual_isotope_minigun_secondary_rings_vm +weapon_unusual_isotope_minigun_smoke +weapon_unusual_isotope_minigun_smoke_vm +weapon_unusual_isotope_minigun_sparks +weapon_unusual_isotope_minigun_sparks_vm +weapon_unusual_isotope_minigun_vm +weapon_unusual_isotope_muzzleglow +weapon_unusual_isotope_pistol +weapon_unusual_isotope_pistol_glow +weapon_unusual_isotope_pistol_glow_vm +weapon_unusual_isotope_pistol_muzzleglow +weapon_unusual_isotope_pistol_muzzleglow_vm +weapon_unusual_isotope_pistol_secondary_rings +weapon_unusual_isotope_pistol_secondary_rings_vm +weapon_unusual_isotope_pistol_smoke +weapon_unusual_isotope_pistol_smoke_vm +weapon_unusual_isotope_pistol_sparks +weapon_unusual_isotope_pistol_sparks_vm +weapon_unusual_isotope_pistol_vm +" + +"//weapon_unusual_isotope.pcf_cont1" +" +weapon_unusual_isotope_revolver +weapon_unusual_isotope_revolver_glow +weapon_unusual_isotope_revolver_glow_vm +weapon_unusual_isotope_revolver_muzzleglow +weapon_unusual_isotope_revolver_muzzleglow_vm +weapon_unusual_isotope_revolver_secondary_rings +weapon_unusual_isotope_revolver_secondary_rings_vm +weapon_unusual_isotope_revolver_smoke +weapon_unusual_isotope_revolver_smoke_vm +weapon_unusual_isotope_revolver_sparks +weapon_unusual_isotope_revolver_sparks_vm +weapon_unusual_isotope_revolver_vm +weapon_unusual_isotope_rocketlauncher +weapon_unusual_isotope_rocketlauncher_glow +weapon_unusual_isotope_rocketlauncher_glow_vm +weapon_unusual_isotope_rocketlauncher_muzzleglow +weapon_unusual_isotope_rocketlauncher_muzzleglow_vm +weapon_unusual_isotope_rocketlauncher_secondary_rings +weapon_unusual_isotope_rocketlauncher_secondary_rings_vm +weapon_unusual_isotope_rocketlauncher_smoke +weapon_unusual_isotope_rocketlauncher_smoke_vm +weapon_unusual_isotope_rocketlauncher_sparks +weapon_unusual_isotope_rocketlauncher_sparks_vm +weapon_unusual_isotope_rocketlauncher_vm +weapon_unusual_isotope_scattergun +weapon_unusual_isotope_scattergun_glow +weapon_unusual_isotope_scattergun_glow_vm +weapon_unusual_isotope_scattergun_muzzleglow +weapon_unusual_isotope_scattergun_muzzleglow_vm +weapon_unusual_isotope_scattergun_secondary_rings +weapon_unusual_isotope_scattergun_secondary_rings_vm +weapon_unusual_isotope_scattergun_smoke +weapon_unusual_isotope_scattergun_smoke_vm +weapon_unusual_isotope_scattergun_sparks +weapon_unusual_isotope_scattergun_sparks_vm +weapon_unusual_isotope_scattergun_vm +weapon_unusual_isotope_secondary_rings +weapon_unusual_isotope_shotgun +weapon_unusual_isotope_shotgun_glow +weapon_unusual_isotope_shotgun_glow_vm +weapon_unusual_isotope_shotgun_muzzleglow +weapon_unusual_isotope_shotgun_muzzleglow_vm +weapon_unusual_isotope_shotgun_secondary_rings +weapon_unusual_isotope_shotgun_secondary_rings_vm +weapon_unusual_isotope_shotgun_smoke +weapon_unusual_isotope_shotgun_smoke_vm +weapon_unusual_isotope_shotgun_sparks +weapon_unusual_isotope_shotgun_sparks_vm +weapon_unusual_isotope_shotgun_vm +weapon_unusual_isotope_smg +weapon_unusual_isotope_smg_glow +weapon_unusual_isotope_smg_glow_vm +weapon_unusual_isotope_smg_muzzleglow +weapon_unusual_isotope_smg_muzzleglow_vm +weapon_unusual_isotope_smg_secondary_rings +weapon_unusual_isotope_smg_secondary_rings_vm +weapon_unusual_isotope_smg_smoke +weapon_unusual_isotope_smg_smoke_vm +weapon_unusual_isotope_smg_sparks +weapon_unusual_isotope_smg_sparks_vm +weapon_unusual_isotope_smg_vm +weapon_unusual_isotope_smoke +" + +"//weapon_unusual_isotope.pcf_cont2" +" +weapon_unusual_isotope_sniperrifle +weapon_unusual_isotope_sniperrifle_glow +weapon_unusual_isotope_sniperrifle_glow_vm +weapon_unusual_isotope_sniperrifle_muzzleglow +weapon_unusual_isotope_sniperrifle_muzzleglow_vm +weapon_unusual_isotope_sniperrifle_secondary_rings +weapon_unusual_isotope_sniperrifle_secondary_rings_vm +weapon_unusual_isotope_sniperrifle_smoke +weapon_unusual_isotope_sniperrifle_smoke_vm +weapon_unusual_isotope_sniperrifle_sparks +weapon_unusual_isotope_sniperrifle_sparks_vm +weapon_unusual_isotope_sniperrifle_vm +weapon_unusual_isotope_sparks +weapon_unusual_isotope_stickybomb_launcher +weapon_unusual_isotope_stickybomb_launcher_glow +weapon_unusual_isotope_stickybomb_launcher_glow_vm +weapon_unusual_isotope_stickybomb_launcher_muzzleglow +weapon_unusual_isotope_stickybomb_launcher_muzzleglow_vm +weapon_unusual_isotope_stickybomb_launcher_secondary_rings +weapon_unusual_isotope_stickybomb_launcher_secondary_rings_vm +weapon_unusual_isotope_stickybomb_launcher_smoke +weapon_unusual_isotope_stickybomb_launcher_smoke_vm +weapon_unusual_isotope_stickybomb_launcher_sparks +weapon_unusual_isotope_stickybomb_launcher_sparks_vm +weapon_unusual_isotope_stickybomb_launcher_vm +weapon_unusual_isotope_wrench +weapon_unusual_isotope_wrench_glow +weapon_unusual_isotope_wrench_glow_vm +weapon_unusual_isotope_wrench_muzzleglow +weapon_unusual_isotope_wrench_muzzleglow_vm +weapon_unusual_isotope_wrench_secondary_rings +weapon_unusual_isotope_wrench_secondary_rings_vm +weapon_unusual_isotope_wrench_smoke +weapon_unusual_isotope_wrench_smoke_vm +weapon_unusual_isotope_wrench_sparks +weapon_unusual_isotope_wrench_sparks_vm +weapon_unusual_isotope_wrench_vm +" + + + +"xms.pcf" //(christmas) +" +taunt_pyro_dosido +taunt_pyro_dosido_embers +taunt_pyro_dosido_flame +taunt_pyro_dosido_glow +taunt_pyro_dosido_smoke +taunt_pyro_dosido_smoke_front +taunt_pyro_dosido_smoke_linger +taunt_pyro_rps_paper +taunt_pyro_rps_rock +taunt_pyro_rps_scissors +xms_icicle_idle +xms_icicle_impact +xms_icicle_impact_dryice +xms_icicle_melt +xms_ornament_glitter +xms_ornament_smash_blue +xms_ornament_smash_red +xms_snowburst +xms_snowburst_child01 +xms_snowburst_child02 +xms_snowburst_child03 +" + + + + + + +//This is a CUSTOM .pcf file, repackaged because its file name and effect names conflicted with default Source Engine/Gmod effects. + +"blood_impact_tf2.pcf" +" +blood_bread_biting +blood_bread_biting2 +blood_decap +blood_decap_arterial_spray +blood_decap_fountain +blood_decap_streaks +blood_impact_backscatter +blood_impact_backscatter_ring +tf2_blood_impact_green_01 +tf2_blood_impact_green_01_droplets +tf2_blood_impact_green_01_goop +tf2_blood_impact_red_01 +tf2_blood_impact_red_01_chunk +tf2_blood_impact_red_01_droplets +tf2_blood_impact_red_01_goop +tf2_blood_impact_red_01_smalldroplets +blood_spray_red_01 +blood_spray_red_01_far +env_grinder_oilspray +env_grinder_oilspray_cash +env_sawblood +env_sawblood_chunk +env_sawblood_goop +env_sawblood_mist +lowV_blood_impact_red_01 +lowV_blood_spray_red_01 +lowV_blood_spray_red_01_far +lowV_debrischunks +lowV_impactglow +lowV_oildroplets +lowV_smallerchunks +lowv_sparks1 //(yes, the v is uncapitalized on just this one) +lowV_water_blood_impact_red_01 +lowV_water_bubbles +lowV_water_debris +temp_blood_spray_red_01 +temp_blood_spray_red_01_far +tfc_sniper_mist +tfc_sniper_mist2 +tfc_sniper_mist_dir +tfc_sniper_mist_streaks +water_blood_impact_red_01 +water_blood_impact_red_01_chunk +water_blood_impact_red_01_goop +" + + + + + + +//CP_SNOWPLOW effects, extracted from the map file + + + +"//eotl_lights.pcf" //these are just copies of the cart/minisentry light fx from flag_particles.pcf, and if they're any different, we don't want them overriding the default ones +" +cart_flashinglight +cart_flashinglight_glow +cart_flashinglight_glow_red +cart_flashinglight_light +cart_flashinglight_light_red +cart_flashinglight_red +" + + + +"laser_effects.pcf" +" +laser_beam_effect +laser_buildup_static +laser_buildup_static_2 +laser_buildup_static_3 +laser_particle_bits +laser_ring_effect +laser_rope1 +snowplow_laser +snowplow_laser_endglow +snowplow_laser_fire +snowplow_laser_rope1 +snowplow_laser_rope2 +snowplow_laser_startglow +snowplow_laser_startglow2 +" + + + +"smoke_blackbillow_eotl.pcf" +" +smoke_eotl_skybox +smoke_eotlflame_skybox +" + + + +"snowplow_snow.pcf" +" +snowplow_clouds01 +snowplow_clouds02 +snowplow_clouds03 +snowplow_clouds04 +snowplow_snow01 +snowplow_snow02 +snowplow_snow03 +snowplow_snow04 +snowplow_snow_b01 +snowplow_snow_b_512 +snowplow_snow_b_800 +snowplow_snow_c01 +" + + + +"snowplow_sparks.pcf" +" +snowplow_sparks01 +" + + + +"snowplow_train_explosions.pcf" +" +fireSmokeExplosion_snowplow_track +fireSmokeExplosion_snowplow_trackb +snowplow_train_explosion_1 +snowplow_train_explosion_2 +snowplow_train_explosion_3 +snowplow_train_explosion_4 +" + + + + + + +//KOTH_SUIJIN effects, extracted from the map file + + + +"env_sakura01.pcf" +" +sakura_512 +" + + + + + + +//ARENA_BYRE effects, extracted from the map file + + + +"byreboom.pcf" +" +byreBoom +byreCharge +byreChargeGlow +byreChild1 +byreChild2 +byreChild3 +byreChild4 +byreChild5 +byreLaser +byreLaserEnd +byreLaserEnd2 +" + + + + + + +//PD_WATERGATE effects, extracted from the map file + + + +"alien_aly_fx.pcf" +" +alien_laser_blue +alien_laser_blue_center +alien_laser_blue_center2 +alien_laser_blue_core_glow +alien_laser_blue_cp01_flashes +alien_laser_blue_cp1_flashes +alien_laser_blue_elec +alien_laser_blue_emit +alien_laser_blue_emit2 +alien_laser_blue_impact +alien_laser_blue_spin +alien_laser_center +alien_laser_center2 +alien_laser_core +alien_laser_core_glow +alien_laser_cp0_flashes +alien_laser_cp1_flashes +alien_laser_elec +alien_laser_elec2 +alien_laser_elec2_blue +alien_laser_elec2_green +alien_laser_elec2_red +alien_laser_emit +alien_laser_emit2 +alien_laser_green +alien_laser_green_center +alien_laser_green_center2 +alien_laser_green_core_glow +alien_laser_green_cp0_flashes +alien_laser_green_cp1_flashes +alien_laser_green_elec +alien_laser_green_emit +alien_laser_green_emit2 +alien_laser_green_impact +alien_laser_green_spin +alien_laser_impact +alien_laser_impact_glowtest +alien_laser_red +alien_laser_red_center +alien_laser_red_center2 +alien_laser_red_core_glow +alien_laser_red_cp0_flashes +alien_laser_red_cp1_flashes +alien_laser_red_elec +alien_laser_red_emit +alien_laser_red_emit2 +alien_laser_red_impact +alien_laser_red_spin +alien_laser_spin +alien_laser_white +cp1 +" + + + +"alien_egan_fx.pcf" +" +alien_abduct_fx +alien_abduct_fx2 +alien_abduct_fx3 +alien_abduct_invis +alien_abduct_invis2 +alien_abduct_invis3 +alien_abduction2 +alien_abduction_bits2 +alien_nuke +alien_nuke_body +alien_nuke_body_fire +alien_nuke_bottomfire +alien_nuke_bottomsmoke2 +alien_nuke_bulk +alien_nuke_bulk_fire +alien_nuke_clouds +alien_nuke_flash +alien_nuke_flash2 +alien_nuke_flashagainnotstolenatall +alien_nuke_flashnotstolenfromeyelander +alien_nuke_invis +alien_nuke_shockwave +alien_nuke_shockwave2 +ufo_3dksybox_return_finale //yes, ksybox +ufo_3dksybox_return_finale_bits +ufo_3dksybox_return_finale_blindinglight +ufo_3dksybox_return_finale_firey +waterfall_bottommist_huge +waterfall_bottomsplash_huge +waterfall_bottomwaves_huge +" + + + +"alien_fxxl.pcf" +" +alien_xl_mothership_explode +alien_xl_mothership_exploding +alien_xl_mothership_exploding2 +alien_xl_mothership_exploding_center +alien_xl_mothership_exploding_center2 +alien_xl_mothership_exploding_center3 +alien_xl_mothership_exploding_center5 //not in original pcf? +alien_xl_mothership_exploding_center_initial +alien_xl_mothership_exploding_center_initial2 +alien_xl_mothership_exploding_center_initial3 +alien_xl_mothership_exploding_center_initial5 +alien_xl_mothership_exploding_initial +alien_xl_mothership_laser_charge +alien_xl_mothership_laser_charge2 +alien_xl_mothership_laser_charge_elec +alien_xl_mothership_laser_charge_sep1_energy +alien_xl_mothership_laser_charge_sep1_energy2 +alien_xl_mothership_laser_charge_sep1_hotplate +alien_xl_mothership_laser_charge_sep1_hotplate2 +alien_xl_mothership_laser_charge_sep1_hotplate3 //not in original pcf +alien_xl_mothership_laser_charge_sep1_sides +alien_xl_mothership_laser_charge_sep2_energy +alien_xl_mothership_laser_charge_sep2_energy2 +alien_xl_mothership_laser_charge_sep2_hotplate +alien_xl_mothership_laser_charge_sep2_hotplate2 +alien_xl_mothership_laser_charge_sep2_prepped +alien_xl_mothership_laser_charge_sep2_sides +alien_xl_mothership_laser_charge_sep3_firing +alien_xl_mothership_laser_charge_sep3_prepped +alien_xl_mothership_laser_sep1 +alien_xl_mothership_laser_sep2 +alien_xl_mothership_laser_sep3 +" + + + +"alien2_fx.pcf" +" +alien_props_canister_lg_bubbles +alien_props_canister_lg_bubbles2 +alien_props_canister_md_elec +alien_props_canister_md_elec2 +alien_props_canister_md_elec2b +//alien_props_canister_md_elec_blue //team-colored ones aren't included in the in-game version +//alien_props_canister_md_elec_blue2 +//alien_props_canister_md_elec_red +//alien_props_canister_md_elec_red2 +alien_props_canister_md_glow +alien_props_canister_md_glow2 +//alien_props_canister_md_glow2_blue +//alien_props_canister_md_glow2_red +alien_props_canister_md_glow_pulses +alien_props_canister_md_glow_pulses2 +alien_props_canister_md_glow_pulses3 +//alien_props_canister_md_glow_pulses3_blue +//alien_props_canister_md_glow_pulses3_red +alien_props_canister_pieces_lg_light +alien_props_canister_pieces_md_light +alien_props_canister_xlg +alien_props_canister_xlg2 +alien_props_canister_xlg_bubbles +alien_props_canister_xlg_bubbles2 +alien_props_cannister_top_xlg_centerglow +alien_props_cannister_top_xlg_downwards +alien_props_cannister_top_xlg_downwards2 +alien_props_cannister_top_xlg_glowup +alien_props_cannister_top_xlg_hotplate +alien_props_cannister_top_xlg_hotplate2 +alien_props_cannister_top_xlg_rings +alien_props_cannister_top_xlg_ringsup +alien_props_cannister_top_xlg_ringsuppush +alien_props_cannister_top_xlg_upward +alien_props_chimney_fire +alien_props_chimney_fire2 +alien_props_chimney_fire_green +alien_props_chimney_fire_green2 +alien_props_crawler_core +alien_props_crawler_elec +alien_props_generic_smoking +alien_props_generic_smoking_loop +alien_props_raygun_charging_into +alien_props_raygun_chargingglows +alien_props_raygun_charinginto +alien_props_raygun_laser +alien_props_raygun_laser2 +alien_props_raygun_laser3 +alien_props_raygun_laser_body +alien_props_raygun_laser_body2 +alien_props_raygun_laser_charge +alien_props_raygun_laser_charge2 +alien_props_raygun_laser_charge2b +alien_props_raygun_laser_charge_BACKUP +alien_props_raygun_laser_chargeb +alien_props_raygun_laser_explosion +alien_props_raygun_laser_explosion_BACKUP +alien_props_raygun_laser_initial +alien_props_raygun_laser_invis +alien_props_ring_glow +alien_props_ring_glowup +alien_props_ring_rings +alien_props_ring_rings2 +alien_props_walker_bits +alien_props_walker_bits2 +alien_props_walker_elec +alien_props_walker_explosion +alien_props_walker_laser +alien_props_walker_laser_body +alien_props_walker_laser_body2 +alien_props_walker_laser_charge +alien_props_walker_laser_charge_glow +alien_props_walker_laser_elec +alien_props_walker_laser_initial +alien_props_walker_laser_invis +alien_props_walker_laser_poof +alien_props_walker_pop +alien_props_walker_pop_nogibs +alien_props_walker_smoke +alien_props_walker_sprite +alien_props_walker_sprites2 +WOAH +WOAH2 //permanent, watch out +" + + + +//pd_watergate has a duplicate of invasion_2fort_fx.pcf, which is exactly the same except the inv_walker_explode effects are missing - we're skipping this one + + + +"pd_icons.pcf" +" +barf_bubbles +barf_mist +bottle_icon_deposit +bottle_icon_leader +pd_bubbles +pd_bubbles_orb +pd_cores_1 //all these effects are unused now because valve just added a hud element instead +pd_cores_2 +pd_cores_3 +pd_cores_4 +pd_cores_5 +pd_cores_6 +pd_cores_7 +pd_cores_8 +pd_cores_9 +pd_cores_10 +pd_cores_11 +pd_cores_12 +pd_cores_13 +pd_cores_14 +pd_cores_15 +pd_cores_16 +pd_cores_17 +pd_cores_18 +pd_cores_19 +pd_cores_20 +pd_cores_21 +pd_cores_22 +pd_cores_23 +pd_cores_24 +pd_cores_25 +pd_cores_26 +pd_cores_27 +pd_cores_28 +pd_cores_29 +pd_cores_30 +pd_cores_31 +pd_cores_32 +pd_cores_33 +pd_cores_34 +pd_cores_35 +pd_cores_36 +pd_cores_37 +pd_cores_38 +pd_cores_39 +pd_cores_40 +pd_cores_41 +pd_cores_42 +pd_cores_43 +pd_cores_44 +pd_cores_45 +pd_cores_46 +pd_cores_47 +pd_cores_48 +pd_cores_49 +pd_cores_50 +pd_cores_1_blue +pd_cores_2_blue +pd_cores_3_blue +pd_cores_4_blue +pd_cores_5_blue +pd_cores_6_blue +pd_cores_7_blue +pd_cores_8_blue +pd_cores_9_blue +pd_cores_10_blue +pd_cores_11_blue +pd_cores_12_blue +pd_cores_13_blue +pd_cores_14_blue +pd_cores_15_blue +pd_cores_16_blue +pd_cores_17_blue +pd_cores_18_blue +pd_cores_19_blue +pd_cores_20_blue +pd_cores_21_blue +pd_cores_22_blue +pd_cores_23_blue +pd_cores_24_blue +pd_cores_25_blue +pd_cores_26_blue +pd_cores_27_blue +pd_cores_28_blue +pd_cores_29_blue +pd_cores_30_blue +pd_cores_31_blue +pd_cores_32_blue +pd_cores_33_blue +pd_cores_34_blue +pd_cores_35_blue +pd_cores_36_blue +pd_cores_37_blue +pd_cores_38_blue +pd_cores_39_blue +pd_cores_40_blue +pd_cores_41_blue +pd_cores_42_blue +pd_cores_43_blue +pd_cores_44_blue +pd_cores_45_blue +pd_cores_46_blue +pd_cores_47_blue +pd_cores_48_blue +pd_cores_49_blue +pd_cores_50_blue +" + + + +"playerdestruction.pcf" //these are all unused too i think +" +pd_player_orb_blue +pd_player_orb_red +pd_player_sparkles_blue +pd_player_sparkles_red +pd_player_still_blue +pd_player_still_red +" + + + + + + +//CTF_2FORT_INVASION effects, extracted from the map file + + + +"invasion_2fort_fx.pcf" +" +alien_intel_trail_chem +alien_intel_trail_chem_big_bubbles +alien_intel_trail_chem_drips +alien_intel_trail_chem_drops +alien_intel_trail_mineral +alien_intel_trail_mineral_bits +alien_intel_trail_mineral_trail +alien_intel_trail_mineral_wisps +inv_canister_fluid_l +inv_canister_fluid_l_bottom_glow +inv_canister_fluid_l_glow +inv_canister_fluid_l_top_glow +inv_canister_fluid_m +inv_canister_fluid_m_bottom_glow +inv_canister_fluid_m_glow +inv_canister_fluid_m_top_glow +inv_canister_fluid_s +inv_canister_fluid_s_bottom_glow +inv_canister_fluid_s_glow +inv_canister_fluid_s_top_glow +inv_canister_mineral_l +inv_canister_mineral_m +inv_canister_mineral_s +inv_condring_plasma +inv_condring_plasma_arc +inv_condring_plasma_core +inv_condring_plasma_edge +inv_condring_plasma_rays +inv_condring_plasma_rope +inv_condring_plasma_sparks +inv_condring_plasma_swirl +inv_condring_plasma_wisps +inv_envglow_green +inv_envglow_green_embers +inv_envglow_green_smoke +inv_envglow_green_wisps +inv_haze_green +inv_haze_green_bits +inv_haze_green_rays +inv_inv_condring_plasma +inv_inv_condring_plasma_rays +inv_irradiator_beam +inv_irradiator_beam_laser +inv_irradiator_beam_laser_back +inv_irradiator_beam_laser_bits +inv_irradiator_beam_laser_sparks +inv_irradiator_beam_laser_wisps +inv_irradiator_beam_muzzle_crown +inv_irradiator_beam_muzzle_electro +inv_quarantine_vent_exhaust +inv_quarantine_vent_exhaust_fan +inv_quarantine_vent_exhaust_jet +inv_quarantine_vent_exhaust_lines +inv_smoke_trail_0 +inv_smoke_trail_1 +inv_spark_drops +inv_walker_explode +inv_walker_explode_arcs +inv_walker_explode_arcs_embers +inv_walker_explode_arcs_smoke +inv_walker_explode_drops +inv_walker_explode_flash +inv_walker_explode_flashup +inv_walker_explode_ring +inv_walker_explode_smoke +inv_walker_explode_wisps +" + + + + + + +//KOTH_PROBED effects, extracted from the map file + + + +"alien_crash_fx2.pcf" +" +alien_ufo_explode +alien_ufo_explode2 +alien_ufo_explode2_alt +alien_ufo_explode2_alt_groundfire +alien_ufo_explode2_alt_smoke2 +alien_ufo_explode2_alt_smoke3 +alien_ufo_explode2_alt_trailing_bits2 +alien_ufo_explode_alt +alien_ufo_explode_alt_elec +alien_ufo_explode_alt_flash +alien_ufo_explode_alt_glow +alien_ufo_explode_alt_smoke +alien_ufo_explode_alt_trail_elec +alien_ufo_explode_alt_trail_smoke +alien_ufo_explode_alt_trailing_bits +alien_ufo_explode_elec +alien_ufo_explode_flash +alien_ufo_explode_glow +alien_ufo_explode_groundfire +alien_ufo_explode_smoke +alien_ufo_explode_smoke2 +alien_ufo_explode_smoke3 +alien_ufo_explode_trail_elec +alien_ufo_explode_trail_smoke +alien_ufo_explode_trailing_bits +alien_ufo_explode_trailing_bits2 +alien_ufo_explode_trailing_bits_alt +alien_ufo_explode_trailing_bitsb +cvbgngfngfjgfdjgfdj //well this sure is a thing +" + + + +"koth_probed_fx.pcf" +" +alien_abduction +alien_abduction_bits +alien_abduction_bits2 +alien_abduction_bits3 +alien_abduction_cow +alien_abduction_cows +alien_abduction_glow2 +alien_megalaser_blindinglight_blue +alien_megalaser_blindinglight_red +alien_megalaser_blue +alien_megalaser_blue_center1 +alien_megalaser_blue_center2 +alien_megalaser_blue_core_glow +alien_megalaser_blue_cp1_flashes +alien_megalaser_blue_elec +alien_megalaser_blue_emit +alien_megalaser_blue_emit2 +alien_megalaser_blue_flash1 //not in original pcf (fantasmos_fx2) +alien_megalaser_blue_flash3 //not in original pcf (fantasmos_fx2) +alien_megalaser_blue_impact +alien_megalaser_blue_spin +alien_megalaser_elec2_blue +alien_megalaser_elec2_red +alien_megalaser_red +alien_megalaser_red_center1 +alien_megalaser_red_center2 +alien_megalaser_red_core_glow +alien_megalaser_red_cp1_flashes +alien_megalaser_red_elec +alien_megalaser_red_emit +alien_megalaser_red_emit2 +alien_megalaser_red_flash1 //not in original pcf (fantasmos_fx2) +alien_megalaser_red_flash3 //not in original pcf (fantasmos_fx2) +alien_megalaser_red_impact +alien_megalaser_red_spin +alien_mothership_explode +alien_mothership_explode_blue +alien_mothership_explode_red +alien_mothership_exploding +alien_mothership_exploding2 +alien_mothership_exploding_center +alien_mothership_exploding_center2 +alien_mothership_exploding_center3 +alien_mothership_exploding_center5 //not in original pcf (alien_fx) +alien_mothership_exploding_center_initial +alien_mothership_exploding_center_initial2 +alien_mothership_exploding_center_initial3 +alien_mothership_exploding_center_initial4_blue +alien_mothership_exploding_center_initial4_red +alien_mothership_exploding_center_initial5 +alien_mothership_exploding_initial +alien_mothership_laser_charge +alien_mothership_laser_charge2 +alien_mothership_laser_charge_elec +alien_mothership_laser_charge_sep1_energy +alien_mothership_laser_charge_sep1_energy2 +alien_mothership_laser_charge_sep1_hotplate +alien_mothership_laser_charge_sep1_hotplate2 +alien_mothership_laser_charge_sep1_hotplate3 //not in original pcf (alien_fx) +alien_mothership_laser_charge_sep1_sides +alien_mothership_laser_sep1 +alien_props_canister_md_elec +alien_props_canister_md_glow +alien_props_canister_md_glow_pulses +alien_props_canister_md_glow_pulses2 +alien_props_canister_md_glow_pulses3 +alien_props_canister_xlg +alien_props_canister_xlg_bubbles +alien_props_chimney_fire +alien_props_chimney_fire_core +alien_props_crawler_core +alien_props_crawler_elec +alien_props_generic_smoking +alien_props_raygun_charging_into +alien_props_raygun_charginginto +alien_props_raygun_laser2 +alien_props_raygun_laser3 +alien_props_raygun_laser_body +alien_props_raygun_laser_body2 +alien_props_raygun_laser_charge +alien_props_raygun_laser_charge2 +alien_props_raygun_laser_charge2b +alien_props_raygun_laser_chargeb +alien_props_raygun_laser_explosion +alien_props_raygun_laser_initial +alien_props_raygun_laser_invis +alien_props_ring_glow +alien_props_ring_glowup +alien_props_ring_rings +alien_props_walker_bits +alien_props_walker_bits2 +alien_props_walker_elec +alien_props_walker_explosion +alien_props_walker_laser +alien_props_walker_laser_body +alien_props_walker_laser_body2 +alien_props_walker_laser_charge +alien_props_walker_laser_charge_glow +alien_props_walker_laser_elec +alien_props_walker_laser_initial +alien_props_walker_laser_invis +alien_props_walker_laser_poof +alien_props_walker_pop +alien_props_walker_pop_nogibs +alien_props_walker_smoke +alien_props_walker_sprite +alien_props_walker_sprites2 +" + +"koth_probed_fx.pcf_cont1" +" +alien_teleporter_blue +alien_teleporter_blue_bottom +alien_teleporter_blue_center +alien_teleporter_blue_center2 +alien_teleporter_blue_center3 +alien_teleporter_blue_elec +alien_teleporter_blue_left +alien_teleporter_blue_right +alien_teleporter_blue_top +alien_teleporter_red +alien_teleporter_red_bottom +alien_teleporter_red_center +alien_teleporter_red_center2 +alien_teleporter_red_center3 +alien_teleporter_red_elec +alien_teleporter_red_left +alien_teleporter_red_right +alien_teleporter_red_top +alien_teleportin +alien_teleportin2 +alien_teleportin2_inner +alien_teleportin2_megaman +alien_teleportin_elec +alien_teleportin_flashes +alien_teleportin_inner +alien_teleportin_megaman +alien_teleportin_outer +alien_teleportin_outer2 +" + + + + + + +//PL_SNOWYCOAST effects, extracted from the map file + + + +"pl_snowycoast_fx.pcf" +" +alien_laser_elec2_red +alien_laser_red +alien_laser_red_center +alien_laser_red_center2 +alien_laser_red_core_glow +alien_laser_red_cp0_flashes +alien_laser_red_cp1_flashes +alien_laser_red_elec +alien_laser_red_emit +alien_laser_red_emit2 +alien_laser_red_impact +alien_laser_red_spin +alien_mothership_explode_xl_center_red +alien_mothership_exploding_xl_center +alien_mothership_exploding_xl_center2 +alien_mothership_exploding_xl_center3 +alien_mothership_exploding_xl_center5 +alien_mothership_exploding_xl_center_initial +alien_mothership_exploding_xl_center_initial2 +alien_mothership_exploding_xl_center_initial3 +alien_mothership_exploding_xl_center_initial4_red +alien_mothership_exploding_xl_center_initial5 +alien_props_canister_md_elec2 +alien_props_canister_md_elec2b +alien_props_canister_md_elec_blue +alien_props_canister_md_elec_blue2 +alien_props_canister_md_elec_red +alien_props_canister_md_elec_red2 +alien_props_canister_md_glow2 +alien_props_canister_md_glow2_blue +alien_props_canister_md_glow2_red +alien_props_canister_md_glow_pulses3 +alien_props_canister_md_glow_pulses3_blue +alien_props_canister_md_glow_pulses3_red +alien_props_canister_pieces_md_light +alien_props_canister_xlg +alien_props_canister_xlg_bubbles +alien_props_chimney_fire_green +alien_props_chimney_fire_green2 +alien_props_chimney_fire_green2_OLD +alien_props_chimney_fire_green_core +alien_props_chimney_fire_green_OLD +env_snow_stormfront_mist //identical to the one in stormfront.pcf (coldfront), except it has fewer max particles (which i don't think it ever reaches anyway) +env_snow_stormfront_small +" + + + + + + +//PD_PIT_OF_DEATH_EVENT effects, extracted from the map file + + + +"pd_pit_of_death_fx.pcf" +" +deathpit_finale_blue +deathpit_finale_blue_ghosts +deathpit_finale_blue_pentagram +deathpit_finale_blue_portal3 +deathpit_finale_blue_portal_baphomet_hand1 +deathpit_finale_blue_portal_hades_hand2 +deathpit_finale_blue_portal_mastermind_eyes +deathpit_finale_blue_portal_tear2 +deathpit_finale_blue_portal_tear3 +deathpit_finale_blue_portal_tear4 +deathpit_finale_neutral_pentagram +deathpit_finale_neutral_portal2 +deathpit_finale_red +deathpit_finale_red_ghosts +deathpit_finale_red_pentagram +deathpit_finale_red_pentagram_BACKUP1 +deathpit_finale_red_portal +deathpit_finale_red_portal2_BACKUP1 +deathpit_finale_red_portal3 +deathpit_finale_red_portal_BACKUP1 +deathpit_finale_red_portal_BACKUP2 +deathpit_finale_red_portal_cthulhu_hand1 +deathpit_finale_red_portal_kraken_eyes +deathpit_finale_red_portal_shub-niggurath_hand2 +deathpit_finale_red_portal_tear +deathpit_finale_red_portal_tear2 +deathpit_finale_red_portal_tear3 +deathpit_finale_red_portal_tear4 +deathpit_pd +deathpit_pd2 +deathpit_pd_appear +deathpit_pd_appear2 +deathpit_pd_core +deathpit_pd_core2 +deathpit_pd_core3 +deathpit_pd_core4 +deathpit_pd_core_funnel +deathpit_pd_core_square +deathpit_pd_core_square_hider +deathpit_pd_core_square_hider2 +deathpit_pd_core_square_hider_oops +deathpit_pd_core_square_hider_oops_original +deathpit_pd_core_square_hider_whoops +deathpit_pd_corners +deathpit_pd_elec +deathpit_pd_elec_BACKUP1 +deathpit_pd_elec_glow +deathpit_pd_elec_strike +deathpit_pd_ghost_flash +deathpit_pd_ghost_flash2 +deathpit_pd_smokey +deathpit_pd_utaunt_puppet_hands_trails +deathpit_skull_eye_blue +deathpit_skull_eye_blue_beams +deathpit_skull_eye_blue_beams_BACKUP1 +deathpit_skull_eye_blue_smoke +deathpit_skull_eye_blue_smoke_BACKUP1 +deathpit_skull_eye_red +deathpit_skull_eye_red_BACKUP1 +deathpit_skull_eye_red_BACKUP2 +deathpit_skull_eye_red_beams +deathpit_skull_eye_red_smoke +playersoul_fx +playersoul_fx_flames +playersoul_fx_orb +playersoul_fx_skull +underworld_gate_capzone +underworld_gate_capzone_BACKUP1 +underworld_gate_capzone_glows +underworld_portal_out +underworld_portal_out01 +underworld_portal_out02 +underworld_portal_out03 +underworld_portal_out04 +underworld_portal_out05 +utaunt_darkness_tentacle_beam +utaunt_darkness_tentacles1 +utaunt_darkness_tentacles1_sub +" + +} \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/vgui/ctrlnumpadmulti.lua b/garrysmod/addons/gmod-tools/lua/vgui/ctrlnumpadmulti.lua new file mode 100644 index 0000000..2d0ebb1 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/ctrlnumpadmulti.lua @@ -0,0 +1,39 @@ + +local PANEL = {} + +function PANEL:Init() + self.Label = vgui.Create("DLabel", self) + self.NumPad = vgui.Create("DNumPadMulti", self) + + self.Label:SetTextColor(color_white) + + self:SetPaintBackground(false) +end + +function PANEL:SetLabel(txt) + self.Label:SetText(txt or "Unnamed CtrlNumPadMulti: ") +end + +function PANEL:SetConVar(varname) + self.ConVar = varname + + self.NumPad:SetConVar(varname) +end + +function PANEL:GetConVar() + return self.ConVar +end + +function PANEL:PerformLayout() + self.NumPad:InvalidateLayout(true) + self.NumPad:Center() + self.NumPad:AlignBottom(5) + + self.Label:CenterHorizontal() + self.Label:AlignTop(5) + self.Label:SizeToContents() + + self:SetTall(self.Label:GetTall() + self.NumPad:GetTall() + 15) +end + +vgui.Register("CtrlNumPadMulti", PANEL, "DPanel") diff --git a/garrysmod/addons/gmod-tools/lua/vgui/disabled/dcatmullcamtracksaveloadbutton.lua b/garrysmod/addons/gmod-tools/lua/vgui/disabled/dcatmullcamtracksaveloadbutton.lua new file mode 100644 index 0000000..7c59d64 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/disabled/dcatmullcamtracksaveloadbutton.lua @@ -0,0 +1,65 @@ +-- Based off of the Duplicator GUI + +local PANEL = {} + +function PANEL:Init() + self.SelectButton = vgui.Create("DButton", self) + + self.DeleteButton = vgui.Create("DImageButton", self) + self.DeleteButton:SetMaterial("gui/silkicons/camera_delete") + self.DeleteButton:SetTooltip("Delete this track from your HDD.") + --[[ + self.EditButton = vgui.Create("DImageButton", self) + self.EditButton:SetMaterial("gui/silkicons/camera_edit") + self.EditButton:SetTooltip("Edit this track's additional information.") + --]] +end + +function PANEL:SetFile(filename) + self.File = filename + + self.SelectButton:SetText(string.Explode(".", self.File)[1]) +end + +function PANEL:Nothing() +end + +function PANEL:Delete() + file.Delete(CatmullRomCams.FilePath .. self.File) + + return self:GetParent():Populate() +end + +function PANEL:SetID(id) + function self.DeleteButton.DoClick() + return Derma_Query("Are you sure you wish to delete this track?", "Confirm Command: Delete File", + "Confirm Deletion", function() return self:Delete() end, + "Cancel", self.Nothing) + end + + function self.SelectButton.DoClick() + return RunConsoleCommand("catmullrom_camera_save_load_file", self.File) + end +end + +function PANEL:PerformLayout() + self.DeleteButton.y = 3 + self.DeleteButton:SizeToContents() + self.DeleteButton:AlignRight(3) + + self.EditButton.y = 3 + self.EditButton:SizeToContents() + self.EditButton:AlignRight(self.DeleteButton:GetWide() + 5) + + self:SetTall(self.DeleteButton:GetTall() + 6) + + self.SelectButton:StretchToParent(3, 3, 3, 3) + self.SelectButton:StretchRightTo(self.EditButton, 3) +end + +function PANEL:Paint() + draw.RoundedBox(4, 0, 0, self:GetWide(), self:GetTall(), Color(0, 0, 0, 150)) + return false +end + +derma.DefineControl("DCatmullCamTrackSaveLoadButton", "Its time to kick ass and chew bubble-gum, and I'm all outa' gum...", PANEL, "DPanel") diff --git a/garrysmod/addons/gmod-tools/lua/vgui/disabled/dcatmullcamtracksaveloadmenu.lua b/garrysmod/addons/gmod-tools/lua/vgui/disabled/dcatmullcamtracksaveloadmenu.lua new file mode 100644 index 0000000..04ed689 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/disabled/dcatmullcamtracksaveloadmenu.lua @@ -0,0 +1,75 @@ +-- Based off of the Duplicator GUI + +PANEL.VoteName = "none" +PANEL.MaterialName = "exclamation" + +function PANEL:Init() + self.RefreshBtn = vgui.Create("DImageButton", self) + self.RefreshBtn:SetMaterial("gui/silkicons/arrow_refresh") + self.RefreshBtn:SetTooltip("Refresh List") + self.RefreshBtn.DoClick = function() return self:Populate() end + + self.SaveBtn = vgui.Create("DImageButton", self) + self.SaveBtn:SetMaterial("gui/silkicons/camera_add") + self.SaveBtn:SetTooltip("Save Track") + self.SaveBtn.DoClick = function() self:Save() end + + self.SaveFileName = vgui.Create("DTextEntry", self) + self.SaveFileName:SetKeyboardInputEnabled(true) + self.SaveFileName:SetEnabled(true) + + self.List = vgui.Create("PanelList", self) + self.List:SetSpacing(1) + + self.SaveList = {} +end + +function PANEL:Save() + --RunConsoleCommand("tool_duplicator_store", self.SaveFileName:GetValue()) +end + +function PANEL:PerformLayout() + self:SetTall(500) + + self.RefreshBtn:SizeToContents() + self.RefreshBtn:AlignRight(5) + self.RefreshBtn:AlignBottom() + + self.SaveBtn:SizeToContents() + self.SaveBtn:AlignRight(self.RefreshBtn:GetWide() + 9) + self.SaveBtn:AlignBottom() + + self.SaveFileName:SetTall(self.SaveBtn:GetTall()) + self.SaveFileName:AlignLeft(5) + self.SaveFileName:AlignBottom() + self.SaveFileName:StretchRightTo(self.SaveBtn, 4) + + self.List:StretchToParent(0, 0, 0, 0) + self.List:StretchBottomTo(self.SaveBtn, 4) +end + +function PANEL:Clear() + self.SaveList = {} + + return self.List:Clear() +end + +function PANEL:Add(id, name) + self.SaveList[id] = name +end + +function PANEL:Populate() + self.List:Clear() + + self.SaveList = file.Find(CatmullRomCams.FilePath .. "*.txt") + + for k, v in pairs(self.SaveList) do + local btn = vgui.Create("DCatmullCamTrackSaveLoadButton", self) + btn:SetFile(v) + btn:SetID(k) + self.List:AddItem(btn) + end +end + +derma.DefineControl("DCatmullCamTrackSaveLoadMenu", "Its time to kick ass and chew bubble-gum, and I'm all outa' gum...", PANEL, "DPanel") + diff --git a/garrysmod/addons/gmod-tools/lua/vgui/dnumpadmulti.lua b/garrysmod/addons/gmod-tools/lua/vgui/dnumpadmulti.lua new file mode 100644 index 0000000..e7b9225 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/dnumpadmulti.lua @@ -0,0 +1,191 @@ +/* _ + () + _| | __ _ __ ___ ___ _ _ + /'_` | /'__`\('__)/' _ ` _ `\ /'_`) +((_| |(___/| | | () () |((_| | +`\__,_)`\____)(_) (_) (_) (_)`\__,_) + + DNumPad + + A loverly multi-use numpad. Now can remember more then once key! + +*/ + +local KP_PERIOD = 10 +local KP_ENTER = 11 +local KP_PLUS = 12 +local KP_MINUS = 13 +local KP_STAR = 14 +local KP_DIV = 15 + +local PANEL = {} + +function PANEL:Init() + self.Buttons = {} + + for i = 0, 15 do + self.Buttons[i] = vgui.Create("DButton", self) + self.Buttons[i]:SetText(i) + self.Buttons[i].DoClick = function(btn) self:OnButtonPressed(btn, i) end + self.Buttons[i].LastToggle = 0 + end + + self.Buttons[KP_PERIOD]:SetText(".") + self.Buttons[KP_ENTER]:SetText("") + self.Buttons[KP_PLUS]:SetText("+") + self.Buttons[KP_MINUS]:SetText("-") + self.Buttons[KP_STAR]:SetText("*") + self.Buttons[KP_DIV]:SetText("/") + + self.Buttons[0]:SetContentAlignment(4) + self.Buttons[0]:SetTextInset(6,0) + + self.m_KeyFlags = 0 +end + +function PANEL:Thinkz() + for k, v in ipairs(self.Buttons) do + if v.Hovered then -- it's highlighted + -- But don't bother because I'm chopping that feature (minor one) due to time restraints + end + end +end + +function PANEL:OnButtonPressed(pButton, iButtonNumber) + pButton.WasSelectedActive = not pButton.WasSelectedActive + + pButton:SetSelected(pButton.WasSelectedActive) + + local hackz = (pButton.WasSelectedActive and self:AddKeyFlag(iButtonNumber, pButton) or self:RemoveKeyFlag(iButtonNumber, pButton)) +end + +function PANEL:PerformLayout() + local ButtonSize = 17 + local Padding = 4 + + self:SetSize(ButtonSize * 4 + Padding * 2, ButtonSize * 5 + Padding * 2) + + self.Buttons[0]:SetSize(ButtonSize * 2, ButtonSize) + self.Buttons[0]:AlignBottom(Padding) + self.Buttons[0]:AlignLeft(Padding) + self.Buttons[KP_PERIOD]:CopyBounds(self.Buttons[0]) + self.Buttons[KP_PERIOD]:SetSize(ButtonSize, ButtonSize) + self.Buttons[KP_PERIOD]:MoveRightOf(self.Buttons[0]) + + self.Buttons[1]:SetSize(ButtonSize, ButtonSize) + self.Buttons[1]:AlignLeft(Padding) + self.Buttons[1]:MoveAbove(self.Buttons[ 0 ]) + self.Buttons[2]:CopyBounds(self.Buttons[1]) + self.Buttons[2]:MoveRightOf(self.Buttons[1]) + self.Buttons[3]:CopyBounds(self.Buttons[2]) + self.Buttons[3]:MoveRightOf(self.Buttons[2]) + + self.Buttons[KP_ENTER]:SetSize(ButtonSize, ButtonSize*2) + self.Buttons[KP_ENTER]:AlignBottom(Padding) + self.Buttons[KP_ENTER]:AlignRight(Padding) + + self.Buttons[KP_PLUS]:CopyBounds(self.Buttons[KP_ENTER]) + self.Buttons[KP_PLUS]:MoveAbove(self.Buttons[KP_ENTER]) + + self.Buttons[KP_MINUS]:CopyBounds(self.Buttons[KP_PLUS]) + self.Buttons[KP_MINUS]:SetSize(ButtonSize, ButtonSize) + self.Buttons[KP_MINUS]:MoveAbove(self.Buttons[KP_PLUS]) + + self.Buttons[KP_STAR]:CopyBounds(self.Buttons[KP_MINUS]) + self.Buttons[KP_STAR]:MoveLeftOf(self.Buttons[KP_MINUS]) + + self.Buttons[KP_DIV]:CopyBounds(self.Buttons[KP_STAR]) + self.Buttons[KP_DIV]:MoveLeftOf(self.Buttons[KP_STAR]) + + self.Buttons[4]:CopyBounds(self.Buttons[1]) + self.Buttons[4]:MoveAbove(self.Buttons[1]) + self.Buttons[5]:CopyBounds(self.Buttons[4]) + self.Buttons[5]:MoveRightOf(self.Buttons[4]) + self.Buttons[6]:CopyBounds(self.Buttons[5]) + self.Buttons[6]:MoveRightOf(self.Buttons[5]) + + self.Buttons[7]:CopyBounds(self.Buttons[4]) + self.Buttons[7]:MoveAbove(self.Buttons[4]) + self.Buttons[8]:CopyBounds(self.Buttons[7]) + self.Buttons[8]:MoveRightOf(self.Buttons[7]) + self.Buttons[9]:CopyBounds(self.Buttons[8]) + self.Buttons[9]:MoveRightOf(self.Buttons[8]) +end + +function PANEL:AddKeyFlag(key_val, pButton) + local bin_flag = 2 ^ key_val + print("Add") + print("Key: ", key_val, "; Flag: ", bin_flag) + print(self.m_KeyFlags and bin_flag) + pButton.LastToggle = CurTime() + .1 -- hackz + print("---") + + if (self.m_KeyFlags and bin_flag) ~= bin_flag then + self.m_KeyFlags = self.m_KeyFlags + bin_flag + + self:UpdateConVar() + end +end + +function PANEL:RemoveKeyFlag(key_val, pButton) + local bin_flag = 2 ^ key_val + print(pButton.LastToggle) + print(CurTime()) + if pButton.LastToggle > CurTime() then return end + print("Remove") + print("Key: ", key_val, "; Flag: ", bin_flag) + print(self.m_KeyFlags and bin_flag) + + + print("---") + --debug.Trace() + if (self.m_KeyFlags and bin_flag) == bin_flag then + self.m_KeyFlags = self.m_KeyFlags - bin_flag + + self:UpdateConVar() + end +end + +function PANEL:SetConVar(cvar_name) + self.m_CVarName = cvar_name +end + +function PANEL:UpdateConVar() + if self.m_CVarName then + print("Update: ", self.m_KeyFlags) + RunConsoleCommand(self.m_CVarName, self.m_KeyFlags) + print("---") + end +end + +function PANEL:Clear() + for i = 1, 15 do + if self.Buttons[i].WasSelectedActive then + self.Buttons[i]:DoClick() + end + end +end + +function PANEL:SetupKeys(data) + self:Clear() + + for k, _ in pairs(data) do + self.Buttons[k]:DoClick() -- ? + print("Setting ", k, "...\n") + end +end + +function PANEL:SetValue(iNumValue) +end + +function PANEL:SetKeySlider(slider) + -- Nevermind... +end + +function PANEL:GetValue() + return self.m_KeyFlags +end + +vgui.Register("DNumPadMulti", PANEL, "DPanel") + + diff --git a/garrysmod/addons/gmod-tools/lua/vgui/stackercontrolpresets.lua b/garrysmod/addons/gmod-tools/lua/vgui/stackercontrolpresets.lua new file mode 100644 index 0000000..71e2a51 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/stackercontrolpresets.lua @@ -0,0 +1,26 @@ +--[[-------------------------------------------------------------------------- +-- Namespace Tables +--------------------------------------------------------------------------]]-- + +local PANEL = {} + +--[[-------------------------------------------------------------------------- +-- Namespace Functions +--------------------------------------------------------------------------]]-- + +--[[-------------------------------------------------------------------------- +-- +-- PANEL:OpenPresetEditor() +-- +--]]-- +function PANEL:OpenPresetEditor() + if ( not self.m_strPreset ) then return end + self.Window = vgui.Create( "StackerPresetEditor" ) + self.Window:MakePopup() + self.Window:Center() + self.Window:SetType( self.m_strPreset ) + self.Window:SetConVars( self.ConVars ) + self.Window:SetPresetControl( self ) +end + +vgui.Register( "StackerControlPresets", PANEL, "ControlPresets" ) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/vgui/stackerdnumslider.lua b/garrysmod/addons/gmod-tools/lua/vgui/stackerdnumslider.lua new file mode 100644 index 0000000..c3e9f23 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/stackerdnumslider.lua @@ -0,0 +1,50 @@ +--[[-------------------------------------------------------------------------- +-- Namespace Tables +--------------------------------------------------------------------------]]-- + +local PANEL = {} + +--[[-------------------------------------------------------------------------- +-- Localized Functions & Variables +--------------------------------------------------------------------------]]-- + +local math = math +local vgui = vgui +local tonumber = tonumber + +--[[-------------------------------------------------------------------------- +-- Namespace Functions +--------------------------------------------------------------------------]]-- + +--[[-------------------------------------------------------------------------- +-- +-- PANEL:SetValue( string, boolean ) +-- +--]]-- +function PANEL:SetValue( val, bSuppress ) + val = math.Clamp( tonumber( val ) or 0, self:GetMin(), self:GetMax() ) + + if ( val == nil ) then return end + if ( self:GetValue() == val ) then return end + + self.Scratch:SetFloatValue( val ) + self:ValueChanged( self:GetValue(), bSuppress ) +end + +--[[-------------------------------------------------------------------------- +-- +-- PANEL:ValueChanged( string, value) +-- +--]]-- +function PANEL:ValueChanged( val, bSuppress ) + val = math.Clamp( tonumber( val ) or 0, self:GetMin(), self:GetMax() ) + self.Slider:SetSlideX( self.Scratch:GetFraction( val ) ) + if ( self.TextArea ~= vgui.GetKeyboardFocus() ) then + self.TextArea:SetValue( self.Scratch:GetTextValue() ) + end + if ( not bSuppress ) then + self:OnValueChanged( val ) + end +end + +vgui.Register( "StackerDNumSlider", PANEL, "DNumSlider" ) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/vgui/stackerpreseteditor.lua b/garrysmod/addons/gmod-tools/lua/vgui/stackerpreseteditor.lua new file mode 100644 index 0000000..75bfee2 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/vgui/stackerpreseteditor.lua @@ -0,0 +1,50 @@ +--[[-------------------------------------------------------------------------- +-- Namespace Tables +--------------------------------------------------------------------------]]-- + +local PANEL = {} + +--[[-------------------------------------------------------------------------- +-- Localized Functions & Variables +--------------------------------------------------------------------------]]-- + +local vgui = vgui +local pairs = pairs +local AccessorFunc = AccessorFunc +local GetConVarString = GetConVarString + +--[[-------------------------------------------------------------------------- +-- Namespace Functions +--------------------------------------------------------------------------]]-- + +AccessorFunc( PANEL, "m_ConCommands", "ConCommands" ) + +--[[-------------------------------------------------------------------------- +-- +-- PANEL:Add() +-- +--]]-- +function PANEL:Add() + if ( not self.m_ConVars ) then return end + + local ToName = self.txtName:GetValue() + if ( not ToName or ToName == "" ) then return end + + -- Todo, Handle name collision + local tabValues = {} + + for k, v in pairs( self.m_ConVars ) do + tabValues[ v.CCmd ] = GetConVarString( v.CVar ) + end + + presets.Add( self.m_strType, ToName, tabValues ) + self:Update() + self.PresetList:SelectByName( ToName ) + self.txtName:SetText( "" ) + + if ( self.m_PresetControl ) then + self.m_PresetControl:Update() + end +end + +vgui.Register( "StackerPresetEditor", PANEL, "PresetEditor" ) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/weapons/dbg_zombieheal/shared.lua b/garrysmod/addons/gmod-tools/lua/weapons/dbg_zombieheal/shared.lua new file mode 100644 index 0000000..7aaba46 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/dbg_zombieheal/shared.lua @@ -0,0 +1,53 @@ +if SERVER then AddCSLuaFile 'shared.lua' end + +SWEP.PrintName = L.zombie_heal +SWEP.Author = 'chelog' +SWEP.DrawAmmo = true +SWEP.Slot = 2 +SWEP.SlotPos = 0 +SWEP.Contact = '' +SWEP.Purpose = '' +SWEP.Instructions = L.instruction_zombie_heal + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = 'DarkRP (Utility)' + +SWEP.ViewModel = 'models/weapons/c_medkit.mdl' +SWEP.WorldModel = 'models/weapons/w_medkit.mdl' +SWEP.UseHands = true + +SWEP.Primary.Recoil = 0 +SWEP.Primary.ClipSize = 200 +SWEP.Primary.DefaultClip = 200 +SWEP.Primary.Automatic = true +SWEP.Primary.Delay = 0.1 + +SWEP.Secondary.Recoil = 0 +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Delay = 0.3 + +function SWEP:PrimaryAttack() + + if CLIENT then return end -- yeah yeah fuck off, it's a swep for 1-day event + self:SetNextPrimaryFire(CurTime() + 1) + + local ent = self.Owner:GetEyeTrace().Entity + if IsValid(ent) and ent:GetClass() == 'prop_ragdoll' and ent:GetModel() == 'models/player/zombie_fast.mdl' and ent:GetPos():DistToSqr(self.Owner:GetPos()) < 10000 then + local ply = ent:GetNetVar('RagdollOwner') + if IsValid(ply) then ply.pendingZombie = nil end + ent:Remove() + + self.Owner:EmitSound('hl1/fvox/boop.wav', 65, 50, 1, CHAN_AUTO) + self:SetNextPrimaryFire(CurTime() + 3) + end + +end + +function SWEP:SecondaryAttack() + + -- nothing + +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advdupe2.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advdupe2.lua new file mode 100644 index 0000000..8165268 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advdupe2.lua @@ -0,0 +1,1857 @@ +--[[ + Title: Adv. Dupe 2 Tool + + Desc: Defines the AD2 tool and assorted functionalities. + + Author: TB + + Version: 1.0 +]] +TOOL.Category = "Construction" +TOOL.Name = "#Tool.advdupe2.name" +cleanup.Register( "AdvDupe2" ) +require "controlpanel" + +duplicator.Allow('prop_dynamic') +duplicator.Allow('prop_dynamic_override') + +if(SERVER)then + CreateConVar("sbox_maxgmod_contr_spawners",5) + + local phys_constraint_system_types = { + Weld = true, + Rope = true, + Elastic = true, + Slider = true, + Axis = true, + AdvBallsocket = true, + NoCollide = true, + Motor = true, + Pulley = true, + Ballsocket = true, + Winch = true, + Hydraulic = true, + WireMotor = true, + WireHydraulic = true + } + --Orders constraints so that the dupe uses as little constraint systems as possible + local function GroupConstraintOrder( ply, constraints ) + --First seperate the nocollides, sorted, and unsorted constraints + local nocollide, sorted, unsorted = {}, {}, {} + for k, v in pairs(constraints) do + if v.Type == "NoCollide" then + nocollide[#nocollide+1] = v + elseif phys_constraint_system_types[v.Type] then + sorted[#sorted+1] = v + else + unsorted[#unsorted+1] = v + end + end + + local sortingSystems = {} + local fullSystems = {} + local function buildSystems(input) + while next(input) ~= nil do + for k, v in pairs(input) do + for systemi, system in pairs(sortingSystems) do + for _, target in pairs(system) do + for x = 1, 4 do + if v.Entity[x] then + for y = 1, 4 do + if target.Entity[y] and v.Entity[x].Index == target.Entity[y].Index then + system[#system + 1] = v + if #system==100 then + fullSystems[#fullSystems + 1] = system + table.remove(sortingSystems, systemi) + end + input[k] = nil + goto super_loopbreak + end + end + end + end + end + end + end + + --Normally skipped by the goto unless no cluster is found. If so, make a new one. + local k = next(input) + sortingSystems[#sortingSystems + 1] = {input[k]} + input[k] = nil + + ::super_loopbreak:: + end + end + buildSystems(sorted) + buildSystems(nocollide) + + local ret = {} + for _, system in pairs(fullSystems) do + for _, v in pairs(system) do + ret[#ret + 1] = v + end + end + for _, system in pairs(sortingSystems) do + for _, v in pairs(system) do + ret[#ret + 1] = v + end + end + for k, v in pairs(unsorted) do + ret[#ret + 1] = v + end + + if #fullSystems ~= 0 then + ply:ChatPrint("DUPLICATOR: WARNING, Number of constraints exceeds 100: (".. #ret .."). Constraint sorting might not work as expected.") + end + + return ret + end + + local function CreationConstraintOrder( constraints ) + local ret = {} + for k, v in pairs( constraints ) do + ret[#ret + 1] = k + end + table.sort(ret) + for i=1, #ret do + ret[i] = constraints[ret[i]] + end + return ret + end + + local function GetSortedConstraints( ply, constraints ) + if ply:GetInfo("advdupe2_sort_constraints") ~= "0" then + return GroupConstraintOrder( ply, constraints ) + else + return CreationConstraintOrder( constraints ) + end + end + + local areacopy_classblacklist = { + gmod_anchor = true + } + + local function PlayerCanDupeCPPI(ply, ent) + if ent.DoNotDuplicate or areacopy_classblacklist[ent:GetClass()] or not IsValid(ent:GetPhysicsObject()) or not duplicator.IsAllowed(ent:GetClass()) then return false end + return ent:CPPIGetOwner()==ply + end + + local function PlayerCanDupeTool(ply, ent) + if ent.DoNotDuplicate or areacopy_classblacklist[ent:GetClass()] or not IsValid(ent:GetPhysicsObject()) or not duplicator.IsAllowed(ent:GetClass()) then return false end + local trace = WireLib and WireLib.dummytrace(ent) or { Entity = ent } + return hook.Run( "CanTool", ply, trace, "advdupe2" ) ~= false + end + + --Find all the entities in a box, given the adjacent corners and the player + local function FindInBox(min, max, ply) + local PPCheck = (tobool(ply:GetInfo("advdupe2_copy_only_mine")) and CPPI~=nil) and PlayerCanDupeCPPI or PlayerCanDupeTool + local Entities = ents.GetAll() --Don't use FindInBox. It has a 512 entity limit. + local EntTable = {} + local pos, ent + for i=1, #Entities do + ent = Entities[i] + pos = ent:GetPos() + if (pos.X>=min.X) and (pos.X<=max.X) and (pos.Y>=min.Y) and (pos.Y<=max.Y) and (pos.Z>=min.Z) and (pos.Z<=max.Z) and PPCheck( ply, ent ) then + EntTable[ent:EntIndex()] = ent + end + end + + return EntTable + end + + --[[ + Name: LeftClick + Desc: Defines the tool's behavior when the player left-clicks. + Params: trace + Returns: success + ]] + function TOOL:LeftClick( trace ) + if(not trace)then return false end + + local ply = self:GetOwner() + if(not ply.AdvDupe2 or not ply.AdvDupe2.Entities)then return false end + + if(ply.AdvDupe2.Pasting or ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.",NOTIFY_ERROR) + return false + end + + local z = math.Clamp((tonumber(ply:GetInfo("advdupe2_offset_z")) + ply.AdvDupe2.HeadEnt.Z), -16000, 16000) + ply.AdvDupe2.Position = trace.HitPos + Vector(0, 0, z) + ply.AdvDupe2.Angle = Angle(ply:GetInfoNum("advdupe2_offset_pitch", 0), ply:GetInfoNum("advdupe2_offset_yaw", 0), ply:GetInfoNum("advdupe2_offset_roll", 0)) + if(tobool(ply:GetInfo("advdupe2_offset_world")))then ply.AdvDupe2.Angle = ply.AdvDupe2.Angle - ply.AdvDupe2.Entities[ply.AdvDupe2.HeadEnt.Index].PhysicsObjects[0].Angle end + + ply.AdvDupe2.Pasting = true + AdvDupe2.Notify(ply,"Pasting...") + local origin + if(tobool(ply:GetInfo("advdupe2_original_origin")))then + origin = Vector(ply.AdvDupe2.HeadEnt.Pos) + end + local x, y, z = ply:GetInfo('advdupe2_original_offset_x'), ply:GetInfo('advdupe2_original_offset_y'), ply:GetInfo('advdupe2_original_offset_z') + if (x ~= 0 or y ~= 0 or z ~= 0) and origin then + origin:Add(Vector(x, y, z)) + end + AdvDupe2.InitPastingQueue(ply, ply.AdvDupe2.Position, ply.AdvDupe2.Angle, origin, tobool(ply:GetInfo("advdupe2_paste_constraints")), tobool(ply:GetInfo("advdupe2_paste_parents")), tobool(ply:GetInfo("advdupe2_paste_disparents")),tobool(ply:GetInfo("advdupe2_paste_protectoveride")),tobool(ply:GetInfo("advdupe2_remove_physics")), tobool(ply:GetInfo("advdupe2_make_perma"))) + return true + end + + --[[ + Name: RightClick + Desc: Defines the tool's behavior when the player right-clicks. + Params: trace + Returns: success + ]] + function TOOL:RightClick( trace ) + local ply = self:GetOwner() + + if(ply.AdvDupe2.Pasting or ply.AdvDupe2.Downloading)then + AdvDupe2.Notify(ply,"Advanced Duplicator 2 is busy.", NOTIFY_ERROR) + return false + end + + --Set Area Copy on or off + if( ply:KeyDown(IN_SPEED) and not ply:KeyDown(IN_WALK) )then + if(self:GetStage()==0)then + AdvDupe2.DrawSelectBox(ply) + self:SetStage(1) + return false + elseif(self:GetStage()==1)then + AdvDupe2.RemoveSelectBox(ply) + self:SetStage(0) + return false + end + end + + if(not trace or not trace.Hit)then return false end + + local Entities, Constraints, AddOne + local HeadEnt = {} + --If area copy is on + if(self:GetStage()==1)then + local area_size = math.Clamp(tonumber(ply:GetInfo("advdupe2_area_copy_size")) or 50, 0, 30720) + local Pos = trace.HitNonWorld and trace.Entity:GetPos() or trace.HitPos + local T = (Vector(area_size,area_size,area_size)+Pos) + local B = (Vector(-area_size,-area_size,-area_size)+Pos) + + local Ents = FindInBox(B,T, ply) + if next(Ents)==nil then + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + return true + end + + local Ent = trace.HitNonWorld and trace.Entity or Ents[next(Ents)] + HeadEnt.Index = Ent:EntIndex() + HeadEnt.Pos = Ent:GetPos() + + Entities, Constraints = AdvDupe2.duplicator.AreaCopy(Ents, HeadEnt.Pos, tobool(ply:GetInfo("advdupe2_copy_outside"))) + + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + elseif trace.HitNonWorld then --Area Copy is off + -- Filter duplicator blocked entities out. + if not duplicator.IsAllowed( trace.Entity:GetClass() ) then + return false + end + + --If Alt is being held, add a prop to the dupe + if(ply:KeyDown(IN_WALK) and ply.AdvDupe2.Entities~=nil and next(ply.AdvDupe2.Entities)~=nil)then + Entities = ply.AdvDupe2.Entities + Constraints = ply.AdvDupe2.Constraints + HeadEnt = ply.AdvDupe2.HeadEnt + + AdvDupe2.duplicator.Copy( trace.Entity, Entities, Constraints, HeadEnt.Pos) + + --Only add the one ghost + AddOne = Entities[trace.Entity:EntIndex()] + else + Entities = {} + Constraints = {} + HeadEnt.Index = trace.Entity:EntIndex() + HeadEnt.Pos = trace.HitPos + + AdvDupe2.duplicator.Copy( trace.Entity, Entities, Constraints, trace.HitPos ) + end + else --Non valid entity or clicked the world + if ply.AdvDupe2.Entities then + --clear the dupe + umsg.Start("AdvDupe2_RemoveGhosts", ply) + umsg.End() + ply.AdvDupe2.Entities = nil + ply.AdvDupe2.Constraints = nil + umsg.Start("AdvDupe2_ResetDupeInfo", ply) + umsg.End() + AdvDupe2.ResetOffsets(ply) + return true + else + --select all owned props + Entities = {} + local PPCheck = (tobool(ply:GetInfo("advdupe2_copy_only_mine")) and CPPI~=nil) and PlayerCanDupeCPPI or PlayerCanDupeTool + for _, ent in pairs(ents.GetAll()) do + if PPCheck( ply, ent ) then + Entities[ent:EntIndex()] = ent + end + end + if next(Entities)==nil then + umsg.Start("AdvDupe2_RemoveGhosts", ply) + umsg.End() + return true + end + + local Ent = Entities[next(Entities)] + HeadEnt.Index = Ent:EntIndex() + HeadEnt.Pos = Ent:GetPos() + + Entities, Constraints = AdvDupe2.duplicator.AreaCopy(Entities, HeadEnt.Pos, tobool(ply:GetInfo("advdupe2_copy_outside"))) + end + end + + if not HeadEnt.Z then + local WorldTrace = util.TraceLine( {mask=MASK_NPCWORLDSTATIC, start=HeadEnt.Pos+Vector(0,0,1), endpos=HeadEnt.Pos-Vector(0,0,50000)} ) + HeadEnt.Z = WorldTrace.Hit and math.abs(HeadEnt.Pos.Z-WorldTrace.HitPos.Z) or 0 + end + + ply.AdvDupe2.HeadEnt = HeadEnt + ply.AdvDupe2.Entities = Entities + ply.AdvDupe2.Constraints = GetSortedConstraints(ply, Constraints) + + net.Start("AdvDupe2_SetDupeInfo") + net.WriteString("") + net.WriteString(ply:Nick()) + net.WriteString(os.date("%d %B %Y")) + net.WriteString(os.date("%I:%M %p")) + net.WriteString("") + net.WriteString("") + net.WriteString(table.Count(ply.AdvDupe2.Entities)) + net.WriteString(#ply.AdvDupe2.Constraints) + net.Send(ply) + + if AddOne then + AdvDupe2.SendGhost(ply, AddOne) + else + AdvDupe2.SendGhosts(ply) + end + + AdvDupe2.ResetOffsets(ply) + + return true + end + + --Checks table, re-draws loading bar, and recreates ghosts when tool is pulled out + function TOOL:Deploy() + local ply = self:GetOwner() + + if not ply.AdvDupe2 then ply.AdvDupe2 = {} end + if not ply.AdvDupe2.Entities then return end + + umsg.Start("AdvDupe2_StartGhosting", ply) + umsg.End() + + if(ply.AdvDupe2.Queued)then + AdvDupe2.InitProgressBar(ply, "Queued: ") + return + end + + if(ply.AdvDupe2.Pasting)then + AdvDupe2.InitProgressBar(ply, "Pasting: ") + return + else + if(ply.AdvDupe2.Uploading)then + AdvDupe2.InitProgressBar(ply, "Opening: ") + return + elseif(ply.AdvDupe2.Downloading)then + AdvDupe2.InitProgressBar(ply, "Saving: ") + return + end + end + + end + + --Removes progress bar + function TOOL:Holster() + AdvDupe2.RemoveProgressBar(self:GetOwner()) + end + + --[[ + Name: Reload + Desc: Creates an Advance Contraption Spawner. + Params: trace + Returns: success + ]] + function TOOL:Reload( trace ) + if(!trace.Hit)then return false end + + local ply = self:GetOwner() + + if(self:GetStage()==1)then + local areasize = math.Clamp(tonumber(ply:GetInfo("advdupe2_area_copy_size")) or 50, 0, 30720) + umsg.Start("AdvDupe2_CanAutoSave", ply) + umsg.Vector(trace.HitPos) + umsg.Short(areasize) + if(trace.Entity)then + umsg.Short(trace.Entity:EntIndex()) + else + umsg.Short(0) + end + umsg.End() + self:SetStage(0) + AdvDupe2.RemoveSelectBox(ply) + ply.AdvDupe2.TempAutoSavePos = trace.HitPos + ply.AdvDupe2.TempAutoSaveSize = areasize + ply.AdvDupe2.TempAutoSaveOutSide = tobool(ply:GetInfo("advdupe2_copy_outside")) + return true + end + + --If a contraption spawner was clicked then update it with the current settings + if(trace.Entity:GetClass()=="gmod_contr_spawner")then + local delay = tonumber(ply:GetInfo("advdupe2_contr_spawner_delay")) + local undo_delay = tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_delay")) + local min + local max + if(not delay)then + delay = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + else + if(not game.SinglePlayer())then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + if (delay < min) then + delay = min + end + elseif(delay<0)then + delay = 0 + end + end + + if(not undo_delay)then + undo_delay = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) + else + if(not game.SinglePlayer())then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) or 0.1 + max = tonumber(GetConVarString("AdvDupe2_MaxContraptionUndoDelay")) or 60 + if(undo_delay < min) then + undo_delay = min + elseif(undo_delay > max)then + undo_delay = max + end + elseif(undo_delay < 0)then + undo_delay = 0 + end + end + trace.Entity:GetTable():SetOptions(ply, delay, undo_delay, tonumber(ply:GetInfo("advdupe2_contr_spawner_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_disgrav")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_disdrag")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_addvel")) or 1 ) + return true + end + + --Create a contraption spawner + if ply.AdvDupe2 and ply.AdvDupe2.Entities then + local headent = ply.AdvDupe2.Entities[ply.AdvDupe2.HeadEnt.Index] + local Pos, Ang + + if(headent)then + if(tobool(ply:GetInfo("advdupe2_original_origin")))then + Pos = ply.AdvDupe2.HeadEnt.Pos + headent.PhysicsObjects[0].Pos + Ang = headent.PhysicsObjects[0].Angle + else + local EntAngle = headent.PhysicsObjects[0].Angle + if(tobool(ply:GetInfo("advdupe2_offset_world")))then EntAngle = Angle(0,0,0) end + trace.HitPos.Z = trace.HitPos.Z + math.Clamp(ply.AdvDupe2.HeadEnt.Z + tonumber(ply:GetInfo("advdupe2_offset_z")) or 0, -16000, 16000) + Pos, Ang = LocalToWorld(headent.PhysicsObjects[0].Pos, EntAngle, trace.HitPos, Angle(math.Clamp(tonumber(ply:GetInfo("advdupe2_offset_pitch")) or 0,-180,180), math.Clamp(tonumber(ply:GetInfo("advdupe2_offset_yaw")) or 0,-180,180), math.Clamp(tonumber(ply:GetInfo("advdupe2_offset_roll")) or 0,-180,180))) + end + + local x, y, z = ply:GetInfo('advdupe2_original_offset_x'), ply:GetInfo('advdupe2_original_offset_y'), ply:GetInfo('advdupe2_original_offset_z') + if x ~= 0 or y ~= 0 or z ~= 0 then + Pos:Add(Vector(x, y, z)) + end + else + AdvDupe2.Notify(ply, "Invalid head entity to spawn contraption spawner.") + return false + end + + if(headent.Class=="gmod_contr_spawner") then + AdvDupe2.Notify(ply, "Cannot make a contraption spawner from a contraption spawner.") + return false + end + + + local spawner = MakeContraptionSpawner( ply, Pos, Ang, ply.AdvDupe2.HeadEnt.Index, table.Copy(ply.AdvDupe2.Entities), table.Copy(ply.AdvDupe2.Constraints), tonumber(ply:GetInfo("advdupe2_contr_spawner_delay")), tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_delay")), headent.Model, tonumber(ply:GetInfo("advdupe2_contr_spawner_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_undo_key")), tonumber(ply:GetInfo("advdupe2_contr_spawner_disgrav")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_disdrag")) or 0, tonumber(ply:GetInfo("advdupe2_contr_spawner_addvel")) or 1, tonumber(ply:GetInfo("advdupe2_contr_spawner_hideprops")) or 0 ) + ply:AddCleanup( "AdvDupe2", spawner ) + undo.Create("gmod_contr_spawner") + undo.AddEntity( spawner ) + undo.SetPlayer( ply ) + undo.Finish() + + return true + end + end + + --Called to clean up the tool when pasting is finished or undo during pasting + function AdvDupe2.FinishPasting(Player, Paste) + Player.AdvDupe2.Pasting=false + AdvDupe2.RemoveProgressBar(Player) + if Player.oldKeypads then + Player:Notify("В твоем дубликате присутствовали кейпады, которые были созданы до одного из обновлений дубликатора, в результате чего больше не работают. Убедись, что все кейпады работают корректно, и пересохрани дубликат.") + Player.oldKeypads = nil + end + if(Paste)then AdvDupe2.Notify(Player,"Finished Pasting!") end + end + + --function for creating a contraption spawner + function MakeContraptionSpawner( ply, Pos, Ang, HeadEnt, EntityTable, ConstraintTable, delay, undo_delay, model, key, undo_key, disgrav, disdrag, addvel, hideprops) + + if not ply:CheckLimit("gmod_contr_spawners") then return nil end + + if(not game.SinglePlayer())then + if(table.Count(EntityTable)>tonumber(GetConVarString("AdvDupe2_MaxContraptionEntities")))then + AdvDupe2.Notify(ply,"Contraption Spawner exceeds the maximum amount of "..GetConVarString("AdvDupe2_MaxContraptionEntities").." entities for a spawner!",NOTIFY_ERROR) + return false + end + if(#ConstraintTable>tonumber(GetConVarString("AdvDupe2_MaxContraptionConstraints")))then + AdvDupe2.Notify(ply,"Contraption Spawner exceeds the maximum amount of "..GetConVarString("AdvDupe2_MaxContraptionConstraints").." constraints for a spawner!",NOTIFY_ERROR) + return false + end + end + + local spawner = ents.Create("gmod_contr_spawner") + if not IsValid(spawner) then return end + + spawner:SetPos(Pos) + spawner:SetAngles(Ang) + spawner:SetModel(model) + spawner:SetRenderMode(RENDERMODE_TRANSALPHA) + spawner:Spawn() + + duplicator.ApplyEntityModifiers(ply, spawner) + + if IsValid(spawner:GetPhysicsObject()) then + spawner:GetPhysicsObject():EnableMotion(false) + end + + local min + local max + if(not delay)then + delay = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + else + if(not game.SinglePlayer())then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + if (delay < min) then + delay = min + end + elseif(delay<0)then + delay = 0 + end + end + + if(not undo_delay)then + undo_delay = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) + else + if(not game.SinglePlayer())then + min = tonumber(GetConVarString("AdvDupe2_MinContraptionUndoDelay")) or 0.1 + max = tonumber(GetConVarString("AdvDupe2_MaxContraptionUndoDelay")) or 60 + if(undo_delay < min) then + undo_delay = min + elseif(undo_delay > max)then + undo_delay = max + end + elseif(undo_delay < 0)then + undo_delay = 0 + end + end + + -- Set options + spawner:SetPlayer(ply) + spawner:GetTable():SetOptions(ply, delay, undo_delay, key, undo_key, disgrav, disdrag, addvel, hideprops) + + local tbl = { + ply = ply, + delay = delay, + undo_delay = undo_delay, + disgrav = disgrav, + disdrag = disdrag, + addvel = addvel, + hideprops = hideprops + } + table.Merge(spawner:GetTable(), tbl) + spawner:SetDupeInfo(HeadEnt, EntityTable, ConstraintTable) + spawner:AddGhosts(ply) + + ply:AddCount("gmod_contr_spawners", spawner) + ply:AddCleanup("gmod_contr_spawner", spawner) + return spawner + end + duplicator.RegisterEntityClass("gmod_contr_spawner", MakeContraptionSpawner, "Pos", "Ang", "HeadEnt", "EntityTable", "ConstraintTable", "delay", "undo_delay", "model", "key", "undo_key", "disgrav", "disdrag", "addvel", "hideprops") + + + function AdvDupe2.InitProgressBar(ply,label) + umsg.Start("AdvDupe2_InitProgressBar",ply) + umsg.String(label) + umsg.End() + end + + function AdvDupe2.DrawSelectBox(ply) + umsg.Start("AdvDupe2_DrawSelectBox", ply) + umsg.End() + end + + function AdvDupe2.RemoveSelectBox(ply) + umsg.Start("AdvDupe2_RemoveSelectBox", ply) + umsg.End() + end + + function AdvDupe2.UpdateProgressBar(ply,percent) + umsg.Start("AdvDupe2_UpdateProgressBar",ply) + umsg.Char(percent) + umsg.End() + end + + function AdvDupe2.RemoveProgressBar(ply) + umsg.Start("AdvDupe2_RemoveProgressBar",ply) + umsg.End() + end + + --Reset the offsets of height, pitch, yaw, and roll back to default + function AdvDupe2.ResetOffsets(ply, keep) + + if(not keep)then + ply.AdvDupe2.Name = nil + end + umsg.Start("AdvDupe2_ResetOffsets", ply) + umsg.End() + end + + function AdvDupe2.UpdateProgressBar(ply, perc) + umsg.Start("AdvDupe2_UpdateProgressBar", ply) + umsg.Short(perc) + umsg.End() + end + + net.Receive("AdvDupe2_CanAutoSave", function(len, ply, len2) + + local desc = net.ReadString() + local ent = net.ReadInt(16) + + if(ent~=0)then + ply.AdvDupe2.AutoSaveEnt = ent + if(ply:GetInfo("advdupe2_auto_save_contraption")=="1")then + ply.AdvDupe2.AutoSaveEnt = ents.GetByIndex( ply.AdvDupe2.AutoSaveEnt ) + end + else + if(ply:GetInfo("advdupe2_auto_save_contraption")=="1")then + AdvDupe2.Notify(ply, "No entity selected to auto save contraption.", NOTIFY_ERROR) + return + end + ply.AdvDupe2.AutoSaveEnt = nil + end + + ply.AdvDupe2.AutoSavePos = ply.AdvDupe2.TempAutoSavePos + ply.AdvDupe2.AutoSaveSize = ply.AdvDupe2.TempAutoSaveSize + ply.AdvDupe2.AutoSaveOutSide = ply.AdvDupe2.TempAutoSaveOutSide + ply.AdvDupe2.AutoSaveContr = ply:GetInfo("advdupe2_auto_save_contraption")=="1" + ply.AdvDupe2.AutoSaveDesc = desc + + local time = tonumber(ply:GetInfo("advdupe2_auto_save_time")) or 5 + if(game.SinglePlayer())then + ply.AdvDupe2.AutoSavePath = net.ReadString() + else + if(time>30)then time = 30 end + if(time=225)then + degree = -135 + elseif(degree<=-225)then + degree = 135 + end + if(degree~=LastXDegree)then + XTotal = 0 + LastXDegree = degree + end + + X2 = degree + else + X2 = X2 + X + if(X2<-180)then + X2 = X2+360 + elseif(X2>180)then + X2 = X2-360 + end + end + RunConsoleCommand("advdupe2_offset_yaw", X2) + end + + /*if(Y~=0)then + local modyaw = LocalPlayer():GetAngles().y + local modyaw2 = tonumber(LocalPlayer():GetInfo("advdupe2_offset_yaw")) + + if(modyaw<0)then modyaw = modyaw + 360 else modyaw = modyaw + 180 end + if(modyaw2<0)then modyaw2 = modyaw2 + 360 else modyaw2 = modyaw2 + 180 end + + modyaw = modyaw - modyaw2 + local modyaw3 = modyaw + if(modyaw3<0)then + modyaw3 = modyaw3 * -1 + end + + local pitch = tonumber(LocalPlayer():GetInfo("advdupe2_offset_pitch")) + local roll = tonumber(LocalPlayer():GetInfo("advdupe2_offset_roll")) + + --print(modyaw3) + if(modyaw3 <= 90)then + pitch = pitch + (Y - Y * (modyaw3/90)) + roll = roll - (Y*(modyaw3/90)) + end + + --if(pitch>180)then pitch = -180 + + RunConsoleCommand("advdupe2_offset_pitch",pitch) + RunConsoleCommand("advdupe2_offset_roll",roll) + end*/ + end + + --Checks binds to modify dupes position and angles + function TOOL:Think() + + if AdvDupe2.HeadGhost then + AdvDupe2.UpdateGhosts() + end + + if(LocalPlayer():KeyDown(IN_USE))then + if(not AdvDupe2.Rotation)then + hook.Add("PlayerBindPress", "AdvDupe2_BindPress", MouseWheelScrolled) + hook.Add("CreateMove", "AdvDupe2_MouseControl", MouseControl) + AdvDupe2.Rotation = true + end + else + if(AdvDupe2.Rotation)then + AdvDupe2.Rotation = false + hook.Remove("PlayerBindPress", "AdvDupe2_BindPress") + hook.Remove("CreateMove", "AdvDupe2_MouseControl") + end + + XTotal = 0 + YTotal = 0 + LastXDegree = 0 + + return + end + end + + --Hinder the player from looking to modify offsets with the mouse + function TOOL:FreezeMovement() + return AdvDupe2.Rotation + end + + language.Add( "Tool.advdupe2.name", "Advanced Duplicator 2" ) + language.Add( "Tool.advdupe2.desc", "Duplicate things." ) + language.Add( "Tool.advdupe2.0", "Primary: Paste, Secondary: Copy, Secondary+World: Select/Deselect All, Secondary+Shift: Area copy." ) + language.Add( "Tool.advdupe2.1", "Primary: Paste, Secondary: Copy an area, Secondary+Shift: Cancel." ) + language.Add( "Undone.AdvDupe2", "Undone AdvDupe2 paste" ) + language.Add( "Cleanup.AdvDupe2", "Adv. Duplications" ) + language.Add( "Cleaned.AdvDupe2", "Cleaned up all Adv. Duplications" ) + language.Add( "SBoxLimit.AdvDupe2", "You've reached the Adv. Duplicator limit!" ) + + CreateClientConVar("advdupe2_offset_world", 0, false, true) + CreateClientConVar("advdupe2_offset_z", 0, false, true) + CreateClientConVar("advdupe2_offset_pitch", 0, false, true) + CreateClientConVar("advdupe2_offset_yaw", 0, false, true) + CreateClientConVar("advdupe2_offset_roll", 0, false, true) + CreateClientConVar("advdupe2_remove_physics", 1, false, true) + CreateClientConVar("advdupe2_make_perma", 0, false, true) + CreateClientConVar("advdupe2_original_origin", 0, false, true) + CreateClientConVar("advdupe2_original_offset_x", 0, false, true) + CreateClientConVar("advdupe2_original_offset_y", 0, false, true) + CreateClientConVar("advdupe2_original_offset_z", 0, false, true) + CreateClientConVar("advdupe2_paste_constraints", 1, false, true) + CreateClientConVar("advdupe2_sort_constraints", 1, true, true) + CreateClientConVar("advdupe2_paste_parents", 1, false, true) + CreateClientConVar("advdupe2_paste_unfreeze", 0, false, true) + CreateClientConVar("advdupe2_preserve_freeze", 0, false, true) + CreateClientConVar("advdupe2_copy_outside", 0, false, true) + CreateClientConVar("advdupe2_copy_only_mine", 1, false, true) + CreateClientConVar("advdupe2_area_copy_size", 300, false, true) + CreateClientConVar("advdupe2_auto_save_contraption", 0, false, true) + CreateClientConVar("advdupe2_auto_save_overwrite", 1, false, true) + CreateClientConVar("advdupe2_auto_save_time", 10, false, true) + + --Contraption Spawner + CreateClientConVar("advdupe2_contr_spawner_key", -1, false, true) + CreateClientConVar("advdupe2_contr_spawner_undo_key", -1, false, true) + CreateClientConVar("advdupe2_contr_spawner_delay", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_undo_delay", 10, false, true) + CreateClientConVar("advdupe2_contr_spawner_disgrav", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_disdrag", 0, false, true) + CreateClientConVar("advdupe2_contr_spawner_addvel", 1, false, true) + CreateClientConVar("advdupe2_contr_spawner_hideprops", 0, false, true) + + --Experimental + CreateClientConVar("advdupe2_paste_disparents", 0, false, true) + CreateClientConVar("advdupe2_paste_protectoveride", 1, false, true) + CreateClientConVar("advdupe2_debug_openfile", 1, false, true) + + local function BuildCPanel(CPanel) + CPanel:ClearControls() + + local FileBrowser = vgui.Create("advdupe2_browser") + CPanel:AddItem(FileBrowser) + FileBrowser:SetSize(CPanel:GetWide(),405) + AdvDupe2.FileBrowser = FileBrowser + + local Check = vgui.Create("DCheckBoxLabel") + + Check:SetText( "Сделать пропы статичными" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_remove_physics" ) + Check:SetValue( 1 ) + Check:SetToolTip("Делает все пропы статичными. Это убирает возможность их разморозить, но решает проблемы со светом и стабильностью") + CPanel:AddItem(Check) + + if LocalPlayer():query(L.permissions_permaprops) then + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Сделать постоянным" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_make_perma" ) + Check:SetValue(0) + CPanel:AddItem(Check) + end + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste at original position" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_original_origin" ) + Check:SetValue( 0 ) + Check:SetToolTip("Paste at the position originally copied") + CPanel:AddItem(Check) + + local PPos = vgui.Create("DPanel") + PPos:SetTall(43) + PPos:SetPaintBackground(false) + for _, v in ipairs({"x", "y", "z"}) do + local cont = PPos:Add("DPanel") + cont:Dock(LEFT) + cont:DockMargin(0, 0, 5, 0) + cont:SetWide(85) + cont:SetPaintBackground(false) + + local cv = GetConVar("advdupe2_original_offset_" .. v) + local nw, title = octolib.numberWang(cont, "Смещение " .. utf8.upper(v), cv:GetFloat(), -5000, 5000) + title:DockMargin(0, 0, 0, 0) + function nw:OnValueChanged(val) + cv:SetFloat(val) + AdvDupe2.UpdateGhosts(true) + end + end + CPanel:AddItem(PPos) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste with constraints" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_constraints" ) + Check:SetValue( 1 ) + Check:SetToolTip("Paste with or without constraints") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Paste with parenting" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_parents" ) + Check:SetValue( 1 ) + Check:SetToolTip("Paste with or without parenting") + CPanel:AddItem(Check) + + local Check_1 = vgui.Create("DCheckBoxLabel") + local Check_2 = vgui.Create("DCheckBoxLabel") + + Check_1:SetText( "Unfreeze all after paste" ) + Check_1:SetDark(true) + Check_1:SetConVar( "advdupe2_paste_unfreeze" ) + Check_1:SetValue( 0 ) + Check_1.OnChange = function() + if(Check_1:GetChecked() and Check_2:GetChecked())then + Check_2:SetValue(0) + end + end + Check_1:SetToolTip("Unfreeze all props after pasting") + CPanel:AddItem(Check_1) + + Check_2:SetText( "Preserve frozen state after paste" ) + Check_2:SetDark(true) + Check_2:SetConVar( "advdupe2_preserve_freeze" ) + Check_2:SetValue( 0 ) + Check_2.OnChange = function() + if(Check_2:GetChecked() and Check_1:GetChecked())then + Check_1:SetValue(0) + end + end + Check_2:SetToolTip("Makes props have the same frozen state as when they were copied") + CPanel:AddItem(Check_2) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Area copy constrained props outside of box" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_copy_outside" ) + Check:SetValue( 0 ) + Check:SetToolTip("Copy entities outside of the area copy that are constrained to entities insde") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "World/Area copy only your own props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_copy_only_mine" ) + Check:SetValue( 1 ) + Check:SetToolTip("Copy entities outside of the area copy that are constrained to entities insde") + CPanel:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Sort constraints by their connections" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_sort_constraints" ) + Check:SetValue( GetConVarNumber("advdupe2_sort_constraints") ) + Check:SetToolTip( "Orders constraints so that they build a rigid constraint system." ) + CPanel:AddItem(Check) + + local NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Area Copy Size:" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 30720 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_area_copy_size" ) + NumSlider:SetToolTip("Change the size of the area copy") + CPanel:AddItem(NumSlider) + + local Category1 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category1) + Category1:SetLabel("Offsets") + Category1:SetExpanded(0) + + + local parent = FileBrowser:GetParent():GetParent():GetParent():GetParent() + --[[Offsets]]-- + local CategoryContent1 = vgui.Create( "DPanelList" ) + CategoryContent1:SetAutoSize( true ) + CategoryContent1:SetDrawBackground( false ) + CategoryContent1:SetSpacing( 1 ) + CategoryContent1:SetPadding( 2 ) + CategoryContent1.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end --Fix the damned mouse not scrolling when it's over the catagories + + Category1:SetContents( CategoryContent1 ) + + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Height Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( 0 ) + NumSlider:SetMax( 2500 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_z") + NumSlider:SetToolTip("Change the Z offset of the dupe") + CategoryContent1:AddItem(NumSlider) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Use World Angles" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_offset_world" ) + Check:SetValue( 0 ) + Check:SetToolTip("Use world angles for the offset instead of the main entity") + CategoryContent1:AddItem(Check) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Pitch Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_pitch") + CategoryContent1:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Yaw Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_yaw") + CategoryContent1:AddItem(NumSlider) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Roll Offset" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( -180 ) + NumSlider:SetMax( 180 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar("advdupe2_offset_roll") + CategoryContent1:AddItem(NumSlider) + + local Btn = vgui.Create("DButton") + Btn:SetText("Reset") + Btn.DoClick = function() + RunConsoleCommand("advdupe2_offset_z", 0) + RunConsoleCommand("advdupe2_offset_pitch", 0) + RunConsoleCommand("advdupe2_offset_yaw", 0) + RunConsoleCommand("advdupe2_offset_roll", 0) + end + CategoryContent1:AddItem(Btn) + + + --[[Dupe Information]]-- + local Category2 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category2) + Category2:SetLabel("Dupe Information") + Category2:SetExpanded(0) + + local CategoryContent2 = vgui.Create( "DPanelList" ) + CategoryContent2:SetAutoSize( true ) + CategoryContent2:SetDrawBackground( false ) + CategoryContent2:SetSpacing( 3 ) + CategoryContent2:SetPadding( 2 ) + Category2:SetContents( CategoryContent2 ) + CategoryContent2.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + AdvDupe2.Info = {} + + local lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.File or "File: ") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.File = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Creator or "Creator:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Creator = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Date or "Date:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Date = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Time or "Time:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Time = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Size or "Size:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Size = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Desc or "Desc:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Desc = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Entities or "Entities:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Entities = lbl + + lbl = vgui.Create( "DLabel" ) + lbl:SetText(AdvDupe2.InfoText.Constraints or "Constraints:") + lbl:SetDark(true) + CategoryContent2:AddItem(lbl) + AdvDupe2.Info.Constraints = lbl + + --[[Contraption Spawner]]-- + local Category3 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category3) + Category3:SetLabel("Contraption Spawner") + Category3:SetExpanded(0) + + local CategoryContent3 = vgui.Create( "DPanelList" ) + CategoryContent3:SetAutoSize( true ) + CategoryContent3:SetDrawBackground( false ) + CategoryContent3:SetSpacing( 3 ) + CategoryContent3:SetPadding( 2 ) + Category3:SetContents( CategoryContent3 ) + CategoryContent3.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + local ctrl = vgui.Create( "CtrlNumPad" ) + ctrl:SetConVar1( "advdupe2_contr_spawner_key" ) + ctrl:SetConVar2( "advdupe2_contr_spawner_undo_key" ) + ctrl:SetLabel1( "Spawn Key") + ctrl:SetLabel2( "Undo Key" ) + CategoryContent3:AddItem(ctrl) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Spawn Delay" ) + NumSlider.Label:SetDark(true) + if(game.SinglePlayer())then + NumSlider:SetMin( 0 ) + else + local min = tonumber(GetConVarString("AdvDupe2_MinContraptionSpawnDelay")) or 0.2 + if(tonumber(LocalPlayer():GetInfo("advdupe2_contr_spawner_delay")) max)then + RunConsoleCommand("advdupe2_contr_spawner_undo_delay", tostring(max)) + end + NumSlider:SetMin( min ) + NumSlider:SetMax( max ) + end + NumSlider:SetDecimals( 1 ) + NumSlider:SetConVar("advdupe2_contr_spawner_undo_delay") + CategoryContent3:AddItem(NumSlider) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable gravity for all spawned props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_disgrav" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable drag for all spawned props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_disdrag" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Add spawner's velocity to contraption" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_addvel" ) + Check:SetValue( 1 ) + CategoryContent3:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable drawing spawner props" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_contr_spawner_hideprops" ) + Check:SetValue( 0 ) + CategoryContent3:AddItem(Check) + + --[[Area Auto Save]]-- + local Category4 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category4) + Category4:SetLabel("Area Auto Save") + Category4:SetExpanded(0) + + local CategoryContent4 = vgui.Create( "DPanelList" ) + CategoryContent4:SetAutoSize( true ) + CategoryContent4:SetDrawBackground( false ) + CategoryContent4:SetSpacing( 3 ) + CategoryContent4:SetPadding( 2 ) + Category4:SetContents( CategoryContent4 ) + CategoryContent4.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Only copy contraption" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_auto_save_contraption" ) + Check:SetValue( 0 ) + Check:SetToolTip("Only copy a contraption instead of an area") + CategoryContent4:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Overwrite File" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_auto_save_overwrite" ) + Check:SetValue( 1 ) + Check:SetToolTip("Overwrite the file instead of creating a new one everytime") + CategoryContent4:AddItem(Check) + + NumSlider = vgui.Create( "DNumSlider" ) + NumSlider:SetText( "Minutes to Save:" ) + NumSlider.Label:SetDark(true) + NumSlider:SetMin( GetConVarNumber("AdvDupe2_AreaAutoSaveTime") ) + NumSlider:SetMax( 30 ) + NumSlider:SetDecimals( 0 ) + NumSlider:SetConVar( "advdupe2_auto_save_time" ) + NumSlider:SetToolTip("Interval time to save in minutes") + CategoryContent4:AddItem(NumSlider) + + local pnl = vgui.Create("Panel") + pnl:SetWide(CPanel:GetWide()-40) + pnl:SetTall(75) + pnl:SetPos(0, 50) + CategoryContent4:AddItem(pnl) + + local label = vgui.Create("DLabel", pnl) + label:SetText("Directory: ") + label:SizeToContents() + label:SetDark(true) + label:SetPos(5,7) + + AdvDupe2.AutoSavePath = "" + local txtbox = vgui.Create("DTextEntry", pnl) + txtbox:SetWide(pnl:GetWide()-100) + txtbox:SetPos(60, 5) + txtbox:SetUpdateOnType(true) + txtbox.OnTextChanged = function(self) + self:SetValue(AdvDupe2.AutoSavePath) + end + + local btn = vgui.Create("DImageButton", pnl) + local x, y = txtbox:GetPos() + btn:SetPos(x + txtbox:GetWide() + 5, 7) + btn:SetMaterial("icon16/folder_explore.png") + btn:SizeToContents() + btn:SetToolTip("Browse") + btn.DoClick = function() + local ScrollBar = parent.VBar + ScrollBar:AnimateTo(0, 1, 0, 0.2) + + FileBrowser.Submit:SetMaterial("icon16/disk.png") + FileBrowser.Submit:SetTooltip("Directory for Area Auto Save") + if(FileBrowser.FileName:GetValue()=="Folder_Name...")then + FileBrowser.FileName:SetValue("File_Name...") + end + FileBrowser.Desc:SetVisible(true) + FileBrowser.Info:SetVisible(false) + FileBrowser.FileName:SetVisible(true) + FileBrowser.FileName:SelectAllOnFocus(true) + FileBrowser.FileName:OnMousePressed() + FileBrowser.FileName:RequestFocus() + FileBrowser.Expanding=true + FileBrowser:Slide(true) + FileBrowser.Submit.DoClick = function() + local name = FileBrowser.FileName:GetValue() + if(name=="" or name=="File_Name...")then + AdvDupe2.Notify("Name field is blank.", NOTIFY_ERROR) + FileBrowser.FileName:SelectAllOnFocus(true) + FileBrowser.FileName:OnGetFocus() + FileBrowser.FileName:RequestFocus() + return + end + local desc = FileBrowser.Desc:GetValue() + if(desc=="Description...")then desc="" end + + if(not IsValid(FileBrowser.Browser.pnlCanvas.m_pSelectedItem) or FileBrowser.Browser.pnlCanvas.m_pSelectedItem.Derma.ClassName~="advdupe2_browser_folder")then + AdvDupe2.Notify("Folder to save Area Auto Save not selected.", NOTIFY_ERROR) + return + end + + FileBrowser.AutoSaveNode = FileBrowser.Browser.pnlCanvas.m_pSelectedItem + txtbox:SetValue(FileBrowser:GetFullPath(FileBrowser.Browser.pnlCanvas.m_pSelectedItem)..name) + AdvDupe2.AutoSavePath = txtbox:GetValue() + txtbox:SetToolTip(txtbox:GetValue()) + AdvDupe2.AutoSaveDesc = desc + + FileBrowser:Slide(false) + ScrollBar:AnimateTo(ScrollBar.CanvasSize, 1, 0, 0.2) + + RunConsoleCommand("AdvDupe2_SetStage") + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", AdvDupe2.DrawSelectionBox) + end + FileBrowser.FileName.OnEnter = function() + FileBrowser.FileName:KillFocus() + FileBrowser.Desc:SelectAllOnFocus(true) + FileBrowser.Desc.OnMousePressed() + FileBrowser.Desc:RequestFocus() + end + FileBrowser.Desc.OnEnter = FileBrowser.Submit.DoClick + end + + btn = vgui.Create("DButton", pnl) + btn:SetSize(50, 35) + btn:SetPos(pnl:GetWide()/4-10, 30) + btn:SetText("Show") + btn.DoClick = function() + if(AdvDupe2.AutoSavePos)then + RunConsoleCommand("advdupe2_area_copy_size", AdvDupe2.AutoSaveSize) + LocalPlayer():SetEyeAngles( (AdvDupe2.AutoSavePos - LocalPlayer():GetShootPos()):Angle() ) + RunConsoleCommand("AdvDupe2_SetStage") + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", AdvDupe2.DrawSelectionBox) + end + end + + btn = vgui.Create("DButton", pnl) + btn:SetSize(50, 35) + btn:SetPos((pnl:GetWide()/4)*3-40, 30) + btn:SetText("Turn Off") + btn:SetDisabled(true) + btn.DoClick = function(self) + RunConsoleCommand("AdvDupe2_RemoveAutoSave") + self:SetDisabled(true) + AdvDupe2.AutoSavePos = nil + end + AdvDupe2.OffButton = btn + + + --[[Experimental Section]]-- + local Category5 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category5) + Category5:SetLabel("Experimental Section") + Category5:SetExpanded(0) + + local CategoryContent5 = vgui.Create( "DPanelList" ) + CategoryContent5:SetAutoSize( true ) + CategoryContent5:SetDrawBackground( false ) + CategoryContent5:SetSpacing( 3 ) + CategoryContent5:SetPadding( 2 ) + Category5:SetContents( CategoryContent5 ) + CategoryContent5.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable parented props physics interaction" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_disparents" ) + Check:SetValue( 0 ) + CategoryContent5:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Disable Dupe Spawn Protection" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_paste_protectoveride" ) + Check:SetValue( 1 ) + Check:SetToolTip("Check this if you things don't look right after pasting.") + CategoryContent5:AddItem(Check) + + Check = vgui.Create("DCheckBoxLabel") + Check:SetText( "Open file after Saving" ) + Check:SetDark(true) + Check:SetConVar( "advdupe2_debug_openfile" ) + Check:SetValue( 1 ) + Check:SetToolTip("Check this if you want your files to be opened after saving them.") + CategoryContent5:AddItem(Check) + + --[[Save Map]]-- + if(LocalPlayer():IsAdmin())then + local Category6 = vgui.Create("DCollapsibleCategory") + CPanel:AddItem(Category6) + Category6:SetLabel("Save Map") + Category6:SetExpanded(0) + + local CategoryContent6 = vgui.Create( "DPanelList" ) + CategoryContent6:SetAutoSize( true ) + CategoryContent6:SetDrawBackground( false ) + CategoryContent6:SetSpacing( 3 ) + CategoryContent6:SetPadding( 2 ) + Category6:SetContents( CategoryContent6 ) + CategoryContent6.OnMouseWheeled = function(self, dlta) parent:OnMouseWheeled(dlta) end + + pnl = vgui.Create("Panel") + pnl:SetWide(CPanel:GetWide()-40) + pnl:SetTall(75) + pnl:SetPos(0, 50) + CategoryContent6:AddItem(pnl) + + label = vgui.Create("DLabel", pnl) + label:SetText("File Name: ") + label:SizeToContents() + label:SetDark(true) + label:SetPos(5,7) + + AdvDupe2.AutoSavePath = "" + + local txtbox2 = vgui.Create("DTextEntry", pnl) + txtbox2:SetWide(pnl:GetWide()-100) + txtbox2:SetPos(60, 5) + txtbox2.OnEnter = function() + btn2:DoClick() + end + + local btn2 = vgui.Create("DImageButton", pnl) + x, y = txtbox2:GetPos() + btn2:SetPos(x + txtbox2:GetWide() + 5, 7) + btn2:SetMaterial("icon16/disk.png") + btn2:SizeToContents() + btn2:SetToolTip("Save Map") + btn2.DoClick = function() + if(txtbox2:GetValue()=="")then return end + RunConsoleCommand("AdvDupe2_SaveMap", txtbox2:GetValue()) + end + end + end + + function TOOL.BuildCPanel(panel) + panel:ClearControls() + panel:AddControl("Header", { + Text = "Advanced Duplicator 2", + Description = "Duplicate stuff." + }) + local function tryToBuild() + local CPanel = controlpanel.Get("advdupe2") + if CPanel and CPanel:GetWide()>16 then + BuildCPanel(CPanel) + else + timer.Simple(0.1,tryToBuild) + end + end + tryToBuild() + end + + local state = 0 + local ToColor = {r=25, g=100, b=40, a=255} + local CurColor = {r=25, g=100, b=40, a=255} + local rate + surface.CreateFont ("AD2Font", {font="Arial", size=40, weight=1000}) ---Remember to use gm_clearfonts + surface.CreateFont ("AD2TitleFont", {font="Arial", size=24, weight=1000}) + function TOOL:DrawToolScreen() + if(not AdvDupe2)then return true end + + local text = "Ready" + if(AdvDupe2.Preview)then + text = "Preview" + end + local state=0 + if(AdvDupe2.ProgressBar.Text)then + state=1 + text = AdvDupe2.ProgressBar.Text + end + + cam.Start2D() + + surface.SetDrawColor(32, 32, 32, 255) + surface.DrawRect(0, 0, 256, 256) + + if(state==0)then + ToColor = {r=25, g=100, b=40, a=255} + else + ToColor = {r=130, g=25, b=40, a=255} + end + + rate = FrameTime()*160 + CurColor.r = math.Approach( CurColor.r, ToColor.r, rate ) + CurColor.g = math.Approach( CurColor.g, ToColor.g, rate ) + + surface.SetDrawColor(CurColor) + surface.DrawRect(13, 13, 230, 230) + + surface.SetTextColor( 255, 255, 255, 255 ) + + draw.SimpleText("Advanced Duplicator 2", "AD2TitleFont", 128, 50, Color(255,255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText(text, "AD2Font", 128, 128, Color(255,255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if(state~=0)then + draw.RoundedBox( 6, 32, 178, 192, 28, Color( 255, 255, 255, 150 ) ) + draw.RoundedBox( 6, 36, 182, 188*(AdvDupe2.ProgressBar.Percent/100), 24, Color( 0, 255, 0, 255 ) ) + elseif(LocalPlayer():KeyDown(IN_USE))then + draw.SimpleText("Height: "..LocalPlayer():GetInfo("advdupe2_offset_z"), "AD2TitleFont", 25, 160, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText("Pitch: "..LocalPlayer():GetInfo("advdupe2_offset_pitch"), "AD2TitleFont", 25, 190, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText("Yaw: "..LocalPlayer():GetInfo("advdupe2_offset_yaw"), "AD2TitleFont", 25, 220, Color(255,255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + end + + cam.End2D() + end + + + local function FindInBox(min, max, ply) + + local Entities = ents.GetAll() + local EntTable = {} + for _,ent in ipairs(Entities) do + if ent:IsPlayer() then continue end + local pos = ent:GetPos() + if (pos.X>=min.X) and (pos.X<=max.X) and (pos.Y>=min.Y) and (pos.Y<=max.Y) and (pos.Z>=min.Z) and (pos.Z<=max.Z) then + --if(ent:GetClass()~="C_BaseFlexclass")then + EntTable[ent:EntIndex()] = ent + --end + end + end + + return EntTable + end + + local GreenSelected = Color(0, 255, 0, 255) + function AdvDupe2.DrawSelectionBox() + + local TraceRes = util.TraceLine(util.GetPlayerTrace(LocalPlayer())) + local i = math.Clamp(tonumber(LocalPlayer():GetInfo("advdupe2_area_copy_size")) or 50, 0, 30720) + + --Bottom Points + local B1 = (Vector(-i,-i,-i)+TraceRes.HitPos) + local B2 = (Vector(-i,i,-i)+TraceRes.HitPos) + local B3 = (Vector(i,i,-i)+TraceRes.HitPos) + local B4 = (Vector(i,-i,-i)+TraceRes.HitPos) + + --Top Points + local T1 = (Vector(-i,-i,i)+TraceRes.HitPos):ToScreen() + local T2 = (Vector(-i,i,i)+TraceRes.HitPos):ToScreen() + local T3 = (Vector(i,i,i)+TraceRes.HitPos):ToScreen() + local T4 = (Vector(i,-i,i)+TraceRes.HitPos):ToScreen() + + if(not AdvDupe2.LastUpdate or CurTime()>=AdvDupe2.LastUpdate)then + + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + local ent = AdvDupe2.ColorEntities[k] + if(IsValid(ent))then + AdvDupe2.ColorEntities[k]:SetColor(v) + end + end + end + + local Entities = FindInBox(B1, (Vector(i,i,i)+TraceRes.HitPos), LocalPlayer()) + AdvDupe2.ColorEntities = Entities + AdvDupe2.EntityColors = {} + for k,v in pairs(Entities)do + AdvDupe2.EntityColors[k] = v:GetColor() + v:SetColor(GreenSelected) + end + AdvDupe2.LastUpdate = CurTime()+0.25 + + end + + local tracedata = {} + tracedata.mask = MASK_NPCWORLDSTATIC + local WorldTrace + + tracedata.start = B1+Vector(0,0,i*2) + tracedata.endpos = B1 + WorldTrace = util.TraceLine( tracedata ) + B1 = WorldTrace.HitPos:ToScreen() + tracedata.start = B2+Vector(0,0,i*2) + tracedata.endpos = B2 + WorldTrace = util.TraceLine( tracedata ) + B2 = WorldTrace.HitPos:ToScreen() + tracedata.start = B3+Vector(0,0,i*2) + tracedata.endpos = B3 + WorldTrace = util.TraceLine( tracedata ) + B3 = WorldTrace.HitPos:ToScreen() + tracedata.start = B4+Vector(0,0,i*2) + tracedata.endpos = B4 + WorldTrace = util.TraceLine( tracedata ) + B4 = WorldTrace.HitPos:ToScreen() + + surface.SetDrawColor( 0, 255, 0, 255 ) + + --Draw Sides + surface.DrawLine(B1.x, B1.y, T1.x, T1.y) + surface.DrawLine(B2.x, B2.y, T2.x, T2.y) + surface.DrawLine(B3.x, B3.y, T3.x, T3.y) + surface.DrawLine(B4.x, B4.y, T4.x, T4.y) + + --Draw Bottom + surface.DrawLine(B1.x, B1.y, B2.x, B2.y) + surface.DrawLine(B2.x, B2.y, B3.x, B3.y) + surface.DrawLine(B3.x, B3.y, B4.x, B4.y) + surface.DrawLine(B4.x, B4.y, B1.x, B1.y) + + --Draw Top + surface.DrawLine(T1.x, T1.y, T2.x, T2.y) + surface.DrawLine(T2.x, T2.y, T3.x, T3.y) + surface.DrawLine(T3.x, T3.y, T4.x, T4.y) + surface.DrawLine(T4.x, T4.y, T1.x, T1.y) + + end + + usermessage.Hook("AdvDupe2_DrawSelectBox",function() + hook.Add("HUDPaint", "AdvDupe2_DrawSelectionBox", AdvDupe2.DrawSelectionBox) + end) + + function AdvDupe2.RemoveSelectBox() + hook.Remove("HUDPaint", "AdvDupe2_DrawSelectionBox") + if AdvDupe2.ColorEntities then + for k,v in pairs(AdvDupe2.EntityColors)do + if(not IsValid(AdvDupe2.ColorEntities[k]))then + AdvDupe2.ColorEntities[k]=nil + else + AdvDupe2.ColorEntities[k]:SetColor(v) + end + end + AdvDupe2.ColorEntities={} + AdvDupe2.EntityColors={} + end + end + usermessage.Hook("AdvDupe2_RemoveSelectBox",function() + AdvDupe2.RemoveSelectBox() + end) + + function AdvDupe2.InitProgressBar(label) + AdvDupe2.ProgressBar = {} + AdvDupe2.ProgressBar.Text = label + AdvDupe2.ProgressBar.Percent = 0 + AdvDupe2.BusyBar = true + end + usermessage.Hook("AdvDupe2_InitProgressBar",function(um) + AdvDupe2.InitProgressBar(um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_UpdateProgressBar",function(um) + AdvDupe2.ProgressBar.Percent = um:ReadChar() + end) + + function AdvDupe2.RemoveProgressBar() + AdvDupe2.ProgressBar = {} + AdvDupe2.BusyBar = false + if(AdvDupe2.Ghosting)then + AdvDupe2.InitProgressBar("Ghosting: ") + AdvDupe2.BusyBar = false + AdvDupe2.ProgressBar.Percent = AdvDupe2.CurrentGhost/AdvDupe2.TotalGhosts*100 + end + end + usermessage.Hook("AdvDupe2_RemoveProgressBar",function(um) + AdvDupe2.RemoveProgressBar() + end) + + usermessage.Hook("AdvDupe2_ResetOffsets",function(um) + RunConsoleCommand("advdupe2_original_origin", "0") + RunConsoleCommand("advdupe2_paste_constraints","1") + RunConsoleCommand("advdupe2_offset_z","0") + RunConsoleCommand("advdupe2_offset_pitch","0") + RunConsoleCommand("advdupe2_offset_yaw","0") + RunConsoleCommand("advdupe2_offset_roll","0") + RunConsoleCommand("advdupe2_paste_parents","1") + RunConsoleCommand("advdupe2_paste_disparents","0") + end) + + usermessage.Hook("AdvDupe2_ReportModel",function(um) + print("Advanced Duplicator 2: Invalid Model: "..um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_ReportClass",function(um) + print("Advanced Duplicator 2: Invalid Class: "..um:ReadString()) + end) + + usermessage.Hook("AdvDupe2_ResetDupeInfo", function(um) + if not AdvDupe2.Info then return end + AdvDupe2.Info.File:SetText("File:") + AdvDupe2.Info.Creator:SetText("Creator:") + AdvDupe2.Info.Date:SetText("Date:") + AdvDupe2.Info.Time:SetText("Time:") + AdvDupe2.Info.Size:SetText("Size:") + AdvDupe2.Info.Desc:SetText("Desc:") + AdvDupe2.Info.Entities:SetText("Entities:") + AdvDupe2.Info.Constraints:SetText("Constraints:") + end) + + usermessage.Hook("AdvDupe2_CanAutoSave", function(um) + if(AdvDupe2.AutoSavePath~="")then + AdvDupe2.AutoSavePos = um:ReadVector() + AdvDupe2.AutoSaveSize = um:ReadShort() + local ent = um:ReadShort() + AdvDupe2.OffButton:SetDisabled(false) + net.Start("AdvDupe2_CanAutoSave") + net.WriteString(AdvDupe2.AutoSaveDesc) + net.WriteInt(ent, 16) + if(game.SinglePlayer())then + net.WriteString(string.sub(AdvDupe2.AutoSavePath, 10, #AdvDupe2.AutoSavePath)) + end + net.SendToServer() + else + AdvDupe2.Notify("Select a directory for the Area Auto Save.", NOTIFY_ERROR) + end + end) + + net.Receive("AdvDupe2_SetDupeInfo", function(len, ply, len2) + if AdvDupe2.Info then + AdvDupe2.Info.File:SetText("File: "..net.ReadString()) + AdvDupe2.Info.Creator:SetText("Creator: "..net.ReadString()) + AdvDupe2.Info.Date:SetText("Date: "..net.ReadString()) + AdvDupe2.Info.Time:SetText("Time: "..net.ReadString()) + AdvDupe2.Info.Size:SetText("Size: "..net.ReadString()) + AdvDupe2.Info.Desc:SetText("Desc: "..net.ReadString()) + AdvDupe2.Info.Entities:SetText("Entities: "..net.ReadString()) + AdvDupe2.Info.Constraints:SetText("Constraints: "..net.ReadString()) + else + AdvDupe2.InfoText.File = "File: "..net.ReadString() + AdvDupe2.InfoText.Creator = "Creator: "..net.ReadString() + AdvDupe2.InfoText.Date = "Date: "..net.ReadString() + AdvDupe2.InfoText.Time = "Time: "..net.ReadString() + AdvDupe2.InfoText.Size = "Size: "..net.ReadString() + AdvDupe2.InfoText.Desc = "Desc: "..net.ReadString() + AdvDupe2.InfoText.Entities = "Entities: "..net.ReadString() + AdvDupe2.InfoText.Constraints = "Constraints: "..net.ReadString() + end + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advmat.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advmat.lua new file mode 100644 index 0000000..c434fb9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advmat.lua @@ -0,0 +1,376 @@ +AddCSLuaFile(); + +TOOL.Category = "Dobrograd" +TOOL.Name = "Advanced Material" +TOOL.ClientConVar["texture"] = ""; +TOOL.ClientConVar["noisetexture"] = "concrete"; +TOOL.ClientConVar["scalex"] = "1"; +TOOL.ClientConVar["scaley"] = "1"; +TOOL.ClientConVar["offsetx"] = "0"; +TOOL.ClientConVar["offsety"] = "0"; +TOOL.ClientConVar["usenoise"] = "0"; +TOOL.ClientConVar["noisescalex"] = "1"; +TOOL.ClientConVar["noisescaley"] = "1"; +TOOL.ClientConVar["noiseoffsetx"] = "0"; +TOOL.ClientConVar["noiseoffsety"] = "0"; +TOOL.DetailWhitelist = { + "concrete", + "metal", + "plaster", + "rock" +}; +TOOL.DetailTranslation = { + concrete = "detail/noise_detail_01", + rock = "detail/rock_detail_01", + metal = "detail/metal_detail_01", + plaster = "detail/plaster_detail_01" +}; +TOOL.Information = { + {name = "left"}, + {name = "right"}, + {name = "reload"} +}; + +/* + MATERIALIZE +*/ + +function TOOL:LeftClick(trace) + if (!IsValid(trace.Entity)) then return false; end; + if (trace.Entity:IsPlayer()) then return false; end; + if (CLIENT) then return true; end; + + local texture = self:GetClientInfo("texture"); + local scalex = tonumber(self:GetClientInfo("scalex")); + local scaley = tonumber(self:GetClientInfo("scaley")); + local offsetx = tonumber(self:GetClientInfo("offsetx")); + local offsety = tonumber(self:GetClientInfo("offsety")); + local usenoise = tobool(self:GetClientInfo("usenoise")); + local noisetexture = self.DetailTranslation[self:GetClientInfo("noisetexture")] or "detail/noise_detail_01"; + local noisescalex = tonumber(self:GetClientInfo("noisescalex")); + local noisescaley = tonumber(self:GetClientInfo("noisescaley")); + local noiseoffsetx = tonumber(self:GetClientInfo("noiseoffsetx")); + local noiseoffsety = tonumber(self:GetClientInfo("noiseoffsety")); + + materials:Set(trace.Entity, string.Trim(texture):lower(), { + ScaleX = scalex, + ScaleY = scaley, + OffsetX = offsetx, + OffsetY = offsety, + UseNoise = usenoise, + NoiseTexture = noisetexture, + NoiseScaleX = noisescalex, + NoiseScaleY = noisescaley, + NoiseOffsetX = noiseoffsetx, + NoiseOffsetY = noiseoffsety + }); + + return true; +end; + +function TOOL:RightClick(trace) + if (trace.Entity:IsPlayer()) then return false; end; + if (CLIENT) then return true; end; + + local bIsMat = false; + + if (IsValid(trace.Entity)) then + if (trace.Entity:GetMaterial() != "") then + if (trace.Entity:GetMaterial():sub(1, 1) != "!") then + bIsMat = true; + end; + end; + end; + + if (!bIsMat and trace.HitTexture[1] == "*" and !trace.Entity.MaterialData) then + return false; + end; + + local tempMat = Material(trace.HitTexture); + local hitNoise = tempMat:GetString("$detail"); + local noiseTexture = false; + + for k, v in pairs(self.DetailTranslation) do + if (v == hitNoise) then + noiseTexture = k; + break; + end; + end; + + local data = trace.Entity.MaterialData or { + texture = bIsMat and trace.Entity:GetMaterial() or trace.HitTexture, + scalex = 1, + scaley = 1, + offsetx = 0, + offsety = 0, + usenoise = noiseTexture and 1 or 0, + noisetexture = noiseTexture + }; + + for k, v in pairs(data) do + if (isbool(v)) then continue; end; + + self:GetOwner():ConCommand("advmat_" .. k:lower() .. " " .. v); + end; + + return true; +end; + +function TOOL:Reload(trace) + if (!IsValid(trace.Entity)) then return false; end; + if (CLIENT) then return true; end; + + materials:Set(trace.Entity, "", {}); + + return true; +end; + +// function TOOL:UpdateGhostMat(player, ent) +// if (!IsValid(ent)) then return; end; +// local trace = player:GetEyeTrace(); + +// if (!IsValid(trace.Entity)) then ent:SetNoDraw(true); return; end; + +// ent:SetModel(trace.Entity:GetModel()); +// ent:SetAngles(trace.Entity:GetAngles()); + +// ent:SetPos(trace.Entity:GetPos()); +// ent:SetNoDraw(false); +// ent:SetColor(Color(255, 255, 255, 255)); + +// ent:SetMaterial("!AdvMatPreview"); +// end; + +local noBump = Material("debug/debugdrawflat"):GetTexture("$bumpmap"); +function TOOL:Think() + if (CLIENT) then + local texture = self:GetClientInfo("texture"); + local scalex = self:GetClientNumber("scalex", 1); + local scaley = self:GetClientNumber("scaley", 1); + local offsetx = self:GetClientNumber("offsetx"); + local offsety = self:GetClientNumber("offsety"); + + local bUseNoise = tobool(self:GetClientInfo("usenoise")); + local noisescalex = self:GetClientNumber("noisescalex", 1); + local noisescaley = self:GetClientNumber("noisescaley", 1); + local noiseoffsetx = self:GetClientNumber("noiseoffsetx", 0); + local noiseoffsety = self:GetClientNumber("noiseoffsety", 0); + + if (texture == "") then + return; + end; + + if (!self.PreviewMat or !self.PreviewMatNoise) then + self.PreviewMat = CreateMaterial("AdvMatPreview", "VertexLitGeneric", { + ["$basetexture"] = texture, + ["$basetexturetransform"] = "center .5 .5 scale " .. (1 / scalex) .. " " .. (1 / scaley) .. " rotate 0 translate " .. offsetx .. " " .. offsety, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 0 + }); + + self.PreviewMatNoise = { + concrete = CreateMaterial("AdvMatPreviewNoiseConcrete", "VertexLitGeneric", { + ["$basetexture"] = texture, + ["$basetexturetransform"] = "center .5 .5 scale " .. (1 / noisescalex) .. " " .. (1 / noisescaley) .. " rotate 0 translate " .. noiseoffsetx .. " " .. noiseoffsety, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 0, + ["$detail"] = "detail/noise_detail_01", + ["$detailtexturetransform"] = "center .5 .5 scale 1 1 rotate 0 translate 0 0", + ["$detailblendmode"] = 0, + }), + + rock = CreateMaterial("AdvMatPreviewNoiseRock", "VertexLitGeneric", { + ["$basetexture"] = texture, + ["$basetexturetransform"] = "center .5 .5 scale " .. (1 / noisescalex) .. " " .. (1 / noisescaley) .. " rotate 0 translate " .. noiseoffsetx .. " " .. noiseoffsety, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 0, + ["$detail"] = "detail/rock_detail_01", + ["$detailtexturetransform"] = "center .5 .5 scale 1 1 rotate 0 translate 0 0", + ["$detailblendmode"] = 0, + }), + + metal = CreateMaterial("AdvMatPreviewNoiseMetal", "VertexLitGeneric", { + ["$basetexture"] = texture, + ["$basetexturetransform"] = "center .5 .5 scale " .. (1 / noisescalex) .. " " .. (1 / noisescaley) .. " rotate 0 translate " .. noiseoffsetx .. " " .. noiseoffsety, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 0, + ["$detail"] = "detail/metal_detail_01", + ["$detailtexturetransform"] = "center .5 .5 scale 1 1 rotate 0 translate 0 0", + ["$detailblendmode"] = 0, + }), + + plaster = CreateMaterial("AdvMatPreviewNoisePlaster", "VertexLitGeneric", { + ["$basetexture"] = texture, + ["$basetexturetransform"] = "center .5 .5 scale " .. (1 / noisescalex) .. " " .. (1 / noisescaley) .. " rotate 0 translate " .. noiseoffsetx .. " " .. noiseoffsety, + ["$vertexcolor"] = 1, + ["$vertexalpha"] = 0, + ["$detail"] = "detail/plaster_detail_01", + ["$detailtexturetransform"] = "center .5 .5 scale 1 1 rotate 0 translate 0 0", + ["$detailblendmode"] = 0, + }), + + }; + end; + + if (bUseNoise) then + local noiseMatrix = Matrix(); + noiseMatrix:Scale(Vector(1 / noisescalex, 1 / noisescaley, 1)); + noiseMatrix:Translate(Vector(noiseoffsetx, noiseoffsety, 0)); + + local noiseTexture = self:GetClientInfo("noisetexture"); + + if (!table.HasValue(self.DetailWhitelist, noiseTexture:lower())) then + noiseTexture = "concrete"; + end; + + self.PreviewMatNoise[noiseTexture]:SetMatrix("$detailtexturetransform", noiseMatrix); + + if (self.noise != self:GetClientInfo("noisetexture")) then + self.noise = noiseTexture; + + self.Preview = self.PreviewMatNoise[noiseTexture] + end; + end; + + local mat = bUseNoise and self.Preview or self.PreviewMat; + + local matrix = Matrix(); + matrix:Scale(Vector(1 / scalex, 1 / scaley, 1)); + matrix:Translate(Vector(offsetx, offsety, 0)); + + if (mat:GetString("$basetexture") != texture) then + mat:SetTexture("$basetexture", Material(texture):GetTexture("$basetexture")); + end; + + mat:SetMatrix("$basetexturetransform", matrix); + end; +end; + +if (CLIENT) then + function TOOL:DrawHUD() + + end; + + hook.Add("PostDrawOpaqueRenderables", "AdvMatPreview", function() + local player = LocalPlayer(); + + if (!IsValid(player)) then return; end; + + if (IsValid(player:GetActiveWeapon()) and player:GetActiveWeapon():GetClass() == "gmod_tool") then + local toolObj = player:GetTool(); + + if (!toolObj) then return; end; + + if (toolObj.Name != "Advanced Material") then return; end; + + local ent = player:GetEyeTrace().Entity; + + if (IsValid(ent)) then + local mat = tobool(toolObj:GetClientInfo("usenoise")) and toolObj.Preview or toolObj.PreviewMat; + + render.MaterialOverride(mat); + ent:DrawModel(); + render.MaterialOverride(); + end; + end; + end); +end; + +/* + Holster + Clear stored objects and reset state +*/ + +function TOOL:Holster() + self:ClearObjects(); + self:SetStage(0); + self:ReleaseGhostEntity(); +end; + +/* + Control Panel +*/ +do + local transformData = { + scalex = 1, + scaley = 1, + offsetx = 0, + offsety = 0 + }; + + local ConVarsDefault = TOOL:BuildConVarList() + function TOOL.BuildCPanel(CPanel) + CPanel:AddControl("Header", { + Description = "#tool.advmat.desc" + }); + + CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "advmat", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + CPanel:TextEntry("#tool.advmat.texture", "advmat_texture"); + + CPanel:NumSlider("#tool.advmat.scalex", "advmat_scalex", 0.01, 5, 2); + CPanel:NumSlider("#tool.advmat.scaley", "advmat_scaley", 0.01, 5, 2); + CPanel:NumSlider("#tool.advmat.offsetx", "advmat_offsetx", 0, 5, 2); + CPanel:NumSlider("#tool.advmat.offsety", "advmat_offsety", 0, 5, 2); + + local baseTextureReset = CPanel:Button("#tool.advmat.reset.base"); + + function baseTextureReset:DoClick() + for k, v in pairs(transformData) do + LocalPlayer():ConCommand("advmat_" .. k:lower() .. " " .. v); + end; + end; + + CPanel:CheckBox("#tool.advmat.usenoise", "advmat_usenoise"); + CPanel:ControlHelp("If this box is checked, your material will be sharpened using an HD detail texture, controlled by the settings below."); + + CPanel:AddControl("ComboBox", { + Label = "#tool.advmat.noisetexture", + Options = list.Get("tool.advmat.details") + }); + + CPanel:NumSlider("#tool.advmat.scalex", "advmat_noisescalex", 0.01, 5, 2); + CPanel:NumSlider("#tool.advmat.scaley", "advmat_noisescaley", 0.01, 5, 2); + CPanel:NumSlider("#tool.advmat.offsetx", "advmat_noiseoffsetx", 0, 5, 2); + CPanel:NumSlider("#tool.advmat.offsety", "advmat_noiseoffsety", 0, 5, 2); + + local noiseTextureReset = CPanel:Button("#tool.advmat.reset.noise"); + + function noiseTextureReset:DoClick() + for k, v in pairs(transformData) do + LocalPlayer():ConCommand("advmat_noise" .. k:lower() .. " " .. v); + end; + end; + end; +end; +/* + Language strings +*/ + +if (CLIENT) then + language.Add("tool.advmat.name", "Advanced Material"); + language.Add("tool.advmat.left", "Set material"); + language.Add("tool.advmat.right", "Copy material"); + language.Add("tool.advmat.reload", "Remove material"); + language.Add("tool.advmat.desc", "Use any material on any prop, with the ability to copy materials from the map."); + language.Add("tool.advmat.texture", "Material to use"); + language.Add("tool.advmat.scalex", "Width Magnification"); + language.Add("tool.advmat.scaley", "Height Magnification"); + language.Add("tool.advmat.offsetx", "Horizontal Translation"); + language.Add("tool.advmat.offsety", "Vertical Translation"); + language.Add("tool.advmat.usenoise", "Use noise texture"); + + language.Add("tool.advmat.noisetexture", "Detail type"); + + language.Add("tool.advmat.reset.base", "Reset Texture Transformations"); + language.Add("tool.advmat.reset.noise", "Reset Noise Transformations"); + + language.Add("tool.advmat.details.concrete", "Concrete"); + language.Add("tool.advmat.details.metal", "Metal"); + language.Add("tool.advmat.details.plaster", "Plaster"); + language.Add("tool.advmat.details.rock", "Rock"); + + list.Set("tool.advmat.details", "#tool.advmat.details.concrete", {advmat_noisetexture = "concrete"}); + list.Set("tool.advmat.details", "#tool.advmat.details.metal", {advmat_noisetexture = "metal"}); + list.Set("tool.advmat.details", "#tool.advmat.details.plaster", {advmat_noisetexture = "plaster"}); + list.Set("tool.advmat.details", "#tool.advmat.details.rock", {advmat_noisetexture = "rock"}); +end; \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advresizer.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advresizer.lua new file mode 100644 index 0000000..5a92626 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/advresizer.lua @@ -0,0 +1,1021 @@ + +TOOL.Category = "Dobrograd" +TOOL.Name = L.resizer +TOOL.ClientConVar[ "sx" ] = "1.0" +TOOL.ClientConVar[ "sy" ] = "1.0" +TOOL.ClientConVar[ "sz" ] = "1.0" +TOOL.ClientConVar[ "smwo" ] = "1" +TOOL.ClientConVar[ "cx" ] = "1.0" +TOOL.ClientConVar[ "cy" ] = "1.0" +TOOL.ClientConVar[ "cz" ] = "1.0" +TOOL.ClientConVar[ "prco" ] = "0" +TOOL.ClientConVar[ "dcp" ] = "0" + +local function IsValidEntity( ent ) + return isentity( ent ) and ent:IsValid() +end + +local function IsValidPhysicsObject( physobj ) + return ( TypeID( physobj ) == TYPE_PHYSOBJ ) and physobj:IsValid() +end +local RESET = Vector( 1, 1, 1 ) +local EMPTY = Vector( 0, 0, 0 ) +local ENT = {} +ENT.Type = "anim" +ENT.Spawnable = false +ENT.DisableDuplicator = true + +local function FindSizeHandler( ent ) + for k, handler in pairs( ents.FindByClass( "sizehandler" ) ) do + if ( handler:GetParent() == ent ) then return handler end + end +end + +local function ResizePhysics( ent, scale ) + ent:PhysicsInit( SOLID_VPHYSICS ) + local physobj = ent:GetPhysicsObject() + if ( not IsValidPhysicsObject( physobj ) ) then return false end + local physmesh = physobj:GetMeshConvexes() + if ( not istable( physmesh ) ) or ( #physmesh < 1 ) then return false end + for convexkey, convex in pairs( physmesh ) do + for poskey, postab in pairs( convex ) do + convex[ poskey ] = postab.pos * scale + end + end + ent:PhysicsInitMultiConvex( physmesh ) + ent:EnableCustomCollisions( true ) + return IsValidPhysicsObject( ent:GetPhysicsObject() ) +end +if ( SERVER ) then + + local meta = FindMetaTable( "Entity" ) +-- if ( meta ) then + local o_StartMotionController = meta.StartMotionController + meta.StartMotionController = function( ent ) + o_StartMotionController( ent ) + ent.IsMotionControlled = true + end + local o_StopMotionController = meta.StopMotionController + meta.StopMotionController = function( ent ) + o_StopMotionController( ent ) + ent.IsMotionControlled = nil + end +-- end + + local function HasValidPhysics( ent ) + return ( ent:GetSolid() == SOLID_VPHYSICS ) and ( ent:GetPhysicsObjectCount() == 1 ) + end + + local ConstraintData = {} + local function ForgetConstraint( ent, RConstraint ) + local Constraints = ent.Constraints + if ( Constraints ) then + local NewTab = {} + for k, Constraint in pairs( Constraints ) do + if ( Constraint ~= RConstraint ) then + table.insert( NewTab, Constraint ) + end + end + ent.Constraints = NewTab + end + end + + local function SafeInsert( t, k, v ) + if ( v ) then + t[ k ] = v + else + t[ k ] = false + end + end + + local function GetConstraintVals( ent, Constraint, Type ) + for Arg, Val in pairs( Constraint:GetTable() ) do + if ( ( string.sub( Arg, 1, 3 ) == "Ent" ) and IsValidEntity( Val ) and ( Val ~= ent ) ) then + ForgetConstraint( Val, Constraint ) + end + end + local Factory = duplicator.ConstraintType[ Type ] + local ConstraintVals = {} + for Key, Arg in pairs( Factory.Args ) do + SafeInsert( ConstraintVals, Key, Constraint[ Arg ] ) + end + ConstraintData[ Constraint ] = { Factory.Func, ConstraintVals } + Constraint:Remove() + end + + local function GetAndResizeConstraintVals( ent, Constraint, Type, scale ) + local LPos = {} + for Arg, Val in pairs( Constraint:GetTable() ) do + if ( string.sub( Arg, 1, 3 ) == "Ent" ) then + if ( Val == ent ) then + table.insert( LPos, "LPos"..string.sub( Arg, 4 ) ) + elseif ( IsValidEntity( Val ) ) then + ForgetConstraint( Val, Constraint ) + end + end + end + local Factory = duplicator.ConstraintType[ Type ] + local ConstraintVals = {} + for Key, Arg in pairs( Factory.Args ) do + if ( table.HasValue( LPos, Arg ) ) then + local Val = Constraint[ Arg ] + if ( isvector( Val ) ) then + ConstraintVals[ Key ] = Val * scale + else + SafeInsert( ConstraintVals, Key, Val ) + end + else + SafeInsert( ConstraintVals, Key, Constraint[ Arg ] ) + end + end + ConstraintData[ Constraint ] = { Factory.Func, ConstraintVals } + Constraint:Remove() + end + + local function StoreConstraintData( ent ) + local Constraints = ent.Constraints + if ( Constraints ) then + for Key, Constraint in pairs( Constraints ) do + if ( IsValidEntity( Constraint ) and ( ConstraintData[ Constraint ] == nil ) ) then + local Type = Constraint.Type + if ( Type ) then + GetConstraintVals( ent, Constraint, Type ) + end + end + Constraints[ Key ] = nil + end + end + end + + local function ResizeAndStoreConstraintData( ent, scale, oldscale ) + local Constraints = ent.Constraints + if ( Constraints ) then + for Key, Constraint in pairs( Constraints ) do + if ( IsValidEntity( Constraint ) and ( ConstraintData[ Constraint ] == nil ) ) then + local Type = Constraint.Type + if ( Type ) then + if ( Type == "Axis" ) then + GetConstraintVals( ent, Constraint, Type ) + else + GetAndResizeConstraintVals( ent, Constraint, Type, Vector( scale.x / oldscale.x, scale.y / oldscale.y, scale.z / oldscale.z ) ) + end + end + end + Constraints[ Key ] = nil + end + end + end + + local function ApplyConstraintData() + for OldConstraint, Factory in pairs( ConstraintData ) do + local NewConstraint = Factory[ 1 ]( unpack( Factory[ 2 ] ) ) + if ( IsValidEntity( NewConstraint ) ) then + -- undo.ReplaceEntity( OldConstraint, NewConstraint ) + -- cleanup.ReplaceEntity( OldConstraint, NewConstraint ) + end + ConstraintData[ OldConstraint ] = nil + end + end + + local PhysicsData = {} + local function StorePhysicsData( physobj ) + PhysicsData[ 1 ] = physobj:IsGravityEnabled() + PhysicsData[ 2 ] = physobj:GetMaterial() + PhysicsData[ 3 ] = physobj:IsCollisionEnabled() + PhysicsData[ 4 ] = physobj:IsDragEnabled() + PhysicsData[ 5 ] = physobj:GetVelocity() + PhysicsData[ 6 ] = physobj:GetAngleVelocity() + PhysicsData[ 7 ] = physobj:IsMotionEnabled() + end + + local function ApplyPhysicsData( physobj ) + physobj:EnableGravity( PhysicsData[ 1 ] ) + physobj:SetMaterial( PhysicsData[ 2 ] ) + physobj:EnableCollisions( PhysicsData[ 3 ] ) + physobj:EnableDrag( PhysicsData[ 4 ] ) + physobj:SetVelocity( PhysicsData[ 5 ] ) + physobj:AddAngleVelocity( PhysicsData[ 6 ] - physobj:GetAngleVelocity() ) + physobj:EnableMotion( PhysicsData[ 7 ] ) + end + + local models_error = Model( "models/error.mdl" ) + local function CreateSizeHandler( ent ) + local handler = ents.Create( "sizehandler" ) + handler:SetPos( ent:GetPos() ) + handler:SetAngles( ent:GetAngles() ) + handler:SetModel( models_error ) + handler:SetNoDraw( true ) + handler:DrawShadow( false ) + handler:SetNotSolid( true ) + handler:SetMoveType( MOVETYPE_NONE ) + handler:SetParent( ent ) + handler:SetTransmitWithParent( true ) + handler:Spawn() + return handler + end + + local function GetSizeHandler( ent ) + local handler = FindSizeHandler( ent ) + if ( IsValidEntity( handler ) ) then return handler end + return CreateSizeHandler( ent ) + end + + local ResizedEntities = {} + local function CreateSizeData( ent, physobj ) + for k, v in pairs( ResizedEntities ) do if ( not IsValidEntity( k ) ) then ResizedEntities[ k ] = nil end end + local sizedata = {} + sizedata[ 1 ] = Vector( 1, 1, 1 ) + sizedata[ 2 ], sizedata[ 3 ] = ent:GetCollisionBounds() + sizedata[ 4 ] = physobj:GetMass() + ResizedEntities[ ent ] = sizedata + return sizedata + end + +--[[ + hook.Add( "EntityRemoved", "advresizer", function( ent ) + if ( ResizedEntities[ ent ] ~= nil ) then ResizedEntities[ ent ] = nil end + end )]] + saverestore.AddSaveHook( "advresizer", function( save ) + save:StartBlock( "advresizer_SaveData" ) + local EntitiesToSave = {} + for ent, sizedata in pairs( ResizedEntities ) do + if ( IsValidEntity( ent ) ) then + table.insert( EntitiesToSave, { ent, sizedata } ) + else + ResizedEntities[ ent ] = nil + end + end + local l = #EntitiesToSave + save:WriteInt( l ) + for Key = 1, l do + local Factory = EntitiesToSave[ Key ] + local ent = Factory[ 1 ] + save:WriteEntity( ent ) + local savedata = { Factory[ 2 ] } + if ( HasValidPhysics( ent ) ) then + local physobj = ent:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + savedata[ 2 ] = { + physobj:IsGravityEnabled(), + physobj:GetMaterial(), + physobj:IsCollisionEnabled(), + physobj:IsDragEnabled(), + physobj:GetVelocity(), + physobj:GetAngleVelocity(), + physobj:IsMotionEnabled(), + physobj:IsAsleep() + } + end + end + saverestore.WriteTable( savedata, save ) + end + save:EndBlock() + end ) + + local EntitiesToRestore = {} + saverestore.AddRestoreHook( "advresizer", function( restore ) + local name = restore:StartBlock() + if ( name == "advresizer_SaveData" ) then + local l = restore:ReadInt() + for i = 1, l do + local ent = restore:ReadEntity() + local savedata = saverestore.ReadTable( restore ) + if ( IsValidEntity( ent ) ) then + EntitiesToRestore[ ent ] = savedata + end + end + end + restore:EndBlock() + end ) + + hook.Add( "Restored", "advresizer", function() + local PhysicsData_Restore = {} + for ent, savedata in pairs( EntitiesToRestore ) do + local sizedata = savedata[ 1 ] + ResizedEntities[ ent ] = sizedata + local physdata = savedata[ 2 ] + if ( physdata ) then + local scale = sizedata[ 1 ] + StoreConstraintData( ent ) + PhysicsData_Restore[ ent ] = physdata + local success = ResizePhysics( ent, scale ) + ent:SetCollisionBounds( sizedata[ 2 ] * scale, sizedata[ 3 ] * scale ) + if ( success ) then + local physobj = ent:GetPhysicsObject() + physobj:SetMass( math.Clamp( sizedata[ 4 ] * scale.x * scale.y * scale.z, 0.1, 50000 ) ) + physobj:SetDamping( 0, 0 ) + else + PhysicsData_Restore[ ent ] = nil + end + end + EntitiesToRestore[ ent ] = nil + end + ApplyConstraintData() + for ent, physdata in pairs( PhysicsData_Restore ) do + local physobj = ent:GetPhysicsObject() + physobj:EnableGravity( physdata[ 1 ] ) + physobj:SetMaterial( physdata[ 2 ] ) + physobj:EnableCollisions( physdata[ 3 ] ) + physobj:EnableDrag( physdata[ 4 ] ) + physobj:SetVelocity( physdata[ 5 ] ) + physobj:AddAngleVelocity( physdata[ 6 ] - physobj:GetAngleVelocity() ) + physobj:EnableMotion( physdata[ 7 ] ) + if ( physdata[ 8 ] ) then physobj:Sleep() else physobj:Wake() end + if ( ent.IsMotionControlled ) then o_StartMotionController( ent ) end + end + end ) + + duplicator.RegisterEntityModifier( "advr", function( ply, ent, data ) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'advresizer') + if override == false then return end + local pscale = Vector( data[ 1 ], data[ 2 ], data[ 3 ] ) + local vscale = Vector( data[ 4 ], data[ 5 ], data[ 6 ] ) + if ( pscale ~= RESET ) and ( HasValidPhysics( ent ) ) then + local physobj = ent:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + local sizedata = CreateSizeData( ent, physobj ) + sizedata[ 1 ]:Set( pscale ) + StorePhysicsData( physobj ) + local success = ResizePhysics( ent, pscale ) + if ( data[ 7 ] ) then + GetSizeHandler( ent ):SetActualPhysicsScale( tostring( RESET ) ) + else + GetSizeHandler( ent ):SetActualPhysicsScale( tostring( pscale ) ) + end + ent:SetCollisionBounds( sizedata[ 2 ] * pscale, sizedata[ 3 ] * pscale ) + if ( success ) then + physobj = ent:GetPhysicsObject() + physobj:SetMass( math.Clamp( sizedata[ 4 ] * pscale.x * pscale.y * pscale.z, 0.1, 50000 ) ) + physobj:SetDamping( 0, 0 ) + ApplyPhysicsData( physobj ) + physobj:Wake() + if ( ent.IsMotionControlled ) then o_StartMotionController( ent ) end + end + end + end + local handler = GetSizeHandler( ent ) + handler:SetVisualScale( tostring( vscale ) ) + + ent._size = pscale + end ) + + function meta:SetSize(size) + if size ~= RESET then + self._size = size + self:SetNetVar('size', size) + duplicator.StoreEntityModifier( self, 'advr', { size.x, size.y, size.z, size.x, size.y, size.z } ) + else + self._size = nil + self:SetNetVar('size', nil) + duplicator.ClearEntityModifier( self, 'advr' ) + GetSizeHandler( self ):SetActualPhysicsScale( tostring( RESET ) ) + end + + if ( size ~= RESET ) and ( HasValidPhysics( self ) ) then + local physobj = self:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + local sizedata = CreateSizeData( self, physobj ) + sizedata[ 1 ]:Set( size ) + StorePhysicsData( physobj ) + local success = ResizePhysics( self, size ) + GetSizeHandler( self ):SetActualPhysicsScale( tostring( size ) ) + self:SetCollisionBounds( sizedata[ 2 ] * size, sizedata[ 3 ] * size ) + if ( success ) then + physobj = self:GetPhysicsObject() + physobj:SetMass( math.Clamp( sizedata[ 4 ] * size.x * size.y * size.z, 0.1, 50000 ) ) + physobj:SetDamping( 0, 0 ) + ApplyPhysicsData( physobj ) + physobj:Wake() + if ( self.IsMotionControlled ) then o_StartMotionController( self ) end + end + end + end + + local handler = GetSizeHandler( self ) + handler:SetVisualScale( tostring( size ) ) + + net.Start( "advresizer_set_visual_size" ) + net.WriteEntity( self ) + net.WriteString( tostring( size ) ) + net.Broadcast() + + net.Start( "advresizer_set_physical_size" ) + net.WriteEntity( self ) + net.WriteString( tostring( size ) ) + net.Broadcast() + + end + + function meta:GetSize() + return self._size and Vector(self._size) or Vector(1,1,1) + end + + function TOOL:GetClientBool( name ) + return tobool( self:GetClientInfo( name ) ) + end + + local SIZEHANDLER = NULL + local WAS_RESIZED = false + util.AddNetworkString( "advresizer_set_physical_size" ) + util.AddNetworkString( "advresizer_fix_physical_size" ) + + function TOOL:SetPhysicalSize( ent, scale ) + ent._size = pscale + if ( HasValidPhysics( ent ) ) then + local physobj = ent:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + local sizedata = ResizedEntities[ ent ] or CreateSizeData( ent, physobj ) + if ( self:GetClientBool( "prco" ) ) then StoreConstraintData( ent ) else ResizeAndStoreConstraintData( ent, scale, sizedata[ 1 ] ) end + StorePhysicsData( physobj ) + local success = ResizePhysics( ent, scale ) + if ( self:GetClientBool( "dcp" ) ) then + net.Start( "advresizer_fix_physical_size" ) + net.WriteEntity( ent ) + net.Broadcast() + if ( WAS_RESIZED ) then + SIZEHANDLER:SetActualPhysicsScale( tostring( RESET ) ) + end + else + if ( WAS_RESIZED ) then + net.Start( "advresizer_set_physical_size" ) + net.WriteEntity( ent ) + net.WriteString( tostring( scale ) ) + net.Broadcast() + else + SIZEHANDLER = CreateSizeHandler( ent ) + end + SIZEHANDLER:SetActualPhysicsScale( tostring( scale ) ) + end + ent:SetCollisionBounds( sizedata[ 2 ] * scale, sizedata[ 3 ] * scale ) + if ( success ) then + physobj = ent:GetPhysicsObject() + physobj:SetMass( math.Clamp( sizedata[ 4 ] * scale.x * scale.y * scale.z, 0.1, 50000 ) ) + physobj:SetDamping( 0, 0 ) + ApplyConstraintData() + ApplyPhysicsData( physobj ) + physobj:Wake() + if ( ent.IsMotionControlled ) then o_StartMotionController( ent ) end + else + ApplyConstraintData() + end + sizedata[ 1 ]:Set( scale ) + end + end + end + + function TOOL:FixPhysicalSize( ent ) + ent._size = nil + if ( HasValidPhysics( ent ) ) then + local physobj = ent:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + local sizedata = ResizedEntities[ ent ] + if ( not sizedata ) then return end + if ( self:GetClientBool( "prco" ) ) then StoreConstraintData( ent ) else ResizeAndStoreConstraintData( ent, RESET, sizedata[ 1 ] ) end + StorePhysicsData( physobj ) + ent:EnableCustomCollisions( false ) + ent:PhysicsInit( SOLID_VPHYSICS ) + net.Start( "advresizer_fix_physical_size" ) + net.WriteEntity( ent ) + net.Broadcast() + if ( WAS_RESIZED ) then + SIZEHANDLER:SetActualPhysicsScale( tostring( RESET ) ) + end + ent:SetCollisionBounds( sizedata[ 2 ], sizedata[ 3 ] ) + physobj = ent:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + physobj:SetMass( sizedata[ 4 ] ) + ApplyConstraintData() + ApplyPhysicsData( physobj ) + physobj:Wake() + if ( ent.IsMotionControlled ) then o_StartMotionController( ent ) end + else + ApplyConstraintData() + end + ResizedEntities[ ent ] = nil + end + end + end + + util.AddNetworkString( "advresizer_set_visual_size" ) + util.AddNetworkString( "advresizer_fix_visual_size" ) + + function TOOL:SetVisualSize( ent, scale ) + if ( not WAS_RESIZED ) then return end + net.Start( "advresizer_set_visual_size" ) + net.WriteEntity( ent ) + net.WriteString( tostring( scale ) ) + net.Broadcast() + end + + function TOOL:FixVisualSize( ent ) + net.Start( "advresizer_fix_visual_size" ) + net.WriteEntity( ent ) + net.Broadcast() + end + + local advresizer_clamp = CreateConVar( "advresizer_clamp", "0", FCVAR_ARCHIVE + FCVAR_NOTIFY, "Force the Prop Resizer to clamp its values." ) + local function ClampVal( scale ) + scale.x = math.Clamp( scale.x, 0.2, 5 ) + scale.y = math.Clamp( scale.y, 0.2, 5 ) + scale.z = math.Clamp( scale.z, 0.2, 5 ) + end + + function TOOL:LeftClick( Trace ) + local ent = Trace.Entity + if ( not IsValidEntity( ent ) ) then return false end + if ( ent:IsRagdoll() ) then return false end + local pscale = Vector( self:GetClientNumber( "sx" ), self:GetClientNumber( "sy" ), self:GetClientNumber( "sz" ) ) + local vscale = pscale + if ( self:GetClientBool( "smwo" ) ) then + -- if ( advresizer_clamp:GetBool() ) then + ClampVal( pscale ) + -- end + else + vscale = Vector( self:GetClientNumber( "cx" ), self:GetClientNumber( "cy" ), self:GetClientNumber( "cz" ) ) + -- if ( advresizer_clamp:GetBool() ) then + ClampVal( pscale ) + ClampVal( vscale ) + -- end + end + SIZEHANDLER = FindSizeHandler( ent ) + WAS_RESIZED = IsValidEntity( SIZEHANDLER ) + if ( pscale == RESET ) then + self:FixPhysicalSize( ent ) + if ( vscale == RESET ) then + self:FixVisualSize( ent ) + if ( WAS_RESIZED ) then SIZEHANDLER:Remove() end + duplicator.ClearEntityModifier( ent, "advr" ) + return true + else + self:SetVisualSize( ent, vscale ) + end + else + self:SetPhysicalSize( ent, pscale ) + if ( vscale == RESET ) then + self:FixVisualSize( ent ) + else + self:SetVisualSize( ent, vscale ) + end + end + if ( not IsValidEntity( SIZEHANDLER ) ) then SIZEHANDLER = CreateSizeHandler( ent ) end + SIZEHANDLER:SetVisualScale( tostring( vscale ) ) + duplicator.StoreEntityModifier( ent, "advr", { pscale.x, pscale.y, pscale.z, vscale.x, vscale.y, vscale.z, self:GetClientBool( "dcp" ) } ) + return true + end + + function TOOL:RightClick( Trace ) + local ent = Trace.Entity + if ( not IsValidEntity( ent ) ) then return false end + if ( ent:IsRagdoll() ) then return false end + SIZEHANDLER = FindSizeHandler( ent ) + WAS_RESIZED = IsValidEntity( SIZEHANDLER ) + self:FixPhysicalSize( ent ) + self:FixVisualSize( ent ) + if ( WAS_RESIZED ) then SIZEHANDLER:Remove() end + duplicator.ClearEntityModifier( ent, "advr" ) + return true + end + +end + +if ( CLIENT ) then + local function IsValidModel( mdl ) + return isstring( mdl ) and mdl ~= models_error + end + local ResizedEntities = {} + local function CreateSizeData( ent ) + for k, v in pairs( ResizedEntities ) do if ( not IsValidEntity( k ) ) then ResizedEntities[ k ] = nil end end + local sizedata = {} + sizedata[ 1 ] = Vector( 1, 1, 1 ) + sizedata[ 2 ], sizedata[ 3 ] = ent:GetRenderBounds() + ResizedEntities[ ent ] = sizedata + return sizedata + end + + local ClientPhysics = {} + local function CreateClientPhysicsData( ent ) + for k, v in pairs( ClientPhysics ) do if ( not IsValidEntity( k ) ) then ClientPhysics[ k ] = nil end end + local physdata = {} + physdata[ 1 ] = Vector( 1, 1, 1 ) + ClientPhysics[ ent ] = physdata + return physdata + end + +--[[ + hook.Add( "EntityRemoved", "advresizer", function( ent ) + if ( ResizedEntities[ ent ] ~= nil ) then ResizedEntities[ ent ] = nil end + end )]] + local function IsBig( scale ) + if ( scale.x >= 4 ) then + return ( scale.y >= 4 ) or ( scale.z >= 4 ) + elseif ( scale.y >= 4 ) then + return ( scale.z >= 4 ) + end + return false + end + + saverestore.AddSaveHook( "advresizer", function( save ) + save:StartBlock( "advresizer_SaveData" ) + local EntitiesToSave = {} + for ent, sizedata in pairs( ResizedEntities ) do + if ( IsValidEntity( ent ) ) then + table.insert( EntitiesToSave, { ent, sizedata } ) + else + ResizedEntities[ ent ] = nil + end + end + local l = #EntitiesToSave + save:WriteInt( l ) + for Key = 1, l do + local Factory = EntitiesToSave[ Key ] + save:WriteEntity( Factory[ 1 ] ) + saverestore.WriteTable( Factory[ 2 ], save ) + end + save:EndBlock() + end ) + + saverestore.AddRestoreHook( "advresizer", function( restore ) + local name = restore:StartBlock() + if ( name == "advresizer_SaveData" ) then + local l = restore:ReadInt() + for Key = 1, l do + local ent = restore:ReadEntity() + local sizedata = saverestore.ReadTable( restore ) + if ( IsValidEntity( ent ) ) then + ResizedEntities[ ent ] = sizedata + end + end + end + restore:EndBlock() + end ) + + net.Receive( "advresizer_set_visual_size", function( l ) + local ent = net.ReadEntity() + local scale = net.ReadString() + if IsValidEntity( ent ) then + ent:SetSize( Vector( scale ) ) + end + end ) + + net.Receive( "advresizer_fix_visual_size", function( l ) + local ent = net.ReadEntity() + if IsValidEntity( ent ) then + ent:SetSize( RESET ) + end + end ) + + local meta = FindMetaTable 'Entity' + + function meta:SetSize(size) + if not IsValid(self) or not IsValidModel( self:GetModel() ) then return end + + if size ~= RESET then + self._size = size + + local sizedata = ResizedEntities[ self ] or CreateSizeData( self ) + local m = Matrix() + m:Scale( size ) + self:EnableMatrix( "RenderMultiply", m ) + self:SetRenderBounds( sizedata[ 2 ] * size, sizedata[ 3 ] * size ) + self:DestroyShadow() + if ( IsBig( size ) ) then self:SetLOD( 0 ) else self:SetLOD( -1 ) end + sizedata[ 1 ]:Set( size ) + + local physdata = ClientPhysics[ self ] or CreateClientPhysicsData( self ) + local success = ResizePhysics( self, size ) + if ( success ) then + local physobj = self:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + physobj:SetPos( self:GetPos() ) + physobj:SetAngles( self:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + end + physdata[ 1 ]:Set( size ) + else + self._size = nil + + local sizedata = ResizedEntities[ self ] + if ( not sizedata ) then return end + self:DisableMatrix( "RenderMultiply" ) + self:SetRenderBounds( sizedata[ 2 ], sizedata[ 3 ] ) + self:DestroyShadow() + self:SetLOD( -1 ) + ResizedEntities[ self ] = nil + + local physdata = ClientPhysics[ self ] + if ( not physdata ) then return end + self:PhysicsDestroy() + ClientPhysics[ self ] = nil + end + end + + function meta:GetSize() + return self._size and Vector(self._size) or Vector(1,1,1) + end + +--[[ + hook.Add( "EntityRemoved", "advresizer_clientphysics", function( ent ) + if ( ClientPhysics[ ent ] ~= nil ) then ClientPhysics[ ent ] = nil end + end )]] + saverestore.AddSaveHook( "clientphysics", function( save ) + save:StartBlock( "PhysData" ) + local EntitiesToSave = {} + for ent, physdata in pairs( ClientPhysics ) do + if ( IsValidEntity( ent ) ) then + table.insert( EntitiesToSave, { ent, physdata } ) + else + ClientPhysics[ ent ] = nil + end + end + local l = #EntitiesToSave + save:WriteInt( l ) + for Key = 1, l do + local Factory = EntitiesToSave[ Key ] + save:WriteEntity( Factory[ 1 ] ) + saverestore.WriteTable( Factory[ 2 ], save ) + end + save:EndBlock() + end ) + + saverestore.AddRestoreHook( "clientphysics", function( restore ) + local name = restore:StartBlock() + if ( name == "PhysData" ) then + local l = restore:ReadInt() + for Key = 1, l do + local ent = restore:ReadEntity() + local physdata = saverestore.ReadTable( restore ) + if ( IsValidEntity( ent ) ) then + ClientPhysics[ ent ] = physdata + end + end + end + restore:EndBlock() + end ) + + net.Receive( "advresizer_set_physical_size", function( l ) + local ent = net.ReadEntity() + local scale = net.ReadString() + if ( IsValidEntity( ent ) ) then + scale = Vector( scale ) + local physdata = ClientPhysics[ ent ] or CreateClientPhysicsData( ent ) + local success = ResizePhysics( ent, scale ) + if ( success ) then + local physobj = ent:GetPhysicsObject() + if ( IsValidPhysicsObject( physobj ) ) then + physobj:SetPos( ent:GetPos() ) + physobj:SetAngles( ent:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + end + physdata[ 1 ]:Set( scale ) + end + end ) + + net.Receive( "advresizer_fix_physical_size", function( l ) + local ent = net.ReadEntity() + if ( IsValidEntity( ent ) ) then + local physdata = ClientPhysics[ ent ] + if ( not physdata ) then return end + ent:PhysicsDestroy() + ClientPhysics[ ent ] = nil + end + end ) + + function ENT:Think() + local ent = self:GetParent() + if ( not IsValidEntity( ent ) ) then return end + if ( not ClientPhysics[ ent ] ) then return end + local physobj = ent:GetPhysicsObject() + if ( not IsValidPhysicsObject( physobj ) ) then return end + physobj:SetPos( ent:GetPos() ) + physobj:SetAngles( ent:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + + function ENT:RefreshVisualSize( ent ) + local sizedata = ResizedEntities[ ent ] + if ( sizedata ) then + local scale = sizedata[ 1 ] + local m = Matrix() + m:Scale( scale ) + ent._size = scale + ent:EnableMatrix( "RenderMultiply", m ) + ent:SetRenderBounds( sizedata[ 2 ] * scale, sizedata[ 3 ] * scale ) + ent:DestroyShadow() + if ( IsBig( scale ) ) then ent:SetLOD( 0 ) else ent:SetLOD( -1 ) end + elseif ( isfunction( self.GetVisualScale ) ) then + local scale = Vector( self:GetVisualScale() ) + if ( scale ~= RESET ) and ( scale ~= EMPTY ) then + sizedata = CreateSizeData( ent ) + sizedata[ 1 ]:Set( scale ) + local m = Matrix() + m:Scale( scale ) + ent._size = scale + ent:EnableMatrix( "RenderMultiply", m ) + ent:SetRenderBounds( sizedata[ 2 ] * scale, sizedata[ 3 ] * scale ) + ent:DestroyShadow() + if ( IsBig( scale ) ) then ent:SetLOD( 0 ) else ent:SetLOD( -1 ) end + end + end + end + + function ENT:RefreshClientPhysics( ent ) + local physdata = ClientPhysics[ ent ] + if ( physdata ) then + local success = ResizePhysics( ent, physdata[ 1 ] ) + if ( success ) then + local physobj = ent:GetPhysicsObject() + physobj:SetPos( ent:GetPos() ) + physobj:SetAngles( ent:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + elseif ( isfunction( self.GetActualPhysicsScale ) ) then + local scale = Vector( self:GetActualPhysicsScale() ) + if ( scale ~= RESET ) and ( scale ~= EMPTY ) then + physdata = CreateClientPhysicsData( ent ) + physdata[ 1 ]:Set( scale ) + local success = ResizePhysics( ent, scale ) + if ( success ) then + local physobj = ent:GetPhysicsObject() + physobj:SetPos( ent:GetPos() ) + physobj:SetAngles( ent:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + end + end + end + + function ENT:OnNetworkEntityCreated() + local ent = self:GetParent() + if ( not IsValidEntity( ent ) ) then return end + if ( isfunction( self.GetVisualScale ) ) then + local sizedata = ResizedEntities[ ent ] + if ( sizedata ) then + local scale = sizedata[ 1 ] + local m = Matrix() + m:Scale( scale ) + ent._size = scale + ent:EnableMatrix( "RenderMultiply", m ) + ent:SetRenderBounds( sizedata[ 2 ] * scale, sizedata[ 3 ] * scale ) + ent:DestroyShadow() + if ( IsBig( scale ) ) then ent:SetLOD( 0 ) else ent:SetLOD( -1 ) end + else + local scale = Vector( self:GetVisualScale() ) + if ( scale ~= RESET ) and ( scale ~= EMPTY ) then + sizedata = CreateSizeData( ent ) + sizedata[ 1 ]:Set( scale ) + local m = Matrix() + m:Scale( scale ) + ent._size = scale + ent:EnableMatrix( "RenderMultiply", m ) + ent:SetRenderBounds( sizedata[ 2 ] * scale, sizedata[ 3 ] * scale ) + ent:DestroyShadow() + if ( IsBig( scale ) ) then ent:SetLOD( 0 ) else ent:SetLOD( -1 ) end + end + end + end + if ( isfunction( self.GetActualPhysicsScale ) ) then + local physdata = ClientPhysics[ ent ] + if ( physdata ) then + local scale = physdata[ 1 ] + local success = ResizePhysics( ent, scale ) + if ( success ) then + local physobj = ent:GetPhysicsObject() + physobj:SetPos( ent:GetPos() ) + physobj:SetAngles( ent:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + else + local scale = Vector( self:GetActualPhysicsScale() ) + if ( scale ~= RESET ) and ( scale ~= EMPTY ) then + physdata = CreateClientPhysicsData( ent ) + physdata[ 1 ]:Set( scale ) + local success = ResizePhysics( ent, scale ) + if ( success ) then + local physobj = ent:GetPhysicsObject() + physobj:SetPos( ent:GetPos() ) + physobj:SetAngles( ent:GetAngles() ) + physobj:EnableMotion( false ) + physobj:Sleep() + end + end + end + end + end + hook.Add( "NetworkEntityCreated", "advresizer", function( ent ) + if ( ent:GetClass() == "sizehandler" ) and isfunction( ent.OnNetworkEntityCreated ) then + ent:OnNetworkEntityCreated() + end + end ) + TOOL.Information = { + { name = "left" }, + { name = "right" } + } + language.Add( "tool.advresizer.name", "Prop Resizer" ) + language.Add( "tool.advresizer.desc", "Resizes props" ) +-- language.Add( "tool.advresizer.0", "Left click to resize. Right click to reset." ) + language.Add( "tool.advresizer.left", "Set size" ) + language.Add( "tool.advresizer.right", "Fix size" ) + language.Add( "tool.advresizer.sx", "Physical X Scale" ) + language.Add( "tool.advresizer.sy", "Physical Y Scale" ) + language.Add( "tool.advresizer.sz", "Physical Z Scale" ) + language.Add( "tool.advresizer.smwo", "Scale Visual with Physical" ) + language.Add( "tool.advresizer.smwo.help", "Use the above values to scale visually." ) + language.Add( "tool.advresizer.cx", "Visual X Scale" ) + language.Add( "tool.advresizer.cy", "Visual Y Scale" ) + language.Add( "tool.advresizer.cz", "Visual Z Scale" ) + language.Add( "tool.advresizer.prco", "Preserve Constraint Locations" ) + language.Add( "tool.advresizer.dcp", "Disable Client Physics" ) + + function TOOL.BuildCPanel( CPanel ) + CPanel:Help( "#tool.advresizer.desc" ) + local ctrl = vgui.Create( "ControlPresets", CPanel ) + ctrl:SetPreset( "advresizer" ) + local default = { + advresizer_sx = "1", + advresizer_sy = "1", + advresizer_sz = "1", + advresizer_smwo = "1", + advresizer_cx = "1", + advresizer_cy = "1", + advresizer_cz = "1", + advresizer_prco = "0", + advresizer_dcp = "0" + } + ctrl:AddOption( "#preset.default", default ) + for k, v in pairs( default ) do ctrl:AddConVar( k ) end + CPanel:AddPanel( ctrl ) + CPanel:NumSlider( "#tool.advresizer.sx", "advresizer_sx", 0.1, 10, 2 ) + CPanel:NumSlider( "#tool.advresizer.sy", "advresizer_sy", 0.1, 10, 2 ) + CPanel:NumSlider( "#tool.advresizer.sz", "advresizer_sz", 0.1, 10, 2 ) + CPanel:CheckBox( "#tool.advresizer.smwo", "advresizer_smwo" ) + CPanel:NumSlider( "#tool.advresizer.cx", "advresizer_cx", 0.1, 10, 2 ) + CPanel:NumSlider( "#tool.advresizer.cy", "advresizer_cy", 0.1, 10, 2 ) + CPanel:NumSlider( "#tool.advresizer.cz", "advresizer_cz", 0.1, 10, 2 ) + CPanel:CheckBox( "#tool.advresizer.prco", "advresizer_prco" ) + CPanel:CheckBox( "#tool.advresizer.dcp", "advresizer_dcp" ) + end +end + +function ENT:SetupDataTables() + self:NetworkVar( "String", 0, "VisualScale", { KeyName = "visualscale" } ) + self:NetworkVar( "String", 1, "ActualPhysicsScale", { KeyName = "actualphysicsscale" } ) + if ( CLIENT ) then + local ent = self:GetParent() + if ( IsValidEntity( ent ) ) then + if ( isfunction( self.RefreshVisualSize ) ) then self:RefreshVisualSize( ent ) end + if ( isfunction( self.RefreshClientPhysics ) ) then self:RefreshClientPhysics( ent ) end + end + end +end +scripted_ents.Register( ENT, "sizehandler" ) + +local unique_name = "Effect_Resizer_641848001" +local function SetDimensions(ply, effect, data, scale) + if not IsValid(effect) then return end + + local override = hook.Run('CanTool', ply, { Entity = effect }, 'effectresizer') + if override == false then return end + + if scale then effect:SetModelScale(scale, ANIM_LENGTH) end -- only happens when called from the toolgun, not from duplicator library + + local old_dims = effect:GetNW2Vector(unique_name, FULL_SIZE) + + local new_dims = data.dimensions + local x, y, z = new_dims.x, new_dims.y, new_dims.z + + local fullsize = x == 1 and y == 1 and z == 1 -- if all dimensions are 1, then there's no dimensions at all + + local xflat = (x == 0) and 1 or 0 + local yflat = (y == 0) and 1 or 0 + local zflat = (z == 0) and 1 or 0 + local tooflat = xflat + yflat + zflat > 1 -- only one axis is allowed to be flat, otherwise nothing is visible + + if fullsize or tooflat then + -- bad/irrelevant dimensions + effect:SetNW2Vector(unique_name, nil) + new_dims = FULL_SIZE + duplicator.ClearEntityModifier(effect, unique_name) -- I've checked the duplicator library source code, and removing a modifier while it's being run will not cause problems. It loops through available modifiers, not through the entity's modifiers. + else + effect:SetNW2Vector(unique_name, data.dimensions) + duplicator.StoreEntityModifier(effect, unique_name, data) + end + + net.Start(unique_name) + net.WriteUInt(effect:EntIndex(), 16) -- entity might not yet be valid clientside, so send as entity index + net.WriteVector(scale and old_dims or new_dims) -- in general, this writes old_dims; However, if this was called from the duplicator library, I don't want the animation to play, so I send new_dims twice. + net.WriteVector(new_dims) + net.Broadcast() +end + +--[[------------------------------------------------------------------------- +Register duplicator entity modifier +---------------------------------------------------------------------------]] +duplicator.RegisterEntityModifier(unique_name, SetDimensions) diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera.lua new file mode 100644 index 0000000..89212b9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera.lua @@ -0,0 +1,48 @@ +TOOL.Category = "Main" +TOOL.Name = "Camera Layout" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + + +TOOL.ClientConVar["key"] = "-1" + +TOOL.ClientConVar["facetraveldir"] = "0" + +TOOL.ClientConVar["bankonturn"] = "1" +TOOL.ClientConVar["bankdelta_max"] = "1" +TOOL.ClientConVar["bank_multi"] = "1" + +TOOL.ClientConVar["zoom"] = "75" + +TOOL.ClientConVar["enable_roll"] = "0" +TOOL.ClientConVar["roll"] = "0" + +--TOOL.ClientConVar["enable_stay_on_end"] = "0" +--TOOL.ClientConVar["enable_looping"] = "0" + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.Layout.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.Layout.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.Layout.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.Layout.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.Layout.BuildCPanel(panel) +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_duration.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_duration.lua new file mode 100644 index 0000000..b11e75d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_duration.lua @@ -0,0 +1,34 @@ +TOOL.Category = "Behavior Modifiers" +TOOL.Name = "Duration Editor" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + +TOOL.ClientConVar["duration"] = "2" +TOOL.ClientConVar["duration_mps"] = "1" + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.DurationEditor.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.DurationEditor.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.DurationEditor.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.DurationEditor.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.DurationEditor.BuildCPanel(panel) +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_hitchcock.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_hitchcock.lua new file mode 100644 index 0000000..07feefd --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_hitchcock.lua @@ -0,0 +1,34 @@ +TOOL.Category = "Behavior Modifiers" +TOOL.Name = "Hitchcock Effect" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + +TOOL.ClientConVar["width"] = "4" +TOOL.ClientConVar["endfov"] = "25" + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.HitchcockEffect.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.HitchcockEffect.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.HitchcockEffect.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.HitchcockEffect.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.HitchcockEffect.BuildCPanel(panel) +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_linker.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_linker.lua new file mode 100644 index 0000000..8de8d21 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_linker.lua @@ -0,0 +1,31 @@ +TOOL.Category = "Behavior Modifiers" +TOOL.Name = "Linker" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.Linker.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.Linker.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.Linker.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.Linker.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.Linker.BuildCPanel(panel) +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_looker.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_looker.lua new file mode 100644 index 0000000..65c2984 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_looker.lua @@ -0,0 +1,37 @@ +TOOL.Category = "Behavior Modifiers" +TOOL.Name = "Smart Look" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + +TOOL.ClientConVar["block"] = tostring(MASK_OPAQUE) +TOOL.ClientConVar["target"] = "1" +TOOL.ClientConVar["range"] = "512" +TOOL.ClientConVar["percent"] = "1" +TOOL.ClientConVar["closest"] = "0" + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.SmartLook.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.SmartLook.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.SmartLook.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.SmartLook.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.SmartLook.BuildCPanel(panel) +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_map_io.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_map_io.lua new file mode 100644 index 0000000..565ba69 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_map_io.lua @@ -0,0 +1,36 @@ +TOOL.Category = "Event Triggering" +TOOL.Name = "Map I/O Editor" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + +TOOL.ClientConVar["user1"] = "0" +TOOL.ClientConVar["user2"] = "0" +TOOL.ClientConVar["user3"] = "0" +TOOL.ClientConVar["user4"] = "0" + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.MapIOEditor.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.MapIOEditor.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.MapIOEditor.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.MapIOEditor.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.MapIOEditor.BuildCPanel(panel) +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_numpad_trigger.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_numpad_trigger.lua new file mode 100644 index 0000000..9dde368 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/catmullrom_camera_numpad_trigger.lua @@ -0,0 +1,33 @@ +TOOL.Category = "Event Triggering" +TOOL.Name = "Numpad Trigger Editor" +TOOL.Command = nil +TOOL.ConfigName = nil +TOOL.Tab = "CRCCams" + + +TOOL.ClientConVar["keys"] = "-1" +TOOL.ClientConVar["hold"] = "0" + +function TOOL:LeftClick(trace) + return CatmullRomCams.SToolMethods.NumPadTrigger.LeftClick(self, trace) +end + +function TOOL:RightClick(trace) + return CatmullRomCams.SToolMethods.NumPadTrigger.RightClick(self, trace) +end + +function TOOL:Reload(trace) + return CatmullRomCams.SToolMethods.NumPadTrigger.Reload(self, trace) +end + +function TOOL:Think() + return CatmullRomCams.SToolMethods.NumPadTrigger.Think(self) +end + +function TOOL:ValidTrace(trace) + return CatmullRomCams.SToolMethods.ValidTrace(trace) +end + +function TOOL.BuildCPanel(panel) + return CatmullRomCams.SToolMethods.NumPadTrigger.BuildCPanel(panel) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/colour.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/colour.lua new file mode 100644 index 0000000..ba3d4ad --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/colour.lua @@ -0,0 +1,131 @@ + +TOOL.Category = "Render" +TOOL.Name = "#tool.colour.name" + +TOOL.ClientConVar[ "r" ] = 255 +TOOL.ClientConVar[ "g" ] = 255 +TOOL.ClientConVar[ "b" ] = 255 +TOOL.ClientConVar[ "a" ] = 255 +TOOL.ClientConVar[ "mode" ] = "0" +TOOL.ClientConVar[ "fx" ] = "0" + +TOOL.Information = { + { name = "left" }, + { name = "right" }, + { name = "reload" } +} + +local function SetColour( ply, ent, data ) + + -- + -- If we're trying to make them transparent them make the render mode + -- a transparent type. This used to fix in the engine - but made HL:S props invisible(!) + -- + if ( data.Color && data.Color.a < 255 && data.RenderMode == RENDERMODE_NORMAL ) then + data.RenderMode = RENDERMODE_TRANSCOLOR + end + + if ( data.Color ) then ent:SetColor( Color( data.Color.r, data.Color.g, data.Color.b, data.Color.a ) ) end + if ( data.RenderMode ) then ent:SetRenderMode( data.RenderMode ) end + if ( data.RenderFX ) then ent:SetKeyValue( "renderfx", data.RenderFX ) end + + if ( SERVER ) then + duplicator.StoreEntityModifier( ent, "colour", data ) + end + +end +duplicator.RegisterEntityModifier( "colour", SetColour ) + +function TOOL:LeftClick( trace ) + + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + if ( !IsValid( ent ) ) then return false end -- The entity is valid and isn't worldspawn + if ( CLIENT ) then return true end + + local r = self:GetClientNumber( "r", 0 ) + local g = self:GetClientNumber( "g", 0 ) + local b = self:GetClientNumber( "b", 0 ) + local a = self:GetClientNumber( "a", 0 ) + local fx = self:GetClientNumber( "fx", 0 ) + local mode = self:GetClientNumber( "mode", 0 ) + + SetColour( self:GetOwner(), ent, { Color = Color( r, g, b, a ), RenderMode = mode, RenderFX = fx } ) + return true + +end + +function TOOL:RightClick( trace ) + + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + if ( !IsValid( ent ) ) then return false end -- The entity is valid and isn't worldspawn + + if ( CLIENT ) then return true end + + local clr = ent:GetColor() + self:GetOwner():ConCommand( "colour_r " .. clr.r ) + self:GetOwner():ConCommand( "colour_g " .. clr.g ) + self:GetOwner():ConCommand( "colour_b " .. clr.b ) + self:GetOwner():ConCommand( "colour_a " .. clr.a ) + self:GetOwner():ConCommand( "colour_fx " .. ent:GetRenderFX() ) + self:GetOwner():ConCommand( "colour_mode " .. ent:GetRenderMode() ) + + return true + +end + +function TOOL:Reload( trace ) + + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + + if ( !IsValid( ent ) ) then return false end -- The entity is valid and isn't worldspawn + if ( CLIENT ) then return true end + + SetColour( self:GetOwner(), ent, { Color = Color(255,255,255, 255), RenderMode = 0, RenderFX = 0 } ) + return true + +end + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel( CPanel ) + + CPanel:AddControl( "Header", { Description = "#tool.colour.desc" } ) + + CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "colour", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + CPanel:AddControl( "Color", { Label = "#tool.colour.color", Red = "colour_r", Green = "colour_g", Blue = "colour_b", Alpha = "colour_a" } ) + + CPanel:AddControl( "ListBox", { Label = "#tool.colour.mode", Options = list.Get( "RenderModes" ) } ) + CPanel:AddControl( "ListBox", { Label = "#tool.colour.fx", Options = list.Get( "RenderFX" ) } ) + +end + +list.Set( "RenderModes", "#rendermode.normal", { colour_mode = 0 } ) +list.Set( "RenderModes", "#rendermode.transcolor", { colour_mode = 1 } ) +list.Set( "RenderModes", "#rendermode.transtexture", { colour_mode = 2 } ) +list.Set( "RenderModes", "#rendermode.glow", { colour_mode = 3 } ) +list.Set( "RenderModes", "#rendermode.transalpha", { colour_mode = 4 } ) +list.Set( "RenderModes", "#rendermode.transadd", { colour_mode = 5 } ) +list.Set( "RenderModes", "#rendermode.transalphaadd", { colour_mode = 8 } ) +list.Set( "RenderModes", "#rendermode.worldglow", { colour_mode = 9 } ) + +list.Set( "RenderFX", "#renderfx.none", { colour_fx = 0 } ) +list.Set( "RenderFX", "#renderfx.pulseslow", { colour_fx = 1 } ) +list.Set( "RenderFX", "#renderfx.pulsefast", { colour_fx = 2 } ) +list.Set( "RenderFX", "#renderfx.pulseslowwide", { colour_fx = 3 } ) +list.Set( "RenderFX", "#renderfx.pulsefastwide", { colour_fx = 4 } ) +list.Set( "RenderFX", "#renderfx.fadeslow", { colour_fx = 5 } ) +list.Set( "RenderFX", "#renderfx.fadefast", { colour_fx = 6 } ) +list.Set( "RenderFX", "#renderfx.solidslow", { colour_fx = 7 } ) +list.Set( "RenderFX", "#renderfx.solidfast", { colour_fx = 8 } ) +list.Set( "RenderFX", "#renderfx.strobeslow", { colour_fx = 9 } ) +list.Set( "RenderFX", "#renderfx.strobefast", { colour_fx = 10 } ) +list.Set( "RenderFX", "#renderfx.strobefaster", { colour_fx = 11 } ) +list.Set( "RenderFX", "#renderfx.flickerslow", { colour_fx = 12 } ) +list.Set( "RenderFX", "#renderfx.flickerfast", { colour_fx = 13 } ) +list.Set( "RenderFX", "#renderfx.distort", { colour_fx = 15 } ) +list.Set( "RenderFX", "#renderfx.hologram", { colour_fx = 16 } ) +list.Set( "RenderFX", "#renderfx.pulsefastwider", { colour_fx = 24 } ) diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/dbg_plate.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/dbg_plate.lua new file mode 100644 index 0000000..13f6887 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/dbg_plate.lua @@ -0,0 +1,94 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Автомобильные знаки' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, +} + +local vars = { + txt = 'SPAWNED', + colBG = Color(255,255,255), + colBorder = Color(40,40,40), + colTitle = Color(40,40,40), + colTxt = Color(0,0,0), +} + +if CLIENT then + for k, v in pairs(vars) do + octolib.vars.init('tools.dbg_plate.' .. k, v) + end +end + +local varList = octolib.table.map(table.GetKeys(vars), function(v) return 'tools.dbg_plate.' .. v end) + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' or not self:GetOwner():query('DBG: Изменять автомобили') then return false end + + if SERVER then + self:GetOwner():GetClientVar(varList, function(vars) + ent:SetNetVar('cd.plate', vars['tools.dbg_plate.txt'] or '') + ent:SetNetVar('cd.plateCol', { + vars['tools.dbg_plate.colBG'] or Color(255,255,255), + vars['tools.dbg_plate.colBorder'] or Color(40,40,40), + vars['tools.dbg_plate.colTitle'] or Color(255,255,255), + vars['tools.dbg_plate.colTxt'] or Color(0,0,0), + }) + end) + else + doEffect(ent) + end + + return true + +end + +function TOOL:RightClick(tr) + + local ent = tr.Entity + if not IsValid(ent) or ent:GetClass() ~= 'gmod_sent_vehicle_fphysics_base' or not self:GetOwner():query('DBG: Изменять автомобили') then return false end + + if SERVER then + ent:SetNetVar('cd.plate', false) + ent:SetNetVar('cd.plateCol', nil) + else + doEffect(ent) + end + + return true + +end + +function TOOL:BuildCPanel() + + octolib.vars.textEntry(self, 'tools.dbg_plate.txt', 'Текст') + octolib.vars.colorPicker(self, 'tools.dbg_plate.colBG', 'Цвет фона', true, true) + octolib.vars.colorPicker(self, 'tools.dbg_plate.colBorder', 'Цвет рамки', true, true) + octolib.vars.colorPicker(self, 'tools.dbg_plate.colTitle', 'Цвет заголовка', true, true) + octolib.vars.colorPicker(self, 'tools.dbg_plate.colTxt', 'Цвет текста', true, true) + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + language.Add('Tool.dbg_plate.name', 'Знаки') + language.Add('Tool.dbg_plate.desc', 'Меняем знаки на автомобилях') + language.Add('Tool.dbg_plate.left', 'Установить знаки') + language.Add('Tool.dbg_plate.right', 'Убрать знаки') +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/dynamite.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/dynamite.lua new file mode 100644 index 0000000..73dc2f9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/dynamite.lua @@ -0,0 +1,178 @@ + +TOOL.Category = "Construction" +TOOL.Name = "#tool.dynamite.name" + +TOOL.ClientConVar[ "group" ] = 52 +TOOL.ClientConVar[ "damage" ] = 200 +TOOL.ClientConVar[ "delay" ] = 0 +TOOL.ClientConVar[ "model" ] = "models/dav0r/tnt/tnt.mdl" +TOOL.ClientConVar[ "remove" ] = 0 + +TOOL.Information = { { name = "left" } } + +cleanup.Register( "dynamite" ) + +local function IsValidDynamiteModel( model ) + for mdl, _ in pairs( list.Get( "DynamiteModels" ) ) do + if ( mdl:lower() == model:lower() ) then return true end + end + return false +end + +function TOOL:LeftClick( trace ) + + if ( !trace.HitPos || IsValid( trace.Entity ) && trace.Entity:IsPlayer() ) then return false end + if ( CLIENT ) then return true end + + local ply = self:GetOwner() + + -- Get client's CVars + local group = self:GetClientNumber( "group" ) + local delay = self:GetClientNumber( "delay" ) + local damage = self:GetClientNumber( "damage" ) + local model = self:GetClientInfo( "model" ) + local remove = self:GetClientNumber( "remove" ) == 1 + + -- If we shot a dynamite, change it's settings + if ( IsValid( trace.Entity ) && trace.Entity:GetClass() == "gmod_dynamite" && trace.Entity:GetPlayer() == ply ) then + + trace.Entity:SetDamage( damage ) + trace.Entity:SetShouldRemove( remove ) + trace.Entity:SetDelay( delay ) + + numpad.Remove( trace.Entity.NumDown ) + trace.Entity.key = group + trace.Entity.NumDown = numpad.OnDown( ply, group, "DynamiteBlow", trace.Entity ) + + return true + end + + if ( !util.IsValidModel( model ) || !util.IsValidProp( model ) || !IsValidDynamiteModel( model ) ) then return false end + if ( !self:GetSWEP():CheckLimit( "dynamite" ) ) then return false end + + local dynamite = MakeDynamite( ply, trace.HitPos, Angle( 0, 0, 0 ), group, damage, model, remove, delay ) + + local CurPos = dynamite:GetPos() + local Offset = CurPos - dynamite:NearestPoint( CurPos - ( trace.HitNormal * 512 ) ) + + dynamite:SetPos( trace.HitPos + Offset ) + + undo.Create( "Dynamite" ) + undo.AddEntity( dynamite ) + undo.SetPlayer( ply ) + undo.Finish() + + return true + +end + +if ( SERVER ) then + + function MakeDynamite( pl, pos, ang, key, damage, model, remove, delay ) + + if ( IsValid( pl ) && !pl:CheckLimit( "dynamite" ) ) then return nil end + if ( !IsValidDynamiteModel( model ) ) then return nil end + + local dynamite = ents.Create( "gmod_dynamite" ) + dynamite:SetPos( pos ) + dynamite:SetAngles( ang ) + dynamite:SetModel( model ) + dynamite:SetShouldRemove( remove ) + dynamite:SetDamage( damage ) + dynamite:SetDelay( delay ) + dynamite:Spawn() + dynamite:Activate() + + if ( IsValid( pl ) ) then + dynamite:SetPlayer( pl ) + end + + table.Merge( dynamite:GetTable(), { + key = key, + pl = pl, + nocollide = nocollide, + description = description, + Damage = damage, + model = model, + remove = remove, + delay = delay + } ) + + dynamite.NumDown = numpad.OnDown( pl, key, "DynamiteBlow", dynamite ) + + if ( IsValid( pl ) ) then + pl:AddCount( "dynamite", dynamite ) + pl:AddCleanup( "dynamite", dynamite ) + end + + DoPropSpawnedEffect( dynamite ) + + return dynamite + + end + duplicator.RegisterEntityClass( "gmod_dynamite", MakeDynamite, "Pos", "Ang", "key", "Damage", "model", "remove", "delay" ) + + numpad.Register( "DynamiteBlow", function( sid, dynamite ) + + if ( !IsValid( dynamite ) ) then return end + + dynamite:Explode( nil, player.GetBySteamID(sid) ) + + end ) + +end + +function TOOL:UpdateGhostDynamite( ent, ply ) + + if ( !IsValid( ent ) ) then return end + + local trace = ply:GetEyeTrace() + if ( !trace.Hit || IsValid( trace.Entity ) && ( trace.Entity:IsPlayer() || trace.Entity:GetClass() == "gmod_dynamite" ) ) then + ent:SetNoDraw( true ) + return + end + + ent:SetAngles( Angle( 0, 0, 0 ) ) + + local CurPos = ent:GetPos() + local Offset = CurPos - ent:NearestPoint( CurPos - ( trace.HitNormal * 512 ) ) + + ent:SetPos( trace.HitPos + Offset ) + + ent:SetNoDraw( false ) + +end + +function TOOL:Think() + + local mdl = self:GetClientInfo( "model" ) + if ( !IsValidDynamiteModel( mdl ) ) then self:ReleaseGhostEntity() return end + + if ( !IsValid( self.GhostEntity ) || self.GhostEntity:GetModel() != mdl ) then + self:MakeGhostEntity( mdl, Vector( 0, 0, 0 ), Angle( 0, 0, 0 ) ) + end + + self:UpdateGhostDynamite( self.GhostEntity, self:GetOwner() ) + +end + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel( CPanel ) + + CPanel:AddControl( "Header", { Description = "#tool.dynamite.help" } ) + + CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "dynamite", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + CPanel:AddControl( "Numpad", { Label = "#tool.dynamite.explode", Command = "dynamite_group" } ) + CPanel:AddControl( "Slider", { Label = "#tool.dynamite.damage", Command = "dynamite_damage", Type = "Float", Min = 0, Max = 500, Help = true } ) + CPanel:AddControl( "Slider", { Label = "#tool.dynamite.delay", Command = "dynamite_delay", Type = "Float", Min = 0, Max = 10, Help = true } ) + CPanel:AddControl( "CheckBox", { Label = "#tool.dynamite.remove", Command = "dynamite_remove" } ) + + CPanel:AddControl( "PropSelect", { Label = "#tool.dynamite.model", ConVar = "dynamite_model", Height = 0, Models = list.Get( "DynamiteModels" ) } ) + +end + +list.Set( "DynamiteModels", "models/dav0r/tnt/tnt.mdl", {} ) +list.Set( "DynamiteModels", "models/dav0r/tnt/tnttimed.mdl", {} ) +list.Set( "DynamiteModels", "models/dynamite/dynamite.mdl", {} ) diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/estates.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/estates.lua new file mode 100644 index 0000000..11a3524 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/estates.lua @@ -0,0 +1,489 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Помещение' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, + { name = 'reload' }, +} + +local ways = {'doors', 'markers'} + +if CLIENT then + + language.Add('Tool.estates.name', 'Помещения') + language.Add('Tool.estates.desc', 'Самый лучший тул') + + language.Add('Tool.estates.left.doors', 'Добавить дверь в выбранное помещение') + language.Add('Tool.estates.right.doors', 'Создать помещение с этой дверью') + language.Add('Tool.estates.reload.doors', 'Удалить дверь из помещения') + language.Add('Tool.estates.left.markers', 'Установить маркер для выбранного помещения') + language.Add('Tool.estates.right.markers', 'Телепортироваться к маркеру выбранного помещения') + language.Add('Tool.estates.reload.markers', 'Удалить маркер помещения') + + local opt = octolib.vars.get('dbg-estates.option') or 1 + language.Add('Tool.estates.left', language.GetPhrase('Tool.estates.left.' .. tostring(ways[opt]))) + language.Add('Tool.estates.right', language.GetPhrase('Tool.estates.right.' .. tostring(ways[opt]))) + language.Add('Tool.estates.reload', language.GetPhrase('Tool.estates.reload.' .. tostring(ways[opt]))) + +else + local nameExamples = { + 'Франклин %d', 'Заправка, город', 'Полиция', + 'Линкольн %d, кв. %d', 'БЦ на Бравери, пав. %d', + 'Дуглас %d', 'Церковь', 'Заправка, пригород' + } + + local function collectData() + local data = netvars.GetNetVar('dbg-estates') or {} + local ans = {} + for i,v in pairs(data) do + ans[i] = table.Copy(v) + local doors = {} + for _,door in ipairs(v.doors or {}) do + doors[#doors + 1] = { + name = ('[%s][%s]'):format(door:EntIndex(), door:GetClass()), + title = door:GetTitle() or '', + id = door:EntIndex(), + } + end + ans[i].doors = doors + end + return ans + end + + TOOL.CollectData = collectData + + TOOL.Ways = { + { -- doors + leftClick = function(self, ply, ent, sel) + if not IsValid(ent) or not ent:IsDoor() then return end + if not sel or sel == 0 or not dbgEstates.exists(sel) then + return ply:Notify('Нужно выбрать помещение') + end + if ent:GetNetVar('estate') == sel then return end + + dbgEstates.linkDoor(ent, sel) + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end, + rightClick = function(self, ply, ent) + if not IsValid(ent) or not ent:IsDoor() then return end + octolib.request.send(ply, {{ + type = 'strShort', + name = 'Название помещения', + desc = 'Обычно название общественного места или адрес', + ph = nameExamples[math.random(#nameExamples)]:format(math.random(10), math.random(10)), + required = true, + }}, function(data) + local name = istable(data) and utf8.sub(string.Trim(data[1] or 'Помещение'), 1, 32) or 'Помещение' + local est = ent:GetEstateID() + if est > 0 then + dbgEstates.unlinkDoor(ent) + end + est = dbgEstates.createEstate(ent, name) + netstream.Start(ply, 'dbg-estates.getToolData', collectData(), est) + end) + end, + reload = function(self, ply, ent) + if not IsValid(ent) or not ent:IsDoor() or ent:IsBlocked() then return end + ent:SetBlocked(true) + netstream.Start(ply, 'dbg-estates.getToolData', self:CollectData()) + end, + }, { -- markers + leftClick = function(self, ply, ent, sel, tr) + if not sel or sel == 0 or not dbgEstates.exists(sel) then + return ply:Notify('Нужно выбрать помещение') + end + + local data = dbgEstates.getData(sel) + local marker = data.marker or {} + octolib.request.send(ply, { + name = { + type = 'strShort', + name = 'Название', + default = marker.name or data.name, + }, + sort = { + type = 'numSlider', + name = 'Порядок в сайдбаре', + desc = 'Чем больше значение, тем ниже маркер будет находиться в списке. Поставь 0, чтобы оставить по умолчанию', + min = 0, + max = 2000, + dec = 0, + default = marker.sort or 0, + }, + icon = { + type = 'iconPicker', + name = 'Иконка', + default = marker.icon, + path = 'materials/octoteam/icons-16/' + }, + perma = { + type = 'check', + name = 'Видимость', + txt = 'Маркер всегда виден в карте на F4', + default = marker.perma, + }, + group = { + type = 'strShort', + name = 'Группа в сайдбаре', + desc = 'Например, если нужно сделать несколько заправок одним пунктом', + default = marker.group, + }, + }, function(res) + res.group = string.Trim(res.group or '', ' ') + if res.group == '' then res.group = nil end + if res.sort == 0 then res.sort = nil end + local nMarker = { + name = tostring(res.name or 'Помещение'), + icon = tostring(res.icon or 'octoteam/icons-16/house.png'), + pos = tr.HitPos, + perma = res.perma, + group = res.group, + sort = res.sort, + } + dbgEstates.updateMarker(sel, nMarker) + ply:Notify('Маркер обновлен') + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end) + end, + rightClick = function(self, ply, ent, sel) + if not sel or sel == 0 or not dbgEstates.exists(sel) then + return ply:Notify('Нужно выбрать помещение') + end + + local data = dbgEstates.getData(sel) + local marker = data.marker + if not marker or not marker.pos then + return ply:Notify('У этого помещения не установлен маркер') + end + ply:SetPos(marker.pos) + end, + reload = function(self, ply, ent, sel) + if not sel or sel == 0 or not dbgEstates.exists(sel) then + return ply:Notify('Нужно выбрать помещение') + end + dbgEstates.updateMarker(sel) + ply:Notify('Маркер удален') + end, + } + } + + netstream.Hook('dbg-estates.getToolData', function(ply) + if not ply:IsSuperAdmin() then return end + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end) + + netstream.Hook('dbg-estates.goToDoor', function(ply, id) + if not ply:IsSuperAdmin() then return end + local ent = ents.GetByIndex(id) + if not IsValid(ent) or not ent:IsDoor() then return end + ply:SetPos(ent:LocalToWorld(ent:OBBCenter())) + end) + + netstream.Hook('dbg-estates.renameDoor', function(ply, id, title) + if not ply:IsSuperAdmin() then return end + local ent = ents.GetByIndex(id) + if not IsValid(ent) or not ent:IsDoor() then return end + title = utf8.sub(string.Trim(title), 1, 128) + if title == '' then title = nil end + ent:SetTitle(title, true) + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end) + + netstream.Hook('dbg-estates.changePrice', function(ply, id, price) + if not ply:IsSuperAdmin() or not isnumber(id) or id < 1 or not isnumber(price) then return end + price = math.max(0, math.floor(price)) + dbgEstates.setEstatePrice(id, price) + ply:Notify('Помещению "' .. tostring(dbgEstates.getData(id).name) .. '" установлена цена ' .. DarkRP.formatMoney(price)) + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end) + + netstream.Hook('dbg-estates.changeOwners', function(ply, id, owners) + if not ply:IsSuperAdmin() or not isnumber(id) or id < 1 or not istable(owners) then return end + dbgEstates.updateOwners(id, owners) + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end) + + netstream.Hook('dbg-estates.changePurpose', function(ply, id, purpose) + if not (ply:IsSuperAdmin() and isnumber(id) and isnumber(purpose) and octolib.math.inRange(purpose, 0, 2)) then return end + dbgEstates.setPurpose(id, purpose) + ply:Notify('Теперь помещение "' .. tostring(dbgEstates.getData(id).name) .. '" можно использовать только для ' .. (purpose == 1 and 'бизнеса' or 'проживания')) + netstream.Start(ply, 'dbg-estates.getToolData', collectData()) + end) + +end + + +function TOOL:LeftClick(tr) + + local ply = self:GetOwner() + if not IsValid(ply) or not ply:IsSuperAdmin() then return false end + if CLIENT then return true end + + local ent = tr.Entity + ply:GetClientVar({'dbg-estates.option', 'dbg-estates.sel'}, function(data) + if not IsValid(ply) or not istable(data) then return end + local opt, sel = data['dbg-estates.option'], data['dbg-estates.sel'] + if not self.Ways[opt] then return end + self.Ways[opt].leftClick(self, ply, ent, sel, tr) + end) + return true + +end + +function TOOL:RightClick(tr) + + local ply = self:GetOwner() + if not IsValid(ply) or not ply:IsSuperAdmin() then return false end + if CLIENT then return true end + + local ent = tr.Entity + ply:GetClientVar({'dbg-estates.option', 'dbg-estates.sel'}, function(data) + if not IsValid(ply) or not istable(data) then return end + local opt, sel = data['dbg-estates.option'], data['dbg-estates.sel'] + if not self.Ways[opt] then return end + self.Ways[opt].rightClick(self, ply, ent, sel, tr) + end) + return true + +end + +function TOOL:Reload(tr) + + local ply = self:GetOwner() + if not IsValid(ply) or not ply:IsSuperAdmin() then return false end + if CLIENT then return true end + + local ent = tr.Entity + ply:GetClientVar({'dbg-estates.option', 'dbg-estates.sel'}, function(data) + if not IsValid(ply) or not istable(data) then return end + local opt, sel = data['dbg-estates.option'], data['dbg-estates.sel'] + if not self.Ways[opt] then return end + self.Ways[opt].reload(self, ply, ent, sel, tr) + end) + return true + +end + +local function paint(ent, name) + local pos = ent:LocalToWorld(ent:OBBCenter()):ToScreen() + if name then + draw.DrawText(name, 'f4.normal-sh', pos.x, pos.y, color_black, TEXT_ALIGN_CENTER) + draw.DrawText(name, 'f4.normal', pos.x, pos.y, color_white, TEXT_ALIGN_CENTER) + end + draw.DrawText(tostring(ent:EntIndex()), 'f4.normal-sh', pos.x, pos.y + 25, color_black, TEXT_ALIGN_CENTER) + draw.DrawText(tostring(ent:EntIndex()), 'f4.normal', pos.x, pos.y + 25, color_white, TEXT_ALIGN_CENTER) +end + +local function render3d() + cam.Start3D() + local sel = octolib.vars.get('dbg-estates.sel') or 0 + if sel == 0 then return cam.End3D() end + local data = dbgEstates.getData(sel).doors + if not data then return cam.End3D() end + for _,v in ipairs(data) do + if IsValid(v) then + local mins, maxs = v:GetCollisionBounds() + render.DrawWireframeBox(v:GetPos(), v:GetAngles(), mins, maxs, color_red) + end + end + cam.End3D() +end + +function TOOL:DrawHUD() + local ply = self:GetOwner() + if not IsValid(ply) or not ply:IsSuperAdmin() then return end + local ent = ply:GetEyeTrace().Entity + if not IsValid(ent) or not ent:IsDoor() then return render3d() end + paint(ent, dbgEstates.getData(ent:GetEstateID()).name) + render3d() +end + +local mats = {} +local colors +if CLIENT then + local matNames = {'door_open', 'map_pin'} + for _,v in ipairs(matNames) do + local mat = 'octoteam/icons/' .. v .. '.png' + Material(mat) + mats[#mats + 1] = CreateMaterial(mat .. '-ignorez', 'UnlitGeneric', { + ['$basetexture'] = mat, + ['$ignorez'] = 1, + ['$translucent'] = 1, + ['$smooth'] = 1, + }) + end + colors = CFG.skinColors +end +function TOOL:DrawToolScreen(w, h) + draw.RoundedBox(0, 0, 0, w, h, colors.bg) + local opt = math.max(1, octolib.vars.get('dbg-estates.option') or 1) + surface.SetMaterial(mats[math.min(#mats, opt)]) + surface.SetDrawColor(255, 255, 255) + local ww, hh = w * 0.5, h * 0.5 + surface.DrawTexturedRect(ww * 0.5, h * 0.1, ww, hh) + draw.DrawText('Помещения', 'f4.medium', ww, h * 0.6, color_white, TEXT_ALIGN_CENTER) + draw.DrawText('powered by Octothorp', 'Trebuchet24', w * 0.95, h * 0.87, color_white, TEXT_ALIGN_RIGHT) +end + +local tags = { + 'yellow', 'red', 'purple', 'pink', 'orange', 'green', 'blue', +} +local estList + +local function addLine(lst, name, data) + local cont = lst:Add('DPanel') + cont:Dock(TOP) + cont:SetTall(20) + cont:SetPaintBackground(false) + + local checkBox = cont:Add('DCheckBox') + checkBox:Dock(LEFT) + checkBox:SetWide(24) + lst.owns[data] = checkBox + + local label = cont:Add('DLabel') + label:SetText(name) + label:Dock(FILL) + label:DockMargin(5, 0, 0, 0) +end + +local function headerClick(self) + self.clck(self) + local id = self:GetParent().id + for _,v in ipairs(estList:GetCanvas():GetChildren()) do + if v.id ~= id then + v:DoExpansion(false) + end + end + octolib.vars.set('dbg-estates.sel', self:GetParent():GetExpanded() and id or nil) +end + +local function rebuildList(data, estId) + if estId then + octolib.vars.set('dbg-estates.sel', estId) + end + if not IsValid(estList) then return end + estList:Clear() + + for k,est in SortedPairsByMemberValue(data, 'name') do + local category = estList:Add(est.name or k ~= 0 and 'Помещение' or 'Заблокированные') + local pnl = vgui.Create('DPanel') + + octolib.label(pnl, 'Помещения:'):DockMargin(5, 5, 5, 5) + local lst = pnl:Add('DListView') + lst:Dock(TOP) + lst:AddColumn('Дверь') + lst:AddColumn('Табличка') + local sz = 20 + for _,v in SortedPairsByMemberValue(est.doors or {}, 'name') do + lst:AddLine(v.name, v.title or '').id = v.id + sz = sz + 17 + end + lst:SetTall(sz) + function lst:OnRowRightClick(_, line) + local menu = DermaMenu() + menu:AddOption('Телепортироваться', function() + netstream.Start('dbg-estates.goToDoor', line.id) + end):SetIcon('octoteam/icons-16/user_go.png') + menu:AddOption('Изменить табличку', function() + Derma_StringRequest('Изменить табличку', 'Укажи новый текст таблички:', line:GetColumnText(2), function(label) + netstream.Start('dbg-estates.renameDoor', line.id, label) + end) + end):SetIcon('octoteam/icons-16/tag_' .. tags[math.random(#tags)] .. '.png') + menu:Open() + end + + if k > 0 then + + local price = octolib.textEntry(pnl, 'Стоимость:') + price:SetNumeric(true) + price:SetValue(est.price or 0) + local saveBtn = octolib.button(price, 'Сохранить', function() + local num = tonumber(price:GetValue()) or 0 + num = math.max(0, math.floor(num)) + netstream.Start('dbg-estates.changePrice', k, num) + end) + saveBtn:Dock(RIGHT) + price.OnValueChange = saveBtn.DoClick + + local owns = pnl:Add('DScrollPanel') + owns.owns = {} + owns:Dock(TOP) + owns:SetTall(100) + owns:DockMargin(5, 0, 0, 5) + for i,v in pairs(dbgDoorGroups.groups) do + addLine(owns, v, 'g:' .. i) + end + for _,job in ipairs(RPExtraTeams) do + addLine(owns, job.name, 'j:' .. job.command) + end + + for _,v in ipairs(est.owners or {}) do + if owns.owns[v] then + owns.owns[v]:SetChecked(true) + end + end + + octolib.button(pnl, 'Сохранить владельцев', function() + local owners = {} + for id,v in pairs(owns.owns) do + if v:GetChecked() then + owners[#owners + 1] = id + end + end + netstream.Start('dbg-estates.changeOwners', k, owners) + end) + + local purpose = octolib.comboBox(pnl, 'Цель использования:', {{'Без ограничений', 0}, {'Только для бизнеса', 1}, {'Только для проживания', 2}}) + purpose:ChooseOptionID((est.purpose or 0) + 1) + function purpose:OnSelect(idx) + netstream.Start('dbg-estates.changePurpose', k, idx-1) + end + + end + + category.id = k + category:SetContents(pnl) + category:SetExpanded(octolib.vars.get('dbg-estates.sel') == k) + category.Header.clck, category.Header.DoClick = category.Header.DoClick, headerClick + end +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = '#Tool.estates.name', + Description = '#Tool.estates.desc' + }) + + octolib.label(self, 'Режим редактирования:') + local way = vgui.Create('DComboBox') + self:AddItem(way) + way:Dock(TOP) + way:SetTall(30) + way:AddChoice('Двери', 'doors', false, 'octoteam/icons-16/door.png') + way:AddChoice('Маркеры', 'markers', false, 'octoteam/icons-16/map.png') + function way:OnSelect(idx, val, data) + octolib.vars.set('dbg-estates.option', idx) + language.Add('Tool.estates.left', language.GetPhrase('Tool.estates.left.' .. data)) + language.Add('Tool.estates.right', language.GetPhrase('Tool.estates.right.' .. data)) + language.Add('Tool.estates.reload', language.GetPhrase('Tool.estates.reload.' .. data)) + end + way:ChooseOptionID(octolib.vars.get('dbg-estates.option') or 1) + + local p = vgui.Create 'DCategoryList' + self:AddItem(p) + p:SetTall(ScrH() - 310) + + octolib.button(self, 'Обновить', function() + netstream.Start('dbg-estates.getToolData') + end):Dock(BOTTOM) + + estList = p + netstream.Hook('dbg-estates.getToolData', rebuildList) + netstream.Start('dbg-estates.getToolData') + +end + diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/fading_door.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/fading_door.lua new file mode 100644 index 0000000..9d64ae9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/fading_door.lua @@ -0,0 +1,773 @@ +TOOL.Category = "Dobrograd" +TOOL.Name = "#Fading Doors" + +TOOL.ClientConVar["key"] = "41" +TOOL.ClientConVar["swap"] = "0" +TOOL.ClientConVar["keyboard"] = "0" +TOOL.ClientConVar["reversed"] = "0" +TOOL.ClientConVar["fulltransparent"] = "0" +TOOL.ClientConVar["mat"] = "sprites/heatwave" +TOOL.ClientConVar["opensound"] = "0" +TOOL.ClientConVar["loopsound"] = "0" +TOOL.ClientConVar["closesound"] = "0" + +list.Add("FDoorMaterials", "sprites/heatwave") +list.Add("FDoorMaterials", "models/wireframe") +list.Add("FDoorMaterials", "debug/env_cubemap_model") +list.Add("FDoorMaterials", "models/shadertest/shader3") +list.Add("FDoorMaterials", "models/shadertest/shader4") +list.Add("FDoorMaterials", "models/shadertest/shader5") +list.Add("FDoorMaterials", "models/shiny") +list.Add("FDoorMaterials", "models/debug/debugwhite") +list.Add("FDoorMaterials", "Models/effects/comball_sphere") +list.Add("FDoorMaterials", "Models/effects/comball_tape") +list.Add("FDoorMaterials", "Models/effects/splodearc_sheet") +list.Add("FDoorMaterials", "Models/effects/vol_light001") +list.Add("FDoorMaterials", "models/props_combine/stasisshield_sheet") +list.Add("FDoorMaterials", "models/props_combine/portalball001_sheet") +list.Add("FDoorMaterials", "models/props_combine/com_shield001a") +list.Add("FDoorMaterials", "models/props_c17/frostedglass_01a") +list.Add("FDoorMaterials", "models/props_lab/Tank_Glass001") +list.Add("FDoorMaterials", "models/props_combine/tprings_globe") +list.Add("FDoorMaterials", "models/rendertarget") +list.Add("FDoorMaterials", "models/screenspace") +list.Add("FDoorMaterials", "brick/brick_model") +list.Add("FDoorMaterials", "models/props_pipes/GutterMetal01a") +list.Add("FDoorMaterials", "models/props_pipes/Pipesystem01a_skin3") +list.Add("FDoorMaterials", "models/props_wasteland/wood_fence01a") +list.Add("FDoorMaterials", "models/props_foliage/tree_deciduous_01a_trunk") +list.Add("FDoorMaterials", "models/props_c17/FurnitureFabric003a") +list.Add("FDoorMaterials", "models/props_c17/FurnitureMetal001a") +list.Add("FDoorMaterials", "models/props_c17/paper01") +list.Add("FDoorMaterials", "models/flesh") + +if SERVER then + util.AddNetworkString("DrawFadeDoor") +end + +local Sounds = {} +Sounds[1] = Sound("doors/doorstop1.wav") +Sounds[2] = Sound("npc/turret_floor/retract.wav") +Sounds[3] = Sound("npc/roller/mine/combine_mine_deactivate1.wav") +Sounds[4] = Sound("npc/roller/mine/combine_mine_deploy1.wav") +Sounds[5] = Sound("npc/roller/mine/rmine_taunt1.wav") +Sounds[6] = Sound("npc/scanner/scanner_nearmiss2.wav") +Sounds[7] = Sound("npc/scanner/scanner_siren1.wav") +Sounds[8] = Sound("npc/barnacle/barnacle_gulp1.wav") +Sounds[9] = Sound("npc/barnacle/barnacle_gulp2.wav") +Sounds[10] = Sound("npc/combine_gunship/attack_start2.wav") +Sounds[11] = Sound("npc/combine_gunship/attack_stop2.wav") +Sounds[12] = Sound("npc/dog/dog_pneumatic1.wav") +Sounds[13] = Sound("npc/dog/dog_pneumatic2.wav") + +util.PrecacheSound(Sounds[1]) +util.PrecacheSound(Sounds[2]) +util.PrecacheSound(Sounds[3]) +util.PrecacheSound(Sounds[4]) +util.PrecacheSound(Sounds[5]) +util.PrecacheSound(Sounds[6]) +util.PrecacheSound(Sounds[7]) +util.PrecacheSound(Sounds[8]) +util.PrecacheSound(Sounds[9]) +util.PrecacheSound(Sounds[10]) +util.PrecacheSound(Sounds[11]) +util.PrecacheSound(Sounds[12]) +util.PrecacheSound(Sounds[13]) + +local LoopSounds = {} +LoopSounds[1] = "ambient/machines/machine6.wav" +LoopSounds[2] = "ambient/energy/force_field_loop1.wav" +LoopSounds[3] = "physics/metal/canister_scrape_smooth_loop1.wav" +LoopSounds[4] = "ambient/levels/citadel/citadel_drone_loop5.wav" +LoopSounds[5] = "ambient/levels/citadel/citadel_drone_loop6.wav" +LoopSounds[6] = "ambient/atmosphere/city_rumble_loop1.wav" +LoopSounds[7] = "ambient/machines/city_ventpump_loop1.wav" +LoopSounds[8] = "ambient/machines/combine_shield_loop3.wav" +LoopSounds[9] = "npc/manhack/mh_engine_loop1.wav" +LoopSounds[10] = "npc/manhack/mh_engine_loop2.wav" + +local function checkAccess(ply) + return serverguard.player:HasPermission(ply, 'DBG: Открывать Fading Door с помощью кнопки') +end + +if CLIENT then + language.Add("Tool.fading_door.name", "Fading Doors") + language.Add("Tool.fading_door.desc", "Позволяет создать исчезающие двери, открываемые с помощью кнопок") + language.Add("Tool.fading_door.0", "Нажми на проп, чтобы создать исчезающую дверь. Правая кнопка - скопировать настройки. Перезарядка - удалить исчезающую дверь.") + + function TOOL:BuildCPanel() + self:AddControl("Header", {Text = "#Tool.fading_door.name", Description = "#Tool.fading_door.desc"}) + self:AddControl("CheckBox", {Label = "Открыть сразу", Command = "fading_door_reversed"}) + self:AddControl("CheckBox", {Label = L.full_transparency, Command = "fading_door_fulltransparent"}) + self:AddControl("CheckBox", {Label = "Одно нажатие для открытия", Command = "fading_door_swap"}) + + if checkAccess(LocalPlayer()) then + self:AddControl("CheckBox", {Label = "Открывать с помощью клавиатуры", Command = "fading_door_keyboard"}) + end + + local DoorOpenSound = vgui.Create("CtrlListBox", self) + DoorOpenSound:AddOption("None", {fading_door_opensound = "0"}) + if file.Exists("sound/doors/doorstop1.wav", "GAME") then DoorOpenSound:AddOption("1", {fading_door_opensound = "1"}) end + if file.Exists("sound/npc/turret_floor/retract.wav", "GAME") then DoorOpenSound:AddOption("2", {fading_door_opensound = "2"}) end + if file.Exists("sound/npc/roller/mine/combine_mine_deactivate1.wav", "GAME") then DoorOpenSound:AddOption("3", {fading_door_opensound = "3"}) end + if file.Exists("sound/npc/roller/mine/combine_mine_deploy1.wav", "GAME") then DoorOpenSound:AddOption("4", {fading_door_opensound = "4"}) end + if file.Exists("sound/npc/roller/mine/rmine_taunt1.wav", "GAME") then DoorOpenSound:AddOption("5", {fading_door_opensound = "5"}) end + if file.Exists("sound/npc/scanner/scanner_nearmiss2.wav", "GAME") then DoorOpenSound:AddOption("6", {fading_door_opensound = "6"}) end + if file.Exists("sound/npc/scanner/scanner_siren1.wav", "GAME") then DoorOpenSound:AddOption("7", {fading_door_opensound = "7"}) end + if file.Exists("sound/npc/barnacle/barnacle_gulp1.wav", "GAME") then DoorOpenSound:AddOption("8", {fading_door_opensound = "8"}) end + if file.Exists("sound/npc/barnacle/barnacle_gulp2.wav", "GAME") then DoorOpenSound:AddOption("9", {fading_door_opensound = "9"}) end + if file.Exists("sound/npc/combine_gunship/attack_start2.wav", "GAME") then DoorOpenSound:AddOption("10", {fading_door_opensound = "10"}) end + if file.Exists("sound/npc/combine_gunship/attack_stop2.wav", "GAME") then DoorOpenSound:AddOption("11", {fading_door_opensound = "11"}) end + if file.Exists("sound/npc/dog/dog_pneumatic1.wav", "GAME") then DoorOpenSound:AddOption("12", {fading_door_opensound = "12"}) end + if file.Exists("sound/npc/dog/dog_pneumatic2.wav", "GAME") then DoorOpenSound:AddOption("13", {fading_door_opensound = "13"}) end + + local left = vgui.Create("DLabel", self) + left:SetText("Звук открытия") + left:SetDark(true) + DoorOpenSound:SetHeight(25) + DoorOpenSound:Dock(TOP) + + self:AddItem(left, DoorOpenSound) + + local DoorActiveSound = vgui.Create("CtrlListBox", self) + DoorActiveSound:AddOption("None", {fading_door_loopsound = "0"}) + if file.Exists("sound/"..LoopSounds[1], "GAME") then DoorActiveSound:AddOption("1", {fading_door_loopsound = "1"}) end + if file.Exists("sound/"..LoopSounds[2], "GAME") then DoorActiveSound:AddOption("2", {fading_door_loopsound = "2"}) end + if file.Exists("sound/"..LoopSounds[3], "GAME") then DoorActiveSound:AddOption("3", {fading_door_loopsound = "3"}) end + if file.Exists("sound/"..LoopSounds[4], "GAME") then DoorActiveSound:AddOption("4", {fading_door_loopsound = "4"}) end + if file.Exists("sound/"..LoopSounds[5], "GAME") then DoorActiveSound:AddOption("5", {fading_door_loopsound = "5"}) end + if file.Exists("sound/"..LoopSounds[6], "GAME") then DoorActiveSound:AddOption("6", {fading_door_loopsound = "6"}) end + if file.Exists("sound/"..LoopSounds[7], "GAME") then DoorActiveSound:AddOption("7", {fading_door_loopsound = "7"}) end + if file.Exists("sound/"..LoopSounds[8], "GAME") then DoorActiveSound:AddOption("8", {fading_door_loopsound = "8"}) end + if file.Exists("sound/"..LoopSounds[9], "GAME") then DoorActiveSound:AddOption("9", {fading_door_loopsound = "9"}) end + if file.Exists("sound/"..LoopSounds[10], "GAME") then DoorActiveSound:AddOption("10", {fading_door_loopsound = "10"}) end + + local left = vgui.Create("DLabel", self) + left:SetText("Активный звук") + left:SetDark(true) + DoorActiveSound:SetHeight(25) + DoorActiveSound:Dock(TOP) + + self:AddItem(left, DoorActiveSound) + + local DoorCloseSound = vgui.Create("CtrlListBox", self) + DoorCloseSound:AddOption("None", {fading_door_closesound = "0"}) + if file.Exists("sound/doors/doorstop1.wav", "GAME") then DoorCloseSound:AddOption("1", {fading_door_closesound = "1"}) end + if file.Exists("sound/npc/turret_floor/retract.wav", "GAME") then DoorCloseSound:AddOption("2", {fading_door_closesound = "2"}) end + if file.Exists("sound/npc/roller/mine/combine_mine_deactivate1.wav", "GAME") then DoorCloseSound:AddOption("3", {fading_door_closesound = "3"}) end + if file.Exists("sound/npc/roller/mine/combine_mine_deploy1.wav", "GAME") then DoorCloseSound:AddOption("4", {fading_door_closesound = "4"}) end + if file.Exists("sound/npc/roller/mine/rmine_taunt1.wav", "GAME") then DoorCloseSound:AddOption("5", {fading_door_closesound = "5"}) end + if file.Exists("sound/npc/scanner/scanner_nearmiss2.wav", "GAME") then DoorCloseSound:AddOption("6", {fading_door_closesound = "6"}) end + if file.Exists("sound/npc/scanner/scanner_siren1.wav", "GAME") then DoorCloseSound:AddOption("7", {fading_door_closesound = "7"}) end + if file.Exists("sound/npc/barnacle/barnacle_gulp1.wav", "GAME") then DoorCloseSound:AddOption("8", {fading_door_closesound = "8"}) end + if file.Exists("sound/npc/barnacle/barnacle_gulp2.wav", "GAME") then DoorCloseSound:AddOption("9", {fading_door_closesound = "9"}) end + if file.Exists("sound/npc/combine_gunship/attack_start2.wav", "GAME") then DoorCloseSound:AddOption("10", {fading_door_closesound = "10"}) end + if file.Exists("sound/npc/combine_gunship/attack_stop2.wav", "GAME") then DoorCloseSound:AddOption("11", {fading_door_closesound = "11"}) end + if file.Exists("sound/npc/dog/dog_pneumatic1.wav", "GAME") then DoorCloseSound:AddOption("12", {fading_door_closesound = "12"}) end + if file.Exists("sound/npc/dog/dog_pneumatic2.wav", "GAME") then DoorCloseSound:AddOption("13", {fading_door_closesound = "13"}) end + + local left = vgui.Create("DLabel", self) + left:SetText("Звук закрытия") + left:SetDark(true) + DoorCloseSound:SetHeight(25) + DoorCloseSound:Dock(TOP) + + self:AddItem(left, DoorCloseSound) + self:AddControl("Numpad", {Label = "Кнопка", ButtonSize = "22", Command = "fading_door_key"}) + --self:MatSelect("fading_door_mat", list.Get("FDoorMaterials"), true, 0.33, 0.33) + end + + local EFFECT = {} + + net.Receive("DrawFadeDoor",function() + local String = net.ReadString() + if String == "0" then + EFFECT.Type = nil + EFFECT.Ent = nil + if EFFECT.Remove == false then EFFECT.Remove = true end + else + EFFECT.Type = nil + EFFECT.Ent = nil + if EFFECT.Remove == nil then util.Effect("render_fade_door", EffectData()) end + EFFECT.Remove = false + + local Table = string.Explode("_",String) + local Ent = ents.GetByIndex(tonumber(Table[1])) + if IsValid(Ent) then + EFFECT.Type = tonumber(Table[2]) + EFFECT.Ent = Ent + end + end + end) + + function EFFECT:Init(data) end + + function EFFECT:Think() + -- This makes the effect always visible. + local pl = LocalPlayer() + local Pos = pl:EyePos() + local Trace = {} + Trace.start = Pos + Trace.endpos = Pos+(pl:GetAimVector()*10) + Trace.filter = {pl} + local TR = util.TraceLine(Trace) + self:SetPos(TR.HitPos) + + -- Remove when ent is not valid. + if !IsValid(EFFECT.Ent) then + EFFECT.Type = nil + EFFECT.Ent = nil + EFFECT.Remove = true + end + + if EFFECT.Remove or EFFECT.Remove == nil then + EFFECT.Remove = nil + return false + end + return true + end + + function EFFECT:Render() + if IsValid(EFFECT.Ent) then + if EFFECT.Type == 1 then + halo.Add({EFFECT.Ent}, Color(255, 255, 255, 255), 10, 10, 1) + elseif EFFECT.Type == 2 then + halo.Add({EFFECT.Ent}, Color(100, 255, 100, 255), 10, 10, 1) + else + halo.Add({EFFECT.Ent}, Color(255, 150, 50, 255), 10, 10, 1) + end + end + end + + effects.Register(EFFECT,"render_fade_door",true) + + function TOOL:LeftClick(tr) + if !tr.Entity or !tr.Entity:IsValid() then return false end + if tr.Entity:IsPlayer() or tr.HitWorld then return false end + return true + end + + function TOOL:RightClick(tr) + if !tr.Entity or !tr.Entity:IsValid() then return false end + if tr.Entity:IsPlayer() or tr.HitWorld then return false end + return true + end + + function TOOL:Reload(tr) + if !tr.Entity or !tr.Entity:IsValid() then return false end + if tr.Entity:IsPlayer() or tr.HitWorld then return false end + return true + end + + return +end + +local function fadeActivate(self) + if self.fadeActive then return end + + self:SetRenderMode( RENDERMODE_TRANSALPHA ) + + self.fadeActive = true + self.fadeMaterial = self:GetColor() or Color(255,255,255,255) + self.fadeDoorMaterial = Color(255,255,255, self.fadeFullTransparent and 0 or 50 ) + self:SetColor(self.fadeDoorMaterial) + self:DrawShadow(false) + if self.fadeCanDisableMotion then self:SetNotSolid(true) else self:SetCollisionGroup(COLLISION_GROUP_WORLD) end + local phys = self:GetPhysicsObject() + if IsValid(phys) then + self.fadeMoveable = phys:IsMoveable() + phys:EnableMotion(false) + end + + if self.fadeDoorOpenSound and Sounds and Sounds[self.fadeDoorOpenSound] then + self:EmitSound(Sounds[self.fadeDoorOpenSound],290,100) + end + + if self.fadeDoorLoopSound and LoopSounds and LoopSounds[self.fadeDoorLoopSound] and !self.FadeDoorSound then + self.FadeDoorSound = CreateSound(self, LoopSounds[self.fadeDoorLoopSound]) + self.FadeDoorSound:Play() + self.FadeDoorSound:ChangeVolume(0.15) + end + + if WireLib then + Wire_TriggerOutput(self, "FadeActive", 1) + end +end + +local function fadeDeactivate(self) + APG.entGhost(self, false, true) + timer.Simple(0, function() APG.entUnGhost(self) end) + + self.fadeActive = false + if self.fadeMaterial then self:SetColor(self.fadeMaterial) end + self:DrawShadow(true) + if self.fadeCanDisableMotion then self:SetNotSolid(false) else self:SetCollisionGroup(COLLISION_GROUP_NONE) end + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(self.fadeMoveable or false) + phys:Wake() + end + + if self.fadeDoorCloseSound and Sounds and Sounds[self.fadeDoorCloseSound] then + self:EmitSound(Sounds[self.fadeDoorCloseSound],290,100) + end + + if self.FadeDoorSound then self.FadeDoorSound:Stop() end + self.FadeDoorSound = nil + + if WireLib then + Wire_TriggerOutput(self, "FadeActive", 0) + end +end + +local function onUp(sid, Ent) + if IsValid(Ent) then + local Activate = false + if Ent.fadeToggle then + if Ent.fadeReversed then + Activate = !Ent.fadeActive + else + Activate = Ent.fadeActive + end + elseif Ent.fadeReversed then + Activate = true + end + + local ply = isstring(sid) and player.GetBySteamID(sid) or isentity(sid) and sid or nil + if IsValid(ply) then + if not Ent.fadeKeyboard and not numpad.FromButton() then + return + end + end + if Activate then + if !Ent.fadeActive then Ent:fadeActivate() end + else + if Ent.fadeActive then Ent:fadeDeactivate() end + end + end +end +numpad.Register("Fading Door onUp", onUp) + +local function onDown(sid, Ent) + if IsValid(Ent) then + local Activate = true + if Ent.fadeToggle then + if Ent.fadeReversed then + Activate = Ent.fadeActive + else + Activate = !Ent.fadeActive + end + elseif Ent.fadeReversed then + Activate = false + end + local ply = player.GetBySteamID(sid) + if IsValid(ply) then + if not Ent.fadeKeyboard and not numpad.FromButton() then + return + end + end + + if Activate then + if !Ent.fadeActive then Ent:fadeActivate() end + else + if Ent.fadeActive then Ent:fadeDeactivate() end + end + end +end +numpad.Register("Fading Door onDown", onDown) + +local function getWireInputs(Ent) + local inputs = Ent.Inputs + local names, types, descs = {}, {}, {} + if inputs then + local num + for _, data in pairs(inputs) do + num = data.Num + names[num] = data.Name + types[num] = data.Type + descs[num] = data.Desc + end + end + return names, types, descs +end + +local function doWireInputs(Ent) + local inputs = Ent.Inputs + if !inputs then + Wire_CreateInputs(Ent, {"Fade"}) + return + end + local names, types, descs = {}, {}, {} + local num + for _, data in pairs(inputs) do + num = data.Num + names[num] = data.Name + types[num] = data.Type + descs[num] = data.Desc + end + table.insert(names, "Fade") + WireLib.AdjustSpecialInputs(Ent, names, types, descs) +end + +local function doWireOutputs(Ent) + local outputs = Ent.Outputs + if !outputs then + Wire_CreateOutputs(Ent, {"FadeActive"}) + return + end + local names, types, descs = {}, {}, {} + local num + for _, data in pairs(outputs) do + num = data.Num + names[num] = data.Name + types[num] = data.Type + descs[num] = data.Desc + end + table.insert(names, "FadeActive") + WireLib.AdjustSpecialOutputs(Ent, names, types, descs) +end + +local function TriggerInput(self, name, value, ...) + if name == "Fade" then + if value == 0 then onUp(nil, self) else onDown(nil, self) end + elseif self.fadeTriggerInput then + return self:fadeTriggerInput(name, value, ...) + end +end + +local function PreEntityCopy(self) + if self then + local info = WireLib.BuildDupeInfo(self) + if info then duplicator.StoreEntityModifier(self, "WireDupeInfo", info) end + if self.fadePreEntityCopy then self:fadePreEntityCopy() end + end +end + +local function PostEntityPaste(self, pl, Ent, ents) + if self then + if self.EntityMods and self.EntityMods.WireDupeInfo then WireLib.ApplyDupeInfo(pl, self, self.EntityMods.WireDupeInfo, function(id) return ents[id] end) end + if self.fadePostEntityPaste then self:fadePostEntityPaste(pl, Ent, ents) end + end +end + +local function onRemove(self) + if self.fadeDeactivate then self:fadeDeactivate() end + self.isFadingDoor = nil + self.PreEntityCopy = self.fadePreEntityCopy + self.fadePreEntityCopy = nil + self.PostEntityPaste = self.fadePostEntityPaste + self.fadePostEntityPaste = nil + self.TriggerInput = self.fadeTriggerInput + self.fadeTriggerInput = nil + duplicator.ClearEntityModifier(self, "Fading Door") + if self.fadeUpNum then numpad.Remove(self.fadeUpNum) end + if self.fadeDownNum then numpad.Remove(self.fadeDownNum) end + self.fadeActive = nil + self.fadeMaterial = nil + if IsValid(self.FadingDoorDummy) then self.FadingDoorDummy:Remove() end + self.FadingDoorDummy = nil + self.fadeToggle = nil + self.fadeDoorMaterial = nil + self.fadeMoveable = nil + self.fadeCanDisableMotion = nil + self.fadeDoorOpenSound = nil + self.fadeDoorCloseSound = nil + self.fadeDoorLoopSound = nil + self.fadeDeactivate = nil + self.fadeUpNum = nil + self.fadeDownNum = nil + self.fadeToggleActive = nil + self.fadeReversed = nil + self.fadeFullTransparent = nil + self.fadeActivate = nil + self.fadeKey = nil + if self.OnDieFunctions then + self.OnDieFunctions["UndoFadingDoor"..self:EntIndex()] = nil + self.OnDieFunctions["Fading Doors"] = nil + end + if WireLib then + if self.Inputs then + Wire_Link_Clear(self, "Fade") + self.Inputs['Fade'] = nil + WireLib._SetInputs(self) + end + if self.Outputs then + local port = self.Outputs['FadeActive'] + if port then + for i,inp in ipairs(port.Connected) do + if inp.Entity:IsValid() then + Wire_Link_Clear(inp.Entity, inp.Name) + end + end + end + self.Outputs['FadeActive'] = nil + WireLib._SetOutputs(self) + end + end + if self.EntityMods and self.EntityMods.WireDupeInfo and self.EntityMods.WireDupeInfo.Wires then self.EntityMods.WireDupeInfo.Wires.Fade = nil end +end + +local function RemoveKeys(self) + if self.FadeDoorSound then self.FadeDoorSound:Stop() end + numpad.Remove(self.fadeUpNum) + numpad.Remove(self.fadeDownNum) +end + +local function dooEet(pl, Ent, stuff) + local override = hook.Run('CanTool', pl, { Entity = Ent }, 'fading_door') + if override == false then return end + + if Ent.isFadingDoor then + if Ent.fadeDeactivate then Ent:fadeDeactivate() end + RemoveKeys(Ent) + else + Ent.isFadingDoor = true + Ent.fadeActivate = fadeActivate + Ent.fadeDeactivate = fadeDeactivate + Ent.fadeToggleActive = fadeToggleActive + Ent:CallOnRemove("Fading Doors", RemoveKeys) + if WireLib then + doWireInputs(Ent) + doWireOutputs(Ent) + Ent.fadeTriggerInput = Ent.fadeTriggerInput or Ent.TriggerInput + Ent.TriggerInput = TriggerInput + if !Ent.IsWire then + if !Ent.fadePreEntityCopy and Ent.PreEntityCopy then Ent.fadePreEntityCopy = Ent.PreEntityCopy end + Ent.PreEntityCopy = PreEntityCopy + if !Ent.fadePostEntityPaste and Ent.PreEntityCopy then Ent.fadePostEntityPaste = Ent.PostEntityPaste end + Ent.PostEntityPaste = PostEntityPaste + end + end + end + Ent.fadeUpNum = numpad.OnUp(pl, stuff.key, "Fading Door onUp", Ent) + Ent.fadeDownNum = numpad.OnDown(pl, stuff.key, "Fading Door onDown", Ent) + Ent.fadeToggle = stuff.toggle + Ent.fadeReversed = stuff.reversed + Ent.fadeFullTransparent = stuff.fulltransparent + Ent.fadeKey = stuff.key + Ent.fadeCanDisableMotion = stuff.CanDisableMotion + Ent.fadeDoorMaterial = stuff.DoorMaterial + Ent.fadeDoorOpenSound = stuff.DoorOpenSound + Ent.fadeKeyboard = stuff.DoorKeyboard + Ent.fadeDoorLoopSound = stuff.DoorLoopSound + Ent.fadeDoorCloseSound = stuff.DoorCloseSound + if stuff.reversed then Ent:fadeActivate() end + duplicator.StoreEntityModifier(Ent, "Fading Door", stuff) + return true +end + +duplicator.RegisterEntityModifier("Fading Door", dooEet) + +if !FadingDoor then + local function legacy(pl, Ent, data) + return dooEet(pl, Ent, { + key = data.Key, + toggle = data.Toggle, + reversed = data.Inverse, + CanDisableMotion = data.CanDisableMotion, + DoorMaterial = data.DoorMaterial, + DoorOpenSound = data.DoorOpenSound, + DoorLoopSound = data.DoorLoopSound, + DoorCloseSound = data.DoorCloseSound + }) + end + duplicator.RegisterEntityModifier("FadingDoor", legacy) + hook.Add("Initialize", "FadingDoor2", function() duplicator.RegisterEntityModifier("FadingDoor", legacy) end) -- No overwrite. +end + +function TOOL:LeftClick(tr) + if !tr.Entity or !tr.Entity:IsValid() then return false end + if tr.Entity:IsPlayer() or tr.HitWorld then return false end + if CLIENT then return true end + + local Ent = tr.Entity + local pl = self:GetOwner() + + if !IsValid(pl) then return false end + + local phys = Ent:GetPhysicsObject() + local CanDisableMotion = false + + if phys:IsValid() then + local MotionEnabled = phys:IsMotionEnabled() + phys:EnableMotion(!MotionEnabled) + CanDisableMotion = MotionEnabled != phys:IsMotionEnabled() + phys:EnableMotion(MotionEnabled) + end + + if self.AimEnt then + self.AimEnt[pl] = nil + net.Start("DrawFadeDoor") + net.WriteString("0") + net.Send(pl) + end + + dooEet(pl, Ent, { + key = self:GetClientNumber("key"), + toggle = self:GetClientNumber("swap") == 1, + reversed = self:GetClientNumber("reversed") == 1, + fulltransparent = self:GetClientNumber("fulltransparent") == 1, + CanDisableMotion = CanDisableMotion, + DoorKeyboard = self:GetClientNumber("keyboard") == 1, + DoorMaterial = self:GetClientInfo("mat"), + DoorOpenSound = self:GetClientNumber("opensound"), + DoorLoopSound = self:GetClientNumber("loopsound"), + DoorCloseSound = self:GetClientNumber("closesound") + }) + + if !IsValid(Ent.FadingDoorDummy) then + local Dummy = ents.Create("info_null") + Dummy.Owner = pl + Dummy.Door = Ent + undo.Create("Undo fading door") + undo.AddEntity(Dummy) + Ent.FadingDoorDummy = Dummy + local UndoT = {Ent,self:GetOwner(),self} + undo.AddFunction(function(Undo, UndoT) + local Ent = UndoT[1] + local pl = UndoT[2] + local Tool = UndoT[3] + if IsValid(Ent) then onRemove(Ent) end + if IsValid(pl) then + if Tool and Tool.AimEnt then Tool.AimEnt[pl] = nil end + net.Start("DrawFadeDoor") + net.WriteString("0") + net.Send(pl) + end + end, UndoT) + undo.SetPlayer(pl) + undo.SetCustomUndoText("Undone Fading Door") + undo.Finish() + + Ent:CallOnRemove("UndoFadingDoor"..Ent:EntIndex(),function(Ent) + if Ent.FadingDoorDummy and Ent.FadingDoorDummy:IsValid() then + if IsValid(Ent.FadingDoorDummy.Owner) then + local PlayerID = Ent.FadingDoorDummy.Owner:UniqueID() + local PlayerUndo = undo:GetTable()[PlayerID] + if PlayerUndo then + for k,v in pairs(PlayerUndo) do + if PlayerUndo[k] and PlayerUndo[k].Name and PlayerUndo[k].Name == "Undo fading door" and PlayerUndo[k].Entities and IsValid(PlayerUndo[k].Entities[1]) and PlayerUndo[k].Entities[1]:GetTable().Door == Ent then + undo:GetTable()[PlayerID][k] = nil + break + end + end + end + end + Ent.FadingDoorDummy:Remove() + end + end,Ent) + end + + return true +end + +function TOOL:RightClick(tr) + if !tr.Entity or !tr.Entity:IsValid() then return false end + if tr.Entity:IsPlayer() or tr.HitWorld then return false end + if CLIENT then return true end + local Ent = tr.Entity + if Ent.isFadingDoor then + local pl = self:GetOwner() + if !IsValid(pl) then return false end + if Ent.fadeKey != nil then pl:ConCommand("fading_door_key "..tostring(Ent.fadeKey)) end + if Ent.fadeToggle != nil then if Ent.fadeToggle then pl:ConCommand("fading_door_swap 1") else pl:ConCommand("fading_door_swap 0") end end + if checkAccess(pl) and Ent.fadeKeyboard != nil then if Ent.fadeKeyboard then pl:ConCommand("fading_door_keyboard 1") else pl:ConCommand("fading_door_keyboard 0") end end + if Ent.fadeReversed != nil then if Ent.fadeReversed then pl:ConCommand("fading_door_reversed 1") else pl:ConCommand("fading_door_reversed 0") end end + if Ent.fadeFullTransparent != nil then if Ent.fadeFullTransparent then pl:ConCommand("fading_door_fulltransparent 1") else pl:ConCommand("fading_door_fulltransparent 0") end end + -- if Ent.fadeDoorMaterial != nil then pl:ConCommand("fading_door_mat "..Ent.fadeDoorMaterial) end + if Ent.fadeDoorOpenSound != nil then pl:ConCommand("fading_door_opensound "..tostring(Ent.fadeDoorOpenSound)) end + if Ent.fadeDoorLoopSound != nil then pl:ConCommand("fading_door_loopsound "..tostring(Ent.fadeDoorLoopSound)) end + if Ent.fadeDoorCloseSound != nil then pl:ConCommand("fading_door_closesound "..tostring(Ent.fadeDoorCloseSound)) end + return true + end +end + +function TOOL:Reload(tr) + if !tr.Entity or !tr.Entity:IsValid() then return false end + if tr.Entity:IsPlayer() or tr.HitWorld then return false end + if CLIENT then return true end + local Ent = tr.Entity + + if Ent.isFadingDoor then + if IsValid(Ent.FadingDoorDummy) then + if IsValid(Ent.FadingDoorDummy.Owner) then + local PlayerID = Ent.FadingDoorDummy.Owner:UniqueID() + local PlayerUndo = undo:GetTable()[PlayerID] + if PlayerUndo then + for k,v in pairs(PlayerUndo) do + if PlayerUndo[k] and PlayerUndo[k].Name and PlayerUndo[k].Name == "Undo fading door" and PlayerUndo[k].Entities and IsValid(PlayerUndo[k].Entities[1]) and PlayerUndo[k].Entities[1]:GetTable().Door == Ent then + undo:GetTable()[PlayerID][k] = nil + break + end + end + end + end + Ent.FadingDoorDummy:Remove() + end + onRemove(Ent) + net.Start("DrawFadeDoor") + net.WriteString(tostring(Ent:EntIndex()).."_1") + net.Send(self:GetOwner()) + return true + end +end + +function TOOL:Holster() + if CLIENT then return end + local pl = self:GetOwner() + if !IsValid(pl) then return false end + if self.AimEnt and self.AimEnt[pl] != nil then + self.AimEnt[pl] = nil + net.Start("DrawFadeDoor") + net.WriteString("0") + net.Send(pl) + end +end + +function TOOL:Think() + if CLIENT then return end + if self.Hold then return end + local pl = self:GetOwner() + local trace = pl:GetEyeTrace() + + if trace.Hit and trace.Entity and trace.Entity:IsValid() and !trace.Entity:IsPlayer() then + if !self.AimEnt then self.AimEnt = {} end + if !self.OldKey then self.OldKey = {} end + if !self.OldToggle then self.OldToggle = {} end + if !self.OldReversed then self.OldReversed = {} end + + if !IsValid(pl) then return false end + + if trace.Entity != self.AimEnt[pl] or self:GetClientNumber("key") != self.OldKey[pl] or self:GetClientNumber("swap") != self.OldToggle[pl] or self:GetClientNumber("reversed") != self.OldReversed[pl] then + self.AimEnt[pl] = trace.Entity + local Key = self:GetClientNumber("key") + self.OldKey[pl] = Key + local Toggle = self:GetClientNumber("swap") + self.OldToggle[pl] = Toggle + local Reversed = self:GetClientNumber("reversed") + self.OldReversed[pl] = Reversed + if trace.Entity.isFadingDoor then + Toggle = Toggle == 1 + Reversed = Reversed == 1 + if trace.Entity.fadeKey == Key and trace.Entity.fadeReversed == Reversed and trace.Entity.fadeToggle == Toggle then + net.Start("DrawFadeDoor") + net.WriteString(tostring(trace.Entity:EntIndex()).."_2") + net.Send(pl) + else + net.Start("DrawFadeDoor") + net.WriteString(tostring(trace.Entity:EntIndex()).."_3") + net.Send(pl) + end + else + net.Start("DrawFadeDoor") + net.WriteString(tostring(trace.Entity:EntIndex()).."_1") + net.Send(pl) + end + end + elseif self.AimEnt and self.AimEnt[pl] != nil then + self.AimEnt[pl] = nil + net.Start("DrawFadeDoor") + net.WriteString("0") + net.Send(pl) + end +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/ignite.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/ignite.lua new file mode 100644 index 0000000..835e6c5 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/ignite.lua @@ -0,0 +1,53 @@ +TOOL.Category = "Dobrograd" +TOOL.Name = L.ignite +TOOL.Command = nil +TOOL.ConfigName = nil + +if CLIENT then +language.Add("tool.ignite.name", "Ignite") +language.Add("tool.ignite.desc", "Light a prop or ragdoll on fire") +language.Add("tool.ignite.0", "Left click to light on fire. Right click to extinguish.") +language.Add("tool.ignite.length", "Duration:") +language.Add("tool.ignite.desc", "How long the prop stays on fire") +end + +TOOL.ClientConVar["length"] = 15 + +function TOOL:LeftClick( trace ) + + if (!trace.Entity) then return false end + if (!trace.Entity:IsValid() ) then return false end + if (trace.Entity:IsPlayer()) then return false end + if (trace.Entity:IsWorld()) then return false end + + if ( CLIENT ) then return true end + + local _length = math.Max( self:GetClientNumber( "length" ), 2 ) + + trace.Entity:Extinguish() + trace.Entity:Ignite( _length, 0 ) + + return true + +end + +function TOOL.BuildCPanel( panel ) + panel:AddControl( "Header", { Text = "#tool.ignite.name", Description = "#tool.ignite.desc" } ) + panel:AddControl( "Slider", { Label = "#tool.ignite.length", Type = "Float", Command = "ignite_length", Min = "0", Max = "1000" } ) +end + +function TOOL:RightClick( trace ) + + if (!trace.Entity) then return false end + if (!trace.Entity:IsValid() ) then return false end + if (trace.Entity:IsPlayer()) then return false end + if (trace.Entity:IsWorld()) then return false end + + // Client can bail out now + if ( CLIENT ) then return true end + + trace.Entity:Extinguish() + + return true + +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/imgscreen.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/imgscreen.lua new file mode 100644 index 0000000..ba6f25f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/imgscreen.lua @@ -0,0 +1,141 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Image Screen' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, +} + +cleanup.Register('imgscreens') + +if CLIENT then + octolib.vars.init('tools.imgscreen.url', 'https://i.imgur.com/lQb95Kc.jpg') + octolib.vars.init('tools.imgscreen.w', 350) + octolib.vars.init('tools.imgscreen.h', 350) + octolib.vars.init('tools.imgscreen.fade', 300) + octolib.vars.init('tools.imgscreen.color', Color(255,255,255, 255)) +else + CreateConVar('sbox_maximgscreens', 10) +end + +local allowedExt = octolib.array.toKeys {'.jpg', '.png'} +function TOOL:LeftClick(tr) + + if CLIENT then return true end + + local ply = self:GetOwner() + local extended = ply:query('DBG: Расширенный Image Screen') + ply:GetClientVar({ + 'tools.imgscreen.url', + 'tools.imgscreen.w', + 'tools.imgscreen.h', + 'tools.imgscreen.fade', + 'tools.imgscreen.color', + }, function(vars) + local url, w, h, col, fade = + tostring(vars['tools.imgscreen.url']) or 'https://i.imgur.com/lQb95Kc.jpg', + math.Clamp(tonumber(vars['tools.imgscreen.w']) or 16, 16, extended and 16384 or 2048), + math.Clamp(tonumber(vars['tools.imgscreen.h']) or 16, 16, extended and 16384 or 2048), + vars['tools.imgscreen.color'] or Color(255,255,255, 255), + extended and math.Clamp(tonumber(vars['tools.imgscreen.fade']) or 300, 10, 2000) or nil + + if url:sub(1, 20) ~= 'https://i.imgur.com/' then + ply:Notify('warning', 'Можно использовать только ссылки с Imgur') + return + end + + if not allowedExt[url:sub(-4)] then + ply:Notify('warning', 'Неправильный формат ссылки') + return + end + + local ent = tr.Entity + if not IsValid(ent) or ent:GetClass() ~= 'imgscreen' then + if not ply:CheckLimit('imgscreens') then return false end + + local pos, ang = LocalToWorld(Vector(0.1,0,0), Angle(90,0,0), tr.HitPos, tr.HitNormal:Angle()) + ent = ents.Create('imgscreen') + ent:SetPos(pos) + ent:SetAngles(ang) + ent:Spawn() + ent:Activate() + + ply:AddCount('imgscreens', ent) + ply:AddCleanup('imgscreens', ent) + + undo.Create('imgscreen') + undo.AddEntity(ent) + undo.SetPlayer(ply) + + if IsValid(tr.Entity) then + ent:PhysicsInit(SOLID_VPHYSICS) + ent:SetSolid(SOLID_VPHYSICS) + ent:SetMoveType(MOVETYPE_VPHYSICS) + local ph = ent:GetPhysicsObject() + ph:SetMass(1) + ph:EnableCollisions(false) + ent:SetCollisionGroup(COLLISION_GROUP_WORLD) + ent.nocollide = true + + local weld = constraint.Weld(ent, tr.Entity, 0, tr.PhysicsBone, 0, true, true) + undo.AddEntity(weld) + else + ent:GetPhysicsObject():EnableMotion(false) + end + undo.Finish() + end + + ent.imgURL = url + ent.imgW = w + ent.imgH = h + ent.imgFade = fade and fade*fade or nil + ent.imgColor = col + ent:UpdateImage() + + end) + + return true +end + +function TOOL:RightClick(tr) + + return false + +end + +function TOOL:BuildCPanel() + + local extended = LocalPlayer():query('DBG: Расширенный Image Screen') + self:AddControl('Header', { + Text = 'Image Screens', + Description = 'Используй этот тул для размещения картинок в игровом мире. Например, его можно использовать для вывесок или граффити' .. (extended and '. Ты используешь расширенную версию инструмента' or ''), + }) + + octolib.vars.presetManager(self, 'tools.imgscreen', {'tools.imgscreen.url', 'tools.imgscreen.w', 'tools.imgscreen.h', 'tools.imgscreen.color'}) + + octolib.vars.textEntry(self, 'tools.imgscreen.url', '') + self:ControlHelp('Принимаются только ссылки с Imgur формата https://i.imgur.com/многобукв.png\nПоддерживаются форматы .png и .jpg') + + octolib.vars.slider(self, 'tools.imgscreen.w', L.width, 16, extended and 16384 or 2048, 0) + octolib.vars.slider(self, 'tools.imgscreen.h', L.height, 16, extended and 16384 or 2048, 0) + if extended then + octolib.vars.slider(self, 'tools.imgscreen.fade', 'Расстояние затухания', 10, 2000, 0) + end + octolib.vars.colorPicker(self, 'tools.imgscreen.color', L.color) + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + language.Add('Tool.imgscreen.name', 'Image Screens') + language.Add('Tool.imgscreen.desc', 'Картинки по ссылке в игровом мире') + language.Add('Tool.imgscreen.left', 'Поставить картинку') + language.Add('Undone_imgscreen', 'Картинка удалена') + language.Add('Cleanup_imgscreens', 'Картинки очищены') + language.Add('Cleaned_imgscreens', 'Картинки очищены') + language.Add('SBoxLimit_imgscreens', 'Достигнут лимит картинок') +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/keypad_willox.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/keypad_willox.lua new file mode 100644 index 0000000..51dbef9 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/keypad_willox.lua @@ -0,0 +1,218 @@ +if (SERVER) then + CreateConVar('sbox_maxkeypads', 10) +end + +TOOL.Category = "Dobrograd" +TOOL.Name = "Кейпады" +TOOL.Command = nil + +TOOL.ClientConVar['weld'] = '1' +TOOL.ClientConVar['freeze'] = '1' + +TOOL.ClientConVar['password'] = '1234' +TOOL.ClientConVar['secure'] = '0' + +TOOL.ClientConVar['repeats_granted'] = '0' +TOOL.ClientConVar['repeats_denied'] = '0' + +TOOL.ClientConVar['length_granted'] = '0.1' +TOOL.ClientConVar['length_denied'] = '0.1' + +TOOL.ClientConVar['delay_granted'] = '0' +TOOL.ClientConVar['delay_denied'] = '0' + +TOOL.ClientConVar['init_delay_granted'] = '0' +TOOL.ClientConVar['init_delay_denied'] = '0' + +TOOL.ClientConVar['key_granted'] = '0' +TOOL.ClientConVar['key_denied'] = '0' + +cleanup.Register("keypads") + +if CLIENT then + language.Add("tool.keypad_willox.name", "Кейпады") + language.Add("tool.keypad_willox.left", "Создать новый кейпад") + language.Add("tool.keypad_willox.right", "Обновить существующий") + language.Add("tool.keypad_willox.desc", "Создает кодовые замки") + + language.Add("Undone_Keypad", "Кейпад удален") + language.Add("Cleanup_keypads", "Кейпады") + language.Add("Cleaned_keypads", "Удалены все кейпады") + + language.Add("SBoxLimit_keypads", "Достигнут лимит кейпадов!") +end + +function TOOL:SetupKeypad(ent, pass) + local data = { + Password = pass, + + RepeatsGranted = self:GetClientNumber("repeats_granted"), + RepeatsDenied = self:GetClientNumber("repeats_denied"), + + LengthGranted = self:GetClientNumber("length_granted"), + LengthDenied = self:GetClientNumber("length_denied"), + + DelayGranted = self:GetClientNumber("delay_granted"), + DelayDenied = self:GetClientNumber("delay_denied"), + + InitDelayGranted = self:GetClientNumber("init_delay_granted"), + InitDelayDenied = self:GetClientNumber("init_delay_denied"), + + KeyGranted = self:GetClientNumber("key_granted"), + KeyDenied = self:GetClientNumber("key_denied"), + + Secure = util.tobool(self:GetClientNumber("secure")), + + Owner = self:GetOwner():SteamID() + } + + ent:SetData(data) +end + +function TOOL:RightClick(tr) + if not IsValid(tr.Entity) or not tr.Entity:GetClass():lower() == "keypad" or not tr.Entity.KeypadData then return false end + + if CLIENT then return true end + + local ply = self:GetOwner() + local password = tonumber(ply:GetInfo("keypad_willox_password")) + + local spawn_pos = tr.HitPos + local trace_ent = tr.Entity + + if password == nil or (string.len(tostring(password)) > 4) or (string.find(tostring(password), "0")) then + ply:PrintMessage(3, "Пароль должен состоять не более чем из четырех цифр и не содержать 0!") + return false + end + + if trace_ent.KeypadData.Owner == ply:SteamID() then + self:SetupKeypad(trace_ent, password) + return true + end +end + +function TOOL:LeftClick(tr) + if IsValid(tr.Entity) and tr.Entity:GetClass():lower() == "player" then return false end + + if CLIENT then return true end + + local ply = self:GetOwner() + local password = self:GetClientNumber("password") + + local spawn_pos = tr.HitPos + tr.HitNormal + local trace_ent = tr.Entity + + if password == nil or (string.len(tostring(password)) > 4) or (string.find(tostring(password), "0")) then + ply:PrintMessage(3, "Пароль должен состоять не более чем из четырех цифр и не содержать 0!") + return false + end + + if not self:GetWeapon():CheckLimit("keypads") then return false end + + local ent = ents.Create("keypad") + ent:SetPos(spawn_pos) + ent:SetAngles(tr.HitNormal:Angle()) + ent:Spawn() + + ent:SetPlayer(ply) + + local freeze = util.tobool(self:GetClientNumber("freeze")) + local weld = util.tobool(self:GetClientNumber("weld")) + + if freeze or weld then + local phys = ent:GetPhysicsObject() + + if IsValid(phys) then + phys:EnableMotion(false) + end + end + + -- if weld then + -- local weld = constraint.Weld(ent, trace_ent, 0, 0, 0, true, false) + -- end + + self:SetupKeypad(ent, password) + + undo.Create("Keypad") + undo.AddEntity(ent) + undo.SetPlayer(ply) + undo.Finish() + + ply:AddCount("keypads", ent) + ply:AddCleanup("keypads", ent) + + return true +end + + +if CLIENT then + local function ResetSettings(ply) + ply:ConCommand("keypad_willox_repeats_granted 0") + ply:ConCommand("keypad_willox_repeats_denied 0") + ply:ConCommand("keypad_willox_length_granted 0.1") + ply:ConCommand("keypad_willox_length_denied 0.1") + ply:ConCommand("keypad_willox_delay_granted 0") + ply:ConCommand("keypad_willox_delay_denied 0") + ply:ConCommand("keypad_willox_init_delay_granted 0") + ply:ConCommand("keypad_willox_init_delay_denied 0") + end + + concommand.Add("keypad_willox_reset", ResetSettings) + + function TOOL.BuildCPanel(CPanel) + local r, l = CPanel:TextEntry("Код доступа", "keypad_willox_password") + r:SetTall(22) + + CPanel:ControlHelp("Максимальная длина: 4\nРазрешены цифры 1-9") + + CPanel:CheckBox("Не показывать код на экране", "keypad_willox_secure") + CPanel:CheckBox("Приварить", "keypad_willox_weld") + CPanel:CheckBox("Закрепить", "keypad_willox_freeze") + + local ctrl = vgui.Create("CtrlNumPad", CPanel) + ctrl:SetConVar1("keypad_willox_key_granted") + ctrl:SetConVar2("Keypad_willox_key_denied") + ctrl:SetLabel1("Если код верен, нажать") + ctrl:SetLabel2("Если код неверен, нажать") + CPanel:AddPanel(ctrl) + + local granted = vgui.Create("DForm") + granted:SetName("Поведение при правильном коде") + + granted:NumSlider("Длина [1]", "keypad_willox_length_granted", 0.1, 10, 2) + granted:NumSlider("Перв. задерж. [2]", "keypad_willox_init_delay_granted", 0, 10, 2) + granted:NumSlider("Доп. задерж. [3]", "keypad_willox_delay_granted", 0, 10, 2) + granted:NumSlider("Доп. активаций [4]", "keypad_willox_repeats_granted", 0, 5, 0) + CPanel:AddItem(granted) + + local denied = vgui.Create("DForm") + denied:SetName("Поведение при неправильном коде") + + denied:NumSlider("Длина [1]", "keypad_willox_length_denied", 0.1, 10, 2) + denied:NumSlider("Перв. задерж. [2]", "keypad_willox_init_delay_denied", 0, 10, 2) + denied:NumSlider("Доп. задерж. [3]", "keypad_willox_delay_denied", 0, 10, 2) + denied:NumSlider("Доп. активаций [4]", "keypad_willox_repeats_denied", 0, 5, 0) + CPanel:AddItem(denied) + + CPanel:Button("Настройки по умолчанию", "keypad_willox_reset") + + CPanel:Help("") + + local faq = CPanel:Help("Справка") + faq:SetFont("GModWorldtip") + + CPanel:Help("[1] Сколько времени удерживать кнопку зажатой?") + CPanel:Help("[2] Через сколько времени после активации нажать кнопку первый раз?") + CPanel:Help("[3] Через сколько времени после предыдущего отжатия кнопки нажать ее еще раз?") + CPanel:Help("[4] Сколько раз дополнительно нужно нажать эту кнопку? ([3] - задержка между нажатиями)") + + CPanel:Help("") + + CPanel:Help("Используй цифры на клавиатуре для ввода кода.") + + CPanel:Help("") + + CPanel:Help("Создано Willox (http://steamcommunity.com/id/wiox)") + CPanel:Help("Модифицировано Octothorp Team (https://octothorp.team)") + end +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/keypad_willox_wire.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/keypad_willox_wire.lua new file mode 100644 index 0000000..8c63441 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/keypad_willox_wire.lua @@ -0,0 +1,226 @@ +if (SERVER) then + -- CreateConVar('sbox_maxkeypads', 10) -- Handled by keypad_willox.lua +end + +TOOL.Category = "Dobrograd" +TOOL.Name = "Кейпады - Wire" +TOOL.Command = nil + +TOOL.ClientConVar['weld'] = '1' +TOOL.ClientConVar['freeze'] = '1' + +TOOL.ClientConVar['password'] = '1234' +TOOL.ClientConVar['secure'] = '0' + +TOOL.ClientConVar['repeats_granted'] = '0' +TOOL.ClientConVar['repeats_denied'] = '0' + +TOOL.ClientConVar['length_granted'] = '0.1' +TOOL.ClientConVar['length_denied'] = '0.1' + +TOOL.ClientConVar['delay_granted'] = '0' +TOOL.ClientConVar['delay_denied'] = '0' + +TOOL.ClientConVar['init_delay_granted'] = '0' +TOOL.ClientConVar['init_delay_denied'] = '0' + +TOOL.ClientConVar['output_on'] = '1' +TOOL.ClientConVar['output_off'] = '0' + +-- cleanup.Register("keypads") -- Handled by keypad_willox.lua + +if CLIENT then + language.Add("tool.keypad_willox_wire.name", "Кейпады - Wire") + language.Add("tool.keypad_willox_wire.left", "Создать новый кейпад") + language.Add("tool.keypad_willox_wire.right", "Обновить существующий") + language.Add("tool.keypad_willox_wire.desc", "Создает кодовые замки") + + --[[ + language.Add("Undone_Keypad", "Undone Keypad") + language.Add("Cleanup_keypads", "Keypads") + language.Add("Cleaned_keypads", "Cleaned up all Keypads") + + language.Add("SBoxLimit_keypads", "You've hit the Keypad limit!") + ]] -- Handled by keypad_willox.lua +end + +function TOOL:SetupKeypad(ent, pass) + local data = { + Password = pass, + + RepeatsGranted = self:GetClientNumber("repeats_granted"), + RepeatsDenied = self:GetClientNumber("repeats_denied"), + + LengthGranted = self:GetClientNumber("length_granted"), + LengthDenied = self:GetClientNumber("length_denied"), + + DelayGranted = self:GetClientNumber("delay_granted"), + DelayDenied = self:GetClientNumber("delay_denied"), + + InitDelayGranted = self:GetClientNumber("init_delay_granted"), + InitDelayDenied = self:GetClientNumber("init_delay_denied"), + + OutputOn = self:GetClientNumber("output_on"), + OutputOff = self:GetClientNumber("output_off"), + + Secure = util.tobool(self:GetClientNumber("secure")), + + Owner = self:GetOwner():SteamID() + } + + ent:SetData(data) +end + +function TOOL:RightClick(tr) + if not WireLib then return false end + if not IsValid(tr.Entity) or not tr.Entity:GetClass():lower() == "keypad_wire" then return false end + + if CLIENT then return true end + + local ply = self:GetOwner() + local password = tonumber(ply:GetInfo("keypad_willox_wire_password")) + + local spawn_pos = tr.HitPos + local trace_ent = tr.Entity + + if password == nil or (string.len(tostring(password)) > 4) or (string.find(tostring(password), "0")) then + ply:PrintMessage(3, "Пароль должен состоять не более чем из четырех цифр и не содержать 0!") + return false + end + + if trace_ent.KeypadData and trace_ent.KeypadData.Owner == ply:SteamID() then + self:SetupKeypad(trace_ent, password) + return true + end +end + +function TOOL:LeftClick(tr) + if not WireLib then return false end + if IsValid(tr.Entity) and tr.Entity:GetClass() == "player" then return false end + + if CLIENT then return true end + + local ply = self:GetOwner() + local password = self:GetClientNumber("password") + + local spawn_pos = tr.HitPos + tr.HitNormal + local trace_ent = tr.Entity + + if password == nil or (string.len(tostring(password)) > 4) or (string.find(tostring(password), "0")) then + ply:PrintMessage(3, "Пароль должен состоять не более чем из четырех цифр и не содержать 0!") + return false + end + + if not self:GetWeapon():CheckLimit("keypads") then return false end + + local ent = ents.Create("keypad_wire") + ent:SetPos(spawn_pos) + ent:SetAngles(tr.HitNormal:Angle()) + ent:Spawn() + + ent:SetPlayer(ply) + + local freeze = util.tobool(self:GetClientNumber("freeze")) + local weld = util.tobool(self:GetClientNumber("weld")) + + if freeze or weld then + local phys = ent:GetPhysicsObject() + + if IsValid(phys) then + phys:EnableMotion(false) + end + end + + if weld then + local weld = constraint.Weld(ent, trace_ent, 0, 0, 0, true, false) + end + + self:SetupKeypad(ent, password) + + undo.Create("Keypad") + undo.AddEntity(ent) + undo.SetPlayer(ply) + undo.Finish() + + ply:AddCount("keypads", ent) + ply:AddCleanup("keypads", ent) + + return true +end + + +if CLIENT then + local function ResetSettings(ply) + ply:ConCommand("keypad_willox_wire_repeats_granted 0") + ply:ConCommand("keypad_willox_wire_repeats_denied 0") + ply:ConCommand("keypad_willox_wire_length_granted 0.1") + ply:ConCommand("keypad_willox_wire_length_denied 0.1") + ply:ConCommand("keypad_willox_wire_delay_granted 0") + ply:ConCommand("keypad_willox_wire_delay_denied 0") + ply:ConCommand("keypad_willox_wire_init_delay_granted 0") + ply:ConCommand("keypad_willox_wire_init_delay_denied 0") + ply:ConCommand("keypad_willox_wire_output_on 1") + ply:ConCommand("keypad_willox_wire_output_off 0") + end + + concommand.Add("keypad_willox_wire_reset", ResetSettings) + + function TOOL.BuildCPanel(CPanel) + if not WireLib then + CPanel:Help("Для этого тула необходим Wiremod") + CPanel:Help("http://wiremod.com/") + CPanel:Help("Аддон в воркшопе: #160250458") + else + local r, l = CPanel:TextEntry("Код доступа", "keypad_willox_wire_password") + r:SetTall(22) + + CPanel:ControlHelp("Максимальная длина: 4\nРазрешены цифры 1-9") + + CPanel:CheckBox("Не показывать код на экране", "keypad_willox_wire_secure") + CPanel:CheckBox("Приварить", "keypad_willox_wire_weld") + CPanel:CheckBox("Закрепить", "keypad_willox_wire_freeze") + + CPanel:NumSlider("Output On:", "keypad_willox_wire_output_on", -10, 10, 0) + CPanel:NumSlider("Output Off:", "keypad_willox_wire_output_off", -10, 10, 0) + + local granted = vgui.Create("DForm") + granted:SetName("Поведение при правильном коде") + + granted:NumSlider("Длина [1]", "keypad_willox_wire_length_granted", 0.1, 10, 2) + granted:NumSlider("Перв. задерж. [2]", "keypad_willox_wire_init_delay_granted", 0, 10, 2) + granted:NumSlider("Доп. задерж. [3]", "keypad_willox_wire_delay_granted", 0, 10, 2) + granted:NumSlider("Доп. активаций [4]", "keypad_willox_wire_repeats_granted", 0, 5, 0) + CPanel:AddItem(granted) + + local denied = vgui.Create("DForm") + denied:SetName("Поведение при неправильном коде") + + denied:NumSlider("Длина [1]", "keypad_willox_wire_length_denied", 0.1, 10, 2) + denied:NumSlider("Перв. задерж. [2]", "keypad_willox_wire_init_delay_denied", 0, 10, 2) + denied:NumSlider("Доп. задерж. [3]", "keypad_willox_wire_delay_denied", 0, 10, 2) + denied:NumSlider("Доп. активаций [4]", "keypad_willox_wire_repeats_denied", 0, 5, 0) + CPanel:AddItem(denied) + + CPanel:Button("Настройки по умолчанию", "keypad_willox_wire_reset") + + CPanel:Help("") + + local faq = CPanel:Help("Information") + faq:SetFont("GModWorldtip") + + CPanel:Help("[1] Сколько времени подавать сигнал?") + CPanel:Help("[2] Через сколько времени после активации подать сигнал первый раз?") + CPanel:Help("[3] Через сколько времени после предыдущей подачи сигнала подать его еще раз?") + CPanel:Help("[4] Сколько раз дополнительно нужно подать сигнал? ([3] - задержка между подачами)") + + CPanel:Help("") + + CPanel:Help("Используй цифры на клавиатуре для ввода кода.") + + CPanel:Help("") + + CPanel:Help("Создано Willox (http://steamcommunity.com/id/wiox)") + CPanel:Help("Модифицировано Octothorp Team (https://octothorp.team)") + end + end +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/lookable.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/lookable.lua new file mode 100644 index 0000000..d34f4ac --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/lookable.lua @@ -0,0 +1,256 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Осмотреть' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, + { name = 'reload' }, +} + +local vars = { + pages = {}, + sound = 'ambient/machines/keyboard3_clicks.wav', + soundVol = 1, + soundDist = 200, +} + +if CLIENT then + for k, v in pairs(vars) do + octolib.vars.init('tools.lookable.' .. k, v) + end +end + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +local varList = octolib.table.map(table.GetKeys(vars), function(v) return 'tools.lookable.' .. v end) + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER then + if ent:IsPlayer() and not self:GetOwner():IsSuperAdmin() then + return false + end + + local ply = self:GetOwner() + ply:GetClientVar(varList, function(vars) + local path = vars['tools.lookable.sound'] or '' + local dist = tonumber(vars['tools.lookable.soundDist']) or 200 + local isURL = path:sub(1, 4) == 'http' + + local data = { + pages = vars['tools.lookable.pages'] or {}, + sound = string.Trim(path) ~= '' and { + volume = tonumber(vars['tools.lookable.soundVol']) or 1, + + url = isURL and path or nil, + dist = isURL and dist or nil, + distInner = isURL and (dist * 0.1) or nil, + + file = not isURL and path or nil, + level = not isURL and (dist / 4) or nil, + }, + } + + doEffect(ent) + ent.lookableData = data + duplicator.StoreEntityModifier(ent, 'lookable', data) + end) + end + + return true + +end + +function TOOL:RightClick(tr) + + if SERVER then return false end + + local ent = tr.Entity + local data = IsValid(ent) and ent.lookableData + if not data then return false end + + for k,v in pairs(data) do + octolib.vars.set('tools.lookable.' .. k, v) + end + + return true + +end + +function TOOL:Reload(tr) + + if not IsFirstTimePredicted() then return false end + + local ent = tr.Entity + local data = IsValid(ent) and ent.lookableData + if not data then return false end + + if SERVER then + ent.lookableData = nil + duplicator.ClearEntityModifier(ent, 'lookable') + end + + doEffect(ent) + + return true + +end + +function TOOL:BuildCPanel() + + octolib.vars.textEntry(self, 'tools.lookable.sound', 'Звук из игры или по URL') + self:Button(L.browser_sound, 'wire_sound_browser_open') + octolib.vars.slider(self, 'tools.lookable.soundVol', 'Громкость звука', 0, 1, 2) + octolib.vars.slider(self, 'tools.lookable.soundDist', 'Дальность звука', 50, 5000, 0) + + octolib.button(self, 'Редактировать страницы', function() + octolib.dataEditor.open('tool.lookable.pages') + end) + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + local function openEditor(save, row) + local f = vgui.Create 'DFrame' + f:SetSize(400, 600) + f:MakePopup() + f:Center() + + if row then + octolib.button(f, 'Удалить', function() + save() + f:Remove() + end):DockMargin(0, 0, 0, 5) + end + + local img = f:Add 'DImage' + img:Dock(TOP) + img:SetTall(350) + img:SetKeepAspect(true) + + local eURL = octolib.textEntry(f, 'Ссылка на изображение') + eURL:SetUpdateOnType(true) + + local eW = octolib.textEntry(f, 'Ширина в пикселях') + eW:SetNumeric(true) + if row and row.w then eW:SetValue(row.w) end + + local eH = octolib.textEntry(f, 'Высота в пикселях') + eH:SetNumeric(true) + if row and row.h then eH:SetValue(row.h) end + + local initial = row and string.Trim(row.url) ~= '' + local function update(self, url) + if not IsValid(self) then return end + octolib.getURLMaterial(url, function(mat) + if not IsValid(self) then return end + img:SetMaterial(mat) + if not initial then + eW:SetValue(tostring(mat:Width())) + eH:SetValue(tostring(mat:Height())) + end + initial = false + end) + end + eURL.OnValueChange = update -- let it load immediately if possible + if row and row.url then eURL:SetValue(row.url) end + eURL.OnValueChange = octolib.func.debounce(update, 0.5) + + octolib.button(f, 'Сохранить', function() + save({ + url = eURL:GetValue(), + w = tonumber(eW:GetValue()) or 0, + h = tonumber(eH:GetValue()) or 0, + }) + f:Remove() + end) + end + + octolib.dataEditor.register('tool.lookable.pages', { + name = 'Осмотреть - Страницы', + columns = { + { field = 'url', name = 'Изображение' }, + }, + load = function(load) + load(octolib.vars.get('tools.lookable.pages') or {}) + end, + save = function(rows) + octolib.vars.set('tools.lookable.pages', rows) + end, + new = function(save) + openEditor(save) + end, + edit = function(row, save) + openEditor(save, row) + end, + }) + + netstream.Hook('tools.lookable', function(data) + local cont, overlay + local sw, sh = ScrW(), ScrH() + + local function openPage(i) + local page = data.pages[i] + if not page then return end + + if IsValid(overlay) then overlay:Remove() end + cont, overlay = octolib.overlay(nil, 'DPanel') + cont:SetPaintBackground(false) + overlay:MakePopup() + + local scale = 1 + if page.h > sh then scale = sh / page.h end + if page.w * scale > sw - 150 then scale = (sw - 150) / page.w end + + local w, h = page.w * scale, page.h * scale + cont:SetSize(w + 150, h) + img = cont:Add 'DImage' + img:SetURL(page.url) + img:SetSize(w, h) + img:Center() + + if i ~= #data.pages then + local bNext = cont:Add 'DImageButton' + bNext:SetSize(64, 64) + bNext:SetImage('octoteam/icons/arrow_right.png') + bNext:SetPos(w + 150 - 5 - 64, (h - 64) / 2) + function bNext:DoClick() + openPage(i + 1) + end + end + + if i > 1 then + local bPrev = cont:Add 'DImageButton' + bPrev:SetSize(64, 64) + bPrev:SetImage('octoteam/icons/arrow_left.png') + bPrev:SetPos(5, (h - 64) / 2) + function bPrev:DoClick() + openPage(i - 1) + end + end + end + openPage(1) + end) + + language.Add('Tool.lookable.name', 'Осмотреть') + language.Add('Tool.lookable.desc', 'Добавь возможность осмотреть предмет') + language.Add('Tool.lookable.left', L.assign) + language.Add('Tool.lookable.right', L.tool_copy) + language.Add('Tool.lookable.reload', L.remove) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/mapret.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/mapret.lua new file mode 100644 index 0000000..c90b678 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/mapret.lua @@ -0,0 +1,3251 @@ +--[[ + \ MAP RETEXTURIZER + =3 ]] local mr_revision = "MAP. RET. rev.12 - 16/04/2018 (dd/mm/yyyy)" --[[ + =o | License: MIT + / Created by: Xalalau Xubilozo + | + \ Garry's Mod Brasil + =< | http://www.gmbrblog.blogspot.com.br/ + =b | https://github.com/xalalau/GMod/tree/master/Map%20Retexturizer + / Enjoy! - Aproveitem! + +----- Special thanks to the testers: + + [*] Beckman + [*] BombermanMaldito + [*] duck + [*] XxtiozionhoxX + [*] le0board + [*] Matsilagi + [*] NickMBR + + Valeu, pessoal!! +]] + +-------------------------------- +--- TOOL STUFF +-------------------------------- + +TOOL.Category = "Render" +TOOL.Name = "#tool.mapret.name" +TOOL.Information = { + {name = "left"}, + {name = "right"}, + {name = "reload"} +} + +if (CLIENT) then + language.Add("tool.mapret.name", "Map Retexturizer") + language.Add("tool.mapret.left", "Set material") + language.Add("tool.mapret.right", "Copy material") + language.Add("tool.mapret.reload", "Remove material") + language.Add("tool.mapret.desc", "Change many materials on models and maps and also use them as decals.") +end + +-------------------------------- +--- CLIENT CVARS +-------------------------------- + +CreateConVar("mapret_admin", "1", { FCVAR_CLIENTCMD_CAN_EXECUTE, FCVAR_NOTIFY, FCVAR_REPLICATED }) + +CreateConVar("mapret_autosave", "1", { FCVAR_CLIENTCMD_CAN_EXECUTE, FCVAR_REPLICATED }) +CreateConVar("mapret_autoload", "", { FCVAR_CLIENTCMD_CAN_EXECUTE, FCVAR_REPLICATED }) +TOOL.ClientConVar["savename"] = "" + +CreateConVar("mapret_skybox", "", { FCVAR_CLIENTCMD_CAN_EXECUTE, FCVAR_REPLICATED }) + +TOOL.ClientConVar["material"] = "" +TOOL.ClientConVar["detail"] = "None" +TOOL.ClientConVar["alpha"] = "1" +TOOL.ClientConVar["offsetx"] = "0" +TOOL.ClientConVar["offsety"] = "0" +TOOL.ClientConVar["scalex"] = "1" +TOOL.ClientConVar["scaley"] = "1" +TOOL.ClientConVar["rotation"] = "0" + +-- Different declarations for these vars because I can control them better this way (SetChecked() works fine) +CreateClientConVar("mapret_preview", 0, false, false) +CreateClientConVar("mapret_decal", 0, false, false) + +-------------------------------- +--- GLOBAL VARS / INITIALIZATION +-------------------------------- + +-- Materials management +local mr_mat = { + -- Tell if material changes were already made since the beggining of the game + -- (Server) + initialized = false, + -- (Shared) + map = { + -- The name of our backup map material files. They are file1, file2, file3... + filename = "mapretexturizer/file", + -- 1024 file limit seemed to be more than enough. I only use this physical method because of GMod limitations + limit = 1024, + -- Data structures, all the modifications + list = {} + }, + -- (Client) + model = { + -- materialID = String, all the modifications + list = {} + }, + -- (Server) + decal = { + -- ID = String, all the modifications + list = {} + }, + -- (Client) + detail = { + -- Menu element + element, + -- Initialized later (Note: only "None" remains as bool) + list = { + ["Concrete"] = false, + ["Metal"] = false, + ["None"] = true, + ["Plaster"] = false, + ["Rock"] = false + } + }, + -- (Client) + skybox = { + -- Text elemment + element_text, + -- Combobox elemment + element_combo, + -- HL2 sky list + hl2_list = { + [""] = "", + ["skybox/sky_borealis01"] = "", + ["skybox/sky_day01_01"] = "", + ["skybox/sky_day01_04"] = "", + ["skybox/sky_day01_05"] = "", + ["skybox/sky_day01_06"] = "", + ["skybox/sky_day01_07"] = "", + ["skybox/sky_day01_08"] = "", + ["skybox/sky_day01_09"] = "", + ["skybox/sky_day02_01"] = "", + ["skybox/sky_day02_02"] = "", + ["skybox/sky_day02_03"] = "", + ["skybox/sky_day02_04"] = "", + ["skybox/sky_day02_05"] = "", + ["skybox/sky_day02_06"] = "", + ["skybox/sky_day02_07"] = "", + ["skybox/sky_day02_09"] = "", + ["skybox/sky_day02_10"] = "", + ["skybox/sky_day03_01"] = "", + ["skybox/sky_day03_02"] = "", + ["skybox/sky_day03_03"] = "", + ["skybox/sky_day03_04"] = "", + ["skybox/sky_day03_05"] = "", + ["skybox/sky_day03_06"] = "", + ["skybox/sky_wasteland02"] = "", + } + }, + -- (Client) + preview = { + -- I have to use this extra entry to store the real newMaterial that the preview material is using + newMaterial = "", + -- For some reason the materials don't set their angles perfectly, so I have troubles comparing the values. This is a workaround + rotation_workaround = -1, + -- HACK to avoid running the TOOL:Holster() code when the player selects the tool for the first time + holster_workaround = false + } +} + +if CLIENT then + -- Detail init + local function CreateMaterialAux(path) + return CreateMaterial(path, "VertexLitGeneric", {["$basetexture"] = path}) + end + + mr_mat.detail.list["Concrete"] = CreateMaterialAux("detail/noise_detail_01") + mr_mat.detail.list["Metal"] = CreateMaterialAux("detail/metal_detail_01") + mr_mat.detail.list["Plaster"] = CreateMaterialAux("detail/plaster_detail_01") + mr_mat.detail.list["Rock"] = CreateMaterialAux("detail/rock_detail_01") + + -- Preview material + CreateMaterial("MatRetPreviewMaterial", "UnlitGeneric", {["$basetexture"] = ""}) +end + +-- Duplicator management +local mr_dup = { + -- If a save is being loaded, the name keep stored here until it's done + -- (Shared) + running = "", + -- Workaround to duplicate map and decal materials + -- (Server) + entity, + -- It simulates a player so we can apply changes in empty servers + -- (Shared) + fake_ply = {}, + -- Disable our generic dup entity physics and rendering after the duplicate + -- (Server) + hidden = false, + -- First dup cleanup + -- (Server) + clean = false, + -- Force to stop the current loading to begin a new one + -- (Shared) + force_stop = false, + -- Special aditive delay for models + -- (Server) + models = { + delay = 0, + max_delay = 0 + }, + -- Register what type of materials the duplicator has + -- (Server) + has = { + models = false, + } +} +-- Now I have to index the rest of duplicator inside players, so the process can be unique for each one of them +local function mr_dup_set(ply) + local add = { + -- Tell if duplicator is running and what it's processing + -- (Shared) + run = "", + -- Register what type of materials the duplicator has + -- (Server) + has = { + map = false, + decals = false + }, + -- Number of elements + -- (Shared) + count = { + total = 0, + current = 0, + errors = { + n = 0, + list = {} + }, + } + } + -- Index into the player + -- (Shared) + ply.mr_dup = add +end +-- Initialize the fake player +mr_dup_set(mr_dup.fake_ply) + +-- Saves and loads! +local mr_manage = { + -- Our folder inside data + -- (Shared) + main_folder = "mapret/", + -- map_folder inside the main_folder + -- (Shared) + map_folder = game.GetMap().."/", + save = { + -- A table to join all the information about the modified materials to be saved + -- (Server) + list = {}, + -- Default save name + -- (Client) + defaul_name = game.GetMap().."_save", + -- Menu element + -- (Client) + element + }, + load = { + -- List of save names + -- (Shared) + list = {}, + -- Menu element + -- (Client) + element + }, + autosave = { + -- Name to be listed in the save list + -- (Server) + name = "[autosave]", + -- The autosave file for this map + -- (Server) + file = "[autosave].txt" + }, + autoload = { + -- autoload.folder inside the map_folder + -- (Server) + folder = "autoload/", + -- The autoload file inside autoload.folder (unique for each map, will receive a save name) + -- (Server) + file = "autoload.txt", + -- Menu element + -- (Client) + element + } +} +-- Initialize paths +-- (Shared) +mr_manage.map_folder = mr_manage.main_folder..mr_manage.map_folder +mr_manage.autoload.folder = mr_manage.map_folder..mr_manage.autoload.folder +mr_manage.autosave.file = mr_manage.map_folder..mr_manage.autosave.file +mr_manage.autoload.file = mr_manage.autoload.folder..mr_manage.autoload.file +if SERVER then + -- Create the main save folder + if !file.Exists(mr_manage.main_folder, "DATA") then + file.CreateDir(mr_manage.main_folder) + end + + -- Create the current map save folder + if !file.Exists(mr_manage.map_folder, "DATA") then + file.CreateDir(mr_manage.map_folder) + end + + -- Create the autoload folder + if !file.Exists(mr_manage.autoload.folder, "DATA") then + file.CreateDir(mr_manage.autoload.folder) + end + + -- Set the autoload command + local value = file.Read(mr_manage.autoload.file, "DATA") + + if value then + RunConsoleCommand("mapret_autoload", value) + else + RunConsoleCommand("mapret_autoload", "") + end + + -- Fill the load list on the server + local files = file.Find(mr_manage.map_folder.."*", "DATA") + + for k,v in pairs(files) do + mr_manage.load.list[v:sub(1, -5)] = mr_manage.map_folder..v + end +end + +-- Add a multiplayer delay in TOOL functions to run Material_ShouldChange() with accuracy +-- (Server) +local multiplayer_action_delay = 0 +if not game.SinglePlayer() then + multiplayer_action_delay = 0.01 +end + +-- I used to use the clientside player entity to store some states but it takes an almost random time to initialize and +-- was causing many syncing issues, so now these variables got moved into mr_ply +-- (Client) +local mr_ply +if CLIENT then + local add = { + mr_previewmode = false, + mr_decalmode = false, + mr_firstspawn = true, + -- mr_dup = table (from the command bellow) + } + + mr_ply = add + + mr_dup_set(mr_ply) +end + +-------------------------------- +--- FUNCTION DECLARATIONS +-------------------------------- + +local Ply_IsAdmin + +local MML_Check +local MML_GetFreeIndex +local MML_InsertElement +local MML_GetElement +local MML_DisableElement +local MML_Clean +local MML_Count + +local Data_Create +local Data_CreateFromMaterial +local Data_Get + +local CVars_SetToData +local CVars_SetToDefaults + +local Material_IsValid +local Material_GetOriginal +local Material_GetCurrent +local Material_GetNew +local Material_ShouldChange +local Material_Restore +local Material_RestoreAll + +local Model_Material_RevertIDName +local Model_Material_GetID +local Model_Material_Create +local Model_Material_Set +local Model_Material_RemoveAll + +local Map_Material_Set +local Map_Material_SetAux +local Map_Material_RemoveAll +local Map_Material_SetAll + +local Decal_Toogle +local Decal_Start +local Decal_Apply +local Decal_RemoveAll + +local Skybox_Apply +local Skybox_Remove +local Skybox_Render +local Skybox_Start + +local Duplicator_CreateEnt +local Duplicator_SendStatusToCl +local Duplicator_SendErrorCountToCl +local Duplicator_LoadModelMaterials +local Duplicator_LoadDecals +local Duplicator_LoadMapMaterials +local Duplicator_Finish + +local Preview_IsOn +local Preview_Toogle + +local Save_Start +local Save_Apply +local Save_SetAuto_Start +local Save_SetAuto_Apply + +local Load_Start +local Load_Apply +local Load_FillList +local Load_Delete_Start +local Load_Delete_Apply +local Load_SetAuto_Start +local Load_SetAuto_Apply +local Load_FirstSpawn + +------------------------------------- +--- GENERAL +------------------------------------- + +-- The tool is admin only, but can be free if the admin runs the cvar mapret_admin 0 +function Ply_IsAdmin(ply) + if ply ~= mr_dup.fake_ply and not ply:IsAdmin() and not ply:IsSuperAdmin() and GetConVar("mapret_admin"):GetString() == "1" then + if CLIENT then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Sorry, this tool is set as admin only!") + end + + return false + end + + return true +end + +------------------------------------- +--- mr_mat.map.list (MML) management +------------------------------------- + +-- Check if the table is full +function MML_Check() + -- Check upper limit + if MML_Count() == mr_mat.map.limit then + -- Limit reached! Try to open new spaces in the mr_mat.map.list table checking if the player removed something and cleaning the entry for real + MML_Clean() + + -- Check again + if MML_Count() == mr_mat.map.limit then + if SERVER then + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] ALERT!!! Tool's material limit reached ("..mr_mat.map.limit..")! Notify the developer for more space.") + end + + return false + end + end + + return true +end + +-- Get a free index +function MML_GetFreeIndex() + local i = 1 + + for k,v in pairs(mr_mat.map.list) do + if v.oldMaterial == nil then + break + end + i = i + 1 + end + + return i +end + +-- Insert an element +function MML_InsertElement(data, position) + mr_mat.map.list[position or MML_GetFreeIndex()] = data +end + +-- Get an element and its index +function MML_GetElement(oldMaterial) + for k,v in pairs(mr_mat.map.list) do + if v.oldMaterial == oldMaterial then + return v, k + end + end + + return nil +end + +-- Disable an element +function MML_DisableElement(element) + for m,n in pairs(element) do + element[m] = nil + end +end + +-- Remove all disabled elements +function MML_Clean() + local i = mr_mat.map.limit + + while i > 0 do + if mr_mat.map.list[i].oldMaterial == nil then + table.remove(mr_mat.map.list, i) + end + i = i - 1 + end +end + +-- Table count +function MML_Count(inTable) + local i = 0 + + for k,v in pairs(inTable or mr_mat.map.list) do + if v.oldMaterial ~= nil then + i = i + 1 + end + end + + return i +end + +-------------------------------- +--- DATA TABLES +-------------------------------- + +-- Set a data table +function Data_Create(ply, tr) + local data = { + ent = tr and tr.Entity or game.GetWorld(), + oldMaterial = tr and Material_GetOriginal(tr) or "", + newMaterial = ply:GetInfo("mapret_material"), + offsetx = ply:GetInfo("mapret_offsetx"), + offsety = ply:GetInfo("mapret_offsety"), + scalex = ply:GetInfo("mapret_scalex") ~= "0" and ply:GetInfo("mapret_scalex") or "0.01", + scaley = ply:GetInfo("mapret_scaley") ~= "0" and ply:GetInfo("mapret_scaley") or "0.01", + rotation = ply:GetInfo("mapret_rotation"), + alpha = ply:GetInfo("mapret_alpha"), + detail = ply:GetInfo("mapret_detail"), + } + + return data +end + +-- Convert a map material into a data table +function Data_CreateFromMaterial(materialName, i) + local theMaterial = Material(materialName) + + local scalex = theMaterial:GetMatrix("$basetexturetransform") and theMaterial:GetMatrix("$basetexturetransform"):GetScale() and theMaterial:GetMatrix("$basetexturetransform"):GetScale()[1] or "1.00" + local scaley = theMaterial:GetMatrix("$basetexturetransform") and theMaterial:GetMatrix("$basetexturetransform"):GetScale() and theMaterial:GetMatrix("$basetexturetransform"):GetScale()[2] or "1.00" + local offsetx = theMaterial:GetMatrix("$basetexturetransform") and theMaterial:GetMatrix("$basetexturetransform"):GetTranslation() and theMaterial:GetMatrix("$basetexturetransform"):GetTranslation()[1] or "0.00" + local offsety = theMaterial:GetMatrix("$basetexturetransform") and theMaterial:GetMatrix("$basetexturetransform"):GetTranslation() and theMaterial:GetMatrix("$basetexturetransform"):GetTranslation()[2] or "0.00" + local newMaterial + + local data = { + ent = game.GetWorld(), + oldMaterial = materialName, + newMaterial = i and mr_mat.map.filename..tostring(i) or "", + offsetx = string.format("%.2f", math.floor((offsetx)*100)/100), + offsety = string.format("%.2f", math.floor((offsety)*100)/100), + scalex = string.format("%.2f", math.ceil((1/scalex)*1000)/1000), + scaley = string.format("%.2f", math.ceil((1/scaley)*1000)/1000), + -- NOTE: for some reason the rotation never returns exactly the same as the one chosen by the user + rotation = theMaterial:GetMatrix("$basetexturetransform") and theMaterial:GetMatrix("$basetexturetransform"):GetAngles() and theMaterial:GetMatrix("$basetexturetransform"):GetAngles().y or "0", + alpha = string.format("%.2f", theMaterial:GetString("$alpha") or "1.00"), + detail = theMaterial:GetString("$detail") and theMaterial:GetTexture("$detail"):GetName() or "None", + } + + -- Get a valid detail key + for k,v in pairs(mr_mat.detail.list) do + if not isbool(v) then + if v:GetTexture("$basetexture"):GetName() == data.detail then + data.detail = k + end + end + end + + if not mr_mat.detail.list[data.detail] then + data.detail = "None" + end + + return data +end + +-- Set a data table with the default properties +function DataTable_CreateDefaults(ply, tr) + local data = { + ent = game.GetWorld(), + oldMaterial = Material_GetCurrent(tr), + newMaterial = ply:GetInfo("mapret_material"), + offsetx = "0.00", + offsety = "0.00", + scalex = "1.00", + scaley = "1.00", + rotation = "0", + alpha = "1.00", + detail = "None", + } + + return data +end + +-- Get the data table if it exists or return nil +function Data_Get(tr) + return IsValid(tr.Entity) and tr.Entity.modifiedmaterial or MML_GetElement(Material_GetOriginal(tr)) +end + +-------------------------------- +--- CVARS +-------------------------------- + +-- Get a stored data and refresh the cvars +function CVars_SetToData(ply, data) + if CLIENT then return; end + + --ply:ConCommand("mapret_detail "..data.detail) -- Server is not getting the right detail, only Client + ply:ConCommand("mapret_offsetx "..data.offsetx) + ply:ConCommand("mapret_offsety "..data.offsety) + ply:ConCommand("mapret_scalex "..data.scalex) + ply:ConCommand("mapret_scaley "..data.scaley) + ply:ConCommand("mapret_rotation "..data.rotation) + ply:ConCommand("mapret_alpha "..data.alpha) +end + +-- Set the cvars to data defaults +function CVars_SetToDefaults(ply) + --ply:ConCommand("mapret_detail ") -- Server is not getting the right detail, only Client + ply:ConCommand("mapret_offsetx 0") + ply:ConCommand("mapret_offsety 0") + ply:ConCommand("mapret_scalex 1") + ply:ConCommand("mapret_scaley 1") + ply:ConCommand("mapret_rotation 0") + ply:ConCommand("mapret_alpha 1") + + mr_mat.detail.element:SetValue("None") +end + +-------------------------------- +--- MATERIALS (GENERAL) +-------------------------------- + +-- Check if a given material path is valid +function Material_IsValid(material) + -- Do not try to load nonexistent materials + if not material or material == "" then + return false + end + + local fileExists = false + + --for _,v in pairs({ ".vmf", ".png", ".jpg" }) do + for _,v in pairs({ ".vmf" }) do + if file.Exists("materials/"..material..v, "GAME") then + fileExists = true + end + end + + if not fileExists then + -- For some reason there are map materials loaded and working but not present in the folders. + -- I guess they are embbeded. So if the material is not considered an error, go ahead... + if Material(material):IsError() then + return false + end + end + + -- Checks + if material == "" or + string.find(material, "../", 1, true) or + string.find(material, "pp/", 1, true) or + not Material(material):GetTexture("$basetexture") or + Material(material):IsError() and not fileExists then + + return false + end + + -- Ok + return true +end + +-- Get the original material full path +function Material_GetOriginal(tr) + -- Model + if IsValid(tr.Entity) then + return tr.Entity:GetMaterials()[1] + -- Map + elseif tr.Entity:IsWorld() then + return string.Trim(tr.HitTexture):lower() + end +end + +-- Get the current material full path +function Material_GetCurrent(tr) + local path + + -- Model + if IsValid(tr.Entity) then + path = tr.Entity.modifiedmaterial + -- Get a material generated for the model + if path then + path = Model_Material_RevertIDName(tr.Entity.modifiedmaterial.newMaterial) + -- Or the real thing + else + path = tr.Entity:GetMaterials()[1] + end + -- Map + elseif tr.Entity:IsWorld() then + local element = MML_GetElement(Material_GetOriginal(tr)) + + if element then + path = element.newMaterial + else + path = Material_GetOriginal(tr) + end + end + + return path +end + +-- Get the new material from the cvar +function Material_GetNew(ply) + return ply:GetInfo("mapret_material") +end + +-- Check if the material should be replaced +function Material_ShouldChange(ply, currentDataIn, newDataIn, tr) + local currentData = table.Copy(currentDataIn) + local newData = table.Copy(newDataIn) + local backup + + -- If the material is still untouched, let's get the data from the map and compare it + if not currentData then + local material = Material_GetCurrent(tr) + + -- If the material is invalid, we can't modify it + if not material then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] The material you are trying to modify has an invalid name.") + + return false + end + + currentData = Data_CreateFromMaterial(material, 0) + currentData.newMaterial = currentData.oldMaterial -- Force the newMaterial to be the oldMaterial + -- Else we need to hide its internal backup + else + backup = currentData.backup + currentData.backup = nil + end + + -- Correct a model newMaterial entry for the comparision + if IsValid(tr.Entity) then + newData.newMaterial = Model_Material_GetID(newData) + end + + -- Check if some property is different + local isDifferent = false + for k,v in pairs(currentData) do + if v ~= newData[k] then + isDifferent = true + break + end + end + + -- Restore the internal backup + currentData.backup = backup + + -- The material needs to be changed if data ~= data2 + if isDifferent then + return true + end + + -- No need for changes + return false +end + +-- Clean previous modifications::: +if SERVER then + util.AddNetworkString("Material_Restore") +end +function Material_Restore(ent, oldMaterial) + local isValid = false + + -- Model + if IsValid(ent) then + if ent.modifiedmaterial then + if CLIENT then + ent:SetMaterial("") + ent:SetRenderMode(RENDERMODE_NORMAL) + ent:SetColor(Color(255,255,255,255)) + end + + ent.modifiedmaterial = nil + + if SERVER then + duplicator.ClearEntityModifier(ent, "MapRetexturizer_Models") + end + + isValid = true + end + -- Map + else + if MML_Count() > 0 then + local element = MML_GetElement(oldMaterial) + + if element then + if CLIENT then + Map_Material_SetAux(element.backup) + end + + MML_DisableElement(element) + + if SERVER then + if MML_Count() == 0 then + if IsValid(mr_dup.entity) then + duplicator.ClearEntityModifier(mr_dup.entity, "MapRetexturizer_Maps") + end + end + end + + isValid = true + end + end + end + -- Run on client + if isValid then + if SERVER then + net.Start("Material_Restore") + net.WriteEntity(ent) + net.WriteString(oldMaterial) + net.Broadcast() + end + + return true + end + + return false +end +if CLIENT then + net.Receive("Material_Restore", function() + Material_Restore(net.ReadEntity(), net.ReadString()) + end) +end + +-- Clean up everything +function Material_RestoreAll(ply, plyLoadingStatus) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + -- Don't start a cleaning process if we are stopping a loading + if not mr_dup.force_stop then + -- Force to stop any loading + local delay = 0 + + if Duplicator_ForceStop(plyLoadingStatus) then + delay = 0.4 + end + + -- cleanup + timer.Create("MapRetMaterialCleanupDelay"..tostring(math.random(999))..tostring(ply), delay, 1, function() + Model_Material_RemoveAll(ply) + Map_Material_RemoveAll(ply) + Decal_RemoveAll(ply) + Skybox_Remove(ply) + + -- Reset the force stop var (It was set true in Duplicator_ForceStop()) + mr_dup.force_stop = false + end) + end +end +if SERVER then + util.AddNetworkString("Material_RestoreAll") + + net.Receive("Material_RestoreAll", function(_,ply) + Material_RestoreAll(ply, net.ReadBool()) + end) + + concommand.Add("mapret_remote_cleanup", function() + Material_RestoreAll(mr_dup.fake_ply, true) + + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Console: cleaning modifications...") + print("[Map Retexturizer] Console: cleaning modifications...") + end) +end + +-------------------------------- +--- MATERIALS (MODELS) +-------------------------------- + +-- Get the old "newMaterial" from a unique model material name generated by this tool +function Model_Material_RevertIDName(materialID) + local parts = string.Explode("-=+", materialID) + local result + + if parts then + result = parts[2] + end + + return result +end + +-- Generate the material unique id +function Model_Material_GetID(data) + if SERVER then return; end + + local materialID = "" + + -- SortedPairs so the order will be always the same + for k,v in SortedPairs(data) do + -- Remove the ent to avoid creating the same material later + if v ~= data.ent then + -- Separate the ID Generator (newMaterial) inside a "-=+" box + if isstring(v) then + if v == data.newMaterial then + v = "-=+"..v.."-=+" + end + -- Round if it's a number + elseif isnumber(v) then + v = math.Round(v) + end + + -- Generating... + materialID = materialID..tostring(v) + end + end + + -- Remove problematic chars + materialID = materialID:gsub(" ", "") + materialID = materialID:gsub("%.", "") + + return materialID +end + +-- Create a new model material (if it doesn't exist yet) and return its unique new name +function Model_Material_Create(data) + local materialID = Model_Material_GetID(data) + + if CLIENT then + -- Create the material if it's necessary + if not mr_mat.model.list[materialID] then + -- Basic info + local material = { + ["$basetexture"] = data.newMaterial, + ["$vertexalpha"] = 0, + ["$vertexcolor"] = 1, + } + + -- Create matrix + local matrix = Matrix() + + matrix:SetAngles(Angle(0, data.rotation, 0)) -- Rotation + matrix:Scale(Vector(1/data.scalex, 1/data.scaley, 1)) -- Scale + matrix:Translate(Vector(data.offsetx, data.offsety, 0)) -- Offset + + -- Create material + local newMaterial + + mr_mat.model.list[materialID] = CreateMaterial(materialID, "VertexLitGeneric", material) + mr_mat.model.list[materialID]:SetTexture("$basetexture", Material(data.newMaterial):GetTexture("$basetexture")) + newMaterial = mr_mat.model.list[materialID] + + -- Apply detail + if data.detail ~= "None" then + if mr_mat.detail.list[data.detail] then + newMaterial:SetTexture("$detail", mr_mat.detail.list[data.detail]:GetTexture("$basetexture")) + newMaterial:SetString("$detailblendfactor", "1") + else + newMaterial:SetString("$detailblendfactor", "0") + end + else + newMaterial:SetString("$detailblendfactor", "0") + end + + -- Try to apply Bumpmap () + local bumpmapPath = data.newMaterial .. "_normal" -- checks for a file placed with the model (named like mymaterial_normal.vtf) + local bumpmap = Material(data.newMaterial):GetTexture("$bumpmap") -- checks for a copied material active bumpmap + + if file.Exists("materials/"..bumpmapPath..".vtf", "GAME") then + if not mr_mat.model.list[bumpmapPath] then + mr_mat.model.list[bumpmapPath] = CreateMaterial(bumpmapPath, "VertexLitGeneric", {["$basetexture"] = bumpmapPath}) + end + newMaterial:SetTexture("$bumpmap", mr_mat.model.list[bumpmapPath]:GetTexture("$basetexture")) + elseif bumpmap then + newMaterial:SetTexture("$bumpmap", bumpmap) + end + + -- Apply matrix + newMaterial:SetMatrix("$basetexturetransform", matrix) + newMaterial:SetMatrix("$detailtexturetransform", matrix) + newMaterial:SetMatrix("$bumptransform", matrix) + end + end + + return materialID +end + +-- Set model material::: +if SERVER then + util.AddNetworkString("Model_Material_Set") +end +function Model_Material_Set(data) + if not IsValid(data.ent) then return end + + if SERVER then + -- Send the modification to every player + net.Start("Model_Material_Set") + net.WriteTable(data) + net.Broadcast() + + -- Set the duplicator + duplicator.StoreEntityModifier(data.ent, "MapRetexturizer_Models", data) + end + + -- Create a material + local materialID = Model_Material_Create(data) + + -- Changes the new material for the created new one + data.newMaterial = materialID + + -- Indicate that the model got modified by this tool + data.ent.modifiedmaterial = data + + -- Set the alpha + data.ent:SetRenderMode(RENDERMODE_TRANSALPHA) + data.ent:SetColor(Color(255,255,255,255*data.alpha)) + + if CLIENT then + -- Apply the material + data.ent:SetMaterial("!"..materialID) + end +end +if CLIENT then + net.Receive("Model_Material_Set", function() + Model_Material_Set(net.ReadTable()) + end) +end + +-- Remove all modified model materials +function Model_Material_RemoveAll(ply) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + for k,v in pairs(ents.GetAll()) do + if IsValid(v) then + Material_Restore(v, "") + end + end +end +if SERVER then + util.AddNetworkString("Model_Material_RemoveAll") + + net.Receive("Model_Material_RemoveAll", function(_,ply) + Model_Material_RemoveAll(ply) + end) +end + +-------------------------------- +--- MATERIALS (MAPS) +-------------------------------- + +-- Set map material::: +if SERVER then + util.AddNetworkString("Map_Material_Set") +end +function Map_Material_Set(ply, data) + -- Note: if data has a backup we need to restore it, otherwise let's just do the normal stuff + + -- Force to skip bad materials (sometimes it happens, so let's just avoid the script errors) + if not data.oldMaterial then + print("[MAP RETEXTURIZER] Map_Material_Set() received a bad material. Skipping it...") + + return + end + + -- Set the backup: + -- Olny register the modifications if they are being made by a player not in the first spawn or + -- a player in the first spawn and initializing the materials on the serverside + if CLIENT or SERVER and not ply.mr_firstSpawn or SERVER and ply.mr_firstSpawn and ply.mr_mat_initializing then + -- Duplicator check + local isNewMaterial = false + + if SERVER then + if not data.backup then + isNewMaterial = true + end + end + + local i + local element = MML_GetElement(data.oldMaterial) + + -- If we are modifying an already modified material + if element then + -- Create an entry in the material Data poiting to the backup data + data.backup = element.backup + + -- Cleanup + MML_DisableElement(element) + Map_Material_SetAux(data.backup) + + -- Get a mr_mat.map.list free index + i = MML_GetFreeIndex() + -- If the material is untouched + else + -- Get a mr_mat.map.list free index + i = MML_GetFreeIndex() + + -- Get the current material info (It's only going to be data.backup if we are running the duplicator) + local dataBackup = data.backup or Data_CreateFromMaterial(data.oldMaterial, i) + + -- Save the material texture + Material(dataBackup.newMaterial):SetTexture("$basetexture", Material(dataBackup.oldMaterial):GetTexture("$basetexture")) + + -- Create an entry in the material Data poting to the new backup Data (data.backup will shows itself already done only if we are running the duplicator) + if not data.backup then + data.backup = dataBackup + end + end + + -- Index the Data + MML_InsertElement(data, i) + + -- Apply the new state to the map material + Map_Material_SetAux(data) + + if SERVER then + -- Set the duplicator + if isNewMaterial then + duplicator.StoreEntityModifier(mr_dup.entity, "MapRetexturizer_Maps", mr_mat.map.list) + end + end + end + + if SERVER then + -- Send the modification to every player + if not ply.mr_firstSpawn then + net.Start("Map_Material_Set") + net.WriteTable(data) + net.WriteBool(true) + net.Broadcast() + -- Or for a single player + else + net.Start("Map_Material_Set") + net.WriteTable(data) + net.WriteBool(false) + net.Send(ply) + end + end +end +if CLIENT then + net.Receive("Map_Material_Set", function() + local ply = LocalPlayer() + local theTable = net.ReadTable() + local isBroadcasted = net.ReadBool() + + -- Block the changes if it's a new player joining in the middle of a loading. He'll have his own load. + if mr_ply.mr_firstSpawn and isBroadcasted then + return + end + + Map_Material_Set(ply, theTable) + end) +end + +-- Copy "all" the data from a material to another (auxiliar to Map_Material_Set()) +function Map_Material_SetAux(data) + if SERVER then return; end + + -- Get the materials + local oldMaterial = Material(data.oldMaterial) + local newMaterial = Material(data.newMaterial) + + -- Apply the base texture + oldMaterial:SetTexture("$basetexture", newMaterial:GetTexture("$basetexture")) +--[[ + -- It's better to not support + if not newMaterial:IsError() then -- If the file is a .vmt + oldMaterial:SetTexture("$basetexture", newMaterial:GetTexture("$basetexture")) + else + oldMaterial:SetTexture("$basetexture", data.newMaterial) + end +]] + + -- Alpha stuff + oldMaterial:SetString("$translucent", "1") + oldMaterial:SetString("$alpha", data.alpha) + + -- Apply the matrix + local texture_matrix = oldMaterial:GetMatrix("$basetexturetransform") + + texture_matrix:SetAngles(Angle(0, data.rotation, 0)) + texture_matrix:SetScale(Vector(1/data.scalex, 1/data.scaley, 1)) + texture_matrix:SetTranslation(Vector(data.offsetx, data.offsety)) + oldMaterial:SetMatrix("$basetexturetransform", texture_matrix) + + -- Apply the detail + if data.detail ~= "None" then + oldMaterial:SetTexture("$detail", mr_mat.detail.list[data.detail]:GetTexture("$basetexture")) + oldMaterial:SetString("$detailblendfactor", "1") + else + oldMaterial:SetString("$detailblendfactor", "0") + oldMaterial:SetString("$detail", "") + oldMaterial:Recompute() + end + + --[[ + -- Old tests that I want to keep here + mapMaterial:SetTexture("$bumpmap", Material(data.newMaterial):GetTexture("$basetexture")) + mapMaterial:SetString("$nodiffusebumplighting", "1") + mapMaterial:SetString("$normalmapalphaenvmapmask", "1") + mapMaterial:SetVector("$color", Vector(100,100,0)) + mapMaterial:SetString("$surfaceprop", "Metal") + mapMaterial:SetTexture("$detail", Material(data.oldMaterial):GetTexture("$basetexture")) + mapMaterial:SetMatrix("$detailtexturetransform", texture_matrix) + mapMaterial:SetString("$detailblendfactor", "0.2") + mapMaterial:SetString("$detailblendmode", "3") + ]]-- +end + +function Map_Material_SetAll(ply) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + -- Create the duplicator entity used to restore map materials, decals and skybox + if SERVER then + Duplicator_CreateEnt() + end + + -- Check upper limit + if not MML_Check() then + return false + end + + -- Get the material + local material = ply:GetInfo("mapret_material") + + -- Don't apply bad materials + if not Material_IsValid(material) then + if SERVER then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Bad material.") + end + + return false + end + + -- Register that the map is manually modified + if not mr_mat.initialized then + mr_mat.initialized = true + end + + -- Clean the map + Material_RestoreAll(ply, true) + + timer.Create("MapRetChangeAllDelay"..tostring(math.random(999))..tostring(ply), 0.5, 1, function() + -- Create a fake loading table + local newTable = {} + local map = {} + + for k, v in pairs (game.GetWorld():GetMaterials()) do + local data = Data_Create(ply) + + -- Ignore water + if not string.find(v, "water") then + data.oldMaterial = v + data.newMaterial = material + + table.insert(map, data) + end + end + + newTable.map = map + + -- Apply the fake load + Load_Apply(ply, newTable) + end) +end +if CLIENT then + -- Set all materials (with confirmation box) + concommand.Add("mapret_changeall", function() + + local qPanel = vgui.Create( "DFrame" ) + qPanel:SetTitle( "Loading Confirmation" ) + qPanel:SetSize( 284, 95 ) + qPanel:SetPos( 10, 10 ) + qPanel:SetDeleteOnClose( true ) + qPanel:SetVisible( true ) + qPanel:SetDraggable( true ) + qPanel:ShowCloseButton( true ) + qPanel:MakePopup( true ) + qPanel:Center() + + local text = vgui.Create( "DLabel", qPanel ) + text:SetPos( 10, 25 ) + text:SetSize( 300, 25) + text:SetText( "Are you sure you want to change all the map materials?" ) + + local buttonYes = vgui.Create( "DButton", qPanel ) + buttonYes:SetPos( 24, 50 ) + buttonYes:SetText( "Yes" ) + buttonYes:SetSize( 120, 30 ) + buttonYes.DoClick = function() + net.Start("Map_Material_SetAll") + net.SendToServer() + qPanel:Close() + end + + local buttonNo = vgui.Create( "DButton", qPanel ) + buttonNo:SetPos( 144, 50 ) + buttonNo:SetText( "No" ) + buttonNo:SetSize( 120, 30 ) + buttonNo.DoClick = function() + qPanel:Close() + end + end) +else + util.AddNetworkString("Map_Material_SetAll") + + net.Receive("Map_Material_SetAll", function(_,ply) + Map_Material_SetAll(ply) + end) +end + +-- Remove all modified map materials +function Map_Material_RemoveAll(ply) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + if MML_Count() > 0 then + for k,v in pairs(mr_mat.map.list) do + if v.oldMaterial ~=nil then + Material_Restore(nil, v.oldMaterial) + end + end + end +end +if SERVER then + util.AddNetworkString("Map_Material_RemoveAll") + + net.Receive("Map_Material_RemoveAll", function(_,ply) + Map_Material_RemoveAll(ply) + end) +end + +-------------------------------- +--- MATERIALS (DECALS) +-------------------------------- + +-- Toogle the decal mode for a player +function Decal_Toogle(ply, value) + if SERVER then return; end + + mr_ply.mr_decalmode = value + + net.Start("MapRetToogleDecal") + net.WriteBool(value) + net.SendToServer() +end +if SERVER then + util.AddNetworkString("MapRetToogleDecal") + + net.Receive("MapRetToogleDecal", function(_, ply) + ply.mr_decalmode = net.ReadBool() + end) +end + +-- Apply decal materials::: +function Decal_Start(ply, tr, duplicatorData) + local mat = tr and Material_GetNew(ply) or duplicatorData.mat + + -- Don't apply bad materials + if not Material_IsValid(mat) then + if SERVER then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Bad material.") + end + + return false + end + + -- Ok for client + if CLIENT then + return true + end + + -- Get the basic properties + local ent = tr and tr.Entity or duplicatorData.ent + local pos = tr and tr.HitPos or duplicatorData.pos + local hit = tr and tr.HitNormal or duplicatorData.hit + + -- Register and duplicator: + -- Olny register the modifications if they are being made by a player not in the first spawn or + -- a player in the first spawn and initializing the materials on the serverside + if not ply.mr_firstSpawn or ply.mr_firstSpawn and ply.mr_mat_initializing then + table.insert(mr_mat.decal.list, {ent = ent, pos = pos, hit = hit, mat = mat}) + + duplicator.StoreEntityModifier(mr_dup.entity, "MapRetexturizer_Decals", mr_mat.decal.list) + end + + -- Send to all players + if not ply.mr_firstSpawn then + net.Start("Decal_Apply") + net.WriteString(mat) + net.WriteEntity(ent) + net.WriteVector(pos) + net.WriteVector(hit) + net.WriteBool(true) + net.Broadcast() + -- Or for a single player + else + net.Start("Decal_Apply") + net.WriteString(mat) + net.WriteEntity(ent) + net.WriteVector(pos) + net.WriteVector(hit) + net.WriteBool(false) + net.Send(ply) + end + + return true +end + +-- Create decal materials +function Decal_Apply(materialPath, ent, pos, normal) + if SERVER then return; end + + -- Create the material + local decalMaterial = mr_mat.decal.list[materialPath.."2"] + + if not decalMaterial then + decalMaterial = CreateMaterial(materialPath.."2", "LightmappedGeneric", {["$basetexture"] = materialPath}) + decalMaterial:SetInt("$decal", 1) + decalMaterial:SetInt("$translucent", 1) + decalMaterial:SetFloat("$decalscale", 1.00) + decalMaterial:SetTexture("$basetexture", Material(materialPath):GetTexture("$basetexture")) + end + + -- Apply the decal + -- Notes: + -- Vertical normals don't work + -- Resizing doesn't work (width x height) + util.DecalEx(decalMaterial, ent, pos, normal, Color(255,255,255,255), decalMaterial:Width(), decalMaterial:Height()) +end +if SERVER then + util.AddNetworkString("Decal_Apply") +end +if CLIENT then + net.Receive("Decal_Apply", function() + local ply = LocalPlayer() + local material = net.ReadString() + local entity = net.ReadEntity() + local position = net.ReadVector() + local normal = net.ReadVector() + local isBroadcasted = net.ReadBool() + + -- Block the changes if it's a new player joining in the middle of a loading. He'll have his own load. + if mr_ply.mr_firstSpawn and isBroadcasted then + return + end + + -- Material, entity, position, normal, color, width and height + Decal_Apply(material, entity, position, normal) + end) +end + +-- Remove all decals +function Decal_RemoveAll(ply) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + for k,v in pairs(player.GetAll()) do + if v:IsValid() then + v:ConCommand("r_cleardecals") + end + end + table.Empty(mr_mat.decal.list) + duplicator.ClearEntityModifier(mr_dup.entity, "MapRetexturizer_Decals") +end +if SERVER then + util.AddNetworkString("Decal_RemoveAll") + + net.Receive("Decal_RemoveAll", function(_, ply) + Decal_RemoveAll(ply) + end) +end + +-------------------------------- +--- SKYBOX MATERIAL +-------------------------------- + +-- Change the skybox +function Skybox_Start(ply, value) + if SERVER then return; end + + net.Start("MapRetSkybox") + net.WriteString(value) + net.SendToServer() +end +function Skybox_Apply(ply, mat) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + -- Create the duplicator entity if it's necessary + Duplicator_CreateEnt() + + -- Set the duplicator + duplicator.StoreEntityModifier(mr_dup.entity, "MapRetexturizer_Skybox", { skybox = mat }) + + -- Apply the material to every client + RunConsoleCommand("mapret_skybox", mat) +end +if SERVER then + util.AddNetworkString("MapRetSkybox") + + net.Receive("MapRetSkybox", function(_, ply) + Skybox_Apply(ply, net.ReadString()) + end) +end + +-- Material rendering +if CLIENT then + local distance = 200 + local width = distance * 2 + local height = distance * 2 + + -- Skybox extra layer rendering + local function Skybox_Render() + local mat = GetConVar("mapret_skybox"):GetString() + + -- Check if it's empty + if mat ~= "" then + local suffixes + local aux = { "ft", "bk", "lf", "rt", "up", "dn" } + + -- If we aren't using a HL2 sky we need to check what is going on + if not mr_mat.skybox.hl2_list[mat] then + -- Check if the material is valid + if not Material_IsValid(mat) and not Material_IsValid(mat.."ft") then + -- Nope + return + else + -- Check if a valid 6 side skybox + for k, v in pairs(aux) do + if not Material_IsValid(mat..v) then + -- If it's not a full skybox, it's a valid single material + suffixes = { "", "", "", "", "", "" } + break + end + end + end + + -- It's a valid full skybox + if not suffixes then + suffixes = aux + end + else + suffixes = aux + end + + -- Render our sky layer + render.OverrideDepthEnable(true, false) + render.SetLightingMode(2) + cam.Start3D(Vector(0, 0, 0), EyeAngles()) + render.SetMaterial(Material(mat..suffixes[1])) + render.DrawQuadEasy(Vector(-distance,0,0), Vector(1,0,0), width, height, Color(255,255,255,255), 180) + render.SetMaterial(Material(mat..suffixes[2])) + render.DrawQuadEasy(Vector(distance,0,0), Vector(-1,0,0), width, height, Color(255,255,255,255), 180) + render.SetMaterial(Material(mat..suffixes[3])) + render.DrawQuadEasy(Vector(0,distance,0), Vector(0,-1,0), width, height, Color(255,255,255,255), 180) + render.SetMaterial(Material(mat..suffixes[4])) + render.DrawQuadEasy(Vector(0,-distance,0), Vector(0,1,0), width, height, Color(255,255,255,255), 180) + render.SetMaterial(Material(mat..suffixes[5])) + render.DrawQuadEasy(Vector(0,0,distance), Vector(0,0,-1), width, height, Color(255,255,255,255), 90) + render.SetMaterial(Material(mat..suffixes[6])) + render.DrawQuadEasy(Vector(0,0,-distance), Vector(0,0,1), width, height, Color(255,255,255,255), 180) + cam.End3D() + render.OverrideDepthEnable(false, false) + render.SetLightingMode(0) + end + end + + hook.Add("PostDraw2DSkyBox", "MapRetSkyboxLayer", function() + Skybox_Render() + end) +end + +-- Remove all decals +function Skybox_Remove(ply) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + RunConsoleCommand("mapret_skybox", "") + + duplicator.ClearEntityModifier(mr_dup.entity, "MapRetexturizer_Skybox") +end +if SERVER then + util.AddNetworkString("Skybox_Remove") + + net.Receive("Skybox_Remove", function(_, ply) + Skybox_Remove(ply) + end) +end + +-------------------------------- +--- DUPLICATOR +-------------------------------- + +-- Models and decals must be processed first than the map. + +-- Set the duplicator +function Duplicator_CreateEnt(ent) + if CLIENT then return; end + + -- Hide/Disable our entity after a duplicator + if not mr_dup.hidden and ent then + mr_dup.entity = ent + mr_dup.entity:SetNoDraw(true) + mr_dup.entity:SetSolid(0) + mr_dup.entity:PhysicsInitStatic(SOLID_NONE) + mr_dup.hidden = true + -- Create a new entity if we don't have one yet + elseif not IsValid(mr_dup.entity) and not ent then + mr_dup.entity = ents.Create("prop_physics") + mr_dup.entity:SetModel("models/props_phx/cannonball_solid.mdl") + mr_dup.entity:SetPos(Vector(0, 0, 0)) + mr_dup.entity:SetNoDraw(true) + mr_dup.entity:Spawn() + mr_dup.entity:SetSolid(0) + mr_dup.entity:PhysicsInitStatic(SOLID_NONE) + end +end + +-- Function to send the duplicator state to the client(s) +function Duplicator_SendStatusToCl(ply, current, total, section, resetValues) + if CLIENT then return; end + + -- Reset the counting + if resetValues then + Duplicator_SendStatusToCl(ply, 0, 0, "") + end + + -- Update every client + if not ply.mr_firstSpawn then + net.Start("MapRetUpdateDupProgress") + net.WriteInt(current or -1, 14) + net.WriteInt(total or -1, 14) + net.WriteString(section or "-1") + net.WriteBool(true) + net.Broadcast() + -- Or a single client + else + net.Start("MapRetUpdateDupProgress") + net.WriteInt(current or -1, 14) + net.WriteInt(total or -1, 14) + net.WriteString(section or "-1") + net.WriteBool(false) + net.Send(ply) + end +end +if SERVER then + util.AddNetworkString("MapRetUpdateDupProgress") +else + -- Updates the duplicator progress in the client + net.Receive("MapRetUpdateDupProgress", function() + local a, b, c = net.ReadInt(14), net.ReadInt(14), net.ReadString() + local ply = LocalPlayer() + local isBroadcasted = net.ReadBool() + + -- Block the changes if it's a new player joining in the middle of a loading. He'll have his own load. + if mr_ply.mr_firstSpawn and isBroadcasted then + return + end + + -- Update the dup state + if c != "-1" then + mr_ply.mr_dup.run = c + end + + if a ~= -1 then + mr_ply.mr_dup.count.current = a + end + + if b ~= -1 then + mr_ply.mr_dup.count.total = b + end + end) +end + +-- If any errors are found +function Duplicator_SendErrorCountToCl(ply, count, material) + if CLIENT then return; end + + -- Send the status all players + if not ply.mr_firstSpawn then + net.Start("MapRetUpdateDupErrorCount") + net.WriteInt(count or 0, 14) + net.WriteString(material or "") + net.WriteBool(true) + net.Broadcast() + -- Or for a single player + else + net.Start("MapRetUpdateDupErrorCount") + net.WriteInt(count or 0, 14) + net.WriteString(material or "") + net.WriteBool(false) + net.Send(ply) + end +end +if SERVER then + util.AddNetworkString("MapRetUpdateDupErrorCount") +else + -- Error printing in the console + net.Receive("MapRetUpdateDupErrorCount", function() + local count = net.ReadInt(14) + local mat = net.ReadString() + local isBroadcasted = net.ReadBool() + + -- Block the changes if it's a new player joining in the middle of a loading. He'll have his own load. + if mr_ply.mr_firstSpawn and isBroadcasted then + return + end + + -- Set the error count + mr_ply.mr_dup.count.errors.n = count + + -- Get the missing material name + if mr_ply.mr_dup.count.errors.n > 0 then + table.insert(mr_ply.mr_dup.count.errors.list, mat) + -- Print the failed materials table + else + if table.Count(mr_ply.mr_dup.count.errors.list)> 0 then + LocalPlayer():PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Check the terminal for the errors.") + print("") + print("-------------------------------------------------------------") + print("[MAP RETEXTURIZER] - Failed to load these files:") + print("-------------------------------------------------------------") + print(table.ToString(mr_ply.mr_dup.count.errors.list, "Missing Materials ", true)) + print("-------------------------------------------------------------") + print("") + table.Empty(mr_ply.mr_dup.count.errors.list) + end + end + end) +end + +-- Load model materials from saves (Models spawn almost at the same time, so my strange timers work nicelly) +function Duplicator_LoadModelMaterials(ply, ent, savedTable) + if CLIENT then return; end + + -- Check if client is valid + if IsEntity(ply) then + if not ply:IsValid() then + return + end + end + + -- First cleanup + if not mr_dup.clean then + mr_dup.clean = true + Material_RestoreAll(ply) + end + + -- Register that we have model materials to duplicate and count elements + if not mr_dup.has.models then + mr_dup.has.models = true + ply.mr_dup.run = "models" + Duplicator_SendStatusToCl(ply, nil, nil, "Model Materials") + end + + -- Set the aditive delay time + mr_dup.models.delay = mr_dup.models.delay + 0.1 + + -- Change the stored entity to the actual one + savedTable.ent = ent + + -- Get the max delay time + if mr_dup.models.delay > mr_dup.models.max_delay then + mr_dup.models.max_delay = mr_dup.models.delay + end + + -- Count 1 + ply.mr_dup.count.total = ply.mr_dup.count.total + 1 + Duplicator_SendStatusToCl(ply, nil, ply.mr_dup.count.total) + + timer.Create("MapRetDuplicatorMapMatWaiting"..tostring(mr_dup.models.delay)..tostring(ply), mr_dup.models.delay, 1, function() + -- Count 2 + ply.mr_dup.count.current = ply.mr_dup.count.current + 1 + Duplicator_SendStatusToCl(ply, ply.mr_dup.count.current) + + -- Check if the material is valid + local isValid = Material_IsValid(savedTable.newMaterial) + + -- Apply the model material + if isValid then + Model_Material_Set(savedTable) + -- Or register an error + else + ply.mr_dup.count.errors.n = ply.mr_dup.count.errors.n + 1 + Duplicator_SendErrorCountToCl(ply, ply.mr_dup.count.errors.n, savedTable.newMaterial) + end + + -- No more entries. Set the next duplicator section to run if it's active and try to reset variables + if mr_dup.models.delay == mr_dup.models.max_delay then + ply.mr_dup.run = "decals" + mr_dup.has.models = false + Duplicator_Finish(ply) + end + end) +end +duplicator.RegisterEntityModifier("MapRetexturizer_Models", Duplicator_LoadModelMaterials) + +-- Load map materials from saves +function Duplicator_LoadDecals(ply, ent, savedTable, position, forceCheck) + if CLIENT then return; end + + -- Check if client is valid + if IsEntity(ply) then + if not ply:IsValid() then + return + end + end + + -- Force check + if forceCheck and not mr_dup.has.models then + ply.mr_dup.run = "decals" + end + + -- Register that we have decals to duplicate + if not ply.mr_dup.has.decals then + ply.mr_dup.has.decals = true + end + + if ply.mr_dup.run == "decals" then + -- First cleanup + if not mr_dup.clean then + if not ply.mr_firstSpawn then + mr_dup.clean = true + Material_RestoreAll(ply) + timer.Create("MapRetDuplicatorDecalsWaitCleanup", 1, 1, function() + Duplicator_LoadDecals(ply, ent, savedTable) + end) + + return + end + end + + -- Fix the duplicator generic spawn entity + if not mr_dup.hidden then + Duplicator_CreateEnt(ent) + end + + if not position then + -- Set the first position + position = 1 + + -- Set the counting + ply.mr_dup.count.total = table.Count(savedTable) + ply.mr_dup.count.current = 0 + + -- Update the client + Duplicator_SendStatusToCl(ply, nil, ply.mr_dup.count.total, "Decals", true) + end + + -- Apply decal + Decal_Start(ply, nil, savedTable[position]) + + -- Count + ply.mr_dup.count.current = ply.mr_dup.count.current + 1 + Duplicator_SendStatusToCl(ply, ply.mr_dup.count.current) + + -- Next material + position = position + 1 + if savedTable[position] and not mr_dup.force_stop then + timer.Create("MapRetDuplicatorDecalDelay"..tostring(ply), 0.1, 1, function() + Duplicator_LoadDecals(ply, nil, savedTable, position) + end) + -- No more entries. Set the next duplicator section to run if it's active and try to reset variables + else + ply.mr_dup.run = "map" + ply.mr_dup.has.decals = false + Duplicator_Finish(ply) + end + else + -- Keep waiting + timer.Create("MapRetDuplicatorDecalWaitModelsDelay"..tostring(ply), 0.3, 1, function() + Duplicator_LoadDecals(ply, ent, savedTable, nil, true) + end) + end +end +duplicator.RegisterEntityModifier("MapRetexturizer_Decals", Duplicator_LoadDecals) + +-- Load map materials from saves +function Duplicator_LoadMapMaterials(ply, ent, savedTable, position, forceCheck) + if CLIENT then return; end + + -- Check if client is valid + if IsEntity(ply) then + if not ply:IsValid() then + return + end + end + + -- Force check + if forceCheck and (not mr_dup.has.models and not ply.mr_dup.has.decals) then + ply.mr_dup.run = "map" + end + + -- Register that we have map materials to duplicate + if not ply.mr_dup.has.map then + ply.mr_dup.has.map = true + end + + if ply.mr_dup.run == "map" then + -- First cleanup + if not mr_dup.clean then + if not ply.mr_firstSpawn then + mr_dup.clean = true + Material_RestoreAll(ply) + timer.Create("MapRetDuplicatorMapMatWaitCleanup", 1, 1, function() + Duplicator_LoadMapMaterials(ply, ent, savedTable) + end) + + return + end + end + + -- Fix the duplicator generic spawn entity + if not mr_dup.hidden then + Duplicator_CreateEnt(ent) + end + + if not position then + -- Set the first position + position = 1 + + -- Set the counting + ply.mr_dup.count.total = MML_Count(savedTable) + ply.mr_dup.count.current = 0 + + -- Update the client + Duplicator_SendStatusToCl(ply, nil, ply.mr_dup.count.total, "Map Materials", true) + end + + -- Check if we have a valid entry + if savedTable[position] and not mr_dup.force_stop then + -- Yes. Is it an INvalid entry? + if savedTable[position].oldMaterial == nil then + -- Yes. Let's check the next entry + Duplicator_LoadMapMaterials(ply, nil, savedTable, position + 1) + + return + end + -- No. Let's apply the changes + -- No more entries. And because it's the last duplicator section, just reset the variables + else + ply.mr_dup.has.map = false + Duplicator_Finish(ply) + + return + end + + -- Count + ply.mr_dup.count.current = ply.mr_dup.count.current + 1 + Duplicator_SendStatusToCl(ply, ply.mr_dup.count.current) + + -- Check if the material is valid + local isValid = Material_IsValid(savedTable[position].newMaterial) + + -- Apply the map material + if isValid then + Map_Material_Set(ply, savedTable[position]) + -- Or register an error + else + ply.mr_dup.count.errors.n = ply.mr_dup.count.errors.n + 1 + Duplicator_SendErrorCountToCl(ply, ply.mr_dup.count.errors.n, savedTable[position].newMaterial) + end + + -- Next material + timer.Create("MapRetDuplicatorMapMatDelay"..tostring(ply), 0.1, 1, function() + Duplicator_LoadMapMaterials(ply, nil, savedTable, position + 1) + end) + else + -- Keep waiting + timer.Create("MapRetDuplicatorMapMatWaitDecalsDelay"..tostring(ply), 0.3, 1, function() + Duplicator_LoadMapMaterials(ply, ent, savedTable, nil, true) + end) + end +end +duplicator.RegisterEntityModifier("MapRetexturizer_Maps", Duplicator_LoadMapMaterials) + +-- Load the skybox +function Duplicator_LoadSkybox(ply, ent, savedTable) + if CLIENT then return; end + + -- This timer is only for good aesthetics on loading + timer.Create("MapRetDuplicatorSkyboxWait", 2.5, 1, function() + -- Check if client is valid + if IsEntity(ply) then + if not ply:IsValid() then + return + end + end + + Skybox_Apply(ply, savedTable.skybox) + end) +end +duplicator.RegisterEntityModifier("MapRetexturizer_Skybox", Duplicator_LoadSkybox) + +-- Render duplicator progress bar based on the ply.mr_dup.count numbers +if CLIENT then + function Duplicator_RenderProgress(ply) + if mr_ply.mr_dup then + if mr_ply.mr_dup.count.total > 0 and mr_ply.mr_dup.count.current > 0 then + local x, y, w, h = 25, ScrH()/2 + 200, 200, 20 + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawOutlinedRect(x, y, w, h) + + surface.SetDrawColor(200, 0, 0, 255) + surface.DrawRect(x + 1.2, y + 1.2, w * (mr_ply.mr_dup.count.current / mr_ply.mr_dup.count.total) - 2, h - 2) + + surface.SetDrawColor(0, 0, 0, 150) + surface.DrawRect(x + 1.2, y - 42, w, h * 2) + + draw.DrawText("MAP RETEXTURIZER","HudHintTextLarge",x+w/2,y-40,Color(255,255,255,255),1) + draw.DrawText(mr_ply.mr_dup.run..": "..tostring(mr_ply.mr_dup.count.current).."/"..tostring(mr_ply.mr_dup.count.total),"CenterPrintText",x+w/2,y-20,Color(255,255,255,255),1) + if mr_ply.mr_dup.count.errors.n > 0 then + draw.DrawText("Errors: "..tostring(mr_ply.mr_dup.count.errors.n),"CenterPrintText",x+w/2,y,Color(255,255,255,255),1) + end + end + end + end + + hook.Add("HUDPaint", "MapRetDupProgress", function() + Duplicator_RenderProgress(LocalPlayer()) + end) +end + +-- Force to stop the duplicator +function Duplicator_ForceStop(plyLoadingStatus) + if SERVER then + if mr_dup.running ~= "" or plyLoadingStatus then + mr_dup.force_stop = true + + net.Start("MapRetForceDupToStop") + net.Broadcast() + + return true + end + + return false + else + mr_dup.force_stop = true + + timer.Create("MapRetDuplicatorForceStop", 0.3, 1, function() + mr_dup.force_stop = false + end) + end + + return +end +if SERVER then + util.AddNetworkString("MapRetForceDupToStop") +else + net.Receive("MapRetForceDupToStop", function() + Duplicator_ForceStop() + end) +end + +-- Reset the duplicator state if it's finished +function Duplicator_Finish(ply) + if CLIENT then return; end + + if not mr_dup.has.models and not ply.mr_dup.has.decals and not ply.mr_dup.has.map then + -- Set the duplicator as initialized + if not mr_mat.initialized then + mr_mat.initialized = true + end + + -- Reset the progress bar + ply.mr_dup.run = "" + ply.mr_dup.count.total = 0 + ply.mr_dup.count.current = 0 + Duplicator_SendStatusToCl(ply, 0, 0, "") + + -- Reset the error counting + if ply.mr_dup.count.errors.n > 0 then + Duplicator_SendErrorCountToCl(0) + ply.mr_dup.count.errors.n = 0 + end + + -- Finish for new players + if ply.mr_firstSpawn then + ply.mr_firstSpawn = false + net.Start("MapRetPlyFirstSpawnEnd") + net.Send(ply) + -- Finish for the normal usage + else + -- Set "running" to nothing + mr_dup.running = "" + net.Start("MapRetDupFinish") + net.Broadcast() + + -- Reset the first clean state + mr_dup.clean = false + + -- Reset model delay adjuster + mr_dup.models.delay = 0 + mr_dup.models.max_delay = 0 + + print("[Map Retexturizer] Loading finished.") + end + end +end +if SERVER then + util.AddNetworkString("MapRetDupFinish") +else + net.Receive("MapRetDupFinish", function() + mr_dup.running = "" + end) +end + +-------------------------------- +--- PREVIEW +-------------------------------- + +-- Toogle the preview mode for a player +function Preview_Toogle(ply, state, setOnClient, setOnServer) + if CLIENT then + if setOnClient then + mr_ply.mr_previewmode = state + end + if setOnServer then + net.Start("MapRetTooglePreview") + net.WriteBool(state) + net.SendToServer() + end + else + if setOnServer then + ply.mr_previewmode = state + end + if setOnClient then + net.Start("MapRetTooglePreview") + net.WriteBool(state) + net.Send(ply) + end + end +end +if SERVER then + util.AddNetworkString("MapRetTooglePreview") +end +net.Receive("MapRetTooglePreview", function(_, ply) + ply = ply or mr_ply + + ply.mr_previewmode = net.ReadBool() +end) + +-- Material rendering +if CLIENT then + function Preview_Render(ply, mapMatMode) + local tr = ply:GetEyeTrace() + local oldData = Data_CreateFromMaterial("MatRetPreviewMaterial", nil) + local newData = mapMatMode and Data_Create(ply, tr) or DataTable_CreateDefaults(ply, tr) + + -- Don't apply bad materials + if not Material_IsValid(newData.newMaterial) then + return + end + + -- Don't render decal materials over the skybox + if not mapMatMode and Material_GetOriginal(tr) == "tools/toolsskybox" then + return + end + + -- Preview adjustments + oldData.newMaterial = mr_mat.preview.newMaterial + if mr_mat.preview.rotation_workaround and mr_mat.preview.rotation_workaround ~= -1 then + oldData.rotation = mr_mat.preview.rotation_workaround -- "Fix" the rotation + end + newData.oldMaterial = "MatRetPreviewMaterial" + + -- Update the material if necessary + if Material_ShouldChange(ply, oldData, newData, tr) then + Map_Material_SetAux(newData) + mr_mat.preview.rotation_workaround = newData.rotation + mr_mat.preview.newMaterial = newData.newMaterial + end + + -- Get the properties + local preview = Material("MatRetPreviewMaterial") + local width = preview:Width() + local height = preview:Height() + + -- Map material + if mapMatMode then + -- Resize + while width > 512 or height > 300 do + width = width/1.1 + height = height/1.1 + end + + -- Render map material + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(preview) + surface.DrawTexturedRect(25, ScrH()/2 - height/2, width, height) + -- Decal + else + local ang = tr.HitNormal:Angle() + + -- Render decal (It's imprecise because util.DecalEx() is buggy) + render.SetMaterial(preview) + render.DrawQuadEasy(tr.HitPos, tr.HitNormal, width, height, Color(255,255,255), 180) + + -- Render imprecision alert + local corretion = 51 + + if height <= 32 then + corretion = 70 + elseif height <= 64 then + corretion = 60 + elseif height <= 128 then + corretion = 53 + end + + cam.Start3D2D(Vector(tr.HitPos.x, tr.HitPos.y, tr.HitPos.z + (height*corretion)/100), Angle(ang.x, ang.y + 90, ang.z + 90), 0.09) + surface.SetFont("CloseCaption_Normal") + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(0, 0) + surface.DrawText("Decals preview may be inaccurate.") + cam.End3D2D() + end + end + + -- Start map materials preview + function Preview_Render_Map_Materials() + local ply = LocalPlayer() + + if mr_ply.mr_previewmode and not mr_ply.mr_decalmode then + Preview_Render(ply, true) + end + end + hook.Add("HUDPaint", "MapRetPreview", function() + Preview_Render_Map_Materials() + end) + + -- Start decals preview + function Preview_Render_Decals() + local ply = LocalPlayer() + + if mr_ply.mr_previewmode and mr_ply.mr_decalmode then + Preview_Render(ply, false) + end + end + hook.Add("PostDrawOpaqueRenderables", "MapRetPreview", function() + Preview_Render_Decals() + end) +end + +-------------------------------- +--- SAVING / LOADING +-------------------------------- + +-- Save the modifications to a file and reload the menu +function Save_Start(ply, forceName) + if SERVER then return; end + + local name = GetConVar("mapret_savename"):GetString() + + if name == "" then + return + end + + net.Start("MapRetSave") + net.WriteString(name) + net.SendToServer() +end +function Save_Apply(name, theFile) + if CLIENT then return; end + + --[[ + -- Not working, just listed. I think that reloading models here is a bad idea + local modelList = {} + + for k,v in pairs(ents.GetAll()) do + if v.modifiedmaterial then + table.insert(modelList, v) + end + end + + mr_manage.save.list[name] = { models = modelList, decals = mr_mat.decal.list, map = mr_mat.map.list, dupEnt = mr_dup.entity} + ]] + + -- Create a save table + mr_manage.save.list[name] = { decals = mr_mat.decal.list, map = mr_mat.map.list, skybox = GetConVar("mapret_skybox"):GetString() } + + -- Save it in a file + file.Write(theFile, util.TableToJSON(mr_manage.save.list[name])) + + -- Server alert + print("[Map Retexturizer] Saved the materials state.") + + -- Associte a name with the saved file + mr_manage.load.list[name] = theFile + + -- Update the load list on every client + net.Start("MapRetSaveAddToLoadList") + net.WriteString(name) + net.Broadcast() +end +if SERVER then + util.AddNetworkString("MapRetSave") + util.AddNetworkString("MapRetSaveAddToLoadList") + + net.Receive("MapRetSave", function(_, ply) + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + local name = net.ReadString() + + Save_Apply(name, mr_manage.map_folder..name..".txt") + end) + + concommand.Add("mapret_remote_save", function(_1, _2, _3, name) + if name == "" then + return + end + + Save_Apply(name, mr_manage.map_folder..name..".txt") + end) +end +if CLIENT then + net.Receive("MapRetSaveAddToLoadList", function() + local name = net.ReadString() + local theFile = mr_manage.map_folder..name..".txt" + + if mr_manage.load.list[name] == nil then + if mr_manage.load.element then mr_manage.load.element:AddChoice(name) end + mr_manage.load.list[name] = theFile + end + end) +end + +-- Set autoloading for the map +function Save_SetAuto_Start(ply, value) + if SERVER then return; end + + -- Set the autosave option on every client + net.Start("MapRetAutoSaveSet") + net.WriteBool(value) + net.SendToServer() +end +function Save_SetAuto_Apply(ply, value) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + -- Remove the autosave timer + if not value then + if timer.Exists("MapRetAutoSave") then + timer.Remove("MapRetAutoSave") + end + end + + -- Set the autosave + RunConsoleCommand("mapret_autosave", value and "1" or "0") +end +if SERVER then + util.AddNetworkString("MapRetAutoSaveSet") + + net.Receive("MapRetAutoSaveSet", function(_, ply) + Save_SetAuto_Apply(ply, net.ReadBool(value)) + end) + + concommand.Add("mapret_remote_autosave", function(_1, _2, _3, valueIn) + local value + + if valueIn == "1" then + value = true + elseif valueIn == "0" then + value = false + else + print("[Map Retexturizer] Invalid value. Choose 1 or 0.") + + return + end + + Save_SetAuto_Apply(mr_dup.fake_ply, value) + + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Console: autosaving "..(value and "enabled" or "disabled")..".") + print("[Map Retexturizer] Console: autosaving "..(value and "enabled" or "disabled")..".") + end) +end + +-- Load modifications +function Load_Start(ply) + if SERVER then return; end + + -- Get and check the name + local name = mr_manage.load.element:GetSelected() + + if not name or name == "" then + return false + end + + -- Load the file + net.Start("MapRetLoad") + net.WriteString(name) + net.SendToServer() +end +function Load_Apply(ply, loadTable) + if CLIENT then return; end + + --[[ + -- Send the model materias (Not working, just listed. I think that reloading models here is a bad idea) + for k,v in pairs(loadTable and loadTable.models or ents.GetAll()) do + if v.modifiedmaterial then + Duplicator_LoadModelMaterials(ply, v, v.modifiedmaterial) + end + end + ]] + + local outTable1, outTable2, outTable3 + + -- Don't start another loading process if we are stopping one yet + if not mr_dup.force_stop then + local delay = 0 + + -- Force to stop any running loading + if not ply.mr_firstSpawn then + Duplicator_ForceStop() + delay = 0.4 + end + + -- Wait to the last command to be done + timer.Create("MapRetFirstJoinStart", delay, 1, function() + -- Apply decals + outTable1 = loadTable and loadTable.decals or mr_mat.decal.list + if table.Count(outTable1) > 0 then + Duplicator_LoadDecals(ply, nil, outTable1) + end + + -- Then map materials + outTable2 = loadTable and loadTable.map or mr_mat.map.list + if MML_Count(outTable2) > 0 then + Duplicator_LoadMapMaterials(ply, nil, outTable2) + end + + -- Then the skybox + local currentSkybox = { skybox = GetConVar("mapret_skybox"):GetString() } + outTable3 = loadTable and loadTable or currentSkybox + if outTable3.skybox ~= "" then + Duplicator_LoadSkybox(ply, nil, outTable3) + end + + if table.Count(outTable1) == 0 and MML_Count(outTable2) == 0 and outTable3.skybox == "" then + -- Manually reset the mr_firstSpawn state if it's true and there aren't any modifications + if ply.mr_firstSpawn then + ply.mr_firstSpawn = false + net.Start("MapRetPlyFirstSpawnEnd") + net.Send(ply) + end + else + -- Server alert + if not ply.mr_firstSpawn then + print("[Map Retexturizer] Loading started...") + end + end + + -- Reset the force stop var (It was set true in Duplicator_ForceStop()) + if not ply.mr_firstSpawn then + mr_dup.force_stop = false + end + end) + end +end +if SERVER then + util.AddNetworkString("MapRetLoad") + util.AddNetworkString("MapRetLoad_SetPly") + + local function Load_Apply_Start(ply, name) + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + -- Get and check the load file + local theFile = mr_manage.load.list[name] + + if theFile == nil then + return false + end + + -- Get the load file content + loadTable = util.JSONToTable(file.Read(theFile, "DATA")) + + if loadTable then + -- Register the name of the loading (one that is running for all the players) + mr_dup.running = name + net.Start("MapRetLoad_SetPly") + net.WriteString(name) + net.Send(ply) + + -- Load it + Load_Apply(ply, loadTable) + + return true + end + + return false + end + + net.Receive("MapRetLoad", function(_, ply) + Load_Apply_Start(ply, net.ReadString()) + end) + + concommand.Add("mapret_remote_load", function(_1, _2, _3, name) + if Load_Apply_Start(mr_dup.fake_ply, name) then + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Console: loading \""..name.."\"...") + else + print("[Map Retexturizer] File not found.") + end + end) +else + net.Receive("MapRetLoad_SetPly", function(_, ply) + mr_dup.running = net.ReadString() + end) +end + +-- Fill the clients load combobox with saves +function Load_FillList() + if SERVER then return; end + + for k,v in pairs(mr_manage.load.list) do + mr_manage.load.element:AddChoice(k) + end +end +if SERVER then + util.AddNetworkString("MapRetLoadFillList") +end +if CLIENT then + net.Receive("MapRetLoadFillList", function() + mr_manage.load.list = net.ReadTable() + end) +end + +-- Prints the load list in the console +if SERVER then + local function Load_ShowList() + print("----------------------------") + print("[Map Retexturizer] SAVES:") + print("----------------------------") + for k,v in pairs(mr_manage.load.list) do + print(k) + end + print("----------------------------") + end + + concommand.Add("mapret_remote_list", function(_1, _2, _3, name) + Load_ShowList() + end) +end + +-- Delete a saved file and reload the menu +function Load_Delete_Start(ply) + if SERVER then return; end + + -- Get the load name and check if it's no empty + local theName = mr_manage.load.element:GetSelected() + + if not theName or theName == "" then + return + end + + -- Remove the load on every client + net.Start("MapRetLoadDeleteSV") + net.WriteString(theName) + net.SendToServer() +end +function Load_Delete_Apply(ply, theName) + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + local theFile = mr_manage.load.list[theName] + + -- Check if the file exists + if theFile == nil then + return false + end + + -- Remove the load entry + mr_manage.load.list[theName] = nil + + -- Remove the load from the autoload if it is there + if GetConVar("mapret_autoload"):GetString() == theName then + RunConsoleCommand("mapret_autoload", "") + end + + -- Delete the file + file.Delete(theFile) + + -- Updates the load list on every client + net.Start("MapRetLoadDeleteCL") + net.WriteString(theName) + net.Broadcast() + + return true +end +if SERVER then + util.AddNetworkString("MapRetLoadDeleteSV") + util.AddNetworkString("MapRetLoadDeleteCL") + + net.Receive("MapRetLoadDeleteSV", function(_, ply) + Load_Delete_Apply(ply, net.ReadString()) + end) + + concommand.Add("mapret_remote_delete", function(_1, _2, _3, name) + if Load_Delete_Apply(mr_dup.fake_ply, name) then + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Console: deleted the save \""..name.."\".") + print("[Map Retexturizer] Console: deleted the save \""..name.."\".") + else + print("[Map Retexturizer] File not found.") + end + end) +end +if CLIENT then + net.Receive("MapRetLoadDeleteCL", function() + local name = net.ReadString() + + mr_manage.load.list[name] = nil + mr_manage.load.element:Clear() + + for k,v in pairs(mr_manage.load.list) do + mr_manage.load.element:AddChoice(k) + end + end) +end + +-- Set autoloading for the map +function Load_SetAuto_Start(ply, text) + if SERVER then return; end + + -- Set the autoload on every client + net.Start("MapRetAutoLoadSet") + net.WriteString(text or mr_manage.load.element:GetText()) + net.SendToServer() +end +function Load_SetAuto_Apply(ply, text) + if CLIENT then return; end + + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + if not mr_manage.load.list[text] and text ~= "" then + return false + end + + RunConsoleCommand("mapret_autoload", text) + + timer.Create("MapRetWaitToSave", 0.3, 1, function() + file.Write(mr_manage.autoload.file, GetConVar("mapret_autoload"):GetString()) + end) + + return true +end +if SERVER then + util.AddNetworkString("MapRetAutoLoadSet") + + net.Receive("MapRetAutoLoadSet", function(_, ply) + local text = net.ReadString() + + Load_SetAuto_Apply(ply, text) + end) + + concommand.Add("mapret_remote_autoload", function(_1, _2, _3, text) + if Load_SetAuto_Apply(mr_dup.fake_ply, text) then + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Console: autoload set to \""..text.."\".") + print("[Map Retexturizer] Console: autoload set to \""..text.."\".") + else + print("[Map Retexturizer] File not found.") + end + end) +end + +-- Load the server modifications on the first spawn (start) +function Load_FirstSpawn(ply) + if CLIENT then return; end + + -- Set the player status + ply.mr_firstSpawn = true + + -- Index duplicator stuff (serverside) + mr_dup_set(ply) + + -- Fill up the player load list + net.Start("MapRetLoadFillList") + net.WriteTable(mr_manage.load.list) + net.Send(ply) + + -- Do not go on if it's not needed + if GetConVar("mapret_skybox"):GetString() == "" + and table.Count(mr_mat.decal.list) == 0 + and MML_Count(mr_mat.map.list) == 0 + and GetConVar("mapret_autoload"):GetString() == "" + then + + -- Register that the player completed the spawn + ply.mr_firstSpawn = false + net.Start("MapRetPlyFirstSpawnEnd") + net.Send(ply) + + return + end + + -- Wait a little (decals need this) + timer.Create("MapRetFirstSpawnApplyDelay"..tostring(ply), 5, 1, function() + -- Send the current modifications + if mr_dup.running == "" and (GetConVar("mapret_autoload"):GetString() == "" or mr_mat.initialized) then + Load_Apply(ply, nil) + -- Or load an saved file + else + -- Register if the player is also initializing the material table + if not mr_mat.initialized then + local isLoading = false + + for k,v in pairs(player.GetAll()) do + if v.mr_mat_initializing then + isLoading = true + end + end + + if not isLoading then + ply.mr_mat_initializing = true + end + end + + -- Get the save name + local autol = mr_dup.running ~= "" and mr_dup.running or file.Read(mr_manage.autoload.file, "DATA") + + -- Load the materials + if autol ~= "" then + local loadTable = util.JSONToTable(file.Read(mr_manage.map_folder..autol..".txt")) + Load_Apply(ply, loadTable) + end + end + end) +end +if SERVER then + util.AddNetworkString("MapRetPlyFirstSpawnEnd") + + hook.Add("PlayerInitialSpawn", "MapRetPlyFirstSpawn", Load_FirstSpawn) +end +if CLIENT then + net.Receive("MapRetPlyFirstSpawnEnd", function() + mr_ply.mr_firstSpawn = false + end) +end + +-------------------------------- +--- TOOL FUNCTIONS +-------------------------------- + +function TOOL_BasicChecks(ply, ent, tr) + -- Admin only + if not Ply_IsAdmin(ply) then + return false + end + + -- The player can't use the tool if he's already in the joining process + local ply2 + if SERVER then + ply2 = ply + else + ply2 = mr_ply + end + if ply2.mr_firstSpawn then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] The tool is not ready to use yet.") + + return false + end + + -- It's not meant to mess with players + if ent:IsPlayer() then + return false + end + + -- We can't mess with displacement materials + if ent:IsWorld() and Material_GetCurrent(tr) == "**displacement**" then + if SERVER then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Sorry, we can't handle displacement materials!") + end + + return false + end + + return true +end + +-- Apply materials +function TOOL:LeftClick(tr) + local ply = self:GetOwner() or LocalPlayer() + local ent = tr.Entity + + -- Basic checks + if not TOOL_BasicChecks(ply, ent, tr) then + return false + end + + -- Do not modify anything in the middle of a loading + local ply2 + if SERVER then + ply2 = ply + else + ply2 = mr_ply + end + if mr_dup.running ~= "" or ply2.mr_dup.run ~= "" then + if SERVER then + if not ply.mr_decalmode then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Wait for the loading to finish.") + end + end + + return false + end + + -- Do not try to modify the skybox + if Material_GetOriginal(tr) == "tools/toolsskybox" then + if SERVER then + if not ply.mr_decalmode then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Modify the skybox using the tool menu.") + end + end + + return false + end + + -- Create the duplicator entity used to restore map materials, decals and skybox + if SERVER then + Duplicator_CreateEnt() + end + + -- If we are dealing with decals + local ply2 = self:GetOwner() or mr_ply + if ply2.mr_decalmode then + return Decal_Start(ply, tr) + end + + -- Check upper limit + if not MML_Check() then + return false + end + + -- Generate the new data + local data = Data_Create(ply, tr) + + -- Don't apply bad materials + if not Material_IsValid(data.newMaterial) then + if SERVER then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Bad material.") + end + + return false + end + + -- Do not apply the material if it's not necessary + if not Material_ShouldChange(ply, Data_Get(tr), data, tr) then + return false + end + + -- Register that the map is manually modified + if not mr_mat.initialized then + mr_mat.initialized = true + end + + -- All verifications are done for the client. Let's only check the autosave now + if CLIENT then + return true + end + + -- Auto save + if GetConVar("mapret_autosave"):GetString() == "1" then + if not timer.Exists("MapRetAutoSave") then + timer.Create("MapRetAutoSave", 60, 1, function() + Save_Apply(mr_manage.autosave.name, mr_manage.autosave.file) + PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] Auto saving...") + end) + end + end + + -- Set + timer.Create("LeftClickMultiplayerDelay"..tostring(math.random(999))..tostring(ply), multiplayer_action_delay, 1, function() + -- model material + if IsValid(ent) then + Model_Material_Set(data) + -- or map material + elseif ent:IsWorld() then + Map_Material_Set(ply, data) + end + end) + + -- Set the Undo + undo.Create("Material") + undo.SetPlayer(ply) + undo.AddFunction(function(tab, data) + if data.oldMaterial then + Material_Restore(ent, data.oldMaterial) + end + end, data) + undo.SetCustomUndoText("Undone a material") + undo.Finish("Material ("..tostring(data.newMaterial)..")") + + return true +end + +-- Copy materials +function TOOL:RightClick(tr) + local ply = self:GetOwner() or LocalPlayer() + local ent = tr.Entity + + -- Basic checks + if not TOOL_BasicChecks(ply, ent, tr) then + return false + end + + -- Create a new data table and try to get the current one + local newData = Data_Get(tr) or true + local oldData = Data_Get(tr) + + -- If the material is invalid we can't get it + if newData == true then + local material = Material_GetOriginal(tr) + + if not material then + ply:PrintMessage(HUD_PRINTTALK, "[Map Retexturizer] The material you are trying to modify has an invalid name.") + + return false + end + + newData = Data_CreateFromMaterial(material) + end + + -- Check if the copy isn't necessary + if Material_GetCurrent(tr) == Material_GetNew(ply) then + if oldData then + if not Material_ShouldChange(ply, oldData, newData, tr) then + return false + end + else + return false + end + end + + if CLIENT then + -- Set the detail element to the right position + local i = 1 + + for k,v in SortedPairs(mr_mat.detail.list) do + if k == newData.detail then + break + else + i = i + 1 + end + end + + if mr_mat.detail.element then + mr_mat.detail.element:ChooseOptionID(i) + end + + return true + end + + -- Copy the material + ply:ConCommand("mapret_material "..Material_GetCurrent(tr)) + + -- Set the cvars to data values + if newData then + CVars_SetToData(ply, newData) + -- Or set the cvars to default values + else + CVars_SetToDefaults(ply) + end + + return true +end + +-- Restore materials +function TOOL:Reload(tr) + local ply = self:GetOwner() or LocalPlayer() + local ent = tr.Entity + + -- Basic checks + if not TOOL_BasicChecks(ply, ent, tr) then + return false + end + + -- Reset the material + if Data_Get(tr) then + if SERVER then + timer.Create("ReloadMultiplayerDelay"..tostring(math.random(999))..tostring(ply), multiplayer_action_delay, 1, function() + Material_Restore(ent, Material_GetOriginal(tr)) + end) + end + + return true + end + + return false +end + +-- Preview mode checking +function TOOL:Deploy() + if CLIENT then return; end + + local ply = self:GetOwner() + + if ply.mr_previewmode then + Preview_Toogle(ply, true, true, false) + end +end + +-- Preview mode checking +function TOOL:Holster() + if SERVER then return; end + + if mr_mat.preview.holster_workaround then -- BUG!! For some reason this function is called when the tool is loaded for the first time + Preview_Toogle(self:GetOwner(), false, true, false) + else + mr_mat.preview.holster_workaround = true + end +end + +-- Panels +function TOOL.BuildCPanel(CPanel) + CPanel:SetName("#tool.mapret.name") + CPanel:Help("#tool.mapret.desc") + + local ply = LocalPlayer() + local properties = { label, a, b, c, d, e, f, baseMaterialReset } + + local function Properties_Toogle(val) + if val then + mr_mat.detail.element:Hide() + else + mr_mat.detail.element:Show() + end + + for k,v in pairs(properties) do + if val then + v:Hide() + else + v:Show() + end + end + end + + CPanel:Help(" ") + local section_general = vgui.Create("DCollapsibleCategory", CPanel) + section_general:SetLabel("General") + CPanel:AddItem(section_general) + RunConsoleCommand("mapret_material", "dev/dev_blendmeasure") + CPanel:TextEntry("Material path", "mapret_material") + CPanel:ControlHelp("\nNote: the command \"mat_crosshair\" can get a displacement material path.") + local previewBox = CPanel:CheckBox("Preview Modifications", "mapret_preview") + CPanel:ControlHelp("The preview is not accurate with decals (GMod bugs).") + timer.Create("MapRetPreviewCheckDelay", 0.2, 1, function() + previewBox:SetChecked(true) + Preview_Toogle(ply, previewBox:GetChecked(), true, true) + end) + function previewBox:OnChange(val) + -- Don't let the player mess with the option if the toolgun is not selected + if ply:GetActiveWeapon():GetClass() ~= "gmod_tool" then + if val then + previewBox:SetChecked(false) + else + previewBox:SetChecked(true) + end + + return false + end + + Preview_Toogle(ply, val, true, true) + end + local decalBox = CPanel:CheckBox("Use as Decal", "mapret_decal") + Decal_Toogle(ply, decalBox:GetChecked()) + function decalBox:OnChange(val) + Properties_Toogle(val) + Decal_Toogle(ply, val) + end +-- CPanel:Button("Change all map materials","mapret_changeall") +-- CPanel:ControlHelp("It doesn't fully work (GMod bugs).") + CPanel:Button("Open Material Browser","mapret_materialbrowser") + + CPanel:Help(" ") + local section_properties = vgui.Create("DCollapsibleCategory", CPanel) + section_properties:SetLabel("Material Properties") + CPanel:AddItem(section_properties) + mr_mat.detail.element, properties.label = CPanel:ComboBox("Select a Detail:", "mapret_detail") + for k,v in SortedPairs(mr_mat.detail.list) do + mr_mat.detail.element:AddChoice(k, k, v) + end + timer.Create("MapRetDetailDefaultDelay", 0.1, 1, function() + mr_mat.detail.element:SetValue("None") + RunConsoleCommand("mapret_detail", "None") + end) + properties.a = CPanel:NumSlider("Alpha", "mapret_alpha", 0, 1, 2) + properties.b = CPanel:NumSlider("Horizontal Translation", "mapret_offsetx", -1, 1, 2) + properties.c = CPanel:NumSlider("Vertical Translation", "mapret_offsety", -1, 1, 2) + properties.d = CPanel:NumSlider("Width Magnification", "mapret_scalex", 0.01, 6, 2) + properties.e = CPanel:NumSlider("Height Magnification", "mapret_scaley", 0.01, 6, 2) + properties.f = CPanel:NumSlider("Rotation", "mapret_rotation", 0, 179, 0) + properties.baseMaterialReset = CPanel:Button("Reset Properties") + function properties.baseMaterialReset:DoClick() + CVars_SetToDefaults(ply) + end + + CPanel:Help(" ") + local section_skybox = vgui.Create("DCollapsibleCategory", CPanel) + section_skybox:SetLabel("Skybox") + CPanel:AddItem(section_skybox) + mr_mat.skybox.element_text = CPanel:TextEntry("In use:", "mapret_skybox") + mr_mat.skybox.element_text.OnEnter = function(self) + Skybox_Start(ply, self:GetValue()) + end + mr_mat.skybox.element_combo = CPanel:ComboBox("HL2:") + function mr_mat.skybox.element_combo:OnSelect(index, value, data) + Skybox_Start(ply, value) + end + for k,v in pairs(mr_mat.skybox.hl2_list) do + mr_mat.skybox.element_combo:AddChoice(k, k) + end + timer.Create("MapRetSkyboxDelay", 0.1, 1, function() + mr_mat.skybox.element_combo:SetValue("") + end) + CPanel:ControlHelp("\nYou can use whatever you want as a sky now.") + CPanel:ControlHelp("developer.valvesoftware.com/wiki/Sky_List.") + + CPanel:Help(" ") + local section_save = vgui.Create("DCollapsibleCategory", CPanel) + section_save:SetLabel("Save") + CPanel:AddItem(section_save) + mr_manage.save.element = CPanel:TextEntry("Filename:", "mapret_savename") + RunConsoleCommand("mapret_savename", mr_manage.save.defaul_name) + CPanel:ControlHelp("\nYour files are being saved under \"./data/"..mr_manage.map_folder.."\".") + CPanel:ControlHelp("\nWARNING! Your modified models will no be saved! If you want to keep them, use the GMod default Save instead.") + local autoSaveBox = CPanel:CheckBox("Autosave", "mapret_autosave") + function autoSaveBox:OnChange(val) + -- Admin only + if not Ply_IsAdmin(ply) then + if val then + autoSaveBox:SetChecked(false) + else + autoSaveBox:SetChecked(true) + end + + return false + end + + Save_SetAuto_Start(ply, val) + end + CPanel:ControlHelp("\nWhen changes are detected it waits 60 seconds to save them automatically in the file \""..mr_manage.autosave.file.."\" under the name of \""..mr_manage.autosave.name.."\" and then repeats this cycle.") + local saveChanges = CPanel:Button("Save") + function saveChanges:DoClick() + Save_Start(ply) + end + + CPanel:Help(" ") + local section_load = vgui.Create("DCollapsibleCategory", CPanel) + section_load:SetLabel("Load") + CPanel:AddItem(section_load) + local mapSec = CPanel:TextEntry("Map:") + mapSec:SetEnabled(false) + mapSec:SetText(game.GetMap()) + mr_manage.autoload.element = CPanel:TextEntry("Autoload:", "mapret_autoload") + mr_manage.autoload.element:SetEnabled(false) + mr_manage.load.element = CPanel:ComboBox("Select a File:") + Load_FillList(ply) + CPanel:Help(" ") + CPanel:Help("Apply:") + local loadSave = CPanel:Button("Load") + function loadSave:DoClick() + Load_Start(ply) + end + local setAutoLoad = CPanel:Button("Set Autoload") + function setAutoLoad:DoClick() + Load_SetAuto_Start(ply) + end + CPanel:Help("Remove:") + local delSave = CPanel:Button("Delete") + function delSave:DoClick() + Load_Delete_Start(ply) + end + local delAutoLoad = CPanel:Button("Remove Autoload") + function delAutoLoad:DoClick() + Load_SetAuto_Start(ply, "") + end + + CPanel:Help(" ") + local section_cleanup = vgui.Create("DCollapsibleCategory", CPanel) + section_cleanup:SetLabel("Cleanup") + CPanel:AddItem(section_cleanup) + local cleanupCombobox = CPanel:ComboBox("Select a section:") + cleanupCombobox:AddChoice("Decals","Decal_RemoveAll") + cleanupCombobox:AddChoice("Map Materials","Map_Material_RemoveAll") + cleanupCombobox:AddChoice("Model Materials","Model_Material_RemoveAll") + cleanupCombobox:AddChoice("Skybox","Skybox_Remove") + cleanupCombobox:AddChoice("All","Material_RestoreAll", true) + local cleanupButton = CPanel:Button("Cleanup","mapret_cleanup_all") + function cleanupButton:DoClick() + local _, netName = cleanupCombobox:GetSelected() + net.Start(netName) + net.WriteBool(mr_ply.mr_dup and mr_ply.mr_dup.run ~= "" and true or false) + net.SendToServer() + end + + CPanel:Help(" ") + CPanel:Help("Report bugs and make requests at the tool workshop page.") + + CPanel:Help(" ") + CPanel:ControlHelp(mr_revision) + CPanel:Help(" ") +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nocollideeverything.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nocollideeverything.lua new file mode 100644 index 0000000..c5efeb0 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nocollideeverything.lua @@ -0,0 +1,168 @@ +TOOL.Category = "Construction" +TOOL.Name = "#tool.nocollideeverything.name" + +TOOL.ClientConVar["noshadow"] = "0" +TOOL.ClientConVar["noself"] = "0" + +if CLIENT then + TOOL.Information = { + {name = "info"}, + {name = "left"}, + {name = "right"}, + {name = "reload"} + } + + language.Add("tool.nocollideeverything.name", "No Collide Everything") + language.Add("tool.nocollideeverything.desc", "Make an object have no collisions at all") + language.Add("tool.nocollideeverything.0", "Hold shift to turn off self-collision on ragdolls") + language.Add("tool.nocollideeverything.left", "Turn off collisions with everything but the world") + language.Add("tool.nocollideeverything.right", "Turn off collisions with everything including the world") + language.Add("tool.nocollideeverything.reload", "Enable collisions") +end + + +local function canTool(entity) + if not entity + or not entity:IsValid() + or entity:IsPlayer() + or not entity:GetPhysicsObject():IsValid() then + return false + else return true end +end + + +local function applyNoColldeEverything(player, entity, data) + if not SERVER then return end + + local override = hook.Run('CanTool', player, { Entity = entity }, 'nocollideeverything') + if override == false then return end + + timer.Simple(0, function() + if not IsValid(entity) then return end + local phys = entity:GetPhysicsObject() + local physCount = entity:GetPhysicsObjectCount() + local noShadow = data.noShadow + local noWorld = data.noWorld + local noSelf = data.noSelf + + if not phys:IsValid() then return end + + if physCount == 1 then + if noWorld then phys:EnableCollisions(false) + else phys:EnableCollisions(true) end + end + + if physCount > 1 then + for num = 0, physCount - 1 do + curPhys = entity:GetPhysicsObjectNum(num) + if curPhys:IsValid() then + if noSelf and not noWorld then curPhys:AddGameFlag(FVPHYSICS_NO_SELF_COLLISIONS) + else curPhys:ClearGameFlag(FVPHYSICS_NO_SELF_COLLISIONS) end + + if noWorld then curPhys:EnableCollisions(false) + else curPhys:EnableCollisions(true) end + end + end + end + + timer.Simple(1, function() -- APG changes collision group + if IsValid(entity) then entity:SetCollisionGroup(COLLISION_GROUP_WORLD) end + end) + entity:DrawShadow(not noShadow) + + duplicator.StoreEntityModifier(entity, "ncEverything", data) + end) +end +duplicator.RegisterEntityModifier("ncEverything", applyNoColldeEverything) + + +local function removeNoColldeEverything(entity, ply) + if not SERVER then return end + if entity.APG_Ghosted then return APG.entUnGhost(entity, ply) end + + local phys = entity:GetPhysicsObject() + local physCount = entity:GetPhysicsObjectCount() + + if physCount == 1 then + phys:EnableCollisions(true) + end + + if physCount > 1 then + for num = 0, physCount - 1 do + curPhys = entity:GetPhysicsObjectNum(num) + if curPhys:IsValid() then + curPhys:ClearGameFlag(FVPHYSICS_NO_SELF_COLLISIONS) + curPhys:EnableCollisions(true) + end + end + end + + entity:SetCollisionGroup(COLLISION_GROUP_NONE) + entity:DrawShadow(true) + + duplicator.ClearEntityModifier(entity, "ncEverything") +end + + +function TOOL:LeftClick(trace) + if CLIENT then return true end + + local entity = trace.Entity + local owner = self:GetOwner() + local shift = owner:KeyDown(IN_SPEED) + local noShadow = tobool(self:GetClientNumber("noshadow")) + if not canTool(entity) then return false end + + local data = { + noShadow = noShadow, + noWorld = false, + noSelf = shift + } + + applyNoColldeEverything(owner, entity, data) + return true +end + + +function TOOL:RightClick(trace) + if CLIENT then return true end + + local entity = trace.Entity + local owner = self:GetOwner() + local shift = owner:KeyDown(IN_SPEED) + local noShadow = tobool(self:GetClientNumber("noshadow")) + if not canTool(entity) then return false end + + local data = { + noShadow = noShadow, + noWorld = true, + noSelf = shift + } + + applyNoColldeEverything(owner, entity, data) + return true +end + + +function TOOL:Reload(trace) + if CLIENT then return true end + local entity = trace.Entity + removeNoColldeEverything(entity, self:GetOwner()) + return true +end + + +function TOOL.BuildCPanel(ControlPanel) + ShadowBox = vgui.Create("DCheckBoxLabel", ControlPanel) + ShadowBox:SetPos(10, 30) + ShadowBox:SetText("Disable shadow") + ShadowBox:SetConVar("nocollideeverything_noshadow") + ShadowBox:SetValue(0) + ShadowBox:SizeToContents() +end + +/* +function TOOL:DrawToolScreen( width, height ) + +end +*/ diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nograb.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nograb.lua new file mode 100644 index 0000000..4470e94 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nograb.lua @@ -0,0 +1,79 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Запретить хватать' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, +} + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER and not ent.nograb then + ent.nograb = true + duplicator.StoreEntityModifier(ent, 'nograb', {}) + self:GetOwner():ChatPrint('Теперь это нельзя схватить рукой') + + doEffect(ent) + end + + return true + +end + +function TOOL:RightClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER and ent.nograb then + ent.nograb = nil + duplicator.ClearEntityModifier(ent, 'nograb') + self:GetOwner():ChatPrint('Теперь это можно схватить рукой') + + doEffect(ent) + end + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'Запретить хвататься', + Description = 'Этот инструмент отключает возможность хватать предмет рукой' + }) + +end + +if CLIENT then + language.Add('Tool.nograb.name', 'Запретить хвататься') + language.Add('Tool.nograb.desc', 'Отключает возможность хватать предмет рукой') + language.Add('Tool.nograb.left', 'Запретить') + language.Add('Tool.nograb.right', 'Восстановить') +else + duplicator.RegisterEntityModifier('nograb', function(ply, ent, data) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'nograb') + if override == false then return end + ent.nograb = data ~= nil + end) + + hook.Add('dbg-hands.canDrag', 'tool.nograb', function(ply, ent, tr) + if ent.nograb then + return false + end + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/noshadow.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/noshadow.lua new file mode 100644 index 0000000..29cc97b --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/noshadow.lua @@ -0,0 +1,73 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Тени' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, +} + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER then + ent:DrawShadow(false) + duplicator.StoreEntityModifier(ent, 'noshadow', {}) + + doEffect(ent) + end + + return true + +end + +function TOOL:RightClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER then + ent:DrawShadow(true) + duplicator.ClearEntityModifier(ent, 'noshadow') + + doEffect(ent) + end + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'Тени', + Description = 'Этот инструмент позволяет включать и отключать тени на предметах' + }) + +end + +if CLIENT then + language.Add('Tool.noshadow.name', 'Тени') + language.Add('Tool.noshadow.desc', 'Отключает/включает тени на предметах') + language.Add('Tool.noshadow.left', 'Отключить') + language.Add('Tool.noshadow.right', 'Включить') +else + duplicator.RegisterEntityModifier('noshadow', function(ply, ent, data) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'noshadow') + if override == false then return end + timer.Simple(1, function() + ent:DrawShadow(data == nil) + end) + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nosit.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nosit.lua new file mode 100644 index 0000000..749c290 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/nosit.lua @@ -0,0 +1,79 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Запретить посадку' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, +} + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER and not ent.nosit then + ent.nosit = true + duplicator.StoreEntityModifier(ent, 'nosit', {}) + self:GetOwner():ChatPrint('Теперь на этом нельзя сидеть') + + doEffect(ent) + end + + return true + +end + +function TOOL:RightClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER and ent.nosit then + ent.nosit = nil + duplicator.ClearEntityModifier(ent, 'nosit') + self:GetOwner():ChatPrint('Запрет на посадку снят') + + doEffect(ent) + end + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'Запретить посадку', + Description = 'Этот инструмент отключает возможность сидеть на предметах' + }) + +end + +if CLIENT then + language.Add('Tool.nosit.name', 'Запретить посадку') + language.Add('Tool.nosit.desc', 'Отключает возможность сидеть на предметах') + language.Add('Tool.nosit.left', 'Запретить') + language.Add('Tool.nosit.right', 'Восстановить') +else + duplicator.RegisterEntityModifier('nosit', function(ply, ent, data) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'nosit') + if override == false then return end + ent.nosit = data ~= nil + end) + + hook.Add('dbg-sit.allow', 'tool.nosit', function(ply, ent, pos, ang) + if IsValid(ent) and ent.nosit then + return false + end + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_desc.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_desc.lua new file mode 100644 index 0000000..46c55b6 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_desc.lua @@ -0,0 +1,132 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = L.octoinv_desc +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, + { name = 'reload' }, +} + +if CLIENT then + octolib.vars.init('tools.desc.time', 3) + octolib.vars.init('tools.desc.name', L.item) + octolib.vars.init('tools.desc.desc', L.desc_item) +end + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER then + if ent:IsPlayer() and not self:GetOwner():IsSuperAdmin() then + return false + end + + local ply = self:GetOwner() + ply:GetClientVar({ + 'tools.desc.name', + 'tools.desc.desc', + 'tools.desc.time', + }, function(vars) + local data = { + name = utf8.sub(vars['tools.desc.name'], 1, 255), + desc = utf8.sub(vars['tools.desc.desc'], 1, 512), + time = tonumber(vars['tools.desc.time']) or 1, + } + + doEffect(ent) + ent:SetNetVar('dbgLook', data) + duplicator.StoreEntityModifier(ent, 'octo_desc', data) + end) + end + + return true + +end + +function TOOL:RightClick(tr) + + if SERVER then return false end + + local ent = tr.Entity + local look = IsValid(ent) and ent:GetNetVar('dbgLook') + if not look then return false end + + octolib.vars.set('tools.desc.name', look.name) + octolib.vars.set('tools.desc.desc', look.desc) + octolib.vars.set('tools.desc.time', look.time) + + return true + +end + +function TOOL:Reload(tr) + + if not IsFirstTimePredicted() then return false end + + local ent = tr.Entity + local look = IsValid(ent) and ent:GetNetVar('dbgLook') + if not look then return false end + + if SERVER then + ent:SetNetVar('dbgLook', nil) + duplicator.ClearEntityModifier(ent, 'octo_desc') + end + + doEffect(ent) + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = L.octoinv_desc, + Description = L.octo_desc_hint + }) + + octolib.vars.presetManager(self, 'tools.desc', {'tools.desc.time', 'tools.desc.name', 'tools.desc.desc'}) + + octolib.vars.slider(self, 'tools.desc.time', L.time_see, 0.1, 60, 1) + octolib.vars.textEntry(self, 'tools.desc.name', L.title) + + self:ControlHelp(L.octo_desc_empty) + local eDesc = octolib.vars.textEntry(self, 'tools.desc.desc', L.octoinv_desc) + eDesc:SetMultiline(true) + eDesc:SetTall(150) + eDesc:SetContentAlignment(7) + + self:ControlHelp(L.octo_desc_help) + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + language.Add('Tool.octo_desc.name', L.octoinv_desc) + language.Add('Tool.octo_desc.desc', L.octo_desc) + language.Add('Tool.octo_desc.left', L.assign) + language.Add('Tool.octo_desc.right', L.tool_copy) + language.Add('Tool.octo_desc.reload', L.remove) +else + duplicator.RegisterEntityModifier('octo_desc', function(ply, ent, data) + local override = hook.Run('CanTool', ply, { Entity = ent }, 'octo_desc') + if override == false then return end + ent:SetNetVar('dbgLook', data) + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_perma.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_perma.lua new file mode 100644 index 0000000..9f8e8a4 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_perma.lua @@ -0,0 +1,78 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = L.octo_perma +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, +} + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +function TOOL:LeftClick(tr) + + local base = tr.Entity + if not IsValid(base) or base:IsPlayer() or not self:GetOwner():query(L.permissions_permaprops) then return false end + + if SERVER then + local data = duplicator.Copy(base) + permaprops.clearData(data) + + for id, entData in pairs(data.Entities) do + local ent = Entity(id) + if IsValid(ent) then + ent:Remove() + end + end + + permaprops.spawn(data) + end + + return true + +end + +function TOOL:RightClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER then + self:GetOwner():ChatPrint(ent.perma and L.prop_set_perma or L.prop_not_perma) + end + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = L.octoinv_desc, + Description = L.octo_perma_hint, + }) + + self:Button(L.save, 'octo_perma_save') + self:ControlHelp(L.perma_save_hint) + + self:Button(L.octo_perma_restart, 'octo_perma_load') + self:ControlHelp(L.perma_restart_hint) + + self:Button(L.remove_from_map, 'octo_perma_clear') + self:ControlHelp(L.remove_from_map_hint) + +end + +if CLIENT then + language.Add('Tool.octo_perma.name', L.octo_perma) + language.Add('Tool.octo_perma.desc', L.octo_perma_desc) + language.Add('Tool.octo_perma.left', L.octo_perma) + language.Add('Tool.octo_perma.right', L.check_perma) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_trigger.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_trigger.lua new file mode 100644 index 0000000..ac0bb17 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_trigger.lua @@ -0,0 +1,323 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = L.trigger +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, +} + +cleanup.Register('octo_triggers') + +if CLIENT then + octolib.vars.init('tools.trigger.sx', 100) + octolib.vars.init('tools.trigger.sy', 100) + octolib.vars.init('tools.trigger.sz', 100) + octolib.vars.init('tools.trigger.uname', 'Триггер') + octolib.vars.init('tools.trigger.times', 1) + octolib.vars.init('tools.trigger.duration', 10) + octolib.vars.init('tools.trigger.title', L.title2) + octolib.vars.init('tools.trigger.text', L.trigger_text) + octolib.vars.init('tools.trigger.method', 'chat') + octolib.vars.init('tools.trigger.gamesound', '') + octolib.vars.init('tools.trigger.urlsound', '') + octolib.vars.init('tools.trigger.volume', 0.5) + octolib.vars.init('tools.trigger.mode', 0) + octolib.vars.init('tools.trigger.3d', false) + octolib.vars.init('tools.trigger.bind', 0) +else + CreateConVar('sbox_maxocto_triggers', 10) +end + +local function getSpawnedTriggers(ply) + local tbl, ans = undo.GetTable(), {} + if SERVER then tbl = tbl[ply:UniqueID()] end + + for _,v in ipairs(undo.GetTable()[ply:UniqueID()] or {}) do + if v.Entities and IsValid(v.Entities[1]) and v.Entities[1]:GetClass() == 'octo_trigger' then + ans[#ans + 1] = v.Entities[1] + end + end + + return ans +end + +function TOOL:LeftClick(tr) + + if CLIENT then return true end + + local ply = self:GetOwner() + + ply:GetClientVar({ + 'tools.trigger.sx', + 'tools.trigger.sy', + 'tools.trigger.sz', + 'tools.trigger.uname', + 'tools.trigger.times', + 'tools.trigger.mode', + 'tools.trigger.duration', + 'tools.trigger.title', + 'tools.trigger.text', + 'tools.trigger.method', + 'tools.trigger.gamesound', + 'tools.trigger.urlsound', + 'tools.trigger.volume', + 'tools.trigger.3d', + 'tools.trigger.bind', + 'tools.trigger.stopsound', + }, function(vars) + local v = Vector( + math.Clamp(tonumber(vars['tools.trigger.sx']) or 1, 1, 1000), + math.Clamp(tonumber(vars['tools.trigger.sy']) or 1, 1, 1000), + math.Clamp(tonumber(vars['tools.trigger.sz']) or 1, 1, 1000) + ) + local name = utf8.sub(string.Trim(vars['tools.trigger.uname'] or 'Триггер'), 1, 128) + if not name or name == '' then + return ply:Notify('warning', 'Укажи уникальное название твоего триггера') + end + + local triggers = getSpawnedTriggers(ply) + for _,v in ipairs(triggers) do + if v.uName == name then + return ply:Notify('warning', 'Триггер с таким названием уже существует') + end + end + + local ent = ents.Create('octo_trigger') + ent:SetPos(tr.HitPos + Vector(0, 0, v.z / 2)) + ent:SetAngles(Angle()) + ent.size = v + ent.times = tonumber(vars['tools.trigger.times']) + ent.mode = tonumber(vars['tools.trigger.mode']) + ent.duration = math.Clamp(tonumber(vars['tools.trigger.duration']) or 10, 1, 300) + ent.title = utf8.sub(vars['tools.trigger.title'], 1, 256) + ent.text = utf8.sub(vars['tools.trigger.text'], 1, 2048) + ent.method = vars['tools.trigger.method'] + ent.gamesound = utf8.sub(vars['tools.trigger.gamesound'], 1, 2048) + ent.sound3d = tobool(vars['tools.trigger.3d']) + ent.bind = vars['tools.trigger.bind'] + ent.uName = name + if ply:query(L.permissions_trigger_url) then ent.urlsound = utf8.sub(vars['tools.trigger.urlsound'], 1, 2048) end + ent.volume = math.Clamp(tonumber(vars['tools.trigger.volume']) or 1, 0, 1) + ent:SetPlayer(ply) + ent:Spawn() + ent:Activate() + + if ent.sound3d then + ply.pending3d = ent + if not vars['tools.trigger.stopsound'] then + ply:Notify('Теперь укажи точку, откуда будет идти звук (ПКМ, чтобы установить на позиции головы)') + end + end + + if vars['tools.trigger.stopsound'] then + ply:Notify('Теперь выбери ранее созданные триггеры, звук которых нужно остановить') + netstream.Start(ply, 'trigger_stopsound_sel', ent:EntIndex(), octolib.table.mapSequential(triggers, function(v) return v.uName end)) + end + + undo.Create('octo_trigger') + undo.AddEntity(ent) + undo.SetPlayer(ply) + undo.Finish() + ply:AddCount('octo_triggers', ent) + ply:AddCleanup('octo_triggers', ent) + end) + + return true +end + +if CLIENT then + netstream.Hook('trigger_stopsound_sel', function(ent, available) + + local fr = vgui.Create 'DFrame' + fr:SetTitle('Выбор Stopsound-триггеров') + fr:SetSize(200, 24) + fr:Center() + fr:MakePopup() + + local tall = 24 + local lst = fr:Add 'DScrollPanel' + lst:Dock(FILL) + local sels = {} + for _,v in ipairs(available) do + local pan = lst:Add('DPanel') + pan:Dock(TOP) + pan:DockMargin(0, 0, 0, 5) + pan:SetTall(30) + local cbox = octolib.checkBox(pan, v) + cbox.uid = v + cbox:DockMargin(5, 5, 5, 5) + tall = tall + 40 + sels[#sels + 1] = cbox + end + + local btn = octolib.button(fr, 'Сохранить', function() + local ans = {} + for _,v in ipairs(sels) do + if v:GetChecked() then + ans[#ans + 1] = v.uid + end + end + netstream.Start('trigger_stopsound_sel', ent, ans) + fr:Close() + end) + + if not available[1] then + local lbl = octolib.label(lst, 'Ранее триггеры не создавались') + lbl:DockMargin(0, 5, 0, 5) + lbl:SetContentAlignment(5) + tall = tall + 40 + end + + btn:Dock(BOTTOM) + tall = tall + 25 + + fr:SetTall(tall) + + end) + +else + + netstream.Hook('trigger_stopsound_sel', function(ply, ent, sel) + if not istable(sel) then return end + ent = Entity(ent) + if not IsValid(ent) or ent:GetClass() ~= 'octo_trigger' or ent:CPPIGetOwner() ~= ply then return end + local triggers = getSpawnedTriggers(ply) + local byName = {} + for _,v in ipairs(triggers) do + byName[v.uName] = v.uid + end + ent.stopsounds = octolib.table.mapSequential(sel, function(v) return byName[v] end) + ply:Notify('Stopsound\'ы сохранены') + if ply.pending3d then + ply:Notify('Теперь укажи точку, откуда будет идти звук (ПКМ, чтобы установить на позиции головы)') + end + end) + +end + +function TOOL:RightClick(tr) + + if CLIENT then return false end + + local ply = self:GetOwner() + if IsValid(ply.pending3d) then + if not tr.Hit then return end + ply.pending3d:SetNetVar('sound3dpos', {ply:GetShootPos(), ply:EyeAngles()}) + ply:Notify('Точка установлена') + ply.pending3d = nil + return true + end + + return false + +end + +function TOOL:DrawHUD() + + local tr = LocalPlayer():GetEyeTrace() + if not tr.Hit then return end + + local ang = Angle() + local v = Vector( + (tonumber(octolib.vars.get('tools.trigger.sx')) or 1) / 2, + (tonumber(octolib.vars.get('tools.trigger.sy')) or 1) / 2, + (tonumber(octolib.vars.get('tools.trigger.sz')) or 1) / 2 + ) + local pos = tr.HitPos + pos.z = pos.z + v.z + + cam.Start3D() + render.DrawWireframeBox(pos, ang, -v, v, color_white, false) + cam.End3D() + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'OctoTriggers', + Description = L.trigger_description + }) + + octolib.vars.presetManager(self, 'tools.trigger', { + 'tools.trigger.sy', + 'tools.trigger.sx', + 'tools.trigger.sz', + 'tools.trigger.uname', + 'tools.trigger.method', + 'tools.trigger.title', + 'tools.trigger.text', + 'tools.trigger.duration', + 'tools.trigger.times', + 'tools.trigger.mode', + 'tools.trigger.stopsound', + 'tools.trigger.gamesound', + 'tools.trigger.urlsound', + 'tools.trigger.volume', + 'tools.trigger.3d', + 'tools.trigger.bind', + }) + + octolib.vars.checkBox(self, 'tools.trigger.draw', L.show_existing) + + octolib.vars.slider(self, 'tools.trigger.sy', L.width, 1, 1000, 0) + octolib.vars.slider(self, 'tools.trigger.sx', L.depth, 1, 1000, 0) + octolib.vars.slider(self, 'tools.trigger.sz', L.height, 1, 1000, 0) + + self:Help('') + + octolib.vars.textEntry(self, 'tools.trigger.uname', 'Уникальное название триггера') + + octolib.vars.comboBox(self, 'tools.trigger.method', L.trigger_type_notification, { + {L.trigger_chat, 'chat'}, + {L.trigger_center, 'center'}, + {L.notification, 'notify'}, + }) + octolib.vars.textEntry(self, 'tools.trigger.title', L.title2) + local e = octolib.vars.textEntry(self, 'tools.trigger.text', L.text ) + e:SetMultiline(true) + e:SetTall(150) + e:SetContentAlignment(7) + + octolib.vars.slider(self, 'tools.trigger.duration', L.duration_sec, 1, 300, 0) + self:ControlHelp(L.trigger_duration_hint) + + self:Help('') + + octolib.vars.slider(self, 'tools.trigger.times', L.trigger_times, 0, 10, 0) + self:ControlHelp(L.trigger_times_hint) + octolib.vars.comboBox(self, 'tools.trigger.mode', 'Отсчет срабатываний', { + {'Для каждого игрока отдельно', 0}, + {'Для всех игроков вместе', 1}, + }) + + self:Help('') + + octolib.label(self, 'Звук') + octolib.vars.checkBox(self, 'tools.trigger.stopsound', 'Останавливать звук другого триггера') + + octolib.vars.textEntry(self, 'tools.trigger.gamesound', L.game_sound) + self:Button(L.browser_sound, 'wire_sound_browser_open') + local eURL = octolib.vars.textEntry(self, 'tools.trigger.urlsound', L.url_sound) + octolib.vars.slider(self, 'tools.trigger.volume', L.volume, 0, 1, 2) + octolib.vars.checkBox(self, 'tools.trigger.3d', '3D-звук') + octolib.vars.binder(self, 'tools.trigger.bind', L.binder) + + if not LocalPlayer():query(L.permissions_trigger_url) then eURL:SetEnabled(false) end + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + language.Add('Tool.octo_trigger.name', 'OctoTriggers') + language.Add('Tool.octo_trigger.desc', L.trigger_desc) + language.Add('Tool.octo_trigger.left', L.trigger_left) + language.Add('Undone_octo_trigger', L.trigger_undone) + language.Add('Cleanup_octo_triggers', L.triggers) + language.Add('Cleaned_octo_triggers', L.trigger_cleaned) + language.Add('SBoxLimit.octo_trigger', L.trigger_limit) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_trigger_plus.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_trigger_plus.lua new file mode 100644 index 0000000..f5fc25e --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octo_trigger_plus.lua @@ -0,0 +1,172 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = L.trigger .. ' (расширенный)' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, +} + +cleanup.Register('octo_triggers_plus') + +if CLIENT then + octolib.vars.init('tools.trigger+.sx', 100) + octolib.vars.init('tools.trigger+.sy', 100) + octolib.vars.init('tools.trigger+.sz', 100) + octolib.vars.init('tools.trigger+.times', 1) + octolib.vars.init('tools.trigger+.mode', 0) + octolib.vars.init('tools.trigger+.action', {}) +else + CreateConVar('sbox_maxocto_triggers_plus', 10) +end + +function TOOL:LeftClick(tr) + local ply = self:GetOwner() + if not ply:query('DBG: Панель ивентов') then return false end + + if CLIENT then return true end + + ply:GetClientVar({ + 'tools.trigger+.sx', + 'tools.trigger+.sy', + 'tools.trigger+.sz', + 'tools.trigger+.times', + 'tools.trigger+.mode', + 'tools.trigger+.action', + }, function(vars) + if not (istable(vars['tools.trigger+.action']) and vars['tools.trigger+.action'].type) then + return ply:Notify('warning', 'Укажи действие или сценарий из панели игрового мастера') + end + + local v = Vector( + math.Clamp(tonumber(vars['tools.trigger+.sx']) or 1, 1, 1000), + math.Clamp(tonumber(vars['tools.trigger+.sy']) or 1, 1, 1000), + math.Clamp(tonumber(vars['tools.trigger+.sz']) or 1, 1, 1000) + ) + + local ent = ents.Create('octo_trigger_plus') + ent:SetPos(tr.HitPos + Vector(0, 0, v.z / 2)) + ent:SetAngles(Angle()) + ent.size = v + ent.times = tonumber(vars['tools.trigger+.times']) + ent.mode = tonumber(vars['tools.trigger+.mode']) + ent.action = istable(vars['tools.trigger+.action']) and vars['tools.trigger+.action'] or {} + ent:Spawn() + ent:Activate() + + undo.Create('octo_trigger_plus') + undo.AddEntity(ent) + undo.SetPlayer(ply) + undo.Finish() + ply:AddCount('octo_triggers_plus', ent) + ply:AddCleanup('octo_triggers_plus', ent) + end) + + return true +end + +function TOOL:DrawHUD() + + local tr = LocalPlayer():GetEyeTrace() + if not tr.Hit then return end + + local ang = Angle() + local v = Vector( + (tonumber(octolib.vars.get('tools.trigger+.sx')) or 1) / 2, + (tonumber(octolib.vars.get('tools.trigger+.sy')) or 1) / 2, + (tonumber(octolib.vars.get('tools.trigger+.sz')) or 1) / 2 + ) + local pos = tr.HitPos + pos.z = pos.z + v.z + + cam.Start3D() + render.DrawWireframeBox(pos, ang, -v, v, color_white, false) + cam.End3D() + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'OctoTriggers', + Description = 'Привет! Это версия триггера, в котором предусмотрено больше возможностей для срабатывания. Доступен этот инструмент только членам нашей команды' + }) + + local ply = LocalPlayer() + if IsValid(ply) and not ply:query('DBG: Панель ивентов') then return end + + octolib.vars.presetManager(self, 'tools.trigger+', { + 'tools.trigger+.sy', + 'tools.trigger+.sx', + 'tools.trigger+.sz', + 'tools.trigger+.times', + 'tools.trigger+.mode', + 'tools.trigger+.action', + }) + + octolib.vars.checkBox(self, 'tools.trigger+.draw', L.show_existing) + + octolib.vars.slider(self, 'tools.trigger+.sy', L.width, 1, 1000, 0) + octolib.vars.slider(self, 'tools.trigger+.sx', L.depth, 1, 1000, 0) + octolib.vars.slider(self, 'tools.trigger+.sz', L.height, 1, 1000, 0) + + self:Help('') + + octolib.vars.slider(self, 'tools.trigger+.times', L.trigger_times, 0, 10, 0) + self:ControlHelp(L.trigger_times_hint) + octolib.vars.comboBox(self, 'tools.trigger+.mode', 'Отсчет срабатываний', { + {'Для каждого игрока отдельно', 0}, + {'Для всех игроков вместе', 1}, + }) + + self:Help('') + + local actionBtn = octolib.button(self, 'Выбери действие или сценарий', function() + local menu = DermaMenu() + -- scenarios + for _,v in ipairs(gmpanel.scenarios.list) do + menu:AddOption(v._name, function() + octolib.vars.set('tools.trigger+.action', { + type = 'scenario', + obj = v, + }) + end):SetIcon(v._icon) + end + menu:AddSpacer() + -- actions + for _, v in ipairs(gmpanel.actions.added) do + menu:AddOption(v._name, function() + octolib.vars.set('tools.trigger+.action', { + type = 'action', + obj = v, + }) + end):SetIcon(v._icon) + end + menu:Open() + end) + self:ControlHelp('Выбранное действие или сценарий не синхронизируется с существующим! После изменений сценария или действия в панели игрового мастера нужно заново выбрать этот сценарий или действие здесь') + + hook.Add('octolib.setVar', 'tools.trigger+.action', function(var, val) + if var ~= 'tools.trigger+.action' then return end + actionBtn:SetText(val.obj._name) + actionBtn:SetImage(val.obj._icon) + end) + + self:Help('') + self:Help('В связи с особенностями архитектуры игровой панели и триггеров, действия будут выполняться только когда тот, кто его установил, на сервере') + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + language.Add('Tool.octo_trigger_plus.name', 'OctoTriggers+') + language.Add('Tool.octo_trigger_plus.desc', L.trigger_desc .. ', версия для администраторов и игровых мастеров') + language.Add('Tool.octo_trigger_plus.left', L.trigger_left) + language.Add('Undone_octo_trigger_plus', L.trigger_undone) + language.Add('Cleanup_octo_triggers_plus', L.triggers_plus) + language.Add('Cleaned_octo_triggers_plus', L.trigger_cleaned) + language.Add('SBoxLimit.octo_trigger_plus', L.trigger_limit) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octoinv_cont.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octoinv_cont.lua new file mode 100644 index 0000000..a385ff8 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/octoinv_cont.lua @@ -0,0 +1,351 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = L.inventory +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, + { name = 'reload' }, +} + +octolib.vars.init('tools.octoinv.data', {}) +octolib.vars.init('tools.octoinv.password', '') +octolib.vars.init('tools.octoinv.soundURL', '') +octolib.vars.init('tools.octoinv.soundFile', octolib.vars.get('tools.octoinv.sound') or '') + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +local function doConts(ent, data) + + ent:ImportInventory(data) + ent.lootable = true + if not ent:GetNetVar('dbgLook') then + ent:SetNetVar('dbgLook', { + name = '', + desc = L.can_be_searched, + time = 1, + }) + end + +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) or ent:IsPlayer() or string.StartWith(ent:GetClass(), 'octoinv_') then return false end + + if SERVER then + local ply = self:GetOwner() + if not ply:query(L.permissions_create_prop_inventory) then + ply:Notify('warning', L.no_access_create_inventory) + return false + end + + ply:GetClientVar({ 'tools.octoinv.data', 'tools.octoinv.time', 'tools.octoinv.soundURL', 'tools.octoinv.soundFile', 'tools.octoinv.period', 'tools.octoinv.password' }, function(vars) + local data = vars['tools.octoinv.data'] + if #data < 1 then return end + + local inv, time = {}, vars['tools.octoinv.time'] + for i, cont in ipairs(data) do + if not cont.id then return end + inv[cont.id] = { + name = cont.name or L.container, + icon = cont.icon or 'octoteam/icons/box.png', + volume = cont.volume or 10, + craft = cont.craft, + } + end + + local soundURL, soundFile, period = string.Trim(vars['tools.octoinv.soundURL']), string.Trim(vars['tools.octoinv.soundFile']) + if soundURL == '' then soundURL = nil end + if soundFile == '' then soundFile = nil end + if soundURL or soundFile then + period = math.Clamp(isnumber(vars['tools.octoinv.period']) and vars['tools.octoinv.period'] or 1, 1, 100) + end + + if not ply:query('DBG: Изменять звук обыска') then + soundURL, soundFile, period = nil + end + + local password = string.Trim(vars['tools.octoinv.password'] or '') + if password == '' then password = nil end + + if ent.inv then ent.inv:Remove() end + doConts(ent, inv) + doEffect(ent) + ent.lootData = { + time = time, + soundURL = soundURL, + soundFile = soundFile, + period = period, + } + ent.password = password + duplicator.StoreEntityModifier(ent, 'octoinv', { + lootTime = time, + lootSoundURL = soundURL, + lootSoundFile = soundFile, + lootPeriod = period, + inv = inv, + password = password, + }) + end) + end + + return true + +end + +-- function TOOL:RightClick(tr) + +-- if SERVER then return false end + +-- local ent = tr.Entity +-- local look = IsValid(ent) and ent:GetNetVar('dbgLook') +-- if not look then return false end + +-- octolib.vars.set('tools.desc.name', look.name) + +-- return true + +-- end + +function TOOL:Reload(tr) + + if CLIENT then return true end + + local ply = self:GetOwner() + if not ply:query(L.permissions_create_prop_inventory) then + ply:Notify('warning', L.no_access_create_inventory) + return false + end + + local ent = tr.Entity + if not IsValid(ent) or ent:IsPlayer() or string.StartWith(ent:GetClass(), 'octoinv_') or not ent.inv then return false end + + ent.inv:Remove() + doEffect(ent) + ent.lootable = nil + ent.lootData = nil + ent.password = nil + duplicator.ClearEntityModifier(ent, 'octoinv') + + local look = ent:GetNetVar('dbgLook') + if look and look.desc == L.can_be_searched then + ent:SetNetVar('dbgLook', nil) + end + + return true + +end + +local contList +local function rebuildList() + + if not IsValid(contList) then return end + contList:Clear() + + local contData = octolib.vars.get('tools.octoinv.data') + contData = contData and table.Copy(contData) or {} + + local bSave = vgui.Create 'DButton' + bSave:SetParent(contList) + bSave:Dock(TOP) + bSave:SetTall(0) + bSave:DockMargin(0, 0, 0, 5) + bSave:SetText(L.save_changes) + function bSave:DoClick() + octolib.vars.set('tools.octoinv.data', contData) + end + + local active = false + local function changed() + if active then return end + active = true + bSave:SizeTo(bSave:GetWide(), 30, 0.5, 0, -1) + end + + for i, cont in ipairs(contData) do + local cp = contList:Add(cont.name) + + local del = cp.Header:Add 'DButton' + del:SetPaintBackground(false) + del:SetText('') + del:SetSize(24, 20) + del:SetIcon('icon16/cross.png') + del:SetToolTip(L.delete_container) + function del:DoClick() + table.remove(contData, i) + octolib.vars.set('tools.octoinv.data', contData) + end + + function cp.Header:PerformLayout() + del:AlignRight() + end + + local p = vgui.Create 'DPanel' + p:DockPadding(5, 5, 5, 5) + + local e1 = octolib.textEntry(p, L.title) + e1:SetValue(cont.name) + e1:SetUpdateOnType(true) + function e1:OnValueChange(val) cont.name = val changed() end + + local e2 = octolib.textEntry(p, L.icon) + e2:SetValue(cont.icon) + e2:SetUpdateOnType(true) + function e2:OnValueChange(val) cont.icon = val changed() end + + local ib = e2:Add 'DButton' + ib:Dock(RIGHT) + ib:SetWide(25) + ib:SetText('') + ib:SetIcon('icon16/color_swatch.png') + ib:SetPaintBackground(false) + function ib:DoClick() + if IsValid(self.picker) then return end + self.picker = octolib.icons.picker(function(val) e2:SetValue(val) end, e2:GetValue()) + end + + local e3 = octolib.slider(p, L.inventory_volume, 0.1, 1000, 1) + e3:SetValue(cont.volume) + function e3:OnValueChanged(val) cont.volume = math.Round(val, 1) changed() end + + local e4 = octolib.checkBox(p, L.can_craft) + e4:SetValue(cont.craft) + function e4:OnChange(val) cont.craft = val and true or nil changed() end + + cp:SetContents(p) + end + + local bAdd = vgui.Create 'DButton' + bAdd:SetParent(contList) + bAdd:Dock(TOP) + bAdd:SetTall(30) + bAdd:DockMargin(0, 5, 0, 0) + bAdd:SetText(L.add_container) + function bAdd:DoClick() + local i = #contData + 1 + contData[i] = { + name = L.container .. i, + icon = 'octoteam/icons/box1.png', + volume = 10, + } + + for i, cont in ipairs(contData) do + cont.id = 'cont' .. i + end + + octolib.vars.set('tools.octoinv.data', contData) + end + +end + +hook.Add('octolib.setVar', 'tools.octoinv', function(var, val) + if var == 'tools.octoinv.data' then rebuildList() end +end) + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = L.inventory, + Description = L.description_inventory + }) + + octolib.vars.slider(self, 'tools.octoinv.time', L.search_time, 0, 20, 1) + + local ply = LocalPlayer() + if ply:query('DBG: Изменять звук обыска') then + + local tabs = vgui.Create('DPropertySheet') + self:AddItem(tabs) + tabs:Dock(TOP) + tabs:SetTall(90) + + local filePan = tabs:Add('DPanel') + filePan:SetPaintBackground(false) + octolib.vars.textEntry(filePan, 'tools.octoinv.soundFile') + octolib.button(filePan, L.browser_sound, function() RunConsoleCommand('wire_sound_browser_open') end) + filePan = tabs:AddSheet('Внутриигровой', filePan, 'icon16/cross.png').Tab + + local urlPan = tabs:Add('DPanel') + urlPan:SetPaintBackground(false) + octolib.vars.textEntry(urlPan, 'tools.octoinv.soundURL') + urlPan = tabs:AddSheet('По URL', urlPan, 'icon16/cross.png').Tab + + function tabs:OnActiveTabChanged(old, new) + if new == filePan then + octolib.vars.set('tools.octoinv.soundURL', '') + else + octolib.vars.set('tools.octoinv.soundFile', '') + end + end + + if octolib.vars.get('tools.octoinv.soundURL') ~= '' then tabs:SetActiveTab(urlPan) end + + octolib.vars.slider(self, 'tools.octoinv.period', 'Период', 1, 100, 0) + self:Help('Период воспроизведения звука измеряется в количестве воспроизведений анимации обыска, например:\n 1 - воспроизводить каждый раз вместе с анимацией обыска;\n 2 - воспроизводить через раз.\nЕсли звук указан, он обязательно воспроизведётся при первой анимации обыска вне зависимости от значения периода') + + end + + octolib.vars.textEntry(self, 'tools.octoinv.password', 'Пароль') + + local p = vgui.Create 'DCategoryList' + self:AddItem(p) + p:SetTall(ScrH() - 120) + + contList = p + rebuildList() + +end + +if SERVER then + + local lootDistSqr = CFG.useDist * CFG.useDist + + duplicator.RegisterEntityModifier('octoinv', function(ply, ent, data) + if IsValid(ply) and not ply:query('DBG: Создавать инвентарь у предмета') then return end + doConts(ent, data.inv) + if not IsValid(ply) or ply:query('DBG: Изменять звук обыска') then + ent.lootData = { + time = data.lootTime, + soundURL = data.lootSoundURL, + soundFile = data.lootSoundFile, + period = data.lootPeriod, + } + else + ent.lootData = { time = data.lootTime } + end + ent.password = data.password + end) + + hook.Add('EntityRemoved', 'tools.octoinv', function(entity) + + if not entity.lootable then + return + end + + local entityInventory = entity:GetInventory() + if not entityInventory then + return + end + + entityInventory:Remove(true) + + end, -1) + +else + + language.Add('Tool.octoinv_cont.name', L.inventory) + language.Add('Tool.octoinv_cont.desc', L.add_container_to_prop) + language.Add('Tool.octoinv_cont.left', L.assign) + language.Add('Tool.octoinv_cont.right', L.tool_copy) + language.Add('Tool.octoinv_cont.reload', L.remove) + +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol.lua new file mode 100644 index 0000000..4c8f209 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol.lua @@ -0,0 +1,797 @@ +TOOL.Category = "Particle Controller" +TOOL.Name = "Adv. Particle Control" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.HighlightedEnt = nil + +TOOL.ClientConVar[ "effectname" ] = "env_fire_large" +TOOL.ClientConVar[ "mode_beam" ] = "0" +TOOL.beamattach1 = 0 +TOOL.beamattach2 = 0 +TOOL.ClientConVar[ "utileffect_scale" ] = "1" +TOOL.ClientConVar[ "utileffect_magnitude" ] = "1" +TOOL.ClientConVar[ "utileffect_radius" ] = "10" +TOOL.ClientConVar[ "color_enabled" ] = "0" +TOOL.ClientConVar[ "color_r" ] = "255" +TOOL.ClientConVar[ "color_g" ] = "20" +TOOL.ClientConVar[ "color_b" ] = "0" +TOOL.ClientConVar[ "color_outofone" ] = "0" + +TOOL.ClientConVar[ "attachnum" ] = "0" +TOOL.ClientConVar[ "repeatrate" ] = "0" +TOOL.ClientConVar[ "repeatsafety" ] = "1" + +TOOL.ClientConVar[ "propmodel" ] = "models/hunter/plates/plate.mdl" +TOOL.ClientConVar[ "propangle" ] = "1" +TOOL.ClientConVar[ "propinvis" ] = "0" + +TOOL.ClientConVar[ "numpadkey" ] = "52" +TOOL.ClientConVar[ "toggle" ] = "1" +TOOL.ClientConVar[ "starton" ] = "1" + +TOOL.Information = { + { name = "info1", stage = 1, icon = "gui/info.png" }, + { name = "left0", stage = 0, icon = "gui/lmb.png" }, + { name = "left1", stage = 1, icon = "gui/lmb.png" }, + { name = "middle01", icon = "gui/mmb.png" }, + { name = "right0", stage = 0, icon = "gui/rmb.png" }, + { name = "right1", stage = 1, icon = "gui/rmb.png" }, + { name = "reload0", stage = 0, icon = "gui/r.png" }, + { name = "reload1", stage = 1, icon = "gui/r.png" }, +} + +if ( CLIENT ) then + language.Add( "tool.particlecontrol.name", "Advanced Particle Controller" ) + language.Add( "tool.particlecontrol.desc", "Attach particle effects to things" ) + language.Add( "tool.particlecontrol.help", "Particles are used for all sorts of different special effects. You can attach them to models and turn them on and off with a key." ) + + language.Add( "tool.particlecontrol.info1", "BEAM EFFECT: Attaches to two points" ) + language.Add( "tool.particlecontrol.left0", "Add an effect to an object" ) + language.Add( "tool.particlecontrol.left1", "Add the other end to an object" ) + language.Add( "tool.particlecontrol.middle01", "Scroll through an object's attachments" ) + language.Add( "tool.particlecontrol.right0", "Attach a new prop with the effect on it" ) + language.Add( "tool.particlecontrol.right1", "Attach a new prop and add the other end to it" ) + language.Add( "tool.particlecontrol.reload0", "Remove all effects from an object" ) + language.Add( "tool.particlecontrol.reload1", "Cancel beam effect" ) +end + +util.PrecacheSound("weapons/pistol/pistol_empty.wav") + + + + +function TOOL:LeftClick( trace ) + + local effectname = self:GetClientInfo( "effectname", 0 ) + local attachnum = self:GetClientNumber( "attachnum", 0 ) + + local repeatrate = self:GetClientNumber( "repeatrate", 0 ) + local repeatsafety = self:GetClientNumber( "repeatsafety", 0 ) + + local numpadkey = self:GetClientNumber( "numpadkey", 0 ) + local toggle = self:GetClientNumber( "toggle", 0 ) + local starton = self:GetClientNumber( "starton", 0 ) + + local utileffectinfo = Vector( self:GetClientNumber( "utileffect_scale", 0 ), self:GetClientNumber( "utileffect_magnitude", 0 ), self:GetClientNumber( "utileffect_radius", 0 ) ) + local colorinfo = nil + if self:GetClientNumber( "color_enabled", 0 ) == 1 then + if self:GetClientNumber( "color_outofone", 0 ) == 1 then + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 0 ) + end + end + + local ply = self:GetOwner() + + + + if self:GetClientNumber( "mode_beam", 0 ) == 0 then + //Not a beam, attach the effect to one entity + if ( trace.Entity:IsValid() ) then + if CLIENT then return true end + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + AttachParticleControllerNormal( ply, trace.Entity, { NewTable = { + EffectName = effectname, + AttachNum = attachnum, + + RepeatRate = repeatrate, + RepeatSafety = repeatsafety, + + Toggle = toggle, + StartOn = starton, + NumpadKey = numpadkey, + + UtilEffectInfo = utileffectinfo, + ColorInfo = colorinfo + } } ) + return true + end + else + //It's a beam, attach the effect between two entities + local iNum = self:NumObjects() + + if ( trace.Entity:IsValid() ) then + //if CLIENT then return true end + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + + self:SetObject( iNum + 1, trace.Entity, trace.HitPos, nil, nil, trace.HitNormal ) + if iNum == 0 then + self.beamattach1 = attachnum + else + self.beamattach2 = attachnum + end + + if ( iNum > 0 ) then + if ( CLIENT ) then + self:ClearObjects() + return true + end + + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + local constraint = constraint.AttachParticleControllerBeam( Ent1, Ent2, { + EffectName = effectname, + AttachNum = self.beamattach1, + AttachNum2 = self.beamattach2, + + RepeatRate = repeatrate, + RepeatSafety = repeatsafety, + + Toggle = toggle, + StartOn = starton, + NumpadKey = numpadkey, + + UtilEffectInfo = utileffectinfo, + ColorInfo = colorinfo + }, ply ) + + self:ClearObjects() + else + self:SetStage( iNum+1 ) + end + + return true + end + end + +end + + + + +function TOOL:RightClick( trace ) + + local effectname = self:GetClientInfo( "effectname", 0 ) + local attachnum = self:GetClientNumber( "attachnum", 0 ) + + local repeatrate = self:GetClientNumber( "repeatrate", 0 ) + local repeatsafety = self:GetClientNumber( "repeatsafety", 0 ) + + local numpadkey = self:GetClientNumber( "numpadkey", 0 ) + local toggle = self:GetClientNumber( "toggle", 0 ) + local starton = self:GetClientNumber( "starton", 0 ) + + local utileffectinfo = Vector( self:GetClientNumber( "utileffect_scale", 0 ), self:GetClientNumber( "utileffect_magnitude", 0 ), self:GetClientNumber( "utileffect_radius", 0 ) ) + local colorinfo = nil + if self:GetClientNumber( "color_enabled", 0 ) == 1 then + if self:GetClientNumber( "color_outofone", 0 ) == 1 then + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 0 ) + end + end + + local ply = self:GetOwner() + + + + local propmodel = self:GetClientInfo( "propmodel", 0 ) + local propangle = self:GetClientNumber( "propangle", 0 ) + //propangle 1: spawn upright + //propangle 2: spawn at surface angle + + if !util.IsValidModel(propmodel) then return false end + if !util.IsValidProp(propmodel) then return false end + if CLIENT then return true end + + prop = ents.Create( "prop_physics" ) + prop:SetModel( propmodel ) + prop:SetPos( trace.HitPos - trace.HitNormal * prop:OBBMins().z ) + if propangle == 1 then prop:SetAngles(Angle(0,trace.HitNormal:Angle().y,0)) else prop:SetAngles(trace.HitNormal:Angle()) end + prop:SetCollisionGroup(20) //COLLISION_GROUP_NONE, nocollide with everything except world + prop:Spawn() + + local shouldweweld = true //don't weld if... + if ( !util.IsValidPhysicsObject(prop, 0) ) then shouldweweld = false end //the prop doesn't have a phys object + if ( !trace.Entity:IsValid() ) then shouldweweld = false end //the thing we clicked on doesn't exist/is the world + if ( trace.Entity && trace.Entity:IsPlayer() ) then shouldweweld = false end //the thing we clicked on is a player + if ( !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then shouldweweld = false end //the thing we clicked on doesn't have a phys object + if shouldweweld == true then + local const = constraint.Weld( prop, trace.Entity, 0, trace.PhysicsBone, 0, true, true ) + else + if util.IsValidPhysicsObject(prop, 0) then prop:GetPhysicsObject():EnableMotion(false) end + end + + if self:GetClientNumber( "propinvis", 0 ) == 1 then + prop:SetRenderMode(1) //we need to change the render mode so the transparency actually shows up + prop:SetColor( Color(255,255,255,0) ) + duplicator.StoreEntityModifier( prop, "colour", { Color = Color(255,255,255,0), RenderMode = 1, RenderFX = 0 } ) + end + + undo.Create( "prop" ) + undo.AddEntity( prop ) + undo.SetPlayer( ply ) + undo.Finish( "Prop ("..tostring(propmodel)..")" ) + + + + if ( !prop:IsValid() ) then return false end + if self:GetClientNumber( "mode_beam", 0 ) == 0 then + //Not a beam, attach the effect to one entity. + AttachParticleControllerNormal( ply, prop, { NewTable = { + EffectName = effectname, + AttachNum = attachnum, + + RepeatRate = repeatrate, + RepeatSafety = repeatsafety, + + Toggle = toggle, + StartOn = starton, + NumpadKey = numpadkey, + + UtilEffectInfo = utileffectinfo, + ColorInfo = colorinfo + } } ) + else + //It's a beam, attach the effect between two entities + local iNum = self:NumObjects() + + self:SetObject( iNum + 1, prop, trace.HitPos, nil, nil, trace.HitNormal ) + if iNum == 0 then + self.beamattach1 = attachnum + else + self.beamattach2 = attachnum + end + + if ( iNum > 0 ) then + if ( CLIENT ) then + self:ClearObjects() + return true + end + + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + local constraint = constraint.AttachParticleControllerBeam( Ent1, Ent2, { + EffectName = effectname, + AttachNum = self.beamattach1, + AttachNum2 = self.beamattach2, + + RepeatRate = repeatrate, + RepeatSafety = repeatsafety, + + Toggle = toggle, + StartOn = starton, + NumpadKey = numpadkey, + + UtilEffectInfo = utileffectinfo, + ColorInfo = colorinfo + }, ply ) + + self:ClearObjects() + else + self:SetStage( iNum+1 ) + end + + return true + end + + return true + +end + + + + +function TOOL:Reload( trace ) + + //if we've selected something for a beam effect, then let us deselect it with reload + if self:GetClientNumber( "mode_beam", 0 ) != 0 and self:NumObjects() > 0 then + //self:GetEnt(1) + self:ClearObjects() + self:SetStage(0) + return true + end + + if ( trace.Entity:IsValid() ) then + local fx = false + + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + + for _, asdf in pairs( ents:GetAll() ) do + if asdf:GetClass() == "particlecontroller_normal" then + if asdf:GetParent() == trace.Entity then + if SERVER then asdf:Remove() end + fx = true + end + if IsValid(asdf:GetTargetEnt2()) then + if asdf:GetTargetEnt2() == trace.Entity or asdf:GetTargetEnt2():GetParent() == trace.Entity then + if SERVER then asdf:Remove() end + fx = true + end + end + end + end + if SERVER then + duplicator.ClearEntityModifier( trace.Entity, "DupeParticleControllerNormal" ) + constraint.RemoveConstraints( trace.Entity, "AttachParticleControllerBeam" ) + end + + return fx + end + +end + + + + +if CLIENT then + + local colorborder = Color(0,0,0,255) + local colorselect = Color(0,255,0,255) + local colorunselect = Color(255,255,255,255) + + function TOOL:DrawHUD() + local pl = LocalPlayer() + local tr = pl:GetEyeTrace() + local attachnum = self:GetClientNumber( "attachnum", 0 ) + + local function DrawHighlightAttachments(ent) + + //If there aren't any attachments, then draw the model origin as selected and stop here: + if !ent:GetAttachments() or !ent:GetAttachments()[1] then + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + + return + end + + + //Draw the unselected model origin, if applicable: + if ent:GetAttachments()[attachnum] then + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder) + end + + //Draw the unselected attachment points: + for _, table in pairs(ent:GetAttachments()) do + local _pos,_ang = ent:GetAttachment(table.id).Pos,ent:GetAttachment(table.id).Ang + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + if table.id != attachnum then + draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect) + draw.SimpleTextOutlined(table.id ..": ".. table.name,"Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder) + end + end + + //Draw the selected attachment point or model origin last, so it renders above all the others: + if !ent:GetAttachments()[attachnum] then + //Model origin + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + else + //Attachment + local _pos,_ang = ent:GetAttachment(attachnum).Pos,ent:GetAttachment(attachnum).Ang + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined(attachnum ..": ".. ent:GetAttachments()[attachnum].name,"Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + end + end + + if IsValid(tr.Entity) and tr.Entity == self.HighlightedEnt then + DrawHighlightAttachments(self.HighlightedEnt) + return end + + if IsValid(tr.Entity) and tr.Entity != self.HighlightedEnt then + //unhighlight the old ent if it exists + if self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + + //highlight the new ent + self.HighlightedEnt = tr.Entity + end + + if !IsValid(tr.Entity) and self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + end + + function TOOL:Holster() + if self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + end + + + + + //All credit for the toolgun scroll wheel code goes to the Wiremod devs. You guys are the best. + local function get_active_tool(ply, tool) + -- find toolgun + local activeWep = ply:GetActiveWeapon() + if not IsValid(activeWep) or activeWep:GetClass() ~= "gmod_tool" or activeWep.Mode ~= tool then return end + + return activeWep:GetToolObject(tool) + end + + local function hookfunc( ply, bind, pressed ) + if not pressed then return end + if bind == "invnext" then + local self = get_active_tool(ply, "particlecontrol") + if not self then return end + + return self:ScrollDown(ply:GetEyeTraceNoCursor()) + elseif bind == "invprev" then + local self = get_active_tool(ply, "particlecontrol") + if not self then return end + + return self:ScrollUp(ply:GetEyeTraceNoCursor()) + end + end + + if game.SinglePlayer() then -- wtfgarry (have to have a delay in single player or the hook won't get added) + timer.Simple(5,function() hook.Add( "PlayerBindPress", "particlecontrol_playerbindpress", hookfunc ) end) + else + hook.Add( "PlayerBindPress", "particlecontrol_playerbindpress", hookfunc ) + end + //End shamefully copied code here. + + function TOOL:Scroll(trace,dir) + if !IsValid(self.HighlightedEnt) then return end + + local attachcount = 0 + if self.HighlightedEnt:GetAttachments() then attachcount = table.Count(self.HighlightedEnt:GetAttachments()) end + local oldattachnum = self:GetClientNumber( "attachnum", 0 ) + if oldattachnum > attachcount then oldattachnum = 0 end + local attachnum = oldattachnum + dir + + if attachnum < 0 then attachnum = attachcount end + if attachnum > attachcount then attachnum = 0 end + RunConsoleCommand("particlecontrol_attachnum", tostring(attachnum)) + self:GetOwner():EmitSound("weapons/pistol/pistol_empty.wav") + return true + end + function TOOL:ScrollUp(trace) return self:Scroll(trace,-1) end + function TOOL:ScrollDown(trace) return self:Scroll(trace,1) end + +end + + + + +if SERVER then + + local function SpawnParticleControllerNormal(ply, ent, DataTable) + + if DataTable == nil or DataTable == {} or DataTable.EffectName == nil or ent == nil or !IsValid(ent) then return end + + + local ParticleControlNormal = ents.Create( "particlecontroller_normal" ) + ParticleControlNormal:SetPos(ent:GetPos()) + ParticleControlNormal:SetAngles(ent:GetAngles()) + ParticleControlNormal:SetParent(ent) + ent:DeleteOnRemove(ParticleControlNormal) + + ParticleControlNormal:SetTargetEnt(ent) + ParticleControlNormal:SetEffectName(DataTable.EffectName) + ParticleControlNormal:SetAttachNum(DataTable.AttachNum) + ParticleControlNormal:SetUtilEffectInfo(DataTable.UtilEffectInfo) + if DataTable.ColorInfo != nil then ParticleControlNormal:SetColor(DataTable.ColorInfo) else ParticleControlNormal:SetColor( Color(0,0,0,0) ) end + + ParticleControlNormal:SetRepeatRate(DataTable.RepeatRate) + if DataTable.RepeatSafety == 1 or DataTable.RepeatSafety == true then ParticleControlNormal:SetRepeatSafety(true) else ParticleControlNormal:SetRepeatSafety(false) end + + + if DataTable.StartOn == 1 or DataTable.StartOn == true then ParticleControlNormal:SetActive(true) else ParticleControlNormal:SetActive(false) end + if DataTable.Toggle == 1 or DataTable.Toggle == true then ParticleControlNormal:SetToggle(true) else ParticleControlNormal:SetToggle(false) end + ParticleControlNormal:SetNumpadKey(DataTable.NumpadKey) + + numpad.OnDown( ply, DataTable.NumpadKey, "Particle_Press", ParticleControlNormal ) + numpad.OnUp( ply, DataTable.NumpadKey, "Particle_Release", ParticleControlNormal ) + ParticleControlNormal:SetNumpadState("") + + + ParticleControlNormal:Spawn() + ParticleControlNormal:Activate() + + end + + + function AttachParticleControllerNormal( ply, ent, Data ) + + if Data.NewTable then + SpawnParticleControllerNormal(ply, ent, Data.NewTable) + + local dupetable = {} + if ent.EntityMods and ent.EntityMods.DupeParticleControllerNormal then dupetable = ent.EntityMods.DupeParticleControllerNormal end + table.insert(dupetable, Data.NewTable) + duplicator.StoreEntityModifier( ent, "DupeParticleControllerNormal", dupetable ) + return end + + end + + + function DupeParticleControllerNormal( ply, ent, Data ) + + local override = hook.Run('CanTool', ply, { Entity = ent }, 'particlecontrol') + if override == false then return end + + //due to a problem with the easy bonemerge tool that causes entity modifiers to be applied TWICE, we need to remove the effects that were added the first time + for _, asdf in pairs( ents:GetAll() ) do + if asdf:GetClass() == "particlecontroller_normal" and asdf:GetParent() == ent then + asdf:Remove() + end + end + + for _, DataTable in pairs (Data) do + SpawnParticleControllerNormal(ply, ent, DataTable) + end + + end + duplicator.RegisterEntityModifier( "DupeParticleControllerNormal", DupeParticleControllerNormal ) + + + + + //we have to redefine some of the constraint functions here because they're local functions that don't exist outside of constraints.lua + //not sure how well these'll work, one of them is ripped straight from the nocollide world tool which uses the same trick for its custom constraints + local MAX_CONSTRAINTS_PER_SYSTEM = 100 + local function CreateConstraintSystem() + local System = ents.Create("phys_constraintsystem") + if !IsValid(System) then return end + System:SetKeyValue("additionaliterations", GetConVarNumber("gmod_physiterations")) + System:Spawn() + System:Activate() + return System + end + local function FindOrCreateConstraintSystem( Ent1, Ent2 ) + local System = nil + Ent2 = Ent2 or Ent1 + -- Does Ent1 have a constraint system? + if ( !Ent1:IsWorld() && Ent1:GetTable().ConstraintSystem && Ent1:GetTable().ConstraintSystem:IsValid() ) then + System = Ent1:GetTable().ConstraintSystem + end + -- Don't add to this system - we have too many constraints on it already. + if ( System && System:IsValid() && System:GetVar( "constraints", 0 ) > MAX_CONSTRAINTS_PER_SYSTEM ) then System = nil end + -- Does Ent2 have a constraint system? + if ( !System && !Ent2:IsWorld() && Ent2:GetTable().ConstraintSystem && Ent2:GetTable().ConstraintSystem:IsValid() ) then + System = Ent2:GetTable().ConstraintSystem + end + -- Don't add to this system - we have too many constraints on it already. + if ( System && System:IsValid() && System:GetVar( "constraints", 0 ) > MAX_CONSTRAINTS_PER_SYSTEM ) then System = nil end + -- No constraint system yet (Or they're both full) - make a new one + if ( !System || !System:IsValid() ) then + --Msg("New Constrant System\n") + System = CreateConstraintSystem() + end + Ent1.ConstraintSystem = System + Ent2.ConstraintSystem = System + System.UsedEntities = System.UsedEntities or {} + table.insert( System.UsedEntities, Ent1 ) + table.insert( System.UsedEntities, Ent2 ) + local ConstraintNum = System:GetVar( "constraints", 0 ) + System:SetVar( "constraints", ConstraintNum + 1 ) + --Msg("System has "..tostring( System:GetVar( "constraints", 0 ) ).." constraints\n") + return System + end + //end ripped constraint functions here. + + //multiple-point "beam" effects use a constraint so the duplicator can group the two entities together + function constraint.AttachParticleControllerBeam( Ent1, Ent2, Data, ply ) + if !Ent1 or !Ent2 then return end + + //onStartConstraint( Ent1, Ent2 ) + local system = FindOrCreateConstraintSystem( Ent1, Ent2 ) + SetPhysConstraintSystem( system ) + + //create a dummy ent for the constraint functions to use + local Constraint = ents.Create("logic_collision_pair") + Constraint:Spawn() + Constraint:Activate() + + + + local ParticleControlBeam = ents.Create( "particlecontroller_normal" ) + ParticleControlBeam:SetPos(Ent1:GetPos()) + ParticleControlBeam:SetAngles(Ent1:GetAngles()) + ParticleControlBeam:SetParent(Ent1) + Ent1:DeleteOnRemove(ParticleControlBeam) + Ent2:DeleteOnRemove(ParticleControlBeam) + + ParticleControlBeam:SetTargetEnt(Ent1) + ParticleControlBeam:SetTargetEnt2(Ent2) + ParticleControlBeam:SetEffectName(Data.EffectName) + ParticleControlBeam:SetAttachNum(Data.AttachNum) + ParticleControlBeam:SetAttachNum2(Data.AttachNum2) + ParticleControlBeam:SetUtilEffectInfo(Data.UtilEffectInfo) + if Data.ColorInfo != nil then ParticleControlBeam:SetColor(Data.ColorInfo) else ParticleControlBeam:SetColor( Color(0,0,0,0) ) end + + ParticleControlBeam:SetRepeatRate(Data.RepeatRate) + if Data.RepeatSafety == 1 or Data.RepeatSafety == true then ParticleControlBeam:SetRepeatSafety(true) else ParticleControlBeam:SetRepeatSafety(false) end + + if Data.StartOn == 1 or Data.StartOn == true then ParticleControlBeam:SetActive(true) else ParticleControlBeam:SetActive(false) end + if Data.Toggle == 1 or Data.Toggle == true then ParticleControlBeam:SetToggle(true) else ParticleControlBeam:SetToggle(false) end + ParticleControlBeam:SetNumpadKey(Data.NumpadKey) + + numpad.OnDown( ply, Data.NumpadKey, "Particle_Press", ParticleControlBeam ) + numpad.OnUp( ply, Data.NumpadKey, "Particle_Release", ParticleControlBeam ) + ParticleControlBeam:SetNumpadState("") + + ParticleControlBeam:Spawn() + ParticleControlBeam:Activate() + + + + //onFinishConstraint( Ent1, Ent2 ) + SetPhysConstraintSystem( NULL ) + + constraint.AddConstraintTable( Ent1, Constraint, Ent2 ) + + local ctable = + { + Type = "AttachParticleControllerBeam", + Ent1 = Ent1, + Ent2 = Ent2, + Data = Data, + ply = ply, + } + + Constraint:SetTable( ctable ) + + return Constraint + end + duplicator.RegisterConstraint( "AttachParticleControllerBeam", constraint.AttachParticleControllerBeam, "Ent1", "Ent2", "Data", "ply" ) + +end + + + + +//we're still testing out a lot of stuff with the cpanel, so let's add a way to refresh it by reselecting the tool +--[[ +TOOL.ClientConVar[ "refresh" ] = 1 +function TOOL:Think() + if SERVER then return end + if self:GetClientNumber("refresh") == 1 then + RunConsoleCommand("particlecontrol_refresh", "0"); + //refresh the cpanel + local panel = controlpanel.Get( "particlecontrol" ) + if ( !panel ) then return end + panel:ClearControls() + self.BuildCPanel(panel) + end +end +function TOOL:Deploy() + RunConsoleCommand("particlecontrol_refresh", "1"); +end +]] + +local ConVarsDefault = TOOL:BuildConVarList() +ConVarsDefault["particlecontrol_attachnum"] = nil //don't save the attachnum in presets, it's used by the other tools too + +function TOOL.BuildCPanel(panel) + + panel:AddControl( "Header", { Description = "#tool.particlecontrol.help" } ) + + //Presets + panel:AddControl( "ComboBox", { + MenuButton = 1, + Folder = "particlecontrol", + Options = { + [ "#preset.default" ] = ConVarsDefault + }, + CVars = table.GetKeys( ConVarsDefault ) + } ) + + + + AddParticleBrowser(panel, { + name = "Effect", + commands = { + effectname = "particlecontrol_effectname", + mode_beam = "particlecontrol_mode_beam", + color = "particlecontrol_color", + utileffect = "particlecontrol_utileffect", + }, + }) + + + + //panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl("Slider", { + Label = "Attachment", + Type = "Integer", + Min = "0", + Max = "10", + Command = "particlecontrol_attachnum", + }) + panel:ControlHelp( "Attachment point on the model to use. Set to 0 to attach to the model origin or to attach model-covering effects to the entire model." ) + + panel:AddControl("Slider", { + Label = "Repeat Rate", + Type = "Float", + Min = "0", + Max = "5", + Command = "particlecontrol_repeatrate", + }) + panel:ControlHelp( "How often the effect plays. Set to 0 to not repeat." ) + + panel:AddControl( "CheckBox", { Label = "Repeat Safety", Command = "particlecontrol_repeatsafety" } ) + panel:ControlHelp( "If on, effects are removed before being repeated. This stops them from piling up endlessly, but can also cause small problems, like effects being cut off. You should probably keep this on unless you know what you're doing." ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + local modellist = { Label = "Prop", ConVar = "particlecontrol_propmodel", Category = "Prop", Height = 1, Models = {} } + modellist.Models["models/hunter/plates/plate025x025.mdl"] = {} + modellist.Models["models/hunter/plates/plate.mdl"] = {} + modellist.Models["models/weapons/w_smg1.mdl"] = {} + modellist.Models["models/props_junk/popcan01a.mdl"] = {} + panel:AddControl( "PropSelect", modellist ) + + panel:AddControl( "ComboBox", { + Label = "Prop Angle", + MenuButton = "0", + Options = { + ["Spawn upright"] = { particlecontrol_propangle = "1" }, + ["Spawn at surface angle"] = { particlecontrol_propangle = "2" } + } + }) + + panel:AddControl( "CheckBox", { Label = "Invisible prop (particles only)", Command = "particlecontrol_propinvis" } ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl( "Numpad", { + Label = "Effect Key", + Command = "particlecontrol_numpadkey", + }) + + panel:AddControl( "CheckBox", { Label = "Toggle", Command = "particlecontrol_toggle" } ) + + panel:AddControl( "CheckBox", { Label = "Start on?", Command = "particlecontrol_starton" } ) + +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol_proj.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol_proj.lua new file mode 100644 index 0000000..9dba315 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol_proj.lua @@ -0,0 +1,885 @@ +TOOL.Category = "Particle Controller" +TOOL.Name = "ParCtrl - Projectiles" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.HighlightedEnt = nil + +TOOL.ClientConVar[ "projfx_enabled" ] = "1" +TOOL.ClientConVar[ "projfx_effectname" ] = "Rocket_Smoke" +TOOL.ClientConVar[ "projfx_utileffect_scale" ] = "1" +TOOL.ClientConVar[ "projfx_utileffect_magnitude" ] = "1" +TOOL.ClientConVar[ "projfx_utileffect_radius" ] = "10" +TOOL.ClientConVar[ "projfx_color_enabled" ] = "0" +TOOL.ClientConVar[ "projfx_color_r" ] = "255" +TOOL.ClientConVar[ "projfx_color_g" ] = "20" +TOOL.ClientConVar[ "projfx_color_b" ] = "0" +TOOL.ClientConVar[ "projfx_color_outofone" ] = "0" + +TOOL.ClientConVar[ "impactfx_enabled" ] = "1" +TOOL.ClientConVar[ "impactfx_effectname" ] = "!UTILEFFECT!Explosion!FLAG4!" +TOOL.ClientConVar[ "impactfx_utileffect_scale" ] = "1" +TOOL.ClientConVar[ "impactfx_utileffect_magnitude" ] = "1" +TOOL.ClientConVar[ "impactfx_utileffect_radius" ] = "10" +TOOL.ClientConVar[ "impactfx_color_enabled" ] = "0" +TOOL.ClientConVar[ "impactfx_color_r" ] = "255" +TOOL.ClientConVar[ "impactfx_color_g" ] = "20" +TOOL.ClientConVar[ "impactfx_color_b" ] = "0" +TOOL.ClientConVar[ "impactfx_color_outofone" ] = "0" + +//TOOL.ClientConVar[ "attachnum" ] = "1" //we're using the standard tool's attachnum var instead so that the selected attachment stays consistent when swapping between tools +TOOL.ClientConVar[ "repeatrate" ] = "0.80" +TOOL.ClientConVar[ "projmodel" ] = "models/weapons/w_missile.mdl" +TOOL.ClientConVar[ "projmodel_skin" ] = "0" +TOOL.ClientConVar[ "projmodel_attachnum" ] = "1" +TOOL.ClientConVar[ "projmodel_material" ] = "" +TOOL.ClientConVar[ "projmodel_invis" ] = "0" +TOOL.ClientConVar[ "impactfx_effectlifetime" ] = "1.0" + +TOOL.ClientConVar[ "projent_spread" ] = "0.04" +TOOL.ClientConVar[ "projent_velocity" ] = "1000" +TOOL.ClientConVar[ "projent_gravity" ] = "0" +TOOL.ClientConVar[ "projent_angle" ] = "0" +TOOL.ClientConVar[ "projent_spin" ] = "0" +TOOL.ClientConVar[ "projent_demomanfix" ] = "0" +TOOL.ClientConVar[ "projent_lifetime_prehit" ] = "10" +TOOL.ClientConVar[ "projent_lifetime_posthit" ] = "0" +TOOL.ClientConVar[ "projent_serverside" ] = "0" + +TOOL.ClientConVar[ "propmodel" ] = "models/weapons/w_smg1.mdl" +TOOL.ClientConVar[ "propangle" ] = "2" +TOOL.ClientConVar[ "propinvis" ] = "0" + +TOOL.ClientConVar[ "numpadkey" ] = "52" +TOOL.ClientConVar[ "toggle" ] = "1" +TOOL.ClientConVar[ "starton" ] = "1" + +TOOL.Information = { + { name = "left0", stage = 0, icon = "gui/lmb.png" }, + { name = "middle0", stage = 0, icon = "gui/mmb.png" }, + { name = "right0", stage = 0, icon = "gui/rmb.png" }, + { name = "reload0", stage = 0, icon = "gui/r.png" }, +} + +if ( CLIENT ) then + language.Add( "tool.particlecontrol_proj.name", "Adv. Particle Controller - Projectiles" ) + language.Add( "tool.particlecontrol_proj.desc", "Attach projectile effects to things" ) + language.Add( "tool.particlecontrol_proj.help", "Projectile effects launch props that have one particle attached to them, and another particle that plays once they expire, either on impact or on a timer." ) + + language.Add( "tool.particlecontrol_proj.left0", "Add a projectile effect to an object" ) + language.Add( "tool.particlecontrol_proj.middle0", "Scroll through an object's attachments" ) + language.Add( "tool.particlecontrol_proj.right0", "Attach a new prop with the projectile effect on it" ) + language.Add( "tool.particlecontrol_proj.reload0", "Remove all projectile effects from an object" ) +end + +util.PrecacheSound("weapons/pistol/pistol_empty.wav") + + + + +function TOOL:LeftClick( trace ) + + local projinfo = nil + if self:GetClientNumber( "projfx_enabled", 0 ) == 1 then + projinfo = { + effectname = self:GetClientInfo( "projfx_effectname", 0 ), + utileffectinfo = Vector( self:GetClientNumber( "projfx_utileffect_scale", 0 ), self:GetClientNumber( "projfx_utileffect_magnitude", 0 ), self:GetClientNumber( "projfx_utileffect_radius", 0 ) ), + } + if self:GetClientNumber( "projfx_color_enabled", 0 ) == 1 then + if self:GetClientNumber( "projfx_color_outofone", 0 ) == 1 then + projinfo.colorinfo = Color( self:GetClientNumber( "projfx_color_r", 0 ), self:GetClientNumber( "projfx_color_g", 0 ), self:GetClientNumber( "projfx_color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + projinfo.colorinfo = Color( self:GetClientNumber( "projfx_color_r", 0 ), self:GetClientNumber( "projfx_color_g", 0 ), self:GetClientNumber( "projfx_color_b", 0 ), 0 ) + end + end + end + + local impactinfo = nil + if self:GetClientNumber( "impactfx_enabled", 0 ) == 1 then + impactinfo = { + effectname = self:GetClientInfo( "impactfx_effectname", 0 ), + utileffectinfo = Vector( self:GetClientNumber( "impactfx_utileffect_scale", 0 ), self:GetClientNumber( "impactfx_utileffect_magnitude", 0 ), self:GetClientNumber( "impactfx_utileffect_radius", 0 ) ), + } + if self:GetClientNumber( "impactfx_color_enabled", 0 ) == 1 then + if self:GetClientNumber( "impactfx_color_outofone", 0 ) == 1 then + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 0 ) + end + end + end + + local attachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + local repeatrate = self:GetClientNumber( "repeatrate", 0 ) + local projmodel = self:GetClientInfo( "projmodel", 0 ) + local projmodel_skin = self:GetClientNumber( "projmodel_skin", 0 ) + local projmodel_attachnum = self:GetClientNumber( "projmodel_attachnum", 0 ) + local projmodel_material = self:GetClientInfo( "projmodel_material", 0 ) + local projmodel_invis = self:GetClientNumber( "projmodel_invis", 0 ) + local impactfx_effectlifetime = self:GetClientNumber( "impactfx_effectlifetime", 0 ) + + local projent_spread = self:GetClientNumber( "projent_spread", 0 ) + local projent_velocity = self:GetClientNumber( "projent_velocity", 0 ) + local projent_gravity = self:GetClientNumber( "projent_gravity", 0 ) + local projent_angle = self:GetClientNumber( "projent_angle", 0 ) + local projent_spin = self:GetClientNumber( "projent_spin", 0 ) + local projent_demomanfix = self:GetClientNumber( "projent_demomanfix", 0 ) + local projent_lifetime_prehit = self:GetClientNumber( "projent_lifetime_prehit", 0 ) + local projent_lifetime_posthit = self:GetClientNumber( "projent_lifetime_posthit", 0 ) + local projent_serverside = self:GetClientNumber( "projent_serverside", 0 ) + + local numpadkey = self:GetClientNumber( "numpadkey", 0 ) + local toggle = self:GetClientNumber( "toggle", 0 ) + local starton = self:GetClientNumber( "starton", 0 ) + + local ply = self:GetOwner() + + + + if ( trace.Entity:IsValid() ) then + if CLIENT then return true end + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + AttachParticleControllerProj( ply, trace.Entity, { NewTable = { + ProjFXInfo = projinfo, + ImpactFXInfo = impactinfo, + + AttachNum = attachnum, + RepeatRate = repeatrate, + ProjModel = projmodel, + ProjModel_Skin = projmodel_skin, + ProjModel_AttachNum = projmodel_attachnum, + ProjModel_Material = projmodel_material, + ProjModel_Invis = projmodel_invis, + ImpactFX_EffectLifetime = impactfx_effectlifetime, + + ProjEnt_Spread = projent_spread, + ProjEnt_Velocity = projent_velocity, + ProjEnt_Gravity = projent_gravity, + ProjEnt_Angle = projent_angle, + ProjEnt_Spin = projent_spin, + ProjEnt_DemomanFix = projent_demomanfix, + ProjEnt_Lifetime_PreHit = projent_lifetime_prehit, + ProjEnt_Lifetime_PostHit = projent_lifetime_posthit, + ProjEnt_Serverside = projent_serverside, + + NumpadKey = numpadkey, + Toggle = toggle, + StartOn = starton, + } } ) + return true + end + +end + + + + +function TOOL:RightClick( trace ) + + local projinfo = nil + if self:GetClientNumber( "projfx_enabled", 0 ) == 1 then + projinfo = { + effectname = self:GetClientInfo( "projfx_effectname", 0 ), + utileffectinfo = Vector( self:GetClientNumber( "projfx_utileffect_scale", 0 ), self:GetClientNumber( "projfx_utileffect_magnitude", 0 ), self:GetClientNumber( "projfx_utileffect_radius", 0 ) ), + } + if self:GetClientNumber( "projfx_color_enabled", 0 ) == 1 then + if self:GetClientNumber( "projfx_color_outofone", 0 ) == 1 then + projinfo.colorinfo = Color( self:GetClientNumber( "projfx_color_r", 0 ), self:GetClientNumber( "projfx_color_g", 0 ), self:GetClientNumber( "projfx_color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + projinfo.colorinfo = Color( self:GetClientNumber( "projfx_color_r", 0 ), self:GetClientNumber( "projfx_color_g", 0 ), self:GetClientNumber( "projfx_color_b", 0 ), 0 ) + end + end + end + + local impactinfo = nil + if self:GetClientNumber( "impactfx_enabled", 0 ) == 1 then + impactinfo = { + effectname = self:GetClientInfo( "impactfx_effectname", 0 ), + utileffectinfo = Vector( self:GetClientNumber( "impactfx_utileffect_scale", 0 ), self:GetClientNumber( "impactfx_utileffect_magnitude", 0 ), self:GetClientNumber( "impactfx_utileffect_radius", 0 ) ), + } + if self:GetClientNumber( "impactfx_color_enabled", 0 ) == 1 then + if self:GetClientNumber( "impactfx_color_outofone", 0 ) == 1 then + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 0 ) + end + end + end + + local attachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + local repeatrate = self:GetClientNumber( "repeatrate", 0 ) + local projmodel = self:GetClientInfo( "projmodel", 0 ) + local projmodel_skin = self:GetClientNumber( "projmodel_skin", 0 ) + local projmodel_attachnum = self:GetClientNumber( "projmodel_attachnum", 0 ) + local projmodel_material = self:GetClientInfo( "projmodel_material", 0 ) + local projmodel_invis = self:GetClientNumber( "projmodel_invis", 0 ) + local impactfx_effectlifetime = self:GetClientNumber( "impactfx_effectlifetime", 0 ) + + local projent_spread = self:GetClientNumber( "projent_spread", 0 ) + local projent_velocity = self:GetClientNumber( "projent_velocity", 0 ) + local projent_gravity = self:GetClientNumber( "projent_gravity", 0 ) + local projent_angle = self:GetClientNumber( "projent_angle", 0 ) + local projent_spin = self:GetClientNumber( "projent_spin", 0 ) + local projent_demomanfix = self:GetClientNumber( "projent_demomanfix", 0 ) + local projent_lifetime_prehit = self:GetClientNumber( "projent_lifetime_prehit", 0 ) + local projent_lifetime_posthit = self:GetClientNumber( "projent_lifetime_posthit", 0 ) + local projent_serverside = self:GetClientNumber( "projent_serverside", 0 ) + + local numpadkey = self:GetClientNumber( "numpadkey", 0 ) + local toggle = self:GetClientNumber( "toggle", 0 ) + local starton = self:GetClientNumber( "starton", 0 ) + + local ply = self:GetOwner() + + + + local propmodel = self:GetClientInfo( "propmodel", 0 ) + local propangle = self:GetClientNumber( "propangle", 0 ) + //propangle 1: spawn upright + //propangle 2: spawn at surface angle + + if !util.IsValidModel(propmodel) then return false end + if !util.IsValidProp(propmodel) then return false end + if CLIENT then return true end + + prop = ents.Create( "prop_physics" ) + prop:SetModel( propmodel ) + prop:SetPos( trace.HitPos - trace.HitNormal * prop:OBBMins().z ) + if propangle == 1 then prop:SetAngles(Angle(0,trace.HitNormal:Angle().y,0)) else prop:SetAngles(trace.HitNormal:Angle()) end + prop:SetCollisionGroup(COLLISION_GROUP_NONE) + prop:Spawn() + + local shouldweweld = true //don't weld if... + if ( !util.IsValidPhysicsObject(prop, 0) ) then shouldweweld = false end //the prop doesn't have a phys object + if ( !trace.Entity:IsValid() ) then shouldweweld = false end //the thing we clicked on doesn't exist/is the world + if ( trace.Entity && trace.Entity:IsPlayer() ) then shouldweweld = false end //the thing we clicked on is a player + if ( !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then shouldweweld = false end //the thing we clicked on doesn't have a phys object + if shouldweweld == true then + local const = constraint.Weld( prop, trace.Entity, 0, trace.PhysicsBone, 0, true, true ) + else + if util.IsValidPhysicsObject(prop, 0) then prop:GetPhysicsObject():EnableMotion(false) end + end + + if self:GetClientNumber( "propinvis", 0 ) == 1 then + prop:SetRenderMode(1) //we need to change the render mode so the transparency actually shows up + prop:SetColor( Color(255,255,255,0) ) + duplicator.StoreEntityModifier( prop, "colour", { Color = Color(255,255,255,0), RenderMode = 1, RenderFX = 0 } ) + end + + undo.Create( "prop" ) + undo.AddEntity( prop ) + undo.SetPlayer( ply ) + undo.Finish( "Prop ("..tostring(propmodel)..")" ) + + + + if ( prop:IsValid() ) then + AttachParticleControllerProj( ply, prop, { NewTable = { + ProjFXInfo = projinfo, + ImpactFXInfo = impactinfo, + + AttachNum = attachnum, + RepeatRate = repeatrate, + ProjModel = projmodel, + ProjModel_Skin = projmodel_skin, + ProjModel_AttachNum = projmodel_attachnum, + ProjModel_Material = projmodel_material, + ProjModel_Invis = projmodel_invis, + ImpactFX_EffectLifetime = impactfx_effectlifetime, + + ProjEnt_Spread = projent_spread, + ProjEnt_Velocity = projent_velocity, + ProjEnt_Gravity = projent_gravity, + ProjEnt_Angle = projent_angle, + ProjEnt_Spin = projent_spin, + ProjEnt_DemomanFix = projent_demomanfix, + ProjEnt_Lifetime_PreHit = projent_lifetime_prehit, + ProjEnt_Lifetime_PostHit = projent_lifetime_posthit, + ProjEnt_Serverside = projent_serverside, + + NumpadKey = numpadkey, + Toggle = toggle, + StartOn = starton, + } } ) + return true + end + +end + + + + +function TOOL:Reload( trace ) + + if ( trace.Entity:IsValid() ) then + local fx = false + + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + + for _, asdf in pairs( ents:GetAll() ) do + if asdf:GetClass() == "particlecontroller_proj" and asdf:GetParent() == trace.Entity then + if SERVER then asdf:Remove() end + fx = true + end + end + if SERVER then + duplicator.ClearEntityModifier( trace.Entity, "DupeParticleControllerProj" ) + end + + return fx + end + +end + + + + +if CLIENT then + + local colorborder = Color(0,0,0,255) + local colorselect = Color(0,255,0,255) + local colorunselect = Color(255,255,255,255) + + function TOOL:DrawHUD() + local pl = LocalPlayer() + local tr = pl:GetEyeTrace() + local attachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + + local function DrawHighlightAttachments(ent) + + //If there aren't any attachments, then draw the model origin as selected and stop here: + if !ent:GetAttachments() or !ent:GetAttachments()[1] then + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + + return + end + + + //Draw the unselected model origin, if applicable: + if ent:GetAttachments()[attachnum] then + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder) + end + + //Draw the unselected attachment points: + for _, table in pairs(ent:GetAttachments()) do + local _pos,_ang = ent:GetAttachment(table.id).Pos,ent:GetAttachment(table.id).Ang + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + if table.id != attachnum then + draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect) + draw.SimpleTextOutlined(table.id ..": ".. table.name,"Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder) + end + end + + //Draw the selected attachment point or model origin last, so it renders above all the others: + if !ent:GetAttachments()[attachnum] then + //Model origin + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + else + //Attachment + local _pos,_ang = ent:GetAttachment(attachnum).Pos,ent:GetAttachment(attachnum).Ang + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined(attachnum ..": ".. ent:GetAttachments()[attachnum].name,"Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + end + end + + if IsValid(tr.Entity) and tr.Entity == self.HighlightedEnt then + DrawHighlightAttachments(self.HighlightedEnt) + return end + + if IsValid(tr.Entity) and tr.Entity != self.HighlightedEnt then + //unhighlight the old ent if it exists + if self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + + //highlight the new ent + self.HighlightedEnt = tr.Entity + end + + if !IsValid(tr.Entity) and self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + end + + function TOOL:Holster() + if self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + end + + + + + //All credit for the toolgun scroll wheel code goes to the Wiremod devs. You guys are the best. + local function get_active_tool(ply, tool) + -- find toolgun + local activeWep = ply:GetActiveWeapon() + if not IsValid(activeWep) or activeWep:GetClass() ~= "gmod_tool" or activeWep.Mode ~= tool then return end + + return activeWep:GetToolObject(tool) + end + + local function hookfunc( ply, bind, pressed ) + if not pressed then return end + if bind == "invnext" then + local self = get_active_tool(ply, "particlecontrol_proj") + if not self then return end + + return self:ScrollDown(ply:GetEyeTraceNoCursor()) + elseif bind == "invprev" then + local self = get_active_tool(ply, "particlecontrol_proj") + if not self then return end + + return self:ScrollUp(ply:GetEyeTraceNoCursor()) + end + end + + if game.SinglePlayer() then -- wtfgarry (have to have a delay in single player or the hook won't get added) + timer.Simple(5,function() hook.Add( "PlayerBindPress", "particlecontrol_proj_playerbindpress", hookfunc ) end) + else + hook.Add( "PlayerBindPress", "particlecontrol_proj_playerbindpress", hookfunc ) + end + //End shamefully copied code here. + + function TOOL:Scroll(trace,dir) + if !IsValid(self.HighlightedEnt) then return end + + local attachcount = 0 + if self.HighlightedEnt:GetAttachments() then attachcount = table.Count(self.HighlightedEnt:GetAttachments()) end + local oldattachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + if oldattachnum > attachcount then oldattachnum = 0 end + local attachnum = oldattachnum + dir + + if attachnum < 0 then attachnum = attachcount end + if attachnum > attachcount then attachnum = 0 end + RunConsoleCommand("particlecontrol_attachnum", tostring(attachnum)) //use the standard tool's attachnum var + self:GetOwner():EmitSound("weapons/pistol/pistol_empty.wav") + return true + end + function TOOL:ScrollUp(trace) return self:Scroll(trace,-1) end + function TOOL:ScrollDown(trace) return self:Scroll(trace,1) end + +end + + + + +if SERVER then + + local function SpawnParticleControllerProj(ply, ent, DataTable) + + if DataTable == nil or DataTable == {} or DataTable.ProjModel == nil or ent == nil or !IsValid(ent) then return end + + + local ParticleControlProj = ents.Create( "particlecontroller_proj" ) + ParticleControlProj:SetPos(ent:GetPos()) + ParticleControlProj:SetAngles(ent:GetAngles()) + ParticleControlProj:SetParent(ent) + ent:DeleteOnRemove(ParticleControlProj) + + ParticleControlProj:SetTargetEnt(ent) + + + if DataTable.ProjFXInfo != nil then + ParticleControlProj:SetProjFX_EffectName(DataTable.ProjFXInfo.effectname) + ParticleControlProj:SetProjFX_UtilEffectInfo(DataTable.ProjFXInfo.utileffectinfo) + if DataTable.ProjFXInfo.colorinfo != nil then + local projfxcolor = Vector(DataTable.ProjFXInfo.colorinfo.r, DataTable.ProjFXInfo.colorinfo.g, DataTable.ProjFXInfo.colorinfo.b) + if DataTable.ProjFXInfo.colorinfo.a then + projfxcolor = projfxcolor / 255 + end + ParticleControlProj:SetProjFX_ColorInfo( projfxcolor ) + else + ParticleControlProj:SetProjFX_ColorInfo( Vector(0,0,0) ) + end + else + ParticleControlProj:SetProjFX_EffectName("") + end + + if DataTable.ImpactFXInfo != nil then + ParticleControlProj:SetImpactFX_EffectName(DataTable.ImpactFXInfo.effectname) + ParticleControlProj:SetImpactFX_UtilEffectInfo(DataTable.ImpactFXInfo.utileffectinfo) + if DataTable.ImpactFXInfo.colorinfo != nil then + local impactfxcolor = Vector(DataTable.ImpactFXInfo.colorinfo.r, DataTable.ImpactFXInfo.colorinfo.g, DataTable.ImpactFXInfo.colorinfo.b) + if DataTable.ImpactFXInfo.colorinfo.a then + impactfxcolor = impactfxcolor / 255 + end + ParticleControlProj:SetImpactFX_ColorInfo( impactfxcolor ) + else + ParticleControlProj:SetImpactFX_ColorInfo( Vector(0,0,0) ) + end + else + ParticleControlProj:SetImpactFX_EffectName("") + end + + ParticleControlProj:SetAttachNum(DataTable.AttachNum) + ParticleControlProj:SetRepeatRate(DataTable.RepeatRate) + + ParticleControlProj:SetProjModel(DataTable.ProjModel) + ParticleControlProj:SetSkin(DataTable.ProjModel_Skin) //also use the controller ent to store the skin + ParticleControlProj:SetProjModel_AttachNum(DataTable.ProjModel_AttachNum) + ParticleControlProj:SetMaterial(DataTable.ProjModel_Material) //and the material + ParticleControlProj:SetProjModel_Invis( tobool(DataTable.ProjModel_Invis) ) + ParticleControlProj:SetImpactFX_EffectLifetime(DataTable.ImpactFX_EffectLifetime) + + ParticleControlProj:SetProjEnt_Spread(DataTable.ProjEnt_Spread) + ParticleControlProj:SetProjEnt_Velocity(DataTable.ProjEnt_Velocity) + ParticleControlProj:SetProjEnt_Gravity( tobool(DataTable.ProjEnt_Gravity) ) + ParticleControlProj:SetProjEnt_Angle(DataTable.ProjEnt_Angle) + ParticleControlProj:SetProjEnt_Spin(DataTable.ProjEnt_Spin) + ParticleControlProj:SetProjEnt_DemomanFix( tobool(DataTable.ProjEnt_DemomanFix) ) + ParticleControlProj:SetProjEnt_Lifetime_PreHit(DataTable.ProjEnt_Lifetime_PreHit) + ParticleControlProj:SetProjEnt_Lifetime_PostHit(DataTable.ProjEnt_Lifetime_PostHit) + ParticleControlProj:SetProjEnt_Serverside( tobool(DataTable.ProjEnt_Serverside) ) + + + ParticleControlProj:SetActive( tobool(DataTable.StartOn) ) + ParticleControlProj:SetToggle( tobool(DataTable.Toggle) ) + ParticleControlProj:SetNumpadKey(DataTable.NumpadKey) + + numpad.OnDown( ply, DataTable.NumpadKey, "Particle_Press", ParticleControlProj ) + numpad.OnUp( ply, DataTable.NumpadKey, "Particle_Release", ParticleControlProj ) + ParticleControlProj:SetNumpadState("") + + + ParticleControlProj:Spawn() + ParticleControlProj:Activate() + + end + + + function AttachParticleControllerProj( ply, ent, Data ) + + if Data.NewTable then + SpawnParticleControllerProj(ply, ent, Data.NewTable) + + local dupetable = {} + if ent.EntityMods and ent.EntityMods.DupeParticleControllerProj then dupetable = ent.EntityMods.DupeParticleControllerProj end + table.insert(dupetable, Data.NewTable) + duplicator.StoreEntityModifier( ent, "DupeParticleControllerProj", dupetable ) + return end + + end + + + function DupeParticleControllerProj( ply, ent, Data ) + + local override = hook.Run('CanTool', ply, { Entity = ent }, 'particlecontrol_proj') + if override == false then return end + + //due to a problem with the easy bonemerge tool that causes entity modifiers to be applied TWICE, we need to remove the effects that were added the first time + for _, asdf in pairs( ents:GetAll() ) do + if asdf:GetClass() == "particlecontroller_proj" and asdf:GetParent() == ent then + asdf:Remove() + end + end + + for _, DataTable in pairs (Data) do + SpawnParticleControllerProj(ply, ent, DataTable) + end + + end + duplicator.RegisterEntityModifier( "DupeParticleControllerProj", DupeParticleControllerProj ) + +end + + + + +//we're still testing out a lot of stuff with the cpanel, so let's add a way to refresh it by reselecting the tool +--[[ +TOOL.ClientConVar[ "refresh" ] = 1 +function TOOL:Think() + if SERVER then return end + if self:GetClientNumber("refresh") == 1 then + RunConsoleCommand("particlecontrol_proj_refresh", "0"); + //refresh the cpanel + local panel = controlpanel.Get( "particlecontrol_proj" ) + if ( !panel ) then return end + panel:ClearControls() + self.BuildCPanel(panel) + end +end +function TOOL:Deploy() + RunConsoleCommand("particlecontrol_proj_refresh", "1"); +end +]] + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel(panel) + + panel:AddControl( "Header", { Description = "#tool.particlecontrol_proj.help" } ) + + //Presets + panel:AddControl( "ComboBox", { + MenuButton = 1, + Folder = "particlecontrol_proj", + Options = { + //[ "#preset.default" ] = ConVarsDefault + [ "Example: Generic Rockets" ] = ConVarsDefault, + [ "Example: TF2 Rockets" ] = { particlecontrol_proj_impactfx_color_b = "0", particlecontrol_proj_impactfx_color_enabled = "0", particlecontrol_proj_impactfx_color_g = "20", particlecontrol_proj_impactfx_color_outofone = "0", particlecontrol_proj_impactfx_color_r = "255", particlecontrol_proj_impactfx_effectlifetime = "1.000000", particlecontrol_proj_impactfx_effectname = "ExplosionCore_Wall", particlecontrol_proj_impactfx_enabled = "1", particlecontrol_proj_impactfx_utileffect_magnitude = "1", particlecontrol_proj_impactfx_utileffect_radius = "10", particlecontrol_proj_impactfx_utileffect_scale = "1", particlecontrol_proj_numpadkey = "52", particlecontrol_proj_projent_angle = "0", particlecontrol_proj_projent_demomanfix = "0", particlecontrol_proj_projent_gravity = "0", particlecontrol_proj_projent_lifetime_posthit = "0.000000", particlecontrol_proj_projent_lifetime_prehit = "10.000000", particlecontrol_proj_projent_serverside = "0", particlecontrol_proj_projent_spin = "0", + particlecontrol_proj_projent_spread = "0", particlecontrol_proj_projent_velocity = "1100.000000", particlecontrol_proj_projfx_color_b = "0", particlecontrol_proj_projfx_color_enabled = "0", particlecontrol_proj_projfx_color_g = "20", particlecontrol_proj_projfx_color_outofone = "0", particlecontrol_proj_projfx_color_r = "255", particlecontrol_proj_projfx_effectname = "rockettrail", particlecontrol_proj_projfx_enabled = "1", particlecontrol_proj_projfx_utileffect_magnitude = "1", particlecontrol_proj_projfx_utileffect_radius = "10", particlecontrol_proj_projfx_utileffect_scale = "1", particlecontrol_proj_projmodel = "models/weapons/w_models/w_rocket.mdl", particlecontrol_proj_projmodel_attachnum = "1", particlecontrol_proj_projmodel_invis = "0", particlecontrol_proj_projmodel_skin = "0", particlecontrol_proj_propangle = "2", particlecontrol_proj_propinvis = "0", particlecontrol_proj_propmodel = "models/weapons/w_smg1.mdl", particlecontrol_proj_repeatrate = "0.800000", + particlecontrol_proj_starton = "1", particlecontrol_proj_toggle = "1" }, + [ "Example: TF2 Grenades, red" ] = { particlecontrol_proj_impactfx_color_b = "0", particlecontrol_proj_impactfx_color_enabled = "0", particlecontrol_proj_impactfx_color_g = "20", particlecontrol_proj_impactfx_color_outofone = "0", particlecontrol_proj_impactfx_color_r = "255", particlecontrol_proj_impactfx_effectlifetime = "1.000000", particlecontrol_proj_impactfx_effectname = "ExplosionCore_MidAir", particlecontrol_proj_impactfx_enabled = "1", particlecontrol_proj_impactfx_utileffect_magnitude = "1", particlecontrol_proj_impactfx_utileffect_radius = "10", particlecontrol_proj_impactfx_utileffect_scale = "1", particlecontrol_proj_numpadkey = "52", particlecontrol_proj_projent_angle = "0", particlecontrol_proj_projent_demomanfix = "0", particlecontrol_proj_projent_gravity = "1", particlecontrol_proj_projent_lifetime_posthit = "2.300000", particlecontrol_proj_projent_lifetime_prehit = "2.300000", particlecontrol_proj_projent_serverside = "0", particlecontrol_proj_projent_spin = "4", + particlecontrol_proj_projent_spread = "0", particlecontrol_proj_projent_velocity = "1217.000000", particlecontrol_proj_projfx_color_b = "0", particlecontrol_proj_projfx_color_enabled = "0", particlecontrol_proj_projfx_color_g = "20", particlecontrol_proj_projfx_color_outofone = "0", particlecontrol_proj_projfx_color_r = "255", particlecontrol_proj_projfx_effectname = "pipebombtrail_red", particlecontrol_proj_projfx_enabled = "1", particlecontrol_proj_projfx_utileffect_magnitude = "1", particlecontrol_proj_projfx_utileffect_radius = "10", particlecontrol_proj_projfx_utileffect_scale = "1", particlecontrol_proj_projmodel = "models/weapons/w_models/w_grenade_grenadelauncher.mdl", particlecontrol_proj_projmodel_attachnum = "0", particlecontrol_proj_projmodel_invis = "0", particlecontrol_proj_projmodel_skin = "0", particlecontrol_proj_propangle = "2", particlecontrol_proj_propinvis = "0", particlecontrol_proj_propmodel = "models/weapons/w_smg1.mdl", particlecontrol_proj_repeatrate = "0.600000", + particlecontrol_proj_starton = "1", particlecontrol_proj_toggle = "1" }, + [ "Example: TF2 Grenades, blue" ] = { particlecontrol_proj_impactfx_color_b = "0", particlecontrol_proj_impactfx_color_enabled = "0", particlecontrol_proj_impactfx_color_g = "20", particlecontrol_proj_impactfx_color_outofone = "0", particlecontrol_proj_impactfx_color_r = "255", particlecontrol_proj_impactfx_effectlifetime = "1.000000", particlecontrol_proj_impactfx_effectname = "ExplosionCore_MidAir", particlecontrol_proj_impactfx_enabled = "1", particlecontrol_proj_impactfx_utileffect_magnitude = "1", particlecontrol_proj_impactfx_utileffect_radius = "10", particlecontrol_proj_impactfx_utileffect_scale = "1", particlecontrol_proj_numpadkey = "52", particlecontrol_proj_projent_angle = "0", particlecontrol_proj_projent_demomanfix = "0", particlecontrol_proj_projent_gravity = "1", particlecontrol_proj_projent_lifetime_posthit = "2.300000", particlecontrol_proj_projent_lifetime_prehit = "2.300000", particlecontrol_proj_projent_serverside = "0", particlecontrol_proj_projent_spin = "4", + particlecontrol_proj_projent_spread = "0", particlecontrol_proj_projent_velocity = "1217.000000", particlecontrol_proj_projfx_color_b = "0", particlecontrol_proj_projfx_color_enabled = "0", particlecontrol_proj_projfx_color_g = "20", particlecontrol_proj_projfx_color_outofone = "0", particlecontrol_proj_projfx_color_r = "255", particlecontrol_proj_projfx_effectname = "pipebombtrail_blue", particlecontrol_proj_projfx_enabled = "1", particlecontrol_proj_projfx_utileffect_magnitude = "1", particlecontrol_proj_projfx_utileffect_radius = "10", particlecontrol_proj_projfx_utileffect_scale = "1", particlecontrol_proj_projmodel = "models/weapons/w_models/w_grenade_grenadelauncher.mdl", particlecontrol_proj_projmodel_attachnum = "0", particlecontrol_proj_projmodel_invis = "0", particlecontrol_proj_projmodel_skin = "1", particlecontrol_proj_propangle = "2", particlecontrol_proj_propinvis = "0", particlecontrol_proj_propmodel = "models/weapons/w_smg1.mdl", particlecontrol_proj_repeatrate = "0.600000", + particlecontrol_proj_starton = "1", particlecontrol_proj_toggle = "1" }, + }, + CVars = table.GetKeys( ConVarsDefault ) + } ) + + + + panel:AddControl( "Checkbox", { Label = "Enable projectile effects?", Command = "particlecontrol_proj_projfx_enabled" } ) + + AddParticleBrowser(panel, { + name = "Projectile Effect", + commands = { + effectname = "particlecontrol_proj_projfx_effectname", + color = "particlecontrol_proj_projfx_color", + utileffect = "particlecontrol_proj_projfx_utileffect", + + enabled = "particlecontrol_proj_projfx_enabled", + }, + }) + + panel:AddControl( "Checkbox", { Label = "Enable impact/expire effects?", Command = "particlecontrol_proj_impactfx_enabled" } ) + + AddParticleBrowser(panel, { + name = "Impact/Expire Effect", + commands = { + effectname = "particlecontrol_proj_impactfx_effectname", + color = "particlecontrol_proj_impactfx_color", + utileffect = "particlecontrol_proj_impactfx_utileffect", + + enabled = "particlecontrol_proj_impactfx_enabled", + }, + }) + + + + //panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl("Slider", { + Label = "Attachment", + Type = "Integer", + Min = "0", + Max = "10", + Command = "particlecontrol_attachnum", //use the standard tool's attachnum var + }) + panel:ControlHelp( "Attachment point on the model to fire projectiles from. Set to 0 to fire from the model origin." ) + + panel:AddControl("Slider", { + Label = "Repeat Rate", + Type = "Float", + Min = "0", + Max = "1", + Command = "particlecontrol_proj_repeatrate" + }) + panel:ControlHelp( "How often the projectile fires. Set to 0 to not repeat." ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + local projmodellist = { Label = "Projectile Model:", ConVar = "particlecontrol_proj_projmodel", Category = "Projectile Model", Height = 1, Models = {} } + projmodellist.Models["models/hunter/plates/plate.mdl"] = {} + projmodellist.Models["models/weapons/w_missile.mdl"] = {} + projmodellist.Models["models/weapons/w_models/w_rocket.mdl"] = {} + projmodellist.Models["models/weapons/w_models/w_grenade_grenadelauncher.mdl"] = {} + + panel:AddControl( "PropSelect", projmodellist ) + + local projmodelentry = vgui.Create( "DTextEntry", panel ) + projmodelentry:SetConVar( "particlecontrol_proj_projmodel" ) + panel:AddItem(projmodelentry) + + panel:AddControl("Slider", { + Label = "Projectile Skin", + Type = "Integer", + Min = "0", + Max = "10", + Command = "particlecontrol_proj_projmodel_skin", + }) + + panel:AddControl("Slider", { + Label = "Projectile Attachment", + Type = "Integer", + Min = "0", + Max = "10", + Command = "particlecontrol_proj_projmodel_attachnum", + }) + panel:ControlHelp( "Attachment point on the projectile to attach the effect to. Set to 0 to attach to model origin." ) + + panel:AddControl("TextBox", { + Label = "Projectile Material", + Description = "", + MaxLength = 255, + Text = "", + Command = "particlecontrol_proj_projmodel_material", + }) + + panel:AddControl( "Checkbox", { Label = "Invisible projectiles (particles only)", Command = "particlecontrol_proj_projmodel_invis" } ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl("Slider", { + Label = "Projectile Spread", + Type = "Float", + Min = "0", + Max = "1", + Command = "particlecontrol_proj_projent_spread" + }) + panel:ControlHelp( "Each unit is 90 degrees of spread - you can type in 2 for 180 degrees or even 4 for 360 degrees." ) + + panel:AddControl("Slider", { + Label = "Projectile Velocity", + Type = "Float", + Min = "0", + Max = "3000", + Command = "particlecontrol_proj_projent_velocity" + }) + //panel:ControlHelp( "" ) + + panel:AddControl( "Checkbox", { Label = "Projectile Gravity", Command = "particlecontrol_proj_projent_gravity" } ) + + panel:AddControl( "ComboBox", { + Label = "Projectile Angle", + MenuButton = "0", + Options = { + ["Forward"] = { particlecontrol_proj_projent_angle = 0 }, + ["Left"] = { particlecontrol_proj_projent_angle = 1 }, + ["Right"] = { particlecontrol_proj_projent_angle = 2 }, + ["Up"] = { particlecontrol_proj_projent_angle = 3 }, + ["Down"] = { particlecontrol_proj_projent_angle = 4 }, + ["Back"] = { particlecontrol_proj_projent_angle = 5 }, + } + }) + + panel:AddControl( "ComboBox", { + Label = "Projectile Spin", + MenuButton = "0", + Options = { + ["Don't spin"] = { particlecontrol_proj_projent_spin = 0 }, + ["Spin pitch"] = { particlecontrol_proj_projent_spin = 1 }, + ["Spin yaw"] = { particlecontrol_proj_projent_spin = 2 }, + ["Spin roll"] = { particlecontrol_proj_projent_spin = 3 }, + ["Spin random"] = { particlecontrol_proj_projent_spin = 4 }, + } + }) + + panel:AddControl( "Checkbox", { Label = "Demoman Weapon Fix", Command = "particlecontrol_proj_projent_demomanfix" } ) + panel:ControlHelp( "Demoman weapon props have their muzzle attachment at an angle for some reason. Use this to fix it." ) + + panel:AddControl("Slider", { + Label = "Lifetime", + Type = "Float", + Min = "0", + Max = "10", + Command = "particlecontrol_proj_projent_lifetime_prehit" + }) + panel:ControlHelp( "How long projectiles last after being launched." ) + + panel:AddControl("Slider", { + Label = "Lifetime (after impact)", + Type = "Float", + Min = "0", + Max = "10", + Command = "particlecontrol_proj_projent_lifetime_posthit" + }) + panel:ControlHelp( "How long projectiles last after hitting something. Set to 0 to expire on impact." ) + panel:AddControl( "Checkbox", { Label = "Serverside projectiles?", Command = "particlecontrol_proj_projent_serverside" } ) + panel:ControlHelp( "Use serverside props for projectiles. These will collide properly with everything instead of passing through, but they'll also put a lot more stress on the game (meaning more lag), and they'll show up in the wrong spot if bonemerged. Only turn this on if you need it." ) + + panel:AddControl("Slider", { + Label = "Impact Effect Lifetime", + Type = "Float", + Min = "0.5", + Max = "5", + Command = "particlecontrol_proj_impactfx_effectlifetime" + }) + //panel:ControlHelp( "Number of seconds before impact effects are removed." ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + local modellist = { Label = "Prop:", ConVar = "particlecontrol_proj_propmodel", Category = "Prop", Height = 1, Models = {} } + modellist.Models["models/hunter/plates/plate025x025.mdl"] = {} + modellist.Models["models/hunter/plates/plate.mdl"] = {} + modellist.Models["models/weapons/w_smg1.mdl"] = {} + modellist.Models["models/weapons/w_models/w_rocketlauncher.mdl"] = {} + + panel:AddControl( "PropSelect", modellist ) + + panel:AddControl( "ComboBox", { + Label = "Prop Angle", + MenuButton = "0", + Options = { + ["Spawn upright"] = { particlecontrol_proj_propangle = "1" }, + ["Spawn at surface angle"] = { particlecontrol_proj_propangle = "2" } + } + }) + + panel:AddControl( "Checkbox", { Label = "Invisible prop (particles only)", Command = "particlecontrol_proj_propinvis" } ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl( "Numpad", { + Label = "Effect Key", + Command = "particlecontrol_proj_numpadkey", + ButtonSize = 22 + }) + + panel:AddControl( "Checkbox", { Label = "Toggle", Command = "particlecontrol_proj_toggle" } ) + + panel:AddControl( "Checkbox", { Label = "Start on?", Command = "particlecontrol_proj_starton" } ) + +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol_tracer.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol_tracer.lua new file mode 100644 index 0000000..47d85af --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/particlecontrol_tracer.lua @@ -0,0 +1,705 @@ +TOOL.Category = "Particle Controller" +TOOL.Name = "ParCtrl - Tracers" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.HighlightedEnt = nil + +TOOL.ClientConVar[ "effectname" ] = "!UTILEFFECT!AR2Tracer" +//TOOL.ClientConVar[ "utileffect_scale" ] = "1" +//TOOL.ClientConVar[ "utileffect_magnitude" ] = "1" +//TOOL.ClientConVar[ "utileffect_radius" ] = "10" +TOOL.ClientConVar[ "color_enabled" ] = "0" +TOOL.ClientConVar[ "color_r" ] = "255" +TOOL.ClientConVar[ "color_g" ] = "20" +TOOL.ClientConVar[ "color_b" ] = "0" +TOOL.ClientConVar[ "color_outofone" ] = "0" + +TOOL.ClientConVar[ "impactfx_enabled" ] = "1" +TOOL.ClientConVar[ "impactfx_effectname" ] = "!UTILEFFECT!AR2Impact" +TOOL.ClientConVar[ "impactfx_utileffect_scale" ] = "1" +TOOL.ClientConVar[ "impactfx_utileffect_magnitude" ] = "1" +TOOL.ClientConVar[ "impactfx_utileffect_radius" ] = "10" +TOOL.ClientConVar[ "impactfx_color_enabled" ] = "0" +TOOL.ClientConVar[ "impactfx_color_r" ] = "255" +TOOL.ClientConVar[ "impactfx_color_g" ] = "20" +TOOL.ClientConVar[ "impactfx_color_b" ] = "0" +TOOL.ClientConVar[ "impactfx_color_outofone" ] = "0" + +//TOOL.ClientConVar[ "attachnum" ] = "1" //we're using the standard tool's attachnum var instead so that the selected attachment stays consistent when swapping between tools +TOOL.ClientConVar[ "repeatrate" ] = "0.1" +TOOL.ClientConVar[ "effectlifetime" ] = "1.0" + +TOOL.ClientConVar[ "tracerspread" ] = "0.02" +TOOL.ClientConVar[ "tracercount" ] = "1" +TOOL.ClientConVar[ "leavebulletholes" ] = "1" + +TOOL.ClientConVar[ "propmodel" ] = "models/weapons/w_smg1.mdl" +TOOL.ClientConVar[ "propangle" ] = "2" +TOOL.ClientConVar[ "propinvis" ] = "0" + +TOOL.ClientConVar[ "numpadkey" ] = "52" +TOOL.ClientConVar[ "toggle" ] = "1" +TOOL.ClientConVar[ "starton" ] = "1" + +TOOL.Information = { + { name = "left0", stage = 0, icon = "gui/lmb.png" }, + { name = "middle0", stage = 0, icon = "gui/mmb.png" }, + { name = "right0", stage = 0, icon = "gui/rmb.png" }, + { name = "reload0", stage = 0, icon = "gui/r.png" }, +} + +if ( CLIENT ) then + language.Add( "tool.particlecontrol_tracer.name", "Adv. Particle Controller - Tracers" ) + language.Add( "tool.particlecontrol_tracer.desc", "Attach tracer effects to things" ) + language.Add( "tool.particlecontrol_tracer.help", "Tracer effects are particles that fire out like bullets, with one end at the attachment and the other end where the \"bullet\" hits something." ) + + language.Add( "tool.particlecontrol_tracer.left0", "Add a tracer effect to an object" ) + language.Add( "tool.particlecontrol_tracer.middle0", "Scroll through an object's attachments" ) + language.Add( "tool.particlecontrol_tracer.right0", "Attach a new prop with the tracer effect on it" ) + language.Add( "tool.particlecontrol_tracer.reload0", "Remove all tracer effects from an object" ) +end + +util.PrecacheSound("weapons/pistol/pistol_empty.wav") + + + + +function TOOL:LeftClick( trace ) + + local effectname = self:GetClientInfo( "effectname", 0 ) + local attachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + + local repeatrate = self:GetClientNumber( "repeatrate", 0 ) + + local numpadkey = self:GetClientNumber( "numpadkey", 0 ) + local toggle = self:GetClientNumber( "toggle", 0 ) + local starton = self:GetClientNumber( "starton", 0 ) + + //local utileffectinfo = Vector( self:GetClientNumber( "utileffect_scale", 0 ), self:GetClientNumber( "utileffect_magnitude", 0 ), self:GetClientNumber( "utileffect_radius", 0 ) ) + local colorinfo = nil + if self:GetClientNumber( "color_enabled", 0 ) == 1 then + if self:GetClientNumber( "color_outofone", 0 ) == 1 then + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 0 ) + end + end + + local tracerspread = self:GetClientNumber( "tracerspread", 0 ) + local tracercount = self:GetClientNumber( "tracercount", 0 ) + local leavebulletholes = self:GetClientNumber( "leavebulletholes", 0 ) + local effectlifetime = self:GetClientNumber( "effectlifetime", 0 ) + + local impactinfo = nil + if self:GetClientNumber( "impactfx_enabled", 0 ) == 1 then + impactinfo = { + effectname = self:GetClientInfo( "impactfx_effectname", 0 ), + utileffectinfo = Vector( self:GetClientNumber( "impactfx_utileffect_scale", 0 ), self:GetClientNumber( "impactfx_utileffect_magnitude", 0 ), self:GetClientNumber( "impactfx_utileffect_radius", 0 ) ), + } + if self:GetClientNumber( "impactfx_color_enabled", 0 ) == 1 then + if self:GetClientNumber( "impactfx_color_outofone", 0 ) == 1 then + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 0 ) + end + end + end + + local ply = self:GetOwner() + + + + if ( trace.Entity:IsValid() ) then + if CLIENT then return true end + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + AttachParticleControllerTracer( ply, trace.Entity, { NewTable = { + EffectName = effectname, + AttachNum = attachnum, + + RepeatRate = repeatrate, + + Toggle = toggle, + StartOn = starton, + NumpadKey = numpadkey, + + ColorInfo = colorinfo, + + TracerSpread = tracerspread, + TracerCount = tracercount, + LeaveBulletHoles = leavebulletholes, + EffectLifetime = effectlifetime, + + ImpactInfo = impactinfo, + } } ) + return true + end + +end + + + + +function TOOL:RightClick( trace ) + + local effectname = self:GetClientInfo( "effectname", 0 ) + local attachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + + local repeatrate = self:GetClientNumber( "repeatrate", 0 ) + + local numpadkey = self:GetClientNumber( "numpadkey", 0 ) + local toggle = self:GetClientNumber( "toggle", 0 ) + local starton = self:GetClientNumber( "starton", 0 ) + + //local utileffectinfo = Vector( self:GetClientNumber( "utileffect_scale", 0 ), self:GetClientNumber( "utileffect_magnitude", 0 ), self:GetClientNumber( "utileffect_radius", 0 ) ) + local colorinfo = nil + if self:GetClientNumber( "color_enabled", 0 ) == 1 then + if self:GetClientNumber( "color_outofone", 0 ) == 1 then + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + colorinfo = Color( self:GetClientNumber( "color_r", 0 ), self:GetClientNumber( "color_g", 0 ), self:GetClientNumber( "color_b", 0 ), 0 ) + end + end + + local tracerspread = self:GetClientNumber( "tracerspread", 0 ) + local tracercount = self:GetClientNumber( "tracercount", 0 ) + local leavebulletholes = self:GetClientNumber( "leavebulletholes", 0 ) + local effectlifetime = self:GetClientNumber( "effectlifetime", 0 ) + + local impactinfo = nil + if self:GetClientNumber( "impactfx_enabled", 0 ) == 1 then + impactinfo = { + effectname = self:GetClientInfo( "impactfx_effectname", 0 ), + utileffectinfo = Vector( self:GetClientNumber( "impactfx_utileffect_scale", 0 ), self:GetClientNumber( "impactfx_utileffect_magnitude", 0 ), self:GetClientNumber( "impactfx_utileffect_radius", 0 ) ), + } + if self:GetClientNumber( "impactfx_color_enabled", 0 ) == 1 then + if self:GetClientNumber( "impactfx_color_outofone", 0 ) == 1 then + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 1 ) //we're using the alpha value to store color_outofone + else + impactinfo.colorinfo = Color( self:GetClientNumber( "impactfx_color_r", 0 ), self:GetClientNumber( "impactfx_color_g", 0 ), self:GetClientNumber( "impactfx_color_b", 0 ), 0 ) + end + end + end + + local ply = self:GetOwner() + + + + local propmodel = self:GetClientInfo( "propmodel", 0 ) + local propangle = self:GetClientNumber( "propangle", 0 ) + //propangle 1: spawn upright + //propangle 2: spawn at surface angle + + if !util.IsValidModel(propmodel) then return false end + if !util.IsValidProp(propmodel) then return false end + if CLIENT then return true end + + prop = ents.Create( "prop_physics" ) + prop:SetModel( propmodel ) + prop:SetPos( trace.HitPos - trace.HitNormal * prop:OBBMins().z ) + if propangle == 1 then prop:SetAngles(Angle(0,trace.HitNormal:Angle().y,0)) else prop:SetAngles(trace.HitNormal:Angle()) end + prop:SetCollisionGroup(20) //COLLISION_GROUP_NONE, nocollide with everything except world + prop:Spawn() + + local shouldweweld = true //don't weld if... + if ( !util.IsValidPhysicsObject(prop, 0) ) then shouldweweld = false end //the prop doesn't have a phys object + if ( !trace.Entity:IsValid() ) then shouldweweld = false end //the thing we clicked on doesn't exist/is the world + if ( trace.Entity && trace.Entity:IsPlayer() ) then shouldweweld = false end //the thing we clicked on is a player + if ( !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then shouldweweld = false end //the thing we clicked on doesn't have a phys object + if shouldweweld == true then + local const = constraint.Weld( prop, trace.Entity, 0, trace.PhysicsBone, 0, true, true ) + else + if util.IsValidPhysicsObject(prop, 0) then prop:GetPhysicsObject():EnableMotion(false) end + end + + if self:GetClientNumber( "propinvis", 0 ) == 1 then + prop:SetRenderMode(1) //we need to change the render mode so the transparency actually shows up + prop:SetColor( Color(255,255,255,0) ) + duplicator.StoreEntityModifier( prop, "colour", { Color = Color(255,255,255,0), RenderMode = 1, RenderFX = 0 } ) + end + + undo.Create( "prop" ) + undo.AddEntity( prop ) + undo.SetPlayer( ply ) + undo.Finish( "Prop ("..tostring(propmodel)..")" ) + + + + if ( prop:IsValid() ) then + AttachParticleControllerTracer( ply, prop, { NewTable = { + EffectName = effectname, + AttachNum = attachnum, + + RepeatRate = repeatrate, + + Toggle = toggle, + StartOn = starton, + NumpadKey = numpadkey, + + ColorInfo = colorinfo, + + TracerSpread = tracerspread, + TracerCount = tracercount, + LeaveBulletHoles = leavebulletholes, + EffectLifetime = effectlifetime, + + ImpactInfo = impactinfo, + } } ) + return true + end + +end + + + + +function TOOL:Reload( trace ) + + if ( trace.Entity:IsValid() ) then + local fx = false + + if trace.Entity:GetClass() == "prop_effect" and trace.Entity.AttachedEntity then trace.Entity = trace.Entity.AttachedEntity end + + for _, asdf in pairs( ents:GetAll() ) do + if asdf:GetClass() == "particlecontroller_tracer" and asdf:GetParent() == trace.Entity then + if SERVER then asdf:Remove() end + fx = true + end + end + if SERVER then + duplicator.ClearEntityModifier( trace.Entity, "DupeParticleControllerTracer" ) + end + + return fx + end + +end + + + + +if CLIENT then + + local colorborder = Color(0,0,0,255) + local colorselect = Color(0,255,0,255) + local colorunselect = Color(255,255,255,255) + + function TOOL:DrawHUD() + local pl = LocalPlayer() + local tr = pl:GetEyeTrace() + local attachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + + local function DrawHighlightAttachments(ent) + + //If there aren't any attachments, then draw the model origin as selected and stop here: + if !ent:GetAttachments() or !ent:GetAttachments()[1] then + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + + return + end + + + //Draw the unselected model origin, if applicable: + if ent:GetAttachments()[attachnum] then + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder) + end + + //Draw the unselected attachment points: + for _, table in pairs(ent:GetAttachments()) do + local _pos,_ang = ent:GetAttachment(table.id).Pos,ent:GetAttachment(table.id).Ang + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + if table.id != attachnum then + draw.RoundedBox(0,_pos.x - 2,_pos.y - 2,4,4,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorunselect) + draw.SimpleTextOutlined(table.id ..": ".. table.name,"Default",textpos.x,textpos.y,colorunselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,1,colorborder) + end + end + + //Draw the selected attachment point or model origin last, so it renders above all the others: + if !ent:GetAttachments()[attachnum] then + //Model origin + local _pos,_ang = ent:GetPos(), ent:GetAngles() + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined("0: (origin)","Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + else + //Attachment + local _pos,_ang = ent:GetAttachment(attachnum).Pos,ent:GetAttachment(attachnum).Ang + local _pos = _pos:ToScreen() + local textpos = {x = _pos.x+5,y = _pos.y-5} + + draw.RoundedBox(0,_pos.x - 3,_pos.y - 3,6,6,colorborder) + draw.RoundedBox(0,_pos.x - 1,_pos.y - 1,2,2,colorselect) + draw.SimpleTextOutlined(attachnum ..": ".. ent:GetAttachments()[attachnum].name,"Default",textpos.x,textpos.y,colorselect,TEXT_ALIGN_LEFT,TEXT_ALIGN_BOTTOM,2,colorborder) + end + end + + if IsValid(tr.Entity) and tr.Entity == self.HighlightedEnt then + DrawHighlightAttachments(self.HighlightedEnt) + return end + + if IsValid(tr.Entity) and tr.Entity != self.HighlightedEnt then + //unhighlight the old ent if it exists + if self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + + //highlight the new ent + self.HighlightedEnt = tr.Entity + end + + if !IsValid(tr.Entity) and self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + end + + function TOOL:Holster() + if self.HighlightedEnt != nil then + self.HighlightedEnt = nil + end + end + + + + + //All credit for the toolgun scroll wheel code goes to the Wiremod devs. You guys are the best. + local function get_active_tool(ply, tool) + -- find toolgun + local activeWep = ply:GetActiveWeapon() + if not IsValid(activeWep) or activeWep:GetClass() ~= "gmod_tool" or activeWep.Mode ~= tool then return end + + return activeWep:GetToolObject(tool) + end + + local function hookfunc( ply, bind, pressed ) + if not pressed then return end + if bind == "invnext" then + local self = get_active_tool(ply, "particlecontrol_tracer") + if not self then return end + + return self:ScrollDown(ply:GetEyeTraceNoCursor()) + elseif bind == "invprev" then + local self = get_active_tool(ply, "particlecontrol_tracer") + if not self then return end + + return self:ScrollUp(ply:GetEyeTraceNoCursor()) + end + end + + if game.SinglePlayer() then -- wtfgarry (have to have a delay in single player or the hook won't get added) + timer.Simple(5,function() hook.Add( "PlayerBindPress", "particlecontrol_tracer_playerbindpress", hookfunc ) end) + else + hook.Add( "PlayerBindPress", "particlecontrol_tracer_playerbindpress", hookfunc ) + end + //End shamefully copied code here. + + function TOOL:Scroll(trace,dir) + if !IsValid(self.HighlightedEnt) then return end + + local attachcount = 0 + if self.HighlightedEnt:GetAttachments() then attachcount = table.Count(self.HighlightedEnt:GetAttachments()) end + local oldattachnum = self:GetOwner():GetInfoNum( "particlecontrol_attachnum", 0 ) //use the standard tool's attachnum var + if oldattachnum > attachcount then oldattachnum = 0 end + local attachnum = oldattachnum + dir + + if attachnum < 0 then attachnum = attachcount end + if attachnum > attachcount then attachnum = 0 end + RunConsoleCommand("particlecontrol_attachnum", tostring(attachnum)) //use the standard tool's attachnum var + self:GetOwner():EmitSound("weapons/pistol/pistol_empty.wav") + return true + end + function TOOL:ScrollUp(trace) return self:Scroll(trace,-1) end + function TOOL:ScrollDown(trace) return self:Scroll(trace,1) end + +end + + + + +if SERVER then + + local function SpawnParticleControllerTracer(ply, ent, DataTable) + + if DataTable == nil or DataTable == {} or DataTable.EffectName == nil or ent == nil or !IsValid(ent) then return end + + + local ParticleControlTracer = ents.Create( "particlecontroller_tracer" ) + ParticleControlTracer:SetPos(ent:GetPos()) + ParticleControlTracer:SetAngles(ent:GetAngles()) + ParticleControlTracer:SetParent(ent) + ent:DeleteOnRemove(ParticleControlTracer) + + ParticleControlTracer:SetTargetEnt(ent) + ParticleControlTracer:SetEffectName(DataTable.EffectName) + ParticleControlTracer:SetAttachNum(DataTable.AttachNum) + //ParticleControlTracer:SetUtilEffectInfo(DataTable.UtilEffectInfo) + if DataTable.ColorInfo != nil then ParticleControlTracer:SetColor(DataTable.ColorInfo) else ParticleControlTracer:SetColor( Color(0,0,0,0) ) end + + ParticleControlTracer:SetTracerSpread(DataTable.TracerSpread) + ParticleControlTracer:SetTracerCount(DataTable.TracerCount) + if DataTable.LeaveBulletHoles == 1 or DataTable.LeaveBulletHoles == true then ParticleControlTracer:SetLeaveBulletHoles(true) else ParticleControlTracer:SetLeaveBulletHoles(false) end + ParticleControlTracer:SetEffectLifetime(DataTable.EffectLifetime or 1.00) //old dupes will have nil + + if DataTable.ImpactInfo != nil then + ParticleControlTracer:SetImpact_EffectName(DataTable.ImpactInfo.effectname) + ParticleControlTracer:SetImpact_UtilEffectInfo(DataTable.ImpactInfo.utileffectinfo) + if DataTable.ImpactInfo.colorinfo != nil then + local impactcolor = Vector(DataTable.ImpactInfo.colorinfo.r, DataTable.ImpactInfo.colorinfo.g, DataTable.ImpactInfo.colorinfo.b) + if DataTable.ImpactInfo.colorinfo.a then + impactcolor = impactcolor / 255 + end + ParticleControlTracer:SetImpact_ColorInfo( impactcolor ) + else + ParticleControlTracer:SetImpact_ColorInfo( Vector(0,0,0) ) + end + else + ParticleControlTracer:SetImpact_EffectName("") + end + + ParticleControlTracer:SetRepeatRate(DataTable.RepeatRate) + + + if DataTable.StartOn == 1 or DataTable.StartOn == true then ParticleControlTracer:SetActive(true) else ParticleControlTracer:SetActive(false) end + if DataTable.Toggle == 1 or DataTable.Toggle == true then ParticleControlTracer:SetToggle(true) else ParticleControlTracer:SetToggle(false) end + ParticleControlTracer:SetNumpadKey(DataTable.NumpadKey) + + numpad.OnDown( ply, DataTable.NumpadKey, "Particle_Press", ParticleControlTracer ) + numpad.OnUp( ply, DataTable.NumpadKey, "Particle_Release", ParticleControlTracer ) + ParticleControlTracer:SetNumpadState("") + + + ParticleControlTracer:Spawn() + ParticleControlTracer:Activate() + + end + + + function AttachParticleControllerTracer( ply, ent, Data ) + + if Data.NewTable then + SpawnParticleControllerTracer(ply, ent, Data.NewTable) + + local dupetable = {} + if ent.EntityMods and ent.EntityMods.DupeParticleControllerTracer then dupetable = ent.EntityMods.DupeParticleControllerTracer end + table.insert(dupetable, Data.NewTable) + duplicator.StoreEntityModifier( ent, "DupeParticleControllerTracer", dupetable ) + return end + + end + + + function DupeParticleControllerTracer( ply, ent, Data ) + + local override = hook.Run('CanTool', ply, { Entity = ent }, 'particlecontrol_tracer') + if override == false then return end + + //due to a problem with the easy bonemerge tool that causes entity modifiers to be applied TWICE, we need to remove the effects that were added the first time + for _, asdf in pairs( ents:GetAll() ) do + if asdf:GetClass() == "particlecontroller_tracer" and asdf:GetParent() == ent then + asdf:Remove() + end + end + + for _, DataTable in pairs (Data) do + SpawnParticleControllerTracer(ply, ent, DataTable) + end + + end + duplicator.RegisterEntityModifier( "DupeParticleControllerTracer", DupeParticleControllerTracer ) + +end + + + + +//we're still testing out a lot of stuff with the cpanel, so let's add a way to refresh it by reselecting the tool +--[[ +TOOL.ClientConVar[ "refresh" ] = 1 +function TOOL:Think() + if SERVER then return end + if self:GetClientNumber("refresh") == 1 then + RunConsoleCommand("particlecontrol_tracer_refresh", "0"); + //refresh the cpanel + local panel = controlpanel.Get( "particlecontrol_tracer" ) + if ( !panel ) then return end + panel:ClearControls() + self.BuildCPanel(panel) + end +end +function TOOL:Deploy() + RunConsoleCommand("particlecontrol_tracer_refresh", "1"); +end +]] + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel(panel) + + panel:AddControl( "Header", { Description = "#tool.particlecontrol_tracer.help" } ) + + //Presets + panel:AddControl( "ComboBox", { + MenuButton = 1, + Folder = "particlecontrol_tracer", + Options = { + //[ "#preset.default" ] = ConVarsDefault + [ "Example: Pulse Rifle" ] = ConVarsDefault, + [ "Example: Generic Bullets" ] = { particlecontrol_tracer_color_b = "0", particlecontrol_tracer_color_enabled = "0", particlecontrol_tracer_color_g = "20", particlecontrol_tracer_color_outofone = "0", particlecontrol_tracer_color_r = "255", particlecontrol_tracer_effectlifetime = "1.000000", particlecontrol_tracer_effectname = "!UTILEFFECT!Tracer", particlecontrol_tracer_impactfx_color_b = "0", particlecontrol_tracer_impactfx_color_enabled = "0", particlecontrol_tracer_impactfx_color_g = "20", particlecontrol_tracer_impactfx_color_outofone = "0", particlecontrol_tracer_impactfx_color_r = "255", particlecontrol_tracer_impactfx_effectname = "!UTILEFFECT!AR2Impact", particlecontrol_tracer_impactfx_enabled = "0", particlecontrol_tracer_impactfx_utileffect_magnitude = "1", particlecontrol_tracer_impactfx_utileffect_radius = "10", particlecontrol_tracer_impactfx_utileffect_scale = "1", particlecontrol_tracer_leavebulletholes = "1", particlecontrol_tracer_numpadkey = "52", + particlecontrol_tracer_propangle = "2", particlecontrol_tracer_propinvis = "0", particlecontrol_tracer_propmodel = "models/weapons/w_smg1.mdl", particlecontrol_tracer_repeatrate = "0.080000", particlecontrol_tracer_starton = "1", particlecontrol_tracer_toggle = "1", particlecontrol_tracer_tracercount = "1", particlecontrol_tracer_tracerspread = "0.050000" }, + [ "Example: Toolgun" ] = { particlecontrol_tracer_color_b = "0", particlecontrol_tracer_color_enabled = "0", particlecontrol_tracer_color_g = "20", particlecontrol_tracer_color_outofone = "0", particlecontrol_tracer_color_r = "255", particlecontrol_tracer_effectlifetime = "1.000000", particlecontrol_tracer_effectname = "!UTILEFFECT!ToolTracer", particlecontrol_tracer_impactfx_color_b = "0", particlecontrol_tracer_impactfx_color_enabled = "0", particlecontrol_tracer_impactfx_color_g = "20", particlecontrol_tracer_impactfx_color_outofone = "0", particlecontrol_tracer_impactfx_color_r = "255", particlecontrol_tracer_impactfx_effectname = "!UTILEFFECT!selection_indicator", particlecontrol_tracer_impactfx_enabled = "1", particlecontrol_tracer_impactfx_utileffect_magnitude = "1", particlecontrol_tracer_impactfx_utileffect_radius = "10", particlecontrol_tracer_impactfx_utileffect_scale = "1", particlecontrol_tracer_leavebulletholes = "0", particlecontrol_tracer_numpadkey = "52", + particlecontrol_tracer_propangle = "2", particlecontrol_tracer_propinvis = "0", particlecontrol_tracer_propmodel = "models/weapons/w_smg1.mdl", particlecontrol_tracer_repeatrate = "1", particlecontrol_tracer_starton = "1", particlecontrol_tracer_toggle = "1", particlecontrol_tracer_tracercount = "1", particlecontrol_tracer_tracerspread = "0" }, + }, + CVars = table.GetKeys( ConVarsDefault ) + } ) + + + + AddParticleBrowserTracer(panel, { + name = "Tracer Effect", + commands = { + effectname = "particlecontrol_tracer_effectname", + color = "particlecontrol_tracer_color", + }, + }) + + + + //panel:AddControl( "Label", { Text = "" } ) + //panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl( "Checkbox", { Label = "Enable impact effects?", Command = "particlecontrol_tracer_impactfx_enabled" } ) + + AddParticleBrowser(panel, { + name = "Impact Effect", + commands = { + effectname = "particlecontrol_tracer_impactfx_effectname", + color = "particlecontrol_tracer_impactfx_color", + utileffect = "particlecontrol_tracer_impactfx_utileffect", + + enabled = "particlecontrol_tracer_impactfx_enabled", + }, + }) + + + + //panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl("Slider", { + Label = "Attachment", + Type = "Integer", + Min = "0", + Max = "10", + Command = "particlecontrol_attachnum", //use the standard tool's attachnum var + }) + panel:ControlHelp( "Attachment point on the model to fire tracers from. Set to 0 to fire from the model origin." ) + + panel:AddControl("Slider", { + Label = "Repeat Rate", + Type = "Float", + Min = "0", + Max = "1", + Command = "particlecontrol_tracer_repeatrate" + }) + panel:ControlHelp( "How often the tracer fires. Set to 0 to not repeat." ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl("Slider", { + Label = "Tracer Spread", + Type = "Float", + Min = "0", + Max = "1", + Command = "particlecontrol_tracer_tracerspread" + }) + panel:ControlHelp( "Each unit is 90 degrees of spread - you can type in 2 for 180 degrees or even 4 for 360 degrees." ) + + panel:AddControl("Slider", { + Label = "Tracers per shot", + Type = "Integer", + Min = "1", + Max = "10", + Command = "particlecontrol_tracer_tracercount" + }) + + panel:AddControl( "Checkbox", { Label = "Leave bullet holes?", Command = "particlecontrol_tracer_leavebulletholes" } ) + + panel:AddControl("Slider", { + Label = "Effect Lifetime", + Type = "Float", + Min = "0.5", + Max = "5", + Command = "particlecontrol_tracer_effectlifetime" + }) + //panel:ControlHelp( "Number of seconds before tracer and impact effects are removed." ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + local modellist = { Label = "Prop:", ConVar = "particlecontrol_tracer_propmodel", Category = "Prop", Height = 1, Models = {} } + modellist.Models["models/hunter/plates/plate025x025.mdl"] = {} + modellist.Models["models/hunter/plates/plate.mdl"] = {} + modellist.Models["models/weapons/w_smg1.mdl"] = {} + modellist.Models["models/weapons/w_models/w_shotgun.mdl"] = {} + + panel:AddControl( "PropSelect", modellist ) + + panel:AddControl( "ComboBox", { + Label = "Prop Angle", + MenuButton = "0", + Options = { + ["Spawn upright"] = { particlecontrol_tracer_propangle = "1" }, + ["Spawn at surface angle"] = { particlecontrol_tracer_propangle = "2" } + } + }) + + panel:AddControl( "Checkbox", { Label = "Invisible prop (particles only)", Command = "particlecontrol_tracer_propinvis" } ) + + + + panel:AddControl( "Label", { Text = "" } ) + panel:AddControl( "Label", { Text = "" } ) + + + + panel:AddControl( "Numpad", { + Label = "Effect Key", + Command = "particlecontrol_tracer_numpadkey", + ButtonSize = 22 + }) + + panel:AddControl( "Checkbox", { Label = "Toggle", Command = "particlecontrol_tracer_toggle" } ) + + panel:AddControl( "Checkbox", { Label = "Start on?", Command = "particlecontrol_tracer_starton" } ) + +end \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/physprop.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/physprop.lua new file mode 100644 index 0000000..8911654 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/physprop.lua @@ -0,0 +1,75 @@ +TOOL.Category = "Construction" +TOOL.Name = "#tool.physprop.name" + +TOOL.ClientConVar[ "gravity_toggle" ] = "1" +TOOL.ClientConVar[ "material" ] = "metal_bouncy" + +TOOL.Information = { { name = "left" } } + +function TOOL:LeftClick( trace ) + if ( !IsValid( trace.Entity ) ) then return false end + if ( trace.Entity:IsPlayer() || trace.Entity:IsWorld() ) then return false end + + -- Make sure there's a physics object to manipulate + if ( SERVER && !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then return false end + + -- Client can bail out here and assume we're going ahead + if ( CLIENT ) then return true end + + -- Get the entity/bone from the trace + local ent = trace.Entity + local Bone = trace.PhysicsBone + + -- Get client's CVars + local gravity = self:GetClientNumber( "gravity_toggle" ) == 1 + local material = self:GetClientInfo( "material" ) + + -- Set the properties + construct.SetPhysProp( self:GetOwner(), ent, Bone, nil, { GravityToggle = gravity, Material = material } ) + -- Set netvar for dbg + ent:SetNetVar("physprop", material) + + DoPropSpawnedEffect( ent ) + + return true + +end + +local ConVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel( CPanel ) + + CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "physprop", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + CPanel:AddControl( "ListBox", { Label = "#tool.physprop.material", Options = list.Get( "PhysicsMaterials" ) } ) + + CPanel:AddControl( "CheckBox", { Label = "#tool.physprop.gravity", Command = "physprop_gravity_toggle" } ) + +end + +list.Set( "PhysicsMaterials", "#physprop.metalbouncy", { physprop_material = "metal_bouncy" } ) +list.Set( "PhysicsMaterials", "#physprop.metal", { physprop_material = "metal" } ) +list.Set( "PhysicsMaterials", "Земля", { physprop_material = "dirt" } ) +list.Set( "PhysicsMaterials", "Грязь", { physprop_material = "slipperyslime" } ) +list.Set( "PhysicsMaterials", "#physprop.wood", { physprop_material = "wood" } ) +list.Set( "PhysicsMaterials", "#physprop.glass", { physprop_material = "glass" } ) +list.Set( "PhysicsMaterials", "#physprop.concrete", { physprop_material = "concrete_block" } ) +list.Set( "PhysicsMaterials", "#physprop.ice", { physprop_material = "ice" } ) +list.Set( "PhysicsMaterials", "#physprop.rubber", { physprop_material = "rubber" } ) +list.Set( "PhysicsMaterials", "#physprop.paper", { physprop_material = "paper" } ) +list.Set( "PhysicsMaterials", "#physprop.flesh", { physprop_material = "zombieflesh" } ) +list.Set( "PhysicsMaterials", "#physprop.superice", { physprop_material = "gmod_ice" } ) +list.Set( "PhysicsMaterials", "#physprop.superbouncy", { physprop_material = "gmod_bouncy" } ) +-- Custom materials +list.Set( "PhysicsMaterials", "Забор", { physprop_material = "fence" } ) +list.Set( "PhysicsMaterials", "Трава", { physprop_material = "grass" } ) +list.Set( "PhysicsMaterials", "Гравий", { physprop_material = "gravel" } ) +list.Set( "PhysicsMaterials", "Металлическая решетка", { physprop_material = "metal_grate" } ) +list.Set( "PhysicsMaterials", "Песок", { physprop_material = "sand" } ) +list.Set( "PhysicsMaterials", "Хлюпанье по воде", { physprop_material = "water_slosh" } ) +list.Set( "PhysicsMaterials", "Плаванье в воде", { physprop_material = "water_wade" } ) +list.Set( "PhysicsMaterials", "Кафельная плитка", { physprop_material = "tile" } ) +list.Set( "PhysicsMaterials", "Деревянная панель", { physprop_material = "wood_panel" } ) +list.Set( "PhysicsMaterials", "Металлическая труба", { physprop_material = "duct_metal" } ) +list.Set( "PhysicsMaterials", "Деревянный ящик", { physprop_material = "wood_box" } ) +list.Set( "PhysicsMaterials", "Металлическая лестница", { physprop_material = "wood_box" } ) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/precision.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/precision.lua new file mode 100644 index 0000000..bc7522f --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/precision.lua @@ -0,0 +1,1683 @@ + +TOOL.Category = "Dobrograd" +TOOL.Name = "#Precision" +TOOL.Command = nil +TOOL.ConfigName = "" + +TOOL.ClientConVar[ "mode" ] = "1" +TOOL.ClientConVar[ "user" ] = "1" + +TOOL.ClientConVar[ "freeze" ] = "1" +TOOL.ClientConVar[ "nocollide" ] = "1" +TOOL.ClientConVar[ "nocollideall" ] = "0" +TOOL.ClientConVar[ "rotation" ] = "15" +TOOL.ClientConVar[ "rotate" ] = "1" +TOOL.ClientConVar[ "offset" ] = "0" +TOOL.ClientConVar[ "forcelimit" ] = "0" +TOOL.ClientConVar[ "torquelimit" ] = "0" +TOOL.ClientConVar[ "friction" ] = "0" +TOOL.ClientConVar[ "width" ] = "1" +TOOL.ClientConVar[ "offsetpercent" ] = "1" +TOOL.ClientConVar[ "removal" ] = "0" +TOOL.ClientConVar[ "move" ] = "1" +TOOL.ClientConVar[ "physdisable" ] = "0" +TOOL.ClientConVar[ "ShadowDisable" ] = "0" +TOOL.ClientConVar[ "allowphysgun" ] = "0" +TOOL.ClientConVar[ "autorotate" ] = "0" +TOOL.ClientConVar[ "entirecontrap" ] = "0" +TOOL.ClientConVar[ "nudge" ] = "25" +TOOL.ClientConVar[ "nudgepercent" ] = "1" +TOOL.ClientConVar[ "disablesliderfix" ] = "0" + +--adv ballsocket +TOOL.ClientConVar[ "XRotMin" ] = "-180" +TOOL.ClientConVar[ "XRotMax" ] = "180" +TOOL.ClientConVar[ "YRotMin" ] = "-180" +TOOL.ClientConVar[ "YRotMax" ] = "180" +TOOL.ClientConVar[ "ZRotMin" ] = "-180" +TOOL.ClientConVar[ "ZRotMax" ] = "180" +TOOL.ClientConVar[ "XRotFric" ] = "0" +TOOL.ClientConVar[ "YRotFric" ] = "0" +TOOL.ClientConVar[ "ZRotFric" ] = "0" +TOOL.ClientConVar[ "FreeMov" ] = "0" + +--Removal +TOOL.ClientConVar[ "removal_nocollide" ] = "1" +TOOL.ClientConVar[ "removal_weld" ] = "1" +TOOL.ClientConVar[ "removal_axis" ] = "1" +TOOL.ClientConVar[ "removal_ballsocket" ] = "1" +TOOL.ClientConVar[ "removal_advballsocket" ]= "1" +TOOL.ClientConVar[ "removal_slider" ] = "1" +TOOL.ClientConVar[ "removal_parent" ] = "1" +TOOL.ClientConVar[ "removal_other" ] = "1" + + +TOOL.ClientConVar[ "enablefeedback" ] = "1" +TOOL.ClientConVar[ "chatfeedback" ] = "1" +TOOL.ClientConVar[ "nudgeundo" ] = "0" +TOOL.ClientConVar[ "moveundo" ] = "1" +TOOL.ClientConVar[ "rotateundo" ] = "1" + +function TOOL:DoParent( Ent1, Ent2 ) + local TempEnt = Ent2 + if !(Ent1 && Ent1:IsValid() && Ent1:EntIndex() != 0) then + self:SendMessage( "Oops, First Target was world or something invalid" ) + return + end + if !(Ent2 && Ent2:IsValid() && Ent2:EntIndex() != 0) then + self:SendMessage( "Oops, Second Target was world or something invalid" ) + return + end + if ( Ent1 == Ent2 ) then + self:SendMessage( "Oops, Can't parent something to itself" ) + return + end + Ent1:SetMoveType(MOVETYPE_NONE) + local disablephysgun = self:GetClientNumber( "allowphysgun" ) == 0 + Ent1.PhysgunDisabled = disablephysgun + Ent1:SetUnFreezable( disablephysgun ) + local Phys1 = Ent1:GetPhysicsObject() + if Phys1:IsValid() then + Phys1:EnableCollisions( false ) + end + while true do + if ( !TempEnt:GetParent():IsValid() ) then + Ent1:SetParent( Ent2 ) + if self:GetClientNumber( "entirecontrap" ) == 0 then self:SendMessage( "Parent Set." ) end + Phys1:Wake() + break + elseif ( TempEnt:GetParent() == Ent1 ) then + UndoParent( TempEnt ) + timer.Simple( 0.1, function()--delay to stop crash + Ent1.SetParent( Ent1, Ent2) + end) + self:SendMessage( "Oops, Closed Parent Loop Detected; Broken loop and set parent." ) + break + else + TempEnt = TempEnt:GetParent() + end + end + Phys1:Wake() + --Phys1:UpdateShadow(Ent1:GetAngles(),Ent1:GetAngles()) +end + +function TOOL:UndoParent( Ent1 ) + Ent1:SetParent( nil ) + Ent1:SetMoveType(MOVETYPE_VPHYSICS) + Ent1.PhysgunDisabled = false + Ent1:SetUnFreezable( false ) + local Phys1 = Ent1:GetPhysicsObject() + if Phys1:IsValid() then + Phys1:EnableCollisions( true ) + Phys1:Wake() + --Phys1:UpdateShadow(Ent1:GetAngles(),Ent1:GetAngles()) + end +end + +function TOOL:DoApply(CurrentEnt, FirstEnt, autorotate, nocollideall, ShadowDisable ) + local CurrentPhys = CurrentEnt:GetPhysicsObject() + + --local col = CurrentEnt:GetCollisionGroup() + --col = 19 + --CurrentEnt:SetCollisionGroup(col) + --self:SendMessage("New group: "..col) + + --if ( CurrentPhys:IsDragEnabled() ) then + --end + --CurrentPhys:SetAngleDragCoefficient(1.05) + --CurrentPhys:SetDragCoefficient(1.05) + + if ( autorotate ) then + if ( CurrentEnt == FirstEnt ) then--Snap-rotate original object first. Rest needs to rotate around it. + local angle = CurrentPhys:RotateAroundAxis( Vector( 0, 0, 1 ), 0 ) + self.anglechange = Vector( angle.p - (math.Round(angle.p/45))*45, angle.r - (math.Round(angle.r/45))*45, angle.y - (math.Round(angle.y/45))*45 ) + if ( table.Count(self.TaggedEnts) == 1 ) then + angle.p = (math.Round(angle.p/45))*45 + angle.r = (math.Round(angle.r/45))*45--Only rotate on these axies if it's singular. + end + angle.y = (math.Round(angle.y/45))*45 + CurrentPhys:SetAngles( angle ) + else + local distance = math.sqrt(math.pow((CurrentEnt:GetPos().X-FirstEnt:GetPos().X),2)+math.pow((CurrentEnt:GetPos().Y-FirstEnt:GetPos().Y),2)) + local theta = math.atan((CurrentEnt:GetPos().Y-FirstEnt:GetPos().Y) / (CurrentEnt:GetPos().X-FirstEnt:GetPos().X)) - math.rad(self.anglechange.Z) + if (CurrentEnt:GetPos().X-FirstEnt:GetPos().X) < 0 then + CurrentEnt:SetPos( Vector( FirstEnt:GetPos().X - (distance*(math.cos(theta))), FirstEnt:GetPos().Y - (distance*(math.sin(theta))), CurrentEnt:GetPos().Z ) ) + else + CurrentEnt:SetPos( Vector( FirstEnt:GetPos().X + (distance*(math.cos(theta))), FirstEnt:GetPos().Y + (distance*(math.sin(theta))), CurrentEnt:GetPos().Z ) ) + end + CurrentPhys:SetAngles( CurrentPhys:RotateAroundAxis( Vector( 0, 0, -1 ), self.anglechange.Z ) ) + end + end + + CurrentPhys:EnableCollisions( !nocollideall ) + CurrentEnt:DrawShadow( !ShadowDisable ) + if physdis then + CurrentEnt:SetMoveType(MOVETYPE_NONE) + CurrentEnt.PhysgunDisabled = disablephysgun + CurrentEnt:SetUnFreezable( disablephysgun ) + else + CurrentEnt:SetMoveType(MOVETYPE_VPHYSICS) + CurrentEnt.PhysgunDisabled = false + CurrentEnt:SetUnFreezable( false ) + end + CurrentPhys:Wake() +end + +function TOOL:CreateUndo(constraint,undoname) + if (constraint) then + undo.Create(undoname) + undo.AddEntity( constraint ) + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + self:GetOwner():AddCleanup( "constraints", constraint ) + end +end + +function TOOL:UndoRepairToggle() + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + if !(CurrentEnt == Ent2 ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() && !CurrentEnt:GetParent():IsValid() ) then--parent? + if ( CurrentEnt:GetPhysicsObjectCount() < 2 ) then --not a ragdoll + if ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_WORLD ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_NONE ) + elseif ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_NONE ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + if ( speeddamp == 0 && angledamp == 0 ) then + CurrentPhys:SetDamping( 5, 5 ) + elseif ( speeddamp == 5 && angledamp == 5 ) then + CurrentPhys:SetDamping( 0, 0 ) + end + CurrentPhys:Wake() + end + end + end + end + end + self.RepairTodo = false +end + +function TOOL:DoConstraint(mode) + self:SetStage(0) + -- Get information we're about to use + local Ent1, Ent2 = self:GetEnt(1), self:GetEnt(2) + + if ( !Ent1:IsValid() || CLIENT ) then + self:ClearObjects() + return false--Something happened to original target, don't continue + end + -- Get client's CVars + local forcelimit = self:GetClientNumber( "forcelimit", 0 ) + local freeze = util.tobool( self:GetClientNumber( "freeze", 1 ) ) + local nocollide = self:GetClientNumber( "nocollide", 0 ) + local nocollideall = util.tobool( self:GetClientNumber( "nocollideall", 0 ) ) + local torquelimit = self:GetClientNumber( "torquelimit", 0 ) + local width = self:GetClientNumber( "width", 1 ) + local friction = self:GetClientNumber( "friction", 0 ) + local physdis = util.tobool( self:GetClientNumber( "physdisable", 0 ) ) + local ShadowDisable = util.tobool( self:GetClientNumber( "ShadowDisable", 0 ) ) + local autorotate = util.tobool(self:GetClientNumber( "autorotate",1 )) + local removal_nocollide = util.tobool(self:GetClientNumber( "removal_nocollide",1 )) + local removal_weld = util.tobool(self:GetClientNumber( "removal_weld",1 )) + local removal_axis = util.tobool(self:GetClientNumber( "removal_axis",1 )) + local removal_ballsocket = util.tobool(self:GetClientNumber( "removal_ballsocket",1 )) + local removal_advballsocket = util.tobool(self:GetClientNumber( "removal_advballsocket",1 )) + local removal_slider = util.tobool(self:GetClientNumber( "removal_slider",1 )) + local removal_parent = util.tobool(self:GetClientNumber( "removal_parent",1 )) + local removal_other = util.tobool(self:GetClientNumber( "removal_other",1 )) + local Bone1 = self:GetBone(1) + local LPos1 = self:GetLocalPos(1) + local Bone2 = nil + local LPos2 = nil + if ( Ent2 && (Ent2:IsValid() || Ent2:IsWorld()) ) then + Bone2 = self:GetBone(2) + LPos2 = self:GetLocalPos(2) + end + local Phys1 = self:GetPhys(1) + + local NumApp = 0 + + + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + if !(CurrentEnt == Ent2 ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() && !CurrentEnt:GetParent():IsValid() ) then--parent? + if ( CurrentEnt:GetPhysicsObjectCount() < 2 ) then --not a ragdoll + if ( util.tobool( nocollide ) && (mode == 1 || mode == 3)) then -- not weld/axis/ballsocket or single application + local constraint = constraint.NoCollide(CurrentEnt, Ent2, 0, Bone2) + end + if ( mode == 1 ) then --Apply + self:DoApply( CurrentEnt, Ent1, autorotate, nocollideall, ShadowDisable ) + elseif ( mode == 2 ) then --Rotate + --self:SendMessage("Sorry, No entire contraption rotating... yet") + --return false elseif ( mode == 3 ) then --move + --self:SendMessage("Sorry, No entire contraption moving... yet") + --return false elseif ( mode == 4 ) then --weld + local constr = constraint.Weld( CurrentEnt, Ent2, 0, Bone2, forcelimit, util.tobool( nocollide ) ) + self:CreateUndo(constr,"Precision_Weld") + elseif ( mode == 5 ) then --doaxis + local constr = constraint.Axis( CurrentEnt, Ent2, Bone1, Bone2, LPos1, LPos2, forcelimit, torquelimit, friction, nocollide ) + self:CreateUndo(constr,"Precision_Axis") + elseif ( mode == 6 ) then --ballsocket + -- local constr = constraint.Ballsocket( CurrentEnt, Ent2, 0, Bone2, LPos2, forcelimit, torquelimit, nocollide ) + -- self:CreateUndo(constr,"Precision_Ballsocket") + elseif ( mode == 7 ) then --adv ballsocket + -- local constr = constraint.AdvBallsocket( CurrentEnt, Ent2, 0, Bone2, LPos1, LPos2, forcelimit, torquelimit, self:GetClientNumber( "XRotMin", -180 ), self:GetClientNumber( "YRotMin", -180 ), self:GetClientNumber( "ZRotMin", -180 ), self:GetClientNumber( "XRotMax", 180 ), self:GetClientNumber( "YRotMax", 180 ), self:GetClientNumber( "ZRotMax", 180 ), self:GetClientNumber( "XRotFric", 0 ), self:GetClientNumber( "YRotFric", 0 ), self:GetClientNumber( "ZRotFric", 0 ), self:GetClientNumber( "FreeMov", 0 ), nocollide ) + -- self:CreateUndo(constr,"Precision_Advanced_Ballsocket") + elseif ( mode == 8 ) then --slider + -- local constraint0 = constraint.Slider( CurrentEnt, Ent2, 0, Bone2, LPos1, LPos2, width ) + -- if (constraint0) then + -- undo.Create("Precision_Slider") + -- if ( self:GetClientNumber( "disablesliderfix" ) == 0 ) then + -- local constraint2 = constraint.AdvBallsocket( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0, 0, 0, -180, -180, 0, 180, 180, 50, 0, 0, 1, 0 ) + -- if (constraint2) then + -- undo.AddEntity( constraint2 ) + -- end + -- local constraint3 = constraint.AdvBallsocket( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0, 0, -180, 0, -180, 180, 0, 180, 0, 50, 0, 1, 0 ) + -- if (constraint3) then + -- undo.AddEntity( constraint3 ) + -- end + -- local constraint4 = constraint.AdvBallsocket( Ent1, Ent2, Bone1, Bone2, LPos1, LPos2, 0, 0, -180, -180, 0, 180, 180, 0, 0, 0, 50, 1, 0 ) + -- if (constraint4) then + -- undo.AddEntity( constraint4 ) + -- end + -- end + -- undo.AddEntity( constraint0 ) + -- undo.SetPlayer( self:GetOwner() ) + -- undo.Finish() + -- self:GetOwner():AddCleanup( "constraints", constraint0 ) + -- end + elseif ( mode == 9 ) then --Parent + -- self:DoParent( CurrentEnt, Ent2 ) + elseif ( mode == 10 && !self.RepairTodo ) then--Repair spaz + if ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_WORLD ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_NONE ) + elseif ( CurrentEnt:GetCollisionGroup() == COLLISION_GROUP_NONE ) then + CurrentEnt:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + --CurrentPhys:EnableGravity( !CurrentPhys:IsGravityEnabled() )--Can't disable gravity - sliders would go nuts and disappear. + local speeddamp,angledamp = CurrentPhys:GetDamping() + if ( speeddamp == 0 && angledamp == 0 ) then + CurrentPhys:SetDamping( 5, 5 ) + elseif ( speeddamp == 5 && angledamp == 5 ) then + CurrentPhys:SetDamping( 0, 0 ) + end + CurrentEnt:SetPos(CurrentEnt:GetPos()) + CurrentPhys:Wake() + elseif ( mode == 11 ) then --Removal + if ( CLIENT ) then return true end--? should probably be in more places + if ( removal_nocollide ) then + constraint.RemoveConstraints( CurrentEnt, "NoCollide" ) + CurrentPhys:EnableCollisions(true) + end + if ( removal_weld ) then + constraint.RemoveConstraints( CurrentEnt, "Weld" ) + end + if ( removal_axis ) then + constraint.RemoveConstraints( CurrentEnt, "Axis" ) + end + if ( removal_ballsocket ) then + constraint.RemoveConstraints( CurrentEnt, "Ballsocket" ) + end + if ( removal_advballsocket ) then + constraint.RemoveConstraints( CurrentEnt, "AdvBallsocket" ) + end + if ( removal_slider ) then + constraint.RemoveConstraints( CurrentEnt, "Slider" ) + end + if ( removal_parent) then + if ( CurrentEnt:GetParent():IsValid() ) then + self:UndoParent( CurrentEnt ) + end + end + if ( removal_other ) then + constraint.RemoveConstraints( CurrentEnt, "Elastic" ) + constraint.RemoveConstraints( CurrentEnt, "Hydraulic" ) + constraint.RemoveConstraints( CurrentEnt, "Keepupright" ) + constraint.RemoveConstraints( CurrentEnt, "Motor" ) + constraint.RemoveConstraints( CurrentEnt, "Muscle" ) + constraint.RemoveConstraints( CurrentEnt, "Pulley" ) + constraint.RemoveConstraints( CurrentEnt, "Rope" ) + constraint.RemoveConstraints( CurrentEnt, "Winch" ) + end + end + if ( mode <= 8 ) then + CurrentPhys:EnableMotion( false ) + CurrentPhys:Wake() + end + end + end + end + end + NumApp = NumApp + 1 + end--Next + if ( mode == 1 ) then + self:SendMessage( NumApp .. " items targeted for apply." ) + elseif ( mode == 2 ) then + self:SendMessage( NumApp .. " items targeted for rotate." ) + elseif ( mode == 3 ) then + self:SendMessage( NumApp .. " items targeted for move." ) + elseif ( mode == 4 ) then + self:SendMessage( NumApp .. " items targeted for weld." ) + elseif ( mode == 5 ) then + self:SendMessage( NumApp .. " items targeted for axis." ) + elseif ( mode == 6 ) then + -- self:SendMessage( NumApp .. " items targeted for ballsocket." ) + elseif ( mode == 7 ) then + -- self:SendMessage( NumApp .. " items targeted for adv. ballsocket." ) + elseif ( mode == 8 ) then + -- self:SendMessage( NumApp .. " items targeted for slider." ) + elseif ( mode == 9 ) then + -- self:SendMessage( NumApp .. " items targeted for parenting." ) + elseif ( mode == 10 ) then + self:SendMessage( NumApp .. " items targeted for repair." ) + elseif ( mode == 11 ) then + self:SendMessage( NumApp .. " items targeted for constraint removal." ) + end + + + if ( mode == 10 ) then + self.RepairTodo = true + timer.Simple( 1.0, function() + self:ClearSelection() + end) + else + self:ClearSelection() + end + -- Clear the objects so we're ready to go again + self:ClearObjects() +end + +function TOOL:SendMessage( message ) + if ( self:GetClientNumber( "enablefeedback" ) == 0 ) then return end + if ( self:GetClientNumber( "chatfeedback" ) == 1 ) then + self:GetOwner():PrintMessage( HUD_PRINTTALK, "Tool: " .. message ) + else + self:GetOwner():PrintMessage( HUD_PRINTCENTER, message ) + end +end + +function TOOL:TargetValidity ( trace, Phys ) if ( SERVER && (!util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) || !Phys:IsValid()) ) then + local mode = self:GetClientNumber( "mode" ) + if ( trace.Entity:GetParent():IsValid() ) then + return 2--Valid parent, but itself isn't + else + return 0--No valid phys + end + elseif ( trace.Entity:IsPlayer() ) then + return 0-- Don't attach players, or to players + elseif ( trace.HitWorld ) then + return 1-- Only allow second click to be here... + else + return 3--Everything seems good + end +end + +function TOOL:StartRotate() + local Ent = self:GetEnt(1) + local Phys = self:GetPhys(1) + local oldposu = Ent:GetPos() + local oldangles = Ent:GetAngles() + + local function MoveUndo( Undo, Entity, oldposu, oldangles ) + if Entity:IsValid() then + Entity:SetAngles( oldangles ) + Entity:SetPos( oldposu ) + end + end + + if ( self:GetClientNumber( "rotateundo" )) then + if SERVER then + undo.Create("Precision_Rotate") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( MoveUndo, Ent, oldposu, oldangles ) + undo.Finish() + end + end + + if IsValid( Phys ) then + Phys:EnableMotion( false ) --else it drifts + end + + local rotation = self:GetClientNumber( "rotation" ) + if ( rotation < 0.02 ) then rotation = 0.02 end + self.axis = self:GetNormal(1) + self.axisY = self.axis:Cross(Ent:GetUp()) + if self:WithinABit( self.axisY, Vector(0,0,0) ) then + self.axisY = self.axis:Cross(Ent:GetForward()) + end + self.axisZ = self.axisY:Cross(self.axis) + self.realdegrees = 0 + self.lastdegrees = -((rotation/2) % rotation) + self.realdegreesY = 0 + self.lastdegreesY = -((rotation/2) % rotation) + self.realdegreesZ = 0 + self.lastdegreesZ = -((rotation/2) % rotation) + self.OldPos = self:GetPos(1)--trace.HitPos +end + +function TOOL:DoMove() + -- Get information we're about to use + local Norm1, Norm2 = self:GetNormal(1), self:GetNormal(2) + local Phys1, Phys2 = self:GetPhys(1), self:GetPhys(2) + + local Ang1, Ang2 = Norm1:Angle(), (Norm2 * -1):Angle() + if self:GetClientNumber( "autorotate" ) == 1 then + Ang2.p = (math.Round(Ang2.p/45))*45 + Ang2.r = (math.Round(Ang2.r/45))*45 + Ang2.y = (math.Round(Ang2.y/45))*45 + Norm2 = Ang2:Forward() * -1 + end + + + local oldposu = self:GetEnt(1):GetPos() + local oldangles = self:GetEnt(1):GetAngles() + + local function MoveUndo( Undo, Entity, oldposu, oldangles ) + if Entity:IsValid() then + Entity:SetAngles( oldangles ) + Entity:SetPos( oldposu ) + end + end + if self:GetClientNumber( "moveundo" ) == 1 then + undo.Create("Precision Move") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( MoveUndo, self:GetEnt(1), oldposu, oldangles ) + undo.Finish() + end + + local rotation = self:GetClientNumber( "rotation" ) + if ( rotation < 0.02 ) then rotation = 0.02 end + if ( (self:GetClientNumber( "rotate" ) == 1 && mode != 1) || mode == 2) then--Set axies for rotation mode directions + self.axis = Norm2 + self.axisY = self.axis:Cross(Vector(0,1,0)) + if self:WithinABit( self.axisY, Vector(0,0,0) ) then + self.axisY = self.axis:Cross(Vector(0,0,1)) + end + self.axisY:Normalize() + self.axisZ = self.axisY:Cross(self.axis) + self.axisZ:Normalize() + self.realdegrees = 0 + self.lastdegrees = -((rotation/2) % rotation) + self.realdegreesY = 0 + self.lastdegreesY = -((rotation/2) % rotation) + self.realdegreesZ = 0 + self.lastdegreesZ = -((rotation/2) % rotation) + else + self.axis = Norm2 + self.axisY = self.axis:Cross(Vector(0,1,0)) + if self:WithinABit( self.axisY, Vector(0,0,0) ) then + self.axisY = self.axis:Cross(Vector(0,0,1)) + end + self.axisY:Normalize() + self.axisZ = self.axisY:Cross(self.axis) + self.axisZ:Normalize() + end + + + + local TargetAngle = Phys1:AlignAngles( Ang1, Ang2 )--Get angle Phys1 would be at if difference between Ang1 and Ang2 was added + + + if self:GetClientNumber( "autorotate" ) == 1 then + TargetAngle.p = (math.Round(TargetAngle.p/45))*45 + TargetAngle.r = (math.Round(TargetAngle.r/45))*45 + TargetAngle.y = (math.Round(TargetAngle.y/45))*45 + end + + Phys1:SetAngles( TargetAngle ) + + + local NewOffset = math.Clamp( self:GetClientNumber( "offset" ), -100, 100 ) + local offsetpercent = self:GetClientNumber( "offsetpercent" ) == 1 + if ( offsetpercent ) then + local Ent2 = self:GetEnt(2) + local glower = Ent2:OBBMins() + local gupper = Ent2:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(Norm2,Ent2:GetForward()) then + height = math.abs(gupper.x - glower.x)-0.5 + elseif self:WithinABit(Norm2,Ent2:GetRight()) then + height = math.abs(gupper.y - glower.y)-0.5 + end + NewOffset = NewOffset / 100 + NewOffset = NewOffset * height + end + Norm2 = Norm2 * (-0.0625 + NewOffset) + local TargetPos = self:GetPos(2) + (Phys1:GetPos() - self:GetPos(1)) + (Norm2) + -- local tr = util.TraceEntity({ + -- start = Phys1:GetPos(), + -- endpos = TargetPos, + -- filter = function(ent) + -- local phys = ent:GetPhysicsObject() + -- if IsValid(phys) and phys:IsMotionEnabled() then + -- return false + -- else + -- return true + -- end + -- end + -- }, Phys1:GetEntity()) + -- if IsValid(tr.Entity) then + -- self:SendMessage( "Нельзя задвигать пропы в динамические объекты" ) + -- return false + -- end + --self:SetPos(2) + + -- Set the position + + Phys1:SetPos( TargetPos ) + Phys1:EnableMotion( false ) + + -- Wake up the physics object so that the entity updates + Phys1:Wake() +end + +function TOOL:SetTransparent(CurrentEnt, trans) + -- local col = CurrentEnt.APG_oldColor or Color(255,255,255) + -- if trans then + -- col = Color(255,255,255, 128) + -- CurrentEnt.APG_oldColor = CurrentEnt.APG_oldColor or Color(255,255,255) + -- end + + -- CurrentEnt:SetColor(col) + -- if ( col["a"] == 255 ) then + -- CurrentEnt:SetRenderMode( 0 ) + -- else + -- CurrentEnt:SetRenderMode( 1 ) + -- end +end + +function TOOL:ClearSelection() + if ( self.RepairTodo ) then + self:UndoRepairToggle() + end + if ( self.TaggedEnts ) then + local color + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + self:SetTransparent(CurrentEnt, false) + end + end + end + + local ent = self.TaggedEnts[1] + if IsValid(ent) then + timer.Simple(0, function() hook.Run('PhysgunDrop', self:GetOwner(), ent) end) + end + end + self.TaggedEnts = {} +end + +function TOOL:SelectEnts(StartEnt, AllConnected) + self:ClearSelection() + if ( CLIENT ) then return end + + hook.Run('PhysgunPickup', self:GetOwner(), StartEnt) + + if ( AllConnected == 1 ) then + local NumApp = 0 + EntsTab = {} + ConstsTab = {} + GetAllEnts(StartEnt, self.TaggedEnts, EntsTab, ConstsTab) + for key,CurrentEnt in pairs(self.TaggedEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + self:SetTransparent(CurrentEnt, true) + end + end + NumApp = NumApp + 1 + end + self:SendMessage( NumApp .. " objects selected." ) + else + if ( StartEnt and StartEnt:IsValid() ) then + local CurrentPhys = StartEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + table.insert(self.TaggedEnts, StartEnt) + self:SetTransparent(StartEnt, true) + end + end + end + +end + +function TOOL:LeftClick( trace ) + local stage = self:GetStage()--0 = started, 1 = moving/second target, 2 = rotation? + local mode = self:GetClientNumber( "mode" ) + local moving = ( mode == 3 || (self:GetClientNumber( "move" ) == 1 && mode >= 3 && mode <= 8 ) ) + local rotating = ( self:GetClientNumber( "rotate" ) == 1 ) + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + + + if ( stage == 0 ) then--first click - choose a target. + if ( self:TargetValidity(trace, Phys) <= 1 ) then + return false--No phys or hit world + end + self:SetObject( 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if (self:GetClientNumber( "entirecontrap" ) == 1 || mode == 10 ) then + self:SelectEnts(trace.Entity,1) + else + self:SelectEnts(trace.Entity,0) + end + if ( mode == 1 || mode == 10 || mode == 11 ) then --Don't care about stage, always apply. + self:DoConstraint(mode) + else + if ( mode == 9 ) then + self:SetStage(1) + else + if ( moving ) then--Moving + self:StartGhostEntity( trace.Entity ) + self:SetStage(1) + elseif ( mode == 2 ) then--Straight to rotate + self:StartRotate() + self:SetStage(2) + else + self:SetStage(1) + end + end + end + elseif ( stage == 1 ) then--Second click + self:SetObject( 2, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if ( self:GetEnt(1) == self:GetEnt(2) ) then + SavedPos = self:GetPos(2) + end + if ( mode == 9 ) then + -- self:DoConstraint(mode) + else + if ( moving ) then + if ( CLIENT ) then + self:ReleaseGhostEntity() + return true + end + if ( SERVER && !game.SinglePlayer() ) then + self:ReleaseGhostEntity() + --return true + end + self:DoMove() + end + if ( rotating ) then + self:StartRotate() + self:SetStage(2) + else + self:DoConstraint(mode) + end + end + elseif ( stage == 2 ) then--Done rotate + self:DoConstraint(mode) + end + return true +end + +function TOOL:WithinABit( v1, v2 ) + local tol = 0.1 + local da = v1.x-v2.x + local db = v1.y-v2.y + local dc = v1.z-v2.z + if da < tol && da > -tol && db < tol && db > -tol && dc < tol && dc > -tol then + return true + else + da = v1.x+v2.x + db = v1.y+v2.y + dc = v1.z+v2.z + if da < tol && da > -tol && db < tol && db > -tol && dc < tol && dc > -tol then + return true + else + return false + end + end +end + +if ( SERVER ) then + + function GetAllEnts( Ent, OrderedEntList, EntsTab, ConstsTab ) + if ( Ent and Ent:IsValid() ) and ( !EntsTab[ Ent:EntIndex() ] ) then + EntsTab[ Ent:EntIndex() ] = Ent + table.insert(OrderedEntList, Ent) + if ( !constraint.HasConstraints( Ent ) ) then return OrderedEntList end + for key, ConstraintEntity in pairs( Ent.Constraints ) do + if ( !ConstsTab[ ConstraintEntity ] ) then + ConstsTab[ ConstraintEntity ] = true + local ConstTable = ConstraintEntity:GetTable() + for i=1, 6 do + local e = ConstTable[ "Ent"..i ] + if ( e and e:IsValid() ) and ( !EntsTab[ e:EntIndex() ] ) then + GetAllEnts( e, OrderedEntList, EntsTab, ConstsTab ) + end + end + end + end + end + return OrderedEntList + end + + function GetAllConstraints( EntsTab ) + local ConstsTab = {} + for key, Ent in pairs( EntsTab ) do + if ( Ent and Ent:IsValid() ) then + local MyTable = constraint.GetTable( Ent ) + for key, Constraint in pairs( MyTable ) do + if ( !ConstsTab[ Constraint.Constraint ] ) then + ConstsTab[ Constraint.Constraint ] = Constraint + end + end + end + end + return ConstsTab + end +end + +function TOOL:UpdateCustomGhost( ghost, player, offset ) + + -- Ghost is identically buggy to that of easyweld... welding two frozen props and two unfrozen props yields different ghosts even if identical allignment + + if (ghost == nil) then return end + if (!ghost:IsValid()) then ghost = nil return end + + local tr = util.GetPlayerTrace( player, player:GetAimVector() ) + local trace = util.TraceLine( tr ) + if (!trace.Hit) then return end + + local Ang1, Ang2 = self:GetNormal(1):Angle(), (trace.HitNormal * -1):Angle() + local TargetAngle = self:GetEnt(1):AlignAngles( Ang1, Ang2 ) + + self.GhostEntity:SetPos( self:GetEnt(1):GetPos() ) + + if self:GetClientNumber( "autorotate" ) == 1 then + TargetAngle.p = (math.Round(TargetAngle.p/45))*45 + TargetAngle.r = (math.Round(TargetAngle.r/45))*45 + TargetAngle.y = (math.Round(TargetAngle.y/45))*45 + end + self.GhostEntity:SetAngles( TargetAngle ) + + local TraceNormal = trace.HitNormal + + local offsetpercent = self:GetClientNumber( "offsetpercent" ) == 1 + local NewOffset = offset + if ( offsetpercent ) then + local glower = trace.Entity:OBBMins() + local gupper = trace.Entity:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(TraceNormal,trace.Entity:GetForward()) then + height = math.abs(gupper.x - glower.x) -0.5 + elseif self:WithinABit(TraceNormal,trace.Entity:GetRight()) then + height = math.abs(gupper.y - glower.y) -0.5 + end + NewOffset = NewOffset / 100 + NewOffset = NewOffset * height + end + + local TranslatedPos = ghost:LocalToWorld( self:GetLocalPos(1) ) + local TargetPos = trace.HitPos + (self:GetEnt(1):GetPos() - TranslatedPos) + (TraceNormal*NewOffset) + + self.GhostEntity:SetPos( TargetPos ) +end + + +function TOOL:Think() + --if CLIENT then return end + local pl = self:GetOwner() + local wep = pl:GetActiveWeapon() + if not wep:IsValid() or wep:GetClass() != "gmod_tool" or pl:GetInfo("gmod_toolmode") != "precision" then return end + + if (self:NumObjects() < 1) then return end + local Ent1 = self:GetEnt(1) + if ( SERVER ) then + if ( !Ent1:IsValid() ) then + self:ClearObjects() + return + end + end + local mode = self:GetClientNumber( "mode" ) + + if self:NumObjects() == 1 && mode != 2 then + if ( (self:GetClientNumber( "move" ) == 1 && mode >= 3) || mode == 3 ) then + if ( mode <= 8 ) then--no move = no ghost in parent mode + local offset = math.Clamp( self:GetClientNumber( "offset" ), -100, 100 ) + self:UpdateCustomGhost( self.GhostEntity, self:GetOwner(), offset ) + end + end + else + local rotate = (self:GetClientNumber( "rotate" ) == 1 && mode != 1) || mode == 2 + if ( SERVER && rotate && mode <= 8 ) then + local offset = math.Clamp( self:GetClientNumber( "offset" ), -100, 100 ) + + local Phys1 = self:GetPhys(1) + + local cmd = self:GetOwner():GetCurrentCommand() + + local rotation = self:GetClientNumber( "rotation" ) + if ( rotation < 0.02 ) then rotation = 0.02 end + local degrees = cmd:GetMouseX() * 0.02 + + local newdegrees = 0 + local changedegrees = 0 + + local angle = 0 + if( self:GetOwner():KeyDown( IN_RELOAD ) ) then + self.realdegreesY = self.realdegreesY + degrees + newdegrees = self.realdegreesY - ((self.realdegreesY + (rotation/2)) % rotation) + changedegrees = self.lastdegreesY - newdegrees + self.lastdegreesY = newdegrees + angle = Phys1:RotateAroundAxis( self.axisY , changedegrees ) + elseif( self:GetOwner():KeyDown( IN_ATTACK2 ) ) then + self.realdegreesZ = self.realdegreesZ + degrees + newdegrees = self.realdegreesZ - ((self.realdegreesZ + (rotation/2)) % rotation) + changedegrees = self.lastdegreesZ - newdegrees + self.lastdegreesZ = newdegrees + angle = Phys1:RotateAroundAxis( self.axisZ , changedegrees ) + else + self.realdegrees = self.realdegrees + degrees + newdegrees = self.realdegrees - ((self.realdegrees + (rotation/2)) % rotation) + changedegrees = self.lastdegrees - newdegrees + self.lastdegrees = newdegrees + angle = Phys1:RotateAroundAxis( self.axis , changedegrees ) + end + Phys1:SetAngles( angle ) + + if ( ( self:GetClientNumber( "move" ) == 1 && mode >= 3) || mode == 3 ) then + local WPos2 = self:GetPos(2) + local Ent2 = self:GetEnt(2) + -- Move so spots join up + local Norm2 = self:GetNormal(2) + + local NewOffset = offset + local offsetpercent = self:GetClientNumber( "offsetpercent" ) == 1 + if ( offsetpercent ) then + local glower = Ent2:OBBMins() + local gupper = Ent2:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(Norm2,Ent2:GetForward()) then + height = math.abs(gupper.x - glower.x) -0.5 + elseif self:WithinABit(Norm2,Ent2:GetRight()) then + height = math.abs(gupper.y - glower.y) -0.5 + end + NewOffset = NewOffset / 100 + NewOffset = NewOffset * height + end + + Norm2 = Norm2 * (-0.0625 + NewOffset) + local TargetPos = Vector(0,0,0) + if ( self:GetEnt(1) == self:GetEnt(2) ) then + ------------------------------------------ + TargetPos = SavedPos + (Phys1:GetPos() - self:GetPos(1)) + (Norm2) + else + TargetPos = WPos2 + (Phys1:GetPos() - self:GetPos(1)) + (Norm2) + end + Phys1:SetPos( TargetPos ) + else + -- Move so rotating on axis + + local TargetPos = (Phys1:GetPos() - self:GetPos(1)) + self.OldPos + Phys1:SetPos( TargetPos ) + end + Phys1:Wake() + end + end +end + +function TOOL:Nudge( trace, direction ) + if (!trace.Entity:IsValid() || trace.Entity:IsPlayer() ) then return false end + local Phys1 = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + if not IsValid(Phys1) then return end + local offsetpercent = self:GetClientNumber( "nudgepercent" ) == 1 + local offset = self:GetClientNumber( "nudge", 100 ) + local max = 8192 + if ( offsetpercent != 1 ) then + if ( offset > max ) then + offset = max + elseif ( offset < -max ) then + offset = -max + end + end + --if ( offset == 0 ) then offset = 1 end + local NewOffset = offset + if ( offsetpercent ) then + local glower = trace.Entity:OBBMins() + local gupper = trace.Entity:OBBMaxs() + local height = math.abs(gupper.z - glower.z) -0.5 + if self:WithinABit(trace.HitNormal,trace.Entity:GetForward()) then + height = math.abs(gupper.x - glower.x)-0.5 + elseif self:WithinABit(trace.HitNormal,trace.Entity:GetRight()) then + height = math.abs(gupper.y - glower.y)-0.5 + end + NewOffset = NewOffset / 100 + local cap = math.floor(max / height)--No more than max units. + if ( NewOffset > cap ) then + NewOffset = cap + elseif ( NewOffset < -cap ) then + NewOffset = -cap + end + NewOffset = NewOffset * height + end + + if ( self:GetClientNumber( "entirecontrap" ) == 1 ) then + local NumApp = 0 + local TargetEnts = {} + local EntsTab = {} + local ConstsTab = {} + GetAllEnts(trace.Entity, TargetEnts, EntsTab, ConstsTab) + for key,CurrentEnt in pairs(TargetEnts) do + if ( CurrentEnt and CurrentEnt:IsValid() ) then + local CurrentPhys = CurrentEnt:GetPhysicsObject() + if ( CurrentPhys:IsValid() ) then + + --[[if ( self:GetClientNumber( "nudgeundo" ) == 1 ) then + local oldpos = CurrentPhys:GetPos() + local function NudgeUndo( Undo, Entity, oldpos ) + if CurrentEnt:IsValid() then + CurrentEnt:SetPos( oldpos ) + end + end + undo.Create("Nrecision Nudge") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( NudgeUndo, CurrentEnt, oldpos ) + undo.Finish() + end]] + local TargetPos = CurrentPhys:GetPos() + trace.HitNormal * NewOffset * direction + -- local tr = util.TraceEntity({ + -- start = CurrentEnt:GetPos(), + -- endpos = TargetPos, + -- filter = function(ent) + -- local phys = ent:GetPhysicsObject() + -- if IsValid(phys) and phys:IsMotionEnabled() then + -- return false + -- else + -- return true + -- end + -- end + -- }, CurrentEnt) + -- if IsValid(tr.Entity) then + -- self:SendMessage( "Нельзя задвигать пропы в динамические объекты" ) + -- return false + -- end + + CurrentPhys:SetPos( TargetPos ) + CurrentPhys:Wake() + if (CurrentEnt:GetMoveType() == 0 ) then --phys disabled, so move manually + CurrentEnt:SetPos( TargetPos ) + end + + end + end + NumApp = NumApp + 1 + end + if ( direction == -1 ) then + self:SendMessage( NumApp .. " items pushed." ) + elseif ( direction == 1 ) then + self:SendMessage( NumApp .. " items pulled." ) + else + self:SendMessage( NumApp .. " items nudged." ) + end + else + if ( self:GetClientNumber( "nudgeundo" ) == 1 ) then + local oldpos = Phys1:GetPos() + local function NudgeUndo( Undo, Entity, oldpos ) + if trace.Entity:IsValid() then + trace.Entity:SetPos( oldpos ) + end + end + undo.Create("Precision PushPull") + undo.SetPlayer(self:GetOwner()) + undo.AddFunction( NudgeUndo, trace.Entity, oldpos ) + undo.Finish() + end + local TargetPos = Phys1:GetPos() + trace.HitNormal * NewOffset * direction + -- local tr = util.TraceEntity({ + -- start = trace.Entity:GetPos(), + -- endpos = TargetPos, + -- filter = function(ent) + -- local phys = ent:GetPhysicsObject() + -- if IsValid(phys) and phys:IsMotionEnabled() then + -- return true + -- else + -- return false + -- end + -- end + -- }, trace.Entity) + -- if IsValid(tr.Entity) then + -- self:SendMessage( "Нельзя задвигать пропы в динамические объекты" ) + -- return false + -- end + + Phys1:SetPos( TargetPos ) + Phys1:Wake() + if ( trace.Entity:GetMoveType() == 0 ) then + trace.Entity:SetPos( TargetPos ) + end + -- Drop everyone sitting on this prop + local owner = self:GetOwner() + hook.Run('OnPhysgunPickup', owner, trace.Entity) + timer.Simple(0, function() + if IsValid(owner) and IsValid(trace.Entity) then + hook.Run('PhysgunDrop', owner, trace.Entity) + end + end) + if ( direction == -1 ) then + self:SendMessage( "target pushed." ) + elseif ( direction == 1 ) then + self:SendMessage( "target pulled." ) + else + self:SendMessage( "target nudged." ) + end + end + return true +end + +function TOOL:RightClick( trace ) + local rotate = self:GetClientNumber( "rotate" ) == 1 + local mode = self:GetClientNumber( "mode" ) + if ( (mode == 2 && self:NumObjects() == 1) || (rotate && self:NumObjects() == 2 ) ) then + if ( CLIENT ) then return false end + else + if ( CLIENT ) then return true end + return self:Nudge( trace, -1 ) + end +end + +function TOOL:Reload( trace ) + local rotate = self:GetClientNumber( "rotate" ) == 1 + local mode = self:GetClientNumber( "mode" ) + if ( (mode == 2 && self:NumObjects() == 1) || (rotate && self:NumObjects() == 2 ) ) then + if ( CLIENT ) then return false end + else + if ( CLIENT ) then return true end + return self:Nudge( trace, 1 ) + end +end + +if CLIENT then + + language.Add( "Tool.precision.name", "Precision Tool 0.98e" ) + language.Add( "Tool.precision.desc", "Accurately moves/constrains objects" ) + language.Add( "Tool.precision.0", "Primary: Move/Apply | Secondary: Push | Reload: Pull" ) + language.Add( "Tool.precision.1", "Target the second item. If enabled, this will move the first item. (Swap weps to cancel)" ) + language.Add( "Tool.precision.2", "Rotate enabled: Turn left and right to rotate the object (Hold Reload or Secondary for other rotation directions!)" ) + + + language.Add("Undone.precision", "Undone Precision Constraint") + language.Add("Undone.precision.nudge", "Undone Precision PushPull") + language.Add("Undone.precision.rotate", "Undone Precision Rotate") + language.Add("Undone.precision.move", "Undone Precision Move") + language.Add("Undone.precision.weld", "Undone Precision Weld") + language.Add("Undone.precision.axis", "Undone Precision Axis") + language.Add("Undone.precision.ballsocket", "Undone Precision Ballsocket") + language.Add("Undone.precision.advanced.ballsocket", "Undone Precision Advanced Ballsocket") + language.Add("Undone.precision.slider", "Undone Precision Slider") + + local showgenmenu = 0--Seems to hide often, probably for the best + + local function AddDefControls( Panel ) + Panel:ClearControls() + + Panel:AddControl("ComboBox", + { + Label = "#Presets", + MenuButton = 1, + Folder = "precision", + Options = {}, + CVars = + { + [0] = "precision_offset", + [1] = "precision_forcelimit", + [2] = "precision_freeze", + [3] = "precision_nocollide", + [4] = "precision_nocollideall", + [5] = "precision_rotation", + [6] = "precision_rotate", + [7] = "precision_torquelimit", + [8] = "precision_friction", + [9] = "precision_mode", + [10] = "precision_width", + [11] = "precision_offsetpercent", + [12] = "precision_removal", + [13] = "precision_move", + [14] = "precision_physdisable", + [15] = "precision_advballsocket", + [16] = "precision_XRotMin", + [17] = "precision_XRotMax", + [18] = "precision_YRotMin", + [19] = "precision_YRotMax", + [20] = "precision_ZRotMin", + [21] = "precision_ZRotMax", + [22] = "precision_XRotFric", + [23] = "precision_YRotFric", + [24] = "precision_ZRotFric", + [25] = "precision_FreeMov", + [26] = "precision_ShadowDisable", + [27] = "precision_allowphysgun", + [28] = "precision_autorotate", + [29] = "precision_massmode", + [30] = "precision_nudge", + [31] = "precision_nudgepercent", + [32] = "precision_disablesliderfix" + } + }) + + --Panel:AddControl( "Label", { Text = "Secondary attack pushes, Reload pulls by this amount:", Description = "Phx 1x is 47.45, Small tiled cube is 11.8625 and thin is 3 exact units" } ) + Panel:AddControl( "Slider", { Label = "Push/Pull Amount", + Type = "Float", + Min = 1, + Max = 100, + Command = "precision_nudge", + Description = "Distance to push/pull props with altfire/reload"} ):SetDecimals( 4 ) + + + Panel:AddControl( "Checkbox", { Label = "Push/Pull as Percent (%) of target's depth", Command = "precision_nudgepercent", Description = "Unchecked = Exact units, Checked = takes % of width from target prop when pushing/pulling" } ) + + + local user = GetConVarNumber('precision_user') or 0 + local mode = GetConVarNumber('precision_mode') or 0 + --Panel:AddControl( "Label", { Text = "Primary attack uses the tool's main mode.", Description = "Select a mode and configure the options, be sure to try new things out!" } ) + + local list = vgui.Create("DListView") + + --17 per item + 16 for title + local height = 203 --All 11 shown + if ( user < 2 ) then + height = 135 --7 shown + elseif ( user < 3 ) then + height = 170 --9 shown + end + + + list:SetSize(30,height) + --list:SizeToContents() + list:AddColumn("Tool Mode") + list:SetMultiSelect(false) + function list:OnRowSelected(LineID, line) + if not (mode == LineID) then + RunConsoleCommand("precision_setmode", LineID) + end + end + + if ( mode == 1 ) then + list:AddLine(" 1 ->Apply<- (Directly apply settings to target)") + else + list:AddLine(" 1 Apply (Directly apply settings to target)") + end + if ( mode == 2 ) then + list:AddLine(" 2 ->Rotate<- (Turn an object without moving it)") + else + list:AddLine(" 2 Rotate (Turn an object without moving it)") + end + if ( mode == 3 ) then + list:AddLine(" 3 ->Move<- (Snap objects together - Great for building!)") + else + list:AddLine(" 3 Move (Snap objects together - Great for building!)") + end + if ( mode == 4 ) then + list:AddLine(" 4 ->Weld<-") + else + list:AddLine(" 4 Weld") + end + if ( mode == 5 ) then + list:AddLine(" 5 ->Axis<-") + else + list:AddLine(" 5 Axis") + end + if ( mode == 6 ) then + list:AddLine(" 6 ->Ballsocket<-") + else + list:AddLine(" 6 Ballsocket") + end + if ( user >= 2 ) then + if ( mode == 7 ) then + list:AddLine(" 7 ->Adv Ballsocket<-") + else + list:AddLine(" 7 Adv Ballsocket") + end + if ( mode == 8 ) then + list:AddLine(" 8 ->Slider<-") + else + list:AddLine(" 8 Slider") + end + end + if ( user >= 3 ) then + if ( mode == 9 ) then + list:AddLine(" 9 ->Parent<- (Like a solid weld, but without object collision)") + else + list:AddLine(" 9 Parent (Like a solid weld, but without object collision)") + end + if ( mode == 10 ) then + list:AddLine("10 ->Repair<- (Attempts to fix a flailing contraption)") + else + list:AddLine("10 Repair (Attempts to fix a flailing contraption)") + end + end + if ( mode == 11 ) then + list:AddLine("11 ->Removal<- (Undoes constraints from target)") + else + list:AddLine("11 Removal (Undoes constraints from target)") + end + list:SortByColumn(1) + Panel:AddItem(list) + + if ( mode >= 4 && mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "Move Target? ('Easy' constraint mode)", Command = "precision_move", Description = "Uncheck this to apply the constraint without altering positions." } ) + end + if ( mode >= 3 && mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "Rotate Target? (Rotation after moving)", Command = "precision_rotate", Description = "Uncheck this to remove the extra click for rotation. Handy for speed building." } ) + --Panel:AddControl( "Label", { Text = "This is the distance from touching of the targeted props after moving:", Description = "Use 0 mostly, % takes the second prop's width." } ) + Panel:AddControl( "Slider", { Label = "Snap Distance", + Type = "Float", + Min = 0, + Max = 10, + Command = "precision_offset", + Description = "Distance offset between joined props. Type in negative to inset when moving."} ) + Panel:AddControl( "Checkbox", { Label = "Snap distance as Percent (%) of target's depth", Command = "precision_offsetpercent", Description = "Unchecked = Exact units, Checked = takes % of width from second prop" } ) + end + if ( mode >= 2 && mode <= 8 ) then + Panel:AddControl( "Slider", { Label = "Rotation Snap (Degrees)", + Type = "Float", + Min = 0.02, + Max = 90, + Command = "precision_rotation", + Description = "Rotation rotates by this amount at a time. No more guesswork. Min: 0.02 degrees "} ):SetDecimals( 4 ) + end + if ( mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "Freeze Target", Command = "precision_freeze", Description = "Freeze props when this tool is used" } ) + + if ( mode >= 3 && mode <= 8 ) then + Panel:AddControl( "Checkbox", { Label = "No Collide Targets", Command = "precision_nocollide", Description = "Nocollide pairs of props when this tool is used. Note: No current way to remove this constraint when used alone." } ) + end + end + + if ( user >= 2 || mode == 1 ) then + if ( (mode >= 3 && mode <= 8) || mode == 1 ) then + Panel:AddControl( "Checkbox", { Label = "Auto-align to world (nearest 45 degrees)", Command = "precision_autorotate", Description = "Rotates to the nearest world axis (similar to holding sprint and use with physgun)" } ) + end + + if ( mode == 1 ) then + Panel:AddControl( "Checkbox", { Label = "Disable target shadow", Command = "precision_ShadowDisable", Description = "Disables shadows cast from the prop" } ) + end + end + + if ( user >= 3 ) then + if ( mode == 1 ) then --apply + Panel:AddControl( "Checkbox", { Label = "Only Collide with Player", Command = "precision_nocollideall", Description = "Nocollides the first prop to everything and the world (except players collide with it). Warning: don't let it fall away through the world." } ) + Panel:AddControl( "Checkbox", { Label = "Disable Physics on object", Command = "precision_physdisable", Description = "Disables physics on the first prop (gravity, being shot etc won't effect it)" } ) + Panel:AddControl( "Checkbox", { Label = "Adv: Allow Physgun on PhysDisable objects", Command = "precision_allowphysgun", Description = "Disabled to stop accidents, use if you want to be able to manually move props after phyics disabling them (may break clipboxes)." } ) + + --Panel:AddControl( "Checkbox", { Label = "Drag", Command = "precision_drag", Description = "" } ) + end + if ( mode == 9 ) then --parent + Panel:AddControl( "Checkbox", { Label = "Adv: Allow Physgun on Parented objects", Command = "precision_allowphysgun", Description = "Disabled to stop accidents, use this if you want to play with the parenting hierarchy etc." } ) + end + end + if ( user >= 2 ) then + if ( mode != 2 && mode != 3 && mode != 10 ) then Panel:AddControl( "Checkbox", { Label = "Entire Contraption! (Everything connected to target)", Command = "precision_entirecontrap", Description = "For mass constraining or removal or nudging or applying of things. Yay generic." } ) + end + end + + if ( user >= 2 ) then + if ( (mode >= 4 && mode <= 7) ) then --breakable constraint + Panel:AddControl( "Slider", { Label = "Force Breakpoint", + Type = "Float", + Min = 0.0, + Max = 5000, + Command = "precision_forcelimit", + Description = "Applies to most constraint modes" } ) + end + + + if ( mode == 5 || mode == 6 || mode == 7 ) then --axis or ballsocket + Panel:AddControl( "Slider", { Label = "Torque Breakpoint", + Type = "Float", + Min = 0.0, + Max = 5000, + Command = "precision_torquelimit", + Description = "Breakpoint of turning/rotational force"} ) + end + end + + if ( mode == 5 ) then --axis + Panel:AddControl( "Slider", { Label = "Axis Friction", + Type = "Float", + Min = 0.0, + Max = 100, + Command = "precision_friction", + Description = "Turning resistance, this is best at 0 in most cases to conserve energy"} ) + end + + if ( mode ==7 ) then --adv ballsocket + Panel:AddControl( "Slider", { Label = "X Rotation Minimum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_XRotMin", + Description = "Rotation minimum of advanced ballsocket in X axis"} ) + + Panel:AddControl( "Slider", { Label = "X Rotation Maximum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_XRotMax", + Description = "Rotation maximum of advanced ballsocket in X axis"} ) + + Panel:AddControl( "Slider", { Label = "Y Rotation Minimum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_YRotMin", + Description = "Rotation minimum of advanced ballsocket in Y axis"} ) + + Panel:AddControl( "Slider", { Label = "Y Rotation Maximum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_YRotMax", + Description = "Rotation maximum of advanced ballsocket in Y axis"} ) + + Panel:AddControl( "Slider", { Label = "Z Rotation Minimum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_ZRotMin", + Description = "Rotation minimum of advanced ballsocket in Z axis"} ) + + Panel:AddControl( "Slider", { Label = "Z Rotation Maximum", + Type = "Float", + Min = -180, + Max = 180, + Command = "precision_ZRotMax", + Description = "Rotation maximum of advanced ballsocket in Z axis"} ) + + Panel:AddControl( "Slider", { Label = "X Rotation Friction", + Type = "Float", + Min = 0, + Max = 100, + Command = "precision_XRotFric", + Description = "Rotation friction of advanced ballsocket in X axis"} ) + + Panel:AddControl( "Slider", { Label = "Y Rotation Friction", + Type = "Float", + Min = 0, + Max = 100, + Command = "precision_YRotFric", + Description = "Rotation friction of advanced ballsocket in Y axis"} ) + + Panel:AddControl( "Slider", { Label = "Z Rotation Friction", + Type = "Float", + Min = 0, + Max = 100, + Command = "precision_ZRotFric", + Description = "Rotation friction of advanced ballsocket in Z axis"} ) + + Panel:AddControl( "Checkbox", { Label = "Free Movement", Command = "precision_FreeMov", Description = "Only lock relative rotation, not position?" } ) + end + + if ( mode == 8 ) then --slider + -- Panel:AddControl( "Slider", { Label = "Slider Width", + -- Type = "Float", + -- Min = 0.0, + -- Max = 10, + -- Command = "precision_width", + -- Description = "Width of the slider black line (0 = invisible)"} ) + + -- Panel:AddControl( "Checkbox", { Label = "Turn Off Minor Slider Stabilisation", Command = "precision_disablesliderfix", Description = "Fix being separate X/Y/Z advanced ballsocket locks between the props. This stops most spaz caused by rotation, but not spaz caused by displacement." } ) + -- Panel:AddControl( "Label", { Text = "Stabilisation is separate X/Y/Z adv. ballsockets; it makes it far less prone to rotation triggered spaz, but the difference is only noticeable sometimes as it's still just as prone to spaz caused by drifting.", Description = "Due to lack of working descriptions at time of coding" } ) + end + + if ( mode == 9 ) then --parent + Panel:AddControl( "Label", { Text = "Parenting Notes:", Description = "Due to lack of working descriptions at time of coding" } ) + Panel:AddControl( "Label", { Text = "Parenting objects is most similar to a very strong weld, but it stops most interaction on the first object when you attach it to the second. Players can walk on it, but it will fall through players. It will not collide with objects or the world. It will also not cause any extra physics lag/spaz. Try it out on a test object, and decide if it's useful to you!", Description = "Due to lack of working descriptions at time of coding" } ) + + Panel:AddControl( "Label", { Text = "Parented objects are most useful for: Adding detail to moving objects without creating extra physics lag. Things like houses that you want to move (though you can only safely walk on parented objects when they are still.)", Description = "Due to lack of working descriptions at time of coding" } ) + + Panel:AddControl( "Label", { Text = "Possible issues: Remove constraints first to avoid spaz. Duplicating or such may cause the collision model to become separated. Best to test it if in doubt.", Description = "Why must labels cause menu flicker? D:" } ) + end + + if ( mode == 10 ) then --repair + Panel:AddControl( "Label", { Text = "Repair mode", Description = "" } ) + Panel:AddControl( "Label", { Text = "Usage: When a contraption is going crazy, colliding, making rubbing noises.", Description = "" } ) + Panel:AddControl( "Label", { Text = "What it does: Temporarily toggles collisions, allowing things that are bent out of shape to pop back.", Description = "" } ) + Panel:AddControl( "Label", { Text = "Warning: No guarantees. This may turn things inside-out or make things worse depending on the situation.", Description = "" } ) + end + if ( mode == 11 ) then --removal + Panel:AddControl( "Label", { Text = "This mode will remove:", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Nocollide", Command = "precision_removal_nocollide", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Weld", Command = "precision_removal_weld", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Axis", Command = "precision_removal_axis", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Ballsocket", Command = "precision_removal_ballsocket", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Adv. Ballsocket", Command = "precision_removal_advballsocket", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Slider", Command = "precision_removal_slider", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Parent", Command = "precision_removal_parent", Description = "" } ) + Panel:AddControl( "Checkbox", { Label = "Other", Command = "precision_removal_other", Description = "" } ) + Panel:AddControl( "Label", { Text = "(Other = Rope/slider variants like winch/hydraulic, also motor/keepupright)", Description = "" } ) + Panel:AddControl( "Button", { Label = "Select All", Command = "precision_removal_all", Description = "" } ) + Panel:AddControl( "Button", { Label = "Select None", Command = "precision_removal_none", Description = "" } ) + + end + if ( showgenmenu == 1 ) then + Panel:AddControl( "Button", { Label = "\\/ General Tool Options \\/", Command = "precision_generalmenu", Description = "Collapse menu" } ) + + + + + local params = {Label = "User Level",Description = "Shows options appropriate to user experience level", MenuButton = "0", Height = 67, Options = {}} + if ( user == 1 ) then + params.Options[" 1 ->Normal<-"] = { precision_setuser = "1" } + else + params.Options[" 1 Normal"] = { precision_setuser = "1" } + end + if ( user == 2 ) then + params.Options[" 2 ->Advanced<-"] = { precision_setuser = "2" } + else + params.Options[" 2 Advanced"] = { precision_setuser = "2" } + end + if ( user == 3 ) then + params.Options[" 3 ->Experimental<-"] = { precision_setuser = "3" } + else + params.Options[" 3 Experimental"] = { precision_setuser = "3" } + end + + Panel:AddControl( "ListBox", params ) + + --Panel:AddControl( "Label", { Text = "General Tool Options:", Description = "Note: These don't save with presets." } ) + Panel:AddControl( "Checkbox", { Label = "Enable tool feedback messages?", Command = "precision_enablefeedback", Description = "Toggle for feedback messages incase they get annoying" } ) + Panel:AddControl( "Checkbox", { Label = "On = Feedback in Chat, Off = Centr Scrn", Command = "precision_chatfeedback", Description = "Chat too cluttered? Can have messages centre screen instead" } ) + --Panel:AddControl( "Checkbox", { Label = "Hide Menu Tips?", Command = "precision_hidehints", Description = "Streamline the menu once you're happy with using the tool." } ) + Panel:AddControl( "Checkbox", { Label = "Add Push/Pull to Undo List", Command = "precision_nudgeundo", Description = "For if you're in danger of nudging somthing to where you can't reach it" } ) + Panel:AddControl( "Checkbox", { Label = "Add Movement to Undo List", Command = "precision_moveundo", Description = "So you don't have to secondary fire with nocollide to undo mistakes" } ) + Panel:AddControl( "Checkbox", { Label = "Add Rotation to Undo List", Command = "precision_rotateundo", Description = "So you can find the exact rotation value easier" } ) + Panel:AddControl( "Button", { Label = "Restore Current Mode Default", Command = "precision_defaultrestore", Description = "Collapse menu" } ) + else + Panel:AddControl( "Button", { Label = "-- General Tool Options --", Command = "precision_generalmenu", Description = "Expand menu" } ) + if ( user == 1 ) then + Panel:AddControl( "Label", { Text = "(Note: For more modes and options like slider, use this options button and change the user level)", Description = "" } ) + end + end + end + + + + local function precision_defaults() + local mode = LocalPlayer():GetInfoNum( "precision_mode", 3 ) + if mode == 1 then + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_autorotate", "1") + RunConsoleCommand("precision_ShadowDisable", "0") + RunConsoleCommand("precision_nocollideall", "0") + RunConsoleCommand("precision_physdisable", "0") + RunConsoleCommand("precision_allowphysgun", "0") + RunConsoleCommand("precision_entirecontrap", "0") + elseif mode == 2 then + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_entirecontrap", "0") + elseif mode == 3 then + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "1") + RunConsoleCommand("precision_entirecontrap", "0") + elseif mode == 4 then + RunConsoleCommand("precision_move", "1") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_forcelimit", "0") + elseif mode == 5 then + RunConsoleCommand("precision_move", "1") + RunConsoleCommand("precision_rotate", "1") + RunConsoleCommand("precision_offset", "0") + RunConsoleCommand("precision_offsetpercent", "1") + RunConsoleCommand("precision_rotation", "15") + RunConsoleCommand("precision_freeze", "1") + RunConsoleCommand("precision_nocollide", "1") + RunConsoleCommand("precision_autorotate", "0") + RunConsoleCommand("precision_entirecontrap", "0") + RunConsoleCommand("precision_forcelimit", "0") + RunConsoleCommand("precision_torquelimit", "0") + RunConsoleCommand("precision_friction", "0") + elseif mode == 6 then + -- RunConsoleCommand("precision_move", "1") + -- RunConsoleCommand("precision_rotate", "1") + -- RunConsoleCommand("precision_offset", "0") + -- RunConsoleCommand("precision_offsetpercent", "1") + -- RunConsoleCommand("precision_rotation", "15") + -- RunConsoleCommand("precision_freeze", "1") + -- RunConsoleCommand("precision_nocollide", "1") + -- RunConsoleCommand("precision_autorotate", "0") + -- RunConsoleCommand("precision_entirecontrap", "0") + -- RunConsoleCommand("precision_forcelimit", "0") + -- RunConsoleCommand("precision_torquelimit", "0") + elseif mode == 7 then + -- RunConsoleCommand("precision_move", "0") + -- RunConsoleCommand("precision_rotate", "1") + -- RunConsoleCommand("precision_offset", "0") + -- RunConsoleCommand("precision_offsetpercent", "1") + -- RunConsoleCommand("precision_rotation", "15") + -- RunConsoleCommand("precision_freeze", "1") + -- RunConsoleCommand("precision_nocollide", "1") + -- RunConsoleCommand("precision_autorotate", "0") + -- RunConsoleCommand("precision_entirecontrap", "0") + -- RunConsoleCommand("precision_forcelimit", "0") + -- RunConsoleCommand("precision_torquelimit", "0") + -- RunConsoleCommand("precision_XRotMin", "0") + -- RunConsoleCommand("precision_XRotMax", "0") + -- RunConsoleCommand("precision_YRotMin", "0") + -- RunConsoleCommand("precision_YRotMax", "0") + -- RunConsoleCommand("precision_ZRotMin", "0") + -- RunConsoleCommand("precision_ZRotMax", "0") + -- RunConsoleCommand("precision_XRotFric", "0") + -- RunConsoleCommand("precision_YRotFric", "0") + -- RunConsoleCommand("precision_ZRotFric", "0") + -- RunConsoleCommand("precision_FreeMov", "1") + elseif mode == 8 then + -- RunConsoleCommand("precision_move", "1") + -- RunConsoleCommand("precision_rotate", "1") + -- RunConsoleCommand("precision_offset", "0") + -- RunConsoleCommand("precision_offsetpercent", "1") + -- RunConsoleCommand("precision_rotation", "15") + -- RunConsoleCommand("precision_freeze", "1") + -- RunConsoleCommand("precision_nocollide", "0") + -- RunConsoleCommand("precision_autorotate", "0") + -- RunConsoleCommand("precision_entirecontrap", "0") + -- RunConsoleCommand("precision_width", "1") + -- RunConsoleCommand("precision_disablesliderfix", "0") + elseif mode == 9 then + -- RunConsoleCommand("precision_allowphysgun", "0") + -- RunConsoleCommand("precision_entirecontrap", "0") + end + precision_updatecpanel() + end + concommand.Add( "precision_defaultrestore", precision_defaults ) + + local function precision_genmenu() + if ( showgenmenu == 1 ) then + showgenmenu = 0 + else + showgenmenu = 1 + end + precision_updatecpanel() + end + concommand.Add( "precision_generalmenu", precision_genmenu ) + + + function precision_setmode( player, tool, args ) + if LocalPlayer():GetInfoNum( "precision_mode", 3 ) != args[1] then + RunConsoleCommand("precision_mode", args[1]) + timer.Simple(0.05, function() precision_updatecpanel() end ) + end + end + concommand.Add( "precision_setmode", precision_setmode ) + + + function precision_setuser( player, tool, args ) + if LocalPlayer():GetInfoNum( "precision_user", 3 ) != args[1] then + RunConsoleCommand("precision_user", args[1]) + timer.Simple(0.05, function() precision_updatecpanel() end ) + end + end + concommand.Add( "precision_setuser", precision_setuser ) + + + function precision_updatecpanel() + local Panel = controlpanel.Get( "precision" ) + if (!Panel) then return end + --custom panel building ( wtf does Panel:AddDefaultControls() get it's defaults from? ) + AddDefControls( Panel ) + end + concommand.Add( "precision_updatecpanel", precision_updatecpanel ) + + function TOOL.BuildCPanel( Panel ) + AddDefControls( Panel ) + end + + local function precision_removalall() + RunConsoleCommand("precision_removal_nocollide", "1") + RunConsoleCommand("precision_removal_weld", "1") + RunConsoleCommand("precision_removal_axis", "1") + RunConsoleCommand("precision_removal_ballsocket", "1") + RunConsoleCommand("precision_removal_advballsocket", "1") + RunConsoleCommand("precision_removal_slider", "1") + RunConsoleCommand("precision_removal_parent", "1") + RunConsoleCommand("precision_removal_other", "1") + precision_updatecpanel() + end + concommand.Add( "precision_removal_all", precision_removalall ) + local function precision_removalnone() + RunConsoleCommand("precision_removal_nocollide", "0") + RunConsoleCommand("precision_removal_weld", "0") + RunConsoleCommand("precision_removal_axis", "0") + RunConsoleCommand("precision_removal_ballsocket", "0") + RunConsoleCommand("precision_removal_advballsocket", "0") + RunConsoleCommand("precision_removal_slider", "0") + RunConsoleCommand("precision_removal_parent", "0") + RunConsoleCommand("precision_removal_other", "0") + precision_updatecpanel() + end + concommand.Add( "precision_removal_none", precision_removalnone ) + + function TOOL:FreezeMovement() + local stage = self:GetStage() + if ( stage == 2 ) then + return true + -- elseif ( iNum > 0 && self:GetClientNumber("mode") == 2 ) then + -- return true + end + return false + end +end + +function TOOL:Holster() + self:ClearObjects() + self:SetStage(0) + self:ClearSelection() +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/progress.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/progress.lua new file mode 100644 index 0000000..59c158d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/progress.lua @@ -0,0 +1,280 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Прогресс' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, + { name = 'reload' }, +} + +local vars = { + time = 3, + name = 'Работа', + animTime = 2, + mode = 0, + animAction = 'drop', + animSound = '', + duration = 10, + title = L.title2, + text = L.trigger_text, + method = 'chat', + gamesound = '', + urlsound = '', + volume = 0.5, + bind = 0, + requirements = {}, + passwordMsg = '', + passwordMsgKey = '', +} + +if CLIENT then + for k, v in pairs(vars) do + octolib.vars.init('tools.progress.' .. k, v) + end +end + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +local varList = octolib.table.map(table.GetKeys(vars), function(v) return 'tools.progress.' .. v end) + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + + if SERVER then + if ent:IsPlayer() and not self:GetOwner():IsSuperAdmin() then + return false + end + + local ply = self:GetOwner() + ply:GetClientVar(varList, function(vars) + local data = { + name = utf8.sub(vars['tools.progress.name'] or '', 1, 255), + time = tonumber(vars['tools.progress.time']) or 1, + mode = tonumber(vars['tools.progress.mode']) or 0, + duration = math.Clamp(tonumber(vars['tools.progress.duration']) or 10, 1, 300), + title = utf8.sub(vars['tools.progress.title'], 1, 256), + text = utf8.sub(vars['tools.progress.text'], 1, 2048), + method = vars['tools.progress.method'], + gamesound = utf8.sub(vars['tools.progress.gamesound'], 1, 2048), + urlsound = ply:query(L.permissions_trigger_url) and utf8.sub(vars['tools.progress.urlsound'], 1, 2048) or nil, + volume = math.Clamp(tonumber(vars['tools.progress.volume']) or 1, 0, 1), + bind = tonumber(vars['tools.progress.bind']) or nil, + animTime = tonumber(vars['tools.progress.animTime']) or 1, + animSound = vars['tools.progress.animSound'], + animAction = vars['tools.progress.animAction'], + requirements = vars['tools.progress.requirements'] or {}, + passwordMsg = string.Trim(vars['tools.progress.passwordMsg'] or ''), + passwordMsgKey = string.Trim(vars['tools.progress.passwordMsgKey'] or ''), + } + + doEffect(ent) + ent.ownerSID = ply:SteamID() + ent.delayedActionData = data + duplicator.StoreEntityModifier(ent, 'del_action', data) + end) + end + + return true + +end + +function TOOL:RightClick(tr) + + if SERVER then return false end + + local ent = tr.Entity + local data = IsValid(ent) and ent.delayedActionData + if not data then return false end + + for k,v in pairs(data) do + octolib.vars.set('tools.progress.' .. k, v) + end + + return true + +end + +function TOOL:Reload(tr) + + if not IsFirstTimePredicted() then return false end + + local ent = tr.Entity + local data = IsValid(ent) and ent.delayedActionData + if not data then return false end + + if SERVER then + ent.delayedActionData = nil + duplicator.ClearEntityModifier(ent, 'del_action') + end + + doEffect(ent) + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'Прогресс', + Description = 'Этот инструмент поможет создать задержку взаимодействия с предметами' + }):DockMargin(0, 10, 0, 10) + + octolib.vars.slider(self, 'tools.progress.time', L.time, 0.1, 60, 0):DockMargin(0, 10, 0, 0) + octolib.vars.textEntry(self, 'tools.progress.name', L.title) + octolib.vars.comboBox(self, 'tools.progress.mode', 'Режим повторения', { + {'Сколько угодно раз', 0,}, + {'По одному разу на игрока', 1,}, + {'Всего один раз', 2,}, + }) + + octolib.button(self, 'Редактировать требования', function() + local editor = octolib.dataEditor.open('tool.progress.requirements') + editor.frame:SetWide(700) + editor.frame:Center() + end):DockMargin(0, 10, 0, 10) + + octolib.vars.textEntry(self, 'tools.progress.passwordMsg', 'Уведомление при отсутствии пароля') + octolib.vars.textEntry(self, 'tools.progress.passwordMsgKey', 'Уведомление при отсутствии ключа') + + self:Help('') + + self:Help('Каждый период во время выполнения отложенного действия будет воспроизводиться анимация') + octolib.vars.slider(self, 'tools.progress.animTime', L.time, 0.1, 10, 2):DockMargin(0, 10, 0, 0) + octolib.vars.comboBox(self, 'tools.progress.animAction', L.action, { + {'Нет', false,}, + {'Одобрять', 'agree',}, + {'Есть', 'eat'}, + {'Кланяться', 'bow',}, + {'Махать пальцем', 'disagree',}, + {'Махать рукой', 'wave',}, + {'Делать', 'drop',}, + {'Бросать предмет', 'throw',}, + {'Махать перед собой', 'shove',}, + {'Давать предмет', 'give',}, + }) + + octolib.vars.textEntry(self, 'tools.progress.animSound', L.game_sound) + self:Button(L.browser_sound, 'wire_sound_browser_open'):DockMargin(0, 10, 0, 0) + + self:Help('') + + self:Help('После выполнения отложенного действия может быть выполнен функционал триггера') + octolib.vars.slider(self, 'tools.progress.duration', L.duration_sec, 1, 300, 0):DockMargin(0, 10, 0, 0) + octolib.vars.textEntry(self, 'tools.progress.title', L.title2) + + local e = octolib.vars.textEntry(self, 'tools.progress.text', L.text) + e:SetMultiline(true) + e:SetTall(150) + e:SetContentAlignment(7) + + octolib.vars.comboBox(self, 'tools.progress.method', L.trigger_type_notification, { + {L.trigger_chat, 'chat'}, + {L.trigger_center, 'center'}, + {L.notification, 'notify'}, + }) + + octolib.vars.textEntry(self, 'tools.progress.gamesound', L.game_sound) + self:Button(L.browser_sound, 'wire_sound_browser_open'):DockMargin(0, 10, 0, 0) + local eURL = octolib.vars.textEntry(self, 'tools.progress.urlsound', L.url_sound) + if not LocalPlayer():query(L.permissions_trigger_url) then eURL:SetEnabled(false) end + octolib.vars.slider(self, 'tools.progress.volume', L.volume, 0, 1, 2):DockMargin(0, 10, 0, 0) + octolib.vars.binder(self, 'tools.progress.bind', L.binder, 0):DockMargin(0, 10, 0, 0) + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + local function openEditor(save, row) + local f = vgui.Create 'DFrame' + f:SetSize(400, 600) + f:SetTitle("Прогресс - Добавить требование") + f:MakePopup() + f:Center() + + local mode = octolib.comboBox(f, 'Пароль или ключ?', { + {"Ключ", 0, true}, + {"Пароль", 1} + }) + + local class, tclass = octolib.textEntry(f, 'Класс предмета') + local password, tpassword = octolib.textEntry(f, 'Пароль') + password:Hide() + tpassword:Hide() + + local amount, tamount = octolib.textEntry(f, 'Количество') + amount:SetNumeric(true) + + local take = octolib.checkBox(f, 'Забирать после использования?') + + mode.OnSelect = function(self, index, text, data) + if data == 0 then + class:Show() + tclass:Show() + amount:Show() + tamount:Show() + password:Hide() + tpassword:Hide() + else + class:Hide() + tclass:Hide() + amount:Hide() + tamount:Hide() + password:Show() + tpassword:Show() + end + end + + octolib.button(f, 'Добавить', function() + save({ + class = class:GetValue(), + password = password:GetValue() or nil, + amount = tonumber(amount:GetValue()) or 1, + take = tobool(take:GetChecked()), + }) + f:Remove() + end) + end + + octolib.dataEditor.register('tool.progress.requirements', { + name = 'Прогресс - Требования', + columns = { + { field = 'class', name = 'Предмет' }, + { field = 'password', name = 'Пароль' }, + { field = 'amount', name = 'Количество' }, + { field = 'take', name = 'Забирать?' }, + }, + load = function(load) + load(octolib.vars.get('tools.progress.requirements') or {}) + end, + save = function(rows) + octolib.vars.set('tools.progress.requirements', rows) + end, + new = function(save) + openEditor(save) + end, + edit = function(row, save) + openEditor(save, row) + end, + }) + + language.Add('Tool.progress.name', 'Прогресс') + language.Add('Tool.progress.desc', 'Добавь задержку на взаимодействие с любым предметом') + language.Add('Tool.progress.left', L.assign) + language.Add('Tool.progress.right', L.tool_copy) + language.Add('Tool.progress.reload', L.remove) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/progress_plus.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/progress_plus.lua new file mode 100644 index 0000000..b3fb190 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/progress_plus.lua @@ -0,0 +1,309 @@ +TOOL.Category = 'Dobrograd' +TOOL.Name = 'Прогресс (расширенный)' +TOOL.Command = nil + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, + { name = 'reload' }, +} + +local vars = { + time = 3, + name = 'Работа', + animTime = 2, + mode = 0, + animAction = 'drop', + animSound = '', + duration = 10, + action = {}, + bind = 0, + requirements = {}, + passwordMsg = '', + passwordMsgKey = '', +} + +if CLIENT then + for k, v in pairs(vars) do + octolib.vars.init('tools.progress+.' .. k, v) + end +end + +local function doEffect(ent) + + local e = EffectData() + e:SetEntity(ent) + e:SetScale(1) + util.Effect('sparkling_ent', e) + +end + +local varList = octolib.table.map(table.GetKeys(vars), function(v) return 'tools.progress+.' .. v end) + +if SERVER then + netstream.Listen('tools.progress+.delayedActionData', function(reply, _, ent) + reply(IsValid(ent) and ent.delayedActionData or {}) + end) +end + +function TOOL:LeftClick(tr) + + local ent = tr.Entity + if not IsValid(ent) then return false end + local ply = self:GetOwner() + if not ply:query('DBG: Панель ивентов') then return false end + + if SERVER then + if ent:IsPlayer() and not self:GetOwner():IsSuperAdmin() then + return false + end + + local ply = self:GetOwner() + local owner = ent:CPPIGetOwner() + if owner ~= ply then + return ply:Notify('warning', 'Этот инструмент можно применять только на своих пропах') + end + + ply:GetClientVar(varList, function(vars) + local data = { + name = utf8.sub(vars['tools.progress+.name'] or '', 1, 255), + time = tonumber(vars['tools.progress+.time']) or 1, + mode = tonumber(vars['tools.progress+.mode']) or 0, + duration = math.Clamp(tonumber(vars['tools.progress+.duration']) or 10, 1, 300), + action = istable(vars['tools.progress+.action']) and vars['tools.progress+.action'] or {}, + bind = tonumber(vars['tools.progress+.bind']) or nil, + animTime = tonumber(vars['tools.progress+.animTime']) or 1, + animSound = vars['tools.progress+.animSound'], + animAction = vars['tools.progress+.animAction'], + requirements = vars['tools.progress+.requirements'] or {}, + passwordMsg = string.Trim(vars['tools.progress+.passwordMsg'] or ''), + passwordMsgKey = string.Trim(vars['tools.progress+.passwordMsgKey'] or ''), + } + + doEffect(ent) + ent.ownerSID = ply:SteamID() + ent.delayedActionData = data + duplicator.StoreEntityModifier(ent, 'del_action', data) + end) + end + + return true + +end + +function TOOL:RightClick(tr) + + if SERVER then return false end + if not IsFirstTimePredicted() then return false end + local ent = tr.Entity + if not IsValid(ent) then return false end + local ply = self:GetOwner() + if not ply:query('DBG: Панель ивентов') then return false end + + netstream.Request('tools.progress+.delayedActionData', ent):Then(function(data) + if table.IsEmpty(data) then return octolib.notify.show('warning', 'На этом энтити не настроено отложенное действие') end + for k, v in pairs(data) do + octolib.vars.set('tools.progress+.' .. k, v) + end + octolib.notify.show('hint', 'Настройки отложенного действия скопированы') + end) + + return true + +end + +function TOOL:Reload(tr) + + if not IsFirstTimePredicted() then return false end + local ply = self:GetOwner() + if not ply:query('DBG: Панель ивентов') then return false end + + local ent = tr.Entity + local data = IsValid(ent) and ent.delayedActionData + if not data then return false end + + if SERVER then + ent.delayedActionData = nil + duplicator.ClearEntityModifier(ent, 'del_action') + end + + doEffect(ent) + + return true + +end + +function TOOL:BuildCPanel() + + self:AddControl('Header', { + Text = 'Прогресс', + Description = 'Привет! Это версия инструмента "Прогресс", в которой предусмотрено больше возможностей для срабатывания. Доступен этот инструмент только членам нашей команды' + }):DockMargin(0, 10, 0, 10) + + local ply = LocalPlayer() + if IsValid(ply) and not ply:query('DBG: Панель ивентов') then return end + + octolib.vars.presetManager(self, 'tools.progress+', varList) + + octolib.vars.slider(self, 'tools.progress+.time', L.time, 0.1, 60, 0):DockMargin(0, 10, 0, 0) + octolib.vars.textEntry(self, 'tools.progress+.name', L.title) + octolib.vars.comboBox(self, 'tools.progress+.mode', 'Режим повторения', { + {'Сколько угодно раз', 0,}, + {'По одному разу на игрока', 1,}, + {'Всего один раз', 2,}, + }) + + octolib.button(self, 'Редактировать требования', function() + local editor = octolib.dataEditor.open('tool.progress+.requirements') + editor.frame:SetWide(700) + editor.frame:Center() + end):DockMargin(0, 10, 0, 10) + + octolib.vars.textEntry(self, 'tools.progress+.passwordMsg', 'Уведомление при отсутствии пароля') + octolib.vars.textEntry(self, 'tools.progress+.passwordMsgKey', 'Уведомление при отсутствии ключа') + + self:Help('') + + self:Help('Каждый период во время выполнения отложенного действия будет воспроизводиться анимация') + octolib.vars.slider(self, 'tools.progress+.animTime', L.time, 0.1, 10, 2):DockMargin(0, 10, 0, 0) + octolib.vars.comboBox(self, 'tools.progress+.animAction', L.action, { + {'Нет', false,}, + {'Одобрять', 'agree',}, + {'Есть', 'eat'}, + {'Кланяться', 'bow',}, + {'Махать пальцем', 'disagree',}, + {'Махать рукой', 'wave',}, + {'Делать', 'drop',}, + {'Бросать предмет', 'throw',}, + {'Махать перед собой', 'shove',}, + {'Давать предмет', 'give',}, + }) + + octolib.vars.textEntry(self, 'tools.progress+.animSound', L.game_sound) + self:Button(L.browser_sound, 'wire_sound_browser_open'):DockMargin(0, 10, 0, 0) + + self:Help('') + + self:Help('После выполнения отложенного действия может быть выполнено действие или сценарий из панели игровых мастеров') + local actionBtn = octolib.button(self, 'Выбери действие или сценарий', function() + local menu = DermaMenu() + -- scenarios + for _,v in ipairs(gmpanel.scenarios.list) do + menu:AddOption(v._name, function() + octolib.vars.set('tools.progress+.action', { + type = 'scenario', + obj = v, + }) + end):SetIcon(v._icon) + end + menu:AddSpacer() + -- actions + for _, v in ipairs(gmpanel.actions.added) do + menu:AddOption(v._name, function() + octolib.vars.set('tools.progress+.action', { + type = 'action', + obj = v, + }) + end):SetIcon(v._icon) + end + menu:Open() + end) + self:ControlHelp('Выбранное действие или сценарий не синхронизируется с существующим! После изменений сценария или действия в панели игрового мастера нужно заново выбрать этот сценарий или действие здесь') + self:Help('В связи с особенностями архитектуры игровой панели, действия будут выполняться от имени владельца пропа, на котором установлен прогресс. Им должен быть онлайн игрок') + + hook.Add('octolib.setVar', 'tools.trigger+.action', function(var, val) + if not (var == 'tools.progress+.action' and IsValid(actionBtn)) then return end + actionBtn:SetText(val.obj._name) + actionBtn:SetImage(val.obj._icon) + end) + + self:Help('') + octolib.vars.binder(self, 'tools.progress+.bind', L.binder, 0):DockMargin(0, 10, 0, 0) + + -- fuck DForm + for _, pnl in ipairs(self:GetChildren()) do + pnl:DockPadding(10, 0, 10, 0) + end + +end + +if CLIENT then + local function openEditor(save, row) + local f = vgui.Create 'DFrame' + f:SetSize(400, 600) + f:SetTitle("Прогресс - Добавить требование") + f:MakePopup() + f:Center() + + local mode = octolib.comboBox(f, 'Пароль или ключ?', { + {"Ключ", 0, true}, + {"Пароль", 1} + }) + + local class, tclass = octolib.textEntry(f, 'Класс предмета') + local password, tpassword = octolib.textEntry(f, 'Пароль') + password:Hide() + tpassword:Hide() + + local amount, tamount = octolib.textEntry(f, 'Количество') + amount:SetNumeric(true) + + local take = octolib.checkBox(f, 'Забирать после использования?') + + mode.OnSelect = function(self, index, text, data) + if data == 0 then + class:Show() + tclass:Show() + amount:Show() + tamount:Show() + password:Hide() + tpassword:Hide() + else + class:Hide() + tclass:Hide() + amount:Hide() + tamount:Hide() + password:Show() + tpassword:Show() + end + end + + octolib.button(f, 'Добавить', function() + save({ + class = class:GetValue(), + password = password:GetValue() or nil, + amount = tonumber(amount:GetValue()) or 1, + take = tobool(take:GetChecked()), + }) + f:Remove() + end) + end + + octolib.dataEditor.register('tool.progress+.requirements', { + name = 'Прогресс - Требования', + columns = { + { field = 'class', name = 'Предмет' }, + { field = 'password', name = 'Пароль' }, + { field = 'amount', name = 'Количество' }, + { field = 'take', name = 'Забирать?' }, + }, + load = function(load) + load(octolib.vars.get('tools.progress+.requirements') or {}) + end, + save = function(rows) + octolib.vars.set('tools.progress+.requirements', rows) + end, + new = function(save) + openEditor(save) + end, + edit = function(row, save) + openEditor(save, row) + end, + }) + + language.Add('Tool.progress_plus.name', 'Прогресс+') + language.Add('Tool.progress_plus.desc', 'Добавь задержку на взаимодействие с любым предметом, версия для администраторов и игровых мастеров') + language.Add('Tool.progress_plus.left', L.assign) + language.Add('Tool.progress_plus.right', L.tool_copy) + language.Add('Tool.progress_plus.reload', L.remove) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/proxycolor.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/proxycolor.lua new file mode 100644 index 0000000..3f957bc --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/proxycolor.lua @@ -0,0 +1,186 @@ +--Tool by NotAKid, it is probably shit soz + +TOOL.Category = "Render" +TOOL.Name = "Proxy Color" + +--add each material proxy we support +TOOL.ClientConVar[ "cs1_r" ] = 255 +TOOL.ClientConVar[ "cs1_g" ] = 255 +TOOL.ClientConVar[ "cs1_b" ] = 255 +TOOL.ClientConVar[ "cs2_r" ] = 255 +TOOL.ClientConVar[ "cs2_g" ] = 255 +TOOL.ClientConVar[ "cs2_b" ] = 255 +TOOL.ClientConVar[ "cs3_r" ] = 255 +TOOL.ClientConVar[ "cs3_g" ] = 255 +TOOL.ClientConVar[ "cs3_b" ] = 255 +TOOL.ClientConVar[ "cs4_r" ] = 255 +TOOL.ClientConVar[ "cs4_g" ] = 255 +TOOL.ClientConVar[ "cs4_b" ] = 255 +TOOL.ClientConVar[ "cs5_r" ] = 255 +TOOL.ClientConVar[ "cs5_g" ] = 255 +TOOL.ClientConVar[ "cs5_b" ] = 255 +TOOL.ClientConVar[ "cs6_r" ] = 255 +TOOL.ClientConVar[ "cs6_g" ] = 255 +TOOL.ClientConVar[ "cs6_b" ] = 255 +TOOL.ClientConVar[ "cs7_r" ] = 255 +TOOL.ClientConVar[ "cs7_g" ] = 255 +TOOL.ClientConVar[ "cs7_b" ] = 255 +TOOL.CurEntity = nil + +if CLIENT then + language.Add("proxycolor", "Proxy Color") + language.Add("tool.proxycolor.name", "Proxy Color") + language.Add("tool.proxycolor.desc", "Set colors for a supported object") + language.Add("tool.proxycolor.Color multiplier.help", "Easy shade/intensity") + language.Add("tool.proxycolor.reload", "Reset color scheme") + language.Add("tool.proxycolor.right", "Copy color scheme") + language.Add("tool.proxycolor.left", "Select object") +end + +TOOL.Information = { + { name = "left", stage = 0}, + { name = "right" }, + { name = "reload" } +} + +function TOOL:LeftClick( trace ) + local ent = trace.Entity + --I honestly dont know what this means, it was a part of the normal color tool + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + + --if the entity is a Simfphys wheel, set the fake Ghost wheel as the selected entity (the ghostwheel is the visual one) + if ent:GetClass() == "gmod_sent_vehicle_fphysics_wheel" then + ent = ent:GetChildren()[2] + end + + --The entity is valid and isn't worldspawn + if IsValid( ent ) then + + if self:GetWeapon():GetNWEntity("CurEntity") != ent then --the select then apply code + self:GetWeapon():SetNWEntity("CurEntity",ent) + self:GetWeapon():EmitSound( "garrysmod/content_downloaded.wav", 75, 100, 1, CHAN_WEAPON) + if CLIENT then + Entity(1):PrintMessage(HUD_PRINTTALK, "[Proxy Color] Rebuilt panel, check your menu!") + end + return + end + if ( CLIENT ) then return true end + + local ColorTable = {} + for i=1,7 do + local cs_r = self:GetClientNumber( "cs"..i.."_r", 0 ) + local cs_g = self:GetClientNumber( "cs"..i.."_g", 0 ) + local cs_b = self:GetClientNumber( "cs"..i.."_b", 0 ) + table.insert(ColorTable, i, Color(cs_r,cs_g,cs_b) ) + end + + ent:SetProxyColors( ColorTable ) + return true + end +end + + +function TOOL:RightClick( trace ) + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + + if ent:GetClass() == "gmod_sent_vehicle_fphysics_wheel" then + ent = ent:GetChildren()[2] + end + + if IsValid( ent ) then + local CT = ent:GetProxyColors() + if !CT then return end + + for i=1,7 do + if CT[i] == nil then CT[i] = Vector(1,1,1) end + self:GetOwner():ConCommand( "proxycolor_cs"..i.."_r " .. CT[i].r*255 ) + self:GetOwner():ConCommand( "proxycolor_cs"..i.."_g " .. CT[i].g*255 ) + self:GetOwner():ConCommand( "proxycolor_cs"..i.."_b " .. CT[i].b*255 ) + end + + return true + end +end + +function TOOL:Reload( trace ) + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + + if ent:GetClass() == "gmod_sent_vehicle_fphysics_wheel" then --resets simfphys vehicle wheel colors + ent = ent:GetChildren()[2] + end + + if CLIENT then return true end + + if IsValid( ent ) then + --reset everything to white + local ColorTable = {} + for i=1,7 do + table.insert(ColorTable, i, Color(255,255,255) ) + end + + ent:SetProxyColors( ColorTable ) + return true + end +end + +function TOOL:Think() + if CLIENT then + local netEnt = self:GetWeapon():GetNWEntity("CurEntity") + if (netEnt != self.CurEntity) then + self.CurEntity = self:GetWeapon():GetNWEntity("CurEntity") + self:UpdateControlPanel() + end + end +end + +function TOOL:UpdateControlPanel() + local CPanel = controlpanel.Get( "proxycolor" ) + CPanel:ClearControls() + self.BuildCPanel( CPanel, self.CurEntity ) +end + +local function HackyListGenThingIdk(i,Selected,CPanel,name) + if name == nil then return end + + --CREATES THE COLLAPSABLE PART OF THE MENU, NAMES IT THE MATERIALS NAME + local collapse = vgui.Create("DCollapsibleCategory") + collapse:SetLabel(name) + CPanel:AddItem(collapse) --adds the collapsable part of the menu to the panel + + local list = vgui.Create("DPanelList",collapse) + list:SetHeight(250) + list:SetPadding(10) + + list:Dock(TOP) + collapse:InvalidateLayout(true) + --CREATES THE COLOR MIXER PART OF THE MENU, PARENTS TO THE NEW COLLAPBABLE PART OF THE MENU + local ColorSlot = vgui.Create( "DColorMixer" ) + ColorSlot:SetLabel(name) + ColorSlot:SetPalette( true ) + ColorSlot:SetAlphaBar( false ) + ColorSlot:SetWangs( true ) + ColorSlot:SetConVarR("proxycolor_cs"..i.."_r") + ColorSlot:SetConVarG("proxycolor_cs"..i.."_g") + ColorSlot:SetConVarB("proxycolor_cs"..i.."_b") + list:AddItem(ColorSlot) + collapse:SetExpanded(true) +end + +local ConVarsDefault = TOOL:BuildConVarList() -- used to get the saved presets +function TOOL.BuildCPanel( CPanel, Selected ) -- the control area of the tool, gonna have to work on it to make multiple proxies easier to use + CPanel:AddControl( "Header", { Description = "#tool.proxycolor.desc" } ) + CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "proxycolor", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } ) + + if !Selected then return end + + --could use a for loop, but in testing i think this was slightly faster + if ( Selected.ColorSlot1 ) then HackyListGenThingIdk(1,Selected,CPanel,Selected.ColorSlot1Name) end + if ( Selected.ColorSlot2 ) then HackyListGenThingIdk(2,Selected,CPanel,Selected.ColorSlot2Name) end + if ( Selected.ColorSlot3 ) then HackyListGenThingIdk(3,Selected,CPanel,Selected.ColorSlot3Name) end + if ( Selected.ColorSlot4 ) then HackyListGenThingIdk(4,Selected,CPanel,Selected.ColorSlot4Name) end + if ( Selected.ColorSlot5 ) then HackyListGenThingIdk(5,Selected,CPanel,Selected.ColorSlot5Name) end + if ( Selected.ColorSlot6 ) then HackyListGenThingIdk(6,Selected,CPanel,Selected.ColorSlot6Name) end + if ( Selected.ColorSlot7 ) then HackyListGenThingIdk(7,Selected,CPanel,Selected.ColorSlot7Name) end +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_animation.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_animation.lua new file mode 100644 index 0000000..61ef83d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_animation.lua @@ -0,0 +1,415 @@ + +TOOL.Category = "Robotboy655" +TOOL.Name = "#tool.rb655_easy_animation.name" +TOOL.AnimationArray = {} + +local gLastSelecetedEntity = NULL +TOOL.SelecetedEntity = NULL + +TOOL.ClientConVar[ "anim" ] = "" +TOOL.ClientConVar[ "speed" ] = "1.0" +TOOL.ClientConVar[ "delay" ] = "0" +TOOL.ClientConVar[ "nohide" ] = "0" +TOOL.ClientConVar[ "loop" ] = "0" + +TOOL.ClientConVar[ "noglow" ] = "0" + +local function MakeNiceName( str ) + local newname = {} + for _, s in pairs( string.Explode( "_", string.Replace( str, " ", "_" ) ) ) do + if ( string.len( s ) == 0 ) then continue end + if ( string.len( s ) == 1 ) then table.insert( newname, string.upper( s ) ) continue end + table.insert( newname, string.upper( string.Left( s, 1 ) ) .. string.Right( s, string.len( s ) - 1 ) ) -- Ugly way to capitalize first letters. + end + + return string.Implode( " ", newname ) +end + +local function IsEntValid( ent ) + if ( !IsValid( ent ) or ent:IsWorld() ) then return false end + if ( table.Count( ent:GetSequenceList() or {} ) != 0 ) then return true end + return false +end + +local function PlayAnimationBase( ent, anim, speed ) + if ( !IsValid( ent ) ) then return end + + -- HACK: This is not perfect, but it will have to do + if ( ent:GetClass() == "prop_dynamic" ) then + ent:Fire( "SetAnimation", anim ) + ent:Fire( "SetPlaybackRate", math.Clamp( tonumber( speed ), 0.05, 3.05 ) ) + return + end + + ent:ResetSequence( ent:LookupSequence( anim ) ) + ent:ResetSequenceInfo() + ent:SetCycle( 0 ) + ent:SetPlaybackRate( math.Clamp( tonumber( speed ), 0.05, 3.05 ) ) + +end + +local UniqueID = 0 +function PlayAnimation( ply, ent, anim, speed, delay, loop, isPreview ) + if ( !IsValid( ent ) ) then return end + + delay = tonumber( delay ) or 0 + loop = tobool( loop ) or false + + UniqueID = UniqueID + 1 + local tid = "rb655_animation_loop_" .. ply:UniqueID() .. "-" .. UniqueID + + if ( isPreview ) then tid = "rb655_animation_loop_preview" .. ply:UniqueID() end + + timer.Create( tid, delay, 1, function() + PlayAnimationBase( ent, anim, speed ) + if ( loop == true && IsValid( ent ) ) then + timer.Adjust( tid, ent:SequenceDuration() / speed, 0, function() + if ( !IsValid( ent ) ) then timer.Remove( tid ) return end + PlayAnimationBase( ent, anim, speed ) + end ) + end + end ) +end + +function TOOL:GetSelecetedEntity() + return self:GetWeapon():GetNWEntity( 1, self.SelecetedEntity ) +end + +function TOOL:SetSelecetedEntity( ent ) + if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end + if ( !IsValid( ent ) ) then ent = NULL end + + if ( self:GetSelecetedEntity() == ent ) then return end + self.SelecetedEntity = ent + + self:GetWeapon():SetNWEntity( 1, ent ) +end + +local gOldCVar1 = GetConVarNumber( "ai_disabled" ) +local gOldCVar2 = GetConVarNumber( "rb655_easy_animation_nohide" ) + +function TOOL:Think() + local ent = self:GetSelecetedEntity() + if ( !IsValid( ent ) ) then self:SetSelecetedEntity( NULL ) end + + if ( CLIENT ) then + if ( gOldCVar1 != GetConVarNumber( "ai_disabled" ) or gOldCVar2 != GetConVarNumber( "rb655_easy_animation_nohide" ) ) then + gOldCVar1 = GetConVarNumber( "ai_disabled" ) + gOldCVar2 = GetConVarNumber( "rb655_easy_animation_nohide" ) + if ( IsEntValid( ent ) && ent:IsNPC() ) then self:UpdateControlPanel() end + end + if ( ent:EntIndex() == gLastSelecetedEntity ) then return end + gLastSelecetedEntity = ent:EntIndex() + self:UpdateControlPanel() + RunConsoleCommand( "rb655_easy_animation_anim", "" ) + end +end + +function TOOL:RightClick( trace ) + if ( SERVER ) then self:SetSelecetedEntity( trace.Entity ) end + return true +end + +function TOOL:Reload( trace ) + if ( SERVER ) then + if ( #self.AnimationArray <= 0 && IsValid( self:GetSelecetedEntity() ) ) then + self:GetSelecetedEntity():SetPlaybackRate( 0 ) + elseif ( #self.AnimationArray > 0 ) then + for id, t in pairs( self.AnimationArray ) do + if ( IsValid( t.ent ) ) then t.ent:SetPlaybackRate( 0 ) end + end + end + + -- Destroy all timers. + for i = 0, UniqueID do timer.Remove( "rb655_animation_loop_" .. self:GetOwner():UniqueID() .. "-" .. i ) UniqueID = 0 end + timer.Remove( "rb655_animation_loop_preview" .. self:GetOwner():UniqueID() ) + end + return true +end + +if ( SERVER ) then + util.AddNetworkString( "rb655_easy_animation_array" ) + + function TOOL:LeftClick( trace ) + local ent = self:GetSelecetedEntity() + local anim = self:GetClientInfo( "anim" ) + + for i = 0, UniqueID do timer.Remove( "rb655_animation_loop_" .. self:GetOwner():UniqueID() .. "-" .. i ) UniqueID = 0 end + timer.Remove( "rb655_animation_loop_preview" .. self:GetOwner():UniqueID() ) + + if ( #self.AnimationArray > 0 ) then + for id, t in pairs( self.AnimationArray ) do + if ( !IsEntValid( t.ent ) or string.len( string.Trim( t.anim ) ) == 0 ) then continue end + PlayAnimation( self:GetOwner(), t.ent, t.anim, t.speed, t.delay, t.loop ) + end + else + if ( !IsEntValid( ent ) or string.len( string.Trim( anim ) ) == 0 ) then return end + PlayAnimation( self:GetOwner(), ent, anim, self:GetClientInfo( "speed" ), self:GetClientInfo( "delay" ), self:GetClientInfo( "loop" ), true ) + end + + return true + end + + concommand.Add( "rb655_easy_animation_anim_do", function( ply, cmd, args ) + local tool = ply:GetTool( "rb655_easy_animation" ) + if ( !tool ) then return end + local ent = tool:GetSelecetedEntity() + + if ( !IsEntValid( ent ) ) then return end + + for i = 0, UniqueID do timer.Remove( "rb655_animation_loop_" .. ply:UniqueID() .. "-" .. i ) UniqueID = 0 end + timer.Remove( "rb655_animation_loop_preview" .. ply:UniqueID() ) + + PlayAnimation( ply, ent, args[ 1 ], ply:GetTool( "rb655_easy_animation" ):GetClientInfo( "speed" ), 0, ply:GetTool():GetClientInfo( "loop" ), true ) + end ) + + concommand.Add( "rb655_easy_animation_add", function( ply, cmd, args ) + local tool = ply:GetTool( "rb655_easy_animation" ) + if ( !tool ) then return end + local e = tool:GetSelecetedEntity() + local a = tool:GetClientInfo( "anim" ) + local s = tool:GetClientInfo( "speed" ) + local d = tool:GetClientInfo( "delay" ) + local l = tool:GetClientInfo( "loop" ) + if ( !IsEntValid( e ) or string.len( string.Trim( a ) ) == 0 ) then return end + + table.insert( tool.AnimationArray, {ent = e, anim = a, speed = s, delay = d, loop = l, ei = e:EntIndex()} ) + net.Start( "rb655_easy_animation_array" ) + net.WriteTable( tool.AnimationArray ) + net.Send( ply ) + end ) + + concommand.Add( "rb655_easy_animation_rid", function( ply, cmd, args ) -- rid is for RemoveID + local tool = ply:GetTool( "rb655_easy_animation" ) + if ( !tool.AnimationArray[ tonumber( args[ 1 ] ) ] ) then return end + if ( tool.AnimationArray[ tonumber( args[ 1 ] ) ].ei != tonumber( args[ 2 ] ) && tonumber( args[ 2 ] ) != 0 ) then return end + + table.remove( tool.AnimationArray, tonumber( args[ 1 ] ) ) + net.Start( "rb655_easy_animation_array" ) + net.WriteTable( tool.AnimationArray ) + net.Send( ply ) + end ) +end + +if ( SERVER ) then return end + +TOOL.Information = { + { name = "info", stage = 1 }, + { name = "left" }, + { name = "right" }, + { name = "reload" }, +} + +language.Add( "tool.rb655_easy_animation.left", "Play all animations" ) +language.Add( "tool.rb655_easy_animation.right", "Select an object to play animations on" ) +language.Add( "tool.rb655_easy_animation.reload", "Pause currently playing animation(s)" ) + +language.Add( "tool.rb655_easy_animation.name", "Easy Animation Tool" ) +language.Add( "tool.rb655_easy_animation.desc", "Easy animations for everyone" ) +language.Add( "tool.rb655_easy_animation.1", "Use context menu to play animations" ) + +language.Add( "tool.rb655_easy_animation.animations", "Animations" ) +language.Add( "tool.rb655_easy_animation.add", "Add current selection" ) +language.Add( "tool.rb655_easy_animation.add.help", "\nIf you want to play animations on multiple entities at one:\n1) Select entity\n2) Select animation from the list, if the entity has any.\n3) Configure sliders to your desire.\n4) Click \"Add current selection\"\n5) Do 1-4 steps as many times as you wish.\n6) Left-click\n\nYou cannot play two animations on the same entity at the same time. The last animation will cut off the first one." ) +language.Add( "tool.rb655_easy_animation.speed", "Animation Speed" ) +language.Add( "tool.rb655_easy_animation.speed.help", "How fast the animation will play." ) +language.Add( "tool.rb655_easy_animation.delay", "Delay" ) +language.Add( "tool.rb655_easy_animation.delay.help", "The time between you left-click and the animation is played." ) +language.Add( "tool.rb655_easy_animation.loop", "Loop Animation" ) +language.Add( "tool.rb655_easy_animation.loop.help", "Play animation again when it ends." ) +language.Add( "tool.rb655_easy_animation.nohide", "Do not filter animations" ) +language.Add( "tool.rb655_easy_animation.nohide.help", "Enabling this option will show you the full list of animations available for selected entity. Please note, that this list can be so long, that GMod may freeze for a few seconds." ) + +language.Add( "tool.rb655_easy_animation.ai", "NPC is selected, but NPC thinking is not disabled!" ) +language.Add( "tool.rb655_easy_animation.ragdoll", "Ragdolls cannot be animated! Open context menu (Hold C) > right click on ragdoll > Make Animatable" ) +language.Add( "tool.rb655_easy_animation.prop", "Props cannot be animated properly! Open context menu (Hold C) > right click on entity > Make Animatable" ) +language.Add( "tool.rb655_easy_animation.badent", "This entity does not have any animations." ) +language.Add( "tool.rb655_easy_animation.noent", "No entity selected." ) + +language.Add( "tool.rb655_easy_animation.noglow", "Don't render glow/halo around models" ) +language.Add( "tool.rb655_easy_animation.noglow.help", "Don't render glow/halo around models when they are selected, and don't draw bounding boxes below animated models. Bounding boxes are a helper for when animations make the ragdolls go outside of their bounding box making them unselectable.\n" ) + +language.Add( "tool.rb655_easy_animation.property", "Make Animatable" ) +language.Add( "tool.rb655_easy_animation.property_ragdoll", "Make Ragdoll" ) +language.Add( "prop_animatable", "Animatable Entity" ) + +function TOOL:GetStage() + if ( IsValid( self:GetSelecetedEntity() ) ) then return 1 end + return 0 +end + +net.Receive( "rb655_easy_animation_array", function( len ) + local tool = LocalPlayer():GetTool( "rb655_easy_animation" ) + tool.AnimationArray = net.ReadTable() + if ( CLIENT ) then tool:UpdateControlPanel() end +end ) + +function TOOL:UpdateControlPanel( index ) + local panel = controlpanel.Get( "rb655_easy_animation" ) + if ( !panel ) then MsgN( "Couldn't find rb655_easy_animation panel!" ) return end + + panel:ClearControls() + self.BuildCPanel( panel, self:GetSelecetedEntity() ) +end + +function TOOL.BuildCPanel( panel, ent ) + if ( !IsValid( ent ) ) then + ent = LocalPlayer():GetTool( "rb655_easy_animation" ):GetSelecetedEntity() + end + + if ( !IsValid( ent ) ) then + panel:AddControl( "Label", { Text = "#tool.rb655_easy_animation.noent" } ) + elseif ( IsEntValid( ent ) ) then + local fine = true + + if ( GetConVarNumber( "ai_disabled" ) == 0 && ent:IsNPC() ) then panel:AddControl( "Label", {Text = "#tool.rb655_easy_animation.ai"} ) fine = false end + if ( ent:GetClass() == "prop_ragdoll" ) then panel:AddControl( "Label", { Text = "#tool.rb655_easy_animation.ragdoll" } ) fine = false end + if ( ent:GetClass() == "prop_physics" or ent:GetClass() == "prop_physics_multiplayer" or ent:GetClass() == "prop_physics_override" ) then panel:AddControl( "Label", { Text = "#tool.rb655_easy_animation.prop" } ) end + + local t = {} + local badBegginings = { "g_", "p_", "e_", "b_", "bg_", "hg_", "tc_", "aim_", "turn", "gest_", "pose_", "pose_", "auto_", "layer_", "posture", "bodyaccent", "a_" } + local badStrings = { "gesture", "posture", "_trans_", "_rot_", "gest", "aim", "bodyflex_", "delta", "ragdoll", "spine", "arms" } + for k, v in SortedPairsByValue( ent:GetSequenceList() ) do + local isbad = false + + for i, s in pairs( badStrings ) do if ( string.find( string.lower( v ), s ) != nil ) then isbad = true break end end + if ( isbad == true && LocalPlayer():GetTool( "rb655_easy_animation" ):GetClientNumber( "nohide" ) == 0 ) then continue end + + for i, s in pairs( badBegginings ) do if ( s == string.Left( string.lower( v ), string.len( s ) ) ) then isbad = true break end end + if ( isbad == true && LocalPlayer():GetTool( "rb655_easy_animation" ):GetClientNumber( "nohide" ) == 0 ) then continue end + + language.Add( "rb655_anim_" .. v, MakeNiceName( v ) ) + t[ "#rb655_anim_" .. v ] = { rb655_easy_animation_anim = v, rb655_easy_animation_anim_do = v } + end + + if ( fine ) then + local filter = panel:AddControl( "TextBox", { Label = "#spawnmenu.quick_filter_tool" } ) + filter:SetUpdateOnType( true ) + + local animList = panel:AddControl( "ListBox", { Label = "#tool.rb655_easy_animation.animations", Options = t, Height = 225 } ) + + -- patch the function to take into account visiblity + function animList:DataLayout() + local y = 0 + for k, Line in ipairs( self.Sorted ) do + if ( !Line:IsVisible() ) then continue end + + Line:SetPos( 1, y ) + Line:SetSize( self:GetWide() - 2, self.m_iDataHeight ) + Line:DataLayout( self ) + + Line:SetAltLine( k % 2 == 1 ) + + y = y + Line:GetTall() + end + + return y + end + + filter.OnValueChange = function( s, txt ) + for id, pnl in pairs( animList:GetCanvas():GetChildren() ) do + if ( !pnl:GetValue( 1 ):lower():find( txt:lower() ) ) then + pnl:SetVisible( false ) + else + pnl:SetVisible( true ) + end + end + animList:SetDirty( true ) + animList:InvalidateLayout() + end + end + elseif ( !IsEntValid( ent ) ) then + panel:AddControl( "Label", { Text = "#tool.rb655_easy_animation.badent" } ) + end + + local pnl = vgui.Create( "DPanelList" ) + pnl:SetHeight( 225 ) + pnl:EnableHorizontal( false ) + pnl:EnableVerticalScrollbar( true ) + pnl:SetSpacing( 2 ) + pnl:SetPadding( 2 ) + Derma_Hook( pnl, "Paint", "Paint", "Panel" ) -- Awesome GWEN background + + local tool = LocalPlayer():GetTool( "rb655_easy_animation" ) + if ( tool && tool.AnimationArray ) then + for i, d in pairs( tool.AnimationArray ) do + local s = vgui.Create( "RAnimEntry" ) + s:SetInfo( i, d.ent, d.anim, d.speed, d.delay, d.loop ) + pnl:AddItem( s ) + end + end + + panel:AddPanel( pnl ) + + panel:AddControl( "Button", { Label = "#tool.rb655_easy_animation.add", Command = "rb655_easy_animation_add" } ) + panel:ControlHelp( "#tool.rb655_easy_animation.add.help" ) + panel:AddControl( "Slider", { Label = "#tool.rb655_easy_animation.speed", Type = "Float", Min = 0.05, Max = 3.05, Command = "rb655_easy_animation_speed", Help = true } ) + panel:AddControl( "Slider", { Label = "#tool.rb655_easy_animation.delay", Type = "Float", Min = 0, Max = 32, Command = "rb655_easy_animation_delay", Help = true } ) + panel:AddControl( "Checkbox", { Label = "#tool.rb655_easy_animation.loop", Command = "rb655_easy_animation_loop", Help = true } ) + panel:AddControl( "Checkbox", { Label = "#tool.rb655_easy_animation.nohide", Command = "rb655_easy_animation_nohide", Help = true } ) + panel:AddControl( "Checkbox", { Label = "#tool.rb655_easy_animation.noglow", Command = "rb655_easy_animation_noglow", Help = true } ) +end + +function TOOL:DrawHUD() + local ent = self:GetSelecetedEntity() + if ( !IsValid( ent ) or tobool( self:GetClientNumber( "noglow" ) ) ) then return end + + local t = { ent } + if ( ent.GetActiveWeapon ) then table.insert( t, ent:GetActiveWeapon() ) end + halo.Add( t, HSVToColor( ( CurTime() * 3 ) % 360, math.abs( math.sin( CurTime() / 2 ) ), 1 ), 2, 2, 1 ) +end + +local PANEL = {} + +function PANEL:Init() + self.ent = nil + self.anim = "attack01" + self.id = 0 + self.eid = 0 + self.speed = 1 + self.delay = 0 + self.loop = false + + self.rem = vgui.Create( "DImageButton", self ) + self.rem:SetImage( "icon16/cross.png" ) + self.rem:SetSize( 16, 16 ) + self.rem:SetPos( 4, 4 ) + self.rem.DoClick = function() + self:RemoveFull() + end +end + +function PANEL:RemoveFull() + self.rem:Remove() + self:Remove() + RunConsoleCommand( "rb655_easy_animation_rid", self.id, self.eid ) +end + +function PANEL:Paint( w, h ) + draw.RoundedBox( 2, 0, 0, w, h, Color( 50, 50, 50, 225 ) ) + if ( !self.ent or !IsValid( self.ent ) ) then self:RemoveFull() return end + + surface.SetFont( "DermaDefault" ) + draw.SimpleText( "#" .. self.ent:GetClass(), "DermaDefault", 24, 0, Color( 255, 255, 255, 255 ) ) + draw.SimpleText( "#rb655_anim_" .. self.anim, "DermaDefault", 24, 10, Color( 255, 255, 255, 255 ) ) + + local tW = surface.GetTextSize( "#" .. self.ent:GetClass() ) + draw.SimpleText( " #" .. self.ent:EntIndex(), "DermaDefault", 24 + tW, 0, Color( 255, 255, 255, 255 ) ) + + local tW2 = surface.GetTextSize( "#rb655_anim_" .. self.anim ) + local t = " [ S: " .. self.speed .. ", D: " .. self.delay + if ( self.loop ) then t = t .. ", Looping" end + draw.SimpleText( t .. " ]", "DermaDefault", 24 + tW2, 10, Color( 255, 255, 255, 255 ) ) +end + +function PANEL:SetInfo( id, e, a, s, d, l ) + self.id = id + self.eid = e:EntIndex() + self.ent = e + self.anim = a + self.speed = s + self.delay = d + self.loop = tobool( l ) +end + +vgui.Register( "RAnimEntry", PANEL, "Panel" ) diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_bodygroup.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_bodygroup.lua new file mode 100644 index 0000000..2380041 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_bodygroup.lua @@ -0,0 +1,178 @@ + +TOOL.Category = "Robotboy655" +TOOL.Name = "#tool.rb655_easy_bodygroup.name" + +local gLastSelecetedEntity = NULL + +local MaxBodyGroups = 72 + +TOOL.ClientConVar[ "noglow" ] = "0" +TOOL.ClientConVar[ "skin" ] = "0" +for i = 0, MaxBodyGroups do TOOL.ClientConVar[ "group" .. i ] = "1" end + +local function MakeNiceName( str ) + local newname = {} + + for _, s in pairs( string.Explode( "_", str ) ) do + if ( string.len( s ) == 1 ) then table.insert( newname, string.upper( s ) ) continue end + table.insert( newname, string.upper( string.Left( s, 1 ) ) .. string.Right( s, string.len( s ) - 1 ) ) -- Ugly way to capitalize first letters. + end + + return string.Implode( " ", newname ) +end + +local function IsEntValid( ent ) + if ( !IsValid( ent ) or ent:IsWorld() ) then return false end + if ( ( ent:SkinCount() or 0 ) > 1 ) then return true end + if ( ( ent:GetNumBodyGroups() or 0 ) > 1) then return true end + if ( ( ent:GetBodygroupCount( 0 ) or 0 ) > 1 ) then return true end + return false +end + +local function SetBodygroup( _, ent, t ) + ent:SetBodygroup( t.group, t.id ) +end +for i = 0, MaxBodyGroups do duplicator.RegisterEntityModifier( "bodygroup" .. i, SetBodygroup ) end -- We only have this so old dupes will work + +function TOOL:GetSelecetedEntity() + return self:GetWeapon():GetNWEntity( "rb655_bodygroup_entity" ) +end + +function TOOL:SetSelecetedEntity( ent ) + if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end + if ( !IsValid( ent ) ) then ent = NULL end + + if ( self:GetSelecetedEntity() == ent ) then return end + + self:GetWeapon():SetNWEntity( "rb655_bodygroup_entity", ent ) +end + +-- The whole Ready system is to make sure it have time to sync the console vars. Not the best idea, but it does work. +if ( SERVER ) then + TOOL.Ready = 0 + + util.AddNetworkString( "rb655_easy_bodygroup_ready" ) + + net.Receive( "rb655_easy_bodygroup_ready", function( len, ply ) + local tool = ply:GetTool( "rb655_easy_bodygroup" ) + if ( tool && net.ReadEntity() == tool:GetSelecetedEntity() ) then tool.Ready = 1 end + end ) +end + +function TOOL:Think() + local ent = self:GetSelecetedEntity() + if ( !IsValid( ent ) ) then self:SetSelecetedEntity( NULL ) end + + if ( CLIENT ) then + if ( ent:EntIndex() == gLastSelecetedEntity ) then return end + gLastSelecetedEntity = ent:EntIndex() + self:UpdateControlPanel() + return + end + + if ( !IsEntValid( ent ) ) then return end + if ( self.Ready == 0 ) then return end + if ( self.Ready > 0 && self.Ready < 50 ) then self.Ready = self.Ready + 1 return end -- Another ugly workaround + + if ( ent:SkinCount() > 1 ) then ent:SetSkin( self:GetClientNumber( "skin" ) ) end + + for i = 0, ent:GetNumBodyGroups() - 1 do + if ( ent:GetBodygroupCount( i ) <= 1 ) then continue end + if ( ent:GetBodygroup( i ) == self:GetClientNumber( "group" .. i ) ) then continue end + SetBodygroup( nil, ent, { group = i, id = self:GetClientNumber( "group" .. i ) } ) + end +end + +function TOOL:LeftClick( trace ) + if ( SERVER && trace.Entity != self:GetSelecetedEntity() ) then + self.Ready = 0 + self:SetSelecetedEntity( trace.Entity ) + end + return true +end + +function TOOL:RightClick( trace ) return self:LeftClick( trace ) end + +function TOOL:Reload() + if ( SERVER ) then + self.Ready = 0 + self:SetSelecetedEntity( self:GetOwner() ) + end + return true +end + +if ( SERVER ) then return end + +TOOL.Information = { + { name = "info", stage = 1 }, + { name = "left" }, + { name = "reload" }, +} + +language.Add( "tool.rb655_easy_bodygroup.left", "Select an object to edit" ) +language.Add( "tool.rb655_easy_bodygroup.reload", "Select yourself" ) + +language.Add( "tool.rb655_easy_bodygroup.name", "Easy Bodygroup Tool" ) +language.Add( "tool.rb655_easy_bodygroup.desc", "Eases change of bodygroups and skins" ) +language.Add( "tool.rb655_easy_bodygroup.1", "Use context menu to edit bodygroups or skins" ) + +language.Add( "tool.rb655_easy_bodygroup.noglow", "Don't render glow/halo around models" ) +language.Add( "tool.rb655_easy_bodygroup.skin", "Skin" ) +language.Add( "tool.rb655_easy_bodygroup.badent", "This entity does not have any skins or bodygroups." ) +language.Add( "tool.rb655_easy_bodygroup.noent", "No entity selected." ) + +function TOOL:GetStage() + if ( IsValid( self:GetSelecetedEntity() ) ) then return 1 end + return 0 +end + +function TOOL:UpdateControlPanel( index ) + local panel = controlpanel.Get( "rb655_easy_bodygroup" ) + if ( !panel ) then MsgN( "Couldn't find rb655_easy_bodygroup panel!" ) return end + + panel:ClearControls() + self.BuildCPanel( panel, self:GetSelecetedEntity() ) +end + +-- We don't use the normal automatic stuff because we need to leave out the noglow convar +local ConVarsDefault = {} +ConVarsDefault[ "rb655_easy_bodygroup_skin" ] = 0 +for i = 0, MaxBodyGroups do ConVarsDefault[ "rb655_easy_bodygroup_group" .. i ] = 0 end + +function TOOL.BuildCPanel( panel, ent ) + panel:AddControl( "Checkbox", { Label = "#tool.rb655_easy_bodygroup.noglow", Command = "rb655_easy_bodygroup_noglow" } ) + + if ( !IsValid( ent ) ) then panel:AddControl( "Label", { Text = "#tool.rb655_easy_bodygroup.noent" } ) return end + if ( !IsEntValid( ent ) ) then panel:AddControl( "Label", { Text = "#tool.rb655_easy_bodygroup.badent" } ) return end + + panel:AddControl( "ComboBox", { + MenuButton = 1, + Folder = "rb655_ez_bg_" .. ent:GetModel():lower():Replace( "/", "_" ):StripExtension():sub( 8 ), -- Some hacky bussiness + Options = { [ "#preset.default" ] = ConVarsDefault }, + CVars = table.GetKeys( ConVarsDefault ) + } ) + + if ( ent:SkinCount() > 1 ) then + LocalPlayer():ConCommand( "rb655_easy_bodygroup_skin " .. ent:GetSkin() ) + panel:AddControl( "Slider", { Label = "#tool.rb655_easy_bodygroup.skin", Max = ent:SkinCount() - 1, Command = "rb655_easy_bodygroup_skin" } ) + end + + for k = 0, ent:GetNumBodyGroups() - 1 do + if ( ent:GetBodygroupCount( k ) <= 1 ) then continue end + LocalPlayer():ConCommand( "rb655_easy_bodygroup_group" .. k .. " " .. ent:GetBodygroup( k ) ) + panel:AddControl( "Slider", { Label = MakeNiceName( ent:GetBodygroupName( k ) ), Max = ent:GetBodygroupCount( k ) - 1, Command = "rb655_easy_bodygroup_group" .. k } ) + end + + net.Start( "rb655_easy_bodygroup_ready" ) + net.WriteEntity( ent ) + net.SendToServer() +end + +function TOOL:DrawHUD() + local ent = self:GetSelecetedEntity() + if ( !IsValid( ent ) or tobool( self:GetClientNumber( "noglow" ) ) ) then return end + + local t = { ent } + if ( ent.GetActiveWeapon ) then table.insert( t, ent:GetActiveWeapon() ) end + halo.Add( t, HSVToColor( ( CurTime() * 3 ) % 360, math.abs( math.sin( CurTime() / 2 ) ), 1 ), 2, 2, 1 ) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_bonemerge.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_bonemerge.lua new file mode 100644 index 0000000..33d5e8c --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/rb655_easy_bonemerge.lua @@ -0,0 +1,548 @@ + +TOOL.Category = "Robotboy655" +TOOL.Name = "#tool.rb655_easy_bonemerge.name" + +TOOL.ClientConVar[ "noglow" ] = "0" + +if ( SERVER ) then + + -- Replaces the bonemerged entity with a custom one for easier everything for the tool + local function ReplaceEntity( oldent ) + local newEntity = ents.Create( "ent_bonemerged" ) + newEntity:SetModel( oldent:GetModel() ) + newEntity:SetSkin( oldent:GetSkin() || 0 ) + if ( oldent:GetFlexScale() != newEntity:GetFlexScale() ) then newEntity:SetFlexScale( oldent:GetFlexScale() ) end -- Don't create unnecessary entities + if ( oldent:GetNumBodyGroups() ) then + for id = 0, oldent:GetNumBodyGroups() - 1 do newEntity:SetBodygroup( id, oldent:GetBodygroup( id ) ) end + end + for i = 0, oldent:GetFlexNum() - 1 do newEntity:SetFlexWeight( i, oldent:GetFlexWeight( i ) ) end + for i = 0, oldent:GetBoneCount() do + if ( oldent:GetManipulateBoneScale( i ) != newEntity:GetManipulateBoneScale( i ) ) then newEntity:ManipulateBoneScale( i, oldent:GetManipulateBoneScale( i ) ) end + if ( oldent:GetManipulateBoneAngles( i ) != newEntity:GetManipulateBoneAngles( i ) ) then newEntity:ManipulateBoneAngles( i, oldent:GetManipulateBoneAngles( i ) ) end + if ( oldent:GetManipulateBonePosition( i ) != newEntity:GetManipulateBonePosition( i ) ) then newEntity:ManipulateBonePosition( i, oldent:GetManipulateBonePosition( i ) ) end + if ( oldent:GetManipulateBoneJiggle( i ) != newEntity:GetManipulateBoneJiggle( i ) ) then newEntity:ManipulateBoneJiggle( i, oldent:GetManipulateBoneJiggle( i ) ) end + end + + newEntity:Spawn() + + newEntity.EntityMods = oldent.EntityMods + newEntity.BoneMods = oldent.BoneMods + + duplicator.ApplyEntityModifiers( nil, newEntity ) + duplicator.ApplyBoneModifiers( nil, newEntity ) + + return newEntity + end + + -- Adds any constrained entities to the bonemerge + function rb655_CheckForWelds( ent, parent ) + if ( !constraint.HasConstraints( ent ) ) then return end + + for _, v in pairs( constraint.GetAllConstrainedEntities( ent ) ) do + if ( v == ent ) then continue end + if ( constraint.FindConstraint( v, "EasyBonemergeParent" ) ) then continue end + + local oldent = v + if ( IsValid( v ) && v:GetClass() == "prop_effect" ) then oldent = v.AttachedEntity end + + local newEntity = ReplaceEntity( oldent ) + + newEntity.LocalPos = ent:WorldToLocal( v:GetPos() ) + newEntity.LocalAng = ent:WorldToLocalAngles( v:GetAngles() ) + + constraint_EasyBonemergeParent( parent, newEntity ) + + v:Remove() + end + end + + -- Allows for bonemerging depth + function rb655_CheckForBonemerges( oldent, newent ) + for _, ent in ipairs(ents.FindByClassAndParent("ent_bonemerged", oldent) or {}) do + if ( !ent.LocalPos ) then + rb655_ApplyBonemerge( ent, newent ) + end + end + end + + -- Entry point + function rb655_ApplyBonemerge( ent, selectedEnt ) + local oldent = ent + if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then oldent = ent.AttachedEntity end + + local newEntity = ReplaceEntity( oldent ) + + constraint_EasyBonemerge( selectedEnt, newEntity ) + rb655_CheckForBonemerges( oldent, newEntity ) + rb655_CheckForWelds( oldent, newEntity ) + + ent:Remove() + + return newEntity + end + + function constraint_EasyBonemerge( Ent1, Ent2, EntityMods, BoneMods ) + if ( !IsValid( Ent1 ) ) then MsgN( "Easy Bonemerge Tool: Your dupe/save is missing the target entity, cannot apply bonemerged props!" ) return end + if ( !IsValid( Ent2 ) ) then MsgN( "Easy Bonemerge Tool: Your dupe/save is missing the bonemerged prop, cannot restore bonemerged prop!" ) return end + + Ent2:SetParent( Ent1, 0 ) + + -- I don't remember why I put these here + Ent2:SetMoveType( MOVETYPE_NONE ) + Ent2:SetLocalPos( Vector( 0, 0, 0 ) ) + Ent2:SetLocalAngles( Angle( 0, 0, 0 ) ) + + Ent2:AddEffects( EF_BONEMERGE ) + --Ent2:Fire( "SetParentAttachment", Ent1:GetAttachments()[1].name ) + + constraint.AddConstraintTable( Ent1, Ent2, Ent2 ) + + Ent2:SetTable( { + Type = "EasyBonemerge", + Ent1 = Ent1, + Ent2 = Ent2, + EntityMods = EntityMods || Ent2.EntityMods, + BoneMods = BoneMods || Ent2.BoneMods + } ) + + duplicator.ApplyEntityModifiers( nil, Ent2 ) + duplicator.ApplyBoneModifiers( nil, Ent2 ) + + Ent1:DeleteOnRemove( Ent2 ) + + rb655_CheckForBonemerges( Ent2, Ent2 ) + + return Ent2 + end + duplicator.RegisterConstraint( "EasyBonemerge", constraint_EasyBonemerge, "Ent1", "Ent2", "EntityMods", "BoneMods" ) + + function constraint_EasyBonemergeParent( Ent1, Ent2, LocalPos, LocalAng, EntityMods, BoneMods ) + if ( !IsValid( Ent1 ) ) then MsgN( "Easy Bonemerge Tool: Your dupe/save is missing parent target entity, cannot apply bonemerged props!" ) return end + if ( !IsValid( Ent2 ) ) then MsgN( "Easy Bonemerge Tool: Your dupe/save is missing parent bonemerged prop, cannot restore bonemerged prop!" ) return end + + Ent2:SetParent( Ent1, 0 ) + Ent2.BoneMergeParent = true + + Ent2:SetLocalPos( LocalPos || Ent2.LocalPos ) + Ent2:SetLocalAngles( LocalAng || Ent2.LocalAng ) + + constraint.AddConstraintTable( Ent1, Ent2, Ent2 ) + + Ent2:SetTable( { + Type = "EasyBonemergeParent", + Ent1 = Ent1, + Ent2 = Ent2, + LocalPos = LocalPos || Ent2.LocalPos, + LocalAng = LocalAng || Ent2.LocalAng, + EntityMods = EntityMods || Ent2.EntityMods, + BoneMods = BoneMods || Ent2.BoneMods + } ) + + duplicator.ApplyEntityModifiers( nil, Ent2 ) + duplicator.ApplyBoneModifiers( nil, Ent2 ) + + Ent1:DeleteOnRemove( Ent2 ) + + return Ent2 + end + duplicator.RegisterConstraint( "EasyBonemergeParent", constraint_EasyBonemergeParent, "Ent1", "Ent2", "LocalPos", "LocalAng", "EntityMods", "BoneMods" ) + + -- Undo bonemerges from UI + util.AddNetworkString( "rb655_bm_undo" ) + net.Receive( "rb655_bm_undo", function( len, ply ) + local ent = net.ReadEntity() + if ( !IsValid( ent ) || ent:GetClass() != "ent_bonemerged" ) then return end + + local parent = ent:GetParent() + if ( !IsValid( parent ) ) then return end + + local tool = ply:GetTool( "rb655_easy_bonemerge" ) + if ( !istable( tool ) ) then return end + + if ( tool:GetSelectedEntity() != parent ) then return end + + ply:SendLua( "hook.Run( 'OnUndo', 'Bonemerge' )" ) + ent:Remove() + end ) + +end + +function TOOL:GetSelectedEntity() + return self:GetWeapon():GetNWEntity( "rb655_bonemerge_entity" ) +end + +function TOOL:SetSelectedEntity( ent ) + if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end + if ( !IsValid( ent ) ) then ent = NULL end + if ( IsValid( ent ) && ent:GetModel():StartWith( "*" ) ) then ent = NULL end + if ( IsValid( ent ) && ent:IsPlayer() && ent != self:GetOwner() && !self:GetOwner():query('DBG: Применять тулы на игроках') ) then ent = NULL end + + if ( self:GetSelectedEntity() == ent ) then return end + + self:GetWeapon():SetNWEntity( "rb655_bonemerge_entity", ent ) +end + +function TOOL:LeftClick( tr ) + local ent = self:GetSelectedEntity() + if ( IsValid( tr.Entity ) && tr.Entity:GetClass() == "prop_effect" ) then tr.Entity = tr.Entity.AttachedEntity end + + if ( !IsValid( ent ) || !IsValid( tr.Entity ) || tr.Entity == ent || tr.Entity:IsPlayer() || tr.Entity:IsNPC() || tr.Entity:GetModel():StartWith( "*" ) ) then return false end + + if ( CLIENT ) then return true end + + local newEntity = rb655_ApplyBonemerge( tr.Entity, ent ) + + undo.Create( "bonemerge" ) + undo.AddEntity( newEntity ) + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + + return true +end + +function TOOL:RightClick( tr ) + local ent = !self:GetOwner():KeyDown( IN_USE ) && tr.Entity || self:GetOwner() + --if ( IsValid( ent ) && #( ent:GetAttachments() || {} ) < 1 ) then return false end + if ( SERVER ) then self:SetSelectedEntity( ent ) end + return true +end + +function TOOL:Reload( tr ) + local ent = !self:GetOwner():KeyDown( IN_USE ) && tr.Entity || self:GetOwner() + if ( !IsValid( ent ) ) then return false end + if ( SERVER --[[&& ( constraint.HasConstraints( ent, "EasyBonemerge" ) || constraint.HasConstraints( ent, "EasyBonemergeParent" ) )]] ) then + constraint.RemoveConstraints( ent, "EasyBonemerge" ) + constraint.RemoveConstraints( ent, "EasyBonemergeParent" ) + end + return true +end + +function TOOL:MakeGhostEntity( model, pos, angle ) + + util.PrecacheModel( model ) + + -- We do ghosting serverside in single player + -- It's done clientside in multiplayer + if ( SERVER && !game.SinglePlayer() ) then return end + if ( CLIENT && game.SinglePlayer() ) then return end + + -- Release the old ghost entity + self:ReleaseGhostEntity() + + -- Don't allow ragdolls/effects to be ghosts + -- if ( !util.IsValidProp( model ) ) then return end + + if ( CLIENT ) then + self.GhostEntity = ents.CreateClientProp( model ) + else + self.GhostEntity = ents.Create( "prop_physics" ) + end + + -- If there's too many entities we might not spawn.. + if ( !IsValid( self.GhostEntity ) ) then + self.GhostEntity = nil + return + end + + self.GhostEntity:SetModel( model ) + self.GhostEntity:SetPos( pos ) + self.GhostEntity:SetAngles( angle ) + self.GhostEntity:Spawn() + + self.GhostEntity:SetSolid( SOLID_VPHYSICS ) + self.GhostEntity:SetMoveType( MOVETYPE_NONE ) + self.GhostEntity:SetNotSolid( true ) + self.GhostEntity:SetRenderMode( RENDERMODE_GLOW ) -- Allows for transparency and proper Z order + self.GhostEntity:SetColor( Color( 255, 255, 255, 128 ) ) + +end + +function TOOL:UpdateGhostEntity( ent, ply, tr ) + if ( !IsValid( ent ) || !IsValid( self:GetSelectedEntity() ) ) then return end + + local trEnt = tr.Entity + + if ( !IsValid( trEnt ) || trEnt == self:GetSelectedEntity() ) then + ent:SetNoDraw( true ) + return + end + + if ( trEnt:GetClass() == "prop_effect" ) then + local attachedEntity = trEnt.AttachedEntity + + if ( !IsValid( trEnt.AttachedEntity ) ) then + local tab = ents.FindByClassAndParent( "prop_dynamic", trEnt ) + if ( tab && IsValid( tab[ 1 ] ) ) then attachedEntity = tab[ 1 ] end + end + + if ( IsValid( attachedEntity ) ) then trEnt = attachedEntity end + end + + if ( trEnt:GetNumBodyGroups() ) then + for id = 0, trEnt:GetNumBodyGroups() - 1 do ent:SetBodygroup( id, trEnt:GetBodygroup( id ) ) end + end + + local clr = trEnt:GetColor() + clr.a = clr.a / 2 + ent:SetColor( clr ) + + ent:SetMaterial( trEnt:GetMaterial() ) + ent:SetSkin( trEnt:GetSkin() || 0 ) + ent:SetModel( trEnt:GetModel() ) + ent:SetParent( self:GetSelectedEntity(), 0 ) + ent:AddEffects( EF_BONEMERGE ) + ent:SetNoDraw( false ) +end + +function TOOL:Think() + if ( !IsValid( self:GetSelectedEntity() ) ) then self:ReleaseGhostEntity() return end + + local tr = util.TraceLine( { + start = self:GetOwner():GetShootPos(), + endpos = self:GetOwner():GetShootPos() + self:GetOwner():GetAimVector() * 16000, + filter = self:GetOwner(), + mask = MASK_ALL, + } ) + + if ( !IsValid( tr.Entity ) || tr.Entity == self:GetSelectedEntity() || tr.Entity:IsPlayer() || tr.Entity:GetModel():StartWith( "*" ) ) then + self:ReleaseGhostEntity() + return + end + + if ( IsValid( tr.Entity ) && !IsValid( self.GhostEntity ) ) then + self:MakeGhostEntity( tr.Entity:GetModel(), Vector( 0, 0, 0 ), Angle( 0, 0, 0 ) ) + end + + self:UpdateGhostEntity( self.GhostEntity, self:GetOwner(), tr ) +end + +if ( SERVER ) then return end + +TOOL.Information = { + { name = "left", stage = 1 }, + { name = "right" }, + { name = "right_use" }, + { name = "reload", stage = 0 }, + { name = "reload_use", stage = 0 }, +} + +language.Add( "tool.rb655_easy_bonemerge.left", "Attach a model to selected object" ) +language.Add( "tool.rb655_easy_bonemerge.right", "Select an object to attach model(s) to" ) +language.Add( "tool.rb655_easy_bonemerge.reload", "Remove all attached model(s) from an object" ) +language.Add( "tool.rb655_easy_bonemerge.right_use", "Select yourself" ) +language.Add( "tool.rb655_easy_bonemerge.reload_use", "Remove all attached model(s) from yourself" ) + +language.Add( "tool.rb655_easy_bonemerge.name", "Easy Bonemerge Tool" ) +language.Add( "tool.rb655_easy_bonemerge.desc", "Attaches models to objects using bonemerging" ) + +language.Add( "tool.rb655_easy_bonemerge.infos", [[ +What is bone merging? +Bone merging is essentially what it sounds like, you select a model and click on other models to merge their bones together. + +For bone merging to work successfully, two models MUST have at least ONE bone with exactly the same name. + +If two models do not meet this requirement, the model you are trying to attach will be placed into the center of coordinates of the selected model, which is usually in the visual center of the model or it's lowest point. + +Once bonemerged, the bones of the target model(s) will be placed into the exact positions of the bones with same names on the selected model. + +You cannot select which bones to attach objects to. Bonemerging features are defined by the model author(s) and cannot be changed without editing the model. + +Selected model - The entity you select with right click +Target model(s) - The entities you left click to bone merge onto the selected model]] ) + +language.Add( "tool.rb655_easy_bonemerge.noshared", "Warning!\nNo shared bones!\nThese 2 models are not bonemerge compatible!" ) +language.Add( "tool.rb655_easy_bonemerge.backwards", "Warning!\nSelected model has less bones than target model!\nYou are most likely trying to bonemerge backwards!" ) + +language.Add( "undone_Bonemerge", "Undone Bonemerged Prop" ) + +language.Add( "tool.rb655_easy_bonemerge.noglow", "Don't render glow/halo around models" ) +language.Add( "tool.rb655_easy_bonemerge.selected_undo", "Undo:" ) +language.Add( "tool.rb655_easy_bonemerge.noent", "No entity selected!" ) +language.Add( "tool.rb655_easy_bonemerge.nomodels", "No attached models!" ) + +function TOOL:GetStage() + if ( IsValid( self:GetSelectedEntity() ) ) then return 1 end + return 0 +end + +local function CountBonemergedChildren( ent ) + local counter = 0 + for k, v in ipairs( ent:GetChildren() ) do + if ( !IsValid( v ) || v:GetClass() != "ent_bonemerged" ) then continue end + + counter = counter + 1 + end + return counter +end + +local function UndoThisBonemerge( ent ) + net.Start( "rb655_bm_undo" ) + net.WriteEntity( ent ) + net.SendToServer() +end + +function TOOL.BuildCPanel( panel ) + + panel:Help( "#tool.rb655_easy_bonemerge.infos" ) + + panel:AddControl( "Checkbox", { Label = "#tool.rb655_easy_bonemerge.noglow", Command = "rb655_easy_bonemerge_noglow" } ) + + local pnl = vgui.Create( "DPanel", panel ) + pnl:Dock( TOP ) + pnl:DockMargin( 10, 10, 10, 10 ) + pnl.Think = function( s ) + local toolgun = LocalPlayer() + if ( !IsValid( toolgun ) || !toolgun.GetTool ) then return end + toolgun = toolgun:GetTool( "rb655_easy_bonemerge" ) + if ( !istable( toolgun ) ) then return end + + local ent = toolgun:GetSelectedEntity() + if ( !IsValid( ent ) && s.LastSelectedEntity != nil ) then + s.LastSelectedEntity = nil + s:Rebuild() + elseif ( IsValid( ent ) && ( s.LastSelectedEntity == nil || s.LastSelectedEntity != ent || ( s.LastChildrenNum || 0 ) != CountBonemergedChildren( ent ) ) ) then + s.LastSelectedEntity = ent + s.LastChildrenNum = CountBonemergedChildren( ent ) + s:Rebuild() + end + end + pnl.Rebuild = function( s ) + for k, v in pairs( s:GetChildren() ) do v:Remove() end + + if ( !IsValid( s.LastSelectedEntity ) ) then + local txt = s:Add( "DLabel" ) + txt:SetText( "#tool.rb655_easy_bonemerge.noent" ) + txt:Dock( TOP ) + txt:SetDark( true ) + txt:DockMargin( 10, 10, 10, 10 ) + + s:SetTall( 40 ) + return + end + + local height = 0 + for k, v in pairs( s.LastSelectedEntity:GetChildren() ) do + if ( !IsValid( v ) || v:GetClass() != "ent_bonemerged" ) then continue end + + local txt = s:Add( "DButton" ) + txt:SetText( "Undo " .. v:GetModel() .. "#" .. v:EntIndex() ) + txt:Dock( TOP ) + txt.ent = v + txt:DockMargin( 5, 5, 5, 0 ) + txt.DoClick = function( t ) + UndoThisBonemerge( t.ent ) + end + + height = height + txt:GetTall() + 5 + end + + if ( height > 0 ) then + s:SetTall( height + 5 ) + else + local txt = s:Add( "DLabel" ) + txt:SetText( "#tool.rb655_easy_bonemerge.nomodels" ) + txt:Dock( TOP ) + txt:SetDark( true ) + txt:DockMargin( 10, 10, 10, 10 ) + s:SetTall( 40 ) + end + end + pnl:Rebuild() + +end + +-------------------------------------------------------------------------- +----------------------------------- HUD ---------------------------------- +-------------------------------------------------------------------------- + +surface.CreateFont( "rb655_easy_bonemerge_font", { + size = ScreenScale( 8 ), + font = "Roboto" +} ) + +local function boxText( txt, _x, _y ) + surface.SetFont( "rb655_easy_bonemerge_font" ) + + local t = string.Explode( "\n", language.GetPhrase( txt ) ) + + local w, h = 0, 0 + for id, txt in pairs( t ) do + local id = id - 1 + local tW, tH = surface.GetTextSize( txt ) + w = math.max( w, tW ) + h = math.max( h, h + tH ) + end + local x, y = _x - w / 2, _y + draw.RoundedBox( 0, x - 5, y, w + 10, h + 10 , Color( 0, 0, 0, 128 ) ) + + for id, txt in pairs( t ) do + local id = id - 1 + local tW, tH = surface.GetTextSize( txt ) + + draw.SimpleText( txt, "rb655_easy_bonemerge_font", _x, _y + id * tH + 5, color_white, 1, 0 ) + end +end + +local crossmat = Material( "icon16/cross.png" ) +function TOOL:DrawHUD() + local ent = self:GetSelectedEntity() + if ( !IsValid( ent ) ) then return end + local ply, ct = LocalPlayer(), CurTime() + + if ( !tobool( self:GetClientNumber( "noglow" ) ) ) then + local t = { ent } + if ( ent.GetActiveWeapon ) then table.insert( t, ent:GetActiveWeapon() ) end + halo.Add( t, HSVToColor( ( ct * 3 ) % 360, math.abs( math.sin( ct / 2 ) ), 1 ), 2, 2, 1 ) + end + + -- =============================================================================================== -- + + local hasBones = false + local target = util.TraceLine( { + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * 16000, + filter = ply, + mask = MASK_ALL, + } ).Entity + + if ( !IsValid( target ) ) then return end + + if ( target:GetClass() == "prop_effect" ) then + local attachedEntity = target.AttachedEntity + + if ( !IsValid( target.AttachedEntity ) ) then + local tab = ents.FindByClassAndParent( "prop_dynamic", target ) + if ( tab && IsValid( tab[ 1 ] ) ) then attachedEntity = tab[ 1 ] end + end + + if ( IsValid( attachedEntity ) ) then target = attachedEntity end + end + + if ( !IsValid( target ) ) then return end + if ( target:GetModel():StartWith( "*" ) ) then return end + + local bones = {} + for id = 0, ent:GetBoneCount() - 1 do table.insert( bones, ent:GetBoneName( id ) ) end + + if ( target:GetBoneCount() ) then + for id = 0, target:GetBoneCount() - 1 do + if ( table.HasValue( bones, target:GetBoneName( id ) ) && target:GetBoneName( id ) != "__INVALIDBONE__" ) then + hasBones = true + break + end + end + end + + if ( !hasBones ) then + boxText( "tool.rb655_easy_bonemerge.noshared", ScrW() / 2, ScrH() / 2 + 32 ) + + local size = 32 + surface.SetDrawColor( color_white ) + surface.SetMaterial( crossmat ) + surface.DrawTexturedRect( ScrW() / 2 - size / 2, ScrH() / 2 - size / 2, size, size ) + end + + if ( hasBones && ent:GetBoneCount() < target:GetBoneCount() && target != ent ) then + boxText( "tool.rb655_easy_bonemerge.backwards", ScrW() / 2, ScrH() / 2 + 100 ) + end +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/shadowremover.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/shadowremover.lua new file mode 100644 index 0000000..8f4dd1d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/shadowremover.lua @@ -0,0 +1,201 @@ + +--[[----------------------------------------------------------------------------------- + + Tool Creator - SnowredWolf | STEAM_0:0:41063225 | https://SnowredWolf.net + Tool Purpose - Remove shadows from props + +-------------------------------------------------------------------------------------]] + + +--[[------------------------------------------------------------------------- + Default tool stuff +---------------------------------------------------------------------------]] + TOOL.Category = "Render" + TOOL.Author = "SnowredWolf" + TOOL.Name = "#tool.shadowremover.name" + TOOL.Desc = "#tool.shadowremover.desc" + TOOL.ConfigName = "" + + TOOL.ClientConVar[ "colorspeed" ] = 10 + +--[[------------------------------------------------------------------------- + Variables and networking +---------------------------------------------------------------------------]] + shadowremovertool = {} + shadowremovertool.proplist = shadowremovertool.proplist or {} + + +--[[------------------------------------------------------------------------- + Setting up the tool options +---------------------------------------------------------------------------]] + if CLIENT then + TOOL.Information = { + { name = "info", stage = 1 }, + { name = "left" }, + { name = "right" }, + { name = "left_reload", icon2 = "gui/r.png"}, + { name = "right_reload", icon2 = "gui/r.png"} + } + + language.Add("tool.shadowremover.name", "Shadow Remover") + language.Add("tool.shadowremover.left", "Remove shadows on the prop you are looking at") + language.Add("tool.shadowremover.right", "Add shadows to the prop you are looking at") + language.Add("tool.shadowremover.left_reload", "Make the prop darker" ) + language.Add("tool.shadowremover.right_reload", "Make the prop brighter" ) + language.Add("tool.shadowremover.desc", "Used to disable map shadows from props, making you able to avoid pitch black props!") + end + +--[[------------------------------------------------------------------------- + Handle what happens when using left click +---------------------------------------------------------------------------]] + function TOOL:LeftClick(trace) + local ent = trace.Entity + + if IsEntity(ent) and (ent:GetClass() == "prop_physics" or ent:GetClass() == "prop_ragdoll") then + if CLIENT then return true end + if not self:GetOwner():KeyDown(IN_RELOAD) then + shadowremovertool.removepropshadow(ent) + else + shadowremovertool.makedarker(self, ent) + end + + return true + end + + return false + end + +--[[------------------------------------------------------------------------- + Handle what happens when using right click +---------------------------------------------------------------------------]] + function TOOL:RightClick(trace) + local ent = trace.Entity + if IsEntity(ent) and (ent:GetClass() == "prop_physics" or ent:GetClass() == "prop_ragdoll") then + if CLIENT then return true end + if not self:GetOwner():KeyDown(IN_RELOAD) then + shadowremovertool.addpropshadows(ent) + else + shadowremovertool.makebrighter(self, ent) + end + return true + end + + return false + end + + +--[[------------------------------------------------------------------------- + Handle what happens when pressing reload +---------------------------------------------------------------------------]] + function TOOL:Reload(trace) return false end + +--[[------------------------------------------------------------------------- + Serverside functions to handle shadows +---------------------------------------------------------------------------]] + if SERVER then + local colnew + local h, s, v + local entcolor + + function shadowremovertool.removepropshadow(ent) + shadowremovertool.proplist[ent] = true + ent.originalcolor = ent:GetColor() + duplicator.StoreEntityModifier( ent, "shadow", {true} ) + netstream.Start(nil, "propshadow", { ent }, true) + end + + function shadowremovertool.addpropshadows(ent) + if not shadowremovertool.proplist[ent] then return false end + shadowremovertool.proplist[ent] = nil + ent:SetColor(ent.originalcolor) + ent.originalcolor = nil + duplicator.ClearEntityModifier( ent, "shadow" ) + + netstream.Start(nil, "propshadow", { ent }, false) + end + + function shadowremovertool.makedarker(self, ent) + if not shadowremovertool.proplist[ent] then return false end + entcolor = ent:GetColor() + h, s, v = ColorToHSV(entcolor) + if v == 0.1 then return end + + if 0.1 < v - self:GetClientNumber( "colorspeed", 10) / 100 then + v = v - self:GetClientNumber( "colorspeed", 10) / 100 + else + v = 0.1 + end + + colnew = HSVToColor(h, s, v) + ent:SetColor(colnew) + duplicator.StoreEntityModifier( ent, "shadow", {true, colnew} ) + end + + function shadowremovertool.makebrighter(self, ent) + if not shadowremovertool.proplist[ent] then return false end + entcolor = ent:GetColor() + h, s, v = ColorToHSV(entcolor) + if v == 0.99 then return end + + if 0.99 > self:GetClientNumber( "colorspeed", 10) / 100 + v then + v = v + self:GetClientNumber( "colorspeed", 10) / 100 + else + v = 0.99 + end + + colnew = HSVToColor(h, s, v) + ent:SetColor(colnew) + end + + function shadowremovertool.loadpropshadowsonjoin(ply) + timer.Simple(12, function() + if not IsValid(ply) then return end + netstream.Heavy(ply, "propshadow", table.GetKeys(shadowremovertool.proplist), true) + end) + end + + hook.Add("PlayerFinishedLoading", "RemoveShadowsWhenInitialSpawn", shadowremovertool.loadpropshadowsonjoin) + end + +--[[------------------------------------------------------------------------- + Build the tool panel +---------------------------------------------------------------------------]] + function TOOL.BuildCPanel(panel) + panel:AddControl("label", { + text = "How fast should a prop become darker/brighter in %?" + }) + + panel:AddControl("Slider", { + Label = "Percentage: ", + Type = "Float", + Min = "1", + Max = "100", + Command = "shadowremover_colorspeed" + }) + end + +--[[------------------------------------------------------------------------- + Clientside networking +---------------------------------------------------------------------------]] + local noShadow = function(self) render.SuppressEngineLighting(true) self:DrawModel() render.SuppressEngineLighting(false) end + netstream.Hook('propshadow', function(ents, enable) + octolib.whenNotNull(ents, function(ent, i) + ent.RenderOverride = enable and noShadow or nil + end, 900) + end) + +duplicator.RegisterEntityModifier("shadow", function(ply, ent, data) + + local override = hook.Run('CanTool', ply, { Entity = ent }, 'shadowremover') + if override == false then return end + + if data then + if data[1] then + shadowremovertool.removepropshadow(ent) + end + if data[2] then + ent:SetColor(data[2]) + end + end + +end) \ No newline at end of file diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/stacker_improved.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/stacker_improved.lua new file mode 100644 index 0000000..28f5132 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/stacker_improved.lua @@ -0,0 +1,1674 @@ +--[[-------------------------------------------------------------------------- + Improved Stacker Tool + + Note: + Please DO NOT reupload this tool (verbatim or small tweaks) to the workshop or other public file-sharing websites. + I actively maintain this tool, so reuploading it may lead to people using outdated, buggy, or malicious copies. + If there is an issue with the tool, LET ME KNOW via one of the following pages: + + - GitHub: https://github.com/Mista-Tea/improved-stacker + - Workshop: http://steamcommunity.com/sharedfiles/filedetails/?id=264467687 + - Facepunch: https://facepunch.com/showthread.php?t=1399120 + + Author: + - Original :: OverloadUT (STEAM_0:1:5250809) + - Updated for GMod 13 :: Marii (STEAM_0:1:16015332) + - Rewritten :: Mista Tea (STEAM_0:0:27507323) + + Changelog: + - May 27th, 2014 :: Added to GitHub + - May 28th, 2014 :: Added to Workshop + - Jun 5th, 2014 :: Massive overhaul + - Jul 24th, 2014 :: Large update + - Aug 12th, 2014 :: Optimizations + - Jun 30th, 2015 :: Bug fixes/features + - Jul 11th, 2015 :: Bug fixes + - Oct 26th, 2015 :: Bug fixes + - Aug 3rd, 2016 :: Bug fixes + - Aug 31st, 2016 :: Bug fixes + - Sep 2nd, 2016 :: Added Bulgarian language support + - Sep 26th, 2017 :: Added ability to toggle use of SHIFT key with LMB/RMB + - Oct 27th, 2017 :: Small client optimization, reverted nocollide implementation back to original + - Apr 14th, 2018 :: Added French language support + + Fixes: + - Prevented crash from players using very high X/Y/Z offset values. + - Prevented crash from players using very high P/Y/R rotate values. + - Prevented crash from very specific constraint settings. + - Fixed the halo option for ghosted props not working. + - Fixed massive FPS drop from halos being rendered in a Think hook instead of a PreDrawHalos hook. + - Fixed materials and color saving when duping stacked props. + - Fixed incorrect stack angles when trying to create a stack on an existing stack. + + Tweaks: + - Added convenience functions to retrieve the client convars. + - Added option to enable/disable automatically applying materials to the stacked props. + - Added option to enable/disable automatically applying colors to the stacked props. + - Added option to enable/disable automatically applying physical properties (gravity, physics material, weight) to the stacked props. + - Added support for props with multiple skins. + - Added support for external prop protections/anti-spam addons with the StackerEntity hook. + - Modified NoCollide to actually no-collide each stacker prop with every other prop in the stack. + + - Added console variables for server operators to limit various parts of stacker. + > stacker_improved_max_per_player <-inf/inf> (less than 0 == no limit) + > stacker_improved_max_per_stack <-inf/inf> (less than 0 == no limit) + > stacker_improved_max_offsetx <-inf/inf> + > stacker_improved_max_offsety <-inf/inf> + > stacker_improved_max_offsetz <-inf/inf> + > stacker_improved_force_stayinworld <0/1> + > stacker_improved_force_weld <0/1> + > stacker_improved_force_freeze <0/1> + > stacker_improved_force_nocollide <0/1> + > stacker_improved_force_nocollide_all <0/1> + > stacker_improved_delay <0/inf> + + - Added console commands for server admins to control the console variables that limit stacker. + > stacker_improved_set_max_per_player <-inf/inf> (less than 0 == no limit) + > stacker_improved_set_max_per_stack <-inf/inf> (less than 0 == no limit) + > stacker_improved_set_maxoffset <-inf/inf> + > stacker_improved_set_maxoffsetx <-inf/inf> + > stacker_improved_set_maxoffsety <-inf/inf> + > stacker_improved_set_maxoffsetz <-inf/inf> + > stacker_improved_set_force_stayinworld <0/1> + > stacker_improved_set_weld <0/1> + > stacker_improved_set_freeze <0/1> + > stacker_improved_set_nocollide <0/1> + > stacker_improved_set_nocollide_all <0/1> + > stacker_improved_set_delay <0/inf> + +----------------------------------------------------------------------------]] + +local mode = TOOL.Mode -- defined by the name of this file (default should be stacker_improved) + +--[[-------------------------------------------------------------------------- +-- Modules & Dependencies +--------------------------------------------------------------------------]]-- + +-- needed for localization support (depends on GMod locale: "gmod_language") +include( "improvedstacker/localify.lua" ) +localify.LoadSharedFile( "improvedstacker/localization.lua" ) -- loads the file containing localized phrases +local L = localify.Localize -- used for translating string tokens into localized phrases +local prefix = "#tool."..mode.."." -- prefix used for this tool's localization tokens + +-- needed for various stacker functionality +include( "improvedstacker/improvedstacker.lua" ) +improvedstacker.Initialize( mode ) + +--[[-------------------------------------------------------------------------- +-- Localized Functions & Variables +--------------------------------------------------------------------------]]-- + +-- localizing global functions/tables is an encouraged practice that improves code efficiency, +-- since accessing a local value is considerably faster than a global value +local bit = bit +local cam = cam +local net = net +local util = util +local math = math +local undo = undo +local halo = halo +local game = game +local ents = ents +local draw = draw +local hook = hook +local list = list +local pairs = pairs +local table = table +local Angle = Angle +local Color = Color +local render = render +local Vector = Vector +local tobool = tobool +local CurTime = CurTime +local surface = surface +local IsValid = IsValid +local localify = localify +local language = language +local tonumber = tonumber +local GetConVar = GetConVar +local construct = construct +local duplicator = duplicator +local constraint = constraint +local concommand = concommand +local LocalPlayer = LocalPlayer +local CreateConVar = CreateConVar +local improvedstacker = improvedstacker +local GetConVarNumber = GetConVarNumber +local RunConsoleCommand = RunConsoleCommand + +local IN_USE = IN_USE +local NOTIFY_ERROR = NOTIFY_ERROR or 1 +local MOVETYPE_NONE = MOVETYPE_NONE +local SOLID_VPHYSICS = SOLID_VPHYSICS +local RENDERMODE_TRANSALPHA = RENDERMODE_TRANSALPHA + +local TRANSPARENT = Color( 255, 255, 255, 150 ) + +local NOTIFY_DURATION = 5 -- the number of seconds to display notifications + +local MAX_ANGLE = 180 + +local showSettings = false + +--[[-------------------------------------------------------------------------- +-- Tool Settings +--------------------------------------------------------------------------]]-- + +TOOL.Category = "Construction" +TOOL.Name = "#tool."..mode..".name" + +TOOL.Information = { + "left", + "right", + { + name = "shift_left", + icon2 = "gui/e.png", + icon = "gui/lmb.png", + + }, + { + name = "shift_right", + icon2 = "gui/e.png", + icon = "gui/rmb.png", + }, + "reload", +} + +if ( CLIENT ) then + + TOOL.ClientConVar[ "mode" ] = improvedstacker.MODE_PROP + TOOL.ClientConVar[ "direction" ] = improvedstacker.DIRECTION_UP + TOOL.ClientConVar[ "count" ] = "1" + TOOL.ClientConVar[ "freeze" ] = "1" + TOOL.ClientConVar[ "weld" ] = "1" + TOOL.ClientConVar[ "nocollide" ] = "1" + TOOL.ClientConVar[ "ghostall" ] = "1" + TOOL.ClientConVar[ "material" ] = "1" + TOOL.ClientConVar[ "physprop" ] = "1" + TOOL.ClientConVar[ "color" ] = "1" + TOOL.ClientConVar[ "offsetx" ] = "0" + TOOL.ClientConVar[ "offsety" ] = "0" + TOOL.ClientConVar[ "offsetz" ] = "0" + TOOL.ClientConVar[ "pitch" ] = "0" + TOOL.ClientConVar[ "yaw" ] = "0" + TOOL.ClientConVar[ "roll" ] = "0" + TOOL.ClientConVar[ "relative" ] = "1" + TOOL.ClientConVar[ "draw_halos" ] = "0" + TOOL.ClientConVar[ "halo_r" ] = "255" + TOOL.ClientConVar[ "halo_g" ] = "0" + TOOL.ClientConVar[ "halo_b" ] = "0" + TOOL.ClientConVar[ "halo_a" ] = "255" + TOOL.ClientConVar[ "draw_axis" ] = "1" + TOOL.ClientConVar[ "axis_labels" ] = "1" + TOOL.ClientConVar[ "axis_angles" ] = "0" + TOOL.ClientConVar[ "opacity" ] = "100" + TOOL.ClientConVar[ "use_shift_key" ] = "0" + + --[[-------------------------------------------------------------------------- + -- Language Settings + --------------------------------------------------------------------------]]-- + + language.Add( "tool."..mode..".name", L(prefix.."name") ) + language.Add( "tool."..mode..".desc", L(prefix.."desc") ) + language.Add( "tool."..mode..".0", L(prefix.."0") ) + language.Add( "tool."..mode..".left", L(prefix.."left") ) + language.Add( "tool."..mode..".shift_left", L(prefix.."shift_left") ) + language.Add( "tool."..mode..".right", L(prefix.."right") ) + language.Add( "tool."..mode..".shift_right", L(prefix.."shift_right") ) + language.Add( "tool."..mode..".reload", L(prefix.."reload") ) + language.Add( "Undone_"..mode, L("Undone_"..mode) ) + + --[[-------------------------------------------------------------------------- + -- Net Messages + --------------------------------------------------------------------------]]-- + + --[[-------------------------------------------------------------------------- + -- Net :: _error( string ) + --]]-- + net.Receive( mode.."_error", function( bytes ) + surface.PlaySound( "buttons/button10.wav" ) + octolib.notify.show( net.ReadString(), net.ReadString() ) + end ) + +end + +--[[-------------------------------------------------------------------------- +-- Console Variables +--------------------------------------------------------------------------]]-- + +-- This is solely for backwards compatibility. +-- We're essentially copying everyone's old cvar values over since we're switching from 'stacker' to 'stacker_improved'. +-- If we didn't do this, we'd run the risk of ruining someone's custom setup +--[[local oldMaxTotal = GetConVar( "stacker_max_total" ) and GetConVar( "stacker_max_total" ):GetInt() or -1 +local oldMaxCount = GetConVar( "stacker_max_count" ) and GetConVar( "stacker_max_count" ):GetInt() or 15 +local oldMaxOffX = GetConVar( "stacker_max_offsetx" ) and GetConVar( "stacker_max_offsetx" ):GetFloat() or 200 +local oldMaxOffY = GetConVar( "stacker_max_offsety" ) and GetConVar( "stacker_max_offsety" ):GetFloat() or 200 +local oldMaxOffZ = GetConVar( "stacker_max_offsetz" ) and GetConVar( "stacker_max_offsetz" ):GetFloat() or 200 +local oldStayInWorld = GetConVar( "stacker_stayinworld" ) and GetConVar( "stacker_stayinworld" ):GetInt() or 1 +local oldFreeze = GetConVar( "stacker_force_freeze" ) and GetConVar( "stacker_force_freeze" ):GetInt() or 0 +local oldWeld = GetConVar( "stacker_force_weld" ) and GetConVar( "stacker_force_weld" ):GetInt() or 0 +local oldNoCollide = GetConVar( "stacker_force_nocollide" ) and GetConVar( "stacker_force_nocollide" ):GetInt() or 0 +local oldDelay = GetConVar( "stacker_delay" ) and GetConVar( "stacker_delay" ):GetFloat() or 0.25 +]] +local cvarFlags, cvarFlagsNotify + +if ( SERVER ) then + cvarFlags = bit.bor( FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE ) + cvarFlagsNotif = bit.bor( cvarFlags, FCVAR_NOTIFY ) +elseif ( CLIENT ) then + cvarFlags = bit.bor( FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE ) + cvarFlagsNotif = bit.bor( cvarFlags, FCVAR_NOTIFY ) +end + +local oldMaxTotal = CreateConVar( "stacker_max_total", -1, cvarFlagsNotif, "Defines the max amount of props that a player can have spawned from stacker" ) +local oldMaxCount = CreateConVar( "stacker_max_count", 15, cvarFlagsNotif, "Defines the max amount of props that can be stacked at a time" ) +local oldDelay = CreateConVar( "stacker_delay", 0.5, cvarFlagsNotif, "Determines the amount of time that must pass before a player can use stacker again" ) +local oldMaxOffX = CreateConVar( "stacker_max_offsetx", 200, cvarFlagsNotif, "Defines the max distance on the x plane that stacked props can be offset (for individual control)" ) +local oldMaxOffY = CreateConVar( "stacker_max_offsety", 200, cvarFlagsNotif, "Defines the max distance on the y plane that stacked props can be offset (for individual control)" ) +local oldMaxOffZ = CreateConVar( "stacker_max_offsetz", 200, cvarFlagsNotif, "Defines the max distance on the z plane that stacked props can be offset (for individual control)" ) +local oldFreeze = CreateConVar( "stacker_force_freeze", 0, cvarFlagsNotif, "Determines whether props should be forced to spawn frozen or not" ) +local oldWeld = CreateConVar( "stacker_force_weld", 0, cvarFlagsNotif, "Determines whether props should be forced to spawn welded or not" ) +local oldNoCollide = CreateConVar( "stacker_force_nocollide", 0, cvarFlagsNotif, "Determines whether props should be forced to spawn nocollided or not" ) +local oldStayInWorld = CreateConVar( "stacker_stayinworld", 1, cvarFlagsNotif, "Determines whether props should be restricted to spawning inside the world or not (addresses possible crashes)" ) + +local cvarMaxPerPlayer = CreateConVar( mode.."_max_per_player", oldMaxTotal:GetInt(), cvarFlags, "Defines the max amount of props that a player can have spawned from stacker" ) +local cvarMaxPerStack = CreateConVar( mode.."_max_per_stack", oldMaxCount:GetInt(), cvarFlags, "Defines the max amount of props that can be stacked at a time" ) +local cvarDelay = CreateConVar( mode.."_delay", oldDelay:GetFloat(), cvarFlags, "Determines the amount of time that must pass before a player can use stacker again" ) +local cvarMaxOffX = CreateConVar( mode.."_max_offsetx", oldMaxOffX:GetFloat(), cvarFlags, "Defines the max distance on the x plane that stacked props can be offset (for individual control)" ) +local cvarMaxOffY = CreateConVar( mode.."_max_offsety", oldMaxOffY:GetFloat(), cvarFlags, "Defines the max distance on the y plane that stacked props can be offset (for individual control)" ) +local cvarMaxOffZ = CreateConVar( mode.."_max_offsetz", oldMaxOffZ:GetFloat(), cvarFlags, "Defines the max distance on the z plane that stacked props can be offset (for individual control)" ) +local cvarFreeze = CreateConVar( mode.."_force_freeze", oldFreeze:GetInt(), cvarFlagsNotif, "Determines whether props should be forced to spawn frozen or not" ) +local cvarWeld = CreateConVar( mode.."_force_weld", oldWeld:GetInt(), cvarFlagsNotif, "Determines whether props should be forced to spawn welded or not" ) +local cvarNoCollide = CreateConVar( mode.."_force_nocollide", oldNoCollide:GetInt(), cvarFlagsNotif, "Determines whether props should be forced to spawn nocollided or not" ) +local cvarNoCollideAll = CreateConVar( mode.."_force_nocollide_all", 0, cvarFlags, "(EXPERIMENTAL, DISABLED) Determines whether props should be nocollide with everything except players, vehicles, and npcs" ) +local cvarStayInWorld = CreateConVar( mode.."_force_stayinworld", oldStayInWorld:GetInt(), cvarFlagsNotif, "Determines whether props should be restricted to spawning inside the world or not (addresses possible crashes)" ) + +--[[-------------------------------------------------------------------------- +-- Console Commands +--------------------------------------------------------------------------]]-- + +if ( CLIENT ) then + + concommand.Add( mode.."_reset_offsets", function( ply, cmd, args ) + -- reset all of the offset values to 0 + RunConsoleCommand( mode.."_offsetx", "0.00" ) + RunConsoleCommand( mode.."_offsety", "0.00" ) + RunConsoleCommand( mode.."_offsetz", "0.00" ) + end ) + + concommand.Add( mode.."_reset_angles", function( ply, cmd, args ) + -- reset all of the angle values to 0 + RunConsoleCommand( mode.."_pitch", "0.00" ) + RunConsoleCommand( mode.."_yaw", "0.00" ) + RunConsoleCommand( mode.."_roll", "0.00" ) + end ) + + concommand.Add( mode.."_reset_admin", function( ply, cmd, args ) + for cmd, val in pairs( improvedstacker.SETTINGS_DEFAULT ) do + RunConsoleCommand( cmd, val ) + end + end ) + +elseif ( SERVER ) then + + local function validateCommand( ply, cmd, arg ) + -- run our hook to see if the server is manually allowing/blocking this player from changing the cvar + -- true: allow + -- false: block + -- nil (default): fallback to a ply:IsAdmin() check + local result, reason = hook.Run( "StackerConVar", ply, cmd, arg ) + + -- if a player ran the command and the server didn't explicitly allow them to change the cvar + if ( IsValid( ply ) and result ~= true ) then + -- if the server blocked the change, send the player an error + if ( result == false ) then + ply:PrintMessage( HUD_PRINTTALK, L(prefix.."error_blocked_by_server", localify.GetLocale( ply )) .. (isstring(reason) and ": " .. reason or "") ) + return false + end + -- if the server didn't give a response, fallback to a ply:IsAdmin() check + if ( result == nil and not ply:IsAdmin() ) then + ply:PrintMessage( HUD_PRINTTALK, L(prefix.."error_not_admin", localify.GetLocale( ply )) .. ": " .. cmd ) + return false + end + end + + -- lastly, ensure the argument is a valid number before returning true + if ( not tonumber( arg ) ) then + ply:PrintMessage( HUD_PRINTTALK, L(prefix.."error_invalid_argument", localify.GetLocale( ply )) ) + return false + end + + return true + end + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_max_per_player", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_max_per_player", args[1] ) ) then return false end + RunConsoleCommand( mode.."_max_per_player", args[1] ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_max_per_stack", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_max_per_stack", args[1] ) ) then return false end + RunConsoleCommand( mode.."_max_per_stack", args[1] ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_max_offset", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_max_offset", args[1] ) ) then return false end + RunConsoleCommand( mode.."_max_offsetx", args[1] ) + RunConsoleCommand( mode.."_max_offsety", args[1] ) + RunConsoleCommand( mode.."_max_offsetz", args[1] ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_max_offsetx", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_max_offsetx", args[1] ) ) then return false end + RunConsoleCommand( mode.."_max_offsetx", args[1] ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_max_offsety", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_max_offsety", args[1] ) ) then return false end + RunConsoleCommand( mode.."_max_offsety", args[1] ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_max_offsetz", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_max_offsetz", args[1] ) ) then return false end + RunConsoleCommand( mode.."_max_offsetz", args[1] ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_force_stayinworld", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_force_stayinworld", args[1] ) ) then return false end + RunConsoleCommand( mode.."_force_stayinworld", tobool( args[1] ) and "1" or "0" ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_force_freeze", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_force_freeze", args[1] ) ) then return false end + RunConsoleCommand( mode.."_force_freeze", tobool( args[1] ) and "1" or "0" ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_force_weld", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_force_weld", args[1] ) ) then return false end + RunConsoleCommand( mode.."_force_weld", tobool( args[1] ) and "1" or "0" ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_force_nocollide", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_force_nocollide", args[1] ) ) then return false end + RunConsoleCommand( mode.."_force_nocollide", tobool( args[1] ) and "1" or "0" ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_force_nocollide_all", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_force_nocollide_all", args[1] ) ) then return false end + RunConsoleCommand( mode.."_force_nocollide_all", tobool( args[1] ) and "1" or "0" ) + end ) + --[[-------------------------------------------------------------]]-- + concommand.Add( mode.."_set_delay", function( ply, cmd, args ) + if ( not validateCommand( ply, mode.."_set_delay", args[1] ) ) then return false end + RunConsoleCommand( mode.."_delay", args[1] ) + end ) + + + util.AddNetworkString( mode.."_error" ) + + --[[-------------------------------------------------------------------------- + -- TOOL:SendError( str ) + -- Convenience function for sending an error to the tool owner. + --]]-- + function TOOL:SendError( str ) + net.Start( mode.."_error" ) + net.WriteString('warning') + net.WriteString( str ) + net.Send( self:GetOwner() ) + end + +end + +--[[-------------------------------------------------------------------------- +-- Convenience Functions +--------------------------------------------------------------------------]]-- + +--[[-------------------------------------------------------------------------- +-- TOOL:GetMaxPerPlayer() and TOOL:GetNumberPlayerEnts() +-- +-- The total number of props a player has spawned from the Stacker tool is recorded +-- on them via ply.TotalStackerEnts. When a player removes a prop that has been spawned +-- from Stacker, the total count is decreased by 1. +-- +-- In combination with the stacker_max_per_player cvar, this function can prevent players +-- from crashing the server by stacking dozens of welded props and unfreezing them. +-- +-- By default, the number of stacker props is -1 (infinite). This is done to not interfere +-- with servers that don't want to limit the number of Stacker props a player can spawn directly. +-- They may still hit cvars like sbox_maxprops before ever hitting stacker_max_per_player. +-- +-- As an example case, if players are crashing your servers by spawning 50 welded chairs +-- and unfreezing them all at once, you can set stacker_max_per_player to 30 so that at any +-- given time they can only have 30 props created by Stacker. Trying to stack any more props +-- would give the player an error message. +--]]-- +function TOOL:GetMaxPerPlayer() return cvarMaxPerPlayer:GetInt() end +function TOOL:GetNumberPlayerEnts() return improvedstacker.GetEntCount( self:GetOwner(), 0 ) end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetStackSize() +-- Gets the amount of props that the client wants to stack at once. +--]]-- +function TOOL:GetStackSize() return self:GetClientNumber( "count" ) end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetMaxPerStack() +-- Gets the maximum amount of props that can be stacked at a time. +--]]-- +function TOOL:GetMaxPerStack() return cvarMaxPerStack:GetInt() end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetDirection() +-- Gets the direction to stack the props. +--]]-- +function TOOL:GetDirection() + local direction = self:GetClientNumber( "direction" ) + return improvedstacker.Directions[ direction ] and direction or improvedstacker.DIRECTION_FRONT +end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetStackerMode() +-- Gets the stacker mode (1 = MODE_WORLD, 2 = MODE_PROP). +--]]-- +function TOOL:GetStackerMode() + local stackMode = self:GetClientNumber( "mode" ) + return improvedstacker.Modes[ stackMode ] and stackMode or improvedstacker.MODE_PROP +end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetOffsetX(), TOOL:GetOffsetY(), TOOL:GetOffsetZ(), TOOL:GetOffsetVector() +-- Gets the distance to offset the position of the stacked props. +-- These values are clamped to prevent server crashes from players +-- using very high offset values. +--]]-- +function TOOL:GetOffsetX() return math.Clamp( self:GetClientNumber( "offsetx" ), -cvarMaxOffX:GetFloat(), cvarMaxOffX:GetFloat() ) end +function TOOL:GetOffsetY() return math.Clamp( self:GetClientNumber( "offsety" ), -cvarMaxOffY:GetFloat(), cvarMaxOffY:GetFloat() ) end +function TOOL:GetOffsetZ() return math.Clamp( self:GetClientNumber( "offsetz" ), -cvarMaxOffZ:GetFloat(), cvarMaxOffZ:GetFloat() ) end +function TOOL:GetOffsetVector() return Vector( self:GetOffsetX(), self:GetOffsetY(), self:GetOffsetZ() ) end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetRotateP(), TOOL:GetRotateY(), TOOL:GetRotateR(), TOOL:GetRotationAngle() +-- Gets the value to rotate the angle of the stacked props. +-- These values are clamped to prevent server crashes from players +-- using very high rotation values. +--]]-- +function TOOL:GetRotateP() return math.Clamp( self:GetClientNumber( "pitch" ), -MAX_ANGLE, MAX_ANGLE ) end +function TOOL:GetRotateY() return math.Clamp( self:GetClientNumber( "yaw" ), -MAX_ANGLE, MAX_ANGLE ) end +function TOOL:GetRotateR() return math.Clamp( self:GetClientNumber( "roll" ), -MAX_ANGLE, MAX_ANGLE ) end +function TOOL:GetRotationAngle() return Angle( self:GetRotateP(), self:GetRotateY(), self:GetRotateR() ) end + +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldFreeze() +-- Returns true if the stacked props should be spawned frozen. +--]]-- +function TOOL:ShouldApplyFreeze() return self:GetClientNumber( "freeze" ) == 1 end +function TOOL:ShouldForceFreeze() return cvarFreeze:GetBool() end +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldWeld() +-- Returns true if the stacked props should be welded together. +--]]-- +-- function TOOL:ShouldApplyWeld() return self:GetClientNumber( "weld" ) == 1 end +-- function TOOL:ShouldForceWeld() return cvarWeld:GetBool() end +function TOOL:ShouldApplyWeld() return false end +function TOOL:ShouldForceWeld() return false end +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldNoCollide() +-- Returns true if the stacked props should be nocollided with each other. +--]]-- +function TOOL:ShouldApplyNoCollide() return self:GetClientNumber( "nocollide" ) == 1 end +function TOOL:ShouldForceNoCollide() return cvarNoCollide:GetBool() end +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldStackRelative() +-- Returns true if the stacked props should be stacked relative to the new rotation. +-- Using this setting will allow you to create curved structures out of props. +--]]-- +function TOOL:ShouldStackRelative() return self:GetClientNumber( "relative" ) == 1 end +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldGhostAll() +-- Returns true if the stacked props should all be ghosted or if only the +-- first stacked prop should be ghosted. +--]]-- +function TOOL:ShouldGhostAll() return self:GetClientNumber( "ghostall" ) == 1 end + +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldAddHalos(), TOOL:GetHaloR(), TOOL:GetHaloG(), TOOL:GetHaloB() TOOL:GetHaloA() TOOL:GetHaloColor() +-- Returns true if the stacked props should have halos drawn on them for added visibility. +-- Gets the RGBA values of the halo color. +--]]-- +function TOOL:ShouldAddHalos() return self:GetClientNumber( "draw_halos" ) == 1 end +function TOOL:GetHaloR() return math.Clamp( self:GetClientNumber( "halo_r" ), 0, 255 ) end +function TOOL:GetHaloG() return math.Clamp( self:GetClientNumber( "halo_g" ), 0, 255 ) end +function TOOL:GetHaloB() return math.Clamp( self:GetClientNumber( "halo_b" ), 0, 255 ) end +function TOOL:GetHaloA() return math.Clamp( self:GetClientNumber( "halo_a" ), 0, 255 ) end +function TOOL:GetHaloColor() return Color( self:GetHaloR(), self:GetHaloG(), self:GetHaloB(), self:GetHaloA() ) end + +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldApplyMaterial() +-- Returns true if the stacked props should have the original prop's material applied. +--]]-- +function TOOL:ShouldApplyMaterial() return self:GetClientNumber( "material" ) == 1 end + +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldApplyColor() +-- Returns true if the stacked props should have the original prop's color applied. +--]]-- +function TOOL:ShouldApplyColor() return self:GetClientNumber( "color" ) == 1 end + +--[[-------------------------------------------------------------------------- +-- TOOL:ShouldApplyPhysicalProperties() +-- Returns true if the stacked props should have the original prop's physicsl properties +-- applied, including gravity, physics material, and weight. +--]]-- +function TOOL:ShouldApplyPhysicalProperties() return self:GetClientNumber( "physprop" ) == 1 end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetDelay() +-- Returns the time in seconds that must pass before a player can use stacker again. +-- For example, if stacker_delay is set to 3, a player must wait 3 seconds in between each +-- use of stacker's left click. A delay of <= 0 means stacks can be created instantly. +--]]-- +function TOOL:GetDelay() return cvarDelay:GetFloat() end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetOpacity() +-- Returns the alpha value (opacity) of the ghosted props seen on the client. +-- Should be between 0 (invisible) and 255 (fully visible). +--]]-- +function TOOL:GetOpacity() return self:GetClientNumber( "opacity" ) end + +--[[-------------------------------------------------------------------------- +-- TOOL:GetUseShiftKey() +-- Returns true if the client has enabled the alternate use of SHIFT in combination +-- with left and right clicking. If enable, holding SHIFT and pressing LMB/RMB will +-- have the same effect as holding E and pressing LMB/RMB. +--]]-- +function TOOL:GetUseShiftKey() return self:GetClientNumber( "use_shift_key" ) == 1 end +--[[-------------------------------------------------------------------------- +-- Tool Functions +--------------------------------------------------------------------------]]-- + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:LeftClick( table, boolean = nil ) +-- +-- Attempts to create a stack of props relative to the entity being left clicked. +--]]-- +function TOOL:LeftClick( tr, isRightClick ) + local ply = self:GetOwner() + + -- check if the player is holding E or SHIFT (as long as they've enabled it) + if ( ply:KeyDown( IN_USE ) or (self:GetUseShiftKey() and ply:KeyDown( IN_SPEED )) ) then + if ( CLIENT ) then return false end + -- increase their stack count by 1 (until it hits the stack max) + local newCount = self:GetStackSize() >= self:GetMaxPerStack() and self:GetMaxPerStack() or self:GetStackSize() + 1 + ply:ConCommand( mode.."_count "..newCount ) + return false + end + + if ( not IsValid( tr.Entity ) or tr.Entity:GetClass() ~= "prop_physics" ) then return false end + if ( CLIENT ) then return true end + + -- otherwise, stack 1 if right-clicking or get the client's stack size value + local count = (isRightClick and 1) or self:GetStackSize() + -- check if the server wants to control how many props the player can use in the stack + local maxCount = hook.Run( "StackerMaxPerStack", ply, count, isRightClick ) or self:GetMaxPerStack() + + -- check if the player's stack size is higher than the server's max allowed size (but only if the server didn't explictly override it) + if ( maxCount >= 0 ) then + if ( count > maxCount ) then self:SendError( L(prefix.."error_max_per_stack", localify.GetLocale( self:GetOwner() )) .. maxCount ) end + count = math.Clamp( count, 0, maxCount ) + end + + -- get the player's last stacker usage time, defaulting to 0 if it hasn't been set + local lastStackTime = improvedstacker.GetLastStackTime( ply, 0 ) + + -- retrieve the time delay between stacker usage + -- we call StackerDelay to let external mods to set their own delays (less than or equal to 0 means no delay) + -- delay time is in seconds (e.g. 0.1 is a tenth of a second) + local delay = hook.Run( "StackerDelay", ply, lastStackTime ) or self:GetDelay() + + -- check if the player is trying to use stacker too quickly + if ( lastStackTime + delay > CurTime() ) then self:SendError( L(prefix.."error_too_quick", localify.GetLocale( self:GetOwner() )) ) return false end + improvedstacker.SetLastStackTime( ply, CurTime() ) + + local stackDirection = self:GetDirection() + local stackMode = self:GetStackerMode() + local stackOffset = self:GetOffsetVector() + local stackRotation = self:GetRotationAngle() + local stackRelative = self:ShouldStackRelative() + + -- determines whether the stacked props are allowed to be positioned outside of the world or not + local stayInWorld = cvarStayInWorld:GetBool() + + -- store the properties of the original prop so we can apply them to the stacked props + local ent = tr.Entity + local entPos = ent:GetPos() + local entAng = ent:GetAngles() + local entMod = ent:GetModel() + local entSkin = ent:GetSkin() + local entMat = ent:GetMaterial() + local physMat = ent:GetPhysicsObject():GetMaterial() + local physGrav = ent:GetPhysicsObject():IsGravityEnabled() + + -- setup a table to hold the original prop's color data so that we can apply it to the stacked props + local colorData = { + Color = ent:GetColor(), + RenderMode = ent:GetRenderMode(), + RenderFX = ent:GetRenderFX() + } + + local newEnt + local newEnts = { ent } + local lastEnt = ent + + local direction, offset + -- we only need to calculate the distance once based on the direction the user selected + local distance = improvedstacker.GetDistance( stackMode, stackDirection, ent ) + + -- setup a new undo block so the player can undo the whole stack at once + undo.Create( mode ) + + -- check if the server wants to control how many stacker entities this player can create + local maxPerPlayer = hook.Run( "StackerMaxPerPlayer", ply, self:GetNumberPlayerEnts() ) or self:GetMaxPerPlayer() + + -- loop for every prop to create in the stack and allow external addons to dictate control over the new stacked entities + for i = 1, count do + + -- check if the player has too many active stacker props spawned out already + local stackerEntsSpawned = self:GetNumberPlayerEnts() + if ( maxPerPlayer >= 0 and stackerEntsSpawned >= maxPerPlayer ) then self:SendError( ("%s (%s)"):format(L(prefix.."error_max_per_player", localify.GetLocale( self:GetOwner() )), maxPerPlayer) ) break end + -- check if the player has exceeded the sbox_maxprops cvar + if ( not self:GetSWEP():CheckLimit( "props" ) ) then break end + -- check if external admin mods are blocking this entity + if ( hook.Run( "PlayerSpawnProp", ply, entMod ) == false ) then break end + + -- if we're positioning the first entity in the stack (regardless of relative to PROP or WORLD), or + -- if we're stacking relative to PROP and on the previous rotation, update the new direction and offset + if ( i == 1 or ( stackMode == improvedstacker.MODE_PROP and stackRelative ) ) then + direction = improvedstacker.GetDirection( stackMode, stackDirection, entAng ) + offset = improvedstacker.GetOffset( stackMode, stackDirection, entAng, stackOffset ) + end + + -- calculate the next stacked entity's position + entPos = entPos + (direction * distance) + offset + -- rotate the next stacked entity's angle by the client's rotation values + improvedstacker.RotateAngle( stackMode, stackDirection, entAng, stackRotation ) + + + -- check if the stacked props would be spawned outside of the world + if ( stayInWorld and not util.IsInWorld( entPos ) ) then self:SendError( L(prefix.."error_not_in_world", localify.GetLocale( self:GetOwner() )) ) break end + + -- create the new stacked entity + newEnt = ents.Create( "prop_physics" ) + newEnt:SetModel( entMod ) + newEnt:SetPos( entPos ) + newEnt:SetAngles( entAng ) + newEnt:SetSkin( entSkin ) + newEnt:Spawn() + + + -- this hook is for external prop protections and anti-spam addons. + -- it is called before undo, ply:AddCount, and ply:AddCleanup to allow developers to + -- remove or mark this entity so that those same functions (if overridden) can + -- detect that the entity came from Stacker + if ( not IsValid( newEnt ) or hook.Run( "StackerEntity", newEnt, ply ) ~= nil ) then break end + if ( not IsValid( newEnt ) or hook.Run( "PlayerSpawnedProp", ply, entMod, newEnt ) ~= nil ) then break end + + -- disabling this for now due to problems with ShouldCollide + --improvedstacker.MarkEntity( self:GetOwner(), newEnt ) + + -- increase the total number of active stacker props spawned by the player by 1 + improvedstacker.IncrementEntCount( ply ) + + -- decrement the total number of active stacker props spawned by the player by 1 + -- when the prop gets removed in any way + newEnt:CallOnRemove( "UpdateStackerTotal", function( ent, ply ) + -- if the player is no longer connected, there is nothing to do + if ( not IsValid( ply ) ) then return end + improvedstacker.DecrementEntCount( ply ) + end, ply ) + + self:ApplyMaterial( newEnt, entMat ) + self:ApplyColor( newEnt, colorData ) + self:ApplyFreeze( ply, newEnt ) + + -- attempt to nocollide the new entity with the last, or break out of the loop if CBaseEntityList::AddNonNetworkableEntity fails + if ( not self:ApplyNoCollide( lastEnt, newEnt ) ) then + newEnt:Remove() + break + end + + -- attempt to weld the new entity with the last, or break out of the loop if CBaseEntityList::AddNonNetworkableEntity fails + if ( not self:ApplyWeld( lastEnt, newEnt ) ) then + newEnt:Remove() + break + end + + self:ApplyPhysicalProperties( ent, newEnt, tr.PhysicsBone, { GravityToggle = physGrav, Material = physMat } ) + + lastEnt = newEnt + table.insert( newEnts, newEnt ) + + undo.AddEntity( newEnt ) + ply:AddCleanup( "props", newEnt ) + end + + newEnts = nil + + undo.SetPlayer( ply ) + undo.Finish() + + -- disabling this for now due to problems with ShouldCollide + --improvedstacker.MarkEntity( self:GetOwner(), ent ) + + return true +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:RightClick( trace ) +-- +-- Performs a LeftClick operation but only creates a single stacked entity. +-- Alternatively, if the player is holding down their USE key, this will +-- decrease their stack count by 1. +--]]-- +function TOOL:RightClick( tr ) + local ply = self:GetOwner() + + -- check if the player is holding E or SHIFT (as long as they've enabled it) + if ( ply:KeyDown( IN_USE ) or (self:GetUseShiftKey() and ply:KeyDown( IN_SPEED )) ) then + if ( CLIENT ) then return false end + -- decrease the player's stack count by 1 (until a minimum of 1) + local count = self:GetStackSize() + local newCount = (count <= 1 and 1) or count - 1 + ply:ConCommand( mode.."_count " .. newCount ) + return false + else + -- create a single entity in the stack + return self:LeftClick( tr, true ) + end + +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:Reload() +-- +-- Switches the client's stack direction. +--]]-- +function TOOL:Reload() + if ( CLIENT ) then return false end + + local ply = self:GetOwner() + local direction = self:GetDirection() + + -- if they were at the last numerical direction (6), wrap around to the first (1) + if ( direction == improvedstacker.DIRECTION_DOWN ) then + direction = improvedstacker.DIRECTION_FRONT + -- otherwise just increment to the next direction + else + direction = direction + 1 + end + + -- make the player update their client direction setting + ply:ConCommand( mode.."_direction " .. direction ) + + return false +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:ApplyMaterial( entity, string ) +-- +-- Applies the original entity's material onto the stacked props. +--]]-- +function TOOL:ApplyMaterial( ent, material ) + if ( not self:ShouldApplyMaterial() ) then ent:SetMaterial( "" ) return end + + -- From: gamemodes/sandbox/entities/weapons/gmod_tool/stools/material.lua + -- "Make sure this is in the 'allowed' list in multiplayer - to stop people using exploits" + if ( not game.SinglePlayer() and not list.Contains( "OverrideMaterials", material ) and material ~= "" ) then return end + + ent:SetMaterial( material ) + duplicator.StoreEntityModifier( ent, "material", { MaterialOverride = material } ) +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:ApplyColor( entity, color ) +-- +-- Applies the original entity's color onto the stacked props. +--]]-- +function TOOL:ApplyColor( ent, data ) + if ( not self:ShouldApplyColor() ) then return end + + ent:SetColor( data.Color ) + ent:SetRenderMode( data.RenderMode ) + ent:SetRenderFX( data.RenderFX ) + + duplicator.StoreEntityModifier( ent, "colour", table.Copy( data ) ) +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:ApplyFreeze( player, entity ) +-- +-- Attempts to freeze the stacked props in place. +--]]-- +function TOOL:ApplyFreeze( ply, ent ) + if ( self:ShouldForceFreeze() or self:ShouldApplyFreeze() ) then + ent:GetPhysicsObject():EnableMotion( false ) + else + ent:GetPhysicsObject():Wake() + end +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:ApplyWeld( entity, entity ) +-- +-- Attempts to weld the new entity to the last entity. +--]]-- +function TOOL:ApplyWeld( lastEnt, newEnt ) + if ( not self:ShouldForceWeld() and not self:ShouldApplyWeld() ) then return true end + + local forceLimit = 0 + local isNocollided = self:ShouldForceNoCollide() or self:ShouldApplyNoCollide() + local deleteOnBreak = false + + local ok, err = pcall( constraint.Weld, lastEnt, newEnt, 0, 0, forceLimit, isNocollided, deleteOnBreak ) + + if ( not ok ) then + print( mode .. ": " .. L(prefix.."error_max_constraints") .." (error: " .. err .. ")" ) + self:SendError( mode .. ": " .. L(prefix.."error_max_constraints", localify.GetLocale( self:GetOwner() )) ) + end + + return ok +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:ApplyNoCollide( entity, entity ) +-- +-- Attempts to nocollide the new entity to the last entity. +--]]-- +function TOOL:ApplyNoCollide( lastEnt, newEnt ) + if ( not self:ShouldForceNoCollide() and not self:ShouldApplyNoCollide() ) then return true end + -- we can skip this function if the client is trying to weld -and- nocollide, because + -- constraint.Weld already has a nocollide parameter + if ( self:ShouldForceWeld() or self:ShouldApplyWeld() ) then return true end + + local ok, err = pcall( constraint.NoCollide, lastEnt, newEnt, 0, 0 ) + + if ( not ok ) then + print( mode .. ": " .. L(prefix.."error_max_constraints") .." (error: " .. err .. ")" ) + self:SendError( mode .. ": " .. L(prefix.."error_max_constraints", localify.GetLocale( self:GetOwner() )) ) + end + + return ok +end + +--[[-------------------------------------------------------------------------- +-- +-- TOOL:ApplyPhysicalProperties( entity, entity, number, table ) +-- +-- Attempts to apply the original entity's Gravity/Physics Material properties +-- and weight onto the stacked propa. +-- +--]]-- +function TOOL:ApplyPhysicalProperties( original, newEnt, boneID, properties ) + if ( not self:ShouldApplyPhysicalProperties() ) then return end + + if ( boneID ) then construct.SetPhysProp( nil, newEnt, boneID, nil, properties ) end + newEnt:GetPhysicsObject():SetMass( original:GetPhysicsObject():GetMass() ) +end + +if ( CLIENT ) then + + -- get the cvars if they're valid (e.g., editing and auto-refreshing this file). + -- otherwise they won't be valid yet when first ran and we have to wait until + -- TOOL:Init() gets called (below) to set them up + local cvarTool = GetConVar( "gmod_toolmode" ) + local cvarCount = GetConVar( mode.."_count" ) + local cvarMode = GetConVar( mode.."_mode" ) + local cvarDirection = GetConVar( mode.."_direction" ) + local cvarOffsetX = GetConVar( mode.."_offsetx" ) + local cvarOffsetY = GetConVar( mode.."_offsety" ) + local cvarOffsetZ = GetConVar( mode.."_offsetz" ) + local cvarPitch = GetConVar( mode.."_pitch" ) + local cvarYaw = GetConVar( mode.."_yaw" ) + local cvarRoll = GetConVar( mode.."_roll" ) + local cvarRelative = GetConVar( mode.."_relative" ) + local cvarMaterial = GetConVar( mode.."_material" ) + local cvarColor = GetConVar( mode.."_color" ) + local cvarGhostAll = GetConVar( mode.."_ghostall" ) + local cvarOpacity = GetConVar( mode.."_opacity" ) + local cvarHalo = GetConVar( mode.."_draw_halos" ) + local cvarHaloR = GetConVar( mode.."_halo_r" ) + local cvarHaloG = GetConVar( mode.."_halo_g" ) + local cvarHaloB = GetConVar( mode.."_halo_b" ) + local cvarHaloA = GetConVar( mode.."_halo_a" ) + local cvarHalo = GetConVar( mode.."_draw_halos" ) + local cvarAxis = GetConVar( mode.."_draw_axis" ) + local cvarAxisLbl = GetConVar( mode.."_axis_labels" ) + local cvarAxisAng = GetConVar( mode.."_axis_angles" ) + + -- offsets for drawing the axis arrows + local o1 = Vector( 0, 0, 0.05 ) + local o2 = Vector( 0, 0, -0.05 ) + local o3 = Vector( 0.05, 0, 0 ) + local o4 = Vector( -0.05, 0, 0 ) + local ao = 2.5 + + -- colors for the axis arrows + local RED = Color( 255, 50, 50 ) + local GREEN = Color( 0, 255, 0 ) + local BLUE = Color( 50, 150, 255 ) + local BLACK = Color( 0, 0, 0 ) + + surface.CreateFont( mode.."_direction", { + font = "Arial", + size = 24, + weight = 700, + antialias = true + }) + + + -- we're creating a bunch of local functions here using the cvars above so that we don't have to + -- rely on the TOOL object (which can be problematic when trying to use it inside a hook). + -- these should be pretty much identical to the TOOL functions created near the top of this file + local function getStackSize() return cvarCount:GetInt() end + local function getMaxPerStack() return cvarMaxPerStack:GetInt() end + local function getStackerMode() return cvarMode:GetInt() end + local function getDirection() return cvarDirection:GetInt() end + local function getOpacity() return cvarOpacity:GetInt() end + local function shouldGhostAll() return cvarGhostAll:GetBool() end + local function shouldStackRelative() return cvarRelative:GetBool() end + local function shouldApplyMaterial() return cvarMaterial:GetBool() end + local function shouldApplyColor() return cvarColor:GetBool() end + local function shouldAddHalos() return cvarHalo:GetBool() end + + local function getOffsetVector() + return Vector( math.Clamp( cvarOffsetX:GetFloat(), -cvarMaxOffX:GetFloat(), cvarMaxOffX:GetFloat() ), + math.Clamp( cvarOffsetY:GetFloat(), -cvarMaxOffY:GetFloat(), cvarMaxOffY:GetFloat() ), + math.Clamp( cvarOffsetZ:GetFloat(), -cvarMaxOffZ:GetFloat(), cvarMaxOffZ:GetFloat() ) ) + end + + local function getRotationAngle() + return Angle( math.Clamp( cvarPitch:GetFloat(), -MAX_ANGLE, MAX_ANGLE ), + math.Clamp( cvarYaw:GetFloat(), -MAX_ANGLE, MAX_ANGLE ), + math.Clamp( cvarRoll:GetFloat(), -MAX_ANGLE, MAX_ANGLE ) ) + end + + local function getHaloColor() + return Color( cvarHaloR:GetInt(), + cvarHaloG:GetInt(), + cvarHaloB:GetInt(), + cvarHaloA:GetInt() ) + end + + --[[-------------------------------------------------------------------------- + -- + -- TOOL:Init() + -- + --]]-- + function TOOL:Init() + -- now the convars are truly valid, so reassign the upvalues + cvarTool = GetConVar( "gmod_toolmode" ) + cvarCount = GetConVar( mode.."_count" ) + cvarMode = GetConVar( mode.."_mode" ) + cvarDirection = GetConVar( mode.."_direction" ) + cvarOffsetX = GetConVar( mode.."_offsetx" ) + cvarOffsetY = GetConVar( mode.."_offsety" ) + cvarOffsetZ = GetConVar( mode.."_offsetz" ) + cvarPitch = GetConVar( mode.."_pitch" ) + cvarYaw = GetConVar( mode.."_yaw" ) + cvarRoll = GetConVar( mode.."_roll" ) + cvarRelative = GetConVar( mode.."_relative" ) + cvarMaterial = GetConVar( mode.."_material" ) + cvarColor = GetConVar( mode.."_color" ) + cvarGhostAll = GetConVar( mode.."_ghostall" ) + cvarOpacity = GetConVar( mode.."_opacity" ) + cvarHalo = GetConVar( mode.."_draw_halos" ) + cvarHaloR = GetConVar( mode.."_halo_r" ) + cvarHaloG = GetConVar( mode.."_halo_g" ) + cvarHaloB = GetConVar( mode.."_halo_b" ) + cvarHaloA = GetConVar( mode.."_halo_a" ) + cvarHalo = GetConVar( mode.."_draw_halos" ) + cvarAxis = GetConVar( mode.."_draw_axis" ) + cvarAxisLbl = GetConVar( mode.."_axis_labels" ) + cvarAxisAng = GetConVar( mode.."_axis_angles" ) + end + + --[[-------------------------------------------------------------------------- + -- + -- createGhostStack( entity, vector, angle ) + -- + -- Attempts to create a stack of ghosted props on the prop the player is currently + -- looking at before they actually left click to create the stack. This acts + -- as a visual aid for the player so they can see the results without actually creating + -- the entities yet (if in multiplayer). + --]]-- + local function createGhostStack( ent ) + if ( improvedstacker.GetGhosts() ) then improvedstacker.ReleaseGhosts() end + + -- truncate the stack size to the maximum allowed by the server + local count = getStackSize() + local maxCount = getMaxPerStack() + if ( not shouldGhostAll() and count ~= 0 ) then count = 1 end + if ( maxCount >= 0 and count > maxCount ) then count = maxCount end + + local entMod = ent:GetModel() + local entSkin = ent:GetSkin() + + local ghosts = {} + local ghost + + -- loop for the total stack size and create a new ghost prop + for i = 1, count do + ghost = ClientsideModel( entMod ) + + if ( not IsValid( ghost ) ) then continue end + + ghost:SetModel( entMod ) + ghost:SetSkin( entSkin ) + ghost:Spawn() + + ghost:SetRenderMode( RENDERMODE_TRANSALPHA ) + + table.insert( ghosts, ghost ) + end + + -- store the ghost array for later use + improvedstacker.SetGhosts( ghosts ) + + return true + end + + --[[-------------------------------------------------------------------------- + -- + -- validateGhostStack() + -- + -- Attempts to validate the status of the ghosted props in the stack. + -- True: all good, ready to update + -- False: something is invalid or missing, clear it + --]]-- + local function validateGhostStack() + -- check if the array of ghosts is valid + local ghosts = improvedstacker.GetGhosts() + if ( not ghosts ) then return false end + + -- check if all the ghost entities are valid + for i = 1, #ghosts do + if ( not IsValid( ghosts[ i ] ) ) then return false end + end + + -- clamp the client's ghost stack to the server's maximum allowed size + local count = getStackSize() + local maxCount = getMaxPerStack() + if ( maxCount >= 0 and count > maxCount ) then count = maxCount end + + -- check if the number of ghosts in the stack matches the client's setting + if ( #ghosts ~= count and shouldGhostAll() ) then return false + -- number of ghosts matches client's setting, so check if we should only be ghosting one + elseif ( #ghosts ~= 1 and not shouldGhostAll() ) then return false end + + return true + end + + --[[-------------------------------------------------------------------------- + -- + -- updateGhostStack( entity ) + -- + -- Attempts to update the positions and angles of all ghosted props in the stack. + --]]-- + local function updateGhostStack( ent ) + local stackMode = getStackerMode() + local stackDirection = getDirection() + local stackOffset = getOffsetVector() + local stackRotation = getRotationAngle() + local stackRelative = shouldStackRelative() + + local applyMat = shouldApplyMaterial() + local applyCol = shouldApplyColor() + + local lastEnt = ent + local entPos = ent:GetPos() + local entAng = ent:GetAngles() + local entMat = ent:GetMaterial() + local entCol = ent:GetColor() + entCol.a = getOpacity() + + local direction, offset + -- we only need to calculate the distance once based on the direction the user selected + local distance = improvedstacker.GetDistance( stackMode, stackDirection, ent ) + + local ghost + local ghosts = improvedstacker.GetGhosts() + + for i = 1, #ghosts do + -- if we're positioning the first entity in the stack (regardless of relative to PROP or WORLD), or + -- if we're stacking relative to PROP and on the previous rotation, update the new direction and offset + if ( i == 1 or ( stackMode == improvedstacker.MODE_PROP and stackRelative ) ) then + direction = improvedstacker.GetDirection( stackMode, stackDirection, entAng ) + offset = improvedstacker.GetOffset( stackMode, stackDirection, entAng, stackOffset ) + end + + -- calculate the next stacked entity's position + entPos = entPos + (direction * distance) + offset + -- rotate the next stacked entity's angle by the client's rotation values + improvedstacker.RotateAngle( stackMode, stackDirection, entAng, stackRotation ) + + local ghost = ghosts[ i ] + ghost:SetPos( entPos ) + ghost:SetAngles( entAng ) + ghost:SetMaterial( ( applyMat and entMat ) or "" ) + ghost:SetColor( ( applyCol and entCol ) or TRANSPARENT ) + ghost:SetNoDraw( false ) + + lastEnt = ghost + end + end + + + --[[-------------------------------------------------------------------------- + -- + -- Hook :: PreDrawHalos + -- + -- Loads the hook that draws halos on the ghosted entities in the stack. + -- + -- This is the appropriate hook to create halos, NOT TOOL:Think(). The latter + -- will be called way more than it needs to be and causes horrible FPS drops in singleplayer. + --]]-- + hook.Add( "PreDrawHalos", mode.."_predrawhalos", function() + local ply = LocalPlayer() + if ( not IsValid( ply ) ) then return end + + -- check if we're looking at a valid entity + local lookingAt = ply:GetEyeTrace().Entity + if ( not ( IsValid( lookingAt ) and lookingAt:GetClass() == "prop_physics" ) ) then + improvedstacker.ReleaseGhosts() + improvedstacker.SetLookedAt( nil ) + return + end + + -- check if they have the toolgun out and have stacker selected + local wep = ply:GetActiveWeapon() + if ( not ( IsValid( wep ) and wep:GetClass() == "gmod_tool" and cvarTool and cvarTool:GetString() == mode ) ) then + improvedstacker.ReleaseGhosts() + improvedstacker.SetLookedAt( nil ) + return + end + + -- check if the current toolobject is valid before trying to use it -- + -- commenting this out for now since I refactored these TOOL functions + -- into just local functions to ditch the need for the tool object + --[[local tool = wep.GetToolObject and wep:GetToolObject() + if ( not ( tool and tool.GetOwner and IsValid( tool:GetOwner() ) ) ) then + return + end]] + + -- specify the entity that the client is currently looking at for future reference + improvedstacker.SetLookingAt( lookingAt ) + -- get the entity that the client was last (successfully) looking at + local lookedAt = improvedstacker.GetLookedAt() + + -- if we're still looking at the same entity from the previous frame + if ( lookingAt == lookedAt ) then + -- if the ghost stack is still valid (nothing got deleted, etc) + if ( validateGhostStack() ) then + -- reposition the stack to the client's most recent stack settings + updateGhostStack( lookingAt ) + else + -- something is wrong in the stack, so remove the ghost entities + improvedstacker.ReleaseGhosts() + improvedstacker.SetLookedAt( nil ) + return + end + -- we looked at something else since the last frame + else + -- try to initialize a new ghost stack + if ( createGhostStack( lookingAt ) ) then + -- ghost stack was successfully created + improvedstacker.SetLookedAt( lookingAt ) + end + end + + -- check if we want to add halos to the ghost stack + if ( not shouldAddHalos() ) then return end + + -- check if there are any ghosts to add halos to at all + local ghosts = improvedstacker.GetGhosts() + if ( not ghosts or #ghosts <= 0 ) then return end + + halo.Add( ghosts, getHaloColor() ) + end ) + + --[[-------------------------------------------------------------------------- + -- + -- Hook :: PostDrawTranslucentRenderables + -- + -- Draws the 2D x/y/z axis when looking at entities with the stacker tool. + --]]-- + + hook.Add( "PostDrawTranslucentRenderables", mode.."_directions", function( drawingDepth, drawingSky ) + if ( drawingSky ) then return end + local ply = LocalPlayer() + if ( not IsValid( ply ) ) then return end + + -- check if we want to draw the axis at all + if ( not ( cvarAxis and cvarAxis:GetBool() ) ) then return end + + -- check if we're looking at a valid entity + local ent = ply:GetEyeTrace().Entity + if ( not IsValid( ent ) ) then + return + end + + -- check if they have the toolgun out and have stacker selected + local wep = ply:GetActiveWeapon() + if ( not ( IsValid( wep ) and wep:GetClass() == "gmod_tool" and cvarTool and cvarTool:GetString() == mode ) ) then + return + end + + local pos = ent:GetPos() + + local f = ent:GetForward() + local r = ent:GetRight() + local u = ent:GetUp() + + -- draw the front arrow (red) + render.DrawLine( pos, pos + (f*50), RED, false ) + render.DrawLine( pos + (f*50) - f*ao + Vector(0,0,ao), pos + (f*50), RED, false ) + render.DrawLine( pos + (f*50) - f*ao - Vector(0,0,ao), pos + (f*50), RED, false ) + render.DrawLine( pos+o1, pos + (f*50) + o1, RED, false ) + render.DrawLine( pos+o2, pos + (f*50) + o2, RED, false ) + + -- draw the right arrow (green) + render.DrawLine( pos, pos + (r*50), GREEN, false ) + render.DrawLine( pos + (r*50) - r*ao + f*ao, pos + (r*50), GREEN, false ) + render.DrawLine( pos + (r*50) - r*ao - f*ao, pos + (r*50), GREEN, false ) + render.DrawLine( pos+o1, pos + (r*50) + o1, GREEN, false ) + render.DrawLine( pos+o2, pos + (r*50) + o2, GREEN, false ) + + -- draw the upward arrow (blue) + render.DrawLine( pos, pos + (u*50), BLUE, false ) + render.DrawLine( pos + (u*50) - u*ao + r*ao, pos + (u*50), BLUE, false ) + render.DrawLine( pos + (u*50) - u*ao - r*ao, pos + (u*50), BLUE, false ) + render.DrawLine( pos+o3, pos + (u*50) + o3, BLUE, false ) + render.DrawLine( pos+o4, pos + (u*50) + o4, BLUE, false ) + + -- check if we want to draw the axis labels + if ( not ( cvarAxisLbl and cvarAxisAng ) ) then return end + if ( not ( cvarAxisLbl:GetBool() or cvarAxisAng:GetBool() ) ) then return end + + local fs = (pos + f*50 - u*5):ToScreen() + local rs = (pos + r*50 - u*5):ToScreen() + local us = (pos + u*55):ToScreen() + + local ang = ent:GetAngles() + + local front = ("%s%s"):format( cvarAxisLbl:GetBool() and L(prefix.."hud_front").." " or "", cvarAxisAng:GetBool() and "("..ang.x..")" or "" ) + local right = ("%s%s"):format( cvarAxisLbl:GetBool() and L(prefix.."hud_right").." " or "", cvarAxisAng:GetBool() and "("..ang.y..")" or "" ) + local upwrd = ("%s%s"):format( cvarAxisLbl:GetBool() and L(prefix.."hud_up").." " or "", cvarAxisAng:GetBool() and "("..ang.z..")" or "" ) + + cam.Start2D() + draw.SimpleTextOutlined( front, mode.."_direction", fs.x, fs.y, RED, 0, 0, 1, BLACK ) + draw.SimpleTextOutlined( right, mode.."_direction", rs.x, rs.y, GREEN, 0, 0, 1, BLACK ) + draw.SimpleTextOutlined( upwrd, mode.."_direction", us.x, us.y, BLUE, 1, 0, 1, BLACK ) + cam.End2D() + + end ) + +end + +if ( CLIENT ) then + --[[-------------------------------------------------------------------------- + -- + -- TOOL.BuildCPanel( panel ) + -- + -- Builds the control panel menu that can be seen when holding Q and accessing + -- the stacker menu. + --]]-- + local function buildCPanel( cpanel ) + -- quick presets for default settings + local presets = { + Label = "Presets", + MenuButton = 1, + Folder = mode, + Options = { + [L(prefix.."combobox_default")] = { + [mode.."_mode"] = tostring(improvedstacker.MODE_PROP), + [mode.."_direction"] = tostring(improvedstacker.DIRECTION_UP), + [mode.."_count"] = "1", + [mode.."_freeze"] = "1", + -- [mode.."_weld"] = "1", + [mode.."_nocollide"] = "1", + [mode.."_ghostall"] = "1", + [mode.."_material"] = "1", + [mode.."_physprop"] = "1", + [mode.."_color"] = "1", + [mode.."_offsetx"] = "0", + [mode.."_offsety"] = "0", + [mode.."_offsetz"] = "0", + [mode.."_pitch"] = "0", + [mode.."_yaw"] = "0", + [mode.."_roll"] = "0", + [mode.."_relative"] = "1", + [mode.."_draw_halos"] = "0", + [mode.."_halo_r"] = "255", + [mode.."_halo_g"] = "0", + [mode.."_halo_b"] = "0", + [mode.."_halo_a"] = "255", + [mode.."_draw_axis"] = "1", + [mode.."_axis_labels"] = "1", + [mode.."_axis_angles"] = "0", + }, + }, + CVars = { + mode.."_mode", + mode.."_direction", + mode.."_count", + mode.."_freeze", + -- mode.."_weld", + mode.."_nocollide", + mode.."_ghostall", + mode.."_material", + mode.."_physprop", + mode.."_color", + mode.."_offsetx", + mode.."_offsety", + mode.."_offsetz", + mode.."_pitch", + mode.."_yaw", + mode.."_roll", + mode.."_relative", + mode.."_draw_halos", + mode.."_halo_r", + mode.."_halo_g", + mode.."_halo_b", + mode.."_halo_a", + mode.."_draw_axis", + mode.."_axis_labels", + mode.."_axis_angles", + } + } + + local relativeOptions = { + [L(prefix.."combobox_world")] = { [mode.."_mode"] = improvedstacker.MODE_WORLD }, + [L(prefix.."combobox_prop")] = { [mode.."_mode"] = improvedstacker.MODE_PROP }, + } + + local relative = { Label = L(prefix.."label_relative"), MenuButton = "0", Options = relativeOptions } + + local directionOptions = { + ["1 - "..L(prefix.."combobox_direction_front")] = { [mode.."_direction"] = improvedstacker.DIRECTION_FRONT }, + ["2 - "..L(prefix.."combobox_direction_back")] = { [mode.."_direction"] = improvedstacker.DIRECTION_BACK }, + ["3 - "..L(prefix.."combobox_direction_right")] = { [mode.."_direction"] = improvedstacker.DIRECTION_RIGHT }, + ["4 - "..L(prefix.."combobox_direction_left")] = { [mode.."_direction"] = improvedstacker.DIRECTION_LEFT }, + ["5 - "..L(prefix.."combobox_direction_up")] = { [mode.."_direction"] = improvedstacker.DIRECTION_UP }, + ["6 - "..L(prefix.."combobox_direction_down")] = { [mode.."_direction"] = improvedstacker.DIRECTION_DOWN }, + } + + local directions = { Label = L(prefix.."label_direction"), MenuButton = "0", Options = directionOptions } + + -- populate the table of valid languages that clients can switch between + local languageOptions = {} + + for code, tbl in pairs( localify.GetLocalizations() ) do + if ( not L(prefix.."language_"..code, code) ) then continue end + + languageOptions[ L(prefix.."language_"..code, code) ] = { localify_language = code } + end + + local languages = { + Label = L(prefix.."label_language"), + MenuButton = 0, + Options = languageOptions, + } + + -- listen for changes to the localify language and reload the tool's menu to update the localizations + cvars.AddChangeCallback( "localify_language", function( name, old, new ) + local cpanel = controlpanel.Get( mode ) + if ( not IsValid( cpanel ) ) then return end + cpanel:ClearControls() + buildCPanel( cpanel ) + end, "improvedstacker" ) + + cpanel:AddControl( "ComboBox", languages ) + cpanel:ControlHelp( "\n" .. L(prefix.."label_credits") ) + cpanel:AddControl( "Label", { Text = L(prefix.."label_presets") } ) + cpanel:AddControl( "ComboBox", presets ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_freeze"), Command = mode.."_freeze" } ) + -- cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_weld"), Command = mode.."_weld" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_nocollide"), Command = mode.."_nocollide" } ) + cpanel:AddControl( "ComboBox", relative ) + cpanel:AddControl( "ComboBox", directions ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_count"), Min = 1, Max = cvarMaxPerStack:GetInt(), Command = mode.."_count", Description = "How many props to create in each stack" } ) + cpanel:AddControl( "Button", { Label = L(prefix.."label_reset_offsets"), Command = mode.."_reset_offsets" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_x"), Type = "Float", Min = - cvarMaxOffX:GetInt(), Max = cvarMaxOffX:GetInt(), Value = 0, Command = mode.."_offsetx" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_y"), Type = "Float", Min = - cvarMaxOffY:GetInt(), Max = cvarMaxOffY:GetInt(), Value = 0, Command = mode.."_offsety" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_z"), Type = "Float", Min = - cvarMaxOffZ:GetInt(), Max = cvarMaxOffZ:GetInt(), Value = 0, Command = mode.."_offsetz" } ) + cpanel:AddControl( "Button", { Label = L(prefix.."label_reset_angles"), Command = mode.."_reset_angles" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_pitch"), Type = "Float", Min = -MAX_ANGLE, Max = MAX_ANGLE, Value = 0, Command = mode.."_pitch" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_yaw"), Type = "Float", Min = -MAX_ANGLE, Max = MAX_ANGLE, Value = 0, Command = mode.."_yaw" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_roll"), Type = "Float", Min = -MAX_ANGLE, Max = MAX_ANGLE, Value = 0, Command = mode.."_roll" } ) + + cpanel:AddControl( "Button", { Label = L(prefix.."label_"..(showSettings and "hide" or "show").."_settings"), Command = mode.."_show_settings" } ) + + if ( showSettings ) then + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_use_shift_key"), Command = mode.."_use_shift_key", Description = "Toggles the ability to hold SHIFT and click the left and right mouse buttons to change stack size" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_relative"), Command = mode.."_relative", Description = "Stacks each prop relative to the prop right before it. This allows you to create curved stacks" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_material"), Command = mode.."_material", Description = "Applies the material of the original prop to all stacked props" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_color"), Command = mode.."_color", Description = "Applies the color of the original prop to all stacked props" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_physprop"), Command = mode.."_physprop", Description = "Applies the physical properties of the original prop to all stacked props" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_ghost"), Command = mode.."_ghostall", Description = "Creates every ghost prop in the stack instead of just the first ghost prop" } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_axis"), Command = mode.."_draw_axis", } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_axis_labels"), Command = mode.."_axis_labels", } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_axis_angles"), Command = mode.."_axis_angles", } ) + cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_halo"), Command = mode.."_draw_halos", Description = "Gives halos to all of the props in to ghosted stack" } ) + cpanel:AddControl( "Slider", { Label = L(prefix.."label_opacity"), Type = "Integer", Min = 0, Max = 255, Command = mode.."_opacity" } ) + cpanel:AddControl( "Color", { Label = L(prefix.."checkbox_halo_color"), Red = mode.."_halo_r", Green = mode.."_halo_g", Blue = mode.."_halo_b", Alpha = mode.."_halo_a" } ) + end + end + + concommand.Add( mode.."_show_settings", function( ply, cmd, args ) + local cpanel = controlpanel.Get( mode ) + if ( not IsValid( cpanel ) ) then return end + showSettings = not showSettings + cpanel:ClearControls() + buildCPanel( cpanel ) + end ) + + TOOL.BuildCPanel = buildCPanel + + --[[-------------------------------------------------------------------------- + -- + -- PopulateToolMenu + -- + -- Builds the admin settings control panel in the utility menu. This allows server + -- operators to quickly and easily save/change Stacker server settings. + --]]-- + hook.Add( "PopulateToolMenu", mode.."AdminUtilities", function() + spawnmenu.AddToolMenuOption( "Utilities", "Admin", mode.."_utils", L(prefix.."name"), "", "", function( cpanel ) + + -- quick presets for default settings + local presets = { + label = "Presets", + menubutton = 1, + folder = mode.."_admin", + options = { + [L(prefix.."combobox_default")] = improvedstacker.SETTINGS_DEFAULT, + [L(prefix.."combobox_sandbox")] = improvedstacker.SETTINGS_SANDBOX, + [L(prefix.."combobox_darkrp")] = improvedstacker.SETTINGS_DARKRP, + [L(prefix.."combobox_singleplayer")] = improvedstacker.SETTINGS_SINGLEPLAYER, + }, + cvars = { + { CVar = mode.."_max_per_player", CCmd = mode.."_set_max_per_player" }, + { CVar = mode.."_max_per_stack", CCmd = mode.."_set_max_per_stack" }, + { CVar = mode.."_delay", CCmd = mode.."_set_delay" }, + { CVar = mode.."_max_offsetx", CCmd = mode.."_set_max_offsetx" }, + { CVar = mode.."_max_offsety", CCmd = mode.."_set_max_offsety" }, + { CVar = mode.."_max_offsetz", CCmd = mode.."_set_max_offsetz" }, + { CVar = mode.."_force_freeze", CCmd = mode.."_set_force_freeze" }, + -- { CVar = mode.."_force_weld", CCmd = mode.."_set_force_weld" }, + { CVar = mode.."_force_nocollide", CCmd = mode.."_set_force_nocollide" }, + { CVar = mode.."_force_stayinworld", CCmd = mode.."_set_force_stayinworld" }, + }, + } + + local ctrl = vgui.Create( "StackerControlPresets", cpanel ) + ctrl:SetPreset( presets.folder ) + for k, v in pairs( presets.options ) do + ctrl:AddOption( k, v ) + end + for k, v in pairs( presets.cvars ) do + ctrl:AddConVar( v ) + end + cpanel:AddItem( ctrl ) + --cpanel:AddControl( "ComboBox", presets ) + + + local bg = Color( 210, 210, 210 ) or Color( 179, 216, 255 ) + local fg = Color( 240, 240, 240 ) or Color( 229, 242, 255 ) + + local sliders = { + { String = "max_per_player", Min = -1, Max = 2048, Decimals = 0 }, + { String = "max_per_stack", Min = 1, Max = 100, Decimals = 0 }, + { String = "delay", Min = 0, Max = 5, }, + { String = "max_offsetx", Min = 0, Max = 10000, }, + { String = "max_offsety", Min = 0, Max = 10000, }, + { String = "max_offsetz", Min = 0, Max = 10000, }, + } + + local sliderlist = vgui.Create( "DListLayout", cpanel ) + sliderlist:DockPadding( 3, 1, 3, 3 ) + sliderlist:SetPaintBackground( true ) + function sliderlist:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, bg ) + end + cpanel:AddItem( sliderlist ) + + for k, data in pairs( sliders ) do + local list = vgui.Create( "DListLayout", sliderlist ) + list:DockPadding( 5, 0, 5, 5 ) + list:DockMargin( 0, 2, 0, 0 ) + list:SetPaintBackground( true ) + function list:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, fg ) + end + + local decimals = data.Decimals or 2 + + local slider = vgui.Create( "StackerDNumSlider", list ) + slider:SetText( L(prefix.."label_"..data.String) ) + slider.Label:SetFont( "DermaDefaultBold" ) + slider:SetMinMax( data.Min, data.Max ) + slider:SetDark( true ) + slider:SizeToContents() + slider:SetDecimals( decimals ) + slider:SetValue( decimals == 0 and GetConVar( mode.."_"..data.String ):GetInt() or GetConVar( mode.."_"..data.String ):GetFloat(), true ) + + local cmd = mode.."_set_"..data.String + + function slider:OnValueChanged( value ) + value = math.Round( value, decimals ) + RunConsoleCommand( cmd, value ) + end + + if ( L(prefix.."help_"..data.String) ) then + local help = vgui.Create( "DLabel", list ) + help:SetText( L(prefix.."help_"..data.String) ) + help:DockMargin( 10, 0, 5, 0 ) + help:SetWrap( true ) + help:SetDark( true ) + help:SetAutoStretchVertical( true ) + help:SetFont( "DermaDefault" ) + end + + if ( L(prefix.."warning_"..data.String) ) then + local help = vgui.Create( "DLabel", list ) + help:SetText( L(prefix.."warning_"..data.String) ) + help:DockMargin( 10, 0, 5, 0 ) + help:SetWrap( true ) + help:SetDark( true ) + help:SetAutoStretchVertical( true ) + help:SetFont( "DermaDefault" ) + help:SetTextColor( Color( 200, 0, 0 ) ) + end + + cvars.AddChangeCallback( mode.."_"..data.String, function( name, old, new ) + if ( not IsValid( slider ) ) then return end + slider:SetValue( GetConVar( mode.."_"..data.String ):GetFloat(), true ) + end, mode.."_"..data.String.."_utilities" ) + end + + + + local checkboxes = { + "freeze", + -- "weld", + "nocollide", + "nocollide_all", + "stayinworld", + } + + local cblist = vgui.Create( "DListLayout", cpanel ) + cblist:DockPadding( 3, 1, 3, 3 ) + cblist:SetPaintBackground( true ) + function cblist:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, bg ) + end + cpanel:AddItem( cblist ) + + for k, data in pairs( checkboxes ) do + local list = vgui.Create( "DListLayout", cblist ) + list:DockPadding( 5, 5, 5, 5 ) + list:DockMargin( 0, 2, 0, 0 ) + list:SetPaintBackground( true ) + function list:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, w, h, fg ) + end + + local cb = vgui.Create( "DCheckBoxLabel", list ) + cb:SetText( L(prefix.."checkbox_"..data) ) + cb:SetChecked( GetConVar( mode.."_force_"..data ):GetBool() ) + cb.Label:SetFont( "DermaDefaultBold" ) + cb:SizeToContents() + cb:SetDark( true ) + -- we don't want this value to be changed while the server is running, so disable the checkbox + if ( data == "nocollide_all" ) then + cb:SetDisabled( true ) + end + + function cb:OnChange( bool ) RunConsoleCommand( mode.."_set_force_"..data, bool and "1" or "0" ) end + + cvars.AddChangeCallback( mode.."_force_"..data, function( name, old, new ) + if ( not IsValid( cb ) ) then return end + cb:SetChecked( tobool( new ) ) + end, mode.."_"..data.."_utilities" ) + + if ( L(prefix.."help_"..data) ) then + local help = vgui.Create( "DLabel", list ) + help:SetText( L(prefix.."help_"..data) ) + help:DockMargin( 25, 5, 5, 0 ) + help:SetWrap( true ) + help:SetDark( true ) + help:SetAutoStretchVertical( true ) + help:SetFont( "DermaDefault" ) + end + + if ( L(prefix.."warning_"..data) ) then + local help = vgui.Create( "DLabel", list ) + help:SetText( L(prefix.."warning_"..data) ) + help:DockMargin( 25, 5, 5, 0 ) + help:SetWrap( true ) + help:SetDark( true ) + help:SetAutoStretchVertical( true ) + help:SetFont( "DermaDefault" ) + help:SetTextColor( Color( 200, 0, 0 ) ) + end + end + end ) + end ) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/submaterial.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/submaterial.lua new file mode 100644 index 0000000..d6f0279 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/submaterial.lua @@ -0,0 +1,443 @@ + +TOOL.Category = "Dobrograd" +TOOL.Name = "SubMaterial"--"#tool.material.name" +if CLIENT then + language.Add( "tool.submaterial.name", "SubMaterial Tool" ) + language.Add( "tool.submaterial.desc", "Allow to override submaterials of model." ) + language.Add( "tool.submaterial.0", "Wheel Up/Down: Select target part, Primary: Apply material, Secondary: Set default material, Reload: Copy material" ) + language.Add( "tool.submaterial.help", "Select material here, type known material string or use HUD to copy materials" ) +end +TOOL.ClientConVar[ "override" ] = "debug/env_cubemap_model" +TOOL.ClientConVar[ "index" ] = 0 + +-- +-- Duplicator function +-- +local function SetSubMaterial( Player, Entity, Data ) + + if ( SERVER ) then + local Mats=Entity:GetMaterials() + local MatCount=table.Count(Mats) + for i=0,MatCount-1 do + local si="SubMaterialOverride_"..tostring(i) + -- Block exploitable material in multiplayer and remove empty strings + if Data[si] and ((!game.SinglePlayer() && string.lower(Data[si]) == "pp/copy" ) or Data[si] == "" ) then + Data[si]=nil + end + Entity:SetSubMaterial( i, Data[si] or "") + end + duplicator.ClearEntityModifier( Entity, "submaterial") + if (table.Count(Data) > 0) then duplicator.StoreEntityModifier( Entity, "submaterial", Data ) end + end + + return true + +end +duplicator.RegisterEntityModifier( "submaterial", SetSubMaterial ) + +local function UpdateSubMat(Player, Entity, Index, Material) + local Mats=Entity:GetMaterials() + local MatCount=table.Count(Mats) + if Index < 0 or Index >= MatCount then return end + local Data={} + for i=0,MatCount-1 do + local mat=Entity:GetSubMaterial(i) + if i==Index then mat=Material end + if mat and mat ~= "" then Data["SubMaterialOverride_"..tostring(i)]=mat end + end + return SetSubMaterial(Player, Entity, Data) +end + + +-- Original set material funct +local function SetMaterial( Player, Entity, Data ) + + if ( SERVER ) then + + -- + -- Make sure this is in the 'allowed' list in multiplayer - to stop people using exploits + -- + --if ( !game.SinglePlayer() && !list.Contains( "OverrideMaterials", Data.MaterialOverride ) && Data.MaterialOverride != "" ) then return end + if not Data.MaterialOverride or (Data.MaterialOverride and (!game.SinglePlayer() && string.lower(Data.MaterialOverride) == "pp/copy" )) then + return + end + Entity:SetMaterial( Data.MaterialOverride ) + duplicator.StoreEntityModifier( Entity, "material", Data ) + end + + return true + +end +--and we will override it because original function eats most of materials even not exploitable! :( +duplicator.RegisterEntityModifier( "material", SetMaterial ) + + +-- +-- Left click applies the current material +-- +function TOOL:LeftClick( trace ) + if not IsValid(trace.Entity) then trace.Entity = self:GetOwner() end + local mat = self:GetClientInfo( "override" ) + if IsValid(trace.Entity) and trace.Entity:IsPlayer() then + local override = hook.Run('submaterial.canUseOnPlayer', self:GetOwner(), trace.Entity, mat) + if override == false then trace.Entity = nil end + end + if not IsValid(trace.Entity) then return end + + if ( CLIENT ) then return true end + + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + + local index = self:GetClientNumber( "index" , 0) + if index < 1 then + SetMaterial( self:GetOwner(), ent, { MaterialOverride = mat } ) + else + UpdateSubMat( self:GetOwner(), ent, index-1, mat ) + end + return true + +end + +hook.Add('submaterial.canUseOnPlayer', 'submaterial', function(ply, target, mat) + if not ply:query('DBG: Применять тулы на игроках') then return false end +end) + +-- +-- Right click reverts the material +-- +function TOOL:RightClick( trace ) + + if ( !IsValid( trace.Entity ) ) then trace.Entity = self:GetOwner() end + if IsValid(trace.Entity) and trace.Entity:IsPlayer() and not self:GetOwner():query('DBG: Применять тулы на игроках') then trace.Entity = nil end + if not IsValid(trace.Entity) then return end + + if ( CLIENT ) then return true end + + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + local index = self:GetClientNumber( "index" , 0) + if index < 1 then + SetMaterial( self:GetOwner(), ent, { MaterialOverride = "" } ) + else + UpdateSubMat( self:GetOwner(), ent, index-1, "" ) + end + return true + +end + + +----- Damn Dirty fix... Thx for Wire Advanced tool developer + +local function get_active_tool(ply, tool) + -- find toolgun + local activeWep = ply:GetActiveWeapon() + if not IsValid(activeWep) or activeWep:GetClass() ~= "gmod_tool" or activeWep.Mode ~= tool then return end + + return activeWep:GetToolObject(tool) +end + +if game.SinglePlayer() then -- wtfgarry (these functions don't get called clientside in single player so we need this hack to fix it) + if SERVER then + util.AddNetworkString( "submaterial_wtfgarry" ) + local function send( ply, funcname ) + net.Start( "submaterial_wtfgarry" ) + net.WriteString( funcname ) + net.Send( ply ) + end + + --function TOOL:LeftClick() send( self:GetOwner(), "LeftClick" ) end + --function TOOL:RightClick() send( self:GetOwner(), "RightClick" ) end + function TOOL:Reload() send( self:GetOwner(), "Reload" ) end + elseif CLIENT then + net.Receive( "submaterial_wtfgarry", function( len ) + local funcname = net.ReadString() + local tool = get_active_tool( LocalPlayer(), "submaterial" ) + if not tool then return end + tool[funcname]( tool, LocalPlayer():GetEyeTrace() ) + end) + end +end +----------------------------- +if CLIENT then + + + TOOL.AimEnt = nil + TOOL.HudData = {} + TOOL.SelIndx = 1 + TOOL.ToolMatString = "" + + function TOOL:Reload( trace ) + + if ( !IsValid( trace.Entity ) ) then trace.Entity = self:GetOwner() end + if IsValid(trace.Entity) and trace.Entity:IsPlayer() and not self:GetOwner():query('DBG: Применять тулы на игроках') then trace.Entity = nil end + if not IsValid(trace.Entity) then return end + + --if ( CLIENT ) then return true end + + local ent = trace.Entity + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + + --local index = self:GetClientNumber( "index" , 0) + local mat=self.HudData.EntCurMatString--"" + + if !mat or mat ~= "" then + RunConsoleCommand("submaterial_override",mat) + end + --LocalPlayer():ChatPrint("Material ".. (((self.SelIndx < 1) and "[Global]") or tostring(self.SelIndx)).." copied: "..mat) + --else LocalPlayer():ChatPrint("Empty material!") end + --end + return true + + end + + + + function TOOL:Scroll(trace,dir) + if !IsValid(self.AimEnt) then return end + local Mats=self.AimEnt:GetMaterials() + local MatCount=table.Count(Mats) + self.SelIndx = self.SelIndx + dir + if(self.SelIndx<0) then self.SelIndx = MatCount end + if(self.SelIndx>MatCount) then self.SelIndx = 0 end + RunConsoleCommand("submaterial_index",tostring(self.SelIndx)) + return true + --self.HudData.EntCurMat=Material(self.AimEnt:GetMaterials()[self.SelIndx]) + + end + function TOOL:ScrollUp(trace) return self:Scroll(trace,-1) end + function TOOL:ScrollDown(trace) return self:Scroll(trace,1) end + + + +---- Thx wire_adv dev again... + local function hookfunc( ply, bind, pressed ) + if not pressed then return end + if bind == "invnext" then + local self = get_active_tool(ply, "submaterial") + if not self then return end + + return self:ScrollDown(ply:GetEyeTraceNoCursor()) + elseif bind == "invprev" then + local self = get_active_tool(ply, "submaterial") + if not self then return end + + return self:ScrollUp(ply:GetEyeTraceNoCursor()) + end + end + + if game.SinglePlayer() then -- wtfgarry (have to have a delay in single player or the hook won't get added) + timer.Simple(5,function() hook.Add( "PlayerBindPress", "submat_tool_playerbindpress", hookfunc ) end) + else + hook.Add( "PlayerBindPress", "submat_tool_playerbindpress", hookfunc ) + end +-------------------------------------------------- + + + local function FixVertexLitMaterial(Mat) + + -- + -- If it's a vertexlitgeneric material we need to change it to be + -- UnlitGeneric so it doesn't go dark when we enter a dark room + -- and flicker all about + -- + if not Mat then return Mat end + local strImage = Mat:GetName() + + if ( string.find( Mat:GetShader(), "VertexLitGeneric" ) || string.find( Mat:GetShader(), "Cable" ) ) then + + local t = Mat:GetString( "$basetexture" ) + + if ( t ) then + + local params = {} + params[ "$basetexture" ] = t + params[ "$vertexcolor" ] = 1 + params[ "$vertexalpha" ] = 1 + + Mat = CreateMaterial( strImage .. "_hud_fx", "UnlitGeneric", params ) + + end + + end + + return Mat + + end + + function TOOL:Think( ) + local ent=LocalPlayer():GetEyeTraceNoCursor().Entity + if not IsValid(ent) then ent = LocalPlayer() end + if ( IsValid( ent.AttachedEntity ) ) then ent = ent.AttachedEntity end + if IsValid(ent) and ent:IsPlayer() and not LocalPlayer():query('DBG: Применять тулы на игроках') then ent = nil end + if self.AimEnt ~= ent then + + self.AimEnt=ent + if IsValid(self.AimEnt) then + self.SelIndx=0 + RunConsoleCommand("submaterial_index",tostring(self.SelIndx)) + self.HudData.Mats=self.AimEnt:GetMaterials() + + end + --print("ThinkUpdate "..tostring(self.AimEnt)) + end + + if IsValid(self.AimEnt) then + self.HudData.CurMats=table.Copy(self.HudData.Mats) + self.HudData.OvrMats={} + + local MatCount=table.Count(self.HudData.Mats) + for i=1,MatCount do + local mat=self.AimEnt:GetSubMaterial(i-1) + if mat and mat ~= "" then self.HudData.OvrMats[i]=mat end + end + table.Merge(self.HudData.CurMats,self.HudData.OvrMats) + self.HudData.GlobalMat=self.AimEnt:GetMaterial() + local EntCurMatString=self.HudData.GlobalMat + local EntOrigMatString=self.HudData.GlobalMat + if self.SelIndx > 0 then EntCurMatString=self.HudData.CurMats[self.SelIndx]; EntOrigMatString=self.HudData.Mats[self.SelIndx] end + if self.HudData.EntCurMatString~=EntCurMatString then + self.HudData.EntCurMatString=EntCurMatString + self.HudData.EntCurMat=FixVertexLitMaterial(Material(EntCurMatString)) + end + if self.HudData.EntOrigMatString~=EntOrigMatString then + self.HudData.EntOrigMatString=EntOrigMatString + self.HudData.EntOrigMat=FixVertexLitMaterial(Material(EntOrigMatString)) + end + end + + if IsValid(self.AimEnt) and self.ToolMatString~=GetConVarString("submaterial_override") then + self.ToolMatString=GetConVarString("submaterial_override") + self.HudData.ToolMat=FixVertexLitMaterial(Material(self.ToolMatString)) + end + + end + function TOOL:DrawHUD( ) + if IsValid(self.AimEnt) then + + ---- List + local Rg=ScrW()/2-50 + local MaxW = 0 + local TextH = 0 + surface.SetFont("ChatFont") + local Hdr=tostring(self.AimEnt)..": "..tostring(table.Count(self.HudData.Mats)).." materials" + MaxW,TextH=surface.GetTextSize(Hdr) + local HdrH = TextH+5 + for _,s in pairs(self.HudData.CurMats) do + local ts,_=surface.GetTextSize(s) + if MaxW 0 then + SetMass( self:GetOwner(), trace.Entity, { Mass = mass } ) + else + net.Start("WeightSTool_1") + net.Send(self:GetOwner()) + --umsg.Start("WeightSTool_1", self:GetOwner()) + --umsg.End() + end + + return true; +end + +function TOOL:RightClick( trace ) + if CLIENT and IsReallyValid(trace) then return true end + if not IsReallyValid(trace) then return end + + local mass = trace.Entity:GetPhysicsObject():GetMass() + self:GetOwner():ConCommand("weight_set "..mass); + return true; +end + +function TOOL:Reload( trace ) + if CLIENT then return false end + if not IsReallyValid(trace) then return false end + local pl = self:GetOwner() + local weight = Weights[trace.Entity:GetModel()] + if not weight then return end + + SetMass( self:GetOwner(), trace.Entity, { Mass = weight } ) + + self.Weapon:EmitSound( Sound( "Airboat.FireGunRevDown" ) ) + self.Weapon:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetNormal( trace.HitNormal ) + effectdata:SetEntity( trace.Entity ) + effectdata:SetAttachment( trace.PhysicsBone ) + util.Effect( "selection_indicator", effectdata ) + + local effectdata = EffectData() + effectdata:SetOrigin( trace.HitPos ) + effectdata:SetStart( pl:GetShootPos() ) + effectdata:SetAttachment( 1 ) + effectdata:SetEntity( self.Weapon ) + util.Effect( "ToolTracer", effectdata ) + + return false +end + +function TOOL:Think() + if CLIENT then return end + local pl = self:GetOwner() + local wep = pl:GetActiveWeapon() + if not wep:IsValid() or wep:GetClass() != "gmod_tool" or pl:GetInfo("gmod_toolmode") != "weight" then return end + local trace = pl:GetEyeTrace() + if not IsReallyValid(trace) then return end + if not Weights[trace.Entity:GetModel()] then + Weights[trace.Entity:GetModel()] = trace.Entity:GetPhysicsObject():GetMass() + end + pl:SetNetworkedFloat("WeightMass", trace.Entity:GetPhysicsObject():GetMass()) + pl:SetNetworkedFloat("OWeight", Weights[trace.Entity:GetModel()] or 0) +end + +function TOOL.BuildCPanel( cp ) + cp:AddControl( "Header", { Text = "#Tool.weight.name", Description = "#Tool.weight.desc" } ) + + local params = { Label = "#Presets", MenuButton = 1, Folder = "weight", Options = {}, CVars = {} } + + params.Options.default = { weight_set = 3 } + table.insert( params.CVars, "weight_set" ) + + cp:AddControl("ComboBox", params ) + cp:AddControl("Slider", { Label = "#Tool.weight.set", Type = "Float", Min = "0.01", Max = "10000", Command = "weight_set" } ) + -- cp:AddControl("Slider", { Label = "Tooltip Scale:", Type = "Numeric", Min = "100", Max = "10000", Command = "weight_tooltip_scale" } ) +end + +if CLIENT then +-- local TipColor = Color( 250, 250, 200, 255 ) + +-- surface.CreateFont("NewTooltip", {font = "coolvetica", size = 300, weight = 500}) +-- surface.CreateFont("GModWorldtip", {font = "coolvetica", size = 24, weight = 500}) + +-- local function DrawWeight() +-- local pl = LocalPlayer() +-- local wep = pl:GetActiveWeapon() + +-- if not wep:IsValid() or wep:GetClass() != "gmod_tool" or pl:GetInfo("gmod_toolmode") != "weight" then return end + +-- local trace = pl:GetEyeTrace() +-- if not IsReallyValid(trace) then return end + +-- local ang = LocalPlayer():EyeAngles() +-- ang:RotateAroundAxis(LocalPlayer():GetForward(), 270) +-- ang:RotateAroundAxis(LocalPlayer():GetRight(), -180) +-- ang:RotateAroundAxis(LocalPlayer():GetUp(), 90) +-- ang = Angle(0, ang.y, ang.r) + +-- local rOBB = trace.Entity:OBBCenter() +-- rOBB:Rotate(trace.Entity:GetAngles()) + +-- local black = Color( 0, 0, 0, 255 ) +-- local tipcol = Color( TipColor.r, TipColor.g, TipColor.b, 255 ) + +-- local mass = LocalPlayer():GetNetworkedFloat("WeightMass") or 0 +-- local omass = LocalPlayer():GetNetworkedFloat("OWeight") or 0 +-- local str = "Current: " .. mass .. ", Original: " .. omass +-- cam.IgnoreZ(true) +-- if (GetConVarNumber("weight_tooltip_old") == 0) then +-- surface.SetFont("NewTooltip") +-- local w,h = surface.GetTextSize(str) +-- cam.Start3D2D(trace.Entity:GetPos()+rOBB, ang, trace.StartPos:Distance(trace.Entity:GetPos()+rOBB)/GetConVarNumber("weight_tooltip_scale")) +-- surface.SetDrawColor(Color(0, 0, 0, 100)) +-- surface.DrawRect(-w/2 - 5, -h/2, w + 10, h) +-- surface.SetTextPos(0 - w/2, -h/2) +-- surface.SetTextColor(Color(255,255,255)) +-- surface.DrawText(str) +-- else +-- surface.SetFont("GModWorldtip") +-- local w,h = surface.GetTextSize(str) +-- cam.Start3D2D(trace.Entity:GetPos()+rOBB, ang, trace.StartPos:Distance(trace.Entity:GetPos()+rOBB)/700) +-- local x,y = -w/1.5, -50 +-- local offset = 30 +-- local padding = 10 -- Does what it says on the tin +-- -- Old draw method +-- -- Outline +-- draw.RoundedBox( 8, x-padding-2, y-padding-2, w+padding*2+4, h+padding*2+4, black ) +-- local verts = {} +-- verts[1] = { x=x+(w/1.5)-offset-3, y=y+h } +-- verts[2] = { x=x+(w/1.5)+offset+3, y=y+h/2 } +-- verts[3] = { x=0, y=2 } +-- draw.NoTexture() +-- surface.SetDrawColor( 0, 0, 0, tipcol.a ) +-- surface.DrawPoly( verts ) + +-- -- Inner "Yellow" +-- draw.RoundedBox( 8, x-padding, y-padding, w+padding*2, h+padding*2, tipcol ) +-- local verts = {} +-- verts[1] = { x=x+(w/1.5)-offset, y=y+h } +-- verts[2] = { x=x+(w/1.5)+offset, y=y+h/2 } +-- verts[3] = { x=0, y=0 } +-- draw.NoTexture() +-- surface.SetDrawColor( tipcol.r, tipcol.g, tipcol.b, tipcol.a ) +-- surface.DrawPoly( verts ) + +-- -- Actual Text +-- draw.DrawText( str, "GModWorldtip", x + w/2, y, black, TEXT_ALIGN_CENTER ) +-- end +-- cam.End3D2D() +-- cam.IgnoreZ(false) +-- end +-- hook.Add("PreDrawEffects", "DrawWeight", DrawWeight) + + local function ZMass() + LocalPlayer():ConCommand("weight_set 1") + GAMEMODE:AddNotify("#Tool.weight.zeromass", NOTIFY_ERROR, 6); + surface.PlaySound( "buttons/button10.wav" ) + end + net.Receive("WeightSTool_1", ZMass) + +-- CreateClientConVar("weight_tooltip_old", "0", true, false) +-- CreateClientConVar("weight_tooltip_scale", "1600", true, false) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/weld.lua b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/weld.lua new file mode 100644 index 0000000..1eda38b --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/gmod_tool/stools/weld.lua @@ -0,0 +1,287 @@ + +TOOL.Category = "Constraints" +TOOL.Name = "#tool.weld.name" + +TOOL.ClientConVar[ "forcelimit" ] = "0" + +TOOL.Information = { + { name = "left", stage = 0 }, + { name = "left_1", stage = 1, op = 2 }, + { name = "right", stage = 0 }, + { name = "right_1", stage = 1, op = 1 }, + { name = "right_2", stage = 2, op = 1 }, + { name = "reload" } +} + +function TOOL:LeftClick( trace ) + + if ( self:GetOperation() == 1 ) then return false end + if ( IsValid( trace.Entity ) && trace.Entity:IsPlayer() ) then return false end + + -- If there's no physics object then we can't constraint it! + if ( SERVER && !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then return false end + + local iNum = self:NumObjects() + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + self:SetObject( iNum + 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + + if ( CLIENT ) then + + if ( iNum > 0 ) then self:ClearObjects() end + return true + + end + + self:SetOperation( 2 ) + + if ( iNum == 0 ) then + + self:SetStage( 1 ) + return true + + end + + if ( iNum == 1 ) then + + -- Get client's CVars + local forcelimit = self:GetClientNumber( "forcelimit" ) + local nocollide = false + + -- Get information we're about to use + local Ent1, Ent2 = self:GetEnt( 1 ), self:GetEnt( 2 ) + local Bone1, Bone2 = self:GetBone( 1 ), self:GetBone( 2 ) + + local constraint = constraint.Weld( Ent1, Ent2, Bone1, Bone2, forcelimit, nocollide ) + if ( constraint ) then + + undo.Create( "Weld" ) + undo.AddEntity( constraint ) + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + + self:GetOwner():AddCleanup( "constraints", constraint ) + + end + + -- Clear the objects so we're ready to go again + self:ClearObjects() + + end + + return true + +end + +function TOOL:RightClick( trace ) + + if ( self:GetOperation() == 2 ) then return false end + + -- Make sure the object we're about to use is valid + local iNum = self:NumObjects() + local Phys = trace.Entity:GetPhysicsObjectNum( trace.PhysicsBone ) + + -- You can click anywhere on the 3rd pass + if ( iNum < 2 ) then + + -- If there's no physics object then we can't constraint it! + if ( SERVER && !util.IsValidPhysicsObject( trace.Entity, trace.PhysicsBone ) ) then return false end + + -- Don't weld players, or to players + if ( trace.Entity:IsPlayer() ) then return false end + + -- Don't do anything with stuff without any physics.. + if ( SERVER && !IsValid( Phys ) ) then return false end + + end + + if ( iNum == 0 ) then + + if ( !IsValid( trace.Entity ) ) then return false end + if ( trace.Entity:GetClass() == "prop_vehicle_jeep" ) then return false end + + end + + self:SetObject( iNum + 1, trace.Entity, trace.HitPos, Phys, trace.PhysicsBone, trace.HitNormal ) + self:SetOperation( 1 ) + + -- + -- Stage 0 - grab an object, make a ghost entity + -- + if ( iNum == 0 ) then + + self:StartGhostEntity( trace.Entity ) + self:SetStage( 1 ) + return true + + end + + -- + -- Stage 1 - choose the spot and object to weld it to + -- + if ( iNum == 1 ) then + + if ( CLIENT ) then + self:ReleaseGhostEntity() + return true + end + + -- fix that weird surfing bug + if self:GetEnt( 1 ) == self:GetEnt( 2 ) then + self:ClearObjects() + return false + end + + -- Get information we're about to use + local Norm1, Norm2 = self:GetNormal( 1 ), self:GetNormal( 2 ) + local Phys1 = self:GetPhys( 1 ) + local WPos2 = self:GetPos( 2 ) + + -- Note: To keep stuff ragdoll friendly try to treat things as physics objects rather than entities + local Ang1, Ang2 = Norm1:Angle(), ( -Norm2 ):Angle() + local TargetAngle = Phys1:AlignAngles( Ang1, Ang2 ) + + Phys1:SetAngles( TargetAngle ) + + -- Move the object so that the hitpos on our object is at the second hitpos + local TargetPos = WPos2 + ( Phys1:GetPos() - self:GetPos( 1 ) ) + + -- Set the position + Phys1:SetPos( TargetPos ) + Phys1:EnableMotion( false ) + + -- Wake up the physics object so that the entity updates + Phys1:Wake() + + self.RotAxis = Norm2 + + self:ReleaseGhostEntity() + + self:SetStage( 2 ) + + return true + + end + + -- + -- Stage 2 - Weld it in place. + -- + if ( iNum == 2 ) then + + if ( CLIENT ) then + + self:ClearObjects() + return true + + end + + -- Get client's CVars + local forcelimit = self:GetClientNumber( "forcelimit" ) + local nocollide = false + + -- Get information we're about to use + local Ent1, Ent2 = self:GetEnt( 1 ), self:GetEnt( 2 ) + local Bone1, Bone2 = self:GetBone( 1 ), self:GetBone( 2 ) + local Phys1 = self:GetPhys( 1 ) + + -- The entity became invalid half way through + if ( !IsValid( Ent1 ) ) then + + self:ClearObjects() + return false + + end + + local constraint = constraint.Weld( Ent1, Ent2, Bone1, Bone2, forcelimit, nocollide ) + if ( constraint ) then + + -- Phys1:EnableMotion( true ) + + undo.Create( "Weld" ) + undo.AddEntity( constraint ) + undo.SetPlayer( self:GetOwner() ) + undo.Finish() + + self:GetOwner():AddCleanup( "constraints", constraint ) + + end + + -- Clear the objects so we're ready to go again + self:ClearObjects() + return true + + end + +end + +function TOOL:Think() + + if ( self:NumObjects() < 1 ) then return end + + if ( self:GetOperation() == 1 ) then + + if ( SERVER && !IsValid( self:GetEnt( 1 ) ) ) then + + self:ClearObjects() + return + + end + + if ( self:NumObjects() == 1 ) then + + self:UpdateGhostEntity() + return + + end + + if ( SERVER && self:NumObjects() == 2 ) then + + local Phys1 = self:GetPhys( 1 ) + + local cmd = self:GetOwner():GetCurrentCommand() + + local degrees = cmd:GetMouseX() * 0.05 + + local angle = Phys1:RotateAroundAxis( self.RotAxis, degrees ) + + Phys1:SetAngles( angle ) + + -- Move so spots join up + local TargetPos = self:GetPos( 2 ) + ( Phys1:GetPos() - self:GetPos( 1 ) ) + Phys1:SetPos( TargetPos ) + Phys1:Wake() + + end + + end + +end + +function TOOL:Reload( trace ) + + if ( !IsValid( trace.Entity ) || trace.Entity:IsPlayer() ) then return false end + if ( CLIENT ) then return true end + + self:ClearObjects() + + return constraint.RemoveConstraints( trace.Entity, "Weld" ) + +end + +function TOOL:FreezeMovement() + + return self:GetOperation() == 1 && self:GetStage() == 2 + +end + +function TOOL:Holster() + + self:ClearObjects() + +end + +function TOOL.BuildCPanel( CPanel ) + + CPanel:AddControl( "Header", { Description = "#tool.weld.help" } ) + CPanel:AddControl( "Slider", { Label = "#tool.forcelimit", Command = "weld_forcelimit", Type = "Float", Min = 0, Max = 1000, Help = true } ) + +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/cl_init.lua b/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/cl_init.lua new file mode 100644 index 0000000..3a4e13d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/cl_init.lua @@ -0,0 +1 @@ +include('shared.lua') diff --git a/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/init.lua b/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/init.lua new file mode 100644 index 0000000..3402c7d --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/init.lua @@ -0,0 +1,29 @@ +AddCSLuaFile('shared.lua') +AddCSLuaFile('cl_init.lua') +include('shared.lua') + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 0.4) + + if not IsValid(self.Owner) or self.Owner.hacking then return end + + local ent = self.Owner:GetEyeTrace().Entity + if not IsValid(ent) or not ent.IsKeypad then return end + if ent:GetPos():DistToSqr(self.Owner:GetShootPos()) > 2500 then return end + if ent:CPPIGetOwner() == self.Owner then return end + + self:SetHoldType('pistol') + self.Owner:Hack(ent, function() + + local vPoint = ent:GetPos() + local effect = EffectData() + effect:SetStart(vPoint) + effect:SetOrigin(vPoint) + effect:SetEntity(ent) + util.Effect('sparks_manhack', effect) + ent:EmitSound('buttons/combine_button7.wav', 70) + + end, nil, function() + self:SetHoldType(self.IdleStance) + end) +end diff --git a/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/shared.lua b/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/shared.lua new file mode 100644 index 0000000..bfa6f30 --- /dev/null +++ b/garrysmod/addons/gmod-tools/lua/weapons/keypad_cracker/shared.lua @@ -0,0 +1,47 @@ +SWEP.PrintName = L.keypad_cracker +SWEP.Category = L.dobrograd +SWEP.Slot = 4 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true +SWEP.Author = 'chelog & Wani4ka' +SWEP.Instructions = L.instruction_keypad_cracker +SWEP.Contact = '' +SWEP.Purpose = '' +SWEP.ViewModel = Model('models/weapons/v_c4.mdl') +SWEP.WorldModel = Model('models/weapons/w_c4.mdl') + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.AnimPrefix = 'python' + +SWEP.Sound = Sound('weapons/deagle/deagle-1.wav') + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +SWEP.IdleStance = 'slam' +SWEP.Icon = 'octoteam/icons/keypad_cracker.png' + +function SWEP:Initialize() + self:SetHoldType(self.IdleStance) +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 0.4) +end + +function SWEP:Holster() + return true +end + +function SWEP:Reload() + return true +end diff --git a/garrysmod/addons/util-apg/lua/apg/cl_menu.lua b/garrysmod/addons/util-apg/lua/apg/cl_menu.lua new file mode 100644 index 0000000..df31d40 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/cl_menu.lua @@ -0,0 +1,397 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + +]]-------------------------------------------- +APG_panels = APG_panels or {} + +local utils = include( "cl_utils.lua" ) or { } + +local function APGBuildStackPanel() + local panel = APG_panels["stack_detection"] + panel.Paint = function( i, w, h) end + + utils.numSlider(panel, 0, 40, 500, 20, "Maximum stacked ents", "stackMax", 3, 50, 0 ) + utils.numSlider(panel, 0, 75, 500, 20, "Stack distance (gmod units)", "stackArea", 5, 50, 0) +end + +local function APGBuildMiscPanel() + local panel = APG_panels["misc"] + panel.Paint = function( i, w, h) end + + utils.switch( panel, 0, 40, 395, 20, "Auto freeze over time", "autoFreeze" ) + utils.numSlider(panel, 0, 70, 500, 20, "Auto freeze delay(seconds)", "autoFreezeTime", 5, 600, 0 ) + utils.switch( panel, 0, 100, 395, 20, "Disable vehicle damages", "vehDamage" ) + utils.switch( panel, 0, 130, 395, 20, "Disable vehicle collisions (with players)", "vehNoCollide" ) + utils.switch( panel, 0, 160, 395, 20, "Block Physgun Reload", "blockPhysgunReload" ) + --APG_numSlider(panel, 0, 75, 500, 20, "Vehicle NoCollide", "vehNoCollide", 5, 50, 0) +end + +local function APGBuildLagPanel() + local panel = APG_panels["lag_detection"] + panel.Paint = function( i, w, h) end + + utils.numSlider(panel, 0, 40, 500, 20, "Lag threshold (%)", "lagTrigger", 5, 1000, 0 ) + utils.numSlider(panel, 0, 75, 500, 20, "Frames lost", "lagsCount", 1, 20, 0) + utils.numSlider(panel, 0, 110, 500, 20, "Heavy lag trigger (seconds)", "bigLag", 1, 5, 1) + utils.comboBox(panel, 0, 145, 500, 20, "Lag fix function", "lagFunc", APG_lagFuncs) + utils.numSlider(panel, 0, 180, 500, 20, "Lag func. delay (seconds)", "lagFuncTime", 1, 300, 0) + --utils.numSlider(panel, 0, 215, 500, 20, "Notification mode ", "lagFuncNotify", 0, 2, 0) +end + +local function APGBuildToolHackPanel() + local panel = APG_panels["misc2"] + panel.Paint = function( i, w, h) end + + utils.switch( panel, 0, 40, 395, 20, "Inject custom hooks into Fading Doors", "thFadingDoors" ) + utils.switch( panel, 0, 75, 395, 20, "Activate fading door ghosting", "FadingDoorGhosting" ) + utils.switch( panel, 0, 105, 395, 20, "Activate FRZR9K (Sleepy Physics)", "frzr9k" ) + utils.switch( panel, 0, 135, 395, 20, "Allow prop killing (Won't work well with ghosting)", "AllowPK" ) +end + +local function APGBuildGhostPanel() + local panel = APG_panels["ghosting"] + panel.Paint = function( i, w, h) + draw.RoundedBox(0,0,37,170,135,Color( 38, 38, 38, 255)) + draw.DrawText( "Ghosting color:", "APG_element_font",5, 37, Color( 189, 189, 189), 3 ) + + draw.RoundedBox(0,175,37,250,250,Color( 38, 38, 38, 255)) + draw.DrawText( "Bad entities:", "APG_element_font", 180, 37, Color( 189, 189, 189), 3 ) + draw.DrawText( "(Right-Click to Toggle)", "APG_title2_font", 280, 38, Color( 189, 189, 189), 3 ) + end + utils.switch( panel, 0, 180, 170, 20, "Always frozen", "alwaysFrozen" ) + + local Mixer = vgui.Create( "CtrlColor", panel ) + Mixer:SetPos(5,55) + Mixer:SetSize(160,110) + Mixer.Mixer.ValueChanged = function(self,color) + APG.cfg["ghost_color"].value = Color( color.r, color.g, color.b, color.a) + end + + local dList = vgui.Create("DListView", panel) + dList:Clear() + dList:SetPos( 180, 55 ) + dList:SetSize(panel:GetWide() - 185, panel:GetTall()-5-55) + dList:SetMultiSelect(false) + dList:SetHideHeaders(false) + dList:AddColumn("Class") + dList:AddColumn("Exact") + + function dList:OnRowRightClick( id, line ) + local key = line:GetColumnText(1) + local value = !tobool(line:GetColumnText(2)) + line:SetColumnText(2, value) + APG.cfg["bad_ents"].value[key] = value + end + + local function updtTab() + dList:Clear() + for class,complete in pairs(APG.cfg["bad_ents"].value) do + dList:AddLine(class, complete) + end + end + updtTab() + + dList.Paint = function(i,w,h) + draw.RoundedBox(0,0,0,w,h,Color(150, 150, 150, 255)) + end + dList.VBar.Paint = function(i,w,h) + surface.SetDrawColor(88, 110, 110, 240) + surface.DrawRect(0,0,w,h) + end + dList.VBar.btnGrip.Paint = function(i,w,h) + surface.SetDrawColor(255, 83, 13,50) + surface.DrawRect(0,0,w,h) + draw.RoundedBox( 0, 1,1,w-2,h-2, Color( 72, 89, 89, 255 ) ) + end + dList.VBar.btnUp.Paint = function(i,w,h) + draw.RoundedBox( 0, 0,0,w,h, Color( 72, 89, 89, 240 ) ) + end + dList.VBar.btnDown.Paint = function(i,w,h) + draw.RoundedBox( 0, 0,0,w,h, Color( 72, 89, 89, 240 ) ) + end + + local TextEntry = vgui.Create( "DTextEntry", panel ) + TextEntry:SetPos( 180, 240 ) + TextEntry:SetSize( 150,20 ) + TextEntry:SetText( "Entity class" ) + TextEntry.OnEnter = function( self ) + chat.AddText( self:GetValue() ) + end + + local Add = vgui.Create( "DButton" , panel) + Add:SetPos( 320, 240 ) + Add:SetSize( 75,20 ) + Add:SetText( "Add" ) + Add.DoClick = function() + if TextEntry:GetValue() == "Entity class" then return end + utils.addBadEntity( TextEntry:GetValue() ) + updtTab() + end + + Add:SetTextColor(Color(255,255,255)) + Add.Paint = function(i,w,h) + draw.RoundedBox(0,0,0,w,h,Color(44, 55, 55, 240)) + draw.RoundedBox(0,1,1,w-2,h-2,Color( 58, 58, 58, 255)) + end + + local Remove = vgui.Create( "DButton" , panel) + Remove:SetPos( 180, 260 ) + Remove:SetSize( 215,20 ) + Remove:SetText( "Remove selected" ) + Remove.DoClick = function() + for k,v in pairs(dList:GetSelected()) do + local key = v:GetValue(1) + APG.cfg["bad_ents"].value[key] = nil + updtTab() + end + end + + Remove:SetTextColor(Color(255,255,255)) + Remove.Paint = function(i,w,h) + draw.RoundedBox(0,0,0,w,h,Color( 58, 58, 58, 255)) + draw.RoundedBox(0,0,0,w,1,Color(30, 30, 30, 125)) + end +end + + +local main_color = Color(32, 255, 0,255) +local function openMenu( len ) + len = net.ReadUInt( 32 ) + if len == 0 then return end + local settings = net.ReadData( len ) + settings = util.Decompress( settings ) + settings = util.JSONToTable( settings ) + + APG.cfg = settings.cfg + table.Merge(APG, settings) + + local APG_Main = vgui.Create( "DFrame" ) + APG_Main:SetSize( 550 , 320) + APG_Main:SetPos( ScrW()/2- APG_Main:GetWide()/2, ScrH()/2 - APG_Main:GetTall()/2) + APG_Main:SetTitle( "" ) + APG_Main:SetVisible( true ) + APG_Main:SetDraggable( true ) + APG_Main:MakePopup() + APG_Main:ShowCloseButton(false) + APG_Main.Paint = function(i,w,h) + draw.RoundedBox(4,0,0,w,h,Color(34, 34, 34,255)) + draw.RoundedBox(0,0,23,w,1,main_color) + local name = "A.P.G. - Anti Prop Griefing Solution" + draw.DrawText( name, "APG_title_font",8, 5, Color( 189, 189, 189), 3 ) + end + local closeButton = vgui.Create("DButton",APG_Main) + closeButton:SetPos(APG_Main:GetWide() - 20,4) + closeButton:SetSize(16,16) + closeButton:SetText('') + closeButton.DoClick = function() + APG_Main:Remove() + end + closeButton.Paint = function(i,w,h) + draw.RoundedBox(0,0,0,w,h,Color(255, 255, 255,3)) + draw.DrawText( "✕", "APG_sideBar_font",0, -2, Color( 189, 189, 189), 3 ) + end + local saveButton = vgui.Create("DButton",APG_Main) + saveButton:SetPos(APG_Main:GetWide() - 96,4) + saveButton:SetSize(72,16) + saveButton:SetText('') + saveButton.DoClick = function() + local settings = APG + settings = util.TableToJSON( settings ) + settings = util.Compress( settings ) + net.Start("apg_settings_c2s") + net.WriteUInt( settings:len(), 32 ) -- Write the length of the data (up to 172054108) + net.WriteData( settings, settings:len() ) -- Write the data + net.SendToServer() + APG_Main:Remove() + end + saveButton.Paint = function(i,w,h) + draw.RoundedBox(0,0,0,w,h,Color(255, 255, 255,3)) + draw.DrawText( "Save settings", "APG_title2_font",w/2, 1, Color( 189, 189, 189), 1 ) + end + + -- Side bar + local sidebar = vgui.Create("DPanel",APG_Main) + sidebar:SetSize( APG_Main:GetWide() / 4 , APG_Main:GetTall() - 35) + sidebar:SetPos(0,30) + sidebar.Paint = function(i,w,h) + draw.RoundedBox(0,0,0,w,h,Color( 33, 33, 33,255)) + draw.RoundedBox(0,w-1,0,1,h,main_color) + end + + local x,y = APG_Main:GetWide() - 150,APG_Main:GetTall() - 35 + local px, py = 145,30 + local first = true + for k, v in next, APG.modules do + local panel = vgui.Create("DPanel",APG_Main) + panel:SetSize(x,y) + panel:SetPos(px, py) + panel:SetVisible(first) + panel.Paint = function() end + APG_panels[k] = panel + first = false + + local button = vgui.Create("DButton",panel) + button:SetPos(0,0) + button:SetSize(panel:GetWide(),35) + button:SetText("") + button.UpdateColours = function( label, skin ) + label:SetTextStyleColor( Color( 189, 189, 189 ) ) + end + button.Paint = function(slf, w, h) + local enabled = APG.modules[k] + + draw.RoundedBox(0,0,h*0.85,w-5,1, Color(0, 96, 0,255)) + local text = utils.getNiceName(k) .. " module " + draw.DrawText( text, "APG_mainPanel_font",5, 8, Color( 189, 189, 189), 3 ) + utils.mainSwitch( w-48, 7.5, enabled ) + end + button.DoClick = function() + APG.modules[k] = not APG.modules[k] + end + end + + local i = 0 + local height = (sidebar:GetTall() - 20) / table.Count(APG.modules) + for k,v in next , APG.modules do + local button = vgui.Create("DButton",sidebar) + button:SetPos(5,(height + 5) * i) + button:SetSize(sidebar:GetWide() - 10 ,height) + button:SetText("") + button.DoClick = function() + for l,m in next, APG_panels do + if k != l then + APG_panels[l]:SetVisible(false) + else + APG_panels[l]:SetVisible(true) + end + end + end + local size = sidebar:GetWide() + button.Paint = function(_,w,h) + local name = utils.getNiceName(k) + if button.Hovered then + draw.RoundedBox(5,0,0,w,h,Color(46, 46, 46,255)) + draw.RoundedBox(0,2,2,w-4,h-4,Color( 36, 36,36, 255)) + end + if APG_panels[k]:IsVisible() then + draw.RoundedBox(0,0,0,w,h,Color( 36, 36,36, 255)) + draw.RoundedBox(0,w*0.15,h*0.72,w*0.7,1, Color(0, 96, 0,255)) + end + + draw.DrawText( name, "APG_sideBar_font",(size - name:len())/2, h*0.35, Color( 189, 189, 189), 1) + end + i = i + 1 + end + APGBuildMiscPanel() + APGBuildGhostPanel() + APGBuildLagPanel() + APGBuildStackPanel() + APGBuildToolHackPanel() +end + +net.Receive( "apg_menu_s2c", openMenu ) + +local function showNotice() + local level = tonumber(net.ReadUInt(3)) + local msg = tostring(net.ReadString()) + + octolib.notify.show(level == 0 and 'rp' or level == 1 and 'ooc' or level == 2 and 'warning', msg) + surface.PlaySound(level == 1 and "common/wpn_denyselect.wav" or level == 2 and "common/wpn_denyselect.wav" or "buttons/button15.wav") + + MsgC(level == 0 and Color(0,255,0) or Color(255,191,0), "[APG] ", Color(255,255,255), msg,"\n") +end + +net.Receive( "apg_notice_s2c", showNotice ) + +properties.Add( "apgoptions", { + MenuLabel = "APG Options", -- Name to display on the context menu + Order = 9999, -- The order to display this property relative to other properties + MenuIcon = "icon16/fire.png", -- The icon to display next to the property + + Filter = function( self, ent, ply ) -- A function that determines whether an entity is valid for this property + if not ply:IsSuperAdmin() then return false end + return (ent.GetClass and ent:GetClass() and IsValid(ent) and ent:EntIndex() > 0) + end, + MenuOpen = function( self, option, ent, tr ) + local submenu = option:AddSubMenu() + local function addoption(str, data) + local menu = submenu:AddOption(str, data.callback) + + if data.icon then + menu:SetImage( data.icon ) + end + + return menu + end + + addoption( "Sleep entities of this Class", { + icon = "icon16/clock.png", + callback = function() self:APGcmd(ent, "sleepclass") end, + }) + + addoption( "Freeze entities of this Class", { + icon = "icon16/bell_delete.png", + callback = function() self:APGcmd(ent, "freezeclass") end, + }) + + submenu:AddSpacer() + + addoption( "Cleanup Owner - Unfrozens", { + icon = "icon16/cog_delete.png", + callback = function() self:APGcmd(ent, "clearunfrozen") end, + }) + + addoption( "Cleanup Owner", { + icon = "icon16/bin_closed.png", + callback = function() self:APGcmd(ent, "clearowner") end, + }) + + submenu:AddSpacer() + + addoption( "Get Owner SteamID", { + icon = "icon16/user.png", + callback = function() self:APGcmd(ent, "getownerid") end, + }) + + addoption( "Get Owner Entity Count", { + icon = "icon16/brick.png", + callback = function() self:APGcmd(ent, "getownercount") end, + }) + + submenu:AddSpacer() + + addoption( "Add this entity class to the Ghosting List", { + icon = "icon16/cross.png", + callback = function() self:APGcmd(ent, "addghost") end, + }) + + addoption( "Remove this entity class from the Ghosting List", { + icon = "icon16/tick.png", + callback = function() self:APGcmd(ent, "remghost") end, + }) + end, + Action = function( self, ent ) end, + APGcmd = function(self, ent, cmd) + if cmd == "getownerid" then + local owner, _ = ent:CPPIGetOwner() + if IsValid(owner) and owner.SteamID then + local id = tostring(owner:SteamID()) + SetClipboardText(id) + chat.AddText(Color(0,255,0), "\n\""..id.."\" has been copied to your clipboard.\n") + else + chat.AddText(Color(255,0,0), "\nOops, that's not a Player!\n") + end + elseif IsValid(ent) and ent.EntIndex then + net.Start("apg_context_c2s") + net.WriteString(cmd) + net.WriteEntity(ent) + net.SendToServer() + end + end, +}) diff --git a/garrysmod/addons/util-apg/lua/apg/cl_utils.lua b/garrysmod/addons/util-apg/lua/apg/cl_utils.lua new file mode 100644 index 0000000..6eb72f3 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/cl_utils.lua @@ -0,0 +1,200 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + +]]-------------------------------------------- + +surface.CreateFont( "APG_title_font", { + font = "Arial", + size = 14, + weight = 700, +} ) + +surface.CreateFont( "APG_title2_font", { + font = "Arial", + size = 13, + weight = 700, +} ) + +surface.CreateFont( "APG_sideBar_font", { + font = "Arial", + size = 18, + weight = 1500, +} ) + +surface.CreateFont( "APG_mainPanel_font", { + font = "Arial", + size = 19, + weight = 8500, +} ) + +surface.CreateFont( "APG_tick_font", { + font = "Arial", + size = 29, + weight = 1900, +} ) + +surface.CreateFont( "APG_element_font", { + font = "Arial", + size = 17, + weight = 1300, +} ) + +surface.CreateFont( "APG_element2_font", { + font = "Arial", + size = 17, + weight = 2900, +} ) + +local utils = {} + +function utils.addBadEntity( class ) + local found = false + for k, v in pairs ( ents.GetAll() ) do + if class == v:GetClass() then + found = true + break + end + end + if not found then + for k in pairs (scripted_ents.GetList()) do + if class == k then + found = true + break + end + end + end + APG.cfg["bad_ents"].value[ class ] = found +end + +function utils.getNiceName( str ) + local nName = string.gsub(str,"^%l",string.upper) + nName = string.gsub(nName,"_", " " ) + return nName +end + +function utils.mainSwitch( x, y, on ) + draw.RoundedBox(10,x,y,45,18,Color( 58, 58, 58, 255)) + if on then + draw.RoundedBox(10,x+1,y+1,45-2,18-2,Color( 11,70,30, 255)) + draw.DrawText( "ON", "APG_title_font",x+8, y+2, Color( 189, 189, 189 ), 3 ) + draw.RoundedBox(10,x+27,y,18,18,Color( 88, 88, 88, 255)) + else + --draw.RoundedBox(10,x,y,45,18,Color( 110, 28, 38, 255)) + draw.RoundedBox(10,x+1,y+1,43,16,Color( 34, 34, 34, 255)) + draw.DrawText( "OFF", "APG_title_font",x+21, y+2, Color( 189, 189, 189), 3 ) + draw.RoundedBox(10,x,y,18,18,Color( 88, 88, 88, 255)) + end + --draw.RoundedBox(0,x+20,y,1,18,Color( 88, 88, 88, 255)) +end + +function utils.switch( panel, x, y, w, h, text, var ) + local button = vgui.Create("DButton",panel) + button:SetPos(x,y) + button:SetSize(w,h) + button:SetText("") + button.Paint = function(slf, w, h) + local enabled = APG.cfg[ var ].value + draw.RoundedBox(0,0,h*0.95,w-5,1, Color(250, 250, 250,1)) + draw.DrawText( text, "APG_element2_font",0, 0, Color( 189, 189, 189), 3 ) + utils.mainSwitch( w-45, 0, enabled ) + end + button.DoClick = function() + APG.cfg[ var ].value = not APG.cfg[ var ].value + end +end + +function utils.numSlider( panel, x, y, w, h, text, var, min, max, decimal ) + local slider = vgui.Create( "DNumSlider", panel ) + slider:SetPos( x, y ) // Set the position + slider:SetSize( w, h ) // Set the size + slider:SetText( "" ) // Set the text above the slider + slider:SetMin( min ) // Set the minimum number you can slide to + slider:SetMax( max ) // Set the maximum number you can slide to + slider:SetDecimals( decimal ) // Decimal places - zero for whole number + slider:SetValue( APG.cfg[var].value ) + slider.OnValueChanged = function( self, newValue ) + APG.cfg[var].value = newValue + end + slider.Paint = function(slf, w, h) + draw.RoundedBox(0,0,h*0.97,w-5,1, Color(250, 250, 250,1)) + draw.DrawText( text, "APG_element2_font",0, 0, Color( 189, 189, 189), 3 ) + end + slider.Slider.Paint = function( slf, w, h) + draw.RoundedBox(0,8,9-1,w-16,1+2, Color(250, 250, 250,1)) + end + slider.Slider.Knob.Paint = function(slf, w, h) + draw.RoundedBox(6,0,4,10,10,Color( 11,70,30, 255)) + end + + slider.Slider:Dock( NODOCK ) + slider.Slider:SetPos( 300, 0 ) + slider.Slider:SetWide( 100 ) + + slider.TextArea:Dock( NODOCK ) + slider.TextArea:SetPos( 265, - 3 ) + slider.TextArea.m_colText = Color(189, 189, 189) + slider.TextArea.Paint = function( self, w, h) + draw.RoundedBox(10,0,1,w-15,h,Color( 58, 58, 58, 255)) + derma.SkinHook( "Paint", "TextEntry", self, w, h ) + end +end + +function utils.textEntry( panel, x, y, w, h, text, var ) + local label = vgui.Create( "DLabel", panel ) + label:SetPos( x, y ) + label:SetSize( w, h ) + label:SetText( text ) + label:SetFont("APG_element2_font") + label:SetColor( Color( 189, 189, 189) ) + label.Paint = function(self, w, h) + draw.RoundedBox(0,0,h*0.97,w,1, Color(250, 250, 250,1)) + end + local txtEntry = vgui.Create( "DTextEntry", panel ) -- create the form as a child of frame + txtEntry:SetPos( x + 267, y-1 ) + txtEntry:SetSize( 125, 20 ) + txtEntry:SetText( "custom" ) + txtEntry.OnEnter = function( self ) + end + +end + +function utils.comboBox(panel, x, y, w, h, text, var, content) + local label = vgui.Create( "DLabel", panel ) + label:SetPos( x, y ) + label:SetSize( w, h ) + label:SetText( text ) + label:SetFont("APG_element2_font") + label:SetColor( Color( 189, 189, 189) ) + label.Paint = function(self, w, h) + draw.RoundedBox(0,0,h*0.97,w,1, Color(250, 250, 250,1)) + end + local comboBox = vgui.Create( "DComboBox", panel ) + comboBox:SetPos( x + 267, y-2 ) + comboBox:SetSize( 125, 20 ) + comboBox:SetValue( APG.cfg[var].value ) + for k, v in pairs ( content ) do + comboBox:AddChoice(v) + end + comboBox.OnSelect = function( panel, index, value ) + APG.cfg[var].value = value + end + comboBox.Paint = function(i, w, h) + draw.RoundedBox(0,0,0,w,h,Color(58, 58, 58, 240)) + end + comboBox:SetTextColor(Color( 189, 189, 189)) + local o_OpenMenu = comboBox.OpenMenu + comboBox.OpenMenu = function( pControlOpener ) + o_OpenMenu(pControlOpener) + comboBox.Menu.Paint = function (i,w,h) + draw.RoundedBox(0,0,0,w,h,Color(58, 58, 58, 240)) + end + end +end + +return utils \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/apg/modules/ghosting.lua b/garrysmod/addons/util-apg/lua/apg/modules/ghosting.lua new file mode 100644 index 0000000..559769a --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/modules/ghosting.lua @@ -0,0 +1,345 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + ============================ + GHOSTING/UNGHOSTING MODULE + ============================ + + Developper informations : + --------------------------------- + Used variables : + ghost_color = { value = Color(34, 34, 34, 220), desc = "Color set on ghosted props" } + bad_ents = { + value = { + ["prop_physics"] = true, + ["wire_"] = false, + ["gmod_"] = false }, + desc = "Entities to ghost/control/secure"} + alwaysFrozen = { value = false, desc = "Set to true to auto freeze props on physgun drop" } + +]]-------------------------------------------- +local mod = "ghosting" + +local disabledClasses = { + gmod_sent_vehicle_fphysics_base = true, + gmod_sent_vehicle_fphysics_wheel = true, + gmod_sent_vehicle_fphysics_attachment = true, +} + +--[[------------------------------------------ + Override base functions +]]-------------------------------------------- +local ENT = FindMetaTable( "Entity" ) +APG.oSetColGroup = APG.oSetColGroup or ENT.SetCollisionGroup +function ENT:SetCollisionGroup( group ) + if not disabledClasses[self:GetClass()] and APG.isBadEnt( self ) and APG.getOwner( self ) then + if group == COLLISION_GROUP_NONE then + if not self.APG_Frozen then + group = COLLISION_GROUP_INTERACTIVE + end +--[[ elseif group == COLLISION_GROUP_INTERACTIVE and APG.isTrap( self ) then + group = COLLISION_GROUP_DEBRIS_TRIGGER --]] + end + end + return APG.oSetColGroup( self, group ) +end + +local PhysObj = FindMetaTable("PhysObj") +APG.oEnableMotion = APG.oEnableMotion or PhysObj.EnableMotion +function PhysObj:EnableMotion( bool ) + local sent = self:GetEntity() + if not disabledClasses[sent:GetClass()] and APG.isBadEnt( sent ) and APG.getOwner( sent ) then + sent.APG_Frozen = not bool + if not sent.APG_Frozen then + sent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + end + end + return APG.oEnableMotion( self, bool) +end + +function APG.isTrap( ent, output ) + local check = false + local center = ent:LocalToWorld(ent:OBBCenter()) + local bRadius = ent:BoundingRadius() + local cache = {} + + if not IsValid(ent:GetParent()) then + for _,v in next, ents.FindInSphere(center, bRadius) do + if (v:IsPlayer() and v:Alive() and not v:IsGhost()) then + local pos = v:GetPos() + local trace = { start = pos, endpos = pos, filter = v } + local tr = util.TraceEntity( trace, v ) + + if tr.Entity == ent then + if output then + table.insert(cache, v) + else + check = v + end + end + elseif v:IsVehicle() then + -- Check if the distance between the spheres centers is less than the sum of their radius. + local vCenter = v:LocalToWorld(v:OBBCenter()) + if center:Distance( vCenter ) < v:BoundingRadius() then + check = v + end + end + + if check then break end + end + end + + if output then + return istable(cache) and cache or {} + end + + return check or false +end + +function APG.entGhost( ent, enforce, noCollide ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if ent.jailWall or ent.apgIgnore then return end + if disabledClasses[ent:GetClass()] then return end + + if not ent.APG_Ghosted then + ent.FPPAntiSpamIsGhosted = nil -- Override FPP Ghosting. + + DropEntityIfHeld(ent) + ent:ForcePlayerDrop() + + ent.APG_oColGroup = ent:GetCollisionGroup() + + if not enforce then + -- If and old collision group was set get it. + if ent.OldCollisionGroup then ent.APG_oColGroup = ent.OldCollisionGroup end -- For FPP + if ent.DPP_oldCollision then ent.APG_oColGroup = ent.DPP_oldCollision end -- For DPP + + ent.OldCollisionGroup = nil + ent.DPP_oldCollision = nil + end + + ent.APG_Ghosted = true + + timer.Simple(0, function() + if not IsValid(ent) then return end + + if not ent.APG_oldColor then + ent.APG_oldColor = ent:GetColor() + if not enforce then + if ent.OldColor then ent.APG_oldColor = ent.OldColor end -- For FPP + if ent.__DPPColor then ent.APG_oldColor = ent.__DPPColor end -- For DPP + + ent.OldColor = nil + ent.__DPPColor = nil + end + end + + ent:SetColor( APG.cfg["ghost_color"].value ) + if noCollide then + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + else + ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER ) + end + end) + + -- ent:SetRenderMode(RENDERMODE_TRANSALPHA) + ent:DrawShadow(false) + + if noCollide then + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + else + ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER ) + end + end +end + +function APG.entUnGhost( ent, ply ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if ent.APG_HeldBy and #ent.APG_HeldBy > 1 then return end + if disabledClasses[ent:GetClass()] then return end + + if ent.APG_Ghosted != false and not ent.APG_Picked then + ent.APG_isTrap = APG.isTrap(ent) + if not ent.APG_isTrap then + ent.APG_Ghosted = false + ent:DrawShadow(true) + ent:SetColor( ent.APG_oldColor or Color(255,255,255,255)) + ent.APG_oldColor = false + + local newColGroup = COLLISION_GROUP_INTERACTIVE + if ent.APG_oColGroup == COLLISION_GROUP_WORLD then + newColGroup = ent.APG_oColGroup + elseif ent.APG_Frozen then + newColGroup = COLLISION_GROUP_NONE + end + ent:SetCollisionGroup( newColGroup ) + else + APG.notify("There is something in this prop!", ply, 1) + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + end + end + ent.APG_Busy = nil +end + +function APG.ConstrainApply( ent, callback ) + local constrained = constraint.GetAllConstrainedEntities(ent) + for _,v in next, constrained do + if IsValid(v) and v != ent then + callback( v ) + end + end +end + +--[[------------------------------------------ + Hooks/Timers +]]-------------------------------------------- + +APG.hookRegister( mod, "PhysgunPickup","APG_makeGhost",function(ply, ent) + if ent.APG_Busy then return false end + if not APG.canPhysGun( ent, ply ) then return end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if disabledClasses[ent:GetClass()] then return end + ent.APG_Picked = true + + APG.entGhost(ent, false, true) + + APG.ConstrainApply( ent, function( _ent ) + if not _ent.APG_Frozen then + _ent.APG_Picked = true + APG.entGhost( _ent, false, true ) + end + end) -- Apply ghost to all constrained ents +end) + +APG.hookRegister( mod, "PlayerUnfrozeObject", "APG_unFreezeInteract", function (ply, ent, object) + if not APG.canPhysGun( ent, ply ) then return end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if disabledClasses[ent:GetClass()] then return end + if APG.cfg["alwaysFrozen"].value then return false end -- Do not unfreeze if Always Frozen is enabled ! + if ent:GetCollisionGroup( ) != COLLISION_GROUP_WORLD then + ent:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE ) + end +end) + +APG.dJobRegister( "unghost", 0.1, 50, function( ent ) + if not IsValid( ent ) then return end + APG.entUnGhost( ent ) +end) + +APG.hookRegister( mod, "PhysgunDrop", "APG_pGunDropUnghost", function( ply, ent ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if disabledClasses[ent:GetClass()] then return end + + local changed = true + local getallconst = constraint.GetAllConstrainedEntities( ent ) + if getallconst then + for k,_ent in pairs(getallconst) do + if _ent.APG_Picked and _ent ~= ent then + changed = false + end + end + end + + if changed then + ent.APG_Picked = false + + if APG.cfg["alwaysFrozen"].value then + APG.freezeIt( ent ) + end + APG.entUnGhost( ent, ply ) + APG.ConstrainApply( ent, function( _ent ) + _ent.APG_Picked = false + APG.startDJob( "unghost", _ent ) + end) -- Apply unghost to all constrained ents + end +end) + +local function fixCollision(ent) + if not IsValid( ent ) then return end + if disabledClasses[ent:GetClass()] then + ent:SetCustomCollisionCheck(true) + ent:CollisionRulesChanged() + return + end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + + --timer.Simple(.1, function() + APG.entGhost( ent, false, true ) + --end) + ent.APG_Busy = true + + timer.Simple(0, function() + local owner = APG.getOwner( ent ) + + if IsValid( owner ) and owner:IsPlayer() then + local pObj = ent:GetPhysicsObject() + if IsValid(pObj) and APG.cfg["alwaysFrozen"].value then + ent.APG_Frozen = true + pObj:EnableMotion(false) + elseif IsValid(pObj) and pObj:IsMoveable() then + ent.APG_Frozen = false + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + end + end + + APG.startDJob( "unghost", ent ) + end) +end +APG.hookRegister(mod, "PlayerSpawnedEffect", "APG_noColOnCreate", function(_, _, ent) fixCollision(ent) end) +APG.hookRegister(mod, "PlayerSpawnedSENT", "APG_noColOnCreate", function(_, ent) fixCollision(ent) end) +APG.hookRegister(mod, "PlayerSpawnedProp", "APG_noColOnCreate", function(_, _, ent) fixCollision(ent) end) +APG.hookRegister(mod, "PlayerSpawnedProp", "APG_freeze", function(_, _, ent) + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end +end) + +local BlockedProperties = {"collision", "persist", "editentity", "drive", "ignite", "statue"} +APG.hookRegister(mod, "CanProperty", "APG_canProperty", function(ply, prop, ent) + local prop = tostring(prop) + if( table.HasValue(BlockedProperties,prop) and ent.APG_Ghosted ) then + APG.log("Cannot set "..prop.." properties on ghosted entities!", ply) + return false + end +end) + +-- Custom Hooks -- + +APG.hookRegister(mod, "APG.FadingDoorToggle", "APG_FadingDoor", function(ent, isFading) + if not disabledClasses[ent:GetClass()] and APG.isBadEnt(ent) and APG.cfg["FadingDoorGhosting"].value then + local ply = APG.getOwner( ent ) + if IsValid(ply) then + if not isFading then + local find = APG.isTrap(ent, true) + for _,v in next, find do + if v.IsPlayer and v:IsPlayer() then + local dir = v:GetForward(); dir.z = 0 + v:SetCollisionGroup(COLLISION_GROUP_PUSHAWAY) + v:SetAbsVelocity((dir * 600) + Vector(0,0,60)) + timer.Simple(1, function() + v:SetCollisionGroup(COLLISION_GROUP_PLAYER) + end) + end + end + elseif ent.APG_Ghosted then + APG.startDJob( "unghost", ent ) + end + end + end +end) + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- +for k, v in next, APG[mod]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) +end + +for k, v in next, APG[mod]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) +end diff --git a/garrysmod/addons/util-apg/lua/apg/modules/lag_detection.lua b/garrysmod/addons/util-apg/lua/apg/modules/lag_detection.lua new file mode 100644 index 0000000..506895c --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/modules/lag_detection.lua @@ -0,0 +1,158 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + + ============================ + LAG DETECTION MODULE + ============================ + + Developper informations : + --------------------------------- + Used variables : + lagTrigger = { value = 75, desc = "% difference between current lag and average lag."} + lagsCount = { value = 8, desc = "Number of consectuives laggy frames in order to run a cleanup."} + bigLag = { value = 2, desc = "Time (seconds) between 2 frames to trigger a cleanup"} + lagFunc = { value = "cleanUp_unfrozen", desc = "Function ran on lag detected" } + lagFuncTime = { value = 20, desc = "Time (seconds) between 2 cleanup (avoid spam)"} + + Ready to hook : + APG_lagDetected = Ran on lag detected by the server. + Example : hook.Add( "APG_lagDetected", "myLagDetectHook", function() print("[APG] Lag detected (printed from my very own hook)") end) + +]]-------------------------------------------- +local mod = "lag_detection" + +--[[-------------------- + Lag fixing functions +]]---------------------- + +local lagFix = { + cleanup_all = function( notify ) APG.cleanUp( "all", notify ) end, + cleanup_unfrozen = function( notify ) APG.cleanUp( "unfrozen", notify ) end, + ghost_unfrozen = APG.ghostThemAll, + freeze_unfrozen = APG.freezeProps, + smart_cleanup = APG.smartCleanup, + custom_function = APG.customFunc, +} + +--[[-------------------- + Utils +]]---------------------- +function APG.process( tab ) + local sum = 0 + local max = 0 + for k, v in pairs( tab ) do + sum = sum + v + if v > max then + max = v + end + end + return sum / (#tab) , max +end + +hook.Remove("APG_lagDetected", "APG_lagDetected_id") -- Sometimes, I dream about cheese. +hook.Add("APG_lagDetected", "APG_lagDetected_id", function() + if not APG then return end + local func = APG.cfg["lagFunc"].value + local notify = APG.cfg["lagFuncNotify"].value + if not lagFix[ func ] then return end + lagFix[ func ]( notify ) +end) + +--[[-------------------- + To replace in UI +]]---------------------- +concommand.Add( "APG_showLag", function(ply, cmd, arg) + if IsValid(ply) and not ply:IsAdmin() then return end + local lastShow = SysTime() + local values = {} + local time = arg[1] or 30 + APG.log("[APG] Processing : please wait " .. time .. " seconds", ply ) + hook.Add("Think","APG_showLag",function() + local curTime = SysTime() + local diff = curTime - lastShow + table.insert(values, diff) + lastShow = curTime + end) + timer.Simple( time , function() + hook.Remove("Think","APG_showLag") + local avg, max = APG.process( values ) + values = {} + APG.log("[APG] Avg : " .. avg .. " | Max : " .. max, ply ) + end) +end) + +--[[-------------------- + Lag detection vars +]]---------------------- +local trigValue = 10 +local tickTable = {} +local delta, curAvg, lagCount = 0, 0, 0 + + +local pause = false +local lastThink = SysTime() + +function APG.resetLag() + trigValue = 10 + tickTable = {} + delta, curAvg, lagCount = 0, 0, 0 + pause = false + lastThink = SysTime() +end + +APG.timerRegister(mod, "APG_process", 5, 0, function() + if not APG.modules[ mod ] then return end + + if #tickTable < 12 or delta < trigValue then -- save every values the first minute + table.insert(tickTable, delta) + if #tickTable > 60 then + table.remove(tickTable, 1) -- it will take 300 seconds to fullfill the table. + end + + curAvg = APG.process( tickTable ) + trigValue = curAvg * ( 1 + APG.cfg["lagTrigger"].value / 100 ) + end +end) + +APG.hookRegister( mod, "Think", "APG_detectLag", function() + if not APG.modules[ mod ] then return end + + local curTime = SysTime() + delta = curTime - lastThink + if delta >= trigValue then + lagCount = lagCount + 1 + if (lagCount >= APG.cfg["lagsCount"].value) or ( delta > APG.cfg["bigLag"].value ) then + lagCount = 0 + if not pause then + pause = true + timer.Simple( APG.cfg["lagFuncTime"].value, function() pause = false end) + + local msg = "WARNING LAG DETECTED : Running lag fix function!" + APG.notify(msg, "all", 2) + + hook.Run( "APG_lagDetected" ) + end + end + else + lagCount = lagCount > 0 and (lagCount - 0.5) or 0 + end + lastThink = curTime +end) + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- +if APG.resetLag then APG.resetLag() end +for k, v in next, APG[mod]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) +end + +for k, v in next, APG[mod]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) +end \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/apg/modules/misc.lua b/garrysmod/addons/util-apg/lua/apg/modules/misc.lua new file mode 100644 index 0000000..469f602 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/modules/misc.lua @@ -0,0 +1,93 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + + ============================ + MISCELLANEOUS MODULE + ============================ + + Developper informations : + --------------------------------- + Used variables : + vehDamage = { value = true, desc = "True to enable vehicles damages, false to disable." } + vehNoCollide = { value = false, desc = "True to disable collisions between vehicles and players"} + autoFreeze = { value = false, desc = "Freeze every unfrozen prop each X seconds" } + autoFreezeTime = { value = 120, desc = "Auto freeze timer (seconds)"} + +]]-------------------------------------------- +local mod = "misc" + +--[[-------------------- + Vehicle damage +]]---------------------- +local vehClasses = octolib.array.toKeys {'prop_vehicle_jeep', 'gmod_sent_vehicle_fphysics_base', 'gmod_sent_vehicle_fphysics_wheel '} +local function isVehDamage(dmg,atk,ent) + if not IsValid(ent) then return false end + if dmg:GetDamageType() == DMG_VEHICLE and (ent:IsVehicle() or vehClasses[ent:GetClass()]) then + return true + end + return APG.FindWAC(ent) -- Detect WAC Vehicles. +end + +--[[-------------------- + No Collide vehicles on spawn +]]---------------------- +APG.hookRegister(mod,"OnEntityCreated","APG_noCollideVeh",function(ent) + timer.Simple(0.03, function() + if APG.cfg["vehNoCollide"].value and (ent:IsVehicle() or APG.FindWAC(ent)) then + ent:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + end + end) +end) + +--[[-------------------- + Disable prop damage +]]---------------------- +APG.hookRegister(mod, "EntityTakeDamage","APG_noPropDmg",function(target, dmg) + local atk, ent = dmg:GetAttacker(), dmg:GetInflictor() + if not APG.cfg["AllowPK"].value then + if not APG.cfg["vehDamage"].value and isVehDamage(dmg,atk,ent) then return end + -- if APG.isBadEnt( ent ) or dmg:GetDamageType() == DMG_CRUSH then + local dmgType = dmg:GetDamageType() + if dmgType == DMG_CRUSH or dmgType == DMG_CRUSH + DMG_SLASH then + dmg:SetDamage(0) + dmg:ScaleDamage(0) + return true -- Returning true overrides and blocks all related damage, it also prevents the hook from running any further preventing unintentional damage from other addons. + end + end +end) + +--[[-------------------- + Block Physgun Reload +]]---------------------- +APG.hookRegister(mod, "OnPhysgunReload", "APG_blockPhysgunReload", function(_, ply) + if APG.cfg["blockPhysgunReload"].value then + -- APG.notify("Physgun Reloading is Currently Disabled", ply, 1) + return false + end +end) + +--[[-------------------- + Auto prop freeze +]]---------------------- +APG.timerRegister( mod, "APG_autoFreeze", APG.cfg["autoFreezeTime"].value, 0, function() + if APG.cfg["autoFreeze"].value then + APG.freezeProps( true ) + end +end) + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- +for k, v in next, APG[mod]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) +end + +for k, v in next, APG[mod]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) +end \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/apg/modules/misc2.lua b/garrysmod/addons/util-apg/lua/apg/modules/misc2.lua new file mode 100644 index 0000000..2f3fcf1 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/modules/misc2.lua @@ -0,0 +1,158 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + + ============================ + STACK DETECTION MODULE + ============================ + + Developper informations : + --------------------------------- + Used variables : + stackMax = { value = 20, desc = "Max amount of entities stacked on a small area"} + stackArea = { value = 15, desc = "Sphere radius for stack detection (gmod units)"} + +]]-------------------------------------------- +local mod = "misc2" + +local function hookAdd(call, key, func) + APG.hookRegister(mod, call, key, func) +end + +local function timerMake(id, delay, times, func) + APG.timerRegister(mod, id, delay, times, func) +end + +hookAdd("CanTool", "APG_canTool", function(ply, tr, tool) + if APG.cfg["thFadingDoors"].value and tool == "fading_door" then + if IsValid(tr.Entity) and not tr.Entity:IsPlayer() then + local ent = tr.Entity + timer.Simple(0, function() + if not IsValid(ent) then return end + if not ent.isFadingDoor then return end + + local state = ent.fadeActive + + if state then + ent:fadeDeactivate() + end + + ent.oldFadeActivate = ent.oldFadeActivate or ent.fadeActivate + function ent:fadeActivate() + if hook.Run("APG.FadingDoorToggle", self, true) then return end + ent:oldFadeActivate() + end + + ent.oldFadeDeactivate = ent.oldFadeDeactivate or ent.fadeDeactivate + function ent:fadeDeactivate() + if hook.Run("APG.FadingDoorToggle", self, false) then return end + ent:oldFadeDeactivate() + end + + if state then + ent:fadeActivate() + end + end) + end + end +end) + +--[[ FRZR9K ]]-- + +local zero = Vector(0,0,0) +local pstop = FrameTime()*3 + +local function getphys(ent) + local phys = IsValid(ent) and ent.GetPhysicsObject and ent:GetPhysicsObject() or false + return IsValid(phys) and phys or false +end + +timerMake("frzr9k", pstop, 0, function() + if APG.cfg["frzr9k"].value then + for _,v in next, ents.GetAll() do + local phys = getphys(v) + if IsValid(phys) and phys:IsMotionEnabled() and not v:IsPlayerHolding() then + local vel = v:GetVelocity() + if vel:Distance(zero) <= 23 then + phys:Sleep() + end + end + end + end +end) + +-- Collision Monitoring -- +local function collcall(ent, data) + local hit = data.HitObject + local mep = data.PhysObject + if IsValid(hit) and IsValid(mep) then + ent._collisions = (ent._collisions or 0) + 1 + if ent._collisions > 23 then + ent._collisions = 0 + mep:SetVelocityInstantaneous(Vector(0,0,0)) + hit:SetVelocityInstantaneous(Vector(0,0,0)) + mep:Sleep() + hit:Sleep() + end + end +end + +hookAdd("OnEntityCreated", "frzr9k", function(ent) + if APG.cfg["frzr9k"].value then + timer.Simple(0.01, function() + if IsValid(ent) and ent.GetPhysicsObject and IsValid(ent:GetPhysicsObject()) then + ent:AddCallback("frzr9k", collcall) + end + end) + end +end) + +-- Requires Fading Door Hooks -- +hookAdd("APG.FadingDoorToggle", "frzr9k", function(ent, faded) + if APG.cfg["frzr9k"].value and IsValid(ent) then + if faded then + local o = APG.getOwner(ent) + local pos = ent:GetPos() + local notify = false + + local doors = {} + local count = 0 + + for _,v in next, ents.FindInSphere(pos, 3) do + if v ~= ent and IsValid(v) and v.isFadingDoor and APG.getOwner(v) == o then + table.insert(doors, v) + count = count + 1 + end + end + + if count > 2 then + for _,v in next, doors do + v:Remove() + end + notify = true + end + else + timer.Simple(0, function() + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS) + local phys = getphys(ent) + phys:EnableMotion(false) + end) + end + end +end) + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- +for k, v in next, APG[mod]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) +end + +for k, v in next, APG[mod]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) +end \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/apg/modules/stack_detection.lua b/garrysmod/addons/util-apg/lua/apg/modules/stack_detection.lua new file mode 100644 index 0000000..ed1b233 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/modules/stack_detection.lua @@ -0,0 +1,85 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + + ============================ + STACK DETECTION MODULE + ============================ + + Developper informations : + --------------------------------- + Used variables : + stackMax = { value = 20, desc = "Max amount of entities stacked on a small area"} + stackArea = { value = 15, desc = "Sphere radius for stack detection (gmod units)"} + +]]-------------------------------------------- +local mod = "stack_detection" + +function APG.checkStack( ent, pcount ) + if not APG.isBadEnt( ent ) then return end + + local efound = ents.FindInSphere(ent:GetPos(), APG.cfg["stackArea"].value ) + local count = 0 + local max_count = APG.cfg["stackMax"].value + for k, v in pairs (efound) do + if APG.isBadEnt( v ) and APG.getOwner( v ) then + count = count + 1 + end + end + if count >= (pcount or max_count) then + local owner, _ = ent:CPPIGetOwner() + ent:Remove() + if not owner.APG_CantPickup then + APG.blockPickup( owner ) + APG.notify("Do not try to crash the server!", ply, 1) + + local msg = owner:Nick().." ["..owner:SteamID().."]" .. " tried to unfreeze a stack of props!" + APG.notify(msg, "admins", 2) + end + end +end + +APG.hookRegister(mod, "PhysgunPickup","APG_stackCheck",function(ply, ent) + if not APG.canPhysGun( ent, ply ) then return end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + APG.checkStack( ent ) +end) + +--[[-------------------- + Stacker Exploit Quick Fix +]]---------------------- +hook.Add( "InitPostEntity", "APG_InitStackFix", function() + timer.Simple(60, function() + local TOOL = weapons.GetStored("gmod_tool")["Tool"][ "stacker" ] or weapons.GetStored("gmod_tool")["Tool"][ "stacker_v2" ] + if not TOOL then return end + + -- Stacker improved (beta) fixed this by setting a maximum number of constraints + -- See : https://git.io/vPvJK + + APG.dJobRegister( "weld", 0.3, 20, function( sents ) + if not IsValid( sents[1] ) or not IsValid( sents[2]) then return end + constraint.Weld( sents[1], sents[2], 0, 0, 0 ) + end) + + function TOOL:ApplyWeld( lastEnt, newEnt ) + if ( not self:ShouldForceWeld() and not self:ShouldApplyWeld() ) then return end + APG.startDJob( "weld", {lastEnt, newEnt} ) + end + end) +end) + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- +for k, v in next, APG[mod]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) +end + +for k, v in next, APG[mod]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) +end \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/apg/modules/tool_hacks.lua b/garrysmod/addons/util-apg/lua/apg/modules/tool_hacks.lua new file mode 100644 index 0000000..b5beb97 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/modules/tool_hacks.lua @@ -0,0 +1,66 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.1.2) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + + ============================ + STACK DETECTION MODULE + ============================ + + Developper informations : + --------------------------------- + Used variables : + stackMax = { value = 20, desc = "Max amount of entities stacked on a small area"} + stackArea = { value = 15, desc = "Sphere radius for stack detection (gmod units)"} + +]]-------------------------------------------- +local mod = "tool_hacks" + +APG.hookRegister(mod, "CanTool", "APG_canTool", function(ply, tr, tool) + if tool == "fading_door" then + if IsValid(tr.Entity) and not tr.Entity:IsPlayer() then + local ent = tr.Entity + timer.Simple(0, function() + if not IsValid(ent) then return end + if not ent.isFadingDoor then return end + + local state = ent.fadeActive + + if state then + ent:fadeDeactivate() + end + + ent.oldFadeActivate = ent.oldFadeActivate or ent.fadeActivate + function ent:fadeActivate() + if hook.Run("APG.FadingDoorToggle", self, true) then return end + ent:oldFadeActivate() + end + + ent.oldFadeDeactivate = ent.oldFadeDeactivate or ent.fadeDeactivate + function ent:fadeDeactivate() + if hook.Run("APG.FadingDoorToggle", self, false) then return end + ent:oldFadeDeactivate() + end + + if state then + ent:fadeActivate() + end + end) + end + end +end) + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- +for k, v in next, APG[mod]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) +end + +for k, v in next, APG[mod]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) +end \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/apg/sv_apg.lua b/garrysmod/addons/util-apg/lua/apg/sv_apg.lua new file mode 100644 index 0000000..c2a2afb --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/sv_apg.lua @@ -0,0 +1,452 @@ +--[[------------------------------------------ + + A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0) + Made by : + - While True (http://steamcommunity.com/id/76561197972967270) + - LuaTenshi (http://steamcommunity.com/id/76561198096713277) + + Licensed to : http://steamcommunity.com/id/76561198136465722 + +]]-------------------------------------------- + +util.AddNetworkString("apg_notice_s2c") +APG = APG or {} + +local IsValid = IsValid +local table = table +local isentity = isentity + +--[[------------------------------------------ + ENTITY Related +]]-------------------------------------------- + +function APG.canPhysGun( ent, ply ) + if not IsValid(ent) then return false end -- The entity isn't valid, don't pickup. + if ply.APG_CantPickup then return false end -- Is APG blocking the pickup? + if ent.CPPICanPhysgun then return ent:CPPICanPhysgun(ply) end -- Let CPPI handle things from here. + + return (not ent.PhysgunDisabled) -- By default everything can be picked up, unless it is PhysgunDisabled. +end + +function APG.isBadEnt( ent ) + if not IsValid(ent) then return false end + if ent.jailWall == true then return false end + if ent.IsWeapon and ent:IsWeapon() then return false end + + local h = hook.Run("APGisBadEnt", ent) + if isbool(h) then return h end + + local class = ent:GetClass() + for k, v in pairs (APG.cfg["bad_ents"].value) do + if ( v and k == class ) or (not v and string.find( class, k) ) then + return true + end + end + + return false +end + +function APG.getOwner( ent ) + local owner, _ = ent:CPPIGetOwner() or ent.FPPOwner or nil + return owner +end + +function APG.killVelocity(ent, extend, freeze, wake_target) + local vec = Vector() + + if ent.GetClass and ent:GetClass() == "player" then ent:SetVelocity(ent:GetVelocity()*-1) return end + if ent:IsWorld() then return end + + ent:SetVelocity(vec) + + local function killvel(phys, freeze) + if not IsValid(phys) then return end + if freeze then phys:EnableMotion(false) return end + + local collision = phys:IsCollisionEnabled() + + phys:EnableCollisions(false) + + phys:SetVelocity(vec) + phys:SetVelocityInstantaneous(vec) + phys:AddAngleVelocity(phys:GetAngleVelocity()*-1) + + phys:EnableCollisions(collision) + + phys:Sleep() + phys:RecheckCollisionFilter() + end + + for i = 0, ent:GetPhysicsObjectCount() do killvel(ent:GetPhysicsObjectNum(i), freeze) end -- Includes self? + + if extend then + for _,v in next, constraint.GetAllConstrainedEntities(ent) do killvel(v:GetPhysicsObject(), freeze) end + end + + if wake_target then + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + end + + ent:CollisionRulesChanged() +end + +function APG.FindWAC(ent) -- Note: Add a config to disable this check. + if not IsValid(ent) then return false end + if not APG.cfg["vehIncludeWAC"].value then return false end + + local e + local i = 0 + if ent.wac_seatswitch or ent.wac_ignore then return true end + for _,v in next, constraint.GetAllConstrainedEntities(ent) do + if v.wac_seatswitch or v.wac_ignore then e = v break end + if i > 12 then break end -- Only check up to 12. + i = i + 1 + end + + return IsValid(e) +end + +function APG.cleanUp( mode, notify, specific ) + local mode = mode or "unfrozen" + for _, v in next, specific or ents.GetAll() do + APG.killVelocity(v,false) + if not APG.isBadEnt(v) or not APG.getOwner( v ) or v:GetParent():IsVehicle() or APG.FindWAC(v) then continue end + if mode == "unfrozen" and v.APG_Frozen then -- Wether to clean only not frozen ents or all ents + continue + else + v:Remove() + end + end +end + +function APG.ghostThemAll( notify ) + for _, v in next, ents.GetAll() do + if not APG.isBadEnt(v) or not APG.getOwner( v ) or v:GetParent():IsVehicle() or v.APG_Frozen then continue end + APG.entGhost( v, false, true ) + end +end + +function APG.freezeIt( ent ) + local pObj = ent:GetPhysicsObject() + if IsValid(pObj) then + pObj:EnableMotion( false) + ent.APG_Frozen = true + end +end + +function APG.freezeProps( notify ) + for _, v in next, ents.GetAll() do + if not APG.isBadEnt(v) or not APG.getOwner( v ) then continue end + APG.freezeIt( v ) + end +end + +local function GetPhysenv() + local env = physenv.GetPerformanceSettings() + local con = {} + local vars = { + "phys_upimpactforcescale", + "phys_impactforcescale", + "phys_pushscale", + "sv_turbophysics", + } + + for _,v in next, vars do + local var = GetConVar(v) + con[v] = var and var:GetString() or nil + end + + return {con = con, env = env} +end + +function APG.smartCleanup( notify ) + local defaults = GetPhysenv() + local phys = table.Copy(defaults.env) + + hook.Add("PlayerSpawnObject", "APG_smartCleanup", function() return false end) + + RunConsoleCommand("phys_upimpactforcescale","0") + RunConsoleCommand("phys_impactforcescale", "0") + RunConsoleCommand("phys_pushscale", "0") + RunConsoleCommand("sv_turbophysics", "1") + + phys.MaxCollisionChecksPerTimestep = 0 + phys.MaxAngularVelocity = 0 + phys.MaxVelocity = 0 + physenv.SetPerformanceSettings(phys) + + local sphere = ents.FindInSphere + local all = ents.GetAll() + local bad = {} + + for _, v in next, all do + if IsValid(v) and v.GetPhysicsObject then + local phys = v:GetPhysicsObject() + if IsValid(phys) and phys:IsMotionEnabled() then + if v.isFadingDoor and APG.isBadEnt(ent) then + SafeRemoveEntity(v) + else + table.insert(bad, {ent = v, phys = phys}) + end + end + end + end + + APG.freezeProps( notify ) + + for _, v in next, bad do + local count = 0 + + local owner = APG.getOwner(v.ent) + local space = sphere(v.ent:GetPos(), 7) + local cache = {} + + for _, ent in next, space do + if owner == APG.getOwner(ent) then + count = count + 1 + table.insert(cache, ent) + end + end + + if count > 4 then + for _, ent in next, cache do + if APG.isBadEnt(ent) then + SafeRemoveEntity(ent) + end + end + end + end + + timer.Simple(1.5, function() -- Give a few seconds for the engine to catch up. + for k,v in next, defaults.con do + RunConsoleCommand(k, v) + end + physenv.SetPerformanceSettings(defaults.env) + hook.Remove("PlayerSpawnObject", "APG_smartCleanup") + end) +end + +function APG.ForcePlayerDrop(ply, ent) + ply:ConCommand("-attack") + timer.Simple(0.1, function() + ent:ForcePlayerDrop() + end) +end + +function APG.blockPickup( ply ) + if not IsValid(ply) or ply.APG_CantPickup then return end + ply.APG_CantPickup = true + timer.Simple(10, function() + if IsValid(ply) then + ply.APG_CantPickup = false + end + end) +end + +function APG.notify(msg, targets, level, log) -- The most advanced notify function in the world. + local logged = false + + local msg = string.Trim(tostring(msg)) + local level = level or 0 + + if type(level) == "string" then + level = string.lower(level) + level = level == "notice" and 0 or level == "warning" and 1 or level == "alert" and 2 + end + + if isentity(targets) and IsValid(targets) and targets:GetClass() == "player" then + targets = {targets} + elseif type(targets) ~= "table" then -- Convert to a table. + targets = string.lower(tostring(targets)) + if targets == "1" or targets == "superadmins" then + local new_targets = {} + for _,v in next, player.GetHumans() do + if not IsValid(v) then continue end + if not (v:IsSuperAdmin()) then continue end + table.insert(new_targets,v) + end + targets = new_targets + elseif targets == "2" or targets == "admins" then + local new_targets = {} + for _,v in next, player.GetHumans() do + if not IsValid(v) then continue end + if not (v:IsAdmin() or v:IsSuperAdmin()) then continue end + table.insert(new_targets,v) + end + targets = new_targets + elseif targets == "0" or targets == "all" or targets == "everyone" then + targets = player.GetHumans() + end + end + + msg = (string.Trim(msg or "") ~= "") and msg or nil + + if msg and (log or level >= 2) then + ServerLog("[APG] ",msg.."\n") + end + + if type(targets) ~= "table" then return false end + + for _,v in next, targets do + if not IsValid(v) then continue end + net.Start("apg_notice_s2c") + net.WriteUInt(level,3) + net.WriteString(msg) + net.Send(v) + end + + return true +end + +--[[------------------------------------------ + Entity pickup part +]]-------------------------------------------- + +hook.Add("PhysgunPickup","APG_PhysgunPickup", function(ply, ent) + if not APG.isBadEnt( ent ) then return end + if not APG.canPhysGun( ent, ply ) then return false end + + ent.APG_Picked = true + ent.APG_Frozen = false + + if ent.APG_HeldBy and ent.APG_HeldBy.plys and not ent.APG_HeldBy.plys[sid] then + local HasHolder = istable(ent.APG_HeldBy.plys) and (table.Count(ent.APG_HeldBy.plys) > 0) + local HeldByLast = ent.APG_HeldBy.last + + if HasHolder then + if HeldByLast and (ply:IsAdmin() or ply:IsSuperAdmin()) then + ent:ForcePlayerDrop() + for _,v in next, ent.APG_HeldBy.plys do + APG.ForcePlayerDrop(v, ent) + end + else + return false + end + end + end + + ent.APG_HeldBy = (ent.APG_HeldBy and istable(ent.APG_HeldBy.plys)) and ent.APG_HeldBy or {plys={}} + ent.APG_HeldBy.plys[ply:SteamID()] = ply + ent.APG_HeldBy.last = {ply = ply, id = ply:SteamID()} + + ply.APG_CurrentlyHolding = ent +end) + +--[[-------------------- + No Collide (between them) on props unfreezed +]]---------------------- +hook.Add("PlayerUnfrozeObject", "APG_PlayerUnfrozeObject", function(ply, ent, object) + if not APG.isBadEnt( ent ) then return end + ent.APG_Frozen = false +end) + +--[[------------------------------------------ + Entity drop part +]]-------------------------------------------- + +--[[-------------------- + PhysGun Drop and Anti Throw Props +]]---------------------- +hook.Add( "PhysgunDrop", "APG_physGunDrop", function( ply, ent ) + ent.APG_HeldBy = ent.APG_HeldBy or {} + + if ent.APG_HeldBy.plys then + ent.APG_HeldBy.plys[ply:SteamID()] = nil -- Remove the holder. + end + + ply.APG_CurrentlyHolding = nil + + if #ent.APG_HeldBy > 0 then return end + ent.APG_Picked = false + + if APG.isBadEnt( ent ) and not APG.cfg["AllowPK"].value then + APG.killVelocity(ent,true,false,true) -- Extend to constrained props, and wake target. + end +end) + +--[[-------------------- + Physgun Drop & Freeze +]]---------------------- +hook.Add( "OnPhysgunFreeze", "APG_OnPhysgunFreeze", function( weap, phys, ent, ply ) + if not APG.isBadEnt( ent ) then return end + ent.APG_Frozen = true +end) + + +--[[-------------------- + Admin utility +]]---------------------- + +function APG.log(msg, ply) + if type(ply) ~= "string" and IsValid(ply) then + ply:PrintMessage(3, msg.."\n") + else + print(msg) + end +end + +--[[-------------------- + APG job manager +--]]---------------------- +local toProcess = toProcess or {} + +function APG.dJobRegister( job, delay, limit, func, onBegin, onEnd ) + local tab = { + content = {}, + delay = delay, + limit = limit, + func = func, + onBegin = onBegin or nil, + onEnd = onEnd or nil + } + toProcess[job] = tab +end + +local function APG_delayedTick( job ) + if toProcess[job].processing and toProcess[job].processing == true then return end + toProcess[job].processing = true + if toProcess[job].onBegin then toProcess[job].onBegin() end + local delay, pLimit = toProcess[job].delay, toProcess[job].limit + local total = #toProcess[job].content + local count = math.Clamp(total,0,pLimit) + for i = 1, count do + local cur = toProcess[job].content[1] + timer.Create( "delay_" .. job .. "_" .. i , ( i - 1 ) * delay , 1, function() + toProcess[job].func( cur ) + end) + table.remove(toProcess[job].content, 1) + end + timer.Create("dJob_" .. job .. "_process", ( count * delay ) + 0.1 , 1, function() toProcess[job].processing = false + if #toProcess[job].content < 1 and toProcess[job].onEnd then toProcess[job].onEnd() end + end) +end + +function APG.startDJob( job, content ) + if not job or not isstring(job) or not content then return end + if not toProcess or not toProcess[job] then + ErrorNoHalt("[APG] No Process Found, Attempting Reload!\n---\nThis Shouldn't Happen Concider Restarting!\n") + APG.reload() + return + end + + if table.HasValue(toProcess[job].content, content) then return end + + -- Is it a problem if there is a same ent being unghosted twice ? + table.insert( toProcess[job].content, content ) + hook.Add("Tick", "APG_delayed_" .. job, function() + if #toProcess[job].content > 0 then + APG_delayedTick( job ) + else + hook.Remove("Tick", "APG_delayed_" .. job) + end + end) +end + +hook.Add("PostGamemodeLoaded", "APG_Load", function() + timer.Simple(0, function() -- Make sure we load last! + APG.reload() + end) +end) diff --git a/garrysmod/addons/util-apg/lua/apg/sv_menu.lua b/garrysmod/addons/util-apg/lua/apg/sv_menu.lua new file mode 100644 index 0000000..88e5d93 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/apg/sv_menu.lua @@ -0,0 +1,139 @@ +util.AddNetworkString("apg_settings_c2s") +util.AddNetworkString("apg_menu_s2c") +util.AddNetworkString("apg_context_c2s") + +local function saveSettings( json ) + if not file.Exists("apg", "DATA") then file.CreateDir( "apg" ) end + file.Write("apg/settings.txt", json) +end + +local function recSettings( len, ply) + if not ply:IsSuperAdmin() then return end + + len = net.ReadUInt( 32 ) + if len == 0 then return end + + local settings = net.ReadData( len ) + settings = util.Decompress( settings ) + + saveSettings( settings ) + + settings = util.JSONToTable( settings ) + APG.cfg = settings.cfg + + table.Merge(APG, settings) + APG.reload() +end +net.Receive( "apg_settings_c2s", recSettings) + +local function sendToClient( ply ) + local settings = {} + settings.cfg = APG.cfg or {} + settings.modules = APG.modules or {} + + settings = util.TableToJSON( settings ) + settings = util.Compress( settings ) + net.Start("apg_menu_s2c") + net.WriteUInt( settings:len(), 32 ) -- Write the length of the data + net.WriteData( settings, settings:len() ) -- Write the data + net.Send(ply) +end + +hook.Add( "PlayerSay", "openAPGmenu", function( ply, text, public ) + text = string.lower( text ) + if ply:IsSuperAdmin() and text == "!apg" then + sendToClient( ply ) + return "" + end +end) + +local function checkOwner(owner, ply) + if ( IsValid(owner) and owner:IsPlayer() ) then + return true + else + APG.notify("The owner of this entity is NOT a Player. (Owner: " .. type(owner) .. ")", ply) + return false + end +end + +local function contextCMD(_,ply) + if not ply:IsSuperAdmin() then return end + + local cmd = net.ReadString() + local ent = net.ReadEntity() + + ent = IsValid(ent) and ent or ply:GetEyeTraceNoCursor().Entity or nil + + local class = IsValid(ent) and ent.GetClass and ent:GetClass() or nil + if not class then return end + + local owner = APG.getOwner(ent) + + if cmd == "addghost" then + if not APG.cfg.bad_ents.value[class] then + APG.cfg.bad_ents.value[class] = true + APG.notify("\""..class.."\" added to Ghost List!", ply) + else + APG.notify("This class is already listed!", ply) + end + elseif cmd == "remghost" then + APG.cfg.bad_ents.value[class] = nil + APG.notify("\""..class.."\" removed from the Ghost List!", ply) + elseif cmd == "clearowner" then + if not checkOwner(owner, ply) then return end + cleanup.CC_Cleanup(owner,"gmod_cleanup",{}) + elseif cmd == "clearunfrozen" then + if not checkOwner(owner, ply) then return end + + local count = 0 + for _,v in next, ents.GetAll() do + if not (IsValid(v) and APG.getOwner(v) == owner) then continue end + if not APG.isBadEnt(v) then continue end + if not v.APG_Frozen then + SafeRemoveEntity(v) + count = count + 1 + end + end + + APG.notify(tostring(count).." entities have been removed!", ply) + elseif cmd == "getownercount" then + if not checkOwner(owner, ply) then return end + + local count = 0 + for _,v in next, ents.GetAll() do + if IsValid(v) and APG.getOwner(v) == owner then + count = count + 1 + end + end + + APG.notify(tostring(owner:Nick()).." has "..count..(count == 1 and " entity." or " entities."), ply) + elseif cmd == "freezeclass" then + local count = 0 + for _,v in next, ents.FindByClass(class) do + if IsValid(v) and not v.APG_Frozen then + count = count + 1 + APG.killVelocity(v, false, true, false) + end + end + APG.notify((count or 0)..(count == 1 and " Entity" or " Entities").." Frozen", ply) + elseif cmd == "sleepclass" then + local count = 0 + for _,v in next, ents.FindByClass(class) do + if IsValid(v) and not v.APG_Frozen then + count = count + 1 + APG.killVelocity(v, false, false, false) + end + end + APG.notify((count or 0)..(count == 1 and " Entity is" or " Entities are").." now Sleeping", ply) + end + + if cmd == "addghost" or cmd == "remghost" then + local settings = {} + settings.cfg = APG.cfg or {} + settings.modules = APG.modules or {} + + saveSettings( util.TableToJSON( settings ) ) + APG.reload() + end +end +net.Receive("apg_context_c2s", contextCMD) diff --git a/garrysmod/addons/util-apg/lua/autorun/client/cl_apg_init.lua b/garrysmod/addons/util-apg/lua/autorun/client/cl_apg_init.lua new file mode 100644 index 0000000..bf15111 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/autorun/client/cl_apg_init.lua @@ -0,0 +1,3 @@ +APG = {} +include( "config/apg.lua" ) +include( "apg/cl_menu.lua" ) diff --git a/garrysmod/addons/util-apg/lua/autorun/server/sv_apg_init.lua b/garrysmod/addons/util-apg/lua/autorun/server/sv_apg_init.lua new file mode 100644 index 0000000..8fef019 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/autorun/server/sv_apg_init.lua @@ -0,0 +1,105 @@ +--[[------------------------------------------ + INITIALIZE APG +]]-------------------------------------------- +APG = {} +APG.modules = APG.modules or {} +--[[------------------------------------------ + CLIENT related +]]-------------------------------------------- +AddCSLuaFile("config/apg.lua") +AddCSLuaFile("apg/cl_utils.lua") +AddCSLuaFile("apg/cl_menu.lua") + +--[[------------------------------------------ + REGISTER Modules +]]-------------------------------------------- +local modules, _ = file.Find("apg/modules/*.lua","LUA") +for _,v in next, modules do + if v then + niceName = string.gsub(tostring(v),"%.lua","") + APG.modules[ niceName ] = false + APG[ niceName ] = { hooks = {}, timers = {}} + end +end + +function APG.hookRegister( module, event, identifier, func ) + table.insert( APG[ module ][ "hooks"], { event = event, identifier = identifier, func = func }) +end + +function APG.timerRegister( module, identifier, delay, repetitions, func ) + table.insert( APG[ module ][ "timers"], { identifier = identifier, delay = delay, repetitions = repetitions, func = func } ) +end + +function APG.load( module ) + APG.unLoad( module ) + APG.modules[ module ] = true + include( "apg/modules/" .. module .. ".lua" ) +end + +function APG.unLoad( module ) + APG.modules[module] = false + + if not (istable(APG[module]) and next(APG[module])) then return end + + local hooks = APG[ module ]["hooks"] + for k, v in next, hooks do + hook.Remove(v.event, v.identifier) + end + + local timers = APG[ module ]["timers"] + for k, v in next, timers do + timer.Remove(v.identifier) + end +end + +function APG.reload( ) + for k, v in next, APG.modules do + if APG.modules[k] == true then + APG.load( k ) + else + APG.unLoad( k ) + end + end +end +--[[------------------------------------------ + LOADING +]]-------------------------------------------- +-- Loading config first +include( "config/apg.lua" ) +-- Loading APG main functions +include( "apg/sv_apg.lua") -- Modules loaded at the bottom +-- Loading APG menu +include( "apg/sv_menu.lua" ) + +--[[------------------------------------------ + CVars INIT +]]-------------------------------------------- + +concommand.Add("apg_set", function( ply, cmd, args, argStr ) + if not ply:IsSuperAdmin() then return end + + if args[1] == "module" then + local _module = APG.modules[ args[2] ] + if _module != nil then + if _module == true then + APG.unLoad( args[2] ) + APG.log( "[APG] Module " .. args[2] .. " disabled.", ply) + else + APG.load( args[2] ) + APG.log( "[APG] Module " .. args[2] .. " enabled.", ply) + end + else + APG.log( "[APG] This module does not exist", ply) + end + + elseif args[1] == "help" then + local cfg = APG.cfg[ args[2] ] + if cfg then + APG.log( cfg.desc, ply) + else + APG.log( "[APG] Help: This setting does not exist", ply) + end + else + APG.log( ply, "Error: unknown setting") + end +end) \ No newline at end of file diff --git a/garrysmod/addons/util-apg/lua/autorun/sh_apg.lua b/garrysmod/addons/util-apg/lua/autorun/sh_apg.lua new file mode 100644 index 0000000..ff08b91 --- /dev/null +++ b/garrysmod/addons/util-apg/lua/autorun/sh_apg.lua @@ -0,0 +1,2 @@ +-- ULX Admin Commands Coming Soon! +--- PLanned Commands for Prop Management, and Server Cleanup. diff --git a/garrysmod/addons/util-dlib/lua/autorun/!sh_dlib.lua b/garrysmod/addons/util-dlib/lua/autorun/!sh_dlib.lua new file mode 100644 index 0000000..b4506ee --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/autorun/!sh_dlib.lua @@ -0,0 +1,37 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +_G.DLib = _G.DLib or {} + +local function load() + if SERVER then + AddCSLuaFile('dlib/cl_init.lua') + AddCSLuaFile('dlib/sh_init.lua') + include('dlib/sh_init.lua') + include('dlib/sv_init.lua') + else + include('dlib/sh_init.lua') + include('dlib/cl_init.lua') + end +end + +concommand.Add('dlib_restart', load) +load() diff --git a/garrysmod/addons/util-dlib/lua/dlib/autorun/.gitkeep b/garrysmod/addons/util-dlib/lua/dlib/autorun/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/garrysmod/addons/util-dlib/lua/dlib/autorun/client/.gitkeep b/garrysmod/addons/util-dlib/lua/dlib/autorun/client/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/garrysmod/addons/util-dlib/lua/dlib/autorun/server/.gitkeep b/garrysmod/addons/util-dlib/lua/dlib/autorun/server/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/garrysmod/addons/util-dlib/lua/dlib/cl_init.lua b/garrysmod/addons/util-dlib/lua/dlib/cl_init.lua new file mode 100644 index 0000000..2268af6 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/cl_init.lua @@ -0,0 +1,79 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 MsgC = MsgC +local SysTime = SysTime +local timeStart = SysTime() + +MsgC('[DLib] Initializing DLib clientside ... ') + +-- DLib.Loader.start('Notify', true) +-- DLib.Loader.include('dlib/modules/notify/client/cl_init.lua') +-- DLib.Loader.finish(false) + +function DLib.GetSkin() + return 'DLib_Black' +end + +-- DLib.Loader.start('HUDCommons') +-- DLib.Loader.loadPureCSTop('dlib/modules/hudcommons') +-- DLib.simpleInclude('modules/hudcommons/base/init.lua') +-- DLib.Loader.finish() + +DLib.Loader.loadPureCS('dlib/vgui') +DLib.simpleInclude('util/client/scrsize.lua') +DLib.simpleInclude('util/client/chat.lua') +DLib.simpleInclude('util/client/buystuff.lua') +DLib.simpleInclude('util/client/ttfreader.lua') +DLib.simpleInclude('util/client/matnotify.lua') +DLib.simpleInclude('util/client/blur.lua') + +DLib.Loader.loadPureCSTop('dlib/modules/client') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) +timeStart = SysTime() +MsgC('[DLib] Running addons ... \n') + +if not VLL_CURR_FILE and not VLL2_FILEDEF then + DLib.Loader.loadPureSHTop('dlib/autorun') + DLib.Loader.loadPureCSTop('dlib/autorun/client') +end + +MsgC(string.format('[DLib] Addons were initialized in %.2f ms\n', (SysTime() - timeStart) * 1000)) + +timeStart = SysTime() +MsgC('[DLib] Loading translations for i18n ... ') + +DLib.i18n.reload() + +concommand.Add('dlib_reload_i18n_cl', function(ply) + timeStart = SysTime() + DLib.Message('Reloading translations for i18n ... ') + DLib.i18n.reload() + hook.Run('DLib.TranslationsReloaded') + DLib.Message(string.format('i18n reload took %.2f ms', (SysTime() - timeStart) * 1000)) +end) + +hook.Run('DLib.TranslationsReloaded') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) + +MsgC('---------------------------------------------------------------\n') diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/astar.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/astar.lua new file mode 100644 index 0000000..006bbb2 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/astar.lua @@ -0,0 +1,539 @@ +local NAV_LOOPS_PER_FRAME = CreateConVar('sv_dlib_nav_loops_per_frame', '50', { + FCVAR_ARCHIVE, + FCVAR_NOTIFY +}, 'A* Searcher iterations per frame') +local NAV_OPEN_LIMIT = CreateConVar('sv_dlib_nav_open_limit', '700', { + FCVAR_REPLICATED, + FCVAR_ARCHIVE, + FCVAR_NOTIFY +}, 'A* Searcher "open" nodes limit (at same time)') +local NAV_LIMIT = CreateConVar('sv_dlib_nav_limit', '4000', { + FCVAR_REPLICATED, + FCVAR_ARCHIVE, + FCVAR_NOTIFY +}, 'A* Searcher total iterations limit') +local FRAME_THERSOLD = CreateConVar('sv_dlib_nav_frame_limit', '5', { + FCVAR_ARCHIVE, + FCVAR_NOTIFY +}, 'A* Searcher time limit (in milliseconds) per calculation per frame') +local TIME_LIMIT = CreateConVar('sv_dlib_nav_time_limit', '2500', { + FCVAR_REPLICATED, + FCVAR_ARCHIVE, + FCVAR_NOTIFY +}, 'A* Searcher total time limit (in milliseconds) per one search') +local AStarNode +do + local _class_0 + local _base_0 = { + SetG = function(self, val) + if val == nil then + val = 0 + end + self.g = val + self.f = self.g + self.h + end, + SetH = function(self, val) + if val == nil then + val = 0 + end + self.h = val + self.f = self.g + self.h + end, + SetFrom = function(self, val) + self.from = val + end, + __tostring = function(self) + return "[DLib:AStarNode:" .. tostring(self.nav) .. "]" + end, + GetG = function(self) + return self.g + end, + GetH = function(self) + return self.h + end, + GetF = function(self) + return self.f + end, + GetPos = function(self) + return self.pos + end, + GetFrom = function(self) + return self.from + end, + HasParent = function(self) + return self.from ~= nil + end, + GetParent = function(self) + return self.from + end, + GetAdjacentAreas = function(self) + return self.nav:GetAdjacentAreas() + end, + Underwater = function(self) + return self.nav:IsUnderwater() + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, nav, g, target) + if g == nil then + g = 0 + end + if target == nil then + target = Vector(0, 0, 0) + end + self.nav = nav + self.pos = self.nav:GetCenter() + self.positions = { + self.pos + } + self.target = target + self.g = g + self.h = target:DistToSqr(self.pos) + self.f = self.g + self.h + end, + __base = _base_0, + __name = "AStarNode" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.NORTH_WEST = 0 + self.NORTH_EAST = 1 + self.SOUTH_EAST = 2 + self.SOUTH_WEST = 3 + self.SIDES = { + self.NORTH_EAST, + self.NORTH_WEST, + self.SOUTH_EAST, + self.SOUTH_WEST + } + AStarNode = _class_0 +end +local AStarTracer +do + local _class_0 + local _base_0 = { + GetOpenNodes = function(self) + return self.opened + end, + GetOpenNodesCount = function(self) + return #self.opened + end, + CopyOpenNodes = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.opened + for _index_0 = 1, #_list_0 do + local node = _list_0[_index_0] + _accum_0[_len_0] = node + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + GetClosedNodes = function(self) + return self.closed + end, + GetClosedNodesCount = function(self) + return #self.closed + end, + CopyClosedNodes = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.closed + for _index_0 = 1, #_list_0 do + local node = _list_0[_index_0] + _accum_0[_len_0] = node + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + GetTotalNodes = function(self) + return self.database + end, + GetTotalNodesCount = function(self) + return #self.database + end, + CopyTotalNodes = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.database + for _index_0 = 1, #_list_0 do + local node = _list_0[_index_0] + _accum_0[_len_0] = node + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + GetIterations = function(self) + return self.iterations + end, + GetTotalIterations = function(self) + return self.iterations + end, + GetCalculationTime = function(self) + return self.totalTime + end, + GetCurrentG = function(self) + return self.currentG + end, + GetLeftDistance = function(self) + return math.max(self.distToEnd - math.sqrt(self.currentG), 0) + end, + GetDistance = function(self) + return self.distToEnd + end, + Distance = function(self) + return self.distToEnd + end, + IsStopped = function(self) + return self.stop + end, + IsWorking = function(self) + return self.working + end, + IsSuccess = function(self) + return self.success + end, + IsFailure = function(self) + return self.failure + end, + IsFinished = function(self) + return self.hasfinished + end, + HasFinished = function(self) + return self.hasfinished + end, + SetSuccessCallback = function(self, val) + if val == nil then + val = (function(self) end) + end + self.callbackSuccess = val + end, + SetFailCallback = function(self, val) + if val == nil then + val = (function(self) end) + end + self.callbackFail = val + end, + SetFailureCallback = function(self, val) + if val == nil then + val = (function(self) end) + end + self.callbackFail = val + end, + SetStopCallback = function(self, val) + if val == nil then + val = (function(self) end) + end + self.callbackStop = val + end, + __tostring = function(self) + return "[DLib:AStarTracer:" .. tostring(self.ID) .. "]" + end, + GetNode = function(self, nav) + local _list_0 = self.database + for _index_0 = 1, #_list_0 do + local data = _list_0[_index_0] + if data.nav == nav then + return data + end + end + end, + AddNode = function(self, node) + return table.insert(self.database, node) + end, + GetPath = function(self) + return self.points + end, + GetPoints = function(self) + return self.points + end, + RecalcPath = function(self) + if not self.hasfinished then + return self.points + end + if self.failure then + return self.points + end + self.points = { + self.endPos + } + local current = self.lastNode + while current do + table.insert(self.points, current:GetPos()) + current = current:GetFrom() + end + table.insert(self.points, self.startPos) + return self.points + end, + Stop = function(self) + if not self.working then + return + end + self.status = self.__class.NAV_STATUS_INTERRUPT + self.working = false + self.stop = true + self.hasfinished = true + hook.Remove('Think', tostring(self)) + return self:callbackStop() + end, + OnSuccess = function(self, node) + self.status = self.__class.NAV_STATUS_SUCCESS + self.lastNode = node + self.working = false + self.success = true + self.hasfinished = true + hook.Remove('Think', tostring(self)) + self:RecalcPath() + return self:callbackSuccess() + end, + OnFailure = function(self, status) + if status == nil then + status = self.__class.NAV_STATUS_GENERIC_FAILURE + end + self.working = false + self.failure = true + self.hasfinished = true + self.status = status + hook.Remove('Think', tostring(self)) + return self:callbackFail(status) + end, + GetStatus = function(self) + return self.status + end, + Start = function(self) + self.lastNodeNav = navmesh.Find(self.endPos, 1, 20, 20)[1] + self.firstNodeNav = navmesh.Find(self.startPos, 1, 20, 20)[1] + self.status = self.__class.NAV_STATUS_WORKING + if not self.lastNodeNav or not self.firstNodeNav then + self:OnFailure(self.__class.NAV_STATUS_FAILURE_NO_OPEN_NODES) + return + end + self.working = true + self.iterations = 0 + self.totalTime = 0 + local newNode = AStarNode(self.firstNodeNav, self.startPos:DistToSqr(self.firstNodeNav:GetCenter()), self.endPos) + self.opened = { + newNode + } + self.database = { + newNode + } + return hook.Add('Think', tostring(self), function() + return self:ThinkHook() + end) + end, + GetNearestNode = function(self) + local current + local min + local index + for i = 1, #self.opened do + local data = self.opened[i] + if not min or data.f < min then + min = data.f + current = data + index = i + end + end + if index then + table.remove(self.opened, index) + end + if current then + table.insert(self.closed, current) + end + return current + end, + ThinkHook = function(self) + local status = xpcall(self.Think, self.__class.OnError, self) + if not status then + return self:OnFailure() + end + end, + Think = function(self) + if not self.working then + hook.Remove('Think', tostring(self)) + return + end + if #self.opened == 0 then + self:OnFailure(self.__class.NAV_STATUS_FAILURE_NO_OPEN_NODES) + return + end + if #self.opened >= self.nodesLimit then + self:OnFailure(self.__class.NAV_STATUS_FAILURE_OPEN_NODES_LIMIT) + return + end + local calculationTime = 0 + for i = 1, self.loopsPerIteration do + local sTime = SysTime() + self.iterations = self.iterations + 1 + if self.hasLimit and self.iterations > self.limit then + self:OnFailure(self.__class.NAV_STATUS_FAILURE_LOOPS_LIMIT) + return + end + local nearest = self:GetNearestNode() + if not nearest then + break + end + if nearest.nav == self.lastNodeNav then + self:OnSuccess(nearest) + return + end + self.currentG = nearest:GetG() + local _list_0 = nearest:GetAdjacentAreas() + for _index_0 = 1, #_list_0 do + local _continue_0 = false + repeat + local node = _list_0[_index_0] + local hitClosed = false + local _list_1 = self.closed + for _index_1 = 1, #_list_1 do + local cl = _list_1[_index_1] + if cl.nav == node then + hitClosed = true + break + end + end + if hitClosed then + _continue_0 = true + break + end + local nodeObject = self:GetNode(node) + if nodeObject then + local dist = nodeObject:GetPos():DistToSqr(nearest:GetPos()) + local lengthMultiplier = 1 + if nodeObject:Underwater() then + lengthMultiplier = lengthMultiplier + 20 + end + local deltaZ = nodeObject:GetPos().z - nearest:GetPos().z + if deltaZ > 0 then + lengthMultiplier = lengthMultiplier + math.max(0, deltaZ) + end + if deltaZ < 0 then + lengthMultiplier = lengthMultiplier + math.max(0, -deltaZ) + end + local distG = nearest:GetG() + dist * lengthMultiplier + if nodeObject:GetG() > distG then + nodeObject:SetG(distG) + nodeObject:SetFrom(nearest) + end + else + nodeObject = AStarNode(node, nearest:GetG() + node:GetCenter():DistToSqr(nearest:GetPos()), self.endPos) + nodeObject:SetFrom(nearest) + self:AddNode(nodeObject) + if node:IsValid() then + table.insert(self.opened, nodeObject) + else + table.insert(self.closed, nodeObject) + end + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + local cTime = (SysTime() - sTime) * 1000 + calculationTime = calculationTime + cTime + self.totalTime = self.totalTime + cTime + if self.totalTime >= self.timeThersold then + self:OnFailure(self.__class.NAV_STATUS_FAILURE_TIME_LIMIT) + return + end + if calculationTime >= self.frameThersold then + break + end + end + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, startPos, endPos, loopsPerIteration, nodesLimit, limit, frameThersold, timeThersold) + if startPos == nil then + startPos = Vector(0, 0, 0) + end + if endPos == nil then + endPos = Vector(0, 0, 0) + end + if loopsPerIteration == nil then + loopsPerIteration = NAV_LOOPS_PER_FRAME:GetInt() + end + if nodesLimit == nil then + nodesLimit = NAV_OPEN_LIMIT:GetInt() + end + if limit == nil then + limit = NAV_LIMIT:GetInt() + end + if frameThersold == nil then + frameThersold = FRAME_THERSOLD:GetFloat() + end + if timeThersold == nil then + timeThersold = TIME_LIMIT:GetFloat() + end + self.ID = self.__class.nextID + self.__class.nextID = self.__class.nextID + 1 + self.working = false + self.hasfinished = false + self.failure = false + self.success = false + self.stop = false + self.opened = { } + self.closed = { } + self.database = { } + self.points = { + startPos, + endPos + } + self.startPos = startPos + self.endPos = endPos + self.distToEnd = endPos:Distance(startPos) + self.loopsPerIteration = loopsPerIteration + self.limit = limit + self.hasLimit = limit ~= 0 + self.frameThersold = frameThersold + self.timeThersold = timeThersold + self.totalTime = 0 + self.iterations = 0 + self.nodesLimit = nodesLimit + self.callbackFail = function(self) end + self.callbackSuccess = function(self) end + self.callbackStop = function(self) end + self.status = self.__class.NAV_STATUS_IDLE + self.currentG = 0 + end, + __base = _base_0, + __name = "AStarTracer" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.nextID = 1 + self.NAV_STATUS_IDLE = -1 + self.NAV_STATUS_SUCCESS = 0 + self.NAV_STATUS_GENERIC_FAILURE = 1 + self.NAV_STATUS_WORKING = 2 + self.NAV_STATUS_FAILURE_TIME_LIMIT = 3 + self.NAV_STATUS_FAILURE_OPEN_NODES_LIMIT = 4 + self.NAV_STATUS_FAILURE_LOOPS_LIMIT = 5 + self.NAV_STATUS_FAILURE_NO_OPEN_NODES = 6 + self.NAV_STATUS_INTERRUPT = 7 + self.OnError = function(err) + print('[DLib AStarTracer ERROR]: ', err) + return print(debug.traceback()) + end + AStarTracer = _class_0 +end +DLib.AStarTracer = AStarTracer +DLib.AStarNode = AStarNode diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/bezier.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/bezier.lua new file mode 100644 index 0000000..f2cdea9 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/bezier.lua @@ -0,0 +1,328 @@ +local Lerp, DLib, table, assert, type +do + local _obj_0 = _G + Lerp, DLib, table, assert, type = _obj_0.Lerp, _obj_0.DLib, _obj_0.table, _obj_0.assert, _obj_0.type +end +DLib.Bezier = { } +do + local _class_0 + local _base_0 = { + AddPoint = function(self, value) + table.insert(self.values, value) + return self + end, + PushPoint = function(self, value) + return self:AddPoint(value) + end, + AddValue = function(self, value) + return self:AddPoint(value) + end, + PushValue = function(self, value) + return self:AddPoint(value) + end, + Add = function(self, value) + return self:AddPoint(value) + end, + Push = function(self, value) + return self:AddPoint(value) + end, + RemovePoint = function(self, i) + return table.remove(self.values, i) + end, + PopPoint = function(self) + return table.remove(self.values) + end, + BezierValues = function(self, t) + return t:tbezier(self.values) + end, + CheckValues = function(self) + return #self.values > 1 + end, + Populate = function(self) + assert(self:CheckValues(), 'at least two values must present') + self.status = true + do + local _accum_0 = { } + local _len_0 = 1 + for t = self.startpos, self.endpos, self.step do + _accum_0[_len_0] = self:BezierValues(t) + _len_0 = _len_0 + 1 + end + self.populated = _accum_0 + end + return self + end, + CopyValues = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.values + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + GetValues = function(self) + return self.values + end, + GetPopulatedValues = function(self) + return self.populated + end, + CopyPopulatedValues = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.populated + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + Lerp = function(self, t, a, b) + return Lerp(t, a, b) + end, + GetValue = function(self, t) + if t == nil then + t = 0 + end + assert(self.status, 'Not populated!') + assert(type(t) == 'number', 'invalid T') + t = t:clamp(0, 1) / self.step + self.startpos + if self.populated[t] then + return self.populated[t] + end + local t2 = t:ceil() + local prevValue = self.populated[t2 - 1] or self.populated[1] + local nextValue = self.populated[t2] or self.populated[2] + return self:Lerp(t % 1, prevValue, nextValue) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, step, startpos, endpos) + if step == nil then + step = 0.05 + end + if startpos == nil then + startpos = 0 + end + if endpos == nil then + endpos = 1 + end + self.values = { } + self.step = step + self.startpos = startpos + self.endpos = endpos + self.populated = { } + self.status = false + end, + __base = _base_0, + __name = "Number" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Bezier.Number = _class_0 +end +local Vector, LerpVector +do + local _obj_0 = _G + Vector, LerpVector = _obj_0.Vector, _obj_0.LerpVector +end +do + local _class_0 + local _parent_0 = DLib.Bezier.Number + local _base_0 = { + CheckValues = function(self) + return #self.valuesX > 1 + end, + GetValues = function(self) + return self.valuesX, self.valuesY, self.valuesZ + end, + CopyValues = function(self) + return (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.valuesX + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.valuesY + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.valuesZ + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() + end, + AddPoint = function(self, value) + table.insert(self.valuesX, value.x) + table.insert(self.valuesY, value.y) + table.insert(self.valuesZ, value.z) + return self + end, + BezierValues = function(self, t) + return Vector(t:tbezier(self.valuesX), t:tbezier(self.valuesY), t:tbezier(self.valuesZ)) + end, + Lerp = function(self, t, a, b) + return LerpVector(t, a, b) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + _class_0.__parent.__init(self, ...) + self.valuesX = { } + self.valuesY = { } + self.valuesZ = { } + end, + __base = _base_0, + __name = "Vector", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.Bezier.Vector = _class_0 +end +local Angle, LerpAngle +do + local _obj_0 = _G + Angle, LerpAngle = _obj_0.Angle, _obj_0.LerpAngle +end +do + local _class_0 + local _parent_0 = DLib.Bezier.Number + local _base_0 = { + CheckValues = function(self) + return #self.valuesP > 1 + end, + GetValues = function(self) + return self.valuesP, self.valuesY, self.valuesR + end, + CopyValues = function(self) + return (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.valuesP + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.valuesY + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.valuesR + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() + end, + AddPoint = function(self, value) + table.insert(self.valuesP, value.p) + table.insert(self.valuesY, value.y) + table.insert(self.valuesR, value.r) + return self + end, + BezierValues = function(self, t) + return Angle(t:tbezier(self.valuesX), t:tbezier(self.valuesY), t:tbezier(self.valuesZ)) + end, + Lerp = function(self, t, a, b) + return LerpAngle(t, a, b) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + _class_0.__parent.__init(self, ...) + self.valuesP = { } + self.valuesY = { } + self.valuesR = { } + end, + __base = _base_0, + __name = "Angle", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.Bezier.Angle = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/camiwatchdog.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/camiwatchdog.lua new file mode 100644 index 0000000..fc5bca7 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/camiwatchdog.lua @@ -0,0 +1,157 @@ +local CAMI = CAMI +local DLib = DLib +local CLIENT = CLIENT +local SERVER = SERVER +local LocalPlayer = LocalPlayer +local pairs = pairs +local ipairs = ipairs +local IsValid = IsValid +local player = player +local timer = timer +local table = table +do + local _class_0 + local _base_0 = { + Track = function(self, ...) + self.tracked:addArray({ + ... + }) + self:TriggerUpdate() + return self + end, + HasPermission = function(self, ply, perm) + if CLIENT and type(ply) == 'string' then + perm = ply + return self.trackedReplies[perm] + else + return self.trackedRepliesPly[ply] and self.trackedRepliesPly[ply][perm] + end + end, + HandlePanel = function(self, perm, pnl) + if SERVER then + return + end + self.trackedPanels[perm] = self.trackedPanels[perm] or { } + table.insert(self.trackedPanels[perm], pnl) + return self + end, + TriggerUpdate = function(self) + if CLIENT then + self:TriggerUpdateClient() + end + return self:TriggerUpdateRegular() + end, + TriggerUpdateClient = function(self) + local ply = LocalPlayer() + if not ply:IsValid() or not ply.UniqueID then + return + end + local _list_0 = self.tracked.values + for _index_0 = 1, #_list_0 do + local perm = _list_0[_index_0] + local status = ProtectedCall(function() + return CAMI.PlayerHasAccess(ply, perm, function(has, reason) + if has == nil then + has = false + end + if reason == nil then + reason = '' + end + local old = self.trackedReplies[perm] + self.trackedReplies[perm] = has + if old ~= has and self.trackedPanels[perm] then + local cleanup = { } + for k, v in ipairs(self.trackedPanels[perm]) do + if IsValid(v) then + v:SetEnabled(has) + else + table.insert(cleanup, k) + end + end + return table.removeValues(self.trackedPanels[perm], cleanup) + end + end) + end) + if not status then + DLib.MessageError('Error while getting permissions for ' .. self.idetifier .. '! Tell Admin mod (if problem is on its side)/Author of addon which use CAMIWatchdog') + DLib.MessageError('Permission in question: ' .. perm) + end + end + end, + TriggerUpdateRegular = function(self) + do + local _tbl_0 = { } + for ply, data in pairs(self.trackedRepliesPly) do + if ply:IsValid() then + _tbl_0[ply] = data + end + end + self.trackedRepliesPly = _tbl_0 + end + local _list_0 = player.GetAll() + for _index_0 = 1, #_list_0 do + local ply = _list_0[_index_0] + self.trackedRepliesPly[ply] = self.trackedRepliesPly[ply] or { } + local _list_1 = self.tracked.values + for _index_1 = 1, #_list_1 do + local perm = _list_1[_index_1] + local status = ProtectedCall(function() + return CAMI.PlayerHasAccess(ply, perm, function(has, reason) + if has == nil then + has = false + end + if reason == nil then + reason = '' + end + if IsValid(ply) then + self.trackedRepliesPly[ply][perm] = has + end + end) + end) + if not status then + DLib.Message('Error while getting permissions for ' .. self.idetifier .. '! Tell Admin mod (if problem is on its side)/Author of addon which use CAMIWatchdog') + DLib.Message('Permission in question: ' .. perm) + end + end + end + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, idetifier, repeatSpeed, ...) + if repeatSpeed == nil then + repeatSpeed = 10 + end + if not idetifier then + error('No idetifier!') + end + self.repeatSpeed = repeatSpeed + self.idetifier = idetifier + self.tracked = DLib.Set() + if CLIENT then + self.trackedReplies = { } + end + if CLIENT then + self.trackedPanels = { } + end + self.trackedRepliesPly = { } + self:Track(...) + timer.Create('DLib.CAMIWatchdog.' .. self.idetifier, repeatSpeed, 0, function() + return self:TriggerUpdate() + end) + return self:TriggerUpdate() + end, + __base = _base_0, + __name = "CAMIWatchdog" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.CAMIWatchdog = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/collector.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/collector.lua new file mode 100644 index 0000000..1411cf2 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/collector.lua @@ -0,0 +1,201 @@ +do + local _class_0 + local _base_0 = { + Add = function(self, ...) + return self:add(...) + end, + Rebuild = function(self, ...) + return self:rebuild(...) + end, + Update = function(self, ...) + return self:update(...) + end, + Calculate = function(self, ...) + return self:calculate(...) + end, + SetSteps = function(self, ...) + return self:setSteps(...) + end, + SetTimeout = function(self, ...) + return self:setTimeout(...) + end, + SetDefault = function(self, ...) + return self:setDefault(...) + end, + add = function(self, val, update) + if val == nil then + val = self.def + end + if update == nil then + update = true + end + local time = CurTimeL() + self.values[self.nextvalue] = { + val, + time + } + self.nextvalue = self.nextvalue + 1 + if self.nextvalue > self.steps then + self.nextvalue = 1 + end + if update then + return self:update() + end + end, + rebuild = function(self) + local time = CurTimeL() + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, self.steps do + _accum_0[_len_0] = { + self.def, + time + } + _len_0 = _len_0 + 1 + end + self.values = _accum_0 + end + end, + update = function(self) + local time = CurTimeL() + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.values + for _index_0 = 1, #_list_0 do + local valData = _list_0[_index_0] + if valData[2] + self.timeout < time then + _accum_0[_len_0] = { + self.def, + time + } + else + _accum_0[_len_0] = valData + end + _len_0 = _len_0 + 1 + end + self.values = _accum_0 + end + end, + calculate = function(self) + local output = 0 + local _list_0 = self.values + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + output = output + val[1] + end + return output + end, + think = function(self) + return self:update() + end, + Think = function(self) + return self:update() + end, + Update = function(self) + return self:update() + end, + setSteps = function(self, steps) + if steps == nil then + steps = self.steps + end + self.steps = steps + self.nextvalue = 1 + return self:rebuild() + end, + setTimeout = function(self, timeout) + if timeout == nil then + timeout = self.timeout + end + self.timeout = timeout + return self:rebuild() + end, + setDefault = function(self, def) + if def == nil then + def = self.def + end + self.def = def + return self:rebuild() + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, steps, timeout, def) + if steps == nil then + steps = 100 + end + if timeout == nil then + timeout = 1 + end + if def == nil then + def = 0 + end + self.steps = steps + self.nextvalue = 1 + self.timeout = timeout + self.def = def + return self:rebuild() + end, + __base = _base_0, + __name = "Collector" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Collector = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.Collector + local _base_0 = { + calculate = function(self) + local average = 0 + local values = self.steps + local _list_0 = self.values + for _index_0 = 1, #_list_0 do + local val = _list_0[_index_0] + average = average + val[1] + end + return average / values + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "Average", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.Average = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/cvars.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/cvars.lua new file mode 100644 index 0000000..1d98998 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/cvars.lua @@ -0,0 +1,153 @@ +local messaging = { } +DLib.MessageMaker(messaging, 'DLib/Message') +do + local _class_0 + local _base_0 = { + create = function(self, name, defvalue, flags, desc) + if defvalue == nil then + defvalue = '0' + end + if flags == nil then + flags = 0 + end + if desc == nil then + desc = '' + end + if name == 'set' then + error('_set is reserved') + end + flags = DLib.util.composeEnums(flags, FCVAR_ARCHIVE, FCVAR_REPLICATED) + self.convars[name] = CreateConVar('sv_' .. self.namespace .. '_' .. name, defvalue, flags, desc) + self.help[name] = desc + self.defaults[name] = defvalue + return self.convars[name] + end, + set = function(self, name, ...) + if not self.convars[name] then + return false + end + if CLIENT then + RunConsoleCommand(self.setname, name, ...) + else + RunConsoleCommand('sv_' .. self.namespace .. '_' .. name, ...) + end + return true + end, + get = function(self, name) + return self.convars[name] + end, + clickfunc = function(self, name) + return function(pnl, newVal) + if type(newVal) == 'boolean' then + return self:set(name, newVal and '1' or '0') + else + return self:set(name, newVal) + end + end + end, + checkbox = function(self, pnlTarget, name) + do + local _with_0 = pnlTarget:CheckBox(self.help[name], 'sv_' .. self.namespace .. '_' .. name) + local cvar = self.convars[name] + _with_0.Button.Think = function() + return _with_0:SetChecked(self:getBool(name)) + end + _with_0.Button.DoClick = function() + return self:set(name, not cvar:GetBool() and '1' or '0') + end + return _with_0 + end + end, + checkboxes = function(self, pnlTarget) + local output + do + local _accum_0 = { } + local _len_0 = 1 + for name, cvar in pairs(self.convars) do + _accum_0[_len_0] = self:checkbox(pnlTarget, name) + _len_0 = _len_0 + 1 + end + output = _accum_0 + end + return output + end, + getBool = function(self, name, ifFail) + if ifFail == nil then + ifFail = false + end + if not self.convars[name] then + return ifFail + end + return self.convars[name].GetBool(self.convars[name], ifFail) + end, + getInt = function(self, name, ifFail) + if ifFail == nil then + ifFail = 0 + end + if not self.convars[name] then + return ifFail + end + return self.convars[name].GetInt(self.convars[name], ifFail) + end, + getFloat = function(self, name, ifFail) + if ifFail == nil then + ifFail = 0 + end + if not self.convars[name] then + return ifFail + end + return self.convars[name].GetFloat(self.convars[name], ifFail) + end, + getString = function(self, name) + if not self.convars[name] then + return '' + end + return self.convars[name].GetString(self.convars[name]) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, namespace) + if not namespace then + error('No namespace!') + end + self.namespace = namespace + self.convars = { } + self.help = { } + self.defaults = { } + self.setname = self.namespace .. '_set' + self.cami = false + if SERVER then + return concommand.Add(self.setname, function(ply, cmd, args) + local name = args[1] or '' + name = name:Trim() + if IsValid(ply) and not ply:IsSuperAdmin() then + return messaging.MessagePlayer(ply, 'No access!') + end + if not self.convars[name] then + return messaging.MessagePlayer(ply, 'Invalid Console Variable - sv_' .. self.namespace .. '_' .. name) + end + if not args[2] then + return messaging.MessagePlayer(ply, 'Value is missing') + end + table.remove(args, 1) + local newval = table.concat(args, ' ') + self:set(name, newval) + return messaging.Message(ply, ' has changed sv_' .. self.namespace .. '_' .. name .. ' to ', newval) + end) + end + end, + __base = _base_0, + __name = "Convars" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Convars = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/dmginfo.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/dmginfo.lua new file mode 100644 index 0000000..884b8f3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/dmginfo.lua @@ -0,0 +1,332 @@ +local damageTypes = { + { + DMG_CRUSH, + 'Crush' + }, + { + DMG_BULLET, + 'Bullet' + }, + { + DMG_SLASH, + 'Slash' + }, + { + DMG_SLASH, + 'Slashing' + }, + { + DMG_BURN, + 'Burn' + }, + { + DMG_BURN, + 'Fire' + }, + { + DMG_BURN, + 'Flame' + }, + { + DMG_VEHICLE, + 'Vehicle' + }, + { + DMG_FALL, + 'Fall' + }, + { + DMG_BLAST, + 'Blast' + }, + { + DMG_CLUB, + 'Club' + }, + { + DMG_SHOCK, + 'Shock' + }, + { + DMG_SONIC, + 'Sonic' + }, + { + DMG_ENERGYBEAM, + 'EnergyBeam' + }, + { + DMG_ENERGYBEAM, + 'Laser' + }, + { + DMG_DROWN, + 'Drown' + }, + { + DMG_PARALYZE, + 'Paralyze' + }, + { + DMG_NERVEGAS, + 'Gaseous' + }, + { + DMG_NERVEGAS, + 'NergeGas' + }, + { + DMG_NERVEGAS, + 'Gas' + }, + { + DMG_POISON, + 'Poision' + }, + { + DMG_ACID, + 'Acid' + }, + { + DMG_AIRBOAT, + 'Airboat' + }, + { + DMG_BUCKSHOT, + 'Buckshot' + }, + { + DMG_DIRECT, + 'Direct' + }, + { + DMG_DISSOLVE, + 'Dissolve' + }, + { + DMG_DROWNRECOVER, + 'DrownRecover' + }, + { + DMG_PHYSGUN, + 'Physgun' + }, + { + DMG_PLASMA, + 'Plasma' + }, + { + DMG_RADIATION, + 'Radiation' + }, + { + DMG_SLOWBURN, + 'Slowburn' + } +} +do + local _class_0 + local _base_0 = { + TypesArray = function(self) + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #damageTypes do + local _des_0 = damageTypes[_index_0] + local dtype, dname + dtype, dname = _des_0[1], _des_0[2] + if self:IsDamageType(dtype) then + _accum_0[_len_0] = dtype + _len_0 = _len_0 + 1 + end + end + return _accum_0 + end, + RecordInflictor = function(self) + if self.inflictor ~= self.attacker or not self.attacker.GetActiveWeapon then + self.recordedInflictor = self.inflictor + return + end + local weapon = self.attacker:GetActiveWeapon() + if not IsValid(weapon) then + self.recordedInflictor = self.inflictor + return + end + self.recordedInflictor = weapon + end, + AddDamage = function(self, damageNum) + if damageNum == nil then + damageNum = 0 + end + self.damage = math.clamp(self.damage + damageNum, 0, 0x7FFFFFFF) + end, + SubtractDamage = function(self, damageNum) + if damageNum == nil then + damageNum = 0 + end + self.damage = math.clamp(self.damage - damageNum, 0, 0x7FFFFFFF) + end, + GetDamageType = function(self) + return self.damageType + end, + GetAttacker = function(self) + return self.attacker + end, + GetInflictor = function(self) + return self.inflictor + end, + GetRecordedInflictor = function(self) + return self.recordedInflictor + end, + GetBaseDamage = function(self) + return self.damage + end, + GetAmmoType = function(self) + return self.ammoType + end, + GetDamage = function(self) + return self.damage + end, + GetDamageBonus = function(self) + return self.damageBonus + end, + GetDamageCustom = function(self) + return self.damageCustomFlags + end, + GetDamageForce = function(self) + return self.damageForce + end, + GetDamagePosition = function(self) + return self.damagePosition + end, + GetReportedPosition = function(self) + return self.reportedPosition + end, + GetDamageForce = function(self) + return self.damageForce + end, + GetDamageType = function(self) + return self.damageType + end, + GetMaxDamage = function(self) + return self.maxDamage + end, + GetBaseDamage = function(self) + return self.baseDamage + end, + IsDamageType = function(self, dtype) + return self.damageType:band(dtype) == dtype + end, + IsBulletDamage = function(self) + return self:IsDamageType(DMG_BULLET) + end, + IsExplosionDamage = function(self) + return self:IsDamageType(DMG_BLAST) + end, + IsFallDamage = function(self) + return self:IsDamageType(DMG_FALL) + end, + ScaleDamage = function(self, scaleBy) + self.damage = math.clamp(self.damage * scaleBy, 0, 0x7FFFFFFF) + end, + SetAmmoType = function(self, ammotype) + self.ammoType = ammotype + end, + SetAttacker = function(self, attacker) + self.attacker = assert(isentity(attacker) and attacker, 'Invalid attacker') + end, + SetInflictor = function(self, attacker) + self.inflictor = assert(isentity(attacker) and attacker, 'Invalid inflictor') + end, + SetRecordedInflictor = function(self, attacker) + self.recordedInflictor = assert(isentity(attacker) and attacker, 'Invalid recorded inflictor') + end, + SetDamage = function(self, dmg) + self.damage = math.clamp(dmg, 0, 0x7FFFFFFF) + end, + SetDamageBonus = function(self, dmg) + self.damageBonus = math.clamp(dmg, 0, 0x7FFFFFFF) + end, + SetMaxDamage = function(self, dmg) + self.maxDamage = math.clamp(dmg, 0, 0x7FFFFFFF) + end, + SetDamageCustom = function(self, dmg) + self.damageCustomFlags = dmg + end, + SetDamageType = function(self, dmg) + self.damageType = dmg + end, + SetDamagePosition = function(self, pos) + self.damagePosition = pos + end, + SetReportedPosition = function(self, pos) + self.reportedPosition = pos + end, + SetDamageForce = function(self, force) + self.damageForce = force + end, + SetBaseDamage = function(self, damage) + self.baseDamage = damage + end, + Copy = function(self) + return DLib.LTakeDamageInfo(self) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, copyfrom) + self.damage = 0 + self.baseDamage = 0 + self.maxDamage = 0 + self.ammoType = 0 + self.damageBonus = 0 + self.damageCustomFlags = 0 + self.damageForce = Vector() + self.reportedPosition = Vector() + self.damagePosition = Vector() + self.damageType = DMG_GENERIC + self.attacker = NULL + self.inflictor = NULL + self.recordedInflictor = NULL + if copyfrom then + do + local _with_0 = copyfrom + self:SetAmmoType(_with_0:GetAmmoType()) + self:SetAttacker(_with_0:GetAttacker()) + self:SetBaseDamage(_with_0:GetBaseDamage()) + self:SetDamage(_with_0:GetDamage()) + self:SetDamageBonus(_with_0:GetDamageBonus()) + self:SetDamageCustom(_with_0:GetDamageCustom()) + self:SetDamageForce(_with_0:GetDamageForce()) + self:SetDamagePosition(_with_0:GetDamagePosition()) + self:SetDamageType(_with_0:GetDamageType()) + self:SetInflictor(_with_0:GetInflictor()) + self:SetMaxDamage(_with_0:GetMaxDamage()) + self:SetReportedPosition(_with_0:GetReportedPosition()) + return _with_0 + end + end + end, + __base = _base_0, + __name = "LTakeDamageInfo" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + for _index_0 = 1, #damageTypes do + local _des_0 = damageTypes[_index_0] + local dtype, dname + dtype, dname = _des_0[1], _des_0[2] + self.__base['Is' .. dname .. 'Damage'] = function(self) + return self:IsDamageType(dtype) + end + end + self.__base.MetaName = 'LTakeDamageInfo' + DLib.LTakeDamageInfo = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/freespace.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/freespace.lua new file mode 100644 index 0000000..0f08103 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/freespace.lua @@ -0,0 +1,227 @@ +do + local _class_0 + local _base_0 = { + GetMins = function(self) + return self.mins + end, + GetMaxs = function(self) + return self.maxs + end, + SetMins = function(self, val) + self.mins = val + end, + SetMaxs = function(self, val) + self.maxs = val + end, + GetPos = function(self) + return self.pos + end, + SetPos = function(self, val) + self.pos = val + end, + GetAddition = function(self) + return self.addition + end, + SetAddition = function(self, val) + self.addition = val + end, + GetStrict = function(self) + return self.strict + end, + GetStrictHeight = function(self) + return self.sheight + end, + SetStrict = function(self, val) + self.strict = val + end, + SetStrictHeight = function(self, val) + self.sheight = val + end, + GetAABB = function(self) + return self.mins, self.maxs + end, + GetSAABB = function(self) + return self.smins, self.smaxs + end, + SetAABB = function(self, val1, val2) + self.mins, self.maxs = val1, val2 + end, + SetSAABB = function(self, val1, val2) + self.smins, self.smaxs = val1, val2 + end, + GetMask = function(self) + return self.mask + end, + SetMask = function(self, val) + self.mask = val + end, + GetMaskReachable = function(self) + return self.maskReachable + end, + SetMaskReachable = function(self, val) + self.maskReachable = val + end, + GetStep = function(self) + return self.step + end, + GetRadius = function(self) + return self.radius + end, + SetStep = function(self, val) + self.step = val + end, + SetRadius = function(self, val) + self.radius = val + end, + check = function(self, target) + if self.usehull then + local tr = util.TraceHull({ + start = self.pos, + endpos = target + self.addition, + mins = self.mins, + maxs = self.maxs, + mask = self.maskReachable, + filter = self.filter:getValues() + }) + if self.strict and not tr.Hit then + local tr2 = util.TraceHull({ + start = target + self.addition, + endpos = target + self.addition + Vector(0, 0, self.sheight), + mins = self.smins, + maxs = self.smaxs, + mask = self.mask, + filter = self.filter:getValues() + }) + return not tr2.Hit, tr, tr2 + end + return not tr.Hit, tr + else + local tr = util.TraceLine({ + start = self.pos, + endpos = target + self.addition, + mask = self.maskReachable, + filter = self.filter:getValues() + }) + if self.strict and not tr.Hit then + local tr2 = util.TraceHull({ + start = target + self.addition, + endpos = target + self.addition + Vector(0, 0, self.sheight), + mins = self.smins, + maxs = self.smaxs, + mask = self.mask, + filter = self.filter:getValues() + }) + return not tr2.Hit, tr, tr2 + end + return not tr.Hit, tr + end + end, + Search = function(self) + if self:check(self.pos) then + return self.pos + end + for radius = 1, self.radius do + for x = -radius, radius do + local pos = self.pos + Vector(x * self.step, radius * self.step, 0) + if self:check(pos) then + return pos + end + pos = self.pos + Vector(x * self.step, -radius * self.step, 0) + if self:check(pos) then + return pos + end + end + for y = -radius, radius do + local pos = self.pos + Vector(radius * self.step, y * self.step, 0) + if self:check(pos) then + return pos + end + pos = self.pos + Vector(-radius * self.step, y * self.step, 0) + if self:check(pos) then + return pos + end + end + end + return false + end, + SearchOptimal = function(self) + local validPositions = self:SearchAll() + if #validPositions == 0 then + return false + end + table.sort(validPositions, function(a, b) + return a:DistToSqr(self.pos) < b:DistToSqr(self.pos) + end) + return validPositions[1] + end, + SearchAll = function(self) + local output = { } + if self:check(self.pos) then + table.insert(output, self.pos) + end + for radius = 1, self.radius do + for x = -radius, radius do + local pos = self.pos + Vector(x * self.step, radius * self.step, 0) + if self:check(pos) then + table.insert(output, pos) + end + pos = self.pos + Vector(x * self.step, -radius * self.step, 0) + if self:check(pos) then + table.insert(output, pos) + end + end + for y = -radius, radius do + local pos = self.pos + Vector(radius * self.step, y * self.step, 0) + if self:check(pos) then + table.insert(output, pos) + end + pos = self.pos + Vector(-radius * self.step, y * self.step, 0) + if self:check(pos) then + table.insert(output, pos) + end + end + end + return output + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, posStart, step, radius) + if posStart == nil then + posStart = Vector(0, 0, 0) + end + if step == nil then + step = 25 + end + if radius == nil then + radius = 10 + end + self.pos = posStart + self.mins = Vector(-4, -4, -4) + self.maxs = Vector(4, 4, 4) + self.step = step + self.radius = radius + self.addition = Vector(0, 0, 0) + self.usehull = true + self.filter = DLib.Set() + self.mask = MASK_SOLID + self.maskReachable = MASK_SOLID + self.strict = false + self.smins = Vector(-16, -16, 0) + self.smaxs = Vector(16, 16, 0) + self.sheight = 70 + end, + __base = _base_0, + __name = "Freespace" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Freespace = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/keybinds.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/keybinds.lua new file mode 100644 index 0000000..d48600d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/keybinds.lua @@ -0,0 +1,1012 @@ +file.mkdir('dlib/keybinds') +DLib.bind = DLib.bind or { } +local bind = DLib.bind +bind.KeyMap = { + [KEY_FIRST] = 'FIRST', + [KEY_NONE] = 'NONE', + [KEY_0] = '0', + [KEY_1] = '1', + [KEY_2] = '2', + [KEY_3] = '3', + [KEY_4] = '4', + [KEY_5] = '5', + [KEY_6] = '6', + [KEY_7] = '7', + [KEY_8] = '8', + [KEY_9] = '9', + [KEY_A] = 'A', + [KEY_B] = 'B', + [KEY_C] = 'C', + [KEY_D] = 'D', + [KEY_E] = 'E', + [KEY_F] = 'F', + [KEY_G] = 'G', + [KEY_H] = 'H', + [KEY_I] = 'I', + [KEY_J] = 'J', + [KEY_K] = 'K', + [KEY_L] = 'L', + [KEY_M] = 'M', + [KEY_N] = 'N', + [KEY_O] = 'O', + [KEY_P] = 'P', + [KEY_Q] = 'Q', + [KEY_R] = 'R', + [KEY_S] = 'S', + [KEY_T] = 'T', + [KEY_U] = 'U', + [KEY_V] = 'V', + [KEY_W] = 'W', + [KEY_X] = 'X', + [KEY_Y] = 'Y', + [KEY_Z] = 'Z', + [KEY_PAD_0] = 'PAD_0', + [KEY_PAD_1] = 'PAD_1', + [KEY_PAD_2] = 'PAD_2', + [KEY_PAD_3] = 'PAD_3', + [KEY_PAD_4] = 'PAD_4', + [KEY_PAD_5] = 'PAD_5', + [KEY_PAD_6] = 'PAD_6', + [KEY_PAD_7] = 'PAD_7', + [KEY_PAD_8] = 'PAD_8', + [KEY_PAD_9] = 'PAD_9', + [KEY_PAD_DIVIDE] = 'PAD_DIVIDE', + [KEY_PAD_MULTIPLY] = 'PAD_MULTIPLY', + [KEY_PAD_MINUS] = 'PAD_MINUS', + [KEY_PAD_PLUS] = 'PAD_PLUS', + [KEY_PAD_ENTER] = 'PAD_ENTER', + [KEY_PAD_DECIMAL] = 'PAD_DECIMAL', + [KEY_LBRACKET] = 'LBRACKET', + [KEY_RBRACKET] = 'RBRACKET', + [KEY_SEMICOLON] = 'SEMICOLON', + [KEY_APOSTROPHE] = 'APOSTROPHE', + [KEY_BACKQUOTE] = 'BACKQUOTE', + [KEY_COMMA] = 'COMMA', + [KEY_PERIOD] = 'PERIOD', + [KEY_SLASH] = 'SLASH', + [KEY_BACKSLASH] = 'BACKSLASH', + [KEY_MINUS] = 'MINUS', + [KEY_EQUAL] = 'EQUAL', + [KEY_ENTER] = 'ENTER', + [KEY_SPACE] = 'SPACE', + [KEY_BACKSPACE] = 'BACKSPACE', + [KEY_TAB] = 'TAB', + [KEY_CAPSLOCK] = 'CAPSLOCK', + [KEY_NUMLOCK] = 'NUMLOCK', + [KEY_ESCAPE] = 'ESCAPE', + [KEY_SCROLLLOCK] = 'SCROLLLOCK', + [KEY_INSERT] = 'INSERT', + [KEY_DELETE] = 'DELETE', + [KEY_HOME] = 'HOME', + [KEY_END] = 'END', + [KEY_PAGEUP] = 'PAGEUP', + [KEY_PAGEDOWN] = 'PAGEDOWN', + [KEY_BREAK] = 'BREAK', + [KEY_LSHIFT] = 'LSHIFT', + [KEY_RSHIFT] = 'RSHIFT', + [KEY_LALT] = 'LALT', + [KEY_RALT] = 'RALT', + [KEY_LCONTROL] = 'LCONTROL', + [KEY_RCONTROL] = 'RCONTROL', + [KEY_LWIN] = 'LWIN', + [KEY_RWIN] = 'RWIN', + [KEY_APP] = 'APP', + [KEY_UP] = 'UP', + [KEY_LEFT] = 'LEFT', + [KEY_DOWN] = 'DOWN', + [KEY_RIGHT] = 'RIGHT', + [KEY_F1] = 'F1', + [KEY_F2] = 'F2', + [KEY_F3] = 'F3', + [KEY_F4] = 'F4', + [KEY_F5] = 'F5', + [KEY_F6] = 'F6', + [KEY_F7] = 'F7', + [KEY_F8] = 'F8', + [KEY_F9] = 'F9', + [KEY_F10] = 'F10', + [KEY_F11] = 'F11', + [KEY_F12] = 'F12', + [KEY_CAPSLOCKTOGGLE] = 'CAPSLOCKTOGGLE', + [KEY_NUMLOCKTOGGLE] = 'NUMLOCKTOGGLE', + [KEY_LAST] = 'LAST', + [KEY_SCROLLLOCKTOGGLE] = 'SCROLLLOCKTOGGLE', + [KEY_COUNT] = 'COUNT', + [KEY_XBUTTON_A] = 'XBUTTON_A', + [KEY_XBUTTON_B] = 'XBUTTON_B', + [KEY_XBUTTON_X] = 'XBUTTON_X', + [KEY_XBUTTON_Y] = 'XBUTTON_Y', + [KEY_XBUTTON_LEFT_SHOULDER] = 'XBUTTON_LEFT_SHOULDER', + [KEY_XBUTTON_RIGHT_SHOULDER] = 'XBUTTON_RIGHT_SHOULDER', + [KEY_XBUTTON_BACK] = 'XBUTTON_BACK', + [KEY_XBUTTON_START] = 'XBUTTON_START', + [KEY_XBUTTON_STICK1] = 'XBUTTON_STICK1', + [KEY_XBUTTON_STICK2] = 'XBUTTON_STICK2', + [KEY_XBUTTON_UP] = 'XBUTTON_UP', + [KEY_XBUTTON_RIGHT] = 'XBUTTON_RIGHT', + [KEY_XBUTTON_DOWN] = 'XBUTTON_DOWN', + [KEY_XBUTTON_LEFT] = 'XBUTTON_LEFT', + [KEY_XSTICK1_RIGHT] = 'XSTICK1_RIGHT', + [KEY_XSTICK1_LEFT] = 'XSTICK1_LEFT', + [KEY_XSTICK1_DOWN] = 'XSTICK1_DOWN', + [KEY_XSTICK1_UP] = 'XSTICK1_UP', + [KEY_XBUTTON_LTRIGGER] = 'XBUTTON_LTRIGGER', + [KEY_XBUTTON_RTRIGGER] = 'XBUTTON_RTRIGGER', + [KEY_XSTICK2_RIGHT] = 'XSTICK2_RIGHT', + [KEY_XSTICK2_LEFT] = 'XSTICK2_LEFT', + [KEY_XSTICK2_DOWN] = 'XSTICK2_DOWN', + [KEY_XSTICK2_UP] = 'XSTICK2_UP' +} +bind.LocalizedButtons = { + UP = 'UP Arrow', + DOWN = 'DOWN Arrow', + LEFT = 'LEFT Arrow', + RIGHT = 'RIGHT Arrow' +} +local KEY_LIST +do + local _accum_0 = { } + local _len_0 = 1 + for key, str in pairs(bind.KeyMap) do + _accum_0[_len_0] = key + _len_0 = _len_0 + 1 + end + KEY_LIST = _accum_0 +end +do + local _tbl_0 = { } + for k, v in pairs(bind.KeyMap) do + _tbl_0[v] = k + end + bind.KeyMapReverse = _tbl_0 +end +bind.SerealizeKeys = function(keys) + if keys == nil then + keys = { } + end + local output + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #keys do + local k = keys[_index_0] + if bind.KeyMap[k] then + _accum_0[_len_0] = bind.KeyMap[k] + _len_0 = _len_0 + 1 + end + end + output = _accum_0 + end + return output +end +bind.UnSerealizeKeys = function(keys) + if keys == nil then + keys = { } + end + local output + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #keys do + local k = keys[_index_0] + if bind.KeyMapReverse[k] then + _accum_0[_len_0] = bind.KeyMapReverse[k] + _len_0 = _len_0 + 1 + end + end + output = _accum_0 + end + return output +end +do + local _class_0 + local _base_0 = { + Setup = function(self, binds) + self.KeybindingsMap = binds + for name, data in pairs(self.KeybindingsMap) do + data.secondary = data.secondary or { } + data.id = name + data.name = data.name or '#BINDNAME?' + data.desc = data.desc or '#BINDDESC?' + data.order = data.order or 100 + end + do + local _accum_0 = { } + local _len_0 = 1 + for name, data in pairs(self.KeybindingsMap) do + _accum_0[_len_0] = data + _len_0 = _len_0 + 1 + end + self.KeybindingsOrdered = _accum_0 + end + return table.sort(self.KeybindingsOrdered, function(a, b) + return a.order < b.order + end) + end, + RegisterBind = function(self, id, name, desc, primary, secondary, order) + if name == nil then + name = '#BINDNAME?' + end + if desc == nil then + desc = '#BINDDESC?' + end + if primary == nil then + primary = { } + end + if secondary == nil then + secondary = { } + end + if order == nil then + order = 100 + end + if not id then + error('No ID specified!') + end + self.KeybindingsMap[id] = { + name = name, + desc = desc, + primary = primary, + secondary = secondary, + order = order, + id = id + } + do + local _accum_0 = { } + local _len_0 = 1 + for name, data in pairs(self.KeybindingsMap) do + _accum_0[_len_0] = data + _len_0 = _len_0 + 1 + end + self.KeybindingsOrdered = _accum_0 + end + return table.sort(self.KeybindingsOrdered, function(a, b) + return a.order < b.order + end) + end, + GetDefaultBindings = function(self) + local output + do + local _accum_0 = { } + local _len_0 = 1 + for id, data in pairs(self.KeybindingsMap) do + local primary = bind.SerealizeKeys(data.primary) + local secondary = bind.SerealizeKeys(data.secondary) + local _value_0 = { + name = id, + primary = primary, + secondary = secondary + } + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + output = _accum_0 + end + return output + end, + UpdateKeysMap = function(self) + local watchButtons + do + local _tbl_0 = { } + local _list_0 = self.Keybindings + for _index_0 = 1, #_list_0 do + local data = _list_0[_index_0] + local _list_1 = data.primary + for _index_1 = 1, #_list_1 do + local key = _list_1[_index_1] + _tbl_0[key] = true + end + end + watchButtons = _tbl_0 + end + local _list_0 = self.Keybindings + for _index_0 = 1, #_list_0 do + local data = _list_0[_index_0] + local _list_1 = data.secondary + for _index_1 = 1, #_list_1 do + local key = _list_1[_index_1] + watchButtons[key] = true + end + end + do + local _tbl_0 = { } + local _list_1 = self.Keybindings + for _index_0 = 1, #_list_1 do + local data = _list_1[_index_0] + _tbl_0[data.name] = data + end + self.KeybindingsUserMap = _tbl_0 + end + do + local _tbl_0 = { } + local _list_1 = self.Keybindings + for _index_0 = 1, #_list_1 do + local data = _list_1[_index_0] + _tbl_0[data.name] = { + name = data.name, + primary = bind.UnSerealizeKeys(data.primary), + secondary = bind.UnSerealizeKeys(data.secondary) + } + end + self.KeybindingsUserMapCheck = _tbl_0 + end + do + local _accum_0 = { } + local _len_0 = 1 + for key, bool in pairs(watchButtons) do + _accum_0[_len_0] = bind.KeyMapReverse[key] + _len_0 = _len_0 + 1 + end + self.WatchingButtons = _accum_0 + end + do + local _tbl_0 = { } + local _list_1 = self.WatchingButtons + for _index_0 = 1, #_list_1 do + local key = _list_1[_index_0] + _tbl_0[key] = false + end + self.PressedButtons = _tbl_0 + end + do + local _tbl_0 = { } + local _list_1 = self.WatchingButtons + for _index_0 = 1, #_list_1 do + local key = _list_1[_index_0] + _tbl_0[key] = { } + end + self.WatchingButtonsPerBinding = _tbl_0 + end + do + local _tbl_0 = { } + local _list_1 = self.Keybindings + for _index_0 = 1, #_list_1 do + local data = _list_1[_index_0] + _tbl_0[data.name] = false + end + self.BindPressStatus = _tbl_0 + end + local _list_1 = self.Keybindings + for _index_0 = 1, #_list_1 do + local _des_0 = _list_1[_index_0] + local name, primary, secondary + name, primary, secondary = _des_0.name, _des_0.primary, _des_0.secondary + for _index_1 = 1, #primary do + local key = primary[_index_1] + table.insert(self.WatchingButtonsPerBinding[bind.KeyMapReverse[key]], name) + end + for _index_1 = 1, #secondary do + local key = secondary[_index_1] + table.insert(self.WatchingButtonsPerBinding[bind.KeyMapReverse[key]], name) + end + end + end, + SetKeyCombination = function(self, bindid, isPrimary, keys, update, doSave) + if bindid == nil then + bindid = '' + end + if isPrimary == nil then + isPrimary = true + end + if keys == nil then + keys = { } + end + if update == nil then + update = true + end + if doSave == nil then + doSave = true + end + if not self.KeybindingsMap[bindid] then + return false + end + if not self.KeybindingsUserMap[bindid] then + return false + end + if isPrimary then + local _list_0 = self.Keybindings + for _index_0 = 1, #_list_0 do + local data = _list_0[_index_0] + if data.name == bindid then + data.primary = keys + break + end + end + else + local _list_0 = self.Keybindings + for _index_0 = 1, #_list_0 do + local data = _list_0[_index_0] + if data.name == bindid then + data.secondary = keys + break + end + end + end + if update then + self:UpdateKeysMap() + end + if doSave then + self:SaveKeybindings() + end + return true + end, + IsKeyDown = function(self, keyid) + if keyid == nil then + keyid = KEY_NONE + end + return self.PressedButtons[keyid] or false + end, + IsBindPressed = function(self, bindid) + if bindid == nil then + bindid = '' + end + if not self.KeybindingsMap[bindid] then + return false + end + if not self.KeybindingsUserMap[bindid] then + return false + end + return self.BindPressStatus[bindid] or false + end, + IsBindDown = IsBindPressed, + InternalIsBindPressed = function(self, bindid) + if bindid == nil then + bindid = '' + end + if not self.KeybindingsMap[bindid] then + return false + end + if not self.KeybindingsUserMapCheck[bindid] then + return false + end + local data = self.KeybindingsUserMapCheck[bindid] + local total = #data.primary + local hits = 0 + local total2 = #data.secondary + local hits2 = 0 + local _list_0 = data.primary + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + if self:IsKeyDown(key) then + hits = hits + 1 + end + end + local _list_1 = data.secondary + for _index_0 = 1, #_list_1 do + local key = _list_1[_index_0] + if self:IsKeyDown(key) then + hits2 = hits2 + 1 + end + end + return total ~= 0 and total == hits or total2 ~= 0 and total2 == hits2 + end, + GetBindString = function(self, bindid) + if bindid == nil then + bindid = '' + end + if not self.KeybindingsMap[bindid] then + return false + end + if not self.KeybindingsUserMap[bindid] then + return false + end + local output + local data = self.KeybindingsUserMap[bindid] + if #data.primary ~= 0 then + output = table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = data.primary + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + _accum_0[_len_0] = bind.LocalizedButtons[key] or key + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ' + ') + end + if #data.secondary ~= 0 then + local tab + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = data.secondary + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + _accum_0[_len_0] = bind.LocalizedButtons[key] or key + _len_0 = _len_0 + 1 + end + tab = _accum_0 + end + if output then + output = output .. (' or ' .. table.concat(tab, ' + ')) + end + if not output then + output = table.concat(tab, ' + ') + end + end + return output or '' + end, + SaveKeybindings = function(self) + return file.Write(self.fpath, util.TableToJSON(self.Keybindings, true)) + end, + LoadKeybindings = function(self) + self.Keybindings = nil + local settingsExists = file.Exists(self.fpath, 'DATA') + if settingsExists then + local read = file.Read(self.fpath, 'DATA') + self.Keybindings = util.JSONToTable(read) + if self.Keybindings then + local defaultBinds = self:GetDefaultBindings() + local valid = true + local hits = { } + local _list_0 = self.Keybindings + for _index_0 = 1, #_list_0 do + local data = _list_0[_index_0] + if not data.primary then + valid = false + break + end + if not data.secondary then + valid = false + break + end + if not data.name then + valid = false + break + end + if type(data.primary) ~= 'table' then + valid = false + break + end + if type(data.secondary) ~= 'table' then + valid = false + break + end + if type(data.name) ~= 'string' then + valid = false + break + end + hits[data.name] = true + end + local shouldSave = false + if valid then + for _index_0 = 1, #defaultBinds do + local data = defaultBinds[_index_0] + if not hits[data.name] then + table.insert(self.Keybindings, data) + shouldSave = true + end + end + self:UpdateKeysMap() + if shouldSave then + self:SaveKeybindings() + end + else + self.Keybindings = nil + end + end + end + if not self.Keybindings then + self.Keybindings = self:GetDefaultBindings() + self:UpdateKeysMap() + self:SaveKeybindings() + end + return self.Keybindings + end, + UpdateKeysStatus = function(self) + if not self.WatchingButtons then + return + end + local _list_0 = self.WatchingButtons + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + local oldStatus = self.PressedButtons[key] + local newStatus = input.IsKeyDown(key) + if oldStatus ~= newStatus then + self.PressedButtons[key] = newStatus + local watching = self.WatchingButtonsPerBinding[key] + if watching then + for _index_1 = 1, #watching do + local name = watching[_index_1] + local oldPressStatus = self.BindPressStatus[name] + local newPressStatus = self:InternalIsBindPressed(name) + if oldPressStatus ~= newPressStatus then + self.BindPressStatus[name] = newPressStatus + if not newPressStatus then + hook.Run(self.vname .. '.BindReleased', name) + else + hook.Run(self.vname .. '.BindPressed', name) + end + end + end + end + end + end + end, + OpenKeybindsMenu = function(self) + do + local frame = vgui.Create('DFrame') + frame:SetSkin('DLib_Black') + frame:SetSize(470, ScrHL() - 200) + frame:SetTitle(self.vname .. ' Keybinds') + frame:Center() + frame:MakePopup() + frame:SetKeyboardInputEnabled(true) + frame.scroll = vgui.Create('DScrollPanel', frame) + frame.scroll:Dock(FILL) + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.KeybindingsOrdered + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local id + id = _des_0.id + do + local _with_0 = vgui.Create('DLibBindRow', frame.scroll) + _with_0:SetTarget(self) + _with_0:SetBindID(id) + _with_0:Dock(TOP) + _accum_0[_len_0] = _with_0 + end + _len_0 = _len_0 + 1 + end + frame.rows = _accum_0 + end + return frame + end + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, vname, binds, doLoad) + if doLoad == nil then + doLoad = true + end + if not vname then + error('Name is required') + end + if not binds then + error('Binds are required') + end + self.vname = vname + self.fname = vname:lower() + self.fpath = 'dlib/keybinds/' .. self.fname .. '.txt' + self:Setup(binds) + if doLoad then + self:LoadKeybindings() + end + return hook.Add('Think', self.vname .. '.Keybinds', function() + return self:UpdateKeysStatus() + end) + end, + __base = _base_0, + __name = "KeyBindsAdapter" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + bind.KeyBindsAdapter = _class_0 +end +bind.PANEL_BIND_FIELD = { + Init = function(self) + self:SetSkin('DLib_Black') + self.lastMousePress = 0 + self.lastMousePressRight = 0 + self.primary = true + self.lock = false + self.combination = { } + self.combinationNew = { } + self:SetMouseInputEnabled(true) + self:SetTooltip('Double RIGHT mouse press to clear binding\nDouble LEFT mouse press to change binding\nWhen changing binding, press needed buttons WITHOUT releasing.\nRelease one of pressed buttons to save.\nTo cancel, press ESCAPE') + self.combinationLabel = vgui.Create('DLabel', self) + self.addColor = 0 + do + local _with_0 = self.combinationLabel + _with_0:Dock(FILL) + _with_0:DockMargin(5, 0, 0, 0) + _with_0:SetTextColor(color_white) + _with_0:SetText('#COMBINATION?') + return _with_0 + end + end, + SetCombinationLabel = function(self, keys) + if keys == nil then + keys = { } + end + local str = table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = bind.SerealizeKeys(keys) + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + _accum_0[_len_0] = bind.LocalizedButtons[key] or key + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ' + ') + return self.combinationLabel:SetText(str) + end, + StopLock = function(self) + self.lock = false + self:SetCursor('none') + if #self.combinationNew == 0 then + self.combinationNew = self.combination + return self:SetCombinationLabel(self.combination) + else + self:GetParent():OnCombinationUpdates(self, self.combinationNew) + self.combination = keys + return self:SetCombinationLabel(self.combinationNew) + end + end, + OnMousePressed = function(self, code) + if code == nil then + code = MOUSE_LEFT + end + if code == MOUSE_LEFT then + if self.lock then + self:StopLock() + return + end + local prev = self.lastMousePress + self.lastMousePress = RealTimeL() + 0.4 + if prev < RealTimeL() then + return + end + self.lock = true + self.combinationNew = { } + self.combinationLabel:SetText('???') + self.mouseX, self.mouseY = self:LocalToScreen(5, 5) + self:SetCursor('blank') + do + local _tbl_0 = { } + for _index_0 = 1, #KEY_LIST do + local key = KEY_LIST[_index_0] + _tbl_0[key] = false + end + self.pressedKeys = _tbl_0 + end + elseif code == MOUSE_RIGHT and not self.lock then + local prev = self.lastMousePressRight + self.lastMousePressRight = RealTimeL() + 0.4 + if prev < RealTimeL() then + return + end + self.combinationNew = { } + self:GetParent():OnCombinationUpdates(self, self.combinationNew) + self.combination = self.combinationNew + return self:SetCombinationLabel(self.combination) + end + end, + OnKeyCodePressed = function(self, code) + if code == nil then + code = KEY_NONE + end + if code == KEY_NONE or code == KEY_FIRST then + return + end + if not self.lock then + return + end + if code == KEY_ESCAPE then + self.lock = false + self.combinationNew = self.combination + self:SetCombinationLabel(self.combination) + self:SetCursor('none') + return + elseif code == KEY_ENTER then + self:StopLock() + return + end + table.insert(self.combinationNew, code) + return self:SetCombinationLabel(self.combinationNew) + end, + OnKeyCodeReleased = function(self, code) + if code == nil then + code = KEY_NONE + end + if code == KEY_NONE or code == KEY_FIRST then + return + end + if self.lock then + return self:StopLock() + end + end, + Paint = function(self, w, h) + if w == nil then + w = 0 + end + if h == nil then + h = 0 + end + surface.SetDrawColor(40 + 90 * self.addColor, 40 + 90 * self.addColor, 40) + surface.DrawRect(0, 0, w, h) + if self.lock then + surface.SetDrawColor(137, 130, 104) + return surface.DrawRect(4, 4, w - 8, h - 8) + end + end, + Think = function(self) + if self:IsHovered() then + self.addColor = math.min(self.addColor + FrameTime() * 10, 1) + else + self.addColor = math.max(self.addColor - FrameTime() * 10, 0) + end + if self.lock then + input.SetCursorPos(self.mouseX, self.mouseY) + for _index_0 = 1, #KEY_LIST do + local key = KEY_LIST[_index_0] + local old = self.pressedKeys[key] + local new = input.IsKeyDown(key) + if old ~= new then + self.pressedKeys[key] = new + if new then + self:OnKeyCodePressed(key) + else + self:OnKeyCodeReleased(key) + end + end + end + end + end +} +bind.PANEL_BIND_INFO = { + Init = function(self) + self:SetSkin('DLib_Black') + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + self.bindid = '' + self.label = vgui.Create('DLabel', self) + self:SetSize(200, 30) + do + local _with_0 = self.label + _with_0:SetText(' #HINT?') + _with_0:Dock(LEFT) + _with_0:DockMargin(10, 0, 0, 0) + _with_0:SetSize(200, 0) + _with_0:SetTooltip(' #DESCRIPTION?') + _with_0:SetTextColor(color_white) + _with_0:SetMouseInputEnabled(true) + end + self.primary = vgui.Create('DLibBindField', self) + do + local _with_0 = self.primary + _with_0:Dock(LEFT) + _with_0:DockMargin(10, 0, 0, 0) + _with_0:SetSize(100, 0) + _with_0.Primary = true + _with_0.combination = { } + end + self.secondary = vgui.Create('DLibBindField', self) + do + local _with_0 = self.secondary + _with_0:Dock(LEFT) + _with_0:DockMargin(10, 0, 0, 0) + _with_0:SetSize(100, 0) + _with_0.Primary = false + _with_0.combination = { } + return _with_0 + end + end, + SetTarget = function(self, target) + self.target = target + end, + SetBindID = function(self, id) + if id == nil then + id = '' + end + self.bindid = id + local data = bind.KeybindingsUserMap[id] + local dataLabels = bind.KeybindingsMap[id] + if not data then + return + end + if not dataLabels then + return + end + do + local _with_0 = self.label + _with_0:SetText(dataLabels.name) + _with_0:SetTooltip(dataLabels.desc) + end + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = bind.UnSerealizeKeys(data.primary) + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + _accum_0[_len_0] = key + _len_0 = _len_0 + 1 + end + self.primary.combination = _accum_0 + end + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = bind.UnSerealizeKeys(data.secondary) + for _index_0 = 1, #_list_0 do + local key = _list_0[_index_0] + _accum_0[_len_0] = key + _len_0 = _len_0 + 1 + end + self.secondary.combination = _accum_0 + end + self.primary:SetCombinationLabel(self.primary.combination) + return self.secondary:SetCombinationLabel(self.secondary.combination) + end, + OnCombinationUpdates = function(self, pnl, newCombination) + if newCombination == nil then + newCombination = { } + end + if self.bindid == '' then + return + end + return self.target:SetKeyCombination(self.bindid, pnl.Primary, bind.SerealizeKeys(newCombination)) + end, + Paint = function(self, w, h) + if w == nil then + w = 0 + end + if h == nil then + h = 0 + end + surface.SetDrawColor(106, 122, 120) + return surface.DrawRect(0, 0, w, h) + end +} +vgui.Register('DLibBindField', bind.PANEL_BIND_FIELD, 'EditablePanel') +vgui.Register('DLibBindRow', bind.PANEL_BIND_INFO, 'EditablePanel') +bind.exportBinds = function(classIn, target) + target.RegisterBind = function(...) + return classIn:RegisterBind(...) + end + target.SerealizeKeys = function(...) + return bind.SerealizeKeys(...) + end + target.UnSerealizeKeys = function(...) + return bind.UnSerealizeKeys(...) + end + target.GetDefaultBindings = function(...) + return classIn:GetDefaultBindings(...) + end + target.UpdateKeysMap = function(...) + return classIn:UpdateKeysMap(...) + end + target.SetKeyCombination = function(...) + return classIn:SetKeyCombination(...) + end + target.IsKeyDown = function(...) + return classIn:IsKeyDown(...) + end + target.IsBindPressed = function(...) + return classIn:IsBindPressed(...) + end + target.IsBindDown = function(...) + return classIn:IsBindPressed(...) + end + target.InternalIsBindPressed = function(...) + return classIn:InternalIsBindPressed(...) + end + target.GetBindString = function(...) + return classIn:GetBindString(...) + end + target.SaveKeybindings = function(...) + return classIn:SaveKeybindings(...) + end + target.LoadKeybindings = function(...) + return classIn:LoadKeybindings(...) + end + target.UpdateKeysStatus = function(...) + return classIn:UpdateKeysStatus(...) + end + target.OpenKeybindsMenu = function(...) + return classIn:OpenKeybindsMenu(...) + end + target.LocalizedButtons = bind.LocalizedButtons +end +return bind diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/measure.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/measure.lua new file mode 100644 index 0000000..07fa507 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/measure.lua @@ -0,0 +1,147 @@ +local math +math = _G.math +local prefixes = { + { + 'deci', + 10 ^ -1 + }, + { + 'centi', + 10 ^ -2 + }, + { + 'milli', + 10 ^ -3 + }, + { + 'micro', + 10 ^ -6 + }, + { + 'nano', + 10 ^ -9 + }, + { + 'deca', + 10 + }, + { + 'hecto', + 10 ^ 2 + }, + { + 'kilo', + 10 ^ 3 + }, + { + 'mega', + 10 ^ 6 + }, + { + 'giga', + 10 ^ 9 + }, + { + 'tera', + 10 ^ 12 + } +} +do + local _class_0 + local _base_0 = { + set = function(self, hammerUnits) + self.hammer = hammerUnits + self.metres = (hammerUnits * 19.05) / 1000 + for _index_0 = 1, #prefixes do + local _des_0 = prefixes[_index_0] + local prefix, size + prefix, size = _des_0[1], _des_0[2] + self[prefix .. 'metres'] = self.metres / size + end + end, + GetMetres = function(self) + return self.metres + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, hammerUnits) + self.hammer = hammerUnits + self.metres = (hammerUnits * 19.05) / 1000 + for _index_0 = 1, #prefixes do + local _des_0 = prefixes[_index_0] + local prefix, size + prefix, size = _des_0[1], _des_0[2] + self[prefix .. 'metres'] = self.metres / size + end + end, + __base = _base_0, + __name = "Measurment" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + for _index_0 = 1, #prefixes do + local _des_0 = prefixes[_index_0] + local prefix, size + prefix, size = _des_0[1], _des_0[2] + local valueOut = prefix .. 'metres' + self.__base['Get' .. prefix:sub(1, 1):upper() .. prefix:sub(2) .. 'metres'] = function(self) + return self[valueOut] + end + self.__base['Get' .. prefix:sub(1, 1):upper() .. prefix:sub(2) .. 'meters'] = function(self) + return self[valueOut] + end + end + DLib.Measurment = _class_0 +end +do + local _class_0 + local _base_0 = { + set = function(self, hammerUnits) + self.hammer = hammerUnits + self.metres = (hammerUnits * 19.05) / 1000 + end, + GetMetres = function(self) + return self.metres + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, hammerUnits) + self.hammer = hammerUnits + self.metres = (hammerUnits * 19.05) / 1000 + end, + __base = _base_0, + __name = "MeasurmentNoCache" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + for _index_0 = 1, #prefixes do + local _des_0 = prefixes[_index_0] + local prefix, size + prefix, size = _des_0[1], _des_0[2] + local valueOut = prefix .. 'metres' + self.__base['Get' .. prefix:sub(1, 1):upper() .. prefix:sub(2) .. 'metres'] = function(self) + return self.metres / size + end + self.__base['Get' .. prefix:sub(1, 1):upper() .. prefix:sub(2) .. 'meters'] = function(self) + return self.metres / size + end + end + DLib.MeasurmentNoCache = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/predictedvars.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/predictedvars.lua new file mode 100644 index 0000000..552a706 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/predictedvars.lua @@ -0,0 +1,236 @@ +local _OBJECTS = DLib.PredictedVarList and DLib.PredictedVarList._OBJECTS or { } +local plyMeta = FindMetaTable('Player') +local cl_showerror = GetConVar('cl_showerror') +do + local _class_0 + local _base_0 = { + GetByName = function(self, id) + return self._OBJECTS[id] + end, + SetSyncTimer = function(self, stimer) + if stimer == nil then + stimer = self.sync_cooldown + end + if game.SinglePlayer() or self.smartSync then + return + end + self.sync_cooldown = assert(type(stimer) == 'number' and stimer >= 0, 'Time must be a positive number!') + return timer.Create('DLib.PredictedVarList.Sync', self.sync_cooldown, 0, function() + return ProtectedCall(self.sync_closure) + end) + end, + Sync = function(self, ply) + if CLIENT then + error('Invalid realm') + end + net.Start(self._nw) + ply.__dlib_predvars = ply.__dlib_predvars or { } + ply.__dlib_predvars[self.netname] = ply.__dlib_predvars[self.netname] or { } + net.WriteTable(ply.__dlib_predvars[self.netname]) + return net.Send(ply) + end, + GetFrame = function(self) + return self.frame_id + end, + AddVar = function(self, identifier, def) + self.vars[identifier] = def + return self + end, + RegisterMeta = function(self, invalidateName, syncName) + local self2 = self + plyMeta[assert(invalidateName, 'Missing invalidate meta name')] = function(self, smart) + if smart == nil then + smart = false + end + return self2:Invalidate(self, smart) + end + plyMeta[assert(syncName, 'Missing sync meta name')] = function(self) + return self2:Sync(self) + end + for name, def in pairs(self.vars) do + plyMeta['Get' .. name] = function(self) + return self2:Get(self, name) + end + plyMeta['Set' .. name] = function(self, ...) + return self2:Set(self, name, ...) + end + plyMeta['Reset' .. name] = function(self) + return self2:Reset(self, name) + end + end + return self + end, + Invalidate = function(self, ply, smart) + if smart == nil then + smart = false + end + if SERVER then + self.frame_id = self.frame_id + 1 + if not ply.__dlib_predvars then + ply.__dlib_predvars = { } + end + if not ply.__dlib_predvars[self.netname] then + ply.__dlib_predvars[self.netname] = { } + end + return + end + if smart and self.lastInvalidate == FrameNumber() then + return + end + self.lastInvalidate = FrameNumber() + if IsFirstTimePredicted() then + self.firstF = true + self.frame_id = self.frame_id + 1 + for key, value in pairs(self.first) do + self.prev[key] = value + self.cur[key] = nil + self.first[key] = nil + end + return + end + self.firstF = false + for key in pairs(self.cur) do + self.cur[key] = nil + end + end, + Get = function(self, ply, identifier, def) + if def == nil then + def = self.vars[identifier] + end + if SERVER then + if not ply.__dlib_predvars or not ply.__dlib_predvars[self.netname] then + return def + end + local val = ply.__dlib_predvars[self.netname][identifier] + if val ~= nil then + return val + end + return def + end + assert(def ~= nil, 'Variable does not exist') + if self.firstF then + local val = self.first[identifier] + if val ~= nil then + return val + end + if self.prev[identifier] ~= nil then + return self.prev[identifier] + end + return def + end + local val = self.cur[identifier] + if val ~= nil then + return val + end + if self.prev[identifier] ~= nil then + return self.prev[identifier] + end + return def + end, + Set = function(self, ply, identifier, val) + if SERVER then + assert(assert(ply.__dlib_predvars, ':Invalidate() was never called with this player')[self.netname], ':Invalidate() was never called with this player')[identifier] = val + if game.SinglePlayer() then + self:Sync(ply) + end + return + end + assert(self.vars[identifier] ~= nil, 'Variable does not exist') + if self.firstF then + self.first[identifier] = val + return + end + self.cur[identifier] = val + end, + Reset = function(self, ply, identifier) + return self:Set(ply, identifier, self.vars[identifier]) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, netname, smartSync) + if smartSync == nil then + smartSync = false + end + self.netname = assert(netname, 'Missing network name') + self.__class._OBJECTS[self.netname] = self + self.vars = { } + self.prev = { } + self.cur = { } + self.first = { } + self.frame_id = 0 + self.firstF = true + self.sync_cooldown = 60 + self.lastInvalidate = 0 + self.smartSync = smartSync + self._nw = 'dlib_pred_' .. netname + if SERVER then + net.pool(self._nw) + if not game.SinglePlayer() then + if not smartSync then + self.sync_closure = function() + local _list_0 = player.GetAll() + for _index_0 = 1, #_list_0 do + local ply = _list_0[_index_0] + if ply.__dlib_predvars and ply.__dlib_predvars[self.netname] then + self:Sync(ply) + end + end + end + return timer.Create('DLib.PredictedVarList.' .. netname, self.sync_cooldown, 0, function() + return ProtectedCall(self.sync_closure) + end) + else + self.sync_closure = function() + local _list_0 = player.GetAll() + for _index_0 = 1, #_list_0 do + local ply = _list_0[_index_0] + local score = ply:PacketLoss():pow(2) / 30 + ply:Ping() / 10 + ply['__dlib_psync_last_' .. netname] = (ply['__dlib_psync_last_' .. netname] or 400) - score + if ply['__dlib_psync_last_' .. netname] <= 0 then + ply['__dlib_psync_last_' .. netname] = 400 + self:Sync(ply) + end + end + end + return timer.Create('DLib.PredictedVarList.' .. netname, 1, 0, function() + return ProtectedCall(self.sync_closure) + end) + end + end + else + return net.receive(self._nw, function() + local newR = net.ReadTable() + if cl_showerror:GetInt() >= 2 then + local num = 0 + for k, v in pairs(newR) do + if v ~= self.prev[k] then + num = num + 1 + local val = self.prev[k] + if val == nil then + val = 'null' + end + DLib.Warning(string.format('%.3d %s:%s', num, netname, k), ' - variable differs (net: ', v, ' pred ', val, ')') + end + end + end + self.prev = newR + end) + end + end, + __base = _base_0, + __name = "PredictedVarList" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._OBJECTS = _OBJECTS + DLib.PredictedVarList = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/rainbow.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/rainbow.lua new file mode 100644 index 0000000..0b8be7a --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/rainbow.lua @@ -0,0 +1,143 @@ +local HUDCommons = HUDCommons +local rnd = util.SharedRandom +local Color = Color +local math = math +local sin = math.sin +local cos = math.cos +do + local _class_0 + local _base_0 = { + Randomize = function(self) + self.pointer = 1 + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, self.iterations do + _accum_0[_len_0] = Color(128 + rnd(self.seed, -128, 127, i * self.strength), 128 + rnd(self.seed, -128, 127, i * self.strength + self.move), 128 + rnd(self.seed, -128, 127, i * self.strength + self.move * 2)) + _len_0 = _len_0 + 1 + end + self.values = _accum_0 + end + return self + end, + Next = function(self) + self.pointer = self.pointer + 1 + if #self.values < self.pointer then + self.pointer = 1 + end + return self.values[self.pointer] + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, seed, iterations, strength, move) + if seed == nil then + seed = 'DLib.RandomRainbow' + end + if iterations == nil then + iterations = 8 + end + if strength == nil then + strength = 1 + end + if move == nil then + move = iterations * 2 + end + self.seed = seed + self.iterations = iterations + self.strength = strength + self.move = move + self.values = { } + self.pointer = 1 + return self:Randomize() + end, + __base = _base_0, + __name = "RandomRainbow" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.RandomRainbow = _class_0 +end +do + local _class_0 + local _base_0 = { + Create = function(self) + self.pointer = 1 + if self.forward then + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, self.length do + _accum_0[_len_0] = Color(128 * self.mult + cos(i * self.strength) * 127 * self.mult, 128 + cos(i * self.strength + self.move) * 127 * self.mult, 128 * self.mult + cos(i * self.strength + self.move * 2) * 127 * self.mult) + _len_0 = _len_0 + 1 + end + self.values = _accum_0 + end + else + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, self.length do + _accum_0[_len_0] = Color(128 * self.mult + sin(i * self.strength) * 127 * self.mult, 128 + sin(i * self.strength + self.move) * 127 * self.mult, 128 * self.mult + sin(i * self.strength + self.move * 2) * 127 * self.mult) + _len_0 = _len_0 + 1 + end + self.values = _accum_0 + end + end + return self + end, + Next = function(self) + self.pointer = self.pointer + 1 + if #self.values < self.pointer then + self.pointer = 1 + end + return self.values[self.pointer] + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, length, strength, move, forward, mult) + if length == nil then + length = 64 + end + if strength == nil then + strength = 0.2 + end + if move == nil then + move = 2 + end + if forward == nil then + forward = true + end + if mult == nil then + mult = 1 + end + self.mult = mult + self.length = length + self.strength = strength + self.move = move + self.forward = forward + self.values = { } + self.pointer = 1 + return self:Create() + end, + __base = _base_0, + __name = "Rainbow" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Rainbow = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/classes/set.lua b/garrysmod/addons/util-dlib/lua/dlib/classes/set.lua new file mode 100644 index 0000000..b5c5e0e --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/classes/set.lua @@ -0,0 +1,296 @@ +local string_format = string.format +local type, pairs +do + local _obj_0 = _G + type, pairs = _obj_0.type, _obj_0.pairs +end +do + local _class_0 + local _base_0 = { + add = function(self, object) + if object == nil then + return false + end + for i, val in ipairs(self.values) do + if val == object then + return false + end + end + table.insert(self.values, object) + return true + end, + Add = function(self, ...) + return self:add(...) + end, + AddArray = function(self, ...) + return self:addArray(...) + end, + Has = function(self, ...) + return self:has(...) + end, + Includes = function(self, ...) + return self:has(...) + end, + Contains = function(self, ...) + return self:has(...) + end, + Remove = function(self, ...) + return self:remove(...) + end, + Delete = function(self, ...) + return self:remove(...) + end, + UnSet = function(self, ...) + return self:remove(...) + end, + GetValues = function(self, ...) + return self:getValues() + end, + CopyValues = function(self, ...) + local _accum_0 = { } + local _len_0 = 1 + for val in ipairs(self.values) do + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + addArray = function(self, objects) + local _list_0 = objects + for _index_0 = 1, #_list_0 do + local object = _list_0[_index_0] + self:add(object) + end + end, + has = function(self, object) + if object == nil then + return false + end + for i, val in ipairs(self.values) do + if val == object then + return true + end + end + return false + end, + includes = function(self, ...) + return self:has(...) + end, + contains = function(self, ...) + return self:has(...) + end, + remove = function(self, object) + if object == nil then + return false + end + for i, val in ipairs(self.values) do + if val == object then + table.remove(self.values, i) + return true + end + end + return false + end, + delete = function(self, ...) + return self:remove(...) + end, + rm = function(self, ...) + return self:remove(...) + end, + unset = function(self, ...) + return self:remove(...) + end, + getValues = function(self) + return self.values + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + self.values = { } + end, + __base = _base_0, + __name = "Set" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Set = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.Set + local _base_0 = { + _hash = function(self, object) + local tp = type(object) + if tp == 'string' or tp == 'number' then + return object + else + return string_format('%p', object) + end + end, + add = function(self, object) + if object == nil then + return false + end + local p = self:_hash(object) + if self.values[p] ~= nil then + return false + end + self.values[p] = object + return true, p + end, + has = function(self, object) + if object == nil then + return false + end + local p = self:_hash(object) + return self.values[p] ~= nil + end, + remove = function(self, object) + if object == nil then + return false + end + local p = self:_hash(object) + if self.values[p] == nil then + return false + end + self.values[p] = nil + return true, p + end, + getValues = function(self) + local _accum_0 = { } + local _len_0 = 1 + for i, val in pairs(self.values) do + _accum_0[_len_0] = val + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + CopyValues = function(self) + return self:getValues() + end, + copyHash = function(self) + local _tbl_0 = { } + for i, val in pairs(self.values) do + _tbl_0[val] = val + end + return _tbl_0 + end, + CopyHashTable = function(self) + local _tbl_0 = { } + for i, val in pairs(self.values) do + _tbl_0[val] = val + end + return _tbl_0 + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "HashSet", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.HashSet = _class_0 +end +do + local _class_0 + local _base_0 = { + encode = function(self, val, indexFail) + if indexFail == nil then + indexFail = 1 + end + if self.enumsInversed[val] == nil then + return indexFail + end + return self.enumsInversed[val] + end, + Encode = function(self, ...) + return self:encode(...) + end, + Decode = function(self, ...) + return self:decode(...) + end, + Write = function(self, ...) + return self:write(...) + end, + Read = function(self, ...) + return self:read(...) + end, + decode = function(self, val, indexFail) + if indexFail == nil then + indexFail = 1 + end + if type(val) ~= 'number' then + val = tonumber(val) + end + if self.enums[val] == nil then + return self.enums[indexFail] + end + return self.enums[val] + end, + write = function(self, val, ifNone) + return net.WriteUInt(self:encode(val, ifNone), net.ChooseOptimalBits(#self.enums)) + end, + read = function(self, ifNone) + return self:decode(net.ReadUInt(net.ChooseOptimalBits(#self.enums)), ifNone) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, ...) + self.enums = { + ... + } + do + local _tbl_0 = { } + for i, v in ipairs(self.enums) do + _tbl_0[v] = i + end + self.enumsInversed = _tbl_0 + end + end, + __base = _base_0, + __name = "Enum" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + DLib.Enum = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/core.lua b/garrysmod/addons/util-dlib/lua/dlib/core/core.lua new file mode 100644 index 0000000..26e2db6 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/core.lua @@ -0,0 +1,98 @@ + +-- +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 meta = debug.getmetatable(1) or {} +local math = math +meta.MetaName = 'number' + +--[[ + @doc + @fname number:__index + @args any key + + @returns + function: associated function in math or bit table +]] +function meta:__index(key) + return meta[key] or math[key] or bit[key] +end + +--[[ + @doc + @fname number:IsValid + + @returns + boolean: false +]] +function meta:IsValid() + return false +end + +local funcs = {} + +for k, v in pairs(math) do + funcs[k:sub(1, 1):lower() .. k:sub(2)] = v +end + +for k, v in pairs(funcs) do + if math[k] == nil then + math[k] = v + end +end + +debug.setmetatable(1, meta) + +--[[ + @doc + @fname net.pool + @args string netname + + @desc + alias of !g:util.AddNetworkString + @enddesc + + @returns + number: created net ID +]] +net.pool = util.AddNetworkString + +--[[ + @doc + @fname net.receive + @args string netname, function handler + + @desc + alias of !g:net.Receive + @enddesc +]] +net.receive = net.Receive + +--[[ + @doc + @fname file.mkdir + @args string dirname + + @desc + alias of !g:file.CreateDir + @enddesc +]] +file.mkdir = file.CreateDir diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/fsutil.lua b/garrysmod/addons/util-dlib/lua/dlib/core/fsutil.lua new file mode 100644 index 0000000..a8e3c99 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/fsutil.lua @@ -0,0 +1,129 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 file = file +local files, dirs = {}, {} +local endfix = '/*' +local searchIn = 'LUA' + +local function findRecursive(dirTarget) + local findFiles = file.Find(dirTarget .. endfix, searchIn) + local _, findDirs = file.Find(dirTarget .. '/*', searchIn) + table.prependString(findFiles, dirTarget .. '/') + table.prependString(findDirs, dirTarget .. '/') + table.append(files, findFiles) + table.append(dirs, findDirs) + + for i, dir in ipairs(findDirs) do + findRecursive(dirTarget .. '/' .. dir) + end +end + +local function findRecursiveVisible(dirTarget) + local findFiles, findDirs = file.FindVisible(dirTarget, searchIn) + + for i, dir in ipairs(findDirs) do + findRecursiveVisible(dirTarget .. '/' .. dir) + end + + table.prependString(findFiles, dirTarget .. '/') + table.prependString(findDirs, dirTarget .. '/') + table.append(files, findFiles) + table.append(dirs, findDirs) +end + +--[[ + @doc + @fname file.FindVisible + @args string dirIn, string datapatchIn = 'LUA' + + @returns + table: found files (bare path) + table: found dirs (bare path) +]] +function file.FindVisible(dir, searchIn) + local fileFind, dirFind = file.Find(dir .. '/*', searchIn or 'LUA') + table.filter(fileFind, function(key, val) return val:sub(1, 1) ~= '.' end) + table.filter(dirFind, function(key, val) return val:sub(1, 1) ~= '.' end) + return fileFind, dirFind +end + +--[[ + @doc + @fname file.FindVisiblePrepend + @args string dirIn, string datapatchIn = 'LUA' + + @returns + table: found files (full paths) + table: found dirs (full paths) +]] +function file.FindVisiblePrepend(dir, searchIn) + local fileFind, dirFind = file.FindVisible(dir, searchIn) + table.prependString(fileFind, dir .. '/') + table.prependString(dirFind, dir .. '/') + return fileFind, dirFind +end + +--[[ + @doc + @fname file.FindRecursive + @args string dirIn, string endfixTo = '/*', string datapatchIn = 'LUA' + + @returns + table: found files (full paths) + table: found dirs (full paths) +]] +function file.FindRecursive(dir, endfixTo, searchIn2) + endfixTo = endfixTo or '/*' + searchIn2 = searchIn2 or 'LUA' + endfix = endfixTo + searchIn = searchIn2 + files, dirs = {}, {} + + findRecursive(dir) + table.sort(files) + table.sort(dirs) + + return files, dirs +end + +--[[ + @doc + @fname file.FindRecursiveVisible + @args string dirIn, string datapatchIn = 'LUA' + + @returns + table: found files (full paths) + table: found dirs (full paths) +]] +function file.FindRecursiveVisible(dir, searchIn2) + searchIn2 = searchIn2 or 'LUA' + searchIn = searchIn2 + files, dirs = {}, {} + + findRecursiveVisible(dir) + table.sort(files) + table.sort(dirs) + + return files, dirs +end + +return file diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/funclib.lua b/garrysmod/addons/util-dlib/lua/dlib/core/funclib.lua new file mode 100644 index 0000000..e8c1a34 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/funclib.lua @@ -0,0 +1,315 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +DLib.fnlib = {} + +local rawequal = rawequal +local error = error +local fnlib = DLib.fnlib +local meta = debug.getmetatable(function() end) or {} + +meta.MetaName = 'function' + +--[[ + @doc + @fname function:__index + @args any key + + @returns + any: associated value (probably a function) in funclib +]] + +--[[ + @doc + @fname nil:__index + @args any key + + @desc + function throws an error at all times upon call + nil seems to have the same metatable as function + @enddesc +]] +function meta:__index(key) + if rawequal(self, nil) then + local i = 1 + local lasts, lastsAll = {}, {} + + while true do + local name, value = debug.getlocal(2, i) + if name == nil then break end + i = i + 1 + + if value == nil then + table.insert(lastsAll, name) + + if name ~= '(*temporary)' then + table.insert(lasts, name) + end + end + end + + if #lasts == 0 and #lastsAll ~= 0 then + error(string.format('attempt to index field %q of a nil value (bytecode local variable)', key), 2) + elseif #lasts == 0 and #lastsAll == 0 then + error(string.format('attempt to index field %q of a nil value (unable to detect)', key), 2) + elseif #lasts == 1 then + error(string.format('attempt to index field %q of a nil value (probably %s?)', key, lasts[1]), 2) + elseif #lasts <= 4 then + error(string.format('attempt to index field %q of a nil value (%i possibilities: %s)', key, #lasts, table.concat(lasts, ', ')), 2) + else + local things = {} + + for i = #lasts - 4, #lasts do + table.insert(things, lasts[i]) + end + + if #lastsAll ~= #lasts then + error(string.format('attempt to index field %q of a nil value (%i possibilities + bytecode local variables, probably %s)', key, #lasts, table.concat(things, ', ')), 2) + else + error(string.format('attempt to index field %q of a nil value (%i possibilities, probably %s)', key, #lasts, table.concat(things, ', ')), 2) + end + end + end + + local val = meta[key] + + if val ~= nil then + return val + end + + return fnlib[key] +end + +local function genError(reason) + return function(self, target) + if type(self) == type(target) then + local format = string.format('%s (both sides are %s values)', reason:format(type(self)), type(self)) + error(format, 2) + elseif type(self) == 'function' or type(self) == 'nil' then + local format = string.format('%s (left side of expression is a %s, right side is a %s)', reason:format(type(self)), type(self), type(target)) + error(format, 2) + end + + local format = string.format('%s (left side of expression is a %s, right side is a %s)', reason:format(type(nil)), type(self), type(target)) + error(format, 2) + end +end + +meta.__unm = genError('attempt to unary minus a %s value') +meta.__add = genError('attempt to add a %s value') +meta.__sub = genError('attempt to substract a %s value') +meta.__mul = genError('attempt to multiply a %s value') +meta.__div = genError('attempt to divide a %s value') +meta.__mod = genError('attempt to modulo a %s value') +meta.__pow = genError('attempt to involute a %s value') +meta.__concat = genError('attempt to concat a %s value') +meta.__lt = genError('attempt to compare (<) a %s value(s)') +meta.__le = genError('attempt to compare (<=) a %s value(s)') + +--[[ + @doc + @fname function:IsValid + + @returns + boolean: false +]] +function meta:IsValid() + return false +end + +debug.setmetatable(function() end, meta) + +local unpack = unpack +local table = table + +-- Falco's FN Lib implementation +-- in a greater™ way + +--[[ + @doc + @fname function:SingleWrap + @args vararg prepend + + @desc + makes a function which on call calls `self` with `prepend` arguments unpacked + @enddesc + + @returns + function: a function to call +]] +function fnlib:SingleWrap(...) + local args = {...} + + return function() + return self(unpack(args)) + end +end + +--[[ + @doc + @fname function:Wrap + @args vararg prepend + @alias function:fp + @alias function:Partial + + @desc + makes a function which on call calls `self` with `prepend` arguments unpacked, + and passing extra arguments passed to packer on call + @enddesc + + @returns + function: a function to call +]] +function fnlib:Wrap(...) + local args = {...} + + return function(...) + local copy = table.qcopy(args) + local args2 = table.append(copy, {...}) + return self(unpack(args2)) + end +end + +-- ??? +fnlib.fp = fnlib.Wrap +fnlib.Partial = fnlib.Wrap + +--[[ + @doc + @fname function:FlipFull + @args vararg prepend + + @desc + packs `self` and on call of returned function flips order of passed arguments + @enddesc + + @returns + function: a function to call +]] +function fnlib:FlipFull(...) + local args = {...} + + return function(...) + local copy = table.qcopy(args) + local args2 = table.append(args2, {...}) + return self(unpack(table.flip(args2))) + end +end + +--[[ + @doc + @fname function:Flip + + @desc + packs a function and flips it's first two arguments (further are untouched) + @enddesc + + @returns + function: a function to call +]] +function fnlib:Flip() + return function(a, b, ...) + return self(b, a, ...) + end +end + +--[[ + @doc + @fname function:Id + @args vararg arguments + + @returns + function: self + vararg: arguments +]] +function fnlib:Id(...) + return self, ... +end + +-- func:Compose(func2, func3, func4) -> +-- func(func2(func3(func4(...)))) + +local select = select +local type = type + +local function Compose(currentFunc, nextFunc, ...) + if nextFunc == nil then return currentFunc end + + nextFunc = Compose(nextFunc, ...) + + return function(...) + return currentFunc(nextFunc(...)) + end +end + + +--[[ + @doc + @fname function:Compose + @args function tableOfOrList + @alias function:fc + + @desc + packs all functions to be called in chain. `func1:Compose(func2, func3, func4) -> func1(func2(func3(func4(arguments passed))))` + @enddesc + + @returns + function: a function to call +]] +function fnlib:Compose(funcList, ...) + if type(funcList) == 'table' then + return Compose(self, unpack(funcList)) + else + return Compose(self, funcList, ...) + end +end + +fnlib.fc = fnlib.Compose + + +--[[ + @doc + @fname function:ReverseArgs + @args vararg arguments + + @returns + vararg: flipped arguments list. same as `unpack(table.flip({...}))` +]] +function fnlib:ReverseArgs(...) + return unpack(table.flip({...})) +end + +--[[ + @doc + @fname function:Apply + @args vararg arguments + + @returns + function: a function to call +]] +function fnlib:Apply(...) + return self(...) +end + +for k, v in pairs(fnlib) do + fnlib[k:sub(1, 1):lower() .. k:sub(2)] = v +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/loader.lua b/garrysmod/addons/util-dlib/lua/dlib/core/loader.lua new file mode 100644 index 0000000..df420f8 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/loader.lua @@ -0,0 +1,160 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +DLib.Loader = DLib.Loader or {} +local Loader = DLib.Loader + +local currentModule, currentModuleEnv +local fenv2 = debug.getinfo(1) and getfenv(1) or _G + +function Loader.include(filIn) + if not currentModule then + return include(filIn) + else + local currentModule2, currentModuleEnv2 = currentModule, currentModuleEnv + local compiled = CompileFile(filIn) + + if not compiled then + DLib.Message("Couldn't include file '" .. filIn .. "' (File not found) ()") + return + end + + setfenv(compiled, setmetatable({}, { + __index = function(self, key) + if key == currentModule2 then + return currentModuleEnv2 + end + + if key == 'include' then + return Loader.include + end + + return fenv2[key] + end, + + __newindex = function(self, key, value) + if key == currentModule2 then + return + end + + fenv2[key] = value + end + })) + + return compiled() + end +end + +function Loader.findShared(inFiles) + return table.filterNew(inFiles, function(_, value) return value:find('/?sh_') end) +end + +function Loader.findClient(inFiles) + return table.filterNew(inFiles, function(_, value) return value:find('/?cl_') end) +end + +function Loader.findServer(inFiles) + return table.filterNew(inFiles, function(_, value) return value:find('/?sv_') end) +end + +function Loader.filterShared(inFiles) + return table.filter(inFiles, function(_, value) return value:find('/?sh_') end) +end + +function Loader.filterClient(inFiles) + return table.filter(inFiles, function(_, value) return value:find('/?cl_') end) +end + +function Loader.filterServer(inFiles) + return table.filter(inFiles, function(_, value) return value:find('/?sv_') end) +end + +function Loader.filter(inFiles) + return Loader.findShared(inFiles), Loader.findClient(inFiles), Loader.findServer(inFiles) +end + +function Loader.shmodule(fil) + if SERVER then AddCSLuaFile('dlib/modules/' .. fil) end + return include('dlib/modules/' .. fil) +end + +function Loader.clclass(fil) + if SERVER then + AddCSLuaFile('dlib/classes/' .. fil) + return {register = function() end} + end + + return include('dlib/classes/' .. fil) +end + +function Loader.shclass(fil) + if SERVER then AddCSLuaFile('dlib/classes/' .. fil) end + return include('dlib/classes/' .. fil) +end + +function Loader.svmodule(fil) + if CLIENT then return end + return include('dlib/modules/' .. fil) +end + +local loaderIsGlobal = false +function Loader.start(moduleName, noGlobal) + currentModuleEnv = DLib[moduleName] or {} + loaderIsGlobal = not noGlobal + + if DLib[moduleName] then + local hit = false + + for k, v in pairs(DLib[moduleName]) do + hit = true + break + end + + if not hit then + DLib[moduleName] = {} + currentModuleEnv = DLib[moduleName] + end + end + + DLib[moduleName] = currentModuleEnv + currentModule = moduleName + + if not noGlobal then + _G[moduleName] = currentModuleEnv + end + + return currentModuleEnv +end + +function Loader.finish(allowGlobal, renameHack) + local created = currentModuleEnv + local createdModule = currentModule + + if not allowGlobal and loaderIsGlobal then + _G[currentModule] = nil + end + + currentModule = nil + currentModuleEnv = nil + + DLib[renameHack or createdModule] = created + + return created +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/loader_modes.lua b/garrysmod/addons/util-dlib/lua/dlib/core/loader_modes.lua new file mode 100644 index 0000000..84c4c9d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/loader_modes.lua @@ -0,0 +1,240 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 Loader = DLib.Loader + +--[[ +local function include2(fileIn) + local status = {xpcall(Loader.include, function(err) print(debug.traceback('[DLIB STARTUP ERROR] ' .. err)) end, fileIn)} + local bool = table.remove(status, 1) + + if bool then + return unpack(status) + end +end +]] + +local function include2(fileIn) + return Loader.include(fileIn) +end + +function Loader.load(targetDir) + local output = {} + local files = file.FindRecursiveVisible(targetDir) + + local sh, cl, sv = Loader.filter(files) + + if SERVER then + for i, fil in ipairs(sh) do + AddCSLuaFile(fil) + table.insert(output, {fil, include2(fil)}) + end + + for i, fil in ipairs(cl) do + AddCSLuaFile(fil) + end + + for i, fil in ipairs(sv) do + table.insert(output, {fil, include2(fil)}) + end + else + for i, fil in ipairs(sh) do + table.insert(output, {fil, include2(fil)}) + end + + for i, fil in ipairs(cl) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +function Loader.loadTop(targetDir) + local output = {} + local files = file.Find(targetDir .. '/*', 'LUA') + + local sh, cl, sv = Loader.filter(files) + + if SERVER then + for i, fil in ipairs(sh) do + AddCSLuaFile(fil) + table.insert(output, {fil, include2(fil)}) + end + + for i, fil in ipairs(cl) do + AddCSLuaFile(fil) + end + + for i, fil in ipairs(sv) do + table.insert(output, {fil, include2(fil)}) + end + else + for i, fil in ipairs(sh) do + table.insert(output, {fil, include2(fil)}) + end + + for i, fil in ipairs(cl) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +function Loader.loadCS(targetDir) + local output = {} + local files = file.FindRecursiveVisible(targetDir) + + local sh, cl = Loader.filter(files) + + if SERVER then + for i, fil in ipairs(sh) do + AddCSLuaFile(fil) + end + + for i, fil in ipairs(cl) do + AddCSLuaFile(fil) + end + else + for i, fil in ipairs(sh) do + table.insert(output, {fil, include2(fil)}) + end + + for i, fil in ipairs(cl) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +function Loader.loadPureCS(targetDir) + local output = {} + local files = file.FindRecursiveVisible(targetDir) + + if SERVER then + for i, fil in ipairs(files) do + AddCSLuaFile(fil) + end + else + for i, fil in ipairs(files) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +function Loader.loadPureSV(targetDir) + if CLIENT then return end + local output = {} + local files = file.FindRecursiveVisible(targetDir) + + for i, fil in ipairs(files) do + table.insert(output, {fil, include2(fil)}) + end + + return output +end + +function Loader.loadPureSH(targetDir) + local output = {} + local files = file.FindRecursiveVisible(targetDir) + + if SERVER then + for i, fil in ipairs(files) do + AddCSLuaFile(fil) + table.insert(output, {fil, include2(fil)}) + end + else + for i, fil in ipairs(files) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +function Loader.loadPureCSTop(targetDir) + local output = {} + local files = file.FindVisiblePrepend(targetDir, 'LUA') + + if SERVER then + for i, fil in ipairs(files) do + AddCSLuaFile(fil) + end + else + for i, fil in ipairs(files) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +Loader.loadPureCLTop = Loader.loadPureCSTop + +function Loader.loadPureSVTop(targetDir) + if CLIENT then return end + local output = {} + local files = file.FindVisiblePrepend(targetDir, 'LUA') + + for i, fil in ipairs(files) do + table.insert(output, {fil, include2(fil)}) + end + + return output +end + +function Loader.loadPureSHTop(targetDir) + local output = {} + local files = file.FindVisiblePrepend(targetDir, 'LUA') + + if SERVER then + for i, fil in ipairs(files) do + AddCSLuaFile(fil) + table.insert(output, {fil, include2(fil)}) + end + else + for i, fil in ipairs(files) do + table.insert(output, {fil, include2(fil)}) + end + end + + return output +end + +function Loader.csModule(targetDir) + if CLIENT then return {} end + + local output = {} + local files = file.FindRecursiveVisible(targetDir) + + if #files == 0 then error('Empty module ' .. targetDir) end + + for i, fil in ipairs(files) do + AddCSLuaFile(fil) + end + + return output +end + +return Loader diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/luaify.lua b/garrysmod/addons/util-dlib/lua/dlib/core/luaify.lua new file mode 100644 index 0000000..c0a0a24 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/luaify.lua @@ -0,0 +1,187 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +-- https://github.com/PAC3-Server/notagain/blob/master/lua/notagain/optimizations/preinit/luaify.lua + +local rawequal = rawequal +local getmetatable = getmetatable +local setmetatable = debug.setmetatable +local pcall = pcall +local rawget = rawget +_G.rawtype = _G.rawtype or type +_G.type = _G.rawtype + +local rawtype = rawtype +local useFallback = false + +local function getmeta(meta) + return meta.MetaName +end + +--[[ + @doc + @fname luatype + @args any value + + @desc + same as !g:type but returns proper types for custom defined types using `.MetaName` property in metatable + @enddesc + + @returns + string: type +]] +local function luatype(var) + if rawequal(var, nil) then + return 'nil' + end + + if rawequal(var, true) or rawequal(var, false) then + return 'boolean' + end + + local meta = getmetatable(var) + + if rawequal(meta, true) or rawequal(meta, false) then + return rawtype(var) + end + + if rawequal(meta, nil) then + if useFallback then + return rawtype(var) + end + + return 'table' + end + + local mstatus, metaname = pcall(getmeta, meta) + if not mstatus then return rawtype(var) end + + if metaname == nil then + local metaname2 = meta.__type + + if metaname2 == nil then + return 'table' + end + + return metaname2 + end + + return metaname +end + +local ctime = coroutine.create(getmetatable) + +if ctime ~= false then + local cmeta = getmetatable(ctime) or {} + cmeta.MetaName = 'thread' + setmetatable(ctime, cmeta) +end + +local bmeta = getmetatable(true) or {} +bmeta.MetaName = 'boolean' +setmetatable(true, cmeta) + +local fmeta = debug.getmetatable(function() end) or {} +fmeta.MetaName = 'function' +setmetatable(function() end, fmeta) + +local strmeta = getmetatable('') or {} +strmeta.MetaName = 'string' + +function strmeta:IsValid() + return false +end + +--[[ + @doc + @fname string.IsValid + + @returns + boolean: false +]] + +--[[ + @doc + @fname string:IsValid + + @returns + boolean: false +]] +function string:IsValid() + return false +end + +setmetatable('', strmeta) + +ProtectedCall(function() + assert(luatype(1) == 'number', luatype(1)) + assert(luatype('') == 'string', luatype('')) + assert(luatype(NULL) == 'Entity', luatype(NULL)) + assert(luatype({}) == 'table', luatype({})) + -- assert(type(coroutine.create(getmetatable)) == 'thread', type(coroutine.create(getmetatable))) + + _G.luatype = luatype + + --[[ + local overridetypes = { + 'string', + 'number', + 'Angle', + 'Vector', + 'Panel', + 'Matrix', + 'function', + 'table', + } + + function _G.isbool(var) + return luatype(var) == 'boolean' + end + + function _G.isboolean(var) + return luatype(var) == 'boolean' + end + + for i, rawname in ipairs(overridetypes) do + local function ischeck(var) + return luatype(var) == rawname + end + + _G['Is' .. rawname:sub(1, 1) .. rawname:sub(2)] = ischeck + _G['is' .. rawname:lower()] = ischeck + _G['is' .. rawname:sub(1, 1) .. rawname:sub(2)] = ischeck + end + + function _G.IsEntity(var) + local tp = luatype(var) + + return tp == 'Entity' or + tp == 'NextBot' or + tp == 'NPC' or + tp == 'Vehicle' or + tp == 'Player' or + tp == 'Weapon' + end + + _G.isEntity = IsEntity + _G.isentity = IsEntity + ]] +end) diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/promise.lua b/garrysmod/addons/util-dlib/lua/dlib/core/promise.lua new file mode 100644 index 0000000..82ba848 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/promise.lua @@ -0,0 +1,206 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 meta = FindMetaTable('Promise') or {} +debug.getregistry().Promise = meta + +local setmetatable = setmetatable +local DLib = DLib +local type = type +local error = error +local xpcall = xpcall +local debug = debug +local ProtectedCall = ProtectedCall + +meta.MetaName = 'Promise' +meta.__index = meta + +local coroutine = coroutine + +--[[ + @doc + @fname DLib.Promise + @args function handler + + @desc + Same as [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) but: + use `Promise:Catch(function)` and + `Promise:Then(function)` + + Alse there is `Promise:Awat(varargForYield)` for use inside coroutine available. + @enddesc + + @returns + Promise: created promise +]] +local function constructor(handler) + local mtype = type(handler) + assert(mtype == 'function' or mtype ~= 'thread', 'Promise handler were not provided (function/thread); got ' .. mtype) + + local self = setmetatable({}, meta) + + self.handlerType = mtype + self.handler = handler + self.success = false + self.executed = false + self.executed_finish = false + self.failure = false + self.traceback = debug.traceback(nil, 2) + + self:execute() + + return self +end + +DLib.Promise = constructor +_G.Promise = constructor + +function meta:execute() + if self.executed then error('wtf dude') end + + self.executed = true + + if self.handlerType == 'function' then + xpcall(self.handler, function(err) + self.errors = {debug.traceback(err, 2)} + self.failure = true + self.executed_finish = true + + if self.reject then + self.reject(debug.traceback(err, 2)) + end + end, function(...) + self.returns = {...} + self.success = true + self.executed_finish = true + + if self.resolve then + self.resolve(...) + end + end, function(...) + self.errors = {...} + self.failure = true + self.executed_finish = true + + if self.reject then + self.reject(...) + end + end) + else + hook.Add('Think', self, function() + local args = {coroutine.resume(self.handler)} + + if not args[1] then + self.errors = {args[2]} + self.failure = true + self.executed_finish = true + + if self.reject then + self.reject(err) + end + + return + end + + local status = coroutine.status(status) + + if status == 'dead' then + table.remove(args, 1) + + self.returns = args + self.success = true + self.executed_finish = true + + if self.resolve then + self.resolve(unpack(args, 1, #args)) + end + end + end) + end + + return self +end + +function meta:catch(handler) + if type(handler) ~= 'function' then + error('Invalid handler; got ' .. type(handler)) + end + + self.reject = handler + + if self.executed and self.failure then + handler(unpack(self.errors, 1, #self.errors)) + end + + return self +end + +function meta:reslv(handler) + if type(handler) ~= 'function' then + error('Invalid handler; got ' .. type(handler)) + end + + self.resolve = handler + + if self.executed and self.success then + handler(unpack(self.returns, 1, #self.returns)) + end + + return self +end + +function meta:IsValid() + return not self.executed_finish +end + +function meta:Await(...) + local thread = assert(coroutine.running(), 'not in a coroutine thread') + + local fulfilled = false + local err + + self:reslv(function() + fulfilled = true + coroutine.resume(thread) + end) + + self:catch(function(err2) + err = err2 + fulfilled = true + coroutine.resume(thread) + end) + + while not fulfilled do + coroutine.yield(...) + end + + if err then + error(err) + end + + return unpack(self.returns, 1, #self.returns) +end + +meta.await = meta.Await +meta.resolv = meta.reslv +meta.after = meta.reslv +meta.Then = meta.reslv +meta.error = meta.catch +meta.Catch = meta.catch diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/sandbox.lua b/garrysmod/addons/util-dlib/lua/dlib/core/sandbox.lua new file mode 100644 index 0000000..69d65f3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/sandbox.lua @@ -0,0 +1,121 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +-- this is a modified DLib's loader code +-- and VLL's adapted features (VLL.Include, VLL.Compile) + +-- This allows you to create secure (or "private global" spaces withit file(s) you load recursively) +-- with your own variables and behaviour +-- in other words, it is much more advanced "module()" + +-- WARNING: Regarding "sandbox", this isn't a sandbox +-- but a tool to load files with some "global" variables modified + +-- ATTENTION - This will NOT work properly within context of +-- addons/code which thinkers with files environment (such as VLL) + +local DLib = DLib +local CompileFile = CompileFile +local CompileString = CompileString +DLib.sandbox = DLib.sandbox or {} +local sandbox = DLib.sandbox + +function sandbox.include(path, ...) + return sandbox.compileFile(path, ...)() +end + +local function wrap(compiled, environment, accessGlobal, global) + if accessGlobal == nil then accessGlobal = true end + function environment.include(fileIn) + return sandbox.include(fileIn, environment, accessGlobal, global) + end + + function environment.CompileFile(fileIn) + return sandbox.compileFile(fileIn, environment, accessGlobal, global) + end + + function environment.RunString(fileIn) + return sandbox.runString(fileIn, environment, accessGlobal, global) + end + + function environment.CompileString(fileIn, identifier) + return sandbox.compileString(fileIn, identifier, environment, accessGlobal, global) + end + + global = global or getfenv(compiled) or getfenv() + local meta = getmetatable(environment) or {} + + if accessGlobal then + if not meta.__newindex then + function meta:__newindex(key, value) + global[key] = value + end + end + + if not meta.__index then + function meta:__index(key) + local val = rawget(environment, key) + + if val ~= nil then + return val + end + + return global[key] + end + end + end + + setmetatable(environment, meta) + setfenv(compiled, environment) + + return compiled +end + +function sandbox.compileFile(path, ...) + local compiled = CompileFile(path) + + if not compiled then + DLib.Message("Couldn't include file '" .. path .. "' (File not found) ()") + return + end + + return wrap(compiled, ...) +end + +function sandbox.compileString(str, identifier, ...) + local compiled = CompileString(str, identifier) + + if not compiled or type(compiled) == 'string' then + return compiled + end + + return wrap(compiled, ...) +end + +function sandbox.runString(str, ...) + local compiled = CompileString(str, 'RunString') + + if type(compiled) == 'string' then + return error(compiled) + end + + return wrap(compiled, ...)() +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/tableutil.lua b/garrysmod/addons/util-dlib/lua/dlib/core/tableutil.lua new file mode 100644 index 0000000..e5205ee --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/tableutil.lua @@ -0,0 +1,668 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 ipairs = ipairs +local pairs = pairs +local table = table +local select = select +local remove = table.remove +local insert = function(self, val) + local newIndex = #self + 1 + self[newIndex] = val + return newIndex +end + +table.unpack = unpack +table.pairs = pairs +table.ipairs = ipairs + +--[[ + @doc + @fname table.append + @args table destination, table source + + @desc + Appends values from source table to destination table. Works only with nurmerical indexed tables +]] +function table.append(destination, source) + if #source == 0 then return destination end + + local i, nextelement = 1, source[1] + + ::append:: + + destination[#destination + 1] = source[i] + i = i + 1 + nextelement = source[i] + + if nextelement ~= nil then + goto append + end + + return destination +end + +--[[ + @doc + @fname table.prependString + @args table destination, string prepend + + @desc + Iterates over destination and prepends string to all values (assuming array contains only strings) +]] +function table.prependString(destination, prepend) + for i, value in ipairs(destination) do + destination[i] = prepend .. value + end + + return destination +end + +--[[ + @doc + @fname table.appendString + @args table destination, string append + + @desc + Iterates over destination and appends string to all values (assuming array contains only strings) +]] +function table.appendString(destination, append) + for i, value in ipairs(destination) do + destination[i] = value .. append + end + + return destination +end + +--[[ + @doc + @fname table.filter + @args table target, function filterFunc + + @desc + Filters table passed + Second argument is a function(key, value, target) + @enddesc + + @returns + table: deleted elements +]] +function table.filter(target, filterFunc) + if not filterFunc then error('table.filter - missing filter function') end + + local toRemove = {} + + for key, value in pairs(target) do + local status = filterFunc(key, value, target) + if not status then + if type(key) == 'number' then + insert(filtered, value) + insert(toRemove, key) + else + filtered[key] = value + target[key] = nil + end + end + end + + for v, i in ipairs(toRemove) do + remove(target, i - v + 1) + end + + return filtered +end + +--[[ + @doc + @fname table.qfilter + @args table target, function filterFunc + + @desc + Filters table passed using goto + Second argument is a function(key, value, target) + @enddesc + + @returns + table: deleted elements +]] +function table.qfilter(target, filterFunc) + if not filterFunc then error('table.qfilter - missing filter function') end + if #target == 0 then return {} end + + local filtered = {} + local toRemove = {} + + local i = 1 + local nextelement = target[i] + + ::filter:: + + local status = filterFunc(i, nextelement, target) + + if not status then + filtered[#filtered + 1] = nextelement + toRemove[#toRemove + 1] = i + end + + i = i + 1 + nextelement = target[i] + + if nextelement ~= nil then + goto filter + end + + if #toRemove ~= 0 then + i = 1 + nextelement = toRemove[i] + + ::rem:: + remove(target, toRemove[i] - i + 1) + + i = i + 1 + nextelement = toRemove[i] + + if nextelement ~= nil then + goto rem + end + end + + return filtered +end + +--[[ + @doc + @fname table.filterNew + @args table target, function filterFunc + + @desc + Filters table passed + Second argument is a function(key, value, target) which should return boolean whenever element pass check + @enddesc + + @returns + table: passed elements +]] +function table.filterNew(target, filterFunc) + if not filterFunc then error('table.filterNew - missing filter function') end + + local filtered = {} + + for key, value in pairs(target) do + local status = filterFunc(key, value, target) + if status then + insert(filtered, value) + end + end + + return filtered +end + +--[[ + @doc + @fname table.qfilterNew + @args table target, function filterFunc + + @desc + Filters table passed using ipairs + Second argument is a function(key, value, target) which should return boolean whenever element pass check + @enddesc + + @returns + table: passed elements +]] +function table.qfilterNew(target, filterFunc) + if not filterFunc then error('table.qfilterNew - missing filter function') end + + local filtered = {} + + for key, value in ipairs(target) do + local status = filterFunc(key, value, target) + if status then + insert(filtered, value) + end + end + + return filtered +end + +--[[ + @doc + @fname table.qmerge + @args table into, table from + + @desc + Filters table passed using ipairs + Second argument is a function(key, value, target) which should return boolean whenever element pass check + @enddesc + + @returns + table: into +]] +function table.qmerge(into, inv) + for i, val in ipairs(inv) do + into[i] = val + end + + return into +end + +--[[ + @doc + @fname table.gcopy + @args table input + @Alias table.qcopy + + @desc + Fastly copies a table assuming it is numeric indexed array + does not copy subtables or values + @enddesc + + @returns + table: copied input +]] +function table.gcopy(input) + if #input == 0 then return {} end + + local reply = {} + + local nextValue, i = input[1], 1 + + ::loop:: + + reply[i] = nextValue + + i = i + 1 + nextValue = input[i] + + if nextValue ~= nil then + goto loop + end + + return reply +end + +table.qcopy = table.gcopy + +--[[ + @doc + @fname table.gcopyRange + @args table input, number start, number endPos + + @desc + Copies array in specified range + @enddesc + + @returns + table: copied input +]] +function table.gcopyRange(input, start, endPos) + if #input < start then return {} end + endPos = endPos or #input + local endPos2 = endPos + 1 + + local reply = {} + + local nextValue, i = input[start], start + local i2 = 0 + + ::loop:: + i2 = i2 + 1 + reply[i2] = nextValue + + i = i + 1 + if i == endPos2 then return reply end + nextValue = input[i] + + if nextValue ~= nil then + goto loop + end + + return reply +end + +--[[ + @doc + @fname table.unshift + @args table input, vararg values + + @desc + Inserts values in the start of an array + @enddesc + + @returns + table: input +]] +function table.unshift(tableIn, ...) + local values = {...} + local count = #values + + if count == 0 then return tableIn end + + for i = #tableIn + count, count, -1 do + tableIn[i] = tableIn[i - count] + end + + local i, nextelement = 1, values[1] + + ::unshift:: + + tableIn[i] = nextelement + i = i + 1 + nextelement = values[i] + + if nextelement ~= nil then + goto unshift + end + + return tableIn +end + +--[[ + @doc + @fname table.construct + @args table input, function callback, number times, vararg prependArgs + + @desc + Calls callback with prependArgs specified times to construct a new array or append values to existing array + @enddesc + + @returns + table: input or newly created array +]] +function table.construct(input, funcToCall, times, ...) + input = input or {} + + for i = 1, times do + input[#input + 1] = funcToCall(...) + end + + return input +end + +--[[ + @doc + @fname table.frandom + @args table input + + @desc + Returns random value from passed array + @enddesc + + @returns + any: returned value from array +]] +function table.frandom(tableIn) + return tableIn[math.random(1, #tableIn)] +end + + +--[[ + @doc + @fname table.qhasValue + @args table input, any value + + @desc + Checks for value present in specified array quickly using ipairs + @enddesc + + @returns + boolean: whenever value is present in input +]] +function table.qhasValue(findIn, value) + for i, val in ipairs(findIn) do + if val == value then return true end + end + + return false +end + +function table.construct2(funcToCall, times, ...) + local output = {} + + for i = 1, times do + output[#output + 1] = funcToCall(i, ...) + end + + return output +end + +--[[ + @doc + @fname table.flipIntoHash + @args table input + + @desc + Iterates array over and creates new table {[value] = index} + @enddesc + + @returns + table: flipped hash table +]] +function table.flipIntoHash(tableIn) + local output = {} + + for i, value in ipairs(tableIn) do + output[value] = i + end + + return output +end + +--[[ + @doc + @fname table.flip + @args table input + + @desc + Returns new flipped array + @enddesc + + @returns + table: flipped array +]] +function table.flip(tableIn) + local values = {} + + for i = #tableIn, 1, -1 do + values[#values + 1] = tableIn[i] + end + + return values +end + +--[[ + @doc + @fname table.sortedFind + @args table findIn, table findWhat, any ifNone + + @desc + Gets hash table (flipIntoHash) of passed table and attempts to search for ANY value specified in findWhat + @enddesc + + @returns + any: found value +]] +function table.sortedFind(findIn, findWhat, ifNone) + local hash = table.flipIntoHash(findIn) + + for i, valueFind in ipairs(findWhat) do + if hash[valueFind] then + return valueFind + end + end + + return ifNone +end + +--[[ + @doc + @fname table.removeValues + @args table findIn, vargarg values + + @desc + Removes values at specified indexes. **INDEX LIST MUST BE SORTED!** (from the smallest index to biggest). + Can also accept array of values as second argument. + @enddesc + + @returns + table: removed values +]] +function table.removeValues(tableIn, ...) + local first = select(1, ...) + local args + + if type(first) == 'table' then + args = first + else + args = {...} + end + + local removed = {} + + for i = #args, 1, -1 do + insert(removed, tableIn[args[i]]) + remove(tableIn, args[i]) + end + + return removed +end + +--[[ + @doc + @fname table.removeByMember + @args table findIn, any memberID, any memberValue + + @desc + Iterates over array and looks for tables with memberID index present and removes it when it is equal to memberValue + @enddesc + + @returns + any: removed value. *nil if value is not found* +]] +function table.removeByMember(tableIn, memberID, memberValue) + local removed + + for i = 1, #tableIn do + local v = tableIn[i] + if type(v) == 'table' and v[memberID] == memberValue then + table.remove(tableIn, i) + removed = v + break + end + end + + return removed +end + +--[[ + @doc + @fname table.deduplicate + @args table tableIn + + @desc + Iterates over array and removed duplicated values + @enddesc + + @returns + table: passed table + table: array contain removed values +]] +function table.deduplicate(tableIn) + local values = {} + local toremove = {} + + for i, v in ipairs(tableIn) do + if values[v] then + insert(toremove, i) + else + values[v] = true + end + end + + table.removeValues(tableIn, toremove) + return tableIn +end + +--[[ + @doc + @fname table.splice + @args table tableIn, number start, number deleteCount, vararg insertValues + + @desc + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice + @enddesc + + @returns + table: removed elements +]] +function table.splice(tableIn, start, deleteCount, ...) + if not deleteCount then + deleteCount = #tableIn - start + 1 + end + + local removed = {} + local inserts = select('#', ...) + local actuallyMove = inserts - deleteCount + + -- inserting more elements than deleting + if actuallyMove > 0 then + -- moving old values to right + for i = #tableIn, start + actuallyMove - 2, -1 do + tableIn[i + actuallyMove] = tableIn[i] + end + + for i = start, start + inserts - 1 do + if i < start + deleteCount and tableIn[i] ~= nil then + table.insert(removed, tableIn[i]) + end + + tableIn[i] = select(i - start + 1, ...) + end + -- inserting same amount of elements as deleting + elseif actuallyMove == 0 then + for i = start, start + deleteCount - 1 do + if tableIn[i] ~= nil then + table.insert(removed, tableIn[i]) + end + + tableIn[i] = select(i - start + 1, ...) + end + -- inserting lesser elements than deleting + else + local sizeof = #tableIn + for i = start, start + inserts do + if tableIn[i] ~= nil then + table.insert(removed, tableIn[i]) + end + + tableIn[i] = select(i - start + 1, ...) + end + + for i = start + inserts, sizeof do + if i - (start + inserts) < -actuallyMove and tableIn[i] ~= nil then + table.insert(removed, tableIn[i]) + end + + tableIn[i] = tableIn[i - actuallyMove] + end + end + + return removed +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/vmdef.lua b/garrysmod/addons/util-dlib/lua/dlib/core/vmdef.lua new file mode 100644 index 0000000..cf85780 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/vmdef.lua @@ -0,0 +1,331 @@ +-- This is a generated file. DO NOT EDIT! + +module(...) + +bcnames = "ISLT ISGE ISLE ISGT ISEQV ISNEV ISEQS ISNES ISEQN ISNEN ISEQP ISNEP ISTC ISFC IST ISF MOV NOT UNM LEN ADDVN SUBVN MULVN DIVVN MODVN ADDNV SUBNV MULNV DIVNV MODNV ADDVV SUBVV MULVV DIVVV MODVV POW CAT KSTR KCDATAKSHORTKNUM KPRI KNIL UGET USETV USETS USETN USETP UCLO FNEW TNEW TDUP GGET GSET TGETV TGETS TGETB TSETV TSETS TSETB TSETM CALLM CALL CALLMTCALLT ITERC ITERN VARG ISNEXTRETM RET RET0 RET1 FORI JFORI FORL IFORL JFORL ITERL IITERLJITERLLOOP ILOOP JLOOP JMP FUNCF IFUNCFJFUNCFFUNCV IFUNCVJFUNCVFUNCC FUNCCW" + +irnames = "LT GE LE GT ULT UGE ULE UGT EQ NE ABC RETF NOP BASE PVAL GCSTEPHIOP LOOP USE PHI RENAMEKPRI KINT KGC KPTR KKPTR KNULL KNUM KINT64KSLOT BNOT BSWAP BAND BOR BXOR BSHL BSHR BSAR BROL BROR ADD SUB MUL DIV MOD POW NEG ABS ATAN2 LDEXP MIN MAX FPMATHADDOV SUBOV MULOV AREF HREFK HREF NEWREFUREFO UREFC FREF STRREFALOAD HLOAD ULOAD FLOAD XLOAD SLOAD VLOAD ASTOREHSTOREUSTOREFSTOREXSTORESNEW XSNEW TNEW TDUP CNEW CNEWI TBAR OBAR XBAR CONV TOBIT TOSTR STRTO CALLN CALLL CALLS CALLXSCARG " + +irfpm = { [0]="floor", "ceil", "trunc", "sqrt", "exp", "exp2", "log", "log2", "log10", "sin", "cos", "tan", "other", } + +irfield = { [0]="str.len", "func.env", "func.pc", "tab.meta", "tab.array", "tab.node", "tab.asize", "tab.hmask", "tab.nomm", "udata.meta", "udata.udtype", "udata.file", "cdata.ctypeid", "cdata.ptr", "cdata.int", "cdata.int64", "cdata.int64_4", } + +ircall = { +[0]="lj_str_cmp", +"lj_str_new", +"lj_strscan_num", +"lj_str_fromint", +"lj_str_fromnum", +"lj_tab_new1", +"lj_tab_dup", +"lj_tab_newkey", +"lj_tab_len", +"lj_gc_step_jit", +"lj_gc_barrieruv", +"lj_mem_newgco", +"lj_math_random_step", +"lj_vm_modi", +"sinh", +"cosh", +"tanh", +"fputc", +"fwrite", +"fflush", +"lj_vm_floor", +"lj_vm_ceil", +"lj_vm_trunc", +"sqrt", +"exp", +"lj_vm_exp2", +"log", +"lj_vm_log2", +"log10", +"sin", +"cos", +"tan", +"lj_vm_powi", +"pow", +"atan2", +"ldexp", +"lj_vm_tobit", +"softfp_add", +"softfp_sub", +"softfp_mul", +"softfp_div", +"softfp_cmp", +"softfp_i2d", +"softfp_d2i", +"softfp_ui2d", +"softfp_f2d", +"softfp_d2ui", +"softfp_d2f", +"softfp_i2f", +"softfp_ui2f", +"softfp_f2i", +"softfp_f2ui", +"fp64_l2d", +"fp64_ul2d", +"fp64_l2f", +"fp64_ul2f", +"fp64_d2l", +"fp64_d2ul", +"fp64_f2l", +"fp64_f2ul", +"lj_carith_divi64", +"lj_carith_divu64", +"lj_carith_modi64", +"lj_carith_modu64", +"lj_carith_powi64", +"lj_carith_powu64", +"lj_cdata_setfin", +"strlen", +"memcpy", +"memset", +"lj_vm_errno", +"lj_carith_mul64", +} + +traceerr = { +[0]="error thrown or hook called during recording", +"trace too long", +"trace too deep", +"too many snapshots", +"blacklisted", +"NYI: bytecode %d", +"leaving loop in root trace", +"inner loop in root trace", +"loop unroll limit reached", +"bad argument type", +"JIT compilation disabled for function", +"call unroll limit reached", +"down-recursion, restarting", +"NYI: C function %s", +"NYI: FastFunc %s", +"NYI: unsupported variant of FastFunc %s", +"NYI: return to lower frame", +"store with nil or NaN key", +"missing metamethod", +"looping index lookup", +"NYI: mixed sparse/dense table", +"symbol not in cache", +"NYI: unsupported C type conversion", +"NYI: unsupported C function type", +"guard would always fail", +"too many PHIs", +"persistent type instability", +"failed to allocate mcode memory", +"machine code too long", +"hit mcode limit (retrying)", +"too many spill slots", +"inconsistent register allocation", +"NYI: cannot assemble IR instruction %d", +"NYI: PHI shuffling too complex", +"NYI: register coalescing too complex", +} + +ffnames = { +[0]="Lua", +"C", +"assert", +"type", +"next", +"pairs", +"ipairs_aux", +"ipairs", +"getmetatable", +"setmetatable", +"getfenv", +"setfenv", +"rawget", +"rawset", +"rawequal", +"unpack", +"select", +"tonumber", +"tostring", +"error", +"pcall", +"xpcall", +"loadfile", +"load", +"loadstring", +"dofile", +"gcinfo", +"collectgarbage", +"newproxy", +"print", +"coroutine.status", +"coroutine.running", +"coroutine.create", +"coroutine.yield", +"coroutine.resume", +"coroutine.wrap_aux", +"coroutine.wrap", +"math.abs", +"math.floor", +"math.ceil", +"math.sqrt", +"math.log10", +"math.exp", +"math.sin", +"math.cos", +"math.tan", +"math.asin", +"math.acos", +"math.atan", +"math.sinh", +"math.cosh", +"math.tanh", +"math.frexp", +"math.modf", +"math.deg", +"math.rad", +"math.log", +"math.atan2", +"math.pow", +"math.fmod", +"math.ldexp", +"math.min", +"math.max", +"math.random", +"math.randomseed", +"bit.tobit", +"bit.bnot", +"bit.bswap", +"bit.lshift", +"bit.rshift", +"bit.arshift", +"bit.rol", +"bit.ror", +"bit.band", +"bit.bor", +"bit.bxor", +"bit.tohex", +"string.len", +"string.byte", +"string.char", +"string.sub", +"string.rep", +"string.reverse", +"string.lower", +"string.upper", +"string.dump", +"string.find", +"string.match", +"string.gmatch_aux", +"string.gmatch", +"string.gsub", +"string.format", +"table.foreachi", +"table.foreach", +"table.getn", +"table.maxn", +"table.insert", +"table.remove", +"table.concat", +"table.sort", +"io.method.close", +"io.method.read", +"io.method.write", +"io.method.flush", +"io.method.seek", +"io.method.setvbuf", +"io.method.lines", +"io.method.__gc", +"io.method.__tostring", +"io.open", +"io.popen", +"io.tmpfile", +"io.close", +"io.read", +"io.write", +"io.flush", +"io.input", +"io.output", +"io.lines", +"io.type", +"os.execute", +"os.remove", +"os.rename", +"os.tmpname", +"os.getenv", +"os.exit", +"os.clock", +"os.date", +"os.time", +"os.difftime", +"os.setlocale", +"debug.getregistry", +"debug.getmetatable", +"debug.setmetatable", +"debug.getfenv", +"debug.setfenv", +"debug.getinfo", +"debug.getlocal", +"debug.setlocal", +"debug.getupvalue", +"debug.setupvalue", +"debug.upvalueid", +"debug.upvaluejoin", +"debug.sethook", +"debug.gethook", +"debug.debug", +"debug.traceback", +"jit.on", +"jit.off", +"jit.flush", +"jit.status", +"jit.attach", +"jit.util.funcinfo", +"jit.util.funcbc", +"jit.util.funck", +"jit.util.funcuvname", +"jit.util.traceinfo", +"jit.util.traceir", +"jit.util.tracek", +"jit.util.tracesnap", +"jit.util.tracemc", +"jit.util.traceexitstub", +"jit.util.ircalladdr", +"jit.opt.start", +"ffi.meta.__index", +"ffi.meta.__newindex", +"ffi.meta.__eq", +"ffi.meta.__len", +"ffi.meta.__lt", +"ffi.meta.__le", +"ffi.meta.__concat", +"ffi.meta.__call", +"ffi.meta.__add", +"ffi.meta.__sub", +"ffi.meta.__mul", +"ffi.meta.__div", +"ffi.meta.__mod", +"ffi.meta.__pow", +"ffi.meta.__unm", +"ffi.meta.__tostring", +"ffi.meta.__pairs", +"ffi.meta.__ipairs", +"ffi.clib.__index", +"ffi.clib.__newindex", +"ffi.clib.__gc", +"ffi.callback.free", +"ffi.callback.set", +"ffi.cdef", +"ffi.new", +"ffi.cast", +"ffi.typeof", +"ffi.istype", +"ffi.sizeof", +"ffi.alignof", +"ffi.offsetof", +"ffi.errno", +"ffi.string", +"ffi.copy", +"ffi.fill", +"ffi.abi", +"ffi.metatype", +"ffi.gc", +"ffi.load", +} + diff --git a/garrysmod/addons/util-dlib/lua/dlib/core/vmdef_x64.lua b/garrysmod/addons/util-dlib/lua/dlib/core/vmdef_x64.lua new file mode 100644 index 0000000..f4d08c0 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/core/vmdef_x64.lua @@ -0,0 +1,363 @@ +-- This is a generated file. DO NOT EDIT! + +return { + + bcnames = "ISLT ISGE ISLE ISGT ISEQV ISNEV ISEQS ISNES ISEQN ISNEN ISEQP ISNEP ISTC ISFC IST ISF ISTYPEISNUM MOV NOT UNM LEN ADDVN SUBVN MULVN DIVVN MODVN ADDNV SUBNV MULNV DIVNV MODNV ADDVV SUBVV MULVV DIVVV MODVV POW CAT KSTR KCDATAKSHORTKNUM KPRI KNIL UGET USETV USETS USETN USETP UCLO FNEW TNEW TDUP GGET GSET TGETV TGETS TGETB TGETR TSETV TSETS TSETB TSETM TSETR CALLM CALL CALLMTCALLT ITERC ITERN VARG ISNEXTRETM RET RET0 RET1 FORI JFORI FORL IFORL JFORL ITERL IITERLJITERLLOOP ILOOP JLOOP JMP FUNCF IFUNCFJFUNCFFUNCV IFUNCVJFUNCVFUNCC FUNCCW", + + irnames = "LT GE LE GT ULT UGE ULE UGT EQ NE ABC RETF NOP BASE PVAL GCSTEPHIOP LOOP USE PHI RENAMEPROF KPRI KINT KGC KPTR KKPTR KNULL KNUM KINT64KSLOT BNOT BSWAP BAND BOR BXOR BSHL BSHR BSAR BROL BROR ADD SUB MUL DIV MOD POW NEG ABS ATAN2 LDEXP MIN MAX FPMATHADDOV SUBOV MULOV AREF HREFK HREF NEWREFUREFO UREFC FREF STRREFLREF ALOAD HLOAD ULOAD FLOAD XLOAD SLOAD VLOAD ASTOREHSTOREUSTOREFSTOREXSTORESNEW XSNEW TNEW TDUP CNEW CNEWI BUFHDRBUFPUTBUFSTRTBAR OBAR XBAR CONV TOBIT TOSTR STRTO CALLN CALLA CALLL CALLS CALLXSCARG ", + + irfpm = { [0]="floor", "ceil", "trunc", "sqrt", "exp", "exp2", "log", "log2", "log10", "sin", "cos", "tan", "other", }, + + irfield = { [0]="str.len", "func.env", "func.pc", "func.ffid", "thread.env", "tab.meta", "tab.array", "tab.node", "tab.asize", "tab.hmask", "tab.nomm", "udata.meta", "udata.udtype", "udata.file", "cdata.ctypeid", "cdata.ptr", "cdata.int", "cdata.int64", "cdata.int64_4", }, + + ircall = { + [0]="lj_str_cmp", + "lj_str_find", + "lj_str_new", + "lj_strscan_num", + "lj_strfmt_int", + "lj_strfmt_num", + "lj_strfmt_char", + "lj_strfmt_putint", + "lj_strfmt_putnum", + "lj_strfmt_putquoted", + "lj_strfmt_putfxint", + "lj_strfmt_putfnum_int", + "lj_strfmt_putfnum_uint", + "lj_strfmt_putfnum", + "lj_strfmt_putfstr", + "lj_strfmt_putfchar", + "lj_buf_putmem", + "lj_buf_putstr", + "lj_buf_putchar", + "lj_buf_putstr_reverse", + "lj_buf_putstr_lower", + "lj_buf_putstr_upper", + "lj_buf_putstr_rep", + "lj_buf_puttab", + "lj_buf_tostr", + "lj_tab_new_ah", + "lj_tab_new1", + "lj_tab_dup", + "lj_tab_clear", + "lj_tab_newkey", + "lj_tab_len", + "lj_gc_step_jit", + "lj_gc_barrieruv", + "lj_mem_newgco", + "lj_math_random_step", + "lj_vm_modi", + "sinh", + "cosh", + "tanh", + "fputc", + "fwrite", + "fflush", + "lj_vm_floor", + "lj_vm_ceil", + "lj_vm_trunc", + "sqrt", + "exp", + "lj_vm_exp2", + "log", + "lj_vm_log2", + "log10", + "sin", + "cos", + "tan", + "lj_vm_powi", + "pow", + "atan2", + "ldexp", + "lj_vm_tobit", + "softfp_add", + "softfp_sub", + "softfp_mul", + "softfp_div", + "softfp_cmp", + "softfp_i2d", + "softfp_d2i", + "lj_vm_sfmin", + "lj_vm_sfmax", + "lj_vm_tointg", + "softfp_ui2d", + "softfp_f2d", + "softfp_d2ui", + "softfp_d2f", + "softfp_i2f", + "softfp_ui2f", + "softfp_f2i", + "softfp_f2ui", + "fp64_l2d", + "fp64_ul2d", + "fp64_l2f", + "fp64_ul2f", + "fp64_d2l", + "fp64_d2ul", + "fp64_f2l", + "fp64_f2ul", + "lj_carith_divi64", + "lj_carith_divu64", + "lj_carith_modi64", + "lj_carith_modu64", + "lj_carith_powi64", + "lj_carith_powu64", + "lj_cdata_newv", + "lj_cdata_setfin", + "strlen", + "memcpy", + "memset", + "lj_vm_errno", + "lj_carith_mul64", + "lj_carith_shl64", + "lj_carith_shr64", + "lj_carith_sar64", + "lj_carith_rol64", + "lj_carith_ror64", + }, + + traceerr = { + [0]="error thrown or hook called during recording", + "trace too short", + "trace too long", + "trace too deep", + "too many snapshots", + "blacklisted", + "retry recording", + "NYI: bytecode %d", + "leaving loop in root trace", + "inner loop in root trace", + "loop unroll limit reached", + "bad argument type", + "JIT compilation disabled for function", + "call unroll limit reached", + "down-recursion, restarting", + "NYI: unsupported variant of FastFunc %s", + "NYI: return to lower frame", + "store with nil or NaN key", + "missing metamethod", + "looping index lookup", + "NYI: mixed sparse/dense table", + "symbol not in cache", + "NYI: unsupported C type conversion", + "NYI: unsupported C function type", + "guard would always fail", + "too many PHIs", + "persistent type instability", + "failed to allocate mcode memory", + "machine code too long", + "hit mcode limit (retrying)", + "too many spill slots", + "inconsistent register allocation", + "NYI: cannot assemble IR instruction %d", + "NYI: PHI shuffling too complex", + "NYI: register coalescing too complex", + }, + + ffnames = { + [0]="Lua", + "C", + "assert", + "type", + "next", + "pairs", + "ipairs_aux", + "ipairs", + "getmetatable", + "setmetatable", + "getfenv", + "setfenv", + "rawget", + "rawset", + "rawequal", + "unpack", + "select", + "tonumber", + "tostring", + "error", + "pcall", + "xpcall", + "loadfile", + "load", + "loadstring", + "dofile", + "gcinfo", + "collectgarbage", + "newproxy", + "print", + "coroutine.status", + "coroutine.running", + "coroutine.isyieldable", + "coroutine.create", + "coroutine.yield", + "coroutine.resume", + "coroutine.wrap_aux", + "coroutine.wrap", + "math.abs", + "math.floor", + "math.ceil", + "math.sqrt", + "math.log10", + "math.exp", + "math.sin", + "math.cos", + "math.tan", + "math.asin", + "math.acos", + "math.atan", + "math.sinh", + "math.cosh", + "math.tanh", + "math.frexp", + "math.modf", + "math.log", + "math.atan2", + "math.pow", + "math.fmod", + "math.ldexp", + "math.min", + "math.max", + "math.random", + "math.randomseed", + "bit.tobit", + "bit.bnot", + "bit.bswap", + "bit.lshift", + "bit.rshift", + "bit.arshift", + "bit.rol", + "bit.ror", + "bit.band", + "bit.bor", + "bit.bxor", + "bit.tohex", + "string.byte", + "string.char", + "string.sub", + "string.rep", + "string.reverse", + "string.lower", + "string.upper", + "string.dump", + "string.find", + "string.match", + "string.gmatch_aux", + "string.gmatch", + "string.gsub", + "string.format", + "table.maxn", + "table.insert", + "table.concat", + "table.sort", + "table.new", + "table.clear", + "io.method.close", + "io.method.read", + "io.method.write", + "io.method.flush", + "io.method.seek", + "io.method.setvbuf", + "io.method.lines", + "io.method.__gc", + "io.method.__tostring", + "io.open", + "io.popen", + "io.tmpfile", + "io.close", + "io.read", + "io.write", + "io.flush", + "io.input", + "io.output", + "io.lines", + "io.type", + "os.execute", + "os.remove", + "os.rename", + "os.tmpname", + "os.getenv", + "os.exit", + "os.clock", + "os.date", + "os.time", + "os.difftime", + "os.setlocale", + "debug.getregistry", + "debug.getmetatable", + "debug.setmetatable", + "debug.getfenv", + "debug.setfenv", + "debug.getinfo", + "debug.getlocal", + "debug.setlocal", + "debug.getupvalue", + "debug.setupvalue", + "debug.upvalueid", + "debug.upvaluejoin", + "debug.sethook", + "debug.gethook", + "debug.debug", + "debug.traceback", + "jit.on", + "jit.off", + "jit.flush", + "jit.status", + "jit.attach", + "jit.util.funcinfo", + "jit.util.funcbc", + "jit.util.funck", + "jit.util.funcuvname", + "jit.util.traceinfo", + "jit.util.traceir", + "jit.util.tracek", + "jit.util.tracesnap", + "jit.util.tracemc", + "jit.util.traceexitstub", + "jit.util.ircalladdr", + "jit.opt.start", + "jit.profile.start", + "jit.profile.stop", + "jit.profile.dumpstack", + "ffi.meta.__index", + "ffi.meta.__newindex", + "ffi.meta.__eq", + "ffi.meta.__len", + "ffi.meta.__lt", + "ffi.meta.__le", + "ffi.meta.__concat", + "ffi.meta.__call", + "ffi.meta.__add", + "ffi.meta.__sub", + "ffi.meta.__mul", + "ffi.meta.__div", + "ffi.meta.__mod", + "ffi.meta.__pow", + "ffi.meta.__unm", + "ffi.meta.__tostring", + "ffi.meta.__pairs", + "ffi.meta.__ipairs", + "ffi.clib.__index", + "ffi.clib.__newindex", + "ffi.clib.__gc", + "ffi.callback.free", + "ffi.callback.set", + "ffi.cdef", + "ffi.new", + "ffi.cast", + "ffi.typeof", + "ffi.typeinfo", + "ffi.istype", + "ffi.sizeof", + "ffi.alignof", + "ffi.offsetof", + "ffi.errno", + "ffi.string", + "ffi.copy", + "ffi.fill", + "ffi.abi", + "ffi.metatype", + "ffi.gc", + "ffi.load", + }, + +} diff --git a/garrysmod/addons/util-dlib/lua/dlib/data/hl2sweps.lua b/garrysmod/addons/util-dlib/lua/dlib/data/hl2sweps.lua new file mode 100644 index 0000000..0623a5b --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/data/hl2sweps.lua @@ -0,0 +1,64 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +return { + weapon_crowbar = { + damage = 25, + name = 'Crowbar', + pellets = 1, + dtype = DMG_CLUB + }, + + weapon_pistol = { + damage = 5, + name = '9mm Pistol', + pellets = 1, + dtype = DMG_BULLET + }, + + weapon_357 = { + damage = 75, + name = '.357 magnum', + pellets = 1, + dtype = DMG_BULLET + }, + + weapon_smg1 = { + damage = 4, + name = 'SMG', + pellets = 1, + dtype = DMG_BULLET + }, + + weapon_ar2 = { + damage = 8, + name = 'SMG', + pellets = 1, + dtype = DMG_BULLET + }, + + weapon_shotgun = { + damage = 8, + name = 'Shotgun', + pellets = 6, + dtype = DMG_BUCKSHOT + }, +} diff --git a/garrysmod/addons/util-dlib/lua/dlib/enums/gmod.lua b/garrysmod/addons/util-dlib/lua/dlib/enums/gmod.lua new file mode 100644 index 0000000..4270e1b --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/enums/gmod.lua @@ -0,0 +1,32 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +CONTENT_ALIGMENT_BOTTOMLEFT = 1 +CONTENT_ALIGMENT_BOTTOMCENTER = 2 +CONTENT_ALIGMENT_BOTTOMRIGHT = 3 + +CONTENT_ALIGMENT_MIDDLELEFT = 4 +CONTENT_ALIGMENT_MIDDLECENTER = 5 +CONTENT_ALIGMENT_MIDDLERIGHT = 6 + +CONTENT_ALIGMENT_TOPLEFT = 7 +CONTENT_ALIGMENT_TOPCENTER = 8 +CONTENT_ALIGMENT_TOPRIGHT = 9 diff --git a/garrysmod/addons/util-dlib/lua/dlib/enums/keymapping.lua b/garrysmod/addons/util-dlib/lua/dlib/enums/keymapping.lua new file mode 100644 index 0000000..19e2ec3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/enums/keymapping.lua @@ -0,0 +1,160 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +local KEY_NONE = KEY_NONE +DLib.KeyMap = {} + +DLib.KeyMap.KEY = { + [KEY_0] = '0', + [KEY_1] = '1', + [KEY_2] = '2', + [KEY_3] = '3', + [KEY_4] = '4', + [KEY_5] = '5', + [KEY_6] = '6', + [KEY_7] = '7', + [KEY_8] = '8', + [KEY_9] = '9', + [KEY_A] = 'A', + [KEY_B] = 'B', + [KEY_C] = 'C', + [KEY_D] = 'D', + [KEY_E] = 'E', + [KEY_F] = 'F', + [KEY_G] = 'G', + [KEY_H] = 'H', + [KEY_I] = 'I', + [KEY_J] = 'J', + [KEY_K] = 'K', + [KEY_L] = 'L', + [KEY_M] = 'M', + [KEY_N] = 'N', + [KEY_O] = 'O', + [KEY_P] = 'P', + [KEY_Q] = 'Q', + [KEY_R] = 'R', + [KEY_S] = 'S', + [KEY_T] = 'T', + [KEY_U] = 'U', + [KEY_V] = 'V', + [KEY_W] = 'W', + [KEY_X] = 'X', + [KEY_Y] = 'Y', + [KEY_Z] = 'Z', + [KEY_PAD_0] = '0', + [KEY_PAD_1] = '1', + [KEY_PAD_2] = '2', + [KEY_PAD_3] = '3', + [KEY_PAD_4] = '4', + [KEY_PAD_5] = '5', + [KEY_PAD_6] = '6', + [KEY_PAD_7] = '7', + [KEY_PAD_8] = '8', + [KEY_PAD_9] = '9', + [KEY_PAD_DIVIDE] = '/', + [KEY_PAD_MULTIPLY] = '*', + [KEY_PAD_MINUS] = '-', + [KEY_PAD_PLUS] = '+', + [KEY_LBRACKET] = '(', + [KEY_RBRACKET] = ')', + [KEY_SEMICOLON] = ';', + [KEY_PAD_DECIMAL] = '.', + [KEY_APOSTROPHE] = "'", + [KEY_BACKQUOTE] = '`', + [KEY_COMMA] = ',', + [KEY_PERIOD] = '.', + [KEY_SLASH] = '/', + [KEY_BACKSLASH] = '\\', + [KEY_MINUS] = '-', + [KEY_EQUAL] = '=', + [KEY_SPACE] = ' ', + [KEY_TAB] = ' ', + [KEY_UP] = 'UP', + [KEY_LEFT] = 'LEFT', + [KEY_DOWN] = 'DOWN', + [KEY_RIGHT] = 'RIGHT', + [KEY_F1] = 'F1', + [KEY_F2] = 'F2', + [KEY_F3] = 'F3', + [KEY_F4] = 'F4', + [KEY_F5] = 'F5', + [KEY_F6] = 'F6', + [KEY_F7] = 'F7', + [KEY_F8] = 'F8', + [KEY_F9] = 'F9', + [KEY_F10] = 'F10', + [KEY_F11] = 'F11', + [KEY_F12] = 'F12', +} + +DLib.KeyMap.KEY_LOWER = {} +DLib.KeyMap.KEY_REVERSE = {} + +for k, v in pairs(DLib.KeyMap.KEY) do + DLib.KeyMap.KEY_LOWER[k] = v:lower() + DLib.KeyMap.KEY_REVERSE[v] = k + DLib.KeyMap.KEY_REVERSE[v:lower()] = k +end + +function DLib.KeyMap.GetKeyFromString(strIn) + return DLib.KeyMap.KEY_REVERSE[strIn:lower()] or KEY_NONE +end + +DLib.KeyMap.NUMBERS_LIST = { + KEY_0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_PAD_0, + KEY_PAD_1, + KEY_PAD_2, + KEY_PAD_3, + KEY_PAD_4, + KEY_PAD_5, + KEY_PAD_6, + KEY_PAD_7, + KEY_PAD_8, + KEY_PAD_9, + KEY_MINUS, + KEY_PAD_MINUS, + KEY_PAD_DECIMAL, + KEY_PERIOD, +} + +DLib.KeyMap.NUMBERS_HASH = {} +DLib.KeyMap.NUMBERS_STR = {} +DLib.KeyMap.NUMBERS_STR_LIST = {} +DLib.KeyMap.NUMBERS_STR_REVERSE = {} + +for k, v in ipairs(DLib.KeyMap.NUMBERS_LIST) do + DLib.KeyMap.NUMBERS_HASH[v] = v + + table.insert(DLib.KeyMap.NUMBERS_STR_LIST, DLib.KeyMap.KEY[v]) + DLib.KeyMap.NUMBERS_STR[v] = DLib.KeyMap.KEY[v] + DLib.KeyMap.NUMBERS_STR_REVERSE[DLib.KeyMap.KEY[v]] = v +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/enums/sdk.lua b/garrysmod/addons/util-dlib/lua/dlib/enums/sdk.lua new file mode 100644 index 0000000..6f6c46c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/enums/sdk.lua @@ -0,0 +1,138 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +-- You may, free of charge, download and use the SDK to develop a modified Valve game +-- running on the Source engine. You may distribute your modified Valve game in source and +-- object code form, but only for free. Terms of use for Valve games are found in the Steam +-- Subscriber Agreement located here: http:--store.steampowered.com/subscriber_agreement/ + +-- You may copy, modify, and distribute the SDK and any modifications you make to the +-- SDK in source and object code form, but only for free. Any distribution of this SDK must +-- include this LICENSE file and thirdpartylegalnotices.txt. + +-- Any distribution of the SDK or a substantial portion of the SDK must include the above +-- copyright notice and the following: + +-- DISCLAIMER OF WARRANTIES. THE SOURCE SDK AND ANY +-- OTHER MATERIAL DOWNLOADED BY LICENSEE IS PROVIDED +-- "AS IS". VALVE AND ITS SUPPLIERS DISCLAIM ALL +-- WARRANTIES WITH RESPECT TO THE SDK, EITHER EXPRESS +-- OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED +-- WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, +-- TITLE AND FITNESS FOR A PARTICULAR PURPOSE. + +-- LIMITATION OF LIABILITY. IN NO EVENT SHALL VALVE OR +-- ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, +-- INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER +-- (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF +-- BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF +-- BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS) +-- ARISING OUT OF THE USE OF OR INABILITY TO USE THE +-- ENGINE AND/OR THE SDK, EVEN IF VALVE HAS BEEN +-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +PHYSGUN_MUST_BE_DETACHED = 0 +PHYSGUN_IS_DETACHING = 1 +PHYSGUN_CAN_BE_GRABBED = 2 +PHYSGUN_ANIMATE_ON_PULL = 3 +PHYSGUN_ANIMATE_IS_ANIMATING = 4 +PHYSGUN_ANIMATE_FINISHED = 5 +PHYSGUN_ANIMATE_IS_PRE_ANIMATING = 6 +PHYSGUN_ANIMATE_IS_POST_ANIMATING = 7 + +TEXTUREFLAGS_POINTSAMPLE = 0x00000001 +TEXTUREFLAGS_TRILINEAR = 0x00000002 +TEXTUREFLAGS_CLAMPS = 0x00000004 +TEXTUREFLAGS_CLAMPT = 0x00000008 +TEXTUREFLAGS_ANISOTROPIC = 0x00000010 +TEXTUREFLAGS_HINT_DXT5 = 0x00000020 +TEXTUREFLAGS_SRGB = 0x00000040 +TEXTUREFLAGS_NORMAL = 0x00000080 +TEXTUREFLAGS_NOMIP = 0x00000100 +TEXTUREFLAGS_NOLOD = 0x00000200 +TEXTUREFLAGS_ALL_MIPS = 0x00000400 +TEXTUREFLAGS_PROCEDURAL = 0x00000800 + +-- These are automatically generated by vtex from the texture data. +TEXTUREFLAGS_ONEBITALPHA = 0x00001000 +TEXTUREFLAGS_EIGHTBITALPHA = 0x00002000 + +-- newer flags from the *.txt config file +TEXTUREFLAGS_ENVMAP = 0x00004000 +TEXTUREFLAGS_RENDERTARGET = 0x00008000 +TEXTUREFLAGS_DEPTHRENDERTARGET = 0x00010000 +TEXTUREFLAGS_NODEBUGOVERRIDE = 0x00020000 +TEXTUREFLAGS_SINGLECOPY = 0x00040000 +TEXTUREFLAGS_STAGING_MEMORY = 0x00080000 +TEXTUREFLAGS_IMMEDIATE_CLEANUP = 0x00100000 +TEXTUREFLAGS_IGNORE_PICMIP = 0x00200000 +TEXTUREFLAGS_UNUSED_00400000 = 0x00400000 +TEXTUREFLAGS_NODEPTHBUFFER = 0x00800000 +TEXTUREFLAGS_UNUSED_01000000 = 0x01000000 +TEXTUREFLAGS_CLAMPU = 0x02000000 +TEXTUREFLAGS_VERTEXTEXTURE = 0x04000000 -- Useable as a vertex texture +TEXTUREFLAGS_SSBUMP = 0x08000000 +TEXTUREFLAGS_UNUSED_10000000 = 0x10000000 + +-- Clamp to border color on all texture coordinates +TEXTUREFLAGS_BORDER = 0x20000000 +TEXTUREFLAGS_UNUSED_40000000 = 0x40000000 +TEXTUREFLAGS_UNUSED_80000000 = 0x80000000 + +-- settings for m_takedamage +DAMAGE_MODE_NO = 0 +DAMAGE_MODE_GODMODE = 0 +DAMAGE_MODE_EVENTS_ONLY = 1 -- Call damage functions, but don't modify health +DAMAGE_MODE_BUDDHA = 1 -- Call damage functions, but don't modify health +DAMAGE_MODE_YES = 2 +DAMAGE_MODE_ENABLED = 2 +DAMAGE_MODE_AIM = 3 + +_G.SNDLVL_NONE = 0 +_G.SNDLVL_20dB = 20 +_G.SNDLVL_25dB = 25 +_G.SNDLVL_30dB = 30 +_G.SNDLVL_35dB = 35 +_G.SNDLVL_40dB = 40 +_G.SNDLVL_45dB = 45 +_G.SNDLVL_50dB = 50 +_G.SNDLVL_55dB = 55 +_G.SNDLVL_60dB = 60 +_G.SNDLVL_IDLE = 60 +_G.SNDLVL_65dB = 65 +_G.SNDLVL_STATIC = 66 +_G.SNDLVL_70dB = 70 +_G.SNDLVL_75dB = 75 +_G.SNDLVL_NORM = 75 +_G.SNDLVL_80dB = 80 +_G.SNDLVL_TALKING = 80 +_G.SNDLVL_85dB = 85 +_G.SNDLVL_90dB = 90 +_G.SNDLVL_95dB = 95 +_G.SNDLVL_100dB = 100 +_G.SNDLVL_105dB = 105 +_G.SNDLVL_110dB = 110 +_G.SNDLVL_120dB = 120 +_G.SNDLVL_130dB = 130 +_G.SNDLVL_140dB = 140 +_G.SNDLVL_GUNFIRE = 140 +_G.SNDLVL_150dB = 150 +_G.SNDLVL_180dB = 180 diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/ctakedmg.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/ctakedmg.lua new file mode 100644 index 0000000..e5a14c3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/ctakedmg.lua @@ -0,0 +1,142 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 meta = FindMetaTable('CTakeDamageInfo') + +local damageTypes = { + {DMG_CRUSH, 'Crush'}, + {DMG_BULLET, 'Bullet'}, + {DMG_SLASH, 'Slash'}, + {DMG_SLASH, 'Slashing'}, + {DMG_BURN, 'Burn'}, + {DMG_BURN, 'Fire'}, + {DMG_BURN, 'Flame'}, + {DMG_VEHICLE, 'Vehicle'}, + {DMG_FALL, 'Fall'}, + {DMG_BLAST, 'Blast'}, + {DMG_CLUB, 'Club'}, + {DMG_SHOCK, 'Shock'}, + {DMG_SONIC, 'Sonic'}, + {DMG_ENERGYBEAM, 'EnergyBeam'}, + {DMG_ENERGYBEAM, 'Laser'}, + {DMG_DROWN, 'Drown'}, + {DMG_PARALYZE, 'Paralyze'}, + {DMG_NERVEGAS, 'Gaseous'}, + {DMG_NERVEGAS, 'NergeGas'}, + {DMG_NERVEGAS, 'Gas'}, + {DMG_POISON, 'Poision'}, + {DMG_ACID, 'Acid'}, + {DMG_AIRBOAT, 'Airboat'}, + {DMG_BUCKSHOT, 'Buckshot'}, + {DMG_DIRECT, 'Direct'}, + {DMG_DISSOLVE, 'Dissolve'}, + {DMG_DROWNRECOVER, 'DrownRecover'}, + {DMG_PHYSGUN, 'Physgun'}, + {DMG_PLASMA, 'Plasma'}, + {DMG_RADIATION, 'Radiation'}, + {DMG_SLOWBURN, 'Slowburn'}, +} + +for i, dmg in ipairs(damageTypes) do + meta['Is' .. dmg[2] .. 'Damage'] = function(self) + return bit.band(self:GetDamageType(), dmg[1]) ~= 0 + end +end + +--[[ + @doc + @fname CTakeDamageInfo:TypesArray + + @returns + table: array of DMG_ numbers +]] +function meta:TypesArray() + local output = {} + local types = self:GetDamageType() + + for i, dmg in ipairs(damageTypes) do + if bit.band(types, dmg[1]) == dmg[1] then + table.insert(output, dmg[1]) + end + end + + return output +end + +--[[ + @doc + @fname CTakeDamageInfo:Copy + @args CTakeDamageInfo copyFrom + + @deprecated + + @desc + This has no positive effects due to how gmod work + also it can accept a table that implements CTakeDamageInfo methods + @enddesc + + @returns + CTakeDamageInfo: Most likely the same object +]] +function meta:Copy(copyDataInto) + local a = self:GetAttacker() + local b = self:GetInflictor() + local c = self:GetDamage() + local d = self:GetMaxDamage() + local e = self:GetReportedPosition() + local j = self:GetDamagePosition() + local g = self:GetDamageType() + + copyDataInto = copyDataInto or DamageInfo() + copyDataInto:SetAttacker(a) + copyDataInto:SetInflictor(b) + copyDataInto:SetDamage(c) + copyDataInto:SetMaxDamage(d) + copyDataInto:SetReportedPosition(e) + copyDataInto:SetDamagePosition(j) + copyDataInto:SetDamageType(g) + return copyDataInto +end + +--[[ + @doc + @fname CTakeDamageInfo:Receive + @args CTakeDamageInfo from + + @deprecated + + @desc + This has no positive effects due to how gmod work + also it can accept a table that implements CTakeDamageInfo methods + @enddesc + + @returns + CTakeDamageInfo: self +]] +function meta:Receive(from) + self:SetAttacker(from:GetAttacker()) + self:SetInflictor(from:GetInflictor()) + self:SetDamage(from:GetDamage()) + self:SetMaxDamage(from:GetMaxDamage()) + self:SetReportedPosition(from:GetReportedPosition()) + self:SetDamagePosition(from:GetDamagePosition()) + self:SetDamageType(from:GetDamageType()) + return self +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/cvar.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/cvar.lua new file mode 100644 index 0000000..810fc04 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/cvar.lua @@ -0,0 +1,89 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 GetConVar = GetConVar + +--[[ + @doc + @fname ConVar + @args vararg arguments + + @desc + alias of !g:GetConVar + @enddesc + + @returns + ConVar: returned object +]] +function _G.ConVar(...) + return GetConVar(...) +end + +local ConVar = FindMetaTable('ConVar') +local math = math +local error = error +local RunConsoleCommand = RunConsoleCommand + +--[[ + @doc + @fname ConVar:GetByType + @args type string, vargarg arguments + + @desc + valid types are: + + `'string'` + `'int'` + `'integer'` + `'number'` + `'uint'` + `'uinteger'` + `'unumber'` + `'float'` + `'bool'` + `'boolean'` + + @enddesc + + @returns + any: returned value +]] +function ConVar:GetByType(typeIn, ...) + if typeIn == 'string' then + return self:GetString(...) + elseif typeIn == 'int' or typeIn == 'integer' or typeIn == 'number' then + return self:GetInt(...) + elseif typeIn == 'uint' or typeIn == 'uinteger' or typeIn == 'unumber' then + return self:GetInt(...):max(0) + elseif typeIn == 'float' then + return self:GetFloat(...) + elseif typeIn == 'bool' or typeIn == 'boolean' then + return self:GetBool(...) + else + error('Unknown variable type - ' .. typeIn .. '!') + end +end + +function ConVar:Reset() + local default = self:GetDefault() + RunConsoleCommand(self:GetName(), default) + return default +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/entity.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/entity.lua new file mode 100644 index 0000000..26df9d2 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/entity.lua @@ -0,0 +1,541 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 entMeta = FindMetaTable('Entity') +local plyMeta = FindMetaTable('Player') +local wepMeta = FindMetaTable('Weapon') +local vehMeta = FindMetaTable('Vehicle') +local npcMeta = FindMetaTable('NPC') + +--[[ + @doc + @fname Entity:IsClientsideEntity + + @returns + boolean +]] +function entMeta:IsClientsideEntity() + return false +end + +--[[ + @doc + @fname Entity:SetNWUInt + @args string name, number value +]] + +function entMeta:SetNWUInt(name, value) + assert(type(value) == 'number', 'Value passed is not a number') + + if value < 0 then + error('Value can not be negative') + end + + if value > 0x100000000 then + error('Integer overflow') + end + + if value >= 0x7FFFFFFF then + value = value - 0x100000000 + end + + self:SetNWInt(name, value) +end + +--[[ + @doc + @fname Entity:GetNWUInt + @args string name, number ifNone + + @returns + number +]] + +function entMeta:GetNWUInt(name, ifNone) + if type(ifNone) == 'number' then + if ifNone < 0 then + error('Value can not be negative') + end + + if ifNone > 0x100000000 then + error('Integer overflow') + end + end + + local value = self:GetNWInt(name, ifNone) + + if value < 0 then + return 0x100000000 + value + else + return value + end +end + +--[[ + @doc + @fname Entity:SetNW2UInt + @args string name, number value +]] +function entMeta:SetNW2UInt(name, value) + assert(type(value) == 'number', 'Value passed is not a number') + + if value < 0 then + error('Value can not be negative') + end + + if value > 0x100000000 then + error('Integer overflow') + end + + if value >= 0x7FFFFFFF then + value = value - 0x100000000 + end + + self:SetNW2Int(name, value) +end + +--[[ + @doc + @fname Entity:GetNW2UInt + @args string name, number ifNone + + @returns + number +]] +function entMeta:GetNW2UInt(name, ifNone) + if type(ifNone) == 'number' then + if ifNone < 0 then + error('Value can not be negative') + end + + if ifNone > 0x100000000 then + error('Integer overflow') + end + end + + local value = self:GetNW2Int(name, ifNone) + + if grab < 0 then + return 0x100000000 + value + else + return value + end +end + +--[[ + @doc + @fname Player:GetActiveWeaponClass + + @returns + any: string or nil +]] +function plyMeta:GetActiveWeaponClass() + local weapon = self:GetActiveWeapon() + if not weapon:IsValid() then return nil end + return weapon:GetClass() +end + +npcMeta.GetActiveWeaponClass = plyMeta.GetActiveWeaponClass + +if CLIENT then + local CSEnt = FindMetaTable('CSEnt') + + function CSEnt:IsClientsideEntity() + return true + end + + function entMeta:IsClientsideEntity() + local class = self:GetClass() + return class == 'class C_PhysPropClientside' or class == 'class C_ClientRagdoll' or class == 'class CLuaEffect' + end +else + --[[ + @doc + @fname Entity:BuddhaEnable + ]] + + function entMeta:BuddhaEnable() + self:SetSaveValue('m_takedamage', DAMAGE_MODE_BUDDHA) + end + + --[[ + @doc + @fname Entity:BuddhaDisable + ]] + function entMeta:BuddhaDisable() + self:SetSaveValue('m_takedamage', DAMAGE_MODE_ENABLED) + end + + --[[ + @doc + @fname Entity:IsBuddhaEnabled + + @returns + boolean + ]] + function entMeta:IsBuddhaEnabled() + return self:GetSaveTable().m_takedamage == DAMAGE_MODE_BUDDHA + end +end + +--[[ + @doc + @fname Player:IsAlive + + @desc + alias of !g:Player:Alive + @enddesc + + @returns + boolean +]] +function plyMeta:IsAlive() + return self:Alive() +end + +--[[ + @doc + @fname Player:GetIsAlive + + @desc + alias of !g:Player:Alive + @enddesc + + @returns + boolean +]] +function plyMeta:GetIsAlive() + return self:Alive() +end + +--[[ + @doc + @fname Weapon:GetClip1 + + @desc + alias of !g:Weapon:Clip1 + @enddesc + + @returns + number +]] +function wepMeta:GetClip1() + return self:Clip1() +end + +--[[ + @doc + @fname Weapon:GetClip2 + + @desc + alias of !g:Weapon:Clip2 + @enddesc + + @returns + number +]] +function wepMeta:GetClip2() + return self:Clip2() +end + +--[[ + @doc + @fname Player:GetMaxArmor + + @returns + number +]] +-- placeholder for now +function plyMeta:GetMaxArmor() + return 100 +end + +local VehicleListIterable = {} + +local function rebuildVehicleList() + for classname, data in pairs(list.GetForEdit('Vehicles')) do + if data.Model then + VehicleListIterable[data.Model:lower()] = data + end + end +end + +timer.Create('DLib.RebuildVehicleListNames', 10, 0, rebuildVehicleList) +rebuildVehicleList() + +--[[ + @doc + @fname Vehicle:GetAmmoType + + @returns + number +]] +function vehMeta:GetAmmoType() + return select(1, self:GetAmmo()) +end + +--[[ + @doc + @fname Vehicle:GetAmmoClip + + @returns + number +]] +function vehMeta:GetAmmoClip() + return select(2, self:GetAmmo()) +end + +--[[ + @doc + @fname Vehicle:GetAmmoCount + + @returns + number +]] +function vehMeta:GetAmmoCount() + return select(3, self:GetAmmo()) +end + +if SERVER then + net.pool('dlib_emitsoundpredicted') +end + +--[[ + @doc + @fname Entity:EmitSoundPredictedR + + @desc + Same as !g:Entity:EmitSound , but if `self` is a player, the sound will not be networked to him. + This DOES NOT correspond to !g:IsFirstTimePredicted , since in some predicted hooks `IsFirstTimePredicted()` always return `false` + Achieved with GMod + basically it is partial fix for https://github.com/Facepunch/garrysmod-issues/issues/2651 + @enddesc +]] +function entMeta:EmitSoundPredictedR(soundName, soundLevel, pitch, volume, channel) + assert(type(soundName) == 'string', 'Whats up with sound name') + soundLevel = soundLevel or 75 + pitch = pitch or 100 + volume = volume or 1 + channel = channel or CHAN_AUTO + + if not self:IsPlayer() then + self:EmitSound(soundName, soundLevel, pitch, volume, channel) + return + end + + if game.SinglePlayer() then + if CLIENT then return end + self:EmitSound(soundName, soundLevel, pitch, volume, channel) + return + end + + if CLIENT then + self:EmitSound(soundName, soundLevel, pitch, volume, channel) + return + end + + if player.GetCount() <= 1 then return end + + local filter = RecipientFilter() + filter:AddPAS(self:EyePos()) + filter:RemovePlayer(self) + + if filter:GetCount() == 0 then return end + + net.Start('dlib_emitsoundpredicted', true) + net.WriteString(soundName) + net.WriteUInt16(soundLevel) + net.WriteUInt8(pitch) + net.WriteFloat(volume) + net.WriteUInt8(channel) + net.WriteEntity(self) + net.Send(filter) +end + +--[[ + @doc + @fname Entity:EmitSoundPredicted + + @desc + Same as !g:Entity:EmitSound , but if `self` is a player, the sound will not be networked to him. + This do correspond to !g:IsFirstTimePredicted + Achieved with GMod + basically it is partial fix for https://github.com/Facepunch/garrysmod-issues/issues/2651 + + Look at `Entity:EmitSoundPredictedR` for non !g:IsFirstTimePredicted based version of this function + @enddesc +]] +function entMeta:EmitSoundPredicted(...) + if not IsFirstTimePredicted() then return end + return self:EmitSoundPredictedR(...) +end + +local language = language + +if CLIENT then + net.receive('dlib_emitsoundpredicted', function() + local soundName, soundLevel, pitch, volume, channel, self = net.ReadString(), net.ReadUInt16(), net.ReadUInt8(), net.ReadFloat(), net.ReadUInt8(), net.ReadEntity() + + if not IsValid(self) then return end + self:EmitSound(soundName, soundLevel, pitch, volume, channel) + end) + + local player = player + local IsValid = FindMetaTable('Entity').IsValid + local GetTable = FindMetaTable('Entity').GetTable + local GetVehicle = FindMetaTable('Player').GetVehicle + local NULL = NULL + local ipairs = ipairs + + --[[ + @doc + @replaces + @fname Vehicle:GetDriver + + @desc + Same as !g:Vehicle:GetDriver + but this function is now Shared + @enddesc + + @returns + Entity: or NULL + ]] + -- function vehMeta:GetDriver() + -- return self._dlib_vehfix or NULL + -- end + + -- local function Think() + -- for i, ply in ipairs(player.GetAll()) do + -- local ply2 = GetTable(ply) + -- local veh = GetVehicle(ply) + + -- if veh ~= ply2._dlib_vehfix then + -- if IsValid(ply2._dlib_vehfix) then + -- ply2._dlib_vehfix._dlib_vehfix = NULL + -- end + + -- ply2._dlib_vehfix = veh + + -- if IsValid(veh) then + -- veh._dlib_vehfix = ply + -- end + -- end + -- end + -- end + + -- hook.Add('Think', 'DLib.GetDriverFix', Think) + + --[[ + @doc + @fname Vehicle:GetPrintName + + @returns + string + ]] + function vehMeta:GetPrintName() + if self.__dlibCachedName then + return self.__dlibCachedName + end + + local getname = self.PrintName or (VehicleListIterable[self:GetModel()] and VehicleListIterable[self:GetModel()].Name) + + if not getname then + local classname = self:GetClass() + getname = language.GetPhrase(classname) + end + + self.__dlibCachedName = getname + + return getname + end +else + entMeta.GetNetworkName = entMeta.GetName + entMeta.SetNetworkName = entMeta.SetName + entMeta.GetNetworkedName = entMeta.GetName + entMeta.SetNetworkedName = entMeta.SetName + entMeta.GetTargetName = entMeta.GetName + entMeta.SetTargetName = entMeta.SetName + + function vehMeta:GetPrintName() + if self.__dlibCachedName then + return self.__dlibCachedName + end + + local getname = self.PrintName + + if not getname then + getname = VehicleListIterable[self:GetModel()] or self:GetClass() + end + + self.__dlibCachedName = getname + + return getname + end +end + +--[[ + @doc + @fname Vehicle:GetPrintNameDLib + @args boolean unlocalized = false + + @desc + When `unlocalized` is `true`, if i18n phrase is present, it will return unlocalized. + @enddesc + + @returns + string +]] +function entMeta:GetPrintNameDLib(unlocalized) + if self:IsPlayer() then return self:Nick() end + + if self.GetPrintName then + local pname = self:GetPrintName() + + if SERVER and pname and pname:startsWith('#') then + local class = self:GetClass() or 'unknown' + local localized = 'info.entity.' .. class .. '.name' + + if DLib.i18n.exists(localized) then + return unlocalized and localized or DLib.i18n.localize(localized) + end + end + + return pname or '' + end + + local class = self:GetClass() or 'unknown' + local localized = 'info.entity.' .. class .. '.name' + + if DLib.i18n.exists(localized) then + return unlocalized and localized or DLib.i18n.localize(localized) + end + + local str = self.PrintName + + if not str or #str == 0 then + str = language and language.GetPhrase and language.GetPhrase(class) or class + end + + if str:startsWith('#') and language and language.GetPhrase then + str = language.GetPhrase(str:sub(1)) or str + end + + return str or self:GetName() +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/extensions.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/extensions.lua new file mode 100644 index 0000000..bf0d090 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/extensions.lua @@ -0,0 +1,699 @@ + +-- +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 PhysObj = FindMetaTable('PhysObj') +local vectorMeta = FindMetaTable('Vector') +local vehicleMeta = FindMetaTable('Vehicle') +local entMeta = FindMetaTable('Entity') +local Color = Color +local math = math +local ipairs = ipairs +local assert = assert +local select = select +local language = language +local list = list +local pairs = pairs +local CLIENT = CLIENT + +_G.angle_empty = Angle() +_G.angle_up = Angle(90) +_G.angle_down = Angle(-90) +_G.angle_left = Angle(0, 90) +_G.angle_right = Angle(0, -90) + +function PhysObj:SetAngleVelocity(newAngle) + return self:AddAngleVelocity(newAngle - self:GetAngleVelocity()) +end + +PhysObj.DLibSetMass = PhysObj.DLibSetMass or PhysObj.SetMass +PhysObj.DLibEnableCollisions = PhysObj.DLibEnableCollisions or PhysObj.EnableCollisions +PhysObj.DLibEnableDrag = PhysObj.DLibEnableDrag or PhysObj.EnableDrag +PhysObj.DLibEnableMotion = PhysObj.DLibEnableMotion or PhysObj.EnableMotion +PhysObj.DLibEnableGravity = PhysObj.DLibEnableGravity or PhysObj.EnableGravity + +function PhysObj:SetMass(newMass) + if newMass <= 0 then + print(debug.traceback('Mass can not be lower or equal to 0!', 2)) + return + end + + return self:DLibSetMass(newMass) +end + +local worldspawn, worldspawnPhys + +-- shut up dumb addons +function PhysObj:EnableCollisions(newStatus) + worldspawn = worldspawn or Entity(0) + worldspawnPhys = worldspawnPhys or worldspawn:GetPhysicsObject() + + if worldspawnPhys == self then + print(debug.traceback('Attempt to call :EnableCollisions() on World PhysObj!', 2)) + return + end + + return self:DLibEnableCollisions(newStatus) +end + +--[[ + @doc + @fname Vector:Copy + + @desc + Same as doing `Vector(self)` + @enddesc + + @returns + Vector +]] +function vectorMeta:Copy() + return Vector(self) +end + +--[[ + @doc + @fname Vector:__call + + @returns + Vector: copy +]] +function vectorMeta:__call() + return Vector(self) +end + +--[[ + @doc + @fname Vector:ToNative + + @returns + Vector: self +]] +function vectorMeta:ToNative() + return self +end + +--[[ + @doc + @fname Vector:IsNormalized + + @returns + boolean +]] +function vectorMeta:IsNormalized() + return self.x <= 1 and self.y <= 1 and self.z <= 1 and self.x >= -1 and self.y >= -1 and self.z >= -1 +end + +--[[ + @doc + @fname Vector:Receive + @args Vector from + + @returns + Vector: self +]] +function vectorMeta:Receive(target) + local x, y, z = target.x, target.y, target.z + self.x, self.y, self.z = x, y, z + return self +end + +--[[ + @doc + @fname Vector:RotateAroundAxis + @args Vector axis, number rotation + + @returns + Vector: self +]] +function vectorMeta:RotateAroundAxis(axis, rotation) + local ang = self:Angle() + ang:RotateAroundAxis(axis, rotation) + return self:Receive(ang:Forward() * self:Length()) +end + +--[[ + @doc + @fname Vector:ToColor + + @returns + Color +]] +function vectorMeta:ToColor() + return Color(self.x * 255, self.y * 255, self.z * 255) +end + +local type = luatype + +--[[ + @doc + @fname Vector:WithinAABox + @args Vector mins, Vector maxs + + @desc + can also accept `LVector` + @enddesc + + @returns + boolean +]] +function vectorMeta:WithinAABox(mins, maxs) + if type(mins) ~= 'Vector' and type(mins) ~= 'LVector' then + error('Vector:WithinAABox(' .. type(mins) .. ', ' .. type(maxs) .. ') - invalid call') + end + + if type(maxs) ~= 'Vector' and type(maxs) ~= 'LVector' then + error('Vector:WithinAABox(' .. type(mins) .. ', ' .. type(maxs) .. ') - invalid call') + end + + return self.x >= mins.x + and self.y >= mins.y + and self.z >= mins.z + and self.x <= maxs.x + and self.y <= maxs.y + and self.z <= maxs.z +end + +--[[ + @doc + @fname sql.EQuery + @args string query + + @desc + Same as gmod's sql.Query except it prints errors in console when one occures + @enddesc + + @returns + any: returned value from database +]] +function sql.EQuery(...) + local data = sql.Query(...) + + if data == false then + DLib.Message('SQL: ', ...) + DLib.Message(sql.LastError()) + end + + return data +end + +--[[ + @doc + @fname math.progression + @args number self, number min, number max, number middle = nil + + @returns + number: position of self between min and max in 0-1 range, or 0-1-0 is middle is not nil +]] +function math.progression(self, min, max, middle) + if self < min then return 0 end + + if middle then + if self < min or self >= max then return 0 end + + if self < middle then + return math.min((self - min) / (middle - min), 1) + elseif self > middle then + return 1 - math.min((self - middle) / (max - middle), 1) + elseif self == middle then + return 1 + end + end + + return math.min((self - min) / (max - min), 1) +end + +--[[ + @doc + @fname math.equal + @args vararg numbers + + @returns + boolean +]] +function math.equal(...) + local amount = select('#', ...) + assert(amount > 1, 'At least two numbers are required!') + local lastValue + + for i = 1, amount do + local value = select(i, ...) + lastValue = lastValue or value + if value ~= lastValue then return false end + end + + return true +end + +--[[ + @doc + @fname math.average + @args vararg numbers + + @returns + number: the average +]] +function math.average(...) + local amount = select('#', ...) + assert(amount > 1, 'At least two numbers are required!') + local total = 0 + + for i = 1, amount do + total = total + select(i, ...) + end + + return total / amount +end + +local type = type +local table = table +local unpack = unpack +local buffer = {} + +local function tbezier(t, values, amount) + assert(type(t) == 'number', 'invalid T variable') + assert(t >= 0 and t <= 1, '0 <= t <= 1!') + assert(#values >= 2, 'at least two values must be provided') + local a, b = values[1], values[2] + + -- linear + if amount == 2 then + return a + (b - a) * t + -- square + elseif amount == 3 then + return (1 - t):pow(2) * a + 2 * t * (1 - t) * b + t:pow(2) * values[3] + -- cube + elseif amount == 4 then + return (1 - t):pow(3) * a + 3 * t * (1 - t):pow(2) * b + 3 * t:pow(2) * (1 - t) * values[3] + t:pow(3) * values[4] + end + + for point = 1, amount do + local point1 = values[point] + local point2 = values[point + 1] + if not point2 then break end + buffer[point] = point1 + (point2 - point1) * t + end + + return tbezier(t, buffer, amount - 1) +end + +--[[ + @doc + @fname math.bezier + @args number t, vararg numbers + + @returns + number +]] +function math.bezier(t, ...) + return tbezier(t, {...}, select('#', ...)) +end + +--[[ + @doc + @fname math.tbezier + @args number t, table numbers + + @returns + number +]] +-- accepts table +function math.tbezier(t, values) + return tbezier(t, values, #values) +end + +--[[ + @doc + @fname math.tformat + @args number time + + @returns + table: formatted table +]] +function math.tformat(time) + local centuries, years, months, weeks, days, hours, minutes, seconds = math.tformatVararg(time) + return { + centuries = centuries, years = years, months = months, weeks = weeks, days = days, hours = hours, minutes = minutes, seconds = seconds + } +end + +--[[ + @doc + @fname math.tformatVararg + @args number time + + @returns + number: centuries + number: years + number: months + number: weeks + number: days + number: hours + number: minutes + number: seconds +]] +function math.tformatVararg(time) + assert(type(time) == 'number', 'Invalid time provided.') + + if time > 0xFFFFFFFFFF then + error('Value is too big! Maximum is ' .. 0xFFFFFFFFFF) + elseif time <= 1 then + return 0, 0, 0, 0, 0, 0, 0, 0 + end + + local centuries = (time - time % 0xBBF81E00) / 0xBBF81E00 + time = time - centuries * 0xBBF81E00 + + local years = (time - time % 0x01E13380) / 0x01E13380 + time = time - years * 0x01E13380 + + local months = ((time - time % 0x00278D00) / 0x00278D00):min(11) + time = time - months * 0x00278D00 + + local weeks = (time - time % 604800) / 604800 + time = time - weeks * 604800 + + local days = (time - time % 86400) / 86400 + time = time - days * 86400 + + local hours = (time - time % 3600) / 3600 + time = time - hours * 3600 + + local minutes = (time - time % 60) / 60 + time = time - minutes * 60 + + local seconds = time:floor() + + return centuries, years, months, weeks, days, hours, minutes, seconds +end + +--[[ + @doc + @fname math.untformat + @args table time + + @desc + reverse of table format of number + @enddesc + + @returns + number +]] +function math.untformat(time) + assert(type(time) == 'table', 'Invalid time provided. You must provide table in math.tformat output format.') + + return math.untformatVararg(time.centuries, time.years, time.months, time.weeks, time.days, time.hours, time.minutes, time.seconds) +end + +--[[ + @doc + @fname math.untformatVararg + @args number centuries, number years, number months, number weeks, number days, number hours, number minutes, number seconds + + @desc + reverse of time numbers format of number + @enddesc + + @returns + number +]] +function math.untformatVararg(centuries, years, months, weeks, days, hours, minutes, seconds) + assert(type(centuries) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(years) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(months) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(weeks) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(days) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(hours) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(minutes) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + assert(type(seconds) == 'number', 'Invalid time provided. You must provide table in math.tformat output format.') + + return centuries * 0xBBF81E00 + + years * 0x01E13380 + + months * 0x00278D00 + + weeks * 604800 + + days * 86400 + + hours * 3600 + + minutes * 60 + + seconds +end + +do + local daysIn = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + } + + local _timeCached = {} + local _timeCachedHigh = {} + + for month = 1, 12 do + local stamp1 = 0 + local stamp2 = 0 + + for _month = 1, month - 1 do + if _month == 2 then + stamp1 = stamp1 + 29 * 86400 + stamp2 = stamp2 + 28 * 86400 + else + stamp1 = stamp1 + daysIn[_month] * 86400 + stamp2 = stamp2 + daysIn[_month] * 86400 + end + end + + _timeCachedHigh[month] = stamp1 + _timeCached[month] = stamp2 + end + + local yearLength = 31536000 + local longYearLength = 31622400 + local isnumber = isnumber + + function math.dateToTimestamp(year, month, day, hour, minute, second) + assert(isnumber(year), 'year is not a number or missing') + assert(isnumber(month), 'month is not a number or missing') + assert(isnumber(day), 'day is not a number or missing') + assert(isnumber(hour), 'hour is not a number or missing') + assert(isnumber(minute), 'minute is not a number or missing') + assert(isnumber(second), 'second is not a number or missing') + + assert(year >= 1970, 'year < 1970 is not supported yet') + assert(month >= 1 and month < 13, 'invalid month') + assert(day >= 1 and day <= 31, 'invalid day') + assert(hour >= 0 and hour <= 24, 'invalid hour') + assert(minute >= 0 and minute <= 60, 'invalid minute') + assert(second >= 0 and second <= 60, 'invalid second') + + local yearS, peaks, skips, shift = year - 1970, 0, 0, 0 + + if year > 2000 then + -- ??? + peaks = ((year - 1600) / 400):floor() + yearS = yearS - peaks + shift = -86400 * peaks + + skips = (((year - 1900) / 100):floor() - peaks):max(0) + yearS = yearS - skips + + local peak2 = (((year - 1968) / 4):floor() - skips):max(0) + yearS = yearS - peak2 + peaks = peaks + peak2 + elseif year < 2000 then + peaks = ((year - 1968) / 4):floor() + yearS = yearS - peaks + else + peaks = 7 + yearS = yearS - 7 + end + + return peaks * longYearLength + skips * yearLength + yearS * yearLength + + ((year % 400 == 0 or year % 100 ~= 0 and year % 4 == 0) and _timeCachedHigh[month] or _timeCached[month]) + + (day - 1) * 86400 + hour * 3600 + minute * 60 + second + shift + end +end + +local CLIENT = CLIENT +local hook = hook +local net = net + +if SERVER then + net.pool('dlib.limithitfix') +end + +local plyMeta = FindMetaTable('Player') + +--[[ + @doc + @fname Player:LimitHit + + @desc + This function no longer produce limit hit message when called clientside + this function is internal and is used by gmod itself + but the override allows you to put !g:Player:CheckLimit in shared code of toolguns + @enddesc +]] +function plyMeta:LimitHit(limit) + -- we call CheckLimit() on client just for prediction + -- so when we actually hit limit - it can produce two messages because client will also try to + -- display this message by calling hook LimitHit. So, let's call that only once. + + -- if you want to call this function clientside despite this text and warning + -- you can run hooks on LimitHit manually by doing so: + -- hook.Run('LimitHit', 'mylimit') + -- you shouldn't really call this function directly clientside + if CLIENT then return end + + net.Start('dlib.limithitfix') + net.WriteString(limit) + net.Send(self) +end + +if CLIENT then + net.receive('dlib.limithitfix', function() + hook.Run('LimitHit', net.ReadString()) + end) +end + +if CLIENT then + local surface = surface + surface._DLibPlaySound = surface._DLibPlaySound or surface.PlaySound + + function surface.PlaySound(path, ...) + assert(type(path) == 'string', 'surface.PlaySound - string expected, got ' .. type(path)) + local can = hook.Run('SurfaceEmitSound', path, ...) + if can == false then return end + return surface._DLibPlaySound(path, ...) + end + + -- cache and speedup lookups a bit + local use_type = CreateConVar('dlib_screenscale', '1', {FCVAR_ARCHIVE}, 'Use screen height as screen scale parameter instead of screen width') + local dlib_screenscale_mul = CreateConVar('dlib_screenscale_mul', '1', {FCVAR_ARCHIVE}, 'GUI Scale multiplier') + DLib.dlib_screenscale_mul = dlib_screenscale_mul + local dlib_screenscale_mul_get = dlib_screenscale_mul:GetFloat(1):max(0) + local ScrWL = ScrWL + local ScrHL = ScrHL + local screenfunc + + if use_type:GetBool() then + function screenfunc(modify) + return ScrHL() / 480 * modify * dlib_screenscale_mul_get + end + else + function screenfunc(modify) + return ScrWL() / 640 * modify * dlib_screenscale_mul_get + end + end + + --[[ + @doc + @fname ScreenSize + @args number modify + + @desc + same as ScreenScale but use screen height (by default) + behvaior can be changed by user + @enddesc + + @returns + number + ]] + function _G.ScreenSize(modify) + return screenfunc(modify) + end + + local function dlib_screenscale_chages() + if use_type:GetBool() then + function screenfunc(modify) + return ScrHL() / 480 * modify + end + else + function screenfunc(modify) + return ScrWL() / 640 * modify + end + end + + DLib.TriggerScreenSizeUpdate(ScrWL(), ScrHL(), ScrWL(), ScrHL()) + end + + cvars.AddChangeCallback('dlib_screenscale', dlib_screenscale_chages, 'DLib') + cvars.AddChangeCallback('dlib_screenscale_mul', function() + dlib_screenscale_mul_get = dlib_screenscale_mul:GetFloat(1):max(0) + DLib.TriggerScreenSizeUpdate(ScrWL(), ScrHL(), ScrWL(), ScrHL()) + end, 'DLib') +end + +local threads1T = {} +local threads1C = {} +local threads2T = {} +local threads2C = {} +local SysTime = SysTime +local coroutine = coroutine +local maximal = 0 + +--[[ + @doc + @fname coroutine.syswait + @args number seconds + + @desc + like !g:coroutine.wait but use `SysTime()` + @enddesc +]] +function coroutine.syswait(seconds) + if type(seconds) ~= 'number'then + error('Invalid seconds amount provided') + end + + local thread = assert(coroutine.running(), 'Not inside coroutine!') + + if seconds < 0 then return end + + table.insert(threads1C, thread) + table.insert(threads1T, SysTime() + seconds) + coroutine.yield() +end + +hook.Add(SERVER and 'Tick' or 'Think', 'DLib.coroutine.syswait', function() + local stime = SysTime() + + for i = 1, #threads2C do + local thread = threads2C[i] + local time = threads2T[i] + + if time and time <= stime then + threads2C[i] = nil + threads2T[i] = nil + + if coroutine.status(thread) == 'suspended' then + local status, err = coroutine.resume(thread) + + if not status then + DLib.MessageError('Error executing thread:') + ErrorNoHalt(err .. '\n') + end + end + end + end + + local sw1t, sw2t, sw1c, sw2c = threads1T, threads2T, threads1C, threads2C + threads2T = sw1t + threads1T = sw2t + threads2C = sw1c + threads1C = sw2c +end, 3) diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/player.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/player.lua new file mode 100644 index 0000000..1eafbc4 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/player.lua @@ -0,0 +1,236 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 GetAll = player.GetAll +local player = player +local ipairs = ipairs +local math = math +local tonumber = tonumber +local tostring = tostring +local table = table +local type = type + +--[[ + @doc + @fname player.InRange + @args Vector position, number range + + @returns + table: of players +]] +function player.InRange(position, range) + range = range ^ 2 + + local output = {} + + for i, ply in ipairs(player.GetAll()) do + if ply:GetPos():DistToSqr(position) <= range then + table.insert(output, ply) + end + end + + return output +end + +--[[ + @doc + @replaces + @fname player.GetBySteamID + @args string steamid + + @desc + !g:player.GetBySteamID but with ipairs (sligtly better performance on heavy servers) + @enddesc + + @returns + Player: or false if player is not found +]] +function player.GetBySteamID(steamid) + if type(steamid) ~= 'string' then return false end + + steamid = steamid:upper() + + for i, ply in ipairs(player.GetAll()) do + if steamid == ply:SteamID() then return ply end + end + + return false +end + +--[[ + @doc + @replaces + @fname player.GetBySteamID64 + @args string steamid64 + + @desc + !g:player.GetBySteamID64 but with ipairs (sligtly better performance on heavy servers) + @enddesc + + @returns + Player: or false if player is not found +]] +function player.GetBySteamID64(steamid) + if type(steamid) ~= 'string' then return false end + + for i, ply in ipairs(player.GetAll()) do + if steamid == ply:SteamID64() then return ply end + end + + return false +end + +--[[ + @doc + @replaces + @fname player.GetByUniqueID + @args string uid + + @desc + !g:player.GetByUniqueID but with ipairs (sligtly better performance on heavy servers) + @enddesc + + @returns + Player: or false if player is not found +]] +function player.GetByUniqueID(id) + if type(id) == 'number' then + id = id:tostring() + end + + if type(id) ~= 'string' then return false end + + for i, ply in ipairs(player.GetAll()) do + if id == ply:UniqueID() then return ply end + end + + return false +end + +local plyMeta = FindMetaTable('Player') + +--[[ + @doc + @fname Player:GetInfoInt + @args string convar, number ifNone + + @returns + number +]] +function plyMeta:GetInfoInt(convar, ifNone) + ifNone = ifNone or 0 + if self:IsBot() then return ifNone end + local info = self:GetInfoDLib(convar) + return math.floor(tonumber(info or ifNone) or ifNone) +end + +--[[ + @doc + @fname Player:GetInfoFloat + @args string convar, number ifNone + + @returns + number +]] +function plyMeta:GetInfoFloat(convar, ifNone) + ifNone = ifNone or 0 + if self:IsBot() then return ifNone end + local info = self:GetInfoDLib(convar) + return tonumber(info or ifNone) or ifNone +end + +local LocalPlayer = LocalPlayer +local SERVER = SERVER + +--[[ + @doc + @fname Player:EyeAnglesFixed + + @returns + Angle: fixed eye angles +]] +function plyMeta:EyeAnglesFixed() + if SERVER or self ~= LocalPlayer() then + return self:EyeAngles() + end + + if not self:InVehicle() then + return self:EyeAngles() + end + + return self:GetVehicle():GetAngles() + self:EyeAngles() +end + +--[[ + @doc + @fname Player:GetInfoBool + @args string convar, boolean ifNone + + @returns + boolean +]] +function plyMeta:GetInfoBool(convar, ifNone) + if ifNone == nil then ifNone = false end + if self:IsBot() then return ifNone end + + local info = self:GetInfoDLib(convar) + + if type(info) == 'nil' or type(info) == 'no value' then + return ifNone + end + + if type(info) == 'boolean' then + return info + end + + if convar == 'false' then return false end + if convar == 'true' then return true end + + local num = tonumber(info) + + if not num then + return ifNone + end + + return num ~= 0 +end + +--[[ + @doc + @fname Player:GetInfoString + @args string convar, string ifNone + + @returns + string +]] +-- differents from GetInfo only by 100% returning string +function plyMeta:GetInfoString(convar, ifNone) + ifNone = ifNone or '' + if self:IsBot() then return ifNone end + + local info = self:GetInfoDLib(convar) + + if type(info) == 'nil' or type(info) == 'no value' then + return ifNone + end + + return info +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/render.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/render.lua new file mode 100644 index 0000000..4c65612 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/render.lua @@ -0,0 +1,103 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +if SERVER then return end + +local render = render +local assert = assert +local type = type +local table = table +local debug = debug + +-- nope, nu stack object, because util.Stack() sux +local stack = {} + +--[[ + @doc + @fname render.PushScissorRect + @args number x, number y, number xEnd, number yEnd + + @desc + stack based version of !g:render.SetScissorRect + @enddesc +]] +function render.PushScissorRect(x, y, xEnd, yEnd) + x = assert(type(x) == 'number' and x, 'x must be a number!') + y = assert(type(y) == 'number' and y, 'y must be a number!') + xEnd = assert(type(xEnd) == 'number' and xEnd, 'xEnd must be a number!') + yEnd = assert(type(yEnd) == 'number' and yEnd, 'xEnd must be a number!') + local amount = #stack + + if amount ~= 0 then + local x2, y2, xEnd2, yEnd2 = stack[amount - 3], stack[amount - 2], stack[amount - 1], stack[amount] + + x = x2:max(x) + y = y2:max(y) + xEnd = xEnd2:min(xEnd) + yEnd = yEnd2:min(yEnd) + end + + table.insert(stack, x) + table.insert(stack, y) + table.insert(stack, xEnd) + table.insert(stack, yEnd) + render.SetScissorRect(x, y, xEnd, yEnd, true) +end + +--[[ + @doc + @fname render.PopScissorRect + + @desc + stack based version of !g:render.SetScissorRect + @enddesc +]] +function render.PopScissorRect() + if #stack == 0 then + render.SetScissorRect(0, 0, 0, 0, false) + return + end + + if #stack == 4 then + table.remove(stack) + table.remove(stack) + table.remove(stack) + table.remove(stack) + render.SetScissorRect(0, 0, 0, 0, false) + return + end + + table.remove(stack) + table.remove(stack) + table.remove(stack) + table.remove(stack) + local amount = #stack + local x, y, xEnd, yEnd = stack[amount - 3], stack[amount - 2], stack[amount - 1], stack[amount] + render.SetScissorRect(x, y, xEnd, yEnd, true) +end + +local function PreRender() + if #stack ~= 0 then + stack = {} + end +end + +hook.Add('PreRender', 'render.PushScissorRect', PreRender) diff --git a/garrysmod/addons/util-dlib/lua/dlib/extensions/string.lua b/garrysmod/addons/util-dlib/lua/dlib/extensions/string.lua new file mode 100644 index 0000000..7231456 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/extensions/string.lua @@ -0,0 +1,410 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 gstring = _G.string +local string = setmetatable(DLib.string or {}, {__index = string}) +DLib.string = string +local unpack = unpack +local os = os +local select = select +local math = math +local table = table + +--[[ + @doc + @fname string.formatname + @args string self + + @desc + first character is uppecased + @enddesc + + @returns + string +]] +function gstring.formatname(self) + return self:sub(1, 1):upper() .. self:sub(2) +end + +--[[ + @doc + @fname string.formatname2 + @args string self + + @desc + first character is uppecased, `_` are replaced with spaces + @enddesc + + @returns + string +]] +function gstring.formatname2(self) + return self:sub(1, 1):upper() .. self:sub(2):replace('_', ' ') +end + +--[[ + @doc + @fname DLib.string.tformat + @args number time + + @deprecated + + @desc + use DLib.i18n.tformat + @enddesc + + @returns + string: formatted time +]] +function string.tformat(time) + if time > 0xFFFFFFFFF then + return 'Way too long' + elseif time <= 1 then + return 'Right now' + end + + local str = '' + + local centuries, years, months, weeks, days, hours, minutes, seconds = math.tformatVararg(time) + + if seconds ~= 0 then + str = seconds .. ' seconds' + end + + if minutes ~= 0 then + str = minutes .. ' minutes ' .. str + end + + if hours ~= 0 then + str = hours .. ' hours ' .. str + end + + if days ~= 0 then + str = days .. ' days ' .. str + end + + if weeks ~= 0 then + str = weeks .. ' weeks ' .. str + end + + if months ~= 0 then + str = months .. ' months ' .. str + end + + if years ~= 0 then + str = years .. ' years ' .. str + end + + if centuries ~= 0 then + str = centuries .. ' centuries ' .. str + end + + return str +end + +--[[ + @doc + @fname DLib.string.qdate + @args number time = os.time(), boolean isLocal = true, boolean removeTimezone = false + + @desc + `isLocal` - time is result of call os.time() or similar + `removeTimezone` - whenever to sub/add time using operating system's timezone (and don't print timezone in final result) + @enddesc + + @returns + string: quick formatted os.date (Human friendly ISO8601 format) +]] +function string.qdate(time, isLocal, removeTimezone) + if time == nil then time = os.time() end + if isLocal == nil then isLocal = true end + + local timezone = os.date('%z', time) + local timezoneNum = 0 + + if isLocal then + if #timezone == 0 then + timezone = '+??:??' + else + timezone = timezone:sub(1, 3) .. ':' .. timezone:sub(4) + timezoneNum = ((tonumber(timezone:sub(2, 3)) or 0) * 3600 + (tonumber(timezone:sub(4)) or 0) * 60) * (timezone[1] == '-' and -1 or 1) + end + end + + if removeTimezone and isLocal then + time = time - timezoneNum + end + + if isLocal and not removeTimezone then + return os.date('%Y-%m-%d %H:%M:%S', time) .. ' UTC' .. timezone + else + return os.date('%Y-%m-%d %H:%M:%S', time) .. ' UTC+00:00' + end +end + +string.HU_IN_M = 40 +string.HU_IN_CM = string.HU_IN_M / 100 + +--[[ + @doc + @fname DLib.string.ddistance + @args number z, boolean newline, number fromZ + + @returns + string: unlocalized Z difference +]] +function string.ddistance(z, newline, from) + if newline == nil then + newline = true + end + + local delta + + if from then + delta = from - z + else + delta = LocalPlayer():GetPos().z - z + end + + if delta > 200 and not newline then + return string.fdistance(delta) .. ' lower' + end + + if delta > 200 and newline then + return '\n' .. string.fdistance(delta) .. ' lower' + end + + if -delta > 200 and not newline then + return string.fdistance(delta) .. 'upper' + end + + if -delta > 200 and newline then + return '\n' .. string.fdistance(delta) .. 'upper' + end + + return '' +end + +--[[ + @doc + @fname DLib.string.fdistance + @args number distanceInHammerUnits + + @returns + string: formatted metres +]] +function string.fdistance(m) + return string.format('%.1fm', m / string.HU_IN_M) +end + +--[[ + @doc + @fname DLib.string.niceName + @args Entity ent + @deprecated + + @desc + Use Entity:GetPrintNameDLib() + @enddesc + + @returns + string +]] +function string.niceName(ent) + if not IsValid(ent) then return '' end + if ent.Nick then return ent:Nick() end + if ent.PrintName and ent.PrintName ~= '' then return ent.PrintName end + if ent.GetPrintName then return ent:GetPrintName() end + return ent:GetClass() +end + +--[[ + @doc + @fname string.split + @args string self, string separator, vararg arguments + + @desc + flip of !g:string.Explode + @enddesc + + @returns + table: of strings +]] +function string.split(stringIn, explodeIn, ...) + return string.Explode(explodeIn, stringIn, ...) +end + +-- fuck https://github.com/Facepunch/garrysmod/pull/1176 +string.StartsWith = string.StartWith + + +--[[ + @doc + @fname string.StartsWith + @args string self, string check + + @desc + [alias](https://github.com/Facepunch/garrysmod/pull/1176) of !g:string.StartWith + @enddesc + + @returns + boolean +]] +gstring.StartsWith = gstring.StartWith + +local funcs = {} + +for k, v in pairs(gstring) do + funcs[k:sub(1, 1):lower() .. k:sub(2)] = v +end + +for k, v in pairs(funcs) do + if gstring[k] == nil then + gstring[k] = v + end +end + +--[[ + @doc + @fname DLib.string.bchar + @args vararg bytes + + @desc + allows to bypass bytes limit of bytes-per-call of !g:string.char transparently + @enddesc + + @returns + string +]] +function string.bchar(...) + local bytes = select('#', ...) + + if bytes < 800 then + return string.char(...) + end + + local input = {...} + local output = '' + local i = -799 + + ::loop:: + i = i + 800 + + output = output .. string.char(unpack(input, i, math.min(i + 799, bytes))) + + if i + 799 < bytes then + goto loop + end + + return output +end + +--[[ + @doc + @fname DLib.string.bcharTable + @args table bytes + + @desc + allows to bypass bytes limit of bytes-per-call of !g:string.char transparently + @enddesc + + @returns + string +]] +function string.bcharTable(input) + local bytes = #input + if bytes == 0 then return '' end + + if bytes < 800 then + return string.char(unpack(input)) + end + + local output = '' + local i = -799 + + ::loop:: + i = i + 800 + + local status, output2 = pcall(string.char, unpack(input, i, math.min(i + 799, bytes))) + + if not status then + for i2 = i, math.min(i + 799, bytes) do + if input[i2] < 0 or input[i2] > 255 then + error(output2 .. ' (' .. input[i2] .. ')') + end + end + end + + output = output .. output2 + + if i + 799 < bytes then + goto loop + end + + return output +end + +--[[ + @doc + @fname DLib.string.bbyte + @args string self, number sliceAt, number sliceEnd + + @desc + allows to bypass bytes limit of bytes-per-call of !g:string.byte transparently + @enddesc + + @returns + table: of bytes +]] +function string.bbyte(strIn, sliceStart, sliceEnd) + local strLen = #strIn + local delta = sliceEnd - sliceStart + + if delta < 800 then + local i = sliceStart - 1 + local output = {} + + ::loop1:: + i = i + 1 + + table.insert(output, strIn:byte(i, i)) + + if i < sliceEnd then + goto loop1 + end + + return output + end + + local output = {} + + local i = sliceStart - 1 + + ::loop:: + i = i + 1 + + table.insert(output, strIn:byte(i, i)) + + if i < sliceEnd then + goto loop + end + + return output +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/i18n/dlib/en.lua b/garrysmod/addons/util-dlib/lua/dlib/i18n/dlib/en.lua new file mode 100644 index 0000000..dbb0090 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/i18n/dlib/en.lua @@ -0,0 +1,580 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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. + + +gui.misc.apply = 'Apply' +gui.misc.ok = 'OK' +gui.misc.cancel = 'Cancel' +gui.misc.yes = 'Yes' +gui.misc.no = 'No' + +gui.entry.invalidsymbol = 'Symbol is not allowed.' +gui.entry.limit = 'Field limit exceeded.' + +gui.dlib.hudcommons.reset = 'Reset' + +gui.dlib.colormixer.palette = 'Palette' +gui.dlib.colormixer.save_color = 'Save current color into this square' +gui.dlib.colormixer.reset_palette = 'Reset palette' +gui.dlib.colormixer.reset_palette_confirm = 'Really?' +gui.dlib.colormixer.palette_window = 'Palette window' +gui.dlib.colormixer.palette_regular = 'Regular palette' +gui.dlib.colormixer.palette_extended = 'Extended palette' +gui.dlib.colormixer.copy_color = 'Copy color in RGB format' +gui.dlib.colormixer.copy_hex_color = 'Copy color in HEX format' + +info.dlib.tformat.seconds = 'seconds' +info.dlib.tformat.minutes = 'minutes' +info.dlib.tformat.hours = 'hours' +info.dlib.tformat.days = 'days' +info.dlib.tformat.weeks = 'weeks' +info.dlib.tformat.months = 'months' +info.dlib.tformat.years = 'years' +info.dlib.tformat.centuries = 'centuries' + +info.dlib.tformat.long = 'Never™' +info.dlib.tformat.now = 'Right now' +info.dlib.tformat.past = 'The past' + +local function define(from, to, target, form1, form2) + for i = from, to do + local div = i % 10 + + if div == 1 and i ~= 11 then + target[tostring(i)] = i .. ' ' .. form1 + else -- when you encounter that english is way too simple + target[tostring(i)] = i .. ' ' .. form2 + end + end +end + +for _, thing in ipairs({info.dlib.tformat.countable, info.dlib.tformat.countable_ago}) do + define(1, 60, thing.second, 'second', 'seconds') + define(1, 60, thing.minute, 'minute', 'minutes') + define(1, 24, thing.hour, 'hour', 'hours') + define(1, 7, thing.day, 'day', 'days') + define(1, 4, thing.week, 'week', 'weeks') + define(1, 12, thing.month, 'month', 'months') + define(1, 100, thing.year, 'year', 'years') + define(1, 100, thing.century, 'century', 'centuries') +end + +info.dlib.tformat.ago = '%s ago' +info.dlib.tformat.ago_inv = '%s in future' + +gui.dlib.friends.title = 'DLib Friends' +gui.dlib.friends.open = 'Open Friends Menu' + +gui.dlib.friends.edit.add_title = 'Adding %s <%s> as friend' +gui.dlib.friends.edit.edit_title = 'Editing %s <%s> friend settings' +gui.dlib.friends.edit.going = 'You are going to be a friend with %s in...' +gui.dlib.friends.edit.youare = 'You are friend with %s in...' +gui.dlib.friends.edit.remove = 'Remove friend' + +gui.dlib.friends.invalid.title = 'Invalid SteamID' +gui.dlib.friends.invalid.ok = 'Okay :(' +gui.dlib.friends.invalid.desc = '%q doesnt look like valid steamid!' + +gui.dlib.friends.settings.steam = 'Consider Steam friends as DLib friends' +gui.dlib.friends.settings.your = 'Your friends ->' +gui.dlib.friends.settings.server = 'Server players ->' + +gui.dlib.friends.settings.foreign = '[Foreign] ' + +gui.dlib.donate.top = 'DLib: Make a donation?' +gui.dlib.donate.button.yes = 'Make a donation!' +gui.dlib.donate.button.paypal = 'Make a donation, but on PayPal!' +gui.dlib.donate.button.no = 'Ask me later' +gui.dlib.donate.button.never = 'Never ask again' +gui.dlib.donate.button.learnabout = 'Read about "Donationware"...' +gui.dlib.donate.button.learnabout_url = 'https://en.wikipedia.org/wiki/Donationware' + +message.dlib.hudcommons.edit.notice = 'Press ESC to close the editor' + +gui.dlib.donate.text = [[Hello there! I see you were AFK for a long time (if you slept all these time, good morning... or whatever), +but so, if you are awake, i want to ask you: Can you please make a dontation? +DLib and all official addons on top of it are Donationware! +Just a bit of donation! Even if you can only give 1$ or 1€, thats great if you would ask! Just imagine: +If everyone who is subscribed to my addons will donate money for a cup of tea it will be enough to cover +all my parent's credits. It could also help a lot to my mother who spend entire days on her job. +Currently, the only thing i do is developing these free addons just for you! +Just for community! For free and it is even open source with easy way of contribution! If you could just +make a small donation you will support next addons: +DLib%s]] + +gui.dlib.donate.more = ' and %i more addons!..' + +gui.dlib.hudcommons.positions = '%s positions' +gui.dlib.hudcommons.fonts = '%s fonts' +gui.dlib.hudcommons.colors = '%s colors' +gui.dlib.hudcommons.font = 'Font' +gui.dlib.hudcommons.font_label = 'Settings for %s' +gui.dlib.hudcommons.save_hint = 'Don\'t forget to host_writeconfig in\nconsole after you did all your changes!' +gui.dlib.hudcommons.weight = 'Weight' +gui.dlib.hudcommons.size = 'Size' + +gui.dlib.notify.families_loading = 'Expect lag, DLib is baking a cake\n(searching for installed font families)' + +-- yotta Y 10008 1024 1000000000000000000000000 septillion quadrillion 1991 +-- zetta Z 10007 1021 1000000000000000000000 sextillion trilliard 1991 +-- exa E 10006 1018 1000000000000000000 quintillion trillion 1975 +-- peta P 10005 1015 1000000000000000 quadrillion billiard 1975 +-- tera T 10004 1012 1000000000000 trillion billion 1960 +-- giga G 10003 109 1000000000 billion milliard 1960 +-- mega M 10002 106 1000000 million 1873 +-- kilo k 10001 103 1000 thousand 1795 +-- hecto h 10002/3 102 100 hundred 1795 +-- deca da 10001/3 101 10 ten 1795 + +-- deci d 1000−1/3 10−1 0.1 tenth 1795 +-- centi c 1000−2/3 10−2 0.01 hundredth 1795 +-- milli m 1000−1 10−3 0.001 thousandth 1795 +-- micro μ 1000−2 10−6 0.000001 millionth 1873 +-- nano n 1000−3 10−9 0.000000001 billionth milliardth 1960 +-- pico p 1000−4 10−12 0.000000000001 trillionth billionth 1960 +-- femto f 1000−5 10−15 0.000000000000001 quadrillionth billiardth (Proposed) 1964 +-- atto a 1000−6 10−18 0.000000000000000001 quintillionth trillionth 1964 +-- zepto z 1000−7 10−21 0.000000000000000000001 sextillionth trilliardth 1991 +-- yocto y 1000−8 10−24 0.000000000000000000000001 septillionth quadrillionth 1991 + +local prefix = { + {'yocto', 'y'}, + {'zepto', 'z'}, + {'atto', 'a'}, + {'femto', 'f'}, + {'pico', 'p'}, + {'nano', 'n'}, + {'micro', 'μ'}, + {'milli', 'm'}, + {'centi', 'c'}, + {'deci', 'd'}, + {'kilo', 'k'}, + {'mega', 'M'}, + {'giga', 'G'}, + {'tera', 'T'}, + {'peta', 'P'}, + {'exa', 'E'}, + {'zetta', 'Z'}, + {'yotta', 'Y'}, +} + +local units = [[hertz Hz frequency 1/s s−1 +radian rad angle m/m 1 +steradian sr solid angle m2/m2 1 +newton N force, weight kg⋅m/s2 kg⋅m⋅s−2 +pascal Pa pressure, stress N/m2 kg⋅m−1⋅s−2 +joule J energy, work, heat N⋅m, C⋅V, W⋅s kg⋅m2⋅s−2 +watt W power, radiant flux J/s, V⋅A kg⋅m2⋅s−3 +coulomb C electric charge or quantity of electricity s⋅A, F⋅V s⋅A +volt V voltage, electrical potential difference, electromotive force W/A, J/C kg⋅m2⋅s−3⋅A−1 +farad F electrical capacitance C/V, s/Ω kg−1⋅m−2⋅s4⋅A2 +ohm Ω electrical resistance, impedance, reactance 1/S, V/A kg⋅m2⋅s−3⋅A−2 +siemens S electrical conductance 1/Ω, A/V kg−1⋅m−2⋅s3⋅A2 +weber Wb magnetic flux J/A, T⋅m2 kg⋅m2⋅s−2⋅A−1 +tesla T magnetic induction, magnetic flux density V⋅s/m2, Wb/m2, N/(A⋅m) kg⋅s−2⋅A−1 +henry H electrical inductance V⋅s/A, Ω⋅s, Wb/A kg⋅m2⋅s−2⋅A−2 +degree Celsius °C temperature relative to 273.15 K K K +lumen lm luminous flux cd⋅sr cd +lux lx illuminance lm/m2 cd⋅sr/m2 +becquerel Bq radioactivity (decays per unit time) 1/s s−1 +gray Gy absorbed dose (of ionizing radiation) J/kg m2⋅s−2 +sievert Sv equivalent dose (of ionizing radiation) J/kg m2⋅s−2 +katal kat catalytic activity mol/s s−1⋅mol]] + +for i, row in ipairs(prefix) do + info.dlib.si.prefix[row[1]].name = row[3] or row[1]:formatname() + info.dlib.si.prefix[row[1]].prefix = row[2] +end + +for i, row in ipairs(units:split('\n')) do + local measure, NaM = row:match('(%S+)%s+(%S+)') + + if measure and NaM then + info.dlib.si.units[measure].name = measure:formatname() + info.dlib.si.units[measure].suffix = NaM + end +end + +info.dlib.si.units.kelvin.name = 'Kelvin' +info.dlib.si.units.kelvin.suffix = 'K' + +info.dlib.si.units.celsius.name = 'Celsius' +info.dlib.si.units.celsius.suffix = 'C' + +info.dlib.si.units.fahrenheit.name = 'Fahrenheit' +info.dlib.si.units.fahrenheit.suffix = 'F' + +info.dlib.si.units.gram.name = 'Gram' +info.dlib.si.units.gram.suffix = 'g' + +info.dlib.si.units.metre.name = 'Metre' +info.dlib.si.units.metre.suffix = 'm' + +info.dlib.si.units.litre.name = 'Litre' +info.dlib.si.units.litre.suffix = 'L' + +-- seems like that on server install required files are corrupted + +info.entity.ammo_357.name = "357 Ammo" +info.entity.ammo_9mmar.name = "9mm Ammo" +info.entity.ammo_9mmbox.name = "9mm Ammo Box" +info.entity.ammo_9mmclip.name = "9mm Ammo" +info.entity.ammo_argrenades.name = "MP5 Grenades" +info.entity.ammo_buckshot.name = "Shotgun Ammo" +info.entity.ammo_crossbow.name = "Crossbow Bolts" +info.entity.ammo_egonclip.name = "Uranium" +info.entity.ammo_gaussclip.name = "Gauss Ammo" +info.entity.ammo_glockclip.name = "Glock Ammo" +info.entity.ammo_mp5clip.name = "MP5 Ammo" +info.entity.ammo_mp5grenades.name = "MP5 Grenades" +info.entity.ammo_rpgclip.name = "RPG Rockets" +info.entity.base_ai.name = "AI" +info.entity.base_anim.name = "Animated Entity" +info.entity.base_brush.name = "Brush" +info.entity.base_edit.name = "Editor" +info.entity.base_entity.name = "Entity" +info.entity.base_gmodentity.name = "Entity" +info.entity.base_nextbot.name = "Nextbot" +info.entity.basehl1combatweapon.name = "Weapon" +info.entity.basehl1mpcombatweapon.name = "Weapon" +info.entity.basehl2mpcombatweapon.name = "Weapon" +info.entity.basehlcombatweapon.name = "Weapon" +info.entity.bmortar.name = "Big Momma Mortar Shot" +info.entity.bounce_bomb.name = "Bouncy Grenade" +info.entity.combine_bouncemine.name = "Combine Bouncy Mine" +info.entity.combine_mine.name = "Combine Mine" +info.entity.concussiveblast.name = "Concussion Explosion" +info.entity.controller_energy_ball.name = "Energy Ball" +info.entity.controller_head_ball.name = "Energy Ball" +info.entity.crane_tip.name = "Crane Magnet" +info.entity.crossbow_bolt_hl1.name = "Crossbow Bolt" +info.entity.crossbow_bolt.name = "Crossbow Bolt" +info.entity.dynamic_prop.name = "Dynamic Prop" +info.entity.edit_fog.name = "Fog Editor" +info.entity.edit_sky.name = "Sky Editor" +info.entity.edit_sun.name = "Sun Editor" +info.entity.ent_watery_leech.name = "Water Leech" +info.entity.entityflame.name = "Fire" +info.entity.env_beam.name = "Beam" +info.entity.env_explosion.name = "Explosion" +info.entity.env_fire.name = "Fire" +info.entity.env_headcrabcanister.name = "Headcrab Canister" +info.entity.env_laser.name = "Laser" +info.entity.env_physexplosion.name = "Physics Explosion" +info.entity.env_physimpact.name = "Impact" +info.entity.env_physwire.name = "Wire" +info.entity.func_brush.name = "Brush" +info.entity.func_button.name = "Button" +info.entity.func_conveyor.name = "Conveyer" +info.entity.func_door_rotating.name = "Door" +info.entity.func_door.name = "Door" +info.entity.func_movelinear.name = "Moving Object" +info.entity.func_pendulum.name = "Pendulum" +info.entity.func_physbox_multiplayer.name = "Physics Object" +info.entity.func_physbox.name = "Physics Object" +info.entity.func_plat.name = "Moving Object" +info.entity.func_platrot.name = "Moving Object" +info.entity.func_pushable.name = "Physics Object" +info.entity.func_recharge.name = "Suit Recharger" +info.entity.func_rot_button.name = "Button" +info.entity.func_rotating.name = "Moving Object" +info.entity.func_tank_combine_cannon.name = "Combine Autogun" +info.entity.func_tank.name = "Mounted Gun" +info.entity.func_tankairboatgun.name = "Airboat Gun" +info.entity.func_tankapcrocket.name = "APC Rocket" +info.entity.func_tanklaser.name = "Tank Laser" +info.entity.func_tankmortar.name = "Tank Mortar" +info.entity.func_tankphyscannister.name = "Tank Canister" +info.entity.func_tankpulselaser.name = "Tank Pulse" +info.entity.func_tankrocket.name = "Tank Rocket" +info.entity.func_tracktrain.name = "Moving Object" +info.entity.func_train.name = "Moving Object" +info.entity.func_wall_toggle.name = "Wall" +info.entity.func_wall.name = "Wall" +info.entity.garg_stomp.name = "Energy" +info.entity.generic_actor.name = "Generic Actor" +info.entity.gmod_anchor.name = "Anchor" +info.entity.gmod_balloon.name = "Balloon" +info.entity.gmod_button.name = "Button" +info.entity.gmod_camera.name = "Camera" +info.entity.gmod_cameraprop.name = "Camera" +info.entity.gmod_dynamite.name = "Dynamite" +info.entity.gmod_emitter.name = "Emitter" +info.entity.gmod_ghost.name = "Ghost" +info.entity.gmod_hoverball.name = "Hoverball" +info.entity.gmod_lamp.name = "Lamp" +info.entity.gmod_light.name = "Light" +info.entity.gmod_thruster.name = "Thruster" +info.entity.gmod_tool.name = "Tool Gun" +info.entity.gmod_wheel.name = "Wheel" +info.entity.grenade_ar2.name = "SMG Grenade" +info.entity.grenade_beam.name = "Mortar Beam" +info.entity.grenade_hand.name = "Hand Grenade" +info.entity.grenade_helicopter.name = "Helicopter Bomb" +info.entity.grenade_homer.name = "Homing Grenade" +info.entity.grenade_mp5.name = "MP5 Grenade" +info.entity.grenade_pathfollower.name = "Pathfollowing Grenade" +info.entity.grenade_spit.name = "Antlion Spit" +info.entity.grenade.name = "Grenade" +info.entity.hl2mp_ragdoll.name = "Ragdoll" +info.entity.hornet.name = "Hornet" +info.entity.hunter_flechette.name = "Flechette" +info.entity.item_ammo_357_large.name = "Large .357 Ammo" +info.entity.item_ammo_357.name = ".357 Ammo" +info.entity.item_ammo_ar2_altfire.name = "Combine Energy Ball" +info.entity.item_ammo_ar2_large.name = "Large AR2 Ammo" +info.entity.item_ammo_ar2.name = "AR2 Ammo" +info.entity.item_ammo_crate.name = "Ammo Crate" +info.entity.item_ammo_crossbow.name = "Crossbow Bolts" +info.entity.item_ammo_pistol_large.name = "Large Pistol Ammo" +info.entity.item_ammo_pistol.name = "Pistol Ammo" +info.entity.item_ammo_smg1_grenade.name = "SMG Grenade" +info.entity.item_ammo_smg1_large.name = "Large SMG Ammo" +info.entity.item_ammo_smg1.name = "SMG Ammo" +info.entity.item_ar2_grenade.name = "SMG Grenade" +info.entity.item_battery.name = "Armor Battery" +info.entity.item_box_buckshot.name = "Shotgun Ammo" +info.entity.item_box_lrounds.name = "Small AR2 Ammo" +info.entity.item_box_mrounds.name = "Small SMG Ammo" +info.entity.item_box_srounds.name = "Small Pistol Ammo" +info.entity.item_dynamic_resupply.name = "Dynamic Resupply" +info.entity.item_flare_round.name = "Flare" +info.entity.item_grubnugget.name = "Grub Nugget" +info.entity.item_healthcharger.name = "Health Charger" +info.entity.item_healthkit.name = "Health Kit" +info.entity.item_healthvial.name = "Health Vial" +info.entity.item_item_crate.name = "Item Crate" +info.entity.item_large_box_lrounds.name = "Large AR2 Ammo" +info.entity.item_large_box_mrounds.name = "Large SMG Ammo" +info.entity.item_large_box_srounds.name = "Large Pistol Ammo" +info.entity.item_longjump.name = "Long Jump Module" +info.entity.item_ml_grenade.name = "RPG Round" +info.entity.item_rpg_round.name = "RPG Round" +info.entity.item_suit.name = "Suit" +info.entity.item_suitcharger.name = "Suit Charger" +info.entity.logic_choreographed_scene.name = "Choreographed Scene" +info.entity.manhack_welder.name = "Manhack Gun" +info.entity.momentary_door.name = "Door" +info.entity.momentary_rot_button.name = "Button" +info.entity.monster_alien_controller.name = "Controller" +info.entity.monster_alien_grunt.name = "Alien Grunt" +info.entity.monster_alien_slave.name = "Alien Slave" +info.entity.monster_apache.name = "Apache Helicopter" +info.entity.monster_babycrab.name = "Baby Crab" +info.entity.monster_barnacle.name = "Barnacle" +info.entity.monster_barney_dead.name = "Dead Security Officer" +info.entity.monster_barney.name = "Security Officer" +info.entity.monster_bigmomma.name = "Gonarch" +info.entity.monster_bloater.name = "Floater" +info.entity.monster_bullchicken.name = "Bullsquid" +info.entity.monster_cockroach.name = "Cockroach" +info.entity.monster_flyer_flock.name = "Flyer Flock" +info.entity.monster_flyer.name = "Flyer" +info.entity.monster_furniture.name = "Actor" +info.entity.monster_gargantua.name = "Gargantua" +info.entity.monster_generic.name = "Actor" +info.entity.monster_gman.name = "G-Man" +info.entity.monster_grunt_repel.name = "Repelling Grunt" +info.entity.monster_headcrab.name = "Headcrab" +info.entity.monster_hevsuit_dead.name = "Dead Researcher" +info.entity.monster_hgrunt_dead.name = "Dead Grunt" +info.entity.monster_houndeye.name = "Houndeye" +info.entity.monster_human_assassin.name = "Assassin" +info.entity.monster_human_grunt.name = "Grunt" +info.entity.monster_ichthyosaur.name = "Ichthyosaur" +info.entity.monster_leech.name = "Leech" +info.entity.monster_miniturret.name = "Mini Turret" +info.entity.monster_mortar.name = "Mortar" +info.entity.monster_nihilanth.name = "Nihilanth" +info.entity.monster_osprey.name = "Osprey" +info.entity.monster_satchel.name = "Satchel Charge" +info.entity.monster_scientist_dead.name = "Dead Scientist" +info.entity.monster_scientist.name = "Scientist" +info.entity.monster_sentry.name = "Sentry" +info.entity.monster_sitting_scientist.name = "Scientist" +info.entity.monster_snark.name = "Snark" +info.entity.monster_tentacle.name = "Tentacle" +info.entity.monster_tripmine.name = "Tripmine" +info.entity.monster_turret.name = "Heavy Turret" +info.entity.monster_zombie.name = "Zombie" +info.entity.mortarshell.name = "Combine Suppression Device" +info.entity.nihilanth_energy_ball.name = "Energy Ball" +info.entity.npc_advisor.name = "Advisor" +info.entity.npc_alyx.name = "Alyx" +info.entity.npc_antlion_grub.name = "Antlion Grub" +info.entity.npc_antlion_worker.name = "Antlion Worker" +info.entity.npc_antlion.name = "Antlion" +info.entity.npc_antlionguard.name = "Antlion Guard" +info.entity.npc_barnacle_tongue_tip.name = "Barnacle Tongue" +info.entity.npc_barnacle.name = "Barnacle" +info.entity.npc_barney.name = "Barney" +info.entity.npc_breen.name = "Breen" +info.entity.npc_bullseye.name = "Target" +info.entity.npc_citizen.name = "Citizen" +info.entity.npc_clawscanner.name = "Claw Scanner" +info.entity.npc_combine_s.name = "Combine Soldier" +info.entity.npc_combine.name = "Combine" +info.entity.npc_combinedropship.name = "Dropship" +info.entity.npc_combinegunship.name = "Gunship" +info.entity.npc_concussiongrenade.name = "Concussion Grenade" +info.entity.npc_contactgrenade.name = "Contact Grenade" +info.entity.npc_crow.name = "Crow" +info.entity.npc_cscanner.name = "City Scanner" +info.entity.npc_dog.name = "Dog" +info.entity.npc_eli.name = "Eli" +info.entity.npc_enemyfinder_combinecannon.name = "Combine Cannon" +info.entity.npc_enemyfinder.name = "Enemy Finder" +info.entity.npc_fastzombie_torso.name = "Fast Zombie Torso" +info.entity.npc_fastzombie.name = "Fast Zombie" +info.entity.npc_fisherman.name = "Fisherman" +info.entity.npc_furniture.name = "Actor" +info.entity.npc_gman.name = "G-Man" +info.entity.npc_grenade_bugbait.name = "Bugbait" +info.entity.npc_grenade_frag.name = "Grenade" +info.entity.npc_handgrenade.name = "Grenade" +info.entity.npc_headcrab_black.name = "Poison Headcrab" +info.entity.npc_headcrab_fast.name = "Fast Headcrab" +info.entity.npc_headcrab_poison.name = "Poison Headcrab" +info.entity.npc_headcrab.name = "Headcrab" +info.entity.npc_helicopter.name = "Helicopter" +info.entity.npc_hunter.name = "Hunter" +info.entity.npc_ichthyosaur.name = "Ichthyosaur" +info.entity.npc_kleiner.name = "Kleiner" +info.entity.npc_launcher.name = "Launcher" +info.entity.npc_magnusson.name = "Magnusson" +info.entity.npc_manhack.name = "Manhack" +info.entity.npc_metropolice.name = "Metro-Police" +info.entity.npc_missiledefense.name = "Missile Defense" +info.entity.npc_monk.name = "Father Grigori" +info.entity.npc_mossman.name = "Mossman" +info.entity.npc_pigeon.name = "Pigeon" +info.entity.npc_poisonzombie.name = "Poison Zombie" +info.entity.npc_rollermine.name = "Rollermine" +info.entity.npc_satchel.name = "Satchel Charge" +info.entity.npc_seagull.name = "Seagull" +info.entity.npc_sniper.name = "Sniper" +info.entity.npc_stalker.name = "Stalker" +info.entity.npc_strider.name = "Strider" +info.entity.npc_tripmine.name = "Tripmine" +info.entity.npc_turret_ceiling.name = "Ceiling Turret" +info.entity.npc_turret_floor.name = "Turret" +info.entity.npc_turret_ground.name = "Ground Turret" +info.entity.npc_vortigaunt.name = "Vortigaunt" +info.entity.npc_zombie_torso.name = "Zombie Torso" +info.entity.npc_zombie.name = "Zombie" +info.entity.npc_zombine.name = "Zombine" +info.entity.phys_magnet.name = "Magnet" +info.entity.physics_cannister.name = "Cannister" +info.entity.physics_prop_ragdoll.name = "Ragdoll" +info.entity.physics_prop.name = "Physics Prop" +info.entity.player.name = "Player" +info.entity.point_hurt.name = "Point Hurt" +info.entity.prop_combine_ball.name = "Combine Ball" +info.entity.prop_door_rotating.name = "Door" +info.entity.prop_dropship_container.name = "Dropship Container" +info.entity.prop_dynamic_ornament.name = "Dynamic Prop" +info.entity.prop_dynamic_override.name = "Dynamic Prop" +info.entity.prop_dynamic.name = "Dynamic Prop" +info.entity.prop_effect.name = "Effect" +info.entity.prop_physics_multiplayer.name = "Physics Prop" +info.entity.prop_physics_override.name = "Physics Prop" +info.entity.prop_physics_respawnable.name = "Physics Prop" +info.entity.prop_physics.name = "Physics Prop" +info.entity.prop_ragdoll_attached.name = "Ragdoll" +info.entity.prop_ragdoll.name = "Ragdoll" +info.entity.prop_sphere.name = "Sphere" +info.entity.prop_stickybomb.name = "Magnusson Device" +info.entity.prop_thumper.name = "Thumper" +info.entity.prop_vehicle_airboat.name = "Airboat" +info.entity.prop_vehicle_apc.name = "Combine APC" +info.entity.prop_vehicle_choreo_generic.name = "Vehicle" +info.entity.prop_vehicle_crane.name = "Crane" +info.entity.prop_vehicle_driveable.name = "Vehicle" +info.entity.prop_vehicle_jeep_old.name = "Vehicle" +info.entity.prop_vehicle_jeep.name = "Vehicle" +info.entity.prop_vehicle_prisoner_pod.name = "Pod" +info.entity.prop_vehicle.name = "Vehicle" +info.entity.proto_sniper.name = "Sniper" +info.entity.ragdoll_motion.name = "Ragdoll Controller" +info.entity.rpg_missile.name = "RPG Missile" +info.entity.rpg_rocket.name = "RPG Rocket" +info.entity.sent_ai.name = "AI" +info.entity.sent_anim.name = "Animated Entity" +info.entity.sent_ball.name = "Bouncy Ball" +info.entity.sent_brush.name = "Brush" +info.entity.sent_nextbot.name = "Nextbot" +info.entity.simple_bot.name = "Bot" +info.entity.simple_physics_brush.name = "Brush" +info.entity.simple_physics_prop.name = "Physics Prop" +info.entity.speaker.name = "Speaker" +info.entity.squidspit.name = "Bullsquid Spit" +info.entity.trigger_hurt.name = "Trigger Hurt" +info.entity.trigger_impact.name = "Impact" +info.entity.trigger_vphysics_motion.name = "Moving Object" +info.entity.trigger_waterydeath.name = "Water Leeches" +info.entity.trigger.name = "Trigger" +info.entity.weapon_357_hl1.name = ".357 Handgun" +info.entity.weapon_357.name = ".357 Magnum" +info.entity.weapon_alyxgun.name = "Alyx Gun" +info.entity.weapon_annabelle.name = "Annabelle" +info.entity.weapon_ar2.name = "AR2" +info.entity.weapon_bugbait.name = "Bugbait" +info.entity.weapon_citizenpackage.name = "Package" +info.entity.weapon_citizensuitcase.name = "Suitcase" +info.entity.weapon_crossbow_hl1.name = "Crossbow" +info.entity.weapon_crossbow.name = "Crossbow" +info.entity.weapon_crowbar_hl1.name = "Crowbar" +info.entity.weapon_crowbar.name = "Crowbar" +info.entity.weapon_cubemap.name = "Cubemaps" +info.entity.weapon_egon.name = "Gluon Gun" +info.entity.weapon_fists.name = "Fists" +info.entity.weapon_flechettegun.name = "Flechette Gun" +info.entity.weapon_frag.name = "Grenade" +info.entity.weapon_gauss.name = "Tau Cannon" +info.entity.weapon_glock.name = "9mm Handgun" +info.entity.weapon_hl2mp_base.name = "Weapon" +info.entity.weapon_hornetgun.name = "Hornet Gun" +info.entity.weapon_medkit.name = "Medkit" +info.entity.weapon_mp5.name = "MP5" +info.entity.weapon_oldmanharpoon.name = "Harpoon" +info.entity.weapon_physcannon.name = "Gravity Gun" +info.entity.weapon_physgun.name = "Physics Gun" +info.entity.weapon_pistol.name = "Pistol" +info.entity.weapon_rpg_hl1.name = "RPG Launcher" +info.entity.weapon_rpg.name = "RPG" +info.entity.weapon_satchel.name = "Satchel Charges" +info.entity.weapon_shotgun_hl1.name = "Shotgun" +info.entity.weapon_shotgun.name = "Shotgun" +info.entity.weapon_slam.name = "S.L.A.M" +info.entity.weapon_smg1.name = "SMG1" +info.entity.weapon_snark.name = "Snarks" +info.entity.weapon_striderbuster.name = "Magnusson Device" +info.entity.weapon_stunstick.name = "Stunstick" +info.entity.weapon_swep.name = "Scripted Weapon" +info.entity.weapon_tripmine.name = "Tripmines" +info.entity.weaponbox.name = "Weapon Box" +info.entity.worldspawn.name = "World" +info.entity.xen_hair.name = "Xen Hair" +info.entity.xen_hull.name = "Large Xen Spore" +info.entity.xen_plantlight.name = "Xen Plant" +info.entity.xen_spore_large.name = "Large Xen Spore" +info.entity.xen_spore_medium.name = "Medium Xen Spore" +info.entity.xen_spore_small.name = "Small Xen Spore" +info.entity.xen_tree.name = "Xen Tree" diff --git a/garrysmod/addons/util-dlib/lua/dlib/i18n/dlib/ru.lua b/garrysmod/addons/util-dlib/lua/dlib/i18n/dlib/ru.lua new file mode 100644 index 0000000..251a908 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/i18n/dlib/ru.lua @@ -0,0 +1,121 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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. + + +gui.misc.apply = 'Применить' +gui.misc.cancel = 'Отмена' +gui.misc.yes = 'Да' +gui.misc.no = 'Нет' + +gui.entry.invalidsymbol = 'Символ не разрешен.' +gui.entry.limit = 'Превышена максимальная длинна значения.' + +gui.dlib.hudcommons.reset = 'Сбросить' + +info.dlib.tformat.seconds = 'секунд' +info.dlib.tformat.minutes = 'минут' +info.dlib.tformat.hours = 'часов' +info.dlib.tformat.days = 'дней' +info.dlib.tformat.weeks = 'недель' +info.dlib.tformat.months = 'месяцев' +info.dlib.tformat.years = 'лет' +info.dlib.tformat.centuries = 'веков' + +info.dlib.tformat.long = 'Никогда™' +info.dlib.tformat.now = 'Прямо сейчас' +info.dlib.tformat.past = 'В прошлом' + +local function define(from, to, target, form1, form2, form3) + for i = from, to do + local div = i % 10 + + if i == 0 or i > 9 and i < 19 or div > 4 or div == 0 then + target[tostring(i)] = i .. ' ' .. form1 + elseif div == 1 then + target[tostring(i)] = i .. ' ' .. form2 + elseif div == 2 or div == 3 or div == 4 then + target[tostring(i)] = i .. ' ' .. form3 + end + end +end + +define(1, 60, info.dlib.tformat.countable.second, 'секунд', 'секунда', 'секунды') +define(1, 60, info.dlib.tformat.countable.minute, 'минут', 'минута', 'минуты') +define(1, 24, info.dlib.tformat.countable.hour, 'часов', 'час', 'часа') +define(1, 7, info.dlib.tformat.countable.day, 'день', 'дня', 'дней') +define(1, 4, info.dlib.tformat.countable.week, 'неделя', 'недели', 'недель') +define(1, 12, info.dlib.tformat.countable.month, 'месяц', 'месяца', 'месяцев') +define(1, 100, info.dlib.tformat.countable.year, 'год', 'года', 'лет') +define(1, 100, info.dlib.tformat.countable.century, 'век', 'века', 'веков') + +define(1, 60, info.dlib.tformat.countable_ago.second, 'секунд', 'секунду', 'секунды') +define(1, 60, info.dlib.tformat.countable_ago.minute, 'минут', 'минуту', 'минуты') +define(1, 24, info.dlib.tformat.countable_ago.hour, 'часов', 'час', 'часа') +define(1, 7, info.dlib.tformat.countable_ago.day, 'дней', 'день', 'дня') +define(1, 4, info.dlib.tformat.countable_ago.week, 'недель', 'неделю', 'недели') +define(1, 12, info.dlib.tformat.countable_ago.month, 'месяцев', 'месяц', 'месяца') +define(1, 100, info.dlib.tformat.countable_ago.year, 'лет', 'год', 'года') +define(1, 100, info.dlib.tformat.countable_ago.century, 'веков', 'век', 'века') + +info.dlib.tformat.ago = '%s тому назад' +info.dlib.tformat.ago_inv = 'Через %s' + +gui.dlib.friends.title = 'DLib друзья' +gui.dlib.friends.open = 'Отрыть меню друзей' + +gui.dlib.friends.edit.add_title = 'Добавление %s <%s> как друга' +gui.dlib.friends.edit.edit_title = 'Редактирование настроек друга %s <%s>' +gui.dlib.friends.edit.going = 'Вы будете другом с %s в...' +gui.dlib.friends.edit.youare = 'Вы являетесь другом с %s в...' +gui.dlib.friends.edit.remove = 'Удалить друга' + +gui.dlib.friends.invalid.title = 'Неверный SteamID' +gui.dlib.friends.invalid.ok = 'Окай :(' +gui.dlib.friends.invalid.desc = '%q не выглядит как SteamID!' + +gui.dlib.friends.settings.steam = 'Считать Steam друзей как DLib друзей' +gui.dlib.friends.settings.your = 'Ваши друзья ->' +gui.dlib.friends.settings.server = 'Игроки на сервере ->' + +gui.dlib.friends.settings.foreign = '[Внешний] ' + +gui.dlib.donate.top = 'DLib: Сделаете пожертвование?' +gui.dlib.donate.button.yes = 'Так и сделать (Яндекс.Деньги)!' +gui.dlib.donate.button.paypal = 'Так и сделать, только на PayPal!' +gui.dlib.donate.button.no = 'Спросить меня позже' +gui.dlib.donate.button.never = 'Больше никогда не спрашивать' +gui.dlib.donate.button.learnabout = 'Прочитать про "Donationware"...' +gui.dlib.donate.button.learnabout_url = 'https://ru.wikipedia.org/wiki/Donationware' + +message.dlib.hudcommons.edit.notice = 'Нажмите ESC для выхода из режима редактирования' + +gui.dlib.donate.text = [[Привет! Как я вижу, Вы были долго не за клавиатурой, чтож... Я хотел бы Вас попросить: +Сделайте пожалуйста пожертвование! DLib как и аддоны котороые базируются на нем являются Donationware +Тоесть, данное ПО существует только из-за энтузиазма и (возможно) регулярных пожертвований пользователей +для поддержания автора данного ПО! Я понимаю, что времена сейчас тугие, и не прошу вас "Пожертвуй сейчас!" +Но хотел что бы Вы хотя бы прочитали данное обращение. Как вы знаете - что к примеру 50₽ это мало, +примерно столько же стоит проезд в автобусе в Москве, но давайте вдумаемся в статистику: если сейчас +абсолютно все, кто использует DLib и другие аддоны пожертвуют 50₽ каждый, то этого будет достаточно +что бы покрыть все кредиты моих родителей, а так же помочь моей Матери, которая работает с 5 утра до 22 вечера +ради того, что бы придти потом домой поспать и не высыпаться на следующий рабочий день. +Если вы сделаете пожертвование, это окажет помощь следующим аддонам, которые используются: +DLib%s]] + +gui.dlib.donate.more = ' и еще %i аддонов!..' diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/loading_stages.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/loading_stages.lua new file mode 100644 index 0000000..5a81e6b --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/loading_stages.lua @@ -0,0 +1,64 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 hook = hook +local DLib = DLib +local CurTime = CurTime + +local init_post_entity = CurTime() > 60 +local initialize = CurTime() > 60 + +--[[ + @doc + @fname AreEntitiesAvailable + + @returns + boolean +]] +function _G.AreEntitiesAvailable() + return init_post_entity +end + +function _G.AreEntitiesAvaliable() + return init_post_entity +end + +--[[ + @doc + @fname IsGamemodeAvaliable + + @returns + boolean +]] +function _G.IsGamemodeAvaliable() + return initialize +end + +if not init_post_entity then + hook.Add('InitPostEntity', 'DLib.LoadingStages', function() + init_post_entity = true + end) +end + +if not initialize then + hook.Add('Initialize', 'DLib.LoadingStages', function() + initialize = true + end) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/lobject.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/lobject.lua new file mode 100644 index 0000000..ee47a18 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/lobject.lua @@ -0,0 +1,127 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +local FindMetaTable = FindMetaTable +local debug = debug +local type = type +local error = error +local setmetatable = setmetatable +local rawget = rawget + +DLib.METADATA = DLib.METADATA or {} + +--[[ + @doc + @fname DLib.CreateLuaObject + @args string objectName, boolean registerGlobalMeta + + @returns + table: meta +]] +function DLib.CreateLuaObject(objectName, registerMetadata) + local meta + + if registerMetadata then + meta = FindMetaTable(objectName) or {} + debug.getregistry()[objectName] = meta + else + meta = DLib.METADATA[objectName] or {} + DLib.METADATA[objectName] = meta + end + + meta.__getters = meta.__getters or {} + + if not meta.__index then + function meta:__index(key) + local getter = meta.__getters[key] + + if getter then + return getter(self, key) + end + + local value = rawget(self, key) + + if value ~= nil then + return value + end + + return meta[key] + end + end + + if not meta.GetClass then + local lower = objectName:lower() + + function meta:GetClass() + return lower + end + end + + if not meta.Create then + function meta.Create(self, ...) + if type(self) == 'table' then + if not self.Copy then + error(objectName .. ':Copy() - method is not implemented') + end + + return self:Copy(...) + end + + local newObject = setmetatable({}, meta) + + if meta.Initialize then + meta.Initialize(newObject, self, ...) + elseif meta.__construct then + meta.__construct(newObject, self, ...) + end + + return newObject + end + end + + return meta +end + +--[[ + @doc + @fname DLib.FindMetaTable + @args string classIn + + @returns + table: meta or nil +]] +function DLib.FindMetaTable(classIn) + return DLib.METADATA[classIn] or FindMetaTable(classIn) or nil +end + +--[[ + @doc + @fname DLib.ConsturctClass + @args string classIn, vararg constructorArgs + + @returns + table: object +]] +function DLib.ConsturctClass(classIn, ...) + local classGet = DLib.FindMetaTable(classIn) + if not classGet then return false end + return classGet.Create(...) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/luabridge.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/luabridge.lua new file mode 100644 index 0000000..41276e3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/luabridge.lua @@ -0,0 +1,438 @@ + +-- +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +if CLIENT then + local pixelvis_handle_t = FindMetaTable('pixelvis_handle_t') + local util = util + + --[[ + @doc + @fname pixelvis_handle_t:Visible + @alias pixelvis_handle_t:IsVisible + @alias pixelvis_handle_t:PixelVisible + @args Vector pos, number radius + + @client + + @desc + !g:util.PixelVisible + @enddesc + + @returns + number: visibility + ]] + function pixelvis_handle_t:Visible(pos, rad) + return util.PixelVisible(pos, rad, self) + end + + function pixelvis_handle_t:IsVisible(pos, rad) + return util.PixelVisible(pos, rad, self) + end + + function pixelvis_handle_t:PixelVisible(pos, rad) + return util.PixelVisible(pos, rad, self) + end + + local player = player + local IsValid = FindMetaTable('Entity').IsValid + local GetTable = FindMetaTable('Entity').GetTable + local GetVehicle = FindMetaTable('Player').GetVehicle + local vehMeta = FindMetaTable('Vehicle') + local NULL = NULL + local ipairs = ipairs + + local LocalPlayer = LocalPlayer + local GetWeapons = FindMetaTable('Player').GetWeapons + + local function updateWeaponFix() + local ply = LocalPlayer() + if not IsValid(ply) then return end + local weapons = GetWeapons(ply) + if not weapons then return end + + for k, wep in ipairs(weapons) do + local tab = GetTable(wep) + + if not tab.DrawWeaponSelection_DLib and tab.DrawWeaponSelection then + tab.DrawWeaponSelection_DLib = tab.DrawWeaponSelection + + tab.DrawWeaponSelection = function(self, x, y, w, h, a) + local can = hook.Run('DrawWeaponSelection', self, x, y, w, h, a) + if can == false then return end + + hook.Run('PreDrawWeaponSelection', self, x, y, width, height, alpha) + local A, B, C, D, E, F = tab.DrawWeaponSelection_DLib(self, x, y, w, h, a) + hook.Run('PostDrawWeaponSelection', self, x, y, width, height, alpha) + return A, B, C, D, E, F + end + end + end + end + + timer.Create('DLib.DrawWeaponSelection', 10, 0, updateWeaponFix) + updateWeaponFix() + + --[[ + @doc + @fname vgui.Create + @replaces + @args string tableName, Panel parent, vararg any + + @desc + Patched !g:vgui.Create which + throws an (no call aborting) error with stack trace when attempting to create non existant panel + and with hooks `VGUIPanelConstructed`, `VGUIPanelInitialized` and `VGUIPanelCreated` being called inside it + if other mod already overrides this function, override is aborted and i18n will be rendered useless for panels + @enddesc + + @returns + Panel: the created panel or nil if panel doesn't exist (with an error sent to error handler) + ]] + + --[[ + @doc + @hook VGUIPanelConstructed + @args Panel self, Panel parent, vararg any + + @desc + Called **before** `Panel:Init()` called + @enddesc + ]] + + --[[ + @doc + @hook VGUIPanelInitialized + @args Panel self, Panel parent, vararg any + + @desc + Called **before** `Panel:Prepare()` called + @enddesc + ]] + + --[[ + @doc + @hook VGUIPanelCreated + @args Panel self, Panel parent, vararg any + + @desc + Called **after** everything. + @enddesc + ]] + + + vgui.DLib_Create = vgui.DLib_Create or vgui.Create + + local patched = DLib._PanelDefinitions ~= nil + local vgui = vgui + + local function patch() + if not vgui.CreateX then + return + end + + if not patched then + if not vgui.GetControlTable then return end + + for i = 1, 10 do + local name, value = debug.getupvalue(vgui.GetControlTable, 1) + + if name == 'PanelFactory' then + PanelDefinitions = value + break + end + end + + if not PanelDefinitions then + return + end + + patched = true + vgui.CreateNative = vgui.CreateX + DLib._PanelDefinitions = PanelDefinitions + vgui.PanelDefinitions = PanelDefinitions + end + + local PanelDefinitions = DLib._PanelDefinitions + + local CreateNative = vgui.CreateNative + local error = error + local table = table + + local function Create(from, class, parent, name, level, ...) + if class == '' then + error(debug.traceback('Tried to create panel with empty classname')) + return + end + + local meta = PanelDefinitions[class] + + if not meta then + local panel = CreateNative(class, parent, name, ...) + + if not panel then + if level == 1 then + error(string.format('(Native) Panel %q does not exist.', class), level + 4) + else + error(string.format('%q tried to derive from (native) panel %q which does not exist.', from, class), level + 4) + end + end + + return panel, true + end + + if not meta.Base then + error(string.format('Meta table of %q does not contain `Base` panel classname', class)) + end + + local panel = Create(class, meta.Base, parent, name or class, level + 1, ...) + + if not panel then + error(string.format('%q cannot derive from %q', class, meta.Base), level + 4) + end + + table.Merge(panel:GetTable(), meta) + panel.BaseClass = PanelDefinitions[meta.Base] + panel.ClassName = class + + if level == 1 then + hook.Run('VGUIPanelConstructed', panel, ...) + end + + if panel.Init then + panel:Init(...) + end + + if level == 1 then + hook.Run('VGUIPanelInitialized', panel, ...) + end + + panel:Prepare() + + if level == 1 then + hook.Run('VGUIPanelCreated', panel, ...) + end + + return panel + end + + function vgui.Create(class, parent, name, ...) + if class == '' then + DLib.MessageError(debug.traceback('BACKWARDS COMPATIVILITY WITH GMOD ENGINE: Tried to create panel with empty classname')) + return + end + + local panel, isNative + local packed, size = {...}, select('#', ...) + + local status = ProtectedCall(function() + panel, isNative = Create(nil, class, parent, name, 1, unpack(packed, 1, size)) + end) + + if not status then + -- error(string.format('Cannot create panel %q! Look for errors above', class), 2) + local rebuild = {} + + for i, line in ipairs(debug.traceback(string.format('Cannot create panel %q! Look for errors above', class)):split('\n')) do + table.insert(rebuild, line) + table.insert(rebuild, '\n') + end + + DLib.MessageError(unpack(rebuild)) + return + end + + if isNative then + hook.Run('VGUIPanelConstructed', panel, ...) + hook.Run('VGUIPanelInitialized', panel, ...) + hook.Run('VGUIPanelCreated', panel, ...) + end + + return panel + end + + function vgui.CreateFromTable(meta, parent, name, ...) + if not meta or not istable(meta) then + error('Invalid meta (PANEL table) provided (typeof ' .. type(meta) .. ')') + end + + if not meta.Base then + error(string.format('Meta table of %p (%s) does not contain `Base` panel classname', meta, meta)) + end + + local panel, isNative + local packed, size = {...}, select('#', ...) + + local status = ProtectedCall(function() + panel, isNative = Create(string.format('%p (%s)', meta, meta), meta.Base, parent, name, 2, unpack(packed, 1, size)) + end) + + if not status then + error(string.format('Cannot create panel %p (%s)! Look for errors above', meta, meta), 2) + end + + table.Merge(panel:GetTable(), meta) + panel.BaseClass = PanelDefinitions[meta.Base] + + hook.Run('VGUIPanelConstructed', panel, ...) + + if panel.Init then + panel:Init(...) + end + + hook.Run('VGUIPanelInitialized', panel, ...) + panel:Prepare() + hook.Run('VGUIPanelCreated', panel, ...) + + return panel + end + end + + --if not DLib._PanelDefinitions then + patch() + + if not patched then + DLib.Message('Unable to fully replace vgui.Create, falling back to old one patch of vgui.Create... Localization might break!') + local vgui = vgui + local ignore = 0 + + function vgui.Create(...) + if ignore == FrameNumberL() then return vgui.DLib_Create(...) end + + ignore = FrameNumberL() + local pnl = vgui.DLib_Create(...) + ignore = 0 + + if not pnl then return end + hook.Run('VGUIPanelConstructed', pnl, ...) + hook.Run('VGUIPanelInitialized', pnl, ...) + hook.Run('VGUIPanelCreated', pnl, ...) + return pnl + end + end + --end +end + +local CSoundPatch = FindMetaTable('CSoundPatch') + +--[[ + @doc + @fname CSoundPatch:IsValid + + @returns + boolean: IsPlaying() +]] +function CSoundPatch:IsValid() + return self:IsPlaying() +end + +--[[ + @doc + @fname CSoundPatch:Remove +]] +function CSoundPatch:Remove() + return self:Stop() +end + +local meta = getmetatable(function() end) or {} + +function meta:tonumber(base) + return tonumber(self, base) +end + +function meta:tostring() + return tostring(self) +end + +debug.setmetatable(function() end, meta) + +--[[ + @doc + @fname string.tonumber + @args number base = 10 + + @returns + number +]] + +--[[ + @doc + @fname string:tonumber + @args number base = 10 + + @returns + number +]] + +--[[ + @doc + @fname math.tonumber + @args number base = 10 + + @returns + number +]] + +--[[ + @doc + @fname number:tonumber + @args number base = 10 + + @returns + number +]] + +--[[ + @doc + @fname string.tostring + + @returns + string +]] + +--[[ + @doc + @fname string:tostring + + @returns + string +]] + +--[[ + @doc + @fname math.tostring + + @returns + string +]] + +--[[ + @doc + @fname number:tostring + + @returns + string +]] +string.tonumber = meta.tonumber +string.tostring = meta.tostring + +math.tonumber = meta.tonumber +math.tostring = meta.tostring diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/luaify.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/luaify.lua new file mode 100644 index 0000000..cd6992a --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/luaify.lua @@ -0,0 +1,96 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +-- make some functions be jit compilable + +if SERVER then + _G.CurTimeL = CurTime + _G.RealTimeL = RealTime + return +end + +--[[ + @docpreprocess + + const vars = [ + 'FrameNumber', + 'RealTime', + 'CurTime', + 'ScrW', + 'ScrH', + ] + + const output = [] + + for (const str of vars) { + const output2 = [] + output2.push(`@fname ${str}L`) + output2.push(`@desc`) + output2.push(`alias of !g:${str} but JIT compilable`) + output2.push(`@enddesc`) + output2.push(`@returns`) + output2.push(`number`) + output.push(output2) + } + + return output +]] + +local DLib = DLib +_G.FrameNumberC = FrameNumberC or FrameNumber +_G.RealTimeC = RealTimeC or RealTime +_G.CurTimeC = CurTimeC or CurTime + +_G.ScrWC = ScrWC or ScrW +_G.ScrHC = ScrHC or ScrH +local ScrWC = ScrWC +local ScrHC = ScrHC +local render = render +local type = type +local assert = assert + +DLib.luaify_rTime = RealTimeC() +DLib.luaify_cTime = CurTimeC() +DLib.luaify_frameNum = FrameNumberC() + +DLib.luaify_scrw = ScrWC() +DLib.luaify_scrh = ScrHC() +DLib.pstatus = false + +function _G.RealTimeL() + return DLib.luaify_rTime +end + +function _G.FrameNumberL() + return DLib.luaify_frameNum +end + +function _G.CurTimeL() + return DLib.luaify_cTime +end + +function _G.ScrWL() + return DLib.luaify_scrw +end + +function _G.ScrHL() + return DLib.luaify_scrh +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/luaify2.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/luaify2.lua new file mode 100644 index 0000000..18c951c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/luaify2.lua @@ -0,0 +1,67 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +-- make some functions be jit compilable + +if SERVER then return end + +local DLib = DLib +local FrameNumberC = FrameNumberC +local RealTimeC = RealTimeC +local CurTimeC = CurTimeC +local ScrWC = ScrWC +local ScrHC = ScrHC +local hook = hook + +local function update() + DLib.luaify_rTime = RealTimeC() + DLib.luaify_cTime = CurTimeC() + DLib.luaify_frameNum = FrameNumberC() + + DLib.luaify_scrw = ScrWC() + DLib.luaify_scrh = ScrHC() + + hook.Run('DLib.ScreenSettingsUpdate', DLib.luaify_scrw, DLib.luaify_scrh) +end + +hook.Add('PreRender', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('Think', 'DLib.UpdateFrameOptions', update, -9) + +local function update() + DLib.luaify_rTime = RealTimeC() + DLib.luaify_cTime = CurTimeC() + DLib.luaify_frameNum = FrameNumberC() +end + +hook.Add('Tick', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('PlayerSwitchWeapon', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('StartCommand', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('SetupMove', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('Move', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('VehicleMove', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('PlayerTick', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('ShouldCollide', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('PlayerButtonDown', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('PlayerButtonUp', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('PhysgunPickup', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('KeyPress', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('KeyRelease', 'DLib.UpdateFrameOptions', update, -9) +hook.Add('FinishMove', 'DLib.UpdateFrameOptions', update, -9) diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/physgunhandler.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/physgunhandler.lua new file mode 100644 index 0000000..1cc52a1 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/physgunhandler.lua @@ -0,0 +1,144 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +if SERVER then + net.pool('DLib.physgun.player') + net.pool('DLib.physgun.playerAngles') +end + +local ply, ent, holder, holderStatus + +local function PhysgunPickup(Uply, Uent) + if Uent:IsPlayer() and Uent:InVehicle() then + return false + end + + ply, ent = Uply, Uent +end + +local function PhysgunDrop(Uply, Uent) + local target = Uply.__dlibPhysgunHandler + + if SERVER and IsValid(target) then + net.Start('DLib.physgun.player') + net.WriteBool(false) + net.Send(target) + end + + Uply.__dlibPhysgunHandler = nil + Uply.__dlibPhysgunHolder = nil + Uply.__dlibUpcomingEyeAngles = nil + + Uent.__dlibPhysgunHandler = nil + Uent.__dlibPhysgunHolder = nil + Uent.__dlibUpcomingEyeAngles = nil +end + +-- Lets handle this mess by ourself +-- as admin addons doesnt even care +local function PlayerNoClip(ply) + if CLIENT and holderStatus and IsValid(holder) then + return false + elseif SERVER and IsValid(ply.__dlibPhysgunHolder) then + return false + end +end + +local function PhysgunPickupPost(status) + if not IsValid(ply) or not IsValid(ent) then return status end + if not ent:IsPlayer() then return status end + + if status then + ply.__dlibPhysgunHandler = ent + ent.__dlibPhysgunHolder = ply + + if SERVER then + net.Start('DLib.physgun.player') + net.WriteBool(true) + net.WritePlayer(ply) + net.Send(ent) + end + end + + return status +end + +local function StartCommand(ply, cmd) + if CLIENT and holderStatus and IsValid(holder) then + cmd:SetMouseX(0) + cmd:SetMouseY(0) + end + + if SERVER and IsValid(ply.__dlibPhysgunHolder) then + cmd:SetMouseX(0) + cmd:SetMouseY(0) + local ang = ply.__dlibUpcomingEyeAngles or ply:EyeAngles() + cmd:SetViewAngles(ang) + + net.Start('DLib.physgun.playerAngles', true) + net.WriteAngle(ang) + net.Send(ply) + + return + end + + local target = ply.__dlibPhysgunHandler + if not IsValid(target) then return end + if not cmd:KeyDown(IN_USE) then return end + local x, y = cmd:GetMouseX() / 4, cmd:GetMouseY() / 7 + + if x ~= 0 or y ~= 0 then + if SERVER then + cmd:SetMouseX(0) + cmd:SetMouseY(0) + end + + local ang = target:EyeAngles() + ang.p = ang.p + y + ang.y = ang.y + x + ang:Normalize() + -- target:SetEyeAngles(ang) + target.__dlibUpcomingEyeAngles = ang + end +end + +if CLIENT then + net.receive('DLib.physgun.player', function() + holderStatus = net.ReadBool() + + if holderStatus then + holder = net.ReadPlayer() + else + holder = NULL + end + end) + + net.receive('DLib.physgun.playerAngles', function() + LocalPlayer():SetEyeAngles(net.ReadAngle()) + end) +end + +hook.Add('StartCommand', 'DLib.PhysgunModifier', StartCommand, -10) +hook.Add('PhysgunPickup', 'DLib.PhysgunModifier', PhysgunPickup, -10) +hook.Add('PhysgunDrop', 'DLib.PhysgunModifier', PhysgunDrop, -10) +hook.Add('PlayerNoClip', 'DLib.PhysgunModifier', PlayerNoClip, -10) +hook.Add('CanPlayerEnterVehicle', 'DLib.PhysgunModifier', PlayerNoClip, -10) +hook.AddPostModifier('PhysgunPickup', 'DLib.PhysgunModifier', PhysgunPickupPost) diff --git a/garrysmod/addons/util-dlib/lua/dlib/luabridge/savetable.lua b/garrysmod/addons/util-dlib/lua/dlib/luabridge/savetable.lua new file mode 100644 index 0000000..e99f259 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/luabridge/savetable.lua @@ -0,0 +1,283 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +local net = net +local player = player +local util = util +local bit = bit + +if SERVER then + net.pool('dlib_sync_savetable_values') +end + +DLib.__TrackedSaveTableValues = DLib.__TrackedSaveTableValues or {} +DLib.__TrackedSaveTableTypes = DLib.__TrackedSaveTableTypes or {} +local track = DLib.__TrackedSaveTableValues +local trackr = {} +local tracktypes = DLib.__TrackedSaveTableTypes + +for i, ply in ipairs(player.GetAll()) do + ply.__dlib_st_cache = nil +end + +local typesFuncs = { + string = { + net.WriteString, + net.ReadString + }, + + int = { + net.WriteInt32, + net.ReadInt32 + }, + + float = { + net.WriteFloat, + net.ReadFloat + }, + + smallint = { + net.WriteInt16, + net.ReadInt16 + }, + + byte = { + net.WriteInt8, + net.ReadInt8 + }, + + uint = { + net.WriteUInt32, + net.ReadUInt32 + }, + + smalluint = { + net.WriteUInt16, + net.ReadUInt16 + }, + + ubyte = { + net.WriteUInt8, + net.ReadUInt8 + }, + + angle = { + net.WriteAngle, + net.ReadAngle + }, + + vector = { + net.WriteVector, + net.ReadVector + }, + + angle_double = { + net.WriteAngleDouble, + net.ReadAngleDouble + }, + + vector_double = { + net.WriteVectorDouble, + net.ReadVectorDouble + }, + + bool = { + net.WriteBool, + net.ReadBool + }, + + entity = { + net.WriteEntity, + net.ReadEntity + }, + + any = { + net.WriteType, + net.ReadType + }, +} + +--[[ + @doc + @fname DLib.TrackSaveTableVar + @args string varname, string vartype + @deprecated + + @desc + Asks DLib for networking this savetable variable to clients. + This function very bandwidth efficently does so, however, it increase serverside lag because + it use !g:GetInternalVariable which is **damn slow** + `vartype` can be anything from next: + `string` + `int` + `uint` + `smallint` + `usmallint` + `byte` + `ubyte` + `float` + `angle` + `vector` + `angle_double` + `vector_double` + `bool` + `entity` + `any` + This is basically a workaround for https://github.com/Facepunch/garrysmod-issues/issues/2552 + @enddesc + + @returns + boolean: whenever variable was registered before. if so, it just updates it's type +]] +function DLib.TrackSaveTableVar(varname, vartype) + if vartype == 'Entity' then vartype = 'entity' end + + if not typesFuncs[vartype] then + error('Unknown variable type provided') + end + + tracktypes[varname] = vartype + + if table.qhasValue(track, varname) then + return false + end + + table.insert(track, varname) + table.sort(track) + + for i, var in ipairs(track) do + if var == varname then + trackr[varname] = i + end + end + + return true +end + +for i, varname in ipairs(track) do + trackr[varname] = i +end + +local plyMeta = FindMetaTable('Player') + +--[[ + @doc + @fname Player:LookupServerInternalVariable + @args string varname, any default + @deprecated + + @returns + any: stored variable +]] +function plyMeta:LookupServerInternalVariable(varname, def) + if SERVER then + local val = self:GetInternalVariable(varname) + if val == nil then return def end + return val + end + + if not self.__dlib_st_cache then return def end + local val = self.__dlib_st_cache[varname] + if val == nil then return def end + return val +end + +if SERVER then + local function PlayerPostThink(ply) + local data = ply.__dlib_st_cache + + if not data then + data = {} + ply.__dlib_st_cache = data + end + + local send + + for i, var in ipairs(track) do + local getvar = ply:GetInternalVariable(var) + + if getvar ~= data[var] and getvar ~= nil then + send = send or {} + send[var] = getvar + data[var] = getvar + end + end + + if not send then return end + + net.Start('dlib_sync_savetable_values') + local flags = {} + local mflags = (#track - #track % 32) / 32 + + for slot = 0, mflags do + flags[slot] = 0 + end + + for var in pairs(send) do + local i = trackr[var] - 1 + local slot = (i - i % 32) / 32 + flags[slot] = flags[slot]:bor(bit.lshift(1, i % 32)) + end + + for slot = 0, mflags do + net.WriteInt32(flags[slot]) + end + + for i, var in ipairs(track) do + local val = send[var] + + if val ~= nil then + typesFuncs[tracktypes[var]][1](val) + end + end + + net.Send(ply) + end + + hook.Add('PlayerPostThink', 'DLib.SaveTableTrack', PlayerPostThink) + return +end + +local LocalPlayer = LocalPlayer + +net.receive('dlib_sync_savetable_values', function() + local ply = LocalPlayer() + local data = ply.__dlib_st_cache + + if not data then + data = {} + ply.__dlib_st_cache = data + end + + local flags = {} + + for slot = 0, (#track - #track % 32) / 32 do + flags[slot] = net.ReadInt32() + end + + for i, var in ipairs(track) do + i = i - 1 + + if flags[(i - i % 32) / 32]:band(bit.lshift(1, i % 32)) ~= 0 then + data[var] = typesFuncs[tracktypes[var]][2]() + end + end +end) diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/bitworker.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/bitworker.lua new file mode 100644 index 0000000..146ac82 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/bitworker.lua @@ -0,0 +1,608 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +DLib.bitworker = DLib.bitworker or {} +local table = table +local DLib = DLib +local math = math +local bitworker = DLib.bitworker +local type = type +local ipairs = ipairs + +local bit = bit +local lshift = bit.lshift +local rshift = bit.rshift +local band = bit.band +local bor = bit.bor +local bxor = bit.bxor +local bnot = bit.bnot +local pow = math.pow + +local function isValidNumber(numIn) + return type(numIn) == 'number' and numIn == numIn and numIn ~= math.huge and numIn ~= -math.huge +end + +local function table_insert(tabIn, val) + tabIn[#tabIn + 1] = val +end + +--[[ + @doc + @fname DLib.bitworker.IntegerToBinary + @args number value, number bitsAmount + + @desc + function is a bit slow (for intensive use) + this uses forwarding bit for negative values (the one used in networking (not in Sorse Engine networking wtf!)/etc/etc) + @enddesc + + @returns + table: bits +]] +function bitworker.IntegerToBinary(numberIn, bitsNum) + if not isValidNumber(numberIn) then + local vr = {} + + for i = 1, bitsNum do + vr[i] = 0 + end + + return vr + end + + local bits = {} + local sign = numberIn >= 0 and 0 or 1 + if sign == 1 then + numberIn = -numberIn + end + + bits[1] = sign + + for i = 2, bitsNum do + bits[i] = 0 + end + + for i = 2, bitsNum do + if numberIn == 0 then break end + local div = numberIn % 2 + numberIn = (numberIn - div) / 2 + bits[bitsNum - i + 2] = div + end + + return bits +end + +--[[ + @doc + @fname DLib.bitworker.IntegerToBinary2 + @args number value, number bitsAmount + + @desc + function is a bit slow (for intensive use) + this uses overflow for negative values (the one used by Sorse Engine) + @enddesc + + @returns + table: bits +]] +function bitworker.IntegerToBinary2(numberIn, bitsNum) + local max = math.pow(2, bitsNum) + + if numberIn < 0 then + numberIn = max + numberIn + elseif numberIn > max then + numberIn = numberIn - max + end + + local bits = {} + + for i = 1, bitsNum do + bits[i] = 0 + end + + for i = 1, bitsNum do + if numberIn == 0 then break end + local div = numberIn % 2 + numberIn = (numberIn - div) / 2 + bits[bitsNum - i + 1] = div + end + + return bits +end + +--[[ + @doc + @fname DLib.bitworker.BinaryToUInteger + @args table bits + + @desc + reversal of UIntegerToBinary + @enddesc + + @returns + number +]] +function bitworker.BinaryToUInteger(inputTable) + local amount = #inputTable + local output = 0 + + for i = 1, amount do + if inputTable[i] == 1 then + output = output + math.pow(2, amount - i) + end + end + + return output +end + +--[[ + @doc + @fname DLib.bitworker.BinaryToInteger + @args table bits + + @desc + reversal of IntegerToBinary + @enddesc + + @returns + number +]] +function bitworker.BinaryToInteger(inputTable) + local direction = inputTable[1] + local amount = #inputTable + local output = 0 + + for i = 2, amount do + if inputTable[i] == 1 then + output = output + math.pow(2, amount - i) + end + end + + if direction == 0 then + return output + else + return -output + end +end + +--[[ + @doc + @fname DLib.bitworker.BinaryToInteger2 + @args table bits + + @desc + reversal of IntegerToBinary2 + @enddesc + + @returns + number +]] +function bitworker.BinaryToInteger2(bits) + local bitsNum = #bits + local max = math.pow(2, bitsNum - 1) - 1 + local output = 0 + + for i = 1, bitsNum do + if bits[i] == 1 then + output = output + math.pow(2, bitsNum - i) + end + end + + if output > max then + output = output - math.pow(2, bitsNum) + end + + return output +end + +--[[ + @doc + @fname DLib.bitworker.UIntegerToBinary + @args number value, number bitsAmount + + @desc + negative values are handled by overflow + so please don't pass negative values ok? + @enddesc + + @returns + table: bits +]] +function bitworker.UIntegerToBinary(numberIn, bitsNum) + if not isValidNumber(numberIn) then + local vr = {} + + for i = 1, bitsNum do + vr[i] = 0 + end + + return vr + end + + if numberIn < 0 then + numberIn = math.pow(2, bitsNum) + numberIn + end + + local bits = {} + + for i = 1, bitsNum do + bits[i] = 0 + end + + for i = 1, bitsNum do + if numberIn == 0 then break end + local div = numberIn % 2 + numberIn = (numberIn - div) / 2 + bits[bitsNum - i + 1] = div + end + + return bits +end + +--[[ + @doc + @fname DLib.bitworker.NumberToMantiss + @args number value, number bitsAllowed + + @returns + table: bits + number: exponent +]] +function bitworker.NumberToMantiss(numberIn, bitsAllowed) + if not isValidNumber(numberIn) then + local bits = {} + + for i = 1, bitsAllowed do + bits[i] = 0 + end + + return bits + end + + local exp = 0 + numberIn = math.abs(numberIn) + local lastMult = numberIn % 1 + + if numberIn >= 2 then + -- try to normalize number to be less than 2 + -- shift to right + while numberIn >= 2 do + numberIn = numberIn / 2 + exp = exp + 1 + end + end + + -- if our number is less than one, shift to left + if exp == 0 and numberIn < 1 then + while numberIn < 1 do + numberIn = numberIn * 2 + exp = exp - 1 + end + end + + -- if number is not a zero, it is known amoung all computers that + -- first bit of mantissa is always 1 + -- so let's assume so + numberIn = numberIn - 1 + + local bits = {} + for i = 1, bitsAllowed do + numberIn = numberIn * 2 + + if numberIn >= 1 then + table_insert(bits, 1) + numberIn = numberIn - 1 + else + table_insert(bits, 0) + end + end + + return bits, exp +end + +--[[ + @doc + @fname DLib.bitworker.NumberToMantissFast + @args number value, number bitsAllowed + + @returns + number: fist part of mantiss (32-bit integer) + number: second part of mantiss (32-bit integer) + number: exponent +]] +function bitworker.NumberToMantissFast(numberIn, bitsAllowed) + if not isValidNumber(numberIn) then + return 0 + end + + local exp, numberOut1, numberOut2 = 0, 0, 0 + numberIn = math.abs(numberIn) + local lastMult = numberIn % 1 + + if numberIn >= 2 then + -- try to normalize number to be less than 2 + -- shift to right + while numberIn >= 2 do + numberIn = numberIn / 2 + exp = exp + 1 + end + end + + -- if our number is less than one, shift to left + if exp == 0 and numberIn < 1 then + while numberIn < 1 do + numberIn = numberIn * 2 + exp = exp - 1 + end + end + + -- if number is not a zero, it is known amoung all computers that + -- first bit of mantissa is always 1 + -- so let's assume so + numberIn = numberIn - 1 + local dest1, dest2 = math.min(32, bitsAllowed), math.min(32, bitsAllowed - 32) + + for i = 1, dest1 do + numberIn = numberIn * 2 + + if numberIn >= 1 then + numberOut1 = bor(numberOut1, lshift(1, dest1 - i)) + numberIn = numberIn - 1 + end + end + + for i = 1, dest2 do + numberIn = numberIn * 2 + + if numberIn >= 1 then + numberOut2 = bor(numberOut2, lshift(1, dest2 - i)) + numberIn = numberIn - 1 + end + end + + return numberOut1, numberOut2, exp +end + +--[[ + @doc + @fname DLib.bitworker.MantissToNumber + @args table bits, number exponent + + @returns + number +]] +function bitworker.MantissToNumber(bitsIn, exp) + exp = exp or 0 + local num = 0 + + for i = 1, #bitsIn do + if bitsIn[i] == 1 then + num = num + pow(2, -i) + end + end + + return pow(2, exp) * (1 + num) +end + +--[[ + @doc + @fname DLib.bitworker.MantissToNumberFast + @args number mantiss1, number mantiss2, number exponent, number bitsIn + + @returns + number +]] +function bitworker.MantissToNumberFast(numberIn1, numberIn2, exp, bitsIn) + exp = exp or 0 + numberIn1 = numberIn1 or 0 + numberIn2 = numberIn2 or 0 + local num = 0 + local dest1 = math.min(32, bitsIn) - 1 + local dest2 = math.min(32, bitsIn - 32) - 1 + + numberIn1 = lshift(numberIn1, math.max(32 - bitsIn, 0)) + numberIn2 = lshift(numberIn2, 63 - bitsIn) + + for i = 0, dest1 do + if band(rshift(numberIn1, 31 - i), 1) == 1 then + num = num + pow(2, -(i + 1)) + end + end + + for i = 0, dest2 do + if band(rshift(numberIn2, 31 - i), 1) == 1 then + num = num + pow(2, -(i + 32)) + end + end + + return pow(2, exp) * (1 + num) +end + +--[[ + @doc + @fname DLib.bitworker.FloatToBinaryIEEE + @args number value, number bitsForExponent, number bitsForMantissa + + @returns + table: bits +]] +-- final range is bitsExponent + bitsMantissa + 2 +-- where 2 is two bits which one forwards number sign and one forward exponent sign +function bitworker.FloatToBinaryIEEE(numberIn, bitsExponent, bitsMantissa) + if not isValidNumber(numberIn) or numberIn == 0 then + local bits = {} + + for i = 0, bitsExponent + bitsMantissa do + table_insert(bits, 0) + end + + return bits + end + + local bits = {numberIn >= 0 and 0 or 1} + local mantissa, exp = bitworker.NumberToMantiss(numberIn, bitsMantissa) + local expBits = bitworker.UIntegerToBinary(exp + math.pow(2, bitsExponent - 1) - 1, bitsExponent) + + table.append(bits, expBits) + table.append(bits, mantissa) + + return bits +end + +--[[ + @doc + @fname DLib.bitworker.FastFloatToBinaryIEEE + @args number value + + @returns + number: integer representation of float +]] +function bitworker.FastFloatToBinaryIEEE(numberIn) + if not isValidNumber(numberIn) or numberIn == 0 then + return 0 + end + + local mantissa1, mantissa2, exp = bitworker.NumberToMantissFast(numberIn, 23) + return bor(lshift(numberIn >= 0 and 0 or 1, 31), lshift(exp + 127, 23), mantissa1) +end + +--[[ + @doc + @fname DLib.bitworker.FastDoubleToBinaryIEEE + @args number value + + @returns + number: integer representation of double (first part) + number: integer representation of double (second part) +]] +function bitworker.FastDoubleToBinaryIEEE(numberIn) + if not isValidNumber(numberIn) or numberIn == 0 then + return 0, 0 + end + + local mantissa1, mantissa2, exp = bitworker.NumberToMantissFast(numberIn, 52) + return bor(lshift(numberIn >= 0 and 0 or 1, 31), lshift(exp + 1023, 20), rshift(mantissa1, 12)), bor(lshift(band(mantissa1, 4095), 20), mantissa2) +end + +--[[ + @doc + @fname DLib.bitworker.BinaryToFloatIEEE + @args table bits, number bitsExponent, number bitsMantissa + + @returns + number +]] +function bitworker.BinaryToFloatIEEE(bitsIn, bitsExponent, bitsMantissa) + local valid = false + + for i = 1, #bitsIn do + if bitsIn[i] ~= 0 then + valid = true + break + end + end + + if not valid then return 0 end + + local forward = bitsIn[1] + local exponent = table.gcopyRange(bitsIn, 2, 2 + bitsExponent - 1) + local exp = bitworker.BinaryToUInteger(exponent) + local mantissa = table.gcopyRange(bitsIn, 2 + bitsExponent) + + local value = bitworker.MantissToNumber(mantissa, exp - math.pow(2, bitsExponent - 1) + 1) + + if forward == 0 then + return value + end + + return -value +end + +--[[ + @doc + @fname DLib.bitworker.FastBinaryToFloatIEEE + @args number integerRepresentation + + @returns + number: float +]] +function bitworker.FastBinaryToFloatIEEE(numberIn) + if numberIn == 0 then return 0 end + local point = rshift(numberIn, 31) + local exp = band(rshift(numberIn, 23), 0xFF) - 127 + local mantissa = band(numberIn, 0x7FFFFF) + local value = bitworker.MantissToNumberFast(mantissa, 0, exp, 23) + + if point == 0 then return value end + return -value +end + +--[[ + @doc + @fname DLib.bitworker.FastBinaryToDoubleIEEE + @args number integerRepresentation1, number integerRepresentation2 + + @returns + number: double +]] +function bitworker.FastBinaryToDoubleIEEE(numberIn1, numberIn2) + if numberIn == 0 then return 0 end + local point = rshift(numberIn1, 31) + local exp = band(rshift(numberIn1, 20), 0x7FF) - 1023 + local mantissa1 = lshift(band(numberIn1, 0xFFFFF), 12) + local mantissa2 = band(numberIn2, 0xFFFFF) + mantissa1 = bor(mantissa1, rshift(numberIn2, 20)) + local value = bitworker.MantissToNumberFast(mantissa1, mantissa2, exp, 52) + + if point == 0 then return value end + return -value +end + +--[[ + @doc + @fname DLib.bitworker.BitsToBytes + @args table bits + + @desc + a table consist of `{1, 1, 1, 0, 0, 0, 1, 0}` turns into `{226}` for example + `{1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1}` turns into `{226, 225}` + @descdesc + + @returns + table: bytes +]] +function bitworker.BitsToBytes(bitsIn) + assert(#bitsIn % 8 == 0, 'Not full bytes') + local output = {} + + for i = 1, #bitsIn / 8 do + output[i] = + bitsIn[(i - 1) * 8 + 8] + + bitsIn[(i - 1) * 8 + 7] * 2 + + bitsIn[(i - 1) * 8 + 6] * 4 + + bitsIn[(i - 1) * 8 + 5] * 8 + + bitsIn[(i - 1) * 8 + 4] * 16 + + bitsIn[(i - 1) * 8 + 3] * 32 + + bitsIn[(i - 1) * 8 + 2] * 64 + + bitsIn[(i - 1) * 8 + 1] * 128 + end + + return output +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/bytesbuffer.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/bytesbuffer.lua new file mode 100644 index 0000000..84cf3ca --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/bytesbuffer.lua @@ -0,0 +1,1134 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +jit.on() +local DLib = DLib +local meta = FindMetaTable('LBytesBuffer') or {} +debug.getregistry().LBytesBuffer = meta +DLib.BytesBufferMeta = meta + +local bitworker = DLib.bitworker + +local type = type +local math = math +local assert = assert +local table = table +local rawget = rawget +local rawset = rawset +local setmetatable = setmetatable +local string = string +local bit = bit +local lshift = bit.lshift +local rshift = bit.rshift +local band = bit.band +local bor = bit.bor +local bxor = bit.bxor + +meta.__index = meta + +--[[ + @doc + @fname DLib.BytesBuffer + @args string binary = '' + + @desc + entry point of BytesBuffer creation + you can pass a string to it to construct bytes array from it + **BUFFER ONLY WORK WITH BIG ENDIAN BYTES** + @descdesc + + @returns + BytesBuffer: newly created object +]] +DLib.BytesBuffer = setmetatable({proto = meta, meta = meta}, {__call = function(self, stringIn) + local obj = setmetatable({}, meta) + obj.bytes = {} + obj.pointer = 0 + obj.length = 0 + + if type(stringIn) == 'string' then + obj.bytes = DLib.string.bbyte(stringIn, 1, #stringIn) + obj.length = #obj.bytes + end + + -- obj:Seek(0) + + return obj +end}) + +--[[ + @doc + @fname BytesBuffer:Seek + @args number position + + @returns + BytesBuffer: self +]] +-- Operations +function meta:Seek(moveTo) + if moveTo < 0 or moveTo > self.length then + error('Seek - invalid position (' .. moveTo .. '; ' .. self.length .. ')', 2) + end + + self.pointer = moveTo + return self +end + +--[[ + @doc + @fname BytesBuffer:Tell + @alias BytesBuffer:Ask + + @returns + number: pointer position +]] +function meta:Tell() + return self.pointer +end + +meta.Ask = meta.Tell + +--[[ + @doc + @fname BytesBuffer:Move + @alias BytesBuffer:Walk + @args number delta + + @returns + BytesBuffer: self +]] +function meta:Move(moveBy) + return self:Seek(self.pointer + moveBy) +end + +meta.Walk = meta.Move + +--[[ + @doc + @fname BytesBuffer:Reset + + @desc + alias of BytesBuffer:Seek(0) + @enddesc + + @returns + BytesBuffer: self +]] +function meta:Reset() + return self:Seek(0) +end + +--[[ + @doc + @fname BytesBuffer:Release + + @desc + sets pointer to 0 and removes internal bytes array + @enddesc + + @returns + BytesBuffer: self +]] +function meta:Release() + self.pointer = 0 + self.bytes = {} + return self +end + +--[[ + @doc + @fname BytesBuffer:GetBytes + + @internal + + @returns + table: of integers (for optimization purpose). editing this array will affect the object! be careful +]] +function meta:GetBytes() + return self.bytes +end + +local function wrap(num, maximal) + if num >= 0 then + return num + end + + return maximal * 2 + num +end + +local function unwrap(num, maximal) + if num < maximal then + return num + end + + return num - maximal * 2 +end + +local function assertType(valueIn, desiredType, funcName) + if type(valueIn) == desiredType then return end + error(funcName .. ' - input is not a ' .. desiredType .. '! typeof ' .. type(valueIn), 3) +end + +local function assertRange(valueIn, min, max, funcName) + if valueIn >= min and valueIn <= max then return end + error(funcName .. ' - size overflow (' .. min .. ' -> ' .. max .. ' vs ' .. valueIn .. ')', 3) +end + + +--[[ + @doc + @fname BytesBuffer:EndOfStream + + @returns + boolean +]] +function meta:EndOfStream() + return self.pointer >= self.length +end + +--[[ + @doc + @fname BytesBuffer:WriteUByte + @args number value + + @returns + BytesBuffer: self +]] +function meta:WriteUByte(valueIn) + assertType(valueIn, 'number', 'WriteUByte') + assertRange(valueIn, 0, 0xFF, 'WriteUByte') + + valueIn = math.floor(valueIn) + + self.bytes[self.pointer + 1] = valueIn + self.pointer = self.pointer + 1 + self.length = #self.bytes + + return self +end + +--[[ + @doc + @fname BytesBuffer:WriteByte_2 + @args number value + + @desc + with value shift + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteByte + @args number value + + @desc + with negative number overflow + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteCHar + @args string char + + @returns + BytesBuffer: self +]] +-- Primitive read/write +-- wrap overflow +function meta:WriteByte_2(valueIn) + assertType(valueIn, 'number', 'WriteByte') + assertRange(valueIn, -0x80, 0x7F, 'WriteByte') + return self:WriteUByte(math.floor(valueIn) + 0x80) +end + +-- one's component +function meta:WriteByte(valueIn) + assertType(valueIn, 'number', 'WriteByte') + assertRange(valueIn, -0x80, 0x7F, 'WriteByte') + return self:WriteUByte(wrap(math.floor(valueIn), 0x80)) +end + +meta.WriteInt8 = meta.WriteByte +meta.WriteUInt8 = meta.WriteUByte + +function meta:WriteChar(char) + assertType(char, 'string', 'WriteChar') + assert(#char == 1, 'Input is not a single char!') + self:WriteUByte(string.byte(char)) + return self +end + +--[[ + @doc + @fname BytesBuffer:WriteShort_2 + @alias BytesBuffer:WriteInt16_2 + @args number value + + @desc + with value shift + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteShort + @alias BytesBuffer:WriteInt16 + @args number value + + @desc + with negative number overflow + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteUShort + @alias BytesBuffer:WriteUInt16 + @args number value + + @returns + BytesBuffer: self +]] +function meta:WriteInt16_2(valueIn) + assertType(valueIn, 'number', 'WriteInt16') + assertRange(valueIn, -0x8000, 0x7FFF, 'WriteInt16') + return self:WriteUInt16(math.floor(valueIn) + 0x8000) +end + +function meta:WriteInt16(valueIn) + assertType(valueIn, 'number', 'WriteInt16') + assertRange(valueIn, -0x8000, 0x7FFF, 'WriteInt16') + return self:WriteUInt16(wrap(math.floor(valueIn), 0x8000)) +end + +function meta:WriteUInt16(valueIn) + assertType(valueIn, 'number', 'WriteUInt16') + assertRange(valueIn, 0, 0xFFFF, 'WriteUInt16') + + self.bytes[self.pointer + 1] = band(rshift(valueIn, 8), 0xFF) + self.bytes[self.pointer + 2] = band(valueIn, 0xFF) + self.pointer = self.pointer + 2 + self.length = #self.bytes + + return self +end + +meta.WriteShort = meta.WriteInt16 +meta.WriteShort_2 = meta.WriteInt16_2 +meta.WriteUShort = meta.WriteUInt16 + +--[[ + @doc + @fname BytesBuffer:WriteInt_2 + @alias BytesBuffer:WriteInt32_2 + @args number value + + @desc + with value shift + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteInt + @alias BytesBuffer:WriteInt32 + @args number value + + @desc + with negative number overflow + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteUInt + @alias BytesBuffer:WriteUInt32 + @args number value + + @returns + BytesBuffer: self +]] +function meta:WriteInt32_2(valueIn) + assertType(valueIn, 'number', 'WriteInt32') + assertRange(valueIn, -0x80000000, 0x7FFFFFFF, 'WriteInt32') + return self:WriteUInt32(math.floor(valueIn) + 0x80000000) +end + +function meta:WriteInt32(valueIn) + assertType(valueIn, 'number', 'WriteInt32') + assertRange(valueIn, -0x80000000, 0x7FFFFFFF, 'WriteInt32') + return self:WriteUInt32(wrap(math.floor(valueIn), 0x80000000)) +end + +function meta:WriteUInt32(valueIn) + assertType(valueIn, 'number', 'WriteUInt32') + assertRange(valueIn, 0, 0xFFFFFFFF, 'WriteUInt32') + + self.bytes[self.pointer + 1] = band(rshift(valueIn, 24), 0xFF) + self.bytes[self.pointer + 2] = band(rshift(valueIn, 16), 0xFF) + self.bytes[self.pointer + 3] = band(rshift(valueIn, 8), 0xFF) + self.bytes[self.pointer + 4] = band(valueIn, 0xFF) + self.pointer = self.pointer + 4 + self.length = #self.bytes + + return self +end + +meta.WriteInt = meta.WriteInt32 +meta.WriteInt_2 = meta.WriteInt32_2 +meta.WriteUInt = meta.WriteUInt32 + +--[[ + @doc + @fname BytesBuffer:WriteLong_2 + @alias BytesBuffer:WriteInt64_2 + @args number value + + @desc + with value shift + due to precision errors, this not actually accurate operation + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteLong + @alias BytesBuffer:WriteInt64 + @args number value + + @desc + with negative number overflow + due to precision errors, this not actually accurate operation + @enddesc + + @returns + BytesBuffer: self +]] + +--[[ + @doc + @fname BytesBuffer:WriteULong + @alias BytesBuffer:WriteUInt64 + @args number value + + @desc + due to precision errors, this not actually accurate operation + @enddesc + + @returns + BytesBuffer: self +]] +function meta:WriteInt64_2(valueIn) + self:WriteUInt64(valueIn + 0x100000000) + return self +end + +function meta:WriteInt64(valueIn) + self:WriteUInt64(wrap(valueIn, 0x100000000)) + return self +end + +function meta:WriteUInt64(valueIn) + self:WriteUInt32((valueIn - valueIn % 0xFFFFFFFF) / 0xFFFFFFFF) + valueIn = valueIn % 0xFFFFFFFF + self:WriteUInt32(valueIn) + return self +end + +function meta:CheckOverflow(name, moveBy) + if self.pointer + moveBy > self.length then + error('Read' .. name .. ' - bytes amount overflow (' .. self.pointer .. ' + ' .. moveBy .. ' vs ' .. self.length .. ')', 3) + end +end + +meta.WriteLong = meta.WriteInt64 +meta.WriteLong_2 = meta.WriteInt64_2 +meta.WriteULong = meta.WriteUInt64 + +--[[ + @doc + @fname BytesBuffer:ReadByte_2 + + @desc + with value shift + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadByte + + @desc + with negative number overflow + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadChar + + @returns + string +]] +function meta:ReadByte_2() + return self:ReadUByte() - 0x80 +end + +function meta:ReadByte() + return unwrap(self:ReadUByte(), 0x80) +end + +function meta:ReadUByte() + self:CheckOverflow('UByte', 1) + local value = self.bytes[self.pointer + 1] + self.pointer = self.pointer + 1 + + return value +end + +meta.ReadInt8 = meta.ReadByte +meta.ReadUInt8 = meta.ReadUByte + +--[[ + @doc + @fname BytesBuffer:ReadInt16_2 + + @desc + with value shift + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadInt16 + + @desc + with negative number overflow + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadUInt16 + + @returns + number +]] +function meta:ReadInt16_2() + return self:ReadUInt16() - 0x8000 +end + +function meta:ReadInt16() + return unwrap(self:ReadUInt16(), 0x8000) +end + +function meta:ReadUInt16() + self:CheckOverflow('UInt16', 2) + + local value = + lshift(self.bytes[self.pointer + 1], 8) + + self.bytes[self.pointer + 2] + + self.pointer = self.pointer + 2 + return value +end + +--[[ + @doc + @fname BytesBuffer:ReadInt32_2 + + @desc + with value shift + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadInt32 + + @desc + with negative number overflow + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadUInt32 + + @returns + number +]] +function meta:ReadInt32_2() + return self:ReadUInt32() - 0x80000000 +end + +function meta:ReadInt32() + return unwrap(self:ReadUInt32(), 0x80000000) +end + +function meta:ReadUInt32() + self:CheckOverflow('UInt32', 4) + + local value = + lshift(self.bytes[self.pointer + 1], 24) + + lshift(self.bytes[self.pointer + 2], 16) + + lshift(self.bytes[self.pointer + 3], 8) + + self.bytes[self.pointer + 4] + + self.pointer = self.pointer + 4 + return value +end + +--[[ + @doc + @fname BytesBuffer:ReadInt64_2 + + @desc + with value shift + due to precision errors, this not actually accurate operation + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadInt64 + + @desc + with negative number overflow + due to precision errors, this not actually accurate operation + @enddesc + + @returns + number +]] + +--[[ + @doc + @fname BytesBuffer:ReadUInt64 + + @desc + due to precision errors, this not actually accurate operation + @enddesc + + @returns + number +]] +function meta:ReadInt64_2() + return self:ReadUInt64() - 0x100000000 +end + +function meta:ReadInt64() + return unwrap(self:ReadUInt64(), 0x100000000) +end + +function meta:ReadUInt64() + self:CheckOverflow('UInt64', 8) + return + self:ReadUByte() * 0x100000000000000 + + self:ReadUByte() * 0x1000000000000 + + self:ReadUByte() * 0x10000000000 + + self:ReadUByte() * 0x100000000 + + self:ReadUByte() * 0x1000000 + + self:ReadUByte() * 0x10000 + + self:ReadUByte() * 0x100 + + self:ReadUByte() +end + +--[[ + @doc + @fname BytesBuffer:WriteFloatSlow + @args number float + + @desc + due to precision errors, this is a slightly inaccurate operation + *This function internally utilize tables, so it can hog memory* + @enddesc + + @returns + BytesBuffer: self +]] +function meta:WriteFloatSlow(valueIn) + assertType(valueIn, 'number', 'WriteFloat') + local bits = bitworker.FloatToBinaryIEEE(valueIn, 8, 23) + local bitsInNumber = bitworker.BinaryToUInteger(bits) + return self:WriteUInt32(bitsInNumber) +end + +--[[ + @doc + @fname BytesBuffer:WriteFloat + @args number float + + @desc + due to precision errors, this is a slightly inaccurate operation + @enddesc + + @returns + BytesBuffer: self +]] +function meta:WriteFloat(valueIn) + assertType(valueIn, 'number', 'WriteFloat') + return self:WriteInt32(bitworker.FastFloatToBinaryIEEE(valueIn)) +end + +--[[ + @doc + @fname BytesBuffer:ReadFloatSlow + + @desc + due to precision errors, this is a slightly inaccurate operation + *This function internally utilize tables, so it can hog memory* + @enddesc + + @returns + number +]] +function meta:ReadFloatSlow() + local bitsInNumber = self:ReadUInt32() + local bits = bitworker.UIntegerToBinary(bitsInNumber, 32) + return bitworker.BinaryToFloatIEEE(bits, 8, 23) +end + +--[[ + @doc + @fname BytesBuffer:ReadFloat + + @desc + due to precision errors, this is a slightly inaccurate operation + @enddesc + + @returns + number +]] +function meta:ReadFloat() + return bitworker.FastBinaryToFloatIEEE(self:ReadUInt32()) +end + +--[[ + @doc + @fname BytesBuffer:WriteDoubleSlow + @args number double + + @desc + due to precision errors, this is a inaccurate operation + *This function internally utilize tables, so it can hog memory* + @enddesc + + @returns + BytesBuffer: self +]] +function meta:WriteDoubleSlow(valueIn) + assertType(valueIn, 'number', 'WriteDouble') + local bits = bitworker.FloatToBinaryIEEE(valueIn, 11, 52) + local bytes = bitworker.BitsToBytes(bits) + + self.bytes[self.pointer + 1] = bytes[1] + self.bytes[self.pointer + 2] = bytes[2] + self.bytes[self.pointer + 3] = bytes[3] + self.bytes[self.pointer + 4] = bytes[4] + self.bytes[self.pointer + 5] = bytes[5] + self.bytes[self.pointer + 6] = bytes[6] + self.bytes[self.pointer + 7] = bytes[7] + self.bytes[self.pointer + 8] = bytes[8] + self.pointer = self.pointer + 8 + self.length = #self.bytes + + return self +end + +--[[ + @doc + @fname BytesBuffer:WriteDouble + @args number double + + @desc + due to precision errors, this is a inaccurate operation + @enddesc + + @returns + BytesBuffer: self +]] +function meta:WriteDouble(valueIn) + assertType(valueIn, 'number', 'WriteDouble') + local int1, int2 = bitworker.FastDoubleToBinaryIEEE(valueIn) + self:WriteInt32(int1) + self:WriteInt32(int2) + return self +end + +--[[ + @doc + @fname BytesBuffer:ReadDoubleSlow + + @desc + due to precision errors, this is a slightly inaccurate operation + *This function internally utilize tables, so it can hog memory* + @enddesc + + @returns + number +]] +function meta:ReadDoubleSlow() + local bytes1 = self:ReadUInt32() + local bytes2 = self:ReadUInt32() + local bits = {} + table.append(bits, bitworker.UIntegerToBinary(bytes1, 32)) + table.append(bits, bitworker.UIntegerToBinary(bytes2, 32)) + return bitworker.BinaryToFloatIEEE(bits, 11, 52) +end + +--[[ + @doc + @fname BytesBuffer:ReadDouble + + @desc + due to precision errors, this is a slightly inaccurate operation + @enddesc + + @returns + number +]] +function meta:ReadDouble() + return bitworker.FastBinaryToDoubleIEEE(self:ReadUInt32(), self:ReadUInt32()) +end + +--[[ + @doc + @fname BytesBuffer:WriteString + @args string data + + @desc + writes NUL terminated string to buffer + errors if NUL is present in string + @enddesc + + @returns + BytesBuffer: self +]] +-- String +function meta:WriteString(stringIn) + assertType(stringIn, 'string', 'WriteString') + if #stringIn == 0 then return self end + local bytes = DLib.string.bbyte(stringIn, 1, #stringIn) + local i = 0 + local len = #bytes + + ::loop:: + i = i + 1 + + if bytes[i] == 0 then + error('Binary data in a string?!') + end + + self:WriteUByte(bytes[i]) + + if i < len then + goto loop + end + + self:WriteUByte(0) + + return self +end + +--[[ + @doc + @fname BytesBuffer:ReadString + @args string data + + @desc + reads buffer until it hits NUL symbol + errors if buffer depleted before NUL is found + @enddesc + + @returns + string +]] +function meta:ReadString() + self:CheckOverflow('ReadString', 1) + local readNext = self:ReadUByte() + local output = {} + + if readNext == 0 then return '' end + + ::loop:: + + table.insert(output, readNext) + + if self:EndOfStream() then + error('No NUL terminator was found, buffer overflow!') + end + + readNext = self:ReadUByte() + + if readNext ~= 0 and readNext ~= nil then + goto loop + end + + return DLib.string.bcharTable(output) +end + +-- Binary Data + +--[[ + @doc + @fname BytesBuffer:WriteBinary + @alias BytesBuffer:WriteData + @args string binary + + @returns + BytesBuffer: self +]] +function meta:WriteBinary(binaryString) + assertType(binaryString, 'string', 'WriteBinary') + if #binaryString == 0 then return self end + + for i = 1, #binaryString do + self:WriteUByte(binaryString:byte(i, i)) + end + + return self +end + +--[[ + @doc + @fname BytesBuffer:ReadBinary + @alias BytesBuffer:ReadData + @args number bytesToRead + + @returns + string +]] +function meta:ReadBinary(readAmount) + assert(readAmount >= 0, 'Read amount must be positive') + if readAmount == 0 then return '' end + self:CheckOverflow('Binary', readAmount) + + local output = {} + + local i = 0 + + ::loop:: + + i = i + 1 + table.insert(output, self:ReadUByte()) + + if i < readAmount then + goto loop + end + + return DLib.string.bcharTable(output) +end + +meta.WriteData = meta.WriteBinary +meta.ReadData = meta.ReadBinary + +function meta:ReadChar() + return string.char(self:ReadUByte()) +end + +--[[ + @doc + @fname BytesBuffer:ToString + + @deprecated + + @returns + string +]] +function meta:ToString() + local pointer = self.pointer + self:Seek(0) + local str = self:ReadBinary(self.length) + self:Seek(pointer) + return str +end + +--[[ + @doc + @fname BytesBuffer:ToFileStream + @args File stream + + @returns + File +]] +function meta:ToFileStream(fileStream) + local bytes = #self.bytes + if bytes == 0 then return fileStream end + + for i, byte in ipairs(self.bytes) do + fileStream:WriteByte(byte) + end + + return fileStream +end + +--[[ + @doc + @fname DLib.BytesBuffer.CompileStructure + @args string structureDef, table customTypes + + @desc + Reads a structure from current pointer position + this is somewhat fancy way of reading typical headers and stuff + The string is in next format: + ``` + type identifier with any symbols + type on_next_line + uint32 thing + ``` + Supported types: + `int8` + `int16` + `int32` + `int64` + `bigint` + `float` + `double` + `uint8` + `uint16` + `uint32` + `uint64` + `ubigint` + `string` - NUL terminated string + @enddesc + + @returns + function: a function to pass `BytesBuffer` to get readed structure +]] +function DLib.BytesBuffer.CompileStructure(structureDef, callbacks) + local output = {} + + for i, line in ipairs(structureDef:split('\n')) do + line = line:trim() + --assert(line ~= '', 'Invalid line definition at ' .. i) + + if line ~= '' then + local findSpace = assert(line:find('%s'), 'Can\'t find variable name at line ' .. i) + local rtype2 = line:sub(1, findSpace):trim() + local rtype = rtype2:lower() + local rname = line:sub(findSpace + 1):trim() + local findCommentary = rname:find('%s//') + + if findCommentary then + rname = rname:sub(1, findCommentary):trim() + end + + if rtype == 'int8' or rtype == 'byte' then + table.insert(output, {rname, meta.ReadByte}) + elseif rtype == 'int16' or rtype == 'short' then + table.insert(output, {rname, meta.ReadInt16}) + elseif rtype == 'int32' or rtype == 'long' or rtype == 'int' then + table.insert(output, {rname, meta.ReadInt32}) + elseif rtype == 'int64' or rtype == 'longlong' or rtype == 'bigint' then + table.insert(output, {rname, meta.ReadInt64}) + elseif rtype == 'uint8' or rtype == 'ubyte' then + table.insert(output, {rname, meta.ReadUByte}) + elseif rtype == 'uint16' or rtype == 'ushort' then + table.insert(output, {rname, meta.ReadUInt16}) + elseif rtype == 'uint32' or rtype == 'ulong' or rtype == 'uint' then + table.insert(output, {rname, meta.ReadUInt32}) + elseif rtype == 'uint64' or rtype == 'ulong64' or rtype == 'biguint' or rtype == 'ubigint' then + table.insert(output, {rname, meta.ReadUInt64}) + elseif rtype == 'float' then + table.insert(output, {rname, meta.ReadFloat}) + elseif rtype == 'double' then + table.insert(output, {rname, meta.ReadDouble}) + elseif rtype == 'variable' or rtype == 'string' then + table.insert(output, {rname, meta.ReadString}) + elseif callbacks and callbacks[rtype2] then + table.insert(output, {rname, callbacks[rtype2]}) + else + DLib.MessageError(debug.traceback('Undefined type: ' .. rtype)) + end + end + end + + return function(self) + local read = {} + + for i, data in ipairs(output) do + read[data[1]] = data[2](self, read) + end + + return read + end +end + +--[[ + @doc + @fname BytesBuffer:ReadStructure + @args string structureDef, table customTypes + + @desc + `DLib.BytesBuffer.CompileStructure(structureDef, customTypes)(self)` + @enddesc + + @returns + table: the read structure +]] +function meta:ReadStructure(structureDef, callbacks) + return DLib.BytesBuffer.CompileStructure(structureDef, callbacks)(self) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/client/friendstatus.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/client/friendstatus.lua new file mode 100644 index 0000000..131bc55 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/client/friendstatus.lua @@ -0,0 +1,209 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 LocalPlayer = LocalPlayer +local net = net +local plyMeta = FindMetaTable('Player') +local cl_dlib_steamfriends = CreateClientConVar('cl_dlib_steamfriends', '1', true, true, 'Consider Steam friends as DLib friends') + +DLib.getinfo.Replicate('cl_dlib_steamfriends') + +plyMeta.GetFriendStatusDLib = plyMeta.GetFriendStatusDLib or plyMeta.GetFriendStatus + +--[[ + @doc + @replaces + @fname Player:GetFriendStatus + @args Player targetPly + + @desc + without player argument is same as described in !g:Player:GetFriendStatus + otherwise returns value based on whenever `self` player is a friend towards `targetPly` + @enddesc + + @returns + string +]] +function plyMeta:GetFriendStatus(targetPly) + if not targetPly then + return self:GetFriendStatusDLib() + end + + if not IsValid(targetPly) then + return 'none' + end + + if self == LocalPlayer() then + return targetPly:GetFriendStatusDLib() + end + + local status = self.DLibFriends + return status and status[targetPly] or 'none' +end + +--[[ + @doc + @fname Player:IsFriend + @args Player targetPly + + @returns + boolean +]] +function plyMeta:IsFriend(target) + local f = self:GetFriendStatus(target) + return f == 'friend' +end + +local function checkFriendDisable(self) + if self == LocalPlayer() then + return cl_dlib_steamfriends:GetBool() + elseif IsValid(self) then + return self:GetInfoBool('cl_dlib_steamfriends', true) + end + + return true +end + +--[[ + @doc + @fname Player:IsFriend2 + @args Player targetPly + + @desc + behavior can be changed by either of involved players + @enddesc + + @returns + boolean +]] +function plyMeta:IsFriend2(target) + if not checkFriendDisable(self) then return false end + if not checkFriendDisable(target) then return false end + + local f = self:GetFriendStatus(target) + return f == 'friend' +end + +--[[ + @doc + @fname Player:IsSteamFriend + @args Player targetPly + + @returns + boolean +]] + +function plyMeta:IsSteamFriend(target) + local f = self:GetFriendStatus(target) + return f == 'friend' +end + +--[[ + @doc + @fname Player:IsSteamFriend2 + @args Player targetPly + + @desc + behavior can be changed by either of involved players + @enddesc + + @returns + boolean +]] +function plyMeta:IsSteamFriend2(target) + if not checkFriendDisable(self) then return false end + if not checkFriendDisable(target) then return false end + + local f = self:GetFriendStatus(target) + return f == 'friend' +end + +--[[ + @doc + @fname Player:IsSteamBlocked + @args Player targetPly + + @returns + boolean +]] +function plyMeta:IsSteamBlocked(target) + local f = self:GetFriendStatus(target) + return f == 'blocked' +end + +local knownStatus = {} +local enums = DLib.Enum('none', 'friend', 'blocked', 'requested') +local IsValid = FindMetaTable('Entity').IsValid + +local function update() + local dirty = false + + for ply, status in pairs(knownStatus) do + if not IsValid(ply) then + dirty = true + knownStatus[ply] = nil + end + end + + for i, ply in ipairs(player.GetAll()) do + local newstatus = ply:GetFriendStatus() + + if knownStatus[ply] ~= newstatus then + knownStatus[ply] = newstatus + dirty = true + end + end + + if dirty then + net.Start('DLib.friendstatus') + + net.WriteUInt(table.Count(knownStatus), 8) + + for ply, status in pairs(knownStatus) do + net.WritePlayer(ply) + enums:write(status) + end + + net.SendToServer() + end +end + +timer.Create('DLib.FriendStatus', 5, 0, update) + +local function friendstatus() + local ply = net.ReadPlayer() + if not IsValid(ply) then return end + + local amount = net.ReadUInt(8) + ply.DLibFriends = {} + local status = ply.DLibFriends + + for i = 1, amount do + local readPly = net.ReadPlayer() + local readEnum = enums:read() + + if IsValid(readPly) then + status[readPly] = readEnum + end + end +end + +net.receive('DLib.friendstatus', friendstatus) diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/color.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/color.lua new file mode 100644 index 0000000..68bb840 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/color.lua @@ -0,0 +1,998 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 string = string +local setmetatable = setmetatable +local math = math +local type = type +local tonumber = tonumber +local Vector = Vector +local ColorToHSV = ColorToHSV +local Lerp = Lerp +local util = util +local assert = assert +local srnd = util.SharedRandom + +local colorMeta = FindMetaTable('Color') or {} +colorMeta.__index = colorMeta +colorMeta.MetaName = 'Color' +debug.getregistry().Color = colorMeta + + +--[[ + @doc + @fname Color + @replaces + @args any r = 255, number g = 255, number b = 255, number a = 255 + + @desc + First argument can be either table with `r, g, b, a` properties defined, or be a number + if it is number, then if it is bigger than 255, it is considered to be a big endian color number. + @enddesc + + @returns + Color: newly created object +]] +local function Color(r, g, b, a) + if IsColor(r) then + g = r.g + b = r.b + a = r.a + r = r.r + elseif type(r) == 'number' and not g and not b and not a then + return ColorBE(r) + elseif type(r) == 'nil' and type(g) ~= 'nil' then + error('I think this is not something you want to do. Red is nil, Green is ' .. type(g) .. ', Blue is ' .. type(b)) + end + + r = (tonumber(r) or 255):clamp(0, 255):floor() + g = (tonumber(g) or 255):clamp(0, 255):floor() + b = (tonumber(b) or 255):clamp(0, 255):floor() + a = (tonumber(a) or 255):clamp(0, 255):floor() + + local newObj = { + r = r, + g = g, + b = b, + a = a, + } + + return setmetatable(newObj, colorMeta) +end + +--[[ + @doc + @fname ColorFromSeed + @args string seed + + @returns + Color: newly created object +]] +function _G.ColorFromSeed(seedIn) + return Color(srnd(seedIn, 0, 255, 0), srnd(seedIn, 0, 255, 1), srnd(seedIn, 0, 255, 2)) +end + +--[[ + @doc + @fname IsColor + @replaces + @args any value + + @desc + unlike !g:IsColor + this can duck type the value + @enddesc + + @returns + boolean +]] +_G.Color = Color +local IsColor + +--[[ + @doc + @fname ColorAlpha + @replaces + @args Color target, number newAlpha + + @returns + Color: copied color with modified alpha +]] +function _G.ColorAlpha(target, newAlpha) + if not IsColor(target) then + error('Input is not a color! typeof ' .. type(target)) + end + + if target.Copy then + return target:Copy():SetAlpha(newAlpha) + else + return Color(target.r, target.g, target.b, newAlpha) + end +end + +do + local pcall = pcall + + local function getMeta(object) + return type(object) == 'table' and + type(object.r) == 'number' and + type(object.g) == 'number' and + type(object.b) == 'number' and + type(object.a) == 'number' + end + + function IsColor(object) + if getmetatable(object) == colorMeta then + return true + end + + local cstatus, cresult = pcall(getMeta, object) + return cstatus and cresult + end +end + +_G.iscolor = IsColor +_G.IsColor = IsColor + +--[[ + @doc + @fname Color:__tostring + + @returns + string +]] +function colorMeta:__tostring() + return string.format('Color[%i %i %i %i]', self.r, self.g, self.b, self.a) +end + +--[[ + @doc + @fname Color:ToHex + + @returns + string +]] +function colorMeta:ToHex() + return string.format('0x%02x%02x%02x', self.r, self.g, self.b) +end + +--[[ + @doc + @fname Color:ToNumberLittle + @alias Color:ToNumberLittlEndian + @alias Color:ToNumberLE + @args boolean writeAlpha + + @desc + turns color into little endian integer + @enddesc + + @returns + number +]] +function colorMeta:ToNumberLittle(writeAlpha) + if writeAlpha then + return self.r:band(255) + self.g:band(255):lshift(8) + self.b:band(255):lshift(16) + self.a:band(255):lshift(24) + else + return self.r:band(255) + self.g:band(255):lshift(8) + self.b:band(255):lshift(16) + end +end + +--[[ + @doc + @fname Color:ToNumber + @alias Color:ToNumberBig + @alias Color:ToNumberBigEndian + @alias Color:ToNumberBE + @args boolean writeAlpha + + @desc + turns color into big endian integer + @enddesc + + @returns + number +]] +function colorMeta:ToNumber(writeAlpha) + if writeAlpha then + return self.r:band(255):lshift(24) + self.g:band(255):lshift(16) + self.b:band(255):lshift(8) + self.a:band(255) + else + return self.r:band(255):lshift(16) + self.g:band(255):lshift(8) + self.b:band(255) + end +end + +colorMeta.ToNumberBig = colorMeta.ToNumber +colorMeta.ToNumberBigEndian = colorMeta.ToNumber +colorMeta.ToNumberBE = colorMeta.ToNumber +colorMeta.ToNumberLittlEndian = colorMeta.ToNumberLittle +colorMeta.ToNumberLE = colorMeta.ToNumberLittle + +--[[ + @doc + @fname ColorFromNumberLittle + @alias ColorFromNumberLittleEndian + @alias ColorFromNumberLE + @alias ColorLE + + @args number value, boolean hasAlpha + + @desc + turns little endian integer into color + @enddesc + + @returns + Color +]] +function _G.ColorFromNumberLittle(numIn, hasAlpha) + assert(type(numIn) == 'number', 'Must be a number!') + if hasAlpha then + local a, b, g, r = + numIn:rshift(24):band(255), + numIn:rshift(16):band(255), + numIn:rshift(8):band(255), + numIn:band(255) + + return Color(r, g, b, a) + end + + local b, g, r = + numIn:rshift(16):band(255), + numIn:rshift(8):band(255), + numIn:band(255) + + return Color(r, g, b) +end + +--[[ + @doc + @fname ColorFromNumber + @alias ColorFromNumberBig + @alias ColorFromNumberBigEndian + @alias ColorFromNumberBE + @alias ColorBE + + @args number value, boolean hasAlpha + + @desc + turns big endian integer into color + @enddesc + + @returns + Color +]] +function _G.ColorFromNumber(numIn, hasAlpha) + assert(type(numIn) == 'number', 'Must be a number!') + if hasAlpha then + local r, g, b, a = + numIn:rshift(24):band(255), + numIn:rshift(16):band(255), + numIn:rshift(8):band(255), + numIn:band(255) + + return Color(r, g, b, a) + end + + local r, g, b = + numIn:rshift(16):band(255), + numIn:rshift(8):band(255), + numIn:band(255) + + return Color(r, g, b) +end + +local ColorFromNumber = ColorFromNumber + +--[[ + @doc + @fname ColorFromHex + @alias ColorHex + @alias ColorHEX + @alias ColorFromHEX + @alias Color16 + @alias ColorFrom16 + + @args string value, boolean hasAlpha + + @returns + Color +]] +function _G.ColorFromHex(hex, hasAlpha) + return ColorFromNumber(tonumber(hex, 16), hasAlpha) +end + +_G.ColorHex = _G.ColorFromHex +_G.ColorHEX = _G.ColorFromHex +_G.ColorFromHEX = _G.ColorFromHex +_G.Color16 = _G.ColorFromHex +_G.ColorFrom16 = _G.ColorFromHex +_G.ColorFromNumberLittleEndian = _G.ColorFromNumberLittle +_G.ColorFromNumberLE = _G.ColorFromNumberLittle +_G.ColorLE = _G.ColorFromNumberLittle +_G.ColorFromNumberBig = _G.ColorFromNumber +_G.ColorFromNumberBigEndian = _G.ColorFromNumber +_G.ColorFromNumberBE = _G.ColorFromNumber +_G.ColorBE = _G.ColorFromNumber + +--[[ + @doc + @fname HSVToColorC + @args number hue, number saturation, number value + + @desc + the old (gmod's) !g:HSVToColor + @enddesc + + @returns + table +]] +_G.HSVToColorC = HSVToColorC or HSVToColor +local HSVToColorC = HSVToColorC + +--[[ + @doc + @fname HSVToColorLua + @args number hue, number saturation, number value + + @desc + JIT compilable !g:HSVToColor + **This function has metatable of color fixed** + **This function has left bitwise shift overflow fixed** + **This function clamp saturation and value (unlike original function), and modulo divide hue (like original function)** + @enddesc + + @returns + Color +]] +function _G.HSVToColorLua(hue, saturation, value) + if type(hue) ~= 'number' then + error('Hue expected to be a number, ' .. type(hue) .. ' given') + end + + if type(saturation) ~= 'number' then + error('Saturation expected to be a number, ' .. type(hue) .. ' given') + end + + if type(value) ~= 'number' then + error('Value (brightness) expected to be a number, ' .. type(hue) .. ' given') + end + + hue = (hue % 360):floor() + if hue < 0 then hue = 360 + hue end + saturation = saturation:clamp(0, 1) + value = value:clamp(0, 1) + + local huei = (hue / 60):floor() % 6 + local valueMin = (1 - saturation) * value + local delta = (value - valueMin) * (hue % 60) / 60 + local valueInc = valueMin + delta + local valueDec = value - delta + + if huei == 0 then + return Color(value * 255, valueInc * 255, valueMin * 255) + elseif huei == 1 then + return Color(valueDec * 255, value * 255, valueMin * 255) + elseif huei == 2 then + return Color(valueMin * 255, value * 255, valueInc * 255) + elseif huei == 3 then + return Color(valueMin * 255, valueDec * 255, value * 255) + elseif huei == 4 then + return Color(valueInc * 255, valueMin * 255, value * 255) + end + + return Color(value * 255, valueMin * 255, valueDec * 255) +end + +--[[ + @doc + @fname HSVToColor + @replaces + @args number hue, number saturation, number value + + @desc + !g:HSVToColor with metatable fixed + @enddesc + + @returns + Color +]] +function _G.HSVToColor(hue, saturation, value) + return setmetatable(HSVToColorC(hue, saturation, value), colorMeta) +end + +--[[ + @doc + @fname Color:__eq + @args any other + + @desc + var1 == var2 + @enddesc + + @returns + boolean +]] +function colorMeta:__eq(target) + if not IsColor(target) then + return false + end + + return target.r == self.r and target.g == self.g and target.b == self.b and target.a == self.a +end + +local function NormalizeColor(r, g, b) + if r >= 0 and r < 256 and g >= 0 and g < 256 and b >= 0 and b < 256 then + return r, g, b + end + + if r < 0 then + g, b = g - r, b - r + r = 0 + end + + if g < 0 then + r, b = r - g, b - g + g = 0 + end + + if b < 0 then + r, g = r - b, g - b + b = 0 + end + + if r >= 0 and r < 256 and g >= 0 and g < 256 and b >= 0 and b < 256 then + return r, g, b + end + + local len = (r:pow(2) + g:pow(2) + b:pow(2)):sqrt() / 255 + + r = (r / len):round() + g = (g / len):round() + b = (b / len):round() + + return r, g, b +end + +--[[ + @doc + @fname NormalizeColor + @args number r, number g, number b + + @desc + normalizes negative and overflown channels + @enddesc + + @returns + number: r + number: g + number: b +]] +_G.NormalizeColor = NormalizeColor + +--[[ + @doc + @fname Color:__add + @args any other + + @desc + var1 + var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + This operation work over color like over normalized vector + @enddesc + + @returns + Color: copy +]] +function colorMeta:__add(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + local r, g, b = mathLogic(self.r, target, self.g, self.b) + g, r, b = mathLogic(g, target, r, b) + b, r, g = mathLogic(b, target, r, g) + + return Color(NormalizeColor(self.r + target, self.g + target, self.b + target)):SetAlpha(self.a) + elseif type(target) == 'Vector' then + return self + target:ToColor() + else + if not IsColor(target) then + error('Color + ' .. type(target) .. ' => Not a function!') + end + + return Color(NormalizeColor(self.r + target.r, self.g + target.g, self.b + target.b)):SetAlpha(self.a) + end +end + +--[[ + @doc + @fname Color:__sub + @args any other + + @desc + var1 - var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + This operation work over color like over normalized vector + @enddesc + + @returns + Color: copy +]] +function colorMeta:__sub(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return Color(NormalizeColor(self.r - target, self.g - target, self.b - target)):SetAlpha(self.a) + elseif type(target) == 'Vector' then + return self - target:ToColor() + else + if not IsColor(target) then + error('Color - ' .. type(target) .. ' => Not a function!') + end + + return Color(NormalizeColor(self.r - target.r, self.g - target.g, self.b - target.b)):SetAlpha(self.a) + end +end + +--[[ + @doc + @fname Color:__mul + @args any other + + @desc + var1 * var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + This operation work over color like over normalized vector + @enddesc + + @returns + Color: copy +]] +function colorMeta:__mul(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return Color(NormalizeColor(self.r * (target / 255), self.g * (target / 255), self.b * (target / 255))):SetAlpha(self.a) + elseif type(target) == 'Vector' then + return self * target:ToColor() + else + if not IsColor(target) then + error('Color * ' .. type(target) .. ' => Not a function!') + end + + return Color(NormalizeColor(self.r * (target.r / 255), self.g * (target.g / 255), self.b * (target.b / 255))):SetAlpha(self.a) + end +end + +--[[ + @doc + @fname Color:__div + @args any other + + @desc + var1 / var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + This operation work over color like over normalized vector + @enddesc + + @returns + Color: copy +]] +function colorMeta:__div(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return Color(NormalizeColor(self.r / target, self.g / target, self.b / target)):SetAlpha(self.a) + elseif type(target) == 'Vector' then + return self / target:ToColor() + else + if not IsColor(target) then + error('Color / ' .. type(target) .. ' => Not a function!') + end + + return Color(NormalizeColor(self.r / target.r, self.g / target.g, self.b / target.b)):SetAlpha(self.a) + end +end + +--[[ + @doc + @fname Color:__mod + @args any other + + @desc + var1 % var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + @enddesc + + @returns + Color: copy +]] +function colorMeta:__mod(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return Color(NormalizeColor(self.r % target, self.g % target, self.b % target)):SetAlpha(self.a) + elseif type(target) == 'Vector' then + return self % target:ToColor() + else + if not IsColor(target) then + error('Color % ' .. type(target) .. ' => Not a function!') + end + + return Color(NormalizeColor(self.r % target.r, self.g % target.g, self.b % target.b)):SetAlpha(self.a) + end +end + +--[[ + @doc + @fname Color:__pow + @args any other + + @desc + var1 ^ var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + @enddesc + + @returns + Color: copy +]] +function colorMeta:__pow(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return Color(NormalizeColor(self.r:pow(target), self.g:pow(target), self.b:pow(target))):SetAlpha(self.a) + elseif type(target) == 'Vector' then + return self ^ target:ToColor() + else + if not IsColor(target) then + error('Color ^ ' .. type(target) .. ' => Not a function!') + end + + return Color(NormalizeColor(self.r:pow(target.r), self.g:pow(target.g), self.b:pow(target.b))):SetAlpha(self.a) + end +end + +--[[ + @doc + @fname Color:__concat + @args any other + + @desc + var1 .. var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on each channel separately if other operand is not a number + @enddesc + + @returns + string +]] +function colorMeta:__concat(target) + if IsColor(self) then + return string.format('%i %i %i %i', self.r, self.g, self.b, self.a) .. target + else + return self .. string.format('%i %i %i %i', target.r, target.g, target.b, target.a) + end +end + +--[[ + @doc + @fname Color:__lt + @args any other + + @desc + var1 < var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on `Length` of Color, which is red + green + blue + @enddesc + + @returns + boolean +]] +function colorMeta:__lt(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return self:Length() < target + elseif type(target) == 'Vector' then + return self < target:ToColor() + else + if not IsColor(target) then + error('Color < ' .. type(target) .. ' => Not a function!') + end + + return self:Length() < target:Length() + end +end + +--[[ + @doc + @fname Color:__le + @args any other + + @desc + var1 <= var2 + accepts `number`, `Vector` and `Color` + throws an error if one of arguments is not in list above + operation is performed on `Length` of Color, which is red + green + blue + @enddesc + + @returns + boolean +]] +function colorMeta:__le(target) + if not IsColor(self) and IsColor(target) then + local s1, s2 = self, target + target = s1 + self = s2 + end + + if type(target) == 'number' then + return self:Length() <= target + elseif type(target) == 'Vector' then + return self <= target:ToColor() + else + if not IsColor(target) then + error('Color <= ' .. type(target) .. ' => Not a function!') + end + + return self:Length() <= target:Length() + end +end + +--[[ + @doc + @fname Color:__unm + + @desc + -var1 + alias for Color:Invert + @enddesc + + @returns + Color: copy +]] +function colorMeta:__unm() + return self:Invert() +end + +--[[ + @doc + @fname Color:Copy + + @returns + Color: copy +]] +function colorMeta:Copy() + return Color(self.r, self.g, self.b, self.a) +end + +--[[ + @doc + @fname Color:Length + + @returns + number: r + g + b +]] +function colorMeta:Length() + return self.r + self.g + self.b +end + +--[[ + @doc + @fname Color:Invert + @args any other + + @desc + Inverts color channels + @enddesc + + @returns + Color: copy +]] +function colorMeta:Invert() + return Color(255 - self.r, 255 - self.g, 255 - self.b, self.a) +end + +--[[ + @doc + @fname Color:ToHSV + + @returns + number: hue + number: saturation + number: value +]] +function colorMeta:ToHSV() + return ColorToHSV(self) +end + +--[[ + @doc + @fname Color:ToVector + + @returns + Vector: normalized vector +]] +function colorMeta:ToVector() + return Vector(self.r / 255, self.g / 255, self.b / 255) +end + +--[[ + @doc + @fname Color:Lerp + @args number t, Color lerpTo + + @desc + You can also use regular !g:Lerp function for this. + @enddesc + + @returns + Color: copy +]] +function colorMeta:Lerp(lerpValue, lerpTo) + if not IsColor(lerpTo) then + error('Color:Lerp - second argument is not a color!') + end + + local r = Lerp(lerpValue, self.r, lerpTo.r) + local g = Lerp(lerpValue, self.g, lerpTo.g) + local b = Lerp(lerpValue, self.b, lerpTo.b) + + return Color(r, g, b, self.a) +end + +--[[ + @docpreprocess + + const methods = [ + 'Red', + 'Green', + 'Blue', + 'Alpha', + ] + + const output = [] + + for (const method of methods) { + const output2 = [] + + output2.push(`@fname Color:Set${method}`) + output2.push(`@args number newValue`) + + output2.push(`@desc`) + output2.push(`Sets \`${method}\` channel of color`) + output2.push(`This **modifies** the original color.`) + output2.push(`@enddesc`) + + output2.push(`@returns`) + output2.push(`Color: self`) + + output.push(output2) + + const output3 = [] + + output3.push(`@fname Color:Modify${method}`) + output3.push(`@args number newValue`) + + output3.push(`@desc`) + output3.push(`Sets \`${method}\` channel of color`) + output3.push(`This **creates a copy** of the original color.`) + output3.push(`@enddesc`) + + output3.push(`@returns`) + output3.push(`Color: copy`) + + output.push(output3) + + const output4 = [] + + output4.push(`@fname Color:Get${method}`) + + output4.push(`@returns`) + output4.push(`number: the \`${method}\` channel of color`) + + output.push(output4) + } + + return output +]] +do + local methods = { + r = 'Red', + g = 'Green', + b = 'Blue', + a = 'Alpha' + } + + for key, method in pairs(methods) do + colorMeta['Set' .. method] = function(self, newValue) + self[key] = (tonumber(newValue) or 255):clamp(0, 255):floor() + return self + end + + colorMeta['Modify' .. method] = function(self, newValue) + local new = Color(self) + new[key] = (tonumber(newValue) or 255):clamp(0, 255):floor() + return new + end + + colorMeta['Get' .. method] = function(self) + return self[key] + end + end +end + +local colorBundle = { + color_black = Color() - 255, + color_white = Color(255, 255, 255), + color_red = Color(255, 0, 0), + color_green = Color(0, 255, 0), + color_blue = Color(0, 0, 255), + color_cyan = Color(0, 255, 255), + color_magenta = Color(255, 0, 255), + color_yellow = Color(255, 255, 0), + color_dlib = Color(0, 0, 0, 255), + color_transparent = Color():SetAlpha(0), +} + +for k, v in pairs(colorBundle) do + _G[k] = v +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/getinfo.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/getinfo.lua new file mode 100644 index 0000000..f0a32b5 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/getinfo.lua @@ -0,0 +1,226 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +local util = util +local pairs = pairs +local ipairs = ipairs +local RealTimeL = RealTimeL +local GetConVar = GetConVar +local net = net +local table = table +local IsValid = IsValid +local LocalPlayer = LocalPlayer + +if SERVER then + net.pool('DLib.getinfo.replicate') +end + +local plyMeta = FindMetaTable('Player') +local entMeta = FindMetaTable('Entity') +DLib.getinfo = DLib.getinfo or {} +local getinfo = DLib.getinfo + +getinfo.bank = getinfo.bank or {} +getinfo.bankCRC = getinfo.bankCRC or {} +getinfo.bankOptimized = {} + +--[[ + @doc + @fname DLib.getinfo.Replicate + @args string cvarname, string valueType = 'string', any default + + @desc + Marks a console variable for replication after it's creation (e.g. when you can specify FCVAR_USERINFO flag on a cvar) + valueType can be either: + `'boolean'` + `'number'` or `'integer'` + `'uinteger'` + `'float'` + `'string'` (by default, if type is invalid or missing) + @enddesc +]] +function getinfo.Replicate(cvarname, valuetype, default) + valuetype = valuetype or 'boolean' + local crc = util.CRC(cvarname) + local writeFunc, readFunc, nwGet, nwSet + + if valuetype == 'boolean' then + readFunc = net.ReadBool + writeFunc = net.WriteBool + nwSet = entMeta.SetNWBool + nwGet = entMeta.GetNWBool + elseif valuetype == 'integer' or valuetype == 'number' then + readFunc = net.ReadInt32 + writeFunc = net.WriteInt32 + nwSet = entMeta.SetNWInt + nwGet = entMeta.GetNWInt + elseif valuetype == 'uinteger' then + readFunc = net.ReadUInt32 + writeFunc = net.WriteUInt32 + nwSet = entMeta.SetNWUInt + nwGet = entMeta.GetNWUInt + elseif valuetype == 'float' then + readFunc = net.ReadFloat + writeFunc = net.WriteFloat + nwSet = entMeta.SetNWFloat + nwGet = entMeta.GetNWFloat + else + readFunc = net.ReadString + writeFunc = net.WriteString + default = tostring(default) + valuetype = 'string' + nwSet = entMeta.SetNWString + nwGet = entMeta.GetNWString + end + + if not nwSet then + error('Missing NW Set for ' .. cvarname .. '! This should never happen') + end + + getinfo.bank[cvarname] = { + crc = crc, + uid = tonumber(crc), + created = RealTimeL(), + cvar = GetConVar(cvarname), + default = default, + valuetype = valuetype, + id = cvarname, + cvarname = cvarname, + writeFunc = writeFunc, + readFunc = readFunc, + nwGet = nwGet, + nwSet = nwSet, + } + + if CLIENT then + cvars.AddChangeCallback(cvarname, getinfo.ReplicateNow, 'DLib.getinfo') + end + + getinfo.bankCRC[crc] = getinfo.bank[cvarname] + getinfo.bankCRC[getinfo.bank[cvarname].uid] = getinfo.bank[cvarname] + + getinfo.Rebuild() +end + +getinfo.Register = getinfo.Replicate + +--[[ + @doc + @fname DLib.getinfo.Rebuild + + @internal + + @returns + table: optimized list for ipairs iteration +]] +function getinfo.Rebuild() + getinfo.bankOptimized = {} + + for cvar, data in pairs(getinfo.bank) do + table.insert(getinfo.bankOptimized, data) + end + + return getinfo.bankOptimized +end + +getinfo.Rebuild() + +if CLIENT then + timer.Create('DLib.getinfo.replication', 10, 0, function() + getinfo.ReplicateNow() + end) + + function getinfo.ReplicateNow() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + net.Start('DLib.getinfo.replicate') + + for i, data in ipairs(getinfo.bankOptimized) do + data.cvar = data.cvar or GetConVar(data.id) + local val = data.cvar:GetByType(data.valuetype) + + --if val ~= data.oldval then + net.WriteUInt(data.uid, 32) + data.writeFunc(val) + + data.nwSet(ply, data.id, val) + data.oldval = val + --end + end + + net.WriteUInt(0, 32) + + net.SendToServer() + end +else + net.receive('DLib.getinfo.replicate', function(len, ply) + if not IsValid(ply) then return end + local nextID = net.ReadUInt(32) + + while nextID ~= 0 and getinfo.bankCRC[nextID] do + local bank = getinfo.bankCRC[nextID] + + local val = bank.readFunc() + bank.nwSet(ply, bank.id, val) + + nextID = net.ReadUInt(32) + end + end) +end + +local cache = {} +local GetConVar_Internal = GetConVar_Internal + +--[[ + @doc + @fname Player:GetInfoDLib + + @args string cvarname + + @returns + string: or nil +]] +function plyMeta:GetInfoDLib(cvarname) + if getinfo.bank[cvarname] then + return getinfo.bank[cvarname].nwGet(self, cvarname) + elseif SERVER then + return self:GetInfo(cvarname) + else + if cache[cvarname] == nil then + cache[cvarname] = GetConVar_Internal(cvarname) + if not cache[cvarname] then + cache[cvarname] = false + end + end + + if not cache[cvarname] then + cache[cvarname] = false + return -- no value, :GetInfo() can return "no value" in vanilla + -- even if gmod wiki stands it (always) return string + end + + return cache[cvarname]:GetString() + end +end + +return getinfo diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/gobjectnotation.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/gobjectnotation.lua new file mode 100644 index 0000000..662bb58 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/gobjectnotation.lua @@ -0,0 +1,1177 @@ +local DLib, type, luatype, istable, table, string, error +do + local _obj_0 = _G + DLib, type, luatype, istable, table, string, error = _obj_0.DLib, _obj_0.type, _obj_0.luatype, _obj_0.istable, _obj_0.table, _obj_0.string, _obj_0.error +end +DLib.GON = DLib.GON or { } +local GON = DLib.GON +GON.HashRegistry = GON.HashRegistry or { } +GON.Registry = GON.Registry or { } +GON.IdentityRegistry = GON.IdentityRegistry or { } +GON.FindProvider = function(identity) + local provider = GON.IdentityRegistry[identity] + return provider or false +end +GON.RemoveProvider = function(provider) + local identity = provider:GetIdentity() + local identify = provider:LuaTypeIdentify() + GON.IdentityRegistry[identity] = nil + if istable(identify) then + for _index_0 = 1, #identify do + local i = identify[_index_0] + GON.HashRegistry[i] = nil + end + elseif isstring(identify) then + GON.HashRegistry[identify] = nil + end + for i, provider2 in ipairs(GON.Registry) do + if provider2:GetIdentity() == identity then + GON.Registry[i] = nil + return + end + end +end +GON.RegisterProvider = function(provider) + local identity = provider:GetIdentity() + local identify = provider:LuaTypeIdentify() + local should_put = provider:ShouldPutIntoMainRegistry() + GON.IdentityRegistry[identity] = provider + if istable(identify) then + for _index_0 = 1, #identify do + local i = identify[_index_0] + GON.HashRegistry[i] = provider + end + elseif isstring(identify) then + GON.HashRegistry[identify] = provider + end + if should_put then + for i, provider2 in ipairs(GON.Registry) do + if provider2:GetIdentity() == identity then + GON.Registry[i] = provider + return + end + end + table.insert(GON.Registry, provider) + end +end +do + local _class_0 + local _base_0 = { + SetValue = function(self, value) + self.value = value + return self + end, + Serialize = function(self) + return error('Not implemented') + end, + GetValue = function(self) + return self.value + end, + GetStructure = function(self) + return self.structure + end, + GetHeapID = function(self) + return self.heapid + end, + GetIdentity = function(self) + return self.__class:GetIdentity() + end, + IsKnownValue = function(self) + return true + end, + GetRegistryID = function(self) + return self._identity_id + end, + IsInstantValue = function(self) + return true + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, structure, id) + self.structure = structure + self.heapid = id + end, + __base = _base_0, + __name = "IDataProvider" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.LuaTypeIdentify = function(self) + return self._IDENTIFY + end + self.ShouldPutIntoMainRegistry = function(self) + return self:LuaTypeIdentify() == nil + end + self.GetIdentity = function(self) + return error('Not implemented') + end + self.Ask = function(self, value, ltype) + if ltype == nil then + ltype = luatype(value) + end + local identify = self:LuaTypeIdentify() + if istable(identify) == 'table' then + return table.qhasValue(identify, ltype) + else + return ltype == identify + end + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return error('Not implemented') + end + GON.IDataProvider = _class_0 +end +do + local _class_0 + local _base_0 = { + GetHeapID = function(self) + return self.heapid + end, + Length = function(self) + return #self.data + end, + IsKnownValue = function(self) + return false + end, + BinaryData = function(self) + return self.data + end, + GetRegistryID = function(self) + return self.registryid + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, structure, data, id, registryid) + self.structure = structure + self.data = data + self.heapid = id + self.registryid = registryid + end, + __base = _base_0, + __name = "UnknownValue" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + GON.UnknownValue = _class_0 +end +do + local _class_0 + local _base_0 = { + GetHeapValue = function(self, id) + return self.heap[id] + end, + NextHeapIdentifier = function(self) + local ret = self.nextid + self.nextid = self.nextid + 1 + return ret + end, + FindInHeap = function(self, value) + if self._heap then + return self._heap[value] or false + end + local _list_0 = self.heap + for _index_0 = 1, #_list_0 do + local provider = _list_0[_index_0] + if provider and provider:IsKnownValue() and provider:GetValue() == value then + return provider + end + end + return false + end, + GetIdentityID = function(self, identity) + local _get = self._identity_registry[identity] + if _get then + return _get + end + _get = self.next_reg_id + if _get >= 0x100 then + error('Too many types in a single file! 255 is the maximum!') + end + self.identity_registry[_get] = identity + self._identity_registry[identity] = _get + self.next_reg_id = self.next_reg_id + 1 + return _get + end, + AddToHeap = function(self, value) + do + local provider = self:FindInHeap(value) + if provider then + return provider + end + end + local ltype = luatype(value) + local provider = GON.HashRegistry[ltype] + if not provider then + local _list_0 = GON.Registry + for _index_0 = 1, #_list_0 do + local prov = _list_0[_index_0] + if prov:Ask(value, ltype) then + provider = prov + break + end + end + end + if not provider then + return false, self.__class.ERROR_MISSING_PROVIDER + end + local identity = provider:GetIdentity() + if not identity then + return false, self.__class.ERROR_NO_IDENTIFIER + end + local iid = self:GetIdentityID(identity) + local id = self:NextHeapIdentifier() + local serialized = provider(self, id) + serialized._identity_id = iid + self.heap[id] = serialized + if self._heap then + self._heap[value] = serialized + end + if not self.root then + self.root = serialized + end + serialized:SetValue(value) + return serialized + end, + SetRoot = function(self, provider) + if not istable(provider) or not provider.GetHeapID then + error('Provider must be GON.IDataProvider! typeof ' .. luatype(provider)) + end + if self.heap[provider:GetHeapID()] ~= provider then + error('Given provider is not part of this structure heap') + end + self.root = provider + end, + IsHeapBig = function(self) + return self.long_heap or #self.heap >= 0xFFFF + end, + WriteHeader = function(self, bytesbuffer) + bytesbuffer:WriteBinary('\xF7\x7FDLib.GON\x00\x02') + bytesbuffer:WriteUByte(self.next_reg_id - 1) + for i = 0, self.next_reg_id - 1 do + bytesbuffer:WriteString(self.identity_registry[i]) + end + end, + WriteHeap = function(self, bytesbuffer) + bytesbuffer:WriteUInt32(#self.heap) + local _list_0 = self.heap + for _index_0 = 1, #_list_0 do + local provider = _list_0[_index_0] + bytesbuffer:WriteUByte(provider:GetRegistryID()) + if provider:IsKnownValue() then + bytesbuffer:WriteUInt16(0) + local pos = bytesbuffer:Tell() + provider:Serialize(bytesbuffer) + local pos2 = bytesbuffer:Tell() + local len = pos2 - pos + bytesbuffer:Move(-len - 2) + bytesbuffer:WriteUInt16(len) + bytesbuffer:Move(len) + else + bytesbuffer:WriteUInt16(provider:Length()) + bytesbuffer:WriteBinary(provider:BinaryData()) + end + end + end, + WriteRoot = function(self, bytesbuffer) + bytesbuffer:WriteUByte(self.root and 1 or 0) + if self.root and self:IsHeapBig() then + bytesbuffer:WriteUInt32(self.root:GetHeapID()) + end + if self.root and not self:IsHeapBig() then + return bytesbuffer:WriteUInt16(self.root:GetHeapID()) + end + end, + ReadHeader = function(self, bytesbuffer) + local read = bytesbuffer:ReadBinary(12) + self.long_heap = false + if read == '\xF7\x7FDLib.GON\x00\x01' then + self.identity_registry = { } + self._identity_registry = { } + self.next_reg_id = bytesbuffer:ReadUByte() + 1 + for i = 0, self.next_reg_id - 1 do + read = bytesbuffer:ReadString() + self.identity_registry[i] = read + self._identity_registry[read] = i + end + self.long_heap = true + return true + elseif read == '\xF7\x7FDLib.GON\x00\x02' then + self.identity_registry = { } + self._identity_registry = { } + self.next_reg_id = bytesbuffer:ReadUByte() + 1 + for i = 0, self.next_reg_id - 1 do + read = bytesbuffer:ReadString() + self.identity_registry[i] = read + self._identity_registry[read] = i + end + return true + end + return false + end, + ReadHeap = function(self, bytesbuffer) + self.heap = { } + self.nextid = 1 + local amount = bytesbuffer:ReadUInt32() + for i = 1, amount do + local heapid = self.nextid + self.nextid = self.nextid + 1 + local iid = bytesbuffer:ReadUByte() + local regid = self.identity_registry[iid] + local provider + if regid then + provider = GON.FindProvider(regid) + end + local len = bytesbuffer:ReadUInt16() + if not provider then + self.heap[heapid] = GON.UnknownValue(self, bytesbuffer:ReadBinary(len), heapid, iid) + else + local pos1 = bytesbuffer:Tell() + local p = provider:Deserialize(bytesbuffer, self, heapid, len) + self.heap[heapid] = p + p._identity_id = iid + if self._heap and p:IsInstantValue() then + self._heap[p:GetValue()] = p + end + local pos2 = bytesbuffer:Tell() + if (pos2 - pos1) ~= len then + error('provider read more or less than required (' .. (pos2 - pos1) .. ' vs ' .. len .. ')') + end + end + end + end, + ReadRoot = function(self, bytesbuffer) + local has_root = bytesbuffer:ReadUByte() == 1 + if has_root then + if self:IsHeapBig() then + self.root = self.heap[bytesbuffer:ReadUInt32()] + end + if not self:IsHeapBig() then + self.root = self.heap[bytesbuffer:ReadUInt16()] + end + else + self.root = nil + end + end, + WriteFile = function(self, bytesbuffer) + self:WriteHeader(bytesbuffer) + self:WriteHeap(bytesbuffer) + self:WriteRoot(bytesbuffer) + return bytesbuffer + end, + ReadFile = function(self, bytesbuffer) + self:ReadHeader(bytesbuffer) + self:ReadHeap(bytesbuffer) + self:ReadRoot(bytesbuffer) + return self + end, + CreateBuffer = function(self) + local bytesbuffer = DLib.BytesBuffer() + return self:WriteFile(bytesbuffer) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, lowmem) + if lowmem == nil then + lowmem = false + end + self.lowmem = lowmem + self.nextid = 1 + self.heap = { } + if not lowmem then + self._heap = { } + end + self.next_reg_id = 0 + self.identity_registry = { } + self._identity_registry = { } + self.long_heap = false + end, + __base = _base_0, + __name = "Structure" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.ERROR_MISSING_PROVIDER = 0 + self.ERROR_NO_IDENTIFIER = 1 + GON.Structure = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteBinary(self.value) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "StringProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'string' + self.GetIdentity = function(self) + return 'builtin:string' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.StringProvider(structure, heapid):SetValue(bytesbuffer:ReadBinary(length)) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.StringProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteDouble(self.value) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "NumberProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'number' + self.GetIdentity = function(self) + return 'builtin:number' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.NumberProvider(structure, heapid):SetValue(bytesbuffer:ReadDouble()) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.NumberProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteUByte(self.value and 1 or 0) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "BooleanProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'boolean' + self.GetIdentity = function(self) + return 'builtin:boolean' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.BooleanProvider(structure, heapid):SetValue(bytesbuffer:ReadUByte() == 1) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.BooleanProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + SetSerializedValue = function(self, value) + self._serialized = value + self.was_serialized = true + self.value = nil + end, + IsInstantValue = function(self) + return false + end, + Rehash = function(self, value, preserveUnknown) + if value == nil then + value = self.value + end + if preserveUnknown == nil then + preserveUnknown = true + end + if self.value and self.structure._heap then + self.structure._heap[self.value] = nil + end + self.value = value + if self.structure._heap then + self.structure._heap[value] = self + end + local copy = self._serialized + self._serialized = { } + if preserveUnknown then + for key, value in pairs(copy) do + local _key = self.structure:GetHeapValue(key) + local _value = self.structure:GetHeapValue(value) + if _key and _value and (not _key:IsKnownValue() or not _value:IsKnownValue()) then + self._serialized[key] = value + end + end + end + for key, value in pairs(value) do + local keyHeap = self.structure:AddToHeap(key) + if keyHeap then + local keyValue = self.structure:AddToHeap(value) + if keyValue then + self._serialized[keyHeap:GetHeapID()] = keyValue:GetHeapID() + end + end + end + return self + end, + SetValue = function(self, value) + self:Rehash(value, false) + self.was_serialized = false + end, + GetValue = function(self) + if not self.was_serialized then + return self.value + end + if self.value and self.structure._heap then + self.structure._heap[self.value] = nil + end + self.value = { } + if self.structure._heap then + self.structure._heap[self.value] = self + end + self.was_serialized = false + for key, value in pairs(self._serialized) do + local _key = self.structure:GetHeapValue(key) + local _value = self.structure:GetHeapValue(value) + if _key and _value and _key:IsKnownValue() and _value:IsKnownValue() then + self.value[_key:GetValue()] = _value:GetValue() + end + end + return self.value + end, + Serialize = function(self, bytesbuffer) + local long_heap = self.structure:IsHeapBig() + for key, value in pairs(self._serialized) do + if long_heap then + bytesbuffer:WriteUInt32(key) + bytesbuffer:WriteUInt32(value) + else + bytesbuffer:WriteUInt16(key) + bytesbuffer:WriteUInt16(value) + end + end + if long_heap then + bytesbuffer:WriteUInt32(0) + end + if not long_heap then + return bytesbuffer:WriteUInt16(0) + end + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TableProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'table' + self.GetIdentity = function(self) + return 'builtin:table' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + local long_heap = structure:IsHeapBig() + local obj = GON.TableProvider(structure, heapid) + local _serialized = { } + if long_heap then + while true do + local readKey = bytesbuffer:ReadUInt32() + if readKey == 0 then + break + end + local readValue = bytesbuffer:ReadUInt32() + if readValue == 0 then + break + end + _serialized[readKey] = readValue + end + else + while true do + local readKey = bytesbuffer:ReadUInt16() + if readKey == 0 then + break + end + local readValue = bytesbuffer:ReadUInt16() + if readValue == 0 then + break + end + _serialized[readKey] = readValue + end + end + obj:SetSerializedValue(_serialized) + return obj + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.TableProvider = _class_0 +end +GON.RegisterProvider(GON.StringProvider) +GON.RegisterProvider(GON.NumberProvider) +GON.RegisterProvider(GON.BooleanProvider) +GON.RegisterProvider(GON.TableProvider) +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + bytesbuffer:WriteDouble(self.value.x) + bytesbuffer:WriteDouble(self.value.y) + return bytesbuffer:WriteDouble(self.value.z) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "VectorProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'Vector' + self.GetIdentity = function(self) + return 'gmod:Vector' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.VectorProvider(structure, heapid):SetValue(Vector(bytesbuffer:ReadDouble(), bytesbuffer:ReadDouble(), bytesbuffer:ReadDouble())) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.VectorProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + bytesbuffer:WriteFloat(self.value.x) + bytesbuffer:WriteFloat(self.value.y) + return bytesbuffer:WriteFloat(self.value.z) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "AngleProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'Angle' + self.GetIdentity = function(self) + return 'gmod:Angle' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.AngleProvider(structure, heapid):SetValue(Angle(bytesbuffer:ReadFloat(), bytesbuffer:ReadFloat(), bytesbuffer:ReadFloat())) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.AngleProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + bytesbuffer:WriteUByte(self.value.r) + bytesbuffer:WriteUByte(self.value.g) + bytesbuffer:WriteUByte(self.value.b) + return bytesbuffer:WriteUByte(self.value.a) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "ColorProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'Color' + self.GetIdentity = function(self) + return 'dlib:Color' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.ColorProvider(structure, heapid):SetValue(Color(bytesbuffer:ReadUByte(), bytesbuffer:ReadUByte(), bytesbuffer:ReadUByte(), bytesbuffer:ReadUByte())) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.ColorProvider = _class_0 +end +GON.RegisterProvider(GON.VectorProvider) +GON.RegisterProvider(GON.AngleProvider) +GON.RegisterProvider(GON.ColorProvider) +local writeVector +writeVector = function(vec, bytesbuffer) + bytesbuffer:WriteFloat(vec.x) + bytesbuffer:WriteFloat(vec.y) + return bytesbuffer:WriteFloat(vec.z) +end +local readVector +readVector = function(bytesbuffer) + return Vector(bytesbuffer:ReadFloat(), bytesbuffer:ReadFloat(), bytesbuffer:ReadFloat()) +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteString(self.value:GetName()) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "ConVarProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'ConVar' + self.GetIdentity = function(self) + return 'gmod:ConVar' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.ConVarProvider(structure, heapid):SetValue(ConVar(bytesbuffer:ReadString())) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.ConVarProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + local tab = self.value:ToTable() + for _index_0 = 1, #tab do + local row = tab[_index_0] + bytesbuffer:WriteDouble(row[1]) + bytesbuffer:WriteDouble(row[2]) + bytesbuffer:WriteDouble(row[3]) + bytesbuffer:WriteDouble(row[4]) + end + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "VMatrixProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'VMatrix' + self.GetIdentity = function(self) + return 'gmod:VMatrix' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + local tab = { } + for i = 1, 4 do + table.insert(tab, { + bytesbuffer:ReadDouble(), + bytesbuffer:ReadDouble(), + bytesbuffer:ReadDouble(), + bytesbuffer:ReadDouble() + }) + end + return GON.VMatrixProvider(structure, heapid):SetValue(Matrix(tab)) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.VMatrixProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteString(self.value:GetName()) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "MaterialProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = 'IMaterial' + self.GetIdentity = function(self) + return 'gmod:IMaterial' + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + return GON.MaterialProvider(structure, heapid):SetValue(Material(bytesbuffer:ReadString()), nil) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.MaterialProvider = _class_0 +end +do + local _class_0 + local _parent_0 = GON.IDataProvider + local _base_0 = { + Serialize = function(self, bytesbuffer) + return self.__class:Write(self.value, bytesbuffer) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "CTakeDamageInfoProvider", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self._IDENTIFY = { + 'CTakeDamageInfo', + 'LTakeDamageInfo' + } + self.GetIdentity = function(self) + return 'dlib:LTakeDamageInfo' + end + self.Write = function(self, obj, bytesbuffer) + do + local _with_0 = bytesbuffer + _with_0:WriteDouble(obj:GetDamage()) + _with_0:WriteDouble(obj:GetBaseDamage()) + _with_0:WriteDouble(obj:GetMaxDamage()) + _with_0:WriteDouble(obj:GetDamageBonus()) + _with_0:WriteUInt32(obj:GetDamageCustom()) + _with_0:WriteUInt32(obj:GetAmmoType()) + writeVector(obj:GetDamagePosition(), bytesbuffer) + writeVector(obj:GetDamageForce(), bytesbuffer) + writeVector(obj:GetReportedPosition(), bytesbuffer) + _with_0:WriteUInt32(obj:GetDamageType()) + return _with_0 + end + end + self.Read = function(self, obj, bytesbuffer) + do + local _with_0 = bytesbuffer + obj:SetDamage(_with_0:ReadDouble()) + obj:SetBaseDamage(_with_0:ReadDouble()) + obj:SetMaxDamage(_with_0:ReadDouble()) + obj:SetDamageBonus(_with_0:ReadDouble()) + obj:SetDamageCustom(_with_0:ReadUInt32()) + obj:SetAmmoType(_with_0:ReadUInt32()) + obj:SetDamagePosition(readVector(bytesbuffer)) + obj:SetDamageForce(readVector(bytesbuffer)) + obj:SetReportedPosition(readVector(bytesbuffer)) + obj:SetDamageType(_with_0:ReadUInt32()) + return _with_0 + end + end + self.Deserialize = function(self, bytesbuffer, structure, heapid, length) + local obj = DLib.LTakeDamageInfo() + self:Read(obj, bytesbuffer) + return GON.CTakeDamageInfoProvider(structure, heapid):SetValue(obj) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + GON.CTakeDamageInfoProvider = _class_0 +end +GON.RegisterProvider(GON.ConVarProvider) +GON.RegisterProvider(GON.VMatrixProvider) +GON.RegisterProvider(GON.MaterialProvider) +GON.RegisterProvider(GON.CTakeDamageInfoProvider) +GON.Serialize = function(value, toBuffer) + if toBuffer == nil then + toBuffer = true + end + local struct = GON.Structure() + struct:AddToHeap(value) + if not toBuffer then + return struct + end + return struct:CreateBuffer() +end +GON.Deserialize = function(bufferIn, retreiveValue) + if retreiveValue == nil then + retreiveValue = true + end + local struct = GON.Structure() + struct:ReadFile(bufferIn) + if not retreiveValue then + return struct + end + if not struct.root then + return + end + return struct.root:GetValue() +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/hook.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/hook.lua new file mode 100644 index 0000000..e7a5b40 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/hook.lua @@ -0,0 +1,1555 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +-- performance and functionality to the core + +jit.on() + +local pairs = pairs +local ipairs = ipairs +local print = print +local debug = debug +local tostring = tostring +local tonumber = tonumber +local type = type +local traceback = debug.traceback +local DLib = DLib +local unpack = unpack +local gxpcall = xpcall +local SysTime = SysTime + +DLib.hook = DLib.hook or {} +local ghook = _G.hook +local hook = DLib.hook + +hook.PROFILING = false +hook.PROFILING_RESULTS_EXISTS = false +hook.PROFILE_STARTED = 0 +hook.PROFILE_ENDS = 0 + +hook.__tableOptimized = hook.__tableOptimized or {} +hook.__table = hook.__table or {} +hook.__tableGmod = hook.__tableGmod or {} +hook.__tableModifiersPost = hook.__tableModifiersPost or {} +hook.__tableModifiersPostOptimized = hook.__tableModifiersPostOptimized or {} +hook.__disabled = hook.__disabled or {} + +local __table = hook.__table +local __disabled = hook.__disabled +local __tableOptimized = hook.__tableOptimized +local __tableGmod = hook.__tableGmod +local __tableModifiersPost = hook.__tableModifiersPost +local __tableModifiersPostOptimized = hook.__tableModifiersPostOptimized + +-- ULib compatibility +-- ugh +_G.HOOK_MONITOR_HIGH = -2 +_G.HOOK_HIGH = -1 +_G.HOOK_NORMAL = 0 +_G.HOOK_LOW = 1 +_G.HOOK_MONITOR_LOW = 2 + +local maximalPriority = -10 +local minimalPriority = 10 + +--[[ + @doc + @fname hook.GetTable + @replaces + @returns + table: `table>` +]] +local function GetTable() + return __tableGmod +end + +--[[ + @doc + @fname hook.GetDLibOptimizedTable + @returns + table: of `eventName -> array of functions` +]] +function hook.GetDLibOptimizedTable() + return __tableOptimized +end + +--[[ + @doc + @fname hook.GetDLibModifiers + @returns + table +]] +function hook.GetDLibModifiers() + return __tableModifiersPost +end + +--[[ + @doc + @fname hook.GetDLibSortedTable + @returns + table +]] +function hook.GetDLibSortedTable() + return __tableOptimized +end + +--[[ + @doc + @replaces + @fname hook.GetULibTable + + @desc + For mods like DarkRP + Althrough, DarkRP can use DLib's Post Modifiers instead for things that + DarkRP currently do with table provided by `GetULibTable` + @enddesc + + @returns + table +]] +function hook.GetULibTable() + return __table +end + +--[[ + @doc + @fname hook.GetDLibTable + @returns + table +]] +function hook.GetDLibTable() + return __table +end + +--[[ + @doc + @fname hook.GetDisabledHooks + @returns + table +]] +function hook.GetDisabledHooks() + return __disabled +end + +local oldHooks + +if ghook ~= DLib.ghook then + if ghook.GetULibTable then + oldHooks = ghook.GetULibTable() + else + oldHooks = {} + + for event, eventData in pairs(ghook.GetTable()) do + oldHooks[event] = {} + oldHooks[event][0] = {} + local target = oldHooks[event][0] + + for hookID, hookFunc in pairs(eventData) do + target[hookID] = {fn = hookFunc} + end + end + end +end + +local function transformStringID(stringID, event) + if type(stringID) == 'number' then + stringID = tostring(stringID) + end + + if type(stringID) == 'boolean' then + error('booleans are not allowed as hookID value', 3) + end + + if type(stringID) ~= 'string' then + local success = pcall(function() + stringID.IsValid(stringID) + end) + + if not success then + --if DLib.DEBUG_MODE:GetBool() then + --DLib.Message(traceback('hook.Add - hook ID is not a string and not a valid object! Using tostring() instead. ' .. type(stringID))) + error('hook.Add - hook ID is not a string and not a valid object! ' .. type(stringID), 3) + --end + stringID = tostring(stringID) + end + end + + return stringID +end + +--[[ + @doc + @fname hook.DisableHook + @args string event, any hookID + + @returns + boolean +]] +function hook.DisableHook(event, stringID) + assert(type(event) == 'string', 'hook.DisableHook - event is not a string! ' .. type(event)) + + if not stringID then + if __disabled[event] then + return false + end + + __disabled[event] = true + return true + else + if not __table[event] then return false end + stringID = transformStringID(stringID, event) + + for priority = -10, 10 do + if __table[event][priority] and __table[event][priority][stringID] then + local wasDisabled = __table[event][priority][stringID].disabled + __table[event][priority][stringID].disabled = true + hook.Reconstruct(event) + return not wasDisabled + end + end + + return false + end +end + +--[[ + @doc + @fname hook.DisableAllHooksExcept + @args string event, any hookID + + @returns + boolean +]] +function hook.DisableAllHooksExcept(event, stringID) + assert(type(event) == 'string', 'hook.DisableAllHooksExcept - event is not a string! ' .. type(event)) + assert(type(stringID) ~= 'nil', 'hook.DisableAllHooksExcept - ID is not a valid value! ' .. type(stringID)) + + if not __table[event] then return false end + stringID = transformStringID(stringID, event) + + for priority = -10, 10 do + if __table[event][priority] then + for id, hookData in pairs(__table[event][priority]) do + if id ~= stringID then + hookData.disabled = true + end + end + end + end + + hook.Reconstruct(event) + return true +end + +--[[ + @doc + @fname hook.DisableHooksByPredicate + @args string event, function predicate + + @desc + Predicate should return true to disable hook + and return false to enable hook + Arguments passed to predicate are `event, id, priority, dlibHookData` + @enddesc + + @returns + boolean +]] +function hook.DisableHooksByPredicate(event, predicate) + assert(type(event) == 'string', 'hook.DisableAllHooksByPredicate - event is not a string! ' .. type(event)) + assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate)) + + if not __table[event] then return false end + + for priority = -10, 10 do + if __table[event][priority] then + for id, hookData in pairs(__table[event][priority]) do + local reply = predicate(event, id, priority, hookData) + + if reply then + hookData.disabled = true + end + end + end + end + + hook.Reconstruct(event) + return true +end + +--[[ + @doc + @fname hook.DisableAllHooksByPredicate + @args function predicate + + @desc + same as `hook.DisableHooksByPredicate`, except it is ran for all events + pass `function() return true end` as predicate to disable **EVERYTHING** + @enddesc + + @returns + boolean +]] +function hook.DisableAllHooksByPredicate(predicate) + assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate)) + + for event, eventData in pairs(__table) do + for priority = -10, 10 do + if eventData[priority] then + for id, hookData in pairs(eventData[priority]) do + local reply = predicate(event, id, priority, hookData) + + if reply then + hookData.disabled = true + end + end + end + end + + hook.Reconstruct(event) + end + + return true +end + +--[[ + @doc + @fname hook.EnableHooksByPredicate + @args string event, function predicate + + @desc + counterpart of `hook.DisableHooksByPredicate` + @enddesc + + @returns + boolean +]] +function hook.EnableHooksByPredicate(event, predicate) + assert(type(event) == 'string', 'hook.DisableAllHooksByPredicate - event is not a string! ' .. type(event)) + assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate)) + + if not __table[event] then return false end + + for priority = -10, 10 do + if __table[event][priority] then + for id, hookData in pairs(__table[event][priority]) do + local reply = predicate(event, id, priority, hookData) + + if reply then + hookData.disabled = false + end + end + end + end + + hook.Reconstruct(event) + return true +end + +--[[ + @doc + @fname hook.EnableAllHooksByPredicate + @args function predicate + + @desc + counterpart of `hook.DisableAllHooksByPredicate` + @enddesc + + @returns + boolean +]] +function hook.EnableAllHooksByPredicate(predicate) + assert(type(predicate) == 'function', 'hook.DisableAllHooksByPredicate - invalid predicate function! ' .. type(predicate)) + + for event, eventData in pairs(__table) do + for priority = -10, 10 do + if eventData[priority] then + for id, hookData in pairs(eventData[priority]) do + local reply = predicate(event, id, priority, hookData) + + if reply then + hookData.disabled = false + end + end + end + end + + hook.Reconstruct(event) + end + + return true +end + +--[[ + @doc + @fname hook.EnableAllHooks + + @returns + table: enabled hooks (copy of __disabled) +]] +function hook.EnableAllHooks() + local toenable = {} + + for k, v in pairs(__disabled) do + table.insert(toenable, k) + end + + for i, v in ipairs(toenable) do + __disabled[v] = nil + end + + for event, eventData in pairs(__table) do + for priority = -10, 10 do + if eventData[priority] then + for id, hookData in pairs(eventData[priority]) do + hookData.disabled = false + end + end + end + + hook.Reconstruct(event) + end + + return toenable +end + +--[[ + @doc + @fname hook.EnableHook + @args string event, any hookID + + @returns + boolean +]] +function hook.EnableHook(event, stringID) + assert(type(event) == 'string', 'hook.EnableHook - event is not a string! ' .. type(event)) + + if not stringID then + if not __disabled[event] then + return false + end + + __disabled[event] = nil + return true + end + + if not __table[event] then return false end + stringID = transformStringID(stringID, event) + + for priority = -10, 10 do + if __table[event][priority] and __table[event][priority][stringID] then + local wasDisabled = __table[event][priority][stringID].disabled + __table[event][priority][stringID].disabled = false + hook.Reconstruct(event) + return wasDisabled + end + end + + return false +end + +--[[ + @doc + @fname hook.Add + @replaces + @args string event, any hookID, function callback, number priority = 0 + + @desc + Refer to !g:hook.Add for main information + `priority` can be a number within range of -10 to 10 inclusive + `hookID` **can** be a number, unlike default gmod behavior, but can not be a boolean + prints tracebacks when some of arguments are invalid instead of silent fail, unlike original hook + throws an error when something goes horribly wrong instead of silent fail, unlike original hook + if priority argument is omitted, then it uses `0` as priority (if hook was not defined before) + and use previous priority if hook already exists (assuming we want to overwrite old hook definition) unlike ULib's hook + this can be useful with software which can provide hook benchmarking by re-defining every single hook using hook.Add + and it doesn't know about hook priorities + @enddesc +]] +function hook.Add(event, stringID, funcToCall, priority) + if type(event) ~= 'string' then + --if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('hook.Add - event is not a string! ' .. type(event))) + --end + + return + end + + __table[event] = __table[event] or {} + + if type(funcToCall) ~= 'function' then + --if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('hook.Add - function is not a function! ' .. type(funcToCall))) + --end + + return + end + + stringID = transformStringID(stringID, event) + + for priority = maximalPriority, minimalPriority do + local eventsTable = __table[event][priority] + + if eventsTable and eventsTable[stringID] then + if not priority then + priority = eventsTable[stringID].priority + end + + eventsTable[stringID] = nil + end + end + + priority = priority or 0 + + if type(priority) ~= 'number' then + priority = tonumber(priority) or 0 + end + + priority = math.Clamp(math.floor(priority), maximalPriority, minimalPriority) + + local hookData = { + event = event, + priority = priority, + funcToCall = funcToCall, + fn = funcToCall, -- ULib + isstring = type(stringID) == 'string', -- ULib + id = stringID, + idString = tostring(stringID), + registeredAt = SysTime(), + typeof = type(stringID) == 'string' + } + + __table[event][priority] = __table[event][priority] or {} + __table[event][priority][stringID] = hookData + __tableGmod[event] = __tableGmod[event] or {} + __tableGmod[event][stringID] = funcToCall + + hook.Reconstruct(event) + return +end + +hook._O_SALT = hook._O_SALT or -0xFFFFFFF + +--[[ + @doc + @fname hook.Once + @replaces + @args string event, function callback, number priority = 0 + + @desc + `hook.Add`, but function would be called back only once. + @enddesc +]] +function hook.Once(event, funcToCall, priority) + hook._O_SALT = hook._O_SALT + 1 + local id = 'hook.Once.' .. hook._O_SALT + + hook.Add(event, id, function(...) + hook.Remove(event, id) + return funcToCall(...) + end, priority) +end + +--[[ + @doc + @fname hook.Remove + @replaces + @args string event, any hookID +]] +function hook.Remove(event, stringID) + if type(event) ~= 'string' then + --if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('hook.Remove - event is not a string! ' .. type(event))) + --end + + return + end + + if type(stringID) == 'nil' then + --if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('hook.Remove - hook id is nil!')) + --end + + return + end + + if not __table[event] then return end + __tableGmod[event] = __tableGmod[event] or {} + + stringID = transformStringID(stringID, event) + + __tableGmod[event][stringID] = nil + + for priority = maximalPriority, minimalPriority do + local eventsTable = __table[event][priority] + + if eventsTable ~= nil then + local oldData = eventsTable[stringID] + if oldData ~= nil then + eventsTable[stringID] = nil + hook.Reconstruct(event) + return + end + end + end +end + +--[[ + @doc + @fname hook.AddPostModifier + @args string event, any hookID, function callback + + @desc + Unique feature of DLib hook + This allows you to define "post-hooks" + hooks which transform data returned by previous hook + Hooks bound to event will receive values returned by upvalue hook. + **This is meant for advanced users only. Use with care and don't try anything stupid!** + If you somehow don't want to mess with passed arguments, **you must return them back** + otherwise engine/hook.Run caller/other post modifiers will receive empty values, like none of hooks returned values + this can be useful for doing "final fixes" to data + like custom final logic for PlayerSay (local chat/roleyplay for example) or fixes to CalcView + using this will not affect admin/fun mods (they will use hook (e.g. PlayerSay) as usual) + and final fixes will always work too despite type of hook + *Limitation of this hook is that it can not see original arguments passed to* `hook.Run` + @enddesc + + @returns + boolean + table: hookData +]] +function hook.AddPostModifier(event, stringID, funcToCall) + __tableModifiersPost[event] = __tableModifiersPost[event] or {} + + if type(event) ~= 'string' then + --if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('hook.AddPostModifier - event is not a string! ' .. type(event))) + --end + + return false + end + + if type(funcToCall) ~= 'function' then + --if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('hook.AddPostModifier - function is not a function! ' .. type(funcToCall))) + --end + + return false + end + + stringID = transformStringID(stringID, event) + + local hookData = { + event = event, + funcToCall = funcToCall, + id = stringID, + idString = tostring(stringID), + registeredAt = SysTime(), + typeof = type(stringID) == 'string' + } + + __tableModifiersPost[event][stringID] = hookData + hook.ReconstructPostModifiers(event) + return true, hookData +end + +--[[ + @doc + @fname hook.RemovePostModifier + @args string event, any hookID + + @returns + boolean + table: hookData +]] +function hook.RemovePostModifier(event, stringID) + if not __tableModifiersPost[event] then return false end + + stringID = transformStringID(stringID, event) + if __tableModifiersPost[event][stringID] then + local old = __tableModifiersPost[event][stringID] + __tableModifiersPost[event][stringID] = nil + hook.ReconstructPostModifiers(event) + return true, old + end + + return false +end + +--[[ + @doc + @fname hook.ReconstructPostModifiers + @args string event + + @internal + + @desc + builds optimized hook table + @enddesc + + @returns + table: sorted array of functions + table: sorted array of hookData +]] +function hook.ReconstructPostModifiers(eventToReconstruct) + if not eventToReconstruct then + for event, tab in pairs(__tableModifiersPost) do + hook.ReconstructPostModifiers(event) + end + + return + end + + __tableModifiersPostOptimized[eventToReconstruct] = {} + local event = __tableModifiersPost[eventToReconstruct] + + local ordered = {} + + if event then + for stringID, hookData in pairs(event) do + if hookData.typeof == false then + if hookData.id:IsValid() then + table.insert(ordered, hookData) + else + event[stringID] = nil + end + else + table.insert(ordered, hookData) + end + end + end + + local cnt = #ordered + + if cnt == 0 then + __tableModifiersPostOptimized[eventToReconstruct] = nil + else + local target = __tableModifiersPostOptimized[eventToReconstruct] + + for i = 1, cnt do + table.insert(target, ordered[i].funcToCall) + end + end + + return __tableModifiersPostOptimized, ordered +end + +--[[ + @doc + @fname hook.ListAllHooks + @args boolean includeDisabled = true + + @returns + table: sorted array of hookData +]] +function hook.ListAllHooks(includeDisabled) + if includeDisabled == nil then includeDisabled = true end + local output = {} + + for event, priorityTable in pairs(__table) do + for priority = maximalPriority, minimalPriority do + local hookList = priorityTable[priority] + + if hookList then + for stringID, hookData in pairs(hookList) do + if not hookData.disabled or includeDisabled then + table.insert(output, hookData) + end + end + end + end + end + + return output +end + +--[[ + @doc + @fname hook.Reconstruct + @args string event + + @internal + + @desc + builds optimized hook table + @enddesc + + @returns + table: sorted array of functions + table: sorted array of hookData +]] +function hook.Reconstruct(eventToReconstruct) + if not eventToReconstruct then + for event, data in pairs(__table) do + hook.Reconstruct(event) + end + + return + end + + __tableOptimized[eventToReconstruct] = {} + local ordered = {} + local priorityTable = __table[eventToReconstruct] + local inboundgmod = __tableGmod[eventToReconstruct] + + if priorityTable then + for priority = maximalPriority, minimalPriority do + local hookList = priorityTable[priority] + + if hookList then + for stringID, hookData in pairs(hookList) do + if not hookData.disabled then + if hookData.typeof == false then + if hookData.id:IsValid() then + table.insert(ordered, hookData) + else + hookList[stringID] = nil + inboundgmod[stringID] = nil + end + else + table.insert(ordered, hookData) + end + end + end + end + end + end + + local cnt = #ordered + + if cnt == 0 then + __tableOptimized[eventToReconstruct] = nil + else + local target = __tableOptimized[eventToReconstruct] + + for i = 1, cnt do + local callable + local hookData = ordered[i] + + if type(hookData.id) == 'string' then + callable = hookData.funcToCall + else + local self = hookData.id + local upfuncCallableSelf = hookData.funcToCall + + callable = function(...) + if not self:IsValid() then + hook.Remove(hookData.event, self) + return + end + + return upfuncCallableSelf(self, ...) + end + end + + if hook.PROFILING then + local THIS_RUNTIME = 0 + local THIS_CALLS = 0 + local upfuncProfiled = callable + + callable = function(...) + THIS_CALLS = THIS_CALLS + 1 + local t = SysTime() + local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = upfuncProfiled(...) + local t2 = SysTime() + + THIS_RUNTIME = THIS_RUNTIME + (t2 - t) + return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M + end + + hookData.profileEnds = function() + hookData.THIS_RUNTIME = THIS_RUNTIME + hookData.THIS_CALLS = THIS_CALLS + end + end + + table.insert(target, callable) + end + end + + return __tableOptimized, ordered +end + +local function Call(...) + return hook.Call2(...) +end + +local function Run(...) + return hook.Run2(...) +end + +local __breakage1 = { + 'HUDPaint', + 'PreDrawHUD', + 'PostDrawHUD', + 'Initialize', + 'InitPostEntity', + 'PreGamemodeInit', + 'PostGamemodeInit', + 'PostGamemodeInitialize', + 'PreGamemodeInitialize', + 'PostGamemodeLoaded', + 'PreGamemodeLoaded', + 'PostRenderVGUI', + 'OnGamemodeLoaded', + + 'CreateMove', + 'StartCommand', + 'SetupMove', +} + +local __breakage = {} + +for i, str in ipairs(__breakage1) do + __breakage[str] = true +end + +-- these hooks can't return any values +hook.StaticHooks = __breakage + +--[[ + @doc + @fname hook.HasHooks + @args string event + + @returns + boolean +]] +function hook.HasHooks(event) + return __tableOptimized[event] ~= nil and #__tableOptimized[event] ~= 0 +end + +--[[ + @doc + @fname hook.CallStatic + @args string event, table hookTable, vararg arguments + + @desc + functions called can not interrupt call loop by returning arguments + can not return arguments + internall used to call some of most popular hooks + which will break the game if at least one function in hook list will return value + @enddesc + + @internal +]] +function hook.CallStatic(event, hookTable, ...) + local post = __tableModifiersPostOptimized[event] + local events = __tableOptimized[event] + + if events == nil then + if hookTable == nil then + return + end + + local gamemodeFunction = hookTable[event] + + if gamemodeFunction == nil then + return + end + + return gamemodeFunction(hookTable, ...) + end + + local i = 1 + local nextevent = events[i] + + ::loop:: + nextevent(...) + i = i + 1 + nextevent = events[i] + + if nextevent ~= nil then + goto loop + end + + if hookTable == nil then + return + end + + local gamemodeFunction = hookTable[event] + + if gamemodeFunction == nil then + return + end + + return gamemodeFunction(hookTable, ...) +end + +--[[ + @doc + @fname hook.Call + @replaces + @args string event, table hookTable, vararg arguments + + @returns + vararg: values +]] +function hook.Call2(event, hookTable, ...) + if __disabled[event] then + return + end + + ITERATING = event + + if __breakage[event] == true then + hook.CallStatic(event, hookTable, ...) + return + end + + local post = __tableModifiersPostOptimized[event] + local events = __tableOptimized[event] + + if events == nil then + if hookTable == nil then + return + end + + local gamemodeFunction = hookTable[event] + + if gamemodeFunction == nil then + return + end + + if post == nil then + return gamemodeFunction(hookTable, ...) + end + + local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = gamemodeFunction(hookTable, ...) + local i = 1 + local nextevent = post[i] + + ::post_mloop1:: + Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M) + + i = i + 1 + nextevent = post[i] + + if nextevent ~= nil then + goto post_mloop1 + end + + return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M + end + + local i = 1 + local nextevent = events[i] + + ::loop:: + local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(...) + + if Q ~= nil then + if post == nil then + return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M + end + + local i = 1 + local nextevent = post[i] + + ::post_mloop2:: + Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M) + + i = i + 1 + nextevent = post[i] + + if nextevent ~= nil then + goto post_mloop2 + end + + return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M + end + + i = i + 1 + nextevent = events[i] + + if nextevent ~= nil then + goto loop + end + + if hookTable == nil then + return + end + + local gamemodeFunction = hookTable[event] + + if gamemodeFunction == nil then + return + end + + if post == nil then + return gamemodeFunction(hookTable, ...) + end + + local Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = gamemodeFunction(hookTable, ...) + local i = 1 + local nextevent = post[i] + + ::post_mloop3:: + Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M = nextevent(Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M) + + i = i + 1 + nextevent = post[i] + + if nextevent ~= nil then + goto post_mloop3 + end + + return Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K, L, Z, X, C, V, B, N, M +end + + +--[[ + @doc + @fname hook.Run + @replaces + @args string event, vararg arguments + + @returns + vararg: values +]] + +--[[ + @doc + @fname gamemode.Call + @replaces + @args string event, vararg arguments + + @returns + vararg: values +]] +if gmod then + local GetGamemode = gmod.GetGamemode + + function hook.Run2(event, ...) + return hook.Call2(event, GetGamemode(), ...) + end + + function gamemode.Call(event, ...) + local gm = GetGamemode() + + if gm == nil then return false end + if gm[event] == nil then return false end + + return hook.Call2(event, gm, ...) + end +else + function hook.Run2(event, ...) + return hook.Call2(event, GAMEMODE, ...) + end + + function gamemode.Call() + return false + end +end + +for k, v in pairs(hook) do + if type(v) == 'function' then + hook[k:sub(1, 1):lower() .. k:sub(2)] = v + end +end + +-- Engine permanently remembers function address +-- So we need to transmit the call to our subfunction in order to modify it on the fly (with no runtime costs because JIT is <3) +-- and local "hook" will never point at wrong table + +hook.Call = Call +hook.Run = Run +hook.GetTable = GetTable + +if oldHooks then + for event, priorityTable in pairs(oldHooks) do + for priority, hookTable in pairs(priorityTable) do + for hookID, hookFunc in pairs(hookTable) do + hook.Add(event, hookID, hookFunc.fn, priority) + end + end + end +end + +setmetatable(hook, { + __call = function(self, ...) + return self.Add(...) + end +}) + +if ghook ~= DLib.ghook and ents.GetCount() < 10 then + DLib.ghook = ghook + + for k, v in pairs(ghook) do + rawset(ghook, k, nil) + end + + setmetatable(DLib.ghook, { + __index = hook, + + __newindex = function(self, key, value) + if hook[key] == value then return end + + if DLib.DEBUG_MODE:GetBool() then + DLib.Message(traceback('DEPRECATED: Do NOT mess with hook system directly! https://goo.gl/NDAQqY\nReport this message to addon author which is involved in this stack trace:\nhook.' .. tostring(key) .. ' (' .. tostring(hook[key]) .. ') -> ' .. tostring(value), 2)) + end + + local status = hook.Call('DLibHookChange', nil, key, value) + if status == false then return end + rawset(hook, key, value) + end, + + __call = function(self, ...) + return self.Add(...) + end + }) +elseif ghook ~= DLib.ghook then + function ghook.AddPostModifier() + + end +end + +DLib.benchhook = { + Add = hook.Add, + Call = hook.Call2, + Run = hook.Run2, + Remove = hook.Remove, + GetTable = hook.GetTable, +} + +local function lua_findhooks(eventName, ply) + DLib.MessagePlayer(ply, '----------------------------------') + DLib.MessagePlayer(ply, string.format('Finding %s hooks for event %q', CLIENT and 'CLIENTSIDE' or 'SERVERSIDE', eventName)) + + local tableToUse = __table[eventName] + + if tableToUse and table.Count(tableToUse) ~= 0 then + for priority = maximalPriority, minimalPriority do + local hookList = tableToUse[priority] + + if hookList then + for stringID, hookData in pairs(hookList) do + local info = debug.getinfo(hookData.funcToCall) + DLib.MessagePlayer(ply, + string.format( + '\t\t%q [%s] at %p (%s: %i->%i)', + stringID, + priority, + hookData.funcToCall, + info.source, + info.linedefined, + info.lastlinedefined + ) + ) + end + end + end + else + DLib.MessagePlayer(ply, 'No hooks defined for specified event') + end + + DLib.MessagePlayer(ply, '----------------------------------') +end + +--[[ + @doc + @fname hook.GetDumpStr + + @internal + + @returns + string +]] +function hook.GetDumpStr() + local lines = {} + + local sorted = {} + + for eventName, eventData in pairs(__table) do + table.insert(sorted, eventName) + end + + table.sort(sorted) + + for i, eventName in ipairs(sorted) do + local eventData = __table[eventName] + + for priority = maximalPriority, minimalPriority do + local hookList = eventData[priority] + + if hookList then + local llines = {} + table.insert(lines, '// Begin list hooks of event ' .. eventName) + + for stringID, hookData in pairs(hookList) do + local info = debug.getinfo(hookData.funcToCall) + + table.insert(llines, + string.format( + '\t%q [%s] at %p (%s: %i->%i)', + tostring(stringID), + tostring(priority), + hookData.funcToCall, + info.source, + info.linedefined, + info.lastlinedefined + ) + ) + end + + table.sort(llines) + table.append(lines, llines) + + table.insert(lines, '// End list hooks of event ' .. eventName .. '\n') + end + end + end + + return table.concat(lines, '\n') +end + +local function lua_findhooks_cl(ply, cmd, args) + if not game.SinglePlayer() and IsValid(ply) and ply:IsPlayer() and not ply:IsAdmin() then return end + + if not args[1] then + DLib.Message('No event name were provided!') + return + end + + lua_findhooks(table.concat(args, ' '):trim(), ply) +end + +local function lua_findhooks_sv(ply, cmd, args) + if not game.SinglePlayer() and IsValid(ply) and ply:IsPlayer() and not ply:IsAdmin() then return end + + if not args[1] then + DLib.Message('No event name were provided!') + return + end + + lua_findhooks(table.concat(args, ' '):trim(), ply) +end + +do + local function autocomplete(cmd, args) + args = args:lower():trim() + + if args[1] == '"' then + args = args:sub(2) + end + + if args[#args] == '"' then + args = args:sub(1, #args - 1) + end + + local output = {} + + for k, v in pairs(__tableGmod) do + if k:lower():startsWith(args) then + table.insert(output, cmd .. ' "' .. k .. '"') + end + end + + table.sort(output) + + return output + end + + timer.Simple(0, function() + if CLIENT then + concommand.Add('lua_findhooks_cl', lua_findhooks_cl, autocomplete) + else + concommand.Add('lua_findhooks', lua_findhooks_sv, autocomplete) + end + end) +end + +local function printProfilingResults(ply) + local deftable = {} + + local totalRuntime = 0 + + for i, hookData in ipairs(hook.ListAllHooks(false)) do + deftable[hookData.event] = deftable[hookData.event] or {runtime = 0, calls = 0, list = {}, name = hookData.event} + + table.insert(deftable[hookData.event].list, hookData) + deftable[hookData.event].runtime = deftable[hookData.event].runtime + hookData.THIS_RUNTIME + totalRuntime = totalRuntime + hookData.THIS_RUNTIME + deftable[hookData.event].calls = deftable[hookData.event].calls + hookData.THIS_CALLS + end + + local sortedtable = {} + + for event, eventTable in pairs(deftable) do + table.sort(eventTable.list, function(a, b) + return a.THIS_RUNTIME > b.THIS_RUNTIME + end) + + table.insert(sortedtable, eventTable) + end + + table.sort(sortedtable, function(a, b) + return a.runtime > b.runtime + end) + + DLib.MessagePlayer(ply, '-----------------------------------') + DLib.MessagePlayer(ply, '------ HOOK PROFILING REPORT ------') + DLib.MessagePlayer(ply, '-----------------------------------') + + local time = hook.PROFILE_ENDS - hook.PROFILE_STARTED + + for pos, eventTable in ipairs(sortedtable) do + if pos > 10 then + DLib.MessagePlayer(ply, '... tail of events ... (', #sortedtable - 10, ' are not shown)') + break + end + + DLib.MessagePlayer(ply, '/// ' .. eventTable.name .. ': Runtime position: ', pos, string.format(' (%.2f%% of game runtime); - Total hook calls: ', (eventTable.runtime / time) * 100), eventTable.calls, + string.format('; Total runtime: %.2f milliseconds (~%.2f microseconds per hook call on average)', eventTable.runtime * 1000, (eventTable.runtime * 1000000) / eventTable.calls)) + + for pos2, hookData in ipairs(eventTable.list) do + if hookData.THIS_RUNTIME <= 0.001 then + DLib.MessagePlayer(ply, '(', #eventTable.list - pos2 + 1, ' are not shown)') + break + end + + DLib.MessagePlayer(ply, 'Hook ID: ', hookData.id) + DLib.MessagePlayer(ply, string.format('\t - Runtime: %.2f milliseconds; %i calls; ~%.2f microseconds per call on average', + hookData.THIS_RUNTIME * 1000, hookData.THIS_CALLS, (hookData.THIS_RUNTIME * 1000000) / hookData.THIS_CALLS)) + end + end + + DLib.MessagePlayer(ply, '--') + DLib.MessagePlayer(ply, 'In total, regular hooks took around ', math.floor((totalRuntime / time) * 10000) / 100, '% of game runtime.') + DLib.MessagePlayer(ply, '--') + + DLib.MessagePlayer(ply, '-----------------------------------') + DLib.MessagePlayer(ply, '--- END OF HOOK PROFILING REPORT --') + DLib.MessagePlayer(ply, '-----------------------------------') +end + +if CLIENT then + concommand.Add('dlib_profile_hooks_cl', function(ply, cmd, args) + if hook.PROFILING then + hook.PROFILE_ENDS = SysTime() + hook.PROFILING = false + hook.PROFILING_RESULTS_EXISTS = true + hook.Reconstruct() + + for i, hookData in ipairs(hook.ListAllHooks(false)) do + hookData.profileEnds() + end + + printProfilingResults(LocalPlayer()) + return + end + + hook.PROFILE_STARTED = SysTime() + hook.PROFILING = true + + DLib.Message('Hook profiling were started') + DLib.Message('When you are ready you can type dlib_profile_hooks_cl again') + DLib.Message('/// NOTE THAT RESULTS BECOME MORE ACCURATE AS PROFILING GOES!') + DLib.Message('/// Disabling it too early will produce false results') + hook.Reconstruct() + end) + + concommand.Add('dlib_profile_hooks_last_cl', function(ply, cmd, args) + if not hook.PROFILING_RESULTS_EXISTS then + DLib.Message('No profiling results exists!') + DLib.Message('Start a new one by typing dlib_profile_hooks_cl') + return + end + + printProfilingResults(LocalPlayer()) + end) +else + concommand.Add('dlib_profile_hooks_last_sv', function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then + DLib.MessagePlayer(ply, 'Not a super admin!') + return + end + + if not hook.PROFILING_RESULTS_EXISTS then + DLib.MessagePlayer(ply, 'No profiling results exists!') + DLib.MessagePlayer(ply, 'Start a new one by typing dlib_profile_hooks_cl') + return + end + + DLib.Message(IsValid(ply) and ply or 'Console', ' requested hook profiling results') + printProfilingResults(ply) + end) + + concommand.Add('dlib_profile_hooks_sv', function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then + DLib.MessagePlayer(ply, 'Not a super admin!') + return + end + + if hook.PROFILING then + DLib.Message(IsValid(ply) and ply or 'Console', ' stopped hook profiling') + hook.PROFILE_ENDS = SysTime() + hook.PROFILING = false + hook.PROFILING_RESULTS_EXISTS = true + hook.Reconstruct() + + for i, hookData in ipairs(hook.ListAllHooks(false)) do + hookData.profileEnds() + end + + printProfilingResults(ply) + return + end + + DLib.Message(IsValid(ply) and ply or 'Console', ' started hook profiling') + + hook.PROFILE_STARTED = SysTime() + hook.PROFILING = true + + DLib.MessagePlayer(ply, 'Hook profiling were started') + DLib.MessagePlayer(ply, 'When you are ready you can type dlib_profile_hooks_cl again') + DLib.MessagePlayer(ply, '/// NOTE THAT RESULTS BECOME MORE ACCURATE AS PROFILING GOES!') + DLib.MessagePlayer(ply, '/// Disabling it too early will produce false results') + hook.Reconstruct() + end) +end + +if file.Exists('autorun/hat_init.lua', 'LUA') then + DLib.Message(string.rep('-', 63)) + DLib.Message(string.rep('W', 63)) + DLib.Message(string.rep('A', 63)) + DLib.Message(string.rep('R', 63)) + DLib.Message(string.rep('N', 63)) + DLib.Message(string.rep('I', 63)) + DLib.Message(string.rep('N', 63)) + DLib.Message(string.rep('G', 63)) + DLib.Message('HAT INSTALLATION DETECTED') + DLib.Message('HAT IS BASICALLY BROKEN FOR YEARS') + DLib.Message('AND IT ALSO BREAK THE GAME, OBVIOUSLY') + DLib.Message('IMPLEMENTING HAT LOADER TRAP') + DLib.Message(string.rep('-', 63)) + + table._DLibCopy = table._DLibCopy or table.Copy + + function table.Copy(tableIn) + if tableIn == _G.concommand or tableIn == _G.hook then + table.Copy = table._DLibCopy + error('Nuh uh. No. Definitely Not. I dont even.', 2) + end + + return table._DLibCopy(tableIn) + end +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/cl_i18n.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/cl_i18n.lua new file mode 100644 index 0000000..8a2aae3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/cl_i18n.lua @@ -0,0 +1,278 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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 i18n = i18n +local hook = hook +local unpack = unpack + +i18n.DEBUG_LANG_STRINGS = CreateConVar('gmod_language_dlib_dbg_cl', '0', {FCVAR_ARCHIVE}, 'Debug language strings (do not localize them)') + +local DefaultPanelCreated + +do + local function languageWatchdog(self) + self:_SetTextDLib(i18n.localize(self._DLibLocalize, unpack(self._DLibLocalizeArgs))) + end + + local function SetText(self, text, ...) + if not text or not isstring(text) then + hook.Remove('DLib.i18n.LangUpdate5', self) + return self:_SetTextDLib(text, ...) + end + + local text2 = text + + if text2[1] == '#' then + text2 = text2:sub(2) + end + + if not i18n.exists(text2) then + hook.Remove('DLib.i18n.LangUpdate5', self) + return self:_SetTextDLib(text, ...) + end + + hook.Add('DLib.i18n.LangUpdate5', self, languageWatchdog) + self._DLibLocalize = text2 + self._DLibLocalizeArgs = {...} + return self:_SetTextDLib(i18n.localize(text2, ...)) + end + + function DefaultPanelCreated(self) + if not self.SetText then return end + + self._SetTextDLib = self._SetTextDLib or self.SetText + self.SetText = SetText + end +end + +local LabelPanelCreated + +do + local function languageWatchdog(self) + self:_SetLabelDLib(i18n.localize(self._DLibLocalize, unpack(self._DLibLocalizeArgs))) + end + + local function SetLabel(self, text, ...) + if not isstring(text) or not i18n.exists(text) then + hook.Remove('DLib.i18n.LangUpdate4', self) + return self:_SetLabelDLib(text, ...) + end + + hook.Add('DLib.i18n.LangUpdate4', self, languageWatchdog) + self._DLibLocalize = text + self._DLibLocalizeArgs = {...} + return self:_SetLabelDLib(i18n.localize(text, ...)) + end + + function LabelPanelCreated(self) + if not self.SetLabel then return end + + self._SetLabelDLib = self._SetLabelDLib or self.SetLabel + self.SetLabel = SetLabel + end +end + +local TooltipPanelCreated + +do + local function languageWatchdog(self) + self:_SetTooltipDLib(i18n.localize(self._DLibLocalize, unpack(self._DLibLocalizeArgs))) + end + + local function SetTooltip(self, text, ...) + if not isstring(text) or not i18n.exists(text) then + hook.Remove('DLib.i18n.LangUpdate3', self) + return self:_SetTooltipDLib(text, ...) + end + + hook.Add('DLib.i18n.LangUpdate3', self, languageWatchdog) + self._DLibLocalize = text + self._DLibLocalizeArgs = {...} + return self:_SetTooltipDLib(i18n.localize(text, ...)) + end + + function TooltipPanelCreated(self) + if not self.SetTooltip then return end + + self._SetTooltipDLib = self._SetTooltipDLib or self.SetTooltip + self.SetTooltip = SetTooltip + end +end + +local TitlePanelCreated + +do + local function languageWatchdog(self) + self:_SetTitleDLib(i18n.localize(self._DLibLocalize, unpack(self._DLibLocalizeArgs))) + end + + local function SetTitle(self, text, ...) + if not isstring(text) or not i18n.exists(text) then + hook.Remove('DLib.i18n.LangUpdate2', self) + return self:_SetTitleDLib(text, ...) + end + + hook.Add('DLib.i18n.LangUpdate2', self, languageWatchdog) + self._DLibLocalize = text + self._DLibLocalizeArgs = {...} + return self:_SetTitleDLib(i18n.localize(text, ...)) + end + + function TitlePanelCreated(self) + if not self.SetTitle then return end + + self._SetTitleDLib = self._SetTitleDLib or self.SetTitle + self.SetTitle = SetTitle + end +end + +local NamedPanelCreated + +do + local function languageWatchdog(self) + self:_SetNameDLib(i18n.localize(self._DLibLocalize, unpack(self._DLibLocalizeArgs))) + end + + local function SetName(self, text, ...) + if not isstring(text) or not i18n.exists(text) then + hook.Remove('DLib.i18n.LangUpdate1', self) + return self:_SetNameDLib(text, ...) + end + + hook.Add('DLib.i18n.LangUpdate1', self, languageWatchdog) + self._DLibLocalize = text + self._DLibLocalizeArgs = {...} + return self:_SetNameDLib(i18n.localize(text, ...)) + end + + function NamedPanelCreated(self) + if not self.SetName then return end + + self._SetNameDLib = self._SetNameDLib or self.SetName + self.SetName = SetName + end +end + +-- lmao this way to workaround +hook.Add('DLib.LanguageChanged2', 'DLib.i18nPanelsBridge', function(...) + hook.Run('DLib.i18n.LangUpdate1', ...) + hook.Run('DLib.i18n.LangUpdate2', ...) + hook.Run('DLib.i18n.LangUpdate3', ...) + hook.Run('DLib.i18n.LangUpdate4', ...) + hook.Run('DLib.i18n.LangUpdate5', ...) +end) + +cvars.AddChangeCallback('gmod_language_dlib_dbg_cl', function() + hook.Run('DLib.i18n.LangUpdate1') + hook.Run('DLib.i18n.LangUpdate2') + hook.Run('DLib.i18n.LangUpdate3') + hook.Run('DLib.i18n.LangUpdate4') + hook.Run('DLib.i18n.LangUpdate5') +end, 'DLib') + +local function vguiPanelCreated(self) + local classname = self:GetClassName():lower() + if classname:find('textentry') or classname:lower():find('input') or classname:lower():find('editor') then return end + + DefaultPanelCreated(self) + LabelPanelCreated(self) + TooltipPanelCreated(self) + TitlePanelCreated(self) + NamedPanelCreated(self) +end + + +--[[ + @doc + @fname DLib.i18n.AddChat + @args vararg arguments + + @client +]] +function i18n.AddChat(...) + local rebuild = i18n.rebuildTable({...}) + return chat.AddText(unpack(rebuild)) +end + +i18n.WatchLegacyPhrases = i18n.WatchLegacyPhrases or {} + +--[[ + @doc + @fname DLib.i18n.RegisterProxy + @args string legacyName, string newName + + @client + @deprecated + + @desc + allows you to do language.Add(legacyName, localized newName) easily + @enddesc +]] +function i18n.RegisterProxy(legacyName, newName) + newName = newName or legacyName + + i18n.WatchLegacyPhrases[legacyName] = newName + language.Add(legacyName, i18n.localize(newName)) +end + +hook.Add('DLib.LanguageChanged', 'DLib.i18n.WatchLegacyPhrases', function(...) + for legacyName, newName in pairs(i18n.WatchLegacyPhrases) do + language.Add(legacyName, i18n.localize(newName)) + end +end) + +hook.Add('VGUIPanelCreated', 'DLib.I18n', vguiPanelCreated) +chat.AddTextLocalized = i18n.AddChat + +local gmod_language, LastLanguage +local LANG_OVERRIDE = CreateConVar('gmod_language_dlib_cl', '', {FCVAR_ARCHIVE}, 'gmod_language override for DLib based addons') + +--[[ + @doc + @fname DLib.i18n.UpdateLang + + @internal +]] +function i18n.UpdateLang() + gmod_language = gmod_language or GetConVar('gmod_language') + if not gmod_language then return end + local grablang = LANG_OVERRIDE:GetString():lower():trim() + + if grablang ~= '' then + i18n.CURRENT_LANG = grablang + else + i18n.CURRENT_LANG = gmod_language:GetString():lower():trim() + end + + if LastLanguage ~= i18n.CURRENT_LANG then + hook.Run('DLib.LanguageChanged', LastLanguage, i18n.CURRENT_LANG) + hook.Run('DLib.LanguageChanged2', LastLanguage, i18n.CURRENT_LANG) + end + + LastLanguage = i18n.CURRENT_LANG + + net.Start('dlib.clientlang') + net.WriteString(i18n.CURRENT_LANG) + net.SendToServer() +end + +cvars.AddChangeCallback('gmod_language', i18n.UpdateLang, 'DLib') +cvars.AddChangeCallback('gmod_language_dlib_cl', i18n.UpdateLang, 'DLib') +timer.Simple(0, i18n.UpdateLang) diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_functions.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_functions.lua new file mode 100644 index 0000000..8cb78fa --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_functions.lua @@ -0,0 +1,267 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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 i18n = i18n +local DLib = DLib +local assert = assert +local type = type + +--[[ + @doc + @fname DLib.i18n.tformatByLang + @args number time, string lang, boolean ago = false + + @returns + string: formatted time in selected locale +]] +function i18n.tformatByLang(time, lang, ago) + assert(type(time) == 'number', 'Invalid time specified') + + if time > 0xFFFFFFFFF then + return i18n.localizeByLang('info.dlib.tformat.long', lang) + elseif time <= 1 and time >= 0 then + return i18n.localizeByLang('info.dlib.tformat.now', lang) + --elseif time < 0 and not ago then + -- return i18n.localizeByLang('info.dlib.tformat.past', lang) + end + + local str = '' + local suffix = ago and '_ago' or '' + + local centuries, years, months, weeks, days, hours, minutes, seconds = math.tformatVararg(time:abs()) + + if seconds ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.second.' .. seconds, lang) + end + + if minutes ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.minute.' .. minutes, lang) .. ' ' .. str + end + + if hours ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.hour.' .. hours, lang) .. ' ' .. str + end + + if days ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.day.' .. days, lang) .. ' ' .. str + end + + if weeks ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.week.' .. weeks, lang) .. ' ' .. str + end + + if months ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.month.' .. months, lang) .. ' ' .. str + end + + if years ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.year.' .. years, lang) .. ' ' .. str + end + + if centuries ~= 0 then + str = i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.century.' .. centuries, lang) .. ' ' .. str + end + + return ago and i18n.localizeByLang('info.dlib.tformat.ago' .. (time < 0 and '_inv' or ''), lang, str) or time < 0 and ('-(' .. str .. ')') or str +end + +--[[ + @doc + @fname DLib.i18n.tformatTableByLang + @args number time, string lang, boolean ago = false + + @returns + table: array of formatted time in selected locale +]] +function i18n.tformatTableByLang(time, lang, ago) + assert(type(time) == 'number', 'Invalid time specified') + + if time > 0xFFFFFFFFF then + return {i18n.localizeByLang('info.dlib.tformat.long', lang)} + elseif time <= 1 and time >= 0 then + return {i18n.localizeByLang('info.dlib.tformat.now', lang)} + elseif time < 0 then + return {i18n.localizeByLang('info.dlib.tformat.past', lang)} + end + + local str = {} + local suffix = ago and '_ago' or '' + + local centuries, years, months, weeks, days, hours, minutes, seconds = math.tformatVararg(time) + + if seconds ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.second.' .. seconds, lang)) + end + + if minutes ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.minute.' .. minutes, lang)) + end + + if hours ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.hour.' .. hours, lang)) + end + + if days ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.day.' .. days, lang)) + end + + if weeks ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.week.' .. weeks, lang)) + end + + if months ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.month.' .. months, lang)) + end + + if years ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.year.' .. years, lang)) + end + + if centuries ~= 0 then + table.insert(str, i18n.localizeByLang('info.dlib.tformat.countable' .. suffix .. '.century.' .. centuries, lang)) + end + + return str +end + +--[[ + @doc + @fname DLib.i18n.tformatRawTable + @args number time + + @returns + table: for use in functions like DLib.LMessage/AddonSpace.LMessage/i18n.AddChat +]] +function i18n.tformatRawTable(time, ago) + assert(type(time) == 'number', 'Invalid time specified') + + if time > 0xFFFFFFFFF then + return {'info.dlib.tformat.long'} + elseif time <= 1 and time >= 0 then + return {'info.dlib.tformat.now'} + --elseif time < 0 then + -- return {'info.dlib.tformat.past'} + end + + local suffix = ago and '_ago' or '' + local str = {} + local centuries, years, months, weeks, days, hours, minutes, seconds = math.tformatVararg(time:abs()) + + if ago then + table.insert(str, 'info.dlib.tformat.ago' .. (time < 0 and '_inv' or '')) + end + + if time < 0 then + table.insert(str, '-(') + end + + if seconds ~= 0 then + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.second.' .. seconds) + end + + if minutes ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.minute.' .. minutes) + end + + if hours ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.hour.' .. hours) + end + + if days ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.day.' .. days) + end + + if weeks ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.week.' .. weeks) + end + + if months ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.month.' .. months) + end + + if years ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.year.' .. years) + end + + if centuries ~= 0 then + table.insert(str, ' ') + table.insert(str, 'info.dlib.tformat.countable' .. suffix .. '.century.' .. centuries) + end + + if time < 0 then + table.insert(str, ')') + end + + return str +end + +--[[ + @doc + @fname DLib.i18n.tformat + @args number time, boolean ago = false + + @returns + string: formatted time +]] +function i18n.tformat(time, ago) + return i18n.tformatByLang(time, i18n.CURRENT_LANG, ago) +end + +--[[ + @doc + @fname DLib.i18n.tformatTable + @args number time, boolean ago = false + + @returns + table: formatted time +]] +function i18n.tformatTable(time, ago) + return i18n.tformatTableByLang(time, i18n.CURRENT_LANG, ago) +end + +--[[ + @doc + @fname DLib.i18n.tformatFor + @args Player ply, number time, boolean ago = false + + @returns + string: formatted time in player's locale +]] +function i18n.tformatFor(ply, time, ago) + return i18n.tformatByLang(time, assert(type(ply) == 'Player' and ply, 'Invalid player provided').DLib_Lang or i18n.CURRENT_LANG) +end + +--[[ + @doc + @fname DLib.i18n.tformatTableFor + @args Player ply, number time, boolean ago = false + + @returns + table: formatted time in player's locale +]] +function i18n.tformatTableFor(ply, time, ago) + return i18n.tformatTableByLang(time, assert(type(ply) == 'Player' and ply, 'Invalid player provided').DLib_Lang or i18n.CURRENT_LANG) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_i18n.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_i18n.lua new file mode 100644 index 0000000..ec3fe54 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_i18n.lua @@ -0,0 +1,825 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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 i18n = i18n +local string = string +local type = type +local error = error +local team = team +local DLib = DLib +local table = table +local IsColor = IsColor + +i18n.hashed = i18n.hashed or {} +i18n.hashedFunc = i18n.hashedFunc or {} +i18n.hashedNoArgs = i18n.hashedNoArgs or {} +i18n.hashedLang = i18n.hashedLang or {} +i18n.hashedLangFunc = i18n.hashedLangFunc or {} +i18n.hashedNoArgsLang = i18n.hashedNoArgsLang or {} + +local formatters = { + ['##'] = function(self) + return {'##'}, 0 + end, + + ['#E'] = function(self, ent) + local ltype = type(ent) + + if ltype == 'Player' then + local nick = ent:Nick() + + if ent.SteamName and ent:SteamName() ~= nick then + nick = nick .. ' (' .. ent:SteamName() .. ')' + end + + return {team.GetColor(ent:Team()) or Color(), nick, color_white, string.format('<%s>', ent:SteamID())} + elseif ltype == 'Entity' then + return {DLib.ENTITY_COLOR:Copy(), tostring(ent)} + elseif ltype == 'NPC' then + return {DLib.NPC_COLOR:Copy(), tostring(ent)} + elseif ltype == 'Vehicle' then + return {DLib.VEHICLE_COLOR:Copy(), tostring(ent)} + elseif ltype == 'NextBot' then + return {DLib.NEXTBOT_COLOR:Copy(), tostring(ent)} + elseif ltype == 'Weapon' then + return {DLib.WEAPON_COLOR:Copy(), tostring(ent)} + else + error('Invalid argument to #E: ' .. ltype) + end + end, + + -- executor + ['#e'] = function(self, ent) + local ltype = type(ent) + + if ltype == 'Player' then + local nick = ent:Nick() + + if ent.SteamName and ent:SteamName() ~= nick then + nick = nick .. ' (' .. ent:SteamName() .. ')' + end + + return {team.GetColor(ent:Team()) or Color(), nick, color_white, string.format('<%s>', ent:SteamID())} + elseif ltype == 'Entity' and not IsValid(ent) then + return {Color(126, 63, 255), 'Console'} + else + error('Invalid argument to #e (executor) - ' .. ltype .. ' (' .. tostring(ent) .. ')') + end + end, + + ['#C'] = function(self, color) + if not IsColor(color) then + error('#C must be a color! ' .. type(color) .. ' given.') + end + + return {color} + end, + + ['#%.%d+i'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to custom #i: ' .. type(val)) + end + + return {DLib.NUMBER_COLOR:Copy(), string.format('%' .. self:sub(2, #self - 1) ..'i', val)} + end, + + ['#i'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to #i: ' .. type(val)) + end + + return {DLib.NUMBER_COLOR:Copy(), string.format('%i', val)} + end, + + ['#%.%d+f'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to custom #f: ' .. type(val)) + end + + return {DLib.NUMBER_COLOR:Copy(), string.format('%' .. self:sub(2, #self - 1) ..'f', val)} + end, + + ['#f'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to #f: ' .. type(val)) + end + + return {DLib.NUMBER_COLOR:Copy(), string.format('%f', val)} + end, + + ['#%.%d+[xX]'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to custom #x/#X: ' .. type(val)) + end + + if self[#self] == 'x' then + return {DLib.NUMBER_COLOR:Copy(), string.format('%' .. self:sub(2, #self - 1) ..'x', val)} + else + return {DLib.NUMBER_COLOR:Copy(), string.format('%' .. self:sub(2, #self - 1) ..'X', val)} + end + end, + + ['#[xX]'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to #x/#X: ' .. type(val)) + end + + if self[2] == 'x' then + return {DLib.NUMBER_COLOR:Copy(), string.format('%x', val)} + else + return {DLib.NUMBER_COLOR:Copy(), string.format('%X', val)} + end + end, + + ['#[duco]'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to #[duco]: ' .. type(val)) + end + + return {DLib.NUMBER_COLOR:Copy(), string.format('%' .. self[2], val)} + end, + + ['#b'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to #b: ' .. type(val)) + end + + local format = '' + + if val < 0 then + val = val + 0xFFFFFFFF + end + + val = val:floor() + + while val > 0 do + local div = val % 2 + val = (val - div) / 2 + format = div .. format + end + + return {DLib.NUMBER_COLOR:Copy(), format} + end, + + ['#%.%d+b'] = function(self, val) + if type(val) ~= 'number' then + error('Invalid argument to custom #b: ' .. type(val)) + end + + local format = '' + + if val < 0 then + val = val + 0xFFFFFFFF + end + + val = val:floor() + + while val > 0 do + local div = val % 2 + val = (val - div) / 2 + format = div .. format + end + + local num = tonumber(self:sub(3, #self - 1)) + + if #format < num then + format = string.rep('0', num - #format) .. format + end + + return {DLib.NUMBER_COLOR:Copy(), format} + end, +} + +--[[ + @doc + @fname DLib.i18n.format + @args string unformatted, Color colorDef = nil, vararg format + + @desc + Supports colors from custom format arguments + This is the same as creating i18n phrase with required arguments put in, + but slower due to `unformatted` being parsed each time on call, when + i18n phrase is parsed only once. + Available arguments are: + `#.0b`, `#b`, `#d`, `#u`, `#c`, `#o`, `#x`, `#.0x`, `#X`, `#.0X`, `#f`, `#.0f`, `#i`, `#.0i`, `#C` = Color, `#E` = Entity, `#e` = Command executor + As well as all `%` arguments !g:string.format accept + @enddesc + + @returns + table: formatted message + number: arguments "consumed" +]] +function i18n.format(unformatted, defColor, ...) + local formatTable = luatype(defColor) == 'Color' + defColor = defColor or color_white + local argsPos = 1 + local searchPos = 1 + local output = {} + local args = {...} + + if not formatTable then + table.unshift(args, defColor) + end + + local hit = true + + while hit and searchPos ~= #unformatted do + hit = false + + local findBest, findBestCutoff, findBestFunc, findFormatter = 0x1000000, 0x1000000 + + for formatter, funcCall in pairs(formatters) do + local findNext, findCutoff = unformatted:find(formatter, searchPos, false) + + if findNext and findBest > findNext then + hit = true + findBest, findBestCutoff, findBestFunc, findFormatter = findNext, findCutoff, funcCall, formatter + end + end + + if findBestFunc then + local slicePre = unformatted:sub(searchPos, findBest - 1) + local count = i18n.countExpressions(slicePre) + + if count ~= 0 then + table.insert(output, string.format(slicePre, unpack(args, argsPos, argsPos + count - 1))) + argsPos = argsPos + count + else + table.insert(output, slicePre) + end + + local ret, grabbed = findBestFunc(unformatted:sub(findBest, findBestCutoff), unpack(args, argsPos, #args)) + + if ret then + table.append(output, ret) + + if not IsColor(ret[#ret]) then + table.insert(output, defColor) + end + end + + argsPos = argsPos + (grabbed or 1) + searchPos = findBestCutoff + 1 + + if searchPos == #unformatted then + table.insert(output, unformatted[#unformatted]) + + if formatTable then + return output, argsPos - 1 + end + + local build = '' + + for i, arg in ipairs(output) do + if type(arg) == 'string' then + build = build .. arg + end + end + + return build, argsPos - 1 + end + end + end + + if searchPos ~= #unformatted then + local slice = unformatted:sub(searchPos) + local count = i18n.countExpressions(slice) + + if count ~= 0 then + table.insert(output, string.format(slice, unpack(args, argsPos, argsPos + count - 1))) + argsPos = argsPos + count + else + table.insert(output, slice) + end + end + + if formatTable then + return output, argsPos - 1 + end + + local build = '' + + for i, arg in ipairs(output) do + if type(arg) == 'string' then + build = build .. arg + end + end + + return build, argsPos - 1 +end + +local function compileExpression(unformatted) + local searchPos = 1 + local funclist = {} + local hit = true + + while hit and searchPos ~= #unformatted do + hit = false + + local findBest, findBestCutoff, findBestFunc, findFormatter = 0x1000000, 0x1000000 + + for formatter, funcCall in pairs(formatters) do + local findNext, findCutoff = unformatted:find(formatter, searchPos, false) + + if findNext and findBest > findNext then + hit = true + findBest, findBestCutoff, findBestFunc, findFormatter = findNext, findCutoff, funcCall, formatter + end + end + + if findBestFunc then + local slicePre = unformatted:sub(searchPos, findBest - 1) + local count = i18n.countExpressions(slicePre) + + if count ~= 0 then + table.insert(funclist, function(...) + return string.format(slicePre, ...), count + end) + else + table.insert(funclist, slicePre) + end + + table.insert(funclist, function(...) + local ret, count = findBestFunc(unformatted:sub(findBest, findBestCutoff), ...) + return ret, count or 1 + end) + + searchPos = findBestCutoff + 1 + + if searchPos == #unformatted then + table.insert(funclist, unformatted[#unformatted]) + break + end + end + end + + if searchPos ~= #unformatted then + local slice = unformatted:sub(searchPos) + local count = i18n.countExpressions(slice) + + if count ~= 0 then + table.insert(funclist, function(...) + return string.format(slice, ...), count + end) + else + table.insert(funclist, slice) + end + end + + return function(defColor, ...) + defColor = defColor or color_white + local output = {} + local argsPos = 1 + local args = {...} + + for i, func in ipairs(funclist) do + local ftype = type(func) + + if ftype == 'string' then + table.insert(output, func) + else + local fret, fcount = func(unpack(args, argsPos, #args)) + local frettype = type(fret) + + if frettype == 'string' then + table.insert(output, fret) + argsPos = argsPos + fcount + elseif frettype == 'table' then + table.append(output, fret) + + if not IsColor(fret[#fret]) then + table.insert(output, defColor) + end + + argsPos = argsPos + fcount + end + end + end + + return output, argsPos - 1 + end +end + +--[[ + @doc + @fname DLib.i18n.localizeByLang + @args string phrase, string lang, vararg format + + @returns + string: formatted message +]] +function i18n.localizeByLang(phrase, lang, ...) + if not i18n.hashed[phrase] or i18n.DEBUG_LANG_STRINGS:GetBool() then + return phrase + end + + if i18n.hashedFunc[phrase] then + local unformatted + + if lang == 'en' or not i18n.hashedLang[lang] then + unformatted = i18n.hashedFunc[phrase] or nil + else + unformatted = i18n.hashedLangFunc[lang] and i18n.hashedLangFunc[lang][phrase] or i18n.hashedFunc[phrase] or nil + end + + if not unformatted then + return phrase + end + + local status, formatted = pcall(unformatted, nil, ...) + + if status then + local output = '' + + for i, value in ipairs(formatted) do + if type(value) == 'string' then + output = output .. value + end + end + + return output + else + return 'Format error: ' .. phrase .. ' ' .. formatted + end + end + + local unformatted + + if lang == 'en' or not i18n.hashedLang[lang] then + unformatted = i18n.hashed[phrase] or phrase + else + unformatted = i18n.hashedLang[lang][phrase] or i18n.hashed[phrase] or phrase + end + + local status, formatted = pcall(string.format, unformatted, ...) + + if status then + return formatted + else + return 'Format error: ' .. phrase .. ' ' .. formatted + end +end + +--[[ + @doc + @fname DLib.i18n.localizeByLangAdvanced + @args string phrase, string lang, Color colorDef = color_white, vararg format + + @desc + Supports colors from custom format arguments + You don't want to use this unless you know that + some of phrases can contain custom format arguments + @enddesc + + @returns + table: formatted message + number: arguments "consumed" +]] +function i18n.localizeByLangAdvanced(phrase, lang, colorDef, ...) + if luatype(colorDef) ~= 'Color' then + return i18n._localizeByLangAdvanced(phrase, lang, color_white, ...) + else + return i18n._localizeByLangAdvanced(phrase, lang, colorDef, ...) + end +end + +function i18n._localizeByLangAdvanced(phrase, lang, colorDef, ...) + if not i18n.hashed[phrase] or i18n.DEBUG_LANG_STRINGS:GetBool() then + return {phrase}, 0 + end + + if i18n.hashedFunc[phrase] then + local unformatted + + if lang == 'en' or not i18n.hashedLang[lang] then + unformatted = i18n.hashedFunc[phrase] or nil + else + unformatted = i18n.hashedLangFunc[lang] and i18n.hashedLangFunc[lang][phrase] or i18n.hashedFunc[phrase] or nil + end + + if not unformatted then + return phrase + end + + local status, formatted, cnum = pcall(unformatted, colorDef, ...) + + if status then + return formatted, cnum + else + return {'Format error: ' .. phrase .. ' ' .. formatted}, 0 + end + end + + local unformatted + + if lang == 'en' or not i18n.hashedLang[lang] then + unformatted = i18n.hashed[phrase] or phrase + else + unformatted = i18n.hashedLang[lang][phrase] or i18n.hashed[phrase] or phrase + end + + local status, formatted, cnum = pcall(string.format, unformatted, ...) + + if status then + return {formatted}, i18n.countExpressions(unformatted) + else + return {'Format error: ' .. phrase .. ' ' .. formatted}, 0 + end +end + +--[[ + @doc + @fname DLib.i18n.countExpressions + @args string str + + @returns + number +]] +function i18n.countExpressions(str) + local i = 0 + + for line in str:gmatch('[%%][^%%]') do + i = i + 1 + end + + return i +end + +--[[ + @doc + @fname DLib.i18n.registerPhrase + @args string lang, string phrase, string unformatted + + @deprecated + @internal + + @returns + boolean: true +]] +function i18n.registerPhrase(lang, phrase, unformatted) + local advanced = false + + for formatter, funcCall in pairs(formatters) do + if unformatted:find(formatter) then + advanced = true + break + end + end + + if lang == 'en' then + i18n.hashed[phrase] = unformatted + else + i18n.hashed[phrase] = i18n.hashed[phrase] or unformatted + i18n.hashedLang[lang] = i18n.hashedLang[lang] or {} + i18n.hashedLang[lang][phrase] = unformatted + end + + if advanced then + local fncompile = compileExpression(unformatted) + + if lang == 'en' then + i18n.hashedFunc[phrase] = fncompile + else + i18n.hashedLangFunc[lang] = i18n.hashedLangFunc[lang] or {} + i18n.hashedLangFunc[lang][phrase] = fncompile + end + else + if lang == 'en' then + i18n.hashedFunc[phrase] = nil + else + i18n.hashedLangFunc[lang] = i18n.hashedLangFunc[lang] or {} + i18n.hashedLangFunc[lang][phrase] = nil + end + end + + if i18n.countExpressions(phrase) == 0 then + if lang == 'en' then + i18n.hashedNoArgs[phrase] = unformatted + else + i18n.hashedNoArgsLang[lang] = i18n.hashedNoArgsLang[lang] or {} + i18n.hashedNoArgsLang[lang][phrase] = unformatted + end + else + if lang == 'en' then + i18n.hashedNoArgs[phrase] = nil + else + i18n.hashedNoArgsLang[lang] = i18n.hashedNoArgsLang[lang] or {} + i18n.hashedNoArgsLang[lang][phrase] = nil + end + end + + return true +end + +--[[ + @doc + @fname DLib.i18n.localize + @args string phrase, vararg format + + @returns + string: formatted message +]] +function i18n.localize(phrase, ...) + return i18n.localizeByLang(phrase, i18n.CURRENT_LANG, ...) +end + +--[[ + @doc + @fname DLib.i18n.localizeAdvanced + @args string phrase, Color colorDef = color_white, vararg format + + @desc + Supports colors from custom format arguments + You don't want to use this unless you know that + some of phrases can contain custom format arguments + @enddesc + + @returns + table: formatted message + number: arguments "consumed" +]] +function i18n.localizeAdvanced(phrase, colorDef, ...) + if luatype(colorDef) ~= 'Color' then + return i18n.localizeByLang(phrase, i18n.CURRENT_LANG, nil, ...) + else + return i18n.localizeByLang(phrase, i18n.CURRENT_LANG, colorDef, ...) + end +end + +--[[ + @doc + @fname DLib.i18n.getRaw + @args string phrase + + @returns + string: or nil +]] +function i18n.getRaw(phrase) + return i18n.getRawByLang(phrase, i18n.CURRENT_LANG) +end + +--[[ + @doc + @fname DLib.i18n.getRaw2 + @args string phrase + + @returns + string: or nil +]] +function i18n.getRaw2(phrase) + return i18n.getRawByLang2(phrase, i18n.CURRENT_LANG) +end + + +--[[ + @doc + @fname DLib.i18n.getRawByLang + @args string phrase, string lang + + @returns + string: or nil +]] +function i18n.getRawByLang(phrase, lang) + return i18n.hashedLang[lang] and i18n.hashedLang[lang][phrase] or i18n.hashed[phrase] +end + +--[[ + @doc + @fname DLib.i18n.getRawByLang2 + @args string phrase, string lang + + @returns + string: or nil +]] +function i18n.getRawByLang2(phrase, lang) + return i18n.hashedLang[lang] and i18n.hashedLang[lang][phrase] or i18n.hashedLang[phrase] and i18n.hashedLang[phrase][lang] or i18n.hashed[phrase] +end + +--[[ + @doc + @fname DLib.i18n.phrasePresent + @alias DLib.i18n.exists + @alias DLib.i18n.phraseExists + @args string phrase + + @returns + boolean +]] +function i18n.phrasePresent(phrase) + return i18n.hashed[phrase] ~= nil +end + +--[[ + @doc + @fname DLib.i18n.safePhrase + @args string phrase + + @returns + boolean +]] +function i18n.safePhrase(phrase) + return i18n.hashedNoArgs[phrase] ~= nil +end + +i18n.exists = i18n.phrasePresent +i18n.phraseExists = i18n.phrasePresent + +local table = table +local type = type + +--[[ + @doc + @fname DLib.i18n.rebuildTable + @args table args, Color colorDef = color_white, boolean backward = false + + @desc + when `backward` is `true`, table will be constructed from it's end. This means that when a phrase require + format arguments, it's arguments can be localized too (recursive localization) + `'info.i18n.phrase_with_two_format_values', 'Player', 'info.i18n.phrase'` + will localize both `info.i18n.phrase_with_two_format_values` and `info.i18n.phrase` + in case if `info.i18n.phrase_with_two_format_values` hold two format values (e.g. `'%s was %s'`) + `false` = `'Player was info.i18n.phrase'` + `true` = `'Player was looking at phrase'` + @enddesc + + @returns + table: a table with localized strings. other types are untouched. does not modify original table +]] +function i18n.rebuildTable(args, colorDef, backward) + return i18n.rebuildTableByLang(args, i18n.CURRENT_LANG, colorDef, backward) +end + +--[[ + @doc + @fname DLib.i18n.rebuildTableByLang + @args table args, string lang, Color colorDef = color_white, boolean backward = false + + @desc + when `backward` is `true`, table will be constructed from it's end. This means that when a phrase require + format arguments, it's arguments can be localized too (recursive localization) + `'info.i18n.phrase_with_two_format_values', 'Player', 'info.i18n.phrase'` + will localize both `info.i18n.phrase_with_two_format_values` and `info.i18n.phrase` + in case if `info.i18n.phrase_with_two_format_values` hold two format values (e.g. `'%s was %s'`) + `false` = `'Player was info.i18n.phrase'` + `true` = `'Player was looking at phrase'` + @enddesc + + @returns + table: a table with localized strings. other types are untouched. does not modify original table +]] +function i18n.rebuildTableByLang(args, lang, colorDef, backward) + if backward == nil then backward = false end + local rebuild + local i = backward and #args or 1 + + if backward then + rebuild = table.qcopy(args) + + while i > 0 do + local arg = rebuild[i] + local index = #rebuild - i + + if type(arg) ~= 'string' or not i18n.exists(arg) then + i = i - 1 + else + local phrase, consumed = i18n.localizeByLangAdvanced(arg, lang, colorDef, unpack(rebuild, i + 1, #rebuild)) + table.splice(rebuild, i, consumed + 1, unpack(phrase, 1, #phrase)) + i = i - 1 - consumed + end + end + else + rebuild = {} + + while i <= #args do + local arg = args[i] + + if type(arg) ~= 'string' or not i18n.exists(arg) then + table.insert(rebuild, arg) + i = i + 1 + else + local phrase, consumed = i18n.localizeByLangAdvanced(arg, lang, colorDef, unpack(args, i + 1, #args)) + i = i + 1 + consumed + table.append(rebuild, phrase) + end + end + end + + return rebuild +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_loader.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_loader.lua new file mode 100644 index 0000000..b106d95 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_loader.lua @@ -0,0 +1,208 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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 filesLoad = {} +local i18n = i18n +local file = file +local ipairs = ipairs +local pairs = pairs +local table = table +local ProtectedCall = ProtectedCall +local getfenv = getfenv + +--[[ + @doc + @fname DLib.i18n.reload + @internal +]] +function i18n.reload(module) + if not module then + local _, dirs = file.Find('dlib/i18n/*', 'LUA') + + for i, dir in ipairs(dirs) do + i18n.reload(dir) + end + + return + end + + local files = file.Find('dlib/i18n/' .. module .. '/*.lua', 'LUA') + + for i2, luafile in ipairs(files) do + -- if luafile:sub(-4) == '.lua' then + AddCSLuaFile('dlib/i18n/' .. module .. '/' .. luafile) + i18n.executeFile(luafile:sub(1, -5), 'dlib/i18n/' .. module .. '/' .. luafile) + -- end + end +end + +--[[ + @doc + @fname DLib.i18n.executeFile + @args string langSpace, string filePath + @internal + @returns + boolean +]] +function i18n.executeFile(langSpace, fileToRun) + return i18n.executeFunction(langSpace, CompileFile(fileToRun)) +end + +local createNamedTable +local setmetatable = setmetatable +local mt = getmetatable + +local tableMeta = { + __index = function(self, key) + if type(key) ~= 'string' then + error('You can only use strings as indexes') + end + + local value = rawget(self, key) + if value ~= nil then return value end + local newTable = createNamedTable(key, self) + rawset(self, key, newTable) + return newTable + end, + + __newindex = function(self, key, value) + if type(value) == 'table' then + error("You don't have to define tables!") + end + + if type(key) ~= 'string' then + error('You can only use strings as indexes') + end + + if type(value) == 'number' then + value = value:tostring() + end + + if type(value) ~= 'string' then + error('You can only define strings') + end + + local parent = mt(self).__parent + local build = mt(self).__name .. '.' .. key + + while parent do + build = mt(parent).__name .. '.' .. build + parent = mt(parent).__parent + end + + rawset(self, key, value) + i18n.registerPhrase(mt(self).__lang, build, value) + end +} + +function createNamedTable(tableName, parent) + local output = {} + local meta = { + __index = tableMeta.__index, + __newindex = tableMeta.__newindex, + __parent = parent, + __name = tableName, + __lang = i18n.LANG_SPACE + } + + return setmetatable(output, meta) +end + +local defaultNames = { + 'gui', 'attack', 'death', 'reason', 'command', + 'click', 'info', 'message', 'err', 'warning', + 'tip', 'player', 'help' +} + +local function __indexEnv(self, key) + local value = rawget(self, key) + + if value ~= nil then + return value + end + + value = getfenv(0)[key] + + if value ~= nil then + return getfenv(0)[key] + end + + local newTable = createNamedTable(key) + rawset(self, key, newTable) + return newTable +end + +local function __newIndexEnv(self, key, value) + getfenv(0)[key] = value +end + +local function protectedFunc(langSpace, funcToRun) + local envToRun = { + LANGUAGE = langSpace + } + + local namespace = {} + + for i, def in ipairs(defaultNames) do + namespace[def] = createNamedTable(def) + end + + for key, value in pairs(namespace) do + envToRun[key] = value + end + + envToRun.NAMESPACE = namespace + envToRun.LANG_NAMESPACE = namespace + envToRun.LANGUAGE_NAMESPACE = namespace + + setmetatable(envToRun, { + __index = __indexEnv, + __newindex = __newIndexEnv, + __lang = langSpace, + __space = namespace + }) + + local oldenv = getfenv(funcToRun) + setfenv(funcToRun, envToRun) + + ProtectedCall(funcToRun) + + if oldenv then + setfenv(funcToRun, oldenv) + end +end + +--[[ + @doc + @fname DLib.i18n.executeFunction + @args string langSpace, function toRun + @internal + + @returns + boolean +]] +function i18n.executeFunction(langSpace, funcToRun) + i18n.LANG_SPACE = langSpace + local status = ProtectedCall(protectedFunc:Wrap(langSpace, funcToRun)) + i18n.LANG_SPACE = nil + + return status +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_units.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_units.lua new file mode 100644 index 0000000..67a7342 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sh_units.lua @@ -0,0 +1,506 @@ + +-- Copyright (C) 2018-2019 DBotThePony + +-- 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. + +i18n.METRES_IN_HU = 0.0254 + +local prefixL = { + {math.pow(10, -24), 'yocto'}, + {math.pow(10, -21), 'zepto'}, + {math.pow(10, -18), 'atto'}, + {math.pow(10, -15), 'femto'}, + {math.pow(10, -12), 'pico'}, + {math.pow(10, -9), 'nano'}, + {math.pow(10, -6), 'micro'}, + {math.pow(10, -3), 'milli'}, + -- {math.pow(10, -2), 'centi', true}, + -- {math.pow(10, -1), 'deci', true}, + {math.pow(10, 3), 'kilo'}, + {math.pow(10, 6), 'mega'}, + {math.pow(10, 9), 'giga'}, + {math.pow(10, 12), 'tera'}, + {math.pow(10, 15), 'peta'}, + {math.pow(10, 18), 'exa'}, + {math.pow(10, 21), 'zetta'}, + {math.pow(10, 24), 'yotta'}, +} + +function i18n.FormatNumImperial(numIn) + if numIn >= -1000 and numIn <= 1000 then + return string.format('%.2f', numIn) + end + + return string.format('%.2fk', numIn / 1000) +end + +--[[ + @doc + @fname DLib.i18n.FormatNum + @args number numIn + + @returns + string +]] +function i18n.FormatNum(numIn, minFormat) + local abs = numIn:abs() + + if abs >= (minFormat or 1) and abs <= 1000 then + return string.format('%.2f', numIn) + end + + local prefix, lastNum = prefixL[1][2], prefixL[1][1] + + for i, row in ipairs(prefixL) do + if row[1] <= abs then + prefix, lastNum = row[2], row[1] + else + break + end + end + + return string.format('%.2f%s', numIn / lastNum, i18n.localize('info.dlib.si.prefix.' .. prefix .. '.prefix')) +end + +--[[ + @doc + @fname DLib.i18n.FormatFrequency + @args number Hz + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatForce + @args number N + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatPressure + @args number Pa + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatWork + @alias DLib.i18n.FormatHeat + @alias DLib.i18n.FormatEnergy + @args number J + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatPower + @args number W + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatVoltage + @args number V + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatElectricalCapacitance + @args number F + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatElectricalResistance + @alias DLib.i18n.FormatImpedance + @alias DLib.i18n.FormatReactance + @args number Ω + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatElectricalConductance + @args number S + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatMagneticFlux + @args number wb + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatMagneticFluxDensity + @alias DLib.i18n.FormatMagneticInduction + @args number T + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatIlluminance + @args number lx + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatRadioactivity + @args number Bq + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatAbsorbedDose + @args number Gy + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatEquivalentDose + @args number Sv + + @returns + string +]] + +--[[ + @doc + @fname DLib.i18n.FormatCatalyticActivity + @args number kat + + @returns + string +]] +local units = [[hertz Hz frequency 1/s s−1 +radian rad angle m/m 1 +steradian sr solidAngle m2/m2 1 +newton N force, weight kg⋅m/s2 kg⋅m⋅s−2 +pascal Pa pressure, stress N/m2 kg⋅m−1⋅s−2 +joule J energy, work, heat N⋅m, C⋅V, W⋅s kg⋅m2⋅s−2 +watt W power, radiantFlux J/s, V⋅A kg⋅m2⋅s−3 +coulomb C electricCharge, electricityQuantity s⋅A, F⋅V s⋅A +volt V voltage, electrical potential difference, electromotive force W/A, J/C kg⋅m2⋅s−3⋅A−1 +farad F electricalCapacitance C/V, s/Ω kg−1⋅m−2⋅s4⋅A2 +ohm Ω electricalResistance, impedance, reactance, resistance 1/S, V/A kg⋅m2⋅s−3⋅A−2 +siemens S electricalConductance 1/Ω, A/V kg−1⋅m−2⋅s3⋅A2 +weber Wb magneticFlux J/A, T⋅m2 kg⋅m2⋅s−2⋅A−1 +tesla T magneticInduction, magneticFluxDensity V⋅s/m2, Wb/m2, N/(A⋅m) kg⋅s−2⋅A−1 +henry H electricalInductance V⋅s/A, Ω⋅s, Wb/A kg⋅m2⋅s−2⋅A−2 +lumen lm luminous flux cd⋅sr cd +lux lx illuminance lm/m2 cd⋅sr/m2 +becquerel Bq radioactivity (decays per unit time) 1/s s−1 +gray Gy absorbedDose (of ionizing radiation) J/kg m2⋅s−2 +sievert Sv equivalentDose (of ionizing radiation) J/kg m2⋅s−2 +katal kat catalyticActivity mol/s s−1⋅mol]] + +for i, row in ipairs(units:split('\n')) do + local measure, NaM, mtype = row:match('(%S+)%s+(%S+)%s+(.+)') + + if measure and NaM and mtype then + for i, ttype in ipairs(mtype:split(',')) do + ttype = ttype:match('(%S+)') + + if ttype then + i18n['Format' .. ttype:formatname()] = function(numIn) + return string.format('%s%s', type(numIn) == 'number' and i18n.FormatNum(numIn) or numIn, i18n.localize('info.dlib.si.units.' .. measure .. '.suffix')) + end + end + end + end +end + +i18n.TEMPERATURE_UNITS = CreateConVar('dlib_unit_system_temperature', 'C', {FCVAR_ARCHIVE}, 'C/K/F') +i18n.TEMPERATURE_UNITS_TYPE_CELSIUS = 0 +i18n.TEMPERATURE_UNITS_TYPE_KELVIN = 1 +i18n.TEMPERATURE_UNITS_TYPE_FAHRENHEIT = 2 + +--[[ + @doc + @fname DLib.i18n.FormatTemperature + @args number numIn, number providedType + + @desc + `providedType` define in whcih temp units `numIn` is + Valid values are: + `DLib.i18n.TEMPERATURE_UNITS_TYPE_CELSIUS` (default) + `DLib.i18n.TEMPERATURE_UNITS_TYPE_KELVIN` + `DLib.i18n.TEMPERATURE_UNITS_TYPE_FAHRENHEIT` + @enddesc + + @returns + string +]] +function i18n.FormatTemperature(tempUnits, providedType) + providedType = providedType or i18n.TEMPERATURE_UNITS_TYPE_CELSIUS + + if providedType == i18n.TEMPERATURE_UNITS_TYPE_CELSIUS then + tempUnits = tempUnits + 273.15 + elseif providedType == i18n.TEMPERATURE_UNITS_TYPE_FAHRENHEIT then + tempUnits = (tempUnits - 32) * 5 / 9 + 273.15 + end + + local units = i18n.TEMPERATURE_UNITS:GetString() + + if units == 'K' then + return string.format('%s°%s', i18n.FormatNum(tempUnits, 0.01), i18n.localize('info.dlib.si.units.kelvin.suffix')) + elseif units == 'F' then + return string.format('%s°%s', i18n.FormatNumImperial((tempUnits - 273.15) * 9 / 5 + 32), i18n.localize('info.dlib.si.units.fahrenheit.suffix')) + else + return string.format('%s°%s', i18n.FormatNum(tempUnits - 273.15, 0.01), i18n.localize('info.dlib.si.units.celsius.suffix')) + end +end + +local sv_gravity = GetConVar('sv_gravity') + +--[[ + @doc + @fname DLib.i18n.FreeFallAcceleration + + @returns + number: for use with `FormatForce` or anything like that +]] +function i18n.FreeFallAcceleration() + return 9.8066 * sv_gravity:GetFloat() / 600 +end + +--[[ + @doc + @fname DLib.i18n.FormatDistance + @args number metresIn + + @returns + string +]] +function i18n.FormatDistance(numIn) + return string.format('%s%s', i18n.FormatNum(numIn), i18n.localize('info.dlib.si.units.metre.suffix')) +end + +--[[ + @doc + @fname DLib.i18n.FormatHU + @alias DLib.i18n.FormatHammerUnits + @args number hammerUnitsIn + + @returns + string +]] +function i18n.FormatHU(numIn) + return i18n.FormatDistance(numIn * i18n.METRES_IN_HU) +end + +i18n.FormatHammerUnits = i18n.FormatHU + +do + local prefixL = table.Copy(prefixL) + + for i, row in ipairs(prefixL) do + row[1] = row[1]:pow(2) + end + +--[[ + @doc + @fname DLib.i18n.FormatArea + @args number squareMetresIn + + @returns + string +]] + function i18n.FormatArea(numIn) + assert(numIn >= 0, 'Area can not be negative') + + if numIn >= 1 and numIn <= 1000 then + return string.format('%.2fm^2', numIn) + end + + local index = 1 + + for i, row in ipairs(prefixL) do + if row[1] <= numIn then + index = i + else + break + end + end + + local lastNum, prefix = prefixL[index][1], prefixL[index][2] + + if numIn / lastNum > 10000 and index < #prefixL then + index = index + 1 + lastNum, prefix = prefixL[index][1], prefixL[index][2] + end + + return string.format('%.2f%s%s^2', numIn / lastNum, i18n.localize('info.dlib.si.prefix.' .. prefix .. '.prefix'), i18n.localize('info.dlib.si.units.metre.suffix')) + end + +--[[ + @doc + @fname DLib.i18n.FormatAreaHU + @alias DLib.i18n.FormatAreaHammerUnits + @args number squareHammerUnitsIn + + @returns + string +]] + function i18n.FormatAreaHU(numIn) + return i18n.FormatArea(numIn * i18n.METRES_IN_HU) + end + + i18n.FormatAreaHammerUnits = i18n.FormatAreaHU +end + +do + local prefixL = table.Copy(prefixL) + + for i, row in ipairs(prefixL) do + row[1] = row[1]:pow(3) + end + + i18n.VOLUME_UNITS = CreateConVar('dlib_unit_system_volume', 'L', {FCVAR_ARCHIVE}, 'L/m') + +--[[ + @doc + @fname DLib.i18n.FormatVolume + @args number litres + + @returns + string +]] + function i18n.FormatVolume(litres) + assert(litres >= 0, 'Volume can not be negative') + + if i18n.VOLUME_UNITS:GetString() == 'm' then + local numIn = litres / 1000 + + if numIn >= 0.0001 and numIn <= 1000000 then + return string.format('%.4fm^3', numIn) + end + + local index = 1 + + for i, row in ipairs(prefixL) do + if row[1] <= numIn then + index = i + else + break + end + end + + local lastNum, prefix = prefixL[index][1], prefixL[index][2] + + if numIn / lastNum > 10000 and index < #prefixL then + index = index + 1 + lastNum, prefix = prefixL[index][1], prefixL[index][2] + end + + return string.format('%.4f%s%s^3', numIn / lastNum, i18n.localize('info.dlib.si.prefix.' .. prefix .. '.prefix'), i18n.localize('info.dlib.si.units.metre.suffix')) + end + + return string.format('%s%s', i18n.FormatNum(litres), i18n.localize('info.dlib.si.units.litre.suffix')) + end + +--[[ + @doc + @fname DLib.i18n.FormatVolumeHU + @alias DLib.i18n.FormatVolumeHammerUnits + @args number cubicHammerUnitsIn + + @returns + string +]] + function i18n.FormatVolumeHU(numIn) + return i18n.FormatVolume(numIn * i18n.METRES_IN_HU * 0.2587786259) + end + + i18n.FormatVolumeHammerUnits = i18n.FormatVolumeHU + + cvars.AddChangeCallback('dlib_unit_system_volume', function(self, old, new) + if new ~= 'L' and new ~= 'm' then + DLib.MessageError('Invalid value for dlib_unit_system_volume specified, reverting to L') + i18n.VOLUME_UNITS:Reset() + end + end, 'DLib') +end + +--[[ + @doc + @fname DLib.i18n.FormatWeight + @args number grams + + @returns + string +]] +function i18n.FormatWeight(numIn) + return string.format('%s%s', i18n.FormatNum(numIn), i18n.localize('info.dlib.si.units.gram.suffix')) +end + +--[[ + @doc + @fname DLib.i18n.GetNormalPressure + + @returns + number: Pa +]] +function i18n.GetNormalPressure() + return 101325 +end + +cvars.AddChangeCallback('dlib_unit_system_temperature', function(self, old, new) + if new ~= 'C' and new ~= 'K' and new ~= 'F' then + DLib.MessageError('Invalid value for dlib_unit_system_temperature specified, reverting to C') + i18n.TEMPERATURE_UNITS:Reset() + end +end, 'DLib') diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sv_i18n.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sv_i18n.lua new file mode 100644 index 0000000..6d9ff9b --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/i18n/sv_i18n.lua @@ -0,0 +1,218 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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. + +net.pool('dlib.clientlang') + +local i18n = i18n + +i18n.DEBUG_LANG_STRINGS = CreateConVar('gmod_language_dlib_dbg', '0', {FCVAR_ARCHIVE}, 'Debug language strings (do not localize them)') + +--[[ + @doc + @fname DLib.i18n.getPlayer + @alias DLib.i18n.getByPlayer + @alias DLib.i18n.localizePlayer + @alias DLib.i18n.localizebyPlayer + @alias DLib.i18n.byPlayer + @alias Player:LocalizePhrase + @alias Player:DLibPhrase + @alias Player:DLibLocalize + @alias Player:GetDLibPhrase + + @args Player ply, string phrase, vararg formatArguments + @server + + @returns + string: formatted message +]] +function i18n.getPlayer(ply, phrase, ...) + return i18n.localizeByLang(phrase, ply.DLib_Lang or 'en', ...) +end + +i18n.getByPlayer = i18n.getPlayer +i18n.localizePlayer = i18n.getPlayer +i18n.localizebyPlayer = i18n.getPlayer +i18n.byPlayer = i18n.getPlayer + +--[[ + @doc + @fname DLib.i18n.getPlayerAdvanced + @alias DLib.i18n.getByPlayerAdvanced + @alias DLib.i18n.localizePlayerAdvanced + @alias DLib.i18n.localizebyPlayerAdvanced + @alias DLib.i18n.byPlayerAdvanced + @alias Player:LocalizePhraseAdvanced + @alias Player:DLibPhraseAdvanced + @alias Player:DLibLocalizeAdvanced + @alias Player:GetDLibPhraseAdvanced + + @args Player ply, string phrase, Color colorDef = color_white, vararg formatArguments + @server + + @desc + Supports colors from custom format arguments + You don't want to use this unless you know that + some of phrases can contain custom format arguments + @enddesc + + @returns + table: formatted message + number: arguments "consumed" +]] +function i18n.getPlayerAdvanced(ply, phrase, ...) + return i18n.localizeByLangAdvanced(phrase, ply.DLib_Lang or 'en', ...) +end + +i18n.getByPlayerAdvanced = i18n.getPlayerAdvanced +i18n.localizePlayerAdvanced = i18n.getPlayerAdvanced +i18n.localizebyPlayerAdvanced = i18n.getPlayerAdvanced +i18n.byPlayerAdvanced = i18n.getPlayerAdvanced + +local plyMeta = FindMetaTable('Player') + +--[[ + @doc + @fname Player:LocalizePhrase + @alias Player:DLibPhrase + @alias Player:DLibLocalize + @alias Player:GetDLibPhrase + + @args string phrase, vararg formatArguments + @server + + @returns + string: formatted message +]] +function plyMeta:LocalizePhrase(phrase, ...) + return i18n.localizeByLang(phrase, self.DLib_Lang or 'en', ...) +end + +plyMeta.DLibPhrase = plyMeta.LocalizePhrase +plyMeta.DLibLocalize = plyMeta.LocalizePhrase +plyMeta.GetDLibPhrase = plyMeta.LocalizePhrase + +--[[ + @doc + @fname Player:LocalizePhraseAdvanced + @alias Player:DLibPhraseAdvanced + @alias Player:DLibLocalizeAdvanced + @alias Player:GetDLibPhraseAdvanced + + @args string phrase, Color colorDef = color_white, vararg formatArguments + @server + + @desc + Supports colors from custom format arguments + You don't want to use this unless you know that + some of phrases can contain custom format arguments + @enddesc + + @returns + table: formatted message + number: arguments "consumed" +]] +function plyMeta:LocalizePhraseAdvanced(phrase, ...) + return i18n.localizeByLangAdvanced(phrase, self.DLib_Lang or 'en', ...) +end + +plyMeta.DLibPhraseAdvanced = plyMeta.LocalizePhraseAdvanced +plyMeta.DLibLocalizeAdvanced = plyMeta.LocalizePhraseAdvanced +plyMeta.GetDLibPhraseAdvanced = plyMeta.LocalizePhraseAdvanced + +local ipairs = ipairs +local player = player +local game = game +local GetName = FindMetaTable('Entity').GetName +local SetName = FindMetaTable('Entity').SetName + +local function tickPlayers() + if game.SinglePlayer() then + timer.Remove('DLib.TickPlayerNames') + hook.Remove('PlayerSpawn', 'DLib.TickPlayerNames') + hook.Remove('DoPlayerDeath', 'DLib.TickPlayerNames') + return + end + + for i, ply in ipairs(player.GetAll()) do + local name = GetName(ply) + + if #name < 4 then + --DLib.Message(ply, ' network name was considered as exploit, changing...') + SetName(ply, '_bad_playername_' .. ply:UserID()) + end + + local nick = ply:Nick() + + if nick == 'unnamed' or i18n.exists(nick) then + ply:Kick('[DLib/I18N] Invalid nickname') + end + end +end + +function i18n.RegisterProxy(...) + -- do nothing +end + +local LANG_OVERRIDE = CreateConVar('gmod_language_dlib_sv', '', {FCVAR_ARCHIVE}, 'gmod_language override for DLib based addons') + + +--[[ + @doc + @hook DLib.LanguageChanged +]] + +function i18n.UpdateLang() + gmod_language = gmod_language or GetConVar('gmod_language') + if not gmod_language then return end + local grablang = LANG_OVERRIDE:GetString():lower():trim() + + if grablang ~= '' then + i18n.CURRENT_LANG = grablang + else + i18n.CURRENT_LANG = gmod_language:GetString():lower():trim() + end + + if LastLanguage ~= i18n.CURRENT_LANG then + hook.Call('DLib.LanguageChanged') + hook.Call('DLib.LanguageChanged2') + end + + LastLanguage = i18n.CURRENT_LANG +end + +cvars.AddChangeCallback('gmod_language', i18n.UpdateLang, 'DLib') +cvars.AddChangeCallback('gmod_language_dlib_sv', i18n.UpdateLang, 'DLib') +timer.Simple(0, i18n.UpdateLang) + +--[[ + @doc + @hook DLib.PlayerLanguageChanges + @args Player ply, string oldOrNil, string new +]] + +net.receive('dlib.clientlang', function(len, ply) + local old = ply.DLib_Lang + ply.DLib_Lang = net.ReadString() + hook.Run('DLib.PlayerLanguageChanges', ply, old, ply.DLib_Lang) +end) + +timer.Create('DLib.TickPlayerNames', 0.5, 0, tickPlayers) +hook.Add('PlayerSpawn', 'DLib.TickPlayerNames', timer.Simple:Wrap(0, tickPlayers)) +hook.Add('DoPlayerDeath', 'DLib.TickPlayerNames', tickPlayers) diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/lerp.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/lerp.lua new file mode 100644 index 0000000..058b750 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/lerp.lua @@ -0,0 +1,135 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 Lerp = Lerp +local math = math + +local function Lerp(t, a, b) + return a + (b - a) * t +end + +--[[ + @doc + @fname LerpQuintic + @args T t, T from, T to + + @returns + T: lerped value +]] +function _G.LerpQuintic(t, a, b) + if t < 0 then return a end + if t >= 1 then return b end + local value = t * t * t * (t * (t * 6 - 15) + 10) + return Lerp(value, a, b) +end + +--[[ + @doc + @fname Quintic + @args T t + + @returns + T +]] +function _G.Quintic(t) + return t * t * t * (t * (t * 6 - 15) + 10) +end + +--[[ + @doc + @fname LerpCosine + @args T t, T from, T to + + @returns + T: lerped value +]] +function _G.LerpCosine(t, a, b) + if t < 0 then return a end + if t >= 1 then return b end + local value = (1 - math.cos(t * math.pi)) / 2 + return Lerp(value, a, b) +end + +--[[ + @doc + @fname Cosine + @args T t + + @returns + T +]] +function _G.Cosine(t) + return (1 - math.cos(t * math.pi)) / 2 +end + +--[[ + @doc + @fname LerpSinusine + @args T t, T from, T to + + @returns + T: lerped value +]] +function _G.LerpSinusine(t, a, b) + if t < 0 then return a end + if t >= 1 then return b end + local value = (1 - math.sin(t * math.pi)) / 2 + return Lerp(value, a, b) +end + +--[[ + @doc + @fname Sinusine + @args T t + + @returns + T +]] +function _G.Sinusine(t) + return (1 - math.sin(t * math.pi)) / 2 +end + +--[[ + @doc + @fname LerpCubic + @args T t, T from, T to + + @returns + T: lerped value +]] +function _G.LerpCubic(t, a, b) + if t < 0 then return a end + if t >= 1 then return b end + local value = -2 * t * t * t + 3 * t * t + return Lerp(value, a, b) +end + +--[[ + @doc + @fname Cubic + @args T t + + @returns + T +]] +function _G.Cubic(t) + return -2 * t * t * t + 3 * t * t +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/luavector.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/luavector.lua new file mode 100644 index 0000000..f9ba2b0 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/luavector.lua @@ -0,0 +1,911 @@ + +-- Copyright (C) 2018-2020 DBotThePony + +-- 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 meta = FindMetaTable('LVector') or {} +debug.getregistry().LVector = meta + +local table = table +local rawget = rawget +local rawset = rawset +local error = error +local string = string +local Vector = Vector +local type = luatype +local setmetatable = setmetatable +local Angle = Angle +local Color = Color +local Lerp = Lerp + +meta.MetaName = 'LVector' + +--[[ + @doc + @fname LVector:__index + @args any key + + @returns + any: associated value or function or nil +]] +function meta:__index(key) + if key == 1 then + return self.x + elseif key == 2 then + return self.y + elseif key == 3 then + return self.z + end + + local func = meta[key] + + if func then + return func + end + + return rawget(self, key) +end + +--[[ + @doc + @fname LVector:__newindex + @args any key, any value + + @internal +]] +function meta:__newindex(key, value) + if key == 1 then + key = 'x' + elseif key == 2 then + key = 'y' + elseif key == 3 then + key = 'z' + end + + rawset(self, key, value) +end + +--[[ + @doc + @fname LVector + @args any x = 0, number y = 0, number z = 0 + + @desc + Creates a new Lua based Vector object + this Vector object **can not** be passed to C defined methods! + but can be used in Lua based methods however (which either don't call C methods + or know that they can get `LVector` instead of `Vector`) + If `x` provided is a `LVector` or `Vector`, it will be copied. + Main advantage over C based `Vector` of this that this class is JIT friendly, + and if used wisely can noticeably increase performance of hot loops + + This custom class implements the most frequent used methods of vectors + @enddesc + + @returns + LVector: newly created object +]] +local function LVector(x, y, z) + if type(x) == 'Vector' or luatype(x) == 'LVector' then + y = x.y + z = x.z + x = x.x + end + + if type(x) ~= 'nil' and type(x) ~= 'number' then + error('Invalid X variable. typeof ' .. type(x)) + end + + if type(y) ~= 'nil' and type(y) ~= 'number' then + error('Invalid Y variable. typeof ' .. type(y)) + end + + if type(z) ~= 'nil' and type(z) ~= 'number' then + error('Invalid Z variable. typeof ' .. type(z)) + end + + local object = { + x = x or 0, + y = y or 0, + z = z or 0 + } + + return setmetatable(object, meta) +end + +_G.LVector = LVector +_G.LuaVector = LVector + +--[[ + @doc + @fname LVector:ToNative + + @returns + Vector +]] +function meta:ToNative() + return Vector(self.x, self.y, self.z) +end + +--[[ + @doc + @fname LVector:ToLua + + @returns + LVector: copy +]] +function meta:ToLua() + return LVector(self) +end + +--[[ + @doc + @fname LVector:Copy + + @returns + LVector: copy +]] +function meta:Copy() + return LVector(self) +end + +--[[ + @doc + @fname LVector:ToVector + + @returns + LVector: copy +]] +function meta:ToVector() + return LVector(self) +end + +--[[ + @doc + @fname LVector:__tostring + + @returns + string +]] +function meta:__tostring() + return string.format('LuaVector [%.6f %.6f %.6f]', self.x, self.y, self.z) +end + +--[[ + @doc + @fname LVector:__call + + @returns + LVector: copy +]] +function meta:__call() + return LVector(self) +end + +-- Vector maths + +local math = math + +--[[ + @doc + @fname LVector:Length + + @returns + number +]] +function meta:Length() + return math.sqrt(self.x:pow(2) + self.y:pow(2) + self.z:pow(2)) +end + +--[[ + @doc + @fname LVector:LengthSqr + + @returns + number +]] +function meta:LengthSqr() + return self.x:pow(2) + self.y:pow(2) + self.z:pow(2) +end + +--[[ + @doc + @fname LVector:Length2D + + @returns + number +]] +function meta:Length2D() + return math.sqrt(self.x:pow(2) + self.y:pow(2)) +end + +--[[ + @doc + @fname LVector:Length2DSqr + + @returns + number +]] +function meta:Length2DSqr() + return self.x:pow(2) + self.y:pow(2) +end + +function meta:Normalize() + local len = self:Length() + self.x, self.y, self.z = self.x / len, self.y / len, self.z / len + return self +end + +--[[ + @doc + @fname LVector:GerNormalized + + @returns + LVector: normalized copy of vector +]] +function meta:GetNormalized() + local len = self:Length() + return LVector(self.x / len, self.y / len, self.z / len) +end + +--[[ + @doc + @fname LVector:IsNormalized + + @returns + boolean +]] +function meta:IsNormalized() + return self.x <= 1 and self.y <= 1 and self.z <= 1 and self.x >= -1 and self.y >= -1 and self.z >= -1 +end + +local UP = LVector(0, 0, 1) +local FORWARD = LVector(1, 0, 0) +local LEFT = LVector(0, 1, 0) + +--[[ + @doc + @fname LVector:Dot + @args LVector another + + @desc + See !g:Vector:Dot + @enddesc + + @returns + number +]] +function meta:Dot(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:Dot(' .. type(another) .. ') - invalid call') + end + + local scalar = + self.x * another.x + + self.y * another.y + + self.z * another.z + + local len = self:Length() * another:Length() + + if len == 0 then return 0 end + + return scalar / len +end + + +--[[ + @doc + @fname LVector:IsZero + + @returns + boolean +]] +function meta:IsZero() + return self.x == 0 and self.y == 0 and self.z == 0 +end + +--[[ + @doc + @fname LVector:Lerp + @args number lerpValue, LVector lerpTo + + @returns + LVector: copy +]] +function meta:Lerp(lerpValue, lerpTo) + if type(lerpTo) ~= 'Vector' and type(lerpTo) ~= 'LVector' then + error('LVector:Lerp(' .. type(lerpValue) .. ', ' .. type(lerpTo) .. ') - invalid call') + end + + local x = Lerp(lerpValue, self.x, lerpTo.x) + local y = Lerp(lerpValue, self.y, lerpTo.x) + local z = Lerp(lerpValue, self.z, lerpTo.z) + + return LVector(x, y, z) +end + +meta.CosinusBetween = meta.Dot +meta.DegreeBetween = meta.Dot + +-- Various vanilla things + +--[[ + @doc + @fname LVector:Add + @args LVector another + + @desc + Adds another vector + This method **modifies** original LVector + @enddesc + + @returns + LVector: self +]] +function meta:Add(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:Add(' .. type(another) .. ') - invalid call') + end + + self.x = self.x + another.x + self.y = self.y + another.y + self.z = self.z + another.z + + return self +end + +--[[ + @doc + @fname LVector:Sub + @args LVector another + + @desc + Substrates another vector + This method **modifies** original LVector + @enddesc + + @returns + LVector: self +]] +function meta:Sub(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:Sub(' .. type(another) .. ') - invalid call') + end + + self.x = self.x - another.x + self.y = self.y - another.y + self.z = self.z - another.z + + return self +end + +--[[ + @doc + @fname LVector:Set + @args LVector another + + @desc + This method **modifies** original LVector + This method sets `x`, `y`, and `z` to their corresponding values from `another` + @enddesc + + @returns + LVector: self +]] +function meta:Set(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:Set(' .. type(another) .. ') - invalid call') + end + + self.x = another.x + self.y = another.y + self.z = another.z + + return self +end + +--[[ + @doc + @fname LVector:Zero + @args LVector another + + @desc + This method **modifies** original LVector + sets `x`, `y` and `z` to 0 + @enddesc + + @returns + LVector: self +]] +function meta:Zero() + self.x = 0 + self.y = 0 + self.z = 0 + + return self +end + +--[[ + @doc + @fname LVector:WithinAABox + @args LVector mins, LVector maxs + + @returns + boolean +]] +function meta:WithinAABox(mins, maxs) + if type(mins) ~= 'Vector' and type(mins) ~= 'LVector' then + error('LVector:WithinAABox(' .. type(mins) .. ', ' .. type(maxs) .. ') - invalid call') + end + + if type(maxs) ~= 'Vector' and type(maxs) ~= 'LVector' then + error('LVector:WithinAABox(' .. type(mins) .. ', ' .. type(maxs) .. ') - invalid call') + end + + return self.x >= mins.x + and self.y >= mins.y + and self.z >= mins.z + and self.x <= maxs.x + and self.y <= maxs.y + and self.z <= maxs.z +end + +--[[ + @doc + @fname LVector:Distance + @args LVector another + + @returns + number: length in hammer units +]] +function meta:Distance(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:Distance(' .. type(another) .. ') - invalid call') + end + + return math.sqrt((self.x - another.x):pow(2) + (self.y - another.y):pow(2) + (self.z - another.z):pow(2)) +end + +--[[ + @doc + @fname LVector:DistToSqr + @args LVector another + + @returns + number: length in hammer units without square rooting +]] +function meta:DistToSqr(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:DistToSqr(' .. type(another) .. ') - invalid call') + end + + return (self.x - another.x):pow(2) + (self.y - another.y):pow(2) + (self.z - another.z):pow(2) +end + +--[[ + @doc + @fname LVector:Mul + @args LVector another + + @desc + Multiplies this vector by another + This method **modifies** original LVector + @enddesc + + @returns + LVector: self +]] +function meta:Mul(number) + if type(number) ~= 'number' then + error('LVector:Mul(' .. type(number) .. ') - invalid call') + end + + self.x = self.x * number + self.y = self.y * number + self.z = self.z * number + + return self +end + +--[[ + @doc + @fname LVector:Div + @args LVector another + + @desc + Divides this vector by another + This method **modifies** original LVector + @enddesc + + @returns + LVector: self +]] +function meta:Div(number) + if type(number) ~= 'number' then + error('LVector:Div(' .. type(number) .. ') - invalid call') + end + + self.x = self.x / number + self.y = self.y / number + self.z = self.z / number + + return self +end + +--[[ + @doc + @fname LVector:ToColor + + @returns + Color +]] +function meta:ToColor() + return Color(self.x / 255, self.y / 255, self.z / 255) +end + +--[[ + @doc + @fname LVector:Angle + + @returns + Angle +]] +function meta:Angle() + local normal = self:GetNormalized() + + local x, y = normal.x, normal.y + local add = 45 + + if x > 0 and y < 0 then + add = -45 + end + + local dotPitch = normal:Dot(UP) + local dotYaw = LVector(x, y):Dot(LVector(1, 1)):acos():deg() + add + + if x > 0 and y < 0 then + dotYaw = -dotYaw + end + + return Angle(-dotPitch:asin():deg(), dotYaw, 0) +end + +-- Extended + +--[[ + @doc + @fname LVector:Invert + @args LVector another + + @desc + Inverts sign of coordinates + This method **modifies** original LVector + @enddesc + + @returns + LVector: self +]] +function meta:Invert() + return LVector(-self.x, -self.y, -self.z) +end + +--[[ + @doc + @fname LVector:IsValid + + @returns + boolean: whenever vector contains valid coordinates +]] +function meta:IsValid() + return + self.x == self.x + and self.y == self.y + and self.z == self.z + and self.x ~= self.x.huge + and self.y ~= self.y.huge + and self.z ~= self.z.huge + and self.x ~= -self.x.huge + and self.y ~= -self.y.huge + and self.z ~= -self.z.huge +end + +-- Lua metamethods + +--[[ + @doc + @fname LVector:__unm + + @desc + -var1 + returns inverted vector + @enddesc + + @returns + LVector: copy +]] +function meta:__unm() + return LVector(self):Invert() +end + +--[[ + @doc + @fname LVector:__eq + @args LVector another + + @desc + var1 == var2 + @enddesc + + @returns + LVector: copy +]] +function meta:__eq(another) + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__eq(' .. type(another) .. ') - invalid call') + end + + return self.x == another.x and self.y == another.y and self.z == another.z +end + +--[[ + @doc + @fname LVector:__add + @args LVector another + + @desc + var1 + var2 + @enddesc + + @returns + LVector: copy +]] +function meta:__add(another) + --[[if type(self) == 'number' then + return LVector(another.x + self, another.y + self, another.z + self) + end + + if type(another) == 'number' then + return LVector(another + self.x, another + self.y, another + self.z) + end]] + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__add(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__add(' .. type(self) .. ') - invalid call') + end + + return LVector(another.x + self.x, another.y + self.y, another.z + self.z) +end + +--[[ + @doc + @fname LVector:__sub + @args LVector another + + @desc + var1 - var2 + @enddesc + + @returns + LVector: copy +]] +function meta:__sub(another) + --[[if type(self) == 'number' then + return LVector(another.x - self, another.y - self, another.z - self) + end + + if type(another) == 'number' then + return LVector(self.x - another, self.y - another, self.z - another) + end]] + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__sub(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__sub(' .. type(self) .. ') - invalid call') + end + + return LVector(self.x - another.x, self.y - another.x, self.z - another.x) +end + +--[[ + @doc + @fname LVector:__mul + @args LVector another + + @desc + var1 * var2 + @enddesc + + @returns + LVector: copy +]] +function meta:__mul(another) + if type(self) == 'number' then + return LVector(another.x * self, another.y * self, another.z * self) + end + + if type(another) == 'number' then + return LVector(self.x * another, self.y * another, self.z * another) + end + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__mul(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__mul(' .. type(self) .. ') - invalid call') + end + + return LVector(self.x * another.x, self.y * another.x, self.z * another.x) +end + +--[[ + @doc + @fname LVector:__div + @args LVector another + + @desc + var1 / var2 + @enddesc + + @returns + LVector: copy +]] +function meta:__div(another) + if type(self) == 'number' then + return LVector(another.x / self, another.y / self, another.z / self) + end + + if type(another) == 'number' then + return LVector(self.x / another, self.y / another, self.z / another) + end + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__div(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__div(' .. type(self) .. ') - invalid call') + end + + return LVector(self.x / another.x, self.y / another.x, self.z / another.x) +end + +--[[ + @doc + @fname LVector:__mod + @args any another + + @desc + var1 % var2 + this function accept `Vector`, `LVector` and `number` arguments + @enddesc + + @returns + LVector: copy +]] +function meta:__mod(another) + if type(self) == 'number' then + return LVector(another.x % self, another.y % self, another.z % self) + end + + if type(another) == 'number' then + return LVector(self.x % another, self.y % another, self.z % another) + end + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__mod(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__mod(' .. type(self) .. ') - invalid call') + end + + return LVector(self.x % another.x, self.y % another.x, self.z % another.x) +end + +--[[ + @doc + @fname LVector:__lt + @args any another + + @desc + var1 < var2 + this function accept `Vector`, `LVector` and `number` arguments + @enddesc + + @returns + boolean +]] +function meta:__lt(another) + if type(self) == 'number' then + return another:Length() < self + end + + if type(another) == 'number' then + return self:Length() < another + end + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__lt(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__lt(' .. type(self) .. ') - invalid call') + end + + return self:Length() < another:Length() +end + +--[[ + @doc + @fname LVector:__le + @args any another + + @desc + var1 <= var2 + this function accept `Vector`, `LVector` and `number` arguments + @enddesc + + @returns + boolean +]] +function meta:__le(another) + if type(self) == 'number' then + return another:Length() <= self + end + + if type(another) == 'number' then + return self:Length() <= another + end + + if type(another) ~= 'Vector' and type(another) ~= 'LVector' then + error('LVector:__le(' .. type(another) .. ') - invalid call') + end + + if type(self) ~= 'Vector' and type(self) ~= 'LVector' then + error('LVector:__le(' .. type(self) .. ') - invalid call') + end + + return self:Length() <= another:Length() +end + +--[[ + @doc + @fname LVector:__concat + @args any another + + @desc + var1 .. var2 + @enddesc + + @returns + string +]] +function meta:__concat(another) + if type(self) == 'LVector' then + return string.format('Vector [%.2f %.2f %.2f]', self.x, self.y, self.z) .. another + else + return self .. string.format('Vector [%.2f %.2f %.2f]', another.x, another.y, another.z) + end +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/nbt.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/nbt.lua new file mode 100644 index 0000000..c966f5d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/nbt.lua @@ -0,0 +1,1509 @@ +local assert, error, DLib, table +do + local _obj_0 = _G + assert, error, DLib, table = _obj_0.assert, _obj_0.error, _obj_0.DLib, _obj_0.table +end +local type = luatype +jit.on() +if DLib.NBT then + for k, v in pairs(DLib.NBT) do + DLib.NBT[k] = nil + end +end +DLib.NBT = DLib.NBT or { } +local Typed = { } +local TypedByID = { } +local TypeID = { + TAG_End = 0, + TAG_Byte = 1, + TAG_Short = 2, + TAG_Int = 3, + TAG_Long = 4, + TAG_Float = 5, + TAG_Double = 6, + TAG_Byte_Array = 7, + TAG_String = 8, + TAG_List = 9, + TAG_Compound = 10, + TAG_Int_Array = 11, + TAG_Long_Array = 12 +} +do + local _class_0 + local _base_0 = { + GetDefault = function(self) + return 0 + end, + GetLength = function(self) + return self.length + end, + CheckValue = function(self, value) + return assert(type(value) ~= 'nil', 'value can not be nil') + end, + GetValue = function(self) + return self.value + end, + SetValue = function(self, value) + if value == nil then + value = self.value + end + self:CheckValue(value) + self.value = value + end, + Serialize = function(self, bytesbuffer) + return error('Method not implemented') + end, + Deserialize = function(self, bytesbuffer) + return error('Method not implemented') + end, + GetTagID = function(self) + return self.__class.TAG_ID + end, + GetPayload = function(self) + return self:GetLength() + end, + PayloadLength = function(self) + return self:GetPayload() + end, + Varies = function(self) + return false + end, + FixedPayloadLength = function(self) + return self.__class.FIXED_LENGTH + end, + Name = function(self) + return self.__class.NAME + end, + GetName = function(self) + return self:Name() + end, + Nick = function(self) + return self:Name() + end, + GetNick = function(self) + return self:Name() + end, + GetType = function(self) + return 'undefined' + end, + MetaName = 'NBTBase', + IsBase = function(self) + return self:Name() == 'TAG_Base' + end, + IsEnd = function(self) + return self:Name() == 'TAG_End' + end, + IsByte = function(self) + return self:Name() == 'TAG_Byte' + end, + IsShort = function(self) + return self:Name() == 'TAG_Short' + end, + IsInt = function(self) + return self:Name() == 'TAG_Int' + end, + IsLong = function(self) + return self:Name() == 'TAG_Long' + end, + IsFloat = function(self) + return self:Name() == 'TAG_Float' + end, + IsDouble = function(self) + return self:Name() == 'TAG_Double' + end, + IsByteArray = function(self) + return self:Name() == 'TAG_Byte_Array' + end, + IsString = function(self) + return self:Name() == 'TAG_String' + end, + IsList = function(self) + return self:Name() == 'TAG_List' + end, + IsTagCompound = function(self) + return self:Name() == 'TAG_Compound' + end, + IsCompound = function(self) + return self:Name() == 'TAG_Compound' + end, + IsIntArray = function(self) + return self:Name() == 'TAG_Int_Array' + end, + IsLongArray = function(self) + return self:Name() == 'TAG_Long_Array' + end, + __tostring = function(self) + return self:Name() .. '[' .. self.__class.NAME .. '][' .. tostring(self.value) .. ']' + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, value) + if value == nil then + value = self:GetDefault() + end + if not self.length then + self.length = 0 + end + self.value = value + if value ~= nil then + return self:CheckValue(value) + end + end, + __base = _base_0, + __name = "Base" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = -1 + self.NAME = 'TAG_Base' + DLib.NBT.Base = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + Serialize = function(self, bytesbuffer) + return self + end, + Deserialize = function(self, bytesbuffer) + return self + end, + GetPayload = function(self) + return 0 + end, + FixedPayloadLength = function(self) + return 0 + end, + MetaName = 'NBTTagEnd', + GetType = function(self) + return 'end' + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagEnd", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.NAME = 'TAG_End' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagEnd = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + _class_0.__parent.__base.CheckValue(self, value) + if type(value) ~= 'number' then + error('Value must be a number! ' .. type(value) .. ' given.') + end + return assert(value >= -0x80 and value < 0x80, 'value overflow') + end, + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteByte(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.value = bytesbuffer:ReadByte() + return self + end, + GetPayload = function(self) + return 1 + end, + GetType = function(self) + return 'byte' + end, + MetaName = 'NBTByte' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagByte", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = 1 + self.NAME = 'TAG_Byte' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagByte = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + _class_0.__parent.__base.CheckValue(self, value) + if type(value) ~= 'number' then + error('Value must be a number! ' .. type(value) .. ' given.') + end + return assert(value >= -0x8000 and value < 0x8000, 'value overflow') + end, + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteInt16(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.value = bytesbuffer:ReadInt16() + return self + end, + GetPayload = function(self) + return 2 + end, + GetType = function(self) + return 'short' + end, + MetaName = 'NBTShort' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagShort", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = 2 + self.NAME = 'TAG_Short' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagShort = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + _class_0.__parent.__base.CheckValue(self, value) + if type(value) ~= 'number' then + error('Value must be a number! ' .. type(value) .. ' given.') + end + return assert(value >= -0x7FFFFFFF and value < 0x7FFFFFFF, 'value overflow') + end, + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteInt32(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.value = bytesbuffer:ReadInt32() + return self + end, + GetPayload = function(self) + return 4 + end, + GetType = function(self) + return 'int' + end, + MetaName = 'NBTInt' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagInt", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = 4 + self.NAME = 'TAG_Int' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagInt = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + _class_0.__parent.__base.CheckValue(self, value) + if type(value) ~= 'number' then + error('Value must be a number! ' .. type(value) .. ' given.') + end + return assert(value >= -9223372036854775808 and value < 9223372036854775808, 'value overflow') + end, + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteInt64(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.value = bytesbuffer:ReadInt64() + return self + end, + GetPayload = function(self) + return 8 + end, + GetType = function(self) + return 'long' + end, + MetaName = 'NBTLong' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagLong", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = 8 + self.NAME = 'TAG_Long' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagLong = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + _class_0.__parent.__base.CheckValue(self, value) + if type(value) ~= 'number' then + return error('Value must be a number! ' .. type(value) .. ' given.') + end + end, + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteFloat(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.value = bytesbuffer:ReadFloat() + return self + end, + GetPayload = function(self) + return 4 + end, + GetType = function(self) + return 'float' + end, + MetaName = 'NBTFloat' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagFloat", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = 4 + self.NAME = 'TAG_Float' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagFloat = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + _class_0.__parent.__base.CheckValue(self, value) + if type(value) ~= 'number' then + return error('Value must be a number! ' .. type(value) .. ' given.') + end + end, + Serialize = function(self, bytesbuffer) + return bytesbuffer:WriteDouble(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.value = bytesbuffer:ReadDouble() + return self + end, + GetPayload = function(self) + return 8 + end, + GetType = function(self) + return 'double' + end, + MetaName = 'NBTDouble' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagDouble", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIXED_LENGTH = 8 + self.NAME = 'TAG_Double' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagDouble = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + CheckValue = function(self, value) + return _class_0.__parent.__base.CheckValue(self, value) and assert(#value < 65536, 'String is too long!') + end, + Serialize = function(self, bytesbuffer) + bytesbuffer:WriteUInt16(#self.value) + return bytesbuffer:WriteBinary(self.value) + end, + Deserialize = function(self, bytesbuffer) + self.length = bytesbuffer:ReadUInt16() + self.value = bytesbuffer:ReadBinary(self.length) + return self + end, + GetDefault = function(self) + return '' + end, + GetLength = function(self) + return #self.value + end, + GetPayload = function(self) + return 2 + self:GetLength() + end, + GetType = function(self) + return 'string' + end, + MetaName = 'NBTString' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagString", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.NAME = 'TAG_String' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagString = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + GetArray = function(self) + return self.array + end, + ExtractValue = function(self, index) + if index == nil then + index = 1 + end + return self.array[index] + end, + AtIndex = function(self, index) + if index == nil then + index = 1 + end + return self.array[index] + end, + GetIndex = function(self, index) + if index == nil then + index = 1 + end + return self.array[index] + end, + GetValue = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.array + for _index_0 = 1, #_list_0 do + local tag = _list_0[_index_0] + _accum_0[_len_0] = tag:GetValue() + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + CopyArray = function(self) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.array + for _index_0 = 1, #_list_0 do + local tag = _list_0[_index_0] + _accum_0[_len_0] = tag + _len_0 = _len_0 + 1 + end + return _accum_0 + end, + iterate = function(self) + return ipairs(self.array) + end, + iterator = function(self) + return ipairs(self.array) + end, + pairs = function(self) + return ipairs(self.array) + end, + ipairs = function(self) + return ipairs(self.array) + end, + AddValue = function(self, value) + return error('Method not implemented') + end, + SerializeLength = function(self, bytesbuffer, length) + if length == nil then + length = self.length + end + return bytesbuffer:WriteUInt32(length) + end, + DeserializeLength = function(self, bytesbuffer) + return bytesbuffer:ReadUInt32() + end, + Serialize = function(self, bytesbuffer) + self:SerializeLength(bytesbuffer, self.length) + local _list_0 = self.array + for _index_0 = 1, #_list_0 do + local tag = _list_0[_index_0] + tag:Serialize(bytesbuffer) + end + return self + end, + ReadTag = function(self, bytesbuffer) + return error('No tag is specified as array type') + end, + Deserialize = function(self, bytesbuffer) + self.length = self:DeserializeLength(bytesbuffer) + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, self.length do + _accum_0[_len_0] = self:ReadTag(bytesbuffer) + _len_0 = _len_0 + 1 + end + self.array = _accum_0 + end + return self + end, + GetPayload = function(self) + return self.length * self.__class.FIELD_LENGTH + self.RANGE + end, + GetType = function(self) + return 'array_undefined' + end, + MetaName = 'NBTArray', + __tostring = function(self) + return self:Name() .. '[' .. self.__class.NAME .. '][' .. self.length .. ']{' .. tostring(self.array) .. '}' + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, values) + _class_0.__parent.__init(self, 'array', -1) + self.array = { } + if values then + local _list_0 = values + for _index_0 = 1, #_list_0 do + local value = _list_0[_index_0] + self:AddValue(value) + end + end + end, + __base = _base_0, + __name = "TagArrayBased", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIELD_LENGTH = 1 + self.RANGE = 4 + self.NAME = 'TAG_Array' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagArrayBased = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.TagArrayBased + local _base_0 = { + AddValue = function(self, value) + self.length = self.length + 1 + table.insert(self:GetArray(), DLib.NBT.TagByte(value)) + return self + end, + ReadTag = function(self, bytesbuffer) + return DLib.NBT.TagByte():Deserialize(bytesbuffer) + end, + GetType = function(self) + return 'array_bytes' + end, + MetaName = 'NBTArrayBytes' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagByteArray", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIELD_LENGTH = 1 + self.RANGE = 4 + self.NAME = 'TAG_Byte_Array' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagByteArray = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.TagArrayBased + local _base_0 = { + AddValue = function(self, value) + self.length = self.length + 1 + table.insert(self:GetArray(), DLib.NBT.TagInt(value)) + return self + end, + ReadTag = function(self, bytesbuffer) + return DLib.NBT.TagInt():Deserialize(bytesbuffer) + end, + GetType = function(self) + return 'array_ints' + end, + MetaName = 'NBTArrayInt' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagIntArray", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIELD_LENGTH = 4 + self.RANGE = 4 + self.NAME = 'TAG_Int_Array' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagIntArray = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.TagArrayBased + local _base_0 = { + AddValue = function(self, value) + self.length = self.length + 1 + table.insert(self:GetArray(), DLib.NBT.TagLong(value)) + return self + end, + ReadTag = function(self, bytesbuffer) + return DLib.NBT.TagLong():Deserialize(bytesbuffer) + end, + GetType = function(self) + return 'array_longs' + end, + MetaName = 'NBTArrayLong' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "TagLongArray", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIELD_LENGTH = 8 + self.RANGE = 4 + self.NAME = 'TAG_Long_Array' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagLongArray = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.TagArrayBased + local _base_0 = { + Serialize = function(self, bytesbuffer) + bytesbuffer:WriteUByte(self.tagID) + bytesbuffer:WriteUInt32(self.length) + local _list_0 = self:GetArray() + for _index_0 = 1, #_list_0 do + local tag = _list_0[_index_0] + tag:Serialize(bytesbuffer) + end + return self + end, + Deserialize = function(self, bytesbuffer) + self.tagID = bytesbuffer:ReadUByte() + self.tagClass = DLib.NBT.GetTyped(self.tagID) + if not self.tagClass then + error('Invalid tag ID specified as array type - ' .. self.tagID) + end + self.length = bytesbuffer:ReadUInt32() + do + local _accum_0 = { } + local _len_0 = 1 + for i = 1, self.length do + _accum_0[_len_0] = self:ReadTag(bytesbuffer) + _len_0 = _len_0 + 1 + end + self.array = _accum_0 + end + return self + end, + AddValue = function(self, ...) + self.length = self.length + 1 + local classIn = self.tagClass + if not classIn then + error('Invalid tag ID specified as array type - ' .. self.tagID) + end + if DLib.NBT.VALID_META[type(select(1, ...))] or type(select(1, ...)) == 'table' and select(1, ...).Serialize then + table.insert(self:GetArray(), select(1, ...)) + else + table.insert(self:GetArray(), classIn(...)) + end + return self + end, + ReadTag = function(self, bytesbuffer) + local classIn = self.tagClass + return classIn():Deserialize(bytesbuffer) + end, + GetPayload = function(self) + local output = 5 + local _list_0 = self:GetArray() + for _index_0 = 1, #_list_0 do + local tag = _list_0[_index_0] + output = output + tag:GetPayload() + end + return output + end, + GetType = function(self) + return 'array' + end, + MetaName = 'NBTList', + __tostring = function(self) + return self:Name() .. '[' .. self.__class.NAME .. '][' .. (DLib.NBT.TYPEID_F[self.tagClass.TAG_ID] or 'ERROR') .. '][' .. self.length .. ']{' .. tostring(self.array) .. '}' + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, tagID, values) + if tagID == nil then + tagID = 1 + end + self.tagID = tagID + self.tagClass = DLib.NBT.GetTyped(tagID) + if not self.tagClass then + error('Invalid tag ID specified as array type - ' .. tagID) + end + _class_0.__parent.__init(self, name) + if values then + for i, val in ipairs(values) do + self:AddValue(val) + end + end + end, + __base = _base_0, + __name = "TagList", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.FIELD_LENGTH = -1 + self.RANGE = 4 + self.NAME = 'TAG_List' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagList = _class_0 +end +do + local _class_0 + local _parent_0 = DLib.NBT.Base + local _base_0 = { + GetTagName = function(self) + return self.name + end, + TagName = function(self) + return self:GetTagName() + end, + SetTagName = function(self, name) + if name == nil then + name = self.name + end + self.name = name + end, + ReadFile = function(self, bytesbuffer) + local status = ProtectedCall(function() + return self:ReadFileProtected(bytesbuffer) + end) + if not status then + Error('Error reading a NBT file from Bytes Buffer! Is file/buffer a valid NBT file and is not corrupted?\n') + end + return status + end, + ReadFileProtected = function(self, bytesbuffer) + assert(bytesbuffer:ReadUByte() == 10, 'Invalid header. Is file corrupted or not a valid NBT file?') + local readNameLen = bytesbuffer:ReadUInt16() + self:SetTagName(bytesbuffer:ReadBinary(readNameLen)) + return self:Deserialize(bytesbuffer) + end, + WriteFile = function(self, bytesbuffer) + bytesbuffer:WriteUByte(self:GetTagID()) + bytesbuffer:WriteUInt16(#self:GetTagName()) + bytesbuffer:WriteBinary(self:GetTagName()) + return self:Serialize(bytesbuffer) + end, + Serialize = function(self, bytesbuffer) + for key, tag in pairs(self.table) do + bytesbuffer:WriteUByte(tag:GetTagID()) + bytesbuffer:WriteUInt16(#key) + bytesbuffer:WriteBinary(key) + tag:Serialize(bytesbuffer) + end + bytesbuffer:WriteUByte(0) + return self + end, + Deserialize = function(self, bytesbuffer) + while true do + local readTagID = bytesbuffer:ReadUByte() + if readTagID == 0 then + break + end + local classIn = DLib.NBT.GetTyped(readTagID) + local readIDLen = bytesbuffer:ReadUInt16() + local readID = bytesbuffer:ReadBinary(readIDLen) + local readTag + if readTagID == TypeID.TAG_Compound then + readTag = classIn(readID) + else + readTag = classIn() + end + readTag:Deserialize(bytesbuffer) + self:AddTag(readID, readTag) + end + return self + end, + AddTag = function(self, key, value) + if key == nil then + key = '' + end + self.table[tostring(key)] = value + if value.SetTagName then + value:SetTagName(key) + end + return self + end, + SetTag = function(self, ...) + return self:AddTag(...) + end, + SetTag2 = function(self, ...) + return self:AddTag2(...) + end, + AddTag2 = function(self, key, value) + if key == nil then + key = '' + end + self:AddTag(key, value) + return value + end, + RemoveTag = function(self, key) + if key == nil then + key = '' + end + self.table[tostring(key)] = nil + return self + end, + __tostring = function(self) + return self:Name() .. '[' .. self:GetTagName() .. '][?]{' .. tostring(self.table) .. '}' + end, + GetValue = function(self) + local _tbl_0 = { } + for key, tag in pairs(self.table) do + _tbl_0[key] = tag:GetValue() + end + return _tbl_0 + end, + iterate = function(self) + return pairs(self.table) + end, + iterator = function(self) + return pairs(self.table) + end, + pairs = function(self) + return pairs(self.table) + end, + HasTag = function(self, key) + if key == nil then + key = '' + end + return self.table[tostring(key)] ~= nil + end, + GetTag = function(self, key) + if key == nil then + key = '' + end + return self.table[tostring(key)] + end, + GetTagValue = function(self, key) + if key == nil then + key = '' + end + return self.table[tostring(key)]:GetValue() + end, + AddByte = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagByte(value)) + end, + AddBool = function(self, key, value) + if key == nil then + key = '' + end + if value == nil then + value = false + end + return self:AddTag(key, DLib.NBT.TagByte(value and 1 or 0)) + end, + AddShort = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagShort(value)) + end, + AddInt = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagInt(value)) + end, + AddFloat = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagFloat(value)) + end, + AddDouble = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagDouble(value)) + end, + AddLong = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagLong(value)) + end, + AddString = function(self, key, value) + if key == nil then + key = '' + end + return self:AddTag(key, DLib.NBT.TagString(value)) + end, + AddByteArray = function(self, key, values) + if key == nil then + key = '' + end + return self:AddTag2(key, DLib.NBT.TagByteArray(values)) + end, + AddIntArray = function(self, key, values) + if key == nil then + key = '' + end + return self:AddTag2(key, DLib.NBT.TagIntArray(values)) + end, + AddLongArray = function(self, key, values) + if key == nil then + key = '' + end + return self:AddTag2(key, DLib.NBT.TagLongArray(values)) + end, + AddTagList = function(self, key, tagID, values) + if key == nil then + key = '' + end + return self:AddTag2(key, DLib.NBT.TagList(tagID, values)) + end, + AddTagCompound = function(self, key, values) + if key == nil then + key = '' + end + return self:AddTag2(key, DLib.NBT.TagCompound(key, values)) + end, + AddTypedValue = function(self, key, value) + if key == nil then + key = '' + end + local _exp_0 = type(value) + if 'number' == _exp_0 then + return self:AddDouble(key, value) + elseif 'string' == _exp_0 then + return self:AddString(key, value) + elseif 'table' == _exp_0 then + return self:AddTagCompound(key, vaue) + else + return error('Unable to tetermine tag type for value - ' .. type(value)) + end + end, + SetByte = function(self, ...) + return self:AddByte(...) + end, + SetBool = function(self, ...) + return self:AddBool(...) + end, + SetShort = function(self, ...) + return self:AddShort(...) + end, + SetInt = function(self, ...) + return self:AddInt(...) + end, + SetFloat = function(self, ...) + return self:AddFloat(...) + end, + SetDouble = function(self, ...) + return self:AddDouble(...) + end, + SetLong = function(self, ...) + return self:AddLong(...) + end, + SetString = function(self, ...) + return self:AddString(...) + end, + SetByteArray = function(self, ...) + return self:AddByteArray(...) + end, + SetIntArray = function(self, ...) + return self:AddIntArray(...) + end, + SetLongArray = function(self, ...) + return self:AddLongArray(...) + end, + SetTagList = function(self, ...) + return self:AddTagList(...) + end, + SetTagCompound = function(self, ...) + return self:AddTagCompound(...) + end, + SetTypedValue = function(self, ...) + return self:AddTypedValue(...) + end, + SetVector = function(self, key, vec) + self:AddTagList(key, TypeID.TAG_Float, { + vec.x, + vec.y, + vec.z + }) + return self + end, + SetAngle = function(self, key, ang) + self:AddTagList(key, TypeID.TAG_Float, { + ang.p, + ang.y, + ang.r + }) + return self + end, + SetColor = function(self, key, color) + self:AddByteArray(key, { + color.r - 128, + color.g - 128, + color.b - 128, + color.a - 128 + }) + return self + end, + GetVector = function(self, key) + local a, b, c + do + local _obj_0 = self:GetTagValue(key) + a, b, c = _obj_0[1], _obj_0[2], _obj_0[3] + end + return Vector(a, b, c) + end, + GetAngle = function(self, key) + local a, b, c + do + local _obj_0 = self:GetTagValue(key) + a, b, c = _obj_0[1], _obj_0[2], _obj_0[3] + end + return Angle(a, b, c) + end, + GetColor = function(self, key) + local a, b, c, d + do + local _obj_0 = self:GetTagValue(key) + a, b, c, d = _obj_0[1], _obj_0[2], _obj_0[3], _obj_0[4] + end + return Color(a + 128, b + 128, c + 128, d + 128) + end, + GetLength = function(self) + return table.Count(self.table) + end, + GetType = function(self) + return 'table' + end, + MetaName = 'NBTCompound' + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, name, values) + if name == nil then + name = 'data' + end + self.name = name + self.table = { } + _class_0.__parent.__init(self) + if values then + for key, value in pairs(values) do + self:AddTypedValue(key, value) + end + end + end, + __base = _base_0, + __name = "TagCompound", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.NAME = 'TAG_Compound' + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + DLib.NBT.TagCompound = _class_0 +end +for k, classname in pairs(DLib.NBT) do + if TypeID[classname.NAME] then + Typed[TypeID[classname.NAME]] = classname + end +end +for k, classname in pairs(DLib.NBT) do + TypedByID[classname.NAME] = classname +end +do + local _tbl_0 = { } + for k, classname in pairs(DLib.NBT) do + _tbl_0[classname.MetaName] = true + end + DLib.NBT.VALID_META = _tbl_0 +end +for typeid, classname in pairs(Typed) do + classname.TAG_ID = typeid +end +DLib.NBT.TYPEID = TypeID +do + local _tbl_0 = { } + for v, k in pairs(TypeID) do + _tbl_0[k] = v + end + DLib.NBT.TYPEID_F = _tbl_0 +end +DLib.NBT.TYPED = Typed +DLib.NBT.TYPED_BY_ID = TypedByID +DLib.NBT.GetTyped = function(index) + if index == nil then + index = 0 + end + if not Typed[index] then + error('invalid tag id specified - ' .. index) + end + return Typed[index] +end +DLib.NBT.GetTypedID = function(id) + if id == nil then + id = 'TAG_Byte' + end + if not TypedByID[id] then + error('invalid tag string id specified - ' .. index) + end + return TypedByID[id] +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/net_ext.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/net_ext.lua new file mode 100644 index 0000000..302779e --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/net_ext.lua @@ -0,0 +1,480 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 net = net +local table = table +local Entity = Entity +local type = type +local error = error + +net.pool = util.AddNetworkString + +--[[ + @doc + @fname net.WritePlayer + @args Player ply +]] +function net.WritePlayer(ply) + local i = ply:EntIndex() + net.WriteUInt(i, 8) + return i +end + +--[[ + @doc + @fname net.ReadPlayer + @returns + Player +]] +function net.ReadPlayer() + return Entity(net.ReadUInt(8)) +end + +--[[ + @doc + @fname net.WriteTypedArray + @args table input, function callback + + @desc + callback should write value provided in first argument + example usage: `net.WriteTypedArray(arr, net.WriteType)` + @enddesc +]] +function net.WriteTypedArray(input, callFunc) + net.WriteUInt(#input, 16) + + for i, value in ipairs(input) do + callFunc(value) + end +end + +--[[ + @doc + @fname net.ReadTypedArray + @args function callback + + @desc + callback should read next value + @enddesc + + @returns + table +]] +function net.ReadTypedArray(callFunc) + return table.construct({}, callFunc, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.WriteArray + @args table arrayIn +]] +function net.WriteArray(input) + net.WriteTypedArray(input, net.WriteType) +end + +--[[ + @doc + @fname net.ReadArray + @returns + table +]] +function net.ReadArray() + return table.construct({}, net.ReadType, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.WriteStringArray + @args table arrayOfStrings +]] +function net.WriteStringArray(input) + net.WriteTypedArray(input, net.WriteString) +end + +--[[ + @doc + @fname net.ReadStringArray + @returns + table: array of strings +]] +function net.ReadStringArray() + return table.construct({}, net.ReadString, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.WriteEntityArray + @args Entity input +]] +function net.WriteEntityArray(input) + net.WriteTypedArray(input, net.WriteEntity) +end + +--[[ + @doc + @fname net.ReadEntityArray + @returns + table: array of entities +]] +function net.ReadEntityArray() + return table.construct({}, net.ReadEntity, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.WritePlayerArray + @args table arrayOfPlayers +]] +function net.WritePlayerArray(input) + net.WriteTypedArray(input, net.WritePlayer) +end + +--[[ + @doc + @fname net.ReadPlayerArray + @returns + table: array of players +]] +function net.ReadPlayerArray() + return table.construct({}, net.ReadPlayer, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.WriteFloatArray + @args table arrayOfFloats +]] + +--[[ + @doc + @fname net.ReadFloatArray + @returns + table: array of floats +]] +function net.WriteFloatArray(input) + net.WriteTypedArray(input, net.WriteFloat) +end + +function net.ReadFloatArray() + return table.construct({}, net.ReadFloat, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.WriteDoubleArray + @args table arrayOfDoubles +]] + +--[[ + @doc + @fname net.ReadDoubleArray + @returns + table: array of doubles +]] +function net.WriteDoubleArray(input) + net.WriteTypedArray(input, net.WriteDouble) +end + +function net.ReadDoubleArray() + return table.construct({}, net.ReadDouble, net.ReadUInt(16)) +end + +--[[ + @doc + @fname net.GReadUInt + @args number bits + + @returns + function: alias of `net.ReadUInt(bits)` +]] + +--[[ + @doc + @fname net.GWriteUInt + @args number bits + + @returns + function: alias of `net.WriteUInt(..., bits)` +]] +function net.GReadUInt(val) + return function() + return net.ReadUInt(val) + end +end + +function net.GWriteUInt(val) + return function(val2) + return net.WriteUInt(val2, val) + end +end + +--[[ + @docpreprocess + + const bits = [8, 16, 32, 64] + + const reply = [] + + for (const bit of bits) { + let output = [] + + output.push(`@fname net.ReadUInt${bit}`) + output.push(`@desc`) + output.push(`typical single-argument alias of it's corresponding call`) + output.push(`@enddesc`) + output.push(`@returns`) + output.push(`number`) + + reply.push(output) + + output = [] + + output.push(`@fname net.ReadInt${bit}`) + output.push(`@desc`) + output.push(`typical single-argument alias of it's corresponding call`) + output.push(`@enddesc`) + output.push(`@returns`) + output.push(`number`) + + reply.push(output) + } + + return reply +]] +net.ReadUInt8 = net.GReadUInt(8) +net.ReadUInt16 = net.GReadUInt(16) +net.ReadUInt32 = net.GReadUInt(32) + +net.WriteUInt8 = net.GWriteUInt(8) +net.WriteUInt16 = net.GWriteUInt(16) +net.WriteUInt32 = net.GWriteUInt(32) + +--[[ + @doc + @fname net.GReadInt + @args number bits + + @returns + function: alias of `net.ReadInt(bits)` +]] + +--[[ + @doc + @fname net.GWriteInt + @args number bits + + @returns + function: alias of `net.WriteInt(..., bits)` +]] +function net.GReadInt(val) + return function() + return net.ReadInt(val) + end +end + +function net.GWriteInt(val) + return function(val2) + return net.WriteInt(val2, val) + end +end + +net.ReadInt8 = net.GReadInt(8) +net.ReadInt16 = net.GReadInt(16) +net.ReadInt32 = net.GReadInt(32) + +net.WriteInt8 = net.GWriteInt(8) +net.WriteInt16 = net.GWriteInt(16) +net.WriteInt32 = net.GWriteInt(32) + +local maxint = 0x100000000 + +--[[ + @doc + @fname net.WriteBigUInt + @args number value + + @desc + due to percision errors, not actually accurate + this function attempts to write a unsigned 64 bits integer to the stream + @enddesc +]] + +--[[ + @doc + @fname net.WriteBigInt + @args number value + + @desc + due to percision errors, not actually accurate + this function attempts to write a 64 bits integer to the stream + @enddesc +]] + +--[[ + @doc + @fname net.ReadBigUInt + + @returns + number: 64 bit unsigned integer +]] + +--[[ + @doc + @fname net.ReadBigInt + + @returns + number: 64 bit integer +]] +function net.WriteBigUInt(val) + local first = val % maxint + local second = (val - first) / maxint + net.WriteUInt32(first) + net.WriteUInt32(second) +end + +function net.ReadBigUInt(val) + local first = net.ReadUInt32() + local second = net.ReadUInt32() + + return first + second * maxint +end + +function net.WriteBigInt(val) + net.WriteBool(val >= 0) + net.WriteBigUInt(val:abs()) +end + +function net.ReadBigInt() + local sign = net.ReadBool() + local value = net.ReadBigUInt() + + if sign then + return value + end + + return -value +end + +net.WriteUInt64 = net.WriteBigUInt +net.WriteInt64 = net.WriteBigInt + +net.ReadUInt64 = net.ReadBigUInt +net.ReadInt64 = net.ReadBigInt + +--[[ + @doc + @fname net.ChooseOptimalBits + @args number maximumPossibilities + + @returns + number: minimal amount of bits required for sending all possible values +]] +function net.ChooseOptimalBits(amount) + local bits = 1 + + while 2 ^ bits <= amount do + bits = bits + 1 + end + + return math.max(bits, 4) +end + +--[[ + @doc + @fname net.WriteVectorDouble + @args Vector value + + @desc + uses `net.WriteDouble` instead of `net.WriteFloat` + @enddesc +]] +function net.WriteVectorDouble(vecIn) + if type(vecIn) ~= 'Vector' then + error('WriteVectorDouble - input is not a vector!') + end + + net.WriteDouble(vecIn.x) + net.WriteDouble(vecIn.y) + net.WriteDouble(vecIn.z) + + return self +end + +--[[ + @doc + @fname net.WriteAngleDouble + @args Angle value + + @desc + uses `net.WriteDouble` instead of `net.WriteFloat` + @enddesc +]] +function net.WriteAngleDouble(angleIn) + if type(angleIn) ~= 'Angle' then + error('WriteAngleDouble - input is not an angle!') + end + + net.WriteDouble(angleIn.p) + net.WriteDouble(angleIn.y) + net.WriteDouble(angleIn.r) + + return self +end + +--[[ + @doc + @fname net.ReadVectorDouble + + @returns + Vector +]] +function net.ReadVectorDouble() + return Vector(net.ReadDouble(), net.ReadDouble(), net.ReadDouble()) +end + +--[[ + @doc + @fname net.ReadAngleDouble + + @returns + Angle +]] +function net.ReadAngleDouble() + return Angle(net.ReadDouble(), net.ReadDouble(), net.ReadDouble()) +end + +local Color = Color + +function net.WriteColor(colIn) + if not IsColor(colIn) then + error('Attempt to write a color which is not a color! ' .. type(colIn)) + end + + net.WriteUInt(colIn.r, 8) + net.WriteUInt(colIn.g, 8) + net.WriteUInt(colIn.b, 8) + net.WriteUInt(colIn.a, 8) +end + +function net.ReadColor() + return Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/predictedvars.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/predictedvars.lua new file mode 100644 index 0000000..20eec45 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/predictedvars.lua @@ -0,0 +1,577 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +-- Those are based on source engine entities + +-- Willox (C) +-- it's less of a limitation and more of proper usage +-- a real source mod is going to add dtvars to the player class, they are a compile-time thing so addons can't really do that +-- although tf2 does have certain controller entities that parent to players and have dtvars, so i guess it's not too different to that +-- i didn't think the jetpack would be so many lines of code... not a great example + +-- despite everything +-- this module does not work (because entities don't work they way i want to use them for this) +-- consider using DLib.PredictedVarList +-- or NW2Vars when their proper version get merged with stable/x64/chromium branch + +local net = net +local DLib = DLib +local string = string +local table = table +local pairs = pairs +local math = math + +if SERVER then + net.pool('dlib_pred_ack') +end + +DLib.pred = DLib.pred or {} +local pred = DLib.pred +local plyMeta = FindMetaTable('Player') + +pred._ack = {} + +local lockRebuild = false + +pred.Vars = pred.Vars or {} +pred._known = pred._known or {} +pred._Vars = pred._Vars or {} +pred.MaxEnt = pred.MaxEnt or 0 + +pred.SlotCounters = pred.SlotCounters or { + String = 0, + Bool = 0, + Float = 0, + Int = 0, + Vector = 0, + Angle = 0, + Entity = 0, +} + +pred.Slots = pred.Slots or { + String = {}, + Bool = {}, + Float = {}, + Int = {}, + Vector = {}, + Angle = {}, + Entity = {}, +} + +--[[ + @doc + @fname DLib.pred.Reload + @internal + + @desc + Forcefully rebuilds all entity definition for mimicking. Does nothing when gmod version is fresh enough. + @enddesc +]] +function pred.Reload() + local vars = pred.Vars + pred.Vars = {} + pred.MaxEnt = 0 + pred._known = {} + pred._Vars = {} + + pred.SlotCounters = { + String = 0, + Bool = 0, + Float = 0, + Int = 0, + Vector = 0, + Angle = 0, + Entity = 0, + } + + lockRebuild = true + + for key, value in pairs(vars) do + pred.Define(key, value.type, value.default) + end + + lockRebuild = false + + for i = 0, pred.MaxEnt do + pred.RebuildEntityDefinition(i) + end +end + +--[[ + @doc + @fname DLib.pred.Define + @args string identify, string type, any default + @deprecated + + @desc + If gmod version is good enough, this function just defines NW2Vars methods to player metatable based + on identify you provided (e.g.) + `plyMeta['Get' .. identify]` + `plyMeta['Set' .. identify]` + `plyMeta['Add' .. identify]` + `plyMeta['Reset' .. identify]` + + if gmod version is old, it tries to mimic NW2Vars behavior by creating predicted network entities + (which of course sometimes do and sometimes do not work properly, thanks to gmod) + Also, in such case, you have to call `Player:DLibInvalidatePrediction(true)` and `Player:DLibInvalidatePrediction(false)` + where you would normally call !g:Entity:SetPredictable + `Player:DLibInvalidatePrediction` is safe to be called serverside and does nothing there + + Types for `type` argument you can find on [Data Tables](https://wiki.garrysmod.com/page/Networking_Entities) gmod wiki page + This function is marked as deprecated since it is somewhat *very* dirty hack over gmod, + but it probably won't be removed + @enddesc +]] +function pred.Define(identify, mtype, default) + if VERSION >= 0x0002e8fb then + plyMeta['Get' .. identify] = function(self) + return self['GetNW2' .. mtype](self, identify, default) + end + + plyMeta['Set' .. identify] = function(self, val) + return self['SetNW2' .. mtype](self, identify, val) + end + + plyMeta['Add' .. identify] = function(self, val, min, max) + if min and not max then + if val < 0 then + return self['SetNW2' .. mtype](self, identify, math.max(self['GetNW2' .. mtype](self, identify, default) + val, min)) + else + return self['SetNW2' .. mtype](self, identify, math.min(self['GetNW2' .. mtype](self, identify, default) + val, min)) + end + elseif min and max then + return self['SetNW2' .. mtype](self, identify, math.clamp(self['GetNW2' .. mtype](self, identify, default) + val, min, max)) + end + + return self['SetNW2' .. mtype](self, identify, self['GetNW2' .. mtype](self, identify, default) + val) + end + + plyMeta['Reset' .. identify] = function(self) + return self['SetNW2' .. mtype](self, identify, default) + end + + return + end + + if pred.Vars[identify] then + -- assert(pred.Vars[identify].type == mtype, 'Can not change type of variable at runtime') + + if pred.Vars[identify].type ~= mtype then + pred.Vars[identify].default = default + pred.Vars[identify].type = mtype + + pred.Reload() + + return + end + + pred.Vars[identify].default = default + + return + end + + if not pred.SlotCounters[mtype] then + error('Invalid variable type provided: ' .. mtype) + end + + local crc = util.CRC(identify):tonumber() + + if crc < 0 then + crc = 0xFFFFFFFF + crc + end + + pred.Vars[identify] = { + identify = identify, + type = mtype, + --slot = slot, + crc = crc, + default = default + } + + local mData = pred.Vars[identify] + local sorted = {} + + for key, data in pairs(pred.Vars) do + sorted[data.type] = sorted[data.type] or {} + table.insert(sorted[data.type], data) + end + + local function sorter(a, b) + return a.crc > b.crc + end + + for itype, list in pairs(sorted) do + table.sort(list, sorter) + + for i, data in ipairs(list) do + data.slot = i - 1 + end + end + + pred._Vars = {} + pred.MaxEnt = 0 + + for key, data in pairs(pred.Vars) do + local _, entId, realSlot = pred.GetEntityAndSlot(key) + data.realSlot = realSlot + data.entId = entId + pred._Vars[entId] = pred._Vars[entId] or {} + pred._Vars[entId][key] = data + pred.MaxEnt = pred.MaxEnt:max(entId) + end + + if not lockRebuild then + for i = 0, pred.MaxEnt do + timer.Create('DLib.pred.DeferReload' .. i, 0, 1, function() + pred.RebuildEntityDefinition(i) + end) + end + end + + plyMeta['Get' .. identify] = function(self) + local ent = self.__dlib_pred_ent and self.__dlib_pred_ent[mData.entId + 1] + if not IsValid(ent) then return mData.default end + if not ent['Get' .. identify] then return mData.default end + return ent['Get' .. identify](ent) + end + + plyMeta['Set' .. identify] = function(self, newValue) + local ent = self.__dlib_pred_ent and self.__dlib_pred_ent[mData.entId + 1] + if not IsValid(ent) then return end + if not ent['Set' .. identify] then return end + return ent['Set' .. identify](ent, newValue) + end + + plyMeta['Add' .. identify] = function(self, val, min, max) + if min and not max then + if val < 0 then + return self['SetNW2' .. mtype](self, identify, math.max(self['Get' .. identify](self) + val, min)) + else + return self['SetNW2' .. mtype](self, identify, math.min(self['Get' .. identify](self) + val, min)) + end + elseif min and max then + return self['SetNW2' .. mtype](self, identify, math.clamp(self['Get' .. identify](self) + val, min, max)) + end + + return self['SetNW2' .. mtype](self, identify, self['Get' .. identify](self) + val) + end + + + plyMeta['Reset' .. identify] = function(self) + return self['Set' .. identify](self, pred.Vars[identify].default) + end +end + +--[[ + @doc + @fname plyMeta:DLibInvalidatePrediction + @args boolean status = false + + @desc + Sets !g:Entity:SetPredictable on all parented DLib vars entities to desired state + Does nothing when gmod version is fresh enough + @enddesc +]] +function plyMeta:DLibInvalidatePrediction(status) + if not self.__dlib_pred_ent or SERVER then return end + + for i, ent in ipairs(self.__dlib_pred_ent) do + if IsValid(ent) then + ent:SetPredictable(status or false) + end + end +end + +--[[ + @doc + @fname DLib.pred.Fingerprint + @args number entitySlotID = nil + + @returns + number: the fingerpring +]] +function pred.Fingerprint(entId) + local fingerprint = 0 + + if not entId then + for key, data in pairs(pred.Vars) do + fingerprint = fingerprint + ((data.crc % 4634661) / 12):floor() + fingerprint = fingerprint + ((util.CRC(data.type):tonumber():rshift(4) % 612348) / 5):floor() + fingerprint = fingerprint + (data.slot:rshift(6) + 23):band(65535) + + fingerprint = fingerprint % 5839581561 + end + elseif pred._Vars[entId] then + for key, data in pairs(pred._Vars[entId]) do + fingerprint = fingerprint + ((data.crc % 4634661) / 12):floor() + fingerprint = fingerprint + ((util.CRC(data.type):tonumber():rshift(4) % 612348) / 5):floor() + fingerprint = fingerprint + (data.slot:rshift(6) + 23):band(65535) + + fingerprint = fingerprint % 5839581561 + end + end + + return fingerprint +end + +--[[ + @doc + @fname DLib.pred.RebuildEntityDefinition + @args number entitySlotID, boolean _now = false + @internal +]] +function pred.RebuildEntityDefinition(entId, _now) + if SERVER and not _now then + pred._ack[entId] = player.GetHumans() + + if #pred._ack[entId] == 0 then + pred.RebuildEntityDefinition(entId, true) + return + end + + net.Start('dlib_pred_ack') + net.WriteUInt8(entId) + net.WriteUInt64(pred.Fingerprint()) + net.Broadcast() + + timer.Start('DLib.pred.RebuildAck', 10, 1, function() + pred.RebuildEntityDefinition(entId, true) + end) + + return + end + + pred._ack[entId] = nil + + local ENT = {} + ENT.Type = 'anim' + ENT.Spawnable = false + ENT.AdminSpawnable = false + ENT.Author = 'DBotThePony' + ENT.PrintName = 'DLib Predicted player variables bundle' + + function ENT:Initialize() + self:SetSolid(SOLID_NONE) + self:SetMoveType(MOVETYPE_NONE) + self:SetNoDraw(true) + self:SetTransmitWithParent(true) + end + + function ENT:Draw() + + end + + function ENT:SetupDataTables() + if not pred._Vars[entId] then return end + + for k, v in pairs(pred._Vars[entId]) do + self:NetworkVar(v.type, v.realSlot, k) + end + + for k, v in pairs(pred._Vars[entId]) do + self['Set' .. k](self, v.default) + end + end + + function ENT:DumpVariables() + local output = {} + if not pred._Vars[entId] then return output end + + for k, v in pairs(pred._Vars[entId]) do + if self['Get' .. k] then + output[k] = self['Get' .. k](self) + end + end + + return output + end + + function ENT:LoadVariables(input) + for k, v in pairs(input) do + if self['Set' .. k] then + self['Set' .. k](self, v) + end + end + end + + function ENT:Think() + if not IsValid(self:GetParent()) then + if CLIENT then return end + + if not IsValid(self.__dlib_parent) then + SafeRemoveEntity(self) + return + else + self:SetParent(self.__dlib_parent) + self:SetPos(self.__dlib_parent:GetPos()) + end + end + + local ply = self:GetParent():GetTable() + + if not ply.__dlib_pred_ent then + ply.__dlib_pred_ent = {} + end + + if SERVER and IsValid(ply.__dlib_pred_ent[entId + 1]) and ply.__dlib_pred_ent[entId + 1] ~= self then + SafeRemoveEntity(ply.__dlib_pred_ent[entId + 1]) + end + + ply.__dlib_pred_ent[entId + 1] = self + end + + scripted_ents.Register(ENT, 'dlib_predictednw' .. entId) + + if CLIENT then return end + + for i, ent in ipairs(ents.FindByClass('dlib_predictednw' .. entId)) do + local dump = ent:DumpVariables() + local ply = ent:GetParent() + SafeRemoveEntity(ent) + + if IsValid(ply) then + ent = ents.Create('dlib_predictednw' .. entId) + ent:SetParent(ply) + ent:SetPos(ply:GetPos()) + ent:LoadVariables(dump) + ent:Spawn() + ent:LoadVariables(dump) + ent.__dlib_parent = ply + ent:Activate() + ent:Think() + end + end + + for i, ply in ipairs(player.GetAll()) do + if not ply.__dlib_pred_ent then + for entId = 0, pred.MaxEnt do + local ent = ents.Create('dlib_predictednw' .. entId) + ent:SetParent(ply) + ent:SetPos(ply:GetPos()) + ent:Spawn() + ent.__dlib_parent = ply + ent:Activate() + ent:Think() + end + else + for entId = 0, pred.MaxEnt do + if not ply.__dlib_pred_ent[entId + 1] then + local ent = ents.Create('dlib_predictednw' .. entId) + ent:SetParent(ply) + ent:SetPos(ply:GetPos()) + ent:Spawn() + ent.__dlib_parent = ply + ent:Activate() + ent:Think() + end + end + end + end +end + +for entId = 0, pred.MaxEnt do + pred.RebuildEntityDefinition(entId) +end + +function pred.GetEntityAndSlot(identify) + local data = assert(pred.Vars[identify], 'Unknown variable name provided') + + if data.type == 'String' then + return data.slot, (data.slot - data.slot % 4) / 4, data.slot % 4 + end + + return data.slot, (data.slot - data.slot % 32) / 32, data.slot % 32 +end + +if CLIENT then + net.receive('dlib_pred_ack', function() + local entId = net.ReadUInt8() + local fingerprint = net.ReadUInt64() + pred.RebuildEntityDefinition(entId) + local mfinger = pred.Fingerprint(entId) + + if mfinger ~= fingerprint then + DLib.MessageError('Integrity check failed in prediction module. Server expected ', fingerprint, ' fingerprint but i got ', mfinger, '. Expect desyncs with server!') + end + + net.Start('dlib_pred_ack') + net.WriteUInt8(entId) + net.WriteUInt64(mfinger) + net.SendToServer() + end) + + return +end + +net.receive('dlib_pred_ack', function(len, ply) + local entId = net.ReadUInt8() + if not pred._ack[entId] then return end + local hit = false + + for i, ply2 in ipairs(pred._ack[entId]) do + if ply2 == ply then + hit = true + table.remove(pred._ack[entId], i) + break + end + end + + if not hit then return end + local fingerprint = net.ReadUInt64() + local mfinger = pred.Fingerprint(entId) + + if mfinger ~= fingerprint then + DLib.MessageError('Integrity check failed in prediction module over client. Client expected ', fingerprint, ' fingerprint, but i got ', mfinger, '. Expect desyncs with that client!') + end + + if #pred._ack[entId] == 0 then + pred.RebuildEntityDefinition(entId, true) + end +end) + +hook.Add('PlayerAuthed', 'DLib.pred', function(ply) + for entId = 0, pred.MaxEnt do + local ent = ents.Create('dlib_predictednw' .. entId) + ent:SetParent(ply) + ent:SetPos(ply:GetPos()) + ent:Spawn() + ent.__dlib_parent = ply + ent:Activate() + ent:Think() + end +end, -3) + +hook.Add('PostCleanupMap', 'DLib.pred', function() + for i, ply in ipairs(player.GetAll()) do + if ply.__dlib_pred_ent then + for entId = 0, pred.MaxEnt do + if not IsValid(ply.__dlib_pred_ent[entId + 1]) then + local ent = ents.Create('dlib_predictednw' .. entId) + ent:SetParent(ply) + ent:SetPos(ply:GetPos()) + ent:Spawn() + ent.__dlib_parent = ply + ent:Activate() + ent:Think() + end + end + end + end +end, -3) diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/server/friendstatus.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/server/friendstatus.lua new file mode 100644 index 0000000..b903f70 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/server/friendstatus.lua @@ -0,0 +1,102 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +net.pool('DLib.friendstatus') +local IsValid = FindMetaTable('Entity').IsValid +local enums = DLib.Enum('none', 'friend', 'blocked', 'requested') + +local table = table +local net = net +local DLib = DLib + +DLib.getinfo.Replicate('cl_dlib_steamfriends') + +local function friendstatus(len, ply) + if not IsValid(ply) then return end + local amount = net.ReadUInt(8) + ply.DLibFriends = {} + local status = ply.DLibFriends + local reply = {} + + for i = 1, amount do + local readPly = net.ReadPlayer() + local readEnum = enums:read() + + if IsValid(readPly) then + status[readPly] = readEnum + table.insert(reply, {readPly, readEnum}) + end + end + + if #reply ~= 0 then + net.Start('DLib.friendstatus') + + net.WritePlayer(ply) + net.WriteUInt(#reply, 8) + + for i, plyData in ipairs(reply) do + net.WritePlayer(plyData[1]) + enums:write(plyData[2]) + end + + net.SendOmit(ply) + end +end + +net.receive('DLib.friendstatus', friendstatus) + +local plyMeta = FindMetaTable('Player') + +function plyMeta:GetFriendStatus(target) + local status = self.DLibFriends + return status and status[target] or 'none' +end + +function plyMeta:IsFriend(target) + local f = self:GetFriendStatus(target) + return f == 'friend' or f == 'requested' +end + +function plyMeta:IsFriend2(target) + if not IsValid(self) or not IsValid(target) then return false end + if not self:GetInfoBool('cl_dlib_steamfriends', true) then return false end + if not target:GetInfoBool('cl_dlib_steamfriends', true) then return false end + local f = self:GetFriendStatus(target) + return f == 'friend' or f == 'requested' +end + +function plyMeta:IsSteamFriend(target) + local f = self:GetFriendStatus(target) + return f == 'friend' or f == 'requested' +end + +function plyMeta:IsSteamFriend2(target) + if not IsValid(self) or not IsValid(target) then return false end + if not self:GetInfoBool('cl_dlib_steamfriends', true) then return false end + if not target:GetInfoBool('cl_dlib_steamfriends', true) then return false end + local f = self:GetFriendStatus(target) + return f == 'friend' or f == 'requested' +end + +function plyMeta:IsSteamBlocked(target) + local f = self:GetFriendStatus(target) + return f == 'blocked' +end \ No newline at end of file diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/sh_cami.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/sh_cami.lua new file mode 100644 index 0000000..637d49e --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/sh_cami.lua @@ -0,0 +1,558 @@ +--[[ +CAMI - Common Admin Mod Interface. +Copyright 2019 CAMI Contributors + +Makes admin mods intercompatible and provides an abstract privilege interface +for third party addons. + +Follows the specification on this page: +https://github.com/glua/CAMI/blob/master/README.md + +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). + } + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +-- Version number in YearMonthDay format. +local version = 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 diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/workarounds/entlang.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/workarounds/entlang.lua new file mode 100644 index 0000000..bedb2f3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/workarounds/entlang.lua @@ -0,0 +1,36 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +if SERVER then return end + +timer.Simple(0, function() + for k, info in pairs(scripted_ents.GetList()) do + if info.t and info.t.ClassName and info.t.PrintName and info.t.PrintName:sub(1, 1) ~= '#' then + language.Add(info.t.ClassName, info.t.PrintName) + end + end + + for i, info in ipairs(weapons.GetList()) do + if info.ClassName and info.PrintName and info.PrintName:sub(1, 1) ~= '#' then + language.Add(info.ClassName, info.PrintName) + end + end +end) diff --git a/garrysmod/addons/util-dlib/lua/dlib/modules/workarounds/killfeed.lua b/garrysmod/addons/util-dlib/lua/dlib/modules/workarounds/killfeed.lua new file mode 100644 index 0000000..47e2aa8 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/modules/workarounds/killfeed.lua @@ -0,0 +1,34 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +if SERVER then return end +local DLib = DLib +local gamemode = gamemode + +local env = {} +env.GM = {} +env.GAMEMODE = env.GM + +DLib.sandbox.include('base/gamemode/cl_deathnotice.lua', env) + +function env.GAMEMODE:AddDeathNotice(...) + return gamemode.Call('AddDeathNotice', ...) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/sh_init.lua b/garrysmod/addons/util-dlib/lua/dlib/sh_init.lua new file mode 100644 index 0000000..c9dbd20 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/sh_init.lua @@ -0,0 +1,221 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib + +DLib.DEBUG_MODE = CreateConVar('dlib_debug', '0', {FCVAR_REPLICATED}, 'Enable debug mode. Setting this to 1 can help you solve weird bugs.') +DLib.STRICT_MODE = CreateConVar('dlib_strict', '0', {FCVAR_REPLICATED}, 'Enable strict mode. Enabling this turns all ErrorNoHalts into execution halting errors. The best way to fix bad code.') + +function DLib.simpleInclude(fil) + if SERVER then AddCSLuaFile('dlib/' .. fil) end + return include('dlib/' .. fil) +end + +local startupText = [[ + ___ _ _ ___ + | \ | | |__] + |__/ |___ | |__] + + ____ ____ ___ ____ ____ _ _ _ _ _ _ ____ + |__/ |___ | \ |___ |___ | |\ | | |\ | | __ + | \ |___ |__/ |___ | | | \| | | \| |__] + + ____ _ _ _ ____ + | __ | | | |__| + |__] |___ |__| | | + +]] + +local startupText2 = [[ + ___ _ _ ___ + | \ | | |__] + |__/ |___ | |__] + + ___ ____ ____ ____ _ _ _ _ _ ____ + |__] |__/ |___ |__| |_/ | |\ | | __ + |__] | \ |___ | | | \_ | | \| |__] + + _ _ ____ _ _ ____ + \_/ | | | | |__/ + | |__| |__| | \ + + ____ _ _ _ ___ + [__ |__| | | + ___] | | | | + + ___ _ _ + | |\/| + | | | + +]] + +MsgC('---------------------------------------------------------------\n') + +if math.random() > 0.1 then + for line in string.gmatch(startupText, '(.-)\r?\n') do + MsgC(line .. '\n') + end +else + for line in string.gmatch(startupText2, '(.-)\r?\n') do + MsgC(line .. '\n') + end +end + +local MsgC = MsgC +local SysTime = SysTime +local timeStart = SysTime() + +MsgC('---------------------------------------------------------------\n') +MsgC('[DLib] Initializing DLib core ... ') + +DLib.simpleInclude('core/core.lua') +DLib.simpleInclude('core/luaify.lua') +DLib.simpleInclude('core/funclib.lua') +DLib.simpleInclude('modules/color.lua') +DLib.MessageMaker = DLib.simpleInclude('util/message.lua') +DLib.MessageMaker(DLib, 'DLib') +DLib.simpleInclude('core/sandbox.lua') +DLib.simpleInclude('core/promise.lua') + +if jit then + if SERVER then + AddCSLuaFile('dlib/core/vmdef.lua') + AddCSLuaFile('dlib/core/vmdef_x64.lua') + end + + if jit.arch == 'x86' then + local vmdef = CompileFile('dlib/core/vmdef.lua') + jit.vmdef = nil + vmdef('jit_vmdef') + jit.vmdef = jit_vmdef + elseif jit.arch == 'x64' then + jit.vmdef = include('dlib/core/vmdef_x64.lua') + end +end + +DLib.CMessage = DLib.MessageMaker +DLib.ConstructMessage = DLib.MessageMaker + +DLib.simpleInclude('util/combathelper.lua') +DLib.simpleInclude('util/util.lua') +DLib.simpleInclude('util/vector.lua') + +DLib.node = DLib.simpleInclude('util/node.lua') + +if CLIENT then + DLib.simpleInclude('util/client/localglobal.lua') +end + +file.mkdir('dlib') + +DLib.simpleInclude('core/tableutil.lua') +DLib.simpleInclude('core/fsutil.lua') +DLib.simpleInclude('core/loader.lua') +DLib.simpleInclude('core/loader_modes.lua') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) +timeStart = SysTime() +MsgC('[DLib] Initializing DLib GLua extensions ... ') + +DLib.Loader.shmodule('bitworker.lua') + +DLib.simpleInclude('luabridge/luaify.lua') + +DLib.simpleInclude('extensions/extensions.lua') +DLib.simpleInclude('extensions/string.lua') +DLib.simpleInclude('extensions/ctakedmg.lua') +DLib.simpleInclude('extensions/cvar.lua') +DLib.simpleInclude('extensions/entity.lua') +DLib.simpleInclude('extensions/render.lua') +DLib.simpleInclude('extensions/player.lua') + +DLib.Loader.shmodule('hook.lua') +DLib.simpleInclude('luabridge/luaify2.lua') +DLib.simpleInclude('luabridge/lobject.lua') + +DLib.simpleInclude('util/http.lua') +DLib.simpleInclude('util/httpclient.lua') +DLib.simpleInclude('util/promisify.lua') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) +timeStart = SysTime() +MsgC('[DLib] Initializing DLib modules ... ') + +DLib.Loader.shmodule('luavector.lua') +DLib.Loader.shmodule('net_ext.lua') +DLib.Loader.shmodule('bytesbuffer.lua') +DLib.Loader.shmodule('nbt.lua') +DLib.Loader.shmodule('gobjectnotation.lua') +DLib.Loader.shmodule('lerp.lua') +DLib.Loader.shmodule('sh_cami.lua') +DLib.Loader.shmodule('getinfo.lua') +DLib.Loader.shmodule('predictedvars.lua') + +DLib.Loader.start('nw') +DLib.Loader.load('dlib/modules/nwvar') +DLib.Loader.finish() + +DLib.simpleInclude('util/queue.lua') + +DLib.Loader.loadPureSHTop('dlib/enums') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) +timeStart = SysTime() +MsgC('[DLib] Initializing DLib classes ... ') + +DLib.Loader.shclass('astar.lua') +DLib.Loader.shclass('dmginfo.lua') +DLib.Loader.shclass('collector.lua') +DLib.Loader.shclass('set.lua') +DLib.Loader.shclass('freespace.lua') +DLib.Loader.shclass('cvars.lua') +DLib.Loader.shclass('rainbow.lua') +DLib.Loader.shclass('camiwatchdog.lua') +DLib.Loader.shclass('measure.lua') +DLib.Loader.shclass('bezier.lua') +DLib.Loader.shclass('predictedvars.lua') +DLib.Loader.clclass('keybinds.lua') + +DLib.Loader.start('i18n') +DLib.Loader.load('dlib/modules/i18n') +DLib.Loader.finish() + +DLib.Loader.start('friends', true) +DLib.Loader.load('dlib/modules/friendsystem') +DLib.Loader.finish() + +if CLIENT then + DLib.VGUI = DLib.VGUI or {} +end + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) +timeStart = SysTime() +MsgC('[DLib] Initializing DLib LuaBridge ... ') + +DLib.simpleInclude('luabridge/luabridge.lua') +DLib.simpleInclude('luabridge/physgunhandler.lua') +DLib.simpleInclude('luabridge/loading_stages.lua') +DLib.simpleInclude('luabridge/savetable.lua') +DLib.Loader.loadPureSHTop('dlib/modules/workarounds') + +DLib.hl2wdata = DLib.simpleInclude('data/hl2sweps.lua') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) diff --git a/garrysmod/addons/util-dlib/lua/dlib/sv_init.lua b/garrysmod/addons/util-dlib/lua/dlib/sv_init.lua new file mode 100644 index 0000000..ab0a2ba --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/sv_init.lua @@ -0,0 +1,76 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 MsgC = MsgC +local SysTime = SysTime +local timeStart = SysTime() + +MsgC('[DLib] Initializing DLib serverside ... ') + +-- CreateConVar('sv_dlib_hud_shift', '1', {FCVAR_REPLICATED, FCVAR_NOTIFY}, 'SV Override: Enable HUD shifting') + +-- AddCSLuaFile('dlib/modules/notify/client/cl_init.lua') +-- DLib.Loader.loadPureCSTop('dlib/modules/hudcommons') +-- DLib.Loader.loadPureCSTop('dlib/modules/hudcommons/base') + +-- DLib.Loader.csModule('dlib/modules/notify/client') +-- DLib.Loader.svmodule('notify/sv_dnotify.lua') +DLib.Loader.csModule('dlib/util/client') +DLib.Loader.csModule('dlib/modules/client') +-- DLib.Loader.svmodule('server/dmysql4.lua') +-- DLib.Loader.svmodule('server/dmysql4_bake.lua') +-- DLib.Loader.svmodule('server/dmysql.lua') +DLib.Loader.svmodule('server/friendstatus.lua') + +DLib.Loader.loadPureCS('dlib/vgui') +include('dlib/util/server/chat.lua') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) +timeStart = SysTime() +MsgC('[DLib] Running addons ... \n') + +if not VLL_CURR_FILE and not VLL2_FILEDEF then + DLib.Loader.loadPureSHTop('dlib/autorun') + DLib.Loader.loadPureSVTop('dlib/autorun/server') + DLib.Loader.loadPureCSTop('dlib/autorun/client') +end + +MsgC(string.format('[DLib] Addons were initialized in %.2f ms\n', (SysTime() - timeStart) * 1000)) + +timeStart = SysTime() +MsgC('[DLib] Loading translations for i18n ... ') + +DLib.i18n.reload() + +concommand.Add('dlib_reload_i18n', function(ply) + if IsValid(ply) then return end + timeStart = SysTime() + + DLib.Message('Reloading translations for i18n ... ') + DLib.i18n.reload() + hook.Run('DLib.TranslationsReloaded') + DLib.Message(string.format('i18n reload took %.2f ms', (SysTime() - timeStart) * 1000)) +end) + +hook.Run('DLib.TranslationsReloaded') + +MsgC(string.format('%.2f ms\n', (SysTime() - timeStart) * 1000)) + +MsgC('---------------------------------------------------------------\n') diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/blur.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/blur.lua new file mode 100644 index 0000000..6b9f5e7 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/blur.lua @@ -0,0 +1,178 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 SCREENCOPY, DRAWMAT, RTW, RTH + +local function refreshRT() + RTW, RTH = ScrW(), ScrH() + + SCREENCOPY = GetRenderTarget('dlib-blur-' .. RTW .. '-' .. RTH, RTW, RTH, false) + + DRAWMAT = CreateMaterial('dlib-blur2', 'UnlitGeneric', { + ['$basetexture'] = 'models/debug/debugwhite', + ['$translucent'] = '0', + ['$color'] = '[1 1 1]', + ['$alpha'] = '1', + ['$nolod'] = '1', + }) + + DRAWMAT:SetFloat('$alpha', '1') + DRAWMAT:SetTexture('$basetexture', SCREENCOPY) +end + +refreshRT() +timer.Simple(0, refreshRT) +hook.Add('ScreenResolutionChanged', 'BAHUD.RefreshRT', refreshRT) +hook.Add('InvalidateMaterialCache', 'BAHUD.RefreshRT', refreshRT) + +DLib.blur = DLib.blur or {} +local blur = DLib.blur + +local FrameNumber = FrameNumber +local render = render +local surface = surface +local DLib = DLib + +local mat_BlurX = Material("pp/blurx") +local mat_BlurY = Material("pp/blury") +local tex_Bloom1 = render.GetBloomTex1() + +-- wtf is with original BlurRenderTarget ? +local function BlurRenderTarget(rt, sizex, sizey, passes) + mat_BlurX:SetTexture('$basetexture', rt) + mat_BlurY:SetTexture('$basetexture', tex_Bloom1) + mat_BlurX:SetFloat('$size', sizex) + mat_BlurY:SetFloat('$size', sizey) + + for i = 1, passes + 1 do + render.PushRenderTarget(tex_Bloom1) + render.SetMaterial(mat_BlurX) + render.DrawScreenQuad() + render.PopRenderTarget() + + render.PushRenderTarget(rt) + render.SetMaterial(mat_BlurY) + render.DrawScreenQuad() + render.PopRenderTarget() + end +end + +local BLUR_X = CreateConVar('dlib_blur_x', '2', {FCVAR_ARCHIVE}, 'Blurring strength at X scale. Do not change unless you know what you are doing!') +local BLUR_Y = CreateConVar('dlib_blur_y', '2', {FCVAR_ARCHIVE}, 'Blurring strength at Y scale. Do not change unless you know what you are doing!') +local BLUR_PASSES = CreateConVar('dlib_blur_passes', '1', {FCVAR_ARCHIVE}, 'Blurring passes. Do not change unless you know what you are doing!') +local BLUR_ENABLE = CreateConVar('dlib_blur_enable', '1', {FCVAR_ARCHIVE}, 'Enable blur utility functions. Usually this does not affect performance or do so slightly.') + +local LAST_DRAW = 0 +local LAST_REFRESH + +--[[ + @doc + @fname DLib.blur.RefreshNow + @args boolean force = false + @client + + @desc + calls render.CopyRenderTargetToTexture and blurs internal render target + @enddesc +]] +function blur.RefreshNow(force) + if not BLUR_ENABLE:GetBool() then return false end + if not render.SupportsPixelShaders_2_0() then return false end + if LAST_REFRESH == FrameNumber() and not force then return false end + if LAST_DRAW < 0 and not force then return false end + LAST_REFRESH = FrameNumber() + LAST_DRAW = LAST_DRAW - 1 + + render.CopyRenderTargetToTexture(SCREENCOPY) + BlurRenderTarget(SCREENCOPY, BLUR_X:GetInt(2):clamp(1, 32), BLUR_Y:GetInt(2):clamp(1, 32), BLUR_PASSES:GetInt(1):clamp(1, 32)) + + return true +end + +hook.Add('PostDrawHUD', 'DLib.Blur', function() + blur.RefreshNow() + if not render.SupportsPixelShaders_2_0() then + hook.Remove('PostDrawHUD', 'DLib.Blur') + end +end, -1) + +--[[ + @doc + @fname DLib.blur.Draw + @args number x, number y, number width, number height + @client +]] +function blur.Draw(x, y, w, h) + if not BLUR_ENABLE:GetBool() then return end + if not render.SupportsPixelShaders_2_0() then return end + LAST_DRAW = 10 + if LAST_REFRESH ~= FrameNumber() then return end + local u, v, eu, ev = x / RTW, y / RTH, (x + w) / RTW, (y + h) / RTH + + surface.SetMaterial(DRAWMAT) + surface.SetDrawColor(255, 255, 255) + render.OverrideDepthEnable(true, false) + surface.DrawTexturedRectUV(x, y, w, h, u, v, eu, ev) + render.OverrideDepthEnable(false) +end + +--[[ + @doc + @fname DLib.blur.DrawOffset + @args number drawx, number drawy, number width, number height, number realx, number realy + @client +]] +function blur.DrawOffset(drawX, drawY, w, h, realX, realY) + if not BLUR_ENABLE:GetBool() then return end + if not render.SupportsPixelShaders_2_0() then return end + LAST_DRAW = 10 + if LAST_REFRESH ~= FrameNumber() then return end + local u, v, eu, ev = realX / RTW, realY / RTH, (realX + w) / RTW, (realY + h) / RTH + + surface.SetMaterial(DRAWMAT) + surface.SetDrawColor(255, 255, 255) + render.OverrideDepthEnable(true, false) + surface.DrawTexturedRectUV(drawX, drawY, w, h, u, v, eu, ev) + render.OverrideDepthEnable(false) +end + +--[[ + @doc + @fname DLib.blur.DrawPanel + @args number width, number height, number screenx, number screeny + @client + + @desc + handy with use of panels. Example usage: `DLib.blut.DrawPanel(w, h, self:LocalToScreen(0, 0))` + @enddesc +]] +function blur.DrawPanel(w, h, x, y) + if not BLUR_ENABLE:GetBool() then return end + if not render.SupportsPixelShaders_2_0() then return end + LAST_DRAW = 10 + if LAST_REFRESH ~= FrameNumber() then return end + local u, v, eu, ev = x / RTW, y / RTH, (x + w) / RTW, (y + h) / RTH + + surface.SetMaterial(DRAWMAT) + surface.SetDrawColor(255, 255, 255) + render.OverrideDepthEnable(true, false) + surface.DrawTexturedRectUV(0, 0, w, h, u, v, eu, ev) + render.OverrideDepthEnable(false) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/buystuff.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/buystuff.lua new file mode 100644 index 0000000..a068f15 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/buystuff.lua @@ -0,0 +1,183 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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. + + +surface.CreateFont('BuyCSSFont', { + font = 'Comic Sans MS', + size = 32 +}) + +surface.CreateFont('BuyCSSFont2', { + font = 'Comic Sans MS', + size = 24 +}) + +surface.CreateFont('BuyDLibPremium', { + font = 'PT Serif', + size = 32 +}) + +surface.CreateFont('BuyRTFont', { + font = 'Roboto', + size = 48 +}) + +surface.CreateFont('BuyFontsFont', { + font = 'Times New Roman', + size = 32 +}) + +local ENABLED = CreateConVar('dlib_replace_missing_textures', '0', {FCVAR_ARCHIVE}, 'Replace missing textures with something less boring') +local buy_rt = GetRenderTargetEx('buy_counter_strike', 128, 128, RT_SIZE_NO_CHANGE, MATERIAL_RT_DEPTH_SHARED, 0, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGB888) + +local Textings = { + {'buy\ncounter\nstrike', 'BuyCSSFont'}, + {'buy\ndlib\npremium', 'BuyDLibPremium'}, + {'install\ndlib\nv3', 'BuyDLibPremium'}, + {'go play\nvalve idiot', 'BuyDLibPremium'}, + {'put texture\nHere!!1', 'BuyDLibPremium'}, + {'hl2.exe\nis\ndumb', 'Default'}, + {'F U C K\nBUY CSS\nF U C K', 'BuyCSSFont'}, + {'here, buy\nyourself some\ncounter-strike', 'BuyCSSFont2'}, + {':RT:', 'BuyRTFont'}, + {':missing:', 'BuyRTFont'}, + {'Times\nNew\nRumanian', 'BuyFontsFont'}, + {':missing_texture:', 'BuyFontsFont'}, + {'install\nnew\nfonts', 'BuyFontsFont'}, + {'use\ncomic sans', 'BuyFontsFont'}, + {'you forgot\nyour texture', 'BuyDLibPremium'}, + {'buy yourself\nsome textures', 'BuyFontsFont'}, + {'texture\nGone\nMISSING', 'BuyDLibPremium'}, + {'dlib did nothing\nto this missing\nTEXTURE', 'BuyCSSFont2'}, + {'no missing\ntextures for\nU', 'BuyCSSFont2'}, +} + +local Backgrounds = { + Color(), + Color(77, 202, 233), + Color(20, 246, 227), + Color(67, 216, 92), + Color(169, 67, 216), + Color(204, 227, 75), + Color(227, 172, 75), + Color(210, 110, 50), + Color(221, 75, 181), + Color(216, 51, 82), + Color(123, 28, 196), +} + +local draw = draw +local surface = surface +local render = render +local cam = cam +local Material = Material +local RealTimeL = RealTimeL +local LerpQuintic = LerpQuintic +local errormat + +local BackgroundIndex = 1 +local BackgroundStart = 0 +local BackgroundNext = 0 +local BackgroundColorCurrent, BackgroundColorState, BackgroundColorNext + +local CurrentText, CurrentFont +local NextText = 0 +local LocalPlayer = LocalPlayer +local nextTraceCheck = 0 + +local function RedrawRT() + if not ENABLED:GetBool() then return end + local compute = false + + if not errormat then + errormat = Material('__error') + DLib.ErrorTexture = DLib.ErrorTexture or errormat:GetTexture('$basetexture') + compute = true + end + + local time = RealTimeL() + + if time > BackgroundNext then + BackgroundIndex = BackgroundIndex + 1 + + if BackgroundIndex > #Backgrounds then + BackgroundIndex = 1 + BackgroundColorCurrent = Backgrounds[1] + BackgroundColorNext = Backgrounds[2] + elseif BackgroundIndex == #Backgrounds then + BackgroundColorCurrent = Backgrounds[BackgroundIndex] + BackgroundColorNext = Backgrounds[1] + else + BackgroundColorCurrent = Backgrounds[BackgroundIndex] + BackgroundColorNext = Backgrounds[BackgroundIndex + 1] + end + + BackgroundStart = time + BackgroundNext = time + 20 + end + + if time > NextText then + local data = table.frandom(Textings) + CurrentText, CurrentFont = data[1], data[2] + NextText = time + math.random(60, 120) + end + + BackgroundColorState = BackgroundColorCurrent:Lerp(time:progression(BackgroundStart, BackgroundNext), BackgroundColorNext) + + render.PushRenderTarget(buy_rt) + cam.Start2D() + + draw.NoTexture() + surface.SetDrawColor(BackgroundColorState) + surface.DrawRect(0, 0, 128, 128) + draw.DrawText(CurrentText, CurrentFont, 64, 18, BackgroundColorState:Invert(), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + render.CopyRenderTargetToTexture(DLib.ErrorTexture) + + cam.End2D() + render.PopRenderTarget() + + if compute then + errormat:SetTexture('$basetexture', buy_rt) + errormat:Recompute() + end + + if time > nextTraceCheck then + local tr = LocalPlayer():GetEyeTrace() + local HitTexture = tr.HitTexture + + if HitTexture then + local mat = Material(HitTexture) + + if mat and mat:IsError() then + --local tex = mat:GetTexture('$basetexture') + --local tex2 = mat:GetTexture('$refracttexture') + --local check1 = not tex or tex:GetName() == '__error' or tex:GetName() == 'error' + --local check2 = not tex2 or tex2:GetName() == '__error' or tex2:GetName() == 'error' + + mat:SetTexture('$basetexture', buy_rt) + mat:Recompute() + end + end + + nextTraceCheck = time + 1 + end +end + +hook.Add('PostRender', 'DLib.BuyCounterStrike', RedrawRT) diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/chat.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/chat.lua new file mode 100644 index 0000000..b71bc06 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/chat.lua @@ -0,0 +1,91 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 chat = setmetatable(DLib.chat or {}, {__index = chat}) +DLib.chat = chat + +--[[ + @doc + @fname DLib.CMessageChat + @args table target, string addonName + + @desc + calls `DLib.CMessage` and then adds networked chat messaging functions + @enddesc + + @returns + table: target +]] +function chat.registerChat(vname, ...) + local nw = 'DLib.AddChatText.' .. vname + local nwL = 'DLib.AddChatTextL.' .. vname + local values = {...} + local func = values[1] + local func2 = values[2] + + if type(func) ~= 'function' then + net.receive(nw, function() + chat.AddText(unpack(table.unshift(net.ReadArray(), unpack(values)))) + end) + + net.receive(nwL, function() + chat.AddTextLocalized(unpack(table.unshift(net.ReadArray(), unpack(values)))) + end) + else + net.receive(nw, function() + chat.AddText(func(net.ReadArray())) + end) + + if type(func2) ~= 'function' then + net.receive(nwL, function() + chat.AddTextLocalized(func(net.ReadArray())) + end) + else + net.receive(nwL, function() + local arr = net.ReadArray() + chat.AddText(func2(arr)) + end) + end + end + + return nw +end + +function chat.registerWithMessages(target, vname, ...) + target = target or {} + DLib.CMessage(target, vname) + + local function input(incomingTable) + return unpack(target.FormatMessage(unpack(incomingTable))) + end + + local function inputL(incomingTable) + return unpack(target.LFormatMessage(unpack(incomingTable))) + end + + chat.registerChat(vname, input, inputL, ...) + return target +end + +chat.RegisterWithMessages = chat.registerWithMessages +DLib.CMessageChat = chat.registerWithMessages +chat.RegisterChat = chat.registerChat + +chat.registerChat('default') diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/localglobal.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/localglobal.lua new file mode 100644 index 0000000..95e41e8 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/localglobal.lua @@ -0,0 +1,215 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 LocalPlayer = LocalPlayer +local IsValid = FindMetaTable('Entity').IsValid +local NULL = NULL + +--[[ + @doc + @fname LocalWeapon + @alias ActiveWeapon + @alias GetActiveWeapon + + @returns + Weapon: which player currently hold in hands or NULL +]] +local function LocalWeapon() + local ply = LocalPlayer() + if not IsValid(ply) then return NULL end + local weapon = ply:GetActiveWeapon() + if not IsValid(weapon) then return NULL end + return weapon +end + +--[[ + @doc + @fname LocalViewModel + @alias LocalViewmodel + + @returns + Entity +]] +function _G.LocalViewModel(...) + local ply = LocalPlayer() + if not IsValid(ply) then return NULL end + return ply:GetViewModel(...) +end + +_G.LocalViewmodel = LocalViewModel + +--[[ + @doc + @fname LocalHands + @alias LocalArms + + @returns + Entity +]] +function _G.LocalHands(...) + local ply = LocalPlayer() + if not IsValid(ply) then return NULL end + return ply:GetHands(...) +end + +_G.LocalArms = LocalHands + +--[[ + @doc + @fname LocalEyeTrace + + @returns + table: !g:TraceResult or `false` +]] +function _G.LocalEyeTrace(...) + local ply = LocalPlayer() + if not IsValid(ply) then return false end + return ply:GetEyeTrace(...) +end + +local LocalEyeTrace = LocalEyeTrace + +--[[ + @doc + @fname LocalLookingAt + + @returns + Entity: or NULL +]] +function _G.LocalLookingAt(...) + local tr = LocalEyeTrace(...) + if not tr then return NULL end + return tr.Entity or NULL +end + +--[[ + @doc + @fname LocalClip1 + + @returns + number +]] +function _G.LocalClip1() + local weapon = LocalWeapon() + if not IsValid(weapon) then return -1 end + return weapon:Clip1() +end + +--[[ + @doc + @fname LocalClip2 + + @returns + number +]] +function _G.LocalClip2() + local weapon = LocalWeapon() + if not IsValid(weapon) then return -1 end + return weapon:Clip2() +end + +--[[ + @doc + @fname LocalMaxClip1 + + @returns + number +]] +function _G.LocalMaxClip1() + local weapon = LocalWeapon() + if not IsValid(weapon) then return -1 end + return weapon:GetMaxClip1() +end + +--[[ + @doc + @fname LocalMaxClip2 + + @returns + number +]] +function _G.LocalMaxClip2() + local weapon = LocalWeapon() + if not IsValid(weapon) then return -1 end + return weapon:GetMaxClip2() +end + +--[[ + @doc + @fname LocalAmmoType1 + + @returns + number +]] +function _G.LocalAmmoType1() + local weapon = LocalWeapon() + if not IsValid(weapon) then return -1 end + return weapon:GetPrimaryAmmoType() +end + +--[[ + @doc + @fname LocalAmmoType2 + + @returns + number +]] +function _G.LocalAmmoType2() + local weapon = LocalWeapon() + if not IsValid(weapon) then return -1 end + return weapon:GetSecondaryAmmoType() +end + +_G.ActiveWeapon = LocalWeapon +_G.GetActiveWeapon = LocalWeapon +_G.LocalWeapon = LocalWeapon + +--[[ + @doc + @fname LocalPos + @alias LocalPosition + + @returns + Vector +]] +function _G.LocalPos() + local ply = LocalPlayer() + if not IsValid(ply) then return Vector() end + return ply:GetPos() +end + +_G.LocalPosition = LocalPos + +--[[ + @doc + @fname LocalAngles + @alias LocalAng + + @returns + Vector +]] +function _G.LocalAngles() + local ply = LocalPlayer() + if not IsValid(ply) then return Angle() end + return ply:GetAngles() +end + +_G.LocalAng = LocalAngles diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/matnotify.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/matnotify.lua new file mode 100644 index 0000000..a01e0b7 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/matnotify.lua @@ -0,0 +1,298 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +hook.Add('ScreenResolutionChanged', 'DLib.Рубат не хочет ничего фиксить', function(lw, lh, w, h) + if lw == w and lh == h then return end + hook.Run('InvalidateMaterialCache') + hook.Run('MaterialVariableChanges') +end) + +local сукападла = [[ +mat_aaquality : 0 : : +mat_accelerate_adjust_exposure_down : 3 : , "cheat" : +mat_alphacoverage : 1 : : +mat_antialias : 8 : : +mat_autoexposure_max : 2 : , "cl" : +mat_autoexposure_min : 0 : , "cl" : +mat_bloom_scalefactor_scalar : 1 : , "cl" : +mat_bloomamount_rate : 0 : , "cheat", "cl" : +mat_bloomscale : 1 : , "cl" : +mat_bufferprimitives : 1 : : +mat_bumpbasis : 0 : , "cheat" : +mat_bumpmap : 1 : : +mat_camerarendertargetoverlaysize : 256 : , "cheat", "cl" : +mat_clipz : 1 : , "cl" : +mat_colcorrection_disableentities : 0 : : Disable map color-correction entities +mat_color_projection : 0 : , "a" : +mat_colorcorrection : 1 : : +mat_compressedtextures : 1 : : +mat_configcurrent : cmd : : show the current video control panel config for the material system +mat_crosshair : cmd : : Display the name of the material under the crosshair +mat_crosshair_edit : cmd : : open the material under the crosshair in the editor defined by mat_crosshair_edit_editor +mat_crosshair_explorer : cmd : : open the material under the crosshair in explorer and highlight the vmt file +mat_crosshair_printmaterial : cmd : : print the material under the crosshair +mat_crosshair_reloadmaterial : cmd : : reload the material under the crosshair +mat_debug_autoexposure : 0 : , "cheat", "cl" : +mat_debug_bloom : 0 : , "cheat", "cl" : +mat_debug_postprocessing_effects : 0 : , "cl" : 0 = off, 1 = show post-processing passes in quadrants of the screen, 2 = only apply post-processing to the centre of the screen +mat_debug_process_halfscreen : 0 : , "cheat", "cl" : +mat_debugalttab : 0 : , "cheat" : +mat_debugdepth : 0 : : +mat_debugdepthmode : 0 : : +mat_debugdepthval : 128 : : +mat_debugdepthvalmax : 256 : : +mat_depthbias_decal : -262144 : , "cheat" : +mat_depthbias_normal : 0 : , "cheat" : +mat_depthbias_shadowmap : 0 : : +mat_diffuse : 1 : , "cheat" : +mat_disable_bloom : 0 : , "cl" : +mat_disable_d3d9ex : 0 : , "a" : Disables Windows Aero DirectX extensions (may positively or negatively affect performance depending on video drivers) +mat_disable_fancy_blending : 0 : : +mat_disable_lightwarp : 0 : : +mat_disable_ps_patch : 0 : : +mat_disablehwmorph : 0 : : Disables HW morphing for particular mods +mat_drawflat : 0 : , "cheat" : +mat_drawTexture : 0 : , "cl" : Enable debug view texture +mat_drawTextureScale : 1 : , "cl" : Debug view texture scale +mat_drawTitleSafe : 0 : : Enable title safe overlay +mat_drawwater : 1 : , "cheat", "cl" : +mat_dump_rts : 0 : , "cl" : +mat_dxlevel : 95 : : +mat_dynamic_tonemapping : 1 : , "cheat" : +mat_edit : cmd : : Bring up the material under the crosshair in the editor +mat_envmapsize : 128 : : +mat_envmaptgasize : 32 : : +mat_excludetextures : 0 : : +mat_exposure_center_region_x : 0 : , "cheat", "cl" : +mat_exposure_center_region_x_flashlight : 0 : , "cheat", "cl" : +mat_exposure_center_region_y : 0 : , "cheat", "cl" : +mat_exposure_center_region_y_flashlight : 0 : , "cheat", "cl" : +mat_fastclip : 0 : , "cheat" : +mat_fastnobump : 0 : , "cheat" : +mat_fastspecular : 1 : : Enable/Disable specularity for visual testing. Will not reload materials and will not affect perf. +mat_fillrate : 0 : , "cheat" : +mat_filterlightmaps : 1 : : +mat_filtertextures : 1 : : +mat_force_bloom : 0 : , "cheat", "cl" : +mat_force_ps_patch : 0 : : +mat_force_tonemap_scale : 0 : , "cheat" : +mat_forceaniso : 16 : : +mat_forcedynamic : 0 : , "cheat" : +mat_forcehardwaresync : 1 : : +mat_forcemanagedtextureintohardware : 0 : : +mat_frame_sync_enable : 1 : , "cheat" : +mat_frame_sync_force_texture : 0 : , "cheat" : Force frame syncing to lock a managed texture. +mat_framebuffercopyoverlaysize : 256 : , "cl" : +mat_fullbright : 0 : , "cheat" : +mat_hdr_enabled : cmd : : Report if HDR is enabled for debugging +mat_hdr_level : 0 : , "a" : Set to 0 for no HDR, 1 for LDR+bloom on HDR maps, and 2 for full HDR on HDR maps. +mat_hdr_manual_tonemap_rate : 1 : : +mat_hdr_tonemapscale : 1 : , "cheat" : The HDR tonemap scale. 1 = Use autoexposure, 0 = eyes fully closed, 16 = eyes wide open. +mat_hdr_uncapexposure : 0 : , "cheat", "cl" : +mat_hsv : 0 : , "cheat", "cl" : +mat_info : cmd : : Shows material system info +mat_leafvis : 0 : , "cheat" : Draw wireframe of current leaf +mat_levelflush : 1 : : +mat_lightmap_pfms : 0 : : Outputs .pfm files containing lightmap data for each lightmap page when a level exits. +mat_loadtextures : 1 : , "cheat" : +mat_luxels : 0 : , "cheat" : +mat_managedtextures : 1 : , "a" : If set, allows Direct3D to manage texture uploading at the cost of extra system memory +mat_max_worldmesh_vertices : 65536 : : +mat_maxframelatency : 1 : : +mat_measurefillrate : 0 : , "cheat" : +mat_mipmaptextures : 1 : : +mat_monitorgamma : 2 : : monitor gamma (typically 2.2 for CRT and 1.7 for LCD) +mat_monitorgamma_tv_enabled : 0 : , "a" : +mat_monitorgamma_tv_exp : 2 : : +mat_monitorgamma_tv_range_max : 255 : : +mat_monitorgamma_tv_range_min : 16 : : +mat_morphstats : 0 : , "cheat" : +mat_motion_blur_enabled : 1 : : +mat_motion_blur_falling_intensity : 1 : , "cl" : +mat_motion_blur_falling_max : 20 : , "cl" : +mat_motion_blur_falling_min : 10 : , "cl" : +mat_motion_blur_forward_enabled : 1 : , "cl" : +mat_motion_blur_percent_of_screen_max : 4 : : +mat_motion_blur_rotation_intensity : 1 : , "cl" : +mat_motion_blur_strength : 1 : , "cl" : +mat_non_hdr_bloom_scalefactor : 0 : , "cl" : +mat_norendering : 0 : , "cheat" : +mat_normalmaps : 0 : , "cheat" : +mat_normals : 0 : , "cheat" : +mat_parallaxmap : 1 : : +mat_picmip : -1 : : +mat_postprocess_x : 4 : , "cl" : +mat_postprocess_y : 1 : , "cl" : +mat_postprocessing_combine : 1 : , "cl" : Combine bloom, software anti-aliasing and color correction into one post-processing pass +mat_powersavingsmode : 0 : , "a" : Power Savings Mode +mat_proxy : 0 : , "cheat" : +mat_queue_mode : 2 : , "a" : The queue/thread mode the material system should use: -1=default, 0=synchronous single thread, 2=queued multithreaded +mat_queue_report : 0 : , "a" : Report thread stalls. Positive number will filter by stalls >= time in ms. -1 reports all locks. +mat_reducefillrate : 0 : : +mat_reduceparticles : 0 : : +mat_reloadallmaterials : cmd : : Reloads all materials +mat_reloadmaterial : cmd : : Reloads a single material +mat_reloadtexture : cmd : : Reloads a single texture +mat_reloadtextures : cmd : : Reloads all textures +mat_remoteshadercompile : 127 : , "cheat" : +mat_report_queue_status : 0 : : +mat_reporthwmorphmemory : cmd : : Reports the amount of size in bytes taken up by hardware morph textures. +mat_reversedepth : 0 : , "cheat" : +mat_savechanges : cmd : : saves current video configuration to the registry +mat_setvideomode : cmd : : sets the width, height, windowed state of the material system +mat_shadercount : cmd : : display count of all shaders and reset that count +mat_shadowstate : 1 : : +mat_show_ab_hdr : 0 : : +mat_show_ab_hdr_hudelement : 0 : , "cheat", "cl" : HDR Demo HUD Element toggle. +mat_show_histogram : 0 : , "cl" : +mat_show_texture_memory_usage : 0 : , "cheat", "numeric" : Display the texture memory usage on the HUD. +mat_showcamerarendertarget : 0 : , "cheat", "cl" : +mat_showenvmapmask : 0 : : +mat_showframebuffertexture : 0 : , "cheat", "cl" : +mat_showlightmappage : -1 : , "cl" : +mat_showlowresimage : 0 : , "cheat" : +mat_showmaterials : cmd : : Show materials. +mat_showmaterialsverbose : cmd : : Show materials (verbose version). +mat_showmiplevels : 0 : , "cheat" : color-code miplevels 2: normalmaps, 1: everything else +mat_showtextures : cmd : : Show used textures. +mat_showwatertextures : 0 : , "cheat", "cl" : +mat_slopescaledepthbias_decal : 0 : , "cheat" : +mat_slopescaledepthbias_normal : 0 : , "cheat" : +mat_slopescaledepthbias_shadowmap : 2 : : +mat_software_aa_blur_one_pixel_lines : 0 : , "a", "cl" : How much software AA should blur one-pixel thick lines: (0.0 - none), (1.0 - lots) +mat_software_aa_debug : 0 : , "cl" : Software AA debug mode: (0 - off), (1 - show number of 'unlike' samples: 0->black, 1->red, 2->green, 3->blue), (2 - show anti-a +mat_software_aa_edge_threshold : 1 : , "a", "cl" : Software AA - adjusts the sensitivity of the software AA shader's edge detection (default 1.0 - a lower value will soften more +mat_software_aa_quality : 0 : , "a", "cl" : Software AA quality mode: (0 - 5-tap filter), (1 - 9-tap filter) +mat_software_aa_strength : 0 : , "a", "cl" : Software AA - perform a software anti-aliasing post-process (an alternative/supplement to MSAA). This value sets the strength o +mat_software_aa_strength_vgui : 1 : , "a", "cl" : Same as mat_software_aa_strength, but forced to this value when called by the post vgui AA pass. +mat_software_aa_tap_offset : 1 : , "a", "cl" : Software AA - adjusts the displacement of the taps used by the software AA shader (default 1.0 - a lower value will make the im +mat_softwarelighting : 0 : : +mat_softwareskin : 0 : , "cheat" : +mat_specular : 1 : : Enable/Disable specularity for perf testing. Will cause a material reload upon change. +mat_spewvertexandpixelshaders : cmd : : Print all vertex and pixel shaders currently loaded to the console +mat_stub : 0 : , "cheat", "cl" : +mat_supportflashlight : 1 : : 0 - do not support flashlight (don't load flashlight shader combos), 1 - flashlight is supported +mat_surfaceid : 0 : , "cheat" : +mat_surfacemat : 0 : , "cheat" : +mat_texture_limit : -1 : , "numeric" : If this value is not -1, the material system will limit the amount of texture memory it uses in a frame. Useful for identifying +mat_texture_list : 0 : : For debugging, show a list of used textures per frame +mat_texture_list_all : 0 : , "numeric" : If this is nonzero, then the texture list panel will show all currently-loaded textures. +mat_texture_list_content_path : 0 : , "a" : The content path to the materialsrc directory. If left unset, it'll assume your content directory is next to the currently runn +mat_texture_list_txlod : cmd : : Adjust LOD of the last viewed texture +1 to inc resolution, -1 to dec resolution +mat_texture_list_txlod_sync : cmd : : 'reset' - resets all run-time changes to LOD overrides, 'save' - saves all changes to material content files +mat_texture_list_view : 1 : , "numeric" : If this is nonzero, then the texture list panel will render thumbnails of currently-loaded textures. +mat_texture_outline_fonts : cmd : : Outline fonts textures. +mat_texture_save_fonts : cmd : : Save all font textures +mat_tonemap_algorithm : 1 : , "cheat" : 0 = Original Algorithm 1 = New Algorithm +mat_tonemap_min_avglum : 3 : , "cheat", "cl" : +mat_tonemap_percent_bright_pixels : 2 : , "cheat", "cl" : +mat_tonemap_percent_target : 60 : , "cheat", "cl" : +mat_tonemapping_occlusion_use_stencil : 0 : : +mat_trilinear : 0 : : +mat_use_compressed_hdr_textures : 1 : : +mat_viewportscale : 1 : , "a", "cl" : Scale down the main viewport (to reduce GPU impact on CPU profiling) +mat_viewportupscale : 1 : , "a", "cl" : Scale the viewport back up +mat_visualize_dof : 0 : , "cheat" : +mat_vsync : 0 : : Force sync to vertical retrace +mat_wateroverlaysize : 256 : , "cl" : +mat_wireframe : 0 : , "cheat" : +mat_yuv : 0 : , "cheat", "cl" : +matchmakingport : 27025 : : Host Matchmaking port +material_override : 0 : , "user", "demo", "server_can_execute", "cl", "lua_client" : +]] + +local пидорас = { + 'mat_fullbright', + 'mat_specular', + 'mat_aaquality', + 'mat_picmip', + 'mat_showlowresimage', +} + +--[[ +for i, дебил in ipairs(сукападла:split('\n')) do + local cvar, cvalue = дебил:match('(%S+)%s+:%s(%S+)') + cvar = cvar:trim() + cvalue = cvalue:trim():tonumber() + + if cvalue then + table.insert(пидорас, cvar) + end +end +]] + +for i, хуита in ipairs(пидорас) do + cvars.AddChangeCallback(хуита, function() + hook.Run('InvalidateMaterialCache') + end, 'DLib.Фиксись блядь') +end + +for i, дебил in ipairs(сукападла:split('\n')) do + local хуйня, эточотакое = дебил:match('(%S+)%s+:%s(%S+)') + + if хуйня then + хуйня = хуйня:trim() + эточотакое = эточотакое:trim():tonumber() + + if эточотакое then + cvars.AddChangeCallback(хуйня, function(хуйня, что_там_было, это_чото_новое) + hook.Run('MaterialVariableChanges', хуйня, что_там_было, это_чото_новое) + end, 'DLib.Все умрут') + end + end +end + +--[[ + @doc + @hook InvalidateMaterialCache + + @desc + Called when engine invalidate material cache + Redefine your !g:CreateMaterial materials after this hook run + @enddesc +]] + +--[[ + @doc + @hook MaterialVariableChanges + @args string cvar, string oldValue, string newValue + + @desc + Called when mat_* variable changes. + @enddesc +]] + +--[[ + @doc + @fname DLib.MaterialCacheHook + @args string name, function func + + @desc + func() + `hook.Add('InvalidateMaterialCache')` + @enddesc +]] +function DLib.MaterialCacheHook(name, func) + func() + return hook.Add('InvalidateMaterialCache', name, func) +end + +timer.Simple(0, function() + hook.Run('InvalidateMaterialCache') +end) diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/scrsize.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/scrsize.lua new file mode 100644 index 0000000..5deecd3 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/scrsize.lua @@ -0,0 +1,112 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +local lastW, lastH = ScrWL(), ScrHL() +local hook = hook + +local function check(w, h) + if w == lastW and h == lastH then return end + + if w ~= lastW then + hook.Run('ScreenWidthChanges', lastW, w) + end + + if h ~= lastH then + hook.Run('ScreenHeightChanges', lastH, h) + end + + DLib.TriggerScreenSizeUpdate(lastW, lastH, w, h) + + lastW, lastH = w, h +end + +--[[ + @doc + @hook ScreenResolutionChanged + @alias ScreenSizeChanged + @alias OnScreenSizeChanged + @alias OnScreenResolutionUpdated + @args number lastWidth, number lastHeight, number width, number height + + @desc + use this hook for recalculating logic based on sizes of screen resolution + @enddesc +]] +function DLib.TriggerScreenSizeUpdate(lw, lh, w, h) + hook.Run('ScreenResolutionChanged', lw, lh, w, h) + hook.Run('ScreenSizeChanged', lw, lh, w, h) + hook.Run('OnScreenSizeChanged', lw, lh, w, h) + hook.Run('OnScreenResolutionUpdated', lw, lh, w, h) +end + +concommand.Add('dlib_reload_materials', function() + DLib.TriggerScreenSizeUpdate(0, 0, lastW, lastH) +end) + +--[[ + @doc + @fname surface.DLibCreateFont + @args string fontName, table fontData + + @desc + Same as !g:surface.CreateFont but `size` getting altered by `ScreenSize()` call and + font is automatically recreated on each `ScreenResolutionChanged` hook call + @enddesc +]] +function surface.DLibCreateFont(fontName, fontData) + fontData.osize = fontData.size + + local function refresh() + fontData.size = ScreenSize(fontData.osize) + surface.CreateFont(fontName, fontData) + end + + hook.Add('ScreenResolutionChanged', fontName, refresh) + refresh() +end + +local dlib_guiding_lines = CreateConVar('dlib_guiding_lines', '0', {}, 'Draw guiding lines on screen') +local gui = gui +local surface = surface +local ScreenSize = ScreenSize + +surface.CreateFont('DLib.GuidingLine', { + font = 'PT Mono', + size = 24, + weight = 500 +}) + +local function DrawGuidingLines() + if not dlib_guiding_lines:GetBool() then return end + + local x, y = gui.MousePos() + if x == 0 and y == 0 then return end + + surface.SetDrawColor(183, 174, 174) + + surface.DrawRect(0, y - ScreenSize(2), lastW, ScreenSize(4)) + surface.DrawRect(x - ScreenSize(2), 0, ScreenSize(4), lastH) + DLib.HUDCommons.WordBox(string.format('X percent: %.2f Y percent: %.2f', x / lastW, y / lastH), 'DLib.GuidingLine', x:clamp(lastW * 0.15, lastW * 0.85), (y - ScreenSize(17)):clamp(lastH * 0.1, lastH * 0.9), color_white, color_black, true) +end + +hook.Add('DLib.ScreenSettingsUpdate', 'DLib.UpdateScreenSize', check) +hook.Add('HUDPaint', 'DLib.DrawGuidingLines', DrawGuidingLines) diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/client/ttfreader.lua b/garrysmod/addons/util-dlib/lua/dlib/util/client/ttfreader.lua new file mode 100644 index 0000000..db9bd08 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/client/ttfreader.lua @@ -0,0 +1,352 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 DLib = DLib +local table = table +local string = string + +DLib.ttf = {} +local meta = FindMetaTable('DLibTTF') or {} +debug.getregistry().DLibTTF = meta +DLib.ttf.meta = meta + +meta.__index = meta + +do + local HeadStruct = DLib.BytesBuffer.CompileStructure([[ + uint32 scaler type + uint16 numTables + uint16 searchRange + uint16 entrySelector + uint16 rangeShift + ]]) + + local OffsetStruct = DLib.BytesBuffer.CompileStructure([[ + uint8 tagA + uint8 tagB + uint8 tagC + uint8 tagD + uint32 checkSum + uint32 offset + uint32 length + ]]) + + function DLib.ttf.Open(bytesbuffer) + local readFrom = bytesbuffer:Ask() + local head = HeadStruct(bytesbuffer) + local offsets = {} + + for i = 1, head.numTables do + local offset = OffsetStruct(bytesbuffer) + offset.tag = string.char(offset.tagA, offset.tagB, offset.tagC, offset.tagD) + offsets[offset.tag] = offset + end + + local self = {} + + self.head = head + self.offsets = offsets + self.bytesbuffer = bytesbuffer + self.bytespos = readFrom + + return setmetatable(self, meta) + end +end + +function meta:HasName() + return self.offsets.name ~= nil +end + +local NameRecordStruct = DLib.BytesBuffer.CompileStructure([[ + UInt16 platformID // Platform identifier code. + UInt16 platformSpecificID // Platform-specific encoding identifier. + UInt16 languageID // Language identifier. + UInt16 nameID // Name identifiers. + UInt16 length // Name string length in bytes. + UInt16 offset // Name string offset in bytes from stringOffset. +]]) + +local NameTableStruct = DLib.BytesBuffer.CompileStructure([[ + UInt16 format // Format selector. Set to 0. + UInt16 count // The number of nameRecords in this name table. + UInt16 stringOffset // Offset in bytes to the beginning of the name character strings. + NameRecord nameRecords // The name records array. +]], {NameRecord = function(self, read) + local output = {} + + for i = 1, read.count do + table.insert(output, NameRecordStruct(self, read)) + end + + return output +end}) + +function meta:GetNames() + if not self.offsets.name then + error('Font lack "name" offsets!') + end + + if self._getNamesCache then + return self._getNamesCache + end + + self.bytesbuffer:Seek(self.bytespos + self.offsets.name.offset) + + local read = NameTableStruct(self.bytesbuffer) + read.name = self.bytesbuffer:ReadBinary(self.offsets.name.length - read.stringOffset) + self._getNamesCache = read + + for i, record in ipairs(read.nameRecords) do + record.contents = read.name:sub(record.offset + 1, record.length + record.offset) + end + + return read +end + +--[[ + 0 Unicode Indicates Unicode version. + 1 Macintosh QuickDraw Script Manager code. + 2 (reserved; do not use) + 3 Microsoft Microsoft encoding. +]] + +--[[ + 0 Roman 17 Malayalam + 1 Japanese 18 Sinhalese + 2 Traditional Chinese 19 Burmese + 3 Korean 20 Khmer + 4 Arabic 21 Thai + 5 Hebrew 22 Laotian + 6 Greek 23 Georgian + 7 Russian 24 Armenian + 8 RSymbol 25 Simplified Chinese + 9 Devanagari 26 Tibetan + 10 Gurmukhi 27 Mongolian + 11 Gujarati 28 Geez + 12 Oriya 29 Slavic + 13 Bengali 30 Vietnamese + 14 Tamil 31 Sindhi + 15 Telugu 32 (Uninterpreted) + 16 Kannada +]] + +--[[ + 0 Default semantics + 1 Version 1.1 semantics + 2 ISO 10646 1993 semantics (deprecated) + 3 Unicode 2.0 or later semantics (BMP only) + 4 Unicode 2.0 or later semantics (non-BMP characters allowed) + 5 Unicode Variation Sequences + 6 Full Unicode coverage (used with type 13.0 cmaps by OpenType) +]] + +--[[ + 0 Copyright notice. + 1 Font Family. + 2 Font Subfamily. + 3 Unique subfamily identification. + 4 Full name of the font. + 5 Version of the name table. + 6 PostScript name of the font. All PostScript names in a font must be identical. They may not be longer than 63 characters and the characters used are restricted to the set of printable ASCII characters (U+0021 through U+007E), less the ten characters '[', ']', '(', ')', '{', '}', '<', '>', '/', and '%'. + 7 Trademark notice. + 8 Manufacturer name. + 9 Designer; name of the designer of the typeface. + 10 Description; description of the typeface. Can contain revision information, usage recommendations, history, features, and so on. + 11 URL of the font vendor (with procotol, e.g., http://, ftp://). If a unique serial number is embedded in the URL, it can be used to register the font. + 12 URL of the font designer (with protocol, e.g., http://, ftp://) + 13 License description; description of how the font may be legally used, or different example scenarios for licensed use. This field should be written in plain language, not legalese. + 14 License information URL, where additional licensing information can be found. + 15 Reserved + 16 Preferred Family. In Windows, the Family name is displayed in the font menu, and the Subfamily name is presented as the Style name. For historical reasons, font families have contained a maximum of four styles, but font designers may group more than four fonts to a single family. The Preferred Family and Preferred Subfamily IDs allow font designers to include the preferred family/subfamily groupings. These IDs are only present if they are different from IDs 1 and 2. + 17 Preferred Subfamily. In Windows, the Family name is displayed in the font menu, and the Subfamily name is presented as the Style name. For historical reasons, font families have contained a maximum of four styles, but font designers may group more than four fonts to a single family. The Preferred Family and Preferred Subfamily IDs allow font designers to include the preferred family/subfamily groupings. These IDs are only present if they are different from IDs 1 and 2. + 18 Compatible Full (Macintosh only). In QuickDraw, the menu name for a font is constructed using the FOND resource. This usually matches the Full Name. If you want the name of the font to appear differently than the Full Name, you can insert the Compatible Full Name in ID 18. This name is not used by OS X itself, but may be used by application developers (e.g., Adobe). + 19 Sample text. This can be the font name, or any other text that the designer thinks is the best sample text to show what the font looks like. + 20–22 Defined by OpenType. + 23–255 Reserved for future expansion. + 256 - 32767 Font-specific names (layout features and settings, variations, track names, etc.) +]] + +function meta:GetNameID(id) + local read = self:GetNames() + + for i, record in ipairs(read.nameRecords) do + if record.nameID == id and (record.platformID == 0 or record.platformID == 1) and record.platformSpecificID == 0 and not record.contents:find('\x00') then + -- Microsoft's encoding sux + return record.contents, record + end + end + + return false +end + +function meta:GetFamily() + return self:GetNameID(1) +end + +function meta:GetUniqueFamily() + return self:GetNameID(3) or self:GetNameID(1) +end + +function DLib.ttf.SearchFamilies() + local files = file.Find('resource/fonts/*.ttf', 'GAME') + local files2 = file.Find('cache/workshop/resource/fonts/*.ttf', 'GAME') + local output = {} + + for i, mfile in ipairs(files) do + local ttf = DLib.ttf.Open(DLib.BytesBuffer(file.Read('resource/fonts/' .. mfile, 'GAME'))) + + if ttf:HasName() then + local getName = ttf:GetFamily() + + if getName and getName ~= '' then + table.insert(output, getName) + end + end + end + + for i, mfile in ipairs(files2) do + if not table.qhasValue(files, mfile) then + local ttf = DLib.ttf.Open(DLib.BytesBuffer(file.Read('cache/workshop/resource/fonts/' .. mfile, 'GAME'))) + + if ttf:HasName() then + local getName = ttf:GetFamily() + + if getName and getName ~= '' then + table.insert(output, getName) + end + end + end + end + + return table.deduplicate(output) +end + +local concurrentRunning + +function DLib.ttf.ASyncSearchFamilies() + if concurrentRunning then + return concurrentRunning + end + + concurrentRunning = DLib.Promise(function(resolve, reject) + local thread = coroutine.create(function() + local files = file.Find('resource/fonts/*.ttf', 'GAME') + local files2 = file.Find('cache/workshop/resource/fonts/*.ttf', 'GAME') + local output = {} + + for i, mfile in ipairs(files) do + local ttf = DLib.ttf.Open(DLib.BytesBuffer(file.Read('resource/fonts/' .. mfile, 'GAME'))) + coroutine.syswait(0.1) + + if ttf:HasName() then + local getName = ttf:GetFamily() + + if getName and getName ~= '' then + table.insert(output, getName) + end + end + end + + for i, mfile in ipairs(files2) do + if not table.qhasValue(files, mfile) then + local ttf = DLib.ttf.Open(DLib.BytesBuffer(file.Read('cache/workshop/resource/fonts/' .. mfile, 'GAME'))) + coroutine.syswait(0.1) + + if ttf:HasName() then + local getName = ttf:GetFamily() + + if getName and getName ~= '' then + table.insert(output, getName) + end + end + end + end + + concurrentRunning = nil + return resolve(table.deduplicate(output)) + end) + + coroutine.resume(thread) + end) + + return concurrentRunning +end + +surface.DLibCreateFont('DLib.ASyncSearchFamilies', { + font = 'Roboto', + size = 24, + weight = 500 +}) + +local surface = surface +local ScrWL, ScrHL = ScrWL, ScrHL +local drawColorNotify = Color(200, 200, 200) +local color_black = color_black +local ScreenSize = ScreenSize +local draw = draw +local TEXT_ALIGN_CENTER = TEXT_ALIGN_CENTER + +hook.Add('PostRenderVGUI', 'DLib.ASyncSearchFamilies', function() + if not concurrentRunning then return end + + surface.SetFont('DLib.ASyncSearchFamilies') + local text = DLib.i18n.localize('gui.dlib.notify.families_loading') + local w, h = surface.GetTextSize(text) + + local x, y = ScrWL() / 2, ScrHL() * 0.03 + x = x - w / 2 - h - ScreenSize(4) + + DLib.HUDCommons.DrawLoading(x + 2, y + 2, h, color_black, 50, math.floor(h / 6):ceil():max(4)) + DLib.HUDCommons.DrawLoading(x, y, h, drawColorNotify, 50, math.floor(h / 6):ceil():max(4)) + x = x + ScreenSize(4) + w / 2 + h + + draw.DrawText(text, 'DLib.ASyncSearchFamilies', x + 1, y + 1, color_black, TEXT_ALIGN_CENTER) + draw.DrawText(text, 'DLib.ASyncSearchFamilies', x, y, drawColorNotify, TEXT_ALIGN_CENTER) +end) + +function DLib.ttf.SearchFamiliesCached() + if DLib.ttf.__familyCache then + return DLib.ttf.__familyCache + end + + DLib.ttf.__familyCache = DLib.ttf.SearchFamilies() + return DLib.ttf.__familyCache +end + +function DLib.ttf.IsFamilyCachePresent() + return DLib.ttf.__familyCache ~= nil +end + +function DLib.ttf.IsFamilyCacheBuilding() + return concurrentRunning ~= nil +end + +function DLib.ttf.ASyncSearchFamiliesCached() + return DLib.Promise(function(resolve, reject) + if DLib.ttf.__familyCache then + return resolve(DLib.ttf.__familyCache) + end + + DLib.ttf.ASyncSearchFamilies():Then(function(list) + DLib.ttf.__familyCache = list + return resolve(DLib.ttf.__familyCache) + end):Catch(reject) + end) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/combathelper.lua b/garrysmod/addons/util-dlib/lua/dlib/util/combathelper.lua new file mode 100644 index 0000000..a522a0d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/combathelper.lua @@ -0,0 +1,241 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 IsValid = FindMetaTable('Entity').IsValid +local type = type +local NULL = NULL +local table = table +DLib.combat = DLib.combat or {} +local combat = DLib.combat + +--[[ + @doc + @fname DLib.combat.findWeapon + @args CTakeDamageInfo dmginfo + + @returns + Weapon + Entity: attacker + Entity: inflictor +]] +function combat.findWeapon(dmginfo) + local attacker, inflictor = dmginfo:GetAttacker(), dmginfo:GetInflictor() + if not IsValid(attacker) or not IsValid(inflictor) then return NULL, attacker, inflictor end + if type(attacker) ~= 'Player' or not (type(inflictor) == 'Weapon' or attacker == inflictor) then return end + local weapon = type(inflictor) == 'Weapon' and inflictor or attacker:GetActiveWeapon() + return weapon, attacker, inflictor +end + +local function interval(val, min, max) + return val > min and val <= max +end + +--[[ + @doc + @fname DLib.combat.inPVS + @args Entity pointFrom, Entity pointTo, Angle eyes = pointFrom:EyeAnglesFixed(), number yawLimit = 60, number pitchLimit = 60 + + @desc + Despite function's name, this only checks whenever pointFrom can see on screen pointTo + using eye angles check assuming they have direct line of sight to each other + @enddesc + + @returns + boolean +]] +function combat.inPVS(point1, point2, eyes, yawLimit, pitchLimit) + if type(point1) ~= 'Vector' then + if point1.EyeAnglesFixed then + eyes = eyes or point1:EyeAnglesFixed() + elseif point1.EyeAngles then + eyes = eyes or point1:EyeAngles() + end + + point1 = point1:EyePos() + end + + if type(point2) ~= 'Vector' then + point2 = point2:EyePos() + end + + yawLimit = yawLimit or 60 + pitchLimit = pitchLimit or 60 + + local ang = (point2 - point1):Angle() + local diffPith = ang.p:AngleDifference(eyes.p) + local diffYaw = ang.y:AngleDifference(eyes.y) + + return interval(diffYaw, -yawLimit, yawLimit) and interval(diffPith, -pitchLimit, pitchLimit) +end + +--[[ + @doc + @fname DLib.combat.turnAngle + @args Entity pointFrom, Entity pointTo, Angle eyes = pointFrom:EyeAnglesFixed() + + @returns + number: pitch delta + number: yaw delta +]] +function combat.turnAngle(point1, point2, eyes) + if type(point1) ~= 'Vector' then + if point1.EyeAnglesFixed then + eyes = eyes or point1:EyeAnglesFixed() + elseif point1.EyeAngles then + eyes = eyes or point1:EyeAngles() + end + + point1 = point1:EyePos() + end + + if type(point2) ~= 'Vector' then + point2 = point2:EyePos() + end + + local ang = (point2 - point1):Angle() + return ang.p:AngleDifference(eyes.p), ang.y:AngleDifference(eyes.y) +end + +--[[ + @doc + @fname DLib.combat.findWeaponAlt + @args CTakeDamageInfo dmginfo + + @returns + Weapon + Entity: attacker + Entity: inflictor +]] +function combat.findWeaponAlt(dmginfo) + local attacker, inflictor = dmginfo:GetAttacker(), dmginfo:GetInflictor() + local weapon = inflictor + + if not IsValid(inflictor) and IsValid(attacker) then + inflictor = attacker + weapon = attacker + end + + if not IsValid(attacker) or not IsValid(inflictor) then + return inflictor, attacker, inflictor + end + + if type(inflictor) ~= 'Weapon' and attacker.GetActiveWeapon then + inflictor = attacker:GetActiveWeapon() + weapon = inflictor + end + + return weapon, attacker, inflictor +end + +--[[ + @doc + @fname DLib.combat.detect + @args CTakeDamageInfo dmginfo + + @returns + Entity: attacker + Entity: weapon or inflictor + Entity: inflictor +]] +function combat.detect(dmginfo) + local weapon, attacker, inflictor = combat.findWeapon(dmginfo) + + if not IsValid(weapon) then + weapon = inflictor + end + + return attacker, weapon, inflictor +end + +--[[ + @doc + @fname DLib.combat.findPlayers + @args Entity self + + @desc + Attempts to find all players involved within certain entity. This can be a vehicle from another mod for example + (SCars or Simfphys or even Neurotec) + **This also include players who are spectating this entity** + @enddesc + + @returns + table: of players found or false, if self is NULL +]] +function combat.findPlayers(self) + if not IsValid(self) then + return false + end + + local specs = {} + + for i, ply in ipairs(player.GetAll()) do + if ply ~= self and ply:GetObserverTarget() == self then + table.insert(specs, ply) + end + end + + if type(self) == 'Player' then + table.insert(specs, self) + return specs + end + + if type(self) == 'Vehicle' then + local driver = self:GetDriver() + + if not IsValid(driver) then + return #specs ~= 0 and specs + end + + table.insert(specs, driver) + return specs + end + + local MEM = {} + local iterate = {self} + + while #iterate > 0 do + local ent = table.remove(iterate) + + if MEM[ent] then + goto CONTINUE + end + + MEM[ent] = true + + for i, ent2 in ipairs(self:GetChildren()) do + if type(ent2) == 'Player' then + table.insert(specs, ent2) + elseif type(ent2) == 'Vehicle' then + local driver = ent2:GetDriver() + + if IsValid(driver) then + table.insert(specs, driver) + end + else + table.insert(iterate, ent2) + end + end + + ::CONTINUE:: + end + + return #specs ~= 0 and table.deduplicate(specs) or false +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/http.lua b/garrysmod/addons/util-dlib/lua/dlib/util/http.lua new file mode 100644 index 0000000..4e9a72c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/http.lua @@ -0,0 +1,357 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 http = http +local DLib = DLib +local HTTP = HTTP +local assert = assert +local type = type +local istable = istable +local isstring = isstring +local pairs = pairs +local table = table +local error = error +local string = string + +local month_map = table.flipIntoHash({ + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'jun', + 'jul', + 'aug', + 'sep', + 'oct', + 'nov', + 'dec' +}) + +--[[ + @doc + @fname http.ParseDate + @args string input + + @desc + Parses date in next format `Tue, 26 Nov 2019 01:18:04 GMT` + @enddesc + + @returns + number: timestamp +]] +function http.ParseDate(input) + local day_of_week, day, month_str, year, hour, minute, second = input:lower():match('([a-z]+),%s+([0-9]+)%s+([a-z]+)%s+([0-9]+)%s+([0-9]+):([0-9]+):([0-9]+)%s+gmt') + + if not day_of_week then + day_of_week, day, month_str, year, hour, minute, second = input:lower():match('([a-z]+),%s+([0-9]+)-([a-z]+)-([0-9]+)%s+([0-9]+):([0-9]+):([0-9]+)%s+gmt') + end + + return math.dateToTimestamp(year:tonumber(), month_map[month_str] or 1, day:tonumber(), hour:tonumber(), minute:tonumber(), second:tonumber()) +end + +local sequences = {} + +do + for i = 1, 100 do + table.insert(sequences, CompileString([[ + local math_random = math.random + local char = string.char + + local function random() + local rand = math_random(1, 3) + + if rand == 1 then + return math_random(48, 57) + elseif rand == 2 then + return math_random(65, 90) + else + return math_random(97, 122) + end + end + + return function(len) + return '--' .. char(]] .. string.rep('random(), ', i + 10):sub(1, -3) .. [[) + end]], 'http.lua:random')()) + end +end + +local function figureoutSeparator(struct, len) + local separator = assert(sequences[len], 'Unable to construct random separator for multipart form! Uh-oh!')() + + for _, value in pairs(struct) do + if istable(value) then + if assert(isstring(value.body) and value.body, 'Body is missing from table inside struct'):find(separator, 1, true) then + return figureoutSeparator(struct, len + 1) + end + else + if isnumber(value) then + value = tostring(value) + struct[_] = tostring(value) + end + + if value:find(separator, 1, true) then + return figureoutSeparator(struct, len + 1) + end + end + end + + return separator +end + +local function whut(name) + assert(isstring(name) and not name:find('"', 1, true) and not name:find('\n', 1, true), 'Names should be ONLY strings and they should not contain special characters') + return name +end + +--[[ + @doc + @fname http.EncodeMultipart + @args table struct + + @desc + Encodes input as `multipart/form-data` + `struct` is a table consist of string `key`s + and `value`s either of string or table, containing + `filename`, `mimetype` and `body` values + @enddesc + + @returns + string: encoded string + string: header value for Content-Type +]] +function http.EncodeMultipart(struct) + assert(istable(struct), 'Structure must be a table!') + local separator = figureoutSeparator(struct, 1) + local build = {} + + table.insert(build, separator) + + for key, value in pairs(struct) do + if istable(value) then + local key = value.key or value.name or key + local str = 'Content-Disposition: form-data; name="' .. whut(key) .. '"' + + if value.filename then + str = str .. '; filename="' .. whut(value.filename) .. '"' + end + + table.insert(build, str) + + if value.mimetype then + assert(not value.mimetype:find(' ', 1, true), 'mime type should not contain spaces') + table.insert(build, 'Content-Type: ' .. whut(value.mimetype) .. '\n') + elseif not value.nomime then + table.insert(build, 'Content-Type: application/octet-stream\n') + else + table.insert(build, '') + end + + table.insert(build, tostring(value.body)) + table.insert(build, separator) + else + table.insert(build, 'Content-Disposition: form-data; name="' .. whut(key) .. '"\n') + table.insert(build, tostring(value)) + table.insert(build, separator) + end + end + + return table.concat(build, '\n'), 'multipart/form-data; boundary=' .. separator:sub(3) +end + +--[[ + @doc + @fname http.EncodeQuery + @args table struct + + @desc + struct is a table of `key -> value` + Both key and value should be strings + @enddesc + + @returns + string: encoded string +]] +function http.EncodeQuery(params) + assert(istable(params), 'Params input must be a table!') + + local build = {} + + for key, value in pairs(params) do + table.insert(build, http.EncodeComponent(key) .. '=' .. http.EncodeComponent(value)) + end + + return table.concat(build, '&') +end + +local function escapeUnicode(input) + if #input == 1 then return input end + + local buf = '' + + for char in input:gmatch('.') do + buf = buf .. '%' .. string.format('%X', string.byte(char)) + end + + return buf +end + +local function escape(input) + return '%' .. string.format('%X', string.byte(input)) +end + +--[[ + @doc + @fname http.EncodeComponent + @args string input + + @desc + See [Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) for details + @enddesc + + @returns + string: encoded string +]] +function http.EncodeComponent(component) + assert(isstring(component), 'Input must be a string!') + return component:gsub("[\x01-\x26]", escape) + :gsub("[\x2b-\x2f]", escape) + :gsub("[\x3a-\x40]", escape) + :gsub("[\x5b-\x5e]", escape) + :gsub("\x60", escape) + :gsub("[\x7b-\x7d]", escape) + :gsub(utf8.charpattern, escapeUnicode) +end + +--[[ + @doc + @fname http.Head + @args string url, function onsuccess, function onfailure, table headers = {} + + @desc + Executes a HTTP HEAD request + onsuccess is a function with `table headers, number code` parameters + @enddesc +]] +function http.Head(url, onsuccess, onfailure, headers) + local request = { + url = url, + method = 'HEAD', + headers = headers or {}, + } + + if onsuccess then + function request.success(code, _, headers) + return onsuccess(headers, code) + end + end + + if onfailure then + function request.failed(err) + return onfailure(err) + end + end + + return HTTP(request) +end + +--[[ + @doc + @fname http.Put + @args string url, string body, function onsuccess, function onfailure, table headers = {} + + @desc + Executes a HTTP PUT request + onsuccess is a function with `string body, number size, table headers, number code` parameters + @enddesc +]] +function http.Put(url, body, onsuccess, onfailure, headers) + local request = { + url = url, + body = body, + method = 'PUT', + headers = headers or {}, + } + + -- как же рубат заебал + -- говорит что не будет добавлять альсы (чтоб избавится от тупых имен функций) и править устаревший код и так далее + -- но такие костыли вставлять - вставляет + if headers['Content-Type'] or headers['content-type'] then + request.type = headers['Content-Type'] or headers['content-type'] + headers['Content-Type'] = nil + headers['content-type'] = nil + end + + if onsuccess then + function request.success(code, body, headers) + return onsuccess(body, #body, headers, code) + end + end + + if onfailure then + function request.failed(err) + return onfailure(err) + end + end + + return HTTP(request) +end + +--[[ + @doc + @fname http.PostBody + @args string url, string body, function onsuccess, function onfailure, table headers = {} + + @desc + Executes a HTTP POST request + onsuccess is a function with `string body, number size, table headers, number code` parameters + @enddesc +]] +function http.PostBody(url, body, onsuccess, onfailure, headers) + local request = { + url = url, + body = body, + method = 'POST', + headers = headers or {}, + } + + -- как же рубат заебал + -- говорит что не будет добавлять альсы (чтоб избавится от тупых имен функций) и править устаревший код и так далее + -- но такие костыли вставлять - вставляет + if headers['Content-Type'] or headers['content-type'] then + request.type = headers['Content-Type'] or headers['content-type'] + headers['Content-Type'] = nil + headers['content-type'] = nil + end + + if onsuccess then + function request.success(code, body, headers) + return onsuccess(body, #body, headers, code) + end + end + + if onfailure then + function request.failed(err) + return onfailure(err) + end + end + + return HTTP(request) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/httpclient.lua b/garrysmod/addons/util-dlib/lua/dlib/util/httpclient.lua new file mode 100644 index 0000000..81be992 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/httpclient.lua @@ -0,0 +1,457 @@ +do + local _class_0 + local _base_0 = { + Expired = function(self, stamp) + if stamp == nil then + stamp = os.time() + end + if self.m_Session then + return false + end + return self.m_Expires < stamp + end, + Is = function(self, path) + if self.Secure and not path:startsWith('https://') then + return false + end + local protocol, domain = path:match('(https?)://([a-zA-Z0-9.-]+)') + if not protocol or not domain then + return false + end + if self.explicitDomain and self.m_Domain ~= domain then + return false + end + if not self.explicitDomain and not domain:endsWith(self.m_Domain) then + return false + end + local uripath = path:sub(#protocol + 4 + #domain):trim() + if uripath == '' then + uripath = '/' + end + return uripath:startsWith(self.m_Path) + end, + Hash = function(self) + return tostring(self.name) .. "_" .. tostring(self.m_Domain) .. "[" .. tostring(self.explicitDomain) .. "]_" .. tostring(util.CRC(self.m_Path)) .. "_" .. tostring(self.m_Secure) + end, + Value = function(self) + return tostring(self.name) .. "=" .. tostring(self.value) + end, + Serialize = function(self) + local build = { + self.name .. '=' .. self.value, + 'Created=' .. os.date('%a, %d %b %Y %H:%M:%S GMT', self.m_CreationTime), + 'Domain=' .. self.m_Domain, + 'Path=' .. self.m_Path + } + if self.m_HttpOnly then + table.insert(build, 'HttpOnly') + end + if not self.m_Session then + table.insert(build, 'Expires=' .. os.date('%a, %d %b %Y %H:%M:%S GMT', self.m_Expires)) + end + if self.explicitDomain then + table.insert(build, 'UnsafeDomain') + end + return table.concat(build, '; ') + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, name, value, domain, path, explicitDomain) + if path == nil then + path = '/' + end + if explicitDomain == nil then + explicitDomain = true + end + assert(isstring(name), 'Name must be a string') + assert(isstring(value), 'Value must be a string') + assert(not name:find(' ', 1, true) and not name:find('=', 1, true) and not name:find(';', 1, true) and not name:find('\n', 1, true), 'Cookie name can not contain special symbols') + assert(not value:find(' ', 1, true) and not value:find('=', 1, true) and not value:find(';', 1, true) and not value:find('\n', 1, true), 'Cookie value can not contain special symbols') + assert(isstring(domain), 'Domain must be a string') + assert(isstring(path), 'Path must be a string') + assert(isstring(domain), 'Domain must be a string') + self.name = name + self.value = value + self.m_HttpOnly = false + self.m_Domain = domain + self.m_Path = path + self.m_Secure = false + self.m_CreationTime = os.time() + self.m_Expires = math.huge + self.m_Session = true + self.explicitDomain = explicitDomain + end, + __base = _base_0, + __name = "Cookie" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.Parse = function(self, strInput, domain, unsafe) + if unsafe == nil then + unsafe = false + end + assert(isstring(strInput), 'Input must be a string') + local split = strInput:split(';') + local _name = table.remove(split, 1) + local startPos, endPos = _name:find('=', 1, true) + if not startPos then + error('Malformed input. First key=value pair in cookie must always be cookie\'s name and value!') + end + local cname = _name:sub(1, startPos - 1) + local cvalue = _name:sub(startPos + 1) + local data = { + unsafedomain = true + } + for _index_0 = 1, #split do + local param = split[_index_0] + local trim = param:trim() + local _exp_0 = trim:lower() + if 'secure' == _exp_0 then + data.secure = true + elseif 'httponly' == _exp_0 then + data.httponly = true + elseif 'unsafedomain' == _exp_0 then + if unsafe then + data.unsafedomain = true + end + else + startPos, endPos = trim:find('=', 1, true) + if startPos then + local name = trim:sub(1, startPos - 1) + local value = trim:sub(startPos + 1) + if name:lower() == 'max-age' then + do + local num = tonumber(value) + if num then + data.override_expires = true + if num == 0 then + data.expires = nil + data.session = true + else + data.expires = os.time() + num + data.session = false + end + end + end + elseif name:lower() == 'expires' and not data.override_expires then + data.expires = http.ParseDate(value) + data.session = false + if data.expires < 10 then + data.expires = nil + data.session = true + end + elseif unsafe and name:lower() == 'created' then + data.created = http.ParseDate(value) + elseif name:lower() == 'domain' then + data.domain = value + data.unsafedomain = false + elseif name:lower() == 'path' then + data.path = value + end + end + end + end + local cookie = self(cname, cvalue, data.domain or domain, data.path) + if data.secure then + cookie:SetIsSecure(true) + end + if data.httponly then + cookie:SetHttpOnly(true) + end + if data.created then + cookie:SetCreationStamp(data.created) + end + if data.domain then + cookie:SetDomain(data.domain) + end + cookie:SetExplicitDomain(data.unsafedomain) + if data.expires then + cookie:SetExpiresStamp(data.expires) + cookie:SetIsSession(data.session) + end + return cookie + end + AccessorFunc(self.__base, 'm_HttpOnly', 'HttpOnly', FORCE_BOOL) + AccessorFunc(self.__base, 'explicitDomain', 'ExplicitDomain', FORCE_BOOL) + AccessorFunc(self.__base, 'm_Session', 'IsSession', FORCE_BOOL) + AccessorFunc(self.__base, 'm_CreationTime', 'CreationStamp', FORCE_NUMBER) + AccessorFunc(self.__base, 'm_Expires', 'ExpiresStamp', FORCE_NUMBER) + AccessorFunc(self.__base, 'm_Secure', 'IsSecure', FORCE_BOOL) + AccessorFunc(self.__base, 'm_Path', 'Path', FORCE_STRING) + AccessorFunc(self.__base, 'm_Domain', 'Domain', FORCE_STRING) + AccessorFunc(self.__base, 'value', 'Value', FORCE_STRING) + http.Cookie = _class_0 +end +do + local _class_0 + local _base_0 = { + Add = function(self, input, domain) + local cookie = isstring(input) and http.Cookie:Parse(input, domain) or input + local hash = cookie:Hash() + if self.jar[hash] then + cookie:SetCreationStamp(self.jar[hash]:GetCreationStamp()) + end + self.jar[hash] = cookie + return self + end, + Remove = function(self, input, domain) + local cookie = isstring(input) and http.Cookie:Parse(input, domain) or input + local hash = cookie:Hash() + if self.jar[hash] then + self.jar[hash] = nil + return true + end + return false + end, + GetFor = function(self, url) + for hash, cookie in pairs(self.jar) do + if cookie:Expired() then + self.jar[hash] = nil + end + end + local _accum_0 = { } + local _len_0 = 1 + for hash, cookie in pairs(self.jar) do + if cookie:Is(url) then + _accum_0[_len_0] = cookie + _len_0 = _len_0 + 1 + end + end + return _accum_0 + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self) + self.jar = { } + end, + __base = _base_0, + __name = "CookieJar" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + http.CookieJar = _class_0 +end +local Promise +Promise = DLib.Promise +do + local _class_0 + local _base_0 = { + CookieList = function(self, url) + local cookies = self.cookiejar:GetFor(url) + if not cookies or #cookies == 0 then + return false + end + return table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #cookies do + local cookie = cookies[_index_0] + _accum_0[_len_0] = cookie:Value() + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), '; ') + end, + Patch = function(self, url, headers) + local cookieList = self:CookieList(url) + local hit = not cookieList + local hit2 = not self.last_referer + if hit and hit2 then + return headers + end + for headerName in pairs(headers) do + if not hit and headerName:lower() == 'cookie' then + hit = true + end + if not hit2 and headerName:lower() == 'referer' then + hit2 = true + end + end + if not hit then + headers.Cookie = cookieList + end + if not hit2 then + headers.Referer = self.last_referer + end + return headers + end, + _Domain = function(self, url) + return url:match('https?://([a-zA-Z0-9.-]+)'):lower() + end, + _Receive = function(self, domain, headers) + for key, value in pairs(headers) do + if key:lower() == 'set-cookie' then + self.cookiejar:Add(value, domain) + end + end + end, + Get = function(self, url, headers) + if headers == nil then + headers = { } + end + return Promise(function(resolve, reject) + return http.PromiseGet(url, self:Patch(url, headers)):Catch(reject):Then(function(body, size, headers, code, ...) + if body == nil then + body = '' + end + if size == nil then + size = 0 + end + if headers == nil then + headers = { } + end + if code == nil then + code = 500 + end + self:_Receive(self:_Domain(url), headers) + if code >= 200 and code < 300 then + self.last_referer = url + end + return resolve(body, size, headers, code, ...) + end) + end) + end, + Post = function(self, url, params, headers) + if headers == nil then + headers = { } + end + return Promise(function(resolve, reject) + return http.PromisePost(url, params, self:Patch(url, headers)):Catch(reject):Then(function(body, size, headers, code, ...) + if body == nil then + body = '' + end + if size == nil then + size = 0 + end + if headers == nil then + headers = { } + end + if code == nil then + code = 500 + end + self:_Receive(self:_Domain(url), headers) + if code >= 200 and code < 300 then + self.last_referer = url + end + return resolve(body, size, headers, code, ...) + end) + end) + end, + PostBody = function(self, url, body, headers) + if headers == nil then + headers = { } + end + return Promise(function(resolve, reject) + return http.PromisePostBody(url, body, self:Patch(url, headers)):Catch(reject):Then(function(body, size, headers, code, ...) + if body == nil then + body = '' + end + if size == nil then + size = 0 + end + if headers == nil then + headers = { } + end + if code == nil then + code = 500 + end + self:_Receive(self:_Domain(url), headers) + if code >= 200 and code < 300 then + self.last_referer = url + end + return resolve(body, size, headers, code, ...) + end) + end) + end, + Put = function(self, url, body, headers) + if headers == nil then + headers = { } + end + return Promise(function(resolve, reject) + return http.PromisePut(url, body, self:Patch(url, headers)):Catch(reject):Then(function(body, size, headers, code, ...) + if body == nil then + body = '' + end + if size == nil then + size = 0 + end + if headers == nil then + headers = { } + end + if code == nil then + code = 500 + end + self:_Receive(self:_Domain(url), headers) + if code >= 200 and code < 300 then + self.last_referer = url + end + return resolve(body, size, headers, code, ...) + end) + end) + end, + Head = function(self, url, headers) + if headers == nil then + headers = { } + end + return Promise(function(resolve, reject) + return http.PromiseHead(url, self:Patch(url, headers)):Catch(reject):Then(function(headers, code, ...) + if headers == nil then + headers = { } + end + if code == nil then + code = 500 + end + self:_Receive(self:_Domain(url), headers) + if code >= 200 and code < 300 then + self.last_referer = url + end + return resolve(headers, code, ...) + end) + end) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, cookiejar) + if isstring(cookiejar) then + error('serializing of cookiejar is not supported yet') + elseif type(cookiejar) == 'table' then + self.cookiejar = cookiejar + else + self.cookiejar = http.CookieJar() + end + self.set_referer = true + self.last_referer = false + end, + __base = _base_0, + __name = "Client" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + AccessorFunc(self.__base, 'set_referer', 'SetReferer', FORCE_BOOL) + AccessorFunc(self.__base, 'last_referer', 'LastReferer') + http.Client = _class_0 + return _class_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/message.lua b/garrysmod/addons/util-dlib/lua/dlib/util/message.lua new file mode 100644 index 0000000..f88ba5c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/message.lua @@ -0,0 +1,1052 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 = table +local DLib = DLib + +-- this WILL break EPOE +-- local MsgC = MsgC +-- local Msg = Msg + +local type = type +local net = net + +local function RepackMessage(strIn) + local output = {} + + for line in string.gmatch(strIn, '([^ ]+)') do + if #output ~= 0 then + table.insert(output, ' ') + end + + table.insert(output, line) + end + + return output +end + +local DEFAULT_TEXT_COLOR = Color(200, 200, 200) +local WARNING_COLOR = Color(239, 215, 52) +local ERROR_COLOR = Color(239, 78, 52) +local BOOLEAN_COLOR = Color(69, 112, 235) +local NUMBER_COLOR = Color(245, 199, 64) +local STEAMID_COLOR = Color(255, 255, 255) +local ENTITY_COLOR = Color(180, 232, 180) +local NPC_COLOR = Color(116, 193, 209) +local NEXTBOT_COLOR = Color(84, 196, 121) +local WEAPON_COLOR = Color(189, 82, 122) +local VEHICLE_COLOR = Color(189, 82, 170) +local FUNCTION_COLOR = Color(102, 133, 237) +local TABLE_COLOR = Color(107, 200, 224) +local URL_COLOR = Color(174, 124, 192) +local NIL_COLOR = Color(89, 93, 251) +local COMMENTARY_COLOR = Color(143, 165, 46) + +DLib.DEFAULT_TEXT_COLOR = DEFAULT_TEXT_COLOR +DLib.WARNING_COLOR = WARNING_COLOR +DLib.ERROR_COLOR = ERROR_COLOR +DLib.BOOLEAN_COLOR = BOOLEAN_COLOR +DLib.NUMBER_COLOR = NUMBER_COLOR +DLib.STEAMID_COLOR = STEAMID_COLOR +DLib.ENTITY_COLOR = ENTITY_COLOR +DLib.FUNCTION_COLOR = FUNCTION_COLOR +DLib.TABLE_COLOR = TABLE_COLOR +DLib.URL_COLOR = URL_COLOR +DLib.NPC_COLOR = NPC_COLOR +DLib.NEXTBOT_COLOR = NEXTBOT_COLOR +DLib.WEAPON_COLOR = WEAPON_COLOR +DLib.VEHICLE_COLOR = VEHICLE_COLOR +DLib.NIL_COLOR = NIL_COLOR +DLib.COMMENTARY_COLOR = COMMENTARY_COLOR + +function DLib.PrettyPrint(val, newline, valType) + MsgC(DLib.GetPrettyPrint(val, valType)) + + if newline == true or newline == nil then + MsgC('\n') + end +end + +function DLib.GetPrettyPrint(val, valType) + valType = valType or type(val) + + if valType == 'nil' then + return BOOLEAN_COLOR, 'nil' + elseif valType == 'number' then + return NUMBER_COLOR, tostring(val) + elseif valType == 'string' then + if val:find('^https?://') then + return URL_COLOR, val + else + return DEFAULT_TEXT_COLOR, val + end + elseif valType == 'Player' then + local nick = val:Nick() + + if val.SteamName and val:SteamName() ~= val:Nick() then + nick = nick .. ' (' .. val:SteamName() .. ')' + end + + return team and team.GetColor(val:Team()) or ENTITY_COLOR, nick, STEAMID_COLOR, '<' .. val:SteamID() .. '>' + elseif valType == 'Entity' then + return ENTITY_COLOR, tostring(val) + elseif valType == 'NPC' then + return NPC_COLOR, tostring(val) + elseif valType == 'Vehicle' then + return VEHICLE_COLOR, tostring(val) + elseif valType == 'NextBot' then + return NEXTBOT_COLOR, tostring(val) + elseif valType == 'Weapon' then + return WEAPON_COLOR, tostring(val) + elseif IsColor(val) then + return FUNCTION_COLOR, tostring(val), val, ' ███' + elseif valType == 'table' then + return TABLE_COLOR, string.format('table - %p (%s)', val, tostring(val)) + elseif valType == 'function' then + local info = debug.getinfo(val) + return FUNCTION_COLOR, string.format('function - %p', val), COMMENTARY_COLOR, ' --[[ ' .. info.short_src .. ': ' .. (info.lastlinedefined ~= info.linedefined and (info.linedefined .. '-' .. info.lastlinedefined) or info.lastlinedefined) .. ' ]]' + elseif valType == 'boolean' then + return BOOLEAN_COLOR, tostring(val) + elseif valType == 'Vector' then + return FUNCTION_COLOR, 'Vector', DEFAULT_TEXT_COLOR, '(', NUMBER_COLOR, val.x, DEFAULT_TEXT_COLOR, ', ', NUMBER_COLOR, val.y, DEFAULT_TEXT_COLOR, ', ', NUMBER_COLOR, val.z, DEFAULT_TEXT_COLOR, ')' + elseif valType == 'Angle' then + return FUNCTION_COLOR, 'Angle', DEFAULT_TEXT_COLOR, '(', NUMBER_COLOR, val.p, DEFAULT_TEXT_COLOR, ', ', NUMBER_COLOR, val.y, DEFAULT_TEXT_COLOR, ', ', NUMBER_COLOR, val.r, DEFAULT_TEXT_COLOR, ')' + end + + return DEFAULT_TEXT_COLOR, tostring(val) +end + +local function __Format(tabIn, prevColor, output) + local max = 0 + + for k, v in pairs(tabIn) do max = max:max(k) end + + for i = 1, max do + local val = tabIn[i] + local valType = type(val) + + if valType == 'nil' then + table.insert(output, NIL_COLOR:Copy()) + table.insert(output, 'nil') + table.insert(output, prevColor:Copy()) + elseif valType == 'number' then + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'string' then + if val:find('^https?://') then + table.insert(output, URL_COLOR:Copy()) + table.insert(output, val) + table.insert(output, prevColor:Copy()) + else + table.insert(output, val) + end + elseif valType == 'Player' then + table.insert(output, team and team.GetColor(val:Team()) or ENTITY_COLOR:Copy()) + table.insert(output, val:Nick()) + + if val.SteamName and val:SteamName() ~= val:Nick() then + table.insert(output, ' (' .. val:SteamName() .. ')') + end + + table.insert(output, STEAMID_COLOR:Copy()) + table.insert(output, '<' .. val:SteamID() .. '>') + table.insert(output, prevColor:Copy()) + elseif valType == 'Entity' then + table.insert(output, ENTITY_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'NPC' then + table.insert(output, NPC_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'Vehicle' then + table.insert(output, VEHICLE_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'NextBot' then + table.insert(output, NEXTBOT_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'Weapon' then + table.insert(output, WEAPON_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif IsColor(val) then + table.insert(output, val) + prevColor = val + elseif valType == 'table' then + table.insert(output, TABLE_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'function' then + table.insert(output, FUNCTION_COLOR:Copy()) + table.insert(output, string.format('function - %p', val)) + table.insert(output, COMMENTARY_COLOR:Copy()) + local info = debug.getinfo(val) + table.insert(output, ' --[[ ' .. info.short_src .. ': ' .. (info.lastlinedefined ~= info.linedefined and (info.linedefined .. '-' .. info.lastlinedefined) or info.lastlinedefined) .. ' ]]') + table.insert(output, prevColor:Copy()) + elseif valType == 'boolean' then + table.insert(output, BOOLEAN_COLOR:Copy()) + table.insert(output, tostring(val)) + table.insert(output, prevColor:Copy()) + elseif valType == 'Vector' then + table.insert(output, FUNCTION_COLOR:Copy()) + table.insert(output, 'Vector') + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, '(') + + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val.x)) + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, ', ') + + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val.y)) + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, ', ') + + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val.z)) + + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, ')') + elseif valType == 'Angle' then + table.insert(output, FUNCTION_COLOR:Copy()) + table.insert(output, 'Angle') + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, '(') + + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val.p)) + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, ', ') + + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val.y)) + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, ', ') + + table.insert(output, NUMBER_COLOR:Copy()) + table.insert(output, tostring(val.r)) + + table.insert(output, DEFAULT_TEXT_COLOR:Copy()) + table.insert(output, ')') + else + table.insert(output, tostring(val)) + end + end +end + +local function FormatMessageRegular(tabIn) + local prevColor = DEFAULT_TEXT_COLOR + local output = {prevColor} + + __Format(tabIn, prevColor, output) + + return output +end + +local function FormatMessageWarning(tabIn) + local prevColor = WARNING_COLOR + local output = {prevColor} + + __Format(tabIn, prevColor, output) + + return output +end + +local function FormatMessageError(tabIn) + local prevColor = ERROR_COLOR + local output = {prevColor} + + __Format(tabIn, prevColor, output) + + return output +end + +local LocalPlayer = LocalPlayer + +--[[ + @doc + @fname DLib.CMessage + @args table target, string addonName, Color prefixColor = Color(0, 200, 0) + + @desc + Defines messaging function + function list it provide (defines) in `target` table is available + under `CMessage` library (pseudo library, it doesn't actually exist on runtime) + @enddesc + + @returns + table: target +]] + +--[[ + @doc + @fname CMessage.Message + @args vargarg values + + @desc + formats and prints a console message + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageWarning + @args vargarg values + + @desc + differs from `CMessage.Message` only by default text color (yellow) + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageError + @args vargarg values + + @desc + differs from `CMessage.Message` only by default text color (red) + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessage + @args vargarg values + + @desc + formats and prints a console message + differs from `Message` by formatting string values with i18n library + `LocalizedMessage` + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageWarning + @args vargarg values + + @desc + differs from `CMessage.LMessage` only by default text color (yellow) + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageError + @args vargarg values + + @desc + differs from `CMessage.LMessage` only by default text color (red) + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessagePlayer + @args Player ply, vargarg values + + @desc + same as it's corresponding part but sends message to player's console. + player can be a NULL entity. if so, prints to server console. + calling clientside always prints to local console. + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageWarningPlayer + @args vargarg values + + @desc + same as it's corresponding part but sends message to player's console. + player can be a NULL entity. if so, prints to server console. + calling clientside always prints to local console. + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageErrorPlayer + @args vargarg values + + @desc + same as it's corresponding part but sends message to player's console. + player can be a NULL entity. if so, prints to server console. + calling clientside always prints to local console. + @enddesc + + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessagePlayer + @args vargarg values + + @desc + same as it's corresponding part but sends message to player's console. + player can be a NULL entity. if so, prints to server console. + calling clientside always prints to local console. + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageWarningPlayer + @args vargarg values + + @desc + same as it's corresponding part but sends message to player's console. + player can be a NULL entity. if so, prints to server console. + calling clientside always prints to local console. + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageErrorPlayer + @args vargarg values + + @desc + same as it's corresponding part but sends message to player's console. + player can be a NULL entity. if so, prints to server console. + calling clientside always prints to local console. + @enddesc + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageAll + @args vargarg values + + @server + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageWarningAll + @args vargarg values + + @server + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.MessageErrorAll + @args vargarg values + + @server + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageAll + @args vargarg values + + @server + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageWarningAll + @args vargarg values + + @server + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.LMessageErrorAll + @args vargarg values + + @server + + @returns + table: formatted table of arguments printed to console +]] + +--[[ + @doc + @fname CMessage.Chat + @args vargarg values + + @client + + @desc + same as it's `Message` counterpart but for printing in chat + @enddesc + + @returns + table: formatted table of arguments printed to chat +]] + +--[[ + @doc + @fname CMessage.ChatWarning + @args vargarg values + + @client + + @desc + same as it's `MessageWarning` counterpart but for printing in chat + @enddesc + + @returns + table: formatted table of arguments printed to chat +]] + +--[[ + @doc + @fname CMessage.ChatError + @args vargarg values + + @client + + @desc + same as it's `MessageError` counterpart but for printing in chat + @enddesc + + @returns + table: formatted table of arguments printed to chat +]] + +--[[ + @doc + @fname CMessage.LChat + @args vargarg values + + @client + + @desc + same as it's `LMessage` counterpart but for printing in chat + @enddesc + + @returns + table: formatted table of arguments printed to chat +]] + +--[[ + @doc + @fname CMessage.LChatWarning + @args vargarg values + + @client + + @desc + same as it's `LMessageWarning` counterpart but for printing in chat + @enddesc + + @returns + table: formatted table of arguments printed to chat +]] + +--[[ + @doc + @fname CMessage.LChatError + @args vargarg values + + @client + + @desc + same as it's `LMessageError` counterpart but for printing in chat + @enddesc + + @returns + table: formatted table of arguments printed to chat +]] +return function(tableTarget, moduleName, moduleColor) + local nwname = 'DLib.Message.' .. util.CRC(moduleName) + local nwnameL = 'DLib.Message.' .. util.CRC(moduleName) .. '.L' + local nwnameW = 'DLib.MessageW.' .. util.CRC(moduleName) + local nwnameWL = 'DLib.MessageW.' .. util.CRC(moduleName) .. '.L' + local nwnameE = 'DLib.MessageE.' .. util.CRC(moduleName) + local nwnameEL = 'DLib.MessageE.' .. util.CRC(moduleName) .. '.L' + + if SERVER then + net.pool(nwname) + net.pool(nwnameW) + net.pool(nwnameE) + net.pool(nwnameL) + net.pool(nwnameWL) + net.pool(nwnameEL) + end + + local PREFIX = '[' .. moduleName .. '] ' + local PREFIX_COLOR = moduleColor or Color(0, 200, 0) + + local function Message(...) + local formatted = FormatMessageRegular({...}) + MsgC(PREFIX_COLOR, PREFIX, unpack(formatted)) + MsgC('\n') + return formatted + end + + local function LMessage(...) + local formatted = FormatMessageRegular(DLib.i18n.rebuildTable({...}, DEFAULT_TEXT_COLOR)) + MsgC(PREFIX_COLOR, PREFIX, unpack(formatted)) + MsgC('\n') + return formatted + end + + local function Warning(...) + local formatted = FormatMessageWarning({...}) + MsgC(PREFIX_COLOR, PREFIX, unpack(formatted)) + MsgC('\n') + return formatted + end + + local function LWarning(...) + local formatted = FormatMessageWarning(DLib.i18n.rebuildTable({...}, WARNING_COLOR)) + MsgC(PREFIX_COLOR, PREFIX, unpack(formatted)) + MsgC('\n') + return formatted + end + + local function PrintError(...) + local formatted = FormatMessageError({...}) + MsgC(PREFIX_COLOR, PREFIX, unpack(formatted)) + MsgC('\n') + return formatted + end + + local function LPrintError(...) + local formatted = FormatMessageError(DLib.i18n.rebuildTable({...}, ERROR_COLOR)) + MsgC(PREFIX_COLOR, PREFIX, unpack(formatted)) + MsgC('\n') + return formatted + end + + local function Chat(...) + local formatted = FormatMessageRegular({...}) + chat.AddText(PREFIX_COLOR, PREFIX, DEFAULT_TEXT_COLOR, unpack(formatted)) + return formatted + end + + local function LChat(...) + local formatted = FormatMessageRegular(DLib.i18n.rebuildTable({...}, DEFAULT_TEXT_COLOR)) + chat.AddText(PREFIX_COLOR, PREFIX, DEFAULT_TEXT_COLOR, unpack(formatted)) + return formatted + end + + local function ChatError(...) + local formatted = FormatMessageError({...}) + chat.AddText(PREFIX_COLOR, PREFIX, ERROR_COLOR, unpack(formatted)) + return formatted + end + + local function LChatError(...) + local formatted = FormatMessageError(DLib.i18n.rebuildTable({...}, ERROR_COLOR)) + chat.AddText(PREFIX_COLOR, PREFIX, ERROR_COLOR, unpack(formatted)) + return formatted + end + + local function ChatWarn(...) + local formatted = FormatMessageWarning({...}) + chat.AddText(PREFIX_COLOR, PREFIX, WARNING_COLOR, unpack(formatted)) + return formatted + end + + local function LChatWarn(...) + local formatted = FormatMessageWarning(DLib.i18n.rebuildTable({...}, WARNING_COLOR)) + chat.AddText(PREFIX_COLOR, PREFIX, WARNING_COLOR, unpack(formatted)) + return formatted + end + + local function FormatMessage(...) + return FormatMessageRegular({PREFIX_COLOR, PREFIX, DEFAULT_TEXT_COLOR, ...}) + end + + local function LFormatMessage(...) + return FormatMessageRegular(DLib.i18n.rebuildTable({PREFIX_COLOR, PREFIX, DEFAULT_TEXT_COLOR, ...}, DEFAULT_TEXT_COLOR)) + end + + local function FormatMessageWarn(...) + return FormatMessageWarning({PREFIX_COLOR, PREFIX, WARNING_COLOR, ...}) + end + + local function LFormatMessageWarn(...) + return FormatMessageWarning(DLib.i18n.rebuildTable({PREFIX_COLOR, PREFIX, WARNING_COLOR, ...}, DEFAULT_TEXT_COLOR)) + end + + local function FormatMessageErr(...) + return FormatMessageError({PREFIX_COLOR, PREFIX, ERROR_COLOR, ...}) + end + + local function LFormatMessageErr(...) + return FormatMessageError(DLib.i18n.rebuildTable({PREFIX_COLOR, PREFIX, ERROR_COLOR, ...}, DEFAULT_TEXT_COLOR)) + end + + local function FormatMessageRaw(...) + return FormatMessageRegular({...}) + end + + local function LFormatMessageRaw(...) + return FormatMessageRegular(DLib.i18n.rebuildTable({...}, DEFAULT_TEXT_COLOR)) + end + + local function FormatMessageWarnRaw(...) + return FormatMessageWarning({...}) + end + + local function LFormatMessageWarnRaw(...) + return FormatMessageWarning(DLib.i18n.rebuildTable({...}, DEFAULT_TEXT_COLOR)) + end + + local function FormatMessageErrRaw(...) + return FormatMessageError({...}) + end + + local function LFormatMessageErrRaw(...) + return FormatMessageError(DLib.i18n.rebuildTable({...}, DEFAULT_TEXT_COLOR)) + end + + local function MessagePlayer(ply, ...) + if CLIENT and ply == LocalPlayer() then return Message(...) end + if CLIENT then return {} end + + if type(ply) == 'table' or type(ply) == 'Player' then + net.Start(nwname, true) + net.WriteArray({...}) + net.Send(ply) + else + return Message(...) + end + end + + local function LMessagePlayer(ply, ...) + if CLIENT and ply == LocalPlayer() then return LMessage(...) end + if CLIENT then return {} end + + if type(ply) == 'table' or type(ply) == 'Player' then + net.Start(nwnameL, true) + net.WriteArray({...}) + net.Send(ply) + else + return LMessage(...) + end + end + + local function MessageAll(...) + if CLIENT then return Message(...) end + + net.Start(nwname, true) + net.WriteArray({...}) + net.Broadcast() + + return Message(...) + end + + local function LMessageAll(...) + if CLIENT then return LMessage(...) end + + net.Start(nwnameL, true) + net.WriteArray({...}) + net.Broadcast() + + return LMessage(...) + end + + local function PrintErrorPlayer(ply, ...) + if CLIENT and ply == LocalPlayer() then return PrintError(...) end + if CLIENT then return {} end + + if type(ply) == 'table' or type(ply) == 'Player' then + net.Start(nwnameE, true) + net.WriteArray({...}) + net.Send(ply) + else + return PrintError(...) + end + end + + local function LPrintErrorPlayer(ply, ...) + if CLIENT and ply == LocalPlayer() then return LPrintError(...) end + if CLIENT then return {} end + + if type(ply) == 'table' or type(ply) == 'Player' then + net.Start(nwnameEL, true) + net.WriteArray({...}) + net.Send(ply) + else + return LPrintError(...) + end + end + + local function PrintErrorAll(...) + if CLIENT then return PrintError(...) end + + net.Start(nwnameE, true) + net.WriteArray({...}) + net.Broadcast() + + return PrintError(...) + end + + local function LPrintErrorAll(...) + if CLIENT then return LPrintError(...) end + + net.Start(nwnameEL, true) + net.WriteArray({...}) + net.Broadcast() + + return LPrintError(...) + end + + local function WarningPlayer(ply, ...) + if CLIENT and ply == LocalPlayer() then return Warning(...) end + if CLIENT then return {} end + + if type(ply) == 'table' or type(ply) == 'Player' then + net.Start(nwnameW, true) + net.WriteArray({...}) + net.Send(ply) + else + return Warning(...) + end + end + + local function LWarningPlayer(ply, ...) + if CLIENT and ply == LocalPlayer() then return LWarning(...) end + if CLIENT then return {} end + + if type(ply) == 'table' or type(ply) == 'Player' then + net.Start(nwnameWL, true) + net.WriteArray({...}) + net.Send(ply) + else + return LWarning(...) + end + end + + local function WarningAll(...) + if CLIENT then return Warning(...) end + + net.Start(nwnameW, true) + net.WriteArray({...}) + net.Broadcast() + + return Warning(...) + end + + local function LWarningAll(...) + if CLIENT then return LWarning(...) end + + net.Start(nwnameWL, true) + net.WriteArray({...}) + net.Broadcast() + + return LWarning(...) + end + + if CLIENT then + net.receive(nwname, function() + local array = net.ReadArray() + Message(unpack(array)) + end) + + net.receive(nwnameL, function() + local array = net.ReadArray() + LMessage(unpack(array)) + end) + + net.receive(nwnameW, function() + local array = net.ReadArray() + Warning(unpack(array)) + end) + + net.receive(nwnameWL, function() + local array = net.ReadArray() + LWarning(unpack(array)) + end) + + net.receive(nwnameE, function() + local array = net.ReadArray() + PrintError(unpack(array)) + end) + + net.receive(nwnameEL, function() + local array = net.ReadArray() + LPrintError(unpack(array)) + end) + end + + local function export(tableTo) + tableTo.Message = Message + tableTo.LMessage = LMessage + tableTo.Warning = Warning + tableTo.LWarning = LWarning + tableTo.MessageWarning = Warning + tableTo.LMessageWarning = LWarning + tableTo.PrintError = PrintError + tableTo.LPrintError = LPrintError + tableTo.MessageError = PrintError + tableTo.LMessageError = LPrintError + + tableTo.textcolor = Color(DEFAULT_TEXT_COLOR) + tableTo.textcolorWarn = Color(WARNING_COLOR) + tableTo.textcolorErr = Color(ERROR_COLOR) + tableTo.textcolorError = Color(ERROR_COLOR) + + tableTo.RepackMessage = RepackMessage + tableTo.FormatMessage = FormatMessage + tableTo.FormatMessageWarning = FormatMessageWarn + tableTo.FormatMessageWarn = FormatMessageWarn + tableTo.FormatMessageError = FormatMessageErr + tableTo.FormatMessageErr = FormatMessageErr + + tableTo.LFormatMessage = LFormatMessage + tableTo.LFormatMessageWarning = LFormatMessageWarn + tableTo.LFormatMessageWarn = LFormatMessageWarn + tableTo.LFormatMessageError = LFormatMessageErr + tableTo.LFormatMessageErr = LFormatMessageErr + + tableTo.FormatMessageRaw = FormatMessageRaw + tableTo.FormatMessageWarningRaw = FormatMessageWarnRaw + tableTo.FormatMessageWarnRaw = FormatMessageWarnRaw + tableTo.FormatMessageErrorRaw = FormatMessageErrRaw + tableTo.FormatMessageErrRaw = FormatMessageErrRaw + + tableTo.LFormatMessageRaw = LFormatMessageRaw + tableTo.LFormatMessageWarningRaw = LFormatMessageWarnRaw + tableTo.LFormatMessageWarnRaw = LFormatMessageWarnRaw + tableTo.LFormatMessageErrorRaw = LFormatMessageErrRaw + tableTo.LFormatMessageErrRaw = LFormatMessageErrRaw + + tableTo.lformatMessage = LFormatMessage + tableTo.message = Message + tableTo.lmessage = LMessage + tableTo.repackMessage = RepackMessage + tableTo.formatMessage = FormatMessage + + if CLIENT then + tableTo.Chat = Chat + tableTo.ChatWarn = ChatWarn + tableTo.ChatError = ChatError + tableTo.ChatMessage = Chat + tableTo.ChatPrint = Chat + tableTo.ChatPrintWarn = ChatWarn + tableTo.ChatPrintWarning = ChatWarn + tableTo.ChatPrintError = ChatError + tableTo.AddChat = Chat + tableTo.chatMessage = Chat + + tableTo.LChat = LChat + tableTo.LChatError = LChatError + tableTo.LChatWarn = LChatWarn + tableTo.LChatMessage = LChat + tableTo.LChatPrint = LChat + tableTo.LChatPrintWarn = LChatWarn + tableTo.LChatPrintWarning = LChatWarn + tableTo.LChatPrintError = LChatError + tableTo.LAddChat = LChat + tableTo.lchatMessage = LChat + else + tableTo.MessageAll = MessageAll + tableTo.MessageErrorAll = PrintErrorAll + tableTo.LMessageErrorAll = LPrintErrorAll + tableTo.PrintErrorAll = PrintErrorAll + tableTo.LPrintErrorAll = LPrintErrorAll + tableTo.MessageWarningAll = WarningAll + tableTo.WarningAll = WarningAll + tableTo.MessageWarningAll = WarningAll + tableTo.LWarningAll = LWarningAll + tableTo.LMessageWarningAll = LWarningAll + tableTo.LMessageAll = LMessageAll + end + + tableTo.MessagePlayer = MessagePlayer + tableTo.MessageErrorPlayer = PrintErrorPlayer + tableTo.PrintErrorPlayer = PrintErrorPlayer + tableTo.WarningPlayer = WarningPlayer + tableTo.MessageWarningPlayer = WarningPlayer + + tableTo.messagePlayer = MessagePlayer + tableTo.messageP = MessagePlayer + + tableTo.LMessagePlayer = LMessagePlayer + tableTo.LPrintErrorPlayer = LPrintErrorPlayer + tableTo.LWarningPlayer = LWarningPlayer + + tableTo.LMessageErrorPlayer = LPrintErrorPlayer + tableTo.LMessageWarningPlayer = LWarningPlayer + + tableTo.lmessagePlayer = LMessagePlayer + tableTo.lmessageP = LMessagePlayer + end + + tableTarget = tableTarget or {} + export(tableTarget) + return export, tableTarget +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/node.lua b/garrysmod/addons/util-dlib/lua/dlib/util/node.lua new file mode 100644 index 0000000..0be964d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/node.lua @@ -0,0 +1,126 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +-- TODO: Consider removal or rewrite +return function() + local export = {} + local usedSlots = {} + + export.slotUsagePriority = { + {0, 0}, + {1, 0}, + {-1, 0}, + {0, 1}, + {0, -1}, + {-1, 1}, + {1, 1}, + {1, -1}, + {-1, -1}, + } + + local slotUsagePriority = export.slotUsagePriority + + function export.findNearest(x, y, size) + usedSlots[size] = usedSlots[size] or {} + local div = size * 24 + local ctab = usedSlots[size] + local slotX, slotY = math.floor(x / div), math.floor(y / div) + + if ctab[slotX] == nil then + ctab[slotX] = {} + ctab[slotX][slotY] = true + return x, y + else + local findFreeSlotX, findFreeSlotY = 0, 0 + + for radius = 1, 10 do + local success = false + + for i, priorityData in ipairs(slotUsagePriority) do + local sx, sy = priorityData[1] * radius + slotX, priorityData[2] * radius + slotY + + if not ctab[sx] or not ctab[sx][sy] then + findFreeSlotX, findFreeSlotY = sx, sy + success = true + break + end + end + + if success then + break + end + end + + ctab[findFreeSlotX] = ctab[findFreeSlotX] or {} + ctab[findFreeSlotX][findFreeSlotY] = true + + return findFreeSlotX * div, findFreeSlotY * div + end + end + + function export.findNearestAlt(x, y, size, sizeH) + size = size or 16 + sizeH = sizeH or size + usedSlots[size] = usedSlots[size] or {} + local div = size * 24 + local divY = sizeH * 24 + local ctab = usedSlots[size] + local slotX, slotY = math.floor(x / div), math.floor(y / divY) + + if ctab[slotX] == nil then + ctab[slotX] = {} + ctab[slotX][slotY] = true + return x, y + else + local findFreeSlotX, findFreeSlotY = 0, 0 + + for radius = 1, 10 do + local success = false + + for x = radius, -radius, -1 do + for y = radius, -radius, -1 do + local sx, sy = x + slotX, y + slotY + + if not ctab[sx] or not ctab[sx][sy] then + findFreeSlotX, findFreeSlotY = sx, sy + success = true + break + end + end + end + + if success then + break + end + end + + ctab[findFreeSlotX] = ctab[findFreeSlotX] or {} + ctab[findFreeSlotX][findFreeSlotY] = true + + return findFreeSlotX * div, findFreeSlotY * divY + end + end + + function export.clear() + usedSlots = {} + end + + return export +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/promisify.lua b/garrysmod/addons/util-dlib/lua/dlib/util/promisify.lua new file mode 100644 index 0000000..0c10c8b --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/promisify.lua @@ -0,0 +1,126 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 http = http +local DLib = DLib +local Promise = DLib.Promise + +http.Get = http.Fetch + +function http.PromiseFetch(url, headers) + return Promise(function(resolve, reject) + http.Fetch(url, resolve, reject, headers) + end) +end + +http.PromiseGet = http.PromiseFetch + +function http.PromisePost(url, params, headers) + return Promise(function(resolve, reject) + http.Post(url, params, resolve, reject, headers) + end) +end + +function http.PromisePostBody(url, body, headers) + return Promise(function(resolve, reject) + http.PostBody(url, body, resolve, reject, headers) + end) +end + +function http.PromiseHead(url, headers) + return Promise(function(resolve, reject) + http.Head(url, resolve, reject, headers) + end) +end + +function http.PromisePut(url, body, params, headers) + return Promise(function(resolve, reject) + http.Put(url, body, resolve, reject, headers) + end) +end + +if CLIENT then + local steamworks = steamworks + + function steamworks.PromiseDownload(wsid, uncompress) + return Promise(function(resolve) + steamworks.Download(wsid, uncompress, function(data) + if not data then + reject('Unsuccessful') + return + end + + resolve(data) + end) + end) + end + + function steamworks.PromiseFileInfo(wsid) + return Promise(function(resolve, reject) + steamworks.FileInfo(wsid, function(data) + if not data then + reject('Unsuccessful') + return + end + + resolve(data) + end) + end) + end + + function steamworks.PromiseGetList(_type, tags, offset, numRetrieve, days, userID) + return Promise(function(resolve, reject) + steamworks.GetList(_type, tags, offset, numRetrieve, days, userID, function(data) + if not data then + reject('Unsuccessful') + return + end + + resolve(data) + end) + end) + end + + function steamworks.PromiseRequestPlayerInfo(steamid64) + return Promise(function(resolve, reject) + steamworks.RequestPlayerInfo(steamid64, function(data) + if not data then + reject('Unsuccessful') + return + end + + resolve(data) + end) + end) + end + + function steamworks.PromiseVoteInfo(wsid) + return Promise(function(resolve, reject) + steamworks.VoteInfo(steamid64, function(data) + if not data then + reject('Unsuccessful') + return + end + + resolve(data) + end) + end) + end +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/queue.lua b/garrysmod/addons/util-dlib/lua/dlib/util/queue.lua new file mode 100644 index 0000000..493658e --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/queue.lua @@ -0,0 +1,86 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 QUQUED_CALLS = {} +local QUQUED_CALLS_WRAPPED = {} + +local table = table +local ipairs = ipairs +local select = select +local unpack = unpack + +--[[ + @doc + @fname DLib.QueuedFunction + @args function funcIn, vargarg values + + @desc + Adds function to internal run queue. + If queue is empty, function provided will be ran on next frame. + If not, function will run after all previous function will be executed over game frames. + Can be used for queueing functions which can be called ina burst on a single game frame + and are not timing critical, like broadcasting log messages. + In example above it will avoid net channel overflow and avoid data discard when using + `reliable = false` in net.Start + @enddesc +]] +function DLib.QueuedFunction(funcIn, ...) + table.insert(QUQUED_CALLS, { + nextevent = funcIn, + args = {...}, + argsNum = select('#', ...) + }) +end + +--[[ + @doc + @fname DLib.WrappedQueueFunction + @args function upvalue + + @desc + same purpose as DLib.QueuedFunction, but instead returns function + which on call will create queue entry + @enddesc + + @returns + function: a function to call. can accept vararg arguments which will be passed to upvalue function +]] +function DLib.WrappedQueueFunction(funcIn) + return function(...) + table.insert(QUQUED_CALLS_WRAPPED, { + nextevent = funcIn, + args = {...}, + argsNum = select('#', ...) + }) + end +end + +hook.Add('Think', 'DLib.util.QueuedFunction', function() + local data = table.remove(QUQUED_CALLS, 1) + if not data then return end + data.nextevent(unpack(data.args, 1, data.argsNum)) +end) + +hook.Add('Think', 'DLib.util.WrappedQueueFunction', function() + local data = table.remove(QUQUED_CALLS_WRAPPED, 1) + if not data then return end + data.nextevent(unpack(data.args, 1, data.argsNum)) +end) diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/server/chat.lua b/garrysmod/addons/util-dlib/lua/dlib/util/server/chat.lua new file mode 100644 index 0000000..892f542 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/server/chat.lua @@ -0,0 +1,118 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +net.pool('DLib.AddChatText') + +local chat = setmetatable(DLib.chat or {}, {__index = chat}) +DLib.chat = chat + +function chat.generate(name, targetTable) + local nw = 'DLib.AddChatText.' .. name + local nwL = 'DLib.AddChatTextL.' .. name + net.pool(nw) + net.pool(nwL) + + local newModule = targetTable or {} + + function newModule.chatPlayer(ply, ...) + net.Start(nw, true) + net.WriteArray({...}) + net.Send(ply) + end + + function newModule.lchatPlayer(ply, ...) + net.Start(nwL, true) + net.WriteArray({...}) + net.Send(ply) + end + + function newModule.chatPlayer2(ply, ...) + if IsValid(ply) and not ply:IsBot() then + net.Start(nw, true) + net.WriteArray({...}) + net.Send(ply) + elseif newModule.Message then + newModule.Message(ply, ' -> ', ...) + end + end + + function newModule.lchatPlayer2(ply, ...) + if IsValid(ply) and not ply:IsBot() then + net.Start(nwL, true) + net.WriteArray({...}) + net.Send(ply) + elseif newModule.Message then + newModule.Message(ply, ' -> ', ...) + end + end + + function newModule.chatAll(...) + net.Start(nw, true) + net.WriteArray({...}) + net.Broadcast() + end + + function newModule.lchatAll(...) + net.Start(nwL, true) + net.WriteArray({...}) + net.Broadcast() + end + + newModule.ChatPlayer = newModule.chatPlayer + newModule.ChatPlayer2 = newModule.chatPlayer2 + newModule.LChatPlayer = newModule.lchatPlayer + newModule.LChatPlayer2 = newModule.lchatPlayer2 + newModule.ChatAll = newModule.chatAll + newModule.LChatAll = newModule.lchatAll + + return newModule +end + +function chat.generateWithMessages(targetTable, name, ...) + targetTable = targetTable or {} + + DLib.CMessage(targetTable, name, ...) + chat.generate(name, targetTable, ...) + + targetTable.chatPlayerMessage = targetTable.chatPlayer + + function targetTable.chatPlayer(ply, ...) + if not IsValid(ply) then + targetTable.Message(...) + return + end + + return targetTable.chatPlayerMessage(ply, ...) + end + + return targetTable +end + +chat.registerWithMessages = chat.generateWithMessages +chat.RegisterWithMessages = chat.generateWithMessages +DLib.CMessageChat = chat.generateWithMessages +chat.RegisterChat = chat.generate + +chat.generate('default', chat) + +chat.player = chat.chatPlayer +chat.all = chat.chatAll + +return chat diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/util.lua b/garrysmod/addons/util-dlib/lua/dlib/util/util.lua new file mode 100644 index 0000000..b5eed9c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/util.lua @@ -0,0 +1,454 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +jit.on() + +local util = setmetatable(DLib.util or {}, {__index = util}) +DLib.util = util +local DLib = DLib +local vgui = vgui +local type = luatype +local ipairs = ipairs +local IsValid = IsValid +local LVector = LVector +local Vector = Vector +local Angle = Angle + +if CLIENT then + concommand.Add('dlib_gui_openurl', function(_, _, args) + return gui.OpenURL(table.concat(args, '%20')) + end) +end + +--[[ + @doc + @fname DLib.VCreate + @args string panelName, Panel parent + + @desc + !g:vgui.Create but does not create panel when parent is not present + @enddesc + + @returns + Panel: created panel +]] +function DLib.VCreate(pnlName, pnlParent) + if not IsValid(pnlParent) then + DLib.Message(debug.traceback('Attempt to create ' .. pnlName .. ' without valid parent!', 2)) + return + end + + return vgui.Create(pnlName, pnlParent) +end + +--[[ + @doc + @fname DLib.util.Clone + @args any variable + + @desc + creates better copy of variable. + can clone tables better than !g:table.Copy + **NOT RECURSION SAFE** + **will attempt to throw an error when encounters a recursion** + @enddesc + + @returns + any: clone +]] +function util.copy(var) + if type(var) == 'table' then + local output = {} + + for k, v in pairs(var) do + if k == var or v == var then + error('Confused by recursion') + end + + output[util.Copy(k)] = util.Copy(v) + end + + return output + end + + if type(var) == 'Angle' then return Angle(var.p, var.y, var.r) end + if type(var) == 'Vector' then return Vector(var) end + if type(var) == 'LVector' then return LVector(var) end + return var +end + +util.clone = util.copy +util.Clone = util.copy +util.Copy = util.copy + +--[[ + @doc + @fname DLib.util.VectorRand + @alias DLib.util.RandomVector + @args number x, number y, number z + + @desc + calls math.Rand(-component, component) for each component + @enddesc + + @returns + Vector +]] +function util.randomVector(mx, my, mz) + return Vector(math.Rand(-mx, mx), math.Rand(-my, my), math.Rand(-mz, mz)) +end + +util.RandomVector = util.randomVector +util.VectorRand = util.randomVector + +--[[ + @doc + @fname DLib.util.ComposeEnums + @args table input, vararg ...numbers + + @desc + :bor()'s numbers in `input` and :bor()'s numbers in vararg towards newly created value + @enddesc + + @returns + number +]] +function util.composeEnums(input, ...) + local num = 0 + + if type(input) == 'table' then + for k, v in ipairs(input) do + num = num:bor(v) + end + else + num = input + end + + return num:bor(...) +end + +util.ComposeEnums = util.composeEnums + +--[[ + @doc + @fname DLib.util.ValidateSteamID + @args string steamid + + @returns + boolean: whenever string provided is regular steamid (STEAM_0) +]] +function util.ValidateSteamID(input) + if not input then return false end + return input:match('STEAM_0:[0-1]:[0-9]+$') ~= nil +end + +--[[ + @doc + @fname DLib.util.SteamLink + @args string steamid + + @desc + accepts steamid and steamid64 + @enddesc + + @returns + string +]] +function util.SteamLink(steamid) + if util.ValidateSteamID(steamid) then + return 'https://steamcommunity.com/profiles/' .. util.SteamIDTo64(steamid) .. '/' + else + return 'https://steamcommunity.com/profiles/' .. steamid .. '/' + end +end + +--[[ + @doc + @fname DLib.util.CreateSharedConvar + @args string cvarname, string cvardef, string description + + @desc + quick way to workaround [this](https://github.com/Facepunch/garrysmod-issues/issues/3323) + adds `FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY` flags serverside and only `FCVAR_REPLICATED` clientside + @enddesc + + @returns + ConVar +]] +function util.CreateSharedConvar(cvarname, cvarvalue, description) + if CLIENT then + return CreateConVar(cvarname, cvarvalue, {FCVAR_REPLICATED}, description) + else + return CreateConVar(cvarname, cvarvalue, {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY}, description) + end +end + +-- Replace PrintTable with something better + +local rawtype = rawtype +local table = table +local error = error +local pcall = pcall +local debug = debug +local strict = false + +local RECURSION_COLOR = Color(195, 222, 69) +local TOO_DEEP_COLOR = Color(196, 158, 91) +local EQUALS_COLOR = Color(169, 117, 222) +local TABLE_TOKEN_COLOR = Color(197, 104, 111) +local COMMENTARY_COLOR = Color(143, 165, 46) + +DLib._OldPrintTable = DLib._OldPrintTable or PrintTable + +local wellknown, prints +local unmap = {} + +for i = 0, 16 do + unmap[string.char(i)] = '\\x' .. string.format('%.2X', i) +end + +local function getValueString(typeIn, valueIn) + if typeIn == 'string' then + return DLib.DEFAULT_TEXT_COLOR, '"' .. valueIn:gsub('"', '\\"'):gsub('\t', '\\t'):gsub('\n', '\\n'):gsub('.', function(str) return unmap[str] or str end) .. '"' + end + + return DLib.GetPrettyPrint(valueIn, typeIn) +end + +function util.PrintableString(valueIn) + return valueIn:gsub('"', '\\"'):gsub('\t', '\\t'):gsub('\n', '\\n'):gsub('.', function(str) return unmap[str] or str end), nil +end + +local comparableTypes = {} + +local function InternalPrintLoop(tableIn, level, recursionCheck) + if prints > 100000 then + error('I dont want to print more. Probably hit a recursion.') + end + + if strict then + if wellknown[tableIn] then + MsgC(RECURSION_COLOR, ' [well known/recursion] ') + return false + end + else + if wellknown[tableIn] and wellknown[tableIn] > 6 then + MsgC(RECURSION_COLOR, ' [well known] ') + return false + end + + if recursionCheck and recursionCheck[tableIn] then + MsgC(RECURSION_COLOR, ' [recursion] ') + return false + end + end + + if level > 10 then + MsgC(TOO_DEEP_COLOR, ' [too deep] ') + return false + end + + local keys = {} + + for k in pairs(tableIn) do + table.insert(keys, k) + end + + table.sort(keys, function(a, b) + local ta, tb = type(a), type(b) + local cmp = false + local token = ta .. '-' .. tb + + if comparableTypes[token] == nil then + comparableTypes[token] = pcall(function() + cmp = a < b + end) + elseif comparableTypes[token] then + cmp = a < b + end + + return cmp + end) + + wellknown[tableIn] = (wellknown[tableIn] or 0) + 1 + + local hitAnything = false + + for i, key in ipairs(keys) do + prints = prints + 1 + + if not hitAnything then + MsgC('\n') + hitAnything = true + end + + local ktp = type(key) + MsgC(string.rep(' ', level * 4), TABLE_TOKEN_COLOR, '[') + MsgC(getValueString(ktp, key)) + MsgC(TABLE_TOKEN_COLOR, ']', EQUALS_COLOR, ' = ') + local value = tableIn[key] + + if type(value) == 'table' then + MsgC(TABLE_TOKEN_COLOR, '{') + local useSpaces = InternalPrintLoop(value, level + 1, strict and '' or recursionCheck or {}) + + if useSpaces then + MsgC(TABLE_TOKEN_COLOR, string.rep(' ', level * 4), '},\n') + else + MsgC(TABLE_TOKEN_COLOR, '},\n') + end + else + local tp = type(value) + MsgC(getValueString(tp, value)) + MsgC(TABLE_TOKEN_COLOR, ',\n') + end + end + + if not hitAnything then + MsgC(COMMENTARY_COLOR, ' --[[ empty ]] ') + end + + return hitAnything +end + +--[[ + @doc + @fname PrintTable + @replaces + @args table value + + @desc + pretty prints a table. + attempts to avoid recursion. + @enddesc +]] +function _G.PrintTable(tableIn) + assert(rawtype(tableIn) == 'table', 'Input must be a table!') + wellknown = {} + prints = 0 + strict = false + MsgC(TABLE_TOKEN_COLOR, '{') + InternalPrintLoop(tableIn, 1) + MsgC(TABLE_TOKEN_COLOR, '}\n') +end + +--[[ + @doc + @fname PrintTableStrict + @args table value + + @desc + pretty prints a table. + strictly avoids any meaning of recursion. + @enddesc +]] +function _G.PrintTableStrict(tableIn) + assert(rawtype(tableIn) == 'table', 'Input must be a table!') + wellknown = {} + prints = 0 + strict = true + MsgC(TABLE_TOKEN_COLOR, '{') + InternalPrintLoop(tableIn, 1) + MsgC(TABLE_TOKEN_COLOR, '}\n') +end + +local files = file.Find('scripts/weapons/*.txt', 'GAME') +local wepdata = {} + +for i, mfile in ipairs(files) do + local parsed = util.KeyValuesToTable(file.Read('scripts/weapons/' .. mfile, 'GAME')) + + if parsed then + wepdata[mfile:sub(1, -5)] = parsed + end +end + +if CLIENT then + for wepclass, data in pairs(wepdata) do + if data.texturedata then + if data.texturedata.weapon and data.texturedata.weapon.file then + data.texturedata.weapon.material = Material(data.texturedata.weapon.file) + end + + if data.texturedata.weapon_s and data.texturedata.weapon_s.file then + data.texturedata.weapon_s.material = Material(data.texturedata.weapon_s.file) + end + + if data.texturedata.ammo and data.texturedata.ammo.file then + data.texturedata.ammo.material = Material(data.texturedata.ammo.file) + end + + if data.texturedata.ammo2 and data.texturedata.ammo2.file then + data.texturedata.ammo2.material = Material(data.texturedata.ammo2.file) + end + end + end +end + +--[[ + @doc + @fname DLib.util.GetWeaponScript + @args Weapon weaponIn + + @returns + table: or nil, if no script present +]] +function DLib.util.GetWeaponScript(weaponIn) + if isstring(weaponIn) then + return wepdata[weaponIn] + elseif type(weaponIn) == 'Weapon' then + return wepdata[weaponIn:GetClass()] + else + error('Invalid weapon provided: ' .. type(weaponIn)) + end +end + +--[[ + @doc + @fname printflags + @args number flags, number minimumDecimals = 0 + + @returns + string: formatted +]] +function _G.printflags(flagsIn, mnum) + assert(type(flagsIn) == 'number', 'flagsIn must be a number') + mnum = mnum or 0 + local format = '' + + if flagsIn < 0 then + flagsIn = flagsIn + 0xFFFFFFFF + end + + flagsIn = flagsIn:floor() + + while flagsIn > 0 do + local div = flagsIn % 2 + flagsIn = (flagsIn - div) / 2 + format = div .. format + end + + if #format < mnum then + format = string.rep('0', mnum - #format) .. format + end + + print(format) + return format +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/util/vector.lua b/garrysmod/addons/util-dlib/lua/dlib/util/vector.lua new file mode 100644 index 0000000..a03ebe0 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/util/vector.lua @@ -0,0 +1,454 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +DLib.vector = DLib.vector or {} +local vector = DLib.vector +local assert = assert +local error = error +local math = math +local ipairs = ipairs +local Vector = Vector +local LVector = LVector +local type = type +local setmetatable = setmetatable + +function vector.FindCenter2D(min, max) + return max - (max - min) * 0.5 +end + +--[[ + @doc + @fname DLib.vector.FindPlaneCentre + @args Vector mins, Vector maxs + + @returns + Vector: centre +]] +-- It differs from vector.Centre by checking whenever surface is the surface +function vector.FindPlaneCentre(mins, maxs) + if mins.y == maxs.y then + local x1 = mins.x + local x2 = maxs.x + local y1 = mins.z + local y2 = maxs.z + + return Vector((x2 - x1) * 0.5, mins.y, (y2 - y1) * 0.5) + elseif mins.x == maxs.x then + local x1 = mins.y + local x2 = maxs.y + local y1 = mins.z + local y2 = maxs.z + + return Vector(mins.x, (x2 - x1) * 0.5, (y2 - y1) * 0.5) + elseif mins.z == maxs.z then + local x1 = mins.x + local x2 = maxs.x + local y1 = mins.y + local y2 = maxs.y + + return Vector((x2 - x1) * 0.5, (y2 - y1) * 0.5, mins.z) + end + + error('One or both of arguments is not a 2D plane!') +end + +--[[ + @doc + @fname DLib.vector.LFindPlaneCentre + @args LVector mins, LVector maxs + + @returns + LVector: centre +]] +function vector.LFindPlaneCentre(mins, maxs) + if mins.y == maxs.y then + local x1 = mins.x + local x2 = maxs.x + local y1 = mins.z + local y2 = maxs.z + + return LVector((x2 - x1) * 0.5, mins.y, (y2 - y1) * 0.5) + elseif mins.x == maxs.x then + local x1 = mins.y + local x2 = maxs.y + local y1 = mins.z + local y2 = maxs.z + + return LVector(mins.x, (x2 - x1) * 0.5, (y2 - y1) * 0.5) + elseif mins.z == maxs.z then + local x1 = mins.x + local x2 = maxs.x + local y1 = mins.y + local y2 = maxs.y + + return LVector((x2 - x1) * 0.5, (y2 - y1) * 0.5, mins.z) + end + + error('One or both of arguments is not a 2D plane!') +end + +--[[ + vector.ExtractFaces: Table { + Face: + V1 + V2 + V3 + V4 + Normal + ... + } +]] + +--[[ + @doc + @fname DLib.vector.ExtractFaces + @args Vector mins, Vector maxs + + @returns + table: {{vertex1, vertex2, vertex3, vertex4, normal}, {vertex1, ...}} +]] + +--[[ + @doc + @fname DLib.vector.LExtractFaces + @args LVector mins, LVector maxs + + @returns + table: {{vertex1, vertex2, vertex3, vertex4, normal}, {vertex1, ...}} +]] +function vector.ExtractFaces(mins, maxs) + return { + { + Vector(mins.x, mins.y, mins.z), + Vector(mins.x, mins.y, maxs.z), + Vector(mins.x, maxs.y, maxs.z), + Vector(mins.x, maxs.y, mins.z), + Vector(-1, 0, 0) + }, + { + Vector(mins.x, mins.y, mins.z), + Vector(maxs.x, mins.y, mins.z), + Vector(maxs.x, mins.y, maxs.z), + Vector(mins.x, mins.y, maxs.z), + Vector(0, -1, 0) + }, + { + Vector(mins.x, maxs.y, mins.z), + Vector(maxs.x, maxs.y, mins.z), + Vector(maxs.x, maxs.y, maxs.z), + Vector(mins.x, maxs.y, maxs.z), + Vector(0, 1, 0) + }, + { + Vector(maxs.x, mins.y, mins.z), + Vector(maxs.x, mins.y, maxs.z), + Vector(maxs.x, maxs.y, maxs.z), + Vector(maxs.x, maxs.y, mins.z), + Vector(1, 0, 0) + }, + { + Vector(mins.x, mins.y, maxs.z), + Vector(maxs.x, mins.y, maxs.z), + Vector(maxs.x, maxs.y, maxs.z), + Vector(mins.x, maxs.y, maxs.z), + Vector(0, 0, 1) + }, + { + Vector(mins.x, mins.y, mins.z), + Vector(maxs.x, mins.y, mins.z), + Vector(maxs.x, maxs.y, mins.z), + Vector(mins.x, maxs.y, mins.z), + Vector(0, 0, -1) + }, + } +end + +function vector.LExtractFaces(mins, maxs) + return { + { + LVector(mins.x, mins.y, mins.z), + LVector(mins.x, mins.y, maxs.z), + LVector(mins.x, maxs.y, maxs.z), + LVector(mins.x, maxs.y, mins.z), + LVector(-1, 0, 0) + }, + { + LVector(mins.x, mins.y, mins.z), + LVector(maxs.x, mins.y, mins.z), + LVector(maxs.x, mins.y, maxs.z), + LVector(mins.x, mins.y, maxs.z), + LVector(0, -1, 0) + }, + { + LVector(mins.x, maxs.y, mins.z), + LVector(maxs.x, maxs.y, mins.z), + LVector(maxs.x, maxs.y, maxs.z), + LVector(mins.x, maxs.y, maxs.z), + LVector(0, 1, 0) + }, + { + LVector(maxs.x, mins.y, mins.z), + LVector(maxs.x, mins.y, maxs.z), + LVector(maxs.x, maxs.y, maxs.z), + LVector(maxs.x, maxs.y, mins.z), + LVector(1, 0, 0) + }, + { + LVector(mins.x, mins.y, maxs.z), + LVector(maxs.x, mins.y, maxs.z), + LVector(maxs.x, maxs.y, maxs.z), + LVector(mins.x, maxs.y, maxs.z), + LVector(0, 0, 1) + }, + { + LVector(mins.x, mins.y, mins.z), + LVector(maxs.x, mins.y, mins.z), + LVector(maxs.x, maxs.y, mins.z), + LVector(mins.x, maxs.y, mins.z), + LVector(0, 0, -1) + }, + } +end + +--[[ + vector.ExtractFacesAndCentre: Table { + Face: + V1 + V2 + V3 + V4 + Normal + Centre + ... + } +]] + +--[[ + @doc + @fname DLib.vector.ExtractFacesAndCentre + @args Vector mins, Vector maxs + + @returns + table: {{vertex1, vertex2, vertex3, vertex4, normal, centre}, {vertex1, ...}} +]] +function vector.ExtractFacesAndCentre(mins, maxs) + local find = vector.ExtractFaces(mins, maxs) + + for i, data in ipairs(find) do + data[6] = vector.Centre(data[1], data[3]) + end + + return find +end + +--[[ + @doc + @fname DLib.vector.FindQuadSize + @args Vector V1, Vector V2, Vector V3, Vector V4 + + @returns + number +]] +function vector.FindQuadSize(V1, V2, V3, V4) + if math.equal(V1.x, V2.x, V3.x, V4.x) then + local minX = math.min(V1.y, V2.y, V3.y, V4.y) + local maxX = math.max(V1.y, V2.y, V3.y, V4.y) + local minY = math.min(V1.z, V2.z, V3.z, V4.z) + local maxY = math.max(V1.z, V2.z, V3.z, V4.z) + + return maxX - minX, maxY - minY + elseif math.equal(V1.y, V2.y, V3.y, V4.y) then + local minX = math.min(V1.x, V2.x, V3.x, V4.x) + local maxX = math.max(V1.x, V2.x, V3.x, V4.x) + local minY = math.min(V1.z, V2.z, V3.z, V4.z) + local maxY = math.max(V1.z, V2.z, V3.z, V4.z) + + return maxX - minX, maxY - minY + elseif math.equal(V1.z, V2.z, V3.z, V4.z) then + local minX = math.min(V1.x, V2.x, V3.x, V4.x) + local maxX = math.max(V1.x, V2.x, V3.x, V4.x) + local minY = math.min(V1.y, V2.y, V3.y, V4.y) + local maxY = math.max(V1.y, V2.y, V3.y, V4.y) + + return maxX - minX, maxY - minY + end + + error('No proper flat surface detected!') +end + +--[[ + @doc + @fname DLib.vector.Centre + @args Vector mins, Vector maxs + + @returns + Vector +]] + +--[[ + @doc + @fname DLib.vector.LCentre + @args LVector mins, LVector maxs + + @returns + LVector +]] +function vector.Centre(mins, maxs) + local deltax = maxs.x - mins.x + local deltay = maxs.y - mins.y + local deltaz = maxs.z - mins.z + return Vector(mins.x + deltax * 0.5, mins.y + deltay * 0.5, mins.z + deltaz * 0.5) +end + +function vector.LCentre(mins, maxs) + local deltax = maxs.x - mins.x + local deltay = maxs.y - mins.y + local deltaz = maxs.z - mins.z + return LVector(mins.x + deltax * 0.5, mins.y + deltay * 0.5, mins.z + deltaz * 0.5) +end + +--[[ + @doc + @fname DLib.vector.CalculateSurfaceFromTwoPoints + @args Vector mins, Vector maxs, Vector zero + + @desc + Give two vectors (e.g. mins and maxs) and origin position + origin position is Vector(0, 0, 0) if mins and maxs are WORLDSPACE vectors + if mins and maxs are local vectors, origin is probably position of entity + which these vectors are belong to + This function will return a table that represent `Ax + By + Cz + D = 0` + @enddesc + + @returns + table: `{A, B, C, D}` +]] +function vector.CalculateSurfaceFromTwoPoints(mins, maxs, zero) + zero = zero or LVector(0, 0, 0) + + local x0, y0, z0 = zero.x, zero.y, zero.z + local x1, y1, z1 = mins.x, mins.y, mins.z + local x2, y2, z2 = maxs.x, maxs.y, maxs.z + + local a1 = -x0 + local a2 = -y0 + local a3 = -z0 + + local b1 = x1 + local b2 = y1 + local b3 = z1 + + local c1 = x2 + local c2 = y2 + local c3 = z2 + + local m1 = b2 * c3 + local m2 = b1 * c2 + local m3 = b3 * c1 + local m4 = b2 * c1 + local m5 = b3 * c2 + local m6 = b1 * c3 + + local X, Y, Z, D = 0, 0, 0, 0 + + -- m1 + X = X + m1 + D = D + a1 * m1 + + -- m2 + Z = Z + m2 + D = D + a3 * m2 + + -- m3 + Y = Y + m3 + D = D + a2 * m3 + + -- m4 + Z = Z - m4 + D = D - a3 * m4 + + -- m5 + X = X - m5 + D = D - a1 * m5 + + -- m6 + Y = Y - m6 + D = D - a2 * m6 + + return {X, Y, Z, D} +end + +--[[ + @doc + @fname DLib.vector.DistanceFromPointToSurface + @args Vector point, table surface + + @desc + surface is `{A, B, C, D}` + @enddesc + + @returns + number +]] +function vector.DistanceFromPointToSurface(point, surfaceTable) + local A, B, C, D = surfaceTable[1], surfaceTable[2], surfaceTable[3], surfaceTable[4] + local Mx, My, Mz = point.x, point.y, point.z + local toDivide = math.abs(A * Mx + B * My + C * Mz + D) + local square = math.sqrt(math.pow(A, 2) + math.pow(B, 2) + math.pow(C, 2)) + return toDivide / square +end + +--[[ + @doc + @fname DLib.vector.DistanceFromPointToPlane + @args Vector point, Vector mins, Vector maxs + + @desc + minimal possible distance from point to plane + @enddesc + + @returns + number +]] +function vector.DistanceFromPointToPlane(point, mins, maxs) + return vector.DistanceFromPointToSurface(point, vector.CalculateSurfaceFromTwoPoints(mins, maxs)) +end + +--[[ + @doc + @fname DLib.vector.IsPositionInsideBox + @args Vector point, Vector mins, Vector maxs + + @deprecated + + @desc + use `Vector:WithinAABox` and `LVector:WithinAABox` + @enddesc + + @returns + boolean +]] +function vector.IsPositionInsideBox(pos, mins, maxs) + return pos.x >= mins.x and pos.x <= maxs.x and + pos.y >= mins.y and pos.y <= maxs.y and + pos.z >= mins.z and pos.z <= maxs.z +end + +return vector diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/avatar.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/avatar.lua new file mode 100644 index 0000000..fa0ced9 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/avatar.lua @@ -0,0 +1,149 @@ + +-- +-- Copyright (C) 2016-2018 DBot + +-- 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. + + +--[[ + @doc + @panel DLib_Avatar + + @desc + !g:AvatarImage that doesn't suck + use this panel as described on !g:AvatarImage + @enddesc +]] +local PANEL = {} +DLib.VGUI.Avatar = PANEL + +function PANEL:AvatarHide() + self.havatar:SetVisible(false) + self.havatar:KillFocus() + self.havatar:SetMouseInputEnabled(false) + self.havatar:SetKeyboardInputEnabled(false) + self.havatar.hover = false + self:SetSkin('DLib_Black') +end + +function PANEL:OnMousePressed(key) + self.hover = true + self.havatar:SetVisible(true) + self.havatar:MakePopup() + self.havatar:SetMouseInputEnabled(false) + self.havatar:SetKeyboardInputEnabled(false) + + if IsValid(self.ply) and self.ply:IsBot() then return end + + if key == MOUSE_LEFT then + if IsValid(self.ply) then + gui.OpenURL('https://steamcommunity.com/profiles/' .. self.ply:SteamID64() .. '/') + elseif self.steamid64 and self.steamid64 ~= '0' then + gui.OpenURL('https://steamcommunity.com/profiles/' .. self.steamid64 .. '/') + end + end +end + +function PANEL:Init() + self:SetCursor('hand') + + local avatar = self:Add('AvatarImage') + self.avatar = avatar + avatar:Dock(FILL) + + local havatar = vgui.Create('AvatarImage') + self.havatar = havatar + havatar:SetVisible(false) + havatar:SetSize(184, 184) + + hook.Add('OnSpawnMenuClose', self, self.AvatarHide) + + self:SetMouseInputEnabled(true) + avatar:SetMouseInputEnabled(false) + avatar:SetKeyboardInputEnabled(false) + havatar:SetMouseInputEnabled(false) + havatar:SetKeyboardInputEnabled(false) +end + +function PANEL:Think() + if not IsValid(self.ply) and not self.steamid then return end + local x, y = gui.MousePos() + + local hover = self:IsHovered() + + local w, h = ScrWL(), ScrHL() + + if x + 204 >= w then + x = x - 214 + end + + if y + 204 >= h then + y = y - 214 + end + + if hover then + if not self.hover then + self.hover = true + self.havatar:SetVisible(true) + self.havatar:MakePopup() + self.havatar:SetMouseInputEnabled(false) + self.havatar:SetKeyboardInputEnabled(false) + end + + self.havatar:SetPos(x + 20, y + 10) + else + if self.hover then + self.havatar:SetVisible(false) + self.havatar:KillFocus() + self.hover = false + end + end +end + +function PANEL:SetPlayer(ply, size) + self.ply = ply + + self.avatar:SetPlayer(ply, size) + self.havatar:SetPlayer(ply, 184) +end + +function PANEL:SetSteamID(steamid, size) + local steamid64 + + if DLib.util.ValidateSteamID(steamid) then + steamid64 = util.SteamIDTo64(steamid) + else + steamid64 = steamid + end + + self.steamid = steamid + self.steamid64 = steamid64 + + self.avatar:SetSteamID(steamid64, size) + self.havatar:SetSteamID(steamid64, 184) +end + +function PANEL:OnRemove() + if IsValid(self.havatar) then + self.havatar:Remove() + end +end + +vgui.Register('DLib_Avatar', PANEL, 'EditablePanel') + +return PANEL diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/button_layout.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/button_layout.lua new file mode 100644 index 0000000..5d0e4bd --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/button_layout.lua @@ -0,0 +1,237 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +--[[ + @doc + @panel DLib_ButtonLayout + @parent DScrollPanel + + @desc + A grid of panels which must expose `:Populate(parent)` method + Can be useful with !p:DLib_PlayerButton + @enddesc +]] +local PANEL = {} +DLib.VGUI.ButtonLayout = PANEL + +local function AccessorFunc(PANEL, varName, getset) + PANEL['Get' .. getset] = function(self) + return self[varName] + end + + PANEL['Set' .. getset] = function(self, newVal) + local old = self[varName] + self[varName] = newVal + self['On' .. getset .. 'Changes'](self, old, newVal) + end +end + +--[[ + @docpreprocess + const names = [ + 'SpacingX', + 'SpacingY', + 'LayoutSizeX', + 'LayoutSizeY' + ] + + const reply = [] + + for (const name of names) { + let output = [] + + output.push(`@fname DLib_ButtonLayout:Get${name}`) + output.push(`@returns`) + output.push(`number`) + + reply.push(output) + + output = [] + + output.push(`@fname DLib_ButtonLayout:Set${name}`) + output.push(`@args number value`) + + reply.push(output) + + output = [] + + output.push(`@fname DLib_ButtonLayout:On${name}Changes`) + output.push(`@args number oldvalue, number newvalue`) + + reply.push(output) + } + + return reply +]] +AccessorFunc(PANEL, 'spacingX', 'SpacingX') +AccessorFunc(PANEL, 'spacingY', 'SpacingY') +AccessorFunc(PANEL, 'layoutSizeX', 'LayoutSizeX') +AccessorFunc(PANEL, 'layoutSizeY', 'LayoutSizeY') + +function PANEL:Init() + self.layoutSizeX = 128 + self.layoutSizeY = 96 + self.buttons = {} + self.buttonsPositions = {} + self.spacingX = 4 + self.spacingY = 4 + self:SetSkin('DLib_Black') +end + +function PANEL:SetLayoutSize(x, y) + self.layoutSizeX, self.layoutSizeY = x, y + self:InvalidateLayout() +end + +function PANEL:OnSpacingXChanges() + self:InvalidateLayout() +end + +function PANEL:OnSpacingYChanges() + self:InvalidateLayout() +end + +function PANEL:OnLayoutSizeXChanges() + self:InvalidateLayout() +end + +function PANEL:OnLayoutSizeYChanges() + self:InvalidateLayout() +end + +--[[ + @doc + @fname DLib_ButtonLayout:AddButton + @args Panel button +]] +function PANEL:AddButton(button) + table.insert(self.buttons, button) + button:SetParent(self:GetCanvas()) + button:SetSize(self.layoutSizeX, self.layoutSizeY) + button.__dlibIsPopulated = false + self:InvalidateLayout() +end + +--[[ + @doc + @fname DLib_ButtonLayout:GetVisibleArea + @returns + number: top y + number: bottom y +]] +function PANEL:GetVisibleArea() + local scroll = self:GetVBar():GetScroll() + return scroll - self.layoutSizeY, scroll + self:GetTall() +end + +--[[ + @doc + @fname DLib_ButtonLayout:GetVisibleButtons + @returns + table: array of Panels +]] +function PANEL:GetVisibleButtons() + local topx, bottomx = self:GetVisibleArea() + local reply = {} + + for i, button in ipairs(self.buttonsPositions) do + local x, y = button.x, button.y + + if y > topx and y < bottomx then + table.insert(reply, button.button) + end + end + + return reply +end + +--[[ + @doc + @fname DLib_ButtonLayout:Clear + + @desc + removes all buttons + @enddesc +]] +function PANEL:Clear() + for i, button in ipairs(self.buttons) do + button:Remove() + end + + self.buttons = {} + self.__lastW, self.__lastH = nil, nil + self:InvalidateLayout() +end + +--[[ + @doc + @fname DLib_ButtonLayout:RebuildButtonsPositions + @args number width, number height + @internal +]] +function PANEL:RebuildButtonsPositions(w, h) + if not w or not h then return end + self.buttonsPositions = {} + local xm, ym = self.layoutSizeX + self.spacingX, self.layoutSizeY + self.spacingY + + local line = 0 + local row = 0 + local limitX = math.max(math.floor(w / xm) - 1, 1) + + for i, button in ipairs(self.buttons) do + if row > limitX then + row = 0 + line = line + 1 + end + + local newx, newy = row * xm, line * ym + button:SetPos(newx, newy) + + self.buttonsPositions[i] = { + button = button, + x = newx, + y = newy + } + + row = row + 1 + end + + self:GetCanvas():SetSize(w, (line + 1) * ym) + + return self.buttonsPositions +end + +function PANEL:PerformLayout(width, height) + if width == self.__lastW and height == self.__lastH then return end + self.__lastW, self.__lastH = width, height + DScrollPanel.PerformLayout(self, width, height) + self:RebuildButtonsPositions(self:GetSize()) + + for i, button in ipairs(self:GetVisibleButtons()) do + if not button.__dlibIsPopulated then + button:Populate(self) + button.__dlibIsPopulated = true + end + end +end + +vgui.Register('DLib_ButtonLayout', PANEL, 'DScrollPanel') + +return PANEL diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/colormixer.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/colormixer.lua new file mode 100644 index 0000000..38ec1cd --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/colormixer.lua @@ -0,0 +1,1092 @@ + +-- Copyright (C) 2020 DBotThePony + +-- 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 ENABLE_GMOD_ALPHA_WANG = CreateConVar('cl_dlib_colormixer_oldalpha', '0', {FCVAR_ARCHIVE}, 'Enable gmod styled alpha bar in color mixers') +local ENABLE_GMOD_HUE_WANG = CreateConVar('cl_dlib_colormixer_oldhue', '0', {FCVAR_ARCHIVE}, 'Enable gmod styled hue bar in color mixers') +local ENABLE_WANG_BARS = CreateConVar('cl_dlib_colormixer_wangbars', '1', {FCVAR_ARCHIVE}, 'Enable color wang bars') + +cvars.AddChangeCallback('cl_dlib_colormixer_oldalpha', function(cvar, old, new) + hook.Run('DLib_ColorMixerAlphaUpdate', tobool(new)) +end, 'DLib') + +cvars.AddChangeCallback('cl_dlib_colormixer_wangbars', function(cvar, old, new) + hook.Run('DLib_ColorMixerWangBarsUpdate', tobool(new)) +end, 'DLib') + +cvars.AddChangeCallback('cl_dlib_colormixer_oldhue', function(cvar, old, new) + hook.Run('DLib_ColorMixerWangHueUpdate', tobool(new)) +end, 'DLib') + +local gradient_r = Material('vgui/gradient-r') +local alpha_grid = Material('gui/alpha_grid.png', 'nocull') +local hue_picker = Material('gui/colors.png') + +local PANEL = {} + +AccessorFunc(PANEL, 'wang_position', 'WangPosition') + +function PANEL:Init() + self.wang_position = 0.5 + self:SetSize(200, 20) +end + +function PANEL:OnCursorMoved(x, y) + if not input.IsMouseDown(MOUSE_LEFT) then return end + local wang_position = math.clamp(x / self:GetWide(), 0, 1) + + if wang_position ~= self.wang_position then + self:ValueChanged(self.wang_position, wang_position) + self.wang_position = wang_position + end +end + +function PANEL:OnMousePressed(mcode) + if mcode == MOUSE_LEFT then + self:MouseCapture(true) + self:OnCursorMoved(self:CursorPos()) + end +end + +function PANEL:OnMouseReleased(mcode) + if mcode == MOUSE_LEFT then + self:MouseCapture(false) + self:OnCursorMoved(self:CursorPos()) + end +end + +function PANEL:ValueChanged(old, new) + +end + +function PANEL:PaintWangControls(w, h) + draw.NoTexture() + surface.SetDrawColor(0, 0, 0, 255) + + local wpos = math.round(self.wang_position * w) + + surface.DrawPoly({ + {x = wpos - 4, y = 0}, + {x = wpos + 4, y = 0}, + {x = wpos, y = 4}, + }) + + surface.SetDrawColor(255, 255, 255, 255) + + surface.DrawPoly({ + {x = wpos - 4, y = h}, + {x = wpos, y = h - 4}, + {x = wpos + 4, y = h}, + }) + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawLine(0, 0, w, 0) + surface.DrawLine(0, 0, 0, h) + surface.DrawLine(w - 1, 0, w - 1, h) + surface.DrawLine(0, h - 1, w, h - 1) +end + +vgui.Register('DLibColorMixer_WangBase', PANEL, 'EditablePanel') + +local PANEL = {} + +AccessorFunc(PANEL, 'left_color', 'LeftColor') +AccessorFunc(PANEL, 'right_color', 'RightColor') + +function PANEL:Init() + self.left_color = Color(0, 0, 0) + self.right_color = Color() +end + +function PANEL:Paint(w, h) + surface.SetMaterial(gradient_r) + + surface.SetDrawColor(self.right_color) + surface.DrawTexturedRect(0, 0, w, h) + + surface.SetDrawColor(self.left_color) + surface.DrawTexturedRectUV(0, 0, w, h, 1, 1, 0, 0) + + self:PaintWangControls(w, h) +end + +vgui.Register('DLibColorMixer_RGBWang', PANEL, 'DLibColorMixer_WangBase') + +local PANEL = {} + +function PANEL:Paint(w, h) + surface.SetMaterial(hue_picker) + + surface.SetDrawColor(255, 255, 255) + surface.DrawTexturedRectRotated(w / 2, h / 2, h, w, -90) + + self:PaintWangControls(w, h) +end + +vgui.Register('DLibColorMixer_HueWang', PANEL, 'DLibColorMixer_WangBase') + +local PANEL = {} + +AccessorFunc(PANEL, 'base_color', 'BaseColor') + +function PANEL:Init() + self.base_color = Color() +end + +local ALPHA_GRID_SIZE = 128 + +function PANEL:Paint(w, h) + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(alpha_grid) + + for i = 0, math.ceil(w / ALPHA_GRID_SIZE) do + surface.DrawTexturedRect(i * ALPHA_GRID_SIZE, 0, ALPHA_GRID_SIZE, ALPHA_GRID_SIZE) + end + + surface.SetMaterial(gradient_r) + + surface.SetDrawColor(self.base_color) + surface.DrawTexturedRect(0, 0, w, h) + + self:PaintWangControls(w, h) +end + +vgui.Register('DLibColorMixer_AlphaWang', PANEL, 'DLibColorMixer_WangBase') + +local PANEL = {} + +local rgba = { + 'red', 'green', 'blue', 'alpha' +} + +local hsv = { + 'hue', 'saturation', 'value' +} + +local wang_panels = table.qcopy(rgba) +table.append(wang_panels, hsv) + +function PANEL:Init() + self.wang_canvas = vgui.Create('EditablePanel', self) + self.wang_canvas:Dock(RIGHT) + -- self.wang_canvas:SetWide(200) + + self.rgb_canvas = vgui.Create('EditablePanel', self.wang_canvas) + self.rgb_canvas:Dock(TOP) + self.rgb_canvas:SetZPos(0) + self.rgb_canvas:DockPadding(5, 0, 5, 0) + + self.wang_label_rgb = vgui.Create('DLabel', self.rgb_canvas) + self.wang_label_rgb:SetText('RGB') + self.wang_label_rgb:Dock(LEFT) + -- self.wang_label_rgb:DockMargin(0, 0, 0, 5) + self.wang_label_rgb:SizeToContents() + + self.palette_button = vgui.Create('DButton', self.rgb_canvas) + self.palette_button:Dock(RIGHT) + self.palette_button:SetText('...') + self.palette_button:SizeToContents() + self.palette_button:SetWide(self.palette_button:GetWide() + 6) + self.palette_button:SetZPos(-1) + + function self.palette_button.DoClick() + self:OpenPaletteWindow() + end + + local add = 1 + + for i, panelname in ipairs(wang_panels) do + if panelname == 'hue' then + self.wang_label_hsv = vgui.Create('DLabel', self.wang_canvas) + self.wang_label_hsv:SetText(' HSV') + self.wang_label_hsv:Dock(TOP) + self.wang_label_hsv:DockMargin(0, 5, 0, 5) + self.wang_label_hsv:SetZPos(i + add) + add = add + 1 + end + + self['wang_canvas_' .. panelname] = vgui.Create('EditablePanel', self.wang_canvas) + self['wang_canvas_' .. panelname]:Dock(TOP) + self['wang_canvas_' .. panelname]:DockMargin(1, 1, 1, 1) + self['wang_canvas_' .. panelname]:SetZPos(i + add) + end + + for i, panelname in ipairs(rgba) do + self['wang_' .. panelname] = vgui.Create('DNumberWang', self['wang_canvas_' .. panelname]) + self['wang_' .. panelname]:Dock(RIGHT) + self['wang_' .. panelname]:SetDecimals(0) + self['wang_' .. panelname]:SetMinMax(0, 255) + self['wang_' .. panelname]:SetWide(40) + + self['wang_' .. panelname .. '_bar'] = vgui.Create(panelname == 'alpha' and 'DLibColorMixer_AlphaWang' or 'DLibColorMixer_RGBWang', self['wang_canvas_' .. panelname]) + self['wang_' .. panelname .. '_bar']:Dock(FILL) + self['wang_' .. panelname .. '_bar']:DockMargin(2, 2, 2, 2) + end + + for i, panelname in ipairs(hsv) do + self['wang_' .. panelname] = vgui.Create('DNumberWang', self['wang_canvas_' .. panelname]) + self['wang_' .. panelname]:Dock(RIGHT) + self['wang_' .. panelname]:SetDecimals(0) + self['wang_' .. panelname]:SetMinMax(0, 100) + self['wang_' .. panelname]:SetWide(40) + end + + self.wang_hue_bar = vgui.Create('DLibColorMixer_HueWang', self.wang_canvas_hue) + self.wang_hue_bar:Dock(FILL) + self.wang_hue_bar:DockMargin(2, 2, 2, 2) + + function self.wang_hue_bar.ValueChanged(wang_hue_bar, oldvalue, newvalue) + self.update = true + self.wang_hue:SetValue(math.round(newvalue * 360)) + self.update = false + + self:UpdateFromHSVWangs('hue', 'bar') + end + + self.wang_saturation_bar = vgui.Create('DLibColorMixer_RGBWang', self.wang_canvas_saturation) + self.wang_saturation_bar:Dock(FILL) + self.wang_saturation_bar:DockMargin(2, 2, 2, 2) + + function self.wang_saturation_bar.ValueChanged(wang_saturation_bar, oldvalue, newvalue) + self.update = true + self.wang_saturation:SetValue(math.round(newvalue * 100)) + self.update = false + + self:UpdateFromHSVWangs('saturation', 'bar') + end + + self.wang_value_bar = vgui.Create('DLibColorMixer_RGBWang', self.wang_canvas_value) + self.wang_value_bar:Dock(FILL) + self.wang_value_bar:DockMargin(2, 2, 2, 2) + + function self.wang_value_bar.ValueChanged(wang_value_bar, oldvalue, newvalue) + self.update = true + self.wang_value:SetValue(math.round(newvalue * 100)) + self.update = false + + self:UpdateFromHSVWangs('value', 'bar') + end + + self.hex_canvas = vgui.Create('EditablePanel', self.wang_canvas) + self.hex_canvas:Dock(TOP) + self.hex_canvas:SetZPos(50) + + self.hex_label = vgui.Create('DLabel', self.hex_canvas) + self.hex_label:Dock(LEFT) + self.hex_label:SetText('HEX:') + self.hex_label:DockMargin(5, 0, 0, 0) + self.hex_label:SetZPos(1) + + self.hex_input = vgui.Create('DTextEntry', self.hex_canvas) + self.hex_input:Dock(FILL) + self.hex_input:SetText('fff') + self.hex_input:SetUpdateOnType(true) + self.hex_input:SetZPos(2) + + function self.hex_input.OnValueChange(hex_input, newvalue) + if self.update then return end + self:ParseHexInput(newvalue, true) + end + + self.wang_hue:SetMinMax(0, 360) + + self:BindRegularWang(self.wang_red, '_r') + self:BindRegularWang(self.wang_green, '_g') + self:BindRegularWang(self.wang_blue, '_b') + self:BindRegularWang(self.wang_alpha, '_a') + + self:BindRegularWangBar(self.wang_red_bar, '_r') + self:BindRegularWangBar(self.wang_green_bar, '_g') + self:BindRegularWangBar(self.wang_blue_bar, '_b') + self:BindRegularWangBar(self.wang_alpha_bar, '_a') + + self:BindHSVWang(self.wang_hue, 'hue') + self:BindHSVWang(self.wang_saturation, 'saturation') + self:BindHSVWang(self.wang_value, 'value') + + self.wang_value_bar:SetLeftColor(Color(0, 0, 0)) + self.wang_saturation_bar:SetLeftColor(Color(0, 0, 0)) + + self.color_cube = vgui.Create('DColorCube', self) + self.color_cube:Dock(FILL) + + function self.color_cube.OnUserChanged(color_cube, newvalue) + newvalue:SetAlpha(self._a) + self:_SetColor(newvalue) + self:UpdateData('color_cube') + end + + self.color_wang = vgui.Create('DRGBPicker', self) + self.color_wang:Dock(RIGHT) + self.color_wang:SetWide(26) + + self.alpha_wang = vgui.Create('DAlphaBar', self) + self.alpha_wang:Dock(RIGHT) + self.alpha_wang:SetWide(26) + + function self.color_wang.OnChange(color_wang, newvalue) + if self.update then return end + + local h, s, v = ColorToHSV(self:GetColor()) + local h2, s2, v2 = ColorToHSV(newvalue) + self:_SetColor(HSVToColor(h2, s, v):SetAlpha(self._a)) + self:UpdateData('hue_wang_old') + end + + function self.alpha_wang.OnChange(color_wang, newvalue) + self._a = math.round(newvalue * 255) + self:UpdateData('alpha_wang_old') + end + + self._r = 255 + self._g = 255 + self._b = 255 + self._a = 255 + self.update = false + + self.allow_alpha = true + self.tall_layout = false + + hook.Add('DLib_ColorMixerAlphaUpdate', self, self.DLib_ColorMixerAlphaUpdate) + hook.Add('DLib_ColorMixerWangBarsUpdate', self, self.DLib_ColorMixerWangBarsUpdate) + hook.Add('DLib_ColorMixerWangHueUpdate', self, self.DLib_ColorMixerWangHueUpdate) + + if not ENABLE_GMOD_ALPHA_WANG:GetBool() then + self.alpha_wang:SetVisible(false) + end + + if not ENABLE_WANG_BARS:GetBool() then + self.wang_red_bar:SetVisible(false) + self.wang_green_bar:SetVisible(false) + self.wang_blue_bar:SetVisible(false) + self.wang_alpha_bar:SetVisible(false) + + self.wang_hue_bar:SetVisible(false) + self.wang_saturation_bar:SetVisible(false) + self.wang_value_bar:SetVisible(false) + end + + if not ENABLE_GMOD_HUE_WANG:GetBool() then + self.color_wang:SetVisible(false) + end + + self:UpdateData() + self:SetTall(260) +end + +do + local function paletteStuff(self, palette) + function palette.DoClick(_, color_chosen, button) + local copy = Color(color_chosen) + if not self:GetAllowAlpha() then copy:SetAlpha(255) end + self:_SetColor(copy) + self:UpdateData() + end + + function palette.OnRightClickButton(_, button) + local menu = DermaMenu() + + menu:AddOption('gui.dlib.colormixer.save_color', function() + palette:SaveColor(button, self:GetColor()) + end) + + menu:AddOption('gui.dlib.colormixer.copy_color', function() + local color = button:GetColor() + + if self:GetAllowAlpha() then + SetClipboardText(string.format('%d, %d, %d, %d', color.r, color.g, color.b, color.a)) + else + SetClipboardText(string.format('%d, %d, %d', color.r, color.g, color.b)) + end + end) + + menu:AddOption('gui.dlib.colormixer.copy_hex_color', function() + local color = button:GetColor() + + if self:GetAllowAlpha() then + SetClipboardText(string.format('%.2x%.2x%.2x%.2x', color.r, color.g, color.b, color.a)) + else + SetClipboardText(string.format('%.2x%.2x%.2x', color.r, color.g, color.b)) + end + end) + + local submenu, button_child = menu:AddSubMenu('gui.dlib.colormixer.reset_palette') + + submenu:AddOption('gui.dlib.colormixer.reset_palette_confirm', function() + palette:ResetSavedColors() + end) + + menu:Open() + end + end + + function PANEL:OpenPaletteWindow() + local posX, posY = self:LocalToScreen(self:GetWide(), 0) + posX = posX + 14 + + if IsValid(self.palette_frame) then + self.palette_frame:SetPos(posX, posY) + self.palette_frame:MakePopup() + + return + end + + local original_color = self:GetColor() + + local frame = vgui.Create('DLib_Window') + frame:SetSize(420, 450) + frame:SetTitle('gui.dlib.colormixer.palette_window') + frame:SetPos(posX, posY) + frame:MakePopup() + + self.palette_frame = frame + + local label = vgui.Create('DLabel', frame) + label:Dock(TOP) + label:SetText('gui.dlib.colormixer.palette_regular') + label:DockMargin(10, 0, 0, 0) + + self.palette = vgui.Create('DColorPalette', frame) + self.palette:Dock(TOP) + self.palette:SetButtonSize(16) + self.palette:SetCookieName('dlib_palette_def') + self.palette:Reset() + self.palette:DockMargin(5, 5, 5, 5) + + label = vgui.Create('DLabel', frame) + label:Dock(TOP) + label:SetText('gui.dlib.colormixer.palette_extended') + label:DockMargin(10, 0, 0, 0) + + self.palette_extended = vgui.Create('DColorPalette', frame) + self.palette_extended:Dock(FILL) + self.palette_extended:SetButtonSize(16) + self.palette_extended:SetNumRows(50) + self.palette_extended:SetCookieName('dlib_palette_ext') + self.palette_extended:Reset() + self.palette_extended:DockMargin(5, 5, 5, 5) + + paletteStuff(self, self.palette) + paletteStuff(self, self.palette_extended) + + local color_button = vgui.Create('DButton', frame) + color_button:Dock(BOTTOM) + color_button:SetText('coolors.co') + color_button:DockMargin(5, 5, 5, 5) + color_button:SetZPos(1) + + function color_button.DoClick() + gui.OpenURL('https://coolors.co/palettes/trending') + end + + color_button = vgui.Create('DButton', frame) + color_button:Dock(BOTTOM) + color_button:SetText('mycolor.space') + color_button:DockMargin(5, 5, 5, 5) + color_button:SetZPos(0) + + function color_button.DoClick() + gui.OpenURL('https://mycolor.space/') + end + end +end + +function PANEL:SetPalette() + -- NO OP +end + +function PANEL:GetPalette() + return true +end + +function PANEL:ParseHexInput(input, fromForm) + if input[1] == '#' then + input = input:sub(2) + end + + if input:startsWith('0x') then + input = input:sub(3) + end + + local r, g, b, a + + if #input == 3 then + r, g, b = input[1]:tonumber(16), input[2]:tonumber(16), input[3]:tonumber(16) + elseif #input == 4 then + r, g, b, a = input[1]:tonumber(16), input[2]:tonumber(16), input[3]:tonumber(16), input[4]:tonumber(16) + elseif #input == 6 then + r, g, b = input:sub(1, 2):tonumber(16), input:sub(3, 4):tonumber(16), input:sub(5, 6):tonumber(16) + elseif #input == 8 then + r, g, b, a = input:sub(1, 2):tonumber(16), input:sub(3, 4):tonumber(16), input:sub(5, 6):tonumber(16), input:sub(7, 8):tonumber(16) + end + + if not r or not g or not b then return end + + if #input < 6 then + r, g, b = r * 0x10, g * 0x10, b * 0x10 + + if a then + a = a * 0x10 + end + end + + if not self.allow_alpha then a = 255 end + + self:_SetColor(Color(r, g, b, a)) + self:UpdateData('hex') +end + +function PANEL:DisableTallLayout() + self.color_cube:Dock(FILL) + self.color_cube:DockMargin(0, 0, 0, 0) + + self.wang_canvas:Dock(RIGHT) + + for i, panelname in ipairs(rgba) do + self['wang_canvas_' .. panelname]:SetParent(self.wang_canvas) + self['wang_' .. panelname]:Dock(RIGHT) + end + + for i, panelname in ipairs(hsv) do + self['wang_canvas_' .. panelname]:SetParent(self.wang_canvas) + end + + self.hex_canvas:SetParent(self.wang_canvas) + + if IsValid(self.wang_canvas_left) then + self.wang_canvas_left:Remove() + end + + if IsValid(self.wang_canvas_right) then + self.wang_canvas_right:Remove() + end + + self.palette_button:SetParent(self.rgb_canvas) + self.palette_button:Dock(RIGHT) + + self:SetTall(260) + self:InvalidateLayout() +end + +function PANEL:EnableTallLayout() + self.color_cube:Dock(TOP) + self.color_cube:SetTall(200) + self.color_cube:DockMargin(4, 4, 4, 4) + + self.wang_canvas:Dock(FILL) + + self.wang_canvas_left = vgui.Create('EditablePanel', self) + self.wang_canvas_left:Dock(LEFT) + + self.wang_canvas_right = vgui.Create('EditablePanel', self) + self.wang_canvas_right:Dock(RIGHT) + + for i, panelname in ipairs(rgba) do + self['wang_canvas_' .. panelname]:SetParent(self.wang_canvas_left) + self['wang_' .. panelname]:Dock(LEFT) + end + + for i, panelname in ipairs(hsv) do + self['wang_canvas_' .. panelname]:SetParent(self.wang_canvas_right) + end + + self.hex_canvas:SetParent(self.wang_canvas_right) + + self.palette_button:SetParent(self.hex_canvas) + self.palette_button:Dock(LEFT) + + self:SetTall(320) + self:InvalidateLayout() +end + +function PANEL:SetTallLayout(shoulddo) + if self.tall_layout == shoulddo then return end + + self.tall_layout = shoulddo + + if shoulddo then + self:EnableTallLayout() + else + self:DisableTallLayout() + end +end + +function PANEL:PerformLayout() + if self.tall_layout and IsValid(self.wang_canvas_left) then + local wide = self:GetWide() + self.wang_canvas_left:SetWide(wide / 2) + self.wang_canvas_right:SetWide(wide / 2) + else + self.wang_canvas:SetWide(ENABLE_WANG_BARS:GetBool() and math.clamp(self:GetWide() * 0.4, 80, 170) or 60) + end +end + +function PANEL:DLib_ColorMixerAlphaUpdate(newvalue) + if newvalue and self.allow_alpha then + if IsValid(self.alpha_wang) then + self.alpha_wang:SetVisible(true) + self:InvalidateLayout() + end + else + if IsValid(self.alpha_wang) then + self.alpha_wang:SetVisible(false) + self:InvalidateLayout() + end + end +end + +function PANEL:DLib_ColorMixerWangHueUpdate(newvalue) + self.color_wang:SetVisible(newvalue) + self:InvalidateLayout() +end + +function PANEL:DLib_ColorMixerWangBarsUpdate(newvalue) + self.wang_red_bar:SetVisible(newvalue) + self.wang_green_bar:SetVisible(newvalue) + self.wang_blue_bar:SetVisible(newvalue) + + self.wang_hue_bar:SetVisible(newvalue) + self.wang_saturation_bar:SetVisible(newvalue) + self.wang_value_bar:SetVisible(newvalue) + + if newvalue and self.allow_alpha then + self.wang_alpha_bar:SetVisible(true) + else + self.wang_alpha_bar:SetVisible(false) + end + + self:InvalidateLayout() +end + +function PANEL:BindRegularWang(wang, index) + function wang.OnValueChanged(wang, newvalue) + if self.update then return end + + self[index] = newvalue + self:UpdateData(index, 'wang') + + self:ValueChanged(self:GetColor()) + self:UpdateConVars() + end +end + +function PANEL:BindRegularWangBar(wang, index) + function wang.ValueChanged(wang, oldvalue, newvalue) + if self.update then return end + + self[index] = newvalue * 255 + self:UpdateData(index, 'bar') + + self:ValueChanged(self:GetColor()) + self:UpdateConVars() + end +end + +function PANEL:BindHSVWang(wang, wang_type) + function wang.OnValueChanged(wang, newvalue) + if self.update then return end + self:UpdateFromHSVWangs(wang_type, 'wang') + end +end + +function PANEL:UpdateHexInput() + self.update = true + + if self.allow_alpha then + self.hex_input:SetValue(string.format('%.2x%.2x%.2x%.2x', self._r, self._g, self._b, self._a)) + else + self.hex_input:SetValue(string.format('%.2x%.2x%.2x', self._r, self._g, self._b)) + end + + self.update = false +end + +function PANEL:UpdateData(update_type, update_subtype, ...) + self:UpdateWangs(update_type, update_subtype, ...) + self:UpdateWangBars(update_type, update_subtype, ...) + self:UpdateHSVWangs(update_type, update_subtype, ...) + self:UpdateHSVWangBars(update_type, update_subtype, ...) + + if update_type ~= 'color_cube' then + self:UpdateColorCube() + end + + if update_type ~= 'alpha_wang_old' then + self:UpdateAlphaBar() + end + + if update_type ~= 'hue_wang_old' then + self:UpdateHueBar() + end + + if update_type ~= 'hex' then + self:UpdateHexInput() + end +end + +function PANEL:UpdateColorCube() + self.update = true + self.color_cube:SetColor(self:GetColor()) + self.update = false +end + +function PANEL:UpdateHueBar() + self.update = true + + local w, h = self.color_wang:GetSize() + local hue = ColorToHSV(self:GetColor()) + + self.color_wang.LastX = w / 2 + self.color_wang.LastY = h - hue / 360 * h + + self.update = false +end + +function PANEL:UpdateAlphaBar() + if not IsValid(self.alpha_wang) then return end + + self.update = true + + self.alpha_wang:SetBarColor(self:GetColor():SetAlpha(255)) + local w, h = self.color_wang:GetSize() + + self.alpha_wang:SetValue(self._a / 255) + + self.update = false +end + +function PANEL:UpdateWangBars(update_type, update_subtype) + self.update = true + + if update_type ~= '_r' or update_subtype ~= 'bar' then + self.wang_red_bar:SetWangPosition(self._r / 255) + self.wang_red_bar:SetLeftColor(Color(0, self._g, self._b)) + self.wang_red_bar:SetRightColor(Color(255, self._g, self._b)) + end + + if update_type ~= '_g' or update_subtype ~= 'bar' then + self.wang_green_bar:SetWangPosition(self._g / 255) + self.wang_green_bar:SetLeftColor(Color(self._r, 0, self._b)) + self.wang_green_bar:SetRightColor(Color(self._r, 255, self._b)) + end + + if update_type ~= '_b' or update_subtype ~= 'bar' then + self.wang_blue_bar:SetWangPosition(self._b / 255) + self.wang_blue_bar:SetLeftColor(Color(self._r, self._g, 0)) + self.wang_blue_bar:SetRightColor(Color(self._r, self._g, 255)) + end + + if (update_type ~= '_a' or update_subtype ~= 'bar') and self.allow_alpha then + self.wang_alpha_bar:SetWangPosition(self._a / 255) + self.wang_alpha_bar:SetBaseColor(self:GetColor():SetAlpha(255)) + end + + self.update = false +end + +function PANEL:UpdateWangs(update_type, update_subtype) + self.update = true + + if update_type ~= '_r' or update_subtype ~= 'wang' then + self.wang_red:SetValue(self._r) + end + + if update_type ~= '_g' or update_subtype ~= 'wang' then + self.wang_green:SetValue(self._g) + end + + if update_type ~= '_b' or update_subtype ~= 'wang' then + self.wang_blue:SetValue(self._b) + end + + if update_type ~= '_a' or update_subtype ~= 'wang' then + self.wang_alpha:SetValue(self._a) + end + + self.update = false +end + +function PANEL:UpdateHSVWangs(update_type, update_subtype) + self.update = true + local hue, saturation, value = ColorToHSV(self:GetColor()) + + if update_type ~= 'hue' or update_subtype ~= 'wang' then + self.wang_hue:SetValue(hue) + end + + if update_type ~= 'saturation' or update_subtype ~= 'wang' then + self.wang_saturation:SetValue(math.round(saturation * 100)) + end + + if update_type ~= 'value' or update_subtype ~= 'wang' then + self.wang_value:SetValue(math.round(value * 100)) + end + + self.update = false +end + +function PANEL:UpdateHSVWangBars(update_type, update_subtype) + self.update = true + + local scol = self:GetColor() + local hue, saturation, value = ColorToHSV(scol) + + if update_type ~= 'hue' or update_subtype ~= 'bar' then + self.wang_hue_bar:SetWangPosition(hue / 360) + end + + if update_type ~= 'saturation' or update_subtype ~= 'bar' then + self.wang_saturation_bar:SetWangPosition(saturation) + -- self.wang_saturation_bar:SetLeftColor(HSVToColorLua(hue, 0, value)) + self.wang_saturation_bar:SetRightColor(HSVToColorLua(hue, 1, value)) + end + + if update_type ~= 'value' or update_subtype ~= 'bar' then + self.wang_value_bar:SetWangPosition(value) + -- self.wang_value_bar:SetLeftColor(HSVToColorLua(hue, saturation, 0)) + self.wang_value_bar:SetRightColor(HSVToColorLua(hue, saturation, 1)) + end + + self.update = false +end + +function PANEL:UpdateFromHSVWangs(...) + local col = HSVToColorLua(self.wang_hue:GetValue(), self.wang_saturation:GetValue() / 100, self.wang_value:GetValue() / 100) + col:SetAlpha(self._a) + self:_SetColor(col) + self:UpdateData(...) +end + +function PANEL:_SetColor(r, g, b, a) + if IsColor(r) then + r, g, b, a = r.r, r.g, r.b, r.a + end + + self._r = r + self._g = g + self._b = b + self._a = a + + self:ValueChanged(self:GetColor()) + self:UpdateConVars() +end + +function PANEL:SetColor(r, g, b, a) + if IsColor(r) then + r, g, b, a = r.r, r.g, r.b, r.a + end + + self._r = r + self._g = g + self._b = b + self._a = a + + self:UpdateData() +end + +function PANEL:ValueChanged(newvalue) + +end + +function PANEL:GetColor() + return Color(self._r, self._g, self._b, self._a) +end + +function PANEL:GetVector() + return Vector(self._r / 255, self._g / 255, self._b / 255) +end + +function PANEL:Think() + self:CheckConVars() +end + +function PANEL:GetAllowAlpha() + return self.allow_alpha +end + +PANEL.GetAlphaBar = PANEL.GetAllowAlpha + +function PANEL:SetAllowAlpha(allow) + assert(isbool(allow), 'allow should be a boolean') + + if self.allow_alpha == allow then return end + self.allow_alpha = allow + + if allow then + self:CheckConVar(self.con_var_alpha, '_a') + + if IsValid(self.alpha_wang) then + self.alpha_wang:SetVisible(true) + end + + self.wang_canvas_alpha:SetVisible(true) + else + self._a = 255 + + if IsValid(self.alpha_wang) then + self.alpha_wang:SetVisible(false) + end + + self.wang_canvas_alpha:SetVisible(false) + end + + self:UpdateHexInput() + self:InvalidateLayout() +end + +PANEL.SetAlphaBar = PANEL.SetAllowAlpha + +AccessorFunc(PANEL, 'con_var_red', 'ConVarR') +AccessorFunc(PANEL, 'con_var_green', 'ConVarG') +AccessorFunc(PANEL, 'con_var_blue', 'ConVarB') +AccessorFunc(PANEL, 'con_var_alpha', 'ConVarA') +-- AccessorFunc(PANEL, 'con_var_combined', 'ConVarCombined') + +function PANEL:SetConVarR(con_var) + if not con_var then + self.con_var_red = nil + return + end + + self.con_var_red = type(con_var) == 'ConVar' and con_var or assert(ConVar(con_var), 'no such ConVar: ' .. con_var) + self:CheckConVar(self.con_var_red, '_r') +end + +function PANEL:SetConVarG(con_var) + if not con_var then + self.con_var_green = nil + return + end + + self.con_var_green = type(con_var) == 'ConVar' and con_var or assert(ConVar(con_var), 'no such ConVar: ' .. con_var) + self:CheckConVar(self.con_var_green, '_g') +end + +function PANEL:SetConVarB(con_var) + if not con_var then + self.con_var_blue = nil + return + end + + self.con_var_blue = type(con_var) == 'ConVar' and con_var or assert(ConVar(con_var), 'no such ConVar: ' .. con_var) + self:CheckConVar(self.con_var_blue, '_b') +end + +function PANEL:SetConVarA(con_var) + if not con_var then + self.con_var_alpha = nil + return + end + + self.con_var_alpha = type(con_var) == 'ConVar' and con_var or assert(ConVar(con_var), 'no such ConVar: ' .. con_var) + + if self.allow_alpha then + self:CheckConVar(self.con_var_alpha, '_a') + end +end + +--[[function PANEL:SetConVarCombined(con_var) + if not con_var then + self.con_var_combined = nil + return + end + + self.con_var_combined = type(con_var) == 'ConVar' and con_var or assert(ConVar(con_var), 'no such ConVar: ' .. con_var) +end]] + +function PANEL:SetConVarAll(prefix) + self.con_var_red = prefix .. '_r' + self.con_var_green = prefix .. '_g' + self.con_var_blue = prefix .. '_b' + self.con_var_alpha = prefix .. '_a' + + self:CheckConVars(true) +end + +PANEL.next_con_var_check = 0 + +function PANEL:CheckConVars(force) + if not force and input.IsMouseDown(MOUSE_LEFT) then return end + if not force and self.next_con_var_check > RealTime() then return end + + local change = self:CheckConVar(self.con_var_red, '_r', false) or + self:CheckConVar(self.con_var_green, '_g', false) or + self:CheckConVar(self.con_var_blue, '_b', false) or + self.allow_alpha and self:CheckConVar(self.con_var_alpha, '_a', false) + + if change then + self:UpdateData() + end +end + +function PANEL:UpdateConVars() + self.next_con_var_check = RealTime() + 0.3 + + if self.con_var_red then + local value = self.con_var_red:GetInt(255) + + if value ~= self._r then + RunConsoleCommand(self.con_var_red:GetName(), self._r:tostring()) + end + end + + if self.con_var_green then + local value = self.con_var_green:GetInt(255) + + if value ~= self._r then + RunConsoleCommand(self.con_var_green:GetName(), self._g:tostring()) + end + end + + if self.con_var_blue then + local value = self.con_var_blue:GetInt(255) + + if value ~= self._r then + RunConsoleCommand(self.con_var_blue:GetName(), self._b:tostring()) + end + end + + if self.allow_alpha and self.con_var_alpha then + local value = self.con_var_alpha:GetInt(255) + + if value ~= self._a then + RunConsoleCommand(self.con_var_alpha:GetName(), self._a:tostring()) + end + end +end + +function PANEL:CheckConVar(con_var, index, update_now) + if not con_var then return false end + + local value = con_var:GetInt(255) + + if value ~= self[index] then + self[index] = value + + if update_now or update_now == nil then + self:UpdateData() + end + + return true + end + + return false +end + +vgui.Register('DLibColorMixer', PANEL, 'EditablePanel') diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/dtextentry.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/dtextentry.lua new file mode 100644 index 0000000..ebff32e --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/dtextentry.lua @@ -0,0 +1,578 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 vgui = vgui +local hook = hook +local IsValid = IsValid +local DLib = DLib +local RealTimeL = RealTimeL +local color_white = color_white +local Color = Color +local color_dlib = color_dlib +local tonumber = tonumber +local surface = surface +local draw = draw + +--[[ + @doc + @panel DLib_TextEntry + @parent DTextEntry + + @desc + DTextEntry with `:OnEnter(value)` callback (which means it will be called after user press Enter) + also contains autocomplete qol fixes + @enddesc +]] +local PANEL = {} +DLib.VGUI.TextEntry = PANEL + +surface.CreateFont('DLib_TextEntry', { + font = 'Roboto', + size = 16, + weight = 500, + extended = true +}) + +function PANEL:Init() + self:SetText('') + self:SetKeyboardInputEnabled(true) + self:SetMouseInputEnabled(true) + self:SetFont('DLib_TextEntry') +end + +function PANEL:OnEnter(value) + +end + +function PANEL:OnGetFocus(...) + if DTextEntry.OnGetFocus then + DTextEntry.OnGetFocus(self, ...) + end + + local autocomplete = self:GetAutoComplete(self:GetText()) + + if autocomplete then + self:OpenAutoComplete(autocomplete) + end +end + +function PANEL:OnMousePressed(mcode) + if DTextEntry.OnMousePressed then + DTextEntry.OnMousePressed(self, mcode) + end + + if mcode == MOUSE_LEFT then + local autocomplete = self:GetAutoComplete(self:GetText()) + + if autocomplete then + self:OpenAutoComplete(autocomplete) + end + end +end + +function PANEL:Think() + if not IsValid(self.Menu) then + self:ConVarStringThink() + end +end + +function PANEL:OpenAutoComplete(tab) + if not tab or #tab == 0 then return end + + if IsValid(self.Menu) then + self.Menu:Remove() + end + + self.Menu = DermaMenu() + + for k, line in ipairs(tab) do + self.Menu:AddOption(line, function() + self:SetText(line) + self:SetCaretPos(#line) + self:UpdateConvarValue() + self:RequestFocus() + self:OnValueChange(line) + end) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, true, self) + self.Menu:SetPos(x, y) + self.Menu:SetMaxHeight(ScrHL() - y - 10) +end + +function PANEL:OnKeyCodeTyped(key) + if key == KEY_FIRST or key == KEY_NONE or key == KEY_TAB then + return true + elseif key == KEY_ENTER then + self:OnEnter((self:GetValue() or ''):Trim()) + self:KillFocus() + + if IsValid(self.Menu) then + self.Menu:Remove() + end + + return true + end + + if DTextEntry.OnKeyCodeTyped then + return DTextEntry.OnKeyCodeTyped(self, key) + end + + return false +end + +function PANEL:GetValueBeforeCaret() + local value = self:GetValue() or '' + return value:sub(1, self:GetCaretPos()) +end + +function PANEL:GetValueAfterCaret() + local value = self:GetValue() or '' + return value:sub(self:GetCaretPos() + 1) +end + +vgui.Register('DLib_TextEntry', PANEL, 'DTextEntry') +local TEXTENTRY = PANEL + +--[[ + @doc + @panel DLib_TextEntry_Configurable + @parent DTextEntry + + @desc + base panel for creating configuration based text entries + @enddesc +]] +PANEL = {} +DLib.VGUI.TextEntry_Configurable = PANEL + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetLengthLimit + + @returns + number +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetLengthLimit + @args number value +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetTooltipTime + + @returns + number +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetTooltipTime + @args number value +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetTooltipShown + + @returns + boolean +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetTooltipShown + @internal + @args boolean value +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetIsWhitelistMode + + @returns + boolean +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetIsWhitelistMode + @args boolean value +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetDisallowedHashSet + + @returns + DLib.HashSet: or nil +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetDisallowedHashSet + @internal + @args table hashset +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetAllowedHashSet + + @returns + DLib.HashSet: or nil +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetAllowedHashSet + @internal + @args table hashset +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:GetDefaultReason + + @returns + string +]] + +--[[ + @doc + @fname DLib_TextEntry_Configurable:SetDefaultReason + @internal + @args string value +]] +AccessorFunc(PANEL, 'lengthLimit', 'LengthLimit') +AccessorFunc(PANEL, 'tooltipTime', 'TooltipTime') +AccessorFunc(PANEL, 'tooltip', 'TooltipShown') +AccessorFunc(PANEL, 'whitelistMode', 'IsWhitelistMode') +AccessorFunc(PANEL, 'disallowed', 'DisallowedHashSet') +AccessorFunc(PANEL, 'allowed', 'AllowedHashSet') +AccessorFunc(PANEL, 'defaultReason', 'DefaultReason') + +function PANEL:Init() + self.allowed = DLib.HashSet() + self.disallowed = DLib.HashSet() + self.whitelistMode = false + self.tooltipTime = 0 + self.tooltip = false + self.lengthLimit = 0 + self.tooltipReason = 'gui.entry.invalidsymbol' + self.defaultReason = 'gui.entry.invalidsymbol' + + hook.Add('PostRenderVGUI', self, self.PostRenderVGUI) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:PredictValueChange + @args string keycode + + @internal + @returns + string +]] +function PANEL:PredictValueChange(key) + local value1 = self:GetValueBeforeCaret() + local value2 = self:GetValueAfterCaret() + local char = DLib.KeyMap.KEY[key] + + if char then + return value1 .. char .. value2 + elseif key == KEY_BACKSPACE then + return value1:sub(1, #value1 - 1) .. value2 + elseif key == KEY_DELETE then + return value1 .. value2:sub(1, #value2 - 1) + end + + return self:GetValue() +end + +local function isTechincal(key) + return key == KEY_BACKSPACE or + key == KEY_DELETE or + key == KEY_UP or + key == KEY_DOWN or + key == KEY_LEFT or + key == KEY_RIGHT +end + +local function isControl(key) + return key == KEY_UP or + key == KEY_DOWN or + key == KEY_LEFT or + key == KEY_RIGHT +end + +function PANEL:OnKeyCodeTyped(key) + local reply = TEXTENTRY.OnKeyCodeTyped(self, key) + if reply == true then return reply end + + if not isTechincal then + if self.whitelistMode then + if not self.allowed:has(key) then + self:Ding() + --return true + end + else + if self.disallowed:has(key) then + self:Ding() + --return true + end + end + end + + if self.lengthLimit > 0 and #(self:GetValue() or '') + 1 > self.lengthLimit then + self:Ding('gui.entry.limit') + --return true + end + + return false +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:AddToBlacklist + @args string keycode +]] +function PANEL:AddToBlacklist(value) + return self.disallowed:add(value) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:AddToWhitelist + @args string keycode +]] +function PANEL:AddToWhitelist(value) + return self.allowed:add(value) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:RemoveFromBlacklist + @args string keycode +]] +function PANEL:RemoveFromBlacklist(value) + return self.disallowed:remove(value) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:RemoveFromWhitelist + @args string keycode +]] +function PANEL:RemoveFromWhitelist(value) + return self.allowed:remove(value) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:InBlacklist + @args string keycode +]] +function PANEL:InBlacklist(value) + return self.disallowed:has(value) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:InWhitelist + @args string keycode +]] +function PANEL:InWhitelist(value) + return self.allowed:add(value) +end + +--[[ + @doc + @fname DLib_TextEntry_Configurable:Ding + @args string reason +]] +function PANEL:Ding(reason) + reason = reason or self.defaultReason + self.tooltipReason = DLib.i18n.localize(reason) + + if self.tooltipTime - 1.95 > RealTimeL() then + self.tooltipTime = RealTimeL() + 1 + self.tooltip = true + return + end + + self.tooltipTime = RealTimeL() + 2 + --surface.PlaySound('resource/warning.wav') + self.tooltip = true +end + +surface.CreateFont('DLib_TextEntry_Warning', { + font = 'Open Sans', + size = 20, + weight = 500 +}) + +--[[ + @doc + @fname DLib_TextEntry_Configurable:PostRenderVGUI + @internal +]] +function PANEL:PostRenderVGUI() + if not IsValid(self) then return end + if not self.tooltip then return end + local time = RealTimeL() + + if self.tooltipTime < time then + self.tooltip = false + return + end + + local x, y = self:LocalToScreen(0, 0) + local w, h = self:GetSize() + + y = y + h + 2 + local fade = math.min(1, (self.tooltipTime - time) * 1.25 + 0.4) + + local value = self:GetValueBeforeCaret() + surface.SetFont(self:GetFont()) + local w = surface.GetTextSize(value) + + surface.SetDrawColor(0, 0, 0, fade * 255) + draw.NoTexture() + DLib.HUDCommons.DrawTriangle(x + 3 + w, y, 15, 20) + DLib.HUDCommons.WordBox(self.tooltipReason, 'DLib_TextEntry_Warning', x + w, y + 20, Color(255, 255, 255, fade * 255)) +end + +vgui.Register('DLib_TextEntry_Configurable', PANEL, 'DLib_TextEntry') + +--[[ + @doc + @panel DLib_TextEntry_Number + @parent DLib_TextEntry_Configurable + + @desc + !p:DLib_TextEntry_Configurable which accept only numbers + @enddesc +]] +local TEXTENTRY_CUSTOM = PANEL +PANEL = {} +DLib.VGUI.TextEntry_Number = PANEL + +--[[ + @doc + @fname DLib_TextEntry_Number:GetDefaultNumber + + @returns + number +]] + +--[[ + @doc + @fname DLib_TextEntry_Number:SetDefaultNumber + @args number value +]] + +--[[ + @doc + @fname DLib_TextEntry_Number:GetIsFloatAllowed + + @returns + boolean +]] + +--[[ + @doc + @fname DLib_TextEntry_Number:SetIsFloatAllowed + @args boolean value +]] + +--[[ + @doc + @fname DLib_TextEntry_Number:GetIsNegativeValueAllowed + + @returns + boolean +]] + +--[[ + @doc + @fname DLib_TextEntry_Number:SetIsNegativeValueAllowed + @args boolean value +]] +AccessorFunc(PANEL, 'defaultNumber', 'DefaultNumber') +AccessorFunc(PANEL, 'allowFloats', 'IsFloatAllowed') +AccessorFunc(PANEL, 'allowNegative', 'IsNegativeValueAllowed') + +function PANEL:Init() + self:SetIsWhitelistMode(true) + self:SetDefaultReason('Only numbers are allowed.') + self.defaultNumber = 0 + self.allowFloats = true + self.allowNegative = true + + for i, number in ipairs(DLib.KeyMap.NUMBERS_LIST) do + self:AddToWhitelist(number) + end +end + +--[[ + @doc + @fname DLib_TextEntry_Number:GetNumber + + @returns + number +]] +function PANEL:GetNumber() + return tonumber(self:GetValue() or '') or self.defaultNumber +end + +function PANEL:OnKeyCodeTyped(key) + local reply = TEXTENTRY_CUSTOM.OnKeyCodeTyped(self, key) + if reply then return true end + + if not self.allowNegative and (key == KEY_MINUS or key == KEY_PAD_MINUS) then + self:Ding('Negative values are not allowed here') + --return true + end + + if not self.allowFloats and (key == KEY_PAD_DECIMAL) then + self:Ding('Floating point values are not allowed here') + --return true + end + + if not isControl(key) then + local newValue = self:PredictValueChange(key) + + if not tonumber(newValue) then + self:Ding('Doing this here will mangle the current value') + --return true + end + end + + return false +end + +vgui.Register('DLib_TextEntry_Number', PANEL, 'DLib_TextEntry_Configurable') diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/dtextinput.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/dtextinput.lua new file mode 100644 index 0000000..49e73cc --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/dtextinput.lua @@ -0,0 +1,229 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 vgui = vgui +local IsValid = IsValid + +local numberEntry = { + 'GetNumber' +} + +do + local getset = { + 'DefaultNumber', + 'IsFloatAllowed', + 'IsNegativeValueAllowed', + 'LengthLimit', + 'TooltipTime', + 'TooltipShown', + 'IsWhitelistMode', + 'DisallowedHashSet', + 'AllowedHashSet', + 'DefaultReason', + } + + for i, get in ipairs(getset) do + table.insert(numberEntry, 'Get' .. get) + table.insert(numberEntry, 'Set' .. get) + end +end + +--[[ + @doc + @panel DLib_NumberInput + + @desc + !p:DLib_TextEntry_Number wrapped in with apply button and `:OnEnter(value)` callback + this expose almost all methods of DLib_NumberInput + however, it does not inherit it! + @enddesc +]] + +--[[ + @doc + @panel DLib_NumberInputLabeled + + @desc + !p:DLib_NumberInput with label which can be accessed using `.label` property + and text can be set using `:SetLabel(string)` + @enddesc +]] + +--[[ + @doc + @panel DLib_NumberInputLabeledBare + + @desc + !p:DLib_NumberInput with label which can be accessed using `.label` property + @enddesc +]] + +--[[ + @doc + @panel DLib_TextInput + + @desc + !p:DLib_TextEntry wrapped in with apply button and `:OnEnter(value)` callback + @enddesc +]] + +--[[ + @doc + @panel DLib_TextInputLabeled + + @desc + !p:DLib_TextInput with label which can be accessed using `.label` property + and text can be set using `:SetLabel(string)` + @enddesc +]] + +--[[ + @doc + @panel DLib_TextInputLabeledBare + + @desc + !p:DLib_TextInput with label which can be accessed using `.label` property + @enddesc +]] +local panels = { + {'DLib_TextEntry_Number', 'Number', numberEntry}, + {'DLib_TextEntry', 'Text'}, +} + +for i, pnlData in ipairs(panels) do + local NAME = pnlData[2] .. 'Input' + local PANEL_ID = pnlData[1] + + local PANEL = {} + DLib.VGUI[NAME] = PANEL + + local function Init(self) + self:SetSize(140, 20) + + self.textInput = vgui.Create(PANEL_ID, self) + self.apply = vgui.Create('DButton', self) + self.apply:SetText('Apply') + self.apply:Dock(RIGHT) + self.apply:DockMargin(4, 0, 4, 0) + self.textInput:Dock(FILL) + self.apply:SizeToContents() + self.apply:SetWide(self.apply:GetWide() + 6) + + function self.apply.DoClick() + return self:OnEnter(self:GetValue()) + end + + function self.textInput.OnEnter(_, ...) + return self:OnEnter(...) + end + end + + PANEL.Init = Init + + function PANEL:RemoveApply() + if IsValid(self.apply) then + self.apply:Remove() + end + end + + function PANEL:GetValue(...) + return self.textInput:GetText(...) + end + + function PANEL:GetText(...) + return self.textInput:GetText(...) + end + + function PANEL:SetValue(...) + return self.textInput:SetText(...) + end + + function PANEL:SetText(...) + return self.textInput:SetText(...) + end + + function PANEL:SetTitle(...) + return self.apply:SetText(...) + end + + function PANEL:GetTitle(...) + return self.apply:GetText(...) + end + + function PANEL:GetInput() + return self.textInput + end + + function PANEL:GetButton() + return self.apply + end + + function PANEL:OnEnter(value) + + end + + if pnlData[3] then + for i2, func in ipairs(pnlData[3]) do + PANEL[func] = function(self, ...) + return self.textInput[func](self.textInput, ...) + end + end + end + + vgui.Register('DLib_' .. NAME, PANEL, 'EditablePanel') + + PANEL = table.Copy(PANEL) + + function PANEL:Init() + self.label = vgui.Create('DLabel', self) + self.label:Dock(TOP) + self.label:SetText('Field Input') + + Init(self) + + self:SetSize(140, 40) + end + + function PANEL:SetTitle(...) + return self.label:SetText(...) + end + + function PANEL:SetLabel(...) + return self.label:SetText(...) + end + + vgui.Register('DLib_' .. NAME .. 'Labeled', PANEL, 'EditablePanel') + + PANEL = table.Copy(PANEL) + + function PANEL:Init() + self.label = vgui.Create('DLabel', self) + self.label:Dock(TOP) + self.label:SetText('Field Input') + + Init(self) + + self:SetSize(140, 40) + self.apply:Remove() + end + + vgui.Register('DLib_' .. NAME .. 'LabeledBare', PANEL, 'EditablePanel') +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/extendedmenu.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/extendedmenu.lua new file mode 100644 index 0000000..489bdec --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/extendedmenu.lua @@ -0,0 +1,89 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 PANEL = {} +DLib.VGUI.DMenu = PANEL + +--[[ + @doc + @panel DLib_Menu + + @desc + a basic extended menu. Has `AddCopyOption`, `AddURLOption`, + `AddSteamID` and `AddSteamID64` methods (quick copy methods) + @enddesc +]] +function PANEL:Init() + self:SetSkin('DLib_Black') +end + +--[[ + @doc + @func DLib_Menu:AddCopyOption + @args string name, string value +]] +function PANEL:AddCopyOption(name, value) + local new = self:AddOption(name, function() + SetClipboardText(value) + end) + + new:SetIcon(DLib.skin.icon.copy()) + + return new +end + +--[[ + @doc + @func DLib_Menu:AddURLOption + @args string name, string url +]] +function PANEL:AddURLOption(name, value) + local new = self:AddOption(name, function() + gui.OpenURL(value) + end) + + new:SetIcon(DLib.skin.icon.url()) + + return new +end + +--[[ + @doc + @func DLib_Menu:AddSteamID + @args string name, string steamid +]] +function PANEL:AddSteamID(name, value) + return self:AddSteamID64(name, util.SteamIDTo64(value)) +end + +--[[ + @doc + @func DLib_Menu:AddSteamID64 + @args string name, string steamid64 +]] +function PANEL:AddSteamID64(name, value) + local new = self:AddURLOption(name, 'https://steamcommunity.com/profiles/' .. value) + new:SetIcon(DLib.skin.icon.user()) + return new +end + +vgui.Register('DLib_Menu', PANEL, 'DMenu') +return PANEL diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/player_button.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/player_button.lua new file mode 100644 index 0000000..cc43030 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/player_button.lua @@ -0,0 +1,134 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 PANEL = {} +DLib.VGUI.PlayerButton = PANEL + +AccessorFunc(PANEL, 'm_DisplayGreen', 'GreenIfOnline') + +--[[ + @doc + @panel DLib_PlayerButton + @parent DButton + + @desc + Creates a new panel which display user's `DLib_Avatar` + and some controls for end user. + + behaves like regular button. + to set which player to display, use `:SetSteamID(steamid)` + @enddesc +]] +function PANEL:Init() + self.isAddingNew = false + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + self:SetText('') + self.label = vgui.Create('DLabel', self) + self.label:SetText('usernaem:tm:\nstaimid:tm:') + self.label:SetPos(0, 64) + self.label:SetContentAlignment(CONTENT_ALIGMENT_MIDDLECENTER) + self.nickname = 'unknown' + self:SetSize(128, 96) + self:SetSkin('DLib_Black') + self.m_DisplayGreen = false +end + +function PANEL:DoClick() + +end + +function PANEL:DoRightClick() + local menu = vgui.Create('DLib_Menu') + menu:AddCopyOption('Copy SteamID', self.steamid) + menu:AddCopyOption('Copy Nickname', self.nickname) + menu:AddURLOption('Open Steam profile', DLib.util.SteamLink(self.steamid)) + menu:Open() +end + +local surface = surface +local derma = derma + +function PANEL:Paint(w, h) + derma.SkinHook('Paint', 'Button', self, w, h) + + if self.ply and self.m_DisplayGreen then + surface.SetDrawColor(20, 180, 24, 100) + surface.DrawRect(0, 0, w, h) + end +end + +function PANEL:PerformLayout(w, h) + if not w or not h then return end + self.label:SizeToContents() + local W, H = self.label:GetSize() + self.label:SetSize(w, H) + + if IsValid(self.avatar) then + self.avatar:SetPos(w / 2 - self.avatar:GetWide() / 2) + end +end + +--[[ + @doc + @fname DLib_PlayerButton:SetSteamID + @args string steamid + + @desc + regular steamid, not steamid64 + @enddesc +]] +function PANEL:SetSteamID(steamid) + self.steamid = steamid + self.ply = player.GetBySteamID(steamid) + + if IsValid(self.avatar) then + self.avatar:SetSteamID(steamid, 64) + end + + self.label:SetText(self.nickname .. '\n' .. steamid) +end + +--[[ + @doc + @fname DLib_PlayerButton:Populate + + @desc + Actually populates the panel. + **You must call this function after you did everything** + This function is present due to speed of avatar panel. + This can be useful when you got scrolling panel and created a lot + of `DLib_PlayerButton`s + @enddesc +]] +function PANEL:Populate() + self.avatar = vgui.Create('DLib_Avatar', self) + local avatar = self.avatar + avatar:SetSize(48, 48) + avatar:SetPos(self:GetWide() / 2 - 24, 0) + avatar:SetSteamID(self.steamid, 64) + self.nickname = DLib.LastNickFormatted(self.steamid) + self.label:SetText(self.nickname .. '\n' .. self.steamid) +end + +vgui.Register('DLib_PlayerButton', PANEL, 'DButton') + +return PANEL diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin.lua new file mode 100644 index 0000000..72e65a1 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin.lua @@ -0,0 +1,137 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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. + + +_G.SKIN = {} +DLib.skin = SKIN + +local DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine + +local nomat = surface.GetTextureID('gui/corner8') + +DLib.skin.ENABLE_BLUR = CreateConVar('dlib_vguiblur', '1', {FCVAR_ARCHIVE}, 'Enable VGUI elements background blur') + +function DLib.skin.Simple_DrawBox(x, y, w, h, color) + if color then + surface_SetDrawColor(color) + end + + surface_SetTexture(nomat) + surface_DrawRect(x, y, w, h) +end + +function DLib.skin.Simple_DrawText(text, font, x, y, col, center) + if font then + surface_SetFont(font) + end + + if center then + x = x - surface_GetTextSize(text) / 2 + end + + if col then + surface_SetTextColor(col.r, col.g, col.b, col.a) + end + + surface_SetTextPos(x, y) + surface_DrawText(text) +end + +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +surface.CreateFont('DLib.SkinRoboto', { + font = 'Roboto', + size = 18, + weight = 500, + extended = true, +}) + +surface.CreateFont('DLib.SkinPT', { + font = 'PT Sans', + size = 20, + weight = 500, + extended = true, +}) + +DLib.skin.PrintName = 'DLib FlatBlack skin utilizing Lua draw functions' +DLib.skin.Author = 'DBot' +DLib.skin.DermaVersion = 1 +DLib.skin.GwenTexture = Material('gwenskin/GModDefault.png') +DLib.skin.fontFrame = 'DLib.SkinPT' +DLib.skin.texGradientUp = Material('gui/gradient_up') +DLib.skin.texGradientDown = Material('gui/gradient_down') +DLib.skin.fontTab = 'DLib.SkinPT' +DLib.skin.fontCategoryHeader = 'TabLarge' + +DLib.skin.tex = {} +DLib.skin.tex.Panels = {} +DLib.skin.tex.Window = {} +DLib.skin.tex.Scroller = {} +DLib.skin.tex.Menu = {} +DLib.skin.tex.Input = {} +DLib.skin.tex.Input.ComboBox = {} +DLib.skin.tex.Input.ComboBox.Button = {} +DLib.skin.tex.Input.UpDown = {} +DLib.skin.tex.Input.UpDown.Up = {} +DLib.skin.tex.Input.UpDown.Down = {} +DLib.skin.tex.Input.Slider = {} +DLib.skin.tex.Input.Slider.H = {} +DLib.skin.tex.Input.Slider.V = {} +DLib.skin.tex.Input.ListBox = {} +DLib.skin.tex.ProgressBar = {} + +function DLib.skin.tex.Panels.Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.colours.bg_bright) +end + +function DLib.skin.tex.Panels.Bright(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.bg_verybright) +end + +function DLib.skin.tex.Panels.Dark(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.background) +end + +function DLib.skin.tex.Panels.Highlight(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.bg_hightlight) +end + +function DLib.skin.tex.Selection(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.SelectColor) +end + +DLib.Loader.loadPureCS('dlib/vgui/skin') + +derma.DefineSkin('DLib_Black', 'Made to look like flat VGUI', SKIN) +derma.RefreshSkins() diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/colours.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/colours.lua new file mode 100644 index 0000000..d734685 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/colours.lua @@ -0,0 +1,243 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +local WINDOW_ALPHA = 200 +local WINDOW_ALPHA_SOFT = 140 + +DLib.skin.colours = {} +DLib.skin.colors = DLib.skin.colours +DLib.skin.colours.white = Color(255, 255, 255) +DLib.skin.colours.black = Color(0, 0, 0) +DLib.skin.colours.gray_bright = Color(225, 225, 225) +DLib.skin.colours.gray = Color(200, 200, 200) +DLib.skin.colours.gray_dark = Color(175, 175, 175) +DLib.skin.colours.bg_bright = Color(45, 45, 45, 200) + +DLib.skin.colours.ComboBoxNormal = Color(30, 30, 30, 150) +DLib.skin.colours.ComboBoxHover = Color(60, 80, 60, 150) +DLib.skin.colours.ComboBoxDown = Color(80, 120, 80, 150) +DLib.skin.colours.ComboBoxDisabled = Color(0, 0, 0, 130) + +DLib.skin.CloseAlpha = 150 + +DLib.skin.bg_color = Color(55, 55, 55, WINDOW_ALPHA) +DLib.skin.bg_color_menu = Color(55, 55, 55, WINDOW_ALPHA_SOFT) +DLib.skin.bg_color_sleep = Color(70, 70, 70, WINDOW_ALPHA) +DLib.skin.bg_color_dark = Color(0, 0, 0, WINDOW_ALPHA) +DLib.skin.bg_color_bright = Color(220, 220, 220, WINDOW_ALPHA) +DLib.skin.frame_border = Color(50, 50, 50, WINDOW_ALPHA) + +DLib.skin.control_color = Color(120, 120, 120) +DLib.skin.control_color_highlight = Color(150, 150, 150, 255) +DLib.skin.control_color_active = Color(110, 150, 250, 255) +DLib.skin.control_color_bright = Color(255, 200, 100, 255) +DLib.skin.control_color_dark = Color(100, 100, 100, 255) + +DLib.skin.bg_alt1 = Color(50, 50, 50, WINDOW_ALPHA) +DLib.skin.bg_alt2 = Color(55, 55, 55, WINDOW_ALPHA) + +DLib.skin.listview_hover = Color(70, 70, 70, 255) +DLib.skin.listview_selected = Color(100, 170, 220, 255) + +DLib.skin.text_bright = Color(255, 255, 255, 255) +DLib.skin.text_normal = Color(255, 255, 255, 255) +DLib.skin.text_dark = Color(175, 175, 175, 255) +DLib.skin.text_highlight = Color(255, 20, 20, 255) + +DLib.skin.combobox_selected = DLib.skin.listview_selected + +DLib.skin.panel_transback = Color(255, 255, 255, 50) +DLib.skin.tooltip = Color(255, 245, 175, 255) + +DLib.skin.colPropertySheet = Color(170, 170, 170, 255) +DLib.skin.colTab = DLib.skin.colPropertySheet +DLib.skin.colTabInactive = Color(140, 140, 140, 255) +DLib.skin.colTabShadow = Color(0, 0, 0, 170) +DLib.skin.colTabText = Color(255, 255, 255, 255) +DLib.skin.colTabTextInactive = Color(0, 0, 0, 200) + +DLib.skin.colCollapsibleCategory = Color(255, 255, 255, 20) + +DLib.skin.colCategoryText = Color(255, 255, 255, 255) +DLib.skin.colCategoryTextInactive = Color(200, 200, 200, 255) + +DLib.skin.colNumberWangBG = Color(255, 240, 150, 255) +DLib.skin.colTextEntryBG = Color(200, 200, 200, 255) +DLib.skin.colTextEntryBorder = Color(140, 140, 140, 255) +DLib.skin.colTextEntryText = Color(25, 25, 25, 255) +DLib.skin.colTextEntryTextHighlight = Color(255, 255, 255, 255) +DLib.skin.colTextEntryTextCursor = Color(0, 0, 100, 255) + +DLib.skin.colTextEntryTextPlaceholder = Color(100, 100, 100) + +DLib.skin.colMenuBG = Color(255, 255, 255, 200) +DLib.skin.colMenuBorder = Color(0, 0, 0, 200) + +DLib.skin.colButtonText = Color(255, 255, 255, 255) +DLib.skin.colButtonTextDisabled = Color(255, 255, 255, 55) +DLib.skin.colButtonBorder = Color(20, 20, 20, 255) +DLib.skin.colButtonBorderHighlight = Color(255, 255, 255, 50) +DLib.skin.colButtonBorderShadow = Color(0, 0, 0, 100) + +DLib.skin.bg_verybright = Color(80, 80, 80, 200) +DLib.skin.bg_hightlight = Color(40, 80, 40, 200) + +DLib.skin.background = DLib.skin.bg_color +DLib.skin.background_inactive = DLib.skin.bg_color_dark +DLib.skin.frame_top = Color(90, 90, 90, WINDOW_ALPHA) + +DLib.skin.ButtonHoverColor = Color(200, 200, 200, 150) +DLib.skin.ButtonAlpha = 150 +DLib.skin.ButtonDefColor = Color(0, 0, 0, 150) +DLib.skin.ButtonDefColor2 = Color(140, 140, 140, 150) + +DLib.skin.CheckBoxBG = Color(30, 30, 30) +DLib.skin.CheckBoxBGD = Color(70, 70, 70) + +DLib.skin.CheckBoxC = Color(105, 255, 250) +DLib.skin.CheckBoxU = Color(255, 148, 148) + +DLib.skin.MenuHoverColor = Color(140, 140, 140) +DLib.skin.MenuSpacer = Color(200, 200, 200) +DLib.skin.MenuSpacerStrip = Color(100, 100, 100, 200) + +DLib.skin.colours.TabControl = Color(0, 0, 0, 200) +DLib.skin.CloseHoverCol = Color(200, 130, 130, DLib.skin.CloseAlpha) + +DLib.skin.SelectColor = Color(203, 225, 203, 50) + +DLib.skin.colours.TabSelected = Color(200, 255, 200, 150) +DLib.skin.colours.TabUnSelected = Color(80, 80, 80, 200) +DLib.skin.colours.TabUnSelected2 = Color(255, 255, 255, 20) + +DLib.skin.colours.windowCol = Color(200, 200, 200) + +-- sub categories + +DLib.skin.tex.CategoryList = {} +DLib.skin.tex.CategoryList.BG = Color(65, 65, 65, 255) +DLib.skin.tex.CategoryList.Headerr = Color(200, 200, 200, 180) + +DLib.skin.Colours = {} + +DLib.skin.Colours.Window = {} +DLib.skin.Colours.Window.TitleActive = Color(255, 255, 255) +DLib.skin.Colours.Window.TitleInactive = Color(200, 200, 200) + +DLib.skin.Colours.Button = {} +DLib.skin.Colours.Button.Normal = Color(200, 200, 200, 225) +DLib.skin.Colours.Button.Disabled = Color(145, 145, 145) +DLib.skin.Colours.Button.Down = Color(255, 255, 255) +DLib.skin.Colours.Button.Hover = Color(225, 225, 225) +DLib.skin.Colours.Button.Menu = Color(255, 255, 255) + +DLib.skin.Colours.Tab = {} +DLib.skin.Colours.Tab.Active = {} +DLib.skin.Colours.Tab.Active.Normal = Color(120, 120, 120) +DLib.skin.Colours.Tab.Active.Hover = Color(0, 120, 0) +DLib.skin.Colours.Tab.Active.Down = Color(0, 140, 0) +DLib.skin.Colours.Tab.Active.Disabled = Color(170, 150, 170) + +DLib.skin.Colours.Tab.Inactive = {} +DLib.skin.Colours.Tab.Inactive.Normal = Color(200, 200, 200) +DLib.skin.Colours.Tab.Inactive.Hover = Color(200, 225, 200) +DLib.skin.Colours.Tab.Inactive.Down = Color(200, 255, 200) +DLib.skin.Colours.Tab.Inactive.Disabled = Color(170, 170, 170) + +DLib.skin.Colours.Label = {} +DLib.skin.Colours.Label.Default = Color(255, 255, 255) +DLib.skin.Colours.Label.Bright = Color(255, 255, 255) +DLib.skin.Colours.Label.Dark = Color(225, 225, 225) +DLib.skin.Colours.Label.Highlight = Color(200, 255, 200) + +DLib.skin.Colours.Tree = {} +DLib.skin.Colours.Tree.Lines = Color(255, 255, 255) ---- !!! +DLib.skin.Colours.Tree.Normal = Color(200, 200, 200) +DLib.skin.Colours.Tree.Hover = Color(200, 255, 200) +DLib.skin.Colours.Tree.Selected = Color(255, 255, 255) + +DLib.skin.Colours.Properties = {} +DLib.skin.Colours.Properties.Line_Normal = GWEN.TextureColor(4 + 8 * 12, 508) +DLib.skin.Colours.Properties.Line_Selected = GWEN.TextureColor(4 + 8 * 13, 508) +DLib.skin.Colours.Properties.Line_Hover = GWEN.TextureColor(4 + 8 * 12, 500) +DLib.skin.Colours.Properties.Title = Color(255, 255, 255) +DLib.skin.Colours.Properties.Column_Normal = GWEN.TextureColor(4 + 8 * 14, 508) +DLib.skin.Colours.Properties.Column_Selected = GWEN.TextureColor(4 + 8 * 15, 508) +DLib.skin.Colours.Properties.Column_Hover = GWEN.TextureColor(4 + 8 * 14, 500) +DLib.skin.Colours.Properties.Border = GWEN.TextureColor(4 + 8 * 15, 500) +DLib.skin.Colours.Properties.Label_Normal = Color(200, 200, 200) +DLib.skin.Colours.Properties.Label_Selected = Color(255, 255, 255) +DLib.skin.Colours.Properties.Label_Hover = Color(200, 255, 200) + +DLib.skin.Colours.Category = {} +DLib.skin.Colours.Category.Line = {} +DLib.skin.Colours.Category.LineAlt = {} + +DLib.skin.Colours.Category.Line.Text = Color(200, 200, 200) +DLib.skin.Colours.Category.Line.Text_Hover = Color(200, 255, 200) +DLib.skin.Colours.Category.Line.Text_Selected = Color(255, 255, 255) +DLib.skin.Colours.Category.Line.Button = DLib.skin.background +DLib.skin.Colours.Category.Line.Button_Hover = Color(100, 100, 100) +DLib.skin.Colours.Category.Line.Button_Selected = Color(130, 130, 130) + +DLib.skin.Colours.Category.LineAlt.Text = Color(200, 200, 200) +DLib.skin.Colours.Category.LineAlt.Text_Hover = Color(200, 255, 200) +DLib.skin.Colours.Category.LineAlt.Text_Selected = Color(255, 255, 255) +DLib.skin.Colours.Category.LineAlt.Button = DLib.skin.background +DLib.skin.Colours.Category.LineAlt.Button_Hover = Color(100, 100, 100) +DLib.skin.Colours.Category.LineAlt.Button_Selected = Color(130, 130, 130) + +DLib.skin.Colours.Category.Header = Color(255, 255, 255) +DLib.skin.Colours.Category.Header_Closed = Color(0, 0, 0) +DLib.skin.Colours.TooltipText = Color(255, 255, 255) + +DLib.skin.tex.Input.ListBox.BG = Color(0, 0, 0, 200) +DLib.skin.tex.Input.ListBox.First = Color(100, 100, 100) +DLib.skin.tex.Input.ListBox.Second = Color(125, 125, 125) +DLib.skin.tex.Input.ListBox.Select = Color(75, 125, 75) + +DLib.skin.tex.Scroller.BackColor = Color(0, 0, 0, 50) +DLib.skin.tex.Scroller.ScrollerColI = Color(200, 200, 200, 255) +DLib.skin.tex.Scroller.ScrollerColD = Color(140, 140, 140, 255) +DLib.skin.tex.Scroller.ScrollerColH = Color(255, 255, 255, 255) +DLib.skin.tex.Scroller.ScrollerColP = Color(200, 255, 200, 255) +DLib.skin.tex.Scroller.BColor = Color(130, 130, 130, 200) +DLib.skin.tex.Scroller.BColorH = Color(160, 160, 160, 200) +DLib.skin.tex.Scroller.BColorP = Color(200, 200, 200, 200) +DLib.skin.tex.Scroller.BColorD = Color(30, 30, 30, 200) +DLib.skin.tex.Scroller.TextColr = Color(255, 255, 255) diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/buttons.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/buttons.lua new file mode 100644 index 0000000..498303c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/buttons.lua @@ -0,0 +1,141 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +--Left +function DLib.skin.tex.Scroller.LeftButton_Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColor) + Simple_DrawText('◀', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.LeftButton_Hover(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorH) + Simple_DrawText('◀', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.LeftButton_Down(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorP) + Simple_DrawText('◀', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.LeftButton_Disabled(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorD) + Simple_DrawText('◀', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +--Up +function DLib.skin.tex.Scroller.UpButton_Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColor) + Simple_DrawText('▲', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.UpButton_Hover(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorH) + Simple_DrawText('▲', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.UpButton_Down(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorP) + Simple_DrawText('▲', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.UpButton_Disabled(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorD) + Simple_DrawText('▲', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +--Down +function DLib.skin.tex.Scroller.DownButton_Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColor) + Simple_DrawText('▼', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.DownButton_Hover(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorH) + Simple_DrawText('▼', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.DownButton_Down(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorP) + Simple_DrawText('▼', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.DownButton_Disabled(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorD) + Simple_DrawText('▼', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +--Right +function DLib.skin.tex.Scroller.RightButton_Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColor) + Simple_DrawText('▶', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.RightButton_Hover(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorH) + Simple_DrawText('▶', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.RightButton_Down(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorP) + Simple_DrawText('▶', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.RightButton_Disabled(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BColorD) + Simple_DrawText('▶', 'Default', x + 2, y + 1, DLib.skin.tex.Scroller.TextColr) +end + +function DLib.skin.tex.Scroller.TrackV(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.BackColor) +end + +function DLib.skin.tex.Scroller.ButtonV_Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.ScrollerColI) +end + +function DLib.skin.tex.Scroller.ButtonV_Hover(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.ScrollerColH) +end + +function DLib.skin.tex.Scroller.ButtonV_Down(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.ScrollerColP) +end + +function DLib.skin.tex.Scroller.ButtonV_Disabled(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Scroller.ScrollerColD) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/common.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/common.lua new file mode 100644 index 0000000..6e48cf4 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/common.lua @@ -0,0 +1,165 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText +local HUDCommons = DLib.HUDCommons + +-- Buttons +function DLib.skin.tex.Button(x, y, w, h, self) + if not self then return end + self.Neon = self.Neon or 0 + + Simple_DrawBox(x, y, w, h, DLib.skin.ButtonDefColor) + Simple_DrawBox(x, y + h / 2 - h * self.Neon / 2, w, h * self.Neon, DLib.skin.ButtonDefColor2) + + self.Neon = Lerp(FrameTime() * 8, self.Neon, 0) +end + +function DLib.skin.tex.Button_Hovered(x, y, w, h, self) + if not self then return end + self.Neon = self.Neon or 0 + + Simple_DrawBox(x, y, w, h, DLib.skin.ButtonDefColor) + Simple_DrawBox(x, y + h / 2 - h * self.Neon / 2, w, h * self.Neon, DLib.skin.ButtonDefColor2) + + self.Neon = Lerp(FrameTime() * 8, self.Neon, 1) +end + +function DLib.skin.tex.Button_Down(x, y, w, h, self) + if not self then return end + self.Neon = self.Neon or 0 + + Simple_DrawBox(x, y, w, h, DLib.skin.ButtonDefColor) + Simple_DrawBox(x, y + h / 2 - h * self.Neon / 2, w, h * self.Neon, DLib.skin.ButtonDefColor2) + + self.Neon = Lerp(FrameTime() * 8, self.Neon, 1) +end + +function DLib.skin.tex.Button_Dead(x, y, w, h, self) + if not self then return end + self.Neon = self.Neon or 0 + + Simple_DrawBox(x, y, w, h, DLib.skin.ButtonDefColor) + Simple_DrawBox(x, y + h / 2 - h * self.Neon / 2, w, h * self.Neon, DLib.skin.ButtonDefColor2) + + self.Neon = Lerp(FrameTime() * 8, self.Neon, 0) +end + +-- Checkboxes + +function DLib.skin.tex.Checkbox_Checked(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.CheckBoxBG) + surface_SetDrawColor(DLib.skin.CheckBoxC) + + local size = math.min(w, h) * 0.8 + + HUDCommons.DrawRotatedRect(x + size * 0.2, y + size * 0.7, size * 0.5, size * 0.15, 45) + HUDCommons.DrawRotatedRect(x + size * .4, y + size * .47 + size * 0.6, size, size * 0.15, -45) +end + +function DLib.skin.tex.Checkbox(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.CheckBoxBG) + surface_SetDrawColor(DLib.skin.CheckBoxU) + + local size = math.min(w, h) * 0.8 + + HUDCommons.DrawRotatedRect(x + size * 0.25, y + size * 0.15, size * 1.2, size * 0.15, 45) + HUDCommons.DrawRotatedRect(x + size * 0.15, y + size, size * 1.2, size * 0.15, -45) +end + +function DLib.skin.tex.CheckboxD_Checked(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.CheckBoxBGD) + surface_SetDrawColor(DLib.skin.CheckBoxC) + + local size = math.min(w, h) * 0.8 + + HUDCommons.DrawRotatedRect(x + size * 0.2, y + size * 0.7, size * 0.5, size * 0.15, 45) + HUDCommons.DrawRotatedRect(x + size * .4, y + size * .47 + size * 0.6, size, size * 0.15, -45) +end + +function DLib.skin.tex.CheckboxD(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.CheckBoxBGD) + surface_SetDrawColor(DLib.skin.CheckBoxU) + + local size = math.min(w, h) * 0.8 + + HUDCommons.DrawRotatedRect(x + size * 0.25, y + size * 0.15, size * 1.2, size * 0.15, 45) + HUDCommons.DrawRotatedRect(x + size * 0.15, y + size, size * 1.2, size * 0.15, -45) +end + +-- Menu + +function DLib.skin.tex.MenuBG_Column(x, y, w, h, self) + Simple_DrawBox(x, y, w, h, DLib.skin.MenuHoverColor) +end + +function DLib.skin.tex.MenuBG(x, y, w, h, self) + Simple_DrawBox(x, y, w, h, DLib.skin.bg_color_menu) +end + +function DLib.skin.tex.MenuBG_Hover(x, y, w, h, self) + Simple_DrawBox(x, y, w, h, DLib.skin.MenuHoverColor) +end + +function DLib.skin.tex.MenuBG_Spacer(x, y, w, h, self) + Simple_DrawBox(x, y, w, h, DLib.skin.MenuSpacer) +end + +function DLib.skin.tex.Menu_Strip(x, y, w, h, self) + Simple_DrawBox(x, y, w, h, DLib.skin.MenuSpacerStrip) +end + +function DLib.skin.tex.Tab_Control(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.colours.TabControl) +end + +function DLib.skin.tex.TabB_Active(x, y, w, h, self) + Simple_DrawBox(x + 4, y, w - 8, h - 8, Selected) +end + +function DLib.skin.tex.TabB_Inactive(x, y, w, h, self) + Simple_DrawBox(x + 4, y + 2, w - 8, h - 2, UnSelected) + Simple_DrawBox(x + 4, y + 12, w - 8, h - 2, UnSelected2) +end + +function DLib.skin.tex.TabT_Active(x, y, w, h, self) + Simple_DrawBox(x + 4, y, w - 8, h - 8, DLib.skin.colours.TabSelected) +end + +function DLib.skin.tex.TabT_Inactive(x, y, w, h, self) + Simple_DrawBox(x + 4, y + 2, w - 8, h - 2, DLib.skin.colours.TabUnSelected) + Simple_DrawBox(x + 4, y + 12, w - 8, h - 2, DLib.skin.colours.TabUnSelected2) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/gwen.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/gwen.lua new file mode 100644 index 0000000..843c153 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/gwen.lua @@ -0,0 +1,84 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +DLib.skin.tex.Shadow = GWEN.CreateTextureBorder(448, 0, 31, 31, 8, 8, 8, 8) +DLib.skin.tex.TreePlus = GWEN.CreateTextureNormal(448, 96, 15, 15) +DLib.skin.tex.TreeMinus = GWEN.CreateTextureNormal(464, 96, 15, 15) +DLib.skin.tex.TextBox = GWEN.CreateTextureBorder(0, 150, 127, 21, 4, 4, 4, 4) +DLib.skin.tex.TextBox_Focus = GWEN.CreateTextureBorder(0, 172, 127, 21, 4, 4, 4, 4) +DLib.skin.tex.TextBox_Disabled = GWEN.CreateTextureBorder(0, 194, 127, 21, 4, 4, 4, 4) +DLib.skin.tex.Menu_Check = GWEN.CreateTextureNormal(448, 112, 15, 15) + +DLib.skin.tex.TabL_Active = GWEN.CreateTextureBorder(64, 384, 31, 63, 8, 8, 8, 8) +DLib.skin.tex.TabL_Inactive = GWEN.CreateTextureBorder(64 + 128, 384, 31, 63, 8, 8, 8, 8) +DLib.skin.tex.TabR_Active = GWEN.CreateTextureBorder(96, 384, 31, 63, 8, 8, 8, 8) +DLib.skin.tex.TabR_Inactive = GWEN.CreateTextureBorder(96 + 128, 384, 31, 63, 8, 8, 8, 8) +DLib.skin.tex.Tab_Bar = GWEN.CreateTextureBorder(128, 352, 127, 31, 4, 4, 4, 4) + +DLib.skin.tex.Input.ComboBox.Button.Normal = GWEN.CreateTextureNormal(496, 272, 15, 15) +DLib.skin.tex.Input.ComboBox.Button.Hover = GWEN.CreateTextureNormal(496, 272 + 16, 15, 15) +DLib.skin.tex.Input.ComboBox.Button.Down = GWEN.CreateTextureNormal(496, 272 + 32, 15, 15) +DLib.skin.tex.Input.ComboBox.Button.Disabled = GWEN.CreateTextureNormal(496, 272 + 48, 15, 15) + +DLib.skin.tex.Input.UpDown.Down.Normal = GWEN.CreateTextureCentered(384, 120, 7, 7) +DLib.skin.tex.Input.UpDown.Down.Hover = GWEN.CreateTextureCentered(384 + 8, 120, 7, 7) +DLib.skin.tex.Input.UpDown.Down.Down = GWEN.CreateTextureCentered(384 + 16, 120, 7, 7) +DLib.skin.tex.Input.UpDown.Down.Disabled = GWEN.CreateTextureCentered(384 + 24, 120, 7, 7) + +DLib.skin.tex.Input.Slider.H.Normal = GWEN.CreateTextureNormal(416, 32, 15, 15) +DLib.skin.tex.Input.Slider.H.Hover = GWEN.CreateTextureNormal(416, 32 + 16, 15, 15) +DLib.skin.tex.Input.Slider.H.Down = GWEN.CreateTextureNormal(416, 32 + 32, 15, 15) +DLib.skin.tex.Input.Slider.H.Disabled = GWEN.CreateTextureNormal(416, 32 + 48, 15, 15) + +DLib.skin.tex.Input.Slider.V.Normal = GWEN.CreateTextureNormal(416 + 16, 32, 15, 15) +DLib.skin.tex.Input.Slider.V.Hover = GWEN.CreateTextureNormal(416 + 16, 32 + 16, 15, 15) +DLib.skin.tex.Input.Slider.V.Down = GWEN.CreateTextureNormal(416 + 16, 32 + 32, 15, 15) +DLib.skin.tex.Input.Slider.V.Disabled = GWEN.CreateTextureNormal(416 + 16, 32 + 48, 15, 15) + +DLib.skin.tex.Input.UpDown.Up.Normal = GWEN.CreateTextureCentered(384, 112, 7, 7) +DLib.skin.tex.Input.UpDown.Up.Hover = GWEN.CreateTextureCentered(384 + 8, 112, 7, 7) +DLib.skin.tex.Input.UpDown.Up.Down = GWEN.CreateTextureCentered(384 + 16, 112, 7, 7) +DLib.skin.tex.Input.UpDown.Up.Disabled = GWEN.CreateTextureCentered(384 + 24, 112, 7, 7) + +DLib.skin.tex.Menu.RightArrow = GWEN.CreateTextureNormal(464, 112, 15, 15) + +DLib.skin.tex.Scroller.TrackH = GWEN.CreateTextureBorder(384, 128, 127, 15, 4, 4, 4, 4) +DLib.skin.tex.Scroller.ButtonH_Normal = GWEN.CreateTextureBorder(384, 128 + 16, 127, 15, 4, 4, 4, 4) +DLib.skin.tex.Scroller.ButtonH_Hover = GWEN.CreateTextureBorder(384, 128 + 32, 127, 15, 4, 4, 4, 4) +DLib.skin.tex.Scroller.ButtonH_Down = GWEN.CreateTextureBorder(384, 128 + 48, 127, 15, 4, 4, 4, 4) +DLib.skin.tex.Scroller.ButtonH_Disabled = GWEN.CreateTextureBorder(384, 128 + 64, 127, 15, 4, 4, 4, 4) diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/menus.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/menus.lua new file mode 100644 index 0000000..c19e39f --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/menus.lua @@ -0,0 +1,81 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +function DLib.skin.tex.Tree(x, y, w, h, self) + Simple_DrawBox(x, y, w, h, DLib.skin.bg_color) +end + +function DLib.skin.tex.Input.ListBox.Background(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Input.ListBox.BG) +end + +function DLib.skin.tex.Input.ListBox.Hovered(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Input.ListBox.BG) +end + +function DLib.skin.tex.Input.ListBox.EvenLine(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Input.ListBox.First) +end + +function DLib.skin.tex.Input.ListBox.OddLine(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Input.ListBox.Second) +end + +function DLib.skin.tex.Input.ListBox.EvenLineSelected(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Input.ListBox.Select) +end + +function DLib.skin.tex.Input.ListBox.OddLineSelected(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.Input.ListBox.Select) +end + +function DLib.skin.tex.Input.ComboBox.Normal(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.colours.ComboBoxNormal) +end + +function DLib.skin.tex.Input.ComboBox.Hover(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.colours.ComboBoxHover) +end + +function DLib.skin.tex.Input.ComboBox.Down(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.colours.ComboBoxDown) +end + +function DLib.skin.tex.Input.ComboBox.Disabled(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.colours.ComboBoxDisabled) +end \ No newline at end of file diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/util.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/util.lua new file mode 100644 index 0000000..80052f1 --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/util.lua @@ -0,0 +1,61 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +function DLib.skin.tex.CategoryList.Outer(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.CategoryList.BG) +end + +function DLib.skin.tex.CategoryList.Inner(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.CategoryList.BG) +end + +function DLib.skin.tex.CategoryList.Header(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.tex.CategoryList.Headerr) +end + +function DLib.skin.tex.Tooltip(x, y, w, h) + Simple_DrawBox(x, y, w, h, DLib.skin.background) +end + +function DLib.skin.tex.ProgressBar.Back(x, y, w, h) + Simple_DrawBox(x, y, w, h, Color(90, 90, 90)) +end + +function DLib.skin.tex.ProgressBar.Front(x, y, w, h) + Simple_DrawBox(x + 2, y + 2, w - 4, h - 4, Color(160, 200, 130)) +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/window.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/window.lua new file mode 100644 index 0000000..341183d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/draw/window.lua @@ -0,0 +1,214 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +function DLib.skin.tex.Window.Normal(x, y, w, h) + Simple_DrawBox(x, y, w, 25, DLib.skin.background) -- top + Simple_DrawBox(x, y, w, h, DLib.skin.background) +end + +function DLib.skin.tex.Window.Inactive(x, y, w, h) + Simple_DrawBox(x, y, w, 25, DLib.skin.background) -- top + Simple_DrawBox(x, y, w, h, DLib.skin.background_inactive) +end + +local buttonOffset = 6 + +function DLib.skin.tex.Window.Close(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(200, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.max(self.Neon - 5 * (FrameTime() * 66), 0) + + surface_SetDrawColor(255, self.Neon * 3, self.Neon * 3, self.Neon * 3) + surface_DrawLine(x + 2, y + 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, y + 5) +end + +function DLib.skin.tex.Window.Close_Hover(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(200, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + surface_SetDrawColor(255, self.Neon * 3, self.Neon * 3, self.Neon * 3) + surface_DrawLine(x + 2, y + 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, y + 5) +end + +function DLib.skin.tex.Window.Close_Down(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, DLib.skin.CloseHoverCol) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(255, 200, 200) + surface_DrawLine(x + 2, y + 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, y + 5) +end + +--Maximize +function DLib.skin.tex.Window.Maxi(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(50 + self.Neon, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.max(self.Neon - 5 * (FrameTime() * 66), 0) + + surface_SetDrawColor(125 + self.Neon * 2, 125 + self.Neon * 2, 125 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 13, w - 4, h - buttonOffset - 13) + surface_DrawLine(x + 2, h - buttonOffset - 5, x + 2, h - buttonOffset - 13) + surface_DrawLine(w - 4, h - buttonOffset - 5, w - 4, h - buttonOffset - 13) +end + +function DLib.skin.tex.Window.Maxi_Hover(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(50 + self.Neon, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(150 + self.Neon * 2, 150 + self.Neon * 2, 150 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 13, w - 4, h - buttonOffset - 13) + surface_DrawLine(x + 2, h - buttonOffset - 5, x + 2, h - buttonOffset - 13) + surface_DrawLine(w - 4, h - buttonOffset - 5, w - 4, h - buttonOffset - 13) +end + +function DLib.skin.tex.Window.Maxi_Down(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, DLib.skin.colours.windowCol) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(150 + self.Neon * 2, 150 + self.Neon * 2, 150 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 13, w - 4, h - buttonOffset - 13) + surface_DrawLine(x + 2, h - buttonOffset - 5, x + 2, h - buttonOffset - 13) + surface_DrawLine(w - 4, h - buttonOffset - 5, w - 4, h - buttonOffset - 13) +end + +function DLib.skin.tex.Window.Restore(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(50 + self.Neon, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.max(self.Neon - 5 * (FrameTime() * 66), 0) + + surface_SetDrawColor(125 + self.Neon * 2, 125 + self.Neon * 2, 125 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 13, w - 4, h - buttonOffset - 13) + surface_DrawLine(x + 2, h - buttonOffset - 5, x + 2, h - buttonOffset - 13) + surface_DrawLine(w - 4, h - buttonOffset - 5, w - 4, h - buttonOffset - 13) +end + +function DLib.skin.tex.Window.Restore_Hover(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(50 + self.Neon, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(150 + self.Neon * 2, 150 + self.Neon * 2, 150 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 13, w - 4, h - buttonOffset - 13) + surface_DrawLine(x + 2, h - buttonOffset - 5, x + 2, h - buttonOffset - 13) + surface_DrawLine(w - 4, h - buttonOffset - 5, w - 4, h - buttonOffset - 13) +end + +function DLib.skin.tex.Window.Restore_Down(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, DLib.skin.colours.windowCol) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(150 + self.Neon * 2, 150 + self.Neon * 2, 150 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) + surface_DrawLine(x + 2, h - buttonOffset - 13, w - 4, h - buttonOffset - 13) + surface_DrawLine(x + 2, h - buttonOffset - 5, x + 2, h - buttonOffset - 13) + surface_DrawLine(w - 4, h - buttonOffset - 5, w - 4, h - buttonOffset - 13) +end + +function DLib.skin.tex.Window.Mini(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(50 + self.Neon, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.max(self.Neon - 5 * (FrameTime() * 66), 0) + + surface_SetDrawColor(125 + self.Neon * 2, 125 + self.Neon * 2, 125 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) +end + +function DLib.skin.tex.Window.Mini_Hover(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, Color(50 + self.Neon, 50 + self.Neon, 50 + self.Neon, DLib.skin.CloseAlpha)) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(125 + self.Neon * 2, 125 + self.Neon * 2, 125 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) +end + +function DLib.skin.tex.Window.Mini_Down(x, y, w, h, self) + if not self then return end + + self.Neon = self.Neon or 0 + Simple_DrawBox(x, y, w, h - buttonOffset, DLib.skin.colours.windowCol) + + self.Neon = math.min(self.Neon + 5 * (FrameTime() * 66), 50) + + surface_SetDrawColor(125 + self.Neon * 2, 125 + self.Neon * 2, 125 + self.Neon * 2) + surface_DrawLine(x + 2, h - buttonOffset - 5, w - 4, h - buttonOffset - 5) +end \ No newline at end of file diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/hooks.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/hooks.lua new file mode 100644 index 0000000..ac6519c --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/hooks.lua @@ -0,0 +1,697 @@ + +-- Copyright (C) 2016-2018 DBot + +-- 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 DLib = DLib +local surface = surface +local draw = draw +local Color = Color +local GWEN = GWEN +local surface_SetTexture = surface.SetTexture +local surface_DrawRect = surface.DrawRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetTextColor = surface.SetTextColor +local surface_SetTextPos = surface.SetTextPos +local surface_DrawText = surface.DrawText +local surface_SetFont = surface.SetFont +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local Simple_DrawBox = DLib.skin.Simple_DrawBox +local Simple_DrawText = DLib.skin.Simple_DrawText + +--[[--------------------------------------------------------- + ExpandButton +-----------------------------------------------------------]] +function DLib.skin:PaintExpandButton(panel, w, h) + + if not panel:GetExpanded() then + self.tex.TreePlus(0, 0, w, h) + else + self.tex.TreeMinus(0, 0, w, h) + end + +end + +local DefaultSkin + +local function access(key) + if DefaultSkin ~= nil then + return DefaultSkin[key] + end + + DefaultSkin = derma.GetSkinTable().Default + return DefaultSkin[key] +end + +--[[--------------------------------------------------------- + TextEntry +-----------------------------------------------------------]] +function DLib.skin:PaintTextEntry(panel, w, h) + -- https://github.com/Facepunch/garrysmod/blob/master/garrysmod/lua/skins/default.lua#L442 + -- this is stupid + local super = access('PaintTextEntry') + + if super then + return super(self, panel, w, h) + else + if panel.m_bBackground then + if panel:GetDisabled() then + self.tex.TextBox_Disabled(0, 0, w, h) + elseif panel:HasFocus() then + self.tex.TextBox_Focus(0, 0, w, h) + else + self.tex.TextBox(0, 0, w, h) + end + end + + panel:DrawTextEntryText(panel:GetTextColor(), panel:GetHighlightColor(), panel:GetCursorColor()) + end +end + +function DLib.skin:SchemeTextEntry(panel) ---------------------- TODO + if access('PaintTextEntry') then return end + panel:SetTextColor(self.colTextEntryText) + panel:SetHighlightColor(self.colTextEntryTextHighlight) + panel:SetCursorColor(self.colTextEntryTextCursor) +end + +--[[--------------------------------------------------------- + Menu +-----------------------------------------------------------]] +function DLib.skin:PaintMenuSpacer(panel, w, h) + self.tex.MenuBG(0, 0, w, h) +end + +--[[--------------------------------------------------------- + MenuOption +-----------------------------------------------------------]] +function DLib.skin:PaintMenuOption(panel, w, h) + if panel.m_bBackground and (panel.Hovered or panel.Highlight) then + self.tex.MenuBG_Hover(0, 0, w, h) + end + + if panel:GetChecked() then + self.tex.Menu_Check(5, h/2-7, 15, 15) + end +end + +--[[--------------------------------------------------------- + MenuRightArrow +-----------------------------------------------------------]] +function DLib.skin:PaintMenuRightArrow(panel, w, h) + + self.tex.Menu.RightArrow(0, 0, w, h) + +end + +--[[--------------------------------------------------------- + PropertySheet +-----------------------------------------------------------]] +function DLib.skin:PaintPropertySheet(panel, w, h) + + -- TODO: Tabs at bottom, left, right + local ActiveTab = panel:GetActiveTab() + local Offset = 0 + if ActiveTab then Offset = ActiveTab:GetTall()-8 end + + if self.ENABLE_BLUR:GetBool() then + DLib.blur.RefreshNow(true) + DLib.blur.DrawOffset(0, Offset, w, h, panel:LocalToScreen(0, Offset)) + end + + self.tex.Tab_Control(0, Offset, w, h-Offset) +end + +--[[--------------------------------------------------------- + Tab +-----------------------------------------------------------]] +function DLib.skin:PaintTab(panel, w, h) + if panel:GetPropertySheet():GetActiveTab() == panel then + return self:PaintActiveTab(panel, w, h) + end + + --DLib.blur.DrawPanel(w, h, panel:LocalToScreen(0, 0)) + self.tex.TabT_Inactive(0, 0, w, h) +end + +function DLib.skin:PaintActiveTab(panel, w, h) + --DLib.blur.DrawPanel(w, h, panel:LocalToScreen(0, 0)) + self.tex.TabT_Active(0, 0, w, h) +end + +--[[--------------------------------------------------------- + Button +-----------------------------------------------------------]] +function DLib.skin:PaintWindowCloseButton(panel, w, h) + if not panel.m_bBackground then return end + + if panel:GetDisabled() then + return self.tex.Window.Close(0, 0, w, h, Color(255, 255, 255, 50)) + end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Window.Close_Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Window.Close_Hover(0, 0, w, h) + end + + self.tex.Window.Close(0, 0, w, h) +end + +function DLib.skin:PaintWindowMinimizeButton(panel, w, h) + if not panel.m_bBackground then return end + + if panel:GetDisabled() then + return self.tex.Window.Mini(0, 0, w, h, Color(255, 255, 255, 50)) + end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Window.Mini_Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Window.Mini_Hover(0, 0, w, h) + end + + self.tex.Window.Mini(0, 0, w, h) +end + +function DLib.skin:PaintWindowMaximizeButton(panel, w, h) + if not panel.m_bBackground then return end + + if panel:GetDisabled() then + return self.tex.Window.Maxi(0, 0, w, h, Color(255, 255, 255, 50)) + end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Window.Maxi_Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Window.Maxi_Hover(0, 0, w, h) + end + + self.tex.Window.Maxi(0, 0, w, h) +end + +--[[--------------------------------------------------------- + VScrollBar +-----------------------------------------------------------]] +function DLib.skin:PaintVScrollBar(panel, w, h) + self.tex.Scroller.TrackV(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ScrollBarGrip +-----------------------------------------------------------]] +function DLib.skin:PaintScrollBarGrip(panel, w, h) + if panel:GetDisabled() then + return self.tex.Scroller.ButtonV_Disabled(0, 0, w, h) + end + + if panel.Depressed then + return self.tex.Scroller.ButtonV_Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Scroller.ButtonV_Hover(0, 0, w, h) + end + + return self.tex.Scroller.ButtonV_Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ButtonDown +-----------------------------------------------------------]] +function DLib.skin:PaintButtonDown(panel, w, h) + if not panel.m_bBackground then return end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Scroller.DownButton_Down(0, 0, w, h) + end + + if panel:GetDisabled() then + return self.tex.Scroller.DownButton_Dead(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Scroller.DownButton_Hover(0, 0, w, h) + end + + self.tex.Scroller.DownButton_Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ButtonUp +-----------------------------------------------------------]] +function DLib.skin:PaintButtonUp(panel, w, h) + if not panel.m_bBackground then return end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Scroller.UpButton_Down(0, 0, w, h) + end + + if panel:GetDisabled() then + return self.tex.Scroller.UpButton_Dead(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Scroller.UpButton_Hover(0, 0, w, h) + end + + self.tex.Scroller.UpButton_Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ButtonLeft +-----------------------------------------------------------]] +function DLib.skin:PaintButtonLeft(panel, w, h) + if not panel.m_bBackground then return end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Scroller.LeftButton_Down(0, 0, w, h) + end + + if panel:GetDisabled() then + return self.tex.Scroller.LeftButton_Dead(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Scroller.LeftButton_Hover(0, 0, w, h) + end + + self.tex.Scroller.LeftButton_Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ButtonRight +-----------------------------------------------------------]] +function DLib.skin:PaintButtonRight(panel, w, h) + + if not panel.m_bBackground then return end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Scroller.RightButton_Down(0, 0, w, h) + end + + if panel:GetDisabled() then + return self.tex.Scroller.RightButton_Dead(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Scroller.RightButton_Hover(0, 0, w, h) + end + + self.tex.Scroller.RightButton_Normal(0, 0, w, h) + +end + +--[[--------------------------------------------------------- + ComboDownArrow +-----------------------------------------------------------]] +function DLib.skin:PaintComboDownArrow(panel, w, h) + if panel.ComboBox:GetDisabled() then + return self.tex.Input.ComboBox.Button.Disabled(0, 0, w, h) + end + + if panel.ComboBox.Depressed or panel.ComboBox:IsMenuOpen() then + return self.tex.Input.ComboBox.Button.Down(0, 0, w, h) + end + + if panel.ComboBox.Hovered then + return self.tex.Input.ComboBox.Button.Hover(0, 0, w, h) + end + + self.tex.Input.ComboBox.Button.Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ComboBox +-----------------------------------------------------------]] +function DLib.skin:PaintComboBox(panel, w, h) + if panel:GetDisabled() then + return self.tex.Input.ComboBox.Disabled(0, 0, w, h) + end + + if panel.Depressed or panel:IsMenuOpen() then + return self.tex.Input.ComboBox.Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Input.ComboBox.Hover(0, 0, w, h) + end + + self.tex.Input.ComboBox.Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + ComboBox +-----------------------------------------------------------]] +function DLib.skin:PaintListBox(panel, w, h) + + self.tex.Input.ListBox.Background(0, 0, w, h) + +end + +--[[--------------------------------------------------------- + NumberUp +-----------------------------------------------------------]] +function DLib.skin:PaintNumberUp(panel, w, h) + if panel:GetDisabled() then + return self.Input.UpDown.Up.Disabled(0, 0, w, h) + end + + if panel.Depressed then + return self.tex.Input.UpDown.Up.Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Input.UpDown.Up.Hover(0, 0, w, h) + end + + self.tex.Input.UpDown.Up.Normal(0, 0, w, h) +end + +--[[--------------------------------------------------------- + NumberDown +-----------------------------------------------------------]] +function DLib.skin:PaintNumberDown(panel, w, h) + if panel:GetDisabled() then + return self.tex.Input.UpDown.Down.Disabled(0, 0, w, h) + end + + if panel.Depressed then + return self.tex.Input.UpDown.Down.Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Input.UpDown.Down.Hover(0, 0, w, h) + end + + self.tex.Input.UpDown.Down.Normal(0, 0, w, h) +end + +function DLib.skin:PaintTreeNode(panel, w, h) + if not panel.m_bDrawLines then return end + + surface_SetDrawColor(self.Colours.Tree.Lines) + + if panel.m_bLastChild then + surface_DrawRect(9, 0, 1, 7) + surface_DrawRect(9, 7, 9, 1) + else + surface_DrawRect(9, 0, 1, h) + surface_DrawRect(9, 7, 9, 1) + end +end + +function DLib.skin:PaintTreeNodeButton(panel, w, h) + if not panel.m_bSelected then return end + + -- Don't worry this isn't working out the size every render + -- it just gets the cached value from inside the Label + local w, _ = panel:GetTextSize() + + self.tex.Selection(38, 0, w + 6, h) +end + +function DLib.skin:PaintSelection(panel, w, h) + self.tex.Selection(0, 0, w, h) +end + +function DLib.skin:PaintSliderKnob(panel, w, h) + if panel:GetDisabled() then return self.tex.Input.Slider.H.Disabled(0, 0, w, h) end + + if panel.Depressed then + return self.tex.Input.Slider.H.Down(0, 0, w, h) + end + + if panel.Hovered then + return self.tex.Input.Slider.H.Hover(0, 0, w, h) + end + + self.tex.Input.Slider.H.Normal(0, 0, w, h) +end + +local function PaintNotches(x, y, w, h, num) + if not num then return end + + local space = w / num + for i=0, num do + surface_DrawRect(x + i * space, y + 4, 1, 5) + end +end + +function DLib.skin:PaintNumSlider(panel, w, h) + surface_SetDrawColor(Color(0, 0, 0, 100)) + surface_DrawRect(8, h / 2 - 1, w - 15, 1) + + PaintNotches(8, h / 2 - 1, w - 16, 1, panel.m_iNotches) +end + +function DLib.skin:PaintProgress(panel, w, h) + self.tex.ProgressBar.Back(0, 0, w, h) + self.tex.ProgressBar.Front(0, 0, w * panel:GetFraction(), h) +end + +function DLib.skin:PaintCollapsibleCategory(panel, w, h) + if not panel:GetExpanded() and h < 40 then + return self.tex.CategoryList.Header(0, 0, w, h) + end + + self.tex.CategoryList.Inner(0, 0, w, h) +end + +function DLib.skin:PaintCategoryList(panel, w, h) + self.tex.CategoryList.Outer(0, 0, w, h) +end + +function DLib.skin:PaintCategoryButton(panel, w, h) + if panel.AltLine then + if panel.Depressed or panel.m_bSelected then + surface_SetDrawColor(self.Colours.Category.LineAlt.Button_Selected) + elseif panel.Hovered then + surface_SetDrawColor(self.Colours.Category.LineAlt.Button_Hover) + else + surface_SetDrawColor(self.Colours.Category.LineAlt.Button) + end + else + if panel.Depressed or panel.m_bSelected then + surface_SetDrawColor(self.Colours.Category.Line.Button_Selected) + elseif panel.Hovered then + surface_SetDrawColor(self.Colours.Category.Line.Button_Hover) + else + surface_SetDrawColor(self.Colours.Category.Line.Button) + end + end + + surface_DrawRect(0, 0, w, h) +end + +function DLib.skin:PaintListViewLine(panel, w, h) + if panel:IsSelected() then + self.tex.Input.ListBox.EvenLineSelected(0, 0, w, h) + elseif panel.Hovered then + self.tex.Input.ListBox.Hovered(0, 0, w, h) + elseif panel.m_bAlt then + self.tex.Input.ListBox.EvenLine(0, 0, w, h) + end +end + +function DLib.skin:PaintListView(panel, w, h) + self.tex.Input.ListBox.Background(0, 0, w, h) +end + +function DLib.skin:PaintTooltip(panel, w, h) + self.tex.Tooltip(0, 0, w, h) +end + +function DLib.skin:PaintMenuBar(panel, w, h) + local Childs = panel:GetChildren() + + for k, v in pairs(Childs) do + if panel.SetTextColor and not panel.FixFuckingTextColor then + panel.FixFuckingTextColor = true + panel:SetTextColor(DLib.skin.Colours.Button.Menu) + end + end + + self.tex.Menu_Strip(0, 0, w, h) +end + +-- END DEFAULT + +function DLib.skin:PaintMenuOption(panel, w, h) + if panel.m_bBackground and (panel.Hovered or panel.Highlight) then + self.tex.MenuBG_Hover(0, 0, w, h) + end + + if panel:GetChecked() then + self.tex.Menu_Check(5, h/2-7, 15, 15) + end +end + +function DLib.skin:PaintMenu(panel, w, h) + if self.ENABLE_BLUR:GetBool() then + DLib.blur.RefreshNow(true) + DLib.blur.DrawPanel(w, h, panel:LocalToScreen(0, 0)) + end + + if panel:GetDrawColumn() then + self.tex.MenuBG_Column(0, 0, w, h) + else + self.tex.MenuBG(0, 0, w, h) + end + + local Canvas = panel:GetCanvas() + + if IsValid(Canvas) then + for k, v in pairs(Canvas:GetChildren()) do + if v.SetTextColor and not v.FIX_FUCKING_COLOR then + v:SetTextColor(DLib.skin.text_normal) + v.FIX_FUCKING_COLOR = true + end + end + end +end + +function DLib.skin:PaintTree(panel, w, h) + if not panel.m_bBackground then return end + self.tex.Tree(0, 0, w, h, panel.m_bgColor, panel) +end + +function DLib.skin:PaintCheckBox(panel, w, h) + if panel:GetChecked() then + if panel:GetDisabled() then + self.tex.CheckboxD_Checked(0, 0, w, h) + else + self.tex.Checkbox_Checked(0, 0, w, h) + end + else + if panel:GetDisabled() then + self.tex.CheckboxD(0, 0, w, h) + else + self.tex.Checkbox(0, 0, w, h) + end + end +end + +function DLib.skin:PaintButton(panel, w, h) + if panel:GetIsMenu() then + if not panel.FixFuckingTextColor then + panel.FixFuckingTextColor = true + panel:SetTextColor(DLib.skin.Colours.Button.Menu) + end + end + + if not panel.m_bBackground then return end + + if panel.Depressed or panel:IsSelected() or panel:GetToggle() then + return self.tex.Button_Down(0, 0, w, h, panel) + end + + if panel:GetDisabled() then + return self.tex.Button_Dead(0, 0, w, h, panel) + end + + if panel.Hovered then + return self.tex.Button_Hovered(0, 0, w, h, panel) + end + + self.tex.Button(0, 0, w, h, panel) +end + +function DLib.skin:PaintWindowCloseButton(panel, w, h) + if not panel.m_bBackground then return end + + if panel:GetDisabled() then + return self.tex.Window.Close(0, 0, w, h, panel) + end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Window.Close_Down(0, 0, w, h, panel) + end + + if panel.Hovered then + return self.tex.Window.Close_Hover(0, 0, w, h, panel) + end + + self.tex.Window.Close(0, 0, w, h, panel) +end + +function DLib.skin:PaintWindowMinimizeButton(panel, w, h) + if not panel.m_bBackground then return end + + if panel:GetDisabled() then + return self.tex.Window.Mini(0, 0, w, h, panel) + end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Window.Mini_Down(0, 0, w, h, panel) + end + + if panel.Hovered then + return self.tex.Window.Mini_Hover(0, 0, w, h, panel) + end + + self.tex.Window.Mini(0, 0, w, h, panel) +end + +function DLib.skin:PaintWindowMaximizeButton(panel, w, h) + if not panel.m_bBackground then return end + + if panel:GetDisabled() then + return self.tex.Window.Maxi(0, 0, w, h, panel) + end + + if panel.Depressed or panel:IsSelected() then + return self.tex.Window.Maxi_Down(0, 0, w, h, panel) + end + + if panel.Hovered then + return self.tex.Window.Maxi_Hover(0, 0, w, h, panel) + end + + self.tex.Window.Maxi(0, 0, w, h, panel) +end + +--[[--------------------------------------------------------- + Frame +-----------------------------------------------------------]] +function DLib.skin:PaintFrame(panel, w, h) + if panel.m_bPaintShadow then + DisableClipping(true) + SKIN.tex.Shadow(-4, -4, w + 10, h + 10) + DisableClipping(false) + end + + if self.ENABLE_BLUR:GetBool() then + DLib.blur.RefreshNow(true) + DLib.blur.DrawPanel(w, h, panel:LocalToScreen(0, 0)) + end + + if panel:HasHierarchicalFocus() then + self.tex.Window.Normal(0, 0, w, h) + else + self.tex.Window.Inactive(0, 0, w, h) + end +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/icons.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/icons.lua new file mode 100644 index 0000000..a2ff92a --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/skin/icons.lua @@ -0,0 +1,74 @@ +local DLib = DLib +local Color = Color +local table = table +DLib.skin.icons = { } +do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = { + 'blue', + 'green', + 'orange', + 'pink', + 'purple', + 'red', + 'yellow' + } + for _index_0 = 1, #_list_0 do + local color = _list_0[_index_0] + _accum_0[_len_0] = "icon16/flag_" .. tostring(color) .. ".png" + _len_0 = _len_0 + 1 + end + DLib.skin.icons.flags = _accum_0 +end +do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = { + 'blue', + 'green', + 'orange', + 'pink', + 'purple', + 'red', + 'yellow' + } + for _index_0 = 1, #_list_0 do + local color = _list_0[_index_0] + _accum_0[_len_0] = "icon16/tag_" .. tostring(color) .. ".png" + _len_0 = _len_0 + 1 + end + DLib.skin.icons.tags = _accum_0 +end +DLib.skin.icons.tag = DLib.skin.icons.tags +DLib.skin.icons.copy = DLib.skin.icons.tags +do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = { + 'bug', + 'bug_go', + 'bug_delete', + 'bug_error' + } + for _index_0 = 1, #_list_0 do + local n = _list_0[_index_0] + _accum_0[_len_0] = "icon16/" .. tostring(n) .. ".png" + _len_0 = _len_0 + 1 + end + DLib.skin.icons.bugs = _accum_0 +end +DLib.skin.icons.url = { + 'icon16/link.png' +} +DLib.skin.icons.bug = DLib.skin.icons.bugs +DLib.skin.icons.user = 'icon16/user.png' +do + local _tbl_0 = { } + for key, value in pairs(DLib.skin.icons) do + _tbl_0[key] = (function() + return table.frandom(value) + end) + end + DLib.skin.icon = _tbl_0 +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/util.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/util.lua new file mode 100644 index 0000000..38df34e --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/util.lua @@ -0,0 +1,44 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + +--[[ + @doc + @fname DLib.VGUI.GenerateDefaultPreset + @args Panel manager + + @desc + a pill for !p:ControlPresets panel + @enddesc + + @client +]] +function DLib.VGUI.GenerateDefaultPreset(manager) + assert(manager.m_strPreset, 'ControlPresets passed must have m_strPreset already defined (`SetPreset` called)') + local preset = {} + + for i, convar in ipairs(manager:GetConVars()) do + local cget = GetConVar(convar) + preset[cget:GetName()] = cget:GetDefault() + end + + presets.Add(manager.m_strPreset, 'Default', preset) + + manager:ReloadPresets() +end diff --git a/garrysmod/addons/util-dlib/lua/dlib/vgui/window.lua b/garrysmod/addons/util-dlib/lua/dlib/vgui/window.lua new file mode 100644 index 0000000..a3cf6ee --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/dlib/vgui/window.lua @@ -0,0 +1,305 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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 surface = surface +local gui = gui +local vgui = vgui +local input = input + +--[[ + @doc + @panel DLib_ResizeTap + + @internal + + @desc + internally used by !p:DLib_Window + can be actually put on any panel. just set DLib_ResizeTap's property `.target` to required panel. + @enddesc +]] +local PANEL = {} + +function PANEL:Init() + self.tapped = false + self.target = NULL + self.effects = true + self.tapX, self.tapY = 0, 0 + self.oldW, self.oldH = 0, 0 + self.minW, self.minH = 100, 30 + self.currX, self.currY = 0, 0 + self.translucent = 0 + self:SetCursor('sizenwse') + + hook.Add('PostRenderVGUI', self, self.PostRenderVGUI) +end + +function PANEL:Paint(w, h) + surface.SetDrawColor(140, 140, 140, 150) + surface.DrawRect(0, 0, w, h) +end + +function PANEL:SetTarget(targetPanel) + self.target = targetPanel + return self +end + +function PANEL:PostRenderVGUI() + if not self.tapped and not self.fadeout then return end + surface.SetDrawColor(self.translucent * 2.5, self.translucent * 2.5, self.translucent * 2.5, self.translucent) + surface.DrawRect(self.currX, self.currY, self:GetNewDimensions()) +end + +function PANEL:OnMousePressed(key) + if key ~= MOUSE_LEFT then return end + self.tapped = true + self.tapX, self.tapY = gui.MousePos() + self.oldW, self.oldH = self.target:GetSize() + self.translucent = 0 + self.currX, self.currY = self.target:GetPos() +end + +function PANEL:Think() + if self.tapped then + if not input.IsMouseDown(MOUSE_LEFT) then + self:OnMouseReleased(MOUSE_LEFT) + return + end + + self.translucent = Lerp(FrameTime() * 5, self.translucent, 100) + end + + if self.fadeout then + self.translucent = Lerp(FrameTime() * 8, self.translucent, 0) + self.fadeout = self.translucent > 1 + end +end + +function PANEL:GetNewDimensions() + local x, y = gui.MousePos() + local diffX, diffY = x - self.tapX, y - self.tapY + return math.max(self.oldW + diffX, self.minW), math.max(self.oldH + diffY, self.minH) +end + +function PANEL:OnMouseReleased() + self.tapped = false + self.fadeout = true + local w, h = self:GetNewDimensions() + self.target:SetSize(w, h) +end + +vgui.Register('DLib_ResizeTap', PANEL, 'EditablePanel') + +--[[ + @doc + @panel DLib_Window + @parent DFrame + + @desc + !g:DFrame but with DLib's VGUI skin and resize control parented to it + and some spice like always `:MakePopup()` + @enddesc +]] +PANEL = {} +DLib.VGUI.Window = PANEL + +function PANEL:Init() + self:SetSize(ScrWL() - 100, ScrHL() - 100) + self:Center() + self:MakePopup() + self:SetTitle('DLib Window') + self:SetSkin('DLib_Black') + + self.bottomBar = vgui.Create('EditablePanel', self) + local bar = self.bottomBar + bar:Dock(BOTTOM) + bar:SetSize(0, 16) + bar:SetMouseInputEnabled(true) + + self.bottomTap = vgui.Create('DLib_ResizeTap', bar) + local tap = self.bottomTap + tap:Dock(RIGHT) + tap:SetSize(16, 16) + tap:SetMouseInputEnabled(true) + tap:SetTarget(self) +end + +--[[ + @doc + @fname DLib_Window:UpdateSize + @args number width, number height +]] +function PANEL:UpdateSize(w, h) + self:SetSize(w, h) + self:Center() +end + +--[[ + @doc + @fname DLib_Window:RemoveResize + + @desc + Removes the, wait, can't we just use !g:DFrame ? + @enddesc +]] +function PANEL:RemoveResize() + if IsValid(self.bottomBar) then + self.bottomBar:Remove() + end +end + +function PANEL:SetLabel(str) + return self:SetTitle(str) +end + +--[[ + @doc + @fname DLib_Window:AddPanel + @args Panel panel, number dockMode +]] +function PANEL:AddPanel(panel, dock) + if type(panel) == 'string' then + panel = vgui.Create(panel, self) + end + + panel:Dock(dock or TOP) + return panel +end + +--[[ + @doc + @fname DLib_Window:Label + @args string text + + @returns + Panel: DLabel +]] +function PANEL:Label(text) + local panel = vgui.Create('DLabel', self) + self:AddPanel(panel) + + if text then + panel:SetText(text) + end + + panel:SizeToContents() + + return panel +end + +vgui.Register('DLib_Window', PANEL, 'DFrame') + +--[[ + @doc + @panel DLib_WindowScroll + @parent DLib_Window + + @desc + !p:DLib_Window with parented scroll panel to it + @enddesc +]] + +--[[ + @doc + @fname DLib_WindowScroll:AddPanel + @args Panel panel, number dockMode +]] +PANEL = {} +DLib.VGUI.WindowScroll = PANEL + +function PANEL:Init() + local scroll = vgui.Create('DScrollPanel', self) + self.scroll = scroll + scroll:Dock(FILL) +end + +--[[ + @doc + @fname DLib_WindowScroll:GetCanvas + + @returns + Panel +]] +function PANEL:GetCanvas() + return self.scroll:GetCanvas() +end + +--[[ + @doc + @fname DLib_WindowScroll:ParentToCanvas + @args Panel child +]] +function PANEL:ParentToCanvas(child) + return child:SetParent(self:GetCanvas()) +end + +--[[ + @doc + @fname DLib_WindowScroll:GetPadding + + @returns + number +]] +function PANEL:GetPadding() + return self.scroll:GetPadding() +end + +--[[ + @doc + @fname DLib_WindowScroll:GetVBar + + @returns + Panel +]] +function PANEL:GetVBar() + return self.scroll:GetVBar() +end + +--[[ + @doc + @fname DLib_WindowScroll:GetScrollPanel + + @returns + Panel +]] +function PANEL:GetScrollPanel() + return self.scroll +end + +--[[ + @doc + @fname DLib_WindowScroll:ScrollToChild + @args Panel child +]] +function PANEL:ScrollToChild(child) + return self.scroll:ScrollToChild(child) +end + +function PANEL:AddPanel(panel, dock) + if type(panel) == 'string' then + panel = vgui.Create(panel, self) + end + + self.scroll:AddItem(panel) + panel:Dock(dock or TOP) + return panel +end + +vgui.Register('DLib_WindowScroll', PANEL, 'DLib_Window') diff --git a/garrysmod/addons/util-dlib/lua/entities/dlib_espawner.lua b/garrysmod/addons/util-dlib/lua/entities/dlib_espawner.lua new file mode 100644 index 0000000..cf0599a --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/entities/dlib_espawner.lua @@ -0,0 +1,327 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +AddCSLuaFile() + +local PICKUP_RANGE = CreateConVar('dlib_spawner_range', '128', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Entity spawner spawn trigger range in Hu') +local RESET_TIMER = CreateConVar('dlib_spawner_timer', '10', {FCVAR_ARCHIVE, FCVAR_NOTIFY}, 'Entity spawner reset timer in seconds') + +ENT.Type = 'anim' +ENT.Author = 'DBot' +ENT.Base = 'base_entity' +ENT.PrintName = 'Enitity Spawner Base' +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.DefaultModel = 'models/items/item_item_crate.mdl' +ENT.SPAWN_HOOK_CALL = 'PlayerSpawnSENT' +ENT.SPAWNED_HOOK_CALL = 'PlayerSpawnedSENT' +ENT.IS_SPAWNER = true + +ENT.PICKUP_RANGE = PICKUP_RANGE +ENT.RESET_TIMER = RESET_TIMER + +function ENT:SetupDataTables() + self:NetworkVar('Float', 0, 'NextSpawn') + self:SetNextSpawn(0) +end + +function ENT:SpawnFunction(ply, tr, class) + if not tr.Hit then return end + + local can = hook.Run(self.SPAWN_HOOK_CALL, ply, self.CLASS) + if can == false then return end + + if not self.TABLE.eSpawnerInfo then + local newEnt = ents.Create(self.CLASS) + newEnt:SetPos(tr.HitPos) + newEnt:Spawn() + newEnt:Activate() + + local mdl = newEnt:GetModel() + local skin = newEnt:GetSkin() + local color = newEnt:GetColor() + local material = newEnt:GetMaterial() + local bg = newEnt:GetBodyGroups() + + self.TABLE.eSpawnerInfo = {} + self.TABLE.eSpawnerInfo.mdl = util.IsValidModel(mdl) and mdl or self.DefaultModel + self.TABLE.eSpawnerInfo.skin = skin + self.TABLE.eSpawnerInfo.color = color + self.TABLE.eSpawnerInfo.material = material + self.TABLE.eSpawnerInfo.bg = bg + self.TABLE.eSpawnerInfo.bginfo = {} + + if bg then + for k, v in pairs(bg) do + self.TABLE.eSpawnerInfo.bginfo[v.id] = newEnt:GetBodygroup(v.id) + end + end + + newEnt:Remove() + end + + local ent = ents.Create(class) + ent:SetPos(tr.HitPos + tr.HitNormal) + + local mdl, skin, color, material = self.TABLE.eSpawnerInfo.mdl, self.TABLE.eSpawnerInfo.skin, self.TABLE.eSpawnerInfo.color, self.TABLE.eSpawnerInfo.material + + if mdl then + ent.Model = mdl + ent:SetModel(mdl) + else + ent:SetModel(self.DefaultModel) + end + + if skin then + ent:SetSkin(skin) + end + + for k, v in pairs(self.TABLE.eSpawnerInfo.bginfo) do + ent:SetBodygroup(k, v) + end + + ent:Spawn() + ent:Activate() + + return ent +end + +local MINS, MAXS = Vector(-10, -10, 0), Vector(10, 10, 10) + +function ENT:GetIsSpawned() + return self:GetNextSpawn() < CurTimeL() +end + +function ENT:Initialize() + timer.Simple(0.3, function() + if not self:IsValid() then return end + self:PhysicsInit(SOLID_NONE) + self:SetSolid(SOLID_NONE) + self:SetMoveType(MOVETYPE_NONE) + end) + + self:DrawShadow(false) + + self.CurrentClip = 0 + + if CLIENT then + self:ClientsideEntity() + self.CurrAngle = Angle(0, 0, 0) + end +end + +function ENT:ClientsideEntity() + if IsValid(self.CModel) then self.CModel:Remove() end + + local ent = octolib.createDummy(self:GetModel()) + self.CModel = ent + ent:SetPos(self:GetPos()) + ent:Spawn() + ent:Activate() + ent:SetNoDraw(true) + + if IsValid(self.CModel2) then self.CModel2:Remove() end + + local ent1 = ent + local ent = octolib.createDummy(self:GetModel()) + local ent2 = ent + self.CModel2 = ent + ent:SetPos(self:GetPos()) + ent:Spawn() + ent:Activate() + ent:SetNoDraw(true) + + local bg = self:GetBodyGroups() + + ent1:SetSkin(self:GetSkin() or 0) + ent1:SetColor(self:GetColor()) + ent1:SetMaterial(self:GetMaterial() or '') + + ent2:SetSkin(self:GetSkin() or 0) + ent2:SetColor(self:GetColor()) + ent2:SetMaterial(self:GetMaterial() or '') + + if bg then + for k, v in pairs(bg) do + ent1:SetBodygroup(v.id, self:GetBodygroup(v.id)) + end + + for k, v in pairs(bg) do + ent2:SetBodygroup(v.id, self:GetBodygroup(v.id)) + end + end +end + +function ENT:DoSpawn(ply) + if ply.EntSpawnerCooldown and ply.EntSpawnerCooldown > CurTimeL() then return false end + + local try = hook.Run(self.SPAWN_HOOK_CALL, ply, self.CLASS) + if try == false then return false end + + if not ply:CheckLimit('sents') then + ply.EntSpawnerCooldown = CurTimeL() + 10 + return false + end + + local sfunc = self.TABLE.SpawnFunction + local ent + local hpos = self:GetPos() + Vector(0, 0, 40) + + if sfunc then + local fakeTrace = { + Hit = true, + HitPos = hpos, + StartPos = hpos, + EndPos = hpos, + HitWorld = true, + HitSky = false, + PhysicsBone = 0, + HitNoDraw = false, + HitNonWorld = false, + HitTexture = '', + HitGroup = HITGROUP_GENERIC, + MatType = MAT_GRASS, -- heh + HitNormal = Vector(0, 0, 1), + Entity = game.GetWorld() + } + + ent = sfunc(self.TABLE, ply, fakeTrace, self.CLASS) + if not ent then return end + else + ent = ents.Create(self.CLASS) + end + + ent:SetPos(hpos) + ent:Spawn() + + ent:PhysWake() + + hook.Run(self.SPAWNED_HOOK_CALL, ply, ent) + + ent:Activate() + + DoPropSpawnedEffect(ent) + + self.LastEntity = ent + self.LastPly = ply + + self:SetNextSpawn(CurTimeL() + (self.ResetTimer or RESET_TIMER:GetFloat())) + + undo.Create('SENT') + undo.AddEntity(ent) + undo.SetPlayer(ply) + + if ent.PrintName then + undo.SetCustomUndoText('Undone ' .. ent.PrintName) + end + + undo.Finish() + + return true +end + +function ENT:Think() + if CLIENT then return end + + if self:GetIsSpawned() then + local lpos = self:GetPos() + local dist = PICKUP_RANGE:GetInt() + + for k, v in ipairs(player.GetAll()) do + if v:GetPos():Distance(lpos) < dist and self:DoSpawn(v) then + break + end + end + end +end + +local debugwtite = Material('models/debug/debugwhite') +local glow = Color(0, 255, 255) + +function ENT:Draw() + if not IsValid(self.CModel) then self:ClientsideEntity() end + if not IsValid(self.CModel2) then self:ClientsideEntity() end + + local mdl = self:GetModel() + self.CModel:SetModel(mdl) + self.CModel2:SetModel(mdl) + + local ang = self.CurrAngle or Angle(0, 0, 0) + local pos = self:GetPos() + + ang.y = ang.y + FrameTime() * 33 + pos.z = pos.z + math.sin(CurTimeL() * 2) * 10 + 20 + + ang:Normalize() + + self.CModel:SetAngles(ang) + self.CModel2:SetAngles(ang) + self.CModel:SetPos(pos) + self.CModel2:SetPos(pos) + + if self:GetIsSpawned() then + self.CModel:DrawModel() + -- God how i hate this part + -- GMod functions have documented the best + self.CurrentClip = (self.CurrentClip or 0) + FrameTime() * 22 + + if self.CurrentClip > 150 then + self.CurrentClip = -150 + end + + local Vec = ang:Forward() + + local First = pos + Vec * self.CurrentClip + local Second = pos + Vec * self.CurrentClip + Vec * 5 + local dot1 = Vec:Dot(First) + local dot2 = (-Vec):Dot(Second) + + render.SuppressEngineLighting(true) + render.ModelMaterialOverride(debugwtite) + render.SetColorModulation(0, 1, 1) + render.ResetModelLighting(1, 1, 1) + + local old = render.EnableClipping(true) + render.PushCustomClipPlane(Vec, dot1) + render.PushCustomClipPlane(-Vec, dot2) + + self.CModel2:DrawModel() + + render.PopCustomClipPlane() + render.PopCustomClipPlane() + render.EnableClipping(old) + + render.SetColorModulation(1, 1, 1) + render.ModelMaterialOverride() + render.SuppressEngineLighting(false) + end + + self.CurrAngle = ang +end + +function ENT:OnRemove() + if CLIENT and IsValid(self.CModel) then + self.CModel:Remove() + end + + if CLIENT and IsValid(self.CModel2) then + self.CModel2:Remove() + end +end diff --git a/garrysmod/addons/util-dlib/lua/entities/dlib_wspawner.lua b/garrysmod/addons/util-dlib/lua/entities/dlib_wspawner.lua new file mode 100644 index 0000000..bbb935d --- /dev/null +++ b/garrysmod/addons/util-dlib/lua/entities/dlib_wspawner.lua @@ -0,0 +1,64 @@ + +-- Copyright (C) 2017-2020 DBotThePony + +-- 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. + + +AddCSLuaFile() + +local grabBaseclass = baseclass.Get('base_entity') + +ENT.Type = 'anim' +ENT.Author = 'DBot' +ENT.Base = 'dlib_espawner' +ENT.PrintName = 'Weapon Spawner Base' +ENT.RenderGroup = RENDERGROUP_BOTH +ENT.Model = 'models/items/item_item_crate.mdl' +ENT.SPAWN_HOOK_CALL = 'PlayerSpawnSWEP' +ENT.SPAWNED_HOOK_CALL = 'PlayerSpawnedSWEP' +ENT.IS_SPAWNER = true + +function ENT:SpawnFunction(ply, tr, class) + if not tr.Hit then return end + + local can = hook.Run(self.SPAWN_HOOK_CALL, ply, self.CLASS, self.TABLE) + if can == false then return end + + local ent = ents.Create(class) + ent:SetPos(tr.HitPos + tr.HitNormal * 3) + ent:SetModel(self.DefaultModel) + ent:Spawn() + ent:Activate() + + return ent +end + +function ENT:DoSpawn(ply) + if IsValid(self.LastGun) and not IsValid(self.LastGun:GetOwner()) then return false end + + local ent = ents.Create(self.CLASS) + ent:SetPos(ply:EyePos()) + ent:Spawn() + + self.LastGun = ent + self.LastPly = ply + + self:SetNextSpawn(CurTimeL() + (self.ResetTimer or self.RESET_TIMER:GetFloat())) + + return true +end diff --git a/garrysmod/addons/util-fprofiler/LICENSE b/garrysmod/addons/util-fprofiler/LICENSE new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/garrysmod/addons/util-fprofiler/README.MD b/garrysmod/addons/util-fprofiler/README.MD new file mode 100644 index 0000000..49e374d --- /dev/null +++ b/garrysmod/addons/util-fprofiler/README.MD @@ -0,0 +1,61 @@ +# Garry's mod profiler + +A profiler based on `debug.sethook` and `SysTime`. Perfect for the following things: + +- Finding performance bottlenecks (i.e. which code is spent the most time on) +- Finding out which functions are called most +- Identifying the source of lag spikes +- Profiling specific functions + +This profiler distinguishes itself from [DBugR](https://github.com/oubliette32/DBugR) in its approach. +DBugR profiles at hooks, timers and net messages specifically. FProfiler looks purely at functions outside of their context. + +I would recommend DBugR to measure networking and/or specific hooks and/or timers. I would recommend FProfiler for the things mentioned above. + +## Using FProfiler +The `FProfiler` console command opens the profiler. Everything can be done from there. + +Here's an explanation of the FProfiler menu: + +UI thing | Description +------------ | ------------- +Realm | Whether you're profiling the client or the server. Note: You need to be a SuperAdmin (or have the `FProfiler` permission in your favourite admin mod) to be allowed to do any serverside profiling! +(Re)start profiling | Starts a profiling session. If there is any previous profiling session, it starts anew, disregarding any old data. +Stop profiling | Stop an ongoing profiling session. +Continue profiling | Continue a profiling session that has previously been stopped. It will simply continue gathering data. +Profiling Focus | Focus the profiling on a specific function. Note: you **cannot** put arbitrary Lua in there, just function names! E.g. `player.GetAll` will work, but `hook.GetTable().Think.Cavity` will _not_. +Bottlenecks | Shows the functions that the game has spent its most time on. The top ones are the ones that hurt your FPS most. +Top n most expensive | Perfect for finding the cause of lag spikes. Lists the functions that took a long time on specific times they were called. Differs from the Bottlenecks tab in that Bottlenecks is about *all* the times the functions were called, this tab is about the single times they ran at their slowest. +Focus button | Sets the profiling focus to the selected function + +## Using FProfiler in code + +FProfiler has an API, a simple one too. All functions listed below are shared: +```lua +-- Starts profiling. +-- When focus is given, the profiler will only profile the focussed upon function, and the functions it calls +FProfiler.start([focus]) + +-- Stops profiling +FProfiler.stop() + +-- Continue profiling +FProfiler.continueProfiling() +``` + +All the data of the profiling sessions can be seen in the `FProfiler` menu. Because of that, there need to be **no** data retrieving functions in the API. +If you don't want to use the UI, you *probably* want the profiling in a custom format. There are some internal functions available for that. Check out `lua/fprofiler/gather.lua` and `lua/fprofiler/report`. + + +## About bottlenecks + +When faced with performance problems, people tend to dive in the code to perform micro-optimisations. Think of localising libraries to the scope of a file, storing `LocalPlayer()` in a variable for re-use and that kind of stuff. This is a naive approach and is unlikely to get you very far. +The reason for that is very simple: micro-optimisations have **very** little effect on the eventual performance of the game. They're called micro-optimisations for a reason. + +What you *should* be after is macro-optimisations, i.e. the big guys. Attacking those will give you the biggest benefit. Doubling your FPS is not uncommon when you attack the big guys. + +What do I mean by macro-optimisation/the big guys you ask? Think of reducing an O(n^2) algorithm to an O(n lg n) one. Think of things like using more efficient data structures, more efficient algorithms, caching the results of complicated calculations, alternative ways to calculate things that don't give the exact right result, but give a "good enough" result and are *way faster* than the original algorithm. **THAT** kind of shit. + +That's where the profiler comes in. Always mistrust what you **think** is a big performance hog is, **measure** it. Even the assumptions of people who have optimising code as their profession are often proven wrong by the profiler. Don't be smug and think you can do any better. + +When working on performance, the profiler is to be your guide. The profiler is to tell you what to optimise. Do not bother with anything other than the most expensive functions for you will be wasting your time. diff --git a/garrysmod/addons/util-fprofiler/lua/autorun/fprofiler.lua b/garrysmod/addons/util-fprofiler/lua/autorun/fprofiler.lua new file mode 100644 index 0000000..6fbb0a6 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/autorun/fprofiler.lua @@ -0,0 +1,38 @@ +FProfiler = {} +FProfiler.Internal = {} +FProfiler.UI = {} + +AddCSLuaFile() +AddCSLuaFile("fprofiler/cami.lua") +AddCSLuaFile("fprofiler/gather.lua") +AddCSLuaFile("fprofiler/report.lua") +AddCSLuaFile("fprofiler/util.lua") +AddCSLuaFile("fprofiler/prettyprint.lua") + +AddCSLuaFile("fprofiler/ui/model.lua") +AddCSLuaFile("fprofiler/ui/frame.lua") +AddCSLuaFile("fprofiler/ui/clientcontrol.lua") +AddCSLuaFile("fprofiler/ui/servercontrol.lua") + +include("fprofiler/cami.lua") + +CAMI.RegisterPrivilege{ + Name = "FProfiler", + MinAccess = "superadmin" +} + + +include("fprofiler/prettyprint.lua") +include("fprofiler/util.lua") +include("fprofiler/gather.lua") +include("fprofiler/report.lua") + + +if CLIENT then + include("fprofiler/ui/model.lua") + include("fprofiler/ui/frame.lua") + include("fprofiler/ui/clientcontrol.lua") + include("fprofiler/ui/servercontrol.lua") +else + include("fprofiler/ui/server.lua") +end diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/cami.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/cami.lua new file mode 100644 index 0000000..76b79c8 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/cami.lua @@ -0,0 +1,524 @@ +--[[ +CAMI - Common Admin Mod Interface. +Makes admin mods intercompatible and provides an abstract privilege interface +for third party addons. + +IMPORTANT: This is a draft script. It is very much WIP. + +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 + 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 = 20150902.1 + +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 usergroupName1 + 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. + Note: this function does NOT return an immediate result! + The result is in the callback! + + 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) + 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. + 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: + None, the answer is given in the callback function in order to allow + for the admin mod to perform e.g. a database lookup. +]] +-- 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) + hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly, + privilegeName, callback, targetPly, extraInfoTbl) +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 pairs(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 diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/gather.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/gather.lua new file mode 100644 index 0000000..06079a1 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/gather.lua @@ -0,0 +1,290 @@ +local timeMeasurementFunc = SysTime + +-- Helper function, created by some ancient Lua dev +-- Retrieves the local variables and their values of a function +local function getupvalues(f) + local t, i, k, v = {}, 1, debug.getupvalue(f, 1) + while k do + t[k] = v + i = i + 1 + k,v = debug.getupvalue(f, i) + end + return t +end + +-- Helper function +-- Get all local variables +local NIL = {} +setmetatable(NIL, {__tostring = function() return "nil" end}) +local function getlocals(level) + local i = 1 + local name, value + local vars = {} + + while true do + name, value = debug.getlocal(level, i) + + if not name then break end + + value = value == nil and NIL or value + vars[name] = value + i = i + 1 + end + + return vars +end + +--[[------------------------------------------------------------------------- +Call counts: + +registers how often function have been called +---------------------------------------------------------------------------]] +local callcounts = {} + + +-- Gets the call counts +FProfiler.Internal.getCallCounts = function() return callcounts end + + +-- Resets the call counts +function FProfiler.Internal.resetCallCounts() + callcounts = {} +end + +--[[------------------------------------------------------------------------- +Inclusive function times + +Keeps track of how long functions take in total +i.e. average time between the start and return of a function * times called + +This includes the time that any function called by this function takes +(that's what the "inclusive" refers to). +Note: recursive calls are not counted double +---------------------------------------------------------------------------]] + +local inclusiveTimes = {} + +-- Gets the inclusive times +FProfiler.Internal.getInclusiveTimes = function() return inclusiveTimes end + +-- Resets the inclusive times +function FProfiler.Internal.resetInclusiveTimes() + inclusiveTimes = {} +end + +--[[------------------------------------------------------------------------- +Top n most expensive single function calls +Keeps track of the functions that took the longest time to run +Note: functions can appear in this list at most once +---------------------------------------------------------------------------]] +local mostExpensiveSingleCalls = {} + +-- Gets most expensive single calls +FProfiler.Internal.getMostExpensiveSingleCalls = function() return mostExpensiveSingleCalls end + +-- Dictionary to make sure the same function doesn't appear multiple times +-- in the top n +local mostExpensiveSingleDict = {} + +function FProfiler.Internal.resetMostExpensiveSingleCalls() + for i = 1, 50 do mostExpensiveSingleCalls[i] = {runtime = 0} end + mostExpensiveSingleDict = {} +end + +-- Initial empty list +FProfiler.Internal.resetMostExpensiveSingleCalls() + +--[[------------------------------------------------------------------------- +Function information +Using debug.getinfo on a function object won't give you any function names +that's because functions can have multiple names. +Luckily, when the functions are called, debug.getinfo(level) gives the +function name and scope +---------------------------------------------------------------------------]] +local functionNames = {} + +FProfiler.Internal.getFunctionNames = function() return functionNames end + +--[[------------------------------------------------------------------------- +Recursion depth + +Used internally to make sure recursive functions' times aren't counted +multiple times +---------------------------------------------------------------------------]] +local recursiveCount = {} + +--[[------------------------------------------------------------------------- +Function start times + +Used internally to keep track of when functions were called +---------------------------------------------------------------------------]] +local startTimes = {} + +--[[------------------------------------------------------------------------- +Lua code event handlers +---------------------------------------------------------------------------]] + +-- The recursion depth of the function that is in focus. +-- Only applies when profiling a specific function (i.e. laying focus upon) +local focusDepth = 0 + +-- Called when a function in the code is called +local function registerFunctionCall(funcInfo) + local func = funcInfo.func + + -- Update call counts + callcounts[func] = (callcounts[func] or 0) + 1 + + -- Increase recursion depth for this function + recursiveCount[func] = (recursiveCount[func] or 0) + 1 + + -- Store function info + local funcname = funcInfo.name or "" + functionNames[func] = functionNames[func] or {} + functionNames[func][funcname] = functionNames[func][funcname] or + { namewhat = funcInfo.namewhat, + nparams = funcInfo.nparams + } + + local time = timeMeasurementFunc() + + -- Update inclusive function times, + -- only when we're on the first recursive call + if recursiveCount[func] == 1 then + startTimes[func] = time + end +end + + +-- Called when a function returns +local function registerReturn(funcInfo) + local time = timeMeasurementFunc() + local func = funcInfo.func + local runtime = time - startTimes[func] + + -- Update inclusive function time + -- Only update on the topmost call, to prevent recursive + -- calls for being counted multiple times. + if recursiveCount[func] == 1 then + inclusiveTimes[func] = (inclusiveTimes[func] or 0) + runtime + end + + -- Maintain recursion depth + recursiveCount[func] = recursiveCount[func] - 1 + + -- Update top n list + -- This path will be taken most often: the function isn't special + -- Also only counts the top recursion + if runtime <= mostExpensiveSingleCalls[50].runtime or recursiveCount[func] > 1 then return end + + -- If the function already appears in the top 10, replace it or discard the result + if mostExpensiveSingleDict[func] then + local i = mostExpensiveSingleDict[func] + + -- Discard this info + if runtime < mostExpensiveSingleCalls[i].runtime then return end + + -- Update the entry + mostExpensiveSingleCalls[i].runtime = runtime + mostExpensiveSingleCalls[i].upvalues = getupvalues(func) + mostExpensiveSingleCalls[i].locals = getlocals(5) + mostExpensiveSingleCalls[i].info = funcInfo + mostExpensiveSingleCalls[i].func = func + + -- Move the updated entry up the top 10 list if applicable + while i > 1 and runtime > mostExpensiveSingleCalls[i - 1].runtime do + mostExpensiveSingleDict[mostExpensiveSingleCalls[i - 1].func] = i + mostExpensiveSingleCalls[i - 1], mostExpensiveSingleCalls[i] = mostExpensiveSingleCalls[i], mostExpensiveSingleCalls[i - 1] + i = i - 1 + end + + mostExpensiveSingleDict[func] = i + + return + end + + -- Knowing that the function belongs in the top n, find its position + local i = 50 + while i >= 1 and runtime > mostExpensiveSingleCalls[i].runtime do + -- Update the dictionary + -- All functions faster than the current one move down the list + if not mostExpensiveSingleCalls[i].func then i = i - 1 continue end + mostExpensiveSingleDict[mostExpensiveSingleCalls[i].func] = i + 1 + + i = i - 1 + end + + -- Insert the expensive call in the top n + mostExpensiveSingleDict[func] = i + 1 + table.insert(mostExpensiveSingleCalls, i + 1, + { + func = func, + runtime = runtime, + info = funcInfo, + upvalues = getupvalues(func), + locals = getlocals(5) + }) + + + -- What was previously the 50th most expensive function + -- is now kicked out of the top 10 + if mostExpensiveSingleCalls[51].func then + mostExpensiveSingleDict[mostExpensiveSingleCalls[51].func] = nil + end + mostExpensiveSingleCalls[51] = nil +end + + +-- Called on any Lua event +local function onLuaEvent(event, focus) + local info = debug.getinfo(3) + local func = info.func + + if event == "call" or event == "tail call" then + -- Only track the focussed function and the functions + -- called by the focussed function + if focus == func then focusDepth = focusDepth + 1 end + if focus and focusDepth == 0 then return end + + registerFunctionCall(info) + else + -- Functions that return right after the call to FProfiler.Internal.start() + -- are not to be counted + if not recursiveCount[func] or recursiveCount[func] == 0 then return end + + if focus == func then focusDepth = focusDepth - 1 end + if focus and focusDepth == 0 then return end + + registerReturn(info) + end +end + +--[[------------------------------------------------------------------------- +Profiling control +---------------------------------------------------------------------------]] + +-- Start profiling +-- focus: only measure data of everything that happens within a certain function +function FProfiler.Internal.start(focus) + -- Empty start times, so unfinished functions aren't + -- registered as returns on a second profiling session + -- local time = SysTime() + -- for k,v in pairs(startTimes) do startTimes[k] = time end + table.Empty(startTimes) + table.Empty(recursiveCount) + + debug.sethook(function(event) onLuaEvent(event, focus) end, "cr") +end + + +-- Stop profiling +function FProfiler.Internal.stop() + debug.sethook() +end + +-- Reset all profiling data +function FProfiler.Internal.reset() + FProfiler.Internal.resetCallCounts() + FProfiler.Internal.resetInclusiveTimes() + FProfiler.Internal.resetMostExpensiveSingleCalls() +end diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/prettyprint.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/prettyprint.lua new file mode 100644 index 0000000..bfeeae6 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/prettyprint.lua @@ -0,0 +1,584 @@ +-- Based on MDave's thing +-- https://gist.github.com/mentlerd/d56ad9e6361f4b86af84 +if SERVER then AddCSLuaFile() end + +local type_weight = { + [TYPE_FUNCTION] = 1, + [TYPE_TABLE] = 2, +} + +local type_colors = { + [TYPE_BOOL] = Color(175, 130, 255), + [TYPE_NUMBER] = Color(175, 130, 255), + [TYPE_STRING] = Color(230, 220, 115), + [TYPE_FUNCTION] = Color(100, 220, 240) +} + +local color_neutral = Color(220, 220, 220) +local color_name = Color(260, 150, 30) + +local color_reference = Color(150, 230, 50) +local color_comment = Color( 30, 210, 30) + +-- 'nil' value +local NIL = {} + +-- Localise for faster access +local pcall = pcall + +local string_len = string.len +local string_sub = string.sub +local string_find = string.find + +local table_concat = table.concat +local table_insert = table.insert +local table_sort = table.sort + + +-- Stream interface +local gMsgF -- Print fragment +local gMsgN -- Print newline +local gMsgC -- Set print color + +local PrintLocals, gBegin, gFinish, PrintTableGrep + +do + local grep_color = Color(235, 70, 70) + + -- Grep parameters (static between gBegin/gEnd) + local grep + local grep_raw + + local grep_proximity + + + -- Current line parameters + local buffer + local colors + local markers + + local baseColor + local currColor + + local length + + -- History + local history + local remain + + + -- Actual printing + local function gCheckMatch( buffer ) + local raw = table_concat(buffer) + + return raw, string_find(raw, grep, 0, grep_raw) + end + + local function gFlushEx( raw, markers, colors, baseColor ) + + -- Print entire buffer + local len = string_len(raw) + + -- Keep track of the current line properties + local index = 1 + local marker = 1 + + local currColor = baseColor + + -- Method to print to a preset area + local function printToIndex( limit, color ) + local mark = markers and markers[marker] + + -- Print all marker areas until we would overshoot + while mark and mark < limit do + + -- Catch up to the marker + MsgC(color or currColor or color_neutral, string_sub(raw, index, mark)) + index = mark +1 + + -- Set new color + currColor = colors[marker] + + -- Select next marker + marker = marker +1 + mark = markers[marker] + + end + + -- Print the remaining between the last marker and the limit + MsgC(color or currColor or color_neutral, string_sub(raw, index, limit)) + index = limit +1 + end + + -- Grep! + local match, last = 1 + local from, to = string_find(raw, grep, 0, grep_raw) + + while from do + printToIndex(from -1) + printToIndex(to, grep_color) + + last = to +1 + from, to = string_find(raw, grep, last, grep_raw) + end + + printToIndex(len) + MsgN() + end + + + local function gCommit() + if grep_proximity then + -- Check if the line has at least one match + local raw, match = gCheckMatch(buffer) + + if match then + + -- Divide matches + if history[grep_proximity] then + MsgN("...") + end + + -- Flush history + if grep_proximity ~= 0 then + local len = #history + + for index = len -1, 1, -1 do + local entry = history[index] + history[index] = nil + + gFlushEx( entry[1], entry[2], entry[3], entry[4] ) + end + + history[len] = nil + end + + -- Flush line, allow next X lines to get printed + gFlushEx( raw, markers, colors, baseColor ) + remain = grep_proximity -1 + + history[grep_proximity +1] = nil + elseif remain > 0 then + -- Flush immediately + gFlushEx( raw, markers, colors, baseColor ) + remain = remain -1 + else + -- Store in history + table_insert(history, 1, {raw, markers, colors, baseColor}) + history[grep_proximity +1] = nil + end + else + -- Flush anyway + gFlushEx( table_concat(buffer), markers, colors, baseColor ) + end + + -- Reset state + length = 0 + buffer = {} + + markers = nil + colors = nil + + baseColor = nil + currColor = nil + end + + -- State machine + function gBegin( new, prox ) + grep = isstring(new) and new + + if grep then + grep_raw = not pcall(string_find, ' ', grep) + grep_proximity = isnumber(prox) and prox + + -- Reset everything + buffer = {} + history = {} + end + + length = 0 + remain = 0 + + baseColor = nil + currColor = nil + end + + function gFinish() + if grep_proximity and history and history[1] then + MsgN("...") + end + + -- Free memory + buffer = nil + markers = nil + colors = nil + + history = nil + end + + + function gMsgC( color ) + if grep then + + -- Try to save some memory by not immediately allocating colors + if length == 0 then + baseColor = color + return + end + + -- Record color change + if color ~= currColor then + if not markers then + markers = {} + colors = {} + end + + -- Record color change + table_insert(markers, length) + table_insert(colors, color) + end + end + + currColor = color + end + + function gMsgF( str ) + if grep then + + -- Split multiline fragments to separate ones + local fragColor = currColor or baseColor + + local last = 1 + local from, to = string_find(str, '\n') + + while from do + local frag = string_sub(str, last, from -1) + local len = from - last + + -- Merge fragment to the line + length = length + len + table_insert(buffer, frag) + + -- Print finished line + gCommit() + + -- Assign base color as previous fragColor + baseColor = fragColor + + -- Look for more + last = to +1 + from, to = string_find(str, '\n', last) + end + + -- Push last fragment + local frag = string_sub(str, last) + local len = string_len(str) - last +1 + + length = length + len + table_insert(buffer, frag) + else + -- Push immediately + MsgC(currColor or baseColor or color_neutral, str) + end + end + + function gMsgN() + -- Print everything in the buffer + if grep then + gCommit() + else + MsgN() + end + + baseColor = nil + currColor = nil + end +end + + +local function InternalPrintValue( value ) + + -- 'nil' values can also be printed + if value == NIL then + gMsgC(color_comment) + gMsgF("nil") + return + end + + local color = type_colors[ TypeID(value) ] + + -- For strings, place quotes + if isstring(value) then + if string_len(value) <= 1 then + value = string.format([['%s']], value) + else + value = string.format([["%s"]], value) + end + + gMsgC(color) + gMsgF(value) + return + end + + -- Workaround for userdata not using MetaName + if string_sub(tostring(value), 0, 8) == "userdata" then + local meta = getmetatable(value) + + if meta and meta.MetaName then + value = string.format("%s: %p", meta.MetaName, value) + end + end + + -- General print + gMsgC(color) + gMsgF(tostring(value)) + + -- For functions append source info + if isfunction(value) then + local info = debug.getinfo(value, 'S') + local aux + + if info.what == 'C' then + aux = "\t-- [C]: -1" + else + if info.linedefined ~= info.lastlinedefined then + aux = string.format("\t-- [%s]: %i-%i", info.short_src, info.linedefined, info.lastlinedefined) + else + aux = string.format("\t-- [%s]: %i", info.short_src, info.linedefined) + end + end + + gMsgC(color_comment) + gMsgF(aux) + end +end + + +-- Associated to object keys +local objID + +local function isprimitive( value ) + local id = TypeID(value) + + return id <= TYPE_FUNCTION and id ~= TYPE_TABLE +end + +local function InternalPrintTable( table, path, prefix, names, todo ) + + -- Collect keys and some info about them + local keyList = {} + local keyStr = {} + + local keyCount = 0 + + for key, value in pairs( table ) do + -- Add to key list for later sorting + table_insert(keyList, key) + + -- Describe key as string + if isprimitive(key) then + keyStr[key] = tostring(key) + else + -- Lookup already known name + local name = names[key] + + -- Assign a new unique identifier + if not name then + objID = objID +1 + name = string.format("%s: obj #%i", tostring(key), objID) + + names[key] = name + todo[key] = true + end + + -- Substitute object with name + keyStr[key] = name + end + + keyCount = keyCount +1 + end + + + -- Exit early for empty tables + if keyCount == 0 then + return + end + + + -- Determine max key length + local keyLen = 4 + + for key, str in pairs(keyStr) do + keyLen = math.max(keyLen, string.len(str)) + end + + -- Sort table keys + if keyCount > 1 then + table_sort( keyList, function( A, B ) + + -- Sort numbers numerically correct + if isnumber(A) and isnumber(B) then + return A < B + end + + -- Weight types + local wA = type_weight[ TypeID( table[A] ) ] or 0 + local wB = type_weight[ TypeID( table[B] ) ] or 0 + + if wA ~= wB then + return wA < wB + end + + -- Order by string representation + return keyStr[A] < keyStr[B] + + end ) + end + + -- Determine the next level ident + local new_prefix = string.format( "%s║%s", prefix, string.rep(' ', keyLen) ) + + -- Mark object as done + todo[table] = nil + + -- Start describing table + for index, key in ipairs(keyList) do + local value = table[key] + + -- Assign names to already described keys/values + local kName = names[key] + local vName = names[value] + + -- Decide to either fully describe, or print the value + local describe = not isprimitive(value) and ( not vName or todo[value] ) + + -- Ident + gMsgF(prefix) + + -- Fancy table guides + local moreLines = (index ~= keyCount) or describe + + if index == 1 then + gMsgF(moreLines and '╦ ' or '═ ') + else + gMsgF(moreLines and '╠ ' or '╚ ') + end + + -- Print key + local sKey = kName or keyStr[key] + + gMsgC(kName and color_reference or color_name) + gMsgF(sKey) + + -- Describe non primitives + describe = istable(value) and ( not names[value] or todo[value] ) and value ~= NIL + + -- Print key postfix + local padding = keyLen - string.len(sKey) + local postfix = string.format(describe and ":%s" or "%s = ", string.rep(' ', padding)) + + gMsgC(color_neutral) + gMsgF(postfix) + + -- Print the value + if describe then + gMsgN() + + -- Expand access path + local new_path = sKey + + if isnumber(key) or kName then + new_path = string.format("%s[%s]", path or '', key) + elseif path then + new_path = string.format("%s.%s", path, new_path) + end + + -- Name the object to mark it done + names[value] = names[value] or new_path + + -- Describe + InternalPrintTable(value, new_path, new_prefix, names, todo) + else + -- Print the value (or the reference name) + if vName and not todo[value] then + gMsgC(color_reference) + gMsgF(string.format("ref: %s",vName)) + else + InternalPrintValue(value) + end + + gMsgN() + end + end + +end + +function PrintTableGrep( table, grep, proximity ) + local base = { + [_G] = "_G", + [table] = "root" + } + + gBegin(grep, proximity) + objID = 0 + InternalPrintTable(table, nil, "", base, {}) + gFinish() +end + +function PrintLocals( level ) + local level = level or 2 + local hash = {} + + for index = 1, 255 do + local name, value = debug.getlocal(2, index) + + if not name then + break + end + + if value == nil then + value = NIL + end + + hash[name] = value + end + + PrintTableGrep( hash ) +end + +function show(...) + local n = select('#', ...) + local tbl = {...} + + for i = 1, n do + if istable(tbl[i]) then MsgN(tostring(tbl[i])) PrintTableGrep(tbl[i]) + else InternalPrintValue(tbl[i]) MsgN() end + end +end + +-- Hacky way of creating a pretty string from the above code +-- because I don't feel like refactoring the entire thing +local strResult +local toStringMsgF = function(txt) + table.insert(strResult, txt) +end + +local toStringMsgN = function() + table.insert(strResult, "\n") +end + +local toStringMsgC = function(_, txt) + table.insert(strResult, txt) +end + +function showStr(...) + local oldF, oldN, oldMsgC, oldMsgN = gMsgF, gMsgN, MsgC, MsgN + gMsgF, gMsgN, MsgC, MsgN = toStringMsgF, toStringMsgN, toStringMsgC, toStringMsgN + + strResult = {} + show(...) + + gMsgF, gMsgN, MsgC, MsgN = oldF, oldN, oldMsgC, oldMsgN + + return table.concat(strResult, "") +end diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/report.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/report.lua new file mode 100644 index 0000000..e1e4ed2 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/report.lua @@ -0,0 +1,104 @@ +local function getData() + local callCounts = FProfiler.Internal.getCallCounts() + local inclusiveTimes = FProfiler.Internal.getInclusiveTimes() + local funcNames = FProfiler.Internal.getFunctionNames() + + local data = {} + for func, called in pairs(callCounts) do + local row = {} + row.func = func + row.info = debug.getinfo(func, "nfS") + row.total_called = called + row.total_time = inclusiveTimes[func] or 0 + row.average_time = row.total_time / row.total_called + + row.name, row.namewhat = nil, nil + + row.names = {} + for name, namedata in pairs(funcNames[func]) do + table.insert(row.names, {name = name, namewhat = namedata.namewhat, nparams = namedata.nparams}) + end + + table.insert(data, row) + end + + return data +end + +local function cull(data, count) + if not count then return data end + + for i = count + 1, #data do + data[i] = nil + end + + return data +end + +--[[------------------------------------------------------------------------- +The functions that are called most often +Their implementations are O(n lg n), +which is probably suboptimal but not worth my time optimising. +---------------------------------------------------------------------------]] +function FProfiler.Internal.mostOftenCalled(count) + local sorted = getData() + + table.SortByMember(sorted, "total_called") + + return cull(sorted, count) +end + +--[[------------------------------------------------------------------------- +The functions that take the longest time in total +---------------------------------------------------------------------------]] +function FProfiler.Internal.mostTimeInclusive(count) + local sorted = getData() + + table.SortByMember(sorted, "total_time") + + return cull(sorted, count) +end + +--[[------------------------------------------------------------------------- +The functions that take the longest average time +---------------------------------------------------------------------------]] +function FProfiler.Internal.mostTimeInclusiveAverage(count) + local sorted = getData() + + table.SortByMember(sorted, "average_time") + + return cull(sorted, count) +end + +--[[------------------------------------------------------------------------- +Get the top of most often called, time inclusive and average +NOTE: This will almost definitely return more than results. +Up to three times is possible. +---------------------------------------------------------------------------]] +function FProfiler.Internal.getAggregatedResults(count) + count = count or 100 + + local dict = {} + local mostTime = FProfiler.Internal.mostTimeInclusive(count) + for i = 1, #mostTime do dict[mostTime[i].func] = true end + + local mostAvg = FProfiler.Internal.mostTimeInclusiveAverage(count) + + for i = 1, #mostAvg do + if dict[mostAvg[i].func] then continue end + dict[mostAvg[i].func] = true + table.insert(mostTime, mostAvg[i]) + end + + local mostCalled = FProfiler.Internal.mostOftenCalled(count) + + for i = 1, #mostCalled do + if dict[mostCalled[i].func] then continue end + dict[mostCalled[i].func] = true + table.insert(mostTime, mostCalled[i]) + end + + table.SortByMember(mostTime, "total_time") + + return mostTime +end diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/clientcontrol.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/clientcontrol.lua new file mode 100644 index 0000000..ffc37bf --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/clientcontrol.lua @@ -0,0 +1,100 @@ +local get, update, onUpdate = FProfiler.UI.getModelValue, FProfiler.UI.updateModel, FProfiler.UI.onModelUpdate + +--[[------------------------------------------------------------------------- +(Re)start clientside profiling +---------------------------------------------------------------------------]] +local function restartProfiling() + if get({"client", "shouldReset"}) then + FProfiler.Internal.reset() + update({"client", "recordTime"}, 0) + end + + local focus = get({"client", "focusObj"}) + + update({"client", "sessionStart"}, CurTime()) + update({"client", "sessionStartSysTime"}, SysTime()) + FProfiler.Internal.start(focus) +end + +--[[------------------------------------------------------------------------- +Stop profiling +---------------------------------------------------------------------------]] +local function stopProfiling() + FProfiler.Internal.stop() + + local newTime = get({"client", "recordTime"}) + SysTime() - (get({"client", "sessionStartSysTime"}) or 0) + + -- Get the aggregated data + local mostTime = FProfiler.Internal.getAggregatedResults(100) + + update({"client", "bottlenecks"}, mostTime) + update({"client", "topLagSpikes"}, FProfiler.Internal.getMostExpensiveSingleCalls()) + + update({"client", "recordTime"}, newTime) + update({"client", "sessionStart"}, nil) + update({"client", "sessionStartSysTime"}, nil) +end + +--[[------------------------------------------------------------------------- +Start/stop recording when the recording status is changed +---------------------------------------------------------------------------]] +onUpdate({"client", "status"}, function(new, old) + if new == old then return end + (new == "Started" and restartProfiling or stopProfiling)() +end) + +--[[------------------------------------------------------------------------- +Update the current selected focus object when data is entered +---------------------------------------------------------------------------]] +onUpdate({"client", "focusStr"}, function(new) + update({"client", "focusObj"}, FProfiler.funcNameToObj(new)) +end) + +--[[------------------------------------------------------------------------- +Update info when a different line is selected +---------------------------------------------------------------------------]] +onUpdate({"client", "currentSelected"}, function(new) + if not new or not new.info or not new.info.linedefined or not new.info.lastlinedefined or not new.info.short_src then return end + + update({"client", "sourceText"}, FProfiler.readSource(new.info.short_src, new.info.linedefined, new.info.lastlinedefined)) +end) + +--[[------------------------------------------------------------------------- +When a function is to be printed to console +---------------------------------------------------------------------------]] +onUpdate({"client", "toConsole"}, function(data) + if not data then return end + + update({"client", "toConsole"}, nil) + show(data) + + file.CreateDir("fprofiler") + file.Write("fprofiler/profiledata.txt", showStr(data)) + MsgC(Color(200, 200, 200), "-----", Color(120, 120, 255), "NOTE", Color(200, 200, 200), "---------------\n") + MsgC(Color(200, 200, 200), "If the above function does not fit in console, you can find it in data/fprofiler/profiledata.txt\n\n") +end) + +--[[------------------------------------------------------------------------- +API function: start profiling +---------------------------------------------------------------------------]] +function FProfiler.start(focus) + update({"client", "focusStr"}, tostring(focus)) + update({"client", "focusObj"}, focus) + update({"client", "shouldReset"}, true) + update({"client", "status"}, "Started") +end + +--[[------------------------------------------------------------------------- +API function: stop profiling +---------------------------------------------------------------------------]] +function FProfiler.stop() + update({"client", "status"}, "Stopped") +end + +--[[------------------------------------------------------------------------- +API function: continue profiling +---------------------------------------------------------------------------]] +function FProfiler.continueProfiling() + update({"client", "shouldReset"}, false) + update({"client", "status"}, "Started") +end diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/frame.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/frame.lua new file mode 100644 index 0000000..eaa05ed --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/frame.lua @@ -0,0 +1,437 @@ +--[[------------------------------------------------------------------------- +The panel that contains the realm switcher +---------------------------------------------------------------------------]] +local REALMPANEL = {} + +function REALMPANEL:Init() + self:DockPadding(0, 0, 0, 0) + self:DockMargin(0, 0, 5, 0) + + self.realmLabel = vgui.Create("DLabel", self) + self.realmLabel:SetDark(true) + self.realmLabel:SetText("Realm:") + + self.realmLabel:SizeToContents() + self.realmLabel:Dock(TOP) + + self.realmbox = vgui.Create("DComboBox", self) + self.realmbox:AddChoice("Client") + self.realmbox:AddChoice("Server") + self.realmbox:Dock(TOP) + + FProfiler.UI.onModelUpdate("realm", function(new) + self.realmbox.selected = new == "client" and 1 or 2 + self.realmbox:SetText(new == "client" and "Client" or "Server") + end) + + FProfiler.UI.onModelUpdate("serverAccess", function(hasAccess) + self.realmbox:SetDisabled(not hasAccess) + + if not hasAccess and self.realmbox.selected == 2 then + FProfiler.UI.updateModel("realm", "client") + end + end) + + self.realmbox.OnSelect = function(_, _, value) FProfiler.UI.updateModel("realm", string.lower(value)) end +end + +function REALMPANEL:PerformLayout() + self.realmLabel:SizeToContents() + local top = ( self:GetTall() - self.realmLabel:GetTall() - self.realmbox:GetTall()) * 0.5 + self:DockPadding(0, top, 0, 0) +end + +derma.DefineControl("FProfileRealmPanel", "", REALMPANEL, "Panel") + +--[[------------------------------------------------------------------------- +The little red or green indicator that indicates whether the focussing +function is correct +---------------------------------------------------------------------------]] +local FUNCINDICATOR = {} + +function FUNCINDICATOR:Init() + self:SetTall(5) + self.color = Color(0, 0, 0, 0) +end + +function FUNCINDICATOR:Paint() + draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.color) +end + +derma.DefineControl("FProfileFuncIndicator", "", FUNCINDICATOR, "DPanel") + +--[[------------------------------------------------------------------------- +The panel that contains the focus text entry and the focus indicator +---------------------------------------------------------------------------]] +local FOCUSPANEL = {} + +function FOCUSPANEL:Init() + self:DockPadding(0, 0, 0, 0) + self:DockMargin(0, 0, 5, 0) + + self.focusLabel = vgui.Create("DLabel", self) + self.focusLabel:SetDark(true) + self.focusLabel:SetText("Profiling Focus:") + + self.focusLabel:SizeToContents() + self.focusLabel:Dock(TOP) + + self.funcIndicator = vgui.Create("FProfileFuncIndicator", self) + self.funcIndicator:Dock(BOTTOM) + + self.focusBox = vgui.Create("DTextEntry", self) + self.focusBox:SetText("") + self.focusBox:SetWidth(150) + self.focusBox:Dock(BOTTOM) + self.focusBox:SetTooltip("Focus the profiling on a single function.\nEnter a global function name here (like player.GetAll)\nYou're not allowed to call functions in here (e.g. hook.GetTable() is not allowed)") + + function self.focusBox:OnChange() + FProfiler.UI.updateCurrentRealm("focusStr", self:GetText()) + end + + FProfiler.UI.onCurrentRealmUpdate("focusObj", function(new) + self.funcIndicator.color = FProfiler.UI.getCurrentRealmValue("focusStr") == "" and Color(0, 0, 0, 0) or new and Color(80, 255, 80, 255) or Color(255, 80, 80, 255) + end) + + FProfiler.UI.onCurrentRealmUpdate("focusStr", function(new, old) + if self.focusBox:GetText() == new then return end + + self.focusBox:SetText(tostring(new)) + end) +end + +function FOCUSPANEL:PerformLayout() + self.focusBox:SetWide(200) + self.focusLabel:SizeToContents() +end + +derma.DefineControl("FProfileFocusPanel", "", FOCUSPANEL, "Panel") + +--[[------------------------------------------------------------------------- +The timer that keeps track of for how long the profiling has been going on +---------------------------------------------------------------------------]] +local TIMERPANEL = {} + +function TIMERPANEL:Init() + self:DockPadding(0, 5, 0, 5) + self:DockMargin(5, 0, 5, 0) + + self.timeLabel = vgui.Create("DLabel", self) + self.timeLabel:SetDark(true) + self.timeLabel:SetText("Total profiling time:") + + self.timeLabel:SizeToContents() + self.timeLabel:Dock(TOP) + + self.counter = vgui.Create("DLabel", self) + self.counter:SetDark(true) + self.counter:SetText("00:00:00") + self.counter:SizeToContents() + self.counter:Dock(RIGHT) + + function self.counter:Think() + local recordTime, sessionStart = FProfiler.UI.getCurrentRealmValue("recordTime"), FProfiler.UI.getCurrentRealmValue("sessionStart") + + local totalTime = recordTime + (sessionStart and (CurTime() - sessionStart) or 0) + + self:SetText(string.FormattedTime(totalTime, "%02i:%02i:%02i")) + end +end + +function TIMERPANEL:PerformLayout() + self.timeLabel:SizeToContents() + self.counter:SizeToContents() +end + +derma.DefineControl("FProfileTimerPanel", "", TIMERPANEL, "Panel") + +--[[------------------------------------------------------------------------- +The top bar +---------------------------------------------------------------------------]] +local MAGICBAR = {} + +function MAGICBAR:Init() + self:DockPadding(5, 5, 5, 5) + self.realmpanel = vgui.Create("FProfileRealmPanel", self) + + -- (Re)Start profiling + self.restartProfiling = vgui.Create("DButton", self) + self.restartProfiling:SetText(" (Re)Start\n Profiling") + self.restartProfiling:DockMargin(0, 0, 5, 0) + self.restartProfiling:Dock(LEFT) + + self.restartProfiling.DoClick = function() + FProfiler.UI.updateCurrentRealm("shouldReset", true) + FProfiler.UI.updateCurrentRealm("status", "Started") + end + + FProfiler.UI.onCurrentRealmUpdate("status", function(new) + self.restartProfiling:SetDisabled(new == "Started") + end) + + -- Stop profiling + self.stopProfiling = vgui.Create("DButton", self) + self.stopProfiling:SetText(" Stop\n Profiling") + self.stopProfiling:DockMargin(0, 0, 5, 0) + self.stopProfiling:Dock(LEFT) + + self.stopProfiling.DoClick = function() + FProfiler.UI.updateCurrentRealm("status", "Stopped") + end + + FProfiler.UI.onCurrentRealmUpdate("status", function(new) + self.stopProfiling:SetDisabled(new == "Stopped") + end) + + -- Continue profiling + self.continueProfiling = vgui.Create("DButton", self) + self.continueProfiling:SetText(" Continue\n Profiling") + self.continueProfiling:DockMargin(0, 0, 5, 0) + self.continueProfiling:Dock(LEFT) + + self.continueProfiling.DoClick = function() + FProfiler.UI.updateCurrentRealm("shouldReset", false) + FProfiler.UI.updateCurrentRealm("status", "Started") + end + + FProfiler.UI.onCurrentRealmUpdate("status", function(new) + self.continueProfiling:SetDisabled(new == "Started") + end) + + self.realmpanel:Dock(LEFT) + + self.focuspanel = vgui.Create("FProfileFocusPanel", self) + self.focuspanel:Dock(LEFT) + + -- Timer + self.timerpanel = vgui.Create("FProfileTimerPanel", self) + self.timerpanel:Dock(RIGHT) +end + +function MAGICBAR:PerformLayout() + self.realmpanel:SizeToChildren(true, false) + self.focuspanel:SizeToChildren(true, false) + self.timerpanel:SizeToChildren(true, false) +end + + +derma.DefineControl("FProfileMagicBar", "", MAGICBAR, "DPanel") + +--[[------------------------------------------------------------------------- +The Bottlenecks tab's contents +---------------------------------------------------------------------------]] +local BOTTLENECKTAB = {} + +function BOTTLENECKTAB:Init() + self:SetMultiSelect(false) + self:AddColumn("Name") + self:AddColumn("Path") + self:AddColumn("Lines") + self:AddColumn("Amount of times called") + self:AddColumn("Total time in ms (inclusive)") + self:AddColumn("Average time in ms (inclusive)") + + FProfiler.UI.onCurrentRealmUpdate("bottlenecks", function(new) + self:Clear() + + for _, row in ipairs(new) do + local names = {} + local path = row.info.short_src + local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A" + local amountCalled = row.total_called + local totalTime = row.total_time * 100 + local avgTime = row.average_time * 100 + + for _, fname in ipairs(row.names or {}) do + if fname.namewhat == "" and fname.name == "" then continue end + table.insert(names, fname.namewhat .. " " .. fname.name) + end + + if #names == 0 then names[1] = "Unknown" end + + local line = self:AddLine(table.concat(names, "/"), path, lines, amountCalled, totalTime, avgTime) + line.data = row + end + end) + + FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old) + if new == old then return end + + for _, line in pairs(self.Lines) do + line:SetSelected(line.data.func == new.func) + end + end) +end + + +function BOTTLENECKTAB:OnRowSelected(id, line) + FProfiler.UI.updateCurrentRealm("currentSelected", line.data) +end + + +derma.DefineControl("FProfileBottleNecks", "", BOTTLENECKTAB, "DListView") + +--[[------------------------------------------------------------------------- +The Top n lag spikes tab's contents +---------------------------------------------------------------------------]] +local TOPTENTAB = {} + +function TOPTENTAB:Init() + self:SetMultiSelect(false) + self:AddColumn("Name") + self:AddColumn("Path") + self:AddColumn("Lines") + self:AddColumn("Runtime in ms") + + FProfiler.UI.onCurrentRealmUpdate("topLagSpikes", function(new) + self:Clear() + + for _, row in ipairs(new) do + if not row.func then break end + + local name = row.info.name and row.info.name ~= "" and (row.info.namewhat .. " " .. row.info.name) or "Unknown" + local path = row.info.short_src + local lines = path ~= "[C]" and row.info.linedefined .. " - " .. row.info.lastlinedefined or "N/A" + local runtime = row.runtime * 100 + + local line = self:AddLine(name, path, lines, runtime) + line.data = row + end + end) + + FProfiler.UI.onCurrentRealmUpdate("currentSelected", function(new, old) + if new == old then return end + + for _, line in pairs(self.Lines) do + line:SetSelected(line.data.func == new.func) + end + end) +end + +function TOPTENTAB:OnRowSelected(id, line) + FProfiler.UI.updateCurrentRealm("currentSelected", line.data) +end + +derma.DefineControl("FProfileTopTen", "", TOPTENTAB, "DListView") + +--[[------------------------------------------------------------------------- +The Tab panel of the bottlenecks and top n lag spikes +---------------------------------------------------------------------------]] +local RESULTSHEET = {} + +function RESULTSHEET:Init() + self:DockMargin(0, 10, 0, 0) + self:SetPadding(0) + + self.bottlenecksTab = vgui.Create("FProfileBottleNecks") + self:AddSheet("Bottlenecks", self.bottlenecksTab) + + self.toptenTab = vgui.Create("FProfileTopTen") + self:AddSheet("Top 50 most expensive function calls", self.toptenTab) + +end + + +derma.DefineControl("FProfileResultSheet", "", RESULTSHEET, "DPropertySheet") + +--[[------------------------------------------------------------------------- +The function details panel +---------------------------------------------------------------------------]] +local FUNCDETAILS = {} + +function FUNCDETAILS:Init() + self.titleLabel = vgui.Create("DLabel", self) + self.titleLabel:SetDark(true) + self.titleLabel:SetFont("DermaLarge") + self.titleLabel:SetText("Function Details") + self.titleLabel:SizeToContents() + -- self.titleLabel:Dock(TOP) + + self.focus = vgui.Create("DButton", self) + self.focus:SetText("Focus") + self.focus:SetTall(50) + self.focus:SetFont("DermaDefaultBold") + self.focus:Dock(BOTTOM) + + function self.focus:DoClick() + local sel = FProfiler.UI.getCurrentRealmValue("currentSelected") + if not sel then return end + + FProfiler.UI.updateCurrentRealm("focusStr", sel.func) + end + + self.source = vgui.Create("DTextEntry", self) + self.source:SetKeyboardInputEnabled(false) + self.source:DockMargin(0, 40, 0, 0) + self.source:SetMultiline(true) + self.source:Dock(FILL) + + FProfiler.UI.onCurrentRealmUpdate("sourceText", function(new) + self.source:SetText(string.Replace(new, "\t", " ")) + end) + + self.toConsole = vgui.Create("DButton", self) + self.toConsole:SetText("Print Details to Console") + self.toConsole:SetTall(50) + self.toConsole:SetFont("DermaDefaultBold") + self.toConsole:Dock(BOTTOM) + + function self.toConsole:DoClick() + FProfiler.UI.updateCurrentRealm("toConsole", FProfiler.UI.getCurrentRealmValue("currentSelected")) + end +end + +function FUNCDETAILS:PerformLayout() + self.titleLabel:CenterHorizontal() +end +derma.DefineControl("FProfileFuncDetails", "", FUNCDETAILS, "DPanel") + +--[[------------------------------------------------------------------------- +The actual frame +---------------------------------------------------------------------------]] +local FRAME = {} + +local frameInstance +function FRAME:Init() + self:SetTitle("FProfiler profiling tool") + self:SetSize(ScrW() * 0.8, ScrH() * 0.8) + self:Center() + self:SetVisible(true) + self:MakePopup() + self:SetDeleteOnClose(false) + + self.magicbar = vgui.Create("FProfileMagicBar", self) + self.magicbar:SetTall(math.max(self:GetTall() * 0.07, 48)) + self.magicbar:Dock(TOP) + + self.resultsheet = vgui.Create("FProfileResultSheet", self) + self.resultsheet:SetWide(self:GetWide() * 0.8) + self.resultsheet:Dock(LEFT) + + self.details = vgui.Create("FProfileFuncDetails", self) + self.details:SetWide(self:GetWide() * 0.2 - 12) + self.details:DockMargin(5, 31, 0, 0) + self.details:Dock(RIGHT) +end + +function FRAME:OnClose() + FProfiler.UI.updateModel("frameVisible", false) +end + +derma.DefineControl("FProfileFrame", "", FRAME, "DFrame") + +--[[------------------------------------------------------------------------- +The command to start the profiler +---------------------------------------------------------------------------]] +concommand.Add("FProfiler", + function() + CAMI.PlayerHasAccess(LocalPlayer(), "FProfiler", function(b, _) + if not b then return end + + frameInstance = frameInstance or vgui.Create("FProfileFrame") + frameInstance:SetVisible(true) + + FProfiler.UI.updateModel("frameVisible", true) + end) + end, + nil, "Starts FProfiler") diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/model.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/model.lua new file mode 100644 index 0000000..9586eaf --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/model.lua @@ -0,0 +1,178 @@ +--[[------------------------------------------------------------------------- +The model describes the data that the drives the UI +Loosely based on the Elm architecture +---------------------------------------------------------------------------]] + +local model = + { + realm = "client", -- "client" or "server" + serverAccess = false, -- Whether the player has access to profile the server + frameVisible = false, -- Whether the frame is visible + + client = { + status = "Stopped", -- Started or Stopped + shouldReset = true, -- Whether profiling should start anew + recordTime = 0, -- Total time spent on the last full profiling session + sessionStart = nil, -- When the last profiling session was started + sessionStartSysTime = nil, -- When the last profiling session was started, measured in SysTime + bottlenecks = {}, -- The list of bottleneck functions + topLagSpikes = {}, -- Top of lagging functions + currentSelected = nil, -- Currently selected function + + focusObj = nil, -- The current function being focussed upon in profiling + focusStr = "", -- The current function name being entered + + toConsole = nil, -- Any functions that should be printed to console + + sourceText = "", -- The text of the source function (if available) + }, + + server = { + status = "Stopped", -- Started or Stopped + shouldReset = true, -- Whether profiling should start anew + bottlenecks = {}, -- The list of bottleneck functions + recordTime = 0, -- Total time spent on the last full profiling session + sessionStart = nil, -- When the last profiling session was started + topLagSpikes = {}, -- Top of lagging functions + currentSelected = nil, -- Currently selected function + + focusObj = nil, -- The current function being focussed upon in profiling + focusStr = "", -- The current function name + + toConsole = nil, -- Any functions that should be printed to console + + sourceText = "", -- The text of the source function (if available) + fromServer = false, -- Whether a change of the model came from the server. + }, + } + + +local updaters = {} + + +--[[------------------------------------------------------------------------- +Update the model. +Automatically calls the registered update hook functions + +e.g. updating the realm would be: +FProfiler.UI.updateModel("realm", "server") +---------------------------------------------------------------------------]] +function FProfiler.UI.updateModel(path, value) + path = istable(path) and path or {path} + + local updTbl = updaters + local mdlTbl = model + local key = path[#path] + + for i = 1, #path - 1 do + mdlTbl = mdlTbl[path[i]] + updTbl = updTbl and updTbl[path[i]] + end + + local oldValue = mdlTbl[key] + mdlTbl[key] = value + + for _, updFunc in ipairs(updTbl and updTbl[key] or {}) do + updFunc(value, oldValue) + end +end + +--[[------------------------------------------------------------------------- +Update the model of the current realm +---------------------------------------------------------------------------]] +function FProfiler.UI.updateCurrentRealm(path, value) + path = istable(path) and path or {path} + + table.insert(path, 1, model.realm) + + FProfiler.UI.updateModel(path, value) +end + +--[[------------------------------------------------------------------------- +Retrieve a value of the model +---------------------------------------------------------------------------]] +function FProfiler.UI.getModelValue(path) + path = istable(path) and path or {path} + + local mdlTbl = model + local key = path[#path] + + for i = 1, #path - 1 do + mdlTbl = mdlTbl[path[i]] + end + + return mdlTbl[key] +end + +--[[------------------------------------------------------------------------- +Retrieve a value of the model regardless of realm +---------------------------------------------------------------------------]] +function FProfiler.UI.getCurrentRealmValue(path) + path = istable(path) and path or {path} + + table.insert(path, 1, model.realm) + + return FProfiler.UI.getModelValue(path) +end + +--[[------------------------------------------------------------------------- +Registers a hook that gets triggered when a certain part of the model is updated +e.g. FProfiler.UI.onModelUpdate("realm", print) prints when the realm is changed +---------------------------------------------------------------------------]] +function FProfiler.UI.onModelUpdate(path, func) + path = istable(path) and path or {path} + + local updTbl = updaters + local mdlTbl = model + local key = path[#path] + + for i = 1, #path - 1 do + mdlTbl = mdlTbl[path[i]] + updTbl[path[i]] = updTbl[path[i]] or {} + updTbl = updTbl[path[i]] + end + + updTbl[key] = updTbl[key] or {} + + table.insert(updTbl[key], func) + + -- Call update with the initial value + if mdlTbl[key] ~= nil then + func(mdlTbl[key], mdlTbl[key]) + end +end + +--[[------------------------------------------------------------------------- +Registers a hook to both realms +---------------------------------------------------------------------------]] +function FProfiler.UI.onCurrentRealmUpdate(path, func) + path = istable(path) and path or {path} + + table.insert(path, 1, "client") + FProfiler.UI.onModelUpdate(path, function(...) + if FProfiler.UI.getModelValue("realm") == "server" then return end + + func(...) + end) + + path[1] = "server" + FProfiler.UI.onModelUpdate(path, function(...) + if FProfiler.UI.getModelValue("realm") == "client" then return end + + func(...) + end) +end + +--[[------------------------------------------------------------------------- +When the realm is changed, all update functions of the new realm are to be called +---------------------------------------------------------------------------]] +FProfiler.UI.onModelUpdate("realm", function(new, old) + if not updaters[new] then return end + + for k, funcTbl in pairs(updaters[new]) do + for _, func in ipairs(funcTbl) do + func(model[new][k], model[new][k]) + end + end +end) + diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/server.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/server.lua new file mode 100644 index 0000000..d2f1fbc --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/server.lua @@ -0,0 +1,295 @@ +--[[------------------------------------------------------------------------- +The server is involved in the ui in the sense that it interacts with its model +---------------------------------------------------------------------------]] + +-- Net messages +util.AddNetworkString("FProfile_startProfiling") +util.AddNetworkString("FProfile_stopProfiling") +util.AddNetworkString("FProfile_focusObj") +-- util.AddNetworkString("FProfile_getSource") +-- util.AddNetworkString("FProfile_printFunction") +util.AddNetworkString("FProfile_fullModelUpdate") +util.AddNetworkString("FProfile_focusUpdate") +util.AddNetworkString("FProfile_unsubscribe") + +--[[------------------------------------------------------------------------- +Simplified version of the model +Contains only what the server needs to know +---------------------------------------------------------------------------]] +local model = +{ + focusObj = nil, -- the function currently in focus + sessionStart = nil, -- When the last profiling session was started. Used for the live timer. + sessionStartSysTime = nil, -- Same as sessionStart, but measured in SysTime. Used to update recordTime + recordTime = 0, -- Total time spent on the last full profiling session + bottlenecks = {}, -- The list of bottleneck functions + topLagSpikes = {}, -- Top of lagging functions + subscribers = RecipientFilter(), -- The players that get updates of the profiler model +} + +--[[------------------------------------------------------------------------- +Helper function: receive a net message +---------------------------------------------------------------------------]] +local function receive(msg, f) + net.Receive(msg, function(len, ply) + -- Check access. + CAMI.PlayerHasAccess(ply, "FProfiler", function(b, _) + if not b then return end + + f(len, ply) + end) + end) +end + +--[[------------------------------------------------------------------------- +Helper function: +Write generic row data to a net message +---------------------------------------------------------------------------]] +local function writeRowData(row) + net.WriteString(tostring(row.func)) + net.WriteString(row.info.short_src) + net.WriteUInt(row.info.linedefined, 16) + net.WriteUInt(row.info.lastlinedefined, 16) +end + +--[[------------------------------------------------------------------------- +Helper function: +Send the bottlenecks to the client +Only sends the things displayed +---------------------------------------------------------------------------]] +local function writeBottleNecks() + net.WriteUInt(#model.bottlenecks, 16) + + for i, row in ipairs(model.bottlenecks) do + writeRowData(row) + + net.WriteUInt(#row.names, 8) + + for j, name in ipairs(row.names) do + net.WriteString(name.name) + net.WriteString(name.namewhat) + end + + net.WriteUInt(row.total_called, 32) + net.WriteDouble(row.total_time) + net.WriteDouble(row.average_time) + end +end + +--[[------------------------------------------------------------------------- +Helper function: +Sends the top n functions +---------------------------------------------------------------------------]] +local function writeTopN() + local count = #model.topLagSpikes + + -- All top N f + for i = count, 0, -1 do + if model.topLagSpikes and model.topLagSpikes[i] and model.topLagSpikes[i].info then break end -- Entry exists + count = i + end + + net.WriteUInt(count, 8) + + for i = 1, count do + local row = model.topLagSpikes[i] + + if not row.info then break end + + writeRowData(row) + + net.WriteString(row.info.name or "") + net.WriteString(row.info.namewhat or "") + net.WriteDouble(row.runtime) + end +end + +-- Start profiling +local function startProfiling() + model.sessionStart = CurTime() + model.sessionStartSysTime = SysTime() + FProfiler.Internal.start(model.focusObj) + + net.Start("FProfile_startProfiling") + net.WriteDouble(model.recordTime) + net.WriteDouble(model.sessionStart) + net.Send(model.subscribers:GetPlayers()) +end + +-- Stop profiling +local function stopProfiling() + FProfiler.Internal.stop() + + model.recordTime = model.recordTime + SysTime() - (model.sessionStartSysTime or 0) + model.sessionStart = nil + model.sessionStartSysTime = nil + + model.bottlenecks = FProfiler.Internal.getAggregatedResults(100) + model.topLagSpikes = FProfiler.Internal.getMostExpensiveSingleCalls() + + net.Start("FProfile_stopProfiling") + net.WriteDouble(model.recordTime) + + writeBottleNecks() + writeTopN() + net.Send(model.subscribers:GetPlayers()) +end + + +--[[------------------------------------------------------------------------- +Receive an update of the function to focus on +---------------------------------------------------------------------------]] +receive("FProfile_focusObj", function(_, ply) + local funcStr = net.ReadString() + + model.focusObj = FProfiler.funcNameToObj(funcStr) + + net.Start("FProfile_focusObj") + net.WriteBool(model.focusObj and true or false) + net.Send(ply) + + -- Send a focus update to all other players + net.Start("FProfile_focusUpdate") + net.WriteString(funcStr) + net.WriteBool(model.focusObj and true or false) + model.subscribers:RemovePlayer(ply) + net.Send(model.subscribers:GetPlayers()) + model.subscribers:AddPlayer(ply) +end) + + +--[[------------------------------------------------------------------------- +Receive a "start profiling" signal +---------------------------------------------------------------------------]] +receive("FProfile_startProfiling", function(_, ply) + local shouldReset = net.ReadBool() + if shouldReset then + FProfiler.Internal.reset() + model.recordTime = 0 + end + + startProfiling() +end) + + +--[[------------------------------------------------------------------------- +Receive a stop profiling signal +---------------------------------------------------------------------------]] +receive("FProfile_stopProfiling", function(_, ply) + stopProfiling() +end) + + +--[[------------------------------------------------------------------------- +Send the source of a function to a client +---------------------------------------------------------------------------]] +-- receive("FProfile_getSource", function(_, ply) +-- local func = FProfiler.funcNameToObj(net.ReadString()) + +-- if not func then return end + +-- local info = debug.getinfo(func) + +-- if not info then return end + +-- net.Start("FProfile_getSource") +-- net.WriteString(FProfiler.readSource(info.short_src, info.linedefined, info.lastlinedefined) or "") +-- net.Send(ply) +-- end) + + +--[[------------------------------------------------------------------------- +Print the details of a function +---------------------------------------------------------------------------]] +-- receive("FProfile_printFunction", function(_, ply) +-- local source = net.ReadBool() -- true is from bottlenecks, false is from Top-N +-- local dataSource = source and model.bottlenecks or model.topLagSpikes +-- local func = net.ReadString() + +-- local data + +-- for _, row in ipairs(dataSource or {}) do +-- if tostring(row.func) == func then data = row break end +-- end + +-- if not data then return end + +-- -- Show the data +-- show(data) +-- local plaintext = showStr(data) + +-- -- Write to file if necessary +-- file.CreateDir("fprofiler") +-- file.Write("fprofiler/profiledata.txt", plaintext) +-- MsgC(Color(200, 200, 200), "-----", Color(120, 120, 255), "NOTE", Color(200, 200, 200), "---------------\n") +-- MsgC(Color(200, 200, 200), "If the above function does not fit in console, you can find it in data/fprofiler/profiledata.txt\n\n") + +-- -- Listen server hosts already see the server console +-- if ply:IsListenServerHost() then return end + +-- -- Send a plaintext version to the client +-- local binary = util.Compress(plaintext) + +-- net.Start("FProfile_printFunction") +-- net.WriteData(binary, #binary) +-- net.Send(ply) +-- end) + + +--[[------------------------------------------------------------------------- +Request of a full model update +Particularly useful when someone else has done (or is performing) a profiling session +and the current player wants to see the results +---------------------------------------------------------------------------]] +receive("FProfile_fullModelUpdate", function(_, ply) + -- This player is now subscribed to the updates + model.subscribers:AddPlayer(ply) + + net.Start("FProfile_fullModelUpdate") + net.WriteBool(model.focusObj ~= nil) + if model.focusObj ~= nil then net.WriteString(tostring(model.focusObj)) end + + -- Bool also indicates whether it's currently profiling + net.WriteBool(model.sessionStart ~= nil) + if model.sessionStart ~= nil then net.WriteDouble(model.sessionStart) end + + net.WriteDouble(model.recordTime) + + writeBottleNecks() + writeTopN() + + net.Send(ply) +end) + +--[[------------------------------------------------------------------------- +Unsubscribe from the updates of the profiler +---------------------------------------------------------------------------]] +receive("FProfile_unsubscribe", function(_, ply) + model.subscribers:RemovePlayer(ply) +end) + +--[[------------------------------------------------------------------------- +API function: start profiling +---------------------------------------------------------------------------]] +function FProfiler.start(focus) + FProfiler.Internal.reset() + + model.recordTime = 0 + model.focusObj = focus + + startProfiling() +end + +--[[------------------------------------------------------------------------- +API function: stop profiling +---------------------------------------------------------------------------]] +function FProfiler.stop() + stopProfiling() +end + +--[[------------------------------------------------------------------------- +API function: continue profiling +---------------------------------------------------------------------------]] +function FProfiler.continueProfiling() + startProfiling() +end diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/servercontrol.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/servercontrol.lua new file mode 100644 index 0000000..e75d2a8 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/ui/servercontrol.lua @@ -0,0 +1,228 @@ +local get, update, onUpdate = FProfiler.UI.getModelValue, FProfiler.UI.updateModel, FProfiler.UI.onModelUpdate + + +--[[------------------------------------------------------------------------- +Update the current selected focus object when data is entered +---------------------------------------------------------------------------]] +onUpdate({"server", "focusStr"}, function(new) + if not new or get({"server", "fromServer"}) then return end + + net.Start("FProfile_focusObj") + net.WriteString(new) + net.SendToServer() +end) + +net.Receive("FProfile_focusObj", function() + update({"server", "focusObj"}, net.ReadBool() and get({"server", "focusStr"}) or nil) +end) + +-- A focus update occurs when someone else changes the focus +net.Receive("FProfile_focusUpdate", function() + update({"server", "fromServer"}, true) + + local focusStr = net.ReadString() + update({"server", "focusStr"}, focusStr) + update({"server", "focusObj"}, net.ReadBool() and focusStr or nil) + + update({"server", "fromServer"}, false) +end) + +--[[------------------------------------------------------------------------- +(Re)start profiling +---------------------------------------------------------------------------]] +local function restartProfiling() + local shouldReset = get({"server", "shouldReset"}) + + net.Start("FProfile_startProfiling") + net.WriteBool(shouldReset) + net.SendToServer() +end + +net.Receive("FProfile_startProfiling", function() + update({"server", "fromServer"}, true) + update({"server", "status"}, "Started") + update({"server", "recordTime"}, net.ReadDouble()) + update({"server", "sessionStart"}, net.ReadDouble()) + update({"server", "fromServer"}, false) +end) + + +--[[------------------------------------------------------------------------- +Stop profiling +---------------------------------------------------------------------------]] +local function stopProfiling() + net.Start("FProfile_stopProfiling") + net.SendToServer() +end + +-- Read a row from a net message +local function readDataRow(countSize, readSpecific) + local res = {} + + local count = net.ReadUInt(countSize) + + for i = 1, count do + local row = {} + row.info = {} + + row.func = net.ReadString() + row.info.short_src = net.ReadString() + row.info.linedefined = net.ReadUInt(16) + row.info.lastlinedefined = net.ReadUInt(16) + + readSpecific(row) + + table.insert(res, row) + end + + return res +end + +-- Read a bottleneck row +local function readBottleneckRow(row) + local nameCount = net.ReadUInt(8) + + row.names = {} + for i = 1, nameCount do + table.insert(row.names, { + name = net.ReadString(), + namewhat = net.ReadString() + }) + end + + row.total_called = net.ReadUInt(32) + row.total_time = net.ReadDouble() + row.average_time = net.ReadDouble() +end + +-- Read the top n row +local function readTopNRow(row) + row.info.name = net.ReadString() + row.info.namewhat = net.ReadString() + row.runtime = net.ReadDouble() +end + +net.Receive("FProfile_stopProfiling", function() + update({"server", "fromServer"}, true) + update({"server", "status"}, "Stopped") + update({"server", "sessionStart"}, nil) + update({"server", "recordTime"}, net.ReadDouble()) + + update({"server", "bottlenecks"}, readDataRow(16, readBottleneckRow)) + update({"server", "topLagSpikes"}, readDataRow(8, readTopNRow)) + update({"server", "fromServer"}, false) +end) + + +--[[------------------------------------------------------------------------- +Start/stop recording when the recording status is changed +---------------------------------------------------------------------------]] +onUpdate({"server", "status"}, function(new, old) + if new == old or get({"server", "fromServer"}) then return end + (new == "Started" and restartProfiling or stopProfiling)() +end) + + +--[[------------------------------------------------------------------------- +Update info when a different line is selected +---------------------------------------------------------------------------]] +onUpdate({"server", "currentSelected"}, function(new) + if not new or not new.info or not new.info.linedefined or not new.info.lastlinedefined or not new.info.short_src then return end + + -- net.Start("FProfile_getSource") + -- net.WriteString(tostring(new.func)) + -- net.SendToServer() +end) + +net.Receive("FProfile_getSource", function() + update({"server", "sourceText"}, net.ReadString()) +end) + + +--[[------------------------------------------------------------------------- +When a function is to be printed to console +---------------------------------------------------------------------------]] +onUpdate({"server", "toConsole"}, function(data) + if not data then return end + + update({"server", "toConsole"}, nil) + + -- net.Start("FProfile_printFunction") + -- net.WriteBool(data.total_called and true or false) -- true for bottleneck function, false for top-n function + -- net.WriteString(tostring(data.func)) + -- net.SendToServer() +end) + +net.Receive("FProfile_printFunction", function(len) + local data = net.ReadData(len) + local decompressed = util.Decompress(data) + + -- Print the text line by line, otherwise big parts of big data will not be printed + local split = string.Explode("\n", decompressed, false) + for _, line in ipairs(split) do + MsgN(line) + end + + -- Write the thing to a file + file.CreateDir("fprofiler") + file.Write("fprofiler/profiledata.txt", showStr(data)) + MsgC(Color(200, 200, 200), "-----", Color(120, 120, 255), "NOTE", Color(200, 200, 200), "---------------\n") + MsgC(Color(200, 200, 200), "In the server's console you can find a colour coded version of the above output.\nIf the above function does not fit in console, you can find it in data/fprofiler/profiledata.txt\n\n") +end) + + +--[[------------------------------------------------------------------------- +Check access when the frame opens +Also request a full serverside model update +---------------------------------------------------------------------------]] +onUpdate("frameVisible", function(isOpen) + -- Don't network if the server doesn't have FProfiler installed + if util.NetworkStringToID("FProfile_fullModelUpdate") == 0 then + update("serverAccess", false) + return + end + + -- Update access + CAMI.PlayerHasAccess(LocalPlayer(), "FProfiler", function(b, _) + update("serverAccess", b) + end) + + if not isOpen then + net.Start("FProfile_unsubscribe") + net.SendToServer() + + return + end + + net.Start("FProfile_fullModelUpdate") + net.SendToServer() +end) + + +net.Receive("FProfile_fullModelUpdate", function() + update({"server", "fromServer"}, true) + + local focusExists = net.ReadBool() + if focusExists then + local focus = net.ReadString() + update({"server", "focusObj"}, focus) + update({"server", "focusStr"}, focus) + end + + local startingTimeExists = net.ReadBool() + + if startingTimeExists then + update({"server", "status"}, "Started") + update({"server", "sessionStart"}, net.ReadDouble()) + else + update({"server", "status"}, "Stopped") + end + + update({"server", "recordTime"}, net.ReadDouble()) + + update({"server", "bottlenecks"}, readDataRow(16, readBottleneckRow)) + update({"server", "topLagSpikes"}, readDataRow(8, readTopNRow)) + + update({"server", "fromServer"}, false) +end) + diff --git a/garrysmod/addons/util-fprofiler/lua/fprofiler/util.lua b/garrysmod/addons/util-fprofiler/lua/fprofiler/util.lua new file mode 100644 index 0000000..c128c25 --- /dev/null +++ b/garrysmod/addons/util-fprofiler/lua/fprofiler/util.lua @@ -0,0 +1,42 @@ + +-- Try to find the function represented by a string +function FProfiler.funcNameToObj(str) + if isfunction(str) then return str end + + local times = FProfiler.Internal.getCallCounts() + for func, _ in pairs(times) do + if tostring(func) == str then return func end + end + + local tbl = _G + local exploded = string.Explode(".", str, false) + if not exploded or not exploded[1] then return end + + for i = 1, #exploded - 1 do + tbl = (tbl or {})[exploded[i]] + if not istable(tbl) then return end + end + + local func = (tbl or {})[exploded[#exploded]] + + if not isfunction(func) then return end + + return func +end + +-- Read a file +function FProfiler.readSource(fname, startLine, endLine) + if not file.Exists(fname, "GAME") then return "" end + if startLine < 0 or endLine < 0 or endLine < startLine then return "" end + + local f = file.Open(fname, "r", "GAME") + + for i = 1, startLine - 1 do f:ReadLine() end + + local res = {} + for i = startLine, endLine do + table.insert(res, f:ReadLine() or "") + end + + return table.concat(res, "\n") +end diff --git a/garrysmod/addons/util-gac/lua/autorun/glorifiedanticheat.lua b/garrysmod/addons/util-gac/lua/autorun/glorifiedanticheat.lua new file mode 100644 index 0000000..6b86eda --- /dev/null +++ b/garrysmod/addons/util-gac/lua/autorun/glorifiedanticheat.lua @@ -0,0 +1,122 @@ + +gAC = gAC or { + config = {}, + storage = {}, + + IDENTIFIER = "g-AC", + NICE_NAME = "g-AC", + + Version = "2.1.5", + Debug = false +} + +local _AddCSLuaFile = AddCSLuaFile +local _file_Exists = file.Exists +local _file_Find = file.Find +local _hook_Add = hook.Add +local _hook_Run = hook.Run +local _include = include +local _print = print + +local version = 1 + +local frile = { + VERSION = version, + + STATE_SERVER = 0, + STATE_CLIENT = 1, + STATE_SHARED = 2 +} + +function frile.includeFile( filename, state ) + if state == frile.STATE_SHARED or filename:find( "sh_" ) then + if SERVER then _AddCSLuaFile( filename ) end + _include( filename ) + elseif state == frile.STATE_SERVER or SERVER and filename:find( "sv_" ) then + _include( filename ) + elseif state == frile.STATE_CLIENT or filename:find( "cl_" ) then + if SERVER then _AddCSLuaFile( filename ) + else _include( filename ) end + end +end + +function frile.includeFolder( currentFolder, ignoreFilesInFolder, ignoreFoldersInFolder ) + if _file_Exists( currentFolder .. "sh_frile.lua", "LUA" ) then + frile.includeFile( currentFolder .. "sh_frile.lua" ) + + return + end + + local files, folders = _file_Find( currentFolder .. "*", "LUA" ) + + if not ignoreFilesInFolder then + for _=1, #files do + local File = files[_] + frile.includeFile( currentFolder .. File ) + end + end + + if not ignoreFoldersInFolder then + for _=1, #folders do + local folder = folders[_] + frile.includeFolder( currentFolder .. folder .. "/" ) + end + end +end + +function gAC.Print( txt ) + _print( gAC.NICE_NAME .. " > " .. txt ) +end + +function gAC.DBGPrint( txt ) + if !gAC.Debug then return end + _print( gAC.NICE_NAME .. " [DBG] > " .. txt ) +end + +-- Do not adjust the load order. You must first load the libraries, followed by the module and last the languages. +frile.includeFolder( "glorifiedanticheat/", false, true ) + +if SERVER then + _hook_Add("gAC.NetworkInit", "gAC.LoadFiles", function() + function frile.includeFile( filename, state ) + if state == frile.STATE_SHARED or filename:find( "sh_" ) then + gAC.AddQuery( filename ) + _include( filename ) + elseif state == frile.STATE_SERVER or SERVER and filename:find( "sv_" ) then + _include( filename ) + elseif state == frile.STATE_CLIENT or filename:find( "cl_" ) then + gAC.AddQuery( filename ) + end + end + frile.includeFolder( "glorifiedanticheat/modules/detectionsys" ) + _hook_Run("gAC.IncludesLoaded") + end) +end + +if SERVER then + frile.includeFile( "gacstorage/sv_gac_init.lua", frile.STATE_SERVER ) + frile.includeFile( "gacnetwork/sv_query.lua", frile.STATE_SERVER ) + function frile.includeFile( filename, state ) + if state == frile.STATE_SHARED or filename:find( "sh_" ) then + gAC.AddQuery( filename ) + _include( filename ) + elseif state == frile.STATE_SERVER or SERVER and filename:find( "sv_" ) then + _include( filename ) + elseif state == frile.STATE_CLIENT or filename:find( "cl_" ) then + gAC.AddQuery( filename ) + end + end + frile.includeFolder( "glorifiedanticheat/modules/" ) + function frile.includeFile( filename, state ) + if state == frile.STATE_SHARED or filename:find( "sh_" ) then + if SERVER then _AddCSLuaFile( filename ) end + _include( filename ) + elseif state == frile.STATE_SERVER or SERVER and filename:find( "sv_" ) then + _include( filename ) + elseif state == frile.STATE_CLIENT or filename:find( "cl_" ) then + if SERVER then _AddCSLuaFile( filename ) end + end + end + _hook_Run("gAC.Init") + frile.includeFile( "gacnetwork/sv_networking.lua", frile.STATE_SERVER ) +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/d3a/commands/cheatlog.lua b/garrysmod/addons/util-gac/lua/d3a/commands/cheatlog.lua new file mode 100644 index 0000000..487c942 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/d3a/commands/cheatlog.lua @@ -0,0 +1,103 @@ +COMMAND.Name = "Cheatlog" + +COMMAND.Flag = "T" +COMMAND.AdminMode = false +COMMAND.CheckRankWeight = false + +COMMAND.Args = {{"player", "Name/SteamID"}} + +COMMAND.CheckArgs = function(pl, cmd, args) + local margs = cmd.Args + local err + local supp = {} + + if (pl:IsPlayer() and !pl:HasAccess(cmd.Flag)) then + err = "'" .. cmd.Flag .. "' access required!" + end + + if (pl:IsPlayer() and cmd.AdminMode and !pl:GetDataVar("adminmode")) then + err = "Adminmode required!" + end + + if (!err) then + for k, v in pairs(margs) do + if (!args[k]) then + err = "_" + break + end + + if (v[1] == "number") then + if (tostring(tonumber(args[k])) != args[k]) then + err = "_" + break + else + table.insert(supp, tonumber(args[k])) + end + elseif (v[1] == "player") then + if args[k] == "@" then + local targ = D3A.Commands.getPicker( pl ) + if targ then + table.insert(supp, targ) + else err = "Couldn't find anyone." end + elseif args[k] == "^" then + table.insert(supp, pl) + else + local targ = D3A.FindPlayer(args[k]) + if (targ) then + table.insert(supp, targ) + elseif (!targ and string.sub(args[k], 1, 8) == "STEAM_0:") then + table.insert(supp, args[k]) + else + err = "Unknown player/steamid " .. args[k] .. "." + break + end + + end + elseif (v[1] == "string") then + args[k] = tostring(args[k]) + end + end + end + + if (err) then + if (err == "_") then + err = "Usage: " .. cmd.Name .. " " + for k, v in pairs(margs) do + err = err .. v[1] .. ":" .. v[2] .. " " + end + end + D3A.Chat.SendToPlayer(pl, err, "ERR") + return false + end + return supp +end + +COMMAND.Run = function(pl, args, supp) + + local targstid, nameid + + if (isstring(supp[1])) then + targstid = supp[1] + nameid = targstid + else + targstid = supp[1]:SteamID() + nameid = supp[1]:NameID() + end + + gAC.GetLog( targstid, function(data) + if isstring(data) then + gAC.ClientMessage( pl, data, Color( 225, 150, 25 ) ) + else + if data == {} or data == nil then + gAC.ClientMessage( pl, nameid .. " has no detections.", Color( 0, 255, 0 ) ) + else + gAC.PrintMessage(pl, HUD_PRINTCONSOLE, "\n\n") + gAC.PrintMessage(pl, HUD_PRINTCONSOLE, "Detection Log for " .. nameid .. "\n") + for k, v in pairs(data) do + gAC.PrintMessage(pl, HUD_PRINTCONSOLE, os.date( "[%H:%M:%S %p - %d/%m/%Y]", v["time"] ) .. " - " .. v["detection"] .. "\n") + end + gAC.ClientMessage( pl, "Look in console.", Color( 0, 255, 0 ) ) + end + end + end) +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacnetwork/sv_networking.lua b/garrysmod/addons/util-gac/lua/gacnetwork/sv_networking.lua new file mode 100644 index 0000000..5d3526f --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacnetwork/sv_networking.lua @@ -0,0 +1,2776 @@ +local _string_char = string.char +local _util_CRC = util.CRC +local _net_WriteData = net.WriteData +local _net_ReadUInt = net.ReadUInt +local _player_GetHumans = player.GetHumans +local _util_Compress = util.Compress +local _math_Round = math.Round +local _string_match = string.match +local _string_gsub = string.gsub +local _string_sub = string.sub +local _math_ceil = math.ceil +local _tonumber = tonumber +local _util_Decompress = util.Decompress +local _net_Send = (SERVER and net.Send or nil) +local _math_random = math.random +local _net_ReadBool = net.ReadBool +local _util_TableToJSON = util.TableToJSON +local _table_remove = table.remove +local _net_WriteBool = net.WriteBool +local _string_Explode = string.Explode +local _string_byte = string.byte +local _string_format = string.format +local _util_JSONToTable = util.JSONToTable +local _string_rep = string.rep +local _net_Start = net.Start +local _hook_Add = hook.Add +local _net_BytesWritten = net.BytesWritten +local _IsValid = IsValid +local _net_Receive = net.Receive +local _player_GetBySteamID64 = player.GetBySteamID64 +local _timer_Simple = timer.Simple +local _hook_Run = hook.Run +local _net_ReadData = net.ReadData +local _net_WriteUInt = net.WriteUInt +local _util_AddNetworkString = (SERVER and util.AddNetworkString or nil) +local _math_randomseed = math.randomseed +local _SysTime = SysTime + +--[[ + GM-LUAI Networking + +local args = {...} +local _1, _2, _3, _4, _5, _6, _7, _8, _10, _11, _32 = 1,2,3,4,5,6,7,8,10,11,32 +local CompileCode = args[_2] +local RunCode = args[_3] +args = args[_1] +_G[args[_5] ] = {} +local function gAC_Send(channelName, data) + data = util.Compress(data) + net.Start(args[_3]) + net.WriteUInt (tonumber(util.CRC (channelName .. args[_4])), _32) + net.WriteData (data, #data) + net.WriteBool (false) + net.SendToServer() +end +local function gAC_GetHandler(channelName) + return _G[args[_5] ][tonumber(util.CRC(channelName .. args[_4]))] +end +local StreamID, ASTToServer = 0, {} +local function gAC_Stream(channelName, data, split) + local channelId = tonumber(util.CRC(channelName .. args[_4])) + local data_compress = util.Compress(data) + local data_size = #data_compress + split = (split == nil and 10000 or split) + local parts = math.ceil( data_size / split ) + if parts == 1 then + gAC_Send(channelName, data) + return + end + StreamID = StreamID + 1 + local ID = '#' .. StreamID + local AstToServer = { + ['Channel'] = channelId, + ['Parts'] = {} + } + for i=1, parts do + local min + local max + if i == 1 then + min = i + max = split + elseif i > 1 and i ~= parts then + min = ( i - 1 ) * split + 1 + max = min + split - 1 + elseif i > 1 and i == parts then + min = ( i - 1 ) * split + 1 + max = data_size + end + local data = string.sub( data_compress, min, max ) + if i < parts && i > 1 then + AstToServer['Parts'][#AstToServer['Parts'] + 1] = { + ['ID'] = ID, + ['Type'] = 3, + ['Data'] = data + } + else + if i == 1 then + AstToServer['Parts'][#AstToServer['Parts'] + 1] = { + ['ID'] = ID, + ['Type'] = 1, + ['Data'] = data + } + end + if i == parts then + AstToServer['Parts'][#AstToServer['Parts'] + 1] = { + ['ID'] = ID, + ['Type'] = 2, + ['Data'] = data + } + end + end + end + local streamdata = util.TableToJSON(AstToServer['Parts'][1]) + table.remove(AstToServer['Parts'], 1) + net.Start(args[_3]) + net.WriteUInt (channelId, 32) + net.WriteData (streamdata, #streamdata) + net.WriteBool(true) + net.SendToServer() + ASTToServer[ID] = AstToServer +end +local function gAC_AddReceiver (channelName, handler) + _G[args[_5] ][tonumber(util.CRC (channelName .. args[_4]))] = handler +end +local AST = {} +local function HandleMessage (bit) + local channelId = net.ReadUInt (_32) + local handler = _G[args[_5] ][channelId] + if not handler then return end + local data = net.ReadData (bit / _8 - _4) + local isstream = net.ReadBool() + if isstream then + data = util.JSONToTable(data) + if data['Type'] == 1 then + AST[data['ID'] ] = data['Data'] + gAC_Send('gAC.StreamResponse', data['ID']) + elseif data['Type'] == 2 then + local _data = AST[data['ID'] ] .. data['Data'] + handler (util.Decompress(_data)) + AST[data['ID'] ] = nil + elseif data['Type'] == 3 then + AST[data['ID'] ] = AST[data['ID'] ] .. data['Data'] + gAC_Send('gAC.StreamResponse', data['ID']) + end + else + handler (util.Decompress(data)) + end +end +gAC_AddReceiver("LoadString", function(data) + RunCode(data, args[_7] .. "gAC.LoadString-" .. #data) +end) +gAC_AddReceiver("LoadPayload", function(data) + local includer = "local gAC_Net = {...} local gAC_Send = gAC_Net[1] local gAC_Stream = gAC_Net[2] local gAC_AddReceiver = gAC_Net[3] local gAC_GetHandler = gAC_Net[4]\n" + local func = CompileCode(includer .. data, args[_7] .. args[_10] .. #data) + func(gAC_Send, gAC_Stream, gAC_AddReceiver, gAC_GetHandler) +end) +gAC_AddReceiver("gAC.StreamResponse", function(data) + local AstToServer = ASTToServer[data] + if AstToServer then + local streamdata = _util_TableToJSON(AstToServer['Parts'][1]) + table.remove(AstToServer['Parts'], 1) + net.Start(args[_3]) + net.WriteUInt (AstToServer['Channel'], 32) + net.WriteData (streamdata, #streamdata) + net.WriteBool(true) + net.SendToServer() + if #AstToServer['Parts'] < 1 then + ASTToServer[data] = nil + end + end +end) +net.Receive (args[_3],function(bit) HandleMessage(bit) end) +gAC_Send('g-AC_PayloadVerification', '') +return gAC_Send, gAC_Stream, gAC_AddReceiver, gAC_GetHandler + +--Client cl_receivers.lua +local _CompileString = CompileString +local _net_Receive = net.Receive +local _util_Decompress = util.Decompress +local _RunString = RunString +local _hook_Add = hook.Add +local _net_Start = net.Start +local _net_SendToServer = (CLIENT and net.SendToServer or nil) +local _string_Explode = string.Explode +local _net_ReadData = net.ReadData +local _util_JSONToTable = util.JSONToTable +_net_Receive("gAC.PlayerInit", function(len) + local codec = _string_Explode("[EXLD]", _net_ReadData(len)) + for i=1, #codec do + if i == #codec then + codec[i] = codec[i]:sub(1, codec[i]:len()-2) + end + codec[i] = _util_Decompress(codec[i]) + end + local func = _CompileString( codec[1], codec[2] ) + func(codec, _CompileString, _RunString) +end) +_hook_Add('InitPostEntity', 'gAC.Payloads', function() + _net_Start('gAC.PlayerInit') + _net_SendToServer() +end) +]] + +if gAC.Network and gAC.Network.ReceiveCount then return end --prevent lua refresh + +gAC = gAC or {} +--[[ + NiceCream's encoder library, making script hidden from reality. + My goals atleast: intense encoder, low performance decoder +]] + +gAC.Encoder = {} + +gAC.Encoder.Unicode_String = "‪" + +--[[ + String Randomizer + Generate randomize string including a Unicode character +]] +function gAC.Encoder.stringrandom(length) + local str = "" + for i = 1, length do + local typo = _math_Round(_math_random(1, 4)) + if typo == 1 then + str = str.. _string_char(_math_random(97, 122)) + elseif typo == 2 then + str = str.. _string_char(_math_random(65, 90)) + elseif typo == 3 then + str = str.. _string_char(_math_random(49, 57)) + end + end + return str +end + +--[[ + Key String to Key Float + Converts a table key into a table of values for encoders/decoders +]] + +function gAC.Encoder.KeyToFloat(s) + local z = {} + for i = 1, #s do + local key = _string_Explode("", s[i]) + z[i] = 0 + for v = 1, #key do + z[i] = z[i] + _string_byte(key[v]) + end + end + if z[i] == 0 or z[i] == 255 then + z[i] = z[i] + _math_Round(_math_random(1, 10)) + end + return z +end + +--[[ + String to Hex +]] + +function gAC.Encoder.ToHex(str) + local byte = '' + for i = 1, #str do + byte = byte .. '\\x' .. _string_format('%02X', _string_byte(str:sub(i, i))) + end + return byte +end + +--[[ + Table to String +]] + +function gAC.Encoder.Tabletostring(tbl) + local str = "{" + local len = #tbl + for i = 1, len do + local v = tbl[i] + if v .. '' ~= v then + str = str .. v .. (i ~= len and ',' or '') + else + str = str .. "'" .. v .. "'" .. (i ~= len and ',' or '') + end + end + str = str .. '}' + return str +end + +--[[ + Encoder + General purpose of encoding string into unreadable format. + Just cause someone tried to look into my creations. +]] + +local function floor(number) + return number - (number % 1) +end + +local function bxor (a,b,c) + local r = 0 + for i = 0, 31 do + local x = (a * .5) + (b * .5) + (c * .5) + if x ~= floor (x) then + r = r + 2^i + end + a = floor (a * .5) + b = floor (b * .5) + c = floor (c * .5) + end + return r +end + +local CharacterForEscape = {['b'] = '\b', ['x'] = true, ['f'] = '\f', ['v'] = '\v', ['0'] = '\0', ['r'] = '\r', ['n'] = '\n', ['t'] = '\t', ['"'] = '"', ["'"] = "'", ['\\'] = '\\'} + +local _isnumber = isnumber +function gAC.Encoder.Encode(data, key) + local function peek(n) + return (data:sub(n, n) or '') + end + key = gAC.Encoder.KeyToFloat(key) + local encode, key_dir, key_len, data_len, skips = {}, 0, #key, #data, 0 + for i = 1, data_len do + key_dir = key_dir + 1 + local CanContinue = true + if peek(i) == '\\' then + if _isnumber(_tonumber(peek(i + 1))) then + skips = skips + 1 + local id = #encode + 1 + encode[id] = '\\' .. peek(i + 1) + for v=1, 2 do + if _isnumber(_tonumber(peek(i + 1 + v))) then + skips = skips + 1 + encode[id] = encode[id] .. peek(i + 1 + v) + end + end + CanContinue = false + elseif peek(i + 1) == 'x' and peek(i + 2) ~= '' then + skips = skips + 3 + encode[#encode + 1] = '\\' .. peek(i + 1) .. peek(i + 2) .. peek(i + 3) + CanContinue = false + elseif CharacterForEscape[peek(i + 1)] and peek(i + 1) ~= 'x' then + skips = skips + 1 + encode[#encode + 1] = '\\' .. peek(i + 1) + CanContinue = false + end + end + if CanContinue then + if skips > 0 then + skips = skips - 1 + encode[#encode + 1] = '' + CanContinue = false + end + if CanContinue then + --encode[#encode + 1] = bxor(_string_byte(data:sub(i, i)), key[key_dir] % 255, (data_len * key_len) % 255) + encode[#encode + 1] = (_string_byte(data:sub(i, i)) * (key[key_dir]) * ((data_len * key_len))) + end + end + if key_dir == key_len then + key_dir = 0 + end + end + return gAC.Encoder.Tabletostring(encode) +end + +--[[ + Decoder function + Used on the client-side realm, simply decodes string into readable format for lua to use. + +function(data) + local key = __EXTK + local decode, key_dir, data_len, key_len = '', 0, #data, #key + for i = 1, data_len do + key_dir = key_dir + 1 + local v = data[i] + if v .. '' ~= v then + --decode = decode .. __CHAR( __XOR(v, key[key_dir] % 255, (data_len * key_len) % 255) ) + decode = decode .. __CHAR( v/((data_len * key_len) % 255)/(key[key_dir] % 255) ) + else + decode = decode .. v + end + if key_dir == key_len then + key_dir = 0 + end + end + return decode +end +]] +gAC.Encoder.Decoder_Func = [[(function(_lvar1)local +_lvar2=__EXTK +local +_lvar3,_lvar4,_lvar5,_lvar6='',0,#_lvar1,#_lvar2 +for +_lvar7=1,_lvar5 +do +_lvar4=_lvar4+1 +local +_lvar8=_lvar1[_lvar7]if +_lvar8..''~=_lvar8 +then +_lvar3=_lvar3..__CHAR(_lvar8/((_lvar5*_lvar6))/(_lvar2[_lvar4]))else +_lvar3=_lvar3.._lvar8 +end +if +_lvar4==_lvar6 +then +_lvar4=0 +end +end +return +_lvar3 +end)]] + +gAC.Network = gAC.Network or {} +gAC.Network.ReceiveCount = 0 +gAC.Network.SendCount = 0 +gAC.Network.AST = {} +gAC.Network.ASTToClient = {} + +gAC.Network.GlobalChannel = gAC.Encoder.stringrandom(_math_Round(_math_random(6, 12))) .. "gAC" .. gAC.Encoder.stringrandom(_math_Round(_math_random(6, 12))) +gAC.Network.Channel_Rand = gAC.Encoder.stringrandom(_math_Round(_math_random(4, 22))) .. "gAC" +gAC.Network.Channel_Glob = gAC.Encoder.stringrandom(_math_Round(_math_random(6, 12))) .. "gAC" .. gAC.Encoder.stringrandom(_math_Round(_math_random(6, 12))) +gAC.Network.Verify_Hook = gAC.Encoder.stringrandom(_math_Round(_math_random(6, 12))) .. "gAC" .. gAC.Encoder.stringrandom(_math_Round(_math_random(6, 12))) + +--Global Decoder, NiceCream got pissed +gAC.Network.Global_Decoder = {} +for i=1, _math_Round(_math_random(6,8)) do + gAC.Network.Global_Decoder[i] = gAC.Encoder.stringrandom(_math_Round(_math_random(4, 8))) +end +local Rand_StrFunc = _math_Round(_math_random(1, 2)) +gAC.Network.Decoder_Var = {"string.lower", "string.upper", "string.Left", "string.Right", "string.rep", "string.reverse", "string.len", "string.byte", +"gcinfo", "jit.status", "util.NetworkIDToString", "GetGlobalInt", "GetGlobalFloat", "GetGlobalString"} +gAC.Network.Decoder_Var = gAC.Network.Decoder_Var[_math_Round(_math_random(1, #gAC.Network.Decoder_Var))] +gAC.Network.Decoder_VarName = gAC.Network.Decoder_Var +gAC.Network.Decoder_Verify = "GAC_" .. gAC.Encoder.stringrandom(_math_Round(_math_random(9, 14))) .. "_" +gAC.Network.Decoder_Get = _string_rep(gAC.Encoder.Unicode_String,_math_Round(_math_random(5, 12))) +gAC.Network.Decoder_Undo = _string_rep(gAC.Encoder.Unicode_String,_math_Round(_math_random(15, 19))) + +local function PerformG(str) + local tbl = _string_Explode(".", str) + local unloadervar = "['" + for k=1, #tbl do + local v = tbl[k] + if tbl[k + 1] then + unloadervar = unloadervar .. gAC.Encoder.ToHex(v) .. "']['" + else + unloadervar = unloadervar .. gAC.Encoder.ToHex(v) .. "']" + end + end + return unloadervar +end +gAC.Network.Decoder_Var = PerformG(gAC.Network.Decoder_Var) + +local Payload_001 = [[--]] .. gAC.Encoder.stringrandom(_math_Round(_math_random(15, 20))) .. [[ + +local +__CHAR,__FLOOR,__XOR +__CHAR=function()local +‪‪={[1]="\1",[2]="\2",[3]="\3",[4]="\4",[5]="\5",[6]="\6",[7]="\7",[8]="\b",[9]="\t",[10]="\n",[11]="\v",[12]="\f",[13]="\r",[14]="\14",[15]="\15",[16]="\16",[17]="\17",[18]="\18",[19]="\19",[20]="\20",[21]="\21",[22]="\22",[23]="\23",[24]="\24",[25]="\25",[26]="\26",[27]="\27",[28]="\28",[29]="\29",[30]="\30",[31]="\31",[32]="\32",[33]="\33",[34]="\"",[35]="\35",[36]="\36",[37]="\37",[38]="\38",[39]="\'",[40]="\40",[41]="\41",[42]="\42",[43]="\43",[44]="\44",[45]="\45",[46]="\46",[47]="\47",[48]="\48",[49]="\49",[50]="\50",[51]="\51",[52]="\52",[53]="\53",[54]="\54",[55]="\55",[56]="\56",[57]="\57",[58]="\58",[59]="\59",[60]="\60",[61]="\61",[62]="\62",[63]="\63",[64]="\64",[65]="\65",[66]="\66",[67]="\67",[68]="\68",[69]="\69",[70]="\70",[71]="\71",[72]="\72",[73]="\73",[74]="\74",[75]="\75",[76]="\76",[77]="\77",[78]="\78",[79]="\79",[80]="\80",[81]="\81",[82]="\82",[83]="\83",[84]="\84",[85]="\85",[86]="\86",[87]="\87",[88]="\88",[89]="\89",[90]="\90",[91]="\91",[92]="\92",[93]="\93",[94]="\94",[95]="\95",[96]="\96",[97]="\97",[98]="\98",[99]="\99",[100]="\100",[101]="\101",[102]="\102",[103]="\103",[104]="\104",[105]="\105",[106]="\106",[107]="\107",[108]="\108",[109]="\109",[110]="\110",[111]="\111",[112]="\112",[113]="\113",[114]="\114",[115]="\115",[116]="\116",[117]="\117",[118]="\118",[119]="\119",[120]="\120",[121]="\121",[122]="\122",[123]="\123",[124]="\124",[125]="\125",[126]="\126",[127]="\127",[128]="\128",[129]="\129",[130]="\130",[131]="\131",[132]="\132",[133]="\133",[134]="\134",[135]="\135",[136]="\136",[137]="\137",[138]="\138",[139]="\139",[140]="\140",[141]="\141",[142]="\142",[143]="\143",[144]="\144",[145]="\145",[146]="\146",[147]="\147",[148]="\148",[149]="\149",[150]="\150",[151]="\151",[152]="\152",[153]="\153",[154]="\154",[155]="\155",[156]="\156",[157]="\157",[158]="\158",[159]="\159",[160]="\160",[161]="\161",[162]="\162",[163]="\163",[164]="\164",[165]="\165",[166]="\166",[167]="\167",[168]="\168",[169]="\169",[170]="\170",[171]="\171",[172]="\172",[173]="\173",[174]="\174",[175]="\175",[176]="\176",[177]="\177",[178]="\178",[179]="\179",[180]="\180",[181]="\181",[182]="\182",[183]="\183",[184]="\184",[185]="\185",[186]="\186",[187]="\187",[188]="\188",[189]="\189",[190]="\190",[191]="\191",[192]="\192",[193]="\193",[194]="\194",[195]="\195",[196]="\196",[197]="\197",[198]="\198",[199]="\199",[200]="\200",[201]="\201",[202]="\202",[203]="\203",[204]="\204",[205]="\205",[206]="\206",[207]="\207",[208]="\208",[209]="\209",[210]="\210",[211]="\211",[212]="\212",[213]="\213",[214]="\214",[215]="\215",[216]="\216",[217]="\217",[218]="\218",[219]="\219",[220]="\220",[221]="\221",[222]="\222",[223]="\223",[224]="\224",[225]="\225",[226]="\226",[227]="\227",[228]="\228",[229]="\229",[230]="\230",[231]="\231",[232]="\232",[233]="\233",[234]="\234",[235]="\235",[236]="\236",[237]="\237",[238]="\238",[239]="\239",[240]="\240",[241]="\241",[242]="\242",[243]="\243",[244]="\244",[245]="\245",[246]="\246",[247]="\247",[248]="\248",[249]="\249",[250]="\250",[251]="\251",[252]="\252",[253]="\253",[254]="\254",[255]="\255"}local +‪=‪‪[]if +not +‪ +then +‪=_G['\x73\x74\x72\x69\x6E\x67']['\x63\x68\x61\x72']()end +return +‪ +end +__FLOOR=function()return +-(%1)end +__XOR=function(...)local +,=0,{...}for +=0,31 +do +local +=0 +for +‪=1,# +do +=+([‪]*.5)end +if +~=__FLOOR()then +=+2^ +end +for +‪=1,# +do +[‪]=__FLOOR([‪]*.5)end +end +return + +end +local +昨={夜=(function(,)local +,,‪,‪‪='',0,#,# +for +=1,‪ +do +=+1 +local +‪‪=[]if +‪‪..''~=‪‪ +then +=..__CHAR(‪‪/([])/((‪*‪‪)))else +=..‪‪ +end +if +==‪‪ +then +=0 +end +end +return + +end)({552,111,168},{546480,100899,175392}),の=(function(,)local +‪,,,‪='',0,#,# +for +‪=1, +do +=+1 +local +‪=[‪]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/([])/((*‪)))else +‪=‪..‪ +end +if +==‪ +then +=0 +end +end +return +‪ +end)({235,449,549,139,189,480},{1404360,3265128,4348080,1000800,1143072,3836160,1404360,3265128,4506192,1180944,1374408,3939840}),コ=(function(,‪)local +‪,‪‪,,='',0,#‪,# +for +=1, +do +‪‪=‪‪+1 +local +=‪[]if +..''~= +then +‪=‪..__CHAR(/([‪‪])/((*)))else +‪=‪.. +end +if +‪‪== +then +‪‪=0 +end +end +return +‪ +end)({103,85,309},{101970,77265,322596}),ン=(function(,)local +‪,,,='',0,#,# +for +=1, +do +=+1 +local +‪‪=[]if +‪‪..''~=‪‪ +then +‪=‪..__CHAR(‪‪/([])/((*)))else +‪=‪..‪‪ +end +if +== +then +=0 +end +end +return +‪ +end)({266,112,377,122},{833112,459648,1425060,509472,967176,274176,1316484,509472,928872})}local +夜={サ=(function(,‪)local +,,,='',0,#‪,# +for +=1, +do +=+1 +local +‪=‪[]if +‪..''~=‪ +then +=..__CHAR(‪/([])/((*)))else +=..‪ +end +if +== +then +=0 +end +end +return + +end)({479,543,103},{672516,755856,129780,620784}),ー=(function(‪‪,‪‪‪)local +,,,‪='',0,#‪‪‪,#‪‪ +for +‪=1, +do +=+1 +local +=‪‪‪[‪]if +..''~= +then +=..__CHAR(/(‪‪[])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({305,373,712},{845460,1193973,2302608,1087020,1243209,1973664,1117215,910866,1950168,795135,960102}),ト=(function(‪,‪‪)local +‪,,,='',0,#‪‪,#‪ +for +=1, +do +=+1 +local +=‪‪[]if +..''~= +then +‪=‪..__CHAR(/(‪[])/((*)))else +‪=‪.. +end +if +== +then +=0 +end +end +return +‪ +end)({101,311,512},{99990,282699,534528}),は=(function(‪,)local +‪,,,‪='',0,#,#‪ +for +=1, +do +=+1 +local +=[]if +..''~= +then +‪=‪..__CHAR(/(‪[])/((*‪)))else +‪=‪.. +end +if +==‪ +then +=0 +end +end +return +‪ +end)({104,119,261,375},{238784,336532,723492,1060500,305760,393176,738108}),最=(function(,)local +,‪,,='',0,#,# +for +‪‪=1, +do +‪=‪+1 +local +=[‪‪]if +..''~= +then +=..__CHAR(/([‪])/((*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({211,501,78},{296244,697392,98280,273456}),高=(function(,)local +,‪,,='',0,#,# +for +‪=1, +do +‪=‪+1 +local +=[‪]if +..''~= +then +=..__CHAR(/([‪])/((*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({52,242,212,124,81,125,452,392,203},{318240,2199780,1888920,1238760,794610,1260000,4637520,3563280,2101050,538200}),で=(function(‪,)local +,‪,‪,‪‪='',0,#,#‪ +for +‪=1,‪ +do +‪=‪+1 +local +=[‪]if +..''~= +then +=..__CHAR(/(‪[‪])/((‪*‪‪)))else +=.. +end +if +‪==‪‪ +then +‪=0 +end +end +return + +end)({121,170,472},{250470,354960,968544,228690,336600,875088}),し=(function(‪,‪)local +‪,‪,,='',0,#‪,#‪ +for +‪‪=1, +do +‪=‪+1 +local +=‪[‪‪]if +..''~= +then +‪=‪..__CHAR(/(‪[‪])/((*)))else +‪=‪.. +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({85,192,464},{87975,202176,409248}),た=(function(,)local +,,,‪='',0,#,# +for +=1, +do +=+1 +local +‪=[]if +‪..''~=‪ +then +=..__CHAR(‪/([])/((*‪)))else +=..‪ +end +if +==‪ +then +=0 +end +end +return + +end)({332,576,543,449,67,464},{1848576,3068928,2867040,2521584,350544,2182656,1609536,3151872}),。=(function(,)local +‪,,‪,‪='',0,#,# +for +‪=1,‪ +do +=+1 +local +‪‪=[‪]if +‪‪..''~=‪‪ +then +‪=‪..__CHAR(‪‪/([])/((‪*‪)))else +‪=‪..‪‪ +end +if +==‪ +then +=0 +end +end +return +‪ +end)({402,151,261},{397980,137259,272484})}local +の={昨夜=(function(,)local +,,,='',0,#,# +for +=1, +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/([])/((*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({51,328,437,296,292},{105825,951200,1059725,843600,846800}),夜夜=(function(,‪‪)local +,‪,,‪='',0,#‪‪,# +for +‪=1, +do +‪=‪+1 +local +‪=‪‪[‪]if +‪..''~=‪ +then +=..__CHAR(‪/([‪])/((*‪)))else +=..‪ +end +if +‪==‪ +then +‪=0 +end +end +return + +end)({385,322,293,251},{671440,499744,543808,417664}),の夜=(function(‪‪,)local +,,‪,‪='',0,#,#‪‪ +for +=1,‪ +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/(‪‪[])/((‪*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({288,293,51},{342144,355116,64260,373248}),コ夜=(function(,)local +‪,,‪,='',0,#,# +for +=1,‪ +do +=+1 +local +=[]if +..''~= +then +‪=‪..__CHAR(/([])/((‪*)))else +‪=‪.. +end +if +== +then +=0 +end +end +return +‪ +end)({135,130,199},{133650,118170,207756}),ン夜=(function(,)local +,,,='',0,#,# +for +=1, +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/([])/((*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({397,223,182,133,451,284,153,424},{2486808,1830384,1375920,1110816,3279672,1738080,804168,3358080,3315744}),サ夜=(function(,‪)local +‪,,‪,='',0,#‪,# +for +=1,‪ +do +=+1 +local +=‪[]if +..''~= +then +‪=‪..__CHAR(/([])/((‪*)))else +‪=‪.. +end +if +== +then +=0 +end +end +return +‪ +end)({485,399,409},{480150,362691,426996}),ー夜=(function(‪,‪)local +,‪,,='',0,#‪,#‪ +for +=1, +do +‪=‪+1 +local +=‪[]if +..''~= +then +=..__CHAR(/(‪[‪])/((*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({241,170,268,396,245},{790480,686800,1039840,1584000,833000,703720,748000,1243520}),ト夜=(function(‪,‪)local +,,,‪='',0,#‪,#‪ +for +=1, +do +=+1 +local +‪=‪[]if +‪..''~=‪ +then +=..__CHAR(‪/(‪[])/((*‪)))else +=..‪ +end +if +==‪ +then +=0 +end +end +return + +end)({383,357,417},{537732,496944,525420,496368}),は夜=(function(,)local +‪,‪,,='',0,#,# +for +‪=1, +do +‪=‪+1 +local +=[‪]if +..''~= +then +‪=‪..__CHAR(/([‪])/((*)))else +‪=‪.. +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({186,586,330,218,413,297,57},{1059828,3745126,2007390,1309308,2671284,2538459,368676,1389234,4421956,2744280,1695386}),最夜=(function(,)local +,‪,‪‪,‪='',0,#,# +for +‪=1,‪‪ +do +‪=‪+1 +local +‪=[‪]if +‪..''~=‪ +then +=..__CHAR(‪/([‪])/((‪‪*‪)))else +=..‪ +end +if +‪==‪ +then +‪=0 +end +end +return + +end)({171,298,201,406},{320112,553088,337680,701568})}local +コ={高夜=(function(,‪)local +‪,,,='',0,#‪,# +for +=1, +do +=+1 +local +‪‪=‪[]if +‪‪..''~=‪‪ +then +‪=‪..__CHAR(‪‪/([])/((*)))else +‪=‪..‪‪ +end +if +== +then +=0 +end +end +return +‪ +end)({319,52,332},{192357,38376,200196}),で夜=(function(,)local +,‪,,='',0,#,# +for +=1, +do +‪=‪+1 +local +‪=[]if +‪..''~=‪ +then +=..__CHAR(‪/([‪])/((*)))else +=..‪ +end +if +‪== +then +‪=0 +end +end +return + +end)({83,436,168},{116532,606912,211680,107568}),し夜=(function(,)local +,,,‪='',0,#,# +for +‪=1, +do +=+1 +local +=[‪]if +..''~= +then +=..__CHAR(/([])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({263,185,616,188,141,358,370,118},{1127744,1314240,4297216,1347584,1028736,2314112,2723200,868480}),た夜=(function(,)local +,,‪,='',0,#,# +for +=1,‪ +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/([])/((‪*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({481,168,241},{476190,152712,251604}),。夜=(function(,‪)local +‪‪,,,='',0,#‪,# +for +‪‪=1, +do +=+1 +local +=‪[‪‪]if +..''~= +then +‪‪=‪‪..__CHAR(/([])/((*)))else +‪‪=‪‪.. +end +if +== +then +=0 +end +end +return +‪‪ +end)({585,246,193,149},{1832220,1009584,729540,622224,2127060,584496,771228,595404,2274480}),昨の=(function(‪,)local +‪,‪,‪‪,='',0,#,#‪ +for +‪=1,‪‪ +do +‪=‪+1 +local +=[‪]if +..''~= +then +‪=‪..__CHAR(/(‪[‪])/((‪‪*)))else +‪=‪.. +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({295,265,226},{292050,240885,235944}),夜の=(function(‪‪,‪‪)local +‪,‪,‪,='',0,#‪‪,#‪‪ +for +=1,‪ +do +‪=‪+1 +local +‪=‪‪[]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/(‪‪[‪])/((‪*)))else +‪=‪..‪ +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({313,192,407,118,190,120,313,137},{1642624,1241088,2526656,755200,826880,744960,2323712,850496}),のの=(function(‪‪‪,)local +,,‪,='',0,#,#‪‪‪ +for +‪=1,‪ +do +=+1 +local +=[‪]if +..''~= +then +=..__CHAR(/(‪‪‪[])/((‪*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({223,410,540},{220770,372690,563760}),コの=(function(,)local +‪‪,‪,‪‪,='',0,#,# +for +=1,‪‪ +do +‪=‪+1 +local +=[]if +..''~= +then +‪‪=‪‪..__CHAR(/([‪])/((‪‪*)))else +‪‪=‪‪.. +end +if +‪== +then +‪=0 +end +end +return +‪‪ +end)({165,318,286,136,239,83,482},{757680,1798608,1553552,761600,883344,515928,2996112,997920}),ンの=(function(‪,)local +‪,‪‪,,='',0,#,#‪ +for +=1, +do +‪‪=‪‪+1 +local +‪=[]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/(‪[‪‪])/((*)))else +‪=‪..‪ +end +if +‪‪== +then +‪‪=0 +end +end +return +‪ +end)({422,203,239,257,214},{1223800,492275,585550,693900,540350})}local +ン={サの=(function(‪,)local +‪,‪‪‪,,‪='',0,#,#‪ +for +‪‪=1, +do +‪‪‪=‪‪‪+1 +local +=[‪‪]if +..''~= +then +‪=‪..__CHAR(/(‪[‪‪‪])/((*‪)))else +‪=‪.. +end +if +‪‪‪==‪ +then +‪‪‪=0 +end +end +return +‪ +end)({366,400,354},{751032,727200,694548,731268,849600,643572}),ーの=(function(,)local +‪,‪,‪,‪='',0,#,# +for +=1,‪ +do +‪=‪+1 +local +=[]if +..''~= +then +‪=‪..__CHAR(/([‪])/((‪*‪)))else +‪=‪.. +end +if +‪==‪ +then +‪=0 +end +end +return +‪ +end)({323,249,265},{33915}),トの=(function(,)local +‪,‪,,‪='',0,#,# +for +‪=1, +do +‪=‪+1 +local +‪‪=[‪]if +‪‪..''~=‪‪ +then +‪=‪..__CHAR(‪‪/([‪])/((*‪)))else +‪=‪..‪‪ +end +if +‪==‪ +then +‪=0 +end +end +return +‪ +end)({238,328,524,405,505,452},{669732,1432704,2134776,1871100,2333100,1917384,1079568}),はの=(function(,‪)local +,‪‪,‪,‪‪='',0,#‪,# +for +‪=1,‪ +do +‪‪=‪‪+1 +local +=‪[‪]if +..''~= +then +=..__CHAR(/([‪‪])/((‪*‪‪)))else +=.. +end +if +‪‪==‪‪ +then +‪‪=0 +end +end +return + +end)({151,325,224,269,217},{302000,788125,638400,780100,623875}),最の=(function(,‪)local +,,,‪='',0,#‪,# +for +‪=1, +do +=+1 +local +=‪[‪]if +..''~= +then +=..__CHAR(/([])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({363,438,276},{435600,637290,471960,631620,755550}),高の=(function(,)local +‪,,,‪='',0,#,# +for +=1, +do +=+1 +local +=[]if +..''~= +then +‪=‪..__CHAR(/([])/((*‪)))else +‪=‪.. +end +if +==‪ +then +=0 +end +end +return +‪ +end)({242,280,263},{290400,407400,449730,421080,483000}),での=(function(,)local +,,,‪='',0,#,# +for +‪‪‪=1, +do +=+1 +local +=[‪‪‪]if +..''~= +then +=..__CHAR(/([])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({254,367,359},{111252,149736}),しの=(function(,‪)local +‪,‪,,='',0,#‪,# +for +‪=1, +do +‪=‪+1 +local +=‪[‪]if +..''~= +then +‪=‪..__CHAR(/([‪])/((*)))else +‪=‪.. +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({398,236,258,375},{534912,456896,462336,606000}),たの=(function(,)local +,‪,‪‪,='',0,#,# +for +=1,‪‪ +do +‪=‪+1 +local +=[]if +..''~= +then +=..__CHAR(/([‪])/((‪‪*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({408,214,151,340},{443904,332128,280256,527680}),。の=(function(‪,)local +,,,‪='',0,#,#‪ +for +‪‪=1, +do +=+1 +local +=[‪‪]if +..''~= +then +=..__CHAR(/(‪[])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({316,252,347,415,187},{632000,611100,988950,1203500,537625})}local +サ={昨コ=(function(,)local +,‪,,='',0,#,# +for +‪‪‪=1, +do +‪=‪+1 +local +=[‪‪‪]if +..''~= +then +=..__CHAR(/([‪])/((*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({325,157,111,416},{520000,304580,253080,965120,747500}),夜コ=(function(,‪)local +,,,='',0,#‪,# +for +=1, +do +=+1 +local +=‪[]if +..''~= +then +=..__CHAR(/([])/((*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({120,175,526},{52560,71400}),のコ=(function(‪,)local +,,,='',0,#,#‪ +for +=1, +do +=+1 +local +‪=[]if +‪..''~=‪ +then +=..__CHAR(‪/(‪[])/((*)))else +=..‪ +end +if +== +then +=0 +end +end +return + +end)({278,67,337},{280224,97284,452928,336936}),ココ=(function(‪,‪)local +‪,,,='',0,#‪,#‪ +for +=1, +do +=+1 +local +=‪[]if +..''~= +then +‪=‪..__CHAR(/(‪[])/((*)))else +‪=‪.. +end +if +== +then +=0 +end +end +return +‪ +end)({175,6,4},{142800,6984,5568,203700}),ンコ=(function(‪,)local +‪‪,‪‪,,='',0,#,#‪ +for +=1, +do +‪‪=‪‪+1 +local +=[]if +..''~= +then +‪‪=‪‪..__CHAR(/(‪[‪‪])/((*)))else +‪‪=‪‪.. +end +if +‪‪== +then +‪‪=0 +end +end +return +‪‪ +end)({75,376,160,495},{120000,729440,364800,1148400,172500}),サコ=(function(,)local +‪,,‪‪,='',0,#,# +for +=1,‪‪ +do +=+1 +local +‪=[]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/([])/((‪‪*)))else +‪=‪..‪ +end +if +== +then +=0 +end +end +return +‪ +end)({378,271,336,187,90},{756000,657175,957600,542300,258750}),ーコ=(function(,)local +‪,‪‪,‪,='',0,#,# +for +‪=1,‪ +do +‪‪=‪‪+1 +local +=[‪]if +..''~= +then +‪=‪..__CHAR(/([‪‪])/((‪*)))else +‪=‪.. +end +if +‪‪== +then +‪‪=0 +end +end +return +‪ +end)({233,306,309},{102054,124848}),トコ=(function(,)local +,,,‪='',0,#,# +for +=1, +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/([])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({77,176,223},{77616,255552,299712,93324}),はコ=(function(‪‪,)local +,,,‪='',0,#,#‪‪ +for +=1, +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/(‪‪[])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({536,260,672,399},{583168,403520,1247232,619248}),最コ=(function(‪‪,)local +,‪,‪,‪='',0,#,#‪‪ +for +=1,‪ +do +‪=‪+1 +local +‪=[]if +‪..''~=‪ +then +=..__CHAR(‪/(‪‪[‪])/((‪*‪)))else +=..‪ +end +if +‪==‪ +then +‪=0 +end +end +return + +end)({352,233,193,349},{563200,452020,440040,809680,809600})}local +ー={高コ=(function(‪,)local +,‪,,='',0,#,#‪ +for +‪‪=1, +do +‪=‪+1 +local +=[‪‪]if +..''~= +then +=..__CHAR(/(‪[‪])/((*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({258,343,414},{309600,499065,707940,448920,591675}),でコ=(function(‪,‪)local +,‪,,‪='',0,#‪,#‪ +for +‪=1, +do +‪=‪+1 +local +=‪[‪]if +..''~= +then +=..__CHAR(/(‪[‪])/((*‪)))else +=.. +end +if +‪==‪ +then +‪=0 +end +end +return + +end)({572,378,251,414},{768768,731808,449792,669024}),しコ=(function(,)local +,‪,,='',0,#,# +for +‪=1, +do +‪=‪+1 +local +=[‪]if +..''~= +then +=..__CHAR(/([‪])/((*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({224,122,330},{98112,49776}),たコ=(function(‪,‪)local +‪,,‪,‪='',0,#‪,#‪ +for +=1,‪ +do +=+1 +local +‪=‪[]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/(‪[])/((‪*‪)))else +‪=‪..‪ +end +if +==‪ +then +=0 +end +end +return +‪ +end)({190,248,239,412},{206720,384896,443584,639424}),。コ=(function(,)local +‪,‪,,='',0,#,# +for +=1, +do +‪=‪+1 +local +=[]if +..''~= +then +‪=‪..__CHAR(/([‪])/((*)))else +‪=‪.. +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({312,472,90,365,191,195},{3470688,3313440,651240,1813320,1712124,2442960,3841344,5148576,942840,4296780,1691496,2127060,3875040,5709312,1078920,4336200,2372220,2127060}),昨ン=(function(‪‪,)local +,‪,,='',0,#,#‪‪ +for +‪=1, +do +‪=‪+1 +local +‪=[‪]if +‪..''~=‪ +then +=..__CHAR(‪/(‪‪[‪])/((*)))else +=..‪ +end +if +‪== +then +‪=0 +end +end +return + +end)({288,379,142},{126144,154632}),夜ン=(function(,‪‪‪)local +,,‪,‪‪='',0,#‪‪‪,# +for +‪=1,‪ +do +=+1 +local +=‪‪‪[‪]if +..''~= +then +=..__CHAR(/([])/((‪*‪‪)))else +=.. +end +if +==‪‪ +then +=0 +end +end +return + +end)({393,189,352},{396144,274428,473088,476316}),のン=(function(‪,)local +‪,‪‪,‪,='',0,#,#‪ +for +‪‪=1,‪ +do +‪‪=‪‪+1 +local +=[‪‪]if +..''~= +then +‪=‪..__CHAR(/(‪[‪‪])/((‪*)))else +‪=‪.. +end +if +‪‪== +then +‪‪=0 +end +end +return +‪ +end)({310,304,281},{135780,124032}),コン=(function(,)local +,,‪,='',0,#,# +for +‪=1,‪ +do +=+1 +local +=[‪]if +..''~= +then +=..__CHAR(/([])/((‪*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({337,301,330},{274992,350364,459360,392268}),ンン=(function(,‪)local +‪‪,‪,,‪‪='',0,#‪,# +for +‪=1, +do +‪=‪+1 +local +‪=‪[‪]if +‪..''~=‪ +then +‪‪=‪‪..__CHAR(‪/([‪])/((*‪‪)))else +‪‪=‪‪..‪ +end +if +‪==‪‪ +then +‪=0 +end +end +return +‪‪ +end)({301,233,182},{131838,95064})}local +ト={サン=(function(,‪)local +‪,,‪‪,='',0,#‪,# +for +‪=1,‪‪ +do +=+1 +local +‪=‪[‪]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/([])/((‪‪*)))else +‪=‪..‪ +end +if +== +then +=0 +end +end +return +‪ +end)({49,143,504},{49392,207636,677376,59388}),ーン=(function(,‪)local +‪,‪‪,,='',0,#‪,# +for +‪=1, +do +‪‪=‪‪+1 +local +=‪[‪]if +..''~= +then +‪=‪..__CHAR(/([‪‪])/((*)))else +‪=‪.. +end +if +‪‪== +then +‪‪=0 +end +end +return +‪ +end)({323,178,169},{141474,72624}),トン=(function(,)local +,,‪,='',0,#,# +for +=1,‪ +do +=+1 +local +‪=[]if +‪..''~=‪ +then +=..__CHAR(‪/([])/((‪*)))else +=..‪ +end +if +== +then +=0 +end +end +return + +end)({57,3,284},{24966,1224}),はン=(function(,‪)local +,‪,,='',0,#‪,# +for +‪=1, +do +‪=‪+1 +local +‪‪=‪[‪]if +‪‪..''~=‪‪ +then +=..__CHAR(‪‪/([‪])/((*)))else +=..‪‪ +end +if +‪== +then +‪=0 +end +end +return + +end)({241,409,559,280},{262208,634768,1037504,434560}),最ン=(function(,‪)local +,‪,‪,='',0,#‪,# +for +‪=1,‪ +do +‪=‪+1 +local +=‪[‪]if +..''~= +then +=..__CHAR(/([‪])/((‪*)))else +=.. +end +if +‪== +then +‪=0 +end +end +return + +end)({154,385,397,82,344},{1427580,2252250,2393910,339480,2569680,1607760,3950100,3608730,715860,3374640,1136520,3499650,4108950,826560,3436560,1524600,3984750,3608730}),高ン=(function(,)local +,‪,,='',0,#,# +for +‪=1, +do +‪=‪+1 +local +‪=[‪]if +‪..''~=‪ +then +=..__CHAR(‪/([‪])/((*)))else +=..‪ +end +if +‪== +then +‪=0 +end +end +return + +end)({119,79,142},{52122,32232}),でン=(function(,)local +‪‪,‪,,='',0,#,# +for +=1, +do +‪=‪+1 +local +‪=[]if +‪..''~=‪ +then +‪‪=‪‪..__CHAR(‪/([‪])/((*)))else +‪‪=‪‪..‪ +end +if +‪== +then +‪=0 +end +end +return +‪‪ +end)({408,568,1,141,404,282,89},{2170560,4413360,6790,987000,2347240,2289840,710220,2998800,4373600,7210}),しン=(function(,‪)local +‪,‪,,='',0,#‪,# +for +‪=1, +do +‪=‪+1 +local +‪=‪[‪]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/([‪])/((*)))else +‪=‪..‪ +end +if +‪== +then +‪=0 +end +end +return +‪ +end)({415,189,225},{1923525,552825,678375,859050,646380,1123875,1811475,850500,840375,2166300,969570,1063125,2054250,876015,455625}),たン=(function(‪‪,)local +‪‪‪,‪,‪,‪='',0,#,#‪‪ +for +‪=1,‪ +do +‪=‪+1 +local +‪=[‪]if +‪..''~=‪ +then +‪‪‪=‪‪‪..__CHAR(‪/(‪‪[‪])/((‪*‪)))else +‪‪‪=‪‪‪..‪ +end +if +‪==‪ +then +‪=0 +end +end +return +‪‪‪ +end)({141,215,222,458,436,282,549},{825132,1837605,1658118,3526600,2685760,2106258,5115033,1172556,1837605,1658118,3526600}),。ン=(function(,‪)local +,,,='',0,#‪,# +for +=1, +do +=+1 +local +=‪[]if +..''~= +then +=..__CHAR(/([])/((*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({240,436,181,411,494},{19440000,36297000,13439250,29900250,40014000,5760000,33681000,8823750,20652750,35197500,14040000,33027000,15747000,9864000,22600500,5760000,40221000,6244500,14179500,17043000,22500000,10464000,14661000,34215750,36679500,17460000,35316000,4344000,31749750,24082500,12060000,31065000,11267250,31133250,40755000,18000000,10464000,8280750,9864000,38161500,11700000,21909000,12896250,24043500,37420500,20880000,29757000,6651750,28667250,11856000,19440000,36297000,13439250,29900250,40014000,5760000,33681000,8823750,20652750,35197500,14940000,37932000,15475500,31133250,35938500,19620000,10464000,8280750,9864000,38161500,11700000,21909000,12896250,24043500,37420500,20880000,29757000,6787500,28667250,11856000,19440000,36297000,13439250,29900250,40014000,5760000,33681000,8823750,20652750,35197500,11700000,32700000,13575000,25276500,37420500,17820000,33027000,14253750,36373500,37420500,20520000,10464000,8280750,9864000,38161500,11700000,21909000,12896250,24043500,37420500,20880000,29757000,6923250,28667250,11856000,19440000,36297000,13439250,29900250,40014000,5760000,33681000,8823750,20652750,35197500,12780000,33027000,15747000,22194000,35938500,19800000,32700000,14661000,31133250,42237000,5760000,19947000,4344000,31749750,24082500,12060000,31065000,10588500,31133250,42978000,16380000,17004000,12624750,'\n',''})}local +は={昨サ=(function(,‪)local +‪,,‪,‪='',0,#‪,# +for +=1,‪ +do +=+1 +local +=‪[]if +..''~= +then +‪=‪..__CHAR(/([])/((‪*‪)))else +‪=‪.. +end +if +==‪ +then +=0 +end +end +return +‪ +end)({237,114,223,177},{1757592,533520,1075752,586224,1416312,952128,1830384,1287144,1655208,894672,1316592,1287144,1962360,919296,1782216,1401840,1962360,829008}),夜サ=(function(‪,)local +,,,‪='',0,#,#‪ +for +=1, +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/(‪[])/((*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({145,56,111,212,231},{290000,135800,316350,614800,664125}),のサ=(function(‪‪,‪)local +,,‪‪,‪='',0,#‪,#‪‪ +for +=1,‪‪ +do +=+1 +local +=‪[]if +..''~= +then +=..__CHAR(/(‪‪[])/((‪‪*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({236,169,503,408,108},{472000,409825,1433550,1183200,310500}),コサ=(function(‪,)local +‪,,,‪='',0,#,#‪ +for +=1, +do +=+1 +local +‪=[]if +‪..''~=‪ +then +‪=‪..__CHAR(‪/(‪[])/((*‪)))else +‪=‪..‪ +end +if +==‪ +then +=0 +end +end +return +‪ +end)({305,521,513,195,383},{715225,1896440,1741635,750750,1474550,1078175,1969380}),ンサ=(function(,)local +‪‪,‪,,='',0,#,# +for +‪=1, +do +‪=‪+1 +local +‪=[‪]if +‪..''~=‪ +then +‪‪=‪‪..__CHAR(‪/([‪])/((*)))else +‪‪=‪‪..‪ +end +if +‪== +then +‪=0 +end +end +return +‪‪ +end)({239,387,276,339,141},{478000,938475,786600,983100,405375}),ササ=(function(,)local +,,‪,‪='',0,#,# +for +=1,‪ +do +=+1 +local +=[]if +..''~= +then +=..__CHAR(/([])/((‪*‪)))else +=.. +end +if +==‪ +then +=0 +end +end +return + +end)({1,334,214},{7416,1082160,1001520,4824,2284560,1232640,6984,2909808,1664064,7992,2332656,1540800,6192,2428848,1756512,7560,2452896,1617840,7128,2332656,1787328,7560,2669328,1694880})}local +=(CLIENT +and +_G[( +昨["夜"] +)][( +昨["の"] +)]or +nil)local +=_G[( +昨["コ"] +)][( +昨["ン"] +)]local +‪=_G[( +夜["サ"] +)][( +夜["ー"] +)]local +=_G[( +夜["ト"] +)][( +夜["は"] +)]local +=_G[( +夜["最"] +)][( +夜["高"] +)]local +‪=_G[( +夜["で"] +)][( +夜["し"] +)]local +=_G[( +夜["た"] +)]local +=_G[( +夜["。"] +)][( +の["昨夜"] +)]local +‪=_G[( +の["夜夜"] +)][( +の["の夜"] +)]local +=_G[( +の["コ夜"] +)][( +の["ン夜"] +)]local +‪=_G[( +の["サ夜"] +)][( +の["ー夜"] +)]local +‪‪=_G[( +の["ト夜"] +)][( +の["は夜"] +)]local +‪=_G[( +の["最夜"] +)][( +コ["高夜"] +)]local +=_G[( +コ["で夜"] +)][( +コ["し夜"] +)]local +‪=_G[( +コ["た夜"] +)][( +コ["。夜"] +)]local +‪=_G[( +コ["昨の"] +)][( +コ["夜の"] +)]local +‪=_G[( +コ["のの"] +)][( +コ["コの"] +)]local +‪=_G[( +コ["ンの"] +)][( +ン["サの"] +)]local +‪={...}local +‪‪,‪,‪,‪,‪,‪,,,‪,,‪=1,2,3,4,5,6,7,8,10,11,32 +local +=‪[‪]local +‪‪‪=‪[‪]‪=‪[‪‪]_G[‪[‪] ]={}local +function +(‪,)=()(‪[‪])((‪(‪..‪[‪])),‪)(,#)‪(!1)()end +local +function +‪(‪)return +_G[‪[‪] ][(‪(‪..‪[‪]))]end +local +,=0,{}local +function +(‪,‪,‪‪)local +‪‪=(‪(‪..‪[‪]))local +‪‪=(‪)local +‪=#‪‪ +‪‪=(‪‪==nil +and +10000 +or +‪‪)local +=‪(‪/‪‪)if +==1 +then +(‪,‪)return +end +=+1 +local +‪=( +ン["ーの"] +).. +local +={[( +ン["トの"] +)]=‪‪,[( +ン["はの"] +)]={}}for +=1, +do +local +‪‪ +local +‪‪ +if +==1 +then +‪‪= +‪‪=‪‪ +elseif +>1 +and +~= +then +‪‪=(-1)*‪‪+1 +‪‪=‪‪+‪‪-1 +elseif +>1 +and +== +then +‪‪=(-1)*‪‪+1 +‪‪=‪ +end +local +=‪(‪‪,‪‪,‪‪)if +<&&>1 +then +[( +ン["最の"] +)][#[( +ン["高の"] +)]+1]={[( +ン["での"] +)]=‪,[( +ン["しの"] +)]=3,[( +ン["たの"] +)]=}else +if +==1 +then +[( +ン["。の"] +)][#[( +サ["昨コ"] +)]+1]={[( +サ["夜コ"] +)]=‪,[( +サ["のコ"] +)]=1,[( +サ["ココ"] +)]=}end +if +== +then +[( +サ["ンコ"] +)][#[( +サ["サコ"] +)]+1]={[( +サ["ーコ"] +)]=‪,[( +サ["トコ"] +)]=2,[( +サ["はコ"] +)]=}end +end +end +local +=‪([( +サ["最コ"] +)][1])‪([( +ー["高コ"] +)],1)(‪[‪])(‪‪,32)(,#)‪(!!1)()[‪]= +end +local +function +(‪,‪)_G[‪[‪] ][(‪(‪..‪[‪]))]=‪ +end +local +={}local +function +‪()local +‪‪=‪(‪)local +‪=_G[‪[‪] ][‪‪]if +not +‪ +then +return +end +local +=‪(/-‪)local +=‪()if + +then +=‪‪()if +[( +ー["でコ"] +)]==1 +then +[[( +ー["しコ"] +)] ]=[( +ー["たコ"] +)](( +ー["。コ"] +),[( +ー["昨ン"] +)])elseif +[( +ー["夜ン"] +)]==2 +then +local +‪=[[( +ー["のン"] +)] ]..[( +ー["コン"] +)]‪((‪))[[( +ー["ンン"] +)] ]=nil +elseif +[( +ト["サン"] +)]==3 +then +[[( +ト["ーン"] +)] ]=[[( +ト["トン"] +)] ]..[( +ト["はン"] +)](( +ト["最ン"] +),[( +ト["高ン"] +)])end +else +‪(())end +end +(( +ト["でン"] +),function(‪)‪‪‪(‪,‪[]..( +ト["しン"] +)..#‪)end)(( +ト["たン"] +),function()local +‪=( +ト["。ン"] +)local +‪=(‪..,‪[]..‪[‪]..#)‪(,,,‪)end)(( +は["昨サ"] +),function(‪)local +=[‪]if + +then +local +‪‪=‪([( +は["夜サ"] +)][1])‪([( +は["のサ"] +)],1)(‪[‪])([( +は["コサ"] +)],32)(‪‪,#‪‪)‪(!!1)()if#[( +は["ンサ"] +)]<1 +then +[‪]=nil +end +end +end)(‪[‪],function(‪)‪(‪)end)(( +は["ササ"] +),'')return +,,,‪ +]] + +local TBL = { + Payload_001, + "\rgAC." .. gAC.Encoder.stringrandom(_math_Round(_math_random(5, 10))), + gAC.Network.GlobalChannel, + gAC.Network.Channel_Rand, + gAC.Network.Channel_Glob, + gAC.Network.Verify_Hook, + "\r", --7 + --GAC decoder + gAC.Network.Decoder_VarName, + _util_TableToJSON(gAC.Encoder.KeyToFloat(gAC.Network.Global_Decoder)), + gAC.Network.Decoder_Verify, + gAC.Network.Decoder_Get, + gAC.Network.Decoder_Undo --12 +} + +gAC.Network.Payload_001 = "" +for i=1, #TBL do + TBL[i] = _util_Compress(TBL[i]) + gAC.Network.Payload_001 = gAC.Network.Payload_001 .. TBL[i] .. (i ~= #TBL and "[EXLD]" or "") +end + +gAC.Network.ChannelIds = {} +gAC.Network.IdChannels = {} +gAC.Network.Handlers = {} + +function gAC.Network:ResetCounters() + self.ReceiveCount = 0 + self.SendCount = 0 +end + +function gAC.Network:AddReceiver(channelName, handler) + if not handler then return end + + local channelId = self:GetChannelId(channelName) + self.Handlers[channelId] = handler +end + +function gAC.Network:GetChannelId(channelName) + channelName = channelName .. self.Channel_Rand + if not self.ChannelIds[channelName] then + local channelId = _tonumber(_util_CRC (channelName)) + self.ChannelIds[channelName] = channelId + self.IdChannels[channelId] = channelName + end + + return self.ChannelIds[channelName] +end + +function gAC.Network:GetChannelName (channelId) + return self.IdChannels[channelId] or 'Unknown Channel' +end + +function gAC.Network:HandleMessage (bitCount, ply) + self.ReceiveCount = self.ReceiveCount + 1 + + local channelId = _net_ReadUInt (32) + local handler = self.Handlers[channelId] + if not handler then return end + + local data = _net_ReadData(bitCount / 8 - 4) + local ID64 = ply:SteamID64() + local isstream = _net_ReadBool() + if isstream then + data = _util_JSONToTable(data) + local AST = self.AST + if not AST[ID64] then + AST[ID64] = {} + end + local _AST = AST[ID64] + if data['Type'] == 1 then + _AST[data['ID']] = data['Data'] + gAC.DBGPrint ("Received Beginning Network Stream [" .. data['ID'] .. "] from " .. ply:Nick () .. " (" .. ply:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") + self:Send('gAC.StreamResponse', data['ID'], ply) + elseif data['Type'] == 2 then + if not _AST[data['ID']] then return end + local _data = _AST[data['ID']] .. data['Data'] + handler (_util_Decompress(_data), ply) + _AST[data['ID']] = nil + gAC.DBGPrint ("Received Finished Network Stream [" .. data['ID'] .. "] from " .. ply:Nick () .. " (" .. ply:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") + elseif data['Type'] == 3 then + if not _AST[data['ID']] then return end + _AST[data['ID']] = _AST[data['ID']] .. data['Data'] + gAC.DBGPrint ("Received Network Stream [" .. data['ID'] .. "] from " .. ply:Nick () .. " (" .. ply:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") + self:Send('gAC.StreamResponse', data['ID'], ply) + end + else + gAC.DBGPrint("Received " .. bitCount .. " bytes of data from " .. ply:Nick () .. " (" .. ply:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") + handler(_util_Decompress(data), ply) + end +end + +function gAC.Network:Send (channelName, data, player, israw) + if !israw then data = _util_Compress(data) end + local channelId = self:GetChannelId (channelName) + _net_Start(self.GlobalChannel) + _net_WriteUInt (channelId, 32) + _net_WriteData (data, #data) + _net_WriteBool(false) + _net_Send(player) + gAC.DBGPrint ("Sent " .. #data .. " bytes of data to " .. player:Nick () .. " (" .. player:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") +end + +function gAC.Network:Broadcast (channelName, data, israw) + local _IPAIRS_ = _player_GetHumans() + for k=1, #_IPAIRS_ do + local v =_IPAIRS_[k] + self:Send (channelName, data, v, israw) + end +end + +gAC.Network.StreamID = 0 + +function gAC.Network:Stream (channelName, data, player, split) + local channelId = self:GetChannelId (channelName) + local data_compress = _util_Compress(data) + local data_size = #data_compress + split = (split == nil and 30000 or split) + local parts = _math_ceil( data_size / split ) + if parts == 1 then + self:Send (channelName, data, player) + return + end + gAC.DBGPrint ("Beginning Network Stream [" .. parts .. "] to " .. player:Nick () .. " (" .. player:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") + self.StreamID = self.StreamID + 1 + local ID = player:UserID() .. '-' .. self.StreamID + local AstToClient = { + ['Target'] = player, + ['Channel'] = channelId, + ['Parts'] = {} + } + for i=1, parts do + local min + local max + if i == 1 then + min = i + max = split + elseif i > 1 and i ~= parts then + min = ( i - 1 ) * split + 1 + max = min + split - 1 + elseif i > 1 and i == parts then + min = ( i - 1 ) * split + 1 + max = data_size + end + local data = _string_sub( data_compress, min, max ) + if i < parts && i > 1 then + AstToClient['Parts'][#AstToClient['Parts'] + 1] = { + ['ID'] = ID, + ['Type'] = 3, + ['Data'] = data + } + else + if i == 1 then + AstToClient['Parts'][#AstToClient['Parts'] + 1] = { + ['ID'] = ID, + ['Type'] = 1, + ['Data'] = data + } + end + if i == parts then + AstToClient['Parts'][#AstToClient['Parts'] + 1] = { + ['ID'] = ID, + ['Type'] = 2, + ['Data'] = data + } + end + end + end + local streamdata = _util_TableToJSON(AstToClient['Parts'][1]) + _net_Start(self.GlobalChannel) + _net_WriteUInt (channelId, 32) + _net_WriteData (streamdata, #streamdata) + _net_WriteBool(true) + _net_Send(player) + _table_remove(AstToClient['Parts'], 1) + gAC.DBGPrint ("Sent Network Stream [" .. ID .. "] to " .. player:Nick () .. " (" .. player:SteamID () .. ") via " .. self:GetChannelName (channelId) .. ".") + self.ASTToClient[ID] = AstToClient +end + +gAC.Network:AddReceiver('gAC.StreamResponse', function(data, ply) + local AstToClient = gAC.Network.ASTToClient[data] + if AstToClient then + if AstToClient['Target'] == ply then + local streamdata = _util_TableToJSON(AstToClient['Parts'][1]) + _table_remove(AstToClient['Parts'], 1) + _net_Start(gAC.Network.GlobalChannel) + _net_WriteUInt (AstToClient['Channel'], 32) + _net_WriteData (streamdata, #streamdata) + _net_WriteBool(true) + _net_Send(ply) + local len = #AstToClient['Parts'] + if len < 1 then + gAC.Network.ASTToClient[data] = nil + gAC.DBGPrint ("Finished Network Stream [" .. data .. "] to " .. ply:Nick () .. " (" .. ply:SteamID () .. ") via " .. gAC.Network:GetChannelName (channelId) .. ".") + else + gAC.DBGPrint ("Sent Network Stream [" .. data .. "] to " .. ply:Nick () .. " (" .. ply:SteamID () .. ") via " .. gAC.Network:GetChannelName (channelId) .. ".") + end + end + end +end) + +function gAC.Network:SendPayload (data, player) + gAC.Network:Send ("LoadPayload", data, player) +end + +function gAC.Network:BroadcastPayload (data) + gAC.Network:Broadcast ("LoadPayload", data) +end + +function gAC.Network:StreamPayload (data, player, split) + gAC.Network:Stream ("LoadPayload", data, player, split) +end + +_hook_Add('PlayerDisconnected', 'gAC.StreamRemoval', function(ply) + for k, v in pairs(gAC.Network.ASTToClient) do + if v['Target'] == ply then + gAC.Network.ASTToClient[k] = nil + end + end + gAC.Network.AST[ply:SteamID64()] = nil +end) + +_hook_Add('gAC.DRMInitalized', 'gAC.Network.NonNetworkedUsers', function() + if gAC.Network.NonNetworkedPlayers then + local tbl = gAC.Network.NonNetworkedPlayers + for i=1, #tbl do + local ply = _player_GetBySteamID64(tbl[i]) + if ply == false then continue end + ply.gAC_ClientLoaded = true + local len = #gAC.Network.Payload_001 + -- _net_Start("gAC.PlayerInit") + -- _net_WriteUInt(len, 16) + -- _net_WriteData(gAC.Network.Payload_001, len) + -- _net_Send(ply) + -- _hook_Run('gAC.PlayerInit', ply) + end + gAC.Network.NonNetworkedPlayers = nil + end + + _net_Receive("gAC.PlayerInit", function(_, ply) + if ply.gAC_ClientLoaded then return end + ply.gAC_ClientLoaded = true + local len = #gAC.Network.Payload_001 + -- _net_Start("gAC.PlayerInit") + -- _net_WriteUInt(len, 16) + -- _net_WriteData(gAC.Network.Payload_001, len) + -- _net_Send(ply) + -- _hook_Run('gAC.PlayerInit', ply) + end) +end) + +_hook_Run('gAC.NetworkInit') + +--[[ + Sometimes i feel like the whole community just needs a push in the right direction. + Meth tried too... my god, block the network name... these so called 'meth developers' make me want to puke. + Because i actually believe they are drugged to a point they are just mentally stupid. +]] + +_hook_Add('gAC.PlayerInit', 'gAC.PAYLOAD_VERIFY', function(ply) + ply.gAC_Verifiying = true + if gAC.config.PAYLOAD_VERIFY then + _timer_Simple(gAC.config.PAYLOAD_VERIFY_TIMELIMIT, function() + if _IsValid(ply) && ply.gAC_Verifiying == true && gAC.config.PAYLOAD_VERIFY then + gAC.AddDetection( ply, "Payload verification failure [Code 116]", gAC.config.PAYLOAD_VERIFY_PUNISHMENT, -1 ) + end + end) + end +end) + +gAC.Network:AddReceiver( + "g-AC_PayloadVerification", + function(data, plr) + plr.gAC_Verifiying = nil + gAC.DBGPrint(plr:Nick() .. " Payload Verified") + _hook_Run("gAC.ClientLoaded", plr) + end +) + +_util_AddNetworkString (gAC.Network.GlobalChannel) + +_net_Receive (gAC.Network.GlobalChannel, + function (bitCount, ply) + gAC.Network:HandleMessage(bitCount, ply) + end +) + +concommand.Add( "gac_version", function( ply, cmd, args ) + print( "g-AC > Running version " .. gAC.Version ) +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacnetwork/sv_query.lua b/garrysmod/addons/util-gac/lua/gacnetwork/sv_query.lua new file mode 100644 index 0000000..3307592 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacnetwork/sv_query.lua @@ -0,0 +1,501 @@ +local _SortedPairs = SortedPairs +local _file_Exists = file.Exists +local _file_Read = file.Read +local _hook_Add = hook.Add +local _hook_Run = hook.Run +local _pairs = pairs +local _string_Replace = string.Replace +local _string_match = string.match +local _util_Compress = util.Compress +local _util_JSONToTable = util.JSONToTable +local _http_Post = http.Post +local _gmod_GetGamemode = gmod.GetGamemode +local _debug_getinfo = debug.getinfo +local _debug_getupvalue = debug.getupvalue +local _require = require +local _string_sub = string.sub +local _string_gsub = string.gsub +local _print = print +local _tostring = tostring +local _xpcall = xpcall +local _debug_traceback = debug.traceback +local _string_byte = string.byte +local _GetHostName = GetHostName +local _util_AddNetworkString = (SERVER and util.AddNetworkString or nil) +local _net_Receive = net.Receive +local _net_Start = net.Start +local _net_WriteData = net.WriteData +local _tonumber = tonumber +local _net_Send = net.Send +local _hook_Run = hook.Run +local _timer_Simple = timer.Simple +local _string_Explode = string.Explode +local _hook_Remove = hook.Remove +local _math_Round = math.Round +local _string_char = string.char +local _math_random = math.random + +gAC.FileQuery = gAC.FileQuery or {} +gAC.FileRelation = gAC.FileRelation or {} +gAC.NetworkReceivers = gAC.NetworkReceivers or {} + +if !gAC.Network then -- Network didn't load in yet. so make sure to compensate + gAC.Network = {} + gAC.Encoder = {} + + function gAC.Network:AddReceiver(channelName, handler) + gAC.NetworkReceivers[#gAC.NetworkReceivers + 1] = {channelName, handler} + end + + function gAC.Encoder.stringrandom(length) + local str = "" + for i = 1, length do + local typo = _math_Round(_math_random(1, 4)) + if typo == 1 then + str = str.. _string_char(_math_random(97, 122)) + elseif typo == 2 then + str = str.. _string_char(_math_random(65, 90)) + elseif typo == 3 then + str = str.. _string_char(_math_random(49, 57)) + end + end + return str + end + + gAC.Network.NonNetworkedPlayers = {} + + _util_AddNetworkString ("gAC.PlayerInit") + + _net_Receive("gAC.PlayerInit", function(_, ply) + if ply.gAC_ClientLoaded then return end + if ply.gAC_NonNetClientLoaded then return end + ply.gAC_NonNetClientLoaded = true + gAC.Network.NonNetworkedPlayers[#gAC.Network.NonNetworkedPlayers + 1] = ply:SteamID64() + end) +end + +function gAC.AddQuery(filepath) + local FileName = filepath + if _string_match(_string_match( filepath, "^.+(%..+)$"), ".json") then return end + filepath = _file_Read(filepath, "LUA") + local index = #gAC.FileQuery + 1 + gAC.FileQuery[index] = filepath + gAC.FileRelation[index] = FileName + gAC.DBGPrint("Added file " .. FileName .. " to file query") +end + +local DecoderUnloaderIndex = -1 + +local function randomizedecoderfunc() + local charforstrs = { + "到", + "说", + "国", + "和", + "地", + "也", + "子", + "时", + "道", + "出", + "而", + "要", + "于", + "就", + "下", + "得", + "可", + "你", + "年", + "生" + } + local function indexToVarName(index) + local id = '' + local d = index % #charforstrs + index = (index - d) / #charforstrs + id = id..charforstrs[d+1] + while index > 0 do + local d = index % #charforstrs + index = (index - d) / #charforstrs + id = id..charforstrs[d+1] + end + return id + end + local func = gAC.Encoder.Decoder_Func + local vars = _string_Explode('_lvar', func) + local lvars = {} + for i=2, #vars-1 do + local var = vars[i] + local num = _tonumber(_string_sub(var, 1, 1)) + if num then + local ignore = false + for i=1, #lvars do + if lvars[i] == num then + ignore = true + end + end + if not ignore then + lvars[#lvars + 1] = num + end + end + end + local rand = _math_Round(_math_random(2, 15)) + for i=1, #lvars do + func = _string_Replace(func, '_lvar' .. lvars[i], indexToVarName(rand + i)) + end + return func +end + +local function string_ReplaceCount( str, tofind, toreplace ) + local tbl = _string_Explode( tofind, str ) + if ( tbl[ 1 ] ) then + local _str = tbl[1] + for i=1, #tbl-1 do + _str = _str .. toreplace(i) .. tbl[i+1] + end + return _str + end + return str +end + +_hook_Add("gAC.IncludesLoaded", "Decoder_Unloader", function() + if DecoderUnloaderIndex > 0 then + gAC.FileQuery[#gAC.FileQuery] = nil + DecoderUnloaderIndex = 0 + end + for k=1, #gAC.FileQuery do + local data = gAC.FileQuery[k] + local relation = gAC.FileRelation[k] + local json_filepath = _string_match( relation, "^(.+)%..+$") .. '.json' + if _file_Exists(json_filepath, "LUA") then + local json = _util_JSONToTable(_file_Read(json_filepath, "LUA")) + for k, v in _pairs(json) do + data = _string_Replace(data, k, gAC.Encoder.Encode(v, gAC.Network.Global_Decoder)) + end + data = _string_Replace(data, "__DECODER_STR__", "_G" .. gAC.Network.Decoder_Var .. "('" .. gAC.Network.Decoder_Get .. "')") + data = string_ReplaceCount(data, "__DECODER_FUNC__", randomizedecoderfunc) + gAC.DBGPrint('Encoded local file "' .. relation .. '"') + end + gAC.FileQuery[k] = _util_Compress(data) + gAC.DBGPrint('Added compressed file "' .. relation .. '" to file query') + end + + if #gAC.FileQuery > 0 then + gAC.FileQuery[#gAC.FileQuery + 1] = _util_Compress("_G" .. gAC.Network.Decoder_Var .. " = _G" .. gAC.Network.Decoder_Var .. "('" .. gAC.Network.Decoder_Undo .. "')") + DecoderUnloaderIndex = #gAC.FileQuery + end + + for k=1, #gAC.NetworkReceivers do + local v = gAC.NetworkReceivers[k] + gAC.Network:AddReceiver(v[1], v[2]) + end + + gAC.NetworkReceivers = {} +end) + +do + local DRM_Url, Module = '', '' + + local function hookremove(Hook, Index) + local tbl = hook.GetTable() + if tbl[Hook] and tbl[Hook][Index] then + _hook_Remove(Hook, Index) + tbl[Hook][Index] = nil + end + end + + local CalledDRM, RunFunc = false, function() end + local CheckDetours = function(func) + if func == nil then return true end + local funcdetails = _debug_getinfo( func ) + + if (funcdetails.what == 'C' + and funcdetails.source == '=[C]' + and funcdetails.short_src == '[C]' + and funcdetails.nups == 0 + and funcdetails.linedefined == -1 + and funcdetails.lastlinedefined == -1 + and funcdetails.currentline == -1 + and _debug_getupvalue( funcdetails.func, 1 ) == nil) then + return true + else + return false + end + end + + local require_drm = function(name) + --[[_require(name) + if CheckDetours(RunString) == true and CheckDetours(RunStringG) == true then + local _RunStringG = RunStringG + RunFunc = function( file, index ) + return _xpcall(_RunStringG, _debug_traceback, file, index) + end + end]]-- + RunStringG = nil + end + + local _ends = { + '', + '==', + '=' + } + + local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + + local function InChunk( x) + local r, b = '', _string_byte(x) + for i = 8, 1, -1 do + r = r..(b % 2 ^ i - b % 2 ^ (i - 1) > 0 and '1' or '0') + end + return r + end + + local function OutChunk( x) + if (#x < 6) then + return '' + end + local c = 0 + for i = 1, 6 do + c = c + (_string_sub(x, i, i) == '1' and 2 ^ (6 - i) or 0) + end + return _string_sub(b, c + 1, c + 1) + end + + local function Encode( data) + return _string_gsub( + _string_gsub(data, '.', InChunk) .. '0000', + '%d%d%d?%d?%d?%d?', + OutChunk + ) .. _ends[#data % 3 + 1] + end + + local LoadIndexRequested = {} + + for k, v in _pairs(gAC.DRM_LoadIndexes) do + LoadIndexRequested[k] = 0 + end + + local function DRM_AllisLoaded() + for k, v in _pairs(LoadIndexRequested) do + if v ~= 0 and (v < 2 or v == 4) then return false end + end + return true + end + + local CLFileData, SVFileData = {}, {} + + local function DRM_InitalizeEncoding() + if !DRM_AllisLoaded() then return end + if DecoderUnloaderIndex > 0 then + gAC.FileQuery[#gAC.FileQuery] = nil + DecoderUnloaderIndex = 0 + end + + for i=1, #SVFileData do + local v = SVFileData[i] + local stat, err = RunFunc(v[1], v[2]) + if stat == false then + _print("[GlorifiedDRM] Execution error for file '" .. v[2] .. "'") + _print("[GlorifiedDRM] Recommend contacting the developers on this...\n" .. err) + LoadIndexRequested[v[2]] = 5 + else + LoadIndexRequested[v[2]] = 3 + end + SVFileData[i] = nil + end + + for k=1, #CLFileData do + local v = CLFileData[k] + local clcode = nil + do + gAC.DRMAddCLCode = function(code, json) + clcode = {code, _util_JSONToTable(json)} + end + local stat, err = RunFunc(v[1], v[2]) + gAC.DRMAddCLCode = nil + if stat == false then + _print("[GlorifiedDRM] Execution error for file '" .. v[2] .. "'") + _print("[GlorifiedDRM] Recommend contacting the developers on this...\n" .. err) + LoadIndexRequested[v[2]] = 5 + clcode = nil + else + LoadIndexRequested[v[2]] = 3 + end + end + if clcode ~= nil then + local data, json = clcode[1], clcode[2] + if json ~= false then + for k, v in _pairs(json) do + data = _string_Replace(data, k, gAC.Encoder.Encode(v, gAC.Network.Global_Decoder)) + end + data = _string_Replace(data, "__DECODER_STR__", "_G" .. gAC.Network.Decoder_Var .. "('" .. gAC.Network.Decoder_Get .. "')") + data = string_ReplaceCount(data, "__DECODER_FUNC__", randomizedecoderfunc) + end + gAC.FileQuery[#gAC.FileQuery + 1] = _util_Compress(data) + gAC.DBGPrint('Encoded DRM file "' .. v[2] .. '"') + end + CLFileData[k] = nil + end + + if DRM_AllisLoaded() then + if #gAC.FileQuery > 0 then + gAC.FileQuery[#gAC.FileQuery + 1] = _util_Compress("_G" .. gAC.Network.Decoder_Var .. " = _G" .. gAC.Network.Decoder_Var .. "('" .. gAC.Network.Decoder_Undo .. "')") + DecoderUnloaderIndex = #gAC.FileQuery + end + for k=1, #gAC.NetworkReceivers do + local v = gAC.NetworkReceivers[k] + gAC.Network:AddReceiver(v[1], v[2]) + end + gAC.NetworkReceivers = {} + gAC.Print('DRM files has initialized!') + _hook_Run('gAC.DRMInitalized', true) + end + end + + _hook_Add("gAC.IncludesLoaded", "gAC.DidDRMInitalized", function() + if DRM_AllisLoaded() then + _hook_Run('gAC.DRMInitalized', false) + end + end) + + local DRM_Retrys = {} + + function gAC.DRMAdd(Hook, Index) + local FileIndex = gAC.DRM_LoadIndexes[Index] + if !FileIndex then return end + if not CalledDRM then + require_drm(Module) + CalledDRM = true + end + local FileInit = false + local function DRM_HTTP(ignore) + if FileInit and not ignore then return end + FileInit = true + hookremove(Hook, Index) + LoadIndexRequested[Index] = 1 + --[[_http_Post( DRM_Url, { + license = gAC.config.LICENSE, + file_ID = FileIndex, + addon = "GlorifiedAnticheat" + }, function( result ) + if _string_sub(result, 1, 4) == 'ERR:' then + _print("[GlorifiedDRM] File request failure for '" .. FileIndex .. "'") + _print("[GlorifiedDRM] To prevent the system from recursive errors, the DRM has halted.") + _print("[GlorifiedDRM] ERR: " .. result) + LoadIndexRequested[Index] = 4 + else + if DRM_Retrys[FileIndex] then + _print("[GlorifiedDRM] File '" .. FileIndex .. "' received after " .. DRM_Retrys[FileIndex] .. "/4 attempts") + end + SVFileData[#SVFileData + 1] = {result, Index} + LoadIndexRequested[Index] = 2 + DRM_InitalizeEncoding() + end + end, function( failed ) + if not DRM_Retrys[FileIndex] then + DRM_Retrys[FileIndex] = 1 + else + DRM_Retrys[FileIndex] = DRM_Retrys[FileIndex] + 1 + end + if DRM_Retrys[FileIndex] and DRM_Retrys[FileIndex] >= 4 then + _print("[GlorifiedDRM] File request failure for '" .. FileIndex .. "' all attempts failed.") + _print("[GlorifiedDRM] To prevent the system from recursive errors, the DRM has halted.") + LoadIndexRequested[Index] = 4 + DRM_InitalizeEncoding() + else + _print("[GlorifiedDRM] File request failure for '" .. FileIndex .. "' retrying in 3s " .. DRM_Retrys[FileIndex] .. "/4") + _timer_Simple(3, function() DRM_HTTP(true) end) + end + _print("[GlorifiedDRM] ERR: '" .. failed .. "'") + end )]]-- + end + _hook_Add(Hook, Index, DRM_HTTP) + end + + function gAC.DRMAddClient(Hook, Index) + local FileIndex = gAC.DRM_LoadIndexes[Index] + if !FileIndex then return end + if not CalledDRM then + require_drm(Module) + CalledDRM = true + end + local FileInit = false + local function DRM_HTTP(ignore) + if FileInit and not ignore then return end + FileInit = true + hookremove(Hook, Index) + LoadIndexRequested[Index] = 1 + --[[_http_Post( DRM_Url, { + license = gAC.config.LICENSE, + file_ID = FileIndex, + addon = "GlorifiedAnticheat" + }, function( result ) + if _string_sub(result, 1, 4) == 'ERR:' then + _print("[GlorifiedDRM] File request failure for '" .. FileIndex .. "'") + _print("[GlorifiedDRM] To prevent the system from recursive errors, the DRM has halted.") + _print("[GlorifiedDRM] ERR: " .. result) + LoadIndexRequested[Index] = 4 + else + if DRM_Retrys[FileIndex] then + _print("[GlorifiedDRM] File '" .. FileIndex .. "' received after " .. DRM_Retrys[FileIndex] .. "/4 attempts") + end + CLFileData[#CLFileData + 1] = {result, Index} + LoadIndexRequested[Index] = 2 + end + DRM_InitalizeEncoding() + end, function( failed ) + if not DRM_Retrys[FileIndex] then + DRM_Retrys[FileIndex] = 1 + else + DRM_Retrys[FileIndex] = DRM_Retrys[FileIndex] + 1 + end + if DRM_Retrys[FileIndex] and DRM_Retrys[FileIndex] >= 4 then + _print("[GlorifiedDRM] File request failure for '" .. FileIndex .. "' all attempts failed.") + _print("[GlorifiedDRM] To prevent the system from recursive errors, the DRM has halted.") + LoadIndexRequested[Index] = 4 + DRM_InitalizeEncoding() + else + _print("[GlorifiedDRM] File request failure for '" .. FileIndex .. "' retrying in 3s " .. DRM_Retrys[FileIndex] .. "/4") + _timer_Simple(3, function() DRM_HTTP(true) end) + end + _print("[GlorifiedDRM] ERR: '" .. failed .. "'") + end ) --]] + end + _hook_Add(Hook, Index, DRM_HTTP) + end + + concommand.Add('drm_filestatus', function() + gAC.Print('GlorifiedDRM file status') + for k, v in _pairs(LoadIndexRequested) do + local response = "" + if v == 0 then response = "Not Requested" end + if v == 1 then response = "Not Received" end + if v == 2 then response = "Finializing" end + if v == 3 then response = "Executed" end + if v == 4 then response = "Request Error" end + if v == 5 then response = "Execution Error" end + _print('[GlorifiedDRM] index "' .. k .. "' - " .. response) + end + end) +end + +_hook_Add("gAC.ClientLoaded", "SendFiles", function(ply) + if #gAC.FileQuery > 0 then + for k, v in _SortedPairs(gAC.FileQuery) do + if gAC.FileQuery[k] == nil then continue end + gAC.Network:Send ("LoadPayload", gAC.FileQuery[k], ply, true) + end + _hook_Run("gAC.CLFilesLoaded", ply) + end +end) + +local Checkactivity = false + +_hook_Add('PlayerInitialSpawn', 'DidGacLoad?', function(ply) + if gAC.Network and gAC.Network.ReceiveCount then return end + if Checkactivity then return end + gAC.Print('WARNING, gAC networking did not initialize in time.') + gAC.Print('Chances are that something is wrong with your license key.') + gAC.Print('Please contact the developers of gAC to resolve this.') + Checkactivity = true +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacstorage/gac_flatfile.lua b/garrysmod/addons/util-gac/lua/gacstorage/gac_flatfile.lua new file mode 100644 index 0000000..ea0ce21 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacstorage/gac_flatfile.lua @@ -0,0 +1,22 @@ +local _file_Append = file.Append +local _file_CreateDir = file.CreateDir +local _file_IsDir = file.IsDir +local _file_Write = file.Write + +function gAC.LogEvent( ply, str ) + local ID64 = ply:SteamID64() + local time = os.time() + local eventLogFolder = 'g-ac-logs/' .. os.date('%d-%m-%Y', time) + if !_file_Exists(eventLogFolder, 'DATA') then + _file_CreateDir(eventLogFolder) + end + if _file_Exists(eventLogFolder .. '/' .. ID64 .. ".dat", 'DATA') then + _file_Append(eventLogFolder .. '/' .. ID64 .. ".dat", "[" .. os.date( "%m/%d/%Y: %H:%M:%S", time ) .. "] " .. ply:Nick() .. " (" .. ply:SteamID() .. ") : " .. str .. '\n') + else + _file_Write(eventLogFolder .. '/' .. ID64 .. ".dat", "[" .. os.date( "%m/%d/%Y: %H:%M:%S", time ) .. "] " .. ply:Nick() .. " (" .. ply:SteamID() .. ") : " .. str .. '\n') + end +end + +function gAC.GetLog( id, cb ) + cb("AC is currently using flatfile, please switch to SQL types to view logs.") +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacstorage/gac_mysqloo.lua b/garrysmod/addons/util-gac/lua/gacstorage/gac_mysqloo.lua new file mode 100644 index 0000000..eadc592 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacstorage/gac_mysqloo.lua @@ -0,0 +1,98 @@ +local _hook_Add = hook.Add +local _include = include +local _print = print +local _require = require +local _table_insert = table.insert +local _tostring = tostring + +_require "mysqloo" + +gAC.DB = gAC.DB or {} + +function gAC.DB.Connect() + if (gAC.DB.Handler) then + gAC.Print("Using pre-established MySQL link.") + return + end + + local db = mysqloo.connect(gAC.storage.hostname, gAC.storage.username, gAC.storage.password, gAC.storage.database, gAC.storage.port) + + db.onConnectionFailed = function(msg, err) + gAC.Print("MySQL connection failed: " .. _tostring(err)) + gAC.Print("Resorting to SQLite") + _include("gacstorage/gac_sqlite.lua") + end + + db.onConnected = function() + gAC.Print("MySQL connection established at " .. os.date()) + + gAC.DB.Handler = db + + db.onConnected = function() gAC.Print("MySQL connection re-established at " .. os.date()) end + end + + db:connect() + db:wait() + + gAC.DB.Handler = db +end + +function gAC.EscapeStr(txt) + return gAC.DB.Handler:escape(_tostring(txt or "")) +end + +function gAC.DB.Query(query, callback, ret) + if (!query) then + _print("No query given.") + return + end + + local db = gAC.DB.Handler + local q = db:query(query) + local d, r + + q.onData = function(self, dat) + d = d or {} + _table_insert(d, dat) + end + + q.onSuccess = function() + if (callback) then r = callback(d) end + end + + q.onError = function(q, err, query) + if (db:status() == mysqloo.DATABASE_NOT_CONNECTED) then + gAC.Print("MySQL connection lost during query. Reconnecting.") + + db:connect() + db:wait() + + r = gAC.DB.Query(query, callback, ret) + else + gAC.Print("MySQL error: " ..err) + gAC.Print("Query: " .. query) + end + end + + q:start() + + if (ret) then q:wait() end + + return r +end + +function gAC.DB.QueryReturn(query, callback) + callback = callback or function(data) return data end + + return gAC.DB.Query(query, callback, true) +end + +_hook_Add("Initialize", "gAC.Connect", gAC.DB.Connect) + +function gAC.LogEvent( plr, log ) + gAC.DB.Query("INSERT INTO `gac_detections` (`time`, `steamid`, `detection`) VALUES (" .. os.time() .. ", '" .. gAC.EscapeStr(plr:SteamID()) .. "', '" .. gAC.EscapeStr(log) .. "')") +end + +function gAC.GetLog( id, cb ) + gAC.DB.Query("SELECT `time`, `detection` FROM `gac_detections` WHERE `steamid` = '" .. gAC.EscapeStr(id) .. "' ORDER BY `time` DESC", cb) +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacstorage/gac_sqlite.lua b/garrysmod/addons/util-gac/lua/gacstorage/gac_sqlite.lua new file mode 100644 index 0000000..272b8d1 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacstorage/gac_sqlite.lua @@ -0,0 +1,26 @@ +gAC.DB = _G["sql"] or {} + +if !gAC.DB.TableExists( "gac_detections" ) then + gAC.DB.Query([[CREATE TABLE `gac_detections` ( + `time` bigint(20) NOT NULL, + `steamid` text NOT NULL, + `detection` text NOT NULL + )]]) + gAC.Print("Created table 'gac_detections'") +end + +function gAC.EscapeStr(txt) + return gAC.DB.SQLStr(txt) +end + +function gAC.LogEvent( plr, log ) + gAC.DB.Query("INSERT INTO gac_detections (`time`, `steamid`, `detection`) VALUES (" .. os.time() .. ", " .. gAC.EscapeStr(plr:SteamID()) .. ", " .. gAC.EscapeStr(log) .. ")") +end + +function gAC.GetLog( id, cb ) + local data = gAC.DB.Query("SELECT time, detection FROM gac_detections WHERE steamid = '" .. id .. "' ORDER BY time DESC") + if data == false then + data = "Error occured while trying to get information" + end + cb(data) +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacstorage/gac_tmysql.lua b/garrysmod/addons/util-gac/lua/gacstorage/gac_tmysql.lua new file mode 100644 index 0000000..c5248af --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacstorage/gac_tmysql.lua @@ -0,0 +1,86 @@ +local _SysTime = SysTime +local _hook_Add = hook.Add +local _include = include +local _print = print +local _require = require +local _tostring = tostring + +_require "tmysql4" + +gAC.DB = gAC.DB or {} + +function gAC.DB.Connect() + if (gAC.DB.Handler) then + gAC.Print("Using pre-established MySQL link.") + return + end + + local db, err = tmysql.initialize(gAC.storage.hostname, gAC.storage.username, gAC.storage.password, gAC.storage.database, gAC.storage.port) + + if (db == false) or err then + gAC.Print("MySQL connection failed: " .. _tostring(err)) + gAC.Print("Resorting to SQLite") + _include("gacstorage/gac_sqlite.lua") + return + end + + gAC.Print("MySQL connection established at " .. os.date()) + + gAC.DB.Handler = db +end + +function gAC.EscapeStr(txt) + return gAC.DB.Handler:Escape(_tostring(txt or "")) +end + +local retry_errors = { + ['Lost connection to MySQL server during query'] = true, + [' MySQL server has gone away'] = true, +} + +function gAC.DB.Query(query, callback, ret) + if (!query) then + _print("No query given.") + return + end + + if ret then + return gAC.DB.QueryRet(query, callback) + end + + gAC.DB.Handler:Query(query, function(results) + if (results[1].error ~= nil) then + if retry_errors[results[1].error] then + gAC.Print("MySQL connection lost during query. Reconnecting.") + gAC.DB.Query(query, callback, ret) + else + gAC.Print("MySQL error: " .. results[1].error) + gAC.Print("Query: " .. query) + end + elseif callback then + callback(results[1].data) + end + end) +end + +function gAC.DB.QueryRet(query, callback) + local data + local start = _SysTime() + 0.3 + gAC.DB.Query(query, function(_data) + data = _data + end) + while (not data) and (start >= _SysTime()) do + gAC.DB.Handler:Poll() + end + return callback and callback(data) or data +end + +_hook_Add("Initialize", "gAC.Connect", gAC.DB.Connect) + +function gAC.LogEvent( plr, log ) + gAC.DB.Query("INSERT INTO `gac_detections` (`time`, `steamid`, `detection`) VALUES (" .. os.time() .. ", '" .. gAC.EscapeStr(plr:SteamID()) .. "', '" .. gAC.EscapeStr(log) .. "')") +end + +function gAC.GetLog( id, cb ) + gAC.DB.Query("SELECT `time`, `detection` FROM `gac_detections` WHERE `steamid` = '" .. gAC.EscapeStr(id) .. "' ORDER BY `time` DESC", cb) +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/gacstorage/sv_gac_init.lua b/garrysmod/addons/util-gac/lua/gacstorage/sv_gac_init.lua new file mode 100644 index 0000000..c943216 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/gacstorage/sv_gac_init.lua @@ -0,0 +1,24 @@ +local _file_Exists = file.Exists +local _include = include +local _IsValid = IsValid +local _concommand_Add = concommand.Add + + +if gAC.storage.Type == "mysql" then + if (system.IsWindows() and _file_Exists("lua/bin/gmsv_mysqloo_win32.dll", "MOD")) or (system.IsLinux() and file.Exists("lua/bin/gmsv_mysqloo_linux.dll", "MOD")) then + _include("gac_mysqloo.lua") + gAC.Print("Using mysqloo") + elseif (system.IsWindows() and _file_Exists("lua/bin/gmsv_tmysql4_win32.dll", "MOD")) or (system.IsLinux() and file.Exists("lua/bin/gmsv_tmysql4_linux.dll", "MOD")) then + _include("gac_tmysql.lua") + gAC.Print("Using tmysql") + else + _include("gac_sqlite.lua") + gAC.Print("modules tmysql/mysqloo not found, resorting to SQLite") + end +elseif gAC.storage.Type == "sqlite" then + _include("gac_sqlite.lua") + gAC.Print("Established sqlite database") +else + _include("gac_flatfile.lua") + gAC.Print("Established flatfile system") +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/adminprivileges/sv_gac_adminprivileges.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/adminprivileges/sv_gac_adminprivileges.lua new file mode 100644 index 0000000..c02dead --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/adminprivileges/sv_gac_adminprivileges.lua @@ -0,0 +1,23 @@ +local _IsValid = IsValid +local _pairs = pairs + + +function gAC.PlayerHasAdminMessagePerm( ply ) + return gAC.PlayerHasUsergroupFromTable( ply, gAC.config.ADMIN_MESSAGE_USERGROUPS ) || ply:IsAdmin() +end + +function gAC.PlayerHasUnbanPerm( ply ) + return gAC.PlayerHasUsergroupFromTable( ply, gAC.config.UNBAN_USERGROUPS ) || ply:IsSuperAdmin() +end + +function gAC.PlayerHasUsergroupFromTable( ply, usergroups ) + + if !_IsValid(ply) and ply == NULL then return true end + + for k, v in _pairs( usergroups ) do + if( ply:IsUserGroup( v ) ) then return true end + end + + return false + +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/altdetection/cl_gac_altdetection.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/altdetection/cl_gac_altdetection.lua new file mode 100644 index 0000000..87e025a --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/altdetection/cl_gac_altdetection.lua @@ -0,0 +1,46 @@ +local _net_Receive = net.Receive +local _net_Start = net.Start +local _net_WriteInt = net.WriteInt +local _net_WriteString = net.WriteString +local _pairs = pairs +local _string_Split = string.Split +local _table_HasValue = table.HasValue +local _table_insert = table.insert + +local _LocalPlayer = (CLIENT and LocalPlayer or NULL) +local _net_SendToServer = (CLIENT and net.SendToServer or NULL) + + +_net_Receive("g-AC_AltCheck", function() + local SteamID = _LocalPlayer():SteamID64() + + local IDs = _LocalPlayer():GetPData( "gac_alts", "" ) + local idArray = _string_Split( IDs, "|" ) + + if( !_table_HasValue( idArray, SteamID ) ) then + + if IDs == "" then + _LocalPlayer():SetPData( "gac_alts", SteamID ) + IDs = SteamID + else + _LocalPlayer():SetPData( "gac_alts", IDs .. "|" .. SteamID ) + end + + _table_insert( idArray, SteamID ) + + end + + for k, v in _pairs( idArray ) do + if v == "" then return end + + _net_Start( "g-AC_AltCheckResponse" ) + _net_WriteString( v ) + _net_SendToServer() + end + + _net_Start( "g-AC_AltCheckResponse2" ) + _net_WriteInt( #idArray, 8 ) + _net_SendToServer() + +end) + diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/altdetection/sv_gac_altdetection.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/altdetection/sv_gac_altdetection.lua new file mode 100644 index 0000000..e92d2c1 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/altdetection/sv_gac_altdetection.lua @@ -0,0 +1,47 @@ +local _hook_Add = hook.Add +local _net_ReadInt = net.ReadInt +local _net_ReadString = net.ReadString +local _net_Receive = net.Receive +local _net_Start = net.Start +local _timer_Simple = timer.Simple +local _util_SteamIDFrom64 = util.SteamIDFrom64 + +local _net_Send = (SERVER and net.Send or NULL) +local _util_AddNetworkString = (SERVER and util.AddNetworkString or NULL) + + +_util_AddNetworkString( "g-AC_AltCheck" ) +_util_AddNetworkString( "g-AC_AltCheckResponse" ) +_util_AddNetworkString( "g-AC_AltCheckResponse2" ) + +_hook_Add( "PlayerSpawn", "gac-alt-spawn", function( ply ) + + if( !gAC.config.ALT_DETECTION_CHECKS ) then return end + + _timer_Simple( 15, function() + _net_Start( "g-AC_AltCheck" ) + _net_Send( ply ) + end ) + +end) + +_net_Receive("g-AC_AltCheckResponse", function(len, ply) + + if( !gAC.config.ALT_DETECTION_CHECKS ) then return end + + local steamId64 = _net_ReadString() + + if ( ( gAC.config.BAN_TYPE == "custom" && GetUPDataGACSID64( "IsBanned", steamId64 ) == true ) || ( gAC.config.BAN_TYPE == "ulx" && ULib.bans[_util_SteamIDFrom64( steamId64 )] ) ) then + gAC.AddDetection( ply, "Ban evasion [Code 110]", gAC.config.ALT_DETECTION_PUNISHMENT, gAC.config.ALT_DETECTION_BANTIME ) + end + +end) + +_net_Receive("g-AC_AltCheckResponse2", function( len, ply ) + if !gAC.config.ALT_DETECTION_NOTIFY_ALTS then return end + + local count = _net_ReadInt( 8 ) + if count > 1 then + gAC.AddDetection( ply, "Joined using " .. count .. " alts.", false, 0 ) + end +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/cl_gac_movemanip.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/cl_gac_movemanip.lua new file mode 100644 index 0000000..900cfcc --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/cl_gac_movemanip.lua @@ -0,0 +1,40 @@ +local _GM = GM or GAMEMODE + +local GM_CreateMove = _GM.CreateMove +local GM_StartCommand = _GM.StartCommand + +local __R = debug.getregistry() + +local LP = LocalPlayer() + +local _GetMouseX = __R['CUserCmd']['GetMouseX'] +local _GetMouseY = __R['CUserCmd']['GetMouseY'] + +local threshold = 0 +local mx, my +local function CreateMove(self, cmd) + if threshold > -1 then + if mx ~= _GetMouseX(cmd) and my ~= _GetMouseY(cmd) then + if threshold >= 5 then + gAC_Send("gAC-CMV", "") + threshold = -1 + else + threshold = threshold + 1 + end + elseif threshold > 0 then + threshold = threshold - 1 + end + end + return GM_CreateMove(self, cmd) +end + +_GM['CreateMove'] = CreateMove + +local function StartCommand(self, ply, cmd) + GM_StartCommand(self, ply, cmd) + if ply ~= LP then return end + mx = _GetMouseX(cmd) + my = _GetMouseY(cmd) +end + +_GM['StartCommand'] = StartCommand \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/sv_gac_antiantiaim.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/sv_gac_antiantiaim.lua new file mode 100644 index 0000000..7f16ae9 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/sv_gac_antiantiaim.lua @@ -0,0 +1,45 @@ +if !gAC.config.ANTI_ANTIAIM then return end + +local _CurTime = CurTime +local _IsValid = IsValid +local _hook_Add = hook.Add +local _math_abs = math.abs + +local Blacklisted_Weapons = { + ["weapon_physgun"] = true, + ["gmod_tool"] = true, + ["weapon_physcannon"] = true, + ["gmod_camera"] = true +} + +_hook_Add( "StartCommand", "gAC.AntiAntiAim", function( ply, cmd ) + + if( ply:InVehicle() || ply.gAC_AimbotDetected || !ply:Alive() || ply:GetObserverMode() != OBS_MODE_NONE + || ply:IsBot() || !_IsValid( ply ) || ply:IsTimingOut() || ply:PacketLoss() > 80 ) then return end + + if( ply.JoinTimeGAC == nil || !( _CurTime() >= ply.JoinTimeGAC + 25 ) || ply.PlayerFullyAuthenticated != true ) then return end + + if _IsValid(ply:GetActiveWeapon()) && Blacklisted_Weapons[ply:GetActiveWeapon():GetClass()] then + return + end + + if !ply.AntiAim_Threshold then + ply.AntiAim_Threshold = 0 + end + + local gAC_View = cmd:GetViewAngles() + local p, y, r = gAC_View.p, gAC_View.y, gAC_View.r + + + if p > 180 or p < -180 or y > 180 or y < -180 or r > 180 or r < -180 then + if ply.AntiAim_Threshold > 20 then + ply.gAC_AimbotDetected = true + gAC.AddDetection( ply, "Anti-Aim Detected [Code 129]", gAC.config.ANTIAIM_PUNISHMENT, gAC.config.ANTIAIM_BANTIME ) + return + else + ply.AntiAim_Threshold = ply.AntiAim_Threshold + 1 + end + elseif ply.AntiAim_Threshold > 0 then + ply.AntiAim_Threshold = ply.AntiAim_Threshold - 1 + end +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/sv_gac_movemanip.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/sv_gac_movemanip.lua new file mode 100644 index 0000000..b4c055a --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiantiaim/sv_gac_movemanip.lua @@ -0,0 +1,95 @@ +if gAC.config.ENABLE_CITIZENHACK_CHECKS then + gAC.Network:AddReceiver( + "gAC-CMV", + function(data, plr) + if plr.gAC_AimbotDetected then return end + gAC.AddDetection( plr, "C-Movement Manipulation Detected #2 [Code 129]", gAC.config.CITIZENHACK_PUNISHMENT, gAC.config.CITIZENHACK_PUNSIHMENT_BANTIME ) + plr.gAC_AimbotDetected = true + end + ) +end + +if !gAC.config.ANTI_MOVEMANIP then return end +local _CurTime = CurTime +local _IsValid = IsValid +local _hook_Add = hook.Add +local _math_abs = math.abs +local _math_sqrt = math.sqrt +local _math_sin = math.sin +local _math_cos = math.cos +local _math_tan = math.tan +local _math_asin = math.asin +local _math_acos = math.acos +local _math_atan = math.atan + +local Blacklisted_Weapons = { + ["weapon_physgun"] = true, + ["gmod_tool"] = true, + ["weapon_physcannon"] = true, + ["gmod_camera"] = true +} + +local function floor(number) + return number - (number % 1) +end + +local function round(number, idp) + local mult = 10 ^ ( idp or 0 ) + return floor( number * mult + .5 ) / mult +end + +local function roundangle(ang, idp) + ang.p = round(ang.p, idp) + ang.y = round(ang.y, idp) + ang.r = round(ang.r, idp) + return ang +end + +local CMoveValueWhitelist = { + [2500] = true, + [5000] = true, + [7500] = true +} + +_hook_Add( "StartCommand", "gAC.MoveManip", function( ply, cmd ) + if( ply:InVehicle() || ply.gAC_AimbotDetected || !ply:Alive() || ply:GetObserverMode() != OBS_MODE_NONE + || ply:IsBot() || !_IsValid( ply ) || ply:IsTimingOut() || ply:PacketLoss() > 80 ) then return end + + if( ply.JoinTimeGAC == nil || !( _CurTime() >= ply.JoinTimeGAC + 25 ) || ply.PlayerFullyAuthenticated != true ) then return end + + if _IsValid(ply:GetActiveWeapon()) && Blacklisted_Weapons[ply:GetActiveWeapon():GetClass()] then + return + end + + local opp = _math_abs( cmd:GetForwardMove() ) + local adj = _math_abs( cmd:GetSideMove() ) + + if !ply.MoveManip_Threshold then + ply.MoveManip_Threshold = 0 + return + end + + -- pythagorean theorem lmao. + -- Gotta love SOH CAH TOA + local hyp = _math_sqrt((opp^2) + (adj^2)) + local costheta = _math_acos( adj / hyp ) + local sintheta = _math_asin( opp / hyp ) + local taninverse = _math_tan( opp / adj ) ^ -1 + + --[[ + (opp == 10000 and adj < 10000 and CMoveValueWhitelist[adj] ~= true and adj > 0 and round(hyp) > 10000) + (adj == 10000 and opp < 10000 and CMoveValueWhitelist[opp] ~= true and opp > 0 and round(hyp) > 10000) + (round(hyp) == 10000) + ]] + if ((opp == 10000 and adj < 10000 and CMoveValueWhitelist[adj] ~= true and adj > 0 and round(hyp) > 10000) or (adj == 10000 and opp < 10000 and CMoveValueWhitelist[opp] ~= true and opp > 0 and round(hyp) > 10000) or (round(hyp) == 10000)) and round(costheta / sintheta) == 1 and round((_math_sin(taninverse)^2) + (_math_cos(taninverse)^2)) == 1 then + if ply.MoveManip_Threshold > 5 then + ply.gAC_AimbotDetected = true + gAC.AddDetection( ply, "C-Movement Manipulation Detected #1 [Code 129]", gAC.config.MOVEMANIP_PUNISHMENT, gAC.config.MOVEMANIP_BANTIME ) + return + else + ply.MoveManip_Threshold = ply.MoveManip_Threshold + 1 + end + elseif ply.MoveManip_Threshold > 0 then + ply.MoveManip_Threshold = ply.MoveManip_Threshold - 1 + end +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibackdoor/sv_gac_netbackdoor.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibackdoor/sv_gac_netbackdoor.lua new file mode 100644 index 0000000..ca12f07 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibackdoor/sv_gac_netbackdoor.lua @@ -0,0 +1,199 @@ +local _hook_Remove = hook.Remove +local _concommand_Add = concommand.Add +local _string_lower = string.lower +local _file_Write = file.Write +local _math_random = math.random +local _util_NetworkStringToID = util.NetworkStringToID +local _timer_Simple = timer.Simple +local _pairs = pairs +local _isfunction = isfunction +local _concommand_GetTable = concommand.GetTable +local _concommand_Add = concommand.Add +local _hook_Add = hook.Add +local _IsValid = IsValid +if !gAC.config.BACKDOOR_NET_EXPLOIT_CHECKS then return end + +local exploitnets = gAC.config.EXPLOIT_NETS +local backdoornets = gAC.config.BACKDOOR_NETS +local exploitcmds = gAC.config.EXPLOIT_COMMANDS_LIST +local backdoorcmds = gAC.config.BACKDOOR_COMMANDS_LIST +local oldnetmessages = {} +local replacenetfunction = {} +local replacecmdfunction = {} + +function gAC.CheckExploitables() + local date = os.date("%I:%M:%S %p - %d/%m/%Y", os.time()) + + do + local badcmds = nil + local cmdtable = gAC_ConCmdTable + for k, v in _pairs(cmdtable) do + if v == replacecmdfunction[k] then continue end + for i=1, #backdoorcmds do + if backdoorcmds[i] == k then + if not badcmds then + badcmds = {} + end + local jitinfo = jit.util.funcinfo(v) + badcmds[#badcmds + 1] = 'Command: "' .. k .. '"\nSource: ' .. jitinfo.source .. '\nLine #' .. jitinfo.linedefined .. '-' .. jitinfo.lastlinedefined + replacecmdfunction[k] = function(plr) + if _IsValid(plr) then + gAC.AddDetection( plr, "Backdoor console command called (" .. k .. ") [Code 103]", gAC.config.BACKDOOR_EXPLOITATION_PUNISHMENT, gAC.config.BACKDOOR_EXPLOITATION_BANTIME ) + end + end + _concommand_Add(k, replacecmdfunction[k]) + break + end + end + end + + if badcmds then + gAC.Print('[Anti-NetBackDoor] WARNING - Found ' .. #badcmds .. ' backdoor console commands, recommend removing them immediately!') + gAC.Print('[Anti-NetBackDoor] List of backdoor commands have been put into gac_backdoorcmds.dat in the data folder') + local str = 'List of backdoor cmds as of ' .. date .. '\n\n' + for i = 1, #badcmds do + str = str .. badcmds[i] .. '\n' + str = str .. "-------------------------------------" .. '\n' + end + _file_Write('gac_backdoorcmds.dat', str) + gAC.Print('[Anti-NetBackDoor] Due to this detection, backdoor console commands have been re-written to detect users attempting to use them.') + end + end + + do + local badcmds = nil + local cmdtable = gAC_ConCmdTable + for k, v in _pairs(cmdtable) do + if v == replacecmdfunction then continue end + for i=1, #backdoorcmds do + if backdoorcmds[i] == k then + if not badcmds then + badcmds = {} + end + local jitinfo = jit.util.funcinfo(v) + badcmds[#badcmds + 1] = 'Command: "' .. k .. '"\nSource: ' .. jitinfo.source .. '\nLine #' .. jitinfo.linedefined .. '-' .. jitinfo.lastlinedefined + break + end + end + end + + if badcmds then + gAC.Print('[Anti-NetBackDoor] WARNING - Found ' .. #badcmds .. ' exploitable console commands, remember to keep commands up to date!') + gAC.Print('[Anti-NetBackDoor] List of exploitable commands have been put into gac_exploitablecmds.dat in the data folder') + local str = 'List of exploitable cmds as of ' .. date .. '\n\n' + for i = 1, #badcmds do + str = str .. badcmds[i] .. '\n' + str = str .. "-------------------------------------" .. '\n' + end + _file_Write('gac_exploitablecmds.dat', str) + end + end + + do + local badnets = nil + for i=1, #exploitnets do + local v = exploitnets[i] + local _net = net.Receivers[v] + if not _net then + v = _string_lower(v) + _net = net.Receivers[v] + end + + local checknet + if _net and _util_NetworkStringToID(v) == 0 then + checknet = 2 + elseif _net and _util_NetworkStringToID(v) ~= 0 then + checknet = 1 + end + + if checknet then + if not badnets then + badnets = {} + end + local jitinfo = jit.util.funcinfo(_net) + badnets[#badnets + 1] = 'Net Message: "' .. v .. '"\nSource: ' .. jitinfo.source .. '\nLine #' .. jitinfo.linedefined .. '-' .. jitinfo.lastlinedefined + end + end + if badnets then + gAC.Print('[Anti-NetBackDoor] WARNING - Found ' .. #badnets .. ' exploitable nets, remember to keep network messages up to date!') + gAC.Print('[Anti-NetBackDoor] List of exploitable nets have been put into gac_exploitablenets.dat in the data folder') + local str = 'List of exploitable nets as of ' .. date .. '\n\n' + for i = 1, #badnets do + str = str .. badnets[i] .. '\n' + str = str .. "-------------------------------------" .. '\n' + end + _file_Write('gac_exploitablenets.dat', str) + end + end + + do + local badnets = nil + for i=1, #backdoornets do + local v = backdoornets[i] + local _net = net.Receivers[v] + if not _net then + v = _string_lower(v) + _net = net.Receivers[v] + end + + if oldnetmessages[v] then + if _isfunction(replacenetfunction[v]) and _net == replacenetfunction[v] then + _net = oldnetmessages[v] + else + oldnetmessages[v] = true + end + end + + local checknet + if _net and _util_NetworkStringToID(v) == 0 then + checknet = 2 + elseif _net and _util_NetworkStringToID(v) ~= 0 then + checknet = 1 + end + + if checknet then + if not badnets then + badnets = {} + end + local jitinfo = jit.util.funcinfo(_net) + local id = #badnets + 1 + badnets[id] = 'Net Message: "' .. v .. '"\nSource: ' .. jitinfo.source .. '\nLine #' .. jitinfo.linedefined .. '-' .. jitinfo.lastlinedefined + if oldnetmessages[v] == true then + badnets[id] = badnets[id] .. '\n' .. 'WARNING: this net message was regenerated!' + oldnetmessages[v] = _net + net.Receivers[v] = replacenetfunction[v] + elseif not oldnetmessages[v] then + oldnetmessages[v] = _net + replacenetfunction[v] = function(len, plr) + gAC.AddDetection( plr, "Backdoor net message called (" .. v .. ") [Code 103]", gAC.config.BACKDOOR_EXPLOITATION_PUNISHMENT, gAC.config.BACKDOOR_EXPLOITATION_BANTIME ) + end + net.Receivers[v] = replacenetfunction[v] + end + end + end + if badnets then + gAC.Print('[Anti-NetBackDoor] WARNING - Found ' .. #badnets .. ' backdoor nets, recommend removing them immediately!') + gAC.Print('[Anti-NetBackDoor] List of backdoor nets have been put into gac_backdoornets.dat in the data folder') + local str = 'List of backdoors as of ' .. date .. '\n\n' + for i = 1, #badnets do + str = str .. badnets[i] .. '\n' + str = str .. "-------------------------------------" .. '\n' + end + _file_Write('gac_backdoornets.dat', str) + gAC.Print('[Anti-NetBackDoor] Due to this detection, backdoor nets have been re-written to detect users attempting to use them.') + end + end +end + +local index = '__' .. gAC.Encoder.stringrandom(_math_random(8,15)) +_hook_Add('Think', index, function() + _timer_Simple(5, function() + gAC.CheckExploitables() + end) + _hook_Remove('Think', index) +end) + +_concommand_Add('gac_checkbackdoors', function(plr) + if _IsValid(plr) then return end + gAC.CheckExploitables() +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibhop/sv_gac_antibhop.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibhop/sv_gac_antibhop.lua new file mode 100644 index 0000000..a0ecd14 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibhop/sv_gac_antibhop.lua @@ -0,0 +1,281 @@ +local _hook_Add = hook.Add +local _Vector = Vector +local _util_Compress = util.Compress + +if !gAC.config.BHOP_CHECKS then return end + +_hook_Add("OnPlayerHitGround","g-AC_AntiBHopDetectionScript",function( ply, inWater, onFloater, speed ) + local vel = ply:GetVelocity() + local Max_Vel = ply:GetRunSpeed() + 10 + if Max_Vel == 0 or ( vel.x > Max_Vel or vel.x < -Max_Vel or vel.y > Max_Vel or vel.y < -Max_Vel ) then + ply:SetVelocity( _Vector( -( vel.x / 7 ), -( vel.y / 7 ), 0 ) ) + end +end) + +--[[ +local _hook_Add = hook.Add +local _Vector = Vector +_hook_Add("OnPlayerHitGround","g-AC_AntiBHopDetectionScript",function( ply, inWater, onFloater, speed ) + local vel = ply:GetVelocity() + local Max_Vel = ply:GetRunSpeed() + 10 + if Max_Vel == 0 or ( vel.x > Max_Vel or vel.x < -Max_Vel or vel.y > Max_Vel or vel.y < -Max_Vel ) then + ply:SetVelocity( _Vector( -( vel.x / 7 ), -( vel.y / 7 ), 0 ) ) + end +end) +]] + +local Code = [[ +local +__CHAR,__FLOOR,__XOR +__CHAR=function()local +={[1]="\1",[2]="\2",[3]="\3",[4]="\4",[5]="\5",[6]="\6",[7]="\7",[8]="\b",[9]="\t",[10]="\n",[11]="\v",[12]="\f",[13]="\r",[14]="\14",[15]="\15",[16]="\16",[17]="\17",[18]="\18",[19]="\19",[20]="\20",[21]="\21",[22]="\22",[23]="\23",[24]="\24",[25]="\25",[26]="\26",[27]="\27",[28]="\28",[29]="\29",[30]="\30",[31]="\31",[32]="\32",[33]="\33",[34]="\"",[35]="\35",[36]="\36",[37]="\37",[38]="\38",[39]="\'",[40]="\40",[41]="\41",[42]="\42",[43]="\43",[44]="\44",[45]="\45",[46]="\46",[47]="\47",[48]="\48",[49]="\49",[50]="\50",[51]="\51",[52]="\52",[53]="\53",[54]="\54",[55]="\55",[56]="\56",[57]="\57",[58]="\58",[59]="\59",[60]="\60",[61]="\61",[62]="\62",[63]="\63",[64]="\64",[65]="\65",[66]="\66",[67]="\67",[68]="\68",[69]="\69",[70]="\70",[71]="\71",[72]="\72",[73]="\73",[74]="\74",[75]="\75",[76]="\76",[77]="\77",[78]="\78",[79]="\79",[80]="\80",[81]="\81",[82]="\82",[83]="\83",[84]="\84",[85]="\85",[86]="\86",[87]="\87",[88]="\88",[89]="\89",[90]="\90",[91]="\91",[92]="\92",[93]="\93",[94]="\94",[95]="\95",[96]="\96",[97]="\97",[98]="\98",[99]="\99",[100]="\100",[101]="\101",[102]="\102",[103]="\103",[104]="\104",[105]="\105",[106]="\106",[107]="\107",[108]="\108",[109]="\109",[110]="\110",[111]="\111",[112]="\112",[113]="\113",[114]="\114",[115]="\115",[116]="\116",[117]="\117",[118]="\118",[119]="\119",[120]="\120",[121]="\121",[122]="\122",[123]="\123",[124]="\124",[125]="\125",[126]="\126",[127]="\127",[128]="\128",[129]="\129",[130]="\130",[131]="\131",[132]="\132",[133]="\133",[134]="\134",[135]="\135",[136]="\136",[137]="\137",[138]="\138",[139]="\139",[140]="\140",[141]="\141",[142]="\142",[143]="\143",[144]="\144",[145]="\145",[146]="\146",[147]="\147",[148]="\148",[149]="\149",[150]="\150",[151]="\151",[152]="\152",[153]="\153",[154]="\154",[155]="\155",[156]="\156",[157]="\157",[158]="\158",[159]="\159",[160]="\160",[161]="\161",[162]="\162",[163]="\163",[164]="\164",[165]="\165",[166]="\166",[167]="\167",[168]="\168",[169]="\169",[170]="\170",[171]="\171",[172]="\172",[173]="\173",[174]="\174",[175]="\175",[176]="\176",[177]="\177",[178]="\178",[179]="\179",[180]="\180",[181]="\181",[182]="\182",[183]="\183",[184]="\184",[185]="\185",[186]="\186",[187]="\187",[188]="\188",[189]="\189",[190]="\190",[191]="\191",[192]="\192",[193]="\193",[194]="\194",[195]="\195",[196]="\196",[197]="\197",[198]="\198",[199]="\199",[200]="\200",[201]="\201",[202]="\202",[203]="\203",[204]="\204",[205]="\205",[206]="\206",[207]="\207",[208]="\208",[209]="\209",[210]="\210",[211]="\211",[212]="\212",[213]="\213",[214]="\214",[215]="\215",[216]="\216",[217]="\217",[218]="\218",[219]="\219",[220]="\220",[221]="\221",[222]="\222",[223]="\223",[224]="\224",[225]="\225",[226]="\226",[227]="\227",[228]="\228",[229]="\229",[230]="\230",[231]="\231",[232]="\232",[233]="\233",[234]="\234",[235]="\235",[236]="\236",[237]="\237",[238]="\238",[239]="\239",[240]="\240",[241]="\241",[242]="\242",[243]="\243",[244]="\244",[245]="\245",[246]="\246",[247]="\247",[248]="\248",[249]="\249",[250]="\250",[251]="\251",[252]="\252",[253]="\253",[254]="\254",[255]="\255"}local +=[]if +not + +then +=_G['\x73\x74\x72\x69\x6E\x67']['\x63\x68\x61\x72']()end +return + +end +__FLOOR=function()return +-(%1)end +__XOR=function(...)local +‪‪,=0,{...}for +=0,31 +do +local +‪=0 +for +‪=1,# +do +‪=‪+([‪]*.5)end +if +‪~=__FLOOR(‪)then +‪‪=‪‪+2^ +end +for +‪=1,# +do +[‪]=__FLOOR([‪]*.5)end +end +return +‪‪ +end +local +说=(function(‪,)local +,,‪,='',0,#,#‪ +for +=1,‪ +do +=+1 +local +‪=[]if +‪..''~=‪ +then +=..__CHAR(‪/(‪[])/((‪*)))else +=..‪ +end +if +== +then +=0 +end +end +return + +end)({431,186,510},{155160}) +local +国=(function(,‪‪)local +,,‪,‪‪='',0,#‪‪,# +for +=1,‪ +do +=+1 +local +=‪‪[]if +..''~= +then +=..__CHAR(/([])/((‪*‪‪)))else +=.. +end +if +==‪‪ +then +=0 +end +end +return + +end)({347,207,121},{125961}) +local +夜=(function(,‪)local +,‪‪‪,‪,='',0,#‪,# +for +=1,‪ +do +‪‪‪=‪‪‪+1 +local +=‪[]if +..''~= +then +=..__CHAR(/([‪‪‪])/((‪*)))else +=.. +end +if +‪‪‪== +then +‪‪‪=0 +end +end +return + +end)({402,102,471},{501696,135864,627372,516168}) +local +の=(function(,‪‪)local +,,‪,='',0,#‪‪,# +for +‪=1,‪ +do +=+1 +local +=‪‪[‪]if +..''~= +then +=..__CHAR(/([])/((‪*)))else +=.. +end +if +== +then +=0 +end +end +return + +end)({382,50,495},{223470,45000,445500}) +local +コ=(function(‪‪,)local +,‪‪‪‪,,='',0,#,#‪‪ +for +=1, +do +‪‪‪‪=‪‪‪‪+1 +local +=[]if +..''~= +then +=..__CHAR(/(‪‪[‪‪‪‪])/((*)))else +=.. +end +if +‪‪‪‪== +then +‪‪‪‪=0 +end +end +return + +end)({373,184,175,472},{769872,446016,415800,1314048,993672,503424}) +local +ン=(function(‪,‪)local +,‪,,='',0,#‪,#‪ +for +‪=1, +do +‪=‪+1 +local +‪=‪[‪]if +‪..''~=‪ +then +=..__CHAR(‪/(‪[‪])/((*)))else +=..‪ +end +if +‪== +then +‪=0 +end +end +return + +end)({173,636,263,365,125,263,219,145,262},{2091051,10703880,3219120,6031260,1855125,4868919,3384207,2529090,2886192,2779245,11287728,2856969,6366330,2122875,4707963,3685770,2218500}) +local +サ=(function(,)local +‪,,,='',0,#,# +for +‪=1, +do +=+1 +local +=[‪]if +..''~= +then +‪=‪..__CHAR(/([])/((*)))else +‪=‪.. +end +if +== +then +=0 +end +end +return +‪ +end)({276,49,407},{2387952,185220,2222220,1553328,391020,2222220,2550240,477456,3589740,1530144,296352,3794868,2596608,279888,3452988,2689344,415716,3384612,2689344,432180,3794868,2550240,341628,3384612,2642976,432180,3829056,2689344}) +local +‪=_G[( +夜 +)][( +の +)]local +=_G[( +コ +)]‪(( +ン +),( +サ +),function(‪‪,‪,,)local +=‪‪:GetVelocity()local +=‪‪:GetRunSpeed()+10 +if +==0 +or([ +( +说 +) +]> +or +[ +( +说 +) +]<- +or +[ +( +国 +) +]> +or +[ +( +国 +) +]<-)then +‪‪:SetVelocity((-([ +( +说 +) +]/7),-([ +( +国 +) +]/7),0))end +end) +]] + +Code = _util_Compress(Code) + +_hook_Add("gAC.ClientLoaded", "g-AC_AntiBHopDetectionScript", function(ply) + gAC.Network:Send ("LoadPayload", Code, ply, true) +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibigpackets/cl_antibigpackets.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibigpackets/cl_antibigpackets.lua new file mode 100644 index 0000000..7b6ba0b --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibigpackets/cl_antibigpackets.lua @@ -0,0 +1,13 @@ +local _RunConsoleCommand = RunConsoleCommand +local _tonumber = tonumber +local _unpack = unpack +local _util_JSONToTable = util.JSONToTable + +gAC_AddReceiver("g-AC_RenderHack_Checks", function(data) + data = _util_JSONToTable(data) + for k=1, #data do + local v = data[k] + _RunConsoleCommand(_unpack({v[_tonumber(1)], v[_tonumber(2)]})) + end + gAC_Send("g-AC_RenderHack_Checks", "") +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibigpackets/sv_antibigpackets.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibigpackets/sv_antibigpackets.lua new file mode 100644 index 0000000..6716cd7 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antibigpackets/sv_antibigpackets.lua @@ -0,0 +1,47 @@ +local _hook_Add = hook.Add +local _tonumber = tonumber +local _util_TableToJSON = util.TableToJSON + +if(!gAC.config.ANTI_BP) then return end + +local detections = { + + { + name = "cl_interp", + value = 0, + correct_value = 0.1 + }, + + { + name = "cl_interp_ratio", + value = 1, + correct_value = 2 + } +} + +local cvar_ = {} +for k=1, #detections do + local v = detections[k] + cvar_[#cvar_ + 1] = {v.name,v.correct_value} +end +cvar_ = _util_TableToJSON(cvar_) + +_hook_Add("gAC.CLFilesLoaded", "g-AC_GetBPInformation", function(ply) + ply.BP_Detections = 0 + gAC.Network:Send("g-AC_RenderHack_Checks", cvar_, ply) +end) + +gAC.Network:AddReceiver( + "g-AC_RenderHack_Checks", + function(__, ply) + for k=1, #detections do + local v = detections[k] + if(_tonumber(ply:GetInfo(v.name)) == v.value) then + ply.BP_Detections = ply.BP_Detections + 1 + end + end + if(ply.BP_Detections == #detections) then + gAC.AddDetection( ply, "Bigpackets User [Code 118]", gAC.config.BP_PUNISHMENT, gAC.config.BP_BANTIME ) + end + end +) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/anticppaimbot/sv_gac_cppaimbot.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/anticppaimbot/sv_gac_cppaimbot.lua new file mode 100644 index 0000000..a4c7e24 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/anticppaimbot/sv_gac_cppaimbot.lua @@ -0,0 +1,66 @@ +local _CurTime = CurTime +local _IsValid = IsValid +local _hook_Add = hook.Add +local _math_abs = math.abs +local _util_TraceLine = util.TraceLine + +local _EyePos = (CLIENT and EyePos or NULL) + +if !gAC.config.ENABLE_CPPAIMBOT_CHECKS then return end + +local Blacklisted_Weapons = { + ["weapon_physgun"] = true, + ["gmod_tool"] = true, + ["weapon_physcannon"] = true +} + +local tr + +_hook_Add( "StartCommand", "gAC_AntiCobalt.StartCommand", function( ply, cmd ) + + if( ply:InVehicle() || ply.gAC_AimbotDetected || !ply:Alive() || ply:GetObserverMode() != OBS_MODE_NONE + || ply:IsBot() || !_IsValid( ply ) || ply:IsTimingOut() || ply:PacketLoss() > 80 ) then return end + + if( ply.JoinTimeGAC == nil || !( _CurTime() >= ply.JoinTimeGAC + 25 ) || ply.PlayerFullyAuthenticated != true ) then return end + + if _IsValid(ply:GetActiveWeapon()) && Blacklisted_Weapons[ply:GetActiveWeapon():GetClass()] then + ply.gAC_CPPAimbotDetections = 0 + return + end + + ply.gAC_CPPMX = _math_abs( cmd:GetMouseX() ) + ply.gAC_CPPMY = _math_abs( cmd:GetMouseY() ) + ply.gAC_CPPAimView = cmd:GetViewAngles() + + if ply.gAC_CPPAimViewOld == nil then + ply.gAC_CPPAimViewOld = ply.gAC_CPPAimView + return + end + + if ply.gAC_CPPAimbotDetections == nil then + ply.gAC_CPPAimbotDetections = 0 + end + + if ply.gAC_CPPMX == 0 && ply.gAC_CPPMY == 0 then + if ( ply.gAC_CPPAimView.p ~= ply.gAC_CPPAimViewOld.p && ply.gAC_CPPAimView.y ~= ply.gAC_CPPAimViewOld.y ) then + tr = _util_TraceLine({start = ply:EyePos(), endpos = ply:EyePos() + ((ply.gAC_CPPAimView):Forward() * (4096 * 8) ), filter = ply}) + if tr.Entity:IsPlayer() then + if ply.gAC_CPPAimbotDetections >= 40 then + ply.gAC_AimbotDetected = true + gAC.AddDetection( ply, "C++ Aimbot detection triggered [Code 123]", gAC.config.CPPAIMBOT_PUNISHMENT, gAC.config.CPPAIMBOT_PUNSIHMENT_BANTIME ) + else + ply.gAC_CPPAimbotDetections = ply.gAC_CPPAimbotDetections + 1 + end + elseif ply.gAC_CPPAimbotDetections != 0 then + ply.gAC_CPPAimbotDetections = ply.gAC_CPPAimbotDetections - 1 + end + elseif ply.gAC_CPPAimbotDetections != 0 then + ply.gAC_CPPAimbotDetections = 0 + end + elseif ply.gAC_CPPAimbotDetections != 0 then + ply.gAC_CPPAimbotDetections = 0 + end + + ply.gAC_CPPAimViewOld = ply.gAC_CPPAimView + +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antienginepred/cl_gac_antienginepred.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antienginepred/cl_gac_antienginepred.lua new file mode 100644 index 0000000..3a3d07f --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antienginepred/cl_gac_antienginepred.lua @@ -0,0 +1,55 @@ +if !gAC.config.ANTI_ENGINEPRED_CHECKS then return end +local _hook_Add = hook.Add +local _math_random = math.random +local _string_char = string.char +local _util_TableToJSON = util.TableToJSON +local _LocalPlayer = LocalPlayer + +local function floor(number) + return number - (number % 1) +end + +local function stringrandom(length) + local str = "" + for i = 1, length do + local typo = floor(_math_random(1, 4) + .5) + if typo == 1 then + str = str.. _string_char(_math_random(97, 122)) + elseif typo == 2 then + str = str.. _string_char(_math_random(65, 90)) + elseif typo == 3 then + str = str.. _string_char(_math_random(49, 57)) + end + end + return str +end + +local CMDNumber = 0 + +_hook_Add("CreateMove", stringrandom(floor(_math_random(10, 15) + .5)), function(cmd) + local cmdnum = cmd:CommandNumber() + if cmdnum == 0 then return end + CMDNumber = cmdnum +end) + +local _failures, _sent = 0, nil + +_hook_Add("SetupMove", stringrandom(floor(_math_random(10, 15) + .5)), function(ply, mv, cmd) + if ply ~= _LocalPlayer() then return end + local cmdnum = cmd:CommandNumber() + if cmdnum == 0 then return end + if CMDNumber ~= 0 && CMDNumber < cmdnum then + if _failures >= 10 && !_sent then + gAC_Send("g-AC_Detections", _util_TableToJSON({ + "Engine Prediction detected [Code 127]", + gAC.config.ANTI_ENGINEPRED_PUNISHMENT, + gAC.config.ANTI_ENGINEPRED_BANTIME + })) + _sent = true + else + _failures = _failures + 1 + end + elseif CMDNumber > cmdnum && _failures > 0 then + _failures = _failures - 1 + end +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_bitconverter.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_bitconverter.lua new file mode 100644 index 0000000..1474792 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_bitconverter.lua @@ -0,0 +1,244 @@ +--[[ + https://github.com/notcake/glib/blob/master/lua/glib/bitconverter.lua + Cake's bitconverter +]] +gAC.BitConverter = {} + +local bit_band = bit.band +local bit_lshift = bit.lshift +local bit_rshift = bit.rshift +local math_floor = math.floor +local math_frexp = math.frexp +local math_ldexp = math.ldexp +local math_huge = math.huge + +-- Integers +function gAC.BitConverter.UInt8ToUInt8s (n) + return n +end + +function gAC.BitConverter.UInt16ToUInt8s (n) + return n % 256, + math_floor (n / 256) % 256 +end + +function gAC.BitConverter.UInt32ToUInt8s (n) + return n % 256, + math_floor (n / 256) % 256, + math_floor (n / 65536) % 256, + math_floor (n / 16777216) % 256 +end + +function gAC.BitConverter.UInt64ToUInt8s (n) + return n % 256, + math_floor (n / 256) % 256, + math_floor (n / 65536) % 256, + math_floor (n / 16777216) % 256, + math_floor (n / 4294967296) % 256, + math_floor (n / 1099511627776) % 256, + math_floor (n / 281474976710656) % 256, + math_floor (n / 72057594037927936) % 256 +end + +function gAC.BitConverter.UInt8sToUInt8(uint80) + return uint80 +end + +function gAC.BitConverter.UInt8sToUInt16 (uint80, uint81) + return uint80 + + uint81 * 256 +end + +function gAC.BitConverter.UInt8sToUInt32 (uint80, uint81, uint82, uint83) + return uint80 + + uint81 * 256 + + uint82 * 65536 + + uint83 * 16777216 +end + +function gAC.BitConverter.UInt8sToUInt64 (uint80, uint81, uint82, uint83, uint84, uint85, uint86, uint87) + return uint80 + + uint81 * 256 + + uint82 * 65536 + + uint83 * 16777216 + + uint84 * 4294967296 + + uint85 * 1099511627776 + + uint86 * 281474976710656 + + uint87 * 72057594037927936 +end + +function gAC.BitConverter.Int8ToUInt8s (n) + if n < 0 then n = n + 256 end + return gAC.BitConverter.UInt8ToUInt8s (n) +end + +function gAC.BitConverter.Int16ToUInt8s (n) + if n < 0 then n = n + 65536 end + return gAC.BitConverter.UInt16ToUInt8s (n) +end + +function gAC.BitConverter.Int32ToUInt8s (n) + if n < 0 then n = n + 4294967296 end + return gAC.BitConverter.UInt32ToUInt8s (n) +end + +function gAC.BitConverter.Int64ToUInt8s (n) + local uint80, uint81, uint82, uint83 = gAC.BitConverter.UInt32ToUInt8s (n % 4294967296) + local uint84, uint85, uint86, uint87 = gAC.BitConverter.Int32ToUInt8s (math_floor (n / 4294967296)) + return uint80, uint81, uint82, uint83, uint84, uint85, uint86, uint87 +end + +function gAC.BitConverter.UInt8sToInt8 (uint80) + local n = gAC.BitConverter.UInt8sToUInt8 (uint80) + if n >= 128 then n = n - 256 end + return n +end + +function gAC.BitConverter.UInt8sToInt16 (uint80, uint81) + local n = gAC.BitConverter.UInt8sToUInt16 (uint80, uint81) + if n >= 32768 then n = n - 65536 end + return n +end + +function gAC.BitConverter.UInt8sToInt32 (uint80, uint81, uint82, uint83) + local n = gAC.BitConverter.UInt8sToUInt32 (uint80, uint81, uint82, uint83) + if n >= 2147483648 then n = n - 4294967296 end + return n +end + +function gAC.BitConverter.UInt8sToInt64 (uint80, uint81, uint82, uint83, uint84, uint85, uint86, uint87) + local low = gAC.BitConverter.UInt8sToUInt32 (uint80, uint81, uint82, uint83) + local high = gAC.BitConverter.UInt8sToInt32 (uint84, uint85, uint86, uint87) + return low + high * 4294967296 +end + +-- IEEE floating point numbers +function gAC.BitConverter.FloatToUInt32 (f) + -- 1 / f is needed to check for -0 + local n = 0 + if f < 0 or 1 / f < 0 then + n = n + 0x80000000 + f = -f + end + + local mantissa = 0 + local biasedExponent = 0 + + if f == math_huge then + biasedExponent = 0xFF + elseif f ~= f then + biasedExponent = 0xFF + mantissa = 1 + elseif f == 0 then + biasedExponent = 0x00 + else + mantissa, biasedExponent = math_frexp (f) + biasedExponent = biasedExponent + 126 + + if biasedExponent <= 0 then + -- Denormal + mantissa = math_floor (mantissa * 2 ^ (23 + biasedExponent) + 0.5) + biasedExponent = 0 + else + mantissa = math_floor ((mantissa * 2 - 1) * 2 ^ 23 + 0.5) + end + end + + n = n + bit_lshift (bit_band (biasedExponent, 0xFF), 23) + n = n + bit_band (mantissa, 0x007FFFFF) + + return n +end + +function gAC.BitConverter.DoubleToUInt32s (f) + -- 1 / f is needed to check for -0 + local high = 0 + local low = 0 + if f < 0 or 1 / f < 0 then + high = high + 0x80000000 + f = -f + end + + local mantissa = 0 + local biasedExponent = 0 + + if f == math_huge then + biasedExponent = 0x07FF + elseif f ~= f then + biasedExponent = 0x07FF + mantissa = 1 + elseif f == 0 then + biasedExponent = 0x00 + else + mantissa, biasedExponent = math_frexp (f) + biasedExponent = biasedExponent + 1022 + + if biasedExponent <= 0 then + -- Denormal + mantissa = math_floor (mantissa * 2 ^ (52 + biasedExponent) + 0.5) + biasedExponent = 0 + else + mantissa = math_floor ((mantissa * 2 - 1) * 2 ^ 52 + 0.5) + end + end + + low = mantissa % 4294967296 + high = high + bit_lshift (bit_band (biasedExponent, 0x07FF), 20) + high = high + bit_band (math_floor (mantissa / 4294967296), 0x000FFFFF) + + return low, high +end + +function gAC.BitConverter.UInt32ToFloat (n) + -- 1 sign bit + -- 8 biased exponent bits (bias of 127, biased value of 0 if 0 or denormal) + -- 23 mantissa bits (implicit 1, unless biased exponent is 0) + + local negative = false + + if n >= 0x80000000 then + negative = true + n = n - 0x80000000 + end + + local biasedExponent = bit_rshift (bit_band (n, 0x7F800000), 23) + local mantissa = bit_band (n, 0x007FFFFF) / (2 ^ 23) + + local f + if biasedExponent == 0x00 then + f = mantissa == 0 and 0 or math_ldexp (mantissa, -126) + elseif biasedExponent == 0xFF then + f = mantissa == 0 and math_huge or (math_huge - math_huge) + else + f = math_ldexp (1 + mantissa, biasedExponent - 127) + end + + return negative and -f or f +end + +function gAC.BitConverter.UInt32sToDouble (low, high) + -- 1 sign bit + -- 11 biased exponent bits (bias of 127, biased value of 0 if 0 or denormal) + -- 52 mantissa bits (implicit 1, unless biased exponent is 0) + + local negative = false + + if high >= 0x80000000 then + negative = true + high = high - 0x80000000 + end + + local biasedExponent = bit_rshift (bit_band (high, 0x7FF00000), 20) + local mantissa = (bit_band (high, 0x000FFFFF) * 4294967296 + low) / 2 ^ 52 + + local f + if biasedExponent == 0x0000 then + f = mantissa == 0 and 0 or math_ldexp (mantissa, -1022) + elseif biasedExponent == 0x07FF then + f = mantissa == 0 and math_huge or (math_huge - math_huge) + else + f = math_ldexp (1 + mantissa, biasedExponent - 1023) + end + + return negative and -f or f +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_inbuffer.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_inbuffer.lua new file mode 100644 index 0000000..1fd5c0c --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_inbuffer.lua @@ -0,0 +1,175 @@ +--[[ + https://github.com/notcake/glib/blob/master/lua/glib/io/inbuffer.lua + Cake's io.inbuffer (converted to not use constuctor) +]] +local string_char = string.char + +function gAC.InBuffer() + local self = { + Position = 1 + } + + -- Position + function self:GetBytesRemaining () + Error ("InBuffer:GetBytesRemaining : Not implemented.") + end + + function self:GetPosition () + return self.Position + end + + function self:GetSize () + Error ("InBuffer:GetSize : Not implemented.") + end + + function self:IsEndOfStream () + Error ("InBuffer:IsEndOfStream : Not implemented.") + end + + function self:Pin () + Error ("InBuffer:Pin : Not implemented.") + end + + function self:SeekRelative (relativeSeekPos) + Error ("InBuffer:SeekRelative : Not implemented.") + end + + function self:SeekAbsolute (seekPos) + Error ("InBuffer:SeekAbsolute : Not implemented.") + end + + function self:UInt8 () + Error ("InBuffer:UInt8 : Not implemented.") + end + + function self:UInt16 () + local low = self:UInt8 () + local high = self:UInt8 () + return high * 0x0100 + low + end + + function self:UInt32 () + local low = self:UInt16 () + local high = self:UInt16 () + return high * 0x00010000 + low + end + + function self:UInt64 () + local low = self:UInt32 () + local high = self:UInt32 () + return high * 4294967296 + low + end + + function self:ULEB128 () + local n = 0 + local factor = 1 + + local done = false + repeat + local byte = self:UInt8 () + if byte >= 0x80 then + byte = byte - 0x80 + else + done = true + end + + n = n + byte * factor + factor = factor * 128 + until done + + return n + end + + function self:Int8 () + Error ("InBuffer:Int8 : Not implemented.") + end + + function self:Int16 () + local low = self:UInt8 () + local high = self:Int8 () + return high * 0x0100 + low + end + + function self:Int32 () + local low = self:UInt16 () + local high = self:Int16 () + return high * 0x00010000 + low + end + + function self:Int64 () + local low = self:UInt32 () + local high = self:Int32 () + return high * 4294967296 + low + end + + function self:Float () + local n = self:UInt32 () + return gAC.BitConverter.UInt32ToFloat (n) + end + + function self:Double () + local low = self:UInt32 () + local high = self:UInt32 () + return gAC.BitConverter.UInt32sToDouble (low, high) + end + + function self:Vector () + local x = self:Float () + local y = self:Float () + local z = self:Float () + return Vector (x, y, z) + end + + function self:Bytes (length) + local data = "" + for i = 1, length do + data = data .. string_char (self:UInt8 ()) + end + + return data + end + + function self:String () + Error ("InBuffer:String : Not implemented.") + end + + function self:StringN8 () + local length = self:UInt8 () + return self:Bytes (length) + end + + function self:StringN16 () + local length = self:UInt16 () + return self:Bytes (length) + end + + function self:StringN32 () + local length = self:UInt32 () + return self:Bytes (length) + end + + function self:StringZ () + local data = "" + local c = self:UInt8 () + while c and c ~= 0 do + if #data > 65536 then + Error ("InBuffer:StringZ : String is too long, infinite loop?") + break + end + + data = data .. string_char (c) + c = self:UInt8 () + end + + return data + end + + function self:LongString () + Error ("InBuffer:LongString : Not implemented.") + end + + function self:Boolean () + return self:UInt8 () ~= 0 + end + return self +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_outbuffer.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_outbuffer.lua new file mode 100644 index 0000000..4c78f17 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_outbuffer.lua @@ -0,0 +1,139 @@ +--[[ + https://github.com/notcake/glib/blob/master/lua/glib/io/outbuffer.lua + Cake's io.outbuffer (converted to not use constuctor) +]] +local bit_band = bit.band +local math_floor = math.floor +local math_min = math.min +local string_byte = string.byte + +function gAC.OutBuffer() + local self = {} + + function self:Clear () + Error ("OutBuffer:Clear : Not implemented.") + end + + function self:GetSize () + Error ("OutBuffer:GetSize : Not implemented.") + end + + function self:GetString () + Error ("OutBuffer:GetString : Not implemented.") + end + + function self:UInt8 (n) + Error ("OutBuffer:UInt8 : Not implemented.") + end + + function self:UInt16 (n) + self:UInt8 (n % 0x0100) + self:UInt8 (math_floor (n / 0x0100)) + end + + function self:UInt32 (n) + self:UInt16 (n % 0x00010000) + self:UInt16 (math_floor (n / 0x00010000)) + end + + function self:UInt64 (n) + self:UInt32 (n % 4294967296) + self:UInt32 (math_floor (n / 4294967296)) + end + + function self:ULEB128 (n) + if n ~= n then n = 0 end + if n < 0 then n = -n end + if n >= 4294967296 then n = 4294967295 end + + while n > 0 do + if n >= 0x80 then + self:UInt8 (0x80 + bit_band (n, 0x7F)) + n = math_floor (n / 0x80) + else + self:UInt8 (bit_band (n, 0x7F)) + end + end + end + + function self:Int8 (n) + Error ("OutBuffer:Int8 : Not implemented.") + end + + function self:Int16 (n) + self:UInt8 (n % 0x0100) + self:Int8 (math_floor (n / 0x0100)) + end + + function self:Int32 (n) + self:UInt16 (n % 0x00010000) + self:Int16 (math_floor (n / 0x00010000)) + end + + function self:Int64 (n) + self:UInt32 (n % 4294967296) + self:Int32 (math_floor (n / 4294967296)) + end + + function self:Float (f) + local n = gAC.BitConverter.FloatToUInt32 (f) + self:UInt32 (n) + end + + function self:Double (f) + local low, high = gAC.BitConverter.DoubleToUInt32s (f) + self:UInt32 (low) + self:UInt32 (high) + end + + function self:Vector (v) + self:Float (v.x) + self:Float (v.y) + self:Float (v.z) + end + + function self:Bytes (data, length) + length = length or #data + length = math_min (length, #data) + for i = 1, length do + self:UInt8 (string_byte (data, i)) + end + end + + function self:String (data) + Error ("OutBuffer:String : Not implemented.") + end + + function self:StringN8 (data) + data = data or "" + + self:UInt8 (#data) + self:Bytes (data) + end + + function self:StringN16 (data) + data = data or "" + + self:UInt16 (#data) + self:Bytes (data) + end + + function self:StringN32 (data) + data = data or "" + + self:UInt32 (#data) + self:Bytes (data) + end + + function self:StringZ (data) + data = data or "" + + self:Bytes (data) + self:UInt8 (0) + end + + function self:Boolean (b) + self:UInt8 (b and 1 or 0) + end + return self +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_stringinbuffer.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_stringinbuffer.lua new file mode 100644 index 0000000..75cbaaf --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_stringinbuffer.lua @@ -0,0 +1,119 @@ +--[[ + https://github.com/notcake/glib/blob/master/lua/glib/io/stringinbuffer.lua + Cake's io.stringinbuffer (converted to not use constuctor) +]] +local math_max = math.max +local string_byte = string.byte +local string_find = string.find +local string_sub = string.sub + +function gAC.StringInBuffer(data) + local self = gAC.InBuffer() + self.Data = data or "" + self.Position = 1 + + -- Position + function self:GetBytesRemaining () + return math_max (0, #self.Data - self.Position + 1) + end + + function self:GetPosition () + return self.Position + end + + function self:GetSize () + return #self.Data + end + + function self:IsEndOfStream () + return self.Position > #self.Data + end + + function self:Pin () + return self + end + + function self:SeekRelative (relativeSeekPos) + self.Position = self.Position + relativeSeekPos + end + + function self:SeekAbsolute (seekPos) + self.Position = seekPos + end + + function self:UInt8 () + local uint80 = string_byte (self.Data, self.Position, self.Position) + self.Position = self.Position + 1 + return gAC.BitConverter.UInt8sToUInt8 (uint80 or 0) + end + + function self:UInt16 () + local uint80, uint81 = string_byte (self.Data, self.Position, self.Position + 1) + self.Position = self.Position + 2 + return gAC.BitConverter.UInt8sToUInt16 (uint80 or 0, uint81 or 0) + end + + function self:UInt32 () + local uint80, uint81, uint82, uint83 = string_byte (self.Data, self.Position, self.Position + 3) + self.Position = self.Position + 4 + return gAC.BitConverter.UInt8sToUInt32 (uint80 or 0, uint81 or 0, uint82 or 0, uint83 or 0) + end + + function self:UInt64 () + local uint80, uint81, uint82, uint83, uint84, uint85, uint86, uint87 = string_byte (self.Data, self.Position, self.Position + 7) + self.Position = self.Position + 8 + return gAC.BitConverter.UInt8sToUInt64 (uint80 or 0, uint81 or 0, uint82 or 0, uint83 or 0, uint84 or 0, uint85 or 0, uint86 or 0, uint87 or 0) + end + + function self:Int8 () + local uint80 = string_byte (self.Data, self.Position, self.Position) + self.Position = self.Position + 1 + return gAC.BitConverter.UInt8sToInt8 (uint80 or 0) + end + + function self:Int16 () + local uint80, uint81 = string_byte (self.Data, self.Position, self.Position + 1) + self.Position = self.Position + 2 + return gAC.BitConverter.UInt8sToInt16 (uint80 or 0, uint81 or 0) + end + + function self:Int32 () + local uint80, uint81, uint82, uint83 = string_byte (self.Data, self.Position, self.Position + 3) + self.Position = self.Position + 4 + return gAC.BitConverter.UInt8sToInt32 (uint80 or 0, uint81 or 0, uint82 or 0, uint83 or 0) + end + + function self:Int64 () + local uint80, uint81, uint82, uint83, uint84, uint85, uint86, uint87 = string_byte (self.Data, self.Position, self.Position + 7) + self.Position = self.Position + 8 + return gAC.BitConverter.UInt8sToInt64 (uint80 or 0, uint81 or 0, uint82 or 0, uint83 or 0, uint84 or 0, uint85 or 0, uint86 or 0, uint87 or 0) + end + + function self:Bytes (length) + local str = string_sub (self.Data, self.Position, self.Position + length - 1) + self.Position = self.Position + length + return str + end + + function self:String () + return self:StringN16 () + end + + function self:StringZ () + local terminatorPosition = string_find (self.Data, "\0", self.Position, true) + if terminatorPosition then + local str = string_sub (self.Data, self.Position, terminatorPosition - 1) + self.Position = terminatorPosition + 1 + return str + else + local str = string_sub (self.Data, self.Position) + self.Position = #self.Data + return str + end + end + + function self:LongString () + return self:StringN32 () + end + return self +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_stringoutbuffer.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_stringoutbuffer.lua new file mode 100644 index 0000000..4962e94 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/io/sv_stringoutbuffer.lua @@ -0,0 +1,58 @@ +--[[ + https://github.com/notcake/glib/blob/master/lua/glib/io/stringoutbuffer.lua + Cake's io.stringoutbuffer (converted to not use constuctor) +]] +local bit_band = bit.band +local bit_rshift = bit.rshift +local math_floor = math.floor +local math_min = math.min +local string_char = string.char +local string_sub = string.sub +local table_concat = table.concat + +function gAC.StringOutBuffer() + local self = gAC.OutBuffer() + self.Data = {} + + function self:Clear () + self.Data = {} + end + + function self:GetSize () + return #self:GetString () + end + + function self:GetString () + if #self.Data > 1 then + self.Data = { table_concat (self.Data) } + end + return self.Data [1] or "" + end + + function self:UInt8 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.UInt8ToUInt8s (n)) end + function self:UInt16 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.UInt16ToUInt8s (n)) end + function self:UInt32 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.UInt32ToUInt8s (n)) end + function self:UInt64 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.UInt64ToUInt8s (n)) end + function self:Int8 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.Int8ToUInt8s (n)) end + function self:Int16 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.Int16ToUInt8s (n)) end + function self:Int32 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.Int32ToUInt8s (n)) end + function self:Int64 (n) self.Data [#self.Data + 1] = string_char (gAC.BitConverter.Int64ToUInt8s (n)) end + + function self:Bytes (data, length) + length = length or #data + length = math_min (length, #data) + self.Data [#self.Data + 1] = length == #data and data or string_sub (data, 1, length) + end + + function self:String (data) + self:StringN16 (data) + end + + function self:LongString (data) + self:StringN32 (data) + end + + self.__len = self.GetSize + self.__tostring = self.GetString + return self +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/sv_gac_antilua.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/sv_gac_antilua.lua new file mode 100644 index 0000000..4326f16 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/sv_gac_antilua.lua @@ -0,0 +1,657 @@ +local _CompileFile = CompileFile +local _SysTime = SysTime +local _math_Round = math.Round +local _jit_util_funcinfo = jit.util.funcinfo +local _jit_attach = jit.attach +local _file_CreateDir = file.CreateDir +local _file_Exists = file.Exists +local _file_Time = file.Time +local _file_Find = file.Find +local _file_Read = file.Read +local _file_Size = file.Size +local _file_Write = file.Write +local _hook_Add = hook.Add +local _isstring = isstring +local _tostring = tostring +local _istable = istable +local _pairs = pairs +local _pcall = pcall +local _timer_Create = timer.Create +local _timer_Start = timer.Start +local _CompileString = CompileString +local _IsValid = IsValid +local _string_dump = string.dump +local _string_lower = string.lower +local _string_sub = string.sub +local _string_Explode = string.Explode +local _string_gsub = string.gsub +local _table_remove = table.remove +local _table_concat = table.concat +local _util_Compress = util.Compress +local _util_Decompress = util.Decompress +local _util_JSONToTable = util.JSONToTable +local _util_TableToJSON = util.TableToJSON +local _bit_rol = bit.rol +local _bit_bxor = bit.bxor +local _debug_getregistry = debug.getregistry + +--[[ + WARNING: + AntiLua is CPU intensive, + only use this if consistant lua cheating is at an all time high! + + "let their be peace on our world of lua" - NiceCream +]] + +_hook_Add("gAC.Init", "gAC.AntiLua", function() + if !gAC.config.AntiLua_CHECK then return end + + --[[ + LuaFileCache, + a full cache of every file that was mounted to the server + this includes the location of the file, the size of it, + the bytecodes generated (if possible) and the functions list (if bytecodes exists) + + LuaSession, + a source cache of lua ran on the client. + keeps track of what was ran to be whitelisted from detections. + like RunString executions from a valid file. + + *Update* + Made it like this now + { + [Player] = { + [Source] = { + bytecode, + functionmap + } + } + } + ]] + gAC.LuaFileCache = gAC.LuaFileCache or nil + gAC.LuaSession = gAC.LuaSession or {} + gAC.FileSourcePath = "LUA" + gAC.CacheVersionIndex = '.version.gac' + gAC.CacheVersion = '1.2.1' + + --[[ + Function used to detect or record information about a server-side execution. + Currently on startup it logs all information of function execution. + ]] + gAC.LuaVM = function(proto) + local jitinfo = _jit_util_funcinfo(proto) + jitinfo.source = _string_gsub(jitinfo.source, "^@", "") + jitinfo.source = _string_gsub(jitinfo.source, "%.InitialCache$", "") + jitinfo.source = gAC.dirtosvlua(jitinfo.source) + gAC.LuaFileCache[jitinfo.source] = gAC.LuaFileCache[jitinfo.source] or {} + local _tbl = gAC.LuaFileCache[jitinfo.source] + if _tbl.bytecodes then return end + _tbl.funclist = _tbl.funclist or {} + _tbl.funclist[#_tbl.funclist + 1] = { + linedefined = jitinfo.linedefined, + lastlinedefined = jitinfo.lastlinedefined, + proto = ByteCode.FunctionToHash(proto, jitinfo) + } + end + + --[[ + Converts string into a Hash String of the string provided + to be readed and identified by the lua VM + ]] + function gAC.HashString(str) + local len = #str + for i=1, #str do + len = _bit_bxor(len, _bit_rol(len, 6) + str:byte(i)) + end + return _bit_rol(len, 3) + end + + --[[ + We gonna need to create a hashstring of 'bc' + to be used as a form of jit.attach method. + ]] + gAC.LuaVMID = gAC.HashString('bc') + + + --[[ + Simply converts any file location provided to meet with + the 'LUA' mount path of the file system + ]] + function gAC.dirtosvlua(loc) + local _loc = loc + _loc = _string_Explode("/",_loc) + if _loc[1] == "addons" then + _table_remove(_loc, 1) + _table_remove(_loc, 1) + _table_remove(_loc, 1) + loc = _table_concat(_loc,"/") + elseif _loc[1] == "lua" then + _table_remove(_loc, 1) + loc = _table_concat(_loc,"/") + elseif _loc[1] == "gamemodes" then + _table_remove(_loc, 1) + loc = _table_concat(_loc,"/") + end + return loc + end + + --[[ + Catalog every mounted lua file + ]] + if gAC.LuaFileCache == nil then + + local function EnumerateFolder (folder, pathId, callback, recursive) + if not callback then return end + + if #folder > 0 then folder = folder .. "/" end + local files, folders = _file_Find(folder .. "*", pathId) + + if not files and not folders then + gAC.Print("[AntiLua] Could not add " .. folder .. " to lua information.") + return + end + + for _, fileName in _pairs(files) do + callback(folder .. fileName, pathId) + end + if recursive then + for _, childFolder in _pairs(folders) do + if childFolder ~= "." and childFolder ~= ".." then + EnumerateFolder(folder .. childFolder, pathId, callback, recursive) + end + end + end + end + + gAC.Print("[AntiLua] Initializing") + + if !_file_Exists("gac-antilua", "DATA") then + _file_CreateDir("gac-antilua") + end + + gAC.LuaFileCache = {} + local _Time = _SysTime() + gAC.Print("[AntiLua] Building lua file cache") + + if _file_Exists("gac-antilua/gac-luacache.dat", "DATA") then + gAC.Print("[AntiLua] Detected an existing lua cache file, reading...") + gAC.LuaFileCache = _util_JSONToTable(_util_Decompress(_file_Read("gac-antilua/gac-luacache.dat", "DATA"))) + if gAC.LuaFileCache[gAC.CacheVersionIndex] ~= gAC.CacheVersion then + gAC.Print("[AntiLua] Lua cache file is outdated, recaching...") + gAC.LuaFileCache = {} + else + gAC.Print("[AntiLua] Checking for modifications...") + end + end + + local _Errors, _UpdateFile, _Path = {}, false, gAC.FileSourcePath + + gAC.LuaFileCache[gAC.CacheVersionIndex] = gAC.CacheVersion + + local function handlepath(path) + if path == "" then return end + if _string_lower (_string_sub (path, -4)) ~= ".lua" then return end + + local _time, _alter = _file_Time(path, _Path), nil + local lower_path, use_lowerpath = _string_lower(path), false + if lower_path ~= path then + use_lowerpath = true + end + + if !gAC.LuaFileCache [lower_path] then + -- gAC.Print("[AntiLua] Excluding " .. path) + if use_lowerpath then + -- gAC.Print("[AntiLua] WARNING: file '" .. path .. "' is using capitalized characters!") + end + _alter = true + _UpdateFile = true + elseif !_istable(gAC.LuaFileCache[lower_path]) or _time ~= gAC.LuaFileCache[lower_path].time then + -- gAC.Print("[AntiLua] Modifying exclusion " .. path) + if use_lowerpath then + -- gAC.Print("[AntiLua] WARNING: file '" .. path .. "' is using capitalized characters!") + end + _alter = true + _UpdateFile = true + end + + if _alter then + gAC.LuaFileCache[lower_path] = { time = _time } + + if use_lowerpath then + gAC.LuaFileCache[lower_path].path = path + end + + local data = _file_Read(path, _Path) + if not data then + data = _file_Read(lower_path, _Path) + end + local func = _CompileString(data, path .. '.InitialCache', false) + if (!func or _isstring(func)) then + gAC.Print("[AntiLua] " .. path .. " Compile Error") + _Errors[#_Errors + 1] = path .. " - Compile Error (switch to source verification)" + func = nil + gAC.LuaFileCache[lower_path] = { time = _time } + if use_lowerpath then + gAC.LuaFileCache[lower_path].path = path + end + return + end + end + end + + local _R = _debug_getregistry() + _R._VMEVENTS = _R._VMEVENTS or {} + _R._VMEVENTS[gAC.LuaVMID] = gAC.LuaVM + + _jit_attach(function() end, "") + + EnumerateFolder ("", _Path, handlepath, true) + + _R._VMEVENTS[gAC.LuaVMID] = nil + + for path, v in _pairs(gAC.LuaFileCache) do + if path == gAC.CacheVersionIndex then continue end + local _path = v.path or path + if _file_Time(_path, _Path) == 0 then + _UpdateFile = true + gAC.Print("[AntiLua] Removing exclusion " .. path) + gAC.LuaFileCache[path] = nil + end + end + + if !_UpdateFile then + gAC.Print("[AntiLua] Everything appears up to standards") + end + + gAC.Print("[AntiLua] Finished building lua file cache, took: " .. _math_Round(_SysTime() - _Time, 2) .. "s") + if #_Errors > 0 then + gAC.Print(#_Errors .. " lua files have issues") + for k=1, #_Errors do + gAC.Print(_Errors[k]) + end + end + + if _UpdateFile then + gAC.Print("[AntiLua] Saving lua cache...") + _Time = _SysTime() + _file_Write("gac-antilua/gac-luacache.dat", _util_Compress(_util_TableToJSON(gAC.LuaFileCache))) + gAC.Print("[AntiLua] Saving took: " .. _math_Round(_SysTime() - _Time, 2) .. "s") + end + + gAC.Print("[AntiLua] Waiting for core detection systems") + end +end) + +_hook_Add("gAC.IncludesLoaded", "gAC.AntiLua", function() -- this is for the DRM + local _jit_util_funcinfo = jit.util.funcinfo + local _jit_attach = jit.attach + local _file_Time = file.Time + local _file_Write = file.Write + local _hook_Add = hook.Add + local _isstring = isstring + local _istable = istable + local _pairs = pairs + local _pcall = pcall + local _timer_Create = timer.Create + local _timer_Start = timer.Start + local _CompileString = CompileString + local _IsValid = IsValid + local _CurTime = CurTime + local _player_GetAll = player.GetAll + local _string_dump = string.dump + local _string_gsub = string.gsub + local _util_JSONToTable = util.JSONToTable + local _util_TableToJSON = util.TableToJSON + local _debug_getregistry = debug.getregistry + local _file_Exists = file.Exists + local _file_CreateDir = file.CreateDir + local _string_lower = string.lower + + if !gAC.config.AntiLua_CHECK then return end + + gAC.Print("[AntiLua] Core detection system has loaded!") + + -- builtin functions can give out a source to "@=[C]" or "[C]" (like pcall being used to isolate RunString errors) + gAC.LuaFuncSources = { + ["function: builtin#21"] = { + source = "=[C]", + short_src = "[C]", + what = "C", + lastlinedefined = -1, + linedefined = -1 + }, + ["function: builtin#20"] = { + source = "=[C]", + short_src = "[C]", + what = "C", + lastlinedefined = -1, + linedefined = -1 + } + } + + --[[ + Verify sources of the lua cache, + if defined source is not in the cache then it's not created by the server. + ]] + function gAC.VerifyLuaSource(funcinfo, userid) + if isstring(funcinfo.source) and funcinfo.source:find('<:@?#$*/>', 1, true) then return true end -- dbg crypt + if funcinfo.source == gAC.CacheVersionIndex and !gAC.LuaSession[userid][funcinfo.source] then + return false + end + if !gAC.LuaFileCache[funcinfo.source] && !gAC.LuaSession[userid][funcinfo.source] then + return false + end + return true + end + + --[[ + Adds new sources to LuaSession, keeping track of all lua compiled code executed. + ]] + function gAC.AddSource(userid, sourceId, code) + if gAC.config.AntiLua_FunctionVerification then + local func, err = _CompileString(code, sourceId .. ".AddSource", false) + if !func or _isstring(func) then + return + end + local dump = _string_dump(func) + local funclist = ByteCode.DumpToFunctionList(dump) + gAC.LuaSession[userid][sourceId] = { + funclist = funclist + } + else + gAC.LuaSession[userid][sourceId] = true + end + end + + --[[ + File re-verification for the lua cache. + Used on files that are reloaded on lua refresh or other lua compiles that needs to be added + ]] + function gAC.UpdateLuaFile(source) + if !gAC.config.AntiLua_LuaRefresh then return end + local time = _file_Time(source, gAC.FileSourcePath) + local cache = gAC.LuaFileCache[source] + if !cache then return end + if time ~= 0 then + if time ~= cache.time then + gAC.Print("[AntiLua] WARNING: lua refresh occured on " .. source .. ", switching to source verification") + gAC.LuaFileCache[source] = { time = time } + end + else + gAC.Print("[AntiLua] WARNING: lua refresh occured on " .. source .. ", switching to source verification") + gAC.LuaFileCache[source] = true + end + end + + --[[ + Verify sources of the lua cache & function information. + same as VerifyLuaSource but uses dump information of lua files to indentify + if it's of a foreign execution or compile. + + *Note, due to bytecode exec method on the client + sometimes or all the time the hash ID of a function + will differ from the server, even though the 'lastlinedefined' and + 'linedefined' are exact to the functions list. + ]] + local LuaFileUpdates = {} -- Prevent spam + if !gAC.config.AntiLua_LuaRefresh then + LuaFileUpdates = nil + end + local ProtoCheck = gAC.config.AntiLua_HashFunctionVerification + function gAC.VerifyFunction(userid, funcinfo) + if !gAC.config.AntiLua_FunctionVerification then return true end + local funclist = nil + if gAC.LuaSession[userid] && gAC.LuaSession[userid][funcinfo.source] && _istable(gAC.LuaSession[userid][funcinfo.source]) && gAC.LuaSession[userid][funcinfo.source].funclist then + funclist = gAC.LuaSession[userid][funcinfo.source].funclist + elseif gAC.LuaFileCache[funcinfo.source] && _istable(gAC.LuaFileCache[funcinfo.source]) && gAC.LuaFileCache[funcinfo.source].funclist then + funclist = gAC.LuaFileCache[funcinfo.source].funclist + end + if funclist then + if LuaFileUpdates && !LuaFileUpdates[funcinfo.source] then + LuaFileUpdates[funcinfo.source] = true + gAC.UpdateLuaFile(funcinfo.source) + return + end + for k=1, #funclist do + local v = funclist[k] + if v.lastlinedefined == funcinfo.lastlinedefined and v.linedefined == funcinfo.linedefined and (ProtoCheck == true and v.proto == funcinfo.proto or true) then + return true + end + end + return false + end + return true + end + + --[[ + Logs and documents all actions commited on this part of the anticheat to a file for development purposes. + Use this to report to us if any failures or false detections occur. + + *Improvements needed for this function + Improve how logging works by making more detailed responses. + ]] + local detectiontypes = { + [1] = "Client's returned a malformed packet of data.", + [2] = "Client's executed function source differentiates from Server's lua cache.", + [3] = "Client's lua stack source differentiates from Server's lua cache.", + [4] = "Client's lua stack bytecode differentiates from Server's lua cache.", + } + function gAC.AntiLuaAddDetection(ply, reasoning, stacktype, clstack, svstack) + ply.LuaExecDetected = true + gAC.AddDetection(ply, reasoning, gAC.config.AntiLua_PUNISHMENT, gAC.config.AntiLua_BANTIME) + + local ID64 = ply:SteamID64() + local time = os.time() + clstack = _util_TableToJSON(clstack, true) + local response = "WARNING: Do not reveal this to cheaters!" + response = response .. "\nDate of Occurance: " .. os.date("%I:%M:%S %p - %d/%m/%Y", time) + response = response .. "\nClient 'https://steamcommunity.com/profiles/" .. ID64 .. "' reply\n" .. clstack + if svstack then + response = response .. "\nServer reply\n" .. svstack + end + if stacktype and detectiontypes[stacktype] then response = response .. '\n' .. detectiontypes[stacktype] end + local folderdate = 'gac-antilua/' .. os.date('%d-%m-%Y', time) + if !_file_Exists(folderdate, 'DATA') then + _file_CreateDir(folderdate) + end + _file_Write(folderdate .. '/' .. ply:SteamID64() .. "-" .. time .. ".dat", response) + end + + _hook_Add("Think", "gAC.AntiLuaNextRequest", function() + local plys = _player_GetAll() + local CT = _CurTime() + for i=1, #plys do + local pl = plys[i] + if not pl.gAC_ClientLoaded or pl.gAC_Verifiying then continue end + if pl.LuaExecDetected then continue end + if not pl.gAC_ALNextReq then pl.gAC_ALNextReq = 0 end + if pl.gAC_ALNextReq ~= -1 and pl.gAC_ALNextReq < CT then + pl.gAC_ALNextReq = -1 + gAC.Network:Send("g-AC_LuaExec", "1", pl) + end + end + end) + + gAC.Network:AddReceiver("g-AC_LuaExec",function(tabledata, ply) + if ply.LuaExecDetected then return end + local CT = _CurTime() + if ply.gAC_ALNextReq ~= -1 then + ply.LuaExecDetected = true + gAC.AddDetection(ply, "AntiLua network manipulation [Code 126]", gAC.config.AntiLua_Net_PUNISHMENT, gAC.config.AntiLua_Net_BANTIME) + return + end + local userid = ply:UserID() + if tabledata == "1" then + if ply.gAC_ALNextReq < CT then + ply.gAC_ALNextReq = _CurTime() + gAC.config.AntiLua_RequestTime + end + _timer_Start("gAC.AntiLua-" .. userid) + return + end + local succ, data = _pcall(_util_JSONToTable, tabledata) + if !succ or #data > 500 then + ply.LuaExecDetected = true + gAC.AddDetection(ply, "AntiLua network manipulation [Code 126]", gAC.config.AntiLua_Net_PUNISHMENT, gAC.config.AntiLua_Net_BANTIME) + return + end + ply.gAC_ALNextReq = CT + gAC.config.AntiLua_RequestTimeActive + _timer_Start("gAC.AntiLua-" .. userid) + for k=1, #data do + local v = data[k] + if v.funcname then + if v.source && _isstring(v.source) then + if v.source == "Startup" and ply.gAC_LuaExecStartup and ply.gAC_LuaExecStartup ~= 2 then + ply.gAC_LuaExecStartup = 2 + continue + end + if gAC.VerifyLuaSource(v, userid) == false then + if v.func && gAC.LuaFuncSources[v.func] then + local isfine = nil + for kk, vv in _pairs(gAC.LuaFuncSources[v.func]) do + if v[kk] == vv then + isfine = true + break + end + end + if isfine then + if v.funcname == "RunString" or v.funcname == "RunStringEx" or v.funcname == "CompileString" then + if v.execidentifier then + gAC.AddSource(userid, v.execidentifier, v.code) + end + end + continue + end + elseif v.source == "[C]" && v.short_src == "[C]" && v.what == "C" then + if v.funcname == "RunString" or v.funcname == "RunStringEx" or v.funcname == "CompileString" then + if v.execidentifier then + gAC.AddSource(userid, v.execidentifier, v.code) + end + end + continue + end + gAC.AntiLuaAddDetection(ply, "Unauthorized lua execution (func: " .. v.funcname .. " | src: " .. v.source .. ") [Code 123]", 2, v) + break + elseif v.funcname == "RunString" or v.funcname == "RunStringEx" or v.funcname == "CompileString" then + if v.execidentifier then + gAC.AddSource(userid, v.execidentifier, v.code) + end + end + else + gAC.AntiLuaAddDetection(ply, "Unauthorized lua execution [Code 123]", 1, v) + break + end + else + if v.source && _isstring(v.source) then + if gAC.VerifyLuaSource(v, userid) == false then + if v.source == "Startup" && !ply.gAC_LuaExecStartup && !gAC.config.AntiLua_IgnoreBoot then + ply.gAC_LuaExecStartup = 1 + continue + else + gAC.AntiLuaAddDetection(ply, "Lua environment manipulation (src: " .. v.source .. ") [Code 124]", 3, v) + break + end + elseif gAC.VerifyFunction(userid, v) == false then + gAC.AntiLuaAddDetection(ply, "Lua environment manipulation (src: " .. v.source .. ") [Code 124]", 4, v) + break + end + else + gAC.AntiLuaAddDetection(ply, "Lua environment manipulation [Code 124]", 1, v) + break + end + end + end + if LuaFileUpdates then + LuaFileUpdates = {} + end + end ) + + _hook_Add("gAC.CLFilesLoaded", "gAC.AntiLua", function(ply) + _timer_Create("gAC.AntiLua-" .. ply:UserID(), gAC.config.AntiLua_Fail_TIMEOUT, 1, function() + if _IsValid(ply) && !ply.LuaExecDetected then + ply.LuaExecDetected = true + gAC.AddDetection(ply, "AntiLua information did not arrive in time [Code 125]", gAC.config.AntiLua_Fail_PUNISHMENT, gAC.config.AntiLua_Fail_BANTIME) + end + end) + end) + + _hook_Add("PlayerInitialSpawn", "gAC.AntiLua", function(ply) + gAC.LuaSession[ply:UserID()] = {} + end) + + _hook_Add("PlayerDisconnected", "gAC.AntiLua", function(ply) + gAC.LuaSession[ply:UserID()] = nil + end) + + --[[ + Lua refresh compatibility, + if a lua file is refresh, we will need to turn on source verification. + because who knows what changed... + ]] + if LuaFileUpdates then + -- Allows us to know when an execution server side was made. + gAC.LuaVM = function(proto) + local jitinfo = _jit_util_funcinfo(proto) + jitinfo.source = _string_gsub(jitinfo.source, "^@", "") + jitinfo.source = gAC.dirtosvlua(jitinfo.source) + jitinfo.source = _string_lower(jitinfo.source) + if _istable(gAC.LuaFileCache[jitinfo.source]) && gAC.LuaFileCache[jitinfo.source].funclist then + gAC.UpdateLuaFile(jitinfo.source) + end + end + + local _R = _debug_getregistry() + _R._VMEVENTS = _R._VMEVENTS or {} + _R._VMEVENTS[gAC.LuaVMID] = gAC.LuaVM + + _jit_attach(function() end, "") + end +end) + +--[[ + For development + + lang.lua addition. + +local opcodemap = +{ + [0x49] = 0x49, + [0x4A] = 0x49, + [0x4B] = 0x4B, + [0x4C] = 0x4B, + [0x4D] = 0x4B, + [0x4E] = 0x4E, + [0x4F] = 0x4E, + [0x50] = 0x4E, + [0x51] = 0x51, + [0x52] = 0x51, + [0x53] = 0x51, +} + +local opcodemap2 = +{ + [0x44] = 0x54, + [0x42] = 0x41, +} + +local function bytecodetoproto(func, funcinfo) + local data = {} + for i = _1, funcinfo.bytecodes - _1 do + local bytecode = _jit_util_funcbc (func, i) + local byte = _bit_band (bytecode, 0xFF) + if opcodemap[byte] then + bytecode = opcodemap[byte] + end + if opcodemap2[byte] then + bytecode = bytecode - byte + bytecode = bytecode + opcodemap2[byte] + end + data [#data + _1] = _string_char ( + _bit_band (bytecode, 0xFF), + _bit_band (_bit_rshift(bytecode, _8), 0xFF), + _bit_band (_bit_rshift(bytecode, _16), 0xFF), + _bit_band (_bit_rshift(bytecode, _24), 0xFF) + ) + end + return _tonumber(_util_CRC(_table_concat(data))) +end +]] \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/sv_gac_antilua_bytecode.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/sv_gac_antilua_bytecode.lua new file mode 100644 index 0000000..3712d98 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiluacheat/sv_gac_antilua_bytecode.lua @@ -0,0 +1,165 @@ +--[[ + Common anticheats use source identification to detect invalids of a lua file. + Using bytecode we can precisely pinpoint what is supposed to be in a lua file. + + This works like string.find but on a higher level of sub'ing and identification. + With the use of finding certain values in a function dump we can find function information + similar to debug.getinfo but only using the dumps to search through. + + After all string.dump was originally intended to get the dump information of lua which + can be ran again in it's dump state as normal lua code. +]] + +local _bit_band = bit.band +local _bit_rshift = bit.rshift +local _jit_util_funcbc = jit.util.funcbc +local _table_concat = table.concat +local _string_char = string.char +local _isstring = isstring +local _tonumber = tonumber +local _util_CRC = util.CRC + +ByteCode = {} + +-- Hex codes of dump lua environment. (operands) +-- https://www.lua.org/manual/5.1/manual.html & https://www.lua.org/source/5.3/lopcodes.h.html +local opcodeMap = +{ + [0x46] = 0x51, -- RET -> LOOP + [0x47] = 0x51, -- RET0 -> LOOP + [0x48] = 0x51, -- RET1 -> LOOP + [0x49] = 0x49, -- FORI -> FORI + [0x4A] = 0x49, -- JFORI -> FORI + [0x4B] = 0x4B, -- FORL -> FORL + [0x4C] = 0x4B, -- IFORL -> FORL + [0x4D] = 0x4B, -- JFORL -> FORL + [0x4E] = 0x4E, -- ITERL -> ITERL + [0x4F] = 0x4E, -- IITERL -> ITERL + [0x50] = 0x4E, -- JITERL -> ITERL + [0x51] = 0x51, -- LOOP -> LOOP + [0x52] = 0x51, -- ILOOP -> LOOP + [0x53] = 0x51, -- JLOOP -> LOOP +} + +-- Hex codes of dump lua environment. (opcode) +-- https://www.lua.org/source/5.3/lopcodes.h.html +local opcodeMap2 = +{ + [0x44] = 0x54, -- ISNEXT -> JMP + [0x42] = 0x41, -- ITERN -> ITERC +} + +--[[ + Uses the given function to get bytecode information. + uses the already provided jit.util.funcinfo information's bytecode instructions + to form a unique identifier for the function. +]] + +function ByteCode.FunctionToHash(func, funcinfo) + local data = {} + for i = 1, funcinfo.bytecodes - 1 do + local bytecode = _jit_util_funcbc (func, i) + local byte = _bit_band (bytecode, 0xFF) + if opcodeMap[byte] then + bytecode = opcodeMap[byte] + end + if opcodeMap2[byte] then + bytecode = bytecode - byte + bytecode = bytecode + opcodeMap2[byte] + end + data [#data + 1] = _string_char ( + _bit_band (bytecode, 0xFF), + _bit_band (_bit_rshift(bytecode, 8), 0xFF), + _bit_band (_bit_rshift(bytecode, 16), 0xFF), + _bit_band (_bit_rshift(bytecode, 24), 0xFF) + ) + end + return _tonumber(_util_CRC(_table_concat(data))) +end + +--[[ + Skims through function dump information and returns + the dump information of that function (like string.dump, + except using it on functions and not an entire execution) +]] + +function ByteCode.ByteCodeDumpToHash(inBuffer, instructionCount) + if _isstring (inBuffer) then + inBuffer = gAC.StringInBuffer(inBuffer) + end + + local outBuffer = gAC.StringOutBuffer() + for i = 1, instructionCount do + local instruction = inBuffer:UInt32() + local opcode = _bit_band(instruction, 0xFF) + + if opcodeMap[opcode] then + -- Remap operands + instruction = opcodeMap [opcode] + end + + if opcodeMap2[opcode] then + -- Remap opcode + instruction = instruction - opcode + instruction = instruction + opcodeMap2[opcode] + end + + outBuffer:UInt32(instruction) + end + + return _tonumber(_util_CRC(outBuffer:GetString())) +end + +--[[ + Skims through function dump information and returns line definitions & etc (kind of line debug.getinfo but using dumps) +]] + +function ByteCode.GetFuncInformation(inBuffer, functionInformation) + functionInformation = functionInformation or {} + + if _isstring (inBuffer) then + inBuffer = gAC.StringInBuffer (inBuffer) + end + + -- this is like the exact iteration of data that comes out of debug.getinfo & jit.util.funcinfo + inBuffer:UInt8() -- Flags + inBuffer:UInt8() -- Fixed parameter count + inBuffer:UInt8() -- Frame size + inBuffer:UInt8() -- Upvalue count + inBuffer:ULEB128() -- Garbage collected constant count + inBuffer:ULEB128() -- Numeric constant count + local instructionCount = inBuffer:ULEB128() -- Instruction count (the bytecode outcome on jit.util.funcinfo) + inBuffer:ULEB128() + + functionInformation.linedefined = inBuffer:ULEB128() + local lineCount = inBuffer:ULEB128() + functionInformation.lastlinedefined = functionInformation.linedefined + lineCount + functionInformation.proto = ByteCode.ByteCodeDumpToHash(inBuffer, instructionCount) + + return functionInformation +end + +--[[ + Skims through string.dump information towards an list of function dump information. +]] + +function ByteCode.DumpToFunctionList(dump) + local inBuffer = gAC.StringInBuffer (dump) + + -- Header, file execution information (signatures > flags > source) + inBuffer:Bytes(4) + inBuffer:UInt8() + inBuffer:Bytes(inBuffer:ULEB128()) + + -- Functions, their execution information + local functionInformationArray = {} + local functionDataLength = inBuffer:ULEB128() + while functionDataLength ~= 0 do + local functionData = inBuffer:Bytes(functionDataLength) + local functionInformation = ByteCode.GetFuncInformation(functionData) + functionInformationArray[#functionInformationArray + 1] = functionInformation + functionDataLength = inBuffer:ULEB128() + end + + return functionInformationArray +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antimeth/cl_gac_antimeth.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antimeth/cl_gac_antimeth.lua new file mode 100644 index 0000000..2320589 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antimeth/cl_gac_antimeth.lua @@ -0,0 +1,20 @@ +local _R = _G['debug']['getregistry']() +local _R_PLAYER_GetUserGroup = _R['Player']['GetUserGroup'] +local _debug_getinfo = debug.getinfo +--[[ + short_src = [C] + source = =[C] + what = C +]] +local detected = false +local function Player_GetUserGroup(self, ...) + if not detected then + local s = _debug_getinfo(2, "S") + if s['short_src'] == "[C]" and s['what'] == "C" then + gAC_Send("CMVa", "1") + detected = true + end + end + return _R_PLAYER_GetUserGroup(self, ...) +end +_R['Player']['GetUserGroup'] = Player_GetUserGroup \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antimeth/sv_gac_antimeth.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antimeth/sv_gac_antimeth.lua new file mode 100644 index 0000000..310de4e --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antimeth/sv_gac_antimeth.lua @@ -0,0 +1,43 @@ +local _hook_Add = hook.Add +local _tonumber = tonumber +local _util_TableToJSON = util.TableToJSON + +if(!gAC.config.ANTI_METH) then return end + +local detections = { + { + name = "rate", + value = 800000, + correct_value = 30000 + }, + + { + name = "cl_updaterate", + value = 66, + correct_value = 30 + } +} + +_hook_Add("gAC.CLFilesLoaded", "g-AC_GetMethInformation", function(ply) + if ply.Methamphetamine_User then return end + ply.Meth_Detections = 0 + for k=1, #detections do + local v = detections[k] + if(_tonumber(ply:GetInfo(v.name)) == v.value) then + ply.Meth_Detections = ply.Meth_Detections + 1 + end + end + if(ply.Meth_Detections == #detections) then + ply.Methamphetamine_User = true + gAC.AddDetection( ply, "Methamphetamine detected #1 [Code 115]", gAC.config.METH_PUNISHMENT, gAC.config.METH_BANTIME ) + end +end) + +gAC.Network:AddReceiver( + "CMVa", + function(__, ply) + if ply.Methamphetamine_User then return end + ply.Methamphetamine_User = true + gAC.AddDetection( ply, "Methamphetamine detected #2 [Code 115]", gAC.config.METH_PUNISHMENT, gAC.config.METH_BANTIME ) + end +) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antinospread/sv_gac_antinospread.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antinospread/sv_gac_antinospread.lua new file mode 100644 index 0000000..0e4ce0c --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antinospread/sv_gac_antinospread.lua @@ -0,0 +1,32 @@ +local _CurTime = CurTime +local _FindMetaTable = FindMetaTable +local _Vector = Vector +local _math_random = math.random +local _math_randomseed = math.randomseed +local _math_sqrt = math.sqrt +local _timer_Simple = timer.Simple +local _type = type + + +if !gAC.config.ANTI_NOSPREAD_CHECKS then return end + +_timer_Simple( 5, function() + local entityMeta = _FindMetaTable( "Entity" ) + + FBFunc = FBFunc or entityMeta.FireBullets + + function entityMeta:FireBullets( bulletInfo, suppressHostEvents ) + if( !bulletInfo || !bulletInfo.Num || bulletInfo.Num > 1 ) then + return FBFunc( self, bulletInfo, suppressHostEvents ) + end + + local bulletSpread = bulletInfo.Spread + if _type( bulletSpread ) == "Vector" then + bulletInfo.Spread = vector_origin + _math_randomseed( _CurTime() + _math_sqrt( bulletInfo.Dir.x ^ 2 * bulletInfo.Dir.y ^ 2 * bulletInfo.Dir.z ^ 2 ) ) + bulletInfo.Dir = bulletInfo.Dir + _Vector( bulletSpread.x * ( ( _math_random() * 2.5 ) - 1 ), bulletSpread.y * ( ( _math_random() * 2.5 ) - 1 ), bulletSpread.z * ( ( _math_random() * 2 ) - 1 ) ) + end + + return FBFunc(self, bulletInfo, suppressHostEvents ) + end +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antinovcoil/cl_gac_antivcoil.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antinovcoil/cl_gac_antivcoil.lua new file mode 100644 index 0000000..c7c6110 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antinovcoil/cl_gac_antivcoil.lua @@ -0,0 +1,76 @@ +if !gAC.config.ANTI_NORECOIL_CHECKS then return end +local _hook_Add = hook.Add +local _debug_getregistry = debug.getregistry +local _math_random = math.random +local _string_char = string.char +local _isfunction = isfunction +local _util_TableToJSON = util.TableToJSON +local _LocalPlayer = LocalPlayer +local _Angle = Angle +local _GetViewEntity = GetViewEntity + +local function floor(number) + return number - (number % 1) +end + +local function round(number, idp) + local mult = 10 ^ ( idp or 0 ) + return floor( number * mult + .5 ) / mult +end + +local function roundangle(ang, idp) + ang.p = round(ang.p, idp) + ang.y = round(ang.y, idp) + ang.r = round(ang.r, idp) + return ang +end + +local function stringrandom(length) + local str = "" + for i = 1, length do + local typo = floor(_math_random(1, 4) + .5) + if typo == 1 then + str = str.. _string_char(_math_random(97, 122)) + elseif typo == 2 then + str = str.. _string_char(_math_random(65, 90)) + elseif typo == 3 then + str = str.. _string_char(_math_random(49, 57)) + end + end + return str +end + +local _R = debug.getregistry() +local _GetViewPunchAngles = _R.Player.GetViewPunchAngles +local _EyeAngles = _R.Entity.EyeAngles +local _failures, _sent = 0, false + +local _CalcView = GAMEMODE.CalcView +function GAMEMODE:CalcView(ply, origin, angles, fov, znear, zfar, ...) + if _LocalPlayer() ~= ply or _GetViewEntity() ~= _LocalPlayer() then + return _CalcView(self, ply, origin, angles, fov, znear, zfar, ...) + end + + local vpunch = _GetViewPunchAngles(ply) + + if round(vpunch.p) == 0 && round(vpunch.y) == 0 && round(vpunch.r) == 0 then + return _CalcView(self, ply, origin, angles, fov, znear, zfar, ...) + end + + if roundangle(_EyeAngles(ply)) ~= roundangle(angles - vpunch) then + if _failures >= 20 && !_sent then + gAC_Send("g-AC_Detections", _util_TableToJSON({ + "No Recoil detected [Code 128]", + gAC.config.ANTI_NORECOIL_PUNISHMENT, + gAC.config.ANTI_NORECOIL_BANTIME + })) + _sent = true + else + _failures = _failures + 1 + end + elseif _failures > 0 then + _failures = _failures - 1 + end + + return _CalcView(self, ply, origin, angles, fov, znear, zfar, ...) +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiscreengrab/cl_gac_antiscreengrab.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiscreengrab/cl_gac_antiscreengrab.lua new file mode 100644 index 0000000..cdf56ba --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiscreengrab/cl_gac_antiscreengrab.lua @@ -0,0 +1,54 @@ +local _math_random = math.random +local _hook_Remove = hook.Remove +local _ScrW = (CLIENT and ScrW or nil) +local _timer_Simple = timer.Simple +local _ScrH = (CLIENT and ScrH or nil) +local _hook_Add = hook.Add +local _string_char = string.char +local _render_Capture = (CLIENT and render.Capture or nil) +-- Anthony, FFF, NiceCream - Anti Screengrab +local function floor(number) + return number - (number % 1) +end + +local function stringrandom(length) + local str = "" + for i = 1, length do + local typo = floor(_math_random(1, 4) + .5) + if typo == 1 then + str = str.. _string_char(_math_random(97, 122)) + elseif typo == 2 then + str = str.. _string_char(_math_random(65, 90)) + elseif typo == 3 then + str = str.. _string_char(_math_random(49, 57)) + end + end + return str +end + +gAC_AddReceiver("CRV", function(data) + local rendercount, renderedcountsaved = 0, 0 + local rc = { + format = "jpeg", + h = _ScrH(), + w = _ScrW(), + quality = q, + x = 0, + y = 0 + } + local HUDPaint = stringrandom(floor(_math_random(4, 8) + .5)) .. 'hpm3' .. stringrandom(floor(_math_random(4, 8) + .5)) + local PostRender = stringrandom(floor(_math_random(4, 8) + .5)) .. 'prm3' .. stringrandom(floor(_math_random(4, 8) + .5)) + _hook_Add( "HUDPaint", HUDPaint, function() rendercount = rendercount + 1 end ) + _timer_Simple(floor(_math_random(5, 10) + .5), function() + _hook_Add("PostRender", PostRender, function() + _hook_Remove("PostRender", PostRender) + renderedcountsaved = rendercount + -- this should call "HUDPaint" again + _render_Capture( rc ) + if(rendercount ~= renderedcountsaved) then + gAC_Send("CRV", "1") + end + _hook_Remove("HUDPaint", HUDPaint) + end) + end) +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiscreengrab/sv_gac_antiscreengrab.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiscreengrab/sv_gac_antiscreengrab.lua new file mode 100644 index 0000000..82181a8 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antiscreengrab/sv_gac_antiscreengrab.lua @@ -0,0 +1,15 @@ +local _hook_Add = hook.Add +if not gAC.config.ANTISCREENGRAB_CHECKS then return end + +gAC.Network:AddReceiver( + "CRV", + function(data, plr) + if plr.AntiScreenGrab then return end + plr.AntiScreenGrab = true + gAC.AddDetection( plr, "Anti-Screengrab Detected [Code 130]", gAC.config.ANTISCREENGRAB_PUNSIHMENT, gAC.config.ANTISCREENGRAB_PUNSIHMENT_BANTIME ) + end +) + +_hook_Add("gAC.CLFilesLoaded", "g-AC_AntiScreenGrab", function(ply) + gAC.Network:Send("CRV", "", ply) +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antisourcecrasher/sv_gac_anti_sourcecrasher.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antisourcecrasher/sv_gac_anti_sourcecrasher.lua new file mode 100644 index 0000000..e1d2684 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/antisourcecrasher/sv_gac_anti_sourcecrasher.lua @@ -0,0 +1,58 @@ +local _hook_Add = hook.Add +local _next = next +local _player_GetBySteamID = player.GetBySteamID +local _require = require + +-- CREDITS TO Octothorp TEAM FOR SLOG AND DETAILS. + +if(!gAC.config.ENABLE_SOURCECRASHER_CHECKS) then return end +if(!system.IsLinux()) then return end + +_require 'slog' +_require 'sourcenet' + +local tag = 'stringcmd_exploit' + +local maxL, maxN = 10000, 100 +local tL, tN = {}, {} + +local function punish(s) + + local pl = _player_GetBySteamID(s) + if pl.kicked then return end + + pl.kicked = true + gAC.AddDetection(pl, "Source Crasher [Code 113]", gAC.config.SOURCECRASHER_PUNISHMENT, gAC.config.SOURCECRASHER_PUNSIHMENT_BANTIME) + if CNetChan and CNetChan(pl:EntIndex()) then + CNetChan(pl:EntIndex()):Shutdown('Source Crasher [Code 113]') + end + +end + +_hook_Add('ExecuteStringCommand', tag, function(s, c) + + local cL, cN = tL[s], tN[s] + if not cL then cL = 0 end + if not cN then cN = 0 end + + if cL > maxL or cN > maxN then + punish(s) + return true + end + + tN[s] = cN + 1 + tL[s] = cL + #c + +end) + +_hook_Add('Tick', tag, function() + + for k, cL in _next, tL do + tL[k] = nil + end + + for k, cN in _next, tN do + tN[k] = nil + end + +end) diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/bansys/sv_gac_bansys.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/bansys/sv_gac_bansys.lua new file mode 100644 index 0000000..3b678d7 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/bansys/sv_gac_bansys.lua @@ -0,0 +1,205 @@ +local _Color = Color +local _RunConsoleCommand = RunConsoleCommand +local _concommand_Add = concommand.Add +local _file_CreateDir = file.CreateDir +local _file_Delete = file.Delete +local _file_Exists = file.Exists +local _file_IsDir = file.IsDir +local _file_Write = file.Write +local _hook_Add = hook.Add +local _string_len = string.len +local _tonumber = tonumber +local _timer_Exists = timer.Exists +local _timer_Remove = timer.Remove +local _timer_Create = timer.Create +local _isstring = isstring +local _player_GetBySteamID = player.GetBySteamID + +function gAC.GetBanSyntax(code) + return code or gAC.config.BAN_MESSAGE_SYNTAX +end + +function gAC.GetFormattedBanText( displayReason, banTime ) + local banString = (gAC.config.BAN_MESSAGE_SYNTAX or code) .. '\n' + banTime = _tonumber( banTime ) + if( banTime == -1 ) then + banString = banString .. "Type: Kick" + elseif( banTime >= 0 ) then + if( banTime == 0 ) then + banString = banString .. "Type: Permanent Ban\n\nPlease appeal if you believe this is false" + else + banString = banString .. "Type: Temporary Ban\n\nPlease appeal if you believe this is false" + end + end + + return banString +end + +if gAC.config.BAN_TYPE == "custom" then + function gAC.AddBan( ply, displayReason, banTime ) + banTime = _tonumber( banTime ) + ply:SetUPDataGAC( "gAC_IsBanned", true ) + ply:SetUPDataGAC( "gAC_BannedAtTime", os.time() ) + ply:SetUPDataGAC( "gAC_BanTime", banTime ) + ply:SetUPDataGAC( "gAC_BanDisplayReason", displayReason ) + + ply:Kick( gAC.GetFormattedBanText( displayReason, banTime ) ) + + if gAC.config.DELAYEDBANS then + if _timer_Exists('gAC.DelayedBan-' .. ID) then return end + if _timer_Exists('gAC.DelayedKick-' .. ID) then + _timer_Remove('gAC.DelayedKick-' .. ID) + end + _timer_Create('gAC.DelayedBan-' .. ID, gAC.config.DELAYEDBANS_TIME, 1, function() + local _ply = _player_GetBySteamID(ID) + if _ply == false then return end + ply:Kick( gAC.GetFormattedBanText( displayReason, banTime ) ) + end) + else + ply:Kick( gAC.GetFormattedBanText( displayReason, banTime ) ) + end + end + + function gAC.RemoveBan( ply ) + ply:SetUPDataGAC( "gAC_IsBanned", false ) + ply:SetUPDataGAC( "gAC_BannedAtTime", 0 ) + ply:SetUPDataGAC( "gAC_BanTime", 1 ) + ply:SetUPDataGAC( "gAC_BanDisplayReason", "nil" ) + end + + function gAC.UnbanCommand( caller, plySID64 ) + if( !gAC.PlayerHasUnbanPerm( caller ) ) then return end + if( !_file_IsDir( "g-ac", "DATA" ) ) then + _file_CreateDir( "g-ac" ) + end + + if( _file_Exists( "g-ac/" .. plySID64 .. ".txt", "DATA" ) ) then gAC.ClientMessage( caller, "That player is already due for an unban.", _Color( 225, 150, 25 ) ) return end + _file_Write( "g-ac/" .. plySID64 .. ".txt", "" ) + gAC.AdminMessage( plySID64, "Ban removed by " .. caller:Nick() .. "" ) + end + + function gAC.BanCheck( ply ) + if( _file_Exists( "g-ac/" .. ply:SteamID64() .. ".txt", "DATA" ) ) then + _file_Delete( "g-ac/" .. ply:SteamID64() .. ".txt" ) + + if( ply:GetUPDataGAC( "gAC_IsBanned" ) == true || ply:GetUPDataGAC( "gAC_IsBanned" ) == "true" || ply:GetUPDataGAC( "gAC_IsBanned" ) == 1 ) then + gAC.RemoveBan( ply ) + + gAC.AdminMessage( ply:Nick(), "Player's ban removed upon login (admin manually unbanned)", false ) + return + end + end + + if( ply:GetUPDataGAC( "gAC_IsBanned" ) == true || ply:GetUPDataGAC( "gAC_IsBanned" ) == "true" || ply:GetUPDataGAC( "gAC_IsBanned" ) == 1 ) then + if( ( os.time() >= ( _tonumber( ply:GetUPDataGAC( "gAC_BannedAtTime" ) ) + ( tonumber( ply:GetUPDataGAC( "gAC_BanTime" ) ) * 60 ) ) ) && tonumber( ply:GetUPDataGAC( "gAC_BanTime" ) ) != 0 ) then + gAC.RemoveBan( ply ) + + gAC.AdminMessage( ply:Nick(), "Player's ban expired.", false ) + else + ply:Kick( gAC.GetFormattedBanText( ply:GetUPDataGAC( "gAC_BanDisplayReason" ), ply:GetUPDataGAC( "gAC_BanTime" ) ) ) + end + end + end + + _hook_Add( "PlayerInitialSpawn", "g-ACPlayerInitialSpawnBanSys", function( ply ) + gAC.BanCheck( ply ) + end ) + + _concommand_Add( "gac-unban", function( ply, cmd, args ) + if( !gAC.PlayerHasUnbanPerm( ply ) ) then gAC.ClientMessage( ply, "You don't have permission to do that!", _Color( 225, 150, 25 ) ) return end + + local steamid64 = args[1] + + if( steamid64 == "" || steamid64 == nil ) then gAC.ClientMessage( ply, "Please input a valid SteamID64.", _Color( 225, 150, 25 ) ) return end + if( _string_len( steamid64 ) != 17 ) then gAC.ClientMessage( ply, "Please input a valid SteamID64.", _Color( 225, 150, 25 ) ) return end + gAC.UnbanCommand( ply, steamid64 ) + end ) +else + local BannablePlys = {} + + function gAC.GetBanType(ply, banTime, displayReason) + ply = (_isstring(ply) and ply or ply:SteamID()) + displayReason = gAC.GetBanSyntax(displayReason) + if gAC.config.BAN_TYPE == "ulx" then + _RunConsoleCommand( "ulx", "banid", ply, banTime, (gAC.config.BAN_MESSAGE_SYNTAX or displayReason) ) + elseif gAC.config.BAN_TYPE == "d3a" then + if( _tonumber( ply:GetUPDataGAC( "gAC_BanTime" ) ) != 0 ) then + _RunConsoleCommand( "d3a", "ban", ply, banTime, "minutes", "'" .. (gAC.config.BAN_MESSAGE_SYNTAX or displayReason) .. "'" ) + else + _RunConsoleCommand( "d3a", "perma", ply, "'" .. (gAC.config.BAN_MESSAGE_SYNTAX or displayReason) .. "'" ) + end + elseif gAC.config.BAN_TYPE == "serverguard" then + _RunConsoleCommand( "serverguard_ban", ply, banTime / 60, (gAC.config.BAN_MESSAGE_SYNTAX or displayReason) ) + elseif gAC.config.BAN_TYPE == "sam" then + SAM.AddBan( ply, nil, banTime / 60, (gAC.config.BAN_MESSAGE_SYNTAX or displayReason) ) + elseif gAC.config.BAN_TYPE == "custom_func" then + gAC.config.BAN_FUNC( ply, banTime, displayReason ) + end + end + + function gAC.AddBan( ply, displayReason, banTime ) + if gAC.config.DELAYEDBANS then + local ID = ply:SteamID() + if _timer_Exists('gAC.DelayedBan-' .. ID) and BannablePlys[ID] then + if BannablePlys[ID]['banTime'] < banTime or (BannablePlys[ID]['banTime'] ~= 0 and banTime == 0) then + if _timer_Exists('gAC.DelayedKick-' .. ID) then + _timer_Remove('gAC.DelayedKick-' .. ID) + end + BannablePlys[ID] = { + ['displayReason'] = displayReason, + ['banTime'] = banTime + } + end + return + end + if _timer_Exists('gAC.DelayedKick-' .. ID) then + _timer_Remove('gAC.DelayedKick-' .. ID) + end + BannablePlys[ID] = { + ['displayReason'] = displayReason, + ['banTime'] = banTime + } + _timer_Create('gAC.DelayedBan-' .. ID, gAC.config.DELAYEDBANS_TIME, 1, function() + if BannablePlys[ID] then + gAC.GetBanType(ID, BannablePlys[ID]['banTime'], BannablePlys[ID]['displayReason']) + BannablePlys[ID] = nil + end + end) + else + gAC.GetBanType(ply, banTime, displayReason) + end + end + + _hook_Add('PlayerDisconnected', 'gAC.BanDisconnected', function(ply) + local ID = ply:SteamID() + if BannablePlys[ID] then + if _timer_Exists('gAC.DelayedBan-' .. ID) then + _timer_Remove('gAC.DelayedBan-' .. ID) + end + gAC.GetBanType(ID, BannablePlys[ID]['banTime'], BannablePlys[ID]['displayReason']) + BannablePlys[ID] = nil + end + end) +end + +function gAC.Kick( ply, displayReason ) + local ID = ply:SteamID() + if gAC.config.DELAYEDKICKS then + if _timer_Exists('gAC.DelayedKick-' .. ID) or _timer_Exists('gAC.DelayedBan-' .. ID) then return end + _timer_Create('gAC.DelayedKick-' .. ID, gAC.config.DELAYEDKICKS_TIME, 1, function() + local _ply = _player_GetBySteamID(ID) + if _ply == false then return end + if gAC.config.KICK_TYPE == "default" then + _ply:Kick( gAC.GetFormattedBanText( displayReason, -1 ) ) + elseif gAC.config.KICK_TYPE == "custom_func" then + gAC.config.KICK_FUNC( _ply, gAC.GetBanSyntax(displayReason) ) + end + end) + else + if gAC.config.KICK_TYPE == "default" then + ply:Kick( gAC.GetFormattedBanText( displayReason, -1 ) ) + elseif gAC.config.KICK_TYPE == "custom_func" then + gAC.config.KICK_FUNC( ply, gAC.GetBanSyntax(displayReason) ) + end + end +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/concommandabuse/cl_gac_concommand_abuse.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/concommandabuse/cl_gac_concommand_abuse.lua new file mode 100644 index 0000000..4113724 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/concommandabuse/cl_gac_concommand_abuse.lua @@ -0,0 +1,55 @@ +local _concommand_Add = concommand.Add +local _pairs = pairs +local _util_JSONToTable = util.JSONToTable +local _concommand_GetTable = concommand.GetTable + +if !gAC.config.ILLEGAL_CONCOMMAND_CHECKS then + return +end + +local TBL = nil +local Logged = {} + +local a = _concommand_GetTable() +for k, v in _pairs(a) do + Logged[#Logged + 1] = k +end + +concommand.Add = function(name, callback, autoComplete, helpText, flags, ...) + if TBL == nil then + Logged[#Logged + 1] = name + else + local notbad = true + for d,f in _pairs(TBL)do + if(name==f)then + notbad = false + end + end + if !notbad then + gAC_Send("g-ACIllegalConCommand", "") + return + end + end + _concommand_Add(name, callback, autoComplete, helpText, flags, ...) +end + +gAC_AddReceiver("g-ACReceiveExploitList", function(data) + TBL = _util_JSONToTable(data) + if Logged then + local notbad = true + for k=1, #Logged do + local v = Logged[k] + for d,f in _pairs(TBL)do + if v == f then + notbad = false + end + end + end + if !notbad then + gAC_Send("g-ACIllegalConCommand", "") + end + Logged = nil + end +end) + +gAC_Send("g-ACReceiveExploitListCS", "") \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/concommandabuse/sv_gac_concommand_abuse.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/concommandabuse/sv_gac_concommand_abuse.lua new file mode 100644 index 0000000..6b4cc4d --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/concommandabuse/sv_gac_concommand_abuse.lua @@ -0,0 +1,12 @@ +local _util_TableToJSON = util.TableToJSON + +if !gAC.config.ILLEGAL_CONCOMMAND_CHECKS then return end +gAC.Network:AddReceiver("g-ACIllegalConCommand",function(data, ply) + gAC.AddDetection( ply, "Illegal console command detected [Code 104]", gAC.config.ILLEGAL_CONCOMMAND_PUNISHMENT, gAC.config.ILLEGAL_CONCOMMAND_BANTIME ) +end ) + +local badcommands = _util_TableToJSON(gAC.config.BAD_COMMANDS_LIST) + +gAC.Network:AddReceiver("g-ACReceiveExploitListCS",function(data, ply) + gAC.Network:Send("g-ACReceiveExploitList", badcommands, ply) +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/cvarmanip/cl_gac_cvarmanip.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/cvarmanip/cl_gac_cvarmanip.lua new file mode 100644 index 0000000..49ffe2a --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/cvarmanip/cl_gac_cvarmanip.lua @@ -0,0 +1,20 @@ +local _GetConVar = GetConVar +local _table_insert = table.insert +local _util_TableToJSON = util.TableToJSON + +gAC_AddReceiver("G-ACcVarManipCS1", function() + local convars = {} + if _GetConVar( "sv_allowcslua" ) != nil then + _table_insert( convars, 0, _GetConVar( "sv_allowcslua" ):GetInt() ) + else + _table_insert( convars, 0, 1 ) + end + + if _GetConVar( "sv_cheats" ) != nil then + _table_insert( convars, 1, _GetConVar( "sv_cheats" ):GetInt() ) + else + _table_insert( convars, 1, 1 ) + end + + gAC_Send("G-ACcVarManipSV1", _util_TableToJSON(convars)) +end ) diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/cvarmanip/sv_gac_cvarmanip.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/cvarmanip/sv_gac_cvarmanip.lua new file mode 100644 index 0000000..48efbe3 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/cvarmanip/sv_gac_cvarmanip.lua @@ -0,0 +1,61 @@ +local _CurTime = CurTime +local _GetConVar = GetConVar +local _hook_Add = hook.Add +local _player_GetAll = player.GetAll +local _util_JSONToTable = util.JSONToTable + +gAC.Network:AddReceiver( + "G-ACcVarManipSV1", + function(checkedVariables, plr) + checkedVariables = _util_JSONToTable(checkedVariables) + if( ( checkedVariables[0] != _GetConVar("sv_allowcslua"):GetInt() && gAC.config.ALLOWCSLUA_CHECKS ) || ( checkedVariables[1] != GetConVar("sv_cheats"):GetInt() && gAC.config.SVCHEATS_CHECKS ) ) then + gAC.AddDetection( plr, "Anti C-var manipulation triggered [Code 100]", gAC.config.CVARMANIP_PUNISHMENT, gAC.config.CVARMANIP_BANTIME ) + plr.GAC_Cvar_Checks = nil + end + plr.HasReceivedVarManipResults = true + end +) + +if( gAC.config.ALLOWCSLUA_CHECKS == true || gAC.config.SVCHEATS_CHECKS == true ) then + _hook_Add("Tick", "gAC-CheckCvars", function() + local _IPAIRS_ = _player_GetAll() + for k=1, #_IPAIRS_ do + local ply =_IPAIRS_[k] + if ply:IsBot() then continue end + if !ply.GAC_Cvar_Checks then continue end + if ply:IsTimingOut() then continue end + if ply.HasReceivedVarManipResults == nil && ply.GAC_Cvar_Checks > 0 && ply.GAC_Cvar_Checks <= _CurTime() then + if ply.GAC_Cvar > 6 then + gAC.AddDetection( ply, "C-var manipulation results haven't returned [Code 101]", gAC.config.CVARMANIP_PUNISHMENT, -1 ) + ply.GAC_Cvar_Checks = nil + continue + end + ply.GAC_Cvar = ply.GAC_Cvar + 1 + ply.GAC_Cvar_Checks = _CurTime() + 20 + gAC.Network:Send("G-ACcVarManipCS1", "", ply) + continue + end + if ply.GAC_Cvar_Checks > _CurTime() then continue end + ply.HasReceivedVarManipResults = nil + ply.GAC_Cvar = 0 + ply.GAC_Cvar_Checks = _CurTime() + 15 + gAC.Network:Send("G-ACcVarManipCS1", "", ply) + end + end ) +end + +_hook_Add("gAC.CLFilesLoaded", "CheckCvars", function(ply) + ply.GAC_Cvar = 0 + ply.GAC_Cvar_Checks = 0 +end) + +if gAC.config.DISABLE_BAD_COMMANDS then + _hook_Add( "Initialize", "g-ACcVarManipSV3", function() + game.ConsoleCommand("sv_allowcslua 0\n") + game.ConsoleCommand("sv_cheats 0\n") + end ) +end + +_hook_Add( "gAC.CLFilesLoaded", "g-ACPlayerInitialSpawnJointimeChecker", function( ply ) + ply.JoinTimeGAC = _CurTime() +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/detectionsys/sv_gac_detectionsys.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/detectionsys/sv_gac_detectionsys.lua new file mode 100644 index 0000000..ea24713 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/detectionsys/sv_gac_detectionsys.lua @@ -0,0 +1,50 @@ +local _util_JSONToTable = util.JSONToTable +local _print = print +local _http_Post = http.Post + +function gAC.AddDetection( ply, displayReason, shouldPunish, banTime ) + if !gAC.Debug && gAC.config.IMMUNE_USERS[ply:SteamID64()] then return end + + gAC.AdminMessage( ply:Nick() .. " (" .. ply:SteamID() .. ")" , displayReason, shouldPunish, banTime ) + gAC.Print( "Detection from " .. ply:Nick() .. " (" .. ply:SteamID() .. ") -> " .. displayReason ) + gAC.SendDetectionWebhook( ply, displayReason, shouldPunish, banTime ) + + local punishmentT = 0 + if shouldPunish == 1 then + punishmentT = banTime + else + punishmentT = -2 + end + + -- _http_Post( "https://stats.g-ac.dev/api/detection/add", { server_id = gAC.server_id, target = ply:SteamID64(), detection = displayReason, punishment = punishmentT }, function( result ) + -- local resp = util.JSONToTable(result) + -- if resp == nil then return end + -- if(resp["success"] == "false") then + -- gAC.Print("[Statistics] Generating statistics report failed: "..resp["error"]) + -- else + -- gAC.Print("[Statistics] Stat report generated. ID: "..resp["id"]) + -- end + -- end, function( failed ) + -- gAC.Print( "[Statistics] Stats report failed: " .. failed ) + -- end ) + + if gAC.Debug then return end + + gAC.LogEvent( ply, displayReason ) + + if !shouldPunish then return end + + if( banTime >= 0 ) then + gAC.AddBan( ply, displayReason, banTime ) + elseif( banTime == -1 ) then + gAC.Kick( ply, displayReason ) + end +end + +gAC.Network:AddReceiver( + "g-AC_Detections", + function(data, plr) + data = _util_JSONToTable(data) + gAC.AddDetection( plr, data[1], data[2], data[3] ) + end +) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/discordwebhook/sv_gac_discordwebhook.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/discordwebhook/sv_gac_discordwebhook.lua new file mode 100644 index 0000000..c4dfb57 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/discordwebhook/sv_gac_discordwebhook.lua @@ -0,0 +1,48 @@ + +local sentConnectWebhook = false + +function gAC.SendConnectionWebhook() + -- http.Post( "https://glorifieddrm.net/gac-webhook.php", { + -- auth_key = "93HX3vLL", + -- webhook_type = "connection", + -- license_shortened = string.sub( gAC.config.LICENSE, 1, 4 ), + -- server_name = GetHostName() + -- } ) +end + +function gAC.SendDetectionWebhook( ply, displayReason, shouldPunish, banTime ) + local detectionPunishment = "No punishment" + local serverWebhook = "nowebhook" + + if gAC.config.ENABLE_DISCORD_WEBHOOK == true && string.len( gAC.config.DISCORD_WEBHOOK_URL ) >= 5 then + serverWebhook = gAC.config.DISCORD_WEBHOOK_URL + end + + if shouldPunish then + if banTime == -1 then + detectionPunishment = "Kick" + elseif banTime == 0 then + detectionPunishment = "Permanent Ban" + elseif banTime >= 1 then + detectionPunishment = "Ban (" .. banTime .. " minutes)" + end + end + + -- http.Post( "https://glorifieddrm.net/gac-webhook.php", { + -- auth_key = "93HX3vLL", + -- webhook_type = "detection", + -- player_detected_name = ply:Nick(), + -- player_detected_steamid = ply:SteamID(), + -- server_name = GetHostName(), + -- detection_code = displayReason, + -- detection_punishment = detectionPunishment, + -- webhook_url = serverWebhook + -- } ) +end + +hook.Add( "Think", "GlorifiedAnticheat.DiscordWebhook.Think", function() + if sentConnectWebhook == false then + gAC.SendConnectionWebhook() + end + hook.Remove( "Think", "GlorifiedAnticheat.DiscordWebhook.Think" ) +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/familysharecheck/sv_gac_familysharecheck.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/familysharecheck/sv_gac_familysharecheck.lua new file mode 100644 index 0000000..1564863 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/familysharecheck/sv_gac_familysharecheck.lua @@ -0,0 +1,26 @@ +local _hook_Add = hook.Add +local _string_len = string.len +local _tonumber = tonumber +local _util_JSONToTable = util.JSONToTable +local _http_Fetch = http.Fetch + + +function gAC.FamilyShareCheck( ply ) + if( _string_len( gAC.config.STEAM_API_KEY ) <= 1 || !gAC.config.ENABLE_FAMILY_SHARE_CHECKS ) then return end + + _http_Fetch( "http://api.steampowered.com/IPlayerService/IsPlayingSharedGame/v0001/?key=" .. gAC.config.STEAM_API_KEY .. "&format=json&steamid=" .. ply:SteamID64() .. "&appid_playing=4000", + function( body ) + if( !body ) then return end + local bodyTable = _util_JSONToTable( body ) + if( !bodyTable || !bodyTable.response || !bodyTable.response.lender_steamid ) then return end + + local ownerSteamID = _tonumber( bodyTable.response.lender_steamid ) + if( ownerSteamID == 0 ) then return end + + gAC.AddDetection( ply, "Joined from family shared account [Code 105]", gAC.config.FAMILY_SHARE_PUNISHMENT, gAC.config.FAMILY_SHARE_BANTIME ) + end ) +end + +-- _hook_Add( "PlayerInitialSpawn", "g-ACFamilyShareCheckInitialSpawn", function( ply ) +-- gAC.FamilyShareCheck( ply ) +-- end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/globalbans/sv_gac_globalbans.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/globalbans/sv_gac_globalbans.lua new file mode 100644 index 0000000..a0a80af --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/globalbans/sv_gac_globalbans.lua @@ -0,0 +1,87 @@ +local _hook_Add = hook.Add +local _hook_Remove = hook.Remove +local _util_JSONToTable = util.JSONToTable +local _tonumber = tonumber +local _print = print +local _http_Post = http.Post +local _game_KickID = game.KickID +local _util_SteamIDFrom64 = util.SteamIDFrom64 + +function gAC.GetFormattedGlobalText( displayReason, banTime ) + local banString = "g-AC Global Ban ["..displayReason.."]".. '\n' + + banTime = _tonumber( banTime ) + if( banTime == -1 ) then + banString = banString .. "Type: Kick" + elseif( banTime >= 0 ) then + if( banTime == 0 ) then + banString = banString .. "Type: Permanent Ban\n\nPlease appeal if you believe this is false" + else + banString = banString .. "Type: Temporary Ban\n\nPlease appeal if you believe this is false" + end + end + + return banString +end + +-- _hook_Add( 'Think', 'g-AC_getGlobalInfo', function() +-- _hook_Remove( 'Think', 'g-AC_getGlobalInfo' ) +-- _http_Post( "https://stats.g-ac.dev/api/server/id", { license = gAC.config.LICENSE, hostname = GetHostName() }, function( result ) +-- local resp = _util_JSONToTable(result) +-- if resp == nil then +-- gAC.Print("[Global Bans] No API response - please contact GlorifiedPig.") +-- return +-- end +-- if(resp["success"] == "false") then +-- gAC.Print( "[Global Bans] Retreiving Server ID failed: " .. resp["error"] ) +-- gAC.server_id = 0 +-- else +-- gAC.Print( "[Global Bans] Server ID has been assigned (" .. resp["id"] .. ")." ) +-- gAC.server_id = resp["id"] +-- end +-- end, function( failed ) +-- gAC.Print( "[Global Bans] Retreiving Server ID failed: " .. failed ) +-- end ) +-- end ) + +-- Due to how admin systems prevent any other system from using CheckPassword, :shrug: + +-- _hook_Add( 'PostGamemodeLoaded', 'gAC.GlobalBansInit', function() +-- local GAMEMODE = GAMEMODE or GM +-- if GAMEMODE.CheckPassword then +-- gAC.CheckPassword_Old = GAMEMODE.CheckPassword +-- function GAMEMODE:CheckPassword(SteamID, IP, sv_Pass, cl_Pass, Name, ...) +-- _http_Post( "https://stats.g-ac.dev/api/checkban", { player = SteamID }, function( result ) +-- local resp = _util_JSONToTable(result) +-- if resp == nil then return end +-- if(resp["success"] == "false") then +-- gAC.Print("[Global Bans] Fetching global ban data failed: "..resp["error"]) +-- else +-- if(resp["banned"] == "true") then +-- _game_KickID(_util_SteamIDFrom64(SteamID), gAC.GetFormattedGlobalText(resp["id"], 0)); +-- end +-- end +-- end, function( failed ) +-- gAC.Print("[Global Bans] Fetching global ban data failed: " .. failed ) +-- end ) +-- return gAC.CheckPassword_Old(self, SteamID, IP, sv_Pass, cl_Pass, Name, ...) +-- end +-- else +-- _hook_Add("PlayerAuthed", "g-AC_getGlobalInfo", function( ply ) +-- _http_Post( "https://stats.g-ac.dev/api/checkban", { player = ply:SteamID64() }, function( result ) +-- local resp = _util_JSONToTable(result) +-- if resp == nil then return end +-- if(resp["success"] == "false") then +-- gAC.Print( "[Global Bans] Fetching global ban data failed: "..resp["error"] ) +-- else +-- if(resp["banned"] == "true") then +-- ply:Kick(gAC.GetFormattedGlobalText(resp["id"], 0)) +-- end +-- end +-- end, function( failed ) +-- gAC.Print("[Global Bans] Fetching global ban data failed: " .. failed ) +-- end ) +-- end) +-- end +-- _hook_Remove( 'PostGamemodeLoaded', 'gAC.GlobalBansInit' ) +-- end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/keybinds/sv_gac_keybindings.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/keybinds/sv_gac_keybindings.lua new file mode 100644 index 0000000..d7ec084 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/keybinds/sv_gac_keybindings.lua @@ -0,0 +1,18 @@ +local _CurTime = CurTime +local _hook_Add = hook.Add + + +if !gAC.config.KEYBIND_CHECKS then return end + +_hook_Add( "PlayerButtonDown", "g-ACPlayerButtonDownKeyBindCheck", function( ply, button ) + + if( ( button == KEY_HOME || button == KEY_INSERT || button == KEY_END ) && ( ply.gAC_TimeSinceKeyCheck == nil || _CurTime() >= ply.gAC_TimeSinceKeyCheck + 10 ) ) then + ply.gAC_TimeSinceKeyCheck = _CurTime() + + local buttonName = "" + if( button == KEY_HOME ) then buttonName = "HOME" elseif( button == KEY_INSERT ) then buttonName = "INSERT" elseif( button == KEY_END ) then buttonName = "END" end + + gAC.AddDetection( ply, "Suspicious keybind (" .. buttonName .. ") pressed [Code 102]", false ) + end + +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/notifications/cl_gac_notifications.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/notifications/cl_gac_notifications.lua new file mode 100644 index 0000000..70bfdbb --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/notifications/cl_gac_notifications.lua @@ -0,0 +1,55 @@ +local _Color = Color +local _IsValid = IsValid +local _isstring = isstring +local _net_ReadTable = net.ReadTable +local _net_Receive = net.Receive +local _string_len = string.len + +local _surface_PlaySound = (CLIENT and surface.PlaySound or NULL) + + +_net_Receive( "g-ACReceiveClientMessage1", function() + + local infoTable = _net_ReadTable() + local ply = infoTable[1] + local displayReason = infoTable[2] + local wasPunished = infoTable[3] + local banTime = infoTable[4] + + if( _isstring( ply ) && _string_len( ply ) == 17 ) then + chat.AddText( _Color( 30, 150, 255 ), gAC.config.SYNTAX, _Color( 255, 50, 50 ), "Detection from '", _Color( 255, 0, 0 ), ply, _Color( 255, 50, 50 ), "'" ) + else + if( !_isstring( ply ) && ply:IsValid() && ply:IsPlayer() ) then + chat.AddText( _Color( 30, 150, 255 ), gAC.config.SYNTAX, _Color( 255, 50, 50 ), "Detection from '", _Color( 255, 0, 0 ), ply:Nick(), _Color( 255, 50, 50 ), "'" ) + end + + if( _isstring( ply ) ) then + chat.AddText( _Color( 30, 150, 255 ), gAC.config.SYNTAX, _Color( 255, 50, 50 ), "Detection from '", _Color( 255, 0, 0 ), ply, _Color( 255, 50, 50 ), "'" ) + end + end + + chat.AddText( _Color( 255, 50, 50 ), "Reasoning: '", _Color( 255, 0, 0 ), displayReason , _Color( 255, 50, 50 ), "'" ) + if( wasPunished ) then + if( banTime == -1 ) then + chat.AddText( _Color( 255, 50, 50 ), "Punishment: ", _Color( 255, 0, 0 ), "Kick" ) + elseif( banTime == 0 ) then + chat.AddText( _Color( 255, 50, 50 ), "Punishment: ", _Color( 255, 0, 0 ), "Permanent Ban" ) + elseif( banTime >= 0 ) then + chat.AddText( _Color( 255, 50, 50 ), "Punishment: ", _Color( 255, 0, 0 ), "Temporary Ban (" .. banTime .. " minutes)" ) + end + end + + if gAC.config.ADMIN_MESSAGE_PING != "" then + _surface_PlaySound(gAC.config.ADMIN_MESSAGE_PING) + end +end ) + +_net_Receive( "g-ACReceiveClientMessage2", function() + + local infoTable = _net_ReadTable() + local message = infoTable[1] + local colour = infoTable[2] + + chat.AddText( _Color( 30, 150, 255 ), gAC.config.SYNTAX, colour, message ) + +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/notifications/sv_gac_notifications.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/notifications/sv_gac_notifications.lua new file mode 100644 index 0000000..d11f86d --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/notifications/sv_gac_notifications.lua @@ -0,0 +1,51 @@ +local _Color = Color +local _IsValid = IsValid +local _MsgC = MsgC +local _net_Start = net.Start +local _net_WriteTable = net.WriteTable +local _pairs = pairs +local _player_GetAll = player.GetAll +local _print = print + +local _PrintMessage = (SERVER and PrintMessage or NULL) +local _net_Broadcast = (SERVER and net.Broadcast or NULL) +local _net_Send = (SERVER and net.Send or NULL) +local _util_AddNetworkString = (SERVER and util.AddNetworkString or NULL) + + +_util_AddNetworkString( "g-ACReceiveClientMessage1" ) +_util_AddNetworkString( "g-ACReceiveClientMessage2" ) + +function gAC.AdminMessage( ply, displayReason, wasPunished, banTime ) + for k, v in _pairs( _player_GetAll() ) do + if( gAC.PlayerHasAdminMessagePerm( v ) ) then + _net_Start( "g-ACReceiveClientMessage1" ) + _net_WriteTable( { ply, displayReason, wasPunished, banTime } ) + _net_Send( v ) + end + end +end + +function gAC.ClientMessage( ply, message, colour ) + if !_IsValid(ply) then + _MsgC( _Color( 30, 150, 255 ), gAC.config.SYNTAX, colour, message .. "\n" ) + else + _net_Start( "g-ACReceiveClientMessage2" ) + _net_WriteTable( { message, colour } ) + _net_Send( ply ) + end +end + +function gAC.PrintMessage( ply, type, message ) + if _IsValid(ply) then + ply:PrintMessage(type, message) + else + _print(message) + end +end + +function gAC.Broadcast( message, colour ) + _net_Start( "g-ACReceiveClientMessage2" ) + _net_WriteTable( { message, colour } ) + _net_Broadcast() +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/uniquepdata/sv_uniquepdata.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/uniquepdata/sv_uniquepdata.lua new file mode 100644 index 0000000..6bd498b --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/uniquepdata/sv_uniquepdata.lua @@ -0,0 +1,34 @@ +local _FindMetaTable = FindMetaTable +local _Format = Format +local _sql_Query = sql.Query +local _sql_QueryValue = sql.QueryValue +local _SQLStr = SQLStr + +local plyMeta = _FindMetaTable( "Player" ) + +if( !plyMeta ) then return end + +if ( !sql.TableExists( "playerupdata" ) ) then + _sql_Query( "CREATE TABLE IF NOT EXISTS playerupdata ( infoid TEXT NOT NULL PRIMARY KEY, value TEXT );" ) +end + +function plyMeta:SetUPDataGAC( name, value ) + name = _Format( "%s[%s]", self:SteamID64(), name ) + _sql_Query( "REPLACE INTO playerupdata ( infoid, value ) VALUES ( " .. _SQLStr( name ) .. ", " .. _SQLStr( value ) .. " )" ) +end + +function plyMeta:GetUPDataGAC( name, default ) + name = _Format( "%s[%s]", self:SteamID64(), name ) + local val = _sql_QueryValue( "SELECT value FROM playerupdata WHERE infoid = " .. _SQLStr( name ) .. " LIMIT 1" ) + if ( val == nil ) then return default end + + return val +end + +function GetUPDataGACSID64( name, steamId, default ) + name = _Format( "%s[%s]", steamId, name ) + local val = _sql_QueryValue( "SELECT value FROM playerupdata WHERE infoid = " .. _SQLStr( name ) .. " LIMIT 1" ) + if ( val == nil ) then return default end + + return val +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/verification/cl_verify.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/verification/cl_verify.lua new file mode 100644 index 0000000..5e4d3d7 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/verification/cl_verify.lua @@ -0,0 +1,43 @@ +local _isbool = isbool +local _isnumber = isnumber +local _istable = istable +local _pairs = pairs +local _type = type +local _util_JSONToTable = util.JSONToTable +local _util_TableToJSON = util.TableToJSON + +local function SendInfo() + gAC_Send("g-AC_ACVerify", "") +end + +gAC_AddReceiver("g-AC_ACVerify", function(data) + local gAC_CFG = _util_JSONToTable(data) + if !gAC or !_istable(gAC) then + SendInfo() + return + else + if !gAC.config or !_istable(gAC.config) then + SendInfo() + return + end + end + for k, v in _pairs(gAC_CFG) do + local CFG = gAC.config[k] + if CFG == nil then + SendInfo() + return + else + if _type(CFG) != _type(v) then + SendInfo() + return + else + if _isbool(v) or _isnumber(v) then + if CFG != v then + SendInfo() + return + end + end + end + end + end +end) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/verification/sv_verify.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/verification/sv_verify.lua new file mode 100644 index 0000000..9dfb914 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/verification/sv_verify.lua @@ -0,0 +1,54 @@ +local _IsValid = IsValid +local _hook_Add = hook.Add +local _isbool = isbool +local _isnumber = isnumber +local _pairs = pairs +local _CurTime = CurTime +local _player_GetHumans = player.GetHumans +local _timer_Simple = timer.Simple +local _util_TableToJSON = util.TableToJSON + +--[[ + Considering how you can block certain functions and values in C++ + this is just to check if there was any alterations to gAC's config + since ya know. LuaI has most of the code localized & protected except for the configs. + + oh and i know meth is going to try and block the global var 'gAC' because if it get's blocked + all functions requiring the config file will fail. +]] + +if !gAC.config.INTEGRITY_CHECKS then return end + +local Configs = {} + +for k, v in _pairs(gAC.config) do + if gAC.config.INTEGRITY_INGORES[k] then continue end + if _isbool(v) or _isnumber(v) then + Configs[k] = v + end +end + +Configs = _util_TableToJSON(Configs) + +_hook_Add("gAC.CLFilesLoaded", "g-AC_verify_initialspawn", function(ply) + ply.GAC_IntegCheck = _CurTime() + gAC.config.INTEGRITY_CHECKS_INTERVAL +end) + +_hook_Add("Think", "g-AC_IntergrityCheck", function() + local plys = _player_GetHumans() + local ct = _CurTime() + for i=1, #plys do + local v = plys[i] + if v.GAC_IntegCheck and v.GAC_IntegCheck < ct then + gAC.Network:Send("g-AC_ACVerify", Configs, v) + v.GAC_IntegCheck = ct + gAC.config.INTEGRITY_CHECKS_INTERVAL + end + end +end) + +gAC.Network:AddReceiver( + "g-AC_ACVerify", + function(data, plr) + gAC.AddDetection( plr, "Integrity check failure [Code 117]", gAC.config.INTEGRITY_CHECKS_PUNISHMENT, gAC.config.INTEGRITY_CHECKS_BANTIME ) + end +) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/vpnchecker/sv_gac_vpnchecker.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/vpnchecker/sv_gac_vpnchecker.lua new file mode 100644 index 0000000..ad6e5c0 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/modules/vpnchecker/sv_gac_vpnchecker.lua @@ -0,0 +1,29 @@ +local _hook_Add = hook.Add +local _print = print +local _string_Split = string.Split +local _http_Fetch = http.Fetch + + +if !gAC.config.VPN_CHECKER then return end + +_hook_Add( "gAC.ClientLoaded", "g-ACPlayerInitialSpawnVPNChecker", function( ply ) + + local vpnChance = 0 + local vpnTable = _string_Split( ply:IPAddress(), ":" ) + + if vpnTable[1] == "loopback" || vpnTable[1] == nil then + _print( "[g-AC] VPN check failed. Are you running a local or P2P server?" ) + return + end + + _http_Fetch( "http://check.getipintel.net/check.php?ip=" .. vpnTable[1] .. "&contact=killingpigs123@gmail.com", + function( body, len, headers, code ) + if body == "1" then + gAC.AddDetection( ply, "Player joined using VPN [Code 122]", false ) + end + end, + function( error ) + _print( "[g-AC] VPN checking failed on player " .. ply:Nick() .. " with code " .. error .. "." ) + end ) + +end ) \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/sh_config.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/sh_config.lua new file mode 100644 index 0000000..c92e44c --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/sh_config.lua @@ -0,0 +1,54 @@ +--[[ + Warning to those with lua systems like GM-LUAI + DO NOT LIVE UPDATE THIS FILE, OR ELSE FILE VERIFICATION WILL FAIL! +]] + +--[[ ADMIN PERMISSION SETTINGS ]]-- + gAC.config.ADMIN_MESSAGE_USERGROUPS = { "admin", "sadmin", "superadmin", "dev", "founder" } -- Set all the usergroups who can see admin messages here. + gAC.config.ADMIN_MESSAGE_PING = "garrysmod/content_downloaded.wav" + gAC.config.UNBAN_USERGROUPS = { "sadmin", "superadmin", "dev", "founder" } -- Set all the usergroups who can unban players here. + gAC.config.SYNTAX = "AC: " -- Syntax for messages. +--[[ ADMIN PERMISSION SETTINGS END ]]-- + +--[[Cheat specific detections]] + --[[ ANTI METH SETTINGS ]]-- + gAC.config.ANTI_METH = true + gAC.config.METH_PUNISHMENT = true + gAC.config.METH_BANTIME = 0 + --[[ ANTI METH SETTINGS END ]]-- +--[[end]] + +--[[General cheating detections]] + --[[ Lua Execution ]] + -- This does something, yet, still in development. + -- WARNING: AntiLua can be CPU intensive depending on how it is configured. + -- I've tried my best to make this as minimal as possible to reserve resources for the server. + -- Only use this if your server has enough resources to spare. + gAC.config.AntiLua_CHECK = true + + -- Please read sv-config for more info on this config. + gAC.config.AntiLua_IgnoreBoot = true + --[[ Lua Execution End]] + + --[[ ANTI Engine Prediction SETTINGS ]]-- + gAC.config.ANTI_ENGINEPRED_CHECKS = true -- Set to 'true' if you want to check for engine predictions (used on aimbots). + + gAC.config.ANTI_ENGINEPRED_PUNISHMENT = true -- Set to 'true' if you want using engine predictions to be punishable. + gAC.config.ANTI_ENGINEPRED_BANTIME = -1 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ ANTI Engine Prediction END ]]-- + + --[[ ANTI No Recoil SETTINGS ]]-- + -- WARNING, This detection modules was not fully tested yet! + gAC.config.ANTI_NORECOIL_CHECKS = true -- Set to 'true' if you want to check for no recoil (used on aimbots/etc). + + gAC.config.ANTI_NORECOIL_PUNISHMENT = true -- Set to 'true' if you want using no recoil to be punishable. + gAC.config.ANTI_NORECOIL_BANTIME = -1 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ ANTI No Recoil END ]]-- + + --[[ ILLEGAL CONCOMMAND SETTINGS ]]-- + gAC.config.ILLEGAL_CONCOMMAND_CHECKS = true -- Set to 'true' if you want to check for illegal console commands. + + gAC.config.ILLEGAL_CONCOMMAND_PUNISHMENT = true -- Set to 'true' if you want using illegal concommands to be punishable. + gAC.config.ILLEGAL_CONCOMMAND_BANTIME = 0 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ ILLEGAL CONCOMMAND SETTINGS END ]]-- +--[[end]] \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/sv_config.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/sv_config.lua new file mode 100644 index 0000000..c96afcf --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/sv_config.lua @@ -0,0 +1,280 @@ +local CFG = table.Copy(gAC.config or {}) -- for ignoring SV configs, don't remove +gAC.config = {} + +--[[ + Hello there and welcome to gAC! + Here are all the necessary configs for them pesky cheaters to stay off your server! + Please chose wisely on your decisions to enable/disable configs as + Every server is unique and may create issues depending on what is on the server. +]] + +gAC.config.LICENSE = "LICENSE" -- If you didn't receive a license please contact GlorifiedPig. + +-- Tutorial for new gAC users -- +--[[ + MySQLOO Table Setup, Simply query this into the SQL query and it should auto generate a table. + + DROP TABLE IF EXISTS `gac_detections`; + CREATE TABLE `gac_detections` ( + `time` bigint(20) COLLATE utf8_unicode_ci NOT NULL, + `steamid` text COLLATE utf8_unicode_ci NOT NULL, + `detection` text COLLATE utf8_unicode_ci NOT NULL, + `index` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +]] +--Note: i will create a function that will be able to gather information on cheat records of a user, for now use this example to create your own command. +--[[ + How to setup cheatlog command. + targetid = player's SteamID (not 64 ok?) + pl = person who executed the command. + data = data retreived from the database + nameid = player's SteamID + Name like this, Cream (STEAM_0:0:8319238493) + + Example, + gAC.GetLog( targetid, function(data) + if isstring(data) then + gAC.ClientMessage( pl, data, Color( 225, 150, 25 ) ) + else + if data == {} or data == nil then + gAC.ClientMessage( pl, nameid .. " has no detections.", Color( 0, 255, 0 ) ) + else + gAC.PrintMessage(pl, HUD_PRINTCONSOLE, "\n\n") + gAC.PrintMessage(pl, HUD_PRINTCONSOLE, "Detection Log for " .. nameid .. "\n") + for k, v in pairs(data) do + gAC.PrintMessage(pl, HUD_PRINTCONSOLE, os.date( "[%H:%M:%S %p - %d/%m/%Y]", v["time"] ) .. " - " .. v["detection"] .. "\n") + end + gAC.ClientMessage( pl, "Look in console.", Color( 0, 255, 0 ) ) + end + end + end) +]] + +--Recommend sqlite, Recommend mysql if you have more than one server (You must know basic knowledge of SQL programming). +gAC.storage.Type = "sqlite" -- Types: flatfile, sqlite, mysql + +-- MySQL Settings ("mysql" module only) +gAC.storage.hostname = "127.0.0.1" +gAC.storage.username = "root" +gAC.storage.password = "root" +gAC.storage.database = "gac" +gAC.storage.port = 3306 + +gAC.config.IMMUNE_USERS = { -- Set all the people you want to be immune to detections here, SteamID64s only. + -- "76561198061230671", -- NiceCream, remove me if you want. +} + +--[[ DISCORD WEBHOOK SETTINGS ]]-- + gAC.config.ENABLE_DISCORD_WEBHOOK = false + gAC.config.DISCORD_WEBHOOK_URL = "" -- To find this, right click a discord channel, go to "Edit Channel", click "Webhooks" and then create a new webhook. Copy and paste the URL here. +--[[ DISCORD WEBHOOK SETTINGS END ]]-- + +--[[ BAN SYSTEM SETTINGS ]]-- + --[[ + Just because some servers want their ban functions to be unique. + Like they always say, uniqueness is key. + + Ban Types: + custom - gAC's custom ban system + ulx - use the ulx ban system + d3a - use D3vine's ban system + serverguard - server-guard's ban system + custom_func - uses BAN_FUNC to ban users, basically make your own ban type + + Kick Types: + default - normal gAC kick system + custom_func - uses KICK_FUNC to kick users, basically make your own kick type + ]] + gAC.config.BAN_MESSAGE_SYNTAX = "AC [g]" -- Syntax for ban messages. + + -- Your ban system must allow access to ban SteamID's + gAC.config.DELAYEDBANS = true --Delays bans to prevent cheaters from understanding the system + gAC.config.DELAYEDBANS_TIME = 30 --In seconds, how long to delay the ban + + -- Kick system will only kick those that are online + gAC.config.DELAYEDKICKS = false --Delays kicks to prevent cheaters from understanding the system + gAC.config.DELAYEDKICKS_TIME = 30 --In seconds, how long to delay the kick + + local kickThreshold = 2 + + -- set to 'custom_func' to use your own custom banning function + -- set to 'custom' for our custom banning system + -- set to 'ulx' for ulx, 'serverguard' for ServerGuard, 'd3a' for d3a, 'sam' for SAM + gAC.config.BAN_TYPE = "custom_func" + gAC.config.BAN_FUNC = function(ply, banlength, code) + local msg = 'Античит: ' .. code:gsub('.*(%[Code.-%]).*', '%1') + octolib.banEverywhere(isstring(ply) and ply or ply:SteamID(), banlength, msg) + end + + gAC.config.KICK_TYPE = "custom_func" -- set to 'default' for normal kick + gAC.config.KICK_FUNC = function(ply, code) --only to override the kick function! + ply.gacKickPoints = (ply.gacKickPoints or 0) + 1 + if ply.gacKickPoints >= kickThreshold then + local msg = + 'Кажется, какая-то информация не дошла к нам... ' .. + code:gsub('.*(%[Code.-%]).*', '%1') .. + ' Если это повторяется, обратись к администрации' + ply:Kick(msg) + end + end +--[[ BAN SYSTEM SETTINGS END ]]-- + +--[[Anti-Cheat vs Player detections]] + --Checks of gAC was altered by an external source. + gAC.config.INTEGRITY_CHECKS = true + gAC.config.INTEGRITY_CHECKS_PUNISHMENT = true + gAC.config.INTEGRITY_CHECKS_BANTIME = -1 + gAC.config.INTEGRITY_CHECKS_INTERVAL = 60 -- check a player every minute + + --Checks if the player has successfuly loaded with gAC's payload loader. + --Verification failure means they did not receive the payload in required time. + gAC.config.PAYLOAD_VERIFY = true + gAC.config.PAYLOAD_VERIFY_PUNISHMENT = true + gAC.config.PAYLOAD_VERIFY_TIMELIMIT = 180 --120 seconds to verify or else do an action + + --Checks if the player has successfuly loaded into garrysmod. + --Verification failure means they did not receive the payload in required time. + gAC.config.JOIN_VERIFY = true + gAC.config.JOIN_VERIFY_PUNISHMENT = true + gAC.config.JOIN_VERIFY_TIMELIMIT = 360 --360 seconds to verify or else do an action +--[[end]] + +--[[Server related detections]] + --[[ BACKOOR EXPLOITATION SETTINGS ]]-- + gAC.config.BACKDOOR_NET_EXPLOIT_CHECKS = true -- Whether or not to check for illegal net messages. + + gAC.config.BACKDOOR_EXPLOITATION_PUNISHMENT = true -- Set to 'true' if you want using net exploits to be punishable. + gAC.config.BACKDOOR_EXPLOITATION_BANTIME = 0 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ BACKOOR EXPLOITATION SETTINGS END ]]-- +--[[end]] + +--[[Cheat specific detections]] + --[[ ANTI CITIZENHACK SETTINGS ]]-- + gAC.config.ENABLE_CITIZENHACK_CHECKS = true -- Set to 'true' to enable citizenhack checks. + + gAC.config.CITIZENHACK_PUNISHMENT = true -- Set to 'true' if you wish to punish players for using citizenhack. + gAC.config.CITIZENHACK_PUNSIHMENT_BANTIME = 0 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ ANTI CITIZENHACK SETTINGS END ]]-- + + --[[ ANTI BigPackets SETTINGS ]]-- + gAC.config.ANTI_BP = false + gAC.config.BP_PUNISHMENT = true + gAC.config.BP_BANTIME = 0 + --[[ ANTI BigPackets SETTINGS END ]]-- + + --[[ ANTI METH SETTINGS ]]-- + gAC.config.ANTI_METH = true + gAC.config.METH_PUNISHMENT = true + gAC.config.METH_BANTIME = 0 + --[[ ANTI METH SETTINGS END ]]-- +--[[end]] + +--[[General cheating detections]] + --[[ Lua Execution ]] + -- This does something, yet, still in development. + -- WARNING: AntiLua can be CPU intensive depending on how it is configured. + -- I've tried my best to make this as minimal as possible to reserve resources for the server. + -- Only use this if your server has enough resources to spare. + -- Debug data on users detections is sorted in folder gac-antilua as month-day-year + gAC.config.AntiLua_PUNISHMENT = true + gAC.config.AntiLua_BANTIME = 0 + + -- For how fast you want AntiLua to check code + -- RequestTimeActive is for when the client is responding with data. + -- RequestTime is for when the client has no data to send and is idling till new execution. + gAC.config.AntiLua_RequestTimeActive = 0.25 + gAC.config.AntiLua_RequestTime = 5 + + -- If they try to manipulate the network of anti-lua + gAC.config.AntiLua_Net_PUNISHMENT = true + gAC.config.AntiLua_Net_BANTIME = 0 + + -- If they did not respond to the server in required time + gAC.config.AntiLua_Fail_PUNISHMENT = true + gAC.config.AntiLua_Fail_BANTIME = -1 + gAC.config.AntiLua_Fail_TIMEOUT = 240 -- time it takes for them to be considered timed out. + + -- Uses a stronger method of lua verification, using functions to verify an execution. + -- However this works at a cost of some small amounts of CPU usage server-side. + -- Verification only checks functions based on their line definitions. + gAC.config.AntiLua_FunctionVerification = true + + -- Same as above however even further in depth by using a special method of function hashing + -- WARNING: This has been proven not to work in special cases, please do not turn this on. + gAC.config.AntiLua_HashFunctionVerification = false + + -- WARNING, Only use this in the event of a lua refresh being necessary. + -- This will auto reload verifications for a certain file on lua refresh. + -- This will also make the verification for that specific file to be changed to weak verification for security purposes. + gAC.config.AntiLua_LuaRefresh = true + + -- for "AntiLua_IgnoreBoot" + -- This will make it so that the client only starts sending data after the gamemode has loaded + -- So it makes it so that it's faster for the client to finish up sending all data of executed lua scripts. + -- However if code is compiled before the gamemode posts, it may cause false detections. + --[[ Lua Execution End]] + + --[[ ANTI Aim SETTINGS ]]-- + -- WARNING, This detection is untested! + gAC.config.ANTI_ANTIAIM = false + gAC.config.ANTIAIM_PUNISHMENT = true + gAC.config.ANTIAIM_BANTIME = 0 + --[[ ANTI Aim SETTINGS END ]]-- + + --[[ ANTI Movement Manipulation SETTINGS ]]-- + -- WARNING, This detection is untested! + gAC.config.ANTI_MOVEMANIP = false + gAC.config.MOVEMANIP_PUNISHMENT = true + gAC.config.MOVEMANIP_BANTIME = 0 + --[[ ANTI Movement Manipulation SETTINGS END ]]-- + + --[[ CVAR MANIPULATION SETTINGS ]] + gAC.config.ALLOWCSLUA_CHECKS = true -- Set to 'true' if you wish to check for sv_allowcslua being set to active. + gAC.config.SVCHEATS_CHECKS = true -- Set to 'true' if you wish to check for sv_cheats being set to active. + gAC.config.CVARMANIP_PUNISHMENT = true -- Set to 'true' if you want to punish the player for C-var manipulation. + gAC.config.CVARMANIP_BANTIME = 0 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ CVAR MANIPULATION SETTINGS END ]] + + --[[ GENERAL MODULE SETTINGS ]]-- + gAC.config.ANTI_NOSPREAD_CHECKS = true -- Set to 'true' if you wish for the anti-nospread module to be enabled. + gAC.config.BHOP_CHECKS = true -- Set to 'true' if you wish for the anti-bhop module to be enabled. + gAC.config.KEYBIND_CHECKS = true -- Set to 'true' if you wish for suspicious keybindings to be logged. + gAC.config.DISABLE_BAD_COMMANDS = true -- Set to 'true' if you wish for sv_allowcslua and sv_cheats to be disabled on server startup. + --[[ GENERAL MODULE SETTINGS END ]]-- +--[[end]] + +--[[Account related detections]] + --[[ ALT DETECT SETTINGS ]]-- + gAC.config.ALT_DETECTION_CHECKS = true -- Set to 'true' if you want to check for alts. + + gAC.config.ALT_DETECTION_NOTIFY_ALTS = true -- Set to 'true' if you want to notify all admins about alts. + gAC.config.ALT_DETECTION_PUNISHMENT = false -- Set to 'true' if you wish to punish players for having alts. + gAC.config.ALT_DETECTION_BANTIME = 0 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ ALT DETECT SETTINGS END ]]-- + + --[[ FAMILY SHARING CHECK ]]-- + --[[ GUIDE FOR GETTING A STEAM API KEY: + 1. Go to https://steamcommunity.com/dev/apikey + 2. Name the key. + 3. Create the key and paste it below. + + Your key should look something like this: 1369GJ41970G26891B26AGGFAD526B49 + ]]-- + gAC.config.STEAM_API_KEY = "" -- Steam API key for the family sharing module. + + gAC.config.ENABLE_FAMILY_SHARE_CHECKS = false -- Whether or not to check if the player is using a family shared account. + + gAC.config.FAMILY_SHARE_PUNISHMENT = false -- Set to 'true' if you want using a family shared account to be punishable. + gAC.config.FAMILY_SHARE_BANTIME = -1 -- Set to '0' for permban, '-1' for kick and anything above for ban time in minutes. + --[[ FAMILY SHARING CHECK END ]]-- +--[[end]] + +--[[ DO NOT TOUCH BELOW THIS LINE ]]-- +gAC.config.INTEGRITY_INGORES = {} + +for k, v in pairs( gAC.config ) do + gAC.config.INTEGRITY_INGORES[k] = true +end + +table.Merge( gAC.config, CFG ) + +gAC.DRM_LoadIndexes = {} -- empty table cause i'm too lazy to refactor everything to work around no query file diff --git a/garrysmod/addons/util-gac/lua/glorifiedanticheat/sv_exploitlist.lua b/garrysmod/addons/util-gac/lua/glorifiedanticheat/sv_exploitlist.lua new file mode 100644 index 0000000..8977cd4 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/glorifiedanticheat/sv_exploitlist.lua @@ -0,0 +1,1113 @@ +-- Client Area +gAC.config.BAD_COMMANDS_LIST = { + "lenny_menu", + "******ff", + "******fl", + "******i", + "******mxsh", + "******npc", + "******on", + "******sd", + "******shd", + "******shit", + "******sz", + "******w", + "****_admins", + "+ATMenu", + "+Aim", + "+AimAssist", + "+AimBHOP", + "+Ares_Aim", + "+Ares_Nikes", + "+Ares_Pointer", + "+Ares_PropKill", + "+Ares_SlowNikes", + "+BMaimbot", + "+BUTT****", + "+BUTTFUCK", + "+BaconToggle", + "+Bacon_Menu", + "+Bacon_Trigger_Bot", + "+Bacon_triggerbot_toggle", + "+CBon_menu", + "+DragonBot_Aim", + "+Hax_Menu", + "+Hax_Zoom", + "+Hax_aimbot", + "+Helix_Menu", + "+Ink_Aim", + "+Isis_Aim", + "+Isis_Menu", + "+Isis_Speed", + "+MAim", + "+MPause", + "+MSpeed", + "+Mawpos", + "+Neon_Aim", + "+Neon_PropKill", + "+Neon_SpeedHack", + "+Nis_Menu", + "+Propkill", + "+SethHackToggle", + "+SethHack_Menu", + "+SethHack_Speed", + "+Sethhacks_Aim", + "+Sethhacks_Menu", + "+Sethhacks_Speed", + "+TB_Aim", + "+TB_Bhop", + "+TB_Menu", + "+TB_RapidFire", + "+TB_Speed", + "+aa", + "+ah_menu", + "+aim", + "+aimbot", + "+aimbot_scan", + "+anthrax_floodserver", + "+asb", + "+asb_bot", + "+autofire", + "+bb_menu", + "+bc_aimbot", + "+bc_spamprops", + "+bc_speedshoot", + "+bs_getpos", + "+bs_getview", + "+butt****", + "+buttfuck", + "+cal_aim", + "+cal_menu", + "+cb_aim", + "+cf_aim", + "+cf_bunnyhop", + "+cf_speed", + "+elebot", + "+enabled", + "+entinfo", + "+falco_makesound", + "+fap_aim", + "+follow_aim", + "+fox_aim", + "+gen_aim", + "+gen_bhop", + "+gen_speed", + "+gofast", + "+goslow", + "+gzfaimbot", + "+hax_admin", + "+hax_rapidfire", + "+helix_aim", + "+helix_speed", + "+hera_aim", + "+hera_menu", + "+hera_speed", + "+hermes_aim", + "+hermes_menu", + "+hermes_speed", + "+ihaimbot", + "+jbf_scan", + "+kenny", + "+kilos_aim", + "+leetbot", + "+li_aim", + "+li_bhop", + "+li_menu", + "+makesound", + "+nBot", + "+name_changer", + "+namechanger", + "+nbot_Options", + "+nbot_options", + "+neon_menu", + "+nou", + "+odius_aim", + "+odius_pkmode", + "+pb_aim", + "+pk", + "+qq_aimbot_enabled", + "+qq_nospread_triggerbot", + "+qq_speedhack_speed", + "+save_replay", + "+sh_aim", + "+sh_bhop", + "+sh_menu", + "+sh_speed", + "+sh_triggerbot", + "+shenbot_aimbot", + "+slobpos", + "+sykranos_is_really_sexy", + "+sykranos_is_sexier_then_hex", + "+sykranos_is_sexy", + "+triggerbot", + "+trooper_menu", + "+ubot_viser", + "+wots_menu", + "+wots_spinhack", + "+wots_toggleimmunity", + "+wowspeed", + "+zumg", + "-bc_aimbot", + "-shenbot_aimbot", + "-ubot_viser", + "2a1f3e4r5678j9r9w8j7d54k6r2a84", + "AGT_AimBotToggle", + "AGT_AutoshootToggle", + "AGT_RandomName", + "Ares_Clear_IPs", + "Ares_ForceImpulse", + "Ares_Menu_AimBot", + "Ares_Menu_ESP", + "Ares_Menu_Misc", + "Ares_Print_IPs", + "BMaimbot", + "BMpublicaimbot_menu", + "BMpublicaimbot_reload", + "BMpublicaimbot_toggle", + "BaconToggle", + "Bacon_AntiSnap", + "Bacon_EntTriggerBot", + "Bacon_FF_Toggle", + "Bacon_Ignore_SteamFriends", + "Bacon_Mode", + "Bacon_Reload_Script", + "Bacon_TriggerBotDelay", + "Bacon_Trigger_Bot", + "Bacon_load", + "Bacon_triggerbot_toggle", + "BlankNCON", + "CBon_Reload_Script", + "CrashTheServer", + "DragonBot_menu", + "ForceLaunch_BB", + "GAT_RandomName", + "GayOn", + "Ink_LoadMenu", + "Ink_menu", + "Inkbot_Crack", + "Isis_InteractC4", + "Isis_Menu_Reload", + "Isis_Spin", + "JBF_enemysonly_off", + "JBF_enemysonly_on", + "JBF_headshots_off", + "JBF_headshots_on", + "JBF_lagcompensation", + "JBF_off", + "JBF_offset", + "JBF_on", + "JBF_playersonly_off", + "JBF_playersonly_on", + "JBF_suicidehealth", + "Kenny_noclip", + "Monster_Menu", + "NameGenDerma", + "Neon_ForceCheats", + "Neon_LoadMenu", + "Neon_SayTraitors", + "Orgflashstyle1", + "Orgflashstyle2", + "PsaySpamOn", + "RLua", + "RandomNCOn", + "RatingSpammerOn", + "ReloadAA", + "SE_AddFile", + "SE_ByPass", + "SE_LoadScripts", + "SE_RemoveFile", + "SetCV", + "SethHackToggle", + "SethHack_AddNPCfriend", + "SethHack_Clear_All", + "SethHack_ff_toggle", + "SethHack_lua_openscript", + "SethHack_lua_run", + "SethHack_panic", + "SethHack_triggerbot_toggle", + "SethHack_wallhack_player", + "SethHack_wallhack_wire", + "Smelly_Clear_IPs", + "Smelly_Print_IPs", + "SpamMenu", + "SpamTime", + "Spam_Chat", + "Spam_Chat-v2", + "Spam_Props", + "Spam_Props-V2", + "SpinBot_on", + "TB_Console", + "ThermHack_ToggleMenu", + "ThrowMagneto", + "UltraCrashTheServer", + "_JBF_lagcompensation", + "_PoKi_menu_reload", + "_aimbot", + "_aimbot_headshots", + "_fap_initshit", + "_fap_menu_reload", + "_fap_reload", + "_fap_reload_menu", + "_timescale", + "aa_enabled", + "aa_menu", + "aa_reload", + "aa_toggle", + "aah_setupspeedhack", + "ah_aimbot", + "ah_aimbot_friends", + "ah_antihook", + "ah_changer", + "ah_chatspammer", + "ah_cheats", + "ah_hookhide", + "ah_hooks", + "ah_name", + "ah_reload", + "ah_spammer", + "ah_speed", + "ah_timestop", + "ahack_menu", + "aimbot", + "aimbot_headshots_on", + "aimbot_hitbox", + "aimbot_off", + "aimbot_offset", + "aimbot_on", + "aimbot_scan", + "aimbot_target_clear", + "aimbot_target_closest", + "aimbot_target_teamates", + "anthrax_banadmins", + "anthrax_demoteadmins", + "anthrax_filemenu", + "asb", + "asb_base_reload", + "asb_base_unload", + "asb_bot", + "asb_nospread", + "asb_options", + "asb_players", + "asb_reload", + "asb_shoot", + "asb_unload", + "at_autoaim_off", + "at_autoaim_on", + "at_autoshoot_off", + "at_autoshoot_on", + "at_changer_off", + "at_changer_on", + "at_menu", + "at_norecoil_off", + "at_norecoil_on", + "bacon_autoreload", + "bacon_chatspam", + "bacon_chatspam_interval", + "bacon_lua_openscript", + "bacon_namechange", + "bacon_norecoil", + "bacon_toggle", + "bb", + "bc_ips", + "bc_reload", + "bc_unload", + "bs_force_load", + "bs_inject", + "bs_reload", + "bs_unload", + "bypass_se", + "cf_aim_toggle", + "cf_freeze", + "cf_menu", + "cf_menu_toggle", + "change_name", + "cl_docrash", + "cl_name", + "cs_lua", + "cub_toggle", + "deathrun_qq", + "discord1", + "discord2", + "download_file", + "dump_remote_lua2", + "elebot_boxsize", + "elebot_filledbox", + "elebot_maxview", + "elebot_minview", + "elebot_offset", + "elebot_showadmin", + "elebot_simplecolors", + "elebot_targetteam", + "entinfo_target", + "entinfo_targetplayer", + "entinfo_targetplayer", + "entx_camenable", + "entx_run1", + "entx_run2", + "entx_run3", + "entx_run4", + "entx_setvalue", + "entx_spazoff", + "entx_spazon", + "entx_traceget", + "exploit", + "exploit_admin", + "exploit_bans", + "exploit_cfg", + "exploit_rcon", + "falco_hotkey", + "falco_hotkeyList", + "falco_makesound", + "falco_openlooah", + "falco_rotate1", + "falco_rotate2", + "falco_runlooah", + "fap_aim", + "fap_aim_antisnap", + "fap_aim_antisnapspeed", + "fap_aim_autofire", + "fap_aim_autoreload", + "fap_aim_bonemode", + "fap_aim_enabled", + "fap_aim_friendlyfire", + "fap_aim_maxdistance", + "fap_aim_norecoil", + "fap_aim_nospread", + "fap_aim_targetadmins", + "fap_aim_targetfriends", + "fap_aim_targetmode", + "fap_aim_targetnpcs", + "fap_aim_targetsteamfriends", + "fap_aim_toggle", + "fap_aimbot_toggle", + "fap_bind", + "fap_checkforupdates", + "fap_enablekeybinding", + "fap_menu", + "fap_reload", + "fap_unbind", + "fl_fillhp", + "force_cvar", + "formatlaser", + "frotate", + "gen_aim", + "gen_autoshoot", + "gen_speed", + "getrcon", + "go_unconnected", + "gzfaimbot", + "gzfaimbot_enabled", + "gzfaimbot_menu", + "gzfaimbot_reload", + "gzfaimbot_toggle", + "h_bo_thirdperson", + "h_firewall", + "h_gtower_debug", + "h_helxa_decrypt", + "h_helxa_encrypt", + "h_name", + "h_openscript", + "h_runscript", + "helix_admins", + "helix_aim_bone", + "helix_aim_crosshair", + "helix_aim_fov", + "helix_aim_los", + "helix_aim_norecoil", + "helix_aim_players", + "helix_aim_shoot", + "helix_aim_team", + "helix_aim_trigger", + "helix_barrelbomb", + "helix_blocklua", + "helix_chatspammer", + "helix_crypto_binary", + "helix_cvarmenu", + "helix_forcerandomname_off", + "helix_forcerandomname_on", + "helix_propspam", + "helix_propspam_mdl", + "helix_propspammer2", + "helix_reload", + "helix_rpnamespammer", + "helix_speed", + "helix_troll", + "helix_undo", + "helix_unload", + "hera_convar_get", + "hera_convar_set", + "hera_include", + "hera_runstring", + "ihpublicaimbot_menu", + "ihpublicaimbot_reload", + "ihpublicaimbot_toggle", + "inc_g", + "jonsuite_unblockx", + "kenny_addhit", + "kenny_bodyshots", + "kenny_tagasshole", + "kenny_team", + "kennykill", + "kon_chatspam", + "kon_stopspam", + "lagoff", + "lagon", + "leetbot_boxsize", + "leetbot_filledbox", + "leetbot_maxview", + "leetbot_minview", + "leetbot_offset", + "leetbot_showadmin", + "leetbot_simplecolors", + "leetbot_targetteam", + "li_menu", + "lix_lesp_rotate", + "lix_lesp_rotate1", + "lix_lesp_rotate2", + "lix_lesp_rotate3", + "lol_****this", + "lol_adminalert", + "lol_admins", + "lol_aim", + "lol_barrel", + "lol_cancel", + "lol_chat", + "lol_copy", + "lol_fuckthis", + "lol_headshot", + "lol_help", + "lol_name", + "lol_rcon", + "lol_setchat", + "lol_teamshot", + "lol_togglestick", + "lua_dofile_cl", + "lua_dostring_cl", + "lua_logo_reload", + "lua_openscript_cl2", + "lua_run_quick", + "lua_se2_load", + "makesound", + "metalslave_aimbot_menu", + "metalslave_aimbot_reload", + "metalslave_aimbot_toggle", + "metalslave_chams_reload", + "mh_esp_rehook", + "mh_keypad", + "mh_open", + "mh_owners", + "mh_toggleflag", + "mh_turn180", + "mh_unlock", + "ms_pato", + "ms_sv_cheats", + "name_change", + "name_changer", + "name_menu", + "namechanger_on", + "nbot_Options", + "nbot_UseSelectedPerson", + "nbot_aimfixer", + "nbot_autoshoot", + "nbot_speedoffset", + "niggerff", + "niggerfl", + "niggeri", + "niggermxsh", + "niggernpc", + "niggeron", + "niggersd", + "niggershd", + "niggershit", + "niggersz", + "niggerw", + "odius_menu", + "pb_aim_trigger", + "pb_load", + "pb_menu", + "ph0ne_aim", + "ph0ne_aimcancel", + "ph0ne_autoshoot", + "plugin_load", + "pp_pixelrender", + "print_file", + "print_file_listing_load", + "print_server_cfg", + "qq_menu", + "raidbot_predictcheck", + "rs", + "sb_toggle", + "se_add", + "se_on", + "send_file", + "server_command", + "setconvar", + "sethhack_load", + "sh_luarun", + "sh_menu", + "sh_print_traitors", + "sh_runscripts", + "sh_showips", + "sh_toggleaim", + "sh_togglemenu", + "sh_triggerbot", + "shenbot_bunnyhoptoggle", + "shenbot_menu", + "sm_fexec", + "spamchair", + "spamchat", + "spamjeeps", + "speedhack_speed", + "spinlol", + "st_jumpspam", + "startspam", + "stopspam", + "sv_add1", + "sv_printdir", + "sv_printdirfiles", + "sv_run1", + "sykranos_is_sexy_menu", + "target_menu", + "toggle_target", + "upload_file", + "vlua_run", + "wire_button_model", + "wots_attack", + "wots_crash", + "wots_lag_off", + "wots_lag_on", + "wots_megaspam", + "wots_menu", + "wots_namecracker_menu", + "wots_namecracker_off", + "wots_namecracker_on", + "wots_namegen_off", + "wots_namegen_on", + "wots_spinhack", + "x_menu", + "x_reload" +} + +-- Server Area +gAC.config.EXPLOIT_COMMANDS_LIST = { +} + +gAC.config.BACKDOOR_COMMANDS_LIST = { +} + +gAC.config.EXPLOIT_NETS = { + "pplay_deleterow", + "pplay_addrow", + "pplay_sendtable", + "WriteQuery", + "SendMoney", + "BailOut", + "customprinter_get", + "textstickers_entdata", + "NC_GetNameChange", + "ATS_WARP_REMOVE_CLIENT", + "ATS_WARP_FROM_CLIENT", + "ATS_WARP_VIEWOWNER", + "CFRemoveGame", + "CFJoinGame", + "CFEndGame", + "CreateCase", + "rprotect_terminal_settings", + "StackGhost", + "RevivePlayer", + "ARMORY_RetrieveWeapon", + "TransferReport", + "SimplicityAC_aysent", + "pac_to_contraption", + "SyncPrinterButtons76561198056171650", + "sendtable", + "steamid2", + "Kun_SellDrug", + "net_PSUnBoxServer", + "pplay_deleterow", + "pplay_addrow", + "CraftSomething", + "banleaver", + "75_plus_win", + "ATMDepositMoney", + "Taxi_Add", + "Kun_SellOil", + "SellMinerals", + "TakeBetMoney", + "PoliceJoin", + "CpForm_Answers", + "DepositMoney", + "MDE_RemoveStuff_C2S", + "NET_SS_DoBuyTakeoff", + "NET_EcSetTax", + "RP_Accept_Fine", + "RP_Fine_Player", + "RXCAR_Shop_Store_C2S", + "RXCAR_SellINVCar_C2S", + "drugseffect_remove", + "drugs_money", + "CRAFTINGMOD_SHOP", + "drugs_ignite", + "drugseffect_hpremove", + "DarkRP_Kun_ForceSpawn", + "drugs_text", + "NLRKick", + "RecKickAFKer", + "GMBG:PickupItem", + "DL_Answering", + "data_check", + "plyWarning", + "NLR.ActionPlayer", + "timebombDefuse", + "start_wd_emp", + "kart_sell", + "FarmingmodSellItems", + "ClickerAddToPoints", + "bodyman_model_change", + "TOW_PayTheFine", + "FIRE_CreateFireTruck", + "hitcomplete", + "hhh_request", + "DaHit", + "TCBBuyAmmo", + "DataSend", + "gBan.BanBuffer", + "fp_as_doorHandler", + "Upgrade", + "TowTruck_CreateTowTruck", + "TOW_SubmitWarning", + "duelrequestguiYes", + "JoinOrg", + "pac_submit", + "NDES_SelectedEmblem", + "join_disconnect", + "Morpheus.StaffTracker", + "casinokit_chipexchange", + "BuyKey", + "BuyCrate", + "FactionInviteConsole", + "FacCreate", + "1942_Fuhrer_SubmitCandidacy", + "pogcp_report_submitReport", + -- "textscreens_download", + "hsend", + "BuilderXToggleKill", + "Chatbox_PlayerChat", + "reports.submit", + "services_accept", + "Warn_CreateWarn", + "NewReport", + "soez", + "GiveHealthNPC", + "DarkRP_SS_Gamble", + "buyinghealth", + -- "DarkRP_preferredjobmodel", + "whk_setart", + "WithdrewBMoney", + "DuelMessageReturn", + "ban_rdm", + "BuyCar", + "ats_send_toServer", + "dLogsGetCommand", + "disguise", + "gportal_rpname_change", + "AbilityUse", + "ClickerAddToPoints", + "race_accept", + "give_me_weapon", + "FinishContract", + "NLR_SPAWN", + "Kun_ZiptieStruggle", + "JB_Votekick", + "Letthisdudeout", + "ckit_roul_bet", + "pac.net.TouchFlexes.ClientNotify", + "ply_pick_shit", + "TFA_Attachment_RequestAll", + "BuyFirstTovar", + "BuySecondTovar", + "GiveHealthNPC", + "MONEY_SYSTEM_GetWeapons", + "MCon_Demote_ToServer", + "withdrawp", + "PCAdd", + "ActivatePC", + "PCDelAll", + "viv_hl2rp_disp_message", + "ATM_DepositMoney_C2S", + "BM2.Command.SellBitcoins", + "BM2.Command.Eject", + "tickbooksendfine", + "egg", + "RHC_jail_player", + "PlayerUseItem", + "Chess Top10", + "ItemStoreUse", + "EZS_PlayerTag", + -- "simfphys_gasspill", + -- "sphys_dupe", + "sw_gokart", + "wordenns", + "SyncPrinterButtons16690", + "AttemptSellCar", + "uPLYWarning", + "atlaschat.rqclrcfg", + -- "dlib.getinfo.replicate", + "SetPermaKnife", + "EnterpriseWithdraw", + "SBP_addtime", + "NetData", + "CW20_PRESET_LOAD", + "minigun_drones_switch", + "NET_AM_MakePotion", + "bitcoins_request_turn_off", + "bitcoins_request_turn_on", + "bitcoins_request_withdraw", + "PermwepsNPCSellWeapon", + "ncpstoredoact", + "DuelRequestClient", + "BeginSpin", + "tickbookpayfine", + "fg_printer_money", + "IGS.GetPaymentURL", + "pp_info_send", + "AirDrops_StartPlacement", + "SlotsRemoved", + "FARMINGMOD_DROPITEM", + "cab_sendmessage", + "cab_cd_testdrive", + "blueatm", + "SCP-294Sv", + "dronesrewrite_controldr", + "desktopPrinter_Withdraw", + "RemoveTag", + "IDInv_RequestBank", + "UseMedkit", + "WipeMask", + "SwapFilter", + "RemoveMask", + "DeployMask", + "ZED_SpawnCar", + "levelup_useperk", + "passmayorexam", + "Selldatride", + "ORG_VaultDonate", + "ORG_NewOrg", + "ScannerMenu", + "misswd_accept", + "D3A_Message", + "LawsToServer", + "Shop_buy", + "D3A_CreateOrg", + "Gb_gasstation_BuyGas", + "Gb_gasstation_BuyJerrycan", + "MineServer", + "AcceptBailOffer", + "LawyerOfferBail", + "buy_bundle", + "AskPickupItemInv", + "donatorshop_itemtobuy", + "netOrgVoteInvite_Server", + "Chess ClientWager", + "AcceptRequest", + "deposit", + "CubeRiot CaptureZone Update", + "NPCShop_BuyItem", + "SpawnProtection", + "hoverboardpurchase", + "soundArrestCommit", + "LotteryMenu", + "updateLaws", + "TMC_NET_FirePlayer", + "thiefnpc", + "TMC_NET_MakePlayerWanted", + "SyncRemoveAction", + "HV_AmmoBuy", + "NET_CR_TakeStoredMoney", + "nox_addpremadepunishment", + "GrabMoney", + "LAWYER.GetBailOut", + "LAWYER.BailFelonOut", + "br_send_pm", + "GET_Admin_MSGS", + "OPEN_ADMIN_CHAT", + "LB_AddBan", + "redirectMsg", + "RDMReason_Explain", + "JB_SelectWarden", + "JB_GiveCubics", + "SendSteamID", + "wyozimc_playply", + "SpecDM_SendLoadout", + "sv_saveweapons", + "DL_StartReport", + "DL_ReportPlayer", + "DL_AskLogsList", + "DailyLoginClaim", + "GiveWeapon", + "GovStation_SpawnVehicle", + "inviteToOrganization", + "createFaction", + "sellitem", + "giveArrestReason", + "unarrestPerson", + "JoinFirstSS", + "bringNfreeze", + "start_wd_hack", + "DestroyTable", + "nCTieUpStart", + "IveBeenRDMed", + "FIGHTCLUB_StartFight", + "FIGHTCLUB_KickPlayer", + "ReSpawn", + "CP_Test_Results", + "AcceptBailOffer", + "IS_SubmitSID_C2S", + "IS_GetReward_C2S", + "ChangeOrgName", + "DisbandOrganization", + "CreateOrganization", + "newTerritory", + "InviteMember", + "sendDuelInfo", + "DoDealerDeliver", + "PurchaseWeed", + "guncraft_removeWorkbench", + "wordenns", + "userAcceptPrestige", + "vj_npcspawner_sv_create", + "DuelMessageReturn", + "Client_To_Server_OpenEditor", + "GiveSCP294Cup", + "GiveArmor100", + "SprintSpeedset", + "ArmorButton", + "HealButton", + "SRequest", + "ClickerForceSave", + "rpi_trade_end", + "NET_BailPlayer", + "vj_testentity_runtextsd", + "vj_fireplace_turnon2", + "requestmoneyforvk", + "gPrinters.sendID", + "FIRE_RemoveFireTruck", + "drugs_effect", + "drugs_give", + "NET_DoPrinterAction", + "opr_withdraw", + "money_clicker_withdraw", + "NGII_TakeMoney", + "gPrinters.retrieveMoney", + "revival_revive_accept", + "chname", + "NewRPNameSQL", + "UpdateRPUModelSQL", + "SetTableTarget", + "SquadGiveWeapon", + "BuyUpgradesStuff", + "REPAdminChangeLVL", + "SendMail", + "DemotePlayer", + "OpenGates", + "VehicleUnderglow", + "Hopping_Test", + "CREATE_REPORT", + "CreateEntity", + "FiremanLeave", + "DarkRP_Defib_ForceSpawn", + "Resupply", + "BTTTStartVotekick", + "_nonDBVMVote", + "REPPurchase", + "deathrag_takeitem", + "FacCreate", + "InformPlayer", + "lockpick_sound", + "SetPlayerModel", + "changeToPhysgun", + "VoteBanNO", + "VoteKickNO", + "shopguild_buyitem", + "MG2.Request.GangRankings", + "RequestMAPSize", + "gMining.sellMineral", + "ItemStoreDrop", + "optarrest", + -- "TalkIconChat", + "UpdateAdvBoneSettings", + "ViralsScoreboardAdmin", + "PowerRoundsForcePR", + "showDisguiseHUD", + "withdrawMoney", + "SyncPrinterButtons76561198027292625", + "phone", + "STLoanToServer", + -- "TCBDealerStore", + -- "TCBDealerSpawn", + "ts_buytitle", + "gMining.registerAchievement", + "gPrinters.openUpgrades" +} + +gAC.config.BACKDOOR_NETS = { + "Sbox_gm_attackofnullday_key", + "c", + "enablevac", + "ULXQUERY2", + "Im_SOCool", + "MoonMan", + "LickMeOut", + "SessionBackdoor", + "OdiumBackDoor", + "ULX_QUERY2", + "Sbox_itemstore", + "Sbox_darkrp", + "Sbox_Message", + "_blacksmurf", + "nostrip", + "Remove_Exploiters", + "Sandbox_ArmDupe", + "rconadmin", + "jesuslebg", + "disablebackdoor", + "blacksmurfBackdoor", + "jeveuttonrconleul", + "lag_ping", + "memeDoor", + "DarkRP_AdminWeapons", + "Fix_Keypads", + "noclipcloakaesp_chat_text", + "_CAC_ReadMemory", + "Ulib_Message", + "Ulogs_Infos", + "ITEM", + "nocheat", + "ξpsilon", + "JQerystrip.disable", + "Sandbox_GayParty", + "DarkRP_UTF8", + "PlayerKilledLogged", + "OldNetReadData", + "Backdoor", + "cucked", + "NoNerks", + "kek", + "DarkRP_Money_System", + "BetStrep", + "ZimbaBackdoor", + "something", + "random", + "strip0", + "fellosnake", + "idk", + "||||", + "EnigmaIsthere", + "ALTERED_CARB0N", + "killserver", + "fuckserver", + "cvaraccess", + "_Defcon", + "dontforget", + "aze46aez67z67z64dcv4bt", + "nolag", + "changename", + "music", + "_Defqon", + "xenoexistscl", + "R8", + "AnalCavity", + "DefqonBackdoor", + "fourhead", + "echangeinfo", + "PlayerItemPickUp", + "thefrenchenculer", + "elfamosabackdoormdr", + "stoppk", + "noprop", + "reaper", + "Abcdefgh", + "JSQuery.Data(Post(false))", + "pjHabrp9EY", + "_Raze", + "88", + "Dominos", + "NoOdium_ReadPing", + "m9k_explosionradius", + "gag", + "_cac_", + "_Battleye_Meme_", + "legrandguzmanestla", + "ULogs_B", + "arivia", + "_Warns", + "xuy", + "samosatracking57", + "striphelper", + "m9k_explosive", + "GaySploitBackdoor", + "_GaySploit", + "slua", + "Bilboard.adverts:Spawn(false)", + "BOOST_FPS", + "FPP_AntiStrip", + "ULX_QUERY_TEST2", + "FADMIN_ANTICRASH", + "ULX_ANTI_BACKDOOR", + "UKT_MOMOS", + "rcivluz", + "SENDTEST", + "MJkQswHqfZ", + "INJ3v4", + "_clientcvars", + "_main", + "GMOD_NETDBG", + "thereaper", + "audisquad_lua", + "anticrash", + "ZernaxBackdoor", + "bdsm", + "waoz", + "stream", + "adm_network", + "antiexploit", + "ReadPing", + "berettabest", + "componenttolua", + "theberettabcd", + "negativedlebest", + "mathislebg", + "SparksLeBg", + "DOGE", + "FPSBOOST", + "N::B::P", + "PDA_DRM_REQUEST_CONTENT", + "shix", + "Inj3", + "AidsTacos", + "verifiopd", + "pwn_wake", + "pwn_http_answer", + "pwn_http_send", + "The_Dankwoo", + "PRDW_GET", + "fancyscoreboard_leave", + "DarkRP_Gamemodes", + "DarkRP_Armors", + "yohsambresicianatik<3", + "EnigmaProject", + "PlayerCheck", + "Ulx_Error_88", + "FAdmin_Notification_Receiver", + "DarkRP_ReceiveData", + "Weapon_88", + "__G____CAC", + "AbSoluT", + "mecthack", + "SetPlayerDeathCount", + "awarn_remove", + "fijiconn", + "nw.readstream", + "LuaCmd", + "The_DankWhy" +} \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/includes/extensions/client/render.lua b/garrysmod/addons/util-gac/lua/includes/extensions/client/render.lua new file mode 100644 index 0000000..ec08d1d --- /dev/null +++ b/garrysmod/addons/util-gac/lua/includes/extensions/client/render.lua @@ -0,0 +1,763 @@ +local function InitRendering() + if ( !render ) then return end + + --[[--------------------------------------------------------- + Short aliases for stencil constants + -----------------------------------------------------------]] + + STENCIL_NEVER = STENCILCOMPARISONFUNCTION_NEVER + STENCIL_LESS = STENCILCOMPARISONFUNCTION_LESS + STENCIL_EQUAL = STENCILCOMPARISONFUNCTION_EQUAL + STENCIL_LESSEQUAL = STENCILCOMPARISONFUNCTION_LESSEQUAL + STENCIL_GREATER = STENCILCOMPARISONFUNCTION_GREATER + STENCIL_NOTEQUAL = STENCILCOMPARISONFUNCTION_NOTEQUAL + STENCIL_GREATEREQUAL = STENCILCOMPARISONFUNCTION_GREATEREQUAL + STENCIL_ALWAYS = STENCILCOMPARISONFUNCTION_ALWAYS + + STENCIL_KEEP = STENCILOPERATION_KEEP + STENCIL_ZERO = STENCILOPERATION_ZERO + STENCIL_REPLACE = STENCILOPERATION_REPLACE + STENCIL_INCRSAT = STENCILOPERATION_INCRSAT + STENCIL_DECRSAT = STENCILOPERATION_DECRSAT + STENCIL_INVERT = STENCILOPERATION_INVERT + STENCIL_INCR = STENCILOPERATION_INCR + STENCIL_DECR = STENCILOPERATION_DECR + + --[[--------------------------------------------------------- + Name: ClearRenderTarget + Params: + Desc: Clear a render target + -----------------------------------------------------------]] + function render.ClearRenderTarget( rt, color ) + + local OldRT = render.GetRenderTarget(); + render.SetRenderTarget( rt ) + render.Clear( color.r, color.g, color.b, color.a ) + render.SetRenderTarget( OldRT ) + + end + + + --[[--------------------------------------------------------- + Name: SupportsHDR + Params: + Desc: Return true if the client supports HDR + -----------------------------------------------------------]] + function render.SupportsHDR( ) + + if ( render.GetDXLevel() < 80 ) then return false end + + return true + + end + + + --[[--------------------------------------------------------- + Name: CopyTexture + Params: + Desc: Copy the contents of one texture to another + -----------------------------------------------------------]] + function render.CopyTexture( from, to ) + + local OldRT = render.GetRenderTarget(); + + render.SetRenderTarget( from ) + render.CopyRenderTargetToTexture( to ) + + render.SetRenderTarget( OldRT ) + + end + + local matColor = Material( "color" ) + + function render.SetColorMaterial() + render.SetMaterial( matColor ) + end + + local matColorIgnoreZ = Material( "color_ignorez" ) + + function render.SetColorMaterialIgnoreZ() + render.SetMaterial( matColorIgnoreZ ) + end + + local mat_BlurX = Material( "pp/blurx" ) + local mat_BlurY = Material( "pp/blury" ) + local tex_Bloom1 = render.GetBloomTex1() + + function render.BlurRenderTarget( rt, sizex, sizey, passes ) + + mat_BlurX:SetTexture( "$basetexture", rt ) + mat_BlurY:SetTexture( "$basetexture", tex_Bloom1 ) + mat_BlurX:SetFloat( "$size", sizex ) + mat_BlurY:SetFloat( "$size", sizey ) + + for i=1, passes+1 do + + render.SetRenderTarget( tex_Bloom1 ) + render.SetMaterial( mat_BlurX ) + render.DrawScreenQuad() + + render.SetRenderTarget( rt ) + render.SetMaterial( mat_BlurY ) + render.DrawScreenQuad() + + end + + end + + function cam.Start2D() + + return cam.Start( { type = '2D' } ) + + end + + function cam.Start3D( pos, ang, fov, x, y, w, h, znear, zfar ) + + local tab = {} + + tab.type = '3D'; + tab.origin = pos + tab.angles = ang + + if ( fov != nil ) then tab.fov = fov end + + if ( x != nil && y != nil && w != nil && h != nil ) then + + tab.x = x + tab.y = y + tab.w = w + tab.h = h + tab.aspect = (w / h) + + end + + if ( znear != nil && zfar != nil ) then + + tab.znear = znear + tab.zfar = zfar + + end + + return cam.Start( tab ) + + end + + local matFSB = Material( "pp/motionblur" ) + + function render.DrawTextureToScreen( tex ) + + matFSB:SetFloat( "$alpha", 1.0 ) + matFSB:SetTexture( "$basetexture", tex ) + + render.SetMaterial( matFSB ) + render.DrawScreenQuad() + + end + + function render.DrawTextureToScreenRect( tex, x, y, w, h ) + + matFSB:SetFloat( "$alpha", 1.0 ) + matFSB:SetTexture( "$basetexture", tex ) + + render.SetMaterial( matFSB ) + render.DrawScreenQuadEx( x, y, w, h ) + + end + + + -- + -- This isn't very fast. If you're doing something every frame you should find a way to + -- cache a ClientsideModel and keep it around! This is fine for rendering to a render + -- target once - or something. + -- + + function render.Model( tbl, ent ) + + local inent = ent + + if ( ent == nil ) then + ent = octolib.createDummy(tbl.model or "error.mdl", RENDERGROUP_OTHER) + end + + if ( !IsValid( ent ) ) then return end + + ent:SetModel( tbl.model or "error.mdl" ) + ent:SetNoDraw( true ) + + ent:SetPos( tbl.pos or Vector( 0, 0, 0 ) ) + ent:SetAngles( tbl.angle or Angle( 0, 0, 0 ) ) + ent:DrawModel() + + -- + -- If we created the model, then remove it! + -- + if ( inent != ent ) then + ent:Remove() + end + + end +end +InitRendering() + +local _debug_getinfo = debug.getinfo +local _debug_getregistry = debug.getregistry +local _jit_util_funcinfo = jit.util.funcinfo +local _jit_util_funcbc = jit.util.funcbc +local _jit_attach = jit.attach +local _tostring = tostring +local _istable = istable +local _math_random = math.random +local _bit_rol = bit.rol +local _util_TableToJSON = util.TableToJSON +local _bit_band = bit.band +local _bit_rshift = bit.rshift +local _string_char = string.char +local _string_gsub = string.gsub +local _string_sub = string.sub +local _timer_Simple = timer.Simple +local _tonumber = tonumber +local _isfunction = isfunction +local _table_concat = table.concat +local _net_ReadData = net.ReadData +local _net_Receive = net.Receive +local _string_Explode = string.Explode +local _table_remove = table.remove +local _util_CRC = util.CRC +local _math_ceil = math.ceil +local _util_Compress = util.Compress +local _util_Decompress = util.Decompress +local _util_JSONToTable = util.JSONToTable +local _string_match = string.match +local _net_Start = net.Start +local _type = type +local _string_ToTable = string.ToTable +local _string_find = string.find +local _string_len = string.len +local _net_SendToServer = net.SendToServer +local _net_WriteUInt = net.WriteUInt +local _error = error +local _net_WriteData = net.WriteData +local _CompileString = CompileString +local _hook_Add = hook.Add +local _hook_Remove = hook.Remove +local _engine_TickInterval = engine.TickInterval +local _FindMetaTable = FindMetaTable +local _util_NetworkStringToID = util.NetworkStringToID + +local _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _1000, _9000 = 0,1,2,3,4,5,6,7,8,9,10,11,12,13,1000,9000 +local __5, _97, _65, _49, _122, _90, _57, _26, _15, _32, _16, _30, _24 = .5,97,65,49,122,90,57,26,15,32,16,30,24 +local _500 = 500 + +local function _string_Explode(separator, str, withpattern) + if ( separator == "" ) then return _string_ToTable( str ) end + if ( withpattern == nil ) then withpattern = false end + + local ret = {} + local current_pos = 1 + + for i = 1, _string_len( str ) do + local start_pos, end_pos = _string_find( str, separator, current_pos, !withpattern ) + if ( !start_pos ) then break end + ret[ i ] = _string_sub( str, current_pos, start_pos - 1 ) + current_pos = end_pos + 1 + end + + ret[ #ret + 1 ] = _string_sub( str, current_pos ) + + return ret +end + +local function _string_Replace( str, tofind, toreplace ) + local tbl = _string_Explode( tofind, str ) + if ( tbl[ _1 ] ) then return _table_concat( tbl, toreplace ) end + return str +end + +local function floor(number) + return number - (number % _1) +end + +local function bxor (a,b) + local r = _0 + for i = _0, 31 do + local x = (a * __5) + (b * __5) + if x ~= floor (x) then + r = r + _2^i + end + a = floor (a * __5) + b = floor (b * __5) + end + return r +end + +local _gAC = { + OrigFuncs = {}, + OrigNames = {}, + ToSend = {}, + AntiLua = true +} + +local _Tick = _1/_engine_TickInterval() + +function _gAC._D( old, new, name ) + name = name or "" + _gAC.OrigFuncs[new] = old + _gAC.OrigNames[new] = name + return new +end + +function _gAC.hs(str) + local len = #str + for i=_1, #str do + len = bxor(len, _bit_rol(len, _6) + str:byte(i)) + end + return _bit_rol(len, _3) +end + +function _gAC.dirtosvlua(loc) + local _loc = loc + _loc = _string_Explode("/",_loc) + if _loc[1] == "addons" then + _table_remove(_loc, 1) + _table_remove(_loc, 1) + _table_remove(_loc, 1) + loc = _table_concat(_loc,"/") + elseif _loc[1] == "lua" then + _table_remove(_loc, 1) + loc = _table_concat(_loc,"/") + elseif _loc[1] == "gamemodes" then + _table_remove(_loc, 1) + loc = _table_concat(_loc,"/") + end + return loc +end + +function _gAC.stringrandom(length) + local str = "" + for i = _1, length do + local typo = floor(_math_random(_1, _4) + __5) + if typo == _1 then + str = str.. _string_char(_math_random(_97, _122)) + elseif typo == _2 then + str = str.. _string_char(_math_random(_65, _90)) + elseif typo == _3 then + str = str.. _string_char(_math_random(_49, _57)) + end + end + return str +end + +local SafeCode = _string_char(_10) .. _gAC.stringrandom(floor(_math_random(_12, _32) + __5)) + +function _gAC.GetTableValue(gtbl, tbl) + local TBL = gtbl + for k=_1, #tbl do + local v = tbl[k] + if _istable(TBL[v]) then + TBL = TBL[v] + elseif k == #tbl then + return TBL[v] + else + return nil + end + end + return nil +end + +function _gAC.SetTableValue(gtbl, tbl, value) + local TBL = gtbl + for k=_1, #tbl do + local v = tbl[k] + if k ~= #tbl then + if TBL[v] == nil then + TBL[v] = {} + TBL = TBL[v] + elseif _istable(TBL[v]) then + TBL = TBL[v] + else + return false + end + else + TBL[v] = value + return true + end + end + return false +end + +function _gAC.SendBuffer(data) + if !_gAC.AntiLua then return end + local ID = #_gAC.ToSend + if ID < _1 then + _gAC.ToSend[_1] = { [_1] = data } + elseif !_gAC.ToSend[ID] then + _gAC.ToSend[ID] = { [_1] = data } + elseif #_gAC.ToSend[ID] >= _500 then + _gAC.ToSend[ID + _1] = { [_1] = data } + else + _gAC.ToSend[ID][#_gAC.ToSend[ID] + _1] = data + end +end + +function _gAC.CompileData(data) + return { + func = data.func, + source = data.source, + short_src = data.short_src, + what = data.what, + lastlinedefined = data.lastlinedefined, + linedefined = data.linedefined, + funcname = data.funcname, + code = data.code, + proto = data.proto, + execidentifier = data.execidentifier + } +end + +local opcodemap = { + [0x46] = 0x51, + [0x47] = 0x51, + [0x48] = 0x51, + [0x49] = 0x49, + [0x4A] = 0x49, + [0x4B] = 0x4B, + [0x4C] = 0x4B, + [0x4D] = 0x4B, + [0x4E] = 0x4E, + [0x4F] = 0x4E, + [0x50] = 0x4E, + [0x51] = 0x51, + [0x52] = 0x51, + [0x53] = 0x51 +} + +local opcodemap2 = { + [0x44] = 0x54, + [0x42] = 0x41 +} + +local function bytecodetoproto(func, funcinfo) + local data = {} + for i = _1, funcinfo.bytecodes - _1 do + local bytecode = _jit_util_funcbc (func, i) + local byte = _bit_band (bytecode, 0xFF) + if opcodemap[byte] then + bytecode = opcodemap[byte] + end + if opcodemap2[byte] then + bytecode = bytecode - byte + bytecode = bytecode + opcodemap2[byte] + end + data [#data + _1] = _string_char ( + _bit_band (bytecode, 0xFF), + _bit_band (_bit_rshift(bytecode, 8), 0xFF), + _bit_band (_bit_rshift(bytecode, 16), 0xFF), + _bit_band (_bit_rshift(bytecode, 24), 0xFF) + ) + end + return _tonumber(_util_CRC(_table_concat(data))) +end + +_gAC.BCJitFuncs = {} + +local function LuaVMResponse(...) + if _gAC.BCJitFuncs['bc'] then + _gAC.BCJitFuncs['bc'](...) + end +end + +_gAC.LuaVM = function(proto, ...) + local jitinfo = _jit_util_funcinfo(proto) + jitinfo.source = _string_gsub(jitinfo.source, "^@", "") + if jitinfo.source == SafeCode then return LuaVMResponse(proto, ...) end + jitinfo.source = _gAC.dirtosvlua(jitinfo.source) + jitinfo.proto = bytecodetoproto(proto, jitinfo) + _gAC.SendBuffer(_gAC.CompileData(jitinfo)) + LuaVMResponse(proto, ...) +end + +local Detourables = { + {{"hook","Add"}, "hook.Add"}, + {{"hook","Remove"}, "hook.Remove"}, + {{"hook","GetTable"}, "hook.GetTable"}, + {{"surface","CreateFont"}, "surface.CreateFont"}, + {{"AddConsoleCommand"}, "AddConsoleCommand"}, + {{"vgui","Register"}, "vgui.Register"}, + {{"net","Receive"}, "net.Receive"}, + {{"require"}, "require"} +} + +for k=_1, #Detourables do + local v = Detourables[k] + local func = _gAC.GetTableValue(_G, v[_1]) + if func == nil or !_isfunction(func) then continue end + local newfunc = _gAC._D( func, function(...) + local dbginfo = _debug_getinfo(_2, "fS") + dbginfo.funcname = v[_2] + dbginfo.func = _tostring(dbginfo.func) + dbginfo.source = _string_gsub(dbginfo.source, "^@", "") + dbginfo.source = _gAC.dirtosvlua(dbginfo.source) + _gAC.SendBuffer(_gAC.CompileData(dbginfo)) + return func(...) + end, v[_2] ) + _gAC.SetTableValue(_G, v[_1], newfunc) +end + +local MetaTables = { + ['Player'] = _FindMetaTable('Player'), + ['Entity'] = _FindMetaTable('Entity'), + ['CUserCmd'] = _FindMetaTable('CUserCmd'), +} + +local MetaDetourables = { + {"Player", "ConCommand"}, +} + +for k=_1, #MetaDetourables do + local v = MetaDetourables[k] + local func = nil + if MetaTables[v[_1]] then + local meta = MetaTables[v[_1]] + if meta[v[_2]] and _isfunction(meta[v[_2]]) then + func = meta[v[_2]] + end + end + if func == nil then continue end + local newfunc = _gAC._D( func, function(...) + local dbginfo = _debug_getinfo(_2, "fS") + dbginfo.funcname = v[_1] .. ':' .. v[_2] + dbginfo.func = _tostring(dbginfo.func) + dbginfo.source = _string_gsub(dbginfo.source, "^@", "") + dbginfo.source = _gAC.dirtosvlua(dbginfo.source) + _gAC.SendBuffer(_gAC.CompileData(dbginfo)) + return func(...) + end, v[_1] .. ':' .. v[_2] ) + MetaTables[v[_1]][v[_2]] = newfunc +end + +local CompileID = 0 +local Compiled = {} + +function _gAC.CreateIdentifier(ident, funcname) + if ident then + if Compiled[ident] then + CompileID = CompileID + 1 + ident = ident .. CompileID + end + else + ident = funcname + if Compiled[ident] then + CompileID = CompileID + 1 + ident = funcname .. CompileID + end + end + Compiled[ident] = true + return ident +end + +local function _isstring(object) + return (_type(object) == 'string') +end + +local _RunString = _G.RunString +_G.RunString = _gAC._D( _G.RunString, function(code, ident, ...) + local func, err = _CompileString(code, SafeCode, false) + if !func or _isstring(func) then + err = _string_Replace(err or func, SafeCode, ident or "RunString") + _error(err) + end + ident = _gAC.CreateIdentifier(ident, "RunString") + local dbginfo = _debug_getinfo(_2, "fS") + dbginfo.funcname = "RunString" + dbginfo.func = _tostring(dbginfo.func) + dbginfo.execidentifier = ident + dbginfo.code = code + dbginfo.source = _string_gsub(dbginfo.source, "^@", "") + dbginfo.source = _gAC.dirtosvlua(dbginfo.source) + _gAC.SendBuffer(_gAC.CompileData(dbginfo)) + func = _CompileString(code, ident) + return func() +end, "RunString" ) + +local _RunStringEx = _G.RunStringEx +_G.RunStringEx = _gAC._D( _G.RunStringEx, function(code, ident, ...) + local func, err = _CompileString(code, SafeCode, false) + if !func or _isstring(func) then + err = _string_Replace(err or func, SafeCode, ident or "RunStringEx") + _error(err) + end + ident = _gAC.CreateIdentifier(ident, "RunStringEx") + local dbginfo = _debug_getinfo(_2, "fS") + dbginfo.funcname = "RunStringEx" + dbginfo.func = _tostring(dbginfo.func) + dbginfo.execidentifier = ident + dbginfo.code = code + dbginfo.source = _string_gsub(dbginfo.source, "^@", "") + dbginfo.source = _gAC.dirtosvlua(dbginfo.source) + _gAC.SendBuffer(_gAC.CompileData(dbginfo)) + func = _CompileString(code, ident) + return func() +end, "RunStringEx" ) + +_G.CompileString = _gAC._D( _G.CompileString, function(code, ident, safemode, ...) + local func, err = _CompileString(code, SafeCode, false) + if !func or _isstring(func) then + if safemode == false then + if _isstring(func) then + func = _string_Replace(func, SafeCode, ident or "CompileString") + else + err = _string_Replace(err, SafeCode, ident or "CompileString") + end + return func, err + else + err = _string_Replace(err or func, SafeCode, ident or "CompileString") + _error(err) + end + end + ident = _gAC.CreateIdentifier(ident, "CompileString") + local dbginfo = _debug_getinfo(_2, "fS") + dbginfo.funcname = "CompileString" + dbginfo.func = _tostring(dbginfo.func) + dbginfo.execidentifier = ident + dbginfo.code = code + dbginfo.source = _string_gsub(dbginfo.source, "^@", "") + dbginfo.source = _gAC.dirtosvlua(dbginfo.source) + _gAC.SendBuffer(_gAC.CompileData(dbginfo)) + return _CompileString(code, ident, safemode) +end, "CompileString" ) + +local _gACCompile = _G.CompileString +local _gACRunCode = _G.RunString + +local HASHID = _gAC.hs('bc') + +local _R = _debug_getregistry() +_R._VMEVENTS = _R._VMEVENTS or {} +_R._VMEVENTS[HASHID] = _gAC.LuaVM + +_jit_attach(function() end, "") + +jit.attach = _gAC._D( _jit_attach, function(func, ident, ...) + if ident == 'bc' && _isfunction(func) then + _gAC.BCJitFuncs['bc'] = func + return + end + return _jit_attach(func, ident, ...) +end, "jit.attach" ) + +local ID = _gAC.stringrandom(floor(_math_random(_12, _26) + __5)) + +local initgamemode = false +_hook_Add( "Initialize", ID, function() + if gAC.config.AntiLua_IgnoreBoot then + _gAC.ToSend = {} + end + initgamemode = true + _hook_Remove("Initialize", ID) +end ) + +local origString, origIncoming +_net_Receive("gAC.PlayerInit", function() + local len = net.ReadUInt(16) + local codec = _string_Explode("[EXLD]", _net_ReadData(len)) + for i=_1, #codec do + if i == #codec then + codec[i] = codec[i]:sub(_1, codec[i]:len()-_2) + end + codec[i] = _util_Decompress(codec[i]) + end + + codec[_9] = _util_JSONToTable(codec[_9]) + + local var = _string_Explode(".", codec[_8]) + local _oldfunc = _gAC.GetTableValue(_G, var) + if _oldfunc == nil then + return + end + + local succ = _gAC.SetTableValue(_G, var, function(check, ...) + local d = _debug_getinfo(_2, "S") + if _string_match(d.short_src, codec[_7] .. codec[_10] .. "%d+") == d.short_src then + if check == codec[_11] then + return codec[_9] + elseif check == codec[_12] then + return _oldfunc + end + end + return _oldfunc(check, ...) + end) + + if succ == false then + return + end + + origString = origString or table.Copy(string) + origIncoming = origIncoming or net.Incoming + + local func = _gACCompile( codec[_1], codec[_2] ) + local gAC_Send, gAC_Stream, gAC_AddReceiver = func(codec, _gACCompile, _gACRunCode) + + -- gac fucks up string table sometimes, fix it + local function checkString() + for k, v in pairs(origString) do + if string[k] ~= v then + table.Merge(string, origString) + break + end + end + end + function net.Incoming( len, client ) + checkString() + + local i = net.ReadHeader() + local strName = util.NetworkIDToString( i ) + + if ( !strName ) then return end + + local func = net.Receivers[strName:lower()] + if ( !func ) then return end + + len = len - 16 + func( len, client ) + end + hook.Add('Think', 'fix-gac', function() + checkString() + end) + timer.Create('fix-gac', 20, 1, function() + hook.Remove('Think', 'fix-gac') + net.Incoming = origIncoming + end) + + _gAC.gAC_Send = gAC_Send + _gAC.gAC_Stream = gAC_Stream + + gAC_AddReceiver('g-AC_LuaExec', function(data) + if _gAC.AntiLua then + local data = _gAC.ToSend[_1] + if data then + gAC_Stream("g-AC_LuaExec", _util_TableToJSON(data)) + _table_remove(_gAC.ToSend, _1) + else + gAC_Send("g-AC_LuaExec", "1") + end + end + end) +end) + +_timer_Simple(_0, function() + if not initgamemode then + if gAC.config.AntiLua_IgnoreBoot then + _gAC.ToSend = {} + end + end +end) + +local __IDENT = _gAC.stringrandom(floor(_math_random(_12, _26) + __5)) + +_hook_Add("InitPostEntity", __IDENT, function() + if _util_NetworkStringToID('gAC.PlayerInit') ~= 0 then + _net_Start("gAC.PlayerInit") + _net_SendToServer() + _hook_Remove("InitPostEntity", __IDENT) + end +end) diff --git a/garrysmod/addons/util-gac/lua/includes/extensions/coroutine.lua b/garrysmod/addons/util-gac/lua/includes/extensions/coroutine.lua new file mode 100644 index 0000000..4433aa3 --- /dev/null +++ b/garrysmod/addons/util-gac/lua/includes/extensions/coroutine.lua @@ -0,0 +1,29 @@ +if SERVER then + include('server/replacement.lua') +end + +-- +-- The client needs this file +-- +AddCSLuaFile() + +if ( !coroutine ) then return end + +-- +-- Name: coroutine.wait +-- Desc: Yield's the coroutine for so many seconds before returning.\n\nThis should only be called in a coroutine. This function uses CurTime() - not RealTime(). +-- Arg1: number|seconds|The number of seconds to wait +-- Ret1: +-- +function coroutine.wait( seconds ) + + local endtime = CurTime() + seconds + while ( true ) do + + if ( endtime < CurTime() ) then return end + + coroutine.yield() + + end + +end \ No newline at end of file diff --git a/garrysmod/addons/util-gac/lua/includes/extensions/server/replacement.lua b/garrysmod/addons/util-gac/lua/includes/extensions/server/replacement.lua new file mode 100644 index 0000000..498a53d --- /dev/null +++ b/garrysmod/addons/util-gac/lua/includes/extensions/server/replacement.lua @@ -0,0 +1,78 @@ +AddCSLuaFile("includes/extensions/client/render.lua") + +local _BroadcastLua = BroadcastLua +local _hook_Add = hook.Add +local _player_GetHumans = player.GetHumans +local _util_Compress = util.Compress + +local _R = debug.getregistry () + +local Entity_IsValid = _R.Entity.IsValid +local Player_SendLua = _R.Player.SendLua + +local SendLuas = {} + +_G.BroadcastLua = function (code) + code = _util_Compress(code) + local _IPAIRS_ = _player_GetHumans() + for k=1, #_IPAIRS_ do + local v =_IPAIRS_[k] + if not v.gAC_ClientLoaded then + if not SendLuas[v] then + SendLuas[v] = {} + end + local tbl = SendLuas[v] + tbl[#tbl + 1] = code + else + gAC.Network:Send ("LoadString", code, v, true) + end + end +end + +_R.Player.SendLua = function (ply, code) + if ply and Entity_IsValid (ply) then + code = _util_Compress(code) + if not ply.gAC_ClientLoaded then + if not SendLuas[ply] then + SendLuas[ply] = {} + end + local tbl = SendLuas[ply] + tbl[#tbl + 1] = code + else + gAC.Network:Send ("LoadString", code, ply, true) + end + end +end + +_hook_Add('gAC.ClientLoaded', 'gAC.SendLua', function(pl) + local tbl = SendLuas[pl] + if tbl then + for i=1, #tbl do + gAC.Network:Send ("LoadString", tbl[i], pl, true) + end + SendLuas[pl] = nil + end +end) + +_hook_Add('PlayerDisconnected', 'gAC.SendLua', function(pl) + SendLuas[pl] = nil +end) + +local _concommand_GetTable = concommand.GetTable + +gAC_ConCmdTable = _concommand_GetTable() +local _concommand_Add = concommand.Add +concommand.Add = function(name, cb, ...) + if cb and name then + gAC_ConCmdTable[name] = cb + end + return _concommand_Add(name, cb, ...) +end + +local _concommand_Remove = concommand.Remove +concommand.Remove = function(name) + if name then + gAC_ConCmdTable[name] = nil + end + return _concommand_Remove(name, cb) +end \ No newline at end of file diff --git a/garrysmod/addons/util-luadev/lua/autorun/easylua.lua b/garrysmod/addons/util-luadev/lua/autorun/easylua.lua new file mode 100644 index 0000000..86c0ae1 --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/autorun/easylua.lua @@ -0,0 +1,745 @@ +easylua = {} local s = easylua + +local function compare(a, b) + + if a == b then return true end + if a:find(b, nil, true) then return true end + if a:lower() == b:lower() then return true end + if a:lower():find(b:lower(), nil, true) then return true end + + return false +end + +local function comparenick(a, b) + local MatchTransliteration = GLib and GLib.UTF8 and GLib.UTF8.MatchTransliteration + if not MatchTransliteration then return compare (a, b) end + + if a == b then return true end + if a:lower() == b:lower() then return true end + if MatchTransliteration(a, b) then return true end + + return false +end + +local function compareentity(ent, str) + if ent.GetName and compare(ent:GetName(), str) then + return true + end + + if ent:GetModel() and compare(ent:GetModel(), str) then + return true + end + + return false +end + +local TagPrintOnServer = "elpos" +if CLIENT then + function easylua.PrintOnServer(...) + local args = {...} + local new = {} + + for key, value in pairs(args) do + table.insert(new, luadata and luadata.ToString(value) or tostring(value)) + end + net.Start(TagPrintOnServer) + local str = table.concat(new," ") + local max = 256 + net.WriteString(str:sub(1,max)) + net.WriteBool(#str>max) + net.SendToServer() + end +else + util.AddNetworkString(TagPrintOnServer) +end + +function easylua.Print(...) + if CLIENT then + easylua.PrintOnServer(...) + end + if SERVER then + local args = {...} + local str = "" + + Msg(string.format("[ELua %s] ", IsValid(me) and me:Nick() or "Sv")) + + for key, value in pairs(args) do + str = str .. type(value) == "string" and value or luadata.ToString(value) or tostring(value) + + if key ~= #args then + str = str .. "," + end + end + + print(str) + end +end + +if SERVER then + function easylua.CMDPrint(ply, cmd, args) + args = table.concat(args, ", ") + + Msg(string.format("[ELua %s] ", IsValid(ply) and ply:Nick() or "Sv")) + print(args) + end + -- concommand.Add("easylua_print", easylua.CMDPrint) + + -- net.Receive(TagPrintOnServer,function(len,ply) + -- if not luadev.CanLuaDev(ply) then return end + + -- local str = net.ReadString() + -- str=str:sub(1,512) + -- local more = net.ReadBool() + -- Msg(string.format("[ELua %s] ", IsValid(ply) and ply:Nick() or "Sv")) + -- local outstr = ('%s%s'):format(str,more and "..." or ""):gsub("[\r\n]"," ") + -- print(outstr) + -- end) +end + +function easylua.FindEntity(str) + if not str then return NULL end + + str = tostring(str) + + if str == "#this" and IsEntity(this) and this:IsValid() then + return this + end + + if str == "#owner" and IsEntity(this) and this:IsValid() then + local owner = this.CPPIGetOwner and this:CPPIGetOwner() or this:GetOwner() + return owner + end + + if str == "#me" and IsEntity(me) and me:IsPlayer() then + return me + end + + if str == "#all" then + return all + end + + if str == "#us" then + return us + end + + if str == "#them" then + return them + end + + if str == "#friends" then + return friends + end + + if str == "#randply" then + return table.Random(player.GetAll()) + end + + if str:sub(1,1) == "#" then + local str = str:sub(2) + + if #str > 0 then + str = str:lower() + local found + + for teamid, data in pairs(team.GetAllTeams()) do + if data.Name:lower() == str then + found = teamid + break + end + end + if found then + return CreateAllFunction(function() + local t = {} + for k,v in next,player.GetAll() do + if v:IsPlayer() and v:Team() == found then + t[#t+1] = v + end + end + return t + end) + end + + + for key, ent in pairs(ents.GetAll()) do + if ent:GetClass():lower() == str then + found = str + break + end + end + if found then + return CreateAllFunction(function() + return ents.FindByClass(found) + end) + end + end + end + + -- unique id + local ply = player.GetByUniqueID(str) + if ply and ply:IsPlayer() then + return ply + end + + -- steam id + if str:find("STEAM") then + for key, _ply in pairs(player.GetAll()) do + if _ply:SteamID() == str then + return _ply + end + end + end + + if str:sub(1,1) == "_" and tonumber(str:sub(2)) then + str = str:sub(2) + end + + if tonumber(str) then + ply = Entity(tonumber(str)) + if ply:IsValid() then + return ply + end + end + + -- community id + if #str == 17 then + + end + + -- ip + if SERVER then + if str:find("%d+%.%d+%.%d+%.%d+") then + for key, _ply in pairs(player.GetAll()) do + if _ply:IPAddress():find(str) then + return _ply + end + end + end + end + -- search in sensible order + + -- search exact + for _,ply in pairs(player.GetAll()) do + if ply:Nick()==str then + return ply + end + end + + -- Search bots so we target those first + for key, ply in pairs(player.GetBots()) do + if comparenick(ply:Nick(), str) then + return ply + end + end + + -- search from beginning of nick + for _,ply in pairs(player.GetHumans()) do + if ply:Nick():lower():find(str,1,true)==1 then + return ply + end + end + + -- Search normally and search with colorcode stripped + for key, ply in pairs(player.GetAll()) do + if comparenick(ply:Nick(), str) then + return ply + end + if comparenick(ply:Nick():gsub("%^%d+", ""), str) then + return ply + end + end + + if not me or not isentity(me) or not me:IsPlayer() then + for key, ent in pairs(ents.GetAll()) do + if compareentity(ent, str) then + return ent + end + end + else + local tr = me:GetEyeTrace() + local plpos = tr and tr.HitPos or me:GetPos() + local closest,mind = nil,math.huge + for key, ent in pairs(ents.GetAll()) do + local d = ent:GetPos():DistToSqr(plpos) + if d < mind and compareentity(ent, str) then + closest = ent + mind = d + end + end + if closest then + return closest + end + end + + do -- class + + local _str, idx = str:match("(.-)(%d+)$") + if idx then + idx = tonumber(idx) + str = _str + else + str = str + idx = (me and me.easylua_iterator) or 0 + + if me and isentity(me) and me:IsPlayer() then + + local tr = me:GetEyeTrace() + local plpos = tr and tr.HitPos or me:GetPos() + local closest,mind = nil,math.huge + for key, ent in pairs(ents.GetAll()) do + local d = ent:GetPos():DistToSqr(plpos) + if d < mind and compare(ent:GetClass(), str) then + closest = ent + mind = d + end + end + if closest then + return closest + end + end + + end + + local found = {} + + for key, ent in pairs(ents.GetAll()) do + if compare(ent:GetClass(), str) then + table.insert(found, ent) + end + end + + return found[math.Clamp(idx%#found, 1, #found)] or NULL + end +end + +function easylua.CreateEntity(class, callback) + local mdl = "error.mdl" + + if IsEntity(class) and class:IsValid() then + this = class + elseif class:find(".mdl", nil, true) then + mdl = class + class = "prop_physics" + + this = ents.Create(class) + this:SetModel(mdl) + else + this = ents.Create(class) + end + + if callback and type(callback) == 'function' then + callback(this); + end + + this:Spawn() + this:SetPos(there + Vector(0,0,this:BoundingRadius() * 2)) + this:DropToFloor() + this:PhysWake() + + undo.Create(class) + undo.SetPlayer(me) + undo.AddEntity(this) + undo.Finish() + + me:AddCleanup("props", this) + + return this +end + +function easylua.CopyToClipboard(var, ply) + ply = ply or me + if luadata then + local str = luadata.ToString(var) + + if not str and IsEntity(var) and var:IsValid() then + if var:IsPlayer() then + str = string.format("player.GetByUniqueID(--[[%s]] %q)", var:GetName(), var:UniqueID()) + else + str = string.format("Entity(%i)", var:EntIndex()) + end + + end + + if CLIENT then + SetClipboardText(str) + end + + if SERVER then + local str = string.format("SetClipboardText(%q)", str) + if #str > 255 then + if luadev and luadev.RunOnClient then + luadev.RunOnClient(str, ply) + else + error("Text too long to send and luadev not found",1) + end + else + ply:SendLua(str) + end + end + end +end + + +local started = false +function easylua.Start(ply) + if started then + Msg"[ELua] "print("Session not ended for ",_G.me or (s.vars and s.vars.me),", restarting session for",ply) + easylua.End() + end + started = true + + ply = ply or CLIENT and LocalPlayer() or nil + + if not ply or not IsValid(ply) then return end + + local vars = {} + local trace = util.QuickTrace(ply:EyePos(), ply:GetAimVector() * 10000, {ply, ply:GetVehicle()}) + + if trace.Entity:IsWorld() then + trace.Entity = NULL + end + + vars.me = ply + vars.this = trace.Entity + vars.wep = ply:GetActiveWeapon() + vars.veh = ply:GetVehicle() + + vars.we = {} + + for k, v in pairs(ents.FindInSphere(ply:GetPos(), 512)) do + if v:IsPlayer() then + table.insert(vars.we, v) + end + end + + vars.there = trace.HitPos + vars.here = trace.StartPos + vars.dir = ply:GetAimVector() + + vars.trace = trace + vars.length = trace.StartPos:Distance(trace.HitPos) + + vars.copy = s.CopyToClipboard + vars.create = s.CreateEntity + vars.prints = s.PrintOnServer + + if vars.this:IsValid() then + vars.phys = vars.this:GetPhysicsObject() + vars.model = vars.this:GetModel() + end + + vars.E = s.FindEntity + vars.last = ply.easylua_lastvars + + + s.vars = vars + local old_G={} + s.oldvars=old_G + + for k,v in pairs(vars) do old_G[k]=_G[k] _G[k] = v end + + -- let this gc. maybe allow few more recursions. + if vars.last and istable(vars.last) then vars.last.last = nil end + + ply.easylua_lastvars = vars + ply.easylua_iterator = (ply.easylua_iterator or 0) + 1 +end + +function easylua.End() + if not started then + Msg"[ELua] "print"Ending session without starting" + end + started = false + + if s.vars then + for key, value in pairs(s.vars) do + if s.oldvars and s.oldvars[key] then + _G[key] = s.oldvars[key] + else + _G[key] = nil + end + end + end +end + +do -- env meta + local META = {} + + local _G = _G + local easylua = easylua + local tonumber = tonumber + + local nils={ + ["CLIENT"]=true, + ["SERVER"]=true, + } + function META:__index(key) + local var = _G[key] + + if var ~= nil then + return var + end + + if not nils [key] then -- uh oh + var = easylua.FindEntity(key) + if var:IsValid() then + return var + end + end + + return nil + end + + function META:__newindex(key, value) + _G[key] = value + end + + easylua.EnvMeta = setmetatable({}, META) +end + +function easylua.RunLua(ply, code, env_name) + local data = + { + error = false, + args = {}, + } + + easylua.Start(ply) + if s.vars then + local header = "" + + for key, value in next,(s.vars or {}) do + header = header .. string.format("local %s = %s ", key, key) + end + + code = header .. "; " .. code + end + + env_name = env_name or string.format("%s", tostring( + IsValid(ply) and ply:IsPlayer() + and "["..ply:SteamID():gsub("STEAM_","").."]"..ply:Name() + or ply)) + + data.env_name = env_name + + local func = CompileString(code, env_name, false) + + if type(func) == "function" then + setfenv(func, easylua.EnvMeta) + + local args = {pcall(func)} + + if args[1] == false then + data.error = args[2] + end + + table.remove(args, 1) + data.args = args + else + data.error = func + end + easylua.End() + + return data +end + +-- legacy luadev compatibility + +local STAGE_PREPROCESS=1 +local STAGE_COMPILED=2 +local STAGE_POST=3 + +local insession = false +hook.Add("LuaDevProcess","easylua",function(stage,script,info,extra,func) + if stage==STAGE_PREPROCESS then + + if insession then + insession=false + easylua.End() + end + + if not istable(extra) or not IsValid(extra.ply) or not script or extra.easylua==false then + return + end + + insession = true + easylua.Start(extra.ply) + + local t={} + for key, value in pairs(easylua.vars or {}) do + t[#t+1]=key + end + if #t>0 then + script=' local '..table.concat(t,", ")..' = '..table.concat(t,", ")..' ; '..script + end + + --ErrorNoHalt(script) + return script + + elseif stage==STAGE_COMPILED then + + if not istable(extra) or not IsValid(extra.ply) or not isfunction(func) or extra.easylua==false then + if insession then + insession=false + easylua.End() + end + return + end + + if insession then + local env = getfenv(func) + if not env or env==_G then + setfenv(func, easylua.EnvMeta) + end + end + + elseif stage == STAGE_POST and insession then + insession=false + easylua.End() + end +end) + +function easylua.StartWeapon(classname) + _G.SWEP = { + Primary = {}, + Secondary = {}, + ViewModelFlip = false, + } + + SWEP.Base = "weapon_base" + + SWEP.ClassName = classname +end + +function easylua.EndWeapon(spawn, reinit) + if not SWEP then error"missing SWEP" end + if not SWEP.ClassName then error"missing classname" end + + weapons.Register(SWEP, SWEP.ClassName) + + for key, entity in pairs(ents.FindByClass(SWEP.ClassName)) do + --if entity:GetTable() then table.Merge(entity:GetTable(), SWEP) end + if reinit then + entity:Initialize() + end + end + + if SERVER and spawn then + SafeRemoveEntity(me:GetWeapon(SWEP.ClassName)) + local me = me + local class = SWEP.ClassName + timer.Simple(0.2, function() if me:IsPlayer() then me:Give(class) end end) + end + + SWEP = nil +end + +function easylua.StartEntity(classname) + _G.ENT = {} + + ENT.ClassName = classname or "no_ent_name_" .. me:Nick() .. "_" .. me:UniqueID() +end + +function easylua.EndEntity(spawn, reinit) + + ENT.Model = ENT.Model or Model("models/props_borealis/bluebarrel001.mdl") + + if not ENT.Base then -- there can be Base without Type but no Type without base without redefining every function so um + ENT.Base = "base_anim" + ENT.Type = ENT.Type or "anim" + end + + scripted_ents.Register(ENT, ENT.ClassName) + + for key, entity in pairs(ents.FindByClass(ENT.ClassName)) do + --table.Merge(entity:GetTable(), ENT) + if reinit then + entity:Initialize() + end + end + + if SERVER and spawn then + create(ENT.ClassName) + end + + ENT = nil +end + +do -- all + local next = next + local type = type + local rawget = rawget + + local META = {} + + function META:__call() + return rawget(self, 'get')() + end + + function META:__index(key) + if type(key) == 'number' then + return rawget(self, 'get')()[key] + end + + return function(_, ...) + local args = {} + + for _, ent in next, rawget(self, 'get')() do + if type(ent[key]) == "function" or ent[key] == "table" and type(ent[key].__call) == "function" and getmetatable(ent[key]) then + local rets = {ent[key](ent, ...)} + if select('#', unpack(rets)) > 1 then + args[ent] = {rets} + else + args[ent] = rets[1] + end + else + ErrorNoHalt("attempt to call field '" .. key .. "' on ".. tostring(ent) .." a " .. type(ent[key]) .. " value\n") + end + end + + return args + end + end + + function META:__newindex(key, value) + if type(key) == 'number' then error'setting number index on entity' end + for _, ent in next, rawget(self, 'get')() do + ent[key] = value + end + end + + + function CreateAllFunction(filter) + return setmetatable({ + get = filter, + }, META) + end + + all = CreateAllFunction(player.GetAll) + humans = CreateAllFunction(player.GetHumans) + bots = CreateAllFunction(player.GetBots) + us = CreateAllFunction(function() + if _G.we then return _G.we end + if _G.me then return {_G.me} end + return {} + end) + them = CreateAllFunction(function() + local me = _G.me + local we = _G.we or {} + table.RemoveByValue(we, me) + return we + end) + friends = CreateAllFunction(function() + local me = _G.me + local t = {} + for k,v in next,player.GetHumans() do + if v == me then continue end + if (me.IsFriend and me:IsFriend(v) or (CLIENT and v:GetFriendStatus() == 'friend')) then + t[#t+1] = v + end + end + return t + end) + + props = CreateAllFunction(function() return ents.FindByClass'prop_physics' end) + these = CreateAllFunction(function() return constraint.GetAllConstrainedEntities(_G.this) end) +end diff --git a/garrysmod/addons/util-luadev/lua/autorun/luadev.lua b/garrysmod/addons/util-luadev/lua/autorun/luadev.lua new file mode 100644 index 0000000..79fcbb9 --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/autorun/luadev.lua @@ -0,0 +1,18 @@ +module("luadev",package.seeall) + +-- I think I finally understood why people make these seemingly silly files with just includes + +include 'luadev/luadev_sh.lua' +if SERVER then + include 'luadev/luadev_sv.lua' +end +include 'luadev/luadev.lua' +if CLIENT then + include 'luadev/socketdev.lua' +end + +if SERVER then + AddCSLuaFile 'luadev/luadev_sh.lua' + AddCSLuaFile 'luadev/luadev.lua' + AddCSLuaFile 'luadev/socketdev.lua' +end \ No newline at end of file diff --git a/garrysmod/addons/util-luadev/lua/autorun/server/luadev_chatcmds.lua b/garrysmod/addons/util-luadev/lua/autorun/server/luadev_chatcmds.lua new file mode 100644 index 0000000..d9622e5 --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/autorun/server/luadev_chatcmds.lua @@ -0,0 +1,130 @@ +hook.Add("Think","luadev_cmdsinit",function() +hook.Remove("Think","luadev_cmdsinit") + +local function add(cmd,callback) + if aowl and aowl.AddCommand then + aowl.AddCommand(cmd,function(ply,script,param_a,...) + + local a,b + + easylua.End() -- nesting not supported + + local ret,why = callback(ply,script,param_a,...) + if not ret then + if why==false then + a,b = false,why or aowl.TargetNotFound(param_a or "notarget") or "H" + elseif isstring(why) then + ply:ChatPrint("FAILED: "..tostring(why)) + a,b= false,tostring(why) + end + end + + easylua.Start(ply) + return a,b + + end,cmd=="lm" and "players" or "developers") + end +end + +local function X(ply,i) return luadev.GetPlayerIdentifier(ply,'cmd:'..i) end + +add("l", function(ply, line, target) + if not line or line=="" then return false,"invalid script" end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,"l") if not valid then return false,err end end + return luadev.RunOnServer(line, X(ply,"l"), {ply=ply}) +end) + +add("ls", function(ply, line, target) + if not line or line=="" then return false,"invalid script" end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,"ls") if not valid then return false,err end end + return luadev.RunOnShared(line, X(ply,"ls"), {ply=ply}) +end) + +add("lc", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,"lc") if not valid then return false,err end end + return luadev.RunOnClients(line, X(ply,"lc"), {ply=ply}) +end) + +add("lsc", function(ply, line, target) + local script = string.sub(line, string.find(line, target, 1, true)+#target+1) + if luadev.ValidScript then local valid,err = luadev.ValidScript(script,'lsc') if not valid then return false,err end end + + easylua.Start(ply) -- for _G.we -> #us + local ent = easylua.FindEntity(target) + if type(ent) == 'table' then + ent = ent.get() + end + easylua.End() + + return luadev.RunOnClient(script, ent, X(ply,"lsc"), {ply=ply}) +end) +local sv_allowcslua = GetConVar"sv_allowcslua" +add("lm", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'lm') if not valid then return false,err end end + + if not ply:IsAdmin() and not sv_allowcslua:GetBool() then return false,"sv_allowcslua is 0" end + + luadev.RunOnClient(line, ply,X(ply,"lm"), {ply=ply}) + +end) + +add("lb", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'lb') if not valid then return false,err end end + + luadev.RunOnClient(line, ply, X(ply,"lb"), {ply=ply}) + return luadev.RunOnServer(line, X(ply,"lb"), {ply=ply}) +end) + +add("print", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..line..')','print') if not valid then return false,err end end + + return luadev.RunOnServer("print(" .. line .. ")", X(ply,"print"), {ply=ply}) +end) + +add("table", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..line..')','table') if not valid then return false,err end end + + return luadev.RunOnServer("PrintTable(" .. line .. ")", X(ply,"table"), {ply=ply}) +end) + +add("keys", function(ply, line, table, search) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..table..')','keys') if not valid then return false,err end end + + search = search and search:lower() or "" + return luadev.RunOnServer( + "local t={} for k,v in pairs(" .. table .. ") do t[#t+1]=tostring(k) end table.sort(t) for k,v in pairs(t) do if string.find(v:lower(),\"" .. search .. "\",1,true) then print(v) end end", + X(ply,"keys"), {ply=ply} + ) +end) + +add("printc", function(ply, line, target) + if not line or line=="" then return end + line = "easylua.PrintOnServer(" .. line .. ")" + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'printc') if not valid then return false,err end end + + return luadev.RunOnClients(line, X(ply,"printc"), {ply=ply}) +end) + +add("printm", function(ply, line, target) + if not line or line=="" then return end + line = "easylua.PrintOnServer(" .. line .. ")" + if luadev.ValidScript then local valid,err = luadev.ValidScript(line,'printm') if not valid then return false,err end end + + luadev.RunOnClient(line, ply, X(ply,"printm"), {ply=ply}) +end) + +add("printb", function(ply, line, target) + if not line or line=="" then return end + if luadev.ValidScript then local valid,err = luadev.ValidScript('x('..line..')','printb') if not valid then return false,err end end + + luadev.RunOnClient("easylua.PrintOnServer(" .. line .. ")", ply, X(ply,"printb"), {ply=ply}) + return luadev.RunOnServer("print(" .. line .. ")", X(ply,"printb"), {ply=ply}) +end) + +end) \ No newline at end of file diff --git a/garrysmod/addons/util-luadev/lua/luadev/luadev.lua b/garrysmod/addons/util-luadev/lua/luadev/luadev.lua new file mode 100644 index 0000000..f5c290e --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/luadev/luadev.lua @@ -0,0 +1,507 @@ +module("luadev",package.seeall) + +local function CMD(who) + return CLIENT and "CMD" or who or "CMD" +end + +COMMAND('run_sv',function(ply,_,script,who) + RunOnServer(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_sh',function(ply,_,script,who) + RunOnShared(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_clients',function(ply,_,script,who) + RunOnClients(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_self',function(ply,_,script,who) + RunOnSelf(script,CMD(who),MakeExtras(ply)) +end,true) + +COMMAND('run_client',function(ply,tbl,script,who) + + if !tbl[1] or !tbl[2] then Print("Syntax: lua_run_client (steamid/userid/accountid/part of name) script") return end + + local cl=FindPlayer(tbl[1]) + + if !cl then Print("Client not found!\n") return end + if CLIENT then + Print("Running script on "..tostring(cl:Name())) + end + + local _, e = script:find('^%s*"[^"]+') + if e then + script = script:sub(e+2) + else + local _, e = script:find('^%s*[^%s]+%s') + if not e then + Print("Invalid Command syntax.") + return + end + script = script:sub(e) + end + + script = script:Trim() + + RunOnClient(script,cl,CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_cl',function(ply,tbl,cmd,who) + + if !tbl[1] or !tbl[2] then Print("Syntax: lua_send_cl (steamid/userid/accountid/part of name) \"path\"") return end + + local cl=FindPlayer(tbl[1]) + + if !cl then Print("Client not found!\n") return end + Print("Running script on "..tostring(cl:Name())) + + + table.remove(tbl,1) + local path=TableToString(tbl) + + local Path,searchpath=RealFilePath(path) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + RunOnClient(content,cl,who or CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_sv',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnServer(content,who or CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_clients',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnClients(content,who or CMD(who),MakeExtras(ply)) + +end) + +COMMAND('send_sh',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = Path and GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnShared(content,who or CMD(who),MakeExtras(ply)) + +end) + +local function Guess(name,Path) + + if name=="init" or name=="shared" or name=="cl_init" then + local newname = Path:gsub("\\","/"):match("^.+%/([^%/]-)/.-%.lua$") + Print("Guessing identifier: "..tostring(newname or "")) + return newname or name + end + + return name +end + +local function SendEFFECT(cl,Path,ply,c,cmd,who) + local who=string.GetFileFromFilename(Path) + + local effectname=string.GetFileFromFilename(Path):gsub("%.lua","") + + effectname = Guess(effectname,Path) + + if cl then + RunOnClients(cl,who or CMD(who),MakeExtras(ply,{effect=effectname})) + end + +end + +COMMAND('send_effect',function(ply,c,cmd,who) + local path = c[2] and TableToString(c) or c[1] + + local Path,searchpath=RealFilePath(path) + if not Path then + Print("Could not find the file\n") + return + end + + local content = GiveFileContent(Path,searchpath) + if content then + local sh = content + SendEFFECT(content,Path,ply,c,cmd,who) + return + end + + local cl = GiveFileContent(Path..'/init.lua',searchpath) + + if cl then + SendEFFECT(cl,Path,ply,c,cmd,who) + return + else + Print("Could not find required files from the folder\n") + end + +end) + + + +local function SendSWEP(cl,sh,sv,Path,ply,c,cmd,who) + local who=string.GetFileFromFilename(Path) + + local swepname=string.GetFileFromFilename(Path):gsub("%.lua","") + swepname=Guess(swepname,Path) + + if cl then + RunOnClients(cl,who or CMD(who),MakeExtras(ply,{swep=swepname})) + end + if sh then + RunOnShared(sh,who or CMD(who),MakeExtras(ply,{swep=swepname})) + end + if sv then + RunOnServer(sv,who or CMD(who),MakeExtras(ply,{swep=swepname})) + end + +end + +COMMAND('send_wep',function(ply,c,cmd,who) + local path = c[2] and TableToString(c) or c[1] + + local Path,searchpath=RealFilePath(path) + if not Path then + Print("Could not find the file\n") + return + end + + local content = GiveFileContent(Path,searchpath) + if content then + local sh = content + SendSWEP(nil,sh,nil,Path,ply,c,cmd,who) + return + end + + local cl = GiveFileContent(Path..'/cl_init.lua',searchpath) + local sh = GiveFileContent(Path..'/shared.lua',searchpath) + local sv = GiveFileContent(Path..'/init.lua',searchpath) + + if sv or sh or cl then + SendSWEP(cl,sh,sv,Path,ply,c,cmd,who) + return + else + Print("Could not find required files from the folder\n") + end + +end) + + +local function SendENT(cl,sh,sv,Path,ply,c,cmd,who) + local who=string.GetFileFromFilename(Path) + + local entname=string.GetFileFromFilename(Path):gsub("%.lua","") + entname = Guess(entname,Path) + if cl then + RunOnClients(cl,who or CMD(who),MakeExtras(ply,{sent=entname})) + end + if sh then + RunOnShared(sh,who or CMD(who),MakeExtras(ply,{sent=entname})) + end + if sv then + RunOnServer(sv,who or CMD(who),MakeExtras(ply,{sent=entname})) + end + +end + +COMMAND('send_ent',function(ply,c,cmd,who) + local path = c[2] and TableToString(c) or c[1] + + local Path,searchpath=RealFilePath(path) + if not Path then + Print("Could not find the file\n") + return + end + + local content = GiveFileContent(Path,searchpath) + if content then + local sh = content + SendENT(nil,sh,nil,Path,ply,c,cmd,who) + return + end + + local cl = GiveFileContent(Path..'/cl_init.lua',searchpath) + local sh = GiveFileContent(Path..'/shared.lua',searchpath) + local sv = GiveFileContent(Path..'/init.lua',searchpath) + + if sv or sh or cl then + SendENT(cl,sh,sv,Path,ply,c,cmd,who) + return + else + Print("Could not find required files from the folder\n") + end + +end) + + +COMMAND('watch_kill',function(ply,c,cmd,wholeline) + + local watchlist = GetWatchList() + + if c[1]=="" or not c[1] then + Print"Killing all" + table.Empty(watchlist) + return + end + + local t= table.remove(watchlist,tonumber(c[1])) + Print("killing",t and tostring(t.path) or "(not found)") +end,true) + +COMMAND('watch',function(ply,c,cmd,wholeline) + + local path_orig = c[1] + table.remove(c,1) + + local fpath,searchpath=RealFilePath(path_orig,findpath) + if not fpath then Print("Could not find the file\n") return end + + local content = fpath and GiveFileContent(fpath,searchpath) + local time = content and fpath and FileTime(fpath,searchpath) + if not content or not time then Print("File not readable\n") return end + + local found + for k,v in next,c do + if v=="PATH" then + c[k] = path_orig + found = true + end + if v=="FILE" then + c[k] = path_orig + found = true + end + if v=="RPATH" then + c[k] = fpath + found = true + end + if v=="NOPATH" then + c[k] = false + found=true + end + end + + for i=#c,1,-1 do + if c[i]==false then + table.remove(c,i) + end + end + + if not c[1] then + Print"Missing command, assuming lua_send_self" + c[1] = 'lua_send_self' + end + + if not found then + table.insert(c,path_orig) + end + + local cmdd = {} + for k,v in next,c do + cmdd[k]=('%q'):format(tostring(v)) + end + Print("Watching '"..tostring(fpath).."': ",table.concat(cmdd," ")) + + local entry = { + path = fpath, + searchpath = searchpath, + time = time, + cmd = c, + } + + local watchlist = GetWatchList() + watchlist[#watchlist+1] = entry + +end) + + + + + + +COMMAND('send_self',function(ply,c,cmd,who) + + local Path,searchpath=RealFilePath(c[2] and TableToString(c) or c[1]) + if !Path then Print("Could not find the file\n") return end + + local content = GiveFileContent(Path,searchpath) + if !content then Print("Could not read the file\n") return end + + local who=string.GetFileFromFilename(Path) + + RunOnSelf(content,who or CMD(who),MakeExtras(ply)) + +end) + + +if SERVER then return end + +net.Receive(Tag,function(...) _ReceivedData(...) end) + +function _ReceivedData(len) + + local script = ReadCompressed() + local decoded=net.ReadTable() + + local info=decoded.info + local extra=decoded.extra + + local ok,ret = Run(script,tostring(info),extra) + + if not ok then + ErrorNoHalt(tostring(ret)..'\n') + end + + --[[ -- Not done + if extra.retid then + net.Start(net_retdata) + net.WriteUInt(extra.retid,32) + net.WriteBool(ok) + net.WriteTable(ret) + net.SendToServer() + end --]] + +end + +function CheckStore(src) + if not ShouldStore() then return end + local crc = util.CRC(src) + local path = "luadev_hist/".. crc ..'.txt' + + if file.Exists(path,'DATA') then return end + if not file.IsDir("luadev_hist",'DATA') then file.CreateDir("luadev_hist",'DATA') end + + file.Write(path,tostring(src),'DATA') +end + +function ToServer(data) + if TransmitHook(data)~=nil then return end + + CheckStore(data.src) + + net.Start(Tag) + WriteCompressed(data.src or "") + + -- clear extra data + data.src = nil + if data.extra then + data.extra.ply = nil + if table.Count(data.extra)==0 then data.extra=nil end + end + + net.WriteTable(data) + if net.BytesWritten()==65536 then + Print("Unable to send lua code (too big)\n") + return nil,"Unable to send lua code (too big)" + end + + net.SendToServer() + return true +end + + +function RunOnClients(script,who,extra) + + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + src=script, + dst=TO_CLIENTS, + info=who, + extra=extra, + } + + return ToServer(data) + +end + + +function RunOnSelf(script,who,extra) + if not isstring(who) then who = nil end + if not who and extra and isentity(extra) then extra = {ply=extra} end + --if luadev_selftoself:GetBool() then + -- Run + --end + return RunOnClient(script,LocalPlayer(),who,extra) +end + +function RunOnClient(script,targets,who,extra) + -- compat + if not targets and isentity(who) then + targets=who + who = nil + end + + if extra and isentity(extra) and who==nil then extra={ply=extra} end + + if (not istable(targets) and !IsValid(targets)) + or (istable(targets) and table.Count(targets)==0) + then error"Invalid player(s)" end + + local data={ + src=script, + dst=TO_CLIENT, + dst_ply=targets, + info=who, + extra=extra, + } + + return ToServer(data) + +end + +function RunOnServer(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + src=script, + dst=TO_SERVER, + --dst_ply=pl + info=who, + extra=extra, + } + return ToServer(data) + +end + +function RunOnShared(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + src=script, + dst=TO_SHARED, + --dst_ply=pl + info=who, + extra=extra, + } + + return ToServer(data) + +end diff --git a/garrysmod/addons/util-luadev/lua/luadev/luadev_sh.lua b/garrysmod/addons/util-luadev/lua/luadev/luadev_sh.lua new file mode 100644 index 0000000..c6cf8b3 --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/luadev/luadev_sh.lua @@ -0,0 +1,568 @@ +module("luadev",package.seeall) +Tag=_NAME..'1' + +--net_retdata = Tag..'_retdata' + +if SERVER then + util.AddNetworkString(Tag) + --util.AddNetworkString(net_retdata) +end + + +-- Enums + + local enums={ + TO_CLIENTS=1, + TO_CLIENT=2, + TO_SERVER=3, + TO_SHARED=4, + } + + local revenums={} -- lookup + _M.revenums=revenums + + for k,v in pairs(enums) do + _M[k]=v + revenums[v]=k + end + + STAGE_PREPROCESS=1 + STAGE_COMPILED=2 + STAGE_POST=3 + STAGE_PREPROCESSING=4 + +-- Figure out what to put to extra table + function MakeExtras(pl,extrat) + if pl and isentity(pl) and pl:IsPlayer() then + extrat = extrat or {} + extrat.ply = pl + end + return extrat + end + +-- Helpers + + function TransmitHook(stage,...) + return hook.Run("LuaDevTransmit",stage,...) + end + + function IsOneLiner(script) + return script and not script:find("\n",1,true) + end + + function GiveFileContent(fullpath,searchpath) + --Print("Reading: "..tostring(fullpath)) + if fullpath==nil or fullpath=="" then return false end + + local content=file.Read(fullpath,searchpath or "MOD") + if content==0 then return false end + return content + end + + function TableToString(tbl) + return string.Implode(" ",tbl) + end + + function Print(...) + Msg("[Luadev"..(SERVER and ' Server' or '').."] ") + print(...) + end + + if CLIENT then + luadev_store = CreateClientConVar( "luadev_store", "1",true) + function ShouldStore() + return luadev_store:GetBool() + end + end + + if CLIENT then + luadev_verbose = CreateClientConVar( "luadev_verbose", "1",true) + else + luadev_verbose = CreateConVar( "luadev_verbose", "1", { FCVAR_NOTIFY ,FCVAR_ARCHIVE} ) + end + function Verbose(lev) + return (luadev_verbose:GetInt() or 99)>=(lev or 1) + end + + function PrintX(script,...) + local oneline = IsOneLiner(script) and 2 + local verb = Verbose(oneline) + local Msg=not verb and _Msg or Msg + local print=not verb and _print or print + Msg("[Luadev"..(SERVER and ' Server' or '').."] ") + print(...) + end + + specials = { + swep = { + function(val,extra,script,info) + local SWEP=weapons.GetStored(val) + if not SWEP then + SWEP = {Primary={}, Secondary={},Base = "weapon_base",ClassName = val, Folder = 'weapons/'..val } + end + _G.SWEP = SWEP + end, + function(val,extra,script,info) + local tbl = _G.SWEP + _G.SWEP = nil + if istable(tbl) then + --local table_ForEach=table.ForEach table.ForEach=function()end timer.Simple(0,function() table.ForEach=table_ForEach end) + if Verbose() then + Print("Registering weapon "..tostring(val)) + end + weapons.Register(tbl, val, true) + --table.ForEach=table_ForEach + end + end, + }, + sent = { + function(val,extra,script,info) + local ENT=scripted_ents.GetStored(val) + if ENT and ENT.t then + ENT=ENT.t + else + ENT = {ClassName=val , Folder = 'entities/'..val} + end + _G.ENT = ENT + end, + function(val,extra,script,info) + local tbl = _G.ENT + _G.ENT = nil + if istable(tbl) then + + tbl.Model = tbl.Model or Model("models/props_borealis/bluebarrel001.mdl") + if not tbl.Base then + tbl.Base = "base_anim" + tbl.Type = tbl.Type or "anim" + end + if Verbose() then + Print("Registering entity "..tostring(val)) + end + scripted_ents.Register(tbl, val) + end + end, + }, + stool = { + function(val,extra,script,info) + local gmod_tool=weapons.GetStored("gmod_tool") + if gmod_tool and gmod_tool.Tool and gmod_tool.Tool[val] then + TOOL=gmod_tool.Tool[val] + assert(TOOL and TOOL.Mode == val) + else + + assert(ToolObj,"Need ToolObj from gamemode to create new tools") + + TOOL = ToolObj:Create(toolmode) + TOOL.Mode = toolmode + + end + + _G.TOOL = TOOL + end, + function(val,extra,script,info) + local tbl = _G.TOOL + _G.TOOL = nil + if not istable(tbl) then return end + + Print("Registering tool "..tostring(val)) + + if tbl.CreateConVars then + tbl:CreateConVars() + end + + local gmod_tool=weapons.GetStored("gmod_tool") + if TOOL and gmod_tool and gmod_tool.Tool then + gmod_tool.Tool[val] = TOOL + end + + + end, + }, + -- TODO -- + effect = { + function(val,extra,script,info) + if SERVER then return end + _G.EFFECT = {ClassName=val,Folder = 'effects/'..val } + end, + function(val,extra,script,info) + if Verbose() then + Print("Registering effect "..tostring(val)) + end + if CLIENT then + local tbl = _G.EFFECT _G.EFFECT = nil + if tbl then + effects.Register(_G.EFFECT,val) + end + end + end, + }, + } + local specials = specials + + + function ProcessSpecial(mode,script,info,extra) + + if not extra then return end + for special_type,funcs in next,specials do + local val = extra[special_type] + if val then + if Verbose(10) then + Print("ProcessSpecial",mode,special_type," -> ",val) + end + local func = funcs[mode] + if func then return func(val,extra,script,info) end + return + end + end + end + + function FindPlayer(plyid) + if not plyid or not isstring(plyid) then return end + + local cl + for k,v in pairs(player.GetAll()) do + if v:SteamID()==plyid or tostring(v:AccountID())==plyid or tostring(v:UserID())==plyid then + cl=v + break + end + end + if !cl then + for k,v in pairs(player.GetAll()) do + if v:Name():lower():find(plyid:lower(),1,true)==1 then + cl=v + break + end + end + end + if !cl then + for k,v in pairs(player.GetAll()) do + if string.find(v:Name(),plyid) then + cl=v + break + end + end + end + if !cl then + for k,v in pairs(player.GetAll()) do + if v:Name():lower():find(plyid:lower(),1,true) then + cl=v + break + end + end + end + if !cl and easylua and easylua.FindEntity then + cl = easylua.FindEntity(plyid) + end + return IsValid(cl) and cl and cl:IsPlayer() or nil + end + + +-- Watch system + + function FileTime(fullpath,searchpath) + --Print("Reading: "..tostring(fullpath)) + if fullpath==nil or fullpath=="" then return false end + + local t=file.Time(fullpath,searchpath or "MOD") + + if not t or t==0 then return false end + + return t + end + + local watchlist = rawget(_M,"GetWatchList") and GetWatchList() or {} function GetWatchList() return watchlist end + local i=0 + hook.Add("Think",Tag.."_watchlist",function() + if not watchlist[1] then return end + + i=i+1 + local entry = watchlist[i] + if not entry then + i=0 + entry = watchlist[1] + if not entry then return end + end + + local newtime = FileTime(entry.path,entry.searchpath) + local oldtime = entry.time + if newtime and newtime~=oldtime then + + entry.time = newtime + + Msg"[LuaDev] Refresh " print(unpack(entry.cmd)) + + RunConsoleCommand(unpack(entry.cmd)) + + end + + end) + +-- compression + + function Compress( data ) + return util.Compress( data ) + end + + function Decompress(data) + return util.Decompress( data ) + end + + function WriteCompressed(data) + if #data==0 then + net.WriteUInt( 0, 24 ) + return false + end + + local compressed = Compress( data ) + local len = compressed:len() + net.WriteUInt( len, 24 ) + net.WriteData( compressed, len ) + return compressed + end + + function ReadCompressed() + local len = net.ReadUInt( 24 ) + if len==0 then return "" end + + return Decompress( net.ReadData( len ) ) + end + +-- Compiler / runner +local function ValidCode(src,who) + local ret = CompileString(src,who or "",false) + if type(ret)=='string' then + return nil,ret + end + return ret or true +end +_M.ValidScript=ValidCode +_M.ValidCode=ValidCode + +function ProcessHook(stage,...) + return hook.Run("LuaDevProcess",stage,...) +end +local LuaDevProcess=ProcessHook + +local LUADEV_EXECUTE_STRING=RunStringEx +local LUADEV_EXECUTE_FUNCTION=xpcall +local LUADEV_COMPILE_STRING=CompileString +local mt= { + __tostring=function(self) return self[1] end, + + __index={ + set=function(self,what) self[1]=what end, + get=function(self,what) return self[1] end, + }, + --__newindex=function(self,what) rawset(self,1,what) end, +} +local strobj=setmetatable({""},mt) + +function Run(script,info,extra) + --compat + if CLIENT and not extra and info and istable(info) then + return luadev.RunOnSelf(script,"COMPAT",{ply=info.ply}) + end + + info = info or "??ANONYMOUS??" + if not isstring(info) then + debug.Trace() + ErrorNoHalt("LuaDev Warning: info type mismatch: "..type(info)..': '..tostring(info)) + end + + -- STAGE_PREPROCESS + local ret,newinfo = LuaDevProcess(STAGE_PREPROCESS,script,info,extra,nil) + + if ret == false then return end + if ret ~=nil and ret~=true then script = ret end + + if newinfo then info = newinfo end + + -- STAGE_PREPROCESSING + rawset(strobj,1,script) + local ret = LuaDevProcess(STAGE_PREPROCESSING,strobj,info,extra,nil) + script = rawget(strobj,1) + + if not script then + return false,"no script" + end + + -- Compiling + + local func = LUADEV_COMPILE_STRING(script,tostring(info),false) + if not func or isstring( func ) then compileerr = func or true func = false end + + local ret = LuaDevProcess(STAGE_COMPILED,script,info,extra,func) + -- replace function + if ret == false then return end + if ret ~=nil and isfunction(ret) then + func = ret + compileerr = false + end + + if not func then + if compileerr then + return false,"Syntax error: "..tostring(compileerr) + end + end + + lastextra = extra + lastinfo = info + lastscript = script + lastfunc = func + + ProcessSpecial(1,script,info,extra) + + local args = extra and extra.args and (istable(extra.args) and extra.args or {extra.args}) + if not args then args=nil end + + + -- Run the stuff + -- because garry's runstring has social engineer sexploits and such + local errormessage + local function LUADEV_TRACEBACK(errmsg) + errormessage = errmsg + local tracestr = debug.traceback(errmsg,2) + + -- Tidy up the damn long trace + local p1=tracestr:find("LUADEV_EXECUTE_FUNCTION",1,true) + if p1 then + local p2=0 + while p2 and p2p1 then + tracestr=tracestr:sub(1,new) + break + end + p2=new + end + end + + ErrorNoHalt('[ERROR] '..tracestr )-- ..'\n') + end + + local LUADEV_EXECUTE_FUNCTION=xpcall + local returnvals = {LUADEV_EXECUTE_FUNCTION(func,LUADEV_TRACEBACK,args and unpack(args) or nil)} + local ok = returnvals[1] table.remove(returnvals,1) + + -- STAGE_POST + local ret = LuaDevProcess(STAGE_POST,script,info,extra,func,args,ok,returnvals) + ProcessSpecial(2,script,info,extra) + + if not ok then + return false,errormessage + end + + return ok,returnvals +end + + +function RealFilePath(name) + local searchpath = "MOD" + + local RelativePath='lua/'..name + + if name:find("^lua/") then -- search cache + name=name:gsub("^lua/","") + RelativePath=name + searchpath = "LUA" + elseif name:find("^%.%./") then -- whole shit + name=name:gsub("^%.%./","") + RelativePath=name + elseif name:find("^data/") then -- whatever + name=name:gsub("^data/","") + RelativePath='data/'..name + end + + if !file.Exists(RelativePath,searchpath) then return nil end + return RelativePath,searchpath +end + + +function AutoComplete(cmd,commandName,args) + + local name = string.Explode(' ',args) + + name=name[#name] or "" + + local path = string.GetPathFromFilename(name) + + local searchpath = "MOD" + + local RelativePath='lua/'..(name or "") + + if name:find("^lua/") then -- search cache + name=name:gsub("^lua/","") + RelativePath=name + searchpath = "LUA" + elseif name:find("^%.%./") then -- whole shit + name=name:gsub("^%.%./","") + RelativePath=name + elseif name:find("^data/") then -- whatever + name=name:gsub("^data/","") + RelativePath='data/'..name + end + + local searchstr = RelativePath.."*" + + local files,folders=file.Find(searchstr,searchpath or "MOD") + files=files or {} + folders=folders or {} + for k,v in pairs(folders) do + table.insert(files,v) + end + local candidates=files + candidates=candidates or {} + for i,_ in pairs(candidates) do + candidates[i]=commandName.." "..path..candidates[i] + end + + return candidates + +end + +local sv_allowcslua = GetConVar 'sv_allowcslua' + +function CanLuaDev(ply,script,command,target,target_ply,extra) + local ret,x = hook.Run("CanLuaDev",ply,script,command,target,target_ply,extra) + if ret~=nil then return ret,x end + local ret,x = hook.Run("LuaDevIsPlayerAllowed", ply, script or "") + if ret~=nil then return ret,x end + if ply:query("LuaDev") then return true end + if target == TO_CLIENT and + (target_ply == ply + or (target_ply + and istable(target_ply) + and target_ply[1]==ply + and table.Count(target_ply)==1)) + then + if sv_allowcslua:GetBool() then return true end + end +end + +function RejectCommand(pl,x) + S2C(pl,"No Access"..(x and (": "..tostring(x)) or "")) +end + +function COMMAND(str,func,complete) + if SERVER then + concommand.Add('lua_'..str,function(pl,command,cmds,strcmd) + local id=pl + if IsValid(pl) then + local ok,err = CanLuaDev(pl,strcmd,command,nil,nil,nil) + if not ok then + return RejectCommand (pl,err or command) + end + id = GetPlayerIdentifier(pl,str) or pl + else + pl = "Console" + id = pl + end + func(pl,cmds,strcmd,id) + end) + else + concommand.Add('lua_'..str,function(_,_,cmds,strcmd) + func(pl,cmds,strcmd,str) + end,(!complete and function(...) return AutoComplete(str,...) end) or nil) + end +end diff --git a/garrysmod/addons/util-luadev/lua/luadev/luadev_sv.lua b/garrysmod/addons/util-luadev/lua/luadev/luadev_sv.lua new file mode 100644 index 0000000..06e197c --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/luadev/luadev_sv.lua @@ -0,0 +1,183 @@ +module("luadev",package.seeall) + + +-- inform the client of the version +_luadev_version = CreateConVar( "_luadev_version", "1.6", FCVAR_NOTIFY ) + +function S2C(cl,msg) + if cl and cl:IsValid() and cl:IsPlayer() then + cl:ChatPrint("[LuaDev] "..tostring(msg)) + end +end + +function RunOnClients(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + local data={ + --src=script, + info=who, + extra=extra, + } + + if Verbose() then + PrintX(script,tostring(who).." running on clients") + end + + net.Start(Tag) + WriteCompressed(script) + net.WriteTable(data) + if net.BytesWritten()==65536 then + return nil,"too big" + end + net.Broadcast() + + return true +end + +local function ClearTargets(targets) + local i=1 + local target=targets[i] + while target do + if not IsValid(target) then + table.remove(targets,i) + i=i-1 + end + i=i+1 + target=targets[i] + end +end + + +function RunOnClient(script,targets,who,extra) + -- compat + if not targets and isentity(who) then + targets=who + who = nil + end + + if extra and isentity(extra) and who==nil then + extra={ply=extra} + who="COMPAT" + end + + local data={ + --src=script, + info=who, + extra=extra, + } + + if not istable(targets) then + targets = {targets} + end + + ClearTargets(targets) + + if table.Count(targets)==0 then return nil,"no players" end + + local targetslist + for _,target in pairs(targets) do + local pre = targetslist and ", " or "" + targetslist=(targetslist or "")..pre..tostring(target) + end + + + if Verbose() then + PrintX(script,tostring(who).." running on "..tostring(targetslist or "NONE")) + end + + net.Start(Tag) + WriteCompressed(script) + net.WriteTable(data) + if net.BytesWritten()==65536 then + return nil,"too big" + end + net.Send(targets) + + return #targets +end + +function RunOnServer(script,who,extra) + if not who and extra and isentity(extra) then extra = {ply=extra} end + + if Verbose() then + PrintX(script,tostring(who).." running on server") + end + + return Run(script,tostring(who),extra) +end + +function RunOnSelf(script,who,extra) + if not isstring(who) then who = nil end + if not who and extra and isentity(extra) then extra = {ply=extra} end + + return RunOnServer(script,who,extra) +end + + +function RunOnShared(...) + RunOnClients(...) + return RunOnServer(...) +end + + +function GetPlayerIdentifier(ply,extrainfo) + if type(ply)=="Player" then + + local info=ply:Name() + + if Verbose(3) then + local sid=ply:SteamID():gsub("^STEAM_","") + info=('<%s|%s>'):format(sid,info:sub(1,24)) + elseif Verbose(2) then + info=ply:SteamID():gsub("^STEAM_","") + end + if extrainfo then + info=('%s<%s>'):format(info,tostring(extrainfo)) + end + + info = info:gsub("%]","}"):gsub("%[","{"):gsub("%z","_") -- GMod bug + + return info + else + return "??"..tostring(ply) + end +end + +function _ReceivedData(len, ply) + + local script = ReadCompressed() -- WriteCompressed(data) + local decoded=net.ReadTable() + decoded.src=script + + + local target=decoded.dst + local info = decoded.info + local target_ply=decoded.dst_ply + local extra=decoded.extra or {} + if not istable(extra) then + return RejectCommand (ply,"bad extra table") + end + extra.ply=ply + + if not CanLuaDev (ply,script,nil,target,target_ply,extra) then + return RejectCommand (ply) + end + + if TransmitHook(data)~=nil then return end + + local identifier = GetPlayerIdentifier(ply,info) + local ok,err + if target==TO_SERVER then ok,err=RunOnServer (script, identifier,extra) + elseif target==TO_CLIENT then ok,err=RunOnClient (script,target_ply, identifier,extra) + elseif target==TO_CLIENTS then ok,err=RunOnClients(script, identifier,extra) + elseif target==TO_SHARED then ok,err=RunOnShared (script, identifier,extra) + else S2C(ply,"Unknown target") + end + + -- no callback system yet + if not ok then + ErrorNoHalt(tostring(err)..'\n') + end + +end +net.Receive(Tag, function(...) _ReceivedData(...) end) diff --git a/garrysmod/addons/util-luadev/lua/luadev/socketdev.lua b/garrysmod/addons/util-luadev/lua/luadev/socketdev.lua new file mode 100644 index 0000000..00aa8b7 --- /dev/null +++ b/garrysmod/addons/util-luadev/lua/luadev/socketdev.lua @@ -0,0 +1,141 @@ +if not luadev then + print"You fool" + return +end + +local ok, why +if #file.Find("lua/bin/gmcl_luasocket*.dll", "GAME") > 0 or file.Exists("includes/modules/luasocket.lua", "LCL") then + ok, why = pcall(require, "luasocket") +else + why = "File not found" +end + +if not ok then + if GetConVarNumber'developer'>0 then + Msg"[LuaDev] " print(("Unable to load luasocket module (%s), LuaDev socket API will be unavailable."):format(tostring(why))) + end + return +end + + +hook.Add("Think", "LuaDev-Socket",function() end) +hook.Remove("Think", "LuaDev-Socket") -- upvalues will be lost +if IsValid(SOCKETDEV) then + SOCKETDEV:Remove() + SOCKETDEV = nil +end + +-- this is probably not needed. You could just close the socket. +collectgarbage() +collectgarbage() -- finalizers will be scheduled for execution in the first pass, but will only execute in the second pass + + +local sock = socket.tcp() +assert(sock:bind("0.0.0.0", 27099)) +sock:settimeout(0) +sock:setoption("reuseaddr", true) +assert(sock:listen(0)) + +local methods = { + self = function( sock ) + local who = sock:receive( "*l" ) + luadev.RunOnSelf( sock:receive( "*a" ), who ) + system.FlashWindow() + end, + sv = function( sock ) + local who = sock:receive( "*l" ) + luadev.RunOnServer( sock:receive( "*a" ), who ) + system.FlashWindow() + end, + sh = function( sock ) + local who = sock:receive( "*l" ) + luadev.RunOnShared( sock:receive( "*a" ), who ) + system.FlashWindow() + end, + cl = function( sock ) + local who = sock:receive( "*l" ) + luadev.RunOnClients( sock:receive( "*a" ), who ) + system.FlashWindow() + end, + ent = function( sock ) + local who = sock:receive( "*l" ) + local contents = "ENT = {}; local ENT=ENT; " + .. sock:receive( "*a" ) + .. "; scripted_ents.Register(ENT, '" + .. who:sub( 0, -5 ) + .. "')" + luadev.RunOnShared( contents, who ) + system.FlashWindow() + end, + wep = function( sock ) + local who = sock:receive( "*l" ) + local contents = "SWEP = {}; local SWEP=SWEP; " + .. sock:receive( "*a" ) + .. "; weapons.Register(SWEP, '" + .. who:sub( 0, -5 ) + .. "')" + luadev.RunOnShared( contents, who ) + system.FlashWindow() + end, + client = function( sock ) + local who = sock:receive( "*l" ) + local to = sock:receive( "*l" ) + to = easylua + and easylua.FindEntity( to ) + or player.GetByID( tonumber( to ) ) + to = { to } + luadev.RunOnClient( sock:receive( "*a" ), to, who ) + system.FlashWindow() + end, + requestPlayers = function( sock ) + local plys = {} + for _, ply in next, player.GetAll() do + table.insert( plys, ply:Nick() ) + end + + sock:send( table.concat( plys, "\n" ) ) + end +} + +local function isLocalIP(parts) + if parts[1] == 127 then return true end + if parts[1] == 192 and parts[2] == 168 then return true end + if parts[1] == 172 and parts[2] >= 16 and parts[2] <= 31 then return true end + return false +end + +SOCKETDEV = vgui.Create("Panel") +SOCKETDEV:SetMouseInputEnabled(false) +SOCKETDEV:SetKeyBoardInputEnabled(false) +SOCKETDEV:SetSize(0, 0) +SOCKETDEV.Think = function() + local cl, a, b, c = sock:accept() + if cl then + local peername = cl:getpeername() + local ipParts = peername:Split('.') + for i = 1, #ipParts do ipParts[i] = tonumber(ipParts[i]) end + + if not isLocalIP(ipParts) then + print("Refused ", peername) + cl:shutdown() + return + end + + cl:settimeout(0) + + local protocol = cl:receive("*l") + local method + + if protocol == "extension" then + method = cl:receive("*l") + else + method = protocol + end + + if method and methods[method] then + methods[ method ]( cl ) + end + + cl:shutdown() + end +end diff --git a/garrysmod/addons/util-other/lua/autorun/1st_april.lua b/garrysmod/addons/util-other/lua/autorun/1st_april.lua new file mode 100644 index 0000000..fd3934d --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/1st_april.lua @@ -0,0 +1,59 @@ +if SERVER and os.date('%d%m') ~= '0104' then return end +-- copied from face poser "smile" preset +local flexes = { + [20] = 1, + [21] = 1, + [22] = 1, + [23] = 1, + [24] = 0, + [25] = 0, + [26] = 0, + [27] = 0.6, + [28] = 0.4, + [29] = 0, + [30] = 0, + [31] = 0, + [32] = 0, + [33] = 1, + [34] = 1, + [35] = 0, + [36] = 0, + [37] = 0, + [38] = 0, + [39] = 0, + [40] = 1, + [41] = 1, + [42] = 0, + [43] = 0, + [44] = 0, +} + +local function smile(ply, mul) + mul = mul or 1 + for k,v in pairs(flexes) do + ply:SetFlexWeight(k, v * mul) + end +end + +if SERVER then + hook.Add('dbg-test.complete', 'dbg-event.april1', function(ply) + smile(ply) + end) +end + +if CLIENT then + + local enabled = false + concommand.Add('dbg_smile', function() + enabled = not enabled + print(enabled and ':)' or ':|') + for _,v in ipairs(player.GetAll()) do + smile(v, enabled and 1 or 0) + end + end) + + hook.Add('octolib.newDormantState', 'dbg-event.april1', function(ply, st) + if not st then smile(ply, enabled and 1 or 0) end + end) + +end diff --git a/garrysmod/addons/util-other/lua/autorun/client/dbg_blockedmodels.lua b/garrysmod/addons/util-other/lua/autorun/client/dbg_blockedmodels.lua new file mode 100644 index 0000000..158d1c2 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/dbg_blockedmodels.lua @@ -0,0 +1,66 @@ +local frame +concommand.Add('dbg_blockedmodels', function() + if not LocalPlayer():query('DBG: Редактировать blacklist пропов') then return end + if IsValid(frame) then return frame:Remove() end + + local isBusy = false + + frame = vgui.Create 'DFrame' + frame:SetSize(800, 800) + frame:Center() + frame:MakePopup() + frame:SetTitle('Список заблокированных моделей') + + local icons = frame:Add('DIconLayout') + icons:Dock(FILL) + + local entry = octolib.textEntry(frame) + entry:Dock(TOP) + entry:DockMargin(5,5,5,10) + entry:SetTall(20) + entry:SetDrawLanguageID(false) + entry:SetValue('') + entry:SetPlaceholderText('Заблокировать модель') + entry.PaintOffset = 5 + + local iconRightClick + local function updateData() + netstream.Request('blockedModels.get'):Then(function(models) + icons:Clear() + for _, model in ipairs(models) do + local icon = icons:Add('SpawnIcon') + icon:SetSize(64, 64) + icon:SetModel(model) + icon.DoRightClick = iconRightClick + end + end) + end + + iconRightClick = function(self) + octolib.menu({ + {'Удалить', 'icon16/delete.png', function() + if isBusy then return end + + isBusy = true + netstream.Request('blockedModels.edit', self:GetModelName(), false):Then(function() + isBusy = false + updateData() + end) + end} + }):Open() + end + + function entry:OnEnter() + if isBusy then return end + + isBusy = true + netstream.Request('blockedModels.edit', entry:GetValue(), true):Then(function() + isBusy = false + updateData() + self:SetValue('') + end) + end + + updateData() + +end) diff --git a/garrysmod/addons/util-other/lua/autorun/client/dbg_physguns.lua b/garrysmod/addons/util-other/lua/autorun/client/dbg_physguns.lua new file mode 100644 index 0000000..0d1d65d --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/dbg_physguns.lua @@ -0,0 +1,51 @@ +hook.Add('Think', 'dbg-physguns', function() +hook.Remove('Think', 'dbg-physguns') + +octolib.vars.init('physgunColor', Color(0,161,255)) +local updatePhysgunColor = octolib.func.debounce(function(col) + netstream.Start('dbg-physguns.changeColor', col) +end, 0.5) +hook.Add('octolib.setVar', 'physgunColor', function(var, val) + if var == 'physgunColor' and val ~= LocalPlayer():GetNetVar('physgunColor', Color(0,161,255)) then + updatePhysgunColor(val) + end +end) + +local physgunTargets = {} +local vector_zero = Vector(0,0,0) + +local function getPhysgunColor(ply) + local col = ply:GetNetVar('physgunColor', {r = 0, g = 161, b = 255}) + return Color(col.r, col.g, col.b) +end + +hook.Add('DrawPhysgunBeam', 'dbg.suppressPhysgun', function(ply, physgun, enabled, target) + + ply:SetWeaponColor(enabled and not ply:GetNetVar('Invisible') and getPhysgunColor(ply):ToVector() or vector_zero) + + if IsValid(target) then + physgunTargets[ply] = target + end + + local wep = LocalPlayer():GetActiveWeapon() + if wep == physgun and octolib.vars.get('hideMyBeam') then return false end + if not IsValid(wep) or (wep:GetClass() ~= 'weapon_physgun' and wep:GetClass() ~= 'gmod_tool') then + return false + end +end) + +hook.Add('PreDrawHalos', 'AddPhysgunHalos', function() + if octolib.vars.get('hidePhysgunHalos') or table.Count(physgunTargets) < 1 then return end + + for k, v in pairs(physgunTargets) do + if IsValid(k) and IsValid(v) then + local size = math.random(1, 2) + local colr = k:GetWeaponColor():ToColor() + Color(math.random(0,88), math.random(0,88), math.random(0,88)) + halo.Add({v}, colr, size, size) + end + end + + physgunTargets = {} +end) + +end) diff --git a/garrysmod/addons/util-other/lua/autorun/client/dbg_spawnmenu.lua b/garrysmod/addons/util-other/lua/autorun/client/dbg_spawnmenu.lua new file mode 100644 index 0000000..18d0a72 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/dbg_spawnmenu.lua @@ -0,0 +1,22 @@ +local function clearTabs() + + if not IsValid(g_SpawnMenu) then return end + + local ply = LocalPlayer() + for k, v in pairs(g_SpawnMenu.CreateMenu.Items) do + if + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.npcs') and not serverguard.player:HasPermission(ply, 'DBG: SpawnNPC') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.entities') and not serverguard.player:HasPermission(ply, 'DBG: SpawnSENT') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.weapons') and not serverguard.player:HasPermission(ply, 'DBG: SpawnSWEP') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.vehicles') and not (serverguard.player:HasPermission(ply, 'DBG: SpawnVehicle') or temp[ply:SteamID()]) or + v.Tab:GetText() == 'simfphys' and not serverguard.player:HasPermission(ply, 'DBG: SpawnSimfphys') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.dupes') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.saves') + then + g_SpawnMenu.CreateMenu:CloseTab(v.Tab, true) + clearTabs() + end + end + +end +hook.Add('SpawnMenuOpen', 'dbg-spawnmenu', clearTabs) diff --git a/garrysmod/addons/util-other/lua/autorun/client/dbg_st.lua b/garrysmod/addons/util-other/lua/autorun/client/dbg_st.lua new file mode 100644 index 0000000..a2b2abb --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/dbg_st.lua @@ -0,0 +1,22 @@ +local function clearTabs() + + if not IsValid(g_SpawnMenu) then return end + + local ply = LocalPlayer() + for k, v in pairs(g_SpawnMenu.CreateMenu.Items) do + if + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.npcs') and not serverguard.player:HasPermission(ply, 'DBG: SpawnNPC') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.entities') and not serverguard.player:HasPermission(ply, 'DBG: SpawnSENT') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.weapons') and not serverguard.player:HasPermission(ply, 'DBG: SpawnSWEP') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.vehicles') and not serverguard.player:HasPermission(ply, 'DBG: SpawnVehicle') or + v.Tab:GetText() == 'simfphys' and not serverguard.player:HasPermission(ply, 'DBG: SpawnSimfphys') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.dupes') or + v.Tab:GetText() == language.GetPhrase('spawnmenu.category.saves') + then + g_SpawnMenu.CreateMenu:CloseTab(v.Tab, true) + clearTabs() + end + end + +end +hook.Add('SpawnMenuOpen', 'dbg-spawnmenu', clearTabs) diff --git a/garrysmod/addons/util-other/lua/autorun/client/dbg_undooverride.lua b/garrysmod/addons/util-other/lua/autorun/client/dbg_undooverride.lua new file mode 100644 index 0000000..57abc1e --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/dbg_undooverride.lua @@ -0,0 +1,68 @@ +local clientUndos = {} +local shouldUpdate = true +undo = undo or {} + +function undo.GetTable() + return clientUndos +end + +local function updateUI() + + local panel = controlpanel.Get('Undo') + if not IsValid(panel) then return end + + panel:ClearControls() + panel:AddControl('Header', {Description = '#spawnmenu.utilities.undo.help'}) + + local box = vgui.Create('DListView') + panel:AddPanel(box) + box:Dock(TOP) + box:SetTall(500) + box:AddColumn('1') + box:SetHideHeaders(true) + function box:OnRowSelected(_, pnl) + RunConsoleCommand('gmod_undonum', pnl.key) + end + + local limit = 100 + for _,v in ipairs(clientUndos) do + + local item = box:AddLine(tostring(v.name)) + item.key = tostring(v.key) + + limit = limit - 1 + if limit <= 0 then break end + + end + +end + +net.Receive('Undo_AddUndo', function() + local k = net.ReadInt(16) + local v = net.ReadString() + table.insert(clientUndos, 1, {key = k, name = v}) + shouldUpdate = true +end) + +net.Receive('Undo_Undone', function() + local key, newUndo = net.ReadInt(16), {} + for _,v in ipairs(clientUndos) do + if v.key ~= key then + newUndo[#newUndo+1] = v + end + end + clientUndos = newUndo + newUndo = nil + shouldUpdate = true +end) + +hook.Add('PostReloadToolsMenu', 'BuildUndoUI', function() + local undoPanel = controlpanel.Get('Undo') + if not IsValid(undoPanel) then return end + function undoPanel:Think() + if shouldUpdate then + timer.Simple(0, updateUI) + shouldUpdate = false + end + end +end) diff --git a/garrysmod/addons/util-other/lua/autorun/client/drp.lua b/garrysmod/addons/util-other/lua/autorun/client/drp.lua new file mode 100644 index 0000000..501d424 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/drp.lua @@ -0,0 +1,191 @@ + -- thanks gmod update +RunConsoleCommand('hud_draw_fixed_reticle', '0') +RunConsoleCommand('hud_quickinfo', '0') + +local plyMeta = FindMetaTable 'Player' +function plyMeta:isMedic() + local job = self:getJobTable() + return job and job.medic or false +end + +local hideHUDElements = { + CMapOverwview = true, + DarkRP_Hungermod = true, + CHudHealth = true, + CHudBattery = true, + CHudSuitPower = true, + CHudDamageIndicator = true, + CHudChat = true, +} + +hook.Add('HUDShouldDraw', 'HideDefaultDarkRPHud', function(name) + if hideHUDElements[name] then return false end +end) + +hook.Add('StartChat', 'crim-compat', function(t) + + netstream.Start('isTyping', true) + +end) + +hook.Add('FinishChat', 'crim-compat', function() + + netstream.Start('isTyping', false) + +end) + +local showhelp = CreateClientConVar('dbg_help_login', 1, true, false) +hook.Add('Think', 'dbg-help', function() + + hook.Remove('Think', 'dbg-help') + + if IsValid(DBG_TUTORIAL) then DBG_TUTORIAL:Remove() end + local f = vgui.Create 'DFrame' + DBG_TUTORIAL = f + + f:SetSize(350, 524) + f:DockPadding(0,24,0,0) + f:AlignTop(5) + f:AlignRight(5) + f:ShowCloseButton(false) + f:SetTitle(L.training) + f:SetVisible(showhelp:GetBool()) + + local w = f:Add 'DButton' + w:SetTall(18) + w:SetText(L.open_wiki) + w:SizeToContentsX(12) + w:AlignTop(3) + w:AlignRight(3) + function w:DoClick() + octoesc.OpenURL('https://wiki.octothorp.team') + end + + local html = f:Add 'DHTML' + html:Dock(FILL) + html:OpenURL('https://old.octothorp.team/dobrograd/help') + + local GM = GM or GAMEMODE + function GM:ShowHelp() + if IsValid(DBG_TUTORIAL) then + DBG_TUTORIAL:SetVisible(not DBG_TUTORIAL:IsVisible()) + end + end + +end) + +local function run() +if not GAMEMODE then return end +-- function GAMEMODE:GrabEarAnimation(ply) + +-- ply.ChatGestureWeight = ply.ChatGestureWeight || 0 + +-- if ply:IsPlayingTaunt() then return end + +-- if ply:IsTyping() and not ply:IsUsingTalkie() then +-- ply.ChatGestureWeight = math.Approach(ply.ChatGestureWeight, 1, FrameTime() * 5.0) +-- else +-- ply.ChatGestureWeight = math.Approach(ply.ChatGestureWeight, 0, FrameTime() * 5.0) +-- end + +-- if ply.ChatGestureWeight > 0 then + +-- ply:AnimRestartGesture(GESTURE_SLOT_VCD, ACT_GMOD_IN_CHAT, true) +-- ply:AnimSetGestureWeight(GESTURE_SLOT_VCD, ply.ChatGestureWeight) + +-- end + +-- end + +function GAMEMODE:CalcMainActivity( ply, velocity ) + + ply.CalcIdeal = ACT_MP_STAND_IDLE + ply.CalcSeqOverride = -1 + + self:HandlePlayerLanding( ply, velocity, ply.m_bWasOnGround ) + + if ( self:HandlePlayerNoClipping( ply, velocity ) || + self:HandlePlayerDriving( ply ) || + self:HandlePlayerVaulting( ply, velocity ) || + self:HandlePlayerJumping( ply, velocity ) || + self:HandlePlayerSwimming( ply, velocity ) || + self:HandlePlayerDucking( ply, velocity ) ) then + else + local len2d = velocity:Length2DSqr() + if ( len2d > 22500 ) then ply.CalcIdeal = ACT_MP_RUN elseif ( len2d > 0.25 ) then ply.CalcIdeal = ACT_MP_WALK end + end + + ply.m_bWasOnGround = ply:IsOnGround() + ply.m_bWasNoclipping = ( ply:GetMoveType() == MOVETYPE_NOCLIP && !ply:InVehicle() ) + + return ply.CalcIdeal, ply.CalcSeqOverride + +end + +hook.Add('UpdateAnimation', 'drp', function(ply, velocity, maxseqgroundspeed) + local len = velocity:Length() + local movement = 1.0 + + if ( len > 0.2 ) then + movement = ( len / maxseqgroundspeed ) + end + + local rate = math.min( movement, 2 ) + + -- if we're under water we want to constantly be swimming.. + if ( ply:WaterLevel() >= 2 ) then + rate = math.max( rate, 0.5 ) + elseif ( !ply:IsOnGround() && len >= 1000 ) then + rate = 0.1 + end + + ply:SetPlaybackRate( rate ) + + if ( ply:InVehicle() ) then + + local Vehicle = ply:GetVehicle() + + -- We only need to do this clientside.. + if ( CLIENT ) then + -- + -- This is used for the 'rollercoaster' arms + -- + local Velocity = Vehicle:GetVelocity() + local fwd = Vehicle:GetUp() + local dp = fwd:Dot( Vector( 0, 0, 1 ) ) + local dp2 = fwd:Dot( Velocity ) + + ply:SetPoseParameter( 'vertical_velocity', ( dp < 0 && dp || 0 ) + dp2 * 0.005 ) + + -- Pass the vehicles steer param down to the player + local steer = Vehicle:GetPoseParameter( 'vehicle_steer' ) + steer = steer * 2 - 1 -- convert from 0..1 to -1..1 + if ( Vehicle:GetClass() == 'prop_vehicle_prisoner_pod' ) then steer = 0 ply:SetPoseParameter( 'aim_yaw', math.NormalizeAngle( ply:GetAimVector():Angle().y - Vehicle:GetAngles().y - 90 ) ) end + ply:SetPoseParameter( 'vehicle_steer', steer ) + + end + + end + + -- GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) +end) +GAMEMODE.UpdateAnimation = octolib.func.zero + +function GAMEMODE:AdjustMouseSensitivity() + + local ply = LocalPlayer() + if ( !IsValid( ply ) ) then return -1 end + + local wep = ply:GetActiveWeapon() + if ( IsValid(wep) && wep.AdjustMouseSensitivity ) then + return wep:AdjustMouseSensitivity() + end + + return -1 + +end +GAMEMODE.DrawDeathNotice = octolib.func.zero +end +hook.Add('darkrp.loadModules', 'dobrograd', run) +run() diff --git a/garrysmod/addons/util-other/lua/autorun/client/hud.lua b/garrysmod/addons/util-other/lua/autorun/client/hud.lua new file mode 100644 index 0000000..9c4c2e6 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/hud.lua @@ -0,0 +1,141 @@ +local ply, W, H + +local colors = {} +colors.black = Color(0, 0, 0, 255) +colors.blue = Color(0, 0, 255, 255) +colors.brightred = Color(200, 30, 30, 255) +colors.darkred = Color(0, 0, 70, 100) +colors.darkblack = Color(0, 0, 0, 200) +colors.gray1 = Color(0, 0, 0, 155) +colors.gray2 = Color(51, 58, 51,100) +colors.red = Color(255, 0, 0, 255) +colors.white = Color(255, 255, 255, 255) +colors.white1 = Color(255, 255, 255, 200) + + +-- VOICE TALK ICON +local vcTexture = Material('octoteam/icons-glyph/microphone.png') +local vcRangeColors = { + [150000] = { Color(1 * 255, 0.7 * 255, 0.4 * 255, 150), 64}, + [500000] = { Color(1 * 255, 0.4 * 255, 0.4 * 255, 150), 76}, + [2250000] = { Color(0.5 * 255, 0.5 * 255, 1 * 255, 150), 80}, + [10000] = { Color(0.5 * 255, 0.5 * 255, 1 * 255, 150), 48}, +} +local function voiceChat() + if not ply.DRPIsTalking then return end + local data = vcRangeColors[ply:GetNetVar('TalkRange', 0)] or vcRangeColors[150000] + surface.SetMaterial(vcTexture) + surface.SetDrawColor(data[1]) + surface.DrawTexturedRectRotated(W / 2, H - 50, data[2], data[2], 0) +end + +-- LOCKDOWN +surface.CreateFont('dbg.lockdown.normal', { + font = 'Calibri', + extended = true, + size = 30, + weight = 350, +}) +surface.CreateFont('dbg.lockdown.normal-sh', { + font = 'Calibri', + extended = true, + size = 30, + weight = 350, + blursize = 3, +}) +local function lockdown() + if not netvars.GetNetVar('lockdown') then return end + local t = { + text = L.lockdown_started, + font = 'dbg.lockdown.normal-sh', + pos = {ScrW() / 2, ScrH() - 25}, + color = color_black, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + local clr = 220 + 35 * math.sin(CurTime() * 3) + draw.Text(t) + t.color = Color(clr,clr,clr) + t.font = 'dbg.lockdown.normal' + draw.Text(t) +end + +-- ARRESTED +local arrestedUntil +local function arrested() + if not arrestedUntil then return end + local ct = CurTime() + if ct >= arrestedUntil or not ply:GetNetVar('Arrested') then + arrestedUntil = nil + return + end + + draw.DrawNonParsedText(L.youre_arrested:format(octolib.time.formatIn(arrestedUntil - ct, true)), 'DarkRPHUD1', W / 2, H - H / 12, colors.white, 1) + +end +net.Receive('gotArrested', function() + arrestedUntil = CurTime() + net.ReadFloat() +end) + +-- ADMINTELL +surface.CreateFont('admintell.title', { + font = 'Calibri', + extended = true, + size = 72, + weight = 350, +}) + +surface.CreateFont('admintell.text', { + font = 'Calibri', + extended = true, + size = 28, + weight = 350, +}) + +local adminTellStart, adminTellTime, adminTellTxt, adminTellW, adminTellH +octolib.notify.registerType('admin', function(time, title, msg) + time, title, msg = time or 0, title or '', msg or '' + if not (time > 0 and (title ~= '' or msg ~= '')) then + adminTellTime = nil + return + end + adminTellStart = CurTime() + adminTellTime = time + + adminTellTxt = markup.Parse(('%s\n%s'):format(title or '', msg or ''), 750) + adminTellW, adminTellH = adminTellTxt:Size() + ply:EmitSound('weapons/fx/tink/shotgun_shell' .. math.random(1,3) .. '.wav', 75, 200) + +end) +local function adminTell() + if not adminTellTime then return end + local ct = CurTime() + if ct - adminTellStart > adminTellTime then + adminTellTime = nil + return + end + + draw.RoundedBox(4, (W - adminTellW) / 2 - 15, 10, adminTellW + 30, adminTellH + 15, colors.darkblack) + adminTellTxt:Draw(W / 2, 15, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + local pr = (ct - adminTellStart) / adminTellTime + draw.RoundedBoxEx(4, (W - adminTellW) / 2 - 15, adminTellH + 20, pr * (adminTellW + 30), 5, color_white, false, false, true) +end + +local function run() +if not GAMEMODE then return end + +GAMEMODE.DrawDeathNotice = octolib.func.zero + +function GAMEMODE:HUDPaint() + ply = ply and IsValid(ply) and ply or LocalPlayer() + W, H = ScrW(), ScrH() + + voiceChat() + lockdown() + arrested() + adminTell() +end +end +hook.Add('darkrp.loadModules', 'dbg-hud', run) +run() diff --git a/garrysmod/addons/util-other/lua/autorun/client/properties.lua b/garrysmod/addons/util-other/lua/autorun/client/properties.lua new file mode 100644 index 0000000..d6d1274 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/properties.lua @@ -0,0 +1,66 @@ +hook.Remove('PreDrawHalos', 'PropertiesHover') + +hook.Add('GUIMousePressed', 'PropertiesClick', function(code, vector) + local ply = LocalPlayer() + if not IsValid(ply) then return end + if not IsValid(vgui.GetHoveredPanel()) or not vgui.GetHoveredPanel():IsWorldClicker() then return end + + if code == MOUSE_RIGHT and not input.IsButtonDown(MOUSE_LEFT) then + properties.OnScreenClick(ply:GetShootPos(), vector) + end +end) + +local wasPressed = false +hook.Add('PreventScreenClicks', 'PropertiesPreventClicks', function() + + if not input.IsButtonDown( MOUSE_RIGHT) then wasPressed = false end + if wasPressed and input.IsButtonDown(MOUSE_RIGHT) and not input.IsButtonDown(MOUSE_LEFT) then return true end + if not IsValid(vgui.GetHoveredPanel()) or not vgui.GetHoveredPanel():IsWorldClicker() then return end + + local ply = LocalPlayer() + if not IsValid(ply) then return end + + -- + -- Are we pressing the right mouse button? + -- (We check whether we're pressing the left too, to allow for physgun freezes) + -- + if input.IsButtonDown(MOUSE_RIGHT) and not input.IsButtonDown(MOUSE_LEFT) then + -- + -- Are we hovering an entity? If so, then stomp the action + -- + local hovered = properties.GetHovered(ply:GetShootPos(), ply:GetAimVector()) + + if IsValid(hovered) then + wasPressed = true + return true + end + end + +end) + +local restrictedEnts = { + gmod_sent_vehicle_fphysics_base = true, + gmod_sent_vehicle_fphysics_wheel = true, +} + +hook.Add('CanProperty', 'dbg-tools', function(ply, name, ent) + if not IsValid(ent) then return end + + if ent:IsDoor() and name ~= 'collision' and name ~= 'bodygroups' and name ~= 'skin' then + return false + end + if ent:GetClass() == 'prop_effect' and name == 'collision' then + return false + end + if restrictedEnts[ent:GetClass()] and not (ply:query('DBG: Изменять автомобили') or name == 'skin' and ply:IsAdmin()) then + return false + end + if GAMEMODE.Config.allowedProperties[name] then + return true + end + if name == 'persist' and ply:IsSuperAdmin() then + return true + end + + return false +end) diff --git a/garrysmod/addons/util-other/lua/autorun/client/unban.lua b/garrysmod/addons/util-other/lua/autorun/client/unban.lua new file mode 100644 index 0000000..a1fa23d --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/unban.lua @@ -0,0 +1,162 @@ +local unbanConsentFrame + +local performLayout = function(self) + timer.Simple(0.2, function() + if IsValid(self) then + self:SizeToChildren(false, true) + end + end) +end +local sizeToY = function(self) + self:SizeToContentsY() +end + +local function panel(parent, name, desc) + + local pan = parent:Add 'DPanel' + pan:Dock(TOP) + pan:DockMargin(0,0,0,5) + pan:DockPadding(5,5,5,5) + pan.PerformLayout = performLayout + + if name then + local l = pan:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5,0,5,5) + l:SetTall(25) + l:SetFont('octolib.normal') + l:SetText(name) + pan.name = l + end + + if desc then + local l = pan:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetText(desc) + l:SetWrap(true) + l.PerformLayout = sizeToY + pan.desc = l + end + + return pan +end + +netstream.Hook('dbg-unban.consent', function(data) + if IsValid(unbanConsentFrame) then unbanConsentFrame:Close() end + + local height = 550 + local fr = vgui.Create 'DFrame' + fr:SetSize(500, 550) + fr:SetSizable(true) + function fr:OnSizeChanged(w, h) + if w ~= 500 then fr:SetWide(500) end + if h > height then fr:SetTall(height) end + end + fr:Center() + fr:SetTitle('Разбан игрока ' .. data.target) + fr:SetAlpha(0) + fr:MakePopup() + unbanConsentFrame = fr + + local scr = fr:Add('DScrollPanel') + scr:Dock(FILL) + + panel(scr, nil, 'Ты собираешься разбанить игрока. Пожалуйста, проверь всю информацию о нем, чтобы убедиться в отсутствии последствий') + local nick = panel(scr, 'Ник игрока', '') + steamworks.RequestPlayerInfo(util.SteamIDTo64(data.target), function(name) + nick.desc:SetText(name) + end) + panel(scr, 'SteamID', data.target) + + local profile = panel(scr, 'Профиль в Steam', 'https://steamcommunity.com/profiles/' .. util.SteamIDTo64(data.target)) + profile.desc:SetMouseInputEnabled(true) + profile.desc:SetCursor('hand') + profile.desc:SetColor(Color(0,130,255)) + profile.desc.DoClick = function(self) + octoesc.OpenURL(self:GetText()) + end + + + panel(scr, 'Блокировку выдал админ', data.admin) + if data.length == 0 then + panel(scr, 'БЛОКИРОВКА БЕССРОЧНАЯ!', 'Особенно внимательно просмотри связанные аккаунты') + else + panel(scr, 'Примерный срок блокировки', octolib.time.formatDuration(data.length)) + end + panel(scr, 'Причина', data.reason) + panel(scr, 'Примерное время в блокировке', octolib.time.formatDuration(data.spent)) + + local unbanReason, unbanBtn = panel(scr, 'Причина снятия блокировки', 'Пожалуйста, укажи причину снятия блокировки аккаунта') + data.unbanReason = data.unbanReason ~= '' and data.unbanReason or nil + local e = unbanReason:Add 'DTextEntry' + e:Dock(TOP) + e:DockMargin(5,5,5,0) + e:SetUpdateOnType(true) + e:SetValue(data.unbanReason or '') + e.OnValueChange = function(e) + local val = string.Trim(e:GetText()) + data.unbanReason = val ~= '' and val or nil + end + + local familyPan = panel(scr, 'Связанные аккаунты', not data.family[1] and 'Нет связанных аккаунтов' or 'ПКМ или двойной ЛКМ по строке откроет профиль игрока в Steam') + local lst = familyPan:Add('DListView') + lst:Dock(TOP) + lst:DockMargin(5,5,5,0) + lst:AddColumn('Ник в стиме') + lst:AddColumn('SteamID') + lst:SetMultiSelect(false) + for i, v in ipairs(data.family) do + if v ~= data.target then + local line = lst:AddLine('', v) + steamworks.RequestPlayerInfo(util.SteamIDTo64(v), function(name) + if IsValid(line) then line:SetColumnText(1, name) end + end) + end + end + function lst:OnRowRightClick(_, line) + octolib.menu({{'Открыть профиль', 'icon16/user_go.png', function() + octoesc.OpenURL('https://steamcommunity.com/profiles/' .. util.SteamIDTo64(line:GetColumnText(2))) + end}}):Open() + end + function lst:OnDoubleClick(_, line) + octoesc.OpenURL('https://steamcommunity.com/profiles/' .. util.SteamIDTo64(line:GetColumnText(2))) + end + lst:SetTall(lst:GetHeaderHeight() + lst:GetDataHeight() * #lst:GetLines()) + + panel(scr, nil, 'Нажми на кнопку ниже, чтобы выдать разбан. Об этом будет уведомлена старшая администрация\nЧтобы отменить снятие блокировки, закрой это окно') + + local sec = data.length == 0 and 30 or 15 + unbanBtn = octolib.button(scr, 'Снять блокировку (' .. sec .. ')', function() + if data.unbanReason == nil then return end + netstream.Start('dbg-unban.consent', data.target, data.unbanReason) + fr:Close() + end) + unbanBtn:SetFont('f4.normal') + unbanBtn:SetTall(45) + unbanBtn:SetEnabled(false) + timer.Create('serverguard.unban.confirm-think', 1, 0, function() + if not unbanBtn:IsValid() then return timer.Remove('serverguard.unban.confirm-think') end + if data.unbanReason ~= nil then + if sec == 0 then + unbanBtn:SetText('Снять блокировку') + unbanBtn:SetEnabled(true) + else + unbanBtn:SetText('Снять блокировку (' .. sec .. ')') + sec = sec - 1 + end + else + unbanBtn:SetText('Укажи причину снятия блокировки') + unbanBtn:SetEnabled(false) + end + end) + fr:InvalidateChildren(true) + timer.Simple(1, function() + local h = octolib.table.reduce(scr:GetCanvas():GetChildren(), function(ch, child) + return ch + child:GetTall() + select(2, child:GetDockMargin()) + select(4, child:GetDockMargin()) + end, 0) + if fr:IsValid() then fr:AlphaTo(255, 0.5) end + height = h + select(2, fr:GetDockPadding()) + select(4, fr:GetDockPadding()) + end) + +end) diff --git a/garrysmod/addons/util-other/lua/autorun/client/wire_soundbrowser.lua b/garrysmod/addons/util-other/lua/autorun/client/wire_soundbrowser.lua new file mode 100644 index 0000000..6c8a9e5 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/client/wire_soundbrowser.lua @@ -0,0 +1,2936 @@ +-- +-- I'm sorry, guys, but I really need this +-- will redo at some point later +-- + +local next = next +local pairs = pairs +local unpack = unpack +local pcall = pcall + +local temp = {} + +local functions = {} +function temp.TimedpairsGetTable() + return functions +end + +function temp.TimedpairsStop(name) + functions[name] = nil +end + +local function copy( t ) -- custom table copy function to convert to numerically indexed table + local ret = {} + for k,v in pairs( t ) do + ret[#ret+1] = { key = k, value = v } + end + return ret +end + +local function Timedpairs() + if not next(functions) then return end + + local toremove = {} + + for name, data in pairs( functions ) do + for i=1,data.step do + data.currentindex = data.currentindex + 1 -- increment index counter + local lookup = data.lookup or {} + if data.currentindex <= #lookup then -- If there are any more values.. + local kv = lookup[data.currentindex] or {} -- Get the current key and value + local ok, err = xpcall( data.callback, debug.traceback, kv.key, kv.value, unpack(data.args) ) -- DO EET + + if not ok then -- oh noes + temp.ErrorNoHalt( "Error in Timedpairs '" .. name .. "': " .. err ) + toremove[#toremove+1] = name + break + elseif err == false then -- They returned false inside the function + toremove[#toremove+1] = name + break + end + else -- Out of keys. Entire table looped + if data.endcallback then -- If we had any end callback function + local kv = lookup[data.currentindex-1] or {} -- get previous key & value + local ok, err = xpcall( data.endcallback, debug.traceback, kv.key, kv.value, unpack(data.args) ) + + if not ok then + temp.ErrorNoHalt( "Error in Timedpairs '" .. name .. "' (in end function): " .. err ) + end + end + toremove[#toremove+1] = name + break + end + end + end + + for i=1,#toremove do -- Remove all that were flagged for removal + functions[toremove[i]] = nil + end +end +if (CLIENT) then + hook.Add("PostRenderVGUI", "temp_Timedpairs", Timedpairs) // Doesn't get paused in single player. Can be important for vguis. +else + hook.Add("Think", "temp_Timedpairs", Timedpairs) // Servers still uses Think. +end + +function temp.Timedpairs(name,tab,step,callback,endcallback,...) + functions[name] = { lookup = copy(tab), step = step, currentindex = 0, callback = callback, endcallback = endcallback, args = {...} } +end + +function temp.Timedcall(callback,...) // calls the given function like simple timer, but isn't affected by game pausing. + local dummytab = {true} + temp.Timedpairs("Timedcall_"..tostring(dummytab),dummytab,1,function(k, v, ...) callback(...) end,nil,...) +end + +local PANEL = {} + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +local function GetFileName(name) + local name = string.Replace(name, ".txt", "") + return string.Replace(name, "/", "") +end + +local function InternalDoClick(self) + self:GetRoot():SetSelectedItem(self) + if (self:DoClick()) then return end + if (self:GetRoot():DoClick(self)) then return end +end + +local function InternalDoRightClick(self) + self:GetRoot():SetSelectedItem(self) + if (self:DoRightClick()) then return end + if (self:GetRoot():DoRightClick(self)) then return end +end + +local function fileName(filepath) + return string.match(filepath, "[/\\]?([^/\\]*)$") +end + +local string_find = string.find +local string_lower = string.lower +function PANEL:Search( str, foldername, fullpath, parentfullpath, first_recursion ) + if not self.SearchFolders[fullpath] then + self.SearchFolders[fullpath] = (self.SearchFolders[parentfullpath] or self.Folders):AddNode( foldername ) + + local files, folders = file.Find( fullpath .. "/*", "DATA" ) + + local node = self.SearchFolders[fullpath] + if fullpath == self.startfolder then self.Root = node end -- get root + node.Icon:SetImage( "icon16/arrow_refresh.png" ) + node:SetExpanded( true ) + + local myresults = 0 + for i=1,#files do + if string_find( string_lower( files[i] ), str, 1, true ) ~= nil then + local filenode = node:AddNode( files[i], "icon16/page_white.png" ) + filenode:SetFileName( fullpath .. "/" .. files[i] ) + myresults = myresults + 1 + end + + coroutine.yield() + end + + if #folders == 0 then + if myresults == 0 then + if node ~= self.Root then node:Remove() end + if first_recursion then + coroutine.yield( false, myresults ) + else + return false, myresults + end + end + + node.Icon:SetImage( "icon16/folder.png" ) + if first_recursion then + coroutine.yield( true, myresults ) + else + return true, myresults + end + else + for i=1,#folders do + local b, res = self:Search( str, folders[i], fullpath .. "/" .. folders[i], fullpath ) + if b then + myresults = myresults + res + end + + coroutine.yield() + end + + + if myresults > 0 then + node.Icon:SetImage( "icon16/folder.png" ) + if first_recursion then + coroutine.yield( true, myresults ) + else + return true, myresults + end + else + if node ~= self.Root then node:Remove() end + if first_recursion then + coroutine.yield( false, myresults ) + else + return false, myresults + end + end + end + end + + if first_recursion then + coroutine.yield( false, 0 ) + else + return false, 0 + end +end + +function PANEL:CheckSearchResults( status, bool, count ) + if bool ~= nil and count ~= nil then -- we're done searching + if count == 0 then + local node = self.Root:AddNode( "No results" ) + node.Icon:SetImage( "icon16/exclamation.png" ) + self.Root.Icon:SetImage( "icon16/folder.png" ) + end + timer.Remove( "wire_expression2_search" ) + return true + elseif not status then -- something went wrong, abort + timer.Remove( "wire_expression2_search" ) + return true + end +end + +function PANEL:StartSearch( str ) + self:UpdateFolders( true ) + + self.SearchFolders = {} + + local crt = coroutine.create( self.Search ) + local status, bool, count = coroutine.resume( crt, self, str, self.startfolder, self.startfolder, "", true ) + self:CheckSearchResults( status, bool, count ) + + timer.Create( "wire_expression2_search", 0, 0, function() + for i=1,100 do -- Load loads of files/folders at a time + local status, bool, count = coroutine.resume( crt ) + + if self:CheckSearchResults( status, bool, count ) then + return -- exit loop etc + end + end + end ) +end + +function PANEL:Init() + self:SetDrawBackground(false) + + self.SearchBox = vgui.Create( "DTextEntry", self ) + self.SearchBox:Dock( TOP ) + self.SearchBox:DockMargin( 0,0,0,0 ) + self.SearchBox:SetValue( "Search..." ) + + local clearsearch = vgui.Create( "DImageButton", self.SearchBox ) + clearsearch:SetMaterial( "icon16/cross.png" ) + local src = self.SearchBox + function clearsearch:DoClick() + src:SetValue( "" ) + src:OnEnter() + src:SetValue( "Search..." ) + end + clearsearch:DockMargin( 2,2,4,2 ) + clearsearch:Dock( RIGHT ) + clearsearch:SetSize( 14, 10 ) + clearsearch:SetVisible( false ) + + + local old = self.SearchBox.OnGetFocus + function self.SearchBox:OnGetFocus() + if self:GetValue() == "Search..." then -- If "Search...", erase it + self:SetValue( "" ) + end + old( self ) + end + + -- On lose focus + local old = self.SearchBox.OnLoseFocus + function self.SearchBox:OnLoseFocus() + if self:GetValue() == "" then -- if empty, reset "Search..." text + timer.Simple( 0, function() self:SetValue( "Search..." ) end ) + end + old( self ) + end + + function self.SearchBox.OnEnter() + local str = self.SearchBox:GetValue() + + if str ~= "" then + self:StartSearch( string.Replace( string.lower( str ), " ", "_" ) ) + + clearsearch:SetVisible( true ) + else + timer.Remove( "wire_expression2_search" ) + self:UpdateFolders() + clearsearch:SetVisible( false ) + end + end + + self.Update = vgui.Create("DButton", self) + self.Update:SetTall(20) + self.Update:Dock(BOTTOM) + self.Update:DockMargin(0, 0, 0, 0) + self.Update:SetText("Update") + self.Update.DoClick = function(button) + self:UpdateFolders() + end + + self.Folders = vgui.Create("DTree", self) + self.Folders:Dock(FILL) + self.Folders:DockMargin(0, 0, 0, 0) + + self.panelmenu = {} + self.filemenu = {} + self.foldermenu = {} + self.lastClick = CurTime() + + self:AddRightClick(self.filemenu, nil, "Open", function() + self:OnFileOpen(self.File:GetFileName(), false) + end) + self:AddRightClick(self.filemenu, nil, "Open in New Tab", function() + self:OnFileOpen(self.File:GetFileName(), true) + end) + self:AddRightClick(self.filemenu, nil, "*SPACER*") + self:AddRightClick(self.filemenu, nil, "Rename to..", function() + local fname = string.StripExtension(fileName(self.File:GetFileName())) + Derma_StringRequestNoBlur("Rename File \"" .. fname .. "\"", "Rename file " .. fname, fname, + function(strTextOut) + -- Renaming starts in the garrysmod folder now, in comparison to other commands that start in the data folder. + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) .. ".txt" + local newFileName = string.GetPathFromFilename(self.File:GetFileName()) .. "/" .. strTextOut + if file.Exists(newFileName, "DATA") then + WireLib.AddNotify("File already exists (" .. strTextOut .. ")", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + elseif not file.Rename(self.File:GetFileName(), newFileName) then + WireLib.AddNotify("Rename was not successful", NOTIFY_ERROR, 7, NOTIFYSOUND_ERROR1) + end + self:UpdateFolders() + end) + end) + self:AddRightClick(self.filemenu, nil, "Copy to..", function() + local fname = string.StripExtension(fileName(self.File:GetFileName())) + Derma_StringRequestNoBlur("Copy File \"" .. fname .. "\"", "Copy File to...", fname, + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(string.GetPathFromFilename(self.File:GetFileName()) .. "/" .. strTextOut .. ".txt", file.Read(self.File:GetFileName())) + self:UpdateFolders() + end) + end) + self:AddRightClick(self.filemenu, nil, "*SPACER*") + self:AddRightClick(self.filemenu, nil, "New File", function() + Derma_StringRequestNoBlur("New File in \"" .. string.GetPathFromFilename(self.File:GetFileName()) .. "\"", "Create new file", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(string.GetPathFromFilename(self.File:GetFileName()) .. "/" .. strTextOut .. ".txt", "") + self:UpdateFolders() + end) + end) + self:AddRightClick(self.filemenu, nil, "Delete", function() + Derma_Query("Delete this file?", "Delete", + "Delete", function() + if (file.Exists(self.File:GetFileName(), "DATA")) then + file.Delete(self.File:GetFileName()) + self:UpdateFolders() + end + end, + "Cancel") + end) + + self:AddRightClick(self.foldermenu, nil, "New File..", function() + Derma_StringRequestNoBlur("New File in \"" .. self.File:GetFolder() .. "\"", "Create new file", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(self.File:GetFolder() .. "/" .. strTextOut .. ".txt", "") + self:UpdateFolders() + end) + end) + self:AddRightClick(self.foldermenu, nil, "New Folder..", function() + Derma_StringRequestNoBlur("new folder in \"" .. self.File:GetFolder() .. "\"", "Create new folder", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.CreateDir(self.File:GetFolder() .. "/" .. strTextOut) + self:UpdateFolders() + end) + end) + self:AddRightClick(self.panelmenu, nil, "New File..", function() + Derma_StringRequestNoBlur("New File in \"" .. self.File:GetFolder() .. "\"", "Create new file", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.Write(self.File:GetFolder() .. "/" .. strTextOut .. ".txt", "") + self:UpdateFolders() + end) + end) + self:AddRightClick(self.panelmenu, nil, "New Folder..", function() + Derma_StringRequestNoBlur("new folder in \"" .. self.File:GetFolder() .. "\"", "Create new folder", "", + function(strTextOut) + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + file.CreateDir(self.File:GetFolder() .. "/" .. strTextOut) + self:UpdateFolders() + end) + end) +end + +function PANEL:OnFileOpen(filepath, newtab) + error("Please override wire_expression2_browser:OnFileOpen(filepath, newtab)", 0) +end + +function PANEL:UpdateFolders( empty ) + self.Folders:Clear(true) + if IsValid(self.Root) then + self.Root:Remove() + end + + if not empty then + self.Root = self.Folders.RootNode:AddFolder(self.startfolder, self.startfolder, "DATA", true) + self.Root:SetExpanded(true) + end + + self.Folders.DoClick = function(tree, node) + if self.File == node and CurTime() <= self.lastClick + 0.5 then + self:OnFileOpen(node:GetFileName()) + elseif self.OpenOnSingleClick then + self.OpenOnSingleClick:LoadFile(node:GetFileName()) + end + self.File = node + self.lastClick = CurTime() + return true + end + self.Folders.DoRightClick = function(tree, node) + self.File = node + if node:GetFileName() then + self:OpenMenu(self.filemenu) + else + self:OpenMenu(self.foldermenu) + end + return true + end + + self:OnFolderUpdate(self.startfolder) +end + +function PANEL:GetFileName() + if not IsValid(self.File) then return end + + return self.File:GetFileName() +end + +function PANEL:GetFileNode() + return self.File +end + +function PANEL:OpenMenu(menu) + if not menu or not IsValid(self.Folders) then return end + if #menu < 1 then return end + + self.Menu = vgui.Create("DMenu", self.Folders) + for k, v in pairs(menu) do + local name, option = v[1], v[2] + if (name == "*SPACER*") then + self.Menu:AddSpacer() + else + self.Menu:AddOption(name, option) + end + end + self.Menu:Open() +end + +function PANEL:AddRightClick(menu, pos, name, option) + if not menu then menu = {} end + if not pos then pos = #menu + 1 end + + if menu[pos] then + table.insert(menu, pos, { name, option }) + return + end + + menu[pos] = { name, option } +end + +function PANEL:RemoveRightClick(name) + for k, v in pairs(self.filemenu) do + if (v[1] == name) then + self.filemenu[k] = nil + break + end + end +end + + +function PANEL:Setup(folder) + self.startfolder = folder + self:UpdateFolders() +end + +function PANEL:OnFolderUpdate(folder) + -- override +end + +PANEL.Refresh = PANEL.UpdateFolders -- self:Refresh() is common + +vgui.Register("wire_expression2_browser", PANEL, "DPanel") + +local PANEL = {} + +AccessorFunc( PANEL, "m_strRootName", "RootName" ) // name of the root Root +AccessorFunc( PANEL, "m_strRootPath", "RootPath" ) // path of the root Root +AccessorFunc( PANEL, "m_strWildCard", "WildCard" ) // "GAME", "DATA" etc. +AccessorFunc( PANEL, "m_tFilter", "FileTyps" ) // "*.wav", "*.mdl", {"*.vmt", "*.vtf"} etc. + +AccessorFunc( PANEL, "m_strOpenPath", "OpenPath" ) // open path +AccessorFunc( PANEL, "m_strOpenFile", "OpenFile" ) // open path+file +AccessorFunc( PANEL, "m_strOpenFilename", "OpenFilename" ) // open file + +AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once +AccessorFunc( PANEL, "m_nMaxItemsPerPage", "MaxItemsPerPage" ) // how may items per page +AccessorFunc( PANEL, "m_nPage", "Page" ) // Page to show + +local invalid_chars = { + ["\n"] = "", + ["\r"] = "", + ["\\"] = "/", + ["//"] = "/", + //["/.svn"] = "", // Disallow access to .svn folders. (Not needed.) + //["/.git"] = "", // Disallow access to .git folders. (Not needed.) +} + +local function ConnectPathes(path1, path2) + local path = "" + + if (isstring(path1) and path1 ~= "") then + path = path1 + if (isstring(path2) and path2 ~= "") then + path = path1.."/"..path2 + end + else + if (isstring(path2) and path2 ~= "") then + path = path2 + end + end + + return path +end + +local function PathFilter(Folder, TxtPanel, Root) + if (!isstring(Folder) or Folder == "") then return end + + local ValidFolder = Folder + + //local ValidFolder = string.lower(Folder) // for .svn and .git filters. + for k, v in pairs(invalid_chars) do + for i = 1, #string.Explode(k, ValidFolder) do + if (!string.match(ValidFolder, k)) then break end + + ValidFolder = string.gsub(ValidFolder, k, v) + end + end + + ValidFolder = string.Trim(ValidFolder) + /*if (string.sub(ValidFolder, 0, 4) == ".svn") then // Disallow access to .svn folders. (Not needed.) + ValidFolder = string.sub(ValidFolder, -4) + if (ValidFolder == ".svn") then + ValidFolder = "" + end + end*/ + + /*if (string.sub(ValidFolder, 0, 4) == ".git") then // Disallow access to .git folders. (Not needed.) + ValidFolder = string.sub(ValidFolder, -4) + if (ValidFolder == ".git") then + ValidFolder = "" + end + end*/ + + ValidFolder = string.Trim(ValidFolder, "/") + + if (IsValid(TxtPanel)) then + TxtPanel:SetText(ValidFolder) + end + + local Dirs = #string.Explode("/", ValidFolder) + for i = 1, Dirs do + if (!file.IsDir(ConnectPathes(Root, ValidFolder), "GAME")) then + ValidFolder = string.GetPathFromFilename(ValidFolder) + ValidFolder = string.Trim(ValidFolder, "/") + end + end + + ValidFolder = string.Trim(ValidFolder, "/") + + if (ValidFolder == "") then return end + return ValidFolder +end + +local function EnableButton(button, bool) + button:SetEnabled(bool) + button:SetMouseInputEnabled(bool) +end + +local function BuildFileList(path, filter, wildcard) + local files = {} + + if (istable(filter)) then + for k, v in ipairs(filter) do + table.Add(files, file.Find(ConnectPathes(path, v), wildcard or "GAME")) + end + else + table.Add(files, file.Find(ConnectPathes(path, tostring(filter)), wildcard or "GAME")) + end + + table.sort(files) + + return files +end + +local function NavigateToFolder(self, path) + if (!IsValid(self)) then return end + + path = ConnectPathes(self.m_strRootPath, path) + + local root = self.Tree:Root() + if (!IsValid(root)) then return end + if (!IsValid(root.ChildNodes)) then return end + + local nodes = root.ChildNodes:GetChildren() + local lastnode = nil + + local nodename = "" + + self.NotUserPressed = true + local dirs = string.Explode("/", path) + for k, v in ipairs(dirs) do + if (nodename == "") then + nodename = string.lower(v) + else + nodename = nodename .. "/" .. string.lower(v) + if (!IsValid(lastnode)) then continue end + if (!IsValid(lastnode.ChildNodes)) then continue end + + nodes = lastnode.ChildNodes:GetChildren() + end + + local found = false + for _, node in pairs(nodes) do + if (!IsValid(node)) then continue end + + local path = string.lower(node.m_strFolder) + if ( nodename == "" ) then break end + + if ( path ~= nodename or found) then + node:SetExpanded(false) + continue + end + + if (k == #dirs) then // just select the last one + self.Tree:SetSelectedItem(node) + end + + node:SetExpanded(true) + lastnode = node + found = true + end + end + + self.NotUserPressed = false +end + +local function ShowFolder(self, path) + if (!IsValid(self)) then return end + + self.m_strOpenPath = path + path = ConnectPathes(self.m_strRootPath, path) + self.oldpage = nil + + self.Files = BuildFileList(path, self.m_tFilter, self.m_strWildCard) + + self.m_nPage = 0 + self.m_nPageCount = math.ceil(#self.Files / self.m_nMaxItemsPerPage) + + self.PageMode = self.m_nPageCount > 1 + self.PageChoosePanel:SetVisible(self.PageMode) + + if (self.m_nPageCount <= 0 or !self.PageMode) then + self.m_nPageCount = 1 + self:SetPage(1) + return + end + + self.PageChooseNumbers:Clear(true) + self.PageChooseNumbers.Buttons = {} + + for i=1, self.m_nPageCount do + self.PageChooseNumbers.Buttons[i] = self.PageChooseNumbers:Add("DButton") + local button = self.PageChooseNumbers.Buttons[i] + + button:SetWide(self.PageButtonSize) + button:Dock(LEFT) + button:SetText(tostring(i)) + button:SetVisible(false) + button:SetToolTip("Page " .. i .. " of " .. self.m_nPageCount) + + button.DoClick = function(panel) + self:SetPage(i) + self:LayoutPages(true) + end + end + + self:SetPage(1) +end + +--[[--------------------------------------------------------- + Name: Init +-----------------------------------------------------------]] +function PANEL:Init() + self.TimedpairsName = "wire_filebrowser_items_" .. tostring({}) + + self.PageButtonSize = 20 + + self:SetListSpeed(6) + self:SetMaxItemsPerPage(200) + + self.m_nPageCount = 1 + + self.m_strOpenPath = nil + self.m_strOpenFile = nil + self.m_strOpenFilename = nil + + self:SetDrawBackground(false) + + self.FolderPathPanel = self:Add("DPanel") + self.FolderPathPanel:DockMargin(0, 0, 0, 3) + self.FolderPathPanel:SetTall(20) + self.FolderPathPanel:Dock(TOP) + self.FolderPathPanel:SetDrawBackground(false) + + self.FolderPathText = self.FolderPathPanel:Add("DTextEntry") + self.FolderPathText:DockMargin(0, 0, 3, 0) + self.FolderPathText:Dock(FILL) + self.FolderPathText.OnEnter = function(panel) + self:SetOpenPath(panel:GetValue()) + end + + self.RefreshIcon = self.FolderPathPanel:Add("DImageButton") // The Folder Button. + self.RefreshIcon:SetImage("icon16/arrow_refresh.png") + self.RefreshIcon:SetWide(20) + self.RefreshIcon:Dock(RIGHT) + self.RefreshIcon:SetToolTip("Refresh") + self.RefreshIcon:SetStretchToFit(false) + self.RefreshIcon.DoClick = function() + self:Refresh() + end + + self.FolderPathIcon = self.FolderPathPanel:Add("DImageButton") // The Folder Button. + self.FolderPathIcon:SetImage("icon16/folder_explore.png") + self.FolderPathIcon:SetWide(20) + self.FolderPathIcon:Dock(RIGHT) + self.FolderPathIcon:SetToolTip("Open Folder") + self.FolderPathIcon:SetStretchToFit(false) + + self.FolderPathIcon.DoClick = function() + self.FolderPathText:OnEnter() + end + + self.NotUserPressed = false + self.Tree = vgui.Create( "DTree" ) + self.Tree:SetClickOnDragHover(false) + self.Tree.OnNodeSelected = function( parent, node ) + local path = node.m_strFolder + + if ( !path ) then return end + path = string.sub(path, #self.m_strRootPath+1) + path = string.Trim(path, "/") + + if (!self.NotUserPressed) then + self.FolderPathText:SetText(path) + end + + if (self.m_strOpenPath == path) then return end + ShowFolder(self, path) + end + + self.PagePanel = vgui.Create("DPanel") + self.PagePanel:SetDrawBackground(false) + + self.PageChoosePanel = self.PagePanel:Add("DPanel") + self.PageChoosePanel:DockMargin(0, 0, 0, 0) + self.PageChoosePanel:SetTall(self.PageButtonSize) + self.PageChoosePanel:Dock(BOTTOM) + self.PageChoosePanel:SetDrawBackground(false) + self.PageChoosePanel:SetVisible(false) + + self.PageLastLeftButton = self.PageChoosePanel:Add("DButton") + self.PageLastLeftButton:SetWide(self.PageButtonSize) + self.PageLastLeftButton:Dock(LEFT) + self.PageLastLeftButton:SetText("<<") + self.PageLastLeftButton.DoClick = function(panel) + self:SetPage(1) + end + + self.PageLastRightButton = self.PageChoosePanel:Add("DButton") + self.PageLastRightButton:SetWide(self.PageButtonSize) + self.PageLastRightButton:Dock(RIGHT) + self.PageLastRightButton:SetText(">>") + self.PageLastRightButton.DoClick = function(panel) + self:SetPage(self.m_nPageCount) + end + + self.PageLeftButton = self.PageChoosePanel:Add("DButton") + self.PageLeftButton:SetWide(self.PageButtonSize) + self.PageLeftButton:Dock(LEFT) + self.PageLeftButton:SetText("<") + self.PageLeftButton.DoClick = function(panel) + if (self.m_nPage <= 1 or !self.PageMode) then + self.m_nPage = 1 + return + end + + self:SetPage(self.m_nPage - 1) + end + + self.PageRightButton = self.PageChoosePanel:Add("DButton") + self.PageRightButton:SetWide(self.PageButtonSize) + self.PageRightButton:Dock(RIGHT) + self.PageRightButton:SetText(">") + self.PageRightButton.DoClick = function(panel) + if (self.m_nPage >= self.m_nPageCount or !self.PageMode) then + self.m_nPage = self.m_nPageCount + return + end + + self:SetPage(self.m_nPage + 1) + end + + self.PageChooseNumbers = self.PageChoosePanel:Add("DPanel") + self.PageChooseNumbers:DockMargin(0, 0, 0, 0) + self.PageChooseNumbers:SetSize(self.PageChoosePanel:GetWide()-60, self.PageChoosePanel:GetTall()) + self.PageChooseNumbers:Center() + self.PageChooseNumbers:SetDrawBackground(false) + + self.PageLoadingProgress = self.PagePanel:Add("DProgress") + self.PageLoadingProgress:DockMargin(0, 0, 0, 0) + self.PageLoadingProgress:SetTall(self.PageButtonSize) + self.PageLoadingProgress:Dock(BOTTOM) + self.PageLoadingProgress:SetVisible(false) + + self.PageLoadingLabel = self.PageLoadingProgress:Add("DLabel") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + self.PageLoadingLabel:SetText("") + self.PageLoadingLabel:SetPaintBackground(false) + self.PageLoadingLabel:SetDark(true) + + + self.List = self.PagePanel:Add( "DListView" ) + self.List:Dock( FILL ) + self.List:SetMultiSelect(false) + local Column = self.List:AddColumn("Name") + Column:SetMinWidth(150) + Column:SetWide(200) + + self.List.OnRowSelected = function(parent, id, line) + local name = line.m_strFilename + local path = line.m_strPath + local file = line.m_strFile + self.m_strOpenFilename = name + self.m_strOpenFile = file + + self:DoClick(file, path, name, parent, line) + end + + self.List.DoDoubleClick = function(parent, id, line) + local name = line.m_strFilename + local path = line.m_strPath + local file = line.m_strFile + self.m_strOpenFilename = name + self.m_strOpenFile = file + + self:DoDoubleClick(file, path, name, parent, line) + end + + self.List.OnRowRightClick = function(parent, id, line) + local name = line.m_strFilename + local path = line.m_strPath + local file = line.m_strFile + self.m_strOpenFilename = name + self.m_strOpenFile = file + + self:DoRightClick(file, path, name, parent, line) + end + + self.SplitPanel = self:Add( "DHorizontalDivider" ) + self.SplitPanel:Dock( FILL ) + self.SplitPanel:SetLeft(self.Tree) + self.SplitPanel:SetRight(self.PagePanel) + self.SplitPanel:SetLeftWidth(200) + self.SplitPanel:SetLeftMin(150) + self.SplitPanel:SetRightMin(300) + self.SplitPanel:SetDividerWidth(3) +end + +function PANEL:Refresh() + local file = self:GetOpenFile() + local page = self:GetPage() + + self.bSetup = self:Setup() + + self:SetOpenFile(file) + self:SetPage(page) +end + + +function PANEL:UpdatePageToolTips() + self.PageLeftButton:SetToolTip("Previous Page (" .. self.m_nPage - 1 .. " of " .. self.m_nPageCount .. ")") + self.PageRightButton:SetToolTip("Next Page (" .. self.m_nPage + 1 .. " of " .. self.m_nPageCount .. ")") + + self.PageLastRightButton:SetToolTip("Last Page (" .. self.m_nPageCount .. " of " .. self.m_nPageCount .. ")") + self.PageLastLeftButton:SetToolTip("First Page (1 of " .. self.m_nPageCount .. ")") +end + +function PANEL:LayoutPages(forcelayout) + if (!self.PageChoosePanel:IsVisible()) then + self.oldpage = nil + return + end + + local x, y = self.PageRightButton:GetPos() + local Wide = x - self.PageLeftButton:GetWide()-40 + if (Wide <= 0 or forcelayout) then + self.oldpage = nil + self:InvalidateLayout() + return + end + if (self.oldpage == self.m_nPage and self.oldpage and self.m_nPage) then return end + self.oldpage = self.m_nPage + + if (self.m_nPage >= self.m_nPageCount) then + EnableButton(self.PageLeftButton, true) + EnableButton(self.PageRightButton, false) + EnableButton(self.PageLastLeftButton, true) + EnableButton(self.PageLastRightButton, false) + elseif (self.m_nPage <= 1) then + EnableButton(self.PageLeftButton, false) + EnableButton(self.PageRightButton, true) + EnableButton(self.PageLastLeftButton, false) + EnableButton(self.PageLastRightButton, true) + else + EnableButton(self.PageLeftButton, true) + EnableButton(self.PageRightButton, true) + EnableButton(self.PageLastLeftButton, true) + EnableButton(self.PageLastRightButton, true) + end + + local ButtonCount = math.ceil(math.floor(Wide/self.PageButtonSize)/2) + local pagepos = math.Clamp(self.m_nPage, ButtonCount, self.m_nPageCount-ButtonCount+1) + + local VisibleButtons = 0 + for i=1, self.m_nPageCount do + local button = self.PageChooseNumbers.Buttons[i] + if (!IsValid(button)) then continue end + + if (pagepos < i+ButtonCount and pagepos >= i-ButtonCount+1) then + button:SetVisible(true) + EnableButton(button, true) + VisibleButtons = VisibleButtons + 1 + else + button:SetVisible(false) + EnableButton(button, false) + end + + button.Depressed = false + end + + local SelectButton = self.PageChooseNumbers.Buttons[self.m_nPage] + if (IsValid(SelectButton)) then + SelectButton.Depressed = true + SelectButton:SetMouseInputEnabled(false) + end + + self.PageChooseNumbers:SetWide(VisibleButtons*self.PageButtonSize) + self.PageChooseNumbers:Center() +end + +function PANEL:AddColumns(...) + local Column = {} + for k, v in ipairs({...}) do + Column[k] = self.List:AddColumn(v) + end + return Column +end + +function PANEL:Think() + if (self.SplitPanel:GetDragging()) then + self.oldpage = nil + self:InvalidateLayout() + end + + if ( !self.bSetup ) then + self.bSetup = self:Setup() + end +end + +function PANEL:PerformLayout() + self:LayoutPages() + self.Tree:InvalidateLayout() + self.List:InvalidateLayout() + + local minw = self:GetWide() - self.SplitPanel:GetRightMin() - self.SplitPanel:GetDividerWidth() + local oldminw = self.SplitPanel:GetLeftWidth(minw) + + if (oldminw > minw) then + self.SplitPanel:SetLeftWidth(minw) + end + + + //Fixes scrollbar glitches on resize + self.Tree:OnMouseWheeled(0) + self.List:OnMouseWheeled(0) + + if (!self.PageLoadingProgress:IsVisible()) then return end + + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() +end + +function PANEL:Setup() + if (!self.m_strRootName) then return false end + if (!self.m_strRootPath) then return false end + + temp.TimedpairsStop(self.TimedpairsName) + + self.m_strOpenPath = nil + self.m_strOpenFile = nil + self.m_strOpenFilename = nil + self.oldpage = nil + + self.Tree:Clear(true) + if (IsValid(self.Root)) then + self.Root:Remove() + end + self.Root = self.Tree.RootNode:AddFolder( self.m_strRootName, self.m_strRootPath, self.m_strWildCard or "GAME", false) + + return true +end + +function PANEL:SetOpenFilename(filename) + if(!isstring(filename)) then filename = "" end + + self.m_strOpenFilename = filename + self.m_strOpenFile = ConnectPathes(self.m_strOpenPath, self.m_strOpenFilename) +end + +function PANEL:SetOpenPath(path) + self.Root:SetExpanded(true) + + path = PathFilter(path, self.FolderPathText, self.m_strRootPath) or "" + if (self.m_strOpenPath == path) then return end + self.oldpage = nil + + NavigateToFolder(self, path) + self.m_strOpenPath = path + self.m_strOpenFile = ConnectPathes(self.m_strOpenPath, self.m_strOpenFilename) +end + +function PANEL:SetOpenFile(file) + if(!isstring(file)) then file = "" end + + self:SetOpenPath(string.GetPathFromFilename(file)) + self:SetOpenFilename(string.GetFileFromFilename("/" .. file)) +end + +function PANEL:SetPage(page) + if (page < 1) then return end + if (page > self.m_nPageCount) then return end + if (page == self.m_nPage) then return end + + temp.TimedpairsStop(self.TimedpairsName) + self.List:Clear(true) + + self.m_nPage = page + self:UpdatePageToolTips() + + local filepage + if(self.PageMode) then + filepage = {} + for i=1, self.m_nMaxItemsPerPage do + local index = i + self.m_nMaxItemsPerPage * (page - 1) + local value = self.Files[index] + if (!value) then break end + filepage[i] = value + end + else + filepage = self.Files + end + + local Fraction = 0 + local FileCount = #filepage + local ShowProgress = (FileCount > self.m_nListSpeed * 5) + + self.PageLoadingProgress:SetVisible(ShowProgress) + if (FileCount <= 0) then + self.PageLoadingProgress:SetVisible(false) + + return + end + + self.PageLoadingProgress:SetFraction(Fraction) + self.PageLoadingLabel:SetText("0 of " .. FileCount .. " files found.") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + + self:InvalidateLayout() + + temp.Timedpairs(self.TimedpairsName, filepage, self.m_nListSpeed, function(id, name, self) + if (!IsValid(self)) then return false end + if (!IsValid(self.List)) then return false end + + local file = ConnectPathes(self.m_strOpenPath, name) + local args, bcontinue, bbreak = self:LineData(id, file, self.m_strOpenPath, name) + + if (bcontinue) then return end // continue + if (bbreak) then return false end // break + + local line = self.List:AddLine(name, unpack(args or {})) + if (!IsValid(line)) then return end + + line.m_strPath = self.m_strOpenPath + line.m_strFilename = name + line.m_strFile = file + + if (self.m_strOpenFile == file) then + self.List:SelectItem(line) + end + + self:OnLineAdded(id, line, file, self.m_strOpenPath, name) + + Fraction = id / FileCount + + if (!IsValid(self.PageLoadingProgress)) then return end + if (!ShowProgress) then return end + + self.PageLoadingProgress:SetFraction(Fraction) + + self.PageLoadingLabel:SetText(id .. " of " .. FileCount .. " files found.") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + end, function(id, name, self) + if (!IsValid(self)) then return end + Fraction = 1 + + if (!IsValid(self.PageLoadingProgress)) then return end + if (!ShowProgress) then return end + + self.PageLoadingProgress:SetFraction(Fraction) + self.PageLoadingLabel:SetText(id .. " of " .. FileCount .. " files found.") + self.PageLoadingLabel:SizeToContents() + self.PageLoadingLabel:Center() + + self.PageLoadingProgress:SetVisible(false) + self:InvalidateLayout() + end, self) +end + +function PANEL:DoClick(file, path, name) + -- Override +end +function PANEL:DoDoubleClick(file, path, name) + -- Override +end +function PANEL:DoRightClick(file, path, name) + -- Override +end + +function PANEL:LineData(id, file, path, name) + return // to override +end + +function PANEL:OnLineAdded(id, line, file, path, name) + return // to override +end + +vgui.Register("wire_filebrowser", PANEL, "DPanel") + +// A sound property browsner. It helps to find all sounds which are defined in sound scripts or by sound.Add(). +// Made by Grocel. + +local PANEL = {} + +local max_char_count = 200 //Name length limit + +AccessorFunc( PANEL, "m_strSearchPattern", "SearchPattern" ) // Pattern to search for. +AccessorFunc( PANEL, "m_strSelectedSound", "SelectedSound" ) // Pattern to search for. +AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once +AccessorFunc( PANEL, "m_nMaxItems", "MaxItems" ) // how may items at maximum + +local function IsInString(strSource, strPattern) + if (!strPattern) then return true end + if (strPattern == "") then return true end + + strSource = string.lower(strSource) + strPattern = string.lower(strPattern) + + if string.find(strSource, strPattern, 0, true) then return true end + + return false +end + +local function GenerateList(self, strPattern) + if (!IsValid(self)) then return end + self:ClearList() + + local soundtable = sound.GetTable() or {} + local soundcount = #soundtable + self.SearchProgress:SetVisible(true) + if (soundcount <= 0) then + self.SearchProgress:SetVisible(false) + + return + end + + self.SearchProgress:SetFraction(0) + self.SearchProgressLabel:SetText("Searching... (0 %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + temp.Timedpairs(self.TimedpairsName, soundtable, self.m_nListSpeed, function(k, v, self) + if (!IsValid(self)) then return false end + if (!IsValid(self.SoundProperties)) then return false end + if (!IsValid(self.SearchProgress)) then return false end + + self.SearchProgress:SetFraction(k / soundcount) + self.SearchProgressLabel:SetText("Searching... ("..math.Round(k / soundcount * 100).." %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + if (self.TabfileCount >= self.m_nMaxItems) then + self.SearchProgress:SetFraction(1) + + self.SearchProgressLabel:SetText("Searching... (100 %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + self.SearchProgress:SetVisible(false) + self:InvalidateLayout() + + return false + end + + if (!IsInString(v, strPattern)) then return end + + self:AddItem(k, v) + + end, function(k, v, self) + if (!IsValid(self)) then return end + if (!IsValid(self.SoundProperties)) then return end + if (!IsValid(self.SearchProgress)) then return end + + self.SearchProgress:SetFraction(1) + + self.SearchProgressLabel:SetText("Searching... (100 %)") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + + self.SearchProgress:SetVisible(false) + self:InvalidateLayout() + end, self) +end + +function PANEL:Init() + self.TimedpairsName = "wire_soundpropertylist_items_" .. tostring({}) + + self:SetDrawBackground(false) + self:SetListSpeed(100) + self:SetMaxItems(400) + + self.SearchPanel = self:Add("DPanel") + self.SearchPanel:DockMargin(0, 0, 0, 3) + self.SearchPanel:SetTall(20) + self.SearchPanel:Dock(TOP) + self.SearchPanel:SetDrawBackground(false) + + self.SearchText = self.SearchPanel:Add("DTextEntry") + self.SearchText:DockMargin(0, 0, 3, 0) + self.SearchText:Dock(FILL) + self.SearchText.OnChange = function(panel) + self:SetSearchPattern(panel:GetValue()) + end + + self.RefreshIcon = self.SearchPanel:Add("DImageButton") // The Folder Button. + self.RefreshIcon:SetImage("icon16/arrow_refresh.png") + self.RefreshIcon:SetWide(20) + self.RefreshIcon:Dock(RIGHT) + self.RefreshIcon:SetToolTip("Refresh") + self.RefreshIcon:SetStretchToFit(false) + self.RefreshIcon.DoClick = function() + self:Refresh() + end + + self.SearchProgress = self:Add("DProgress") + self.SearchProgress:DockMargin(0, 0, 0, 0) + self.SearchProgress:SetTall(20) + self.SearchProgress:Dock(BOTTOM) + self.SearchProgress:SetVisible(false) + + self.SearchProgressLabel = self.SearchProgress:Add("DLabel") + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() + self.SearchProgressLabel:SetText("") + self.SearchProgressLabel:SetPaintBackground(false) + self.SearchProgressLabel:SetDark(true) + + + self.SoundProperties = self:Add("DListView") + self.SoundProperties:SetMultiSelect(false) + self.SoundProperties:Dock(FILL) + + local Column = self.SoundProperties:AddColumn("No.") + Column:SetFixedWidth(30) + Column:SetWide(30) + + local Column = self.SoundProperties:AddColumn("ID") + Column:SetFixedWidth(40) + Column:SetWide(40) + + self.SoundProperties:AddColumn("Name") + + self.SoundProperties.OnRowSelected = function(parent, id, line) + local name = line.m_strSoundname + local data = line.m_tabData + self.m_strSelectedSound = name + + self:DoClick(name, data, parent, line) + end + + self.SoundProperties.DoDoubleClick = function(parent, id, line) + local name = line.m_strSoundname + local data = line.m_tabData + self.m_strSelectedSound = name + + self:DoDoubleClick(name, data, parent, line) + end + + self.SoundProperties.OnRowRightClick = function(parent, id, line) + local name = line.m_strSoundname + local data = line.m_tabData + self.m_strSelectedSound = name + + self:DoRightClick(name, data, parent, line) + end + + self:Refresh() +end + +function PANEL:PerformLayout() + if (!self.SearchProgress:IsVisible()) then return end + + self.SearchProgressLabel:SizeToContents() + self.SearchProgressLabel:Center() +end + +function PANEL:ClearList() + temp.TimedpairsStop(self.TimedpairsName) + self.SoundProperties:Clear(true) + + self.TabfileCount = 0 +end + +function PANEL:AddItem(...) + local itemtable = {...} + local item = itemtable[2] + + if (!isstring(item) or item == "") then return end + if (self.TabfileCount > self.m_nMaxItems) then return end + if (#item > max_char_count) then return end + + local itemargs = {} + local i = 0 + + for k, v in ipairs(itemtable) do + if (k == 2) then continue end + + i = i + 1 + itemargs[i] = v + end + + local line = self.SoundProperties:AddLine(self.TabfileCount + 1, ...) + line.m_strSoundname = item + line.m_tabData = itemargs + + if (self.m_strSelectedSound == item) then + self.SoundProperties:SelectItem(line) + end + + self.TabfileCount = self.TabfileCount + 1 + return line +end + +function PANEL:SetSearchPattern(strPattern) + self.m_strSearchPattern = strPattern or "" + self:Refresh() +end + +function PANEL:SetSelectedSound(strSelectedSound) + self.m_strSelectedSound = strSelectedSound or "" + self:Refresh() +end + +function PANEL:Refresh() + GenerateList(self, self.m_strSearchPattern) +end + +function PANEL:DoClick(name, data, parent, line) + -- Override +end +function PANEL:DoDoubleClick(name, data, parent, line) + -- Override +end +function PANEL:DoRightClick(name, data, parent, line) + -- Override +end + +vgui.Register("wire_soundpropertylist", PANEL, "DPanel") + +// A list editor. It allows reading editing and saving lists as *.txt files. +// It uses wire_expression2_browser for it's file browser. +// The files have an easy structure for easy editing. Rows are separated by '\n' and columns by '|'. +// Made by Grocel. + +local PANEL = {} + +AccessorFunc( PANEL, "m_strRootPath", "RootPath" ) // path of the root Root +AccessorFunc( PANEL, "m_strList", "List" ) // List file +AccessorFunc( PANEL, "m_strFile", "File" ) // sounds listed in list files +AccessorFunc( PANEL, "m_bUnsaved", "Unsaved" ) // edited list file Saved? +AccessorFunc( PANEL, "m_strSelectedList", "SelectedList" ) // Selected list file + +AccessorFunc( PANEL, "m_nListSpeed", "ListSpeed" ) // how many items to list an once +AccessorFunc( PANEL, "m_nMaxItems", "MaxItems" ) // how may items at maximum + +local max_char_count = 200 //File length limit + +local invalid_filename_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["|"] = "", + ["\\"] = "", + ['"'] = "", + [" "] = "_", +} + +local invalid_chars = { + ["*"] = "", + ["?"] = "", + [">"] = "", + ["<"] = "", + ["\\"] = "", + ['"'] = "", +} + +local function ConnectPathes(path1, path2) + local path = "" + + if (isstring(path1) and path1 ~= "") then + path = path1 + if (isstring(path2) and path2 ~= "") then + path = path1.."/"..path2 + end + else + if (isstring(path2) and path2 ~= "") then + path = path2 + end + end + + return path +end + +//Parse the lines from a given file object +local function ReadLine(filedata) + if (!filedata) then return end + + local fileline = "" + local comment = false + local count = 0 + + for i=1, 32 do // skip 32 lines at maximum + local line = "" + local fileend = false + + for i=1, max_char_count+56 do // maximum chars per line + local byte = filedata:ReadByte() + fileend = !byte + + if (fileend) then break end // file end + local char = string.char(byte) + + if (invalid_chars[char]) then // replace invalid chars + char = invalid_chars[char] + end + + if (char == "\n") then break end // line end + line = line .. char + end + line = string.Trim(line) + + if (!fileend and line == "") then continue end + fileline = line + + break + end + + local linetable = string.Explode("|", fileline) or {} + + if (#linetable == 0) then return end + + for k, v in ipairs(linetable) do // cleanup + local line = linetable[k] + + if (k == 1) then + line = string.Trim(line, "/") + end + line = string.Trim(line) + + linetable[k] = line + end + + if (#linetable[1] == 0) then return end + + return linetable +end + +local function fileName(filepath) + return string.match(filepath, "[/\\]?([^/\\]*)$") +end + +local function SaveTo(self, func, ...) + if (!IsValid(self)) then return end + local args = {...} + + local path = self.FileBrowser:GetFileName() or self.m_strList or "" + + Derma_StringRequestNoBlur( + "Save to New File", + "", + string.sub(fileName(path), 0, -5), // remove .txt at the end + + function( strTextOut ) + if (!IsValid(self)) then return end + + strTextOut = string.gsub(strTextOut, ".", invalid_filename_chars) + if (strTextOut == "") then return end + + local filepath = string.GetPathFromFilename(path) + if (!filepath or filepath == "") then filepath = self.m_strRootPath.."/" end + + local saved = self:SaveList(filepath..strTextOut..".txt") + if (saved and func) then + func(self, unpack(args)) + end + end + ) + return true +end + +//Ask for override: Opens a confirmation if the file name is different box. +local function AsForOverride(self, func, filename, ...) + if (!IsValid(self)) then return end + + if (!func) then return end + if (filename == self.m_strList) then func(self, filename, ...) return end + if (!file.Exists(filename, "DATA")) then func(self, filename, ...) return end + + local args = {...} + + Derma_Query( + "Overwrite this file?", + "Save To", + "Overwrite", + + function() + if (!IsValid(self)) then return end + + func(self, filename, unpack(args)) + end, + + "Cancel" + ) +end + +//Ask for save: Opens a confirmation box. +local function AsForSave(self, func, ...) + if (!IsValid(self)) then return end + + if (!func) then return end + if (!self.m_bUnsaved) then func(self, ...) return end + + local args = {...} + + Derma_Query( "Would you like to save the changes?", + "Unsaved List!", + + "Yes", // Save and resume. + function() + if (!IsValid(self)) then return end + + if (!self.m_strList or self.m_strList == "") then + SaveTo(self, func, unpack(args)) + return + end + + local saved = self:SaveList(self.m_strList) + if (saved) then + func(self, unpack(args)) + end + end, + + "No", // Don't save and resume. + function() + if (!IsValid(self)) then return end + func(self, unpack(args)) + end, + + "Cancel" // Do nothing. + ) +end + + +function PANEL:Init() + self.TimedpairsName = "wire_listeditor_items_" .. tostring({}) + + self:SetDrawBackground(false) + + self:SetListSpeed(40) + self:SetMaxItems(512) + self:SetUnsaved(false) + + self.TabfileCount = 0 + self.Tabfile = {} + + self.ListsPanel = vgui.Create("DPanel") + self.ListsPanel:SetDrawBackground(false) + + self.FilesPanel = vgui.Create("DPanel") + self.FilesPanel:SetDrawBackground(false) + + self.FileBrowser = self.ListsPanel:Add("wire_expression2_browser") + self.FileBrowser:Dock(FILL) + self.FileBrowser.OnFileOpen = function(panel, listfile) + self:OpenList(listfile) + end + self.FileBrowser:RemoveRightClick("Open in New Tab") // we don't need tabs. + self.FileBrowser:AddRightClick(self.FileBrowser.filemenu,4,"Save To..", function() + self:SaveList(self.FileBrowser:GetFileName()) + end) + self.FileBrowser.Update:Remove() // it's replaced + + self.Files = self.FilesPanel:Add("DListView") + self.Files:SetMultiSelect(false) + self.Files:Dock(FILL) + + local Column = self.Files:AddColumn("No.") + Column:SetFixedWidth(30) + Column:SetWide(30) + + self.Files:AddColumn("Name") + + local Column = self.Files:AddColumn("Type") + Column:SetFixedWidth(70) + Column:SetWide(70) + + self.Files.OnRowSelected = function(parent, id, line) + local name = line.m_strFilename + local data = line.m_tabData + self.m_strFile = name + self.m_strSelectedList = self.m_strList + + self:DoClick(name, data, parent, line) + end + + self.Files.DoDoubleClick = function(parent, id, line) + local name = line.m_strFilename + local data = line.m_tabData + self.m_strFile = name + self.m_strSelectedList = self.m_strList + + self:DoDoubleClick(name, data, parent, line) + end + + self.Files.OnRowRightClick = function(parent, id, line) + local name = line.m_strFilename + local data = line.m_tabData + self.m_strFile = name + self.m_strSelectedList = self.m_strList + + self:DoRightClick(name, data, parent, line) + end + + self.ListTopPanel = self.FilesPanel:Add("DPanel") + self.ListTopPanel:SetDrawBackground(false) + self.ListTopPanel:Dock(TOP) + self.ListTopPanel:SetTall(20) + self.ListTopPanel:DockMargin(0, 0, 0, 3) + + self.SaveIcon = self.ListTopPanel:Add("DImageButton") + self.SaveIcon:SetImage("icon16/table_save.png") + self.SaveIcon:SetWide(20) + self.SaveIcon:Dock(LEFT) + self.SaveIcon:SetToolTip("Save list") + self.SaveIcon:SetStretchToFit(false) + self.SaveIcon:DockMargin(0, 0, 0, 0) + self.SaveIcon.DoClick = function() + if (!self.m_strList or self.m_strList == "") then + SaveTo(self) + return + end + + self:SaveList(self.m_strList) + end + + self.SaveToIcon = self.ListTopPanel:Add("DImageButton") + self.SaveToIcon:SetImage("icon16/disk.png") + self.SaveToIcon:SetWide(20) + self.SaveToIcon:Dock(LEFT) + self.SaveToIcon:SetToolTip("Save To..") + self.SaveToIcon:SetStretchToFit(false) + self.SaveToIcon:DockMargin(0, 0, 0, 0) + self.SaveToIcon.DoClick = function() + SaveTo(self) + end + + self.NewIcon = self.ListTopPanel:Add("DImageButton") + self.NewIcon:SetImage("icon16/table_add.png") + self.NewIcon:SetWide(20) + self.NewIcon:Dock(LEFT) + self.NewIcon:SetToolTip("New list") + self.NewIcon:SetStretchToFit(false) + self.NewIcon:DockMargin(10, 0, 0, 0) + self.NewIcon.DoClick = function() + self:ClearList() + end + + self.RefreshIcon = self.ListTopPanel:Add("DImageButton") + self.RefreshIcon:SetImage("icon16/arrow_refresh.png") + self.RefreshIcon:SetWide(20) + self.RefreshIcon:Dock(LEFT) + self.RefreshIcon:SetToolTip("Refresh and Reload") + self.RefreshIcon:SetStretchToFit(false) + self.RefreshIcon:DockMargin(0, 0, 0, 0) + self.RefreshIcon.DoClick = function() + self:Refresh() + end + + self.ListNameLabel = self.ListTopPanel:Add("DLabel") + self.ListNameLabel:SetText("") + self.ListNameLabel:SetWide(20) + self.ListNameLabel:Dock(FILL) + self.ListNameLabel:DockMargin(12, 0, 0, 0) + self.ListNameLabel:SetDark(true) + + self.SplitPanel = self:Add( "DHorizontalDivider" ) + self.SplitPanel:Dock( FILL ) + self.SplitPanel:SetLeft(self.ListsPanel) + self.SplitPanel:SetRight(self.FilesPanel) + self.SplitPanel:SetLeftWidth(200) + self.SplitPanel:SetLeftMin(150) + self.SplitPanel:SetRightMin(300) + self.SplitPanel:SetDividerWidth(3) + + self:SetRootPath("wirelists") +end + +function PANEL:PerformLayout() + local minw = self:GetWide() - self.SplitPanel:GetRightMin() - self.SplitPanel:GetDividerWidth() + local oldminw = self.SplitPanel:GetLeftWidth(minw) + + if (oldminw > minw) then + self.SplitPanel:SetLeftWidth(minw) + end + + //Fixes scrollbar glitches on resize + if (IsValid(self.FileBrowser.Folders)) then + self.FileBrowser.Folders:OnMouseWheeled(0) + end + self.Files:OnMouseWheeled(0) +end + +function PANEL:UpdateListNameLabel() + if (!IsValid(self.ListNameLabel)) then return end + + self.ListNameLabel:SetText((self.m_bUnsaved and "*" or "")..(self.m_strList or "")) +end + + +function PANEL:ClearList() + AsForSave(self, function(self) + self:SetList(nil) + self:SetUnsaved(false) + self.TabfileCount = 0 + + temp.TimedpairsStop(self.TimedpairsName) + self.Files:Clear(true) + self.Tabfile = {} + end) +end + +function PANEL:Setup() + if (!self.m_strRootPath) then return false end + self.m_strSelectedList = nil + self.m_strFile = nil + + self:ClearList() + + self.FileBrowser:Setup(self.m_strRootPath) + + return true +end + +function PANEL:Refresh() + self.FileBrowser:Refresh() + self:OpenList(self.m_strList) + + self:InvalidateLayout() +end + +function PANEL:Think() + if (self.SplitPanel:GetDragging()) then + self:InvalidateLayout() + end + + if ( !self.bSetup ) then + self.bSetup = self:Setup() + end +end + +function PANEL:AddItem(...) + local itemtable = {...} + local item = itemtable[1] + + if (!isstring(item) or item == "") then return end + if (self.TabfileCount > self.m_nMaxItems) then return end + if (#item > max_char_count) then return end + if (self.Tabfile[item]) then return end + + local itemargs = {} + local i = 0 + + for k, v in ipairs(itemtable) do + if (k == 1) then continue end + + i = i + 1 + itemargs[i] = v + end + self.Tabfile[item] = itemargs + + local line = self.Files:AddLine(self.TabfileCount + 1, ...) + line.m_strFilename = item + line.m_tabData = itemargs + + //if (self.m_strFile == item) then + if (self.m_strSelectedList == self.m_strList and self.m_strFile == item) then + self.Files:SelectItem(line) + end + + self.TabfileCount = self.TabfileCount + 1 + self:SetUnsaved(true) + return line +end + +function PANEL:ItemInList(item) + if (!item) then return false end + if (self.Tabfile[item]) then return true end + + return false +end + +function PANEL:RemoveItem(item) + if (!item) then return end + if (!self.Tabfile[item]) then return end + if (!self.Files.Lines) then return end + + for k, v in ipairs(self.Files.Lines) do + if (v.m_strFilename == item) then + self.Files:RemoveLine(v:GetID()) + self.Tabfile[item] = nil + self:SetUnsaved(true) + self.TabfileCount = self.TabfileCount - 1 + + break + end + end +end + +function PANEL:OpenList(strfile) + if (!strfile) then return end + if (strfile == "") then return end + + AsForSave(self, function(self, strfile) + local filedata = file.Open(strfile, "rb", "DATA") + if (!filedata) then return end + + temp.TimedpairsStop(self.TimedpairsName) + self.Files:Clear(true) + self.Tabfile = {} + self.TabfileCount = 0 + + local counttab={} + for i=1, self.m_nMaxItems do + counttab[i] = true + end + + temp.Timedpairs(self.TimedpairsName, counttab, self.m_nListSpeed, function(index, _, self, filedata) + if (!IsValid(self)) then + filedata:Close() + return false + end + + if (!IsValid(self.Files)) then + filedata:Close() + return false + end + + if (self.TabfileCount >= self.m_nMaxItems) then + filedata:Close() + self:SetUnsaved(false) + + return false + end + + local linetable = ReadLine(filedata) + if (!linetable) then // do not add to empty lines + filedata:Close() + self:SetUnsaved(false) + + return false + end + + self:AddItem(unpack(linetable)) + self:SetUnsaved(false) + end, function(index, _, self, filedata) + filedata:Close() + + if (!IsValid(self)) then return end + self:SetUnsaved(false) + end, self, filedata) + + self:SetUnsaved(false) + self:SetList(strfile) + end, strfile) +end + +function PANEL:SaveList(strfile) + if (!self.Tabfile) then return end + if (!strfile) then return end + if (strfile == "") then return end + + AsForOverride(self, function(self, strfile) + local filedata = file.Open(strfile, "w", "DATA") + if (!filedata) then + Derma_Query( "File could not be saved!", + "Error!", + "OK" + ) + + return + end + + for key, itemtable in SortedPairs(self.Tabfile) do + local item = key + for k, supitem in ipairs(itemtable) do + item = item.." | "..supitem + end + filedata:Write(item.."\n") + end + + filedata:Close() + + self:SetUnsaved(false) + self:SetList(strfile) + + self:Refresh() + end, strfile) +end + +function PANEL:SetRootPath(path) + self.m_strRootPath = path + + self.bSetup = self:Setup() +end + +function PANEL:SetUnsaved(bool) + self.m_bUnsaved = bool + + self:UpdateListNameLabel() +end + +function PANEL:SetList(listfile) + self.m_strList = listfile + + self:UpdateListNameLabel() +end + +function PANEL:DoClick(name, data, parent, line) + -- Override +end +function PANEL:DoDoubleClick(name, data, parent, line) + -- Override +end +function PANEL:DoRightClick(name, data, parent, line) + -- Override +end + +vgui.Register("wire_listeditor", PANEL, "DPanel") + + +local max_char_count = 200 //File length limit +local max_char_chat_count = 110 // chat has a ~128 char limit, varies depending on char wide. + +local Disabled_Gray = Color(140, 140, 140, 255) + +local SoundBrowserPanel = nil +local TabFileBrowser = nil +local TabSoundPropertyList = nil +local TabFavourites = nil +local SoundInfoTree = nil +local SoundInfoTreeRoot = nil + +local SoundObj = nil +local SoundObjNoEffect = nil + +local TranslateCHAN = { + [CHAN_REPLACE] = "CHAN_REPLACE", + [CHAN_AUTO] = "CHAN_AUTO", + [CHAN_WEAPON] = "CHAN_WEAPON", + [CHAN_VOICE] = "CHAN_VOICE", + [CHAN_ITEM] = "CHAN_ITEM", + [CHAN_BODY] = "CHAN_BODY", + [CHAN_STREAM] = "CHAN_STREAM", + [CHAN_STATIC] = "CHAN_STATIC", + [CHAN_VOICE2] = "CHAN_VOICE2", + [CHAN_VOICE_BASE] = "CHAN_VOICE_BASE", + [CHAN_USER_BASE] = "CHAN_USER_BASE" +} + +// Output the infos about the given sound. +local function GetFileInfos(strfile) + if (!isstring(strfile) or strfile == "") then return end + + local nsize = tonumber(file.Size("sound/" .. strfile, "GAME") or "-1") + local strformat = string.lower(string.GetExtensionFromFilename(strfile) or "n/a") + + return nsize, strformat +end + +local function FormatSize(nsize) + if (!nsize) then return end + + //Negative filessizes aren't Valid. + if (nsize < 0) then return end + + return nsize, string.NiceSize(nsize) +end + +local function FormatLength(nduration) + if (!nduration) then return end + + //Negative durations aren't Valid. + if (nduration < 0) then return end + + local nm = math.floor(nduration / 60) + local ns = math.floor(nduration % 60) + local nms = (nduration % 1) * 1000 + return nduration, (string.format("%01d", nm)..":"..string.format("%02d", ns).."."..string.format("%03d", nms)) +end + +local function GetInfoTable(strfile) + local nsize, strformat, nduration = GetFileInfos(strfile) + if (!nsize) then return end + + nduration = SoundDuration(strfile) //Get the duration for the info text only. + if(nduration) then + nduration = math.Round(nduration * 1000) / 1000 + end + local nduration, strduration = FormatLength(nduration, nsize) + local nsizeB, strsize = FormatSize(nsize) + + local T = {} + local tabproperty = sound.GetProperties(strfile) + + if (tabproperty) then + T = tabproperty + else + T.Path = strfile + T.Duration = {strduration or "n/a", nduration and nduration.." sec"} + T.Size = {strsize or "n/a", nsizeB and nsizeB.." Bytes"} + T.Format = strformat + end + + return T, !tabproperty +end + + +// Output the infos about the given sound. +local oldstrfile +local function GenerateInfoTree(strfile, backnode, count) + if(oldstrfile == strfile and strfile) then return end + oldstrfile = strfile + + local SoundData, IsFile = GetInfoTable(strfile) + + if (!IsValid(backnode)) then + if (IsValid(SoundInfoTreeRoot)) then + SoundInfoTreeRoot:Remove() + end + end + if(!SoundData) then return end + + local strcount = "" + if (count) then + strcount = " ("..count..")" + end + + if (IsFile) then + local index = "" + local node = nil + local mainnode = nil + local subnode = nil + + if (IsValid(backnode)) then + mainnode = backnode:AddNode("Sound File"..strcount, "icon16/sound.png") + else + mainnode = SoundInfoTree:AddNode("Sound File", "icon16/sound.png") + SoundInfoTreeRoot = mainnode + end + + + do + index = "Path" + node = mainnode:AddNode(index, "icon16/sound.png") + subnode = node:AddNode(SoundData[index], "icon16/page.png") + subnode.IsSoundNode = true + subnode.IsDataNode = true + end + do + index = "Duration" + node = mainnode:AddNode(index, "icon16/time.png") + for k, v in pairs(SoundData[index]) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + index = "Size" + node = mainnode:AddNode(index, "icon16/disk.png") + for k, v in pairs(SoundData[index]) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + index = "Format" + node = mainnode:AddNode(index, "icon16/page_white_key.png") + subnode = node:AddNode(SoundData[index], "icon16/page.png") + subnode.IsDataNode = true + end + else + local node = nil + local mainnode = nil + + if (IsValid(backnode)) then + mainnode = backnode:AddNode("Sound Property"..strcount, "icon16/table_gear.png") + else + mainnode = SoundInfoTree:AddNode("Sound Property", "icon16/table_gear.png") + SoundInfoTreeRoot = mainnode + end + + do + node = mainnode:AddNode("Name", "icon16/sound.png") + subnode = node:AddNode(SoundData["name"], "icon16/page.png") + subnode.IsSoundNode = true + subnode.IsDataNode = true + end + do + local tabchannel = SoundData["channel"] or 0 + if (istable(tabchannel)) then + node = mainnode:AddNode("Channel", "icon16/page_white_gear.png") + for k, v in pairs(tabchannel) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + subnode = node:AddNode(TranslateCHAN[v] or TranslateCHAN[CHAN_USER_BASE], "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Channel", "icon16/page_white_gear.png") + subnode = node:AddNode(tabchannel, "icon16/page.png") + subnode.IsDataNode = true + subnode = node:AddNode(TranslateCHAN[tabchannel] or TranslateCHAN[CHAN_USER_BASE], "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tablevel = SoundData["level"] or 0 + if (istable(tablevel)) then + node = mainnode:AddNode("Level", "icon16/page_white_gear.png") + for k, v in pairs(tablevel) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Level", "icon16/page_white_gear.png") + subnode = node:AddNode(tablevel, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tabpitch = SoundData["volume"] or 0 + if (istable(tabpitch)) then + node = mainnode:AddNode("Volume", "icon16/page_white_gear.png") + for k, v in pairs(tabpitch) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Volume", "icon16/page_white_gear.png") + subnode = node:AddNode(tabpitch, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tabpitch = SoundData["pitch"] or 0 + if (istable(tabpitch)) then + node = mainnode:AddNode("Pitch", "icon16/page_white_gear.png") + for k, v in pairs(tabpitch) do + subnode = node:AddNode(v, "icon16/page.png") + subnode.IsDataNode = true + end + else + node = mainnode:AddNode("Pitch", "icon16/page_white_gear.png") + subnode = node:AddNode(tabpitch, "icon16/page.png") + subnode.IsDataNode = true + end + end + do + local tabsound = SoundData["sound"] or "" + if (istable(tabsound)) then + node = mainnode:AddNode("Sounds", "icon16/table_multiple.png") + else + node = mainnode:AddNode("Sound", "icon16/table.png") + end + + node.SubData = tabsound + node.BackNode = mainnode + node.Expander.DoClick = function(self) + if (!IsValid(SoundInfoTree)) then return end + if (!IsValid(node)) then return end + + node:SetExpanded(false) + SoundInfoTree:SetSelectedItem(node) + end + node:AddNode("Dummy") + end + end + + if (IsValid(backnode)) then + return + end + + if (IsValid(SoundInfoTreeRoot)) then + SoundInfoTreeRoot:SetExpanded(true) + end +end + +// Set the volume of the sound. +local function SetSoundVolume(volume) + if(!SoundObj) then return end + + SoundObj:ChangeVolume(tonumber(volume) or 1, 0.1) +end + +// Set the pitch of the sound. +local function SetSoundPitch(pitch) + if(!SoundObj) then return end + + SoundObj:ChangePitch(tonumber(pitch) or 100, 0.1) +end + +// Play the given sound, if no sound is given then mute a playing sound. +local function PlaySound(file, volume, pitch) + if(SoundObj) then + SoundObj:Stop() + SoundObj = nil + end + + if (!file or file == "") then return end + + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + util.PrecacheSound(file) + + SoundObj = CreateSound(ply, file) + if(SoundObj) then + SoundObj:PlayEx(tonumber(volume) or 1, tonumber(pitch) or 100) + end +end + +// Play the given sound without effects, if no sound is given then mute a playing sound. +local function PlaySoundNoEffect(file) + if(SoundObjNoEffect) then + SoundObjNoEffect:Stop() + SoundObjNoEffect = nil + end + + if (!file or file == "") then return end + + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + util.PrecacheSound(file) + + SoundObjNoEffect = CreateSound(ply, file) + if(SoundObjNoEffect) then + SoundObjNoEffect:PlayEx(1, 100) + end +end + +local function SetupSoundemitter(strSound) + // Setup the Soundemitter stool with the soundpath. + RunConsoleCommand("wire_soundemitter_sound", strSound) + + // Pull out the soundemitter stool after setup. + spawnmenu.ActivateTool("wire_soundemitter") +end + +local function SetupClipboard(strSound) + // Copy the soundpath to Clipboard. + SetClipboardText(strSound) +end + +local function Sendmenu(strSound, SoundEmitter, nSoundVolume, nSoundPitch) // Open a sending and setup menu on right click on a sound file. + if (!isstring(strSound)) then return end + if (strSound == "") then return end + + local Menu = DermaMenu() + local MenuItem = nil + + if (SoundEmitter) then + + //Setup soundemitter + MenuItem = Menu:AddOption("Setup soundemitter", function() + SetupSoundemitter(strSound) + end) + MenuItem:SetImage("icon16/sound.png") + + //Setup soundemitter and close + MenuItem = Menu:AddOption("Setup soundemitter and close", function() + SetupSoundemitter(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/sound.png") + + //Copy to clipboard + MenuItem = Menu:AddOption("Copy to clipboard", function() + SetupClipboard(strSound) + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Copy to clipboard and close + MenuItem = Menu:AddOption("Copy to clipboard and close", function() + SetupClipboard(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/page_paste.png") + + else + + //Copy to clipboard + MenuItem = Menu:AddOption("Copy to clipboard", function() + SetupClipboard(strSound) + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Copy to clipboard and close + MenuItem = Menu:AddOption("Copy to clipboard and close", function() + SetupClipboard(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Setup soundemitter + MenuItem = Menu:AddOption("Setup soundemitter", function() + SetupSoundemitter(strSound) + end) + MenuItem:SetImage("icon16/sound.png") + + //Setup soundemitter and close + MenuItem = Menu:AddOption("Setup soundemitter and close", function() + SetupSoundemitter(strSound) + SoundBrowserPanel:Close() + end) + MenuItem:SetImage("icon16/sound.png") + + end + + Menu:AddSpacer() + + if (IsValid(TabFavourites)) then + // Add the soundpath to the favourites. + if (TabFavourites:ItemInList(strSound)) then + + //Remove from favourites + MenuItem = Menu:AddOption("Remove from favourites", function() + TabFavourites:RemoveItem(strSound) + end) + MenuItem:SetImage("icon16/bin_closed.png") + + else + + //Add to favourites + MenuItem = Menu:AddOption("Add to favourites", function() + TabFavourites:AddItem(strSound, sound.GetProperties(strSound) and "property" or "file") + end) + MenuItem:SetImage("icon16/star.png") + local max_item_count = TabFavourites:GetMaxItems() + local count = TabFavourites.TabfileCount + if (count >= max_item_count) then + MenuItem:SetTextColor(Disabled_Gray) // custom disabling + MenuItem.DoClick = function() end + + MenuItem:SetToolTip("The favourites list is Full! It can't hold more than "..max_item_count.." items!") + end + + end + end + + Menu:AddSpacer() + + //Print to console + MenuItem = Menu:AddOption("Print to console", function() + // Print the soundpath in the Console/HUD. + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + ply:PrintMessage( HUD_PRINTTALK, strSound) + end) + MenuItem:SetImage("icon16/monitor_go.png") + + //Print to Chat + MenuItem = Menu:AddOption("Print to Chat", function() + // Say the the soundpath. + RunConsoleCommand("say", strSound) + end) + MenuItem:SetImage("icon16/group_go.png") + + local len = #strSound + if (len > max_char_chat_count) then + MenuItem:SetTextColor(Disabled_Gray) // custom disabling + MenuItem.DoClick = function() end + + MenuItem:SetToolTip("The filepath ("..len.." chars) is too long to print in chat. It should be shorter than "..max_char_chat_count.." chars!") + end + + Menu:AddSpacer() + + //Play + MenuItem = Menu:AddOption("Play", function() + PlaySound(strSound, nSoundVolume, nSoundPitch, strtype) + PlaySoundNoEffect() + end) + MenuItem:SetImage("icon16/control_play.png") + + //Play without effects + MenuItem = Menu:AddOption("Play without effects", function() + PlaySound() + PlaySoundNoEffect(strSound, strtype) + end) + MenuItem:SetImage("icon16/control_play_blue.png") + + Menu:Open() +end + +local function Infomenu(parent, node, SoundEmitter, nSoundVolume, nSoundPitch) + if(!IsValid(node)) then return end + if(!node.IsDataNode) then return end + + local strNodeName = node:GetText() + local IsSoundNode = node.IsSoundNode + + if(IsSoundNode) then + Sendmenu(strNodeName, SoundEmitter, nSoundVolume, nSoundPitch) + return + end + + local Menu = DermaMenu() + + //Copy to clipboard + MenuItem = Menu:AddOption("Copy to clipboard", function() + SetupClipboard(strNodeName) + end) + MenuItem:SetImage("icon16/page_paste.png") + + //Print to console + MenuItem = Menu:AddOption("Print to console", function() + // Print the soundpath in the Console/HUD. + local ply = LocalPlayer() + if (!IsValid(ply)) then return end + + ply:PrintMessage( HUD_PRINTTALK, strNodeName) + end) + MenuItem:SetImage("icon16/monitor_go.png") + + //Print to Chat + MenuItem = Menu:AddOption("Print to Chat", function() + // Say the the soundpath. + RunConsoleCommand("say", strNodeName) + end) + MenuItem:SetImage("icon16/group_go.png") + + local len = #strNodeName + if (len > max_char_chat_count) then + MenuItem:SetTextColor(Disabled_Gray) // custom disabling + MenuItem.DoClick = function() end + + MenuItem:SetToolTip("The filepath ("..len.." chars) is too long to print in chat. It should be shorter than "..max_char_chat_count.." chars!") + end + + Menu:Open() +end + +// Save the file path. It should be cross session. +// It's used when opening the browser in the e2 editor. +local function SaveFilePath(panel, file) + if (!IsValid(panel)) then return end + if (panel.Soundemitter) then return end + + panel:SetCookie("wire_soundfile", file) +end + +// Open the Sound Browser. +local function CreateSoundBrowser(path, se) + local soundemitter = false + if (isstring(path) and path ~= "") then + soundemitter = true + + if (tonumber(se) ~= 1) then + soundemitter = false + end + end + + if (tonumber(se) == 1) then + soundemitter = true + end + + local strSound = "" + local nSoundVolume = 1 + local nSoundPitch = 100 + + if(IsValid(SoundBrowserPanel)) then SoundBrowserPanel:Remove() end + if(IsValid(TabFileBrowser)) then TabFileBrowser:Remove() end + if(IsValid(TabSoundPropertyList)) then TabSoundPropertyList:Remove() end + if(IsValid(TabFavourites)) then TabFavourites:Remove() end + if(IsValid(SoundInfoTree)) then SoundInfoTree:Remove() end + if(IsValid(SoundInfoTreeRoot)) then SoundInfoTreeRoot:Remove() end + + SoundBrowserPanel = vgui.Create("DFrame") // The main frame. + SoundBrowserPanel:SetPos(50,25) + SoundBrowserPanel:SetSize(750, 500) + + SoundBrowserPanel:SetMinWidth(700) + SoundBrowserPanel:SetMinHeight(400) + + SoundBrowserPanel:SetSizable(true) + SoundBrowserPanel:SetDeleteOnClose( false ) + SoundBrowserPanel:SetTitle("Sound Browser") + SoundBrowserPanel:SetVisible(false) + SoundBrowserPanel:SetCookieName( "wire_sound_browser" ) + + TabFileBrowser = vgui.Create("wire_filebrowser") // The file tree browser. + TabSoundPropertyList = vgui.Create("wire_soundpropertylist") // The sound property browser. + TabFavourites = vgui.Create("wire_listeditor") // The favourites manager. + + TabFileBrowser:SetListSpeed(6) + TabFileBrowser:SetMaxItemsPerPage(200) + + TabSoundPropertyList:SetListSpeed(100) + TabSoundPropertyList:SetMaxItems(400) + + TabFavourites:SetListSpeed(40) + TabFavourites:SetMaxItems(512) + + local BrowserTabs = vgui.Create("DPropertySheet") // The tabs. + BrowserTabs:DockMargin(5, 5, 5, 5) + BrowserTabs:AddSheet("File Browser", TabFileBrowser, "icon16/folder.png", false, false, "Browse your sound folder.") + BrowserTabs:AddSheet("Sound Property Browser", TabSoundPropertyList, "icon16/table_gear.png", false, false, "Browse the sound properties.") + BrowserTabs:AddSheet("Favourites", TabFavourites, "icon16/star.png", false, false, "View your favourites.") + + SoundInfoTree = vgui.Create("DTree") // The info tree. + SoundInfoTree:SetClickOnDragHover(false) + local oldClicktime = CurTime() + SoundInfoTree.DoClick = function( parent, node ) + if (!IsValid(parent)) then return end + if (!IsValid(node)) then return end + parent:SetSelectedItem(node) + + local Clicktime = CurTime() + if ((Clicktime - oldClicktime) > 0.3) then oldClicktime = Clicktime return end + oldClicktime = Clicktime + + if (!node.IsSoundNode) then return end + + local file = node:GetText() + PlaySound(file, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + end + SoundInfoTree.DoRightClick = function( parent, node ) + if (!IsValid(parent)) then return end + if (!IsValid(node)) then return end + + parent:SetSelectedItem(node) + Infomenu(parent, node, SoundEmitter, nSoundVolume, nSoundPitch) + end + + SoundInfoTree.OnNodeSelected = function( parent, node ) + if (!IsValid(parent)) then return end + if (!IsValid(node)) then return end + + local backnode = node.BackNode + if (!IsValid(node.BackNode)) then + node:SetExpanded(!node.m_bExpanded) + return + end + + local tabsound = node.SubData + if (!tabsound) then + node:SetExpanded(!node.m_bExpanded) + return + end + + node:SetExpanded(false) + node:Remove() + + if (istable(tabsound)) then + node = backnode:AddNode("Sounds", "icon16/table_multiple.png") + for k, v in pairs(tabsound) do + GenerateInfoTree(v, node, k) + end + else + node = backnode:AddNode("Sound", "icon16/table.png") + GenerateInfoTree(tabsound, node) + end + + node:SetExpanded(false) + parent:SetSelectedItem(node) + node:SetExpanded(!node.m_bExpanded) + end + + local SplitPanel = SoundBrowserPanel:Add( "DHorizontalDivider" ) + SplitPanel:Dock(FILL) + SplitPanel:SetLeft(BrowserTabs) + SplitPanel:SetRight(SoundInfoTree) + SplitPanel:SetLeftWidth(570) + SplitPanel:SetLeftMin(500) + SplitPanel:SetRightMin(150) + SplitPanel:SetDividerWidth(3) + + TabFileBrowser:SetRootName("sound") + TabFileBrowser:SetRootPath("sound") + TabFileBrowser:SetWildCard("GAME") + TabFileBrowser:SetFileTyps({"*.mp3","*.wav"}) + + //TabFileBrowser:AddColumns("Type", "Size", "Length") //getting the duration is very slow. + local Columns = TabFileBrowser:AddColumns("Format", "Size") + Columns[1]:SetFixedWidth(70) + Columns[1]:SetWide(70) + Columns[2]:SetFixedWidth(70) + Columns[2]:SetWide(70) + + TabFileBrowser.LineData = function(self, id, strfile, ...) + if (#strfile > max_char_count) then return nil, true end // skip and hide to long filenames. + + local nsize, strformat, nduration = GetFileInfos(strfile) + if (!nsize) then return end + + local nsizeB, strsize = FormatSize(nsize, nduration) + local nduration, strduration = FormatLength(nduration, nsize) + + //return {strformat, strsize or "n/a", strduration or "n/a"} //getting the duration is very slow. + return {strformat, strsize or "n/a"} + end + + TabFileBrowser.OnLineAdded = function(self, id, line, strfile, ...) + + end + + TabFileBrowser.DoClick = function(parent, file) + SaveFilePath(SoundBrowserPanel, file) + + strSound = file + GenerateInfoTree(file) + end + + TabFileBrowser.DoDoubleClick = function(parent, file) + PlaySound(file, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + SaveFilePath(SoundBrowserPanel, file) + + strSound = file + end + + TabFileBrowser.DoRightClick = function(parent, file) + Sendmenu(file, SoundBrowserPanel.Soundemitter, nSoundVolume, nSoundPitch) + SaveFilePath(SoundBrowserPanel, file) + + strSound = file + GenerateInfoTree(file) + end + + + TabSoundPropertyList.DoClick = function(parent, property) + SaveFilePath(SoundBrowserPanel, property) + + strSound = property + GenerateInfoTree(property) + end + + TabSoundPropertyList.DoDoubleClick = function(parent, property) + PlaySound(property, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + SaveFilePath(SoundBrowserPanel, property) + + strSound = property + end + + TabSoundPropertyList.DoRightClick = function(parent, property) + Sendmenu(property, SoundBrowserPanel.Soundemitter, nSoundVolume, nSoundPitch) + SaveFilePath(SoundBrowserPanel, property) + + strSound = property + GenerateInfoTree(property) + end + + file.CreateDir("soundlists") + TabFavourites:SetRootPath("soundlists") + + TabFavourites.DoClick = function(parent, item, data) + if(file.Exists("sound/"..item, "GAME")) then + TabFileBrowser:SetOpenFile(item) + end + + strSound = item + GenerateInfoTree(item) + end + + TabFavourites.DoDoubleClick = function(parent, item, data) + if(file.Exists("sound/"..item, "GAME")) then + TabFileBrowser:SetOpenFile(item) + end + + PlaySound(item, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + strSound = item + end + + TabFavourites.DoRightClick = function(parent, item, data) + if(file.Exists("sound/"..item, "GAME")) then + TabFileBrowser:SetOpenFile(item) + end + + Sendmenu(item, SoundBrowserPanel.Soundemitter, nSoundVolume, nSoundPitch) + strSound = item + GenerateInfoTree(item) + end + + local ControlPanel = SoundBrowserPanel:Add("DPanel") // The bottom part of the frame. + ControlPanel:DockMargin(0, 5, 0, 0) + ControlPanel:Dock(BOTTOM) + ControlPanel:SetTall(60) + ControlPanel:SetDrawBackground(false) + + local ButtonsPanel = ControlPanel:Add("DPanel") // The buttons. + ButtonsPanel:DockMargin(4, 0, 0, 0) + ButtonsPanel:Dock(RIGHT) + ButtonsPanel:SetWide(250) + ButtonsPanel:SetDrawBackground(false) + + local TunePanel = ControlPanel:Add("DPanel") // The effect Sliders. + TunePanel:DockMargin(0, 4, 0, 0) + TunePanel:Dock(LEFT) + TunePanel:SetWide(350) + TunePanel:SetDrawBackground(false) + + local TuneVolumeSlider = TunePanel:Add("DNumSlider") // The volume slider. + TuneVolumeSlider:DockMargin(2, 0, 0, 0) + TuneVolumeSlider:Dock(TOP) + TuneVolumeSlider:SetText("Volume") + TuneVolumeSlider:SetDecimals(0) + TuneVolumeSlider:SetMinMax(0, 100) + TuneVolumeSlider:SetValue(100) + TuneVolumeSlider.Label:SetWide(40) + TuneVolumeSlider.OnValueChanged = function(self, val) + nSoundVolume = val / 100 + SetSoundVolume(nSoundVolume) + end + + local TunePitchSlider = TunePanel:Add("DNumSlider") // The pitch slider. + TunePitchSlider:DockMargin(2, 0, 0, 0) + TunePitchSlider:Dock(BOTTOM) + TunePitchSlider:SetText("Pitch") + TunePitchSlider:SetDecimals(0) + TunePitchSlider:SetMinMax(0, 255) + TunePitchSlider:SetValue(100) + TunePitchSlider.Label:SetWide(40) + TunePitchSlider.OnValueChanged = function(self, val) + nSoundPitch = val + SetSoundPitch(nSoundPitch) + end + + local PlayStopPanel = ButtonsPanel:Add("DPanel") // Play and stop. + PlayStopPanel:DockMargin(0, 0, 0, 2) + PlayStopPanel:Dock(TOP) + PlayStopPanel:SetDrawBackground(false) + + local PlayButton = PlayStopPanel:Add("DButton") // The play button. + PlayButton:SetText("Play") + PlayButton:Dock(LEFT) + PlayButton:SetWide(PlayStopPanel:GetWide() / 2 - 2) + PlayButton.DoClick = function() + PlaySound(strSound, nSoundVolume, nSoundPitch) + PlaySoundNoEffect() + end + + local StopButton = PlayStopPanel:Add("DButton") // The stop button. + StopButton:SetText("Stop") + StopButton:Dock(RIGHT) + StopButton:SetWide(PlayButton:GetWide()) + StopButton.DoClick = function() + PlaySound() // Mute a playing sound by not giving a sound. + PlaySoundNoEffect() + end + + local SoundemitterButton = ButtonsPanel:Add("DButton") // The soundemitter button. Hidden in e2 mode. + SoundemitterButton:SetText("Send to soundemitter") + SoundemitterButton:DockMargin(0, 2, 0, 0) + SoundemitterButton:Dock(FILL) + SoundemitterButton:SetVisible(false) + SoundemitterButton.DoClick = function(btn) + SetupSoundemitter(strSound) + end + + local ClipboardButton = ButtonsPanel:Add("DButton") // The soundemitter button. Hidden in soundemitter mode. + ClipboardButton:SetText("Copy to clipboard") + ClipboardButton:DockMargin(0, 2, 0, 0) + ClipboardButton:Dock(FILL) + ClipboardButton:SetVisible(false) + ClipboardButton.DoClick = function(btn) + SetupClipboard(strSound) + end + + local oldw, oldh = SoundBrowserPanel:GetSize() + SoundBrowserPanel.PerformLayout = function(self, ...) + SoundemitterButton:SetVisible(self.Soundemitter) + ClipboardButton:SetVisible(!self.Soundemitter) + + local w = self:GetWide() + local rightw = SplitPanel:GetLeftWidth() + w - oldw + + if (rightw < SplitPanel:GetLeftMin()) then + rightw = SplitPanel:GetLeftMin() + end + SplitPanel:SetLeftWidth(rightw) + + local minw = w - SplitPanel:GetRightMin() + SplitPanel:GetDividerWidth() + if (SplitPanel:GetLeftWidth() > minw) then + SplitPanel:SetLeftWidth(minw) + end + + PlayStopPanel:SetTall(ControlPanel:GetTall() / 2 - 2) + PlayButton:SetWide(PlayStopPanel:GetWide() / 2 - 2) + StopButton:SetWide(PlayButton:GetWide()) + + if (self.Soundemitter) then + SoundemitterButton:SetTall(PlayStopPanel:GetTall() - 2) + else + ClipboardButton:SetTall(PlayStopPanel:GetTall() - 2) + end + + oldw, oldh = self:GetSize() + + DFrame.PerformLayout(self, ...) + end + + SoundBrowserPanel.OnClose = function() // Set effects back and mute when closing. + nSoundVolume = 1 + nSoundPitch = 100 + TuneVolumeSlider:SetValue(nSoundVolume * 100) + TunePitchSlider:SetValue(nSoundPitch) + vgui.GetWorldPanel():SetWorldClicker(false) -- Not allow the breakage of other addons installed. + PlaySound() + PlaySoundNoEffect() + end + + SoundBrowserPanel:InvalidateLayout(true) +end + +// Open the Sound Browser. +local function OpenSoundBrowser(pl, cmd, args) + local path = args[1] // nil or "" will put the browser in e2 mode else the soundemitter mode is applied. + local se = args[2] + + if (!IsValid(SoundBrowserPanel)) then + CreateSoundBrowser(path, se) + end + + SoundBrowserPanel:SetVisible(true) + SoundBrowserPanel:MakePopup() + SoundBrowserPanel:InvalidateLayout(true) + + vgui.GetWorldPanel():SetWorldClicker(true) + + if (!IsValid(TabFileBrowser)) then return end + + //Replaces the timer, doesn't get paused in singleplayer. + temp.Timedcall(function(SoundBrowserPanel, TabFileBrowser, path, se) + if (!IsValid(SoundBrowserPanel)) then return end + if (!IsValid(TabFileBrowser)) then return end + + local soundemitter = false + if (isstring(path) and path ~= "") then + soundemitter = true + end + + local soundemitter = false + if (isstring(path) and path ~= "") then + soundemitter = true + + if (tonumber(se) ~= 1) then + soundemitter = false + end + end + + if (tonumber(se) == 1) then + soundemitter = true + end + + SoundBrowserPanel.Soundemitter = soundemitter + SoundBrowserPanel:InvalidateLayout(true) + + if (!soundemitter) then + path = SoundBrowserPanel:GetCookie("wire_soundfile", "") // load last session + end + TabFileBrowser:SetOpenFile(path) + end, SoundBrowserPanel, TabFileBrowser, path, se) +end + +concommand.Add("wire_sound_browser_open", OpenSoundBrowser) \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/autorun/content-check.lua b/garrysmod/addons/util-other/lua/autorun/content-check.lua new file mode 100644 index 0000000..1905c07 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/content-check.lua @@ -0,0 +1,53 @@ +local unwantedAddons = octolib.array.toKeys({ + '2437373117', -- gta4 cars + '2429099714', -- gta4 cars - shared + '2468527592', -- gta4 cars - hq + '757604550', -- wos + '2143558752', -- xdreanims +}) + +if SERVER then + + local addonsText = { + 'Кажется, у тебя установлены аддоны, которые могут конфликтовать с нашими: вероятнее всего, некоторый контент будет отображаться неправильно', + 'Чтобы исправить эту проблему, отпишись от этих аддонов:', + } + + hook.Add('dbg-char.spawn', 'dbg.contentCheck', function(ply) + if ply.contentWarned then return end + ply.contentWarned = true + + timer.Simple(5, function() + if not IsValid(ply) then return end + netstream.Request(ply, 'dbg.contentCheck'):Then(function(res) + if not istable(res) then return end + if res.addons[1] then + for _, text in ipairs(addonsText) do + octochat.talkTo(ply, color_red, text) + end + for _, addonName in ipairs(res.addons) do + octochat.talkTo(ply, Color(250,250,200), '• ' .. addonName) + end + end + if res.lowpoly ~= 0 then + octochat.talkTo(ply, color_red, (res.addons[1] and 'Также, у' or 'У') .. 'станови качество моделей на "Высокое" в настройках графики игры, чтобы исправить отображение автомобилей. Эта настройка незначительно влияет на производительность') + end + end) + end) + end) + +else + netstream.Listen('dbg.contentCheck', function(reply) + local addons = engine.GetAddons() + local unwanted = {} + for _, v in ipairs(addons) do + if unwantedAddons[v.wsid] then + unwanted[#unwanted + 1] = ('%s (%s)'):format(v.title, v.wsid) + end + end + reply({ + addons = unwanted, + lowpoly = GetConVar('r_rootlod'):GetInt(), + }) + end) +end diff --git a/garrysmod/addons/util-other/lua/autorun/dbg_spawnlists.lua b/garrysmod/addons/util-other/lua/autorun/dbg_spawnlists.lua new file mode 100644 index 0000000..5f8011b --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/dbg_spawnlists.lua @@ -0,0 +1,85 @@ +if SERVER then + local function AddCSLuaFolder(path) + local files, folders = file.Find(path .. '/*', 'LUA') + for _, fileName in ipairs(files) do + if fileName:sub(-4) == '.lua' then + AddCSLuaFile(path .. '/' .. fileName) + end + end + + for _, folderName in ipairs(folders) do + AddCSLuaFolder(path .. '/' .. folderName) + end + end + AddCSLuaFolder('spawnlists') +else + hook.Add('PopulatePropMenu', 'dbg-props', function() + local curID = 8000 + + local packs = {} + local function includeFolder(target, parentID, path) + local categoryFile = path .. '/_category.lua' + local files, folders = file.Find(path .. '/*', 'LUA') + + local parentPack + if file.Exists(categoryFile, 'LUA') then + parentPack = include(categoryFile) + parentPack.id = curID + parentPack.parent = parentID + parentPack.packs = {} + parentPack.content = parentPack.content or {} + + target[#target + 1] = parentPack + + parentID = curID + curID = curID + 1 + end + + for _, fileName in ipairs(files) do + if fileName:sub(-4) == '.lua' and fileName ~= '_category.lua' then + local childPack = include(path .. '/' .. fileName) + childPack.id = curID + curID = curID + 1 + childPack.parent = parentID + childPack.content = childPack.content or {} + + local childTarget = parentPack and parentPack.packs or target + childTarget[#childTarget + 1] = childPack + end + end + + for _, folderName in ipairs(folders) do + includeFolder(parentPack and parentPack.packs or target, parentID, path .. '/' .. folderName) + end + end + includeFolder(packs, 0, 'spawnlists') + + local function addPack(pack) + local content = {} + for _, category in ipairs(pack.content or {}) do + -- add category header + content[#content + 1] = { + type = 'header', + text = category[1], + } + + -- add category models + for i = 2, #category do + local mdl = category[i] + content[#content + 1] = { + type = 'model', + model = mdl, + } + end + end + spawnmenu.AddPropCategory('dbg-props.build' .. pack.id, pack.name, content, pack.icon, pack.id, pack.parent) + + for _, childPack in ipairs(pack.packs or {}) do + addPack(childPack) + end + end + for _, pack in ipairs(packs) do + addPack(pack) + end + end) +end diff --git a/garrysmod/addons/util-other/lua/autorun/dbg_st.lua b/garrysmod/addons/util-other/lua/autorun/dbg_st.lua new file mode 100644 index 0000000..5d6004a --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/dbg_st.lua @@ -0,0 +1,9 @@ +local meta = FindMetaTable 'Player' + +local function isvip( self ) + + return self:query( L.permissions_dobrodey ) + +end +meta.IsVIP = isvip +meta.IsVip = isvip diff --git a/garrysmod/addons/util-other/lua/autorun/drp.lua b/garrysmod/addons/util-other/lua/autorun/drp.lua new file mode 100644 index 0000000..79d62c1 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/drp.lua @@ -0,0 +1,266 @@ +local footsteps = { + ['dirt'] = { + 'player/footsteps/dirt1.wav', + 'player/footsteps/dirt2.wav', + 'player/footsteps/dirt3.wav', + 'player/footsteps/dirt4.wav', + }, + ['concrete_block'] = { + 'player/footsteps/concrete1.wav', + 'player/footsteps/concrete2.wav', + 'player/footsteps/concrete3.wav', + 'player/footsteps/concrete4.wav', + }, + ['paper'] = { + 'physics/plaster/ceiling_tile_step1.wav', + 'physics/plaster/ceiling_tile_step2.wav', + 'physics/plaster/ceiling_tile_step3.wav', + 'physics/plaster/ceiling_tile_step4.wav', + }, + ['wood'] = { + 'player/footsteps/wood1.wav', + 'player/footsteps/wood2.wav', + 'player/footsteps/wood3.wav', + 'player/footsteps/wood4.wav', + }, + ['ice'] = { + 'physics/plaster/ceiling_tile_step1.wav', + 'physics/plaster/ceiling_tile_step2.wav', + 'physics/plaster/ceiling_tile_step3.wav', + 'physics/plaster/ceiling_tile_step4.wav', + }, + ['metal'] = { + 'player/footsteps/metal1.wav', + 'player/footsteps/metal2.wav', + 'player/footsteps/metal3.wav', + 'player/footsteps/metal4.wav', + }, + ['zombieflesh'] = { + 'physics/flesh/flesh_impact_hard1.wav', + 'physics/flesh/flesh_impact_hard2.wav', + 'physics/body/body_medium_impact_soft3.wav', + 'physics/body/body_medium_impact_soft4.wav', + }, + ['metal_bouncy'] = { + 'player/footsteps/metal1.wav', + 'player/footsteps/metal2.wav', + 'player/footsteps/metal3.wav', + 'player/footsteps/metal4.wav', + }, + ['rubber'] = { + 'player/footsteps/rubber.wav', + }, + ['slipperyslime'] = { + 'player/footsteps/mud1.wav', + 'player/footsteps/mud2.wav', + 'player/footsteps/mud3.wav', + 'player/footsteps/mud4.wav', + }, + ['glass'] = { + 'physics/glass/glass_sheet_step1.wav', + 'physics/glass/glass_sheet_step2.wav', + 'physics/glass/glass_sheet_step3.wav', + 'physics/glass/glass_sheet_step4.wav', + }, + ['gmod_ice'] = { + 'physics/plaster/ceiling_tile_step1.wav', + 'physics/plaster/ceiling_tile_step2.wav', + 'physics/plaster/ceiling_tile_step3.wav', + 'physics/plaster/ceiling_tile_step4.wav', + }, + ['gmod_bouncy'] = { + 'physics/flesh/flesh_impact_hard1.wav', + 'physics/flesh/flesh_impact_hard2.wav', + 'physics/body/body_medium_impact_soft3.wav', + 'physics/body/body_medium_impact_soft4.wav', + }, + ['fence'] = { + 'player/footsteps/chainlink1.wav', + 'player/footsteps/chainlink2.wav', + 'player/footsteps/chainlink3.wav', + 'player/footsteps/chainlink4.wav', + }, + ['grass'] = { + 'player/footsteps/grass1.wav', + 'player/footsteps/grass2.wav', + 'player/footsteps/grass3.wav', + 'player/footsteps/grass4.wav', + }, + ['gravel'] = { + 'player/footsteps/gravel1.wav', + 'player/footsteps/gravel2.wav', + 'player/footsteps/gravel2.wav', + 'player/footsteps/gravel3.wav', + }, + ['metal_grate'] = { + 'player/footsteps/metalgrate1.wav', + 'player/footsteps/metalgrate2.wav', + 'player/footsteps/metalgrate3.wav', + 'player/footsteps/metalgrate4.wav', + }, + ['sand'] = { + 'player/footsteps/sand1.wav', + 'player/footsteps/sand2.wav', + 'player/footsteps/sand3.wav', + 'player/footsteps/sand4.wav', + }, + ['water_slosh'] = { + 'player/footsteps/slosh1.wav', + 'player/footsteps/slosh2.wav', + 'player/footsteps/slosh3.wav', + 'player/footsteps/slosh4.wav', + }, + ['water_wade'] = { + 'player/footsteps/wade1.wav', + 'player/footsteps/wade2.wav', + 'player/footsteps/wade3.wav', + 'player/footsteps/wade4.wav', + 'player/footsteps/wade5.wav', + 'player/footsteps/wade6.wav', + 'player/footsteps/wade7.wav', + 'player/footsteps/wade8.wav', + }, + ['tile'] = { + 'player/footsteps/tile1.wav', + 'player/footsteps/tile2.wav', + 'player/footsteps/tile3.wav', + 'player/footsteps/tile4.wav', + }, + ['wood_panel'] = { + 'player/footsteps/woodpanel1.wav', + 'player/footsteps/woodpanel2.wav', + 'player/footsteps/woodpanel3.wav', + 'player/footsteps/woodpanel4.wav', + }, + ['duct_metal'] = { + 'player/footsteps/duct1.wav', + 'player/footsteps/duct2.wav', + 'player/footsteps/duct3.wav', + 'player/footsteps/duct4.wav', + }, + ['wood_box'] = { + 'physics/wood/wood_box_footstep1.wav', + 'physics/wood/wood_box_footstep2.wav', + 'physics/wood/wood_box_footstep3.wav', + 'physics/wood/wood_box_footstep4.wav', + }, + ['wood_box'] = { + 'physics/wood/ladder1.wav', + 'physics/wood/ladder2.wav', + 'physics/wood/ladder3.wav', + 'physics/wood/ladder4.wav', + }, +} + +hook.Add('PlayerFootstep', 'dbg-physfootsteps', function(ply) + local ent = ply:GetGroundEntity() + if IsValid(ent) and not ent:IsWorld() then + local physprop = ent:GetNetVar('physprop', nil) + if not physprop then return end + + local sound = footsteps[physprop] and table.Random(footsteps[physprop]) + if not sound then return end + + if SERVER then + ply:EmitSound(sound, 35) + end + + return true + end +end) + +-- player meta + +local plyMeta = FindMetaTable 'Player' +function plyMeta:isMedic() + local job = self:getJobTable() + return job and job.medic or false +end + +function plyMeta:isEMS() + if not IsValid(self) then return false end + if self:isCP() then return true end + if RPExtraTeams[self:Team()].ems then return true end + return false +end + +function plyMeta:isGov() + if not IsValid(self) then return false end + if self:isEMS() or DarkRP.isFirefighter(self) then return true end + return false +end + +DarkRP = DarkRP or {} + +function DarkRP.isAdmin(ply) + return ply:IsAdmin(), L.this_only_admin +end + +function DarkRP.isSuperAdmin(ply) + return ply:IsSuperAdmin() +end + +function DarkRP.isAdminTeam(ply) + return ply:Team() == TEAM_ADMIN +end + +function DarkRP.isMayor(ply) + return ply:isMayor() or false, L.can_do_only_mayor +end + +function DarkRP.isChief(ply) + return ply:isChief() or false, 'Это может делать только лейтенант полиции' +end + +function DarkRP.isCop(ply) + return ply:isCP() or false, L.this_only_police +end + +function DarkRP.isMedic(ply) + return ply:getJobTable().medic or false +end + +function DarkRP.isMech(ply) + return ply:getJobTable().mech or false +end + +function DarkRP.isFirefighter(ply) + return ply:Team() == TEAM_FIREFIGHTER +end + +function DarkRP.isGov(ply) + return ply:isGov() or false +end + +function DarkRP.isWorker(ply) + return ply:getJobTable().worker or false +end + +function DarkRP.isTaxist(ply) + return ply:Team() == TEAM_TAXI +end + +function DarkRP.freeze(ply, ent, callback) + local pos = ent:GetPos() + timer.Simple(1, function() + if ent:GetPos():DistToSqr(pos) > 1 then + if isfunction(callback) then + callback(false, L.unstable) + end + return + end + ent:GetPhysicsObject():EnableMotion(false) + ent.static = true + if isfunction(callback) then + callback(true, L.item_freezed) + end + hook.Run('PlayerFrozeObject', ply, ent, ent:GetPhysicsObject()) + end) +end + +function DarkRP.unfreeze(ply, ent) + local ph = ent:GetPhysicsObject() + ph:EnableMotion(true) + ent.static = nil + hook.Run('PlayerUnfrozeObject', ply, ent, ph) +end \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/autorun/playertest.lua b/garrysmod/addons/util-other/lua/autorun/playertest.lua new file mode 100644 index 0000000..2b0a4b1 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/playertest.lua @@ -0,0 +1,2 @@ +octolib.client('playertest/client') +octolib.server('playertest/server') diff --git a/garrysmod/addons/util-other/lua/autorun/server/dbg_blockedmodels.lua b/garrysmod/addons/util-other/lua/autorun/server/dbg_blockedmodels.lua new file mode 100644 index 0000000..4f5b3be --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/dbg_blockedmodels.lua @@ -0,0 +1,36 @@ +local blockedModels = {} +local function loadBlockedModels() + octolib.getDBVar('blockedModels'):Then(function(data) + blockedModels = octolib.array.toKeys(data) + end) +end +hook.Add('octolib.db.init', 'dbg.noIdiots.blockedModels', loadBlockedModels) +if octolib.db and octolib.db:status() == mysqloo.DATABASE_CONNECTED then + loadBlockedModels() +end + +local function saveBlockedModels() + octolib.setDBVar('blockedModels', nil, table.GetKeys(blockedModels)) +end + +netstream.Listen('blockedModels.edit', function(reply, ply, model, add) + if not ply:query('DBG: Редактировать blacklist пропов') then return reply() end + blockedModels[model] = add and true or nil + saveBlockedModels() + + reply() +end) + +netstream.Listen('blockedModels.get', function(reply, ply) + if not ply:query('DBG: Редактировать blacklist пропов') then return end + reply(table.GetKeys(blockedModels)) +end) + +local function canSpawn(ply, model) + if blockedModels[model] and not ply:query('DBG: Пропы из blacklist') then + ply:Notify('warning', 'Ты не можешь спаунить этот проп') + return false + end +end +hook.Add('PlayerSpawnProp', 'dbg.blacklist', canSpawn) +hook.Add('PlayerSpawnEffect', 'dbg.blacklist', canSpawn) diff --git a/garrysmod/addons/util-other/lua/autorun/server/dbg_blood.lua b/garrysmod/addons/util-other/lua/autorun/server/dbg_blood.lua new file mode 100644 index 0000000..d3e8a23 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/dbg_blood.lua @@ -0,0 +1,52 @@ +local bloodDecalMats = { + [BLOOD_COLOR_RED] = 'Blood', + [BLOOD_COLOR_YELLOW] = 'YellowBlood', + [BLOOD_COLOR_GREEN] = 'YellowBlood', + [BLOOD_COLOR_MECH] = 'ManhackSparks', + [BLOOD_COLOR_ANTLION] = 'YellowBlood', + [BLOOD_COLOR_ZOMBIE] = 'YellowBlood', + [BLOOD_COLOR_ANTLION_WORKER] = 'YellowBlood' +} + +local function applyBloodOverride(ent) + if ent:GetBloodColor() == DONT_BLEED then return end + + ent.overrideBloodColor = ent:GetBloodColor() + ent:SetBloodColor(DONT_BLEED) +end +hook.Add('PlayerSpawn', 'dbg-blood', applyBloodOverride) + +hook.Add('PostEntityTakeDamage', 'dbg-blood', function(ent, dmg) + if not dmg:IsBulletDamage() or dmg:GetDamage() < 5 + or not (ent:IsPlayer() or ent:IsNPC() or ent:IsNextBot()) then + return + end + + applyBloodOverride(ent) + + local bloodColor = ent.overrideBloodColor + if not bloodColor then return end + + local hitPos = dmg:GetDamagePosition() + local hitDir = dmg:GetDamageForce():GetNormalized() + VectorRand() * 0.2 + + local effectData = EffectData() + effectData:SetOrigin(hitPos) + effectData:SetColor(bloodColor) + util.Effect('BloodImpact', effectData) + + local decalMat = bloodDecalMats[bloodColor] or 'Blood' + if bloodColor == BLOOD_COLOR_MECH or not decalMat then return end + + local decalTargetPos = hitPos + (hitDir * math.random(35, 150)) + if not util.TraceLine({ + start = hitPos, + endpos = decalTargetPos, + filter = ent, + }).Hit then + hitPos = Vector(decalTargetPos) + decalTargetPos:Add(Vector(0, 0, -80)) + end + + util.Decal(decalMat, hitPos, decalTargetPos, ent) +end) \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/autorun/server/dbg_physguns.lua b/garrysmod/addons/util-other/lua/autorun/server/dbg_physguns.lua new file mode 100644 index 0000000..0a687e0 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/dbg_physguns.lua @@ -0,0 +1,35 @@ +local pmeta = FindMetaTable 'Player' +function pmeta:ChangePhysgunColor(col) + if not IsColor(col) then return end + + self.changePGCol = self.changePGCol or octolib.func.debounceStart(function(col) + if not IsColor(col) then return end + col.a = 255 + if col.r == 0 and col.g == 0 and col.b == 0 then + col = Color(1,1,1) -- tried to make an invisible beam + end + if IsValid(self) then + if not self:GetNetVar('os_build') then + return self:Notify('warning', 'Смена цвета физгана доступна только Строителям') + end + self:SetNetVar('physgunColor', col) + end + end, 0.5) + + self.changePGCol(col) + +end + +netstream.Hook('dbg-physguns.changeColor', pmeta.ChangePhysgunColor) +hook.Add('dbg-char.spawn', 'dbg-physguns', function(ply) + timer.Simple(3, function() + if not IsValid(ply) then return end + if not ply:GetNetVar('os_build') then return end + ply:GetClientVar({'physgunColor'}, function(vars) + if not istable(vars.physgunColor) then return end + local col = Color(vars.physgunColor.r or 0, vars.physgunColor.g or 161, vars.physgunColor.b or 255) + if col == Color(0,161,255) then return end + ply:ChangePhysgunColor(col) + end) + end) +end) diff --git a/garrysmod/addons/util-other/lua/autorun/server/dbg_prop_effect.lua b/garrysmod/addons/util-other/lua/autorun/server/dbg_prop_effect.lua new file mode 100644 index 0000000..772551d --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/dbg_prop_effect.lua @@ -0,0 +1,4 @@ +hook.Add('PlayerSpawnedEffect', 'dbg.noCollideForEffects', function(ply, mdl, ent) + if not IsValid(ent) then return end + ent:SetCollisionGroup(COLLISION_GROUP_WORLD) +end) diff --git a/garrysmod/addons/util-other/lua/autorun/server/dbg_spawnlog.lua b/garrysmod/addons/util-other/lua/autorun/server/dbg_spawnlog.lua new file mode 100644 index 0000000..25bf6df --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/dbg_spawnlog.lua @@ -0,0 +1,26 @@ +hook.Add('octolib.db.init', 'dbg-spawnlog', function() + octolib.db:RunQuery([[CREATE TABLE IF NOT EXISTS spawn_log ( + model VARCHAR(255) NOT NULL, + spawned INT NOT NULL DEFAULT 1, + PRIMARY KEY (model) + )]]) +end) + +local cache = {} +timer.Create('dbg-spawnlog.flush', 5, 0, function() + if not table.IsEmpty(cache) then + local str = table.concat(octolib.table.mapSequential(cache, function(amount, mdl) + return '(\'' .. octolib.db:escape(mdl) .. '\',' .. amount .. ')' + end), ',') + octolib.db:RunQuery([[INSERT INTO spawn_log (model, spawned) VALUES ]] .. str .. [[ ON DUPLICATE KEY UPDATE spawned = spawned + VALUES(spawned)]]) + table.Empty(cache) + end +end) + +local function updateCount(_, mdl) + cache[mdl] = (cache[mdl] or 0) + 1 +end + +hook.Add('PlayerSpawnedProp', 'dbg-spawnlog', updateCount) +hook.Add('PlayerSpawnedEffect', 'dbg-spawnlog', updateCount) +hook.Add('PlayerSpawnedRagdoll', 'dbg-spawnlog', updateCount) diff --git a/garrysmod/addons/util-other/lua/autorun/server/dbg_unstuck.lua b/garrysmod/addons/util-other/lua/autorun/server/dbg_unstuck.lua new file mode 100644 index 0000000..3076fa3 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/dbg_unstuck.lua @@ -0,0 +1,21 @@ +local offset = Vector(2,2,2) + +timer.Create('loopUnstuck', 3, 0, function() + octolib.func.throttle(player.GetAll(), 5, 0.1, function(ply) + if IsValid(v) and v:IsPlayer() and v:Alive() and not v:InVehicle() then + local isStuck = false + for _, ent in ipairs(ents.FindInBox(v:GetPos() + v:OBBMins() + offset, v:GetPos() + v:OBBMaxs() - offset)) do + if IsValid(ent) and ent ~= v and ent:IsPlayer() and ent:Alive() and not (ent:IsGhost() or v:IsGhost()) then + v:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + v:SetVelocity(Vector(-10, -10, 1) * 20) + ent:SetVelocity(Vector(10, 10, 1) * 20) + isStuck = true + end + end + + if not isStuck then + v:SetCollisionGroup(v:IsGhost() and COLLISION_GROUP_IN_VEHICLE or COLLISION_GROUP_PLAYER) + end + end + end) +end) diff --git a/garrysmod/addons/util-other/lua/autorun/server/drp.lua b/garrysmod/addons/util-other/lua/autorun/server/drp.lua new file mode 100644 index 0000000..3e5830c --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/drp.lua @@ -0,0 +1,198 @@ +NOTIFY_GENERIC = 0 +NOTIFY_ERROR = 1 +NOTIFY_UNDO = 2 +NOTIFY_HINT = 3 +NOTIFY_CLEANUP = 4 + +local meta = FindMetaTable('Player') + +function meta:applyPlayerClassVars(applyHealth) + + local playerClass = baseclass.Get(player_manager.GetPlayerClass(self)) + + self:SetBaseWalkSpeed(playerClass.WalkSpeed >= 0 and playerClass.WalkSpeed or GAMEMODE.Config.walkspeed) + self:SetBaseRunSpeed(playerClass.RunSpeed >= 0 and playerClass.RunSpeed or (self:isCP() and GAMEMODE.Config.runspeedcp or GAMEMODE.Config.runspeed)) + self:SetBaseLadderClimbSpeed(playerClass.LadderClimbSpeed or 200) + + hook.Call('UpdatePlayerSpeed', GAMEMODE, self) -- Backwards compatitibly, do not use + + self:SetCrouchedWalkSpeed(playerClass.CrouchedWalkSpeed) + self:SetDuckSpeed(playerClass.DuckSpeed) + self:SetUnDuckSpeed(playerClass.UnDuckSpeed) + self:SetBaseJumpPower(playerClass.JumpPower) + self:AllowFlashlight(playerClass.CanUseFlashlight) + + self:SetMaxHealth(playerClass.MaxHealth >= 0 and playerClass.MaxHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100)) + if applyHealth then + self:SetHealth(playerClass.StartHealth >= 0 and playerClass.StartHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100)) + end + self:SetArmor(playerClass.StartArmor) + + self.dropWeaponOnDeath = playerClass.DropWeaponOnDie + self:SetNoCollideWithTeammates(false) + self:SetAvoidPlayers(false) + + hook.Call('playerClassVarsApplied', nil, self) + +end + +-- some chat commands + +-- +-- CHAT STUFF +-- + +hook.Add('canDemote', 'FixDemote', function() + local players_count = player.GetCount() + + if players_count <= 2 then + + return false, L.vote_not_enough_players + + end +end) + +local restrictedEnts = { + gmod_sent_vehicle_fphysics_base = true, + gmod_sent_vehicle_fphysics_wheel = true, +} + +local function run() +if not GAMEMODE then return end + +function GAMEMODE:OnAchievementAchieved() + -- nothing +end + +function GAMEMODE:CalcMainActivity( ply, velocity ) + + ply.CalcIdeal = ACT_MP_STAND_IDLE + ply.CalcSeqOverride = -1 + + if not self:HandlePlayerDriving(ply) and not self:HandlePlayerDucking(ply, velocity) then + local len2d = velocity:Length2DSqr() + if len2d > 22500 then ply.CalcIdeal = ACT_MP_RUN elseif len2d > 0.25 then ply.CalcIdeal = ACT_MP_WALK end + end + + ply.m_bWasOnGround = ply:IsOnGround() + ply.m_bWasNoclipping = ply:GetMoveType() == MOVETYPE_NOCLIP and not ply:InVehicle() + + return ply.CalcIdeal, ply.CalcSeqOverride + +end + +function GAMEMODE:CanProperty(ply, property, ent) + if restrictedEnts[ent:GetClass()] and not (ply:query('DBG: Изменять автомобили') or property == 'skin' and ply:IsAdmin()) then + return false + end + + if ent:IsDoor() and property ~= 'collision' and property ~= 'bodygroups' and property ~= 'skin' then + return false + end + + if ent:GetClass() == 'prop_effect' and property == 'collision' then + return false + end + + if self.Config.allowedProperties[property] and ent:CPPICanTool(ply, 'remover') then + return true + end + + if property == 'persist' and ply:IsSuperAdmin() then + return true + end + + ply:Notify('warning', 'Property disabled for now') + return false -- Disabled until antiminge measure is found +end + +end +hook.Add('darkrp.loadModules', 'dbg-property', run) +run() + +hook.Add('canDemote', 'dbg-demote', function() + + for _, v in ipairs(player.GetAll()) do + if v:IsAdmin() and v:GetAFKTime() <= CFG.afkAdminNotActive and cats.config.actualAdminRanks[v:GetUserGroup()] then + return false, L.ticket_admins + end + end + +end) + +hook.Add('canUnarrest', 'dbg-demote', function(ply, arrested) + + local tr = {} + tr.start = ply:EyePos() + tr.endpos = arrested:EyePos() + tr.filter = { ply, arrested } + tr = util.TraceLine(tr) + + if tr.Hit then return false, L.something_interferes end + +end) + +hook.Add('dbg-char.firstSpawn', 'dbg-wanted', function(ply) + + timer.Simple(3, function() + if not IsValid(ply) then return end + + local wanted = ply:GetDBVar('wanted') + if wanted and wanted.till then + local time = wanted.till - os.time() + if time > 0 then + ply:wanted(nil, wanted.reason, time) + else + ply:SetDBVar('wanted', nil) + end + end + end) + +end) + +hook.Add('hungerUpdate', 'dbg-admin', function(ply) + if ply:Team() == TEAM_ADMIN or ply:IsGhost() then return true end +end) + +hook.Add('hungerUpdate', 'dbg-jobs.k9', function(ply) + if ply:getJobTable().notHuman then return true end +end) + +hook.Add('octoinv.canLock', 'dbg-admin', function(ply, ent) + if ply:Team() == TEAM_ADMIN then + return true + end +end, -3) + +hook.Add('octoinv.canUnlock', 'dbg-admin', function(ply, ent) + if ply:Team() == TEAM_ADMIN then + return true + end +end, -3) + +local disabledWeps = octolib.array.toKeys {'gmod_camera','gmod_tool', 'weapon_physgun'} +hook.Add('octolib.canUse', 'dbg-use.disablePhysAndToolGun', function(ply) + local wep = ply:GetActiveWeapon() + if IsValid(wep) and disabledWeps[wep:GetClass()] then + return false + end +end) + +hook.Add('EntityTakeDamage', 'dbg-move', function(ent, dmg) + + if ent:IsPlayer() and not hook.Run('shouldViewPunchOnDamage', ent) then + local ang = math.random() * math.pi * 2 + local dmgAmount = dmg:GetDamage() / 1.5 + ent:ViewPunch(Angle(math.sin(ang) * dmgAmount, math.cos(ang) * dmgAmount, 0)) + end + +end) + +hook.Add('OnPlayerChangedTeam', 'dbg-move', function(ply) + + timer.Simple(1, function() + if not IsValid(ply) then return end + ply:MoveModifier('job', ply:getJobTable().movemods) + end) + +end) diff --git a/garrysmod/addons/util-other/lua/autorun/server/noidiots.lua b/garrysmod/addons/util-other/lua/autorun/server/noidiots.lua new file mode 100644 index 0000000..f9d30b3 --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/noidiots.lua @@ -0,0 +1,134 @@ +hook.Add('OnPhysgunReload', 'antiPropKill', function(wep, ply) + + return false + +end) + +hook.Add('PlayerSpawnedProp', 'dbg.noIdiots', function(ply, mdl, ent) + + if IsValid(ent) then + local mins, maxs = ent:GetModelBounds() + local size = maxs - mins + local vol = size.x * size.y * size.z + if (size.x > 1000 or size.y > 1000 or size.z > 1000 or vol > 2000000) and not (ply:query('DBG: Большие пропы') or ply:GetNetVar('os_build')) then + ent:Remove() + ply:Notify('warning', L.too_big) + return + end + end + +end) + +local minTimeToBuild = 1 * 60 * 60 -- 1 hour +local newbieMsg = 'У тебя недостаточно наигранного времени для того, чтобы начать строить. Советуем уделить первый час игры ознакомлению с механиками сервера!' +local function canSpawn(ply) + + -- lookbook + if ply:GetDBVar('lookbook') then return end + + if ply:TriggerCooldown('lookbook', 10) then + local request = {{ + name = 'Стиль построек Доброграда', + desc = 'Мы заинтересованы в том, чтобы все постройки в городе выглядели естественно и вписывались в общую картину. Похоже, ты собираешься внести свой вклад в эту картину. Пожалуйста, ознакомься с этим списком рекомендаций по постройке, чтобы не испортить ее:', + buttonText = 'Лукбук Доброграда', + buttonURL = 'https://octo.gg/lookbook', + }} + if CFG.dev or ply:GetTimeTotal() >= minTimeToBuild then + request[#request + 1] = { + desc = 'Если администраторы посчитают твою постройку нарушающей общую стилистику Доброграда, они в уведомлении попросят тебя исправить что-то в ней или перестроить вовсе. Если эти просьбы будут проигнорированы, им придется удалить постройку.\n\nОтметь ниже, когда ознакомишься с лукбуком, осознаешь важность поддержания общей стилистики Доброграда и будешь готов украшать город своей постройкой:', + type = 'check', + txt = 'Вперед строить!', + required = true, + } + else + request[#request + 1] = { desc = newbieMsg } + end + + octolib.request.send(ply, request, function(data) + if not IsValid(ply) then return end + if not CFG.dev and ply:GetTimeTotal() < minTimeToBuild then + return ply:Notify('warning', newbieMsg) + end + if data and data[2] then + ply:SetDBVar('lookbook', true) + end + end) + end + + return false + +end +hook.Add('PlayerSpawnProp', 'dbg.noIdiots', canSpawn) +hook.Add('PlayerSpawnEffect', 'dbg.noIdiots', canSpawn) +hook.Add('CanTool', 'dbg.noIdiots', canSpawn) + +local function freezeRagdoll(ent) + if ent:GetClass() == 'prop_ragdoll' then + for i = 0, ent:GetPhysicsObjectCount() - 1 do + local phys = ent:GetPhysicsObjectNum(i) + if IsValid(phys) then phys:EnableMotion(false) end + end + end +end + +hook.Add('octoperma.spawned', 'dbg.freezeRagdolls', function(entities) + for _, ent in pairs(entities) do + freezeRagdoll(ent) + end +end) + +local timeForAD2 = 10 * 60 * 60 -- 10 hours +hook.Add('CanTool', 'dbg-tools', function(ply, tr, tool) + + if tool == 'advdupe2' and not CFG.dev and ply:GetTimeTotal() < timeForAD2 then + ply:Notify('warning', L.need_10_hours) + return false + end + + if tool == 'remover' then + local ent = tr.Entity + if IsValid(ent) and ent:IsDoor() then return false end + end + +end) + +hook.Add('PhysgunPickup', 'dbg.helloFromTawich', function(ply, ent) + if ent:GetName() == 'trainent' then + return false + end +end) + +local disabled = octolib.array.toKeys {'octo_trigger', 'octo_trigger_plus'} +hook.Add('PhysgunPickup', 'dbg.helloFromSighty', function(ply, ent) + if disabled[ent:GetClass()] then + return false + end +end) + +concommand.Add('gmod_admin_cleanup', function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then + ply:Notify('warning', 'Данная кнопка запломбирована') + else cleanup.CC_AdminCleanup(ply, cmd, args) end +end, nil, '', {FCVAR_DONTRECORD}) + +-- +-- PROP PUSH FIXES +-- +local noPickup = { + gmod_button = true, + keypad = true, +} +hook.Add('PhysgunPickup', 'dbg.noPropSurf', function(ply, ent) + if noPickup[ent:GetClass()] and not ply:IsSuperAdmin() then return false end +end) + +local ghost = { + gmod_button = true, + keypad = true, + gmod_light = true, +} +hook.Add('APGisBadEnt', 'dbg.noPropPush', function(ent) + if ghost[ent:GetClass()] then return true end +end) + +-- i think something will be added here later diff --git a/garrysmod/addons/util-other/lua/autorun/server/unban.lua b/garrysmod/addons/util-other/lua/autorun/server/unban.lua new file mode 100644 index 0000000..c0248da --- /dev/null +++ b/garrysmod/addons/util-other/lua/autorun/server/unban.lua @@ -0,0 +1,249 @@ +local maxFamilyDisplay = 20 + +local function notifyDiscord(admin, sid, ban, reason) + local permanent = ban.endTime <= 0 + local console = not IsValid(admin) + octolib.func.chain({ + + function(nxt) + octolib.getDBVar(sid, 'family', {}):Then(nxt):Catch(ErrorNoHalt) + end, + + function(nxt, family) + + local toCheck = { util.SteamIDTo64(sid) } + if not console then toCheck[#toCheck + 1] = admin:SteamID64() end + local familySize = 1 + for i, v in ipairs(family) do + if v ~= sid then + toCheck[#toCheck + 1] = util.SteamIDTo64(v) + familySize = familySize + 1 + end + if familySize > maxFamilyDisplay then break end -- check only 10 first family members + end + octolib.getSteamData(toCheck, nxt) + + end, + + function(nxt, response) + + local steamData = { -- change data format for convenience + target = response[1], + admin = not console and response[2] or nil, + family = {}, + } + for i = (console and 2 or 3), #response do + steamData.family[#steamData.family + 1] = response[i] + end + + local embed = { + author = { + name = steamData.admin and steamData.admin.name or 'Console', + icon_url = steamData.admin and steamData.admin.avatar or 'https://cdn.octothorp.team/img/logo/white.png', + url = steamData.admin and ('https://steamcommunity.com/profiles/' .. steamData.admin.steamid64) or 'https://panel.octothorp.team/', + }, + title = steamData.target.name, + url = 'https://steamcommunity.com/profiles/' .. steamData.target.steamid64, + description = 'Игроку в ' .. (permanent and 'перманентной ' or '') .. 'блокировке выдан разбан', + thumbnail = { url = steamData.target.avatar }, + fields = { + { + name = 'SteamID', + value = steamData.target.steamid, + }, { + name = 'Блокировку выдал', + value = ban.admin, + }, { + name = 'Причина блокировки', + value = ban.reason, + }, { + name = 'Причина снятия блокировки', + value = reason or 'Не указана', + }, { + name = 'Примерное время в блокировке', + value = octolib.time.formatDuration(os.time() - ban.startTime), + }, + }, + } + if not permanent then + table.insert(embed.fields, 3, { + name = 'Примерный срок блокировки', + value = octolib.time.formatDuration(ban.endTime - ban.startTime), + }) + end + + for i, member in ipairs(steamData.family) do + embed.fields[#embed.fields + 1] = { + name = 'Связанный аккаунт #' .. i, + value = '[' .. member.name .. '](https://steamcommunity.com/profiles/' .. member.steamid64 .. ')\n' .. member.steamid, + inline = true, + } + end + + if #steamData.family == maxFamilyDisplay then + embed.footer = { + text = ('Отображаются только первые %s связанных аккаунтов. Полный список можно посмотреть на https://octothorp.team/admin/octolib-vars'):format(maxFamilyDisplay), + icon_url = 'https://img.icons8.com/color/48/asterisk.png', + } + end + + if CFG.webhooks.unban then + octoservices:post('/discord/webhook/' .. CFG.webhooks.unban, { + content = permanent and CFG.adminMention or nil, + embeds = { embed }, + }):Catch(function(err) + octolib.msg('Error getting Steam data: %s', err) + end) + end + end, + }) +end + +local function requestConsent(admin, sid, ban, reason) + octolib.getDBVar(sid, 'family', {}):Then(function(family) + if not IsValid(admin) then return end + netstream.Start(admin, 'dbg-unban.consent', { + target = sid, + admin = ban.admin, + reason = ban.reason, + length = ban.endTime > 0 and (ban.endTime - ban.startTime) or 0, + spent = os.time() - ban.startTime, + family = family, + unbanReason = reason or nil, + }) + end) +end + +local function requestConsoleConsent(sid, ban, reason) + octolib.func.chain({ + + function(nxt) + octolib.getDBVar(sid, 'family', {}):Then(nxt):Catch(ErrorNoHalt) + end, + + function(nxt, family) + local toCheck = { util.SteamIDTo64(sid) } + local size = 1 + for i, v in ipairs(family) do + if v ~= sid then + toCheck[#toCheck + 1] = util.SteamIDTo64(v) + size = size + 1 + end + if size > maxFamilyDisplay then break end -- check only 10 first family members + end + octolib.getSteamData(toCheck, nxt) + end, + + function(nxt, response) + + local steamData = { -- change data format for convenience + target = response[1], + family = {}, + } + for i = 2, #response do + steamData.family[#steamData.family + 1] = response[i] + end + + octolib.msg('=================================') + octolib.msg('Ты собираешься разбанить игрока, находящегося в блокировке') + octolib.msg('Пожалуйста, проверь всю информацию о нем, чтобы убедиться в отсутствии последствий') + octolib.msg('Ник игрока: %s', steamData.target.name) + octolib.msg('SteamID: %s', steamData.target.steamid) + octolib.msg('Ссылка на профиль: https://steamcommunity.com/profiles/%s', steamData.target.steamid64) + octolib.msg('Блокировку выдавал админ: %s', ban.admin) + if ban.endTime > 0 then + octolib.msg('Примерная длительность блокировки: %s', octolib.time.formatDuration(ban.endTime - ban.startTime)) + else + octolib.msg('БЛОКИРОВКА БЕССРОЧНАЯ! Особенно внимательно просмотри связанные аккаунты') + end + octolib.msg('Причина блокировки: %s', ban.reason) + octolib.msg('Причина снятия блокировки: %s', reason) + octolib.msg('Примерное время в блокировке: %s', octolib.time.formatDuration(os.time() - ban.startTime)) + + if steamData.family[1] then + octolib.msg('Связанные аккаунты:') + for _, member in ipairs(steamData.family) do + octolib.msg('- %s, %s, https://steamcommunity.com/profiles/%s', member.name, member.steamid, member.steamid64) + end + if #steamData.family == maxFamilyDisplay then + octolib.msg('(Отображаются только первые %s связанных аккаунтов, полный список на https://octothorp.team/admin/octolib-vars)', maxFamilyDisplay) + end + else octolib.msg('Связанные аккаунты: отсутствуют') end + + octolib.msg('Введи эту команду еще раз, чтобы выдать разбан. Об этом будет уведомлена старшая администрация') + octolib.msg('sg unban "%s" "%s"', sid, reason) + octolib.msg('=================================') + + end, + + }) + +end + +-- consents['admin steamid'] = 'last target steamid allowed to unban' +-- consoleConsents['target steamid allowed to unban'] = true +local consents, consoleConsents = {}, {} + +hook.Add('serverguard.PreventPlayerUnban', 'dbg-security.unbanConfirmation', function(sid, admin, reason) + + local ban = serverguard.banTable[sid] + local asid = IsValid(admin) and admin:SteamID() or false + if not ban or (ban.endTime > 0 and ban.endTime <= os.time()) then + consents[asid] = nil -- player is not banned permanently + if asid then admin:Notify('warning', 'Этот игрок не заблокирован') + else octolib.msg('Этот игрок не заблокирован') end + return + end + + if IsValid(admin) then + + if ban.endTime <= 0 and not admin:query('Unban permanently banned players') then + admin:Notify('warning', 'Обратись к старшей администрации, чтобы снять перманентные блокировки') + return true -- prevent unban + end + if consents[asid] ~= sid then + requestConsent(admin, sid, ban, reason) + return true -- prevent unban for now + end + consents[asid] = nil + notifyDiscord(admin, sid, ban, reason) + + else + + if not consoleConsents[sid] then + requestConsoleConsent(sid, ban, reason) + consoleConsents[sid] = true + timer.Simple(120, function() -- console has to re-enter unban command in 2 minutes + consoleConsents[sid] = nil + end) + return true -- prevent unban for now + else + notifyDiscord(nil, sid, ban, reason) + consoleConsents[sid] = nil + end + + end +end) + +hook.Add('serverguard.PlayerBannedBySteamID', 'dbg-security.rebanCheck', function(sid, len, reason, admin) + local ban = serverguard.banTable[sid] + if not ban then return end + + if ban.endTime <= 0 or (ban.endTime - os.time()) >= (len - 1) * 60 then + if IsValid(admin) then + admin:Notify('warning', 'Сначала разбань этого игрока, затем выдай ему бан повторно') + else + octolib.msg('Сначала разбань этого игрока, затем выдай ему бан повторно') + end + return true + end +end) + +netstream.Hook('dbg-unban.consent', function(ply, sid, reason) + if not (octolib.string.isSteamID(sid) and ply:query('Unban')) then return end + local ban = serverguard.banTable[sid] + if not ban then return end + if not (ban.endTime > 0 or ply:query('Unban permanently banned players')) then return end + consents[ply:SteamID()] = sid + ply:ConCommand(('sg unban "%s" "%s"'):format(sid, reason)) +end) diff --git a/garrysmod/addons/util-other/lua/cmenu/items/sticknote.lua b/garrysmod/addons/util-other/lua/cmenu/items/sticknote.lua new file mode 100644 index 0000000..a601f74 --- /dev/null +++ b/garrysmod/addons/util-other/lua/cmenu/items/sticknote.lua @@ -0,0 +1,89 @@ +octolib.vars.init('dbg.sticknotes', {}) +octogui.cmenu.stickNotes = octogui.cmenu.stickNotes or {} + +local saveStickNotes = octolib.func.debounce(function() + local newData = {} + for uid, pnl in pairs(octogui.cmenu.stickNotes) do + if IsValid(pnl) then + newData[uid] = { + x = pnl:GetX(), + y = pnl:GetY(), + w = pnl:GetWide(), + h = pnl:GetTall(), + text = pnl.text:GetValue(), + } + else octogui.cmenu.stickNotes[uid] = nil end + end + octolib.vars.set('dbg.sticknotes', newData) +end, 0.5) + +local function createSticknote(uid, x, y, w, h, text) + uid = uid or octolib.string.uuid():sub(1, 8) + if IsValid(octogui.cmenu.stickNotes[uid]) then + octogui.cmenu.stickNotes[uid]:Remove() + end + local f = vgui.Create 'DFrame' + f:DockPadding(4, 15, 4, 4) + f:SetKeyboardInputEnabled(false) + f:SetMouseInputEnabled(false) + f:MakePopup() + f:SetSize(w or 200, h or 200) + f:SetSizable(true) + f:SetTitle('') + function f:OnRemove() + octogui.cmenu.stickNotes[uid] = nil + saveStickNotes() + end + if x then f:SetX(x) + else f:AlignLeft(20) end + if y then f:SetY(y) + else f:CenterVertical() end + f.btnMinim:SetVisible(false) + f.btnMaxim:SetVisible(false) + f.Paint = octolib.func.zero + octogui.cmenu.stickNotes[uid] = f + + f.OnSizeChanged = saveStickNotes + f.oldX, f.oldY = f:GetPos() + f.oldThink = f.Think + function f:Think() + self:oldThink() + if f.oldX ~= f:GetX() or f.oldY ~= f:GetY() then + f.oldX, f.oldY = f:GetPos() + saveStickNotes() + end + end + + local e = f:Add 'DTextEntry' + e:Dock(FILL) + e:SetMultiline(true) + e:SetDrawLanguageID(false) + e:SetText(text or L.sticknote_hint) + f.text = e + e.PaintOffset = 4 + function e:Think() + if vgui.CursorVisible() then + local cx, cy = gui.MousePos() + local wx, wy, ww, wh = f:GetBounds() + local show = (cx >= wx and cx <= wx + ww and cy >= wy and cy <= wy + wh) + if f:IsKeyboardInputEnabled() ~= show then + f:SetKeyboardInputEnabled(show) + f:SetMouseInputEnabled(show) + end + end + end + e.OnChange = saveStickNotes +end + +hook.Add('Think', 'dbg.sticknotes', function() + hook.Remove('Think', 'dbg.sticknotes') + for uid, note in pairs(octolib.vars.get('dbg.sticknotes')) do + createSticknote(uid, note.x, note.y, note.w, note.h, note.text) + end +end) + +octogui.cmenu.registerItem('other', 'sticknote', { + text = L.sticknote_create, + icon = octolib.icons.silk16('note_add'), + action = function() createSticknote() end, +}) diff --git a/garrysmod/addons/util-other/lua/includes/modules/numpad.lua b/garrysmod/addons/util-other/lua/includes/modules/numpad.lua new file mode 100644 index 0000000..237d9e7 --- /dev/null +++ b/garrysmod/addons/util-other/lua/includes/modules/numpad.lua @@ -0,0 +1,248 @@ +--[[--------------------------------------------------------- + This module was primarily developed to enable toolmodes + to share the numpad. + + Scripted Entities can add functions to be excecuted when + a certain key on the numpad is pressed or released. +-----------------------------------------------------------]] + +if ( !SERVER ) then return end + +local isstring = isstring +local tonumber = tonumber +local pairs = pairs +local unpack = unpack +local table = table +local ErrorNoHalt = ErrorNoHalt +local MsgN = MsgN +local saverestore = saverestore +local math = math +local IsValid = IsValid + +module( "numpad" ) + +local functions = {} +local keys_in = {} +local keys_out = {} +local keystates = {} +local lastindex = 1 +local button_fired = false + +function Debug() + PrintTable(functions) + PrintTable(keys_in) + PrintTable(keys_out) +end + +function FromButton() + + return button_fired == true + +end + +local function Save( save ) + + saverestore.WriteTable( keys_in, save ) + saverestore.WriteTable( keys_out, save ) + saverestore.WriteVar( lastindex, save ) + +end + +local function Restore( restore ) + + keys_in = saverestore.ReadTable( restore ) + keys_out = saverestore.ReadTable( restore ) + lastindex = saverestore.ReadVar( restore ) + +end + +saverestore.AddSaveHook( "NumpadModule", Save ) +saverestore.AddRestoreHook( "NumpadModule", Restore ) + +--[[--------------------------------------------------------- + Returns a unique index based on a player. +-----------------------------------------------------------]] +local function GetPlayerIndex( ply ) + + return isstring(ply) and ply or IsValid(ply) and ply:SteamID() or nil + +end + +--[[--------------------------------------------------------- + Fires the impulse to the child functions +-----------------------------------------------------------]] +local function FireImpulse( tab, uid ) + + if not tab or not tab[uid] then return end + + for k, v in pairs(tab[uid]) do + local func = functions[v.name] + if not func then continue end + local retval = func(uid, unpack(v.arg)) + + if retval == false then + tab[uid][k] = nil + end + end + +end + +--[[--------------------------------------------------------- + Console Command +-----------------------------------------------------------]] +function Activate( uid, num, bIsButton ) + + uid = GetPlayerIndex(uid) + + local key = math.Clamp( tonumber( num ), 0, 256 ) + + -- Hack. Kinda. Don't call it again until the key has been lifted. + -- When holding down 9 or 3 on the numpad it will repeat. Ignore that. + keystates[uid] = keystates[uid] or {} + if keystates[uid][key] then return end + keystates[uid][key] = true + + button_fired = bIsButton + + FireImpulse( keys_in[ key ], uid ) + + button_fired = false + +end + +--[[--------------------------------------------------------- + Console Command +-----------------------------------------------------------]] +function Deactivate( uid, num, bIsButton ) + + uid = GetPlayerIndex(uid) + + local key = math.Clamp( tonumber( num ) , 0, 256 ) + + keystates[uid] = keystates[uid] or {} + keystates[uid][key] = nil + if table.Count(keystates[uid]) == 0 then + keystates[uid] = nil + end + + button_fired = bIsButton + + FireImpulse( keys_out[ key ], uid ) + + button_fired = false + +end + +--[[--------------------------------------------------------- + Toggle +-----------------------------------------------------------]] +function Toggle( uid, num ) + + local key = math.Clamp( tonumber( num ), 0, 256 ) + + uid = GetPlayerIndex(uid) + + keystates[uid] = keystates[uid] or {} + if ( keystates[ uid ][ key ] ) then return Deactivate( uid, num ) end + + return Activate( uid, num ) + +end + +--[[--------------------------------------------------------- + Adds an impulse to to the specified table +-----------------------------------------------------------]] +local function AddImpulse( table, uid, impulse ) + + lastindex = lastindex + 1 + + uid = GetPlayerIndex(uid) + + table[ uid ] = table[ uid ] or {} + table[ uid ][ lastindex ] = impulse + + return lastindex + +end + +--[[--------------------------------------------------------- + Adds a function to call when ply presses key +-----------------------------------------------------------]] +function OnDown( uid, key, name, ... ) + + if ( !key ) then ErrorNoHalt( "ERROR: OnDown key is nil!\n" ) return end + if ( key ~= key ) then MsgN( "ERROR: OnDown key is NaN!" ) return end + keys_in[ key ] = keys_in[ key ] or {} + + uid = GetPlayerIndex(uid) + + local impulse = {} + impulse.name = name + impulse.arg = { ... } + + table.insert( impulse.arg, uid ) + + return AddImpulse( keys_in[ key ], uid, impulse ) + +end + +--[[--------------------------------------------------------- + Adds a function to call when ply releases key +-----------------------------------------------------------]] +function OnUp( uid, key, name, ... ) + + if ( !key ) then ErrorNoHalt( "ERROR: OnUp key is nil!\n" ) return end + if ( key ~= key ) then MsgN( "ERROR: OnUp key is NaN!" ) return end + keys_out[ key ] = keys_out[ key ] or {} + + uid = GetPlayerIndex(uid) + + local impulse = {} + impulse.name = name + impulse.arg = { ... } + + table.insert( impulse.arg, uid ) + + return AddImpulse( keys_out[ key ], uid, impulse ) + +end + +--[[--------------------------------------------------------- + Removes key from tab (by unique index) +-----------------------------------------------------------]] +local function RemoveFromKeyTable( tab, idx ) + + for k, v_key in pairs( tab ) do + + for k_, v_player in pairs( v_key ) do + + if ( v_player[ idx ] != nil ) then + v_player[ idx ] = nil + end + + end + + end + +end + +--[[--------------------------------------------------------- + Removes key (by unique index) +-----------------------------------------------------------]] +function Remove( idx ) + + if ( !idx ) then return end + + RemoveFromKeyTable( keys_out, idx ) + RemoveFromKeyTable( keys_in, idx ) + +end + +--[[--------------------------------------------------------- + Register a function +-----------------------------------------------------------]] +function Register( name, func ) + + functions[ name ] = func + +end diff --git a/garrysmod/addons/util-other/lua/spawnlists/_example.txt b/garrysmod/addons/util-other/lua/spawnlists/_example.txt new file mode 100644 index 0000000..c28d101 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/_example.txt @@ -0,0 +1,6 @@ +return { + name = '', + icon = 'icon16/folder.png', + content = { + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/accessories.lua b/garrysmod/addons/util-other/lua/spawnlists/accessories.lua new file mode 100644 index 0000000..c342681 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/accessories.lua @@ -0,0 +1,11 @@ +return { + name = 'Аксессуары', + icon = 'octoteam/icons-16/hat.png', + content = { + {'Голова', + 'models/russianhat.mdl', + 'models/russianhat1.mdl', + 'models/russianhat2.mdl', + }, + }, +} diff --git a/garrysmod/addons/util-other/lua/spawnlists/business/_category.lua b/garrysmod/addons/util-other/lua/spawnlists/business/_category.lua new file mode 100644 index 0000000..dae54f9 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/business/_category.lua @@ -0,0 +1,4 @@ +return { + name = 'Бизнес', + icon = 'icon16/money.png', +} diff --git a/garrysmod/addons/util-other/lua/spawnlists/business/civil.lua b/garrysmod/addons/util-other/lua/spawnlists/business/civil.lua new file mode 100644 index 0000000..96306f6 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/business/civil.lua @@ -0,0 +1,1228 @@ +return { + name = 'Гражданский', + icon = 'octoteam/icons-16/travel.png', + content = { + {'Безопасность', + 'models/sal/ammunation/firealarm.mdl', + 'models/sal/ammunation/fireextinguisher.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/fire_exting_2a.mdl', + 'models/sal/ammunation/cctv.mdl', + 'models/gta_prop_michou/v_serv_securitycam_03.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/alarmbox.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/v_res_fh_speaker_high.mdl', + 'models/mark2580/gtav/tequilala_map/misc/prop_alarm_01.mdl', + 'models/mark2580/gtav/tequilala_map/misc/prop_cctv_cam_06a.mdl', + 'models/props_equipment/security_desk1.mdl', + 'models/props_lab/securitybank.mdl', + 'models/props/cs_assault/camera.mdl', + 'models/props/motion_sensor_01.mdl', + 'models/props/hospital/alarmbox.mdl', + 'models/props/hospital/firealarm.mdl', + }, + {'Офис', + 'models/sal/ammunation/binder1.mdl', + 'models/sal/ammunation/binder2.mdl', + 'models/sal/ammunation/binder3.mdl', + 'models/sal/ammunation/binder4.mdl', + 'models/sal/ammunation/binder5.mdl', + 'models/sal/ammunation/shredder.mdl', + 'models/sal/ammunation/stapler.mdl', + 'models/sal/ammunation/open_binder.mdl', + 'models/sal/ammunation/organizer1.mdl', + 'models/sal/ammunation/organizer2.mdl', + 'models/sal/ammunation/paper1.mdl', + 'models/sal/ammunation/paper2.mdl', + 'models/sal/ammunation/paper3.mdl', + 'models/sal/ammunation/paper4.mdl', + 'models/sal/ammunation/paper5.mdl', + 'models/sal/ammunation/fax.mdl', + 'models/sal/ammunation/deskorganizer.mdl', + 'models/sal/ammunation/eraser1.mdl', + 'models/sal/ammunation/eraser2.mdl', + 'models/sal/ammunation/calculator.mdl', + 'models/sal/ammunation/bulletboard.mdl', + 'models/sal/ammunation/bulletboard_2.mdl', + 'models/sal/ammunation/scissors.mdl', + 'models/sal/ammunation/telephone.mdl', + 'models/sal/ammunation/printer.mdl', + 'models/sal/ammunation/pencup.mdl', + 'models/sal/ammunation/mousepad.mdl', + 'models/sal/ammunation/mouse.mdl', + 'models/sal/ammunation/keyboard.mdl', + 'models/sal/ammunation/monitor.mdl', + 'models/sal/ammunation/computer.mdl', + 'models/sal/ammunation/cabinet_square.mdl', + 'models/sal/ammunation/box1.mdl', + 'models/sal/ammunation/box3.mdl', + 'models/sal/ammunation/box4.mdl', + 'models/sal/ammunation/box6.mdl', + 'models/sal/ammunation/whiteboard.mdl', + 'models/sal/ammunation/magazine.mdl', + 'models/sal/ammunation/single_magazine.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_printer.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/v_res_fh_towerfan_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/conditioner.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/v_res_fa_radioalrm.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/clock_02.mdl', + 'models/sal/ammunation/clock.mdl', + 'models/sal/office/binder2.mdl', + 'models/sal/office/flag.mdl', + 'models/sal/office/organizer.mdl', + 'models/sal/office/pencup.mdl', + 'models/props_office/file_cabinet_03.mdl', + 'models/props_interiors/copymachine01.mdl', + 'models/props_interiors/printer.mdl', + 'models/props_interiors/paper_tray.mdl', + 'models/props_interiors/water_cooler.mdl', + 'models/props_interiors/corkboardverticle01.mdl', + 'models/props/cs_office/file_box.mdl', + 'models/props/cs_office/offcorkboarda.mdl', + 'models/sal/ammunation/filecab1.mdl', + 'models/sal/ammunation/filecab2.mdl', + 'models/sal/ammunation/filecab3.mdl', + 'models/sal/ammunation/filecab4.mdl', + 'models/props_lab/filecabinet02.mdl', + 'models/props_wasteland/controlroom_filecabinet001a.mdl', + 'models/props_wasteland/controlroom_filecabinet002a.mdl', + 'models/props/cs_office/file_cabinet1.mdl', + 'models/props/cs_office/file_cabinet1_group.mdl', + 'models/props/cs_office/file_cabinet2.mdl', + 'models/props/cs_office/file_cabinet3.mdl', + 'models/props/de_nuke/file_cabinet1_group.mdl', + 'models/props/cs_office/shelves_metal1.mdl', + 'models/props/cs_office/shelves_metal2.mdl', + 'models/props/cs_office/shelves_metal3.mdl', + 'models/props_lab/partsbin01.mdl', + 'models/props_lab/clipboard.mdl', + 'models/props_c17/briefcase001a.mdl', + 'models/props_c17/suitcase001a.mdl', + 'models/props_c17/suitcase_passenger_physics.mdl', + 'models/props_lab/bindergraylabel01a.mdl', + 'models/props_lab/binderredlabel.mdl', + 'models/props_lab/bindergreen.mdl', + 'models/props_lab/binderblue.mdl', + 'models/props/cs_office/exit_wall.mdl', + 'models/props/cs_office/exit_ceiling.mdl', + 'models/props_office/office_chair.mdl', + 'models/props_office/stapler.mdl', + 'models/props_office/table_square.mdl', + 'models/props_office/waterdispencer.mdl', + }, + {'Электроника', + 'models/kerry/props/harddisk.mdl', + 'models/kerry/props/laptop_agent_14.mdl', + 'models/kerry/props/laptop_agent_14_close.mdl', + 'models/kerry/props/laptop_close.mdl', + 'models/kerry/props/laptop_open.mdl', + 'models/kerry/props/xm_prop_x17_laptop_lester_01.mdl', + 'models/kerry/props/xm_prop_x17_laptop_lester_01_close.mdl', + 'models/props_c17/computer01_keyboard.mdl', + 'models/props_lab/monitor01a.mdl', + 'models/props_lab/monitor01b.mdl', + 'models/props/de_inferno/tv_monitor01.mdl', + 'models/props_lab/monitor02.mdl', + 'models/props/cs_office/computer.mdl', + 'models/props/cs_office/computer_case.mdl', + 'models/props/cs_office/computer_keyboard.mdl', + 'models/props/cs_office/computer_monitor.mdl', + 'models/highrise/cubicle_monitor_01.mdl', + 'models/props_office/computer_desktop02.mdl', + 'models/props_office/computer_keyboard01.mdl', + 'models/props_office/computer_monitor02.mdl', + 'models/testmodels/macbook_pro.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_laptop.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_keyboard_black.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_mousemat.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_monitor.mdl', + 'models/Highrise/cubicle_monitor_01.mdl', + 'models/props/cs_office/computer.mdl', + 'models/sal/office/keyboard.mdl', + 'models/sal/office/monitor.mdl', + 'models/sal/office/mouse.mdl', + 'models/lt_c/tech/cellphone.mdl', + 'models/lt_c/tech/tablet_civ.mdl', + 'models/nirrti/tablet/tablet_sfm.mdl', + 'models/sterling/ajr_phone_w.mdl', + 'models/props/cs_militia/oldphone01.mdl', + 'models/props/cs_office/phone.mdl', + 'models/props/cs_office/phone_p1.mdl', + 'models/props/cs_office/phone_p2.mdl', + 'models/props_lab/plotter.mdl', + 'models/props/cs_office/projector.mdl', + 'models/sterling/gsmart_watch.mdl', + 'models/unconid/home_assistant/amazon_dot_1.mdl', + 'models/unconid/home_assistant/amazon_dot_2.mdl', + 'models/unconid/home_assistant/amazon_dot_3.mdl', + 'models/unconid/home_assistant/amazon_echo_2.mdl', + 'models/unconid/home_assistant/amazon_echo_3.mdl', + 'models/unconid/home_assistant/amazon_echo_4.mdl', + 'models/unconid/home_assistant/google_home_mini.mdl', + 'models/gaming-models/nebukadnezar/gibs/pc_gibs/pc_grafix.mdl', + 'models/gaming-models/nebukadnezar/gibs/pc_gibs/pc_harddrive.mdl', + 'models/gaming-models/nebukadnezar/gibs/pc_gibs/pc_mother.mdl', + 'models/gaming-models/nebukadnezar/gibs/pc_gibs/pc_ram.mdl', + 'models/gaming-models/nebukadnezar/gibs/pc_gibs/pc_ram_2.mdl', + 'models/gaming-models/nebukadnezar/gibs/pc_gibs/pc_ram_3.mdl', + 'models/unconid/pc_models/monitors/lcd_acer_16x9.mdl', + 'models/unconid/pc_models/monitors/lcd_lg_4x3.mdl', + 'models/unconid/pc_models/monitors/lcd_super_ultrawide.mdl', + 'models/unconid/pc_models/monitors/lcd_ultrawide_curved.mdl', + 'models/unconid/pc_models/monitors/lcd_ultrawide_nc.mdl', + 'models/props_office/computer_monitor_01.mdl', + }, + {'Продовольственный магазин', + 'models/props_shops/prop_bakery_basket_01_a.mdl', + 'models/props_shops/prop_bakery_basket_01_b.mdl', + 'models/props_shops/prop_bakery_basket_01_c.mdl', + 'models/props_shops/prop_bakery_basket_02_a.mdl', + 'models/props_shops/prop_bakery_basket_02_b.mdl', + 'models/props_shops/prop_bakery_basket_02_c.mdl', + 'models/props_shops/prop_bakery_basket_03_a.mdl', + 'models/props_shops/prop_bakery_basket_03_b.mdl', + 'models/props_shops/prop_bakery_basket_03_c.mdl', + 'models/props_shops/prop_bakery_basket_04_a.mdl', + 'models/props_shops/prop_bakery_basket_04_b.mdl', + 'models/props_shops/prop_bakery_basket_04_c.mdl', + 'models/props_shops/prop_bakery_basket_04_d.mdl', + 'models/props_shops/prop_bakery_basket_04_e.mdl', + 'models/props_shops/prop_bakery_basket_05_a.mdl', + 'models/props_shops/prop_bakery_basket_05_b.mdl', + 'models/props_shops/prop_bakery_basket_05_c.mdl', + 'models/props_shops/prop_bakery_basket_05_d.mdl', + 'models/props_shops/prop_bakery_basket_05_e.mdl', + 'models/props_shops/prop_bakery_bread_01_a.mdl', + 'models/props_shops/prop_bakery_bread_01_b.mdl', + 'models/props_shops/prop_bakery_flourbag_01_a.mdl', + 'models/props_shops/prop_bakery_flourbag_01_b.mdl', + 'models/props_shops/prop_bakery_flourbag_01_c.mdl', + 'models/props_shops/prop_bakery_flourbag_02_a.mdl', + 'models/props_shops/prop_bakery_flourbag_02_b.mdl', + 'models/props_shops/prop_bakery_flourbag_02_c.mdl', + 'models/props_shops/prop_bakery_floursack_01_a.mdl', + 'models/props_shops/prop_bakery_floursack_01_b.mdl', + 'models/props_shops/prop_bakery_floursack_01_c.mdl', + 'models/props_shops/prop_bakery_floursack_02_a.mdl', + 'models/props_shops/prop_bakery_floursack_02_b.mdl', + 'models/props_shops/prop_bakery_floursack_02_c.mdl', + 'models/props_shops/prop_bakery_floursack_02_d.mdl', + 'models/props_shops/prop_bakery_rollingpin_01.mdl', + 'models/props_shops/prop_bakery_sieve_01.mdl', + 'models/props_shops/prop_bar_corner_a.mdl', + 'models/props_shops/prop_bar_corner_b.mdl', + 'models/props_shops/prop_bar_end_a.mdl', + 'models/props_shops/prop_bar_end_b.mdl', + 'models/props_shops/prop_bar_long_a.mdl', + 'models/props_shops/prop_bar_long_b.mdl', + 'models/props_shops/prop_bar_medium_a.mdl', + 'models/props_shops/prop_bar_medium_b.mdl', + 'models/props_shops/prop_bar_shelf_a.mdl', + 'models/props_shops/prop_bar_sign_a.mdl', + 'models/props_shops/prop_bar_sign_b.mdl', + 'models/props_shops/prop_bar_sign_c.mdl', + 'models/props_shops/prop_bar_sign_d.mdl', + 'models/props_shops/prop_bar_small_a.mdl', + 'models/props_shops/prop_bar_small_b.mdl', + 'models/props_shops/prop_bar_top_a.mdl', + 'models/props_shops/prop_bar_top_b.mdl', + 'models/props_shops/prop_bar_top_corner.mdl', + 'models/props_shops/prop_bar_trim_a.mdl', + 'models/props_shops/prop_butcher_board_01_a.mdl', + 'models/props_shops/prop_butcher_board_01_b.mdl', + 'models/props_shops/prop_butcher_board_01_c.mdl', + 'models/props_shops/prop_butcher_board_02_a.mdl', + 'models/props_shops/prop_butcher_board_02_b.mdl', + 'models/props_shops/prop_butcher_board_02_c.mdl', + 'models/props_shops/prop_butcher_board_02_d.mdl', + 'models/props_shops/prop_butcher_cleaver_01_a.mdl', + 'models/props_shops/prop_butcher_cleaver_01_b.mdl', + 'models/props_shops/prop_butcher_cleaver_01_c.mdl', + 'models/props_shops/prop_butcher_grinder_01_a.mdl', + 'models/props_shops/prop_butcher_grinder_01_b.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_a.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_a_empty.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_b.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_b_empty.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_c.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_d.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_e.mdl', + 'models/props_shops/prop_butcher_hangingmeat_01_f.mdl', + 'models/props_shops/prop_butcher_paper_01_a.mdl', + 'models/props_shops/prop_butcher_scale_01_a.mdl', + 'models/props_shops/prop_cafe_espresso.mdl', + 'models/props_shops/prop_extra_books_a.mdl', + 'models/props_shops/prop_extra_books_b.mdl', + 'models/props_shops/prop_extra_books_c.mdl', + 'models/props_shops/prop_extra_books_d.mdl', + 'models/props_shops/prop_extra_books_e.mdl', + 'models/props_shops/prop_extra_books_f.mdl', + 'models/props_shops/prop_extra_books_g.mdl', + 'models/props_shops/prop_extra_books_group_a.mdl', + 'models/props_shops/prop_extra_books_group_b.mdl', + 'models/props_shops/prop_extra_books_group_c.mdl', + 'models/props_shops/prop_extra_books_group_d.mdl', + 'models/props_shops/prop_extra_books_group_e.mdl', + 'models/props_shops/prop_extra_papers_a.mdl', + 'models/props_shops/prop_extra_papers_b.mdl', + 'models/props_shops/prop_extra_papers_c.mdl', + 'models/props_shops/prop_extra_papers_d.mdl', + 'models/props_shops/prop_extra_papers_e.mdl', + 'models/props_shops/prop_extra_papers_f.mdl', + 'models/props_shops/prop_shop_rug_a.mdl', + 'models/props_shops/prop_shop_rug_b.mdl', + 'models/props_shops/prop_shop_rug_c.mdl', + 'models/props_shops/prop_shop_shelf_a.mdl', + 'models/props_shops/prop_shop_shelf_b.mdl', + 'models/props_shops/prop_shop_shelf_c.mdl', + 'models/props_shops/prop_shop_shelf_d.mdl', + 'models/props_shops/prop_shop_shelf_e.mdl', + 'models/props_shops/prop_shop_shelf_f.mdl', + 'models/props_shops/prop_shop_shelf_g.mdl', + 'models/props_shops/prop_shop_shelf_g2.mdl', + 'models/props_shops/prop_shop_shelf_h.mdl', + 'models/props_shops/prop_shop_shelf_i.mdl', + 'models/props_shops/prop_shop_shelf_j.mdl', + 'models/props_shops/prop_shop_sign_a.mdl', + 'models/props_shops/prop_shop_sign_b.mdl', + 'models/props_shops/prop_shop_sign_c.mdl', + 'models/props_shops/prop_shop_sign_d.mdl', + 'models/props_shops/prop_shop_sign_floor_a.mdl', + 'models/props_shops/prop_shop_sign_floor_b.mdl', + 'models/props_shops/prop_shop_table.mdl', + }, + {'На дороге', + 'models/gta_prop_michou/prop_food_van_01.mdl', + 'models/gta_prop_michou/prop_food_van_02.mdl', + 'models/sal/vending/hotdog3.mdl', + 'models/props/de_tides/vending_cart.mdl', + 'models/props/de_tides/vending_cart_base.mdl', + 'models/props/de_tides/vending_cart_base.mdl', + 'models/zerochain/fruitslicerjob/fs_shop.mdl', + 'models/zerochain/fruitslicerjob/fs_shop_glass.mdl', + 'models/zerochain/fruitslicerjob/fs_shop_india.mdl', + 'models/zerochain/fruitslicerjob/fs_wheel.mdl', + }, + {'Касса', + 'models/props_interiors/cashregister01.mdl', + 'models/gta_prop_michou/v_ret_gc_cashreg.mdl', + 'models/mark2580/gtav/tequilala_map/misc/prop_till_03.mdl', + 'models/sal/ammunation/register.mdl', + 'models/props_shops/prop_shop_cashregister.mdl', + 'models/sickness/cashregister_pos.mdl', + 'models/props_c17/cashregister01a.mdl', + 'models/props_mall/cash_register.mdl', + }, + {'Повар', + 'models/gta_prop_michou/prop_cooker_03.mdl', + 'models/gta_prop_michou/prop_ff_counter_01.mdl', + 'models/gta_prop_michou/prop_ff_counter_02.mdl', + 'models/gta_prop_michou/prop_ff_counter_03.mdl', + 'models/gta_prop_michou/prop_ff_sink_02.mdl', + 'models/gta_prop_michou/prop_griddle_02.mdl', + 'models/gta_prop_michou/prop_kebab_grill.mdl', + 'models/gta_prop_michou/prop_food_bin_01.mdl', + 'models/gta_prop_michou/prop_food_bs_bshelf.mdl', + 'models/gta_prop_michou/prop_food_bs_soda_01.mdl', + 'models/gta_prop_michou/prop_food_bs_soda_02.mdl', + 'models/props_equipment/fountain_drinks.mdl', + 'models/sickness/bk_booth2.mdl', + 'models/props_downtown/booth01.mdl', + 'models/props_downtown/booth02.mdl', + 'models/props_warehouse/table_01.mdl', + 'models/props/de_tides/menu_stand.mdl', + 'models/props_interiors/table_picnic.mdl', + 'models/props/cs_militia/wood_bench.mdl', + 'models/props/cs_militia/wood_table.mdl', + 'models/props_urban/plastic_chair001.mdl', + 'models/props_interiors/patio_chair2_white.mdl', + 'models/props/de_tides/patio_chair2.mdl', + 'models/props/de_tides/patio_table2.mdl', + 'models/props/de_tides/patio_table.mdl', + }, + {'Супермаркет', + 'models/gta_prop_michou/2478.mdl', + 'models/gta_prop_michou/247_1.mdl', + 'models/gta_prop_michou/247_2.mdl', + 'models/gta_prop_michou/cqdre.mdl', + 'models/gta_prop_michou/cs6_01_247_market_det.mdl', + 'models/gta_prop_michou/hei_v_72_carpet02.mdl', + 'models/gta_prop_michou/prop_atm_01.mdl', + 'models/gta_prop_michou/prop_cctv_02_sm.mdl', + 'models/gta_prop_michou/prop_food_bs.mdl', + 'models/gta_prop_michou/prop_gumball_02.mdl', + 'models/gta_prop_michou/prop_gumball_03.mdl', + 'models/gta_prop_michou/prop_juice_dispenser.mdl', + 'models/gta_prop_michou/prop_rub_trolley01a.mdl', + 'models/gta_prop_michou/prop_sacktruck_02b.mdl', + 'models/gta_prop_michou/prop_slush_dispenser.mdl', + 'models/gta_prop_michou/v_10_liquorfagdisplay.mdl', + 'models/gta_prop_michou/v_10_liquorporn.mdl', + 'models/gta_prop_michou/v_54_hall_cctv.mdl', + 'models/gta_prop_michou/v_6.mdl', + 'models/gta_prop_michou/v_66_po.mdl', + 'models/gta_prop_michou/v_66_po_2.mdl', + 'models/gta_prop_michou/v_68_fridgefood.mdl', + 'models/gta_prop_michou/v_68_mags.mdl', + 'models/gta_prop_michou/v_68_post.mdl', + 'models/gta_prop_michou/v_68_posters.mdl', + 'models/gta_prop_michou/v_ind_cm_fan.mdl', + 'models/gta_prop_michou/v_ret_247_donuts.mdl', + 'models/gta_prop_michou/v_ret_247_lottery.mdl', + 'models/gta_prop_michou/v_ret_247_lotterysign.mdl', + 'models/gta_prop_michou/v_ret_247_pharmstuff.mdl', + 'models/gta_prop_michou/v_ret_247_sweetcount.mdl', + 'models/gta_prop_michou/v_ret_247shelves01.mdl', + 'models/gta_prop_michou/v_ret_247shelves02.mdl', + 'models/gta_prop_michou/v_ret_247shelves03.mdl', + 'models/gta_prop_michou/v_ret_247shelves04.mdl', + 'models/gta_prop_michou/v_ret_247shelves05.mdl', + 'models/gta_prop_michou/v_ret_gassweets.mdl', + 'models/gta_prop_michou/v_ret_ml.mdl', + 'models/sal/ammunation/fagmachine1.mdl', + 'models/props_interiors/magazine_rack.mdl', + 'models/props_interiors/shelvinggrocery01.mdl', + 'models/props_interiors/shelvingstore01.mdl', + 'models/domestic/market_counter_curve.mdl', + 'models/domestic/market_counter_flat.mdl', + 'models/domestic/market_counter_shelf.mdl', + 'models/domestic/market_counter_step.mdl', + 'models/furniture/shelves_industrial_01.mdl', + 'models/furniture/shelves_industrial_01_32w.mdl', + 'models/props_mall/mall_display_07.mdl', + 'models/props_mall/mall_display_09.mdl', + 'models/props_mall/mall_counter_corner.mdl', + 'models/props_mall/mall_counter_long.mdl', + 'models/props_mall/mall_counter_short.mdl', + 'models/props_mall/mall_display_04.mdl', + 'models/props_mall/mall_display_05.mdl', + 'models/props_unique/grocerystorechiller01.mdl', + 'models/props_mall/mall_display_06.mdl', + 'models/props_mall/mall_display_06b.mdl', + 'models/props_mall/mall_display_06c.mdl', + 'models/props_mall/mall_display_06d.mdl', + 'models/props_mall/mall_display_11.mdl', + }, + {'Торговые автоматы', + 'models/sal/vending/coffee.mdl', + 'models/gta_prop_michou/prop_vend_fridge01.mdl', + 'models/gta_prop_michou/prop_vend_snak_01_tu.mdl', + 'models/gta_prop_michou/prop_vend_soda_01.mdl', + 'models/sal/vending/sprunk.mdl', + 'models/props_canteen/vendingmachine01.mdl', + 'models/nt/props_street/vend1.mdl', + 'models/nt/props_street/vend2.mdl', + 'models/nt/props_street/vend3.mdl', + 'models/nt/props_street/vend4.mdl', + 'models/nt/props_street/vend5.mdl', + }, + {'Развлечения', + 'models/props_downtown/pooltable.mdl', + 'models/de_vegas/card_table.mdl', + 'models/maxib123/pooltable.mdl', + 'models/mark2580/gtav/tequilala_map/basement/prop_pooltable_02.mdl', + 'models/mark2580/gtav/tequilala_map/basement/prop_arcade_01.mdl', + 'models/mark2580/gtav/tequilala_map/basement/prop_dart_bd_cab_01.mdl', + 'models/mark2580/gtav/tequilala_map/basement/prop_jukebox_01.mdl', + 'models/mark2580/gtav/tequilala_map/electrical/club_roc_mixer1.mdl', + 'models/mark2580/gtav/tequilala_map/electrical/club_roc_zstand.mdl', + 'models/mark2580/gtav/tequilala_map/electrical/club_roc_mixer2.mdl', + 'models/mark2580/gtav/tequilala_map/electrical/club_vu_deckcase.mdl', + 'models/props_vtmb/turntable.mdl', + 'models/props_furniture/piano.mdl', + 'models/props_furniture/piano_bench.mdl', + 'models/sims/piano.mdl', + 'models/acoustic guitar/acousticguitar.mdl', + 'models/themask/scenebuildthemes/toys/abnd_decor_pinball.mdl', + 'models/themask/scenebuildthemes/toys/mining_arcade_bp.mdl', + 'models/themask/scenebuildthemes/toys/sm_dart_board_01a.mdl', + }, + {'Клуб', + 'models/mark2580/gtav/tequilala_map/electrical/club_roc_mscreen.mdl', + 'models/mark2580/gtav/tequilala_map/misc/serv_waste_bin1.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_cabamp.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_eq1.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_eq2.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_gstand.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_micstd.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_cab1.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_cab2.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_cab3.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_monitor.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_spot_b.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_spot_g.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_spot_off.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_spot_r.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_spot_w.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/club_roc_spot_y.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/prop_el_guitar_03.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/drums.mdl', + 'models/mark2580/gtav/tequilala_map/scene_stuff/prop_speaker_06.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad1.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad2.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad3.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad4.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad5.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad6.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad7.mdl', + 'models/mark2580/gtav/tequilala_map/signs_ads/ad8.mdl', + 'models/mark2580/gtav/tequilala_map/basement/ceiling_lamp.mdl', + 'models/mark2580/gtav/tequilala_map/basement/ceiling_vent.mdl', + 'models/mark2580/gtav/tequilala_map/basement/flyers_01.mdl', + 'models/mark2580/gtav/tequilala_map/basement/flyers_04.mdl', + 'models/mark2580/gtav/tequilala_map/basement/flyers_05.mdl', + 'models/mark2580/gtav/tequilala_map/basement/flyers_06.mdl', + 'models/mark2580/gtav/tequilala_map/basement/flyers_07.mdl', + 'models/mark2580/gtav/tequilala_map/basement/prop_pool_rack_02.mdl', + 'models/mark2580/gtav/tequilala_map/basement/wall_lamp_01.mdl', + 'models/mark2580/gtav/tequilala_map/basement/wall_lamp_02.mdl', + 'models/mark2580/gtav/tequilala_map/decor/2flr_bar_ads_02.mdl', + 'models/mark2580/gtav/tequilala_map/decor/back_stair_lamps.mdl', + 'models/mark2580/gtav/tequilala_map/decor/ceiling_vent.mdl', + 'models/mark2580/gtav/tequilala_map/decor/scene_bar_ads_01.mdl', + 'models/mark2580/gtav/tequilala_map/decor/scene_bar_ads_04.mdl', + 'models/mark2580/gtav/tequilala_map/entry/entry_lamp.mdl', + 'models/mark2580/gtav/tequilala_map/entry/entry_window_frame.mdl', + 'models/mark2580/gtav/tequilala_map/entry/flyers_01.mdl', + 'models/mark2580/gtav/tequilala_map/entry/flyers_02.mdl', + 'models/mark2580/gtav/tequilala_map/entry/flyers_03.mdl', + 'models/mark2580/gtav/tequilala_map/entry/flyers_04.mdl', + 'models/mark2580/gtav/tequilala_map/entry/flyers_05.mdl', + 'models/mark2580/gtav/tequilala_map/entry/misc_stuff.mdl', + 'models/mark2580/gtav/tequilala_map/entry/pole_rope.mdl', + 'models/mark2580/gtav/tequilala_map/misc/2flr_2ext_lamp.mdl', + 'models/mark2580/gtav/tequilala_map/misc/dj_zone_lamp.mdl', + 'models/mark2580/gtav/tequilala_map/misc/exit_sign.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_01.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_02.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_03.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_04.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_05.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_06.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_07.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_08.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_09.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_10.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_11.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_12.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_13.mdl', + 'models/mark2580/gtav/tequilala_map/misc/flyers_14.mdl', + 'models/mark2580/gtav/tequilala_map/misc/metal_balk_2_2flr.mdl', + 'models/mark2580/gtav/tequilala_map/misc/sofa_2flr_lamp.mdl', + 'models/mark2580/gtav/tequilala_map/misc/up_bar_front.mdl', + 'models/mark2580/gtav/tequilala_map/misc/up_bar_rear.mdl', + 'models/mark2580/gtav/tequilala_map/misc/up_bar_shelf.mdl', + 'models/mark2580/gtav/tequilala_map/misc/up_bar_shelf_tv.mdl', + 'models/mark2580/gtav/tequilala_map/misc/up_bar_top.mdl', + 'models/mark2580/gtav/tequilala_map/scene/ceiling_rig.mdl', + 'models/mark2580/gtav/tequilala_map/scene/ceiling_stuff.mdl', + 'models/mark2580/gtav/tequilala_map/scene/column_shelf.mdl', + 'models/mark2580/gtav/tequilala_map/scene/curtains.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_01.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_02.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_03.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_05.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_06.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_07.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_08.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_09.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_10.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_11.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_12.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_13.mdl', + 'models/mark2580/gtav/tequilala_map/scene/flyers_14.mdl', + 'models/mark2580/gtav/tequilala_map/scene/lamp_01.mdl', + 'models/mark2580/gtav/tequilala_map/scene/lamp_02.mdl', + 'models/mark2580/gtav/tequilala_map/scene/metal_balk_2flr.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_bar_front.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_bar_rear.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_bar_shelf.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_bar_shelf_tv.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_bar_sink.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_bar_top.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_ropes.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_sofa_lamp_01.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_sofa_lamp_02.mdl', + 'models/mark2580/gtav/tequilala_map/scene/scene_wall_lamp.mdl', + 'models/mark2580/gtav/tequilala_map/scene/wall_shelf.mdl', + }, + {'Казино', + 'models/models/bkr_prop_money_pokerbucket.mdl', + 'models/models/h4_prop_casino_blckjack_01c.mdl', + 'models/models/vw_prop_casino_3cardpoker_01.mdl', + 'models/models/vw_prop_casino_roulette_01.mdl', + 'models/models/vw_prop_casino_track_chair_01.mdl', + 'models/models/vw_prop_chip_100dollar_x1.mdl', + 'models/models/vw_prop_chip_10dollar_x1.mdl', + 'models/models/vw_prop_chip_10kdollar_x1.mdl', + 'models/models/vw_prop_chip_1kdollar_x1.mdl', + 'models/models/vw_prop_chip_500dollar_x1.mdl', + 'models/models/vw_prop_chip_50dollar_x1.mdl', + 'models/models/vw_prop_chip_5kdollar_x1.mdl', + 'models/models/vw_prop_plaq_10kdollar_x1.mdl', + 'models/models/vw_prop_plaq_1kdollar_x1.mdl', + 'models/models/vw_prop_plaq_5kdollar_x1.mdl', + 'models/models/vw_prop_roulette_rake.mdl', + 'models/models/vw_prop_vw_chips_pile_01a.mdl', + 'models/models/vw_prop_vw_chips_pile_02a.mdl', + 'models/models/vw_prop_vw_chips_pile_03a.mdl', + 'models/models/vw_prop_vw_table_01a.mdl', + }, + {'Оружейная', + 'models/sal/ammunation/cabinet_center.mdl', + 'models/sal/ammunation/cabinet_corner.mdl', + 'models/sal/ammunation/cabinet_glass1.mdl', + 'models/sal/ammunation/cabinet_glass2_fix3.mdl', + 'models/sal/ammunation/cabinet_shelf_corner_4.mdl', + 'models/sal/ammunation/cabinet_shelf_end_fix2.mdl', + 'models/sal/ammunation/cabinet_shelf_fix.mdl', + 'models/sal/ammunation/shelf2.mdl', + 'models/sal/ammunation/shelf1_shelf.mdl', + 'models/sal/ammunation/shelf1.mdl', + 'models/sal/ammunation/firestand.mdl', + 'models/sal/ammunation/side3.mdl', + 'models/sal/ammunation/range_center.mdl', + 'models/sal/ammunation/range_stall.mdl', + 'models/sal/ammunation/range_stall_5.mdl', + 'models/sal/ammunation/range_nums.mdl', + 'models/sal/ammunation/range_top1.mdl', + 'models/sal/ammunation/range_top2.mdl', + 'models/sal/ammunation/lockers1.mdl', + 'models/sal/ammunation/americanflag.mdl', + 'models/sal/ammunation/award1.mdl', + 'models/sal/ammunation/award2.mdl', + 'models/sal/ammunation/award3.mdl', + 'models/sal/ammunation/award4.mdl', + 'models/sal/ammunation/award5.mdl', + 'models/sal/ammunation/award6.mdl', + 'models/sal/ammunation/bucket1.mdl', + 'models/sal/ammunation/cablecover.mdl', + 'models/sal/ammunation/coatrack.mdl', + 'models/sal/ammunation/counterdoor_1_fix.mdl', + 'models/sal/ammunation/exitsign.mdl', + 'models/sal/ammunation/fan.mdl', + 'models/sal/ammunation/floormat.mdl', + 'models/sal/ammunation/framedpic1.mdl', + 'models/sal/ammunation/framedpic2.mdl', + 'models/sal/ammunation/framedpic3.mdl', + 'models/sal/ammunation/framedpic4.mdl', + 'models/sal/ammunation/framedpic5.mdl', + 'models/sal/ammunation/framedpic6.mdl', + 'models/sal/ammunation/framedpic7.mdl', + 'models/sal/ammunation/gascan.mdl', + 'models/sal/ammunation/poster1.mdl', + 'models/sal/ammunation/poster10.mdl', + 'models/sal/ammunation/poster13.mdl', + 'models/sal/ammunation/poster14.mdl', + 'models/sal/ammunation/poster15.mdl', + 'models/sal/ammunation/poster19.mdl', + 'models/sal/ammunation/poster2.mdl', + 'models/sal/ammunation/poster20.mdl', + 'models/sal/ammunation/poster21.mdl', + 'models/sal/ammunation/poster22.mdl', + 'models/sal/ammunation/poster24.mdl', + 'models/sal/ammunation/poster25.mdl', + 'models/sal/ammunation/poster26.mdl', + 'models/sal/ammunation/poster27.mdl', + 'models/sal/ammunation/poster28.mdl', + 'models/sal/ammunation/poster29.mdl', + 'models/sal/ammunation/poster3.mdl', + 'models/sal/ammunation/poster30.mdl', + 'models/sal/ammunation/poster31.mdl', + 'models/sal/ammunation/poster32.mdl', + 'models/sal/ammunation/poster33.mdl', + 'models/sal/ammunation/poster35.mdl', + 'models/sal/ammunation/poster36.mdl', + 'models/sal/ammunation/poster37.mdl', + 'models/sal/ammunation/poster38.mdl', + 'models/sal/ammunation/poster39.mdl', + 'models/sal/ammunation/poster4.mdl', + 'models/sal/ammunation/poster40.mdl', + 'models/sal/ammunation/poster41.mdl', + 'models/sal/ammunation/poster43.mdl', + 'models/sal/ammunation/poster44.mdl', + 'models/sal/ammunation/poster45.mdl', + 'models/sal/ammunation/poster46.mdl', + 'models/sal/ammunation/poster48.mdl', + 'models/sal/ammunation/poster49.mdl', + 'models/sal/ammunation/poster5.mdl', + 'models/sal/ammunation/poster50.mdl', + 'models/sal/ammunation/poster51.mdl', + 'models/sal/ammunation/poster53.mdl', + 'models/sal/ammunation/poster55.mdl', + 'models/sal/ammunation/poster56.mdl', + 'models/sal/ammunation/poster57.mdl', + 'models/sal/ammunation/poster59.mdl', + 'models/sal/ammunation/poster6.mdl', + 'models/sal/ammunation/poster60.mdl', + 'models/sal/ammunation/poster61.mdl', + 'models/sal/ammunation/poster62.mdl', + 'models/sal/ammunation/poster63.mdl', + 'models/sal/ammunation/poster64.mdl', + 'models/sal/ammunation/poster65.mdl', + 'models/sal/ammunation/poster8.mdl', + 'models/sal/ammunation/poster9.mdl', + 'models/sal/ammunation/range_casings.mdl', + 'models/sal/ammunation/rug.mdl', + 'models/sal/ammunation/poster47.mdl', + 'models/sal/ammunation/shirt4.mdl', + 'models/sal/ammunation/shirt5.mdl', + 'models/sal/ammunation/shirt6.mdl', + 'models/sal/ammunation/sleepingbag3.mdl', + 'models/sal/ammunation/sleepingbag1.mdl', + 'models/sal/ammunation/sleepingbag2.mdl', + 'models/sal/ammunation/sprinkler.mdl', + 'models/sal/ammunation/poster58.mdl', + 'models/sal/ammunation/target1.mdl', + 'models/sal/ammunation/target2.mdl', + 'models/sal/ammunation/target3.mdl', + 'models/sal/ammunation/target4.mdl', + 'models/sal/ammunation/target_move1.mdl', + 'models/sal/ammunation/target_move2.mdl', + 'models/sal/ammunation/target_move3.mdl', + 'models/sal/ammunation/usflag.mdl', + 'models/sal/ammunation/vent.mdl', + 'models/props_unique/spawn_apartment/coffeeammo.mdl', + 'models/dansgunshelf/dansgunshelf.mdl', + }, + {'Оружие', + 'models/sal/ammunation/nightstick.mdl', + 'models/sal/ammunation/knife.mdl', + 'models/sal/ammunation/knifeholder1.mdl', + 'models/sal/ammunation/knifeholder2.mdl', + 'models/sal/ammunation/pistol1.mdl', + 'models/sal/ammunation/pistol2.mdl', + 'models/sal/ammunation/pistol3.mdl', + 'models/sal/ammunation/pistol4.mdl', + 'models/sal/ammunation/large_gun.mdl', + 'models/sal/ammunation/assault1.mdl', + 'models/sal/ammunation/assault2.mdl', + 'models/sal/ammunation/assault3.mdl', + 'models/sal/ammunation/launcher.mdl', + 'models/sal/ammunation/machinegun1.mdl', + 'models/sal/ammunation/machinegun2.mdl', + 'models/sal/ammunation/minigun.mdl', + 'models/sal/ammunation/mp5.mdl', + 'models/sal/ammunation/uzi.mdl', + 'models/sal/ammunation/sniper1.mdl', + 'models/sal/ammunation/sniper2.mdl', + 'models/sal/ammunation/rocketprop.mdl', + 'models/sal/ammunation/rpg.mdl', + 'models/sal/ammunation/shotgun1.mdl', + 'models/sal/ammunation/shotgun2.mdl', + 'models/sal/ammunation/shotgun3.mdl', + 'models/sal/ammunation/grenade1.mdl', + 'models/sal/ammunation/grenade2.mdl', + 'models/sal/ammunation/grenade3.mdl', + 'models/sal/ammunation/largebullet.mdl', + 'models/sal/ammunation/ammo_buckshot.mdl', + 'models/sal/ammunation/ammo_longrifle1.mdl', + 'models/sal/ammunation/ammo_longrifle2.mdl', + 'models/sal/ammunation/ammo_pistol_closed.mdl', + 'models/sal/ammunation/ammo_rifle_closed.mdl', + 'models/sal/ammunation/ammo_shotgun_closed.mdl', + 'models/sal/ammunation/ammo_shotgun_open.mdl', + 'models/sal/ammunation/ammobox_closed.mdl', + 'models/sal/ammunation/ammobox_open.mdl', + 'models/sal/ammunation/ammocrate.mdl', + 'models/sal/ammunation/ammocrate2.mdl', + 'models/sal/ammunation/guncase.mdl', + 'models/sal/ammunation/guncase1.mdl', + 'models/sal/ammunation/guncase2.mdl', + 'models/craphead_scripts/armory_robbery2/w_lockpick.mdl', + 'models/weapons/w_hacktool.mdl', + }, + {'Снаряжение', + 'models/sal/ammunation/armour1.mdl', + 'models/sal/ammunation/armour2.mdl', + 'models/sal/ammunation/armour3.mdl', + 'models/sal/ammunation/armour4.mdl', + 'models/sal/ammunation/armour5.mdl', + 'models/sal/ammunation/parachute.mdl', + 'models/sal/ammunation/shirt1.mdl', + 'models/sal/ammunation/shirt2.mdl', + 'models/sal/ammunation/shirt3.mdl', + 'models/sal/ammunation/shoe1.mdl', + 'models/sal/ammunation/shoe2.mdl', + 'models/sal/ammunation/shoe3.mdl', + 'models/sal/ammunation/clothesrack.mdl', + 'models/sal/ammunation/gasmask.mdl', + 'models/sal/ammunation/headphones1.mdl', + 'models/sal/ammunation/headphones2.mdl', + 'models/sal/ammunation/headphones3.mdl', + 'models/sal/ammunation/protective_glasses.mdl', + 'models/sal/ammunation/protective_glasses_folded.mdl', + 'models/props_interiors/closet_clothes.mdl', + 'models/props_mall/clothing_dress_shirts.mdl', + 'models/props_mall/clothing_jeans.mdl', + 'models/props_mall/clothing_pants01.mdl', + 'models/props_mall/clothing_pants02.mdl', + 'models/props_mall/clothing_pants_draping01.mdl', + 'models/props_mall/clothing_pants_draping02.mdl', + 'models/props_mall/clothing_shirts01.mdl', + 'models/props_mall/clothing_shirts02.mdl', + 'models/props_mall/clothing_shirts03.mdl', + 'models/props_mall/clothing_shirts_draping01.mdl', + 'models/props_mall/clothing_tshirts.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/sm_bulletproofvest_01.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/sm_clothes_hanging_01.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/sm_clothes_hanging_02.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/sm_clothes_hanging_03.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/sm_clothes_pile_01.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/sm_clothes_pile_joy_01.mdl', + }, + {'Аэропорт', + 'models/props_interiors/chairs_airport.mdl', + 'models/props_trainstation/traincar_seats001.mdl', + }, + {'Мастерская', + 'models/mark2580/gtav/garage_stuff/air_compressor_01a.mdl', + 'models/mark2580/gtav/garage_stuff/aircon_s_05a.mdl', + 'models/mark2580/gtav/garage_stuff/aircon_s_07b.mdl', + 'models/mark2580/gtav/garage_stuff/bench_grinder_01a.mdl', + 'models/mark2580/gtav/garage_stuff/bench_vice_01a.mdl', + 'models/mark2580/gtav/garage_stuff/benchvice.mdl', + 'models/mark2580/gtav/garage_stuff/bike_battery_01.mdl', + 'models/mark2580/gtav/garage_stuff/blowtorch.mdl', + 'models/mark2580/gtav/garage_stuff/bucket.mdl', + 'models/mark2580/gtav/garage_stuff/bucket_02a.mdl', + 'models/mark2580/gtav/garage_stuff/bugzap.mdl', + 'models/mark2580/gtav/garage_stuff/bumper_01.mdl', + 'models/mark2580/gtav/garage_stuff/bumper_02.mdl', + 'models/mark2580/gtav/garage_stuff/bumper_03.mdl', + 'models/mark2580/gtav/garage_stuff/bumper_04.mdl', + 'models/mark2580/gtav/garage_stuff/bumper_05.mdl', + 'models/mark2580/gtav/garage_stuff/bumper_06.mdl', + 'models/mark2580/gtav/garage_stuff/cable02.mdl', + 'models/mark2580/gtav/garage_stuff/car_battery_01.mdl', + 'models/mark2580/gtav/garage_stuff/car_bonnet_01.mdl', + 'models/mark2580/gtav/garage_stuff/car_bonnet_02.mdl', + 'models/mark2580/gtav/garage_stuff/car_door_01.mdl', + 'models/mark2580/gtav/garage_stuff/car_door_02.mdl', + 'models/mark2580/gtav/garage_stuff/car_door_03.mdl', + 'models/mark2580/gtav/garage_stuff/car_door_04.mdl', + 'models/mark2580/gtav/garage_stuff/car_exhaust_01.mdl', + 'models/mark2580/gtav/garage_stuff/car_jack_01a.mdl', + 'models/mark2580/gtav/garage_stuff/car_seat.mdl', + 'models/mark2580/gtav/garage_stuff/car_seat2.mdl', + 'models/mark2580/gtav/garage_stuff/carcreeper.mdl', + 'models/mark2580/gtav/garage_stuff/carjack.mdl', + 'models/mark2580/gtav/garage_stuff/carrack.mdl', + 'models/mark2580/gtav/garage_stuff/clamp_01.mdl', + 'models/mark2580/gtav/garage_stuff/compressor_01.mdl', + 'models/mark2580/gtav/garage_stuff/compressor_02.mdl', + 'models/mark2580/gtav/garage_stuff/compressor_03.mdl', + 'models/mark2580/gtav/garage_stuff/cs_hammer.mdl', + 'models/mark2580/gtav/garage_stuff/custom_engine.mdl', + 'models/mark2580/gtav/garage_stuff/drill.mdl', + 'models/mark2580/gtav/garage_stuff/drill_01a.mdl', + 'models/mark2580/gtav/garage_stuff/engine_hoist_02a.mdl', + 'models/mark2580/gtav/garage_stuff/etricmotor_01.mdl', + 'models/mark2580/gtav/garage_stuff/exhaust_pipe.mdl', + 'models/mark2580/gtav/garage_stuff/gascanister.mdl', + 'models/mark2580/gtav/garage_stuff/grinder.mdl', + 'models/mark2580/gtav/garage_stuff/grinder_01a.mdl', + 'models/mark2580/gtav/garage_stuff/hndtrk_n2.mdl', + 'models/mark2580/gtav/garage_stuff/impact_driver_01a.mdl', + 'models/mark2580/gtav/garage_stuff/impexp_hammer.mdl', + 'models/mark2580/gtav/garage_stuff/jerrycan01.mdl', + 'models/mark2580/gtav/garage_stuff/ladder.mdl', + 'models/mark2580/gtav/garage_stuff/lubcan.mdl', + 'models/mark2580/gtav/garage_stuff/mallet.mdl', + 'models/mark2580/gtav/garage_stuff/metalfoodjar_01.mdl', + 'models/mark2580/gtav/garage_stuff/mpgjackframe.mdl', + 'models/mark2580/gtav/garage_stuff/oilbot01.mdl', + 'models/mark2580/gtav/garage_stuff/oilbot02.mdl', + 'models/mark2580/gtav/garage_stuff/oilbot03.mdl', + 'models/mark2580/gtav/garage_stuff/oilbot04.mdl', + 'models/mark2580/gtav/garage_stuff/oilbot05.mdl', + 'models/mark2580/gtav/garage_stuff/oiltub.mdl', + 'models/mark2580/gtav/garage_stuff/paint_spray01a.mdl', + 'models/mark2580/gtav/garage_stuff/paint_spray01b.mdl', + 'models/mark2580/gtav/garage_stuff/paintbckt.mdl', + 'models/mark2580/gtav/garage_stuff/paltruck.mdl', + 'models/mark2580/gtav/garage_stuff/plastic_canister.mdl', + 'models/mark2580/gtav/garage_stuff/pliers.mdl', + 'models/mark2580/gtav/garage_stuff/pliers_01.mdl', + 'models/mark2580/gtav/garage_stuff/pliers_02.mdl', + 'models/mark2580/gtav/garage_stuff/pliers_03.mdl', + 'models/mark2580/gtav/garage_stuff/powersaw.mdl', + 'models/mark2580/gtav/garage_stuff/rasp_01.mdl', + 'models/mark2580/gtav/garage_stuff/rasp_02.mdl', + 'models/mark2580/gtav/garage_stuff/rasp_03.mdl', + 'models/mark2580/gtav/garage_stuff/sacktruck_01.mdl', + 'models/mark2580/gtav/garage_stuff/sand_blaster_01a.mdl', + 'models/mark2580/gtav/garage_stuff/screwdvr01.mdl', + 'models/mark2580/gtav/garage_stuff/screwdvr02.mdl', + 'models/mark2580/gtav/garage_stuff/screwdvr03.mdl', + 'models/mark2580/gtav/garage_stuff/sdriver_01.mdl', + 'models/mark2580/gtav/garage_stuff/sdriver_02.mdl', + 'models/mark2580/gtav/garage_stuff/shelfes_01.mdl', + 'models/mark2580/gtav/garage_stuff/shelfes_02.mdl', + 'models/mark2580/gtav/garage_stuff/shelfes_03.mdl', + 'models/mark2580/gtav/garage_stuff/shelfrk.mdl', + 'models/mark2580/gtav/garage_stuff/smallplasticbox.mdl', + 'models/mark2580/gtav/garage_stuff/span_01.mdl', + 'models/mark2580/gtav/garage_stuff/span_02.mdl', + 'models/mark2580/gtav/garage_stuff/span_03.mdl', + 'models/mark2580/gtav/garage_stuff/spray.mdl', + 'models/mark2580/gtav/garage_stuff/stepladder.mdl', + 'models/mark2580/gtav/garage_stuff/tape_01.mdl', + 'models/mark2580/gtav/garage_stuff/tool_box_01.mdl', + 'models/mark2580/gtav/garage_stuff/tool_box_01b.mdl', + 'models/mark2580/gtav/garage_stuff/tool_box_02.mdl', + 'models/mark2580/gtav/garage_stuff/tool_box_02b.mdl', + 'models/mark2580/gtav/garage_stuff/tool_box_04.mdl', + 'models/mark2580/gtav/garage_stuff/tool_cabinet_01a.mdl', + 'models/mark2580/gtav/garage_stuff/tool_cabinet_01b.mdl', + 'models/mark2580/gtav/garage_stuff/tool_cabinet_01c.mdl', + 'models/mark2580/gtav/garage_stuff/tool_chest_01a.mdl', + 'models/mark2580/gtav/garage_stuff/tool_draw_01a.mdl', + 'models/mark2580/gtav/garage_stuff/tool_draw_01b.mdl', + 'models/mark2580/gtav/garage_stuff/tool_draw_01c.mdl', + 'models/mark2580/gtav/garage_stuff/tool_draw_01d.mdl', + 'models/mark2580/gtav/garage_stuff/tool_draw_01e.mdl', + 'models/mark2580/gtav/garage_stuff/tool_torch.mdl', + 'models/mark2580/gtav/garage_stuff/toolboard.mdl', + 'models/mark2580/gtav/garage_stuff/toolchest_01.mdl', + 'models/mark2580/gtav/garage_stuff/toolchest_02.mdl', + 'models/mark2580/gtav/garage_stuff/toolchest_03.mdl', + 'models/mark2580/gtav/garage_stuff/toolchest_04.mdl', + 'models/mark2580/gtav/garage_stuff/toolchest_05.mdl', + 'models/props_warehouse/toolbox.mdl', + 'models/mark2580/gtav/garage_stuff/tools_cabinet_01.mdl', + 'models/mark2580/gtav/garage_stuff/tools_cabinet_02.mdl', + 'models/mark2580/gtav/garage_stuff/tools_wood_table.mdl', + 'models/mark2580/gtav/garage_stuff/torque_wrench_01a.mdl', + 'models/mark2580/gtav/garage_stuff/transmission_lift_01a.mdl', + 'models/mark2580/gtav/garage_stuff/vise.mdl', + 'models/mark2580/gtav/garage_stuff/welder_ragdoll.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_01.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_02.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_03.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_04.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_05.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_06.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_balancer_01a.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_balancer_01a_ragdoll.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_hub_01.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_rim_01.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_rim_02.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_rim_03.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_rim_04.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_rim_05.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_tyre.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_tyre2.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_tyre3.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_tyre4.mdl', + 'models/mark2580/gtav/garage_stuff/wheel_tyre5.mdl', + 'models/mark2580/gtav/garage_stuff/wood_shelf_01.mdl', + 'models/mark2580/gtav/garage_stuff/wood_shelf_02.mdl', + 'models/mark2580/gtav/garage_stuff/wrench.mdl', + 'models/mark2580/gtav/garage_stuff/wrench2.mdl', + 'models/mark2580/gtav/garage_stuff/wrench3.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/fire_alarm_control.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/garage_unitlarge.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/garagepartition.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/garagepartition_glass.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/elecbox_20.mdl', + 'models/codeandmodeling_samgaze_gta/gr_prop_gr_lathe_01b.mdl', + 'models/codeandmodeling_samgaze_gta/gr_prop_gr_lathe_01c.mdl', + 'models/codeandmodeling_samgaze_gta/gr_prop_gr_speeddrill_0.mdl', + 'models/codeandmodeling_samgaze_gta/gr_prop_gr_vertmill_01c.mdl', + 'models/codeandmodeling_samgaze_gta/prop_tool_bench02.mdl', + 'models/brickscrafting/workbench_1.mdl', + 'models/brickscrafting/workbench_2.mdl', + 'models/props/de_nuke/nuclearcontrolbox.mdl', + 'models/props/de_nuke/nucleartestcabinet.mdl', + 'models/props_lab/eyescanner.mdl', + 'models/props_lab/keypad.mdl', + 'models/items/battery.mdl', + 'models/items/car_battery01.mdl', + 'models/props_lab/crematorcase.mdl', + 'models/xqm/hydcontrolbox.mdl', + 'models/props_lab/powerbox01a.mdl', + 'models/props_lab/powerbox02a.mdl', + 'models/props_lab/powerbox02b.mdl', + 'models/props_warehouse/toolbox.mdl', + 'models/props_canal/winch02.mdl', + 'models/props/cs_militia/sawhorse.mdl', + 'models/props_c17/handrail04_medium.mdl', + 'models/props/cs_militia/reload_scale.mdl', + 'models/props/cs_militia/reloadingpress01.mdl', + 'models/props_canal/mattpipe.mdl', + 'models/weapons/w_spade.mdl', + 'models/props_junk/shovel01a.mdl', + 'models/props/cs_militia/axe.mdl', + 'models/props_c17/tools_pliers01a.mdl', + 'models/props_c17/tools_wrench01a.mdl', + 'models/props_c17/trappropeller_lever.mdl', + 'models/props/cs_militia/circularsaw01.mdl', + 'models/props_c17/trappropeller_blade.mdl', + 'models/props_vehicles/carparts_axel01a.mdl', + 'models/props_vehicles/carparts_muffler01a.mdl', + 'models/props_vehicles/carparts_tire01a.mdl', + 'models/props_vehicles/carparts_wheel01a.mdl', + 'models/props_vehicles/carparts_door01a.mdl', + 'models/props/de_train/biohazardtank_dm_10.mdl', + 'models/thrusters/jetpack.mdl', + 'models/props_c17/trappropeller_engine.mdl', + 'models/gibs/airboat_broken_engine.mdl', + 'models/props/de_nuke/equipment2.mdl', + 'models/props/de_nuke/equipment3a.mdl', + 'models/props/de_nuke/equipment3b.mdl', + 'models/props/de_train/processor.mdl', + 'models/props/de_train/processor_nobase.mdl', + 'models/props_wasteland/laundry_washer003.mdl', + 'models/props/de_nuke/powerplanttank.mdl', + 'models/props_wasteland/laundry_washer001a.mdl', + 'models/props_industrial/oil_storage.mdl', + 'models/props_vehicles/generatortrailer01.mdl', + 'models/props_industrial/winch_deck.mdl', + 'models/props_industrial/winch_stern.mdl', + 'models/props_c17/canister01a.mdl', + 'models/props_c17/canister02a.mdl', + 'models/props_junk/propanecanister001a.mdl', + 'models/props_citizen_tech/firetrap_propanecanister01a.mdl', + 'models/props_c17/canister_propane01a.mdl', + 'models/props_borealis/bluebarrel001.mdl', + 'models/props_c17/woodbarrel001.mdl', + 'models/props/de_train/barrel.mdl', + 'models/props/cs_militia/paintbucket01.mdl', + 'models/props_wasteland/laundry_basket001.mdl', + 'models/props/de_prodigy/concretebags.mdl', + 'models/props/de_prodigy/concretebags3.mdl', + 'models/props/de_prodigy/concretebags2.mdl', + 'models/props/cs_militia/sheetrock_leaning.mdl', + 'models/props/cs_militia/fertilizer.mdl', + 'models/props_junk/trashdumpster02b.mdl', + 'models/props_junk/wood_pallet001a.mdl', + 'models/props_junk/cardboard_box001a.mdl', + 'models/props_junk/cardboard_box002a.mdl', + 'models/props_junk/cardboard_box003a.mdl', + 'models/props_junk/cardboard_box004a.mdl', + 'models/props_lab/box01a.mdl', + 'models/props/cs_assault/dryer_box.mdl', + 'models/props/cs_assault/washer_box.mdl', + 'models/props/de_nuke/nuclearcontainerboxclosed.mdl', + 'models/props/cs_office/cardboard_box02.mdl', + 'models/props/cs_office/cardboard_box03.mdl', + 'models/items/item_item_crate.mdl', + 'models/props_junk/wood_crate001a_damagedmax.mdl', + 'models/props/de_nuke/crate_extrasmall.mdl', + 'models/props/cs_militia/boxes_frontroom.mdl', + 'models/props/cs_militia/food_stack.mdl', + 'models/props/de_nuke/winch.mdl', + 'models/props_wasteland/cranemagnet01a.mdl', + 'models/props/cs_assault/wirepipe.mdl', + 'models/props_c17/gasmeter001a.mdl', + 'models/props/de_nuke/electricalbox02.mdl', + 'models/props_c17/gasmeter002a.mdl', + 'models/props_c17/gasmeter003a.mdl', + 'models/props/cs_militia/fencewoodlog04_long.mdl', + 'models/props_lab/pipesystem01b.mdl', + 'models/props_wasteland/pipecluster003a_small.mdl', + 'models/props_pipes/pipecluster08d_extender128.mdl', + 'models/props_pipes/pipecluster08d_extender64.mdl', + 'models/props_pipes/valve001.mdl', + 'models/props_pipes/valve002.mdl', + 'models/props_pipes/valve003.mdl', + 'models/props_pipes/valvewheel002.mdl', + 'models/props_wasteland/gear01.mdl', + 'models/props_wasteland/gear02.mdl', + 'models/props/de_prodigy/spool.mdl', + 'models/props/de_prodigy/spoolwire.mdl', + 'models/props_office/aircan.mdl', + }, + {'Склад', + 'models/gta_prop_maxime/prop_barier_conc_05b.mdl', + 'models/gta_prop_maxime/prop_barrel_exp_01a.mdl', + 'models/gta_prop_maxime/prop_boxpile_10a.mdl', + 'models/gta_prop_maxime/prop_boxpile_10b.mdl', + 'models/gta_prop_maxime/prop_cementmixer_02a.mdl', + 'models/gta_prop_maxime/prop_conc_blocks01a.mdl', + 'models/gta_prop_maxime/prop_cons_ply02.mdl', + 'models/gta_prop_maxime/prop_container_old1.mdl', + 'models/sal/ammunation/container_fix2.mdl', + 'models/gta_prop_maxime/prop_contnr_pile_01a.mdl', + 'models/gta_prop_maxime/prop_crosssaw_01.mdl', + 'models/gta_prop_maxime/prop_diggerbkt_01.mdl', + 'models/gta_prop_maxime/prop_drywallpile_01.mdl', + 'models/gta_prop_maxime/prop_flattruck_01a.mdl', + 'models/gta_prop_maxime/prop_generator_03a.mdl', + 'models/gta_prop_maxime/prop_girder_01b.mdl', + 'models/gta_prop_maxime/prop_metal_plates01.mdl', + 'models/gta_prop_maxime/prop_metal_plates02.mdl', + 'models/gta_prop_maxime/prop_paints_pallete01.mdl', + 'models/gta_prop_maxime/prop_pile_dirt_01.mdl', + 'models/gta_prop_maxime/prop_pipes_01b.mdl', + 'models/gta_prop_maxime/prop_plywoodpile_01a.mdl', + 'models/gta_prop_maxime/prop_rebar_pile01.mdl', + 'models/gta_prop_maxime/prop_skip_06a.mdl', + 'models/gta_prop_maxime/prop_tool_bench01.mdl', + 'models/gta_prop_maxime/prop_wheelbarrow02a.mdl', + 'models/gta_prop_maxime/prop_woodpile_02a.mdl', + 'models/props_industrial/warehouse_shelf001.mdl', + 'models/props_industrial/warehouse_shelf002.mdl', + 'models/props_industrial/warehouse_shelf003.mdl', + 'models/props_industrial/warehouse_shelf004.mdl', + 'models/props/cs_assault/moneypallete.mdl', + 'models/props/cs_assault/moneypallet03.mdl', + 'models/props/cs_assault/handtruck.mdl', + 'models/props/cs_assault/forklift.mdl', + 'models/props/cs_office/paperbox_pile_01.mdl', + 'models/props/cs_office/cardboard_box03.mdl', + 'models/props/de_dust/stoneblock01a.mdl', + 'models/props/cs_militia/shelves.mdl', + 'models/props/cs_militia/shelves_wood.mdl', + 'models/props/cs_assault/forklift.mdl', + 'models/props/cs_assault/handtruck.mdl', + 'models/props_junk/pushcart01a.mdl', + 'models/props/de_prodigy/pushcart.mdl', + 'models/props_wasteland/laundry_cart002.mdl', + }, + {'Телестудия', + 'models/sal/camera/camera_full.mdl', + 'models/sal/camera/camera_bottom.mdl', + 'models/sal/camera/camera_top.mdl', + 'models/props_vtmb/camcorder.mdl', + 'models/maxofs2d/camera.mdl', + 'models/tools/camera/camera.mdl', + }, + {'Фотостудия', + 'models/mmd/studio/camera.mdl', + 'models/mmd/studio/light_lamp.mdl', + 'models/mmd/studio/photo_studio.mdl', + 'models/mmd/studio/umbrella_flash.mdl', + }, + {'Спортзал', + 'models/sal/props/gym/prop_barbell_01.mdl', + 'models/sal/props/gym/prop_barbell_02.mdl', + 'models/sal/props/gym/prop_barbell_100kg.mdl', + 'models/sal/props/gym/prop_barbell_10kg.mdl', + 'models/sal/props/gym/prop_barbell_20kg.mdl', + 'models/sal/props/gym/prop_barbell_30kg.mdl', + 'models/sal/props/gym/prop_barbell_40kg.mdl', + 'models/sal/props/gym/prop_barbell_50kg.mdl', + 'models/sal/props/gym/prop_barbell_60kg.mdl', + 'models/sal/props/gym/prop_barbell_80kg.mdl', + 'models/sal/props/gym/prop_pris_bars_01_fix.mdl', + 'models/sal/props/gym/prop_weight_squat.mdl', + }, + {'Серверная', + 'models/props_computers/laptop.mdl', + 'models/props_computers/server_rack.mdl', + 'models/props_computers/server_rack_laptop.mdl', + 'models/props_lab/servers.mdl', + 'models/props_lab/workspace001.mdl', + 'models/props_lab/workspace002.mdl', + 'models/props_lab/workspace003.mdl', + 'models/props_lab/workspace004.mdl', + 'models/props/de_prodigy/wall_console1.mdl', + 'models/props/de_prodigy/wall_console3.mdl', + 'models/props/de_prodigy/wall_console2.mdl', + 'models/props_lab/plotter.mdl', + 'models/props_lab/tpplug.mdl', + 'models/props_lab/tpplugholder.mdl', + 'models/props_lab/tpplugholder_single.mdl', + 'models/props_lab/reciever01a.mdl', + 'models/props_lab/reciever01b.mdl', + 'models/props_lab/reciever01c.mdl', + 'models/props_lab/reciever01d.mdl', + 'models/props_lab/reciever_cart.mdl', + 'models/enities/hacktool/hacktool_serv_box.mdl', + 'models/enities/hacktool/hacktool_serv_door.mdl', + 'models/enities/hacktool/hacktool_serv_in.mdl', + }, + {'Прочее', + 'models/props/cs_italy/it_mkt_shelf1.mdl', + 'models/props_mall/mall_display_04.mdl', + 'models/props_mall/mall_display_11.mdl', + 'models/props_c17/furnitureshelf002a.mdl', + }, + {'Коробки', + 'models/physrbox/cardboard_flat.mdl', + 'models/physrbox/cardboard_medium.mdl', + 'models/physrbox/cardboard_small.mdl', + 'models/physrbox/cardboard_square.mdl', + 'models/physrbox/cardboard_taped.mdl', + 'models/physrbox/cardboard_white.mdl', + 'models/physrbox/damaged_long.mdl', + 'models/physrbox/damaged_square.mdl', + 'models/physrbox/taped_big.mdl', + 'models/physrbox/taped_medium.mdl', + 'models/physrbox/taped_white.mdl', + }, + {'Бильярд', + 'models/fo3_ball02.mdl', + 'models/fo3_ball05.mdl', + 'models/fo3_ball08.mdl', + 'models/fo3_ball_10.mdl', + 'models/fo3_ball_13.mdl', + 'models/fo3_pooltable_clean.mdl', + 'models/fo3_pooltable_ruined.mdl', + 'models/billiards/ball.mdl', + 'models/billiards/cr_table_10ft.mdl', + 'models/billiards/cr_table_12ft.mdl', + 'models/billiards/cr_table_9ft.mdl', + 'models/billiards/cue.mdl', + 'models/billiards/table_10ft.mdl', + 'models/billiards/table_12ft.mdl', + 'models/billiards/table_9ft.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_cue.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball1.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball10.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball11.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball2.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball3.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball4.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball5.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball6.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball7.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball8.mdl', + 'models/brewstersmodels/thesimsbustinout/aristoscratch_pool_table_ball9.mdl', + 'models/brewstersmodels/thesimsbustinout/overlord_pool_table.mdl', + 'models/johny-srka/poolball.mdl', + 'models/johny-srka/poolball_exploding.mdl', + 'models/props_downtown/pooltable.mdl', + }, + {'Студия звукозаписи', + 'models/nvc/audio/sound_panel_1x1.mdl', + 'models/nvc/audio/sound_panel_2x2.mdl', + 'models/nvc/audio/sound_panel_2x3.mdl', + }, + {'Пчеловодство', + 'models/sterling/bks_beehive.mdl', + 'models/sterling/bks_beekeepinghive.mdl', + 'models/sterling/bks_beemask.mdl', + 'models/sterling/bks_c_beesmoker.mdl', + 'models/sterling/bks_c_vaccum.mdl', + 'models/sterling/bks_honeyextractor.mdl', + 'models/sterling/bks_honeyjar.mdl', + 'models/sterling/bks_w_beesmoker.mdl', + 'models/sterling/bks_w_vaccum.mdl', + 'models/sterling/bks_woodcrate.mdl', + 'models/sterling/bks_woodenframe.mdl', + }, + {'Винодельня', + 'models/sterling/wms_bucket_container.mdl', + 'models/sterling/wms_fermentation_tank.mdl', + 'models/sterling/wms_vineyard.mdl', + 'models/sterling/wms_vineyard_grapes.mdl', + 'models/sterling/wms_winebarrel.mdl', + 'models/sterling/wms_winebottle_01.mdl', + 'models/sterling/wms_winebottle_container.mdl', + 'models/sterling/wms_winepress.mdl', + }, + {'Ювелирный магазин', + 'models/sterling/ajr_alarm.mdl', + 'models/sterling/ajr_cabnet.mdl', + 'models/sterling/ajr_cabnet2.mdl', + 'models/sterling/ajr_cabnet3.mdl', + 'models/sterling/ajr_hammer_w.mdl', + 'models/sterling/ajr_ringbox.mdl', + 'models/sterling/ajr_stand_necklace.mdl', + 'models/sterling/ajr_stand_watch.mdl', + 'models/sterling/jewlery_cabnet.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/business/criminal.lua b/garrysmod/addons/util-other/lua/spawnlists/business/criminal.lua new file mode 100644 index 0000000..6a1aeac --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/business/criminal.lua @@ -0,0 +1,130 @@ +return { + name = 'Криминальный', + icon = 'octoteam/icons-16/gun.png', + content = { + {'Мет', + 'models/winningrook/gtav/meth/acetone/acetone.mdl', + 'models/winningrook/gtav/meth/ammonia/ammonia.mdl', + 'models/winningrook/gtav/meth/hcacid/hcacid.mdl', + 'models/winningrook/gtav/meth/lithium_battery/lithium_battery.mdl', + 'models/winningrook/gtav/meth/meth_chiller/meth_chiller.mdl', + 'models/winningrook/gtav/meth/meth_scoop/meth_scoop.mdl', + 'models/winningrook/gtav/meth/meth_tray/meth_tray.mdl', + 'models/winningrook/gtav/meth/meth_tub/meth_tub.mdl', + 'models/winningrook/gtav/meth/meth_ziplock/meth_ziplock_open.mdl', + 'models/winningrook/gtav/meth/meth_ziplock/meth_ziplock_small.mdl', + 'models/winningrook/gtav/meth/phosphoru/phosphoru.mdl', + 'models/winningrook/gtav/meth/pseudoeph_box/pseudoeph_box.mdl', + 'models/winningrook/gtav/meth/sacid/sacid.mdl', + 'models/winningrook/gtav/meth/sodium/sodium.mdl', + 'models/winningrook/gtav/meth/toulene/toulene.mdl', + }, + {'Деньги', + 'models/mark2580/gtav/mp_office_03c/accessories/award_bronze.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/award_diamond.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/award_gold.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/award_plastic.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/award_silver.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_case.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_crate_01.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_note_01.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_004.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_005.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_006.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_01.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_01_1.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_02.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_02_1.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_07.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_pile_8.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_roll_01.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_scatter_01.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_scatter_02.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/cash_scatter_03.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/swag_counterfeit1.mdl', + 'models/mark2580/gtav/mp_office_03c/accessories/swag_counterfeit2.mdl', + 'models/models/ba_dlc_ba_int2_money_roll.mdl', + 'models/models/bkr_prop_bkr_cash_roll_01.mdl', + 'models/models/bkr_prop_bkr_cash_scatter_01.mdl', + 'models/models/bkr_prop_bkr_cash_scatter_03.mdl', + 'models/models/bkr_prop_bkr_cashpile_01.mdl', + 'models/models/bkr_prop_bkr_cashpile_02.mdl', + 'models/models/bkr_prop_bkr_cashpile_04.mdl', + 'models/models/bkr_prop_money_counter.mdl', + 'models/models/bkr_prop_moneypack_01a.mdl', + 'models/models/bkr_prop_prtmachine_dryer_spin.mdl', + 'models/models/ch_prop_ch_cash_trolly_01a.mdl', + 'models/models/ch_prop_ch_cash_trolly_01b.mdl', + 'models/models/ch_prop_ch_cash_trolly_01c.mdl', + 'models/models/dollarsbills.mdl', + 'models/models/ex_prop_crate_money_bc.mdl', + 'models/models/ex_prop_crate_money_sc.mdl', + 'models/models/prop_cash_case_02.mdl', + }, + {'Золото', + 'models/okxapack/valuables/valuable_bar.mdl', + 'models/okxapack/valuables/valuable_bar_cheap.mdl', + 'models/okxapack/valuables/valuable_bar_small.mdl', + 'models/okxapack/valuables/valuable_bar_small_stack.mdl', + 'models/okxapack/valuables/valuable_bar_small_stack_double.mdl', + 'models/okxapack/valuables/valuable_bar_small_stack_double_quad.mdl', + 'models/okxapack/valuables/valuable_bar_stack.mdl', + 'models/okxapack/valuables/valuable_bar_stack_double.mdl', + 'models/okxapack/valuables/valuable_bar_stack_double_quad.mdl', + }, + {'Наркотики', + 'models/winningrook/gtav/weed/weed_bag/weed_bag.mdl', + 'models/winningrook/gtav/weed/weed_bag_pile/weed_bag_pile.mdl', + 'models/winningrook/gtav/weed/weed_big_bag01/weed_big_bag01.mdl', + 'models/winningrook/gtav/weed/weed_big_bag02/weed_big_bag02.mdl', + 'models/winningrook/gtav/weed/weed_big_bag03/weed_big_bag03.mdl', + 'models/winningrook/gtav/weed/weed_big_bag_open/weed_big_bag_open.mdl', + 'models/winningrook/gtav/weed/weed_bucket01/weed_bucket01.mdl', + 'models/winningrook/gtav/weed/weed_bucket02/weed_bucket02.mdl', + 'models/winningrook/gtav/weed/weed_bud01/weed_bud01.mdl', + 'models/winningrook/gtav/weed/weed_bud02/weed_bud02.mdl', + 'models/winningrook/gtav/weed/weed_bud03/weed_bud03.mdl', + 'models/winningrook/gtav/weed/weed_dry_pile01/weed_dry_pile01.mdl', + 'models/winningrook/gtav/weed/weed_dry_pile02/weed_dry_pile02.mdl', + 'models/winningrook/gtav/weed/weed_dry_pile03/weed_dry_pile03.mdl', + 'models/winningrook/gtav/weed/weed_drying/weed_drying.mdl', + 'models/winningrook/gtav/weed/weed_pallet01/weed_pallet01.mdl', + 'models/winningrook/gtav/weed/weed_pallet02/weed_pallet02.mdl', + 'models/winningrook/gtav/weed/weed_pallet03/weed_pallet03.mdl', + 'models/winningrook/gtav/weed/weed_pallet04/weed_pallet04.mdl', + 'models/winningrook/gtav/weed/weed_plant_large01/weed_plant_large01.mdl', + 'models/winningrook/gtav/weed/weed_plant_large02/weed_plant_large02.mdl', + 'models/winningrook/gtav/weed/weed_plant_medium01/weed_plant_medium01.mdl', + 'models/winningrook/gtav/weed/weed_plant_medium02/weed_plant_medium02.mdl', + 'models/winningrook/gtav/weed/weed_plant_small01/weed_plant_small01.mdl', + 'models/winningrook/gtav/weed/weed_plant_small02/weed_plant_small02.mdl', + 'models/winningrook/gtav/weed/weed_plant_small03/weed_plant_small03.mdl', + 'models/winningrook/gtav/weed/weed_pot_stacked01/weed_pot_stacked01.mdl', + 'models/winningrook/gtav/weed/weed_pot_stacked02/weed_pot_stacked02.mdl', + 'models/winningrook/gtav/weed/weed_pot_stacked03/weed_pot_stacked03.mdl', + 'models/winningrook/gtav/weed/weed_scales01/weed_scales01.mdl', + 'models/winningrook/gtav/weed/weed_scales02/weed_scales02.mdl', + 'models/winningrook/gtav/weed/weed_smallbag/weed_smallbag.mdl', + 'models/winningrook/gtav/weed/weed_spray/weed_spray.mdl', + }, + {'Контрабанда', + 'models/ba_prop_battle_crates_rifles_01a.mdl', + 'models/bag.mdl', + 'models/bikercase.mdl', + 'models/bionazard.mdl', + 'models/bones.mdl', + 'models/case.mdl', + 'models/cokebox.mdl', + 'models/drug.mdl', + 'models/fakebox.mdl', + 'models/gems.mdl', + 'models/jewerly.mdl', + 'models/methtable.mdl', + 'models/picture.mdl', + 'models/safe.mdl', + 'models/tablet.mdl', + 'models/tobak.mdl', + 'models/watercrate.mdl', + }, + }, +} diff --git a/garrysmod/addons/util-other/lua/spawnlists/business/government.lua b/garrysmod/addons/util-other/lua/spawnlists/business/government.lua new file mode 100644 index 0000000..229c0cb --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/business/government.lua @@ -0,0 +1,196 @@ +return { + name = 'Государственный', + icon = 'octoteam/icons-16/building.png', + content = { + {'Больница', + 'models/gta_prop_michou/ex_prop_ex_laptop_01a.mdl', + 'models/gta_prop_michou/km_v_labbench5.mdl', + 'models/gta_prop_michou/med_cor_unita.mdl', + 'models/gta_prop_michou/med_metalfume.mdl', + 'models/gta_prop_michou/medwastebin.mdl', + 'models/gta_prop_michou/v_med_barrel.mdl', + 'models/gta_prop_michou/v_med_bed1.mdl', + 'models/gta_prop_michou/v_med_bedtable.mdl', + 'models/gta_prop_michou/v_med_bench1.mdl', + 'models/gta_prop_michou/v_med_benchcentr.mdl', + 'models/gta_prop_michou/v_med_benchset1.mdl', + 'models/gta_prop_michou/v_med_bigtable.mdl', + 'models/gta_prop_michou/v_med_bin.mdl', + 'models/gta_prop_michou/v_med_bottles1.mdl', + 'models/gta_prop_michou/v_med_bottles2.mdl', + 'models/gta_prop_michou/v_med_bottles3.mdl', + 'models/gta_prop_michou/v_med_centrifuge1.mdl', + 'models/gta_prop_michou/v_med_centrifuge2.mdl', + 'models/gta_prop_michou/v_med_cooler.mdl', + 'models/gta_prop_michou/v_med_cor_autopsytbl.mdl', + 'models/gta_prop_michou/v_med_cor_cemtrolly.mdl', + 'models/gta_prop_michou/v_med_cor_emblmtable.mdl', + 'models/gta_prop_michou/v_med_cor_largecupboard.mdl', + 'models/gta_prop_michou/v_med_cor_lightbox.mdl', + 'models/gta_prop_michou/v_med_cor_minifridge.mdl', + 'models/gta_prop_michou/v_med_cor_tvstand.mdl', + 'models/gta_prop_michou/v_med_cor_wallunita.mdl', + 'models/gta_prop_michou/v_med_cor_wallunitb.mdl', + 'models/gta_prop_michou/v_med_cor_whiteboard.mdl', + 'models/gta_prop_michou/v_med_flask.mdl', + 'models/gta_prop_michou/v_med_fumesink.mdl', + 'models/gta_prop_michou/v_med_gastank.mdl', + 'models/gta_prop_michou/v_med_hazmatscan.mdl', + 'models/gta_prop_michou/v_med_hospheadwall1.mdl', + 'models/gta_prop_michou/v_med_lab_fridge.mdl', + 'models/gta_prop_michou/v_med_lab_optable.mdl', + 'models/gta_prop_michou/v_med_lab_wallcab.mdl', + 'models/gta_prop_michou/v_med_microscope.mdl', + 'models/gta_prop_michou/v_med_oscillator1.mdl', + 'models/gta_prop_michou/v_med_oscillator2.mdl', + 'models/gta_prop_michou/v_med_oscillator3.mdl', + 'models/gta_prop_michou/v_med_oscillator4.mdl', + 'models/gta_prop_michou/v_med_testtubes.mdl', + 'models/props_unique/wheelchair01.mdl', + 'models/props_unique/hospital/exam_table.mdl', + 'models/props_unique/hospital/gurney.mdl', + 'models/props_equipment/surgicaltray_01.mdl', + 'models/props_unique/hospital/hospital_bed.mdl', + 'models/props_unique/hospital/iv_pole.mdl', + 'models/props_unique/hospital/surgery_lamp.mdl', + 'models/props_interiors/medicalcabinet02.mdl', + 'models/pg_props/pg_hospital/pg_optable.mdl', + 'models/props_lab/jar01a.mdl', + 'models/props_lab/jar01b.mdl', + 'models/props_questionableethics/bonesaw.mdl', + 'models/props_questionableethics/ripcutters.mdl', + 'models/props_questionableethics/scalpel.mdl', + 'models/props_questionableethics/scissors.mdl', + 'models/props_questionableethics/microscope.mdl', + 'models/props_lab/retinalscanner.mdl', + 'models/props/sycreations/forensics/autopsy.mdl', + 'models/props/hospital/hospital_bed.mdl', + 'models/props/hospital/hospital_bed_cloth.mdl', + 'models/props/hospital/hospital_bodybag.mdl', + 'models/props/hospital/hospital_closet01.mdl', + 'models/props/hospital/hospital_closet02.mdl', + 'models/props/hospital/hospital_closet03.mdl', + 'models/props/hospital/hospital_closet04.mdl', + 'models/props/hospital/hospital_closet05.mdl', + 'models/props/hospital/hospital_infusion.mdl', + 'models/props/hospital/hospital_radio01.mdl', + 'models/props/hospital/hospital_radio02.mdl', + 'models/props/hospital/hospital_seat.mdl', + 'models/props/hospital/hospital_stretcher.mdl', + 'models/props/hospital/largecabinet.mdl', + 'models/props/hospital/smallbed01.mdl', + 'models/props/hospital/smallcabinet.mdl', + 'models/props/hospital/xraymachine.mdl', + 'models/props/hospital/smallbed02.mdl', + 'models/props/hospital/smallbed03.mdl', + 'models/props/hospital/tissuebox.mdl', + 'models/props/hospital/tissuebox_empty.mdl', + 'models/props/interior/medicbox.mdl', + 'models/props/interior/medicbox_static.mdl', + 'models/w_models/weapons/w_eq_painpills.mdl', + 'models/themask/scenebuildthemes/tools/education/sm_whiteboard.mdl', + }, + {'Полиция', + 'models/rob/oldpolicepack/desk.mdl', + 'models/rob/oldpolicepack/motorola.mdl', + 'models/rob/oldpolicepack/motorolaradio1.mdl', + 'models/rob/oldpolicepack/motorolaradio2.mdl', + 'models/rob/oldpolicepack/sirenbox.mdl', + 'models/weapons/custom/w_batram.mdl', + 'models/hospitals/bodybag1.mdl', + 'models/hospitals/bodybag2.mdl', + 'models/hospitals/bodybag3.mdl', + 'models/props/generic/bodybag01.mdl', + 'models/props/generic/bodybag02.mdl', + 'models/props/generic/bodybag03.mdl', + 'models/props/generic/bodybag04.mdl', + 'models/props/generic/bodybag05.mdl', + 'models/props/generic/bodybag_group1.mdl', + 'models/props/generic/bodybag_group2.mdl', + 'models/props/generic/bodybag_group3.mdl', + 'models/props/generic/bodybag_group4.mdl', + 'models/props/sycreations/forensics/bullet.mdl', + 'models/props/sycreations/forensics/police_3d_scanner.mdl', + 'models/sterling/kobralost_ballistic.mdl', + 'models/craphead_scripts/armory_robbery2/armory.mdl', + 'models/bshields/dshield.mdl', + 'models/bshields/dshield_open.mdl', + 'models/bshields/hshield.mdl', + 'models/bshields/rshield.mdl', + }, + {'Документы', + 'models/kerry/dea_pass.mdl', + 'models/kerry/detectiv_pass.mdl', + 'models/kerry/fbi_pass.mdl', + 'models/kerry/marshel_pass.mdl', + 'models/kerry/passport_england.mdl', + 'models/kerry/passport_rus.mdl', + 'models/kerry/passport_usa.mdl', + }, + {'Военные', + 'models/npcs/sentry_ground.mdl', + 'models/props_marines/50_cal_destroyed.mdl', + 'models/props_marines/50_cal_static.mdl', + 'models/props_marines/50cal.mdl', + 'models/props_marines/alicepack.mdl', + 'models/props_marines/ammo_crate.mdl', + 'models/props_marines/ammo_crate02.mdl', + 'models/props_marines/ammo_crate02_static.mdl', + 'models/props_marines/ammobox01.mdl', + 'models/props_marines/ammocrate01.mdl', + 'models/props_marines/ammocrate01_static.mdl', + 'models/props_marines/army_radio.mdl', + 'models/props_marines/bayonet.mdl', + 'models/props_marines/broken_granade_box.mdl', + 'models/props_marines/c4_explosive.mdl', + 'models/props_marines/cot.mdl', + 'models/props_marines/etool.mdl', + 'models/props_marines/ivstand.mdl', + 'models/props_marines/ivstand_bag.mdl', + 'models/props_marines/mil_flashlight.mdl', + 'models/props_marines/missile_crate.mdl', + 'models/props_marines/missile_crate_lid.mdl', + 'models/props_marines/missile_crate_open.mdl', + 'models/props_marines/ordnance_crate.mdl', + 'models/props_marines/prc77_radio.mdl', + 'models/props_marines/sandbag.mdl', + 'models/props_marines/sandbag_static.mdl', + 'models/props_marines/sandbags_long128.mdl', + 'models/props_marines/sandbags_semicircle.mdl', + 'models/props_marines/sentry_undeployed.mdl', + 'models/props_marines/tow_missile_projectile.mdl', + 'models/props_marines/tow_missile_system.mdl', + 'models/props_marines/tow_missile_system_mp.mdl', + 'models/props_marines/triplaser.mdl', + }, + {'Лаборатория', + 'models/labware/beaker1.mdl', + 'models/labware/beaker2.mdl', + 'models/labware/bottle1.mdl', + 'models/labware/bottle2.mdl', + 'models/labware/burner.mdl', + 'models/labware/flask1.mdl', + 'models/labware/flask2.mdl', + 'models/labware/goggles.mdl', + 'models/labware/grad1.mdl', + 'models/labware/grad2.mdl', + 'models/labware/tube1.mdl', + 'models/labware/tube2.mdl', + 'models/labware/tuberack1.mdl', + 'models/labware/tuberack2.mdl', + 'models/labware/washer.mdl', + 'models/craphead_scripts/gmodparty/gmodparty_teleporter.mdl', + }, + {'Рекламные щиты', + 'models/zerochain/props_advertisement/zad_adsign01.mdl', + 'models/zerochain/props_advertisement/zad_adsign02.mdl', + 'models/zerochain/props_advertisement/zad_adsign03.mdl', + 'models/zerochain/props_advertisement/zad_adsign05.mdl', + }, + {'Информационный стенд', + 'models/2rek/gb/gb_brd.mdl', + 'models/2rek/gmib/gmib_board.mdl', + 'models/2rek/gmib/gmib_board_legs.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/construction/_category.lua b/garrysmod/addons/util-other/lua/spawnlists/construction/_category.lua new file mode 100644 index 0000000..320095c --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/construction/_category.lua @@ -0,0 +1,4 @@ +return { + name = 'Строительство', + icon = 'octoteam/icons-16/hammer.png', +} diff --git a/garrysmod/addons/util-other/lua/spawnlists/construction/buildings.lua b/garrysmod/addons/util-other/lua/spawnlists/construction/buildings.lua new file mode 100644 index 0000000..5cc3355 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/construction/buildings.lua @@ -0,0 +1,695 @@ +return { + name = 'Здания', + icon = 'octoteam/icons-16/house_one.png', + content = { + {'Сцены', + 'models/scene_building/sewer_system/arch_hall_3way.mdl', + 'models/scene_building/sewer_system/arch_hall_4way.mdl', + 'models/scene_building/sewer_system/arch_hall_corner.mdl', + 'models/scene_building/sewer_system/arch_small_door1.mdl', + 'models/scene_building/sewer_system/arch_small_door2.mdl', + 'models/scene_building/sewer_system/arch_small_hall.mdl', + 'models/scene_building/sewer_system/arch_small_hall_med.mdl', + 'models/scene_building/sewer_system/arch_small_hall_small.mdl', + 'models/scene_building/sewer_system/beam_door.mdl', + 'models/scene_building/sewer_system/beam_hall.mdl', + 'models/scene_building/sewer_system/beam_hall_sky.mdl', + 'models/scene_building/sewer_system/beam_hall_sky_dip.mdl', + 'models/scene_building/sewer_system/comp_roundroom.mdl', + 'models/scene_building/sewer_system/tunnel_2door.mdl', + 'models/scene_building/sewer_system/tunnel_2sec.mdl', + 'models/scene_building/sewer_system/tunnel_3sec.mdl', + 'models/scene_building/sewer_system/tunnel_big_bend.mdl', + 'models/scene_building/sewer_system/tunnel_door.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_2sec.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_3sec.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_bend.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_bend_half.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_ent.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_ent_gate.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_long.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_mid.mdl', + 'models/scene_building/sewer_system/tunnel_pipe_short.mdl', + 'models/scene_building/sewer_system/tunnel_straight.mdl', + 'models/scene_building/small_hallways/hall_1door.mdl', + 'models/scene_building/small_hallways/hall_1door_med.mdl', + 'models/scene_building/small_hallways/hall_1door_side.mdl', + 'models/scene_building/small_hallways/hall_1door_sml.mdl', + 'models/scene_building/small_hallways/hall_2door_l.mdl', + 'models/scene_building/small_hallways/hall_2door_opp.mdl', + 'models/scene_building/small_hallways/hall_2door_opp_small.mdl', + 'models/scene_building/small_hallways/hall_2door_r.mdl', + 'models/scene_building/small_hallways/hall_2door_side.mdl', + 'models/scene_building/small_hallways/hall_3door.mdl', + 'models/scene_building/small_hallways/hall_connector.mdl', + 'models/scene_building/small_hallways/hall_connector_3way.mdl', + 'models/scene_building/small_hallways/hall_connector_4way.mdl', + 'models/scene_building/small_hallways/hall_connector_corner.mdl', + 'models/scene_building/small_hallways/hall_connector_deadend.mdl', + 'models/scene_building/small_rooms/1door.mdl', + 'models/scene_building/small_rooms/1door_l.mdl', + 'models/scene_building/small_rooms/1door_r.mdl', + 'models/scene_building/small_rooms/2door_opp.mdl', + 'models/scene_building/small_rooms/2door_opp_l.mdl', + 'models/scene_building/small_rooms/2door_opp_ml.mdl', + 'models/scene_building/small_rooms/2door_opp_mr.mdl', + 'models/scene_building/small_rooms/2door_opp_r.mdl', + 'models/scene_building/small_rooms/2door_opposites.mdl', + 'models/scene_building/small_rooms/2door_sides.mdl', + 'models/scene_building/small_rooms/3door.mdl', + 'models/scene_building/small_rooms/4door.mdl', + 'models/scene_building/small_rooms/stairs_straight.mdl', + }, + {'Блокинг', + 'models/props/dev_barrel32x48.mdl', + 'models/props/dev_box32x.mdl', + 'models/props/dev_box48x.mdl', + 'models/props/dev_box64x.mdl', + 'models/props/dev_boxset.mdl', + 'models/props/dev_boxset2.mdl', + 'models/props/dev_boxset3.mdl', + 'models/props/dev_chair.mdl', + 'models/props/dev_counter.mdl', + 'models/props/dev_counter_pc.mdl', + 'models/props/dev_desk.mdl', + 'models/props/dev_desk_pc.mdl', + 'models/props/dev_desk_pc_chair.mdl', + 'models/props/dev_door.mdl', + 'models/props/dev_doorframe_door.mdl', + 'models/props/dev_light.mdl', + 'models/props/dev_lightrod.mdl', + 'models/props/dev_pc.mdl', + 'models/props/dev_pc_keyboard.mdl', + 'models/props/dev_pc_screen.mdl', + }, + {'Основное', + 'models/props/de_inferno/brickpillara.mdl', + 'models/props/de_inferno/brickpillarb.mdl', + 'models/props/cs_militia/militiawindow02_breakable_frame.mdl', + 'models/props_ktwalls/kt_wall128.mdl', + 'models/props_ktwalls/kt_wall64.mdl', + 'models/props_ktwalls/kt_wall32.mdl', + 'models/props_ktwalls/kt_wall16.mdl', + 'models/props_ktwalls/kt_wall16_pillar.mdl', + 'models/props_ktwalls/kt_wall8.mdl', + 'models/props_ktwalls/kt_wall128_window1.mdl', + 'models/props_ktwalls/kt_wall128_window2.mdl', + 'models/props_ktwalls/kt_wall128_window3.mdl', + 'models/props_ktwalls/kt_wall128_window4.mdl', + 'models/props_ktwalls/kt_wall128_hole1.mdl', + 'models/props_ktwalls/kt_wall128_hole2.mdl', + 'models/props_ktwalls/kt_wall128_hole3.mdl', + 'models/props_ktwalls/kt_wall128_hole4.mdl', + 'models/props_ktwalls/kt_wall128_hole5.mdl', + 'models/props_ktwalls/kt_wall128_hole6.mdl', + 'models/props_ktwalls/kt_wall128_hole7.mdl', + 'models/props_ktwalls/kt_wall128_hole8.mdl', + 'models/props_ktwalls/kt_wall128_utility1.mdl', + 'models/props_ktwalls/kt_wall128_utility2.mdl', + 'models/props_ktwalls/kt_wall128_utility3.mdl', + 'models/props_ktwalls/kt_utility4.mdl', + 'models/props_ktwalls/kt_utility5.mdl', + 'models/props_ktwalls/kt_ceiling642.mdl', + 'models/props_ktwalls/kt_ceiling64.mdl', + 'models/props_ktwalls/kt_ceiling128.mdl', + 'models/props_ktwalls/kt_ceiling256.mdl', + 'models/1x1 floor 01.mdl', + 'models/1x1 floor 02.mdl', + 'models/1x1 floor 03.mdl', + 'models/1x1 floor 04.mdl', + 'models/1x1 wall 01.mdl', + 'models/1x1 wall 02.mdl', + 'models/2x2 floor 01.mdl', + 'models/2x2 floor 02.mdl', + 'models/2x2 floor 03.mdl', + 'models/2x2 floor 04.mdl', + 'models/3x1 wall 01.mdl', + 'models/3x1 wall 02.mdl', + 'models/3x4 wall 01.mdl', + 'models/3x4 wall 02.mdl', + 'models/4x4 floor 01.mdl', + 'models/4x4 floor 02.mdl', + 'models/4x4 floor 03.mdl', + 'models/4x4 floor 04.mdl', + 'models/conelrg.mdl', + 'models/conemed.mdl', + 'models/conemedlrg.mdl', + 'models/conesml.mdl', + 'models/cubelrg.mdl', + 'models/cubemed.mdl', + 'models/cubemedlrg.mdl', + 'models/cubesml.mdl', + 'models/cylinderlrg.mdl', + 'models/cylindermed.mdl', + 'models/cylindermedlrg.mdl', + 'models/cylindersmall.mdl', + 'models/dodecahedron large.mdl', + 'models/dodecahedron small.mdl', + 'models/icosahedron large.mdl', + 'models/icosahedron small.mdl', + 'models/legobrick.mdl', + 'models/n_blox_12.mdl', + 'models/n_blox_24.mdl', + 'models/shapes/small_stair.mdl', + 'models/shapes/med_longplane.mdl', + 'models/cube/cube.mdl', + 'models/octahedron large.mdl', + 'models/octahedron small.mdl', + 'models/pentagonal prism.mdl', + 'models/pentagonal pyramid.mdl', + 'models/piller.mdl', + 'models/plywood 1x1.mdl', + 'models/plywood 1x2.mdl', + 'models/plywood 1x3.mdl', + 'models/plywood 1x4.mdl', + 'models/plywood 2x2.mdl', + 'models/plywood 2x3.mdl', + 'models/plywood 2x4.mdl', + 'models/plywood 3x3.mdl', + 'models/plywood 3x4.mdl', + 'models/plywood 4x4.mdl', + 'models/rectprismlrg.mdl', + 'models/rectprismmed.mdl', + 'models/rectprismmedlrg.mdl', + 'models/rectprismsml.mdl', + 'models/rectpyramidlrg.mdl', + 'models/rectpyramidmed.mdl', + 'models/rectpyramidmedlrg.mdl', + 'models/rectpyramidsml.mdl', + 'models/rhombicosidodecahedron large.mdl', + 'models/rhombicosidodecahedron small.mdl', + 'models/roof1.mdl', + 'models/rt screen 1x1.mdl', + 'models/rt screen 2x2.mdl', + 'models/rt screen 4x4.mdl', + 'models/spherelrg.mdl', + 'models/spheremed.mdl', + 'models/spheremedlrg.mdl', + 'models/spheresml.mdl', + 'models/trippy.mdl', + 'models/triprismlrg.mdl', + 'models/triprismmed.mdl', + 'models/triprismmedlrg.mdl', + 'models/triprismsml.mdl', + 'models/tripyramidlrg.mdl', + 'models/tripyramidmed.mdl', + 'models/tripyramidmedlrg.mdl', + 'models/tripyramidsml.mdl', + 'models/shapes/lego_6x2.mdl', + 'models/cube/hyper_cube.mdl', + 'models/cube/the_dreaded_cone.mdl', + 'models/cube/the_dreaded_cone_hyper.mdl', + 'models/dasmatze/bricks/1x1.mdl', + 'models/dasmatze/bricks/1x10.mdl', + 'models/dasmatze/bricks/1x12.mdl', + 'models/dasmatze/bricks/1x1headlight.mdl', + 'models/dasmatze/bricks/1x1multi.mdl', + 'models/dasmatze/bricks/1x1round.mdl', + 'models/dasmatze/bricks/1x2.mdl', + 'models/dasmatze/bricks/1x2grill.mdl', + 'models/dasmatze/bricks/1x3.mdl', + 'models/dasmatze/bricks/1x4.mdl', + 'models/dasmatze/bricks/1x6.mdl', + 'models/dasmatze/bricks/1x8.mdl', + 'models/dasmatze/bricks/2x2.mdl', + 'models/dasmatze/bricks/2x3.mdl', + 'models/dasmatze/bricks/2x4.mdl', + 'models/dasmatze/bricks/2x6.mdl', + 'models/dasmatze/bricks/2x8.mdl', + 'models/dasmatze/bricks/4x1multi.mdl', + 'models/dasmatze/bricks/curvedslope1x1.mdl', + 'models/dasmatze/bricks/curvedslope4x1.mdl', + 'models/dasmatze/bricks/door1x4x4.mdl', + 'models/dasmatze/bricks/doorframe1x4x4.mdl', + 'models/dasmatze/bricks/doorglass1x4x4.mdl', + 'models/dasmatze/bricks/panel1x1.mdl', + 'models/dasmatze/bricks/panel1x2.mdl', + 'models/dasmatze/bricks/panel1x3.mdl', + 'models/dasmatze/bricks/panel1x4.mdl', + 'models/dasmatze/bricks/panel1x6.mdl', + 'models/dasmatze/bricks/panel1x8.mdl', + 'models/dasmatze/bricks/panel2x2.mdl', + 'models/dasmatze/bricks/plate1x1.mdl', + 'models/dasmatze/bricks/plate1x10.mdl', + 'models/dasmatze/bricks/plate1x12.mdl', + 'models/dasmatze/bricks/plate1x1round.mdl', + 'models/dasmatze/bricks/plate1x2.mdl', + 'models/dasmatze/bricks/plate1x3.mdl', + 'models/dasmatze/bricks/plate1x4.mdl', + 'models/dasmatze/bricks/plate1x6.mdl', + 'models/dasmatze/bricks/plate1x8.mdl', + 'models/dasmatze/bricks/plate2x2.mdl', + 'models/dasmatze/bricks/plate2x3.mdl', + 'models/dasmatze/bricks/plate2x4.mdl', + 'models/dasmatze/bricks/plate2x6.mdl', + 'models/dasmatze/bricks/plate2x8.mdl', + 'models/dasmatze/bricks/plate4x4.mdl', + 'models/dasmatze/bricks/plate4x6.mdl', + 'models/dasmatze/bricks/plate4x8.mdl', + 'models/dasmatze/bricks/plate8x8grate.mdl', + 'models/dasmatze/bricks/slope1x2.mdl', + 'models/dasmatze/bricks/slope1x3.mdl', + 'models/dasmatze/bricks/slope1x4.mdl', + 'models/dasmatze/bricks/slope2x2.mdl', + 'models/dasmatze/bricks/slope2x2concave.mdl', + 'models/dasmatze/bricks/slope2x2corner.mdl', + 'models/dasmatze/bricks/slope2x2hang.mdl', + 'models/dasmatze/bricks/slope2x3.mdl', + 'models/dasmatze/bricks/slope2x4.mdl', + 'models/dasmatze/bricks/slope3x2.mdl', + 'models/dasmatze/bricks/slope3x3concave.mdl', + 'models/dasmatze/bricks/slope3x3corner.mdl', + 'models/dasmatze/bricks/slope4x2.mdl', + 'models/dasmatze/bricks/slope4x3.mdl', + 'models/dasmatze/bricks/slope4x4.mdl', + 'models/dasmatze/bricks/slope4x4concave.mdl', + 'models/dasmatze/bricks/slope4x4corner.mdl', + 'models/dasmatze/bricks/slope4x4double.mdl', + 'models/dasmatze/bricks/slope4x4doubleend.mdl', + 'models/dasmatze/bricks/slope6x8.mdl', + 'models/dasmatze/bricks/slope8x2.mdl', + 'models/dasmatze/bricks/slopeinverted1x2.mdl', + 'models/dasmatze/bricks/slopeinverted1x3.mdl', + 'models/dasmatze/bricks/slopeinverted1x4.mdl', + 'models/dasmatze/bricks/slopeinverted2x2.mdl', + 'models/dasmatze/bricks/slopeinverted2x2corner.mdl', + 'models/dasmatze/bricks/slopeinverted2x2cornercorner.mdl', + 'models/dasmatze/bricks/slopeinverted2x3.mdl', + 'models/dasmatze/bricks/slopeinverted2x4.mdl', + 'models/dasmatze/bricks/slopeinverted3x3corner.mdl', + 'models/dasmatze/bricks/slopeinverted4x2.mdl', + 'models/dasmatze/bricks/slopeinverted4x4corner.mdl', + 'models/dasmatze/bricks/slopetop1x2.mdl', + 'models/dasmatze/bricks/slopetop1x2end.mdl', + 'models/dasmatze/bricks/slopetop2x2.mdl', + 'models/dasmatze/bricks/slopetop2x2concave.mdl', + 'models/dasmatze/bricks/window1x4x3.mdl', + 'models/dasmatze/bricks/window1x4x3shutter.mdl', + 'models/shapes/med_ramp.mdl', + 'models/shapes/big_plane.mdl', + 'models/shapes/med_box.mdl', + 'models/shapes/big_box.mdl', + 'models/shapes/small_longbox.mdl', + 'models/jaanus/rtscreen_medium.mdl', + 'models/jaanus/rtscreen_small.mdl', + 'models/jaanus/rtscreen_tiny.mdl', + 'models/jaanus/rtscreen_tinyish.mdl', + 'models/jojobull/kapla/kapla01.mdl', + 'models/jojobull/kapla/kapla02.mdl', + 'models/jojobull/kapla/kapla03.mdl', + 'models/jojobull/kapla/kapla04.mdl', + 'models/jojobull/kapla/kapla05.mdl', + 'models/jojobull/kapla/kapla06.mdl', + 'models/mixerman3d/shapes/big_box.mdl', + 'models/mixerman3d/shapes/big_plane.mdl', + 'models/mixerman3d/shapes/lego_1x2.mdl', + 'models/mixerman3d/shapes/lego_1x4.mdl', + 'models/mixerman3d/shapes/lego_2x2.mdl', + 'models/shapes/lego_2x4.mdl', + 'models/mixerman3d/shapes/lego_2x4.mdl', + 'models/mixerman3d/shapes/lego_6x2.mdl', + 'models/shapes/lego_2x2.mdl', + 'models/shapes/lego_1x2.mdl', + 'models/mixerman3d/shapes/med_box.mdl', + 'models/mixerman3d/shapes/med_longplane.mdl', + 'models/shapes/med_plus.mdl', + 'models/mixerman3d/shapes/med_plus.mdl', + 'models/mixerman3d/shapes/med_ramp.mdl', + 'models/mixerman3d/shapes/small_longbox.mdl', + 'models/shapes/small_plane.mdl', + 'models/mixerman3d/shapes/small_plane.mdl', + 'models/mixerman3d/shapes/small_ramp.mdl', + 'models/mixerman3d/shapes/small_stair.mdl', + 'models/nes/block/block.mdl', + 'models/nes/block10/block10.mdl', + 'models/nes/block11/block11.mdl', + 'models/nes/block12/block12.mdl', + 'models/nes/block13/block13.mdl', + 'models/nes/block14/block14.mdl', + 'models/nes/block15/block15.mdl', + 'models/nes/block16/block16.mdl', + 'models/nes/block3/block3.mdl', + 'models/nes/block4/block4.mdl', + 'models/nes/block5/block5.mdl', + 'models/nes/block6/block6.mdl', + 'models/nes/block7/block7.mdl', + 'models/nes/block8/block8.mdl', + 'models/nes/block9/block9.mdl', + 'models/nightreaper/concrete/10x10x100_beam_long.mdl', + 'models/nightreaper/concrete/10x10x10_block_large.mdl', + 'models/nightreaper/concrete/10x10x25_beam_tiny.mdl', + 'models/nightreaper/concrete/10x10x50_beam_short.mdl', + 'models/nightreaper/concrete/10x10x75_beam_medium.mdl', + 'models/nightreaper/concrete/5x100x100_doorway.mdl', + 'models/nightreaper/concrete/5x100x100_double_doorway.mdl', + 'models/nightreaper/concrete/5x100x100_full_window.mdl', + 'models/nightreaper/concrete/5x100x100_high_window.mdl', + 'models/nightreaper/concrete/5x100x100_panel_flat.mdl', + 'models/nightreaper/concrete/5x100x100_window.mdl', + 'models/nightreaper/concrete/5x100x25_panel_flat.mdl', + 'models/nightreaper/concrete/5x100x50_panel_flat.mdl', + 'models/nightreaper/concrete/5x100x75_panel_flat.mdl', + 'models/nightreaper/concrete/5x25x25_panel_flat.mdl', + 'models/nightreaper/concrete/5x40x85_door.mdl', + 'models/nightreaper/concrete/5x50x100_doorway.mdl', + 'models/nightreaper/concrete/5x50x100_full_window.mdl', + 'models/nightreaper/concrete/5x50x100_high_window.mdl', + 'models/nightreaper/concrete/5x50x100_window.mdl', + 'models/nightreaper/concrete/5x50x25_panel_flat.mdl', + 'models/nightreaper/concrete/5x50x50_panel_flat.mdl', + 'models/nightreaper/concrete/5x5x100_beam_long.mdl', + 'models/nightreaper/concrete/5x5x25_beam_tiny.mdl', + 'models/nightreaper/concrete/5x5x50_beam_short.mdl', + 'models/nightreaper/concrete/5x5x5_block_small.mdl', + 'models/nightreaper/concrete/5x5x75_beam_medium.mdl', + 'models/nightreaper/concrete/5x75x25_panel_flat.mdl', + 'models/nightreaper/concrete/5x75x50_panel_flat.mdl', + 'models/nightreaper/concrete/5x75x75_panel_flat.mdl', + 'models/nightreaper/glass/1x40x40_glass.mdl', + 'models/nightreaper/glass/1x40x50_glass.mdl', + 'models/nightreaper/glass/1x90x40_glass.mdl', + 'models/nightreaper/glass/1x90x50_glass.mdl', + 'models/nightreaper/glass/1x90x90_glass.mdl', + 'models/nightreaper/hardwood/10x10x100_beam_long.mdl', + 'models/nightreaper/hardwood/10x10x10_block_large.mdl', + 'models/nightreaper/hardwood/10x10x25_beam_tiny.mdl', + 'models/nightreaper/hardwood/10x10x50_beam_short.mdl', + 'models/nightreaper/hardwood/10x10x75_beam_medium.mdl', + 'models/nightreaper/hardwood/5x100x100_doorway.mdl', + 'models/nightreaper/hardwood/5x100x100_double_doorway.mdl', + 'models/nightreaper/hardwood/5x100x100_full_window.mdl', + 'models/nightreaper/hardwood/5x100x100_high_window.mdl', + 'models/nightreaper/hardwood/5x100x100_panel_flat.mdl', + 'models/nightreaper/hardwood/5x100x100_window.mdl', + 'models/nightreaper/hardwood/5x100x25_panel_flat.mdl', + 'models/nightreaper/hardwood/5x100x50_panel_flat.mdl', + 'models/nightreaper/hardwood/5x100x75_panel_flat.mdl', + 'models/nightreaper/hardwood/5x25x25_panel_flat.mdl', + 'models/nightreaper/hardwood/5x40x85_door.mdl', + 'models/nightreaper/hardwood/5x50x100_doorway.mdl', + 'models/nightreaper/hardwood/5x50x100_full_window.mdl', + 'models/nightreaper/hardwood/5x50x100_high_window.mdl', + 'models/nightreaper/hardwood/5x50x100_window.mdl', + 'models/nightreaper/hardwood/5x50x25_panel_flat.mdl', + 'models/nightreaper/hardwood/5x50x50_panel_flat.mdl', + 'models/nightreaper/hardwood/5x5x100_beam_long.mdl', + 'models/nightreaper/hardwood/5x5x25_beam_tiny.mdl', + 'models/nightreaper/hardwood/5x5x50_beam_short.mdl', + 'models/nightreaper/hardwood/5x5x5_block_small.mdl', + 'models/nightreaper/hardwood/5x5x75_beam_medium.mdl', + 'models/nightreaper/hardwood/5x75x25_panel_flat.mdl', + 'models/nightreaper/hardwood/5x75x50_panel_flat.mdl', + 'models/nightreaper/hardwood/5x75x75_panel_flat.mdl', + 'models/nightreaper/softwood/10x10x100_beam_long.mdl', + 'models/nightreaper/softwood/10x10x10_block_large.mdl', + 'models/nightreaper/softwood/10x10x25_beam_tiny.mdl', + 'models/nightreaper/softwood/10x10x50_beam_short.mdl', + 'models/nightreaper/softwood/10x10x75_beam_medium.mdl', + 'models/nightreaper/softwood/5x100x100_doorway.mdl', + 'models/nightreaper/softwood/5x100x100_double_doorway.mdl', + 'models/nightreaper/softwood/5x100x100_full_window.mdl', + 'models/nightreaper/softwood/5x100x100_high_window.mdl', + 'models/nightreaper/softwood/5x100x100_panel_flat.mdl', + 'models/nightreaper/softwood/5x100x100_window.mdl', + 'models/nightreaper/softwood/5x100x25_panel_flat.mdl', + 'models/nightreaper/softwood/5x100x50_panel_flat.mdl', + 'models/nightreaper/softwood/5x100x75_panel_flat.mdl', + 'models/nightreaper/softwood/5x25x25_panel_flat.mdl', + 'models/nightreaper/softwood/5x40x85_door.mdl', + 'models/nightreaper/softwood/5x50x100_doorway.mdl', + 'models/nightreaper/softwood/5x50x100_full_window.mdl', + 'models/nightreaper/softwood/5x50x100_high_window.mdl', + 'models/nightreaper/softwood/5x50x100_window.mdl', + 'models/nightreaper/softwood/5x50x25_panel_flat.mdl', + 'models/nightreaper/softwood/5x50x50_panel_flat.mdl', + 'models/nightreaper/softwood/5x5x100_beam_long.mdl', + 'models/nightreaper/softwood/5x5x25_beam_tiny.mdl', + 'models/nightreaper/softwood/5x5x50_beam_short.mdl', + 'models/nightreaper/softwood/5x5x5_block_small.mdl', + 'models/nightreaper/softwood/5x5x75_beam_medium.mdl', + 'models/nightreaper/softwood/5x75x25_panel_flat.mdl', + 'models/nightreaper/softwood/5x75x50_panel_flat.mdl', + 'models/nightreaper/softwood/5x75x75_panel_flat.mdl', + 'models/om3ga2/cube16.mdl', + 'models/om3ga2/cube32.mdl', + 'models/om3ga2/cube64.mdl', + 'models/om3ga2/disc128.mdl', + 'models/om3ga2/disc256.mdl', + 'models/om3ga2/disc32.mdl', + 'models/om3ga2/disc64.mdl', + 'models/om3ga2/floor128.mdl', + 'models/om3ga2/floor256.mdl', + 'models/om3ga2/floor32.mdl', + 'models/om3ga2/floor512.mdl', + 'models/om3ga2/floor64.mdl', + 'models/om3ga2/pole1_128.mdl', + 'models/om3ga2/pole1_256.mdl', + 'models/om3ga2/pole1_32.mdl', + 'models/om3ga2/pole1_64.mdl', + 'models/om3ga2/pole2_128.mdl', + 'models/om3ga2/pole2_256.mdl', + 'models/om3ga2/pole2_512.mdl', + 'models/om3ga2/pole2_64.mdl', + 'models/om3ga2/sphere64.mdl', + 'models/om3ga2/sphere16.mdl', + 'models/om3ga2/sphere32.mdl', + 'models/om3ga2/wall64.mdl', + 'models/om3ga2/wall128.mdl', + 'models/om3ga2/wall256.mdl', + 'models/om3ga2/wall512.mdl', + 'models/pcbs/pcb128.mdl', + 'models/pcbs/pcb64.mdl', + 'models/pcbs/pcb32.mdl', + 'models/pcbs/pcb64wide.mdl', + 'models/shapes/lego_1x4.mdl', + 'models/shapes/small_ramp.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_window.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_window_glass.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_ref_prt_04.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_living_glass2_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_window2.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_glass_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_entry_elev_high.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/elevator.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/elevator_door.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/elevator_panel.mdl', + 'models/props_c17/elevator01.mdl', + }, + {'Лестницы', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_living_stair_up.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_living_stair_up_glass.mdl', + 'models/mark2580/gtav/tequilala_map/misc/main_exit_stairs.mdl', + 'models/mark2580/gtav/tequilala_map/misc/scene_stairs.mdl', + 'models/mark2580/gtav/tequilala_map/misc/side_stairs.mdl', + 'models/mark2580/gtav/tequilala_map/misc/side_stairs2.mdl', + 'models/mark2580/gtav/tequilala_map/misc/back_stairs.mdl', + 'models/escale/escale_forward.mdl', + 'models/escale/escale_invert.mdl', + 'models/props_exteriors/stairs_house_01.mdl', + 'models/props_exteriors/wood_stairs_120.mdl', + 'models/props_exteriors/wood_stairs_120_swamp.mdl', + 'models/props_exteriors/wood_stairs_40.mdl', + 'models/props_exteriors/wood_stairs_swamp.mdl', + 'models/props_exteriors/wood_stairs_wide_48.mdl', + 'models/props_interiors/stair_metal_01.mdl', + }, + {'Балкон', + 'models/mark2580/gtav/mp_apa_06/misc/apa_mp_h_06_balcony.mdl', + }, + {'Занавески и ставни', + 'models/mark2580/gtav/mp_apa_low/bedroom/curtains.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/curtains_01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_mpmidapart_curtains_02.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_mpmidapart_curtains_03.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_05_living_blinds_high_01.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_05_living_blinds_high_02.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_05_living_blinds_high_03.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_05_living_blinds_high_04.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_05_living_blinds_high_05.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_blinds.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_16_mpmidapart_curtains_04.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_mpmidapart_curtains_01.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/window.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/window_blind.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/window.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/bath_mesh_window.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/curtains_02.mdl', + 'models/sal/ammunation/blinds1.mdl', + 'models/sal/ammunation/blinds2.mdl', + 'models/sal/ammunation/blinds3.mdl', + 'models/sal/ammunation/blinds4.mdl', + 'models/props_urban/hotel_curtain001.mdl', + 'models/sims/gm_blinds.mdl', + 'models/sims/gm_windowcolonial.mdl', + 'models/sims/gm_windowloft.mdl', + 'models/props_shops/prop_curtains_01_a.mdl', + 'models/props_shops/prop_curtains_01_b.mdl', + 'models/props_shops/prop_curtains_02_a.mdl', + 'models/props_shops/prop_curtains_02_b.mdl', + 'models/props_shops/prop_curtains_02_c.mdl', + 'models/props_shops/prop_curtains_02_d.mdl', + 'models/props_shops/prop_curtains_02_e.mdl', + 'models/props_shops/prop_curtains_03_a.mdl', + 'models/props_shops/prop_curtains_03_b.mdl', + 'models/props_shops/prop_curtains_03_c.mdl', + 'models/props_shops/prop_curtains_03_d.mdl', + 'models/props_shops/prop_curtains_04_a.mdl', + 'models/props_shops/prop_curtains_04_b.mdl', + 'models/props_shops/prop_curtains_04_c.mdl', + 'models/props_shops/prop_curtains_04_d.mdl', + 'models/props_shops/prop_curtains_05_a.mdl', + 'models/props_shops/prop_curtains_05_b.mdl', + 'models/props_shops/prop_curtains_05_c.mdl', + 'models/props_shops/prop_curtains_06_a.mdl', + 'models/props_shops/prop_curtains_06_b.mdl', + 'models/props_shops/prop_curtains_06_c.mdl', + 'models/props_windows/hotel_window.mdl', + 'models/props_windows/hotel_window_glass001.mdl', + 'models/props_windows/window_industrial.mdl', + 'models/props_windows/window_urban_bars_med.mdl', + 'models/props_windows/window_urban_bars_sm.mdl', + 'models/props_windows/window_urban_sash_32_72_full.mdl', + 'models/props_windows/window_urban_sash_48_88_full.mdl', + }, + {'Двери', + 'models/props_c17/door01_left.mdl', + 'models/props_c17/door02_double.mdl', + 'models/props_borealis/borealis_door001a.mdl', + 'models/props_c17/door03_left.mdl', + 'models/props_doors/door03_slotted_left.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_p_mp_door_mpa.mdl', + 'models/mark2580/gtav/tequilala_map/misc/tequila_door_01.mdl', + 'models/sal/ammunation/door2.mdl', + 'models/sal/ammunation/door_2.mdl', + 'models/sal/ammunation/door_3.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/electro_wall_door.mdl', + 'models/sims/gm_door.mdl', + 'models/sims/gm_doorframe.mdl', + 'models/housepack2/house3backdoor.mdl', + 'models/housepack2/house4garagedoor.mdl', + 'models/housepack2/house4slidingdoor.mdl', + 'models/props/doors/door_barricade.mdl', + 'models/props/doors/door_barricade_prop_door_rotating.mdl', + 'models/props/hospital/hospital_door01.mdl', + 'models/props/hospital/hospital_door01_dynamic.mdl', + 'models/props_doors/door_rotate_112.mdl', + 'models/props_doors/door_sliding_112.mdl', + 'models/props_doors/doorfreezer01.mdl', + 'models/props_doors/doorglassmain01_small.mdl', + 'models/props_doors/doormain01_small.mdl', + 'models/props_doors/doormainmetalsmall01.mdl', + 'models/props_downtown/door_trim_56_112_02.mdl', + 'models/props_downtown/metal_door_112.mdl', + 'models/props_downtown/metal_door_112_16_frame.mdl', + 'models/props_downtown/metal_door_112_frame.mdl', + 'models/props_downtown/metal_door_doublewide_112_16_frame.mdl', + 'models/props_downtown/metal_door_doublewide_112_frame.mdl', + 'models/oskar/interior/door01.mdl', + 'models/oskar/interior/door02.mdl', + 'models/agency/doors/door56.mdl', + 'models/agency/doors/frame56.mdl', + 'models/agency/doors/frame88.mdl', + }, + {'Одноэтажные дома', + 'models/housepack2/house1.mdl', + 'models/housepack2/house2.mdl', + 'models/housepack2/house3garagedoorduo.mdl', + 'models/housepack2/house3.mdl', + 'models/megalomaniac/megahouse.mdl', + }, + {'Двухэтажные дома', + 'models/erikszeug/gebaeude/eriksvilla.mdl', + 'models/housepack2/house4.mdl', + 'models/kunoszeug/bunkerhaus.mdl', + 'models/kunoszeug/kunohaus_neu.mdl', + 'models/kunoszeug/traumwohnung-stein.mdl', + 'models/kunoszeug/wohnblock.mdl', + }, + {'Многоэтажные дома', + 'models/kunoszeug/altkunohaus.mdl', + 'models/kunoszeug/towerb.mdl', + }, + {'Япония', + 'models/props_japan/andon_post_lantern01.mdl', + 'models/props_japan/bamboo_barrier_128.mdl', + 'models/props_japan/bamboo_barrier_256.mdl', + 'models/props_japan/bamboo_card01_256.mdl', + 'models/props_japan/bamboo_cluster01.mdl', + 'models/props_japan/banner01.mdl', + 'models/props_japan/byoubu_paperscreen01.mdl', + 'models/props_japan/calligraphy_set01.mdl', + 'models/props_japan/deer_scare01.mdl', + 'models/props_japan/gong01.mdl', + 'models/props_japan/goshintou_paper_lantern01.mdl', + 'models/props_japan/hanging_lantern01.mdl', + 'models/props_japan/hanging_lantern02.mdl', + 'models/props_japan/hanging_rope_lanterns01.mdl', + 'models/props_japan/japanese_bridge01.mdl', + 'models/props_japan/japanese_bridge02.mdl', + 'models/props_japan/japanese_bridge02_roof.mdl', + 'models/props_japan/japanese_bridge02_roof_spine.mdl', + 'models/props_japan/japanese_lantern01.mdl', + 'models/props_japan/japanese_resupply01.mdl', + 'models/props_japan/japanese_stool01.mdl', + 'models/props_japan/japanese_upgrade_station01.mdl', + 'models/props_japan/japanese_upgrade_station01_roof.mdl', + 'models/props_japan/japanese_upgrade_station01_roofbits.mdl', + 'models/props_japan/katana_stand01.mdl', + 'models/props_japan/obon_lantern01.mdl', + 'models/props_japan/obon_lantern01_large.mdl', + 'models/props_japan/obon_lantern01_sm.mdl', + 'models/props_japan/rooftiles01_128.mdl', + 'models/props_japan/rooftiles01_128_underside.mdl', + 'models/props_japan/rooftiles01_256.mdl', + 'models/props_japan/rooftiles01_256_underside.mdl', + 'models/props_japan/rooftiles01_45corner.mdl', + 'models/props_japan/rooftiles01_45corner_sm.mdl', + 'models/props_japan/rooftiles01_45corner_sm_underside.mdl', + 'models/props_japan/rooftiles01_45corner_underside.mdl', + 'models/props_japan/rooftiles01_64.mdl', + 'models/props_japan/rooftiles01_64_underside.mdl', + 'models/props_japan/rooftiles01_corner.mdl', + 'models/props_japan/rooftiles01_corner_b.mdl', + 'models/props_japan/rooftiles01_corner_inverted.mdl', + 'models/props_japan/rooftiles01_corner_inverted_b.mdl', + 'models/props_japan/rooftiles01_corner_inverted_underside.mdl', + 'models/props_japan/rooftiles01_corner_underside.mdl', + 'models/props_japan/rooftiles01_end.mdl', + 'models/props_japan/rooftiles01_single.mdl', + 'models/props_japan/rooftiles01_single_underside_l.mdl', + 'models/props_japan/rooftiles01_single_underside_r.mdl', + 'models/props_japan/rooftiles01_spine_tiled.mdl', + 'models/props_japan/rooftiles01_spine_tiled_128.mdl', + 'models/props_japan/rooftiles01_spine_tiled_large.mdl', + 'models/props_japan/rooftiles01_spine_tiled_straight.mdl', + 'models/props_japan/rooftiles01_spine_wood.mdl', + 'models/props_japan/rooftiles01_wedge.mdl', + 'models/props_japan/sakura_tree01.mdl', + 'models/props_japan/sakura_tree01b.mdl', + 'models/props_japan/sakura_tree01c.mdl', + 'models/props_japan/sakura_tree_tall01.mdl', + 'models/props_japan/sakura_tree_tall02.mdl', + 'models/props_japan/shoji_paper_wall_128.mdl', + 'models/props_japan/shoji_paper_wall_64.mdl', + 'models/props_japan/temple_column01_192.mdl', + 'models/props_japan/temple_doorframe_decor01.mdl', + 'models/props_japan/temple_moongate01.mdl', + 'models/props_japan/torii01.mdl', + 'models/props_japan/torii02.mdl', + 'models/props_japan/torii03.mdl', + 'models/props_japan/wall_mount_lantern01.mdl', + 'models/props_japan/wall_rooftiles01_256.mdl', + 'models/props_japan/wall_rooftiles01_corner01.mdl', + 'models/props_japan/wall_rooftiles01_corner02.mdl', + 'models/props_japan/wall_rooftiles01_endcap.mdl', + 'models/props_japan/wood_wall_frame01.mdl', + 'models/props_japan/wood_wall_frame01_small.mdl', + 'models/props_japan/wooden_sign01.mdl', + }, + {'Прочее', + 'models/sal/ammunation/cabin3.mdl', + 'models/props_rooftop/acunit01.mdl', + 'models/props_rooftop/acvent01.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/construction/environment.lua b/garrysmod/addons/util-other/lua/spawnlists/construction/environment.lua new file mode 100644 index 0000000..7ec2430 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/construction/environment.lua @@ -0,0 +1,696 @@ +return { + name = 'Окружение', + icon = 'octoteam/icons-16/tree.png', + content = { + {'Знаки', + 'models/props_barriers/prop_consign_01a.mdl', + 'models/props_barriers/prop_consign_01b.mdl', + 'models/props_barriers/prop_sign_sec_01.mdl', + 'models/props_barriers/prop_sign_sec_02.mdl', + 'models/props_barriers/prop_sign_sec_03.mdl', + 'models/props_barriers/prop_sign_sec_04.mdl', + 'models/props_barriers/prop_sign_sec_05.mdl', + 'models/props_barriers/prop_sign_sec_06.mdl', + 'models/props_barriers/models/models/props_signs/prop_sign_road_06a.mdl', + 'models/props_signs/prop_bus_stop_sign.mdl', + 'models/props_signs/prop_sign_interstate_01.mdl', + 'models/props_signs/prop_sign_interstate_02.mdl', + 'models/props_signs/prop_sign_interstate_03.mdl', + 'models/props_signs/prop_sign_interstate_04.mdl', + 'models/props_signs/prop_sign_interstate_05.mdl', + 'models/props_signs/prop_sign_loading_1.mdl', + 'models/props_signs/prop_sign_road_01a.mdl', + 'models/props_signs/prop_sign_road_01b.mdl', + 'models/props_signs/prop_sign_road_01c.mdl', + 'models/props_signs/prop_sign_road_02a.mdl', + 'models/props_signs/prop_sign_road_03a.mdl', + 'models/props_signs/prop_sign_road_03b.mdl', + 'models/props_signs/prop_sign_road_03c.mdl', + 'models/props_signs/prop_sign_road_03d.mdl', + 'models/props_signs/prop_sign_road_03e.mdl', + 'models/props_signs/prop_sign_road_03f.mdl', + 'models/props_signs/prop_sign_road_03g.mdl', + 'models/props_signs/prop_sign_road_03h.mdl', + 'models/props_signs/prop_sign_road_03i.mdl', + 'models/props_signs/prop_sign_road_03j.mdl', + 'models/props_signs/prop_sign_road_03k.mdl', + 'models/props_signs/prop_sign_road_03l.mdl', + 'models/props_signs/prop_sign_road_03m.mdl', + 'models/props_signs/prop_sign_road_03n.mdl', + 'models/props_signs/prop_sign_road_03o.mdl', + 'models/props_signs/prop_sign_road_03p.mdl', + 'models/props_signs/prop_sign_road_03q.mdl', + 'models/props_signs/prop_sign_road_03r.mdl', + 'models/props_signs/prop_sign_road_03s.mdl', + 'models/props_signs/prop_sign_road_03t.mdl', + 'models/props_signs/prop_sign_road_03u.mdl', + 'models/props_signs/prop_sign_road_03v.mdl', + 'models/props_signs/prop_sign_road_03w.mdl', + 'models/props_signs/prop_sign_road_03x.mdl', + 'models/props_signs/prop_sign_road_03y.mdl', + 'models/props_signs/prop_sign_road_03z.mdl', + 'models/props_signs/prop_sign_road_04a.mdl', + 'models/props_signs/prop_sign_road_04b.mdl', + 'models/props_signs/prop_sign_road_04c.mdl', + 'models/props_signs/prop_sign_road_04d.mdl', + 'models/props_signs/prop_sign_road_04e.mdl', + 'models/props_signs/prop_sign_road_04f.mdl', + 'models/props_signs/prop_sign_road_04g.mdl', + 'models/props_signs/prop_sign_road_04h.mdl', + 'models/props_signs/prop_sign_road_04i.mdl', + 'models/props_signs/prop_sign_road_04j.mdl', + 'models/props_signs/prop_sign_road_04k.mdl', + 'models/props_signs/prop_sign_road_04l.mdl', + 'models/props_signs/prop_sign_road_04m.mdl', + 'models/props_signs/prop_sign_road_04n.mdl', + 'models/props_signs/prop_sign_road_04o.mdl', + 'models/props_signs/prop_sign_road_04p.mdl', + 'models/props_signs/prop_sign_road_04q.mdl', + 'models/props_signs/prop_sign_road_04r.mdl', + 'models/props_signs/prop_sign_road_04s.mdl', + 'models/props_signs/prop_sign_road_04t.mdl', + 'models/props_signs/prop_sign_road_04u.mdl', + 'models/props_signs/prop_sign_road_04v.mdl', + 'models/props_signs/prop_sign_road_04w.mdl', + 'models/props_signs/prop_sign_road_04x.mdl', + 'models/props_signs/prop_sign_road_04y.mdl', + 'models/props_signs/prop_sign_road_04z.mdl', + 'models/props_signs/prop_sign_road_04za.mdl', + 'models/props_signs/prop_sign_road_04zb.mdl', + 'models/props_signs/prop_sign_road_05a.mdl', + 'models/props_signs/prop_sign_road_05b.mdl', + 'models/props_signs/prop_sign_road_05c.mdl', + 'models/props_signs/prop_sign_road_05d.mdl', + 'models/props_signs/prop_sign_road_05e.mdl', + 'models/props_signs/prop_sign_road_05f.mdl', + 'models/props_signs/prop_sign_road_05g.mdl', + 'models/props_signs/prop_sign_road_05h.mdl', + 'models/props_signs/prop_sign_road_05i.mdl', + 'models/props_signs/prop_sign_road_05j.mdl', + 'models/props_signs/prop_sign_road_05k.mdl', + 'models/props_signs/prop_sign_road_05l.mdl', + 'models/props_signs/prop_sign_road_05m.mdl', + 'models/props_signs/prop_sign_road_05n.mdl', + 'models/props_signs/prop_sign_road_05o.mdl', + 'models/props_signs/prop_sign_road_05p.mdl', + 'models/props_signs/prop_sign_road_05q.mdl', + 'models/props_signs/prop_sign_road_05r.mdl', + 'models/props_signs/prop_sign_road_05s.mdl', + 'models/props_signs/prop_sign_road_05t.mdl', + 'models/props_signs/prop_sign_road_05u.mdl', + 'models/props_signs/prop_sign_road_05v.mdl', + 'models/props_signs/prop_sign_road_05w.mdl', + 'models/props_signs/prop_sign_road_05x.mdl', + 'models/props_signs/prop_sign_road_05y.mdl', + 'models/props_signs/prop_sign_road_05z.mdl', + 'models/props_signs/prop_sign_road_05za.mdl', + 'models/props_signs/prop_sign_road_06a.mdl', + 'models/props_signs/prop_sign_road_06b.mdl', + 'models/props_signs/prop_sign_road_06c.mdl', + 'models/props_signs/prop_sign_road_06d.mdl', + 'models/props_signs/prop_sign_road_06e.mdl', + 'models/props_signs/prop_sign_road_06f.mdl', + 'models/props_signs/prop_sign_road_06g.mdl', + 'models/props_signs/prop_sign_road_06h.mdl', + 'models/props_signs/prop_sign_road_06i.mdl', + 'models/props_signs/prop_sign_road_06j.mdl', + 'models/props_signs/prop_sign_road_06k.mdl', + 'models/props_signs/prop_sign_road_06l.mdl', + 'models/props_signs/prop_sign_road_06m.mdl', + 'models/props_signs/prop_sign_road_06n.mdl', + 'models/props_signs/prop_sign_road_06o.mdl', + 'models/props_signs/prop_sign_road_06p.mdl', + 'models/props_signs/prop_sign_road_07a.mdl', + 'models/props_signs/prop_sign_road_callbox.mdl', + 'models/props_signs/prop_sign_route_01.mdl', + 'models/props_signs/prop_sign_route_11.mdl', + 'models/props_signs/prop_sign_route_13.mdl', + 'models/props_signs/prop_sign_taxi_1.mdl', + }, + {'Ограда', + 'models/azok30_barriere/azok30_barriere.mdl', + 'models/als/speed_bump/bump_element_black_phys.mdl', + 'models/als/speed_bump/bump_element_black_static.mdl', + 'models/als/speed_bump/bump_element_end_black_phys.mdl', + 'models/als/speed_bump/bump_element_end_black_static.mdl', + 'models/als/speed_bump/bump_element_end_yellow_phys.mdl', + 'models/als/speed_bump/bump_element_end_yellow_static.mdl', + 'models/als/speed_bump/bump_element_yellow_phys.mdl', + 'models/als/speed_bump/bump_element_yellow_static.mdl', + 'models/als/speed_bump/road_bump_full.mdl', + 'models/props_barriers/prop_barrier_wat_03a.mdl', + 'models/props_barriers/prop_barrier_wat_03b.mdl', + 'models/props_barriers/prop_barrier_work01a.mdl', + 'models/props_barriers/prop_barrier_work01a_folded.mdl', + 'models/props_barriers/prop_barrier_work01b.mdl', + 'models/props_barriers/prop_barrier_work01d.mdl', + 'models/props_barriers/prop_barrier_work02a.mdl', + 'models/props_barriers/prop_barrier_work04a.mdl', + 'models/props_barriers/prop_barrier_work05.mdl', + 'models/props_barriers/prop_barrier_work06a.mdl', + 'models/props_barriers/prop_barrier_work06b.mdl', + 'models/props_barriers/prop_consign_02a.mdl', + 'models/props_barriers/prop_roadcone01a.mdl', + 'models/props_barriers/prop_roadcone01b.mdl', + 'models/props_barriers/prop_roadcone01c.mdl', + 'models/props_barriers/prop_roadcone02a.mdl', + 'models/props_barriers/prop_roadcone02b.mdl', + 'models/props_barriers/prop_roadcone02c.mdl', + 'models/props_barriers/prop_roadpole_01a.mdl', + 'models/props_barriers/prop_roadpole_01b.mdl', + 'models/props/cs_militia/coveredbridge01_bottom.mdl', + 'models/props/de_inferno/lattice.mdl', + 'models/props_c17/handrail04_short.mdl', + 'models/props/de_tides/tides_woodenfences_b.mdl', + 'models/props/de_tides/tides_woodenfences_a.mdl', + 'models/props_interiors/elevatorshaft_door01a.mdl', + 'models/props_c17/gate_door01a.mdl', + 'models/props_c17/gate_door02a.mdl', + 'models/props_building_details/storefront_template001a_bars.mdl', + 'models/props/cs_militia/grate.mdl', + 'models/props_c17/concrete_barrier001a.mdl', + 'models/props_phx/construct/concrete_barrier00.mdl', + 'models/props_phx/construct/concrete_barrier01.mdl', + 'models/props/cs_assault/barrelwarning.mdl', + 'models/props/cs_assault/pylon.mdl', + 'models/props_junk/trafficcone001a.mdl', + 'models/props_wasteland/barricade002a.mdl', + 'models/props_wasteland/barricade001a.mdl', + 'models/props_wasteland/prison_celldoor001b.mdl', + 'models/props_wasteland/prison_gate001a.mdl', + 'models/props_wasteland/prison_gate001b.mdl', + 'models/props_wasteland/prison_slidingdoor001a.mdl', + 'models/sal/ammunation/fence1.mdl', + 'models/sal/ammunation/fence2_2.mdl', + 'models/sal/ammunation/railing1.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/ironwork_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/ironwork_02.mdl', + 'models/props/de_inferno/railingspikedgate.mdl', + 'models/props_trainstation/handrail_64decoration001a.mdl', + 'models/props/de_inferno/railing01.mdl', + 'models/props/de_inferno/railing03.mdl', + 'models/props/de_inferno/railingspiked.mdl', + 'models/props/de_tides/gate.mdl', + 'models/props_lab/teleportgate.mdl', + 'models/props/de_train/chainlinkgate.mdl', + 'models/props_c17/fence01a.mdl', + 'models/props_c17/fence01b.mdl', + 'models/props_c17/fence02a.mdl', + 'models/props_c17/fence02b.mdl', + 'models/props_c17/fence03a.mdl', + 'models/props_c17/fence04a.mdl', + 'models/props_wasteland/exterior_fence001a.mdl', + 'models/props_wasteland/exterior_fence001b.mdl', + 'models/props_wasteland/exterior_fence002a.mdl', + 'models/props_wasteland/exterior_fence002b.mdl', + 'models/props_wasteland/exterior_fence002c.mdl', + 'models/props_wasteland/exterior_fence002d.mdl', + 'models/props_wasteland/exterior_fence002e.mdl', + 'models/props_wasteland/exterior_fence003a.mdl', + 'models/props_wasteland/exterior_fence003b.mdl', + 'models/props_wasteland/interior_fence001a.mdl', + 'models/props_wasteland/interior_fence001b.mdl', + 'models/props_wasteland/interior_fence001c.mdl', + 'models/props_wasteland/interior_fence001d.mdl', + 'models/props_wasteland/interior_fence001e.mdl', + 'models/props_wasteland/interior_fence002a.mdl', + 'models/props_wasteland/interior_fence002b.mdl', + 'models/props_wasteland/interior_fence002c.mdl', + 'models/props_wasteland/interior_fence002d.mdl', + 'models/props_wasteland/interior_fence002e.mdl', + 'models/props_wasteland/interior_fence001g.mdl', + 'models/props_wasteland/interior_fence002f.mdl', + 'models/props_wasteland/interior_fence003a.mdl', + 'models/props_wasteland/interior_fence003b.mdl', + 'models/props_wasteland/interior_fence003d.mdl', + 'models/props_wasteland/interior_fence003e.mdl', + 'models/props_wasteland/interior_fence003f.mdl', + 'models/props_wasteland/interior_fence004a.mdl', + 'models/props_wasteland/interior_fence004b.mdl', + 'models/props/cs_militia/housefence.mdl', + 'models/props/cs_militia/housefence_door.mdl', + 'models/props_lab/blastdoor001a.mdl', + 'models/props_lab/blastdoor001b.mdl', + 'models/props_lab/blastdoor001c.mdl', + 'models/props_building_details/courtyard_template001c_bars.mdl', + 'models/props_building_details/courtyard_template002c_bars.mdl', + 'models/props/cs_militia/fencewoodlog03_long.mdl', + 'models/props/cs_militia/fence_ranch.mdl', + 'models/props/cs_militia/fencewoodlog01_short.mdl', + 'models/props/cs_militia/fencewoodlog02_short.mdl', + 'models/props_street/police_barricade.mdl', + 'models/props_street/police_barricade2.mdl', + 'models/props_street/police_barricade3.mdl', + 'models/props_street/police_barricade4.mdl', + 'models/props_street/police_barricade_368in.mdl', + 'models/props_street/police_barricade_496in.mdl', + 'models/props_construction/constructioncone.mdl', + 'models/props_construction/constructioncone_stack3.mdl', + 'models/evenmorepolicestuff/cone.mdl', + 'models/evenmorepolicestuff/drum.mdl', + 'models/evenmorepolicestuff/pole.mdl', + 'models/props_fairgrounds/traffic_barrel.mdl', + 'models/props_fairgrounds/trailermessageboard.mdl', + }, + {'Улица', + 'models/sal/ammunation/phonebox.mdl', + 'models/props_equipment/phone_booth.mdl', + 'models/props_waterfront/awning01.mdl', + 'models/props_c17/awning001a.mdl', + 'models/props_c17/awning002a.mdl', + 'models/props_street/awning_department_store.mdl', + 'models/env/furniture/pool_recliner/pool_recliner.mdl', + 'models/props/de_piranesi/pi_sundial.mdl', + 'models/props_urban/bench001.mdl', + 'models/props/de_inferno/bench_wood.mdl', + 'models/props_trainstation/bench_indoor001a.mdl', + 'models/props_trainstation/benchoutdoor01a.mdl', + 'models/props_wasteland/cafeteria_bench001a.mdl', + 'models/props/de_inferno/bench_concrete.mdl', + 'models/props/de_inferno/bench_wood.mdl', + 'models/props/de_piranesi/pi_bench.mdl', + 'models/props/de_train/lockerbench.mdl', + 'models/props/de_inferno/fountain.mdl', + 'models/props_unique/firepit_campground.mdl', + 'models/props_equipment/sleeping_bag1.mdl', + 'models/props_equipment/sleeping_bag2.mdl', + 'models/props_urban/outhouse001.mdl', + 'models/props/cs_italy/it_mkt_table3.mdl', + 'models/props/cs_militia/table_shed.mdl', + 'models/props/cs_militia/table_kitchen.mdl', + 'models/props/cs_militia/logpile2.mdl', + 'models/props/de_piranesi/pi_orrery.mdl', + 'models/props/de_dust/grainbasket01a.mdl', + 'models/props/de_dust/grainbasket01b.mdl', + 'models/props/de_dust/grainbasket01c.mdl', + 'models/props/de_piranesi/pi_orrery.mdl', + 'models/props/de_piranesi/pi_sundial.mdl', + 'models/props/de_prodigy/platform.mdl', + 'models/balloons/balloon_classicheart.mdl', + 'models/maxofs2d/balloon_classic.mdl', + 'models/props_phx/misc/soccerball.mdl', + 'models/props_c17/playground_carousel01.mdl', + 'models/props_c17/playground_jungle_gym01a.mdl', + 'models/props_c17/playground_jungle_gym01b.mdl', + 'models/props_c17/playground_swingset01.mdl', + 'models/props_c17/playground_swingset_seat01a.mdl', + 'models/props_c17/playground_teetertoter_seat.mdl', + 'models/props_c17/playground_teetertoter_stan.mdl', + 'models/props_c17/playgroundslide01.mdl', + 'models/props_c17/playgroundtick-tack-toe_post01.mdl', + 'models/props/de_inferno/hay_bails.mdl', + 'models/props/de_inferno/hay_bail_stack.mdl', + 'models/props/cs_militia/haybale_target.mdl', + 'models/props/cs_militia/haybale_target_02.mdl', + 'models/props/cs_militia/haybale_target_03.mdl', + 'models/props/de_inferno/logpile.mdl', + 'models/props/cs_militia/ropeladder01.mdl', + 'models/props/cs_militia/ladderwood.mdl', + 'models/props_c17/metalladder001.mdl', + 'models/props/cs_assault/ladderaluminium128.mdl', + 'models/props/cs_office/ladder1.mdl', + 'models/props_junk/bicycle01a.mdl', + 'models/props_equipment/phone_booth.mdl', + 'models/props_trainstation/payphone001a.mdl', + 'models/props_trainstation/payphone_reciever001a.mdl', + 'models/props/cs_assault/streetsign01.mdl', + 'models/props/cs_assault/streetsign02.mdl', + 'models/sickness/splitway_02.mdl', + 'models/props/cs_assault/nostopssign.mdl', + 'models/props_trainstation/clock01.mdl', + 'models/props/cs_assault/stoplight.mdl', + 'models/props/de_tides/tides_partyflags.mdl', + 'models/props/de_inferno/light_streetlight.mdl', + 'models/props/de_tides/tides_streetlight.mdl', + 'models/props_trainstation/tracksign01.mdl', + 'models/props_trainstation/tracksign03.mdl', + 'models/props_trainstation/tracksign02.mdl', + 'models/props_street/mail_dropbox.mdl', + 'models/props_equipment/gas_pump.mdl', + 'models/props_wasteland/gaspump001a.mdl', + 'models/props_c17/gravestone001a.mdl', + 'models/props_c17/gravestone002a.mdl', + 'models/props_c17/gravestone003a.mdl', + 'models/props_c17/gravestone004a.mdl', + 'models/props_c17/gravestone_coffinpiece001a.mdl', + 'models/props_c17/gravestone_coffinpiece002a.mdl', + 'models/props/de_inferno/monument.mdl', + 'models/props_c17/gravestone_cross001a.mdl', + 'models/props_c17/gravestone_cross001b.mdl', + 'models/props_c17/gravestone_statue001a.mdl', + 'models/props_wasteland/horizontalcoolingtank04.mdl', + 'models/gta_prop_michou/prop_gnome1.mdl', + 'models/gta_prop_michou/prop_gnome2.mdl', + 'models/gta_prop_michou/prop_gnome3.mdl', + 'models/props_downtown/horse_statue.mdl', + 'models/props_urban/big_wheel001.mdl', + 'models/props_street/firehydrant.mdl', + 'models/props_street/mail_dropbox.mdl', + 'models/props_urban/ashtray_stand001.mdl', + 'models/props_urban/bench001.mdl', + 'models/props_urban/garbage_can001.mdl', + 'models/props_urban/pool_ladder001.mdl', + 'models/props_urban/roof_tower001.mdl', + 'models/props_urban/round_table_umbrella001.mdl', + 'models/props_urban/round_table_umbrella002.mdl', + 'models/props_exteriors/chimney2.mdl', + 'models/props_interiors/bbq_grill.mdl', + }, + {'Мусор', + 'models/sal/trash/binbag.mdl', + 'models/sal/trash/card0.mdl', + 'models/sal/trash/card1b.mdl', + 'models/sal/trash/card2b.mdl', + 'models/sal/trash/card3.mdl', + 'models/sal/trash/card4.mdl', + 'models/sal/trash/card5.mdl', + 'models/sal/trash/litter1_f3.mdl', + 'models/sal/trash/litter2_f4.mdl', + 'models/sal/trash/litter3_f4.mdl', + 'models/sal/trash/litter4_f3.mdl', + 'models/sal/trash/litter5_f3.mdl', + 'models/sal/ammunation/carpettile_raised.mdl', + 'models/sal/ammunation/dumpster1_fix2.mdl', + 'models/props_junk/dumpster.mdl', + 'models/props_junk/trashcluster01a_corner.mdl', + 'models/props_junk/trashdumpster01a.mdl', + 'models/props_junk/trashbin01a.mdl', + 'models/props/cs_office/trash_can_p.mdl', + 'models/props_junk/trashcluster01a.mdl', + 'models/props_junk/garbage128_composite001a.mdl', + 'models/props_junk/garbage128_composite001c.mdl', + 'models/props_junk/garbage256_composite002a.mdl', + 'models/props_junk/garbage_carboard002a.mdl', + 'models/props_interiors/trashcankitchen01.mdl', + 'models/Highrise/trashcanashtray_01.mdl', + 'models/Highrise/trash_can_03.mdl', + 'models/props_interiors/trashcan01.mdl', + 'models/props/cs_office/trash_can_p.mdl', + 'models/sims/trashcan.mdl', + }, + {'Ферма', + 'models/sal/ammunation/tree1_fix.mdl', + 'models/props/de_tides/planter.mdl', + 'models/props/de_inferno/lattice.mdl', + 'models/props_farm/cider_squisher_01.mdl', + 'models/props_farm/cow_dead2.mdl', + 'models/props_farm/cow_dead2_hornless.mdl', + 'models/props_farm/cow_dead3.mdl', + 'models/props_farm/cow_dead3_hornless.mdl', + 'models/props_farm/db_table1.mdl', + 'models/props_farm/db_tub.mdl', + 'models/props_farm/gate_01.mdl', + 'models/props_farm/gatepost_01.mdl', + 'models/props_farm/kr_milkbox.mdl', + 'models/props_farm/kr_table01.mdl', + 'models/props_farm/m_chickencoop.mdl', + 'models/props_farm/mar_trough.mdl', + 'models/props_farm/r_chopwood.mdl', + 'models/props_farm/r_ladder1.mdl', + 'models/props_farm/renault_yl.mdl', + 'models/props_farm/renault_yl_physic.mdl', + 'models/props_farm/s_barn_door_left.mdl', + 'models/props_farm/s_barn_door_right.mdl', + 'models/props_farm/shovel01.mdl', + 'models/props_farm/shovel01_physic.mdl', + 'models/props_farm/sickle01.mdl', + 'models/props_farm/sickle01_physic.mdl', + 'models/props_farm/sme_ciderdoorfrmoutter_01.mdl', + 'models/props_farm/sme_ciderdoorfrmoutter_02.mdl', + 'models/props_farm/sme_ciderdoorfrmoutter_03.mdl', + 'models/props_farm/sme_ciderdoorfrmoutter_04.mdl', + 'models/props_farm/sme_ciderdoorfrmoutter_05.mdl', + 'models/props_farm/sme_ciderrailing_01.mdl', + 'models/props_farm/sme_ciderstair_01.mdl', + 'models/props_farm/sme_ciderstairrun_01.mdl', + 'models/props_farm/sme_ciderstairs01a.mdl', + 'models/props_farm/sme_ciderstairs01b.mdl', + 'models/props_farm/sme_ciderstairs_01.mdl', + 'models/props_farm/sme_ciderstairs_02.mdl', + 'models/props_farm/sme_cidertruss_01.mdl', + 'models/props_farm/sme_cidertruss_02.mdl', + 'models/props_farm/sme_cidertruss_03.mdl', + 'models/props_farm/sme_cidertruss_04.mdl', + 'models/props_farm/sme_cidertruss_05.mdl', + 'models/props_farm/sme_cidertruss_06.mdl', + 'models/props_farm/sme_ciderwind_01.mdl', + 'models/props_farm/sme_ciderwindfrm_01.mdl', + 'models/props_farm/sme_ciderwindlft_02.mdl', + 'models/props_farm/sme_ciderwindrt_02.mdl', + 'models/props_farm/truss01.mdl', + 'models/props_farm/truss01a.mdl', + 'models/props_farm/wagon01.mdl', + 'models/props_farm/wagon_01.mdl', + 'models/props_farm/wagonwheel_01.mdl', + 'models/props_farm/woodpile.mdl', + 'models/props_foliage/ac_appletree01.mdl', + 'models/props_foliage/ac_appletree02.mdl', + 'models/props_foliage/ah_apple_tree_single001.mdl', + 'models/props_foliage/ah_ash_tree001.mdl', + 'models/props_foliage/ah_ash_tree002.mdl', + 'models/props_foliage/ah_ash_tree_cluster1.mdl', + 'models/props_foliage/ah_ash_tree_lg.mdl', + 'models/props_foliage/ah_ash_tree_med.mdl', + 'models/props_foliage/ah_ash_tree_small001.mdl', + 'models/props_foliage/ah_dead_trunk001.mdl', + 'models/props_foliage/ah_dead_trunk002.mdl', + 'models/props_foliage/ah_dead_trunk003.mdl', + 'models/props_foliage/ah_dead_trunk004.mdl', + 'models/props_foliage/ah_domestic_hedge128.mdl', + 'models/props_foliage/ah_domestic_hedge128_mid.mdl', + 'models/props_foliage/ah_domestic_hedge256.mdl', + 'models/props_foliage/ah_domestic_hedge256_mid.mdl', + 'models/props_foliage/ah_domestic_shrub001.mdl', + 'models/props_foliage/ah_fern_single001.mdl', + 'models/props_foliage/ah_fern_single002.mdl', + 'models/props_foliage/ah_grass_1024x512_001.mdl', + 'models/props_foliage/ah_grass_128x64_001.mdl', + 'models/props_foliage/ah_grass_256x256_001.mdl', + 'models/props_foliage/ah_grass_256x256_001_flowers.mdl', + 'models/props_foliage/ah_grass_256x512_001.mdl', + 'models/props_foliage/ah_grass_256x64_001.mdl', + 'models/props_foliage/ah_grass_512x1024_001.mdl', + 'models/props_foliage/ah_grass_512x512_001.mdl', + 'models/props_foliage/ah_grass_512x512_001_flowers.mdl', + 'models/props_foliage/ah_hawthorn_512_static.mdl', + 'models/props_foliage/ah_hawthorn_sm_static.mdl', + 'models/props_foliage/ah_hay_stubble_256x512.mdl', + 'models/props_foliage/ah_hedge_1024_skybox_close.mdl', + 'models/props_foliage/ah_hedge_1024_skybox_far.mdl', + 'models/props_foliage/ah_hedge_1024_skybox_mid.mdl', + 'models/props_foliage/ah_hedge_1024_static.mdl', + 'models/props_foliage/ah_hedge_1024_static_sky_16.mdl', + 'models/props_foliage/ah_hedge_1024_static_skybox.mdl', + 'models/props_foliage/ah_hedge_2048_skybox_far.mdl', + 'models/props_foliage/ah_hedge_2048_skybox_near.mdl', + 'models/props_foliage/ah_hedge_2048_static.mdl', + 'models/props_foliage/ah_hedge_2048_tall_mix.mdl', + 'models/props_foliage/ah_hedge_2048_tall_mix_skybox.mdl', + 'models/props_foliage/ah_hedge_512_static.mdl', + 'models/props_foliage/ah_hedge_512_static_skybox.mdl', + 'models/props_foliage/ah_large_pine.mdl', + 'models/props_foliage/ah_medium_pine.mdl', + 'models/props_foliage/ah_medium_pine_skybox.mdl', + 'models/props_foliage/ah_skybox_bill_forest.mdl', + 'models/props_foliage/ah_small_pine.mdl', + 'models/props_foliage/ah_super_large_pine002.mdl', + 'models/props_foliage/ah_super_pine001.mdl', + 'models/props_foliage/ah_wheat_field_512x128.mdl', + 'models/props_foliage/ah_wheat_field_512x512.mdl', + 'models/props_foliage/ah_wheat_field_skybox_square.mdl', + 'models/props_foliage/appleorchard01.mdl', + 'models/props_foliage/appleorchard02.mdl', + 'models/props_foliage/appleorchard03.mdl', + 'models/props_foliage/appleorchard_small.mdl', + 'models/props_foliage/appletree01.mdl', + 'models/props_foliage/ash01.mdl', + 'models/props_foliage/ash01_skybox.mdl', + 'models/props_foliage/ash02.mdl', + 'models/props_foliage/ash02_skybox.mdl', + 'models/props_foliage/ash03.mdl', + 'models/props_foliage/ash03_skybox.mdl', + 'models/props_foliage/bramble001a.mdl', + 'models/props_foliage/cabbage_01.mdl', + 'models/props_foliage/haytuffs01.mdl', + 'models/props_foliage/haytuffs01_high.mdl', + 'models/props_foliage/haytuffs01_mix.mdl', + 'models/props_foliage/haytuffs01_short.mdl', + 'models/props_foliage/haytuffs02.mdl', + 'models/props_foliage/hedgerow01_large-bushy.mdl', + 'models/props_foliage/hedgerow01_large-endpiece.mdl', + 'models/props_foliage/hedgerow01_large-full.mdl', + 'models/props_foliage/hedgerow01_large-thick.mdl', + 'models/props_foliage/hedgerow01_medium-bushy.mdl', + 'models/props_foliage/hedgerow01_medium-bushy_skybox.mdl', + 'models/props_foliage/hedgerow01_medium-endpiece.mdl', + 'models/props_foliage/hedgerow01_medium-full.mdl', + 'models/props_foliage/hedgerow01_medium-thick.mdl', + 'models/props_foliage/hedgerow01_small-bushy.mdl', + 'models/props_foliage/hedgerow01_small-endpiece.mdl', + 'models/props_foliage/hedgerow01_small-full.mdl', + 'models/props_foliage/hedgerow01_small-thick.mdl', + 'models/props_foliage/hedgerow02_large-skybox.mdl', + 'models/props_foliage/hedgerow02_large.mdl', + 'models/props_foliage/hedgerow02_medium-skybox.mdl', + 'models/props_foliage/hedgerow02_medium.mdl', + 'models/props_foliage/hedgerow02_medium_ash01.mdl', + 'models/props_foliage/hedgerow02_medium_ash01_skybox.mdl', + 'models/props_foliage/hedgerow02_medium_exlong_ash01.mdl', + 'models/props_foliage/hedgerow02_medium_exlong_poplar01.mdl', + 'models/props_foliage/hedgerow02_medium_exlong_poplar01_skybox.mdl', + 'models/props_foliage/hedgerow02_medium_exlong_poplar01a.mdl', + 'models/props_foliage/hedgerow02_medium_exlong_poplar01b.mdl', + 'models/props_foliage/hedgerow02_medium_long-skybox.mdl', + 'models/props_foliage/hedgerow02_medium_long.mdl', + 'models/props_foliage/hedgerow02_medium_long_poplar01.mdl', + 'models/props_foliage/hedgerow02_medium_long_poplar01_skybox.mdl', + 'models/props_foliage/hedgerow02_medium_long_poplar01a.mdl', + 'models/props_foliage/hedgerow02_medium_long_poplar01a_skybox.mdl', + 'models/props_foliage/hedgerow02_small-skybox.mdl', + 'models/props_foliage/hedgerow02_small.mdl', + 'models/props_foliage/hedgerow_01.mdl', + 'models/props_foliage/hedgerow_02.mdl', + 'models/props_foliage/hedgerow_03.mdl', + 'models/props_foliage/hedgerow_04.mdl', + 'models/props_foliage/hedgerow_05.mdl', + 'models/props_foliage/hedgerow_06.mdl', + 'models/props_foliage/hedgerow_07.mdl', + 'models/props_foliage/hedgerow_08.mdl', + 'models/props_foliage/low_bush01.mdl', + 'models/props_foliage/lowgrass01.mdl', + 'models/props_foliage/lowgrass02.mdl', + 'models/props_foliage/lowgrass03.mdl', + 'models/props_foliage/poplar01.mdl', + 'models/props_foliage/poplar01_skybox.mdl', + 'models/props_foliage/poplar02.mdl', + 'models/props_foliage/poplar02_skybox.mdl', + 'models/props_foliage/r_corn1.mdl', + 'models/props_foliage/r_detailprop1.mdl', + 'models/props_foliage/r_detailprop2.mdl', + 'models/props_foliage/r_detailprop3.mdl', + 'models/props_foliage/r_hedge1.mdl', + 'models/props_foliage/r_hedge2.mdl', + 'models/props_foliage/r_hedge3.mdl', + 'models/props_foliage/r_hedge4_l.mdl', + 'models/props_foliage/r_hedge4_m.mdl', + 'models/props_foliage/r_hedge4_m_exlong.mdl', + 'models/props_foliage/r_hedge4_m_exlong_skybox.mdl', + 'models/props_foliage/r_hedge4_m_long.mdl', + 'models/props_foliage/r_hedge4_m_long_skybox.mdl', + 'models/props_foliage/r_hedge4_s.mdl', + 'models/props_foliage/r_hedge_trees1.mdl', + 'models/props_foliage/r_hedge_trees1_skybox.mdl', + 'models/props_foliage/r_hedge_trees2.mdl', + 'models/props_foliage/r_hedge_trees3.mdl', + 'models/props_foliage/r_hedge_trees3_skybox.mdl', + 'models/props_foliage/r_hedgerow1_long.mdl', + 'models/props_foliage/r_hedgerow1_long_skybox.mdl', + 'models/props_foliage/r_hedgerow3_long.mdl', + 'models/props_foliage/r_hedgerow_bush1.mdl', + 'models/props_foliage/r_hedgerow_bush2.mdl', + 'models/props_foliage/r_hedgerow_bush3.mdl', + 'models/props_foliage/r_maple1.mdl', + 'models/props_foliage/r_plant1.mdl', + 'models/props_foliage/r_plant2.mdl', + 'models/props_foliage/r_plant3.mdl', + 'models/props_foliage/r_plant4.mdl', + 'models/props_foliage/r_plant5.mdl', + 'models/props_foliage/r_plant6.mdl', + 'models/props_foliage/r_plant7.mdl', + 'models/props_foliage/r_shapedhedge1.mdl', + 'models/props_foliage/r_shapedhedge1a.mdl', + 'models/props_foliage/r_shrub1.mdl', + 'models/props_foliage/r_shrub2.mdl', + 'models/props_foliage/r_smallbush1.mdl', + 'models/props_foliage/r_wildhedge1.mdl', + 'models/props_foliage/r_wildhedge1_exlong.mdl', + 'models/props_foliage/r_wildhedge1_exlong_skybox.mdl', + 'models/props_foliage/r_wildhedge1_short.mdl', + 'models/props_foliage/r_wildhedge1_skybox.mdl', + 'models/props_foliage/r_wildhedge2.mdl', + 'models/props_foliage/rd_appletree01.mdl', + 'models/props_foliage/rd_ash01.mdl', + 'models/props_foliage/rd_bush04.mdl', + 'models/props_foliage/rd_hedgerow01.mdl', + 'models/props_foliage/rd_mediumbush01.mdl', + 'models/props_foliage/rd_rock_group01.mdl', + 'models/props_foliage/rd_rock_group02.mdl', + 'models/props_foliage/rd_tree01a.mdl', + 'models/props_foliage/rd_willow.mdl', + 'models/props_foliage/rnl_ash01.mdl', + 'models/props_foliage/rnl_ash_leaf_fall.mdl', + 'models/props_foliage/shapedhedges_01.mdl', + 'models/props_foliage/shrub_01a.mdl', + 'models/props_foliage/small-tree01.mdl', + 'models/props_foliage/small-tree01_skybox.mdl', + 'models/props_foliage/stump01.mdl', + 'models/props_foliage/stump02.mdl', + 'models/props_foliage/tree_cliff_01a.mdl', + 'models/props_foliage/tree_cliff_02a.mdl', + 'models/props_foliage/tree_deciduous_01a-lod.mdl', + 'models/props_foliage/tree_deciduous_01a.mdl', + 'models/props_foliage/tree_deciduous_02a.mdl', + 'models/props_foliage/tree_deciduous_03a.mdl', + 'models/props_foliage/tree_deciduous_03b.mdl', + 'models/props_foliage/waterlily01.mdl', + 'models/props_foliage/grasses/shrub_m_bg.mdl', + 'models/props_farm/ah_hay_pile001.mdl', + 'models/props_farm/ah_hay_pile001_skybox.mdl', + 'models/props_farm/ah_rect_hay_bale001.mdl', + 'models/props_farm/ah_rect_hay_bale_lg001.mdl', + 'models/props_farm/ah_round_hay_bale001.mdl', + 'models/props_farm/ah_round_hay_bale001_skybox.mdl', + 'models/props_foliage/hedge_128.mdl', + 'models/props_foliage/hedge_256.mdl', + 'models/props_foliage/hedge_256_128high.mdl', + 'models/props_foliage/mall_big_plant01.mdl', + 'models/props_foliage/mall_bigleaves_plant01.mdl', + 'models/props_foliage/mall_bigleaves_plant02.mdl', + 'models/props_foliage/mall_bigleaves_plant03.mdl', + 'models/props_foliage/mall_bush02.mdl', + 'models/props_foliage/mall_tree_large01.mdl', + 'models/props_foliage/tree_pine04_efficient.mdl', + 'models/props_foliage/tree_pine04_tall.mdl', + 'models/props_foliage/tree_pine04_tencluster.mdl', + 'models/props_foliage/urban_balcony_planter01.mdl', + 'models/props_foliage/urban_balcony_planter02.mdl', + 'models/props_foliage/urban_bush01.mdl', + 'models/props_foliage/urban_bush02.mdl', + 'models/props_foliage/urban_grass_bush01.mdl', + 'models/props_foliage/urban_hedge_256_128_high.mdl', + 'models/props_foliage/urban_pot_bigplant01.mdl', + 'models/props_foliage/urban_pot_clay01.mdl', + 'models/props_foliage/urban_pot_clay02.mdl', + 'models/props_foliage/urban_pot_clay03.mdl', + 'models/props_foliage/urban_pot_fancy01.mdl', + 'models/props_foliage/urban_streettree01_medium.mdl', + 'models/props_foliage/urban_tree_base_bushes01.mdl', + 'models/props_foliage/urban_tree_base_bushes02.mdl', + 'models/props_foliage/urban_tree_giant01_small.mdl', + }, + {'Скейт-парк', + 'models/sal/skate/skate_flatramp.mdl', + 'models/sal/skate/skate_funbox2.mdl', + 'models/sal/skate/skate_halfpipe7.mdl', + 'models/sal/skate/skate_quarterpipe3.mdl', + 'models/sal/skate/skate_rail2.mdl', + 'models/sal/skate/skate_spiner2.mdl', + }, + {'Трубы', + 'models/sal/ammunation/rangepipe_1.mdl', + 'models/sal/ammunation/rangepipe_10.mdl', + 'models/sal/ammunation/rangepipe_11.mdl', + 'models/sal/ammunation/rangepipe_12.mdl', + 'models/sal/ammunation/rangepipe_15.mdl', + 'models/sal/ammunation/rangepipe_2.mdl', + 'models/sal/ammunation/rangepipe_3.mdl', + 'models/sal/ammunation/rangepipe_4.mdl', + 'models/sal/ammunation/rangepipe_5.mdl', + 'models/sal/ammunation/rangepipe_6.mdl', + 'models/sal/ammunation/rangepipe_7.mdl', + 'models/sal/ammunation/rangepipe_8.mdl', + 'models/sal/ammunation/rangepipe_9.mdl', + 'models/sal/ammunation/pipes1.mdl', + }, + }, +} diff --git a/garrysmod/addons/util-other/lua/spawnlists/food.lua b/garrysmod/addons/util-other/lua/spawnlists/food.lua new file mode 100644 index 0000000..31c11aa --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/food.lua @@ -0,0 +1,545 @@ +return { + name = 'Еда и продукты', + icon = 'octoteam/icons-16/apple.png', + content = { + {'Хлебобулочные изделия', + 'models/props_shops/prop_bakery_bread_01_c.mdl', + 'models/props_shops/prop_bakery_bread_02_a.mdl', + 'models/props_shops/prop_bakery_bread_02_b.mdl', + 'models/props_shops/prop_bakery_bread_02_c.mdl', + 'models/props_shops/prop_bakery_bread_03_a.mdl', + 'models/props_shops/prop_bakery_bread_03_b.mdl', + 'models/props_shops/prop_bakery_bread_03_c.mdl', + 'models/props_shops/prop_bakery_bread_04_a.mdl', + 'models/props_shops/prop_bakery_bread_05_a.mdl', + 'models/props_shops/prop_bakery_bread_06_a.mdl', + 'models/props_shops/prop_bakery_bread_07_a.mdl', + 'models/props_shops/prop_bakery_bread_08_a.mdl', + 'models/props_shops/prop_bakery_bread_09_a.mdl', + 'models/props_shops/prop_bakery_bread_10_a.mdl', + 'models/props_shops/prop_bakery_bread_11_a.mdl', + 'models/foodnhouseholditems/bread-1.mdl', + 'models/foodnhouseholditems/bread-2.mdl', + 'models/foodnhouseholditems/bread-3.mdl', + 'models/foodnhouseholditems/bread-4.mdl', + 'models/foodnhouseholditems/bread_loaf.mdl', + 'models/foodnhouseholditems/bread_half.mdl', + 'models/foodnhouseholditems/bread_slice.mdl', + 'models/foodnhouseholditems/bagette.mdl', + 'models/foodnhouseholditems/bagel1.mdl', + 'models/foodnhouseholditems/bagel2.mdl', + 'models/foodnhouseholditems/bagel3.mdl', + 'models/foodnhouseholditems/pretzel.mdl', + 'models/foodnhouseholditems/croissant.mdl', + 'models/foodnhouseholditems/chocolatine.mdl', + 'models/foodnhouseholditems/donut.mdl', + 'models/foodnhouseholditems/pie.mdl', + 'models/foodnhouseholditems/sweetroll.mdl', + 'models/foodnhouseholditems/toast.mdl', + 'models/props_shops/prop_shop_calzone.mdl', + 'models/props_shops/prop_shop_pizza.mdl', + }, + {'Фрукты и овощи', + 'models/foodnhouseholditems/watermelon_unbreakable.mdl', + 'models/foodnhouseholditems/watermelon_half.mdl', + 'models/foodnhouseholditems/watermelon_slice.mdl', + 'models/foodnhouseholditems/bananna.mdl', + 'models/foodnhouseholditems/bananna_bunch.mdl', + 'models/foodnhouseholditems/orange.mdl', + 'models/foodnhouseholditems/pineapple.mdl', + 'models/foodnhouseholditems/grapes1.mdl', + 'models/foodnhouseholditems/grapes2.mdl', + 'models/foodnhouseholditems/grapes3.mdl', + 'models/foodnhouseholditems/coconut.mdl', + 'models/foodnhouseholditems/coconut_half.mdl', + 'models/foodnhouseholditems/apple.mdl', + 'models/foodnhouseholditems/apple1.mdl', + 'models/foodnhouseholditems/apple2.mdl', + 'models/foodnhouseholditems/pear.mdl', + 'models/foodnhouseholditems/pumpkin01.mdl', + 'models/foodnhouseholditems/corn.mdl', + 'models/foodnhouseholditems/leek.mdl', + 'models/foodnhouseholditems/eggplant.mdl', + 'models/foodnhouseholditems/gourd.mdl', + 'models/foodnhouseholditems/cabbage1.mdl', + 'models/foodnhouseholditems/cabbage2.mdl', + 'models/foodnhouseholditems/cabbage3.mdl', + 'models/foodnhouseholditems/lettuce.mdl', + 'models/foodnhouseholditems/carrot.mdl', + 'models/foodnhouseholditems/chili.mdl', + 'models/foodnhouseholditems/pepper1.mdl', + 'models/foodnhouseholditems/pepper2.mdl', + 'models/foodnhouseholditems/pepper3.mdl', + 'models/foodnhouseholditems/potato.mdl', + 'models/foodnhouseholditems/tomato.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/orange_01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/orange_02.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/orange_03.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/orange_v2.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/apple_01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/apple_02.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/apple_03.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/apple_04.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/apple_green.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/apple_red.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/banana.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/fruitbowl_01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/fruitbowl_02.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/fruitbowl_03.mdl', + 'models/mark2580/gtav/barstuff/bar_lemons.mdl', + 'models/mark2580/gtav/barstuff/bar_limes.mdl', + 'models/mark2580/gtav/barstuff/bar_oranges.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mp_h_acc_fruitbowl_01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mp_h_acc_fruitbowl_02.mdl', + }, + {'Вредная еда', + 'models/foodnhouseholditems/pizza.mdl', + 'models/foodnhouseholditems/pizzaslice.mdl', + 'models/foodnhouseholditems/pizzaslicehalf.mdl', + 'models/foodnhouseholditems/pizzab.mdl', + 'models/foodnhouseholditems/pizzabox.mdl', + 'models/foodnhouseholditems/pizzaboxbottom.mdl', + 'models/foodnhouseholditems/pizzaboxtop.mdl', + 'models/food/burger.mdl', + 'models/food/hotdog.mdl', + 'models/foodnhouseholditems/burgergtasa.mdl', + 'models/foodnhouseholditems/burgergtaiv.mdl', + 'models/foodnhouseholditems/burgersims2.mdl', + 'models/foodnhouseholditems/mcdburger.mdl', + 'models/foodnhouseholditems/mcdburgerbox.mdl', + 'models/foodnhouseholditems/mcdburgerboxclosed.mdl', + 'models/foodnhouseholditems/mcdburgerboxempty.mdl', + 'models/foodnhouseholditems/mcdfrenchfries.mdl', + 'models/foodnhouseholditems/mcdfrenchfriesempty.mdl', + 'models/foodnhouseholditems/mcdfrenchfry.mdl', + 'models/foodnhouseholditems/mcdfriedchickenleg.mdl', + 'models/foodnhouseholditems/mcdfriedchickenlegs.mdl', + 'models/foodnhouseholditems/mcdketchup.mdl', + 'models/foodnhouseholditems/mcdketchup2.mdl', + 'models/foodnhouseholditems/mcdmeal.mdl', + 'models/foodnhouseholditems/mcdmeal2.mdl', + 'models/foodnhouseholditems/mcdmealplate.mdl', + 'models/foodnhouseholditems/hotdog.mdl', + 'models/foodnhouseholditems/sandwich.mdl', + 'models/foodnhouseholditems/chicken_wrap.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/bs_cup.mdl', + 'models/gta_prop_michou/prop_food_bs_burg3.mdl', + 'models/gta_prop_michou/prop_food_bs_coffee.mdl', + 'models/gta_prop_michou/prop_food_cups2.mdl', + 'models/gta_prop_michou/prop_food_tray_01.mdl', + 'models/gta_prop_michou/prop_food_tray_02.mdl', + 'models/gta_prop_michou/prop_food_tray_03.mdl', + 'models/hotdog.mdl', + 'models/sal/ammunation/burgerbag.mdl', + 'models/props_canteen/pizza_box.mdl', + 'models/props_canteen/taco.mdl', + }, + {'Продовольственные товары и еда', + 'models/foodnhouseholditems/cookies.mdl', + 'models/foodnhouseholditems/digestive.mdl', + 'models/foodnhouseholditems/digestive2.mdl', + 'models/foodnhouseholditems/kellogscornflakes.mdl', + 'models/foodnhouseholditems/pandapuffs.mdl', + 'models/foodnhouseholditems/cheerios.mdl', + 'models/foodnhouseholditems/applejacks.mdl', + 'models/foodnhouseholditems/cokopops.mdl', + 'models/foodnhouseholditems/chocorings.mdl', + 'models/foodnhouseholditems/toblerone.mdl', + 'models/foodnhouseholditems/toffifee.mdl', + 'models/foodnhouseholditems/marabou1.mdl', + 'models/foodnhouseholditems/marabou2.mdl', + 'models/foodnhouseholditems/marabou3.mdl', + 'models/foodnhouseholditems/marabou4.mdl', + 'models/foodnhouseholditems/kinderbox.mdl', + 'models/foodnhouseholditems/kinderboxopen.mdl', + 'models/foodnhouseholditems/kinderboxempty.mdl', + 'models/foodnhouseholditems/kindersurprise.mdl', + 'models/foodnhouseholditems/kindersurprise2.mdl', + 'models/foodnhouseholditems/kindersurprisehalf.mdl', + 'models/foodnhouseholditems/chipsdoritos.mdl', + 'models/foodnhouseholditems/chipsdoritos2.mdl', + 'models/foodnhouseholditems/chipsdoritos3.mdl', + 'models/foodnhouseholditems/chipsdoritos4.mdl', + 'models/foodnhouseholditems/chipsdoritos5.mdl', + 'models/foodnhouseholditems/chipsdoritos6.mdl', + 'models/foodnhouseholditems/chipsfritos.mdl', + 'models/foodnhouseholditems/chipsfritosbbq.mdl', + 'models/foodnhouseholditems/chipsfritoshoops.mdl', + 'models/foodnhouseholditems/chipsfritostwists.mdl', + 'models/foodnhouseholditems/chipslays.mdl', + 'models/foodnhouseholditems/chipslays2.mdl', + 'models/foodnhouseholditems/chipslays3.mdl', + 'models/foodnhouseholditems/chipslays3.mdl', + 'models/foodnhouseholditems/chipslays4.mdl', + 'models/foodnhouseholditems/chipslays5.mdl', + 'models/foodnhouseholditems/chipslays6.mdl', + 'models/foodnhouseholditems/chipslays7.mdl', + 'models/foodnhouseholditems/chipslays8.mdl', + 'models/foodnhouseholditems/chipsbag1.mdl', + 'models/foodnhouseholditems/chipsbag2.mdl', + 'models/foodnhouseholditems/chipsbag3.mdl', + 'models/foodnhouseholditems/chipscheezit.mdl', + 'models/foodnhouseholditems/chipstropical.mdl', + 'models/foodnhouseholditems/chipstwisties.mdl', + 'models/foodnhouseholditems/ketchup.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/ketchup.mdl', + 'models/ketchup.mdl', + 'models/sinap.mdl', + 'models/foodnhouseholditems/picklejar.mdl', + 'models/foodnhouseholditems/coffee_nescafe.mdl', + 'models/foodnhouseholditems/honey_jar.mdl', + 'models/foodnhouseholditems/nutella.mdl', + 'models/foodnhouseholditems/peanut_butter.mdl', + 'models/foodnhouseholditems/icecream1.mdl', + 'models/foodnhouseholditems/icecream_open1.mdl', + 'models/foodnhouseholditems/icecream_empty1.mdl', + 'models/foodnhouseholditems/icecream_lid1.mdl', + 'models/foodnhouseholditems/icecream2.mdl', + 'models/foodnhouseholditems/icecream_open2.mdl', + 'models/foodnhouseholditems/icecream_empty2.mdl', + 'models/foodnhouseholditems/icecream_lid2.mdl', + 'models/foodnhouseholditems/icecream3.mdl', + 'models/foodnhouseholditems/icecream_open3.mdl', + 'models/foodnhouseholditems/icecream_empty3.mdl', + 'models/foodnhouseholditems/icecream_lid3.mdl', + 'models/foodnhouseholditems/icecream4.mdl', + 'models/foodnhouseholditems/icecream_open4.mdl', + 'models/foodnhouseholditems/icecream_empty4.mdl', + 'models/foodnhouseholditems/icecream_lid4.mdl', + 'models/foodnhouseholditems/icecream5.mdl', + 'models/foodnhouseholditems/icecream_open5.mdl', + 'models/foodnhouseholditems/icecream_empty5.mdl', + 'models/foodnhouseholditems/icecream_lid5.mdl', + 'models/foodnhouseholditems/icecream6.mdl', + 'models/foodnhouseholditems/icecream_open6.mdl', + 'models/foodnhouseholditems/icecream_empty6.mdl', + 'models/foodnhouseholditems/icecream_lid6.mdl', + 'models/foodnhouseholditems/egg.mdl', + 'models/foodnhouseholditems/egg1.mdl', + 'models/props_phx/misc/egg.mdl', + 'models/foodnhouseholditems/egg2.mdl', + 'models/foodnhouseholditems/egg_box1.mdl', + 'models/foodnhouseholditems/egg_box2.mdl', + 'models/foodnhouseholditems/egg_box3.mdl', + 'models/foodnhouseholditems/egg_box4.mdl', + 'models/foodnhouseholditems/cheesewheel1a.mdl', + 'models/foodnhouseholditems/cheesewheel1b.mdl', + 'models/foodnhouseholditems/cheesewheel1c.mdl', + 'models/foodnhouseholditems/cheesewheel2a.mdl', + 'models/foodnhouseholditems/cheesewheel2b.mdl', + 'models/foodnhouseholditems/cheesewheel2c.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/tincorn.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/tomsoup.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/potnoodle.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/cereal01.mdl', + 'models/ronnie76props/cerealbox/cerealbox.mdl', + 'models/props_junk/food_pile02.mdl', + 'models/mark2580/gtav/barstuff/food_sugarjar.mdl', + 'models/mark2580/gtav/barstuff/cockshaker.mdl', + 'models/mark2580/gtav/barstuff/martinishaker_closed.mdl', + 'models/mark2580/gtav/barstuff/martinishaker_open.mdl', + 'models/props_canteen/donutbox.mdl', + }, + {'Торты и закуски', + 'models/foodnhouseholditems/cake.mdl', + 'models/foodnhouseholditems/cake5a.mdl', + 'models/foodnhouseholditems/cake4a.mdl', + 'models/foodnhouseholditems/cake3a.mdl', + 'models/foodnhouseholditems/cake2a.mdl', + 'models/foodnhouseholditems/cake1a.mdl', + 'models/foodnhouseholditems/cakeslice1.mdl', + 'models/foodnhouseholditems/cakepiece.mdl', + 'models/foodnhouseholditems/cake_b.mdl', + 'models/foodnhouseholditems/cake5b.mdl', + 'models/foodnhouseholditems/cake4b.mdl', + 'models/foodnhouseholditems/cake3b.mdl', + 'models/foodnhouseholditems/cake2b.mdl', + 'models/foodnhouseholditems/cake1b.mdl', + 'models/foodnhouseholditems/cakeslice2.mdl', + 'models/foodnhouseholditems/cakepiece2.mdl', + 'models/foodnhouseholditems/pancakes.mdl', + 'models/foodnhouseholditems/pancake.mdl', + 'models/foodnhouseholditems/icecream.mdl', + 'models/mark2580/gtav/barstuff/bar_beans.mdl', + }, + {'Напитки', + 'models/foodnhouseholditems/sodacan01.mdl', + 'models/foodnhouseholditems/sodacan02.mdl', + 'models/foodnhouseholditems/sodacan03.mdl', + 'models/foodnhouseholditems/sodacan04.mdl', + 'models/foodnhouseholditems/sodacan05.mdl', + 'models/foodnhouseholditems/sodacan06.mdl', + 'models/foodnhouseholditems/sodacanb01.mdl', + 'models/foodnhouseholditems/sodacanb02.mdl', + 'models/foodnhouseholditems/sodacanb03.mdl', + 'models/foodnhouseholditems/sodacanb04.mdl', + 'models/foodnhouseholditems/sodacanc01.mdl', + 'models/foodnhouseholditems/cola_swift1.mdl', + 'models/foodnhouseholditems/cola_swift2.mdl', + 'models/foodnhouseholditems/cola.mdl', + 'models/foodnhouseholditems/colabig.mdl', + 'models/foodnhouseholditems/sprunk1.mdl', + 'models/foodnhouseholditems/sprunk2.mdl', + 'models/props/cs_office/water_bottle.mdl', + 'models/drug_mod/the_bottle_of_water.mdl', + 'models/foodnhouseholditems/juicesmall.mdl', + 'models/foodnhouseholditems/juice.mdl', + 'models/foodnhouseholditems/juice2.mdl', + 'models/foodnhouseholditems/juice3.mdl', + 'models/foodnhouseholditems/juice4.mdl', + 'models/foodnhouseholditems/milk.mdl', + 'models/foodnhouseholditems/milk2.mdl', + 'models/zerochain/fruitslicerjob/fs_sweeteners.mdl', + 'models/foodnhouseholditems/pineapple_drink.mdl', + 'models/foodnhouseholditems/coconut_drink.mdl', + 'models/cookingmod/coffee/coffee.mdl', + 'models/themask/scenebuildthemes/groceries/sm_coffee_cup_paper_02.mdl', + }, + {'Алкогольные напитки', + 'models/foodnhouseholditems/beer_master.mdl', + 'models/foodnhouseholditems/beer_stoltz.mdl', + 'models/foodnhouseholditems/beercan01.mdl', + 'models/foodnhouseholditems/beercan02.mdl', + 'models/foodnhouseholditems/beercan03.mdl', + 'models/drug_mod/alcohol_can.mdl', + 'models/foodnhouseholditems/champagneonplate.mdl', + 'models/foodnhouseholditems/champagne.mdl', + 'models/foodnhouseholditems/champagne2.mdl', + 'models/foodnhouseholditems/champagne2b.mdl', + 'models/foodnhouseholditems/champagne3.mdl', + 'models/foodnhouseholditems/wine_red1.mdl', + 'models/foodnhouseholditems/wine_red2.mdl', + 'models/foodnhouseholditems/wine_red3.mdl', + 'models/foodnhouseholditems/wine_rose1.mdl', + 'models/foodnhouseholditems/wine_rose2.mdl', + 'models/foodnhouseholditems/wine_white1.mdl', + 'models/foodnhouseholditems/wine_white2.mdl', + 'models/foodnhouseholditems/wine_white3.mdl', + 'models/foodnhouseholditems/winebottle1.mdl', + 'models/foodnhouseholditems/winebottle2.mdl', + 'models/foodnhouseholditems/winebottle3.mdl', + 'models/foodnhouseholditems/winebottle4.mdl', + 'models/props_shops/prop_bar_bottle_a.mdl', + 'models/props_shops/prop_bar_bottle_b.mdl', + 'models/props_shops/prop_bar_bottle_c.mdl', + 'models/props_shops/prop_bar_bottle_d.mdl', + 'models/props_shops/prop_bar_bottle_f.mdl', + 'models/props_shops/prop_bar_bottle_g.mdl', + 'models/props_shops/prop_bar_bottle_h.mdl', + 'models/mark2580/gtav/barstuff/beer_am.mdl', + 'models/mark2580/gtav/barstuff/beer_bar.mdl', + 'models/mark2580/gtav/barstuff/beer_jakey.mdl', + 'models/mark2580/gtav/barstuff/beer_logger.mdl', + 'models/mark2580/gtav/barstuff/beer_patriot.mdl', + 'models/mark2580/gtav/barstuff/beer_pissh.mdl', + 'models/mark2580/gtav/barstuff/bottle_brandy.mdl', + 'models/mark2580/gtav/barstuff/bottle_cognac.mdl', + 'models/mark2580/gtav/barstuff/bottle_cognac_2.mdl', + 'models/mark2580/gtav/barstuff/cava.mdl', + 'models/mark2580/gtav/barstuff/champ_bucket.mdl', + 'models/mark2580/gtav/barstuff/plonk_red.mdl', + 'models/mark2580/gtav/barstuff/plonk_rose.mdl', + 'models/mark2580/gtav/barstuff/plonk_white.mdl', + 'models/mark2580/gtav/barstuff/rum_bottle.mdl', + 'models/mark2580/gtav/barstuff/rum_bottle_2.mdl', + 'models/mark2580/gtav/barstuff/champ_jer_01a.mdl', + 'models/mark2580/gtav/barstuff/cherenkov_01.mdl', + 'models/mark2580/gtav/barstuff/tequila_bottle.mdl', + 'models/mark2580/gtav/barstuff/vodka_bottle.mdl', + 'models/mark2580/gtav/barstuff/whiskey_bottle.mdl', + 'models/mark2580/gtav/barstuff/whiskey_bottle_2.mdl', + 'models/mark2580/gtav/barstuff/wine_red.mdl', + 'models/mark2580/gtav/barstuff/wine_rose.mdl', + 'models/props_shops/prop_bar_bottle_i.mdl', + 'models/props_interiors/bottles_shelf_break05.mdl', + 'models/props_interiors/bottles_shelf_break06.mdl', + 'models/props_interiors/bottles_shelf_break07.mdl', + 'models/props_interiors/bottles_shelf_break08.mdl', + 'models/props_interiors/bottles_shelf_break09.mdl', + 'models/props_shops/prop_bar_bottle_e.mdl', + 'models/props_interiors/bottles_shelf_break01.mdl', + 'models/props_interiors/bottles_shelf_break02.mdl', + 'models/props_interiors/bottles_shelf_break03.mdl', + 'models/props_interiors/bottles_shelf_break04.mdl', + 'models/props_interiors/bottles_shelf_break10.mdl', + 'models/props_interiors/bottles_shelf_break11.mdl', + 'models/props_interiors/bottles_shelf_break12.mdl', + 'models/models/prop_bottle_macbeth.mdl', + 'models/models/prop_bottle_richard.mdl', + 'models/mark2580/gtav/barstuff/pitcher_01.mdl', + 'models/mark2580/gtav/barstuff/champ_flute.mdl', + 'models/mark2580/gtav/barstuff/cocktail.mdl', + 'models/mark2580/gtav/barstuff/glass_09.mdl', + 'models/mark2580/gtav/barstuff/glass_08.mdl', + 'models/mark2580/gtav/barstuff/tequila.mdl', + 'models/mark2580/gtav/barstuff/tequsunrise.mdl', + 'models/mark2580/gtav/barstuff/glass_07.mdl', + 'models/mark2580/gtav/barstuff/glass_10.mdl', + 'models/mark2580/gtav/barstuff/glass_11.mdl', + 'models/mark2580/gtav/barstuff/optic_jd.mdl', + 'models/mark2580/gtav/barstuff/optic_jd_static.mdl', + 'models/mark2580/gtav/barstuff/optic_rum.mdl', + 'models/mark2580/gtav/barstuff/optic_rum_static.mdl', + 'models/mark2580/gtav/barstuff/optic_vodka.mdl', + 'models/mark2580/gtav/barstuff/optic_vodka_static.mdl', + }, + {'Морепродукты', + 'models/foodnhouseholditems/fishcatfish.mdl', + 'models/foodnhouseholditems/fishbass.mdl', + 'models/foodnhouseholditems/fishgolden.mdl', + 'models/foodnhouseholditems/fishrainbow.mdl', + 'models/foodnhouseholditems/piranha.mdl', + 'models/foodnhouseholditems/fishsteak.mdl', + 'models/foodnhouseholditems/salmon.mdl', + 'models/foodnhouseholditems/lobster.mdl', + 'models/foodnhouseholditems/lobster2.mdl', + 'models/props/cs_militia/fishriver01.mdl', + 'models/props/de_inferno/goldfish.mdl', + 'models/zerochain/fruitslicerjob/fs_cardboardbox.mdl', + }, + {'Мясо', + 'models/foodnhouseholditems/bacon.mdl', + 'models/foodnhouseholditems/baconcooked.mdl', + 'models/foodnhouseholditems/bacon_2.mdl', + 'models/foodnhouseholditems/meat3.mdl', + 'models/foodnhouseholditems/meat4.mdl', + 'models/foodnhouseholditems/meat5.mdl', + 'models/foodnhouseholditems/meat6.mdl', + 'models/foodnhouseholditems/meat7.mdl', + 'models/foodnhouseholditems/meat8.mdl', + 'models/foodnhouseholditems/meat9.mdl', + 'models/foodnhouseholditems/meat9b.mdl', + 'models/foodnhouseholditems/meat_ribs.mdl', + 'models/foodnhouseholditems/steak1.mdl', + 'models/foodnhouseholditems/steak2.mdl', + 'models/foodnhouseholditems/turkey.mdl', + 'models/foodnhouseholditems/turkey2.mdl', + 'models/foodnhouseholditems/turkeyleg.mdl', + 'models/props_shops/prop_butcher_meat_01_a.mdl', + 'models/props_shops/prop_butcher_meat_01_b.mdl', + 'models/props_shops/prop_butcher_meat_01_c.mdl', + 'models/props_shops/prop_butcher_meat_01_d.mdl', + 'models/props_shops/prop_butcher_meat_01_e.mdl', + 'models/props_shops/prop_butcher_meat_01_f.mdl', + 'models/props_shops/prop_butcher_meat_01_g.mdl', + 'models/props_shops/prop_butcher_meat_02_a.mdl', + 'models/props_shops/prop_butcher_meat_02_b.mdl', + 'models/props_shops/prop_butcher_meat_02_c.mdl', + 'models/props_shops/prop_butcher_meat_02_d.mdl', + 'models/props_shops/prop_butcher_meathalf_01_a.mdl', + 'models/props_shops/prop_butcher_meathalf_01_b.mdl', + 'models/props_shops/prop_butcher_meathalf_01_c.mdl', + 'models/props_shops/prop_butcher_sausage_01_a.mdl', + 'models/props_shops/prop_butcher_sausage_01_b.mdl', + 'models/props_shops/prop_butcher_sausage_01_c.mdl', + 'models/props_shops/prop_butcher_sausage_01_d.mdl', + 'models/props_shops/prop_butcher_sausage_01_e.mdl', + 'models/props_shops/prop_butcher_sausage_01_f.mdl', + 'models/props_shops/prop_butcher_sausage_01_g.mdl', + 'models/props_shops/prop_butcher_set_01_a.mdl', + 'models/props_shops/prop_butcher_set_01_b.mdl', + 'models/props_shops/prop_butcher_set_01_c.mdl', + 'models/props_shops/prop_butcher_set_01_d.mdl', + 'models/props_shops/prop_butcher_set_01_e.mdl', + 'models/props_shops/prop_butcher_set_01_f.mdl', + }, + {'Кухонная утварь', + 'models/foodnhouseholditems/plate.mdl', + 'models/foodnhouseholditems/servingplate.mdl', + 'models/foodnhouseholditems/cakeplate.mdl', + 'models/foodnhouseholditems/champagneplate.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/fruitbowl_01_1.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/fruitbowl_02_1.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/mbowl.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/fruitbowls/fruitbowl_03_1.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_02.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_03.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_04.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_05.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_06.mdl', + 'models/mark2580/gtav/barstuff/pitcher_02.mdl', + 'models/foodnhouseholditems/champglass.mdl', + 'models/mark2580/gtav/barstuff/glass_03.mdl', + 'models/mark2580/gtav/barstuff/glass_04.mdl', + 'models/mark2580/gtav/barstuff/glass_05.mdl', + 'models/mark2580/gtav/barstuff/pint_glass_01.mdl', + 'models/mark2580/gtav/barstuff/glass_02.mdl', + 'models/mark2580/gtav/barstuff/glass_06.mdl', + 'models/mark2580/gtav/barstuff/shot_glass.mdl', + 'models/mark2580/gtav/barstuff/glass_stack_09.mdl', + 'models/mark2580/gtav/barstuff/glass_01.mdl', + 'models/mark2580/gtav/barstuff/glass_stack.mdl', + 'models/mark2580/gtav/barstuff/glass_stack_2.mdl', + 'models/mark2580/gtav/tequilala_map/bar_bottles/prop_shot_glass.mdl', + 'models/mark2580/gtav/tequilala_map/bar_bottles/prop_whiskey_01.mdl', + 'models/mark2580/gtav/tequilala_map/bar_bottles/prop_glass_stack_04.mdl', + 'models/mark2580/gtav/tequilala_map/bar_bottles/prop_glass_stack_05.mdl', + 'models/mark2580/gtav/tequilala_map/bar_bottles/prop_glass_stack_10.mdl', + 'models/mark2580/gtav/tequilala_map/bar_bottles/prop_glass_stack_09.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_08.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_09.mdl', + 'models/props_shops/prop_bar_glass_c.mdl', + 'models/props_shops/prop_bar_glass_d.mdl', + 'models/props_shops/prop_bar_glass_a.mdl', + 'models/props_shops/prop_bar_glass_b.mdl', + 'models/sal/ammunation/cup.mdl', + 'models/props_shops/prop_bar_glass_e.mdl', + 'models/props_shops/prop_bar_glass_f.mdl', + 'models/props_shops/prop_bar_glass_g.mdl', + 'models/props_shops/prop_bar_glass_h.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/mug_04.mdl', + 'models/sal/ammunation/coffeecup.mdl', + 'models/sal/ammunation/coffeecup2.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_glasses_07.mdl', + 'models/zerochain/fruitslicerjob/fs_fruitcup.mdl', + 'models/props_canteen/spork.mdl', + }, + {'Чистящие средства и т.д.', + 'models/foodnhouseholditems/windowcleaner.mdl', + 'models/foodnhouseholditems/cillitbang.mdl', + 'models/foodnhouseholditems/lemoncleaner.mdl', + 'models/foodnhouseholditems/blox.mdl', + 'models/foodnhouseholditems/blox2.mdl', + 'models/foodnhouseholditems/clorox.mdl', + 'models/foodnhouseholditems/softener1.mdl', + 'models/foodnhouseholditems/softener2.mdl', + 'models/foodnhouseholditems/softener3.mdl', + 'models/foodnhouseholditems/softener4.mdl', + 'models/foodnhouseholditems/soapfud.mdl', + 'models/foodnhouseholditems/soapjizz.mdl', + 'models/foodnhouseholditems/toiletpaper1.mdl', + 'models/foodnhouseholditems/toiletpaper2.mdl', + }, + {'Товары для покупок и т.д.', + 'models/foodnhouseholditems/trolley.mdl', + 'models/foodnhouseholditems/trolley2.mdl', + 'models/foodnhouseholditems/paperbag1.mdl', + 'models/foodnhouseholditems/paperbag2.mdl', + 'models/foodnhouseholditems/paperbag3.mdl', + 'models/foodnhouseholditems/paperbag4.mdl', + 'models/foodnhouseholditems/paperbag5.mdl', + 'models/foodnhouseholditems/newspaper1.mdl', + 'models/foodnhouseholditems/newspaper2.mdl', + 'models/foodnhouseholditems/balloons.mdl', + 'models/foodnhouseholditems/balloonshappy.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/breadbin_01.mdl', + 'models/mark2580/gtav/barstuff/bar_caddy.mdl', + 'models/mark2580/gtav/barstuff/stirrers.mdl', + 'models/mark2580/gtav/barstuff/bar_drinkstraws.mdl', + 'models/mark2580/gtav/barstuff/coasterdisp.mdl', + 'models/mark2580/gtav/barstuff/napkindisp.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/ashtray_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/bong_01.mdl', + 'models/mark2580/gtav/tequilala_map/bar/prop_bar_pump_06.mdl', + 'models/mark2580/gtav/tequilala_map/bar/prop_bar_pump_06_gta4.mdl', + 'models/scenery/structural/vesuvius/bartap.mdl', + 'models/props_interiors/bottles_shelf.mdl', + 'models/mark2580/gtav/tequilala_map/bar/prop_bar_fridge_02.mdl', + 'models/mark2580/gtav/tequilala_map/bar/prop_bar_fridge_03.mdl', + 'models/mark2580/gtav/tequilala_map/bar/prop_bar_ice_01.mdl', + 'models/props/cs_militia/caseofbeer01.mdl', + 'models/props_canteen/food_tray.mdl', + }, + }, +} diff --git a/garrysmod/addons/util-other/lua/spawnlists/home/_category.lua b/garrysmod/addons/util-other/lua/spawnlists/home/_category.lua new file mode 100644 index 0000000..1d9e2de --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/home/_category.lua @@ -0,0 +1,4 @@ +return { + name = 'Дом', + icon = 'icon16/television.png', +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/home/bathroom.lua b/garrysmod/addons/util-other/lua/spawnlists/home/bathroom.lua new file mode 100644 index 0000000..c3732fe --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/home/bathroom.lua @@ -0,0 +1,117 @@ +return { + name = 'Ванная', + icon = 'octoteam/icons-16/toilet_pan.png', + content = { + {'Душевые кабины', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_shower.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/shower.mdl', + 'models/mark2580/gtav/mp_apa_mid/bathroom/v_16_mid_bath_mesh_delta_high.mdl', + 'models/env/furniture/showerbase/showerbase.mdl', + 'models/env/furniture/shower/shower.mdl', + 'models/props_interiors/bathtub01.mdl', + 'models/sims/gm_shower.mdl', + 'models/props_interiors/bathtub01a.mdl', + 'models/props_c17/furniturebathtub001a.mdl', + 'models/props/cs_militia/urine_trough.mdl', + 'models/props_wasteland/laundry_cart001.mdl', + 'models/furniturepack3/bathroom/shower_a.mdl', + 'models/furniturepack3/bathroom/tub_a.mdl', + 'models/furniturepack3/bathroom/tub_b.mdl', + 'models/props/interior/bathtub01.mdl', + 'models/themask/scenebuildthemes/furniture/hygiene/bathtub_01.mdl', + }, + {'Мойки', + 'models/props_c17/furnituresink001a.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_bath.mdl', + 'models/env/furniture/ensuite1_sink/ensuite1_sink.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_sink_cabinet.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/sink.mdl', + 'models/mark2580/gtav/mp_apa_mid/bathroom/v_16_mid_bath_mesh_delta_high_wood.mdl', + 'models/env/furniture/square_sink/sink_double.mdl', + 'models/env/furniture/square_sink/sink_merged_b.mdl', + 'models/env/furniture/wc_double_cupboard/wc_double_cupboard.mdl', + 'models/props_wasteland/prison_sink001a.mdl', + 'models/furniturepack3/bathroom/sink_a.mdl', + 'models/furniturepack3/bathroom/sink_b.mdl', + 'models/props/interior/sinkwallmounted01.mdl', + 'models/props_downtown/bathroom_vanity01.mdl', + 'models/props_interiors/pedestal_sink.mdl', + 'models/oskar/interior/kommod01.mdl', + }, + {'Унитазы', + 'models/mark2580/gtav/mp_apa_low/bathroom/toilet.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_toilet.mdl', + 'models/env/furniture/ensuite1_toilet/ensuite1_toilet.mdl', + 'models/env/furniture/ensuite1_toilet/ensuite1_toilet_b.mdl', + 'models/props_interiors/urinal01.mdl', + 'models/sims/gm_toilet.mdl', + 'models/props_c17/furnituretoilet001a.mdl', + 'models/props/cs_militia/toilet.mdl', + 'models/props_wasteland/prison_toilet01.mdl', + 'models/furniturepack3/bathroom/toilet_a.mdl', + 'models/props/interior/toilet_commercial01a.mdl', + 'models/props/interior/toilet_residential01a.mdl', + 'models/props/interior/toilet_residential01b.mdl', + 'models/props/interior/toilet_residential02a.mdl', + 'models/props_interiors/urinal01.mdl', + 'models/props_interiors/toilet.mdl', + }, + {'Ковры', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_carpet.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/rug.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/carpet.mdl', + }, + {'Украшения', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mp_h_acc_candles_04_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mp_h_acc_candles_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_shelf_01.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/apa_mpa6_bathrm_shelf_02.mdl', + 'models/props_interiors/soap_dispenser.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toilet_shamp_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toilet_shamp_02_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toilet_soap_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toilet_soap_02_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toilet_soap_03_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toilet_soap_04_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/v_serv_bs_looroll_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/v_ret_ps_cologne_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/v_ret_ps_cologne_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toothpaste_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bathroom/prop_toothbrush_01_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/hanger2.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/hanger.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/hanger1.mdl', + 'models/props_wasteland/prison_heater002a.mdl', + 'models/props_interiors/toiletpaperdispenser_residential.mdl', + 'models/props_interiors/toiletpaperroll.mdl', + 'models/props/cs_militia/toothbrushset01.mdl', + 'models/props/cs_militia/soap_rope.mdl', + 'models/props_wasteland/prison_pipefaucet001a.mdl', + 'models/props/cs_office/paper_towels.mdl', + 'models/props_c17/furniturewashingmachine001a.mdl', + 'models/props/cs_militia/dryer.mdl', + 'models/props_interiors/washer.mdl', + 'models/props_junk/garbage_plasticbottle001a.mdl', + 'models/props_junk/garbage_plasticbottle002a.mdl', + 'models/props_junk/garbage_milkcarton001a.mdl', + 'models/props/de_inferno/laundrylineflat.mdl', + 'models/props/de_inferno/laundrylineacross.mdl', + 'models/props_junk/metalbucket01a.mdl', + 'models/props_junk/metalbucket02a.mdl', + 'models/props_junk/plasticbucket001a.mdl', + 'models/props_c17/furnitureboiler001a.mdl', + 'models/props/cs_militia/furnace01.mdl', + 'models/props_wasteland/laundry_dryer002.mdl', + 'models/props_wasteland/laundry_dryer001.mdl', + 'models/props/interior/toiletpaperdispenser_residential.mdl', + 'models/props/interior/toiletpaperroll.mdl', + 'models/props/interior/towelrack01.mdl', + 'models/props/interior/waterheater01.mdl', + 'models/props_interiors/toiletpaperdispenser_residential.mdl', + 'models/props_interiors/toiletpaperroll.mdl', + 'models/props_interiors/paper_towel_dispenser.mdl', + 'models/props_interiors/trashcankitchen01.mdl', + 'models/oskar/interior/washingmachine01.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/home/common.lua b/garrysmod/addons/util-other/lua/spawnlists/home/common.lua new file mode 100644 index 0000000..a494502 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/home/common.lua @@ -0,0 +1,398 @@ +return { + name = 'Общее', + icon = 'octoteam/icons-16/world.png', + content = { + {'Картины, постеры', + 'models/props_junk/ravenholmsign.mdl', + 'models/props/de_tides/sign_swinging.mdl', + 'models/props/de_tides/tides_staffonly_sign.mdl', + 'models/props_lab/corkboard001.mdl', + 'models/props_lab/corkboard002.mdl', + 'models/props/cs_office/offcorkboarda.mdl', + 'models/props/cs_office/offcertificatea.mdl', + 'models/props_lab/frame001a.mdl', + 'models/props_lab/frame002a.mdl', + 'models/props_c17/frame002a.mdl', + 'models/props/de_inferno/picture1.mdl', + 'models/props/de_inferno/picture2.mdl', + 'models/props/de_inferno/picture3.mdl', + 'models/env/decor/painting/paint_ship_a.mdl', + 'models/props/cs_office/offpaintinga.mdl', + 'models/props/cs_office/offpaintingb.mdl', + 'models/props/cs_office/offpaintingd.mdl', + 'models/props/cs_office/offpaintinge.mdl', + 'models/props/cs_office/offpaintingf.mdl', + 'models/props/cs_office/offpaintingg.mdl', + 'models/props/cs_office/offpaintingh.mdl', + 'models/props/cs_office/offpaintingi.mdl', + 'models/props/cs_office/offpaintingj.mdl', + 'models/props/cs_office/offpaintingk.mdl', + 'models/props/cs_office/offpaintingl.mdl', + 'models/props/cs_office/offpaintingm.mdl', + 'models/props/cs_office/offpaintingo.mdl', + 'models/furniturepack3/pictures/frame_a.mdl', + 'models/furniturepack3/pictures/frame_a_big.mdl', + 'models/furniturepack3/pictures/mastersofhardcore.mdl', + 'models/furniturepack3/pictures/posters_101drinking.mdl', + 'models/furniturepack3/pictures/posters_assassinscreed.mdl', + 'models/furniturepack3/pictures/posters_borderlands_1.mdl', + 'models/furniturepack3/pictures/posters_brains.mdl', + 'models/furniturepack3/pictures/posters_brains_zombie.mdl', + 'models/furniturepack3/pictures/posters_coolstory.mdl', + 'models/furniturepack3/pictures/posters_daftpunk.mdl', + 'models/furniturepack3/pictures/posters_deadpool_a.mdl', + 'models/furniturepack3/pictures/posters_doom.mdl', + 'models/furniturepack3/pictures/posters_dragrace.mdl', + 'models/furniturepack3/pictures/posters_equality.mdl', + 'models/furniturepack3/pictures/posters_exploited.mdl', + 'models/furniturepack3/pictures/posters_fallenstars.mdl', + 'models/furniturepack3/pictures/posters_fallout3.mdl', + 'models/furniturepack3/pictures/posters_fartzone.mdl', + 'models/furniturepack3/pictures/posters_fightclub.mdl', + 'models/furniturepack3/pictures/posters_gaming_2.mdl', + 'models/furniturepack3/pictures/posters_gaming_3.mdl', + 'models/furniturepack3/pictures/posters_gaming_4.mdl', + 'models/furniturepack3/pictures/posters_gaming_5.mdl', + 'models/furniturepack3/pictures/posters_gaming_a.mdl', + 'models/furniturepack3/pictures/posters_ghostbusters.mdl', + 'models/furniturepack3/pictures/posters_guitarchords.mdl', + 'models/furniturepack3/pictures/posters_halo_a.mdl', + 'models/furniturepack3/pictures/posters_halo_b.mdl', + 'models/furniturepack3/pictures/posters_hardcore.mdl', + 'models/furniturepack3/pictures/posters_hellrock.mdl', + 'models/furniturepack3/pictures/posters_highwayofthreats.mdl', + 'models/furniturepack3/pictures/posters_housemusic.mdl', + 'models/furniturepack3/pictures/posters_iheartmusic.mdl', + 'models/furniturepack3/pictures/posters_imnotcen.mdl', + 'models/furniturepack3/pictures/posters_internet.mdl', + 'models/furniturepack3/pictures/posters_ipurged.mdl', + 'models/furniturepack3/pictures/posters_keepcalm_a.mdl', + 'models/furniturepack3/pictures/posters_keepcalm_b.mdl', + 'models/furniturepack3/pictures/posters_kungfupanda.mdl', + 'models/furniturepack3/pictures/posters_marajuana.mdl', + 'models/furniturepack3/pictures/posters_mario_a.mdl', + 'models/furniturepack3/pictures/posters_meplusdog.mdl', + 'models/furniturepack3/pictures/posters_metroid_a.mdl', + 'models/furniturepack3/pictures/posters_minecraft_a.mdl', + 'models/furniturepack3/pictures/posters_nosmoking.mdl', + 'models/furniturepack3/pictures/posters_openmind.mdl', + 'models/furniturepack3/pictures/posters_pikachu.mdl', + 'models/furniturepack3/pictures/posters_pokemon.mdl', + 'models/furniturepack3/pictures/posters_portal_a.mdl', + 'models/furniturepack3/pictures/posters_proudbychoice.mdl', + 'models/furniturepack3/pictures/posters_ratchetandclank.mdl', + 'models/furniturepack3/pictures/posters_scarsymm.mdl', + 'models/furniturepack3/pictures/posters_skaters.mdl', + 'models/furniturepack3/pictures/posters_skull.mdl', + 'models/furniturepack3/pictures/posters_skyrim_a.mdl', + 'models/furniturepack3/pictures/posters_slipknot.mdl', + 'models/furniturepack3/pictures/posters_sonic_a.mdl', + 'models/furniturepack3/pictures/posters_spaceballs.mdl', + 'models/furniturepack3/pictures/posters_spaceinvaders_a.mdl', + 'models/furniturepack3/pictures/posters_spaceinvaders_b.mdl', + 'models/furniturepack3/pictures/posters_stevenuniverse.mdl', + 'models/furniturepack3/pictures/posters_stormtrooper.mdl', + 'models/furniturepack3/pictures/posters_successfart.mdl', + 'models/furniturepack3/pictures/posters_suicidejoker.mdl', + 'models/furniturepack3/pictures/posters_tequila.mdl', + 'models/furniturepack3/pictures/posters_terraria_a.mdl', + 'models/furniturepack3/pictures/posters_therewillbeblood.mdl', + 'models/furniturepack3/pictures/posters_tlm.mdl', + 'models/furniturepack3/pictures/posters_tombraider_a.mdl', + 'models/furniturepack3/pictures/posters_twistedmetal.mdl', + 'models/furniturepack3/pictures/posters_wreckitralph.mdl', + 'models/furniturepack3/pictures/posters_yeswecannabis.mdl', + 'models/furniturepack3/pictures/posters_yourfuture.mdl', + 'models/furniturepack3/pictures/posters_zelda_a.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_dining_art3_high2.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_dining_art3_high3.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_dining_art3_high4.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_dining_art3_high5.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_dining_art3_high6.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_art_high_prt3.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_art_high_prt4.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_art_high_prt2.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_art_high_prt1.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_kitchen_art_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_art_high_01.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_art_high_02.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_art_high_03.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedroom_art_high_04.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/ls_poster.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/poster_01.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/poster_02.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/poster_03.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/poster_04.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_goldrecords.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_pics.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_16_midapt_posters_07.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_16_midapt_posters_08.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/wall_poster_01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_posters_02.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_posters_05.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_posters_06.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_art_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mp_h_acc_artwalll_02_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_posters_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/windows_treeglow.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_posters_04.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/fireplace.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_posters_03.mdl', + 'models/models/h4_int_04_wall_decor.mdl', + 'models/models/v_ret_mirror.mdl', + 'models/models/h4_int_04_lion.mdl', + 'models/models/h4_int_04_coat_of_arms.mdl', + 'models/props_interiors/painting_landscape01.mdl', + 'models/props_interiors/painting_portrait01.mdl', + 'models/props_furniture/picture_frame8.mdl', + 'models/props/cs_office/offinspa.mdl', + 'models/props/cs_office/offinspb.mdl', + 'models/props/cs_office/offinspc.mdl', + 'models/props/cs_office/offinspd.mdl', + 'models/props/cs_office/offinspf.mdl', + 'models/props/cs_office/offinspg.mdl', + 'models/props/cs_office/offpaintinga.mdl', + 'models/props/cs_office/offpaintingb.mdl', + 'models/props/cs_office/offpaintingd.mdl', + 'models/props/cs_office/offpaintinge.mdl', + 'models/props/cs_office/offpaintingf.mdl', + 'models/props/cs_office/offpaintingg.mdl', + 'models/props/cs_office/offpaintingh.mdl', + 'models/props/cs_office/offpaintingi.mdl', + 'models/props/cs_office/offpaintingj.mdl', + 'models/props/cs_office/offpaintingk.mdl', + 'models/props/cs_office/offpaintingl.mdl', + 'models/props/cs_office/offpaintingm.mdl', + 'models/props/cs_office/offpaintingo.mdl', + 'models/props_office/pictureframe04.mdl', + 'models/props/interior/pictureframe01a.mdl', + 'models/props/interior/pictureframe01b.mdl', + 'models/props/interior/pictureframe01c.mdl', + 'models/props/interior/pictureframe01d.mdl', + 'models/props/interior/pictureframe01e.mdl', + 'models/props/interior/pictureframe01f.mdl', + 'models/props/interior/pictureframe01g.mdl', + 'models/nt/props_office/painting_one.mdl', + 'models/nt/props_office/painting_three.mdl', + 'models/nt/props_office/painting_two.mdl', + }, + {'Растения для дома', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_fa_plant01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_tree_high.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/plant_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/plant_04.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/plant_int_04b.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/flower.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/prop_plant_int_04a.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_plant_high.mdl', + 'models/models/v_res_rubberplant.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/prop_plant_int_03a.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/flower.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_dining_plant1_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_dining_plant2_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_living_plant1_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_05_ivy_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_acc_plant_tall_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_acc_vase_flowers_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_acc_vase_flowers_04_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_plant_palmtree_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/v_ret_ps_flowers_02_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/v_ret_ps_flowers_01_high.mdl', + 'models/models/v_res_mflowers.mdl', + 'models/props_plants/plantairport01.mdl', + 'models/Highrise/potted_plant_05.mdl', + 'models/env/decor/tall_plant_b/tall_plant_b.mdl', + 'models/env/decor/plant_decofern/plant_decofern.mdl', + 'models/props_junk/terracotta01.mdl', + 'models/props_lab/cactus.mdl', + 'models/props/cs_office/plant01.mdl', + 'models/props/de_inferno/flower_barrel.mdl', + 'models/props/de_tides/flowerbed.mdl', + 'models/props/de_inferno/fountain_bowl.mdl', + 'models/props/de_inferno/pot_big.mdl', + 'models/props/de_inferno/claypot03.mdl', + 'models/props/de_inferno/claypot03_damage_01.mdl', + 'models/props/de_inferno/potted_plant1.mdl', + 'models/props/de_tides/planter.mdl', + 'models/props/de_inferno/potted_plant1_p1.mdl', + 'models/props/de_inferno/potted_plant2.mdl', + 'models/props/de_inferno/potted_plant2_p1.mdl', + 'models/props/de_inferno/potted_plant3.mdl', + 'models/props/de_inferno/potted_plant3_p1.mdl', + 'models/env/decor/plant_decofern/plant_decofern.mdl', + 'models/props_plants/plantairport01.mdl', + 'models/env/decor/tall_plant_b/tall_plant_b.mdl', + 'models/props/de_inferno/cactus.mdl', + 'models/props/de_inferno/cactus2.mdl', + 'models/props/de_inferno/largebush01.mdl', + 'models/props/de_inferno/largebush02.mdl', + 'models/props/de_inferno/largebush03.mdl', + 'models/props/de_inferno/largebush04.mdl', + 'models/props/de_inferno/largebush05.mdl', + 'models/props/de_inferno/largebush06.mdl', + 'models/props_foliage/ivy_01.mdl', + 'models/props/cs_militia/vine_wall2.mdl', + 'models/props/de_inferno/claypot01.mdl', + 'models/props/de_inferno/claypot02.mdl', + 'models/props_c17/pottery01a.mdl', + 'models/props_c17/pottery02a.mdl', + 'models/props_c17/pottery04a.mdl', + 'models/props_c17/pottery05a.mdl', + 'models/props_c17/pottery06a.mdl', + 'models/props_c17/pottery07a.mdl', + 'models/props_c17/pottery08a.mdl', + 'models/props_c17/pottery09a.mdl', + 'models/props_c17/pottery_large01a.mdl', + 'models/props_plants/bush.mdl', + 'models/props_plants/claypot03.mdl', + 'models/props_plants/plantairport01.mdl', + 'models/props_plants/planthanging01.mdl', + 'models/props_foliage/urban_balcony_planter01a.mdl', + 'models/props_foliage/urban_balcony_planter01a_small.mdl', + }, + {'Эффекты', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_02_living_fire_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/candle_fire2.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/candle_fire.mdl', + }, + {'Свет', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_floorlamp_b_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_floorlamp_c_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_hologen_light_01.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_hologen_light_02.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_shelves_lamps.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/ceilinglight_01.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/ceilinglight_02.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/ceilinglight_03.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/led_floor.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_lamp.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_lamp2.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/desklamp.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/lamp.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/table_lamp_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/tablelamp2.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_ceiling_lamp.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_d_lampa_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_fa_candle01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_fa_candle02.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_fa_candle03.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_fa_candle04.mdl', + 'models/models/v_res_m_candlelrg.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_m_lampstand_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_mp_sofa_high_lamp.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/v_16_midapt_kitchen_spotlight.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mp_h_lit_floorlamp_03_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_mp_h_02_lightpend02_high.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mpa6_kitchen_lamp_long001_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/lamp.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/apa_mp_h_lit_floorlamp_01_high.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/ceiling_lamp.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mpa6_lightpend2_high.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_studylamp.mdl', + 'models/mark2580/gtav/tequilala_map/misc/club_roc_lampoff.mdl', + 'models/sal/ammunation/range_light.mdl', + 'models/sal/ammunation/range_mat.mdl', + 'models/sal/ammunation/desklamp.mdl', + 'models/sal/ammunation/ceiling_light.mdl', + 'models/sal/ammunation/light1.mdl', + 'models/props_unique/spawn_apartment/lantern.mdl', + 'models/env/lighting/lamp_trumpet/lamp_trumpet_tall.mdl', + 'models/env/lighting/jelly_lamp/jellylamp.mdl', + 'models/env/lighting/corridor_ceil_lamp/corridor_ceil_lamp.mdl', + 'models/env/lighting/corridorlamp/corridorlamp.mdl', + 'models/props_urban/light_fixture01.mdl', + 'models/Highrise/tall_lamp_01.mdl', + 'models/U4Lab/track_lighting_a.mdl', + 'models/Highrise/sconce_01.mdl', + 'models/wilderness/lamp6.mdl', + 'models/props_interiors/lamp_table02.mdl', + 'models/splayn/rp/lr/lamp_small.mdl', + 'models/splayn/rp/lr/lamp_tall.mdl', + 'models/props/cs_assault/light_shop2.mdl', + 'models/sims/gm_lightwall.mdl', + 'models/sims/lightwall2.mdl', + 'models/sickness/parkinglotlight.mdl', + 'models/props/de_tides/tides_streetlight.mdl', + 'models/u4lab/track_lighting_a.mdl', + 'models/props_c17/light_floodlight02_off.mdl', + 'models/props_interiors/furniture_lamp01a.mdl', + 'models/highrise/tall_lamp_01.mdl', + 'models/props_lab/desklamp01.mdl', + 'models/env/lighting/corridor_ceil_lamp/corridor_ceil_lamp.mdl', + 'models/highrise/sconce_01.mdl', + 'models/props/de_chateau/light_chandelier02.mdl', + 'models/props/de_tides/tides_light_fixture.mdl', + 'models/props/cs_assault/streetlight.mdl', + 'models/props_lighting/lightfixture02.mdl', + 'models/props/de_nuke/wall_light_off.mdl', + 'models/props/cs_office/light_ceiling.mdl', + 'models/props_wasteland/light_spotlight01_lamp.mdl', + 'models/props_wasteland/light_spotlight02_lamp.mdl', + 'models/props_wasteland/prison_cagedlight001a.mdl', + 'models/props_c17/light_cagelight01_on.mdl', + 'models/props/de_nuke/emergency_lighta.mdl', + 'models/props/de_nuke/light_red2.mdl', + 'models/props/de_nuke/light_red1.mdl', + 'models/props_wasteland/prison_lamp001c.mdl', + 'models/props/cs_militia/lightfixture01.mdl', + 'models/props/de_train/floodlight.mdl', + 'models/props/de_train/light_security.mdl', + 'models/props_c17/light_decklight01_off.mdl', + 'models/props_c17/light_decklight01_on.mdl', + 'models/props/cs_assault/floodlight02.mdl', + 'models/props_c17/light_domelight02_on.mdl', + 'models/props/de_inferno/wall_lamp2.mdl', + 'models/props/cs_italy/it_lampholder2.mdl', + 'models/props/de_tides/sign.mdl', + 'models/props_construction/light_floodlight01a.mdl', + 'models/haxxer/normandy/lamp01.mdl', + 'models/haxxer/normandy/lamp02.mdl', + 'models/props/hospital/ceiling_light1.mdl', + 'models/props/interior/ceiling_light.mdl', + 'models/props/lamps/ceiling_light01.mdl', + 'models/props/lamps/desklamp01.mdl', + 'models/props/lamps/halogen_light.mdl', + 'models/props/lamps/p_lamp_fluor_01.mdl', + 'models/props/lamps/p_lamp_fluor_02.mdl', + 'models/props/lamps/sconce_candle01.mdl', + 'models/props_equipment/light_floodlight.mdl', + 'models/props_furniture/inn_chandelier1.mdl', + 'models/props_furniture/lamp1.mdl', + 'models/props_interiors/furniture_lamp01a.mdl', + 'models/props_interiors/lamp_floor.mdl', + 'models/props_interiors/lamp_table01.mdl', + 'models/props_interiors/lamp_table02.mdl', + 'models/props_interiors/lightsconce02.mdl', + 'models/props_lighting/lampbedside01.mdl', + 'models/props_lighting/lightfixture03.mdl', + 'models/props_lighting/lightfixture05_off.mdl', + 'models/props_misc/lamp-1.mdl', + 'models/props_downtown/light02_32.mdl', + 'models/props_unique/spawn_apartment/lantern.mdl', + 'models/props_urban/hotel_lamp001.mdl', + 'models/props_urban/porch_light001.mdl', + 'models/props_urban/porch_light001_02.mdl', + }, + {'Вентиляторы', + 'models/props/de_inferno/ceiling_fan_blade.mdl', + 'models/props/de_prodigy/fan.mdl', + 'models/props/interior/ceiling_fan.mdl', + }, + {'Розетки, выключатели и т.д.', + 'models/sal/ammunation/outlet.mdl', + 'models/sal/ammunation/outlet2.mdl', + 'models/sal/ammunation/switch.mdl', + 'models/sal/ammunation/powerstrip.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_metalswitches_01.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_metalswitches_02.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_serv_switch_2_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/lr_smodrv_2socket_001_high.mdl', + 'models/props/socked_01a.mdl', + 'models/props/socked_01b.mdl', + 'models/props/socked_switch_01a.mdl', + 'models/props/socked_switch_01b.mdl', + 'models/props/switch_01a.mdl', + 'models/props/switch_01b.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/home/kitchen.lua b/garrysmod/addons/util-other/lua/spawnlists/home/kitchen.lua new file mode 100644 index 0000000..7978df9 --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/home/kitchen.lua @@ -0,0 +1,169 @@ +return { + name = 'Кухня', + icon = 'octoteam/icons-16/cookie_chocolate.png', + content = { + {'Наборы', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_ref_prt_01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_ref_prt_03.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_ref_prt_02.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/kitchen_ref_prt_05.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mpa6_dining_kitchen_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/v_16_mpmidapart01_high.mdl', + }, + {'Плиты', + 'models/gta_prop_michou/prop_cooker_03.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/plate.mdl', + 'models/sickness/stove_01.mdl', + 'models/props_c17/furniturestove001a.mdl', + 'models/props/cs_militia/stove01.mdl', + 'models/props_wasteland/kitchen_stove001a.mdl', + 'models/props_wasteland/kitchen_stove002a.mdl', + 'models/cookingmod/gas_stove/gas_stove.mdl', + 'models/zerochain/props_pizza/zpizmak_oven.mdl', + 'models/zerochain/props_pizza/zpizmak_ovendoor.mdl', + 'models/props/appliances/kitchen_stove_home.mdl', + 'models/props_interiors/stove02.mdl', + }, + {'Шкафчики и тумбы', + 'models/mark2580/gtav/mp_apa_low/kitchen/cabinet_03.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/cabinet_02.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/cabinet_04.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/cabinet_01.mdl', + 'models/gta_prop_michou/prop_ff_counter_01.mdl', + 'models/gta_prop_michou/prop_ff_counter_02.mdl', + 'models/gta_prop_michou/prop_ff_counter_03.mdl', + 'models/props_wasteland/kitchen_counter001a.mdl', + 'models/props_wasteland/kitchen_counter001c.mdl', + 'models/props_wasteland/kitchen_counter001d.mdl', + 'models/props_wasteland/kitchen_counter001b.mdl', + 'models/props_wasteland/kitchen_shelf001a.mdl', + 'models/props_wasteland/kitchen_shelf002a.mdl', + 'models/props_c17/furniturecupboard001a.mdl', + 'models/furniturepack3/kitchen/counter_a.mdl', + 'models/furniturepack3/kitchen/counter_a_1x2.mdl', + 'models/furniturepack3/kitchen/counter_a_1x4.mdl', + 'models/furniturepack3/kitchen/counter_b.mdl', + 'models/furniturepack3/kitchen/counter_b_1x2.mdl', + 'models/furniturepack3/kitchen/counter_b_1x4.mdl', + 'models/furniturepack3/kitchen/counter_overhead_a.mdl', + 'models/furniturepack3/kitchen/counter_overhead_a_1x2.mdl', + 'models/furniturepack3/kitchen/counter_overhead_a_1x4.mdl', + 'models/furniturepack3/kitchen/counter_overhead_b.mdl', + 'models/furniturepack3/kitchen/counter_overhead_b_1x2.mdl', + 'models/furniturepack3/kitchen/counter_overhead_b_1x4.mdl', + 'models/furniturepack3/kitchen/counter_overhead_half_a.mdl', + 'models/furniturepack3/kitchen/counter_overhead_half_b.mdl', + 'models/furniturepack3/kitchen/counter_overhead_half_c.mdl', + 'models/furniturepack3/kitchen/counter_overhead_sec_a.mdl', + 'models/furniturepack3/kitchen/counter_sec.mdl', + 'models/furniturepack3/kitchen/counter_sec_b.mdl', + 'models/furniturepack3/kitchen/counter_sec_b_inv.mdl', + 'models/furniturepack3/kitchen/counter_sec_overhead_b.mdl', + 'models/haxxer/normandy/kitchentable.mdl', + 'models/furniture/kitchen_set01_counter_cabinets01.mdl', + 'models/furniture/kitchen_set01_counter_drawers01.mdl', + 'models/furniture/kitchen_set01_dishwasher01.mdl', + 'models/furniture/kitchen_set02_black_old_bar_end01.mdl', + 'models/furniture/kitchen_set02_black_old_counter1_1.mdl', + 'models/furniture/kitchen_set02_black_old_pantry.mdl', + 'models/furniture/kitchen_set02_black_old_upper.mdl', + 'models/furniture/kitchen_set02_black_old_upper_cornerl.mdl', + 'models/furniture/kitchen_set02_black_old_upper_cornerr.mdl', + 'models/furniture/kitchen_set02_old_bar_curve01.mdl', + 'models/furniture/kitchen_set02_old_bar_end01.mdl', + 'models/furniture/kitchen_set02_old_bar_straight01.mdl', + 'models/furniture/kitchen_set02_old_counter1_1.mdl', + 'models/props_furniture/kitchen_cabinet1.mdl', + 'models/props_furniture/kitchen_countertop1.mdl', + 'models/props_furniture/kitchen_shelf1.mdl', + 'models/props_furniture/kitchen_vent1.mdl', + 'models/props_interiors/industrial_table01.mdl', + 'models/props_interiors/stovehood01.mdl', + 'models/oskar/interior/kitchen_fan.mdl', + }, + {'Мойки', + 'models/gta_prop_michou/prop_ff_sink_02.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/sink.mdl', + 'models/props_interiors/sink_kitchen.mdl', + 'models/furniturepack3/kitchen/counter_sink_a.mdl', + 'models/furniturepack3/kitchen/counter_sink_b.mdl', + 'models/props/interior/farmkitchensink01.mdl', + 'models/props/interior/laundrysink01.mdl', + 'models/furniture/kitchen_set01_sink01.mdl', + 'models/furniture/kitchen_set02_black_old_sink01.mdl', + 'models/furniture/kitchen_set02_old_sink01.mdl', + }, + {'Кухонные бытовые приборы', + 'models/gta_prop_michou/prop_microwave_1.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_micro_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/prop_coffee_mac_02.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/v_res_tre_mixer_high.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/v_res_m_kscales_high.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/kettle_01.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/grater.mdl', + 'models/gta_prop_michou/prop_kebab_grill.mdl', + 'models/gta_prop_michou/prop_griddle_02.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_toaster_01_high.mdl', + 'models/props_interiors/coffee_maker.mdl', + 'models/sims/microwave.mdl', + 'models/sims/gm_coffeemaker.mdl', + 'models/props_unique/coffeemachine01.mdl', + 'models/props/cs_office/microwave.mdl', + 'models/props/cs_militia/microwave01.mdl', + 'models/props_canteen/microwave01.mdl', + 'models/props_office/coffe_machine.mdl', + 'models/props_office/coffe_pot.mdl', + 'models/zerochain/fruitslicerjob/fs_mixer.mdl', + 'models/props/interior/coffee_maker.mdl', + 'models/props_interiors/toaster.mdl', + 'models/themask/scenebuildthemes/furniture/business/sm_coffee_station_01.mdl', + 'models/oskar/interior/prop_kitchen_dishhold.mdl', + }, + {'Холодильники', + 'models/props_c17/FurnitureFridge001a.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/fridge.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/v_res_fridgemoda_high.mdl', + 'models/sims/gm_fridge.mdl', + 'models/props_interiors/refrigerator03.mdl', + 'models/sickness/fridge_01.mdl', + 'models/props_wasteland/kitchen_fridge001a.mdl', + 'models/props/cs_militia/refrigerator01.mdl', + 'models/props_c17/furniturefridge001a.mdl', + 'models/props_interiors/refrigerator01a.mdl', + 'models/props_interiors/refrigeratordoor01a.mdl', + 'models/props_interiors/refrigeratordoor02a.mdl', + 'models/props/appliances/fridge1.mdl', + 'models/props/appliances/fridge2.mdl', + 'models/props_downtown/mini_fridge.mdl', + 'models/props_interiors/fridge_mini.mdl', + }, + {'Другие украшения', + 'models/models/prop_pot_rack.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen/kit_wall-stuff_01.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_wok01_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_wok_high.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/sponge01.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/washlq.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_knife01_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_knife_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/prop_knife_stand_high.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/v_res_mchopboard_high.mdl', + 'models/props_c17/grinderclamp01a.mdl', + 'models/props_c17/metalpot001a.mdl', + 'models/props_c17/metalpot002a.mdl', + 'models/props_interiors/pot01a.mdl', + 'models/props_interiors/pot02a.mdl', + 'models/props_junk/meathook001a.mdl', + 'models/props_lab/cleaver.mdl', + 'models/props_lab/ladel.mdl', + 'models/weapons/w_knife_ct.mdl', + 'models/props_c17/furniturefireplace001a.mdl', + 'models/props_junk/garbage_metalcan001a.mdl', + 'models/props_junk/garbage_metalcan002a.mdl', + 'models/cookingmod/frying_pan/frying_pan.mdl', + 'models/cookingmod/pan/pan.mdl', + 'models/cookingmod/tray/tray.mdl', + 'models/props/appliances/fridge_1930.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/spawnlists/home/living.lua b/garrysmod/addons/util-other/lua/spawnlists/home/living.lua new file mode 100644 index 0000000..6052c0f --- /dev/null +++ b/garrysmod/addons/util-other/lua/spawnlists/home/living.lua @@ -0,0 +1,699 @@ +return { + name = 'Гостиная', + icon = 'octoteam/icons-16/sofa.png', + content = { + {'Столы', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mp_h_din_table_06_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/v_res_m_dinetble_replace_high.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/sidetable.mdl', + 'models/mark2580/gtav/mp_apa_06/misc/apa_mp_h_06_balcony_stuff.mdl', + 'models/sal/ammunation/table1.mdl', + 'models/sal/ammunation/table3.mdl', + 'models/props_interiors/dining_table_round.mdl', + 'models/props_interiors/dinning_table_oval.mdl', + 'models/props_downtown/booth_table.mdl', + 'models/sims/gm_diningtable.mdl', + 'models/sims3props/table.mdl', + 'models/props/cs_office/table_meeting.mdl', + 'models/sal/office/conftable3.mdl', + 'models/props_c17/furnituretable001a.mdl', + 'models/props_c17/furnituretable002a.mdl', + 'models/props/de_tides/restaurant_table.mdl', + 'models/props/cs_militia/wood_table.mdl', + 'models/wooden_furniture/table_circle_f.mdl', + 'models/wooden_furniture/table_middle.mdl', + 'models/domestic/folding_table.mdl', + 'models/furniture/desk_office_01.mdl', + 'models/furniture/dining_table1.mdl', + 'models/furniture/dining_table2.mdl', + 'models/furniture/furniture_patio_table.mdl', + 'models/wooden_furniture/table_big.mdl', + 'models/props_downtown/booth_table.mdl', + 'models/props_interiors/dining_table_round.mdl', + 'models/props_interiors/dinning_table_oval.mdl', + 'models/props_interiors/table_folding.mdl', + 'models/props_interiors/table_motel.mdl', + 'models/props_interiors/tablecafe_square01.mdl', + 'models/props_furniture/heavy_table.mdl', + 'models/props_furniture/it_mkt_table2.mdl', + 'models/props_interiors/table_cafeteria.mdl', + 'models/props_interiors/table_console.mdl', + 'models/props_interiors/table_end.mdl', + 'models/props_interiors/table_kitchen.mdl', + 'models/props_urban/round_table001.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/sm_table_wood_02.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/sm_table_wood_02a.mdl', + 'models/sal/office/officedesk.mdl', + 'models/sal/office/officedesk_clean4.mdl', + 'models/models/v_res_mbdresser.mdl', + 'models/sal/ammunation/table4.mdl', + 'models/sal/ammunation/officedesk.mdl', + 'models/sims3props/dsk.mdl', + 'models/sims/desk.mdl', + 'models/U4Lab/desk_office_a.mdl', + 'models/props_warehouse/office_furniture_desk.mdl', + 'models/props_warehouse/office_furniture_desk_corner.mdl', + 'models/props_office/desk_01.mdl', + 'models/props_interiors/desk_executive.mdl', + 'models/env/furniture/largedesk/largedesk.mdl', + 'models/props_combine/breendesk.mdl', + 'models/props_interiors/furniture_desk01a.mdl', + 'models/splayn/table_gewerbe.mdl', + 'models/splayn/rp/of/desk1.mdl', + 'models/furniturepack3/desks/desk_a.mdl', + 'models/furniturepack3/desks/desk_b.mdl', + 'models/furniturepack3/desks/desk_c.mdl', + 'models/furniturepack3/desks/desk_d.mdl', + 'models/furniturepack3/desks/desk_e.mdl', + 'models/furniturepack3/desks/desk_f.mdl', + 'models/furniturepack3/desks/desk_g_1.mdl', + 'models/furniturepack3/desks/desk_g_2.mdl', + 'models/furniturepack3/desks/desk_h.mdl', + 'models/furniturepack3/desks/desk_i.mdl', + 'models/furniturepack3/desks/desk_j.mdl', + 'models/furniturepack3/desks/desk_k.mdl', + 'models/furniturepack3/desks/desk_l.mdl', + 'models/furniturepack3/desks/desk_m.mdl', + 'models/furniturepack3/desks/desk_n.mdl', + 'models/furniturepack3/desks/desk_o.mdl', + 'models/furniturepack3/desks/desk_p.mdl', + 'models/furniturepack3/desks/desk_q.mdl', + 'models/furniturepack3/desks/desk_r.mdl', + 'models/furniturepack3/desks/desk_s.mdl', + 'models/furniturepack3/desks/desk_t.mdl', + 'models/furniturepack3/desks/desk_u.mdl', + 'models/furniturepack3/desks/desk_v.mdl', + 'models/haxxer/normandy/meddesk.mdl', + 'models/haxxer/normandy/meddeskcor.mdl', + 'models/props/interior/metal_desk_closed.mdl', + 'models/props_interiors/desk_executive.mdl', + 'models/props_interiors/desk_metal.mdl', + 'models/props_office/desk_01.mdl', + 'models/themask/scenebuildthemes/furniture/desk/officedesk_02a.mdl', + 'models/themask/scenebuildthemes/furniture/desk/sm_computer_desk_01.mdl', + 'models/themask/scenebuildthemes/furniture/desk/sm_executive_desk_01.mdl', + 'models/themask/scenebuildthemes/furniture/desk/sm_executive_desk_02.mdl', + 'models/props/cs_militia/bar01.mdl', + 'models/props_downtown/bar_long.mdl', + 'models/props_downtown/bar_long_endcorner.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mp_h_tab_sidelrg_07_high.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_sofa_table.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/coffeetable.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_04.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_tab_coffee_13_high.mdl', + 'models/mark2580/gtav/tequilala_map/misc/club_roc_ctable.mdl', + 'models/props/cs_office/table_coffee.mdl', + 'models/props_warehouse/office_furniture_coffee_table.mdl', + 'models/testmodels/coffee_table_long.mdl', + 'models/props_c17/furnituretable003a.mdl', + 'models/props/cs_office/table_coffee.mdl', + 'models/splayn/rp/lr/entertainment_c1.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_a.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_b.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_c.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_d.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_e.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_f.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_g.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_h.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_i.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_j.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_k.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_l.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_m.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_n.mdl', + 'models/furniturepack3/coffee_tables/coffeetable_o.mdl', + 'models/haxxer/normandy/coffetable.mdl', + 'models/props/interior/coffee_table_glass.mdl', + 'models/props/interior/coffee_table_glass_small.mdl', + 'models/domestic/glass_coffee_table.mdl', + 'models/furniture/bench_modern_plastic.mdl', + 'models/furniture/patio_modern_shorttable.mdl', + 'models/props_interiors/coffee_table_oval.mdl', + 'models/props_interiors/coffee_table_rectangular.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedwide_06_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_cabinet3.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_cabinet.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/curbstone.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_bedsidetable_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_cabinet2.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_06_livingsideboard_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_sideboardm02_high.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_cabinet.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_storageunit_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_storagebox_high.mdl', + 'models/mark2580/gtav/mp_apa_06/studyroom/apa_mpa6_studydetail_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_02.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_03.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/tv_cabinet.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_dining_shelf.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_sideboard_high.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/coffeetable_mid.mdl', + 'models/props_interiors/side_table_square.mdl', + 'models/sims/gm_endtable.mdl', + 'models/scenery/furniture/coffeetable1/vestbl.mdl', + 'models/props/de_inferno/tableantique.mdl', + 'models/sal/ammunation/table2.mdl', + 'models/models/v_res_mconsolemod.mdl', + 'models/models/v_res_cabinet.mdl', + 'models/models/h4_int_04_side_table.mdl', + 'models/models/v_res_m_console.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_str_avunitl_04_edited.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_03_stuff.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_shelves_hifi.mdl', + 'models/sims/gm_hometheatre.mdl', + 'models/props_interiors/desk_motel.mdl', + 'models/props_vtmb/chinacabinet.mdl', + 'models/props_c17/furnituredrawer001a.mdl', + 'models/props_c17/furnituredrawer001a_chunk01.mdl', + 'models/props_c17/furnituredrawer001a_chunk02.mdl', + 'models/props_c17/furnituredrawer003a.mdl', + 'models/props_interiors/furniture_cabinetdrawer01a.mdl', + 'models/props_interiors/furniture_cabinetdrawer02a.mdl', + 'models/props_c17/furnituredrawer002a.mdl', + 'models/props_wasteland/prison_shelf002a.mdl', + 'models/props/cs_militia/gun_cabinet.mdl', + 'models/props_interiors/furniture_shelf01a.mdl', + 'models/props/cs_italy/winerack_large.mdl', + 'models/props/cs_italy/winerack_small.mdl', + 'models/props_c17/shelfunit01a.mdl', + 'models/props_c17/furnitureshelf001a.mdl', + 'models/props_c17/furnitureshelf001b.mdl', + 'models/props_wasteland/controlroom_desk001a.mdl', + 'models/props_wasteland/controlroom_desk001b.mdl', + 'models/props/cs_italy/it_mkt_table2.mdl', + 'models/props/cs_italy/it_mkt_table1.mdl', + 'models/props/cs_italy/it_mkt_table3.mdl', + 'models/props_wasteland/cafeteria_table001a.mdl', + 'models/props/cs_militia/table_shed.mdl', + 'models/props/cs_militia/table_kitchen.mdl', + 'models/props_interiors/furniture_vanity01a.mdl', + 'models/splayn/rp/lr/endtable1.mdl', + 'models/furniturepack3/bedroom/bedroom_table_a.mdl', + 'models/furniturepack3/bedroom/bedroom_table_b.mdl', + 'models/furniturepack3/bedroom/drawer_a.mdl', + 'models/furniturepack3/bedroom/drawer_b.mdl', + 'models/furniturepack3/bedroom/drawer_d.mdl', + 'models/furniturepack3/bedroom/drawer_e.mdl', + 'models/furniturepack3/bedroom/drawer_f.mdl', + 'models/furniturepack3/bedroom/drawer_g.mdl', + 'models/furniturepack3/bedroom/drawer_h.mdl', + 'models/furniturepack3/bedroom/drawer_i.mdl', + 'models/furniturepack3/bedroom/drawer_j.mdl', + 'models/furniturepack3/bedroom/drawer_k.mdl', + 'models/furniturepack3/bedroom/drawer_l.mdl', + 'models/furniturepack3/livingroom/shelf_a.mdl', + 'models/furniturepack3/livingroom/shelf_b.mdl', + 'models/furniturepack3/livingroom/tvstand_a.mdl', + 'models/furniturepack3/livingroom/tvstand_b.mdl', + 'models/furniturepack3/livingroom/tvstand_c.mdl', + 'models/furniturepack3/livingroom/tvstand_d.mdl', + 'models/furniturepack3/livingroom/tvstand_e.mdl', + 'models/furniturepack3/livingroom/tvstand_f.mdl', + 'models/furniturepack3/livingroom/tvstand_g.mdl', + 'models/furniturepack3/livingroom/tvstand_h.mdl', + 'models/wooden_furniture/bureau.mdl', + 'models/wooden_furniture/cupboard.mdl', + 'models/wooden_furniture/dresser.mdl', + 'models/wooden_furniture/table_con.mdl', + 'models/wooden_furniture/table_circle.mdl', + 'models/props/interior/dresser_short.mdl', + 'models/props/interior/dresser_tall.mdl', + 'models/props/interior/dresser_wardrobe01.mdl', + 'models/props/interior/desk_motel.mdl', + 'models/props/interior/coffee-table.mdl', + 'models/props_interiors/dresser_short.mdl', + 'models/props_interiors/dresser_tall.mdl', + 'models/props_downtown/dresser.mdl', + 'models/props_downtown/side_table.mdl', + 'models/props_interiors/tv_cabinet.mdl', + 'models/props_interiors/entertainment_unit.mdl', + 'models/themask/scenebuildthemes/furniture/desk/computerdesk_01.mdl', + 'models/themask/scenebuildthemes/furniture/desk/frontdesk_01.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/sm_dining_room_hutch_02.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/sm_dresser_01.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/coffee_table_01.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/end_table_01.mdl', + 'models/oskar/interior/table01.mdl', + 'models/oskar/interior/tvbench01.mdl', + 'models/props/de_house/de_house_table01.mdl', + 'models/themask/scenebuildthemes/furniture/sm_twe_table_01.mdl', + 'models/themask/scenebuildthemes/furniture/business/conferencetable_01.mdl', + 'models/themask/scenebuildthemes/furniture/business/deskoffice_01.mdl', + 'models/themask/scenebuildthemes/furniture/business/sm_cafe_table_01.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/fancytable_01.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/sm_table_coffee_01.mdl', + 'models/themask/scenebuildthemes/furniture/tablestuff/uc_officetable_01.mdl', + 'models/themask/scenebuildthemes/furniture/sm_tv_stand_01.mdl', + }, + {'Диваны и тахты', + 'models/props/cs_militia/couch.mdl', + 'models/props_interiors/furniture_couch01a.mdl', + 'models/props_c17/furniturecouch001a.mdl', + 'models/props_c17/furniturecouch002a.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/sofa1.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/sofa2.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_mp_sofa_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_06_sofa_high.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_sofa.mdl', + 'models/models/v_res_r_sofa.mdl', + 'models/mark2580/gtav/tequilala_map/basement/club_officesofa.mdl', + 'models/mark2580/gtav/tequilala_map/misc/couch_01.mdl', + 'models/mark2580/gtav/tequilala_map/misc/couch_02.mdl', + 'models/props_vtmb/sofa.mdl', + 'models/props_interiors/sofa01.mdl', + 'models/props_interiors/sofa02.mdl', + 'models/props_warehouse/office_furniture_couch.mdl', + 'models/env/furniture/decosofa_wood/decosofa_wood_dou.mdl', + 'models/Highrise/lobby_chair_02.mdl', + 'models/testmodels/sofa_double.mdl', + 'models/splayn/rp/lr/couch.mdl', + 'models/props/cs_office/sofa.mdl', + 'models/props/cs_militia/couch.mdl', + 'models/sims3props/sof2.mdl', + 'models/splayn/rp/lr/couch.mdl', + 'models/furniturepack3/couches/couch_a.mdl', + 'models/furniturepack3/couches/couch_b.mdl', + 'models/furniturepack3/couches/couch_c.mdl', + 'models/props_office/sofa_casual.mdl', + 'models/props/interior/comfy_couch_2seat.mdl', + 'models/props/interior/comfy_couch_3seat.mdl', + 'models/www.gaming-models.de/couch/couch_2er.mdl', + 'models/www.gaming-models.de/couch/couch_3er.mdl', + 'models/furniture/couch_suede_brown_01.mdl', + 'models/themask/scenebuildthemes/furniture/sofa_01.mdl', + 'models/themask/scenebuildthemes/furniture/sofa_sleazyclub_02_a.mdl', + 'models/themask/scenebuildthemes/furniture/sofa_sleazyclub_02_b.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/ottoman_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_church_bench_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_couch_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_couch_02.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_couch_leather_02.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/gotham_objects_bench2a.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/parkbench_01.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/sm_bench_metal_01.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/sm_bench_metal_02.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/sm_bench_wood_01.mdl', + 'models/oskar/interior/hallbench01.mdl', + }, + {'Кресла', + 'models/sal/office/lazychair.mdl', + 'models/sal/office/chair2.mdl', + 'models/sal/office/chair1.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_chairarm_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_mp_stripchair_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_stn_chairarm_23_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa_06_pouf_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/stool.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_m_stool_replaced_high.mdl', + 'models/models/v_res_m_stool.mdl', + 'models/models/v_res_mbbedtable.mdl', + 'models/models/v_res_m_armchair.mdl', + 'models/props_interiors/chairlobby01.mdl', + 'models/props_vtmb/armchair.mdl', + 'models/props_interiors/sofa_chair02.mdl', + 'models/props_interiors/ottoman01.mdl', + 'models/Highrise/lobby_chair_01.mdl', + 'models/testmodels/sofa_single.mdl', + 'models/splayn/rp/lr/chair.mdl', + 'models/props/cs_office/sofa_chair.mdl', + 'models/chairs/armchair.mdl', + 'models/sims/gm_livingchair.mdl', + 'models/props_c17/furniturearmchair001a.mdl', + 'models/props_interiors/furniture_couch02a.mdl', + 'models/splayn/rp/lr/chair.mdl', + 'models/furniturepack3/couches/couch_d.mdl', + 'models/props_office/chair_casual.mdl', + 'models/haxxer/normandy/comfychair.mdl', + 'models/props/interior/comfy_couch_1seat.mdl', + 'models/furniture/modular_wall_bench.mdl', + 'models/furniture/modular_wall_bench_angle.mdl', + 'models/furniture/modular_wall_bench_angle_in.mdl', + 'models/furniture/modular_wall_bench_half.mdl', + 'models/furniture/couch_modular02_old_lazboy.mdl', + 'models/props_urban/hotel_chair001.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/armchair.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_chair_fabric_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_chair_fabric_02_bigger.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/fancychair_01.mdl', + }, + {'Стулья', + 'models/nova/airboat_seat.mdl', + 'models/nova/jeep_seat.mdl', + 'models/props/de_inferno/chairantique.mdl', + 'models/props_c17/furniturechair001a.mdl', + 'models/props_interiors/furniture_chair01a.mdl', + 'models/props_wasteland/controlroom_chair001a.mdl', + 'models/props_interiors/furniture_chair03a.mdl', + 'models/nova/chair_plastic01.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_office_chair_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/chair01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_chair_high.mdl', + 'models/sal/office/officechair5_2.mdl', + 'models/models/v_ret_chair.mdl', + 'models/mark2580/gtav/tequilala_map/misc/corp_cd_chair.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mpa6_din_chair_00_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/kitchen/v_res_m_dinechair_high.mdl', + 'models/sal/ammunation/chair1.mdl', + 'models/sal/ammunation/chair2.mdl', + 'models/sal/ammunation/officechair.mdl', + 'models/U4Lab/chair_office_a.mdl', + 'models/props/cs_office/chair_office.mdl', + 'models/nova/chair_office01.mdl', + 'models/props_interiors/chair_office2.mdl', + 'models/props_interiors/chair01.mdl', + 'models/props_interiors/chair_cafeteria.mdl', + 'models/sal/ammunation/bench1.mdl', + 'models/props/de_tides/patio_chair.mdl', + 'models/sims3props/chr.mdl', + 'models/sims/gm_diningchair.mdl', + 'models/props_vtmb/chairfancyhotel.mdl', + 'models/nova/chair_office02.mdl', + 'models/props_c17/chair_kleiner03a.mdl', + 'models/props/de_tides/patio_chair2.mdl', + 'models/props_interiors/patio_chair2_white.mdl', + 'models/props_urban/plastic_chair001.mdl', + 'models/splayn/chair_gewerbe.mdl', + 'models/furniturepack3/chairs/chair_a.mdl', + 'models/haxxer/normandy/medchair.mdl', + 'models/chair_relax/chair_relax.mdl', + 'models/furniture/kitchen_set04_chair01.mdl', + 'models/furniture/office_chair_modern_plastic.mdl', + 'models/furniture/office_chair_leather.mdl', + 'models/furniture/cafe_lounge_chair.mdl', + 'models/furniture/dining_chair.mdl', + 'models/furniture/dining_chair2.mdl', + 'models/furniture/furniture_patio_chair.mdl', + 'models/props_furniture/chair2.mdl', + 'models/props_furniture/hotel_chair.mdl', + 'models/props_interiors/chair_office2.mdl', + 'models/props_interiors/chair_thonet.mdl', + 'models/props_interiors/foldingchair.mdl', + 'models/agency/chairs/officechair.mdl', + 'models/nt/props_office/officechair.mdl', + 'models/nt/props_office/officechair02.mdl', + 'models/oskar/exterior/chair_plastic.mdl', + 'models/themask/scenebuildthemes/furniture/sm_tw_chair_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/baseopschair.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/gotham_objects_furniture_chair.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/bridge_chair.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/deskchair_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/deskchair_02.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/foldingchair_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/foldingchair_02.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_chair_library_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/sm_dh_rocking_chair_01.mdl', + 'models/themask/scenebuildthemes/furniture/st_furn_childchair.mdl', + 'models/themask/scenebuildthemes/furniture/st_furn_deskchair01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/v_res_fh_kitnstool_high.mdl', + 'models/env/furniture/bstoolred/bstoolred.mdl', + 'models/props_furniture/cafe_barstool1.mdl', + 'models/props/cs_militia/barstool01.mdl', + 'models/props_c17/chair_stool01a.mdl', + 'models/pg_props/pg_mobel/pg_barstool.mdl', + 'models/furniture/kitchen_set03_chair01.mdl', + 'models/themask/scenebuildthemes/furniture/business/sm_bar_stool_01.mdl', + 'models/themask/scenebuildthemes/furniture/business/sm_cafe_chair_01.mdl', + 'models/themask/scenebuildthemes/furniture/chairstuff/barstool_01.mdl', + 'models/oskar/interior/chair01.mdl', + 'models/themask/scenebuildthemes/furniture/outdoor/bananachair.mdl', + }, + {'Книжные полки', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_dining_books_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_08_books_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_books_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa6_living_window2_shelves.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_shelves.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_shelves_books.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_stuff_prt2.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_stuff_prt3.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_stuff_prt4.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/book_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/book_02.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/book_03.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/book_shelf.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_02_books.mdl', + 'models/sal/office/officeshelf.mdl', + 'models/sims3props/shlf.mdl', + 'models/sims/gm_bookcase.mdl', + 'models/props/cs_office/bookshelf1.mdl', + 'models/props/cs_havana/bookcase_large.mdl', + 'models/props/cs_havana/bookcase_small.mdl', + 'models/props/cs_office/bookshelf1.mdl', + 'models/props/cs_office/bookshelf2.mdl', + 'models/props/cs_office/bookshelf3.mdl', + 'models/props/interior/cabinet.mdl', + 'models/props_interiors/bookcase_48_96.mdl', + 'models/props_interiors/bookcase_64_96.mdl', + 'models/props_interiors/bookcasehutch01.mdl', + 'models/props_interiors/books01.mdl', + 'models/props_interiors/books02.mdl', + 'models/props_interiors/bottles_shelf.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/bookshelf.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/bookshelfstanding_01_sg.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/officeshelf_01.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/shelf_deskshelves_afa_s2,0.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/sm_bookshelf_01.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/sm_bookshelf_rectory_01.mdl', + 'models/themask/scenebuildthemes/furniture/cabinetstuff/sm_bookshelf_rectory_02.mdl', + 'models/oskar/interior/billy.mdl', + }, + {'Гардероб', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_wardrobe_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/wardrobe.mdl', + 'models/mark2580/gtav/mp_apa_06/wardrobe/apa_mpa6_wardrobe_details_high.mdl', + 'models/models/v_res_mcupboard.mdl', + 'models/sims3props/wall.mdl', + 'models/props_c17/furnituredresser001a.mdl', + 'models/props_lab/lockerdoorright.mdl', + 'models/props_lab/lockers.mdl', + 'models/props_c17/lockers001a.mdl', + 'models/props_wasteland/controlroom_storagecloset001a.mdl', + 'models/props_wasteland/controlroom_storagecloset001b.mdl', + 'models/furniturepack3/bedroom/drawer_c.mdl', + 'models/props/rpd/locker1.mdl', + 'models/props/rpd/locker2.mdl', + 'models/props/rpd/locker_set.mdl', + 'models/props/rpd/metal_cabinet.mdl', + 'models/nt/props_office/shoji1.mdl', + }, + {'Кровати', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedwide_06_high_prt1.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/apa_mpa6_bedwide_06_high_prt2.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/bed.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/bed_cloth.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_16_mid_bed_bed_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_16_mid_bed_bed_high_cloth.mdl', + 'models/props_interiors/bed_motel.mdl', + 'models/props_downtown/bed_motel01.mdl', + 'models/env/furniture/bed_secondclass/beddouble_group.mdl', + 'models/env/furniture/bed_andrea/bed_andrea_1st.mdl', + 'models/sims3props/motor.mdl', + 'models/sims3props/bed1.mdl', + 'models/sims/gm_pinkbed.mdl', + 'models/sims/gm_bedcolonial.mdl', + 'models/props_c17/furniturebed001a.mdl', + 'models/props/de_inferno/bed.mdl', + 'models/props/cs_militia/bunkbed.mdl', + 'models/props/cs_militia/bunkbed2.mdl', + 'models/furniturepack3/bedroom/bed_b.mdl', + 'models/furniturepack3/bedroom/bed.mdl', + 'models/furniturepack3/bedroom/bed_c.mdl', + 'models/furniturepack3/bedroom/bed_d.mdl', + 'models/furniturepack3/bedroom/bed_e.mdl', + 'models/furniturepack3/bedroom/full_bed_a.mdl', + 'models/furniturepack3/bedroom/full_bed_b.mdl', + 'models/furniturepack3/bedroom/full_bed_c.mdl', + 'models/furniturepack3/bedroom/full_bed_d.mdl', + 'models/furniturepack3/bedroom/full_bed_e.mdl', + 'models/furniturepack3/bedroom/full_bed_f.mdl', + 'models/furniturepack3/bedroom/full_bed_g.mdl', + 'models/furniturepack3/bedroom/full_bed_h.mdl', + 'models/furniturepack3/bedroom/full_bed_i.mdl', + 'models/furniturepack3/bedroom/full_bed_j.mdl', + 'models/furniturepack3/bedroom/full_bed_k.mdl', + 'models/furniturepack3/bedroom/full_bed_l.mdl', + 'models/furniturepack3/bedroom/full_bed_m.mdl', + 'models/furniturepack3/bedroom/full_bed_n.mdl', + 'models/furniturepack3/bedroom/twin_bed_a.mdl', + 'models/furniturepack3/bedroom/twin_bed_b.mdl', + 'models/furniturepack3/bedroom/twin_bed_c1.mdl', + 'models/furniturepack3/bedroom/twin_bed_c2.mdl', + 'models/furniturepack3/bedroom/twin_bed_d.mdl', + 'models/furniturepack3/bedroom/twin_bed_e.mdl', + 'models/furniturepack3/bedroom/twin_bed_f.mdl', + 'models/furniturepack3/bedroom/twin_bed_g.mdl', + 'models/furniturepack3/bedroom/twin_bed_h.mdl', + 'models/furniturepack3/bedroom/twin_bed_i.mdl', + 'models/furniturepack3/bedroom/twin_bed_j.mdl', + 'models/furniturepack3/bedroom/twin_bed_k.mdl', + 'models/furniturepack3/bedroom/twin_bed_l.mdl', + 'models/haxxer/normandy/comfybed.mdl', + 'models/props/interior/bunkbed.mdl', + 'models/pg_props/pg_mobel/pg_bed01.mdl', + 'models/pg_props/pg_mobel/pg_old_bed01.mdl', + 'models/pg_props/pg_mobel/pg_old_bed02.mdl', + 'models/furniture/bed_large_01.mdl', + 'models/props_downtown/bed_motel01.mdl', + 'models/props_interiors/baby_crib.mdl', + 'models/props_interiors/bed.mdl', + 'models/props_interiors/bed_houseboat.mdl', + 'models/props_interiors/kids_bed.mdl', + 'models/props/de_house/bed_rustic.mdl', + 'models/themask/scenebuildthemes/furniture/st_furn_bed01.mdl', + }, + {'Ковры', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_06_entrancerug_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_acc_rugwooll_03_rsref002_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_01_living_plant_base_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_carpet_01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_carpet_02.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/rug_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/rug_02.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_rug.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/carpet02.mdl', + }, + {'Электроника', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_phone.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/remote_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/remote_02.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/vhs_tape.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/vcr_01.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/boombox_01.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_pcspeaker.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/speaker.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/v_res_mm_audio.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_speaker.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/tv_03.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/tv_flat_03.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/prop_tv_flat_01_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/el_guitar_03.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/el_guitar_01.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/el_guitar_02.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_shelves_guitars.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/console_01.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/controller_01.mdl', + 'models/sal/ammunation/tv.mdl', + 'models/sal/ammunation/tv2.mdl', + 'models/props/cs_office/tv_plasma.mdl', + 'models/U4Lab/tv_monitor_plasma.mdl', + 'models/gmod_tower/suitetv.mdl', + 'models/testmodels/apple_display.mdl', + 'models/sims/gm_stereo.mdl', + }, + {'Прочие украшения', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_m_horsefig.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_sculpt_dec_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_sculpt_decd_high.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/hero_statuette.mdl', + 'models/mark2580/gtav/mp_apa_06/bedroom/v_res_fashmagopen_high.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/apa_mpa6_tray_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/v_res_fa_bread01.mdl', + 'models/mark2580/gtav/mp_apa_06/kitchen/v_serv_bs_looroll01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_acc_vase_01_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mp_h_acc_vase_02_high.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa7_living_fireplace_burnlogs.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/apa_mpa7_living_fireplace_grid.mdl', + 'models/mark2580/gtav/mp_apa_06/lobby/v_res_jcushiond_high.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_bookend.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/bike_mount012.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/ceiling_fire_alarm.mdl', + 'models/mark2580/gtav/mp_apa_hi_garage/ceiling_vent.mdl', + 'models/mark2580/gtav/mp_apa_low/bathroom/cabinet.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/basket.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_stuff_prt1.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/brm_stuff_u_bed.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/cd_case.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/dvd_case.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/dvd_disc.mdl', + 'models/mark2580/gtav/mp_apa_low/bedroom/shelf.mdl', + 'models/mark2580/gtav/mp_apa_low/kitchen_misc/basket.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/lighter_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_01.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_02.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_03.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_04.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_05.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_06.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/photo_frame_07.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/sketchpad.mdl', + 'models/mark2580/gtav/mp_apa_low/lobby/tabloidsc.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_mlaundry_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_basketmess_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_flatbasket_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/bedroom/v_res_tre_washbasket_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_midapt_cabinet_02_stuff_01.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_16_mpmidapart13_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_jewelbox_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_mp_sofa_high_box_table.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_r_figauth1_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_fruitbowl_high.mdl', + 'models/mark2580/gtav/mp_apa_mid/hall/v_res_tre_storagebox_high.mdl', + 'models/models/v_res_r_figauth2.mdl', + 'models/models/v_res_r_figoblisk.mdl', + 'models/mark2580/gtav/mp_apa_06/planningrm/planningrm_skateboard.mdl', + 'models/props_vtmb/fancy_phone.mdl', + 'models/props_c17/furnitureradiator001a.mdl', + 'models/props_interiors/Radiator01a.mdl', + 'models/gibs/hgibs.mdl', + 'models/props_junk/harpoon002a.mdl', + 'models/props_c17/signpole001.mdl', + 'models/props_junk/sawblade001a.mdl', + 'models/props_c17/pottery03a.mdl', + 'models/props/de_tides/vending_turtle.mdl', + 'models/props_c17/playgroundtick-tack-toe_block01a.mdl', + 'models/maxofs2d/companion_doll.mdl', + 'models/props_c17/doll01.mdl', + 'models/props_lab/huladoll.mdl', + 'models/props_lab/chess.mdl', + 'models/props/cs_militia/mountedfish01.mdl', + 'models/props_wasteland/prison_padlock001a.mdl', + 'models/props_wasteland/prison_padlock001b.mdl', + 'models/props_combine/breenbust.mdl', + 'models/props_combine/breenglobe.mdl', + 'models/props_trainstation/trainstation_clock001.mdl', + 'models/props_c17/clock01.mdl', + 'models/props_combine/breenclock.mdl', + 'models/props_c17/light_magnifyinglamp02.mdl', + 'models/props/cs_italy/it_mkt_container1.mdl', + 'models/props/cs_italy/it_mkt_container1a.mdl', + 'models/props/cs_italy/it_mkt_container2.mdl', + 'models/props/cs_italy/it_mkt_container3.mdl', + 'models/props/cs_italy/it_mkt_container3a.mdl', + 'models/props_junk/plasticcrate01a.mdl', + 'models/props_junk/plasticcrate01a.mdl', + 'models/props_junk/plasticcrate01a.mdl', + 'models/props_lab/kennel_physics.mdl', + 'models/props_lab/citizenradio.mdl', + 'models/props/cs_office/radio.mdl', + 'models/splayn/rp/phone_old.mdl', + 'models/splayn/rp/ac/vase.mdl', + 'models/barkbark/bigtrophy.mdl', + 'models/barkbark/mediumtrophy.mdl', + 'models/barkbark/smalltrophy.mdl', + 'models/gibs/furniture_gibs/furniture_vanity01a_gib05.mdl', + 'models/props/interior/sign_exit.mdl', + 'models/props/interior/sign_exit_2.mdl', + 'models/props_urban/exit_sign001.mdl', + 'models/props_urban/exit_sign002.mdl', + 'models/curtain.mdl', + 'models/curtain2.mdl', + 'models/curtain3.mdl', + 'models/props_interiors/alarm_clock.mdl', + 'models/props_interiors/dvd_player.mdl', + 'models/props_interiors/vcr.mdl', + 'models/props_downtown/iron.mdl', + 'models/props_downtown/ironing_board.mdl', + 'models/props_downtown/keycard_reader.mdl', + 'models/themask/scenebuildthemes/furniture/deco/obj_clock_grandfather.mdl', + 'models/themask/scenebuildthemes/furniture/electronics/boxtv_01.mdl', + 'models/themask/scenebuildthemes/furniture/hygiene/sm_tissue_box_01.mdl', + 'models/themask/scenebuildthemes/furniture/sm_tv_02_sm_tv_01.mdl', + 'models/themask/scenebuildthemes/furniture/sm_umbrella_bin_01.mdl', + 'models/themask/scenebuildthemes/furniture/clothes/scafandre_helmet_01.mdl', + 'models/themask/scenebuildthemes/furniture/deco/animal_head.mdl', + 'models/themask/scenebuildthemes/furniture/deco/ballclock.mdl', + }, + }, +} \ No newline at end of file diff --git a/garrysmod/addons/util-other/lua/vgui/dmenuoption.lua b/garrysmod/addons/util-other/lua/vgui/dmenuoption.lua new file mode 100644 index 0000000..399df8b --- /dev/null +++ b/garrysmod/addons/util-other/lua/vgui/dmenuoption.lua @@ -0,0 +1,148 @@ +local PANEL = {} + +AccessorFunc( PANEL, "m_pMenu", "Menu" ) +AccessorFunc( PANEL, "m_bChecked", "Checked" ) +AccessorFunc( PANEL, "m_bCheckable", "IsCheckable" ) + +function PANEL:Init() + + self:SetContentAlignment( 4 ) + self:SetTextInset( 30, 0 ) -- Room for icon on left + self:SetTextColor( Color( 10, 10, 10 ) ) + self:SetChecked( false ) + +end + +function PANEL:SetSubMenu( menu ) + + self.SubMenu = menu + + if ( !IsValid( self.SubMenuArrow ) ) then + + self.SubMenuArrow = vgui.Create( "DPanel", self ) + self.SubMenuArrow.Paint = function( panel, w, h ) derma.SkinHook( "Paint", "MenuRightArrow", panel, w, h ) end + + end + +end + +function PANEL:AddSubMenu() + + local SubMenu = DermaMenu( self ) + SubMenu:SetVisible( false ) + SubMenu:SetParent( self ) + + self:SetSubMenu( SubMenu ) + + return SubMenu + +end + +function PANEL:OnCursorEntered() + + if ( IsValid( self.ParentMenu ) ) then + self.ParentMenu:OpenSubMenu( self, self.SubMenu ) + return + end + + local p = self:GetParent() + if not IsValid(p) or not p.OpenSubMenu then return end + p:OpenSubMenu( self, self.SubMenu ) + +end + +function PANEL:OnCursorExited() +end + +function PANEL:Paint( w, h ) + + derma.SkinHook( "Paint", "MenuOption", self, w, h ) + + -- + -- Draw the button text + -- + return false + +end + +function PANEL:OnMousePressed( mousecode ) + + self.m_MenuClicking = true + + DButton.OnMousePressed( self, mousecode ) + +end + +function PANEL:OnMouseReleased( mousecode ) + + DButton.OnMouseReleased( self, mousecode ) + + if ( self.m_MenuClicking && mousecode == MOUSE_LEFT ) then + + self.m_MenuClicking = false + CloseDermaMenus() + + end + +end + +function PANEL:DoRightClick() + + if ( self:GetIsCheckable() ) then + self:ToggleCheck() + end + +end + +function PANEL:DoClickInternal() + + if ( self:GetIsCheckable() ) then + self:ToggleCheck() + end + + if ( self.m_pMenu ) then + + self.m_pMenu:OptionSelectedInternal( self ) + + end + +end + +function PANEL:ToggleCheck() + + self:SetChecked( !self:GetChecked() ) + self:OnChecked( self:GetChecked() ) + +end + +function PANEL:OnChecked( b ) +end + +function PANEL:PerformLayout() + + self:SizeToContents() + self:SetWide( self:GetWide() + 30 ) + + local w = math.max( self:GetParent():GetWide(), self:GetWide() ) + + self:SetSize( w, 22 ) + + if ( IsValid( self.SubMenuArrow ) ) then + + self.SubMenuArrow:SetSize( 15, 15 ) + self.SubMenuArrow:CenterVertical() + self.SubMenuArrow:AlignRight( 4 ) + + end + + DButton.PerformLayout( self ) + +end + +function PANEL:GenerateExample() + + -- Do nothing! + +end + +derma.DefineControl( "DMenuOption", "Menu Option Line", PANEL, "DButton" ) diff --git a/garrysmod/addons/util-security/lua/autorun/client/6YuTL2cLbUKlN9zg5UCchHL4txTAfq.lua b/garrysmod/addons/util-security/lua/autorun/client/6YuTL2cLbUKlN9zg5UCchHL4txTAfq.lua new file mode 100644 index 0000000..5b90fc6 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/client/6YuTL2cLbUKlN9zg5UCchHL4txTAfq.lua @@ -0,0 +1,415 @@ +hook.Add('PlayerFinishedLoading', 'antiexp-grab', function() + + if CFG.dev then return end + + local stringFind, fileFind, fileRead = string.find, file.Find, file.Read + + local queue = {} + local goodFiles = { + ['addons/luadev/lua/autorun/easylua.lua'] = true, + ['addons/luadev/lua/luadev/luadev.lua'] = true, + ['addons/luadev/lua/luadev/luadev_sh.lua'] = true, + ['addons/luadev/lua/luadev/socketdev.lua'] = true, + ['lua/autorun/base_npcs.lua'] = true, + ['lua/autorun/base_vehicles.lua'] = true, + ['lua/autorun/developer_functions.lua'] = true, + ['lua/autorun/easylua.lua'] = true, + ['lua/autorun/game_hl2.lua'] = true, + ['lua/autorun/menubar.lua'] = true, + ['lua/autorun/properties.lua'] = true, + ['lua/autorun/utilities_menu.lua'] = true, + ['lua/autorun/client/demo_recording.lua'] = true, + ['lua/autorun/client/gm_demo.lua'] = true, + ['lua/autorun/properties/bodygroups.lua'] = true, + ['lua/autorun/properties/bone_manipulate.lua'] = true, + ['lua/autorun/properties/collisions.lua'] = true, + ['lua/autorun/properties/drive.lua'] = true, + ['lua/autorun/properties/editentity.lua'] = true, + ['lua/autorun/properties/gravity.lua'] = true, + ['lua/autorun/properties/ignite.lua'] = true, + ['lua/autorun/properties/keep_upright.lua'] = true, + ['lua/autorun/properties/kinect_controller.lua'] = true, + ['lua/autorun/properties/npc_scale.lua'] = true, + ['lua/autorun/properties/persist.lua'] = true, + ['lua/autorun/properties/remove.lua'] = true, + ['lua/autorun/properties/skin.lua'] = true, + ['lua/autorun/properties/statue.lua'] = true, + ['lua/autorun/server/admin_functions.lua'] = true, + ['lua/autorun/server/sensorbones/css.lua'] = true, + ['lua/autorun/server/sensorbones/eli.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_engineer.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_heavy.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_medic.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_pyro_demo.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_scout.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_sniper.lua'] = true, + ['lua/autorun/server/sensorbones/tf2_spy_solider.lua'] = true, + ['lua/autorun/server/sensorbones/valvebiped.lua'] = true, + ['lua/derma/derma.lua'] = true, + ['lua/derma/derma_animation.lua'] = true, + ['lua/derma/derma_example.lua'] = true, + ['lua/derma/derma_gwen.lua'] = true, + ['lua/derma/derma_menus.lua'] = true, + ['lua/derma/derma_utils.lua'] = true, + ['lua/derma/init.lua'] = true, + ['lua/drive/drive_base.lua'] = true, + ['lua/drive/drive_noclip.lua'] = true, + ['lua/drive/drive_sandbox.lua'] = true, + ['lua/entities/sent_ball.lua'] = true, + ['lua/entities/widget_arrow.lua'] = true, + ['lua/entities/widget_axis.lua'] = true, + ['lua/entities/widget_base.lua'] = true, + ['lua/entities/widget_bones.lua'] = true, + ['lua/entities/widget_disc.lua'] = true, + ['lua/includes/gmsave.lua'] = true, + ['lua/includes/init.lua'] = true, + ['lua/includes/init_menu.lua'] = true, + ['lua/includes/menu.lua'] = true, + ['lua/includes/util.lua'] = true, + ['lua/includes/vgui_base.lua'] = true, + ['lua/includes/extensions/angle.lua'] = true, + ['lua/includes/extensions/coroutine.lua'] = true, + ['lua/includes/extensions/debug.lua'] = true, + ['lua/includes/extensions/entity.lua'] = true, + ['lua/includes/extensions/ents.lua'] = true, + ['lua/includes/extensions/file.lua'] = true, + ['lua/includes/extensions/game.lua'] = true, + ['lua/includes/extensions/math.lua'] = true, + ['lua/includes/extensions/motionsensor.lua'] = true, + ['lua/includes/extensions/net.lua'] = true, + ['lua/includes/extensions/player.lua'] = true, + ['lua/includes/extensions/player_auth.lua'] = true, + ['lua/includes/extensions/string.lua'] = true, + ['lua/includes/extensions/table.lua'] = true, + ['lua/includes/extensions/util.lua'] = true, + ['lua/includes/extensions/vector.lua'] = true, + ['lua/includes/extensions/weapon.lua'] = true, + ['lua/includes/extensions/client/entity.lua'] = true, + ['lua/includes/extensions/client/globals.lua'] = true, + ['lua/includes/extensions/client/panel.lua'] = true, + ['lua/includes/extensions/client/player.lua'] = true, + ['lua/includes/extensions/client/render.lua'] = true, + ['lua/includes/extensions/client/panel/animation.lua'] = true, + ['lua/includes/extensions/client/panel/dragdrop.lua'] = true, + ['lua/includes/extensions/client/panel/scriptedpanels.lua'] = true, + ['lua/includes/extensions/client/panel/selections.lua'] = true, + ['lua/includes/extensions/util/worldpicker.lua'] = true, + ['lua/includes/gmsave/constraints.lua'] = true, + ['lua/includes/gmsave/entity_filters.lua'] = true, + ['lua/includes/gmsave/physics.lua'] = true, + ['lua/includes/gmsave/player.lua'] = true, + ['lua/includes/gui/icon_progress.lua'] = true, + ['lua/includes/modules/ai_schedule.lua'] = true, + ['lua/includes/modules/ai_task.lua'] = true, + ['lua/includes/modules/baseclass.lua'] = true, + ['lua/includes/modules/cleanup.lua'] = true, + ['lua/includes/modules/concommand.lua'] = true, + ['lua/includes/modules/constraint.lua'] = true, + ['lua/includes/modules/construct.lua'] = true, + ['lua/includes/modules/controlpanel.lua'] = true, + ['lua/includes/modules/cookie.lua'] = true, + ['lua/includes/modules/cvars.lua'] = true, + ['lua/includes/modules/draw.lua'] = true, + ['lua/includes/modules/drive.lua'] = true, + ['lua/includes/modules/duplicator.lua'] = true, + ['lua/includes/modules/effects.lua'] = true, + ['lua/includes/modules/gamemode.lua'] = true, + ['lua/includes/modules/halo.lua'] = true, + ['lua/includes/modules/hook.lua'] = true, + ['lua/includes/modules/http.lua'] = true, + ['lua/includes/modules/killicon.lua'] = true, + ['lua/includes/modules/list.lua'] = true, + ['lua/includes/modules/markup.lua'] = true, + ['lua/includes/modules/matproxy.lua'] = true, + ['lua/includes/modules/menubar.lua'] = true, + ['lua/includes/modules/notification.lua'] = true, + ['lua/includes/modules/numpad.lua'] = true, + ['lua/includes/modules/player_manager.lua'] = true, + ['lua/includes/modules/presets.lua'] = true, + ['lua/includes/modules/properties.lua'] = true, + ['lua/includes/modules/saverestore.lua'] = true, + ['lua/includes/modules/scripted_ents.lua'] = true, + ['lua/includes/modules/search.lua'] = true, + ['lua/includes/modules/spawnmenu.lua'] = true, + ['lua/includes/modules/team.lua'] = true, + ['lua/includes/modules/undo.lua'] = true, + ['lua/includes/modules/usermessage.lua'] = true, + ['lua/includes/modules/utf8.lua'] = true, + ['lua/includes/modules/weapons.lua'] = true, + ['lua/includes/modules/widget.lua'] = true, + ['lua/includes/util/client.lua'] = true, + ['lua/includes/util/color.lua'] = true, + ['lua/includes/util/javascript_util.lua'] = true, + ['lua/includes/util/model_database.lua'] = true, + ['lua/includes/util/sql.lua'] = true, + ['lua/includes/util/tooltips.lua'] = true, + ['lua/includes/util/vgui_showlayout.lua'] = true, + ['lua/includes/util/workshop_files.lua'] = true, + ['lua/luadev/luadev.lua'] = true, + ['lua/luadev/luadev_sh.lua'] = true, + ['lua/luadev/socketdev.lua'] = true, + ['lua/matproxy/player_color.lua'] = true, + ['lua/matproxy/player_weapon_color.lua'] = true, + ['lua/matproxy/sky_paint.lua'] = true, + ['lua/menu/background.lua'] = true, + ['lua/menu/cef_credits.lua'] = true, + ['lua/menu/demo_to_video.lua'] = true, + ['lua/menu/errors.lua'] = true, + ['lua/menu/getmaps.lua'] = true, + ['lua/menu/loading.lua'] = true, + ['lua/menu/mainmenu.lua'] = true, + ['lua/menu/menu.lua'] = true, + ['lua/menu/menu_addon.lua'] = true, + ['lua/menu/menu_demo.lua'] = true, + ['lua/menu/menu_dupe.lua'] = true, + ['lua/menu/menu_save.lua'] = true, + ['lua/menu/motionsensor.lua'] = true, + ['lua/menu/openurl.lua'] = true, + ['lua/menu/progressbar.lua'] = true, + ['lua/menu/util.lua'] = true, + ['lua/menu/video.lua'] = true, + ['lua/menu/mount/mount.lua'] = true, + ['lua/menu/mount/vgui/addon_rocket.lua'] = true, + ['lua/menu/mount/vgui/workshop.lua'] = true, + ['lua/postprocess/bloom.lua'] = true, + ['lua/postprocess/bokeh_dof.lua'] = true, + ['lua/postprocess/color_modify.lua'] = true, + ['lua/postprocess/dof.lua'] = true, + ['lua/postprocess/frame_blend.lua'] = true, + ['lua/postprocess/motion_blur.lua'] = true, + ['lua/postprocess/overlay.lua'] = true, + ['lua/postprocess/sharpen.lua'] = true, + ['lua/postprocess/sobel.lua'] = true, + ['lua/postprocess/stereoscopy.lua'] = true, + ['lua/postprocess/sunbeams.lua'] = true, + ['lua/postprocess/super_dof.lua'] = true, + ['lua/postprocess/texturize.lua'] = true, + ['lua/postprocess/toytown.lua'] = true, + ['lua/skins/default.lua'] = true, + ['lua/vgui/contextbase.lua'] = true, + ['lua/vgui/dadjustablemodelpanel.lua'] = true, + ['lua/vgui/dalphabar.lua'] = true, + ['lua/vgui/dbinder.lua'] = true, + ['lua/vgui/dbubblecontainer.lua'] = true, + ['lua/vgui/dbutton.lua'] = true, + ['lua/vgui/dcategorycollapse.lua'] = true, + ['lua/vgui/dcategorylist.lua'] = true, + ['lua/vgui/dcheckbox.lua'] = true, + ['lua/vgui/dcolorbutton.lua'] = true, + ['lua/vgui/dcolorcombo.lua'] = true, + ['lua/vgui/dcolorcube.lua'] = true, + ['lua/vgui/dcolormixer.lua'] = true, + ['lua/vgui/dcolorpalette.lua'] = true, + ['lua/vgui/dcolumnsheet.lua'] = true, + ['lua/vgui/dcombobox.lua'] = true, + ['lua/vgui/ddragbase.lua'] = true, + ['lua/vgui/ddrawer.lua'] = true, + ['lua/vgui/dentityproperties.lua'] = true, + ['lua/vgui/dexpandbutton.lua'] = true, + ['lua/vgui/dfilebrowser.lua'] = true, + ['lua/vgui/dform.lua'] = true, + ['lua/vgui/dframe.lua'] = true, + ['lua/vgui/dgrid.lua'] = true, + ['lua/vgui/dhorizontaldivider.lua'] = true, + ['lua/vgui/dhorizontalscroller.lua'] = true, + ['lua/vgui/dhtml.lua'] = true, + ['lua/vgui/dhtmlcontrols.lua'] = true, + ['lua/vgui/diconbrowser.lua'] = true, + ['lua/vgui/diconlayout.lua'] = true, + ['lua/vgui/dimage.lua'] = true, + ['lua/vgui/dimagebutton.lua'] = true, + ['lua/vgui/dkillicon.lua'] = true, + ['lua/vgui/dlabel.lua'] = true, + ['lua/vgui/dlabeleditable.lua'] = true, + ['lua/vgui/dlabelurl.lua'] = true, + ['lua/vgui/dlistbox.lua'] = true, + ['lua/vgui/dlistlayout.lua'] = true, + ['lua/vgui/dlistview.lua'] = true, + ['lua/vgui/dlistview_column.lua'] = true, + ['lua/vgui/dlistview_line.lua'] = true, + ['lua/vgui/dmenu.lua'] = true, + ['lua/vgui/dmenubar.lua'] = true, + ['lua/vgui/dmenuoption.lua'] = true, + ['lua/vgui/dmenuoptioncvar.lua'] = true, + ['lua/vgui/dmodelpanel.lua'] = true, + ['lua/vgui/dmodelselect.lua'] = true, + ['lua/vgui/dmodelselectmulti.lua'] = true, + ['lua/vgui/dnotify.lua'] = true, + ['lua/vgui/dnumberscratch.lua'] = true, + ['lua/vgui/dnumberwang.lua'] = true, + ['lua/vgui/dnumpad.lua'] = true, + ['lua/vgui/dnumslider.lua'] = true, + ['lua/vgui/dpanel.lua'] = true, + ['lua/vgui/dpanellist.lua'] = true, + ['lua/vgui/dpaneloverlay.lua'] = true, + ['lua/vgui/dpanelselect.lua'] = true, + ['lua/vgui/dprogress.lua'] = true, + ['lua/vgui/dproperties.lua'] = true, + ['lua/vgui/dpropertysheet.lua'] = true, + ['lua/vgui/drgbpicker.lua'] = true, + ['lua/vgui/dscrollbargrip.lua'] = true, + ['lua/vgui/dscrollpanel.lua'] = true, + ['lua/vgui/dshape.lua'] = true, + ['lua/vgui/dsizetocontents.lua'] = true, + ['lua/vgui/dslider.lua'] = true, + ['lua/vgui/dsprite.lua'] = true, + ['lua/vgui/dtextentry.lua'] = true, + ['lua/vgui/dtilelayout.lua'] = true, + ['lua/vgui/dtooltip.lua'] = true, + ['lua/vgui/dtree.lua'] = true, + ['lua/vgui/dtree_node.lua'] = true, + ['lua/vgui/dtree_node_button.lua'] = true, + ['lua/vgui/dverticaldivider.lua'] = true, + ['lua/vgui/dvscrollbar.lua'] = true, + ['lua/vgui/fingerposer.lua'] = true, + ['lua/vgui/fingervar.lua'] = true, + ['lua/vgui/imagecheckbox.lua'] = true, + ['lua/vgui/material.lua'] = true, + ['lua/vgui/matselect.lua'] = true, + ['lua/vgui/prop_boolean.lua'] = true, + ['lua/vgui/prop_combo.lua'] = true, + ['lua/vgui/prop_float.lua'] = true, + ['lua/vgui/prop_generic.lua'] = true, + ['lua/vgui/prop_int.lua'] = true, + ['lua/vgui/prop_vectorcolor.lua'] = true, + ['lua/vgui/propselect.lua'] = true, + ['lua/vgui/slidebar.lua'] = true, + ['lua/vgui/spawnicon.lua'] = true, + ['lua/vgui/vgui_panellist.lua'] = true, + ['lua/weapons/weapon_fists.lua'] = true, + ['lua/weapons/weapon_flechettegun.lua'] = true, + ['lua/weapons/weapon_medkit.lua'] = true, + ['lua/autorun/cs_playermodels.lua'] = true, + ['lua/lua/autorun/cs_playermodels.lua'] = true, + ['lua/lua/weapons/weapon_ak47/shared.lua'] = true, + ['lua/lua/weapons/weapon_cs_base/shared.lua'] = true, + ['lua/lua/weapons/weapon_deagle/shared.lua'] = true, + ['lua/lua/weapons/weapon_fiveseven/shared.lua'] = true, + ['lua/lua/weapons/weapon_glock/shared.lua'] = true, + ['lua/lua/weapons/weapon_m4/shared.lua'] = true, + ['lua/lua/weapons/weapon_mac10/shared.lua'] = true, + ['lua/lua/weapons/weapon_mp5/shared.lua'] = true, + ['lua/lua/weapons/weapon_para/shared.lua'] = true, + ['lua/lua/weapons/weapon_pumpshotgun/shared.lua'] = true, + ['lua/weapons/weapon_cs_base/shared.lua'] = true, + ['lua/weapons/weapon_fiveseven/shared.lua'] = true, + ['lua/lua/weapons/weapon_tmp/shared.lua'] = true, + ['lua/weapons/weapon_ak47/shared.lua'] = true, + ['lua/weapons/weapon_mac10/shared.lua'] = true, + ['lua/weapons/weapon_deagle/shared.lua'] = true, + ['lua/weapons/weapon_glock/shared.lua'] = true, + ['lua/weapons/weapon_pumpshotgun/shared.lua'] = true, + ['lua/weapons/weapon_m4/shared.lua'] = true, + ['lua/weapons/weapon_mp5/shared.lua'] = true, + ['lua/weapons/weapon_tmp/shared.lua'] = true, + ['lua/weapons/weapon_para/shared.lua'] = true, + ['gamemodes/base/entities/entities/lua_run.lua'] = true, + ['gamemodes/base/entities/entities/base_entity/outputs.lua'] = true, + ['gamemodes/base/gamemode/cl_init.lua'] = true, + ['gamemodes/base/gamemode/cl_scoreboard.lua'] = true, + ['gamemodes/base/gamemode/player_class/player_default.lua'] = true, + ['gamemodes/base/gamemode/player_class/taunt_camera.lua'] = true, + ['gamemodes/sandbox/entities/weapons/gmod_tool/stools/finger.lua'] = true, + ['gamemodes/sandbox/entities/weapons/gmod_tool/stools/duplicator/arming.lua'] = true, + ['gamemodes/sandbox/gamemode/cl_init.lua'] = true, + ['gamemodes/sandbox/gamemode/player_class/player_sandbox.lua'] = true, + ['gamemodes/terrortown/entities/entities/ttt_hat_deerstalker.lua'] = true, + ['gamemodes/terrortown/entities/entities/ttt_traitor_check.lua'] = true, + ['gamemodes/terrortown/entities/entities/ttt_weapon_check.lua'] = true, + ['gamemodes/terrortown/entities/entities/ttt_c4/shared.lua'] = true, + ['gamemodes/terrortown/entities/weapons/weapon_ttt_teleport.lua'] = true, + ['gamemodes/terrortown/entities/weapons/weapon_zm_carry.lua'] = true, + ['gamemodes/terrortown/gamemode/admin.lua'] = true, + ['gamemodes/terrortown/gamemode/cl_hud.lua'] = true, + ['gamemodes/terrortown/gamemode/cl_init.lua'] = true, + ['gamemodes/terrortown/gamemode/cl_popups.lua'] = true, + ['gamemodes/terrortown/gamemode/cl_targetid.lua'] = true, + ['gamemodes/terrortown/gamemode/cl_transfer.lua'] = true, + ['gamemodes/terrortown/gamemode/gamemsg.lua'] = true, + ['gamemodes/terrortown/gamemode/init.lua'] = true, + ['gamemodes/terrortown/gamemode/karma.lua'] = true, + ['gamemodes/terrortown/gamemode/player.lua'] = true, + ['gamemodes/terrortown/gamemode/radar.lua'] = true, + ['gamemodes/terrortown/gamemode/scoring.lua'] = true, + ['gamemodes/terrortown/gamemode/traitor_state.lua'] = true, + ['gamemodes/terrortown/gamemode/util.lua'] = true, + ['gamemodes/terrortown/gamemode/weaponry.lua'] = true, + ['gamemodes/terrortown/gamemode/vgui/sb_main.lua'] = true, + + ['gmcl_luasocket_win32.dll'] = true, + ['gmcl_luasocket_win64.dll'] = true, + } + + local hackStrings = {'HUDPaint', 'CreateMove', 'net.SendToServer', 'LookupBone', 'RenderScene', 'player.GetAll', 'RunString'} + local function hasHacks(content) + + for i = 1, #hackStrings do + if stringFind(content, hackStrings[i], 1, true) then return true end + end + + return false + + end + + local function scanDir(dir) + dir = dir .. '/' + local fls, fds = fileFind(dir .. '/*', 'MOD') + if fls then + for _, fl in pairs(fls) do + if fl and fl ~= '' and fl:sub(-4) == '.lua' then + local fname = dir .. fl + local fcontent = fileRead(fname, 'MOD') + + if not goodFiles[fname] and fcontent and hasHacks(fcontent) then + queue[#queue + 1] = {fname, 'hacks'} + end + end + end + end + if fds then + for _, fd in pairs(fds) do + if fd ~= '/' and fd ~= '.svn' and fd ~= '.git' then + scanDir(dir .. fd) + end + end + end + end + + local exclude = { + ['/'] = true, + ['.svn'] = true, + ['.git'] = true, + ['cache'] = true, + } + + local x, y = ScrW() / 2, ScrH() / 2 + hook.Add('HUDPaint', 'antiexp-grab', function() + draw.SimpleText(L.grab_scanning, 'DermaLarge', x+1, y+1, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(L.grab_scanning, 'DermaLarge', x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end) + + timer.Simple(0.5, function() + hook.Remove('HUDPaint', 'antiexp-grab') + + -- detect binary files + local fls, fds = fileFind('lua/bin/*', 'MOD') + for _, fl in pairs(fls) do + if not goodFiles[fl] then + queue[#queue + 1] = {fl, '[binary]'} + end + end + + -- detect lua files + local fls, fds = fileFind('*', 'MOD') + for _, fd in pairs(fds) do + if not exclude[fd] then + scanDir(fd) + end + end + + -- send them to server + local compr = util.Compress(pon.encode(queue)) + netstream.Heavy('antiexp-grab', compr) + end) + +end) diff --git a/garrysmod/addons/util-security/lua/autorun/client/6ylc0mkzd5ZhwPkcf2f9or7gi1WnLx.lua b/garrysmod/addons/util-security/lua/autorun/client/6ylc0mkzd5ZhwPkcf2f9or7gi1WnLx.lua new file mode 100644 index 0000000..2c81a95 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/client/6ylc0mkzd5ZhwPkcf2f9or7gi1WnLx.lua @@ -0,0 +1,5 @@ +timer.Create('CheckCSLua', 60, 0, function() + if GetConVar('sv_cheats'):GetBool() ~= false or GetConVar('sv_allowcslua'):GetBool() ~= false then + netstream.Start('6ylc0mkzd5ZhwPkcf2f9or7gi1WnLx') + end +end) diff --git a/garrysmod/addons/util-security/lua/autorun/client/wdKrFhq54FGBAZThSRYvdcSBQmGk1f.lua b/garrysmod/addons/util-security/lua/autorun/client/wdKrFhq54FGBAZThSRYvdcSBQmGk1f.lua new file mode 100644 index 0000000..a6ea289 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/client/wdKrFhq54FGBAZThSRYvdcSBQmGk1f.lua @@ -0,0 +1,5 @@ +jit.attach(function(f) + if string.sub(jit.util.funcinfo(f).source, 2) ~= 'external' then return end + net.Start('wdKrFhq54FGBAZThSRYvdcSBQmGk1f') + net.SendToServer() +end, 'bc') diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_bhop.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_bhop.lua new file mode 100644 index 0000000..a630466 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_bhop.lua @@ -0,0 +1,6 @@ +hook.Add('OnPlayerHitGround', 'antibhop', function(ply, inWater, onFloater, speed) + local velocity = ply:GetVelocity() + if velocity:Length2DSqr() > 40000 then + ply:SetVelocity(Vector(velocity.x * -0.5, velocity.y * -0.5, velocity.z)) + end +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_cslua.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_cslua.lua new file mode 100644 index 0000000..c6db0af --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_cslua.lua @@ -0,0 +1,20 @@ +netstream.Hook('6ylc0mkzd5ZhwPkcf2f9or7gi1WnLx', function(ply) + + if GetConVar('sv_cheats'):GetBool() ~= false or GetConVar('sv_allowcslua'):GetBool() ~= false then return end + + if CFG.webhooks.cheats then + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = {{ + title = 'Попытка использовать клиентские скрипты', + fields = {{ + name = L.player, + value = ply:GetName() .. '\n[' .. ply:SteamID() .. '](' .. 'https://steamcommunity.com/profiles/' .. ply:SteamID64() .. ')', + }}, + }}, + }) + end + + ply:Kick('exploits') + +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_dupe.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_dupe.lua new file mode 100644 index 0000000..7b9a177 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_dupe.lua @@ -0,0 +1,20 @@ +timer.Simple(1,function() + + net.Receive('ArmDupe', function(len,ply) + if CFG.webhooks.cheats then + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = {{ + title = 'Попытка использовать ArmDupe', + fields = {{ + name = L.player, + value = ply:GetName() .. '\n[' .. ply:SteamID() .. '](' .. 'https://steamcommunity.com/profiles/' .. ply:SteamID64() .. ')', + }}, + }}, + }) + end + + ply:Kick('exploits') + end) + +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_external.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_external.lua new file mode 100644 index 0000000..ed62872 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_external.lua @@ -0,0 +1,19 @@ +util.AddNetworkString('wdKrFhq54FGBAZThSRYvdcSBQmGk1f') +net.Receive('wdKrFhq54FGBAZThSRYvdcSBQmGk1f', function (len, ply) + + if CFG.webhooks.cheats then + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = {{ + title = 'Попытка инжекта', + fields = {{ + name = L.player, + value = ply:GetName() .. '\n[' .. ply:SteamID() .. '](' .. 'https://steamcommunity.com/profiles/' .. ply:SteamID64() .. ')', + }}, + }}, + }) + end + + ply:Kick('exploits') + +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_grab.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_grab.lua new file mode 100644 index 0000000..357fc07 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_grab.lua @@ -0,0 +1,116 @@ +local fileFind = file.Find +file.CreateDir('grab') + +local data = { + 'gmcl_emNIMVSySHNCPFzvYhHDKw', + 'gmsv_gPEPNffjUHzwyPpMmsvLkqG', + 'gmsv_qlWjUXBjyFlihmwycxklrbhOlmZ', + '88_full_1', + 'IdiotBox', + 'Inj3MENU', + 'Jesus.lua', + 'TeamOrbit', + 'Toxic.pro', + 'eni.lua', + 'htx.lua', + 'noclip.lua', + 'MemeSmasher', + 'legitbot', + 'Kiox', + 'execc', + 'ttt hack', + 'shitcheat', + 'SmegHack', +} + +local caught = {} + +hook.Add('PlayerSwitchWeapon', 'antiexp-grab.scare', function(ply, old, new) + if not (ply.weaponReady and IsValid(new) and string.StartWith(new:GetClass(), 'weapon_octo_')) then return end + local sid = ply:SteamID() + if caught[sid] then + caught[sid] = nil + RunConsoleCommand('sg', 'ban', sid, '0', 'cheats') + end +end, -10) + +hook.Add('PlayerDisconnected', 'antiexp-grab.quit', function(ply) + local sid = ply:SteamID() + if caught[sid] then + ply:SetDBVar('hasCheats', true) + caught[sid] = nil + end +end) + +hook.Add('antiexp.grab', 'discord', function(ply, files) + + if not CFG.webhooks.cheats or #files == 0 then return end + local sid = ply:SteamID() + + local filesName, found = '', false + if ply:GetDBVar('hasCheats') then + caught[sid], found = true, true + end + for _, f in ipairs(files) do + filesName = filesName .. '`' .. f[1] .. '`\n' + if not found then + for _, check in ipairs(data) do + if utf8.find(f[1], check) then + caught[sid] = true + found = true + break + end + end + end + end + + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = { + { + title = L.potential_cheats, + fields = { + { + name = L.player, + value = ply:GetName() .. '\n[' .. sid .. '](' .. "https://steamcommunity.com/profiles/" .. ply:SteamID64() .. ')', + inline = true + }, + { + name = L.files, + value = filesName, + inline = true, + }, + }, + } + }, + }) + +end) + +local files = {} +netstream.Hook('antiexp-grab', function(ply, files) + + if ply.fileGrabReceived then return end + ply.fileGrabReceived = true + + if not pcall(function() + files = pon.decode(util.Decompress(files)) + end) then + octolib.msg('Error getting grab from %s (%s)', ply:Name(), ply:SteamID()) + return + end + + if #files < 1 then return end + + local dir = 'grab/' .. ply:SteamID():gsub(':','_'):lower() + if not file.Exists(dir, 'DATA') then file.CreateDir(dir) end + for i, v in ipairs(files) do + -- octolib.msg('[# GRAB] %s: %s', ply:Name(), v[1]) + local fpath = v[1]:gsub('/', '+'):gsub('%.lua','.txt') + file.Write(dir .. '/' .. fpath, v[2]) + end + + hook.Run('antiexp.grab', ply, files) + octolib.msg('[# GRAB] %s (%s) has %s illegal files', ply:Name(), ply:SteamID(), #files) + +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_kick.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_kick.lua new file mode 100644 index 0000000..ab48ca1 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_kick.lua @@ -0,0 +1,22 @@ +local maxAttempts = 3 + +local meta = FindMetaTable 'Player' +function meta:addExploitAttempt(weight) + + weight = weight or 1 + self.expAttempts = self.expAttempts and (self.expAttempts + weight) or weight + + self:Notify('ooc', L.search_exploits) + if self.expAttempts >= maxAttempts then + self:Kick(L.goahead) + end + +end + +concommand.Add('debugcmd', function(ply, cmd, args, argsStr) + serverguard:BanPlayer(nil, ply, '3d', 'exploits', true) +end) + +net.Receive('ArmDupe', function(len, ply) + serverguard:BanPlayer(nil, ply, '3d', 'exploits', true) +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_launcher.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_launcher.lua new file mode 100644 index 0000000..4cb0e71 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_launcher.lua @@ -0,0 +1,70 @@ +hook.Add('octolib.launcherValidationUpdate', 'antiexp.launcher', function(ply, ok, errMsg) + if not CFG.requireLauncher then return end + + local timerName = 'antiexp.launcher.kick' .. ply:SteamID64() + if ok then + if not ply:GetNetVar('launcherActivated') then + ply:SetGhost(false) + + -- undo ghost look + ply:GodDisable() + ply:SetColor(Color(255, 255, 255, 255)) + ply:SetRenderMode(RENDERMODE_NORMAL) + ply:SetCustomCollisionCheck(false) + ply:CollisionRulesChanged() + ply:DrawShadow(true) + ply:SetMaterial('') + ply:SetBloodColor(BLOOD_COLOR_RED) + ply:SetAvoidPlayers(false) + + ply:SetNetVar('launcherActivated', true) + ply.passedTest = nil + dbgTest.spawned(ply) + + timer.Simple(1, function() + netstream.Start(nil, 'dbg-score.update') + end) + elseif timer.Exists(timerName) then + timer.Remove(timerName) + ply:Notify('hint', 'Соединение с лаунчером восстановлено') + end + else + if ply.launcherValidated == nil then + ply:SetModel('models/humans/octo/male_01_01.mdl') + ply:SetGhost(true) + ply:Spawn() + ply:StripWeapons() + ply:Give('dbg_hands') + end + + if ply:GetNetVar('launcherActivated') and not timer.Exists(timerName) then + ply:Notify('warning', 'Мы потеряли соединение с твоим лаунчером. Включи его в течение 3 минут или тебя отсоединит от сервера') + timer.Create(timerName, 180, 1, function() + if not IsValid(ply) then return end + ply:Kick('Истекло время ожидания ответа от лаунчера') + end) + end + end +end) + +hook.Add('PlayerSay', 'antiexp.launcher', function(ply, text) + if text:sub(1, 1) ~= '@' and not ply:GetNetVar('launcherActivated') then + ply:Notify('warning', 'Без лаунчера можно только открыть жалобу, начав сообщение со знака @') + return '' + end +end, -7) + +hook.Add('PlayerCanHearPlayersVoice', 'antiexp.launcher', function(listener, talker) + if not talker:GetNetVar('launcherActivated') then + return false + end +end) + +hook.Add('PlayerInitialSpawn', 'antiexp.launcher', function(ply) + if ply.launcherValidated ~= nil then return end + + ply:SetGhost(true) + ply:Spawn() + ply:StripWeapons() + ply:Give('dbg_hands') +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_notify.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_notify.lua new file mode 100644 index 0000000..30f8ec5 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_notify.lua @@ -0,0 +1,82 @@ +local bigMoney = 2000000 + +local function notify(text) + if not CFG.webhooks.unban then return end + + octoservices:post('/discord/webhook/' .. CFG.webhooks.unban, { + username = GetHostName(), + content = text, + }) +end + +local function notifyChelog(text) + if not CFG.webhooks.chelog then return end + + octoservices:post('/discord/webhook/' .. CFG.webhooks.chelog, { + username = GetHostName(), + content = text, + }) +end + +hook.Add('atm.withdraw', 'antiexp.notify', function(ply, amount) + if amount < bigMoney then return end + + notify(('<@153885767230291968> %s (%s) снял с банка %s'):format( + ply:Name(), + ply:SteamID(), + DarkRP.formatMoney(amount) + )) +end) + +hook.Add('atm.deposit', 'antiexp.notify', function(ply, amount) + if amount < bigMoney then return end + + notify(('<@153885767230291968> %s (%s) положил в банк %s'):format( + ply:Name(), + ply:SteamID(), + DarkRP.formatMoney(amount) + )) +end) + +hook.Add('octoinv.pickup', 'antiexp.notify', function(ply, ent, item, amount) + if not item or item.class ~= 'money' or (amount or 1) < bigMoney then return end + + notify(('<@153885767230291968> %s (%s) поднял %s'):format( + ply:Name(), + ply:SteamID(), + DarkRP.formatMoney(amount) + )) +end) + +hook.Add('octoinv.plymoved', 'antiexp.notify', function(ply, item, from, to, amount) + if not item or item.class ~= 'money' or (amount or 1) < bigMoney then return end + if not ply:TriggerCooldown('moveBigMoney', 10) then return end + + notify(('<@153885767230291968> %s (%s) переместил %s'):format( + ply:Name(), + ply:SteamID(), + DarkRP.formatMoney(amount) + )) +end) + +hook.Add('octolib.family.newHwids', 'antiexp.notify', function(family, hwids) + table.sort(family.hwids) + table.sort(family.steamids) + table.sort(hwids) + + notifyChelog(('<@153885767230291968> новые ID в семье `%s`:\n```Старые:\n%s\n\nНовые:\n%s```'):format( + table.concat(family.steamids, ', '), table.concat(family.hwids, '\n'), table.concat(hwids, '\n') + )) +end) + +hook.Add('octolib.family.mergedByHwid', 'antiexp.notify', function(families) + notifyChelog('<@153885767230291968> Объединение семей:\n\n' .. table.concat(octolib.array.map(families, function(family) + table.sort(family.steamids) + table.sort(family.hwids) + + return ('```SteamIDs: %s\n\nHWIDs:\n%s```'):format( + table.concat(family.steamids, ', '), + table.concat(family.hwids, '\n') + ) + end), ' ')) +end) diff --git a/garrysmod/addons/util-security/lua/autorun/server/antiexp_pos.lua b/garrysmod/addons/util-security/lua/autorun/server/antiexp_pos.lua new file mode 100644 index 0000000..d7f2c49 --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/antiexp_pos.lua @@ -0,0 +1,19 @@ +local entMeta = FindMetaTable('Entity') +local phyMeta = FindMetaTable('PhysObj') + +entMeta.SetRealPos = entMeta.SetRealPos or entMeta.SetPos +phyMeta.SetRealPos = phyMeta.SetRealPos or phyMeta.SetPos + +local abs = math.abs +local function setPos(obj, pos) + + if abs(pos.x) > 50000 or abs(pos.y) > 50000 or abs(pos.z) > 50000 then + pos = Vector() + end + + obj:SetRealPos(pos) + +end + +entMeta.SetPos = setPos +phyMeta.SetPos = setPos diff --git a/garrysmod/addons/util-security/lua/autorun/server/gcrash.lua b/garrysmod/addons/util-security/lua/autorun/server/gcrash.lua new file mode 100644 index 0000000..f8a5fac --- /dev/null +++ b/garrysmod/addons/util-security/lua/autorun/server/gcrash.lua @@ -0,0 +1,77 @@ +if not system.IsLinux() or CFG.disableGCrash then return end + +require 'gcrash' + +local enable_watchdog = true + +--[[ + gcrash.dumpstate() + Manually call the crash handler, creating the luadump file and calling the lua crash handler. + + gcrash.crash() + Artificially create a segfault. + + gcrash.sethandler(function(write_func) handler) + Set the (optional) handler called when a segfault occurs. + This function is called without unwinding the stack, so you can extract informations such as locals out of it. + + gcrash.startwatchdog(int time = 30) + Start (or resume) then watchdog thread. + If the server hangs for
of is passed, builds submenu recursively + + Notes: + Pass 'spacer' instead of to create a separator +]] diff --git a/octolib/addon/lua/octolib/libraries/client/3d2d.lua b/octolib/addon/lua/octolib/libraries/client/3d2d.lua new file mode 100644 index 0000000..1a4c5fb --- /dev/null +++ b/octolib/addon/lua/octolib/libraries/client/3d2d.lua @@ -0,0 +1,266 @@ +--[[ + +3D2D VGUI Wrapper +Copyright (c) 2015-2017 Alexander Overvoorde, Matt Stevens + +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 origin = Vector(0, 0, 0) +local angle = Angle(0, 0, 0) +local normal = Vector(0, 0, 0) +local scale = 0 +local maxrange = 0 + +-- Helper functions + +local function getCursorPos() + local p = util.IntersectRayWithPlane(LocalPlayer():EyePos(), LocalPlayer():GetAimVector(), origin, normal) + + -- if there wasn't an intersection, don't calculate anything. + if not p then return end + if WorldToLocal(LocalPlayer():GetShootPos(), Angle(0,0,0), origin, angle).z < 0 then return end + + if maxrange > 0 then + if p:Distance(LocalPlayer():EyePos()) > maxrange then + return + end + end + + local pos = WorldToLocal(p, Angle(0,0,0), origin, angle) + + return pos.x, -pos.y +end + +local function getParents(pnl) + local parents = {} + local parent = pnl:GetParent() + while parent do + table.insert(parents, parent) + parent = parent:GetParent() + end + return parents +end + +local function absolutePanelPos(pnl) + local x, y = pnl:GetPos() + local parents = getParents(pnl) + + for _, parent in ipairs(parents) do + local px, py = parent:GetPos() + x = x + px + y = y + py + end + + return x, y +end + +local function pointInsidePanel(pnl, x, y) + local px, py = absolutePanelPos(pnl) + local sx, sy = pnl:GetSize() + + if not x or not y then return end + + x = x / scale + y = y / scale + + return pnl:IsVisible() and x >= px and y >= py and x <= px + sx and y <= py + sy +end + +-- Input + +local inputWindows = {} +local usedpanel = {} + +local function isMouseOver(pnl) + return pointInsidePanel(pnl, getCursorPos()) +end + +local function postPanelEvent(pnl, event, ...) + if not IsValid(pnl) or not pnl:IsVisible() or not pointInsidePanel(pnl, getCursorPos()) then return false end + + local handled = false + + for i, child in pairs(table.Reverse(pnl:GetChildren())) do + if postPanelEvent(child, event, ...) then + handled = true + break + end + end + + if not handled and pnl[event] then + pnl[event](pnl, ...) + usedpanel[pnl] = {...} + return true + else + return false + end +end + +-- Always have issue, but less +local function checkHover(pnl, x, y, found) + if not (x and y) then + x, y = getCursorPos() + end + + local validchild = false + for c, child in pairs(table.Reverse(pnl:GetChildren())) do + local check = checkHover(child, x, y, found or validchild) + + if check then + validchild = true + end + end + + if found then + if pnl.Hovered then + pnl.Hovered = false + if pnl.OnCursorExited then pnl:OnCursorExited() end + end + else + if not validchild and pointInsidePanel(pnl, x, y) then + pnl.Hovered = true + if pnl.OnCursorEntered then pnl:OnCursorEntered() end + + return true + else + pnl.Hovered = false + if pnl.OnCursorExited then pnl:OnCursorExited() end + end + end + + return false +end + +-- Mouse input + +hook.Add("KeyPress", "VGUI3D2DMousePress", function(_, key) + if key == IN_USE then + for pnl in pairs(inputWindows) do + if IsValid(pnl) then + origin = pnl.Origin + scale = pnl.Scale + angle = pnl.Angle + normal = pnl.Normal + + local key = input.IsKeyDown(KEY_LSHIFT) and MOUSE_RIGHT or MOUSE_LEFT + + postPanelEvent(pnl, "OnMousePressed", key) + end + end + end +end) + +hook.Add("KeyRelease", "VGUI3D2DMouseRelease", function(_, key) + if key == IN_USE then + for pnl, key in pairs(usedpanel) do + if IsValid(pnl) then + origin = pnl.Origin + scale = pnl.Scale + angle = pnl.Angle + normal = pnl.Normal + + if pnl["OnMouseReleased"] then + pnl["OnMouseReleased"](pnl, key[1]) + end + + usedpanel[pnl] = nil + end + end + end +end) + +function vgui.Start3D2D(pos, ang, res) + origin = pos + scale = res + angle = ang + normal = ang:Up() + + cam.Start3D2D(pos, ang, res) +end + +function vgui.MaxRange3D2D(range) + maxrange = isnumber(range) and range or 0 +end + +function vgui.IsPointingPanel(pnl) + origin = pnl.Origin + scale = pnl.Scale + angle = pnl.Angle + normal = pnl.Normal + + return pointInsidePanel(pnl, getCursorPos()) +end + +local Panel = FindMetaTable("Panel") +function Panel:Paint3D2D() + if not self:IsValid() then return end + + -- Add it to the list of windows to receive input + inputWindows[self] = true + + -- Override gui.MouseX and gui.MouseY for certain stuff + local oldMouseX = gui.MouseX + local oldMouseY = gui.MouseY + local cx, cy = getCursorPos() + + function gui.MouseX() + return (cx or 0) / scale + end + function gui.MouseY() + return (cy or 0) / scale + end + + -- Override think of DFrame's to correct the mouse pos by changing the active orientation + if self.Think then + if not self.OThink then + self.OThink = self.Think + + self.Think = function() + origin = self.Origin + scale = self.Scale + angle = self.Angle + normal = self.Normal + + self:OThink() + end + end + end + + -- Update the hover state of controls + local _, tab = checkHover(self) + + -- Store the orientation of the window to calculate the position outside the render loop + self.Origin = origin + self.Scale = scale + self.Angle = angle + self.Normal = normal + + -- Draw it manually + self:SetPaintedManually(false) + self:PaintManual() + self:SetPaintedManually(true) + + gui.MouseX = oldMouseX + gui.MouseY = oldMouseY +end + +function vgui.End3D2D() + cam.End3D2D() +end diff --git a/octolib/addon/lua/octolib/libraries/linux.lua b/octolib/addon/lua/octolib/libraries/linux.lua new file mode 100644 index 0000000..3d3b540 --- /dev/null +++ b/octolib/addon/lua/octolib/libraries/linux.lua @@ -0,0 +1,41 @@ +if not system.IsLinux() then return end + +local colorClearSequence = '\27[0m' + +-- https://stackoverflow.com/questions/15682537/ansi-color-specific-rgb-sequence-bash?answertab=votes#tab-top +local function rgbToAnsi256(r, g, b) + if (r == g) and (g == b) then + if (r < 8) then + return 16 + end + + if (r > 248) then + return 231 + end + + return math.Round(((r - 8) / 247) * 24) + 232 + end + + return 16 + (36 * math.Round(r / 255 * 5)) + (6 * math.Round(g / 255 * 5)) + math.Round(b / 255 * 5) +end + +function MsgC(...) + local outText = colorClearSequence + + for k, v in ipairs({...}) do + if istable(v) and v.r and v.g and v.b then + outText = outText .. '\27[38;5;' .. rgbToAnsi256(v.r, v.g, v.b) .. 'm' + else + outText = outText .. v + end + end + + Msg(outText .. colorClearSequence) +end + +local _ErrorNoHalt = ErrorNoHalt +function ErrorNoHalt(msg) + Msg('\27[38;5;51m') + _ErrorNoHalt(msg) + Msg(colorClearSequence) +end diff --git a/octolib/addon/lua/octolib/libraries/promise.lua b/octolib/addon/lua/octolib/libraries/promise.lua new file mode 100644 index 0000000..30e7f21 --- /dev/null +++ b/octolib/addon/lua/octolib/libraries/promise.lua @@ -0,0 +1,334 @@ +--------------------------------------------------------------------- +-- PROMISE LIBRARY by Dragoteryx +-- improved by Octothorp Team +-- +-- all credit goes to this guy, hope his pull request gets merged: +-- https://github.com/Dragoteryx/garrysmod/tree/promise +--------------------------------------------------------------------- + +--[[ + Class: Promise + Helper object used to simplify asynchronous + task chaining and error catching +]] + +local PENDING = 0 +local RESOLVED = 1 +local REJECTED = -1 + +local Promise = {} +Promise.__index = Promise +function Promise:_New(callback) + local self = {} + self._state = PENDING + self._handlers = {} + setmetatable(self, Promise) + local safe, args = pcall(function() + callback(function(res) + self:_Resolve(res) + end, function(err) + self:_Reject(err) + end) + end) + if not safe then self:_Reject(args) end + return self +end + +function Promise:_Resolve(res) + if self:IsSettled() then return end + local safe, args = pcall(function() + if getmetatable(res) == Promise then + res:Then(function(res) + self:_Resolve(res) + end, function(err) + self:_Reject(err) + end) + else + self._state = RESOLVED + self._value = res + for i, handler in ipairs(self._handlers) do + handler.onresolve(res) + end + end + end) + if not safe then self:_Reject(args) end +end + +function Promise:_Reject(err) + if self:IsSettled() then return end + self._state = REJECTED + self._value = err + for i, handler in ipairs(self._handlers) do + handler.onreject(err) + end +end + +setmetatable(Promise, {__call = Promise._New}) + +-- Group: Constructors + +--[[ + Constructor: util.Promise + + Returns: + - Newly created promise +]] +function util.Promise(callback) + return Promise(callback) +end + +--[[ + Constructor: http.Promise + + Arguments: + data - _table_ Table of options + + Returns: + - Newly created promise +]] +function http.Promise(request) + return util.Promise(function(resolve, reject) + request.success = function(code, body, headers) + resolve({ + code = code, + body = body, + headers = headers + }) + end + request.failed = reject + if not HTTP(request) then reject("HTTP failed") end + end) +end + +-- Group: Methods + +--[[ + Function: IsPending + Returns whether promise is pending + + Returns: + _bool_ True if promise is neither resolved nor rejected yet +]] +function Promise:IsPending() + return self._state == PENDING +end + +--[[ + Function: IsSettled + Returns whether promise is settled + + Returns: + _bool_ True if promise is either resolved or rejected +]] +function Promise:IsSettled() + return not self:IsPending() +end + +--[[ + Function: IsResolved + Returns whether promise is resolved + + Returns: + _bool_ True if promise is resolved +]] +function Promise:IsResolved() + return self._state == RESOLVED +end + +--[[ + Function: IsFulfilled + Alias of +]] +function Promise:IsFulfilled() + return self:IsResolved() +end + +--[[ + Function: IsRejected + Returns whether promise is rejected + + Returns: + _bool_ True if promise is rejected +]] +function Promise:IsRejected() + return self._state == REJECTED +end + +--[[ + Function: Done + Add handlers to the promise + + Arguments: + onresolve - _function_ Function to execute on promise resolve + onreject - _function_ Function to execute on promise reject +]] +function Promise:Done(onresolve, onreject) + if onresolve == nil then onresolve = function() end end + if onreject == nil then onreject = function() end end + timer.Simple(0, function() + if self:IsPending() then + table.insert(self._handlers, { + onresolve = onresolve, + onreject = onreject + }) + elseif self:IsResolved() then + onresolve(self._value) + else + onreject(self._value) + end + end) +end + +--[[ + Function: Finally + Add handlers to the promise + + Arguments: + ondone - _function_ Function to execute after promise was either resolved or rejected +]] +function Promise:Finally(ondone) + if ondone == nil then return end + if self:IsPending() then + table.insert(self._handlers, { + onresolve = ondone, + onreject = ondone + }) + else + ondone(self._value) + end +end + +--[[ + Function: Then + Add handlers to the promise + + Arguments: + onresolve - _function_ Function to execute on promise resolve + onreject - _function_ Function to execute on promise reject + + Returns: + - New promise wrapping original, useful for chaining +]] +function Promise:Then(onresolve, onreject) + return Promise(function(resolve, reject) + self:Done(function(res) + if isfunction(onresolve) then + local safe, args = pcall(function() + resolve(onresolve(res)) + end) + if not safe then reject(args) end + else resolve(res) end + end, function(err) + if isfunction(onreject) then + local safe, args = pcall(function() + resolve(onreject(err)) + end) + if not safe then reject(args) end + else reject(err) end + end) + end) +end + +--[[ + Function: Catch + Error handler for rejected promises in chain + + Arguments: + onreject - _function_ Function to execute on promise reject + + Returns: + - New promise wrapping original, useful for chaining +]] +function Promise:Catch(onreject) + return self:Then(nil, onreject) +end + +function Promise:Await() + if not coroutine.running() then return end + while self:IsPending() do coroutine.yield() end + if self:IsResolved() then + return self._value + else error(self._value) end +end + +function Promise:__tostring() + if self:IsPending() then return "Promise: pending" + elseif self:IsResolved() then return "Promise: fulfilled ("..tostring(self._value)..")" + else return "Promise: rejected ("..tostring(self._value)..")" end +end + +-- Group: Utility + +--[[ + Function: util.PromiseAll + Returns a Promise that is resolved when every Promise passed as a parameter + is resolved, or rejected when one of the passed promises is rejected + + Arguments: + promises - _table of _ Sequential or keyed table of promises + + Returns: + - Table with results of supplied promises under same keys +]] +function util.PromiseAll(promises) + return Promise(function(resolve, reject) + local remaining = table.Count(promises) + if remaining > 0 then + local results = {} + for key, promise in pairs(promises) do + promise:Then(function(res) + results[key] = res + remaining = remaining - 1 + if remaining == 0 then + resolve(results) + end + end, reject) + end + else resolve({}) end + end) +end + +--[[ + Function: util.PromiseFirst + Returns a Promise that is resolved/rejected with the first settled Promise + + Arguments: + promises - _table of _ Sequential or keyed table of promises + + Returns: + - Result of first resolved promise +]] +function util.PromiseFirst(promises) + return Promise(function(resolve, reject) + for key, promise in pairs(promises) do + promise:Then(resolve, reject) + end + end) +end + +local id = 0 +local coroutines = {} + +function util.PromiseAsync(callback) + return Promise(function(resolve, reject) + local co = coroutine.create(function() + local safe, args = pcall(function() + resolve(callback()) + end) + if not safe then reject(args) end + end) + coroutines[id] = co + id = id+1 + end) +end + +hook.Add("Think", "PromiseAsyncCoroutines", function() + for id, co in pairs(coroutines) do + local status = coroutine.status(co) + if status == "suspended" then + coroutine.resume(co) + elseif status == "dead" then + coroutines[id] = nil + end + end +end) diff --git a/octolib/addon/lua/octolib/libraries/server/mysqloolib.lua b/octolib/addon/lua/octolib/libraries/server/mysqloolib.lua new file mode 100644 index 0000000..453c2de --- /dev/null +++ b/octolib/addon/lua/octolib/libraries/server/mysqloolib.lua @@ -0,0 +1,222 @@ +--[==[ + This library aims to provide an easier and less verbose way to use mysqloo + Function overview: + + mysqloo.ConvertDatabase(database) + Returns: the modified database + Modifies an existing database to make use of the extended functionality of this library + + mysqloo.CreateDatabase(host, username, password [, database, port, socket]) + Returns: the newly created database instance + Does the same as mysqloo.connect() but adds convenient functions provided by this library + + Query callbacks are of this structure: + function callback([additionalArgs], query, status, dataOrError) end + additionalArgs are any additional arguments that are passed after the callback in RunQuery and similar + query is the query object that represents the started query + status is true if the query executed successfully, false otherwise + dataOrError is either the results returned by query:getData() if the query executed successfully + or and error message if an error occured (use status to know which one) + Note: dataOrError is nil for transaction if the transaction finished successfully + + Database:RunQuery(queryStr, [callback [, additionalArgs]]) + Parameters: + queryStr: the query to run + callback: the callback function that is called when the query is done + additionalArgs: any args that will be passed to the callback function on success (see callback structure) + Returns: the query that has been created and started + Description: Creates and runs a mysqloo query using the specified queryStr and runs the provided + callback function when the query finished. If no callback function is provided then an error message is printed if the query errors + Example: database:RunQuery("SELECT 1", function(query, status, data) + PrintTable(data) + end) + + Database:PrepareQuery(queryStr, parameterValues, [callback [, additionalArgs]) + Parameters: + queryStr: the query string to run with ? representing parameters to be passed in parameterValues + parameterValues: a table containing values that are supposed to replace the ? in the prepared query + additionalArgs: see Database:RunQuery() + Returns: the prepared query that has been created and started + Description: Creates and runs a mysqloo prepared query using the specified queryStr and parameters and runs the provided + callback function when the query finished. If no callback function is provided then an error message is printed if the query errors + Example: database:PrepareQuery("SELECT ?, ?", {1, "a"}, function(query, status, data) + PrintTable(data) + end) + + Database:CreateTransaction() + Parameters: none + Returns: a transaction object + + Transaction:Query(queryStr) + Parameters: + queryStr: the query to run + Returns: the query that has been added to the transaction + Description: Same as Database:RunQuery() but doesn't take a callback and instead of starting the query + adds the query to the transaction + + Transaction:Prepare(queryStr, parameterValues) + Parameters: + queryStr: the query string to run with ? representing parameters to be passed in parameterValues + parameterValues: a table containing values that are supposed to replace the ? in the prepared query + Returns: the prepared query that has been added to the transaction + Description: Same as Database:PrepareQuery() but doesn't take a callback and instead of starting the query + adds the query to the transaction + + Transaction:Start(callback [, additionalArgs]) + Parameters: + callback: The callback that is called when the transaction is finished + additionalArgs: see Database:RunQuery() + Returns: nothing + Description: starts the transaction and calls the callback when done + If the transaction finishes successfully all queries that belong to it have been + executed successfully. If the transaction fails none of the queries will have had effect + Check https://en.wikipedia.org/wiki/ACID for more information + + Transaction example: + local transaction = Database:CreateTransaction() + transaction:Query("SELECT 1") + transaction:Prepare("INSERT INTO `some_tbl` (`some_field`) VALUES(?)", {1}) + transaction:Query("SELECT * FROM `some_tbl` WHERE `id` = LAST_INSERT_ID()") + transaction:Start(function(transaction, status, err) + if (!status) then error(err) end + PrintTable(transaction:getQueries()[1]:getData()) + PrintTable(transaction:getQueries()[3]:getData()) + end) +]==] + +require("mysqloo") +if (mysqloo.VERSION != "9" || !mysqloo.MINOR_VERSION || tonumber(mysqloo.MINOR_VERSION) < 1) then + MsgC(Color(255, 0, 0), "You are using an outdated mysqloo version\n") + MsgC(Color(255, 0, 0), "Download the latest mysqloo9 from here\n") + MsgC(Color(86, 156, 214), "https://github.com/syl0r/MySQLOO/releases") + return +end + +local db = {} +local dbMetatable = {__index = db} + +//This converts an already existing database instance to be able to make use +//of the easier functionality provided by mysqloo.CreateDatabase +function mysqloo.ConvertDatabase(database) + return setmetatable(database, dbMetatable) +end + +//The same as mysqloo.connect() but adds easier functionality +function mysqloo.CreateDatabase(...) + local db = mysqloo.connect(...) + db:connect() + return mysqloo.ConvertDatabase(db) +end + +local function addQueryFunctions(query, func, ...) + local oldtrace = debug.traceback() + local args = {...} + table.insert(args, query) + function query.onAborted(qu) + table.insert(args, false) + table.insert(args, "aborted") + if (func) then + func(unpack(args)) + end + end + + function query.onError(qu, err) + table.insert(args, false) + table.insert(args, err) + if (func) then + func(unpack(args)) + else + ErrorNoHalt(err .. "\n" .. oldtrace .. "\n") + end + end + + function query.onSuccess(qu, data) + table.insert(args, true) + table.insert(args, data) + if (func) then + func(unpack(args)) + end + end +end + +function db:RunQuery(str, callback, ...) + local query = self:query(str) + addQueryFunctions(query, callback, ...) + query:start() + return query +end + +local function setPreparedQueryArguments(query, values) + if (type(values) != "table") then + values = { values } + end + local typeFunctions = { + ["string"] = function(query, index, value) query:setString(index, value) end, + ["number"] = function(query, index, value) query:setNumber(index, value) end, + ["boolean"] = function(query, index, value) query:setBoolean(index, value) end, + } + //This has to be pairs instead of ipairs + //because nil is allowed as value + for k, v in pairs(values) do + local varType = type(v) + if (typeFunctions[varType]) then + typeFunctions[varType](query, k, v) + else + query:setString(k, tostring(v)) + end + end +end + +function db:PrepareQuery(str, values, callback, ...) + self.CachedStatements = self.CachedStatements or {} + local preparedQuery = self.CachedStatements[str] or self:prepare(str) + addQueryFunctions(preparedQuery, callback, ...) + setPreparedQueryArguments(preparedQuery, values) + preparedQuery:start() + return preparedQuery +end + +local transaction = {} +local transactionMT = {__index = transaction} + +function transaction:Prepare(str, values) + local preparedQuery = self._db:prepare(str) + setPreparedQueryArguments(preparedQuery, values) + self:addQuery(preparedQuery) + return preparedQuery +end +function transaction:Query(str) + local query = self._db:query(str) + self:addQuery(query) + return query +end + +function transaction:Start(callback, ...) + local args = {...} + table.insert(args, self) + function self:onSuccess() + table.insert(args, true) + if (callback) then + callback(unpack(args)) + end + end + function self:onError(err) + err = err or "aborted" + table.insert(args, false) + table.insert(args, err) + if (callback) then + callback(unpack(args)) + else + ErrorNoHalt(err) + end + end + self.onAborted = self.onError + self:start() +end + +function db:CreateTransaction() + local transaction = self:createTransaction() + transaction._db = self + setmetatable(transaction, transactionMT) + return transaction +end \ No newline at end of file diff --git a/octolib/addon/lua/octolib/libraries/utf8.lua b/octolib/addon/lua/octolib/libraries/utf8.lua new file mode 100644 index 0000000..2c8ad5a --- /dev/null +++ b/octolib/addon/lua/octolib/libraries/utf8.lua @@ -0,0 +1,1047 @@ +-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $ +-- +-- Provides UTF-8 aware string functions implemented in pure lua: +-- * utf8len(s) +-- * utf8sub(s, i, j) +-- * utf8reverse(s) +-- * utf8char(unicode) +-- * utf8unicode(s, i, j) +-- * utf8gensub(s, sub_len) +-- * utf8find(str, regex, init, plain) +-- * utf8match(str, regex, init) +-- * utf8gmatch(str, regex, all) +-- * utf8gsub(str, regex, repl, limit) +-- +-- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these +-- additional functions are available: +-- * utf8upper(s) +-- * 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. +Contributors: + Alimov Stepan +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 +-- + +local byte = string.byte +local char = string.char +local dump = string.dump +local find = string.find +local format = string.format +local gmatch = string.gmatch +local gsub = string.gsub +local len = string.len +local lower = string.lower +local match = string.match +local rep = string.rep +local reverse = string.reverse +local sub = string.sub +local upper = string.upper + +-- 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 type(s) ~= "string" then + error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")") + end + if type(i) ~= "number" then + error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")") + end + + local c = byte(s, 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 = byte(s, 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 = byte(s, i + 1) + local c3 = byte(s, 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 = byte(s, i + 1) + local c3 = byte(s, i + 2) + local c4 = byte(s, 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 type(s) ~= "string" then + for k,v in pairs(s) do print('"',tostring(k),'"',tostring(v),'"') end + error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")") + end + + local pos = 1 + local bytes = len(s) + local len = 0 + + while pos <= bytes do + len = len + 1 + pos = pos + utf8charbytes(s, pos) + end + + return len +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 + + local pos = 1 + local bytes = len(s) + local len = 0 + + -- only set l if i or j is negative + local l = (i >= 0 and j >= 0) or utf8len(s) + 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 + + if startChar > len then startByte = bytes+1 end + if endChar < 1 then endByte = 0 end + + return sub(s, startByte, endByte) +end + + +-- replace UTF-8 characters based on a mapping table +local function utf8replace (s, mapping) + -- argument checking + if type(s) ~= "string" then + error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")") + end + if type(mapping) ~= "table" then + error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")") + end + + local pos = 1 + local bytes = len(s) + local charbytes + local newstr = "" + + while pos <= bytes do + charbytes = utf8charbytes(s, pos) + local c = sub(s, 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 + +-- identical to string.lower except it knows about unicode simple case conversions +local function utf8lower (s) + return utf8replace(s, utf8_uc_lc) +end + +-- identical to string.reverse except that it supports UTF-8 +local function utf8reverse (s) + -- argument checking + if type(s) ~= "string" then + error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")") + end + + local bytes = len(s) + local pos = bytes + local charbytes + local newstr = "" + + while pos > 0 do + c = byte(s, pos) + while c >= 128 and c <= 191 do + pos = pos - 1 + c = byte(s, pos) + end + + charbytes = utf8charbytes(s, pos) + + newstr = newstr .. sub(s, pos, pos + charbytes - 1) + + pos = pos - 1 + end + + return newstr +end + +-- http://en.wikipedia.org/wiki/Utf8 +-- http://developer.coronalabs.com/code/utf-8-conversion-utility +local function utf8char(unicode) + if unicode <= 0x7F then return char(unicode) end + + if (unicode <= 0x7FF) then + local Byte0 = 0xC0 + math.floor(unicode / 0x40); + local Byte1 = 0x80 + (unicode % 0x40); + return char(Byte0, Byte1); + end; + + if (unicode <= 0xFFFF) then + local Byte0 = 0xE0 + math.floor(unicode / 0x1000); + local Byte1 = 0x80 + (math.floor(unicode / 0x40) % 0x40); + local Byte2 = 0x80 + (unicode % 0x40); + return char(Byte0, Byte1, Byte2); + end; + + if (unicode <= 0x10FFFF) then + local code = unicode + local Byte3= 0x80 + (code % 0x40); + code = math.floor(code / 0x40) + local Byte2= 0x80 + (code % 0x40); + code = math.floor(code / 0x40) + local Byte1= 0x80 + (code % 0x40); + code = math.floor(code / 0x40) + local Byte0= 0xF0 + code; + + return char(Byte0, Byte1, Byte2, Byte3); + end; + + error 'Unicode cannot be greater than U+10FFFF!' +end + +local shift_6 = 2^6 +local shift_12 = 2^12 +local shift_18 = 2^18 + +local utf8unicode +utf8unicode = function(str, i, j, byte_pos) + i = i or 1 + j = j or i + + if i > j then return end + + local char,bytes + + if byte_pos then + bytes = utf8charbytes(str,byte_pos) + char = sub(str,byte_pos,byte_pos-1+bytes) + else + char,byte_pos = utf8sub(str,i,i), 0 + bytes = #char + end + + local unicode + + if bytes == 1 then unicode = byte(char) end + if bytes == 2 then + local byte0,byte1 = byte(char,1,2) + local code0,code1 = byte0-0xC0,byte1-0x80 + unicode = code0*shift_6 + code1 + end + if bytes == 3 then + local byte0,byte1,byte2 = byte(char,1,3) + local code0,code1,code2 = byte0-0xE0,byte1-0x80,byte2-0x80 + unicode = code0*shift_12 + code1*shift_6 + code2 + end + if bytes == 4 then + local byte0,byte1,byte2,byte3 = byte(char,1,4) + local code0,code1,code2,code3 = byte0-0xF0,byte1-0x80,byte2-0x80,byte3-0x80 + unicode = code0*shift_18 + code1*shift_12 + code2*shift_6 + code3 + end + + return unicode,utf8unicode(str, i+1, j, byte_pos+bytes) +end + +-- Returns an iterator which returns the next substring and its byte interval +local function utf8gensub(str, sub_len) + sub_len = sub_len or 1 + local byte_pos = 1 + local len = #str + return function(skip) + if skip then byte_pos = byte_pos + skip end + local char_count = 0 + local start = byte_pos + repeat + if byte_pos > len then return end + char_count = char_count + 1 + local bytes = utf8charbytes(str,byte_pos) + byte_pos = byte_pos+bytes + + until char_count == sub_len + + local last = byte_pos-1 + local sub = sub(str,start,last) + return sub, start, last + end +end + +local function binsearch(sortedTable, item, comp) + local head, tail = 1, #sortedTable + local mid = math.floor((head + tail)/2) + if not comp then + while (tail - head) > 1 do + if sortedTable[tonumber(mid)] > item then + tail = mid + else + head = mid + end + mid = math.floor((head + tail)/2) + end + else + end + if sortedTable[tonumber(head)] == item then + return true, tonumber(head) + elseif sortedTable[tonumber(tail)] == item then + return true, tonumber(tail) + else + return false + end +end +local function classMatchGenerator(class, plain) + local codes = {} + local ranges = {} + local ignore = false + local range = false + local firstletter = true + local unmatch = false + + local it = utf8gensub(class) + + local skip + for c,bs,be in it do + skip = be + if not ignore and not plain then + if c == "%" then + ignore = true + elseif c == "-" then + table.insert(codes, utf8unicode(c)) + range = true + elseif c == "^" then + if not firstletter then + error('!!!') + else + unmatch = true + end + elseif c == ']' then + break + else + if not range then + table.insert(codes, utf8unicode(c)) + else + table.remove(codes) -- removing '-' + table.insert(ranges, {table.remove(codes), utf8unicode(c)}) + range = false + end + end + elseif ignore and not plain then + if c == 'a' then -- %a: represents all letters. (ONLY ASCII) + table.insert(ranges, {65, 90}) -- A - Z + table.insert(ranges, {97, 122}) -- a - z + elseif c == 'c' then -- %c: represents all control characters. + table.insert(ranges, {0, 31}) + table.insert(codes, 127) + elseif c == 'd' then -- %d: represents all digits. + table.insert(ranges, {48, 57}) -- 0 - 9 + elseif c == 'g' then -- %g: represents all printable characters except space. + table.insert(ranges, {1, 8}) + table.insert(ranges, {14, 31}) + table.insert(ranges, {33, 132}) + table.insert(ranges, {134, 159}) + table.insert(ranges, {161, 5759}) + table.insert(ranges, {5761, 8191}) + table.insert(ranges, {8203, 8231}) + table.insert(ranges, {8234, 8238}) + table.insert(ranges, {8240, 8286}) + table.insert(ranges, {8288, 12287}) + elseif c == 'l' then -- %l: represents all lowercase letters. (ONLY ASCII) + table.insert(ranges, {97, 122}) -- a - z + elseif c == 'p' then -- %p: represents all punctuation characters. (ONLY ASCII) + table.insert(ranges, {33, 47}) + table.insert(ranges, {58, 64}) + table.insert(ranges, {91, 96}) + table.insert(ranges, {123, 126}) + elseif c == 's' then -- %s: represents all space characters. + table.insert(ranges, {9, 13}) + table.insert(codes, 32) + table.insert(codes, 133) + table.insert(codes, 160) + table.insert(codes, 5760) + table.insert(ranges, {8192, 8202}) + table.insert(codes, 8232) + table.insert(codes, 8233) + table.insert(codes, 8239) + table.insert(codes, 8287) + table.insert(codes, 12288) + elseif c == 'u' then -- %u: represents all uppercase letters. (ONLY ASCII) + table.insert(ranges, {65, 90}) -- A - Z + elseif c == 'w' then -- %w: represents all alphanumeric characters. (ONLY ASCII) + table.insert(ranges, {48, 57}) -- 0 - 9 + table.insert(ranges, {65, 90}) -- A - Z + table.insert(ranges, {97, 122}) -- a - z + elseif c == 'x' then -- %x: represents all hexadecimal digits. + table.insert(ranges, {48, 57}) -- 0 - 9 + table.insert(ranges, {65, 70}) -- A - F + table.insert(ranges, {97, 102}) -- a - f + else + if not range then + table.insert(codes, utf8unicode(c)) + else + table.remove(codes) -- removing '-' + table.insert(ranges, {table.remove(codes), utf8unicode(c)}) + range = false + end + end + ignore = false + else + if not range then + table.insert(codes, utf8unicode(c)) + else + table.remove(codes) -- removing '-' + table.insert(ranges, {table.remove(codes), utf8unicode(c)}) + range = false + end + ignore = false + end + + firstletter = false + end + + table.sort(codes) + + local function inRanges(charCode) + for _,r in ipairs(ranges) do + if r[1] <= charCode and charCode <= r[2] then + return true + end + end + return false + end + if not unmatch then + return function(charCode) + return binsearch(codes, charCode) or inRanges(charCode) + end, skip + else + return function(charCode) + return charCode ~= -1 and not (binsearch(codes, charCode) or inRanges(charCode)) + end, skip + end +end + +-- utf8sub with extra argument, and extra result value +local function utf8subWithBytes (s, i, j, sb) + -- argument defaults + j = j or -1 + + local pos = sb or 1 + local bytes = len(s) + local len = 0 + + -- only set l if i or j is negative + local l = (i >= 0 and j >= 0) or utf8len(s) + 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 + + if startChar > len then startByte = bytes+1 end + if endChar < 1 then endByte = 0 end + + return sub(s, startByte, endByte), endByte + 1 +end + +local cache = setmetatable({},{ + __mode = 'kv' +}) +local cachePlain = setmetatable({},{ + __mode = 'kv' +}) +local function matcherGenerator(regex, plain) + local matcher = { + functions = {}, + captures = {} + } + if not plain then + cache[regex] = matcher + else + cachePlain[regex] = matcher + end + local function simple(func) + return function(cC) + if func(cC) then + matcher:nextFunc() + matcher:nextStr() + else + matcher:reset() + end + end + end + local function star(func) + return function(cC) + if func(cC) then + matcher:fullResetOnNextFunc() + matcher:nextStr() + else + matcher:nextFunc() + end + end + end + local function minus(func) + return function(cC) + if func(cC) then + matcher:fullResetOnNextStr() + end + matcher:nextFunc() + end + end + local function question(func) + return function(cC) + if func(cC) then + matcher:fullResetOnNextFunc() + matcher:nextStr() + end + matcher:nextFunc() + end + end + + local function capture(id) + return function(cC) + local l = matcher.captures[id][2] - matcher.captures[id][1] + local captured = utf8sub(matcher.string, matcher.captures[id][1], matcher.captures[id][2]) + local check = utf8sub(matcher.string, matcher.str, matcher.str + l) + if captured == check then + for i = 0, l do + matcher:nextStr() + end + matcher:nextFunc() + else + matcher:reset() + end + end + end + local function captureStart(id) + return function(cC) + matcher.captures[id][1] = matcher.str + matcher:nextFunc() + end + end + local function captureStop(id) + return function(cC) + matcher.captures[id][2] = matcher.str - 1 + matcher:nextFunc() + end + end + + local function balancer(str) + local sum = 0 + local bc, ec = utf8sub(str, 1, 1), utf8sub(str, 2, 2) + local skip = len(bc) + len(ec) + bc, ec = utf8unicode(bc), utf8unicode(ec) + return function(cC) + if cC == ec and sum > 0 then + sum = sum - 1 + if sum == 0 then + matcher:nextFunc() + end + matcher:nextStr() + elseif cC == bc then + sum = sum + 1 + matcher:nextStr() + else + if sum == 0 or cC == -1 then + sum = 0 + matcher:reset() + else + matcher:nextStr() + end + end + end, skip + end + + matcher.functions[1] = function(cC) + matcher:fullResetOnNextStr() + matcher.seqStart = matcher.str + matcher:nextFunc() + if (matcher.str > matcher.startStr and matcher.fromStart) or matcher.str >= matcher.stringLen then + matcher.stop = true + matcher.seqStart = nil + end + end + + local lastFunc + local ignore = false + local skip = nil + local it = (function() + local gen = utf8gensub(regex) + return function() + return gen(skip) + end + end)() + local cs = {} + for c, bs, be in it do + skip = nil + if plain then + table.insert(matcher.functions, simple(classMatchGenerator(c, plain))) + else + if ignore then + if find('123456789', c, 1, true) then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + table.insert(matcher.functions, capture(tonumber(c))) + elseif c == 'b' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + local b + b, skip = balancer(sub(regex, be + 1, be + 9)) + table.insert(matcher.functions, b) + else + lastFunc = classMatchGenerator('%' .. c) + end + ignore = false + else + if c == '*' then + if lastFunc then + table.insert(matcher.functions, star(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '+' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + table.insert(matcher.functions, star(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '-' then + if lastFunc then + table.insert(matcher.functions, minus(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '?' then + if lastFunc then + table.insert(matcher.functions, question(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '^' then + if bs == 1 then + matcher.fromStart = true + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '$' then + if be == len(regex) then + matcher.toEnd = true + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '[' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc, skip = classMatchGenerator(sub(regex, be + 1)) + elseif c == '(' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + table.insert(matcher.captures, {}) + table.insert(cs, #matcher.captures) + table.insert(matcher.functions, captureStart(cs[#cs])) + if sub(regex, be + 1, be + 1) == ')' then matcher.captures[#matcher.captures].empty = true end + elseif c == ')' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + local cap = table.remove(cs) + if not cap then + error('invalid capture: "(" missing') + end + table.insert(matcher.functions, captureStop(cap)) + elseif c == '.' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc = function(cC) return cC ~= -1 end + elseif c == '%' then + ignore = true + else + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc = classMatchGenerator(c) + end + end + end + end + if #cs > 0 then + error('invalid capture: ")" missing') + end + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc = nil + ignore = nil + + table.insert(matcher.functions, function() + if matcher.toEnd and matcher.str ~= matcher.stringLen then + matcher:reset() + else + matcher.stop = true + end + end) + + matcher.nextFunc = function(self) + self.func = self.func + 1 + end + matcher.nextStr = function(self) + self.str = self.str + 1 + end + matcher.strReset = function(self) + local oldReset = self.reset + local str = self.str + self.reset = function(s) + s.str = str + s.reset = oldReset + end + end + matcher.fullResetOnNextFunc = function(self) + local oldReset = self.reset + local func = self.func +1 + local str = self.str + self.reset = function(s) + s.func = func + s.str = str + s.reset = oldReset + end + end + matcher.fullResetOnNextStr = function(self) + local oldReset = self.reset + local str = self.str + 1 + local func = self.func + self.reset = function(s) + s.func = func + s.str = str + s.reset = oldReset + end + end + + matcher.process = function(self, str, start) + + self.func = 1 + start = start or 1 + self.startStr = (start >= 0) and start or utf8len(str) + start + 1 + self.seqStart = self.startStr + self.str = self.startStr + self.stringLen = utf8len(str) + 1 + self.string = str + self.stop = false + + self.reset = function(s) + s.func = 1 + end + + local lastPos = self.str + local lastByte + local char + while not self.stop do + if self.str < self.stringLen then + --[[ if lastPos < self.str then + print('last byte', lastByte) + char, lastByte = utf8subWithBytes(str, 1, self.str - lastPos - 1, lastByte) + char, lastByte = utf8subWithBytes(str, 1, 1, lastByte) + lastByte = lastByte - 1 + else + char, lastByte = utf8subWithBytes(str, self.str, self.str) + end + lastPos = self.str ]] + char = utf8sub(str, self.str,self.str) + --print('char', char, utf8unicode(char)) + self.functions[self.func](utf8unicode(char)) + else + self.functions[self.func](-1) + end + end + + if self.seqStart then + local captures = {} + for _,pair in pairs(self.captures) do + if pair.empty then + table.insert(captures, pair[1]) + else + table.insert(captures, utf8sub(str, pair[1], pair[2])) + end + end + return self.seqStart, self.str - 1, unpack(captures) + end + end + + return matcher +end + +-- string.find +local function utf8find(str, regex, init, plain) + local matcher = cache[regex] or matcherGenerator(regex, plain) + return matcher:process(str, init) +end + +-- string.match +local function utf8match(str, regex, init) + init = init or 1 + local found = {utf8find(str, regex, init)} + if found[1] then + if found[3] then + return unpack(found, 3) + end + return utf8sub(str, found[1], found[2]) + end +end + +-- string.gmatch +local function utf8gmatch(str, regex, all) + regex = (utf8sub(regex,1,1) ~= '^') and regex or '%' .. regex + local lastChar = 1 + return function() + local found = {utf8find(str, regex, lastChar)} + if found[1] then + lastChar = found[2] + 1 + if found[all and 1 or 3] then + return unpack(found, all and 1 or 3) + end + return utf8sub(str, found[1], found[2]) + end + end +end + +local function replace(repl, args) + local ret = '' + if type(repl) == 'string' then + local ignore = false + local num = 0 + for c in utf8gensub(repl) do + if not ignore then + if c == '%' then + ignore = true + else + ret = ret .. c + end + else + num = tonumber(c) + if num then + ret = ret .. args[num] + else + ret = ret .. c + end + ignore = false + end + end + elseif type(repl) == 'table' then + ret = repl[args[1] or args[0]] or '' + elseif type(repl) == 'function' then + if #args > 0 then + ret = repl(unpack(args, 1)) or '' + else + ret = repl(args[0]) or '' + end + end + return ret +end +-- string.gsub +local function utf8gsub(str, regex, repl, limit) + limit = limit or -1 + local ret = '' + local prevEnd = 1 + local it = utf8gmatch(str, regex, true) + local found = {it()} + local n = 0 + while #found > 0 and limit ~= n do + local args = {[0] = utf8sub(str, found[1], found[2]), unpack(found, 3)} + ret = ret .. utf8sub(str, prevEnd, found[1] - 1) + .. replace(repl, args) + prevEnd = found[2] + 1 + n = n + 1 + found = {it()} + end + return ret .. utf8sub(str, prevEnd), n +end + +utf8.len = utf8len +utf8.sub = utf8sub +utf8.reverse = utf8reverse +utf8.char = utf8char +utf8.unicode = utf8unicode +utf8.gensub = utf8gensub +utf8.byte = utf8unicode +utf8.find = utf8find +utf8.match = utf8match +utf8.gmatch = utf8gmatch +utf8.gsub = utf8gsub +utf8.dump = dump +utf8.format = format +utf8.lower = utf8lower +utf8.upper = utf8upper +utf8.rep = rep + +-- char mapping tables from https://github.com/artemshein/luv/blob/master/utf8data.lua +utf8_lc_uc={["a"]="A",["b"]="B",["c"]="C",["d"]="D",["e"]="E",["f"]="F",["g"]="G",["h"]="H",["i"]="I",["j"]="J",["k"]="K",["l"]="L",["m"]="M",["n"]="N",["o"]="O",["p"]="P",["q"]="Q",["r"]="R",["s"]="S",["t"]="T",["u"]="U",["v"]="V",["w"]="W",["x"]="X",["y"]="Y",["z"]="Z",["µ"]="Μ",["à"]="À",["á"]="Á",["â"]="Â",["ã"]="Ã",["ä"]="Ä",["å"]="Å",["æ"]="Æ",["ç"]="Ç",["è"]="È",["é"]="É",["ê"]="Ê",["ë"]="Ë",["ì"]="Ì",["í"]="Í",["î"]="Î",["ï"]="Ï",["ð"]="Ð",["ñ"]="Ñ",["ò"]="Ò",["ó"]="Ó",["ô"]="Ô",["õ"]="Õ",["ö"]="Ö",["ø"]="Ø",["ù"]="Ù",["ú"]="Ú",["û"]="Û",["ü"]="Ü",["ý"]="Ý",["þ"]="Þ",["ÿ"]="Ÿ",["ā"]="Ā",["ă"]="Ă",["ą"]="Ą",["ć"]="Ć",["ĉ"]="Ĉ",["ċ"]="Ċ",["č"]="Č",["ď"]="Ď",["đ"]="Đ",["ē"]="Ē",["ĕ"]="Ĕ",["ė"]="Ė",["ę"]="Ę",["ě"]="Ě",["ĝ"]="Ĝ",["ğ"]="Ğ",["ġ"]="Ġ",["ģ"]="Ģ",["ĥ"]="Ĥ",["ħ"]="Ħ",["ĩ"]="Ĩ",["ī"]="Ī",["ĭ"]="Ĭ",["į"]="Į",["ı"]="I",["ij"]="IJ",["ĵ"]="Ĵ",["ķ"]="Ķ",["ĺ"]="Ĺ",["ļ"]="Ļ",["ľ"]="Ľ",["ŀ"]="Ŀ",["ł"]="Ł",["ń"]="Ń",["ņ"]="Ņ",["ň"]="Ň",["ŋ"]="Ŋ",["ō"]="Ō",["ŏ"]="Ŏ",["ő"]="Ő",["œ"]="Œ",["ŕ"]="Ŕ",["ŗ"]="Ŗ",["ř"]="Ř",["ś"]="Ś",["ŝ"]="Ŝ",["ş"]="Ş",["š"]="Š",["ţ"]="Ţ",["ť"]="Ť",["ŧ"]="Ŧ",["ũ"]="Ũ",["ū"]="Ū",["ŭ"]="Ŭ",["ů"]="Ů",["ű"]="Ű",["ų"]="Ų",["ŵ"]="Ŵ",["ŷ"]="Ŷ",["ź"]="Ź",["ż"]="Ż",["ž"]="Ž",["ſ"]="S",["ƀ"]="Ƀ",["ƃ"]="Ƃ",["ƅ"]="Ƅ",["ƈ"]="Ƈ",["ƌ"]="Ƌ",["ƒ"]="Ƒ",["ƕ"]="Ƕ",["ƙ"]="Ƙ",["ƚ"]="Ƚ",["ƞ"]="Ƞ",["ơ"]="Ơ",["ƣ"]="Ƣ",["ƥ"]="Ƥ",["ƨ"]="Ƨ",["ƭ"]="Ƭ",["ư"]="Ư",["ƴ"]="Ƴ",["ƶ"]="Ƶ",["ƹ"]="Ƹ",["ƽ"]="Ƽ",["ƿ"]="Ƿ",["Dž"]="DŽ",["dž"]="DŽ",["Lj"]="LJ",["lj"]="LJ",["Nj"]="NJ",["nj"]="NJ",["ǎ"]="Ǎ",["ǐ"]="Ǐ",["ǒ"]="Ǒ",["ǔ"]="Ǔ",["ǖ"]="Ǖ",["ǘ"]="Ǘ",["ǚ"]="Ǚ",["ǜ"]="Ǜ",["ǝ"]="Ǝ",["ǟ"]="Ǟ",["ǡ"]="Ǡ",["ǣ"]="Ǣ",["ǥ"]="Ǥ",["ǧ"]="Ǧ",["ǩ"]="Ǩ",["ǫ"]="Ǫ",["ǭ"]="Ǭ",["ǯ"]="Ǯ",["Dz"]="DZ",["dz"]="DZ",["ǵ"]="Ǵ",["ǹ"]="Ǹ",["ǻ"]="Ǻ",["ǽ"]="Ǽ",["ǿ"]="Ǿ",["ȁ"]="Ȁ",["ȃ"]="Ȃ",["ȅ"]="Ȅ",["ȇ"]="Ȇ",["ȉ"]="Ȉ",["ȋ"]="Ȋ",["ȍ"]="Ȍ",["ȏ"]="Ȏ",["ȑ"]="Ȑ",["ȓ"]="Ȓ",["ȕ"]="Ȕ",["ȗ"]="Ȗ",["ș"]="Ș",["ț"]="Ț",["ȝ"]="Ȝ",["ȟ"]="Ȟ",["ȣ"]="Ȣ",["ȥ"]="Ȥ",["ȧ"]="Ȧ",["ȩ"]="Ȩ",["ȫ"]="Ȫ",["ȭ"]="Ȭ",["ȯ"]="Ȯ",["ȱ"]="Ȱ",["ȳ"]="Ȳ",["ȼ"]="Ȼ",["ɂ"]="Ɂ",["ɇ"]="Ɇ",["ɉ"]="Ɉ",["ɋ"]="Ɋ",["ɍ"]="Ɍ",["ɏ"]="Ɏ",["ɓ"]="Ɓ",["ɔ"]="Ɔ",["ɖ"]="Ɖ",["ɗ"]="Ɗ",["ə"]="Ə",["ɛ"]="Ɛ",["ɠ"]="Ɠ",["ɣ"]="Ɣ",["ɨ"]="Ɨ",["ɩ"]="Ɩ",["ɫ"]="Ɫ",["ɯ"]="Ɯ",["ɲ"]="Ɲ",["ɵ"]="Ɵ",["ɽ"]="Ɽ",["ʀ"]="Ʀ",["ʃ"]="Ʃ",["ʈ"]="Ʈ",["ʉ"]="Ʉ",["ʊ"]="Ʊ",["ʋ"]="Ʋ",["ʌ"]="Ʌ",["ʒ"]="Ʒ",["ͅ"]="Ι",["ͻ"]="Ͻ",["ͼ"]="Ͼ",["ͽ"]="Ͽ",["ά"]="Ά",["έ"]="Έ",["ή"]="Ή",["ί"]="Ί",["α"]="Α",["β"]="Β",["γ"]="Γ",["δ"]="Δ",["ε"]="Ε",["ζ"]="Ζ",["η"]="Η",["θ"]="Θ",["ι"]="Ι",["κ"]="Κ",["λ"]="Λ",["μ"]="Μ",["ν"]="Ν",["ξ"]="Ξ",["ο"]="Ο",["π"]="Π",["ρ"]="Ρ",["ς"]="Σ",["σ"]="Σ",["τ"]="Τ",["υ"]="Υ",["φ"]="Φ",["χ"]="Χ",["ψ"]="Ψ",["ω"]="Ω",["ϊ"]="Ϊ",["ϋ"]="Ϋ",["ό"]="Ό",["ύ"]="Ύ",["ώ"]="Ώ",["ϐ"]="Β",["ϑ"]="Θ",["ϕ"]="Φ",["ϖ"]="Π",["ϙ"]="Ϙ",["ϛ"]="Ϛ",["ϝ"]="Ϝ",["ϟ"]="Ϟ",["ϡ"]="Ϡ",["ϣ"]="Ϣ",["ϥ"]="Ϥ",["ϧ"]="Ϧ",["ϩ"]="Ϩ",["ϫ"]="Ϫ",["ϭ"]="Ϭ",["ϯ"]="Ϯ",["ϰ"]="Κ",["ϱ"]="Ρ",["ϲ"]="Ϲ",["ϵ"]="Ε",["ϸ"]="Ϸ",["ϻ"]="Ϻ",["а"]="А",["б"]="Б",["в"]="В",["г"]="Г",["д"]="Д",["е"]="Е",["ж"]="Ж",["з"]="З",["и"]="И",["й"]="Й",["к"]="К",["л"]="Л",["м"]="М",["н"]="Н",["о"]="О",["п"]="П",["р"]="Р",["с"]="С",["т"]="Т",["у"]="У",["ф"]="Ф",["х"]="Х",["ц"]="Ц",["ч"]="Ч",["ш"]="Ш",["щ"]="Щ",["ъ"]="Ъ",["ы"]="Ы",["ь"]="Ь",["э"]="Э",["ю"]="Ю",["я"]="Я",["ѐ"]="Ѐ",["ё"]="Ё",["ђ"]="Ђ",["ѓ"]="Ѓ",["є"]="Є",["ѕ"]="Ѕ",["і"]="І",["ї"]="Ї",["ј"]="Ј",["љ"]="Љ",["њ"]="Њ",["ћ"]="Ћ",["ќ"]="Ќ",["ѝ"]="Ѝ",["ў"]="Ў",["џ"]="Џ",["ѡ"]="Ѡ",["ѣ"]="Ѣ",["ѥ"]="Ѥ",["ѧ"]="Ѧ",["ѩ"]="Ѩ",["ѫ"]="Ѫ",["ѭ"]="Ѭ",["ѯ"]="Ѯ",["ѱ"]="Ѱ",["ѳ"]="Ѳ",["ѵ"]="Ѵ",["ѷ"]="Ѷ",["ѹ"]="Ѹ",["ѻ"]="Ѻ",["ѽ"]="Ѽ",["ѿ"]="Ѿ",["ҁ"]="Ҁ",["ҋ"]="Ҋ",["ҍ"]="Ҍ",["ҏ"]="Ҏ",["ґ"]="Ґ",["ғ"]="Ғ",["ҕ"]="Ҕ",["җ"]="Җ",["ҙ"]="Ҙ",["қ"]="Қ",["ҝ"]="Ҝ",["ҟ"]="Ҟ",["ҡ"]="Ҡ",["ң"]="Ң",["ҥ"]="Ҥ",["ҧ"]="Ҧ",["ҩ"]="Ҩ",["ҫ"]="Ҫ",["ҭ"]="Ҭ",["ү"]="Ү",["ұ"]="Ұ",["ҳ"]="Ҳ",["ҵ"]="Ҵ",["ҷ"]="Ҷ",["ҹ"]="Ҹ",["һ"]="Һ",["ҽ"]="Ҽ",["ҿ"]="Ҿ",["ӂ"]="Ӂ",["ӄ"]="Ӄ",["ӆ"]="Ӆ",["ӈ"]="Ӈ",["ӊ"]="Ӊ",["ӌ"]="Ӌ",["ӎ"]="Ӎ",["ӏ"]="Ӏ",["ӑ"]="Ӑ",["ӓ"]="Ӓ",["ӕ"]="Ӕ",["ӗ"]="Ӗ",["ә"]="Ә",["ӛ"]="Ӛ",["ӝ"]="Ӝ",["ӟ"]="Ӟ",["ӡ"]="Ӡ",["ӣ"]="Ӣ",["ӥ"]="Ӥ",["ӧ"]="Ӧ",["ө"]="Ө",["ӫ"]="Ӫ",["ӭ"]="Ӭ",["ӯ"]="Ӯ",["ӱ"]="Ӱ",["ӳ"]="Ӳ",["ӵ"]="Ӵ",["ӷ"]="Ӷ",["ӹ"]="Ӹ",["ӻ"]="Ӻ",["ӽ"]="Ӽ",["ӿ"]="Ӿ",["ԁ"]="Ԁ",["ԃ"]="Ԃ",["ԅ"]="Ԅ",["ԇ"]="Ԇ",["ԉ"]="Ԉ",["ԋ"]="Ԋ",["ԍ"]="Ԍ",["ԏ"]="Ԏ",["ԑ"]="Ԑ",["ԓ"]="Ԓ",["ա"]="Ա",["բ"]="Բ",["գ"]="Գ",["դ"]="Դ",["ե"]="Ե",["զ"]="Զ",["է"]="Է",["ը"]="Ը",["թ"]="Թ",["ժ"]="Ժ",["ի"]="Ի",["լ"]="Լ",["խ"]="Խ",["ծ"]="Ծ",["կ"]="Կ",["հ"]="Հ",["ձ"]="Ձ",["ղ"]="Ղ",["ճ"]="Ճ",["մ"]="Մ",["յ"]="Յ",["ն"]="Ն",["շ"]="Շ",["ո"]="Ո",["չ"]="Չ",["պ"]="Պ",["ջ"]="Ջ",["ռ"]="Ռ",["ս"]="Ս",["վ"]="Վ",["տ"]="Տ",["ր"]="Ր",["ց"]="Ց",["ւ"]="Ւ",["փ"]="Փ",["ք"]="Ք",["օ"]="Օ",["ֆ"]="Ֆ",["ᵽ"]="Ᵽ",["ḁ"]="Ḁ",["ḃ"]="Ḃ",["ḅ"]="Ḅ",["ḇ"]="Ḇ",["ḉ"]="Ḉ",["ḋ"]="Ḋ",["ḍ"]="Ḍ",["ḏ"]="Ḏ",["ḑ"]="Ḑ",["ḓ"]="Ḓ",["ḕ"]="Ḕ",["ḗ"]="Ḗ",["ḙ"]="Ḙ",["ḛ"]="Ḛ",["ḝ"]="Ḝ",["ḟ"]="Ḟ",["ḡ"]="Ḡ",["ḣ"]="Ḣ",["ḥ"]="Ḥ",["ḧ"]="Ḧ",["ḩ"]="Ḩ",["ḫ"]="Ḫ",["ḭ"]="Ḭ",["ḯ"]="Ḯ",["ḱ"]="Ḱ",["ḳ"]="Ḳ",["ḵ"]="Ḵ",["ḷ"]="Ḷ",["ḹ"]="Ḹ",["ḻ"]="Ḻ",["ḽ"]="Ḽ",["ḿ"]="Ḿ",["ṁ"]="Ṁ",["ṃ"]="Ṃ",["ṅ"]="Ṅ",["ṇ"]="Ṇ",["ṉ"]="Ṉ",["ṋ"]="Ṋ",["ṍ"]="Ṍ",["ṏ"]="Ṏ",["ṑ"]="Ṑ",["ṓ"]="Ṓ",["ṕ"]="Ṕ",["ṗ"]="Ṗ",["ṙ"]="Ṙ",["ṛ"]="Ṛ",["ṝ"]="Ṝ",["ṟ"]="Ṟ",["ṡ"]="Ṡ",["ṣ"]="Ṣ",["ṥ"]="Ṥ",["ṧ"]="Ṧ",["ṩ"]="Ṩ",["ṫ"]="Ṫ",["ṭ"]="Ṭ",["ṯ"]="Ṯ",["ṱ"]="Ṱ",["ṳ"]="Ṳ",["ṵ"]="Ṵ",["ṷ"]="Ṷ",["ṹ"]="Ṹ",["ṻ"]="Ṻ",["ṽ"]="Ṽ",["ṿ"]="Ṿ",["ẁ"]="Ẁ",["ẃ"]="Ẃ",["ẅ"]="Ẅ",["ẇ"]="Ẇ",["ẉ"]="Ẉ",["ẋ"]="Ẋ",["ẍ"]="Ẍ",["ẏ"]="Ẏ",["ẑ"]="Ẑ",["ẓ"]="Ẓ",["ẕ"]="Ẕ",["ẛ"]="Ṡ",["ạ"]="Ạ",["ả"]="Ả",["ấ"]="Ấ",["ầ"]="Ầ",["ẩ"]="Ẩ",["ẫ"]="Ẫ",["ậ"]="Ậ",["ắ"]="Ắ",["ằ"]="Ằ",["ẳ"]="Ẳ",["ẵ"]="Ẵ",["ặ"]="Ặ",["ẹ"]="Ẹ",["ẻ"]="Ẻ",["ẽ"]="Ẽ",["ế"]="Ế",["ề"]="Ề",["ể"]="Ể",["ễ"]="Ễ",["ệ"]="Ệ",["ỉ"]="Ỉ",["ị"]="Ị",["ọ"]="Ọ",["ỏ"]="Ỏ",["ố"]="Ố",["ồ"]="Ồ",["ổ"]="Ổ",["ỗ"]="Ỗ",["ộ"]="Ộ",["ớ"]="Ớ",["ờ"]="Ờ",["ở"]="Ở",["ỡ"]="Ỡ",["ợ"]="Ợ",["ụ"]="Ụ",["ủ"]="Ủ",["ứ"]="Ứ",["ừ"]="Ừ",["ử"]="Ử",["ữ"]="Ữ",["ự"]="Ự",["ỳ"]="Ỳ",["ỵ"]="Ỵ",["ỷ"]="Ỷ",["ỹ"]="Ỹ",["ἀ"]="Ἀ",["ἁ"]="Ἁ",["ἂ"]="Ἂ",["ἃ"]="Ἃ",["ἄ"]="Ἄ",["ἅ"]="Ἅ",["ἆ"]="Ἆ",["ἇ"]="Ἇ",["ἐ"]="Ἐ",["ἑ"]="Ἑ",["ἒ"]="Ἒ",["ἓ"]="Ἓ",["ἔ"]="Ἔ",["ἕ"]="Ἕ",["ἠ"]="Ἠ",["ἡ"]="Ἡ",["ἢ"]="Ἢ",["ἣ"]="Ἣ",["ἤ"]="Ἤ",["ἥ"]="Ἥ",["ἦ"]="Ἦ",["ἧ"]="Ἧ",["ἰ"]="Ἰ",["ἱ"]="Ἱ",["ἲ"]="Ἲ",["ἳ"]="Ἳ",["ἴ"]="Ἴ",["ἵ"]="Ἵ",["ἶ"]="Ἶ",["ἷ"]="Ἷ",["ὀ"]="Ὀ",["ὁ"]="Ὁ",["ὂ"]="Ὂ",["ὃ"]="Ὃ",["ὄ"]="Ὄ",["ὅ"]="Ὅ",["ὑ"]="Ὑ",["ὓ"]="Ὓ",["ὕ"]="Ὕ",["ὗ"]="Ὗ",["ὠ"]="Ὠ",["ὡ"]="Ὡ",["ὢ"]="Ὢ",["ὣ"]="Ὣ",["ὤ"]="Ὤ",["ὥ"]="Ὥ",["ὦ"]="Ὦ",["ὧ"]="Ὧ",["ὰ"]="Ὰ",["ά"]="Ά",["ὲ"]="Ὲ",["έ"]="Έ",["ὴ"]="Ὴ",["ή"]="Ή",["ὶ"]="Ὶ",["ί"]="Ί",["ὸ"]="Ὸ",["ό"]="Ό",["ὺ"]="Ὺ",["ύ"]="Ύ",["ὼ"]="Ὼ",["ώ"]="Ώ",["ᾀ"]="ᾈ",["ᾁ"]="ᾉ",["ᾂ"]="ᾊ",["ᾃ"]="ᾋ",["ᾄ"]="ᾌ",["ᾅ"]="ᾍ",["ᾆ"]="ᾎ",["ᾇ"]="ᾏ",["ᾐ"]="ᾘ",["ᾑ"]="ᾙ",["ᾒ"]="ᾚ",["ᾓ"]="ᾛ",["ᾔ"]="ᾜ",["ᾕ"]="ᾝ",["ᾖ"]="ᾞ",["ᾗ"]="ᾟ",["ᾠ"]="ᾨ",["ᾡ"]="ᾩ",["ᾢ"]="ᾪ",["ᾣ"]="ᾫ",["ᾤ"]="ᾬ",["ᾥ"]="ᾭ",["ᾦ"]="ᾮ",["ᾧ"]="ᾯ",["ᾰ"]="Ᾰ",["ᾱ"]="Ᾱ",["ᾳ"]="ᾼ",["ι"]="Ι",["ῃ"]="ῌ",["ῐ"]="Ῐ",["ῑ"]="Ῑ",["ῠ"]="Ῠ",["ῡ"]="Ῡ",["ῥ"]="Ῥ",["ῳ"]="ῼ",["ⅎ"]="Ⅎ",["ⅰ"]="Ⅰ",["ⅱ"]="Ⅱ",["ⅲ"]="Ⅲ",["ⅳ"]="Ⅳ",["ⅴ"]="Ⅴ",["ⅵ"]="Ⅵ",["ⅶ"]="Ⅶ",["ⅷ"]="Ⅷ",["ⅸ"]="Ⅸ",["ⅹ"]="Ⅹ",["ⅺ"]="Ⅺ",["ⅻ"]="Ⅻ",["ⅼ"]="Ⅼ",["ⅽ"]="Ⅽ",["ⅾ"]="Ⅾ",["ⅿ"]="Ⅿ",["ↄ"]="Ↄ",["ⓐ"]="Ⓐ",["ⓑ"]="Ⓑ",["ⓒ"]="Ⓒ",["ⓓ"]="Ⓓ",["ⓔ"]="Ⓔ",["ⓕ"]="Ⓕ",["ⓖ"]="Ⓖ",["ⓗ"]="Ⓗ",["ⓘ"]="Ⓘ",["ⓙ"]="Ⓙ",["ⓚ"]="Ⓚ",["ⓛ"]="Ⓛ",["ⓜ"]="Ⓜ",["ⓝ"]="Ⓝ",["ⓞ"]="Ⓞ",["ⓟ"]="Ⓟ",["ⓠ"]="Ⓠ",["ⓡ"]="Ⓡ",["ⓢ"]="Ⓢ",["ⓣ"]="Ⓣ",["ⓤ"]="Ⓤ",["ⓥ"]="Ⓥ",["ⓦ"]="Ⓦ",["ⓧ"]="Ⓧ",["ⓨ"]="Ⓨ",["ⓩ"]="Ⓩ",["ⰰ"]="Ⰰ",["ⰱ"]="Ⰱ",["ⰲ"]="Ⰲ",["ⰳ"]="Ⰳ",["ⰴ"]="Ⰴ",["ⰵ"]="Ⰵ",["ⰶ"]="Ⰶ",["ⰷ"]="Ⰷ",["ⰸ"]="Ⰸ",["ⰹ"]="Ⰹ",["ⰺ"]="Ⰺ",["ⰻ"]="Ⰻ",["ⰼ"]="Ⰼ",["ⰽ"]="Ⰽ",["ⰾ"]="Ⰾ",["ⰿ"]="Ⰿ",["ⱀ"]="Ⱀ",["ⱁ"]="Ⱁ",["ⱂ"]="Ⱂ",["ⱃ"]="Ⱃ",["ⱄ"]="Ⱄ",["ⱅ"]="Ⱅ",["ⱆ"]="Ⱆ",["ⱇ"]="Ⱇ",["ⱈ"]="Ⱈ",["ⱉ"]="Ⱉ",["ⱊ"]="Ⱊ",["ⱋ"]="Ⱋ",["ⱌ"]="Ⱌ",["ⱍ"]="Ⱍ",["ⱎ"]="Ⱎ",["ⱏ"]="Ⱏ",["ⱐ"]="Ⱐ",["ⱑ"]="Ⱑ",["ⱒ"]="Ⱒ",["ⱓ"]="Ⱓ",["ⱔ"]="Ⱔ",["ⱕ"]="Ⱕ",["ⱖ"]="Ⱖ",["ⱗ"]="Ⱗ",["ⱘ"]="Ⱘ",["ⱙ"]="Ⱙ",["ⱚ"]="Ⱚ",["ⱛ"]="Ⱛ",["ⱜ"]="Ⱜ",["ⱝ"]="Ⱝ",["ⱞ"]="Ⱞ",["ⱡ"]="Ⱡ",["ⱥ"]="Ⱥ",["ⱦ"]="Ⱦ",["ⱨ"]="Ⱨ",["ⱪ"]="Ⱪ",["ⱬ"]="Ⱬ",["ⱶ"]="Ⱶ",["ⲁ"]="Ⲁ",["ⲃ"]="Ⲃ",["ⲅ"]="Ⲅ",["ⲇ"]="Ⲇ",["ⲉ"]="Ⲉ",["ⲋ"]="Ⲋ",["ⲍ"]="Ⲍ",["ⲏ"]="Ⲏ",["ⲑ"]="Ⲑ",["ⲓ"]="Ⲓ",["ⲕ"]="Ⲕ",["ⲗ"]="Ⲗ",["ⲙ"]="Ⲙ",["ⲛ"]="Ⲛ",["ⲝ"]="Ⲝ",["ⲟ"]="Ⲟ",["ⲡ"]="Ⲡ",["ⲣ"]="Ⲣ",["ⲥ"]="Ⲥ",["ⲧ"]="Ⲧ",["ⲩ"]="Ⲩ",["ⲫ"]="Ⲫ",["ⲭ"]="Ⲭ",["ⲯ"]="Ⲯ",["ⲱ"]="Ⲱ",["ⲳ"]="Ⲳ",["ⲵ"]="Ⲵ",["ⲷ"]="Ⲷ",["ⲹ"]="Ⲹ",["ⲻ"]="Ⲻ",["ⲽ"]="Ⲽ",["ⲿ"]="Ⲿ",["ⳁ"]="Ⳁ",["ⳃ"]="Ⳃ",["ⳅ"]="Ⳅ",["ⳇ"]="Ⳇ",["ⳉ"]="Ⳉ",["ⳋ"]="Ⳋ",["ⳍ"]="Ⳍ",["ⳏ"]="Ⳏ",["ⳑ"]="Ⳑ",["ⳓ"]="Ⳓ",["ⳕ"]="Ⳕ",["ⳗ"]="Ⳗ",["ⳙ"]="Ⳙ",["ⳛ"]="Ⳛ",["ⳝ"]="Ⳝ",["ⳟ"]="Ⳟ",["ⳡ"]="Ⳡ",["ⳣ"]="Ⳣ",["ⴀ"]="Ⴀ",["ⴁ"]="Ⴁ",["ⴂ"]="Ⴂ",["ⴃ"]="Ⴃ",["ⴄ"]="Ⴄ",["ⴅ"]="Ⴅ",["ⴆ"]="Ⴆ",["ⴇ"]="Ⴇ",["ⴈ"]="Ⴈ",["ⴉ"]="Ⴉ",["ⴊ"]="Ⴊ",["ⴋ"]="Ⴋ",["ⴌ"]="Ⴌ",["ⴍ"]="Ⴍ",["ⴎ"]="Ⴎ",["ⴏ"]="Ⴏ",["ⴐ"]="Ⴐ",["ⴑ"]="Ⴑ",["ⴒ"]="Ⴒ",["ⴓ"]="Ⴓ",["ⴔ"]="Ⴔ",["ⴕ"]="Ⴕ",["ⴖ"]="Ⴖ",["ⴗ"]="Ⴗ",["ⴘ"]="Ⴘ",["ⴙ"]="Ⴙ",["ⴚ"]="Ⴚ",["ⴛ"]="Ⴛ",["ⴜ"]="Ⴜ",["ⴝ"]="Ⴝ",["ⴞ"]="Ⴞ",["ⴟ"]="Ⴟ",["ⴠ"]="Ⴠ",["ⴡ"]="Ⴡ",["ⴢ"]="Ⴢ",["ⴣ"]="Ⴣ",["ⴤ"]="Ⴤ",["ⴥ"]="Ⴥ",["a"]="A",["b"]="B",["c"]="C",["d"]="D",["e"]="E",["f"]="F",["g"]="G",["h"]="H",["i"]="I",["j"]="J",["k"]="K",["l"]="L",["m"]="M",["n"]="N",["o"]="O",["p"]="P",["q"]="Q",["r"]="R",["s"]="S",["t"]="T",["u"]="U",["v"]="V",["w"]="W",["x"]="X",["y"]="Y",["z"]="Z",["𐐨"]="𐐀",["𐐩"]="𐐁",["𐐪"]="𐐂",["𐐫"]="𐐃",["𐐬"]="𐐄",["𐐭"]="𐐅",["𐐮"]="𐐆",["𐐯"]="𐐇",["𐐰"]="𐐈",["𐐱"]="𐐉",["𐐲"]="𐐊",["𐐳"]="𐐋",["𐐴"]="𐐌",["𐐵"]="𐐍",["𐐶"]="𐐎",["𐐷"]="𐐏",["𐐸"]="𐐐",["𐐹"]="𐐑",["𐐺"]="𐐒",["𐐻"]="𐐓",["𐐼"]="𐐔",["𐐽"]="𐐕",["𐐾"]="𐐖",["𐐿"]="𐐗",["𐑀"]="𐐘",["𐑁"]="𐐙",["𐑂"]="𐐚",["𐑃"]="𐐛",["𐑄"]="𐐜",["𐑅"]="𐐝",["𐑆"]="𐐞",["𐑇"]="𐐟",["𐑈"]="𐐠",["𐑉"]="𐐡",["𐑊"]="𐐢",["𐑋"]="𐐣",["𐑌"]="𐐤",["𐑍"]="𐐥",["𐑎"]="𐐦",["𐑏"]="𐐧",} +utf8_uc_lc={["A"]="a",["B"]="b",["C"]="c",["D"]="d",["E"]="e",["F"]="f",["G"]="g",["H"]="h",["I"]="i",["J"]="j",["K"]="k",["L"]="l",["M"]="m",["N"]="n",["O"]="o",["P"]="p",["Q"]="q",["R"]="r",["S"]="s",["T"]="t",["U"]="u",["V"]="v",["W"]="w",["X"]="x",["Y"]="y",["Z"]="z",["À"]="à",["Á"]="á",["Â"]="â",["Ã"]="ã",["Ä"]="ä",["Å"]="å",["Æ"]="æ",["Ç"]="ç",["È"]="è",["É"]="é",["Ê"]="ê",["Ë"]="ë",["Ì"]="ì",["Í"]="í",["Î"]="î",["Ï"]="ï",["Ð"]="ð",["Ñ"]="ñ",["Ò"]="ò",["Ó"]="ó",["Ô"]="ô",["Õ"]="õ",["Ö"]="ö",["Ø"]="ø",["Ù"]="ù",["Ú"]="ú",["Û"]="û",["Ü"]="ü",["Ý"]="ý",["Þ"]="þ",["Ā"]="ā",["Ă"]="ă",["Ą"]="ą",["Ć"]="ć",["Ĉ"]="ĉ",["Ċ"]="ċ",["Č"]="č",["Ď"]="ď",["Đ"]="đ",["Ē"]="ē",["Ĕ"]="ĕ",["Ė"]="ė",["Ę"]="ę",["Ě"]="ě",["Ĝ"]="ĝ",["Ğ"]="ğ",["Ġ"]="ġ",["Ģ"]="ģ",["Ĥ"]="ĥ",["Ħ"]="ħ",["Ĩ"]="ĩ",["Ī"]="ī",["Ĭ"]="ĭ",["Į"]="į",["İ"]="i",["IJ"]="ij",["Ĵ"]="ĵ",["Ķ"]="ķ",["Ĺ"]="ĺ",["Ļ"]="ļ",["Ľ"]="ľ",["Ŀ"]="ŀ",["Ł"]="ł",["Ń"]="ń",["Ņ"]="ņ",["Ň"]="ň",["Ŋ"]="ŋ",["Ō"]="ō",["Ŏ"]="ŏ",["Ő"]="ő",["Œ"]="œ",["Ŕ"]="ŕ",["Ŗ"]="ŗ",["Ř"]="ř",["Ś"]="ś",["Ŝ"]="ŝ",["Ş"]="ş",["Š"]="š",["Ţ"]="ţ",["Ť"]="ť",["Ŧ"]="ŧ",["Ũ"]="ũ",["Ū"]="ū",["Ŭ"]="ŭ",["Ů"]="ů",["Ű"]="ű",["Ų"]="ų",["Ŵ"]="ŵ",["Ŷ"]="ŷ",["Ÿ"]="ÿ",["Ź"]="ź",["Ż"]="ż",["Ž"]="ž",["Ɓ"]="ɓ",["Ƃ"]="ƃ",["Ƅ"]="ƅ",["Ɔ"]="ɔ",["Ƈ"]="ƈ",["Ɖ"]="ɖ",["Ɗ"]="ɗ",["Ƌ"]="ƌ",["Ǝ"]="ǝ",["Ə"]="ə",["Ɛ"]="ɛ",["Ƒ"]="ƒ",["Ɠ"]="ɠ",["Ɣ"]="ɣ",["Ɩ"]="ɩ",["Ɨ"]="ɨ",["Ƙ"]="ƙ",["Ɯ"]="ɯ",["Ɲ"]="ɲ",["Ɵ"]="ɵ",["Ơ"]="ơ",["Ƣ"]="ƣ",["Ƥ"]="ƥ",["Ʀ"]="ʀ",["Ƨ"]="ƨ",["Ʃ"]="ʃ",["Ƭ"]="ƭ",["Ʈ"]="ʈ",["Ư"]="ư",["Ʊ"]="ʊ",["Ʋ"]="ʋ",["Ƴ"]="ƴ",["Ƶ"]="ƶ",["Ʒ"]="ʒ",["Ƹ"]="ƹ",["Ƽ"]="ƽ",["DŽ"]="dž",["Dž"]="dž",["LJ"]="lj",["Lj"]="lj",["NJ"]="nj",["Nj"]="nj",["Ǎ"]="ǎ",["Ǐ"]="ǐ",["Ǒ"]="ǒ",["Ǔ"]="ǔ",["Ǖ"]="ǖ",["Ǘ"]="ǘ",["Ǚ"]="ǚ",["Ǜ"]="ǜ",["Ǟ"]="ǟ",["Ǡ"]="ǡ",["Ǣ"]="ǣ",["Ǥ"]="ǥ",["Ǧ"]="ǧ",["Ǩ"]="ǩ",["Ǫ"]="ǫ",["Ǭ"]="ǭ",["Ǯ"]="ǯ",["DZ"]="dz",["Dz"]="dz",["Ǵ"]="ǵ",["Ƕ"]="ƕ",["Ƿ"]="ƿ",["Ǹ"]="ǹ",["Ǻ"]="ǻ",["Ǽ"]="ǽ",["Ǿ"]="ǿ",["Ȁ"]="ȁ",["Ȃ"]="ȃ",["Ȅ"]="ȅ",["Ȇ"]="ȇ",["Ȉ"]="ȉ",["Ȋ"]="ȋ",["Ȍ"]="ȍ",["Ȏ"]="ȏ",["Ȑ"]="ȑ",["Ȓ"]="ȓ",["Ȕ"]="ȕ",["Ȗ"]="ȗ",["Ș"]="ș",["Ț"]="ț",["Ȝ"]="ȝ",["Ȟ"]="ȟ",["Ƞ"]="ƞ",["Ȣ"]="ȣ",["Ȥ"]="ȥ",["Ȧ"]="ȧ",["Ȩ"]="ȩ",["Ȫ"]="ȫ",["Ȭ"]="ȭ",["Ȯ"]="ȯ",["Ȱ"]="ȱ",["Ȳ"]="ȳ",["Ⱥ"]="ⱥ",["Ȼ"]="ȼ",["Ƚ"]="ƚ",["Ⱦ"]="ⱦ",["Ɂ"]="ɂ",["Ƀ"]="ƀ",["Ʉ"]="ʉ",["Ʌ"]="ʌ",["Ɇ"]="ɇ",["Ɉ"]="ɉ",["Ɋ"]="ɋ",["Ɍ"]="ɍ",["Ɏ"]="ɏ",["Ά"]="ά",["Έ"]="έ",["Ή"]="ή",["Ί"]="ί",["Ό"]="ό",["Ύ"]="ύ",["Ώ"]="ώ",["Α"]="α",["Β"]="β",["Γ"]="γ",["Δ"]="δ",["Ε"]="ε",["Ζ"]="ζ",["Η"]="η",["Θ"]="θ",["Ι"]="ι",["Κ"]="κ",["Λ"]="λ",["Μ"]="μ",["Ν"]="ν",["Ξ"]="ξ",["Ο"]="ο",["Π"]="π",["Ρ"]="ρ",["Σ"]="σ",["Τ"]="τ",["Υ"]="υ",["Φ"]="φ",["Χ"]="χ",["Ψ"]="ψ",["Ω"]="ω",["Ϊ"]="ϊ",["Ϋ"]="ϋ",["Ϙ"]="ϙ",["Ϛ"]="ϛ",["Ϝ"]="ϝ",["Ϟ"]="ϟ",["Ϡ"]="ϡ",["Ϣ"]="ϣ",["Ϥ"]="ϥ",["Ϧ"]="ϧ",["Ϩ"]="ϩ",["Ϫ"]="ϫ",["Ϭ"]="ϭ",["Ϯ"]="ϯ",["ϴ"]="θ",["Ϸ"]="ϸ",["Ϲ"]="ϲ",["Ϻ"]="ϻ",["Ͻ"]="ͻ",["Ͼ"]="ͼ",["Ͽ"]="ͽ",["Ѐ"]="ѐ",["Ё"]="ё",["Ђ"]="ђ",["Ѓ"]="ѓ",["Є"]="є",["Ѕ"]="ѕ",["І"]="і",["Ї"]="ї",["Ј"]="ј",["Љ"]="љ",["Њ"]="њ",["Ћ"]="ћ",["Ќ"]="ќ",["Ѝ"]="ѝ",["Ў"]="ў",["Џ"]="џ",["А"]="а",["Б"]="б",["В"]="в",["Г"]="г",["Д"]="д",["Е"]="е",["Ж"]="ж",["З"]="з",["И"]="и",["Й"]="й",["К"]="к",["Л"]="л",["М"]="м",["Н"]="н",["О"]="о",["П"]="п",["Р"]="р",["С"]="с",["Т"]="т",["У"]="у",["Ф"]="ф",["Х"]="х",["Ц"]="ц",["Ч"]="ч",["Ш"]="ш",["Щ"]="щ",["Ъ"]="ъ",["Ы"]="ы",["Ь"]="ь",["Э"]="э",["Ю"]="ю",["Я"]="я",["Ѡ"]="ѡ",["Ѣ"]="ѣ",["Ѥ"]="ѥ",["Ѧ"]="ѧ",["Ѩ"]="ѩ",["Ѫ"]="ѫ",["Ѭ"]="ѭ",["Ѯ"]="ѯ",["Ѱ"]="ѱ",["Ѳ"]="ѳ",["Ѵ"]="ѵ",["Ѷ"]="ѷ",["Ѹ"]="ѹ",["Ѻ"]="ѻ",["Ѽ"]="ѽ",["Ѿ"]="ѿ",["Ҁ"]="ҁ",["Ҋ"]="ҋ",["Ҍ"]="ҍ",["Ҏ"]="ҏ",["Ґ"]="ґ",["Ғ"]="ғ",["Ҕ"]="ҕ",["Җ"]="җ",["Ҙ"]="ҙ",["Қ"]="қ",["Ҝ"]="ҝ",["Ҟ"]="ҟ",["Ҡ"]="ҡ",["Ң"]="ң",["Ҥ"]="ҥ",["Ҧ"]="ҧ",["Ҩ"]="ҩ",["Ҫ"]="ҫ",["Ҭ"]="ҭ",["Ү"]="ү",["Ұ"]="ұ",["Ҳ"]="ҳ",["Ҵ"]="ҵ",["Ҷ"]="ҷ",["Ҹ"]="ҹ",["Һ"]="һ",["Ҽ"]="ҽ",["Ҿ"]="ҿ",["Ӏ"]="ӏ",["Ӂ"]="ӂ",["Ӄ"]="ӄ",["Ӆ"]="ӆ",["Ӈ"]="ӈ",["Ӊ"]="ӊ",["Ӌ"]="ӌ",["Ӎ"]="ӎ",["Ӑ"]="ӑ",["Ӓ"]="ӓ",["Ӕ"]="ӕ",["Ӗ"]="ӗ",["Ә"]="ә",["Ӛ"]="ӛ",["Ӝ"]="ӝ",["Ӟ"]="ӟ",["Ӡ"]="ӡ",["Ӣ"]="ӣ",["Ӥ"]="ӥ",["Ӧ"]="ӧ",["Ө"]="ө",["Ӫ"]="ӫ",["Ӭ"]="ӭ",["Ӯ"]="ӯ",["Ӱ"]="ӱ",["Ӳ"]="ӳ",["Ӵ"]="ӵ",["Ӷ"]="ӷ",["Ӹ"]="ӹ",["Ӻ"]="ӻ",["Ӽ"]="ӽ",["Ӿ"]="ӿ",["Ԁ"]="ԁ",["Ԃ"]="ԃ",["Ԅ"]="ԅ",["Ԇ"]="ԇ",["Ԉ"]="ԉ",["Ԋ"]="ԋ",["Ԍ"]="ԍ",["Ԏ"]="ԏ",["Ԑ"]="ԑ",["Ԓ"]="ԓ",["Ա"]="ա",["Բ"]="բ",["Գ"]="գ",["Դ"]="դ",["Ե"]="ե",["Զ"]="զ",["Է"]="է",["Ը"]="ը",["Թ"]="թ",["Ժ"]="ժ",["Ի"]="ի",["Լ"]="լ",["Խ"]="խ",["Ծ"]="ծ",["Կ"]="կ",["Հ"]="հ",["Ձ"]="ձ",["Ղ"]="ղ",["Ճ"]="ճ",["Մ"]="մ",["Յ"]="յ",["Ն"]="ն",["Շ"]="շ",["Ո"]="ո",["Չ"]="չ",["Պ"]="պ",["Ջ"]="ջ",["Ռ"]="ռ",["Ս"]="ս",["Վ"]="վ",["Տ"]="տ",["Ր"]="ր",["Ց"]="ց",["Ւ"]="ւ",["Փ"]="փ",["Ք"]="ք",["Օ"]="օ",["Ֆ"]="ֆ",["Ⴀ"]="ⴀ",["Ⴁ"]="ⴁ",["Ⴂ"]="ⴂ",["Ⴃ"]="ⴃ",["Ⴄ"]="ⴄ",["Ⴅ"]="ⴅ",["Ⴆ"]="ⴆ",["Ⴇ"]="ⴇ",["Ⴈ"]="ⴈ",["Ⴉ"]="ⴉ",["Ⴊ"]="ⴊ",["Ⴋ"]="ⴋ",["Ⴌ"]="ⴌ",["Ⴍ"]="ⴍ",["Ⴎ"]="ⴎ",["Ⴏ"]="ⴏ",["Ⴐ"]="ⴐ",["Ⴑ"]="ⴑ",["Ⴒ"]="ⴒ",["Ⴓ"]="ⴓ",["Ⴔ"]="ⴔ",["Ⴕ"]="ⴕ",["Ⴖ"]="ⴖ",["Ⴗ"]="ⴗ",["Ⴘ"]="ⴘ",["Ⴙ"]="ⴙ",["Ⴚ"]="ⴚ",["Ⴛ"]="ⴛ",["Ⴜ"]="ⴜ",["Ⴝ"]="ⴝ",["Ⴞ"]="ⴞ",["Ⴟ"]="ⴟ",["Ⴠ"]="ⴠ",["Ⴡ"]="ⴡ",["Ⴢ"]="ⴢ",["Ⴣ"]="ⴣ",["Ⴤ"]="ⴤ",["Ⴥ"]="ⴥ",["Ḁ"]="ḁ",["Ḃ"]="ḃ",["Ḅ"]="ḅ",["Ḇ"]="ḇ",["Ḉ"]="ḉ",["Ḋ"]="ḋ",["Ḍ"]="ḍ",["Ḏ"]="ḏ",["Ḑ"]="ḑ",["Ḓ"]="ḓ",["Ḕ"]="ḕ",["Ḗ"]="ḗ",["Ḙ"]="ḙ",["Ḛ"]="ḛ",["Ḝ"]="ḝ",["Ḟ"]="ḟ",["Ḡ"]="ḡ",["Ḣ"]="ḣ",["Ḥ"]="ḥ",["Ḧ"]="ḧ",["Ḩ"]="ḩ",["Ḫ"]="ḫ",["Ḭ"]="ḭ",["Ḯ"]="ḯ",["Ḱ"]="ḱ",["Ḳ"]="ḳ",["Ḵ"]="ḵ",["Ḷ"]="ḷ",["Ḹ"]="ḹ",["Ḻ"]="ḻ",["Ḽ"]="ḽ",["Ḿ"]="ḿ",["Ṁ"]="ṁ",["Ṃ"]="ṃ",["Ṅ"]="ṅ",["Ṇ"]="ṇ",["Ṉ"]="ṉ",["Ṋ"]="ṋ",["Ṍ"]="ṍ",["Ṏ"]="ṏ",["Ṑ"]="ṑ",["Ṓ"]="ṓ",["Ṕ"]="ṕ",["Ṗ"]="ṗ",["Ṙ"]="ṙ",["Ṛ"]="ṛ",["Ṝ"]="ṝ",["Ṟ"]="ṟ",["Ṡ"]="ṡ",["Ṣ"]="ṣ",["Ṥ"]="ṥ",["Ṧ"]="ṧ",["Ṩ"]="ṩ",["Ṫ"]="ṫ",["Ṭ"]="ṭ",["Ṯ"]="ṯ",["Ṱ"]="ṱ",["Ṳ"]="ṳ",["Ṵ"]="ṵ",["Ṷ"]="ṷ",["Ṹ"]="ṹ",["Ṻ"]="ṻ",["Ṽ"]="ṽ",["Ṿ"]="ṿ",["Ẁ"]="ẁ",["Ẃ"]="ẃ",["Ẅ"]="ẅ",["Ẇ"]="ẇ",["Ẉ"]="ẉ",["Ẋ"]="ẋ",["Ẍ"]="ẍ",["Ẏ"]="ẏ",["Ẑ"]="ẑ",["Ẓ"]="ẓ",["Ẕ"]="ẕ",["Ạ"]="ạ",["Ả"]="ả",["Ấ"]="ấ",["Ầ"]="ầ",["Ẩ"]="ẩ",["Ẫ"]="ẫ",["Ậ"]="ậ",["Ắ"]="ắ",["Ằ"]="ằ",["Ẳ"]="ẳ",["Ẵ"]="ẵ",["Ặ"]="ặ",["Ẹ"]="ẹ",["Ẻ"]="ẻ",["Ẽ"]="ẽ",["Ế"]="ế",["Ề"]="ề",["Ể"]="ể",["Ễ"]="ễ",["Ệ"]="ệ",["Ỉ"]="ỉ",["Ị"]="ị",["Ọ"]="ọ",["Ỏ"]="ỏ",["Ố"]="ố",["Ồ"]="ồ",["Ổ"]="ổ",["Ỗ"]="ỗ",["Ộ"]="ộ",["Ớ"]="ớ",["Ờ"]="ờ",["Ở"]="ở",["Ỡ"]="ỡ",["Ợ"]="ợ",["Ụ"]="ụ",["Ủ"]="ủ",["Ứ"]="ứ",["Ừ"]="ừ",["Ử"]="ử",["Ữ"]="ữ",["Ự"]="ự",["Ỳ"]="ỳ",["Ỵ"]="ỵ",["Ỷ"]="ỷ",["Ỹ"]="ỹ",["Ἀ"]="ἀ",["Ἁ"]="ἁ",["Ἂ"]="ἂ",["Ἃ"]="ἃ",["Ἄ"]="ἄ",["Ἅ"]="ἅ",["Ἆ"]="ἆ",["Ἇ"]="ἇ",["Ἐ"]="ἐ",["Ἑ"]="ἑ",["Ἒ"]="ἒ",["Ἓ"]="ἓ",["Ἔ"]="ἔ",["Ἕ"]="ἕ",["Ἠ"]="ἠ",["Ἡ"]="ἡ",["Ἢ"]="ἢ",["Ἣ"]="ἣ",["Ἤ"]="ἤ",["Ἥ"]="ἥ",["Ἦ"]="ἦ",["Ἧ"]="ἧ",["Ἰ"]="ἰ",["Ἱ"]="ἱ",["Ἲ"]="ἲ",["Ἳ"]="ἳ",["Ἴ"]="ἴ",["Ἵ"]="ἵ",["Ἶ"]="ἶ",["Ἷ"]="ἷ",["Ὀ"]="ὀ",["Ὁ"]="ὁ",["Ὂ"]="ὂ",["Ὃ"]="ὃ",["Ὄ"]="ὄ",["Ὅ"]="ὅ",["Ὑ"]="ὑ",["Ὓ"]="ὓ",["Ὕ"]="ὕ",["Ὗ"]="ὗ",["Ὠ"]="ὠ",["Ὡ"]="ὡ",["Ὢ"]="ὢ",["Ὣ"]="ὣ",["Ὤ"]="ὤ",["Ὥ"]="ὥ",["Ὦ"]="ὦ",["Ὧ"]="ὧ",["ᾈ"]="ᾀ",["ᾉ"]="ᾁ",["ᾊ"]="ᾂ",["ᾋ"]="ᾃ",["ᾌ"]="ᾄ",["ᾍ"]="ᾅ",["ᾎ"]="ᾆ",["ᾏ"]="ᾇ",["ᾘ"]="ᾐ",["ᾙ"]="ᾑ",["ᾚ"]="ᾒ",["ᾛ"]="ᾓ",["ᾜ"]="ᾔ",["ᾝ"]="ᾕ",["ᾞ"]="ᾖ",["ᾟ"]="ᾗ",["ᾨ"]="ᾠ",["ᾩ"]="ᾡ",["ᾪ"]="ᾢ",["ᾫ"]="ᾣ",["ᾬ"]="ᾤ",["ᾭ"]="ᾥ",["ᾮ"]="ᾦ",["ᾯ"]="ᾧ",["Ᾰ"]="ᾰ",["Ᾱ"]="ᾱ",["Ὰ"]="ὰ",["Ά"]="ά",["ᾼ"]="ᾳ",["Ὲ"]="ὲ",["Έ"]="έ",["Ὴ"]="ὴ",["Ή"]="ή",["ῌ"]="ῃ",["Ῐ"]="ῐ",["Ῑ"]="ῑ",["Ὶ"]="ὶ",["Ί"]="ί",["Ῠ"]="ῠ",["Ῡ"]="ῡ",["Ὺ"]="ὺ",["Ύ"]="ύ",["Ῥ"]="ῥ",["Ὸ"]="ὸ",["Ό"]="ό",["Ὼ"]="ὼ",["Ώ"]="ώ",["ῼ"]="ῳ",["Ω"]="ω",["K"]="k",["Å"]="å",["Ⅎ"]="ⅎ",["Ⅰ"]="ⅰ",["Ⅱ"]="ⅱ",["Ⅲ"]="ⅲ",["Ⅳ"]="ⅳ",["Ⅴ"]="ⅴ",["Ⅵ"]="ⅵ",["Ⅶ"]="ⅶ",["Ⅷ"]="ⅷ",["Ⅸ"]="ⅸ",["Ⅹ"]="ⅹ",["Ⅺ"]="ⅺ",["Ⅻ"]="ⅻ",["Ⅼ"]="ⅼ",["Ⅽ"]="ⅽ",["Ⅾ"]="ⅾ",["Ⅿ"]="ⅿ",["Ↄ"]="ↄ",["Ⓐ"]="ⓐ",["Ⓑ"]="ⓑ",["Ⓒ"]="ⓒ",["Ⓓ"]="ⓓ",["Ⓔ"]="ⓔ",["Ⓕ"]="ⓕ",["Ⓖ"]="ⓖ",["Ⓗ"]="ⓗ",["Ⓘ"]="ⓘ",["Ⓙ"]="ⓙ",["Ⓚ"]="ⓚ",["Ⓛ"]="ⓛ",["Ⓜ"]="ⓜ",["Ⓝ"]="ⓝ",["Ⓞ"]="ⓞ",["Ⓟ"]="ⓟ",["Ⓠ"]="ⓠ",["Ⓡ"]="ⓡ",["Ⓢ"]="ⓢ",["Ⓣ"]="ⓣ",["Ⓤ"]="ⓤ",["Ⓥ"]="ⓥ",["Ⓦ"]="ⓦ",["Ⓧ"]="ⓧ",["Ⓨ"]="ⓨ",["Ⓩ"]="ⓩ",["Ⰰ"]="ⰰ",["Ⰱ"]="ⰱ",["Ⰲ"]="ⰲ",["Ⰳ"]="ⰳ",["Ⰴ"]="ⰴ",["Ⰵ"]="ⰵ",["Ⰶ"]="ⰶ",["Ⰷ"]="ⰷ",["Ⰸ"]="ⰸ",["Ⰹ"]="ⰹ",["Ⰺ"]="ⰺ",["Ⰻ"]="ⰻ",["Ⰼ"]="ⰼ",["Ⰽ"]="ⰽ",["Ⰾ"]="ⰾ",["Ⰿ"]="ⰿ",["Ⱀ"]="ⱀ",["Ⱁ"]="ⱁ",["Ⱂ"]="ⱂ",["Ⱃ"]="ⱃ",["Ⱄ"]="ⱄ",["Ⱅ"]="ⱅ",["Ⱆ"]="ⱆ",["Ⱇ"]="ⱇ",["Ⱈ"]="ⱈ",["Ⱉ"]="ⱉ",["Ⱊ"]="ⱊ",["Ⱋ"]="ⱋ",["Ⱌ"]="ⱌ",["Ⱍ"]="ⱍ",["Ⱎ"]="ⱎ",["Ⱏ"]="ⱏ",["Ⱐ"]="ⱐ",["Ⱑ"]="ⱑ",["Ⱒ"]="ⱒ",["Ⱓ"]="ⱓ",["Ⱔ"]="ⱔ",["Ⱕ"]="ⱕ",["Ⱖ"]="ⱖ",["Ⱗ"]="ⱗ",["Ⱘ"]="ⱘ",["Ⱙ"]="ⱙ",["Ⱚ"]="ⱚ",["Ⱛ"]="ⱛ",["Ⱜ"]="ⱜ",["Ⱝ"]="ⱝ",["Ⱞ"]="ⱞ",["Ⱡ"]="ⱡ",["Ɫ"]="ɫ",["Ᵽ"]="ᵽ",["Ɽ"]="ɽ",["Ⱨ"]="ⱨ",["Ⱪ"]="ⱪ",["Ⱬ"]="ⱬ",["Ⱶ"]="ⱶ",["Ⲁ"]="ⲁ",["Ⲃ"]="ⲃ",["Ⲅ"]="ⲅ",["Ⲇ"]="ⲇ",["Ⲉ"]="ⲉ",["Ⲋ"]="ⲋ",["Ⲍ"]="ⲍ",["Ⲏ"]="ⲏ",["Ⲑ"]="ⲑ",["Ⲓ"]="ⲓ",["Ⲕ"]="ⲕ",["Ⲗ"]="ⲗ",["Ⲙ"]="ⲙ",["Ⲛ"]="ⲛ",["Ⲝ"]="ⲝ",["Ⲟ"]="ⲟ",["Ⲡ"]="ⲡ",["Ⲣ"]="ⲣ",["Ⲥ"]="ⲥ",["Ⲧ"]="ⲧ",["Ⲩ"]="ⲩ",["Ⲫ"]="ⲫ",["Ⲭ"]="ⲭ",["Ⲯ"]="ⲯ",["Ⲱ"]="ⲱ",["Ⲳ"]="ⲳ",["Ⲵ"]="ⲵ",["Ⲷ"]="ⲷ",["Ⲹ"]="ⲹ",["Ⲻ"]="ⲻ",["Ⲽ"]="ⲽ",["Ⲿ"]="ⲿ",["Ⳁ"]="ⳁ",["Ⳃ"]="ⳃ",["Ⳅ"]="ⳅ",["Ⳇ"]="ⳇ",["Ⳉ"]="ⳉ",["Ⳋ"]="ⳋ",["Ⳍ"]="ⳍ",["Ⳏ"]="ⳏ",["Ⳑ"]="ⳑ",["Ⳓ"]="ⳓ",["Ⳕ"]="ⳕ",["Ⳗ"]="ⳗ",["Ⳙ"]="ⳙ",["Ⳛ"]="ⳛ",["Ⳝ"]="ⳝ",["Ⳟ"]="ⳟ",["Ⳡ"]="ⳡ",["Ⳣ"]="ⳣ",["A"]="a",["B"]="b",["C"]="c",["D"]="d",["E"]="e",["F"]="f",["G"]="g",["H"]="h",["I"]="i",["J"]="j",["K"]="k",["L"]="l",["M"]="m",["N"]="n",["O"]="o",["P"]="p",["Q"]="q",["R"]="r",["S"]="s",["T"]="t",["U"]="u",["V"]="v",["W"]="w",["X"]="x",["Y"]="y",["Z"]="z",["𐐀"]="𐐨",["𐐁"]="𐐩",["𐐂"]="𐐪",["𐐃"]="𐐫",["𐐄"]="𐐬",["𐐅"]="𐐭",["𐐆"]="𐐮",["𐐇"]="𐐯",["𐐈"]="𐐰",["𐐉"]="𐐱",["𐐊"]="𐐲",["𐐋"]="𐐳",["𐐌"]="𐐴",["𐐍"]="𐐵",["𐐎"]="𐐶",["𐐏"]="𐐷",["𐐐"]="𐐸",["𐐑"]="𐐹",["𐐒"]="𐐺",["𐐓"]="𐐻",["𐐔"]="𐐼",["𐐕"]="𐐽",["𐐖"]="𐐾",["𐐗"]="𐐿",["𐐘"]="𐑀",["𐐙"]="𐑁",["𐐚"]="𐑂",["𐐛"]="𐑃",["𐐜"]="𐑄",["𐐝"]="𐑅",["𐐞"]="𐑆",["𐐟"]="𐑇",["𐐠"]="𐑈",["𐐡"]="𐑉",["𐐢"]="𐑊",["𐐣"]="𐑋",["𐐤"]="𐑌",["𐐥"]="𐑍",["𐐦"]="𐑎",["𐐧"]="𐑏",} diff --git a/octolib/addon/lua/octolib/modules/_client/imgur.lua b/octolib/addon/lua/octolib/modules/_client/imgur.lua new file mode 100644 index 0000000..8264afd --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_client/imgur.lua @@ -0,0 +1,92 @@ +local original, proxy = 'https://i.imgur.com/', 'https://wani4ka.ru/api/imgur/' + +local server + +function octolib.imgurLoaded() + return server ~= nil +end + +function octolib.forceImgurProxy(useProxy) + server = useProxy and proxy or original +end + +function octolib.imgurUsesProxy() + return server == proxy +end + +http.Fetch('https://i.imgur.com/7pODAQu.jpg', function(body, size, headers, code) + server = code == 200 and original or proxy + hook.Run('octolib.imgur.loaded') +end, function() + server = proxy +end) + +function octolib.imgurImage(name) + if not octolib.imgurLoaded() then + ErrorNoHalt('Tried to load imgur image before server determined') + end + return (server or original) .. name +end + +octolib.loadingMat = Material('octoteam/icons/clock.png') +local imgCache = {} + +local function fileNameFromURL(url) + return 'imgscreen/' .. + string.StripExtension(url):gsub('https?://', ''):gsub('[\\/:*?"<>|%.]', '_') .. + '.' .. string.GetExtensionFromFilename(url) +end + +local function matNameFromURL(url) + return '../data/' .. fileNameFromURL(url) +end + +function octolib.getURLMaterial(url, callback, forceReload) + local mat = imgCache[url] + if not mat then + imgCache[url] = octolib.loadingMat + mat = octolib.loadingMat + + http.Fetch(url, function(content) + file.Write(fileNameFromURL(url), content) + + local matName = matNameFromURL(url) + RunConsoleCommand('mat_reloadmaterial', string.StripExtension(matName)) + imgCache[url] = Material(matName) + + if isfunction(callback) then callback(imgCache[url]) end + end) + else + if forceReload then + RunConsoleCommand('mat_reloadmaterial', string.StripExtension(matNameFromURL(url))) + end + if isfunction(callback) then callback(mat) end + end + return mat +end + +function octolib.getImgurMaterial(url, callback, forceReload) + return octolib.getURLMaterial(octolib.imgurImage(url), callback, forceReload) +end + +local function clearCache() + + file.CreateDir('imgscreen') + local fls = file.Find('imgscreen/*', 'DATA') + for _, fl in ipairs(fls) do + file.Delete('imgscreen/' .. fl) + end + +end +hook.Add('Shutdown', 'imgscreen.clearCache', clearCache) +hook.Add('PlayerFinishedLoading', 'imgscreen.clearCache', clearCache) + +hook.Add('Think', 'octolib-imgur.init', function() + hook.Remove('Think', 'octolib-imgur.init') + vgui.GetControlTable('DImage').SetURL = function(self, url) + octolib.getURLMaterial(url, function(mat) + if not IsValid(self) then return end + self:SetMaterial(mat) + end) + end +end) diff --git a/octolib/addon/lua/octolib/modules/_client/panels.lua b/octolib/addon/lua/octolib/modules/_client/panels.lua new file mode 100644 index 0000000..f67f7a1 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_client/panels.lua @@ -0,0 +1,432 @@ +--[[ + Namespace: octolib + + Group: panels +]] + +local function doMenu(m, entries) + + for i, entry in ipairs(entries) do + if istable(entry) then + local name, icon, more = unpack(entry) + + local sm, mo + if isfunction(more) then + mo = m:AddOption(name, more) + elseif istable(more) then + sm, mo = m:AddSubMenu(name) + doMenu(sm, more) + end + + if icon and mo then mo:SetIcon(icon) end + elseif entry == 'spacer' then + m:AddSpacer() + end + end + +end + +--[[ + Function: menu + Builds interactive context menu + + Arguments: +
entries - Sequential table of + + Returns: + - + + Example: + --- Lua + local m = octolib.menu({ + { 'Hello', 'icon16/tick.png', function() print('Hello') end }, -- with icon + { 'I am', nil, function() print('There') end }, -- without icon + { 'Menu', nil, { -- with submenu + { 'Submenu item', nil, function() print('So sub') end }, + }}, + }) + + m:Open() + --- +]] +function octolib.menu(entries) + + local m = DermaMenu() + doMenu(m, entries) + + return m + +end + +function octolib.fQuery(...) + + local args = {...} + return function() Derma_Query(unpack(args)) end + +end + +function octolib.fStringRequest(...) + + local args = {...} + return function() Derma_StringRequest(unpack(args)) end + +end + +function octolib.overlay(pnl, clOrPnl, persist, col) + + local o = vgui.Create(persist and 'DPanel' or 'DButton') + if IsValid(pnl) then pnl:Add(o) end + + local p + if isstring(clOrPnl) then + p = o:Add(clOrPnl) + else + clOrPnl:SetParent(o) + p = clOrPnl + end + + function o:Think() + local parent = self:GetParent() + if not IsValid(parent) then return end + + local newW, newH = parent:GetSize() + if newW ~= self.overlay_oldW or self.overlay_oldH ~= newH then + self:SetPos(0, 0) + self:SetSize(newW, newH) + if IsValid(p) then p:Center() end + end + self.overlay_oldW, self.overlay_oldH = newW, newH + end + + o.bgCol = col or Color(0,0,0, 200) + function o:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, self.bgCol) + end + + if not persist then + o:SetText('') + function o:DoClick() + self:Remove() + end + end + + function p:OnRemove() o:Remove() end + + return p, o + +end + +-- +-- QUICK PANEL CREATION +-- + +function octolib.button(pnl, txt, click) + + local b = vgui.Create 'DButton' + if pnl.AddItem then pnl:AddItem(b) else pnl:Add(b) end + b:Dock(TOP) + b:SetTall(25) + b:SetText(txt) + b.DoClick = click + + return b + +end + +function octolib.label(pnl, txt) + + local t = vgui.Create 'DLabel' + if pnl.AddItem then pnl:AddItem(t) else pnl:Add(t) end + t:Dock(TOP) + t:SetTall(18) + t:SetContentAlignment(4) + t:SetText(txt) + + return t + +end + +function octolib.slider(pnl, txt, min, max, prc) + + local e = vgui.Create 'DNumSlider' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:SetTall(30) + e:SetText(txt) + e:SetMinMax(min, max) + e:SetDecimals(prc) + e.Slider.Knob:NoClipping(false) + + return e + +end + +function octolib.checkBox(pnl, txt) + + local e = vgui.Create 'DCheckBoxLabel' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:DockMargin(0, 5, 0, 5) + e:SetText(txt) + + return e + +end + +function octolib.textEntry(pnl, txt) + + local t = txt and octolib.label(pnl, txt) or nil + if t then + t:DockMargin(3, 5, 0, 0) + end + + local e = vgui.Create 'DTextEntry' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:SetTall(25) + e:DockMargin(0, 0, 0, 5) + + return e, t + +end + +function octolib.numberWang(pnl, txt, val, min, max) + + local t = txt and octolib.label(pnl, txt) or nil + if t then + t:DockMargin(3, 5, 0, 0) + end + + local e = vgui.Create 'DNumberWang' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:SetTall(25) + e:DockMargin(0, 0, 0, 5) + if min then e:SetMin(min) end + if max then e:SetMax(max) end + if val then e:SetValue(val) end + + function e.Up:DoClick() + if e:IsEnabled() then + e:SetValue(e:GetValue() + e:GetInterval()) + end + end + function e.Down:DoClick() + if e:IsEnabled() then + e:SetValue(e:GetValue() - e:GetInterval()) + end + end + function e:OnChange() + local val = self:GetValue() + local fixed = val + if self:GetMin() then fixed = math.max(self:GetMin(), fixed) end + if self:GetMax() then fixed = math.min(self:GetMax(), fixed) end + fixed = math.Round(fixed, self:GetDecimals()) + if fixed ~= val then + local cpos = self:GetCaretPos() + self:SetText(fixed) + self:SetCaretPos(math.min(cpos, utf8.len(tostring(fixed)))) + else + self:OnValueChanged(val) + end + end + + return e, t + +end + +function octolib.colorPicker(pnl, txt, enableAlpha, enablePalette) + + local t = txt and octolib.label(pnl, txt) or nil + if t then + t:DockMargin(3, 5, 0, 0) + end + + local e = vgui.Create 'DColorMixer' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:SetTall(150) + e:DockMargin(0, 0, 0, 5) + e:SetAlphaBar(enableAlpha == true) + e:SetWangs(true) + e:SetPalette(enablePalette == true) + + return e, t + +end + +function octolib.comboBox(pnl, txt, choices) + + local t = txt and octolib.label(pnl, txt) or nil + if t then + t:DockMargin(3, 5, 0, 0) + end + + local e = vgui.Create 'DComboBox' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:SetTall(25) + e:DockMargin(0, 0, 0, 5) + e:SetSortItems(false) + + choices = choices or {} + for i, v in ipairs(choices) do + e:AddChoice(v[1], v[2], v[3]) + end + + return e, t + +end + +function octolib.textEntryIcon(pnl, txt, path) + + local t = txt and octolib.label(pnl, txt) or nil + if t then + t:DockMargin(3, 5, 0, 0) + end + + local e = vgui.Create 'DTextEntry' + if pnl.AddItem then pnl:AddItem(e) else pnl:Add(e) end + e:Dock(TOP) + e:SetTall(25) + e:DockMargin(0, 0, 0, 5) + + local b = e:Add 'DButton' + b:Dock(RIGHT) + b:SetWide(25) + b:SetText('') + b:SetIcon('icon16/color_swatch.png') + b:SetPaintBackground(false) + function b:DoClick() + if IsValid(self.picker) then return end + self.picker = octolib.icons.picker(function(val) e:SetValue(val) end, e:GetValue(), path) + end + + return e, t + +end + +function octolib.confirmDialog(pnl, question, callback, noCancel) + local fr = octolib.overlay(pnl, 'DPanel') + fr:SetSize(180, 60) + octolib.label(fr, question):SetContentAlignment(5) + local btmPan = fr:Add('DPanel') + btmPan:Dock(FILL) + btmPan:SetPaintBackground(false) + local btn = octolib.button(btmPan, 'Да', function() callback(true) fr:Remove() end) + btn:Dock(LEFT) + btn:SetWide(50) + btn:DockMargin(5, 0, 5, 5) + local btn = octolib.button(btmPan, 'Нет', function() callback(false) fr:Remove() end) + btn:Dock(LEFT) + btn:SetWide(50) + btn:DockMargin(5, 0, 5, 5) + if not noCancel then + local btn = octolib.button(btmPan, 'Отмена', function() fr:Remove() end) + btn:Dock(FILL) + btn:DockMargin(5, 0, 5, 5) + end +end + +function octolib.binder(pnl, txt, val) + local t = txt and octolib.label(pnl, txt) or nil + if t then + t:DockMargin(3, 5, 0, 0) + end + + local b = vgui.Create 'DBinder' + if pnl.AddItem then pnl:AddItem(b) else pnl:Add(b) end + b:Dock(TOP) + b:SetTall(24) + b:SetContentAlignment(5) + + return b +end + +hook.Add('Think', 'octolib.panels', function() + hook.Remove('Think', 'octolib.panels') + + local Panel = FindMetaTable 'Panel' + surface.CreateFont('octolib.hint', { + font = 'Calibri', + extended = true, + size = 18, + weight = 350, + }) + + surface.CreateFont('octolib.hint-sh', { + font = 'Calibri', + extended = true, + size = 40, + weight = 350, + blursize = 10, + }) + + local function paintHint(self, w, h) + surface.DisableClipping(true) + + local al = self.anim + surface.SetFont('octolib.hint') + local tw, th = surface.GetTextSize(self.hint) + local x, y = w / 2, -16*#self.lines + + self.shText = self.shText or ('|'):rep(math.floor((tw+30)/15)) + draw.SimpleText(self.shText, 'octolib.hint-sh', x, y, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + draw.RoundedBox(4, (w-tw-14) / 2, y - 10, tw + 14, -y+6, Color(170,119,102, al*255)) + for i, line in ipairs(self.lines) do + draw.SimpleText(line, 'octolib.hint', x, y+(i-1)*16, Color(255,255,255, al*255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + draw.SimpleText('u', 'marlett', x, -10, Color(170,119,102, al*255), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + surface.DisableClipping(false) + end + + local function thinkHint(self) + self.anim = math.Approach( self.anim, self.Hovered and 1 or 0, FrameTime() / 0.1 ) + end + + -- Class: Panel + + --[[ + Function: AddHint + Adds simple fading tooltop above the panel generally used to place usage hints + + Arguments: + text - Text to place into tooltip + ]] + function Panel:AddHint(text) + self.realPaint = self.realPaint or self.Paint or function() end + self.realThink = self.realThink or self.Think or function() end + self.realEnter = self.realEnter or self.OnCursorEntered or function() end + self.realExit = self.realExit or self.OnCursorExited or function() end + + self.anim = 0 + self.hint = isfunction(text) and '' or text or 'Hint text' + self.lines = self.hint:split('\n') + + self.Paint = function(self, w, h) + if self.anim > 0 then paintHint(self, w, h) end + self:realPaint(w, h) + end + + self.Think = function(self) + self:realThink() + thinkHint(self) + if self.anim > 0 and isfunction(text) then + self.hint = text() + self.lines = self.hint:split('\n') + end + end + + self.OnCursorEntered = function(self) + self:realEnter() + self:SetDrawOnTop(true) + end + + self.OnCursorExited = function(self) + self:realExit() + self:SetDrawOnTop(false) + end + end +end) diff --git a/octolib/addon/lua/octolib/modules/_client/render.lua b/octolib/addon/lua/octolib/modules/_client/render.lua new file mode 100644 index 0000000..ff6ba7d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_client/render.lua @@ -0,0 +1,34 @@ +octolib.drawDebug = false + +surface.CreateFont('3d.medium', { + font = 'Calibri', + extended = true, + size = 42, + weight = 350, +}) + +timer.Create('octolib.drawDebug', 2, 0, function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + local wep = ply:GetActiveWeapon() + octolib.drawDebug = ply:Team() == TEAM_ADMIN and IsValid(wep) and wep:GetClass() == 'weapon_physgun' +end) + +local colors = CFG.skinColors +function render.DrawBubble(ent, pos, ang, text, maxDist, fadeDist) + if IsValid(ent) then + pos, ang = LocalToWorld(pos, ang, ent:GetPos(), ent:GetAngles()) + end + + local al = math.Clamp(1 - (pos:DistToSqr(EyePos()) - fadeDist * fadeDist) / (maxDist * maxDist), 0, 1) * 255 + if al <= 0 then return end + + cam.Start3D2D(pos, ang, 0.1) + surface.SetFont('3d.medium') + local w, h = surface.GetTextSize(text) + draw.RoundedBox(8, -w/2 - 10, -h/2 - 3, w + 20, h + 6, ColorAlpha(colors.bg50, al)) + + draw.SimpleText(text, '3d.medium', 0, 0, Color(238,238,238, al), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() + +end diff --git a/octolib/addon/lua/octolib/modules/_client/skin.lua b/octolib/addon/lua/octolib/modules/_client/skin.lua new file mode 100644 index 0000000..6ee11dd --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_client/skin.lua @@ -0,0 +1,701 @@ +CreateClientConVar('octolib_blur', '1', true, false) + +if CFG.disabledModules.skin then return end + +local surface = surface +local draw = draw +local Color = Color + +CFG.skinColors = CFG.skinColors or {} +local cols = CFG.skinColors + +cols.b = cols.b or Color(65,132,209, 255) +cols.y = cols.y or Color(240,202,77, 255) +cols.r = cols.r or Color(222,91,73, 255) +cols.g = cols.g or Color(102,170,170, 255) +cols.o = cols.o or Color(170,119,102, 255) + +cols.bg = cols.bg or Color(85,68,85, 255) + +cols.bg95 = cols.bg95 or Color(cols.bg.r, cols.bg.g, cols.bg.b, 241) +cols.bg60 = cols.bg60 or Color(cols.bg.r, cols.bg.g, cols.bg.b, 150) +cols.bg50 = cols.bg50 or Color(cols.bg.r / 2, cols.bg.g / 2, cols.bg.b / 2, 255) + +cols.bg_d = cols.bg_d or Color(cols.bg.r * 0.75, cols.bg.g * 0.75, cols.bg.b * 0.75, 255) +cols.bg_l = cols.bg_l or Color(cols.bg.r * 1.25, cols.bg.g * 1.25, cols.bg.b * 1.25, 255) +cols.bg_grey = cols.bg_grey or Color(180,180,180, 255) +cols.g_d = cols.g_d or Color(cols.g.r * 0.75, cols.g.g * 0.75, cols.g.b * 0.75, 255) +cols.r_d = cols.r_d or Color(cols.r.r * 0.75, cols.r.g * 0.75, cols.r.b * 0.75, 255) + +cols.hvr = cols.hvr or Color(0,0,0, 50) +cols.dsb = cols.dsb or Color(255,255,255, 50) + +local toBg, toBgTime = cols.bg, 1 +local toG, toGTime = cols.g, 1 + +local function updateColors() + + local colBg, colG = cols.bg, cols.g + if colBg == toBg and colG == toG then + return hook.Remove('Think', 'octolib.skinColors') + end + + -- Update colBg + local ft = FrameTime() + colBg.r = octolib.math.lerp(colBg.r, toBg.r, ft / toBgTime, 0, 255) + colBg.g = octolib.math.lerp(colBg.g, toBg.g, ft / toBgTime, 0, 255) + colBg.b = octolib.math.lerp(colBg.b, toBg.b, ft / toBgTime, 0, 255) + local r, g, b = colBg.r, colBg.g, colBg.b + cols.bg = colBg + cols.bg95 = ColorAlpha(colBg, 241) + cols.bg60 = ColorAlpha(colBg, 150) + cols.bg50 = Color(r / 2, g / 2, b / 2, 255) + cols.bg_d = Color(r * 0.75, g * 0.75, b * 0.75, 255) + cols.bg_l = Color(r * 1.25, g * 1.25, b * 1.25, 255) + + -- Update colG + colG.r = octolib.math.lerp(colG.r, toG.r, ft / toGTime, 0, 255) + colG.g = octolib.math.lerp(colG.g, toG.g, ft / toGTime, 0, 255) + colG.b = octolib.math.lerp(colG.b, toG.b, ft / toGTime, 0, 255) + r, g, b = colG.r, colG.g, colG.b + cols.g = colG + cols.g_d = Color(r * 0.75, g * 0.75, b * 0.75) +end + +function octolib.changeSkinColor(bgColor, gColor, delta) + if not IsColor(bgColor) and not IsColor(gColor) then return end + + delta = delta or 1 + toBg, toBgTime = bgColor or toBg, delta + toG, toGTime = gColor or toG, delta + hook.Add('Think', 'octolib.skinColors', updateColors) +end + +surface.CreateFont('dbg-icons', { + font = 'FontAwesome', + extended = true, + size = 13, + weight = 400, +}) + +surface.CreateFont('dbg-icons2', { + font = 'FontAwesome', + extended = true, + size = 14, + weight = 400, +}) + +surface.CreateFont('dbg-icons3', { + font = 'FontAwesome', + extended = true, + size = 9, + weight = 400, +}) + +local SKIN = {} +SKIN.PrintName = 'DBG Derma Skin' +SKIN.Author = 'chelog' + +SKIN.fontFrame = 'DermaDefault' +SKIN.fontTab = 'DermaDefault' +SKIN.fontCategoryHeader = 'TabLarge' + +SKIN.GwenTexture = Material('gwenskin/GModDefault.png') +SKIN.Shadow = GWEN.CreateTextureBorder(448, 0, 31, 31, 8, 8, 8, 8) +SKIN.colOcto = cols + +SKIN.bg_color = Color(101, 100, 105, 255) +SKIN.bg_color_sleep = Color(70, 70, 70, 255) +SKIN.bg_color_dark = Color(55, 57, 61, 255) +SKIN.bg_color_bright = Color(220, 220, 220, 255) +SKIN.frame_border = Color(50, 50, 50, 255) + +SKIN.control_color = Color(120, 120, 120, 255) +SKIN.control_color_highlight = Color(150, 150, 150, 255) +SKIN.control_color_active = Color(110, 150, 250, 255) +SKIN.control_color_bright = Color(255, 200, 100, 255) +SKIN.control_color_dark = Color(100, 100, 100, 255) + +SKIN.bg_alt1 = Color(50, 50, 50, 255) +SKIN.bg_alt2 = Color(55, 55, 55, 255) + +SKIN.listview_hover = Color(70, 70, 70, 255) +SKIN.listview_selected = Color(100, 170, 220, 255) +SKIN.combobox_selected = SKIN.listview_selected + +SKIN.text_bright = Color(255, 255, 255, 255) +SKIN.text_normal = Color(180, 180, 180, 255) +SKIN.text_dark = Color(255, 255, 255, 255) +SKIN.text_highlight = Color(255, 20, 20, 255) + +SKIN.panel_transback = Color(255, 255, 255, 50) +SKIN.tooltip = Color(255, 245, 175, 255) +SKIN.colPropertySheet = Color(170, 170, 170, 255) + +SKIN.colTab = SKIN.colPropertySheet +SKIN.colTabInactive = Color(140, 140, 140, 255) +SKIN.colTabShadow = Color(0, 0, 0, 170) +SKIN.colTabText = Color(255, 255, 255, 255) +SKIN.colTabTextInactive = Color(0, 0, 0, 200) + +SKIN.colCollapsibleCategory = Color(255, 255, 255, 20) + +SKIN.colCategoryText = Color(255, 255, 255, 255) +SKIN.colCategoryTextInactive = Color(200, 200, 200, 255) + +SKIN.colNumberWangBG = Color(255, 240, 150, 255) +SKIN.colTextEntryBG = cols.bg +SKIN.colTextEntryBorder = Color(20, 20, 20, 255) +SKIN.colTextEntryText = Color(30, 30, 30, 255) +SKIN.colTextEntryTextHighlight = cols.o +SKIN.colTextEntryTextCursor = Color(30, 30, 30, 255) +SKIN.colTextEntryTextPlaceholder= Color(100, 100, 100, 100) + +SKIN.colMenuBG = cols.bg +SKIN.colMenuBorder = cols.o + +SKIN.colButtonText = Color(255, 255, 255, 255) +SKIN.colButtonTextDisabled = Color(255, 255, 255, 55) +SKIN.colButtonBorder = Color(20, 20, 20, 255) +SKIN.colButtonBorderHighlight = Color(255, 255, 255, 50) +SKIN.colButtonBorderShadow = Color(0, 0, 0, 100) + +SKIN.Colours = {} + +SKIN.Colours.Button = {} +SKIN.Colours.Button.Disabled = Color(0,0,0,100) +SKIN.Colours.Button.Down = Color(180,180,180,255) +SKIN.Colours.Button.Hover = Color(255,255,255,255) +SKIN.Colours.Button.Normal = Color(255,255,255,255) + +SKIN.Colours.Category = {} +SKIN.Colours.Category.Header = Color(255,255,255,255) +SKIN.Colours.Category.Header_Closed = Color(255,255,255,150) +SKIN.Colours.Category.Line = {} +SKIN.Colours.Category.Line.Button = Color(255,255,255,0) +SKIN.Colours.Category.Line.Button_Hover = Color(0,0,0,8) +SKIN.Colours.Category.Line.Button_Selected = Color(255,216,0,255) +SKIN.Colours.Category.Line.Text = Color(200,200,200,255) +SKIN.Colours.Category.Line.Text_Hover = Color(255,255,255,255) +SKIN.Colours.Category.Line.Text_Selected = Color(255,255,255,255) +SKIN.Colours.Category.LineAlt = {} +SKIN.Colours.Category.LineAlt.Button = Color(0,0,0,26) +SKIN.Colours.Category.LineAlt.Button_Hover = Color(0,0,0,32) +SKIN.Colours.Category.LineAlt.Button_Selected = Color(255,216,0,255) +SKIN.Colours.Category.LineAlt.Text = Color(200,200,200,255) +SKIN.Colours.Category.LineAlt.Text_Hover = Color(255,255,255,255) +SKIN.Colours.Category.LineAlt.Text_Selected = Color(255,255,255,255) + +SKIN.Colours.Label = {} +SKIN.Colours.Label.Bright = Color(255,255,255,255) +SKIN.Colours.Label.Dark = Color(255,255,255,255) +SKIN.Colours.Label.Default = Color(255,255,255,255) +SKIN.Colours.Label.Highlight = Color(255,0,0,255) + +SKIN.Colours.Properties = {} +SKIN.Colours.Properties.Border = cols.bg +SKIN.Colours.Properties.Column_Hover = Color(118,199,255,59) +SKIN.Colours.Properties.Column_Normal = Color(255,255,255,0) +SKIN.Colours.Properties.Column_Selected = Color(118,199,255,255) +SKIN.Colours.Properties.Label_Hover = Color(50,50,50,255) +SKIN.Colours.Properties.Label_Normal = Color(0,0,0,255) +SKIN.Colours.Properties.Label_Selected = Color(0,0,0,255) +SKIN.Colours.Properties.Line_Hover = Color(156,156,156,255) +SKIN.Colours.Properties.Line_Normal = Color(156,156,156,255) +SKIN.Colours.Properties.Line_Selected = Color(156,156,156,255) +SKIN.Colours.Properties.Title = Color(255,255,255,255) + +SKIN.Colours.Tab = {} +SKIN.Colours.Tab.Active = {} +SKIN.Colours.Tab.Active.Disabled = Color(233,233,233,204) +SKIN.Colours.Tab.Active.Down = Color(255,255,255,255) +SKIN.Colours.Tab.Active.Hover = Color(255,255,255,255) +SKIN.Colours.Tab.Active.Normal = Color(255,255,255,255) +SKIN.Colours.Tab.Inactive = {} +SKIN.Colours.Tab.Inactive.Disabled = Color(210,210,210,204) +SKIN.Colours.Tab.Inactive.Down = Color(255,255,255,255) +SKIN.Colours.Tab.Inactive.Hover = Color(249,249,249,153) +SKIN.Colours.Tab.Inactive.Normal = Color(255,255,255,102) + +SKIN.Colours.TooltipText = Color(255,255,255,255) + +SKIN.Colours.Tree = {} +SKIN.Colours.Tree.Hover = cols.g +SKIN.Colours.Tree.Normal = color_white +SKIN.Colours.Tree.Lines = Color(255,255,255, 35) +SKIN.Colours.Tree.Selected = color_white + +SKIN.Colours.Window = {} +SKIN.Colours.Window.TitleActive = Color(255,255,255,204) +SKIN.Colours.Window.TitleInactive = Color(255,255,255,92) + +SKIN.Colours.TooltipText = Color(255,255,255,255) + +local function shadow(x, y, w, h) + + SKIN.Shadow(x, y, w, h) + +end + +function SKIN:PaintPanel(pnl, w, h) + + if not pnl.m_bBackground then return end + + draw.RoundedBox(4, 0, 0, w, h, cols.bg_d) + draw.RoundedBox(4, 1, 1, w-2, h-2, cols.bg60) + +end + +function SKIN:PaintFrame(pnl, w, h) + + -- if pnl.m_bPaintShadow then + DisableClipping( true ) + shadow( -4, -4, w+10, h+10 ) + DisableClipping( false ) + -- end + + draw.RoundedBox(4, 0, 0, w, h, cols.bg) + draw.RoundedBoxEx(4, 0, 0, w, 24, Color(0,0,0, 80), true, true, false, false) + +end + +function SKIN:PaintTextEntry(pnl, w, h) + + if pnl.m_bBackground then + if pnl.PaintOffset then + surface.DisableClipping(true) + if pnl:GetDisabled() then + draw.RoundedBox(4, -pnl.PaintOffset, -pnl.PaintOffset, w + pnl.PaintOffset*2, h + pnl.PaintOffset*2, Color(255,255,255, 100)) + elseif pnl:HasFocus() then + draw.RoundedBox(4, -pnl.PaintOffset, -pnl.PaintOffset, w + pnl.PaintOffset*2, h + pnl.PaintOffset*2, cols.y) + else + draw.RoundedBox(4, -pnl.PaintOffset, -pnl.PaintOffset, w + pnl.PaintOffset*2, h + pnl.PaintOffset*2, color_white) + end + surface.DisableClipping(false) + else + if pnl:GetDisabled() then + draw.RoundedBox(4, 0, 0, w, h, Color(255,255,255, 100)) + elseif pnl:HasFocus() then + draw.RoundedBox(4, 0, 0, w, h, cols.y) + else + draw.RoundedBox(4, 0, 0, w, h, color_white) + end + end + end + + if (pnl.GetPlaceholderText and pnl.GetPlaceholderColor and pnl:GetPlaceholderText() and pnl:GetPlaceholderText():Trim() ~= "" and pnl:GetPlaceholderColor() and (not pnl:GetText() or pnl:GetText() == "")) then + local oldText = pnl:GetText() + local str = pnl:GetPlaceholderText() + if str:StartWith("#") then str = language.GetPhrase(str:sub(2)) end + + pnl:SetText(str) + pnl:DrawTextEntryText(pnl:GetPlaceholderColor(), pnl:GetHighlightColor(), pnl:GetCursorColor()) + pnl:SetText(oldText) + + return + end + + pnl:DrawTextEntryText(pnl:GetTextColor(), pnl:GetHighlightColor(), pnl:GetCursorColor()) + +end + +function SKIN:PaintButton(pnl, w, h) + + if not pnl.m_bBackground then return end + + local off = h > 20 and 2 or 1 + if pnl.Depressed then + draw.RoundedBox(4, 0, off, w, h-off, cols.g) + draw.RoundedBox(4, 0, off, w, h-off, cols.hvr) + else + draw.RoundedBox(4, 0, 0, w, h, cols.g_d) + draw.RoundedBox(4, 0, 0, w, h-off, cols.g) + if pnl.Disabled then + draw.RoundedBox(4, 0, 0, w, h, cols.dsb) + elseif pnl.Hovered then + draw.RoundedBox(4, 0, 0, w, h, cols.hvr) + end + end + +end + +function SKIN:PaintTree( pnl, w, h ) + + if not pnl.m_bBackground then return end + + draw.RoundedBox(4, 0, 0, w, h, Color(0,0,0, 80)) + draw.RoundedBox(4, 1, 1, w-2, h-2, cols.bg60) + +end + +function SKIN:PaintSelection( pnl, w, h ) + + draw.RoundedBox(4, 0, 0, w, h, cols.o) + +end + +function SKIN:PaintTreeNodeButton( pnl, w, h ) + + if not pnl.m_bSelected then return end + + local w, _ = pnl:GetTextSize() + draw.RoundedBox(4, 38, 2, w + 6, h-3, cols.o) + +end + +function SKIN:PaintTreeNode( pnl, w, h ) + + if not pnl.m_bDrawLines then return end + + surface.SetDrawColor( self.Colours.Tree.Lines ) + if ( pnl.m_bLastChild ) then + surface.DrawRect( 9, 0, 1, 8 ) + surface.DrawRect( 10, 7, 8, 1 ) + else + surface.DrawRect( 9, 0, 1, h ) + surface.DrawRect( 10, 7, 8, 1 ) + end + +end + +function SKIN:PaintListViewLine( pnl, w, h ) + + if ( pnl:IsSelected() ) then + draw.RoundedBox(4, 0, 0, w, h, cols.o) + elseif ( pnl.Hovered ) then + draw.RoundedBox(4, 0, 0, w, h, cols.hvr) + elseif ( pnl.m_bAlt ) then + draw.RoundedBox(4, 0, 0, w, h, Color(0,0,0, 35)) + end + +end + +function SKIN:PaintListView( pnl, w, h ) + + if not pnl.m_bBackground then return end + -- draw.RoundedBox(4, 0, 0, w, h, cols.hvr) + +end + +function SKIN:PaintCategoryList( panel, w, h ) + + -- self.tex.CategoryList.Outer( 0, 0, w, h ) + +end + +function SKIN:PaintCategoryButton( panel, w, h ) + + if ( panel.AltLine ) then + + if ( panel.Depressed or panel.m_bSelected ) then draw.RoundedBox(4, 0, 0, w, h, cols.o) + elseif ( panel.Hovered ) then draw.RoundedBox(4, 0, 0, w, h, cols.hvr) + else draw.RoundedBox(4, 0, 0, w, h, Color(255,255,255, 1)) end + + else + + if ( panel.Depressed or panel.m_bSelected ) then draw.RoundedBox(4, 0, 0, w, h, cols.o) + elseif ( panel.Hovered ) then draw.RoundedBox(4, 0, 0, w, h, cols.hvr) + end + + end + +end + +function SKIN:PaintSliderKnob( pnl, w, h ) + + if pnl:GetDisabled() then + return draw.SimpleText(utf8.char(0xf111), 'dbg-icons', w/2, h/2, cols.dsb, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + elseif pnl.Depressed or pnl.Hovered then + return draw.SimpleText(utf8.char(0xf111), 'dbg-icons', w/2, h/2, cols.g, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText(utf8.char(0xf111), 'dbg-icons', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + +end + +function SKIN:PaintNumSlider( pnl, w, h ) + + draw.RoundedBox(1, 8, h/2-1, w-15, 2, Color(255,255,255, 25)) + if not pnl.m_TextColorSet then + if pnl:GetParent().TextArea then + pnl:GetParent().TextArea:SetTextColor(Color(255,255,255)) + end + pnl.m_TextColorSet = true + end + +end + +function SKIN:PaintTab(pnl, w, h) + + if pnl:IsActive() then + draw.RoundedBoxEx(4, 2, 0, w-5, h, cols.bg, true, true, false, false) + else + -- draw.RoundedBoxEx(4, 2, 0, w-5, h, cols.bg, true, true, false, false) + end + +end + +function SKIN:PaintPropertySheet(pnl, w, h) + + draw.RoundedBox(4, 0, 2, w, h-2, cols.bg) + draw.RoundedBoxEx(4, 0, 2, w, 18, Color(0,0,0, 220), true, true, false, false) + +end + +function SKIN:PaintCollapsibleCategory(pnl, w, h) + + draw.RoundedBox(4, 0, 5, w, h-5, cols.bg) + draw.RoundedBox(4, 0, 0, w, 20, cols.g) + +end + +function SKIN:PaintWindowCloseButton(pnl, w, h) + + if pnl.Disabled then return end + + if pnl.Depressed then + draw.RoundedBox(8, w/2-8, h/2-7, 16, 16, cols.r) + if pnl.Hovered then + draw.SimpleText(utf8.char(0xf00d), 'dbg-icons', w / 2, h / 2, Color(0,0,0, 120), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + else + draw.RoundedBox(8, w/2-8, h/2-7, 16, 16, cols.r_d) + draw.RoundedBox(8, w/2-8, h/2-8, 16, 16, cols.r) + if pnl.Hovered then + draw.SimpleText(utf8.char(0xf00d), 'dbg-icons', w / 2, h / 2-1, Color(0,0,0, 120), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + +end + +function SKIN:PaintTooltip( pnl, w, h ) + + surface.DisableClipping(true) + + shadow(-7, -4, w + 14, h + 8) + draw.RoundedBox(4, -3, 0, w + 6, h, cols.o) + draw.NoTexture() + surface.DrawPoly({ + {x = w/2 - 5, y = h}, + {x = w/2 + 5, y = h}, + {x = w/2, y = h + 5}, + }) + + surface.DisableClipping(false) + +end + +function SKIN:PaintVScrollBar( panel, w, h ) + + draw.RoundedBox(4, w/2-2, w+3, 4, h-6 - w*2, Color(255,255,255, 25)) + +end + +function SKIN:PaintScrollBarGrip( panel, w, h ) + + draw.RoundedBox(4, w/2-4, 0, 8, h, cols.g) + + if ( panel:GetDisabled() ) then + return draw.RoundedBox(4, w/2-4, 0, 8, h, cols.dsb) + end + + if ( panel.Depressed ) then + return draw.RoundedBox(4, w/2-4, 0, 8, h, cols.hvr) + end + + if ( panel.Hovered ) then + return draw.RoundedBox(4, w/2-4, 0, 8, h, cols.hvr) + end + +end + +function SKIN:PaintButtonDown( pnl, w, h ) + + if not pnl.m_bBackground or pnl:GetDisabled() then return end + + local col = Color(255,255,255, 25) + if pnl.Hovered or pnl.Depressed or pnl:IsSelected() then + col.a = 255 + end + + draw.SimpleText(utf8.char(0xf078), 'dbg-icons2', w/2, h/2-1, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintButtonUp( pnl, w, h ) + + if not pnl.m_bBackground or pnl:GetDisabled() then return end + + local col = Color(255,255,255, 25) + if pnl.Hovered or pnl.Depressed or pnl:IsSelected() then + col.a = 255 + end + + draw.SimpleText(utf8.char(0xf077), 'dbg-icons2', w/2, h/2-1, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintButtonLeft( pnl, w, h ) + + if not pnl.m_bBackground or pnl:GetDisabled() then return end + + local col = Color(255,255,255, 25) + if pnl.Hovered or pnl.Depressed or pnl:IsSelected() then + col.a = 255 + end + + draw.SimpleText(utf8.char(0xf053), 'dbg-icons2', w/2, h/2-1, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintButtonRight( pnl, w, h ) + + if not pnl.m_bBackground or pnl:GetDisabled() then return end + + local col = Color(255,255,255, 25) + if pnl.Hovered or pnl.Depressed or pnl:IsSelected() then + col.a = 255 + end + + draw.SimpleText(utf8.char(0xf054), 'dbg-icons2', w/2, h/2-1, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintMenu( pnl, w, h ) + + surface.DisableClipping(true) + shadow(-5, -5, w + 10, h + 10) + surface.DisableClipping(false) + + draw.RoundedBox(4, 0, 0, w, h, cols.bg_l) + draw.RoundedBox(4, 1, 1, w-2, h-2, cols.bg) + +end + +function SKIN:PaintMenuSpacer( pnl, w, h ) + + surface.SetDrawColor(cols.bg_l) + surface.DrawRect(0, 0, w, h) + +end + +function SKIN:PaintMenuOption( pnl, w, h ) + + if pnl.m_bBackground and (pnl.Hovered or pnl.Highlight) then + draw.RoundedBox(4, 0, 0, w, h, cols.o) + end + + if pnl:GetChecked() then + draw.SimpleText(utf8.char(0xf00c), 'dbg-icons2', 16, h/2, Color(255,255,255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + -- draw.RoundedBox(4, 0, 0, w, h, cols.o) + end + + if not pnl.m_TextColorSet then + pnl:SetTextColor(Color(255,255,255)) + pnl.m_TextColorSet = true + end + +end + +function SKIN:PaintMenuRightArrow( pnl, w, h ) + + draw.SimpleText(utf8.char(0xf054), 'dbg-icons3', w-5, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintMenuBar( pnl, w, h ) + + draw.RoundedBox(0, 0, 0, w, h, cols.bg) + +end + +function SKIN:PaintExpandButton( pnl, w, h ) + + if pnl:GetExpanded() then + draw.SimpleText(utf8.char(0xf111), 'dbg-icons', w/2, h/2, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(utf8.char(0xf056), 'dbg-icons', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText(utf8.char(0xf111), 'dbg-icons', w/2, h/2, cols.bg, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(utf8.char(0xf055), 'dbg-icons', w/2, h/2, Color(255,255,255, 80), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + +end + +function SKIN:PaintComboDownArrow( pnl, w, h ) + + draw.SimpleText(utf8.char(0xf078), 'dbg-icons', w/2, h/2, Color(0,0,0, 120), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintComboBox( pnl, w, h ) + + if pnl:GetDisabled() then + draw.RoundedBox(4, 0, 0, w, h, Color(255,255,255, 100)) + elseif pnl:HasFocus() then + draw.RoundedBox(4, 0, 0, w, h, cols.y) + elseif pnl.Depressed or pnl:IsMenuOpen() then + draw.RoundedBox(4, 0, 0, w, h, cols.y) + else + draw.RoundedBox(4, 0, 0, w, h, color_white) + end + + if not pnl.m_TextColorSet then + pnl:SetTextColor(Color(30,30,30)) + pnl.m_TextColorSet = true + end + +end + +function SKIN:PaintCheckBox( pnl, w, h ) + + local col = Color(255,255,255, 255) + if pnl:GetDisabled() then + col.a = 100 + end + + if pnl:GetChecked() then + draw.SimpleText(utf8.char(0xf14a), 'dbg-icons2', w/2, h/2-1, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText(utf8.char(0xf0c8), 'dbg-icons2', w/2, h/2-1, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + +end + +function SKIN:PaintNumberUp( pnl, w, h ) + + draw.SimpleText(utf8.char(0xf077), 'dbg-icons3', w/2, h/2, Color(30,30,30), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintNumberDown( pnl, w, h ) + + draw.SimpleText(utf8.char(0xf078), 'dbg-icons3', w/2, h/2, Color(30,30,30), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + +end + +function SKIN:PaintWindowMaximizeButton(pnl, w, h) end +function SKIN:PaintWindowMinimizeButton(pnl, w, h) end + +function SKIN:PaintProgress(pnl, w, h) + + local y = h / 2 - 9 + draw.RoundedBox(8, 0, y, w, 18, Color(0,0,0, 65)) + local fr = pnl:GetFraction() + if fr > 0 then + draw.RoundedBox(8, 1, y + 1, (w-18) * fr + 16, 16, cols.g) + end + +end + +local skin = derma.GetNamedSkin('octolib') +if skin then + for k, v in pairs(SKIN) do skin[k] = v end +else + derma.DefineSkin('octolib', 'DBG skin', SKIN) +end + +hook.Add('ForceDermaSkin', 'dbg-skin', function() + + return 'octolib' + +end) diff --git a/octolib/addon/lua/octolib/modules/_client/timer-message.lua b/octolib/addon/lua/octolib/modules/_client/timer-message.lua new file mode 100644 index 0000000..e1519f8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_client/timer-message.lua @@ -0,0 +1,52 @@ +surface.CreateFont('octolib.timer.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +surface.CreateFont('octolib.timer.normal-sh', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, + blursize = 3, +}) + +local message, timeFinish +netstream.Hook('octolib.timer', function(text, delay) + message = text + timeFinish = CurTime() + delay +end) + +local function niceTime(time) + + local m, s + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format('%02i:%02i', m, s) + +end + +hook.Add('HUDPaint', 'octolib.timer', function() + + if not timeFinish then return end + + local timeLeft = timeFinish - CurTime() + local text = message:format(niceTime(math.max(timeLeft, 0))) + + if timeLeft < 0 then + surface.SetAlphaMultiplier(timeLeft + 1) + end + + draw.DrawText(text, 'octolib.timer.normal-sh', 10, 5, color_black) + draw.DrawText(text, 'octolib.timer.normal', 10, 5, color_white) + + surface.SetAlphaMultiplier(1) + + if timeLeft <= -1 then + text, timeFinish = nil + end + +end) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/_server/database.lua b/octolib/addon/lua/octolib/modules/_server/database.lua new file mode 100644 index 0000000..785bb14 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/database.lua @@ -0,0 +1,89 @@ +require('mysqloo') + +function octolib.reconnectDB() + + octolib.msg('DB: Connecting...') + + local config = CFG.db + local db = mysqloo.CreateDatabase(config.host, config.user, config.pass, config.main, config.port, config.socket) + function db:onConnected() + octolib.msg('DB: Connected.') + octolib.db = db + -- octolib.db:RunQuery('SET NAMES utf8') + + timer.Create('octolib.db.heartbeat', 30, 0, function() + local status = octolib.db:status() + if status ~= mysqloo.DATABASE_CONNECTED and status ~= mysqloo.DATABASE_CONNECTED then + octolib.reconnectDB() + timer.Remove('octolib.db.heartbeat') + end + end) + hook.Run('octolib.db.init', db) + end + + function db:onConnectionFailed(data) + octolib.msg('DB: Connection failed: ') + print(data) + + octolib.msg('DB: Reconnecting in 30 seconds...') + timer.Simple(30, octolib.reconnectDB) + + timer.Remove('octolib.db.heartbeat') + end + +end +octolib.reconnectDB() + +hook.Add('octolib.db.init', 'octolib.db.tick', function() + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.main .. [[.octolib_queue ( + serverID VARCHAR(30) NOT NULL, + event VARCHAR(30) NOT NULL, + data TEXT + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + +end) + +-- +-- DB SOCKET (kinda) +-- + +function octolib.sendCmd(serverID, event, data) + + octolib.db:PrepareQuery('insert into ' .. CFG.db.main .. '.octolib_queue(serverID, event, data) values(?, ?, ?)', { serverID, event, util.TableToJSON(data or {}) }) + +end + +local servers = CFG.serversList or {'dbg', 'dbg2'} +function octolib.sendCmdToOthers(event, data) + for _,serverID in ipairs(servers) do + if serverID ~= CFG.serverID then + octolib.sendCmd(serverID, event, data) + end + end +end + +if CFG.dbTick then + timer.Create('octolib.db.tick', CFG.dbTickTime, 0, function() + if not octolib.db or octolib.db:status() ~= mysqloo.DATABASE_CONNECTED then return end + octolib.db:PrepareQuery('select event, data from ' .. CFG.db.main .. '.octolib_queue where serverID = ?', { CFG.serverID }, function(q, st, res) + if st and istable(res) then + if #res > 0 then + for i, v in ipairs(res) do + if v.event then + hook.Run('octolib.event:' .. v.event, isstring(v.data) and util.JSONToTable(v.data) or {}) + if CFG.dev then + octolib.dmsg('DB hook: ' .. v.event) + end + end + end + octolib.db:PrepareQuery('delete from ' .. CFG.db.main .. '.octolib_queue where serverID = ?', { CFG.serverID }) + end + else + ErrorNoHalt('DB ERROR: ' .. res) + end + end) + end) +end diff --git a/octolib/addon/lua/octolib/modules/_server/dbvars.lua b/octolib/addon/lua/octolib/modules/_server/dbvars.lua new file mode 100644 index 0000000..c153b6d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/dbvars.lua @@ -0,0 +1,210 @@ +-- Namespace: octolib + +--[[ + Group: dbvars + Save data between sessions and server restarts + + It is stored in database according to CFG.db.name, so dbvars will + be synced across servers using same database +]] + +hook.Add('octolib.db.init', 'octolib.dbvars', function() + + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS octolib_vars ( + steamID VARCHAR(30) NOT NULL, + data TEXT, + PRIMARY KEY (steamID) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) + +end) + +local function save(id, tbl) + + return util.Promise(function(resolve) + local data = pon.encode(tbl) + if data then + octolib.db:PrepareQuery([[ + REPLACE INTO octolib_vars(steamID, data) + VALUES(?, ?) + ]], { id, data }, resolve) + end + end) + +end + +local asyncSetQueue = octolib.queue.create(function(data, done) + octolib.db:PrepareQuery([[ + SELECT * FROM octolib_vars + WHERE steamID = ? + ]], { data.id }, function(q, st, res) + local res = st and res and res[1] + local stored = pon.decode(res and res.data or '[}') or {} + if data.name then + stored[data.name] = data.val + else + stored = data.val + end + + save(data.id, stored) + :Finally(function() + done() + data.resolve() + end) + end) +end) + +hook.Add('PlayerInitialSpawn', 'octolib.dbvars', function(ply) + + local steamID = ply:SteamID() + octolib.db:PrepareQuery([[ + SELECT * FROM octolib_vars + WHERE steamID = ? + ]], { steamID }, function(q, st, res) + if not IsValid(ply) then return end + local res = st and res and res[1] + if res and res.data then + local data = pon.decode(res.data) or {} + ply.dbvars = data + else + ply.dbvars = {} + save(steamID, {}) + + -- TODO: put in a hook + if CFG.webhooks.cheats then + octoservices:post('/discord/webhook/' .. CFG.webhooks.cheats, { + username = GetHostName(), + embeds = {{ + title = 'Первый вход на сервер', + fields = {{ + name = L.player, + value = ply:Name() .. '\n[' .. ply:SteamID() .. '](' .. 'https://steamcommunity.com/profiles/' .. ply:SteamID64() .. ')', + }}, + }}, + }) + end + end + hook.Run('octolib.dbvars-loaded', ply) + end) + +end) + +--[[ + Function: setDBVar + Sets DBVar and saves it to database + + Arguments: + id - Unique object ID to store data under + name - Property id + val - value to store in the property +]] +function octolib.setDBVar(id, name, val) + + return util.Promise(function(resolve) + local ply = player.GetBySteamID(id) + if IsValid(ply) then + if name then + ply:SetDBVar(name, val) + else + ply.dbvars = val + save(ply:SteamID(), ply.dbvars) + end + return resolve() + end + + asyncSetQueue:Add({ + id = id, + name = name, + val = val, + resolve = resolve, + }) + end) + +end + +--[[ + Function: getDBVar + Gets DBVar from database + + Arguments: + id - Unique object ID + name = nil - Property id, leave nil if you want to get whole object + default = nil - Value that will be returned if there's no such entry in database. + Rejection will not be called if this argument provided. + suppressOnline = nil - Update dbvars from database if player is online + + Returns: + () - Property value or whole data object +]] +function octolib.getDBVar(id, name, default, suppressOnline) + + return util.Promise(function(resolve, reject) + local ply = player.GetBySteamID(id) + if IsValid(ply) and not suppressOnline then + resolve((name and ply:GetDBVar(name)) or (not name and ply.dbvars) or default ~= nil and default or nil) + return + end + + octolib.db:PrepareQuery([[ + SELECT * FROM octolib_vars + WHERE steamID = ? + ]], { id }, function(q, st, res) + local res = st and res and res[1] + if res and res.data then + local data = pon.decode(res.data) or {} + if suppressOnline and IsValid(ply) then ply.dbvars = data end + if not name then + resolve(data) + elseif data[name] then + resolve(data[name], data) + elseif default then + resolve(default ~= nil and default or nil, data) + else + reject('Could not find data under supplied key for this ID') + end + elseif default then + resolve(default) + else + reject('Data with this ID not found') + end + end) + end) + +end + +local Player = FindMetaTable 'Player' + +-- Class: Player + +--[[ + Function: SetDBVar + Alias of where id is player's SteamID +]] +function Player:SetDBVar(name, val) + + if not name or isfunction(val) then return end + + self.dbvars = self.dbvars or {} + + if self.dbvars[name] == val and not istable(val) then return end + self.dbvars[name] = val + + save(self:SteamID(), self.dbvars) + +end + +--[[ + Function: GetDBVar + Alias of where id is player's SteamID, unlike + original this is syncronous and uses cached data making it preferable + for player's data retrieval + + Returns: + - Property value or whole object +]] +function Player:GetDBVar(name, backup) + + return self.dbvars and self.dbvars[name] or backup + +end diff --git a/octolib/addon/lua/octolib/modules/_server/errors.lua b/octolib/addon/lua/octolib/modules/_server/errors.lua new file mode 100644 index 0000000..61c1714 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/errors.lua @@ -0,0 +1,71 @@ +if CFG.disabledModules.errors then return end + +require 'luaerror' + +luaerror.EnableRuntimeDetour(true) +luaerror.EnableCompiletimeDetour(true) +luaerror.EnableClientDetour(true) + +local function formatError(fullerror, stack) + return table.concat({ fullerror, unpack(octolib.table.mapSequential(stack, function(e, i) + return ('%s. %s:%s'):format(i, e.short_src, e.currentline) + end)) }, '\n ') +end + +hook.Add('LuaError', 'octolib.errors', function(isruntime, fullerror, sourcefile, sourceline, errorstr, stack) + hook.Run('octolib.error', formatError(fullerror, stack)) +end) + +hook.Add('ClientLuaError', 'octolib.errors', function(player, fullerror, sourcefile, sourceline, errorstr, stack) + hook.Run('octolib.error', formatError(fullerror, stack), player) +end) + +-- local errors = {} +-- if errors[msg] then +-- errors[msg].count = errors[msg].count + 1 +-- errors[msg].last = os.time() +-- else +-- local txt, cl = msg:sub(2), false +-- if lastmsg:find('|STEAM_') then +-- txt = lastmsg .. txt +-- cl = true +-- for path in string.gmatch(txt, '%-%s.-%.lua') do +-- if not file.Exists(path:sub(3), 'GAME') then return end +-- end +-- end + +-- errors[msg] = { +-- txt = txt, +-- client = cl, +-- count = 1, +-- last = os.time(), +-- } +-- end + +-- timer.Create('octolib-errors', 60, 0, function() + +-- local date = os.date('*t', os.time()) +-- if date.hour == 4 and date.min == 55 then +-- local i = 0 +-- for k, v in pairs(errors) do +-- timer.Simple(i * 2, function() +-- octoservices:post('/discord/webhook/' .. CFG.webhooks.error, { +-- content = '<@153885767230291968>', +-- embeds = {{ +-- title = v.client and 'Клиентсайд ошибка' or 'Серверсайд ошибка', +-- description = v.txt, +-- fields = { +-- {name = 'Сервер', value = string.format('%s (%s)', GetHostName(), game.GetIPAddress())}, +-- {name = 'Раз за день', value = tostring(v.count), inline = true}, +-- {name = 'Последняя', value = os.date('%d/%m/%Y в %H:%M', v.last), inline = true}, +-- }, +-- }}, +-- }) +-- end) +-- i = i + 1 +-- end + +-- errors = {} +-- end + +-- end) diff --git a/octolib/addon/lua/octolib/modules/_server/resource.lua b/octolib/addon/lua/octolib/modules/_server/resource.lua new file mode 100644 index 0000000..e01b9a9 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/resource.lua @@ -0,0 +1,9 @@ +RunConsoleCommand('sv_downloadurl', 'https://cdn.octothorp.team/gmod/') + +hook.Add('InitPostEntity', 'octolib.content', function() + + if file.Exists('config/content.lua', 'LUA') then + octolib.server('config/content') + end + +end) diff --git a/octolib/addon/lua/octolib/modules/_server/rewards.lua b/octolib/addon/lua/octolib/modules/_server/rewards.lua new file mode 100644 index 0000000..eeb8cfb --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/rewards.lua @@ -0,0 +1,163 @@ +if CFG.disabledModules.rewards then return end + +hook.Add('octolib.db.init', 'octolib.rewards', function() + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS rewards_forum ( + steamID VARCHAR(30) NOT NULL, + received DATE NOT NULL, + PRIMARY KEY (steamID) + ) + ]]) +end) + +local postUrl = 'https://vk.com/octoteam?w=wall-155454901_1667' + +local function checkForumReward(ply, handler) + + local family = octolib.table.map(ply:GetDBVar('family', {}), function(sid) + return '\'' .. octolib.db:escape(sid) .. '\'' + end) + family[#family + 1] = '\'' .. octolib.db:escape(ply:SteamID()) .. '\'' + + octolib.db:RunQuery('SELECT steamID FROM rewards_forum WHERE steamID IN (' .. table.concat(family, ',') .. ')', function(q, st, res) + handler(istable(res) and res[1]) + end) +end + +local function checkVkPostPinned(ply, handler) + octoservices:get('/vk/pinned/' .. util.SteamIDTo64(ply:SteamID())):Then(function(resp) + local data = resp and resp.data + + if not data or resp.code == 500 then return handler(false, 'internal') end + if resp.code == 401 or resp.code == 404 then return handler(false, 'not_attached') end + if resp.code == 400 then return handler(false, data.error) end + return handler(data.pinned, not data.pinned and 'wrong_post' or nil) + + end):Catch(function(err) + ErrorNoHalt(err) + handler(false, err) + end) +end + +local function notifyAboutRewards(ply, forumID, forumRewardClaimed, vkPinned, vkError) + + if not forumRewardClaimed then + ply:Notify('warning', ('Форум: %sприкреплен, награда не получена\n'):format(forumID and '' or 'не '), + 'Чтобы получить награду за регистрацию на форуме:\n', + '1. Зарегистрируйся на форуме и подтверди электронную почту: ', {'https://forum.octothorp.team/'}, '\n', + '2. Авторизуйся на сайте и зайди в Настройки: ', {'https://octothorp.team/'}, '\n', + '3. Выбери вкладку "Аккаунт" и нажми "Прикрепить форум"\n', + '4. Вставь ссылку на свой аккаунт и нажми "Готово"\n', + '5. Вернись в игру и введи в чат команду /forum') + else ply:Notify('hint', ('Форум: %sприкреплен, награда получена'):format(forumID and '' or 'не ')) end + + if not vkPinned then + local title = ('VK: %sприкреплен, награда неактивна (обновляется не чаще 1 раза в 30 минут)\n'):format(vkError == 'not_attached' and 'не ' or '') + local msg + if vkError == 'internal' then + msg = { title, 'Мы не можем проверить, привязан ли твой ВК, из-за внутренней технической ошибки :(' } + elseif vkError == 'not_attached' then + msg = {title, 'Чтобы получать бесплатную фишку за каждые полчаса игры, выполни эту инструкцию: ', {'http://vk.cc/86kQOA'}} + elseif vkError == 15 or vkError == 30 then + msg = {title, 'Сделай страницу в VK открытой, чтобы мы смогли проверить закрепленный пост. Подробности в этой инструкции: ', {'http://vk.cc/86kQOA'}} + else + msg = {title, 'Чтобы получать бесплатную фишку за каждые полчаса игры, сделай репост этой записи себе на страницу и закрепи ее: ', {postUrl}} + end + ply:Notify('warning', unpack(msg)) + else ply:Notify('hint', 'VK: прикреплен, награда активна') end +end + +octolib.rewardCommands = {} + +function octolib.rewardCommands.forum(ply) + + local id = ply:SteamID() + octolib.func.chain({ + function(nxt) + checkForumReward(ply, nxt) + end, + function(nxt, claimed) + if not IsValid(ply) then return end + if claimed then + return ply:Notify('warning', L.already_reward) + end + octoservices:post('/users/search', { steamID = id }):Then(nxt):Catch(ErrorNoHalt) + end, + function(nxt, resp) + if not IsValid(ply) then return end + local user = resp.data and resp.data[1] or {} + if not user.forumID then + return ply:Notify('warning', 'Для этого тебе нужно прикрепить аккаунт форума во вкладке "Аккаунт" настроек на сайте https://octothorp.team/') + end + + CFG.forumRewardHandler(ply, user, function() + octolib.db:PrepareQuery([[INSERT INTO rewards_forum (steamID, received) VALUES (?, NOW())]], { id }) + end) + end, + }) + +end + +function octolib.rewardCommands.rewards(ply) + + ply:Notify('Собираем данные, секундочку...') + local forumID, forumRewardClaimed + octolib.func.chain({ + function(nxt) + octoservices:post('/users/search', { steamID = ply:SteamID() }):Then(nxt):Catch(ErrorNoHalt) + end, + function(nxt, resp) + if not IsValid(ply) then return end + local user = resp and resp.data and resp.data[1] + if not user then + return ply:Notify('warning', 'Чтобы проверить свои награды, авторизуйся на сайте https://octothorp.team/') + end + forumID = user.forumID + checkForumReward(ply, nxt) + end, + function(nxt, claimed) + if not IsValid(ply) then return end + forumRewardClaimed = claimed + checkVkPostPinned(ply, nxt) + end, + function(nxt, pinned, error) + if IsValid(ply) then notifyAboutRewards(ply, forumID, forumRewardClaimed, pinned, error) end + end, + }) + +end + +octolib.ptime.createTimer('octolib.rewards', 30, function(ply) + checkVkPostPinned(ply, function(pinned, error) + if not IsValid(ply) then return end + if pinned then + ply:osAddMoney(1) + ply:Notify('hint', L.vk_salary) + end + end) +end) + +hook.Add('PlayerFinishedLoading', 'octolib.rewards', function(ply) + + local forumRewardClaimed + octolib.func.chain({ + function(nxt) + timer.Simple(120, nxt) + end, + function(nxt) + if not IsValid(ply) then return end + checkForumReward(ply, nxt) + end, + function(nxt, claimed) + if not IsValid(ply) then return end + forumRewardClaimed = true + checkVkPostPinned(ply, nxt) + end, + function(nxt, vkPostPinned) + if not (forumRewardClaimed and vkPostPinned) and IsValid(ply) then + ply:Notify('ooc', L.bonus_hint) + end + end, + }) + +end) diff --git a/octolib/addon/lua/octolib/modules/_server/socket.lua b/octolib/addon/lua/octolib/modules/_server/socket.lua new file mode 100644 index 0000000..17edbd8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/socket.lua @@ -0,0 +1,162 @@ +require('gwsockets') + +local requestTimeout = 15 +local MSG_REQUEST = 0 +local MSG_REPLY = 1 + +octolib.metaSocket = octolib.metaSocket or {} +local SocketClient = octolib.metaSocket +SocketClient = SocketClient or {} +SocketClient.__index = SocketClient + +function octolib.socket(url, key) + + local inst = GWSockets.createWebSocket(url) + local client = setmetatable({ + instance = inst, + url = url, + key = key or '', + handlers = {}, + pending = {}, + connected = false, + nextReqID = 0, + }, SocketClient) + + function inst:onMessage(data) client:HandleMessage(data) end + function inst:onConnected() + self.connected = false + self:write(key or '') + end + function inst:onError(data) + self.connected = false + client:HandleError(data) + end + function inst:onDisconnected() + self.connected = false + client:OnDisconnect() + end + client:Listen('_hb', function(reply) reply(true) end) + + return client + +end + +function SocketClient:Connect() + + self.instance:open() + +end + +function SocketClient:Disconnect(force) + + if force then + self.instance:closeNow() + else + self.instance:close() + end + +end + +function SocketClient:HandleMessage(data) + + if data == 'ok' then + self.connected = true + self:OnConnect() + return + end + + if self:OnMessage(data) then return end + + local msg = util.JSONToTable(data) + if not msg then + print('Unknown socket message: ' .. data) + return + end + + if msg[1] == MSG_REQUEST then + local reqID, name, data = unpack(msg, 2) + local handler = self.handlers[name] + if handler then + local function reply(data) + self.instance:write(util.TableToJSON({ MSG_REPLY, reqID, data })) + end + handler(reply, data) + end + elseif msg[1] == MSG_REPLY then + local reqID, data = unpack(msg, 2) + local resolve = self.pending[reqID] + if resolve then resolve(data) end + self.pending[reqID] = nil + timer.Remove('socket.req' .. reqID) + end + +end + +function SocketClient:Listen(name, callback) + + self.handlers[name] = callback + +end + +function SocketClient:Request(name, data) + + return util.Promise(function(res, rej) + local reqID = self.nextReqID + self.nextReqID = self.nextReqID + 1 + self.instance:write(util.TableToJSON({ MSG_REQUEST, reqID, name, data })) + + self.pending[reqID] = res + timer.Create('socket.req' .. reqID, requestTimeout, 1, function() + rej('timeout') + self.pending[reqID] = nil + end) + end) + +end + +function SocketClient:HandleError(msg) + + self:OnError(msg) + +end + +function SocketClient:IsConnected() + + return self.instance:isConnected() + +end + +function SocketClient:Clear() + + return self.instance:clearQueue() + +end + +function SocketClient:Close(force) + + if force then + return self.instance:closeNow() + else + return self.instance:close() + end + +end + +-- +-- overridables +-- +function SocketClient:OnConnect() + print('Auth success for ' .. self.url) +end + +function SocketClient:OnDisconnect() + print('Disconnected from ' .. self.url) +end + +function SocketClient:OnError(msg) + print('Error from ' .. self.url .. ': ' .. msg) +end + +function SocketClient:OnMessage(msg) + -- return true to abort default +end diff --git a/octolib/addon/lua/octolib/modules/_server/steam.lua b/octolib/addon/lua/octolib/modules/_server/steam.lua new file mode 100644 index 0000000..6825ec8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/steam.lua @@ -0,0 +1,25 @@ +local cache = {} + +local function getResults(tbl) + return octolib.table.map(tbl, function(id) return cache[id] end) +end + +function octolib.getSteamData(tbl, callback) + + local toFetch = octolib.array.filter(tbl, function(id) return not cache[id] end) + if #toFetch < 1 then return callback(getResults(tbl)) end + + octoservices:get('/steam/user/' .. table.concat(toFetch, ',')):Then(function(res) + if res.data then + if toFetch[2] then + for _, info in ipairs(res.data) do + cache[info.steamid64] = info + end + else cache[res.data.steamid64] = res.data end + end + callback(getResults(tbl)) + end):Catch(function(err) + octolib.msg('Error getting Steam data: %s', err) + end) + +end diff --git a/octolib/addon/lua/octolib/modules/_server/tests.lua b/octolib/addon/lua/octolib/modules/_server/tests.lua new file mode 100644 index 0000000..4d9f047 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/_server/tests.lua @@ -0,0 +1,193 @@ +octolib.testSectionMap = {} +octolib.tests = {} + +function octolib.registerTests(data, sectionName) + if not CFG.dev then return end -- save some memory in production + + local children = sectionName and octolib.testSectionMap[sectionName].children or octolib.tests + if data.children then + local section = octolib.testSectionMap[data.name] + if not section then + section = { + name = data.name, + children = {}, + } + + octolib.testSectionMap[section.name] = section + table.insert(children, section) + end + + for _, v in ipairs(data.children) do + octolib.registerTests(v, section.name) + end + else + local test = { + name = data.name, + timeout = data.timeout, + run = data.run, + } + + local _, oldKey = octolib.table.find(children, function(v) + return v.name == test.name + end) + if oldKey then + children[oldKey] = test + else + table.insert(children, test) + end + + end +end + +function octolib.runTests(callback) + local curLevel = -1 + local queue = {} + + local function addToQueue(data) + curLevel = curLevel + 1 + if data.children then + local sectionLevel = curLevel + queue[#queue + 1] = function(done) + TESTER_SOCKET:Request('printSection', { + name = data.name, + level = sectionLevel, + }) + done() + end + + for _, v in ipairs(data.children) do + addToQueue(v) + end + else + queue[#queue + 1] = function(done) + local startTime = SysTime() + timer.Create('testTimeout', data.timeout or 5, 1, function() + TESTER_SOCKET:Request('testResult', { + name = data.name, + time = SysTime() - startTime, + error = 'Timeout', + }) + done() + end) + + local function completed(error) + TESTER_SOCKET:Request('testResult', { + name = data.name, + time = SysTime() - startTime, + error = error, + }) + timer.Remove('testTimeout') + done() + end + + -- we want completed to be called on both success and failure + xpcall(data.run, completed, completed) + end + end + curLevel = curLevel - 1 + end + + for _, v in ipairs(octolib.tests) do + addToQueue(v) + end + + local function nextJob() + local job = table.remove(queue, 1) + if job then + job(nextJob) + else + callback() + end + end + nextJob() +end + +if not CFG.dev or not CFG.keys.services then return end + +local errors = {} +hook.Add('octolib.error', 'tester', function(msg, ply) + if IsValid(ply) then msg = ('[%s | %s]\n'):format(ply:Name(), ply:SteamID()) .. msg end + errors[#errors + 1] = msg +end) + +octolib.registerTests({ + name = 'octolib', + children = { + { + name = 'No startup errors', + run = function(done) + if #errors > 0 then + done(table.concat(errors, '\n\n')) + else + done() + end + end, + }, + }, +}) + +local reconnectInterval = 5 +local reconnectTimer = 'tester.reconnect' +local shouldLog = true +local uri = system.IsLinux() and 'ws://localhost:8888' or 'ws://my.app:8888' + +if TESTER_SOCKET then TESTER_SOCKET:Close() end + +TESTER_SOCKET = octolib.socket(uri, CFG.keys.services) +TESTER_SOCKET:Connect() + +function TESTER_SOCKET:OnConnect() + shouldLog = true + print('Socket connected to server') + timer.Remove(reconnectTimer) +end + +function TESTER_SOCKET:OnDisconnect() + if shouldLog then + print('Socket disconnected, retrying every ' .. reconnectInterval .. ' seconds...') + shouldLog = false + end + + timer.Create(reconnectTimer, reconnectInterval, 1, function() + self:Clear() + self:Connect() + timer.Remove(reconnectTimer) + end) +end + +function TESTER_SOCKET:OnError(msg) + -- print('Socket error: ' .. msg) +end + +local evalID = 0 +TESTER_SOCKET.evalReplies = {} + +TESTER_SOCKET:Listen('eval', function(reply, data) + evalID = evalID + 1 + + TESTER_SOCKET.evalReplies[evalID] = function(data) + reply({ data = data }) + TESTER_SOCKET.evalReplies[evalID] = nil + end + + local code = ('local reply = TESTER_SOCKET.evalReplies[%s]\n'):format(evalID) .. data.code + local func = CompileString(code, 'socket', true) + local ok, returns = xpcall(func, function(err) + reply({ + error = err .. '\n' .. debug.traceback() + :gsub('(stack traceback:\n).-\n', '%1') + :gsub('%[C%]: in function \'xpcall\'.+', ''), + }) + end) + + if ok and returns ~= nil then + reply({ data = returns }) + end +end) + +TESTER_SOCKET:Listen('runTests', function(reply) + octolib.runTests(function() + TESTER_SOCKET:Request('finish') + end) + reply() +end) diff --git a/octolib/addon/lua/octolib/modules/afk/client.lua b/octolib/addon/lua/octolib/modules/afk/client.lua new file mode 100644 index 0000000..1d16980 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/afk/client.lua @@ -0,0 +1,98 @@ +if CFG.disabledModules.afk then return end + +local lastPress, isAFK = 0, false + +local function startTicking() + + netstream.Start('octolib.afk', false) + isAFK = false + +end + +local function stopTicking() + + netstream.Start('octolib.afk', true) + isAFK = true + +end + +hook.Add('PlayerButtonDown', 'octolib', function(ply, button) + + if not system.HasFocus() then return end + + lastPress = CurTime() + if isAFK then startTicking() end + +end) + +timer.Create('octolib.afktick', 1, 0, function() + + if isAFK then return end + if CurTime() >= lastPress + CFG.afkTime then stopTicking() end + +end) + +local blur, blurState = Material( 'pp/blurscreen' ), 0 +local colors = CFG.skinColors +hook.Add( 'RenderScreenspaceEffects', 'octolib.afk', function() + + local a = 1 - math.pow( 1 - blurState, 2 ) + if a > 0 and CFG.drawOverlay then + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -a * 0.2, + ['$pp_colour_contrast'] = 1 + 0.5 * a, + ['$pp_colour_colour'] = 1 - a, + } + + if GetConVar('octolib_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor( 255, 255, 255, a * 255 ) + surface.SetMaterial( blur ) + + for i = 1, 3 do + blur:SetFloat( '$blur', a * i * 2 ) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect( -1, -1, ScrW() + 2, ScrH() + 2 ) + end + else + colMod['$pp_colour_brightness'] = -0.4 * a + colMod['$pp_colour_contrast'] = 1 + 0.2 * a + DrawColorModify(colMod) + end + + local col = colors.bg + draw.NoTexture() + surface.SetDrawColor(col.r, col.g, col.b, a * 100) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + end + +end) + +local clock = Material('octoteam/icons/clock.png') +hook.Add('HUDPaint', 'octolib.afk', function() + + if CFG.drawOverlay and blurState > 0 then + surface.SetDrawColor(255,255,255, blurState * 255) + surface.SetMaterial(clock) + surface.DrawTexturedRect(ScrW() / 2 - 32, ScrH() / 2 - 32, 64, 64) + end + +end) + +hook.Add('Think', 'octolib.afk', function() + + if not system.HasFocus() and not isAFK then stopTicking() end + blurState = math.Approach(blurState, isAFK and 1 or 0, FrameTime() / 2) + +end) + +startTicking() diff --git a/octolib/addon/lua/octolib/modules/afk/server.lua b/octolib/addon/lua/octolib/modules/afk/server.lua new file mode 100644 index 0000000..75be787 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/afk/server.lua @@ -0,0 +1,22 @@ +if CFG.disabledModules.afk then return end + +netstream.Hook('octolib.afk', function(ply, afk) + + afk = tobool(afk) + if ply:IsAFK() ~= afk then hook.Run('octolib.afk.changed', ply, afk) end + ply:SetNetVar('afk', afk and CurTime() - CFG.afkTime or nil) + +end) + +CFG.afkKickTime = CFG.afkKickTime or 600 +timer.Create('octolib.afk.kick', 60, 0, function() + + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + + if ply:GetAFKTime() > CFG.afkKickTime and not ply:IsAdmin() then + ply:Kick('AFK') + end + end) + +end) diff --git a/octolib/addon/lua/octolib/modules/afk/shared.lua b/octolib/addon/lua/octolib/modules/afk/shared.lua new file mode 100644 index 0000000..d4c0766 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/afk/shared.lua @@ -0,0 +1,14 @@ +local ply = FindMetaTable 'Player' + +if CFG.disabledModules.afk then return end + +function ply:GetAFKTime() + local wentAFK = self:GetNetVar('afk') + if not wentAFK then return 0 end + + return CurTime() - wentAFK +end + +function ply:IsAFK() + return tobool(self:GetNetVar('afk')) +end diff --git a/octolib/addon/lua/octolib/modules/anim/cl_anim.lua b/octolib/addon/lua/octolib/modules/anim/cl_anim.lua new file mode 100644 index 0000000..5e6c67a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/cl_anim.lua @@ -0,0 +1,75 @@ +local ply = FindMetaTable 'Player' + +function ply:DoAnimation(animID) + + local state = { weight = 1 } + local function update() + if not IsValid(self) then return end + self:AnimSetGestureWeight(GESTURE_SLOT_CUSTOM, state.weight) + end + + octolib.tween.create(0.2, state, { weight = 0 }, 'inOutQuad', function() + if not IsValid(self) then return end + self:AnimRestartGesture(GESTURE_SLOT_CUSTOM, animID, true) + octolib.tween.create(0.2, state, { weight = 1 }, 'inOutQuad', nil, update) + end, update) + +end + +netstream.Hook('player-anim', function(ply, animID) + + if IsValid(ply) then + ply:DoAnimation(animID) + end + +end) + +netstream.Hook('player-custom-anim', function(ply, catID, animID) + + if not IsValid(ply) then return end + + if not catID then + octolib.stopAnimations(ply) + return + end + + local cat = octolib.animations[catID or -1] + if not cat then return end + + local anim = cat.anims[animID or -1] + if not anim then return end + + if cat.type == 'act' then + ply:DoAnimation(anim[2]) + return + end + + if cat.type == 'seq' then + local seqID = ply:LookupSequence(anim[2]) + octolib.overrideSequence[ply] = seqID + + if seqID == -1 then + octolib.stopAnimations(ply) + return + end + + if cat.stand then + octolib.resetSequenceOnMove[ply] = true + else + octolib.resetSequenceOnMove[ply] = nil + end + + if anim.bones then + octolib.manipulateBones(ply, anim.bones, 0.4) + else + octolib.manipulateBones(ply, nil, 0.4) + end + + if anim.startTime then + octolib.overrideSequenceTime[ply] = anim.startTime + end + + return + end + +end) diff --git a/octolib/addon/lua/octolib/modules/anim/cl_bind.lua b/octolib/addon/lua/octolib/modules/anim/cl_bind.lua new file mode 100644 index 0000000..3008ca6 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/cl_bind.lua @@ -0,0 +1,109 @@ +octolib.registerBindHandler('anim', { + name = 'Запустить анимацию', + run = function(data) + if not istable(data) then return end + + local catID, animID = unpack(data) + if catID == '_emotions' then + local row = (octolib.vars.get('faceposes') or {})[animID] + netstream.Start('player-flex', row and row.flexes or {}) + else + netstream.Start('player-anim', catID, animID) + end + end, + buildBinder = function(cont, bindID, bind) + cont:SetTall(25) + + local but = octolib.button(cont, 'Неизвестная анимация', function(self) + local opts = {} + for catID, cat in SortedPairsByMemberValue(octolib.animations, 'order') do + if not cat.hide then + local catOpts = { cat.name, nil, {} } + for animID, anim in ipairs(cat.anims) do + table.insert(catOpts[3], { anim[1], nil, function() + octolib.setBind(bindID, bind.button, bind.action, { catID, animID }, bind.on) + end }) + end + table.insert(opts, catOpts) + end + end + + local rows = octolib.table.mapSequential(octolib.vars.get('faceposes') or {}, function(v, i) return { name = v.name, id = i } end) + table.insert(rows, 1, { name = 'Нейтральность', id = -1 }) + + table.insert(opts, { 'Эмоции', nil, octolib.table.mapSequential(rows, function(row) + return { row.name, nil, function() + octolib.setBind(bindID, bind.button, bind.action, { '_emotions', row.id }, bind.on) + end } + end), + }) + + octolib.menu(opts):Open() + end) + + + local catID, animID = unpack(istable(bind.data) and bind.data or {}) + if catID == '_emotions' then + if animID == -1 then + but:SetText('Нейтральность') + else + local row = (octolib.vars.get('faceposes') or {})[animID] + if row then but:SetText(row.name) end + end + else + local anim = catID and animID and octolib.animations[catID] and octolib.animations[catID].anims[animID] + if anim then but:SetText(anim[1]) end + end + end, +}) + +octolib.registerBindHandler('anim-cat', { + name = 'Меню анимаций', + run = function(data) + if not data then return end + + local opts = {} + if data == '_emotions' then + table.insert(opts, { 'Нейтральность', nil, function() netstream.Start('player-flex', {}) end }) + for _, row in ipairs(octolib.vars.get('faceposes') or {}) do + table.insert(opts, { row.name, nil, function() + netstream.Start('player-flex', row.flexes) + end }) + end + else + if not octolib.animations[data] then return end + for animID, anim in ipairs(octolib.animations[data].anims) do + table.insert(opts, { anim[1], nil, function() + netstream.Start('player-anim', data, animID) + end }) + end + end + + octogui.circularMenu(opts) + end, + buildBinder = function(cont, bindID, bind) + cont:SetTall(25) + + local but = octolib.button(cont, 'Неизвестная категория', function(self) + local opts = {} + for catID, cat in SortedPairsByMemberValue(octolib.animations, 'order') do + if not cat.hide then + table.insert(opts, { cat.name, nil, function() + octolib.setBind(bindID, bind.button, bind.action, catID, bind.on) + end }) + end + end + table.insert(opts, { 'Эмоции', nil, function() + octolib.setBind(bindID, bind.button, bind.action, '_emotions', bind.on) + end }) + octolib.menu(opts):Open() + end) + + local cat = octolib.animations[bind.data] + if cat then + but:SetText(cat.name) + elseif bind.data == '_emotions' then + but:SetText('Эмоции') + end + end, +}) diff --git a/octolib/addon/lua/octolib/modules/anim/cl_flex.lua b/octolib/addon/lua/octolib/modules/anim/cl_flex.lua new file mode 100644 index 0000000..cc004dc --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/cl_flex.lua @@ -0,0 +1,162 @@ +local running = {} +hook.Add('Think', 'octolib-anim.flexUpdate', function() + + local ct = CurTime() + for ply, data in pairs(running) do + if IsValid(ply) then + local frac = math.EaseInOut(math.Clamp(math.TimeFraction(data.start, data.finish, ct), 0, 1), 0.7, 0.7) + for flexID, weight in pairs(data.tgt) do + local curWeight = Lerp(frac, data.old[flexID] or 0, weight) + ply:SetFlexWeight(flexID, curWeight) + end + if ct > data.finish then running[ply] = nil end + else + running[ply] = nil + end + end + +end) + +local ply = FindMetaTable 'Player' + +function ply:ApplyFlex(flexes, time) + + local old = {} + local tgt = {} + + for i = 0, self:GetFlexNum() - 1 do + old[i] = self:GetFlexWeight(i) or 0 + tgt[i] = 0 + end + + for flexID, weight in pairs(flexes or {}) do + tgt[flexID] = weight + end + + for flexID, weight in pairs(tgt) do + if old[flexID] == weight then + old[flexID] = nil + tgt[flexID] = nil + end + end + + self.octolib_flexes = tgt + + running[self] = { + old = old, + tgt = tgt, + start = CurTime(), + finish = CurTime() + (time or 0), + } + +end + +netstream.Hook('player-flex', function(ply, flexes) + + if not IsValid(ply) then return end + ply:ApplyFlex(flexes, 0.4) + +end) + +local defaultEmotions = { + { + name = 'Улыбка', + flexes = { [10] = 0.81, [11] = 0.8, [12] = 0.53, [13] = 0.53, [16] = 0.6, [17] = 0.7, [18] = 1.0, [19] = 0.22, [22] = 0.84, [23] = 0.61, [33] = 0.66, [34] = 0.67, [37] = 0.54 }, + }, { + name = 'Удивление', + flexes = { [10] = 1.0, [11] = 1.0, [19] = 0.41, [27] = 0.15, [28] = 0.15, [33] = 0.01, [37] = 0.24, [39] = 0.28, [40] = 0.16, [41] = 0.17 }, + }, { + name = 'Грусть', + flexes = { [10] = 0.84, [11] = 0.85, [16] = 1.0, [17] = 1.0, [18] = 0.98, [19] = 0.38, [24] = 0.39, [25] = 0.42, [26] = 1.0 }, + }, { + name = 'Недоверие', + flexes = { [11] = 0.07, [12] = 0.01, [13] = 1.0, [14] = 1.0, [16] = 0.6, [18] = 0.59, [19] = 0.14, [20] = 0.6, [24] = 0.47, [26] = 0.63, [33] = 1.0, [37] = 0.28, [38] = 0.63 }, + }, { + name = 'Угрюмость', + flexes = { [14] = 1.0, [15] = 1.0, [19] = 0.53, [26] = 0.75, [37] = 0.48, [38] = 0.6 }, + }, { + name = 'Злость', + flexes = { [14] = 1.0, [15] = 1.0, [18] = 0.99, [20] = 0.3, [21] = 0.3, [24] = 0.95, [25] = 0.97, [26] = 0.85, [27] = 0.05 }, + } +} + +octolib.vars.init('faceposes', defaultEmotions) + +local function openEditor(save, data) + + local f = vgui.Create 'DFrame' + f:SetSize(600, 600) + f:Center() + f:MakePopup() + f:SetTitle('Редактирование эмоции') + + local poser = f:Add 'octolib_faceposer' + poser:CopyEntity(LocalPlayer()) + if data then poser:Import(data.flexes) end + + local name = octolib.textEntry(f) + name:DockMargin(5, 5, 5, 10) + name:SetTall(15) + name:SetPlaceholderText('Название') + name.PaintOffset = 5 + if data then name:SetValue(data.name) end + + if save then + local bp = f:Add 'DPanel' + bp:Dock(BOTTOM) + bp:DockMargin(0, 5, 0, 0) + bp:SetTall(25) + bp:SetPaintBackground(false) + + local bSave = octolib.button(bp, 'Сохранить', function() + save({ + name = name:GetValue(), + flexes = poser:Export(), + }) + f:Close() + end) + bSave:Dock(RIGHT) + bSave:DockMargin(5, 0, 0, 0) + bSave:SizeToContentsX(20) + + local bPresets = octolib.button(bp, 'Шаблоны', function() + octolib.menu(octolib.table.mapSequential(defaultEmotions, function(row) return { + row.name, nil, function() poser:Import(row.flexes) end, + } end)):Open() + end) + bPresets:Dock(RIGHT) + bPresets:DockMargin(5, 0, 0, 0) + bPresets:SizeToContentsX(20) + + local bDelete = octolib.button(bp, 'Удалить', function() + save(nil) + f:Close() + end) + bDelete:Dock(LEFT) + bDelete:DockMargin(0, 0, 5, 0) + bDelete:SizeToContentsX(20) + end + +end + +octolib.dataEditor.register('faceposes', { + name = 'Эмоции', + columns = { + { field = 'name', name = 'Название' }, + }, + load = function(load) + load(octolib.vars.get('faceposes') or defaultEmotions) + end, + save = function(rows) + octolib.vars.set('faceposes', rows) + end, + new = function(save) + openEditor(save) + end, + edit = function(row, save) + openEditor(save, row) + end, + validate = function(row) + return isstring(row.name) and string.Trim(row.name) ~= '' and istable(row.flexes) + end, +}) diff --git a/octolib/addon/lua/octolib/modules/anim/cl_gamemode.lua b/octolib/addon/lua/octolib/modules/anim/cl_gamemode.lua new file mode 100644 index 0000000..b99a96f --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/cl_gamemode.lua @@ -0,0 +1,58 @@ +local function run() + if not GAMEMODE then return end + function GAMEMODE:HandlePlayerNoClipping(ply) + + if ply:GetMoveType() == MOVETYPE_NOCLIP and not ply:InVehicle() then + ply.CalcIdeal = ACT_MP_SWIM + return true + end + + end + + function GAMEMODE:HandlePlayerVaulting(ply, velocity) + + if (velocity:LengthSqr() < 90000 and not ply:GetNetVar('flying')) then return end + if (ply:IsOnGround()) then return end + + ply.CalcIdeal = ACT_MP_SWIM + + return true + + end + + function GAMEMODE:MouthMoveAnimation(ply) + + if not ply.mmFlexes or ply.mmFlexes.model ~= ply:GetModel() then + ply.mmFlexes = { + ply:GetFlexIDByName('jaw_drop'), + ply:GetFlexIDByName('left_part'), + ply:GetFlexIDByName('right_part'), + ply:GetFlexIDByName('left_mouth_drop'), + ply:GetFlexIDByName('right_mouth_drop'), + ply:GetFlexIDByName('right_funneler'), + ply:GetFlexIDByName('left_funneler'), + model = ply:GetModel(), + } + end + + if not ply.octolib_flexes or not ply.octolib_flexes[ply.mmFlexes[1]] then + ply.octolib_flexes = ply.octolib_flexes or {} + for _, id in ipairs(ply.mmFlexes) do + ply.octolib_flexes[id] = 0 + end + end + + local tgt = ply:IsSpeaking() and math.Clamp(ply:VoiceVolume() * 2.5, 0, 2) or 0 + if ply.mmWeight ~= tgt then + ply.mmWeight = math.Approach(ply.mmWeight or 0, tgt, FrameTime() * 8) + + for _, id in ipairs(ply.mmFlexes) do + ply:SetFlexWeight(id, ply.mmWeight + (ply.octolib_flexes[id] or 0)) + end + end + + end +end + +hook.Add('darkrp.loadModules', 'player-anim', run) +run() diff --git a/octolib/addon/lua/octolib/modules/anim/cl_menu.lua b/octolib/addon/lua/octolib/modules/anim/cl_menu.lua new file mode 100644 index 0000000..e5b9e5c --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/cl_menu.lua @@ -0,0 +1,43 @@ +function octolib.createAnimSelectMenu() + + local hasDonate = LocalPlayer():GetNetVar('os_dobro') + + local menu = DermaMenu() + + for catID, cat in SortedPairsByMemberValue(octolib.animations, 'order') do + local catOpt = menu:AddSubMenu(cat.name) + + for i, anim in pairs(cat.anims) do + local opt = catOpt:AddOption(anim[1], function() netstream.Start('player-anim', catID, i) end) + if cat.donate and not hasDonate then opt:SetAlpha(75) end + end + end + + local rows = octolib.vars.get('faceposes') or {} + local catOpt = menu:AddSubMenu('Эмоции') + catOpt:AddOption('Нейтральность', function() netstream.Start('player-flex', {}) end) + for _, row in pairs(rows) do + catOpt:AddOption(row.name, function() netstream.Start('player-flex', row.flexes) end) + end + if #rows > 0 then catOpt:AddSpacer() end + catOpt:AddOption('Редактор эмоций...', function() + octolib.dataEditor.open('faceposes') + end):SetIcon('icon16/pencil.png') + + return menu + +end + +hook.Add('CreateMove', 'player-anim', function() + + if input.WasKeyPressed(KEY_F2) and not vgui.CursorVisible() then + gui.EnableScreenClicker(true) + + local menu = octolib.createAnimSelectMenu() + menu:Open() + menu:Center() + + gui.EnableScreenClicker(false) + end + +end) diff --git a/octolib/addon/lua/octolib/modules/anim/sh_anim.lua b/octolib/addon/lua/octolib/modules/anim/sh_anim.lua new file mode 100644 index 0000000..5b8e27a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/sh_anim.lua @@ -0,0 +1,133 @@ +octolib.animations = octolib.animations or {} +function octolib.registerAnimationCategory(id, data) + octolib.animations[id] = data +end + +local function load(path) + local fs, _ = file.Find(path .. '*.lua', 'LUA') + for _, f in pairs(fs) do + octolib.shared(path .. f:sub(1, -5)) + end +end + +load('config/octolib-anim/') + +local angle_zero = Angle() + +local running = {} +hook.Add('Think', 'octolib-anim.manipulate', function() + + local ct = CurTime() + local deleted = 0 + for i = 1, #running do + local data = running[i - deleted] + local ply = data.ply + if IsValid(ply) then + local frac = math.EaseInOut(math.Clamp(math.TimeFraction(data.start, data.finish, ct), 0, 1), 0.7, 0.7) + for boneID, ang in pairs(data.tgt) do + local curAng = LerpAngle(frac, data.old[boneID], ang) + ply:ManipulateBoneAngles(boneID, curAng) + end + if ct > data.finish then + table.remove(running, i) + deleted = deleted + 1 + end + else + table.remove(running, i) + deleted = deleted + 1 + end + end + +end) + +octolib.overrideSequence = octolib.overrideSequence or {} +octolib.overrideSequenceTime = octolib.overrideSequenceTime or {} +octolib.resetSequenceOnMove = octolib.resetSequenceOnMove or {} +hook.Add('CalcMainActivity', 'octolib-anim', function(ply, vel) + + local seq = octolib.overrideSequence[ply] + if not seq then return end + + local standing = ply:IsOnGround() and not ply:Crouching() + if octolib.resetSequenceOnMove[ply] and (not standing or vel.x ~= 0 or vel.y ~= 0 or vel.z ~= 0) then + octolib.stopAnimations(ply, true) + return + end + + if not standing then return end + + local time = octolib.overrideSequenceTime[ply] + if time then + ply:SetAnimTime(CurTime() - time) + octolib.overrideSequenceTime[ply] = nil + end + + return ACT_HL2MP_WALK, seq + +end) + +timer.Create('octolib-anim.cleanup', 60, 0, function() + + for ply, _ in pairs(octolib.overrideSequence) do + if not IsValid(ply) then + octolib.overrideSequence[ply] = nil + end + end + +end) + +function octolib.manipulateBones(ply, tbl, time) + + if not IsValid(ply) then return end + + local old = {} + local tgt = {} + + if not tbl then + for i = 0, ply:GetBoneCount() - 1 do + old[i] = ply:GetManipulateBoneAngles(i) or angle_zero + tgt[i] = angle_zero + end + end + + for bone, ang in pairs(tbl or {}) do + local boneID = ply:LookupBone(bone) + if boneID then + old[boneID] = ply:GetManipulateBoneAngles(boneID) or angle_zero + tgt[boneID] = ang + end + end + + for boneID, ang in pairs(tgt) do + if old[boneID] == ang then + old[boneID] = nil + tgt[boneID] = nil + end + end + + running[#running + 1] = { + ply = ply, + old = old, + tgt = tgt, + start = CurTime(), + finish = CurTime() + (time or 0), + } + +end + +function octolib.stopAnimations(ply, noSync) + + octolib.overrideSequence[ply] = nil + octolib.overrideSequenceTime[ply] = nil + octolib.resetSequenceOnMove[ply] = nil + octolib.manipulateBones(ply, nil, 0.4) + ply.octolib_customAnim = nil + + if SERVER then + ply:MoveModifier('anim', nil) + if not noSync then + netstream.StartPVS(ply:GetPos(), 'player-custom-anim', ply, nil) + end + end + +end diff --git a/octolib/addon/lua/octolib/modules/anim/shared.lua b/octolib/addon/lua/octolib/modules/anim/shared.lua new file mode 100644 index 0000000..c1961fe --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/shared.lua @@ -0,0 +1,3 @@ +if CFG.disabledModules.anim then return end +octolib.include.client('vgui') +octolib.include.prefixed('.') diff --git a/octolib/addon/lua/octolib/modules/anim/sv_anim.lua b/octolib/addon/lua/octolib/modules/anim/sv_anim.lua new file mode 100644 index 0000000..5f13bd8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/sv_anim.lua @@ -0,0 +1,84 @@ +-- +-- GESTURES +-- + +local function run() + if not GAMEMODE then return end + GAMEMODE.PlayerShouldTaunt = octolib.func.no + GAMEMODE.HandlePlayerNoClipping = octolib.func.zero + GAMEMODE.HandlePlayerVaulting = octolib.func.zero + GAMEMODE.HandlePlayerJumping = octolib.func.zero + GAMEMODE.HandlePlayerSwimming = octolib.func.zero + GAMEMODE.HandlePlayerVaulting = octolib.func.zero +end +hook.Add('darkrp.loadModules', 'player-anim', run) +run() + +local ply = FindMetaTable 'Player' +function ply:DoAnimation(animID, freeze) + + self:AnimRestartGesture(GESTURE_SLOT_CUSTOM, animID, true) + + local s = self:SelectWeightedSequence(animID) + local d = self:SequenceDuration(s) + self.nextAnim = CurTime() + d + + if freeze then + self:Freeze(true) + timer.Simple(d, function() + self:Freeze(false) + end) + end + + netstream.StartPVS(self:GetPos(), 'player-anim', self, animID) + +end + +netstream.Hook('player-anim', function(ply, catID, animID) + + if not IsValid(ply) or CurTime() < (ply.nextAnim or 0) then return end + + local cat = octolib.animations[catID or -1] + if not cat or cat.hide then return end + + local anim = cat.anims[animID or -1] + if not anim then return end + + if not ply:Alive() or ply:IsFrozen() or ply:InVehicle() or hook.Run('octolib.canUseAnimation', ply, cat, anim) == false then return end + + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep.CanUseAnimation and wep:CanUseAnimation(cat, anim) == false then return end + + if cat.donate and not ply:GetNetVar('os_dobro') then + ply:Notify('warning', L.gesture_only_for_dobro) + return + end + + if cat.type == 'act' then + octolib.stopAnimations(ply) + ply:DoAnimation(anim[2], cat.freeze) + end + + if cat.type == 'seq' then + local seqID = ply:LookupSequence(anim[2]) + octolib.overrideSequence[ply] = seqID + + if seqID == -1 then + octolib.stopAnimations(ply) + return + end + + if cat.stand then + octolib.resetSequenceOnMove[ply] = true + ply:MoveModifier('anim', { nojump = true }) + else + octolib.resetSequenceOnMove[ply] = nil + ply:MoveModifier('anim', nil) + end + + ply.octolib_customAnim = { catID, animID } + netstream.StartPVS(ply:GetPos(), 'player-custom-anim', ply, catID, animID) + end + +end) + diff --git a/octolib/addon/lua/octolib/modules/anim/sv_flex.lua b/octolib/addon/lua/octolib/modules/anim/sv_flex.lua new file mode 100644 index 0000000..883102a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/sv_flex.lua @@ -0,0 +1,24 @@ +local function applyFlex(ply, flexes) + + for i = 0, ply:GetFlexNum() - 1 do + ply:SetFlexWeight(i, flexes[i] or 0) + end + +end + +netstream.Hook('player-flex', function(ply, data) + + if not istable(data) then return end + + local flexes = {} + for i = 0, ply:GetFlexNum() - 1 do + local val = math.Round(math.Clamp(data[i] or 0, 0, 1), 2) + flexes[i] = val ~= 0 and val or nil + end + + ply.octolib_flexes = flexes + applyFlex(ply, ply.octolib_flexes or {}) + + netstream.StartPVS(ply:GetPos(), 'player-flex', ply, flexes) + +end) diff --git a/octolib/addon/lua/octolib/modules/anim/sv_hooks.lua b/octolib/addon/lua/octolib/modules/anim/sv_hooks.lua new file mode 100644 index 0000000..f10c39b --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/sv_hooks.lua @@ -0,0 +1,16 @@ +hook.Add('PlayerEnteredPVS', 'octolib-anim', function(tgt, ply) + + local catID, animID + if istable(tgt.octolib_customAnim) then + catID, animID = unpack(tgt.octolib_customAnim) + end + netstream.Start(ply, 'player-custom-anim', tgt, catID, animID) + netstream.Start(ply, 'player-flex', tgt, tgt.octolib_flexes) + +end) + +local function stop(ply) + octolib.stopAnimations(ply) +end +hook.Add('PlayerEnteredVehicle', 'octolib-anim', stop) +hook.Add('PlayerSwitchWeapon', 'octolib-anim', stop) diff --git a/octolib/addon/lua/octolib/modules/anim/vgui/octolib_faceposer.lua b/octolib/addon/lua/octolib/modules/anim/vgui/octolib_faceposer.lua new file mode 100644 index 0000000..3b3180b --- /dev/null +++ b/octolib/addon/lua/octolib/modules/anim/vgui/octolib_faceposer.lua @@ -0,0 +1,101 @@ +local PANEL = {} + +function PANEL:Init() + + self:Dock(FILL) + self:SetPaintBackground(false) + self.flexes = {} + + local m = self:Add 'DAdjustableModelPanel' + m:Dock(LEFT) + m:SetWide(300) + m.LayoutEntity = function(m, ent) + for k, v in pairs(self.flexes) do + ent:SetFlexWeight(k, v) + end + ent:SetEyeTarget(m:GetCamPos()) + end + self.model = m + + m.FirstPersonControls = function(m) + local x, y = m:CaptureMouse() + m.aLookAngle = m.aLookAngle + Angle( y * 0.1, x * -0.1, 0 ) + m.vCamPos = m.basePos - m.aLookAngle:Forward() * 20 + end + + local sp = self:Add 'DScrollPanel' + sp:Dock(FILL) + sp.pnlCanvas:DockPadding(10, 0, 10, 0) + sp:SetPaintBackground(true) + self.sliderContainer = sp + +end + +function PANEL:CopyEntity(ent) + + local m = self.model + m:SetModel(ent:GetModel()) + local ent = m.Entity + ent:SetSkin(ent:GetSkin()) + for k, v in ipairs(ent:GetBodyGroups()) do + ent:SetBodygroup(k, ent:GetBodygroup(v.id)) + end + + local att = ent:GetAttachment(ent:LookupAttachment('eyes')) + local ang = Angle(0, 173, 0) + local basePos = att.Pos + Vector(0, 0, -1.5) + local pos = basePos - ang:Forward() * 20 + m.basePos = basePos + m:SetFOV(25) + m:SetCamPos(pos) + m:SetLookAng(ang) + + self.flexes = {} + self.sliders = {} + + local function onChange(slider, val) + val = math.Round(val, 2) + self.flexes[slider.flexID] = val ~= 0 and val or nil + ent:SetFlexWeight(slider.flexID, val) + end + + for i = 0, ent:GetFlexNum() - 1 do + local niceName = string.Explode('_', ent:GetFlexName(i)) + niceName = table.concat(octolib.table.mapSequential(niceName, function(v) return utf8.upper(utf8.sub(v, 1, 1)) .. utf8.lower(utf8.sub(v, 2)) end), ' ') + + self.flexes[i] = math.Round(ent:GetFlexWeight(i), 2) + + local slider = octolib.slider(self.sliderContainer, niceName, 0, 1, 2) + slider:SetValue(self.flexes[i]) + slider.flexID = i + slider.OnValueChanged = onChange + self.sliders[i] = slider + end + +end + +function PANEL:Import(data) + + for _, slider in pairs(self.sliders) do + slider:SetValue(data[slider.flexID] or 0) + end + +end + +function PANEL:Export() + + local out = table.Copy(self.flexes) + for k, v in pairs(out) do + local nv = math.Round(v, 2) + if nv > 0 then + out[k] = nv + else + out[k] = nil + end + end + + return out + +end + +vgui.Register('octolib_faceposer', PANEL, 'DPanel') diff --git a/octolib/addon/lua/octolib/modules/api.lua b/octolib/addon/lua/octolib/modules/api.lua new file mode 100644 index 0000000..25994d2 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/api.lua @@ -0,0 +1,182 @@ +--[[ + Class: octolib.API + Object to simplify work with HTTP endpoints +]] + +local API = {} +API.__index = API + +-- +-- Group: Constructors +-- + +--[[ + Function: octolib.api + Create API object + + Arguments: + config - API object config + + Example: + --- Lua + local myApi = octolib.api({ + url = 'http://example.com/api', + headers = { ['Authorization'] = 't0ps3cr3t' }, + }) + + myApi:get('/users/1'):Then(function(result) + if result.code == 200 then + print('User name is ' .. result.data.username) + else + print('Failed to get user info') + end + end) + --- +]] +function octolib.api(config) + + local api = { + url = config.url, + headers = config.headers, + } + + setmetatable(api, API) + return api + +end + +-- +-- Group: Methods +-- + +--[[ + Function: _request + Perform generic request, used internally, use HTTP verbs methods instead + + Arguments: + path - Path relative to endpoint + verb - +
data - optional table of data + + Returns: + () - Promise with results of request +]] +function API:_request(path, verb, data) + + local url = self.url .. path + local opts = { + headers = self.headers, + url = url, + method = verb:lower(), + type = 'application/json', + } + + if data then + if opts.method == 'get' then + local params = {} + for k, v in pairs(data) do params[#params + 1] = ('%s=%s'):format(octolib.string.urlEncode(k), octolib.string.urlEncode(v)) end + opts.url = opts.url .. '?' .. table.concat(params, '&') + else + opts.body = util.TableToJSON(data) + end + end + + return util.Promise(function(res, rej) + opts.success = function(code, body, headers) + local data = body and util.JSONToTable(body) + if data then + res({ + code = code, + data = data, + }) + else + rej('Invalid response:' .. body) + end + end + + opts.failed = function(reason) + print(('API request on %s failed: %s'):format(url, reason)) + rej(reason) + end + + HTTP(opts) + end) + +end + +--[[ + Function: get + GET request to api endpoint. Data will be encoded into URL + + --- Prototype + function API:get(path, data) + --- + + Arguments: + path - Path relative to endpoint +
data - optional table of data + + Returns: + () - Result of API request +]] + +--[[ + Function: post + POST request to api endpoint. Data will be encoded into request body as JSON + Also available with same parameters: API:put(), API:delete(), API:patch() + + --- Prototype + function API:get(path, data) + --- + + Arguments: + path - Path relative to endpoint +
data - optional table of data + + Returns: + () - Result of API request +]] + +-- define HTTP verbs funcs +local verbs = {'get', 'post', 'put', 'delete', 'patch'} +for i, verb in ipairs(verbs) do + API[verb] = function(self, path, data) + return self:_request(path, verb, data) + end +end + +-- +-- Group: Structures +-- + +--[[ + Type: ConfigAPI + API config structure + + Properties: + url - Endpoint URL +
headers - Keyed table of headers sent with each request +]] +--[[ + Type: RequestResult + HTTP response structure + + Properties: + code - +
data - Parsed response body +]] + +local function octoservicesTryInit() + if not CFG.octoservicesURL then return end + + hook.Remove('Think', 'octolib.api.octoservices') + + octoservices = octolib.api({ + url = CFG.octoservicesURL, + headers = { ['x-api-key'] = CFG.keys and CFG.keys.services or nil }, + }) + + hook.Run('octoservices.init', octoservices) +end +octoservicesTryInit() +hook.Add('Think', 'octolib.api.octoservices', octoservicesTryInit) diff --git a/octolib/addon/lua/octolib/modules/array.lua b/octolib/addon/lua/octolib/modules/array.lua new file mode 100644 index 0000000..7c4c61d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/array.lua @@ -0,0 +1,153 @@ +octolib.array = octolib.array or {} + +function octolib.array.toKeys(array, value) + if value == nil then + value = true + end + + local out = {} + for _, v in ipairs(array) do + out[v] = value + end + + return out +end + +function octolib.array.toStack(array, reversed) + local stack = util.Stack() + if reversed then array = table.Reverse(array) end + + for i = 1, #array do + stack:Push(array[i]) + end + + return stack +end + +function octolib.array.random(t) + local i = math.random(#t) + return t[i], i +end + +function octolib.array.randomWeighted(items, flattenBy) + flattenBy = flattenBy or 0 + + local total, ids = 0, {} + for i, v in ipairs(items) do total = total + v[1] + flattenBy end + for i, v in ipairs(items) do ids[v[2]] = (v[1] + flattenBy) / total end + + local case = math.random() + for i, v in pairs(ids) do + case = case - v + if case <= 0 then return i end + end + + return items[#items][2] -- just in case, should never happen +end + +function octolib.array.count(array, func) + local count = 0 + for i, v in ipairs(array) do + if func(v, i) then count = count + 1 end + end + + return count +end + +function octolib.array.shuffle(array) + local count = #array + for i = 1, count do + local i2 = math.random(count) + array[i], array[i2] = array[i2], array[i] + end + + return array +end + +function octolib.array.series(value, amount) + local out = {} + for _ = 1, amount or 1 do + out[#out + 1] = value + end + + return out +end + +function octolib.array.page(array, pageSize, pageNum) + pageNum = pageNum or 1 + return { unpack(array, (pageNum - 1) * pageSize + 1, pageNum * pageSize) } +end + +function octolib.array.map(array, func) + local out = {} + for i, v in ipairs(array) do + out[i] = func(v, i, array) + end + + return out +end + +function octolib.array.reduce(array, func, initVal) + local out = initVal or array[1] + for i, v in ipairs(array) do + out = func(out, v, i, array) + end + + return out +end + +function octolib.array.some(array, func) + for i, v in ipairs(array) do + if func(v, i) then return true end + end + + return false +end + +function octolib.array.every(array, func) + for i, v in ipairs(array) do + if not func(v, i) then return false end + end + + return true +end + +function octolib.array.find(array, func) + for i, v in ipairs(array) do + if func(v, i) then return v, i end + end +end + +function octolib.array.filter(array, func, limit) + local out = {} + local foundAmount = 0 + for i, v in ipairs(array) do + if func(v, i) then + foundAmount = foundAmount + 1 + out[foundAmount] = v + if limit and foundAmount >= limit then break end + end + end + + return out +end + +function octolib.array.filterMultiple(array, funcs, limit) + local out = {} + local foundAmount = 0 + for i, v in ipairs(array) do + if octolib.array.every(funcs, function(func) + return func(v, i) + end) then + foundAmount = foundAmount + 1 + out[foundAmount] = v + if limit and foundAmount >= limit then break end + end + end + + return out +end + +function octolib.array.filterQuery(tbl, query, limit) + return octolib.array.filterMultiple(tbl, octolib.table.precacheFilterFunctions(query), limit) +end diff --git a/octolib/addon/lua/octolib/modules/audio.lua b/octolib/addon/lua/octolib/modules/audio.lua new file mode 100644 index 0000000..c7afbb0 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/audio.lua @@ -0,0 +1,216 @@ +local running = {} + +octolib.audio = octolib.audio or {} + +function octolib.audio.play(data) + if SERVER then + local receivers + if data.pos and data.dist then + local pos = data.pos + if IsValid(data.ent) then + pos = data.ent:LocalToWorld(data.pos) + end + receivers = octolib.array.filter(ents.FindInSphere(pos, data.dist), function(ent) return ent:IsPlayer() end) + end + netstream.Start(receivers, 'octolib.sound', data) + return + end + + if data.url then + local tags = data.tags or {'noplay'} + if data.pos then tags[#tags + 1] = '3d' end + + sound.PlayURL(data.url, table.concat(tags, ' '), function(chan) + if IsValid(chan) then + chan:SetVolume(data.volume or 1) + if data.pos then + local pos = data.pos + if IsValid(data.ent) then + pos = data.ent:LocalToWorld(data.pos) + end + chan:SetPos(pos) + end + if data.dist then chan:Set3DFadeDistance(data.distInner or 0, data.dist) end + chan:Play() + running[chan] = data + else + octolib.notify.show('warning', 'Не удалось загрузить звук. Возможно, ссылка неправильная') + end + end) + elseif data.file then + local pos = data.pos + if IsValid(data.ent) then + pos = data.ent:LocalToWorld(data.pos) + end + sound.Play(data.file, pos or LocalPlayer():EyePos(), data.level or 70, data.pitch or 100, data.volume or 1) + end +end + +if CLIENT then + netstream.Hook('octolib.sound', octolib.audio.play) + + hook.Add('Think', 'octolib.sound', function() + for chan, data in pairs(running) do + if IsValid(chan) then + local pos = data.pos + if pos and IsValid(data.ent) then + pos = data.ent:LocalToWorld(data.pos) + chan:SetPos(pos) + end + + if data.dist and pos:Distance(LocalPlayer():EyePos()) > data.dist then + chan:Stop() + running[chan] = nil + end + else + running[chan] = nil + end + end + end) + + octolib.audio.streams = octolib.audio.streams or {} + + local function restartStream(stream) + stream.stopped = nil + + sound.PlayFile('sound/stream_radio/noise.wav', '3d noplay', function(st) + if IsValid(stream.noise) then stream.noise:Stop() end + if stream.stopped then return end + stream.noise = st + stream:Play(true) + end) + sound.PlayURL(stream.url, '3d noplay', function(st) + if IsValid(stream.st) then stream.st:Stop() end + if stream.stopped then return end + stream.st = st + stream:Play(true) + if IsValid(stream.noise) then stream.noise:Stop() end + end) + end + + hook.Add('Think', 'octolib.streams', function() + local pos = LocalPlayer():GetShootPos() + + for _,v in pairs(octolib.audio.streams) do + if not v.url then continue end + + if v.parent then + if not IsValid(v.parent) then + if not v.stopped then v:Stop() end + continue + else v:SetPos(v.parent:GetPos()) end + end + + if not v.pos then continue end + + local dst = v.pos:DistToSqr(pos) + if not v.stopped and dst > v.distSqr then + v:Stop(true) + elseif v.stopped and not v.customStop and dst <= v.distSqr then + restartStream(v) + end + + end + end) + + octolib.audio.metaStream = octolib.audio.metaStream or {} + + local AudioStream = octolib.audio.metaStream + AudioStream.__index = AudioStream + + local defaults = { + volume = 0.2, + distance = 600, + distSqr = 360000, + pos = Vector(0,0,0), + } + + function octolib.audio.stream() + local stream = table.Copy(defaults) + setmetatable(stream, AudioStream) + stream.uid = octolib.string.uuid() + octolib.audio.streams[stream.uid] = stream + + return stream + end + + local function setup(stream, st) + if not IsValid(st) then return end + st:SetVolume(stream.volume) + st:Set3DFadeDistance(stream.distance / 3, stream.distance) + st:Set3DEnabled(stream.pos ~= nil) + if stream.pos ~= nil then + st:SetPos(stream.pos) + end + end + + function AudioStream:SetParent(ent) + self.parent = ent + end + + function AudioStream:ForEachChannel(act) + if IsValid(self.noise) then act(self.noise) end + if IsValid(self.st) then act(self.st) end + end + + function AudioStream:Play(upd) + if not self:IsValid() then return end + self:ForEachChannel(function(chan) + if upd then setup(self, chan) end + chan:Play() + end) + end + + function AudioStream:SetDistance(dist) + if not isnumber(dist) then return end + self.distance = dist + self.distSqr = dist * dist + self:ForEachChannel(function(chan) + chan:Set3DFadeDistance(dist / 3, dist) + end) + end + + function AudioStream:SetURL(url) + if not isstring(url) then + return self:Stop() + end + self.url = url + restartStream(self) + end + + function AudioStream:SetVolume(vol) + if not isnumber(vol) then return end + self.volume = vol + self:ForEachChannel(function(chan) + chan:SetVolume(vol) + end) + end + + function AudioStream:SetPos(pos) + if pos ~= nil and not isvector(pos) then return end + self.pos = pos + self:ForEachChannel(function(chan) + chan:Set3DEnabled(pos ~= nil) + if pos ~= nil then + chan:SetPos(pos) + end + end) + end + + function AudioStream:Stop(notCustom) + self:ForEachChannel(function(chan) + chan:Stop() + end) + self.stopped = true + self.customStop = not notCustom + end + + function AudioStream:Remove() + octolib.audio.streams[self.uid] = nil + self:Stop() + end + + function AudioStream:IsValid() + return octolib.audio.streams[self.uid] ~= nil + end +end diff --git a/octolib/addon/lua/octolib/modules/backup/server.lua b/octolib/addon/lua/octolib/modules/backup/server.lua new file mode 100644 index 0000000..2241242 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/backup/server.lua @@ -0,0 +1,306 @@ +if CFG.disabledModules.backup then return end + +octolib.linkedCache = octolib.linkedCache or {} +local cache = octolib.linkedCache +local cleanupAfter = CFG.backupCleanupAfter +local period = CFG.backupPeriod +local pruneTime = CFG.backupPruneTime + +function octolib.getLinkedEnts(sID, classes) + + local owned = {} + if classes then + local classesKeyed = octolib.array.toKeys(classes) + for ent, steamID in pairs(cache) do + if sID == steamID and IsValid(ent) and classesKeyed[ent:GetClass()] then + table.insert(owned, ent) + end + end + else + for ent, steamID in pairs(cache) do + if sID == steamID then + table.insert(owned, ent) + end + end + end + + return owned + +end + +local registeredClasses = {} +function octolib.registerBackupClass(class, save, load) + + if save and load then + registeredClasses[class] = {save, load} + else + registeredClasses[class] = nil + end + +end + +local registeredMods = {} +function octolib.registerBackupMod(mod, save, load) + + if save and load then + registeredMods[mod] = {save, load} + else + registeredMods[mod] = nil + end + +end + +local pendingBackup = {} +function octolib.restoreBackup(ply) + + local stored = pendingBackup[ply:SteamID()] + if not stored then return end + + for id, data in pairs(stored) do + local load = registeredMods[id] and registeredMods[id][2] + if load then load(ply, data) end + end + + if stored.e then + for _, entData in ipairs(stored.e) do + local handler = registeredClasses[entData.c] + if handler then + local ent = ents.Create(entData.c) + ent:SetPos(entData.p) + ent:SetAngles(entData.a) + cache[ent] = steamID + + handler[2](ent, ply, entData.d) + ent:Spawn() + ent:LinkPlayer(ply) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + + octolib.msg('Restored %s for %s', entData.c, ply:SteamID()) + end + end + end + + ply:Notify('ooc', L.pendingbackup) + pendingBackup[ply:SteamID()] = nil + +end + +function octolib.getPlayerOnline(steamID, callback) + + octolib.db:PrepareQuery('select * from octolib_players where steamID = ?', { steamID }, function(q, st, rows) + local row = st and rows[1] + if not istable(row) then return callback(false) end + + local data = pon.decode(row.data) + callback(row.serverID, data) + end) + +end + +--------------------------------------- +-- TIMERS +--------------------------------------- + +local curPlayerID, curPlayer = 0 +local function increaseCounter() + + curPlayerID = curPlayerID + 1 + if curPlayerID > game.MaxPlayers() then curPlayerID = 1 end + curPlayer = Entity(curPlayerID) + +end + +timer.Create('octolib.backup.save', 10, 0, function() + + if player.GetCount() < 1 then return end + + while not IsValid(curPlayer) do + increaseCounter() + end + + if curPlayer.SaveInventory then curPlayer:SaveInventory(true) end + increaseCounter() + +end) + +function octolib.backupNow() + + if player.GetCount() < 1 then return end + + local toUpdate = {} + + octolib.func.throttle(octolib.table.toKeyVal(cache), 10, 0.2, function(data) + local ent, steamID = data[1], data[2] + if not IsValid(ent) then + cache[ent] = nil + return + end + + local class = ent:GetClass() + if not registeredClasses[class] then return end + + local data = registeredClasses[class][1](ent) + if not data then return end + + toUpdate[steamID] = toUpdate[steamID] or {e = {}} + + local pos = ent:GetPos() + pos.x = math.floor(pos.x) + pos.y = math.floor(pos.y) + pos.z = math.floor(pos.z) + + local ang = ent:GetAngles() + ang.p = math.floor(ang.p) + ang.y = math.floor(ang.y) + ang.r = math.floor(ang.r) + + local entities = toUpdate[steamID].e + entities[#entities + 1] = { c = class, p = pos, a = ang, d = data } + end):Then(function() + return octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + local steamID = ply:SteamID() + toUpdate[steamID] = toUpdate[steamID] or {} + for id, handler in pairs(registeredMods) do + toUpdate[steamID][id] = handler[1](ply) + end + end) + end):Then(function() + for steamID, data in pairs(toUpdate) do + data = pon.encode(data) + octolib.db:PrepareQuery([[ + insert into octolib_players(steamID, serverID, data) values (?, ?, ?) + on duplicate key update serverID = values(serverID), data = values(data) + ]], { + steamID, CFG.serverID, data + }) + end + + octolib.msg('Entities backed up.') + end) + +end +timer.Create('octolib.backup.backup', period, 0, octolib.backupNow) + +--------------------------------------- +-- HOOKS +--------------------------------------- + +hook.Add('EntityRemoved', 'octolib.backup', function(ent) + cache[ent] = nil +end) + +hook.Add('PlayerDisconnected', 'octolib.backup', function(ply) + + local sID = ply:SteamID() + local toRemove = octolib.getLinkedEnts(sID) + + octolib.db:PrepareQuery('delete from octolib_players where serverID = ? and steamID = ?', { CFG.serverID, sID }) + + timer.Create('octolib.backup' .. sID, cleanupAfter, 1, function() + for _, ent in ipairs(toRemove) do + if IsValid(ent) and cache[ent] == sID then + ent:Remove() + cache[ent] = nil + end + end + octolib.msg('Cleaned up entities of %s', sID) + end) + +end) + +hook.Add('PlayerInitialSpawn', 'octolib.backup', function(ply) + + local sID = ply:SteamID() + local tname = 'octolib.backup' .. sID + if timer.Exists(tname) then + timer.Remove(tname) + octolib.msg('Player is back! Aborting removal for %s', sID) + for ent, steamID in pairs(cache) do + if IsValid(ent) and sID == steamID then + ent:LinkPlayer(ply) + if ent.owner then ent.owner = ply end + if ent.SetPlayer then ent:SetPlayer(ply) end + end + end + end + + octolib.db:PrepareQuery('replace into octolib_players(steamID, serverID, data) values(?, ?, \'[}\')', { ply:SteamID(), CFG.serverID }) + +end) + +hook.Add('octolib.db.init', 'octolib.backup', function() + + octolib.func.chain({ + function(next) + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS octolib_players ( + steamID VARCHAR(30) NOT NULL, + serverID VARCHAR(30) NOT NULL, + data TEXT, + PRIMARY KEY (steamID) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]], next) + end, + function(next) + octolib.db:PrepareQuery('select * from octolib_players where serverID = ?', { CFG.serverID }, next) + end, + function(next, _, _, data) + for _, row in ipairs(data) do pendingBackup[row.steamID] = pon.decode(row.data) end + octolib.db:PrepareQuery('delete from octolib_players where serverID = ?', { CFG.serverID }) + timer.Simple(pruneTime, next) + end, + function() + table.Empty(pendingBackup) + end, + }) + +end) + +gameevent.Listen('player_disconnect') +hook.Add('player_disconnect', 'octolib.backup', function(data) + + local sID + if octolib.string.isSteamID(data.networkid) then + sID = data.networkid + elseif (data.networkid:find('^7656119%d+$')) then + sID = util.SteamIDFrom64(data.networkid) + else + return + end + + octolib.db:PrepareQuery('delete from octolib_players where serverID = ? and steamID = ?', { CFG.serverID, sID }) +end) + +--------------------------------------- +-- META +--------------------------------------- + +local entMeta = FindMetaTable 'Entity' +function entMeta:LinkPlayer(ply) + + if not IsValid(ply) or not IsValid(self) then cache[self] = nil return end + cache[self] = ply:SteamID() + +end + +local plyMeta = FindMetaTable 'Player' +function plyMeta:LinkEntity(ent) + + if not IsValid(ent) then cache[ent] = nil return end + cache[ent] = self:SteamID() + +end + +--------------------------------------- +-- CONFIG +--------------------------------------- + +local fs, _ = file.Find('config/octolib-backup/*.lua', 'LUA') +for _, f in pairs(fs) do + fname = 'config/octolib-backup/' .. f + include(fname) +end diff --git a/octolib/addon/lua/octolib/modules/bind/client.lua b/octolib/addon/lua/octolib/modules/bind/client.lua new file mode 100644 index 0000000..2408598 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/bind/client.lua @@ -0,0 +1,203 @@ +octolib.bindHandlers = octolib.bindHandlers or {} + +if CFG.serverGroupIDvars ~= CFG.serverGroupID and octolib.vars.get('binds_' .. CFG.serverGroupID) then + octolib.vars.set('binds_' .. CFG.serverGroupIDvars, octolib.vars.get('binds_' .. CFG.serverGroupID)) + octolib.vars.set('binds_' .. CFG.serverGroupID, nil) +end + +function octolib.registerBindHandler(id, data) + octolib.bindHandlers[id] = data +end + +octolib.bindCache = octolib.bindCache or {} + +local mapDown, mapUp = {}, {} +local function rebuildMaps() + + table.Empty(mapDown) + table.Empty(mapUp) + + for _, bind in ipairs(octolib.bindCache) do + local tbl = bind.on == 'down' and mapDown or mapUp + if not tbl[bind.button] then tbl[bind.button] = {} end + table.insert(tbl[bind.button], function() + octolib.bindHandlers[bind.action].run(bind.data) + end) + end + + octolib.bindsUpdate = CurTime() + +end + +function octolib.setBind(id, button, action, data, on) + + if button then + octolib.bindCache[id or (#octolib.bindCache + 1)] = { + button = button, + action = action, + data = data, + on = on or 'down', + } + elseif id then + table.remove(octolib.bindCache, id) + else + return + end + + octolib.vars.set('binds_' .. (CFG.serverGroupIDvars or CFG.serverGroupID), octolib.bindCache) + rebuildMaps() + +end + +hook.Add('PlayerButtonDown', 'octolib-bind', function(_, but) + if not IsFirstTimePredicted() then return end + + local funcs = mapDown[but] + if funcs then + for _, func in ipairs(funcs) do func() end + end +end) + +hook.Add('PlayerButtonUp', 'octolib-bind', function(_, but) + if not IsFirstTimePredicted() then return end + + local funcs = mapUp[but] + if funcs then + for _, func in ipairs(funcs) do func() end + end +end) + +hook.Add('octolib.configLoaded', 'octolib-bind', function() + octolib.bindCache = octolib.vars.get('binds_' .. (CFG.serverGroupIDvars or CFG.serverGroupID)) or {} + rebuildMaps() +end) + +local PANEL = {} + +function PANEL:Think() + + if self.lastUpdate == octolib.bindsUpdate then return end + self.lastUpdate = octolib.bindsUpdate + + self:RebuildList() + +end + +function PANEL:RebuildList() + + self:Clear() + + for i = 1, #octolib.bindCache do + local row = self:Add 'octolib_binds_row' + row:SetBind(i) + end + + octolib.button(self, 'Создать', function() + octolib.setBind(nil, KEY_SPACE, 'chat', 'Привет!') + end) + +end + +vgui.Register('octolib_binds', PANEL, 'DScrollPanel') + +local PANEL = {} + +function PANEL:Init() + + self:Dock(TOP) + self:SetTall(31) + self:DockMargin(0, 0, 0, 5) + + local top = self:Add 'DPanel' + top:Dock(TOP) + top:DockMargin(3, 3, 3, 3) + top:SetTall(25) + top:SetPaintBackground(false) + + local cb = octolib.comboBox(top, nil, { + {'При нажатии', 'down', true}, + {'При отжатии', 'up'}, + }) + cb:Dock(LEFT) + cb:DockMargin(0, 0, 3, 0) + cb:SetWide(100) + function cb:OnSelect() end + self.on = cb + + local b = top:Add 'DBinder' + b:Dock(LEFT) + b:DockMargin(0, 0, 3, 0) + b:SetWide(50) + function b:SetSelectedNumber(but) + self.m_iSelectedNumber = but + self:UpdateText() + end + self.binder = b + + local del = top:Add 'DImageButton' + del:Dock(RIGHT) + del:SetWide(16) + del:DockMargin(3, 4, 0, 5) + del:SetImage('icon16/cross.png') + del:SetTooltip('Удалить') + self.del = del + + local opts = {} + for id, data in pairs(octolib.bindHandlers) do + opts[#opts + 1] = { data.name, id } + end + local cb = octolib.comboBox(top, nil, opts) + cb:Dock(FILL) + cb:DockMargin(0, 0, 0, 0) + function cb:OnSelect() end + self.action = cb + + local custom = self:Add 'DPanel' + custom:Dock(TOP) + custom:DockMargin(3, 0, 3, 3) + custom:SetPaintBackground(false) + custom:SetTall(0) + self.custom = custom + +end + +function PANEL:SetBind(bindID) + + local bind = octolib.bindCache[bindID] + local handler = octolib.bindHandlers[bind.action] + + function self.del:DoClick() + octolib.setBind(bindID, nil) + end + + self.on:ChooseOptionID(bind.on == 'down' and 1 or 2) + function self.on:OnSelect(_, _, val) + local bind = octolib.bindCache[bindID] + octolib.setBind(bindID, bind.button, bind.action, bind.data, val) + end + + self.binder:SetValue(bind.button) + function self.binder:SetSelectedNumber(but) + self.m_iSelectedNumber = but + self:UpdateText() + + local bind = octolib.bindCache[bindID] + octolib.setBind(bindID, but, bind.action, bind.data, bind.on) + end + + self.action:SetValue(handler.name) + function self.action:OnSelect(_, _, val) + local bind = octolib.bindCache[bindID] + octolib.setBind(bindID, bind.button, val, nil, bind.on) + end + + if handler.buildBinder then + handler.buildBinder(self.custom, bindID, bind) + local h = self.custom:GetTall() + if h > 0 then h = h + 3 end + self:SetTall(31 + h) + end + +end + +vgui.Register('octolib_binds_row', PANEL, 'DPanel') diff --git a/octolib/addon/lua/octolib/modules/build.lua b/octolib/addon/lua/octolib/modules/build.lua new file mode 100644 index 0000000..567d78c --- /dev/null +++ b/octolib/addon/lua/octolib/modules/build.lua @@ -0,0 +1 @@ +local ‪ = _G local ‪‪ = ‪['\115\116\114\105\110\103'] local ‪‪‪ = ‪['\98\105\116']['\98\120\111\114'] local function ‪‪‪‪‪‪‪(‪‪‪‪) if ‪‪['\108\101\110'](‪‪‪‪) == 0 then return ‪‪‪‪ end local ‪‪‪‪‪ = '' local ‪‪‪‪‪‪ = 0 for _ in ‪‪['\103\109\97\116\99\104'](‪‪‪‪,'\46') do if _ == '\124' then ‪‪‪‪‪ = ‪‪‪‪‪..‪‪['\99\104\97\114'](‪‪‪(‪‪‪‪‪‪,108)) ‪‪‪‪‪‪ = 0 else ‪‪‪‪‪‪ = ‪‪‪‪‪‪ +1 end end return ‪‪‪‪‪ end local end‪‪=‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪2u|‪‪sA|‪m|‪‪‪‪‪‪Ym|‪‪‪‪‪‪‪m|‪‪‪‪‪‪‪‪‪‪J|‪‪‪‪‪‪‪‪‪b|‪‪a|‪‪0|‪‪‪‪‪‪‪‪5|‪‪‪‪‪‪‪‪‪‪‪0y|‪‪‪‪‪‪‪T|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪OD|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪S|‪‪‪et|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪6A|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪bK|‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪6|‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪O|V2|‪‪‪‪‪‪‪‪‪‪‪‪dH|4t|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪N|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪uo|‪‪S9|‪‪‪‪‪‪‪‪‪‪‪4B|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪7g|‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪M|‪‪‪‪‪‪‪‪‪S0|‪J4|‪‪d|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪Gq|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪pi|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪f|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪k|‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪M|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪oC|‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪2a|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪L|‪‪‪‪‪‪‪‪‪‪‪‪‪EV|‪‪‪‪‪‪CG|‪‪‪‪‪‪‪‪‪‪‪‪‪|‪3d|‪‪‪‪‪‪‪‪‪zL|‪E|‪‪‪‪‪‪‪‪‪Cq|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪R|‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪mR|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪k|‪‪‪‪z|‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪Em|‪‪‪‪‪‪‪‪‪Wl|‪‪‪‪GA|‪‪|‪DM|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪w|‪‪‪‪‪‪7Q|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪|‪‪‪‪‪‪‪|‪‪‪‪2|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪bm|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪Y|‪‪‪‪‪‪‪‪‪‪‪‪s|‪‪‪‪‪‪tt|‪E0|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪zd|‪‪Q|‪‪‪‪‪‪‪‪‪‪‪‪‪jV|‪‪‪‪‪‪‪‪‪‪‪‪Qn|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪OK|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪TE|‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪xP|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪S|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪2v|‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪z|‪‪‪‪‪‪‪‪‪oQ|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪y|‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪G|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪CU|‪‪‪‪‪‪‪‪‪‪‪‪‪s5|‪‪‪Y|‪‪‪‪‪‪‪‪‪‪‪‪c|q|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪4i|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪mh|‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪k|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪f|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪Z|‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|RG|‪‪‪N|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪j|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪9c|‪‪‪‪‪‪‪‪‪‪‪‪‪d|‪‪‪‪‪‪‪‪‪‪‪‪k|‪ry|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪pJ|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪Nj|‪‪‪‪‪‪‪‪‪‪‪x|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪O|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|t|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪s6|‪‪‪‪‪‪‪‪‪‪‪Z|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪R|‪‪‪‪‪‪‪‪DP|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪R|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪P|‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪l|‪‪‪‪Ox|‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪|‪‪‪‪‪‪‪‪3|‪‪‪‪z|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪BY|‪‪AS|‪‪‪‪‪‪‪‪‪‪‪f9|‪|‪‪‪‪‪‪‪‪‪‪‪‪4|‪‪‪Q|‪‪‪‪‪‪‪‪‪1|‪‪‪‪‪‪‪‪‪43|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪9|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪2|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪u|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪g|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪6|‪‪‪‪‪‪‪4|‪‪‪‪‪‪‪0|‪‪‪‪‪‪‪‪‪‪v|‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪Q|‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪7|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪M|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪w|‪‪‪‪9N|‪‪‪G|‪‪‪Fj|‪‪‪|‪‪‪‪‪‪MJ|‪‪‪‪‪‪‪‪iN|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪oT|‪‪‪‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪‪‪‪‪Hj|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪j|‪‪‪‪‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪‪K9|p3|‪‪‪g|‪‪‪‪‪‪‪‪B|‪‪|‪‪m|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪t|‪‪‪WP|‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|R|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪H|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪2r|‪‪‪|‪‪|zZ|‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪4|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪aS|‪‪‪‪‪‪‪‪eT|‪‪‪‪‪‪‪‪‪‪‪‪y|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪Wt|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪U|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪sT|‪‪‪‪‪‪‪‪‪G|‪‪‪‪‪‪‪‪‪‪‪zI|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪9|‪|‪‪‪dm|‪‪‪‪‪‪‪‪‪‪FY|‪‪‪‪‪‪‪0|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪E|‪‪‪9R||‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪T8|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪N|‪zn|‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪A|‪‪‪‪‪|‪‪‪‪U|‪‪‪‪‪‪gM|jp|‪‪‪I|‪‪‪‪‪‪0H|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪Wr|jT|‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪p|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪P|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪v|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪v|]]local ‪‪‪elseif=end‪‪[‪‪‪‪‪‪‪[[|‪‪‪|dD|]]](end‪‪)local break‪=0 local ‪elseif=function ()break‪=break‪+1 if break‪>‪‪‪elseif then break‪=1 end return ‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪|‪vh|GI|‪‪‪WS|]]][‪‪‪‪‪‪‪[[‪‪‪‪Tj|‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪|‪‪‪|]]](end‪‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪M|‪‪‪‪‪‪‪‪0|‪‪‪‪ny|]]](end‪‪,break‪,break‪))%32 end local function‪‪‪=function (if‪‪‪‪‪‪‪‪)break‪=0 local ‪‪‪‪‪until={}for ‪‪‪‪‪‪‪‪‪‪‪‪‪and in ‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪|‪vh|GI|‪‪‪WS|]]][‪‪‪‪‪‪‪[[‪‪‪Io|c|‪‪‪‪U|‪‪‪‪‪‪‪‪|‪‪‪‪‪|‪W|]]](if‪‪‪‪‪‪‪‪,‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|]])do local ‪‪local=‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪|‪vh|GI|‪‪‪WS|]]][‪‪‪‪‪‪‪[[‪‪‪‪Tj|‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪|‪‪‪|]]](‪‪‪‪‪‪‪‪‪‪‪‪‪and)-‪elseif()if ‪‪local<0 then ‪‪local=‪‪local+256 end ‪‪‪‪‪until[#‪‪‪‪‪until+1]=‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪|‪vh|GI|‪‪‪WS|]]][‪‪‪‪‪‪‪[[‪‪‪‪‪|‪D|‪‪‪‪A|‪‪‪‪‪‪‪‪‪‪|]]](‪‪local)end return ‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪|‪‪‪‪z|‪‪‪‪13||‪‪‪|]]][‪‪‪‪‪‪‪[[‪‪‪‪‪|‪|9B|‪‪‪‪‪|‪‪‪‪1|‪‪‪‪‪‪‪‪|]]](‪‪‪‪‪until)[‪‪‪‪‪‪‪[[‪‪‪kz|‪‪‪‪‪‪‪‪‪‪t|‪‪‪‪‪‪‪‪n|‪‪‪‪sB|]]](‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪|‪‪‪‪z|‪‪‪‪13||‪‪‪|]]][‪‪‪‪‪‪‪[[‪‪‪‪‪|‪|9B|‪‪‪‪‪|‪‪‪‪1|‪‪‪‪‪‪‪‪|]]](‪‪‪‪‪until),‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪r|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪|‪vh|GI|‪‪‪WS|]]][‪‪‪‪‪‪‪[[‪‪‪‪‪|‪D|‪‪‪‪A|‪‪‪‪‪‪‪‪‪‪|]]](7),‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|]])end ‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪R|]]][‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪F|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪fN|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|]]]=function (or‪‪‪)local ‪‪‪‪‪‪‪end=‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪Pd|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪WB|‪‪‪‪‪‪‪‪‪‪‪‪‪‪tE|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪qu|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪4|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪W|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪2|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪n|]]local else‪‪‪‪‪=‪[‪‪‪‪‪‪‪[[‪‪7A|‪‪‪|‪‪‪‪zy|‪‪‪‪‪‪‪‪U|‪‪‪8p|]]][‪‪‪‪‪‪‪[[‪‪‪x0|‪‪‪|‪‪‪‪‪‪‪‪|‪LM|gT|‪‪‪P|‪|]]](2)if else‪‪‪‪‪ and else‪‪‪‪‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪k|‪J|‪|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪w|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪|]]]then ‪‪‪‪‪‪‪end=‪‪‪‪‪‪‪end..else‪‪‪‪‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪k|‪J|‪|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪w|‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪|]]]end ‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪m|‪‪‪‪‪|‪‪‪‪N|||]]](‪[‪‪‪‪‪‪‪[[‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪UZ|‪|z|‪‪‪‪‪‪‪‪‪m|‪PE||‪‪‪|‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪|‪‪‪‪‪‪‪‪‪‪|‪RI|GD|‪‪‪bk|]]](function‪‪‪(or‪‪‪),‪‪‪‪‪‪‪end))end \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/cfg.lua b/octolib/addon/lua/octolib/modules/cfg.lua new file mode 100644 index 0000000..c4ed7ae --- /dev/null +++ b/octolib/addon/lua/octolib/modules/cfg.lua @@ -0,0 +1,3 @@ +CFG = CFG or {} +octolib.shared('config/octolib_sh') +octolib.server('config/octolib_sv') diff --git a/octolib/addon/lua/octolib/modules/config/contract/sv_database.lua b/octolib/addon/lua/octolib/modules/config/contract/sv_database.lua new file mode 100644 index 0000000..8c43b2f --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/contract/sv_database.lua @@ -0,0 +1,21 @@ +hook.Add('octolib.db.init', 'octolib.config', function() + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS octolib_config ( + id VARCHAR(255) NOT NULL, + value TEXT, + PRIMARY KEY (id) + ) ENGINE=INNODB CHARACTER SET utf8 COLLATE utf8_general_ci + ]]) +end) + +function octolib.config.getDatabaseValue(id, callback) + octolib.db:PrepareQuery('select from octolib_config where id = ?', { id }, function(q, st, rows) + callback(rows[1] and rows[1].value or nil) + end) +end + +function octolib.config.setDatabaseValue(id, value, callback) + octolib.db:PrepareQuery('update octolib_config set value = ? where id = ?', { value, id }, callback and function(q, st) + callback(st and q:affectedRows() > 0) + end) +end diff --git a/octolib/addon/lua/octolib/modules/config/contract/sv_net.lua b/octolib/addon/lua/octolib/modules/config/contract/sv_net.lua new file mode 100644 index 0000000..cd63d44 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/contract/sv_net.lua @@ -0,0 +1,19 @@ +netstream.Listen('octolib.config.get', function(reply, ply, id) + local option = octolib.config.storedOptions[id] + if not option or not option:CanRead(ply) then + return reply() + end + + reply(option:Get()) +end) + +netstream.Listen('octolib.config.set', function(reply, ply, id, value) + local option = octolib.config.storedOptions[id] + if not option or not option:CanWrite(ply) then + return reply(false) + end + + option:Set(value) + + reply(true) +end) diff --git a/octolib/addon/lua/octolib/modules/config/meta/cl_option.lua b/octolib/addon/lua/octolib/modules/config/meta/cl_option.lua new file mode 100644 index 0000000..2658670 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/meta/cl_option.lua @@ -0,0 +1,27 @@ +local Option = octolib.meta.getOrCreate('configOption') + +function Option:Load(callback, fail) + if not self:CanRead(LocalPlayer()) then + return callback() + end + + netstream.Request('octolib.config.get', self.id) + :Then(function(value) + self:Set(value) + + if callback then + callback(value) + end + end) + :Catch(fail) +end + +function Option:Save(callback, fail) + if not self:CanWrite(LocalPlayer()) then + return callback(false) + end + + netstream.Request('octolib.config.set', self.id, self.value) + :Then(callback) + :Catch(fail) +end diff --git a/octolib/addon/lua/octolib/modules/config/meta/sh_option.lua b/octolib/addon/lua/octolib/modules/config/meta/sh_option.lua new file mode 100644 index 0000000..c86b64f --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/meta/sh_option.lua @@ -0,0 +1,44 @@ +local Option = octolib.meta.getOrCreate('configOption') + +function Option:On(name, id, callback) + self.listeners = self.listeners or {} + self.listeners[name] = self.listeners[name] or {} + self.listeners[name][id] = callback +end + +function Option:Off(name, id, callback) + if not self.listeners or not self.listeners[name] then + return + end + + self.listeners[name][id] = nil +end + +function Option:Emit(name, ...) + if not self.listeners or not self.listeners[name] then + return + end + + for _, callback in pairs(self.listeners[name]) do + callback(...) + end +end + +function Option:Get() + return self.value +end + +function Option:Set(value) + if not self:Validate(value) then + return + end + + self.value = value + + self:Emit('changed', value) +end + +function Option:Validate(value) + if type(self.validate) ~= 'function' then return true end + return self.validate(value) +end diff --git a/octolib/addon/lua/octolib/modules/config/meta/sv_option.lua b/octolib/addon/lua/octolib/modules/config/meta/sv_option.lua new file mode 100644 index 0000000..3e73f24 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/meta/sv_option.lua @@ -0,0 +1,21 @@ +local Option = octolib.meta.getOrCreate('configOption') + +function Option:Load(callback) + octolib.config.getDatabaseValue(self.id, function(value) + self:Set(value) + + if callback then + callback(value) + end + end) +end + +function Option:Save(callback) + octolib.config.setDatabaseValue(self.id, self.value, function() + self:Emit('changed', self.value) + + if callback then + callback(success) + end + end) +end diff --git a/octolib/addon/lua/octolib/modules/config/shared.lua b/octolib/addon/lua/octolib/modules/config/shared.lua new file mode 100644 index 0000000..377eea8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/shared.lua @@ -0,0 +1,47 @@ +-- TODO: implement config + +-- octolib.config = octolib.config or {} +-- octolib.config.storedOptions = octolib.config.storedOptions or {} +-- octolib.config.types = octolib.config.types or {} + +-- octolib.include.prefixed('meta') +-- octolib.include.prefixed('contract') +-- octolib.include.client('vgui') + +-- function octolib.config.option(id, optionConfig) +-- local option +-- if octolib.config.storedOptions[id] then +-- option = octolib.config.storedOptions[id] +-- else +-- option = setmetatable({ id = id }, octolib.meta.stored.configOption) +-- option:Load() +-- end + +-- if optionConfig then table.Merge(option, optionConfig) end + +-- octolib.config.storedOptions[id] = option + +-- return option +-- end + +-- function octolib.config.defineType(name, options) +-- local type +-- local changed = false +-- if octolib.config.types[name] then +-- type = octolib.config.types[name] + +-- if options then +-- table.Merge(type, options) +-- changed = true +-- end +-- else +-- type = options +-- changed = true +-- end + +-- if changed then +-- octolib.config.types[name] = type +-- end + +-- return type +-- end diff --git a/octolib/addon/lua/octolib/modules/config/vgui/octolib_config_menu.lua b/octolib/addon/lua/octolib/modules/config/vgui/octolib_config_menu.lua new file mode 100644 index 0000000..2126a4a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/vgui/octolib_config_menu.lua @@ -0,0 +1 @@ +-- TODO diff --git a/octolib/addon/lua/octolib/modules/config/vgui/octolib_config_option.lua b/octolib/addon/lua/octolib/modules/config/vgui/octolib_config_option.lua new file mode 100644 index 0000000..2126a4a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/config/vgui/octolib_config_option.lua @@ -0,0 +1 @@ +-- TODO diff --git a/octolib/addon/lua/octolib/modules/css.lua b/octolib/addon/lua/octolib/modules/css.lua new file mode 100644 index 0000000..2bf0529 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/css.lua @@ -0,0 +1,31 @@ +if CFG.disabledModules.css then return end + +if SERVER then + util.PrecacheModel('models/props/cs_assault/money.mdl') + + local texts = { + {'Кажется, у тебя не установлен (или установлен неправильно) контент CSS!'}, + {'Доброград активно использует этот контент, поэтому с твоей стороны могут наблюдаться различные "Error\'ки", "Эмо-текстуры" и так далее'}, + {'Инструкция по установке CSS-контента: ', {'https://octo.gg/gmod-css'}}, + {'Устранение прочих проблем с контентом: ', {'https://octo.gg/gmod-content'}}, + } + + hook.Add('PlayerFinishedLoading', 'dbg.csscheck', function(ply) + if ply.cssWarned then return end + ply.cssWarned = true + timer.Simple(5, function() + if not IsValid(ply) then return end + netstream.Request(ply, 'dbg.csscheck'):Then(function(res) + if res then return end + for _, text in ipairs(texts) do + octochat.talkTo(ply, Color(225,0,0), unpack(text)) + end + end) + end) + end) + +else + netstream.Listen('dbg.csscheck', function(reply) + reply(util.IsValidModel('models/props/cs_assault/money.mdl')) + end) +end diff --git a/octolib/addon/lua/octolib/modules/data-editor/client.lua b/octolib/addon/lua/octolib/modules/data-editor/client.lua new file mode 100644 index 0000000..d758afe --- /dev/null +++ b/octolib/addon/lua/octolib/modules/data-editor/client.lua @@ -0,0 +1,154 @@ +octolib.dataEditor = octolib.dataEditor or {} + +octolib.dataEditor.registered = octolib.dataEditor.registered or {} + +-- TODO: optimize saving by updating single row? +function octolib.dataEditor.register(id, data) + octolib.dataEditor.registered[id] = data +end + +function octolib.dataEditor.open(dataOrID) + local editor = istable(dataOrID) and dataOrID or octolib.dataEditor.registered[dataOrID or ''] + if not editor then return end + override = istable(override) and override or {} + + local cache = {} + + local f = vgui.Create 'DFrame' + f:SetSize(350, 500) + f:Center() + f:MakePopup() + f:SetTitle(override.name or editor.name or 'Редактирование данных') + + local mp = f:Add 'DPanel' + mp:Dock(FILL) + mp:DockMargin(0, 0, 0, 0) + mp:DockPadding(0, 0, 0, 0) + mp:SetPaintBackground(false) + + local l = mp:Add 'DListView' + l:Dock(FILL) + local columns = override.columns or editor.columns or {{ field = '_id', name = 'ID' }} + for _, col in ipairs(columns) do + local c = l:AddColumn(col.name) + if col.width then c:SetFixedWidth(col.width) end + end + + local function updateList() + l:Clear() + for id, row in ipairs(cache) do + local data = octolib.table.mapSequential(columns, function(v) return row[v.field] end) + local line = l:AddLine(unpack(data)) + line.rowID = id + line.row = row + end + end + + local loadFunc = override.load or editor.load + local function load() + loadFunc(function(rows) + cache = rows + updateList() + end) + end + + l:AddLine('Загрузка...') + load() + + local editFunc = override.edit or editor.edit + local saveFunc = override.save or editor.save + local function edit(row, id) + editFunc(row, function(data) + if data then + cache[id] = data + else + table.remove(cache, id) + end + + saveFunc(cache) + updateList() + end) + end + + function l:DoDoubleClick(i, line) + edit(line.row, line.rowID) + end + + function l:OnRowRightClick(i, line) + octolib.menu({ + {'Изменить', 'icon16/pencil.png', function() + edit(line.row, line.rowID) + end}, + {'Экспорт', 'icon16/page_go.png', function() + SetClipboardText(pon.encode(line.row)) + Derma_Message('Код скопирован, теперь ты можешь его куда-нибудь вставить', 'Успех', 'Понятно') + end}, + {'Удалить', 'icon16/delete.png', function() + table.remove(cache, line.rowID) + saveFunc(cache) + updateList() + end}, + }):Open() + end + + local bp = mp:Add 'DPanel' + bp:Dock(TOP) + bp:DockMargin(0, 0, 0, 5) + bp:SetTall(25) + bp:SetPaintBackground(false) + + local newFunc = override.new or editor.new + local bNew = octolib.button(bp, 'Создать', function() + newFunc(function(data) + if not data then return end + + table.insert(cache, data) + + saveFunc(cache) + updateList() + end) + end) + bNew:Dock(RIGHT) + bNew:DockMargin(5, 0, 0, 0) + bNew:SizeToContentsX(20) + + local validateFunc = override.validate or editor.validate + local bImport = octolib.button(bp, 'Импорт', octolib.fStringRequest('Импорт данных', 'Вставь код, полученный при экспорте', '', function(s) + local ok, data = pcall(pon.decode, s) + if ok and (not validateFunc or validateFunc(data)) then + table.insert(cache, data) + saveFunc(cache) + updateList() + else + Derma_Message('Не получилось расшифровать код, проверь, правильно ли ты его скопировал', 'Ошибка', 'Понятно') + end + end)) + bImport:Dock(RIGHT) + bImport:DockMargin(5, 0, 0, 0) + bImport:SizeToContentsX(20) + + local bRefresh = octolib.button(bp, 'Обновить', load) + bRefresh:Dock(LEFT) + bRefresh:DockMargin(0, 0, 5, 0) + bRefresh:SizeToContentsX(20) + + return { + frame = f, + panel = mp, + getCache = function() + return cache + end, + } +end + +-- debug +-- concommand.Add('octolib_data', function(ply, cmd, args, argStr) +-- local editor = octolib.dataEditor.registered[argStr or ''] +-- if not editor then return print('Такого редактора не существует') end + +-- octolib.dataEditor.open(argStr) +-- end, function(cmd, argStr) +-- return octolib.table.mapSequential(table.GetKeys(octolib.dataEditor.registered), function(key) +-- return key:find(argStr:Trim()) and ('octolib_data ' .. key) or nil +-- end) +-- end) diff --git a/octolib/addon/lua/octolib/modules/data-editor/server.lua b/octolib/addon/lua/octolib/modules/data-editor/server.lua new file mode 100644 index 0000000..209dc11 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/data-editor/server.lua @@ -0,0 +1 @@ +-- TODO: diff --git a/octolib/addon/lua/octolib/modules/delay/client.lua b/octolib/addon/lua/octolib/modules/delay/client.lua new file mode 100644 index 0000000..9cfa190 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/delay/client.lua @@ -0,0 +1,89 @@ +local delays = {} + +surface.CreateFont('octolib.use', { + font = 'Calibri', + extended = true, + size = 82, + weight = 350, +}) + +surface.CreateFont('octolib.use-sh', { + font = 'Calibri', + extended = true, + size = 82, + weight = 350, + blursize = 5, +}) + +netstream.Hook('octolib.delay', function(id, active, text, time) + + local id = id + local active = active + + if active then + delays[id] = { + text = text, + start = CurTime(), + time = time - CurTime(), + } + else + delays[id] = nil + end + +end) + +local cx, cy = 0, 0 +local size = 40 +local p1, p2 = {}, {} +for i = 1, 36 do + local a1 = math.rad((i-1) * -10 + 180) + local a2 = math.rad(i * -10 + 180) + p1[i] = { x = cx + math.sin(a1) * size, y = cy + math.cos(a1) * size } + p2[i] = { + { x = cx, y = cy }, + { x = cx + math.sin(a1) * size, y = cy + math.cos(a1) * size }, + { x = cx + math.sin(a2) * size, y = cy + math.cos(a2) * size }, + } +end + +local override +hook.Add('dbg-view.chShouldDraw', 'octolib.delay', function() + + override = table.Count(delays) > 0 + if override then return true end + +end) + +hook.Add('dbg-view.chPaint', 'octolib.delay', function(tr, icon) + + for id, data in pairs(delays) do + local segs = math.min(math.ceil((CurTime() - data.start) / data.time * 36), 36) + local text = data.text .. ('.'):rep(math.floor(CurTime() * 2 % 4)) + draw.SimpleText(text, 'octolib.use-sh', 0 + 60, 0, color_black, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(text, 'octolib.use', 0 + 60, 0, Color(255,255,255, 200), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + draw.NoTexture() + surface.SetDrawColor(255,255,255, 50) + surface.DrawPoly(p1) + + surface.SetDrawColor(255,255,255, 150) + for i = 1, segs do + surface.DrawPoly(p2[i]) + end + + return true + end + +end) + +hook.Add('dbg-view.chOverride', 'octolib.delay', function(tr, icon) + + local ply = LocalPlayer() + if override and (not tr.Hit or tr.Fraction > 0.03) then + local aim = (ply.viewAngs or ply:EyeAngles()):Forward() + tr.HitPos = ply:GetShootPos() + aim * 60 + tr.HitNormal = -aim + tr.Fraction = 0.03 + end + +end) diff --git a/octolib/addon/lua/octolib/modules/delay/server.lua b/octolib/addon/lua/octolib/modules/delay/server.lua new file mode 100644 index 0000000..37c373f --- /dev/null +++ b/octolib/addon/lua/octolib/modules/delay/server.lua @@ -0,0 +1,84 @@ +util.AddNetworkString 'octolib.delay' +local activeDelays = {} + +local function removeDelay(id) + + if not activeDelays[id] then return end + + local ply = activeDelays[id].ply + if IsValid(ply) then + netstream.Start(ply, 'octolib.delay', id, false) + ply:SetNetVar('currentAction', nil) + end + + activeDelays[id] = nil + +end + +local meta = FindMetaTable 'Player' +function meta:DelayedAction(id, text, delayData, periodData) + + id = id .. self:SteamID() + + if activeDelays[id] and isfunction(activeDelays[id].fail) then activeDelays[id].fail() end + if delayData.time <= 0 then + removeDelay(id) + delayData.succ() + return + end + + if delayData.check and not delayData.check() then + return isfunction(delayData.fail) and delayData.fail() + end + + local finish = CurTime() + delayData.time + activeDelays[id] = { + ply = self, + finish = finish, + check = delayData.check, + succ = delayData.succ, + fail = delayData.fail, + } + + if periodData then + if periodData.inst then periodData.action() end + timer.Create(id, periodData.time, periodData.reps or 0, function() + if CurTime() > finish or not activeDelays[id] or not IsValid(self) then return timer.Destroy(id) end + periodData.action() + end) + end + + netstream.Start(self, 'octolib.delay', id, true, text, finish) + self:SetNetVar('currentAction', text) + +end + +function meta:GetRunningAction(id) + + id = id .. self:SteamID() + return activeDelays[id] + +end + +hook.Add('Think', 'octolib.delay', function() + + for id, data in pairs(activeDelays) do + if not IsValid(data.ply) then + removeDelay(id) + if isfunction(data.fail) then + data.fail() + end + end + + if data.check and not data.check() then + removeDelay(id) + if isfunction(data.fail) then + data.fail() + end + elseif CurTime() >= data.finish then + removeDelay(id) + data.succ() + end + end + +end) diff --git a/octolib/addon/lua/octolib/modules/devtools/shared.lua b/octolib/addon/lua/octolib/modules/devtools/shared.lua new file mode 100644 index 0000000..4f8d2f3 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/shared.lua @@ -0,0 +1,3 @@ +octolib.devtools = octolib.devtools or {} + +octolib.include.client('tools') diff --git a/octolib/addon/lua/octolib/modules/devtools/tools/bones.lua b/octolib/addon/lua/octolib/modules/devtools/tools/bones.lua new file mode 100644 index 0000000..8816c02 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/tools/bones.lua @@ -0,0 +1,88 @@ +function octolib.devtools.bones(ent, bones) + + if not CFG.dev and not LocalPlayer():IsSuperAdmin() then return end + + if not bones then return end + + local w = vgui.Create 'DFrame' + w:SetSize(300, 500) + w:AlignBottom(10) + w:AlignLeft(10) + + local prop = w:Add 'DProperties' + prop:Dock(FILL) + + local rowLabelW = 25 + local function rowLayout(self) + self:SetTall(20) + self.Label:SetWide(rowLabelW) + end + + local function setupRow(cat, name, change, decimals) + local r = prop:CreateRow(cat, name) + r:Setup('Float', { min = -180, max = 180 }) + r:SetValue(0) + r.PerformLayout = rowLayout + r.Paint = nil + + local l = r:GetChild(1):GetChild(0) + l.Paint = function() end + l:GetChild(0):SetDecimals(decimals or 1) + function r:DataChanged(v) change(v) end + + return r + end + + for boneID, ang in pairs(bones) do + local function updateAng(ang) + ent:ManipulateBoneAngles(boneID, ang) + end + + local boneName = ent:GetBoneName(boneID) + setupRow(boneName, 'P', function(v) ang.p = math.Round(v, 1) updateAng(ang) end):SetValue(ang.p) + setupRow(boneName, 'Y', function(v) ang.y = math.Round(v, 1) updateAng(ang) end):SetValue(ang.y) + setupRow(boneName, 'R', function(v) ang.r = math.Round(v, 1) updateAng(ang) end):SetValue(ang.r) + end + + for name, pnl in pairs(prop.Categories) do + pnl.Container.Paint = octolib.func.zero + pnl.Expand:DoClick() + end + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(20) + b:SetText(L.copy) + function b:DoClick() + local parts = {} + for k, v in pairs(bones) do + if v.p ~= 0 or v.y ~= 0 or v.r ~= 0 then + table.insert(parts, ('[\'%s\'] = Angle(%.1f, %.1f, %.1f),'):format(ent:GetBoneName(k), math.Round(v.p, 1), math.Round(v.y, 1), math.Round(v.r, 1))) + end + end + SetClipboardText(table.concat(parts, '\n')) + end + + function w:OnClose() + for i = 0, ent:GetBoneCount() - 1 do + ent:ManipulateBoneAngles(i, Angle()) + end + end + + return w + +end + +concommand.Add('octolib_tool_bones', function(ply) + + local ent = LocalPlayer():GetEyeTrace().Entity + if not IsValid(ent) then ent = LocalPlayer() end + + local bones = {} + for i = 0, ent:GetBoneCount() - 1 do + bones[i] = ent:GetManipulateBoneAngles(i) or Angle() + end + + octolib.devtools.bones(ent, bones) + +end) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/devtools/tools/child.lua b/octolib/addon/lua/octolib/modules/devtools/tools/child.lua new file mode 100644 index 0000000..0eb7050 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/tools/child.lua @@ -0,0 +1,31 @@ +concommand.Add('octolib_tool_child', function(ply, cmd, args, argStr) + + if not CFG.dev and not LocalPlayer():IsSuperAdmin() then return end + + local parent = LocalPlayer():GetEyeTrace().Entity + if not IsValid(parent) then parent = LocalPlayer() end + + if not args or not args[1] or IsUselessModel(args[1]) then + print(L.format_command) + print(L.model_space) + return + end + + local csEnt = octolib.createDummy(args[1]) + if not IsValid(csEnt) then + print(L.failure_create_object) + return + end + + local data = octolib.devtools.positions(Vector(0, 0, 0), Angle(0, 0, 0), 1) + function data.updatePos(pos) csEnt:SetLocalPos(data.pos) end + function data.updateAng(ang) csEnt:SetLocalAngles(data.ang) end + function data.updateScale(scale) csEnt:SetModelScale(data.scale) end + function data.onClose() csEnt:Remove() end + + csEnt:SetParent(parent, tonumber(args[2]) or 1) + csEnt:SetLocalPos(data.pos) + csEnt:SetLocalAngles(data.ang) + csEnt:SetModelScale(data.scale) + +end) diff --git a/octolib/addon/lua/octolib/modules/devtools/tools/getpos.lua b/octolib/addon/lua/octolib/modules/devtools/tools/getpos.lua new file mode 100644 index 0000000..50766e6 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/tools/getpos.lua @@ -0,0 +1,8 @@ +concommand.Add('octolib_tool_getpos', function(ply, cmd, args, argStr) + local pos, ang = ply:GetPos(), ply:EyeAngles() + local text = ('[%d %d %d]{%d %d %d}'):format(pos.x, pos.y, pos.z, ang.p, ang.y, ang.r) + + print(text) + SetClipboardText(text) + print('Copied to clipboard!') +end) diff --git a/octolib/addon/lua/octolib/modules/devtools/tools/posanglist.lua b/octolib/addon/lua/octolib/modules/devtools/tools/posanglist.lua new file mode 100644 index 0000000..e38fd06 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/tools/posanglist.lua @@ -0,0 +1,74 @@ +local icon = octolib.icons.silk16('location_pin', 'smooth mips') + +concommand.Add('octolib_tool_posanglist', function(ply, cmd, args, argStr) + if not CFG.dev and not LocalPlayer():IsSuperAdmin() then return end + + local model = argStr:Trim() ~= '' and argStr or 'models/hunter/blocks/cube05x05x05.mdl' + + local showIcons = false + hook.Add('HUDPaint', 'octolib.posanglist', function() + if not octolib.flyEditor.active then + return hook.Remove('HUDPaint', 'octolib.posanglist') + end + + if not showIcons then return end + + for _, movable in pairs(octolib.flyEditor.movables) do + local pos = movable.csent:GetPos():ToScreen() + surface.SetMaterial(icon) + surface.SetDrawColor(255,255,255, 255) + surface.DrawTexturedRect(pos.x - 8, pos.y - 8, 16, 16) + end + end) + + local function clearMovables() + for _, movable in pairs(octolib.flyEditor.movables) do + movable:Remove() + end + end + + octolib.flyEditor.start({ + noclip = true, + maxDist = 0, + buttons = { + {'Отображать сквозь карту', octolib.icons.silk32('eye_close'), function(self) + showIcons = not showIcons + self:SetImage(showIcons and octolib.icons.silk32('eye') or octolib.icons.silk32('eye_close')) + end}, + {'Импорт', octolib.icons.silk32('map_add'), function() + Derma_StringRequest('Импорт данных', 'Введи код, полученный при экспорте', '', function(input) + for row in input:gmatch('{(.-)}') do + local posX, posY, posZ = row:match('Vector%((.-),(.-),(.-)%)') + local angP, angY, angR = row:match('Angle%((.-),(.-),(.-)%)') + octolib.createMoveable({ + name = 'Позиция', + model = model, + pos = Vector(tonumber(posX), tonumber(posY), tonumber(posZ)), + ang = Angle(tonumber(angP), tonumber(angY), tonumber(angR)), + }) + end + end) + end}, + {'Экспорт', octolib.icons.silk32('map_go'), function() + local out = {} + for _, movable in pairs(octolib.flyEditor.movables) do + local posX, posY, posZ = movable.csent:GetPos():Unpack() + local angP, angY, angR = movable.csent:GetAngles():Unpack() + out[#out + 1] = ('{ Vector(%g, %g, %g), Angle(%g, %g, %g) },'):format(posX, posY, posZ, angP, angY, angR) + end + SetClipboardText(table.concat(out, '\n')) + end}, + {'Очистить', octolib.icons.silk32('map_delete'), function() + clearMovables() + end}, + }, + canCreate = { + {'Новая позиция', { + model = model, + name = 'Позиция', + }} + }, + }, function(movables) + + end) +end) diff --git a/octolib/addon/lua/octolib/modules/devtools/tools/poseditor.lua b/octolib/addon/lua/octolib/modules/devtools/tools/poseditor.lua new file mode 100644 index 0000000..cffd962 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/tools/poseditor.lua @@ -0,0 +1,86 @@ +function octolib.devtools.positions(pos, ang, scale) + pos = pos or Vector(0, 0, 0) + ang = ang or Angle(0, 0, 0) + + local toReturn = { + pos = pos, + ang = ang, + scale = scale or 1, + } + + local w = vgui.Create 'DFrame' + w:SetSize(300, 300) + w:AlignBottom(10) + w:AlignLeft(10) + toReturn.panel = w + + local prop = w:Add 'DProperties' + prop:Dock(FILL) + + local rowLabelW = 25 + local function rowLayout(self) + + self:SetTall(20) + self.Label:SetWide(rowLabelW) + + end + + local function setupRow(cat, name, change, decimals) + local r = prop:CreateRow(cat, name) + r:Setup('Float', { min = -1000, max = 1000 }) + r:SetValue(0) + r.PerformLayout = rowLayout + r.Paint = nil + + local l = r:GetChild(1):GetChild(0) + l.Paint = function() end + l:GetChild(0):SetDecimals(decimals or 1) + function r:DataChanged(v) change(v) end + + return r + end + + local function rowCatPaint(self, w, h) end + + local function updatePos(pos) + if not toReturn.updatePos then return end + toReturn.updatePos(pos) + end + + local function updateAng(ang) + if not toReturn.updateAng then return end + toReturn.updateAng(ang) + end + + local function updateScale(scale) + if not toReturn.updateScale then return end + toReturn.updateScale(scale) + end + + setupRow(L.position, 'X', function(v) pos.x = math.Round(v, 1) updatePos(pos) end):SetValue(pos.x) + setupRow(L.position, 'Y', function(v) pos.y = math.Round(v, 1) updatePos(pos) end):SetValue(pos.y) + setupRow(L.position, 'Z', function(v) pos.z = math.Round(v, 1) updatePos(pos) end):SetValue(pos.z) + setupRow(L.angle, 'P', function(v) ang.p = math.Round(v, 1) updateAng(ang) end):SetValue(ang.p) + setupRow(L.angle, 'Y', function(v) ang.y = math.Round(v, 1) updateAng(ang) end):SetValue(ang.y) + setupRow(L.angle, 'R', function(v) ang.r = math.Round(v, 1) updateAng(ang) end):SetValue(ang.r) + setupRow(L.size, 'S', function(v) toReturn.scale = math.Round(v, 2) updateScale(toReturn.scale) end, 2):SetValue(toReturn.scale) + + for name, pnl in pairs(prop.Categories) do + pnl.Container.Paint = rowCatPaint + end + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(20) + b:SetText(L.copy) + function b:DoClick() + SetClipboardText(('Vector(%.1f, %.1f, %.1f), Angle(%.1f, %.1f, %.1f), %.1f'):format(pos.x, pos.y, pos.z, ang.p, ang.y, ang.r, toReturn.scale)) + end + + function w:OnClose() + if not toReturn.onClose then return end + toReturn.onClose(self) + end + + return toReturn +end \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/devtools/tools/poslist.lua b/octolib/addon/lua/octolib/modules/devtools/tools/poslist.lua new file mode 100644 index 0000000..9cdb344 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/devtools/tools/poslist.lua @@ -0,0 +1,100 @@ +concommand.Add('octolib_tool_poslist', function(ply, cmd, args, argStr) + if not CFG.dev and not LocalPlayer():IsSuperAdmin() then return end + + local cache = {} + + local w = vgui.Create 'DFrame' + w:SetSize(200, 400) + w:AlignBottom(10) + w:AlignLeft(10) + w:SetTitle('Редактор позиций') + + local l = w:Add 'DListView' + l:Dock(FILL) + l:AddColumn('ID'):SetFixedWidth(20) + l:AddColumn(L.position) + function l:Rebuild() + self:Clear() + for i, v in ipairs(cache) do + local line = self:AddLine(i, ('X:%s Y:%s Y:%s'):format(v.x, v.y, v.z)) + line.id = i + end + end + function l:OnRowRightClick(i, line) + local m = DermaMenu() + m:AddOption(L.delete, function() + table.remove(cache, line.id) + l:Rebuild() + end) + m:Open() + end + + local function roundVector(pos) + + pos.x = math.Round(pos.x) + pos.y = math.Round(pos.y) + pos.z = math.Round(pos.z) + + return pos + + end + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(20) + b:SetText(L.menu) + function b:DoClick() + octolib.menu({ + {'Экспорт', nil, function() + local res = '' + for i, v in ipairs(cache) do + res = res .. ('Vector(%s, %s, %s),\n'):format(v.x, v.y, v.z) + end + SetClipboardText(res) + end}, + {'Импорт', nil, octolib.fStringRequest('Импорт позиций', 'Вставь текст, полученный при экспорте', '', function(text) + for s in string.gmatch(text, 'Vector%((.-)%)') do + table.insert(cache, roundVector(Vector(s:gsub(',', '')))) + end + l:Rebuild() + end)}, + {'Очистить', nil, function() + table.Empty(cache) + l:Rebuild() + end}, + }):Open() + end + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(20) + b:SetText(L.position_camera) + function b:DoClick() + table.insert(cache, roundVector(EyePos())) + l:Rebuild() + end + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(20) + b:SetText(L.position_player) + function b:DoClick() + table.insert(cache, roundVector(LocalPlayer():GetPos())) + l:Rebuild() + end + + hook.Add('HUDPaint', 'draw-positions', function() + for i, v in ipairs(cache) do + local pos = v:ToScreen() + local x, y = pos.x, pos.y + draw.RoundedBox(0, x - 3, y - 3, 6, 6, color_black) + draw.RoundedBox(0, x - 2, y - 2, 4, 4, color_white) + draw.SimpleText(i, 'DermaDefault', x, y + 5, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + end) + + function w:OnClose() + cache = nil + hook.Remove('HUDPaint', 'draw-positions') + end +end) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/entity.lua b/octolib/addon/lua/octolib/modules/entity.lua new file mode 100644 index 0000000..9d86e3a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/entity.lua @@ -0,0 +1,81 @@ +octolib.entity = octolib.entity or {} + +function octolib.whenNotNull(ents, func, timeout) + + local tName = 'wnn' .. tostring(ents) + + local cache = {} + for i = 1, #ents do cache[i] = i end + + for i = #cache, 1, -1 do + local ent = ents[cache[i]] + if IsValid(ent) then + func(ent, cache[i]) + table.remove(cache, i) + end + end + + if #cache < 1 then + hook.Remove('pon.entityCreated', tName) + end + + hook.Add('pon.entityCreated', tName, function(ent, tbl, key) + if tbl ~= ents then return end + + func(ent, key) + table.RemoveByValue(cache, key) + + if #cache < 1 then + cache = nil + hook.Remove('pon.entityCreated', tName) + end + end) + + if timeout then + timer.Create(tName, timeout, 1, function() + cache = nil + hook.Remove('pon.entityCreated', tName) + end) + end + +end + +function octolib.entity.dummyTrace(ent) + local pos = ent:GetPos() + return { + FractionLeftSolid = 0, + HitNonWorld = true, + Fraction = 0, + Entity = ent, + HitPos = pos, + HitNormal = Vector(0,0,0), + HitBox = 0, + Normal = Vector(1,0,0), + Hit = true, + HitGroup = 0, + MatType = 0, + StartPos = pos, + PhysicsBone = 0, + WorldToLocal = Vector(0,0,0), + } +end + +local entityMeta = FindMetaTable 'Entity' + +entityMeta.SetBodygroup = octolib.func.detour( + entityMeta.SetBodygroup, + 'Entity:SetBodyGroup', + function(original, ent, bgID, val) + if hook.Run('EntityBodygroupChange', ent, bgID, val) == true then return end + original(ent, bgID, val) + end +) + +entityMeta.SetSubMaterial = octolib.func.detour( + entityMeta.SetSubMaterial, + 'Entity:SetSubMaterial', + function(original, ent, index, material) + if hook.Run('EntitySubMaterialChange', ent, index, material) == true then return end + original(ent, index, material) + end +) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/entries/client.lua b/octolib/addon/lua/octolib/modules/entries/client.lua new file mode 100644 index 0000000..a1db734 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/entries/client.lua @@ -0,0 +1,376 @@ +local function addElement(p, k, field) + local p_f = p:Add 'DPanel' + p_f:Dock(TOP) + p_f:DockMargin(0,0,0,5) + p_f:DockPadding(5,5,5,5) + p_f:SetPaintBackground(false) + function p_f:PerformLayout() + if IsValid(self) then + self:SizeToChildren(false, true) + end + end + + if field.name then + local l = p_f:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5,0,5,5) + l:SetTall(25) + l:SetFont('octolib.normal') + l:SetText(field.name) + end + + if field.desc then + local l = p_f:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetText(field.desc) + l:SetWrap(true) + function l:PerformLayout() + self:SizeToContentsY() + end + end + + if field.type == 'strShort' then + local e = p_f:Add 'DTextEntry' + e:Dock(TOP) + e:SetTall(30) + e:SetUpdateOnType(true) + if field.numeric then e:SetNumeric(true) end + if field.ph then e:SetPlaceholderText(field.ph) end + e.OnKeyCode = function(e) + e.prevCaret = e:GetCaretPos() + end + e.OnValueChange = function(e, val) + if field.len and utf8.len(val) > field.len then + local caret = e.prevCaret or utf8.len(val) + if utf8.len(p.cache[k] or '') < field.len then + val = utf8.sub(val, 1, field.len) + caret = field.len + else val = p.cache[k] or utf8.sub(val, 1, field.len) end + e:SetText(val) + e:SetCaretPos(caret) + end + e.prevVal = p.cache[k] + val = string.Trim(val) + p.cache[k] = val ~= '' and val or nil + p.checkFields() + if val ~= field.default then + p.saved = false + end + end + if field.default then + e:SetValue(field.default) + end + p.updateFuncs[k] = function(val) + if val ~= nil then e:SetValue(val) + elseif field.default ~= nil then e:SetValue(field.default) + else e:SetValue('') end + end + elseif field.type == 'strLong' then + local e = p_f:Add 'DTextEntry' + e:Dock(TOP) + e:SetTall(field.tall or 200) + e:SetUpdateOnType(true) + e:SetMultiline(true) + e:SetWrap(true) + e:SetContentAlignment(7) + if field.ph then e:SetPlaceholderText(field.ph) end + e.OnKeyCode = function(e) + e.prevCaret = e:GetCaretPos() + end + e.OnValueChange = function(e, val) + if field.len and utf8.len(val) > field.len then + local caret = e.prevCaret or utf8.len(val) + if utf8.len(p.cache[k] or '') < field.len then + val = utf8.sub(val, 1, field.len) + caret = field.len + else val = p.cache[k] or utf8.sub(val, 1, field.len) end + e:SetText(val) + e:SetCaretPos(caret) + end + e.prevVal = p.cache[k] + val = string.Trim(val) + p.cache[k] = val ~= '' and val or nil + p.checkFields() + if val ~= field.default then + p.saved = false + end + end + if field.default then + e:SetValue(field.default) + end + p.updateFuncs[k] = function(val) + if val ~= nil then e:SetValue(val) + elseif field.default ~= nil then e:SetValue(field.default) + else e:SetValue('') end + end + elseif field.type == 'numSlider' then + local e = p_f:Add 'DNumSlider' + e:Dock(TOP) + e:DockMargin(5,5,5,5) + e:SetTall(30) + e:SetDecimals(field.dec or 2) + e:SetText(field.txt or L.choose_value) + e:SetMin(field.min or 0) + e:SetMax(field.max or 100) + e:SetValue(field.val or field.min or 0) + e.OnValueChanged = function(e, val) + p.cache[k] = val or nil + p.checkFields() + if val ~= field.default then + p.saved = false + end + end + if field.default then + e:SetValue(field.default) + end + p.updateFuncs[k] = function(val) + if val ~= nil then e:SetValue(val) + elseif field.default ~= nil then e:SetValue(field.default) + elseif field.min ~= nil then e:SetValue(field.min) + else e:SetValue(0) end + end + elseif field.type == 'comboBox' then + local e = p_f:Add 'DComboBox' + e:Dock(TOP) + e:SetTall(30) + e:SetSortItems(false) + e.OnSelect = function(e, i, v, val) + p.cache[k] = val ~= '' and val or nil + p.checkFields() + if val ~= field.default then + p.saved = false + end + end + if field.opts then + for i, v in ipairs(field.opts) do + e:AddChoice(unpack(v)) + end + end + p.updateFuncs[k] = function(val) + if val == nil then val = field.default end + for k,v in pairs(e.Data) do + if v == val or val == nil then + e:ChooseOptionID(k) + return + end + end + end + elseif field.type == 'check' then + local e = p_f:Add 'DCheckBoxLabel' + e:Dock(TOP) + e:DockMargin(0,5,0,5) + e:SetTall(30) + e:SetChecked(field.check or false) + e:SetText(field.txt or L.there_is) + e.OnChange = function(e, val) + p.cache[k] = val + p.checkFields() + if val ~= field.default then + p.saved = false + end + end + e:SetValue(field.default or false) + p.updateFuncs[k] = function(val) + if val ~= nil then e:SetValue(val) + elseif field.default ~= nil then e:SetValue(field.default) + else e:SetValue(false) end + end + elseif field.type == 'color' then + local e = p_f:Add 'DColorMixer' + e:Dock(TOP) + e:DockMargin(0,5,0,5) + e:SetTall(120) + e:SetAlphaBar(true) + e:SetWangs(true) + e:SetPalette(false) + e.ValueChanged = function(e, col) + p.cache[k] = col + p.checkFields() + if col ~= field.default then + p.saved = false + end + end + e:SetColor(field.default or Color(255,255,255, 255)) + p.updateFuncs[k] = function(val) + if val ~= nil then e:SetColor(val) + elseif field.default ~= nil then e:SetColor(field.default) + else e:SetColor(Color(255,255,255, 255)) end + end + elseif field.type == 'iconPicker' then + local e = octolib.textEntryIcon(p_f, nil, field.path) + e:SetTall(30) + e:SetUpdateOnType(true) + e.OnValueChange = function(e) + local val = string.Trim(e:GetText()) + p.cache[k] = val ~= '' and val or nil + p.checkFields() + if val ~= field.default then + p.saved = false + end + end + if field.default then + e:SetValue(field.default) + end + p.updateFuncs[k] = function(val) + if val ~= nil then e:SetValue(val) + elseif field.default ~= nil then e:SetValue(field.default) + else e:SetValue('') end + end + end +end + +function octolib.entries.gui(title, data, res, close) + + local f = vgui.Create('DFrame') + f:SetSize(600, 500) + f:Center() + f:MakePopup() + f:SetTitle(title) + + local l = f:Add('DListView') + l:Dock(LEFT) + l:DockMargin(0, 0, 5, 0) + l:SetWide(200) + l:AddColumn('Название') + l:SetHideHeaders(true) + l:SetMultiSelect(false) + + local p = f:Add 'DScrollPanel' + p:Dock(FILL) + p.cache = {} + p.updateFuncs = {} + p.saved = true + + local bp = f:Add 'DPanel' + bp:Dock(BOTTOM) + bp:SetTall(30) + bp:SetPaintBackground(false) + + local entries, curID, bAdd = {} + local function refresh(selectID) + l:Clear() + for i, entry in ipairs(entries) do + local line = l:AddLine(entry[data.labelIndex]) + line.entryID = i + end + p:SetVisible(#l.Sorted > 0) + bp:SetVisible(#l.Sorted > 0) + bAdd:SetEnabled(#entries < (data.maxEntries or 10)) + l:ClearSelection() + l:SelectItem(l.Sorted[selectID or #l.Sorted]) + curID = selectID or #l.Sorted + end + + local function save() + p.saved = true + if not curID then return end + + entries[curID] = table.Copy(p.cache) + res(entries) + refresh(curID) + end + + local bSave = octolib.button(bp, 'Сохранить', save) + bSave:Dock(LEFT) + bSave:SetWide(190) + + local bRemove = octolib.button(bp, 'Удалить', function() + if not curID then return end + + local upd = entries[curID].__new + table.remove(entries, curID) + if not upd then res(entries) end + refresh() + p.saved = true + end) + bRemove:Dock(RIGHT) + bRemove:SetWide(190) + + function p.checkFields() + for k, field in pairs(data.fields) do + if field.required and (not p.cache[k] or string.Trim(p.cache[k]) == '') then + bSave:SetEnabled(false) + bSave:SetText(L.fill_required) + return + end + end + + bSave:SetEnabled(true) + bSave:SetText('Сохранить') + end + + for k,v in pairs(data.fields) do + addElement(p, k, v) + end + + for _,entry in ipairs(data.entries) do + entries[#entries + 1] = entry + end + + local function load(id) + local entry = id and entries[id] + if not entry then return end + curID = id + for k,v in pairs(p.updateFuncs) do + v(entry[k]) + end + p.saved = true + end + + function l:OnRowSelected(i, line) + local id = line.entryID + if not p.saved then + octolib.confirmDialog(f, 'Сохранить изменения?', function(b) + p.saved = true + if b then save() end + load(id) + self:ClearSelection() + self:SelectItem(self.Sorted[id]) + end) + else load(id) end + end + + bAdd = octolib.button(l, 'Создать', function() + if not curID then return end + if not p.saved then + octolib.confirmDialog(f, 'Сохранить изменения?', function(b) + p.saved = true + if b then save() end + entries[#entries + 1] = {[data.labelIndex] = data.fields[data.labelIndex].default or 'Новая запись', __new = true} + refresh() + end) + else + entries[#entries + 1] = {[data.labelIndex] = data.fields[data.labelIndex].default or 'Новая запись', __new = true} + refresh() + end + end) + bAdd:Dock(BOTTOM) + bAdd:SetTall(20) + + refresh() + + local oClose = f.Close + function f:Close() + if not p.saved then + octolib.confirmDialog(self, 'Сохранить изменения?', function(b) + p.saved = true + if b then save() end + oClose(self) + end) + else oClose(self) end + end + + function f:OnClose() + if isfunction(close) then close() end + end + +end + +netstream.Hook('octolib.guiEntries', function(id, title, data) + octolib.entries.gui(title, data, function(res) + netstream.Start('octolib.guiEntries', id, res) + end, function() + netstream.Start('octolib.guiEntries.close', id) + end) +end) diff --git a/octolib/addon/lua/octolib/modules/entries/server.lua b/octolib/addon/lua/octolib/modules/entries/server.lua new file mode 100644 index 0000000..268810b --- /dev/null +++ b/octolib/addon/lua/octolib/modules/entries/server.lua @@ -0,0 +1,21 @@ +local pending = {} + +netstream.Hook('octolib.guiEntries', function(ply, id, res) + if not pending[ply] or not pending[ply][id] then return end + pending[ply][id](res) +end) + +netstream.Hook('octolib.guiEntries.close', function(ply, id) + if pending[ply] then pending[ply][id] = nil end +end) + +hook.Add('PlayerDisconnected', 'octolib.guiEntries.close', function(ply) + pending[ply] = nil +end) + +function octolib.entries.gui(ply, title, data, res) + local id = octolib.string.uuid() + pending[ply] = pending[ply] or {} + pending[ply][id] = res + netstream.Start(ply, 'octolib.guiEntries', id, title, data) +end diff --git a/octolib/addon/lua/octolib/modules/entries/shared.lua b/octolib/addon/lua/octolib/modules/entries/shared.lua new file mode 100644 index 0000000..e420cd4 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/entries/shared.lua @@ -0,0 +1 @@ +octolib.entries = octolib.entries or {} diff --git a/octolib/addon/lua/octolib/modules/family/client.lua b/octolib/addon/lua/octolib/modules/family/client.lua new file mode 100644 index 0000000..9240a88 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/family/client.lua @@ -0,0 +1,71 @@ +surface.CreateFont('octolib.family.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +local colors = CFG.skinColors +netstream.Hook('octolib.family', function(msg) + + if msg then + surface.PlaySound('ambient/creatures/pigeon_idle2.wav') + local txt = markup.Parse('' .. msg .. '', 600) + local blur = Material('pp/blurscreen') + hook.Add('RenderScreenspaceEffects', 'octolib.family', function() + + local colMod = { + ['$pp_colour_addr'] = 0, + ['$pp_colour_addg'] = 0, + ['$pp_colour_addb'] = 0, + ['$pp_colour_mulr'] = 0, + ['$pp_colour_mulg'] = 0, + ['$pp_colour_mulb'] = 0, + ['$pp_colour_brightness'] = -0.2, + ['$pp_colour_contrast'] = 1 + 0.5, + ['$pp_colour_colour'] = 0, + } + + if GetConVar('octolib_blur'):GetBool() then + DrawColorModify(colMod) + + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat('$blur', i * 2) + blur:Recompute() + + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(-1, -1, ScrW() + 2, ScrH() + 2) + end + else + colMod['$pp_colour_brightness'] = -0.4 + colMod['$pp_colour_contrast'] = 1 + 0.2 + DrawColorModify(colMod) + end + + draw.NoTexture() + local col = colors.bg + surface.SetDrawColor(col.r,col.g,col.b, 100) + surface.DrawRect(-1, -1, ScrW() + 1, ScrH() + 1) + + end) + + + local lock = Material('octoteam/icons/lock.png') + hook.Add('HUDPaint', 'octolib.family', function() + + surface.SetDrawColor(255,255,255, 255) + surface.SetMaterial(lock) + surface.DrawTexturedRect(ScrW() / 2 - 32, ScrH() / 2 - 180, 64, 64) + + txt:Draw((ScrW() - 600) / 2, ScrH() / 2 - 100, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP, 255) + + end) + else + hook.Remove('RenderScreenspaceEffects', 'octolib.family') + hook.Remove('HUDPaint', 'octolib.family') + end + +end) diff --git a/octolib/addon/lua/octolib/modules/family/server.lua b/octolib/addon/lua/octolib/modules/family/server.lua new file mode 100644 index 0000000..7d1cc4f --- /dev/null +++ b/octolib/addon/lua/octolib/modules/family/server.lua @@ -0,0 +1,176 @@ +octolib.family = octolib.family or {} + +hook.Add('octolib.db.init', 'octolib.family', function() + octolib.db:RunQuery([[ + CREATE TABLE IF NOT EXISTS ]] .. CFG.db.shop .. [[.octolib_family ( + id INT NOT NULL AUTO_INCREMENT, + steamids TEXT, + hwids TEXT, + PRIMARY KEY (id) + ) + ]]) +end) + +local function isValidHwid(hwid) + local t, id = unpack(string.Split(hwid, ':')) + return id and #id == 32 +end + +local function familyFromRow(row) + if not row then return end + return { + id = row.id, + hwids = row.hwids:Split(','), + steamids = row.steamids:Split(','), + } +end + +local function saveFamily(family, callback) + family.hwids = octolib.array.filter(family.hwids, isValidHwid) + + -- remove duplicates + family.steamids = table.GetKeys(octolib.array.toKeys(family.steamids)) + family.hwids = table.GetKeys(octolib.array.toKeys(family.hwids)) + + octolib.db:PrepareQuery('update ' .. CFG.db.shop .. '.octolib_family set steamids = ?, hwids = ? where id = ?', { + table.concat(family.steamids, ','), + table.concat(family.hwids, ','), + family.id, + }, function() + callback(family) + end) +end + +function octolib.family.getBySteamID(steamID64, callback) + if octolib.string.isSteamID(steamID64) then + steamID64 = util.SteamIDTo64(steamID64) + end + + octolib.db:RunQuery('select * from ' .. CFG.db.shop .. '.octolib_family where steamids like \'%' .. octolib.db:escape(steamID64) .. '%\'', function(q, st, rows) + callback(familyFromRow(rows[1])) + end) +end + +function octolib.family.getByHwid(hwid, callback) + octolib.db:RunQuery('select * from ' .. CFG.db.shop .. '.octolib_family where hwids like \'%' .. octolib.db:escape(hwid) .. '%\'', function(q, st, rows) + callback(familyFromRow(rows[1])) + end) +end + +function octolib.family.merge(fID1, fID2, callback) + if fID1 == fID2 then return callback() end + + octolib.func.chain({ + function(done) + octolib.db:PrepareQuery('select * from ' .. CFG.db.shop .. '.octolib_family where id in (?, ?)', { fID1, fID2 }, done) + end, + function(done, q, st, rows) + local family1 = familyFromRow(rows[1]) + local family2 = familyFromRow(rows[2]) + if not family1 or not family2 then return end + + -- indexes could be swapped during query and we want family 1 to persist + if family1.id == fID2 then + family1, family2 = family2, family1 + end + + table.Add(family1.hwids, family2.hwids) + table.Add(family1.steamids, family2.steamids) + + octolib.db:PrepareQuery('delete from ' .. CFG.db.shop .. '.octolib_family where id = ?', { family2.id }) + saveFamily(family1, callback) + end, + }) +end + +function octolib.family.storeHwids(steamID64, hwids, callback) + if octolib.string.isSteamID(steamID64) then + steamID64 = util.SteamIDTo64(steamID64) + end + + hwids = octolib.array.filter(hwids, isValidHwid) + + octolib.func.chain({ + function(done) + octolib.family.getBySteamID(steamID64, function(family) + if family then + return done(family) + end + + -- create new family if this steamid doesn't belong to any + octolib.db:PrepareQuery('insert into ' .. CFG.db.shop .. '.octolib_family(steamids, hwids) values(?, ?)', { + steamID64, + table.concat(hwids, ','), + }, function(q) + local family = { + id = q:lastInsert(), + steamids = { steamID64 }, + hwids = hwids, + } + + hook.Run('octolib.family.created', family) + done(family) + end) + end) + end, + function(done, family) + -- get all families with given hwids + local where = table.concat(octolib.array.map(hwids, function(hwid) + return 'hwids like \'%' .. octolib.db:escape(hwid) .. '%\'' + end), ' or ') + + octolib.db:RunQuery('select * from ' .. CFG.db.shop .. '.octolib_family where ' .. where, function(q, st, rows) + local families = { family } -- first family is always the given steamid's one + table.Add(families, octolib.array.map(rows, familyFromRow)) + + done(families) + end) + end, + function(done, families) + local mainFamily = table.remove(families, 1) + if not mainFamily then + -- should be created in first step anyway but who knows + error('error while storing hwids?!') + end + + -- check if we have new hwids against main family + local newHwids = {} + for _, hwid in ipairs(hwids) do + if not table.HasValue(mainFamily.hwids, hwid) then + while #mainFamily.hwids >= 100 do + table.remove(mainFamily.hwids, 1) + end + table.insert(newHwids, hwid) + end + end + if #newHwids > 0 then + hook.Run('octolib.family.newHwids', mainFamily, newHwids) + table.Add(mainFamily.hwids, newHwids) + end + + -- merge families one by one + local funcs = octolib.array.map(families, function(family) + return function(done) + octolib.family.merge(mainFamily.id, family.id, done) + end + end) + + if #newHwids > 0 then + -- save main family before merging others + table.insert(funcs, 1, function(done) + saveFamily(mainFamily, done) + end) + end + + local newFamilies = octolib.array.filter(families, function(family) + return family.id ~= mainFamily.id + end) + if #newFamilies > 0 then + hook.Run('octolib.family.mergedByHwid', table.Add({ mainFamily }, newFamilies)) + end + + table.insert(funcs, callback) + octolib.func.chain(funcs) + end, + }) +end diff --git a/octolib/addon/lua/octolib/modules/fly-editor/client.lua b/octolib/addon/lua/octolib/modules/fly-editor/client.lua new file mode 100644 index 0000000..a6f9eaa --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/client.lua @@ -0,0 +1,663 @@ +local editor = octolib.flyEditor +editor.active = false +editor.baseMoveSpeed = 1 +editor.ply = LocalPlayer() +editor.vPos = Vector() +editor.vAng = Angle() +editor.fov = 80 +editor.anchor = Vector() +editor.radius = 300 +editor.movablePosCache = {} +editor.cmd = { + fly = true, + move = Vector(), + rotate = Angle(), +} + +editor.dir = Vector() +editor.movables = {} +editor.moveSpeed = 100 +editor.dist = 0 + +local sensitivity = GetConVar('sensitivity'):GetFloat() +local vector_zero, angle_zero = Vector(), Angle() +local colHighlight = Color(247, 132, 5) + +surface.CreateFont('fly-editor.notify', { + font = 'Calibri', + extended = true, + size = 24, + weight = 350, +}) + +hook.Add('PreDrawHalos', 'fly-editor.scene', function() + if not editor.highlight then return end + halo.Add({ editor.highlight.csent }, colHighlight, 2, 2, 2) +end) + +function editor.getAnchorPos() + + if IsValid(editor.anchorEnt) then + return editor.anchorEnt:LocalToWorld(editor.anchor) + else + return editor.anchor + end + +end + +function editor.setMaxDist(dist) + + editor.radius = dist + editor.sphereSegments = math.Clamp(math.floor(dist * 0.07), 8, 100) + +end + +function editor.notify(text, time) + + time = time or 1.5 + editor.message = { text, CurTime() + time } + +end + +function editor.exit() + + editor.active = false + + if IsValid(editor.pnlScene) then editor.pnlScene:Remove() end + if IsValid(editor.pnlToolbar) then editor.pnlToolbar:Remove() end + if IsValid(editor.pnlActions) then editor.pnlActions:Remove() end + if IsValid(editor.pnlInspector) then editor.pnlInspector:Remove() end + for _, movable in pairs(editor.movables) do + movable:Remove() + end + table.Empty(editor.movables) + + if editor.options and editor.options.canCreate then + concommand.Add('gm_spawn', function(_, _, args) netstream.Start('gm_spawn', args) end) + end + +end + +function editor.addMovable(data, id) + + if data.parent and not isentity(data.parent) then + local propParent = editor.movables[data.parent] + data.parent = isentity(propParent) and propParent or + istable(propParent) and propParent.csent or data.parent + end + + if not id then id = #editor.movables + 1 end + return octolib.createMoveable(data, id) + +end + +local buts = {} +function editor.start(options, funcOk, funcCancel) + + if editor.active or not funcOk then return end + + options = options or {} + + editor.active = true + editor.doneClickerFix = false + editor.ply = LocalPlayer() + editor.vAng = options.vAng or editor.ply:EyeAngles() + editor.vPos = options.vPos or (editor.ply:EyePos() - editor.vAng:Forward() * 15) + editor.anchorEnt = options.anchorEnt + editor.anchor = Vector(options.anchor or IsValid(editor.anchorEnt) and Vector() or editor.vPos) + editor.setMaxDist(options.maxDist or 300) + editor.tool = options.tool or editor.TOOL_MOVE + editor.space = options.space or editor.SPACE_GLOBAL + editor.origin = options.origin or editor.ORIGIN_ZERO + editor.options = options + + editor.pnlScene = vgui.Create 'fe_scene' + editor.pnlInspector = vgui.Create 'fe_inspector' + + local toolbar = vgui.Create 'fe_toolbar' + toolbar:SetAlignment(7) + editor.pnlToolbar = toolbar + + buts.tMove = toolbar:AddButton('Двигать', 'octoteam/icons-32/transform_move.png', function(but) + editor.selectTool(editor.TOOL_MOVE) + editor.notify('Инструмент: Двигать') + end) + buts.tMove:SetAlpha(editor.tool == editor.TOOL_MOVE and 255 or 50) + buts.tRotate = toolbar:AddButton('Вращать', 'octoteam/icons-32/transform_rotate.png', function(but) + editor.selectTool(editor.TOOL_ROTATE) + editor.notify('Инструмент: Вращать') + end) + buts.tRotate:SetAlpha(editor.tool == editor.TOOL_ROTATE and 255 or 50) + buts.tScale = toolbar:AddButton('Изменить размер', 'octoteam/icons-32/transform_scale.png', function(but) + editor.selectTool(editor.TOOL_SCALE) + editor.notify('Инструмент: Размер') + end) + buts.tScale:SetAlpha(editor.tool == editor.TOOL_SCALE and 255 or 50) + + toolbar:AddSpacer() + + buts.sGlobal = toolbar:AddButton('Глобальное пространство', 'octoteam/icons-32/globe_network.png', function(but) + editor.space = editor.SPACE_GLOBAL + buts.sGlobal:SetAlpha(255) + buts.sLocal:SetAlpha(50) + buts.sParent:SetAlpha(50) + editor.notify('Пространство: Глобальное') + end) + buts.sGlobal:SetAlpha(editor.space == editor.SPACE_GLOBAL and 255 or 50) + buts.sLocal = toolbar:AddButton('Локальное пространство', 'octoteam/icons-32/drive_network.png', function(but) + editor.space = editor.SPACE_LOCAL + buts.sGlobal:SetAlpha(50) + buts.sLocal:SetAlpha(255) + buts.sParent:SetAlpha(50) + editor.notify('Пространство: Локальное') + end) + buts.sLocal:SetAlpha(editor.space == editor.SPACE_LOCAL and 255 or 50) + buts.sParent = toolbar:AddButton('Пространство родителя', 'octoteam/icons-32/network_folder.png', function(but) + editor.space = editor.SPACE_PARENT + buts.sGlobal:SetAlpha(50) + buts.sLocal:SetAlpha(50) + buts.sParent:SetAlpha(255) + editor.notify('Пространство: Родитель') + end) + buts.sParent:SetAlpha(editor.space == editor.SPACE_PARENT and 255 or 50) + + toolbar:AddSpacer() + + buts.oZero = toolbar:AddButton('Нулевая ось', 'octoteam/icons-32/shape_align_bottom.png', function(but) + editor.origin = editor.ORIGIN_ZERO + buts.oZero:SetAlpha(255) + buts.oCenter:SetAlpha(50) + editor.notify('Ось: Нулевая') + end) + buts.oZero:SetAlpha(editor.origin == editor.ORIGIN_ZERO and 255 or 50) + buts.oCenter = toolbar:AddButton('Центральная ось', 'octoteam/icons-32/shape_align_middle.png', function(but) + editor.origin = editor.ORIGIN_CENTER + buts.oZero:SetAlpha(50) + buts.oCenter:SetAlpha(255) + editor.notify('Ось: Центр') + end) + buts.oCenter:SetAlpha(editor.origin == editor.ORIGIN_CENTER and 255 or 50) + + toolbar:AddSpacer() + buts.aResetProp = toolbar:AddButton('Сбросить положение', 'octoteam/icons-32/arrow_rotate_anticlockwise.png', function(but) + if not editor.selected then return end + local ent = editor.selected.csent + ent:SetAngles(Angle()) + ent:SetModelScale(1) + ent:SetSize(Vector(1,1,1)) + editor.notify('Положение сброшено') + end) + + local function getChanges() + local changes = {} + for i, movable in pairs(editor.movables) do + local changesHere = movable:GetChanges() + if not table.IsEmpty(changesHere) then + changes[movable.id or movable:GetEntity()] = changesHere + end + end + return changes + end + + local actions = vgui.Create 'fe_toolbar' + actions:SetAlignment(8) + editor.pnlActions = actions + + if options.buttons then + for _, button in ipairs(options.buttons) do + actions:AddButton(unpack(button)) + end + actions:AddSpacer() + end + + if options.canCreate then + if istable(options.canCreate) then + actions:AddButton('Добавить', 'octoteam/icons-32/add.png', function(but) + octolib.menu(octolib.table.mapSequential(options.canCreate, function(data) + return { data[1], nil, function() + local prop = table.Copy(data[2]) + local tr = util.QuickTrace(editor.vPos, editor.vAng:Forward() * 2048) + prop.pos = tr.Hit and tr.HitPos or editor.vPos + editor.addMovable(prop) + end} + end)):Open() + end) + actions:AddSpacer() + else + concommand.Add('gm_spawn', function(_, _, args) + local mdl = args[1] + if not mdl then return end + + local tr = util.QuickTrace(editor.vPos, editor.vAng:Forward() * 1000) + editor.addMovable({ + model = mdl, + pos = tr.Hit and tr.HitPos or editor.vPos, + }) + end) + end + end + + actions:AddButton('Применить', 'octoteam/icons-32/tick.png', function(but) + local changes = getChanges() + local doNotClose = funcOk(changes) + if doNotClose ~= true then + editor.exit() + end + end) + actions:AddButton('Отмена', 'octoteam/icons-32/cross.png', function(but) + if funcCancel then + local changes = getChanges() + funcCancel(changes) + end + editor.exit() + end) + + for id, prop in pairs(options.props or {}) do + if options.parent and not prop.parent then + prop.parent = options.parent + if not prop.pos then + prop.pos = Vector() + prop.ang = Angle() + end + end + + editor.addMovable(prop, id) + end + + editor.recalcMovablePositions() + +end +netstream.Hook('octolib.fly-editor', function(id, options) + + editor.start(options, function(changes) + netstream.Start('octolib.fly-editor', id, true, changes) + end, function(changes) + netstream.Start('octolib.fly-editor', id, false, changes) + end) + +end) + +function editor.selectTool(tool) + editor.tool = tool + buts.tMove:SetAlpha(tool == editor.TOOL_MOVE and 255 or 50) + buts.tRotate:SetAlpha(tool == editor.TOOL_ROTATE and 255 or 50) + buts.tScale:SetAlpha(tool == editor.TOOL_SCALE and 255 or 50) + editor.grab = nil + editor.axis = nil +end + +function editor.recalcMovablePositions() + + table.Empty(editor.movablePosCache) + for id, movable in pairs(editor.movables) do + if IsValid(movable.csent) then + editor.movablePosCache[id] = movable.csent:LocalToWorld(movable.csent:OBBCenter()):ToScreen() + end + end + +end + +local wasPressing = {} +function editor.thinkInput() + + local cmd = editor.cmd + cmd.lmb = input.IsMouseDown(MOUSE_LEFT) and (vgui.IsHoveringWorld() or wasPressing.lmb) + cmd.rmb = input.IsMouseDown(MOUSE_RIGHT) and (vgui.IsHoveringWorld() or wasPressing.rmb) + cmd.alt = input.IsKeyDown(KEY_LALT) + cmd.shift = input.IsKeyDown(KEY_LSHIFT) + + if cmd.lmb ~= wasPressing.lmb then + if editor.highlight and cmd.lmb then + editor.pnlScene:SelectByMovable(editor.highlight) + elseif editor.selected then + if cmd.lmb then + editor.axis, editor.grab = editor.selected:TryClick() + else + editor.axis, editor.grab = nil + editor.recalcMovablePositions() + end + end + end + + if cmd.rmb ~= wasPressing.rmb then + local enable = cmd.rmb + cmd.isMoving = enable + if enable then wasPressing.mousePos = { gui.MousePos() } end + gui.EnableScreenClicker(not enable) + if not enable then + gui.SetMousePos(unpack(wasPressing.mousePos or {0,0})) + editor.recalcMovablePositions() + end + end + + editor.moveSpeed = (cmd.shift and 300 or cmd.alt and 25 or 100) * editor.baseMoveSpeed + wasPressing.lmb = cmd.lmb + wasPressing.rmb = cmd.rmb + + if input.IsKeyDown(KEY_DELETE) and editor.selected and not editor.selected.static then + editor.selected:Remove() + end + +end + +local hull = { Vector(-5, -5, -5), Vector(5, 5, 5) } +function editor.thinkMoving() + + local cmd = editor.cmd + + editor.vAng:Add(cmd.rotate * sensitivity / 200) + + local magSqr = cmd.move:LengthSqr() + if magSqr > 0 then + local move = cmd.move:GetNormalized() * editor.moveSpeed * FrameTime() + + local delta = editor.vAng:Forward() * move.y + editor.vAng:Right() * move.x + if move.x ~= 0 or move.y ~= 0 then + delta = delta + editor.vAng:Up() * move.z + else + delta = Vector(0, 0, move.z) + end + local tgtPos = editor.vPos + delta + + if not editor.options.noclip then + local tr = util.TraceHull { + start = editor.vPos, + endpos = tgtPos, + mins = hull[1], + maxs = hull[2], + mask = MASK_SOLID_BRUSHONLY, + } + + if tr.Hit then + local ang = tr.HitNormal:Angle() + local rest = WorldToLocal(tgtPos, angle_zero, tr.HitPos, ang) + rest.x = 0 + delta = LocalToWorld(rest, angle_zero, vector_zero, ang) + tgtPos = editor.vPos + delta + + local tr = util.TraceHull { + start = editor.vPos, + endpos = tgtPos, + mins = hull[1], + maxs = hull[2], + mask = MASK_SOLID_BRUSHONLY, + } + + if tr.Hit then + delta = tr.HitPos - editor.vPos + end + end + end + editor.vPos:Add(delta) + + local anchor = editor.getAnchorPos() + editor.dist = editor.vPos:Distance(anchor) + if editor.radius > 0 and editor.dist > editor.radius then + editor.vPos = anchor + (editor.vPos - anchor):GetNormalized() * editor.radius + end + end + +end + +local function getHoveredMovable() + + local mx, my = gui.MousePos() + local editor = octolib.flyEditor + for id, scrPos in pairs(editor.movablePosCache) do + if math.abs(scrPos.x - mx) < 15 and math.abs(scrPos.y - my) < 15 and editor.movables[id] ~= editor.selected then + return editor.movables[id] + end + end + +end + +function editor.thinkIdle() + + local pnl = vgui.GetHoveredPanel() + local hoveredByPanel = IsValid(pnl) and IsValid(pnl:GetParent()) and pnl:GetParent().movable + local hoveredInWorld = getHoveredMovable() + editor.highlight = hoveredByPanel or hoveredInWorld or nil + + local editor = octolib.flyEditor + local inspector = editor.pnlInspector + local inspectorVisible = inspector:IsVisible() + if editor.selected and not inspectorVisible then + inspector:SetVisible(true) + elseif not editor.selected and inspectorVisible then + inspector:SetVisible(false) + end + +end + +function editor.thinkAnchor() + + local anchor = editor.anchorEnt + if not IsValid(anchor) then return end + + local newPos, newAng = anchor:GetPos(), anchor:GetAngles() + if not editor.lastAnchorPos then + editor.lastAnchorPos = newPos + editor.lastAnchorAng = newAng + end + + if newPos ~= editor.lastAnchorPos then + editor.vPos:Add(newPos - editor.lastAnchorPos) + editor.lastAnchorPos = newPos + end + + if newAng ~= editor.lastAnchorAng then + local angDelta = newAng - editor.lastAnchorAng + editor.vAng:Add(angDelta) + + local relPos = anchor:WorldToLocal(editor.vPos) + local newRelPos = Vector(relPos) + newRelPos:Rotate(angDelta) + local posDelta = LocalToWorld(newRelPos - relPos, angle_zero, vector_zero, newAng) + editor.vPos:Add(posDelta) + + editor.lastAnchorAng = newAng + end + +end + +local binds = { + invnext = function() + if not editor.cmd.isMoving then return end + editor.baseMoveSpeed = math.max(editor.baseMoveSpeed * 0.5, 0.25) + editor.notify('Скорость передвижения: x' .. math.Round(editor.baseMoveSpeed, 2)) + return true + end, + invprev = function() + if not editor.cmd.isMoving then return end + editor.baseMoveSpeed = math.min(editor.baseMoveSpeed * 2, 32) + editor.notify('Скорость передвижения: x' .. math.Round(editor.baseMoveSpeed, 2)) + return true + end, + slot1 = function() if IsValid(buts.tMove) then buts.tMove:DoClick() return true end end, + slot2 = function() if IsValid(buts.tRotate) then buts.tRotate:DoClick() return true end end, + slot3 = function() if IsValid(buts.tScale) then buts.tScale:DoClick() return true end end, + slot4 = function() if IsValid(buts.sGlobal) then buts.sGlobal:DoClick() return true end end, + slot5 = function() if IsValid(buts.sLocal) then buts.sLocal:DoClick() return true end end, + slot6 = function() if IsValid(buts.sParent) then buts.sParent:DoClick() return true end end, + slot7 = function() if IsValid(buts.oZero) then buts.oZero:DoClick() return true end end, + slot8 = function() if IsValid(buts.oCenter) then buts.oCenter:DoClick() return true end end, + ['+use'] = function() + if editor.tool == editor.TOOL_MOVE and IsValid(buts.tRotate) then return buts.tRotate:DoClick() end + if editor.tool == editor.TOOL_ROTATE and IsValid(buts.tScale) then return buts.tScale:DoClick() end + if editor.tool == editor.TOOL_SCALE and IsValid(buts.tMove) then return buts.tMove:DoClick() end + end, + ['+reload'] = function() + if editor.space == editor.SPACE_GLOBAL and IsValid(buts.sLocal) then return buts.sLocal:DoClick() end + if editor.space == editor.SPACE_LOCAL and IsValid(buts.sParent) then return buts.sParent:DoClick() end + if editor.space == editor.SPACE_PARENT and IsValid(buts.sGlobal) then return buts.sGlobal:DoClick() end + end, + ['impulse 100'] = function() + if editor.origin == editor.ORIGIN_ZERO and IsValid(buts.oCenter) then return buts.oCenter:DoClick() end + if editor.origin == editor.ORIGIN_CENTER and IsValid(buts.oZero) then return buts.oZero:DoClick() end + end, +} + +hook.Add('PlayerBindPress', 'fly-editor', function(ply, bind, press) + + if editor.active and binds[bind] and press then + return binds[bind]() + end + +end) + +hook.Add('dbg-view.override', 'fly-editor', function() + if editor.active then return false end +end) + +hook.Add('ShouldDrawLocalPlayer', 'fly-editor', function() + if editor.active then return true end +end) + +hook.Add('CalcView', 'fly-editor', function(ply, pos, ang, fov) + + if not editor.active then return end + + editor.thinkInput() + editor.thinkAnchor() + + if editor.cmd.isMoving then + editor.thinkMoving() + else + editor.thinkIdle() + end + + return { + origin = editor.vPos, + angles = editor.vAng, + fov = editor.fov, + } + +end) + +hook.Add('CreateMove', 'fly-editor', function(cmd) + + if not editor.active then return end + + local move = editor.cmd.move + move.x = cmd:GetSideMove() + move.y = cmd:GetForwardMove() + move.z = cmd:KeyDown(IN_JUMP) and 10000 or 0 + + cmd:ClearMovement() + cmd:ClearButtons() + +end) + +hook.Add('InputMouseApply', 'fly-editor', function(cmd, x, y, ang) + + if not editor.active then return end + + local rotate = editor.cmd.rotate + rotate.p = y + rotate.y = -x + + cmd:SetMouseX(0) + cmd:SetMouseY(0) + + return true + +end) + +hook.Add('RenderScene', 'fly-editor', function(pos, ang, fov) + + if not editor.active then return end + + local mx, my = gui.MousePos() + local dir = util.AimVector(ang, fov, mx, my, ScrW(), ScrH()) + + local tr = util.TraceLine { + start = editor.vPos, + endpos = editor.vPos + dir * 4096, + } + + editor.dir = dir + editor.trace = tr + +end) + +hook.Add('PostDrawTranslucentRenderables', 'fly-editor', function() + + if not editor.active then return end + + if editor.radius > 0 then + local al = editor.dist / editor.radius + if al > 0.8 then + local segs = editor.sphereSegments + + local col = Color(255,255,255, (al - 0.8) * 5 * 30) + render.SetColorMaterial() + render.CullMode(MATERIAL_CULLMODE_CW) + render.DrawSphere(editor.getAnchorPos(), editor.radius + 15, segs, segs, col) + render.CullMode(MATERIAL_CULLMODE_CCW) + end + end + + local movable = editor.selected + if movable and IsValid(movable.csent) then + movable:Render() + end + +end) + +local cols = CFG.skinColors +hook.Add('HUDPaint', 'fly-editor', function() + + if not editor.active then return end + + if not editor.doneClickerFix then -- hack to fix opening with console + gui.EnableScreenClicker(true) + editor.doneClickerFix = true + end + + local msg = editor.message + if msg then + local al = math.min(msg[2] - CurTime(), 1) + if al > 0 then + surface.SetAlphaMultiplier(al) + + surface.SetFont('fly-editor.notify') + local x, y = ScrW() / 2, ScrH() - 50 + local w, h = surface.GetTextSize(msg[1]) + + draw.RoundedBox(8, x - w/2 - 10, y - h/2 - 3, w + 20, h + 6, ColorAlpha(cols.bg, 200)) + draw.SimpleText(msg[1], 'fly-editor.notify', x, y, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + surface.SetAlphaMultiplier(1) + else + editor.message = nil + end + end + +end) + +editor.exit() + +-- local canCreate = {} +-- for k, v in pairs(octoinv.collectables) do +-- for i, mdl in ipairs(v.models) do +-- local data = {} +-- if istable(mdl) then +-- data.model = mdl[1] +-- data.skin = mdl.skin +-- data.scale = mdl.scale +-- data.col = mdl.color +-- else +-- data.model = mdl +-- end + +-- canCreate[#canCreate + 1] = {v.name .. ' ' .. i, data} +-- end +-- end + +-- octolib.flyEditor(me, { +-- canCreate = canCreate, +-- maxDist = 0, +-- noclip = true, +-- }, function(changed, options) +-- PrintTable(changed) +-- end) diff --git a/octolib/addon/lua/octolib/modules/fly-editor/meta/cl_movable.lua b/octolib/addon/lua/octolib/modules/fly-editor/meta/cl_movable.lua new file mode 100644 index 0000000..83e2413 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/meta/cl_movable.lua @@ -0,0 +1,858 @@ +local vector_zero, angle_zero = Vector(), Angle() +local snapTable = { + move = { 0.1, 0.5, 1, 4, 16, 64, 128, 256, 512 }, + rotate = { 1, 2, 5, 10, 15, 30, 45, 60, 90 }, +} + +local shiftMul = 0.1 +local snapMode = 5 +local snapCache + +-- local actionMul = 1 +-- hook.Add('VGUIMousePressed', 'fly-editor.movable', function(pnl, code) +-- local editor = octolib.flyEditor +-- if not editor.active or not editor.selected or not editor.axis then return end + +-- if code == MOUSE_WHEEL_DOWN then +-- actionMul = math.max(actionMul * 0.5, 0.125) +-- octolib.flyEditor.notify('Скорость взаимодействия: x' .. math.Round(actionMul, 2)) +-- elseif code == MOUSE_WHEEL_UP then +-- actionMul = math.min(actionMul * 2, 32) +-- octolib.flyEditor.notify('Скорость взаимодействия: x' .. math.Round(actionMul, 2)) +-- end +-- end) + +local function hideOriginalEntity(ent) + function ent:RenderOverride() + local movable = self.movable + if not movable or not IsValid(movable.csent) then + self.RenderOverride = nil + end + end +end + +local function getEntInfo(ent) + + if not isentity(ent) then return {} end + + local bgs = {} + for i, v in ipairs(ent:GetBodyGroups()) do + local id = v.id + bgs[id] = ent:GetBodygroup(id) + end + + local parent = ent:GetParent() + if IsValid(parent) and parent.movable then + parent = parent.movable:GetEntity() + end + + local pos, ang = ent:GetPos(), ent:GetAngles() + if istable(parent) and parent.movable and IsValid(parent.movable.csent) then + local parentEnt = parent.movable.csent + pos, ang = WorldToLocal(pos, ang, parentEnt:GetPos(), parentEnt:GetAngles()) + elseif IsValid(parent) then + pos, ang = ent:GetLocalPos(), ent:GetLocalAngles() + end + + parent = istable(parent) and parent.movable and parent.movable.id or IsValid(parent) and parent or nil + + return { + pos = pos, + ang = ang, + model = ent:GetModel(), + skin = ent:GetSkin(), + col = ent:GetColor(), + -- mat = ent:GetMaterial(), + scale = ent:GetModelScale(), + size = ent.GetSize and ent:GetSize() or nil, + parent = parent, + bgs = bgs, + } + +end + +local Movable = {} +Movable.__index = Movable + +local cols = { + x = Color(255,0,0), + xTr = Color(255,0,0, 20), + y = Color(0,255,0), + yTr = Color(0,255,0, 20), + z = Color(0,0,255), + zTr = Color(0,0,255, 20), +} + +function octolib.createMoveable(prop, id) + + local data + if isentity(prop) then + if not IsValid(prop) then return end + + local parent = prop:GetParent() + local hasParent = IsValid(parent) + data = { + model = prop:GetModel(), + parent = prop:GetParent(), + pos = hasParent and prop:GetLocalPos() or prop:GetPos(), + ang = hasParent and prop:GetLocalAngles() or prop:GetAngles(), + scale = prop:GetModelScale(), + mat = prop:GetMaterial(), + skin = prop:GetSkin(), + col = prop:GetColor(), + rg = prop:GetRenderGroup(), + size = prop.GetSize and prop:GetSize() or nil, + } + + local bgs = {} + for _, v in ipairs(prop:GetBodyGroups()) do + local id = v.id + bgs[id] = prop:GetBodygroup(id) + end + data.bgs = bgs + else + data = prop + end + + local csent = ClientsideModel(data.model, data.rg) + octolib.applyEntData(csent, data) + + timer.Simple(0, function() + local parent = csent:GetParent() + if IsValid(parent) then + if parent.movable and IsValid(parent.movable.csent) then + csent:SetParent(parent.movable.csent) + else + csent:SetParent(parent) + end + end + end) + + local editor = octolib.flyEditor + if not id then + id = 1 + while editor.movables[id] do + id = id + 1 + end + end + local movable = setmetatable({ + ent = prop, + csent = csent, + id = id, + limits = data.limits, + static = data.static, + name = data.name, + icon = data.icon, + customDraw = data.customDraw, + }, Movable) + + if data.customDraw then + function csent:RenderOverride() + data.customDraw(self, movable, editor) + end + end + + if isentity(prop) then + hideOriginalEntity(prop) + end + prop.movable = movable + csent.movable = movable + + editor.movables[id] = movable + editor.pnlScene:AddMovable(movable) + + editor.recalcMovablePositions() + + return movable + +end + +function Movable:Remove() + + local ent = self.ent + if IsValid(ent) and ent.movable == self then + ent.movable = nil + end + + if IsValid(self.csent) then + for _, child in pairs(self.csent:GetChildren()) do + if child.movable then + child.movable:Remove() + else + child:Remove() + end + end + self.csent:Remove() + end + + local editor = octolib.flyEditor + if editor.selected == self then + editor.selected = nil + end + + editor.pnlScene:RemoveMovable(self) + editor.movables[self.id] = nil + +end + +function Movable:GetEntity() + + local ent = self.ent + if not istable(ent) and not IsValid(ent) then + self:Remove() + return + end + + return ent + +end + +function Movable:GetCSEntity() + + local csent = self.csent + if not IsValid(csent) then + self:Remove() + return + end + + return csent + +end + +function Movable:Move(off) + + local csent = self:GetCSEntity() + if not csent then return end + + local pos = csent:GetPos() + pos:Add(off) + csent:SetPos(pos) + +end + +octolib.gizmos = octolib.gizmos or {} +local gz = octolib.gizmos +gz.radius = 20 +gz.dx1 = Vector(1, 0, 0) +gz.dy1 = Vector(0, 1, 0) +gz.dz1 = Vector(0, 0, 1) +gz.dx = Vector(gz.radius, 0, 0) +gz.dy = Vector(0, gz.radius, 0) +gz.dz = Vector(0, 0, gz.radius) +gz.ax = Angle(0, 0, 0) +gz.ay = Angle(0, 90, 0) +gz.az = Angle(90, 0, 0) +gz.mins = Vector(-10, -2, -2) +gz.maxs = Vector(10, 2, 2) + +function Movable:Rotate(off) + + local csent = self:GetCSEntity() + if not csent then return end + + local editor = octolib.flyEditor + local around = angle_zero + if editor.space == editor.SPACE_LOCAL then + around = csent:GetAngles() + off.y = -off.y + elseif editor.space == editor.SPACE_PARENT then + local parent = csent:GetParent() + if IsValid(parent) then + around = parent:GetAngles() + end + end + + local ang = csent:GetAngles() + ang:RotateAroundAxis(around:Forward(), off.p) + ang:RotateAroundAxis(around:Right(), -off.y) + ang:RotateAroundAxis(around:Up(), off.r) + + if editor.origin == editor.ORIGIN_CENTER then + local obbCenter = csent:OBBCenter() + local center1 = csent:LocalToWorld(obbCenter) + local center2 = LocalToWorld(obbCenter, angle_zero, csent:GetPos(), ang) + self:Move(center1 - center2) + end + + csent:SetAngles(ang) + +end + +function Movable:Clone() + + local info = getEntInfo(self.csent) + info.name = self.name + info.icon = self.icon + local newMovable = octolib.flyEditor.addMovable(info) + + for _, child in pairs(self.csent:GetChildren()) do + if not child.movable then continue end + local childInfo = getEntInfo(child.movable.csent) + childInfo.parent = newMovable.csent + childInfo.name = child.name + childInfo.icon = child.icon + octolib.flyEditor.addMovable(childInfo) + end + + return newMovable + +end + +function Movable:GetChanges() + + local ent, csent = self:GetEntity(), self:GetCSEntity() + if not ent then return {} end + if not csent then return { deleted = true } end + + return octolib.table.diff(getEntInfo(ent), getEntInfo(csent)) + +end + +local draws = {} + +function draws.move(editor, pos, ang, axis, grab) + local scale = pos:Distance(editor.vPos) / 120 + local px = LocalToWorld(gz.dx * scale, angle_zero, pos, ang) + local py = LocalToWorld(gz.dy * scale, angle_zero, pos, ang) + local pz = LocalToWorld(gz.dz * scale, angle_zero, pos, ang) + render.DrawLine(pos, px, cols.x) + render.DrawLine(pos, py, cols.y) + render.DrawLine(pos, pz, cols.z) +end + +function draws.moveGrabbed(editor, pos, ang, axis, grab) + local scale = pos:Distance(editor.vPos) / 120 + local movable = editor.selected + if movable.static then return end + + local dir = editor.dir * 4096 + local dx = LocalToWorld(gz.dx * scale, angle_zero, vector_zero, ang) + local dy = LocalToWorld(gz.dy * scale, angle_zero, vector_zero, ang) + local dz = LocalToWorld(gz.dz * scale, angle_zero, vector_zero, ang) + + local off + if axis == editor.AXIS_X then + local st, en = pos - dx * 100, pos + dx * 100 + render.DrawLine(st, en, cols.x, true) + render.DrawLine(st, en, cols.xTr) + + if math.abs(dir.y) > math.abs(dir.z) then + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dy) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.x == 0 then return end + off = LocalToWorld(Vector(proj.x, 0, 0), angle_zero, vector_zero, ang) + else + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dz) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.x == 0 then return end + off = LocalToWorld(Vector(proj.x, 0, 0), angle_zero, vector_zero, ang) + end + elseif axis == editor.AXIS_Y then + local st, en = pos - dy * 100, pos + dy * 100 + render.DrawLine(st, en, cols.y, true) + render.DrawLine(st, en, cols.yTr) + + if math.abs(dir.x) > math.abs(dir.z) then + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dx) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.y == 0 then return end + off = LocalToWorld(Vector(0, proj.y, 0), angle_zero, vector_zero, ang) + else + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dz) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.y == 0 then return end + off = LocalToWorld(Vector(0, proj.y, 0), angle_zero, vector_zero, ang) + end + elseif axis == editor.AXIS_Z then + local st, en = pos - dz * 100, pos + dz * 100 + render.DrawLine(st, en, cols.z, true) + render.DrawLine(st, en, cols.zTr) + + if math.abs(dir.x) > math.abs(dir.y) then + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dx) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.z == 0 then return end + off = LocalToWorld(Vector(0, 0, proj.z), angle_zero, vector_zero, ang) + else + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dy) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.z == 0 then return end + off = LocalToWorld(Vector(0, 0, proj.z), angle_zero, vector_zero, ang) + end + end + + if off then + local moveOff = Vector(off) + if input.IsShiftDown() then moveOff:Mul(shiftMul) end + + if movable.limits then + local parentPos = IsValid(movable.csent:GetParent()) and movable.csent:GetParent():GetPos() or Vector() + local pos = movable.csent:GetPos() - parentPos + if movable.limits.dist then + local dist = (pos + off):LengthSqr() + if dist > movable.limits.dist * movable.limits.dist then + moveOff = (pos + off):GetNormalized() * movable.limits.dist - pos + end + end + end + + if input.IsControlDown() then + snapCache = snapCache or Vector() + snapCache:Add(moveOff) + + local snapDist = snapTable.move[snapMode] + local dist = snapCache:Length() + local snapped = math.floor(dist / snapDist) * snapDist + if snapped ~= 0 then + local dir = snapCache:GetNormalized() + movable:Move(dir * snapped) + snapCache = dir * (dist - snapped) + end + else + movable:Move(moveOff) + end + + grab[2]:Add(off) + end +end + +local segs, circle = 30, {} +for i = 0, segs-1 do + local rad = math.pi * 2 / segs * i + circle[i] = { math.cos(rad), math.sin(rad) } +end + +function draws.rotate(editor, pos, ang, axis, grab) + local radius = pos:Distance(editor.vPos) / 10 + local dx = LocalToWorld(gz.dx1 * radius * 1.2, angle_zero, vector_zero, ang) + local dy = LocalToWorld(gz.dy1 * radius * 1.2, angle_zero, vector_zero, ang) + local dz = LocalToWorld(gz.dz1 * radius * 1.2, angle_zero, vector_zero, ang) + + local vPos, vAng = editor.vPos, editor.vAng + local vDist = pos:DistToSqr(vPos - vAng:Forward() * 2 * radius / 20) + + for i1 = 0, segs-1 do + local i2 = i1 < segs-1 and i1 + 1 or 0 + local c1, c2, s1, s2 = circle[i1][1], circle[i2][1], circle[i1][2], circle[i2][2] + local p1, p2 + + p1, p2 = pos + c1 * dy + s1 * dz, pos + c2 * dy + s2 * dz + render.DrawLine(p1, p2, p1:DistToSqr(vPos) < vDist and p2:DistToSqr(vPos) < vDist and cols.x or cols.xTr) + + p1, p2 = pos + c1 * dz + s1 * dx, pos + c2 * dz + s2 * dx + render.DrawLine(p1, p2, p1:DistToSqr(vPos) < vDist and p2:DistToSqr(vPos) < vDist and cols.y or cols.yTr) + + p1, p2 = pos + c1 * dx + s1 * dy, pos + c2 * dx + s2 * dy + render.DrawLine(p1, p2, p1:DistToSqr(vPos) < vDist and p2:DistToSqr(vPos) < vDist and cols.z or cols.zTr) + end +end + +function draws.rotateGrabbed(editor, pos, ang, axis, grab) + local radius = pos:Distance(editor.vPos) / 10 + local dx = LocalToWorld(gz.dx1 * radius * 1.2, angle_zero, vector_zero, ang) + local dy = LocalToWorld(gz.dy1 * radius * 1.2, angle_zero, vector_zero, ang) + local dz = LocalToWorld(gz.dz1 * radius * 1.2, angle_zero, vector_zero, ang) + + local mul = input.IsShiftDown() and shiftMul or 1 + + local vPos, dir = editor.vPos, editor.dir + local movable = editor.selected + if movable.static then return end + + if axis == editor.AXIS_X then + for i1 = 0, segs-1 do + local i2 = i1 < segs-1 and i1+1 or 0 + local c1, c2, s1, s2 = circle[i1][1], circle[i2][1], circle[i1][2], circle[i2][2] + + local p1, p2 = pos + c1 * dy + s1 * dz, pos + c2 * dy + s2 * dz + render.DrawLine(p1, p2, cols.x) + end + + local curHit = util.IntersectRayWithPlane(vPos, dir, pos, dx) + if not curHit then return end + render.DrawLine(pos, pos + (curHit - pos):GetNormalized() * radius, color_white) + + local proj = WorldToLocal(curHit, angle_zero, pos, ang) + local oldAng = math.atan2(grab.y, grab.z) + local curAng = math.atan2(proj.y, proj.z) + if oldAng ~= curAng then + local delta = math.deg(oldAng - curAng) * mul + if input.IsControlDown() then + snapCache = (snapCache or 0) + delta + + local snapDist = snapTable.rotate[snapMode] + local snapped = math.Round(snapCache / snapDist) * snapDist + if snapped ~= 0 then + movable:Rotate(Angle(snapped, 0, 0)) + snapCache = snapCache - snapped + end + else + movable:Rotate(Angle(delta, 0, 0)) + end + if editor.space ~= editor.SPACE_LOCAL then editor.grab = proj end + end + elseif axis == editor.AXIS_Y then + for i1 = 0, segs-1 do + local i2 = i1 < segs-1 and i1 + 1 or 0 + local c1, c2, s1, s2 = circle[i1][1], circle[i2][1], circle[i1][2], circle[i2][2] + + local p1, p2 = pos + c1 * dz + s1 * dx, pos + c2 * dz + s2 * dx + render.DrawLine(p1, p2, cols.y) + end + + local curHit = util.IntersectRayWithPlane(vPos, dir, pos, dy) + if not curHit then return end + render.DrawLine(pos, pos + (curHit - pos):GetNormalized() * radius, color_white) + + local proj = WorldToLocal(curHit, angle_zero, pos, ang) + local oldAng = math.atan2(grab.x, grab.z) + local curAng = math.atan2(proj.x, proj.z) + if oldAng ~= curAng then + local global = editor.space ~= editor.SPACE_LOCAL + local delta = math.deg(oldAng - curAng) * mul + if global then + editor.grab = proj + delta = -delta + end + + if input.IsControlDown() then + snapCache = (snapCache or 0) + delta + + local snapDist = snapTable.rotate[snapMode] + local snapped = math.Round(snapCache / snapDist) * snapDist + if snapped ~= 0 then + movable:Rotate(Angle(0, snapped, 0)) + snapCache = snapCache - snapped + end + else + movable:Rotate(Angle(0, delta, 0)) + end + end + elseif axis == editor.AXIS_Z then + for i1 = 0, segs-1 do + local i2 = i1 < segs-1 and i1 + 1 or 0 + local c1, c2, s1, s2 = circle[i1][1], circle[i2][1], circle[i1][2], circle[i2][2] + + local p1, p2 = pos + c1 * dx + s1 * dy, pos + c2 * dx + s2 * dy + render.DrawLine(p1, p2, cols.z) + end + + local curHit = util.IntersectRayWithPlane(vPos, dir, pos, dz) + if not curHit then return end + render.DrawLine(pos, pos + (curHit - pos):GetNormalized() * radius, color_white) + + local proj = WorldToLocal(curHit, angle_zero, pos, ang) + local oldAng = math.atan2(grab.x, grab.y) + local curAng = math.atan2(proj.x, proj.y) + if oldAng ~= curAng then + local delta = math.deg(oldAng - curAng) * mul + if input.IsControlDown() then + snapCache = (snapCache or 0) + delta + + local snapDist = snapTable.rotate[snapMode] + local snapped = math.Round(snapCache / snapDist) * snapDist + if snapped ~= 0 then + movable:Rotate(Angle(0, 0, snapped)) + snapCache = snapCache - snapped + end + else + movable:Rotate(Angle(0, 0, delta)) + end + + if editor.space ~= editor.SPACE_LOCAL then editor.grab = proj end + end + end +end + +function draws.scale(editor, pos, ang, axis, grab) + local scale = pos:Distance(editor.vPos) / 120 + local px, ax = LocalToWorld(gz.dx * scale, angle_zero, pos, ang) + local py, ay = LocalToWorld(gz.dy * scale, angle_zero, pos, ang) + local pz, az = LocalToWorld(gz.dz * scale, angle_zero, pos, ang) + render.DrawLine(pos, px, cols.x) + render.DrawLine(pos, py, cols.y) + render.DrawLine(pos, pz, cols.z) + local mins, maxs = Vector(-1,-1,-1) * scale, Vector(1,1,1) * scale + render.DrawWireframeBox(px, ax, mins, maxs, cols.x) + render.DrawWireframeBox(py, ay, mins, maxs, cols.y) + render.DrawWireframeBox(pz, az, mins, maxs, cols.z) +end + +local function scaleRecursive(ent, delta, updateLocation) + local size, pos = ent:GetSize(), ent:GetLocalPos() + for _, axis in ipairs({'x', 'y', 'z'}) do + local aDelta = delta[axis] + if aDelta ~= 0 then + local oldSize = size[axis] + size[axis] = oldSize + aDelta + if updateLocation then + pos[axis] = pos[axis] * (oldSize + aDelta) / oldSize + end + end + end + + if updateLocation then + ent:SetLocalPos(pos) + else + ent:SetSize(size) + end + + for _, ent in ipairs(ent:GetChildren()) do + scaleRecursive(ent, delta, true) + end +end + +function draws.scaleGrabbed(editor, pos, ang, axis, grab) + local scale = pos:Distance(editor.vPos) / 120 + local movable = editor.selected + if movable.static then return end + + local dir = editor.dir * 4096 + local dx = LocalToWorld(gz.dx * scale, angle_zero, vector_zero, ang) + local dy = LocalToWorld(gz.dy * scale, angle_zero, vector_zero, ang) + local dz = LocalToWorld(gz.dz * scale, angle_zero, vector_zero, ang) + local mul = (input.IsShiftDown() and shiftMul or 1) * 0.1 + + local off + if axis == editor.AXIS_X then + local st, en = pos - dx * 100, pos + dx * 100 + render.DrawLine(st, en, cols.x, true) + render.DrawLine(st, en, cols.xTr) + + if math.abs(dir.y) > math.abs(dir.z) then + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dy) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.x == 0 then return end + off = Vector(proj.x, 0, 0) + else + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dz) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.x == 0 then return end + off = Vector(proj.x, 0, 0) + end + elseif axis == editor.AXIS_Y then + local st, en = pos - dy * 100, pos + dy * 100 + render.DrawLine(st, en, cols.y, true) + render.DrawLine(st, en, cols.yTr) + + if math.abs(dir.x) > math.abs(dir.z) then + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dx) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.y == 0 then return end + off = Vector(0, proj.y, 0) + else + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dz) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.y == 0 then return end + off = Vector(0, proj.y, 0) + end + elseif axis == editor.AXIS_Z then + local st, en = pos - dz * 100, pos + dz * 100 + render.DrawLine(st, en, cols.z, true) + render.DrawLine(st, en, cols.zTr) + + if math.abs(dir.x) > math.abs(dir.y) then + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dx) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.z == 0 then return end + off = Vector(0, 0, proj.z) + else + local curHit = util.IntersectRayWithPlane(grab[1], dir, pos, dy) + if not curHit then return end + + local proj = WorldToLocal(curHit, angle_zero, grab[2], ang) + if proj.z == 0 then return end + off = Vector(0, 0, proj.z) + end + end + + if off and off:LengthSqr() > 0 then + off:Mul(mul) + + if movable.limits and movable.limits.scale then + local size = movable.csent:GetSize() + local min, max = unpack(movable.limits.scale) + for _, axis in ipairs({'x', 'y', 'z'}) do + off[axis] = math.Clamp(off[axis] + size[axis], min, max) - size[axis] + end + end + + scaleRecursive(movable.csent, off) + grab[2]:Add(LocalToWorld(off / mul, angle_zero, vector_zero, ang)) + end +end + +local function getPosAng(csent, editor) + + local pos = csent:GetPos() + local ang + + if editor.space == editor.SPACE_LOCAL or editor.tool == editor.TOOL_SCALE then + ang = csent:GetAngles() + elseif editor.space == editor.SPACE_GLOBAL then + ang = angle_zero + elseif editor.space == editor.SPACE_PARENT then + local parent = csent:GetParent() + if IsValid(parent) then + ang = parent:GetAngles() + else + ang = angle_zero + end + end + + if editor.origin == editor.ORIGIN_CENTER then + pos = csent:LocalToWorld(csent:OBBCenter()) + end + + return pos, ang + +end + +function Movable:Render() + + local csent = self:GetCSEntity() + if not csent then return end + + if not self.selected then return end + + local editor = octolib.flyEditor + local tool = editor.tool + local axis, grab = editor.axis, editor.grab + local pos, ang = getPosAng(csent, editor) + + if not axis then snapCache = nil end + if tool == editor.TOOL_MOVE then + if not axis then + draws.move(editor, pos, ang, axis, grab) + else + draws.moveGrabbed(editor, pos, ang, axis, grab) + end + elseif tool == editor.TOOL_ROTATE then + if not axis then + draws.rotate(editor, pos, ang, axis, grab) + else + draws.rotateGrabbed(editor, pos, ang, axis, grab) + end + elseif tool == editor.TOOL_SCALE then + if not axis then + draws.scale(editor, pos, ang, axis, grab) + else + draws.scaleGrabbed(editor, pos, ang, axis, grab) + end + end + +end + +function Movable:TryClick() + + local csent = self:GetCSEntity() + if not csent then return end + + local editor = octolib.flyEditor + local tool = editor.tool + local vPos, vAng, dir = editor.vPos, editor.vAng, editor.dir * 4096 + local pos, ang = getPosAng(csent, editor) + + if tool == editor.TOOL_MOVE then + local scale = pos:Distance(editor.vPos) / 120 + local px, ax = LocalToWorld(gz.dx / 2 * scale, gz.ax, pos, ang) + if util.IntersectRayWithOBB(vPos, dir, px, ax, gz.mins * scale, gz.maxs * scale) then + local hit = util.IntersectRayWithPlane(vPos, dir, pos, ang:Up()) + if not hit then return end + return editor.AXIS_X, { vPos, hit } + end + + local py, ay = LocalToWorld(gz.dy / 2 * scale, gz.ay, pos, ang) + if util.IntersectRayWithOBB(vPos, dir, py, ay, gz.mins * scale, gz.maxs * scale) then + local hit = util.IntersectRayWithPlane(vPos, dir, pos, ang:Up()) + if not hit then return end + return editor.AXIS_Y, { vPos, hit } + end + + local pz, az = LocalToWorld(gz.dz / 2 * scale, gz.az, pos, ang) + if util.IntersectRayWithOBB(vPos, dir, pz, az, gz.mins * scale, gz.maxs * scale) then + local hit = util.IntersectRayWithPlane(vPos, dir, pos, ang:Right()) + if not hit then return end + return editor.AXIS_Z, { vPos, hit } + end + elseif tool == editor.TOOL_ROTATE then + local radius = pos:Distance(editor.vPos) / 10 + local dx = LocalToWorld(gz.dx1 * radius * 1.2, angle_zero, vector_zero, ang) + local dy = LocalToWorld(gz.dy1 * radius * 1.2, angle_zero, vector_zero, ang) + local dz = LocalToWorld(gz.dz1 * radius * 1.2, angle_zero, vector_zero, ang) + local r2 = radius * radius * 1.2 * 1.2 + local vDist = pos:DistToSqr(vPos - vAng:Forward() * 2 * radius / 20) + local curHit + + curHit = util.IntersectRayWithPlane(vPos, dir, pos, dx) + if curHit and curHit:DistToSqr(vPos) < vDist then + local proj = WorldToLocal(curHit, angle_zero, pos, ang) + if octolib.math.inRange(proj:LengthSqr() / r2, 0.8, 1.2) then + return editor.AXIS_X, proj + end + end + + curHit = util.IntersectRayWithPlane(vPos, dir, pos, dy) + if curHit and curHit:DistToSqr(vPos) < vDist then + local proj = WorldToLocal(curHit, angle_zero, pos, ang) + if octolib.math.inRange(proj:LengthSqr() / r2, 0.8, 1.2) then + proj:Normalize() + return editor.AXIS_Y, proj + end + end + + curHit = util.IntersectRayWithPlane(vPos, dir, pos, dz) + if curHit and curHit:DistToSqr(vPos) < vDist then + local proj = WorldToLocal(curHit, angle_zero, pos, ang) + if octolib.math.inRange(proj:LengthSqr() / r2, 0.8, 1.2) then + proj:Normalize() + return editor.AXIS_Z, proj + end + end + elseif tool == editor.TOOL_SCALE then + local scale = pos:Distance(editor.vPos) / 120 + local px, ax = LocalToWorld(gz.dx / 2 * scale, gz.ax, pos, ang) + if util.IntersectRayWithOBB(vPos, dir, px, ax, gz.mins * scale, gz.maxs * scale) then + local hit = util.IntersectRayWithPlane(vPos, dir, pos, ang:Up()) + if not hit then return end + return editor.AXIS_X, { vPos, hit } + end + + local py, ay = LocalToWorld(gz.dy / 2 * scale, gz.ay, pos, ang) + if util.IntersectRayWithOBB(vPos, dir, py, ay, gz.mins * scale, gz.maxs * scale) then + local hit = util.IntersectRayWithPlane(vPos, dir, pos, ang:Up()) + if not hit then return end + return editor.AXIS_Y, { vPos, hit } + end + + local pz, az = LocalToWorld(gz.dz / 2 * scale, gz.az, pos, ang) + if util.IntersectRayWithOBB(vPos, dir, pz, az, gz.mins * scale, gz.maxs * scale) then + local hit = util.IntersectRayWithPlane(vPos, dir, pos, ang:Right()) + if not hit then return end + return editor.AXIS_Z, { vPos, hit } + end + end + +end diff --git a/octolib/addon/lua/octolib/modules/fly-editor/server.lua b/octolib/addon/lua/octolib/modules/fly-editor/server.lua new file mode 100644 index 0000000..8d866c3 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/server.lua @@ -0,0 +1,152 @@ +local sessions = {} + +local function switchEditor(ply, on) + + if on then + ply:Freeze(true) + ply:GodEnable() + ply.inFlyEditor = true + else + ply:Freeze(false) + ply:GodDisable() + ply.inFlyEditor = nil + end + +end + +local curID = 0 +function octolib.flyEdit(ply, options, funcOk, funcCancel) + + curID = curID + 1 + + switchEditor(ply, true) + sessions[ply] = { curID, options, funcOk, funcCancel } + netstream.Start(ply, 'octolib.fly-editor', curID, options) + + timer.Create('fly-editor.' .. ply:SteamID(), 30 * 60, 1, function() + octolib.flyEditCancel(ply) + end) + +end + +function octolib.flyEditCancel(ply) + + local session = sessions[ply] + if not session then return end + + local funcCancel = session[3] + if funcCancel then funcCancel({}) end + + sessions[ply] = nil + switchEditor(ply, false) + timer.Remove('fly-editor.' .. ply:SteamID()) + +end + +netstream.Hook('octolib.fly-editor', function(ply, id, ok, changed) + + local session = sessions[ply] + if not session then return end + if session[1] ~= id then + ply:Notify('warning', 'Что-то пошло не так при синхронизации данных, попробуй еще раз') + octolib.flyEditCancel(ply) + return + end + + local options, funcOk, funcCancel = unpack(session, 2) + + changed = changed or {} + for ent in pairs(changed) do + if isentity(ent) and not table.HasValue(options.props, ent) then + changed[ent] = nil + end + end + + if ok and funcOk then funcOk(changed, options) end + if not ok and funcCancel then funcCancel(changed, options) end + session[id] = nil + + switchEditor(ply, false) + timer.Remove('fly-editor.' .. ply:SteamID()) + +end) + +hook.Add('PlayerDisconnected', 'octolib.fly-editor', function(ply) + octolib.flyEditCancel(ply) +end) + +netstream.Hook('gm_spawn', function(ply, args) + CCSpawn(ply, 'gm_spawn', args) +end) + +concommand.Add('octolib_flyeditor', function(ply) + if not ply:query('octolib.flyEditor') or ply.inFlyEditor then return end + + ply:SelectWeapon('weapon_physgun') + + local existing = octolib.array.filter(ents.GetAll(), function(ent) return ent:CPPIGetOwner() == ply and ent:GetClass():find('prop_') end) + octolib.flyEdit(ply, { + maxDist = 0, + noclip = true, + canCreate = true, + props = existing, + }, function(changed, options) + if table.Count(changed) < 1 or not ply:query('octolib.flyEditor') then return end + + local parentQueue = {} + + undo.Create('octolib.flyEditor') + undo.SetPlayer(ply) + + for id, data in pairs(changed) do + local mdl = data.model + + local ent = existing[id] + if not IsValid(ent) then + if util.IsValidProp(mdl) then + ent = DoPlayerEntitySpawn(ply, 'prop_physics', mdl, 0) + FixInvalidPhysicsObject(ent) + DoPropSpawnedEffect(ent) + else + ent = DoPlayerEntitySpawn(ply, 'prop_effect', mdl, 0) + if IsValid(ent.AttachedEntity) then + DoPropSpawnedEffect(ent.AttachedEntity) + end + end + end + data._ent = ent + + if data.deleted then + ent:Remove() + continue + end + + if data.parent then + parentQueue[ent] = { + parent = data.parent, + pos = data.pos, + ang = data.ang, + } + + data.parent = nil + data.pos = nil + data.ang = nil + end + + octolib.applyEntData(ent, data) + local ph = ent:GetPhysicsObject() + if IsValid(ph) then ph:EnableMotion(false) end + ent:Activate() -- to fix scale physics + ent:CPPISetOwner(ply) + + undo.AddEntity(ent) + ply:AddCleanup('props', ent) + end + undo.Finish('octolib.flyEditor (' .. os.date('%X', os.time()) .. ')') + + for ent, data in pairs(parentQueue) do + if isnumber(data.parent) then data.parent = changed[data.parent]._ent end + octolib.applyEntData(ent, data) + end + end) +end) diff --git a/octolib/addon/lua/octolib/modules/fly-editor/shared.lua b/octolib/addon/lua/octolib/modules/fly-editor/shared.lua new file mode 100644 index 0000000..e9973c8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/shared.lua @@ -0,0 +1,69 @@ +octolib.flyEditor = octolib.flyEditor or {} + +octolib.include.prefixed('meta') +octolib.include.client('vgui') + +octolib.flyEditor.AXIS_X = 0 +octolib.flyEditor.AXIS_Y = 1 +octolib.flyEditor.AXIS_Z = 2 +octolib.flyEditor.TOOL_MOVE = 0 +octolib.flyEditor.TOOL_ROTATE = 1 +octolib.flyEditor.TOOL_SCALE = 2 +octolib.flyEditor.SPACE_GLOBAL = 0 +octolib.flyEditor.SPACE_LOCAL = 1 +octolib.flyEditor.SPACE_PARENT = 2 +octolib.flyEditor.ORIGIN_ZERO = 0 +octolib.flyEditor.ORIGIN_CENTER = 1 + +octolib.entDefaults = { + parent = NULL, + pos = Vector(), + ang = Angle(), + skin = 0, + col = Color(255,255,255, 255), + mat = '', + scale = 1, + bgs = {[0] = 0}, +} + +function octolib.applyEntData(ent, changes) + + if not IsValid(ent) then return end + + local parent = ent:GetParent() + if changes.parent then + if changes.parent == '--deleted--' then changes.parent = nil end + parent = changes.parent + ent:SetParent(parent) + end + if changes.pos then + if IsValid(parent) then + ent:SetLocalPos(changes.pos) + else + ent:SetPos(changes.pos) + end + end + if changes.ang then + if IsValid(parent) then + ent:SetLocalAngles(changes.ang) + else + ent:SetAngles(changes.ang) + end + end + if changes.model then ent:SetModel(changes.model) end + if changes.skin then ent:SetSkin(changes.skin) end + if changes.col then ent:SetColor(changes.col) end + if changes.mat then ent:SetMaterial(changes.mat) end + if changes.size then + ent:SetModelScale(1) + ent:SetSize(changes.size) + elseif changes.scale then + ent:SetModelScale(changes.scale) + end + if changes.bgs then + for k, v in pairs(changes.bgs) do + ent:SetBodygroup(k, v) + end + end + +end diff --git a/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_inspector.lua b/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_inspector.lua new file mode 100644 index 0000000..6c96f5c --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_inspector.lua @@ -0,0 +1,34 @@ +local PANEL = {} + +function PANEL:Init() + + self:SetSize(250, 400) + self:AlignRight(5) + self:AlignTop(5) + + self.controls = {} + local c = self.controls + + c.col = self:CreateRow('Properties', 'Color') + c.col:Setup('VectorColor') + c.col:SetValue(Vector(1,1,1)) + function c.col.DataChanged(p, strCol) + if not IsValid(self.ent) then return end + local vCol = Vector(strCol) + self.VectorValue = vCol + self.ent:SetColor(vCol:ToColor()) + end + +end + +function PANEL:SetEntity(ent) + + self.ent = ent + + local c = self.controls + local col = ent:GetColor() + c.col:SetValue(Vector(col.r / 255, col.g / 255, col.b / 255)) + +end + +vgui.Register('fe_inspector', PANEL, 'DProperties') diff --git a/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_scene.lua b/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_scene.lua new file mode 100644 index 0000000..fd63fc1 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_scene.lua @@ -0,0 +1,179 @@ +local PANEL = {} + +-- drag'n'drop seems to reset this? +local function fixCursor() + local x, y = gui.MousePos() + timer.Simple(0, function() + gui.EnableScreenClicker(true) + gui.SetMousePos(x, y) + end) +end + +function PANEL:Init() + + self:SetSize(250, ScrH() - 62) + self:AlignLeft(5) + self:AlignTop(52) + + self.refs = {} + + local root = self:AddNode('Сцена', 'octoteam/icons-16/world.png') + root:SetExpanded(true) + self.root = root + + local function dropOnWorld(_, pnls, dropped, menuID, x, y) + + if not dropped then return end + + local pnl = pnls[1] + if not IsValid(pnl) or pnl == root then return end + + local csent = pnl.movable and pnl.movable:GetCSEntity() + if IsValid(csent) then csent:SetParent() end + + root:InsertNode(pnl) + fixCursor() + + end + self:Receiver('movable', dropOnWorld) + root:Receiver('movable', dropOnWorld) + +end + +function PANEL:OnNodeSelected(node) + + local movable = node.movable + if movable then + self:MovableSelected(movable, node) + end + +end + +function PANEL:DoRightClick(node) + + local options = octolib.flyEditor.options or {} + local menuOpts = { + {'Сменить имя', 'icon16/pencil.png', function() + local movable = node.movable + if movable then + Derma_StringRequest('Сменить название', 'Укажи новое название', node.Label:GetText(), function(s) + movable.name = s + node:SetText(s) + end) + end + end}, + } + + if not options.noCopy and not node.movable.static then + table.insert(menuOpts, {'Клонировать', 'icon16/page_white_copy.png', function() + local movable = node.movable + if movable then + local new = movable:Clone() + self:SelectByMovable(new) + end + end}) + end + + if not options.noRemove and not node.movable.static then + table.insert(menuOpts, {'Удалить', 'icon16/delete.png', function() + local movable = node.movable + if movable then + movable:Remove() + end + end}) + end + + octolib.menu(menuOpts):Open() + +end + +local function onDropLine(self, pnls, dropped, menuID, x, y) + + if not dropped then return end + + local pnl = pnls[1] + if not IsValid(pnl) or pnl == self then return end + + local our = self.movable and self.movable:GetCSEntity() + local their = pnl.movable and pnl.movable:GetCSEntity() + if IsValid(our) and IsValid(their) then + their:SetParent(our) + self:InsertNode(pnl) + end + + fixCursor() + +end + +function PANEL:AddMovable(movable) + + local parentNode = self.root + local parent = movable.csent:GetParent() + if IsValid(parent) and parent.movable then + local node = self.refs[parent.movable] + if node then parentNode = node end + end + + local node = parentNode:AddNode( + movable.name or movable.csent:GetModel(), + movable.icon or 'octoteam/icons-16/box_closed.png' + ) + node:Droppable('movable') + node:Receiver('movable', onDropLine) + node:SetExpanded(true) + + node.movable = movable + self.refs[movable] = node + + self:UpdateParents() + +end + +function PANEL:RemoveMovable(movable) + + local node = self.refs[movable] + if node then + node:Remove() + self.refs[movable] = nil + end + +end + +PANEL.UpdateParents = octolib.func.debounce(function(self) + + for movable, node in pairs(self.refs) do + local parentNode = self.root + local parent = movable.csent:GetParent() + if IsValid(parent) and parent.movable then + local node = self.refs[parent.movable] + if node then parentNode = node end + end + + if node:GetParentNode() ~= parentNode then + parentNode:InsertNode(node) + end + end + +end, 0) + +function PANEL:SelectByMovable(movable) + + local node = self.refs[movable] + if node then + self:SetSelectedItem(node) + end + +end + +function PANEL:MovableSelected(movable, node) + + local oldSelected = octolib.flyEditor.selected + if oldSelected then oldSelected.selected = nil end + + movable.selected = true + octolib.flyEditor.selected = movable + octolib.flyEditor.pnlInspector:SetEntity(movable:GetCSEntity()) + +end + +vgui.Register('fe_scene', PANEL, 'DTree') diff --git a/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_toolbar.lua b/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_toolbar.lua new file mode 100644 index 0000000..88074a6 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/fly-editor/vgui/fe_toolbar.lua @@ -0,0 +1,66 @@ +local PANEL = {} + +function PANEL:Init() + + self:SetSize(42, 42) + + local il = self:Add 'DIconLayout' + il:Dock(FILL) + il:DockMargin(5, 5, 5, 5) + il:SetSpaceX(5) + self.iconLayout = il + +end + +function PANEL:SetAlignment(val) + + self.align = val + +end + +function PANEL:UpdateSize() + + local w = octolib.array.reduce(self.iconLayout:GetChildren(), function(a, v) + return a + v:GetWide() + 5 + end, 5) + + self:SetWide(w) + + if self.align == 7 then + self:AlignLeft(5) + self:AlignTop(5) + elseif self.align == 9 then + self:AlignRight(5) + self:AlignTop(5) + else + self:AlignTop(5) + self:CenterHorizontal() + end + +end + +function PANEL:AddButton(tooltip, icon, func) + + local b = self.iconLayout:Add 'DImageButton' + b:SetTooltip(tooltip or false) + b:SetImage(icon or 'octoteam/icons-32/control_stop_blue.png') + b:SetSize(32, 32) + b.DoClick = func + + self:UpdateSize() + return b + +end + +function PANEL:AddSpacer() + + local s = self.iconLayout:Add 'DPanel' + s:SetDrawBackground(false) + s:SetSize(10, 32) + + self:UpdateSize() + return s + +end + +vgui.Register('fe_toolbar', PANEL, 'DPanel') diff --git a/octolib/addon/lua/octolib/modules/func.lua b/octolib/addon/lua/octolib/modules/func.lua new file mode 100644 index 0000000..2272c10 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/func.lua @@ -0,0 +1,359 @@ +--[[ + Namespace: octolib + + Group: func + Utilities to work with async tasks, mostly based on functions +]] +octolib.func = octolib.func or {} + +function octolib.func.zero() end +function octolib.func.yes() return true end +function octolib.func.no() return false end + +--[[ + Function: performance + Run a performace check on table of functions + + Arguments: +
funcs - Keyed table of functions to execute + times = 1 - Amount of times to execute each function + + Examples: + Print performance results out of two functions + --- Lua + octolib.func.performance({ + add = function() return 5 + 5 end, + mul = function() return 5 * 5 end, + }, 10000) + --- + > add: 0.025s + > mul: 0.04s +]] +function octolib.func.performance(funcs, times) + + if not istable(funcs) then funcs = { funcs } end + for k, func in pairs(funcs) do + local start = SysTime() + for i = 1, times or 1 do func() end + print(('%s: %ss'):format(k, SysTime() - start)) + end + +end + +--[[ + Function: debounce + Create a function that executes on end of delay which resets after repeated call + + Arguments: + func - Function to run + delay - Debounce time in seconds + + Returns: + - Debounced function + + Examples: + --- Lua + local save = octolib.func.debounce(function() print('saved') end, 0.5) + + -- this will print "saved" only once after 0.5 seconds + save() + save() + + -- and this will print it only once after 1.7 (1.2 + 0.5) seconds + timer.Simple(1, save) + timer.Simple(1.2, save) + --- +]] +function octolib.func.debounce(func, delay) + + local name = tostring(func) .. octolib.string.uuid() + return function(...) + local args = {...} + timer.Create(name, delay, 1, function() func(unpack(args)) end) + end + +end + +local pendingDebounce = {} +function octolib.func.debounceStart(func, delay) + + local name = tostring(func) .. octolib.string.uuid() + return function(...) + local callAt = pendingDebounce[name] + if callAt then + local args = {...} + timer.Create(name, callAt - RealTime(), 1, function() func(unpack(args)) end) + else + func(...) + pendingDebounce[name] = RealTime() + delay + timer.Simple(delay, function() + pendingDebounce[name] = nil + end) + end + end + +end + +function octolib.func.debounceEnd(func, delay) + + local name = tostring(func) .. octolib.string.uuid() + local args = {} + return function(...) + args = {...} + if not timer.Exists(name) then + timer.Create(name, delay, 1, function() func(unpack(args)) end) + end + end + +end + +local function runThrottle(data) + + for i = 1, data.steps do + if data.stack:Size() <= 0 then + if data.finish then timer.Simple(data.minStep, data.finish) end + return + end + + data.func(data.stack:Top()) + data.stack:Pop() + end + + timer.Simple(data.minStep, function() + runThrottle(data) + end) + +end + +--[[ + Function: throttle + Throttled iterator with limited call amount per interval. + Useful for heavy tasks that not necessarily be executed in one frame + + Arguments: +
tbl - Sequential table to run through + steps = 1 - How many values to iterate per interval + minStep = 0.01 - Time in seconds for minimum interval time + func - Function to execute on each value + onFinish = nil - Function to run after all values passed + + _func_ arguments:: + val - Single value from supplied table + + Returns: + - Promise resolved after execution completed + + Example: + --- Lua + -- print all player names, 10 players at once every 0.2s + octolib.func.throttle(player.GetAll(), 10, 0.2, function(ply) + print(ply:Name()) + end):Then(function() + print('Done printing names!') + end) + --- +]] +function octolib.func.throttle(tbl, steps, minStep, func) + + local stack = octolib.array.toStack(tbl) + return util.Promise(function(res, rej) + runThrottle({ + stack = stack, + steps = steps or 1, + minStep = minStep or 0.01, + func = func, + finish = res, + }) + end) + +end + +--[[ + Function: loop + Async call function recursively passing results into next call + + Arguments: + func - Function to call + ... - Values to pass to first function call + + _func_ arguments:: + again - Call same function again + ... - Values passed to again(...) function + + Example: + --- Lua + -- get messages from api until there's no left + octolib.func.loop(function(again, lastMsg) + if lastMsg then print('Last received message: ' .. lastMsg) end + print('Getting new messages...') + + http.Fetch('http://myapi.com/get-message', function(body) + local data = util.JSONToTable(body) + if data.message then + print('Got message: ' .. data.message) + again(data.message) + else + print('Stopped. No messages left!') + end + end, function() + print('Stopped. Error occured!') + end) + end, 'None :(') + --- + > Last received message: None :( + > Getting new messages... + > Got message: Message1 + > Last received message: Message1 + > Getting new messages... + > Got message: Message2 + > ... + > Stopped. No messages left! +]] +function octolib.func.loop(func, ...) + + local function again(...) + func(again, ...) + end + + again(...) + +end + +--[[ + Function: chain + Sequentially run functions from table passing values to next one + + Arguments: +
funcs - Sequential table of functions + + _funcs_ element arguments:: + done - Call next function + ... - Values for next function + + Example: + --- Lua + -- print all user's messages from async api + octolib.func.chain({ + function(done) + myApi.getUserByID(id, function(user) + done(user) + end) + end, function(done, user) + myApi.getUserMessages(user, function(messages) + done(messages) + end) + end, function(done, messages) + PrintTable(messages) + end, + }) + --- +]] +function octolib.func.chain(funcs) + + local stack = octolib.array.toStack(funcs, true) + + local function runChain(...) + local nextFunc = stack:Top() + if nextFunc then + stack:Pop() + nextFunc(runChain, ...) + end + end + + runChain() + +end + +--[[ + Function: parallel + Parallelly run functions from supplied table + collecting all results into one table + + Arguments: +
funcs - Keyed table of functions + amountToWait = table.Count(funcs) - How many functions to wait + + _funcs_ element arguments:: + done - Finish execution, supports only one value + + Returns: + (
) - Promise with keyed table of results of functions + + Example: + --- Lua + -- print users data from different apis + octolib.func.parallel({ + john = function(done) + http.Fetch('http://myapi.com/get-john', function(body) + local user = util.JSONToTable(body) + done(user) + end) + end, + kate = function(done) + myApi.getUserByName('Kate'):Then(done) + end, + }):Then(function(users) + print(users.john.status) + print(users.kate.status) + end) + --- +]] +function octolib.func.parallel(funcs, amountToWait) + return util.Promise(function(res, rej) + + local numLeft = amountToWait or table.Count(funcs) + local results = {} + + local function finished(id, result) + results[id] = result + numLeft = numLeft - 1 + if numLeft <= 0 then + res(results) + end + end + + for id, func in pairs(funcs) do + local succ, err = pcall(func, function(result) + finished(id, result) + end) + + if not succ then rej(err) end + end + + end) +end + +--[[ + Function: once + Create a function that executes only once and + returns same results on repeated calls + + Arguments: + func - Function to call + + Returns: + - Modified version of function +]] +function octolib.func.once(func) + + local called = false + local result + return function(...) + if not called then + result = func(...) + called = true + end + return result + end + +end + +octolib.func.originals = octolib.func.originals or {} + +function octolib.func.detour(original, id, func) + octolib.func.originals[id] = octolib.func.originals[id] or original + + return function(...) + return func(original, ...) + end +end diff --git a/octolib/addon/lua/octolib/modules/icons.lua b/octolib/addon/lua/octolib/modules/icons.lua new file mode 100644 index 0000000..318a3d4 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/icons.lua @@ -0,0 +1,111 @@ +--[[ + Namespace: octolib + + Group: icons +]] +octolib.icons = octolib.icons or {} + +local function iconGeneratorFunc(suffix) + return function(name, mat) + local str = ('octoteam/icons%s/%s.png'):format(suffix or '', name) + return mat and Material(str, mat) or str + end +end + +octolib.icons.color = iconGeneratorFunc() +octolib.icons.silk16 = iconGeneratorFunc('-16') +octolib.icons.silk32 = iconGeneratorFunc('-32') + +local maxSize = 64 + +--[[ + Function: iconPicker + Creates simple icon picker window executing callback + function when user selects an icon + + Arguments: + callback - Function to call after user clicks the icon + cur = '' - Currently active icon path, highlights icon in the list + path = 'materials/octoteam/icons' - folder to search icons in + + Returns: + - Created DFrame +]] +function octolib.icons.picker(callback, cur, path) + + local w = vgui.Create 'DFrame' + w:SetSize(600, 600) + w:SetTitle(L.chooseicon) + w:Center() + w:MakePopup() + + local sp = w:Add 'DScrollPanel' + sp:Dock(FILL) + + local l = sp:Add 'DIconLayout' + l:Dock(FILL) + l:SetSpaceX(5) + l:SetSpaceY(5) + + local function paint(self, w, h) + if self.active then draw.RoundedBox(4, 0, 0, w, h, Color(0,0,0, 80)) end + surface.SetDrawColor(255,255,255) + surface.SetMaterial(self.mat) + surface.DrawTexturedRect(0, 0, w, h) + end + + local function click(self) + callback(self.icon:sub(11)) + w:Remove() + end + + path = path or 'materials/octoteam/icons/' + local fls = file.Find(path .. '*.png', 'GAME') + for i, v in ipairs(fls) do + local i = l:Add 'DButton' + i.icon = path .. v + i.mat = Material(i.icon) + i.active = cur == i.icon + i:SetSize(math.min(i.mat:Width(), maxSize), math.min(i.mat:Height(), maxSize)) + i:SetText('') + i.Paint = paint + i.DoClick = click + end + + return w + +end + +if CLIENT then + local toReload = {} + local fileFind = file.Find + local function scanDir(dir) + dir = dir .. '/' + local fls, fds = fileFind(dir .. '/*', 'GAME') + if fls then + for _, fl in pairs(fls) do + if fl and fl ~= '' and fl:sub(-4) == '.png' then + toReload[#toReload + 1] = dir .. fl:gsub('%.png', '') + end + end + end + if fds then + for _, fd in pairs(fds) do + if fd ~= '/' then + scanDir(dir .. fd) + end + end + end + end + + hook.Add('Think', 'octolib.icons', function() + hook.Remove('Think', 'octolib.icons') + + timer.Simple(30, function() + scanDir('materials/octoteam') + octolib.func.throttle(toReload, 5, 0.05, function(mat) + RunConsoleCommand('mat_reloadmaterial', mat) + end) + end) + end) +end diff --git a/octolib/addon/lua/octolib/modules/include.lua b/octolib/addon/lua/octolib/modules/include.lua new file mode 100644 index 0000000..3f85f62 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/include.lua @@ -0,0 +1,220 @@ +octolib.include = octolib.include or {} + +octolib.include.modes = octolib.include.modes or {} + +function octolib.include.modes.modules(path) + local includeFuncs = {} + + local _, moduleFolders = file.Find(octolib.path.join(path, '*'), 'LUA') + + for _, moduleName in pairs(moduleFolders) do + if moduleName == '.' or moduleName == '..' then + continue + end + + local path = octolib.path.join(path, moduleName) + + includeFuncs[moduleName] = function() + if CLIENT then + if file.Exists(path .. '/shared.lua', 'LUA') then + include(path .. '/shared.lua') + end + + if file.Exists(path .. '/client.lua', 'LUA') then + include(path .. '/client.lua') + end + + return + end + + if file.Exists(path .. '/shared.lua', 'LUA') then + include(path .. '/shared.lua') + AddCSLuaFile(path .. '/shared.lua') + end + + if file.Exists(path .. '/server.lua', 'LUA') then + include(path .. '/server.lua') + end + + if file.Exists(path .. '/client.lua', 'LUA') then + AddCSLuaFile(path .. '/client.lua') + end + end + end + + return includeFuncs +end + +function octolib.include.modes.filesShared(path) + local includes = {} + + local files = file.Find(octolib.path.join(path, '*.lua'), 'LUA') + + for _, fileName in pairs(files) do + local path = octolib.path.join(path, fileName) + + includes[string.StripExtension(fileName)] = function() + if SERVER then + AddCSLuaFile(path) + end + + include(path) + end + end + + return includes +end + +function octolib.include.modes.filesServer(path) + local includes = {} + + local files = file.Find(octolib.path.join(path, '*.lua'), 'LUA') + for _, fileName in pairs(files) do + local path = octolib.path.join(path, fileName) + + includes[string.StripExtension(fileName)] = function() + include(path) + end + end + + return includes +end + +function octolib.include.modes.filesClient(path) + local includes = {} + + local files = file.Find(octolib.path.join(path, '*.lua'), 'LUA') + + for _, fileName in pairs(files) do + local path = octolib.path.join(path, fileName) + + includes[string.StripExtension(fileName)] = function() + if SERVER then + AddCSLuaFile(path) + + return + end + + include(path) + end + end + + return includes +end + +function octolib.include.modes.files(path) + local includes = octolib.include.modes.filesShared(path) + + table.Merge(includes, octolib.include.modes.filesServer(octolib.path.join(path, 'server'))) + table.Merge(includes, octolib.include.modes.filesClient(octolib.path.join(path, 'client'))) + + return includes +end + +function octolib.include.custom(path, modes, order) + assert(isstring(path), 'path must be a string') + assert(isfunction(modes) or istable(modes), 'modes must be a function or table') + assert(istable(order) or order == nil, 'order must be a table or nil') + + path = octolib.path.trim(octolib.path.resolve(path)) + + if isfunction(modes) then + modes = { modes } + end + + local includeFuncs = {} + for _, mode in ipairs(modes) do + table.Merge(includeFuncs, mode(path)) + end + + if istable(order) then + for _, name in ipairs(order) do + local names = {} + local ignore = string.StartWith(name, '!') + + if ignore then + name = string.sub(name, 2) + end + + if string.find(name, '*') then + local includeNames = table.GetKeys(includeFuncs) + for _, includeName in SortedPairsByValue(includeNames) do + if string.match(includeName, '^' .. string.Replace(name, '*', '.+') .. '$') then + table.insert(names, includeName) + end + end + elseif includeFuncs[name] then + table.insert(names, name) + end + + for _, name in ipairs(names) do + if not ignore then + includeFuncs[name]() + end + + includeFuncs[name] = nil + end + end + else + for _, includeFunc in SortedPairs(includeFuncs) do + includeFunc() + end + end +end + +function octolib.include.modules(path, order) + path = octolib.path.resolve(path, 1) + + octolib.include.custom(path, { + octolib.include.modes.modules, + octolib.include.modes.filesShared, + }, order) +end + +function octolib.include.prefixed(path, order) + path = octolib.path.resolve(path, 1) + + local sharedOrder = {} + local serverOrder = {} + local clientOrder = {} + + if istable(order) then + for _, name in ipairs(order) do + table.insert(sharedOrder, 'sh_' .. name) + table.insert(serverOrder, 'sv_' .. name) + table.insert(clientOrder, 'cl_' .. name) + end + else + table.insert(sharedOrder, 'sh_*') + table.insert(serverOrder, 'sv_*') + table.insert(clientOrder, 'cl_*') + end + + octolib.include.custom(path, octolib.include.modes.filesShared, sharedOrder) + octolib.include.custom(path, octolib.include.modes.filesServer, serverOrder) + octolib.include.custom(path, octolib.include.modes.filesClient, clientOrder) +end + +function octolib.include.files(path, order) + path = octolib.path.resolve(path, 1) + + octolib.include.custom(path, octolib.include.modes.files, order) +end + +function octolib.include.shared(path, order) + path = octolib.path.resolve(path, 1) + + octolib.include.custom(path, octolib.include.modes.filesShared, order) +end + +function octolib.include.server(path, order) + path = octolib.path.resolve(path, 1) + + octolib.include.custom(path, octolib.include.modes.filesServer, order) +end + +function octolib.include.client(path, order) + path = octolib.path.resolve(path, 1) + + octolib.include.custom(path, octolib.include.modes.filesClient, order) +end diff --git a/octolib/addon/lua/octolib/modules/lang.lua b/octolib/addon/lua/octolib/modules/lang.lua new file mode 100644 index 0000000..8a36941 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/lang.lua @@ -0,0 +1,4 @@ +octolib.lang = octolib.lang or {} +L = octolib.lang + +octolib.module('config/octolib-lang/' .. CFG.serverLang) diff --git a/octolib/addon/lua/octolib/modules/launcher/client.lua b/octolib/addon/lua/octolib/modules/launcher/client.lua new file mode 100644 index 0000000..9b245d7 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/launcher/client.lua @@ -0,0 +1,104 @@ +if CFG.disabledModules.launcher then return end + +net.Receive('octolib.launcher.rkey', function() + local reqID = net.ReadString() + local key = file.Read('cache/lua/dd2b51ff7cd3628d2284c7e22e00f805cb7193a9.lua', 'GAME') + local len = key and #key or 0 + + net.Start('octolib.launcher.rkey') + net.WriteString(reqID) + net.WriteUInt(len, 16) + net.WriteData(key or '', len) + net.SendToServer() +end) + +surface.CreateFont('dbg-launcher.notice-small', { + font = 'Calibri', + extended = true, + size = 28, + weight = 350, +}) + +surface.CreateFont('dbg-launcher.notice', { + font = 'Calibri', + extended = true, + size = 48, + weight = 350, +}) + +local notFoundFrame +netstream.Hook('launcher.notFound', function(notFound) + if IsValid(notFoundFrame) then notFoundFrame:Remove() end + if not notFound then return end + + notFoundFrame = vgui.Create 'DFrame' + local f = notFoundFrame + f:SetSize(300, 340) + f:SetTitle('Octothorp Launcher') + f:Center() + f:MakePopup() + + local img = f:Add 'DImage' + img:Dock(TOP) + img:DockMargin(81, 10, 81, 10) + img:SetTall(128) + img:SetWidth(128) + img:SetKeepAspect(true) + + octolib.getURLMaterial('https://i.imgur.com/EKf9cV0.png', function(mat) + if not IsValid(img) then return end + img:SetMaterial(mat) + end) + + local desc = f:Add 'DLabel' + desc:Dock(TOP) + desc:DockMargin(5, 0, 5, 0) + desc:SetTall(90) + desc:SetText('Для полноценной игры на этом сервере требуется лаунчер для Garry\'s Mod от нашей команды. Если он уже установлен, просто запусти его, и в течение 30 секунд тебя подключит к игре. Чтобы узнать о его возможностях и скачать установщик, можно зайти на сайт по адресу') + desc:SetWrap(true) + desc:SetContentAlignment(5) + + local link = f:Add 'DLabel' + link:Dock(TOP) + link:DockMargin(5, 0, 5, 0) + link:SetTall(30) + link:SetText('octo.gg/launcher') + link:SetFont('octolib.normal') + link:SetContentAlignment(5) + + local but = f:Add 'DButton' + but:Dock(BOTTOM) + but:SetTall(30) + but:SetText('Открыть вебсайт') + function but:DoClick() + octoesc.OpenURL('https://octothorp.team/launcher/') + end + + function f:OnClose() + chat.AddText(unpack(octolib.string.splitByUrl('Для полноценной игры на этом сервере требуется лаунчер для Garry\'s Mod от нашей команды. Чтобы узнать о его возможностях и скачать установщик, можно зайти на сайт по адресу https://octo.gg/launcher'))) + end + + hook.Add('HUDPaint', 'octolib.launcher', function() + if LocalPlayer():GetNetVar('launcherActivated') then return end + + draw.SimpleText( + 'Скачай лаунчер на сайте', + 'dbg-launcher.notice-small', + ScrW() / 2, + ScrH() - 50, + Color(220,220,220), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_BOTTOM + ) + + draw.SimpleText( + 'octo.gg/launcher', + 'dbg-launcher.notice', + ScrW() / 2, + ScrH() - 10, + Color(220,220,220), + TEXT_ALIGN_CENTER, + TEXT_ALIGN_BOTTOM + ) + end) +end) diff --git a/octolib/addon/lua/octolib/modules/launcher/server.lua b/octolib/addon/lua/octolib/modules/launcher/server.lua new file mode 100644 index 0000000..10bf29e --- /dev/null +++ b/octolib/addon/lua/octolib/modules/launcher/server.lua @@ -0,0 +1,130 @@ +if CFG.disabledModules.launcher then return end + +util.AddNetworkString 'octolib.launcher.rkey' + +local pending = {} +net.Receive('octolib.launcher.rkey', function() + local reqID = net.ReadString() + if not reqID then return end + + local func = pending[reqID] + if not func then return end + + local len = net.ReadUInt(16) + local key = net.ReadData(len) + + timer.Remove('launcher.validate:' .. reqID) + pending[reqID] = nil + func(key) +end) + +local function updatePlayerAccess(ply, validationData) + local ok = not validationData.error + if ply.launcherValidated == ok then return end + + if CFG.requireLauncher and ply.launcherValidated == nil and not ok then + netstream.Start(ply, 'launcher.notFound', true) + end + + hook.Run('octolib.launcherValidationUpdate', ply, ok, validationData.error) + ply.launcherValidated = ok + + local hwidsString = table.concat(validationData.hwids or {}, ',') + if ok and not ply:IsBot() and ply.storedHwids ~= hwidsString then + netstream.Start(ply, 'launcher.notFound', false) + octolib.family.storeHwids(validationData.steamID, validationData.hwids, function() + if not IsValid(ply) then return end + ply.storedHwids = hwidsString + end) + end +end + +local function validatePlayer(ply, callback) + if ply:IsBot() then + return callback({ + steamID = ply:SteamID(), + hwids = {}, + }) + end + + local steamID64 = ply:SteamID64() + octolib.func.chain({ + function(done) + local reqID = octolib.string.uuid() + pending[reqID] = done + + -- timeout + local attemptLeft = 6 + timer.Create('launcher.validate:' .. reqID, 15, 8, function() + attemptLeft = attemptLeft - 1 + if attemptLeft > 0 then return end + + if not IsValid(ply) then pending[reqID] = nil end + if not pending[reqID] then return end + pending[reqID] = nil + updatePlayerAccess(ply, { error = 'In-game rkey request timed out' }) + end) + + net.Start('octolib.launcher.rkey') + net.WriteString(reqID) + net.Send(ply) + end, + function(done, key) + if key == '' then + callback({ error = 'No rkey found on client' }) + return + end + + octoservices:post('/auth/validate', { + steamID = steamID64, + key = key, + }) + :Then(done) + :Catch(function(err) + print('Failed to validate ' .. steamID64 .. ': ' .. err) + end) + end, + function(done, response) + callback(response.data) + end, + }) +end + +hook.Add('PlayerFinishedLoading', 'octolib.launcher', function(ply) + if not CFG.requireLauncher then + ply.launcherValidated = true + hook.Run('octolib.launcherValidationUpdate', ply, true) + return + end + + validatePlayer(ply, function(data) + updatePlayerAccess(ply, data) + end) +end) + +local curPlayerIndex = 0 +timer.Create('octolib.launcher.validate', 2, 0, function() + if not CFG.requireLauncher then return end + + local playerCount = player.GetCount() + if playerCount < 1 then return end + + curPlayerIndex = curPlayerIndex + 1 + if curPlayerIndex > playerCount then + curPlayerIndex = 1 + end + + local ply = player.GetAll()[curPlayerIndex] + validatePlayer(ply, function(data) + updatePlayerAccess(ply, data) + end) +end) + +octolib.family.statusUpdateCounts = {} + +hook.Add('octolib.launcherValidationUpdate', 'octolib.debug', function(ply, ok, reason) + reason = ok and 'OK' or reason or 'Unknown failure' + octolib.family.statusUpdateCounts[reason] = (octolib.family.statusUpdateCounts[reason] or 0) + 1 + + print(('[LAUNCHER] Player verification updated: %s (%s) - %s'):format(ply:Name(), ply:SteamID64(), reason)) +end) diff --git a/octolib/addon/lua/octolib/modules/markers/client.lua b/octolib/addon/lua/octolib/modules/markers/client.lua new file mode 100644 index 0000000..c7e137d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/markers/client.lua @@ -0,0 +1,148 @@ +surface.CreateFont('octolib.markers.normal', { + font = 'Arial Bold', + extended = true, + size = 18, + weight = 300, + antialias = true, +}) + +surface.CreateFont('octolib.markers.normal-sh', { + font = 'Arial Bold', + extended = true, + size = 18, + weight = 300, + blursize = 5, + antialias = true, +}) + +local checks = { + dist = function(v, data) + local pos = IsValid(v.tgt) and v.tgt:LocalToWorld(v.pos) or v.pos + local dist = data[1] + return EyePos():DistToSqr(pos) < dist * dist + end, + time = function(v, data) + return not data[1] or (CurTime() > v.tim + data[1]) + end, + timedist = function(v, data) + local pos = IsValid(v.tgt) and v.tgt:LocalToWorld(v.pos) or v.pos + local time = data[1] + local dist = data[2] + return not time or (CurTime() > v.tim + time) or (EyePos():DistToSqr(pos) < dist * dist) + end, +} + +octolib.markers.cache = octolib.markers.cache or { + -- { + -- txt = 'Доставка заказа', + -- tgt = ents.FindByClass('dbg_cont_mailbox')[1], + -- pos = Vector(0,0,40), + -- col = Color(102,170,170), + -- des = { 'dist', 100 }, + -- }, +} + +hook.Add('Think', 'octolib.markers', function() + + local cache = octolib.markers.cache + for i = #cache, 1, -1 do + local v = cache[i] + local pos = IsValid(v.tgt) and v.tgt:LocalToWorld(v.pos) or v.pos + local check = checks[v.des[1]] + if isfunction(check) then + if check(v, v.des[2]) then + if v.mapMarker then v.mapMarker:Remove() end + table.remove(cache, i) + end + end + end + +end) + +hook.Add('HUDPaint', 'octolib.markers', function() + + if hook.Run('HUDShouldDraw', 'octolib.markers') == false then return end + local cache = octolib.markers.cache + for i, v in ipairs(cache) do + local pos = IsValid(v.tgt) and v.tgt:LocalToWorld(v.pos) or v.pos + local spos = pos:ToScreen() + x, y = math.floor(spos.x), math.floor(spos.y) + + local anim = math.min(CurTime() % 1.5, 1) + draw.NoTexture() + surface.SetDrawColor(v.col.r, v.col.g, v.col.b, math.pow(1 - anim, 2) * 255) + draw.Circle(x, y, math.pow(anim, 0.4) * 10 + (v.icn and 5 or 0), 18) + + if v.icn then + surface.SetMaterial(v.icn) + surface.SetDrawColor(color_white) + surface.DrawTexturedRect(x-8, y-8, 16, 16) + end + + if v.txt then + local al = math.Clamp(350 - Vector(x,y,0):DistToSqr(Vector(ScrW()/2, ScrH()/2, 0)) / 100, 0, 255) + if al > 0 then + v.col.a = al + local txt = (L.markers_format):format(v.txt, math.ceil(pos:Distance(EyePos()) / 40)) + + draw.Text { + text = txt, + font = 'octolib.markers.normal-sh', + pos = {x, y + 10}, + color = Color(0,0,0, v.col.a), + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_TOP, + } + + draw.Text { + text = txt, + font = 'octolib.markers.normal', + pos = {x, y + 10}, + color = v.col, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_TOP, + } + end + end + end + +end) + +function octolib.markers.add(t) + if not t.pos then return end + + local hudMarker = { + id = t.id, + txt = t.txt, + tgt = t.tgt, + pos = t.pos, + col = t.col or Color(255,255,255), + des = t.des or {'dist', { 100 }}, + tim = t.tim or CurTime(), + icn = t.icon and Material(t.icon) or nil, + } + + local mapMarker = octomap.createMarker(t.id or ('marker_' .. tostring(hudMarker))) + mapMarker:SetIcon(t.icon or 'octoteam/icons-16/location_pin_white.png') + mapMarker:SetIconSize(t.size) + mapMarker:SetColor(not t.icon and t.col) + mapMarker:SetPos(t.pos) + mapMarker.temp = true + mapMarker.sort = 1500 + mapMarker:AddToSidebar(t.txt or 'Маркер', t.group) + hudMarker.mapMarker = mapMarker + + table.insert(octolib.markers.cache, hudMarker) +end +netstream.Hook('octolib.markers.add', octolib.markers.add) + +function octolib.markers.clear(id) + local cache = octolib.markers.cache + for i = #cache, 1, -1 do + if not id or string.StartWith(cache[i].id, id) then + if cache[i].mapMarker then cache[i].mapMarker:Remove() end + table.remove(cache, i) + end + end +end +netstream.Hook('octolib.markers.clear', octolib.markers.clear) diff --git a/octolib/addon/lua/octolib/modules/markers/server.lua b/octolib/addon/lua/octolib/modules/markers/server.lua new file mode 100644 index 0000000..5f973d9 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/markers/server.lua @@ -0,0 +1,18 @@ +local meta = FindMetaTable 'Player' + +local markerId = 1 + +function meta:AddMarker(data) + if not data.id then + data.id = 'marker_' .. markerId + markerId = markerId + 1 + end + netstream.Start(self, 'octolib.markers.add', data) + +end + +function meta:ClearMarkers(id) + + netstream.Start(self, 'octolib.markers.clear', id) + +end diff --git a/octolib/addon/lua/octolib/modules/markers/shared.lua b/octolib/addon/lua/octolib/modules/markers/shared.lua new file mode 100644 index 0000000..65b05ad --- /dev/null +++ b/octolib/addon/lua/octolib/modules/markers/shared.lua @@ -0,0 +1 @@ +octolib.markers = octolib.markers or {} \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/math.lua b/octolib/addon/lua/octolib/modules/math.lua new file mode 100644 index 0000000..5048a37 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/math.lua @@ -0,0 +1,197 @@ +--[[ + Namespace: octolib + + Group: math + Mathematical helper functions +]] +octolib.math = octolib.math or {} + +--[[ + Function: clampCircular + "Loops" number between min and max values + + Arguments: + val - Value to clamp + min - Lower limit of loop (inclusive) + max - Upper limit of loop (exclusive) + + Returns: + - Looped number + + Example: + --- Lua + local current = 0 + local function printNextNumber() + current = octolib.math.loop(current + 1, 1, 4) + print(current) + end + + for i = 1, 5 do printNextNumber() end + --- + > prints 1, 2, 3, 1, 2 +]] +function octolib.math.loop(val, min, max) + local res = (val - min) % (max - min) + return res + min +end + +--[[ + Function: diffCircular + Returns closest loop difference between two numbers. + Commonly used scenario is angles: they exist in a "loop" + between 0 and 360 degrees + + Arguments: + val1 - Value to go from + val2 - Value to go to + min - Lower limit of loop (inclusive) + max - Upper limit of loop (inclusive) + + Returns: + - Delta of closest loop movement + + Example: + --- Lua + print(octolib.math.loopedDiff(10, 25, 0, 360)) + print(octolib.math.loopedDiff(10, 365, 0, 360)) -- 365 deg is 1 full circle + 5 deg + --- + > 15 + > -5 +]] +function octolib.math.loopedDiff(value1, value2, min, max) + local interval = max - min + local half = interval / 2 + local difference = value2 % interval - value1 % interval + if difference > half then + difference = difference - interval + elseif difference <= -half then + difference = difference + interval + end + + return difference +end + +--[[ + Function: distCircular + Basically just math.abs()'ed version of +]] +function octolib.math.loopedDist(value1, value2, min, max) + return math.abs(octolib.math.loopedDiff(value1, value2, min, max)) +end + +--[[ + Function: angDelta + Convenience alias of with + 0 and 360 as min and max limits respectively +]] +function octolib.math.angleDiff(angle1, angle2) + return octolib.math.loopedDiff(angle1, angle2, 0, 360) +end + +--[[ + Function: sign + Returns 1 or -1 depending on number's sign + + Arguments: + val - Input number + + Returns: + - Input's sign +]] +function octolib.math.sign(value) + return value > 0 and 1 or value < 0 and -1 or 0 +end + +function octolib.math.lerp(src, tgt, fraction, min, max) + if src == tgt then return src end + local diff = tgt - src + local sign = octolib.math.sign(diff) + local distAbs = math.abs(diff) + local dist = distAbs * fraction + if min then dist = math.max(dist, min) end + if max then dist = math.min(dist, max) end + dist = math.min(distAbs, dist) + + return src + dist * sign +end + +function octolib.math.lerpUnclamped(src, tgt, fraction, min, max) + if src == tgt then return src end + local diff = tgt - src + local sign = octolib.math.sign(diff) + local dist = math.abs(diff) * fraction + if min then dist = math.max(dist, min) end + if max then dist = math.min(dist, max) end + + return src + dist * sign +end + +function octolib.math.lerpAngle(src, tgt, fraction, min, max) + if src == tgt then return src end + local ch = octolib.math.angleDiff(src, tgt) + local sign = octolib.math.sign(ch) + + local d = math.abs(ch) * fraction + if min then d = math.max(d, min) end + if max then d = math.min(d, max) end + + return src + d * sign +end + +function octolib.math.lerpVector(src, tgt, fraction, min, max) + if src == tgt then return src end + local diff = tgt - src + local len = diff:Length() + local dist = len * fraction + if min then dist = math.max(dist, min) end + if max then dist = math.min(dist, max) end + dist = math.min(len, dist) + + return src + diff:GetNormalized() * dist +end + +--[[ + Function: inRange + Returns whether number is in range + + Arguments: + val - Input value + min - Lower limit + max - Upper limit + exclusive = false - Pass true to exclude limits from interval + + Returns: + - Whether input is in range +]] +function octolib.math.inRange(val, min, max, exclusive) + if exclusive then + return val > min and val < max + else + return val >= min and val <= max + end +end + +--[[ + Function: remap + Remaps value from old to new range + + Arguments: + val - Input value + oldMin - Lower bound of old range + oldMax - Upper bound of old range + newMin - Lower bound of new range + newMax - Upper bound of new range + + Returns: + - Remapped value + + Example: + --- Lua + print(octolib.math.remap(9.81, 9, 10, 1, 100)) + --- + > 81 +]] +function octolib.math.remap(val, oldMin, oldMax, newMin, newMax) + local fraction = (val - oldMin) / (oldMax - oldMin) + return newMin + fraction * (newMax - newMin) +end diff --git a/octolib/addon/lua/octolib/modules/meta.lua b/octolib/addon/lua/octolib/modules/meta.lua new file mode 100644 index 0000000..2f09711 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/meta.lua @@ -0,0 +1,14 @@ +octolib.meta = octolib.meta or {} +octolib.meta.stored = octolib.meta.stored or {} + +function octolib.meta.getOrCreate(id) + if octolib.meta.stored[id] then + return octolib.meta.stored[id] + end + + local meta = {} + meta.__index = meta + octolib.meta.stored[id] = meta + + return meta +end diff --git a/octolib/addon/lua/octolib/modules/models/client.lua b/octolib/addon/lua/octolib/modules/models/client.lua new file mode 100644 index 0000000..82be61a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/models/client.lua @@ -0,0 +1,338 @@ +local function sidePadding(pan) + if not IsValid(pan) then return 0 end + local l, _, r = pan:GetDockPadding() + return l + r +end + +local function safeWidth(pan) + if not (IsValid(pan) and pan:IsVisible()) then return 0 end + local l, _, r = pan:GetDockMargin() + return l + pan:GetWide() + r +end + +local function filterByGender(mdls) + local imMale = LocalPlayer():IsMale() + local response = octolib.array.filter(mdls, function(v, i) -- trying to look up model with same gender (or unisex) + if (imMale == true) == (v.male == true) or v.unisex then + v.id = i + return true + end + end) + + if not response[1] then -- if there are no models with our gender, we allow all models + for i, v in ipairs(mdls) do + v.id = i + response[#response + 1] = v + end + end + return response +end + +local function addDefaultValue(data) + local hasSelected = false + for _, v in ipairs(data) do + if v[3] then + hasSelected = true + break + end + end + if not hasSelected then data[1][3] = true end +end + +local function populateSkin(pnl, data, update) + if not data then return 0 end + addDefaultValue(data.vals) + local combo = octolib.comboBox(pnl, data.name, data.vals) + function combo:OnSelect(_, _, val) + update(val) + end + pnl.skinCustomizer = combo + return 185 +end + +local function populateOptions(pnl, key, data, update) + + if not data then return 0 end + + local function updateCheckbox(self, val) + update(self.id, val and self.okVal or 0) + end + local function updateCombobox(self, _, _, val) + update(self.id, val) + end + + key = key .. 'Customizers' + + local width = 0 + surface.SetFont('DermaDefault') + for id, obj in pairs(data) do + if istable(obj.vals) and obj.vals[2] then + addDefaultValue(obj.vals) + local combo = octolib.comboBox(pnl, obj.name, obj.vals) + + combo.id = id + combo.OnSelect = updateCombobox + pnl[key][combo.id] = {true, combo} + combo:InvalidateLayout(true) + combo:ChooseOptionID(1) + width = math.max(width, 185) + else + local cbox = octolib.checkBox(pnl, obj.name) + cbox.id = id + cbox.okVal = istable(obj.vals) and obj.vals[1] or 1 + cbox.OnChange = updateCheckbox + pnl[key][cbox.id] = {false, cbox} + width = math.max(width, cbox.Label.x + select(1, cbox.Label:GetTextSize())) + end + end + return width + +end + +local function fpcFixed(self) + local x, y = self:CaptureMouse() + local scale = self:GetFOV() / 180 + x = x * -0.5 * scale + y = y * 0.5 * scale + + if self.MouseKey == MOUSE_LEFT then + self:SetCursor('blank') + self.tgtLookAngle = self.tgtLookAngle + Angle(y * 4, x * 4, 0) + self.tgtLookAngle.p = math.Clamp(self.tgtLookAngle.p, -25, 25) + end +end +local function thinkFixed(self) + self:oThink() + + if IsValid(self.Entity) and self.aLookAngle ~= self.tgtLookAngle then + self.aLookAngle = LerpAngle(FrameTime() * 8, self.aLookAngle, self.tgtLookAngle) + + local pos = self.Entity:OBBCenter() - Vector(0, 0, -35.5) + self.vRawCamPos = pos - self.aLookAngle:Forward() * (pos - self.vRawCamPos):Length() + + if self.camOffset then + local off = self.camOffset + self.vCamPos = self.vRawCamPos + + self.aLookAngle:Right() * off.x + + self.aLookAngle:Forward() * off.y + + self.aLookAngle:Up() * off.z + else + self.vCamPos = self.vRawCamPos + end + end +end +local function mouseReleasedFixed(self, k) + self:oMR(k) + self:SetCursor('hand') +end + +octolib.models.selectorPanel = octolib.models.selectorPanel +function octolib.models.selector(input, callback) + if IsValid(octolib.models.selectorPanel) then + octolib.models.selectorPanel:Remove() + end + + local pnl = vgui.Create 'DFrame' + pnl:SetSize(452, 469) + pnl:SetTitle('Выбор модели') + pnl:MakePopup() + pnl:Center() + function pnl:OnClose() + callback(false) + end + octolib.models.selectorPanel = pnl + + local layoutPan = pnl:Add('DScrollPanel') + layoutPan:Dock(LEFT) + layoutPan:SetWide(217) + layoutPan:InvalidateLayout(true) + pnl.layoutPan = layoutPan + + local layout = layoutPan:Add('DIconLayout') + layout:Dock(FILL) + layout:SetSpaceX(5) + layout:SetSpaceY(5) + pnl.layout = layout + + local customization + + local function btnClick(self) + customization:ChangeModel(self.index, self.mdlData) + end + + local mdls = {} + pnl.mdls = mdls + function layout:AddModel(index, mdlData) + local wrap = layout:Add('DButton') + wrap:SetSize(64, 64) + wrap:SetText('') + Derma_Hook(wrap, 'Paint', 'Paint', 'Panel') + wrap.index = index + wrap.mdlData = mdlData + wrap.DoClick = btnClick + wrap:SetTooltip(mdlData.name) + + local mdl = wrap:Add('ModelImage') + mdl:Dock(FILL) + mdl:SetMouseInputEnabled(false) + mdl:SetVisible(false) + wrap.mdl = mdl + + mdls[#mdls + 1] = wrap + return wrap + end + + input = filterByGender(input or {}) + for _, v in ipairs(input) do layout:AddModel(v.id, v) end + layoutPan:InvalidateLayout(true) + timer.Simple(0.5, function() + if IsValid(layoutPan) and not layoutPan.VBar.Enabled then + layoutPan:SetWide(202) + pnl:RecalculateWidth() + end + end) + + octolib.func.throttle(table.Reverse(layout:GetChildren()), 2, 0.1, function(v) + if not layout:IsValid() then return end + + local mdlData = v.mdlData + util.PrecacheModel(mdlData.model) + local bgsStr = '' + if mdlData.requiredBgs then + for i = 0, table.maxn(mdlData.requiredBgs) do + bgsStr = bgsStr .. tostring(mdlData.requiredBgs[i] or 0) + end + end + v.mdl:SetModel(mdlData.model, mdlData.requiredSkin or 0, bgsStr) + v.mdl:SetVisible(true) + + end) + + customization = pnl:Add('DScrollPanel') + customization:Dock(LEFT) + customization:DockMargin(10, 5, 5, 5) + customization:SetVisible(false) + pnl.params = customization + + local mpWrap = pnl:Add 'DPanel' + mpWrap:Dock(LEFT) + mpWrap:DockMargin(5, 0, 0, 0) + mpWrap:SetWide(225) + pnl.mpWrap = mpWrap + + local modelPreview = mpWrap:Add('DAdjustableModelPanel') + modelPreview:Dock(FILL) + modelPreview:SetFont('octolib.normal') + modelPreview:SetText('Выбери модель слева') + modelPreview:SetMouseInputEnabled(false) + modelPreview.FirstPersonControls = fpcFixed + modelPreview.oThink = modelPreview.Think + modelPreview.Think = thinkFixed + modelPreview.oMR = modelPreview.OnMouseReleased + modelPreview.OnMouseReleased = mouseReleasedFixed + modelPreview.OnMouseWheeled = octolib.func.zero + modelPreview.LayoutEntity = octolib.func.zero + modelPreview:SetCamPos(Vector(100,0,35.5)) + modelPreview:SetLookAng(Angle(0,180,0)) + modelPreview:SetFirstPerson(true) + modelPreview:SetFOV(25) + modelPreview.vRawCamPos = Vector(0,0,5.5) + modelPreview.tgtLookAngle = Angle(0,180,0) + pnl.modelPreview = modelPreview + + local submitBtn = octolib.button(mpWrap, 'Выбрать', function() + local ent = modelPreview.Entity + + local bgs = {} + for _, bg in pairs(ent:GetBodyGroups()) do + bgs[bg.id] = ent:GetBodygroup(bg.id) + end + + callback(pnl.index, ent:GetSkin(), bgs, pnl.mats) + pnl:Close() + end) + submitBtn:Dock(BOTTOM) + submitBtn:SetTall(25) + submitBtn:SetVisible(false) + pnl.submitBtn = submitBtn + + function pnl:RecalculateWidth(add) + add = add or 0 + + local totalWidth = sidePadding(self) + safeWidth(mpWrap) + safeWidth(layoutPan) + add + local d = self:GetWide() - totalWidth + self:SetWide(totalWidth) + + local x, y = self:GetPos() + x = x + (d / 2) + self:SetPos(x, y) + end + pnl:RecalculateWidth() + + function customization:ChangeModel(index, data) + self:GetCanvas():Clear() + self.skinCustomizer, self.bgCustomizers, self.matCustomizers = nil, {}, {} + modelPreview:SetModel(data.model) + modelPreview:SetMouseInputEnabled(true) + if IsValid(submitBtn) then submitBtn:SetVisible(true) end + + local ent = modelPreview.Entity + ent:SetPos(-ent:OBBCenter() + (data.previewOffset or Vector())) + modelPreview.vRawCamPos = Vector(100, 0, 33) + modelPreview.tgtLookAngle = modelPreview.aLookAngle + modelPreview:SetText('') + + modelPreview.viewOffset = data.previewOffset or Vector() + + pnl.index = index + pnl.mats = {} + + local paramsWidth = 0 + if data.requiredSkin then + ent:SetSkin(data.requiredSkin) + else + paramsWidth = populateSkin(self, data.skin, function(val) + modelPreview.Entity:SetSkin(val) + end) + end + if data.requiredBgs then + for k, v in pairs(data.requiredBgs) do + ent:SetBodygroup(k, v) + end + end + if data.requiredMats then + for k, v in pairs(data.requiredMats) do + pnl.mats[k] = v + ent:SetSubMaterial(k, v) + end + end + paramsWidth = math.max(paramsWidth, populateOptions(self, 'bg', data.bgs, function(bgID, bgVal) + modelPreview.Entity:SetBodygroup(bgID, bgVal) + end)) + + paramsWidth = math.max(paramsWidth, populateOptions(self, 'mat', data.subMaterials, function(matID, matVal) + modelPreview.Entity:SetSubMaterial(matID, matVal) + pnl.mats[matID] = matVal + end)) + + self:SetVisible(paramsWidth > 0) + self:SetWide(paramsWidth) + self:InvalidateLayout(true) + if self.VBar:IsEnabled() then + self:SetWide(paramsWidth + 15) + self:InvalidateLayout(true) + end + + pnl:RecalculateWidth(safeWidth(self)) + + end + + return pnl + +end + +netstream.Hook('octolib.modelSelector', function(mdls) + octolib.models.selector(mdls, function(index, skin, bgs, mats) + netstream.Start('octolib.modelSelector', index, skin, bgs, mats) + end) +end) diff --git a/octolib/addon/lua/octolib/modules/models/server.lua b/octolib/addon/lua/octolib/modules/models/server.lua new file mode 100644 index 0000000..298e919 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/models/server.lua @@ -0,0 +1,105 @@ +function octolib.models.getValidSelection(mdl, skin, userBgs, userMats) + -- validate skin + skin = isnumber(skin) and skin or 0 + if not mdl.requiredSkin and mdl.skin and mdl.skin.vals then + local found = false + for _, v in ipairs(mdl.skin.vals) do + if v[2] == skin then + found = true + end + end + if not found then skin = 0 end + elseif mdl.requiredSkin then skin = mdl.requiredSkin + else skin = 0 end + + -- validate bodygroups + userBgs = istable(userBgs) and userBgs or {} + local bgs = {} + if mdl.bgs then + for bgID, bgVal in pairs(userBgs) do + local bgData = mdl.bgs[bgID] + if not bgData then continue end + + if bgData.vals then + for _, selection in ipairs(bgData.vals) do + if selection[2] == bgVal then + bgs[bgID] = bgVal + break + end + end + else + if bgVal == 0 or bgVal == 1 then + bgs[bgID] = bgVal + end + end + end + end + if mdl.requiredBgs then + for bgID, bgVal in pairs(mdl.requiredBgs) do + bgs[bgID] = bgVal + end + end + + -- validate materials + userMats = istable(userMats) and userMats or {} + local mats = {} + if mdl.subMaterials then + for matID, matVal in pairs(userMats) do + local matData = mdl.subMaterials[matID] + if not matData then continue end + + for _, selection in ipairs(matData.vals) do + if selection[2] == matVal then + mats[matID] = matVal + break + end + end + end + end + if mdl.requiredMats then + for matID, matVal in pairs(mdl.requiredMats) do + mats[matID] = matVal + end + end + + return skin, bgs, mats +end + +local Player = FindMetaTable('Player') + +local pending = {} +function Player:SelectModel(mdls, callback) + if not istable(mdls) then return end + + local id = self:SteamID() + netstream.Start(self, 'octolib.modelSelector', mdls) + + pending[id] = { self, mdls, callback } + timer.Create('octolib.modelSelectorTimeout' .. id, 600, 1, function() + local callback = pending[id][2] + if callback then callback(false) end + pending[id] = nil + end) +end + +netstream.Hook('octolib.modelSelector', function(ply, index, userSkin, userBgs, userMats) + local sid = ply:SteamID() + if not pending[sid] then return end + + timer.Remove('octolib.modelSelectorTimeout' .. sid) + local func = pending[sid][3] or octolib.func.zero + if not index then return func(ply, false) end + + local mdl = pending[sid][2][index] + if not istable(mdl) then return func(ply, false) end + + local skin, bgs, mats = octolib.models.getValidSelection(mdl, userSkin, userBgs, userMats) + func(ply, mdl, skin, bgs, mats) + pending[sid] = nil +end) + +hook.Add('PlayerDisconnected', 'octolib.modelSelector', function(ply) + local sid = ply:SteamID() + pending[sid] = nil + timer.Remove('octolib.modelSelectorTimeout' .. sid) +end) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/models/shared.lua b/octolib/addon/lua/octolib/modules/models/shared.lua new file mode 100644 index 0000000..e5e4f06 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/models/shared.lua @@ -0,0 +1,22 @@ +octolib.models = octolib.models or {} + +local additionalFemaleModels = octolib.array.toKeys({ + 'medic_01_f.mdl', + 'medic_02_f.mdl', + 'medic_03_f.mdl', + 'medic_04_f.mdl', + 'medic_05_f.mdl', + 'medic_06_f.mdl', +}) + +function octolib.models.isMale(mdl) + if additionalFemaleModels[mdl:gsub('.+%/', '')] then return false end + + return not mdl:find('female') +end + +local Entity = FindMetaTable 'Entity' + +function Entity:IsMale() + return octolib.models.isMale(self:GetModel()) +end \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/move/client.lua b/octolib/addon/lua/octolib/modules/move/client.lua new file mode 100644 index 0000000..d728aeb --- /dev/null +++ b/octolib/addon/lua/octolib/modules/move/client.lua @@ -0,0 +1,30 @@ +if CFG.disabledModules.move then return end + +local cmds = {'duck', 'attack2'} + +local last, is, cvar = {}, {}, {} +hook.Add('Think', 'octolib.move', function() + hook.Remove('Think', 'octolib.move') + timer.Simple(1, function() + for i, cmd in ipairs(cmds) do cvar[cmd] = CreateClientConVar('cl_octolib_sticky_' .. cmd, '1') end + end) +end) + +hook.Add('PlayerBindPress', 'octolib.move', function(ply, bind, pressed) + + for i, cmd in ipairs(cmds) do + if bind == '+' .. cmd and cvar[cmd] and cvar[cmd]:GetBool() then + if is[cmd] then + RunConsoleCommand('-' .. cmd) + is[cmd] = false + else + if CurTime() - (last[cmd] or 0) < 0.3 then + RunConsoleCommand('+' .. cmd) + is[cmd] = true + end + last[cmd] = CurTime() + end + end + end + +end) diff --git a/octolib/addon/lua/octolib/modules/move/server.lua b/octolib/addon/lua/octolib/modules/move/server.lua new file mode 100644 index 0000000..da1a4e5 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/move/server.lua @@ -0,0 +1,96 @@ +if CFG.disabledModules.move then return end + +local defaultWalk, defaultRun, defaultLadder = 80, 185, 200 +local defaultJump = 200 +local meta = FindMetaTable 'Player' + +function meta:UpdateSpeed() + + local walk, run, ladder = self.moveWalkSpeed or defaultWalk, self.moveRunSpeed or defaultRun, self.moveLadderSpeed or defaultLadder + local jump = self.moveJumpPower or defaultJump + local norun, nojump, nostand = false, false + + self.moveMods = self.moveMods or {} + for modID, mod in pairs(self.moveMods) do + if mod.walkadd then walk = walk + mod.walkadd end + if mod.walkmul then walk = walk * mod.walkmul end + if mod.runadd then run = run + mod.runadd end + if mod.runmul then run = run * mod.runmul end + if mod.ladderadd then ladder = ladder + mod.ladderadd end + if mod.laddermul then ladder = ladder * mod.laddermul end + if mod.jumpadd then jump = jump + mod.jumpadd end + if mod.jumpmul then jump = jump * mod.jumpmul end + if mod.norun then norun = true end + if mod.nojump then nojump = true end + if mod.nostand then nostand = true end + end + + self:SetWalkSpeed(walk) + self:SetRunSpeed(norun and walk or run) + self:SetLadderClimbSpeed(ladder) + self:SetJumpPower(nojump and 0 or jump) + self:SetCanWalk(false) + self:SetLocalVar('nostand', nostand or nil) + self:SetLocalVar('norun', norun or nil) + +end + +function meta:MoveModifier(name, val) + + if not IsValid(self) then return end + + self.moveMods = self.moveMods or {} + self.moveMods[name] = val + self:UpdateSpeed() + +end + +function meta:GetMoveModifier(name) + + return self.moveMods and self.moveMods[name] or nil + +end + +function meta:SetBaseWalkSpeed(val) + + self.moveWalkSpeed = val + self:UpdateSpeed() + +end + +function meta:SetBaseRunSpeed(val) + + self.moveRunSpeed = val + self:UpdateSpeed() + +end + +function meta:SetBaseJumpPower(val) + + self.moveJumpPower = val + self:UpdateSpeed() + +end + +function meta:SetBaseLadderClimbSpeed(val) + + self.moveLadderSpeed = val + self:UpdateSpeed() + +end + +hook.Add('PlayerSpawn', 'octolib.move', function(ply) + + timer.Simple(0.5, function() + if not IsValid(ply) then return end + ply:UpdateSpeed() + end) + +end) + +-- quickie to fix fast crouching bug +hook.Add('CanPlayerEnterVehicle', 'octolib.move', function(ply, veh, role) + + if ply:Crouching() then return false end + +end) diff --git a/octolib/addon/lua/octolib/modules/move/shared.lua b/octolib/addon/lua/octolib/modules/move/shared.lua new file mode 100644 index 0000000..da3e339 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/move/shared.lua @@ -0,0 +1,27 @@ +if CFG.disabledModules.move then return end + +hook.Add('StartCommand', 'octolib.move', function(ply, cmd) + + local fwd = cmd:GetForwardMove() + if cmd:KeyDown(IN_SPEED) then + local side = cmd:GetSideMove() + if (fwd < 0 or (fwd == 0 and side ~= 0)) and ply:GetMoveType() ~= MOVETYPE_NOCLIP then + cmd:RemoveKey(IN_SPEED) + cmd:RemoveKey(IN_WALK) + end + end + + if cmd:KeyDown(IN_JUMP) and ply:GetJumpPower() == 0 and not ply:InVehicle() then + cmd:RemoveKey(IN_JUMP) + end + + if not cmd:KeyDown(IN_DUCK) and ply:GetNetVar('nostand') and not ply:InVehicle() then + cmd:SetButtons(cmd:GetButtons() + IN_DUCK) + end + + if ply:GetNetVar('norun') then + cmd:RemoveKey(IN_SPEED) + cmd:RemoveKey(IN_WALK) + end + +end) diff --git a/octolib/addon/lua/octolib/modules/net/client.lua b/octolib/addon/lua/octolib/modules/net/client.lua new file mode 100644 index 0000000..4cac8de --- /dev/null +++ b/octolib/addon/lua/octolib/modules/net/client.lua @@ -0,0 +1,86 @@ +if (netvars) then return end + +netvars = netvars or {} + +local entityMeta = FindMetaTable('Entity') +local playerMeta = FindMetaTable('Player') + +local stored = {} +local globals = {} + +netstream.Hook('nVar', function(index, key, value) + stored[index] = stored[index] or {} + stored[index][key] = value + hook.Run('octolib.netVarUpdate', index, key, value) +end) + +netstream.Hook('nDel', function(index) + if stored[index] then + for k in pairs(stored[index]) do + hook.Run('octolib.netVarUpdate', index, k) + end + end + stored[index] = nil +end) + +netstream.Hook('nLcl', function(key, value) + local index = LocalPlayer():EntIndex() + stored[index] = stored[index] or {} + stored[index][key] = value + hook.Run('octolib.netVarUpdate', index, key, value) +end) + +netstream.Hook('gVar', function(key, value) + globals[key] = value + hook.Run('octolib.netVarUpdate', nil, key, value) +end) + +netstream.Hook('netvars_full', function(theirStored, theirGlobals) + + for index, data in pairs(theirStored) do + for key, value in pairs(data) do + stored[index] = stored[index] or {} + stored[index][key] = value + hook.Run('octolib.netVarUpdate', index, key, value) + end + end + + for key, value in pairs(theirGlobals) do + globals[key] = value + hook.Run('octolib.netVarUpdate', nil, key, value) + end + +end) + +netstream.Hook('nUpd', function(keys, updStored, updGlobals) + for _, key in ipairs(keys) do + for index, data in pairs(updStored) do + stored[index] = stored[index] or {} + stored[index][key] = updStored[index][key] + end + globals[key] = updGlobals[key] + end +end) + +function netvars.GetNetVar(key, default) + local value = globals[key] + if value ~= nil then return value end + + return default +end + +function entityMeta:GetNetVar(key, default) + local index = self:EntIndex() + + local tbl = stored[index] + if tbl and tbl[key] ~= nil then return tbl[key] end + + return default +end + +function entityMeta:SetNetVar(key, default) + -- wut? some addons use it for some reason +end + +playerMeta.GetNetVar = entityMeta.GetNetVar +playerMeta.GetLocalVar = entityMeta.GetNetVar diff --git a/octolib/addon/lua/octolib/modules/net/server.lua b/octolib/addon/lua/octolib/modules/net/server.lua new file mode 100644 index 0000000..d247680 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/net/server.lua @@ -0,0 +1,156 @@ +-- This code is taken from NutScript. +-- NutScript and code license are found here: +-- https://github.com/Chessnut/NutScript + +netvars = netvars or {} + +local entityMeta = FindMetaTable('Entity') +local playerMeta = FindMetaTable('Player') + +local stored = netvars.stored or {} +local locals = netvars.locals or {} +local globals = netvars.globals or {} +local registered = netvars.registered or {} + +netvars.stored = stored +netvars.locals = locals +netvars.globals = globals +netvars.registered = registered + +function netvars.Register(key, data) + if data == nil or istable(data) then + registered[key] = data + end +end + +function netvars.GetReceivers(key, receivers) + + if not (registered[key] and isfunction(registered[key].checkAccess)) then + return receivers + elseif not receivers then + receivers = player.GetAll() + elseif not istable(receivers) then + receivers = { receivers } + end + + return octolib.array.filter(receivers, registered[key].checkAccess) +end + +function netvars.HasAccess(ply, key) + return not (registered[key] and isfunction(registered[key].checkAccess)) or registered[key].checkAccess(ply) +end + +function netvars.SetNetVar(key, value, receiver) + if isfunction(value) or not istable(value) and globals[key] == value then return end + + globals[key] = value + netstream.Start(netvars.GetReceivers(key, receiver), 'gVar', key, value) +end + +function playerMeta:UpdateNetVars() + local storedToSend, globalsToSend = {}, {} + for k, v in pairs(registered) do + if not v.checkAccess then continue end + local hasAccess = v.checkAccess(self) + if globals[k] ~= nil then + if hasAccess then + globalsToSend[k] = globals[k] + end + end + for index, vars in pairs(stored) do + if vars[k] ~= nil then + storedToSend[index] = storedToSend[index] or {} + if hasAccess then + storedToSend[index][k] = vars[k] + end + end + end + end + netstream.Heavy(self, 'nUpd', table.GetKeys(registered), storedToSend, globalsToSend) +end + +function entityMeta:SendNetVar(key, receiver) + local index = self:EntIndex() + netstream.Start(netvars.GetReceivers(key, receiver), 'nVar', index, key, stored[index] and stored[index][key]) +end + +function entityMeta:ClearNetVars(receiver) + local index = self:EntIndex() + stored[index] = nil + locals[index] = nil + netstream.Start(receiver, 'nDel', index) +end + +function entityMeta:SetNetVar(key, value, receiver) + if isfunction(value) or not istable(value) and value == self:GetNetVar(key) then return end + + local index = self:EntIndex() + stored[index] = stored[index] or {} + stored[index][key] = value + + self:SendNetVar(key, receiver) +end + +function entityMeta:GetNetVar(key, default) + local index = self:EntIndex() + + local ourLocal = locals[index] + local ourStored = stored[index] + local value = ourLocal and ourLocal[key] or ourStored and ourStored[key] + if value ~= nil then return value end + + return default +end + +function playerMeta:SetLocalVar(key, value) + if isfunction(value) or not istable(value) and value == self:GetNetVar(key) then return end + + local index = self:EntIndex() + locals[index] = locals[index] or {} + locals[index][key] = value + + netstream.Start(self, 'nLcl', key, value) +end + +playerMeta.GetLocalVar = entityMeta.GetNetVar + +function netvars.GetNetVar(key, default) + local value = globals[key] + if value ~= nil then return value end + + return default +end + +hook.Add('EntityRemoved', 'nCleanUp', function(entity) + entity:ClearNetVars() +end, 10) -- just in case addons use vars in this hook + +hook.Add('PlayerFinishedLoading', 'nSync', function(ply) + timer.Simple(10, function() -- sometimes it misses some info + if not IsValid(ply) then return end + + local storedToSend = {} -- filter stored + for index, vars in pairs(stored) do + storedToSend[index] = {} + local empty = true + for key, value in pairs(vars) do + if netvars.HasAccess(ply, key) then + storedToSend[index][key], empty = value, false + end + end + if empty then storedToSend[index] = nil end + end + + local globalsToSend = {} -- filter globals + for key, value in pairs(globals) do + if netvars.HasAccess(ply, key) then + globalsToSend[key] = value + end + end + + netstream.Heavy(ply, 'netvars_full', storedToSend, globalsToSend) + + end) +end) + +hook.Add('octolib.updateNetVars', 'nUpd', playerMeta.UpdateNetVars) diff --git a/octolib/addon/lua/octolib/modules/net/shared.lua b/octolib/addon/lua/octolib/modules/net/shared.lua new file mode 100644 index 0000000..743c7b4 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/net/shared.lua @@ -0,0 +1,36 @@ +-- hook.Add("Think", "InitMyOverride", function() +-- hook.Remove("Think", "InitMyOverride") + +-- local PLAYER, ENTITY = FindMetaTable 'Player', FindMetaTable 'Entity' +-- local GetTable = ENTITY.GetTable +-- local GetOwner = ENTITY.GetOwner + +-- function PLAYER:__index(key) +-- return PLAYER[key] or ENTITY[key] or GetTable(self)[key] +-- end + +-- function ENTITY:__index(key) +-- if not key then return end + +-- local res = (key == "Owner" and GetOwner(self)) or ENTITY[key] +-- if res ~= nil then return res end + +-- local t = GetTable(self) +-- if t then return t[key] end +-- end + +-- local cachedValid = {} + +-- function IsValid(obj) +-- if not obj or cachedValid[obj] ~= nil then return obj and cachedValid[obj] end +-- local isvalid = obj.IsValid +-- cachedValid[obj] = isvalid and isvalid(obj) or false + +-- return cachedValid[obj] +-- end + +-- hook.Add('Think', 'flushIsValid', function() +-- table.Empty(cachedValid) +-- end) + +-- end) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/netstream/client.lua b/octolib/addon/lua/octolib/modules/netstream/client.lua new file mode 100644 index 0000000..cf92b0d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/netstream/client.lua @@ -0,0 +1,120 @@ +local stored = netstream.stored +local cache = netstream.cache + +function netstream.Start(name, ...) + local encodedData = pon.encode({...}) + + if (encodedData and #encodedData > 0) then + net.Start('NetStreamDS') + net.WriteString(name) + net.WriteUInt(#encodedData, 32) + net.WriteData(encodedData, #encodedData) + net.SendToServer() + end +end + +function netstream.Heavy(name, ...) + local dataTable = {...} + local encodedData = pon.encode(dataTable) + local split = netstream.Split(encodedData) + + if (encodedData and #encodedData > 0) then + for k, v in ipairs(split) do + net.Start('NetStreamHeavy') + net.WriteString(name) + net.WriteUInt(#v, 32) + net.WriteData(v, #v) + net.WriteUInt(k, 8) + net.WriteUInt(#split, 8) + net.SendToServer() + end + end +end + +function netstream.Request(name, ...) + local args = {...} + return util.Promise(function(res, rej) + local reqID = netstream.nextReqID + netstream.nextReqID = netstream.nextReqID + 1 + + local msgName = 'nsr-' .. reqID + netstream.Hook(msgName, function(data) + netstream.Hook(msgName, nil) + res(data) + end) + netstream.Start('nsr', name, reqID, unpack(args)) + + timer.Create(msgName, netstream.requestTimeout, 1, function() + rej('timeout') + netstream.Hook(msgName, nil) + timer.Remove(msgName) + end) + end) +end + +net.Receive('NetStreamDS', function(_) + 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 and stored[NS_DS_NAME] then + local bStatus, value = pcall(pon.decode, NS_DS_DATA) + + if (bStatus) then + stored[NS_DS_NAME](unpack(value)) + else + ErrorNoHalt('NetStream: ' .. NS_DS_NAME .. '\n' .. value .. '\n') + end + end + + NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil +end) + +net.Receive('NetStreamHeavy', function(_) + local NS_DS_NAME = net.ReadString() + local NS_DS_LENGTH = net.ReadUInt(32) + local NS_DS_DATA = net.ReadData(NS_DS_LENGTH) + local NS_DS_PIECE = net.ReadUInt(8) + local NS_DS_TOTAL = net.ReadUInt(8) + + if not cache[NS_DS_NAME] then + cache[NS_DS_NAME] = '' + end + + if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then + if (NS_DS_PIECE < NS_DS_TOTAL) then + if (NS_DS_PIECE == 1) then + cache[NS_DS_NAME] = '' + end + + cache[NS_DS_NAME] = cache[NS_DS_NAME] .. NS_DS_DATA + else + cache[NS_DS_NAME] = cache[NS_DS_NAME] .. NS_DS_DATA + + if (stored[NS_DS_NAME]) then + local bStatus, value = pcall(pon.decode, cache[NS_DS_NAME]) + + if (bStatus) then + stored[NS_DS_NAME](unpack(value)) + else + ErrorNoHalt('NetStream Heavy: ' .. NS_DS_NAME .. '\n' .. value .. '\n') + end + + cache[NS_DS_NAME] = nil + end + end + end + + NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH, NS_DS_PIECE, NS_DS_TOTAL = nil, nil, nil, nil, nil +end) + +netstream.Hook('nsr', function(name, reqID, ...) + local callback = netstream.requestCallbacks[name] + if not callback then return end + + local reply = function(...) + netstream.Start('nsr-' .. reqID, ...) + end + + callback(reply, ...) +end) diff --git a/octolib/addon/lua/octolib/modules/netstream/server.lua b/octolib/addon/lua/octolib/modules/netstream/server.lua new file mode 100644 index 0000000..08773c8 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/netstream/server.lua @@ -0,0 +1,186 @@ +util.AddNetworkString 'NetStreamDS' +util.AddNetworkString 'NetStreamHeavy' + +local stored = netstream.stored +local cache = netstream.cache + +function netstream.Start(ply, name, ...) + local recipients = {} + local bShouldSend = false + + if (type(ply) ~= 'table') then + if not ply then + ply = player.GetAll() + else + ply = {ply} + end + end + + for _, v in ipairs(ply) do + if (type(v) == 'Player') then + recipients[#recipients + 1] = v + + bShouldSend = true + end + end + + local encodedData = pon.encode({...}) + + if (encodedData and #encodedData > 0 and bShouldSend) then + net.Start('NetStreamDS') + net.WriteString(name) + net.WriteUInt(#encodedData, 32) + net.WriteData(encodedData, #encodedData) + net.Send(recipients) + end +end + +function netstream.StartPVS(pos, name, ...) + local rf = RecipientFilter() + rf:AddPVS(pos) + netstream.Start(rf:GetPlayers(), name, ...) +end + +function netstream.Heavy(ply, name, ...) + local recipients = {} + local bShouldSend = false + + if (type(ply) != 'table') then + if (!ply) then + ply = player.GetAll() + else + ply = {ply} + end + end + + for k, v in ipairs(ply) do + if (type(v) == 'Player') then + recipients[#recipients + 1] = v + + bShouldSend = true + end + end + + local encodedData = pon.encode({...}) + local split = netstream.Split(encodedData) + + if (encodedData and #encodedData > 0 and bShouldSend) then + for k, v in ipairs(split) do + net.Start('NetStreamHeavy') + net.WriteString(name) + net.WriteUInt(#v, 32) + net.WriteData(v, #v) + net.WriteUInt(k, 8) + net.WriteUInt(#split, 8) + net.Send(recipients) + end + end +end + +function netstream.Request(ply, name, ...) + local args = {...} + return util.Promise(function(res, rej) + local reqID = netstream.nextReqID + netstream.nextReqID = netstream.nextReqID + 1 + + local msgName = 'nsr-' .. reqID + netstream.Hook(msgName, function(_, data) + netstream.Hook(msgName, nil) + res(data) + end) + netstream.Start(ply, 'nsr', name, reqID, unpack(args)) + + timer.Create(msgName, netstream.requestTimeout, 1, function() + rej('timeout') + timer.Remove(msgName) + end) + end) +end + +net.Receive('NetStreamDS', function(_, ply) + 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 + ply.nsDataStreamName = NS_DS_NAME + ply.nsDataStreamData = '' + + if (ply.nsDataStreamName and ply.nsDataStreamData) then + ply.nsDataStreamData = NS_DS_DATA + + if (stored[ply.nsDataStreamName]) then + local bStatus, value = pcall(pon.decode, ply.nsDataStreamData) + + if (bStatus) then + stored[ply.nsDataStreamName](ply, unpack(value)) + else + ErrorNoHalt('NetStream: ' .. NS_DS_NAME .. '\n' .. value .. '\n') + end + end + + ply.nsDataStreamName = nil + ply.nsDataStreamData = nil + end + end + + NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH = nil, nil, nil +end) + +net.Receive('NetStreamHeavy', function(_, ply) + local NS_DS_NAME = net.ReadString() + local NS_DS_LENGTH = net.ReadUInt(32) + local NS_DS_DATA = net.ReadData(NS_DS_LENGTH) + local NS_DS_PIECE = net.ReadUInt(8) + local NS_DS_TOTAL = net.ReadUInt(8) + + if (NS_DS_NAME and NS_DS_DATA and NS_DS_LENGTH) then + ply.nsDataStreamName = NS_DS_NAME + ply.nsDataStreamData = '' + + if not cache[ply.nsDataStreamName] then + cache[ply.nsDataStreamName] = '' + end + + if (ply.nsDataStreamName and ply.nsDataStreamData) then + ply.nsDataStreamData = NS_DS_DATA + + if (NS_DS_PIECE < NS_DS_TOTAL) then + if (NS_DS_PIECE == 1) then + cache[ply.nsDataStreamName] = '' + end + + cache[ply.nsDataStreamName] = cache[ply.nsDataStreamName] .. ply.nsDataStreamData + else + cache[ply.nsDataStreamName] = cache[ply.nsDataStreamName] .. ply.nsDataStreamData + + if (stored[ply.nsDataStreamName]) then + local bStatus, value = pcall(pon.decode, cache[ply.nsDataStreamName]) + + if (bStatus) then + stored[ply.nsDataStreamName](ply, unpack(value)) + else + ErrorNoHalt('NetStream: ' .. NS_DS_NAME .. '\n' .. value .. '\n') + end + end + + cache[ply.nsDataStreamName] = nil + ply.nsDataStreamName = nil + ply.nsDataStreamData = nil + end + end + end + + NS_DS_NAME, NS_DS_DATA, NS_DS_LENGTH, NS_DS_PIECE, NS_DS_TOTAL = nil, nil, nil, nil, nil +end) + +netstream.Hook('nsr', function(ply, name, reqID, ...) + local callback = netstream.requestCallbacks[name] + if not callback then return end + + local reply = function(...) + netstream.Start(ply, 'nsr-' .. reqID, ...) + end + + callback(reply, ply, ...) +end) diff --git a/octolib/addon/lua/octolib/modules/netstream/shared.lua b/octolib/addon/lua/octolib/modules/netstream/shared.lua new file mode 100644 index 0000000..e6c616e --- /dev/null +++ b/octolib/addon/lua/octolib/modules/netstream/shared.lua @@ -0,0 +1,35 @@ +netstream = netstream or {} +netstream.stored = netstream.stored or {} +netstream.cache = netstream.cache or {} + +netstream.requestTimeout = 15 +netstream.requestCallbacks = netstream.requestCallbacks or {} +netstream.nextReqID = netstream.nextReqID or 0 + +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 + +function netstream.Hook(name, Callback) + netstream.stored[name] = Callback +end + +function netstream.Listen(name, callback) + netstream.requestCallbacks[name] = callback +end diff --git a/octolib/addon/lua/octolib/modules/notify/client.lua b/octolib/addon/lua/octolib/modules/notify/client.lua new file mode 100644 index 0000000..ba4fefb --- /dev/null +++ b/octolib/addon/lua/octolib/modules/notify/client.lua @@ -0,0 +1,36 @@ +octolib.notify.types = octolib.notify.types or { + _generic = octolib.func.zero, +} + +octolib.notify.cache = octolib.notify.cache or {} + +function octolib.notify.registerType(type, handler) + if type == '_generic' and not handler then + handler = octolib.func.zero + end + octolib.notify.types[type] = handler +end + +function octolib.notify.show(type, ...) + local data = {...} + if not data[1] then + data = {type} + type = '_generic' + end + type = type or '_generic' + + if octolib.notify.types[type] then octolib.notify.types[type](unpack(data)) + else octolib.notify.types._generic(unpack(data)) end +end + +netstream.Hook('octolib.notify', octolib.notify.show) + +netstream.Hook('octolib-notifs.add', function(notif) + octolib.notify.cache[#octolib.notify.cache + 1] = notif + hook.Run('octolib.notify.cacheUpdate', octolib.notify.cache) +end) + +netstream.Hook('octolib-notifs.sync', function(notifs) + octolib.notify.cache = notifs + hook.Run('octolib.notify.cacheUpdate', octolib.notify.cache) +end) diff --git a/octolib/addon/lua/octolib/modules/notify/server.lua b/octolib/addon/lua/octolib/modules/notify/server.lua new file mode 100644 index 0000000..8d49889 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/notify/server.lua @@ -0,0 +1,144 @@ +octolib.notify.actions = octolib.notify.actions or {} + +function octolib.notify.registerActions(id, handlers) + octolib.notify.actions[id] = handlers +end + +local function getActions(t, ply, data) + + local out = {} + for i, handler in ipairs(octolib.notify.actions[t]) do + local name, action = handler(ply, data) + if not name then continue end + + out[i] = { name, action } + end + + return out + +end + +local function getClientData(notif) + + local t, date, data = unpack(notif) + return { + data.text or '', date or 0, + octolib.table.map(getActions(t, ply, data), function(v) return v[1] end) -- {[actionID] = actionName} + } + +end + +local offlineNotifyQueue = octolib.queue.create(function(data, done) + local sid, notif = unpack(data) + octolib.getDBVar(sid, 'notifs') + :Then(function(notifs) + notifs = notifs or {} + notifs[#notifs + 1] = notif + octolib.setDBVar(sid, 'notifs', notifs) + :Finally(done) + end) + :Catch(function() + octolib.setDBVar(sid, 'notifs', { notif }) + :Finally(done) + end) +end) + +local function doNotify(ply, sid, type, args) + + if octolib.notify.actions[type] then -- notification with actions + local data = args[2] or {} + local notif = { type, os.time(), data } + if not data.text then data.text = args[1] end + + if IsValid(ply) then + ply:Notify(args[1]) + netstream.Start(ply, 'octolib-notifs.add', getClientData(notif)) + end + + offlineNotifyQueue:Add({ sid, notif }) + return + end + + if not isstring(ply) then -- fire'n'forget notification + if not args[1] then + -- first argument is message, use _generic type + args = { type } + type = nil + end + type = type or '_generic' + + netstream.Start(ply, 'octolib.notify', type, unpack(args)) + end + +end + +function octolib.notify.send(plyOrSid, type, ...) + + type = type or '_generic' + local args = {...} + if not plyOrSid then + return doNotify(nil, sid, type, args) + end + + local ply, sid = octolib.players.resolve(plyOrSid) + + if not IsValid(ply) then + octolib.getPlayerOnline(sid, function(serverID) + if serverID then + octolib.sendCmd(serverID, 'notifyPlayer', { sid, type, unpack(args) }) + else + doNotify(ply, sid, type, args) + end + end) + else + doNotify(ply, sid, type, args) + end + +end + +function octolib.notify.sendAll(type, ...) + octolib.notify.send(nil, type, ...) +end + +function octolib.notify.sync(ply) + + local toSend = octolib.table.mapSequential(ply:GetDBVar('notifs') or {}, function(notif) return getClientData(notif) end) + netstream.Start(ply, 'octolib-notifs.sync', toSend) + +end +hook.Add('PlayerFinishedLoading', 'octolib.notify', octolib.notify.sync) + +netstream.Listen('octolib-notify.read', function(reply, ply, id, actionID) + + local notifs = ply:GetDBVar('notifs') + local notif = notifs and notifs[id] + if not notif then return octolib.notify.sync(ply) end + + local t, date, data = unpack(notif) + + local remove = false + local actions = getActions(t, ply, data) + if #actions < 1 then + remove = true + else + local name, action = unpack(actions[actionID]) + if not name then return octolib.notify.sync(ply) end + + remove = action(ply, data) + end + + if remove then + table.remove(notifs, id) + end + + ply:SetDBVar('notifs', notifs) + reply(remove or false) + +end) + +hook.Add('octolib.event:notifyPlayer', 'octolib.notify', function(data) + octolib.notify.send(unpack(data)) +end) + +local ply = FindMetaTable 'Player' +ply.Notify = octolib.notify.send diff --git a/octolib/addon/lua/octolib/modules/notify/shared.lua b/octolib/addon/lua/octolib/modules/notify/shared.lua new file mode 100644 index 0000000..a6b66b3 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/notify/shared.lua @@ -0,0 +1 @@ +octolib.notify = octolib.notify or {} \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/path.lua b/octolib/addon/lua/octolib/modules/path.lua new file mode 100644 index 0000000..9085343 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/path.lua @@ -0,0 +1,61 @@ +octolib.path = octolib.path or {} + +function octolib.path.current(level) + assert(isnumber(level) or level == nil, 'level must be a number or nil') + + local path = debug.getinfo(2 + (level or 0), 'S').short_src:Split('/') + + return '/' .. table.concat({ unpack(path, 4, #path - 1) }, '/') +end + +function octolib.path.resolve(path, level) + assert(isstring(path), 'path must be a string') + assert(isnumber(level) or level == nil, 'level must be a number or nil') + + path = octolib.path.normalize(path) + + if string.StartWith(path, '/') then + return path + end + + return octolib.path.join(octolib.path.current(1 + (level or 0)), path) +end + +function octolib.path.normalize(path) + assert(isstring(path), 'path must be a string') + + if path == '.' then + path = '' + elseif string.StartWith(path, './') then + path = string.sub(path, 3) + end + + local result = {} + + for element in string.gmatch(path, '[^/\\]+') do + if element == '..' and #result ~= 0 then + table.remove(result) + elseif element ~= '' then + table.insert(result, element) + end + end + + return (string.StartWith(path, '/') and '/' or '') .. table.concat(result, '/') +end + +function octolib.path.trim(path) + assert(isstring(path), 'path must be a string') + + return string.Trim(path, '/') +end + +function octolib.path.join(...) + local elements = { ... } + local result = '' + + for _, element in ipairs(elements) do + result = result .. octolib.path.normalize(element) .. '/' + end + + return octolib.path.normalize(result) +end diff --git a/octolib/addon/lua/octolib/modules/perlin.lua b/octolib/addon/lua/octolib/modules/perlin.lua new file mode 100644 index 0000000..449bc4b --- /dev/null +++ b/octolib/addon/lua/octolib/modules/perlin.lua @@ -0,0 +1,114 @@ +-- forked from https://gist.github.com/kymckay/25758d37f8e3872e1636d90ad41fe2ed + +local perlin = {} +perlin.p = {} + +local permutation = {151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 +} + +for i=0,255 do + perlin.p[i] = permutation[i+1] + perlin.p[i+256] = permutation[i+1] +end + +function perlin:noise(x, y, z) + y = y or 0 + z = z or 0 + + local xi = bit.band(math.floor(x),255) + local yi = bit.band(math.floor(y),255) + local zi = bit.band(math.floor(z),255) + + x = x - math.floor(x) + y = y - math.floor(y) + z = z - math.floor(z) + + local u = self.fade(x) + local v = self.fade(y) + local w = self.fade(z) + + local p = self.p + local A, AA, AB, AAA, ABA, AAB, ABB, B, BA, BB, BAA, BBA, BAB, BBB + A = p[xi ] + yi + AA = p[A ] + zi + AB = p[A+1 ] + zi + AAA = p[ AA ] + ABA = p[ AB ] + AAB = p[ AA+1 ] + ABB = p[ AB+1 ] + + B = p[xi+1] + yi + BA = p[B ] + zi + BB = p[B+1 ] + zi + BAA = p[ BA ] + BBA = p[ BB ] + BAB = p[ BA+1 ] + BBB = p[ BB+1 ] + + return self.lerp(w, + self.lerp(v, + self.lerp(u, + self:grad(AAA,x,y,z), + self:grad(BAA,x-1,y,z) + ), + self.lerp(u, + self:grad(ABA,x,y-1,z), + self:grad(BBA,x-1,y-1,z) + ) + ), + self.lerp(v, + self.lerp(u, + self:grad(AAB,x,y,z-1), self:grad(BAB,x-1,y,z-1) + ), + self.lerp(u, + self:grad(ABB,x,y-1,z-1), self:grad(BBB,x-1,y-1,z-1) + ) + ) + ) +end + +perlin.dot_product = { + [0x0] = function(x,y,z) return x + y end, + [0x1] = function(x,y,z) return -x + y end, + [0x2] = function(x,y,z) return x - y end, + [0x3] = function(x,y,z) return -x - y end, + [0x4] = function(x,y,z) return x + z end, + [0x5] = function(x,y,z) return -x + z end, + [0x6] = function(x,y,z) return x - z end, + [0x7] = function(x,y,z) return -x - z end, + [0x8] = function(x,y,z) return y + z end, + [0x9] = function(x,y,z) return -y + z end, + [0xA] = function(x,y,z) return y - z end, + [0xB] = function(x,y,z) return -y - z end, + [0xC] = function(x,y,z) return y + x end, + [0xD] = function(x,y,z) return -y + z end, + [0xE] = function(x,y,z) return y - x end, + [0xF] = function(x,y,z) return -y - z end +} +function perlin:grad(hash, x, y, z) + return self.dot_product[bit.band(hash,0xF)](x,y,z) +end + +function perlin.fade(t) + return t * t * t * (t * (t * 6 - 15) + 10) +end + +function perlin.lerp(t, a, b) + return a + t * (b - a) +end + +function octolib.perlin(x, y, z) + return perlin:noise(x, y, z) +end diff --git a/octolib/addon/lua/octolib/modules/players/client.lua b/octolib/addon/lua/octolib/modules/players/client.lua new file mode 100644 index 0000000..5e9e748 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/players/client.lua @@ -0,0 +1,34 @@ +timer.Create('octolib.playerTick', CFG.playerTickTime, 0, function() + octolib.func.throttle(player.GetAll(), 10, 0.1, function(ply) + if not IsValid(ply) then return end + ply.eyeDist = EyePos():DistToSqr(ply:GetPos()) + + if ply.prevDormant ~= ply:IsDormant() then + hook.Run('octolib.newDormantState', ply, ply:IsDormant()) + ply.prevDormant = ply:IsDormant() + end + end) + +end) + +local tellServerCache = {} +hook.Add('octolib.newDormantState', 'octolib.playerTick', function(ply, st) + if not st and ply:IsPlayer() then + tellServerCache[ply] = true + end +end) + +timer.Create('octolib.newDormantState', 3, 0, function() + if table.Count(tellServerCache) < 1 then return end + + netstream.Start('PlayersEnteredPVS', table.GetKeys(tellServerCache)) + tellServerCache = {} +end) + +local Player = FindMetaTable('Player') +local Entity = FindMetaTable('Entity') + +-- see serverside for explanation +function Player:GetModel() + return self.GetNetVar and self:GetNetVar('model') or Entity.GetModel(self) +end diff --git a/octolib/addon/lua/octolib/modules/players/server.lua b/octolib/addon/lua/octolib/modules/players/server.lua new file mode 100644 index 0000000..7cf4050 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/players/server.lua @@ -0,0 +1,111 @@ +octolib.players.count = octolib.players.count or 0 + +gameevent.Listen('player_connect') +gameevent.Listen('player_disconnect') + +local Player = FindMetaTable 'Player' + +function octolib.players.getCount() + return player.GetCount() +end + +function octolib.players.getAdmins() + return octolib.array.filter(player.GetAll(), Player.IsAdmin) +end + +hook.Add('player_connect', 'octolib.players', function(data) + octolib.players.count = octolib.players.count + 1 +end) + +hook.Add('player_disconnect', 'octolib.players', function(data) + octolib.players.count = octolib.players.count - 1 +end) + +hook.Add('CheckPassword', 'octolib.adminslots', function(sid, ip, sv, cl, name) + if game.MaxPlayers() - octolib.players.getCount() < CFG.adminSlots and + not CFG.isAdminSteamID(util.SteamIDFrom64(sid)) + then + return false, L.adminslots + end +end) + +hook.Add('onPlayerChangedName', 'octolib.rpNames', function(ply, old, new) + ply:SetDBVar('lastRPName', new) +end) + +local ipInfo = octolib.api({ url = 'http://ip-api.com/json' }) +hook.Add('PlayerInitialSpawn', 'octolib.ipInfo', function(ply) + if ply:IsBot() then return end + + ipInfo:get('/' .. ply:IPAddress():gsub(':.+', '')):Then(function(res) + hook.Run('octolib.ipInfo', ply, res.data) + end) +end) + +-- +-- BOTS +-- + +local botIDs = {} +for id = 1, 128 do botIDs[#botIDs + 1] = ('BOT%03d'):format(id) end + +for _, ply in ipairs(player.GetAll()) do + local botID = ply:GetNetVar('botID') + if botID then + table.RemoveByValue(botIDs, botID) + end +end + +hook.Add('PlayerInitialSpawn', 'octolib.players.id', function(ply) + if ply:IsBot() then + ply:SetNetVar('botID', table.remove(botIDs, 1)) + end +end, -1) + +hook.Add('PlayerDisconnected', 'octolib.players.id', function(ply) + local botID = ply:GetNetVar('botID') + if botID and not table.HasValue(botIDs, botID) then + botIDs[#botIDs + 1] = botID + table.sort(botIDs) + end +end) + +netstream.Hook('PlayersEnteredPVS', function(ply, tgts) + for _, tgt in ipairs(tgts) do + -- tgt is the player who entered ply's PVS + hook.Run('PlayerEnteredPVS', tgt, ply) + end +end) + +local Player = FindMetaTable 'Player' +local Entity = FindMetaTable 'Entity' + +function Player:SetModel(mdl) + return self.SetNetVar and self:SetNetVar('model', mdl) or Entity.SetModel(self, mdl) +end + +local activeCooldowns = {} + +function Player:GetCooldown(id) + local sID = self:SteamID() + + return activeCooldowns[sID] and activeCooldowns[sID][id] +end + +function Player:TriggerCooldown(id, time, force) + if not force and self:GetCooldown(id) then + return false + end + + local sID = self:SteamID() + activeCooldowns[sID] = activeCooldowns[sID] or {} + activeCooldowns[sID][id] = CurTime() + time + timer.Create(sID .. ':cooldown:' .. id, time, 1, function() + activeCooldowns[sID][id] = nil + if table.Count(activeCooldowns[sID]) < 1 then + activeCooldowns[sID] = nil + end + end) + + return true +end diff --git a/octolib/addon/lua/octolib/modules/players/shared.lua b/octolib/addon/lua/octolib/modules/players/shared.lua new file mode 100644 index 0000000..d9c966a --- /dev/null +++ b/octolib/addon/lua/octolib/modules/players/shared.lua @@ -0,0 +1,30 @@ +octolib.players = octolib.players or {} + +function octolib.players.resolve(value) + if isstring(value) then + return player.GetBySteamID(value), value + else + return value, value:SteamID() + end +end + +local Player = FindMetaTable('Player') + +function Player:GetEyeTraceLimited(length) + local aim = self:EyeAngles():Forward() + local t = {} + + t.start = self:GetShootPos() + t.endpos = t.start + aim * length + t.filter = { self } + + hook.Run('octolib.eyeTraceFilter', self, t.filter) + + return util.TraceLine(t) +end + +for _, name in ipairs({ 'AccountID', 'SteamID', 'SteamID64', 'UniqueID' }) do + Player[name] = octolib.func.detour(Player[name], 'Player:' .. name, function(original, player) + return player:GetNetVar('botID') or original(player) + end) +end diff --git a/octolib/addon/lua/octolib/modules/poly.lua b/octolib/addon/lua/octolib/modules/poly.lua new file mode 100644 index 0000000..38cb211 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/poly.lua @@ -0,0 +1,54 @@ +--[[ + Namespace: octolib + + Group: poly + Polygon helper functions +]] +octolib.poly = octolib.poly or {} + +--[[ + Function: polyRotate + Destructively rotates polygon structure around (0,0) point + + Arguments: +
poly - Polygon structure to rotate, will be changed in place + ang - Angle in degrees clockwise + + Returns: +
- Input _poly_, with rotated points +]] +function octolib.poly.rotate(poly, ang) + if ang == 0 then return poly end + + local deg = math.rad(ang) + local sin, cos = math.sin(deg), math.cos(deg) + + for _, vert in ipairs(poly) do + local x, y = vert.x, vert.y + + local xn = x * cos - y * sin + local yn = x * sin + y * cos + + vert.x, vert.y = xn, yn + end + + return poly +end + +--[[ + Function: polyTranslate + Destructively move polygon structure + + Arguments: +
poly - Polygon structure to move, will be changed in place + dx - Distance to translate along X axis + dy - Distance to translate along Y axis + + Returns: +
- Input _poly_, with trnaslated points +]] +function octolib.poly.translate(poly, dx, dy) + for _, vert in ipairs(poly) do + vert.x, vert.y = vert.x + dx, vert.y + dy + end +end \ No newline at end of file diff --git a/octolib/addon/lua/octolib/modules/pon.lua b/octolib/addon/lua/octolib/modules/pon.lua new file mode 100644 index 0000000..59bc8c9 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/pon.lua @@ -0,0 +1,460 @@ +local pon = {} +_G.pon = pon + +pon.pendingEnts = {} +function pon.queueEntsFlush() end + +hook.Add('Think', 'octolib.pon.init', function() + hook.Remove('Think', 'octolib.pon.init') + + pon.queueEntsFlush = octolib.func.debounce(function() + table.Empty(pon.pendingEnts) + end, 300) +end) + +local type = type +local tonumber = tonumber +local format = string.format +local insert = table.insert + +do + local encode = {} + + local cacheSize = 0 + + encode['table'] = function(tbl, output, cache) + if cache[tbl] then + insert(output, format('(%u)', cache[tbl])) + return + else + cacheSize = cacheSize + 1 + cache[tbl] = cacheSize + end + + local first = next(tbl) + local predictedNumeric = 1 + + -- starts with a sequential type + if first == 1 then + insert(output, '{') + + 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 + insert(output, format('(%u)', pid)) + else + cacheSize = cacheSize + 1 + cache[v] = cacheSize + encode.string(v, output) + end + elseif IsColor(v) then + encode.Color(v, output, cache) + else + encode[tv](v, output, cache) + end + else + break + end + end + + predictedNumeric = predictedNumeric - 1 + else + predictedNumeric = nil + end + + -- start with dictionary type + if predictedNumeric == nil then + insert(output, '[') + else + -- break sequential for dictionary + local kv = next(tbl, predictedNumeric) + if kv then + insert(output, '~') + end + 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 + insert(output, format('(%u)', pid)) + else + cacheSize = cacheSize + 1 + cache[k] = cacheSize + + encode.string(k, output) + end + elseif IsColor(k) then + encode.Color(k, output, cache) + else + encode[tk](k, output, cache) + end + + -- WRITE VALUE + if tv == 'string' then + local pid = cache[v] + if pid then + insert(output, format('(%u)', pid)) + else + cacheSize = cacheSize + 1 + cache[v] = cacheSize + + encode.string(v, output) + end + elseif IsColor(v) then + encode.Color(v, output, cache) + else + encode[tv](v, output, cache) + end + end + + insert(output, '}') + end + -- ENCODE STRING + local gsub = string.gsub + encode['string'] = function(str, output) + local estr, count = gsub(str, ';', '\\;') + if count == 0 then + insert(output, '\'' .. str .. ';') + else + insert(output, '"' .. estr .. '";') + end + end + local b62alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + local function b62encode(num) + if num < 0 then num = -num end + local str = '' + while num > 0 do + str = str .. b62alphabet[num % 62 + 1] + num = math.floor(num / 62) + end + return string.reverse(str) + end + -- ENCODE NUMBER + encode['number'] = function(num, output) + if num % 1 == 0 then + insert(output, (num < 0 and 'y' or 'Y') .. b62encode(num) .. ';') + else + insert(output, tonumber(num) .. ';') + end + end + -- ENCODE BOOLEAN + encode['boolean'] = function(val, output) + insert(output, val and 't' or 'f') + end + -- ENCODE VECTOR + encode['Vector'] = function(val, output) + insert(output, 'v' .. val.x .. ',' .. val.y .. ',' .. val.z .. ';') + end + -- ENCODE COLOR + encode['Color'] = function(val, output) + insert(output, 'l' .. val.r .. ',' .. val.g .. ',' .. val.b .. ',' .. val.a .. ';') + end + -- ENCODE ANGLE + encode['Angle'] = function(val, output) + insert(output, 'a' .. val.p .. ',' .. val.y .. ',' .. val.r .. ';') + end + encode['Entity'] = function(val, output) + insert(output, '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'] + + -- untransmittable values fix + encode['function'] = function(val, output) + insert(output, 'w') + end + encode['userdata'] = function(val, output) + insert(output, 'u') + end + encode['thread'] = function(val, output) + insert(output, 'h') + end + encode['CSoundPatch'] = function(val, output) + insert(output, 'c') + end + + do + local concat = table.concat + function pon.encode(tbl) + assert(istable(tbl), 'Table excepted for encode.') + + local output = {} + cacheSize = 0 + encode['table'](tbl, output, {}) + local res = concat(output) + + return res + end + end +end + +do + local tonumber, select = tonumber, select + local find, sub, gsub, gmatch, Explode = string.find, string.sub, string.gsub, string.gmatch, string.Explode + local Vector, Angle, Entity = Vector, Angle, Entity + + local decode = {} + + -- keep track of encoding table stack to restore null refs + local curStack = {} + + -- sequential or mixed table + decode['{'] = function(index, str, cache) + local cur = {} + insert(cache, cur) + + local k = 1 + local v, tv + 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 + + index = index + 1 + curStack[#curStack + 1] = {cur, k} + index, v = decode[tv](index, str, cache) + curStack[#curStack] = nil + + cur[k] = v + + k = k + 1 + end + + -- dictionary after sequential + local tk + while true do + tk = sub(str, index, index) + if not tk or tk == '}' then + index = index + 1 + break + end + + index = index + 1 + index, k = decode[tk](index, str, cache) + + tv = sub(str, index, index) + index = index + 1 + index, v = decode[tv](index, str, cache) + + cur[k] = v + end + + return index, cur + end + + -- dictionary table + decode['['] = function(index, str, cache) + local cur = {} + insert(cache, cur) + + local k, v, tk, tv + while true do + tk = sub(str, index, index) + if not tk or tk == '}' then + index = index + 1 + break + end + + index = index + 1 + index, k = decode[tk](index, str, cache) + + tv = sub(str, index, index) + index = index + 1 + index, v = decode[tv](index, str, cache) + + cur[k] = v + end + + return index, cur + end + + -- pointer + decode['('] = function(index, str, cache) + local finish = find(str, ')', index, true) + local num = tonumber(sub(str, index, finish - 1)) + index = finish + 1 + return index, cache[num] + end + + -- string + decode['\''] = function(index, str, cache) + local finish = find(str, ';', index, true) + local res = sub(str, index, finish - 1) + index = finish + 1 + + insert(cache, res) + return index, res + end + -- escaped string + decode['"'] = function(index, str, cache) + local finish = find(str, '";', index, true) + local res = gsub(sub(str, index, finish - 1), '\\;', ';') + index = finish + 2 + + insert(cache, res) + return index, res + end + + -- number + decode['n'] = function(index, str) + 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(index, str) + 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(index, str) + local finish = find(str, ';', index, true) + local num = -tonumber(sub(str, index, finish - 1), 16) + index = finish + 1 + return index, num + end + local b62alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + local function b62decode(str) + local num = 0 + for letter in gmatch(str, '.') do + num = num * 62 + (select(1, find(b62alphabet, letter, 1, true)) or 0) - 1 + end + return num + end + -- positive base62 + decode['Y'] = function(index, str) + local finish = find(str, ';', index, true) + local num = b62decode(sub(str, index, finish - 1)) + index = finish + 1 + return index, num + end + -- negative base62 + decode['y'] = function(index, str) + local index, num = decode['Y'](index, str) + return index, -num + end + + -- boolean + decode['t'] = function(index) + return index, true + end + decode['f'] = function(index) + return index, false + end + + -- Vector + decode['v'] = function(index, str) + 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 + -- Color + decode['l'] = function(index, str) + 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, Color(tonumber(segs[1]), tonumber(segs[2]), tonumber(segs[3]), tonumber(segs[4])) + end + -- Angle + decode['a'] = function(index, str) + 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(index, str) + 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 + + local ent = Entity(num) + if not IsValid(ent) and curStack[#curStack] then + pon.pendingEnts[num] = curStack[#curStack] + pon.queueEntsFlush() + end + + return index, ent + end + end + + -- untransmittable values fix + decode['w'] = function(index) + return index, 'function' + end + decode['u'] = function(index) + return index, 'userdata' + end + decode['h'] = function(index) + return index, 'thread' + end + decode['с'] = function(index) + return index, 'sound' + end + + function pon.decode(data) + assert(isstring(data), 'String excepted for decode.') + + local _, res = decode[sub(data, 1, 1)](2, data, {}) + return res + end +end + +local function entCreated(ent) + local id = ent:EntIndex() + local link = pon.pendingEnts[id] + if link then + local tbl, key = unpack(link) + tbl[key] = ent + pon.pendingEnts[id] = nil + hook.Run('pon.entityCreated', ent, tbl, key) + end +end +hook.Add('OnEntityCreated', 'octolib.netVar', entCreated) +hook.Add('NetworkEntityCreated', 'octolib.netVar', entCreated) + +hook.Add('EntityRemoved', 'octolib.netVar', function(ent) + local id = ent:EntIndex() + pon.pendingEnts[id] = nil +end) diff --git a/octolib/addon/lua/octolib/modules/ptime/server.lua b/octolib/addon/lua/octolib/modules/ptime/server.lua new file mode 100644 index 0000000..0bd1a0d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/ptime/server.lua @@ -0,0 +1,133 @@ +if CFG.disabledModules.ptime then return end + +local vars = { + sessionStart = 'pt.sessionStart', + here = { + nv = 'pt.here', + db = 'pt_' .. CFG.serverID, + }, + total = { + nv = 'pt.total', + db = 'pt', + }, +} + +local hasOldData = sql.TableExists('utime') +local function tryMigrate(ply) + + if not hasOldData or not IsValid(ply) then return end + + local sid = ply:SteamID() + local row = sql.QueryRow('SELECT totaltime FROM utime WHERE steamid=\'' .. sid .. '\';') + if not row then return end + + local old = tonumber(row.totaltime) or 0 + ply:SetNetVar(vars.here.nv, old) + ply:SetDBVar(vars.here.db, old) + + local total = ply:GetNetVar(vars.total.nv) + if old > total then + ply:SetNetVar(vars.total.nv, old) + ply:SetDBVar(vars.total.db, old) + end + + sql.Query('DELETE FROM utime WHERE steamid=\'' .. sid .. '\';') + +end + +hook.Add('octolib.dbvars-loaded', 'play-time', function(ply) + + ply:SetNetVar(vars.total.nv, ply:GetDBVar(vars.total.db, 0)) + ply:SetNetVar(vars.here.nv, ply:GetDBVar(vars.here.db, 0)) + + tryMigrate(ply) + + if not ply:IsAFK() then + ply:SetNetVar(vars.sessionStart, CurTime()) + end + +end) + +hook.Add('PlayerDisconnected', 'play-time', function(ply) + + ply:SaveTime() + +end) + +hook.Add('octolib.afk.changed', 'play-time', function(ply, afk) + + if afk then + ply:SaveTime() + ply:SetNetVar(vars.sessionStart, nil) + else + ply:SetNetVar(vars.sessionStart, CurTime()) + end + +end) + +local pmeta = FindMetaTable('Player') + +function pmeta:SaveTime() + + local ct = CurTime() + local diff = ct - self:GetNetVar(vars.sessionStart, ct) + if diff <= 0 then return end + + local time = self:GetNetVar(vars.total.nv, 0) + diff + self:SetDBVar(vars.total.db, time) + self:SetNetVar(vars.total.nv, time) + + time = self:GetNetVar(vars.here.nv, 0) + diff + self:SetDBVar(vars.here.db, time) + self:SetNetVar(vars.here.nv, time) + + self:SetNetVar(vars.sessionStart, ct) + +end + +function pmeta:SetTimeTotal(val) + + self:SetNetVar(vars.total.nv, val) + self:SetDBVar(vars.total.db, val) + +end + +function pmeta:SetTimeHere(val) + + self:SetNetVar(vars.here.nv, val) + self:SetDBVar(vars.here.db, val) + +end + +------------ +-- PTIMER -- +------------ +octolib.ptime = octolib.ptime or {} +octolib.ptime.timers = octolib.ptime.timers or {} + +-- unique timer id, delay in minutes (int), callback +function octolib.ptime.createTimer(identifier, delay, func) + if not (identifier and isnumber(delay) and isfunction(func)) then + return ErrorNoHalt('Incorrect arguments') + end + octolib.ptime.timers[identifier] = { math.floor(delay), func } +end + +function octolib.ptime.removeTimer(identifier) + if identifier then octolib.ptime.timers[identifier] = nil end +end + +timer.Create('octolib.ptime.iterate', 60, 0, function() + for _, ply in ipairs(player.GetAll()) do + if ply:IsAFK() then continue end + + local minutesPlayed = math.floor(ply:GetTimeTotal() / 60) + for _, v in pairs(octolib.ptime.timers) do + if minutesPlayed % v[1] == 0 and ply.lastRewards ~= minutesPlayed then + ply.lastRewards = minutesPlayed + v[2](ply, minutesPlayed) + end + end + + end +end) diff --git a/octolib/addon/lua/octolib/modules/ptime/shared.lua b/octolib/addon/lua/octolib/modules/ptime/shared.lua new file mode 100644 index 0000000..4391435 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/ptime/shared.lua @@ -0,0 +1,22 @@ +if CFG.disabledModules.ptime then return end + +local vars = { + here = 'pt.here', + total = 'pt.total', + sessionStart = 'pt.sessionStart', +} + +local meta = FindMetaTable('Player') + +function meta:GetTimeSession() + local ct = CurTime() + return ct - self:GetNetVar(vars.sessionStart, ct) +end + +function meta:GetTimeTotal() + return self:GetNetVar(vars.total, 0) + self:GetTimeSession() +end + +function meta:GetTimeHere() + return self:GetNetVar(vars.here, 0) + self:GetTimeSession() +end diff --git a/octolib/addon/lua/octolib/modules/questions/client.lua b/octolib/addon/lua/octolib/modules/questions/client.lua new file mode 100644 index 0000000..1c40e30 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/questions/client.lua @@ -0,0 +1,184 @@ +octolib.questions.active = octolib.questions.active or {} + +local function createQuestionBase(uid, text, time, sound) + local fr = vgui.Create 'DFrame' + fr:SetSize(300, 24) + fr:CenterVertical(0.4) + fr:ShowCloseButton(false) + fr:SetTitle('') + fr:SetDraggable(false) + fr:SetVisible(false) + fr.uid = uid + octolib.questions.active[#octolib.questions.active + 1] = fr + + local createTime = CurTime() + local progress = fr:Add 'DProgress' + progress:SetPos(4, 9) + progress:SetSize(292, 6) + function progress:Think() + self:SetFraction((CurTime() - createTime) / time) + end + fr.progress = progress + + if sound then + LocalPlayer():EmitSound(sound, 100, 100) + end + + local lbl = fr:Add 'DLabel' + lbl:SetPos(5, 29) + lbl:SetWide(290) + lbl:SetMultiline(true) + lbl:SetWrap(true) + lbl:SetText(text) + + local h = markup.Parse('' .. text:gsub('<', '\\<'):gsub('>', '\\>') .. '', 290):GetHeight() + lbl:SetTall(h) + fr:SetTall(fr:GetTall() + h + 10) + + local superRemove = fr.Remove + function fr:Remove() + local idx = table.RemoveByValue(octolib.questions.active, self) + if not idx then return end + local tall = self:GetTall() + 10 + self:SlideUp(0.3) + timer.Simple(0.3, function() + superRemove(self) + end) + timer.Simple(0.5, function() + for i = 1, idx-1 do + octolib.questions.active[i]:MoveBy(0, -tall, 0.3, 0, 0.3) + end + end) + end + + timer.Simple(0, function() + local tall = fr:GetTall() + 10 + for _,v in ipairs(octolib.questions.active) do + if v ~= fr then v:MoveBy(0, tall, 0.3, 0, 0.3) end + end + timer.Simple(0.3, function() + if IsValid(fr) then fr:SlideDown(0.3) end + end) + end) + + return fr + +end + +local function findQuestion(uid) + for _,v in ipairs(octolib.questions.active) do + if v.uid == uid then return v end + end +end + +netstream.Hook('octolib.question.start', function(uid, text, left, right, time, sound) + + if IsValid(findQuestion(uid)) then return end + + local fr = createQuestionBase(uid, text, time, sound) + fr.recipient = true + surface.SetFont('DermaDefault') + + local rgtBtn = fr:Add('DButton') + local rWide = surface.GetTextSize(right) + rWide = math.max(75, rWide + 10) + rgtBtn:SetPos(fr:GetWide() - (rWide + 8), fr:GetTall() + 10) + rgtBtn:SetText(right) + + rgtBtn:SetWide(rWide) + function rgtBtn:DoClick() + netstream.Start('octolib.question.reply', uid, false) + fr:Remove() + end + + local lftBtn = fr:Add 'DButton' + local lWide = surface.GetTextSize(left) + lWide = math.max(75, lWide + 10) + lftBtn:SetPos(fr:GetWide() - (rWide + 8 + lWide + 5), fr:GetTall() + 10) + lftBtn:SetText(left) + lftBtn:SetWide(lWide) + function lftBtn:DoClick() + netstream.Start('octolib.question.reply', uid, true) + fr:Remove() + end + + fr:SetTall(fr:GetTall() + rgtBtn:GetTall() + 18) + + timer.Simple(time, function() + if IsValid(fr) then + fr:SetEnabled(false) + end + end) +end) + +netstream.Hook('octolib.question.finish', function(uid) + local fr = findQuestion(uid) + if IsValid(fr) and fr.recipient then fr:Remove() end +end) + +local function lerpFractionThink(pan) + function pan:Think() + if self.tgtFr then + local st = math.Clamp(math.TimeFraction(self.startLerp, self.endLerp, CurTime()), 0, 1) + local frac = octolib.tween.easing.inOutQuad(st, 0, 1, 1) + if frac > 0 then + self:SetFraction(Lerp(frac, self.srcFr, self.tgtFr)) + elseif frac == 1 then + self.srcFr, self.tgtFr, self.startLerp, self.endLerp = nil + end + end + end + + function pan:LerpFractionTo(fr) + self.srcFr = self:GetFraction() + self.tgtFr = fr + self.startLerp = CurTime() + self.endLerp = self.startLerp + 0.3 + end +end + +netstream.Hook('octolib.question.spectateStart', function(uid, text, left, right, time, sound) + if IsValid(findQuestion(uid)) then return end + + local fr = createQuestionBase(uid, text, time, sound) + fr.recipient = false + + local leftOpt = fr:Add 'DProgressLabel' + leftOpt:SetPos(5, fr:GetTall() + 10) + leftOpt:SetWide(290) + leftOpt:SetText(left .. ': 0') + leftOpt:AttachToEdge() + lerpFractionThink(leftOpt) + leftOpt.text = left + fr:SetTall(fr:GetTall() + leftOpt:GetTall() + 15) + + local rightOpt = fr:Add 'DProgressLabel' + rightOpt:SetPos(5, fr:GetTall()) + rightOpt:SetWide(290) + rightOpt:SetText(right .. ': 0') + rightOpt:AttachToEdge() + lerpFractionThink(rightOpt) + rightOpt.text = right + fr:SetTall(fr:GetTall() + rightOpt:GetTall() + 5) + + fr.left, fr.right = leftOpt, rightOpt +end) + +netstream.Hook('octolib.question.spectateUpdate', function(uid, left, right) + local fr = findQuestion(uid) + if not IsValid(fr) then return end + local sum = left + right + fr.left:LerpFractionTo(left / sum) + fr.right:LerpFractionTo(right / sum) + fr.left:SetText(fr.left.text .. ': ' .. left) + fr.right:SetText(fr.right.text .. ': ' .. right) +end) + +netstream.Hook('octolib.question.spectateFinish', function(uid) + local fr = findQuestion(uid) + if not IsValid(fr) or fr.recipient then return end + fr.progress:AlphaTo(0, 1, 0) + timer.Simple(5, function() + fr:Remove() + end) +end) diff --git a/octolib/addon/lua/octolib/modules/questions/server.lua b/octolib/addon/lua/octolib/modules/questions/server.lua new file mode 100644 index 0000000..888dc9b --- /dev/null +++ b/octolib/addon/lua/octolib/modules/questions/server.lua @@ -0,0 +1,82 @@ +octolib.questions.active = octolib.questions.active or {} + +function octolib.questions.finish(uid, silent) + local q = octolib.questions.active[uid] + if not q then return end + local yes, no = {}, {} + for i,v in pairs(q.replies) do + if v then yes[#yes + 1] = i else no[#no + 1] = i end + end + local szY, szN = #yes, #no + if not silent then + local suck, err = pcall(function() q.onFinish(szY - szN, szY, szN, yes, no) end) + if not suck then ErrorNoHalt(err) end + end + octolib.questions.active[uid] = nil + netstream.Start(q.spectators, 'octolib.question.spectateFinish', uid) + netstream.Start(q.recipients, 'octolib.question.finish', uid) + timer.Remove('octolib.question.' .. uid) +end + +function octolib.questions.start(data) + if not istable(data) then return end + if not isstring(data.text) or not isfunction(data.onFinish) then return end + if IsEntity(data.recipients) then data.recipients = {data.recipients} end + if data.recipients ~= nil and not istable(data.recipients) then return end + + local uid = octolib.string.uuid() + octolib.questions.active[uid] = { + recipients = data.recipients, + spectators = data.spectators or {}, + onFinish = data.onFinish, + onReply = data.onReply, + replies = {}, + } + if data.spectators then + netstream.Start(data.spectators, 'octolib.question.spectateStart', uid, data.text, data.left or 'За', data.right or 'Против', data.time or 60, data.sound) + end + timer.Simple(0, function() + netstream.Start(data.recipients, 'octolib.question.start', uid, data.text, data.left or 'За', data.right or 'Против', data.time or 60, data.sound) + end) + timer.Create('octolib.question.' .. uid, data.time or 60, 1, function() + octolib.questions.finish(uid) + end) + + return uid +end + +netstream.Hook('octolib.question.reply', function(ply, uid, reply) + if not uid then return end + local q = octolib.questions.active[uid] + if not q then return ply:Notify('warning', 'Голосование уже завершилось или не существует') end + if q.replies[ply:SteamID()] ~= nil then + return ply:Notify('warning', 'Твой голос уже засчитан') + end + if q.recipients and not table.HasValue(q.recipients, ply) then + return ply:Notify('warning', 'Ты не можешь принимать участие в этом голосовании') + end + + reply = tobool(reply) + + if isfunction(q.onReply) then + local executed, ansOverride, msg, notifyType = pcall(function() return q.onReply(ply, reply) end) + if executed then + if ansOverride ~= nil then reply = tobool(ansOverride) end + if msg then + ply:Notify(notifyType or 'rp', msg) + end + else ErrorNoHalt(ansOverride) end + end + + q.replies[ply:SteamID()] = reply + + local yes, no = 0, 0 + for _,v in pairs(q.replies) do + if v then yes = yes + 1 else no = no + 1 end + end + netstream.Start(q.spectators, 'octolib.question.spectateUpdate', uid, yes, no) + + if table.Count(q.replies) == (q.recipients and #q.recipients or player.GetCount()) then + octolib.questions.finish(uid) + end +end) diff --git a/octolib/addon/lua/octolib/modules/questions/shared.lua b/octolib/addon/lua/octolib/modules/questions/shared.lua new file mode 100644 index 0000000..38fab61 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/questions/shared.lua @@ -0,0 +1 @@ +octolib.questions = octolib.questions or {} diff --git a/octolib/addon/lua/octolib/modules/queue.lua b/octolib/addon/lua/octolib/modules/queue.lua new file mode 100644 index 0000000..066e452 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/queue.lua @@ -0,0 +1,37 @@ +octolib.queue = octolib.queue or {} + +local Queue = {} +Queue.__index = Queue + +function octolib.queue.create(processFunc) + return setmetatable({ + processFunc = processFunc, + pending = {}, + isRunning = false, + }, Queue) +end + +function Queue:Add(data) + table.insert(self.pending, data) + self:Run() +end + +function Queue:IsRunning() + return self.isRunning +end + +function Queue:Run() + if self:IsRunning() then return end + self.isRunning = true + + local data = table.remove(self.pending, 1) + if not data then + self.isRunning = false + return + end + + self.processFunc(data, function() + self.isRunning = false + self:Run() + end) +end diff --git a/octolib/addon/lua/octolib/modules/render/client.lua b/octolib/addon/lua/octolib/modules/render/client.lua new file mode 100644 index 0000000..132699f --- /dev/null +++ b/octolib/addon/lua/octolib/modules/render/client.lua @@ -0,0 +1,155 @@ +octolib.renderModel = octolib.renderModel or {} + +local vector_right = Vector(1, 0, 0) +local vector_forward = Vector(0, 1, 0) +local vector_up = Vector(0, 0, 1) +local angle_zero = Angle() +local emptyPng = render.Capture({ x = 0, y = 0, w = 1, h = 1, format = 'png' }) + +local waitingDests = {} +local waitingImages = {} +local renderedImages = {} + +file.CreateDir('octolib/render') +hook.Add('ShutDown', 'octolib.renderModel.clenup', function() + local files = file.Find('octolib/render/*', 'DATA') + for _, name in ipairs(files) do + file.Delete('octolib/render/' .. name) + end +end) + +local queue = {} +hook.Add('Think', 'octolib.renderModel.queue', function() + if not queue[1] then return end + + local destination, settings, fileName, filePath, matPath = unpack(table.remove(queue, 1)) + + local imgW, imgH = destination.width * 3, destination.height * 3 + local texture = GetRenderTarget(('octolib_render_%d_%d'):format(imgW, imgH), imgW, imgH) + local camFov = settings.camFov + local camAng = settings.camAng + local mdlAng = Angle() + mdlAng:RotateAroundAxis(vector_forward, camAng.p) + mdlAng:RotateAroundAxis(vector_up, camAng.y) + mdlAng:RotateAroundAxis(vector_right, camAng.r) + + render.PushRenderTarget(texture) + render.Clear(0,0,0,0, true, true) + + local csent = ClientsideModel(destination.model, RENDERGROUP_BOTH) + local mins, maxs = csent:GetModelBounds() + csent:SetPos((mins + maxs) / -2) + csent:SetAngles(mdlAng) + csent:SetSkin(destination.skin or 0) + for id, val in pairs(destination.bg or {}) do + csent:SetBodygroup(id, val) + end + + local camDist = mins:Distance(maxs) / 2 / math.tan(math.rad(camFov / (2 * settings.camFovMod))) + local camPos = Vector(-camDist, 0, 0) + + local light1Pos = Vector(camPos) + light1Pos:Rotate(Angle(0, 120, 0)) + local light2Pos = Vector(camPos) + light2Pos:Rotate(Angle(0, -120, 0)) + + local light1 = { + type = MATERIAL_LIGHT_POINT, + pos = light1Pos, + color = Vector(0.5, 0.5, 0.5), + fiftyPercentDistance = camDist * 2.5, + zeroPercentDistance = camDist * 3, + } + + local light2 = { + type = MATERIAL_LIGHT_POINT, + pos = light2Pos, + color = Vector(0.5, 0.5, 0.5), + fiftyPercentDistance = camDist * 2.5, + zeroPercentDistance = camDist * 3, + } + + cam.Start3D(camPos + settings.camPos, angle_zero, camFov, 0, 0, imgW, imgH) + render.OverrideAlphaWriteEnable(true, true) + render.SuppressEngineLighting(true) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + render.SetLocalModelLights({ light1, light2 }) + render.SetWriteDepthToDestAlpha(false) + render.SetColorModulation(1, 1, 1) + render.ResetModelLighting(0.75, 0.75, 0.75) + csent:DrawModel() + local imageData = render.Capture({ + format = 'png', + x = 0, y = 0, + w = imgW, h = imgH, + farz = camDist * 2, + }) + render.SetLocalModelLights() + render.PopFilterMin() + render.SuppressEngineLighting(false) + render.OverrideAlphaWriteEnable(false) + cam.End3D() + + csent:Remove() + render.PopRenderTarget() + + file.Write(filePath, imageData) + RunConsoleCommand('mat_reloadmaterial', matPath:StripExtension()) + + local image = Material(matPath, 'smooth noclamp mips') + for _, destination in ipairs(waitingDests[fileName]) do + destination.image = image + end + + waitingDests[fileName] = nil + renderedImages[fileName] = image +end) + +function octolib.renderModel.getFileName(destination) + local model = destination.model + local skin = destination.skin or 0 + local bg = table.concat(octolib.array.map(destination.bg or {}, function(v) return v end), ',') + local size = destination.width .. destination.height + + return util.CRC(size .. model .. skin .. bg) +end + +function octolib.renderModel.queueRender(destination, settings) + local image = destination.image + if image then + destination.image = image + return + end + + local fileName = octolib.renderModel.getFileName(destination) + local filePath = 'octolib/render/' .. fileName .. '.png' + local matPath = '../data/' .. filePath + + local renderedImages = renderedImages[fileName] + if renderedImages then + destination.image = renderedImages + return + end + + if not waitingImages[fileName] then + file.Write(filePath, emptyPng) + RunConsoleCommand('mat_reloadmaterial', matPath:StripExtension()) + destination.image = Material(matPath, 'smooth noclamp') + waitingImages[fileName] = destination.image + end + + local waiting = waitingDests[fileName] + if not waiting then + waitingDests[fileName] = { destination } + waiting = waitingDests[fileName] + queue[#queue + 1] = { destination, settings, fileName, filePath, matPath } + else + waiting[#waitingDests + 1] = destination + end +end + +function octolib.renderModel.clear(destination) + local fileName = octolib.renderModel.getFileName(destination) + renderedImages[fileName] = nil + destination.image = nil +end diff --git a/octolib/addon/lua/octolib/modules/render/shared.lua b/octolib/addon/lua/octolib/modules/render/shared.lua new file mode 100644 index 0000000..08ed7d6 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/render/shared.lua @@ -0,0 +1 @@ +octolib.include.client('vgui') diff --git a/octolib/addon/lua/octolib/modules/render/vgui/octolib_rendermodel.lua b/octolib/addon/lua/octolib/modules/render/vgui/octolib_rendermodel.lua new file mode 100644 index 0000000..62e34bc --- /dev/null +++ b/octolib/addon/lua/octolib/modules/render/vgui/octolib_rendermodel.lua @@ -0,0 +1,155 @@ +concommand.Add('octolib_rendermodel', function() + local dest = { + width = 16, + height = 16, + model = 'models/props_junk/garbage_bag001a.mdl', + skin = 0, + } + local renderSettings = { + camFov = 20, + camFovMod = 1, + camPos = Vector(0, 0, 0), + camAng = Angle(0, 0, 0), + } + + local frame = vgui.Create 'DFrame' + frame:SetSize(550, 1000) + frame:Center() + frame:MakePopup() + frame:SetTitle('Настройка рендера модели') + + local preview = frame:Add 'DPanel' + preview:Dock(TOP) + preview:SetTall(512) + function preview:Paint(w, h) + draw.RoundedBox(4, 0, 0, w, h, Color(0,0,0)) + + local iw, ih = dest.width, dest.height + local x, y = (w - iw) / 2, (h - ih) / 2 + draw.RoundedBox(4, x, y, iw, ih, Color(85,68,85)) + + if not dest.image then return end + + surface.SetMaterial(dest.image) + surface.SetDrawColor(255,255,255) + surface.DrawTexturedRect(x, y, iw, ih) + end + + -- controls + local rebuild = octolib.func.debounce(function() + octolib.renderModel.clear(dest) + octolib.renderModel.queueRender(dest, renderSettings) + end, 0.1) + + local eModel = octolib.textEntry(frame, 'Модель') + eModel:DockMargin(5, 5, 5, 5) + eModel:SetTall(15) + eModel:SetPlaceholderText('Модель') + eModel:SetUpdateOnType(true) + eModel.PaintOffset = 5 + function eModel:OnValueChange(text) + dest.model = text + rebuild() + end + eModel:SetValue(dest.model) + + local sWidth = octolib.slider(frame, 'Ширина', 16, 512, 0) + function sWidth:OnValueChanged(val) + dest.width = math.Round(val) + rebuild() + end + sWidth:SetValue(dest.width) + + local sHeight = octolib.slider(frame, 'Высота', 16, 512, 0) + function sHeight:OnValueChanged(val) + dest.height = math.Round(val) + rebuild() + end + sHeight:SetValue(dest.height) + + local sFov = octolib.slider(frame, 'FOV', 10, 90, 1) + function sFov:OnValueChanged(val) + renderSettings.camFov = math.Round(val, 1) + rebuild() + end + sFov:SetValue(renderSettings.camFov) + + local sFovMod = octolib.slider(frame, 'Множитель FOV', 0.2, 2, 2) + function sFovMod:OnValueChanged(val) + renderSettings.camFovMod = math.Round(val, 2) + rebuild() + end + sFovMod:SetValue(renderSettings.camFovMod) + + local sPitch = octolib.slider(frame, 'Угол: Pitch', -180, 180, 0) + function sPitch:OnValueChanged(val) + local newAngle = Angle(renderSettings.camAng) + newAngle.p = val + renderSettings.camAng = newAngle + rebuild() + end + sPitch:SetValue(renderSettings.camAng.p) + + local sYaw = octolib.slider(frame, 'Угол: Yaw', -180, 180, 0) + function sYaw:OnValueChanged(val) + local newAngle = Angle(renderSettings.camAng) + newAngle.y = val + renderSettings.camAng = newAngle + rebuild() + end + sYaw:SetValue(renderSettings.camAng.y) + + local sRoll = octolib.slider(frame, 'Угол: Roll', -180, 180, 0) + function sRoll:OnValueChanged(val) + local newAngle = Angle(renderSettings.camAng) + newAngle.r = val + renderSettings.camAng = newAngle + rebuild() + end + sRoll:SetValue(renderSettings.camAng.r) + + local sX = octolib.slider(frame, 'Позиция: X', -50, 50, 1) + function sX:OnValueChanged(val) + local newVector = Vector(renderSettings.camPos) + newVector.x = val + renderSettings.camPos = newVector + rebuild() + end + sX:SetValue(renderSettings.camPos.x) + + local sY = octolib.slider(frame, 'Позиция: Y', -50, 50, 1) + function sY:OnValueChanged(val) + local newVector = Vector(renderSettings.camPos) + newVector.y = val + renderSettings.camPos = newVector + rebuild() + end + sY:SetValue(renderSettings.camPos.y) + + local sZ = octolib.slider(frame, 'Позиция: Z', -50, 50, 1) + function sZ:OnValueChanged(val) + local newVector = Vector(renderSettings.camPos) + newVector.z = val + renderSettings.camPos = newVector + rebuild() + end + sZ:SetValue(renderSettings.camPos.z) + + local bCopy = octolib.button(frame, 'Скопировать код', function() + local out = '' + -- out = out .. ('model = \'%s\',\n'):format(eModel:GetValue()) + -- out = out .. ('width = %d,\n'):format(sWidth:GetValue()) + -- out = out .. ('height = %d,\n'):format(sHeight:GetValue()) + out = out .. ('camFov = %d,\n'):format(sFov:GetValue()) + out = out .. ('camFovMod = %.2f,\n'):format(sFovMod:GetValue()) + + local camAng = Angle(sPitch:GetValue(), sYaw:GetValue(), sRoll:GetValue()) + out = out .. ('camAng = Angle(%d, %d, %d),\n'):format(camAng.p, camAng.y, camAng.r) + + local camPos = Vector(sX:GetValue(), sY:GetValue(), sZ:GetValue()) + out = out .. ('camPos = Vector(%.1f, %.1f, %.1f),\n'):format(camPos.x, camPos.y, camPos.z) + + SetClipboardText(out) + end) + bCopy:Dock(BOTTOM) +end) diff --git a/octolib/addon/lua/octolib/modules/request/client.lua b/octolib/addon/lua/octolib/modules/request/client.lua new file mode 100644 index 0000000..d5333bc --- /dev/null +++ b/octolib/addon/lua/octolib/modules/request/client.lua @@ -0,0 +1,327 @@ +surface.CreateFont('octolib.normal', { + font = 'Calibri', + extended = true, + size = 27, + weight = 350, +}) + +function octolib.request.open(data, done) + + local f = vgui.Create 'DFrame' + f:SetBackgroundBlur(true) + f:SetSize(400, 55) + f:SetTitle(L.request) + f:SetAlpha(1) + f:Center() + f:MakePopup() + function f.btnClose:DoClick() + done(false) + f:Remove() + end + + local p = f:Add 'DScrollPanel' + p:Dock(FILL) + timer.Simple(0.5, function() + if not IsValid(f) then return end + local x, y = f:GetPos() + local h = math.min(p.pnlCanvas:GetTall() + 70, 600) + f:SizeTo(400, h, 0.5, 0, 0.5) + f:MoveTo(x, (ScrH() - h) / 2, 0.5, 0, 0.5) + f:AlphaTo(255, 0.5) + end) + + local b = f:Add 'DButton' + b:Dock(BOTTOM) + b:DockMargin(0,4,0,0) + b:SetTall(30) + b:SetText(L.send) + + local cache, empty = {}, {} + local function checkFields() + for k, field in pairs(data) do + if field.required and empty[k] then + b:SetEnabled(false) + b:SetText(L.fill_required) + return + end + end + + b:SetEnabled(true) + b:SetText(L.ready) + end + + function b:DoClick() + done(cache) + f:Remove() + end + + for k, field in pairs(data) do + local p_f = p:Add 'DPanel' + p_f:Dock(TOP) + p_f:DockMargin(0,0,0,5) + p_f:DockPadding(5,5,5,5) + function p_f:PerformLayout() + timer.Simple(0.2, function() + if IsValid(self) then + self:SizeToChildren(false, true) + end + end) + end + + if field.name then + local l = p_f:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5,0,5,5) + l:SetTall(25) + l:SetFont('octolib.normal') + l:SetText(field.name) + end + + if field.desc then + local l = p_f:Add 'DLabel' + l:Dock(TOP) + l:DockMargin(5,0,5,0) + l:SetText(field.desc) + l:SetWrap(true) + function l:PerformLayout() + self:SizeToContentsY() + end + end + + if field.type == 'strShort' then + local e = p_f:Add 'DTextEntry' + e:Dock(TOP) + e:SetTall(30) + e:SetUpdateOnType(true) + if field.numeric then e:SetNumeric(true) end + if field.ph then e:SetPlaceholderText(field.ph) end + e.OnValueChange = function(e) + local val = string.Trim(e:GetText()) + cache[k] = val ~= '' and val or nil + empty[k] = (not cache[k] or string.Trim(cache[k]) == '') + checkFields() + end + if field.default then + e:SetValue(field.default) + end + elseif field.type == 'strLong' then + local e = p_f:Add 'DTextEntry' + e:Dock(TOP) + e:SetTall(200) + e:SetUpdateOnType(true) + e:SetMultiline(true) + e:SetWrap(true) + e:SetContentAlignment(7) + if field.ph then e:SetPlaceholderText(field.ph) end + e.OnValueChange = function(e) + local val = string.Trim(e:GetText()) + cache[k] = val ~= '' and val or nil + empty[k] = (not cache[k] or string.Trim(cache[k]) == '') + checkFields() + end + if field.default then + e:SetValue(field.default) + end + elseif field.type == 'numSlider' then + local e = p_f:Add 'DNumSlider' + e:Dock(TOP) + e:DockMargin(5,5,5,5) + e:SetTall(30) + e:SetDecimals(field.dec or 2) + e:SetText(field.txt or L.choose_value) + e:SetMin(field.min or 0) + e:SetMax(field.max or 100) + e:SetValue(field.val or field.min or 0) + e.OnValueChanged = function(e, val) + cache[k] = val or nil + empty[k] = (not cache[k]) + checkFields() + end + if field.default then + e:SetValue(field.default) + end + elseif field.type == 'comboBox' then + local e = p_f:Add 'DComboBox' + e:Dock(TOP) + e:SetTall(30) + e:SetSortItems(false) + e.OnSelect = function(e, i, v, val) + cache[k] = val ~= '' and val or nil + empty[k] = (not cache[k]) + checkFields() + end + if field.opts then + for i, v in ipairs(field.opts) do + e:AddChoice(unpack(v)) + end + end + elseif field.type == 'check' then + local e = p_f:Add 'DCheckBoxLabel' + e:Dock(TOP) + e:DockMargin(0,5,0,5) + e:SetTall(30) + e:SetChecked(field.check or false) + e:SetText(field.txt or L.there_is) + e.OnChange = function(e, val) + cache[k] = val + empty[k] = (not cache[k]) + checkFields() + end + e:SetValue(field.default or false) + elseif field.type == 'checkGroup' then + local e = p_f:Add 'DScrollPanel' + e:Dock(TOP) + e:SetTall(150) + + for i, data in ipairs(field.opts) do + local check = e:Add 'DCheckBoxLabel' + check:Dock(TOP) + check:DockMargin(0,5,0,5) + check:SetTall(30) + check:SetChecked(data[3] or false) + check:SetText(data[1]) + + cache[k] = cache[k] or {} + cache[k][data[2]] = data[3] + + local tbl = cache[k] + check.OnChange = function(e, val) + tbl[data[2]] = val == true or nil + cache[k] = table.GetKeys(tbl) + empty[k] = (not cache[k]) + checkFields() + end + end + elseif field.type == 'color' then + local e = p_f:Add 'DColorMixer' + e:Dock(TOP) + e:DockMargin(0,5,0,5) + e:SetTall(120) + e:SetAlphaBar(true) + e:SetWangs(true) + e:SetPalette(false) + e.ValueChanged = function(e, col) + cache[k] = col + empty[k] = (not cache[k]) + checkFields() + end + e:SetColor(field.default or Color(255,255,255, 255)) + elseif field.type == 'player' then + local sp = p_f:Add 'DPanel' + sp:Dock(TOP) + sp:DockMargin(0,5,0,5) + sp:SetTall(30) + sp.Paint = function() end + + local e = sp:Add 'DTextEntry' + e:Dock(FILL) + e:SetTall(30) + e.OnChange = function(e) + local val = string.Trim(e:GetText()) + cache[k] = val ~= '' and val or nil + empty[k] = (not cache[k] or string.Trim(cache[k]) == '') + checkFields() + end + + local b = sp:Add 'DButton' + b:Dock(RIGHT) + b:DockMargin(3,3,0,3) + b:SetWide(24) + b:SetText('') + b:SetImage('icon16/user_go.png') + b.DoClick = function(b) + local m = DermaMenu() + for i, ply in ipairs(player.GetAll()) do + m:AddOption(ply:Name(), function() + e:SetValue(string.format('%s (%s)', ply:Name(), ply:SteamID())) + e:OnChange() + end) + end + m:Open() + end + elseif field.type == 'iconPicker' then + local e = octolib.textEntryIcon(p_f, nil, field.path) + e:SetTall(30) + e:SetUpdateOnType(true) + e.OnValueChange = function(e) + local val = string.Trim(e:GetText()) + cache[k] = val ~= '' and val or nil + checkFields() + end + if field.default then + e:SetValue(field.default) + end + elseif field.type == 'item' and octoinv then + local sp = p_f:Add 'DScrollPanel' + sp:Dock(TOP) + sp:SetTall(150) + sp:SetPaintBackground(false) + + local il = sp:Add 'DIconLayout' + il:Dock(FILL) + il:SetSpaceX(4) + il:SetSpaceY(4) + + local function click(pnl) + if not field.single and cache[k] then cache[k] = octolib.array.toKeys(cache[k]) end + + if IsValid(pnl.selectedIcon) then + pnl.selectedIcon:Remove() + if field.single then + cache[k] = nil + else + cache[k][pnl.id] = nil + end + if cache[k] and table.Count(cache[k]) < 1 then cache[k] = nil end + checkFields() + else + if field.single then + for _, p in ipairs(pnl:GetParent():GetChildren()) do + if IsValid(p.selectedIcon) then p.selectedIcon:Remove() end + end + cache[k] = nil + end + + local icon = pnl:Add 'DImage' + icon:SetMouseInputEnabled(false) + icon:SetImage('icon16/tick.png') + icon:SetSize(16, 16) + icon:AlignLeft(4) + icon:AlignTop(2) + pnl.selectedIcon = icon + if field.single then + cache[k] = pnl.id + else + cache[k] = cache[k] or {} + cache[k][pnl.id] = true + end + checkFields() + end + + if not field.single and cache[k] then cache[k] = table.GetKeys(cache[k]) end + end + + for id, item in pairs(field.items) do + local p = octoinv.createItemPanel(il, item) + p.id = id + p.DoClick = click + end + end + + if field.buttonURL then + local btn = octolib.button(p_f, field.buttonText or 'Открыть', function() + octoesc.OpenURL(field.buttonURL) + end) + btn:DockMargin(0,5,0,5) + end + end + + checkFields() + +end + +netstream.Hook('octolib.request', function(id, data) + octolib.request.open(data, function(data) + netstream.Start('octolib.request', id, data) + end) +end) diff --git a/octolib/addon/lua/octolib/modules/request/server.lua b/octolib/addon/lua/octolib/modules/request/server.lua new file mode 100644 index 0000000..9542adb --- /dev/null +++ b/octolib/addon/lua/octolib/modules/request/server.lua @@ -0,0 +1,39 @@ +local pending = {} +function octolib.request.send(ply, fields, callback, cancel) + local id = octolib.string.uuid() + netstream.Start(ply, 'octolib.request', id, fields) + + pending[id] = { ply, callback, cancel } + timer.Create('octolib.requestTimeout' .. id, 600, 1, function() + local request = pending[id] + if not request then return end + + local cancel = request[3] + if cancel then cancel() end + pending[id] = nil + end) +end + +netstream.Hook('octolib.request', function(ply, id, data) + if not pending[id] or pending[id][1] ~= ply then return end + + timer.Remove('octolib.requestTimeout' .. id) + if data then + pending[id][2](data) + elseif pending[id][3] then + pending[id][3]() + end + + pending[id] = nil +end) + +hook.Add('PlayerDisconnected', 'octolib.request', function(ply) + for id, r in pairs(pending) do + if r[1] == ply then + timer.Remove('octolib.requestTimeout' .. id) + if r[3] then r[3]() end + pending[id] = nil + octolib.msg('Removing active request due to player leave: %s', id) + end + end +end) diff --git a/octolib/addon/lua/octolib/modules/request/shared.lua b/octolib/addon/lua/octolib/modules/request/shared.lua new file mode 100644 index 0000000..b7b1364 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/request/shared.lua @@ -0,0 +1 @@ +octolib.request = octolib.request or {} diff --git a/octolib/addon/lua/octolib/modules/restart/client.lua b/octolib/addon/lua/octolib/modules/restart/client.lua new file mode 100644 index 0000000..adcc0d9 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/restart/client.lua @@ -0,0 +1,63 @@ +surface.CreateFont('octolib.restart.normal', { + font = 'Calibri', + extended = true, + size = 36, + weight = 350, +}) + +surface.CreateFont('octolib.restart.normal-sh', { + font = 'Calibri', + extended = true, + size = 36, + weight = 350, + blursize = 3, +}) + +local function niceTime(time) + + local m, s + m = math.floor(time / 60) % 60 + s = math.floor(time) % 60 + + return string.format('Рестарт через %02i:%02i', m, s) + +end + +local restartTime = 0 +local function drawOverlay() + + if hook.Run('HUDShouldDraw', 'octolib.restart') == false then return end + local x, y = ScrW() / 2, ScrH() - 25 + local timeLeft = restartTime - CurTime() + local text = niceTime(math.max(timeLeft, 0)) + + if timeLeft % 30 <= 1 then + x = x + math.sin(timeLeft * math.pi * 10) * 10 + end + + local t = { + text = text, + font = 'octolib.restart.normal-sh', + pos = { x, y }, + color = color_black, + xalign = TEXT_ALIGN_CENTER, + yalign = TEXT_ALIGN_CENTER, + } + + draw.Text(t) + t.color = color_white + t.font = 'octolib.restart.normal' + draw.Text(t) + +end + +netstream.Hook('octolib.restart', function(delay) + + if delay then + restartTime = CurTime() + delay + hook.Add('HUDPaint', 'octolib.restart', drawOverlay) + else + hook.Remove('HUDPaint', 'octolib.restart') + end + +end) diff --git a/octolib/addon/lua/octolib/modules/restart/server.lua b/octolib/addon/lua/octolib/modules/restart/server.lua new file mode 100644 index 0000000..9821327 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/restart/server.lua @@ -0,0 +1,50 @@ +octolib.restart = octolib.restart or {} + +function octolib.restart.trigger() + RunConsoleCommand('sv_hibernate_think', '1') + + hook.Add('CheckPassword', 'octolib.restart', function(sid, ip, sv, cl, name) + return false, 'Идет перезагрузка, подожди пару минут...' + end) + + timer.Simple(5, function() + RunConsoleCommand('_restart') + end) + + for _, ply in ipairs(player.GetAll()) do + ply:Kick('Перезагружаемся, присоединяйся через пару минут...') + end +end + +function octolib.restart.schedule(time) + timer.Create('octolib.restart', time, 1, octolib.restart.trigger) + netstream.Start(nil, 'octolib.restart', time) + + local startTime = CurTime() + hook.Add('PlayerFinishedLoading', 'octolib.restart', function(ply) + local timePassed = CurTime() - startTime + netstream.Start(ply, 'octolib.restart', time - timePassed) + end) +end + +function octolib.restart.cancel() + timer.Remove('octolib.restart') + hook.Remove('PlayerFinishedLoading', 'octolib.restart') + hook.Remove('CheckPassword', 'octolib.restart') + netstream.Start(nil, 'octolib.restart', false) +end + +concommand.Add('octolib_restart', function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then return end + + local time = tonumber(args[1]) + if not time then return end + + octolib.restart.schedule(time) +end) + +concommand.Add('octolib_restart_cancel', function(ply, cmd, args) + if IsValid(ply) and not ply:IsSuperAdmin() then return end + + octolib.restart.cancel() +end) diff --git a/octolib/addon/lua/octolib/modules/screen/client.lua b/octolib/addon/lua/octolib/modules/screen/client.lua new file mode 100644 index 0000000..862ab3d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/screen/client.lua @@ -0,0 +1,51 @@ +function octolib.screen.capture(config) + + return util.Promise(function(resolve, reject) + hook.Add('PostRender', 'octolib.screen', function() + hook.Remove('PostRender', 'octolib.screen') + resolve(render.Capture(config or { format = 'png', quality = 100, alpha = false, h = ScrH(), w = ScrW(), x = 0, y = 0 })) + end) + end) + +end + +local function getUrl() + return octolib.imgurUsesProxy() and 'https://wani4ka.ru/api/imgur/upload' or 'https://api.imgur.com/3' +end + +local api +hook.Add('PlayerFinishedLoading', 'octolib.imgur', function() + api = octolib.api({ + url = getUrl(), + headers = {}, + }) +end) + +function octolib.screen.sendToImgur(data) + + return util.Promise(function(resolve, reject) + if not api then return reject('Неизвестная техническая ошибка') end + api.url = getUrl() + api:post('/image', { + image = util.Base64Encode(data.img), + name = data.name or 'grab.png', + type = data.type or 'png', + title = data.title or 'octolib.screen', + }):Then(function(res) + resolve(res.data) + end):Catch(function(r) + reject(tostring(r)) + end) + end) + +end + +netstream.Listen('octolib.imgur', function(reply, key) + api.headers.Authorization = 'Client-ID ' .. tostring(key) + octolib.screen.capture():Then(function(img) + return octolib.screen.sendToImgur({ + img = img, + title = ('%s (%s)'):format(LocalPlayer():Name(), LocalPlayer():SteamID()), + }) + end):Then(reply):Catch(reply) +end) diff --git a/octolib/addon/lua/octolib/modules/screen/server.lua b/octolib/addon/lua/octolib/modules/screen/server.lua new file mode 100644 index 0000000..fe36d3c --- /dev/null +++ b/octolib/addon/lua/octolib/modules/screen/server.lua @@ -0,0 +1,9 @@ +function octolib.screen.sendToImgur(ply) + + return util.Promise(function(resolve, reject) + netstream.Request(ply, 'octolib.imgur', CFG.imgurKey):Then(function(res) + if istable(res) then resolve(res) else reject(res) end + end):Catch(reject) + end) + +end diff --git a/octolib/addon/lua/octolib/modules/screen/shared.lua b/octolib/addon/lua/octolib/modules/screen/shared.lua new file mode 100644 index 0000000..70ea8c4 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/screen/shared.lua @@ -0,0 +1 @@ +octolib.screen = octolib.screenreen or {} diff --git a/octolib/addon/lua/octolib/modules/sound/client.lua b/octolib/addon/lua/octolib/modules/sound/client.lua new file mode 100644 index 0000000..e4ba61e --- /dev/null +++ b/octolib/addon/lua/octolib/modules/sound/client.lua @@ -0,0 +1,22 @@ +local DSP = 1 +local meta = FindMetaTable('Player') + +function meta:GetDSP() + return DSP +end + +meta.SetDSP = octolib.func.detour( + meta.SetDSP, + 'Player:SetDSP', + function(original, player, value) + DSP = value + + return original(player, value) + end +) + +netstream.Hook('octolib.setDSP', function(value) + local ply = LocalPlayer() + if not IsValid(ply) then return end + ply:SetDSP(value) +end) diff --git a/octolib/addon/lua/octolib/modules/sound/server.lua b/octolib/addon/lua/octolib/modules/sound/server.lua new file mode 100644 index 0000000..84ea5a3 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/sound/server.lua @@ -0,0 +1,9 @@ +local meta = FindMetaTable('Player') + +meta.SetDSP = octolib.func.detour( + meta.SetDSP, + 'Player:SetDSP', + function(original, player, value) + netstream.Start(player, 'octolib.setDSP', value) + end +) diff --git a/octolib/addon/lua/octolib/modules/space.lua b/octolib/addon/lua/octolib/modules/space.lua new file mode 100644 index 0000000..7f633ac --- /dev/null +++ b/octolib/addon/lua/octolib/modules/space.lua @@ -0,0 +1,26 @@ +octolib.space = octolib.space or {} + +function octolib.space.getMapZ(x, y, dir, maxTries) + dir = dir and octolib.math.sign(dir) or -1 + maxTries = maxTries or 10 + + local tr = { + HitPos = Vector(x, y, -dir * 5000), + HitSky = true, + } + + local tries = 0 + while tr.HitSky or tr.HitNoDraw do + tr = util.TraceLine { + start = tr.HitPos + Vector(0, 0, dir), + endpos = tr.HitPos + Vector(0, 0, dir * 10000), + collisiongroup = COLLISION_GROUP_WORLD, + } + + tries = tries + 1 + if tries > maxTries then break end + end + + local result = tr.HitPos and (tr.HitPos + tr.HitNormal) or Vector(x, y, 0) + return result.z +end diff --git a/octolib/addon/lua/octolib/modules/string.lua b/octolib/addon/lua/octolib/modules/string.lua new file mode 100644 index 0000000..9e03ea4 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/string.lua @@ -0,0 +1,193 @@ +octolib.string = octolib.string or {} + +local utfPattern = '[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*' +local letterPattern = '[%z%s\x41-\x5A\x61-\x7A\x7F-\x7F\xC2-\xF4][\x80-\xBF]*' + +function octolib.string.camel(str) + local res = '' + for w in string.gmatch(str, '[^%s]+') do + local f = true + for c in string.gmatch(w, utfPattern) do + if f then + res = res .. utf8.upper(c) + f = false + else + res = res .. utf8.lower(c) + end + end + res = res .. ' ' + end + + return res:Trim() +end + +function octolib.string.stripNonWord(str, ignore) + local res = '' + for c in string.gmatch(str, '[%z%s' .. (ignore or '') .. '\x7F-\x7F\xC2-\xF4][\x80-\xBF]*') do + res = res .. c + end + + return res +end + +function octolib.string.urlEncode(url) + if not isstring(url) then url = tostring(url) end + + return url:gsub('[^%w _~%.%-]', function(str) + local formated = ('%X'):format(string.byte(str)) + return '%' .. ((formated:len() == 1) and '0' or '') .. formated + end):gsub(' ', '+') +end + +function octolib.string.signed(number) + number = tonumber(number) or 0 + + return number > 0 + and '+' .. number + or tostring(number) +end + +function octolib.string.separateDigits(number, separator) + separator = separator or ',' + + local out = tostring(number) + while true do + out, k = string.gsub(out, '^(-?%d+)(%d%d%d)', '%1' .. separator .. '%2') + if k == 0 then break end + end + + return out +end + +function octolib.string.formatCount(num, single, plural, plural2) + num = math.abs(num) + if plural2 then + if num > 4 and num < 21 then + return plural2 + else + local lastDigit = num % 10 + if lastDigit == 1 then + return single + elseif lastDigit > 1 and lastDigit < 5 then + return plural + else + return plural2 + end + end + else + return num == 1 and single or plural + end +end + +function octolib.string.formatCountExt(num, rules) + for i, rule in ipairs(rules) do + local nextRule = rules[i+1] + if not nextRule or num < nextRule[1] then + local _num = math.floor(num / rule[1]) + return ('%s %s'):format(_num, octolib.string.formatCount(_num, unpack(rule, 2))) + end + end +end + +function octolib.string.isSteamID(str) + if not isstring(str) then return false end + return str:find('^STEAM_[0-5]:[01]:%d+$') ~= nil +end + +function octolib.string.isSteamID64(str) + if not isstring(str) then return false end + return str:gmatch('^7656119%d%d%d%d%d%d%d%d%d%d$') ~= nil +end + +function octolib.string.compressSteamID(str) + if not octolib.string.isSteamID(str) then return str end + str = str:Replace(':', '') + str = str:sub(8) + return str:sub(1, 1) .. pon.encode({tonumber(str:sub(2))}):sub(3, -3) +end + +function octolib.string.decompressSteamID(str) + if not isstring(str) or str:len() < 2 then return str end + if octolib.string.isSteamID(str) then return str end + local ok, num = pcall(function() return pon.decode('{Y' .. str:sub(2) .. ';}')[1] end) + if not (ok and num) then return str end + return ('STEAM_0:%s:%d'):format(str:sub(1, 1), num) +end + +local urlPattern = 'https?://[^%s/%$%.%?#].[^%s]*' +function octolib.string.isUrl(str) + return isstring(str) and str:gsub(urlPattern, '', 1) == '' +end + +function octolib.string.splitByUrl(str) + if not isstring(str) then return {} end + + local nonUrls = string.Explode(urlPattern, str, true) + local urls = {} + for v in string.gmatch(str, urlPattern) do + urls[#urls + 1] = v + end + + local united = {} + local nextUrl, nextPhrase = 1, 1 + if string.StartWith(str, nonUrls[1]) then + united[1] = nonUrls[1] + nextPhrase = 2 + end + + for i = nextPhrase, #nonUrls do + if urls[nextUrl] then + united[#united + 1] = {urls[nextUrl]} + nextUrl = nextUrl + 1 + end + united[#united + 1] = nonUrls[i] + end + for i = nextUrl, #urls do + united[#united + 1] = {urls[i]} + end + + return united +end + +function octolib.string.uuid() + local bytes = {} + for i = 1, 16 do bytes[i] = math.random(0, 0xFF) end + bytes[7] = bit.bor(0x40, bit.band(bytes[7], 0x0F)) + bytes[9] = bit.bor(0x80, bit.band(bytes[7], 0x3F)) + + return string.format('%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x', unpack(bytes)) +end + +local defaultAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' +function octolib.string.random(size, alphabet) + size = isnumber(size) and math.max(1, size) or 6 + alphabet = isstring(alphabet) and alphabet or defaultAlphabet + local isUtf8 = false + for i = 1, string.len(alphabet) do + if string.byte(alphabet, i) > 128 then + isUtf8 = true + break + end + end + + local result, alphabetLength = {} + if isUtf8 then + alphabetLength = utf8.len(alphabet) + for i = 1, size do + local pos = math.random(alphabetLength) + result[i] = utf8.sub(alphabet, pos, pos) + end + else + alphabetLength = #alphabet + for i = 1, size do + result[i] = alphabet[math.random(alphabetLength)] + end + end + + return table.concat(result) +end + +local STRING = getmetatable '' +function STRING:__mod(tbl) + return self:gsub('{(.-)}', tbl) +end diff --git a/octolib/addon/lua/octolib/modules/table.lua b/octolib/addon/lua/octolib/modules/table.lua new file mode 100644 index 0000000..35b5247 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/table.lua @@ -0,0 +1,219 @@ +octolib.table = octolib.table or {} + +function octolib.table.toKeyVal(tbl) + local out = {} + for k, v in pairs(tbl) do + out[#out + 1] = {k, v} + end + + return out +end + +function octolib.table.count(tbl, func) + local count = 0 + for k, v in pairs(tbl) do + if func(v, k) then count = count + 1 end + end + + return count +end + +function octolib.table.map(tbl, func) + local out = {} + for k, v in pairs(tbl) do + out[k] = func(v, k, tbl) + end + + return out +end + +function octolib.table.mapSequential(tbl, func) + local out = {} + local index = 1 + for i, v in pairs(tbl) do + out[index] = func(v, i, tbl) + index = index + 1 + end + + return out +end + +function octolib.table.strip(tbl, defaults, depth) + depth = depth or 1 + + if depth == 0 then + return tbl + end + + for k, default in pairs(defaults) do + if tbl[k] == default then + tbl[k] = nil + elseif istable(tbl[k]) then + if istable(default) and table.Count(octolib.table.diff(tbl[k], default)) == 0 then + octolib.table.strip(tbl[k], default, depth - 1) + else + tbl[k] = nil + end + end + end + + return tbl +end + +function octolib.table.reduce(tbl, func, initVal) + local a = initVal or tbl[table.GetKeys(tbl)[1]] + for k, v in pairs(tbl) do + a = func(a, v, k, tbl) + end + + return a +end + +function octolib.table.find(tbl, func) + for k, v in pairs(tbl) do + if func(v, k) then return v, k end + end +end + +function octolib.table.filter(tbl, func) + local found = {} + for k, v in pairs(tbl) do + if func(v, k) then found[k] = v end + end + + return found +end + +function octolib.table.filterMultiple(tbl, funcs, limit) + local out = {} + local foundAmount = 0 + for k, v in pairs(tbl) do + if octolib.array.every(funcs, function(func) + return func(v, k) + end) then + out[k] = v + foundAmount = foundAmount + 1 + if limit and foundAmount >= limit then break end + end + end + + return out +end + +local queryTypes = { + -- general + _equals = function(v, arg) + return v == arg + end, + _not = function(v, arg) + return v ~= arg + end, + _exists = function(v, arg) + return (arg and v ~= nil) or (not arg and v == nil) + end, + _custom = function(v, arg) + return arg(v) + end, + + -- numbers + _gt = function(v, arg) + return isnumber(v) and v > arg + end, + _gte = function(v, arg) + return isnumber(v) and v >= arg + end, + _lt = function(v, arg) + return isnumber(v) and v < arg + end, + _lte = function(v, arg) + return isnumber(v) and v <= arg + end, + _between = function(v, arg) + return isnumber(v) and (v > arg[1] and v < arg[2]) + end, + _notBetween = function(v, arg) + return isnumber(v) and (v < arg[1] or v > arg[2]) + end, + _betweenInc = function(v, arg) + return isnumber(v) and (v >= arg[1] and v <= arg[2]) + end, + _notBetweenInc = function(v, arg) + return isnumber(v) and (v <= arg[1] or v >= arg[2]) + end, + + -- strings + _find = function(v, arg) + return isstring(v) and v:find(arg, 1, true) + end, + _regex = function(v, arg) + return isstring(v) and v:find(arg) + end, + _starts = function(v, arg) + return isstring(v) and v:StartWith(arg) + end, + _ends = function(v, arg) + return isstring(v) and v:EndsWith(arg) + end, +} + +function octolib.table.precacheFilterFunctions(query) + local funcs = {} + for k, v in pairs(query) do + if istable(v) then + for queryType, queryVal in pairs(v) do + local func = queryTypes[queryType] or queryTypes._equals + funcs[#funcs + 1] = function(item) + return func(item[k], queryVal) + end + end + else + funcs[#funcs + 1] = function(item) + return queryTypes._equals(item[k], v) + end + end + end + + return funcs +end + +function octolib.table.filterQuery(tbl, query, limit) + return octolib.table.filterMultiple(tbl, octolib.table.precacheFilterFunctions(query), limit) +end + +function octolib.table.diff(t1, t2, changes) + changes = changes or {} + + for k, v in pairs(t1) do + if istable(v) then + if t2[k] ~= nil then + local innerChanges = {} + octolib.table.diff(v, t2[k], innerChanges) + if not table.IsEmpty(innerChanges) then + changes[k] = innerChanges + end + else + changes[k] = '--deleted--' + end + else + if t2[k] ~= nil then + if v ~= t2[k] then + changes[k] = t2[k] + end + else + changes[k] = '--deleted--' + end + end + end + + for k, v in pairs(t2) do + if t1[k] == nil then + if istable(v) then + changes[k] = table.Copy(v) + else + changes[k] = v + end + end + end + + return changes +end diff --git a/octolib/addon/lua/octolib/modules/time.lua b/octolib/addon/lua/octolib/modules/time.lua new file mode 100644 index 0000000..ae34737 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/time.lua @@ -0,0 +1,73 @@ +octolib.time = octolib.time or {} + +local rulesTime = { + {1, 'секунда', 'секунды', 'секунд'}, + {60, 'минута', 'минуты', 'минут'}, + {60 * 60, 'час', 'часа', 'часов'}, + {60 * 60 * 24, 'день', 'дня', 'дней'}, + {60 * 60 * 24 * 7, 'неделя', 'недели', 'недель'}, + {60 * 60 * 24 * 30, 'месяц', 'месяца', 'месяцев'}, + {60 * 60 * 24 * 365, 'год', 'года', 'лет'}, +} octolib.rulesTime = rulesTime + +function octolib.time.formatDuration(sec, rulesOverride) + return octolib.string.formatCountExt(sec, rulesOverride or rulesTime) +end + +local rulesTimeIn = table.Copy(rulesTime) +rulesTimeIn[1][2] = 'секунду' +rulesTimeIn[2][2] = 'минуту' +rulesTimeIn[5][2] = 'неделю' + +function octolib.time.formatIn(sec, short) + if short then + return octolib.string.formatCountExt(sec, rulesTimeIn) + else + return 'через ' .. octolib.string.formatCountExt(sec, rulesTimeIn) + end +end + +local multipliers = {} + +multipliers.seconds = 1 +multipliers.second = multipliers.seconds +multipliers.sec = multipliers.seconds +multipliers.s = multipliers.seconds + +multipliers.minutes = multipliers.seconds * 60 +multipliers.minute = multipliers.minutes +multipliers.min = multipliers.minutes +multipliers.m = multipliers.minutes + +multipliers.hours = multipliers.minutes * 60 +multipliers.hour = multipliers.hours +multipliers.hr = multipliers.hours +multipliers.h = multipliers.hours + +multipliers.days = multipliers.hours * 24 +multipliers.day = multipliers.days +multipliers.d = multipliers.days + +multipliers.weeks = multipliers.days * 7 +multipliers.week = multipliers.weeks +multipliers.w = multipliers.weeks + +multipliers.months = multipliers.days * 30 +multipliers.month = multipliers.months +multipliers.mo = multipliers.months + +multipliers.years = multipliers.days * 365 +multipliers.year = multipliers.years +multipliers.yr = multipliers.years +multipliers.y = multipliers.years + +function octolib.time.toSeconds(...) + local total = 0 + + local args = {...} + for i = 1, #args, 2 do + total = total + args[i] * multipliers[args[i + 1]] + end + + return total +end diff --git a/octolib/addon/lua/octolib/modules/tween.lua b/octolib/addon/lua/octolib/modules/tween.lua new file mode 100644 index 0000000..c1a3499 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/tween.lua @@ -0,0 +1,426 @@ +--[[ + Forked from https://github.com/kikito/tween.lua + Modified and adapted for Garry's Mod by Octothorp Team + + 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. +]] + +local tween = {} + +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 targetType, newPath + for k,targetValue in pairs(target) do + targetType, newPath = type(targetValue), copyTables({}, path) + table.insert(newPath, tostring(k)) + if targetType == 'number' then + assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") + elseif targetType == 'table' then + checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) + else + assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") + end + end +end + +local function checkNewParams(duration, subject, target, easing) + assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + local tsubject = type(subject) + assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject)) + assert(type(target)== 'table', "target must be a table. Was " .. tostring(target)) + assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing)) + checkSubjectAndTargetRecursively(subject, target) +end + +local function getEasingFunction(easing) + easing = easing or "linear" + if type(easing) == 'string' then + local name = easing + easing = tween.easing[name] + if type(easing) ~= 'function' 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 type(v) == 'table' 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 + +-- Tween methods + +local Tween = {} +local Tween_mt = {__index = Tween} + +function Tween:set(clock) + assert(type(clock) == 'number', "clock must be a positive number or 0") + + local changed = clock ~= self.clock + self.initial = self.initial or copyTables({}, self.target, self.subject) + self.clock = clock + + if self.clock <= 0 then + + self.clock = 0 + self.progress = 0 + copyTables(self.subject, self.initial) + + elseif self.clock >= self.duration then -- the tween has expired + + self.clock = self.duration + self.progress = 1 + copyTables(self.subject, self.target) + + else + + self.progress = self.clock / self.duration + performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) + + end + + if changed then self:onUpdate() end + if not self.finished and self.progress >= 1 then + self.finished = true + self:onFinish() + end + + return self.clock >= self.duration +end + +function Tween:reset() + return self:set(0) +end + +function Tween:update(dt) + assert(type(dt) == 'number', "dt must be a number") + return self:set(self.clock + dt) +end + + +local function createTween(duration, subject, target, easing) + easing = getEasingFunction(easing) + checkNewParams(duration, subject, target, easing) + return setmetatable({ + duration = duration, + subject = subject, + target = target, + easing = easing, + clock = 0, + progress = 0, + }, Tween_mt) +end + +-- +-- OCTOLIB +-- adding progress, pause, callbacks, helper functions and +-- dependence on game's Think cycle +-- + +octolib.tween = tween +octolib.tween.active = {} + +local activeTweens = octolib.tween.active + +hook.Add('Think', 'octolib.tween', function() + local ft = FrameTime() + for i = #activeTweens, 1, -1 do + local tw = activeTweens[i] + if not tw.paused then + tw:update(ft) + if tw.progress >= 1 then + table.remove(activeTweens, i) + end + end + end +end) + +function Tween:onUpdate() + -- to be overridden +end + +function Tween:onFinish() + -- to be overridden +end + +function Tween:setProgress(progress) + self:set(self.duration * progress) +end + +function Tween:toggle() + self.paused = not self.paused +end + +function Tween:resume() + self.paused = false +end + +function Tween:pause() + self.paused = true +end + +function Tween:remove() + table.RemoveByValue(activeTweens, self) +end + +function octolib.tween.create(time, subject, target, easing, onFinish, onUpdate) + local tw = createTween(time, subject, target, easing) + if onUpdate then tw.onUpdate = onUpdate end + if onFinish then tw.onFinish = onFinish end + activeTweens[#activeTweens + 1] = tw + + return tw +end diff --git a/octolib/addon/lua/octolib/modules/use/client.lua b/octolib/addon/lua/octolib/modules/use/client.lua new file mode 100644 index 0000000..3aaba5d --- /dev/null +++ b/octolib/addon/lua/octolib/modules/use/client.lua @@ -0,0 +1,48 @@ +if CFG.disabledModules.use then return end + +netstream.Hook('octolib.use', function(ent, data) + local opts = { } + if data then + for useID, useData in pairs(data) do + table.insert(opts, { useData[1] or L.action, useData[2], function() + if useData[3] then + local m = DermaMenu() + if #useData[3] > 0 then + for i, opt in ipairs(useData[3]) do + m:AddOption(opt[1], function() + netstream.Start('octolib.use', ent, useID, {unpack(opt, 2)}) + end) + end + + else + m:AddOption(L.empty) + end + m:Open() + m:Center() + else + netstream.Start('octolib.use', ent, useID) + end + end }) + end + end + + if #opts < 1 then + table.insert(opts, { 'Нет действий', 'octoteam/icons/emote_question.png', function() end }) + end + + octogui.circularMenu(opts) +end) + +octolib.use.classes = octolib.use.classes or {} +netstream.Hook('octolib.use.classes', function(classes) + + for i, class in ipairs(classes) do + octolib.use.classes[class] = true + end + +end) + +octolib.include.prefixed('/config/octolib-use', { + 'hooks', + '*', +}) diff --git a/octolib/addon/lua/octolib/modules/use/server.lua b/octolib/addon/lua/octolib/modules/use/server.lua new file mode 100644 index 0000000..faa2f64 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/use/server.lua @@ -0,0 +1,112 @@ +if CFG.disabledModules.use then return end + +local useDist = CFG.useDist +local useDistSqr = useDist * useDist + +local defaultActions = { + function(ply, ent) + return L.use, 'octoteam/icons/hand_point.png', function(ply, ent, args) + ent:Use(ply, ply, USE_TOGGLE, 1) + end + end, +} + +function octolib.use.getEntityUses(ent) + if not IsValid(ent) then return end + local class = ent:GetClass() + + local itemClass = octoinv and octoinv.itemClasses[class] + if itemClass and isfunction(octoinv.items[itemClass].pickup) then + local actions = table.Copy(CFG.use[class] or defaultActions) + + actions[#actions + 1] = function(ply, ent) + if octoinv.items[itemClass].pickup(ply, ent) == false then return end + return L.pickup, 'octoteam/icons/arrow_up2.png', function(ply, ent) + ply:PickupItem(ent) + end + end + return actions + end + + return CFG.use[class] +end + +local useClasses = {} +for class, _ in pairs(CFG.use) do table.insert(useClasses, class) end +hook.Add('PlayerFinishedLoading', 'octolib.use', function(ply) + netstream.Start(ply, 'octolib.use.classes', useClasses) +end) + +hook.Add('PlayerUse', 'octolib.use', function(ply, ent) + if octolib.use.getEntityUses(ent) and not ply.octolib_usenow then return false end +end) + +hook.Add('KeyPress', 'octolib.use', function(ply, key) + if key == IN_USE then + if hook.Run('octolib.canUse', ply) == false then return end + ply.octolib_uselast = CurTime() + end +end) + +hook.Add('KeyRelease', 'octolib.use', function(ply, key) + if key == IN_USE then + if hook.Run('octolib.canUse', ply) ~= false then + local tr = octolib.use.getTrace(ply) + local ent = tr.Entity + local uses = octolib.use.getEntityUses(ent) + if uses and ply.octolib_uselast and CurTime() - ply.octolib_uselast < 0.3 then + for _, use in pairs(uses) do + local name, _, action = use(ply, ent) + if name and action then + action(ply, ent) + break + end + end + end + end + ply.octolib_uselast = nil + end +end) + +local function tick(ply) + if not ply.octolib_uselast or CurTime() - ply.octolib_uselast <= 0.2 then return end + ply.octolib_uselast = nil + if hook.Run('octolib.canUse', ply) == false then return end + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == 'weapon_physgun' and ply:KeyDown(IN_ATTACK) then return end -- if player is rotating with physgun + + local tr = octolib.use.getTrace(ply) + local ent = tr.Entity + local uses = octolib.use.getEntityUses(ent) + if uses then + local actions = {} + for i, use in pairs(uses) do + local name, icon, _, args = use(ply, ent) + if name then + icon = icon or 'octoteam/icons/error.png' + actions[i] = { name, icon } + if args then actions[i][3] = args(ply, ent) end + end + end + netstream.Start(ply, 'octolib.use', ent, actions) + end + +end +hook.Add('PlayerTick', 'octolib.use', tick) +hook.Add('VehicleMove', 'octolib.use', tick) + +netstream.Hook('octolib.use', function(ply, ent, useID, args) + local uses = octolib.use.getEntityUses(ent) + if not uses or not uses[useID] then return end + + local plyPos = ply:GetShootPos() + if plyPos:DistToSqr(ent:NearestPoint(plyPos)) > useDistSqr then return end + + local _, _, action = uses[useID](ply, ent) + if action then action(ply, ent, args) end +end) + +octolib.include.prefixed('/config/octolib-use', { + 'hooks', + '*', +}) diff --git a/octolib/addon/lua/octolib/modules/use/shared.lua b/octolib/addon/lua/octolib/modules/use/shared.lua new file mode 100644 index 0000000..2e12404 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/use/shared.lua @@ -0,0 +1,15 @@ +if CFG.disabledModules.use then return end + +octolib.use = octolib.use or {} + +function octolib.use.getTrace(ply) + return ply:GetEyeTraceLimited(CFG.useDist) +end + +function octolib.use.check(ply, ent) + if not IsValid(ply) or not IsValid(ent) then return false end + + local tr = octolib.use.getTrace(ply) + + return tr.Entity == ent +end diff --git a/octolib/addon/lua/octolib/modules/vars/client.lua b/octolib/addon/lua/octolib/modules/vars/client.lua new file mode 100644 index 0000000..8393439 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/vars/client.lua @@ -0,0 +1,259 @@ +octolib.vars = octolib.vars or {} + +netstream.Hook('octolib.getVars', function(vars) + local res = {} + + if not istable(vars) then vars = { vars } end + for i, var in ipairs(vars) do + res[var] = octolib.vars.get(var) + end + + netstream.Start('octolib.getVars', res) +end) + +netstream.Hook('octolib.setVar', octolib.setVar) + +-- +-- some panel helpers +-- + +local hookFuncs = { + DComboBox = function(pnl, _var, hName) + hook.Add('octolib.setVar', hName, function(var, val) + if not IsValid(pnl) then + hook.Remove('octolib.setVar', hName) + return + end + + if var ~= _var then return end + for i, v in ipairs(pnl.Data) do + if v == val then + return pnl:ChooseOptionID(i) + end + end + end) + end, + + DColorMixer = function(pnl, _var, hName) + hook.Add('octolib.setVar', hName, function(var, val) + if not IsValid(pnl) then + hook.Remove('octolib.setVar', hName) + return + end + + if var == _var and val ~= pnl:GetColor() then pnl:SetColor(val) end + end) + end, + + DTextEntry = function(pnl, _var, hName) + hook.Add('octolib.setVar', hName, function(var, val) + if not IsValid(pnl) then + hook.Remove('octolib.setVar', hName) + return + end + + if var == _var and val ~= pnl:GetText() then pnl:SetText(val) end + end) + end, + + other = function(pnl, _var, hName) + hook.Add('octolib.setVar', hName, function(var, val) + if not IsValid(pnl) then + hook.Remove('octolib.setVar', hName) + return + end + + if var == _var and pnl:GetValue() ~= val then pnl:SetValue(val) end + end) + end, +} + +local nextPanelID = 1 +local function setupHooks(pnl, _var) + pnl.octoVarID = nextPanelID + nextPanelID = nextPanelID + 1 + + local hName = 'octolib.panel' .. pnl.octoVarID + local addHook = hookFuncs[pnl:GetTable().ThisClass] or hookFuncs.other + + addHook(pnl, _var, hName) + pnl.OnRemove = function() hook.Remove('octolib.setVar', hName) end +end + +function octolib.vars.slider(pnl, var, txt, min, max, prc) + local e = octolib.slider(pnl, txt, min, max, prc) + e:SetValue(tonumber(octolib.vars.get(var)) or min) + e.OnValueChanged = function(self, val) + octolib.vars.set(var, math.Round(val, prc)) + end + setupHooks(e, var) + + return e +end + +function octolib.vars.checkBox(pnl, var, txt) + local e = octolib.checkBox(pnl, txt) + e:SetValue(octolib.vars.get(var) or false) + e.OnChange = function(self, val) + octolib.vars.set(var, val) + end + setupHooks(e, var) + + return e +end + +function octolib.vars.textEntry(pnl, var, txt) + local e, t = octolib.textEntry(pnl, txt) + e:SetUpdateOnType(true) + e:SetValue(tostring(octolib.vars.get(var)) or '') + e.OnValueChange = function(self, val) + octolib.vars.set(var, val) + end + setupHooks(e, var) + + return e, t +end + +function octolib.vars.comboBox(pnl, var, txt, choices) + choices = choices or {} + local curVar = octolib.vars.get(var) + for i, v in ipairs(choices) do + v[3] = curVar == v[2] + end + + local e, t = octolib.comboBox(pnl, txt, choices) + e.OnSelect = function(self, i, name, val) + octolib.vars.set(var, val) + end + setupHooks(e, var) + + return e, t +end + +function octolib.vars.binder(pnl, var, txt, value) + local e, t = octolib.binder(pnl, txt, value) + e:SetValue(tonumber(octolib.vars.get(var)) or 0) + e.OnChange = function(self, val) + octolib.vars.set(var, tonumber(val)) + end + setupHooks(e, var) + + return e, t + +end + +function octolib.vars.colorPicker(pnl, var, txt, alpha, palette) + local e, t = octolib.colorPicker(pnl, txt, alpha, palette) + e:SetColor(octolib.vars.get(var) or Color(255,255,255)) + e.ValueChanged = function(self, val) + if octolib.vars.get(var) ~= val then + octolib.vars.set(var, val) + end + end + setupHooks(e, var) + + return e, t +end + +function octolib.vars.presetManager(pnl, path, vars) + local wrap = pnl.AddItem and pnl:AddItem('DPanel') or pnl:Add('DPanel') + wrap:DockMargin(10, 10, 10, 0) + wrap:Dock(TOP) + wrap:SetTall(20) + wrap:SetText('') + + local sel = wrap:Add('DComboBox') + sel:Dock(FILL) + + path = 'presets.' .. (path or '') + local function rebuild() + sel:Clear() + local data = octolib.vars.get(path) or {} + for _, v in ipairs(data) do + sel:AddChoice(v[1], v[2]) + end + end + rebuild() + + local iconDelete = wrap:Add('DImageButton') + iconDelete:Dock(RIGHT) + iconDelete:SetWide(20) + iconDelete:SetStretchToFit(false) + iconDelete:SetImage('octoteam/icons-16/cross.png') + iconDelete:AddHint('Удалить шаблон') + function iconDelete:DoClick() + Derma_Query('Ты точно хочешь удалить этот шаблон?', 'Удаление шаблона', 'Да', function() + local id = sel:GetSelectedID() + if not id or id < 1 then return end + local data = octolib.vars.get(path) or {} + table.remove(data, id) + octolib.vars.set(path, table.Copy(data)) + rebuild() + end, 'Нет') + end + iconDelete:SetVisible(false) + + local iconSave = wrap:Add('DImageButton') + iconSave:Dock(RIGHT) + iconSave:SetWide(20) + iconSave:SetStretchToFit(false) + iconSave:SetImage('octoteam/icons-16/file_save_as.png') + iconSave:AddHint('Обновить шаблон') + function iconSave:DoClick() + local id = sel:GetSelectedID() + if not id then return end + local data = octolib.vars.get(path) or {} + data = data[id] or {} + Derma_StringRequest('Обновление шаблона', 'Укажи новое название шаблона', data[1] or '', function(name) + name = string.Trim(name) + if name == '' then return end + + local data = {} + for _, v in ipairs(vars) do + data[v] = octolib.vars.get(v) + end + + local vars = octolib.vars.get(path) or {} + vars[id] = {name, data} + octolib.vars.set(path, table.Copy(vars)) + rebuild() + sel:ChooseOptionID(id) + + end, nil, 'Обновить', 'Отмена') + end + iconSave:SetVisible(false) + + local iconCreate = wrap:Add('DImageButton') + iconCreate:Dock(RIGHT) + iconCreate:SetWide(20) + iconCreate:SetStretchToFit(false) + iconCreate:SetImage('octoteam/icons-16/plus.png') + iconCreate:AddHint('Создать шаблон') + function iconCreate:DoClick() + Derma_StringRequest('Создание шаблона', 'Укажи название нового шаблона', '', function(name) + name = string.Trim(name) + if name == '' then return end + + local data = {} + for _, v in ipairs(vars) do + data[v] = octolib.vars.get(v) + end + + local vars = octolib.vars.get(path) or {} + vars[#vars + 1] = {name, data} + octolib.vars.set(path, table.Copy(vars)) + rebuild() + sel:ChooseOptionID(#vars) + + end, nil, 'Создать', 'Отмена') + end + + function sel:OnSelect(_, _, data) + for k, v in pairs(data or {}) do + octolib.vars.set(k, v) + end + + iconDelete:SetVisible(true) + iconSave:SetVisible(true) + end +end diff --git a/octolib/addon/lua/octolib/modules/vars/server.lua b/octolib/addon/lua/octolib/modules/vars/server.lua new file mode 100644 index 0000000..1e493b0 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/vars/server.lua @@ -0,0 +1,25 @@ +local pending = {} + +netstream.Hook('octolib.getVars', function(ply, vars) + local callback = pending[ply] + if not callback then return end + + callback(vars) + pending[ply] = nil +end) + +local ply = FindMetaTable 'Player' + +function ply:GetClientVar(vars, callback) + if self:IsBot() then + callback({}) + return + end + + pending[self] = callback + netstream.Start(self, 'octolib.getVars', vars) +end + +function ply:SetClientVar(var, val) + netstream.Start(self, 'octolib.setVar', var, val) +end diff --git a/octolib/addon/lua/octolib/modules/vars/shared.lua b/octolib/addon/lua/octolib/modules/vars/shared.lua new file mode 100644 index 0000000..116c66e --- /dev/null +++ b/octolib/addon/lua/octolib/modules/vars/shared.lua @@ -0,0 +1,40 @@ +octolib.vars = octolib.vars or {} + +local fname = 'octolib_vars.dat' +local function load() + local txt = file.Read(fname, 'DATA') or '[}' + octolib.vars = pon.decode(txt) or {} +end +pcall(load) + +local function save() + file.Write(fname, pon.encode(octolib.vars)) +end +local saveDebounced = octolib.func.debounce(save, 1) + +function octolib.vars.set(var, val, saveNow) + + if not istable(val) and octolib.vars[var] == val then return end + octolib.vars[var] = val + if saveNow then + save() + else + saveDebounced() + end + + hook.Run('octolib.setVar', var, val) + +end + +function octolib.vars.init(var, val) + + if octolib.vars[var] ~= nil then return end + octolib.vars.set(var, val) + +end + +function octolib.vars.get(var) + + return octolib.vars[var] + +end diff --git a/octolib/addon/lua/octolib/modules/whitelist/client.lua b/octolib/addon/lua/octolib/modules/whitelist/client.lua new file mode 100644 index 0000000..795f0e5 --- /dev/null +++ b/octolib/addon/lua/octolib/modules/whitelist/client.lua @@ -0,0 +1,62 @@ +local w +concommand.Add('dbg_whitelist', function() + + if IsValid(w) then w:Remove() return end + + w = vgui.Create 'DFrame' + w:SetSize(400, 600) + w:SetSizable(true) + w:SetTitle(L.manage_whitelist) + w:MakePopup() + w:Center() + + local b = w:Add 'DButton' + b:Dock(BOTTOM) + b:SetTall(25) + b:SetText(L.add_in_whitelist) + function b:DoClick() + Derma_StringRequest(L.add_in_whitelist2, L.print_steamid_hint, '', function(s) + Derma_StringRequest('Причина', 'Введи причину добавления в вайтлист', '', function(s2) + netstream.Start('whitelist.add', s, s2) + end, nil, L.add, L.cancel) + end, function() end, L.ok, L.cancel) + end + + local l = w:Add 'DListView' + l:Dock(FILL) + l:AddColumn('SteamID') + l:AddColumn('SteamID64') + l:AddColumn(L.name_octoinv) + l:AddColumn('Причина') + function l:OnRowRightClick(i, line) + if not line.sid then return end + local m = DermaMenu() + m:AddOption('Изменить причину', function() + Derma_StringRequest('Причина', 'Введи причину добавления в вайтлист', '', function(s) + netstream.Start('whitelist.add', line.sid, s) + end, nil, L.add, L.cancel) + end):SetImage('icon16/page_edit.png') + m:AddOption(L.delete, function() + netstream.Start('whitelist.remove', line.sid) + end):SetImage('icon16/delete.png') + m:Open() + end + w.l = l + + l:AddLine(L.loading) + netstream.Start('whitelist.get') + +end) + +netstream.Hook('whitelist.get', function(data) + + if not IsValid(w) then return end + + w.l:Clear() + for sid, reason in pairs(data) do + local line = w.l:AddLine(util.SteamIDFrom64(sid), sid, L.loading, reason) + line.sid = sid + steamworks.RequestPlayerInfo(sid, function(name) line:SetColumnText(3, name) end) + end + +end) diff --git a/octolib/addon/lua/octolib/modules/whitelist/server.lua b/octolib/addon/lua/octolib/modules/whitelist/server.lua new file mode 100644 index 0000000..dab7fdf --- /dev/null +++ b/octolib/addon/lua/octolib/modules/whitelist/server.lua @@ -0,0 +1,105 @@ +local cache = {} + +local function load() + + local f = file.Read 'dbg-whitelist.dat' + if not f then return end + + cache = pon.decode(f) or cache + +end + +local function save() + + file.Write('dbg-whitelist.dat', pon.encode(cache)) + +end + +local function send(ply) + + netstream.Start(ply, 'whitelist.get', cache) + +end + +local function output(user, msg) + if user:IsPlayer() then + user:ChatPrint(msg) + else + print(msg) + end +end + +local function add(id, ply, reason) + + if ply:IsPlayer() and not ply:IsSuperAdmin() then return end + if id:find('STEAM_') then id = util.SteamIDTo64(id) end + if id:len() ~= 17 then return end + + if string.Trim(reason or '') == '' then + return output(ply, 'Укажи причину добавления в вайтлист: dbg_wl_add ' .. id .. ' "причина"') + end + + if not cache[id] then + output(ply, util.SteamIDFrom64(id) .. L.add_whitelist_hint .. ': ' .. reason) + end + + cache[id] = reason + save() + send(ply) + +end + +local function remove(id, ply) + + if ply:IsPlayer() and not ply:IsSuperAdmin() then return end + if id:find('STEAM_') then id = util.SteamIDTo64(id) end + if id:len() ~= 17 then return end + + cache[id] = nil + save() + send(ply) + + output(ply, util.SteamIDFrom64(id) .. L.remove_whitelist_hint) + +end + +local function clear(ply) + if ply:IsPlayer() then return end + table.Empty(cache) + save() + print('Вайтлист очищен. Добавь сюда кого-нибудь, чтобы на сервер не могли зайти') +end + +local function sendList(ply) + if ply:IsPlayer() then return end + print('===') + for sid64, reason in pairs(cache) do + print(sid64, reason) + end + print('===') +end + +hook.Add('Initialize', 'dbg-whitelist', function() + + load() + +end) +load() + +hook.Add('CheckPassword', 'dbg-whitelist', function(sid) + + if table.Count(cache) > 0 and not cache[sid] then + print('Denied whitelist for ' .. sid) + return false, L.denied_whitelist_hint + end + +end) + +netstream.Hook('whitelist.get', function(ply) send(ply) end) +netstream.Hook('whitelist.add', function(ply, id, reason) add(id, ply, reason) end) +netstream.Hook('whitelist.remove', function(ply, id) remove(id, ply) end) +concommand.Add('dbg_wl_add', function(ply, cmd, args) add(args[1], ply, args[2]) end) +concommand.Add('dbg_wl_edit', function(ply, cmd, args) add(args[1], ply, args[2]) end) +concommand.Add('dbg_wl_list', function(ply, cmd, args) sendList(ply) end) +concommand.Add('dbg_wl_remove', function(ply, cmd, args) remove(args[1], ply) end) +concommand.Add('dbg_wl_clear', function(ply, cmd, args) clear(ply) end) \ No newline at end of file diff --git a/octolib/addon/lua/octolib/patches/cl_morematerials.lua b/octolib/addon/lua/octolib/patches/cl_morematerials.lua new file mode 100644 index 0000000..28e1ef7 --- /dev/null +++ b/octolib/addon/lua/octolib/patches/cl_morematerials.lua @@ -0,0 +1,262 @@ +list.Add('OverrideMaterials', 'models/XQM//Deg360') +list.Add('OverrideMaterials', 'models/XQM//LightLinesGB') +list.Add('OverrideMaterials', 'models/XQM//LightLinesRed') +list.Add('OverrideMaterials', 'models/XQM//SquaredMat') +list.Add('OverrideMaterials', 'models/XQM//WoodTexture_1') +list.Add('OverrideMaterials', 'models/airboat/airboat_blur02') +list.Add('OverrideMaterials', 'models/antlion/antlion_innards') +list.Add('OverrideMaterials', 'models/barnacle/roots') +list.Add('OverrideMaterials', 'models/combine_advisor/body9') +list.Add('OverrideMaterials', 'models/combine_advisor/mask') +list.Add('OverrideMaterials', 'models/combine_scanner/scanner_eye') +list.Add('OverrideMaterials', 'models/debug/debugwhite') +list.Add('OverrideMaterials', 'models/dog/eyeglass') +list.Add('OverrideMaterials', 'models/effects/slimebubble_sheet') +list.Add('OverrideMaterials', 'models/effects/splode1_sheet') +list.Add('OverrideMaterials', 'models/effects/splode_sheet') +list.Add('OverrideMaterials', 'models/gibs/metalgibs/metal_gibs') +list.Add('OverrideMaterials', 'models/gibs/woodgibs/woodgibs01') +list.Add('OverrideMaterials', 'models/gibs/woodgibs/woodgibs02') +list.Add('OverrideMaterials', 'models/gibs/woodgibs/woodgibs03') +list.Add('OverrideMaterials', 'models/player/player_chrome1') +list.Add('OverrideMaterials', 'models/props_animated_breakable/smokestack/brickwall002a') +list.Add('OverrideMaterials', 'models/props_building_details/courtyard_template001c_bars') +list.Add('OverrideMaterials', 'models/props_building_details/courtyard_template001c_bars') +list.Add('OverrideMaterials', 'models/props_buildings/destroyedbuilldingwall01a') +list.Add('OverrideMaterials', 'models/props_buildings/plasterwall021a') +list.Add('OverrideMaterials', 'models/props_c17/frostedglass_01a') +list.Add('OverrideMaterials', 'models/props_c17/furniturefabric001a') +list.Add('OverrideMaterials', 'models/props_c17/furniturefabric002a') +list.Add('OverrideMaterials', 'models/props_c17/furnituremetal001a') +list.Add('OverrideMaterials', 'models/props_c17/gate_door02a') +list.Add('OverrideMaterials', 'models/props_c17/metalladder001') +list.Add('OverrideMaterials', 'models/props_c17/metalladder002') +list.Add('OverrideMaterials', 'models/props_c17/metalladder003') +list.Add('OverrideMaterials', 'models/props_canal/canal_bridge_railing_01a') +list.Add('OverrideMaterials', 'models/props_canal/canal_bridge_railing_01b') +list.Add('OverrideMaterials', 'models/props_canal/canal_bridge_railing_01c') +list.Add('OverrideMaterials', 'models/props_canal/canalmap_sheet') +list.Add('OverrideMaterials', 'models/props_canal/coastmap_sheet') +list.Add('OverrideMaterials', 'models/props_canal/metalcrate001d') +list.Add('OverrideMaterials', 'models/props_canal/metalwall005b') +list.Add('OverrideMaterials', 'models/props_canal/rock_riverbed01a') +list.Add('OverrideMaterials', 'models/props_combine/citadel_cable') +list.Add('OverrideMaterials', 'models/props_combine/citadel_cable_b') +list.Add('OverrideMaterials', 'models/props_combine/com_shield001a') +list.Add('OverrideMaterials', 'models/props_combine/combine_interface_disp') +list.Add('OverrideMaterials', 'models/props_combine/combine_monitorbay_disp') +list.Add('OverrideMaterials', 'models/props_combine/metal_combinebridge001') +list.Add('OverrideMaterials', 'models/props_combine/pipes01') +list.Add('OverrideMaterials', 'models/props_combine/pipes03') +list.Add('OverrideMaterials', 'models/props_combine/prtl_sky_sheet') +list.Add('OverrideMaterials', 'models/props_debris/building_template010a') +list.Add('OverrideMaterials', 'models/props_debris/building_template022j') +list.Add('OverrideMaterials', 'models/props_debris/composite_debris') +list.Add('OverrideMaterials', 'models/props_debris/concretefloor013a') +list.Add('OverrideMaterials', 'models/props_debris/concretefloor020a') +list.Add('OverrideMaterials', 'models/props_debris/concretewall019a') +list.Add('OverrideMaterials', 'models/props_debris/metalwall001a') +list.Add('OverrideMaterials', 'models/props_debris/plasterceiling008a') +list.Add('OverrideMaterials', 'models/props_debris/plasterwall009d') +list.Add('OverrideMaterials', 'models/props_debris/plasterwall021a') +list.Add('OverrideMaterials', 'models/props_debris/plasterwall034a') +list.Add('OverrideMaterials', 'models/props_debris/plasterwall034d') +list.Add('OverrideMaterials', 'models/props_debris/plasterwall039c') +list.Add('OverrideMaterials', 'models/props_debris/plasterwall040c') +list.Add('OverrideMaterials', 'models/props_debris/tilefloor001c') +list.Add('OverrideMaterials', 'models/props_foliage/driftwood_01a') +list.Add('OverrideMaterials', 'models/props_foliage/oak_tree01') +list.Add('OverrideMaterials', 'models/props_foliage/tree_deciduous_01a_trunk') +list.Add('OverrideMaterials', 'models/props_interiors/metalfence007a') +list.Add('OverrideMaterials', 'models/props_junk/plasticcrate01a') +list.Add('OverrideMaterials', 'models/props_junk/plasticcrate01b') +list.Add('OverrideMaterials', 'models/props_junk/plasticcrate01c') +list.Add('OverrideMaterials', 'models/props_junk/plasticcrate01d') +list.Add('OverrideMaterials', 'models/props_junk/plasticcrate01e') +list.Add('OverrideMaterials', 'models/props_lab/Tank_Glass001') +list.Add('OverrideMaterials', 'models/props_lab/door_klab01') +list.Add('OverrideMaterials', 'models/props_lab/security_screens') +list.Add('OverrideMaterials', 'models/props_lab/security_screens2') +list.Add('OverrideMaterials', 'models/props_lab/warp_sheet') +list.Add('OverrideMaterials', 'models/props_pipes/GutterMetal01a') +list.Add('OverrideMaterials', 'models/props_pipes/destroyedpipes01a') +list.Add('OverrideMaterials', 'models/props_pipes/pipemetal001a') +list.Add('OverrideMaterials', 'models/props_pipes/pipeset_metal02') +list.Add('OverrideMaterials', 'models/props_pipes/pipesystem01a_skin1') +list.Add('OverrideMaterials', 'models/props_pipes/pipesystem01a_skin2') +list.Add('OverrideMaterials', 'models/props_vents/borealis_vent001') +list.Add('OverrideMaterials', 'models/props_vents/borealis_vent001b') +list.Add('OverrideMaterials', 'models/props_vents/borealis_vent001c') +list.Add('OverrideMaterials', 'models/props_wasteland/concretefloor010a') +list.Add('OverrideMaterials', 'models/props_wasteland/concretewall064b') +list.Add('OverrideMaterials', 'models/props_wasteland/concretewall066a') +list.Add('OverrideMaterials', 'models/props_wasteland/dirtwall001a') +list.Add('OverrideMaterials', 'models/props_wasteland/metal_tram001a') +list.Add('OverrideMaterials', 'models/props_wasteland/quarryobjects01') +list.Add('OverrideMaterials', 'models/props_wasteland/rockcliff02a') +list.Add('OverrideMaterials', 'models/props_wasteland/rockcliff02b') +list.Add('OverrideMaterials', 'models/props_wasteland/rockcliff02c') +list.Add('OverrideMaterials', 'models/props_wasteland/rockcliff04a') +list.Add('OverrideMaterials', 'models/props_wasteland/rockgranite02a') +list.Add('OverrideMaterials', 'models/props_wasteland/tugboat01') +list.Add('OverrideMaterials', 'models/props_wasteland/tugboat02') +list.Add('OverrideMaterials', 'models/props_wasteland/wood_fence01a') +list.Add('OverrideMaterials', 'models/props_wasteland/wood_fence01a_skin2') +list.Add('OverrideMaterials', 'models/shadertest/predator') +list.Add('OverrideMaterials', 'models/weapons/v_crossbow/rebar_glow') +list.Add('OverrideMaterials', 'models/weapons/v_crowbar/crowbar_cyl') +list.Add('OverrideMaterials', 'models/weapons/v_grenade/grenade body') +list.Add('OverrideMaterials', 'models/weapons/v_slam/new light1') +list.Add('OverrideMaterials', 'models/weapons/v_slam/new light2') +list.Add('OverrideMaterials', 'models/weapons/v_smg1/texture5') +list.Add('OverrideMaterials', 'models/XQM/BoxFull_diffuse') +list.Add('OverrideMaterials', 'models/XQM/CellShadedCamo_diffuse') +list.Add('OverrideMaterials', 'models/XQM/CinderBlock_Tex') +list.Add('OverrideMaterials', 'models/XQM/JetBody2TailPiece_diffuse') +list.Add('OverrideMaterials', 'models/XQM/PoleX1_diffuse') +list.Add('OverrideMaterials', 'models/XQM/Rails/gumball_1') +list.Add('OverrideMaterials', 'models/XQM/SquaredMatInverted') +list.Add('OverrideMaterials', 'models/XQM/WoodPlankTexture') +list.Add('OverrideMaterials', 'models/XQM/boxfull_diffuse') +list.Add('OverrideMaterials', 'models/dav0r/hoverball') +list.Add('OverrideMaterials', 'phoenix_storms/Fender_chrome') +list.Add('OverrideMaterials', 'phoenix_storms/Fender_white') +list.Add('OverrideMaterials', 'phoenix_storms/Fender_wood') +list.Add('OverrideMaterials', 'phoenix_storms/Future_vents') +list.Add('OverrideMaterials', 'phoenix_storms/FuturisticTrackRamp_1-2') +list.Add('OverrideMaterials', 'phoenix_storms/OfficeWindow_1-1') +list.Add('OverrideMaterials', 'phoenix_storms/Pro_gear_side') +list.Add('OverrideMaterials', 'phoenix_storms/black_brushes') +list.Add('OverrideMaterials', 'phoenix_storms/black_chrome') +list.Add('OverrideMaterials', 'phoenix_storms/blue_steel') +list.Add('OverrideMaterials', 'phoenix_storms/camera') +list.Add('OverrideMaterials', 'phoenix_storms/car_tire') +list.Add('OverrideMaterials', 'phoenix_storms/checkers_map') +list.Add('OverrideMaterials', 'phoenix_storms/cigar') +list.Add('OverrideMaterials', 'phoenix_storms/concrete0') +list.Add('OverrideMaterials', 'phoenix_storms/concrete1') +list.Add('OverrideMaterials', 'phoenix_storms/concrete2') +list.Add('OverrideMaterials', 'phoenix_storms/concrete3') +list.Add('OverrideMaterials', 'phoenix_storms/construct/concrete_barrier00') +list.Add('OverrideMaterials', 'phoenix_storms/construct/concrete_barrier2_00') +list.Add('OverrideMaterials', 'phoenix_storms/construct/concrete_pipe_00') +list.Add('OverrideMaterials', 'phoenix_storms/egg') +list.Add('OverrideMaterials', 'phoenix_storms/gear') +list.Add('OverrideMaterials', 'phoenix_storms/gear_top') +list.Add('OverrideMaterials', 'phoenix_storms/grey_chrome') +list.Add('OverrideMaterials', 'phoenix_storms/grey_steel') +list.Add('OverrideMaterials', 'phoenix_storms/heli') +list.Add('OverrideMaterials', 'phoenix_storms/indentTiles2') +list.Add('OverrideMaterials', 'phoenix_storms/iron_rails') +list.Add('OverrideMaterials', 'phoenix_storms/mat/mat_phx_carbonfiber') +list.Add('OverrideMaterials', 'phoenix_storms/mat/mat_phx_carbonfiber2') +list.Add('OverrideMaterials', 'phoenix_storms/mat/mat_phx_metallic') +list.Add('OverrideMaterials', 'phoenix_storms/mat/mat_phx_metallic2') +list.Add('OverrideMaterials', 'phoenix_storms/mat/mat_phx_plastic') +list.Add('OverrideMaterials', 'phoenix_storms/mat/mat_phx_plastic2') +list.Add('OverrideMaterials', 'phoenix_storms/metal_plate') +list.Add('OverrideMaterials', 'phoenix_storms/metal_wheel') +list.Add('OverrideMaterials', 'phoenix_storms/metalbox') +list.Add('OverrideMaterials', 'phoenix_storms/metalbox2') +list.Add('OverrideMaterials', 'phoenix_storms/metalfence004a') +list.Add('OverrideMaterials', 'phoenix_storms/middle') +list.Add('OverrideMaterials', 'phoenix_storms/mrref2') +list.Add('OverrideMaterials', 'phoenix_storms/output_jack') +list.Add('OverrideMaterials', 'phoenix_storms/pack2/chrome') +list.Add('OverrideMaterials', 'phoenix_storms/pack2/interior_sides') +list.Add('OverrideMaterials', 'phoenix_storms/pack2/train_floor') +list.Add('OverrideMaterials', 'phoenix_storms/potato') +list.Add('OverrideMaterials', 'phoenix_storms/pro_gear_top2') +list.Add('OverrideMaterials', 'phoenix_storms/ps_grass') +list.Add('OverrideMaterials', 'phoenix_storms/road') +list.Add('OverrideMaterials', 'phoenix_storms/roadside') +list.Add('OverrideMaterials', 'phoenix_storms/side') +list.Add('OverrideMaterials', 'phoenix_storms/simplyMetallic1') +list.Add('OverrideMaterials', 'phoenix_storms/simplyMetallic2') +list.Add('OverrideMaterials', 'phoenix_storms/smallwheel') +list.Add('OverrideMaterials', 'phoenix_storms/spheremappy') +list.Add('OverrideMaterials', 'phoenix_storms/t_light') +list.Add('OverrideMaterials', 'phoenix_storms/thruster') +list.Add('OverrideMaterials', 'phoenix_storms/tiles2') +list.Add('OverrideMaterials', 'phoenix_storms/top') +list.Add('OverrideMaterials', 'phoenix_storms/torpedo') +list.Add('OverrideMaterials', 'phoenix_storms/trains/track_beamside') +list.Add('OverrideMaterials', 'phoenix_storms/trains/track_beamtop') +list.Add('OverrideMaterials', 'phoenix_storms/trains/track_plate') +list.Add('OverrideMaterials', 'phoenix_storms/trains/track_plateside') +list.Add('OverrideMaterials', 'phoenix_storms/white_brushes') +list.Add('OverrideMaterials', 'phoenix_storms/white_fps') +list.Add('OverrideMaterials', 'phoenix_storms/window') +list.Add('OverrideMaterials', 'phoenix_storms/wire/pcb_blue') +list.Add('OverrideMaterials', 'phoenix_storms/wire/pcb_green') +list.Add('OverrideMaterials', 'phoenix_storms/wire/pcb_red') +list.Add('OverrideMaterials', 'phoenix_storms/wood_dome') +list.Add('OverrideMaterials', 'phoenix_storms/wood_side') + +function engine.IsMounted(g) + for k,v in pairs(engine.GetGames()) do + if (' cstrike') then + return true + end + end +end + +if IsMounted('cstrike') and (engine.IsMounted('cstrike')) then + list.Add('OverrideMaterials', 'models/cs_havana/wndb') + list.Add('OverrideMaterials', 'models/cs_havana/wndd') + list.Add('OverrideMaterials', 'models/cs_italy/light_orange') + list.Add('OverrideMaterials', 'models/cs_italy/plaster') + list.Add('OverrideMaterials', 'models/cs_italy/pwtrim2') + list.Add('OverrideMaterials', 'models/de_cbble/wndarch') + list.Add('OverrideMaterials', 'models/de_chateau/ch_arch_b1') + list.Add('OverrideMaterials', 'models/pi_window/plaster') + list.Add('OverrideMaterials', 'models/pi_window/trim128') + list.Add('OverrideMaterials', 'models/props/cs_assault/dollar') + list.Add('OverrideMaterials', 'models/props/cs_assault/fireescapefloor') + list.Add('OverrideMaterials', 'models/props/cs_assault/metal_stairs1') + list.Add('OverrideMaterials', 'models/props/cs_assault/moneywrap') + list.Add('OverrideMaterials', 'models/props/cs_assault/moneywrap02') + list.Add('OverrideMaterials', 'models/props/cs_assault/moneytop') + list.Add('OverrideMaterials', 'models/props/cs_assault/pylon') + list.Add('OverrideMaterials', 'models/props/CS_militia/boulder01') + list.Add('OverrideMaterials', 'models/props/CS_militia/milceil001') + list.Add('OverrideMaterials', 'models/props/CS_militia/militiarock') + list.Add('OverrideMaterials', 'models/props/CS_militia/militiarockb') + list.Add('OverrideMaterials', 'models/props/CS_militia/milwall006') + list.Add('OverrideMaterials', 'models/props/CS_militia/rocks01') + list.Add('OverrideMaterials', 'models/props/CS_militia/roofbeams01') + list.Add('OverrideMaterials', 'models/props/CS_militia/roofbeams02') + list.Add('OverrideMaterials', 'models/props/CS_militia/roofbeams03') + list.Add('OverrideMaterials', 'models/props/CS_militia/RoofEdges') + list.Add('OverrideMaterials', 'models/props/cs_office/clouds') + list.Add('OverrideMaterials', 'models/props/cs_office/file_cabinet2') + list.Add('OverrideMaterials', 'models/props/cs_office/file_cabinet3') + list.Add('OverrideMaterials', 'models/props/cs_office/screen') + list.Add('OverrideMaterials', 'models/props/cs_office/snowmana') + list.Add('OverrideMaterials', 'models/props/de_inferno/de_inferno_boulder_03') + list.Add('OverrideMaterials', 'models/props/de_inferno/infflra') + list.Add('OverrideMaterials', 'models/props/de_inferno/infflrd') + list.Add('OverrideMaterials', 'models/props/de_inferno/inftowertop') + list.Add('OverrideMaterials', 'models/props/de_inferno/offwndwb_break') + list.Add('OverrideMaterials', 'models/props/de_inferno/roofbits') + list.Add('OverrideMaterials', 'models/props/de_inferno/tileroof01') + list.Add('OverrideMaterials', 'models/props/de_inferno/woodfloor008a') + list.Add('OverrideMaterials', 'models/props/de_nuke/nukconcretewalla') + list.Add('OverrideMaterials', 'models/props/de_nuke/nukecardboard') + list.Add('OverrideMaterials', 'models/props/de_nuke/pipeset_metal') +end + +timer.Simple(0, function() + local mats = list.GetForEdit('OverrideMaterials') + local cleaner = {} + for i, mat in pairs(mats) do + cleaner[mat] = true + mats[i] = nil + end + local i = 1 + for mat in pairs(cleaner) do + mats[i] = mat + i = i + 1 + end + table.sort(mats) +end) diff --git a/octolib/addon/lua/octolib/patches/cl_physpropclientside.lua b/octolib/addon/lua/octolib/patches/cl_physpropclientside.lua new file mode 100644 index 0000000..8c018eb --- /dev/null +++ b/octolib/addon/lua/octolib/patches/cl_physpropclientside.lua @@ -0,0 +1,13 @@ +hook.Add('OnEntityCreated', 'octolib.physpropclientside', function(ent) + + if ent:GetClass() == 'class C_PhysPropClientside' then + timer.Simple(10, function() + for _, v in ipairs(ents.GetAll()) do + if v:GetClass() == 'class C_PhysPropClientside' then + v:Remove() + end + end + end) + end + +end) diff --git a/octolib/addon/lua/octolib/patches/cl_setconvars.lua b/octolib/addon/lua/octolib/patches/cl_setconvars.lua new file mode 100644 index 0000000..1dda905 --- /dev/null +++ b/octolib/addon/lua/octolib/patches/cl_setconvars.lua @@ -0,0 +1,17 @@ +hook.Add('PlayerFinishedLoading', 'octolib.setconvars', function() + RunConsoleCommand('rope_smooth', '0') + RunConsoleCommand('Rope_wind_dist', '0') + RunConsoleCommand('Rope_shake', '0') + RunConsoleCommand('violence_ablood', '1') + RunConsoleCommand('mat_queue_mode', '-1') + RunConsoleCommand('cl_threaded_bone_setup', '1') + RunConsoleCommand('gmod_mcore_test', '1') + RunConsoleCommand('cl_threaded_client_leaf_system', '0') + RunConsoleCommand('r_queued_ropes', '1') + RunConsoleCommand('r_threaded_client_shadow_manager', '1') + RunConsoleCommand('r_fastzreject', '-1') + RunConsoleCommand('Cl_ejectbrass', '0') + RunConsoleCommand('Muzzleflash_light', '0') + RunConsoleCommand('cl_wpn_sway_interp', '0') + RunConsoleCommand('in_usekeyboardsampletime', '0') +end) diff --git a/octolib/addon/lua/octolib/patches/cl_stereoscopy.lua b/octolib/addon/lua/octolib/patches/cl_stereoscopy.lua new file mode 100644 index 0000000..11ac19b --- /dev/null +++ b/octolib/addon/lua/octolib/patches/cl_stereoscopy.lua @@ -0,0 +1,78 @@ + +--[[--------------------------------------------------------- + Register the convars that will control this effect +-----------------------------------------------------------]] +local pp_stereoscopy = CreateClientConVar( "pp_stereoscopy", "0", false, false ) +local pp_stereoscopy_size = CreateClientConVar( "pp_stereoscopy_size", "6", true, false ) +cvars.AddChangeCallback("pp_stereoscopy_size", function(cv, old, new) + if not octolib.math.inRange(tonumber(new), 0, 10) then + pp_stereoscopy_size:SetFloat(6) + end +end, "octolib.fix") + +--[[--------------------------------------------------------- + Can be called from engine or hooks using bloom.Draw +-----------------------------------------------------------]] +function RenderStereoscopy( ViewOrigin, ViewAngles ) + + render.Clear( 0, 0, 0, 255 ) + + local w = ScrW() / 2.2 + local h = ScrH() / 2.2 + + local Right = ViewAngles:Right() * math.Clamp( pp_stereoscopy_size:GetFloat(), 0, 10 ) + + local view = {} + + view.y = ScrH() / 2 - h / 2 + view.w = w + view.h = h + view.angles = ViewAngles + + -- Left + view.x = ScrW() / 2 - w - 10 + view.origin = ViewOrigin + Right + render.RenderView( view ) + + -- Right + view.x = ScrW() / 2 + 10 + view.origin = ViewOrigin - Right + render.RenderView( view ) + +end + +--[[--------------------------------------------------------- + The function to draw the bloom (called from the hook) +-----------------------------------------------------------]] +hook.Add( "RenderScene", "RenderStereoscopy", function( ViewOrigin, ViewAngles, ViewFOV ) + + if ( !pp_stereoscopy:GetBool() ) then return end + + RenderStereoscopy( ViewOrigin, ViewAngles ) + + -- Return true to override drawing the scene + return true + +end ) + +list.Set( "PostProcess", "#stereoscopy_pp", { + + icon = "gui/postprocess/stereoscopy.png", + convar = "pp_stereoscopy", + category = "#effects_pp", + + cpanel = function( CPanel ) + + CPanel:AddControl( "Header", { Description = "#stereoscopy_pp.desc" } ) + CPanel:AddControl( "CheckBox", { Label = "#stereoscopy_pp.enable", Command = "pp_stereoscopy" } ) + + local params = { Options = {}, CVars = {}, MenuButton = "1", Folder = "stereoscopy" } + params.Options[ "#preset.default" ] = { pp_stereoscopy_size = "6" } + params.CVars = table.GetKeys( params.Options[ "#preset.default" ] ) + CPanel:AddControl( "ComboBox", params ) + + CPanel:AddControl( "Slider", { Label = "#stereoscopy_pp.size", Command = "pp_stereoscopy_size", Type = "Float", Min = "0", Max = "10" } ) + + end + +} ) diff --git a/octolib/addon/lua/octolib/patches/sh_finishedloading.lua b/octolib/addon/lua/octolib/patches/sh_finishedloading.lua new file mode 100644 index 0000000..3370503 --- /dev/null +++ b/octolib/addon/lua/octolib/patches/sh_finishedloading.lua @@ -0,0 +1,33 @@ +if CLIENT then + hook.Add('Think', 'octolib.finishedLoading', function() + hook.Remove('Think', 'octolib.finishedLoading') + + netstream.Start('octolib.finishedLoading') + hook.Run('PlayerFinishedLoading') + end) +end + +if SERVER then + local playersFinished = {} + + netstream.Hook('octolib.finishedLoading', function(ply) + if playersFinished[ply] then return end + + hook.Run('PlayerFinishedLoading', ply) + playersFinished[ply] = true + end) + + hook.Add('PlayerDisconnected', 'octolib.finishedLoading', function(ply) + playersFinished[ply] = nil + end) + + -- simulate hook for bots (mainly for automated tests) + hook.Add('PlayerInitialSpawn', 'octolib.finishedLoading', function(ply) + if ply:IsBot() then + timer.Simple(1, function() + hook.Run('PlayerFinishedLoading', ply) + ply.FinishedLoading = true + end) + end + end) +end diff --git a/octolib/addon/lua/octolib/patches/sh_sendlua.lua b/octolib/addon/lua/octolib/patches/sh_sendlua.lua new file mode 100644 index 0000000..7a94665 --- /dev/null +++ b/octolib/addon/lua/octolib/patches/sh_sendlua.lua @@ -0,0 +1,17 @@ +if CLIENT then + netstream.Hook('sendLua', function(lua) + RunString(lua) + end) +end + +if SERVER then + local pmeta = FindMetaTable 'Player' + + function pmeta:SendLua(lua) + netstream.Start(self, 'sendLua', lua) + end + + function BroadcastLua(lua) + netstream.Start(nil, 'sendLua', lua) + end +end diff --git a/octolib/addon/lua/octolib/patches/sh_vehiclechanged.lua b/octolib/addon/lua/octolib/patches/sh_vehiclechanged.lua new file mode 100644 index 0000000..2976ddb --- /dev/null +++ b/octolib/addon/lua/octolib/patches/sh_vehiclechanged.lua @@ -0,0 +1,28 @@ +if CLIENT then + local lastVehicle + + hook.Add('Think', 'octolib.vehicleChanged', function() + local ply = LocalPlayer() + local currentVehicle = ply:GetVehicle() + + if currentVehicle ~= lastVehicle then + hook.Run('VehicleChanged', ply, currentVehicle, lastVehicle) + lastVehicle = currentVehicle + end + end) + + netstream.Hook('vehicleChanged', function(ply, new, old) + if ply == LocalPlayer() then return end -- detected by client in Think hook above + hook.Run('VehicleChanged', ply, new, old) + end) +end + +if SERVER then + hook.Add('PlayerEnteredVehicle', 'octolib.vehicleChanged', function(ply, veh, role) + netstream.Start(nil, 'vehicleChanged', ply, veh, nil) + end) + + hook.Add('PlayerLeaveVehicle', 'octolib.vehicleChanged', function(ply, veh) + netstream.Start(nil, 'vehicleChanged', ply, nil, veh) + end) +end diff --git a/octolib/addon/lua/octolib/patches/sh_widgets.lua b/octolib/addon/lua/octolib/patches/sh_widgets.lua new file mode 100644 index 0000000..23a3ce2 --- /dev/null +++ b/octolib/addon/lua/octolib/patches/sh_widgets.lua @@ -0,0 +1,5 @@ +hook.Add('PreGamemodeLoaded', 'octolib.widgets', function() + widgets.PlayerTick = nil + hook.Remove( 'PlayerTick', 'TickWidgets' ) + hook.Remove( 'PostDrawEffects', 'RenderWidgets' ) +end) diff --git a/octolib/addon/lua/octolib/tests/database.lua b/octolib/addon/lua/octolib/tests/database.lua new file mode 100644 index 0000000..92ee94c --- /dev/null +++ b/octolib/addon/lua/octolib/tests/database.lua @@ -0,0 +1,20 @@ +octolib.registerTests({ + name = 'Basic database usage', + run = function(finish) + octolib.func.chain({ + function(done) + octolib.db:RunQuery('drop table if exists tester') + octolib.db:RunQuery('create table if not exists tester(id varchar(32))', done) + end, + function(done, q, st, data) + octolib.db:PrepareQuery('insert into tester(id) values(?)', {'it works'}, done) + end, + function(done) + octolib.db:RunQuery('select * from tester', done) + end, + function(done, q, st, data) + finish(data[1].id ~= 'it works' and 'Data mismatch') + end, + }) + end, +}, 'octolib') diff --git a/octolib/addon/lua/octolib/vgui/dmarkup.lua b/octolib/addon/lua/octolib/vgui/dmarkup.lua new file mode 100644 index 0000000..7b51210 --- /dev/null +++ b/octolib/addon/lua/octolib/vgui/dmarkup.lua @@ -0,0 +1,40 @@ +local PANEL = {} + +function PANEL:Init() + + self:SetText('Hello World!') + +end + +function PANEL:SetText(text) + + self.oldW = self:GetWide() + self.markupContent = text + + self:UpdateMarkup() + +end + +function PANEL:PerformLayout(w, h) + + if w == self.oldW then return end + self.oldW = w + + self:UpdateMarkup() + +end + +function PANEL:UpdateMarkup() + + self.markup = markup.Parse(self.markupContent, self:GetWide()) + self:SetTall(self.markup:GetHeight()) + +end + +function PANEL:Paint(w, h) + + self.markup:Draw(0, 0, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + +end + +vgui.Register('DMarkup', PANEL, 'DPanel') diff --git a/octolib/addon/lua/octolib/vgui/dnumslider.lua b/octolib/addon/lua/octolib/vgui/dnumslider.lua new file mode 100644 index 0000000..63a3270 --- /dev/null +++ b/octolib/addon/lua/octolib/vgui/dnumslider.lua @@ -0,0 +1,284 @@ + +local PANEL = {} + +AccessorFunc( PANEL, "m_fDefaultValue", "DefaultValue" ) + +function PANEL:Init() + + self.TextArea = self:Add( "DTextEntry" ) + self.TextArea:Dock( RIGHT ) + self.TextArea:SetPaintBackground( false ) + self.TextArea:SetWide( 45 ) + self.TextArea:SetNumeric( true ) + self.TextArea:SetUpdateOnType( true ) + self.TextArea.OnValueChange = function( textentry, val ) + local cPos = self.TextArea:GetCaretPos() + local clamped = math.Clamp( tonumber( val ) || 0, self:GetMin(), self:GetMax() ) + if clamped != val then + self.TextArea:SetText( clamped ) + self.TextArea:SetCaretPos( cPos ) + end + self:SetValue( clamped ) + end + -- Causes automatic clamp to min/max, disabled for now. TODO: Enforce this with a setter/getter? + --self.TextArea.OnEnter = function( textarea, val ) textarea:SetText( self.Scratch:GetTextValue() ) end -- Update the text + + self.Slider = self:Add( "DSlider", self ) + self.Slider:SetLockY( 0.5 ) + self.Slider.TranslateValues = function( slider, x, y ) return self:TranslateSliderValues( x, y ) end + self.Slider:SetTrapInside( true ) + self.Slider:Dock( FILL ) + self.Slider:SetHeight( 16 ) + self.Slider.Knob.OnMousePressed = function( panel, mcode ) + if ( mcode == MOUSE_MIDDLE ) then + self:ResetToDefaultValue() + return + end + self.Slider:OnMousePressed( mcode ) + end + Derma_Hook( self.Slider, "Paint", "Paint", "NumSlider" ) + + self.Label = vgui.Create ( "DLabel", self ) + self.Label:Dock( LEFT ) + self.Label:SetMouseInputEnabled( true ) + + self.Scratch = self.Label:Add( "DNumberScratch" ) + self.Scratch:SetImageVisible( false ) + self.Scratch:Dock( FILL ) + self.Scratch.OnValueChanged = function() self:ValueChanged( self.Scratch:GetFloatValue() ) end + + self:SetTall( 32 ) + + self:SetMin( 0 ) + self:SetMax( 1 ) + self:SetDecimals( 2 ) + self:SetText( "" ) + self:SetValue( 0.5 ) + + -- + -- You really shouldn't be messing with the internals of these controls from outside.. + -- .. but if you are, this might stop your code from fucking us both. + -- + self.Wang = self.Scratch + +end + +function PANEL:SetMinMax( min, max ) + self.Scratch:SetMin( tonumber( min ) ) + self.Scratch:SetMax( tonumber( max ) ) + self:UpdateNotches() +end + +function PANEL:SetDark( b ) + self.Label:SetDark( b ) +end + +function PANEL:GetMin() + return self.Scratch:GetMin() +end + +function PANEL:GetMax() + return self.Scratch:GetMax() +end + +function PANEL:GetRange() + return self:GetMax() - self:GetMin() +end + +function PANEL:ResetToDefaultValue() + if ( !self:GetDefaultValue() ) then return end + self:SetValue( self:GetDefaultValue() ) +end + +function PANEL:SetMin( min ) + + if ( !min ) then min = 0 end + + self.Scratch:SetMin( tonumber( min ) ) + self:UpdateNotches() + +end + +function PANEL:SetMax( max ) + + if ( !max ) then max = 0 end + + self.Scratch:SetMax( tonumber( max ) ) + self:UpdateNotches() + +end + +function PANEL:SetValue( val ) + + val = math.Clamp( tonumber( val ) || 0, self:GetMin(), self:GetMax() ) + + if ( self:GetValue() == val ) then return end + + self.Scratch:SetValue( val ) -- This will also call ValueChanged + + self:ValueChanged( self:GetValue() ) -- In most cases this will cause double execution of OnValueChanged + +end + +function PANEL:GetValue() + return self.Scratch:GetFloatValue() +end + +function PANEL:SetDecimals( d ) + self.Scratch:SetDecimals( d ) + self:UpdateNotches() + self:ValueChanged( self:GetValue() ) -- Update the text +end + +function PANEL:GetDecimals() + return self.Scratch:GetDecimals() +end + +-- +-- Are we currently changing the value? +-- +function PANEL:IsEditing() + + return self.Scratch:IsEditing() || self.TextArea:IsEditing() || self.Slider:IsEditing() + +end + +function PANEL:IsHovered() + + return self.Scratch:IsHovered() || self.TextArea:IsHovered() || self.Slider:IsHovered() || vgui.GetHoveredPanel() == self + +end + +function PANEL:PerformLayout() + + self.Label:SetWide( self:GetWide() / 2.4 ) + +end + +function PANEL:SetConVar( cvar ) + self.Scratch:SetConVar( cvar ) + self.TextArea:SetConVar( cvar ) +end + +function PANEL:SetText( text ) + self.Label:SetText( text ) +end + +function PANEL:GetText() + return self.Label:GetText() +end + +function PANEL:ValueChanged( val ) + + val = math.Clamp( tonumber( val ) || 0, self:GetMin(), self:GetMax() ) + + if ( self.TextArea != vgui.GetKeyboardFocus() ) then + self.TextArea:SetValue( self.Scratch:GetTextValue() ) + end + + self.Slider:SetSlideX( self.Scratch:GetFraction( val ) ) + + self:OnValueChanged( val ) + +end + +function PANEL:OnValueChanged( val ) + + -- For override + +end + +function PANEL:TranslateSliderValues( x, y ) + + self:SetValue( self.Scratch:GetMin() + ( x * self.Scratch:GetRange() ) ) + + return self.Scratch:GetFraction(), y + +end + +function PANEL:GetTextArea() + + return self.TextArea + +end + +function PANEL:UpdateNotches() + + local range = self:GetRange() + self.Slider:SetNotches( nil ) + + if ( range < self:GetWide() / 4 ) then + return self.Slider:SetNotches( range ) + else + self.Slider:SetNotches( self:GetWide() / 4 ) + end + +end + +function PANEL:SetEnabled( b ) + self.TextArea:SetEnabled( b ) + self.Slider:SetEnabled( b ) + self.Scratch:SetEnabled( b ) + FindMetaTable( "Panel" ).SetEnabled( self, b ) -- There has to be a better way! +end + +function PANEL:GenerateExample( ClassName, PropertySheet, Width, Height ) + + local ctrl = vgui.Create( ClassName ) + ctrl:SetWide( 200 ) + ctrl:SetMin( 1 ) + ctrl:SetMax( 10 ) + ctrl:SetText( "Example Slider!" ) + ctrl:SetDecimals( 0 ) + + PropertySheet:AddSheet( ClassName, ctrl, nil, true, true ) + +end + +derma.DefineControl( "DNumSlider", "Menu Option Line", table.Copy( PANEL ), "Panel" ) + +-- No example for this fella +PANEL.GenerateExample = nil + +function PANEL:PostMessage( name, _, val ) + + if ( name == "SetInteger" ) then + if ( val == "1" ) then + self:SetDecimals( 0 ) + else + self:SetDecimals( 2 ) + end + end + + if ( name == "SetLower" ) then + self:SetMin( tonumber( val ) ) + end + + if ( name == "SetHigher" ) then + self:SetMax( tonumber( val ) ) + end + + if ( name == "SetValue" ) then + self:SetValue( tonumber( val ) ) + end + +end + +function PANEL:PerformLayout() + + self.Scratch:SetVisible( false ) + self.Label:SetVisible( false ) + + self.Slider:StretchToParent( 0, 0, 0, 0 ) + self.Slider:SetSlideX( self.Scratch:GetFraction() ) + +end + +function PANEL:SetActionFunction( func ) + + self.OnValueChanged = function( self, val ) func( self, "SliderMoved", val, 0 ) end + +end + +-- Compat +derma.DefineControl( "Slider", "Backwards Compatibility", PANEL, "Panel" ) diff --git a/octolib/addon/lua/octolib/vgui/dprogresslabel.lua b/octolib/addon/lua/octolib/vgui/dprogresslabel.lua new file mode 100644 index 0000000..3f22972 --- /dev/null +++ b/octolib/addon/lua/octolib/vgui/dprogresslabel.lua @@ -0,0 +1,42 @@ +local PANEL = {} + +function PANEL:Init() + + self.label = '' + self.font = 'DermaDefault' + self.BasePaint = vgui.GetControlTable('DProgress').Paint + +end + +function PANEL:SetText(txt) + + self.label = tostring(txt) + +end + +function PANEL:SetFont(txt) + + self.font = tostring(txt) + +end + +function PANEL:AttachToEdge(detach) + self.edge = not detach +end + +function PANEL:Paint(w, h) + self:BasePaint(w, h) + if string.Trim(self.label) == '' then return end + local x, align + if not self.edge then + x, align = w / 2, TEXT_ALIGN_CENTER + else + x, align = w * self:GetFraction() - 5, TEXT_ALIGN_RIGHT + surface.SetFont(self.font) + local tw = surface.GetTextSize(self.label) + x = math.max(x, tw + 7) + end + draw.SimpleText(self.label, self.font, x, h / 2, color_white, align, TEXT_ALIGN_CENTER) +end + +vgui.Register('DProgressLabel', PANEL, 'DProgress') diff --git a/octolib/addon/lua/octolib/vgui/dproperties.lua b/octolib/addon/lua/octolib/vgui/dproperties.lua new file mode 100644 index 0000000..7a36ceb --- /dev/null +++ b/octolib/addon/lua/octolib/vgui/dproperties.lua @@ -0,0 +1,246 @@ +local cols = CFG.skinColors + +-- +-- The row - contained in a category. +-- +local tblRow = vgui.RegisterTable( { + + Init = function( self ) + + self:Dock( TOP ) + + self.Label = self:Add( "DLabel" ) + self.Label:Dock( LEFT ) + self.Label:DockMargin( 4, 2, 2, 2 ) + + local Skin = self:GetSkin() + self.Label:SetTextColor(Skin.Colours.Properties.Label_Normal) + + self.Container = self:Add( "Panel" ) + self.Container:Dock( FILL ) + + end, + + PerformLayout = function( self ) + + self:SetTall( 20 ) + self.Label:SetWide( self:GetWide() * 0.45 ) + + end, + + Setup = function( self, type, vars ) + + self.Container:Clear() + + local Name = "DProperty_" .. type + + self.Inner = self.Container:Add( Name ) + if ( !IsValid( self.Inner ) ) then self.Inner = self.Container:Add( "DProperty_Generic" ) end + + self.Inner:SetRow( self ) + self.Inner:Dock( FILL ) + self.Inner:Setup( vars ) + + -- First copy the value if it was somehow set before Setup() was + self.Inner:SetEnabled( self:IsEnabled() ) + + -- Then override our methods so they affect Inner element instead + self.IsEnabled = function( self ) + return self.Inner:IsEnabled() + end + self.SetEnabled = function( self, b ) + self.Inner:SetEnabled( b ) + end + + end, + + SetValue = function( self, val ) + + -- + -- Don't update the value if our cache'd value is the same. + -- + if ( self.CacheValue && self.CacheValue == val ) then return end + self.CacheValue = val + + if ( IsValid( self.Inner ) ) then + self.Inner:SetValue( val ) + end + + end, + + Paint = function( self, w, h ) + + if ( !IsValid( self.Inner ) ) then return end + + local Skin = self:GetSkin() + local editing = self.Inner:IsEditing() + local disabled = !self.Inner:IsEnabled() || !self:IsEnabled() + + if ( disabled ) then + surface.SetDrawColor( Skin.Colours.Properties.Column_Disabled ) + surface.DrawRect( w * 0.45, 0, w, h ) + elseif ( editing ) then + surface.SetDrawColor( Skin.Colours.Properties.Column_Selected ) + surface.DrawRect( 0, 0, w * 0.45, h ) + end + + surface.SetDrawColor( Skin.Colours.Properties.Border ) + surface.DrawRect( w - 1, 0, 1, h ) + surface.DrawRect( w * 0.45, 0, 1, h ) + surface.DrawRect( 0, h - 1, w, 1 ) + + if ( disabled ) then + self.Label:SetTextColor( Skin.Colours.Properties.Label_Disabled ) + elseif ( editing ) then + self.Label:SetTextColor( Skin.Colours.Properties.Label_Selected ) + else + self.Label:SetTextColor( Skin.Colours.Properties.Label_Normal ) + end + + end + +}, "Panel" ) + +-- +-- The category - contained in a dproperties +-- +local tblCategory = vgui.RegisterTable( { + + Init = function( self ) + + self:Dock( TOP ) + self.Rows = {} + + self.Header = self:Add( "Panel" ) + + self.Label = self.Header:Add( "DLabel" ) + self.Label:Dock( FILL ) + self.Label:SetContentAlignment( 4 ) + + self.Expand = self.Header:Add( "DExpandButton" ) + self.Expand:Dock( LEFT ) + self.Expand:SetSize( 16, 16 ) + self.Expand:DockMargin( 0, 4, 0, 4 ) + self.Expand:SetExpanded( true ) + self.Expand.DoClick = function() + + self.Container:SetVisible( !self.Container:IsVisible() ) + self.Expand:SetExpanded( self.Container:IsVisible() ) + self:InvalidateLayout() + + end + + self.Header:Dock( TOP ) + + self.Container = self:Add( "Panel" ) + self.Container:Dock( TOP ) + self.Container:DockMargin( 16, 0, 0, 0 ) + self.Container.Paint = function( pnl, w, h ) + surface.SetDrawColor( cols.bg ) + surface.DrawRect( 0, 0, w, h ) + end + + end, + + PerformLayout = function( self ) + + self.Container:SizeToChildren( false, true ) + self:SizeToChildren( false, true ) + + local Skin = self:GetSkin() + self.Label:SetTextColor( Skin.Colours.Properties.Title ) + self.Label:DockMargin( 4, 0, 0, 0 ) + + end, + + GetRow = function( self, name, bCreate ) + + if ( IsValid( self.Rows[ name ] ) ) then return self.Rows[ name ] end + if ( !bCreate ) then return end + + local row = self.Container:Add( tblRow ) + + row.Label:SetText( name ) + + self.Rows[ name ] = row + + return row + + end, + + Paint = function( self, w, h ) + + surface.SetDrawColor( cols.bg ) + surface.DrawRect( 0, 0, w, h ) + + end + +}, "Panel" ) + +--[[--------------------------------------------------------- + DProperties +-----------------------------------------------------------]] + +local PANEL = {} + +function PANEL:Init() + + self.Categories = {} + +end + +-- +-- Size to children vertically +-- +function PANEL:PerformLayout() + + self:SizeToChildren( false, true ) + +end + +function PANEL:Clear() + self:GetCanvas():Clear() +end + +function PANEL:GetCanvas() + + if ( !IsValid( self.Canvas ) ) then + + self.Canvas = self:Add( "DScrollPanel" ) + self.Canvas:Dock( FILL ) + + end + + return self.Canvas + +end + +-- +-- Get or create a category +-- +function PANEL:GetCategory( name, bCreate ) + + local cat = self.Categories[ name ] + if ( IsValid( cat ) ) then return cat end + + if ( !bCreate ) then return end + + cat = self:GetCanvas():Add( tblCategory ) + cat.Label:SetText( name ) + self.Categories[ name ] = cat + return cat + +end + +-- +-- Creates a row under the specified category. +-- You should then call :Setup on the row. +-- +function PANEL:CreateRow( category, name ) + + local cat = self:GetCategory( category, true ) + return cat:GetRow( name, true ) + +end + +derma.DefineControl( "DProperties", "", PANEL, "Panel" ) diff --git a/octolib/addon/lua/vgui/dtabslist.lua b/octolib/addon/lua/vgui/dtabslist.lua new file mode 100644 index 0000000..057f768 --- /dev/null +++ b/octolib/addon/lua/vgui/dtabslist.lua @@ -0,0 +1,79 @@ +local PANEL = {} + +AccessorFunc(PANEL, 'menuList', 'MenuList') +AccessorFunc(PANEL, 'scr', 'Canvas') + +function PANEL:Init() + + self:SetPaintBackground(false) + self.menuList = self:Add 'DListView' + self.menuList:Dock(LEFT) + self.menuList:SetWide(150) + self.menuList:AddColumn('Icon'):SetFixedWidth(48) + self.menuList:AddColumn('Name') + self.menuList:SetHideHeaders(true) + self.menuList:SetDataHeight(42) + self.menuList:DockMargin(2, 7, 0, 3) + self.menuList:SetMultiSelect(false) + + self.menuList.OnRowSelected = function(_, _, row) + if IsValid(self.scr) then self.scr:Remove() end + self.scr = self:Add 'DScrollPanel' + self.scr:Dock(FILL) + self.scr:DockMargin(5, 3, 5, 0) + self.scr.pnlCanvas:DockPadding(20, 5, 25, 20) + + local container = row.build(self.scr) + if IsValid(container) then + self.scr:Remove() + container:SetParent(self) + container:Dock(FILL) + container:DockMargin(15, 5, 5, 5) + self.scr = container + end + end + + self.RecalcWidth = octolib.func.debounce(function() + local maxWidth = 0 + surface.SetFont('DermaDefault') + local iconWidth = self.menuList:ColumnWidth(1) + for _, row in ipairs(self.menuList:GetLines()) do + maxWidth = math.max(maxWidth, iconWidth + select(1, surface.GetTextSize(row:GetValue(2)))) + end + self.menuList:SetWide(maxWidth + 25) + end, 0) + +end + +local function paintIconInCenter(self, w, h) + local mat = self:GetMaterial() + local matW, matH = math.min(mat:Width(), 32), math.min(mat:Height(), 32) + surface.SetMaterial(mat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect((w-matW) / 2, (h-matH) / 2, matW, matH) +end +function PANEL:AddTab(name, icon, build) + local iconPnl = vgui.Create 'DImage' + iconPnl:SetImage(icon:find('/') and icon or octolib.icons.silk32(icon)) + + iconPnl.Paint = paintIconInCenter + local line = self.menuList:AddLine(iconPnl, name) + line.build = build + self:RecalcWidth() + return line +end + +function PANEL:SelectFirstTab() + self.menuList:SelectFirstItem() +end + +function PANEL:SelectTab(index) + self.menuList:SelectItem(self.menuList:GetLine(index)) +end + +function PANEL:Clear() + self.menuList:Clear() + if IsValid(self.scr) then self.scr:Remove() end +end + +vgui.Register('DTabsList', PANEL, 'DPanel') diff --git a/octolib/addon/lua/weapons/gmod_tool/stools/advremover.lua b/octolib/addon/lua/weapons/gmod_tool/stools/advremover.lua new file mode 100644 index 0000000..f208407 --- /dev/null +++ b/octolib/addon/lua/weapons/gmod_tool/stools/advremover.lua @@ -0,0 +1,112 @@ +TOOL.Category = 'octolib' +TOOL.Name = 'Advanced Remover' + +TOOL.Information = { + { name = 'left' }, + { name = 'right' }, +} + +octolib.vars.init('tools.advremover.radius', 100) +octolib.vars.init('tools.advremover.own', true) +octolib.vars.init('tools.advremover.ignoreWeapons', true) + +local function DoRemoveEntity(ent) + + if (!IsValid(ent) || ent:IsPlayer()) then return false end + + -- Nothing for the client to do here + if (CLIENT) then return true end + + -- Remove all constraints (this stops ropes from hanging around) + constraint.RemoveAll(ent) + + -- Remove it properly in 1 second + timer.Simple(1, function() if (IsValid(ent)) then ent:Remove() end end) + + -- Make it non solid + ent:SetNotSolid(true) + ent:SetMoveType(MOVETYPE_NONE) + ent:SetNoDraw(true) + + -- Send Effect + local ed = EffectData() + ed:SetOrigin(ent:GetPos()) + ed:SetEntity(ent) + util.Effect('entity_remove', ed, true, true) + + return true + +end + +local ignoreClasses = octolib.array.toKeys{ 'player', 'predicted_viewmodel' } + +function TOOL:LeftClick(trace) + + if SERVER then + local ply = self:GetOwner() + ply:GetClientVar({ + 'tools.advremover.radius', + 'tools.advremover.own', + 'tools.advremover.ignoreWeapons', + }, function(vars) + for _, ent in ipairs(ents.FindInSphere(trace.HitPos, math.Clamp(vars['tools.advremover.radius'], 1, 1000))) do + if hook.Run('CanTool', ply, octolib.entity.dummyTrace(ent), 'advremover') == false then continue end + if ignoreClasses[ent:GetClass()] then continue end + if vars['tools.advremover.own'] and ent:CPPIGetOwner() ~= ply then continue end + if vars['tools.advremover.ignoreWeapons'] and ent:IsWeapon() and IsValid(ent:GetOwner()) then continue end + + DoRemoveEntity(ent) + end + end) + end + + return true + +end + +local enabled = false +function TOOL:RightClick() + + if SERVER or not IsFirstTimePredicted() then return end + + enabled = not enabled + + if enabled then + hook.Add('PostDrawTranslucentRenderables', 'octolib.advremover', function() + local pos = LocalPlayer():GetEyeTrace().HitPos + if not pos then return end + + local r = octolib.vars.get('tools.advremover.radius') + local steps = math.Clamp(math.floor(math.pow(r, 0.5)), 6, 50) + + render.DrawWireframeSphere(pos, r, steps, steps, ColorAlpha(color_white, 10)) + render.DrawWireframeSphere(pos, r, steps, steps, color_white, true) + end) + else + hook.Remove('PostDrawTranslucentRenderables', 'octolib.advremover') + end + +end + +function TOOL:Holster() + + if SERVER then return end + + hook.Remove('PostDrawTranslucentRenderables', 'octolib.advremover') + +end + +function TOOL:BuildCPanel() + + octolib.vars.slider(self, 'tools.advremover.radius', 'Радиус', 1, 1000, 0) + octolib.vars.checkBox(self, 'tools.advremover.own', 'Только мои энтити') + octolib.vars.checkBox(self, 'tools.advremover.ignoreWeapons', 'Игнорировать оружие в руках') + +end + +if CLIENT then + language.Add('Tool.advremover.name', 'Advanced Remover') + language.Add('Tool.advremover.desc', 'Удалитель с плюшками') + language.Add('Tool.advremover.left', 'Удалить в радиусе') + language.Add('Tool.advremover.left', 'Включить предпросмотр зоны') +end diff --git a/octolib/addon/lua/weapons/octolib_custom/cl_init.lua b/octolib/addon/lua/weapons/octolib_custom/cl_init.lua new file mode 100644 index 0000000..d8e5db3 --- /dev/null +++ b/octolib/addon/lua/weapons/octolib_custom/cl_init.lua @@ -0,0 +1,49 @@ +include 'shared.lua' + +function SWEP:RemoveModel() + if IsValid(self.csEnt) then self.csEnt:Remove() end + octolib.manipulateBones(self:GetOwner(), nil, 0.4) +end + +function SWEP:DrawWorldModel() + + self:SetHoldType(self.HoldType) + + local worldModel = self:GetNetVar('WorldModel') + if worldModel and (self.lastMdl ~= worldModel or not IsValid(self.csEnt)) then + self:RemoveModel() + + local ent = ClientsideModel(worldModel.model) + local ply = self:GetOwner() + ent:SetParent(ply, ply:LookupAttachment(worldModel.attachment or 'anim_attachment_rh')) + ent:SetLocalPos(worldModel.pos or Vector()) + ent:SetLocalAngles(worldModel.ang or Angle()) + if worldModel.color then ent:SetColor(worldModel.color) end + if worldModel.skin then ent:SetSkin(worldModel.skin) end + if worldModel.material then ent:SetMaterial(worldModel.material) end + if worldModel.scale then ent:SetModelScale(worldModel.scale) end + if worldModel.bodyGroups then + for k, v in pairs(worldModel.bodyGroups) do + ent:SetBodygroup(k, v) + end + end + + self.csEnt = ent + self.lastMdl = worldModel + + if worldModel.bones then + octolib.manipulateBones(ply, worldModel.bones, 0.4) + end + end + +end + +function SWEP:Holster() + + self:RemoveModel() + return true + +end + +SWEP.OwnerChanged = SWEP.RemoveModel +SWEP.OnRemove = SWEP.RemoveModel diff --git a/octolib/addon/lua/weapons/octolib_custom/init.lua b/octolib/addon/lua/weapons/octolib_custom/init.lua new file mode 100644 index 0000000..8905ece --- /dev/null +++ b/octolib/addon/lua/weapons/octolib_custom/init.lua @@ -0,0 +1,40 @@ +AddCSLuaFile 'shared.lua' +AddCSLuaFile 'cl_init.lua' +include 'shared.lua' + +function SWEP:SetWorldModel(model, data) + + self.WorldModel = model + self.WorldModelPos = data.pos or self.WorldModelPos + self.WorldModelAng = data.ang or self.WorldModelAng + + local worldModel = { model = model } + table.Merge(worldModel, data) + self:SetNetVar('WorldModel', worldModel) + +end + +function SWEP:Initialize() + + if not self:GetNetVar('WorldModel') then + self.WorldModelBodyGroups.BaseClass = nil -- WHAT THE FUCK?! + self:SetWorldModel(self.WorldModel, { + pos = self.WorldModelPos, + ang = self.WorldModelAng, + attachment = self.WorldModelAtt, + scale = self.WorldModelScale, + bones = self.WorldModelBones, + skin = self.WorldModelSkin, + bodyGroups = self.WorldModelBodyGroups, + }) + end + +end + +function SWEP:Deploy() + self:SetHoldType(self.HoldType) +end + +function SWEP:Holster() + return true +end diff --git a/octolib/addon/lua/weapons/octolib_custom/shared.lua b/octolib/addon/lua/weapons/octolib_custom/shared.lua new file mode 100644 index 0000000..475de62 --- /dev/null +++ b/octolib/addon/lua/weapons/octolib_custom/shared.lua @@ -0,0 +1,34 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.Base = 'weapon_base' +SWEP.Category = 'octolib' +SWEP.PrintName = 'Кастомная модель' +SWEP.Instructions = '' +SWEP.Slot = 0 +SWEP.SlotPos = 9 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = false +SWEP.ViewModel = '' +SWEP.Spawnable = false +SWEP.AdminOnly = false + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '' + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = '' + +SWEP.HoldType = 'knife' +SWEP.WorldModel = 'models/props_lab/cleaver.mdl' +SWEP.WorldModelAtt = 'anim_attachment_rh' +SWEP.WorldModelPos = Vector(-3.1, -0.6, 0.1) +SWEP.WorldModelAng = Angle(-18.4, 0, 0) +SWEP.WorldModelScale = 1 +SWEP.WorldModelSkin = 0 +SWEP.WorldModelBodyGroups = {} diff --git a/octolib/config.example.ts b/octolib/config.example.ts new file mode 100644 index 0000000..853ce84 --- /dev/null +++ b/octolib/config.example.ts @@ -0,0 +1,39 @@ +import Config from './octolib/core/config' + +const config: Config = { + dev: true, + serverGroupID: 'test', + serverID: 'test', + + tickrate: 66, + port: 27015, + language: 'en', + workshopCollection: -1, + gamemode: 'sandbox', + map: 'gm_construct', + maxPlayers: 16, + hibernateThink: true, + + octoservicesURL: 'https://octothorp.team/api', + + serverAccount: '', + steamKey: '', + imgurKey: '', + + keys: {}, + + webhooks: {}, + + db: { + host: 'localhost', + user: 'root', + pass: '', + port: 3306, + + main: 'test', + admin: 'test', + shop: 'test', + }, +} + +export default config diff --git a/octolib/core/config.d.ts b/octolib/core/config.d.ts new file mode 100644 index 0000000..eb4fff1 --- /dev/null +++ b/octolib/core/config.d.ts @@ -0,0 +1,51 @@ +export default interface Config { + dev: boolean + serverGroupID: string + serverID: string + + tickrate: number + port: number + language: string + workshopCollection: number + gamemode: string + map: string + maxPlayers: number + hibernateThink: boolean + + /** octoservices api base url */ + octoservicesURL: string + + /** Steam account token from https://steamcommunity.com/dev/managegameservers */ + serverAccount: string + steamKey: string + imgurKey?: string + + keys: { + services?: string + logs?: string + cats?: string + test?: string + } + + webhooks: { + cats?: string + cheats?: string + error?: string + unban?: string + } + + db: { + host: string + user: string + pass: string + port: number + socket?: string + + /** Main server database name */ + main: string + /** Admin (ranks, bans, etc.) database name */ + admin: string + /** Donate shop database name */ + shop: string + } +} diff --git a/octolib/core/gameServer.ts b/octolib/core/gameServer.ts new file mode 100644 index 0000000..9edd232 --- /dev/null +++ b/octolib/core/gameServer.ts @@ -0,0 +1,51 @@ +import '../scripts/generate-env' + +import childProcess from 'child_process' +import { info, ok, fail } from '../core/log' +import config from '../../config' + +export const start = () => { + info('Launching game server...') + const exePath = process.platform === 'win32' ? '../srcds.exe' : '../srcds_run' + const gameServer = childProcess.spawn(exePath, [ + '-allowlocalhttp', + '-console', + '-game', 'garrysmod', + '-tickrate', config.tickrate.toString(), + '-port', config.port.toString(), + '+sv_setsteamaccount', config.serverAccount, + '+host_workshop_collection', config.workshopCollection.toString(), + '+gmod_language', config.language, + '+gamemode', config.gamemode, + '+map', config.map, + '+maxplayers', config.maxPlayers.toString(), + '+sv_hibernate_think', config.hibernateThink ? '1' : '0', + ], { + stdio: 'inherit', + }) + + gameServer.on('close', code => { + if (code === 0) + ok('Game server shut down') + else + fail('Game server exited with non-ok code ' + code) + }) + + const killServer = () => { + if (!gameServer.killed) { + gameServer.kill() + ok('Killed game server') + } + } + + process.on('beforeExit', killServer) + process.on('uncaughtException', error => { + killServer() + throw error + }) + + if (gameServer.stdin) + process.stdin.pipe(gameServer.stdin) + + return gameServer +} diff --git a/octolib/core/log.ts b/octolib/core/log.ts new file mode 100644 index 0000000..951adb2 --- /dev/null +++ b/octolib/core/log.ts @@ -0,0 +1,16 @@ +import day from 'dayjs' + +export const empty = (lines: number = 1) => + console.log('\n'.repeat(lines - 1)) + +export const now = () => + `[${day().format('MM.DD HH:mm:ss')}]` + +export const info = (text: any) => + console.log(`${now()} ${text}`) + +export const ok = (text: any) => + console.log(`${now()} ${'✔'.green} ${text}`) + +export const fail = (text: any) => + console.log(`${now()} ${'❌'.red} ${text}`) diff --git a/octolib/core/socket/client.ts b/octolib/core/socket/client.ts new file mode 100644 index 0000000..af0394d --- /dev/null +++ b/octolib/core/socket/client.ts @@ -0,0 +1,27 @@ +import WebSocket from 'ws' +import { Message } from './message' +import { SocketServer } from './server' + +export class ClientSocket extends WebSocket { + public client?: Client +} + +export class Client { + public clientID: string + public socket: ClientSocket + public server: SocketServer + + constructor(server: SocketServer, socket: ClientSocket, id: string) { + this.server = server + this.socket = socket + this.clientID = id + } + + send(message: Message) { + this.socket.send(message.toJSON()) + } + + request(method: string, data?: any) { + return this.server.request(this, method, data) + } +} diff --git a/octolib/core/socket/message.ts b/octolib/core/socket/message.ts new file mode 100644 index 0000000..f2c7ad2 --- /dev/null +++ b/octolib/core/socket/message.ts @@ -0,0 +1,53 @@ +/** Enum for socket message types */ +export enum MessageType { + Unknown = -1, + Request = 0, + Response = 1, +} + +export class Message { + public type: MessageType = MessageType.Unknown + public id: number = -1 + public method: string = '' + public data: any + + constructor (type: MessageType, msgID: number, methodOrData: string | any, data?: any) { + this.type = type + this.id = msgID + + switch (type) { + case MessageType.Request: + this.method = methodOrData + this.data = data + break + case MessageType.Response: + this.data = methodOrData + break + default: throw new Error('Unknown message type') + } + } + + /** Create request message object */ + static request(id: number, method: string, data: any): Message { + return new this(MessageType.Request, id, method, data) + } + + /** Create response message object */ + static response(id: number, data: any): Message { + return new this(MessageType.Response, id, data) + } + + serialize () { + switch (this.type) { + case MessageType.Request: + return [this.type, this.id, this.method, this.data] + case MessageType.Response: + return [this.type, this.id, this.data] + default: throw new Error('Data corrupted') + } + } + + toJSON () { + return JSON.stringify(this.serialize()) + } +} diff --git a/octolib/core/socket/server.ts b/octolib/core/socket/server.ts new file mode 100644 index 0000000..fb6b872 --- /dev/null +++ b/octolib/core/socket/server.ts @@ -0,0 +1,173 @@ +import { EventEmitter } from 'events' +import { Server } from 'http' +import WebSocket from 'ws' +import { Client, ClientSocket } from './client' +import { Message, MessageType } from './message' + +type ReplyFunc = (data?: any) => void +type HandlerFunc = (reply: ReplyFunc, client: Client, data?: any) => void +type HandlerAllowFunc = (socket: Client, method: string, data?: any) => boolean +type PendingRequest = [(data: unknown) => void, NodeJS.Timeout] + +export type Handler = { + allow?: string[] | HandlerAllowFunc + handle: HandlerFunc +} + +export class SocketServer extends EventEmitter { + clients: Map = new Map() + server: WebSocket.Server + authTimeout: number = 10 + requestTimeout: number = 15 + heartbeatInterval: number = 30 + + private _nextMessageID = 0 + private _pendingRequests: Map = new Map() + private _requestHandlers: Map = new Map() + private _tokenMap: Map = new Map() + + constructor (server: Server) { + super() + + this.server = new WebSocket.Server({ server }) + this.server.on('connection', (socket: ClientSocket) => this._handleConnection(socket)) + } + + registerToken(clientID: string, token: string) { + this._tokenMap.set(token, clientID) + } + + isConnected(clientID: string) { + return this.clients.has(clientID) + } + + send(clientID: string, msg: Message) { + const payload = msg.toJSON() + this.clients.get(clientID)?.socket.send(payload) + } + + request(clientSelector: string | Client, method: string, data?: any) { + const client = typeof clientSelector === 'string' ? this.clients.get(clientSelector) : clientSelector + const msgID = this._nextMessageID ++ + const promise = new Promise((res, rej) => { + if (!client) { + this._pendingRequests.delete(msgID) + rej('no clients connected') + return + } + + client.send(Message.request(msgID, method, data)) + const timeout = setTimeout(() => { + rej('request timed out') + this._pendingRequests.delete(msgID) + }, this.requestTimeout * 1000) + + this._pendingRequests.set(msgID, [res, timeout]) + }) + + return promise + } + + listen(method: string, handler: Handler) { + this._requestHandlers.set(method, handler) + } + + private _handleConnection(socket: ClientSocket) { + const timeout = setTimeout(() => { + socket.send('Auth timed out.') + socket.close() + }, this.authTimeout * 1000) + + socket.once('message', key => { + clearTimeout(timeout) + + const clientID = this._tokenMap.get(key.toString()) + if (!clientID) { + socket.send('No access.') + socket.close() + return + } + + if (this.isConnected(clientID)) { + socket.send('Client with this ID is already connected.') + socket.close() + return + } + + this._setupClient(clientID, socket) + }) + } + + private _setupClient(clientID: string, socket: ClientSocket) { + const client = new Client(this, socket, clientID) + socket.on('close', reason => this._handleDisconnect(socket, reason.toString())) + socket.on('error', err => this.emit('error', socket, err)) + socket.on('message', msg => this._handleMessage(socket, msg)) + socket.client = client + this.clients.set(clientID, client) + + socket.send('ok') + const heartbeatInterval = setInterval(() => { + this.request(client, '_hb').catch(() => { + clearInterval(heartbeatInterval) + this._handleDisconnect(socket, 'heartbeat timeout') + }) + }, this.heartbeatInterval * 1000) + + this.emit('connected', client) + } + + private _handleDisconnect(socket: ClientSocket, reason: string) { + if (!socket.client) return + + this.clients.delete(socket.client.clientID) + this.emit('disconnected', socket.client, reason) + } + + private _handleMessage(socket: ClientSocket, payload: any) { + const client = socket.client + if (!client) return + + let msg + try { + const [type, msgID, methodOrData, data] = JSON.parse(payload) + msg = new Message(type, msgID, methodOrData, data) + } catch (e: any) { + socket.send(e) + return + } + + switch (msg.type) { + case MessageType.Request: this._handleRequest(client, msg); break; + case MessageType.Response: this._handleReply(client, msg); break; + default: client.send(Message.response(msg.id, { error: 'Unknown message type.' })); break; + } + } + + private _handleRequest(client: Client, msg: Message) { + const handler = this._requestHandlers.get(msg.method) + if (!handler) return client.send(Message.response(msg.id, { error: 'Unknown method.' })) + + if (handler.allow) { + if ( + (handler.allow.constructor === Array && handler.allow.indexOf(client.clientID) === -1) || + (typeof(handler.allow) === 'function' && !handler.allow(client, msg.method, msg.data)) + ) + return client.send(Message.response(msg.id, { error: 'No access.' })) + } + + handler.handle(data => client.send(Message.response(msg.id, data)), client, msg.data) + this.emit('request', client, msg.id, msg.method, msg.data) + } + + private _handleReply(client: Client, msg: Message) { + const request = this._pendingRequests.get(msg.id) + if (!request) return + + const [resolve, timeout] = request + this._pendingRequests.delete(msg.id) + + clearTimeout(timeout) + resolve(msg.data) + } +} diff --git a/octolib/core/tester.ts b/octolib/core/tester.ts new file mode 100644 index 0000000..59d2f6a --- /dev/null +++ b/octolib/core/tester.ts @@ -0,0 +1,68 @@ +import express from 'express' +import EventEmitter from 'events' +import { SocketServer } from './socket/server' +import { Client } from './socket/client' + +type GameResult = { + /** Error message, if occured */ + error: string + /** Data returned from game */ + returns: any +} + +class Tester extends EventEmitter { + + /** Is game server connected to socket */ + isConnected: boolean = false + /** Our socket-server */ + server: SocketServer + /** Game's WebSocket client */ + client?: Client + + constructor(server: SocketServer) { + super() + + this.server = server + + this.server.on('connected', (client: Client) => { + this.client = client + this.isConnected = true + this.emit('connected', client) + }) + + this.server.on('disconnected', client => { + if (client !== this.client) return + delete this.client + this.isConnected = false + this.emit('disconnected', client) + }) + } + + /** Wait for server connections */ + waitForConnection(): Promise { + return new Promise((res, rej) => { + if (this.isConnected) return res() + this.once('connected', res) + }) + } + + /** Execute lua code on server and return results (if returned from lua) */ + exec(code: any): Promise { + if (!this.client) throw new Error('No socket connected') + return this.server.request(this.client, 'eval', { code }) as Promise + } + + /** Run method on connected game client */ + request(method: string, data?: any): Promise { + if (!this.client) throw new Error('No socket connected') + return this.server.request(this.client, method, data) + } + +} + +export const app = express() +export const http = app.listen(8888) +export const socket = new SocketServer(http) +export const tester = new Tester(socket) + +app.use(express.text() as any) diff --git a/octolib/package.json b/octolib/package.json new file mode 100644 index 0000000..fb24fae --- /dev/null +++ b/octolib/package.json @@ -0,0 +1,38 @@ +{ + "name": "octolib", + "version": "1.0.0", + "description": "", + "private": true, + "scripts": { + "setup": "npx ts-node scripts/setup.ts", + "server": "npx ts-node scripts/start-server.ts", + "dev": "npx ts-node scripts/dev.ts", + "test": "npx ts-node scripts/test.ts", + "reset": "npx ts-node scripts/reset.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/octothorp-team/gmod-octolib" + }, + "author": "chelog", + "license": "ISC", + "dependencies": { + "body-parser": "^1.19.0", + "colorts": "^0.1.63", + "dayjs": "^1.10.5", + "express": "^4.17.1", + "mysql2": "^2.2.5", + "ws": "^7.4.6" + }, + "devDependencies": { + "@types/express": "^4.17.12", + "@types/node": "^15.12.1", + "@types/ws": "^7.4.4", + "eslint": "^7.28.0", + "ora": "^5.4.0", + "sudo-prompt": "^9.2.1", + "ts-node": "^10.0.0", + "ts-node-dev": "^1.1.6", + "typescript": "^4.3.2" + } +} diff --git a/octolib/scripts/_prerun.ts b/octolib/scripts/_prerun.ts new file mode 100644 index 0000000..5fd7540 --- /dev/null +++ b/octolib/scripts/_prerun.ts @@ -0,0 +1,5 @@ +import 'colorts/lib/string' + +import dayjs from 'dayjs' +import 'dayjs/locale/ru' +dayjs.locale('ru') diff --git a/octolib/scripts/dev.ts b/octolib/scripts/dev.ts new file mode 100644 index 0000000..15924f4 --- /dev/null +++ b/octolib/scripts/dev.ts @@ -0,0 +1,23 @@ +import './_prerun' + +import { resolve } from 'path' +import { info, ok } from '../core/log' +import { app, tester } from '../core/tester' +import config from '../../config' + +import './start-server' + +const htmlPath = resolve(__dirname, '../sender/index.html') +app.get('/', (req, res) => res.sendFile(htmlPath)) +app.get('/status', (req, res) => res.send(tester.isConnected)) +app.post('/eval', async (req, res) => { + try { + res.send(await tester.exec(req.body)) + } catch (e: any) { + res.send(e.toString()) + } +}) + +info('Waiting for game server to connect...') +tester.server.registerToken('main', config.keys.services!) +tester.waitForConnection().then(() => ok('Game server connected! Open http://localhost:8888 to send code')) diff --git a/octolib/scripts/generate-env.ts b/octolib/scripts/generate-env.ts new file mode 100644 index 0000000..4b227f2 --- /dev/null +++ b/octolib/scripts/generate-env.ts @@ -0,0 +1,10 @@ +import fs from 'fs' +import path from 'path' +import config from '../../config' + +const envPath = path.resolve(__dirname, '../../.env.json') +fs.writeFileSync(envPath, JSON.stringify(config, null, 2)) +import './generate-lsac' + +// allow dynamic imports +export default {} diff --git a/octolib/scripts/generate-lsac.ts b/octolib/scripts/generate-lsac.ts new file mode 100644 index 0000000..8829fd3 --- /dev/null +++ b/octolib/scripts/generate-lsac.ts @@ -0,0 +1,16 @@ +import fs from 'fs' +import path from 'path' +import config from '../../config' + +const envPath = path.resolve(__dirname, '../../garrysmod/addons/util-lsac') +if (fs.existsSync(envPath)) + fs.writeFileSync(path.resolve(envPath, 'mysql.txt'), JSON.stringify({ + DBIPAddress: config.db.host, + DBPort: config.db.port, + DBUsername: config.db.user, + DBPassword: config.db.pass, + DBSchema: config.db.main, + })) + +// allow dynamic imports +export default {} diff --git a/octolib/scripts/reset.ts b/octolib/scripts/reset.ts new file mode 100644 index 0000000..bc4e13b --- /dev/null +++ b/octolib/scripts/reset.ts @@ -0,0 +1,41 @@ +import './_prerun' + +import fs from 'fs' +import path from 'path' +import mysql from 'mysql2/promise' +import ora from 'ora' +import config from '../../config' + +async function reset() { + const spinner = ora({ + spinner: 'moon', + }) + + spinner.start('Resetting database...') + try { + const conn = await mysql.createConnection({ + host: config.db.host, + port: config.db.port, + user: config.db.user, + password: config.db.pass, + }) + await conn.execute(`drop database if exists \`${config.db.main}\``) + await conn.execute(`create database \`${config.db.main}\` collate 'utf8mb4_unicode_ci'`) + conn.destroy() + spinner.succeed('Database reset') + } catch (e: any) { + spinner.fail('Database reset failed') + throw e + } + + spinner.start('Resetting data folder...') + try { + const dataPath = path.resolve(__dirname, '../../garrysmod/data') + if (fs.existsSync(dataPath)) + fs.rmdirSync(dataPath, { recursive: true }) + spinner.succeed('Data folder reset') + } catch (e: any) { + spinner.fail('Data folder reset failed') + throw e + } +} diff --git a/octolib/scripts/setup.ts b/octolib/scripts/setup.ts new file mode 100644 index 0000000..5297643 --- /dev/null +++ b/octolib/scripts/setup.ts @@ -0,0 +1,88 @@ +import './_prerun' + +import fs from 'fs' +import path from 'path' +import sudo from 'sudo-prompt' +import ora from 'ora' +import mysql from 'mysql2/promise' +import Config from '../core/config' + +async function setup() { + const spinner = ora({ + spinner: 'moon', + }) + + // config + let config: Config + spinner.start('Checking config file...') + try { + const configPath = path.resolve(__dirname, '../../config.ts') + if (!fs.existsSync(configPath)) { + const configExampleProjectPath = path.resolve(__dirname, '../../config.example.ts') + if (fs.existsSync(configExampleProjectPath)) + // if available use project example + fs.copyFileSync(configExampleProjectPath, configPath) + else + // if not use octolib example + fs.copyFileSync(path.resolve(__dirname, '../config.example.ts'), configPath) + } + config = (await import(configPath)).default as Config + if (!config.db) throw new Error('Config file is corrupted') + spinner.succeed('Config file is OK') + } catch (e: any) { + spinner.fail('Config file check failed') + throw e + } + + // .env + spinner.start('Generating environment file...') + try { + await import('./generate-env') + spinner.succeed('Environment file is OK') + } catch { + spinner.succeed('Environment file generation failed') + } + + // gmod addon + spinner.start('Checking gmod addon...') + try { + const addonPath = path.resolve(__dirname, '../../garrysmod/addons/local-octolib') + const addonSource = path.resolve(__dirname, '../addon') + if (!fs.existsSync(addonPath)) { + if (process.platform === 'win32') + sudo.exec(`mklink /D "${addonPath}" "${addonSource}"`, { + name: 'octolib', + }, function(error, stdout, stderr) { + if (error) { + spinner.fail('Could not install gmod addon') + throw error + } + console.log('stdout: ' + stdout) + }) + else + fs.symlinkSync(addonSource, addonPath) + } + spinner.succeed('GMod addon is OK') + } catch (e: any) { + spinner.fail('GMod addon check failed') + throw(e) + } + + // database + spinner.start('Checking database...') + try { + const conn = await mysql.createConnection({ + host: config.db.host, + port: config.db.port, + user: config.db.user, + password: config.db.pass, + }) + await conn.execute(`create database if not exists \`${config.db.main}\` collate 'utf8mb4_unicode_ci'`) + conn.destroy() + spinner.succeed('Database is OK') + } catch (e: any) { + spinner.fail('Database check failed') + throw e + } +} +setup() diff --git a/octolib/scripts/start-server.ts b/octolib/scripts/start-server.ts new file mode 100644 index 0000000..26d3d9d --- /dev/null +++ b/octolib/scripts/start-server.ts @@ -0,0 +1,4 @@ +import './_prerun' + +import { start } from '../core/gameServer' +start() diff --git a/octolib/scripts/test.ts b/octolib/scripts/test.ts new file mode 100644 index 0000000..660cda3 --- /dev/null +++ b/octolib/scripts/test.ts @@ -0,0 +1,71 @@ +import './_prerun' + +import { empty, fail, info, ok } from '../core/log' +import { tester } from '../core/tester' +import { start } from '../core/gameServer' +import config from '../../config' + +type TestSection = { + name: string + level: number +} + +type TestResult = { + name: string + time: number + error?: string +} + +const gameServer = process.argv[2] == 'server' ? start() : undefined + +info('Waiting for game server to connect...') +tester.server.registerToken('main', config.keys.services!) + +let curLevel = -1 +let noErrors = true +tester.server.listen('printSection', { + handle(reply, client, section: TestSection) { + reply() + + curLevel = section.level + const prefix = ' '.repeat(curLevel) + info(`${prefix}${section.name.underline.bold}`) + } +}) + +tester.server.listen('testResult', { + handle(reply, client, result: TestResult) { + reply() + + const prefix = ' '.repeat(curLevel + 1) + if (result.error) { + info(`${prefix}${'❌'.red} ${result.name} (${Math.round(result.time * 1000)}ms):\n${result.error}`) + noErrors = false + } + else + info(`${prefix}${'✔'.green} ${result.name} (${Math.round(result.time * 1000)}ms)`) + }, +}) + +tester.server.listen('finish', { + handle(reply, client, data) { + reply() + empty() + gameServer?.kill() + + if (noErrors) { + ok('All tests finished successfully!') + process.exit(0) + } + else { + fail('Some tests failed. See the report above') + process.exit(1) + } + }, +}) + +tester.waitForConnection().then(() => { + ok('Game server connected! Starting tests...') + empty() + tester.request('runTests') +}) diff --git a/octolib/sender/index.html b/octolib/sender/index.html new file mode 100644 index 0000000..3735f22 --- /dev/null +++ b/octolib/sender/index.html @@ -0,0 +1,120 @@ + + + + + + Send lua to GMod server + + + + + + + + + + + + + + + +
-- use 'return' statement or global function 'reply(data)' to get response
+
+ + + diff --git a/octolib/tsconfig.json b/octolib/tsconfig.json new file mode 100644 index 0000000..e2d9d9f --- /dev/null +++ b/octolib/tsconfig.json @@ -0,0 +1,67 @@ +{ + "compilerOptions": { + /* Basic Options */ + // "incremental": true, + "target": "es6", + "module": "commonjs", + "lib": ["ESNext"], + // "allowJs": true, + // "checkJs": true, + // "jsx": "preserve", + // "declaration": true, + // "declarationMap": true, + "sourceMap": true, + // "outFile": "./", + "outDir": "./dist", + // "rootDir": "./", + // "composite": true, + // "tsBuildInfoFile": "./", + "removeComments": true, + // "noEmit": true, + // "importHelpers": true, + // "downlevelIteration": true, + // "isolatedModules": true, + + /* Strict Type-Checking Options */ + "strict": true, + // "noImplicitAny": true, + // "strictNullChecks": true, + // "strictFunctionTypes": true, + // "strictBindCallApply": true, + // "strictPropertyInitialization": true, + // "noImplicitThis": true, + "alwaysStrict": true, + + /* Additional Checks */ + // "noUnusedLocals": true, + // "noUnusedParameters": true, + "noImplicitReturns": true, + // "noFallthroughCasesInSwitch": true, + + /* Module Resolution Options */ + "moduleResolution": "node", + // "baseUrl": ".", + // "paths": {}, + // "rootDirs": [], + // "typeRoots": [], + // "types": [], + // "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "preserveSymlinks": true, + // "allowUmdGlobalAccess": true, + + /* Source Map Options */ + // "sourceRoot": "", + // "mapRoot": "", + // "inlineSourceMap": true, + // "inlineSources": true, + + /* Experimental Options */ + // "experimentalDecorators": true, + // "emitDecoratorMetadata": true, + + /* Advanced Options */ + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/server.cfg b/server.cfg new file mode 100644 index 0000000..f302c8d --- /dev/null +++ b/server.cfg @@ -0,0 +1,86 @@ +// Please do not set RCon in here, use the startup parameters. + +hostname "[#] Строим Доброград" +sv_password "8" +sv_visiblemaxplayers 88 +sv_loadingurl "https://old.octothorp.team/loading/dobrograd/?server=nd" +sv_downloadurl "https://cdn.octothorp.team/gmod" + +// Steam Server List Settings +sv_region "255" +sv_lan "0" +sv_max_queries_sec_global "30000" +sv_max_queries_window "45" +sv_max_queries_sec "5" +sv_location "ru" + +// Server Limits +sbox_maxprops 2048 +sbox_maxragdolls 999 +sbox_maxnpcs 999 +sbox_maxballoons 999 +sbox_maxeffects 999 +sbox_maxdynamite 999 +sbox_maxlamps 999 +sbox_maxlights 999 +sbox_maxthrusters 999 +sbox_maxwheels 999 +sbox_maxhoverballs 999 +sbox_maxvehicles 999 +sbox_maxbuttons 999 +sbox_maxsents 999 +sbox_maxemitters 999 +sbox_godmode 0 +sbox_noclip 0 +gmod_suit 0 + +sv_crazyphysics_warning 1 +sv_crazyphysics_defuse 1 +sv_crazyphysics_remove 1 + +// Network Settings - Please keep these set to default. + +sv_minrate 2097152 +sv_maxrate 2097152 +sv_maxupdaterate 16 +sv_minupdaterate 16 +sv_maxcmdrate 16 +sv_mincmdrate 16 +sv_allowupload 0 +sv_allowdownload 0 +net_maxfilesize 256 + +gmod_physiterations 2 +net_splitpacket_maxrate 45000 +decalfrequency 12 + +// Execute Ban Files - Please do not edit +exec banned_ip.cfg +exec banned_user.cfg + +// Add custom lines under here +sv_hibernate_think 1 + +braxnet_atm_startmoney 5000 +braxnet_atm_salary 0 +brax_crowbar_mode 2 +brax_crowbar_money_atm 5000 + +sitting_can_sit_on_players 0 +sitting_can_damage_players_sitting 1 + +enhanceddamage_headdamagescale 10 +enhanceddamage_armdamagescale 0.5 +enhanceddamage_legdamagescale 0.5 +enhanceddamage_chestdamagescale 2 +enhanceddamage_stomachdamagescale 1 +enhanceddamage_nutsdamagescale 2.5 +enhanceddamage_handdamagescale 0.25 +enhanceddamage_enablesounds 0 +enhanceddamage_handdropchance 0 +enhanceddamage_armdropchance 0 + +sv_simfphys_devmode 0 +sbox_maxtextscreens 3 +sbox_maxkeypads 5 +sbox_maximgscreens 500 \ No newline at end of file diff --git a/server.ci.cfg b/server.ci.cfg new file mode 100644 index 0000000..58d4262 --- /dev/null +++ b/server.ci.cfg @@ -0,0 +1,53 @@ +sv_hibernate_think 1 + +sv_maxupdaterate 16 +sv_minupdaterate 16 + +sv_allowupload 0 + +hostname "octoteam ci/cd" +sv_password "8" +rcon_password "" +net_maxfilesize 64 + +braxnet_atm_startmoney 5000 +braxnet_atm_salary 0 +brax_crowbar_mode 2 +brax_crowbar_money_atm 5000 + +sbox_maxprops 5000 +sbox_maxkeypads 3 +sbox_maxtextscreens 3 +sbox_maxvehicles 8 +sbox_maxwire_lights 3 +sbox_maxwire_lamps 3 +sbox_maxwire_egps 16 + +sitting_can_sit_on_players 0 +sitting_can_damage_players_sitting 1 + +enhanceddamage_headdamagescale 10 +enhanceddamage_armdamagescale 0.5 +enhanceddamage_legdamagescale 0.5 +enhanceddamage_chestdamagescale 2 +enhanceddamage_stomachdamagescale 1 +enhanceddamage_nutsdamagescale 2.5 +enhanceddamage_handdamagescale 0.25 +enhanceddamage_enablesounds 0 +enhanceddamage_handdropchance 0 +enhanceddamage_armdropchance 0 + +sv_crazyphysics_warning 1 +sv_crazyphysics_defuse 1 +sv_crazyphysics_remove 1 + +sbox_godmode 0 +sbox_noclip 0 + +sv_region 255 +sv_visiblemaxplayers 88 + +sv_simfphys_devmode 0 +sv_allowupload 0 +sv_allowdownload 0 +sv_downloadurl "https://cdn.octothorp.team/gmod" \ No newline at end of file